├── 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 | 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:
  • bananas
  • apples
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