sm-mustache
aka SM\Mustache
is
a template processor◥
implementation of mustache templates◥
written in PHP◥
and compatible with
mustache specification◥
in its reasonable parts.
the main chapters of this document are
syntax definitions
and usage instructions.
what is templating language?
A templating language is a specific syntax or markup that allows for the creation of templates. It provides ways to define placeholders, variables, conditions, loops, and other elements that can be used to generate dynamic content.
On the other hand, a template processor is a software or tool that takes a template written in a templating language and applies it to specific data to generate an output. It interprets the template using the defined syntax rules and processes the data accordingly. The template processor replaces the placeholders with actual values and executes any logic defined in the template. It can also handle other operations like rendering the template into a final output format, such as HTML, JSON, or plain text.
In summary, a templating language is the language in which templates are written, while a template processor is the software or tool that interprets and processes those templates to generate the desired output. The templating language provides the rules and structure, while the template processor executes those rules to generate the final result.
are template processors relevant these days?
Yes, template processors are still relevant these days. Template processors are tools or software libraries that allow you to generate dynamic content by combining static text templates with placeholder variables and logic. They are commonly used in web development, email generation, document generation, and other applications where dynamic content creation is required.
Template processors provide a convenient way to separate the design and presentation of content from the logic or data that drives it, making it easier to manage and maintain. They often support various template languages that offer syntax for conditionals, loops, and other control structures. This allows developers to dynamically generate content based on specific conditions or iterate over data collections.
Many popular programming frameworks and content management systems also incorporate template processors as a core feature. For example, Django uses its own template engine, and PHP has popular template processors like Smarty and Twig.
Overall, template processors continue to be relevant because they simplify dynamic content generation, improve code readability and maintainability, and streamline web development and other related tasks.
2008/04 CTemplate
Welcome to the C++ CTemplate system! (This project was originally called Google Templates, due to its origin as the template system used for Google search result pages, but now has a more general name matching its community-owned nature.
Motivation
A template system can be used to separate output formatting specifications, which govern the appearance and location of output text and data elements, from the executable logic which prepares the data and makes decisions about what appears in the output.
Template systems lie along a continuum of power versus separation. "Powerful" constructs like variable assignment or conditional statements make it easy to modify the look of an application within the template system exclusively, without having to modify any of the underlying "application logic". They do so, however, at the cost of separation, turning the templates themselves into part of the application logic.
This template system leans strongly towards preserving the separation of logic and presentation. It is intentionally constrained in the features it supports and, as a result, applications tend to require quite a bit of code to instantiate a template. This may not be to everybody's tastes. However, while this design limits the power of the template language, it does not limit the power or flexibility of the template system. This system supports arbitrarily complex text formatting. Many Google applications, including the "main" Google web search, use this system for formatting output.
Finally, this system is designed with an eye towards efficiency. Template instantiation is very quick, with an eye towards minimizing both memory use and memory fragmentation.
2008/04 Erlang Template Engine (Prototype)
Some may argue, that it is pointless to write one's own template engine. But what if there's no suitable one? Common template engines are too heavy, provide enormous functionality, most of which is unnecessary rubbish, if you use templates right. What do I mean by right use of templates? To answer this question, I would like you to answer another one: what templates are intended for? Templates are intended for representation, hence must not include any logic. Logic is for developers, templates are for designers. Only template engines following this concept but still fully-functional I'm aware of are PHPLib's Template and Google CTemplate. Update: Plus Template::Simple for Perl.
Several years ago I wrote a template engine in Perl, which used PHPLib syntax, but had object-oriented approach for templates, so that every template or block inside a template was an object. If I get round to that, I'll clean up the code and post it in this blog later, and perhaps switch to Google CTemplate syntax, which I discovered recently.
If we deal with an object-oriented language, the natural way of representing a template is wrapping it into an object, using object's properties for template's variables and regarding nested templates as nested objects.
Erlang is a functional programming language. What is the natural way to express anything in a functional programming language? Right, wrap it into a function! In this article I present my experiment of creating a template engine using functional paradigm.
My Erlang template engine uses Google CTemplate syntax. Template variables are expressed through function arguments, and nested templates are expressed through descent calls to other functions.
2009/09 mustache.rb
Inspired by ctemplate and et, Mustache is a framework-agnostic way to render logic-free views.
As ctemplates says, "It emphasizes separating logic from presentation: it is impossible to embed application logic in this template language."
For a list of implementations (other than Ruby) and tips, see http://mustache.github.io/.
Overview
Think of Mustache as a replacement for your views. Instead of views consisting of ERB or HAML with random helpers and arbitrary logic, your views are broken into two parts: a Ruby class and an HTML template.
We call the Ruby class the "view" and the HTML template the "template."
All your logic, decisions, and code is contained in your view. All your markup is contained in your template. The template does nothing but reference methods in your view.
This strict separation makes it easier to write clean templates, easier to test your views, and more fun to work on your app's front end.
Why?
I like writing Ruby. I like writing HTML. I like writing JavaScript.
I don't like writing ERB, Haml, Liquid, Django Templates, putting Ruby in my HTML, or putting JavaScript in my HTML.
2009/10 mustache.js
mustache.js is a zero-dependency implementation of the mustache template system in JavaScript.
Mustache is a logic-less template syntax. It can be used for HTML, config files, source code - anything. It works by expanding tags in a template using values provided in a hash or object.
We call it "logic-less" because there are no if statements, else clauses, or for loops. Instead there are only tags. Some tags are replaced with a value, some nothing, and others a series of values.
The Parable of Mustache.js
In October 2009 Defunkt released mustache.rb. It looked useful to me at the time, but I needed it in JavaScript. The code was just under 200 lines and simple enough that I thought I could port it over on a lazy Sunday, or Saturday, I forget.
So I did, and to this day, I believe my biggest contribution to mustache.js was not all the fancy stuff that happened since, but the initial transliteration of Ruby to JavaScript. Yes, transliteration, Ruby and JavaScript are close enough that I did a more or less line by line port of the original code. I can’t claim I did any programming myself there.
If you look at the early versions of both projects, they look remarkably similar
mustache.rb was a huge hit in Ruby land right away and soon after I did my port a number of very smart people came around and rewrote the existing RegEx based “parser” (eugh) into a proper parser and compiler combination to make sure all proper computer science theory is applied and things run most efficient.
I looked back at the code then and couldn’t find my way around, things were split over dozens of files and I am sure it all made sense to a very organised brain, but I couldn’t even begin to start to understand what the whole thing did. And I remember distinctly that I was happy that I found mustache.rb in the before state so that I had a chance to port it. I would never have begun to try if I had found the “more optimal” solution.
The realisation there though made me also a bit sad. I am all for doing the right thing and executing things faster and everything, but it bugged me that the clarity of the code was all gone now.
Then other smart people came and wanted to do the same for mustache.js and I was hesitant. But instead of trying to argue with clarity, because fuck that if we can make it 10x faster, my argument was that these parsers & compilers are way too long for a JavaScript implementation that was targeted at the browser. My tagline was “by the time your compiler is finally done downloading, my RegEx parser has already produced a result”. It was of course a bit of a cop-out, but I got quite far with it.
Until Nate showed up and wrote a parser/compiler in ~350 lines of code (that was roughly the acceptable limit for me then) and I finally lost that argument too. But it took a while until mustache.js finally got its well needed superpowers, with huge help from Michael.
Meanwhile, I met Yehuda Katz at a conference and someone introduced me as “Jan, he does CouchDB and mustache.js” and Yehuda just said “Oh boy, I’m going to upset you soon.” and then avoided me for the rest of the conference (or it was just coincidence, I don’t know :). A week later he released handlebars.js, a mustache.js-inspired templating engine with some extra features that were really useful. But it wasn’t according to spec, just close enough, that a lot of people started using it. I thought “good for them”, but I secretly (or not so secretly) wanted to steal the best ideas and get people to use mustache.js instead. That didn’t really go anywhere because people stopped working on the spec in a sensible manner and we couldn’t agree on more features. And eventually, I spent more time with CouchDB again.
In the meantime many more mustache implementations in other languages appeared and one implicit design goal between was it to allow sharing of templates between them and produce compatible output. There is even a spec.
Around the same time Twitter started using mustache.js in the frontend and it was a great honour. But they too preferred a proper parser/compiler implementation so @fat & team went ahead and wrote Hogan, a mustache.js-compatible implementation with a faster load and runtime. @fat talked about this at his 2012 JSConf US talk, he got asked why he didn’t submit a pull request to mustache.js instead of making a new project. He said that a Pull Request that consists of “Hey, I threw away all your code and replaced it with mine” isn’t such a good idea. I agree, it wouldn’t have left the best impression.
He praised though, that they were able to build a compatible implementation that could compete on its own merits while still being compatible with a spec and he said that all library development should be done that way. Too often we conflate a great idea with its implementation and we would be better of allowing different ones and let users pick and choose their poison.
While watching @fat speak, it dawned on me that if I look at things this way, then the RegEx implementation of mustache.js is just another implementation of the same spec and its merits are tight and accessible code. And I could have hugged him for it. In fact I ran up on stage during the talk and nearly ran him over (22m:15s). My beloved implementation still had a reason to exist and that made me very happy. Until today, the 0.4 branch of mustache.js is still around.
Lessons Learned:
Accessible code has merit over optimised code, even if the optimised code should be used most of the time.
Build libraries with a spec, encourage competing implementations. (We plan to do this for Hoodie)
Hugs win.
The state of mustache today is alright, the implementations are all reasonably mature projects and people just use them. But there are a few that want to move it forward but are hindered by the complacency of the early adopters who have moved on to other things (including me). I hope we can get that resolved eventually.
On that note, mustache.js has a number of interesting Pull Requests and issues open and if anyone is interested in taking over maintainership, please make yourself heard in on the GitHub project.
2010/07 handlebars.js
Handlebars provides the power necessary to let you build semantic templates effectively with no frustration. Handlebars is largely compatible with Mustache templates. In most cases it is possible to swap out Mustache with Handlebars and continue using your current templates.
Checkout the official Handlebars docs site at handlebarsjs.com and try our live demo.
Precompiling Templates
Handlebars allows templates to be precompiled and included as javascript code rather than the handlebars template allowing for faster startup time. Full details are located here.
Differences Between Handlebars.js and Mustache
Handlebars.js adds a couple of additional features to make writing templates easier and also changes a tiny detail of how partials work.
- Nested Paths
- Helpers
- Block Expressions
- Literal Values
- Delimited Comments
Block expressions have the same syntax as mustache sections but should not be confused with one another. Sections are akin to an implicit each or with statement depending on the input data and helpers are explicit pieces of code that are free to implement whatever behavior they like. The mustache spec defines the exact behavior of sections. In the case of name conflicts, helpers are given priority.
Compatibility
There are a few Mustache behaviors that Handlebars does not implement.
- Handlebars deviates from Mustache slightly in that it does not perform recursive lookup by default. The compile time compat flag must be set to enable this functionality. Users should note that there is a performance cost for enabling this flag. The exact cost varies by template, but it's recommended that performance sensitive operations should avoid this mode and instead opt for explicit path references.
- The optional Mustache-style lambdas are not supported. Instead Handlebars provides its own lambda resolution that follows the behaviors of helpers.
- Handlebars does not allow space between the opening {{ and a command character such as #, / or >. The command character must immediately follow the braces, so for example {{> partial }} is allowed but {{ > partial }} is not.
- Alternative delimiters are not supported.
2011/12 hogan.js
Features
Hogan is fast--try it on your workload.
Hogan has separate scanning, parsing and code generation phases. This way it's possible to add new features without touching the scanner at all, and many different code generation techniques can be tried without changing the parser.
Hogan exposes scan and parse methods. These can be useful for pre-processing templates on the server.
It's also possible to use HoganTemplate objects without the Hogan compiler present. That means you can pre-compile your templates on the server, and avoid shipping the compiler. However, the optional lambda features from the Mustache spec require the compiler and the original template source to be present.
Hogan also supports template inheritance, and maintains compatibility with other implementations like mustache.java, mustache.php, and GRMustache
Why Hogan.js?
Why another templating library?
Hogan.js was written to meet three templating library requirements: good performance, standalone template objects, and a parser API.
logic-less
- in comparison with other template processors, where data is sliced and diced inside the template, this implementation strives to control the data flow through the template. in practice, it looks like more concise syntax with selections instead of cryptic expressions. thus, less mental load - more templating.data for template
- template is more important than data, a variety sets of data may fit into a single template. paths used in the template should look nice, readable and explanatory. the question of escaping sigils to accommodate data should never arise - avoidtemplate for data
at all costs, do a preliminary data massaging when necessary.not null
- a null◥ is the absence of data. the absence of data leads to the search for imperative solution in the template, which contradicts thelogic-less
principle. thus, to fill the gaps, the use of helpers is recommended - replace nulls with special case values.
null's author (Tony Hoare) about null
if you want to avoid using null then you have to invent a whole sort of sub-language for initializing
sub-language here is a bits of php code that stuff your data with flags and summaries determined over that data; i call them helper routines or render helpers or simply helpers.
running in the JIT mode◥, this implementation is comparable to various JS implementations:
The army consists of the first infantry division and eight million replacements.
Sebastian Junger
the smallest unit of mustache language◥ is the clause◥ which is removed or replaced◥ in the resulting output.
a clause consists of:
- delimiters
- sigil (optional)
- path or literal◥ or annotation◥ (optional)
there are two kinds of clauses:
for better appearance◥, all clause components - delimiters, sigil and path can align with each other◥ using whitespace◥.
delimiters◥ are
a pair of markers
that frame the stem◥
of a clause in the template.
the default are {{
and }}
which look like a moustache◥.
the first delimiter is called opening, the second - closing.
what balanced delimiters are?
Balanced delimiters refer to a pair of opening and closing symbols, such as parentheses "()," brackets "[]," or curly braces "{}," used in programming languages and mathematical expressions to group and organize elements. They are called balanced because they ensure that the order and nesting of the delimiters are proper and consistent.
The term "balanced" implies that for a set of delimiters to be considered balanced, every opening symbol must have a corresponding closing symbol, and they must match in terms of type (e.g., an opening parenthesis matches with a closing parenthesis). Moreover, the order in which the delimiters appear must follow a hierarchical structure, meaning that each opening delimiter must be closed before a higher-level delimiter can be closed.
For example, the expression "(2 + 3) * [4 / {5 - 1}]" contains balanced delimiters because each opening symbol has a corresponding closing symbol, and the nested order of the delimiters is correct. If any opening or closing symbol is missing, or if the order of the delimiters is incorrect, the expression would be considered unbalanced.
custom delimiters can be
set once during instantiation.
some examples: {: :}
, /* */
, <{ }>
, <[ ]>
,
<% %>
, [% %]
, {% %}
, (( ))
, [[ ]]
,
<!--{ }-->
, {{{ }}}
.
such balanced delimiters do not have to mirror each other or be of equal length, but they must differ to avoid delimiter collision. a single character may also be used as a delimiter, but:
There is no such thing as accident; It is fate misnamed.
Napoleon Bonaparte
what is delimiter collision?
Delimiter collision refers to a situation where the chosen delimiter used to separate data fields within a dataset appears within the actual data itself, leading to ambiguity and confusion.
This problem often arises in data processing, particularly when dealing with structured data formats such as CSV (comma-separated values) files or plain text documents with delimiters. These delimiters are crucial for software to correctly identify and separate the individual data fields.
When the chosen delimiter is mistakenly present within the data, it can cause issues during parsing or processing the data. For example, if a comma is chosen as the delimiter and a data field contains a comma as part of its value, the software may incorrectly interpret the comma within the field as a delimiter, resulting in the data being split into undesired parts.
Similarly, if a delimiter collision occurs in a plain text document, it may cause problems when extracting or analyzing the data using text mining or natural language processing techniques. This can lead to errors, loss of data integrity, and inaccurate analysis or insights.
To address delimiter collision, various strategies can be employed. One approach is to use a different delimiter or to select more unique and less likely characters as delimiters, such as special characters or combinations of characters. Another approach is to employ escaping techniques, where the delimiter within the data is preceded by an escape character to indicate that it should be treated as part of the data rather than a delimiter.
Overall, delimiter collision is an important consideration when working with datasets and requires careful planning and handling to ensure accurate and reliable data processing.
a sigil◥ is a symbol◥ that is suffixed◥ to the opening delimiter.
a sigil effectively denotes the type of clause:
&
- variable (optional modifier)!
- commentary#
,^
,@
- primary section|
- alternative section/
- terminus
a path◥ consists of names and dots◥ that indicate the address of a value◥ on the context stack.
every name in the path denotes an array key◥ or object property◥ which should exist within the container◥ somewhere on the stack.
it is crusial to follow a rigid naming convention◥ - use of alphanumeric characters◥ and one of word combinatory practices - hyphen-case◥, camelCase◥ or snake_case◥ is recommended. the name cannot contain a dot◥ or whitespace◥ characters - they have a special meaning.
a path that is prefixed◥ with one or multiple dots is called absolute◥. the number of dots represents a backpedal that enables explicit value addressing◥ on the stack.
examples: .name
, ..name
, ...name
a special case of an absolute path is when it contains no names at all - the value is simply taken from the stack.
examples: .
, ...
, .....
a path that is not absolute (has no backpedal) is called relative◥ - its first name is searched once◥ on the stack. the search◥ is conducted inside containers◥, starting from the top of the stack.
examples: name
, name-of-the-city
, nameOfTheFunction
relative path cannot be empty, it always consists of one or multiple names.
multiple names in the path are joined◥ with the dot◥ character.
examples: user.name
, ..absolute.path
, relative-path.to.value
lets examine how the dot notation applies
for the first.second
path:
there are two names - the first
name
is looked up and found on the stack,
it points to the container in which
the second
name resides.
the second
name is associated with
a value that is returned as the result
of path resolution.
for the first.second.third
path - the rule extrapolates -
the third
value must be extracted from the second
container
which must be extracted from the first
container
which must be found on the stack.
when a
space◥ character
is met in the path, it denotes the end of
the path that resolves to lambda and
the start of the argument◥
to that lambda◥.
the lambda is invoked during the rendering process and
the result of the call is taken as a value the path resolves.
for example in the utils.isEven _index
path,
the utils.isEven
is the actual relative path
that should resolve to lambda function◥
and the _index
literal◥
will be passed as its argument.
a variable◥ is an independent◥ clause of mustache language◥ that consists of delimiters and path.
examples: {{relative.path}}
, {{.absolute.path}}
,
{{random 1,100}}
, {{.}}
Make everything as simple as possible, but not simpler.
Albert Einstein
a variable◥
may be affixed◥
with the &
sigil -
a modifier◥
that controls the escaping
of the value.
escaping◥
is only a historical title for
a common post-processing mechanism
which is disabled by default -
the affix has no effect.
examples: {{&user-input}}
, {{&.code.javascript}}
a comment◥
is an independent◥
clause
of mustache language◥
that consists of delimiters,
the !
sigil
and annotation◥:
{{!
When I read commentary
about suggestions for where C should go,
I often think back and give thanks that
it wasn't developed under
the advice of a worldwide crowd.
Dennis Ritchie 🚀
}}
upon rendering, a comment is stripped◥ from the resulting output.
a block◥ consists of one or more sections◥ and a terminus.
a section◥ consists of a dependent◥ clause and a section's content◥ that ends with a terminus or another section.
A dependent clause is like a dependent child: incapable of standing on its own but able to cause a lot of trouble.
William Safire
the first section of a block aka the primary section - denotes the block type. its clause represents a conditional construct◥ that tests resolved value◥ for truthiness or falsiness - the result◥ influences the way the block renders.
available block types (sigils) are:
#
,@
- expects truthy^
- expects falsy
any subsequent section◥
is called an alternative section
and its clause is affixed◥
with |
sigil.
upon rendering, a block is removed or replaced◥ as a whole.
I cannot comprehend how any man can want anything but the truth.
Marcus Aurelius
the path of a primary section resolves to a value◥ which is coerced◥ to a boolean◥. when it coerces to false - its falsy, otherwise, it is true and truthy.
this mustache◥ implementation defines the following falsy values:
null
- value not found or lambda returns nullfalse
- boolean◥ (without coercion)0
- zero number◥''
- empty string◥[]
- empty array◥ or countable object◥
every other value - is truthy value.
this block type is the simplest - its primary section is rendered only when the path resolves to falsy.
{{^user.active}}
user {{user.name}} is not active
{{/}}
{{^array-count}}
array is empty
{{/}}
note that countable container◥ value is negated◥ by its elements count:
{{^array}}
array is empty
{{/}}
the primary section of this block is rendered only when the path resolves to truthy.
for a boolean value, it behaves the same as negated◥ falsy block:
{{#user.active}}
user {{user.name}} is active
{{/}}
for a non-boolean scalar (string or number) or
for a non-countable container (array or object),
the value is pushed to the context stack
and becomes a temporary helper that is accessible
with a .
backpedal
and is pulled after a single render:
{{#array-count}}
number of elements in array: {{.}}
{{/}}
{{#user}}
user name is {{.name}}
{{/}}
for a countable container◥ (array or object), the value is iterated - each element is pushed to the context stack, rendered and pulled out:
{{#array}}
value={{.}};
{{/}}
that is, the primiary section is rendered as many times, as many elements are in the container, but the stack expands only for a single value.
this block improves the iteration capability◥ of the truthy block and introduces traversion capability. a truthy value resolved from the path must be a countable container◥, otherwise a processing exception will be thrown.
this implementation designates index-based arrays for iteration, while key-based aka associative arrays for traversion.
a set of auxiliary variables is created for both variants:
_count
- number of elements_first
- first iteration/traversion indicator◥_last
- last iteration/traversion indicator◥_index
- current iteration/traversion number◥_key
- current traverion name string◥
these enhance the rendering of containers◥:
{{@array}}
{{_index}}={{.}};
{{/}}
{{@array}}
{{.}}{{^_last}},{{/}}
{{/}}
{{@path.to.traversable.object}}
{{_key}}: {{.}}{{^_last}},{{/}}
{{/}}
while rendering, both iteration and traversal
expand the stack with the current value
which may be accessed with .
backpedal.
auxiliary variables have
a similar _
backpedal,
but they live in a separate stack and
only pertain to iterator blocks:
date,time,info;
{{@events}}
{{@.}}
{{__key}},{{_key}},{{.}};
{{/}}
{{/}}
It is important to note that iteration requires that all elements in the container are of the same type.
every block type may have an alternative section◥ that renders once the primary condition is unmet and it does not push anything to the stack:
{{#number}}
the number is {{.}}
{{|}}
the number is zero or 0 or not found
{{/}}
{{^number}}
the number is zero or 0 or not found
{{|}}
the number is non-zero, {{number}}
{{/}}
being the opposite of the primary conditional◥, the OR section◥ construct allows to save on blocks count:
{{#array-count}}
array is not empty
{{/}}
{{^array-count}}
array is empty
{{/}}
VS
{{#array-count}}
array is not empty
{{|}}
array is empty
{{/}}
a profound application may be found with iterators:
{{@array}}
index={{_index}},value={{.}};
{{|}}
array is empty
{{/}}
a truthy block or a falsy block that contains a conditional OR section aka CASE section is called a switch block◥:
{{#path.to.value}}
TRUTHY default,
will match non-zero number or non-empty string
{{|}}
FALSY default,
will only match the empty string since
there is a CASE with number zero
{{|0}}
will match 0,"0"
{{|1}}
will match 1,"1"
{{|2}}
will match 2,"2"
{{|hello}}
will match "hello" string
{{/}}
the CASE section contains a literal◥ which is matched in the first place:
{{^number-that-is-zero}}
will never render
{{|0}}
will render
{{/}}
both primary section and OR section without a literal◥ aka unconditional CASE section may be represented as default cases in an underlying metalanguage◥:
switch (stringify(path.to.value)) {
case "0":
# ...
break;
case "1":
# ...
break;
case "2":
# ...
break;
case "hello":
# ...
break;
default:
if (path.to.value)
{
# TRUTHY default
# ...
}
else
{
# FALSY default
# ...
}
break;
}
a fallthrough CASE section◥ is also possible:
{{^number}}
zero
{{|1|2|3}}
one or two or three
{{|
4|5
}}
four or five
{{|6}}
six
{{|}}
unknown number
{{/}}
the path of this block must resolve to a numeric or string value, otherwise a processing exception is thrown. to match CASE section literals, a number is coerced◥ to a string.
a terminus◥
is a dependent◥
clause
of mustache language◥
that consists of delimiters,
the /
sigil
and an optional annotation◥.
the only purpose of a terminus◥ is to terminate the block◥.
{{/
a terminus clause may look like a comment, irrelevant,
but it depends on block's might, without it,
there will be a terrible failure - a barbarian invasion!
the opposite is also true - when a terminus is plowed
by ignorant peasant, the block demarks itself as open
to worldwide scum - a terrible failure!
}}
to start rendering templates -
an instance◥
of SM\Mustache
must be created:
$m = \SM\Mustache::new();# with default options
a prepare
method is used
either for preparations or
for experimenting◥
with the engine:
echo $m->prepare(# outputs: "Hello world!"
'Hello {{name}}!', # 0: template
['name'=>'world'] # 1: data
);
echo $m->prepare(# outputs: "Hello world!"
'Hello {.}!', # 0: template
'world', # 1: data
'{ }' # 2: custom delimiters
);
both opening and closing delimiters
are passed as a single string
separated with a
space character:
$m = \SM\Mustache::new([
'delims' => '<< >>'
]);
echo $m->prepare(# outputs: "Hello world!"
'Hello <<.>>!',
'world'
);
post-processing is disabled by default,
to enable a simple HTML escaping◥
pass the true
:
$m = \SM\Mustache::new([
'escape' => true
]);
echo $m->prepare(# outputs: "Hello O'Reily!"
'Hello {{&name}}!',
['name' => "O'Reily"]
);
to do a custom..
by default, boolean◥
values (either true
or false
) render to empty.
for example, in the .name
path,
a single dot◥
points to the top of the stack,
which must be the container◥
where the name
resides.
in the ..name
, there is a double dot
pointing downwards (top to bottom)
to the second container◥
on the stack and the name
value inside that container.
in the ...name
, a triple dot points to
the third value on the stack
and so on..
internally, mustache instance represents a stack◥.
any composite data◥ (an array◥ or an object◥) pushed to the stack prior to template processing is called a helper aka helper data aka data that helps in rendering.
helpers may be set at instantiation:
$m = SM\Mustache::new([# push one
'helper' => $helper1
]);
$m = SM\Mustache::new([# push many
'helpers' => [$helper1, $helper2, $helper3]
]);
or afterwards:
$m->push($helper1);
$m->push($helper2)->push($helper3);
they can be removed with:
$m->pull();# removes $helper3
$m->pull(true);# removes all
$m = SM\Mustache::new([
'helpers' => [
['name' => 'Joe', 'age' => 81, 'another' => ['name' => 'Sleepy']],
['name' => 'Barak', 'age' => 62],
['name' => 'Donald','another' => ['term' => 2024]]
]
]);
$m->value('name');# Donald
$m->value('age');# 62
$m->value('another.name');# NOT FOUND ~ null
$m->value('...another.name');# Sleepy
when .
precedes a path, the value is fetched
rather than looked up, that is,
the .
selector points to the top of the stack,
..
to the second value from the top, etc.
echo $m->value('.name');# Donald
echo $m->value('..name');# Barak
echo $m->value('...name');# Joe
There are no secrets to success. It is the result of preparation, hard work, and learning from failure.
Colin Powell
delimiters are set once for the instance◥ :
$m = SM\Mustache::new(['delims' => '<% %>']);
or, arbitrarily, with preparational methods :
$txt = $m->prepare($template, $data, '[[ ]]');
$id = $m->prep($template, '{: :}');;
2
3
yes, that's a feature i want to deviate a bit. make it opt in instead of opt out, meaning that {{&userInput}} is whats going to be escaped. im not sure how to do that, maybe an option to the instance that inverts default behavior. it is based on HTML and proposition that templates are typed by users. developer knows exactly what "stage" currently is and whether it s a user input or template composition.
oke, let me explain how i see stage things in the modern template EVALuator. HTML-only days are over, so it must be generic view.
stage 1: preparation templates should be somehow beautiful and easy to read and to modify - for example, check how beautiful are json spec files - long one-liners with zero indentation, yaml files, in this case, arent the source of truth, because they bypass stage 1 implicitly. json is what we need, big lengthy json objects with infinite one-liners.. (put some irony here).
i think you agree that template storage represents a hasmap - name=>template content, it can be filename (still a name) or json object key, also a name bound with content - string type. php arrays are better than JSON files, aesthetically. i bet any language does better job in template sourcing - better representation for a developer.
single template, one may wish to see is:
{:BR:}{:TAB:} Hey, {{name}}, you are {:#ansi_color red:}{{age}}{:/ansi_color:} years old!
this tiny indentation brings an issue to non-HTML sources. "remove indentation around standalone blocks" feature in mustache doesnt fully do OUTdentation. the issue is obscure in HTML, because HTML usually eats any whitespace in the view. same as in yaml files, you dont see it. i think that explicit outdentation is required. mine has two methods for this stage:
function outdent(string $template): string; function prepare(string $template, mixed $data=null, string $customDelimiters=''): string
the process may look like
foreach ($templateStorage as $name => &$template) { $template = $m->prepare($m->outdent($template), $initialData, '{: :}'); } initial data may look like
[ 'BR' => "\n", 'TAB' => "\t", 'ansi_color' => (function(..){..}), ] now template is explicitly indented for the target source (console/terminal that supports ansi sequences)
stage 2: compilation or cache preSET this is completely optional. the only benefit of this stage is significant performance jump which is related to addressing mechanism - instead of copying potentially large strings and calculating hashes over them, you use identifiers. this not required for experiments or few template renderings. mine has a method:
function set(string $template): int the process is similar
foreach ($templateStorage as $name => &$template) { $template = $m->set($template); } now templates are integers. getting text back is possible.
stage 3: rendering is also optional. experiments and playgrounds dont need methods that use cache. few template renders are fine with prepare(). otherwise, yours is some kind of an app that renders periodically and able to benefit from caching and memoization. mine has:
function render(string $template, mixed $data=null): string; function get(int $template, mixed $data=null): string; rendering may look like
$result = $m->render($templateStorage[$name], $data);# or when precached $result = $m->get($templateStorage[$name], $data); regarding to lambdas, i suppose that most of them will live a short life at the stage 1 and wont be invoked periodically as projected for stage 3. there is no need for constant ansi wraps or other special/control character insertion in a thoughtful template.
@jgonggrijp
So if I understand correctly, your implementation makes two passes over the template, one as a kind of preprocessing/macro expansion step and one for the final rendering.
yes, stage 1 and 2 can be treated as a single step.
What does the use ($ANSI) notation, between the function parameter list and the function body, mean in PHP?
it only passes external variable for use. in JS that is achieved by closure scopes, phps have that use () clause.
@agentgt
type NODE = STRING | BOOL | NULL | NUMBER | OBJECT | LIST | NODE They do not know that "a" will even be there. However unlike the reflective model where it could be anything they just need to recursively pattern match the above.
With the reflective model you have to ask the type "tell me everything about you". In the transform model you ask "which of these types are you"?
i see it as optimization properties. i did some type memoization which is based on template then data, not data then template. im not determined about actual structure yet. for example {{lambda argument}} is clearly a function - because it has an argument - no need to typecheck here, so ill select fragility over implicit stability
also consider cache fragility:
$template = $m->outdent('
{{#list}} {{.}} {{/list}}
'); echo $m->render($template, ['list'=>[1,2,3,4,5]]);# prints 12345, template is cached echo $m->render($template, ['list'=>['one','two','three']]);# FATAL! - expecting integers in the array echo $m->prepare($template, ['list'=>['one','two','three']]);# prints onetwothree, template cache is ignored
powerful
parse exceptions render exceptions
1
2
3