Git Product home page Git Product logo

morestachio's Introduction

Morestachio

FOSSA Status Converage tests

Icon

A Lightweight, powerful, flavorful, templating engine for C# and other .net-based languages. Its a fork of Mustachio.

Need help?

Need general help? open a Discussion
Found a bug? open a Bug

Installing Morestachio:

Project Nuget Github Status Description
Morestachio
Nuget Morestachio
GitHub release (latest by date including pre-releases) Build status Deployment Status The base Morestachio lib
Morestachio.Linq
Nuget Morestachio Linq
Build status Deployment Status Linq formatter
Morestachio.Runner
Nuget Morestachio Runner
Build status Deployment Status An executable interface for invoking a Morestachio Template
Morestachio.Newtonsoft.Json
Nuget Morestachio Json
Build status Deployment Status Newtonsoft Json types support
Morestachio.System.Text.Json
Nuget Morestachio System.Text.Json
Build status Deployment Status System.Text Json types support
Morestachio.System.Xml.Linq
Nuget Morestachio.System.Xml.Linq
Build status Deployment Status XDocument types support
Morestachio.Extensions.Logging
Nuget Morestachio.Extensions.Logging
Build status Deployment Status Microsoft.Extensions.Logging.ILogger support

What's this for?

Morestachio allows you to create simple text-based templates that are fast and safe to render. It is optimized for WebServers and offers a high degree of customization with its formatter syntax.

Morestachio Playground:

Try it out, without consequenses. The Morestachio online editor allows you to create templates within your browser: Editor

How to use Morestachio:

// Your template
var sourceTemplate = "Dear {{name}}, this is definitely a personalized note to you. Very truly yours, {{sender}}";

// Parse the template into the document tree. 
var document = await ParserOptionsBuilder
   .New() //creates a new builder that inherts all default values
   .WithTemplate(sourceTemplate) //sets the template for that builder
   .BuildAndParseAsync(); //Builds the template and calls ParseAsync() on the returned ParserOptions

// Create the values for the template model:
dynamic model = new ExpandoObject();
model.name = "John";
model.sender = "Sally";
// or with dictionarys
IDictionary model = new Dictionary<string, object>();
model["name"] = "John";
model["sender"] = "Sally";
//or with any other object
var model = new { name= "John", sender= "Sally" };

//create an object based renderer or a compiled renderer
//the renderer is reusable and ThreadSave
var renderer = document.CreateRenderer();

// Render the template with your model and get the result as a string
var content = renderer.RenderAndStringify(model); // Dear John, this is definitely a personalized note to you. Very truly yours, Sally
Key Features

