From 0e11ce9ee50ba91ba85a6aef849fc12273c201b1 Mon Sep 17 00:00:00 2001 From: Jan Lehnardt Date: Mon, 5 Oct 2009 01:41:35 +0200 Subject: [PATCH] initial drop --- Rakefile | 10 +++ examples/comments.html | 1 + examples/comments.js | 5 ++ examples/comments.txt | 1 + examples/complex.html | 16 ++++ examples/complex.js | 25 ++++++ examples/complex.txt | 1 + examples/escaped.html | 1 + examples/escaped.rb | 14 ++++ examples/inner_partial.html | 1 + examples/simple.html | 5 ++ examples/simple.js | 8 ++ examples/simple.rb | 26 ++++++ examples/simple.txt | 3 + examples/template_partial.html | 2 + examples/template_partial.rb | 14 ++++ examples/unescaped.html | 1 + examples/unescaped.rb | 14 ++++ examples/view_partial.html | 3 + examples/view_partial.rb | 18 +++++ mustache.js | 110 ++++++++++++++++++++++++++ runner.js | 140 +++++++++++++++++++++++++++++++++ test/mustache_test.js | 0 test/mustache_test.rb | 34 ++++++++ 24 files changed, 453 insertions(+) create mode 100644 Rakefile create mode 100644 examples/comments.html create mode 100644 examples/comments.js create mode 100644 examples/comments.txt create mode 100644 examples/complex.html create mode 100644 examples/complex.js create mode 100644 examples/complex.txt create mode 100644 examples/escaped.html create mode 100644 examples/escaped.rb create mode 100644 examples/inner_partial.html create mode 100644 examples/simple.html create mode 100644 examples/simple.js create mode 100644 examples/simple.rb create mode 100644 examples/simple.txt create mode 100644 examples/template_partial.html create mode 100644 examples/template_partial.rb create mode 100644 examples/unescaped.html create mode 100644 examples/unescaped.rb create mode 100644 examples/view_partial.html create mode 100644 examples/view_partial.rb create mode 100644 mustache.js create mode 100644 runner.js create mode 100644 test/mustache_test.js create mode 100644 test/mustache_test.rb diff --git a/Rakefile b/Rakefile new file mode 100644 index 0000000..39fea06 --- /dev/null +++ b/Rakefile @@ -0,0 +1,10 @@ +require 'rake/testtask' + +task :default => :test + +Rake::TestTask.new do |t| + t.libs << 'lib' + t.pattern = 'test/**/*_test.rb' + t.verbose = false +end + diff --git a/examples/comments.html b/examples/comments.html new file mode 100644 index 0000000..5036801 --- /dev/null +++ b/examples/comments.html @@ -0,0 +1 @@ +

{{title}}{{! just something interesting... or not... }}

diff --git a/examples/comments.js b/examples/comments.js new file mode 100644 index 0000000..bcf196d --- /dev/null +++ b/examples/comments.js @@ -0,0 +1,5 @@ +var comments = { + title: function() { + return "A Comedy of Errors"; + } +}; \ No newline at end of file diff --git a/examples/comments.txt b/examples/comments.txt new file mode 100644 index 0000000..071e026 --- /dev/null +++ b/examples/comments.txt @@ -0,0 +1 @@ +

A Comedy of Errors{{! just something interesting... or not... }}

diff --git a/examples/complex.html b/examples/complex.html new file mode 100644 index 0000000..e7ca1b5 --- /dev/null +++ b/examples/complex.html @@ -0,0 +1,16 @@ +

{{header}}

