Skip to content

Commit

Permalink
feat: Handlebars template engine (#12)
Browse files Browse the repository at this point in the history
  • Loading branch information
Seddryck authored Oct 19, 2024
1 parent 3601a60 commit ef4e7c3
Show file tree
Hide file tree
Showing 8 changed files with 133 additions and 21 deletions.
12 changes: 8 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ dotnet tool install -g Didot-cli

## QuickStart

**Didot** is a command-line tool designed for generating files based on templating. It supports *YAML*, *JSON*, and *XML* as source data formats and provides flexibility in templating through both *Scriban* and *DotLiquid* engines. With Didot, you can easily automate file generation by combining structured data from YAML, JSON, or XML files with customizable templates using Scriban or DotLiquid.
**Didot** is a command-line tool designed for generating files based on templating. It supports *YAML*, *JSON*, and *XML* as source data formats and provides flexibility in templating through both *Scriban*, *Liquid* and *Handlebars* templates languages. With Didot, you can easily automate file generation by combining structured data from YAML, JSON, or XML files with customizable templates using Scriban or DotLiquid.

### Supported Data Formats:

Expand All @@ -59,6 +59,10 @@ Didot utilizes some templating engines, which allow for powerful and flexible te
- Secure (no access to system objects), making it ideal for user-generated templates.
- Allows both dynamic and static templating.
- Supports filters, tags, and various control flow structures.
- **Handlebars**: Templates with the `.hbs` extension are parsed using a Handlebars template engine. Handlebars C# port of the popular JavaScript Handlebars templating engine.
- Simple syntax for generating HTML or text files from templates.
- Support for helpers, partial templates, and block helpers.
- Good separation of logic from presentation.

### Command Usage:

Expand All @@ -76,8 +80,8 @@ didot -t template.scriban -s data.yaml -o page.html

In this example:

* template.scriban is the Scriban template file.
* data.yaml is the source file containing the structured data in YAML format.
* result.txt is the output file that will contain the generated content.
* `template.scriban` is the Scriban template file.
* `data.yaml` is the source file containing the structured data in YAML format.
* `page.html` is the output file that will contain the generated content.

Make sure that the template file and source file are correctly formatted and aligned with your data model to produce the desired result.
1 change: 1 addition & 0 deletions src/Didot.Core/Didot.Core.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

<ItemGroup>
<PackageReference Include="DotLiquid" Version="2.2.692" />
<PackageReference Include="Handlebars.Net" Version="2.1.6" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
<PackageReference Include="Scriban" Version="5.10.0" />
<PackageReference Include="YamlDotNet" Version="16.1.3" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ public ITemplateEngine GetTemplateEngine(string extension)
{
".scriban" => new ScribanWrapper(),
".liquid" => new DotLiquidWrapper(),
".hbs" => new HandlebarsWrapper(),
_ => throw new NotSupportedException()
};
}
24 changes: 24 additions & 0 deletions src/Didot.Core/TemplateEngines/HandlebarsWrapper.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using HandlebarsDotNet;