Morestachio is build upon Mustachio and extends the mustachio syntax in a a lot of points.

  1. each object can be formatted by adding formatter to morestachio
  2. Templates will be parsed as streams and will create a new stream for its output. 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 and write the result ether directly to an output stream or the Disc.
  3. Its Lightning fast. Even unreasonably huge templates that contain >5000 instructions can be executed in around 0.5 secounds
  4. Morestachio accepts any object as source
  5. Cancellation of Template generation is supported
  6. Async calls are supported (For Formatters)
  7. No External Depedencies for Morestachio.dll.
  8. Support for several .Net framworks:
  9. Build in Localization support and Logging support
  10. Supports user Encoding of the result template
  11. Supports Template Partials {{#import 'secondary_template' }}
  12. Complex paths are supported {{ this.is.a.valid.path }} and {{ ../this.goes.up.one.level }} and {{ ~.this.goes.up.to.Root }}
  13. Loops with #each & #do & #while & #repeat
  14. Object Enumeration with #each data.?
  15. Formatters can be declared in C# and be called from the template to provide you with a maximum of freedom
  16. Extensive (275) Build-In list of Formatters for a broad usecases
  17. The Parser produces a Serilizable Document Tree that can be send to clients to provide a rich user edit experience

Checkout the Github Wiki for an extensive documentation: https://github.com/JPVenson/morestachio/wiki

Template partials ARE a great feature for large scale template development.

You can create a Partial with the {{#declare NAME}}Partial{{/declare}} syntax. You can navigate up inside this partials. Partials can also be nested but are currently restricted to a maximum recursion of 255 depth. The programmer has the choice to define a behavior that ether throws an Exception or does nothing and ignores any deeper recusions.

A Partial must be declared before its usage with {{#import 'NAME'}} but you can use a partial to create hirarical templates.

You can even inject your predefined Partials into all of your Templates by utilizing the PartialsStore. Use your own IPartialStore or a build in one with ParserOptionsBuilder.WithDefaultPartialStore(store => {...add partials to store...}).

Infos about new features

Its possible to use plain C# objects they will be called by reflection. Also you can now set the excact size of the template to limit it (this could be come handy if you are in a hostet environment) use the ParserOptionsBuilder.WithMaxSize() option to define a max size. It will be enforced on exact that amount of bytes in the stream.

Variable Output

One mayor component is the usage of Variable output strategies in morestachio.
The output handling is done by a IByteCounterStream that wraps your specific output. This can ether be a Stream, TextWriter, StringBuilder or anything else. For thoese types Morestachio has pre defined Implementations named ByteCounterStream, ByteCounterTextWriter and ByteCounterStringBuilder. All thoese types are enforcing the ParserOptionsBuilder.WithMaxSize() config if set and will write your template with the set ParserOptionsBuilder.WithEncoding()

Formatter

With Morestachio you can invoke C# methods from you template, so called 'Formatters'. There are Build in formatters you can call in any template, registered via the DefaultFormatterService.Default class. When you add a formatter in the default service, it will be availible in every template. You can also add formatters per-template via the ParserOptionsBuilder.WithFormatters service.

To Invoke a formatter from you template use the Function syntax:

{{Just.One.Formattable.FormatterToCall().Thing}}

This links a c# function named "FormatterToCall".

You can register delegates by using ParserOptionsBuilder.WithFormatter(...) or you can create a public static class that has methods attributed with the MorestachioFormatterAttribute and add them via the ParserOptionsBuilder.WithFormatters<TType> or you can use an instance method attributed with the MorestachioFormatterAttribute.

The formatter CAN return a new object on wich you can call new Propertys or it can return a string. There are formatter prepaired for types implementing the IFormattable interface. This includes all Primitve types. That means for example that you can call the ToString formatter on any DateTime:

{{MyObject.DateTime.ToString("D")}} <-- this will resolve a property "MyObject" and then "DateTime" and will call ToString on it with the argument "D"

Formatter References Can be used to reference another property/key in the template and then use it in a Formatter. Everything that is not a string (ether prefixed and suffixed with " or ') will be threaded as an expression that also can contain formatter calls

{{MyObject.Value.ToString(Key)}}

This will call a formatter that is resposible for the type that Value has and will give it whats in Key. Example:

//create the template
var template = "{{Value.ToStringX(Key)}}";
//create the model
var model = new Dictionary<string, object>();
model["Value"] = DateTime.Now; 
model["Key"] = "D";
//now add a formatter for our DateTime and add it to the ParserOptions

var document = await ParserOptionsBuilder
   .New()
   .WithTemplate(template)
//                         Value   | Argument | Return
   .WithFormatter(new Func<DateTime, string   , string>((value, argument) => {
     //value will be the DateTime object and argument will be the value from Key
     return value.ToString(argument);
   }, "ToStringX")
   .BuildAndParseAsync();

document.CreateRenderer().RenderAndStringify(model); // Friday, September 21, 2018 ish

License

FOSSA Status

morestachio's People

Contributors

atheken avatar charleslindberghmcgill avatar dependabot[bot] avatar derekrushforth avatar emp1motus avatar fossabot avatar jpvenson avatar nickcanz avatar streammz avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar

morestachio's Issues

Interface generics matching in `MorestachioFormatterService`

Is your feature request related to a problem? Please describe.
Currently the MorestachioFormatterService is not capable of matching generic interfaces like

IEnumerable<T> assignable from IList<T>

Describe the solution you'd like
It should be possible to check if thoses interfaces can match maybe including matching of generic types like IDictionary<TKey,TValue> from Dictionary<TKey, TValue> via the Type.GetGenericTypeDefinition

using "#if" with a formatter as a condition

When using an if Statement with a Formatter the result of the evaluation is used as the current Scope.
For example:
Given is a custom formatter like:

[MorestachioFormatter("TestFormatter(", "XXX")]
public static bool TestFormatter(object originalObject)
{
	return true;
}

When this formatter is used as a condition:

{{#if .("TestFormatter")}}
{{.}}
{{/If}}

=> Results in "true", but the expected result is the representation of the current scope before the if.

If a regular path is used everything works as expected:

{{#If root.Istrue}}
{{.}}
{{/If}}

=> Results in the the representation of the current scope before the if

Root Access

Hi,
retrieval of data with the ~ keyword, outside of any scope, does not return the excpected result.
Called within an each statement, or without the ~ keyword, everything is fine.

This test should cover the scenario:

[Test]
public void TestRootAccess()
{
	var templateWorking = "{{#EACH data.testList.Select()}}" +
	                      "{{#var eachValue = ~data.testInt}}" +
	                      "{{/EACH}}" +
	                      "{{eachValue}} = {{~data.testInt}} = {{data.testInt}}";

	var parsingOptionsWorking = new ParserOptions(templateWorking, null, ParserFixture.DefaultEncoding);
	parsingOptionsWorking.Formatters.AddFromType(typeof(DynamicLinq));
	parsingOptionsWorking.Formatters.AddFromType(typeof(NumberFormatter));
	var parsedTemplateWorking = Parser.ParseWithOptions(parsingOptionsWorking);

	var modelWorking = new Dictionary<string, object>()
	{
		{
			"data", new Dictionary<string, object>()
			{
				{
					"testList",
					new string[1]
					{
						string.Empty
					}
				},
				{
					"testInt",
					2
				}
			}
		}
	};
    
	var result = parsedTemplateWorking.Create(modelWorking).Stream.Stringify(true, ParserFixture.DefaultEncoding);
	Assert.AreEqual("2 = 2 = 2", result);
}

Operators cannot parse when there is a string at the left hand of the operator

Describe the bug
A clear and concise description of what the bug is.

To Reproduce
Steps to reproduce the behavior:

  1. What class
  2. What input
  3. What Environment

Expected behavior
A clear and concise description of what you expected to happen.

Screenshots
If applicable, add screenshots to help explain your problem.

Desktop (please complete the following information):

  • OS: [e.g. iOS]
  • Version [e.g. 22]
  • Got code from
  • CodeProject
  • Nuget
  • Github
  • Elsewhere (please define)

Additional context
Add any other context about the problem here.

MorestachioFormatterService is not thread safe

Describe the bug
The MorestachioFormatterService is not thread safe.

To Reproduce

XXX threw exception: 
System.TypeInitializationException: The type initializer for 'Morestachio.Formatter.Framework.MorestachioFormatterService' threw an exception. ---> System.TypeInitializationException: The type initializer for 'Morestachio.Formatter.Predefined.Accounting.CurrencyHandler' threw an exception. ---> System.ArgumentException: An item with the same key has already been added. Key: PYG
  Stack Trace:
      at System.Collections.Generic.Dictionary`2.TryInsert(TKey key, TValue value, InsertionBehavior behavior)
   at System.Collections.Generic.Dictionary`2.Add(TKey key, TValue value)
   at System.Linq.Enumerable.ToDictionary[TSource,TKey,TElement](IEnumerable`1 source, Func`2 keySelector, Func`2 elementSelector, IEqualityComparer`1 comparer)
   at System.Linq.Enumerable.ToDictionary[TSource,TKey,TElement](IEnumerable`1 source, Func`2 keySelector, Func`2 elementSelector)
   at Morestachio.Formatter.Predefined.Accounting.CurrencyHandler..cctor()
--- End of inner exception stack trace ---
    at Morestachio.Formatter.Predefined.Accounting.CurrencyHandler.get_DefaultHandler()
   at Morestachio.Formatter.Framework.MorestachioFormatterService..cctor()
--- End of inner exception stack trace ---
    at Morestachio.Formatter.Framework.MorestachioFormatterService..ctor(Boolean useCache)
   at Morestachio.ParserOptions..ctor(ITemplateContainer template, Func`2 sourceStream, Encoding encoding)
   at Morestachio.ParserOptions..ctor(ITemplateContainer template, Func`1 sourceStream, Encoding encoding)
   at Morestachio.ParserOptions..ctor(String template, Func`1 sourceStream, Encoding encoding)
   at Morestachio.ParserOptions..ctor(String template, Func`1 sourceStream)
   at Morestachio.ParserOptions..ctor(String template)

Expected behavior
Should use thread safe collections internally: https://docs.microsoft.com/en-us/dotnet/standard/collections/thread-safe/

Provide Default behaviour of "Nullable" types in plain objects when value is NULL

Hello!, I'm currently generating javascript files with a template, injecting the values with morestachio.

The Problem

Part of my template file (javascript.tpl):

 var loginBonusAmount = {{LoginBonusAmount}};

Part of my plain object (JavascriptParams.cs):

public int? LoginBonusAmount { get; set; }

Wanted output if property is NULL:

var loginBonusAmount = null;

Current output:

var loginBonusAmount = ;

The Solution

public class NullableValuesResolver : IValueResolver {
        public bool CanResolve(Type type, object value, string path, ContextObject context) {
            var propertyType = type.GetProperty(path).PropertyType;
            return Nullable.GetUnderlyingType(propertyType) != null;
        }

        public object Resolve(Type type, object value, string path, ContextObject context) {
            var result = type.GetProperty(path).GetValue(value);
            if (result == null)
            {
                return "null";
            }
            return result;
        }
    }

[...]

 var options = new ParserOptions(sourceTemplate)
 {
    ValueResolver = new NullableValuesResolver()
 };

Inline whitespace control not always renders correctly

Describe the bug
When using the ToParsableStringDocumentVisitor to render an document where one token contains the |- whitespace operator the operator is not always rendered when on root.

To Reproduce
as seen in ParserCanVariableScope test

Unary Operators in expressions cannot parse other operators on right hand

Describe the bug
Assume an expression like this:
e > 5 && e < 8
The ExpressionParser will parse the expression like this:
(e > 5 && e) < 8

To Reproduce

Expected behavior
Expected the parser to parse the expression like this: (e > 5) && (e < 8)

Desktop (please complete the following information):

  • OS: WIN10
  • Version 5.0.1.534
  • Got code from
  • CodeProject
  • Nuget
  • Github
  • Elsewhere (please define)

Additional context

Formatter Results as Arguments

Hi,
using the result of a Formatter Call as an argument for another Formatter call raises an Exception.
As soon as the result of the first Formatter is stored in a Variable everything works as expected.

This test should cover the scenario:

[Test]
public void TestMethodAsArgumentAccess()
{
	var options = new ParserOptions("{{data.Multiply(data.Multiply(data))}}", null, DefaultEncoding);
	options.Formatters.AddFromType(typeof(NumberFormatter));
	var template = Parser.ParseWithOptions(options);
	var andStringify = template.CreateAndStringify(new Dictionary<string, object>()
	{
		{"data", 2}
	});

	Assert.That(andStringify, Is.EqualTo("8"));
}

public static class NumberFormatter
{
	[MorestachioFormatter("Multiply", "XXX")]
	public static decimal Multiply(object value, object value2)
	{
		decimal a = 0;
		decimal.TryParse(value.ToString(), out a);
		decimal b = 0;
		decimal.TryParse(value2.ToString(), out b);
		return a * b;
	}
}

CurrencyHandler duplicate keys in Currencies dictionary causes exception

Describe the bug
In class CurrencyHandler the use of ISOCurrencySymbol for the key to the dictionary DefaultHandler.Currencies causes an exception due to duplicates, and Morestachio cannot be instantiated. The same issue had an attempted fix with #21

To Reproduce
Steps to reproduce the behavior:

  1. Class CurrencyHandler
  2. Cultures from a Windows 2012 Server (see list of LCIDs/Cultures)
  3. Windows 2012 Server

Expected behavior
That when attempting to populate the Dictionary for DefaultHandler.Currencies any duplicate ISOCurrencySymbols do not cause an exception and Morestachio can be instantiated.

Screenshots
See the report here, we get exactly the same exception: #21

Desktop (please complete the following information):

  • OS: Windows 2012
  • Windows Server 2012 R2 Standard (64Bit) 9600 (Xen)
  • Got code from Nuget and Github (got the CurrencyHandler from the master branch to test on affected servers)

Additional context
The problem is only occurring on two servers we have out of dozens, so the problem is clearly server configuration related. I have attached here a file with the LCIDs and Culture object properties from one of the servers that the instantiation fails on. I believe that the problem is that the Linq query to populate DefaultHandler.Currencies does not omit duplicate ISOCurrencySymbols when grouping by LCID and CurrencyEnglishName (lines 17 through 33 in CurrencyHandler.cs).

LCID.csv

Thanks in advance! Joe

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.