Explorar el Código

Merge remote-tracking branch 'twitter/master' into merge-twitter

* twitter/master: (34 commits)
  Check for existence of engines before running tests, better logging
  version -twitter-b
  fix a bug in regex generators, make cache global
  cached regexes
  remove the backslash escaping
  updated changelog
  re-link jsc and add a bit more doc
  remove i18n stuff
  fix typo in readme
  nice clean up to render_section, as suggested by @esbie
  preserve translation-hint correctly
  made gettext return the right thing for translation-hint mode
  send translation mode in single parameter to _
  updated i18n test
  don't render during i18n step, just do replacements
  remove commented out lines from run_js
  clean up some logic
  added test descriptions and instructions to the readme
  more readme stuff
  moved new tests into new examples/* files
  ...
tags/0.4.0
Jan Lehnardt hace 14 años
padre
commit
8d260790eb
Se han modificado 15 ficheros con 361 adiciones y 145 borrados
  1. +9
    -0
      CHANGES.md
  2. +31
    -2
      README.md
  3. +2
    -0
      THANKS.md
  4. +1
    -0
      examples/double_render.html
  5. +5
    -0
      examples/double_render.js
  6. +1
    -0
      examples/double_render.txt
  7. +1
    -0
      examples/empty_sections.html
  8. +1
    -0
      examples/empty_sections.js
  9. +1
    -0
      examples/empty_sections.txt
  10. +4
    -0
      examples/two_sections.html
  11. +1
    -0
      examples/two_sections.js
  12. +1
    -0
      examples/two_sections.txt
  13. +1
    -1
      lib/package.json
  14. +105
    -33
      mustache.js
  15. +197
    -109
      test/mustache_spec.rb

+ 9
- 0
CHANGES.md Ver fichero

@@ -1,5 +1,14 @@
# mustache.js Changes

## 0.3.1-dev-twitter-b (8/23/2011)

* Cached regexes for improved performance

## 0.3.1-dev-twitter (12/3/2010)

* fixed double-rendering bug
* added Rhino test-runner alongside JavaScriptCore

## 0.3.1 (??-??-????)

## 0.3.0 (21-07-2010)


+ 31
- 2
README.md Ver fichero

@@ -199,11 +199,10 @@ will tell mustache.js to look for a object in the context's property
`winnings`. It will then use that object as the context for the template found
in `partials` for `winnings`.


## Escaping

mustache.js does escape all values when using the standard double mustache
syntax. Characters which will be escaped: `& \ " < >`. To disable escaping,
syntax. Characters which will be escaped: `& \ " ' < >`. To disable escaping,
simply use triple mustaches like `{{{unescaped_variable}}}`.

Example: Using `{{variable}}` inside a template for `5 > 2` will result in `5 &gt; 2`, where as the usage of `{{{variable}}}` will result in `5 > 2`.
@@ -304,8 +303,38 @@ directory.
Run `rake commonjs` to get a CommonJS compatible plugin file in the
`mustache-commonjs/` directory which you can also use with [Node.js][].

## Testing

To run the mustache.js test suite, run `rake spec`. All specs will be run first with JavaScriptCore (using `jsc`)
and again with Rhino, using `java org.mozilla.javascript.tools.shell.Main`.

JavaScriptCore is used from the OSX default location:

/System/Library/Frameworks/JavaScriptCore.framework/Versions/A/Resources/jsc

To install Rhino on OSX, follow [these instructions](Rhino Install).

### Adding Tests

Tests are located in the `examples/` directory. Adding a new test requires three files. Here's an example to add a test named "foo":

`examples/foo.html` (the template):

foo {{bar}}

`examples/foo.js` (the view context):

var foo = {
bar: "baz"
};

`examples/foo.txt` (the expected output):

foo baz

[jQuery]: http://jquery.com/
[Dojo]: http://www.dojotoolkit.org/
[Yui]: http://developer.yahoo.com/yui/
[CommonJS]: http://www.commonjs.org/
[Node.js]: http://nodejs.org/
[Rhino Install]: http://michaux.ca/articles/installing-rhino-on-os-x

+ 2
- 0
THANKS.md Ver fichero

@@ -18,3 +18,5 @@ Mustache.js wouldn't kick ass if it weren't for these fine souls:
* Jason Smith / jhs
* Aaron Gibralter / agibralter
* Ross Boucher / boucher
* Matt Sanford / mzsanford
* Ben Cherry / bcherry

+ 1
- 0
examples/double_render.html Ver fichero

@@ -0,0 +1 @@
{{#foo}}{{bar}}{{/foo}}

+ 5
- 0
examples/double_render.js Ver fichero

@@ -0,0 +1,5 @@
var double_render = {
foo: true,
bar: "{{win}}",
win: "FAIL"
};

+ 1
- 0
examples/double_render.txt Ver fichero

@@ -0,0 +1 @@
{{win}}

+ 1
- 0
examples/empty_sections.html Ver fichero

@@ -0,0 +1 @@
{{#foo}}{{/foo}}foo{{#bar}}{{/bar}}

+ 1
- 0
examples/empty_sections.js Ver fichero

@@ -0,0 +1 @@
var empty_sections = {};

+ 1
- 0
examples/empty_sections.txt Ver fichero

@@ -0,0 +1 @@
foo

+ 4
- 0
examples/two_sections.html Ver fichero

@@ -0,0 +1,4 @@
{{#foo}}
{{/foo}}
{{#bar}}
{{/bar}}

+ 1
- 0
examples/two_sections.js Ver fichero

@@ -0,0 +1 @@
var two_sections = {};

+ 1
- 0
examples/two_sections.txt Ver fichero

@@ -0,0 +1 @@


+ 1
- 1
lib/package.json Ver fichero

@@ -3,6 +3,6 @@
"author": "http://mustache.github.com/",
"description": "{{ mustache }} in JavaScript — Logic-less templates.",
"keywords": ["template"],
"version": "0.3.1-dev",
"version": "0.3.1-dev-twitter",
"main": "./mustache"
}

+ 105
- 33
mustache.js Ver fichero

@@ -5,6 +5,7 @@
*/

