Click here to Skip to main content
13,792,857 members
Click here to Skip to main content
Add your own
alternative version

Stats

4.4K views
29 downloads
10 bookmarked
Posted 22 Sep 2018
Licenced CC (BY-ND 3.0)

Morestachio. More Mustachio, Less Mustache

, 25 Sep 2018
Rate this:
Please Sign up or sign in to vote.
Morstachio the upstarter of Mustachio. Formatter and best friend.

Introduction

Today, I want to introduce you to Morestachio. A Fork of Mustachio.

Morestachio is created upon Mustachio and is a Template formatting engine completely written in C#.

Github: Morestachio

Morestachio can be installed via NuGet:

Install-Package Morestachio

Background

As most projects, this one was created in the need of a fast, simple but somehow extendable formatting syntax for template generation. First, I have found Mustachio. It worked great but had some drawbacks as every data must be prepared in code or at least before it was given to the engine. This might be ok for developers when you have that one template that always looks the same, but in my case, I needed more. I wanted to let users create templates on a single Data Source. That had brought some problems with it as there was no way in Filtering, Ordering or "formatting" the data in a general sense.

"Formatting? is that not what the engine should do?"

Yes, but what about the user that wants only the day of the DateTime object or wants to only display every 2nd item in a list or else. This was the more of formatting I was missing from Mustachio. So I decided to create a Fork from it and implement all these missing things and even some more.

Using the Code

var sourceTemplate = "Dear {{name}}, this is definitely a personalized note to you. 
                      Very truly yours, {{sender}}";
var template = Morestachio.Parser.ParseWithOptions(new Morestachio.ParserOptions(sourceTemplate));
// Create the values for the template model:
dynamic model = new ExpandoObject();
model.name = "John";
model.sender = "Sally";
var result = template.CreateAndStringify(model);
Console.WriteLine("Dynamic Object: " + result); // Dear John, this is definitely 
                                                // a personalized note to you. Very truly yours, Sally
        
// or with dictionaries
model = new Dictionary<string, object>();
model["name"] = "John";
model["sender"] = "Sally";
result = template.CreateAndStringify(model);
Console.WriteLine("Dictionary<string,object>: " + result); // Dear John, this is definitely 
                                                  //a personalized note to you. Very truly yours, Sally
        
//or with any other object
model = new JohnAndSally();        
model.name = "John";
model.sender = "Sally";
result = template.CreateAndStringify(model);
Console.WriteLine("object: " + result); // Dear John, this is definitely 
                                        // a personalized note to you. Very truly yours, Sally

That is the most basic part you have to know. Take an object, ether an IDictionary<string,object> or another class instance compared with a template and run it with the Parser.

Nested Objects

Nested object can be simply called by using a dot. For example:

var sourceTemplate = "Dear {{other.name}}, this is definitely a personalized note to you. 
                      Very truly yours, {{other.sender}}"
var template = Morestachio.Parser.Parse(new Morestachio.ParserOptions(sourceTemplate));

var otherModel = new Dictionary<string, object>();
otherModel["name"] = "John"; 
otherModel["sender"] = "Sally";

var model = new Dictionary<string, object>();
model["other"] = otherModel;

// Combine the model with the template to get content:
template.CreateAndStringify(model) // Dear John, this is definitely a personalized note to you. 
                                   // Very truly yours, Sally

Scoping

There is no IF ELSE syntax in morestachio but you can check any value for "falsey". Falsey values are any values that are considered as either invalid or representations of boolean false.

A value is considered falsey if:

  • it's either: null, boolean false, numeric 0, string empty, collection empty (whitespaces are allowed)

A scope will only be applied if the value is not falsey. You can apply a scope by prefixing it with #.

Inverted Scope

An inverted scope is just a scope that will be applied if the value is falsey. An inverted scope can be used by prefixing the value with ^.

var sourceTemplate = "" +
        "{{#other}} Other exists{{/other}}" +
        "{{^other}} Other does not exists{{/other}}," +
        " And"+
        "{{#another}}Another exists {{/another}} " +
        "{{^another}}Another does not exists {{/another}}";

var template = Morestachio.Parser.Parse(new Morestachio.ParserOptions(sourceTemplate));

var otherModel = new Dictionary<string, object>();
otherModel["otherother"] = "Test";
var model = new Dictionary<string, object>();
model["other"] = otherModel;
model["navigateUp"] = "Test 2";

// Combine the model with the template to get content:
template.CreateAndStringify(model); // Other exists And Another does not exist

