Widened README to 100 columns.

Not sure why I had it at 80 before, though maybe I'm about to find out when I
push and the formatting is all hosed.
This commit is contained in:
Michael Bayne
2014-03-18 09:29:30 -07:00
parent 9861b54aa0
commit 5ca07f70f0
+108 -129
View File
@@ -1,56 +1,49 @@
This is a Java implementation of the [Mustache template This is a Java implementation of the [Mustache template language](http://mustache.github.com/).
language](http://mustache.github.com/). There exists [another Java There exists [another Java implementation of Mustache](http://github.com/spullara/mustache.java),
implementation of Mustache](http://github.com/spullara/mustache.java), but the but the motivations for this version are sufficiently different as to justify (in the author's
motivations for this version are sufficiently different as to justify (in the mind, anyhow) the duplication.
author's mind, anyhow) the duplication.
Motivations Motivations
=========== ===========
* Zero dependencies. You can include this single tiny library in your project * Zero dependencies. You can include this single tiny library in your project and start making
and start making use of templates. use of templates.
* Usability on a variety of target platforms. The other Java Mustache * Usability on a variety of target platforms. The other Java Mustache implementation requires
implementation requires that a Java compiler be available to compile that a Java compiler be available to compile templates into Java classes. This implementation
templates into Java classes. This implementation makes no such requirements makes no such requirements and as a result is usable on Android, or other exciting places where
and as a result is usable on Android, or other exciting places where a Java a Java compiler is not available. It is even possible to avoid the use of reflection and
compiler is not available. It is even possible to avoid the use of provide all of your data as a series of nested Maps, if desired.
reflection and provide all of your data as a series of nested Maps, if
desired.
* [Proguard](http://proguard.sourceforge.net/) and * [Proguard](http://proguard.sourceforge.net/) and [JarJar](http://code.google.com/p/jarjar/)
[JarJar](http://code.google.com/p/jarjar/) friendly. Though the library friendly. Though the library will reflectively access your data (if you desire it), the library
will reflectively access your data (if you desire it), the library makes no makes no other internal use of reflection or by name instantiation of classes. So you can embed
other internal use of reflection or by name instantiation of classes. So it using Proguard or JarJar without any annoying surprises.
you can embed it using Proguard or JarJar without any annoying surprises.
* Minimal API footprint. There are really only two methods you need to know * Minimal API footprint. There are really only two methods you need to know about: `compile` and
about: `compile` and `execute`. You can even chain them together in cases `execute`. You can even chain them together in cases where performance is of no consequence.
where performance is of no consequence.
Its existence justified by the above motivations, this implementation then Its existence justified by the above motivations, this implementation then strives to provide
strives to provide additional benefits: additional benefits:
* It is available via Maven Central, see below for details. * It is available via Maven Central, see below for details.
* It is reasonably performant. Templates are parsed separately from * It is reasonably performant. Templates are parsed separately from execution. A template will
execution. A template will specialize its variables on (class of context, specialize its variables on (class of context, name) pairs so that if a variable is first
name) pairs so that if a variable is first resolved to be (for example) a resolved to be (for example) a field of the context object, that will be attempted directly on
field of the context object, that will be attempted directly on subsequent subsequent template invocations, and the slower full resolution will only be tried if accessing
template invocations, and the slower full resolution will only be tried if the variable as a field fails.
accessing the variable as a field fails.
Get It Get It
====== ======
JMustache is available via Maven Central and can thus be easily added to your JMustache is available via Maven Central and can thus be easily added to your Maven, Ivy, etc.
Maven, Ivy, etc. projects by adding a dependency on projects by adding a dependency on `com.samskivert:jmustache:1.9`. Or download the
`com.samskivert:jmustache:1.9`. Or download the [pre-built jar [pre-built jar file](http://repo1.maven.org/maven2/com/samskivert/jmustache/1.9/jmustache-1.9.jar).
file](http://repo1.maven.org/maven2/com/samskivert/jmustache/1.9/jmustache-1.9.jar).
Usage Usage
===== =====
Using JMustache is very simple. Supply your template as a `String` or a Using JMustache is very simple. Supply your template as a `String` or a `Reader` and get back a
`Reader` and get back a `Template` that you can execute on any context: `Template` that you can execute on any context:
String text = "One, two, {{three}}. Three sir!"; String text = "One, two, {{three}}. Three sir!";
Template tmpl = Mustache.compiler().compile(text); Template tmpl = Mustache.compiler().compile(text);
@@ -65,8 +58,8 @@ Use `Reader` and `Writer` if you're doing something more serious:
Mustache.compiler().compile(template).execute(data, out); Mustache.compiler().compile(template).execute(data, out);
} }
The execution context can be any Java object. Variables will be resolved via The execution context can be any Java object. Variables will be resolved via the following
the following mechanisms: mechanisms:
* If the context is a `Map`, `Map.get` will be used. * If the context is a `Map`, `Map.get` will be used.
* If a non-void method with the same name as the variable exists, it will be called. * If a non-void method with the same name as the variable exists, it will be called.
@@ -96,23 +89,25 @@ Example:
// Elvis: 75 // Elvis: 75
// Madonna: 52 // Madonna: 52
As you can see from the example, the fields (and methods) need not be public. As you can see from the example, the fields (and methods) need not be public. The `persons` field
The `persons` field in the anonymous class created to act as a context is in the anonymous class created to act as a context is accessible. Note that the use of non-public
accessible. Note that the use of non-public fields will not work in a sandboxed fields will not work in a sandboxed security environment.
security environment.
Sections behave as you would expect: Sections behave as you would expect:
* `Boolean` values enable or disable the section. * `Boolean` values enable or disable the section.
* Array, `Iterator`, or `Iterable` values repeatedly execute the section with each element used as the context for each iteration. Empty collections result in zero instances of the section being included in the template. * Array, `Iterator`, or `Iterable` values repeatedly execute the section with each element used as
* An unresolvable or null value is treated as false (by default, see _Default Values_ for more details). the context for each iteration. Empty collections result in zero instances of the section being
included in the template.
* An unresolvable or null value is treated as false (by default, see _Default Values_ for more
details).
* Any other object results in a single execution of the section with that object as a context. * Any other object results in a single execution of the section with that object as a context.
See the code in See the code in
[MustacheTest.java](http://github.com/samskivert/jmustache/blob/master/src/test/java/com/samskivert/mustache/MustacheTest.java) [MustacheTest.java](http://github.com/samskivert/jmustache/blob/master/src/test/java/com/samskivert/mustache/MustacheTest.java)
for concrete examples. See also the [Mustache for concrete examples. See also the
documentation](http://mustache.github.com/mustache.5.html) for details on the [Mustache documentation](http://mustache.github.com/mustache.5.html) for details on the template
template syntax. syntax.
Partials Partials
-------- --------
@@ -129,16 +124,14 @@ If you wish to make use of partials (e.g. `{{>subtmpl}}`) you must provide a
String tmpl = "...{{>subtmpl}}..."; String tmpl = "...{{>subtmpl}}...";
c.compile(tmpl).execute(); c.compile(tmpl).execute();
The above snippet will load `new File(templateDir, "subtmpl")` when compiling The above snippet will load `new File(templateDir, "subtmpl")` when compiling the template.
the template.
Lambdas Lambdas
------- -------
JMustache implements lambdas by passing you a `Template.Fragment` instance JMustache implements lambdas by passing you a `Template.Fragment` instance which you can use to
which you can use to execute the fragment of the template that was passed to execute the fragment of the template that was passed to the lambda. You can decorate the results of
the lambda. You can decorate the results of the fragment execution, as shown in the fragment execution, as shown in the standard Mustache documentation on lambdas:
the standard Mustache documentation on lambdas:
String tmpl = "{{#bold}}{{name}} is awesome.{{/bold}}"; String tmpl = "{{#bold}}{{name}} is awesome.{{/bold}}";
Mustache.compiler().compile(tmpl).execute(new Object() { Mustache.compiler().compile(tmpl).execute(new Object() {
@@ -154,8 +147,8 @@ the standard Mustache documentation on lambdas:
// result: // result:
<b>Willy is awesome.</b> <b>Willy is awesome.</b>
You can also obtain the results of the fragment execution to do things like You can also obtain the results of the fragment execution to do things like internationalization or
internationalization or caching: caching:
Object ctx = new Object() { Object ctx = new Object() {
Mustache.Lambda i18n = new Mustache.Lambda() { Mustache.Lambda i18n = new Mustache.Lambda() {
@@ -170,16 +163,16 @@ internationalization or caching:
<h2>{{#i18n}}title{{/i18n}</h2> <h2>{{#i18n}}title{{/i18n}</h2>
{{#i18n}}welcome_msg{{/i18n}} {{#i18n}}welcome_msg{{/i18n}}
Currently there is no support for "unexecuting" the template and obtaining the Currently there is no support for "unexecuting" the template and obtaining the original Mustache
original Mustache template text contained in the section. File a feature template text contained in the section. File a feature request with a sane use case if you have
request with a sane use case if you have one. one.
Default Values Default Values
-------------- --------------
By default, an exception will be thrown any time a variable cannot be resolved, By default, an exception will be thrown any time a variable cannot be resolved, or resolves to
or resolves to null. You can change this behavior in two ways. If you want to null. You can change this behavior in two ways. If you want to provide a value for use in all such
provide a value for use in all such circumstances, use `defaultValue()`: circumstances, use `defaultValue()`:
String tmpl = "{{exists}} {{nullValued}} {{doesNotExist}}?"; String tmpl = "{{exists}} {{nullValued}} {{doesNotExist}}?";
Mustache.compiler().defaultValue("what").compile(tmpl).execute(new Object() { Mustache.compiler().defaultValue("what").compile(tmpl).execute(new Object() {
@@ -190,9 +183,8 @@ provide a value for use in all such circumstances, use `defaultValue()`:
// result: // result:
Say what what? Say what what?
If you only wish to provide a default value for variables that resolve to null, If you only wish to provide a default value for variables that resolve to null, and wish to
and wish to preserve exceptions in cases where variables cannot be resolved, preserve exceptions in cases where variables cannot be resolved, use `nullValue()`:
use `nullValue()`:
String tmpl = "{{exists}} {{nullValued}} {{doesNotExist}}?"; String tmpl = "{{exists}} {{nullValued}} {{doesNotExist}}?";
Mustache.compiler().nullValue("what").compile(tmpl).execute(new Object() { Mustache.compiler().nullValue("what").compile(tmpl).execute(new Object() {
@@ -202,9 +194,9 @@ use `nullValue()`:
}); });
// throws MustacheException when executing the template because doesNotExist cannot be resolved // throws MustacheException when executing the template because doesNotExist cannot be resolved
When using a `Map` as a context, `nullValue()` will only be used when the map When using a `Map` as a context, `nullValue()` will only be used when the map contains a mapping to
contains a mapping to `null`. If the map lacks a mapping for a given variable, `null`. If the map lacks a mapping for a given variable, then it is considered unresolvable and
then it is considered unresolvable and throws an exception. throws an exception.
Map<String,String> map = new HashMap<String,String>(); Map<String,String> map = new HashMap<String,String>();
map.put("exists", "Say"); map.put("exists", "Say");
@@ -214,20 +206,18 @@ then it is considered unresolvable and throws an exception.
Mustache.compiler().nullValue("what").compile(tmpl).execute(map); Mustache.compiler().nullValue("what").compile(tmpl).execute(map);
// throws MustacheException when executing the template because doesNotExist cannot be resolved // throws MustacheException when executing the template because doesNotExist cannot be resolved
Note that section behavior deviates from the above specification (for Note that section behavior deviates from the above specification (for historical reasons and
historical reasons and because it's kind of useful). By default, a section that because it's kind of useful). By default, a section that is not resolvable or resolves to null will
is not resolvable or resolves to null will be omitted (and conversely, an be omitted (and conversely, an inverse section that is not resolvable or resolves to null will be
inverse section that is not resolvable or resolves to null will be included). included). If you use `defaultValue()`, this behavior is preserved. If you use `nullValue()`,
If you use `defaultValue()`, this behavior is preserved. If you use sections that refer to an unresolvable variable will now throw an exception (sections that refer to
`nullValue()`, sections that refer to an unresolvable variable will now throw a resolvable, but null-valued variable, will behave as before).
an exception (sections that refer to a resolvable, but null-valued variable,
will behave as before).
Extensions Extensions
========== ==========
JMustache extends the basic Mustache template language with some additional JMustache extends the basic Mustache template language with some additional functionality. These
functionality. These additional features are enumerated below: additional features are enumerated below:
Not escaping HTML by default Not escaping HTML by default
---------------------------- ----------------------------
@@ -280,9 +270,8 @@ Special variables
----------------- -----------------
### this ### this
You can use the special variable `this` to refer to the context object itself You can use the special variable `this` to refer to the context object itself instead of one of its
instead of one of its members. This is particularly useful when iterating over members. This is particularly useful when iterating over lists.
lists.
Mustache.compiler().compile("{{this}}").execute("hello"); // returns: hello Mustache.compiler().compile("{{this}}").execute("hello"); // returns: hello
Mustache.compiler().compile("{{#names}}{{this}}{/names}}").execute(new Object() { Mustache.compiler().compile("{{#names}}{{this}}{/names}}").execute(new Object() {
@@ -298,19 +287,17 @@ Note that you can also use the special variable `.` to mean the same thing.
}); });
// result: TomDickHarry // result: TomDickHarry
`.` is apparently supported by other Mustache implementations, though it does `.` is apparently supported by other Mustache implementations, though it does not appear in the
not appear in the official documentation. official documentation.
### -first and -last ### -first and -last
You can use the special variables `-first` and `-last` to perform special You can use the special variables `-first` and `-last` to perform special processing for list
processing for list elements. `-first` resolves to `true` when inside a section elements. `-first` resolves to `true` when inside a section that is processing the first of a list
that is processing the first of a list of elements. It resolves to `false` at of elements. It resolves to `false` at all other times. `-last` resolves to `true` when inside a
all other times. `-last` resolves to `true` when inside a section that is section that is processing the last of a list of elements. It resolves to `false` at all other
processing the last of a list of elements. It resolves to `false` at all other
times. times.
One will often make use of these special variables in an inverted section, as One will often make use of these special variables in an inverted section, as follows:
follows:
String tmpl = "{{#things}}{{^-first}}, {{/-first}}{{this}}{{/things}}"; String tmpl = "{{#things}}{{^-first}}, {{/-first}}{{this}}{{/things}}";
Mustache.compiler().compile(tmpl).execute(new Object() { Mustache.compiler().compile(tmpl).execute(new Object() {
@@ -318,16 +305,14 @@ follows:
}); });
// result: one, two, three // result: one, two, three
Note that the values of `-first` and `-last` refer only to the inner-most Note that the values of `-first` and `-last` refer only to the inner-most enclosing section. If you
enclosing section. If you are processing a section within a section, there is are processing a section within a section, there is no way to find out whether you are in the first
no way to find out whether you are in the first or last iteration of an outer or last iteration of an outer section.
section.
### -index ### -index
The `-index` special variable contains 1 for the first iteration through a The `-index` special variable contains 1 for the first iteration through a section, 2 for the
section, 2 for the second, 3 for the third and so forth. It contains 0 at all second, 3 for the third and so forth. It contains 0 at all other times. Note that it also contains
other times. Note that it also contains 0 for a section that is populated by a 0 for a section that is populated by a singleton value rather than a list.
singleton value rather than a list.
String tmpl = "My favorite things:\n{{#things}}{{-index}}. {{this}}\n{{/things}}"; String tmpl = "My favorite things:\n{{#things}}{{-index}}. {{this}}\n{{/things}}";
Mustache.compiler().compile(tmpl).execute(new Object() { Mustache.compiler().compile(tmpl).execute(new Object() {
@@ -342,9 +327,8 @@ singleton value rather than a list.
Compound variables Compound variables
------------------ ------------------
In addition to resolving simple variables using the context, you can use In addition to resolving simple variables using the context, you can use compound variables to
compound variables to extract data from sub-objects of the current context. For extract data from sub-objects of the current context. For example:
example:
Mustache.compiler().compile("Hello {{field.who}}!").execute(new Object() { Mustache.compiler().compile("Hello {{field.who}}!").execute(new Object() {
public Object field = new Object() { public Object field = new Object() {
@@ -358,22 +342,21 @@ By taking advantage of reflection and bean-property-style lookups, you can do ko
Mustache.compiler().compile("Hello {{class.name}}!").execute(new Object()); Mustache.compiler().compile("Hello {{class.name}}!").execute(new Object());
// result: Hello java.lang.Object! // result: Hello java.lang.Object!
Note that compound variables are essentially short-hand for using singleton Note that compound variables are essentially short-hand for using singleton sections. The above
sections. The above examples could also be represented as: examples could also be represented as:
Hello {{#field}}{{who}}{{/field}}! Hello {{#field}}{{who}}{{/field}}!
Hello {{#class}}{{name}}{{/class}}! Hello {{#class}}{{name}}{{/class}}!
Note also that one semantic difference exists between nested singleton sections Note also that one semantic difference exists between nested singleton sections and compound
and compound variables: after resolving the object for the first component of variables: after resolving the object for the first component of the compound variable, parent
the compound variable, parent contexts will not be searched when resolving contexts will not be searched when resolving subcomponents.
subcomponents.
Newline trimming Newline trimming
---------------- ----------------
If the opening or closing section tag are the only thing on a line, any newline If the opening or closing section tag are the only thing on a line, any newline following the tag
following the tag is trimmed. This allows for civilized templates, like: is trimmed. This allows for civilized templates, like:
Favorite foods: Favorite foods:
{{#people}} {{#people}}
@@ -389,21 +372,20 @@ which produces output like:
rather than: rather than:
Favorite foods: Favorite foods:
- Elvis Presley likes peanut butter. - Elvis Presley likes peanut butter.
- Mahatma Gandhi likes aloo dum. - Mahatma Gandhi likes aloo dum.
which would be produced without the newline trimming. Note: the current implementation does not
which would be produced without the newline trimming. Note: the current handle Windows-style CRLF data. If you're a Windows user, how about sending me a patch?
implementation does not handle Windows-style CRLF data. If you're a Windows
user, how about sending me a patch?
Nested Contexts Nested Contexts
--------------- ---------------
If a variable is not found in a nested context, it is resolved in the next If a variable is not found in a nested context, it is resolved in the next outer context. This
outer context. This allows usage like the following: allows usage like the following:
String template = "{{outer}}:\n{{#inner}}{{outer}}.{{this}}\n{{/inner}}"; String template = "{{outer}}:\n{{#inner}}{{outer}}.{{this}}\n{{/inner}}";
Mustache.compiler().compile(template).execute(new Object() { Mustache.compiler().compile(template).execute(new Object() {
@@ -416,17 +398,15 @@ outer context. This allows usage like the following:
// foo.baz // foo.baz
// foo.bif // foo.bif
Note that if a variable _is_ defined in an inner context, it shadows the same Note that if a variable _is_ defined in an inner context, it shadows the same name in the outer
name in the outer context. There is presently no way to access the variable context. There is presently no way to access the variable from the outer context.
from the outer context.
Invertible Lambdas Invertible Lambdas
------------------ ------------------
For some applications, it may be useful for lambdas to be executed for an For some applications, it may be useful for lambdas to be executed for an inverse section rather
inverse section rather than having the section omitted altogether. This allows than having the section omitted altogether. This allows for proper conditional substitution when
for proper conditional substitution when statically translating templates into statically translating templates into other languages or contexts:
other languages or contexts:
String template = "{{#condition}}result if true{{/condition}}\n" + String template = "{{#condition}}result if true{{/condition}}\n" +
"{{^condition}}result if false{{/condition}}"; "{{^condition}}result if false{{/condition}}";
@@ -460,9 +440,8 @@ InvertibleLambda whenever you need a single function with two modes of operation
Standards Mode Standards Mode
-------------- --------------
The more intrusive of these extensions, specifically the searching of parent The more intrusive of these extensions, specifically the searching of parent contexts and the use
contexts and the use of compound varables, can be disabled when creating a of compound varables, can be disabled when creating a compiler, like so:
compiler, like so:
Map<String,String> ctx = new HashMap<String,String>(); Map<String,String> ctx = new HashMap<String,String>();
ctx.put("foo.bar", "baz"); ctx.put("foo.bar", "baz");
@@ -472,7 +451,7 @@ compiler, like so:
Limitations Limitations
=========== ===========
In the name of simplicity, some features of Mustache were omitted or In the name of simplicity, some features of Mustache were omitted or simplified:
simplified:
* `{{= =}}` only supports one or two character delimiters. This is just because I'm lazy and it simplifies the parser. * `{{= =}}` only supports one or two character delimiters. This is just because I'm lazy and it
simplifies the parser.