Skip to content

Commit

Permalink
fix: Restore README
Browse files Browse the repository at this point in the history
  • Loading branch information
Antaris committed Nov 1, 2024
1 parent 9e40ccd commit ae24fcc
Showing 1 changed file with 319 additions and 3 deletions.
322 changes: 319 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,321 @@
# SailthruSDK
# FuManchu

An opinionated .NET SDK built for the Sailthru API
Text-templating using a Handlebars-like syntax, in your .NET Project. Tokenizer/Parser based on the Razor engine provided by Microsoft.

[![.github/workflows/main.yml](https://github.com/IngeniumSE/SailthruSDK/actions/workflows/main.yml/badge.svg)](https://github.com/IngeniumSE/SailthruSDK/actions/workflows/main.yml) [![.github/workflows/release.yml](https://github.com/IngeniumSE/SailthruSDK/actions/workflows/release.yml/badge.svg)](https://github.com/IngeniumSE/SailthruSDK/actions/workflows/release.yml)
[![NuGet](https://img.shields.io/nuget/v/FuManchu.svg)](https://www.nuget.org/packages/FuManchu/) [![.github/workflows/main.yml](https://github.com/IngeniumSE/FuManchu/actions/workflows/main.yml/badge.svg)](https://github.com/IngeniumSE/FuManchu/actions/workflows/main.yml) [![.github/workflows/release.yml](https://github.com/IngeniumSE/FuManchu/actions/workflows/release.yml/badge.svg)](https://github.com/IngeniumSE/FuManchu/actions/workflows/release.yml)

Quick Start
-----------

First thing, install FuManchu from Nuget:

Install-Package FuManchu

Next, add a namespace using:

using FuManchu;

Then, you're good to go:

Handlebars.Compile("<template-name>", "Hello {{world}}!");
string result = Handlebars.Run("<template-name>", new { world = "World" });

There is also a shorthand:

string result = Handlebars.CompileAndRun("<template-name>", "Hello {{world}}!", new { world = "World" });

Documentation
-------------

Documentation site is coming soon. But for now, some easy instructions are provided below.

Compiling templates
-------------------
The static `Handlebars` class provides a singleton instance of the `IHandlebarsService` type. This API is modelled on the HandlebarsJS JavaScript API, so if you are familiar with that framework, you'll be fine with FuManchu.

Let's define a template and pass it to the service:

string template = "Hello {{name}}";
var templateFunc = Handlebars.Compile("name", template);

The `Compile` function returns a `HandlebarTemplate` delegate which you can call, passing in your model to return you're result.

string result = templateFunc(new { name = "Matt" });

This is equivalent to the following:

string result = Handlebars.Run("name", new { name = "Matt" });

When you call `Compile` your template is parsed and can be executed multiple times with new data.

Block Tags
------------
Block tags are define using the `{{#tag}}{{/tag}}` syntax. There are three types of block tags; built-in tags, implicit tags and helper tags.

**Built-ins: if, elseif, else**

You can use the `if`, `elseif`, `else` tags to provide conditional logic to your templates.

{{#if value}}
True
{{/if}}

{{#if value}}
True
{{else}}
False
{{/if}}

{{#if value1}}
Value 1
{{#elseif value2}}
Value 2
{{else}}
None
{{/if}}

We resolve the truthfulness of values using the same semantics of *truthy/falsely* logic of JavaScript, therefore, values that are `null`, or the number zero, empty enumerables and empty strings, are all considered false. Everything else is considered true.

The `{{else}}` tag can be also be written as `{{^}}` in your templates

{{#if value}}
True
{{^}}
False
{{/if}}

**Built-ins: unless, else**

`unless` is the negated version of `if`. It will allow you to assume truthful values, and present output if the value is falsey.

{{#unless value}}
Value is not true!
{{/unless}}

{{#unless value}}
Value is not true!
{{else}}
Value was true!
{{/unless}}

The `{{else}}` tag can be also be written as `{{^}}` in your templates

**Built-ins: each, else**

The `each` tag allows you to iterate over enumerable objects.

<ul>
{{#each items}}
<li>{{value}}</li>
{{/each}}
</ul>

The `each` block tag creates a scope around the target model (therefore each child of `items`, above), to allow you to use the `{{value}}` expressions, where `value` is a property of a child of `items`. A more concrete example could be:

var people = new [] { new Person() { Name = "Matt" }, new Person() { Name = "Stuart" } };
string template = "<ul>{{#each this}}<li>{{Name}}</li>{{/each}}</ul>";
// result: <ul><li>Matt</li><li>Stuart</li></ul>
string result = Handlebars.CompileAndRun("name", template, people);

The `each` tag also supports the variables `@index`, `@first`, `@last`. If you enumerate over an `IDictionary`, you also have access to `@key`.

var people = new [] { new Person() { Name = "Matt" }, new Person() { Name = "Stuart" } };
string template = "<ul>{{#each this}}<li>{{@index}}: {{Name}}</li>{{/each}}</ul>";
// result: <ul><li>0: Matt</li><li>1: Stuart</li></ul>
string result = Handlebars.CompileAndRun("name", template, people);

`@index` tracks the current index of the item in the enumerable. `@first` and `@last` represent true/false values as to whether you are enumerating the first or last value in an enumerable. `@key` represents the dictionary key.

You can provided a `{{else}}` switch to provide an output when the enumerable is empty:

{{#each items}}
Item {{@index}}
{{else}}
No items!
{{/each}}

The `{{else}}` tag can be also be written as `{{^}}` in your templates

**Built-ins: with, else**
The `with` block creates a scope around the parameter argument.

var model = new { person = new { forename = "Matt", surname = "Abbott" } };

{{#with person}}
Name: {{forename}} {{surname}}
{{/with}}

Again, as with `each`, you can use the `{{else}}` switch to provide an output if the value passed into the `with` tag is falsey:

{{#with person}}
Name: {{forename}} {{surname}}
{{else}}
Nobody :-(
{{/with}}

The `{{else}}` tag can be also be written as `{{^}}` in your templates

Implicit Block Tags
-

You can use shorthand `{{#tag}}{{/tag}}` where "tag" is the name of a property on your model, instead of using full tags, e.g.

var model = new { person = new { forename = "Matt", surname = "Abbott" } };

{{#person}}
Name: {{forename}} {{surname}}
{{/person}}

The above is equivalent to:

{{#if person}}
{{#with person}}
Name: {{forename}} {{surname}}
{{/with}}
{{/if}}

If you're property also happens to be an enumerable, then the implicit block tag works like `each`:

{{#people}}
<li>{{@index}}: {{forename}} {{surname}}</li>
{{/people}}

Inverted Block Tags
-
Inverted block tags follow the rules for implicit block tags, but are used to provided content when the tag expression resolves to *falsey*.

{{^power}}
You have no power here!
{{/power}}

Block Helpers
-

You can register custom helpers using the `Handlebars` service. You need to register a helper ahead of time, which you can then call from your template.

Handlebars.RegisterHelper("list", options => {
var enumerable = options.Data as IEnumerable ?? new[] { (object)options.Data };

return "<ul>"
+ string.Join("", enumerable.OfType<object>().Select(options.Fn))
+ "</ul>";
});

For block helpers, the `options` parameter provides access to the content of your block helper, therefore given the following usage:

{{#list people}}
<li>{{forename}} {{surname}}</li>
{{/list}}

When calling `options.Fn` (or `options.Render`), the content of your custom helper block is rendered, scoped to the value passed to the render function.

**Arguments and Hash parameters**
You can pass additional information to your helpers using your helper block, e.g.:

var model = new {
people = new List<People>(),
message = "Hello World"
};

Handlebars.RegisterHelper("list", options => {
var people = options.Data as List<People>;
string message = options.Arguments[1] as string;
string cssClass = options.Hash["class"];

return "<ul class=\"" + cssClass + "\">"
+ "<li>" + message + "</li>"
+ string.Join("", enumerable.OfType<object>().Select(options.Fn))
+ "</ul>";
});

{{#list people message class="nav nav-pills"}}...{{/list}}

An instance of `HelperOptions` is passed as the value of `options`, which provides the input arguments (`people` and `message`) and a has (`IDictionary<string, object>`, `class="nav nav-pills"`) which are accessible. `options.Data` provides a shorthand access to `options.Arguments[0]` as `dynamic`, and `options.Hash` provides readonly access to `options.Parameters`. Both `options.Data` and `options.Hash` and provided for API compatability with HandlebarsJS.

Expression Tags
-
Expression tags are simple `{{value}}` type tags that allow you to render content into your templates, by binding values from your input models (or 'contexts' in HandlebarsJS speak). There are a variety of ways you can call these properties, so given:

var model = new {
person = new {
forename = "Matt",
surname = "Abbott",
age = 30,
job = new {
title = "Developer"
}
}
};

{{person.forename}}
{{./person.forename}}
{{this.person.forename}}
{{this/person/forename}}
{{@root.person.forename}}

And also within scopes:

{{#with person}}
{{#with job}}
{{../forename}} {{../surname}}
{{/with}}
{{/with}}

You can use these same 'context paths' as arguments and hash parameters in your block tags too:

{{#if ../people}}
{{#with @root.people}}

And with your custom helpers too:

{{#list people [email protected]}}

Expression Helpers
-
Just like Block Helpers, you can create Expression Helpers too, using the same function as before:

Handlebars.RegisterHelper("name", options => {
return string.Format("{0} {1}", options.Data.forename, options.data.surname);
});

Called using the expression syntax, this time with arguments:

var model = new { forename = "Matt", surname = "Abbott" };

{{name this}}

Partial Templates
-
Partial templates allow you to break your Handlebars templates into discreet units. To register a partial, you call the `Handlebars.RegisterPartial` method

Handlebars.RegisterPartial("name", "{{forename}} {{surname}}");

You can then call your partial from your template:

var model = new { forename = "Matt", surname = "Abbott" };

{{>name}}

You can override the model passed to your template, by providing an additional arguments:

var model = new { person = new { forename = "Matt", surname = "Abbott" } };

{{>name person}}

You can alternatively pass through parameters, if you need to provide a parameter-like experience:

var model = new { person = new { forename = "Matt", surname = "Abbott" } };

{{>name firstName=person.forename lastName=person.surname}}

Text Encoding
-
Like HandlebarsJS, FuManchu encodes values by default, therefore all calls, such as `{{forename}}` etc, will be encoded. If you need to render the raw value, you can use the triple-brace syntax:

{{{forename}}} {{{surname}}}

Singleton vs Instance Services
-
The `Handlebars` type provides singleton access to an instance of `IHandlebarsService`. If you need instance access instead (such as injection through IoC/DI), you can simply register `HandlebarsService` as an instance of `IHandlebarsService` in whatever appropriate lifetime scope you require. This will enable you to partition your helpers/partials and compiled templates per instances of `IHandlebarsService`.

0 comments on commit ae24fcc

Please sign in to comment.