var Mustache = function() {
var regexCache = {};
var Renderer = function() {};

Renderer.prototype = {
@@ -34,13 +35,22 @@ var Mustache = function() {
}
}

// get the pragmas together
template = this.render_pragmas(template);

// render the template
var html = this.render_section(template, context, partials);
if(in_recursion) {
return this.render_tags(html, context, partials, in_recursion);

// render_section did not find any sections, we still need to render the tags
if (html === false) {
html = this.render_tags(template, context, partials, in_recursion);
}

this.render_tags(html, context, partials, in_recursion);
if (in_recursion) {
return html;
} else {
this.sendLines(html);
}
},

/*
@@ -52,6 +62,15 @@ var Mustache = function() {
}
},

sendLines: function(text) {
if (text) {
var lines = text.split("\n");
for (var i = 0; i < lines.length; i++) {
this.send(lines[i]);
}
}
},

/*
Looks for %PRAGMAS
*/
@@ -62,11 +81,13 @@ var Mustache = function() {
}

var that = this;
var regex = new RegExp(this.otag + "%([\\w-]+) ?([\\w]+=[\\w]+)?" +
this.ctag, "g");
var regex = this.getCachedRegex("render_pragmas", function(otag, ctag) {
return new RegExp(otag + "%([\\w-]+) ?([\\w]+=[\\w]+)?" + ctag, "g");
});

return template.replace(regex, function(match, pragma, options) {
if(!that.pragmas_implemented[pragma]) {
throw({message:
throw({message:
"This implementation of mustache doesn't understand the '" +
pragma + "' pragma"});
}
@@ -99,45 +120,74 @@ var Mustache = function() {
*/
render_section: function(template, context, partials) {
if(!this.includes("#", template) && !this.includes("^", template)) {
return template;
// did not render anything, there were no sections
return false;
}

var that = this;
// CSW - Added "+?" so it finds the tighest bound, not the widest
var regex = new RegExp(this.otag + "(\\^|\\#)\\s*(.+)\\s*" + this.ctag +
"\n*([\\s\\S]+?)" + this.otag + "\\/\\s*\\2\\s*" + this.ctag +
"\\s*", "mg");

var regex = this.getCachedRegex("render_section", function(otag, ctag) {
// This regex matches _the first_ section ({{#foo}}{{/foo}}), and captures the remainder
return new RegExp(
"^([\\s\\S]*?)" + // all the crap at the beginning that is not {{*}} ($1)

otag + // {{
"(\\^|\\#)\\s*(.+)\\s*" + // #foo (# == $2, foo == $3)
ctag + // }}

"\n*([\\s\\S]*?)" + // between the tag ($2). leading newlines are dropped

otag + // {{
"\\/\\s*\\3\\s*" + // /foo (backreference to the opening tag).
ctag + // }}

"\\s*([\\s\\S]*)$", // everything else in the string ($4). leading whitespace is dropped.

"g");
});


// for each {{#foo}}{{/foo}} section do...
return template.replace(regex, function(match, type, name, content) {
var value = that.find(name, context);
if(type == "^") { // inverted section
if(!value || that.is_array(value) && value.length === 0) {
return template.replace(regex, function(match, before, type, name, content, after) {
// before contains only tags, no sections
var renderedBefore = before ? that.render_tags(before, context, partials, true) : "",

// after may contain both sections and tags, so use full rendering function
renderedAfter = after ? that.render(after, context, partials, true) : "",

// will be computed below
renderedContent,

value = that.find(name, context);

if (type === "^") { // inverted section
if (!value || that.is_array(value) && value.length === 0) {
// false or empty list, render it
return that.render(content, context, partials, true);
renderedContent = that.render(content, context, partials, true);
} else {
return "";
renderedContent = "";
}
} else if(type == "#") { // normal section
if(that.is_array(value)) { // Enumerable, Let's loop!
return that.map(value, function(row) {
return that.render(content, that.create_context(row),
partials, true);
} else if (type === "#") { // normal section
if (that.is_array(value)) { // Enumerable, Let's loop!
renderedContent = that.map(value, function(row) {
return that.render(content, that.create_context(row), partials, true);
}).join("");
} else if(that.is_object(value)) { // Object, Use it as subcontext!
return that.render(content, that.create_context(value),
} else if (that.is_object(value)) { // Object, Use it as subcontext!
renderedContent = that.render(content, that.create_context(value),
partials, true);
} else if(typeof value === "function") {
} else if (typeof value === "function") {
// higher order section
return value.call(context, content, function(text) {
renderedContent = value.call(context, content, function(text) {
return that.render(text, context, partials, true);
});
} else if(value) { // boolean section
return that.render(content, context, partials, true);
} else if (value) { // boolean section
renderedContent = that.render(content, context, partials, true);
} else {
return "";
renderedContent = "";
}
}

return renderedBefore + renderedContent + renderedAfter;
});
},

@@ -148,9 +198,12 @@ var Mustache = function() {
// tit for tat
var that = this;



var new_regex = function() {
return new RegExp(that.otag + "(=|!|>|\\{|%)?([^\\/#\\^]+?)\\1?" +
that.ctag + "+", "g");
return that.getCachedRegex("render_tags", function(otag, ctag) {
return new RegExp(otag + "(=|!|>|\\{|%)?([^\\/#\\^]+?)\\1?" + ctag + "+", "g");
});
};

var regex = new_regex();
@@ -300,12 +353,31 @@ var Mustache = function() {
}
return r;
}
},

getCachedRegex: function(name, generator) {
var byOtag = regexCache[this.otag];
if (!byOtag) {
byOtag = regexCache[this.otag] = {};
}

var byCtag = byOtag[this.ctag];
if (!byCtag) {
byCtag = byOtag[this.ctag] = {};
}

var regex = byCtag[name];
if (!regex) {
regex = byCtag[name] = generator(this.otag, this.ctag);
}

return regex;
}
};

return({
name: "mustache.js",
version: "0.3.1-dev",
version: "0.3.1-dev-twitter-b",

/*
Turns a template and view into HTML
@@ -315,7 +387,7 @@ var Mustache = function() {
if(send_fun) {
renderer.send = send_fun;
}
renderer.render(template, view, partials);
renderer.render(template, view || {}, partials);
if(!send_fun) {
return renderer.buffer.join("\n");
}


+ 197
- 109
test/mustache_spec.rb Ver fichero

@@ -22,135 +22,223 @@ def load_test(dir, name, partial=false)
end
end

describe "mustache" do
before(:all) do
@mustache = File.read(__DIR__ + "/../mustache.js")
end
JSC_PATH = "/System/Library/Frameworks/JavaScriptCore.framework/Versions/A/Resources/jsc"
RHINO_JAR = "org.mozilla.javascript.tools.shell.Main"

it "should return the same when invoked multiple times" do
js = <<-JS
#{@mustache}
Mustache.to_html("x")
print(Mustache.to_html("x"));
JS
run_js(js).should == "x\n"
engines_run = 0

end
describe "mustache" do

it "should clear the context after each run" do
js = <<-JS
#{@mustache}
Mustache.to_html("{{#list}}{{x}}{{/list}}", {list: [{x: 1}]})
try {
print(Mustache.to_html("{{#list}}{{x}}{{/list}}", {list: [{}]}));
} catch(e) {
print('ERROR: ' + e.message);
}
JS
run_js(js).should == "\n"
end
non_partials.each do |testname|
describe testname do
it "should generate the correct html" do

view, template, expect = load_test(__DIR__, testname)
runner = <<-JS
try {
#{@mustache}
#{view}
var template = #{template};
var result = Mustache.to_html(template, #{testname});
print(result);
} catch(e) {
print('ERROR: ' + e.message);
shared_examples_for "Mustache rendering" do

before(:all) do
engines_run += 1
mustache = File.read(__DIR__ + "/../mustache.js")
stubbed_gettext = <<-JS
// Stubbed gettext translation method for {{_i}}{{/i}} tags in Mustache.
function _(params) {
if (typeof params === "string") {
return params
}
JS

run_js(runner).should == expect
return params.text;
}
JS

@boilerplate = <<-JS
#{mustache}
#{stubbed_gettext}
JS
end

it "should return the same when invoked multiple times" do
js = <<-JS
#{@boilerplate}
Mustache.to_html("x")
print(Mustache.to_html("x"));
JS
run_js(@run_js, js).should == "x\n"

end

it "should clear the context after each run" do
js = <<-JS
#{@boilerplate}
Mustache.to_html("{{#list}}{{x}}{{/list}}", {list: [{x: 1}]})
try {
print(Mustache.to_html("{{#list}}{{x}}{{/list}}", {list: [{}]}));
} catch(e) {
print('ERROR: ' + e.message);
}
JS
run_js(@run_js, js).should == "\n"
end

non_partials.each do |testname|
describe testname do
it "should generate the correct html" do

view, template, expect = load_test(__DIR__, testname)

runner = <<-JS
try {
#{@boilerplate}
#{view}
var template = #{template};
var result = Mustache.to_html(template, #{testname});
print(result);
} catch(e) {
print('ERROR: ' + e.message);
}
JS

run_js(@run_js, runner).should == expect
end
it "should sendFun the correct html" do

view, template, expect = load_test(__DIR__, testname)

runner = <<-JS
try {
#{@boilerplate}
#{view}
var chunks = [];
var sendFun = function(chunk) {
if (chunk != "") {
chunks.push(chunk);
}
}
var template = #{template};
Mustache.to_html(template, #{testname}, null, sendFun);
print(chunks.join("\\n"));
} catch(e) {
print('ERROR: ' + e.message);
}
JS

run_js(@run_js, runner).strip.should == expect.strip
end
end
it "should sendFun the correct html" do

view, template, expect = load_test(__DIR__, testname)

runner = <<-JS
try {
#{@mustache}
#{view}
var chunks = [];
var sendFun = function(chunk) {
if (chunk != "") {
chunks.push(chunk);
end

partials.each do |testname|
describe testname do
it "should generate the correct html" do

view, template, partial, expect =
load_test(__DIR__, testname, true)

runner = <<-JS
try {
#{@boilerplate}
#{view}
var template = #{template};
var partials = {"partial": #{partial}};
var result = Mustache.to_html(template, partial_context, partials);
print(result);
} catch(e) {
print('ERROR: ' + e.message);
}
JS

run_js(@run_js, runner).should == expect
end
it "should sendFun the correct html" do

view, template, partial, expect =
load_test(__DIR__, testname, true)

runner = <<-JS
try {
#{@boilerplate}
#{view};
var template = #{template};
var partials = {"partial": #{partial}};
var chunks = [];
var sendFun = function(chunk) {
if (chunk != "") {
chunks.push(chunk);
}
}
Mustache.to_html(template, partial_context, partials, sendFun);
print(chunks.join("\\n"));
} catch(e) {
print('ERROR: ' + e.message);
}
var template = #{template};
Mustache.to_html(template, #{testname}, null, sendFun);
print(chunks.join("\\n"));
} catch(e) {
print('ERROR: ' + e.message);
}
JS
JS

run_js(runner).strip.should == expect.strip
run_js(@run_js, runner).strip.should == expect.strip
end
end
end
end

partials.each do |testname|
describe testname do
it "should generate the correct html" do

view, template, partial, expect =
load_test(__DIR__, testname, true)

runner = <<-JS
try {
#{@mustache}
#{view}
var template = #{template};
var partials = {"partial": #{partial}};
var result = Mustache.to_html(template, partial_context, partials);
print(result);
} catch(e) {
print('ERROR: ' + e.message);
}
JS
run_js(runner).should == expect
context "running in JavaScriptCore (WebKit, Safari)" do
if File.exists?(JSC_PATH)
before(:each) do
@run_js = :run_js_jsc
end

before(:all) do
puts "\nTesting mustache.js in JavaScriptCore:\n"
end
it "should sendFun the correct html" do

view, template, partial, expect =
load_test(__DIR__, testname, true)

runner = <<-JS
try {
#{@mustache}
#{view};
var template = #{template};
var partials = {"partial": #{partial}};
var chunks = [];
var sendFun = function(chunk) {
if (chunk != "") {
chunks.push(chunk);
}
}
Mustache.to_html(template, partial_context, partials, sendFun);
print(chunks.join("\\n"));
} catch(e) {
print('ERROR: ' + e.message);
}
JS

run_js(runner).strip.should == expect.strip
after(:all) do
puts "\nDone\n"
end

it_should_behave_like "Mustache rendering"
else
puts "\nSkipping tests in JavaScriptCore (jsc not found)\n"
end
end

context "running in Rhino (Mozilla)" do
if !`java #{RHINO_JAR} 'foo' 2>&1`.match(/ClassNotFoundException/)
before(:each) do
@run_js = :run_js_rhino
end

before(:all) do
puts "\nTesting mustache.js in Rhino:\n"
end

after(:all) do
puts "\nDone\n"
end

it_should_behave_like "Mustache rendering"
else
puts "\nSkipping tests in Rhino (JAR #{RHINO_JAR} was not found)\n"
end
end

def run_js(js)
context "suite" do
before(:all) do
puts "\nVerifying that we ran at the tests in at least one engine\n"
end

after(:all) do
puts "\nDone\n"
end

it "should have run at least one time" do
engines_run.should > 0
end
end

def run_js(runner, js)
send(runner, js)
end

def run_js_jsc(js)
File.open("runner.js", 'w') {|f| f << js}
`#{JSC_PATH} runner.js`
end

def run_js_rhino(js)
File.open("runner.js", 'w') {|f| f << js}
`js runner.js`
`java #{RHINO_JAR} runner.js`
end
end


Cargando…
Cancelar
Guardar