Skip to content

Commit 0ebad2d

Browse files
authored
feat: reference partials and functions in Didot.Core (only ST, Scriban and Hdb) (#173)
1 parent 8792aae commit 0ebad2d

21 files changed

+377
-5
lines changed

docs/_data/navigation_docs.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@
2727
- configuration
2828
- feature-formatter
2929
- feature-mappings
30+
- feature-partial
31+
- feature-function
3032

3133
- title: Parsers
3234
docs:

docs/_docs/feature-function.md

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
---
2+
title: Defining a function
3+
tags: [template, features]
4+
---
5+
## Overview
6+
7+
A template function is a function defined inside a template that can render content dynamically. When passed as an argument to another function (like map, each, or custom helpers), it acts as a kind of callback, formatting or transforming values within the template itself.
8+
9+
You define a template block or lambda that takes arguments and renders something with them. Then you pass that function to another function (e.g., a mapping function or loop).
10+
11+
## Registering a Function
12+
13+
To register a function, use the `AddFunction` method of an `ITemplateEngine`. The method requires:
14+
15+
- A name for the function – the identifier for the function.
16+
- A function returning a string representing the function template.
17+
18+
```csharp
19+
var factory = new FileBasedTemplateEngineFactory();
20+
var engine = factory.GetByTag(tag);
21+
engine.AddFunction("Hello", () => "function definition");
22+
```
23+
24+
## Using Functions in Templates
25+
26+
Once registered, functions can be applied directly in templates by referencing the function name and passing values to the parameters. All examples below will have the same output:
27+
28+
```text
29+
Greetings: Mr. Einstein Albert!
30+
```
31+
32+
### Scriban
33+
34+
The main template is defined as,
35+
36+
```liquid
37+
{% raw %}Greetings: {{ Hello(model.Name.First, model.Name.Last) }}!{% endraw %}
38+
```
39+
40+
and the function itself is defined as,
41+
42+
```text
43+
{%- raw -%}
44+
{{ func Hello(firstName, lastName) -}}
45+
Mr. {{ lastName }} {{ firstName -}}
46+
{{ end }}
47+
{% endraw %}
48+
```
49+
50+
### StringTemplate
51+
52+
The main template is defined as,
53+
54+
```text
55+
{% raw %}Greetings: <Hello(model.Name.First, model.Name.Last)>!{% endraw %}
56+
```
57+
58+
and the function itself is defined as,
59+
60+
```text
61+
{% raw %}Hello(firstName, lastName)::= Mr. <lastName> <firstName>{% endraw %}
62+
```
63+
64+
### Liquid, Fluid, Handlebars, Morestachio and SmartFormat
65+
66+
This feature is not supported.
67+
68+
### Conclusions
69+
70+
Template functions that accept another template function give you a clean, composable way to define how data should be rendered, and reuse that rendering logic flexibly.

docs/_docs/feature-mappings.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ Once registered, mappings can be applied directly in templates by referencing th
2222
```csharp
2323
var factory = new FileBasedTemplateEngineFactory();
2424
var engine = factory.GetByTag(tag);
25-
engine.AddMappings("greetings", new Dictionary<object, string>() { {"french", "Bonjour"}, {"english", "Hi"}, {"spanish", "Ola"} })
25+
engine.AddMappings("greetings", new Dictionary<object, string>() { {"french", "Bonjour"}, {"english", "Hi"}, {"spanish", "Ola"} });
2626
```
2727

2828
Assume the model contains a property *Language*, and a dictionary named *greetings* has been registered to provide translations. The following examples demonstrate how to apply mappings across different template engines:

docs/_docs/feature-partial.md

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
---
2+
title: Defining a partial
3+
tags: [template, features]
4+
---
5+
## Overview
6+
7+
A partial is a reusable snippet of template code that can be included or embedded within one or more templates. It allows you to extract repeated layout or formatting logic into a separate, named unit, improving both readability and maintainability.
8+
9+
When a template includes a partial, the templating engine:
10+
11+
1. Looks up the partial by its name
12+
2. Loads or renders its content
13+
3. Injects the rendered result into the main template at the point of inclusion
14+
15+
## Registering a Partial
16+
17+
To register a partial, use the `AddPartial` method of an `ITemplateEngine`. The method requires:
18+
19+
- A name for the partial – the identifier for the partial.
20+
- A function returning a string representing the partial template.
21+
22+
## Using Partials in Templates
23+
24+
Once registered, mappings can be applied directly in templates by referencing the dictionary name.
25+
26+
```csharp
27+
var factory = new FileBasedTemplateEngineFactory();
28+
var engine = factory.GetByTag(tag);
29+
engine.AddPartial("greetings", () => "Welcome");
30+
```
31+
32+
In this case, at each occurrence of the partial *greetings*, it will output the text *Welcome*.
33+
34+
```text
35+
Welcome, Albert Einstein!
36+
```
37+
38+
### Scriban
39+
40+
```liquid
41+
{% raw %}{{include 'greetings'}}, {{model.Name.First}} {{model.Name.Last}}!{% endraw %}
42+
```
43+
44+
### Handlebars
45+
46+
```handlebars
47+
{% raw %}{{> Greetings }}, {{model.Name.First}} {{model.Name.Last}}!{% endraw %}
48+
```
49+
50+
### StringTemplate
51+
52+
```text
53+
{% raw %}<Greetings()>, <model.Name.First> <model.Name.Last>!{% endraw %}
54+
```
55+
56+
### Liquid, Fluid, Morestachio and SmartFormat
57+
58+
This feature is not supported.

src/Didot.Core/ITemplateEngine.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ public interface ITemplateEngine
99
{
1010
void AddMappings(string mapKey, IDictionary<string, object> mappings);
1111
void AddFormatter(string name, Func<object?, string> function);
12+
void AddFunction(string name, Func<string> template);
13+
void AddPartial(string name, Func<string> template);
1214
string Render(string template, object model);
1315
string Render(Stream template, object model);
1416
}

src/Didot.Core/TemplateEngines/BaseTemplateWrapper.cs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ public BaseTemplateEngine(TemplateConfiguration configuration)
1818

1919
protected Dictionary<string, IDictionary<string, object>> Mappings { get; } = [];
2020
protected Dictionary<string, Func<object?, string>> Formatters { get; } = [];
21+
protected Dictionary<string, Func<string>> Functions { get; } = [];
22+
protected Dictionary<string, Func<string>> Partials { get; } = [];
2123

2224
public virtual void AddMappings(string mapKey, IDictionary<string, object> mappings)
2325
{
@@ -31,6 +33,18 @@ public virtual void AddFormatter(string name, Func<object?, string> function)
3133
Formatters[name] = function;
3234
}
3335

36+
public virtual void AddFunction(string name, Func<string> template)
37+
{
38+
if (!Functions.TryAdd(name, template))
39+
Functions[name] = template;
40+
}
41+
42+
public virtual void AddPartial(string name, Func<string> template)
43+
{
44+
if (!Partials.TryAdd(name, template))
45+
Partials[name] = template;
46+
}
47+
3448
public abstract string Render(string template, object model);
3549
public abstract string Render(Stream stream, object model);
3650
}

