├── examples
├── empty_partial.2.html
├── error_not_found.txt
├── error_not_found.html
├── array_partial.html
├── empty_partial.txt
├── escaped.html
├── two_in_a_row.txt
├── unescaped.html
├── array_of_strings.txt
├── unescaped.txt
├── escaped.txt
├── template_partial.2.html
├── two_in_a_row.html
├── array_of_strings_options.txt
├── empty_template.js
├── error_not_found.js
├── unknown_pragma.js
├── empty_partial.html
├── template_partial.html
├── unknown_pragma.html
├── empty_partial.js
├── template_partial.txt
├── empty_template.html
├── empty_template.txt
├── recursion_with_same_names.txt
├── comments.html
├── reuse_of_enumerables.txt
├── view_partial.html
├── array_of_strings.js
├── simple.txt
├── comments.txt
├── null_string.txt
├── two_in_a_row.js
├── array_of_strings.html
├── array_partial.txt
├── escaped.js
├── array_partial.js
├── unescaped.js
├── array_of_strings_options.js
├── comments.js
├── null_string.html
├── unknown_pragma.txt
├── array_partial.2.html
├── delimiters.html
├── recursion_with_same_names.html
├── simple.html
├── array_of_strings_options.html
├── reuse_of_enumerables.html
├── reuse_of_enumerables.js
├── view_partial.2.html
├── view_partial.txt
├── template_partial.js
├── delimiters.txt
├── complex.txt
├── simple.js
├── null_string.js
├── recursion_with_same_names.js
├── delimiters.js
├── complex.html
├── view_partial.js
└── complex.js
├── .gitignore
├── mustache-yui3
├── mustache.js.tpl.pre
└── mustache.js.tpl.post
├── mustache-dojo
├── mustache.js.tpl.post
└── mustache.js.tpl.pre
├── mustache-jquery
├── jquery.mustache.js.tpl.post
└── jquery.mustache.js.tpl.pre
├── package.json
├── THANKS.md
├── CHANGES.md
├── LICENSE
├── Rakefile
├── test
└── mustache_spec.rb
├── README.md
├── mustache.js
└── lib
└── mustache.js
/examples/empty_partial.2.html:
--------------------------------------------------------------------------------
1 | yo
--------------------------------------------------------------------------------
/examples/error_not_found.txt:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/examples/error_not_found.html:
--------------------------------------------------------------------------------
1 | {{foo}}
--------------------------------------------------------------------------------
/examples/array_partial.html:
--------------------------------------------------------------------------------
1 | {{>partial}}
--------------------------------------------------------------------------------
/examples/empty_partial.txt:
--------------------------------------------------------------------------------
1 | hey 1
2 | yo
3 |
--------------------------------------------------------------------------------
/examples/escaped.html:
--------------------------------------------------------------------------------
1 |
{{title}}
--------------------------------------------------------------------------------
/examples/two_in_a_row.txt:
--------------------------------------------------------------------------------
1 | Welcome, Joe!
2 |
--------------------------------------------------------------------------------
/examples/unescaped.html:
--------------------------------------------------------------------------------
1 | {{{title}}}
--------------------------------------------------------------------------------
/examples/array_of_strings.txt:
--------------------------------------------------------------------------------
1 | hello world
2 |
--------------------------------------------------------------------------------
/examples/unescaped.txt:
--------------------------------------------------------------------------------
1 | Bear > Shark
2 |
--------------------------------------------------------------------------------
/examples/escaped.txt:
--------------------------------------------------------------------------------
1 | Bear > Shark
2 |
--------------------------------------------------------------------------------
/examples/template_partial.2.html:
--------------------------------------------------------------------------------
1 | Again, {{again}}!
--------------------------------------------------------------------------------
/examples/two_in_a_row.html:
--------------------------------------------------------------------------------
1 | {{greeting}}, {{name}}!
--------------------------------------------------------------------------------
/examples/array_of_strings_options.txt:
--------------------------------------------------------------------------------
1 | hello world
2 |
--------------------------------------------------------------------------------
/examples/empty_template.js:
--------------------------------------------------------------------------------
1 | var empty_template = {};
2 |
--------------------------------------------------------------------------------
/examples/error_not_found.js:
--------------------------------------------------------------------------------
1 | var error_not_found = {bar: 2};
--------------------------------------------------------------------------------
/examples/unknown_pragma.js:
--------------------------------------------------------------------------------
1 | var unknown_pragma = {};
2 |
--------------------------------------------------------------------------------
/examples/empty_partial.html:
--------------------------------------------------------------------------------
1 | hey {{foo}}
2 | {{>partial}}
3 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | runner.js
2 | jquery.mustache.js
3 | dojox
4 | yui3
5 |
--------------------------------------------------------------------------------
/examples/template_partial.html:
--------------------------------------------------------------------------------
1 | {{title}}
2 | {{>partial}}
--------------------------------------------------------------------------------
/examples/unknown_pragma.html:
--------------------------------------------------------------------------------
1 | {{%I-HAVE-THE-GREATEST-MUSTACHE}}
2 |
--------------------------------------------------------------------------------
/examples/empty_partial.js:
--------------------------------------------------------------------------------
1 | var partial_context = {
2 | foo: 1
3 | };
4 |
--------------------------------------------------------------------------------
/examples/template_partial.txt:
--------------------------------------------------------------------------------
1 | Welcome
2 | Again, Goodbye!
3 |
--------------------------------------------------------------------------------
/mustache-yui3/mustache.js.tpl.pre:
--------------------------------------------------------------------------------
1 | YUI.add("mustache", function(Y) {
2 |
--------------------------------------------------------------------------------
/examples/empty_template.html:
--------------------------------------------------------------------------------
1 | Test
--------------------------------------------------------------------------------
/examples/empty_template.txt:
--------------------------------------------------------------------------------
1 | Test
2 |
--------------------------------------------------------------------------------
/examples/recursion_with_same_names.txt:
--------------------------------------------------------------------------------
1 | name
2 | desc
3 | t1
4 | 0
5 | t2
6 | 1
7 |
--------------------------------------------------------------------------------
/examples/comments.html:
--------------------------------------------------------------------------------
1 | {{title}}{{! just something interesting... or not... }}
2 |
--------------------------------------------------------------------------------
/examples/reuse_of_enumerables.txt:
--------------------------------------------------------------------------------
1 | t1
2 | 0
3 | t2
4 | 1
5 | t1
6 | 0
7 | t2
8 | 1
9 |
--------------------------------------------------------------------------------
/examples/view_partial.html:
--------------------------------------------------------------------------------
1 | {{greeting}}
2 | {{>partial}}
3 | {{farewell}}
--------------------------------------------------------------------------------
/examples/array_of_strings.js:
--------------------------------------------------------------------------------
1 | var array_of_strings = {array_of_strings: ['hello', 'world']};
2 |
--------------------------------------------------------------------------------
/examples/simple.txt:
--------------------------------------------------------------------------------
1 | Hello Chris
2 | You have just won $10000!
3 | Well, $6000, after taxes.
4 |
--------------------------------------------------------------------------------
/mustache-yui3/mustache.js.tpl.post:
--------------------------------------------------------------------------------
1 |
2 | Y.mustache = Mustache.to_html;
3 |
4 | }, "0");
5 |
--------------------------------------------------------------------------------
/examples/comments.txt:
--------------------------------------------------------------------------------
1 | A Comedy of Errors{{! just something interesting... or not... }}
2 |
--------------------------------------------------------------------------------
/examples/null_string.txt:
--------------------------------------------------------------------------------
1 | Hello Elise
2 | glytch true
3 | binary false
4 | value
5 | numeric NaN
6 |
--------------------------------------------------------------------------------
/examples/two_in_a_row.js:
--------------------------------------------------------------------------------
1 | var two_in_a_row = {
2 | name: "Joe",
3 | greeting: "Welcome"
4 | };
5 |
--------------------------------------------------------------------------------
/examples/array_of_strings.html:
--------------------------------------------------------------------------------
1 | {{%IMPLICIT-ITERATOR}}
2 | {{#array_of_strings}} {{.}} {{/array_of_strings}}
--------------------------------------------------------------------------------
/examples/array_partial.txt:
--------------------------------------------------------------------------------
1 | Here's a non-sense array of values
2 |
3 |
4 | 1
5 | 2
6 | 3
7 | 4
8 |
9 |
--------------------------------------------------------------------------------
/examples/escaped.js:
--------------------------------------------------------------------------------
1 | var escaped = {
2 | title: function() {
3 | return "Bear > Shark";
4 | }
5 | };
6 |
--------------------------------------------------------------------------------
/examples/array_partial.js:
--------------------------------------------------------------------------------
1 | var partial_context = {
2 | partial: {
3 | array: ['1', '2', '3', '4']
4 | }
5 | };
--------------------------------------------------------------------------------
/examples/unescaped.js:
--------------------------------------------------------------------------------
1 | var unescaped = {
2 | title: function() {
3 | return "Bear > Shark";
4 | }
5 | };
6 |
--------------------------------------------------------------------------------
/examples/array_of_strings_options.js:
--------------------------------------------------------------------------------
1 | var array_of_strings_options = {array_of_strings_options: ['hello', 'world']};
2 |
--------------------------------------------------------------------------------
/examples/comments.js:
--------------------------------------------------------------------------------
1 | var comments = {
2 | title: function() {
3 | return "A Comedy of Errors";
4 | }
5 | };
6 |
--------------------------------------------------------------------------------
/examples/null_string.html:
--------------------------------------------------------------------------------
1 | Hello {{name}}
2 | glytch {{glytch}}
3 | binary {{binary}}
4 | value {{value}}
5 | numeric {{numeric}}
--------------------------------------------------------------------------------
/examples/unknown_pragma.txt:
--------------------------------------------------------------------------------
1 | ERROR: This implementation of mustache doesn't understand the 'I-HAVE-THE-GREATEST-MUSTACHE' pragma
2 |
--------------------------------------------------------------------------------
/examples/array_partial.2.html:
--------------------------------------------------------------------------------
1 | Here's a non-sense array of values
2 |
3 | {{%IMPLICIT-ITERATOR}}
4 | {{#array}}
5 | {{.}}
6 | {{/array}}
--------------------------------------------------------------------------------
/examples/delimiters.html:
--------------------------------------------------------------------------------
1 | {{=<% %>=}}*
2 | <% first %>
3 | * <% second %>
4 | <%=| |=%>
5 | * | third |
6 | |={{ }}=|
7 | * {{ fourth }}
--------------------------------------------------------------------------------
/examples/recursion_with_same_names.html:
--------------------------------------------------------------------------------
1 | {{ name }}
2 | {{ description }}
3 |
4 | {{#terms}}
5 | {{name}}
6 | {{index}}
7 | {{/terms}}
8 |
--------------------------------------------------------------------------------
/examples/simple.html:
--------------------------------------------------------------------------------
1 | Hello {{name}}
2 | You have just won ${{value}}!
3 | {{#in_ca}}
4 | Well, ${{ taxed_value }}, after taxes.
5 | {{/in_ca}}
--------------------------------------------------------------------------------
/examples/array_of_strings_options.html:
--------------------------------------------------------------------------------
1 | {{%IMPLICIT-ITERATOR iterator=rob}}
2 | {{#array_of_strings_options}} {{rob}} {{/array_of_strings_options}}
--------------------------------------------------------------------------------
/examples/reuse_of_enumerables.html:
--------------------------------------------------------------------------------
1 | {{#terms}}
2 | {{name}}
3 | {{index}}
4 | {{/terms}}
5 | {{#terms}}
6 | {{name}}
7 | {{index}}
8 | {{/terms}}
9 |
--------------------------------------------------------------------------------
/examples/reuse_of_enumerables.js:
--------------------------------------------------------------------------------
1 | var reuse_of_enumerables = {
2 | terms: [
3 | {name: 't1', index: 0},
4 | {name: 't2', index: 1},
5 | ]
6 | };
--------------------------------------------------------------------------------
/examples/view_partial.2.html:
--------------------------------------------------------------------------------
1 | Hello {{name}}
2 | You have just won ${{value}}!
3 | {{#in_ca}}
4 | Well, ${{ taxed_value }}, after taxes.
5 | {{/in_ca}}
6 |
--------------------------------------------------------------------------------
/examples/view_partial.txt:
--------------------------------------------------------------------------------
1 | Welcome
2 | Hello Chris
3 | You have just won $10000!
4 | Well, $6000, after taxes.
5 |
6 | Fair enough, right?
7 |
--------------------------------------------------------------------------------
/examples/template_partial.js:
--------------------------------------------------------------------------------
1 | var partial_context = {
2 | title: function() {
3 | return "Welcome";
4 | },
5 | partial: {
6 | again: "Goodbye"
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/mustache-dojo/mustache.js.tpl.post:
--------------------------------------------------------------------------------
1 |
2 | d.mustache = function(template, view, partials) {
3 | return Mustache.to_html(template, view, partials);
4 | };
5 | })(dojo);
--------------------------------------------------------------------------------
/examples/delimiters.txt:
--------------------------------------------------------------------------------
1 | *
2 | It worked the first time.
3 | * And it worked the second time.
4 | * Then, surprisingly, it worked the third time.
5 | * Fourth time also fine!.
6 |
--------------------------------------------------------------------------------
/examples/complex.txt:
--------------------------------------------------------------------------------
1 | Colors
2 |
7 |
--------------------------------------------------------------------------------
/examples/simple.js:
--------------------------------------------------------------------------------
1 | var simple = {
2 | name: "Chris",
3 | value: 10000,
4 | taxed_value: function() {
5 | return this.value - (this.value * 0.4);
6 | },
7 | in_ca: true
8 | };
9 |
--------------------------------------------------------------------------------
/mustache-jquery/jquery.mustache.js.tpl.post:
--------------------------------------------------------------------------------
1 |
2 | $.mustache = function(template, view, partials) {
3 | return Mustache.to_html(template, view, partials);
4 | };
5 |
6 | })(jQuery);
7 |
--------------------------------------------------------------------------------
/examples/null_string.js:
--------------------------------------------------------------------------------
1 | var null_string = {
2 | name: "Elise",
3 | glytch: true,
4 | binary: false,
5 | value: null,
6 | numeric: function() {
7 | return NaN;
8 | }
9 | };
10 |
--------------------------------------------------------------------------------
/examples/recursion_with_same_names.js:
--------------------------------------------------------------------------------
1 | var recursion_with_same_names = {
2 | name: 'name',
3 | description: 'desc',
4 | terms: [
5 | {name: 't1', index: 0},
6 | {name: 't2', index: 1},
7 | ]
8 | };
--------------------------------------------------------------------------------
/mustache-jquery/jquery.mustache.js.tpl.pre:
--------------------------------------------------------------------------------
1 | /*
2 | Shameless port of a shameless port
3 | @defunkt => @janl => @aq
4 |
5 | See http://github.com/defunkt/mustache for more info.
6 | */
7 |
8 | ;(function($) {
9 |
10 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "mustache",
3 | "author": "Jan Lehnardt",
4 | "description": "{{mustaches}} in JavaScript — shameless port from @defunkt",
5 | "keywords": ["template"],
6 | "version": "0.2.3"
7 | }
8 |
--------------------------------------------------------------------------------
/examples/delimiters.js:
--------------------------------------------------------------------------------
1 | var delimiters = {
2 | first: "It worked the first time.",
3 | second: "And it worked the second time.",
4 | third: "Then, surprisingly, it worked the third time.",
5 | fourth: "Fourth time also fine!."
6 | }
7 |
--------------------------------------------------------------------------------
/mustache-dojo/mustache.js.tpl.pre:
--------------------------------------------------------------------------------
1 | /*
2 | Shameless port of a shameless port
3 | @defunkt => @janl => @aq => @voodootikigod
4 |
5 | See http://github.com/defunkt/mustache for more info.
6 | */
7 |
8 | dojo.provide("dojox.string.mustache");
9 | ;(function(d) {
10 |
--------------------------------------------------------------------------------
/examples/complex.html:
--------------------------------------------------------------------------------
1 | {{header}}
2 | {{#list}}
3 |
4 | {{#item}}
5 | {{#current}}
6 | - {{name}}
7 | {{/current}}
8 | {{#link}}
9 | - {{name}}
10 | {{/link}}
11 | {{/item}}
12 |
13 | {{/list}}
14 | {{#empty}}
15 | The list is empty.
16 | {{/empty}}
--------------------------------------------------------------------------------
/examples/view_partial.js:
--------------------------------------------------------------------------------
1 | var partial_context = {
2 | greeting: function() {
3 | return "Welcome";
4 | },
5 |
6 | farewell: function() {
7 | return "Fair enough, right?";
8 | },
9 |
10 | partial: {
11 | name: "Chris",
12 | value: 10000,
13 | taxed_value: function() {
14 | return this.value - (this.value * 0.4);
15 | },
16 | in_ca: true
17 | }
18 | };
19 |
20 |
--------------------------------------------------------------------------------
/THANKS.md:
--------------------------------------------------------------------------------
1 | # Thanks
2 |
3 | Mustache.js wouldn't kick ass if it weren't for these fine souls:
4 |
5 | * Chris Wanstrath / defunkt
6 | * Alexander Lang / langalex
7 | * Sebastian Cohnen / tisba
8 | * J Chris Anderson / jchris
9 | * Tom Robinson / tlrobinson
10 | * Aaron Quint / quirkey
11 | * Douglas Crockford
12 | * Nikita Vasilyev / NV
13 | * Elise Wood / glytch
14 | * Damien Mathieu / dmathieu
15 | * Jakub Kuźma / qoobaa
16 | * Will Leinweber / will
17 |
--------------------------------------------------------------------------------
/examples/complex.js:
--------------------------------------------------------------------------------
1 | var complex = {
2 | header: function() {
3 | return "Colors";
4 | },
5 | item: [
6 | {name: "red", current: true, url: "#Red"},
7 | {name: "green", current: false, url: "#Green"},
8 | {name: "blue", current: false, url: "#Blue"}
9 | ],
10 | link: function() {
11 | return this["current"] !== true;
12 | },
13 | list: function() {
14 | return this.item.length !== 0;
15 | },
16 | empty: function() {
17 | return this.item.length === 0;
18 | }
19 | };
20 |
--------------------------------------------------------------------------------
/CHANGES.md:
--------------------------------------------------------------------------------
1 | # mustache.js Changes
2 |
3 | ## 0.3.0 (??-??-????)
4 |
5 |
6 | ## 0.2.3 (28-03-2010)
7 |
8 | * Better error message for missing partials.
9 | * Added more robust type detection.
10 | * Parse pragmas only once.
11 | * Throw exception when encountering an unknown pragma.
12 | * Ignore undefined partial contexts. Returns verbatim partials.
13 | * Added yui3 packaging.
14 |
15 |
16 | ## 0.2.2 (11-02-2010)
17 |
18 | * ctemplate compat: Partials are indicated by >, not <.
19 | * Add support for {{%PRAGMA}} to enable features.
20 | * Made array of strings an option. Enable with `{{%JSTACHE-ENABLE-STRING-ARRAYS}}`.
21 | * mustache compat: Don't barf on unknown variables.
22 | * Add `rake dojo` target to create a dojo package.
23 | * Add streaming api.
24 | * Rename JSTACHE-ENABLE-STRING-ARRAYS to IMPLICIT-ITERATOR.
25 | * Add support for pragma options.
26 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2009 Chris Wanstrath (Ruby)
2 | Copyright (c) 2010 Jan Lehnardt (JavaScript)
3 |
4 | Permission is hereby granted, free of charge, to any person obtaining
5 | a copy of this software and associated documentation files (the
6 | "Software"), to deal in the Software without restriction, including
7 | without limitation the rights to use, copy, modify, merge, publish,
8 | distribute, sublicense, and/or sell copies of the Software, and to
9 | permit persons to whom the Software is furnished to do so, subject to
10 | the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be
13 | included in all copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
19 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
20 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
21 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
--------------------------------------------------------------------------------
/Rakefile:
--------------------------------------------------------------------------------
1 | require 'rake'
2 | require 'spec/rake/spectask'
3 |
4 | task :default => :spec
5 |
6 | Spec::Rake::SpecTask.new(:spec) do |t|
7 | #t.spec_opts = ['--options', "\"#{File.dirname(__FILE__)}/spec/spec.opts\""]
8 | t.spec_files = FileList['test/*_spec.rb']
9 | end
10 |
11 | desc "Run all specs"
12 | task :spec do
13 | end
14 |
15 | task :commonjs do
16 | print "Packaging for CommonJS\n"
17 | `mkdir lib`
18 | `cp mustache.js lib/mustache.js`
19 | print "Done.\n"
20 | end
21 |
22 | task :jquery do
23 | print "Packaging for jQuery\n"
24 | source = "mustache-jquery"
25 | target_jq = "jquery.mustache.js"
26 | `cat #{source}/#{target_jq}.tpl.pre mustache.js #{source}/#{target_jq}.tpl.post > #{target_jq}`
27 | print "Done, see ./#{target_jq}\n"
28 | end
29 |
30 |
31 | task :dojo do
32 | print "Packaging for dojo\n"
33 | source = "mustache-dojo"
34 | target_js = "mustache.js"
35 | `mkdir -p dojox; mkdir -p dojox/string`
36 | `cat #{source}/#{target_js}.tpl.pre mustache.js #{source}/#{target_js}.tpl.post > dojox/string/#{target_js}`
37 | print "Done, see ./dojox/string/#{target_js} Include using dojo.require('dojox.string.mustache.'); \n"
38 | end
39 |
40 | task :yui3 do
41 | print "Packaging for YUI3\n"
42 | source = "mustache-yui3"
43 | target_js = "mustache.js"
44 | `mkdir -p yui3; mkdir -p yui3/mustache`
45 | `cat #{source}/#{target_js}.tpl.pre mustache.js #{source}/#{target_js}.tpl.post > yui3/mustache/#{target_js}`
46 | print "Done, see ./yui3/mustache/#{target_js}\n"
47 | end
48 |
49 | task :clean do
50 | `git clean -fdx`
51 | end
52 |
--------------------------------------------------------------------------------
/test/mustache_spec.rb:
--------------------------------------------------------------------------------
1 | require 'rubygems'
2 | require 'json'
3 |
4 | __DIR__ = File.dirname(__FILE__)
5 |
6 | testnames = Dir.glob(__DIR__ + '/../examples/*.js').map do |name|
7 | File.basename name, '.js'
8 | end
9 |
10 | non_partials = testnames.select{|t| not t.include? "partial"}
11 | partials = testnames.select{|t| t.include? "partial"}
12 |
13 | def load_test(dir, name, partial=false)
14 | view = File.read(dir + "/../examples/#{name}.js")
15 | template = File.read(dir + "/../examples/#{name}.html").to_json
16 | expect = File.read(dir + "/../examples/#{name}.txt")
17 | if not partial
18 | [view, template, expect]
19 | else
20 | partial = File.read(dir + "/../examples/#{name}.2.html").to_json
21 | [view, template, partial, expect]
22 | end
23 | end
24 |
25 | describe "mustache" do
26 | before(:all) do
27 | @mustache = File.read(__DIR__ + "/../mustache.js")
28 | end
29 |
30 | it "should clear the context after each run" do
31 | js = <<-JS
32 | #{@mustache}
33 | Mustache.to_html("{{#list}}{{x}}{{/list}}", {list: [{x: 1}]})
34 | try {
35 | print(Mustache.to_html("{{#list}}{{x}}{{/list}}", {list: [{}]}));
36 | } catch(e) {
37 | print('ERROR: ' + e.message);
38 | }
39 | JS
40 | run_js(js).should == "\n"
41 | end
42 |
43 | non_partials.each do |testname|
44 | describe testname do
45 | it "should generate the correct html" do
46 |
47 | view, template, expect = load_test(__DIR__, testname)
48 |
49 | runner = <<-JS
50 | try {
51 | #{@mustache}
52 | #{view}
53 | var template = #{template};
54 | var result = Mustache.to_html(template, #{testname});
55 | print(result);
56 | } catch(e) {
57 | print('ERROR: ' + e.message);
58 | }
59 | JS
60 |
61 | run_js(runner).should == expect
62 | end
63 | it "should sendFun the correct html" do
64 |
65 | view, template, expect = load_test(__DIR__, testname)
66 |
67 | runner = <<-JS
68 | try {
69 | #{@mustache}
70 | #{view}
71 | var chunks = [];
72 | var sendFun = function(chunk) {
73 | if (chunk != "") {
74 | chunks.push(chunk);
75 | }
76 | }
77 | var template = #{template};
78 | Mustache.to_html(template, #{testname}, null, sendFun);
79 | print(chunks.join("\\n"));
80 | } catch(e) {
81 | print('ERROR: ' + e.message);
82 | }
83 | JS
84 |
85 | run_js(runner).strip.should == expect.strip
86 | end
87 | end
88 | end
89 |
90 | partials.each do |testname|
91 | describe testname do
92 | it "should generate the correct html" do
93 |
94 | view, template, partial, expect =
95 | load_test(__DIR__, testname, true)
96 |
97 | runner = <<-JS
98 | try {
99 | #{@mustache}
100 | #{view}
101 | var template = #{template};
102 | var partials = {"partial": #{partial}};
103 | var result = Mustache.to_html(template, partial_context, partials);
104 | print(result);
105 | } catch(e) {
106 | print('ERROR: ' + e.message);
107 | }
108 | JS
109 |
110 | run_js(runner).should == expect
111 | end
112 | it "should sendFun the correct html" do
113 |
114 | view, template, partial, expect =
115 | load_test(__DIR__, testname, true)
116 |
117 | runner = <<-JS
118 | try {
119 | #{@mustache}
120 | #{view};
121 | var template = #{template};
122 | var partials = {"partial": #{partial}};
123 | var chunks = [];
124 | var sendFun = function(chunk) {
125 | if (chunk != "") {
126 | chunks.push(chunk);
127 | }
128 | }
129 | Mustache.to_html(template, partial_context, partials, sendFun);
130 | print(chunks.join("\\n"));
131 | } catch(e) {
132 | print('ERROR: ' + e.message);
133 | }
134 | JS
135 |
136 | run_js(runner).strip.should == expect.strip
137 | end
138 | end
139 | end
140 |
141 | def run_js(js)
142 | File.open("runner.js", 'w') {|f| f << js}
143 | `js runner.js`
144 | end
145 | end
146 |
147 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # mustache.js — Logic-less templates with JavaScript
2 |
3 | > What could be more logical awesome than no logic at all?
4 |
5 | For a list of implementations (other than JavaScript) and editor
6 | plugins, see .
7 |
8 |
9 | ## Where to Use?
10 |
11 | You can use mustache.js rendering stuff in various scenarios. E.g. you can render
12 | templates in your browser, or rendering server-side stuff with [node.js][node.js],
13 | use it for rendering stuff in [CouchDB][couchdb]'s views.
14 |
15 |
16 | ## Who Uses Mustache?
17 |
18 | An updated list is kept on the Github wiki. Add yourself, if you use
19 | mustache.js:
20 |
21 |
22 |
23 | ## Usage
24 |
25 | A quick example how to use mustache.js:
26 |
27 | var view = {
28 | title: "Joe",
29 | calc: function() {
30 | return 2 + 4;
31 | }
32 | }
33 |
34 | var template = "{{title}} spends {{calc}}";
35 |
36 | var html = Mustache.to_html(template, view);
37 |
38 | `template` is a simple string with mustache tags and `view` is a JavaScript object containing the.
39 |
40 |
41 | ## Template Tag Types
42 |
43 | There are several types of tags currently implemented in mustache.js.
44 |
45 | For a language-agnostic overview of Mustache's template syntax, see
46 | the `mustache(5)` manpage or
47 | .
48 |
49 | ### Simple Tags
50 |
51 | Tags are always surrounded by mustaches like this `{{foobar}}`.
52 |
53 | var view = {name: "Joe", say_hello: function(){ return "hello" }}
54 |
55 | template = "{{say_hello}}, {{name}}"
56 |
57 |
58 | ### Conditional Sections
59 |
60 | Conditional sections begin with `{{#condition}}` and end with `{{/condition}}`. When
61 | `condition` evaluates to true, the section is rendered, otherwise the hole block will
62 | output nothing at all. `condition` may be a function returning true/false or a simple
63 | boolean.
64 |
65 | var view = {condition: function() {
66 | // [...your code goes here...]
67 | return true;
68 | }}
69 |
70 | {{#condition}}
71 | I will be visible if condition is true
72 | {{/condition}}
73 |
74 |
75 | ### Enumerable Sections
76 |
77 | Enumerable Sections use the same syntax as condition sections do.
78 | `{{#shopping_items}}` and `{{/shopping_items}}`. Actually the view decides how
79 | mustache.js renders the section. If the view returns an array, it will iterator over
80 | the items. Use `{{.}}` to access the current item inside the enumeration section.
81 |
82 | var view = {name: "Joe's shopping card",
83 | items: ["bananas", "apples"]}
84 |
85 | var template = "{{name}}: {{#items}}- {{.}}
{{/items}}
"
86 |
87 | Outputs:
88 | Joe's shopping card:
89 |
90 |
91 | ### View Partials
92 |
93 | mustache.js supports a quite powerful but yet simple view partial mechanism. Use the
94 | following syntax for partials: `{{>partial_name}}`
95 |
96 | var view = {
97 | name: "Joe",
98 | winnings: {
99 | value: 1000,
100 | taxed_value: function() {
101 | return this.value - (this.value * 0.4);
102 | }
103 | }
104 | };
105 |
106 | var template = "Welcome, {{name}}! {{>winnings}}"
107 | var partials = {
108 | winnings: "You just won ${{value}} (which is ${{taxed_value}} after tax)"};
109 |
110 | var output = Mustache.to_html(template, view, partials)
111 |
112 | output will be:
113 | Welcome, Joe! You just won $1000 (which is $600 after tax)
114 |
115 | You invoke a partial with `{{>winnings}}`. Invoking the partial `winnings` will tell
116 | mustache.js to look for a object in the context's property `winnings`. It will then
117 | use that object as the context for the template found in `partials` for `winnings`.
118 |
119 |
120 | ## Escaping
121 |
122 | mustache.js does escape all values when using the standard double mustache syntax.
123 | Characters which will be escaped: `& \ " < >`. To disable escaping, simply use
124 | tripple mustaches like `{{{unescaped_variable}}}`.
125 |
126 | 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`.
127 |
128 |
129 | ## Streaming
130 |
131 | To stream template results out of mustache.js, you can pass an optional `send()`
132 | callback to the `to_html()` call:
133 |
134 | Mustache.to_html(template, view, partials, function(line) {
135 | print(line);
136 | });
137 |
138 |
139 | ## Pragmas
140 |
141 | Pragma tags let you alter the behaviour of mustache.js. They have the format of
142 |
143 | {{%PRAGMANAME}}
144 |
145 | and they accept options:
146 |
147 | {{%PRAGMANAME option=value}}
148 |
149 |
150 | ### IMPLICIT-ITERATOR
151 |
152 | When using a block to iterate over an enumerable (Array), mustache.js expects an
153 | objects as enumerable items. The implicit iterator pragma enables optional behaviour
154 | of allowing literals as enumerable items. Consider this view:
155 |
156 | var view = {
157 | foo: [1, 2, 3, 4, 5, "french"]
158 | };
159 |
160 | The following template can iterate over the member `foo`:
161 |
162 | {{%IMPLICIT-ITERATOR}}
163 | {{#foo}}
164 | {{.}}
165 | {{/foo}}
166 |
167 | If you don't like the dot in there, the pragma accepts an option to set your own
168 | iteration marker:
169 |
170 | {{%IMPLICIT-ITERATOR iterator=bob}}
171 | {{#foo}}
172 | {{bob}}
173 | {{/foo}}
174 |
175 |
176 | ## More Examples and Documentation
177 |
178 | See `examples/` for more goodies and read the [original mustache docs][m]
179 |
180 | ## Command Line
181 |
182 | See `mustache(1)` man page or
183 |
184 | for command line docs.
185 |
186 | Or just install it as a RubyGem:
187 |
188 | $ gem install mustache
189 | $ mustache -h
190 |
191 | [m]: http://github.com/defunkt/mustache/#readme
192 | [node.js]: http://nodejs.org
193 | [couchdb]: http://couchdb.apache.org
194 |
--------------------------------------------------------------------------------
/mustache.js:
--------------------------------------------------------------------------------
1 | /*
2 | mustache.js — Logic-less templates in JavaScript
3 |
4 | See http://mustache.github.com/ for more info.
5 | */
6 |
7 | var Mustache = function() {
8 | var Renderer = function() {};
9 |
10 | Renderer.prototype = {
11 | otag: "{{",
12 | ctag: "}}",
13 | pragmas: {},
14 | buffer: [],
15 | pragmas_implemented: {
16 | "IMPLICIT-ITERATOR": true
17 | },
18 |
19 | render: function(template, context, partials, in_recursion) {
20 | // fail fast
21 | if(template.indexOf(this.otag) == -1) {
22 | if(in_recursion) {
23 | return template;
24 | } else {
25 | this.send(template);
26 | return;
27 | }
28 | }
29 |
30 | if(!in_recursion) {
31 | this.buffer = [];
32 | }
33 |
34 | template = this.render_pragmas(template);
35 | var html = this.render_section(template, context, partials);
36 | if(in_recursion) {
37 | return this.render_tags(html, context, partials, in_recursion);
38 | }
39 |
40 | this.render_tags(html, context, partials, in_recursion);
41 | },
42 |
43 | /*
44 | Sends parsed lines
45 | */
46 | send: function(line) {
47 | if(line != "") {
48 | this.buffer.push(line);
49 | }
50 | },
51 |
52 | /*
53 | Looks for %PRAGMAS
54 | */
55 | render_pragmas: function(template) {
56 | // no pragmas
57 | if(template.indexOf(this.otag + "%") == -1) {
58 | return template;
59 | }
60 |
61 | var that = this;
62 | var regex = new RegExp(this.otag + "%([\\w_-]+) ?([\\w]+=[\\w]+)?"
63 | + this.ctag);
64 | return template.replace(regex, function(match, pragma, options) {
65 | if(!that.pragmas_implemented[pragma]) {
66 | throw({message: "This implementation of mustache doesn't understand the '"
67 | + pragma + "' pragma"});
68 | }
69 | that.pragmas[pragma] = {};
70 | if(options) {
71 | var opts = options.split("=");
72 | that.pragmas[pragma][opts[0]] = opts[1];
73 | }
74 | return "";
75 | // ignore unknown pragmas silently
76 | });
77 | },
78 |
79 | /*
80 | Tries to find a partial in the global scope and render it
81 | */
82 | render_partial: function(name, context, partials) {
83 | if(!partials || !partials[name]) {
84 | throw({message: "unknown_partial '" + name + "'"});
85 | }
86 | if(typeof(context[name]) != "object") {
87 | return partials[name];
88 | }
89 | return this.render(partials[name], context[name], partials, true);
90 | },
91 |
92 | /*
93 | Renders boolean and enumerable sections
94 | */
95 | render_section: function(template, context, partials) {
96 | if(template.indexOf(this.otag + "#") == -1) {
97 | return template;
98 | }
99 | var that = this;
100 | // CSW - Added "+?" so it finds the tighest bound, not the widest
101 | var regex = new RegExp(this.otag + "\\#(.+)" + this.ctag +
102 | "\\s*([\\s\\S]+?)" + this.otag + "\\/\\1" + this.ctag + "\\s*", "mg");
103 |
104 | // for each {{#foo}}{{/foo}} section do...
105 | return template.replace(regex, function(match, name, content) {
106 | var value = that.find(name, context);
107 | if(that.is_array(value)) { // Enumerable, Let's loop!
108 | return that.map(value, function(row) {
109 | return that.render(content, that.merge(context,
110 | that.create_context(row)), partials, true);
111 | }).join("");
112 | } else if(value) { // boolean section
113 | return that.render(content, context, partials, true);
114 | } else {
115 | return "";
116 | }
117 | });
118 | },
119 |
120 | /*
121 | Replace {{foo}} and friends with values from our view
122 | */
123 | render_tags: function(template, context, partials, in_recursion) {
124 | // tit for tat
125 | var that = this;
126 |
127 | var new_regex = function() {
128 | return new RegExp(that.otag + "(=|!|>|\\{|%)?([^\/#]+?)\\1?" +
129 | that.ctag + "+", "g");
130 | };
131 |
132 | var regex = new_regex();
133 | var lines = template.split("\n");
134 | for (var i=0; i < lines.length; i++) {
135 | lines[i] = lines[i].replace(regex, function(match, operator, name) {
136 | switch(operator) {
137 | case "!": // ignore comments
138 | return match;
139 | case "=": // set new delimiters, rebuild the replace regexp
140 | that.set_delimiters(name);
141 | regex = new_regex();
142 | return "";
143 | case ">": // render partial
144 | return that.render_partial(name, context, partials);
145 | case "{": // the triple mustache is unescaped
146 | return that.find(name, context);
147 | default: // escape the value
148 | return that.escape(that.find(name, context));
149 | }
150 | }, this);
151 | if(!in_recursion) {
152 | this.send(lines[i]);
153 | }
154 | }
155 |
156 | if(in_recursion) {
157 | return lines.join("\n");
158 | }
159 | },
160 |
161 | set_delimiters: function(delimiters) {
162 | var dels = delimiters.split(" ");
163 | this.otag = this.escape_regex(dels[0]);
164 | this.ctag = this.escape_regex(dels[1]);
165 | },
166 |
167 | escape_regex: function(text) {
168 | // thank you Simon Willison
169 | if(!arguments.callee.sRE) {
170 | var specials = [
171 | '/', '.', '*', '+', '?', '|',
172 | '(', ')', '[', ']', '{', '}', '\\'
173 | ];
174 | arguments.callee.sRE = new RegExp(
175 | '(\\' + specials.join('|\\') + ')', 'g'
176 | );
177 | }
178 | return text.replace(arguments.callee.sRE, '\\$1');
179 | },
180 |
181 | /*
182 | find `name` in current `context`. That is find me a value
183 | from the view object
184 | */
185 | find: function(name, context) {
186 | name = this.trim(name);
187 | if(typeof context[name] === "function") {
188 | return context[name].apply(context);
189 | }
190 | if(context[name] !== undefined) {
191 | return context[name];
192 | }
193 | // silently ignore unkown variables
194 | return "";
195 | },
196 |
197 | // Utility methods
198 |
199 | /*
200 | Does away with nasty characters
201 | */
202 | escape: function(s) {
203 | return ((s == null) ? "" : s).toString().replace(/[&"<>\\]/g, function(s) {
204 | switch(s) {
205 | case "&": return "&";
206 | case "\\": return "\\\\";;
207 | case '"': return '\"';;
208 | case "<": return "<";
209 | case ">": return ">";
210 | default: return s;
211 | }
212 | });
213 | },
214 |
215 | /*
216 | Merges all properties of object `b` into object `a`.
217 | `b.property` overwrites a.property`
218 | */
219 | merge: function(a, b) {
220 | var _new = {};
221 | for(var name in a) {
222 | if(a.hasOwnProperty(name)) {
223 | _new[name] = a[name];
224 | }
225 | };
226 | for(var name in b) {
227 | if(b.hasOwnProperty(name)) {
228 | _new[name] = b[name];
229 | }
230 | };
231 | return _new;
232 | },
233 |
234 | // by @langalex, support for arrays of strings
235 | create_context: function(_context) {
236 | if(this.is_object(_context)) {
237 | return _context;
238 | } else if(this.pragmas["IMPLICIT-ITERATOR"]) {
239 | var iterator = this.pragmas["IMPLICIT-ITERATOR"].iterator || ".";
240 | var ctx = {};
241 | ctx[iterator] = _context;
242 | return ctx;
243 | }
244 | },
245 |
246 | is_object: function(a) {
247 | return a && typeof a == "object";
248 | },
249 |
250 | is_array: function(a) {
251 | return Object.prototype.toString.call(a) === '[object Array]';
252 | },
253 |
254 | /*
255 | Gets rid of leading and trailing whitespace
256 | */
257 | trim: function(s) {
258 | return s.replace(/^\s*|\s*$/g, "");
259 | },
260 |
261 | /*
262 | Why, why, why? Because IE. Cry, cry cry.
263 | */
264 | map: function(array, fn) {
265 | if (typeof array.map == "function") {
266 | return array.map(fn);
267 | } else {
268 | var r = [];
269 | var l = array.length;
270 | for(var i=0;i|\\{|%)?([^\/#]+?)\\1?" +
129 | that.ctag + "+", "g");
130 | };
131 |
132 | var regex = new_regex();
133 | var lines = template.split("\n");
134 | for (var i=0; i < lines.length; i++) {
135 | lines[i] = lines[i].replace(regex, function(match, operator, name) {
136 | switch(operator) {
137 | case "!": // ignore comments
138 | return match;
139 | case "=": // set new delimiters, rebuild the replace regexp
140 | that.set_delimiters(name);
141 | regex = new_regex();
142 | return "";
143 | case ">": // render partial
144 | return that.render_partial(name, context, partials);
145 | case "{": // the triple mustache is unescaped
146 | return that.find(name, context);
147 | default: // escape the value
148 | return that.escape(that.find(name, context));
149 | }
150 | }, this);
151 | if(!in_recursion) {
152 | this.send(lines[i]);
153 | }
154 | }
155 |
156 | if(in_recursion) {
157 | return lines.join("\n");
158 | }
159 | },
160 |
161 | set_delimiters: function(delimiters) {
162 | var dels = delimiters.split(" ");
163 | this.otag = this.escape_regex(dels[0]);
164 | this.ctag = this.escape_regex(dels[1]);
165 | },
166 |
167 | escape_regex: function(text) {
168 | // thank you Simon Willison
169 | if(!arguments.callee.sRE) {
170 | var specials = [
171 | '/', '.', '*', '+', '?', '|',
172 | '(', ')', '[', ']', '{', '}', '\\'
173 | ];
174 | arguments.callee.sRE = new RegExp(
175 | '(\\' + specials.join('|\\') + ')', 'g'
176 | );
177 | }
178 | return text.replace(arguments.callee.sRE, '\\$1');
179 | },
180 |
181 | /*
182 | find `name` in current `context`. That is find me a value
183 | from the view object
184 | */
185 | find: function(name, context) {
186 | name = this.trim(name);
187 | if(typeof context[name] === "function") {
188 | return context[name].apply(context);
189 | }
190 | if(context[name] !== undefined) {
191 | return context[name];
192 | }
193 | // silently ignore unkown variables
194 | return "";
195 | },
196 |
197 | // Utility methods
198 |
199 | /*
200 | Does away with nasty characters
201 | */
202 | escape: function(s) {
203 | return ((s == null) ? "" : s).toString().replace(/[&"<>\\]/g, function(s) {
204 | switch(s) {
205 | case "&": return "&";
206 | case "\\": return "\\\\";;
207 | case '"': return '\"';;
208 | case "<": return "<";
209 | case ">": return ">";
210 | default: return s;
211 | }
212 | });
213 | },
214 |
215 | /*
216 | Merges all properties of object `b` into object `a`.
217 | `b.property` overwrites a.property`
218 | */
219 | merge: function(a, b) {
220 | var _new = {};
221 | for(var name in a) {
222 | if(a.hasOwnProperty(name)) {
223 | _new[name] = a[name];
224 | }
225 | };
226 | for(var name in b) {
227 | if(b.hasOwnProperty(name)) {
228 | _new[name] = b[name];
229 | }
230 | };
231 | return _new;
232 | },
233 |
234 | // by @langalex, support for arrays of strings
235 | create_context: function(_context) {
236 | if(this.is_object(_context)) {
237 | return _context;
238 | } else if(this.pragmas["IMPLICIT-ITERATOR"]) {
239 | var iterator = this.pragmas["IMPLICIT-ITERATOR"].iterator || ".";
240 | var ctx = {};
241 | ctx[iterator] = _context;
242 | return ctx;
243 | }
244 | },
245 |
246 | is_object: function(a) {
247 | return a && typeof a == "object";
248 | },
249 |
250 | is_array: function(a) {
251 | return Object.prototype.toString.call(a) === '[object Array]';
252 | },
253 |
254 | /*
255 | Gets rid of leading and trailing whitespace
256 | */
257 | trim: function(s) {
258 | return s.replace(/^\s*|\s*$/g, "");
259 | },
260 |
261 | /*
262 | Why, why, why? Because IE. Cry, cry cry.
263 | */
264 | map: function(array, fn) {
265 | if (typeof array.map == "function") {
266 | return array.map(fn);
267 | } else {
268 | var r = [];
269 | var l = array.length;
270 | for(var i=0;i