Compare commits
231 Commits
1.0.0.beta.1
...
1.0.rc.1
| Author | SHA1 | Date | |
|---|---|---|---|
| ce74c36118 | |||
| 26b1438f20 | |||
| b83e5e0ea6 | |||
| f6aea819f3 | |||
| 647ee28ca1 | |||
| d117b50fd3 | |||
| ed8c2b95b8 | |||
| aba2269ddb | |||
| 967c69b2da | |||
| 5a6e4f1ddd | |||
| f55ca6c489 | |||
| 4b73b51e12 | |||
| acc04c2826 | |||
| 89f5ab8aaf | |||
| dc0426d836 | |||
| 6761d4c6d1 | |||
| 3426969221 | |||
| 9ce3032678 | |||
| 2b3e777340 | |||
| 4ffe1b8e5a | |||
| d8825b1961 | |||
| fc99d90337 | |||
| eb509a4b8b | |||
| 09ac8ac24b | |||
| adb8486e5f | |||
| 1e151e2030 | |||
| fe7d16de91 | |||
| 075076975d | |||
| 8be16c64f9 | |||
| f79af6bfa3 | |||
| efb1e25690 | |||
| 36b242a710 | |||
| 659be5a690 | |||
| 46c04fa71e | |||
| 5e8be14d78 | |||
| 72e05d623c | |||
| ff1547ea04 | |||
| 7a393a972b | |||
| 92b6c1401a | |||
| 0afc8b58d2 | |||
| 1082ec2414 | |||
| 727eb26cb6 | |||
| 175c6fae0f | |||
| 3486b530be | |||
| bc5efc1767 | |||
| 90adaa3cc8 | |||
| 8786a6c6e2 | |||
| 246325085f | |||
| 68c76c9b2e | |||
| facefe8fed | |||
| 8e70e3b58b | |||
| 730c412925 | |||
| 2418feaf19 | |||
| c2591f428b | |||
| 98b3327c75 | |||
| 992fdf2071 | |||
| bd9a84a0b7 | |||
| 24e04bad94 | |||
| e6079ca5e2 | |||
| bef909f5cb | |||
| 0b97fea56f | |||
| 2ea95ca08d | |||
| cb9db98642 | |||
| 58e2c2410f | |||
| 6040149cf8 | |||
| 17cdb0091b | |||
| 730c2617af | |||
| 757d5250dc | |||
| 163a92ebe0 | |||
| c79c761460 | |||
| e474e56b80 | |||
| 69307d0e2b | |||
| 9852502636 | |||
| 7a0bf9d332 | |||
| 2e7ea96818 | |||
| 932e2970ad | |||
| 373d10edb1 | |||
| bd0f74fbd9 | |||
| e57e626480 | |||
| cb04822812 | |||
| 647d5adf85 | |||
| 6df51733d3 | |||
| 9c9d92413e | |||
| c6eec286a8 | |||
| f6070f0084 | |||
| 87e6f21765 | |||
| 3afc572f6b | |||
| fea981a3f6 | |||
| 799c7ed3ce | |||
| b2a27347e8 | |||
| 215472dda2 | |||
| 8338bc888a | |||
| 5d15ef9625 | |||
| 42e31ef6db | |||
| deac4f5141 | |||
| 79b730ccf3 | |||
| ec3e5e3914 | |||
| 5f9919dcf7 | |||
| 608aa94fc3 | |||
| 9998d9d9bc | |||
| 924cc3e86c | |||
| 25b8e314af | |||
| 43431d2a78 | |||
| b36776f4c6 | |||
| f23fbb84e2 | |||
| 87599daef1 | |||
| 374a38ed36 | |||
| b501c380af | |||
| 7dd11fa3f2 | |||
| defc2f3403 | |||
| 9a27aa98b7 | |||
| 4458b5fa13 | |||
| 7cd34edcc2 | |||
| 7b18e51bf8 | |||
| cd927e9cda | |||
| b832c85923 | |||
| 33bb89beda | |||
| 33bde69750 | |||
| 2422a6518f | |||
| 0768f07b17 | |||
| 2f3d5977d1 | |||
| 8d38ebd251 | |||
| d630894d9b | |||
| 8f617ca624 | |||
| 57e3bd72a8 | |||
| 6b18873d23 | |||
| c1c455acc8 | |||
| f2f1f5f9bd | |||
| 7212e2b7d7 | |||
| fc84308cc9 | |||
| 91bbc4fd2c | |||
| 696dc2dc36 | |||
| 96571b93c1 | |||
| 3db5fb742e | |||
| c561fdf8b1 | |||
| f7480d5b42 | |||
| 93ebee8faf | |||
| 20350a3c07 | |||
| 8cd4d939f3 | |||
| 98e66f898e | |||
| 85a32a18e2 | |||
| 90648c0e2d | |||
| 6ef3d1316f | |||
| f3a2374dda | |||
| cbadb33d51 | |||
| 17e1b1fab7 | |||
| 6eea3e4d44 | |||
| 48726c7fed | |||
| 47c5cd71ea | |||
| a9bcd369f3 | |||
| b3e70414d8 | |||
| c9d851e9a4 | |||
| 86d71e6fc8 | |||
| 542f98190b | |||
| 16c0907607 | |||
| 292e32292b | |||
| 3c5cac3f3f | |||
| 2f7b724d5a | |||
| 318c08a97e | |||
| d6b97cf34d | |||
| 82e3344aed | |||
| 6349c1a0c7 | |||
| f53737ef23 | |||
| 1a88356dc3 | |||
| 5d4b549dc3 | |||
| d044ada7aa | |||
| 77a20a3a9f | |||
| 9821da8df7 | |||
| 2d538baf72 | |||
| c7e8ddd6b5 | |||
| 6fcebec713 | |||
| 993c793565 | |||
| f8edc59025 | |||
| 3af9de7407 | |||
| 72ea816cf7 | |||
| d641dae5f1 | |||
| ccf32821df | |||
| 8e23ce87d3 | |||
| 5a3522cf2e | |||
| aaec001931 | |||
| 3d9e5070f5 | |||
| 82a1b0e85c | |||
| 600a669d12 | |||
| 5aa12b5e09 | |||
| 6a6edf5ae6 | |||
| aeda7e389f | |||
| 09e79f0587 | |||
| 4a9270aa38 | |||
| 59f5331db4 | |||
| 74bd8cac60 | |||
| 542d64f0be | |||
| 5e611205ac | |||
| 2e77f1c777 | |||
| 8c49721c70 | |||
| 2fc01ff73a | |||
| bba08ad95f | |||
| 034dafddac | |||
| 6aa9a30b02 | |||
| f2dccb753f | |||
| 471f3b9748 | |||
| a927125909 | |||
| 9062cac3f0 | |||
| 059a80661d | |||
| 37e36bf137 | |||
| 3c2086eb2a | |||
| 1ff0b657be | |||
| 17dbfa7004 | |||
| 52900c89f7 | |||
| be8f4f6fb9 | |||
| 1482f1ae72 | |||
| 0f78345d0c | |||
| 4eeda34ad2 | |||
| ed1be562e2 | |||
| 04b359d15b | |||
| f07f70ca42 | |||
| 28d35180bd | |||
| 0e22301e8f | |||
| 4e5cf17311 | |||
| a4ca50533d | |||
| 1aa512d227 | |||
| 2f4644529c | |||
| d109e31f62 | |||
| 5168037b5a | |||
| 92535a9158 | |||
| 4f309bb3aa | |||
| b4c949cc07 | |||
| 90bb568faf | |||
| 60040e60fb | |||
| a00054e26b | |||
| b291a1ad8c | |||
| dec196b4d9 |
+5
-1
@@ -1,5 +1,9 @@
|
||||
dist
|
||||
lib/handlebars/parser.js
|
||||
vendor
|
||||
.rvmrc
|
||||
.DS_Store
|
||||
lib/handlebars/compiler/parser.js
|
||||
node_modules
|
||||
*.sublime-project
|
||||
*.sublime-workspace
|
||||
npm-debug.log
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
{
|
||||
"predef": [
|
||||
"console",
|
||||
"Ember",
|
||||
"DS",
|
||||
"Handlebars",
|
||||
"Metamorph",
|
||||
"ember_assert",
|
||||
"ember_warn",
|
||||
"ember_deprecate",
|
||||
"ember_deprecateFunc",
|
||||
"require",
|
||||
"suite",
|
||||
"equal",
|
||||
"equals",
|
||||
"test",
|
||||
"testBoth",
|
||||
"raises",
|
||||
"deepEqual",
|
||||
"start",
|
||||
"stop",
|
||||
"ok",
|
||||
"strictEqual",
|
||||
"module"
|
||||
],
|
||||
|
||||
"node" : true,
|
||||
"es5" : true,
|
||||
"browser" : true,
|
||||
|
||||
"boss" : true,
|
||||
"curly": false,
|
||||
"debug": false,
|
||||
"devel": false,
|
||||
"eqeqeq": true,
|
||||
"evil": true,
|
||||
"forin": false,
|
||||
"immed": false,
|
||||
"laxbreak": false,
|
||||
"newcap": true,
|
||||
"noarg": true,
|
||||
"noempty": false,
|
||||
"nonew": false,
|
||||
"nomen": false,
|
||||
"onevar": false,
|
||||
"plusplus": false,
|
||||
"regexp": false,
|
||||
"undef": true,
|
||||
"sub": true,
|
||||
"strict": false,
|
||||
"white": false
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
.DS_Store
|
||||
.gitignore
|
||||
.rvmrc
|
||||
Gemfile
|
||||
Gemfile.lock
|
||||
Rakefile
|
||||
bench/*
|
||||
dist/*
|
||||
spec/*
|
||||
src/*
|
||||
vendor/*
|
||||
@@ -1,4 +1,5 @@
|
||||
source "http://rubygems.org"
|
||||
|
||||
gem "therubyracer", ">= 0.8.0"
|
||||
gem "rake"
|
||||
gem "therubyracer", ">= 0.9.8"
|
||||
gem "rspec"
|
||||
|
||||
+15
-11
@@ -1,20 +1,24 @@
|
||||
GEM
|
||||
remote: http://rubygems.org/
|
||||
specs:
|
||||
diff-lcs (1.1.2)
|
||||
rspec (2.1.0)
|
||||
rspec-core (~> 2.1.0)
|
||||
rspec-expectations (~> 2.1.0)
|
||||
rspec-mocks (~> 2.1.0)
|
||||
rspec-core (2.1.0)
|
||||
rspec-expectations (2.1.0)
|
||||
diff-lcs (~> 1.1.2)
|
||||
rspec-mocks (2.1.0)
|
||||
therubyracer (0.8.0)
|
||||
diff-lcs (1.1.3)
|
||||
libv8 (3.3.10.4)
|
||||
rake (0.9.2.2)
|
||||
rspec (2.11.0)
|
||||
rspec-core (~> 2.11.0)
|
||||
rspec-expectations (~> 2.11.0)
|
||||
rspec-mocks (~> 2.11.0)
|
||||
rspec-core (2.11.0)
|
||||
rspec-expectations (2.11.1)
|
||||
diff-lcs (~> 1.1.3)
|
||||
rspec-mocks (2.11.1)
|
||||
therubyracer (0.10.1)
|
||||
libv8 (~> 3.3.10)
|
||||
|
||||
PLATFORMS
|
||||
ruby
|
||||
|
||||
DEPENDENCIES
|
||||
rake
|
||||
rspec
|
||||
therubyracer (>= 0.8.0)
|
||||
therubyracer (>= 0.9.8)
|
||||
|
||||
+180
-84
@@ -1,8 +1,13 @@
|
||||
[](http://travis-ci.org/wycats/handlebars.js)
|
||||
|
||||
Handlebars.js
|
||||
=============
|
||||
|
||||
Handlebars.js is an extension to the [Mustache templating language](http://mustache.github.com/) created by Chris Wanstrath. Handlebars.js and Mustache are both logicless templating languages that keep the view and the code separated like we all know they should be.
|
||||
|
||||
Checkout the official Handlebars docs site at [http://www.handlebarsjs.com](http://www.handlebarsjs.com).
|
||||
|
||||
|
||||
Installing
|
||||
----------
|
||||
Installing Handlebars is easy. Simply [download the package from GitHub](https://github.com/wycats/handlebars.js/archives/master) and add it to your web pages (you should usually use the most recent version).
|
||||
@@ -13,21 +18,23 @@ In general, the syntax of Handlebars.js templates is a superset of Mustache temp
|
||||
|
||||
Once you have a template, use the Handlebars.compile method to compile the template into a function. The generated function takes a context argument, which will be used to render the template.
|
||||
|
||||
var source = "<p>Hello, my name is {{name}}. I am from {{hometown}}. I have " +
|
||||
"{{kids/length}} kids:</p>" +
|
||||
"<ul>{{#kids}}<li>{{name}} is {{age}}</li>{{/kids}}</ul>";
|
||||
var template = Handlebars.compile(source);
|
||||
```js
|
||||
var source = "<p>Hello, my name is {{name}}. I am from {{hometown}}. I have " +
|
||||
"{{kids.length}} kids:</p>" +
|
||||
"<ul>{{#kids}}<li>{{name}} is {{age}}</li>{{/kids}}</ul>";
|
||||
var template = Handlebars.compile(source);
|
||||
|
||||
var data = { "name": "Alan", "hometown": "Somewhere, TX",
|
||||
"kids": [{"name": "Jimmy", "age": "12"}, {"name": "Sally", "age": "4"}]};
|
||||
var result = template(data);
|
||||
var data = { "name": "Alan", "hometown": "Somewhere, TX",
|
||||
"kids": [{"name": "Jimmy", "age": "12"}, {"name": "Sally", "age": "4"}]};
|
||||
var result = template(data);
|
||||
|
||||
// Would render:
|
||||
// <p>Hello, my name is Alan. I am from Somewhere, TX. I have 2 kids:</p>
|
||||
// <ul>
|
||||
// <li>Jimmy is 12</li>
|
||||
// <li>Sally is 4</li>
|
||||
// </ul>
|
||||
// Would render:
|
||||
// <p>Hello, my name is Alan. I am from Somewhere, TX. I have 2 kids:</p>
|
||||
// <ul>
|
||||
// <li>Jimmy is 12</li>
|
||||
// <li>Sally is 4</li>
|
||||
// </ul>
|
||||
```
|
||||
|
||||
|
||||
Registering Helpers
|
||||
@@ -37,22 +44,23 @@ You can register helpers that Handlebars will use when evaluating your
|
||||
template. Here's an example, which assumes that your objects have a URL
|
||||
embedded in them, as well as the text for a link:
|
||||
|
||||
Handlebars.registerHelper('link_to', function(context) {
|
||||
return "<a href='" + context.url + "'>" + context.body + "</a>";
|
||||
});
|
||||
```js
|
||||
Handlebars.registerHelper('link_to', function(context) {
|
||||
return "<a href='" + context.url + "'>" + context.body + "</a>";
|
||||
});
|
||||
|
||||
var context = { posts: [{url: "/hello-world", body: "Hello World!"}] };
|
||||
var source = "<ul>{{#posts}}<li>{{{link_to this}}}</li></ul>"
|
||||
var context = { posts: [{url: "/hello-world", body: "Hello World!"}] };
|
||||
var source = "<ul>{{#posts}}<li>{{{link_to this}}}</li>{{/posts}}</ul>"
|
||||
|
||||
var template = Handlebars.compile(source);
|
||||
template(context);
|
||||
|
||||
// Would render:
|
||||
//
|
||||
// <ul>
|
||||
// <li><a href='/hello-world'>Hello World!</a></li>
|
||||
// </ul>
|
||||
var template = Handlebars.compile(source);
|
||||
template(context);
|
||||
|
||||
// Would render:
|
||||
//
|
||||
// <ul>
|
||||
// <li><a href='/hello-world'>Hello World!</a></li>
|
||||
// </ul>
|
||||
```
|
||||
|
||||
Escaping
|
||||
--------
|
||||
@@ -71,44 +79,54 @@ Handlebars.js adds a couple of additional features to make writing templates eas
|
||||
|
||||
### Paths
|
||||
|
||||
Handlebars.js supports an extended expression syntax that we call paths. Paths are made up of typical expressions and / characters. Expressions allow you to not only display data from the current context, but to display data from contexts that are descendents and ancestors of the current context.
|
||||
Handlebars.js supports an extended expression syntax that we call paths. Paths are made up of typical expressions and . characters. Expressions allow you to not only display data from the current context, but to display data from contexts that are descendents and ancestors of the current context.
|
||||
|
||||
To display data from descendent contexts, use the `/` character. So, for example, if your data were structured like:
|
||||
To display data from descendent contexts, use the `.` character. So, for example, if your data were structured like:
|
||||
|
||||
var data = {"person": { "name": "Alan" }, company: {"name": "Rad, Inc." } };
|
||||
```js
|
||||
var data = {"person": { "name": "Alan" }, company: {"name": "Rad, Inc." } };
|
||||
```
|
||||
|
||||
you could display the person's name from the top-level context with the following expression:
|
||||
|
||||
{{person/name}}
|
||||
```
|
||||
{{person.name}}
|
||||
```
|
||||
|
||||
Similarly, if already traversed into the person object you could still display the company's name with an expression like `{{../company/name}}`, so:
|
||||
You can backtrack using `../`. For example, if you've already traversed into the person object you could still display the company's name with an expression like `{{../company.name}}`, so:
|
||||
|
||||
{{#person}}{{name}} - {{../company/name}}{{/person}}
|
||||
```
|
||||
{{#person}}{{name}} - {{../company.name}}{{/person}}
|
||||
```
|
||||
|
||||
would render:
|
||||
|
||||
Alan - Rad, Inc.
|
||||
```
|
||||
Alan - Rad, Inc.
|
||||
```
|
||||
|
||||
### Strings
|
||||
|
||||
When calling a helper, you can pass paths or Strings as parameters. For
|
||||
instance:
|
||||
|
||||
Handlebars.registerHelper('link_to', function(title, context) {
|
||||
return "<a href='/posts" + context.id + "'>" + title + "</a>"
|
||||
});
|
||||
```js
|
||||
Handlebars.registerHelper('link_to', function(title, context) {
|
||||
return "<a href='/posts" + context.id + "'>" + title + "</a>"
|
||||
});
|
||||
|
||||
var context = { posts: [{url: "/hello-world", body: "Hello World!"}] };
|
||||
var source = '<ul>{{#posts}}<li>{{{link_to "Post" this}}}</li></ul>'
|
||||
var context = { posts: [{url: "/hello-world", body: "Hello World!"}] };
|
||||
var source = '<ul>{{#posts}}<li>{{{link_to "Post" this}}}</li>{{/posts}}</ul>'
|
||||
|
||||
var template = Handlebars.compile(source);
|
||||
template(context);
|
||||
var template = Handlebars.compile(source);
|
||||
template(context);
|
||||
|
||||
// Would render:
|
||||
//
|
||||
// <ul>
|
||||
// <li><a href='/hello-world'>Post!</a></li>
|
||||
// </ul>
|
||||
// Would render:
|
||||
//
|
||||
// <ul>
|
||||
// <li><a href='/hello-world'>Post!</a></li>
|
||||
// </ul>
|
||||
```
|
||||
|
||||
When you pass a String as a parameter to a helper, the literal String
|
||||
gets passed to the helper function.
|
||||
@@ -118,23 +136,25 @@ gets passed to the helper function.
|
||||
|
||||
Handlebars.js also adds the ability to define block helpers. Block helpers are functions that can be called from anywhere in the template. Here's an example:
|
||||
|
||||
var source = "<ul>{{#people}}<li>{{{#link}}}{{name}}{{/link}}</li>{{/people}}</ul>";
|
||||
Handlebars.registerHelper('link', function(context, fn) {
|
||||
return '<a href="/people/' + this.__get__("id") + '">' + fn(this) + '</a>';
|
||||
});
|
||||
var template = Handlebars.compile(source);
|
||||
```js
|
||||
var source = "<ul>{{#people}}<li>{{{#link}}}{{name}}{{/link}}</li>{{/people}}</ul>";
|
||||
Handlebars.registerHelper('link', function(context, fn) {
|
||||
return '<a href="/people/' + this.__get__("id") + '">' + fn(this) + '</a>';
|
||||
});
|
||||
var template = Handlebars.compile(source);
|
||||
|
||||
var data = { "people": [
|
||||
{ "name": "Alan", "id": 1 },
|
||||
{ "name": "Yehuda", "id": 2 }
|
||||
]};
|
||||
template(data);
|
||||
var data = { "people": [
|
||||
{ "name": "Alan", "id": 1 },
|
||||
{ "name": "Yehuda", "id": 2 }
|
||||
]};
|
||||
template(data);
|
||||
|
||||
// Should render:
|
||||
// <ul>
|
||||
// <li><a href="/people/1">Alan</a></li>
|
||||
// <li><a href="/people/2">Yehuda</a></li>
|
||||
// </ul>
|
||||
// Should render:
|
||||
// <ul>
|
||||
// <li><a href="/people/1">Alan</a></li>
|
||||
// <li><a href="/people/2">Yehuda</a></li>
|
||||
// </ul>
|
||||
```
|
||||
|
||||
Whenever the block helper is called it is given two parameters, the argument that is passed to the helper, or the current context if no argument is passed and the compiled contents of the block. Inside of the block helper the value of `this` is the current context, wrapped to include a method named `__get__` that helps translate paths into values within the helpers.
|
||||
|
||||
@@ -145,28 +165,88 @@ Handlebars when it encounters a partial (`{{> partialName}}`). Partials
|
||||
can either be String templates or compiled template functions. Here's an
|
||||
example:
|
||||
|
||||
var source = "<ul>{{#people}}<li>{{> link}}</li>{{/people}}</ul>";
|
||||
```js
|
||||
var source = "<ul>{{#people}}<li>{{> link}}</li>{{/people}}</ul>";
|
||||
|
||||
Handlebars.registerPartial('link', '<a href="/people/{{id}}">{{name}}</a>')
|
||||
var template = Handlebars.compile(source);
|
||||
Handlebars.registerPartial('link', '<a href="/people/{{id}}">{{name}}</a>')
|
||||
var template = Handlebars.compile(source);
|
||||
|
||||
var data = { "people": [
|
||||
{ "name": "Alan", "id": 1 },
|
||||
{ "name": "Yehuda", "id": 2 }
|
||||
]};
|
||||
var data = { "people": [
|
||||
{ "name": "Alan", "id": 1 },
|
||||
{ "name": "Yehuda", "id": 2 }
|
||||
]};
|
||||
|
||||
template(data);
|
||||
template(data);
|
||||
|
||||
// Should render:
|
||||
// <ul>
|
||||
// <li><a href="/people/1">Alan</a></li>
|
||||
// <li><a href="/people/2">Yehuda</a></li>
|
||||
// </ul>
|
||||
```
|
||||
|
||||
### Comments
|
||||
|
||||
You can add comments to your templates with the following syntax:
|
||||
|
||||
```js
|
||||
{{! This is a comment }}
|
||||
```
|
||||
|
||||
You can also use real html comments if you want them to end up in the output.
|
||||
|
||||
```html
|
||||
<div>
|
||||
{{! This comment will not end up in the output }}
|
||||
<!-- This comment will show up in the output -->
|
||||
</div>
|
||||
```
|
||||
|
||||
// Should render:
|
||||
// <ul>
|
||||
// <li><a href="/people/1">Alan</a></li>
|
||||
// <li><a href="/people/2">Yehuda</a></li>
|
||||
// </ul>
|
||||
|
||||
Precompiling Templates
|
||||
----------------------
|
||||
|
||||
TODO in the rewrite. This will use RubyRacer and not node.
|
||||
Handlebars allows templates to be precompiled and included as javascript
|
||||
code rather than the handlebars template allowing for faster startup time.
|
||||
|
||||
### Installation
|
||||
The precompiler script may be installed via npm using the `npm install -g handlebars`
|
||||
command.
|
||||
|
||||
### Usage
|
||||
|
||||
<pre>
|
||||
Precompile handlebar templates.
|
||||
Usage: handlebars template...
|
||||
|
||||
Options:
|
||||
-f, --output Output File [string]
|
||||
-k, --known Known helpers [string]
|
||||
-o, --knownOnly Known helpers only [boolean]
|
||||
-m, --min Minimize output [boolean]
|
||||
-s, --simple Output template function only. [boolean]
|
||||
-r, --root Template root. Base value that will be stripped from template names. [string]
|
||||
</pre>
|
||||
|
||||
If using the precompiler's normal mode, the resulting templates will be stored
|
||||
to the `Handlebars.templates` object using the relative template name sans the
|
||||
extension. These templates may be executed in the same manner as templates.
|
||||
|
||||
If using the simple mode the precompiler will generate a single javascript method.
|
||||
To execute this method it must be passed to the using the `Handlebars.template`
|
||||
method and the resulting object may be as normal.
|
||||
|
||||
### Optimizations
|
||||
|
||||
- Rather than using the full _handlebars.js_ library, implementations that
|
||||
do not need to compile templates at runtime may include _handlebars.runtime.js_
|
||||
whose min+gzip size is approximately 1k.
|
||||
- If a helper is known to exist in the target environment they may be defined
|
||||
using the `--known name` argument may be used to optimize accesses to these
|
||||
helpers for size and speed.
|
||||
- When all helpers are known in advance the `--knownOnly` argument may be used
|
||||
to optimize all block helper references.
|
||||
|
||||
|
||||
Performance
|
||||
-----------
|
||||
@@ -178,9 +258,7 @@ Building
|
||||
--------
|
||||
|
||||
To build handlebars, just run `rake release`, and you will get two files
|
||||
in the `dist` directory. The debug version comes with stack trace
|
||||
annotations for webkit browsers, but will slightly increase startup
|
||||
time.
|
||||
in the `dist` directory.
|
||||
|
||||
|
||||
Upgrading
|
||||
@@ -192,27 +270,45 @@ changed.
|
||||
|
||||
Instead of:
|
||||
|
||||
template(context, helpers, partials, [data])
|
||||
```js
|
||||
template(context, helpers, partials, [data])
|
||||
```
|
||||
|
||||
Use:
|
||||
|
||||
template(context, {helpers: helpers, partials: partials, data: data}
|
||||
```js
|
||||
template(context, {helpers: helpers, partials: partials, data: data})
|
||||
```
|
||||
|
||||
Known Issues
|
||||
------------
|
||||
* Handlebars.js can be cryptic when there's an error while rendering.
|
||||
|
||||
Handlebars Contrib
|
||||
------------------
|
||||
Alan Johnson, a.k.a. commondream, keeps a repository of useful helpers in the [handlebars-contrib](http://github.com/commondream/handlebars-contrib) repository. Feel free to copy those, or add some of your own.
|
||||
* Using a variable, helper, or partial named `class` causes errors in IE browsers. (Instead, use `className`)
|
||||
|
||||
Handlebars in the Wild
|
||||
-----------------
|
||||
* Don Park wrote an Express.js view engine adapter for Handlebars.js called [hbs](http://github.com/donpark/hbs)
|
||||
* [jblotus](http://github.com/jblotus) created [http://tryhandlebarsjs.com](http://tryhandlebarsjs.com) for anyone who would
|
||||
like to try out Handlebars.js in their browser.
|
||||
* Don Park wrote an Express.js view engine adapter for Handlebars.js called [hbs](http://github.com/donpark/hbs).
|
||||
* [sammy.js](http://github.com/quirkey/sammy) by Aaron Quint, a.k.a. quirkey, supports Handlebars.js as one of its template plugins.
|
||||
* [SproutCore](http://www.sproutcore.com) uses Handlebars.js as its main templating engine, extending it with automatic data binding support.
|
||||
* [Ember.js](http://www.emberjs.com) makes Handlebars.js the primary way to structure your views, also with automatic data binding support.
|
||||
* Les Hill (@leshill) wrote a Rails Asset Pipeline gem named [handlebars_assets](http://github.com/leshill/handlebars_assets).
|
||||
|
||||
Helping Out
|
||||
-----------
|
||||
To build Handlebars.js you'll need a few things installed.
|
||||
|
||||
* Node.js
|
||||
* Jison, for building the compiler - `npm install jison`
|
||||
* Ruby
|
||||
* therubyracer, for running tests - `gem install therubyracer`
|
||||
* rspec, for running tests - `gem install rspec`
|
||||
|
||||
There's a Gemfile in the repo, so you can run `bundle` to install rspec and therubyracer if you've got bundler installed.
|
||||
|
||||
To build Handlebars.js from scratch, you'll want to run `rake compile` in the root of the project. That will build Handlebars and output the results to the dist/ folder. To run tests, run `rake spec`. You can also run our set of benchmarks with `rake bench`.
|
||||
|
||||
If you notice any problems, please report them to the GitHub issue tracker at [http://github.com/wycats/handlebars.js/issues](http://github.com/wycats/handlebars.js/issues). Feel free to contact commondream or wycats through GitHub with any other questions or feature requests. To submit changes fork the project and send a pull request.
|
||||
|
||||
License
|
||||
|
||||
@@ -1,24 +1,35 @@
|
||||
require "rubygems"
|
||||
require "bundler/setup"
|
||||
|
||||
file "lib/handlebars/parser.js" => ["src/handlebars.yy","src/handlebars.l"] do
|
||||
if ENV['PATH'].split(':').any? {|folder| File.exists?(folder+'/jison')}
|
||||
system "jison src/handlebars.yy src/handlebars.l"
|
||||
File.open("lib/handlebars/parser.js", "w") do |file|
|
||||
def compile_parser
|
||||
system "./node_modules/jison/lib/jison/cli-wrapper.js src/handlebars.yy src/handlebars.l"
|
||||
if $?.success?
|
||||
File.open("lib/handlebars/compiler/parser.js", "w") do |file|
|
||||
file.puts File.read("handlebars.js") + ";"
|
||||
end
|
||||
|
||||
sh "rm handlebars.js"
|
||||
else
|
||||
puts "Jison is not installed. Try running `npm install jison`."
|
||||
puts "Failed to run Jison."
|
||||
end
|
||||
end
|
||||
|
||||
task :compile => "lib/handlebars/parser.js"
|
||||
file "lib/handlebars/compiler/parser.js" => ["src/handlebars.yy","src/handlebars.l"] do
|
||||
if File.exists?('./node_modules/jison/lib/jison/cli-wrapper.js')
|
||||
compile_parser
|
||||
else
|
||||
puts "Jison is not installed. Trying `npm install jison`."
|
||||
sh "npm install jison"
|
||||
compile_parser
|
||||
end
|
||||
end
|
||||
|
||||
task :compile => "lib/handlebars/compiler/parser.js"
|
||||
|
||||
desc "run the spec suite"
|
||||
task :spec => [:release] do
|
||||
system "rspec -cfs spec"
|
||||
rc = system "rspec -cfs spec"
|
||||
fail "rspec spec failed with exit code #{$?.exitstatus}" if (rc.nil? || ! rc || $?.exitstatus != 0)
|
||||
end
|
||||
|
||||
task :default => [:compile, :spec]
|
||||
@@ -28,18 +39,17 @@ def remove_exports(string)
|
||||
match ? match[1] : string
|
||||
end
|
||||
|
||||
minimal_deps = %w(parser base ast visitor utils compiler).map do |file|
|
||||
minimal_deps = %w(base compiler/parser compiler/base compiler/ast utils compiler/compiler runtime).map do |file|
|
||||
"lib/handlebars/#{file}.js"
|
||||
end
|
||||
|
||||
debug_deps = %w(parser base ast visitor printer utils compiler debug).map do |file|
|
||||
runtime_deps = %w(base utils runtime).map do |file|
|
||||
"lib/handlebars/#{file}.js"
|
||||
end
|
||||
|
||||
directory "dist"
|
||||
|
||||
minimal_deps.unshift "dist"
|
||||
debug_deps.unshift "dist"
|
||||
|
||||
def build_for_task(task)
|
||||
FileUtils.rm_rf("dist/*") if File.directory?("dist")
|
||||
@@ -61,47 +71,47 @@ file "dist/handlebars.js" => minimal_deps do |task|
|
||||
build_for_task(task)
|
||||
end
|
||||
|
||||
file "dist/handlebars.debug.js" => debug_deps do |task|
|
||||
file "dist/handlebars.runtime.js" => runtime_deps do |task|
|
||||
build_for_task(task)
|
||||
end
|
||||
|
||||
task :build => [:compile, "dist/handlebars.js"]
|
||||
task :debug => [:compile, "dist/handlebars.debug.js"]
|
||||
task :runtime => [:compile, "dist/handlebars.runtime.js"]
|
||||
|
||||
desc "build the build and debug versions of handlebars"
|
||||
task :release => [:build, :debug]
|
||||
desc "build the build and runtime version of handlebars"
|
||||
task :release => [:build, :runtime]
|
||||
|
||||
directory "vendor"
|
||||
|
||||
desc "benchmark against dust.js and mustache.js"
|
||||
task :bench => "vendor" do
|
||||
require "open-uri"
|
||||
File.open("vendor/mustache.js", "w") do |file|
|
||||
file.puts open("https://github.com/janl/mustache.js/raw/master/mustache.js").read
|
||||
file.puts "module.exports = Mustache;"
|
||||
end
|
||||
#File.open("vendor/mustache.js", "w") do |file|
|
||||
#file.puts open("https://github.com/janl/mustache.js/raw/master/mustache.js").read
|
||||
#file.puts "module.exports = Mustache;"
|
||||
#end
|
||||
|
||||
File.open("vendor/benchmark.js", "w") do |file|
|
||||
file.puts open("https://github.com/mathiasbynens/benchmark.js/raw/master/benchmark.js").read
|
||||
file.puts open("https://raw.github.com/bestiejs/benchmark.js/master/benchmark.js").read
|
||||
end
|
||||
|
||||
if File.directory?("vendor/dustjs")
|
||||
system "cd vendor/dustjs && git pull"
|
||||
else
|
||||
system "git clone git://github.com/akdubya/dustjs.git vendor/dustjs"
|
||||
end
|
||||
#if File.directory?("vendor/dustjs")
|
||||
#system "cd vendor/dustjs && git pull"
|
||||
#else
|
||||
#system "git clone git://github.com/akdubya/dustjs.git vendor/dustjs"
|
||||
#end
|
||||
|
||||
if File.directory?("vendor/coffee")
|
||||
system "cd vendor/coffee && git pull"
|
||||
else
|
||||
system "git clone git://github.com/jashkenas/coffee-script.git vendor/coffee"
|
||||
end
|
||||
#if File.directory?("vendor/coffee")
|
||||
#system "cd vendor/coffee && git pull"
|
||||
#else
|
||||
#system "git clone git://github.com/jashkenas/coffee-script.git vendor/coffee"
|
||||
#end
|
||||
|
||||
if File.directory?("vendor/eco")
|
||||
system "cd vendor/eco && git pull"
|
||||
else
|
||||
system "git clone git://github.com/sstephenson/eco.git vendor/eco"
|
||||
end
|
||||
#if File.directory?("vendor/eco")
|
||||
#system "cd vendor/eco && git pull && npm update"
|
||||
#else
|
||||
#system "git clone git://github.com/sstephenson/eco.git vendor/eco && cd vendor/eco && npm update"
|
||||
#end
|
||||
|
||||
system "node bench/handlebars.js"
|
||||
end
|
||||
|
||||
@@ -112,6 +112,8 @@ BenchWarmer.prototype = {
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
print("\n");
|
||||
},
|
||||
startLine: function(name) {
|
||||
var winners = Benchmark.map(this.winners(this.currentBenches), function(bench) {
|
||||
|
||||
+1
-4
@@ -13,10 +13,7 @@ var Mustache = require("mustache");
|
||||
var ecoExports = require("eco");
|
||||
|
||||
eco = function(str) {
|
||||
var module = {};
|
||||
var template = new Function("module", ecoExports.compile(str));
|
||||
template(module);
|
||||
return module.exports;
|
||||
return ecoExports(str);
|
||||
}
|
||||
|
||||
var benchDetails = {
|
||||
|
||||
Executable
+159
@@ -0,0 +1,159 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
var optimist = require('optimist')
|
||||
.usage('Precompile handlebar templates.\nUsage: $0 template...', {
|
||||
'f': {
|
||||
'type': 'string',
|
||||
'description': 'Output File',
|
||||
'alias': 'output'
|
||||
},
|
||||
'a': {
|
||||
'type': 'boolean',
|
||||
'description': 'Exports amd style (require.js)',
|
||||
'alias': 'amd'
|
||||
},
|
||||
'h': {
|
||||
'type': 'string',
|
||||
'description': 'Path to handlebar.js (only valid for amd-style)',
|
||||
'alias': 'handlebarPath',
|
||||
'default': ''
|
||||
},
|
||||
'k': {
|
||||
'type': 'string',
|
||||
'description': 'Known helpers',
|
||||
'alias': 'known'
|
||||
},
|
||||
'o': {
|
||||
'type': 'boolean',
|
||||
'description': 'Known helpers only',
|
||||
'alias': 'knownOnly'
|
||||
},
|
||||
'm': {
|
||||
'type': 'boolean',
|
||||
'description': 'Minimize output',
|
||||
'alias': 'min'
|
||||
},
|
||||
's': {
|
||||
'type': 'boolean',
|
||||
'description': 'Output template function only.',
|
||||
'alias': 'simple'
|
||||
},
|
||||
'r': {
|
||||
'type': 'string',
|
||||
'description': 'Template root. Base value that will be stripped from template names.',
|
||||
'alias': 'root'
|
||||
}
|
||||
})
|
||||
|
||||
.check(function(argv) {
|
||||
var template = [0];
|
||||
if (!argv._.length) {
|
||||
throw 'Must define at least one template or directory.';
|
||||
}
|
||||
|
||||
argv._.forEach(function(template) {
|
||||
try {
|
||||
fs.statSync(template);
|
||||
} catch (err) {
|
||||
throw 'Unable to open template file "' + template + '"';
|
||||
}
|
||||
});
|
||||
})
|
||||
.check(function(argv) {
|
||||
if (argv.simple && argv.min) {
|
||||
throw 'Unable to minimze simple output';
|
||||
}
|
||||
if (argv.simple && (argv._.length !== 1 || fs.statSync(argv._[0]).isDirectory())) {
|
||||
throw 'Unable to output multiple templates in simple mode';
|
||||
}
|
||||
});
|
||||
|
||||
var fs = require('fs'),
|
||||
handlebars = require('../lib/handlebars'),
|
||||
basename = require('path').basename,
|
||||
uglify = require('uglify-js');
|
||||
|
||||
var argv = optimist.argv,
|
||||
template = argv._[0];
|
||||
|
||||
// Convert the known list into a hash
|
||||
var known = {};
|
||||
if (argv.known && !Array.isArray(argv.known)) {
|
||||
argv.known = [argv.known];
|
||||
}
|
||||
if (argv.known) {
|
||||
for (var i = 0, len = argv.known.length; i < len; i++) {
|
||||
known[argv.known[i]] = true;
|
||||
}
|
||||
}
|
||||
|
||||
var output = [];
|
||||
if (!argv.simple) {
|
||||
if (argv.amd) {
|
||||
output.push('define([\'' + argv.handlebarPath + 'handlebars\'], function(Handlebars) {\n');
|
||||
} else {
|
||||
output.push('(function() {\n');
|
||||
}
|
||||
output.push(' var template = Handlebars.template, templates = Handlebars.templates = Handlebars.templates || {};\n');
|
||||
}
|
||||
function processTemplate(template, root) {
|
||||
var path = template,
|
||||
stat = fs.statSync(path);
|
||||
if (stat.isDirectory()) {
|
||||
fs.readdirSync(template).map(function(file) {
|
||||
var path = template + '/' + file;
|
||||
|
||||
if (/\.handlebars$/.test(path) || fs.statSync(path).isDirectory()) {
|
||||
processTemplate(path, root || template);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
var data = fs.readFileSync(path, 'utf8');
|
||||
|
||||
var options = {
|
||||
knownHelpers: known,
|
||||
knownHelpersOnly: argv.o
|
||||
};
|
||||
|
||||
// Clean the template name
|
||||
if (!root) {
|
||||
template = basename(template);
|
||||
} else if (template.indexOf(root) === 0) {
|
||||
template = template.substring(root.length+1);
|
||||
}
|
||||
template = template.replace(/\.handlebars$/, '');
|
||||
|
||||
if (argv.simple) {
|
||||
output.push(handlebars.precompile(data, options) + '\n');
|
||||
} else {
|
||||
output.push('templates[\'' + template + '\'] = template(' + handlebars.precompile(data, options) + ');\n');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
argv._.forEach(function(template) {
|
||||
processTemplate(template, argv.root);
|
||||
});
|
||||
|
||||
// Output the content
|
||||
if (!argv.simple) {
|
||||
if (argv.amd) {
|
||||
output.push('});');
|
||||
} else {
|
||||
output.push('})();');
|
||||
}
|
||||
}
|
||||
output = output.join('');
|
||||
|
||||
if (argv.min) {
|
||||
var ast = uglify.parser.parse(output);
|
||||
ast = uglify.uglify.ast_mangle(ast);
|
||||
ast = uglify.uglify.ast_squeeze(ast);
|
||||
output = uglify.uglify.gen_code(ast);
|
||||
}
|
||||
|
||||
if (argv.output) {
|
||||
fs.writeFileSync(argv.output, output, 'utf8');
|
||||
} else {
|
||||
console.log(output);
|
||||
}
|
||||
+6
-7
@@ -1,13 +1,12 @@
|
||||
var Handlebars = require("handlebars/base");
|
||||
var Handlebars = require("./handlebars/base");
|
||||
module.exports = Handlebars;
|
||||
|
||||
require("handlebars/utils");
|
||||
// Each of these augment the Handlebars object. No need to setup here.
|
||||
// (This is done to easily share code between commonjs and browse envs)
|
||||
require("./handlebars/utils");
|
||||
|
||||
require("handlebars/ast");
|
||||
require("handlebars/printer");
|
||||
require("handlebars/visitor");
|
||||
|
||||
require("handlebars/compiler");
|
||||
require("./handlebars/compiler");
|
||||
require("./handlebars/runtime");
|
||||
|
||||
// BEGIN(BROWSER)
|
||||
|
||||
|
||||
+51
-45
@@ -1,20 +1,11 @@
|
||||
var handlebars = require("handlebars/parser").parser;
|
||||
|
||||
// BEGIN(BROWSER)
|
||||
var Handlebars = {};
|
||||
|
||||
Handlebars.VERSION = "1.0.beta.1";
|
||||
/*jshint eqnull:true*/
|
||||
this.Handlebars = {};
|
||||
|
||||
Handlebars.Parser = handlebars;
|
||||
(function(Handlebars) {
|
||||
|
||||
Handlebars.parse = function(string) {
|
||||
Handlebars.Parser.yy = Handlebars.AST;
|
||||
return Handlebars.Parser.parse(string);
|
||||
};
|
||||
|
||||
Handlebars.print = function(ast) {
|
||||
return new Handlebars.PrintVisitor().accept(ast);
|
||||
};
|
||||
Handlebars.VERSION = "1.0.rc.1";
|
||||
|
||||
Handlebars.helpers = {};
|
||||
Handlebars.partials = {};
|
||||
@@ -36,15 +27,16 @@ Handlebars.registerHelper('helperMissing', function(arg) {
|
||||
}
|
||||
});
|
||||
|
||||
Handlebars.registerHelper('blockHelperMissing', function(context, fn, inverse) {
|
||||
inverse = inverse || function() {};
|
||||
var toString = Object.prototype.toString, functionType = "[object Function]";
|
||||
|
||||
Handlebars.registerHelper('blockHelperMissing', function(context, options) {
|
||||
var inverse = options.inverse || function() {}, fn = options.fn;
|
||||
|
||||
|
||||
var ret = "";
|
||||
var type = Object.prototype.toString.call(context);
|
||||
var type = toString.call(context);
|
||||
|
||||
if(type === "[object Function]") {
|
||||
context = context();
|
||||
}
|
||||
if(type === functionType) { context = context.call(this); }
|
||||
|
||||
if(context === true) {
|
||||
return fn(this);
|
||||
@@ -52,26 +44,36 @@ Handlebars.registerHelper('blockHelperMissing', function(context, fn, inverse) {
|
||||
return inverse(this);
|
||||
} else if(type === "[object Array]") {
|
||||
if(context.length > 0) {
|
||||
for(var i=0, j=context.length; i<j; i++) {
|
||||
ret = ret + fn(context[i]);
|
||||
}
|
||||
return Handlebars.helpers.each(context, options);
|
||||
} else {
|
||||
ret = inverse(this);
|
||||
return inverse(this);
|
||||
}
|
||||
return ret;
|
||||
} else {
|
||||
return fn(context);
|
||||
}
|
||||
}, function(context, fn) {
|
||||
return fn(context);
|
||||
});
|
||||
|
||||
Handlebars.registerHelper('each', function(context, fn, inverse) {
|
||||
var ret = "";
|
||||
Handlebars.K = function() {};
|
||||
|
||||
Handlebars.createFrame = Object.create || function(object) {
|
||||
Handlebars.K.prototype = object;
|
||||
var obj = new Handlebars.K();
|
||||
Handlebars.K.prototype = null;
|
||||
return obj;
|
||||
};
|
||||
|
||||
Handlebars.registerHelper('each', function(context, options) {
|
||||
var fn = options.fn, inverse = options.inverse;
|
||||
var ret = "", data;
|
||||
|
||||
if (options.data) {
|
||||
data = Handlebars.createFrame(options.data);
|
||||
}
|
||||
|
||||
if(context && context.length > 0) {
|
||||
for(var i=0, j=context.length; i<j; i++) {
|
||||
ret = ret + fn(context[i]);
|
||||
if (data) { data.index = i; }
|
||||
ret = ret + fn(context[i], { data: data });
|
||||
}
|
||||
} else {
|
||||
ret = inverse(this);
|
||||
@@ -79,32 +81,36 @@ Handlebars.registerHelper('each', function(context, fn, inverse) {
|
||||
return ret;
|
||||
});
|
||||
|
||||
Handlebars.registerHelper('if', function(context, fn, inverse) {
|
||||
if(!context || context == []) {
|
||||
return inverse(this);
|
||||
Handlebars.registerHelper('if', function(context, options) {
|
||||
var type = toString.call(context);
|
||||
if(type === functionType) { context = context.call(this); }
|
||||
|
||||
if(!context || Handlebars.Utils.isEmpty(context)) {
|
||||
return options.inverse(this);
|
||||
} else {
|
||||
return fn(this);
|
||||
return options.fn(this);
|
||||
}
|
||||
});
|
||||
|
||||
Handlebars.registerHelper('unless', function(context, fn, inverse) {
|
||||
return Handlebars.helpers['if'].call(this, context, inverse, fn);
|
||||
Handlebars.registerHelper('unless', function(context, options) {
|
||||
var fn = options.fn, inverse = options.inverse;
|
||||
options.fn = inverse;
|
||||
options.inverse = fn;
|
||||
|
||||
return Handlebars.helpers['if'].call(this, context, options);
|
||||
});
|
||||
|
||||
Handlebars.registerHelper('with', function(context, fn) {
|
||||
return fn(context);
|
||||
Handlebars.registerHelper('with', function(context, options) {
|
||||
return options.fn(context);
|
||||
});
|
||||
|
||||
Handlebars.logger = {
|
||||
DEBUG: 0, INFO: 1, WARN: 2, ERROR: 3, level: 3,
|
||||
Handlebars.registerHelper('log', function(context) {
|
||||
Handlebars.log(context);
|
||||
});
|
||||
|
||||
// override in the host environment
|
||||
log: function(level, str) {}
|
||||
};
|
||||
|
||||
Handlebars.log = function(level, str) { Handlebars.logger.log(level, str); };
|
||||
}(this.Handlebars));
|
||||
|
||||
// END(BROWSER)
|
||||
|
||||
module.exports = Handlebars;
|
||||
module.exports = this.Handlebars;
|
||||
|
||||
|
||||
@@ -1,736 +0,0 @@
|
||||
var Handlebars = require("handlebars");
|
||||
|
||||
// BEGIN(BROWSER)
|
||||
Handlebars.Compiler = function() {};
|
||||
Handlebars.JavaScriptCompiler = function() {};
|
||||
|
||||
(function(Compiler, JavaScriptCompiler) {
|
||||
Compiler.OPCODE_MAP = {
|
||||
appendContent: 1,
|
||||
getContext: 2,
|
||||
lookupWithHelpers: 3,
|
||||
lookup: 4,
|
||||
append: 5,
|
||||
invokeMustache: 6,
|
||||
appendEscaped: 7,
|
||||
pushString: 8,
|
||||
truthyOrFallback: 9,
|
||||
functionOrFallback: 10,
|
||||
invokeProgram: 11,
|
||||
invokePartial: 12,
|
||||
push: 13,
|
||||
invokeInverse: 14,
|
||||
assignToHash: 15,
|
||||
pushStringParam: 16
|
||||
};
|
||||
|
||||
Compiler.MULTI_PARAM_OPCODES = {
|
||||
appendContent: 1,
|
||||
getContext: 1,
|
||||
lookupWithHelpers: 1,
|
||||
lookup: 1,
|
||||
invokeMustache: 2,
|
||||
pushString: 1,
|
||||
truthyOrFallback: 1,
|
||||
functionOrFallback: 1,
|
||||
invokeProgram: 2,
|
||||
invokePartial: 1,
|
||||
push: 1,
|
||||
invokeInverse: 1,
|
||||
assignToHash: 1,
|
||||
pushStringParam: 1
|
||||
};
|
||||
|
||||
Compiler.DISASSEMBLE_MAP = {};
|
||||
|
||||
for(var prop in Compiler.OPCODE_MAP) {
|
||||
var value = Compiler.OPCODE_MAP[prop];
|
||||
Compiler.DISASSEMBLE_MAP[value] = prop;
|
||||
}
|
||||
|
||||
Compiler.multiParamSize = function(code) {
|
||||
return Compiler.MULTI_PARAM_OPCODES[Compiler.DISASSEMBLE_MAP[code]];
|
||||
};
|
||||
|
||||
Compiler.prototype = {
|
||||
compiler: Compiler,
|
||||
|
||||
disassemble: function() {
|
||||
var opcodes = this.opcodes, opcode, nextCode;
|
||||
var out = [], str, name, value;
|
||||
|
||||
for(var i=0, l=opcodes.length; i<l; i++) {
|
||||
opcode = opcodes[i];
|
||||
|
||||
if(opcode === 'DECLARE') {
|
||||
name = opcodes[++i];
|
||||
value = opcodes[++i];
|
||||
out.push("DECLARE " + name + " = " + value);
|
||||
} else {
|
||||
str = Compiler.DISASSEMBLE_MAP[opcode];
|
||||
|
||||
var extraParams = Compiler.multiParamSize(opcode);
|
||||
var codes = [];
|
||||
|
||||
for(var j=0; j<extraParams; j++) {
|
||||
nextCode = opcodes[++i];
|
||||
|
||||
if(typeof nextCode === "string") {
|
||||
nextCode = "\"" + nextCode.replace("\n", "\\n") + "\"";
|
||||
}
|
||||
|
||||
codes.push(nextCode);
|
||||
}
|
||||
|
||||
str = str + " " + codes.join(" ");
|
||||
|
||||
out.push(str);
|
||||
}
|
||||
}
|
||||
|
||||
return out.join("\n");
|
||||
},
|
||||
|
||||
guid: 0,
|
||||
|
||||
compile: function(program, options) {
|
||||
this.children = [];
|
||||
this.depths = {list: []};
|
||||
this.options = options || {};
|
||||
return this.program(program);
|
||||
},
|
||||
|
||||
accept: function(node) {
|
||||
return this[node.type](node);
|
||||
},
|
||||
|
||||
program: function(program) {
|
||||
var statements = program.statements, statement;
|
||||
this.opcodes = [];
|
||||
|
||||
for(var i=0, l=statements.length; i<l; i++) {
|
||||
statement = statements[i];
|
||||
this[statement.type](statement);
|
||||
}
|
||||
|
||||
this.depths.list = this.depths.list.sort(function(a, b) {
|
||||
return a - b;
|
||||
});
|
||||
|
||||
return this;
|
||||
},
|
||||
|
||||
compileProgram: function(program) {
|
||||
var result = new this.compiler().compile(program, this.options);
|
||||
var guid = this.guid++;
|
||||
|
||||
this.usePartial = this.usePartial || result.usePartial;
|
||||
|
||||
this.children[guid] = result;
|
||||
|
||||
for(var i=0, l=result.depths.list.length; i<l; i++) {
|
||||
depth = result.depths.list[i];
|
||||
|
||||
if(depth < 2) { continue; }
|
||||
else { this.addDepth(depth - 1); }
|
||||
}
|
||||
|
||||
return guid;
|
||||
},
|
||||
|
||||
block: function(block) {
|
||||
var mustache = block.mustache;
|
||||
var depth, child, inverse, inverseGuid;
|
||||
|
||||
var params = this.setupStackForMustache(mustache);
|
||||
|
||||
var programGuid = this.compileProgram(block.program);
|
||||
|
||||
if(block.program.inverse) {
|
||||
inverseGuid = this.compileProgram(block.program.inverse);
|
||||
this.declare('inverse', inverseGuid);
|
||||
}
|
||||
|
||||
this.opcode('invokeProgram', programGuid, params.length);
|
||||
this.declare('inverse', null);
|
||||
this.opcode('append');
|
||||
},
|
||||
|
||||
inverse: function(block) {
|
||||
this.ID(block.mustache.id);
|
||||
var programGuid = this.compileProgram(block.program);
|
||||
|
||||
this.opcode('invokeInverse', programGuid);
|
||||
this.opcode('append');
|
||||
},
|
||||
|
||||
hash: function(hash) {
|
||||
var pairs = hash.pairs, pair, val;
|
||||
|
||||
this.opcode('push', '{}');
|
||||
|
||||
for(var i=0, l=pairs.length; i<l; i++) {
|
||||
pair = pairs[i];
|
||||
val = pair[1];
|
||||
|
||||
this.accept(val);
|
||||
this.opcode('assignToHash', pair[0]);
|
||||
}
|
||||
},
|
||||
|
||||
partial: function(partial) {
|
||||
var id = partial.id;
|
||||
this.usePartial = true;
|
||||
|
||||
if(partial.context) {
|
||||
this.ID(partial.context);
|
||||
} else {
|
||||
this.opcode('push', 'context');
|
||||
}
|
||||
|
||||
this.opcode('invokePartial', id.original);
|
||||
this.opcode('append');
|
||||
},
|
||||
|
||||
content: function(content) {
|
||||
this.opcode('appendContent', content.string);
|
||||
},
|
||||
|
||||
mustache: function(mustache) {
|
||||
var params = this.setupStackForMustache(mustache);
|
||||
|
||||
this.opcode('invokeMustache', params.length, mustache.id.original);
|
||||
|
||||
if(mustache.escaped) {
|
||||
this.opcode('appendEscaped');
|
||||
} else {
|
||||
this.opcode('append');
|
||||
}
|
||||
},
|
||||
|
||||
ID: function(id) {
|
||||
this.addDepth(id.depth);
|
||||
|
||||
this.opcode('getContext', id.depth);
|
||||
|
||||
this.opcode('lookupWithHelpers', id.parts[0] || null);
|
||||
|
||||
for(var i=1, l=id.parts.length; i<l; i++) {
|
||||
this.opcode('lookup', id.parts[i]);
|
||||
}
|
||||
},
|
||||
|
||||
STRING: function(string) {
|
||||
this.opcode('pushString', string.string);
|
||||
},
|
||||
|
||||
comment: function() {},
|
||||
|
||||
// HELPERS
|
||||
pushParams: function(params) {
|
||||
var i = params.length, param;
|
||||
|
||||
while(i--) {
|
||||
param = params[i];
|
||||
|
||||
if(this.options.stringParams) {
|
||||
if(param.depth) {
|
||||
this.addDepth(param.depth);
|
||||
}
|
||||
|
||||
this.opcode('getContext', param.depth || 0);
|
||||
this.opcode('pushStringParam', param.string);
|
||||
} else {
|
||||
this[param.type](param);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
opcode: function(name, val1, val2) {
|
||||
this.opcodes.push(Compiler.OPCODE_MAP[name]);
|
||||
if(val1 !== undefined) { this.opcodes.push(val1); }
|
||||
if(val2 !== undefined) { this.opcodes.push(val2); }
|
||||
},
|
||||
|
||||
declare: function(name, value) {
|
||||
this.opcodes.push('DECLARE');
|
||||
this.opcodes.push(name);
|
||||
this.opcodes.push(value);
|
||||
},
|
||||
|
||||
addDepth: function(depth) {
|
||||
if(depth === 0) { return; }
|
||||
|
||||
if(!this.depths[depth]) {
|
||||
this.depths[depth] = true;
|
||||
this.depths.list.push(depth);
|
||||
}
|
||||
},
|
||||
|
||||
setupStackForMustache: function(mustache) {
|
||||
var params = mustache.params;
|
||||
|
||||
this.pushParams(params);
|
||||
|
||||
if(mustache.hash) {
|
||||
this.hash(mustache.hash);
|
||||
} else {
|
||||
this.opcode('push', '{}');
|
||||
}
|
||||
|
||||
this.ID(mustache.id);
|
||||
|
||||
return params;
|
||||
}
|
||||
};
|
||||
|
||||
JavaScriptCompiler.prototype = {
|
||||
// PUBLIC API: You can override these methods in a subclass to provide
|
||||
// alternative compiled forms for name lookup and buffering semantics
|
||||
nameLookup: function(parent, name, type) {
|
||||
if(JavaScriptCompiler.RESERVED_WORDS[name] || name.indexOf('-') !== -1) {
|
||||
return parent + "['" + name + "']";
|
||||
} else {
|
||||
return parent + "." + name;
|
||||
}
|
||||
},
|
||||
|
||||
appendToBuffer: function(string) {
|
||||
return "buffer = buffer + " + string + ";";
|
||||
},
|
||||
|
||||
initializeBuffer: function() {
|
||||
return this.quotedString("");
|
||||
},
|
||||
// END PUBLIC API
|
||||
|
||||
compile: function(environment, options) {
|
||||
this.environment = environment;
|
||||
this.options = options || {};
|
||||
|
||||
this.preamble();
|
||||
|
||||
this.stackSlot = 0;
|
||||
this.stackVars = [];
|
||||
this.registers = {list: []};
|
||||
|
||||
this.compileChildren(environment, options);
|
||||
|
||||
Handlebars.log(Handlebars.logger.DEBUG, environment.disassemble() + "\n\n");
|
||||
|
||||
var opcodes = environment.opcodes, opcode, name, declareName, declareVal;
|
||||
|
||||
this.i = 0;
|
||||
|
||||
for(l=opcodes.length; this.i<l; this.i++) {
|
||||
opcode = this.nextOpcode(0);
|
||||
|
||||
if(opcode[0] === 'DECLARE') {
|
||||
this.i = this.i + 2;
|
||||
this[opcode[1]] = opcode[2];
|
||||
} else {
|
||||
this.i = this.i + opcode[1].length;
|
||||
this[opcode[0]].apply(this, opcode[1]);
|
||||
}
|
||||
}
|
||||
|
||||
return this.createFunction();
|
||||
},
|
||||
|
||||
nextOpcode: function(n) {
|
||||
var opcodes = this.environment.opcodes, opcode = opcodes[this.i + n], name, val;
|
||||
var extraParams, codes;
|
||||
|
||||
if(opcode === 'DECLARE') {
|
||||
name = opcodes[this.i + 1];
|
||||
val = opcodes[this.i + 2];
|
||||
return ['DECLARE', name, val];
|
||||
} else {
|
||||
name = Compiler.DISASSEMBLE_MAP[opcode];
|
||||
|
||||
extraParams = Compiler.multiParamSize(opcode);
|
||||
codes = [];
|
||||
|
||||
for(var j=0; j<extraParams; j++) {
|
||||
codes.push(opcodes[this.i + j + 1 + n]);
|
||||
}
|
||||
|
||||
return [name, codes];
|
||||
}
|
||||
},
|
||||
|
||||
eat: function(opcode) {
|
||||
this.i = this.i + opcode.length;
|
||||
},
|
||||
|
||||
preamble: function() {
|
||||
var out = [];
|
||||
out.push("var buffer = " + this.initializeBuffer() + ", currentContext = context");
|
||||
|
||||
var copies = "helpers = helpers || Handlebars.helpers;";
|
||||
if(this.environment.usePartial) { copies = copies + " partials = partials || Handlebars.partials;"; }
|
||||
out.push(copies);
|
||||
|
||||
// track the last context pushed into place to allow skipping the
|
||||
// getContext opcode when it would be a noop
|
||||
this.lastContext = 0;
|
||||
this.source = out;
|
||||
},
|
||||
|
||||
createFunction: function() {
|
||||
var container = {
|
||||
escapeExpression: Handlebars.Utils.escapeExpression,
|
||||
invokePartial: Handlebars.VM.invokePartial,
|
||||
programs: [],
|
||||
program: function(i, helpers, partials, data) {
|
||||
var programWrapper = this.programs[i];
|
||||
if(data) {
|
||||
return Handlebars.VM.program(this.children[i], helpers, partials, data);
|
||||
} else if(programWrapper) {
|
||||
return programWrapper;
|
||||
} else {
|
||||
programWrapper = this.programs[i] = Handlebars.VM.program(this.children[i], helpers, partials);
|
||||
return programWrapper;
|
||||
}
|
||||
},
|
||||
programWithDepth: Handlebars.VM.programWithDepth,
|
||||
noop: Handlebars.VM.noop
|
||||
};
|
||||
var locals = this.stackVars.concat(this.registers.list);
|
||||
|
||||
if(locals.length > 0) {
|
||||
this.source[0] = this.source[0] + ", " + locals.join(", ");
|
||||
}
|
||||
|
||||
this.source[0] = this.source[0] + ";";
|
||||
|
||||
this.source.push("return buffer;");
|
||||
|
||||
var params = ["Handlebars", "context", "helpers", "partials"];
|
||||
|
||||
if(this.options.data) { params.push("data"); }
|
||||
|
||||
for(var i=0, l=this.environment.depths.list.length; i<l; i++) {
|
||||
params.push("depth" + this.environment.depths.list[i]);
|
||||
}
|
||||
|
||||
if(params.length === 4 && !this.environment.usePartial) { params.pop(); }
|
||||
|
||||
params.push(this.source.join("\n"));
|
||||
|
||||
var fn = Function.apply(this, params);
|
||||
fn.displayName = "Handlebars.js";
|
||||
|
||||
Handlebars.log(Handlebars.logger.DEBUG, fn.toString() + "\n\n");
|
||||
|
||||
container.render = fn;
|
||||
|
||||
container.children = this.environment.children;
|
||||
|
||||
return function(context, options, $depth) {
|
||||
try {
|
||||
options = options || {};
|
||||
var args = [Handlebars, context, options.helpers, options.partials, options.data];
|
||||
var depth = Array.prototype.slice.call(arguments, 2);
|
||||
args = args.concat(depth);
|
||||
return container.render.apply(container, args);
|
||||
} catch(e) {
|
||||
throw e;
|
||||
}
|
||||
};
|
||||
},
|
||||
|
||||
appendContent: function(content) {
|
||||
this.source.push(this.appendToBuffer(this.quotedString(content)));
|
||||
},
|
||||
|
||||
append: function() {
|
||||
var local = this.popStack();
|
||||
this.source.push("if(" + local + " || " + local + " === 0) { " + this.appendToBuffer(local) + " }");
|
||||
},
|
||||
|
||||
appendEscaped: function() {
|
||||
var opcode = this.nextOpcode(1), extra = "";
|
||||
|
||||
if(opcode[0] === 'appendContent') {
|
||||
extra = " + " + this.quotedString(opcode[1][0]);
|
||||
this.eat(opcode);
|
||||
}
|
||||
|
||||
this.source.push(this.appendToBuffer("this.escapeExpression(" + this.popStack() + ")" + extra));
|
||||
},
|
||||
|
||||
getContext: function(depth) {
|
||||
if(this.lastContext !== depth) {
|
||||
this.lastContext = depth;
|
||||
|
||||
if(depth === 0) {
|
||||
this.source.push("currentContext = context;");
|
||||
} else {
|
||||
this.source.push("currentContext = depth" + depth + ";");
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
lookupWithHelpers: function(name) {
|
||||
if(name) {
|
||||
var topStack = this.nextStack();
|
||||
|
||||
var toPush = "if('" + name + "' in helpers) { " + topStack +
|
||||
" = " + this.nameLookup('helpers', name, 'helper') +
|
||||
"; } else { " + topStack + " = " +
|
||||
this.nameLookup('currentContext', name, 'context') +
|
||||
"; }";
|
||||
|
||||
this.source.push(toPush);
|
||||
} else {
|
||||
this.pushStack("currentContext");
|
||||
}
|
||||
},
|
||||
|
||||
lookup: function(name) {
|
||||
var topStack = this.topStack();
|
||||
this.source.push(topStack + " = " + this.nameLookup(topStack, name, 'context') + ";");
|
||||
},
|
||||
|
||||
pushStringParam: function(string) {
|
||||
this.pushStack("currentContext");
|
||||
this.pushString(string);
|
||||
},
|
||||
|
||||
pushString: function(string) {
|
||||
this.pushStack(this.quotedString(string));
|
||||
},
|
||||
|
||||
push: function(name) {
|
||||
this.pushStack(name);
|
||||
},
|
||||
|
||||
invokeMustache: function(paramSize, original) {
|
||||
this.populateParams(paramSize, this.quotedString(original), "{}", null, function(nextStack, helperMissingString, id) {
|
||||
this.source.push("else if(" + id + "=== undefined) { " + nextStack + " = helpers.helperMissing.call(" + helperMissingString + "); }");
|
||||
this.source.push("else { " + nextStack + " = " + id + "; }");
|
||||
});
|
||||
},
|
||||
|
||||
invokeProgram: function(guid, paramSize) {
|
||||
var inverse = this.programExpression(this.inverse);
|
||||
var mainProgram = this.programExpression(guid);
|
||||
|
||||
this.populateParams(paramSize, null, mainProgram, inverse, function(nextStack, helperMissingString, id) {
|
||||
this.source.push("else { " + nextStack + " = helpers.blockHelperMissing.call(" + helperMissingString + "); }");
|
||||
});
|
||||
},
|
||||
|
||||
populateParams: function(paramSize, helperId, program, inverse, fn) {
|
||||
var id = this.popStack(), nextStack;
|
||||
var params = [], param, stringParam;
|
||||
|
||||
var hash = this.popStack();
|
||||
|
||||
this.register('tmp1', program);
|
||||
this.source.push('tmp1.hash = ' + hash + ';');
|
||||
|
||||
if(this.options.stringParams) {
|
||||
this.source.push('tmp1.contexts = [];');
|
||||
}
|
||||
|
||||
for(var i=0; i<paramSize; i++) {
|
||||
param = this.popStack();
|
||||
params.push(param);
|
||||
|
||||
if(this.options.stringParams) {
|
||||
this.source.push('tmp1.contexts.push(' + this.popStack() + ');');
|
||||
}
|
||||
}
|
||||
|
||||
if(inverse) {
|
||||
this.source.push('tmp1.fn = tmp1;');
|
||||
this.source.push('tmp1.inverse = ' + inverse + ';');
|
||||
}
|
||||
|
||||
if(this.options.data) {
|
||||
this.source.push('tmp1.data = data;');
|
||||
}
|
||||
|
||||
params.push('tmp1');
|
||||
|
||||
// TODO: This is legacy behavior. Deprecate and remove.
|
||||
if(inverse) {
|
||||
params.push(inverse);
|
||||
}
|
||||
|
||||
this.populateCall(params, id, helperId || id, fn);
|
||||
},
|
||||
|
||||
populateCall: function(params, id, helperId, fn) {
|
||||
var paramString = ["context"].concat(params).join(", ");
|
||||
var helperMissingString = ["context"].concat(helperId).concat(params).join(", ");
|
||||
|
||||
nextStack = this.nextStack();
|
||||
|
||||
this.source.push("if(typeof " + id + " === 'function') { " + nextStack + " = " + id + ".call(" + paramString + "); }");
|
||||
fn.call(this, nextStack, helperMissingString, id);
|
||||
},
|
||||
|
||||
invokeInverse: function(guid) {
|
||||
var program = this.programExpression(guid);
|
||||
|
||||
var blockMissingParams = ["context", this.topStack(), "this.noop", program];
|
||||
this.pushStack("helpers.blockHelperMissing.call(" + blockMissingParams.join(", ") + ")");
|
||||
},
|
||||
|
||||
invokePartial: function(context) {
|
||||
this.pushStack("this.invokePartial(" + this.nameLookup('partials', context, 'partial') + ", '" + context + "', " + this.popStack() + ", helpers, partials);");
|
||||
},
|
||||
|
||||
assignToHash: function(key) {
|
||||
var value = this.popStack();
|
||||
var hash = this.topStack();
|
||||
|
||||
this.source.push(hash + "['" + key + "'] = " + value + ";");
|
||||
},
|
||||
|
||||
// HELPERS
|
||||
|
||||
compiler: JavaScriptCompiler,
|
||||
|
||||
compileChildren: function(environment, options) {
|
||||
var children = environment.children, child, compiler;
|
||||
var compiled = [];
|
||||
|
||||
for(var i=0, l=children.length; i<l; i++) {
|
||||
child = children[i];
|
||||
compiler = new this.compiler();
|
||||
|
||||
compiled[i] = compiler.compile(child, options);
|
||||
}
|
||||
|
||||
environment.rawChildren = children;
|
||||
environment.children = compiled;
|
||||
},
|
||||
|
||||
programExpression: function(guid) {
|
||||
if(guid == null) { return "this.noop"; }
|
||||
|
||||
var programParams = [guid, "helpers", "partials"];
|
||||
|
||||
var depths = this.environment.rawChildren[guid].depths.list;
|
||||
|
||||
if(this.options.data) { programParams.push("data"); }
|
||||
|
||||
for(var i=0, l = depths.length; i<l; i++) {
|
||||
depth = depths[i];
|
||||
|
||||
if(depth === 1) { programParams.push("context"); }
|
||||
else { programParams.push("depth" + (depth - 1)); }
|
||||
}
|
||||
|
||||
if(!this.environment.usePartial) {
|
||||
if(programParams[3]) {
|
||||
programParams[2] = "null";
|
||||
} else {
|
||||
programParams.pop();
|
||||
}
|
||||
}
|
||||
|
||||
if(depths.length === 0) {
|
||||
return "this.program(" + programParams.join(", ") + ")";
|
||||
} else {
|
||||
programParams[0] = "this.children[" + guid + "]";
|
||||
return "this.programWithDepth(" + programParams.join(", ") + ")";
|
||||
}
|
||||
},
|
||||
|
||||
register: function(name, val) {
|
||||
this.useRegister(name);
|
||||
this.source.push(name + " = " + val + ";");
|
||||
},
|
||||
|
||||
useRegister: function(name) {
|
||||
if(!this.registers[name]) {
|
||||
this.registers[name] = true;
|
||||
this.registers.list.push(name);
|
||||
}
|
||||
},
|
||||
|
||||
pushStack: function(item) {
|
||||
this.source.push(this.nextStack() + " = " + item + ";");
|
||||
return "stack" + this.stackSlot;
|
||||
},
|
||||
|
||||
nextStack: function() {
|
||||
this.stackSlot++;
|
||||
if(this.stackSlot > this.stackVars.length) { this.stackVars.push("stack" + this.stackSlot); }
|
||||
return "stack" + this.stackSlot;
|
||||
},
|
||||
|
||||
popStack: function() {
|
||||
return "stack" + this.stackSlot--;
|
||||
},
|
||||
|
||||
topStack: function() {
|
||||
return "stack" + this.stackSlot;
|
||||
},
|
||||
|
||||
quotedString: function(str) {
|
||||
return '"' + str
|
||||
.replace(/\\/g, '\\\\')
|
||||
.replace(/"/g, '\\"')
|
||||
.replace(/\n/g, '\\n')
|
||||
.replace(/\r/g, '\\r') + '"';
|
||||
}
|
||||
};
|
||||
|
||||
var reservedWords = ("break case catch continue default delete do else finally " +
|
||||
"for function if in instanceof new return switch this throw " +
|
||||
"try typeof var void while with null true false").split(" ");
|
||||
|
||||
compilerWords = JavaScriptCompiler.RESERVED_WORDS = {};
|
||||
|
||||
for(var i=0, l=reservedWords.length; i<l; i++) {
|
||||
compilerWords[reservedWords[i]] = true;
|
||||
}
|
||||
|
||||
})(Handlebars.Compiler, Handlebars.JavaScriptCompiler);
|
||||
|
||||
Handlebars.VM = {
|
||||
programWithDepth: function(fn, passedHelpers, passedPartials, passedData, $depth) {
|
||||
var args = Array.prototype.slice.call(arguments, 4);
|
||||
|
||||
return function(context, helpers, partials, data) {
|
||||
var options = {
|
||||
helpers: helpers || passedHelpers,
|
||||
partials: partials || passedPartials,
|
||||
data: data || passedData
|
||||
};
|
||||
|
||||
return fn.apply(this, [context, options].concat(args));
|
||||
};
|
||||
},
|
||||
program: function(fn, helpers, partials, data) {
|
||||
return function(context, h2, p2, d2) {
|
||||
return fn(context, {helpers: h2 || helpers, partials: p2 || partials, data: d2 || data});
|
||||
};
|
||||
},
|
||||
noop: function() { return ""; },
|
||||
compile: function(string, options) {
|
||||
var ast = Handlebars.parse(string);
|
||||
var environment = new Handlebars.Compiler().compile(ast, options);
|
||||
return new Handlebars.JavaScriptCompiler().compile(environment, options);
|
||||
},
|
||||
invokePartial: function(partial, name, context, helpers, partials) {
|
||||
if(partial === undefined) {
|
||||
throw new Handlebars.Exception("The partial " + name + " could not be found");
|
||||
} else if(partial instanceof Function) {
|
||||
return partial(context, {helpers: helpers, partials: partials});
|
||||
} else {
|
||||
partials[name] = Handlebars.VM.compile(partial);
|
||||
return partials[name](context, {helpers: helpers, partials: partials});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Handlebars.compile = Handlebars.VM.compile;
|
||||
// END(BROWSER)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
var Handlebars = require("handlebars");
|
||||
var Handlebars = require('./base');
|
||||
|
||||
// BEGIN(BROWSER)
|
||||
(function() {
|
||||
@@ -11,12 +11,26 @@ var Handlebars = require("handlebars");
|
||||
if(inverse) { this.inverse = new Handlebars.AST.ProgramNode(inverse); }
|
||||
};
|
||||
|
||||
Handlebars.AST.MustacheNode = function(params, hash, unescaped) {
|
||||
Handlebars.AST.MustacheNode = function(rawParams, hash, unescaped) {
|
||||
this.type = "mustache";
|
||||
this.id = params[0];
|
||||
this.params = params.slice(1);
|
||||
this.hash = hash;
|
||||
this.escaped = !unescaped;
|
||||
this.hash = hash;
|
||||
|
||||
var id = this.id = rawParams[0];
|
||||
var params = this.params = rawParams.slice(1);
|
||||
|
||||
// a mustache is an eligible helper if:
|
||||
// * its id is simple (a single part, not `this` or `..`)
|
||||
var eligibleHelper = this.eligibleHelper = id.isSimple;
|
||||
|
||||
// a mustache is definitely a helper if:
|
||||
// * it is an eligible helper, and
|
||||
// * it has at least one parameter or hash segment
|
||||
this.isHelper = eligibleHelper && (params.length || hash);
|
||||
|
||||
// if a mustache is an eligible helper but not a definite
|
||||
// helper, it is ambiguous, and will be resolved in a later
|
||||
// pass or at runtime.
|
||||
};
|
||||
|
||||
Handlebars.AST.PartialNode = function(id, context) {
|
||||
@@ -34,18 +48,16 @@ var Handlebars = require("handlebars");
|
||||
}
|
||||
};
|
||||
|
||||
Handlebars.AST.BlockNode = function(mustache, program, close) {
|
||||
Handlebars.AST.BlockNode = function(mustache, program, inverse, close) {
|
||||
verifyMatch(mustache.id, close);
|
||||
this.type = "block";
|
||||
this.mustache = mustache;
|
||||
this.program = program;
|
||||
};
|
||||
this.inverse = inverse;
|
||||
|
||||
Handlebars.AST.InverseNode = function(mustache, program, close) {
|
||||
verifyMatch(mustache.id, close);
|
||||
this.type = "inverse";
|
||||
this.mustache = mustache;
|
||||
this.program = program;
|
||||
if (this.inverse && !this.program) {
|
||||
this.isInverse = true;
|
||||
}
|
||||
};
|
||||
|
||||
Handlebars.AST.ContentNode = function(string) {
|
||||
@@ -68,14 +80,22 @@ var Handlebars = require("handlebars");
|
||||
var part = parts[i];
|
||||
|
||||
if(part === "..") { depth++; }
|
||||
else if(part === "." || part === "this") { continue; }
|
||||
else if(part === "." || part === "this") { this.isScoped = true; }
|
||||
else { dig.push(part); }
|
||||
}
|
||||
|
||||
this.parts = dig;
|
||||
this.string = dig.join('.');
|
||||
this.depth = depth;
|
||||
this.isSimple = (dig.length === 1) && (depth === 0);
|
||||
|
||||
// an ID is simple if it only has one part, and that part is not
|
||||
// `..` or `this`.
|
||||
this.isSimple = parts.length === 1 && !this.isScoped && depth === 0;
|
||||
};
|
||||
|
||||
Handlebars.AST.DataNode = function(id) {
|
||||
this.type = "DATA";
|
||||
this.id = id;
|
||||
};
|
||||
|
||||
Handlebars.AST.StringNode = function(string) {
|
||||
@@ -83,6 +103,16 @@ var Handlebars = require("handlebars");
|
||||
this.string = string;
|
||||
};
|
||||
|
||||
Handlebars.AST.IntegerNode = function(integer) {
|
||||
this.type = "INTEGER";
|
||||
this.integer = integer;
|
||||
};
|
||||
|
||||
Handlebars.AST.BooleanNode = function(bool) {
|
||||
this.type = "BOOLEAN";
|
||||
this.bool = bool;
|
||||
};
|
||||
|
||||
Handlebars.AST.CommentNode = function(comment) {
|
||||
this.type = "comment";
|
||||
this.comment = comment;
|
||||
@@ -0,0 +1,27 @@
|
||||
var handlebars = require("./parser").parser;
|
||||
var Handlebars = require("../base");
|
||||
|
||||
// BEGIN(BROWSER)
|
||||
Handlebars.Parser = handlebars;
|
||||
|
||||
Handlebars.parse = function(string) {
|
||||
Handlebars.Parser.yy = Handlebars.AST;
|
||||
return Handlebars.Parser.parse(string);
|
||||
};
|
||||
|
||||
Handlebars.print = function(ast) {
|
||||
return new Handlebars.PrintVisitor().accept(ast);
|
||||
};
|
||||
|
||||
Handlebars.logger = {
|
||||
DEBUG: 0, INFO: 1, WARN: 2, ERROR: 3, level: 3,
|
||||
|
||||
// override in the host environment
|
||||
log: function(level, str) {}
|
||||
};
|
||||
|
||||
Handlebars.log = function(level, str) { Handlebars.logger.log(level, str); };
|
||||
|
||||
// END(BROWSER)
|
||||
|
||||
module.exports = Handlebars;
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,7 @@
|
||||
// Each of these module will augment the Handlebars object as it loads. No need to perform addition operations
|
||||
module.exports = require("./base");
|
||||
require("./visitor");
|
||||
require("./printer");
|
||||
|
||||
require("./ast");
|
||||
require("./compiler");
|
||||
@@ -1,5 +1,4 @@
|
||||
var Handlebars = require("handlebars");
|
||||
require("handlebars/visitor");
|
||||
var Handlebars = require("./base");
|
||||
|
||||
// BEGIN(BROWSER)
|
||||
Handlebars.PrintVisitor = function() { this.padding = 0; };
|
||||
@@ -19,31 +18,17 @@ Handlebars.PrintVisitor.prototype.pad = function(string, newline) {
|
||||
};
|
||||
|
||||
Handlebars.PrintVisitor.prototype.program = function(program) {
|
||||
var out = this.pad("PROGRAM:"),
|
||||
var out = "",
|
||||
statements = program.statements,
|
||||
inverse = program.inverse,
|
||||
i, l;
|
||||
|
||||
this.padding++;
|
||||
|
||||
for(i=0, l=statements.length; i<l; i++) {
|
||||
out = out + this.accept(statements[i]);
|
||||
}
|
||||
|
||||
this.padding--;
|
||||
|
||||
if(inverse) {
|
||||
out = out + this.pad("{{^}}");
|
||||
|
||||
this.padding++;
|
||||
|
||||
for(i=0, l=inverse.statements.length; i<l; i++) {
|
||||
out = out + this.accept(inverse.statements[i]);
|
||||
}
|
||||
}
|
||||
|
||||
this.padding--;
|
||||
|
||||
return out;
|
||||
};
|
||||
|
||||
@@ -53,25 +38,25 @@ Handlebars.PrintVisitor.prototype.block = function(block) {
|
||||
out = out + this.pad("BLOCK:");
|
||||
this.padding++;
|
||||
out = out + this.accept(block.mustache);
|
||||
out = out + this.accept(block.program);
|
||||
if (block.program) {
|
||||
out = out + this.pad("PROGRAM:");
|
||||
this.padding++;
|
||||
out = out + this.accept(block.program);
|
||||
this.padding--;
|
||||
}
|
||||
if (block.inverse) {
|
||||
if (block.program) { this.padding++; }
|
||||
out = out + this.pad("{{^}}");
|
||||
this.padding++;
|
||||
out = out + this.accept(block.inverse);
|
||||
this.padding--;
|
||||
if (block.program) { this.padding--; }
|
||||
}
|
||||
this.padding--;
|
||||
|
||||
return out;
|
||||
};
|
||||
|
||||
Handlebars.PrintVisitor.prototype.inverse = function(block) {
|
||||
var out = "";
|
||||
|
||||
out = out + this.pad("INVERSE:");
|
||||
this.padding++;
|
||||
out = out + this.accept(block.mustache);
|
||||
out = out + this.accept(block.program);
|
||||
this.padding--;
|
||||
|
||||
return out;
|
||||
};
|
||||
|
||||
|
||||
Handlebars.PrintVisitor.prototype.mustache = function(mustache) {
|
||||
var params = mustache.params, paramStrings = [], hash;
|
||||
|
||||
@@ -109,6 +94,14 @@ Handlebars.PrintVisitor.prototype.STRING = function(string) {
|
||||
return '"' + string.string + '"';
|
||||
};
|
||||
|
||||
Handlebars.PrintVisitor.prototype.INTEGER = function(integer) {
|
||||
return "INTEGER{" + integer.integer + "}";
|
||||
};
|
||||
|
||||
Handlebars.PrintVisitor.prototype.BOOLEAN = function(bool) {
|
||||
return "BOOLEAN{" + bool.bool + "}";
|
||||
};
|
||||
|
||||
Handlebars.PrintVisitor.prototype.ID = function(id) {
|
||||
var path = id.parts.join("/");
|
||||
if(id.parts.length > 1) {
|
||||
@@ -118,6 +111,10 @@ Handlebars.PrintVisitor.prototype.ID = function(id) {
|
||||
}
|
||||
};
|
||||
|
||||
Handlebars.PrintVisitor.prototype.DATA = function(data) {
|
||||
return "@" + data.id;
|
||||
};
|
||||
|
||||
Handlebars.PrintVisitor.prototype.content = function(content) {
|
||||
return this.pad("CONTENT[ '" + content.string + "' ]");
|
||||
};
|
||||
@@ -1,4 +1,4 @@
|
||||
var Handlebars = require("handlebars");
|
||||
var Handlebars = require("./base");
|
||||
|
||||
// BEGIN(BROWSER)
|
||||
|
||||
@@ -1,29 +0,0 @@
|
||||
var Handlebars = require("handlebars");
|
||||
|
||||
// BEGIN(BROWSER)
|
||||
(function() {
|
||||
var classes = ["Lexer", "PrintVisitor", "Context", "Runtime", "Exception"];
|
||||
var prop;
|
||||
|
||||
for(var i=0, l=classes.length; i<l; i++) {
|
||||
var className = classes[i], klass = Handlebars[className];
|
||||
klass.displayName = "new Handlebars." + className;
|
||||
|
||||
for(prop in klass) {
|
||||
if(klass.hasOwnProperty(prop)) {
|
||||
klass[prop].displayName = "Handlebars." + className + "#" + prop;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for(prop in Handlebars.Utils) {
|
||||
if(Handlebars.Utils.hasOwnProperty(prop)) {
|
||||
Handlebars.Utils[prop].displayName = "Handlebars.Utils." + prop;
|
||||
}
|
||||
}
|
||||
|
||||
Handlebars.parse.displayName = "Handlebars.parse";
|
||||
Handlebars.print.displayName = "Handlebars.print";
|
||||
Handlebars.compile.displayName = "Handlebars.compile";
|
||||
})();
|
||||
// END(BROWSER)
|
||||
@@ -0,0 +1,68 @@
|
||||
var Handlebars = require("./base");
|
||||
|
||||
// BEGIN(BROWSER)
|
||||
Handlebars.VM = {
|
||||
template: function(templateSpec) {
|
||||
// Just add water
|
||||
var container = {
|
||||
escapeExpression: Handlebars.Utils.escapeExpression,
|
||||
invokePartial: Handlebars.VM.invokePartial,
|
||||
programs: [],
|
||||
program: function(i, fn, data) {
|
||||
var programWrapper = this.programs[i];
|
||||
if(data) {
|
||||
return Handlebars.VM.program(fn, data);
|
||||
} else if(programWrapper) {
|
||||
return programWrapper;
|
||||
} else {
|
||||
programWrapper = this.programs[i] = Handlebars.VM.program(fn);
|
||||
return programWrapper;
|
||||
}
|
||||
},
|
||||
programWithDepth: Handlebars.VM.programWithDepth,
|
||||
noop: Handlebars.VM.noop
|
||||
};
|
||||
|
||||
return function(context, options) {
|
||||
options = options || {};
|
||||
return templateSpec.call(container, Handlebars, context, options.helpers, options.partials, options.data);
|
||||
};
|
||||
},
|
||||
|
||||
programWithDepth: function(fn, data, $depth) {
|
||||
var args = Array.prototype.slice.call(arguments, 2);
|
||||
|
||||
return function(context, options) {
|
||||
options = options || {};
|
||||
|
||||
return fn.apply(this, [context, options.data || data].concat(args));
|
||||
};
|
||||
},
|
||||
program: function(fn, data) {
|
||||
return function(context, options) {
|
||||
options = options || {};
|
||||
|
||||
return fn(context, options.data || data);
|
||||
};
|
||||
},
|
||||
noop: function() { return ""; },
|
||||
invokePartial: function(partial, name, context, helpers, partials, data) {
|
||||
var options = { helpers: helpers, partials: partials, data: data };
|
||||
|
||||
if(partial === undefined) {
|
||||
throw new Handlebars.Exception("The partial " + name + " could not be found");
|
||||
} else if(partial instanceof Function) {
|
||||
return partial(context, options);
|
||||
} else if (!Handlebars.compile) {
|
||||
throw new Handlebars.Exception("The partial " + name + " could not be compiled when running in runtime-only mode");
|
||||
} else {
|
||||
partials[name] = Handlebars.compile(partial, {data: data !== undefined});
|
||||
return partials[name](context, options);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Handlebars.template = Handlebars.VM.template;
|
||||
|
||||
// END(BROWSER)
|
||||
|
||||
+17
-6
@@ -1,9 +1,16 @@
|
||||
var Handlebars = require("handlebars");
|
||||
var Handlebars = require("./base");
|
||||
|
||||
// BEGIN(BROWSER)
|
||||
Handlebars.Exception = function(message) {
|
||||
this.message = message;
|
||||
var tmp = Error.prototype.constructor.apply(this, arguments);
|
||||
|
||||
for (var p in tmp) {
|
||||
if (tmp.hasOwnProperty(p)) { this[p] = tmp[p]; }
|
||||
}
|
||||
|
||||
this.message = tmp.message;
|
||||
};
|
||||
Handlebars.Exception.prototype = new Error();
|
||||
|
||||
// Build out our basic SafeString type
|
||||
Handlebars.SafeString = function(string) {
|
||||
@@ -15,15 +22,19 @@ Handlebars.SafeString.prototype.toString = function() {
|
||||
|
||||
(function() {
|
||||
var escape = {
|
||||
"&": "&",
|
||||
"<": "<",
|
||||
">": ">"
|
||||
">": ">",
|
||||
'"': """,
|
||||
"'": "'",
|
||||
"`": "`"
|
||||
};
|
||||
|
||||
var badChars = /&(?!\w+;)|[<>]/g;
|
||||
var possible = /[&<>]/;
|
||||
var badChars = /[&<>"'`]/g;
|
||||
var possible = /[&<>"'`]/;
|
||||
|
||||
var escapeChar = function(chr) {
|
||||
return escape[chr] || "&"
|
||||
return escape[chr] || "&";
|
||||
};
|
||||
|
||||
Handlebars.Utils = {
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
{
|
||||
"name": "handlebars",
|
||||
"description": "Extension of the Mustache logicless template language",
|
||||
"version": "1.0.0-rc.1",
|
||||
"homepage": "http://www.handlebarsjs.com/",
|
||||
"keywords": [
|
||||
"handlebars mustache template html"
|
||||
],
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git://github.com/wycats/handlebars.js.git"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.4.7"
|
||||
},
|
||||
"dependencies": {
|
||||
"optimist": "~0.3",
|
||||
"uglify-js": "~1.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"mocha": "*"
|
||||
},
|
||||
"main": "lib/handlebars.js",
|
||||
"bin": {
|
||||
"handlebars": "bin/handlebars"
|
||||
},
|
||||
"scripts": {
|
||||
"test": "node_modules/.bin/mocha -u qunit spec/qunit_spec.js"
|
||||
},
|
||||
"optionalDependencies": {}
|
||||
}
|
||||
@@ -39,11 +39,11 @@ Module.new do
|
||||
end
|
||||
end
|
||||
|
||||
js_context["p"] = proc do |str|
|
||||
js_context["p"] = proc do |this, str|
|
||||
p str
|
||||
end
|
||||
|
||||
js_context["ok"] = proc do |ok, message|
|
||||
js_context["ok"] = proc do |this, ok, message|
|
||||
js_context["$$RSPEC1$$"] = ok
|
||||
|
||||
result = js_context.eval("!!$$RSPEC1$$")
|
||||
@@ -58,13 +58,14 @@ Module.new do
|
||||
assert result, message
|
||||
end
|
||||
|
||||
js_context["equals"] = proc do |first, second, message|
|
||||
js_context["equals"] = proc do |this, first, second, message|
|
||||
js_context["$$RSPEC1$$"] = first
|
||||
js_context["$$RSPEC2$$"] = second
|
||||
|
||||
result = js_context.eval("$$RSPEC1$$ == $$RSPEC2$$")
|
||||
|
||||
message ||= "#{first} did not == #{second}"
|
||||
additional_message = "#{first.inspect} did not == #{second.inspect}"
|
||||
message = message ? "#{message} (#{additional_message})" : additional_message
|
||||
|
||||
unless result
|
||||
backtrace = js_backtrace(js_context)
|
||||
@@ -76,11 +77,11 @@ Module.new do
|
||||
|
||||
js_context["equal"] = js_context["equals"]
|
||||
|
||||
js_context["module"] = proc do |name|
|
||||
js_context["suite"] = proc do |this, name|
|
||||
test_context.module(name)
|
||||
end
|
||||
|
||||
js_context["test"] = proc do |name, function|
|
||||
js_context["test"] = proc do |this, name, function|
|
||||
test_context.test(name, function)
|
||||
end
|
||||
|
||||
|
||||
+88
-24
@@ -3,11 +3,13 @@ require "spec_helper"
|
||||
describe "Parser" do
|
||||
let(:handlebars) { @context["Handlebars"] }
|
||||
|
||||
def program(&block)
|
||||
before(:all) do
|
||||
@compiles = true
|
||||
end
|
||||
|
||||
def root(&block)
|
||||
ASTBuilder.build do
|
||||
program do
|
||||
instance_eval(&block)
|
||||
end
|
||||
instance_eval(&block)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -92,6 +94,14 @@ describe "Parser" do
|
||||
string.inspect
|
||||
end
|
||||
|
||||
def integer(string)
|
||||
"INTEGER{#{string}}"
|
||||
end
|
||||
|
||||
def boolean(string)
|
||||
"BOOLEAN{#{string}}"
|
||||
end
|
||||
|
||||
def hash(*pairs)
|
||||
"HASH{" + pairs.map {|k,v| "#{k}=#{v}" }.join(", ") + "}"
|
||||
end
|
||||
@@ -100,82 +110,135 @@ describe "Parser" do
|
||||
"ID:#{id}"
|
||||
end
|
||||
|
||||
def data(id)
|
||||
"@#{id}"
|
||||
end
|
||||
|
||||
def path(*parts)
|
||||
"PATH:#{parts.join("/")}"
|
||||
end
|
||||
end
|
||||
|
||||
it "parses simple mustaches" do
|
||||
ast_for("{{foo}}").should == program { mustache id("foo") }
|
||||
ast_for("{{foo}}").should == root { mustache id("foo") }
|
||||
end
|
||||
|
||||
it "parses simple mustaches with data" do
|
||||
ast_for("{{@foo}}").should == root { mustache data("foo") }
|
||||
end
|
||||
|
||||
it "parses mustaches with paths" do
|
||||
ast_for("{{foo/bar}}").should == program { mustache path("foo", "bar") }
|
||||
ast_for("{{foo/bar}}").should == root { mustache path("foo", "bar") }
|
||||
end
|
||||
|
||||
it "parses mustaches with this/foo" do
|
||||
ast_for("{{this/foo}}").should == program { mustache id("foo") }
|
||||
ast_for("{{this/foo}}").should == root { mustache id("foo") }
|
||||
end
|
||||
|
||||
it "parses mustaches with - in a path" do
|
||||
ast_for("{{foo-bar}}").should == program { mustache id("foo-bar") }
|
||||
ast_for("{{foo-bar}}").should == root { mustache id("foo-bar") }
|
||||
end
|
||||
|
||||
it "parses mustaches with parameters" do
|
||||
ast_for("{{foo bar}}").should == program { mustache id("foo"), [id("bar")] }
|
||||
ast_for("{{foo bar}}").should == root { mustache id("foo"), [id("bar")] }
|
||||
end
|
||||
|
||||
it "parses mustaches with hash arguments" do
|
||||
ast_for("{{foo bar=baz}}").should == program do
|
||||
mustache id("foo"), [], hash(["bar", "ID:baz"])
|
||||
ast_for("{{foo bar=baz}}").should == root do
|
||||
mustache id("foo"), [], hash(["bar", id("baz")])
|
||||
end
|
||||
|
||||
ast_for("{{foo bar=baz bat=bam}}").should == program do
|
||||
ast_for("{{foo bar=1}}").should == root do
|
||||
mustache id("foo"), [], hash(["bar", integer("1")])
|
||||
end
|
||||
|
||||
ast_for("{{foo bar=true}}").should == root do
|
||||
mustache id("foo"), [], hash(["bar", boolean("true")])
|
||||
end
|
||||
|
||||
ast_for("{{foo bar=false}}").should == root do
|
||||
mustache id("foo"), [], hash(["bar", boolean("false")])
|
||||
end
|
||||
|
||||
ast_for("{{foo bar=@baz}}").should == root do
|
||||
mustache id("foo"), [], hash(["bar", data("baz")])
|
||||
end
|
||||
|
||||
ast_for("{{foo bar=baz bat=bam}}").should == root do
|
||||
mustache id("foo"), [], hash(["bar", "ID:baz"], ["bat", "ID:bam"])
|
||||
end
|
||||
|
||||
ast_for("{{foo bar=baz bat=\"bam\"}}").should == program do
|
||||
ast_for("{{foo bar=baz bat=\"bam\"}}").should == root do
|
||||
mustache id("foo"), [], hash(["bar", "ID:baz"], ["bat", "\"bam\""])
|
||||
end
|
||||
|
||||
ast_for("{{foo omg bar=baz bat=\"bam\"}}").should == program do
|
||||
ast_for("{{foo bat='bam'}}").should == root do
|
||||
mustache id("foo"), [], hash(["bat", "\"bam\""])
|
||||
end
|
||||
|
||||
ast_for("{{foo omg bar=baz bat=\"bam\"}}").should == root do
|
||||
mustache id("foo"), [id("omg")], hash(["bar", id("baz")], ["bat", string("bam")])
|
||||
end
|
||||
|
||||
ast_for("{{foo omg bar=baz bat=\"bam\" baz=1}}").should == root do
|
||||
mustache id("foo"), [id("omg")], hash(["bar", id("baz")], ["bat", string("bam")], ["baz", integer("1")])
|
||||
end
|
||||
|
||||
ast_for("{{foo omg bar=baz bat=\"bam\" baz=true}}").should == root do
|
||||
mustache id("foo"), [id("omg")], hash(["bar", id("baz")], ["bat", string("bam")], ["baz", boolean("true")])
|
||||
end
|
||||
|
||||
ast_for("{{foo omg bar=baz bat=\"bam\" baz=false}}").should == root do
|
||||
mustache id("foo"), [id("omg")], hash(["bar", id("baz")], ["bat", string("bam")], ["baz", boolean("false")])
|
||||
end
|
||||
end
|
||||
|
||||
it "parses mustaches with string parameters" do
|
||||
ast_for("{{foo bar \"baz\" }}").should == program { mustache id("foo"), [id("bar"), string("baz")] }
|
||||
ast_for("{{foo bar \"baz\" }}").should == root { mustache id("foo"), [id("bar"), string("baz")] }
|
||||
end
|
||||
|
||||
it "parses mustaches with INTEGER parameters" do
|
||||
ast_for("{{foo 1}}").should == root { mustache id("foo"), [integer("1")] }
|
||||
end
|
||||
|
||||
it "parses mustaches with BOOLEAN parameters" do
|
||||
ast_for("{{foo true}}").should == root { mustache id("foo"), [boolean("true")] }
|
||||
ast_for("{{foo false}}").should == root { mustache id("foo"), [boolean("false")] }
|
||||
end
|
||||
|
||||
it "parses mutaches with DATA parameters" do
|
||||
ast_for("{{foo @bar}}").should == root { mustache id("foo"), [data("bar")] }
|
||||
end
|
||||
|
||||
it "parses contents followed by a mustache" do
|
||||
ast_for("foo bar {{baz}}").should == program do
|
||||
ast_for("foo bar {{baz}}").should == root do
|
||||
content "foo bar "
|
||||
mustache id("baz")
|
||||
end
|
||||
end
|
||||
|
||||
it "parses a partial" do
|
||||
ast_for("{{> foo }}").should == program { partial id("foo") }
|
||||
ast_for("{{> foo }}").should == root { partial id("foo") }
|
||||
end
|
||||
|
||||
it "parses a partial with context" do
|
||||
ast_for("{{> foo bar}}").should == program { partial id("foo"), id("bar") }
|
||||
ast_for("{{> foo bar}}").should == root { partial id("foo"), id("bar") }
|
||||
end
|
||||
|
||||
it "parses a comment" do
|
||||
ast_for("{{! this is a comment }}").should == program do
|
||||
ast_for("{{! this is a comment }}").should == root do
|
||||
comment " this is a comment "
|
||||
end
|
||||
end
|
||||
|
||||
it "parses a multi-line comment" do
|
||||
ast_for("{{!\nthis is a multi-line comment\n}}").should == program do
|
||||
ast_for("{{!\nthis is a multi-line comment\n}}").should == root do
|
||||
multiline_comment "this is a multi-line comment"
|
||||
end
|
||||
end
|
||||
|
||||
it "parses an inverse section" do
|
||||
ast_for("{{#foo}} bar {{^}} baz {{/foo}}").should == program do
|
||||
ast_for("{{#foo}} bar {{^}} baz {{/foo}}").should == root do
|
||||
block do
|
||||
mustache id("foo")
|
||||
|
||||
@@ -191,11 +254,11 @@ describe "Parser" do
|
||||
end
|
||||
|
||||
it "parses a standalone inverse section" do
|
||||
ast_for("{{^foo}}bar{{/foo}}").should == program do
|
||||
inverted_block do
|
||||
ast_for("{{^foo}}bar{{/foo}}").should == root do
|
||||
block do
|
||||
mustache id("foo")
|
||||
|
||||
program do
|
||||
inverse do
|
||||
content "bar"
|
||||
end
|
||||
end
|
||||
@@ -205,6 +268,7 @@ describe "Parser" do
|
||||
it "raises if there's a Parse error" do
|
||||
lambda { ast_for("{{foo}") }.should raise_error(V8::JSError, /Parse error on line 1/)
|
||||
lambda { ast_for("{{foo &}}")}.should raise_error(V8::JSError, /Parse error on line 1/)
|
||||
lambda { ast_for("{{#goodbyes}}{{/hellos}}") }.should raise_error(V8::JSError, /goodbyes doesn't match hellos/)
|
||||
end
|
||||
|
||||
it "knows how to report the correct line number in errors" do
|
||||
|
||||
+612
-198
File diff suppressed because it is too large
Load Diff
+72
-21
@@ -44,24 +44,18 @@ module Handlebars
|
||||
("\n" * prelines) + ret
|
||||
end
|
||||
|
||||
def self.js_load(file)
|
||||
str = File.read(file)
|
||||
CONTEXT.eval(remove_exports(str), file)
|
||||
end
|
||||
|
||||
CONTEXT = V8::Context.new
|
||||
CONTEXT.instance_eval do |context|
|
||||
def self.load_helpers(context)
|
||||
context["exports"] = nil
|
||||
|
||||
context["p"] = proc do |val|
|
||||
context["p"] = proc do |this, val|
|
||||
p val if ENV["DEBUG_JS"]
|
||||
end
|
||||
|
||||
context["puts"] = proc do |val|
|
||||
context["puts"] = proc do |this, val|
|
||||
puts val if ENV["DEBUG_JS"]
|
||||
end
|
||||
|
||||
context["puts_node"] = proc do |val|
|
||||
context["puts_node"] = proc do |this, val|
|
||||
puts context["Handlebars"]["PrintVisitor"].new.accept(val)
|
||||
puts
|
||||
end
|
||||
@@ -71,19 +65,75 @@ module Handlebars
|
||||
puts Handlebars::Spec.js_backtrace(context)
|
||||
puts
|
||||
end
|
||||
end
|
||||
|
||||
Handlebars::Spec.js_load('lib/handlebars/parser.js')
|
||||
Handlebars::Spec.js_load('lib/handlebars/base.js');
|
||||
Handlebars::Spec.js_load('lib/handlebars/ast.js');
|
||||
Handlebars::Spec.js_load('lib/handlebars/visitor.js');
|
||||
Handlebars::Spec.js_load('lib/handlebars/printer.js')
|
||||
Handlebars::Spec.js_load('lib/handlebars/utils.js')
|
||||
Handlebars::Spec.js_load('lib/handlebars/compiler.js')
|
||||
Handlebars::Spec.js_load('lib/handlebars.js')
|
||||
def self.js_load(context, file)
|
||||
str = File.read(file)
|
||||
context.eval(remove_exports(str), file)
|
||||
end
|
||||
|
||||
CONTEXT = V8::Context.new
|
||||
CONTEXT.instance_eval do |context|
|
||||
Handlebars::Spec.load_helpers(context);
|
||||
|
||||
Handlebars::Spec.js_load(context, 'lib/handlebars/base.js');
|
||||
Handlebars::Spec.js_load(context, 'lib/handlebars/utils.js');
|
||||
Handlebars::Spec.js_load(context, 'lib/handlebars/runtime.js');
|
||||
|
||||
context["CompilerContext"] = {}
|
||||
CompilerContext = context["CompilerContext"]
|
||||
CompilerContext["compile"] = proc do |this, *args|
|
||||
template, options = args[0], args[1] || nil
|
||||
templateSpec = COMPILE_CONTEXT["Handlebars"]["precompile"].call(template, options);
|
||||
context["Handlebars"]["template"].call(context.eval("(#{templateSpec})"));
|
||||
end
|
||||
CompilerContext["compileWithPartial"] = proc do |this, *args|
|
||||
template, options = args[0], args[1] || nil
|
||||
FULL_CONTEXT["Handlebars"]["compile"].call(template, options);
|
||||
end
|
||||
end
|
||||
|
||||
COMPILE_CONTEXT = V8::Context.new
|
||||
COMPILE_CONTEXT.instance_eval do |context|
|
||||
Handlebars::Spec.load_helpers(context);
|
||||
|
||||
Handlebars::Spec.js_load(context, 'lib/handlebars/base.js');
|
||||
Handlebars::Spec.js_load(context, 'lib/handlebars/utils.js');
|
||||
Handlebars::Spec.js_load(context, 'lib/handlebars/compiler/parser.js');
|
||||
Handlebars::Spec.js_load(context, 'lib/handlebars/compiler/base.js');
|
||||
Handlebars::Spec.js_load(context, 'lib/handlebars/compiler/ast.js');
|
||||
Handlebars::Spec.js_load(context, 'lib/handlebars/compiler/visitor.js');
|
||||
Handlebars::Spec.js_load(context, 'lib/handlebars/compiler/printer.js');
|
||||
Handlebars::Spec.js_load(context, 'lib/handlebars/compiler/compiler.js');
|
||||
|
||||
context["Handlebars"]["logger"]["level"] = ENV["DEBUG_JS"] ? context["Handlebars"]["logger"][ENV["DEBUG_JS"]] : 4
|
||||
|
||||
context["Handlebars"]["logger"]["log"] = proc do |level, str|
|
||||
context["Handlebars"]["logger"]["log"] = proc do |this, level, str|
|
||||
logger_level = context["Handlebars"]["logger"]["level"].to_i
|
||||
|
||||
if logger_level <= level
|
||||
puts str
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
FULL_CONTEXT = V8::Context.new
|
||||
FULL_CONTEXT.instance_eval do |context|
|
||||
Handlebars::Spec.load_helpers(context);
|
||||
|
||||
Handlebars::Spec.js_load(context, 'lib/handlebars/base.js');
|
||||
Handlebars::Spec.js_load(context, 'lib/handlebars/utils.js');
|
||||
Handlebars::Spec.js_load(context, 'lib/handlebars/compiler/parser.js');
|
||||
Handlebars::Spec.js_load(context, 'lib/handlebars/compiler/base.js');
|
||||
Handlebars::Spec.js_load(context, 'lib/handlebars/compiler/ast.js');
|
||||
Handlebars::Spec.js_load(context, 'lib/handlebars/compiler/visitor.js');
|
||||
Handlebars::Spec.js_load(context, 'lib/handlebars/compiler/printer.js');
|
||||
Handlebars::Spec.js_load(context, 'lib/handlebars/compiler/compiler.js');
|
||||
Handlebars::Spec.js_load(context, 'lib/handlebars/runtime.js');
|
||||
|
||||
context["Handlebars"]["logger"]["level"] = ENV["DEBUG_JS"] ? context["Handlebars"]["logger"][ENV["DEBUG_JS"]] : 4
|
||||
|
||||
context["Handlebars"]["logger"]["log"] = proc do |this, level, str|
|
||||
logger_level = context["Handlebars"]["logger"]["level"].to_i
|
||||
|
||||
if logger_level <= level
|
||||
@@ -100,7 +150,8 @@ require "test/unit/assertions"
|
||||
RSpec.configure do |config|
|
||||
config.include Test::Unit::Assertions
|
||||
|
||||
config.before(:all) do
|
||||
@context = Handlebars::Spec::CONTEXT
|
||||
# Each is required to allow classes to mark themselves as compiler tests
|
||||
config.before(:each) do
|
||||
@context = @compiles ? Handlebars::Spec::COMPILE_CONTEXT : Handlebars::Spec::CONTEXT
|
||||
end
|
||||
end
|
||||
|
||||
+85
-1
@@ -5,6 +5,9 @@ describe "Tokenizer" do
|
||||
let(:parser) { @context["handlebars"] }
|
||||
let(:lexer) { @context["handlebars"]["lexer"] }
|
||||
|
||||
before(:all) do
|
||||
@compiles = true
|
||||
end
|
||||
Token = Struct.new(:name, :text)
|
||||
|
||||
def tokenize(string)
|
||||
@@ -41,6 +44,29 @@ describe "Tokenizer" do
|
||||
result[1].should be_token("ID", "foo")
|
||||
end
|
||||
|
||||
it "supports escaping delimiters" do
|
||||
result = tokenize("{{foo}} \\{{bar}} {{baz}}")
|
||||
result.should match_tokens(%w(OPEN ID CLOSE CONTENT CONTENT OPEN ID CLOSE))
|
||||
|
||||
result[4].should be_token("CONTENT", "{{bar}} ")
|
||||
end
|
||||
|
||||
it "supports escaping multiple delimiters" do
|
||||
result = tokenize("{{foo}} \\{{bar}} \\{{baz}}")
|
||||
result.should match_tokens(%w(OPEN ID CLOSE CONTENT CONTENT CONTENT))
|
||||
|
||||
result[3].should be_token("CONTENT", " ")
|
||||
result[4].should be_token("CONTENT", "{{bar}} ")
|
||||
result[5].should be_token("CONTENT", "{{baz}}")
|
||||
end
|
||||
|
||||
it "supports escaping a triple stash" do
|
||||
result = tokenize("{{foo}} \\{{{bar}}} {{baz}}")
|
||||
result.should match_tokens(%w(OPEN ID CLOSE CONTENT CONTENT OPEN ID CLOSE))
|
||||
|
||||
result[4].should be_token("CONTENT", "{{{bar}}} ")
|
||||
end
|
||||
|
||||
it "tokenizes a simple path" do
|
||||
result = tokenize("{{foo/bar}}")
|
||||
result.should match_tokens(%w(OPEN ID SEP ID CLOSE))
|
||||
@@ -53,6 +79,16 @@ describe "Tokenizer" do
|
||||
tokenize("{{foo.bar.baz}}").should match_tokens(%w(OPEN ID SEP ID SEP ID CLOSE))
|
||||
end
|
||||
|
||||
it "allows path literals with []" do
|
||||
result = tokenize("{{foo.[bar]}}")
|
||||
result.should match_tokens(%w(OPEN ID SEP ID CLOSE))
|
||||
end
|
||||
|
||||
it "allows multiple path literals on a line with []" do
|
||||
result = tokenize("{{foo.[bar]}}{{foo.[baz]}}")
|
||||
result.should match_tokens(%w(OPEN ID SEP ID CLOSE OPEN ID SEP ID CLOSE))
|
||||
end
|
||||
|
||||
it "tokenizes {{.}} as OPEN ID CLOSE" do
|
||||
result = tokenize("{{.}}")
|
||||
result.should match_tokens(%w(OPEN ID CLOSE))
|
||||
@@ -77,12 +113,18 @@ describe "Tokenizer" do
|
||||
result[3].should be_token("ID", "foo")
|
||||
end
|
||||
|
||||
it "tokenizes a simple mustahe with spaces as 'OPEN ID CLOSE'" do
|
||||
it "tokenizes a simple mustache with spaces as 'OPEN ID CLOSE'" do
|
||||
result = tokenize("{{ foo }}")
|
||||
result.should match_tokens(%w(OPEN ID CLOSE))
|
||||
result[1].should be_token("ID", "foo")
|
||||
end
|
||||
|
||||
it "tokenizes a simple mustache with line breaks as 'OPEN ID ID CLOSE'" do
|
||||
result = tokenize("{{ foo \n bar }}")
|
||||
result.should match_tokens(%w(OPEN ID ID CLOSE))
|
||||
result[1].should be_token("ID", "foo")
|
||||
end
|
||||
|
||||
it "tokenizes raw content as 'CONTENT'" do
|
||||
result = tokenize("foo {{ bar }} baz")
|
||||
result.should match_tokens(%w(CONTENT OPEN ID CLOSE CONTENT))
|
||||
@@ -165,6 +207,22 @@ describe "Tokenizer" do
|
||||
result[2].should be_token("STRING", %{bar"baz})
|
||||
end
|
||||
|
||||
it "tokenizes numbers" do
|
||||
result = tokenize(%|{{ foo 1 }}|)
|
||||
result.should match_tokens(%w(OPEN ID INTEGER CLOSE))
|
||||
result[2].should be_token("INTEGER", "1")
|
||||
end
|
||||
|
||||
it "tokenizes booleans" do
|
||||
result = tokenize(%|{{ foo true }}|)
|
||||
result.should match_tokens(%w(OPEN ID BOOLEAN CLOSE))
|
||||
result[2].should be_token("BOOLEAN", "true")
|
||||
|
||||
result = tokenize(%|{{ foo false }}|)
|
||||
result.should match_tokens(%w(OPEN ID BOOLEAN CLOSE))
|
||||
result[2].should be_token("BOOLEAN", "false")
|
||||
end
|
||||
|
||||
it "tokenizes hash arguments" do
|
||||
result = tokenize("{{ foo bar=baz }}")
|
||||
result.should match_tokens %w(OPEN ID ID EQUALS ID CLOSE)
|
||||
@@ -172,6 +230,18 @@ describe "Tokenizer" do
|
||||
result = tokenize("{{ foo bar baz=bat }}")
|
||||
result.should match_tokens %w(OPEN ID ID ID EQUALS ID CLOSE)
|
||||
|
||||
result = tokenize("{{ foo bar baz=1 }}")
|
||||
result.should match_tokens %w(OPEN ID ID ID EQUALS INTEGER CLOSE)
|
||||
|
||||
result = tokenize("{{ foo bar baz=true }}")
|
||||
result.should match_tokens %w(OPEN ID ID ID EQUALS BOOLEAN CLOSE)
|
||||
|
||||
result = tokenize("{{ foo bar baz=false }}")
|
||||
result.should match_tokens %w(OPEN ID ID ID EQUALS BOOLEAN CLOSE)
|
||||
|
||||
result = tokenize("{{ foo bar\n baz=bat }}")
|
||||
result.should match_tokens %w(OPEN ID ID ID EQUALS ID CLOSE)
|
||||
|
||||
result = tokenize("{{ foo bar baz=\"bat\" }}")
|
||||
result.should match_tokens %w(OPEN ID ID ID EQUALS STRING CLOSE)
|
||||
|
||||
@@ -183,6 +253,20 @@ describe "Tokenizer" do
|
||||
result[2].should be_token("ID", "omg")
|
||||
end
|
||||
|
||||
it "tokenizes special @ identifiers" do
|
||||
result = tokenize("{{ @foo }}")
|
||||
result.should match_tokens %w( OPEN DATA CLOSE )
|
||||
result[1].should be_token("DATA", "foo")
|
||||
|
||||
result = tokenize("{{ foo @bar }}")
|
||||
result.should match_tokens %w( OPEN ID DATA CLOSE )
|
||||
result[2].should be_token("DATA", "bar")
|
||||
|
||||
result = tokenize("{{ foo bar=@baz }}")
|
||||
result.should match_tokens %w( OPEN ID ID EQUALS DATA CLOSE )
|
||||
result[4].should be_token("DATA", "baz")
|
||||
end
|
||||
|
||||
it "does not time out in a mustache with a single } followed by EOF" do
|
||||
Timeout.timeout(1) { tokenize("{{foo}").should match_tokens(%w(OPEN ID)) }
|
||||
end
|
||||
|
||||
+39
-22
@@ -1,31 +1,48 @@
|
||||
|
||||
%x mu
|
||||
%x mu emu
|
||||
|
||||
%%
|
||||
|
||||
[^\x00]*?/("{{") { this.begin("mu"); if (yytext) return 'CONTENT'; }
|
||||
[^\x00]*?/("{{") {
|
||||
if(yytext.slice(-1) !== "\\") this.begin("mu");
|
||||
if(yytext.slice(-1) === "\\") yytext = yytext.substr(0,yyleng-1), this.begin("emu");
|
||||
if(yytext) return 'CONTENT';
|
||||
}
|
||||
|
||||
[^\x00]+ { return 'CONTENT'; }
|
||||
|
||||
<mu>"{{>" { return 'OPEN_PARTIAL'; }
|
||||
<mu>"{{#" { return 'OPEN_BLOCK'; }
|
||||
<mu>"{{/" { return 'OPEN_ENDBLOCK'; }
|
||||
<mu>"{{^" { return 'OPEN_INVERSE'; }
|
||||
<mu>"{{"\s*"else" { return 'OPEN_INVERSE'; }
|
||||
<mu>"{{{" { return 'OPEN_UNESCAPED'; }
|
||||
<mu>"{{&" { return 'OPEN_UNESCAPED'; }
|
||||
<mu>"{{!"[\s\S]*?"}}" { yytext = yytext.substr(3,yyleng-5); this.begin("INITIAL"); return 'COMMENT'; }
|
||||
<mu>"{{" { return 'OPEN'; }
|
||||
<emu>[^\x00]{2,}?/("{{"|<<EOF>>) {
|
||||
if(yytext.slice(-1) !== "\\") this.popState();
|
||||
if(yytext.slice(-1) === "\\") yytext = yytext.substr(0,yyleng-1);
|
||||
return 'CONTENT';
|
||||
}
|
||||
|
||||
<mu>"=" { return 'EQUALS'; }
|
||||
<mu>"."/[} ] { return 'ID'; }
|
||||
<mu>".." { return 'ID'; }
|
||||
<mu>[/.] { return 'SEP'; }
|
||||
<mu>\s+ { /*ignore whitespace*/ }
|
||||
<mu>"}}}" { this.begin("INITIAL"); return 'CLOSE'; }
|
||||
<mu>"}}" { this.begin("INITIAL"); return 'CLOSE'; }
|
||||
<mu>'"'("\\"["]|[^"])*'"' { yytext = yytext.substr(1,yyleng-2).replace(/\\"/g,'"'); return 'STRING'; }
|
||||
<mu>[a-zA-Z0-9_-]+/[=} /.] { return 'ID'; }
|
||||
<mu>. { return 'INVALID'; }
|
||||
<mu>"{{>" { return 'OPEN_PARTIAL'; }
|
||||
<mu>"{{#" { return 'OPEN_BLOCK'; }
|
||||
<mu>"{{/" { return 'OPEN_ENDBLOCK'; }
|
||||
<mu>"{{^" { return 'OPEN_INVERSE'; }
|
||||
<mu>"{{"\s*"else" { return 'OPEN_INVERSE'; }
|
||||
<mu>"{{{" { return 'OPEN_UNESCAPED'; }
|
||||
<mu>"{{&" { return 'OPEN_UNESCAPED'; }
|
||||
<mu>"{{!"[\s\S]*?"}}" { yytext = yytext.substr(3,yyleng-5); this.popState(); return 'COMMENT'; }
|
||||
<mu>"{{" { return 'OPEN'; }
|
||||
|
||||
<INITIAL,mu><<EOF>> { return 'EOF'; }
|
||||
<mu>"=" { return 'EQUALS'; }
|
||||
<mu>"."/[} ] { return 'ID'; }
|
||||
<mu>".." { return 'ID'; }
|
||||
<mu>[\/.] { return 'SEP'; }
|
||||
<mu>\s+ { /*ignore whitespace*/ }
|
||||
<mu>"}}}" { this.popState(); return 'CLOSE'; }
|
||||
<mu>"}}" { this.popState(); return 'CLOSE'; }
|
||||
<mu>'"'("\\"["]|[^"])*'"' { yytext = yytext.substr(1,yyleng-2).replace(/\\"/g,'"'); return 'STRING'; }
|
||||
<mu>"'"("\\"[']|[^'])*"'" { yytext = yytext.substr(1,yyleng-2).replace(/\\"/g,'"'); return 'STRING'; }
|
||||
<mu>"@"[a-zA-Z]+ { yytext = yytext.substr(1); return 'DATA'; }
|
||||
<mu>"true"/[}\s] { return 'BOOLEAN'; }
|
||||
<mu>"false"/[}\s] { return 'BOOLEAN'; }
|
||||
<mu>[0-9]+/[}\s] { return 'INTEGER'; }
|
||||
<mu>[a-zA-Z0-9_$-]+/[=}\s\/.] { return 'ID'; }
|
||||
<mu>'['[^\]]*']' { yytext = yytext.substr(1, yyleng-2); return 'ID'; }
|
||||
<mu>. { return 'INVALID'; }
|
||||
|
||||
<INITIAL,mu><<EOF>> { return 'EOF'; }
|
||||
|
||||
|
||||
+40
-33
@@ -3,50 +3,50 @@
|
||||
%%
|
||||
|
||||
root
|
||||
: program EOF { return $1 }
|
||||
: program EOF { return $1; }
|
||||
;
|
||||
|
||||
program
|
||||
: statements simpleInverse statements { $$ = new yy.ProgramNode($1, $3) }
|
||||
| statements { $$ = new yy.ProgramNode($1) }
|
||||
| "" { $$ = new yy.ProgramNode([]) }
|
||||
: statements simpleInverse statements { $$ = new yy.ProgramNode($1, $3); }
|
||||
| statements { $$ = new yy.ProgramNode($1); }
|
||||
| "" { $$ = new yy.ProgramNode([]); }
|
||||
;
|
||||
|
||||
statements
|
||||
: statement { $$ = [$1] }
|
||||
| statements statement { $1.push($2); $$ = $1 }
|
||||
: statement { $$ = [$1]; }
|
||||
| statements statement { $1.push($2); $$ = $1; }
|
||||
;
|
||||
|
||||
statement
|
||||
: openInverse program closeBlock { $$ = new yy.InverseNode($1, $2, $3) }
|
||||
| openBlock program closeBlock { $$ = new yy.BlockNode($1, $2, $3) }
|
||||
| mustache { $$ = $1 }
|
||||
| partial { $$ = $1 }
|
||||
| CONTENT { $$ = new yy.ContentNode($1) }
|
||||
| COMMENT { $$ = new yy.CommentNode($1) }
|
||||
: openInverse program closeBlock { $$ = new yy.BlockNode($1, $2.inverse, $2, $3); }
|
||||
| openBlock program closeBlock { $$ = new yy.BlockNode($1, $2, $2.inverse, $3); }
|
||||
| mustache { $$ = $1; }
|
||||
| partial { $$ = $1; }
|
||||
| CONTENT { $$ = new yy.ContentNode($1); }
|
||||
| COMMENT { $$ = new yy.CommentNode($1); }
|
||||
;
|
||||
|
||||
openBlock
|
||||
: OPEN_BLOCK inMustache CLOSE { $$ = new yy.MustacheNode($2[0], $2[1]) }
|
||||
: OPEN_BLOCK inMustache CLOSE { $$ = new yy.MustacheNode($2[0], $2[1]); }
|
||||
;
|
||||
|
||||
openInverse
|
||||
: OPEN_INVERSE inMustache CLOSE { $$ = new yy.MustacheNode($2[0], $2[1]) }
|
||||
: OPEN_INVERSE inMustache CLOSE { $$ = new yy.MustacheNode($2[0], $2[1]); }
|
||||
;
|
||||
|
||||
closeBlock
|
||||
: OPEN_ENDBLOCK path CLOSE { $$ = $2 }
|
||||
: OPEN_ENDBLOCK path CLOSE { $$ = $2; }
|
||||
;
|
||||
|
||||
mustache
|
||||
: OPEN inMustache CLOSE { $$ = new yy.MustacheNode($2[0], $2[1]) }
|
||||
| OPEN_UNESCAPED inMustache CLOSE { $$ = new yy.MustacheNode($2[0], $2[1], true) }
|
||||
: OPEN inMustache CLOSE { $$ = new yy.MustacheNode($2[0], $2[1]); }
|
||||
| OPEN_UNESCAPED inMustache CLOSE { $$ = new yy.MustacheNode($2[0], $2[1], true); }
|
||||
;
|
||||
|
||||
|
||||
partial
|
||||
: OPEN_PARTIAL path CLOSE { $$ = new yy.PartialNode($2) }
|
||||
| OPEN_PARTIAL path path CLOSE { $$ = new yy.PartialNode($2, $3) }
|
||||
: OPEN_PARTIAL path CLOSE { $$ = new yy.PartialNode($2); }
|
||||
| OPEN_PARTIAL path path CLOSE { $$ = new yy.PartialNode($2, $3); }
|
||||
;
|
||||
|
||||
simpleInverse
|
||||
@@ -54,42 +54,49 @@ simpleInverse
|
||||
;
|
||||
|
||||
inMustache
|
||||
: path params hash { $$ = [[$1].concat($2), $3] }
|
||||
| path params { $$ = [[$1].concat($2), null] }
|
||||
| path hash { $$ = [[$1], $2] }
|
||||
| path { $$ = [[$1], null] }
|
||||
: path params hash { $$ = [[$1].concat($2), $3]; }
|
||||
| path params { $$ = [[$1].concat($2), null]; }
|
||||
| path hash { $$ = [[$1], $2]; }
|
||||
| path { $$ = [[$1], null]; }
|
||||
| DATA { $$ = [[new yy.DataNode($1)], null]; }
|
||||
;
|
||||
|
||||
params
|
||||
: params param { $1.push($2); $$ = $1; }
|
||||
| param { $$ = [$1] }
|
||||
| param { $$ = [$1]; }
|
||||
;
|
||||
|
||||
param
|
||||
: path { $$ = $1 }
|
||||
| STRING { $$ = new yy.StringNode($1) }
|
||||
: path { $$ = $1; }
|
||||
| STRING { $$ = new yy.StringNode($1); }
|
||||
| INTEGER { $$ = new yy.IntegerNode($1); }
|
||||
| BOOLEAN { $$ = new yy.BooleanNode($1); }
|
||||
| DATA { $$ = new yy.DataNode($1); }
|
||||
;
|
||||
|
||||
hash
|
||||
: hashSegments { $$ = new yy.HashNode($1) }
|
||||
: hashSegments { $$ = new yy.HashNode($1); }
|
||||
;
|
||||
|
||||
hashSegments
|
||||
: hashSegments hashSegment { $1.push($2); $$ = $1 }
|
||||
| hashSegment { $$ = [$1] }
|
||||
: hashSegments hashSegment { $1.push($2); $$ = $1; }
|
||||
| hashSegment { $$ = [$1]; }
|
||||
;
|
||||
|
||||
hashSegment
|
||||
: ID EQUALS path { $$ = [$1, $3] }
|
||||
| ID EQUALS STRING { $$ = [$1, new yy.StringNode($3)] }
|
||||
: ID EQUALS path { $$ = [$1, $3]; }
|
||||
| ID EQUALS STRING { $$ = [$1, new yy.StringNode($3)]; }
|
||||
| ID EQUALS INTEGER { $$ = [$1, new yy.IntegerNode($3)]; }
|
||||
| ID EQUALS BOOLEAN { $$ = [$1, new yy.BooleanNode($3)]; }
|
||||
| ID EQUALS DATA { $$ = [$1, new yy.DataNode($3)]; }
|
||||
;
|
||||
|
||||
path
|
||||
: pathSegments { $$ = new yy.IdNode($1) }
|
||||
: pathSegments { $$ = new yy.IdNode($1); }
|
||||
;
|
||||
|
||||
pathSegments
|
||||
: pathSegments SEP ID { $1.push($3); $$ = $1; }
|
||||
| ID { $$ = [$1] }
|
||||
| ID { $$ = [$1]; }
|
||||
;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user