Skip to content

Commit

Permalink
Feature/section lambda render (#55)
Browse files Browse the repository at this point in the history
* Add lambda overload for rendering in context

This allocates a new string writer for this which isn't super efficent
but this isn't likely to be a common case

* Update benchmark dotnet to the latest version

* Add docs for lambda render function
  • Loading branch information
Romanx committed Jun 2, 2019
1 parent 8654bf0 commit b98e6f0
Show file tree
Hide file tree
Showing 9 changed files with 322 additions and 166 deletions.
261 changes: 138 additions & 123 deletions docs/how-to.md
@@ -1,123 +1,138 @@
# How to use

Stubble follows the mustache spec explicitly so anything that works in other spec compliant mustache libraries such as [mustache.js](https://github.com/janl/mustache.js), [mustache.java](https://github.com/spullara/mustache.java) and [mustache.php](https://github.com/bobthecow/mustache.php) should work without any issues.

The full spec can be found [here.](https://mustache.github.io/mustache.5.html)

## Stubble Specifics

Stubble is designed to be as slim as possible providing almost no utility functions for finding templates or converting your data into the right format. That is left up to the user however extension packages will be provided in future for common use cases such as Json.Net and System.Data.

### Standard Example Usecase

```csharp
using Stubble.Core.Builders;
using System.IO;
using System.Text;

// Sync
public void SyncRender() {
var stubble = new StubbleBuilder().Build();
var myObj = new MyObj();
using (StreamReader streamReader = new StreamReader(@".\Path\To\My\File.Mustache", Encoding.UTF8))
{
var output = stubble.Render(streamReader.ReadToEnd(), myObj);
// Do Stuff
}
}

// Async
public async Task SyncRender() {
var stubble = new StubbleBuilder().Build();
var myObj = new MyObj();
using (StreamReader streamReader = new StreamReader(@".\Path\To\My\File.Mustache", Encoding.UTF8))
{
var content = await streamReader.ReadToEndAsync();
var output = await stubble.RenderAsync(content, myObj);
// Do Stuff
}
}
```

OR

```csharp
var stubble = new StubbleBuilder().Build();
Dictionary<string, object> dataHash = GetMyData();

//Sync
var output = stubble.Render("{{Foo}}", dataHash);

// Async
var output = await stubble.RenderAsync("{{Foo}}", dataHash);
```

It's as simple as that.

### Configuration

To configure your stubble instance you can provide a configuration function in which you set any specifics you like.
An example of this is below:

```csharp
var stubble = new StubbleBuilder()
.Configure(settings => {
settings.IgnoreCaseOnKeyLookup = true;
settings.MaxRecursionDepth = 512;
settings.AddJsonNet(); // Extension method from extension library
})
.Build();

Dictionary<string, object> dataHash = GetMyData();
var output = stubble.Render("{{Foo}}", dataHash);
```

### Lambdas

> **Note:** These details only affect the Stubble.Core renderer and not the compilation renderer since compilation does not currently support lambdas.
Stubble implements the mustache language extension for Lambdas which are anonymous functions which you can use to in your templates.
There are two types of Lambdas in Stubble **Tag Lambdas** and **Section Lambdas**.

If the dynamic argument is defined, it is the context of the function much like using "this" however since the compiler can't know what you mean in your function the dynamic argument is used to be interrogated at runtime.

#### Interpolation: or how I learnt to return tags and love them

If you return tags from your lambda they will be expanded before being rendered. This way you you can build new templates to return from your templates. _My head hurts..._

#### Tag Lambdas

Tag lambdas are rendered in place of the tag in the template. They have to be the type `Func<dynamic, object>` or `Func<object>`

**Example**

```csharp
var obj = new {
Bar = "Bar",
Foo = new Func<dynamic, object>((dyn) => { return dyn.Bar; })
};

stubble.Render("{{Foo}}", obj); // Outputs: "Bar"
```

#### Section Lambdas

Section lambdas are used to wrap sections of a template. The contents of the section is passed into the lambda as an argument. They have to be of the type `Func<dynamic, string, object>` or `Func<string, object>`

**Example**

```csharp
var obj = new {
Bar = "Bar",
Foo = new Func<dynamic, string, object>((dyn, str) => { return str.Replace("World", dyn.Bar); })
};

stubble.Render("{{Foo}} Hello World {{/Foo}}", obj); //Outputs: " Hello Bar "
var obj2 = new {
Bar = "Bar",
Foo = new Func<string, object>((str) => { return "Foo {{Bar}}"; })
};

stubble.Render("{{Foo}} Hello World {{/Foo}}", obj2); //Outputs: "Foo Bar"
```
# How to use

Stubble follows the mustache spec explicitly so anything that works in other spec compliant mustache libraries such as [mustache.js](https://github.com/janl/mustache.js), [mustache.java](https://github.com/spullara/mustache.java) and [mustache.php](https://github.com/bobthecow/mustache.php) should work without any issues.

The full spec can be found [here.](https://mustache.github.io/mustache.5.html)

## Stubble Specifics

Stubble is designed to be as slim as possible providing almost no utility functions for finding templates or converting your data into the right format. That is left up to the user however extension packages will be provided in future for common use cases such as Json.Net and System.Data.

### Standard Example Usecase

```csharp
using Stubble.Core.Builders;
using System.IO;
using System.Text;

// Sync
public void SyncRender() {
var stubble = new StubbleBuilder().Build();
var myObj = new MyObj();
using (StreamReader streamReader = new StreamReader(@".\Path\To\My\File.Mustache", Encoding.UTF8))
{
var output = stubble.Render(streamReader.ReadToEnd(), myObj);
// Do Stuff
}
}

// Async
public async Task SyncRender() {
var stubble = new StubbleBuilder().Build();
var myObj = new MyObj();
using (StreamReader streamReader = new StreamReader(@".\Path\To\My\File.Mustache", Encoding.UTF8))
{
var content = await streamReader.ReadToEndAsync();
var output = await stubble.RenderAsync(content, myObj);
// Do Stuff
}
}
```

OR

```csharp
var stubble = new StubbleBuilder().Build();
Dictionary<string, object> dataHash = GetMyData();

//Sync
var output = stubble.Render("{{Foo}}", dataHash);

// Async
var output = await stubble.RenderAsync("{{Foo}}", dataHash);
```

It's as simple as that.

### Configuration

To configure your stubble instance you can provide a configuration function in which you set any specifics you like.
An example of this is below:

```csharp
var stubble = new StubbleBuilder()
.Configure(settings => {
settings.IgnoreCaseOnKeyLookup = true;
settings.MaxRecursionDepth = 512;
settings.AddJsonNet(); // Extension method from extension library
})
.Build();

Dictionary<string, object> dataHash = GetMyData();
var output = stubble.Render("{{Foo}}", dataHash);
```

### Lambdas

> **Note:** These details only affect the Stubble.Core renderer and not the compilation renderer since compilation does not currently support lambdas.
Stubble implements the mustache language extension for Lambdas which are anonymous functions which you can use to in your templates.
There are two types of Lambdas in Stubble **Tag Lambdas** and **Section Lambdas**.

If the dynamic argument is defined, it is the context of the function much like using "this" however since the compiler can't know what you mean in your function the dynamic argument is used to be interrogated at runtime.

#### Interpolation: or how I learnt to return tags and love them

If you return tags from your lambda they will be expanded before being rendered. This way you you can build new templates to return from your templates. _My head hurts..._

#### Tag Lambdas

Tag lambdas are rendered in place of the tag in the template. They have to be the type `Func<dynamic, object>` or `Func<object>`

**Example**

```csharp
var obj = new {
Bar = "Bar",
Foo = new Func<dynamic, object>((dyn) => { return dyn.Bar; })
};

stubble.Render("{{Foo}}", obj); // Outputs: "Bar"
```

#### Section Lambdas

Section lambdas are used to wrap sections of a template. The contents of the section is passed into the lambda as an argument. They have to be one of the following types:
- `Func<dynamic, string, object>`
- `Func<string, object>`
- `Func<string, Func<string, string>, object>`
- `Func<dynamic, string, Func<string, string>, object>`

The signature with `Func<string, string>` as an argument will be provided a renderer that will render the passed template within the current context. This can be used for my dynamic templates.

_**Note:** This is not particularly performant since this is an edge case and used in such scenarios. We would recommend performing any logic outside of your templates before rendering._

**Example**

```csharp
var obj = new {
Bar = "Bar",
Foo = new Func<dynamic, string, object>((dyn, str) => { return str.Replace("World", dyn.Bar); })
};

stubble.Render("{{Foo}} Hello World {{/Foo}}", obj); //Outputs: " Hello Bar "
var obj2 = new {
Bar = "Bar",
Foo = new Func<string, object>((str) => { return "Foo {{Bar}}"; })
};

stubble.Render("{{Foo}} Hello World {{/Foo}}", obj2); //Outputs: "Foo Bar"
var obj3 = new {
Bar = "Bar",
Foo = new Func<string, Func<string, string>, object>((str, render) => { return "Foo " + render("{{Bar}}"); })
};

stubble.Render("{{Foo}} Hello World {{/Foo}}", obj3); //Outputs: "Foo Bar"
```
Expand Up @@ -38,7 +38,7 @@ protected override void Write(CompilationRenderer renderer, SectionToken obj, Co
{
var innerType = value.Type.GetElementTypeOfIEnumerable();
var param = Expression.Parameter(innerType);
expression = WriteIEnumerable(value, param, innerType, renderer.Render(obj, context.Push(innerType, param)) as List<Expression>);
expression = WriteIEnumerable(value, param, renderer.Render(obj, context.Push(innerType, param)) as List<Expression>);
}
else if (typeof(IEnumerator).IsAssignableFrom(value.Type))
{
Expand Down Expand Up @@ -90,7 +90,7 @@ protected override async Task WriteAsync(CompilationRenderer renderer, SectionTo

if (sectionContent.Count > 0)
{
expression = WriteIEnumerable(value, param, innerType, sectionContent);
expression = WriteIEnumerable(value, param, sectionContent);
}
}
else if (typeof(IEnumerator).IsAssignableFrom(value.Type))
Expand Down Expand Up @@ -128,7 +128,7 @@ protected override async Task WriteAsync(CompilationRenderer renderer, SectionTo
}
}

private static Expression WriteIEnumerable(Expression value, ParameterExpression param, Type type, List<Expression> blockContent)
private static Expression WriteIEnumerable(Expression value, ParameterExpression param, List<Expression> blockContent)
{
if (blockContent.Count > 0)
{
Expand Down

0 comments on commit b98e6f0

Please sign in to comment.