+{{#list}} + +{{/list}} +{{#empty}} +

The list is empty.

+{{/empty}} \ No newline at end of file diff --git a/examples/complex.js b/examples/complex.js new file mode 100644 index 0000000..1b4bf9e --- /dev/null +++ b/examples/complex.js @@ -0,0 +1,25 @@ +var complex = { + header: function() { + return "Colors"; + }, + item: [ + {name: "red", current: true, url: "#Red"}, + {name: "green", current: false, url: "#Green"}, + {name: "blue", current: false, url: "#Blue"} + ], + link: function() { + var v = this["current"] === true; + // print("link() returns " + v); + return v; + }, + list: function() { + var v = this.item.length !== 0; + // print("list() returns " + v); + return v; + }, + empty: function() { + var v = this.item.length === 0; + // print("empty() returns " + v); + return v; + } +}; diff --git a/examples/complex.txt b/examples/complex.txt new file mode 100644 index 0000000..05bc23d --- /dev/null +++ b/examples/complex.txt @@ -0,0 +1 @@ +

Colors

\ No newline at end of file diff --git a/examples/escaped.html b/examples/escaped.html new file mode 100644 index 0000000..8be4ccb --- /dev/null +++ b/examples/escaped.html @@ -0,0 +1 @@ +

{{title}}

\ No newline at end of file diff --git a/examples/escaped.rb b/examples/escaped.rb new file mode 100644 index 0000000..d3b3010 --- /dev/null +++ b/examples/escaped.rb @@ -0,0 +1,14 @@ +$LOAD_PATH.unshift File.dirname(__FILE__) + '/../lib' +require 'mustache' + +class Escaped < Mustache + self.path = File.dirname(__FILE__) + + def title + "Bear > Shark" + end +end + +if $0 == __FILE__ + puts Escaped.to_html +end diff --git a/examples/inner_partial.html b/examples/inner_partial.html new file mode 100644 index 0000000..2863764 --- /dev/null +++ b/examples/inner_partial.html @@ -0,0 +1 @@ +Again, {{title}}! \ No newline at end of file diff --git a/examples/simple.html b/examples/simple.html new file mode 100644 index 0000000..03df206 --- /dev/null +++ b/examples/simple.html @@ -0,0 +1,5 @@ +Hello {{name}} +You have just won ${{value}}! +{{#in_ca}} +Well, ${{ taxed_value }}, after taxes. +{{/in_ca}} \ No newline at end of file diff --git a/examples/simple.js b/examples/simple.js new file mode 100644 index 0000000..30f9834 --- /dev/null +++ b/examples/simple.js @@ -0,0 +1,8 @@ +var simple = { + name: "Chris", + value: 10000, + taxed_value: function() { + return this.value - (this.value * 0.4); + }, + in_ca: true +}; diff --git a/examples/simple.rb b/examples/simple.rb new file mode 100644 index 0000000..8072466 --- /dev/null +++ b/examples/simple.rb @@ -0,0 +1,26 @@ +$LOAD_PATH.unshift File.dirname(__FILE__) + '/../lib' +require 'mustache' + +class Simple < Mustache + self.path = File.dirname(__FILE__) + + def name + "Chris" + end + + def value + 10_000 + end + + def taxed_value + value - (value * 0.4) + end + + def in_ca + true + end +end + +if $0 == __FILE__ + puts Simple.to_html +end diff --git a/examples/simple.txt b/examples/simple.txt new file mode 100644 index 0000000..5d75d65 --- /dev/null +++ b/examples/simple.txt @@ -0,0 +1,3 @@ +Hello Chris +You have just won $10000! +Well, $6000, after taxes. diff --git a/examples/template_partial.html b/examples/template_partial.html new file mode 100644 index 0000000..ed5bd85 --- /dev/null +++ b/examples/template_partial.html @@ -0,0 +1,2 @@ +

{{title}}

+{{{{{title}}} \ No newline at end of file diff --git a/examples/unescaped.rb b/examples/unescaped.rb new file mode 100644 index 0000000..538ada8 --- /dev/null +++ b/examples/unescaped.rb @@ -0,0 +1,14 @@ +$LOAD_PATH.unshift File.dirname(__FILE__) + '/../lib' +require 'mustache' + +class Unescaped < Mustache + self.path = File.dirname(__FILE__) + + def title + "Bear > Shark" + end +end + +if $0 == __FILE__ + puts Unescaped.to_html +end diff --git a/examples/view_partial.html b/examples/view_partial.html new file mode 100644 index 0000000..59aadfd --- /dev/null +++ b/examples/view_partial.html @@ -0,0 +1,3 @@ +

{{greeting}}

+{{{{farewell}} \ No newline at end of file diff --git a/examples/view_partial.rb b/examples/view_partial.rb new file mode 100644 index 0000000..4decbaa --- /dev/null +++ b/examples/view_partial.rb @@ -0,0 +1,18 @@ +$LOAD_PATH.unshift File.dirname(__FILE__) + '/../lib' +require 'mustache' + +class ViewPartial < Mustache + self.path = File.dirname(__FILE__) + + def greeting + "Welcome" + end + + def farewell + "Fair enough, right?" + end +end + +if $0 == __FILE__ + puts ViewPartial.to_html +end diff --git a/mustache.js b/mustache.js new file mode 100644 index 0000000..f310d89 --- /dev/null +++ b/mustache.js @@ -0,0 +1,110 @@ +/* + Shamless port of http://github.com/defunkt/mustache + by Jan Lehnardt + + Thanks @defunkt for the awesome code + TBD: License + + ChangeLog: + - 04.10.2009: Ininitial port at http://devhouseberlin.de/ + +*/ + +var Mustache = { + name: "mustache.js", + version: "0.1", + debug: true, + stack: " ", + context: {}, + to_html: function(view, template) { + return this.render(template, view); + }, + + render: function(template, view) { + this.stack = this.stack + " "; + // fail fast + if(template.indexOf("{{") == -1) { + return template; + } + this.context = context = this.merge((this.context || {}), view); + var html = this.render_section(template); + // restore context, recursion might have messed it up + // this.context = context; + return this.render_tags(html); + }, + + merge: function(a, b) { + for(var name in b) { + if(b.hasOwnProperty(name)) { + a[name] = b[name]; + } + } + return a; + }, + + render_section: function(template) { + if(template.indexOf("{{#") == -1) { + return template; + } + var that = this; + return template.replace(/\{\{\#(.+)\}\}\s*(.+)\s*\{\{\/\1\}\}\s*/mg, + function(match, name, content) { + print(match); + print(name); + print(content); + var value = that.find(name); + if(that.is_array(value)) { + return value.map(function(row) { + return that.render(content, row); + }).join(''); + } else if(value) { + return that.render(content); + } else { + return ""; + } + } + ); + }, + + is_array: function(a) { + return (a && + typeof a === 'object' && + a.constructor === Array); + }, + + render_tags: function(template) { + // values + var that = this; + return template.replace(/\{\{(!|<|\{)?([^\/#]+?)\1?\}\}+/mg, + function (match, operator, name) { + switch(operator) { + case "!": // ignore comments + return match; + // TODO: partials + // case "<": // render partial + // return this.render_partial() + case '{': // the triple mustache is unescaped + return that.find(name); + default: // escape the value + return that.find(name); + } + }, this); + }, + + find: function(name) { + name = this.trim(name); + // print(this.stack + "find(" + name + ")"); + var context = this.context; + if(typeof context[name] === "function") { + return context[name].apply(context); + } + if(context[name] !== undefined) { + return context[name]; + } + throw("Can't find " + name + " in " + context); + }, + + trim: function(s) { + return s.replace(/^\s*|\s*$/g, ''); + }, +}; diff --git a/runner.js b/runner.js new file mode 100644 index 0000000..81c628d --- /dev/null +++ b/runner.js @@ -0,0 +1,140 @@ +/* + Shamless port of http://github.com/defunkt/mustache + by Jan Lehnardt + + Thanks @defunkt for the awesome code + TBD: License + + ChangeLog: + - 04.10.2009: Ininitial port at http://devhouseberlin.de/ + +*/ + +var Mustache = { + name: "mustache.js", + version: "0.1", + debug: true, + stack: " ", + context: {}, + to_html: function(view, template) { + return this.render(template, view); + }, + + render: function(template, view) { + this.stack = this.stack + " "; + // fail fast + if(template.indexOf("{{") == -1) { + return template; + } + this.context = context = this.merge((this.context || {}), view); + var html = this.render_section(template); + // restore context, recursion might have messed it up + // this.context = context; + return this.render_tags(html); + }, + + merge: function(a, b) { + for(var name in b) { + if(b.hasOwnProperty(name)) { + a[name] = b[name]; + } + } + return a; + }, + + render_section: function(template) { + if(template.indexOf("{{#") == -1) { + return template; + } + var that = this; + return template.replace(/\{\{\#(.+)\}\}\s*(.+)\s*\{\{\/\1\}\}\s*/mg, + function(match, name, content) { + print(match); + print(name); + print(content); + var value = that.find(name); + if(that.is_array(value)) { + return value.map(function(row) { + return that.render(content, row); + }).join(''); + } else if(value) { + return that.render(content); + } else { + return ""; + } + } + ); + }, + + is_array: function(a) { + return (a && + typeof a === 'object' && + a.constructor === Array); + }, + + render_tags: function(template) { + // values + var that = this; + return template.replace(/\{\{(!|<|\{)?([^\/#]+?)\1?\}\}+/mg, + function (match, operator, name) { + switch(operator) { + case "!": // ignore comments + return match; + // TODO: partials + // case "<": // render partial + // return this.render_partial() + case '{': // the triple mustache is unescaped + return that.find(name); + default: // escape the value + return that.find(name); + } + }, this); + }, + + find: function(name) { + name = this.trim(name); + // print(this.stack + "find(" + name + ")"); + var context = this.context; + if(typeof context[name] === "function") { + return context[name].apply(context); + } + if(context[name] !== undefined) { + return context[name]; + } + throw("Can't find " + name + " in " + context); + }, + + trim: function(s) { + return s.replace(/^\s*|\s*$/g, ''); + }, +}; + +var complex = { + header: function() { + return "Colors"; + }, + item: [ + {name: "red", current: true, url: "#Red"}, + {name: "green", current: false, url: "#Green"}, + {name: "blue", current: false, url: "#Blue"} + ], + link: function() { + var v = this["current"] === true; + // print("link() returns " + v); + return v; + }, + list: function() { + var v = this.item.length !== 0; + // print("list() returns " + v); + return v; + }, + empty: function() { + var v = this.item.length === 0; + // print("empty() returns " + v); + return v; + } +}; + +var template = "

{{header}}<\/h1>\n{{#list}}\n