| @@ -1,5 +1,9 @@ | |||||
| .DS_Store | |||||
| .rvmrc | |||||
| runner.js | runner.js | ||||
| jquery.mustache.js | jquery.mustache.js | ||||
| qooxdoo.mustache.js | |||||
| dojox | dojox | ||||
| yui3 | yui3 | ||||
| commonjs.mustache.js | |||||
| requirejs.mustache.js | |||||
| @@ -1,5 +1,5 @@ | |||||
| rvm: | rvm: | ||||
| - 1.9.2 | - 1.9.2 | ||||
| before_script: | before_script: | ||||
| - sudo apt-get install xulrunner-1.9 | |||||
| - sudo apt-get -y install xulrunner-2.0 | |||||
| - gem install rspec | - gem install rspec | ||||
| @@ -1,49 +0,0 @@ | |||||
| # mustache.js Changes | |||||
| ## 0.3.1-dev-twitter-b (8/23/2011) | |||||
| * Cached regexes for improved performance | |||||
| ## 0.3.1-dev-twitter (12/3/2010) | |||||
| * fixed double-rendering bug | |||||
| * added Rhino test-runner alongside JavaScriptCore | |||||
| ## 0.3.1 (??-??-????) | |||||
| ## 0.3.0 (21-07-2010) | |||||
| * Improved whitespace handling. | |||||
| * Make IMPLICIT ITERATORS a first class feature. | |||||
| * Fix Rhino compat. | |||||
| * CommonJS packaging is no longer a special case. | |||||
| * DRY Rakefile. | |||||
| * Allow whitespace around tag names. | |||||
| * Fix partial scope. | |||||
| * Fix Comments. | |||||
| * Added inverted sections. | |||||
| * Avoid double encoding of entities. | |||||
| * Use sections to dereference subcontexts. | |||||
| * Added higher order sections. | |||||
| ## 0.2.3 (28-03-2010) | |||||
| * Better error message for missing partials. | |||||
| * Added more robust type detection. | |||||
| * Parse pragmas only once. | |||||
| * Throw exception when encountering an unknown pragma. | |||||
| * Ignore undefined partial contexts. Returns verbatim partials. | |||||
| * Added yui3 packaging. | |||||
| ## 0.2.2 (11-02-2010) | |||||
| * ctemplate compat: Partials are indicated by >, not <. | |||||
| * Add support for {{%PRAGMA}} to enable features. | |||||
| * Made array of strings an option. Enable with `{{%JSTACHE-ENABLE-STRING-ARRAYS}}`. | |||||
| * mustache compat: Don't barf on unknown variables. | |||||
| * Add `rake dojo` target to create a dojo package. | |||||
| * Add streaming api. | |||||
| * Rename JSTACHE-ENABLE-STRING-ARRAYS to IMPLICIT-ITERATOR. | |||||
| * Add support for pragma options. | |||||
| @@ -1,348 +1,418 @@ | |||||
| # mustache.js — Logic-less templates with JavaScript | |||||
| # mustache.js - Logic-less {{mustache}} templates with JavaScript | |||||
| > What could be more logical awesome than no logic at all? | > What could be more logical awesome than no logic at all? | ||||
| For a list of implementations (other than JavaScript) and editor | |||||
| plugins, see <http://mustache.github.com/>. | |||||
| [mustache.js](http://github.com/janl/mustache.js) is an implementation of the | |||||
| [Mustache](http://mustache.github.com/) template system in JavaScript. | |||||
| [Mustache](http://mustache.github.com/) 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. | |||||
| ## Where to Use? | |||||
| 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. | |||||
| You can use mustache.js rendering stuff in various scenarios. E.g. you can | |||||
| render templates in your browser, or rendering server-side stuff with | |||||
| [node.js][node.js], use it for rendering stuff in [CouchDB][couchdb]’s views. | |||||
| For a language-agnostic overview of Mustache's template syntax, see the | |||||
| `mustache(5)` [manpage](http://mustache.github.com/mustache.5.html). | |||||
| ## Where to use mustache.js? | |||||
| ## Who Uses Mustache? | |||||
| You can use mustache.js to render templates in many various scenarios where you | |||||
| can use JavaScript. For example, you can render templates in a browser, | |||||
| server-side using [node](http://nodejs.org/), in [CouchDB](http://couchdb.apache.org/) | |||||
| views, or in almost any other environment where you can use JavaScript. | |||||
| An updated list is kept on the Github wiki. Add yourself, if you use | |||||
| mustache.js: <http://wiki.github.com/janl/mustache.js/beard-competition> | |||||
| ## Who uses mustache.js? | |||||
| An updated list of mustache.js users is kept [on the Github wiki](http://wiki.github.com/janl/mustache.js/beard-competition). | |||||
| Add yourself or your company if you use mustache.js! | |||||
| ## Usage | ## Usage | ||||
| A quick example how to use mustache.js: | |||||
| Below is quick example how to use mustache.js: | |||||
| var view = { | var view = { | ||||
| title: "Joe", | title: "Joe", | ||||
| calc: function() { | |||||
| calc: function () { | |||||
| return 2 + 4; | return 2 + 4; | ||||
| } | } | ||||
| } | |||||
| }; | |||||
| var template = "{{title}} spends {{calc}}"; | |||||
| var output = Mustache.render("{{title}} spends {{calc}}", view); | |||||
| var html = Mustache.to_html(template, view); | |||||
| In this example, the `Mustache.render` function takes two parameters: 1) the | |||||
| [mustache](http://mustache.github.com/) template and 2) a `view` object that | |||||
| contains the data and code needed to render the template. | |||||
| `template` is a simple string with mustache tags and `view` is a JavaScript | |||||
| object containing the data and any code to render the template. | |||||
| ### CommonJS | |||||
| mustache.js is usable without any modification in both browsers and [CommonJS](http://www.commonjs.org/) | |||||
| environments like [node.js](http://nodejs.org/). To use it as a CommonJS module, | |||||
| simply require the file, like this: | |||||
| ## Template Tag Types | |||||
| var Mustache = require("mustache"); | |||||
| There are several types of tags currently implemented in mustache.js. | |||||
| ## Templates | |||||
| For a language-agnostic overview of Mustache’s template syntax, see the | |||||
| `mustache(5)` manpage or <http://mustache.github.com/mustache.5.html>. | |||||
| A [mustache](http://mustache.github.com/) template is a string that contains | |||||
| any number of mustache tags. Tags are indicated by the double mustaches that | |||||
| surround them. `{{person}}` is a tag, as is `{{#person}}`. In both examples we | |||||
| refer to `person` as the tag's key. | |||||
| ### Simple Tags | |||||
| There are several types of tags available in mustache.js. | |||||
| Tags are always surrounded by mustaches like this `{{foobar}}`. | |||||
| ### Variables | |||||
| var view = {name: "Joe", say_hello: function(){ return "hello" }} | |||||
| The most basic tag type is a simple variable. A `{{name}}` tag renders the value | |||||
| of the `name` key in the current context. If there is no such key, nothing is | |||||
| rendered. | |||||
| template = "{{say_hello}}, {{name}}" | |||||
| All variables are HTML-escaped by default. If you want to render unescaped HTML, | |||||
| use the triple mustache: `{{{name}}}`. You can also use `&` to unescape a | |||||
| variable. | |||||
| Template: | |||||
| #### Accessing values in nested objects (Dot Notation) | |||||
| * {{name}} | |||||
| * {{age}} | |||||
| * {{company}} | |||||
| * {{{company}}} | |||||
| * {{&company}} | |||||
| To access data logically grouped into nested objects, specify a '.' delimited | |||||
| path to the value. | |||||
| View: | |||||
| var contact = { | |||||
| name: {first: "Bill", last: "Bobitybob" }, | |||||
| age: 37 | |||||
| { | |||||
| "name": "Chris", | |||||
| "company": "<b>GitHub</b>" | |||||
| } | } | ||||
| template = "Hello, {{name.first}} {{name.last}}. You are {{age}} years old." | |||||
| Output: | |||||
| *NOTICE*: The dot notation feature was recently implemented for the 0.4 | |||||
| release, which is not out as of Nov 9 2011. You can find the feature in the | |||||
| current master branch of mustachejs. | |||||
| * Chris | |||||
| * | |||||
| * <b>GitHub</b> | |||||
| * <b>GitHub</b> | |||||
| * <b>GitHub</b> | |||||
| ### Conditional Sections | |||||
| JavaScript's dot notation may be used to access keys that are properties of | |||||
| objects in a view. | |||||
| Conditional sections begin with `{{#condition}}` and end with | |||||
| `{{/condition}}`. When `condition` evaluates to true, the section is rendered, | |||||
| otherwise the whole block will output nothing at all. `condition` may be a | |||||
| function returning true/false or a simple boolean. | |||||
| Template: | |||||
| var view = {condition: function() { | |||||
| // [...your code goes here...] | |||||
| return true; | |||||
| }} | |||||
| * {{name.first}} {{name.last}} | |||||
| * {{age}} | |||||
| {{#condition}} | |||||
| I will be visible if condition is true | |||||
| {{/condition}} | |||||
| View: | |||||
| { | |||||
| "name": { | |||||
| "first": "Michael", | |||||
| "last": "Jackson" | |||||
| }, | |||||
| "age": "RIP" | |||||
| } | |||||
| ### Enumerable Sections | |||||
| Output: | |||||
| Enumerable Sections use the same syntax as condition sections do. | |||||
| `{{#shopping_items}}` and `{{/shopping_items}}`. Actually the view decides how | |||||
| mustache.js renders the section. If the view returns an array, it will | |||||
| iterator over the items. Use `{{.}}` to access the current item inside the | |||||
| enumeration section. | |||||
| * Michael Jackson | |||||
| * RIP | |||||
| var view = {name: "Joe's shopping card", | |||||
| items: ["bananas", "apples"]} | |||||
| ### Sections | |||||
| var template = "{{name}}: <ul> {{#items}}<li>{{.}}</li>{{/items}} </ul>" | |||||
| Sections render blocks of text one or more times, depending on the value of the | |||||
| key in the current context. | |||||
| Outputs: | |||||
| Joe's shopping card: <ul><li>bananas</li><li>apples</li></ul> | |||||
| A section begins with a pound and ends with a slash. That is, `{{#person}}` | |||||
| begins a `person` section, while `{{/person}}` ends it. The text between the two | |||||
| tags is referred to as that section's "block". | |||||
| The behavior of the section is determined by the value of the key. | |||||
| ### Higher Order Sections | |||||
| #### False Values or Empty Lists | |||||
| If a section key returns a function, it will be called and passed both the | |||||
| unrendered block of text and a renderer convenience function. | |||||
| If the `person` key exists and has a value of `null`, `undefined`, or `false`, | |||||
| or is an empty list, the block will not be rendered. | |||||
| Given this object: | |||||
| Template: | |||||
| "name": "Tater", | |||||
| "bolder": function() { | |||||
| return function(text, render) { | |||||
| return "<b>" + render(text) + '</b>' | |||||
| } | |||||
| } | |||||
| Shown. | |||||
| {{#person}} | |||||
| Never shown! | |||||
| {{/person}} | |||||
| And this template: | |||||
| View: | |||||
| {{#bolder}}Hi {{name}}.{{/bolder}} | |||||
| { | |||||
| "person": true | |||||
| } | |||||
| We'll get this output: | |||||
| Output: | |||||
| <b>Hi Tater.</b> | |||||
| Shown. | |||||
| #### Non-Empty Lists | |||||
| As you can see, we’re pre-processing the text in the block. This can be used | |||||
| to implement caching, filters (like syntax highlighting), etc. | |||||
| If the `person` key exists and is not `null`, `undefined`, or `false`, and is | |||||
| not an empty list the block will be rendered one or more times. | |||||
| You can use `this.name` to access the attribute `name` from your view. | |||||
| When the value is a list, the block is rendered once for each item in the list. | |||||
| The context of the block is set to the current item in the list for each | |||||
| iteration. In this way we can loop over collections. | |||||
| ### Dereferencing Sections | |||||
| Template: | |||||
| If your data has components that are logically grouped into nested objects, | |||||
| you may wish to dereference an object to access its values. | |||||
| {{#stooges}} | |||||
| <b>{{name}}</b> | |||||
| {{/stooges}} | |||||
| Given this object: | |||||
| View: | |||||
| { | { | ||||
| "name": "Bill", | |||||
| "address": { | |||||
| "street": "801 Streetly street", | |||||
| "city": "Boston", | |||||
| "state": "MA", | |||||
| "zip" "02101" | |||||
| } | |||||
| "stooges": [ | |||||
| { "name": "Moe" }, | |||||
| { "name": "Larry" }, | |||||
| { "name": "Curly" } | |||||
| ] | |||||
| } | } | ||||
| And this template: | |||||
| <h1>Contact: {{name}}</h1> | |||||
| {{#address}} | |||||
| <p>{{street}}</p> | |||||
| <p>{{city}}, {{state}} {{zip}}</p> | |||||
| {{/address}} | |||||
| Output: | |||||
| We'll get this output: | |||||
| <b>Moe</b> | |||||
| <b>Larry</b> | |||||
| <b>Curly</b> | |||||
| <h1>Contact: Bill</h1> | |||||
| <p>801 Streetly street</p> | |||||
| <p>Boston, MA 02101</p> | |||||
| When looping over an array of strings, a `.` can be used to refer to the current | |||||
| item in the list. | |||||
| ### Inverted Sections | |||||
| Template: | |||||
| An inverted section opens with `{{^section}}` instead of `{{#section}}` and | |||||
| uses a boolean negative to evaluate. Empty arrays are considered falsy. | |||||
| {{#musketeers}} | |||||
| * {{.}} | |||||
| {{/musketeers}} | |||||
| View: | View: | ||||
| var inverted_section = { | |||||
| "repo": [] | |||||
| { | |||||
| "musketeers": ["Athos", "Aramis", "Porthos", "D'Artagnan"] | |||||
| } | } | ||||
| Template: | |||||
| {{#repo}}<b>{{name}}</b>{{/repo}} | |||||
| {{^repo}}No repos :({{/repo}} | |||||
| Output: | |||||
| Result: | |||||
| * Athos | |||||
| * Aramis | |||||
| * Porthos | |||||
| * D'Artagnan | |||||
| No repos :( | |||||
| If the value of a section variable is a function, it will be called in the | |||||
| context of the current item in the list on each iteration. | |||||
| Template: | |||||
| ### View Partials | |||||
| {{#beatles}} | |||||
| * {{name}} | |||||
| {{/beatles}} | |||||
| mustache.js supports a quite powerful but yet simple view partial mechanism. | |||||
| Use the following syntax for partials: `{{>partial_name}}` | |||||
| View: | |||||
| var view = { | |||||
| name: "Joe", | |||||
| winnings: { | |||||
| value: 1000, | |||||
| taxed_value: function() { | |||||
| return this.value - (this.value * 0.4); | |||||
| } | |||||
| { | |||||
| "beatles": [ | |||||
| { "firstName": "John", "lastName": "Lennon" }, | |||||
| { "firstName": "Paul", "lastName": "McCartney" }, | |||||
| { "firstName": "George", "lastName": "Harrison" }, | |||||
| { "firstName": "Ringo", "lastName": "Starr" } | |||||
| ], | |||||
| "name": function () { | |||||
| return this.firstName + " " + this.lastName; | |||||
| } | } | ||||
| }; | |||||
| var template = "Welcome, {{name}}! {{>winnings}}" | |||||
| var partials = { | |||||
| winnings: "You just won ${{value}} (which is ${{taxed_value}} after tax)"}; | |||||
| var output = Mustache.to_html(template, view, partials) | |||||
| output will be: | |||||
| Welcome, Joe! You just won $1000 (which is $600 after tax) | |||||
| You invoke a partial with `{{>winnings}}`. Invoking the partial `winnings` | |||||
| will tell mustache.js to look for a object in the context's property | |||||
| `winnings`. It will then use that object as the context for the template found | |||||
| in `partials` for `winnings`. | |||||
| ## Escaping | |||||
| mustache.js does escape all values when using the standard double mustache | |||||
| syntax. Characters which will be escaped: `& \ " ' < >`. To disable escaping, | |||||
| simply use triple mustaches like `{{{unescaped_variable}}}`. | |||||
| Example: Using `{{variable}}` inside a template for `5 > 2` will result in `5 > 2`, where as the usage of `{{{variable}}}` will result in `5 > 2`. | |||||
| } | |||||
| Output: | |||||
| ## Streaming | |||||
| * John Lennon | |||||
| * Paul McCartney | |||||
| * George Harrison | |||||
| * Ringo Starr | |||||
| To stream template results out of mustache.js, you can pass an optional | |||||
| `send()` callback to the `to_html()` call: | |||||
| #### Functions | |||||
| Mustache.to_html(template, view, partials, function(line) { | |||||
| print(line); | |||||
| }); | |||||
| If the value of a section key is a function, it is called with the section's | |||||
| literal block of text, un-rendered, as its first argument. The second argument | |||||
| is a special rendering function that uses the current view as its view argument. | |||||
| It is called in the context of the current view object. | |||||
| Template: | |||||
| ## Pragmas | |||||
| {{#bold}}Hi {{name}}.{{/bold}} | |||||
| Pragma tags let you alter the behaviour of mustache.js. They have the format | |||||
| of | |||||
| View: | |||||
| {{%PRAGMANAME}} | |||||
| { | |||||
| "name": "Tater", | |||||
| "bold": function () { | |||||
| return function (text, render) { | |||||
| return "<b>" + render(text) + "</b>"; | |||||
| } | |||||
| } | |||||
| } | |||||
| and they accept options: | |||||
| Output: | |||||
| {{%PRAGMANAME option=value}} | |||||
| <b>Hi Tater.</b> | |||||
| ### Inverted Sections | |||||
| ### IMPLICIT-ITERATOR | |||||
| An inverted section opens with `{{^section}}` instead of `{{#section}}`. The | |||||
| block of an inverted section is rendered only if the value of that section's tag | |||||
| is `null`, `undefined`, `false`, or an empty list. | |||||
| When using a block to iterate over an enumerable (Array), mustache.js expects | |||||
| an objects as enumerable items. The implicit iterator pragma enables optional | |||||
| behaviour of allowing literals as enumerable items. Consider this view: | |||||
| Template: | |||||
| var view = { | |||||
| foo: [1, 2, 3, 4, 5, "french"] | |||||
| }; | |||||
| {{#repos}}<b>{{name}}</b>{{/repos}} | |||||
| {{^repos}}No repos :({{/repos}} | |||||
| The following template can iterate over the member `foo`: | |||||
| View: | |||||
| {{%IMPLICIT-ITERATOR}} | |||||
| {{#foo}} | |||||
| {{.}} | |||||
| {{/foo}} | |||||
| { | |||||
| "repos": [] | |||||
| } | |||||
| If you don't like the dot in there, the pragma accepts an option to set your | |||||
| own iteration marker: | |||||
| Output: | |||||
| {{%IMPLICIT-ITERATOR iterator=bob}} | |||||
| {{#foo}} | |||||
| {{bob}} | |||||
| {{/foo}} | |||||
| No repos :( | |||||
| ## More Examples and Documentation | |||||
| ### Comments | |||||
| See `examples/` for more goodies and read the [original mustache docs][m] | |||||
| Comments begin with a bang and are ignored. The following template: | |||||
| ## Command Line | |||||
| <h1>Today{{! ignore me }}.</h1> | |||||
| See `mustache(1)` man page or | |||||
| <http://defunkt.github.com/mustache/mustache.1.html> | |||||
| for command line docs. | |||||
| Will render as follows: | |||||
| Or just install it as a RubyGem: | |||||
| <h1>Today.</h1> | |||||
| $ gem install mustache | |||||
| $ mustache -h | |||||
| Comments may contain newlines. | |||||
| [m]: http://github.com/defunkt/mustache/#readme | |||||
| [node.js]: http://nodejs.org | |||||
| [couchdb]: http://couchdb.apache.org | |||||
| ### Partials | |||||
| Partials begin with a greater than sign, like {{> box}}. | |||||
| ## Plugins for jQuery, Dojo, Yui, CommonJS, qooxdoo | |||||
| Partials are rendered at runtime (as opposed to compile time), so recursive | |||||
| partials are possible. Just avoid infinite loops. | |||||
| This repository lets you build modules for [jQuery][], [Dojo][], [Yui][] and | |||||
| [CommonJS][] / [Node.js][] with the help of `rake`. | |||||
| They also inherit the calling context. Whereas in ERB you may have this: | |||||
| NOTE: The default `rake` task is only used for testing and require rspec to be | |||||
| installed (see below). | |||||
| <%= partial :next_more, :start => start, :size => size %> | |||||
| Run `rake jquery` to get a jQuery compatible plugin file in the | |||||
| `mustache-jquery/` directory. | |||||
| Mustache requires only this: | |||||
| Run `rake dojo` to get a Dojo compatible plugin file in the `mustache-dojo/` | |||||
| directory. | |||||
| {{> next_more}} | |||||
| Run `rake yui` to get a Yui compatible plugin file in the `mustache-yui/` | |||||
| directory. | |||||
| Why? Because the `next_more.mustache` file will inherit the `size` and `start` | |||||
| variables from the calling context. In this way you may want to think of | |||||
| partials as includes, or template expansion, even though it's not literally true. | |||||
| Run `rake commonjs` to get a CommonJS compatible plugin file in the | |||||
| `mustache-commonjs/` directory which you can also use with [Node.js][]. | |||||
| For example, this template and partial: | |||||
| Run `rake qooxdoo` to get a qooxdoo compatible file named `qooxdoo.mustache.js`. | |||||
| base.mustache: | |||||
| <h2>Names</h2> | |||||
| {{#names}} | |||||
| {{> user}} | |||||
| {{/names}} | |||||
| ## Testing | |||||
| user.mustache: | |||||
| <strong>{{name}}</strong> | |||||
| NOTE: You will need to install rspec first by running `gem install rspec`. | |||||
| Can be thought of as a single, expanded template: | |||||
| To run the mustache.js test suite, run `rake spec`. All specs will be run first with JavaScriptCore (using `jsc`) | |||||
| and again with Rhino, using `java org.mozilla.javascript.tools.shell.Main`. | |||||
| <h2>Names</h2> | |||||
| {{#names}} | |||||
| <strong>{{name}}</strong> | |||||
| {{/names}} | |||||
| JavaScriptCore is used from the OSX default location: | |||||
| In mustache.js an object of partials may be passed as the third argument to | |||||
| `Mustache.render`. The object should be keyed by the name of the partial, and | |||||
| its value should be the partial text. | |||||
| /System/Library/Frameworks/JavaScriptCore.framework/Versions/A/Resources/jsc | |||||
| ### Set Delimiter | |||||
| To install Rhino on OSX, follow [these instructions](Rhino Install). | |||||
| Set Delimiter tags start with an equals sign and change the tag delimiters from | |||||
| `{{` and `}}` to custom strings. | |||||
| ### Adding Tests | |||||
| Consider the following contrived example: | |||||
| Tests are located in the `examples/` directory. Adding a new test requires three files. Here's an example to add a test named "foo": | |||||
| * {{ default_tags }} | |||||
| {{=<% %>=}} | |||||
| * <% erb_style_tags %> | |||||
| <%={{ }}=%> | |||||
| * {{ default_tags_again }} | |||||
| `examples/foo.html` (the template): | |||||
| Here we have a list with three items. The first item uses the default tag style, | |||||
| the second uses ERB style as defined by the Set Delimiter tag, and the third | |||||
| returns to the default style after yet another Set Delimiter declaration. | |||||
| foo {{bar}} | |||||
| According to [ctemplates](http://google-ctemplate.googlecode.com/svn/trunk/doc/howto.html), | |||||
| this "is useful for languages like TeX, where double-braces may occur in the | |||||
| text and are awkward to use for markup." | |||||
| `examples/foo.js` (the view context): | |||||
| Custom delimiters may not contain whitespace or the equals sign. | |||||
| var foo = { | |||||
| bar: "baz" | |||||
| }; | |||||
| ## Streaming | |||||
| `examples/foo.txt` (the expected output): | |||||
| To stream template results out of mustache.js, you can pass an optional callback | |||||
| to the call to `Mustache.render`: | |||||
| foo baz | |||||
| Mustache.render(template, view, partials, function (chunk) { | |||||
| print(chunk); | |||||
| }); | |||||
| [jQuery]: http://jquery.com/ | |||||
| [Dojo]: http://www.dojotoolkit.org/ | |||||
| [Yui]: http://developer.yahoo.com/yui/ | |||||
| [CommonJS]: http://www.commonjs.org/ | |||||
| [Node.js]: http://nodejs.org/ | |||||
| [Rhino Install]: http://michaux.ca/articles/installing-rhino-on-os-x | |||||
| When the template is finished rendering, the callback will be called with `null` | |||||
| after which it won't be called anymore for that rendering. | |||||
| ## Plugins for JavaScript Libraries | |||||
| By default mustache.js may be used in a browser or any [CommonJS](http://www.commonjs.org/) | |||||
| environment, including [node](http://nodejs.org/). Additionally, mustache.js may | |||||
| be built specifically for several different client libraries and platforms, | |||||
| including the following: | |||||
| - [jQuery](http://jquery.com/) | |||||
| - [MooTools](http://mootools.net/) | |||||
| - [Dojo](http://www.dojotoolkit.org/) | |||||
| - [YUI](http://developer.yahoo.com/yui/) | |||||
| - [RequireJS](http://requirejs.org/) | |||||
| - [qooxdoo](http://qooxdoo.org/) | |||||
| These may be built using [Rake](http://rake.rubyforge.org/) and one of the | |||||
| following commands: | |||||
| $ rake jquery | |||||
| $ rake mootools | |||||
| $ rake dojo | |||||
| $ rake yui | |||||
| $ rake requirejs | |||||
| $ rake qooxdoo | |||||
| ## Thanks | |||||
| Mustache.js wouldn't kick ass if it weren't for these fine souls: | |||||
| * Chris Wanstrath / defunkt | |||||
| * Alexander Lang / langalex | |||||
| * Sebastian Cohnen / tisba | |||||
| * J Chris Anderson / jchris | |||||
| * Tom Robinson / tlrobinson | |||||
| * Aaron Quint / quirkey | |||||
| * Douglas Crockford | |||||
| * Nikita Vasilyev / NV | |||||
| * Elise Wood / glytch | |||||
| * Damien Mathieu / dmathieu | |||||
| * Jakub Kuźma / qoobaa | |||||
| * Will Leinweber / will | |||||
| * dpree | |||||
| * Jason Smith / jhs | |||||
| * Aaron Gibralter / agibralter | |||||
| * Ross Boucher / boucher | |||||
| * Matt Sanford / mzsanford | |||||
| * Ben Cherry / bcherry | |||||
| * Michael Jackson / mjijackson | |||||
| @@ -1,4 +1,5 @@ | |||||
| require 'rake' | require 'rake' | ||||
| require 'rake/clean' | |||||
| task :default => :spec | task :default => :spec | ||||
| @@ -7,53 +8,52 @@ task :spec do | |||||
| require 'rspec/core/rake_task' | require 'rspec/core/rake_task' | ||||
| RSpec::Core::RakeTask.new(:spec) do |t| | RSpec::Core::RakeTask.new(:spec) do |t| | ||||
| #t.spec_opts = ['--options', "\"#{File.dirname(__FILE__)}/spec/spec.opts\""] | #t.spec_opts = ['--options', "\"#{File.dirname(__FILE__)}/spec/spec.opts\""] | ||||
| t.pattern = 'test/*_spec.rb' | |||||
| t.pattern = 'spec/*_spec.rb' | |||||
| end | end | ||||
| end | end | ||||
| # Creates a task that uses the various template wrappers to make a wrapped | |||||
| # output file. There is some extra complexity because Dojo and YUI use | |||||
| # different final locations. | |||||
| def templated_build(name, opts={}) | def templated_build(name, opts={}) | ||||
| # Create a rule that uses the .tmpl.{pre,post} stuff to make a final, | |||||
| # wrapped, output file. | |||||
| # There is some extra complexity because Dojo and YUI3 use different | |||||
| # template files and final locations. | |||||
| short = name.downcase | short = name.downcase | ||||
| source = "mustache-#{short}" | |||||
| source = File.join("wrappers", short) | |||||
| dependencies = ["mustache.js"] + Dir.glob("#{source}/*.tpl.*") | dependencies = ["mustache.js"] + Dir.glob("#{source}/*.tpl.*") | ||||
| target_js = opts[:location] ? "mustache.js" : "#{short}.mustache.js" | |||||
| CLEAN.include(opts[:location] ? opts[:location] : target_js) | |||||
| desc "Package for #{name}" | desc "Package for #{name}" | ||||
| task short.to_sym => dependencies do | task short.to_sym => dependencies do | ||||
| target_js = opts[:location] ? "mustache.js" : "#{short}.mustache.js" | |||||
| puts "Packaging for #{name}" | puts "Packaging for #{name}" | ||||
| sh "mkdir -p #{opts[:location]}" if opts[:location] | |||||
| sh "cat #{source}/#{target_js}.tpl.pre mustache.js \ | |||||
| #{source}/#{target_js}.tpl.post > #{opts[:location] || '.'}/#{target_js}" | |||||
| # extra | |||||
| if opts[:extra] | |||||
| sh "sed -e 's/{{version}}/#{version}/' #{source}/#{opts[:extra]} \ | |||||
| > #{opts[:location]}/#{opts[:extra]}" | |||||
| end | |||||
| mkdir_p opts[:location] if opts[:location] | |||||
| files = [ | |||||
| "#{source}/mustache.js.pre", | |||||
| 'mustache.js', | |||||
| "#{source}/mustache.js.post" | |||||
| ] | |||||
| puts "Done, see #{opts[:location] || '.'}/#{target_js}" | |||||
| open("#{opts[:location] || '.'}/#{target_js}", 'w') do |f| | |||||
| files.each {|file| f << File.read(file) } | |||||
| end | |||||
| puts "Done, see #{opts[:location] || '.'}/#{target_js}" | |||||
| end | end | ||||
| end | end | ||||
| templated_build "CommonJS", :location => "lib", :extra => "package.json" | |||||
| templated_build "jQuery" | templated_build "jQuery" | ||||
| templated_build "qooxdoo" | |||||
| templated_build "MooTools" | |||||
| templated_build "Dojo", :location => "dojox/string" | templated_build "Dojo", :location => "dojox/string" | ||||
| templated_build "YUI3", :location => "yui3/mustache" | templated_build "YUI3", :location => "yui3/mustache" | ||||
| templated_build "requirejs" | |||||
| def version | |||||
| File.read("mustache.js").match('version: "([^\"]+)",$')[1] | |||||
| end | |||||
| templated_build "RequireJS" | |||||
| templated_build "qooxdoo" | |||||
| desc "Remove temporary files." | |||||
| task :clean do | |||||
| sh "git clean -fdx" | |||||
| task :minify do | |||||
| # npm install uglify-js | |||||
| mmjs = "mustache.min.js" | |||||
| `echo "/*! Version: 0.5.1-dev */" > #{mmjs}` | |||||
| `uglifyjs mustache.js >> #{mmjs}` | |||||
| puts "Created #{mmjs}" | |||||
| end | end | ||||
| @@ -0,0 +1,62 @@ | |||||
| ## Running the mustache.js test suite | |||||
| The mustache.js test suite uses the [RSpec](http://rspec.info/) testing | |||||
| framework. In order to run the tests you'll need to install [Ruby](http://ruby-lang.org/) | |||||
| as well as the `rake`, `rspec` (>=2), and `json` [RubyGems](http://rubygems.org/). | |||||
| ### How to install Ruby and the required gems from source | |||||
| Make sure you have the required tools to compile it: | |||||
| $ apt-get install build-essential libssl-dev libreadline5-dev zlib1g-dev | |||||
| Download and extract the Ruby source, and install it: | |||||
| $ wget ftp://ftp.ruby-lang.org/pub/ruby/stable-snapshot.tar.gz | |||||
| $ tar xvzf stable-snapshot.tar.gz | |||||
| $ cd ruby | |||||
| $ ./configure && make && make install | |||||
| Download and extract RubyGems, and install it: | |||||
| $ wget http://production.cf.rubygems.org/rubygems/rubygems-1.8.12.tgz | |||||
| $ tar xzvf rubygems-1.8.12.tgz | |||||
| $ cd rubygems-1.8.12 | |||||
| $ ruby setup.rb | |||||
| If you want to update RubyGems: | |||||
| $ gem update --system | |||||
| Install the required gems: | |||||
| $ gem install rake rspec json | |||||
| That's it! | |||||
| ### How to run the tests | |||||
| The mustache.js test suite currently uses 4 different JavaScript runtime engines | |||||
| to maximize portability across platforms and browsers. They are: | |||||
| * node | |||||
| * SpiderMonkey (Mozilla, Firefox) | |||||
| * JavaScriptCore (WebKit, Safari) | |||||
| * Rhino (Mozilla, Java) | |||||
| When the test suite runs it will automatically determine which platforms are | |||||
| available on your machine and run on all of them. The suite must run on at least | |||||
| one platform in order to succeed. | |||||
| Once you have at least one JavaScript platform installed, you can run the test | |||||
| suite with the following command: | |||||
| $ rake | |||||
| ### How to create a test | |||||
| All test files live in the spec/_files directory. To create a new test: | |||||
| * Create a template file called `somename.mustache` | |||||
| * Create a JavaScript file containing the view called `somename.js` | |||||
| * Create a text file with the expected result called `somename.txt` | |||||
| @@ -1,54 +0,0 @@ | |||||
| ## How to run the tests | |||||
| To run the test, you need ruby and the following gems installed : rake, rspec (>=2), json | |||||
| ### How to install ruby and the required gems from source | |||||
| Make sure you have the required tools to compile it | |||||
| # apt-get install build-essential libssl-dev libreadline5-dev zlib1g-dev | |||||
| Download ruby source and extract the source | |||||
| $ cd ~/ | |||||
| $ wget ftp://ftp.ruby-lang.org/pub/ruby/stable-snapshot.tar.gz | |||||
| $ tar xvzf stable-snapshot.tar.gz | |||||
| Install it | |||||
| $ ./configure && make | |||||
| # make install | |||||
| download the last version of RubyGems from here | |||||
| http://rubyforge.org/frs/?group_id=126 | |||||
| Extract the source | |||||
| $ tar xzvf rubygems-1.8.4.tgz | |||||
| Install it | |||||
| $ cd rubygems-1.8.4 | |||||
| # ruby setup.rb | |||||
| If you want to update RubyGems | |||||
| # gem update --system | |||||
| Install the required gems | |||||
| # gem install rake rspec json | |||||
| That's it! | |||||
| ### How to run the tests | |||||
| $ rake | |||||
| ### How to create a test | |||||
| - Create a template file `somename.html` | |||||
| - Create a javascript file with data and functions `somename.js` | |||||
| - Create a file the expected result `somename.txt` | |||||
| Done! | |||||
| @@ -1,22 +0,0 @@ | |||||
| # Thanks | |||||
| Mustache.js wouldn't kick ass if it weren't for these fine souls: | |||||
| * Chris Wanstrath / defunkt | |||||
| * Alexander Lang / langalex | |||||
| * Sebastian Cohnen / tisba | |||||
| * J Chris Anderson / jchris | |||||
| * Tom Robinson / tlrobinson | |||||
| * Aaron Quint / quirkey | |||||
| * Douglas Crockford | |||||
| * Nikita Vasilyev / NV | |||||
| * Elise Wood / glytch | |||||
| * Damien Mathieu / dmathieu | |||||
| * Jakub Kuźma / qoobaa | |||||
| * Will Leinweber / will | |||||
| * dpree | |||||
| * Jason Smith / jhs | |||||
| * Aaron Gibralter / agibralter | |||||
| * Ross Boucher / boucher | |||||
| * Matt Sanford / mzsanford | |||||
| * Ben Cherry / bcherry | |||||
| @@ -1,3 +0,0 @@ | |||||
| var partial_context = { | |||||
| numbers: ['1', '2', '3', '4'] | |||||
| }; | |||||
| @@ -1 +0,0 @@ | |||||
| {{#array_of_strings}}{{.}} {{/array_of_strings}} | |||||
| @@ -1,2 +0,0 @@ | |||||
| {{%IMPLICIT-ITERATOR iterator=rob}} | |||||
| {{#array_of_strings_options}}{{rob}} {{/array_of_strings_options}} | |||||
| @@ -1 +0,0 @@ | |||||
| var array_of_strings_options = {array_of_strings_options: ['hello', 'world']}; | |||||
| @@ -1 +0,0 @@ | |||||
| hello world | |||||
| @@ -1,5 +0,0 @@ | |||||
| var partial_context = { | |||||
| partial: { | |||||
| array: ['1', '2', '3', '4'] | |||||
| } | |||||
| }; | |||||
| @@ -1,6 +0,0 @@ | |||||
| <h1>Colors</h1> | |||||
| <ul> | |||||
| <li><strong>red</strong></li> | |||||
| <li><a href="#Green">green</a></li> | |||||
| <li><a href="#Blue">blue</a></li> | |||||
| </ul> | |||||
| @@ -1 +0,0 @@ | |||||
| yo | |||||
| @@ -1,3 +0,0 @@ | |||||
| var partial_context = { | |||||
| foo: 1 | |||||
| }; | |||||
| @@ -1 +0,0 @@ | |||||
| <html><head></head><body><h1>Test</h1></body></html> | |||||
| @@ -1 +0,0 @@ | |||||
| @@ -1,2 +0,0 @@ | |||||
| {{#repo}}<b>{{name}}</b>{{/repo}} | |||||
| {{^repo}}No repos :({{/repo}} | |||||
| @@ -1,4 +0,0 @@ | |||||
| 1 | |||||
| 2 | |||||
| 3 | |||||
| @@ -1 +0,0 @@ | |||||
| Again, {{again}}! | |||||
| @@ -1,8 +0,0 @@ | |||||
| var partial_context = { | |||||
| title: function() { | |||||
| return "Welcome"; | |||||
| }, | |||||
| partial: { | |||||
| again: "Goodbye" | |||||
| } | |||||
| } | |||||
| @@ -1 +0,0 @@ | |||||
| {{greeting}}, {{name}}! | |||||
| @@ -1 +0,0 @@ | |||||
| @@ -1 +0,0 @@ | |||||
| <h1>{{{title}}}</h1> | |||||
| @@ -1 +0,0 @@ | |||||
| {{%I-HAVE-THE-GREATEST-MUSTACHE}} | |||||
| @@ -1 +0,0 @@ | |||||
| var unknown_pragma = {}; | |||||
| @@ -1 +0,0 @@ | |||||
| ERROR: This implementation of mustache doesn't understand the 'I-HAVE-THE-GREATEST-MUSTACHE' pragma | |||||
| @@ -1,19 +0,0 @@ | |||||
| var partial_context = { | |||||
| greeting: function() { | |||||
| return "Welcome"; | |||||
| }, | |||||
| farewell: function() { | |||||
| return "Fair enough, right?"; | |||||
| }, | |||||
| partial: { | |||||
| name: "Chris", | |||||
| value: 10000, | |||||
| taxed_value: function() { | |||||
| return this.value - (this.value * 0.4); | |||||
| }, | |||||
| in_ca: true | |||||
| } | |||||
| }; | |||||
| @@ -1,19 +0,0 @@ | |||||
| var partial_context = { | |||||
| greeting: function() { | |||||
| return "Welcome"; | |||||
| }, | |||||
| farewell: function() { | |||||
| return "Fair enough, right?"; | |||||
| }, | |||||
| partial: { | |||||
| name: "Chris", | |||||
| value: 10000, | |||||
| taxed_value: function() { | |||||
| return this.value - (this.value * 0.4); | |||||
| }, | |||||
| in_ca: true | |||||
| } | |||||
| }; | |||||
| @@ -1,8 +0,0 @@ | |||||
| { | |||||
| "name": "mustache", | |||||
| "author": "http://mustache.github.com/", | |||||
| "description": "{{ mustache }} in JavaScript — Logic-less templates.", | |||||
| "keywords": ["template"], | |||||
| "version": "0.3.1-dev-twitter", | |||||
| "main": "./mustache" | |||||
| } | |||||
| @@ -1,7 +0,0 @@ | |||||
| exports.name = Mustache.name; | |||||
| exports.version = Mustache.version; | |||||
| exports.to_html = function() { | |||||
| return Mustache.to_html.apply(this, arguments); | |||||
| }; | |||||
| @@ -1,6 +0,0 @@ | |||||
| /* | |||||
| * CommonJS-compatible mustache.js module | |||||
| * | |||||
| * See http://github.com/janl/mustache.js for more info. | |||||
| */ | |||||
| @@ -1,7 +0,0 @@ | |||||
| { | |||||
| "name": "mustache", | |||||
| "author": "http://mustache.github.com/", | |||||
| "description": "{{ mustache }} in JavaScript — Logic-less templates.", | |||||
| "keywords": ["template"], | |||||
| "version": "{{version}}" | |||||
| } | |||||
| @@ -1,4 +0,0 @@ | |||||
| dojox.mustache = dojo.hitch(Mustache, "to_html"); | |||||
| })(); | |||||
| @@ -1,6 +0,0 @@ | |||||
| $.mustache = function(template, view, partials) { | |||||
| return Mustache.to_html(template, view, partials); | |||||
| }; | |||||
| })(jQuery); | |||||
| @@ -1,4 +0,0 @@ | |||||
| Y.mustache = Mustache.to_html; | |||||
| }, "0"); | |||||
| @@ -1,422 +1,536 @@ | |||||
| /* | |||||
| mustache.js — Logic-less templates in JavaScript | |||||
| See http://mustache.github.com/ for more info. | |||||
| */ | |||||
| var Mustache = function() { | |||||
| var regexCache = {}; | |||||
| var Renderer = function() {}; | |||||
| Renderer.prototype = { | |||||
| otag: "{{", | |||||
| ctag: "}}", | |||||
| pragmas: {}, | |||||
| buffer: [], | |||||
| pragmas_implemented: { | |||||
| "IMPLICIT-ITERATOR": true | |||||
| }, | |||||
| context: {}, | |||||
| render: function(template, context, partials, in_recursion) { | |||||
| // reset buffer & set context | |||||
| if(!in_recursion) { | |||||
| this.context = context; | |||||
| this.buffer = []; // TODO: make this non-lazy | |||||
| } | |||||
| // fail fast | |||||
| if(!this.includes("", template)) { | |||||
| if(in_recursion) { | |||||
| return template; | |||||
| } else { | |||||
| this.send(template); | |||||
| return; | |||||
| } | |||||
| } | |||||
| // get the pragmas together | |||||
| template = this.render_pragmas(template); | |||||
| // render the template | |||||
| var html = this.render_section(template, context, partials); | |||||
| // render_section did not find any sections, we still need to render the tags | |||||
| if (html === false) { | |||||
| html = this.render_tags(template, context, partials, in_recursion); | |||||
| } | |||||
| if (in_recursion) { | |||||
| return html; | |||||
| } else { | |||||
| this.sendLines(html); | |||||
| } | |||||
| }, | |||||
| /* | |||||
| Sends parsed lines | |||||
| */ | |||||
| send: function(line) { | |||||
| if(line !== "") { | |||||
| this.buffer.push(line); | |||||
| } | |||||
| }, | |||||
| /*! | |||||
| * mustache.js - Logic-less {{mustache}} templates with JavaScript | |||||
| * http://github.com/janl/mustache.js | |||||
| */ | |||||
| var Mustache = (typeof module !== "undefined" && module.exports) || {}; | |||||
| (function (exports) { | |||||
| exports.name = "mustache.js"; | |||||
| exports.version = "0.5.0-dev"; | |||||
| exports.tags = ["{{", "}}"]; | |||||
| exports.parse = parse; | |||||
| exports.compile = compile; | |||||
| exports.render = render; | |||||
| exports.clearCache = clearCache; | |||||
| // This is here for backwards compatibility with 0.4.x. | |||||
| exports.to_html = function (template, view, partials, send) { | |||||
| var result = render(template, view, partials); | |||||
| if (typeof send === "function") { | |||||
| send(result); | |||||
| } else { | |||||
| return result; | |||||
| } | |||||
| }; | |||||
| sendLines: function(text) { | |||||
| if (text) { | |||||
| var lines = text.split("\n"); | |||||
| for (var i = 0; i < lines.length; i++) { | |||||
| this.send(lines[i]); | |||||
| } | |||||
| } | |||||
| }, | |||||
| /* | |||||
| Looks for %PRAGMAS | |||||
| */ | |||||
| render_pragmas: function(template) { | |||||
| // no pragmas | |||||
| if(!this.includes("%", template)) { | |||||
| return template; | |||||
| var _toString = Object.prototype.toString; | |||||
| var _isArray = Array.isArray; | |||||
| var _forEach = Array.prototype.forEach; | |||||
| var _trim = String.prototype.trim; | |||||
| var isArray; | |||||
| if (_isArray) { | |||||
| isArray = _isArray; | |||||
| } else { | |||||
| isArray = function (obj) { | |||||
| return _toString.call(obj) === "[object Array]"; | |||||
| }; | |||||
| } | |||||
| var forEach; | |||||
| if (_forEach) { | |||||
| forEach = function (obj, callback, scope) { | |||||
| return _forEach.call(obj, callback, scope); | |||||
| }; | |||||
| } else { | |||||
| forEach = function (obj, callback, scope) { | |||||
| for (var i = 0, len = obj.length; i < len; ++i) { | |||||
| callback.call(scope, obj[i], i, obj); | |||||
| } | } | ||||
| }; | |||||
| } | |||||
| var spaceRe = /^\s*$/; | |||||
| function isWhitespace(string) { | |||||
| return spaceRe.test(string); | |||||
| } | |||||
| var trim; | |||||
| if (_trim) { | |||||
| trim = function (string) { | |||||
| return string == null ? "" : _trim.call(string); | |||||
| }; | |||||
| } else { | |||||
| var trimLeft, trimRight; | |||||
| if (isWhitespace("\xA0")) { | |||||
| trimLeft = /^\s+/; | |||||
| trimRight = /\s+$/; | |||||
| } else { | |||||
| // IE doesn't match non-breaking spaces with \s, thanks jQuery. | |||||
| trimLeft = /^[\s\xA0]+/; | |||||
| trimRight = /[\s\xA0]+$/; | |||||
| } | |||||
| var that = this; | |||||
| var regex = this.getCachedRegex("render_pragmas", function(otag, ctag) { | |||||
| return new RegExp(otag + "%([\\w-]+) ?([\\w]+=[\\w]+)?" + ctag, "g"); | |||||
| }); | |||||
| trim = function (string) { | |||||
| return string == null ? "" : | |||||
| String(string).replace(trimLeft, "").replace(trimRight, ""); | |||||
| }; | |||||
| } | |||||
| var escapeMap = { | |||||
| "&": "&", | |||||
| "<": "<", | |||||
| ">": ">", | |||||
| '"': '"', | |||||
| "'": ''' | |||||
| }; | |||||
| return template.replace(regex, function(match, pragma, options) { | |||||
| if(!that.pragmas_implemented[pragma]) { | |||||
| throw({message: | |||||
| "This implementation of mustache doesn't understand the '" + | |||||
| pragma + "' pragma"}); | |||||
| } | |||||
| that.pragmas[pragma] = {}; | |||||
| if(options) { | |||||
| var opts = options.split("="); | |||||
| that.pragmas[pragma][opts[0]] = opts[1]; | |||||
| } | |||||
| return ""; | |||||
| // ignore unknown pragmas silently | |||||
| }); | |||||
| }, | |||||
| /* | |||||
| Tries to find a partial in the curent scope and render it | |||||
| */ | |||||
| render_partial: function(name, context, partials) { | |||||
| name = this.trim(name); | |||||
| if(!partials || partials[name] === undefined) { | |||||
| throw({message: "unknown_partial '" + name + "'"}); | |||||
| } | |||||
| if(typeof(context[name]) != "object") { | |||||
| return this.render(partials[name], context, partials, true); | |||||
| } | |||||
| return this.render(partials[name], context[name], partials, true); | |||||
| }, | |||||
| /* | |||||
| Renders inverted (^) and normal (#) sections | |||||
| */ | |||||
| render_section: function(template, context, partials) { | |||||
| if(!this.includes("#", template) && !this.includes("^", template)) { | |||||
| // did not render anything, there were no sections | |||||
| return false; | |||||
| } | |||||
| function escapeHTML(string) { | |||||
| return String(string).replace(/&(?!\w+;)|[<>"']/g, function (s) { | |||||
| return escapeMap[s] || s; | |||||
| }); | |||||
| } | |||||
| /** | |||||
| * Adds the `template`, `line`, and `file` properties to the given error | |||||
| * object and alters the message to provide more useful debugging information. | |||||
| */ | |||||
| function debug(e, template, line, file) { | |||||
| file = file || "<template>"; | |||||
| var lines = template.split("\n"), | |||||
| start = Math.max(line - 3, 0), | |||||
| end = Math.min(lines.length, line + 3), | |||||
| context = lines.slice(start, end); | |||||
| var c; | |||||
| for (var i = 0, len = context.length; i < len; ++i) { | |||||
| c = i + start + 1; | |||||
| context[i] = (c === line ? " >> " : " ") + context[i]; | |||||
| } | |||||
| var that = this; | |||||
| e.template = template; | |||||
| e.line = line; | |||||
| e.file = file; | |||||
| e.message = [file + ":" + line, context.join("\n"), "", e.message].join("\n"); | |||||
| var regex = this.getCachedRegex("render_section", function(otag, ctag) { | |||||
| // This regex matches _the first_ section ({{#foo}}{{/foo}}), and captures the remainder | |||||
| return new RegExp( | |||||
| "^([\\s\\S]*?)" + // all the crap at the beginning that is not {{*}} ($1) | |||||
| return e; | |||||
| } | |||||
| otag + // {{ | |||||
| "(\\^|\\#)\\s*(.+)\\s*" + // #foo (# == $2, foo == $3) | |||||
| ctag + // }} | |||||
| /** | |||||
| * Looks up the value of the given `name` in the given context `stack`. | |||||
| */ | |||||
| function lookup(name, stack, defaultValue) { | |||||
| if (name === ".") { | |||||
| return stack[stack.length - 1]; | |||||
| } | |||||
| "\n*([\\s\\S]*?)" + // between the tag ($2). leading newlines are dropped | |||||
| var names = name.split("."); | |||||
| var lastIndex = names.length - 1; | |||||
| var target = names[lastIndex]; | |||||
| otag + // {{ | |||||
| "\\/\\s*\\3\\s*" + // /foo (backreference to the opening tag). | |||||
| ctag + // }} | |||||
| var value, context, i = stack.length, j, localStack; | |||||
| while (i) { | |||||
| localStack = stack.slice(0); | |||||
| context = stack[--i]; | |||||
| "\\s*([\\s\\S]*)$", // everything else in the string ($4). leading whitespace is dropped. | |||||
| j = 0; | |||||
| while (j < lastIndex) { | |||||
| context = context[names[j++]]; | |||||
| "g"); | |||||
| }); | |||||
| if (context == null) { | |||||
| break; | |||||
| } | |||||
| localStack.push(context); | |||||
| } | |||||
| // for each {{#foo}}{{/foo}} section do... | |||||
| return template.replace(regex, function(match, before, type, name, content, after) { | |||||
| // before contains only tags, no sections | |||||
| var renderedBefore = before ? that.render_tags(before, context, partials, true) : "", | |||||
| if (context && typeof context === "object" && target in context) { | |||||
| value = context[target]; | |||||
| break; | |||||
| } | |||||
| } | |||||
| // after may contain both sections and tags, so use full rendering function | |||||
| renderedAfter = after ? that.render(after, context, partials, true) : "", | |||||
| // If the value is a function, call it in the current context. | |||||
| if (typeof value === "function") { | |||||
| value = value.call(localStack[localStack.length - 1]); | |||||
| } | |||||
| // will be computed below | |||||
| renderedContent, | |||||
| if (value == null) { | |||||
| return defaultValue; | |||||
| } | |||||
| value = that.find(name, context); | |||||
| return value; | |||||
| } | |||||
| if (type === "^") { // inverted section | |||||
| if (!value || that.is_array(value) && value.length === 0) { | |||||
| // false or empty list, render it | |||||
| renderedContent = that.render(content, context, partials, true); | |||||
| } else { | |||||
| renderedContent = ""; | |||||
| } | |||||
| } else if (type === "#") { // normal section | |||||
| if (that.is_array(value)) { // Enumerable, Let's loop! | |||||
| renderedContent = that.map(value, function(row) { | |||||
| return that.render(content, that.create_context(row), partials, true); | |||||
| }).join(""); | |||||
| } else if (that.is_object(value)) { // Object, Use it as subcontext! | |||||
| renderedContent = that.render(content, that.create_context(value), | |||||
| partials, true); | |||||
| } else if (typeof value === "function") { | |||||
| // higher order section | |||||
| renderedContent = value.call(context, content, function(text) { | |||||
| return that.render(text, context, partials, true); | |||||
| }); | |||||
| } else if (value) { // boolean section | |||||
| renderedContent = that.render(content, context, partials, true); | |||||
| } else { | |||||
| renderedContent = ""; | |||||
| } | |||||
| } | |||||
| function renderSection(name, stack, callback, inverted) { | |||||
| var buffer = ""; | |||||
| var value = lookup(name, stack); | |||||
| return renderedBefore + renderedContent + renderedAfter; | |||||
| if (inverted) { | |||||
| // From the spec: inverted sections may render text once based on the | |||||
| // inverse value of the key. That is, they will be rendered if the key | |||||
| // doesn't exist, is false, or is an empty list. | |||||
| if (value == null || value === false || (isArray(value) && value.length === 0)) { | |||||
| buffer += callback(); | |||||
| } | |||||
| } else if (isArray(value)) { | |||||
| forEach(value, function (value) { | |||||
| stack.push(value); | |||||
| buffer += callback(); | |||||
| stack.pop(); | |||||
| }); | }); | ||||
| }, | |||||
| /* | |||||
| Replace {{foo}} and friends with values from our view | |||||
| */ | |||||
| render_tags: function(template, context, partials, in_recursion) { | |||||
| // tit for tat | |||||
| var that = this; | |||||
| var new_regex = function() { | |||||
| return that.getCachedRegex("render_tags", function(otag, ctag) { | |||||
| return new RegExp(otag + "(=|!|>|\\{|%)?([^\\/#\\^]+?)\\1?" + ctag + "+", "g"); | |||||
| }); | |||||
| } else if (typeof value === "object") { | |||||
| stack.push(value); | |||||
| buffer += callback(); | |||||
| stack.pop(); | |||||
| } else if (typeof value === "function") { | |||||
| var scope = stack[stack.length - 1]; | |||||
| var scopedRender = function (template) { | |||||
| return render(template, scope); | |||||
| }; | }; | ||||
| buffer += value.call(scope, callback(), scopedRender) || ""; | |||||
| } else if (value) { | |||||
| buffer += callback(); | |||||
| } | |||||
| var regex = new_regex(); | |||||
| var tag_replace_callback = function(match, operator, name) { | |||||
| switch(operator) { | |||||
| case "!": // ignore comments | |||||
| return ""; | |||||
| case "=": // set new delimiters, rebuild the replace regexp | |||||
| that.set_delimiters(name); | |||||
| regex = new_regex(); | |||||
| return ""; | |||||
| case ">": // render partial | |||||
| return that.render_partial(name, context, partials); | |||||
| case "{": // the triple mustache is unescaped | |||||
| return that.find(name, context); | |||||
| default: // escape the value | |||||
| return that.escape(that.find(name, context)); | |||||
| } | |||||
| }; | |||||
| var lines = template.split("\n"); | |||||
| for(var i = 0; i < lines.length; i++) { | |||||
| lines[i] = lines[i].replace(regex, tag_replace_callback, this); | |||||
| if(!in_recursion) { | |||||
| this.send(lines[i]); | |||||
| return buffer; | |||||
| } | |||||
| /** | |||||
| * Parses the given `template` and returns the source of a function that, | |||||
| * with the proper arguments, will render the template. Recognized options | |||||
| * include the following: | |||||
| * | |||||
| * - file The name of the file the template comes from (displayed in | |||||
| * error messages) | |||||
| * - tags An array of open and close tags the `template` uses. Defaults | |||||
| * to the value of Mustache.tags | |||||
| * - debug Set `true` to log the body of the generated function to the | |||||
| * console | |||||
| * - space Set `true` to preserve whitespace from lines that otherwise | |||||
| * contain only a {{tag}}. Defaults to `false` | |||||
| */ | |||||
| function parse(template, options) { | |||||
| options = options || {}; | |||||
| var tags = options.tags || exports.tags, | |||||
| openTag = tags[0], | |||||
| closeTag = tags[tags.length - 1]; | |||||
| var code = [ | |||||
| 'var buffer = "";', // output buffer | |||||
| "\nvar line = 1;", // keep track of source line number | |||||
| "\ntry {", | |||||
| '\nbuffer += "' | |||||
| ]; | |||||
| var spaces = [], // indices of whitespace in code on the current line | |||||
| hasTag = false, // is there a {{tag}} on the current line? | |||||
| nonSpace = false; // is there a non-space char on the current line? | |||||
| // Strips all space characters from the code array for the current line | |||||
| // if there was a {{tag}} on it and otherwise only spaces. | |||||
| var stripSpace = function () { | |||||
| if (hasTag && !nonSpace && !options.space) { | |||||
| while (spaces.length) { | |||||
| code.splice(spaces.pop(), 1); | |||||
| } | } | ||||
| } else { | |||||
| spaces = []; | |||||
| } | } | ||||
| if(in_recursion) { | |||||
| return lines.join("\n"); | |||||
| hasTag = false; | |||||
| nonSpace = false; | |||||
| }; | |||||
| var sectionStack = [], updateLine, nextOpenTag, nextCloseTag; | |||||
| var setTags = function (source) { | |||||
| tags = trim(source).split(/\s+/); | |||||
| nextOpenTag = tags[0]; | |||||
| nextCloseTag = tags[tags.length - 1]; | |||||
| }; | |||||
| var includePartial = function (source) { | |||||
| code.push( | |||||
| '";', | |||||
| updateLine, | |||||
| '\nvar partial = partials["' + trim(source) + '"];', | |||||
| '\nif (partial) {', | |||||
| '\n buffer += render(partial,stack[stack.length - 1],partials);', | |||||
| '\n}', | |||||
| '\nbuffer += "' | |||||
| ); | |||||
| }; | |||||
| var openSection = function (source, inverted) { | |||||
| var name = trim(source); | |||||
| if (name === "") { | |||||
| throw debug(new Error("Section name may not be empty"), template, line, options.file); | |||||
| } | } | ||||
| }, | |||||
| set_delimiters: function(delimiters) { | |||||
| var dels = delimiters.split(" "); | |||||
| this.otag = this.escape_regex(dels[0]); | |||||
| this.ctag = this.escape_regex(dels[1]); | |||||
| }, | |||||
| escape_regex: function(text) { | |||||
| // thank you Simon Willison | |||||
| if(!arguments.callee.sRE) { | |||||
| var specials = [ | |||||
| '/', '.', '*', '+', '?', '|', | |||||
| '(', ')', '[', ']', '{', '}', '\\' | |||||
| ]; | |||||
| arguments.callee.sRE = new RegExp( | |||||
| '(\\' + specials.join('|\\') + ')', 'g' | |||||
| ); | |||||
| sectionStack.push({name: name, inverted: inverted}); | |||||
| code.push( | |||||
| '";', | |||||
| updateLine, | |||||
| '\nvar name = "' + name + '";', | |||||
| '\nvar callback = (function () {', | |||||
| '\n return function () {', | |||||
| '\n var buffer = "";', | |||||
| '\nbuffer += "' | |||||
| ); | |||||
| }; | |||||
| var openInvertedSection = function (source) { | |||||
| openSection(source, true); | |||||
| }; | |||||
| var closeSection = function (source) { | |||||
| var name = trim(source); | |||||
| var openName = sectionStack.length != 0 && sectionStack[sectionStack.length - 1].name; | |||||
| if (!openName || name != openName) { | |||||
| throw debug(new Error('Section named "' + name + '" was never opened'), template, line, options.file); | |||||
| } | } | ||||
| return text.replace(arguments.callee.sRE, '\\$1'); | |||||
| }, | |||||
| /* | |||||
| find `name` in current `context`. That is find me a value | |||||
| from the view object | |||||
| */ | |||||
| find: function(name, context) { | |||||
| name = this.trim(name); | |||||
| // Checks whether a value is thruthy or false or 0 | |||||
| function is_kinda_truthy(bool) { | |||||
| return bool === false || bool === 0 || bool; | |||||
| var section = sectionStack.pop(); | |||||
| code.push( | |||||
| '";', | |||||
| '\n return buffer;', | |||||
| '\n };', | |||||
| '\n})();' | |||||
| ); | |||||
| if (section.inverted) { | |||||
| code.push("\nbuffer += renderSection(name,stack,callback,true);"); | |||||
| } else { | |||||
| code.push("\nbuffer += renderSection(name,stack,callback);"); | |||||
| } | } | ||||
| var value; | |||||
| // check for dot notation eg. foo.bar | |||||
| if(name.match(/([a-z_]+)\./ig)){ | |||||
| var childValue = this.walk_context(name, context); | |||||
| if(is_kinda_truthy(childValue)) { | |||||
| value = childValue; | |||||
| code.push('\nbuffer += "'); | |||||
| }; | |||||
| var sendPlain = function (source) { | |||||
| code.push( | |||||
| '";', | |||||
| updateLine, | |||||
| '\nbuffer += lookup("' + trim(source) + '",stack,"");', | |||||
| '\nbuffer += "' | |||||
| ); | |||||
| }; | |||||
| var sendEscaped = function (source) { | |||||
| code.push( | |||||
| '";', | |||||
| updateLine, | |||||
| '\nbuffer += escapeHTML(lookup("' + trim(source) + '",stack,""));', | |||||
| '\nbuffer += "' | |||||
| ); | |||||
| }; | |||||
| var line = 1, c, callback; | |||||
| for (var i = 0, len = template.length; i < len; ++i) { | |||||
| if (template.slice(i, i + openTag.length) === openTag) { | |||||
| i += openTag.length; | |||||
| c = template.substr(i, 1); | |||||
| updateLine = '\nline = ' + line + ';'; | |||||
| nextOpenTag = openTag; | |||||
| nextCloseTag = closeTag; | |||||
| hasTag = true; | |||||
| switch (c) { | |||||
| case "!": // comment | |||||
| i++; | |||||
| callback = null; | |||||
| break; | |||||
| case "=": // change open/close tags, e.g. {{=<% %>=}} | |||||
| i++; | |||||
| closeTag = "=" + closeTag; | |||||
| callback = setTags; | |||||
| break; | |||||
| case ">": // include partial | |||||
| i++; | |||||
| callback = includePartial; | |||||
| break; | |||||
| case "#": // start section | |||||
| i++; | |||||
| callback = openSection; | |||||
| break; | |||||
| case "^": // start inverted section | |||||
| i++; | |||||
| callback = openInvertedSection; | |||||
| break; | |||||
| case "/": // end section | |||||
| i++; | |||||
| callback = closeSection; | |||||
| break; | |||||
| case "{": // plain variable | |||||
| closeTag = "}" + closeTag; | |||||
| // fall through | |||||
| case "&": // plain variable | |||||
| i++; | |||||
| nonSpace = true; | |||||
| callback = sendPlain; | |||||
| break; | |||||
| default: // escaped variable | |||||
| nonSpace = true; | |||||
| callback = sendEscaped; | |||||
| } | } | ||||
| } | |||||
| else{ | |||||
| if(is_kinda_truthy(context[name])) { | |||||
| value = context[name]; | |||||
| } else if(is_kinda_truthy(this.context[name])) { | |||||
| value = this.context[name]; | |||||
| var end = template.indexOf(closeTag, i); | |||||
| if (end === -1) { | |||||
| throw debug(new Error('Tag "' + openTag + '" was not closed properly'), template, line, options.file); | |||||
| } | } | ||||
| } | |||||
| if(typeof value === "function") { | |||||
| return value.apply(context); | |||||
| } | |||||
| if(value !== undefined) { | |||||
| return value; | |||||
| } | |||||
| // silently ignore unkown variables | |||||
| return ""; | |||||
| }, | |||||
| walk_context: function(name, context){ | |||||
| var path = name.split('.'); | |||||
| // if the var doesn't exist in current context, check the top level context | |||||
| var value_context = (context[path[0]] != undefined) ? context : this.context; | |||||
| var value = value_context[path.shift()]; | |||||
| while(value != undefined && path.length > 0){ | |||||
| value_context = value; | |||||
| value = value[path.shift()]; | |||||
| } | |||||
| // if the value is a function, call it, binding the correct context | |||||
| if(typeof value === "function") { | |||||
| return value.apply(value_context); | |||||
| } | |||||
| return value; | |||||
| }, | |||||
| // Utility methods | |||||
| /* includes tag */ | |||||
| includes: function(needle, haystack) { | |||||
| return haystack.indexOf(this.otag + needle) != -1; | |||||
| }, | |||||
| /* | |||||
| Does away with nasty characters | |||||
| */ | |||||
| escape: function(s) { | |||||
| s = String(s === null ? "" : s); | |||||
| return s.replace(/&(?!\w+;)|["'<>\\]/g, function(s) { | |||||
| switch(s) { | |||||
| case "&": return "&"; | |||||
| case '"': return '"'; | |||||
| case "'": return '''; | |||||
| case "<": return "<"; | |||||
| case ">": return ">"; | |||||
| default: return s; | |||||
| var source = template.substring(i, end); | |||||
| if (callback) { | |||||
| callback(source); | |||||
| } | } | ||||
| }); | |||||
| }, | |||||
| // by @langalex, support for arrays of strings | |||||
| create_context: function(_context) { | |||||
| if(this.is_object(_context)) { | |||||
| return _context; | |||||
| } else { | |||||
| var iterator = "."; | |||||
| if(this.pragmas["IMPLICIT-ITERATOR"]) { | |||||
| iterator = this.pragmas["IMPLICIT-ITERATOR"].iterator; | |||||
| // Maintain line count for \n in source. | |||||
| var n = 0; | |||||
| while (~(n = source.indexOf("\n", n))) { | |||||
| line++; | |||||
| n++; | |||||
| } | } | ||||
| var ctx = {}; | |||||
| ctx[iterator] = _context; | |||||
| return ctx; | |||||
| } | |||||
| }, | |||||
| is_object: function(a) { | |||||
| return a && typeof a == "object"; | |||||
| }, | |||||
| is_array: function(a) { | |||||
| return Object.prototype.toString.call(a) === '[object Array]'; | |||||
| }, | |||||
| /* | |||||
| Gets rid of leading and trailing whitespace | |||||
| */ | |||||
| trim: function(s) { | |||||
| return s.replace(/^\s*|\s*$/g, ""); | |||||
| }, | |||||
| /* | |||||
| Why, why, why? Because IE. Cry, cry cry. | |||||
| */ | |||||
| map: function(array, fn) { | |||||
| if (typeof array.map == "function") { | |||||
| return array.map(fn); | |||||
| i = end + closeTag.length - 1; | |||||
| openTag = nextOpenTag; | |||||
| closeTag = nextCloseTag; | |||||
| } else { | } else { | ||||
| var r = []; | |||||
| var l = array.length; | |||||
| for(var i = 0; i < l; i++) { | |||||
| r.push(fn(array[i])); | |||||
| c = template.substr(i, 1); | |||||
| switch (c) { | |||||
| case '"': | |||||
| case "\\": | |||||
| nonSpace = true; | |||||
| code.push("\\" + c); | |||||
| break; | |||||
| case "\r": | |||||
| // Ignore carriage returns. | |||||
| break; | |||||
| case "\n": | |||||
| spaces.push(code.length); | |||||
| code.push("\\n"); | |||||
| stripSpace(); // Check for whitespace on the current line. | |||||
| line++; | |||||
| break; | |||||
| default: | |||||
| if (isWhitespace(c)) { | |||||
| spaces.push(code.length); | |||||
| } else { | |||||
| nonSpace = true; | |||||
| } | |||||
| code.push(c); | |||||
| } | } | ||||
| return r; | |||||
| } | } | ||||
| }, | |||||
| } | |||||
| getCachedRegex: function(name, generator) { | |||||
| var byOtag = regexCache[this.otag]; | |||||
| if (!byOtag) { | |||||
| byOtag = regexCache[this.otag] = {}; | |||||
| } | |||||
| if (sectionStack.length != 0) { | |||||
| throw debug(new Error('Section "' + sectionStack[sectionStack.length - 1].name + '" was not closed properly'), template, line, options.file); | |||||
| } | |||||
| var byCtag = byOtag[this.ctag]; | |||||
| if (!byCtag) { | |||||
| byCtag = byOtag[this.ctag] = {}; | |||||
| } | |||||
| // Clean up any whitespace from a closing {{tag}} that was at the end | |||||
| // of the template without a trailing \n. | |||||
| stripSpace(); | |||||
| var regex = byCtag[name]; | |||||
| if (!regex) { | |||||
| regex = byCtag[name] = generator(this.otag, this.ctag); | |||||
| } | |||||
| code.push( | |||||
| '";', | |||||
| "\nreturn buffer;", | |||||
| "\n} catch (e) { throw {error: e, line: line}; }" | |||||
| ); | |||||
| return regex; | |||||
| // Ignore `buffer += "";` statements. | |||||
| var body = code.join("").replace(/buffer \+= "";\n/g, ""); | |||||
| if (options.debug) { | |||||
| if (typeof console != "undefined" && console.log) { | |||||
| console.log(body); | |||||
| } else if (typeof print === "function") { | |||||
| print(body); | |||||
| } | |||||
| } | } | ||||
| }; | |||||
| return({ | |||||
| name: "mustache.js", | |||||
| version: "0.4.0-dev", | |||||
| /* | |||||
| Turns a template and view into HTML | |||||
| */ | |||||
| to_html: function(template, view, partials, send_fun) { | |||||
| var renderer = new Renderer(); | |||||
| if(send_fun) { | |||||
| renderer.send = send_fun; | |||||
| return body; | |||||
| } | |||||
| /** | |||||
| * Used by `compile` to generate a reusable function for the given `template`. | |||||
| */ | |||||
| function _compile(template, options) { | |||||
| var args = "view,partials,stack,lookup,escapeHTML,renderSection,render"; | |||||
| var body = parse(template, options); | |||||
| var fn = new Function(args, body); | |||||
| // This anonymous function wraps the generated function so we can do | |||||
| // argument coercion, setup some variables, and handle any errors | |||||
| // encountered while executing it. | |||||
| return function (view, partials) { | |||||
| partials = partials || {}; | |||||
| var stack = [view]; // context stack | |||||
| try { | |||||
| return fn(view, partials, stack, lookup, escapeHTML, renderSection, render); | |||||
| } catch (e) { | |||||
| throw debug(e.error, template, e.line, options.file); | |||||
| } | } | ||||
| renderer.render(template, view || {}, partials); | |||||
| if(!send_fun) { | |||||
| return renderer.buffer.join("\n"); | |||||
| }; | |||||
| } | |||||
| // Cache of pre-compiled templates. | |||||
| var _cache = {}; | |||||
| /** | |||||
| * Clear the cache of compiled templates. | |||||
| */ | |||||
| function clearCache() { | |||||
| _cache = {}; | |||||
| } | |||||
| /** | |||||
| * Compiles the given `template` into a reusable function using the given | |||||
| * `options`. In addition to the options accepted by Mustache.parse, | |||||
| * recognized options include the following: | |||||
| * | |||||
| * - cache Set `false` to bypass any pre-compiled version of the given | |||||
| * template. Otherwise, a given `template` string will be cached | |||||
| * the first time it is parsed | |||||
| */ | |||||
| function compile(template, options) { | |||||
| options = options || {}; | |||||
| // Use a pre-compiled version from the cache if we have one. | |||||
| if (options.cache !== false) { | |||||
| if (!_cache[template]) { | |||||
| _cache[template] = _compile(template, options); | |||||
| } | } | ||||
| return _cache[template]; | |||||
| } | } | ||||
| }); | |||||
| }(); | |||||
| return _compile(template, options); | |||||
| } | |||||
| /** | |||||
| * High-level function that renders the given `template` using the given | |||||
| * `view` and `partials`. If you need to use any of the template options (see | |||||
| * `compile` above), you must compile in a separate step, and then call that | |||||
| * compiled function. | |||||
| */ | |||||
| function render(template, view, partials) { | |||||
| return compile(template)(view, partials); | |||||
| } | |||||
| })(Mustache); | |||||
| @@ -0,0 +1,14 @@ | |||||
| <?xml version="1.0"?> | |||||
| <package > | |||||
| <metadata> | |||||
| <id>mustache.js</id> | |||||
| <version>0.5.0-dev</version> | |||||
| <authors>mustache.js Authors</authors> | |||||
| <licenseUrl>https://github.com/janl/mustache.js/blob/master/LICENSE</licenseUrl> | |||||
| <projectUrl>http://mustache.github.com/</projectUrl> | |||||
| <requireLicenseAcceptance>false</requireLicenseAcceptance> | |||||
| <description>Logic-less templates in JavaScript.</description> | |||||
| <releaseNotes></releaseNotes> | |||||
| <tags>mustache template templates javascript</tags> | |||||
| </metadata> | |||||
| </package> | |||||
| @@ -0,0 +1,8 @@ | |||||
| { | |||||
| "name": "mustache", | |||||
| "version": "0.5.0-dev", | |||||
| "description": "Logic-less {{mustache}} templates with JavaScript", | |||||
| "author": "mustache.js Authors <http://github.com/janl/mustache.js>", | |||||
| "keywords": ["mustache", "template", "templates", "ejs"], | |||||
| "main": "./mustache" | |||||
| } | |||||
| @@ -0,0 +1,3 @@ | |||||
| var ampersand_escape = { | |||||
| message: "Some <code>" | |||||
| }; | |||||
| @@ -0,0 +1 @@ | |||||
| {{&message}} | |||||
| @@ -0,0 +1 @@ | |||||
| Some <code> | |||||
| @@ -0,0 +1 @@ | |||||
| {{#array_of_strings}}{{.}} {{/array_of_strings}} | |||||
| @@ -0,0 +1,3 @@ | |||||
| var backslashes = { | |||||
| value: "\\abc" | |||||
| }; | |||||
| @@ -0,0 +1,7 @@ | |||||
| * {{value}} | |||||
| * {{{value}}} | |||||
| * {{&value}} | |||||
| <script> | |||||
| foo = { bar: 'abc\"xyz\"' }; | |||||
| foo = { bar: 'x\'y' }; | |||||
| </script> | |||||
| @@ -0,0 +1,7 @@ | |||||
| * \abc | |||||
| * \abc | |||||
| * \abc | |||||
| <script> | |||||
| foo = { bar: 'abc\"xyz\"' }; | |||||
| foo = { bar: 'x\'y' }; | |||||
| </script> | |||||
| @@ -0,0 +1,4 @@ | |||||
| var changing_delimiters = { | |||||
| "foo": "foooooooooooooo", | |||||
| "bar":"<b>bar!</b>" | |||||
| }; | |||||
| @@ -0,0 +1 @@ | |||||
| {{=<% %>=}}<% foo %> {{foo}} <%{bar}%> {{{bar}}} | |||||
| @@ -0,0 +1 @@ | |||||
| foooooooooooooo {{foo}} <b>bar!</b> {{{bar}}} | |||||
| @@ -13,4 +13,4 @@ | |||||
| {{/list}} | {{/list}} | ||||
| {{#empty}} | {{#empty}} | ||||
| <p>The list is empty.</p> | <p>The list is empty.</p> | ||||
| {{/empty}} | |||||
| {{/empty}} | |||||
| @@ -0,0 +1,6 @@ | |||||
| <h1>Colors</h1> | |||||
| <ul> | |||||
| <li><strong>red</strong></li> | |||||
| <li><a href="#Green">green</a></li> | |||||
| <li><a href="#Blue">blue</a></li> | |||||
| </ul> | |||||
| @@ -0,0 +1,8 @@ | |||||
| var context_lookup = { | |||||
| "outer": { | |||||
| "id": 1, | |||||
| "second": { | |||||
| "nothing": 2 | |||||
| } | |||||
| } | |||||
| }; | |||||
| @@ -0,0 +1 @@ | |||||
| {{#outer}}{{#second}}{{id}}{{/second}}{{/outer}} | |||||
| @@ -0,0 +1 @@ | |||||
| 1 | |||||
| @@ -4,4 +4,4 @@ | |||||
| <%=| |=%> | <%=| |=%> | ||||
| * | third | | * | third | | ||||
| |={{ }}=| | |={{ }}=| | ||||
| * {{ fourth }} | |||||
| * {{ fourth }} | |||||
| @@ -0,0 +1,4 @@ | |||||
| var disappearing_whitespace = { | |||||
| bedrooms: true, | |||||
| total: 1 | |||||
| }; | |||||
| @@ -0,0 +1 @@ | |||||
| {{#bedrooms}}{{total}}{{/bedrooms}} BED | |||||
| @@ -0,0 +1 @@ | |||||
| 1 BED | |||||
| @@ -0,0 +1,3 @@ | |||||
| var empty_list = { | |||||
| jobs: [] | |||||
| }; | |||||
| @@ -0,0 +1,4 @@ | |||||
| These are the jobs: | |||||
| {{#jobs}} | |||||
| {{.}} | |||||
| {{/jobs}} | |||||
| @@ -0,0 +1 @@ | |||||
| These are the jobs: | |||||
| @@ -0,0 +1,6 @@ | |||||
| var empty_string = { | |||||
| description: "That is all!", | |||||
| child: { | |||||
| description: "" | |||||
| } | |||||
| }; | |||||
| @@ -0,0 +1 @@ | |||||
| {{description}}{{#child}}{{description}}{{/child}} | |||||
| @@ -0,0 +1 @@ | |||||
| That is all! | |||||
| @@ -0,0 +1 @@ | |||||
| <html><head></head><body><h1>Test</h1></body></html> | |||||