While you are inside a scope, all paths you write are prefixed with the path you have used in the scope. So if you want to print the properties of other while you are inside the scope of other, you do not have to write the full path and can instead write the path direct like (from the example above):

{{#other}} {{otherother}} {{/other}}

will yield "Test".

Navigating Scopes Up

With the dot syntax, you can navigate down, with the ../ syntax, you can navigate one scope up like:

{{#other}} {{../otherother}} {{/other}}

will yield "Test 2". You can go up more than one level by stacking the ../ expression more than one time like ../../../ to go 3 levels up. If you reach the root while doing this, you will stay there.

Lists and #Each

Lists can be looped with the #each syntax. For example:

var sourceTemplate = "Names: {{#each names}} {{.}}, {{/each}}";
var template = Morestachio.Parser.ParseWithOptions(new Morestachio.ParserOptions(sourceTemplate));

var otherModel = new List<string>(); 
otherModel.Add("John"); 
otherModel.Add("Sally");

var model = new Dictionary<string, object>();
model["names"] = otherModel;

// Combine the model with the template to get content:
Console.WriteLine(template.CreateAndStringify(model)); // Names: John, Sally,

In addition to Mustachio, there are some special keywords inside an loop:

Name Type Description
$first bool Is the current item the first in the collection
$index int The Index in the list
$middel bool Is the current item not the first or last
$last bool Is the current item the last one
$odd bool Is the index odd
$even bool Is the index even

This is very helpful to get format your output like this:

var sourceTemplate = "Names: {{#each names}} {{.}} {{^$last}},{{/$last}} {{/each}}";
var template = Morestachio.Parser.Parse(new Morestachio.ParserOptions(sourceTemplate)); 
var model = new List<string>(); 
otherModel.Add("John"); 
otherModel.Add("Sally"); 
var model = new Dictionary<string, object> (); 
model["names"] = otherModel; 
// Combine the model with the template to get content: 
template.ParseTemplate(model).Stringify(); // Names: John, Sally

Formatter

Each primitive object such as:

string	
bool	
char	
int		
double	
short	
float	
long	
byte	
sbyte	
decimal	

including DateTimes or every object that implements IFormattable can be formatted by default. Those default formatters are declared in the ContextObject.PrintableTypes dictionary. You can add or remove any default formatter there globally. Formatters can be used to change how a value is rendered onto your template. The power of this syntax is quite powerful as you can declare a formatter that changes the appearance of every object or you can define a template where one of your objects should be displayed without repeating it in your template.

For example, can you change who a Byte should be displayed? When you call byte.ToString(), you will get the numeric representation of that byte as a string like:

0x101.ToString()
"257"

if you have a byte in your model, you can call the default formatter:

var withoutFormatterTemplate = "{{no}}";
var withFormatterTemplate = "{{no(X2)}}";
var withoutFormatter = 
      Morestachio.Parser.Parse(new Morestachio.ParserOptions(withoutFormatterTemplate)); 
var withFormatter= Morestachio.Parser.Parse(new Morestachio.ParserOptions(withFormatterTemplate));

var model = new Dictionary<string, object>();
model["no"] = 0x101;

// Combine the model with the template to get content:
withoutFormatter.CreateAndStringify(model); //257 
withFormatter.CreateAndStringify(model); //101

Parser Option Formatter

You can add formatters that are bound to the ParserOptions and therefore are only valid for one template by calling AddFormatter on the particular ParserOptions object.

var parserOption = new Morestachio.ParserOptions("{{fooBaObject(test)}}");
parserOption.AddFormatter<FooBa, string, string>((value, argument) => {
    //this is the callback for your formatter
    //the value is the instance of the kind of object you have specified in the first generic argument
    //the argument is the string or object that is defined in the template
    //the last generic argument is the return type
    //value = instance of FooBa
    //argument = "Test"
    return value.FooBaText;
});

A formatter is bound to a type. If you define a formatter for a type in the ParserOption, it will overwrite the formatter used in the global collection. You can also access the values of the formatted object like:

{{that.is.an.formattable(test).object.that.returns(foobArea).something.else}}

or:

{{#each list(groupBy f).also.by(propA)}} {{/each}}

with the $prop$ syntax, you can reference other values and handle it to the formatter.

{{obj.propA($obj.propB$)}}

Multi Formatter

With version 2.2.110 comes multi formatter. Multiformatters are able to accept more than 1 argument either named or unnamed.

To register a Multi formatter, you have to call the ParsingOptions.AddMultipleArgumentsFormatter<T> function. That function accepts only delegate and a description. The delegate must not return a value and can accept more than one argument from the template.

With this change comes the need to escape commas within even a single value formatter. The syntax for Multiformatter are:

[Name] Value, ...

Name: Is Optional. You must not use a name in the template. In that case, the argument will be matched by its position with the Formatter. If a Formatter does not have the corresponding type on the given position, it will be skipped.

Value: Is either a string that does not contain a comma:

AnyString, other

Or with name:

[NameOfParam] AnyString, [otherName] other

or a string escaped with quotes (ether single quote or double quotes):

"contains comma, and comma", other

Quotes can be escaped with \" or \':

or a reference to another value:

$refToField$, other

Points of Interest

Key Differences between Morestachio and mustachio

Morestachio is built upon Mustachio and extends the mustachio syntax in a few ways:

  1. Each value can be formatted by adding formatter to the morestachio.
  2. Templates will be parsed as streams and will create a new stream. This is better when creating larger templates and best for web as you can also limit the length of the "to be" created template to a certain size.
  3. Morestachio accepts any object besides the Dictionary<string,object> from mustachio.

Key Differences between Morestachio and Mustache

Morestachio contains a few modifications to the core Mustache language that are important.

  1. each blocks are recommended for handling arrays of values.
  2. Complex paths are supported, for example {{ this.is.a.valid.path }} and {{ ../this.goes.up.one.level }}
  3. Template partials ({{> secondary_template }}) are not supported. But can be rebuilt by using formatter.

Formatter Framework

I am currently developing a small set of useful formatters in a lib besides the main Morestachio project. It will contain most of the Linq.Extentions like FirstOrDefault or OrderBy that can be attached to each Morestachio template. The first draft is included in the example code.

History

  1. Made Fork from Mustachio

License

This article, along with any associated source code and files, is licensed under The Creative Commons Attribution-NoDerivatives 3.0 Unported

Share

About the Author

Jean-Pierre Bachmann
Software Developer Freelancer
Germany Germany
I am a Young German Developer.

I was working since 2012 as a Junior Software Developer in the area of WPF with DevExpress and WinForms. I also had some experience with TSQL and Asp.net with AngularJS.

From January 2015 i will be working as an Software Consultant.

From June 2015 i will work as an Freelancer

You may also be interested in...

Comments and Discussions

 
SuggestionHelpers Pin
Member 271240424-Sep-18 22:11
professionalMember 271240424-Sep-18 22:11 
GeneralRe: Helpers Pin
Jean-Pierre Bachmann25-Sep-18 0:05
professionalJean-Pierre Bachmann25-Sep-18 0:05 
SuggestionRe: Helpers Pin
Member 271240425-Sep-18 0:15
professionalMember 271240425-Sep-18 0:15 
GeneralRe: Helpers Pin
Jean-Pierre Bachmann25-Sep-18 0:52
professionalJean-Pierre Bachmann25-Sep-18 0:52 
SuggestionRe: Helpers Pin
Member 271240425-Sep-18 1:30
professionalMember 271240425-Sep-18 1:30 
GeneralRe: Helpers Pin
Jean-Pierre Bachmann25-Sep-18 3:39
professionalJean-Pierre Bachmann25-Sep-18 3:39 
GeneralRe: Helpers Pin
Jean-Pierre Bachmann25-Sep-18 14:28
professionalJean-Pierre Bachmann25-Sep-18 14:28 
GeneralRe: Helpers Pin
Member 271240426-Sep-18 22:48
professionalMember 271240426-Sep-18 22:48 
GeneralRe: Helpers Pin
Jean-Pierre Bachmann4-Dec-18 3:07
professionalJean-Pierre Bachmann4-Dec-18 3:07 
GeneralMy vote of 5 Pin
LightTempler22-Sep-18 8:05
memberLightTempler22-Sep-18 8:05 
GeneralRe: My vote of 5 Pin
Jean-Pierre Bachmann22-Sep-18 12:12
professionalJean-Pierre Bachmann22-Sep-18 12:12 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

Permalink | Advertise | Privacy | Cookies | Terms of Use | Mobile
Web01 | 2.8.181207.3 | Last Updated 25 Sep 2018
Article Copyright 2018 by Jean-Pierre Bachmann
Everything else Copyright © CodeProject, 1999-2018
Layout: fixed | fluid