Intro to Usematch

I was unhappy with the cheap and nasty, roll-it-your-own templating engine that I’d put up with in previous incarnations of ergo-cms. I’d seen quite a lot of clamour about Mustache and how great it is and was interested to see if it could drop into ergo-cms very easily, while still retaining the feature set that I’d become used to having.

Unfortunately, it fell flat. :sigh:

Although a popular engine, Mustache is surprisingly minimalist in quite a few areas, particularly that of extensibility, which in my eyes, is always a no-go. Then again, that’s the way those crazy ruby guys go, and so it shouldn’t have come as a surprise to me that the feature set of Mustache was rather locked down.

Looking at the code of Mustache, it seemed quite clear it was going to be that way forever when I saw this little gem:

 // Export the escaping function so that the user may override it.
  // See https://github.com/janl/mustache.js/issues/244
  mustache.escape = escapeHtml;

Wondering why, I looked at escapeHtml and saw that they were escaping some pretty strange things indeed:

 var entityMap = {
    '&': '&',
    '<': '<',
    '>': '>',
    '"': '"',
    "'": ''',
    '/': '/',
    '`': '`',
    '=': '='
  };
  function escapeHtml (string) {
    return String(string).replace(/[&<>"'`=\/]/g, function fromEntityMap (s) {
      return entityMap[s];
    });
  }

Hopefully someone can explain to me the logic of escaping quotes and slashes and equals to their unicode equivalent (especially and limited to the context of escaping HTML)!? I’ve always just escaped the first 4 on the list (&, <, > and “), although the last one (“) is completely uncessary.

But those things aside, I discovered that Mustache couldn’t handle a few things that I need for this CMS, which are:

  • The ability to define default properties in the template itself.
  • The ability to change what kind of encoding is used. I need to use the template engine for more than just HTML. I need it for XML and txt too!
  • The ability to dynamically ‘futz’ with the data, morphing it according to designer requirements.
Having also looked at a similar project Handlebars, I just didn’t ‘get it’. This is quite proably because it seems aimed at templating within the browser landscape, which is not a good fit for this project at all. Nevertheless, I didn’t see any solutions for the above requirements jumping up at me.

So, I decided that I needed to write a new templating engine, one that satifies my requirements, but is also 100% compatible with a mustache template. Three days later: voila! We have Usematch (it’s an anagram of Mustache). Much of the time was actually getting it to pass all of Mustache’s rendering tests. I’m pretty happy with it, and this post is using Usematch, along with the previous mentioned JSINF.

There’s still a feature I need to add in, however. Namely, the ability to run section blocks through a pre-filter. Post-filtering is working wonderfully:

const usematch = require('usematch');
var data = {
	title:"Hello World!",
	toUpper: function(value) { return value.toUpperCase(); }
}	
console.log(usematch.render('{{title #toUpper}}', data));
// prints: "HELLO WORLD!"

So, back to my pre-filter… With it you could do some data mangling (sorting it, & only returning the top 10 elements, etc). I had in mind this notation: {{#posts @filterThePosts}}, and parameters can be passed by name: {{#posts @customFilter{len:10} }}.

The script for handling something like this would look like:

var data = {
	posts:[
		{ title: "First Post!", ...}
		{ title: "Second Post!", ...}
	],
	customFilter: function(data, named_params) {
		data = data.sort( ... )
		return data.slice(named_params.len || 10);
	}
}

I’m beginning to like the idea of an ‘automagic’ filter, however:

var data = {
	posts:[
		{ title: "First Post!", ...}
		{ title: "Second Post!", ...}
	],
	posts.prefilter: data.customFilter,
	customFilter: function(data, named_params) {
		return data.sort( ... )
			.slice(named_params.len || 10);
	}
}

This way, when rendering a section, Usematch will look for section.prefilter and call the function with an arbitary set of params. It simplifies the Usematch markup considerably:

{{#posts @{len:5,sortBy:'date'} }}

This looks a lot more readable, no? The downside is that it’s limited to one filter per section. No big deal I think… especially since you could manually chain filters anyhow.

Happy days. :-D

<< Previous Next >>