namespace Didot.Core.TemplateEngines;
public class HandlebarsWrapper : ITemplateEngine
{
public string Render(string template, dynamic model)
{
var templateInstance = Handlebars.Compile(template);
return templateInstance(model);
}

public string Render(Stream stream, dynamic model)
{
using var reader = new StreamReader(stream);
var template = reader.ReadToEnd();
return Render(template, model);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ public void Render_SingleProperty_Successful()
public void Render_MultiProperty_Successful()
{
var engine = new DotLiquidWrapper();
var model = new Dictionary<object, object>()
var model = new Dictionary<string, object>()
{ { "Name", "Albert"}, {"Age", 30 } };
var result = engine.Render("Hello {{model.Name}}. You're {{model.Age}} years old.", new { model });
Assert.That(result, Is.EqualTo("Hello Albert. You're 30 years old."));
Expand All @@ -34,9 +34,9 @@ public void Render_MultiProperty_Successful()
public void Render_NestedProperties_Successful()
{
var engine = new DotLiquidWrapper();
var name = new Dictionary<object, object>()
var name = new Dictionary<string, object>()
{ { "First", "Albert"}, {"Last", "Einstein" } };
var model = new Dictionary<object, object>()
var model = new Dictionary<string, object>()
{ { "Name", name}, {"Age", 30 } };
var result = engine.Render("Hello {{model.Name.First}} {{model.Name.Last}}. Your age is {{model.Age}} years old.", new { model });
Assert.That(result, Is.EqualTo("Hello Albert Einstein. Your age is 30 years old."));
Expand All @@ -46,9 +46,9 @@ public void Render_NestedProperties_Successful()
public void Render_ArrayItems_Successful()
{
var engine = new DotLiquidWrapper();
var albert = new Dictionary<object, object>()
var albert = new Dictionary<string, object>()
{ { "Name", "Albert"}, {"Age", 30 } };
var nikola = new Dictionary<object, object>()
var nikola = new Dictionary<string, object>()
{ { "Name", "Nikola"}, {"Age", 50 } };
var model = new[] { albert, nikola };
var result = engine.Render("Hello {{model[0].Name}}. Your colleague is {{model[1].Age}} years old.", new { model });
Expand All @@ -59,9 +59,9 @@ public void Render_ArrayItems_Successful()
public void Render_ArrayLoop_Successful()
{
var engine = new DotLiquidWrapper();
var albert = new Dictionary<object, object>()
var albert = new Dictionary<string, object>()
{ { "Name", "Albert"}, {"Age", 30 } };
var nikola = new Dictionary<object, object>()
var nikola = new Dictionary<string, object>()
{ { "Name", "Nikola"}, {"Age", 50 } };
var model = new[] { albert, nikola };
var result = engine.Render("Hello {% for item in model %}{{ item.Name }}{% if forloop.last == false %}, {% endif %}{% endfor %}!", new { model });
Expand All @@ -72,7 +72,7 @@ public void Render_ArrayLoop_Successful()
public void Render_Stream_Successful()
{
var engine = new DotLiquidWrapper();
var model = new Dictionary<object, object>()
var model = new Dictionary<string, object>()
{ { "Name", "World"} };
using var stream = new MemoryStream(Encoding.UTF8.GetBytes("Hello {{model.Name}}"));
var result = engine.Render(stream, new { model });
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ public class FileBasedTemplateEngineFactoryTests
[Test]
[TestCase(".scriban", typeof(ScribanWrapper))]
[TestCase(".liquid", typeof(DotLiquidWrapper))]
[TestCase(".hbs", typeof(HandlebarsWrapper))]
public void GetSourceParser_Extension_CorrectParser(string extension, Type expected)
{
var factory = new FileBasedTemplateEngineFactory();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
using System;
using System.Collections.Generic;
using System.Dynamic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Didot.Core.TemplateEngines;
using NUnit.Framework;

namespace Didot.Core.Testing.TemplateEngines;
public class HandlebarsWrapperTests
{
[Test]
public void Render_SingleProperty_Successful()
{
var engine = new HandlebarsWrapper();
var model = new Dictionary<string, object>()
{ { "Name", "World"} };
var result = engine.Render("Hello {{model.Name}}", new { model });
Assert.That(result, Is.EqualTo("Hello World"));
}

[Test]
public void Render_MultiProperty_Successful()
{
var engine = new HandlebarsWrapper();
var model = new Dictionary<string, object>()
{ { "Name", "Albert"}, {"Age", 30 } };
var result = engine.Render("Hello {{model.Name}}. You're {{model.Age}} years old.", new { model });
Assert.That(result, Is.EqualTo("Hello Albert. You're 30 years old."));
}

[Test]
public void Render_NestedProperties_Successful()
{
var engine = new HandlebarsWrapper();
var name = new Dictionary<string, object>()
{ { "First", "Albert"}, {"Last", "Einstein" } };
var model = new Dictionary<string, object>()
{ { "Name", name}, {"Age", 30 } };
var result = engine.Render("{{#with model}}Hello {{#with Name}}{{First}} {{Last}}{{/with}}. Your age is {{Age}} years old.{{/with}}", new { model });
Assert.That(result, Is.EqualTo("Hello Albert Einstein. Your age is 30 years old."));
}

[Test]
public void Render_ArrayItems_Successful()
{
var engine = new HandlebarsWrapper();
var albert = new Dictionary<string, object>()
{ { "Name", "Albert"}, {"Age", 30 } };
var nikola = new Dictionary<string, object>()
{ { "Name", "Nikola"}, {"Age", 50 } };
var model = new[] { albert, nikola };
var result = engine.Render("Hello {{model.0.Name}}. Your colleague is {{model.1.Age}} years old.", new { model });
Assert.That(result, Is.EqualTo("Hello Albert. Your colleague is 50 years old."));
}

[Test]
public void Render_ArrayLoop_Successful()
{
var engine = new HandlebarsWrapper();
var albert = new Dictionary<string, object>()
{ { "Name", "Albert"}, {"Age", 30 } };
var nikola = new Dictionary<string, object>()
{ { "Name", "Nikola"}, {"Nikola", 50 } };
var model = new[] { albert, nikola };
var result = engine.Render("Hello {{#each model}}{{Name}}{{#unless @last}}, {{/unless}}{{/each}}!", new { model });
Assert.That(result, Is.EqualTo("Hello Albert, Nikola!"));
}

[Test]
public void Render_Stream_Successful()
{
var engine = new HandlebarsWrapper();
var model = new Dictionary<string, object>()
{ { "Name", "World"} };
using var stream = new MemoryStream(Encoding.UTF8.GetBytes("Hello {{model.Name}}"));
var result = engine.Render(stream, new { model });
Assert.That(result, Is.EqualTo("Hello World"));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ public class ScribanWrapperTests
public void Render_SingleProperty_Successful()
{
var engine = new ScribanWrapper();
var model = new Dictionary<object, object>()
var model = new Dictionary<string, object>()
{ { "Name", "World"} };
var result = engine.Render("Hello {{model.Name}}", new { model });
Assert.That(result, Is.EqualTo("Hello World"));
Expand All @@ -24,7 +24,7 @@ public void Render_SingleProperty_Successful()
public void Render_MultiProperty_Successful()
{
var engine = new ScribanWrapper();
var model = new Dictionary<object, object>()
var model = new Dictionary<string, object>()
{ { "Name", "Albert"}, {"Age", 30 } };
var result = engine.Render("Hello {{model.Name}}. You're {{model.Age}} years old.", new { model });
Assert.That(result, Is.EqualTo("Hello Albert. You're 30 years old."));
Expand All @@ -34,9 +34,9 @@ public void Render_MultiProperty_Successful()
public void Render_NestedProperties_Successful()
{
var engine = new ScribanWrapper();
var name = new Dictionary<object, object>()
var name = new Dictionary<string, object>()
{ { "First", "Albert"}, {"Last", "Einstein" } };
var model = new Dictionary<object, object>()
var model = new Dictionary<string, object>()
{ { "Name", name}, {"Age", 30 } };
var result = engine.Render("Hello {{model.Name.First}} {{model.Name.Last}}. Your age is {{model.Age}} years old.", new { model });
Assert.That(result, Is.EqualTo("Hello Albert Einstein. Your age is 30 years old."));
Expand All @@ -46,9 +46,9 @@ public void Render_NestedProperties_Successful()
public void Render_Array_Successful()
{
var engine = new ScribanWrapper();
var albert = new Dictionary<object, object>()
var albert = new Dictionary<string, object>()
{ { "Name", "Albert"}, {"Age", 30 } };
var nikola = new Dictionary<object, object>()
var nikola = new Dictionary<string, object>()
{ { "Name", "Nikola"}, {"Age", 50 } };
var model = new[] { albert, nikola };
var result = engine.Render("Hello {{model[0].Name}}. Your colleague is {{model[1].Age}} years old.", new { model });
Expand All @@ -59,9 +59,9 @@ public void Render_Array_Successful()
public void Render_ArrayLoop_Successful()
{
var engine = new ScribanWrapper();
var albert = new Dictionary<object, object>()
var albert = new Dictionary<string, object>()
{ { "Name", "Albert"}, {"Age", 30 } };
var nikola = new Dictionary<object, object>()
var nikola = new Dictionary<string, object>()
{ { "Name", "Nikola"}, {"Age", 50 } };
var model = new[] { albert, nikola };
var result = engine.Render("Hello {{ for item in model; item.Name; if !for.last; \", \"; end; end }}!", new { model });
Expand All @@ -72,7 +72,7 @@ public void Render_ArrayLoop_Successful()
public void Render_Stream_Successful()
{
var engine = new ScribanWrapper();
var model = new Dictionary<object, object>()
var model = new Dictionary<string, object>()
{ { "Name", "World"} };
using var stream = new MemoryStream(Encoding.UTF8.GetBytes("Hello {{model.Name}}"));
var result = engine.Render(stream, new { model });
Expand Down

0 comments on commit ef4e7c3

Please sign in to comment.