Compare commits

..

93 Commits

Author SHA1 Message Date
kpdecker 2a073e0993 1.0.12 2013-05-31 14:16:53 -04:00
kpdecker f63b226b76 Update to 1.0.0 final 2013-05-31 14:16:45 -04:00
kpdecker 16fd601a5c Merge global and passed helpers and partials 2013-05-31 13:12:06 -04:00
kpdecker ef062adcc2 Move external AST tests into qunit spec 2013-05-31 13:11:22 -04:00
kpdecker 2024209519 Move upgrade notes to release-notes 2013-05-30 15:57:15 -04:00
kpdecker d7b345b2da Allow execution of helpers on the context
Fixes #285
2013-05-30 15:47:30 -04:00
kpdecker 17659b9720 Ignore minimized files from git 2013-05-29 15:31:08 -04:00
kpdecker 58a0b4f17d Require matching braces in escaped expressions
Fixes #437
2013-05-29 15:30:50 -04:00
kpdecker 61f64e9f04 Add minify task to release target 2013-05-29 15:21:19 -04:00
kpdecker 66f6f94867 Update to latest uglify lib 2013-05-29 15:17:10 -04:00
kpdecker c540d7186a Allow function arguments to with and each
Via @mcdan
Fixes #239
2013-05-29 12:23:55 -04:00
kpdecker 293672432b improve error message on missing helper
Via @stefanpenner
Fixes #523
2013-05-29 10:57:48 -04:00
kpdecker da2aabe7bd Allow ID, STRING, or INTEGER for partial name
Fixes #519
2013-05-29 10:46:28 -04:00
kpdecker 3ddbc5237f Protect from object prototype modifications
Fixes #534
2013-05-28 17:03:07 -04:00
kpdecker 503e32208b Improve tracking of original path values 2013-05-27 19:47:17 -04:00
kpdecker 15186dc9dd Add supported environments statement
Fixes #344
2013-05-27 19:03:16 -04:00
kpdecker 2c8dd74997 Add unescaped tokenizer tests 2013-05-27 14:59:45 -05:00
kpdecker 3d51088d4d Fail if jison fails 2013-05-27 14:58:49 -05:00
kpdecker 822a8911ec Add support for complex ids in @data references 2013-05-27 14:40:52 -05:00
kpdecker 5f349913aa Update in the wild section
Fixes #246
2013-05-27 12:29:57 -05:00
kpdecker c1020a0130 Add unicode support for ID tokens
Fixes #433
Fixes #469
2013-05-27 11:56:28 -05:00
kpdecker bff71d79d3 Update release notes 2013-05-25 12:32:16 -05:00
kpdecker 4d95d58e15 Rebuild 2013-05-25 12:27:31 -05:00
Kevin Decker 47e0aa795a Merge pull request #526 from jamesgorrie/master
Fixing regex on partial names
2013-05-25 10:07:46 -07:00
kpdecker 6bf9c6fc13 Merge branch 'master' of github.com:wycats/handlebars.js 2013-05-22 08:40:28 -04:00
kpdecker 533be694b5 Use better variable name 2013-05-21 13:12:11 -05:00
kpdecker a97820f54b Update testing notes
Fixes #467
2013-05-21 13:03:41 -05:00
kpdecker f6b6361ad7 Fix typo 2013-05-21 13:03:01 -05:00
Kevin Decker 10669e0e36 Merge pull request #533 from blessenm/amd-precompilation-fix
Fix for the issue #517
2013-05-21 08:46:27 -07:00
Blessan Mathew 69bcdf36c7 Fix for issue #517.
Now when compiiled with amd option and a single input argument, we are
checking to see whether the input is a file or folder.
2013-05-21 20:50:46 +05:30
kpdecker 5ba17c9642 Merge branch 'master' of github.com:wycats/handlebars.js 2013-05-19 11:47:01 -05:00
kpdecker 7fb9750717 Update block helper docs in README
Fixes #231, #259
2013-05-19 11:45:20 -05:00
kpdecker c95d9d68ea Use context rather than arg in README examples 2013-05-19 11:30:26 -05:00
kpdecker 3aef7f604a Misc readme updates 2013-05-19 11:30:04 -05:00
James Gorrie f5fe6d0bf6 Allowed for points to be in partial path name 2013-05-17 16:58:34 +01:00
Kevin Decker 94cc41900b Merge pull request #515 from jjclark1982/master
Allow requiring ".handlebars" files within Node.js scripts
2013-05-14 21:10:14 -07:00
Jesse Clark d649b3581e Only run "require" tests when require() is available 2013-05-15 10:50:01 +10:00
Jesse Clark 229b82bef5 Add unit tests for require() handler 2013-05-15 10:38:45 +10:00
Jesse Clark 531be77138 Publish a Node.js require() handler for handlebars files 2013-05-14 22:26:20 +10:00
kpdecker 4cf0410b7c 1.0.11 2013-05-13 23:09:21 -05:00
kpdecker 2ec5a97f4a Update release notes 2013-05-13 23:08:43 -05:00
kpdecker ddc4d31861 Rev to rc4 2013-05-13 23:07:29 -05:00
kpdecker 85dd39427b Add regex escapes 2013-05-10 01:35:25 -05:00
kpdecker 8b65fa771e Update release notes 2013-05-10 01:24:11 -05:00
kpdecker fa6cec26bf Update client packager versions 2013-05-10 01:02:13 -05:00
kpdecker 436973f470 Version update script 2013-05-10 01:01:55 -05:00
Kevin Decker a2796784ab Merge pull request #505 from MikeMayer/master
Include a .nuspec package file to include handlebars.js on NuGet
2013-05-09 22:37:23 -07:00
Kevin Decker d355d1f6aa Fix links in release notes 2013-05-10 00:35:05 -05:00
Kevin Decker 71c5e36268 Merge pull request #489 from DevinClark/bower-update
Added a component.json file for Twitter's Bower package manager.
2013-05-09 22:29:27 -07:00
kpdecker 44ae572e2f Add release notes
Fixes #471
2013-05-10 00:14:55 -05:00
Kevin Decker 7cd9cdb0eb Merge pull request #513 from tommydudebreaux/master
Using handlebars with browserify causes IE to throw - TypeError: Invalid calling object
2013-05-09 21:03:03 -07:00
Tommy Messbauer 090ee7c59a added local pointer to handlerbars.utils to allow this to browserify properly for IE 2013-05-09 17:17:34 -05:00
MikeMayer 8a5705a253 Explicitly only include the handlebars.js from the dist folder 2013-05-01 09:58:17 -07:00
MikeMayer 344bb9092d Create handlebars.js.nuspec for inclusion in NuGet
Added a .nuspec file for those people who use Visual Studio and NuGet and might want to use handlebars.js deployed from NuGet in their application.
2013-05-01 09:52:55 -07:00
Utkarsh Sengar ef325733bb Added link to a gist for loading handlebars templates (sync and async) in README. 2013-04-28 13:54:47 -05:00
kpdecker 18798a7546 Adjust formatting of bullet list 2013-04-28 13:53:01 -05:00
Kevin Decker bbce33778e Merge pull request #499 from gilesbowkett/paragraph_width
adjusted paragraph width for readability
2013-04-28 11:44:19 -07:00
DevinClark fc52a65c70 Changed component.json to bower.json per Bower 0.9.0 update. 2013-04-25 21:05:21 -05:00
Giles Bowkett 06e593d32e paragraph width 2013-04-25 14:44:52 -07:00
Kevin Decker 16a6fe15f2 Merge pull request #492 from iangreenleaf/patch-1
Change download link in install instructions
2013-04-25 09:39:13 -07:00
Ian Young d8d2a6a1d2 Change download link in install instructions
The install instructions in the readme were still pointing to the GitHub downloads page, which is deprecated and out of date.

Updated to point to the official site, which has a big download button.
2013-04-16 16:32:40 -07:00
Kevin Decker 4c35f89c2a Merge pull request #488 from tnwinc/amd-module-return-values
Amd module return values
2013-04-08 10:41:08 -07:00
DevinClark 5c9aa9e4d6 Added a component.json file for Twitter's Bower package manager.
See [https://github.com/twitter/bower#defining-a-package](Defining a
package) for more information about how the file was generated.
2013-04-08 12:28:13 -05:00
James Maroney ca576c27fc If building AMD module and compiling multiple templates, return the full
templates (or partials) hash from the AMD module
2013-04-08 10:37:18 -04:00
James Maroney 85a21f2359 If building AMD modules, and only one template is being compiled,
return the compiled template from the AMD module
2013-04-08 10:36:43 -04:00
Kevin Decker 075fdb8ac4 Merge pull request #454 from leshill/fix_string_mode_contexts
Add contexts for string mode hash values
2013-04-07 17:21:40 -07:00
kpdecker 4429ffa9f3 Allow multiple partial and helper registration
Fixes #369
2013-04-07 18:04:51 -05:00
kpdecker 73f2016a6a Remove FULL_CONTEXT from spec_helper 2013-04-07 16:50:57 -05:00
kpdecker 381c113d66 Rebuild 2013-04-07 12:41:55 -05:00
kpdecker 0329c852bd Merge branch 'program-metadata' 2013-04-07 12:29:54 -05:00
kpdecker f6ff5c648b Restore knownHelpersOnly throw
Fixes #302
2013-04-06 23:23:40 -05:00
kpdecker 12d68caa58 Allow compilation of empty string
Fixes #461
2013-04-06 23:17:39 -05:00
kpdecker ff32b4e2ad Escape passed regex 2013-04-06 23:14:29 -05:00
kpdecker c59882cce1 Merge branch 'master' of github.com:matteoagosti/handlebars.js into matteoagosti-master
Conflicts:
	bin/handlebars