src/Didot.Core/TemplateEngines/DotLiquidWrapper.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,10 @@ public DotLiquidWrapper(TemplateConfiguration configuration)
1919

2020
public override void AddFormatter(string name, Func<object?, string> function)
2121
=> throw new NotImplementedException();
22+
public override void AddFunction(string name, Func<string> template)
23+
=> throw new NotImplementedException();
24+
public override void AddPartial(string name, Func<string> template)
25+
=> throw new NotImplementedException();
2226

2327
public override string Render(string source, object model)
2428
{

src/Didot.Core/TemplateEngines/FluidWrapper.cs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66
using System.Threading.Tasks;
77
using Fluid;
88
using Fluid.Values;
9+
using Microsoft.Extensions.FileProviders;
10+
using Microsoft.Extensions.Primitives;
911
using SmartFormat.Core.Extensions;
1012

1113
namespace Didot.Core.TemplateEngines;
@@ -21,6 +23,12 @@ public FluidWrapper(TemplateConfiguration configuration)
2123
: base(configuration)
2224
{ }
2325

26+
public override void AddFunction(string name, Func<string> template)
27+
=> throw new NotImplementedException();
28+
29+
public override void AddPartial(string name, Func<string> template)
30+
=> throw new NotImplementedException();
31+
2432
public override string Render(string source, object model)
2533
{
2634
var template = Parser.Parse(source);

src/Didot.Core/TemplateEngines/HandlebarsWrapper.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ public HandlebarsWrapper()
1717
public HandlebarsWrapper(TemplateConfiguration configuration)
1818
: base(configuration)
1919
{ }
20+
public override void AddFunction(string name, Func<string> template)
21+
=> throw new NotImplementedException();
2022

2123
public override string Render(string template, object model)
2224
{
@@ -32,6 +34,9 @@ public override string Render(Stream stream, object model)
3234
using var reader = new StreamReader(stream);
3335
var templateInstance = handlebarsContext.Compile(reader);
3436

37+
foreach (var include in Partials)
38+
handlebarsContext.RegisterTemplate(include.Key, include.Value.Invoke());
39+
3540
using var writer = new StringWriter(); // StringWriter as TextWriter for output
3641
templateInstance(writer, model);
3742
return writer.ToString();

src/Didot.Core/TemplateEngines/MorestachioWrapper.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,11 @@ public MorestachioWrapper()
1717
public MorestachioWrapper(TemplateConfiguration configuration)
1818
: base(configuration)
1919
{ }
20+
public override void AddFunction(string name, Func<string> template)
21+
=> throw new NotImplementedException();
22+
23+
public override void AddPartial(string name, Func<string> template)
24+
=> throw new NotImplementedException();
2025

2126
public override string Render(string template, object model)
2227
{

0 commit comments

Comments
 (0)