2013-04-06 23:07:50 -05:00
kpdecker 4d66d0c0a6 Escape unicode newlines in string literals
Fixes #375
2013-04-06 16:42:24 -05:00
kpdecker bee0facaca Rebuild 2013-04-06 15:49:13 -05:00
Kevin Decker af3501636c Merge pull request #462 from broady/master
Fix support for Rhino
2013-04-06 13:48:47 -07:00
kpdecker e33d9b4dcf Add test case for GH-458 2013-04-06 15:28:27 -05:00
kpdecker 31aaa30fcd Rebuild 2013-04-06 15:28:18 -05:00
kpdecker ec5882a31a Merge branch 'master' of github.com:jpfiset/handlebars.js into jpfiset-master 2013-04-06 15:18:30 -05:00
kpdecker 671c07e699 Force toString in escapeExpression
Fixes #211
2013-04-06 14:46:04 -05:00
kpdecker f4d0092bb8 escapeExpression tests 2013-04-06 14:42:33 -05:00
kpdecker fe74d65f2b isEmpty test 2013-04-06 14:26:08 -05:00
kpdecker 9e4b59e815 Add support for \\{ escapes
Fixes #456
2013-03-31 16:27:34 -05:00
Les Hill 53de75927c Add contexts for string mode hash values
Allows for evaluating hash parameters such as ../city in string mode.
2013-03-19 21:41:36 -07:00
kpdecker bcc15ea5e7 Rebuild dist 2013-03-13 21:56:30 -05:00
Kevin Decker 21977633a0 Merge pull request #213 from thejohnfreeman/master
Block helper "with" should check for empty context
2013-03-13 19:54:26 -07:00
Chris Broadfoot e4a0ac4b04 Fix Rhino support 2013-03-04 16:08:00 -08:00
Jean-Pierre Fiset 681f1a6396 Allow colon characters in identifiers
Issue #460
2013-03-04 13:33:13 -05:00
Jean-Pierre Fiset bf30bf9115 Allow accessing properties in current context using paths. This is to
avoid name collisions with registered helper functions.

Issue #458
2013-03-04 13:25:00 -05:00
kpdecker baccdb4cfc Add program metadata
Include program id and depth on the generated wrapper objects. This
allows helpers who are passed these objects to differentiate between
helpers for cases where they may want to cache the generated DOM
structure.
2013-02-17 04:27:42 -06:00
Matteo Agosti 53d6b4ca04 Supporting custom template extension
Fix missing escape
2012-11-08 12:22:35 +01:00
John Freeman 0872fb6699 block helper "with" should check for empty context 2012-03-31 23:09:27 -05:00
25 changed files with 1031 additions and 313 deletions
+1
View File
@@ -2,6 +2,7 @@ vendor
.rvmrc
.DS_Store
lib/handlebars/compiler/parser.js
dist/*.min.js
node_modules
*.sublime-project
*.sublime-workspace
+125 -51
View File
@@ -3,20 +3,30 @@
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.
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).
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).
Installing Handlebars is easy. Simply download the package [from the
official site](http://handlebarsjs.com/) and add it to your web pages
(you should usually use the most recent version).
Usage
-----
In general, the syntax of Handlebars.js templates is a superset of Mustache templates. For basic syntax, check out the [Mustache manpage](http://mustache.github.com/mustache.5.html).
In general, the syntax of Handlebars.js templates is a superset
of Mustache templates. For basic syntax, check out the [Mustache
manpage](http://mustache.github.com/mustache.5.html).
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.
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.
```js
var source = "<p>Hello, my name is {{name}}. I am from {{hometown}}. I have " +
@@ -45,12 +55,12 @@ template. Here's an example, which assumes that your objects have a URL
embedded in them, as well as the text for a link:
```js
Handlebars.registerHelper('link_to', function(context) {
return "<a href='" + context.url + "'>" + context.body + "</a>";
Handlebars.registerHelper('link_to', function() {
return "<a href='" + this.url + "'>" + this.body + "</a>";
});
var context = { posts: [{url: "/hello-world", body: "Hello World!"}] };
var source = "<ul>{{#posts}}<li>{{{link_to this}}}</li>{{/posts}}</ul>"
var source = "<ul>{{#posts}}<li>{{{link_to}}}</li>{{/posts}}</ul>"
var template = Handlebars.compile(source);
template(context);
@@ -62,6 +72,14 @@ template(context);
// </ul>
```
Helpers take precedence over fields defined on the context. To access a field
that is masked by a helper, a path reference may be used. In the example above
a field named `link_to` on the `context` object would be referenced using:
```
{{./link_to}}
```
Escaping
--------
@@ -75,25 +93,34 @@ To explicitly *not* escape the contents, use the triple-mustache
Differences Between Handlebars.js and Mustache
----------------------------------------------
Handlebars.js adds a couple of additional features to make writing templates easier and also changes a tiny detail of how partials work.
Handlebars.js adds a couple of additional features to make writing
templates easier and also changes a tiny detail of how partials work.
### 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 descendants 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 descendant contexts, use the `.` character. So, for
example, if your data were structured like:
```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:
You could display the person's name from the top-level context with the
following expression:
```
{{person.name}}
```
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:
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}}
@@ -111,12 +138,12 @@ When calling a helper, you can pass paths or Strings as parameters. For
instance:
```js
Handlebars.registerHelper('link_to', function(title, context) {
return "<a href='/posts" + context.url + "'>" + title + "!</a>"
Handlebars.registerHelper('link_to', function(title, options) {
return "<a href='/posts" + this.url + "'>" + title + "!</a>"
});
var context = { posts: [{url: "/hello-world", body: "Hello World!"}] };
var source = '<ul>{{#posts}}<li>{{{link_to "Post" this}}}</li>{{/posts}}</ul>'
var source = '<ul>{{#posts}}<li>{{{link_to "Post"}}}</li>{{/posts}}</ul>'
var template = Handlebars.compile(source);
template(context);
@@ -134,7 +161,9 @@ gets passed to the helper function.
### Block Helpers
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:
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:
```js
var source = "<ul>{{#people}}<li>{{#link}}{{name}}{{/link}}</li>{{/people}}</ul>";
@@ -156,7 +185,18 @@ template(data);
// </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.
Whenever the block helper is called it is given one or more parameters,
any arguments that are passed in the helper in the call and an `options`
object containing the `fn` function which executes the block's child.
The block's current context may be accessed through `this`.
Block helpers have the same syntax as mustache sections but should not be
confused with one another. Sections are akin to an implicit `each` or
`with` statement depending on the input data and helpers are explicit
pieces of code that are free to implement whatever behavior they like.
The [mustache spec](http://mustache.github.io/mustache.5.html)
defines the exact behavior of sections. In the case of name conflicts,
helpers are given priority.
### Partials
@@ -229,13 +269,15 @@ Options:
-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 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.
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
@@ -248,11 +290,33 @@ method and the resulting object may be as normal.
- When all helpers are known in advance the `--knownOnly` argument may be used
to optimize all block helper references.
Supported Environments
----------------------
Handlebars has been designed to work in any ECMAScript 3 environment. This includes
- Node.js
- Chrome
- Firefox
- Safari 5+
- Opera 11+
- IE 6+
Older versions and other runtimes are likely to work but have not been formally
tested.
Performance
-----------
In a rough performance test, precompiled Handlebars.js templates (in the original version of Handlebars.js) rendered in about half the time of Mustache templates. It would be a shame if it were any other way, since they were precompiled, but the difference in architecture does have some big performance advantages. Justin Marney, a.k.a. [gotascii](http://github.com/gotascii), confirmed that with an [independent test](http://sorescode.com/2010/09/12/benchmarks.html). The rewritten Handlebars (current version) is faster than the old version, and we will have some benchmarks in the near future.
In a rough performance test, precompiled Handlebars.js templates (in
the original version of Handlebars.js) rendered in about half the
time of Mustache templates. It would be a shame if it were any other
way, since they were precompiled, but the difference in architecture
does have some big performance advantages. Justin Marney, a.k.a.
[gotascii](http://github.com/gotascii), confirmed that with an
[independent test](http://sorescode.com/2010/09/12/benchmarks.html). The
rewritten Handlebars (current version) is faster than the old version,
and we will have some benchmarks in the near future.
Building
@@ -265,21 +329,7 @@ in the `dist` directory.
Upgrading
---------
When upgrading from the Handlebars 0.9 series, be aware that the
signature for passing custom helpers or partials to templates has
changed.
Instead of:
```js
template(context, helpers, partials, [data])
```
Use:
```js
template(context, {helpers: helpers, partials: partials, data: data})
```
See [release-notes.md](https://github.com/wycats/handlebars.js/blob/master/release-notes.md) for upgrade notes.
Known Issues
------------
@@ -288,30 +338,54 @@ Known Issues
Handlebars in the Wild
-----------------
* [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).
* [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).
* [Gist about Synchronous and asynchronous loading of external handlebars templates](https://gist.github.com/2287070)
* [Lumbar](walmartlabs.github.io/lumbar) provides easy module-based template management for handlebars projects.
* [YUI](http://yuilibrary.com/yui/docs/handlebars/) implements a port of handlebars
Have a project using Handlebars? Send us a [pull request](https://github.com/wycats/handlebars.js/pull/new/master)!
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.
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`.
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 test`. You can also
run our set of benchmarks with `rake bench`. Node tests can be run with
`npm test` or `rake npm_test`. The default rake target will compile and
run both test suites.
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.
Some environments, notably Windows, have issues running therubyracer. Under these
envrionments the `rake compile` and `npm test` should be sufficient to test
most handlebars functionality.
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
-------
Handlebars.js is released under the MIT license.
+23 -2
View File
@@ -10,7 +10,7 @@ def compile_parser
sh "rm handlebars.js"
else
puts "Failed to run Jison."
fail "Failed to run Jison."
end
end
@@ -90,8 +90,29 @@ task :runtime => [:compile] do |task|
Rake::Task["dist/handlebars.runtime.js"].execute
end
# Updates the various version numbers.
task :version => [] do |task|
# TODO : Pull from package.json when the version numbers are synced
version = File.read("lib/handlebars/base.js").match(/Handlebars.VERSION = "(.*)";/)[1]
content = File.read("bower.json")
File.open("bower.json", "w") do |file|
file.puts content.gsub(/"version":.*/, "\"version\": \"#{version}\",")
end
content = File.read("handlebars.js.nuspec")
File.open("handlebars.js.nuspec", "w") do |file|
file.puts content.gsub(/<version>.*<\/version>/, "<version>#{version}</version>")
end
end
task :minify => [] do |task|
system "./node_modules/.bin/uglifyjs --comments -o dist/handlebars.min.js dist/handlebars.js"
system "./node_modules/.bin/uglifyjs --comments -o dist/handlebars.runtime.min.js dist/handlebars.runtime.js"
end
desc "build the build and runtime version of handlebars"
task :release => [:build, :runtime]
task :release => [:version, :build, :runtime, :minify]
directory "vendor"
+26 -6
View File
@@ -64,6 +64,12 @@ var optimist = require('optimist')
'type': 'boolean',
'description': 'Include data when compiling',
'alias': 'data'
},
'e': {
'type': 'string',
'description': 'Template extension.',
'alias': 'extension',
'default': 'handlebars'
}
})
@@ -109,6 +115,10 @@ if (argv.known) {
}
}
// Build file extension pattern
var extension = argv.extension.replace(/[\\^$*+?.():=!|{}\-\[\]]/g, function(arg) { return '\\' + arg; });
extension = new RegExp('\\.' + extension + '$');
var output = [];
if (!argv.simple) {
if (argv.amd) {
@@ -131,7 +141,7 @@ function processTemplate(template, root) {
fs.readdirSync(template).map(function(file) {
var path = template + '/' + file;
if (/\.handlebars$/.test(path) || fs.statSync(path).isDirectory()) {
if (extension.test(path) || fs.statSync(path).isDirectory()) {
processTemplate(path, root || template);
}
});
@@ -153,13 +163,19 @@ function processTemplate(template, root) {
} else if (template.indexOf(root) === 0) {
template = template.substring(root.length+1);
}
template = template.replace(/\.handlebars$/, '');
template = template.replace(extension, '');
if (argv.simple) {
output.push(handlebars.precompile(data, options) + '\n');
} else if (argv.partial) {
if(argv.amd && (argv._.length == 1 && !fs.statSync(argv._[0]).isDirectory())) {
output.push('return ');
}
output.push('Handlebars.partials[\'' + template + '\'] = template(' + handlebars.precompile(data, options) + ');\n');
} else {
if(argv.amd && (argv._.length == 1 && !fs.statSync(argv._[0]).isDirectory())) {
output.push('return ');
}
output.push('templates[\'' + template + '\'] = template(' + handlebars.precompile(data, options) + ');\n');
}
}
@@ -172,6 +188,13 @@ argv._.forEach(function(template) {
// Output the content
if (!argv.simple) {
if (argv.amd) {
if(argv._.length > 1 || (argv._.length == 1 && fs.statSync(argv._[0]).isDirectory())) {
if(argv.partial){
output.push('return Handlebars.partials;\n');
} else {
output.push('return templates;\n');
}
}
output.push('});');
} else if (!argv.commonjs) {
output.push('})();');
@@ -180,10 +203,7 @@ if (!argv.simple) {
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);
output = uglify.minify(output, {fromString: true}).code;
}
if (argv.output) {
+9
View File
@@ -0,0 +1,9 @@
{
"name": "handlebars.js",
"version": "1.0.0",
"main": "dist/handlebars.js",
"ignore": [
"node_modules",
"components"
]
}
+178 -100
View File
@@ -29,36 +29,49 @@ var Handlebars = {};
;
// lib/handlebars/base.js
Handlebars.VERSION = "1.0.0-rc.3";
Handlebars.COMPILER_REVISION = 2;
Handlebars.VERSION = "1.0.0";
Handlebars.COMPILER_REVISION = 4;
Handlebars.REVISION_CHANGES = {
1: '<= 1.0.rc.2', // 1.0.rc.2 is actually rev2 but doesn't report it
2: '>= 1.0.0-rc.3'
2: '== 1.0.0-rc.3',
3: '== 1.0.0-rc.4',
4: '>= 1.0.0'
};
Handlebars.helpers = {};
Handlebars.partials = {};
var toString = Object.prototype.toString,
functionType = '[object Function]',
objectType = '[object Object]';
Handlebars.registerHelper = function(name, fn, inverse) {
if(inverse) { fn.not = inverse; }
this.helpers[name] = fn;
if (toString.call(name) === objectType) {
if (inverse || fn) { throw new Handlebars.Exception('Arg not supported with multiple helpers'); }
Handlebars.Utils.extend(this.helpers, name);
} else {
if (inverse) { fn.not = inverse; }
this.helpers[name] = fn;
}
};
Handlebars.registerPartial = function(name, str) {
this.partials[name] = str;
if (toString.call(name) === objectType) {
Handlebars.Utils.extend(this.partials, name);
} else {
this.partials[name] = str;
}
};
Handlebars.registerHelper('helperMissing', function(arg) {
if(arguments.length === 2) {
return undefined;
} else {
throw new Error("Could not find property '" + arg + "'");
throw new Error("Missing helper: '" + arg + "'");
}
});
var toString = Object.prototype.toString, functionType = "[object Function]";
Handlebars.registerHelper('blockHelperMissing', function(context, options) {
var inverse = options.inverse || function() {}, fn = options.fn;
@@ -112,6 +125,9 @@ Handlebars.registerHelper('each', function(context, options) {
var fn = options.fn, inverse = options.inverse;
var i = 0, ret = "", data;
var type = toString.call(context);
if(type === functionType) { context = context.call(this); }
if (options.data) {
data = Handlebars.createFrame(options.data);
}
@@ -140,23 +156,26 @@ Handlebars.registerHelper('each', function(context, options) {
return ret;
});
Handlebars.registerHelper('if', function(context, options) {
var type = toString.call(context);
if(type === functionType) { context = context.call(this); }
Handlebars.registerHelper('if', function(conditional, options) {
var type = toString.call(conditional);
if(type === functionType) { conditional = conditional.call(this); }
if(!context || Handlebars.Utils.isEmpty(context)) {
if(!conditional || Handlebars.Utils.isEmpty(conditional)) {
return options.inverse(this);
} else {
return options.fn(this);
}
});
Handlebars.registerHelper('unless', function(context, options) {
return Handlebars.helpers['if'].call(this, context, {fn: options.inverse, inverse: options.fn});
Handlebars.registerHelper('unless', function(conditional, options) {
return Handlebars.helpers['if'].call(this, conditional, {fn: options.inverse, inverse: options.fn});
});
Handlebars.registerHelper('with', function(context, options) {
return options.fn(context);
var type = toString.call(context);
if(type === functionType) { context = context.call(this); }
if (!Handlebars.Utils.isEmpty(context)) return options.fn(context);
});
Handlebars.registerHelper('log', function(context, options) {
@@ -169,9 +188,9 @@ Handlebars.registerHelper('log', function(context, options) {
var handlebars = (function(){
var parser = {trace: function trace() { },
yy: {},
symbols_: {"error":2,"root":3,"program":4,"EOF":5,"simpleInverse":6,"statements":7,"statement":8,"openInverse":9,"closeBlock":10,"openBlock":11,"mustache":12,"partial":13,"CONTENT":14,"COMMENT":15,"OPEN_BLOCK":16,"inMustache":17,"CLOSE":18,"OPEN_INVERSE":19,"OPEN_ENDBLOCK":20,"path":21,"OPEN":22,"OPEN_UNESCAPED":23,"OPEN_PARTIAL":24,"partialName":25,"params":26,"hash":27,"DATA":28,"param":29,"STRING":30,"INTEGER":31,"BOOLEAN":32,"hashSegments":33,"hashSegment":34,"ID":35,"EQUALS":36,"PARTIAL_NAME":37,"pathSegments":38,"SEP":39,"$accept":0,"$end":1},
terminals_: {2:"error",5:"EOF",14:"CONTENT",15:"COMMENT",16:"OPEN_BLOCK",18:"CLOSE",19:"OPEN_INVERSE",20:"OPEN_ENDBLOCK",22:"OPEN",23:"OPEN_UNESCAPED",24:"OPEN_PARTIAL",28:"DATA",30:"STRING",31:"INTEGER",32:"BOOLEAN",35:"ID",36:"EQUALS",37:"PARTIAL_NAME",39:"SEP"},
productions_: [0,[3,2],[4,2],[4,3],[4,2],[4,1],[4,1],[4,0],[7,1],[7,2],[8,3],[8,3],[8,1],[8,1],[8,1],[8,1],[11,3],[9,3],[10,3],[12,3],[12,3],[13,3],[13,4],[6,2],[17,3],[17,2],[17,2],[17,1],[17,1],[26,2],[26,1],[29,1],[29,1],[29,1],[29,1],[29,1],[27,1],[33,2],[33,1],[34,3],[34,3],[34,3],[34,3],[34,3],[25,1],[21,1],[38,3],[38,1]],
symbols_: {"error":2,"root":3,"program":4,"EOF":5,"simpleInverse":6,"statements":7,"statement":8,"openInverse":9,"closeBlock":10,"openBlock":11,"mustache":12,"partial":13,"CONTENT":14,"COMMENT":15,"OPEN_BLOCK":16,"inMustache":17,"CLOSE":18,"OPEN_INVERSE":19,"OPEN_ENDBLOCK":20,"path":21,"OPEN":22,"OPEN_UNESCAPED":23,"CLOSE_UNESCAPED":24,"OPEN_PARTIAL":25,"partialName":26,"params":27,"hash":28,"dataName":29,"param":30,"STRING":31,"INTEGER":32,"BOOLEAN":33,"hashSegments":34,"hashSegment":35,"ID":36,"EQUALS":37,"DATA":38,"pathSegments":39,"SEP":40,"$accept":0,"$end":1},
terminals_: {2:"error",5:"EOF",14:"CONTENT",15:"COMMENT",16:"OPEN_BLOCK",18:"CLOSE",19:"OPEN_INVERSE",20:"OPEN_ENDBLOCK",22:"OPEN",23:"OPEN_UNESCAPED",24:"CLOSE_UNESCAPED",25:"OPEN_PARTIAL",31:"STRING",32:"INTEGER",33:"BOOLEAN",36:"ID",37:"EQUALS",38:"DATA",40:"SEP"},
productions_: [0,[3,2],[4,2],[4,3],[4,2],[4,1],[4,1],[4,0],[7,1],[7,2],[8,3],[8,3],[8,1],[8,1],[8,1],[8,1],[11,3],[9,3],[10,3],[12,3],[12,3],[13,3],[13,4],[6,2],[17,3],[17,2],[17,2],[17,1],[17,1],[27,2],[27,1],[30,1],[30,1],[30,1],[30,1],[30,1],[28,1],[34,2],[34,1],[35,3],[35,3],[35,3],[35,3],[35,3],[26,1],[26,1],[26,1],[29,2],[21,1],[39,3],[39,1]],
performAction: function anonymous(yytext,yyleng,yylineno,yy,yystate,$$,_$) {
var $0 = $$.length - 1;
@@ -212,7 +231,10 @@ case 17: this.$ = new yy.MustacheNode($$[$0-1][0], $$[$0-1][1]);
break;
case 18: this.$ = $$[$0-1];
break;
case 19: this.$ = new yy.MustacheNode($$[$0-1][0], $$[$0-1][1]);
case 19:
// Parsing out the '&' escape token at this level saves ~500 bytes after min due to the removal of one parser node.
this.$ = new yy.MustacheNode($$[$0-1][0], $$[$0-1][1], $$[$0-2][2] === '&');
break;
case 20: this.$ = new yy.MustacheNode($$[$0-1][0], $$[$0-1][1], true);
break;
@@ -230,7 +252,7 @@ case 26: this.$ = [[$$[$0-1]], $$[$0]];
break;
case 27: this.$ = [[$$[$0]], null];
break;
case 28: this.$ = [[new yy.DataNode($$[$0])], null];
case 28: this.$ = [[$$[$0]], null];
break;
case 29: $$[$0-1].push($$[$0]); this.$ = $$[$0-1];
break;
@@ -244,7 +266,7 @@ case 33: this.$ = new yy.IntegerNode($$[$0]);
break;
case 34: this.$ = new yy.BooleanNode($$[$0]);
break;
case 35: this.$ = new yy.DataNode($$[$0]);
case 35: this.$ = $$[$0];
break;
case 36: this.$ = new yy.HashNode($$[$0]);
break;
@@ -260,20 +282,26 @@ case 41: this.$ = [$$[$0-2], new yy.IntegerNode($$[$0])];
break;
case 42: this.$ = [$$[$0-2], new yy.BooleanNode($$[$0])];
break;
case 43: this.$ = [$$[$0-2], new yy.DataNode($$[$0])];
case 43: this.$ = [$$[$0-2], $$[$0]];
break;
case 44: this.$ = new yy.PartialNameNode($$[$0]);
break;
case 45: this.$ = new yy.IdNode($$[$0]);
case 45: this.$ = new yy.PartialNameNode(new yy.StringNode($$[$0]));
break;
case 46: $$[$0-2].push($$[$0]); this.$ = $$[$0-2];
case 46: this.$ = new yy.PartialNameNode(new yy.IntegerNode($$[$0]));
break;
case 47: this.$ = [$$[$0]];
case 47: this.$ = new yy.DataNode($$[$0]);
break;
case 48: this.$ = new yy.IdNode($$[$0]);
break;
case 49: $$[$0-2].push({part: $$[$0], separator: $$[$0-1]}); this.$ = $$[$0-2];
break;
case 50: this.$ = [{part: $$[$0]}];
break;
}
},
table: [{3:1,4:2,5:[2,7],6:3,7:4,8:6,9:7,11:8,12:9,13:10,14:[1,11],15:[1,12],16:[1,13],19:[1,5],22:[1,14],23:[1,15],24:[1,16]},{1:[3]},{5:[1,17]},{5:[2,6],7:18,8:6,9:7,11:8,12:9,13:10,14:[1,11],15:[1,12],16:[1,13],19:[1,19],20:[2,6],22:[1,14],23:[1,15],24:[1,16]},{5:[2,5],6:20,8:21,9:7,11:8,12:9,13:10,14:[1,11],15:[1,12],16:[1,13],19:[1,5],20:[2,5],22:[1,14],23:[1,15],24:[1,16]},{17:23,18:[1,22],21:24,28:[1,25],35:[1,27],38:26},{5:[2,8],14:[2,8],15:[2,8],16:[2,8],19:[2,8],20:[2,8],22:[2,8],23:[2,8],24:[2,8]},{4:28,6:3,7:4,8:6,9:7,11:8,12:9,13:10,14:[1,11],15:[1,12],16:[1,13],19:[1,5],20:[2,7],22:[1,14],23:[1,15],24:[1,16]},{4:29,6:3,7:4,8:6,9:7,11:8,12:9,13:10,14:[1,11],15:[1,12],16:[1,13],19:[1,5],20:[2,7],22:[1,14],23:[1,15],24:[1,16]},{5:[2,12],14:[2,12],15:[2,12],16:[2,12],19:[2,12],20:[2,12],22:[2,12],23:[2,12],24:[2,12]},{5:[2,13],14:[2,13],15:[2,13],16:[2,13],19:[2,13],20:[2,13],22:[2,13],23:[2,13],24:[2,13]},{5:[2,14],14:[2,14],15:[2,14],16:[2,14],19:[2,14],20:[2,14],22:[2,14],23:[2,14],24:[2,14]},{5:[2,15],14:[2,15],15:[2,15],16:[2,15],19:[2,15],20:[2,15],22:[2,15],23:[2,15],24:[2,15]},{17:30,21:24,28:[1,25],35:[1,27],38:26},{17:31,21:24,28:[1,25],35:[1,27],38:26},{17:32,21:24,28:[1,25],35:[1,27],38:26},{25:33,37:[1,34]},{1:[2,1]},{5:[2,2],8:21,9:7,11:8,12:9,13:10,14:[1,11],15:[1,12],16:[1,13],19:[1,19],20:[2,2],22:[1,14],23:[1,15],24:[1,16]},{17:23,21:24,28:[1,25],35:[1,27],38:26},{5:[2,4],7:35,8:6,9:7,11:8,12:9,13:10,14:[1,11],15:[1,12],16:[1,13],19:[1,19],20:[2,4],22:[1,14],23:[1,15],24:[1,16]},{5:[2,9],14:[2,9],15:[2,9],16:[2,9],19:[2,9],20:[2,9],22:[2,9],23:[2,9],24:[2,9]},{5:[2,23],14:[2,23],15:[2,23],16:[2,23],19:[2,23],20:[2,23],22:[2,23],23:[2,23],24:[2,23]},{18:[1,36]},{18:[2,27],21:41,26:37,27:38,28:[1,45],29:39,30:[1,42],31:[1,43],32:[1,44],33:40,34:46,35:[1,47],38:26},{18:[2,28]},{18:[2,45],28:[2,45],30:[2,45],31:[2,45],32:[2,45],35:[2,45],39:[1,48]},{18:[2,47],28:[2,47],30:[2,47],31:[2,47],32:[2,47],35:[2,47],39:[2,47]},{10:49,20:[1,50]},{10:51,20:[1,50]},{18:[1,52]},{18:[1,53]},{18:[1,54]},{18:[1,55],21:56,35:[1,27],38:26},{18:[2,44],35:[2,44]},{5:[2,3],8:21,9:7,11:8,12:9,13:10,14:[1,11],15:[1,12],16:[1,13],19:[1,19],20:[2,3],22:[1,14],23:[1,15],24:[1,16]},{14:[2,17],15:[2,17],16:[2,17],19:[2,17],20:[2,17],22:[2,17],23:[2,17],24:[2,17]},{18:[2,25],21:41,27:57,28:[1,45],29:58,30:[1,42],31:[1,43],32:[1,44],33:40,34:46,35:[1,47],38:26},{18:[2,26]},{18:[2,30],28:[2,30],30:[2,30],31:[2,30],32:[2,30],35:[2,30]},{18:[2,36],34:59,35:[1,60]},{18:[2,31],28:[2,31],30:[2,31],31:[2,31],32:[2,31],35:[2,31]},{18:[2,32],28:[2,32],30:[2,32],31:[2,32],32:[2,32],35:[2,32]},{18:[2,33],28:[2,33],30:[2,33],31:[2,33],32:[2,33],35:[2,33]},{18:[2,34],28:[2,34],30:[2,34],31:[2,34],32:[2,34],35:[2,34]},{18:[2,35],28:[2,35],30:[2,35],31:[2,35],32:[2,35],35:[2,35]},{18:[2,38],35:[2,38]},{18:[2,47],28:[2,47],30:[2,47],31:[2,47],32:[2,47],35:[2,47],36:[1,61],39:[2,47]},{35:[1,62]},{5:[2,10],14:[2,10],15:[2,10],16:[2,10],19:[2,10],20:[2,10],22:[2,10],23:[2,10],24:[2,10]},{21:63,35:[1,27],38:26},{5:[2,11],14:[2,11],15:[2,11],16:[2,11],19:[2,11],20:[2,11],22:[2,11],23:[2,11],24:[2,11]},{14:[2,16],15:[2,16],16:[2,16],19:[2,16],20:[2,16],22:[2,16],23:[2,16],24:[2,16]},{5:[2,19],14:[2,19],15:[2,19],16:[2,19],19:[2,19],20:[2,19],22:[2,19],23:[2,19],24:[2,19]},{5:[2,20],14:[2,20],15:[2,20],16:[2,20],19:[2,20],20:[2,20],22:[2,20],23:[2,20],24:[2,20]},{5:[2,21],14:[2,21],15:[2,21],16:[2,21],19:[2,21],20:[2,21],22:[2,21],23:[2,21],24:[2,21]},{18:[1,64]},{18:[2,24]},{18:[2,29],28:[2,29],30:[2,29],31:[2,29],32:[2,29],35:[2,29]},{18:[2,37],35:[2,37]},{36:[1,61]},{21:65,28:[1,69],30:[1,66],31:[1,67],32:[1,68],35:[1,27],38:26},{18:[2,46],28:[2,46],30:[2,46],31:[2,46],32:[2,46],35:[2,46],39:[2,46]},{18:[1,70]},{5:[2,22],14:[2,22],15:[2,22],16:[2,22],19:[2,22],20:[2,22],22:[2,22],23:[2,22],24:[2,22]},{18:[2,39],35:[2,39]},{18:[2,40],35:[2,40]},{18:[2,41],35:[2,41]},{18:[2,42],35:[2,42]},{18:[2,43],35:[2,43]},{5:[2,18],14:[2,18],15:[2,18],16:[2,18],19:[2,18],20:[2,18],22:[2,18],23:[2,18],24:[2,18]}],
defaultActions: {17:[2,1],25:[2,28],38:[2,26],57:[2,24]},
table: [{3:1,4:2,5:[2,7],6:3,7:4,8:6,9:7,11:8,12:9,13:10,14:[1,11],15:[1,12],16:[1,13],19:[1,5],22:[1,14],23:[1,15],25:[1,16]},{1:[3]},{5:[1,17]},{5:[2,6],7:18,8:6,9:7,11:8,12:9,13:10,14:[1,11],15:[1,12],16:[1,13],19:[1,19],20:[2,6],22:[1,14],23:[1,15],25:[1,16]},{5:[2,5],6:20,8:21,9:7,11:8,12:9,13:10,14:[1,11],15:[1,12],16:[1,13],19:[1,5],20:[2,5],22:[1,14],23:[1,15],25:[1,16]},{17:23,18:[1,22],21:24,29:25,36:[1,28],38:[1,27],39:26},{5:[2,8],14:[2,8],15:[2,8],16:[2,8],19:[2,8],20:[2,8],22:[2,8],23:[2,8],25:[2,8]},{4:29,6:3,7:4,8:6,9:7,11:8,12:9,13:10,14:[1,11],15:[1,12],16:[1,13],19:[1,5],20:[2,7],22:[1,14],23:[1,15],25:[1,16]},{4:30,6:3,7:4,8:6,9:7,11:8,12:9,13:10,14:[1,11],15:[1,12],16:[1,13],19:[1,5],20:[2,7],22:[1,14],23:[1,15],25:[1,16]},{5:[2,12],14:[2,12],15:[2,12],16:[2,12],19:[2,12],20:[2,12],22:[2,12],23:[2,12],25:[2,12]},{5:[2,13],14:[2,13],15:[2,13],16:[2,13],19:[2,13],20:[2,13],22:[2,13],23:[2,13],25:[2,13]},{5:[2,14],14:[2,14],15:[2,14],16:[2,14],19:[2,14],20:[2,14],22:[2,14],23:[2,14],25:[2,14]},{5:[2,15],14:[2,15],15:[2,15],16:[2,15],19:[2,15],20:[2,15],22:[2,15],23:[2,15],25:[2,15]},{17:31,21:24,29:25,36:[1,28],38:[1,27],39:26},{17:32,21:24,29:25,36:[1,28],38:[1,27],39:26},{17:33,21:24,29:25,36:[1,28],38:[1,27],39:26},{21:35,26:34,31:[1,36],32:[1,37],36:[1,28],39:26},{1:[2,1]},{5:[2,2],8:21,9:7,11:8,12:9,13:10,14:[1,11],15:[1,12],16:[1,13],19:[1,19],20:[2,2],22:[1,14],23:[1,15],25:[1,16]},{17:23,21:24,29:25,36:[1,28],38:[1,27],39:26},{5:[2,4],7:38,8:6,9:7,11:8,12:9,13:10,14:[1,11],15:[1,12],16:[1,13],19:[1,19],20:[2,4],22:[1,14],23:[1,15],25:[1,16]},{5:[2,9],14:[2,9],15:[2,9],16:[2,9],19:[2,9],20:[2,9],22:[2,9],23:[2,9],25:[2,9]},{5:[2,23],14:[2,23],15:[2,23],16:[2,23],19:[2,23],20:[2,23],22:[2,23],23:[2,23],25:[2,23]},{18:[1,39]},{18:[2,27],21:44,24:[2,27],27:40,28:41,29:48,30:42,31:[1,45],32:[1,46],33:[1,47],34:43,35:49,36:[1,50],38:[1,27],39:26},{18:[2,28],24:[2,28]},{18:[2,48],24:[2,48],31:[2,48],32:[2,48],33:[2,48],36:[2,48],38:[2,48],40:[1,51]},{21:52,36:[1,28],39:26},{18:[2,50],24:[2,50],31:[2,50],32:[2,50],33:[2,50],36:[2,50],38:[2,50],40:[2,50]},{10:53,20:[1,54]},{10:55,20:[1,54]},{18:[1,56]},{18:[1,57]},{24:[1,58]},{18:[1,59],21:60,36:[1,28],39:26},{18:[2,44],36:[2,44]},{18:[2,45],36:[2,45]},{18:[2,46],36:[2,46]},{5:[2,3],8:21,9:7,11:8,12:9,13:10,14:[1,11],15:[1,12],16:[1,13],19:[1,19],20:[2,3],22:[1,14],23:[1,15],25:[1,16]},{14:[2,17],15:[2,17],16:[2,17],19:[2,17],20:[2,17],22:[2,17],23:[2,17],25:[2,17]},{18:[2,25],21:44,24:[2,25],28:61,29:48,30:62,31:[1,45],32:[1,46],33:[1,47],34:43,35:49,36:[1,50],38:[1,27],39:26},{18:[2,26],24:[2,26]},{18:[2,30],24:[2,30],31:[2,30],32:[2,30],33:[2,30],36:[2,30],38:[2,30]},{18:[2,36],24:[2,36],35:63,36:[1,64]},{18:[2,31],24:[2,31],31:[2,31],32:[2,31],33:[2,31],36:[2,31],38:[2,31]},{18:[2,32],24:[2,32],31:[2,32],32:[2,32],33:[2,32],36:[2,32],38:[2,32]},{18:[2,33],24:[2,33],31:[2,33],32:[2,33],33:[2,33],36:[2,33],38:[2,33]},{18:[2,34],24:[2,34],31:[2,34],32:[2,34],33:[2,34],36:[2,34],38:[2,34]},{18:[2,35],24:[2,35],31:[2,35],32:[2,35],33:[2,35],36:[2,35],38:[2,35]},{18:[2,38],24:[2,38],36:[2,38]},{18:[2,50],24:[2,50],31:[2,50],32:[2,50],33:[2,50],36:[2,50],37:[1,65],38:[2,50],40:[2,50]},{36:[1,66]},{18:[2,47],24:[2,47],31:[2,47],32:[2,47],33:[2,47],36:[2,47],38:[2,47]},{5:[2,10],14:[2,10],15:[2,10],16:[2,10],19:[2,10],20:[2,10],22:[2,10],23:[2,10],25:[2,10]},{21:67,36:[1,28],39:26},{5:[2,11],14:[2,11],15:[2,11],16:[2,11],19:[2,11],20:[2,11],22:[2,11],23:[2,11],25:[2,11]},{14:[2,16],15:[2,16],16:[2,16],19:[2,16],20:[2,16],22:[2,16],23:[2,16],25:[2,16]},{5:[2,19],14:[2,19],15:[2,19],16:[2,19],19:[2,19],20:[2,19],22:[2,19],23:[2,19],25:[2,19]},{5:[2,20],14:[2,20],15:[2,20],16:[2,20],19:[2,20],20:[2,20],22:[2,20],23:[2,20],25:[2,20]},{5:[2,21],14:[2,21],15:[2,21],16:[2,21],19:[2,21],20:[2,21],22:[2,21],23:[2,21],25:[2,21]},{18:[1,68]},{18:[2,24],24:[2,24]},{18:[2,29],24:[2,29],31:[2,29],32:[2,29],33:[2,29],36:[2,29],38:[2,29]},{18:[2,37],24:[2,37],36:[2,37]},{37:[1,65]},{21:69,29:73,31:[1,70],32:[1,71],33:[1,72],36:[1,28],38:[1,27],39:26},{18:[2,49],24:[2,49],31:[2,49],32:[2,49],33:[2,49],36:[2,49],38:[2,49],40:[2,49]},{18:[1,74]},{5:[2,22],14:[2,22],15:[2,22],16:[2,22],19:[2,22],20:[2,22],22:[2,22],23:[2,22],25:[2,22]},{18:[2,39],24:[2,39],36:[2,39]},{18:[2,40],24:[2,40],36:[2,40]},{18:[2,41],24:[2,41],36:[2,41]},{18:[2,42],24:[2,42],36:[2,42]},{18:[2,43],24:[2,43],36:[2,43]},{5:[2,18],14:[2,18],15:[2,18],16:[2,18],19:[2,18],20:[2,18],22:[2,18],23:[2,18],25:[2,18]}],
defaultActions: {17:[2,1]},
parseError: function parseError(str, hash) {
throw new Error(str);
},
@@ -554,84 +582,82 @@ lexer.performAction = function anonymous(yy,yy_,$avoiding_name_collisions,YY_STA
var YYSTATE=YY_START
switch($avoiding_name_collisions) {
case 0:
case 0: yy_.yytext = "\\"; return 14;
break;
case 1:
if(yy_.yytext.slice(-1) !== "\\") this.begin("mu");
if(yy_.yytext.slice(-1) === "\\") yy_.yytext = yy_.yytext.substr(0,yy_.yyleng-1), this.begin("emu");
if(yy_.yytext) return 14;
break;
case 1: return 14;
case 2: return 14;
break;
case 2:
case 3:
if(yy_.yytext.slice(-1) !== "\\") this.popState();
if(yy_.yytext.slice(-1) === "\\") yy_.yytext = yy_.yytext.substr(0,yy_.yyleng-1);
return 14;
break;
case 3: yy_.yytext = yy_.yytext.substr(0, yy_.yyleng-4); this.popState(); return 15;
case 4: yy_.yytext = yy_.yytext.substr(0, yy_.yyleng-4); this.popState(); return 15;
break;
case 4: this.begin("par"); return 24;
case 5: return 25;
break;
case 5: return 16;
case 6: return 16;
break;
case 6: return 20;
break;
case 7: return 19;
case 7: return 20;
break;
case 8: return 19;
break;
case 9: return 23;
case 9: return 19;
break;
case 10: return 23;
break;
case 11: this.popState(); this.begin('com');
case 11: return 22;
break;
case 12: yy_.yytext = yy_.yytext.substr(3,yy_.yyleng-5); this.popState(); return 15;
case 12: this.popState(); this.begin('com');
break;
case 13: return 22;
case 13: yy_.yytext = yy_.yytext.substr(3,yy_.yyleng-5); this.popState(); return 15;
break;
case 14: return 36;
case 14: return 22;
break;
case 15: return 35;
case 15: return 37;
break;
case 16: return 35;
case 16: return 36;
break;
case 17: return 39;
case 17: return 36;
break;
case 18: /*ignore whitespace*/
case 18: return 40;
break;
case 19: this.popState(); return 18;
case 19: /*ignore whitespace*/
break;
case 20: this.popState(); return 18;
case 20: this.popState(); return 24;
break;
case 21: yy_.yytext = yy_.yytext.substr(1,yy_.yyleng-2).replace(/\\"/g,'"'); return 30;
case 21: this.popState(); return 18;
break;
case 22: yy_.yytext = yy_.yytext.substr(1,yy_.yyleng-2).replace(/\\'/g,"'"); return 30;
case 22: yy_.yytext = yy_.yytext.substr(1,yy_.yyleng-2).replace(/\\"/g,'"'); return 31;
break;
case 23: yy_.yytext = yy_.yytext.substr(1); return 28;
case 23: yy_.yytext = yy_.yytext.substr(1,yy_.yyleng-2).replace(/\\'/g,"'"); return 31;
break;
case 24: return 32;
case 24: return 38;
break;
case 25: return 32;
case 25: return 33;
break;
case 26: return 31;
case 26: return 33;
break;
case 27: return 35;
case 27: return 32;
break;
case 28: yy_.yytext = yy_.yytext.substr(1, yy_.yyleng-2); return 35;
case 28: return 36;
break;
case 29: return 'INVALID';
case 29: yy_.yytext = yy_.yytext.substr(1, yy_.yyleng-2); return 36;
break;
case 30: /*ignore whitespace*/
case 30: return 'INVALID';
break;
case 31: this.popState(); return 37;
break;
case 32: return 5;
case 31: return 5;
break;
}
};
lexer.rules = [/^(?:[^\x00]*?(?=(\{\{)))/,/^(?:[^\x00]+)/,/^(?:[^\x00]{2,}?(?=(\{\{|$)))/,/^(?:[\s\S]*?--\}\})/,/^(?:\{\{>)/,/^(?:\{\{#)/,/^(?:\{\{\/)/,/^(?:\{\{\^)/,/^(?:\{\{\s*else\b)/,/^(?:\{\{\{)/,/^(?:\{\{&)/,/^(?:\{\{!--)/,/^(?:\{\{![\s\S]*?\}\})/,/^(?:\{\{)/,/^(?:=)/,/^(?:\.(?=[} ]))/,/^(?:\.\.)/,/^(?:[\/.])/,/^(?:\s+)/,/^(?:\}\}\})/,/^(?:\}\})/,/^(?:"(\\["]|[^"])*")/,/^(?:'(\\[']|[^'])*')/,/^(?:@[a-zA-Z]+)/,/^(?:true(?=[}\s]))/,/^(?:false(?=[}\s]))/,/^(?:-?[0-9]+(?=[}\s]))/,/^(?:[a-zA-Z0-9_$-]+(?=[=}\s\/.]))/,/^(?:\[[^\]]*\])/,/^(?:.)/,/^(?:\s+)/,/^(?:[a-zA-Z0-9_$-/]+)/,/^(?:$)/];
lexer.conditions = {"mu":{"rules":[4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,32],"inclusive":false},"emu":{"rules":[2],"inclusive":false},"com":{"rules":[3],"inclusive":false},"par":{"rules":[30,31],"inclusive":false},"INITIAL":{"rules":[0,1,32],"inclusive":true}};
lexer.rules = [/^(?:\\\\(?=(\{\{)))/,/^(?:[^\x00]*?(?=(\{\{)))/,/^(?:[^\x00]+)/,/^(?:[^\x00]{2,}?(?=(\{\{|$)))/,/^(?:[\s\S]*?--\}\})/,/^(?:\{\{>)/,/^(?:\{\{#)/,/^(?:\{\{\/)/,/^(?:\{\{\^)/,/^(?:\{\{\s*else\b)/,/^(?:\{\{\{)/,/^(?:\{\{&)/,/^(?:\{\{!--)/,/^(?:\{\{![\s\S]*?\}\})/,/^(?:\{\{)/,/^(?:=)/,/^(?:\.(?=[}\/ ]))/,/^(?:\.\.)/,/^(?:[\/.])/,/^(?:\s+)/,/^(?:\}\}\})/,/^(?:\}\})/,/^(?:"(\\["]|[^"])*")/,/^(?:'(\\[']|[^'])*')/,/^(?:@)/,/^(?:true(?=[}\s]))/,/^(?:false(?=[}\s]))/,/^(?:-?[0-9]+(?=[}\s]))/,/^(?:[^\s!"#%-,\.\/;->@\[-\^`\{-~]+(?=[=}\s\/.]))/,/^(?:\[[^\]]*\])/,/^(?:.)/,/^(?:$)/];
lexer.conditions = {"mu":{"rules":[5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31],"inclusive":false},"emu":{"rules":[3],"inclusive":false},"com":{"rules":[4],"inclusive":false},"INITIAL":{"rules":[0,1,2,31],"inclusive":true}};
return lexer;})()
parser.lexer = lexer;
function Parser () { this.yy = {}; }Parser.prototype = parser;parser.Parser = Parser;
@@ -717,21 +743,24 @@ Handlebars.AST.HashNode = function(pairs) {
Handlebars.AST.IdNode = function(parts) {
this.type = "ID";
this.original = parts.join(".");
var dig = [], depth = 0;
var original = "",
dig = [],
depth = 0;
for(var i=0,l=parts.length; i<l; i++) {
var part = parts[i];
var part = parts[i].part;
original += (parts[i].separator || '') + part;
if (part === ".." || part === "." || part === "this") {
if (dig.length > 0) { throw new Handlebars.Exception("Invalid path: " + this.original); }
if (dig.length > 0) { throw new Handlebars.Exception("Invalid path: " + original); }
else if (part === "..") { depth++; }
else { this.isScoped = true; }
}
else { dig.push(part); }
}
this.original = original;
this.parts = dig;
this.string = dig.join('.');
this.depth = depth;
@@ -745,7 +774,7 @@ Handlebars.AST.IdNode = function(parts) {
Handlebars.AST.PartialNameNode = function(name) {
this.type = "PARTIAL_NAME";
this.name = name;
this.name = name.original;
};
Handlebars.AST.DataNode = function(id) {
@@ -755,13 +784,15 @@ Handlebars.AST.DataNode = function(id) {
Handlebars.AST.StringNode = function(string) {
this.type = "STRING";
this.string = string;
this.stringModeValue = string;
this.original =
this.string =
this.stringModeValue = string;
};
Handlebars.AST.IntegerNode = function(integer) {
this.type = "INTEGER";
this.integer = integer;
this.original =
this.integer = integer;
this.stringModeValue = Number(integer);
};
@@ -815,6 +846,14 @@ var escapeChar = function(chr) {
};
Handlebars.Utils = {
extend: function(obj, value) {
for(var key in value) {
if(value.hasOwnProperty(key)) {
obj[key] = value[key];
}
}
},
escapeExpression: function(string) {
// don't escape SafeStrings, since they're already safe
if (string instanceof Handlebars.SafeString) {
@@ -823,6 +862,11 @@ Handlebars.Utils = {
return "";
}
// Force a string conversion as this will be done by the append regardless and
// the regex test will do this transparently behind the scenes, causing issues if
// an object's to string has escaped characters in it.
string = string.toString();
if(!possible.test(string)) { return string; }
return string.replace(badChars, escapeChar);
},
@@ -1023,6 +1067,10 @@ Compiler.prototype = {
val = pair[1];
if (this.options.stringParams) {
if(val.depth) {
this.addDepth(val.depth);
}
this.opcode('getContext', val.depth || 0);
this.opcode('pushStringParam', val.stringModeValue, val.type);
} else {
this.accept(val);
@@ -1106,7 +1154,7 @@ Compiler.prototype = {
if (this.options.knownHelpers[name]) {
this.opcode('invokeKnownHelper', params.length, name);
} else if (this.knownHelpersOnly) {
} else if (this.options.knownHelpersOnly) {
throw new Error("You specified knownHelpersOnly, but used the unknown helper " + name);
} else {
this.opcode('invokeHelper', params.length, name);
@@ -1131,7 +1179,15 @@ Compiler.prototype = {
DATA: function(data) {
this.options.data = true;
this.opcode('lookupData', data.id);
if (data.id.isScoped || data.id.depth) {
throw new Handlebars.Exception('Scoped data references are not supported: ' + data.original);
}
this.opcode('lookupData');
var parts = data.id.parts;
for(var i=0, l=parts.length; i<l; i++) {
this.opcode('lookup', parts[i]);
}
},
STRING: function(string) {
@@ -1330,8 +1386,9 @@ JavaScriptCompiler.prototype = {
if (!this.isChild) {
var namespace = this.namespace;
var copies = "helpers = helpers || " + namespace + ".helpers;";
if (this.environment.usePartial) { copies = copies + " partials = partials || " + namespace + ".partials;"; }
var copies = "helpers = this.merge(helpers, " + namespace + ".helpers);";
if (this.environment.usePartial) { copies = copies + " partials = this.merge(partials, " + namespace + ".partials);"; }
if (this.options.data) { copies = copies + " data = data || {};"; }
out.push(copies);
} else {
@@ -1360,7 +1417,9 @@ JavaScriptCompiler.prototype = {
// Generate minimizer alias mappings
if (!this.isChild) {
for (var alias in this.context.aliases) {
this.source[1] = this.source[1] + ', ' + alias + '=' + this.context.aliases[alias];
if (this.context.aliases.hasOwnProperty(alias)) {
this.source[1] = this.source[1] + ', ' + alias + '=' + this.context.aliases[alias];
}
}
}
@@ -1579,7 +1638,7 @@ JavaScriptCompiler.prototype = {
//
// Push the result of looking up `id` on the current data
lookupData: function(id) {
this.push(this.nameLookup('data', id, 'data'));
this.push('data');
},
// [pushStringParam]
@@ -1607,16 +1666,18 @@ JavaScriptCompiler.prototype = {
if (this.options.stringParams) {
this.register('hashTypes', '{}');
this.register('hashContexts', '{}');
}
},
pushHash: function() {
this.hash = {values: [], types: []};
this.hash = {values: [], types: [], contexts: []};
},
popHash: function() {
var hash = this.hash;
this.hash = undefined;
if (this.options.stringParams) {
this.register('hashContexts', '{' + hash.contexts.join(',') + '}');
this.register('hashTypes', '{' + hash.types.join(',') + '}');
}
this.push('{\n ' + hash.values.join(',\n ') + '\n }');
@@ -1684,8 +1745,9 @@ JavaScriptCompiler.prototype = {
this.context.aliases.helperMissing = 'helpers.helperMissing';
var helper = this.lastHelper = this.setupHelper(paramSize, name, true);
var nonHelper = this.nameLookup('depth' + this.lastContext, name, 'context');
this.push(helper.name);
this.push(helper.name + ' || ' + nonHelper);
this.replaceStack(function(name) {
return name + ' ? ' + name + '.call(' +
helper.callParams + ") " + ": helperMissing.call(" +
@@ -1759,14 +1821,18 @@ JavaScriptCompiler.prototype = {
// and pushes the hash back onto the stack.
assignToHash: function(key) {
var value = this.popStack(),
context,
type;
if (this.options.stringParams) {
type = this.popStack();
this.popStack();
context = this.popStack();
}
var hash = this.hash;
if (context) {
hash.contexts.push("'" + key + "': " + context);
}
if (type) {
hash.types.push("'" + key + "': " + type);
}
@@ -1827,12 +1893,7 @@ JavaScriptCompiler.prototype = {
else { programParams.push("depth" + (depth - 1)); }
}
if(depths.length === 0) {
return "self.program(" + programParams.join(", ") + ")";
} else {
programParams.shift();
return "self.programWithDepth(" + programParams.join(", ") + ")";
}
return (depths.length === 0 ? "self.program(" : "self.programWithDepth(") + programParams.join(", ") + ")";
},
register: function(name, val) {
@@ -1964,7 +2025,9 @@ JavaScriptCompiler.prototype = {
.replace(/\\/g, '\\\\')
.replace(/"/g, '\\"')
.replace(/\n/g, '\\n')
.replace(/\r/g, '\\r') + '"';
.replace(/\r/g, '\\r')
.replace(/\u2028/g, '\\u2028') // Per Ecma-262 7.3 + 7.8.4
.replace(/\u2029/g, '\\u2029') + '"';
},
setupHelper: function(paramSize, name, missingParams) {
@@ -2020,6 +2083,7 @@ JavaScriptCompiler.prototype = {
if (this.options.stringParams) {
options.push("contexts:[" + contexts.join(",") + "]");
options.push("types:[" + types.join(",") + "]");
options.push("hashContexts:hashContexts");
options.push("hashTypes:hashTypes");
}
@@ -2070,7 +2134,7 @@ JavaScriptCompiler.isValidJavaScriptVariableName = function(name) {
};
Handlebars.precompile = function(input, options) {
if (!input || (typeof input !== 'string' && input.constructor !== Handlebars.AST.ProgramNode)) {
if (input == null || (typeof input !== 'string' && input.constructor !== Handlebars.AST.ProgramNode)) {
throw new Handlebars.Exception("You must pass a string or Handlebars AST to Handlebars.precompile. You passed " + input);
}
@@ -2084,7 +2148,7 @@ Handlebars.precompile = function(input, options) {
};
Handlebars.compile = function(input, options) {
if (!input || (typeof input !== 'string' && input.constructor !== Handlebars.AST.ProgramNode)) {
if (input == null || (typeof input !== 'string' && input.constructor !== Handlebars.AST.ProgramNode)) {
throw new Handlebars.Exception("You must pass a string or Handlebars AST to Handlebars.compile. You passed " + input);
}
@@ -2122,13 +2186,21 @@ Handlebars.VM = {
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;
programWrapper = Handlebars.VM.program(i, fn, data);
} else if (!programWrapper) {
programWrapper = this.programs[i] = Handlebars.VM.program(i, fn);
}
return programWrapper;
},
merge: function(param, common) {
var ret = param || common;
if (param && common) {
ret = {};
Handlebars.Utils.extend(ret, common);
Handlebars.Utils.extend(ret, param);
}
return ret;
},
programWithDepth: Handlebars.VM.programWithDepth,
noop: Handlebars.VM.noop,
@@ -2160,21 +2232,27 @@ Handlebars.VM = {
};
},
programWithDepth: function(fn, data, $depth) {
var args = Array.prototype.slice.call(arguments, 2);
programWithDepth: function(i, fn, data /*, $depth */) {
var args = Array.prototype.slice.call(arguments, 3);
return function(context, options) {
var program = function(context, options) {
options = options || {};
return fn.apply(this, [context, options.data || data].concat(args));
};
program.program = i;
program.depth = args.length;
return program;
},
program: function(fn, data) {
return function(context, options) {
program: function(i, fn, data) {
var program = function(context, options) {
options = options || {};
return fn(context, options.data || data);
};
program.program = i;
program.depth = 0;
return program;
},
noop: function() { return ""; },
invokePartial: function(partial, name, context, helpers, partials, data) {
+73 -27
View File
@@ -29,36 +29,49 @@ var Handlebars = {};
;
// lib/handlebars/base.js
Handlebars.VERSION = "1.0.0-rc.3";
Handlebars.COMPILER_REVISION = 2;
Handlebars.VERSION = "1.0.0";
Handlebars.COMPILER_REVISION = 4;
Handlebars.REVISION_CHANGES = {
1: '<= 1.0.rc.2', // 1.0.rc.2 is actually rev2 but doesn't report it
2: '>= 1.0.0-rc.3'
2: '== 1.0.0-rc.3',
3: '== 1.0.0-rc.4',
4: '>= 1.0.0'
};
Handlebars.helpers = {};
Handlebars.partials = {};
var toString = Object.prototype.toString,
functionType = '[object Function]',
objectType = '[object Object]';
Handlebars.registerHelper = function(name, fn, inverse) {
if(inverse) { fn.not = inverse; }
this.helpers[name] = fn;
if (toString.call(name) === objectType) {
if (inverse || fn) { throw new Handlebars.Exception('Arg not supported with multiple helpers'); }
Handlebars.Utils.extend(this.helpers, name);
} else {
if (inverse) { fn.not = inverse; }
this.helpers[name] = fn;
}
};
Handlebars.registerPartial = function(name, str) {
this.partials[name] = str;
if (toString.call(name) === objectType) {
Handlebars.Utils.extend(this.partials, name);
} else {
this.partials[name] = str;
}
};
Handlebars.registerHelper('helperMissing', function(arg) {
if(arguments.length === 2) {
return undefined;
} else {
throw new Error("Could not find property '" + arg + "'");
throw new Error("Missing helper: '" + arg + "'");
}
});
var toString = Object.prototype.toString, functionType = "[object Function]";
Handlebars.registerHelper('blockHelperMissing', function(context, options) {
var inverse = options.inverse || function() {}, fn = options.fn;
@@ -112,6 +125,9 @@ Handlebars.registerHelper('each', function(context, options) {
var fn = options.fn, inverse = options.inverse;
var i = 0, ret = "", data;
var type = toString.call(context);
if(type === functionType) { context = context.call(this); }
if (options.data) {
data = Handlebars.createFrame(options.data);
}
@@ -140,23 +156,26 @@ Handlebars.registerHelper('each', function(context, options) {
return ret;
});
Handlebars.registerHelper('if', function(context, options) {
var type = toString.call(context);
if(type === functionType) { context = context.call(this); }
Handlebars.registerHelper('if', function(conditional, options) {
var type = toString.call(conditional);
if(type === functionType) { conditional = conditional.call(this); }
if(!context || Handlebars.Utils.isEmpty(context)) {
if(!conditional || Handlebars.Utils.isEmpty(conditional)) {
return options.inverse(this);
} else {
return options.fn(this);
}
});
Handlebars.registerHelper('unless', function(context, options) {
return Handlebars.helpers['if'].call(this, context, {fn: options.inverse, inverse: options.fn});
Handlebars.registerHelper('unless', function(conditional, options) {
return Handlebars.helpers['if'].call(this, conditional, {fn: options.inverse, inverse: options.fn});
});
Handlebars.registerHelper('with', function(context, options) {
return options.fn(context);
var type = toString.call(context);
if(type === functionType) { context = context.call(this); }
if (!Handlebars.Utils.isEmpty(context)) return options.fn(context);
});
Handlebars.registerHelper('log', function(context, options) {
@@ -203,6 +222,14 @@ var escapeChar = function(chr) {
};
Handlebars.Utils = {
extend: function(obj, value) {
for(var key in value) {
if(value.hasOwnProperty(key)) {
obj[key] = value[key];
}
}
},
escapeExpression: function(string) {
// don't escape SafeStrings, since they're already safe
if (string instanceof Handlebars.SafeString) {
@@ -211,6 +238,11 @@ Handlebars.Utils = {
return "";
}
// Force a string conversion as this will be done by the append regardless and
// the regex test will do this transparently behind the scenes, causing issues if
// an object's to string has escaped characters in it.
string = string.toString();
if(!possible.test(string)) { return string; }
return string.replace(badChars, escapeChar);
},
@@ -238,13 +270,21 @@ Handlebars.VM = {
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;
programWrapper = Handlebars.VM.program(i, fn, data);
} else if (!programWrapper) {
programWrapper = this.programs[i] = Handlebars.VM.program(i, fn);
}
return programWrapper;
},
merge: function(param, common) {
var ret = param || common;
if (param && common) {
ret = {};
Handlebars.Utils.extend(ret, common);
Handlebars.Utils.extend(ret, param);
}
return ret;
},
programWithDepth: Handlebars.VM.programWithDepth,
noop: Handlebars.VM.noop,
@@ -276,21 +316,27 @@ Handlebars.VM = {
};
},
programWithDepth: function(fn, data, $depth) {
var args = Array.prototype.slice.call(arguments, 2);
programWithDepth: function(i, fn, data /*, $depth */) {
var args = Array.prototype.slice.call(arguments, 3);
return function(context, options) {
var program = function(context, options) {
options = options || {};
return fn.apply(this, [context, options.data || data].concat(args));
};
program.program = i;
program.depth = args.length;
return program;
},
program: function(fn, data) {
return function(context, options) {
program: function(i, fn, data) {
var program = function(context, options) {
options = options || {};
return fn(context, options.data || data);
};
program.program = i;
program.depth = 0;
return program;
},
noop: function() { return ""; },
invokePartial: function(partial, name, context, helpers, partials, data) {
+17
View File
@@ -0,0 +1,17 @@
<?xml version="1.0"?>
<package>
<metadata>
<id>handlebars.js</id>
<version>1.0.0</version>
<authors>handlebars.js Authors</authors>
<licenseUrl>https://github.com/wycats/handlebars.js/blob/master/LICENSE</licenseUrl>
<projectUrl>https://github.com/wycats/handlebars.js/</projectUrl>
<requireLicenseAcceptance>false</requireLicenseAcceptance>
<description>Extension of the Mustache logicless template language</description>
<releaseNotes></releaseNotes>
<tags>handlebars mustache template html</tags>
</metadata>
<files>
<file src="dist\handlebars.js" target="Content\Scripts" />
</files>
</package>
+11
View File
@@ -21,6 +21,17 @@ Handlebars.create = create;
module.exports = Handlebars; // instantiate an instance
// Publish a Node.js require() handler for .handlebars and .hbs files
if (require.extensions) {
var extension = function(module, filename) {
var fs = require("fs");
var templateString = fs.readFileSync(filename, "utf8");
module.exports = Handlebars.compile(templateString);
};
require.extensions[".handlebars"] = extension;
require.extensions[".hbs"] = extension;
}
// BEGIN(BROWSER)
// END(BROWSER)
+35 -16
View File
@@ -6,36 +6,49 @@ var Handlebars = {};
// BEGIN(BROWSER)
Handlebars.VERSION = "1.0.0-rc.3";
Handlebars.COMPILER_REVISION = 2;
Handlebars.VERSION = "1.0.0";
Handlebars.COMPILER_REVISION = 4;
Handlebars.REVISION_CHANGES = {
1: '<= 1.0.rc.2', // 1.0.rc.2 is actually rev2 but doesn't report it
2: '>= 1.0.0-rc.3'
2: '== 1.0.0-rc.3',
3: '== 1.0.0-rc.4',
4: '>= 1.0.0'
};
Handlebars.helpers = {};
Handlebars.partials = {};
var toString = Object.prototype.toString,
functionType = '[object Function]',
objectType = '[object Object]';
Handlebars.registerHelper = function(name, fn, inverse) {
if(inverse) { fn.not = inverse; }
this.helpers[name] = fn;
if (toString.call(name) === objectType) {
if (inverse || fn) { throw new Handlebars.Exception('Arg not supported with multiple helpers'); }
Handlebars.Utils.extend(this.helpers, name);
} else {
if (inverse) { fn.not = inverse; }
this.helpers[name] = fn;
}
};
Handlebars.registerPartial = function(name, str) {
this.partials[name] = str;
if (toString.call(name) === objectType) {
Handlebars.Utils.extend(this.partials, name);
} else {
this.partials[name] = str;
}
};
Handlebars.registerHelper('helperMissing', function(arg) {
if(arguments.length === 2) {
return undefined;
} else {
throw new Error("Could not find property '" + arg + "'");
throw new Error("Missing helper: '" + arg + "'");
}
});
var toString = Object.prototype.toString, functionType = "[object Function]";
Handlebars.registerHelper('blockHelperMissing', function(context, options) {
var inverse = options.inverse || function() {}, fn = options.fn;
@@ -89,6 +102,9 @@ Handlebars.registerHelper('each', function(context, options) {
var fn = options.fn, inverse = options.inverse;
var i = 0, ret = "", data;
var type = toString.call(context);
if(type === functionType) { context = context.call(this); }
if (options.data) {
data = Handlebars.createFrame(options.data);
}
@@ -117,23 +133,26 @@ Handlebars.registerHelper('each', function(context, options) {
return ret;
});
Handlebars.registerHelper('if', function(context, options) {
var type = toString.call(context);
if(type === functionType) { context = context.call(this); }
Handlebars.registerHelper('if', function(conditional, options) {
var type = toString.call(conditional);
if(type === functionType) { conditional = conditional.call(this); }
if(!context || Handlebars.Utils.isEmpty(context)) {
if(!conditional || Handlebars.Utils.isEmpty(conditional)) {
return options.inverse(this);
} else {
return options.fn(this);
}
});
Handlebars.registerHelper('unless', function(context, options) {
return Handlebars.helpers['if'].call(this, context, {fn: options.inverse, inverse: options.fn});
Handlebars.registerHelper('unless', function(conditional, options) {
return Handlebars.helpers['if'].call(this, conditional, {fn: options.inverse, inverse: options.fn});
});
Handlebars.registerHelper('with', function(context, options) {
return options.fn(context);
var type = toString.call(context);
if(type === functionType) { context = context.call(this); }
if (!Handlebars.Utils.isEmpty(context)) return options.fn(context);
});
Handlebars.registerHelper('log', function(context, options) {
+13 -8
View File
@@ -67,21 +67,24 @@ Handlebars.AST.HashNode = function(pairs) {
Handlebars.AST.IdNode = function(parts) {
this.type = "ID";
this.original = parts.join(".");
var dig = [], depth = 0;
var original = "",
dig = [],
depth = 0;
for(var i=0,l=parts.length; i<l; i++) {
var part = parts[i];
var part = parts[i].part;
original += (parts[i].separator || '') + part;
if (part === ".." || part === "." || part === "this") {
if (dig.length > 0) { throw new Handlebars.Exception("Invalid path: " + this.original); }
if (dig.length > 0) { throw new Handlebars.Exception("Invalid path: " + original); }
else if (part === "..") { depth++; }
else { this.isScoped = true; }
}
else { dig.push(part); }
}
this.original = original;
this.parts = dig;
this.string = dig.join('.');
this.depth = depth;
@@ -95,7 +98,7 @@ Handlebars.AST.IdNode = function(parts) {
Handlebars.AST.PartialNameNode = function(name) {
this.type = "PARTIAL_NAME";
this.name = name;
this.name = name.original;
};
Handlebars.AST.DataNode = function(id) {
@@ -105,13 +108,15 @@ Handlebars.AST.DataNode = function(id) {
Handlebars.AST.StringNode = function(string) {
this.type = "STRING";
this.string = string;
this.stringModeValue = string;
this.original =
this.string =
this.stringModeValue = string;
};
Handlebars.AST.IntegerNode = function(integer) {
this.type = "INTEGER";
this.integer = integer;
this.original =
this.integer = integer;
this.stringModeValue = Number(integer);
};
+38 -18
View File
@@ -189,6 +189,10 @@ Compiler.prototype = {
val = pair[1];
if (this.options.stringParams) {
if(val.depth) {
this.addDepth(val.depth);
}
this.opcode('getContext', val.depth || 0);
this.opcode('pushStringParam', val.stringModeValue, val.type);
} else {
this.accept(val);
@@ -272,7 +276,7 @@ Compiler.prototype = {
if (this.options.knownHelpers[name]) {
this.opcode('invokeKnownHelper', params.length, name);
} else if (this.knownHelpersOnly) {
} else if (this.options.knownHelpersOnly) {
throw new Error("You specified knownHelpersOnly, but used the unknown helper " + name);
} else {
this.opcode('invokeHelper', params.length, name);
@@ -297,7 +301,15 @@ Compiler.prototype = {
DATA: function(data) {
this.options.data = true;
this.opcode('lookupData', data.id);
if (data.id.isScoped || data.id.depth) {
throw new Handlebars.Exception('Scoped data references are not supported: ' + data.original);
}
this.opcode('lookupData');
var parts = data.id.parts;
for(var i=0, l=parts.length; i<l; i++) {
this.opcode('lookup', parts[i]);
}
},
STRING: function(string) {
@@ -496,8 +508,9 @@ JavaScriptCompiler.prototype = {
if (!this.isChild) {
var namespace = this.namespace;
var copies = "helpers = helpers || " + namespace + ".helpers;";
if (this.environment.usePartial) { copies = copies + " partials = partials || " + namespace + ".partials;"; }
var copies = "helpers = this.merge(helpers, " + namespace + ".helpers);";
if (this.environment.usePartial) { copies = copies + " partials = this.merge(partials, " + namespace + ".partials);"; }
if (this.options.data) { copies = copies + " data = data || {};"; }
out.push(copies);
} else {
@@ -526,7 +539,9 @@ JavaScriptCompiler.prototype = {
// Generate minimizer alias mappings
if (!this.isChild) {
for (var alias in this.context.aliases) {
this.source[1] = this.source[1] + ', ' + alias + '=' + this.context.aliases[alias];
if (this.context.aliases.hasOwnProperty(alias)) {
this.source[1] = this.source[1] + ', ' + alias + '=' + this.context.aliases[alias];
}
}
}
@@ -745,7 +760,7 @@ JavaScriptCompiler.prototype = {
//
// Push the result of looking up `id` on the current data
lookupData: function(id) {
this.push(this.nameLookup('data', id, 'data'));
this.push('data');
},
// [pushStringParam]
@@ -773,16 +788,18 @@ JavaScriptCompiler.prototype = {
if (this.options.stringParams) {
this.register('hashTypes', '{}');
this.register('hashContexts', '{}');
}
},
pushHash: function() {
this.hash = {values: [], types: []};
this.hash = {values: [], types: [], contexts: []};
},
popHash: function() {
var hash = this.hash;
this.hash = undefined;
if (this.options.stringParams) {
this.register('hashContexts', '{' + hash.contexts.join(',') + '}');
this.register('hashTypes', '{' + hash.types.join(',') + '}');
}
this.push('{\n ' + hash.values.join(',\n ') + '\n }');
@@ -850,8 +867,9 @@ JavaScriptCompiler.prototype = {
this.context.aliases.helperMissing = 'helpers.helperMissing';
var helper = this.lastHelper = this.setupHelper(paramSize, name, true);
var nonHelper = this.nameLookup('depth' + this.lastContext, name, 'context');
this.push(helper.name);
this.push(helper.name + ' || ' + nonHelper);
this.replaceStack(function(name) {
return name + ' ? ' + name + '.call(' +
helper.callParams + ") " + ": helperMissing.call(" +
@@ -925,14 +943,18 @@ JavaScriptCompiler.prototype = {
// and pushes the hash back onto the stack.
assignToHash: function(key) {
var value = this.popStack(),
context,
type;
if (this.options.stringParams) {
type = this.popStack();
this.popStack();
context = this.popStack();
}
var hash = this.hash;
if (context) {
hash.contexts.push("'" + key + "': " + context);
}
if (type) {
hash.types.push("'" + key + "': " + type);
}
@@ -993,12 +1015,7 @@ JavaScriptCompiler.prototype = {
else { programParams.push("depth" + (depth - 1)); }
}
if(depths.length === 0) {
return "self.program(" + programParams.join(", ") + ")";
} else {
programParams.shift();
return "self.programWithDepth(" + programParams.join(", ") + ")";
}
return (depths.length === 0 ? "self.program(" : "self.programWithDepth(") + programParams.join(", ") + ")";
},
register: function(name, val) {
@@ -1130,7 +1147,9 @@ JavaScriptCompiler.prototype = {
.replace(/\\/g, '\\\\')
.replace(/"/g, '\\"')
.replace(/\n/g, '\\n')
.replace(/\r/g, '\\r') + '"';
.replace(/\r/g, '\\r')
.replace(/\u2028/g, '\\u2028') // Per Ecma-262 7.3 + 7.8.4
.replace(/\u2029/g, '\\u2029') + '"';
},
setupHelper: function(paramSize, name, missingParams) {
@@ -1186,6 +1205,7 @@ JavaScriptCompiler.prototype = {
if (this.options.stringParams) {
options.push("contexts:[" + contexts.join(",") + "]");
options.push("types:[" + types.join(",") + "]");
options.push("hashContexts:hashContexts");
options.push("hashTypes:hashTypes");
}
@@ -1236,7 +1256,7 @@ JavaScriptCompiler.isValidJavaScriptVariableName = function(name) {
};
Handlebars.precompile = function(input, options) {
if (!input || (typeof input !== 'string' && input.constructor !== Handlebars.AST.ProgramNode)) {
if (input == null || (typeof input !== 'string' && input.constructor !== Handlebars.AST.ProgramNode)) {
throw new Handlebars.Exception("You must pass a string or Handlebars AST to Handlebars.precompile. You passed " + input);
}
@@ -1250,7 +1270,7 @@ Handlebars.precompile = function(input, options) {
};
Handlebars.compile = function(input, options) {
if (!input || (typeof input !== 'string' && input.constructor !== Handlebars.AST.ProgramNode)) {
if (input == null || (typeof input !== 'string' && input.constructor !== Handlebars.AST.ProgramNode)) {
throw new Handlebars.Exception("You must pass a string or Handlebars AST to Handlebars.compile. You passed " + input);
}
+1 -1
View File
@@ -121,7 +121,7 @@ Handlebars.PrintVisitor.prototype.PARTIAL_NAME = function(partialName) {
};
Handlebars.PrintVisitor.prototype.DATA = function(data) {
return "@" + data.id;
return "@" + this.accept(data.id);
};
Handlebars.PrintVisitor.prototype.content = function(content) {
+25 -11
View File
@@ -12,13 +12,21 @@ Handlebars.VM = {
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;
programWrapper = Handlebars.VM.program(i, fn, data);
} else if (!programWrapper) {
programWrapper = this.programs[i] = Handlebars.VM.program(i, fn);
}
return programWrapper;
},
merge: function(param, common) {
var ret = param || common;
if (param && common) {
ret = {};
Handlebars.Utils.extend(ret, common);
Handlebars.Utils.extend(ret, param);
}
return ret;
},
programWithDepth: Handlebars.VM.programWithDepth,
noop: Handlebars.VM.noop,
@@ -50,21 +58,27 @@ Handlebars.VM = {
};
},
programWithDepth: function(fn, data, $depth) {
var args = Array.prototype.slice.call(arguments, 2);
programWithDepth: function(i, fn, data /*, $depth */) {
var args = Array.prototype.slice.call(arguments, 3);
return function(context, options) {
var program = function(context, options) {
options = options || {};
return fn.apply(this, [context, options.data || data].concat(args));
};
program.program = i;
program.depth = args.length;
return program;
},
program: function(fn, data) {
return function(context, options) {
program: function(i, fn, data) {
var program = function(context, options) {
options = options || {};
return fn(context, options.data || data);
};
program.program = i;
program.depth = 0;
return program;
},
noop: function() { return ""; },
invokePartial: function(partial, name, context, helpers, partials, data) {
+15
View File
@@ -1,5 +1,7 @@
exports.attach = function(Handlebars) {
var toString = Object.prototype.toString;
// BEGIN(BROWSER)
var errorProps = ['description', 'fileName', 'lineNumber', 'message', 'name', 'number', 'stack'];
@@ -39,6 +41,14 @@ var escapeChar = function(chr) {
};
Handlebars.Utils = {
extend: function(obj, value) {
for(var key in value) {
if(value.hasOwnProperty(key)) {
obj[key] = value[key];
}
}
},
escapeExpression: function(string) {
// don't escape SafeStrings, since they're already safe
if (string instanceof Handlebars.SafeString) {
@@ -47,6 +57,11 @@ Handlebars.Utils = {
return "";
}
// Force a string conversion as this will be done by the append regardless and
// the regex test will do this transparently behind the scenes, causing issues if
// an object's to string has escaped characters in it.
string = string.toString();
if(!possible.test(string)) { return string; }
return string.replace(badChars, escapeChar);
},
+2 -2
View File
@@ -1,7 +1,7 @@
{
"name": "handlebars",
"description": "Extension of the Mustache logicless template language",
"version": "1.0.10",
"version": "1.0.12",
"homepage": "http://www.handlebarsjs.com/",
"keywords": [
"handlebars mustache template html"
@@ -15,7 +15,7 @@
},
"dependencies": {
"optimist": "~0.3",
"uglify-js": "~1.2"
"uglify-js": "~2.3"
},
"devDependencies": {
"benchmark": "~1.0",
+93
View File
@@ -0,0 +1,93 @@
# Release Notes
## Development
[Commits](https://github.com/wycats/handlebars.js/compare/v1.0.12...master)
## v1.0.12 / 1.0.0 - May 31 2013
- [#515](https://github.com/wycats/handlebars.js/issues/515) - Add node require extensions support ([@jjclark1982](https://github.com/jjclark1982))
- [#517](https://github.com/wycats/handlebars.js/issues/517) - Fix amd precompiler output with directories ([@blessenm](https://github.com/blessenm))
- [#433](https://github.com/wycats/handlebars.js/issues/433) - Add support for unicode ids
- [#469](https://github.com/wycats/handlebars.js/issues/469) - Add support for `?` in ids
- [#534](https://github.com/wycats/handlebars.js/issues/534) - Protect from object prototype modifications
- [#519](https://github.com/wycats/handlebars.js/issues/519) - Fix partials with . name ([@jamesgorrie](https://github.com/jamesgorrie))
- [#519](https://github.com/wycats/handlebars.js/issues/519) - Allow ID or strings in partial names
- [#437](https://github.com/wycats/handlebars.js/issues/437) - Require matching brace counts in escaped expressions
- Merge passed partials and helpers with global namespace values
- Add support for complex ids in @data references
- Docs updates
Compatibility notes:
- The parser is now stricter on `{{{`, requiring that the end token be `}}}`. Templates that do not
follow this convention should add the additional brace value.
- Code that relies on global the namespace being muted when custom helpers or partials are passed will need to explicitly pass an `undefined` value for any helpers that should not be available.
[Commits](https://github.com/wycats/handlebars.js/compare/v1.0.11...v1.0.12)
## v1.0.11 / 1.0.0-rc4 - May 13 2013
- [#458](https://github.com/wycats/handlebars.js/issues/458) - Fix `./foo` syntax ([@jpfiset](https://github.com/jpfiset))
- [#460](https://github.com/wycats/handlebars.js/issues/460) - Allow `:` in unescaped identifers ([@jpfiset](https://github.com/jpfiset))
- [#471](https://github.com/wycats/handlebars.js/issues/471) - Create release notes (These!)
- [#456](https://github.com/wycats/handlebars.js/issues/456) - Allow escaping of `\\`
- [#211](https://github.com/wycats/handlebars.js/issues/211) - Fix exception in `escapeExpression`
- [#375](https://github.com/wycats/handlebars.js/issues/375) - Escape unicode newlines
- [#461](https://github.com/wycats/handlebars.js/issues/461) - Do not fail when compiling `""`
- [#302](https://github.com/wycats/handlebars.js/issues/302) - Fix sanity check in knownHelpersOnly mode
- [#369](https://github.com/wycats/handlebars.js/issues/369) - Allow registration of multiple helpers and partial by passing definition object
- Add bower package declaration ([@DevinClark](https://github.com/DevinClark))
- Add NuSpec package declaration ([@MikeMayer](https://github.com/MikeMayer))
- Handle empty context in `with` ([@thejohnfreeman](https://github.com/thejohnfreeman))
- Support custom template extensions in CLI ([@matteoagosti](https://github.com/matteoagosti))
- Fix Rhino support ([@broady](https://github.com/broady))
- Include contexts in string mode ([@leshill](https://github.com/leshill))
- Return precompiled scripts when compiling to AMD ([@JamesMaroney](https://github.com/JamesMaroney))
- Docs updates ([@iangreenleaf](https://github.com/iangreenleaf), [@gilesbowkett](https://github.com/gilesbowkett), [@utkarsh2012](https://github.com/utkarsh2012))
- Fix `toString` handling under IE and browserify ([@tommydudebreaux](https://github.com/tommydudebreaux))
- Add program metadata
[Commits](https://github.com/wycats/handlebars.js/compare/v1.0.10...v1.0.11)
## v1.0.10 - Node - Feb 27 2013
- [#428](https://github.com/wycats/handlebars.js/issues/428) - Fix incorrect rendering of nested programs
- Fix exception message ([@tricknotes](https://github.com/tricknotes))
- Added negative number literal support
- Concert library to single IIFE
- Add handlebars-source gemspec ([@machty](https://github.com/machty))
[Commits](https://github.com/wycats/handlebars.js/compare/v1.0.9...v1.0.10)
## v1.0.9 - Node - Feb 15 2013
- Added `Handlebars.create` API in node module for sandboxed instances ([@tommydudebreaux](https://github.com/tommydudebreaux))
[Commits](https://github.com/wycats/handlebars.js/compare/1.0.0-rc.3...v1.0.9)
## 1.0.0-rc3 - Browser - Feb 14 2013
- Prevent use of `this` or `..` in illogical place ([@leshill](https://github.com/leshill))
- Allow AST passing for `parse`/`compile`/`precompile` ([@machty](https://github.com/machty))
- Optimize generated output by inlining statements where possible
- Check compiler version when evaluating templates
- Package browser dist in npm package
[Commits](https://github.com/wycats/handlebars.js/compare/v1.0.8...1.0.0-rc.3)
## Prior Versions
When upgrading from the Handlebars 0.9 series, be aware that the
signature for passing custom helpers or partials to templates has
changed.
Instead of:
```js
template(context, helpers, partials, [data])
```
Use:
```js
template(context, {helpers: helpers, partials: partials, data: data})
```
+1
View File
@@ -0,0 +1 @@
{{foo}}
+1
View File
@@ -0,0 +1 @@
Hello, {{name}}!
+6 -13
View File
@@ -111,7 +111,7 @@ describe "Parser" do
end
def data(id)
"@#{id}"
"@ID:#{id}"
end
def partial_name(name)
@@ -125,6 +125,10 @@ describe "Parser" do
it "parses simple mustaches" do
ast_for("{{foo}}").should == root { mustache id("foo") }
ast_for("{{foo?}}").should == root { mustache id("foo?") }
ast_for("{{foo_}}").should == root { mustache id("foo_") }
ast_for("{{foo-}}").should == root { mustache id("foo-") }
ast_for("{{foo:}}").should == root { mustache id("foo:") }
end
it "parses simple mustaches with data" do
@@ -230,7 +234,7 @@ describe "Parser" do
end
it "parses a partial with a complex name" do
ast_for("{{> shared/partial}}").should == root { partial partial_name("shared/partial") }
ast_for("{{> shared/partial?.bar}}").should == root { partial partial_name("shared/partial?.bar") }
end
it "parses a comment" do
@@ -413,21 +417,10 @@ describe "Parser" do
end
context "externally compiled AST" do
it "can pass through an already-compiled AST" do
ast_for(@context.eval('new Handlebars.AST.ProgramNode([ new Handlebars.AST.ContentNode("Hello")]);')).should == root do
content "Hello"
end
end
it "can pass through an already-compiled AST via compile/precompile" do
@context = Handlebars::Spec::FULL_CONTEXT
code = 'Handlebars.compile(new Handlebars.AST.ProgramNode([ new Handlebars.AST.ContentNode("Hello")]))();'
@context.eval(code).should == "Hello"
code = @context.eval 'Handlebars.precompile(new Handlebars.AST.ProgramNode([ new Handlebars.AST.ContentNode("Hello")]))'
@context.eval("(#{code})(this)").should == "Hello"
end
end
end
+267 -9
View File
@@ -42,14 +42,6 @@ function shouldCompileToWithPartials(string, hashOrArray, partials, expected, me
function compileWithPartials(string, hashOrArray, partials) {
var template = CompilerContext[partials ? 'compileWithPartial' : 'compile'](string), ary;
if(Object.prototype.toString.call(hashOrArray) === "[object Array]") {
var helpers = hashOrArray[1];
if(helpers) {
for(var prop in Handlebars.helpers) {
helpers[prop] = helpers[prop] || Handlebars.helpers[prop];
}
}
ary = [];
ary.push(hashOrArray[0]);
ary.push({ helpers: hashOrArray[1], partials: hashOrArray[2] });
@@ -92,6 +84,12 @@ test("most basic", function() {
shouldCompileTo("{{foo}}", { foo: "foo" }, "foo");
});
test("escaping", function() {
shouldCompileTo("\\{{foo}}", { foo: "food" }, "{{foo}}");
shouldCompileTo("\\\\{{foo}}", { foo: "food" }, "\\food");
shouldCompileTo("\\\\ {{foo}}", { foo: "food" }, "\\\\ food");
});
test("compiling with a basic context", function() {
shouldCompileTo("Goodbye\n{{cruel}}\n{{world}}!", {cruel: "cruel", world: "world"}, "Goodbye\ncruel\nworld!",
"It works if all the required keys are provided");
@@ -159,6 +157,14 @@ test("functions", function() {
"functions are bound to the context");
});
test("functions with context argument", function() {
shouldCompileTo("{{awesome frank}}",
{awesome: function(context) { return context; },
frank: "Frank"},
"Frank", "functions are called with context arguments");
});
test("paths with hyphens", function() {
shouldCompileTo("{{foo-bar}}", {"foo-bar": "baz"}, "baz", "Paths can contain hyphens (-)");
shouldCompileTo("{{foo.foo-bar}}", {foo: {"foo-bar": "baz"}}, "baz", "Paths can contain hyphens (-)");
@@ -486,6 +492,38 @@ test("the helpers hash is available is nested contexts", function() {
"helpers hash is available in nested contexts.");
});
test("the helper hash should augment the global hash", function() {
Handlebars.registerHelper('test_helper', function() { return 'found it!'; });
shouldCompileTo(
"{{test_helper}} {{#if cruel}}Goodbye {{cruel}} {{world}}!{{/if}}", [
{cruel: "cruel"},
{world: function() { return "world!"; }}
],
"found it! Goodbye cruel world!!");
});
test("Multiple global helper registration", function() {
var helpers = Handlebars.helpers;
try {
Handlebars.helpers = {};
Handlebars.registerHelper({
'if': helpers['if'],
world: function() { return "world!"; },
test_helper: function() { return 'found it!'; }
});
shouldCompileTo(
"{{test_helper}} {{#if cruel}}Goodbye {{cruel}} {{world}}!{{/if}}",
[{cruel: "cruel"}],
"found it! Goodbye cruel world!!");
} finally {
if (helpers) {
Handlebars.helpers = helpers;
}
}
});
suite("partials");
test("basic partials", function() {
@@ -550,6 +588,33 @@ test("Partials with slash paths", function() {
shouldCompileToWithPartials(string, [hash, {}, {'shared/dude':dude}], true, "Dudes: Jeepers", "Partials can use literal paths");
});
test("Partials with slash and point paths", function() {
var string = "Dudes: {{> shared/dude.thing}}";
var dude = "{{name}}";
var hash = {name:"Jeepers", another_dude:"Creepers"};
shouldCompileToWithPartials(string, [hash, {}, {'shared/dude.thing':dude}], true, "Dudes: Jeepers", "Partials can use literal with points in paths");
});
test("Global Partials", function() {
Handlebars.registerPartial('global_test', '{{another_dude}}');
var string = "Dudes: {{> shared/dude}} {{> global_test}}";
var dude = "{{name}}";
var hash = {name:"Jeepers", another_dude:"Creepers"};
shouldCompileToWithPartials(string, [hash, {}, {'shared/dude':dude}], true, "Dudes: Jeepers Creepers", "Partials can use globals or passed");
});
test("Multiple partial registration", function() {
Handlebars.registerPartial({
'shared/dude': '{{name}}',
global_test: '{{another_dude}}'
});
var string = "Dudes: {{> shared/dude}} {{> global_test}}";
var hash = {name:"Jeepers", another_dude:"Creepers"};
shouldCompileToWithPartials(string, [hash], true, "Dudes: Jeepers Creepers", "Partials can use globals or passed");
});
test("Partials with integer path", function() {
var string = "Dudes: {{> 404}}";
var dude = "{{name}}";
@@ -557,6 +622,26 @@ test("Partials with integer path", function() {
shouldCompileToWithPartials(string, [hash, {}, {404:dude}], true, "Dudes: Jeepers", "Partials can use literal paths");
});
test("Partials with complex path", function() {
var string = "Dudes: {{> 404/asdf?.bar}}";
var dude = "{{name}}";
var hash = {name:"Jeepers", another_dude:"Creepers"};
shouldCompileToWithPartials(string, [hash, {}, {'404/asdf?.bar':dude}], true, "Dudes: Jeepers", "Partials can use literal paths");
});
test("Partials with escaped", function() {
var string = "Dudes: {{> [+404/asdf?.bar]}}";
var dude = "{{name}}";
var hash = {name:"Jeepers", another_dude:"Creepers"};
shouldCompileToWithPartials(string, [hash, {}, {'+404/asdf?.bar':dude}], true, "Dudes: Jeepers", "Partials can use literal paths");
});
test("Partials with string", function() {
var string = "Dudes: {{> \"+404/asdf?.bar\"}}";
var dude = "{{name}}";
var hash = {name:"Jeepers", another_dude:"Creepers"};
shouldCompileToWithPartials(string, [hash, {}, {'+404/asdf?.bar':dude}], true, "Dudes: Jeepers", "Partials can use literal paths");
});
suite("String literal parameters");
@@ -641,7 +726,7 @@ test("if a context is not found, helperMissing is used", function() {
shouldThrow(function() {
var template = CompilerContext.compile("{{hello}} {{link_to world}}");
template({});
}, [Error, "Could not find property 'link_to'"], "Should throw exception");
}, [Error, "Missing helper: 'link_to'"], "Should throw exception");
});
test("if a context is not found, custom helperMissing is used", function() {
@@ -703,6 +788,11 @@ test("Functions are bound to the context in knownHelpers only mode", function()
var result = template({foo: function() { return this.bar; }, bar: 'bar'});
equal(result, "bar", "'bar' should === '" + result);
});
test("Unknown helper call in knownHelpers only mode should throw", function() {
shouldThrow(function() {
CompilerContext.compile("{{typeof hello}}", {knownHelpersOnly: true});
}, Error, 'specified knownHelpersOnly');
});
suite("blockHelperMissing");
@@ -727,6 +817,10 @@ test("with", function() {
var string = "{{#with person}}{{first}} {{last}}{{/with}}";
shouldCompileTo(string, {person: {first: "Alan", last: "Johnson"}}, "Alan Johnson");
});
test("with with function argument", function() {
var string = "{{#with person}}{{first}} {{last}}{{/with}}";
shouldCompileTo(string, {person: function() { return {first: "Alan", last: "Johnson"};}}, "Alan Johnson");
});
test("if", function() {
var string = "{{#if goodbye}}GOODBYE {{/if}}cruel {{world}}!";
@@ -791,6 +885,15 @@ test("each with @index", function() {
equal(result, "0. goodbye! 1. Goodbye! 2. GOODBYE! cruel world!", "The @index variable is used");
});
test("each with function argument", function() {
var string = "{{#each goodbyes}}{{text}}! {{/each}}cruel {{world}}!";
var hash = {goodbyes: function () { return [{text: "goodbye"}, {text: "Goodbye"}, {text: "GOODBYE"}];}, world: "world"};
shouldCompileTo(string, hash, "goodbye! Goodbye! GOODBYE! cruel world!",
"each with array function argument iterates over the contents when not empty");
shouldCompileTo(string, {goodbyes: [], world: "world"}, "cruel world!",
"each with array function argument ignores the contents when empty");
});
test("data passed to helpers", function() {
var string = "{{#each letters}}{{this}}{{detectDataInsideEach}}{{/each}}";
var hash = {letters: ['a', 'b', 'c']};
@@ -890,6 +993,54 @@ test("hash values can be looked up via @foo", function() {
equals("Hello world", result, "@foo as a parameter retrieves template data");
});
test("nested parameter data can be looked up via @foo.bar", function() {
var template = CompilerContext.compile("{{hello @world.bar}}");
var helpers = {
hello: function(noun) {
return "Hello " + noun;
}
};
var result = template({}, { helpers: helpers, data: { world: {bar: "world" } } });
equals("Hello world", result, "@foo as a parameter retrieves template data");
});
test("nested parameter data does not fail with @world.bar", function() {
var template = CompilerContext.compile("{{hello @world.bar}}");
var helpers = {
hello: function(noun) {
return "Hello " + noun;
}
};
var result = template({}, { helpers: helpers, data: { foo: {bar: "world" } } });
equals("Hello undefined", result, "@foo as a parameter retrieves template data");
});
test("parameter data throws when using this scope references", function() {
var string = "{{#goodbyes}}{{text}} cruel {{@./name}}! {{/goodbyes}}";
shouldThrow(function() {
CompilerContext.compile(string);
}, Error, "Should throw exception");
});
test("parameter data throws when using parent scope references", function() {
var string = "{{#goodbyes}}{{text}} cruel {{@../name}}! {{/goodbyes}}";
shouldThrow(function() {
CompilerContext.compile(string);
}, Error, "Should throw exception");
});
test("parameter data throws when using complex scope references", function() {
var string = "{{#goodbyes}}{{text}} cruel {{@foo/../name}}! {{/goodbyes}}";
shouldThrow(function() {
CompilerContext.compile(string);
}, Error, "Should throw exception");
});
test("data is inherited downstream", function() {
var template = CompilerContext.compile("{{#let foo=bar.baz}}{{@foo}}{{/let}}", { data: true });
var helpers = {
@@ -1286,6 +1437,32 @@ test("in string mode, hash parameters get type information", function() {
equal(result, "Helper called");
});
test("in string mode, hash parameters get context information", function() {
var template = CompilerContext.compile('{{#with dale}}{{tomdale he.says desire="need" noun=../dad/joke bool=true}}{{/with}}', { stringParams: true });
var context = {dale: {}};
var helpers = {
tomdale: function(exclamation, options) {
equal(exclamation, "he.says");
equal(options.types[0], "ID");
equal(options.contexts.length, 1);
equal(options.hashContexts.noun, context);
equal(options.hash.desire, "need");
equal(options.hash.noun, "dad.joke");
equal(options.hash.bool, true);
return "Helper called";
},
"with": function(context, options) {
return options.fn(options.contexts[0][context]);
}
};
var result = template(context, { helpers: helpers });
equal(result, "Helper called");
});
test("when inside a block in String mode, .. passes the appropriate context in the options hash to a block helper", function() {
var template = CompilerContext.compile('{{#with dale}}{{#tomdale ../need dad.joke}}wot{{/tomdale}}{{/with}}', {stringParams: true});
@@ -1373,6 +1550,10 @@ test("Passing falsy values to Handlebars.compile throws an error", function() {
}, "You must pass a string or Handlebars AST to Handlebars.precompile. You passed null");
});
test("can pass through an already-compiled AST via compile/precompile", function() {
equal(Handlebars.compile(new Handlebars.AST.ProgramNode([ new Handlebars.AST.ContentNode("Hello")]))(), 'Hello')
});
test('GH-408: Multiple loops fail', function() {
var context = [
{ name: "John Doe", location: { city: "Chicago" } },
@@ -1397,3 +1578,80 @@ test('GS-428: Nested if else rendering', function() {
shouldCompileTo(succeedingTemplate, [{}, helpers], ' Expected ');
shouldCompileTo(failingTemplate, [{}, helpers], ' Expected ');
});
test('GH-458: Scoped this identifier', function() {
shouldCompileTo('{{./foo}}', {foo: 'bar'}, 'bar');
});
test('GH-375: Unicode line terminators', function() {
shouldCompileTo('\u2028', {}, '\u2028');
});
test('GH-534: Object prototype aliases', function() {
Object.prototype[0xD834] = true;
shouldCompileTo('{{foo}}', { foo: 'bar' }, 'bar');
delete Object.prototype[0xD834];
});
test('GH-437: Matching escaping', function() {
shouldThrow(function() {
CompilerContext.compile('{{{a}}');
}, Error);
shouldThrow(function() {
CompilerContext.compile('{{a}}}');
}, Error);
});
suite('Utils');
test('escapeExpression', function() {
equal(Handlebars.Utils.escapeExpression('foo<&"\'>'), 'foo&lt;&amp;&quot;&#x27;&gt;');
equal(Handlebars.Utils.escapeExpression(new Handlebars.SafeString('foo<&"\'>')), 'foo<&"\'>');
equal(Handlebars.Utils.escapeExpression(''), '');
equal(Handlebars.Utils.escapeExpression(undefined), '');
equal(Handlebars.Utils.escapeExpression(null), '');
equal(Handlebars.Utils.escapeExpression(false), '');
equal(Handlebars.Utils.escapeExpression(0), '0');
equal(Handlebars.Utils.escapeExpression({}), {}.toString());
equal(Handlebars.Utils.escapeExpression([]), [].toString());
});
test('isEmpty', function() {
equal(Handlebars.Utils.isEmpty(undefined), true);
equal(Handlebars.Utils.isEmpty(null), true);
equal(Handlebars.Utils.isEmpty(false), true);
equal(Handlebars.Utils.isEmpty(''), true);
equal(Handlebars.Utils.isEmpty([]), true);
equal(Handlebars.Utils.isEmpty(0), false);
equal(Handlebars.Utils.isEmpty([1]), false);
equal(Handlebars.Utils.isEmpty('foo'), false);
equal(Handlebars.Utils.isEmpty({bar: 1}), false);
});
if (typeof(require) !== 'undefined') {
suite('Require');
test('Load .handlebars files with require()', function() {
var template = require("./example_1");
assert.deepEqual(template, require("./example_1.handlebars"));
var expected = 'foo\n';
var result = template({foo: "foo"});
equal(result, expected);
});
test('Load .hbs files with require()', function() {
var template = require("./example_2");
assert.deepEqual(template, require("./example_2.hbs"));
var expected = 'Hello, World!\n';
var result = template({name: "World"});
equal(result, expected);
});
}
+1 -18
View File
@@ -87,7 +87,7 @@ module Handlebars
end
CompilerContext["compileWithPartial"] = proc do |this, *args|
template, options = args[0], args[1] || nil
FULL_CONTEXT["Handlebars"]["compile"].call(template, options);
context["Handlebars"]["compile"].call(template, options);
end
end
@@ -116,23 +116,6 @@ module Handlebars
end
end
end
FULL_CONTEXT = V8::Context.new
FULL_CONTEXT.instance_eval do |context|
Handlebars::Spec.load_helpers(context);
Handlebars::Spec.js_load(context, 'dist/handlebars.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
puts str
end
end
end
end
end
+34 -14
View File
@@ -41,6 +41,21 @@ describe "Tokenizer" do
result[1].should be_token("ID", "foo")
end
it "supports unescaping with &" do
result = tokenize("{{&bar}}")
result.should match_tokens(%w(OPEN ID CLOSE))
result[0].should be_token("OPEN", "{{&")
result[1].should be_token("ID", "bar")
end
it "supports unescaping with {{{" do
result = tokenize("{{{bar}}}")
result.should match_tokens(%w(OPEN_UNESCAPED ID CLOSE_UNESCAPED))
result[1].should be_token("ID", "bar")
end
it "supports escaping delimiters" do
result = tokenize("{{foo}} \\{{bar}} {{baz}}")
result.should match_tokens(%w(OPEN ID CLOSE CONTENT CONTENT OPEN ID CLOSE))
@@ -129,24 +144,29 @@ describe "Tokenizer" do
result[4].should be_token("CONTENT", " baz")
end
it "tokenizes a partial as 'OPEN_PARTIAL PARTIAL_NAME CLOSE'" do
it "tokenizes a partial as 'OPEN_PARTIAL ID CLOSE'" do
result = tokenize("{{> foo}}")
result.should match_tokens(%w(OPEN_PARTIAL PARTIAL_NAME CLOSE))
result.should match_tokens(%w(OPEN_PARTIAL ID CLOSE))
end
it "tokenizes a partial with context as 'OPEN_PARTIAL PARTIAL_NAME ID CLOSE'" do
it "tokenizes a partial with context as 'OPEN_PARTIAL ID ID CLOSE'" do
result = tokenize("{{> foo bar }}")
result.should match_tokens(%w(OPEN_PARTIAL PARTIAL_NAME ID CLOSE))
result.should match_tokens(%w(OPEN_PARTIAL ID ID CLOSE))
end
it "tokenizes a partial without spaces as 'OPEN_PARTIAL PARTIAL_NAME CLOSE'" do
it "tokenizes a partial without spaces as 'OPEN_PARTIAL ID CLOSE'" do
result = tokenize("{{>foo}}")
result.should match_tokens(%w(OPEN_PARTIAL PARTIAL_NAME CLOSE))
result.should match_tokens(%w(OPEN_PARTIAL ID CLOSE))
end
it "tokenizes a partial space at the end as 'OPEN_PARTIAL PARTIAL_NAME CLOSE'" do
it "tokenizes a partial space at the end as 'OPEN_PARTIAL ID CLOSE'" do
result = tokenize("{{>foo }}")
result.should match_tokens(%w(OPEN_PARTIAL PARTIAL_NAME CLOSE))
result.should match_tokens(%w(OPEN_PARTIAL ID CLOSE))
end
it "tokenizes a partial space at the end as 'OPEN_PARTIAL ID CLOSE'" do
result = tokenize("{{>foo/bar.baz }}")
result.should match_tokens(%w(OPEN_PARTIAL ID SEP ID SEP ID CLOSE))
end
it "tokenizes a comment as 'COMMENT'" do
@@ -280,16 +300,16 @@ describe "Tokenizer" do
it "tokenizes special @ identifiers" do
result = tokenize("{{ @foo }}")
result.should match_tokens %w( OPEN DATA CLOSE )
result[1].should be_token("DATA", "foo")
result.should match_tokens %w( OPEN DATA ID CLOSE )
result[2].should be_token("ID", "foo")
result = tokenize("{{ foo @bar }}")
result.should match_tokens %w( OPEN ID DATA CLOSE )
result[2].should be_token("DATA", "bar")
result.should match_tokens %w( OPEN ID DATA ID CLOSE )
result[3].should be_token("ID", "bar")
result = tokenize("{{ foo bar=@baz }}")
result.should match_tokens %w( OPEN ID ID EQUALS DATA CLOSE )
result[4].should be_token("DATA", "baz")
result.should match_tokens %w( OPEN ID ID EQUALS DATA ID CLOSE )
result[5].should be_token("ID", "baz")
end
it "does not time out in a mustache with a single } followed by EOF" do
+19 -9
View File
@@ -1,8 +1,9 @@
%x mu emu com par
%x mu emu com
%%
"\\\\"/("{{") { yytext = "\\"; return 'CONTENT'; }
[^\x00]*?/("{{") {
if(yytext.slice(-1) !== "\\") this.begin("mu");
if(yytext.slice(-1) === "\\") yytext = yytext.substr(0,yyleng-1), this.begin("emu");
@@ -19,35 +20,44 @@
<com>[\s\S]*?"--}}" { yytext = yytext.substr(0, yyleng-4); this.popState(); return 'COMMENT'; }
<mu>"{{>" { this.begin("par"); return 'OPEN_PARTIAL'; }
<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>"{{&" { return 'OPEN'; }
<mu>"{{!--" { this.popState(); this.begin('com'); }
<mu>"{{!"[\s\S]*?"}}" { yytext = yytext.substr(3,yyleng-5); this.popState(); return 'COMMENT'; }
<mu>"{{" { return 'OPEN'; }
<mu>"=" { return 'EQUALS'; }
<mu>"."/[} ] { return 'ID'; }
<mu>"."/[}\/ ] { return 'ID'; }
<mu>".." { return 'ID'; }
<mu>[\/.] { return 'SEP'; }
<mu>\s+ { /*ignore whitespace*/ }
<mu>"}}}" { this.popState(); return 'CLOSE'; }
<mu>"}}}" { this.popState(); return 'CLOSE_UNESCAPED'; }
<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>"@" { 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'; }
/*
ID is the inverse of control characters.
Control characters ranges:
[\s] Whitespace
[!"#%-,\./] !, ", #, %, &, ', (, ), *, +, ,, ., /, Exceptions in range: $, -
[;->@] ;, <, =, >, @, Exceptions in range: :, ?
[\[-\^`] [, \, ], ^, `, Exceptions in range: _
[\{-~] {, |, }, ~
*/
<mu>[^\s!"#%-,\.\/;->@\[-\^`\{-~]+/[=}\s\/.] { return 'ID'; }
<mu>'['[^\]]*']' { yytext = yytext.substr(1, yyleng-2); return 'ID'; }
<mu>. { return 'INVALID'; }
<par>\s+ { /*ignore whitespace*/ }
<par>[a-zA-Z0-9_$-/]+ { this.popState(); return 'PARTIAL_NAME'; }
<INITIAL,mu><<EOF>> { return 'EOF'; }
+17 -8
View File
@@ -42,8 +42,11 @@ closeBlock
;
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 {
// Parsing out the '&' escape token at this level saves ~500 bytes after min due to the removal of one parser node.
$$ = new yy.MustacheNode($2[0], $2[1], $1[2] === '&');
}
| OPEN_UNESCAPED inMustache CLOSE_UNESCAPED { $$ = new yy.MustacheNode($2[0], $2[1], true); }
;
@@ -61,7 +64,7 @@ inMustache
| path params { $$ = [[$1].concat($2), null]; }
| path hash { $$ = [[$1], $2]; }
| path { $$ = [[$1], null]; }
| DATA { $$ = [[new yy.DataNode($1)], null]; }
| dataName { $$ = [[$1], null]; }
;
params
@@ -74,7 +77,7 @@ param
| STRING { $$ = new yy.StringNode($1); }
| INTEGER { $$ = new yy.IntegerNode($1); }
| BOOLEAN { $$ = new yy.BooleanNode($1); }
| DATA { $$ = new yy.DataNode($1); }
| dataName { $$ = $1; }
;
hash
@@ -91,11 +94,17 @@ hashSegment
| 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)]; }
| ID EQUALS dataName { $$ = [$1, $3]; }
;
partialName
: PARTIAL_NAME { $$ = new yy.PartialNameNode($1); }
: path { $$ = new yy.PartialNameNode($1); }
| STRING { $$ = new yy.PartialNameNode(new yy.StringNode($1)); }
| INTEGER { $$ = new yy.PartialNameNode(new yy.IntegerNode($1)); }
;
dataName
: DATA path { $$ = new yy.DataNode($2); }
;
path
@@ -103,7 +112,7 @@ path
;
pathSegments
: pathSegments SEP ID { $1.push($3); $$ = $1; }
| ID { $$ = [$1]; }
: pathSegments SEP ID { $1.push({part: $3, separator: $2}); $$ = $1; }
| ID { $$ = [{part: $1}]; }
;