Compare commits
183 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 5ec78a8c70 | |||
| 5333f316c3 | |||
| 8a836e2272 | |||
| cc554a5813 | |||
| ed879a6068 | |||
| 2e21e2bc9a | |||
| bdfdbea09c | |||
| b50ef03823 | |||
| 6e6269fcd7 | |||
| 7378f854c3 | |||
| fed5818876 | |||
| 0e81f0a082 | |||
| a00c598266 | |||
| 606fa55b0a | |||
| 8e09f0ee4e | |||
| c8f4b570c1 | |||
| b617375219 | |||
| 63a8e0caa2 | |||
| 5a164d0ca5 | |||
| 01b0f656bb | |||
| 406f2ee01f | |||
| a023cb4dd9 | |||
| c7dc353956 | |||
| ad3037cf54 | |||
| 44ce5f40aa | |||
| 3385de0921 | |||
| abc160ae8e | |||
| 5ee91cb58a | |||
| ef9e0dc2ab | |||
| 0d0c4d1bbd | |||
| 8ff49cef52 | |||
| 1b885d496a | |||
| 45e14b7b45 | |||
| 1379e8f50c | |||
| 6272c15074 | |||
| 959ee557b0 | |||
| 577b760b50 | |||
| 8c19874497 | |||
| 8fc16367b0 | |||
| 400916c225 | |||
| fee23342b1 | |||
| 32d6363841 | |||
| 20c965cd65 | |||
| 6c9f98c025 | |||
| c393c81561 | |||
| 205c61cfb1 | |||
| ddaff8ea29 | |||
| 685cf92bcb | |||
| 7a6c228592 | |||
| 0a3b3c2831 | |||
| 251ec3b128 | |||
| c21118d04b | |||
| 0f00f31f6b | |||
| 1dc417f1a5 | |||
| 9f59de9657 | |||
| 3406b95cfd | |||
| 7b48a284e0 | |||
| 98a6717e9e | |||
| b7c95e9feb | |||
| b1e561ee26 | |||
| 690ae2edc7 | |||
| 496ed8a25f | |||
| d20f02d10d | |||
| 33b53ef989 | |||
| f08d48764d | |||
| 9365b82900 | |||
| bf091777a9 | |||
| 94c840b6ed | |||
| 861d6f7b8d | |||
| fffb5a985f | |||
| 08781798f5 | |||
| 641fe335df | |||
| 733b7c8752 | |||
| 9a86e40970 | |||
| efad390ffc | |||
| 3696d23752 | |||
| a5a3ab01d3 | |||
| c83354101e | |||
| 7b1fdf814c | |||
| c7b28a65da | |||
| 9a27693fae | |||
| 2d9a5151e8 | |||
| 3934b3ab92 | |||
| 05b82a203e | |||
| f71f4e759e | |||
| 6e9198b207 | |||
| cc8ed7a9e7 | |||
| 454d20e3b2 | |||
| 0aef72cb8e | |||
| bff5fab8f9 | |||
| f97b99e0cd | |||
| 6c07367cd6 | |||
| 83b8e846a3 | |||
| b0d217e13d | |||
| c13c7df73d | |||
| b63d74a7b3 | |||
| e7a64f018c | |||
| 0f5061e445 | |||
| 7c896a074d | |||
| e70430dd2e | |||
| 9d4353c35c | |||
| c727e1f97c | |||
| 6c45f49b24 | |||
| 495cd05a7e | |||
| 452afbf2ff | |||
| 408192ba9f | |||
| 2a4a5447f5 | |||
| 1c274088c1 | |||
| 0b5e82e1c3 | |||
| 233caf3f7c | |||
| 91ffd32cad | |||
| 2571dd8e8e | |||
| 729d9fdffe | |||
| a634cd3219 | |||
| 95d84badca | |||
| 9a2d1d6009 | |||
| 08093d72f0 | |||
| 958273c2e6 | |||
| 1c2b74e537 | |||
| b85cad8df6 | |||
| d21de5d569 | |||
| 519fac5081 | |||
| 624a4c71aa | |||
| 3ce04ddd23 | |||
| ea3a5a1eb6 | |||
| 269dd492bb | |||
| 2ae0ef6eef | |||
| 06d515a89d | |||
| 0de8dac702 | |||
| d716fd01c5 | |||
| 77e6bfc5a1 | |||
| 00f74420f9 | |||
| a62cbad95a | |||
| ba31ef8ae4 | |||
| a44fe470b4 | |||
| bb9831b73e | |||
| c532b89af6 | |||
| 72eb10ca4b | |||
| bd643ce12b | |||
| 93b07605cd | |||
| 85750f8d0a | |||
| fac71ce700 | |||
| 324d615726 | |||
| c3cbaa25a4 | |||
| 9b1f9c7e44 | |||
| 8e868ab225 | |||
| 5d4b8da344 | |||
| 279e038ba7 | |||
| 1c08771215 | |||
| 9f265b9761 | |||
| 0aa54f49de | |||
| 1f11cc0186 | |||
| 9a49d35023 | |||
| b664997dc3 | |||
| ac82842cb6 | |||
| efddc3c09c | |||
| f3e8b18925 | |||
| 15b55a307b | |||
| 231a8d7256 | |||
| e2ba22eaad | |||
| 1bb640be41 | |||
| 2a851067b9 | |||
| 410141c31e | |||
| 060c087e62 | |||
| 5acf77a99a | |||
| fe057168ae | |||
| c83c363a58 | |||
| aa7a45b443 | |||
| a458fe26f4 | |||
| 2f9495c1df | |||
| b9fe7ce618 | |||
| 868ef4b309 | |||
| 8c09b3c896 | |||
| 9bbc177fd5 | |||
| 93faffa549 | |||
| d2fb3a4906 | |||
| d0805e9cfa | |||
| 2c1d509c6c | |||
| 569f288551 | |||
| d0d2168ec2 | |||
| 8cba84df11 | |||
| 00bfdd782f | |||
| afd595bd6e |
@@ -16,6 +16,7 @@
|
||||
"objectLiteralDuplicateProperties": true,
|
||||
"objectLiteralShorthandMethods": true,
|
||||
"objectLiteralShorthandProperties": true,
|
||||
"restParams": true,
|
||||
"spread": true,
|
||||
"templateStrings": true
|
||||
},
|
||||
@@ -39,12 +40,12 @@
|
||||
"no-dupe-keys": 2,
|
||||
"no-duplicate-case": 2,
|
||||
"no-empty": 2,
|
||||
"no-empty-class": 2,
|
||||
"no-empty-character-class": 2,
|
||||
"no-ex-assign": 2,
|
||||
"no-extra-boolean-cast": 2,
|
||||
"no-extra-parens": 0,
|
||||
"no-extra-semi": 2,
|
||||
"no-func-assign": 2,
|
||||
"no-func-assign": 0,
|
||||
|
||||
// Stylistic... might consider disallowing in the future
|
||||
"no-inner-declarations": 0,
|
||||
@@ -54,7 +55,7 @@
|
||||
"no-negated-in-lhs": 2,
|
||||
"no-obj-calls": 2,
|
||||
"no-regex-spaces": 2,
|
||||
"no-reserved-keys": 2, // Important for IE
|
||||
"quote-props": [2, "as-needed", {"keywords": true}],
|
||||
"no-sparse-arrays": 0,
|
||||
|
||||
// Optimizer and coverage will handle/highlight this and can be useful for debugging
|
||||
@@ -128,7 +129,7 @@
|
||||
"no-catch-shadow": 2,
|
||||
"no-delete-var": 2,
|
||||
"no-label-var": 2,
|
||||
"no-shadow": 2,
|
||||
"no-shadow": 0,
|
||||
"no-shadow-restricted-names": 2,
|
||||
"no-undef": 2,
|
||||
"no-undef-init": 2,
|
||||
@@ -174,7 +175,7 @@
|
||||
"no-ternary": 0,
|
||||
"no-trailing-spaces": 2,
|
||||
"no-underscore-dangle": 0,
|
||||
"no-wrap-func": 2,
|
||||
"no-extra-parens": [2, "functions"],
|
||||
"one-var": 0,
|
||||
"operator-assignment": 0,
|
||||
"padded-blocks": 0,
|
||||
@@ -191,7 +192,7 @@
|
||||
"space-infix-ops": 2,
|
||||
"space-return-throw-case": 2,
|
||||
"space-unary-ops": 2,
|
||||
"spaced-line-comment": 2,
|
||||
"spaced-comment": [2, "always", {"markers": [","]}],
|
||||
"wrap-regex": 1,
|
||||
|
||||
"no-var": 1
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
# Handlebars-template fixtures in test cases need deterministic eol
|
||||
*.handlebars text eol=lf
|
||||
*.hbs text eol=lf
|
||||
|
||||
# Lexer files as well
|
||||
*.l text eol=lf
|
||||
+1
-1
@@ -1,2 +1,2 @@
|
||||
instrumentation:
|
||||
excludes: ['**/spec/**']
|
||||
excludes: ['**/spec/**', '**/handlebars/compiler/parser.js']
|
||||
|
||||
+5
-1
@@ -13,11 +13,15 @@ env:
|
||||
- secure: Nm4AgSfsgNB21kgKrF9Tl7qVZU8YYREhouQunFracTcZZh2NZ2XH5aHuSiXCj88B13Cr/jGbJKsZ4T3QS3wWYtz6lkyVOx3H3iI+TMtqhD9RM3a7A4O+4vVN8IioB2YjhEu0OKjwgX5gp+0uF+pLEi7Hpj6fupD3AbbL5uYcKg8=
|
||||
matrix:
|
||||
include:
|
||||
- node_js: '0.10'
|
||||
- node_js: '5'
|
||||
env:
|
||||
- PUBLISH=true
|
||||
- secure: pLTzghtVll9yGKJI0AaB0uI8GypfWxLTaIB0ZL8//yN3nAEIKMhf/RRilYTsn/rKj2NUa7vt2edYILi3lttOUlCBOwTc9amiRms1W8Lwr/3IdWPeBLvLuH1zNJRm2lBAwU4LBSqaOwhGaxOQr6KHTnWudhNhgOucxpZfvfI/dFw=
|
||||
- secure: yERYCf7AwL11D9uMtacly/THGV8BlzsMmrt+iQVvGA3GaY6QMmfYqf6P6cCH98sH5etd1Y+1e6YrPeMjqI6lyRllT7FptoyOdHulazQe86VQN4sc0EpqMlH088kB7gGjTut9Z+X9ViooT5XEh9WA5jXEI9pXhQJNoIHkWPuwGuY=
|
||||
- node_js: '4'
|
||||
cache:
|
||||
directories:
|
||||
- node_modules
|
||||
|
||||
git:
|
||||
depth: 100
|
||||
|
||||
+21
-2
@@ -6,7 +6,7 @@ Please see our [FAQ](https://github.com/wycats/handlebars.js/blob/master/FAQ.md)
|
||||
|
||||
Should you run into other issues with the project, please don't hesitate to let us know by filing an [issue][issue]! In general we are going to ask for an example of the problem failing, which can be as simple as a jsfiddle/jsbin/etc. We've put together a jsfiddle [template][jsfiddle] to ease this. (We will keep this link up to date as new releases occur, so feel free to check back here)
|
||||
|
||||
Pull requests containing only failing thats demonstrating the issue are welcomed and this also helps ensure that your issue won't regress in the future once it's fixed.
|
||||
Pull requests containing only failing tests demonstrating the issue are welcomed and this also helps ensure that your issue won't regress in the future once it's fixed.
|
||||
|
||||
Documentation issues on the handlebarsjs.com site should be reported on [handlebars-site](https://github.com/wycats/handlebars-site).
|
||||
|
||||
@@ -42,6 +42,25 @@ The `grunt dev` implements watching for tests and allows for in browser testing
|
||||
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).
|
||||
|
||||
##Running Tests
|
||||
|
||||
To run tests locally, first install all dependencies.
|
||||
```sh
|
||||
npm install
|
||||
```
|
||||
|
||||
Clone the mustache specs into the spec/mustache folder.
|
||||
```sh
|
||||
cd spec
|
||||
rm -r mustache
|
||||
git clone https://github.com/mustache/spec.git mustache
|
||||
```
|
||||
|
||||
From the root directory, run the tests.
|
||||
```sh
|
||||
npm test
|
||||
```
|
||||
|
||||
## Ember testing
|
||||
|
||||
The current ember distribution should be tested as part of the handlebars release process. This requires building the `handlebars-source` gem locally and then executing the ember test script.
|
||||
@@ -77,4 +96,4 @@ After this point the handlebars site needs to be updated to point to the new ver
|
||||
[generator-release]: https://github.com/walmartlabs/generator-release
|
||||
[pull-request]: https://github.com/wycats/handlebars.js/pull/new/master
|
||||
[issue]: https://github.com/wycats/handlebars.js/issues/new
|
||||
[jsfiddle]: http://jsfiddle.net/9D88g/26/
|
||||
[jsfiddle]: https://jsfiddle.net/9D88g/47/
|
||||
|
||||
+15
-12
@@ -1,4 +1,4 @@
|
||||
/*eslint-disable no-process-env */
|
||||
/* eslint-disable no-process-env */
|
||||
module.exports = function(grunt) {
|
||||
|
||||
grunt.initConfig({
|
||||
@@ -9,6 +9,8 @@ module.exports = function(grunt) {
|
||||
},
|
||||
files: [
|
||||
'*.js',
|
||||
'bench/**/*.js',
|
||||
'tasks/**/*.js',
|
||||
'lib/**/!(*.min|parser).js',
|
||||
'spec/**/!(*.amd|json2|require).js'
|
||||
]
|
||||
@@ -20,7 +22,7 @@ module.exports = function(grunt) {
|
||||
dist: {
|
||||
options: {
|
||||
processContent: function(content) {
|
||||
return grunt.template.process('/*!\n\n <%= pkg.name %> v<%= pkg.version %>\n\n<%= grunt.file.read("LICENSE") %>\n@license\n*/\n')
|
||||
return grunt.template.process('/**!\n\n @license\n <%= pkg.name %> v<%= pkg.version %>\n\n<%= grunt.file.read("LICENSE") %>\n*/\n')
|
||||
+ content;
|
||||
}
|
||||
},
|
||||
@@ -43,7 +45,9 @@ module.exports = function(grunt) {
|
||||
|
||||
babel: {
|
||||
options: {
|
||||
loose: ['es6.modules']
|
||||
sourceMaps: 'inline',
|
||||
loose: ['es6.modules'],
|
||||
auxiliaryCommentBefore: 'istanbul ignore next'
|
||||
},
|
||||
amd: {
|
||||
options: {
|
||||
@@ -75,7 +79,7 @@ module.exports = function(grunt) {
|
||||
module: {
|
||||
loaders: [
|
||||
// the optional 'runtime' transformer tells babel to require the runtime instead of inlining it.
|
||||
{ test: /\.jsx?$/, exclude: /node_modules/, loader: 'babel-loader?optional=runtime&loose=es6.modules' }
|
||||
{ test: /\.jsx?$/, exclude: /node_modules/, loader: 'babel-loader?optional=runtime&loose=es6.modules&auxiliaryCommentBefore=istanbul%20ignore%20next' }
|
||||
]
|
||||
},
|
||||
output: {
|
||||
@@ -121,7 +125,7 @@ module.exports = function(grunt) {
|
||||
options: {
|
||||
mangle: true,
|
||||
compress: true,
|
||||
preserveComments: 'some'
|
||||
preserveComments: /(?:^!|@(?:license|preserve|cc_on))/
|
||||
},
|
||||
dist: {
|
||||
files: [{
|
||||
@@ -158,15 +162,14 @@ module.exports = function(grunt) {
|
||||
build: process.env.TRAVIS_JOB_ID,
|
||||
urls: ['http://localhost:9999/spec/?headless=true', 'http://localhost:9999/spec/amd.html?headless=true'],
|
||||
detailedError: true,
|
||||
concurrency: 2,
|
||||
concurrency: 4,
|
||||
browsers: [
|
||||
{browserName: 'chrome'},
|
||||
{browserName: 'firefox'},
|
||||
{browserName: 'safari', version: 7, platform: 'OS X 10.9'},
|
||||
{browserName: 'safari', version: 6, platform: 'OS X 10.8'},
|
||||
{browserName: 'firefox', platform: 'Linux'},
|
||||
{browserName: 'safari', version: 9, platform: 'OS X 10.11'},
|
||||
{browserName: 'safari', version: 8, platform: 'OS X 10.10'},
|
||||
{browserName: 'internet explorer', version: 11, platform: 'Windows 8.1'},
|
||||
{browserName: 'internet explorer', version: 10, platform: 'Windows 8'},
|
||||
{browserName: 'internet explorer', version: 9, platform: 'Windows 7'}
|
||||
{browserName: 'internet explorer', version: 10, platform: 'Windows 8'}
|
||||
]
|
||||
}
|
||||
},
|
||||
@@ -207,7 +210,7 @@ module.exports = function(grunt) {
|
||||
this.registerTask('globals', ['webpack']);
|
||||
this.registerTask('tests', ['concat:tests']);
|
||||
|
||||
this.registerTask('release', 'Build final packages', ['eslint', 'amd', 'uglify', 'copy:dist', 'copy:components', 'copy:cdnjs']);
|
||||
this.registerTask('release', 'Build final packages', ['eslint', 'amd', 'uglify', 'test:min', 'copy:dist', 'copy:components', 'copy:cdnjs']);
|
||||
|
||||
// Load tasks from npm
|
||||
grunt.loadNpmTasks('grunt-contrib-clean');
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
Copyright (C) 2011-2014 by Yehuda Katz
|
||||
Copyright (C) 2011-2016 by Yehuda Katz
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
||||
+19
-290
@@ -10,24 +10,12 @@ 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).
|
||||
[http://www.handlebarsjs.com](http://www.handlebarsjs.com) and the live demo at [http://tryhandlebarsjs.com/](http://tryhandlebarsjs.com/).
|
||||
|
||||
Installing
|
||||
----------
|
||||
Installing Handlebars is easy. Simply download the package [from the official site](http://handlebarsjs.com/) or the [bower repository][bower-repo] and add it to your web pages (you should usually use the most recent version).
|
||||
|
||||
For web browsers, a free CDN is available at [jsDelivr](http://www.jsdelivr.com/#!handlebarsjs). Advanced usage, such as [version aliasing & concocting](https://github.com/jsdelivr/jsdelivr#usage), is available.
|
||||
|
||||
Alternatively, if you prefer having the latest version of handlebars from
|
||||
the 'master' branch, passing builds of the 'master' branch are automatically
|
||||
published to S3. You may download the latest passing master build by grabbing
|
||||
a `handlebars-latest.js` file from the [builds page][builds-page]. When the
|
||||
build is published, it is also available as a `handlebars-gitSHA.js` file on
|
||||
the builds page if you need a version to refer to others.
|
||||
`handlebars-runtime.js` builds are also available.
|
||||
|
||||
**Note**: The S3 builds page is provided as a convenience for the community,
|
||||
but you should not use it for hosting Handlebars in production.
|
||||
See our [installation documentation](http://handlebarsjs.com/installation.html).
|
||||
|
||||
Usage
|
||||
-----
|
||||
@@ -57,295 +45,34 @@ var result = template(data);
|
||||
// </ul>
|
||||
```
|
||||
|
||||
Full documentation and more examples are at [handlebarsjs.com](http://handlebarsjs.com/).
|
||||
|
||||
Registering Helpers
|
||||
-------------------
|
||||
|
||||
You can register helpers that Handlebars will use when evaluating your
|
||||
template. Here's an example, which assumes that your objects have a URL
|
||||
embedded in them, as well as the text for a link:
|
||||
|
||||
```js
|
||||
Handlebars.registerHelper('link_to', function() {
|
||||
return new Handlebars.SafeString("<a href='" + Handlebars.Utils.escapeExpression(this.url) + "'>" + Handlebars.Utils.escapeExpression(this.body) + "</a>");
|
||||
});
|
||||
|
||||
var context = { posts: [{url: "/hello-world", body: "Hello World!"}] };
|
||||
var source = "<ul>{{#posts}}<li>{{link_to}}</li>{{/posts}}</ul>"
|
||||
|
||||
var template = Handlebars.compile(source);
|
||||
template(context);
|
||||
|
||||
// Would render:
|
||||
//
|
||||
// <ul>
|
||||
// <li><a href='/hello-world'>Hello World!</a></li>
|
||||
// </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
|
||||
--------
|
||||
|
||||
By default, the `{{expression}}` syntax will escape its contents. This
|
||||
helps to protect you against accidental XSS problems caused by malicious
|
||||
data passed from the server as JSON.
|
||||
|
||||
To explicitly *not* escape the contents, use the triple-mustache
|
||||
(`{{{}}}`). You have seen this used in the above example.
|
||||
Precompiling Templates
|
||||
----------------------
|
||||
|
||||
Handlebars allows templates to be precompiled and included as javascript code rather than the handlebars template allowing for faster startup time. Full details are located [here](http://handlebarsjs.com/precompilation.html).
|
||||
|
||||
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.
|
||||
|
||||
### 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 descendants and ancestors of the
|
||||
current context.
|
||||
|
||||
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:
|
||||
|
||||
```
|
||||
{{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:
|
||||
|
||||
```
|
||||
{{#with person}}{{name}} - {{../company.name}}{{/with}}
|
||||
```
|
||||
|
||||
would render:
|
||||
|
||||
```
|
||||
Alan - Rad, Inc.
|
||||
```
|
||||
|
||||
### Strings
|
||||
|
||||
When calling a helper, you can pass paths or Strings as parameters. For
|
||||
instance:
|
||||
|
||||
```js
|
||||
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"}}}</li>{{/posts}}</ul>'
|
||||
|
||||
var template = Handlebars.compile(source);
|
||||
template(context);
|
||||
|
||||
// Would render:
|
||||
//
|
||||
// <ul>
|
||||
// <li><a href='/posts/hello-world'>Post!</a></li>
|
||||
// </ul>
|
||||
```
|
||||
|
||||
When you pass a String as a parameter to a helper, the literal String
|
||||
gets passed to the helper function.
|
||||
|
||||
|
||||
### 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:
|
||||
|
||||
```js
|
||||
var source = "<ul>{{#people}}<li>{{#link}}{{name}}{{/link}}</li>{{/people}}</ul>";
|
||||
Handlebars.registerHelper('link', function(options) {
|
||||
return '<a href="/people/' + this.id + '">' + options.fn(this) + '</a>';
|
||||
});
|
||||
var template = Handlebars.compile(source);
|
||||
|
||||
var data = { "people": [
|
||||
{ "name": "Alan", "id": 1 },
|
||||
{ "name": "Yehuda", "id": 2 }
|
||||
]};
|
||||
template(data);
|
||||
|
||||
// Should render:
|
||||
// <ul>
|
||||
// <li><a href="/people/1">Alan</a></li>
|
||||
// <li><a href="/people/2">Yehuda</a></li>
|
||||
// </ul>
|
||||
```
|
||||
|
||||
Whenever the block helper is called it is given one or more parameters,
|
||||
any arguments that are passed into 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
|
||||
|
||||
You can register additional templates as partials, which will be used by
|
||||
Handlebars when it encounters a partial (`{{> partialName}}`). Partials
|
||||
can either be String templates or compiled template functions. Here's an
|
||||
example:
|
||||
|
||||
```js
|
||||
var source = "<ul>{{#people}}<li>{{> link}}</li>{{/people}}</ul>";
|
||||
|
||||
Handlebars.registerPartial('link', '<a href="/people/{{id}}">{{name}}</a>')
|
||||
var template = Handlebars.compile(source);
|
||||
|
||||
var data = { "people": [
|
||||
{ "name": "Alan", "id": 1 },
|
||||
{ "name": "Yehuda", "id": 2 }
|
||||
]};
|
||||
|
||||
template(data);
|
||||
|
||||
// Should render:
|
||||
// <ul>
|
||||
// <li><a href="/people/1">Alan</a></li>
|
||||
// <li><a href="/people/2">Yehuda</a></li>
|
||||
// </ul>
|
||||
```
|
||||
|
||||
Partials can also accept parameters
|
||||
|
||||
```js
|
||||
var source = "<div>{{> roster rosterProperties people=listOfPeople}}</div>";
|
||||
|
||||
Handlebars.registerPartial('roster', '<h2>{{rosterName}}</h2>{{#people}}<span>{{id}}: {{name}}</span>{{/people}}')
|
||||
var template = Handlebars.compile(source);
|
||||
|
||||
var data = {
|
||||
"listOfPeople": [
|
||||
{ "name": "Alan", "id": 1 },
|
||||
{ "name": "Yehuda", "id": 2 }
|
||||
],
|
||||
"rosterProperties": {
|
||||
"rosterName": "Cool People"
|
||||
}
|
||||
};
|
||||
|
||||
template(data);
|
||||
|
||||
// Should render:
|
||||
// <div>
|
||||
// <h2>Cool People</h2>
|
||||
// <span>1: Alan</span>
|
||||
// <span>2: Yehuda</span>
|
||||
// </div>
|
||||
|
||||
```
|
||||
|
||||
### Comments
|
||||
|
||||
You can add comments to your templates with the following syntax:
|
||||
|
||||
```js
|
||||
{{! This is a comment }}
|
||||
```
|
||||
|
||||
You can also use real html comments if you want them to end up in the output.
|
||||
|
||||
```html
|
||||
<div>
|
||||
{{! This comment will not end up in the output }}
|
||||
<!-- This comment will show up in the output -->
|
||||
</div>
|
||||
```
|
||||
- [Nested Paths](http://handlebarsjs.com/#paths)
|
||||
- [Helpers](http://handlebarsjs.com/#helpers)
|
||||
- [Block Expressions](http://handlebarsjs.com/#block-expressions)
|
||||
- [Literal Values](http://handlebarsjs.com/#literals)
|
||||
- [Delimited Comments](http://handlebarsjs.com/#comments)
|
||||
|
||||
Block expressions 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.
|
||||
|
||||
### Compatibility
|
||||
|
||||
There are a few Mustache behaviors that Handlebars does not implement.
|
||||
- Handlebars deviates from Mustache slightly in that it does not perform recursive lookup by default. The compile time `compat` flag must be set to enable this functionality. Users should note that there is a performance cost for enabling this flag. The exact cost varies by template, but it's recommended that performance sensitive operations should avoid this mode and instead opt for explicit path references.
|
||||
- The optional Mustache-style lambdas are not supported. Instead Handlebars provides it's own lambda resolution that follows the behaviors of helpers.
|
||||
- Alternative delimeters are not supported.
|
||||
- The optional Mustache-style lambdas are not supported. Instead Handlebars provides its own lambda resolution that follows the behaviors of helpers.
|
||||
- Alternative delimiters are not supported.
|
||||
|
||||
|
||||
Precompiling Templates
|
||||
----------------------
|
||||
|
||||
Handlebars allows templates to be precompiled and included as javascript
|
||||
code rather than the handlebars template allowing for faster startup time.
|
||||
|
||||
### Installation
|
||||
The precompiler script may be installed via npm using the `npm install -g handlebars`
|
||||
command.
|
||||
|
||||
### Usage
|
||||
|
||||
<pre>
|
||||
Precompile handlebar templates.
|
||||
Usage: handlebars template...
|
||||
|
||||
Options:
|
||||
-a, --amd Create an AMD format function (allows loading with RequireJS) [boolean]
|
||||
-f, --output Output File [string]
|
||||
-k, --known Known helpers [string]
|
||||
-o, --knownOnly Known helpers only [boolean]
|
||||
-m, --min Minimize output [boolean]
|
||||
-s, --simple Output template function only. [boolean]
|
||||
-r, --root Template root. Base value that will be stripped from template names. [string]
|
||||
-c, --commonjs Exports CommonJS style, path to Handlebars module [string]
|
||||
-h, --handlebarPath Path to handlebar.js (only valid for amd-style) [string]
|
||||
-n, --namespace Template namespace [string]
|
||||
-p, --partial Compiling a partial template [boolean]
|
||||
-d, --data Include data when compiling [boolean]
|
||||
-e, --extension Template extension. [string]
|
||||
-b, --bom Removes the BOM (Byte Order Mark) from the beginning of the templates. [boolean]
|
||||
</pre>
|
||||
|
||||
If using the precompiler's normal mode, the resulting templates will be
|
||||
stored to the `Handlebars.templates` object using the relative template
|
||||
name sans the extension. These templates may be executed in the same
|
||||
manner as templates.
|
||||
|
||||
If using the simple mode the precompiler will generate a single
|
||||
javascript method. To execute this method it must be passed to
|
||||
the `Handlebars.template` method and the resulting object may be used as normal.
|
||||
|
||||
### Optimizations
|
||||
|
||||
- Rather than using the full _handlebars.js_ library, implementations that
|
||||
do not need to compile templates at runtime may include _handlebars.runtime.js_
|
||||
whose min+gzip size is approximately 1k.
|
||||
- If a helper is known to exist in the target environment they may be defined
|
||||
using the `--known name` argument may be used to optimize accesses to these
|
||||
helpers for size and speed.
|
||||
- When all helpers are known in advance the `--knownOnly` argument may be used
|
||||
to optimize all block helper references.
|
||||
- Implementations that do not use `@data` variables can improve performance of
|
||||
iteration centric templates by specifying `{data: false}` in the compiler options.
|
||||
|
||||
Supported Environments
|
||||
----------------------
|
||||
|
||||
@@ -394,6 +121,7 @@ Handlebars in the Wild
|
||||
* [Assemble](http://assemble.io), by [@jonschlinkert](https://github.com/jonschlinkert)
|
||||
and [@doowb](https://github.com/doowb), is a static site generator that uses Handlebars.js
|
||||
as its template engine.
|
||||
* [Cory](https://github.com/leo/cory), by [@leo](https://github.com/leo), is another tiny static site generator
|
||||
* [CoSchedule](http://coschedule.com) An editorial calendar for WordPress that uses Handlebars.js
|
||||
* [dashbars](https://github.com/pismute/dashbars) A modern helper library for Handlebars.js.
|
||||
* [Ember.js](http://www.emberjs.com) makes Handlebars.js the primary way to
|
||||
@@ -414,13 +142,16 @@ Handlebars in the Wild
|
||||
Handlebars.js with [jQuery](http://jquery.com/).
|
||||
* [Lumbar](http://walmartlabs.github.io/lumbar) provides easy module-based template management for
|
||||
handlebars projects.
|
||||
* [Marionette.Handlebars](https://github.com/hashchange/marionette.handlebars) adds support for Handlebars and Mustache templates to Marionette.
|
||||
* [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.
|
||||
* [YUI](http://yuilibrary.com/yui/docs/handlebars/) implements a port of handlebars
|
||||
* [Swag](https://github.com/elving/swag) by [@elving](https://github.com/elving) is a growing collection of helpers for handlebars.js. Give your handlebars.js templates some swag son!
|
||||
* [DOMBars](https://github.com/blakeembrey/dombars) is a DOM-based templating engine built on the Handlebars parser and runtime
|
||||
* [DOMBars](https://github.com/blakeembrey/dombars) is a DOM-based templating engine built on the Handlebars parser and runtime **DEPRECATED**
|
||||
* [promised-handlebars](https://github.com/nknapp/promised-handlebars) is a wrapper for Handlebars that allows helpers to return Promises.
|
||||
* [just-handlebars-helpers](https://github.com/leapfrogtechnology/just-handlebars-helpers) A fully tested lightweight package with common Handlebars helpers.
|
||||
|
||||
External Resources
|
||||
------------------
|
||||
@@ -433,6 +164,4 @@ License
|
||||
-------
|
||||
Handlebars.js is released under the MIT license.
|
||||
|
||||
[bower-repo]: https://github.com/components/handlebars.js
|
||||
[builds-page]: http://builds.handlebarsjs.com.s3.amazonaws.com/bucket-listing.html?sort=lastmod&sortdir=desc
|
||||
[pull-request]: https://github.com/wycats/handlebars.js/pull/new/master
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"globals": {
|
||||
"require": true
|
||||
},
|
||||
"rules": {
|
||||
// Disabling for tests, for now.
|
||||
"no-path-concat": 0,
|
||||
|
||||
"no-var": 0,
|
||||
"no-shadow": 0,
|
||||
"handle-callback-err": 0,
|
||||
"no-console": 0
|
||||
}
|
||||
}
|
||||
+1
-2
@@ -1,5 +1,4 @@
|
||||
var _ = require('underscore'),
|
||||
async = require('async'),
|
||||
var async = require('async'),
|
||||
fs = require('fs'),
|
||||
zlib = require('zlib');
|
||||
|
||||
|
||||
+1
-1
@@ -2,7 +2,7 @@ var fs = require('fs');
|
||||
|
||||
var metrics = fs.readdirSync(__dirname);
|
||||
metrics.forEach(function(metric) {
|
||||
if (metric === 'index.js' || !/(.*)\.js$/.test(metric)) {
|
||||
if (metric === 'index.js' || !(/(.*)\.js$/.test(metric))) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
module.exports = {
|
||||
helpers: {
|
||||
foo: function(options) {
|
||||
foo: function() {
|
||||
return '';
|
||||
}
|
||||
},
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
module.exports = {
|
||||
context: { names: [{name: "Moe"}, {name: "Larry"}, {name: "Curly"}, {name: "Shemp"}] },
|
||||
handlebars: "{{#each names}}{{name}}{{/each}}",
|
||||
dust: "{#names}{name}{/names}",
|
||||
mustache: "{{#names}}{{name}}{{/names}}",
|
||||
eco: "<% for item in @names: %><%= item.name %><% end %>"
|
||||
context: { names: [{name: 'Moe'}, {name: 'Larry'}, {name: 'Curly'}, {name: 'Shemp'}] },
|
||||
handlebars: '{{#each names}}{{name}}{{/each}}',
|
||||
dust: '{#names}{name}{/names}',
|
||||
mustache: '{{#names}}{{name}}{{/names}}',
|
||||
eco: '<% for item in @names: %><%= item.name %><% end %>'
|
||||
};
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
module.exports = {
|
||||
context: { names: [{name: "Moe"}, {name: "Larry"}, {name: "Curly"}, {name: "Shemp"}] },
|
||||
handlebars: "{{#names}}{{name}}{{/names}}"
|
||||
}
|
||||
context: { names: [{name: 'Moe'}, {name: 'Larry'}, {name: 'Curly'}, {name: 'Shemp'}] },
|
||||
handlebars: '{{#names}}{{name}}{{/names}}'
|
||||
};
|
||||
|
||||
@@ -3,13 +3,13 @@ var fs = require('fs');
|
||||
module.exports = {
|
||||
context: {
|
||||
header: function() {
|
||||
return "Colors";
|
||||
return 'Colors';
|
||||
},
|
||||
hasItems: true, // To make things fairer in mustache land due to no `{{if}}` construct on arrays
|
||||
items: [
|
||||
{name: "red", current: true, url: "#Red"},
|
||||
{name: "green", current: false, url: "#Green"},
|
||||
{name: "blue", current: false, url: "#Blue"}
|
||||
{name: 'red', current: true, url: '#Red'},
|
||||
{name: 'green', current: false, url: '#Green'},
|
||||
{name: 'blue', current: false, url: '#Blue'}
|
||||
]
|
||||
},
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
module.exports = {
|
||||
context: { names: [{name: "Moe"}, {name: "Larry"}, {name: "Curly"}, {name: "Shemp"}] },
|
||||
handlebars: "{{#each names}}{{@index}}{{name}}{{/each}}"
|
||||
}
|
||||
context: { names: [{name: 'Moe'}, {name: 'Larry'}, {name: 'Curly'}, {name: 'Shemp'}] },
|
||||
handlebars: '{{#each names}}{{@index}}{{name}}{{/each}}'
|
||||
};
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
module.exports = {
|
||||
context: { names: [{name: "Moe"}, {name: "Larry"}, {name: "Curly"}, {name: "Shemp"}], foo: 'bar' },
|
||||
handlebars: "{{#each names}}{{../foo}}{{/each}}",
|
||||
mustache: "{{#names}}{{foo}}{{/names}}",
|
||||
eco: "<% for item in @names: %><%= @foo %><% end %>"
|
||||
context: { names: [{name: 'Moe'}, {name: 'Larry'}, {name: 'Curly'}, {name: 'Shemp'}], foo: 'bar' },
|
||||
handlebars: '{{#each names}}{{../foo}}{{/each}}',
|
||||
mustache: '{{#names}}{{foo}}{{/names}}',
|
||||
eco: '<% for item in @names: %><%= @foo %><% end %>'
|
||||
};
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
module.exports = {
|
||||
context: { names: [{bat: 'foo', name: ["Moe"]}, {bat: 'foo', name: ["Larry"]}, {bat: 'foo', name: ["Curly"]}, {bat: 'foo', name: ["Shemp"]}], foo: 'bar' },
|
||||
handlebars: "{{#each names}}{{#each name}}{{../bat}}{{../../foo}}{{/each}}{{/each}}",
|
||||
mustache: "{{#names}}{{#name}}{{bat}}{{foo}}{{/name}}{{/names}}",
|
||||
eco: "<% for item in @names: %><% for child in item.name: %><%= item.bat %><%= @foo %><% end %><% end %>"
|
||||
context: { names: [{bat: 'foo', name: ['Moe']}, {bat: 'foo', name: ['Larry']}, {bat: 'foo', name: ['Curly']}, {bat: 'foo', name: ['Shemp']}], foo: 'bar' },
|
||||
handlebars: '{{#each names}}{{#each name}}{{../bat}}{{../../foo}}{{/each}}{{/each}}',
|
||||
mustache: '{{#names}}{{#name}}{{bat}}{{foo}}{{/name}}{{/names}}',
|
||||
eco: '<% for item in @names: %><% for child in item.name: %><%= item.bat %><%= @foo %><% end %><% end %>'
|
||||
};
|
||||
|
||||
@@ -2,7 +2,7 @@ var fs = require('fs');
|
||||
|
||||
var templates = fs.readdirSync(__dirname);
|
||||
templates.forEach(function(template) {
|
||||
if (template === 'index.js' || !/(.*)\.js$/.test(template)) {
|
||||
if (template === 'index.js' || !(/(.*)\.js$/.test(template))) {
|
||||
return;
|
||||
}
|
||||
module.exports[RegExp.$1] = require('./' + RegExp.$1);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
module.exports = {
|
||||
context: { person: { name: "Larry", age: 45 } },
|
||||
handlebars: "{{#person}}{{name}}{{age}}{{/person}}"
|
||||
context: { person: { name: 'Larry', age: 45 } },
|
||||
handlebars: '{{#person}}{{name}}{{age}}{{/person}}'
|
||||
};
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
module.exports = {
|
||||
context: { person: { name: "Larry", age: 45 } },
|
||||
handlebars: "{{#with person}}{{name}}{{age}}{{/with}}",
|
||||
dust: "{#person}{name}{age}{/person}",
|
||||
eco: "<%= @person.name %><%= @person.age %>",
|
||||
mustache: "{{#person}}{{name}}{{age}}{{/person}}"
|
||||
context: { person: { name: 'Larry', age: 45 } },
|
||||
handlebars: '{{#with person}}{{name}}{{age}}{{/with}}',
|
||||
dust: '{#person}{name}{age}{/person}',
|
||||
eco: '<%= @person.name %><%= @person.age %>',
|
||||
mustache: '{{#person}}{{name}}{{age}}{{/person}}'
|
||||
};
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
module.exports = {
|
||||
context: { name: '1', kids: [{ name: '1.1', kids: [{name: '1.1.1', kids: []}] }] },
|
||||
partials: {
|
||||
mustache: { recursion: "{{name}}{{#kids}}{{>recursion}}{{/kids}}" },
|
||||
handlebars: { recursion: "{{name}}{{#each kids}}{{>recursion}}{{/each}}" }
|
||||
mustache: { recursion: '{{name}}{{#kids}}{{>recursion}}{{/kids}}' },
|
||||
handlebars: { recursion: '{{name}}{{#each kids}}{{>recursion}}{{/each}}' }
|
||||
},
|
||||
handlebars: "{{name}}{{#each kids}}{{>recursion}}{{/each}}",
|
||||
dust: "{name}{#kids}{>recursion:./}{/kids}",
|
||||
mustache: "{{name}}{{#kids}}{{>recursion}}{{/kids}}"
|
||||
handlebars: '{{name}}{{#each kids}}{{>recursion}}{{/each}}',
|
||||
dust: '{name}{#kids}{>recursion:./}{/kids}',
|
||||
mustache: '{{name}}{{#kids}}{{>recursion}}{{/kids}}'
|
||||
};
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
module.exports = {
|
||||
context: { peeps: [{name: "Moe", count: 15}, {name: "Larry", count: 5}, {name: "Curly", count: 1}] },
|
||||
context: { peeps: [{name: 'Moe', count: 15}, {name: 'Larry', count: 5}, {name: 'Curly', count: 1}] },
|
||||
partials: {
|
||||
mustache: { variables: "Hello {{name}}! You have {{count}} new messages." },
|
||||
handlebars: { variables: "Hello {{name}}! You have {{count}} new messages." }
|
||||
mustache: { variables: 'Hello {{name}}! You have {{count}} new messages.' },
|
||||
handlebars: { variables: 'Hello {{name}}! You have {{count}} new messages.' }
|
||||
},
|
||||
|
||||
handlebars: "{{#each peeps}}{{>variables}}{{/each}}",
|
||||
dust: "{#peeps}{>variables/}{/peeps}",
|
||||
mustache: "{{#peeps}}{{>variables}}{{/peeps}}"
|
||||
handlebars: '{{#each peeps}}{{>variables}}{{/each}}',
|
||||
dust: '{#peeps}{>variables/}{/peeps}',
|
||||
mustache: '{{#peeps}}{{>variables}}{{/peeps}}'
|
||||
};
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
module.exports = {
|
||||
context: { person: { name: {bar: {baz: "Larry"}}, age: 45 } },
|
||||
handlebars: "{{person.name.bar.baz}}{{person.age}}{{person.foo}}{{animal.age}}",
|
||||
dust: "{person.name.bar.baz}{person.age}{person.foo}{animal.age}",
|
||||
eco: "<%= @person.name.bar.baz %><%= @person.age %><%= @person.foo %><% if @animal: %><%= @animal.age %><% end %>",
|
||||
mustache: "{{person.name.bar.baz}}{{person.age}}{{person.foo}}{{animal.age}}"
|
||||
context: { person: { name: {bar: {baz: 'Larry'}}, age: 45 } },
|
||||
handlebars: '{{person.name.bar.baz}}{{person.age}}{{person.foo}}{{animal.age}}',
|
||||
dust: '{person.name.bar.baz}{person.age}{person.foo}{animal.age}',
|
||||
eco: '<%= @person.name.bar.baz %><%= @person.age %><%= @person.foo %><% if @animal: %><%= @animal.age %><% end %>',
|
||||
mustache: '{{person.name.bar.baz}}{{person.age}}{{person.foo}}{{animal.age}}'
|
||||
};
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
module.exports = {
|
||||
context: {},
|
||||
handlebars: "Hello world",
|
||||
dust: "Hello world",
|
||||
mustache: "Hello world",
|
||||
eco: "Hello world"
|
||||
handlebars: 'Hello world',
|
||||
dust: 'Hello world',
|
||||
mustache: 'Hello world',
|
||||
eco: 'Hello world'
|
||||
};
|
||||
|
||||
@@ -4,11 +4,11 @@ module.exports = {
|
||||
return 'foo ' + value;
|
||||
},
|
||||
header: function() {
|
||||
return "Colors";
|
||||
return 'Colors';
|
||||
}
|
||||
},
|
||||
handlebars: "{{echo (header)}}",
|
||||
eco: "<%= @echo(@header()) %>"
|
||||
handlebars: '{{echo (header)}}',
|
||||
eco: '<%= @echo(@header()) %>'
|
||||
};
|
||||
|
||||
module.exports.context = module.exports.helpers;
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
module.exports = {
|
||||
context: {name: "Mick", count: 30},
|
||||
handlebars: "Hello {{name}}! You have {{count}} new messages.",
|
||||
dust: "Hello {name}! You have {count} new messages.",
|
||||
mustache: "Hello {{name}}! You have {{count}} new messages.",
|
||||
eco: "Hello <%= @name %>! You have <%= @count %> new messages."
|
||||
context: {name: 'Mick', count: 30},
|
||||
handlebars: 'Hello {{name}}! You have {{count}} new messages.',
|
||||
dust: 'Hello {name}! You have {count} new messages.',
|
||||
mustache: 'Hello {{name}}! You have {{count}} new messages.',
|
||||
eco: 'Hello <%= @name %>! You have <%= @count %> new messages.'
|
||||
};
|
||||
|
||||
|
||||
+16
-17
@@ -1,23 +1,22 @@
|
||||
var _ = require('underscore'),
|
||||
runner = require('./util/template-runner'),
|
||||
templates = require('./templates'),
|
||||
|
||||
eco, dust, Handlebars, Mustache, eco;
|
||||
eco, dust, Handlebars, Mustache;
|
||||
|
||||
try {
|
||||
dust = require("dustjs-linkedin");
|
||||
dust = require('dustjs-linkedin');
|
||||
} catch (err) { /* NOP */ }
|
||||
|
||||
try {
|
||||
Mustache = require("mustache");
|
||||
Mustache = require('mustache');
|
||||
} catch (err) { /* NOP */ }
|
||||
|
||||
try {
|
||||
eco = require("eco");
|
||||
eco = require('eco');
|
||||
} catch (err) { /* NOP */ }
|
||||
|
||||
function error() {
|
||||
throw new Error("EWOT");
|
||||
throw new Error('EWOT');
|
||||
}
|
||||
|
||||
function makeSuite(bench, name, template, handlebarsOnly) {
|
||||
@@ -34,19 +33,19 @@ function makeSuite(bench, name, template, handlebarsOnly) {
|
||||
mustacheOut;
|
||||
|
||||
var handlebar = Handlebars.compile(template.handlebars, {data: false}),
|
||||
compat = Handlebars.compile(template.handlebars, {data: false, compat: true}),
|
||||
compat = Handlebars.compile(template.handlebars, {data: false, compat: true}),
|
||||
options = {helpers: template.helpers};
|
||||
_.each(template.partials && template.partials.handlebars, function(partial, name) {
|
||||
Handlebars.registerPartial(name, Handlebars.compile(partial, {data: false}));
|
||||
_.each(template.partials && template.partials.handlebars, function(partial, partialName) {
|
||||
Handlebars.registerPartial(partialName, Handlebars.compile(partial, {data: false}));
|
||||
});
|
||||
|
||||
handlebarsOut = handlebar(context, options);
|
||||
bench("handlebars", function() {
|
||||
bench('handlebars', function() {
|
||||
handlebar(context, options);
|
||||
});
|
||||
|
||||
compatOut = compat(context, options);
|
||||
bench("compat", function() {
|
||||
bench('compat', function() {
|
||||
compat(context, options);
|
||||
});
|
||||
|
||||
@@ -61,8 +60,8 @@ function makeSuite(bench, name, template, handlebarsOnly) {
|
||||
|
||||
dust.render(templateName, context, function(err, out) { dustOut = out; });
|
||||
|
||||
bench("dust", function() {
|
||||
dust.render(templateName, context, function(err, out) { });
|
||||
bench('dust', function() {
|
||||
dust.render(templateName, context, function() {});
|
||||
});
|
||||
} else {
|
||||
bench('dust', error);
|
||||
@@ -75,11 +74,11 @@ function makeSuite(bench, name, template, handlebarsOnly) {
|
||||
|
||||
ecoOut = ecoTemplate(context);
|
||||
|
||||
bench("eco", function() {
|
||||
bench('eco', function() {
|
||||
ecoTemplate(context);
|
||||
});
|
||||
} else {
|
||||
bench("eco", error);
|
||||
bench('eco', error);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -90,11 +89,11 @@ function makeSuite(bench, name, template, handlebarsOnly) {
|
||||
if (mustacheSource) {
|
||||
mustacheOut = Mustache.to_html(mustacheSource, context, mustachePartials);
|
||||
|
||||
bench("mustache", function() {
|
||||
bench('mustache', function() {
|
||||
Mustache.to_html(mustacheSource, context, mustachePartials);
|
||||
});
|
||||
} else {
|
||||
bench("mustache", error);
|
||||
bench('mustache', error);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+33
-35
@@ -1,7 +1,7 @@
|
||||
var _ = require('underscore'),
|
||||
Benchmark = require("benchmark");
|
||||
Benchmark = require('benchmark');
|
||||
|
||||
var BenchWarmer = function(names) {
|
||||
function BenchWarmer() {
|
||||
this.benchmarks = [];
|
||||
this.currentBenches = [];
|
||||
this.names = [];
|
||||
@@ -9,9 +9,9 @@ var BenchWarmer = function(names) {
|
||||
this.minimum = Infinity;
|
||||
this.maximum = -Infinity;
|
||||
this.errors = {};
|
||||
};
|
||||
}
|
||||
|
||||
var print = require("sys").print;
|
||||
var print = require('util').print;
|
||||
|
||||
BenchWarmer.prototype = {
|
||||
winners: function(benches) {
|
||||
@@ -29,7 +29,7 @@ BenchWarmer.prototype = {
|
||||
});
|
||||
},
|
||||
push: function(name, fn) {
|
||||
if(this.names.indexOf(name) == -1) {
|
||||
if (this.names.indexOf(name) == -1) {
|
||||
this.names.push(name);
|
||||
}
|
||||
|
||||
@@ -37,9 +37,9 @@ BenchWarmer.prototype = {
|
||||
this.first = false;
|
||||
|
||||
var bench = new Benchmark(fn, {
|
||||
name: this.suiteName + ": " + name,
|
||||
name: this.suiteName + ': ' + name,
|
||||
onComplete: function() {
|
||||
if(first) { self.startLine(suiteName); }
|
||||
if (first) { self.startLine(suiteName); }
|
||||
self.writeBench(bench);
|
||||
self.currentBenches.push(bench);
|
||||
}, onError: function() {
|
||||
@@ -58,7 +58,7 @@ BenchWarmer.prototype = {
|
||||
this.printHeader('ops/msec', true);
|
||||
|
||||
Benchmark.invoke(this.benchmarks, {
|
||||
name: "run",
|
||||
name: 'run',
|
||||
onComplete: function() {
|
||||
self.scaleTimes();
|
||||
|
||||
@@ -76,7 +76,7 @@ BenchWarmer.prototype = {
|
||||
print('\n');
|
||||
|
||||
var errors = false, prop, bench;
|
||||
for(prop in self.errors) {
|
||||
for (prop in self.errors) {
|
||||
if (self.errors.hasOwnProperty(prop)
|
||||
&& self.errors[prop].error.message !== 'EWOT') {
|
||||
errors = true;
|
||||
@@ -84,18 +84,18 @@ BenchWarmer.prototype = {
|
||||
}
|
||||
}
|
||||
|
||||
if(errors) {
|
||||
print("\n\nErrors:\n");
|
||||
for(prop in self.errors) {
|
||||
if (errors) {
|
||||
print('\n\nErrors:\n');
|
||||
for (prop in self.errors) {
|
||||
if (self.errors.hasOwnProperty(prop)
|
||||
&& self.errors[prop].error.message !== 'EWOT') {
|
||||
bench = self.errors[prop];
|
||||
print("\n" + bench.name + ":\n");
|
||||
print('\n' + bench.name + ':\n');
|
||||
print(bench.error.message);
|
||||
if(bench.error.stack) {
|
||||
print(bench.error.stack.join("\n"));
|
||||
if (bench.error.stack) {
|
||||
print(bench.error.stack.join('\n'));
|
||||
}
|
||||
print("\n");
|
||||
print('\n');
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -104,7 +104,7 @@ BenchWarmer.prototype = {
|
||||
}
|
||||
});
|
||||
|
||||
print("\n");
|
||||
print('\n');
|
||||
},
|
||||
|
||||
scaleTimes: function() {
|
||||
@@ -121,10 +121,10 @@ BenchWarmer.prototype = {
|
||||
printHeader: function(title, winners) {
|
||||
var benchSize = 0, names = this.names, i, l;
|
||||
|
||||
for(i=0, l=names.length; i<l; i++) {
|
||||
for (i = 0, l = names.length; i < l; i++) {
|
||||
var name = names[i];
|
||||
|
||||
if(benchSize < name.length) { benchSize = name.length; }
|
||||
if (benchSize < name.length) { benchSize = name.length; }
|
||||
}
|
||||
|
||||
this.nameSize = benchSize + 2;
|
||||
@@ -133,28 +133,28 @@ BenchWarmer.prototype = {
|
||||
|
||||
this.startLine(title);
|
||||
horSize = horSize + this.benchSize;
|
||||
for(i=0, l=names.length; i<l; i++) {
|
||||
for (i = 0, l = names.length; i < l; i++) {
|
||||
this.writeValue(names[i]);
|
||||
horSize = horSize + this.benchSize;
|
||||
}
|
||||
|
||||
if (winners) {
|
||||
print("WINNER(S)");
|
||||
horSize = horSize + "WINNER(S)".length;
|
||||
print('WINNER(S)');
|
||||
horSize = horSize + 'WINNER(S)'.length;
|
||||
}
|
||||
|
||||
print("\n" + new Array(horSize + 1).join("-"));
|
||||
print('\n' + new Array(horSize + 1).join('-'));
|
||||
},
|
||||
|
||||
startLine: function(name) {
|
||||
var winners = Benchmark.map(this.winners(this.currentBenches), function(bench) {
|
||||
return bench.name.split(": ")[1];
|
||||
return bench.name.split(': ')[1];
|
||||
});
|
||||
|
||||
this.currentBenches = [];
|
||||
|
||||
print(winners.join(", "));
|
||||
print("\n");
|
||||
print(winners.join(', '));
|
||||
print('\n');
|
||||
|
||||
if (name) {
|
||||
this.writeValue(name);
|
||||
@@ -163,9 +163,9 @@ BenchWarmer.prototype = {
|
||||
writeBench: function(bench) {
|
||||
var out;
|
||||
|
||||
if(!bench.error) {
|
||||
if (!bench.error) {
|
||||
var count = bench.hz,
|
||||
moe = count * bench.stats.rme / 100,
|
||||
moe = count * bench.stats.rme / 100,
|
||||
minimum,
|
||||
maximum;
|
||||
|
||||
@@ -174,24 +174,22 @@ BenchWarmer.prototype = {
|
||||
minimum = count - moe;
|
||||
maximum = count + moe;
|
||||
|
||||
out = count + " ±" + moe + " (" + bench.cycles + ")";
|
||||
out = count + ' ±' + moe + ' (' + bench.cycles + ')';
|
||||
|
||||
this.times[bench.suiteName][bench.benchName] = count;
|
||||
this.minimum = Math.min(this.minimum, minimum);
|
||||
this.maximum = Math.max(this.maximum, maximum);
|
||||
} else if (bench.error.message === 'EWOT') {
|
||||
out = 'NA';
|
||||
} else {
|
||||
if (bench.error.message === 'EWOT') {
|
||||
out = 'NA';
|
||||
} else {
|
||||
out = 'E';
|
||||
}
|
||||
out = 'E';
|
||||
}
|
||||
this.writeValue(out);
|
||||
},
|
||||
|
||||
writeValue: function(out) {
|
||||
var padding = this.benchSize - out.length + 1;
|
||||
out = out + new Array(padding).join(" ");
|
||||
out = out + new Array(padding).join(' ');
|
||||
print(out);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -22,6 +22,8 @@ module.exports = function(grunt, makeSuite, callback) {
|
||||
});
|
||||
|
||||
warmer.bench(function() {
|
||||
callback && callback(warmer.times, warmer.scaled);
|
||||
if (callback) {
|
||||
callback(warmer.times, warmer.scaled);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
+26
-9
@@ -26,7 +26,7 @@ var optimist = require('optimist')
|
||||
'type': 'string',
|
||||
'description': 'Path to handlebar.js (only valid for amd-style)',
|
||||
'alias': 'handlebarPath',
|
||||
'default': ''
|
||||
'default': ''
|
||||
},
|
||||
'k': {
|
||||
'type': 'string',
|
||||
@@ -54,17 +54,27 @@ var optimist = require('optimist')
|
||||
'description': 'Output template function only.',
|
||||
'alias': 'simple'
|
||||
},
|
||||
'N': {
|
||||
'type': 'string',
|
||||
'description': 'Name of passed string templates. Optional if running in a simple mode. Required when operating on multiple templates.',
|
||||
'alias': 'name'
|
||||
},
|
||||
'i': {
|
||||
'type': 'string',
|
||||
'description': 'Generates a template from the passed CLI argument.\n"-" is treated as a special value and causes stdin to be read for the template value.',
|
||||
'alias': 'string'
|
||||
},
|
||||
'r': {
|
||||
'type': 'string',
|
||||
'description': 'Template root. Base value that will be stripped from template names.',
|
||||
'alias': 'root'
|
||||
},
|
||||
'p' : {
|
||||
'p': {
|
||||
'type': 'boolean',
|
||||
'description': 'Compiling a partial template',
|
||||
'alias': 'partial'
|
||||
},
|
||||
'd' : {
|
||||
'd': {
|
||||
'type': 'boolean',
|
||||
'description': 'Include data when compiling',
|
||||
'alias': 'data'
|
||||
@@ -92,6 +102,7 @@ var optimist = require('optimist')
|
||||
}
|
||||
})
|
||||
|
||||
.wrap(120)
|
||||
.check(function(argv) {
|
||||
if (argv.version) {
|
||||
return;
|
||||
@@ -100,12 +111,18 @@ var optimist = require('optimist')
|
||||
|
||||
|
||||
var argv = optimist.argv;
|
||||
argv.templates = argv._;
|
||||
argv.files = argv._;
|
||||
delete argv._;
|
||||
|
||||
if (argv.help || (!argv.templates.length && !argv.version)) {
|
||||
optimist.showHelp();
|
||||
return;
|
||||
}
|
||||
var Precompiler = require('../dist/cjs/precompiler');
|
||||
Precompiler.loadTemplates(argv, function(err, opts) {
|
||||
if (err) {
|
||||
throw err;
|
||||
}
|
||||
|
||||
return require('../dist/cjs/precompiler').cli(argv);
|
||||
if (opts.help || (!opts.templates.length && !opts.version)) {
|
||||
optimist.showHelp();
|
||||
} else {
|
||||
Precompiler.cli(opts);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
{
|
||||
"name": "handlebars",
|
||||
"version": "3.0.3",
|
||||
"version": "4.0.9",
|
||||
"main": "handlebars.js",
|
||||
"license": "MIT",
|
||||
"dependencies": {}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<package>
|
||||
<metadata>
|
||||
<id>handlebars.js</id>
|
||||
<version>3.0.3</version>
|
||||
<version>4.0.9</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>
|
||||
|
||||
+42
-2
@@ -83,10 +83,23 @@ interface PartialStatement <: Statement {
|
||||
name: PathExpression | SubExpression;
|
||||
params: [ Expression ];
|
||||
hash: Hash;
|
||||
|
||||
|
||||
indent: string;
|
||||
strip: StripFlags | null;
|
||||
}
|
||||
|
||||
interface PartialBlockStatement <: Statement {
|
||||
type: "PartialBlockStatement";
|
||||
name: PathExpression | SubExpression;
|
||||
params: [ Expression ];
|
||||
hash: Hash;
|
||||
|
||||
program: Program | null;
|
||||
|
||||
indent: string;
|
||||
openStrip: StripFlags | null;
|
||||
closeStrip: StripFlags | null;
|
||||
}
|
||||
```
|
||||
|
||||
`name` will be a `SubExpression` when tied to a dynamic partial, i.e. `{{> (foo) }}`, otherwise this is a path or literal whose `original` value is used to lookup the desired partial.
|
||||
@@ -107,6 +120,33 @@ interface CommentStatement <: Statement {
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
```java
|
||||
interface Decorator <: Statement {
|
||||
type: "Decorator";
|
||||
|
||||
path: PathExpression | Literal;
|
||||
params: [ Expression ];
|
||||
hash: Hash;
|
||||
|
||||
strip: StripFlags | null;
|
||||
}
|
||||
|
||||
interface DecoratorBlock <: Statement {
|
||||
type: "DecoratorBlock";
|
||||
path: PathExpression | Literal;
|
||||
params: [ Expression ];
|
||||
hash: Hash;
|
||||
|
||||
program: Program | null;
|
||||
|
||||
openStrip: StripFlags | null;
|
||||
closeStrip: StripFlags | null;
|
||||
}
|
||||
```
|
||||
|
||||
Decorator paths only utilize the `path.original` value and as a consequence do not support depthed evaluation.
|
||||
|
||||
### Expressions
|
||||
|
||||
```java
|
||||
@@ -236,7 +276,7 @@ The `Handlebars.JavaScriptCompiler` object has a number of methods that may be c
|
||||
|
||||
- `parent` is the existing code in the path resolution
|
||||
- `name` is the current path component
|
||||
- `type` is the type of name being evaluated. May be one of `context`, `data`, `helper`, or `partial`.
|
||||
- `type` is the type of name being evaluated. May be one of `context`, `data`, `helper`, `decorator`, or `partial`.
|
||||
|
||||
Note that this does not impact dynamic partials, which implementors need to be aware of. Overriding `VM.resolvePartial` may be required to support dynamic cases.
|
||||
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
# Decorators
|
||||
|
||||
Decorators allow for blocks to be annotated with metadata or wrapped in functionality prior to execution of the block. This may be used to communicate with the containing helper or to setup a particular state in the system prior to running the block.
|
||||
|
||||
Decorators are registered through similar methods as helpers, `registerDecorators` and `unregisterDecorators`. These can then be referenced via the friendly name in the template using the `{{* decorator}}` and `{{#* decorator}}{/decorator}}` syntaxes. These syntaxes are derivatives of the normal mustache syntax and as such have all of the same argument and whitespace behaviors.
|
||||
|
||||
Decorators are executed when the block program is instantiated and are passed `(program, props, container, context, data, blockParams, depths)`.
|
||||
|
||||
- `program`: The block to wrap
|
||||
- `props`: Object used to set metadata on the final function. Any values set on this object will be set on the function, regardless of if the original function is replaced or not. Metadata should be applied using this object as values applied to `program` may be masked by subsequent decorators that may wrap `program`.
|
||||
- `container`: The current runtime container
|
||||
- `context`: The current context. Since the decorator is run before the block that contains it, this is the parent context.
|
||||
- `data`: The current `@data` values
|
||||
- `blockParams`: The current block parameters stack
|
||||
- `depths`: The current context stack
|
||||
|
||||
Decorators may set values on `props` or return a modified function that wraps `program` in particular behaviors. If the decorator returns nothing, then `program` is left unaltered.
|
||||
|
||||
The [inline partial](https://github.com/wycats/handlebars.js/blob/master/lib/handlebars/decorators/inline.js) implementation provides an example of decorators being used for both metadata and wrapping behaviors.
|
||||
+26
-192
@@ -1,8 +1,11 @@
|
||||
import * as Utils from './utils';
|
||||
import {createFrame, extend, toString} from './utils';
|
||||
import Exception from './exception';
|
||||
import {registerDefaultHelpers} from './helpers';
|
||||
import {registerDefaultDecorators} from './decorators';
|
||||
import logger from './logger';
|
||||
|
||||
export const VERSION = '3.0.1';
|
||||
export const COMPILER_REVISION = 6;
|
||||
export const VERSION = '4.0.9';
|
||||
export const COMPILER_REVISION = 7;
|
||||
|
||||
export const REVISION_CHANGES = {
|
||||
1: '<= 1.0.rc.2', // 1.0.rc.2 is actually rev2 but doesn't report it
|
||||
@@ -10,31 +13,31 @@ export const REVISION_CHANGES = {
|
||||
3: '== 1.0.0-rc.4',
|
||||
4: '== 1.x.x',
|
||||
5: '== 2.0.0-alpha.x',
|
||||
6: '>= 2.0.0-beta.1'
|
||||
6: '>= 2.0.0-beta.1',
|
||||
7: '>= 4.0.0'
|
||||
};
|
||||
|
||||
const isArray = Utils.isArray,
|
||||
isFunction = Utils.isFunction,
|
||||
toString = Utils.toString,
|
||||
objectType = '[object Object]';
|
||||
const objectType = '[object Object]';
|
||||
|
||||
export function HandlebarsEnvironment(helpers, partials) {
|
||||
export function HandlebarsEnvironment(helpers, partials, decorators) {
|
||||
this.helpers = helpers || {};
|
||||
this.partials = partials || {};
|
||||
this.decorators = decorators || {};
|
||||
|
||||
registerDefaultHelpers(this);
|
||||
registerDefaultDecorators(this);
|
||||
}
|
||||
|
||||
HandlebarsEnvironment.prototype = {
|
||||
constructor: HandlebarsEnvironment,
|
||||
|
||||
logger: logger,
|
||||
log: log,
|
||||
log: logger.log,
|
||||
|
||||
registerHelper: function(name, fn) {
|
||||
if (toString.call(name) === objectType) {
|
||||
if (fn) { throw new Exception('Arg not supported with multiple helpers'); }
|
||||
Utils.extend(this.helpers, name);
|
||||
extend(this.helpers, name);
|
||||
} else {
|
||||
this.helpers[name] = fn;
|
||||
}
|
||||
@@ -45,200 +48,31 @@ HandlebarsEnvironment.prototype = {
|
||||
|
||||
registerPartial: function(name, partial) {
|
||||
if (toString.call(name) === objectType) {
|
||||
Utils.extend(this.partials, name);
|
||||
extend(this.partials, name);
|
||||
} else {
|
||||
if (typeof partial === 'undefined') {
|
||||
throw new Exception('Attempting to register a partial as undefined');
|
||||
throw new Exception(`Attempting to register a partial called "${name}" as undefined`);
|
||||
}
|
||||
this.partials[name] = partial;
|
||||
}
|
||||
},
|
||||
unregisterPartial: function(name) {
|
||||
delete this.partials[name];
|
||||
}
|
||||
};
|
||||
},
|
||||
|
||||
function registerDefaultHelpers(instance) {
|
||||
instance.registerHelper('helperMissing', function(/* [args, ]options */) {
|
||||
if (arguments.length === 1) {
|
||||
// A missing field in a {{foo}} constuct.
|
||||
return undefined;
|
||||
registerDecorator: function(name, fn) {
|
||||
if (toString.call(name) === objectType) {
|
||||
if (fn) { throw new Exception('Arg not supported with multiple decorators'); }
|
||||
extend(this.decorators, name);
|
||||
} else {
|
||||
// Someone is actually trying to call something, blow up.
|
||||
throw new Exception('Missing helper: "' + arguments[arguments.length - 1].name + '"');
|
||||
}
|
||||
});
|
||||
|
||||
instance.registerHelper('blockHelperMissing', function(context, options) {
|
||||
let inverse = options.inverse,
|
||||
fn = options.fn;
|
||||
|
||||
if (context === true) {
|
||||
return fn(this);
|
||||
} else if (context === false || context == null) {
|
||||
return inverse(this);
|
||||
} else if (isArray(context)) {
|
||||
if (context.length > 0) {
|
||||
if (options.ids) {
|
||||
options.ids = [options.name];
|
||||
}
|
||||
|
||||
return instance.helpers.each(context, options);
|
||||
} else {
|
||||
return inverse(this);
|
||||
}
|
||||
} else {
|
||||
if (options.data && options.ids) {
|
||||
let data = createFrame(options.data);
|
||||
data.contextPath = Utils.appendContextPath(options.data.contextPath, options.name);
|
||||
options = {data: data};
|
||||
}
|
||||
|
||||
return fn(context, options);
|
||||
}
|
||||
});
|
||||
|
||||
instance.registerHelper('each', function(context, options) {
|
||||
if (!options) {
|
||||
throw new Exception('Must pass iterator to #each');
|
||||
}
|
||||
|
||||
let fn = options.fn,
|
||||
inverse = options.inverse,
|
||||
i = 0,
|
||||
ret = '',
|
||||
data,
|
||||
contextPath;
|
||||
|
||||
if (options.data && options.ids) {
|
||||
contextPath = Utils.appendContextPath(options.data.contextPath, options.ids[0]) + '.';
|
||||
}
|
||||
|
||||
if (isFunction(context)) { context = context.call(this); }
|
||||
|
||||
if (options.data) {
|
||||
data = createFrame(options.data);
|
||||
}
|
||||
|
||||
function execIteration(field, index, last) {
|
||||
if (data) {
|
||||
data.key = field;
|
||||
data.index = index;
|
||||
data.first = index === 0;
|
||||
data.last = !!last;
|
||||
|
||||
if (contextPath) {
|
||||
data.contextPath = contextPath + field;
|
||||
}
|
||||
}
|
||||
|
||||
ret = ret + fn(context[field], {
|
||||
data: data,
|
||||
blockParams: Utils.blockParams([context[field], field], [contextPath + field, null])
|
||||
});
|
||||
}
|
||||
|
||||
if (context && typeof context === 'object') {
|
||||
if (isArray(context)) {
|
||||
for (let j = context.length; i < j; i++) {
|
||||
execIteration(i, i, i === context.length - 1);
|
||||
}
|
||||
} else {
|
||||
let priorKey;
|
||||
|
||||
for (let key in context) {
|
||||
if (context.hasOwnProperty(key)) {
|
||||
// We're running the iterations one step out of sync so we can detect
|
||||
// the last iteration without have to scan the object twice and create
|
||||
// an itermediate keys array.
|
||||
if (priorKey) {
|
||||
execIteration(priorKey, i - 1);
|
||||
}
|
||||
priorKey = key;
|
||||
i++;
|
||||
}
|
||||
}
|
||||
if (priorKey) {
|
||||
execIteration(priorKey, i - 1, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (i === 0) {
|
||||
ret = inverse(this);
|
||||
}
|
||||
|
||||
return ret;
|
||||
});
|
||||
|
||||
instance.registerHelper('if', function(conditional, options) {
|
||||
if (isFunction(conditional)) { conditional = conditional.call(this); }
|
||||
|
||||
// Default behavior is to render the positive path if the value is truthy and not empty.
|
||||
// The `includeZero` option may be set to treat the condtional as purely not empty based on the
|
||||
// behavior of isEmpty. Effectively this determines if 0 is handled by the positive path or negative.
|
||||
if ((!options.hash.includeZero && !conditional) || Utils.isEmpty(conditional)) {
|
||||
return options.inverse(this);
|
||||
} else {
|
||||
return options.fn(this);
|
||||
}
|
||||
});
|
||||
|
||||
instance.registerHelper('unless', function(conditional, options) {
|
||||
return instance.helpers['if'].call(this, conditional, {fn: options.inverse, inverse: options.fn, hash: options.hash});
|
||||
});
|
||||
|
||||
instance.registerHelper('with', function(context, options) {
|
||||
if (isFunction(context)) { context = context.call(this); }
|
||||
|
||||
let fn = options.fn;
|
||||
|
||||
if (!Utils.isEmpty(context)) {
|
||||
if (options.data && options.ids) {
|
||||
let data = createFrame(options.data);
|
||||
data.contextPath = Utils.appendContextPath(options.data.contextPath, options.ids[0]);
|
||||
options = {data: data};
|
||||
}
|
||||
|
||||
return fn(context, options);
|
||||
} else {
|
||||
return options.inverse(this);
|
||||
}
|
||||
});
|
||||
|
||||
instance.registerHelper('log', function(message, options) {
|
||||
let level = options.data && options.data.level != null ? parseInt(options.data.level, 10) : 1;
|
||||
instance.log(level, message);
|
||||
});
|
||||
|
||||
instance.registerHelper('lookup', function(obj, field) {
|
||||
return obj && obj[field];
|
||||
});
|
||||
}
|
||||
|
||||
export let logger = {
|
||||
methodMap: { 0: 'debug', 1: 'info', 2: 'warn', 3: 'error' },
|
||||
|
||||
// State enum
|
||||
DEBUG: 0,
|
||||
INFO: 1,
|
||||
WARN: 2,
|
||||
ERROR: 3,
|
||||
level: 1,
|
||||
|
||||
// Can be overridden in the host environment
|
||||
log: function(level, message) {
|
||||
if (typeof console !== 'undefined' && logger.level <= level) {
|
||||
let method = logger.methodMap[level];
|
||||
(console[method] || console.log).call(console, message); // eslint-disable-line no-console
|
||||
this.decorators[name] = fn;
|
||||
}
|
||||
},
|
||||
unregisterDecorator: function(name) {
|
||||
delete this.decorators[name];
|
||||
}
|
||||
};
|
||||
|
||||
export let log = logger.log;
|
||||
|
||||
export function createFrame(object) {
|
||||
let frame = Utils.extend({}, object);
|
||||
frame._parent = object;
|
||||
return frame;
|
||||
}
|
||||
export {createFrame, logger};
|
||||
|
||||
@@ -1,137 +1,13 @@
|
||||
let AST = {
|
||||
Program: function(statements, blockParams, strip, locInfo) {
|
||||
this.loc = locInfo;
|
||||
this.type = 'Program';
|
||||
this.body = statements;
|
||||
|
||||
this.blockParams = blockParams;
|
||||
this.strip = strip;
|
||||
},
|
||||
|
||||
MustacheStatement: function(path, params, hash, escaped, strip, locInfo) {
|
||||
this.loc = locInfo;
|
||||
this.type = 'MustacheStatement';
|
||||
|
||||
this.path = path;
|
||||
this.params = params || [];
|
||||
this.hash = hash;
|
||||
this.escaped = escaped;
|
||||
|
||||
this.strip = strip;
|
||||
},
|
||||
|
||||
BlockStatement: function(path, params, hash, program, inverse, openStrip, inverseStrip, closeStrip, locInfo) {
|
||||
this.loc = locInfo;
|
||||
this.type = 'BlockStatement';
|
||||
|
||||
this.path = path;
|
||||
this.params = params || [];
|
||||
this.hash = hash;
|
||||
this.program = program;
|
||||
this.inverse = inverse;
|
||||
|
||||
this.openStrip = openStrip;
|
||||
this.inverseStrip = inverseStrip;
|
||||
this.closeStrip = closeStrip;
|
||||
},
|
||||
|
||||
PartialStatement: function(name, params, hash, strip, locInfo) {
|
||||
this.loc = locInfo;
|
||||
this.type = 'PartialStatement';
|
||||
|
||||
this.name = name;
|
||||
this.params = params || [];
|
||||
this.hash = hash;
|
||||
|
||||
this.indent = '';
|
||||
this.strip = strip;
|
||||
},
|
||||
|
||||
ContentStatement: function(string, locInfo) {
|
||||
this.loc = locInfo;
|
||||
this.type = 'ContentStatement';
|
||||
this.original = this.value = string;
|
||||
},
|
||||
|
||||
CommentStatement: function(comment, strip, locInfo) {
|
||||
this.loc = locInfo;
|
||||
this.type = 'CommentStatement';
|
||||
this.value = comment;
|
||||
|
||||
this.strip = strip;
|
||||
},
|
||||
|
||||
SubExpression: function(path, params, hash, locInfo) {
|
||||
this.loc = locInfo;
|
||||
|
||||
this.type = 'SubExpression';
|
||||
this.path = path;
|
||||
this.params = params || [];
|
||||
this.hash = hash;
|
||||
},
|
||||
|
||||
PathExpression: function(data, depth, parts, original, locInfo) {
|
||||
this.loc = locInfo;
|
||||
this.type = 'PathExpression';
|
||||
|
||||
this.data = data;
|
||||
this.original = original;
|
||||
this.parts = parts;
|
||||
this.depth = depth;
|
||||
},
|
||||
|
||||
StringLiteral: function(string, locInfo) {
|
||||
this.loc = locInfo;
|
||||
this.type = 'StringLiteral';
|
||||
this.original =
|
||||
this.value = string;
|
||||
},
|
||||
|
||||
NumberLiteral: function(number, locInfo) {
|
||||
this.loc = locInfo;
|
||||
this.type = 'NumberLiteral';
|
||||
this.original =
|
||||
this.value = Number(number);
|
||||
},
|
||||
|
||||
BooleanLiteral: function(bool, locInfo) {
|
||||
this.loc = locInfo;
|
||||
this.type = 'BooleanLiteral';
|
||||
this.original =
|
||||
this.value = bool === 'true';
|
||||
},
|
||||
|
||||
UndefinedLiteral: function(locInfo) {
|
||||
this.loc = locInfo;
|
||||
this.type = 'UndefinedLiteral';
|
||||
this.original = this.value = undefined;
|
||||
},
|
||||
|
||||
NullLiteral: function(locInfo) {
|
||||
this.loc = locInfo;
|
||||
this.type = 'NullLiteral';
|
||||
this.original = this.value = null;
|
||||
},
|
||||
|
||||
Hash: function(pairs, locInfo) {
|
||||
this.loc = locInfo;
|
||||
this.type = 'Hash';
|
||||
this.pairs = pairs;
|
||||
},
|
||||
HashPair: function(key, value, locInfo) {
|
||||
this.loc = locInfo;
|
||||
this.type = 'HashPair';
|
||||
this.key = key;
|
||||
this.value = value;
|
||||
},
|
||||
|
||||
// Public API used to evaluate derived attributes regarding AST nodes
|
||||
helpers: {
|
||||
// a mustache is definitely a helper if:
|
||||
// * it is an eligible helper, and
|
||||
// * it has at least one parameter or hash segment
|
||||
helperExpression: function(node) {
|
||||
return !!(node.type === 'SubExpression' || node.params.length || node.hash);
|
||||
return (node.type === 'SubExpression')
|
||||
|| ((node.type === 'MustacheStatement' || node.type === 'BlockStatement')
|
||||
&& !!((node.params && node.params.length) || node.hash));
|
||||
},
|
||||
|
||||
scopedId: function(path) {
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import parser from './parser';
|
||||
import AST from './ast';
|
||||
import WhitespaceControl from './whitespace-control';
|
||||
import * as Helpers from './helpers';
|
||||
import { extend } from '../utils';
|
||||
@@ -7,7 +6,7 @@ import { extend } from '../utils';
|
||||
export { parser };
|
||||
|
||||
let yy = {};
|
||||
extend(yy, Helpers, AST);
|
||||
extend(yy, Helpers);
|
||||
|
||||
export function parse(input, options) {
|
||||
// Just return if an already-compiled AST was passed in.
|
||||
@@ -20,6 +19,6 @@ export function parse(input, options) {
|
||||
return new yy.SourceLocation(options && options.srcName, locInfo);
|
||||
};
|
||||
|
||||
let strip = new WhitespaceControl();
|
||||
let strip = new WhitespaceControl(options);
|
||||
return strip.accept(parser.parse(input));
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/*global define */
|
||||
/* global define */
|
||||
import {isArray} from '../utils';
|
||||
|
||||
let SourceNode;
|
||||
@@ -69,6 +69,9 @@ function CodeGen(srcFile) {
|
||||
}
|
||||
|
||||
CodeGen.prototype = {
|
||||
isEmpty() {
|
||||
return !this.source.length;
|
||||
},
|
||||
prepend: function(source, loc) {
|
||||
this.source.unshift(this.wrap(source, loc));
|
||||
},
|
||||
@@ -90,7 +93,8 @@ CodeGen.prototype = {
|
||||
}
|
||||
},
|
||||
|
||||
empty: function(loc = this.currentLocation || {start: {}}) {
|
||||
empty: function() {
|
||||
let loc = this.currentLocation || {start: {}};
|
||||
return new SourceNode(loc.start.line, loc.start.column, this.srcFile);
|
||||
},
|
||||
wrap: function(chunk, loc = this.currentLocation || {start: {}}) {
|
||||
@@ -137,22 +141,22 @@ CodeGen.prototype = {
|
||||
},
|
||||
|
||||
|
||||
generateList: function(entries, loc) {
|
||||
let ret = this.empty(loc);
|
||||
generateList: function(entries) {
|
||||
let ret = this.empty();
|
||||
|
||||
for (let i = 0, len = entries.length; i < len; i++) {
|
||||
if (i) {
|
||||
ret.add(',');
|
||||
}
|
||||
|
||||
ret.add(castChunk(entries[i], this, loc));
|
||||
ret.add(castChunk(entries[i], this));
|
||||
}
|
||||
|
||||
return ret;
|
||||
},
|
||||
|
||||
generateArray: function(entries, loc) {
|
||||
let ret = this.generateList(entries, loc);
|
||||
generateArray: function(entries) {
|
||||
let ret = this.generateList(entries);
|
||||
ret.prepend('[');
|
||||
ret.add(']');
|
||||
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
/* eslint-disable new-cap */
|
||||
|
||||
import Exception from '../exception';
|
||||
import {isArray, indexOf} from '../utils';
|
||||
import AST from './ast';
|
||||
@@ -66,8 +68,9 @@ Compiler.prototype = {
|
||||
};
|
||||
if (knownHelpers) {
|
||||
for (let name in knownHelpers) {
|
||||
/* istanbul ignore else */
|
||||
if (name in knownHelpers) {
|
||||
options.knownHelpers[name] = knownHelpers[name];
|
||||
this.options.knownHelpers[name] = knownHelpers[name];
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -89,6 +92,11 @@ Compiler.prototype = {
|
||||
},
|
||||
|
||||
accept: function(node) {
|
||||
/* istanbul ignore next: Sanity code */
|
||||
if (!this[node.type]) {
|
||||
throw new Exception('Unknown type: ' + node.type, node);
|
||||
}
|
||||
|
||||
this.sourceNode.unshift(node);
|
||||
let ret = this[node.type](node);
|
||||
this.sourceNode.shift();
|
||||
@@ -148,14 +156,32 @@ Compiler.prototype = {
|
||||
this.opcode('append');
|
||||
},
|
||||
|
||||
DecoratorBlock(decorator) {
|
||||
let program = decorator.program && this.compileProgram(decorator.program);
|
||||
let params = this.setupFullMustacheParams(decorator, program, undefined),
|
||||
path = decorator.path;
|
||||
|
||||
this.useDecorators = true;
|
||||
this.opcode('registerDecorator', params.length, path.original);
|
||||
},
|
||||
|
||||
PartialStatement: function(partial) {
|
||||
this.usePartial = true;
|
||||
|
||||
let program = partial.program;
|
||||
if (program) {
|
||||
program = this.compileProgram(partial.program);
|
||||
}
|
||||
|
||||
let params = partial.params;
|
||||
if (params.length > 1) {
|
||||
throw new Exception('Unsupported number of partial arguments: ' + params.length, partial);
|
||||
} else if (!params.length) {
|
||||
params.push({type: 'PathExpression', parts: [], depth: 0});
|
||||
if (this.options.explicitPartialContext) {
|
||||
this.opcode('pushLiteral', 'undefined');
|
||||
} else {
|
||||
params.push({type: 'PathExpression', parts: [], depth: 0});
|
||||
}
|
||||
}
|
||||
|
||||
let partialName = partial.name.original,
|
||||
@@ -164,7 +190,7 @@ Compiler.prototype = {
|
||||
this.accept(partial.name);
|
||||
}
|
||||
|
||||
this.setupFullMustacheParams(partial, undefined, undefined, true);
|
||||
this.setupFullMustacheParams(partial, program, undefined, true);
|
||||
|
||||
let indent = partial.indent || '';
|
||||
if (this.options.preventIndent && indent) {
|
||||
@@ -175,9 +201,12 @@ Compiler.prototype = {
|
||||
this.opcode('invokePartial', isDynamic, partialName, indent);
|
||||
this.opcode('append');
|
||||
},
|
||||
PartialBlockStatement: function(partialBlock) {
|
||||
this.PartialStatement(partialBlock);
|
||||
},
|
||||
|
||||
MustacheStatement: function(mustache) {
|
||||
this.SubExpression(mustache); // eslint-disable-line new-cap
|
||||
this.SubExpression(mustache);
|
||||
|
||||
if (mustache.escaped && !this.options.noEscape) {
|
||||
this.opcode('appendEscaped');
|
||||
@@ -185,6 +214,10 @@ Compiler.prototype = {
|
||||
this.opcode('append');
|
||||
}
|
||||
},
|
||||
Decorator(decorator) {
|
||||
this.DecoratorBlock(decorator);
|
||||
},
|
||||
|
||||
|
||||
ContentStatement: function(content) {
|
||||
if (content.value) {
|
||||
@@ -216,13 +249,16 @@ Compiler.prototype = {
|
||||
this.opcode('pushProgram', program);
|
||||
this.opcode('pushProgram', inverse);
|
||||
|
||||
path.strict = true;
|
||||
this.accept(path);
|
||||
|
||||
this.opcode('invokeAmbiguous', name, isBlock);
|
||||
},
|
||||
|
||||
simpleSexpr: function(sexpr) {
|
||||
this.accept(sexpr.path);
|
||||
let path = sexpr.path;
|
||||
path.strict = true;
|
||||
this.accept(path);
|
||||
this.opcode('resolvePossibleLambda');
|
||||
},
|
||||
|
||||
@@ -236,6 +272,7 @@ Compiler.prototype = {
|
||||
} else if (this.options.knownHelpersOnly) {
|
||||
throw new Exception('You specified knownHelpersOnly, but used the unknown helper ' + name, sexpr);
|
||||
} else {
|
||||
path.strict = true;
|
||||
path.falsy = true;
|
||||
|
||||
this.accept(path);
|
||||
@@ -258,9 +295,9 @@ Compiler.prototype = {
|
||||
this.opcode('pushContext');
|
||||
} else if (path.data) {
|
||||
this.options.data = true;
|
||||
this.opcode('lookupData', path.depth, path.parts);
|
||||
this.opcode('lookupData', path.depth, path.parts, path.strict);
|
||||
} else {
|
||||
this.opcode('lookupOnContext', path.parts, path.falsy, scoped);
|
||||
this.opcode('lookupOnContext', path.parts, path.falsy, path.strict, scoped);
|
||||
}
|
||||
},
|
||||
|
||||
@@ -389,8 +426,9 @@ Compiler.prototype = {
|
||||
value = val.original || value;
|
||||
if (value.replace) {
|
||||
value = value
|
||||
.replace(/^\.\//g, '')
|
||||
.replace(/^\.$/g, '');
|
||||
.replace(/^this(?:\.|$)/, '')
|
||||
.replace(/^\.\//, '')
|
||||
.replace(/^\.$/, '');
|
||||
}
|
||||
|
||||
this.opcode('pushId', val.type, value);
|
||||
@@ -450,6 +488,7 @@ export function compile(input, options = {}, env) {
|
||||
throw new Exception('You must pass a string or Handlebars AST to Handlebars.compile. You passed ' + input);
|
||||
}
|
||||
|
||||
options = Object.assign({}, options);
|
||||
if (!('data' in options)) {
|
||||
options.data = true;
|
||||
}
|
||||
@@ -508,6 +547,13 @@ function transformLiteralToPath(sexpr) {
|
||||
let literal = sexpr.path;
|
||||
// Casting to string here to make false and 0 literal values play nicely with the rest
|
||||
// of the system.
|
||||
sexpr.path = new AST.PathExpression(false, 0, [literal.original + ''], literal.original + '', literal.loc);
|
||||
sexpr.path = {
|
||||
type: 'PathExpression',
|
||||
data: false,
|
||||
depth: 0,
|
||||
parts: [literal.original + ''],
|
||||
original: literal.original + '',
|
||||
loc: literal.loc
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,15 @@
|
||||
import Exception from '../exception';
|
||||
|
||||
function validateClose(open, close) {
|
||||
close = close.path ? close.path.original : close;
|
||||
|
||||
if (open.path.original !== close) {
|
||||
let errorNode = {loc: open.path.loc};
|
||||
|
||||
throw new Exception(open.path.original + " doesn't match " + close, errorNode);
|
||||
}
|
||||
}
|
||||
|
||||
export function SourceLocation(source, locInfo) {
|
||||
this.source = source;
|
||||
this.start = {
|
||||
@@ -32,8 +42,8 @@ export function stripComment(comment) {
|
||||
.replace(/-?-?~?\}\}$/, '');
|
||||
}
|
||||
|
||||
export function preparePath(data, parts, locInfo) {
|
||||
locInfo = this.locInfo(locInfo);
|
||||
export function preparePath(data, parts, loc) {
|
||||
loc = this.locInfo(loc);
|
||||
|
||||
let original = data ? '@' : '',
|
||||
dig = [],
|
||||
@@ -49,7 +59,7 @@ export function preparePath(data, parts, locInfo) {
|
||||
|
||||
if (!isLiteral && (part === '..' || part === '.' || part === 'this')) {
|
||||
if (dig.length > 0) {
|
||||
throw new Exception('Invalid path: ' + original, {loc: locInfo});
|
||||
throw new Exception('Invalid path: ' + original, {loc});
|
||||
} else if (part === '..') {
|
||||
depth++;
|
||||
depthString += '../';
|
||||
@@ -59,7 +69,14 @@ export function preparePath(data, parts, locInfo) {
|
||||
}
|
||||
}
|
||||
|
||||
return new this.PathExpression(data, depth, dig, original, locInfo);
|
||||
return {
|
||||
type: 'PathExpression',
|
||||
data,
|
||||
depth,
|
||||
parts: dig,
|
||||
original,
|
||||
loc
|
||||
};
|
||||
}
|
||||
|
||||
export function prepareMustache(path, params, hash, open, strip, locInfo) {
|
||||
@@ -67,40 +84,59 @@ export function prepareMustache(path, params, hash, open, strip, locInfo) {
|
||||
let escapeFlag = open.charAt(3) || open.charAt(2),
|
||||
escaped = escapeFlag !== '{' && escapeFlag !== '&';
|
||||
|
||||
return new this.MustacheStatement(path, params, hash, escaped, strip, this.locInfo(locInfo));
|
||||
let decorator = (/\*/.test(open));
|
||||
return {
|
||||
type: decorator ? 'Decorator' : 'MustacheStatement',
|
||||
path,
|
||||
params,
|
||||
hash,
|
||||
escaped,
|
||||
strip,
|
||||
loc: this.locInfo(locInfo)
|
||||
};
|
||||
}
|
||||
|
||||
export function prepareRawBlock(openRawBlock, content, close, locInfo) {
|
||||
if (openRawBlock.path.original !== close) {
|
||||
let errorNode = {loc: openRawBlock.path.loc};
|
||||
|
||||
throw new Exception(openRawBlock.path.original + " doesn't match " + close, errorNode);
|
||||
}
|
||||
export function prepareRawBlock(openRawBlock, contents, close, locInfo) {
|
||||
validateClose(openRawBlock, close);
|
||||
|
||||
locInfo = this.locInfo(locInfo);
|
||||
let program = new this.Program([content], null, {}, locInfo);
|
||||
let program = {
|
||||
type: 'Program',
|
||||
body: contents,
|
||||
strip: {},
|
||||
loc: locInfo
|
||||
};
|
||||
|
||||
return new this.BlockStatement(
|
||||
openRawBlock.path, openRawBlock.params, openRawBlock.hash,
|
||||
program, undefined,
|
||||
{}, {}, {},
|
||||
locInfo);
|
||||
return {
|
||||
type: 'BlockStatement',
|
||||
path: openRawBlock.path,
|
||||
params: openRawBlock.params,
|
||||
hash: openRawBlock.hash,
|
||||
program,
|
||||
openStrip: {},
|
||||
inverseStrip: {},
|
||||
closeStrip: {},
|
||||
loc: locInfo
|
||||
};
|
||||
}
|
||||
|
||||
export function prepareBlock(openBlock, program, inverseAndProgram, close, inverted, locInfo) {
|
||||
// When we are chaining inverse calls, we will not have a close path
|
||||
if (close && close.path && openBlock.path.original !== close.path.original) {
|
||||
let errorNode = {loc: openBlock.path.loc};
|
||||
|
||||
throw new Exception(openBlock.path.original + ' doesn\'t match ' + close.path.original, errorNode);
|
||||
if (close && close.path) {
|
||||
validateClose(openBlock, close);
|
||||
}
|
||||
|
||||
let decorator = (/\*/.test(openBlock.open));
|
||||
|
||||
program.blockParams = openBlock.blockParams;
|
||||
|
||||
let inverse,
|
||||
inverseStrip;
|
||||
|
||||
if (inverseAndProgram) {
|
||||
if (decorator) {
|
||||
throw new Exception('Unexpected inverse block on decorator', inverseAndProgram);
|
||||
}
|
||||
|
||||
if (inverseAndProgram.chain) {
|
||||
inverseAndProgram.program.body[0].closeStrip = close.strip;
|
||||
}
|
||||
@@ -115,9 +151,62 @@ export function prepareBlock(openBlock, program, inverseAndProgram, close, inver
|
||||
program = inverted;
|
||||
}
|
||||
|
||||
return new this.BlockStatement(
|
||||
openBlock.path, openBlock.params, openBlock.hash,
|
||||
program, inverse,
|
||||
openBlock.strip, inverseStrip, close && close.strip,
|
||||
this.locInfo(locInfo));
|
||||
return {
|
||||
type: decorator ? 'DecoratorBlock' : 'BlockStatement',
|
||||
path: openBlock.path,
|
||||
params: openBlock.params,
|
||||
hash: openBlock.hash,
|
||||
program,
|
||||
inverse,
|
||||
openStrip: openBlock.strip,
|
||||
inverseStrip,
|
||||
closeStrip: close && close.strip,
|
||||
loc: this.locInfo(locInfo)
|
||||
};
|
||||
}
|
||||
|
||||
export function prepareProgram(statements, loc) {
|
||||
if (!loc && statements.length) {
|
||||
const firstLoc = statements[0].loc,
|
||||
lastLoc = statements[statements.length - 1].loc;
|
||||
|
||||
/* istanbul ignore else */
|
||||
if (firstLoc && lastLoc) {
|
||||
loc = {
|
||||
source: firstLoc.source,
|
||||
start: {
|
||||
line: firstLoc.start.line,
|
||||
column: firstLoc.start.column
|
||||
},
|
||||
end: {
|
||||
line: lastLoc.end.line,
|
||||
column: lastLoc.end.column
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
type: 'Program',
|
||||
body: statements,
|
||||
strip: {},
|
||||
loc: loc
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
export function preparePartialBlock(open, program, close, locInfo) {
|
||||
validateClose(open, close);
|
||||
|
||||
return {
|
||||
type: 'PartialBlockStatement',
|
||||
name: open.path,
|
||||
params: open.params,
|
||||
hash: open.hash,
|
||||
program,
|
||||
openStrip: open.strip,
|
||||
closeStrip: close && close.strip,
|
||||
loc: this.locInfo(locInfo)
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -12,15 +12,15 @@ function JavaScriptCompiler() {}
|
||||
JavaScriptCompiler.prototype = {
|
||||
// PUBLIC API: You can override these methods in a subclass to provide
|
||||
// alternative compiled forms for name lookup and buffering semantics
|
||||
nameLookup: function(parent, name /* , type*/) {
|
||||
nameLookup: function(parent, name/* , type*/) {
|
||||
if (JavaScriptCompiler.isValidJavaScriptVariableName(name)) {
|
||||
return [parent, '.', name];
|
||||
} else {
|
||||
return [parent, "['", name, "']"];
|
||||
return [parent, '[', JSON.stringify(name), ']'];
|
||||
}
|
||||
},
|
||||
depthedLookup: function(name) {
|
||||
return [this.aliasable('this.lookup'), '(depths, "', name, '")'];
|
||||
return [this.aliasable('container.lookup'), '(depths, "', name, '")'];
|
||||
},
|
||||
|
||||
compilerInfo: function() {
|
||||
@@ -64,6 +64,7 @@ JavaScriptCompiler.prototype = {
|
||||
this.name = this.environment.name;
|
||||
this.isChild = !!context;
|
||||
this.context = context || {
|
||||
decorators: [],
|
||||
programs: [],
|
||||
environments: []
|
||||
};
|
||||
@@ -81,7 +82,7 @@ JavaScriptCompiler.prototype = {
|
||||
|
||||
this.compileChildren(environment, options);
|
||||
|
||||
this.useDepths = this.useDepths || environment.useDepths || this.options.compat;
|
||||
this.useDepths = this.useDepths || environment.useDepths || environment.useDecorators || this.options.compat;
|
||||
this.useBlockParams = this.useBlockParams || environment.useBlockParams;
|
||||
|
||||
let opcodes = environment.opcodes,
|
||||
@@ -107,16 +108,43 @@ JavaScriptCompiler.prototype = {
|
||||
throw new Exception('Compile completed with content left on stack');
|
||||
}
|
||||
|
||||
if (!this.decorators.isEmpty()) {
|
||||
this.useDecorators = true;
|
||||
|
||||
this.decorators.prepend('var decorators = container.decorators;\n');
|
||||
this.decorators.push('return fn;');
|
||||
|
||||
if (asObject) {
|
||||
this.decorators = Function.apply(this, ['fn', 'props', 'container', 'depth0', 'data', 'blockParams', 'depths', this.decorators.merge()]);
|
||||
} else {
|
||||
this.decorators.prepend('function(fn, props, container, depth0, data, blockParams, depths) {\n');
|
||||
this.decorators.push('}\n');
|
||||
this.decorators = this.decorators.merge();
|
||||
}
|
||||
} else {
|
||||
this.decorators = undefined;
|
||||
}
|
||||
|
||||
let fn = this.createFunctionContext(asObject);
|
||||
if (!this.isChild) {
|
||||
let ret = {
|
||||
compiler: this.compilerInfo(),
|
||||
main: fn
|
||||
};
|
||||
let programs = this.context.programs;
|
||||
|
||||
if (this.decorators) {
|
||||
ret.main_d = this.decorators; // eslint-disable-line camelcase
|
||||
ret.useDecorators = true;
|
||||
}
|
||||
|
||||
let {programs, decorators} = this.context;
|
||||
for (i = 0, l = programs.length; i < l; i++) {
|
||||
if (programs[i]) {
|
||||
ret[i] = programs[i];
|
||||
if (decorators[i]) {
|
||||
ret[i + '_d'] = decorators[i];
|
||||
ret.useDecorators = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -163,6 +191,7 @@ JavaScriptCompiler.prototype = {
|
||||
// getContext opcode when it would be a noop
|
||||
this.lastContext = 0;
|
||||
this.source = new CodeGen(this.options.srcName);
|
||||
this.decorators = new CodeGen(this.options.srcName);
|
||||
},
|
||||
|
||||
createFunctionContext: function(asObject) {
|
||||
@@ -189,7 +218,7 @@ JavaScriptCompiler.prototype = {
|
||||
}
|
||||
}
|
||||
|
||||
let params = ['depth0', 'helpers', 'partials', 'data'];
|
||||
let params = ['container', 'depth0', 'helpers', 'partials', 'data'];
|
||||
|
||||
if (this.useBlockParams || this.useDepths) {
|
||||
params.push('blockParams');
|
||||
@@ -359,7 +388,7 @@ JavaScriptCompiler.prototype = {
|
||||
// Escape `value` and append it to the buffer
|
||||
appendEscaped: function() {
|
||||
this.pushSource(this.appendToBuffer(
|
||||
[this.aliasable('this.escapeExpression'), '(', this.popStack(), ')']));
|
||||
[this.aliasable('container.escapeExpression'), '(', this.popStack(), ')']));
|
||||
},
|
||||
|
||||
// [getContext]
|
||||
@@ -390,7 +419,7 @@ JavaScriptCompiler.prototype = {
|
||||
//
|
||||
// Looks up the value of `name` on the current context and pushes
|
||||
// it onto the stack.
|
||||
lookupOnContext: function(parts, falsy, scoped) {
|
||||
lookupOnContext: function(parts, falsy, strict, scoped) {
|
||||
let i = 0;
|
||||
|
||||
if (!scoped && this.options.compat && !this.lastContext) {
|
||||
@@ -401,7 +430,7 @@ JavaScriptCompiler.prototype = {
|
||||
this.pushContext();
|
||||
}
|
||||
|
||||
this.resolvePath('context', parts, i, falsy);
|
||||
this.resolvePath('context', parts, i, falsy, strict);
|
||||
},
|
||||
|
||||
// [lookupBlockParam]
|
||||
@@ -424,25 +453,25 @@ JavaScriptCompiler.prototype = {
|
||||
// On stack, after: data, ...
|
||||
//
|
||||
// Push the data lookup operator
|
||||
lookupData: function(depth, parts) {
|
||||
lookupData: function(depth, parts, strict) {
|
||||
if (!depth) {
|
||||
this.pushStackLiteral('data');
|
||||
} else {
|
||||
this.pushStackLiteral('this.data(data, ' + depth + ')');
|
||||
this.pushStackLiteral('container.data(data, ' + depth + ')');
|
||||
}
|
||||
|
||||
this.resolvePath('data', parts, 0, true);
|
||||
this.resolvePath('data', parts, 0, true, strict);
|
||||
},
|
||||
|
||||
resolvePath: function(type, parts, i, falsy) {
|
||||
resolvePath: function(type, parts, i, falsy, strict) {
|
||||
if (this.options.strict || this.options.assumeObjects) {
|
||||
this.push(strictLookup(this.options.strict, this, parts, type));
|
||||
this.push(strictLookup(this.options.strict && strict, this, parts, type));
|
||||
return;
|
||||
}
|
||||
|
||||
let len = parts.length;
|
||||
for (; i < len; i++) {
|
||||
/*eslint-disable no-loop-func */
|
||||
/* eslint-disable no-loop-func */
|
||||
this.replaceStack((current) => {
|
||||
let lookup = this.nameLookup(current, parts[i], type);
|
||||
// We want to ensure that zero and false are handled properly if the context (falsy flag)
|
||||
@@ -454,7 +483,7 @@ JavaScriptCompiler.prototype = {
|
||||
return [' && ', lookup];
|
||||
}
|
||||
});
|
||||
/*eslint-enable no-loop-func */
|
||||
/* eslint-enable no-loop-func */
|
||||
}
|
||||
},
|
||||
|
||||
@@ -466,7 +495,7 @@ JavaScriptCompiler.prototype = {
|
||||
// If the `value` is a lambda, replace it on the stack by
|
||||
// the return value of the lambda
|
||||
resolvePossibleLambda: function() {
|
||||
this.push([this.aliasable('this.lambda'), '(', this.popStack(), ', ', this.contextName(0), ')']);
|
||||
this.push([this.aliasable('container.lambda'), '(', this.popStack(), ', ', this.contextName(0), ')']);
|
||||
},
|
||||
|
||||
// [pushStringParam]
|
||||
@@ -561,6 +590,24 @@ JavaScriptCompiler.prototype = {
|
||||
}
|
||||
},
|
||||
|
||||
// [registerDecorator]
|
||||
//
|
||||
// On stack, before: hash, program, params..., ...
|
||||
// On stack, after: ...
|
||||
//
|
||||
// Pops off the decorator's parameters, invokes the decorator,
|
||||
// and inserts the decorator into the decorators list.
|
||||
registerDecorator(paramSize, name) {
|
||||
let foundDecorator = this.nameLookup('decorators', name, 'decorator'),
|
||||
options = this.setupHelperArgs(name, paramSize);
|
||||
|
||||
this.decorators.push([
|
||||
'fn = ',
|
||||
this.decorators.functionCall(foundDecorator, '', ['fn', 'props', 'container', options]),
|
||||
' || fn;'
|
||||
]);
|
||||
},
|
||||
|
||||
// [invokeHelper]
|
||||
//
|
||||
// On stack, before: hash, inverse, program, params..., ...
|
||||
@@ -644,7 +691,7 @@ JavaScriptCompiler.prototype = {
|
||||
// and pushes the result of the invocation back.
|
||||
invokePartial: function(isDynamic, name, indent) {
|
||||
let params = [],
|
||||
options = this.setupParams(name, 1, params, false);
|
||||
options = this.setupParams(name, 1, params);
|
||||
|
||||
if (isDynamic) {
|
||||
name = this.popStack();
|
||||
@@ -656,6 +703,7 @@ JavaScriptCompiler.prototype = {
|
||||
}
|
||||
options.helpers = 'helpers';
|
||||
options.partials = 'partials';
|
||||
options.decorators = 'container.decorators';
|
||||
|
||||
if (!isDynamic) {
|
||||
params.unshift(this.nameLookup('partials', name, 'partial'));
|
||||
@@ -669,7 +717,7 @@ JavaScriptCompiler.prototype = {
|
||||
options = this.objectLiteral(options);
|
||||
params.push(options);
|
||||
|
||||
this.push(this.source.functionCall('this.invokePartial', '', params));
|
||||
this.push(this.source.functionCall('container.invokePartial', '', params));
|
||||
},
|
||||
|
||||
// [assignToHash]
|
||||
@@ -730,24 +778,27 @@ JavaScriptCompiler.prototype = {
|
||||
child = children[i];
|
||||
compiler = new this.compiler(); // eslint-disable-line new-cap
|
||||
|
||||
let index = this.matchExistingProgram(child);
|
||||
let existing = this.matchExistingProgram(child);
|
||||
|
||||
if (index == null) {
|
||||
if (existing == null) {
|
||||
this.context.programs.push(''); // Placeholder to prevent name conflicts for nested children
|
||||
index = this.context.programs.length;
|
||||
let index = this.context.programs.length;
|
||||
child.index = index;
|
||||
child.name = 'program' + index;
|
||||
this.context.programs[index] = compiler.compile(child, options, this.context, !this.precompile);
|
||||
this.context.decorators[index] = compiler.decorators;
|
||||
this.context.environments[index] = child;
|
||||
|
||||
this.useDepths = this.useDepths || compiler.useDepths;
|
||||
this.useBlockParams = this.useBlockParams || compiler.useBlockParams;
|
||||
child.useDepths = this.useDepths;
|
||||
child.useBlockParams = this.useBlockParams;
|
||||
} else {
|
||||
child.index = index;
|
||||
child.name = 'program' + index;
|
||||
child.index = existing.index;
|
||||
child.name = 'program' + existing.index;
|
||||
|
||||
this.useDepths = this.useDepths || child.useDepths;
|
||||
this.useBlockParams = this.useBlockParams || child.useBlockParams;
|
||||
this.useDepths = this.useDepths || existing.useDepths;
|
||||
this.useBlockParams = this.useBlockParams || existing.useBlockParams;
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -755,7 +806,7 @@ JavaScriptCompiler.prototype = {
|
||||
for (let i = 0, len = this.context.environments.length; i < len; i++) {
|
||||
let environment = this.context.environments[i];
|
||||
if (environment && environment.equals(child)) {
|
||||
return i;
|
||||
return environment;
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -771,7 +822,7 @@ JavaScriptCompiler.prototype = {
|
||||
programParams.push('depths');
|
||||
}
|
||||
|
||||
return 'this.program(' + programParams.join(', ') + ')';
|
||||
return 'container.program(' + programParams.join(', ') + ')';
|
||||
},
|
||||
|
||||
useRegister: function(name) {
|
||||
@@ -935,18 +986,28 @@ JavaScriptCompiler.prototype = {
|
||||
setupHelper: function(paramSize, name, blockHelper) {
|
||||
let params = [],
|
||||
paramsInit = this.setupHelperArgs(name, paramSize, params, blockHelper);
|
||||
let foundHelper = this.nameLookup('helpers', name, 'helper');
|
||||
let foundHelper = this.nameLookup('helpers', name, 'helper'),
|
||||
callContext = this.aliasable(`${this.contextName(0)} != null ? ${this.contextName(0)} : (container.nullContext || {})`);
|
||||
|
||||
return {
|
||||
params: params,
|
||||
paramsInit: paramsInit,
|
||||
name: foundHelper,
|
||||
callParams: [this.contextName(0)].concat(params)
|
||||
callParams: [callContext].concat(params)
|
||||
};
|
||||
},
|
||||
|
||||
setupParams: function(helper, paramSize, params) {
|
||||
let options = {}, contexts = [], types = [], ids = [], param;
|
||||
let options = {},
|
||||
contexts = [],
|
||||
types = [],
|
||||
ids = [],
|
||||
objectArgs = !params,
|
||||
param;
|
||||
|
||||
if (objectArgs) {
|
||||
params = [];
|
||||
}
|
||||
|
||||
options.name = this.quotedString(helper);
|
||||
options.hash = this.popStack();
|
||||
@@ -965,8 +1026,8 @@ JavaScriptCompiler.prototype = {
|
||||
// Avoid setting fn and inverse if neither are set. This allows
|
||||
// helpers to do a check for `if (options.fn)`
|
||||
if (program || inverse) {
|
||||
options.fn = program || 'this.noop';
|
||||
options.inverse = inverse || 'this.noop';
|
||||
options.fn = program || 'container.noop';
|
||||
options.inverse = inverse || 'container.noop';
|
||||
}
|
||||
|
||||
// The parameters go on to the stack in order (making sure that they are evaluated in order)
|
||||
@@ -985,6 +1046,10 @@ JavaScriptCompiler.prototype = {
|
||||
}
|
||||
}
|
||||
|
||||
if (objectArgs) {
|
||||
options.args = this.source.generateArray(params);
|
||||
}
|
||||
|
||||
if (this.trackIds) {
|
||||
options.ids = this.source.generateArray(ids);
|
||||
}
|
||||
@@ -1003,15 +1068,17 @@ JavaScriptCompiler.prototype = {
|
||||
},
|
||||
|
||||
setupHelperArgs: function(helper, paramSize, params, useRegister) {
|
||||
let options = this.setupParams(helper, paramSize, params, true);
|
||||
let options = this.setupParams(helper, paramSize, params);
|
||||
options = this.objectLiteral(options);
|
||||
if (useRegister) {
|
||||
this.useRegister('options');
|
||||
params.push('options');
|
||||
return ['options=', options];
|
||||
} else {
|
||||
} else if (params) {
|
||||
params.push(options);
|
||||
return '';
|
||||
} else {
|
||||
return options;
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -1061,7 +1128,7 @@ function strictLookup(requireTerminal, compiler, parts, type) {
|
||||
}
|
||||
|
||||
if (requireTerminal) {
|
||||
return [compiler.aliasable('this.strict'), '(', stack, ', ', compiler.quotedString(parts[i]), ')'];
|
||||
return [compiler.aliasable('container.strict'), '(', stack, ', ', compiler.quotedString(parts[i]), ')'];
|
||||
} else {
|
||||
return stack;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/*eslint-disable new-cap */
|
||||
/* eslint-disable new-cap */
|
||||
import Visitor from './visitor';
|
||||
|
||||
export function print(ast) {
|
||||
@@ -15,10 +15,10 @@ PrintVisitor.prototype.pad = function(string) {
|
||||
let out = '';
|
||||
|
||||
for (let i = 0, l = this.padding; i < l; i++) {
|
||||
out = out + ' ';
|
||||
out += ' ';
|
||||
}
|
||||
|
||||
out = out + string + '\n';
|
||||
out += string + '\n';
|
||||
return out;
|
||||
};
|
||||
|
||||
@@ -37,7 +37,7 @@ PrintVisitor.prototype.Program = function(program) {
|
||||
}
|
||||
|
||||
for (i = 0, l = body.length; i < l; i++) {
|
||||
out = out + this.accept(body[i]);
|
||||
out += this.accept(body[i]);
|
||||
}
|
||||
|
||||
this.padding--;
|
||||
@@ -48,24 +48,28 @@ PrintVisitor.prototype.Program = function(program) {
|
||||
PrintVisitor.prototype.MustacheStatement = function(mustache) {
|
||||
return this.pad('{{ ' + this.SubExpression(mustache) + ' }}');
|
||||
};
|
||||
PrintVisitor.prototype.Decorator = function(mustache) {
|
||||
return this.pad('{{ DIRECTIVE ' + this.SubExpression(mustache) + ' }}');
|
||||
};
|
||||
|
||||
PrintVisitor.prototype.BlockStatement = function(block) {
|
||||
PrintVisitor.prototype.BlockStatement =
|
||||
PrintVisitor.prototype.DecoratorBlock = function(block) {
|
||||
let out = '';
|
||||
|
||||
out = out + this.pad('BLOCK:');
|
||||
out += this.pad((block.type === 'DecoratorBlock' ? 'DIRECTIVE ' : '') + 'BLOCK:');
|
||||
this.padding++;
|
||||
out = out + this.pad(this.SubExpression(block));
|
||||
out += this.pad(this.SubExpression(block));
|
||||
if (block.program) {
|
||||
out = out + this.pad('PROGRAM:');
|
||||
out += this.pad('PROGRAM:');
|
||||
this.padding++;
|
||||
out = out + this.accept(block.program);
|
||||
out += this.accept(block.program);
|
||||
this.padding--;
|
||||
}
|
||||
if (block.inverse) {
|
||||
if (block.program) { this.padding++; }
|
||||
out = out + this.pad('{{^}}');
|
||||
out += this.pad('{{^}}');
|
||||
this.padding++;
|
||||
out = out + this.accept(block.inverse);
|
||||
out += this.accept(block.inverse);
|
||||
this.padding--;
|
||||
if (block.program) { this.padding--; }
|
||||
}
|
||||
@@ -84,6 +88,22 @@ PrintVisitor.prototype.PartialStatement = function(partial) {
|
||||
}
|
||||
return this.pad('{{> ' + content + ' }}');
|
||||
};
|
||||
PrintVisitor.prototype.PartialBlockStatement = function(partial) {
|
||||
let content = 'PARTIAL BLOCK:' + partial.name.original;
|
||||
if (partial.params[0]) {
|
||||
content += ' ' + this.accept(partial.params[0]);
|
||||
}
|
||||
if (partial.hash) {
|
||||
content += ' ' + this.accept(partial.hash);
|
||||
}
|
||||
|
||||
content += ' ' + this.pad('PROGRAM:');
|
||||
this.padding++;
|
||||
content += this.accept(partial.program);
|
||||
this.padding--;
|
||||
|
||||
return this.pad('{{> ' + content + ' }}');
|
||||
};
|
||||
|
||||
PrintVisitor.prototype.ContentStatement = function(content) {
|
||||
return this.pad("CONTENT[ '" + content.value + "' ]");
|
||||
@@ -148,4 +168,4 @@ PrintVisitor.prototype.Hash = function(hash) {
|
||||
PrintVisitor.prototype.HashPair = function(pair) {
|
||||
return pair.key + '=' + this.accept(pair.value);
|
||||
};
|
||||
/*eslint-enable new-cap */
|
||||
/* eslint-enable new-cap */
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import Exception from '../exception';
|
||||
import AST from './ast';
|
||||
|
||||
function Visitor() {
|
||||
this.parents = [];
|
||||
@@ -13,8 +12,9 @@ Visitor.prototype = {
|
||||
acceptKey: function(node, name) {
|
||||
let value = this.accept(node[name]);
|
||||
if (this.mutating) {
|
||||
// Hacky sanity check:
|
||||
if (value && (!value.type || !AST[value.type])) {
|
||||
// Hacky sanity check: This may have a few false positives for type for the helper
|
||||
// methods but will generally do the right thing without a lot of overhead.
|
||||
if (value && !Visitor.prototype[value.type]) {
|
||||
throw new Exception('Unexpected node type "' + value.type + '" found when accepting ' + name + ' on ' + node.type);
|
||||
}
|
||||
node[name] = value;
|
||||
@@ -50,6 +50,11 @@ Visitor.prototype = {
|
||||
return;
|
||||
}
|
||||
|
||||
/* istanbul ignore next: Sanity code */
|
||||
if (!this[object.type]) {
|
||||
throw new Exception('Unknown type: ' + object.type, object);
|
||||
}
|
||||
|
||||
if (this.current) {
|
||||
this.parents.unshift(this.current);
|
||||
}
|
||||
@@ -70,35 +75,23 @@ Visitor.prototype = {
|
||||
this.acceptArray(program.body);
|
||||
},
|
||||
|
||||
MustacheStatement: function(mustache) {
|
||||
this.acceptRequired(mustache, 'path');
|
||||
this.acceptArray(mustache.params);
|
||||
this.acceptKey(mustache, 'hash');
|
||||
},
|
||||
MustacheStatement: visitSubExpression,
|
||||
Decorator: visitSubExpression,
|
||||
|
||||
BlockStatement: function(block) {
|
||||
this.acceptRequired(block, 'path');
|
||||
this.acceptArray(block.params);
|
||||
this.acceptKey(block, 'hash');
|
||||
BlockStatement: visitBlock,
|
||||
DecoratorBlock: visitBlock,
|
||||
|
||||
this.acceptKey(block, 'program');
|
||||
this.acceptKey(block, 'inverse');
|
||||
},
|
||||
PartialStatement: visitPartial,
|
||||
PartialBlockStatement: function(partial) {
|
||||
visitPartial.call(this, partial);
|
||||
|
||||
PartialStatement: function(partial) {
|
||||
this.acceptRequired(partial, 'name');
|
||||
this.acceptArray(partial.params);
|
||||
this.acceptKey(partial, 'hash');
|
||||
this.acceptKey(partial, 'program');
|
||||
},
|
||||
|
||||
ContentStatement: function(/* content */) {},
|
||||
CommentStatement: function(/* comment */) {},
|
||||
|
||||
SubExpression: function(sexpr) {
|
||||
this.acceptRequired(sexpr, 'path');
|
||||
this.acceptArray(sexpr.params);
|
||||
this.acceptKey(sexpr, 'hash');
|
||||
},
|
||||
SubExpression: visitSubExpression,
|
||||
|
||||
PathExpression: function(/* path */) {},
|
||||
|
||||
@@ -116,4 +109,21 @@ Visitor.prototype = {
|
||||
}
|
||||
};
|
||||
|
||||
function visitSubExpression(mustache) {
|
||||
this.acceptRequired(mustache, 'path');
|
||||
this.acceptArray(mustache.params);
|
||||
this.acceptKey(mustache, 'hash');
|
||||
}
|
||||
function visitBlock(block) {
|
||||
visitSubExpression.call(this, block);
|
||||
|
||||
this.acceptKey(block, 'program');
|
||||
this.acceptKey(block, 'inverse');
|
||||
}
|
||||
function visitPartial(partial) {
|
||||
this.acceptRequired(partial, 'name');
|
||||
this.acceptArray(partial.params);
|
||||
this.acceptKey(partial, 'hash');
|
||||
}
|
||||
|
||||
export default Visitor;
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
import Visitor from './visitor';
|
||||
|
||||
function WhitespaceControl() {
|
||||
function WhitespaceControl(options = {}) {
|
||||
this.options = options;
|
||||
}
|
||||
WhitespaceControl.prototype = new Visitor();
|
||||
|
||||
WhitespaceControl.prototype.Program = function(program) {
|
||||
const doStandalone = !this.options.ignoreStandalone;
|
||||
|
||||
let isRoot = !this.isRootSeen;
|
||||
this.isRootSeen = true;
|
||||
|
||||
@@ -31,7 +34,7 @@ WhitespaceControl.prototype.Program = function(program) {
|
||||
omitLeft(body, i, true);
|
||||
}
|
||||
|
||||
if (inlineStandalone) {
|
||||
if (doStandalone && inlineStandalone) {
|
||||
omitRight(body, i);
|
||||
|
||||
if (omitLeft(body, i)) {
|
||||
@@ -42,13 +45,13 @@ WhitespaceControl.prototype.Program = function(program) {
|
||||
}
|
||||
}
|
||||
}
|
||||
if (openStandalone) {
|
||||
if (doStandalone && openStandalone) {
|
||||
omitRight((current.program || current.inverse).body);
|
||||
|
||||
// Strip out the previous content node if it's whitespace only
|
||||
omitLeft(body, i);
|
||||
}
|
||||
if (closeStandalone) {
|
||||
if (doStandalone && closeStandalone) {
|
||||
// Always strip the next node
|
||||
omitRight(body, i);
|
||||
|
||||
@@ -58,7 +61,10 @@ WhitespaceControl.prototype.Program = function(program) {
|
||||
|
||||
return program;
|
||||
};
|
||||
WhitespaceControl.prototype.BlockStatement = function(block) {
|
||||
|
||||
WhitespaceControl.prototype.BlockStatement =
|
||||
WhitespaceControl.prototype.DecoratorBlock =
|
||||
WhitespaceControl.prototype.PartialBlockStatement = function(block) {
|
||||
this.accept(block.program);
|
||||
this.accept(block.inverse);
|
||||
|
||||
@@ -106,7 +112,8 @@ WhitespaceControl.prototype.BlockStatement = function(block) {
|
||||
}
|
||||
|
||||
// Find standalone else statments
|
||||
if (isPrevWhitespace(program.body)
|
||||
if (!this.options.ignoreStandalone
|
||||
&& isPrevWhitespace(program.body)
|
||||
&& isNextWhitespace(firstInverse.body)) {
|
||||
omitLeft(program.body);
|
||||
omitRight(firstInverse.body);
|
||||
@@ -118,6 +125,7 @@ WhitespaceControl.prototype.BlockStatement = function(block) {
|
||||
return strip;
|
||||
};
|
||||
|
||||
WhitespaceControl.prototype.Decorator =
|
||||
WhitespaceControl.prototype.MustacheStatement = function(mustache) {
|
||||
return mustache.strip;
|
||||
};
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
import registerInline from './decorators/inline';
|
||||
|
||||
export function registerDefaultDecorators(instance) {
|
||||
registerInline(instance);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
import {extend} from '../utils';
|
||||
|
||||
export default function(instance) {
|
||||
instance.registerDecorator('inline', function(fn, props, container, options) {
|
||||
let ret = fn;
|
||||
if (!props.partials) {
|
||||
props.partials = {};
|
||||
ret = function(context, options) {
|
||||
// Create a new partials stack frame prior to exec.
|
||||
let original = container.partials;
|
||||
container.partials = extend({}, original, props.partials);
|
||||
let ret = fn(context, options);
|
||||
container.partials = original;
|
||||
return ret;
|
||||
};
|
||||
}
|
||||
|
||||
props.partials[options.args[0]] = options.fn;
|
||||
|
||||
return ret;
|
||||
});
|
||||
}
|
||||
@@ -19,13 +19,28 @@ function Exception(message, node) {
|
||||
this[errorProps[idx]] = tmp[errorProps[idx]];
|
||||
}
|
||||
|
||||
/* istanbul ignore else */
|
||||
if (Error.captureStackTrace) {
|
||||
Error.captureStackTrace(this, Exception);
|
||||
}
|
||||
|
||||
if (loc) {
|
||||
this.lineNumber = line;
|
||||
this.column = column;
|
||||
try {
|
||||
if (loc) {
|
||||
this.lineNumber = line;
|
||||
|
||||
// Work around issue under safari where we can't directly set the column value
|
||||
/* istanbul ignore next */
|
||||
if (Object.defineProperty) {
|
||||
Object.defineProperty(this, 'column', {
|
||||
value: column,
|
||||
enumerable: true
|
||||
});
|
||||
} else {
|
||||
this.column = column;
|
||||
}
|
||||
}
|
||||
} catch (nop) {
|
||||
/* Ignore if the browser is very particular */
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
import registerBlockHelperMissing from './helpers/block-helper-missing';
|
||||
import registerEach from './helpers/each';
|
||||
import registerHelperMissing from './helpers/helper-missing';
|
||||
import registerIf from './helpers/if';
|
||||
import registerLog from './helpers/log';
|
||||
import registerLookup from './helpers/lookup';
|
||||
import registerWith from './helpers/with';
|
||||
|
||||
export function registerDefaultHelpers(instance) {
|
||||
registerBlockHelperMissing(instance);
|
||||
registerEach(instance);
|
||||
registerHelperMissing(instance);
|
||||
registerIf(instance);
|
||||
registerLog(instance);
|
||||
registerLookup(instance);
|
||||
registerWith(instance);
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
import {appendContextPath, createFrame, isArray} from '../utils';
|
||||
|
||||
export default function(instance) {
|
||||
instance.registerHelper('blockHelperMissing', function(context, options) {
|
||||
let inverse = options.inverse,
|
||||
fn = options.fn;
|
||||
|
||||
if (context === true) {
|
||||
return fn(this);
|
||||
} else if (context === false || context == null) {
|
||||
return inverse(this);
|
||||
} else if (isArray(context)) {
|
||||
if (context.length > 0) {
|
||||
if (options.ids) {
|
||||
options.ids = [options.name];
|
||||
}
|
||||
|
||||
return instance.helpers.each(context, options);
|
||||
} else {
|
||||
return inverse(this);
|
||||
}
|
||||
} else {
|
||||
if (options.data && options.ids) {
|
||||
let data = createFrame(options.data);
|
||||
data.contextPath = appendContextPath(options.data.contextPath, options.name);
|
||||
options = {data: data};
|
||||
}
|
||||
|
||||
return fn(context, options);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
import {appendContextPath, blockParams, createFrame, isArray, isFunction} from '../utils';
|
||||
import Exception from '../exception';
|
||||
|
||||
export default function(instance) {
|
||||
instance.registerHelper('each', function(context, options) {
|
||||
if (!options) {
|
||||
throw new Exception('Must pass iterator to #each');
|
||||
}
|
||||
|
||||
let fn = options.fn,
|
||||
inverse = options.inverse,
|
||||
i = 0,
|
||||
ret = '',
|
||||
data,
|
||||
contextPath;
|
||||
|
||||
if (options.data && options.ids) {
|
||||
contextPath = appendContextPath(options.data.contextPath, options.ids[0]) + '.';
|
||||
}
|
||||
|
||||
if (isFunction(context)) { context = context.call(this); }
|
||||
|
||||
if (options.data) {
|
||||
data = createFrame(options.data);
|
||||
}
|
||||
|
||||
function execIteration(field, index, last) {
|
||||
if (data) {
|
||||
data.key = field;
|
||||
data.index = index;
|
||||
data.first = index === 0;
|
||||
data.last = !!last;
|
||||
|
||||
if (contextPath) {
|
||||
data.contextPath = contextPath + field;
|
||||
}
|
||||
}
|
||||
|
||||
ret = ret + fn(context[field], {
|
||||
data: data,
|
||||
blockParams: blockParams([context[field], field], [contextPath + field, null])
|
||||
});
|
||||
}
|
||||
|
||||
if (context && typeof context === 'object') {
|
||||
if (isArray(context)) {
|
||||
for (let j = context.length; i < j; i++) {
|
||||
if (i in context) {
|
||||
execIteration(i, i, i === context.length - 1);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
let priorKey;
|
||||
|
||||
for (let key in context) {
|
||||
if (context.hasOwnProperty(key)) {
|
||||
// We're running the iterations one step out of sync so we can detect
|
||||
// the last iteration without have to scan the object twice and create
|
||||
// an itermediate keys array.
|
||||
if (priorKey !== undefined) {
|
||||
execIteration(priorKey, i - 1);
|
||||
}
|
||||
priorKey = key;
|
||||
i++;
|
||||
}
|
||||
}
|
||||
if (priorKey !== undefined) {
|
||||
execIteration(priorKey, i - 1, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (i === 0) {
|
||||
ret = inverse(this);
|
||||
}
|
||||
|
||||
return ret;
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
import Exception from '../exception';
|
||||
|
||||
export default function(instance) {
|
||||
instance.registerHelper('helperMissing', function(/* [args, ]options */) {
|
||||
if (arguments.length === 1) {
|
||||
// A missing field in a {{foo}} construct.
|
||||
return undefined;
|
||||
} else {
|
||||
// Someone is actually trying to call something, blow up.
|
||||
throw new Exception('Missing helper: "' + arguments[arguments.length - 1].name + '"');
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
import {isEmpty, isFunction} from '../utils';
|
||||
|
||||
export default function(instance) {
|
||||
instance.registerHelper('if', function(conditional, options) {
|
||||
if (isFunction(conditional)) { conditional = conditional.call(this); }
|
||||
|
||||
// Default behavior is to render the positive path if the value is truthy and not empty.
|
||||
// The `includeZero` option may be set to treat the condtional as purely not empty based on the
|
||||
// behavior of isEmpty. Effectively this determines if 0 is handled by the positive path or negative.
|
||||
if ((!options.hash.includeZero && !conditional) || isEmpty(conditional)) {
|
||||
return options.inverse(this);
|
||||
} else {
|
||||
return options.fn(this);
|
||||
}
|
||||
});
|
||||
|
||||
instance.registerHelper('unless', function(conditional, options) {
|
||||
return instance.helpers['if'].call(this, conditional, {fn: options.inverse, inverse: options.fn, hash: options.hash});
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
export default function(instance) {
|
||||
instance.registerHelper('log', function(/* message, options */) {
|
||||
let args = [undefined],
|
||||
options = arguments[arguments.length - 1];
|
||||
for (let i = 0; i < arguments.length - 1; i++) {
|
||||
args.push(arguments[i]);
|
||||
}
|
||||
|
||||
let level = 1;
|
||||
if (options.hash.level != null) {
|
||||
level = options.hash.level;
|
||||
} else if (options.data && options.data.level != null) {
|
||||
level = options.data.level;
|
||||
}
|
||||
args[0] = level;
|
||||
|
||||
instance.log(... args);
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
export default function(instance) {
|
||||
instance.registerHelper('lookup', function(obj, field) {
|
||||
return obj && obj[field];
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
import {appendContextPath, blockParams, createFrame, isEmpty, isFunction} from '../utils';
|
||||
|
||||
export default function(instance) {
|
||||
instance.registerHelper('with', function(context, options) {
|
||||
if (isFunction(context)) { context = context.call(this); }
|
||||
|
||||
let fn = options.fn;
|
||||
|
||||
if (!isEmpty(context)) {
|
||||
let data = options.data;
|
||||
if (options.data && options.ids) {
|
||||
data = createFrame(options.data);
|
||||
data.contextPath = appendContextPath(options.data.contextPath, options.ids[0]);
|
||||
}
|
||||
|
||||
return fn(context, {
|
||||
data: data,
|
||||
blockParams: blockParams([context], [data && data.contextPath])
|
||||
});
|
||||
} else {
|
||||
return options.inverse(this);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
import {indexOf} from './utils';
|
||||
|
||||
let logger = {
|
||||
methodMap: ['debug', 'info', 'warn', 'error'],
|
||||
level: 'info',
|
||||
|
||||
// Maps a given level value to the `methodMap` indexes above.
|
||||
lookupLevel: function(level) {
|
||||
if (typeof level === 'string') {
|
||||
let levelMap = indexOf(logger.methodMap, level.toLowerCase());
|
||||
if (levelMap >= 0) {
|
||||
level = levelMap;
|
||||
} else {
|
||||
level = parseInt(level, 10);
|
||||
}
|
||||
}
|
||||
|
||||
return level;
|
||||
},
|
||||
|
||||
// Can be overridden in the host environment
|
||||
log: function(level, ...message) {
|
||||
level = logger.lookupLevel(level);
|
||||
|
||||
if (typeof console !== 'undefined' && logger.lookupLevel(logger.level) <= level) {
|
||||
let method = logger.methodMap[level];
|
||||
if (!console[method]) { // eslint-disable-line no-console
|
||||
method = 'log';
|
||||
}
|
||||
console[method](...message); // eslint-disable-line no-console
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export default logger;
|
||||
@@ -1,4 +1,4 @@
|
||||
/*global window */
|
||||
/* global window */
|
||||
export default function(Handlebars) {
|
||||
/* istanbul ignore next */
|
||||
let root = typeof global !== 'undefined' ? global : window,
|
||||
@@ -8,5 +8,6 @@ export default function(Handlebars) {
|
||||
if (root.Handlebars === Handlebars) {
|
||||
root.Handlebars = $Handlebars;
|
||||
}
|
||||
return Handlebars;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -20,8 +20,6 @@ export function checkRevision(compilerInfo) {
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Remove this line and break up compilePartial
|
||||
|
||||
export function template(templateSpec, env) {
|
||||
/* istanbul ignore next */
|
||||
if (!env) {
|
||||
@@ -31,6 +29,8 @@ export function template(templateSpec, env) {
|
||||
throw new Exception('Unknown template object: ' + typeof templateSpec);
|
||||
}
|
||||
|
||||
templateSpec.main.decorator = templateSpec.main_d;
|
||||
|
||||
// Note: Using env.VM references rather than local var references throughout this section to allow
|
||||
// for external users to override these as psuedo-supported APIs.
|
||||
env.VM.checkRevision(templateSpec.compiler);
|
||||
@@ -38,6 +38,9 @@ export function template(templateSpec, env) {
|
||||
function invokePartialWrapper(partial, context, options) {
|
||||
if (options.hash) {
|
||||
context = Utils.extend({}, context, options.hash);
|
||||
if (options.ids) {
|
||||
options.ids[0] = true;
|
||||
}
|
||||
}
|
||||
|
||||
partial = env.VM.resolvePartial.call(this, partial, context, options);
|
||||
@@ -89,7 +92,9 @@ export function template(templateSpec, env) {
|
||||
invokePartial: invokePartialWrapper,
|
||||
|
||||
fn: function(i) {
|
||||
return templateSpec[i];
|
||||
let ret = templateSpec[i];
|
||||
ret.decorator = templateSpec[i + '_d'];
|
||||
return ret;
|
||||
},
|
||||
|
||||
programs: [],
|
||||
@@ -119,6 +124,8 @@ export function template(templateSpec, env) {
|
||||
|
||||
return obj;
|
||||
},
|
||||
// An empty object to use as replacement for null-contexts
|
||||
nullContext: Object.seal({}),
|
||||
|
||||
noop: env.VM.noop,
|
||||
compilerInfo: templateSpec.compiler
|
||||
@@ -134,10 +141,18 @@ export function template(templateSpec, env) {
|
||||
let depths,
|
||||
blockParams = templateSpec.useBlockParams ? [] : undefined;
|
||||
if (templateSpec.useDepths) {
|
||||
depths = options.depths ? [context].concat(options.depths) : [context];
|
||||
if (options.depths) {
|
||||
depths = context != options.depths[0] ? [context].concat(options.depths) : options.depths;
|
||||
} else {
|
||||
depths = [context];
|
||||
}
|
||||
}
|
||||
|
||||
return templateSpec.main.call(container, context, container.helpers, container.partials, data, blockParams, depths);
|
||||
function main(context/*, options*/) {
|
||||
return '' + templateSpec.main(container, context, container.helpers, container.partials, data, blockParams, depths);
|
||||
}
|
||||
main = executeDecorators(templateSpec.main, main, container, options.depths || [], data, blockParams);
|
||||
return main(context, options);
|
||||
}
|
||||
ret.isTop = true;
|
||||
|
||||
@@ -148,9 +163,13 @@ export function template(templateSpec, env) {
|
||||
if (templateSpec.usePartial) {
|
||||
container.partials = container.merge(options.partials, env.partials);
|
||||
}
|
||||
if (templateSpec.usePartial || templateSpec.useDecorators) {
|
||||
container.decorators = container.merge(options.decorators, env.decorators);
|
||||
}
|
||||
} else {
|
||||
container.helpers = options.helpers;
|
||||
container.partials = options.partials;
|
||||
container.decorators = options.decorators;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -169,13 +188,21 @@ export function template(templateSpec, env) {
|
||||
|
||||
export function wrapProgram(container, i, fn, data, declaredBlockParams, blockParams, depths) {
|
||||
function prog(context, options = {}) {
|
||||
return fn.call(container,
|
||||
let currentDepths = depths;
|
||||
if (depths && context != depths[0] && !(context === container.nullContext && depths[0] === null)) {
|
||||
currentDepths = [context].concat(depths);
|
||||
}
|
||||
|
||||
return fn(container,
|
||||
context,
|
||||
container.helpers, container.partials,
|
||||
options.data || data,
|
||||
blockParams && [options.blockParams].concat(blockParams),
|
||||
depths && [context].concat(depths));
|
||||
currentDepths);
|
||||
}
|
||||
|
||||
prog = executeDecorators(fn, prog, container, depths, data, blockParams);
|
||||
|
||||
prog.program = i;
|
||||
prog.depth = depths ? depths.length : 0;
|
||||
prog.blockParams = declaredBlockParams || 0;
|
||||
@@ -184,7 +211,11 @@ export function wrapProgram(container, i, fn, data, declaredBlockParams, blockPa
|
||||
|
||||
export function resolvePartial(partial, context, options) {
|
||||
if (!partial) {
|
||||
partial = options.partials[options.name];
|
||||
if (options.name === '@partial-block') {
|
||||
partial = options.data['partial-block'];
|
||||
} else {
|
||||
partial = options.partials[options.name];
|
||||
}
|
||||
} else if (!partial.call && !options.name) {
|
||||
// This is a dynamic partial that returned a string
|
||||
options.name = partial;
|
||||
@@ -194,7 +225,34 @@ export function resolvePartial(partial, context, options) {
|
||||
}
|
||||
|
||||
export function invokePartial(partial, context, options) {
|
||||
// Use the current closure context to save the partial-block if this partial
|
||||
const currentPartialBlock = options.data && options.data['partial-block'];
|
||||
options.partial = true;
|
||||
if (options.ids) {
|
||||
options.data.contextPath = options.ids[0] || options.data.contextPath;
|
||||
}
|
||||
|
||||
let partialBlock;
|
||||
if (options.fn && options.fn !== noop) {
|
||||
options.data = createFrame(options.data);
|
||||
// Wrapper function to get access to currentPartialBlock from the closure
|
||||
let fn = options.fn;
|
||||
partialBlock = options.data['partial-block'] = function partialBlockWrapper(context, options = {}) {
|
||||
|
||||
// Restore the partial-block from the closure for the execution of the block
|
||||
// i.e. the part inside the block of the partial call.
|
||||
options.data = createFrame(options.data);
|
||||
options.data['partial-block'] = currentPartialBlock;
|
||||
return fn(context, options);
|
||||
};
|
||||
if (fn.partials) {
|
||||
options.partials = Utils.extend({}, options.partials, fn.partials);
|
||||
}
|
||||
}
|
||||
|
||||
if (partial === undefined && partialBlock) {
|
||||
partial = partialBlock;
|
||||
}
|
||||
|
||||
if (partial === undefined) {
|
||||
throw new Exception('The partial ' + options.name + ' could not be found');
|
||||
@@ -212,3 +270,12 @@ function initData(context, data) {
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
function executeDecorators(fn, prog, container, depths, data, blockParams) {
|
||||
if (fn.decorator) {
|
||||
let props = {};
|
||||
prog = fn.decorator(prog, props, container, depths && depths[0], data, blockParams, depths);
|
||||
Utils.extend(prog, props);
|
||||
}
|
||||
return prog;
|
||||
}
|
||||
|
||||
+15
-8
@@ -4,17 +4,18 @@ const escape = {
|
||||
'>': '>',
|
||||
'"': '"',
|
||||
"'": ''',
|
||||
'`': '`'
|
||||
'`': '`',
|
||||
'=': '='
|
||||
};
|
||||
|
||||
const badChars = /[&<>"'`]/g,
|
||||
possible = /[&<>"'`]/;
|
||||
const badChars = /[&<>"'`=]/g,
|
||||
possible = /[&<>"'`=]/;
|
||||
|
||||
function escapeChar(chr) {
|
||||
return escape[chr];
|
||||
}
|
||||
|
||||
export function extend(obj /* , ...source */) {
|
||||
export function extend(obj/* , ...source */) {
|
||||
for (let i = 1; i < arguments.length; i++) {
|
||||
for (let key in arguments[i]) {
|
||||
if (Object.prototype.hasOwnProperty.call(arguments[i], key)) {
|
||||
@@ -30,8 +31,8 @@ export let toString = Object.prototype.toString;
|
||||
|
||||
// Sourced from lodash
|
||||
// https://github.com/bestiejs/lodash/blob/master/LICENSE.txt
|
||||
/*eslint-disable func-style, no-var */
|
||||
var isFunction = function(value) {
|
||||
/* eslint-disable func-style */
|
||||
let isFunction = function(value) {
|
||||
return typeof value === 'function';
|
||||
};
|
||||
// fallback for older versions of Chrome and Safari
|
||||
@@ -41,8 +42,8 @@ if (isFunction(/x/)) {
|
||||
return typeof value === 'function' && toString.call(value) === '[object Function]';
|
||||
};
|
||||
}
|
||||
export var isFunction;
|
||||
/*eslint-enable func-style, no-var */
|
||||
export {isFunction};
|
||||
/* eslint-enable func-style */
|
||||
|
||||
/* istanbul ignore next */
|
||||
export const isArray = Array.isArray || function(value) {
|
||||
@@ -91,6 +92,12 @@ export function isEmpty(value) {
|
||||
}
|
||||
}
|
||||
|
||||
export function createFrame(object) {
|
||||
let frame = extend({}, object);
|
||||
frame._parent = object;
|
||||
return frame;
|
||||
}
|
||||
|
||||
export function blockParams(params, ids) {
|
||||
params.path = ids;
|
||||
return params;
|
||||
|
||||
+180
-85
@@ -1,35 +1,161 @@
|
||||
/*eslint-disable no-console */
|
||||
/* eslint-disable no-console */
|
||||
import Async from 'async';
|
||||
import fs from 'fs';
|
||||
import * as Handlebars from './handlebars';
|
||||
import {basename} from 'path';
|
||||
import {SourceMapConsumer, SourceNode} from 'source-map';
|
||||
import uglify from 'uglify-js';
|
||||
|
||||
module.exports.loadTemplates = function(opts, callback) {
|
||||
loadStrings(opts, function(err, strings) {
|
||||
if (err) {
|
||||
callback(err);
|
||||
} else {
|
||||
loadFiles(opts, function(err, files) {
|
||||
if (err) {
|
||||
callback(err);
|
||||
} else {
|
||||
opts.templates = strings.concat(files);
|
||||
callback(undefined, opts);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
function loadStrings(opts, callback) {
|
||||
let strings = arrayCast(opts.string),
|
||||
names = arrayCast(opts.name);
|
||||
|
||||
if (names.length !== strings.length
|
||||
&& strings.length > 1) {
|
||||
return callback(new Handlebars.Exception('Number of names did not match the number of string inputs'));
|
||||
}
|
||||
|
||||
Async.map(strings, function(string, callback) {
|
||||
if (string !== '-') {
|
||||
callback(undefined, string);
|
||||
} else {
|
||||
// Load from stdin
|
||||
let buffer = '';
|
||||
process.stdin.setEncoding('utf8');
|
||||
|
||||
process.stdin.on('data', function(chunk) {
|
||||
buffer += chunk;
|
||||
});
|
||||
process.stdin.on('end', function() {
|
||||
callback(undefined, buffer);
|
||||
});
|
||||
}
|
||||
},
|
||||
function(err, strings) {
|
||||
strings = strings.map((string, index) => ({
|
||||
name: names[index],
|
||||
path: names[index],
|
||||
source: string
|
||||
}));
|
||||
callback(err, strings);
|
||||
});
|
||||
}
|
||||
|
||||
function loadFiles(opts, callback) {
|
||||
// Build file extension pattern
|
||||
let extension = (opts.extension || 'handlebars').replace(/[\\^$*+?.():=!|{}\-\[\]]/g, function(arg) { return '\\' + arg; });
|
||||
extension = new RegExp('\\.' + extension + '$');
|
||||
|
||||
let ret = [],
|
||||
queue = (opts.files || []).map((template) => ({template, root: opts.root}));
|
||||
Async.whilst(() => queue.length, function(callback) {
|
||||
let {template: path, root} = queue.shift();
|
||||
|
||||
fs.stat(path, function(err, stat) {
|
||||
if (err) {
|
||||
return callback(new Handlebars.Exception(`Unable to open template file "${path}"`));
|
||||
}
|
||||
|
||||
if (stat.isDirectory()) {
|
||||
opts.hasDirectory = true;
|
||||
|
||||
fs.readdir(path, function(err, children) {
|
||||
/* istanbul ignore next : Race condition that being too lazy to test */
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
children.forEach(function(file) {
|
||||
let childPath = path + '/' + file;
|
||||
|
||||
if (extension.test(childPath) || fs.statSync(childPath).isDirectory()) {
|
||||
queue.push({template: childPath, root: root || path});
|
||||
}
|
||||
});
|
||||
|
||||
callback();
|
||||
});
|
||||
} else {
|
||||
fs.readFile(path, 'utf8', function(err, data) {
|
||||
/* istanbul ignore next : Race condition that being too lazy to test */
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
if (opts.bom && data.indexOf('\uFEFF') === 0) {
|
||||
data = data.substring(1);
|
||||
}
|
||||
|
||||
// Clean the template name
|
||||
let name = path;
|
||||
if (!root) {
|
||||
name = basename(name);
|
||||
} else if (name.indexOf(root) === 0) {
|
||||
name = name.substring(root.length + 1);
|
||||
}
|
||||
name = name.replace(extension, '');
|
||||
|
||||
ret.push({
|
||||
path: path,
|
||||
name: name,
|
||||
source: data
|
||||
});
|
||||
|
||||
callback();
|
||||
});
|
||||
}
|
||||
});
|
||||
},
|
||||
function(err) {
|
||||
if (err) {
|
||||
callback(err);
|
||||
} else {
|
||||
callback(undefined, ret);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
module.exports.cli = function(opts) {
|
||||
if (opts.version) {
|
||||
console.log(Handlebars.VERSION);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!opts.templates.length) {
|
||||
if (!opts.templates.length && !opts.hasDirectory) {
|
||||
throw new Handlebars.Exception('Must define at least one template or directory.');
|
||||
}
|
||||
|
||||
opts.templates.forEach(function(template) {
|
||||
try {
|
||||
fs.statSync(template);
|
||||
} catch (err) {
|
||||
throw new Handlebars.Exception(`Unable to open template file "${template}"`);
|
||||
}
|
||||
});
|
||||
|
||||
if (opts.simple && opts.min) {
|
||||
throw new Handlebars.Exception('Unable to minimize simple output');
|
||||
}
|
||||
if (opts.simple && (opts.templates.length !== 1 || fs.statSync(opts.templates[0]).isDirectory())) {
|
||||
|
||||
const multiple = opts.templates.length !== 1 || opts.hasDirectory;
|
||||
if (opts.simple && multiple) {
|
||||
throw new Handlebars.Exception('Unable to output multiple templates in simple mode');
|
||||
}
|
||||
|
||||
// Force simple mode if we have only one template and it's unnamed.
|
||||
if (!opts.amd && !opts.commonjs && opts.templates.length === 1
|
||||
&& !opts.templates[0].name) {
|
||||
opts.simple = true;
|
||||
}
|
||||
|
||||
// Convert the known list into a hash
|
||||
let known = {};
|
||||
if (opts.known && !Array.isArray(opts.known)) {
|
||||
@@ -41,9 +167,7 @@ module.exports.cli = function(opts) {
|
||||
}
|
||||
}
|
||||
|
||||
// Build file extension pattern
|
||||
let extension = opts.extension.replace(/[\\^$*+?.():=!|{}\-\[\]]/g, function(arg) { return '\\' + arg; });
|
||||
extension = new RegExp('\\.' + extension + '$');
|
||||
const objectName = opts.partial ? 'Handlebars.partials' : 'templates';
|
||||
|
||||
let output = new SourceNode();
|
||||
if (!opts.simple) {
|
||||
@@ -63,81 +187,47 @@ module.exports.cli = function(opts) {
|
||||
}
|
||||
output.add('{};\n');
|
||||
}
|
||||
function processTemplate(template, root) {
|
||||
let path = template,
|
||||
stat = fs.statSync(path);
|
||||
if (stat.isDirectory()) {
|
||||
fs.readdirSync(template).map(function(file) {
|
||||
let childPath = template + '/' + file;
|
||||
|
||||
if (extension.test(childPath) || fs.statSync(childPath).isDirectory()) {
|
||||
processTemplate(childPath, root || template);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
let data = fs.readFileSync(path, 'utf8');
|
||||
|
||||
if (opts.bom && data.indexOf('\uFEFF') === 0) {
|
||||
data = data.substring(1);
|
||||
}
|
||||
|
||||
let options = {
|
||||
knownHelpers: known,
|
||||
knownHelpersOnly: opts.o
|
||||
};
|
||||
|
||||
if (opts.map) {
|
||||
options.srcName = path;
|
||||
}
|
||||
if (opts.data) {
|
||||
options.data = true;
|
||||
}
|
||||
|
||||
// Clean the template name
|
||||
if (!root) {
|
||||
template = basename(template);
|
||||
} else if (template.indexOf(root) === 0) {
|
||||
template = template.substring(root.length + 1);
|
||||
}
|
||||
template = template.replace(extension, '');
|
||||
|
||||
let precompiled = Handlebars.precompile(data, options);
|
||||
|
||||
// If we are generating a source map, we have to reconstruct the SourceNode object
|
||||
if (opts.map) {
|
||||
let consumer = new SourceMapConsumer(precompiled.map);
|
||||
precompiled = SourceNode.fromStringWithSourceMap(precompiled.code, consumer);
|
||||
}
|
||||
|
||||
if (opts.simple) {
|
||||
output.add([precompiled, '\n']);
|
||||
} else if (opts.partial) {
|
||||
if (opts.amd && (opts.templates.length == 1 && !fs.statSync(opts.templates[0]).isDirectory())) {
|
||||
output.add('return ');
|
||||
}
|
||||
output.add(['Handlebars.partials[\'', template, '\'] = template(', precompiled, ');\n']);
|
||||
} else {
|
||||
if (opts.amd && (opts.templates.length == 1 && !fs.statSync(opts.templates[0]).isDirectory())) {
|
||||
output.add('return ');
|
||||
}
|
||||
output.add(['templates[\'', template, '\'] = template(', precompiled, ');\n']);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
opts.templates.forEach(function(template) {
|
||||
processTemplate(template, opts.root);
|
||||
let options = {
|
||||
knownHelpers: known,
|
||||
knownHelpersOnly: opts.o
|
||||
};
|
||||
|
||||
if (opts.map) {
|
||||
options.srcName = template.path;
|
||||
}
|
||||
if (opts.data) {
|
||||
options.data = true;
|
||||
}
|
||||
|
||||
let precompiled = Handlebars.precompile(template.source, options);
|
||||
|
||||
// If we are generating a source map, we have to reconstruct the SourceNode object
|
||||
if (opts.map) {
|
||||
let consumer = new SourceMapConsumer(precompiled.map);
|
||||
precompiled = SourceNode.fromStringWithSourceMap(precompiled.code, consumer);
|
||||
}
|
||||
|
||||
if (opts.simple) {
|
||||
output.add([precompiled, '\n']);
|
||||
} else {
|
||||
if (!template.name) {
|
||||
throw new Handlebars.Exception('Name missing for template');
|
||||
}
|
||||
|
||||
if (opts.amd && !multiple) {
|
||||
output.add('return ');
|
||||
}
|
||||
output.add([objectName, '[\'', template.name, '\'] = template(', precompiled, ');\n']);
|
||||
}
|
||||
});
|
||||
|
||||
// Output the content
|
||||
if (!opts.simple) {
|
||||
if (opts.amd) {
|
||||
if (opts.templates.length > 1 || (opts.templates.length == 1 && fs.statSync(opts.templates[0]).isDirectory())) {
|
||||
if (opts.partial) {
|
||||
output.add('return Handlebars.partials;\n');
|
||||
} else {
|
||||
output.add('return templates;\n');
|
||||
}
|
||||
if (multiple) {
|
||||
output.add(['return ', objectName, ';\n']);
|
||||
}
|
||||
output.add('});');
|
||||
} else if (!opts.commonjs) {
|
||||
@@ -160,9 +250,6 @@ module.exports.cli = function(opts) {
|
||||
outSourceMap: opts.map,
|
||||
inSourceMap: JSON.parse(output.map)
|
||||
});
|
||||
if (opts.map) {
|
||||
output.code += '\n//# sourceMappingURL=' + opts.map + '\n';
|
||||
}
|
||||
}
|
||||
|
||||
if (opts.map) {
|
||||
@@ -176,3 +263,11 @@ module.exports.cli = function(opts) {
|
||||
console.log(output);
|
||||
}
|
||||
};
|
||||
|
||||
function arrayCast(value) {
|
||||
value = value != null ? value : [];
|
||||
if (!Array.isArray(value)) {
|
||||
value = [value];
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
+12
-10
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "handlebars",
|
||||
"barename": "handlebars",
|
||||
"version": "3.0.3",
|
||||
"version": "4.0.9",
|
||||
"description": "Handlebars provides the power necessary to let you build semantic templates effectively with no frustration",
|
||||
"homepage": "http://www.handlebarsjs.com/",
|
||||
"keywords": [
|
||||
@@ -21,15 +21,15 @@
|
||||
"node": ">=0.4.7"
|
||||
},
|
||||
"dependencies": {
|
||||
"async": "^1.4.0",
|
||||
"optimist": "^0.6.1",
|
||||
"source-map": "^0.1.40"
|
||||
"source-map": "^0.4.4"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"uglify-js": "~2.3"
|
||||
"uglify-js": "^2.6"
|
||||
},
|
||||
"devDependencies": {
|
||||
"async": "^0.9.0",
|
||||
"aws-sdk": "~1.5.0",
|
||||
"aws-sdk": "^2.1.49",
|
||||
"babel-loader": "^5.0.0",
|
||||
"babel-runtime": "^5.1.10",
|
||||
"benchmark": "~1.0",
|
||||
@@ -45,16 +45,18 @@
|
||||
"grunt-contrib-requirejs": "0.x",
|
||||
"grunt-contrib-uglify": "0.x",
|
||||
"grunt-contrib-watch": "0.x",
|
||||
"grunt-eslint": "^11.0.0",
|
||||
"grunt-eslint": "^17.1.0",
|
||||
"grunt-saucelabs": "8.x",
|
||||
"grunt-webpack": "^1.0.8",
|
||||
"istanbul": "^0.3.0",
|
||||
"jison": "~0.3.0",
|
||||
"keen.io": "0.0.3",
|
||||
"mocha": "~1.20.0",
|
||||
"mustache": "0.x",
|
||||
"semver": "^4.0.0",
|
||||
"underscore": "^1.5.1"
|
||||
"mock-stdin": "^0.3.0",
|
||||
"mustache": "^2.1.3",
|
||||
"semver": "^5.0.1",
|
||||
"underscore": "^1.5.1",
|
||||
"webpack": "^1.12.6",
|
||||
"webpack-dev-server": "^1.12.1"
|
||||
},
|
||||
"main": "lib/index.js",
|
||||
"bin": {
|
||||
|
||||
Executable
+95
@@ -0,0 +1,95 @@
|
||||
#! /usr/bin/env node
|
||||
/* eslint-disable no-console, no-var */
|
||||
// Util script for debugging source code generation issues
|
||||
|
||||
var script = process.argv[2].replace(/\\n/g, '\n'),
|
||||
verbose = process.argv[3] === '-v';
|
||||
|
||||
var Handlebars = require('./lib'),
|
||||
SourceMap = require('source-map'),
|
||||
SourceMapConsumer = SourceMap.SourceMapConsumer;
|
||||
|
||||
var template = Handlebars.precompile(script, {
|
||||
srcName: 'input.hbs',
|
||||
destName: 'output.js',
|
||||
|
||||
assumeObjects: true,
|
||||
compat: false,
|
||||
strict: true,
|
||||
trackIds: true,
|
||||
knownHelpersOnly: false
|
||||
});
|
||||
|
||||
if (!verbose) {
|
||||
console.log(template);
|
||||
} else {
|
||||
var consumer = new SourceMapConsumer(template.map),
|
||||
lines = template.code.split('\n'),
|
||||
srcLines = script.split('\n');
|
||||
|
||||
console.log();
|
||||
console.log('Source:');
|
||||
srcLines.forEach(function(source, index) {
|
||||
console.log(index + 1, source);
|
||||
});
|
||||
console.log();
|
||||
console.log('Generated:');
|
||||
console.log(template.code);
|
||||
lines.forEach(function(source, index) {
|
||||
console.log(index + 1, source);
|
||||
});
|
||||
console.log();
|
||||
console.log('Map:');
|
||||
console.log(template.map);
|
||||
console.log();
|
||||
|
||||
function collectSource(lines, lineName, colName, order) {
|
||||
var ret = {},
|
||||
ordered = [],
|
||||
last;
|
||||
|
||||
function collect(current) {
|
||||
if (last) {
|
||||
var mapLines = lines.slice(last[lineName] - 1, current && current[lineName]);
|
||||
if (mapLines.length) {
|
||||
if (current) {
|
||||
mapLines[mapLines.length - 1] = mapLines[mapLines.length - 1].slice(0, current[colName]);
|
||||
}
|
||||
mapLines[0] = mapLines[0].slice(last[colName]);
|
||||
}
|
||||
ret[last[lineName] + ':' + last[colName]] = mapLines.join('\n');
|
||||
ordered.push({
|
||||
startLine: last[lineName],
|
||||
startCol: last[colName],
|
||||
endLine: current && current[lineName]
|
||||
});
|
||||
}
|
||||
last = current;
|
||||
}
|
||||
|
||||
consumer.eachMapping(collect, undefined, order);
|
||||
collect();
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
srcLines = collectSource(srcLines, 'originalLine', 'originalColumn', SourceMapConsumer.ORIGINAL_ORDER);
|
||||
lines = collectSource(lines, 'generatedLine', 'generatedColumn');
|
||||
|
||||
consumer.eachMapping(function(mapping) {
|
||||
var originalSrc = srcLines[mapping.originalLine + ':' + mapping.originalColumn],
|
||||
generatedSrc = lines[mapping.generatedLine + ':' + mapping.generatedColumn];
|
||||
|
||||
if (!mapping.originalLine) {
|
||||
console.log('generated', mapping.generatedLine + ':' + mapping.generatedColumn, generatedSrc);
|
||||
} else {
|
||||
console.log('map',
|
||||
mapping.source,
|
||||
mapping.originalLine + ':' + mapping.originalColumn,
|
||||
originalSrc,
|
||||
'->',
|
||||
mapping.generatedLine + ':' + mapping.generatedColumn,
|
||||
generatedSrc);
|
||||
}
|
||||
});
|
||||
}
|
||||
+144
-2
@@ -2,7 +2,149 @@
|
||||
|
||||
## Development
|
||||
|
||||
[Commits](https://github.com/wycats/handlebars.js/compare/v3.0.3...master)
|
||||
[Commits](https://github.com/nknapp/handlebars.js/compare/v4.0.9...master)
|
||||
|
||||
## v4.0.9 - May 21st, 2017
|
||||
|
||||
- [#1327](https://github.com/wycats/handlebars.js/issues/1327) Handlebars.compile() does not modify "options" anymore
|
||||
- pending [#1331](https://github.com/wycats/handlebars.js/issues/1331) Attempts to build Handlebars in a Windows environment
|
||||
- Fix build in windows - cc554a5
|
||||
- Ensure LF line-edings in handlebars-template fixtures (*.hbs) - ed879a6
|
||||
- Run integration test with `node handlebars -a ...` on Windows - 2e21e2b
|
||||
- Ensure LF line-edings in lexer-files (*.l) - bdfdbea
|
||||
- Force LF line-endings for spec/artifacts - b50ef03
|
||||
- Use istanbul/lib/cli.js instead of node_modules/.bin/istanbul - 6e6269f
|
||||
- TravisCI: Publish valid semver tags independently of the branch - 7378f85
|
||||
|
||||
Compatibility notes:
|
||||
- No compatibility issues are expected.
|
||||
|
||||
[Commits](https://github.com/nknapp/handlebars.js/compare/v4.0.8...v4.0.9)
|
||||
|
||||
## v4.0.8 - May 2nd, 2017
|
||||
- [#1341](https://github.com/wycats/handlebars.js/issues/1341) [#1342](https://github.com/wycats/handlebars.js/issues/1342) Allow partial-blocks to be executed without "options" ([@nknapp](https://github.com/nknapp)) - a00c598
|
||||
|
||||
Compatibility notes:
|
||||
- No breaking changes
|
||||
|
||||
[Commits](https://github.com/nknapp/handlebars.js/compare/v4.0.7...v4.0.8)
|
||||
|
||||
## v4.0.7 - April 29th, 2017
|
||||
- [#1319](https://github.com/wycats/handlebars.js/issues/1319): Fix context-stack when calling block-helpers on null values ([@nknapp](https://github.com/nknapp)) - c8f4b57
|
||||
- [#1315](https://github.com/wycats/handlebars.js/pull/1315) Parser: Change suffix to use ES6 default module export ([@Turbo87](https://github.com/Turbo87))- b617375
|
||||
- [#1290](https://github.com/wycats/handlebars.js/pull/1290) [#1252](https://github.com/wycats/handlebars.js/issue/1290) Add more tests for partial-blocks and inline partials ([@nknapp](https://github.com/nknapp)) - 63a8e0c
|
||||
- [#1252](https://github.com/wycats/handlebars.js/issue/1290) Using @partial-block twice in a template not possible ([@nknapp](https://github.com/nknapp)) - 5a164d0
|
||||
- [#1310](https://github.com/wycats/handlebars.js/pull/1310) Avoid duplicate "sourceMappingURL=" lines. ([@joonas-lahtinen](https://github.com/joonas-lahtinen)) - 01b0f65
|
||||
- [#1275](https://github.com/wycats/handlebars.js/pull/1275) require('sys') is deprecated, using 'util' instead ([@travnels](https://github.com/travnels)) - 406f2ee
|
||||
- [#1285](https://github.com/wycats/handlebars.js/pull/1285) [#1284](https://github.com/wycats/handlebars.js/issues/1284) Make "column"-property of Errors enumerable ([@nknapp](https://github.com/nknapp)) - a023cb4
|
||||
- [#1285](https://github.com/wycats/handlebars.js/pull/1285) Testcase to verify that compile-errors have a column-property ([@nknapp](https://github.com/nknapp)) - c7dc353
|
||||
|
||||
[Commits](https://github.com/lawnsea/handlebars.js/compare/v4.0.6...v4.0.7)
|
||||
|
||||
## v4.0.6 - November 12th, 2016
|
||||
- [#1243](https://github.com/wycats/handlebars.js/pull/1243) - Walk up data frames for nested @partial-block ([@lawnsea](https://github.com/lawnsea))
|
||||
- [#1210](https://github.com/wycats/handlebars.js/pull/1210) - Add a new lightweight package based on handlebars in the README ([@kabirbaidhya](https://github.com/kabirbaidhya))
|
||||
- [#1187](https://github.com/wycats/handlebars.js/pull/1187) - Ensure that existing blockParams and depths are respected on dupe programs ([@charleso](https://github.com/charleso))
|
||||
- [#1191](https://github.com/wycats/handlebars.js/pull/1191) - Added cory ([@leo](https://github.com/leo))
|
||||
- [#1177](https://github.com/wycats/handlebars.js/pull/1177) - Preserve License info in Closure Compiler ([@gennadiylitvinyuk](https://github.com/gennadiylitvinyuk))
|
||||
- [#1171](https://github.com/wycats/handlebars.js/pull/1171) - Contributing doc fix: failing thats -> failing tests ([@paulfalgout](https://github.com/paulfalgout))
|
||||
- [#1166](https://github.com/wycats/handlebars.js/pull/1166) - Update license date ([@timwangdev](https://github.com/timwangdev))
|
||||
- Update jsfiddle to point to latest - 959ee55 (originally dfc7554 by [@kpdecker](https://github.com/kpdecker))
|
||||
- [#1163](https://github.com/wycats/handlebars.js/pull/1163) - Fix typos on decorators-api.md. ([@adjohnson916](https://github.com/adjohnson916))
|
||||
- Drop extra Error params - 8c19874 (originally 63fdb92 by [@kpdecker](https://github.com/kpdecker))
|
||||
- [#1153](https://github.com/wycats/handlebars.js/pull/1153) - Add documentation for running tests to contributing.md ([@ryanmurakami](https://github.com/ryanmurakami))
|
||||
- Avoid error in older browsers in test - 400916c (originally a6121ca by [@kpdecker](https://github.com/kpdecker))
|
||||
- Update target browser test versions - fee2334 (originally 871c32a by [@kpdecker](https://github.com/kpdecker))
|
||||
- Exclude coverage check in exception conditional - 32d6363 (originally 326734b by [@kpdecker](https://github.com/kpdecker))
|
||||
- Fix throw when creating exception object in Safari - 20c965c (originally 2ea6119 by [@kpdecker](https://github.com/kpdecker))
|
||||
- Update build for modern node versions - 6c9f98c (originally 8289c0b by [@kpdecker](https://github.com/kpdecker))
|
||||
- [#1135](https://github.com/wycats/handlebars.js/issues/1135) - Relax depth check for context push - c393c81 (originally 25458fd by [@kpdecker](https://github.com/kpdecker))
|
||||
|
||||
[Commits](https://github.com/wycats/handlebars.js/compare/v4.0.5...v4.0.6)
|
||||
|
||||
## v4.0.5 - November 19th, 2015
|
||||
- [#1132](https://github.com/wycats/handlebars.js/pull/1132) - Update uglify-js to avoid vulnerability ([@plynchnlm](https://api.github.com/users/plynchnlm))
|
||||
- [#1129](https://github.com/wycats/handlebars.js/issues/1129) - Minified lib returns an empty string ([@bricss](https://api.github.com/users/bricss))
|
||||
- Return current handlebars instance from noConflict - 685cf92
|
||||
- Add webpack to dev dependency to support npm 3 - 7a6c228
|
||||
- Further relax uglify dependency - 0a3b3c2
|
||||
- Include tests for minimized artifacts - c21118d
|
||||
- Fix lint errors under latest eslint - 9f59de9
|
||||
- Add print-script helper script - 98a6717
|
||||
|
||||
[Commits](https://github.com/wycats/handlebars.js/compare/v4.0.4...v4.0.5)
|
||||
|
||||
## v4.0.4 - October 29th, 2015
|
||||
- [#1121](https://github.com/wycats/handlebars.js/pull/1121) - Include partial name in 'undefined partial' exception message ([@shinypb](https://api.github.com/users/shinypb))
|
||||
- [#1125](https://github.com/wycats/handlebars.js/pull/1125) - Add promised-handlebars to "in-the-wild"-list ([@nknapp](https://api.github.com/users/nknapp))
|
||||
|
||||
[Commits](https://github.com/wycats/handlebars.js/compare/v4.0.3...v4.0.4)
|
||||
|
||||
## v4.0.3 - September 23rd, 2015
|
||||
- [#1099](https://github.com/wycats/handlebars.js/issues/1099) - @partial-block is overridden ([@btmorex](https://api.github.com/users/btmorex))
|
||||
- [#1093](https://github.com/wycats/handlebars.js/issues/1093) - #each skips iteration on undefined values ([@florianpilz](https://api.github.com/users/florianpilz))
|
||||
- [#1092](https://github.com/wycats/handlebars.js/issues/1092) - Square braces in key name ([@distantnative](https://api.github.com/users/distantnative))
|
||||
- [#1091](https://github.com/wycats/handlebars.js/pull/1091) - fix typo in release notes ([@nikolas](https://api.github.com/users/nikolas))
|
||||
- [#1090](https://github.com/wycats/handlebars.js/pull/1090) - grammar fixes in 4.0.0 release notes ([@nikolas](https://api.github.com/users/nikolas))
|
||||
|
||||
Compatibility notes:
|
||||
- `each` iteration with `undefined` values has been restored to the 3.0 behaviors. Helper calls with undefined context values will now execute against an arbitrary empty object to avoid executing against global object in non-strict mode.
|
||||
- `]` can now be included in `[]` wrapped identifiers by escaping with `\`. Any `[]` identifiers that include `\` will now have to properly escape these values.
|
||||
|
||||
[Commits](https://github.com/wycats/handlebars.js/compare/v4.0.2...v4.0.3)
|
||||
|
||||
## v4.0.2 - September 4th, 2015
|
||||
- [#1089](https://github.com/wycats/handlebars.js/issues/1089) - "Failover content" not working in multiple levels of inline partials ([@michaellopez](https://api.github.com/users/michaellopez))
|
||||
|
||||
[Commits](https://github.com/wycats/handlebars.js/compare/v4.0.1...v4.0.2)
|
||||
|
||||
## v4.0.1 - September 2nd, 2015
|
||||
- Fix failure when using decorators in partials - 05b82a2
|
||||
|
||||
[Commits](https://github.com/wycats/handlebars.js/compare/v4.0.0...v4.0.1)
|
||||
|
||||
## v4.0.0 - September 1st, 2015
|
||||
- [#1082](https://github.com/wycats/handlebars.js/pull/1082) - Decorators and Inline Partials ([@kpdecker](https://api.github.com/users/kpdecker))
|
||||
- [#1076](https://github.com/wycats/handlebars.js/pull/1076) - Implement partial blocks ([@kpdecker](https://api.github.com/users/kpdecker))
|
||||
- [#1087](https://github.com/wycats/handlebars.js/pull/1087) - Fix #each when last object entry has empty key ([@denniskuczynski](https://api.github.com/users/denniskuczynski))
|
||||
- [#1084](https://github.com/wycats/handlebars.js/pull/1084) - Bump uglify version to fix vulnerability ([@John-Steidley](https://api.github.com/users/John-Steidley))
|
||||
- [#1068](https://github.com/wycats/handlebars.js/pull/1068) - Fix typo ([@0xack13](https://api.github.com/users/0xack13))
|
||||
- [#1060](https://github.com/wycats/handlebars.js/pull/1060) - #1056 Fixed grammar for nested raw blocks ([@ericbn](https://api.github.com/users/ericbn))
|
||||
- [#1052](https://github.com/wycats/handlebars.js/pull/1052) - Updated year in License ([@maqnouch](https://api.github.com/users/maqnouch))
|
||||
- [#1037](https://github.com/wycats/handlebars.js/pull/1037) - Fix minor typos in README ([@tomxtobin](https://api.github.com/users/tomxtobin))
|
||||
- [#1032](https://github.com/wycats/handlebars.js/issues/1032) - Is it possible to render a partial without the parent scope? ([@aputinski](https://api.github.com/users/aputinski))
|
||||
- [#1019](https://github.com/wycats/handlebars.js/pull/1019) - Fixes typo in tests ([@aymerick](https://api.github.com/users/aymerick))
|
||||
- [#1016](https://github.com/wycats/handlebars.js/issues/1016) - Version mis-match ([@mayankdedhia](https://api.github.com/users/mayankdedhia))
|
||||
- [#1023](https://github.com/wycats/handlebars.js/issues/1023) - is it possible for nested custom helpers to communicate between each other?
|
||||
- [#893](https://github.com/wycats/handlebars.js/issues/893) - [Proposal] Section blocks.
|
||||
- [#792](https://github.com/wycats/handlebars.js/issues/792) - feature request: inline partial definitions
|
||||
- [#583](https://github.com/wycats/handlebars.js/issues/583) - Parent path continues to drill down depth with multiple conditionals
|
||||
- [#404](https://github.com/wycats/handlebars.js/issues/404) - Add named child helpers that can be referenced by block helpers
|
||||
- Escape = in HTML content - [83b8e84](https://github.com/wycats/handlebars.js/commit/83b8e84)
|
||||
- Drop AST constructors in favor of JSON - [95d84ba](https://github.com/wycats/handlebars.js/commit/95d84ba)
|
||||
- Pass container rather than exec as context - [9a2d1d6](https://github.com/wycats/handlebars.js/commit/9a2d1d6)
|
||||
- Add ignoreStandalone compiler option - [ea3a5a1](https://github.com/wycats/handlebars.js/commit/ea3a5a1)
|
||||
- Ignore empty when iterating on sparse arrays - [06d515a](https://github.com/wycats/handlebars.js/commit/06d515a)
|
||||
- Add support for string and stdin precompilation - [0de8dac](https://github.com/wycats/handlebars.js/commit/0de8dac)
|
||||
- Simplify object assignment generation logic - [77e6bfc](https://github.com/wycats/handlebars.js/commit/77e6bfc)
|
||||
- Bulletproof AST.helpers.helperExpression - [93b0760](https://github.com/wycats/handlebars.js/commit/93b0760)
|
||||
- Always return string responses - [8e868ab](https://github.com/wycats/handlebars.js/commit/8e868ab)
|
||||
- Pass undefined fields to helpers in strict mode - [5d4b8da](https://github.com/wycats/handlebars.js/commit/5d4b8da)
|
||||
- Avoid depth creation when context remains the same - [279e038](https://github.com/wycats/handlebars.js/commit/279e038)
|
||||
- Improve logging API - [9a49d35](https://github.com/wycats/handlebars.js/commit/9a49d35)
|
||||
- Fix with operator in no @data mode - [231a8d7](https://github.com/wycats/handlebars.js/commit/231a8d7)
|
||||
- Allow empty key name in each iteration - [1bb640b](https://github.com/wycats/handlebars.js/commit/1bb640b)
|
||||
- Add with block parameter support - [2a85106](https://github.com/wycats/handlebars.js/commit/2a85106)
|
||||
- Fix escaping of non-javascript identifiers - [410141c](https://github.com/wycats/handlebars.js/commit/410141c)
|
||||
- Fix location information for programs - [93faffa](https://github.com/wycats/handlebars.js/commit/93faffa)
|
||||
|
||||
Compatibility notes:
|
||||
- Depthed paths are now conditionally pushed on to the stack. If the helper uses the same context, then a new stack is not created. This leads to behavior that better matches expectations for helpers like `if` that do not seem to alter the context. Any instances of `../` in templates will need to be checked for the correct behavior under 4.0.0. In general templates will either reduce the number of `../` instances or leave them as is. See [#1028](https://github.com/wycats/handlebars.js/issues/1028).
|
||||
- The `=` character is now HTML escaped. This closes a potential exploit case when using unquoted attributes, i.e. `<div foo={{bar}}>`. In general it's recommended that attributes always be quoted when their values are generated from a mustache to avoid any potential exploit surfaces.
|
||||
- AST constructors have been dropped in favor of plain old javascript objects
|
||||
- The runtime version has been increased. Precompiled templates will need to use runtime of at least 4.0.0.
|
||||
|
||||
[Commits](https://github.com/wycats/handlebars.js/compare/v3.0.3...v4.0.0)
|
||||
|
||||
## v3.0.3 - April 28th, 2015
|
||||
- [#1004](https://github.com/wycats/handlebars.js/issues/1004) - Latest version breaks with RequireJS (global is undefined) ([@boskee](https://api.github.com/users/boskee))
|
||||
@@ -185,7 +327,7 @@ Compatibility notes:
|
||||
- INTEGER -> NUMBER
|
||||
- Additional PartialNode hash parameter
|
||||
- New RawBlockNode type
|
||||
- Data frames now have a `_parent` field. This is internal but is enumerable for performance/compatability reasons.
|
||||
- Data frames now have a `_parent` field. This is internal but is enumerable for performance/compatibility reasons.
|
||||
|
||||
[Commits](https://github.com/wycats/handlebars.js/compare/v1.3.0...v2.0.0-alpha.1)
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Mocha</title>
|
||||
|
||||
+53
-98
@@ -3,113 +3,49 @@ describe('ast', function() {
|
||||
return;
|
||||
}
|
||||
|
||||
var LOCATION_INFO = {
|
||||
start: {
|
||||
line: 1,
|
||||
column: 1
|
||||
},
|
||||
end: {
|
||||
line: 1,
|
||||
column: 1
|
||||
}
|
||||
};
|
||||
var AST = Handlebars.AST;
|
||||
|
||||
function testLocationInfoStorage(node) {
|
||||
equals(node.loc.start.line, 1);
|
||||
equals(node.loc.start.column, 1);
|
||||
equals(node.loc.end.line, 1);
|
||||
equals(node.loc.end.column, 1);
|
||||
}
|
||||
|
||||
describe('MustacheStatement', function() {
|
||||
it('should store args', function() {
|
||||
var mustache = new handlebarsEnv.AST.MustacheStatement({}, null, null, true, {}, LOCATION_INFO);
|
||||
equals(mustache.type, 'MustacheStatement');
|
||||
equals(mustache.escaped, true);
|
||||
testLocationInfoStorage(mustache);
|
||||
});
|
||||
});
|
||||
describe('BlockStatement', function() {
|
||||
it('should throw on mustache mismatch', function() {
|
||||
shouldThrow(function() {
|
||||
handlebarsEnv.parse('\n {{#foo}}{{/bar}}');
|
||||
}, Handlebars.Exception, "foo doesn't match bar - 2:5");
|
||||
});
|
||||
|
||||
it('stores location info', function() {
|
||||
var mustacheNode = new handlebarsEnv.AST.MustacheStatement([{ original: 'foo'}], null, null, false, {});
|
||||
var block = new handlebarsEnv.AST.BlockStatement(
|
||||
mustacheNode,
|
||||
null, null,
|
||||
{body: []},
|
||||
{body: []},
|
||||
{},
|
||||
{},
|
||||
{},
|
||||
LOCATION_INFO);
|
||||
testLocationInfoStorage(block);
|
||||
});
|
||||
});
|
||||
describe('PathExpression', function() {
|
||||
it('stores location info', function() {
|
||||
var idNode = new handlebarsEnv.AST.PathExpression(false, 0, [], 'foo', LOCATION_INFO);
|
||||
testLocationInfoStorage(idNode);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Hash', function() {
|
||||
it('stores location info', function() {
|
||||
var hash = new handlebarsEnv.AST.Hash([], LOCATION_INFO);
|
||||
testLocationInfoStorage(hash);
|
||||
});
|
||||
});
|
||||
describe('helpers', function() {
|
||||
describe('#helperExpression', function() {
|
||||
it('should handle mustache statements', function() {
|
||||
equals(AST.helpers.helperExpression({type: 'MustacheStatement', params: [], hash: undefined}), false);
|
||||
equals(AST.helpers.helperExpression({type: 'MustacheStatement', params: [1], hash: undefined}), true);
|
||||
equals(AST.helpers.helperExpression({type: 'MustacheStatement', params: [], hash: {}}), true);
|
||||
});
|
||||
it('should handle block statements', function() {
|
||||
equals(AST.helpers.helperExpression({type: 'BlockStatement', params: [], hash: undefined}), false);
|
||||
equals(AST.helpers.helperExpression({type: 'BlockStatement', params: [1], hash: undefined}), true);
|
||||
equals(AST.helpers.helperExpression({type: 'BlockStatement', params: [], hash: {}}), true);
|
||||
});
|
||||
it('should handle subexpressions', function() {
|
||||
equals(AST.helpers.helperExpression({type: 'SubExpression'}), true);
|
||||
});
|
||||
it('should work with non-helper nodes', function() {
|
||||
equals(AST.helpers.helperExpression({type: 'Program'}), false);
|
||||
|
||||
describe('ContentStatement', function() {
|
||||
it('stores location info', function() {
|
||||
var content = new handlebarsEnv.AST.ContentStatement('HI', LOCATION_INFO);
|
||||
testLocationInfoStorage(content);
|
||||
});
|
||||
});
|
||||
equals(AST.helpers.helperExpression({type: 'PartialStatement'}), false);
|
||||
equals(AST.helpers.helperExpression({type: 'ContentStatement'}), false);
|
||||
equals(AST.helpers.helperExpression({type: 'CommentStatement'}), false);
|
||||
|
||||
describe('CommentStatement', function() {
|
||||
it('stores location info', function() {
|
||||
var comment = new handlebarsEnv.AST.CommentStatement('HI', {}, LOCATION_INFO);
|
||||
testLocationInfoStorage(comment);
|
||||
});
|
||||
});
|
||||
equals(AST.helpers.helperExpression({type: 'PathExpression'}), false);
|
||||
|
||||
describe('NumberLiteral', function() {
|
||||
it('stores location info', function() {
|
||||
var integer = new handlebarsEnv.AST.NumberLiteral('6', LOCATION_INFO);
|
||||
testLocationInfoStorage(integer);
|
||||
});
|
||||
});
|
||||
equals(AST.helpers.helperExpression({type: 'StringLiteral'}), false);
|
||||
equals(AST.helpers.helperExpression({type: 'NumberLiteral'}), false);
|
||||
equals(AST.helpers.helperExpression({type: 'BooleanLiteral'}), false);
|
||||
equals(AST.helpers.helperExpression({type: 'UndefinedLiteral'}), false);
|
||||
equals(AST.helpers.helperExpression({type: 'NullLiteral'}), false);
|
||||
|
||||
describe('StringLiteral', function() {
|
||||
it('stores location info', function() {
|
||||
var string = new handlebarsEnv.AST.StringLiteral('6', LOCATION_INFO);
|
||||
testLocationInfoStorage(string);
|
||||
});
|
||||
});
|
||||
|
||||
describe('BooleanLiteral', function() {
|
||||
it('stores location info', function() {
|
||||
var bool = new handlebarsEnv.AST.BooleanLiteral('true', LOCATION_INFO);
|
||||
testLocationInfoStorage(bool);
|
||||
});
|
||||
});
|
||||
|
||||
describe('PartialStatement', function() {
|
||||
it('stores location info', function() {
|
||||
var pn = new handlebarsEnv.AST.PartialStatement('so_partial', [], {}, {}, LOCATION_INFO);
|
||||
testLocationInfoStorage(pn);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Program', function() {
|
||||
it('storing location info', function() {
|
||||
var pn = new handlebarsEnv.AST.Program([], null, {}, LOCATION_INFO);
|
||||
testLocationInfoStorage(pn);
|
||||
equals(AST.helpers.helperExpression({type: 'Hash'}), false);
|
||||
equals(AST.helpers.helperExpression({type: 'HashPair'}), false);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -123,8 +59,18 @@ describe('ast', function() {
|
||||
equals(node.loc.end.column, lastColumn);
|
||||
}
|
||||
|
||||
ast = Handlebars.parse('line 1 {{line1Token}}\n line 2 {{line2token}}\n line 3 {{#blockHelperOnLine3}}\nline 4{{line4token}}\n' +
|
||||
'line5{{else}}\n{{line6Token}}\n{{/blockHelperOnLine3}}');
|
||||
ast = Handlebars.parse(
|
||||
'line 1 {{line1Token}}\n' // 1
|
||||
+ ' line 2 {{line2token}}\n' // 2
|
||||
+ ' line 3 {{#blockHelperOnLine3}}\n' // 3
|
||||
+ 'line 4{{line4token}}\n' // 4
|
||||
+ 'line5{{else}}\n' // 5
|
||||
+ '{{line6Token}}\n' // 6
|
||||
+ '{{/blockHelperOnLine3}}\n' // 7
|
||||
+ '{{#open}}\n' // 8
|
||||
+ '{{else inverse}}\n' // 9
|
||||
+ '{{else}}\n' // 10
|
||||
+ '{{/open}}'); // 11
|
||||
body = ast.body;
|
||||
|
||||
it('gets ContentNode line numbers', function() {
|
||||
@@ -155,14 +101,23 @@ describe('ast', function() {
|
||||
var blockHelperNode = body[5],
|
||||
program = blockHelperNode.program;
|
||||
|
||||
testColumns(program, 3, 5, 8, 5);
|
||||
testColumns(program, 3, 5, 31, 5);
|
||||
});
|
||||
|
||||
it('correctly records the line numbers of an inverse of a block helper', function() {
|
||||
var blockHelperNode = body[5],
|
||||
inverse = blockHelperNode.inverse;
|
||||
|
||||
testColumns(inverse, 5, 7, 5, 0);
|
||||
testColumns(inverse, 5, 7, 13, 0);
|
||||
});
|
||||
|
||||
it('correctly records the line number of chained inverses', function() {
|
||||
var chainInverseNode = body[7];
|
||||
|
||||
testColumns(chainInverseNode.program, 8, 9, 9, 0);
|
||||
testColumns(chainInverseNode.inverse, 9, 10, 16, 0);
|
||||
testColumns(chainInverseNode.inverse.body[0].program, 9, 10, 16, 0);
|
||||
testColumns(chainInverseNode.inverse.body[0].inverse, 10, 11, 8, 0);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
+12
-4
@@ -22,6 +22,10 @@ describe('basic context', function() {
|
||||
'It works if all the required keys are provided');
|
||||
});
|
||||
|
||||
it('compiling with a string context', function() {
|
||||
shouldCompileTo('{{.}}{{length}}', 'bye', 'bye3');
|
||||
});
|
||||
|
||||
it('compiling with an undefined context', function() {
|
||||
shouldCompileTo('Goodbye\n{{cruel}}\n{{world.bar}}!', undefined, 'Goodbye\n\n!');
|
||||
|
||||
@@ -57,14 +61,14 @@ describe('basic context', function() {
|
||||
shouldCompileTo('num: {{num1/num2}}', {num1: {num2: 0}}, 'num: 0');
|
||||
});
|
||||
it('false', function() {
|
||||
/*eslint-disable no-new-wrappers */
|
||||
/* eslint-disable no-new-wrappers */
|
||||
shouldCompileTo('val1: {{val1}}, val2: {{val2}}', {val1: false, val2: new Boolean(false)}, 'val1: false, val2: false');
|
||||
shouldCompileTo('val: {{.}}', false, 'val: false');
|
||||
shouldCompileTo('val: {{val1/val2}}', {val1: {val2: false}}, 'val: false');
|
||||
|
||||
shouldCompileTo('val1: {{{val1}}}, val2: {{{val2}}}', {val1: false, val2: new Boolean(false)}, 'val1: false, val2: false');
|
||||
shouldCompileTo('val: {{{val1/val2}}}', {val1: {val2: false}}, 'val: false');
|
||||
/*eslint-enable */
|
||||
/* eslint-enable */
|
||||
});
|
||||
|
||||
it('should handle undefined and null', function() {
|
||||
@@ -203,8 +207,12 @@ describe('basic context', function() {
|
||||
});
|
||||
|
||||
it('literal references', function() {
|
||||
shouldCompileTo('Goodbye {{[foo bar]}} world!', {'foo bar': 'beautiful'},
|
||||
'Goodbye beautiful world!', 'Literal paths can be used');
|
||||
shouldCompileTo('Goodbye {{[foo bar]}} world!', {'foo bar': 'beautiful'}, 'Goodbye beautiful world!');
|
||||
shouldCompileTo('Goodbye {{"foo bar"}} world!', {'foo bar': 'beautiful'}, 'Goodbye beautiful world!');
|
||||
shouldCompileTo("Goodbye {{'foo bar'}} world!", {'foo bar': 'beautiful'}, 'Goodbye beautiful world!');
|
||||
shouldCompileTo('Goodbye {{"foo[bar"}} world!', {'foo[bar': 'beautiful'}, 'Goodbye beautiful world!');
|
||||
shouldCompileTo('Goodbye {{"foo\'bar"}} world!', {"foo'bar": 'beautiful'}, 'Goodbye beautiful world!');
|
||||
shouldCompileTo("Goodbye {{'foo\"bar'}} world!", {'foo"bar': 'beautiful'}, 'Goodbye beautiful world!');
|
||||
});
|
||||
|
||||
it("that current context path ({{.}}) doesn't hit helpers", function() {
|
||||
|
||||
+195
@@ -65,6 +65,13 @@ describe('blocks', function() {
|
||||
shouldCompileTo(string, hash, 'Goodbye cruel sad OMG!');
|
||||
});
|
||||
|
||||
it('works with cached blocks', function() {
|
||||
var template = CompilerContext.compile('{{#each person}}{{#with .}}{{first}} {{last}}{{/with}}{{/each}}', {data: false});
|
||||
|
||||
var result = template({person: [{first: 'Alan', last: 'Johnson'}, {first: 'Alan', last: 'Johnson'}]});
|
||||
equals(result, 'Alan JohnsonAlan Johnson');
|
||||
});
|
||||
|
||||
describe('inverted sections', function() {
|
||||
it('inverted sections with unset value', function() {
|
||||
var string = '{{#goodbyes}}{{this}}{{/goodbyes}}{{^goodbyes}}Right On!{{/goodbyes}}';
|
||||
@@ -118,6 +125,16 @@ describe('blocks', function() {
|
||||
shouldCompileTo('{{#people}}\n{{name}}\n{{^}}\n{{none}}\n{{/people}}\n', {none: 'No people'},
|
||||
'No people\n');
|
||||
});
|
||||
it('block standalone else sections can be disabled', function() {
|
||||
shouldCompileTo(
|
||||
'{{#people}}\n{{name}}\n{{^}}\n{{none}}\n{{/people}}\n',
|
||||
[{none: 'No people'}, {}, {}, {ignoreStandalone: true}],
|
||||
'\nNo people\n\n');
|
||||
shouldCompileTo(
|
||||
'{{#none}}\n{{.}}\n{{^}}\nFail\n{{/none}}\n',
|
||||
[{none: 'No people'}, {}, {}, {ignoreStandalone: true}],
|
||||
'\nNo people\n\n');
|
||||
});
|
||||
it('block standalone chained else sections', function() {
|
||||
shouldCompileTo('{{#people}}\n{{name}}\n{{else if none}}\n{{none}}\n{{/people}}\n', {none: 'No people'},
|
||||
'No people\n');
|
||||
@@ -149,4 +166,182 @@ describe('blocks', function() {
|
||||
shouldCompileTo(string, [hash, undefined, undefined, true], 'Goodbye cruel ');
|
||||
});
|
||||
});
|
||||
|
||||
describe('decorators', function() {
|
||||
it('should apply mustache decorators', function() {
|
||||
var helpers = {
|
||||
helper: function(options) {
|
||||
return options.fn.run;
|
||||
}
|
||||
};
|
||||
var decorators = {
|
||||
decorator: function(fn) {
|
||||
fn.run = 'success';
|
||||
return fn;
|
||||
}
|
||||
};
|
||||
shouldCompileTo(
|
||||
'{{#helper}}{{*decorator}}{{/helper}}',
|
||||
{hash: {}, helpers: helpers, decorators: decorators},
|
||||
'success');
|
||||
});
|
||||
it('should apply allow undefined return', function() {
|
||||
var helpers = {
|
||||
helper: function(options) {
|
||||
return options.fn() + options.fn.run;
|
||||
}
|
||||
};
|
||||
var decorators = {
|
||||
decorator: function(fn) {
|
||||
fn.run = 'cess';
|
||||
}
|
||||
};
|
||||
shouldCompileTo(
|
||||
'{{#helper}}{{*decorator}}suc{{/helper}}',
|
||||
{hash: {}, helpers: helpers, decorators: decorators},
|
||||
'success');
|
||||
});
|
||||
|
||||
it('should apply block decorators', function() {
|
||||
var helpers = {
|
||||
helper: function(options) {
|
||||
return options.fn.run;
|
||||
}
|
||||
};
|
||||
var decorators = {
|
||||
decorator: function(fn, props, container, options) {
|
||||
fn.run = options.fn();
|
||||
return fn;
|
||||
}
|
||||
};
|
||||
shouldCompileTo(
|
||||
'{{#helper}}{{#*decorator}}success{{/decorator}}{{/helper}}',
|
||||
{hash: {}, helpers: helpers, decorators: decorators},
|
||||
'success');
|
||||
});
|
||||
it('should support nested decorators', function() {
|
||||
var helpers = {
|
||||
helper: function(options) {
|
||||
return options.fn.run;
|
||||
}
|
||||
};
|
||||
var decorators = {
|
||||
decorator: function(fn, props, container, options) {
|
||||
fn.run = options.fn.nested + options.fn();
|
||||
return fn;
|
||||
},
|
||||
nested: function(fn, props, container, options) {
|
||||
props.nested = options.fn();
|
||||
}
|
||||
};
|
||||
shouldCompileTo(
|
||||
'{{#helper}}{{#*decorator}}{{#*nested}}suc{{/nested}}cess{{/decorator}}{{/helper}}',
|
||||
{hash: {}, helpers: helpers, decorators: decorators},
|
||||
'success');
|
||||
});
|
||||
|
||||
it('should apply multiple decorators', function() {
|
||||
var helpers = {
|
||||
helper: function(options) {
|
||||
return options.fn.run;
|
||||
}
|
||||
};
|
||||
var decorators = {
|
||||
decorator: function(fn, props, container, options) {
|
||||
fn.run = (fn.run || '') + options.fn();
|
||||
return fn;
|
||||
}
|
||||
};
|
||||
shouldCompileTo(
|
||||
'{{#helper}}{{#*decorator}}suc{{/decorator}}{{#*decorator}}cess{{/decorator}}{{/helper}}',
|
||||
{hash: {}, helpers: helpers, decorators: decorators},
|
||||
'success');
|
||||
});
|
||||
|
||||
it('should access parent variables', function() {
|
||||
var helpers = {
|
||||
helper: function(options) {
|
||||
return options.fn.run;
|
||||
}
|
||||
};
|
||||
var decorators = {
|
||||
decorator: function(fn, props, container, options) {
|
||||
fn.run = options.args;
|
||||
return fn;
|
||||
}
|
||||
};
|
||||
shouldCompileTo(
|
||||
'{{#helper}}{{*decorator foo}}{{/helper}}',
|
||||
{hash: {'foo': 'success'}, helpers: helpers, decorators: decorators},
|
||||
'success');
|
||||
});
|
||||
it('should work with root program', function() {
|
||||
var run;
|
||||
var decorators = {
|
||||
decorator: function(fn, props, container, options) {
|
||||
equals(options.args[0], 'success');
|
||||
run = true;
|
||||
return fn;
|
||||
}
|
||||
};
|
||||
shouldCompileTo(
|
||||
'{{*decorator "success"}}',
|
||||
{hash: {'foo': 'success'}, decorators: decorators},
|
||||
'');
|
||||
equals(run, true);
|
||||
});
|
||||
it('should fail when accessing variables from root', function() {
|
||||
var run;
|
||||
var decorators = {
|
||||
decorator: function(fn, props, container, options) {
|
||||
equals(options.args[0], undefined);
|
||||
run = true;
|
||||
return fn;
|
||||
}
|
||||
};
|
||||
shouldCompileTo(
|
||||
'{{*decorator foo}}',
|
||||
{hash: {'foo': 'fail'}, decorators: decorators},
|
||||
'');
|
||||
equals(run, true);
|
||||
});
|
||||
|
||||
describe('registration', function() {
|
||||
it('unregisters', function() {
|
||||
handlebarsEnv.decorators = {};
|
||||
|
||||
handlebarsEnv.registerDecorator('foo', function() {
|
||||
return 'fail';
|
||||
});
|
||||
|
||||
equals(!!handlebarsEnv.decorators.foo, true);
|
||||
handlebarsEnv.unregisterDecorator('foo');
|
||||
equals(handlebarsEnv.decorators.foo, undefined);
|
||||
});
|
||||
|
||||
it('allows multiple globals', function() {
|
||||
handlebarsEnv.decorators = {};
|
||||
|
||||
handlebarsEnv.registerDecorator({
|
||||
foo: function() {},
|
||||
bar: function() {}
|
||||
});
|
||||
|
||||
equals(!!handlebarsEnv.decorators.foo, true);
|
||||
equals(!!handlebarsEnv.decorators.bar, true);
|
||||
handlebarsEnv.unregisterDecorator('foo');
|
||||
handlebarsEnv.unregisterDecorator('bar');
|
||||
equals(handlebarsEnv.decorators.foo, undefined);
|
||||
equals(handlebarsEnv.decorators.bar, undefined);
|
||||
});
|
||||
it('fails with multiple and args', function() {
|
||||
shouldThrow(function() {
|
||||
handlebarsEnv.registerDecorator({
|
||||
world: function() { return 'world!'; },
|
||||
testHelper: function() { return 'found it!'; }
|
||||
}, {});
|
||||
}, Error, 'Arg not supported with multiple decorators');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
+95
-4
@@ -32,6 +32,11 @@ describe('builtin helpers', function() {
|
||||
shouldCompileTo(string, {goodbye: function() {return this.foo; }, world: 'world'}, 'cruel world!',
|
||||
'if with function does not show the contents when returns undefined');
|
||||
});
|
||||
|
||||
it('should not change the depth list', function() {
|
||||
var string = '{{#with foo}}{{#if goodbye}}GOODBYE cruel {{../world}}!{{/if}}{{/with}}';
|
||||
shouldCompileTo(string, {foo: {goodbye: true}, world: 'world'}, 'GOODBYE cruel world!');
|
||||
});
|
||||
});
|
||||
|
||||
describe('#with', function() {
|
||||
@@ -47,6 +52,16 @@ describe('builtin helpers', function() {
|
||||
var string = '{{#with person}}Person is present{{else}}Person is not present{{/with}}';
|
||||
shouldCompileTo(string, {}, 'Person is not present');
|
||||
});
|
||||
it('with provides block parameter', function() {
|
||||
var string = '{{#with person as |foo|}}{{foo.first}} {{last}}{{/with}}';
|
||||
shouldCompileTo(string, {person: {first: 'Alan', last: 'Johnson'}}, 'Alan Johnson');
|
||||
});
|
||||
it('works when data is disabled', function() {
|
||||
var template = CompilerContext.compile('{{#with person as |foo|}}{{foo.first}} {{last}}{{/with}}', {data: false});
|
||||
|
||||
var result = template({person: {first: 'Alan', last: 'Johnson'}});
|
||||
equals(result, 'Alan Johnson');
|
||||
});
|
||||
});
|
||||
|
||||
describe('#each', function() {
|
||||
@@ -210,6 +225,16 @@ describe('builtin helpers', function() {
|
||||
'each with array function argument ignores the contents when empty');
|
||||
});
|
||||
|
||||
it('each object when last key is an empty string', function() {
|
||||
var string = '{{#each goodbyes}}{{@index}}. {{text}}! {{/each}}cruel {{world}}!';
|
||||
var hash = {goodbyes: {'a': {text: 'goodbye'}, b: {text: 'Goodbye'}, '': {text: 'GOODBYE'}}, world: 'world'};
|
||||
|
||||
var template = CompilerContext.compile(string);
|
||||
var result = template(hash);
|
||||
|
||||
equal(result, '0. goodbye! 1. Goodbye! 2. GOODBYE! cruel world!', 'Empty string key is not skipped');
|
||||
});
|
||||
|
||||
it('data passed to helpers', function() {
|
||||
var string = '{{#each letters}}{{this}}{{detectDataInsideEach}}{{/each}}';
|
||||
var hash = {letters: ['a', 'b', 'c']};
|
||||
@@ -232,7 +257,7 @@ describe('builtin helpers', function() {
|
||||
});
|
||||
|
||||
describe('#log', function() {
|
||||
/*eslint-disable no-console */
|
||||
/* eslint-disable no-console */
|
||||
if (typeof console === 'undefined') {
|
||||
return;
|
||||
}
|
||||
@@ -276,7 +301,7 @@ describe('builtin helpers', function() {
|
||||
};
|
||||
|
||||
shouldCompileTo(string, [hash,,,, {level: '03'}], '');
|
||||
equals(3, levelArg);
|
||||
equals('03', levelArg);
|
||||
equals('whee', logArg);
|
||||
});
|
||||
it('should output to info', function() {
|
||||
@@ -311,13 +336,79 @@ describe('builtin helpers', function() {
|
||||
});
|
||||
it('should handle missing logger', function() {
|
||||
var string = '{{log blah}}';
|
||||
var hash = { blah: 'whee' };
|
||||
var hash = { blah: 'whee' },
|
||||
called = false;
|
||||
|
||||
console.error = undefined;
|
||||
console.log = function(log) {
|
||||
equals('whee', log);
|
||||
called = true;
|
||||
};
|
||||
|
||||
shouldCompileTo(string, [hash,,,, {level: '03'}], '');
|
||||
equals(true, called);
|
||||
});
|
||||
/*eslint-enable no-console */
|
||||
|
||||
it('should handle string log levels', function() {
|
||||
var string = '{{log blah}}';
|
||||
var hash = { blah: 'whee' };
|
||||
var called;
|
||||
|
||||
console.error = function(log) {
|
||||
equals('whee', log);
|
||||
called = true;
|
||||
};
|
||||
|
||||
shouldCompileTo(string, [hash,,,, {level: 'error'}], '');
|
||||
equals(true, called);
|
||||
|
||||
called = false;
|
||||
|
||||
shouldCompileTo(string, [hash,,,, {level: 'ERROR'}], '');
|
||||
equals(true, called);
|
||||
});
|
||||
it('should handle hash log levels', function() {
|
||||
var string = '{{log blah level="error"}}';
|
||||
var hash = { blah: 'whee' };
|
||||
var called;
|
||||
|
||||
console.error = function(log) {
|
||||
equals('whee', log);
|
||||
called = true;
|
||||
};
|
||||
|
||||
shouldCompileTo(string, hash, '');
|
||||
equals(true, called);
|
||||
});
|
||||
it('should handle hash log levels', function() {
|
||||
var string = '{{log blah level="debug"}}';
|
||||
var hash = { blah: 'whee' };
|
||||
var called = false;
|
||||
|
||||
console.info = console.log = console.error = console.debug = function(log) {
|
||||
equals('whee', log);
|
||||
called = true;
|
||||
};
|
||||
|
||||
shouldCompileTo(string, hash, '');
|
||||
equals(false, called);
|
||||
});
|
||||
it('should pass multiple log arguments', function() {
|
||||
var string = '{{log blah "foo" 1}}';
|
||||
var hash = { blah: 'whee' };
|
||||
var called;
|
||||
|
||||
console.info = console.log = function(log1, log2, log3) {
|
||||
equals('whee', log1);
|
||||
equals('foo', log2);
|
||||
equals(1, log3);
|
||||
called = true;
|
||||
};
|
||||
|
||||
shouldCompileTo(string, hash, '');
|
||||
equals(true, called);
|
||||
});
|
||||
/* eslint-enable no-console */
|
||||
});
|
||||
|
||||
|
||||
|
||||
+45
-2
@@ -38,13 +38,53 @@ describe('compiler', function() {
|
||||
}, Error, 'You must pass a string or Handlebars AST to Handlebars.compile. You passed [object Object]');
|
||||
});
|
||||
|
||||
it('should include the location in the error (row and column)', function() {
|
||||
try {
|
||||
Handlebars.compile(' \n {{#if}}\n{{/def}}')();
|
||||
equal(true, false, 'Statement must throw exception. This line should not be executed.');
|
||||
} catch (err) {
|
||||
equal(err.message, 'if doesn\'t match def - 2:5', 'Checking error message');
|
||||
if (Object.getOwnPropertyDescriptor(err, 'column').writable) {
|
||||
// In Safari 8, the column-property is read-only. This means that even if it is set with defineProperty,
|
||||
// its value won't change (https://github.com/jquery/esprima/issues/1290#issuecomment-132455482)
|
||||
// Since this was neither working in Handlebars 3 nor in 4.0.5, we only check the column for other browsers.
|
||||
equal(err.column, 5, 'Checking error column');
|
||||
}
|
||||
equal(err.lineNumber, 2, 'Checking error row');
|
||||
}
|
||||
});
|
||||
|
||||
it('should include the location as enumerable property', function() {
|
||||
try {
|
||||
Handlebars.compile(' \n {{#if}}\n{{/def}}')();
|
||||
equal(true, false, 'Statement must throw exception. This line should not be executed.');
|
||||
} catch (err) {
|
||||
equal(err.propertyIsEnumerable('column'), true, 'Checking error column');
|
||||
}
|
||||
});
|
||||
|
||||
it('can utilize AST instance', function() {
|
||||
equal(Handlebars.compile(new Handlebars.AST.Program([ new Handlebars.AST.ContentStatement('Hello')], null, {}))(), 'Hello');
|
||||
equal(Handlebars.compile({
|
||||
type: 'Program',
|
||||
body: [ {type: 'ContentStatement', value: 'Hello'}]
|
||||
})(), 'Hello');
|
||||
});
|
||||
|
||||
it('can pass through an empty string', function() {
|
||||
equal(Handlebars.compile('')(), '');
|
||||
});
|
||||
|
||||
it('should not modify the options.data property(GH-1327)', function() {
|
||||
var options = {data: [{a: 'foo'}, {a: 'bar'}]};
|
||||
Handlebars.compile('{{#each data}}{{@index}}:{{a}} {{/each}}', options)();
|
||||
equal(JSON.stringify(options, 0, 2), JSON.stringify({data: [{a: 'foo'}, {a: 'bar'}]}, 0, 2));
|
||||
});
|
||||
|
||||
it('should not modify the options.knownHelpers property(GH-1327)', function() {
|
||||
var options = {knownHelpers: {}};
|
||||
Handlebars.compile('{{#each data}}{{@index}}:{{a}} {{/each}}', options)();
|
||||
equal(JSON.stringify(options, 0, 2), JSON.stringify({knownHelpers: {}}, 0, 2));
|
||||
});
|
||||
});
|
||||
|
||||
describe('#precompile', function() {
|
||||
@@ -58,7 +98,10 @@ describe('compiler', function() {
|
||||
});
|
||||
|
||||
it('can utilize AST instance', function() {
|
||||
equal(/return "Hello"/.test(Handlebars.precompile(new Handlebars.AST.Program([ new Handlebars.AST.ContentStatement('Hello')]), null, {})), true);
|
||||
equal(/return "Hello"/.test(Handlebars.precompile({
|
||||
type: 'Program',
|
||||
body: [ {type: 'ContentStatement', value: 'Hello'}]
|
||||
})), true);
|
||||
});
|
||||
|
||||
it('can pass through an empty string', function() {
|
||||
|
||||
Vendored
+8
-3
@@ -4,7 +4,12 @@ var fs = require('fs'),
|
||||
vm = require('vm');
|
||||
|
||||
global.Handlebars = 'no-conflict';
|
||||
vm.runInThisContext(fs.readFileSync(__dirname + '/../../dist/handlebars.js'), 'dist/handlebars.js');
|
||||
|
||||
var filename = 'dist/handlebars.js';
|
||||
if (global.minimizedTest) {
|
||||
filename = 'dist/handlebars.min.js';
|
||||
}
|
||||
vm.runInThisContext(fs.readFileSync(__dirname + '/../../' + filename), filename);
|
||||
|
||||
global.CompilerContext = {
|
||||
browser: true,
|
||||
@@ -19,12 +24,12 @@ global.CompilerContext = {
|
||||
};
|
||||
|
||||
function safeEval(templateSpec) {
|
||||
/*eslint-disable no-eval, no-console */
|
||||
/* eslint-disable no-eval, no-console */
|
||||
try {
|
||||
return eval('(' + templateSpec + ')');
|
||||
} catch (err) {
|
||||
console.error(templateSpec);
|
||||
throw err;
|
||||
}
|
||||
/*eslint-enable no-eval, no-console */
|
||||
/* eslint-enable no-eval, no-console */
|
||||
}
|
||||
|
||||
Vendored
+30
-11
@@ -1,11 +1,27 @@
|
||||
var AssertError;
|
||||
if (Error.captureStackTrace) {
|
||||
AssertError = function AssertError(message, caller) {
|
||||
Error.prototype.constructor.call(this, message);
|
||||
this.message = message;
|
||||
|
||||
if (Error.captureStackTrace) {
|
||||
Error.captureStackTrace(this, caller || AssertError);
|
||||
}
|
||||
};
|
||||
|
||||
AssertError.prototype = new Error();
|
||||
} else {
|
||||
AssertError = Error;
|
||||
}
|
||||
|
||||
global.shouldCompileTo = function(string, hashOrArray, expected, message) {
|
||||
shouldCompileToWithPartials(string, hashOrArray, false, expected, message);
|
||||
};
|
||||
|
||||
global.shouldCompileToWithPartials = function(string, hashOrArray, partials, expected, message) {
|
||||
global.shouldCompileToWithPartials = function shouldCompileToWithPartials(string, hashOrArray, partials, expected, message) {
|
||||
var result = compileWithPartials(string, hashOrArray, partials);
|
||||
if (result !== expected) {
|
||||
throw new Error("'" + result + "' should === '" + expected + "': " + message);
|
||||
throw new AssertError("'" + result + "' should === '" + expected + "': " + message, shouldCompileToWithPartials);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -13,7 +29,10 @@ global.compileWithPartials = function(string, hashOrArray, partials) {
|
||||
var template,
|
||||
ary,
|
||||
options;
|
||||
if (Object.prototype.toString.call(hashOrArray) === '[object Array]') {
|
||||
if (hashOrArray && hashOrArray.hash) {
|
||||
ary = [hashOrArray.hash, hashOrArray];
|
||||
delete hashOrArray.hash;
|
||||
} else if (Object.prototype.toString.call(hashOrArray) === '[object Array]') {
|
||||
ary = [];
|
||||
ary.push(hashOrArray[0]);
|
||||
ary.push({ helpers: hashOrArray[1], partials: hashOrArray[2] });
|
||||
@@ -31,9 +50,9 @@ global.compileWithPartials = function(string, hashOrArray, partials) {
|
||||
};
|
||||
|
||||
|
||||
global.equals = global.equal = function(a, b, msg) {
|
||||
global.equals = global.equal = function equals(a, b, msg) {
|
||||
if (a !== b) {
|
||||
throw new Error("'" + a + "' should === '" + b + "'" + (msg ? ': ' + msg : ''));
|
||||
throw new AssertError("'" + a + "' should === '" + b + "'" + (msg ? ': ' + msg : ''), equals);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -42,15 +61,15 @@ global.shouldThrow = function(callback, type, msg) {
|
||||
try {
|
||||
callback();
|
||||
failed = true;
|
||||
} catch (err) {
|
||||
if (type && !(err instanceof type)) {
|
||||
throw new Error('Type failure: ' + err);
|
||||
} catch (caught) {
|
||||
if (type && !(caught instanceof type)) {
|
||||
throw new AssertError('Type failure: ' + caught);
|
||||
}
|
||||
if (msg && !(msg.test ? msg.test(err.message) : msg === err.message)) {
|
||||
equal(msg, err.message);
|
||||
if (msg && !(msg.test ? msg.test(caught.message) : msg === caught.message)) {
|
||||
throw new AssertError('Throw mismatch: Expected ' + caught.message + ' to match ' + msg + '\n\n' + caught.stack, shouldThrow);
|
||||
}
|
||||
}
|
||||
if (failed) {
|
||||
throw new Error('It failed to throw');
|
||||
throw new AssertError('It failed to throw', shouldThrow);
|
||||
}
|
||||
};
|
||||
|
||||
Vendored
+2
-2
@@ -13,12 +13,12 @@ global.CompilerContext = {
|
||||
};
|
||||
|
||||
function safeEval(templateSpec) {
|
||||
/*eslint-disable no-eval, no-console */
|
||||
/* eslint-disable no-eval, no-console */
|
||||
try {
|
||||
return eval('(' + templateSpec + ')');
|
||||
} catch (err) {
|
||||
console.error(templateSpec);
|
||||
throw err;
|
||||
}
|
||||
/*eslint-enable no-eval, no-console */
|
||||
/* eslint-enable no-eval, no-console */
|
||||
}
|
||||
|
||||
Vendored
+22
-8
@@ -1,4 +1,4 @@
|
||||
/*eslint-disable no-console */
|
||||
/* eslint-disable no-console */
|
||||
var fs = require('fs'),
|
||||
Mocha = require('mocha'),
|
||||
path = require('path');
|
||||
@@ -7,21 +7,35 @@ var errors = 0,
|
||||
testDir = path.dirname(__dirname),
|
||||
grep = process.argv[2];
|
||||
|
||||
var files = [ testDir + '/basic.js' ];
|
||||
// Lazy hack, but whatever
|
||||
if (grep === '--min') {
|
||||
global.minimizedTest = true;
|
||||
grep = undefined;
|
||||
}
|
||||
|
||||
var files = fs.readdirSync(testDir)
|
||||
.filter(function(name) { return (/.*\.js$/).test(name); })
|
||||
.map(function(name) { return testDir + '/' + name; });
|
||||
|
||||
run('./node', function() {
|
||||
run('./browser', function() {
|
||||
run('./runtime', function() {
|
||||
/*eslint-disable no-process-exit */
|
||||
if (global.minimizedTest) {
|
||||
run('./runtime', function() {
|
||||
run('./browser', function() {
|
||||
/* eslint-disable no-process-exit */
|
||||
process.exit(errors);
|
||||
/*eslint-enable no-process-exit */
|
||||
/* eslint-enable no-process-exit */
|
||||
});
|
||||
});
|
||||
});
|
||||
} else {
|
||||
run('./runtime', function() {
|
||||
run('./browser', function() {
|
||||
run('./node', function() {
|
||||
/* eslint-disable no-process-exit */
|
||||
process.exit(errors);
|
||||
/* eslint-enable no-process-exit */
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
function run(env, callback) {
|
||||
|
||||
Vendored
+8
-3
@@ -4,7 +4,12 @@ var fs = require('fs'),
|
||||
vm = require('vm');
|
||||
|
||||
global.Handlebars = 'no-conflict';
|
||||
vm.runInThisContext(fs.readFileSync(__dirname + '/../../dist/handlebars.runtime.js'), 'dist/handlebars.runtime.js');
|
||||
|
||||
var filename = 'dist/handlebars.runtime.js';
|
||||
if (global.minimizedTest) {
|
||||
filename = 'dist/handlebars.runtime.min.js';
|
||||
}
|
||||
vm.runInThisContext(fs.readFileSync(__dirname + '/../../' + filename), filename);
|
||||
|
||||
var parse = require('../../dist/cjs/handlebars/compiler/base').parse;
|
||||
var compiler = require('../../dist/cjs/handlebars/compiler/compiler');
|
||||
@@ -39,12 +44,12 @@ global.CompilerContext = {
|
||||
};
|
||||
|
||||
function safeEval(templateSpec) {
|
||||
/*eslint-disable no-eval, no-console */
|
||||
/* eslint-disable no-eval, no-console */
|
||||
try {
|
||||
return eval('(' + templateSpec + ')');
|
||||
} catch (err) {
|
||||
console.error(templateSpec);
|
||||
throw err;
|
||||
}
|
||||
/*eslint-enable no-eval, no-console */
|
||||
/* eslint-enable no-eval, no-console */
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
define(['handlebars.runtime'], function(Handlebars) {
|
||||
Handlebars = Handlebars["default"]; var template = Handlebars.template, templates = Handlebars.templates = Handlebars.templates || {};
|
||||
return templates['empty'] = template({"compiler":[6,">= 2.0.0-beta.1"],"main":function(depth0,helpers,partials,data) {
|
||||
return templates['empty'] = template({"compiler":[7,">= 4.0.0"],"main":function(container,depth0,helpers,partials,data) {
|
||||
return "";
|
||||
},"useData":true});
|
||||
});
|
||||
|
||||
+24
-1
@@ -28,6 +28,29 @@ describe('helpers', function() {
|
||||
'raw block helper gets raw content');
|
||||
});
|
||||
|
||||
it('helper for nested raw block gets raw content', function() {
|
||||
var string = '{{{{a}}}} {{{{b}}}} {{{{/b}}}} {{{{/a}}}}';
|
||||
var helpers = {
|
||||
a: function(options) {
|
||||
return options.fn();
|
||||
}
|
||||
};
|
||||
shouldCompileTo(string, [{}, helpers], ' {{{{b}}}} {{{{/b}}}} ', 'raw block helper should get nested raw block as raw content');
|
||||
});
|
||||
|
||||
it('helper block with identical context', function() {
|
||||
var string = '{{#goodbyes}}{{name}}{{/goodbyes}}';
|
||||
var hash = {name: 'Alan'};
|
||||
var helpers = {goodbyes: function(options) {
|
||||
var out = '';
|
||||
var byes = ['Goodbye', 'goodbye', 'GOODBYE'];
|
||||
for (var i = 0, j = byes.length; i < j; i++) {
|
||||
out += byes[i] + ' ' + options.fn(this) + '! ';
|
||||
}
|
||||
return out;
|
||||
}};
|
||||
shouldCompileTo(string, [hash, helpers], 'Goodbye Alan! goodbye Alan! GOODBYE Alan! ');
|
||||
});
|
||||
it('helper block with complex lookup expression', function() {
|
||||
var string = '{{#goodbyes}}{{../name}}{{/goodbyes}}';
|
||||
var hash = {name: 'Alan'};
|
||||
@@ -35,7 +58,7 @@ describe('helpers', function() {
|
||||
var out = '';
|
||||
var byes = ['Goodbye', 'goodbye', 'GOODBYE'];
|
||||
for (var i = 0, j = byes.length; i < j; i++) {
|
||||
out += byes[i] + ' ' + options.fn(this) + '! ';
|
||||
out += byes[i] + ' ' + options.fn({}) + '! ';
|
||||
}
|
||||
return out;
|
||||
}};
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Mocha</title>
|
||||
|
||||
@@ -16,9 +16,9 @@ describe('javascript-compiler api', function() {
|
||||
handlebarsEnv.JavaScriptCompiler.prototype.nameLookup = function(parent, name) {
|
||||
return parent + '.bar_' + name;
|
||||
};
|
||||
/*eslint-disable camelcase */
|
||||
/* eslint-disable camelcase */
|
||||
shouldCompileTo('{{foo}}', { bar_foo: 'food' }, 'food');
|
||||
/*eslint-enable camelcase */
|
||||
/* eslint-enable camelcase */
|
||||
});
|
||||
|
||||
// Tests nameLookup dot vs. bracket behavior. Bracket is required in certain cases
|
||||
|
||||
+1
-1
Submodule spec/mustache updated: 72233f3ffd...83b0721610
+61
-2
@@ -39,6 +39,12 @@ describe('parser', function() {
|
||||
it('parses mustaches with - in a path', function() {
|
||||
equals(astFor('{{foo-bar}}'), '{{ PATH:foo-bar [] }}\n');
|
||||
});
|
||||
it('parses mustaches with escaped [] in a path', function() {
|
||||
equals(astFor('{{[foo[\\]]}}'), '{{ PATH:foo[] [] }}\n');
|
||||
});
|
||||
it('parses escaped \\\\ in path', function() {
|
||||
equals(astFor('{{[foo\\\\]}}'), '{{ PATH:foo\\ [] }}\n');
|
||||
});
|
||||
|
||||
it('parses mustaches with parameters', function() {
|
||||
equals(astFor('{{foo bar}}'), '{{ PATH:foo [PATH:bar] }}\n');
|
||||
@@ -65,7 +71,7 @@ describe('parser', function() {
|
||||
equals(astFor('{{foo undefined null}}'), '{{ PATH:foo [UNDEFINED, NULL] }}\n');
|
||||
});
|
||||
|
||||
it('parses mutaches with DATA parameters', function() {
|
||||
it('parses mustaches with DATA parameters', function() {
|
||||
equals(astFor('{{foo @bar}}'), '{{ PATH:foo [@PATH:bar] }}\n');
|
||||
});
|
||||
|
||||
@@ -113,6 +119,18 @@ describe('parser', function() {
|
||||
equals(astFor('{{> shared/partial?.bar}}'), '{{> PARTIAL:shared/partial?.bar }}\n');
|
||||
});
|
||||
|
||||
it('parsers partial blocks', function() {
|
||||
equals(astFor('{{#> foo}}bar{{/foo}}'), '{{> PARTIAL BLOCK:foo PROGRAM:\n CONTENT[ \'bar\' ]\n }}\n');
|
||||
});
|
||||
it('should handle parser block mismatch', function() {
|
||||
shouldThrow(function() {
|
||||
astFor('{{#> goodbyes}}{{/hellos}}');
|
||||
}, Error, (/goodbyes doesn't match hellos/));
|
||||
});
|
||||
it('parsers partial blocks with arguments', function() {
|
||||
equals(astFor('{{#> foo context hash=value}}bar{{/foo}}'), '{{> PARTIAL BLOCK:foo PATH:context HASH{hash=PATH:value} PROGRAM:\n CONTENT[ \'bar\' ]\n }}\n');
|
||||
});
|
||||
|
||||
it('parses a comment', function() {
|
||||
equals(astFor('{{! this is a comment }}'), "{{! ' this is a comment ' }}\n");
|
||||
});
|
||||
@@ -228,7 +246,48 @@ describe('parser', function() {
|
||||
|
||||
describe('externally compiled AST', function() {
|
||||
it('can pass through an already-compiled AST', function() {
|
||||
equals(astFor(new Handlebars.AST.Program([new Handlebars.AST.ContentStatement('Hello')], null)), 'CONTENT[ \'Hello\' ]\n');
|
||||
equals(astFor({
|
||||
type: 'Program',
|
||||
body: [ {type: 'ContentStatement', value: 'Hello'}]
|
||||
}), 'CONTENT[ \'Hello\' ]\n');
|
||||
});
|
||||
});
|
||||
|
||||
describe('directives', function() {
|
||||
it('should parse block directives', function() {
|
||||
equals(astFor('{{#* foo}}{{/foo}}'), 'DIRECTIVE BLOCK:\n PATH:foo []\n PROGRAM:\n');
|
||||
});
|
||||
it('should parse directives', function() {
|
||||
equals(astFor('{{* foo}}'), '{{ DIRECTIVE PATH:foo [] }}\n');
|
||||
});
|
||||
it('should fail if directives have inverse', function() {
|
||||
shouldThrow(function() {
|
||||
astFor('{{#* foo}}{{^}}{{/foo}}');
|
||||
}, Error, /Unexpected inverse/);
|
||||
});
|
||||
});
|
||||
|
||||
it('GH1024 - should track program location properly', function() {
|
||||
var p = Handlebars.parse('\n'
|
||||
+ ' {{#if foo}}\n'
|
||||
+ ' {{bar}}\n'
|
||||
+ ' {{else}} {{baz}}\n'
|
||||
+ '\n'
|
||||
+ ' {{/if}}\n'
|
||||
+ ' ');
|
||||
|
||||
// We really need a deep equals but for now this should be stable...
|
||||
equals(JSON.stringify(p.loc), JSON.stringify({
|
||||
start: { line: 1, column: 0 },
|
||||
end: { line: 7, column: 4 }
|
||||
}));
|
||||
equals(JSON.stringify(p.body[1].program.loc), JSON.stringify({
|
||||
start: { line: 2, column: 13 },
|
||||
end: { line: 4, column: 7 }
|
||||
}));
|
||||
equals(JSON.stringify(p.body[1].inverse.loc), JSON.stringify({
|
||||
start: { line: 4, column: 15 },
|
||||
end: { line: 6, column: 5 }
|
||||
}));
|
||||
});
|
||||
});
|
||||
|
||||
+247
-1
@@ -41,6 +41,28 @@ describe('partials', function() {
|
||||
'Partials can be passed a context');
|
||||
});
|
||||
|
||||
it('partials with no context', function() {
|
||||
var partial = '{{name}} ({{url}}) ';
|
||||
var hash = {dudes: [{name: 'Yehuda', url: 'http://yehuda'}, {name: 'Alan', url: 'http://alan'}]};
|
||||
shouldCompileToWithPartials(
|
||||
'Dudes: {{#dudes}}{{>dude}}{{/dudes}}',
|
||||
[hash, {}, {dude: partial}, {explicitPartialContext: true}],
|
||||
true,
|
||||
'Dudes: () () ');
|
||||
shouldCompileToWithPartials(
|
||||
'Dudes: {{#dudes}}{{>dude name="foo"}}{{/dudes}}',
|
||||
[hash, {}, {dude: partial}, {explicitPartialContext: true}],
|
||||
true,
|
||||
'Dudes: foo () foo () ');
|
||||
});
|
||||
|
||||
it('partials with string context', function() {
|
||||
var string = 'Dudes: {{>dude "dudes"}}';
|
||||
var partial = '{{.}}';
|
||||
var hash = {};
|
||||
shouldCompileToWithPartials(string, [hash, {}, {dude: partial}], true, 'Dudes: dudes');
|
||||
});
|
||||
|
||||
it('partials with undefined context', function() {
|
||||
var string = 'Dudes: {{>dude dudes}}';
|
||||
var partial = '{{foo}} Empty';
|
||||
@@ -81,7 +103,7 @@ describe('partials', function() {
|
||||
shouldThrow(function() {
|
||||
var undef;
|
||||
handlebarsEnv.registerPartial('undefined_test', undef);
|
||||
}, Handlebars.Exception, 'Attempting to register a partial as undefined');
|
||||
}, Handlebars.Exception, 'Attempting to register a partial called "undefined_test" as undefined');
|
||||
});
|
||||
|
||||
it('rendering template partial in vm mode throws an exception', function() {
|
||||
@@ -189,6 +211,224 @@ describe('partials', function() {
|
||||
handlebarsEnv.compile = compile;
|
||||
});
|
||||
|
||||
describe('partial blocks', function() {
|
||||
it('should render partial block as default', function() {
|
||||
shouldCompileToWithPartials(
|
||||
'{{#> dude}}success{{/dude}}',
|
||||
[{}, {}, {}],
|
||||
true,
|
||||
'success');
|
||||
});
|
||||
it('should execute default block with proper context', function() {
|
||||
shouldCompileToWithPartials(
|
||||
'{{#> dude context}}{{value}}{{/dude}}',
|
||||
[{context: {value: 'success'}}, {}, {}],
|
||||
true,
|
||||
'success');
|
||||
});
|
||||
it('should propagate block parameters to default block', function() {
|
||||
shouldCompileToWithPartials(
|
||||
'{{#with context as |me|}}{{#> dude}}{{me.value}}{{/dude}}{{/with}}',
|
||||
[{context: {value: 'success'}}, {}, {}],
|
||||
true,
|
||||
'success');
|
||||
});
|
||||
|
||||
it('should not use partial block if partial exists', function() {
|
||||
shouldCompileToWithPartials(
|
||||
'{{#> dude}}fail{{/dude}}',
|
||||
[{}, {}, {dude: 'success'}],
|
||||
true,
|
||||
'success');
|
||||
});
|
||||
|
||||
it('should render block from partial', function() {
|
||||
shouldCompileToWithPartials(
|
||||
'{{#> dude}}success{{/dude}}',
|
||||
[{}, {}, {dude: '{{> @partial-block }}'}],
|
||||
true,
|
||||
'success');
|
||||
});
|
||||
it('should be able to render the partial-block twice', function() {
|
||||
shouldCompileToWithPartials(
|
||||
'{{#> dude}}success{{/dude}}',
|
||||
[{}, {}, {dude: '{{> @partial-block }} {{> @partial-block }}'}],
|
||||
true,
|
||||
'success success');
|
||||
});
|
||||
it('should render block from partial with context', function() {
|
||||
shouldCompileToWithPartials(
|
||||
'{{#> dude}}{{value}}{{/dude}}',
|
||||
[{context: {value: 'success'}}, {}, {dude: '{{#with context}}{{> @partial-block }}{{/with}}'}],
|
||||
true,
|
||||
'success');
|
||||
});
|
||||
it('should allow the #each-helper to be used along with partial-blocks', function() {
|
||||
shouldCompileToWithPartials(
|
||||
'<template>{{#> list value}}value = {{.}}{{/list}}</template>',
|
||||
[
|
||||
{value: ['a', 'b', 'c']},
|
||||
{},
|
||||
{
|
||||
list: '<list>{{#each .}}<item>{{> @partial-block}}</item>{{/each}}</list>'
|
||||
}
|
||||
],
|
||||
true,
|
||||
'<template><list><item>value = a</item><item>value = b</item><item>value = c</item></list></template>');
|
||||
});
|
||||
it('should render block from partial with context (twice)', function() {
|
||||
shouldCompileToWithPartials(
|
||||
'{{#> dude}}{{value}}{{/dude}}',
|
||||
[
|
||||
{context: {value: 'success'}},
|
||||
{},
|
||||
{
|
||||
dude: '{{#with context}}{{> @partial-block }} {{> @partial-block }}{{/with}}'
|
||||
}
|
||||
],
|
||||
true,
|
||||
'success success');
|
||||
});
|
||||
it('should render block from partial with context', function() {
|
||||
shouldCompileToWithPartials(
|
||||
'{{#> dude}}{{../context/value}}{{/dude}}',
|
||||
[{context: {value: 'success'}}, {}, {dude: '{{#with context}}{{> @partial-block }}{{/with}}'}],
|
||||
true,
|
||||
'success');
|
||||
});
|
||||
it('should render block from partial with block params', function() {
|
||||
shouldCompileToWithPartials(
|
||||
'{{#with context as |me|}}{{#> dude}}{{me.value}}{{/dude}}{{/with}}',
|
||||
[{context: {value: 'success'}}, {}, {dude: '{{> @partial-block }}'}],
|
||||
true,
|
||||
'success');
|
||||
});
|
||||
it('should render nested partial blocks', function() {
|
||||
shouldCompileToWithPartials(
|
||||
'<template>{{#> outer}}{{value}}{{/outer}}</template>',
|
||||
[
|
||||
{value: 'success'},
|
||||
{},
|
||||
{
|
||||
outer: '<outer>{{#> nested}}<outer-block>{{> @partial-block}}</outer-block>{{/nested}}</outer>',
|
||||
nested: '<nested>{{> @partial-block}}</nested>'
|
||||
}
|
||||
],
|
||||
true,
|
||||
'<template><outer><nested><outer-block>success</outer-block></nested></outer></template>');
|
||||
});
|
||||
it('should render nested partial blocks at different nesting levels', function() {
|
||||
shouldCompileToWithPartials(
|
||||
'<template>{{#> outer}}{{value}}{{/outer}}</template>',
|
||||
[
|
||||
{value: 'success'},
|
||||
{},
|
||||
{
|
||||
outer: '<outer>{{#> nested}}<outer-block>{{> @partial-block}}</outer-block>{{/nested}}{{> @partial-block}}</outer>',
|
||||
nested: '<nested>{{> @partial-block}}</nested>'
|
||||
}
|
||||
],
|
||||
true,
|
||||
'<template><outer><nested><outer-block>success</outer-block></nested>success</outer></template>');
|
||||
});
|
||||
it('should render nested partial blocks at different nesting levels (twice)', function() {
|
||||
shouldCompileToWithPartials(
|
||||
'<template>{{#> outer}}{{value}}{{/outer}}</template>',
|
||||
[
|
||||
{value: 'success'},
|
||||
{},
|
||||
{
|
||||
outer: '<outer>{{#> nested}}<outer-block>{{> @partial-block}} {{> @partial-block}}</outer-block>{{/nested}}{{> @partial-block}}+{{> @partial-block}}</outer>',
|
||||
nested: '<nested>{{> @partial-block}}</nested>'
|
||||
}
|
||||
],
|
||||
true,
|
||||
'<template><outer><nested><outer-block>success success</outer-block></nested>success+success</outer></template>');
|
||||
});
|
||||
it('should render nested partial blocks (twice at each level)', function() {
|
||||
shouldCompileToWithPartials(
|
||||
'<template>{{#> outer}}{{value}}{{/outer}}</template>',
|
||||
[
|
||||
{value: 'success'},
|
||||
{},
|
||||
{
|
||||
outer: '<outer>{{#> nested}}<outer-block>{{> @partial-block}} {{> @partial-block}}</outer-block>{{/nested}}</outer>',
|
||||
nested: '<nested>{{> @partial-block}}{{> @partial-block}}</nested>'
|
||||
}
|
||||
],
|
||||
true,
|
||||
'<template><outer>' +
|
||||
'<nested><outer-block>success success</outer-block><outer-block>success success</outer-block></nested>' +
|
||||
'</outer></template>');
|
||||
});
|
||||
});
|
||||
|
||||
describe('inline partials', function() {
|
||||
it('should define inline partials for template', function() {
|
||||
shouldCompileTo('{{#*inline "myPartial"}}success{{/inline}}{{> myPartial}}', {}, 'success');
|
||||
});
|
||||
it('should overwrite multiple partials in the same template', function() {
|
||||
shouldCompileTo('{{#*inline "myPartial"}}fail{{/inline}}{{#*inline "myPartial"}}success{{/inline}}{{> myPartial}}', {}, 'success');
|
||||
});
|
||||
it('should define inline partials for block', function() {
|
||||
shouldCompileTo('{{#with .}}{{#*inline "myPartial"}}success{{/inline}}{{> myPartial}}{{/with}}', {}, 'success');
|
||||
shouldThrow(function() {
|
||||
shouldCompileTo('{{#with .}}{{#*inline "myPartial"}}success{{/inline}}{{/with}}{{> myPartial}}', {}, 'success');
|
||||
}, Error, /myPartial could not/);
|
||||
});
|
||||
it('should override global partials', function() {
|
||||
shouldCompileTo('{{#*inline "myPartial"}}success{{/inline}}{{> myPartial}}', {hash: {}, partials: {myPartial: function() { return 'fail'; }}}, 'success');
|
||||
});
|
||||
it('should override template partials', function() {
|
||||
shouldCompileTo('{{#*inline "myPartial"}}fail{{/inline}}{{#with .}}{{#*inline "myPartial"}}success{{/inline}}{{> myPartial}}{{/with}}', {}, 'success');
|
||||
});
|
||||
it('should override partials down the entire stack', function() {
|
||||
shouldCompileTo('{{#with .}}{{#*inline "myPartial"}}success{{/inline}}{{#with .}}{{#with .}}{{> myPartial}}{{/with}}{{/with}}{{/with}}', {}, 'success');
|
||||
});
|
||||
|
||||
it('should define inline partials for partial call', function() {
|
||||
shouldCompileToWithPartials(
|
||||
'{{#*inline "myPartial"}}success{{/inline}}{{> dude}}',
|
||||
[{}, {}, {dude: '{{> myPartial }}'}],
|
||||
true,
|
||||
'success');
|
||||
});
|
||||
it('should define inline partials in partial block call', function() {
|
||||
shouldCompileToWithPartials(
|
||||
'{{#> dude}}{{#*inline "myPartial"}}success{{/inline}}{{/dude}}',
|
||||
[{}, {}, {dude: '{{> myPartial }}'}],
|
||||
true,
|
||||
'success');
|
||||
});
|
||||
it('should render nested inline partials', function() {
|
||||
shouldCompileToWithPartials(
|
||||
'{{#*inline "outer"}}{{#>inner}}<outer-block>{{>@partial-block}}</outer-block>{{/inner}}{{/inline}}' +
|
||||
'{{#*inline "inner"}}<inner>{{>@partial-block}}</inner>{{/inline}}' +
|
||||
'{{#>outer}}{{value}}{{/outer}}',
|
||||
[{value: 'success'}, {}, {}],
|
||||
true,
|
||||
'<inner><outer-block>success</outer-block></inner>');
|
||||
});
|
||||
it('should render nested inline partials with partial-blocks on different nesting levels', function() {
|
||||
shouldCompileToWithPartials(
|
||||
'{{#*inline "outer"}}{{#>inner}}<outer-block>{{>@partial-block}}</outer-block>{{/inner}}{{>@partial-block}}{{/inline}}' +
|
||||
'{{#*inline "inner"}}<inner>{{>@partial-block}}</inner>{{/inline}}' +
|
||||
'{{#>outer}}{{value}}{{/outer}}',
|
||||
[{value: 'success'}, {}, {}],
|
||||
true,
|
||||
'<inner><outer-block>success</outer-block></inner>success');
|
||||
});
|
||||
it('should render nested inline partials (twice at each level)', function() {
|
||||
shouldCompileToWithPartials(
|
||||
'{{#*inline "outer"}}{{#>inner}}<outer-block>{{>@partial-block}} {{>@partial-block}}</outer-block>{{/inner}}{{/inline}}' +
|
||||
'{{#*inline "inner"}}<inner>{{>@partial-block}}{{>@partial-block}}</inner>{{/inline}}' +
|
||||
'{{#>outer}}{{value}}{{/outer}}',
|
||||
[{value: 'success'}, {}, {}],
|
||||
true,
|
||||
'<inner><outer-block>success success</outer-block><outer-block>success success</outer-block></inner>');
|
||||
});
|
||||
});
|
||||
|
||||
it('should pass compiler flags', function() {
|
||||
if (Handlebars.compile) {
|
||||
var env = Handlebars.create();
|
||||
@@ -231,6 +471,12 @@ describe('partials', function() {
|
||||
var hash = {root: 'yes', dudes: [{name: 'Yehuda', url: 'http://yehuda'}, {name: 'Alan', url: 'http://alan'}]};
|
||||
shouldCompileToWithPartials(string, [hash, {}, {dude: partial}, true], true, 'Dudes: Yehuda (http://yehuda) yes Alan (http://alan) yes ');
|
||||
});
|
||||
it('partials can access parents with custom context', function() {
|
||||
var string = 'Dudes: {{#dudes}}{{> dude "test"}}{{/dudes}}';
|
||||
var partial = '{{name}} ({{url}}) {{root}} ';
|
||||
var hash = {root: 'yes', dudes: [{name: 'Yehuda', url: 'http://yehuda'}, {name: 'Alan', url: 'http://alan'}]};
|
||||
shouldCompileToWithPartials(string, [hash, {}, {dude: partial}, true], true, 'Dudes: Yehuda (http://yehuda) yes Alan (http://alan) yes ');
|
||||
});
|
||||
it('partials can access parents without data', function() {
|
||||
var string = 'Dudes: {{#dudes}}{{> dude}}{{/dudes}}';
|
||||
var partial = '{{name}} ({{url}}) {{root}} ';
|
||||
|
||||
+124
-43
@@ -1,4 +1,4 @@
|
||||
/*eslint-disable no-console */
|
||||
/* eslint-disable no-console */
|
||||
describe('precompiler', function() {
|
||||
// NOP Under non-node environments
|
||||
if (typeof process === 'undefined') {
|
||||
@@ -16,6 +16,12 @@ describe('precompiler', function() {
|
||||
precompile,
|
||||
minify,
|
||||
|
||||
emptyTemplate = {
|
||||
path: __dirname + '/artifacts/empty.handlebars',
|
||||
name: 'empty',
|
||||
source: ''
|
||||
},
|
||||
|
||||
file,
|
||||
content,
|
||||
writeFileSync;
|
||||
@@ -51,10 +57,9 @@ describe('precompiler', function() {
|
||||
Precompiler.cli({templates: []});
|
||||
}, Handlebars.Exception, 'Must define at least one template or directory.');
|
||||
});
|
||||
it('should throw on missing template', function() {
|
||||
shouldThrow(function() {
|
||||
Precompiler.cli({templates: ['foo']});
|
||||
}, Handlebars.Exception, 'Unable to open template file "foo"');
|
||||
it('should handle empty/filtered directories', function() {
|
||||
Precompiler.cli({hasDirectory: true, templates: []});
|
||||
// Success is not throwing
|
||||
});
|
||||
it('should throw when combining simple and minimized', function() {
|
||||
shouldThrow(function() {
|
||||
@@ -66,107 +71,183 @@ describe('precompiler', function() {
|
||||
Precompiler.cli({templates: [__dirname + '/artifacts/empty.handlebars', __dirname + '/artifacts/empty.handlebars'], simple: true});
|
||||
}, Handlebars.Exception, 'Unable to output multiple templates in simple mode');
|
||||
});
|
||||
it('should throw when missing name', function() {
|
||||
shouldThrow(function() {
|
||||
Precompiler.cli({templates: [{source: ''}], amd: true});
|
||||
}, Handlebars.Exception, 'Name missing for template');
|
||||
});
|
||||
it('should throw when combining simple and directories', function() {
|
||||
shouldThrow(function() {
|
||||
Precompiler.cli({templates: [__dirname], simple: true});
|
||||
Precompiler.cli({hasDirectory: true, templates: [1], simple: true});
|
||||
}, Handlebars.Exception, 'Unable to output multiple templates in simple mode');
|
||||
});
|
||||
it('should enumerate directories by extension', function() {
|
||||
Precompiler.cli({templates: [__dirname + '/artifacts'], extension: 'hbs'});
|
||||
equal(/'example_2'/.test(log), true);
|
||||
log = '';
|
||||
|
||||
Precompiler.cli({templates: [__dirname + '/artifacts'], extension: 'handlebars'});
|
||||
equal(/'empty'/.test(log), true);
|
||||
equal(/'example_1'/.test(log), true);
|
||||
});
|
||||
it('should protect from regexp patterns', function() {
|
||||
Precompiler.cli({templates: [__dirname + '/artifacts'], extension: 'hb(s'});
|
||||
// Success is not throwing
|
||||
});
|
||||
it('should output simple templates', function() {
|
||||
Handlebars.precompile = function() { return 'simple'; };
|
||||
Precompiler.cli({templates: [__dirname + '/artifacts/empty.handlebars'], simple: true, extension: 'handlebars'});
|
||||
Precompiler.cli({templates: [emptyTemplate], simple: true});
|
||||
equal(log, 'simple\n');
|
||||
});
|
||||
it('should default to simple templates', function() {
|
||||
Handlebars.precompile = function() { return 'simple'; };
|
||||
Precompiler.cli({templates: [{source: ''}]});
|
||||
equal(log, 'simple\n');
|
||||
});
|
||||
it('should output amd templates', function() {
|
||||
Handlebars.precompile = function() { return 'amd'; };
|
||||
Precompiler.cli({templates: [__dirname + '/artifacts/empty.handlebars'], amd: true, extension: 'handlebars'});
|
||||
Precompiler.cli({templates: [emptyTemplate], amd: true});
|
||||
equal(/template\(amd\)/.test(log), true);
|
||||
});
|
||||
it('should output multiple amd', function() {
|
||||
Handlebars.precompile = function() { return 'amd'; };
|
||||
Precompiler.cli({templates: [__dirname + '/artifacts'], amd: true, extension: 'handlebars', namespace: 'foo'});
|
||||
Precompiler.cli({templates: [emptyTemplate, emptyTemplate], amd: true, namespace: 'foo'});
|
||||
equal(/templates = foo = foo \|\|/.test(log), true);
|
||||
equal(/return templates/.test(log), true);
|
||||
equal(/template\(amd\)/.test(log), true);
|
||||
});
|
||||
it('should output amd partials', function() {
|
||||
Handlebars.precompile = function() { return 'amd'; };
|
||||
Precompiler.cli({templates: [__dirname + '/artifacts/empty.handlebars'], amd: true, partial: true, extension: 'handlebars'});
|
||||
Precompiler.cli({templates: [emptyTemplate], amd: true, partial: true});
|
||||
equal(/return Handlebars\.partials\['empty'\]/.test(log), true);
|
||||
equal(/template\(amd\)/.test(log), true);
|
||||
});
|
||||
it('should output multiple amd partials', function() {
|
||||
Handlebars.precompile = function() { return 'amd'; };
|
||||
Precompiler.cli({templates: [__dirname + '/artifacts'], amd: true, partial: true, extension: 'handlebars'});
|
||||
Precompiler.cli({templates: [emptyTemplate, emptyTemplate], amd: true, partial: true});
|
||||
equal(/return Handlebars\.partials\[/.test(log), false);
|
||||
equal(/template\(amd\)/.test(log), true);
|
||||
});
|
||||
it('should output commonjs templates', function() {
|
||||
Handlebars.precompile = function() { return 'commonjs'; };
|
||||
Precompiler.cli({templates: [__dirname + '/artifacts/empty.handlebars'], commonjs: true, extension: 'handlebars'});
|
||||
Precompiler.cli({templates: [emptyTemplate], commonjs: true});
|
||||
equal(/template\(commonjs\)/.test(log), true);
|
||||
});
|
||||
|
||||
it('should set data flag', function() {
|
||||
Handlebars.precompile = function(data, options) { equal(options.data, true); return 'simple'; };
|
||||
Precompiler.cli({templates: [__dirname + '/artifacts/empty.handlebars'], simple: true, extension: 'handlebars', data: true});
|
||||
Precompiler.cli({templates: [emptyTemplate], simple: true, data: true});
|
||||
equal(log, 'simple\n');
|
||||
});
|
||||
|
||||
it('should set known helpers', function() {
|
||||
Handlebars.precompile = function(data, options) { equal(options.knownHelpers.foo, true); return 'simple'; };
|
||||
Precompiler.cli({templates: [__dirname + '/artifacts/empty.handlebars'], simple: true, extension: 'handlebars', known: 'foo'});
|
||||
equal(log, 'simple\n');
|
||||
});
|
||||
|
||||
it('should handle different root', function() {
|
||||
Handlebars.precompile = function() { return 'simple'; };
|
||||
Precompiler.cli({templates: [__dirname + '/artifacts/empty.handlebars'], simple: true, extension: 'handlebars', root: 'foo/'});
|
||||
Precompiler.cli({templates: [emptyTemplate], simple: true, known: 'foo'});
|
||||
equal(log, 'simple\n');
|
||||
});
|
||||
it('should output to file system', function() {
|
||||
Handlebars.precompile = function() { return 'simple'; };
|
||||
Precompiler.cli({templates: [__dirname + '/artifacts/empty.handlebars'], simple: true, extension: 'handlebars', output: 'file!'});
|
||||
Precompiler.cli({templates: [emptyTemplate], simple: true, output: 'file!'});
|
||||
equal(file, 'file!');
|
||||
equal(content, 'simple\n');
|
||||
equal(log, '');
|
||||
});
|
||||
it('should handle BOM', function() {
|
||||
Handlebars.precompile = function(template) { return template === 'a' ? 'simple' : 'fail'; };
|
||||
Precompiler.cli({templates: [__dirname + '/artifacts/bom.handlebars'], simple: true, extension: 'handlebars', bom: true});
|
||||
equal(log, 'simple\n');
|
||||
});
|
||||
|
||||
it('should output minimized templates', function() {
|
||||
Handlebars.precompile = function() { return 'amd'; };
|
||||
uglify.minify = function() { return {code: 'min'}; };
|
||||
Precompiler.cli({templates: [__dirname + '/artifacts/empty.handlebars'], min: true, extension: 'handlebars'});
|
||||
Precompiler.cli({templates: [emptyTemplate], min: true});
|
||||
equal(log, 'min');
|
||||
});
|
||||
|
||||
it('should output map', function() {
|
||||
Precompiler.cli({templates: [__dirname + '/artifacts/empty.handlebars'], map: 'foo.js.map', extension: 'handlebars'});
|
||||
Precompiler.cli({templates: [emptyTemplate], map: 'foo.js.map'});
|
||||
|
||||
equal(file, 'foo.js.map');
|
||||
equal(/sourceMappingURL=/.test(log), true);
|
||||
equal(log.match(/sourceMappingURL=/g).length, 1);
|
||||
});
|
||||
|
||||
it('should output map', function() {
|
||||
Precompiler.cli({templates: [__dirname + '/artifacts/empty.handlebars'], min: true, map: 'foo.js.map', extension: 'handlebars'});
|
||||
Precompiler.cli({templates: [emptyTemplate], min: true, map: 'foo.js.map'});
|
||||
|
||||
equal(file, 'foo.js.map');
|
||||
equal(/sourceMappingURL=/.test(log), true);
|
||||
equal(log.match(/sourceMappingURL=/g).length, 1);
|
||||
});
|
||||
|
||||
describe('#loadTemplates', function() {
|
||||
it('should throw on missing template', function(done) {
|
||||
Precompiler.loadTemplates({files: ['foo']}, function(err) {
|
||||
equal(err.message, 'Unable to open template file "foo"');
|
||||
done();
|
||||
});
|
||||
});
|
||||
it('should enumerate directories by extension', function(done) {
|
||||
Precompiler.loadTemplates({files: [__dirname + '/artifacts'], extension: 'hbs'}, function(err, opts) {
|
||||
equal(opts.templates.length, 1);
|
||||
equal(opts.templates[0].name, 'example_2');
|
||||
done(err);
|
||||
});
|
||||
});
|
||||
it('should enumerate all templates by extension', function(done) {
|
||||
Precompiler.loadTemplates({files: [__dirname + '/artifacts'], extension: 'handlebars'}, function(err, opts) {
|
||||
equal(opts.templates.length, 3);
|
||||
equal(opts.templates[0].name, 'bom');
|
||||
equal(opts.templates[1].name, 'empty');
|
||||
equal(opts.templates[2].name, 'example_1');
|
||||
done(err);
|
||||
});
|
||||
});
|
||||
it('should handle regular expression characters in extensions', function(done) {
|
||||
Precompiler.loadTemplates({files: [__dirname + '/artifacts'], extension: 'hb(s'}, function(err) {
|
||||
// Success is not throwing
|
||||
done(err);
|
||||
});
|
||||
});
|
||||
it('should handle BOM', function(done) {
|
||||
var opts = {files: [__dirname + '/artifacts/bom.handlebars'], extension: 'handlebars', bom: true};
|
||||
Precompiler.loadTemplates(opts, function(err, opts) {
|
||||
equal(opts.templates[0].source, 'a');
|
||||
done(err);
|
||||
});
|
||||
});
|
||||
|
||||
it('should handle different root', function(done) {
|
||||
var opts = {files: [__dirname + '/artifacts/empty.handlebars'], simple: true, root: 'foo/'};
|
||||
Precompiler.loadTemplates(opts, function(err, opts) {
|
||||
equal(opts.templates[0].name, __dirname + '/artifacts/empty');
|
||||
done(err);
|
||||
});
|
||||
});
|
||||
|
||||
it('should accept string inputs', function(done) {
|
||||
var opts = {string: ''};
|
||||
Precompiler.loadTemplates(opts, function(err, opts) {
|
||||
equal(opts.templates[0].name, undefined);
|
||||
equal(opts.templates[0].source, '');
|
||||
done(err);
|
||||
});
|
||||
});
|
||||
it('should accept string array inputs', function(done) {
|
||||
var opts = {string: ['', 'bar'], name: ['beep', 'boop']};
|
||||
Precompiler.loadTemplates(opts, function(err, opts) {
|
||||
equal(opts.templates[0].name, 'beep');
|
||||
equal(opts.templates[0].source, '');
|
||||
equal(opts.templates[1].name, 'boop');
|
||||
equal(opts.templates[1].source, 'bar');
|
||||
done(err);
|
||||
});
|
||||
});
|
||||
it('should accept stdin input', function(done) {
|
||||
var stdin = require('mock-stdin').stdin();
|
||||
Precompiler.loadTemplates({string: '-'}, function(err, opts) {
|
||||
equal(opts.templates[0].source, 'foo');
|
||||
done(err);
|
||||
});
|
||||
stdin.send('fo');
|
||||
stdin.send('o');
|
||||
stdin.end();
|
||||
});
|
||||
it('error on name missing', function(done) {
|
||||
var opts = {string: ['', 'bar']};
|
||||
Precompiler.loadTemplates(opts, function(err) {
|
||||
equal(err.message, 'Number of names did not match the number of string inputs');
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should complete when no args are passed', function(done) {
|
||||
Precompiler.loadTemplates({}, function(err, opts) {
|
||||
equal(opts.templates.length, 0);
|
||||
done(err);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
+121
-2
@@ -103,13 +103,13 @@ describe('Regressions', function() {
|
||||
});
|
||||
|
||||
it('GH-534: Object prototype aliases', function() {
|
||||
/*eslint-disable no-extend-native */
|
||||
/* eslint-disable no-extend-native */
|
||||
Object.prototype[0xD834] = true;
|
||||
|
||||
shouldCompileTo('{{foo}}', { foo: 'bar' }, 'bar');
|
||||
|
||||
delete Object.prototype[0xD834];
|
||||
/*eslint-enable no-extend-native */
|
||||
/* eslint-enable no-extend-native */
|
||||
});
|
||||
|
||||
it('GH-437: Matching escaping', function() {
|
||||
@@ -172,4 +172,123 @@ describe('Regressions', function() {
|
||||
var result = template(context);
|
||||
equals(result, 'foo');
|
||||
});
|
||||
|
||||
it('GH-1021: Each empty string key', function() {
|
||||
var data = {
|
||||
'': 'foo',
|
||||
'name': 'Chris',
|
||||
'value': 10000
|
||||
};
|
||||
|
||||
shouldCompileTo('{{#each data}}Key: {{@key}}\n{{/each}}', {data: data}, 'Key: \nKey: name\nKey: value\n');
|
||||
});
|
||||
|
||||
it('GH-1054: Should handle simple safe string responses', function() {
|
||||
var root = '{{#wrap}}{{>partial}}{{/wrap}}';
|
||||
var partials = {
|
||||
partial: '{{#wrap}}<partial>{{/wrap}}'
|
||||
};
|
||||
var helpers = {
|
||||
wrap: function(options) {
|
||||
return new Handlebars.SafeString(options.fn());
|
||||
}
|
||||
};
|
||||
|
||||
shouldCompileToWithPartials(root, [{}, helpers, partials], true, '<partial>');
|
||||
});
|
||||
|
||||
it('GH-1065: Sparse arrays', function() {
|
||||
var array = [];
|
||||
array[1] = 'foo';
|
||||
array[3] = 'bar';
|
||||
shouldCompileTo('{{#each array}}{{@index}}{{.}}{{/each}}', {array: array}, '1foo3bar');
|
||||
});
|
||||
|
||||
it('GH-1093: Undefined helper context', function() {
|
||||
var obj = {foo: undefined, bar: 'bat'};
|
||||
var helpers = {
|
||||
helper: function() {
|
||||
// It's valid to execute a block against an undefined context, but
|
||||
// helpers can not do so, so we expect to have an empty object here;
|
||||
for (var name in this) {
|
||||
if (this.hasOwnProperty(name)) {
|
||||
return 'found';
|
||||
}
|
||||
}
|
||||
// And to make IE happy, check for the known string as length is not enumerated.
|
||||
return (this == 'bat' ? 'found' : 'not');
|
||||
}
|
||||
};
|
||||
|
||||
shouldCompileTo('{{#each obj}}{{{helper}}}{{.}}{{/each}}', [{obj: obj}, helpers], 'notfoundbat');
|
||||
});
|
||||
|
||||
it('should support multiple levels of inline partials', function() {
|
||||
var string = '{{#> layout}}{{#*inline "subcontent"}}subcontent{{/inline}}{{/layout}}';
|
||||
var partials = {
|
||||
doctype: 'doctype{{> content}}',
|
||||
layout: '{{#> doctype}}{{#*inline "content"}}layout{{> subcontent}}{{/inline}}{{/doctype}}'
|
||||
};
|
||||
shouldCompileToWithPartials(string, [{}, {}, partials], true, 'doctypelayoutsubcontent');
|
||||
});
|
||||
it('GH-1089: should support failover content in multiple levels of inline partials', function() {
|
||||
var string = '{{#> layout}}{{/layout}}';
|
||||
var partials = {
|
||||
doctype: 'doctype{{> content}}',
|
||||
layout: '{{#> doctype}}{{#*inline "content"}}layout{{#> subcontent}}subcontent{{/subcontent}}{{/inline}}{{/doctype}}'
|
||||
};
|
||||
shouldCompileToWithPartials(string, [{}, {}, partials], true, 'doctypelayoutsubcontent');
|
||||
});
|
||||
it('GH-1099: should support greater than 3 nested levels of inline partials', function() {
|
||||
var string = '{{#> layout}}Outer{{/layout}}';
|
||||
var partials = {
|
||||
layout: '{{#> inner}}Inner{{/inner}}{{> @partial-block }}',
|
||||
inner: ''
|
||||
};
|
||||
shouldCompileToWithPartials(string, [{}, {}, partials], true, 'Outer');
|
||||
});
|
||||
|
||||
it('GH-1135 : Context handling within each iteration', function() {
|
||||
var obj = {array: [1], name: 'John'};
|
||||
var helpers = {
|
||||
myif: function(conditional, options) {
|
||||
if (conditional) {
|
||||
return options.fn(this);
|
||||
} else {
|
||||
return options.inverse(this);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
shouldCompileTo(
|
||||
'{{#each array}}\n'
|
||||
+ ' 1. IF: {{#if true}}{{../name}}-{{../../name}}-{{../../../name}}{{/if}}\n'
|
||||
+ ' 2. MYIF: {{#myif true}}{{../name}}={{../../name}}={{../../../name}}{{/myif}}\n'
|
||||
+ '{{/each}}', [obj, helpers],
|
||||
' 1. IF: John--\n'
|
||||
+ ' 2. MYIF: John==\n');
|
||||
});
|
||||
|
||||
it('GH-1186: Support block params for existing programs', function() {
|
||||
var string =
|
||||
'{{#*inline "test"}}{{> @partial-block }}{{/inline}}'
|
||||
+ '{{#>test }}{{#each listOne as |item|}}{{ item }}{{/each}}{{/test}}'
|
||||
+ '{{#>test }}{{#each listTwo as |item|}}{{ item }}{{/each}}{{/test}}';
|
||||
|
||||
shouldCompileTo(string, { listOne: ['a'], listTwo: ['b']}, 'ab', '');
|
||||
});
|
||||
|
||||
it('GH-1319: "unless" breaks when "each" value equals "null"', function() {
|
||||
var string = '{{#each list}}{{#unless ./prop}}parent={{../value}} {{/unless}}{{/each}}';
|
||||
shouldCompileTo(string, { value: 'parent', list: [ null, 'a'] }, 'parent=parent parent=parent ', '');
|
||||
});
|
||||
|
||||
it('GH-1341: 4.0.7 release breaks {{#if @partial-block}} usage', function() {
|
||||
var string = 'template {{>partial}} template';
|
||||
var partials = {
|
||||
partialWithBlock: '{{#if @partial-block}} block {{> @partial-block}} block {{/if}}',
|
||||
partial: '{{#> partialWithBlock}} partial {{/partialWithBlock}}'
|
||||
};
|
||||
shouldCompileToWithPartials(string, [{}, {}, partials], true, 'template block partial block template');
|
||||
});
|
||||
});
|
||||
|
||||
+3
-3
@@ -14,19 +14,19 @@ describe('runtime', function() {
|
||||
it('should throw on version mismatch', function() {
|
||||
shouldThrow(function() {
|
||||
Handlebars.template({
|
||||
main: true,
|
||||
main: {},
|
||||
compiler: [Handlebars.COMPILER_REVISION + 1]
|
||||
});
|
||||
}, Error, /Template was precompiled with a newer version of Handlebars than the current runtime/);
|
||||
shouldThrow(function() {
|
||||
Handlebars.template({
|
||||
main: true,
|
||||
main: {},
|
||||
compiler: [Handlebars.COMPILER_REVISION - 1]
|
||||
});
|
||||
}, Error, /Template was precompiled with an older version of Handlebars than the current runtime/);
|
||||
shouldThrow(function() {
|
||||
Handlebars.template({
|
||||
main: true
|
||||
main: {}
|
||||
});
|
||||
}, Error, /Template was precompiled with an older version of Handlebars than the current runtime/);
|
||||
});
|
||||
|
||||
+2
-2
@@ -34,9 +34,9 @@ describe('spec', function() {
|
||||
var data = _.clone(test.data);
|
||||
if (data.lambda) {
|
||||
// Blergh
|
||||
/*eslint-disable no-eval */
|
||||
/* eslint-disable no-eval */
|
||||
data.lambda = eval('(' + data.lambda.js + ')');
|
||||
/*eslint-enable no-eval */
|
||||
/* eslint-enable no-eval */
|
||||
}
|
||||
it(name + ' - ' + test.name, function() {
|
||||
if (test.partials) {
|
||||
|
||||
@@ -78,6 +78,23 @@ describe('strict', function() {
|
||||
template({hello: {}});
|
||||
}, Exception, /"bar" not defined in/);
|
||||
});
|
||||
|
||||
it('should allow undefined parameters when passed to helpers', function() {
|
||||
var template = CompilerContext.compile('{{#unless foo}}success{{/unless}}', {strict: true});
|
||||
equals(template({}), 'success');
|
||||
});
|
||||
|
||||
it('should allow undefined hash when passed to helpers', function() {
|
||||
var template = CompilerContext.compile('{{helper value=@foo}}', {strict: true});
|
||||
var helpers = {
|
||||
helper: function(options) {
|
||||
equals('value' in options.hash, true);
|
||||
equals(options.hash.value, undefined);
|
||||
return 'success';
|
||||
}
|
||||
};
|
||||
equals(template({}, {helpers: helpers}), 'success');
|
||||
});
|
||||
});
|
||||
|
||||
describe('assume objects', function() {
|
||||
|
||||
+19
-1
@@ -146,6 +146,11 @@ describe('Tokenizer', function() {
|
||||
shouldMatchTokens(result, ['OPEN', 'ID', 'SEP', 'ID', 'CLOSE', 'OPEN', 'ID', 'SEP', 'ID', 'CLOSE']);
|
||||
});
|
||||
|
||||
it('allows escaped literals in []', function() {
|
||||
var result = tokenize('{{foo.[bar\\]]}}');
|
||||
shouldMatchTokens(result, ['OPEN', 'ID', 'SEP', 'ID', 'CLOSE']);
|
||||
});
|
||||
|
||||
it('tokenizes {{.}} as OPEN ID CLOSE', function() {
|
||||
var result = tokenize('{{.}}');
|
||||
shouldMatchTokens(result, ['OPEN', 'ID', 'CLOSE']);
|
||||
@@ -214,6 +219,10 @@ describe('Tokenizer', function() {
|
||||
shouldMatchTokens(result, ['OPEN_PARTIAL', 'ID', 'SEP', 'ID', 'SEP', 'ID', 'CLOSE']);
|
||||
});
|
||||
|
||||
it('tokenizes partial block declarations', function() {
|
||||
var result = tokenize('{{#> foo}}');
|
||||
shouldMatchTokens(result, ['OPEN_PARTIAL_BLOCK', 'ID', 'CLOSE']);
|
||||
});
|
||||
it('tokenizes a comment as "COMMENT"', function() {
|
||||
var result = tokenize('foo {{! this is a comment }} bar {{ baz }}');
|
||||
shouldMatchTokens(result, ['CONTENT', 'COMMENT', 'CONTENT', 'OPEN', 'ID', 'CLOSE']);
|
||||
@@ -237,6 +246,15 @@ describe('Tokenizer', function() {
|
||||
shouldMatchTokens(result, ['OPEN_BLOCK', 'ID', 'CLOSE', 'CONTENT', 'OPEN_ENDBLOCK', 'ID', 'CLOSE']);
|
||||
});
|
||||
|
||||
it('tokenizes directives', function() {
|
||||
shouldMatchTokens(
|
||||
tokenize('{{#*foo}}content{{/foo}}'),
|
||||
['OPEN_BLOCK', 'ID', 'CLOSE', 'CONTENT', 'OPEN_ENDBLOCK', 'ID', 'CLOSE']);
|
||||
shouldMatchTokens(
|
||||
tokenize('{{*foo}}'),
|
||||
['OPEN', 'ID', 'CLOSE']);
|
||||
});
|
||||
|
||||
it('tokenizes inverse sections as "INVERSE"', function() {
|
||||
shouldMatchTokens(tokenize('{{^}}'), ['INVERSE']);
|
||||
shouldMatchTokens(tokenize('{{else}}'), ['INVERSE']);
|
||||
@@ -264,7 +282,7 @@ describe('Tokenizer', function() {
|
||||
});
|
||||
|
||||
it('tokenizes mustaches with String params as "OPEN ID ID STRING CLOSE"', function() {
|
||||
var result = tokenize('{{ foo bar \'baz\' }}');
|
||||
var result = tokenize('{{ foo bar \"baz\" }}');
|
||||
shouldMatchTokens(result, ['OPEN', 'ID', 'ID', 'STRING', 'CLOSE']);
|
||||
shouldBeToken(result[3], 'STRING', 'baz');
|
||||
});
|
||||
|
||||
+48
-2
@@ -47,12 +47,14 @@ describe('track ids', function() {
|
||||
equals(template(context, {helpers: helpers}), 'HELP ME MY BOSS is.a:foo slave.driver:bar');
|
||||
});
|
||||
it('should note ../ and ./ references', function() {
|
||||
var template = CompilerContext.compile('{{wycats ./is.a ../slave.driver}}', {trackIds: true});
|
||||
var template = CompilerContext.compile('{{wycats ./is.a ../slave.driver this.is.a this}}', {trackIds: true});
|
||||
|
||||
var helpers = {
|
||||
wycats: function(passiveVoice, noun, options) {
|
||||
wycats: function(passiveVoice, noun, thiz, thiz2, options) {
|
||||
equal(options.ids[0], 'is.a');
|
||||
equal(options.ids[1], '../slave.driver');
|
||||
equal(options.ids[2], 'is.a');
|
||||
equal(options.ids[3], '');
|
||||
|
||||
return 'HELP ME MY BOSS ' + options.ids[0] + ':' + passiveVoice + ' ' + options.ids[1] + ':' + noun;
|
||||
}
|
||||
@@ -188,4 +190,48 @@ describe('track ids', function() {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('partials', function() {
|
||||
var helpers = {
|
||||
blockParams: function(name, options) {
|
||||
return name + ':' + options.ids[0] + '\n';
|
||||
},
|
||||
wycats: function(name, options) {
|
||||
return name + ':' + options.data.contextPath + '\n';
|
||||
}
|
||||
};
|
||||
|
||||
it('should pass track id for basic partial', function() {
|
||||
var template = CompilerContext.compile('Dudes: {{#dudes}}{{> dude}}{{/dudes}}', {trackIds: true}),
|
||||
hash = {dudes: [{name: 'Yehuda', url: 'http://yehuda'}, {name: 'Alan', url: 'http://alan'}]};
|
||||
|
||||
var partials = {
|
||||
dude: CompilerContext.compile('{{wycats name}}', {trackIds: true})
|
||||
};
|
||||
|
||||
equals(template(hash, {helpers: helpers, partials: partials}), 'Dudes: Yehuda:dudes.0\nAlan:dudes.1\n');
|
||||
});
|
||||
|
||||
it('should pass track id for context partial', function() {
|
||||
var template = CompilerContext.compile('Dudes: {{> dude dudes}}', {trackIds: true}),
|
||||
hash = {dudes: [{name: 'Yehuda', url: 'http://yehuda'}, {name: 'Alan', url: 'http://alan'}]};
|
||||
|
||||
var partials = {
|
||||
dude: CompilerContext.compile('{{#each this}}{{wycats name}}{{/each}}', {trackIds: true})
|
||||
};
|
||||
|
||||
equals(template(hash, {helpers: helpers, partials: partials}), 'Dudes: Yehuda:dudes..0\nAlan:dudes..1\n');
|
||||
});
|
||||
|
||||
it('should invalidate context for partials with parameters', function() {
|
||||
var template = CompilerContext.compile('Dudes: {{#dudes}}{{> dude . bar="foo"}}{{/dudes}}', {trackIds: true}),
|
||||
hash = {dudes: [{name: 'Yehuda', url: 'http://yehuda'}, {name: 'Alan', url: 'http://alan'}]};
|
||||
|
||||
var partials = {
|
||||
dude: CompilerContext.compile('{{wycats name}}', {trackIds: true})
|
||||
};
|
||||
|
||||
equals(template(hash, {helpers: helpers, partials: partials}), 'Dudes: Yehuda:true\nAlan:true\n');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -18,6 +18,7 @@ describe('utils', function() {
|
||||
describe('#escapeExpression', function() {
|
||||
it('shouhld escape html', function() {
|
||||
equals(Handlebars.Utils.escapeExpression('foo<&"\'>'), 'foo<&"'>');
|
||||
equals(Handlebars.Utils.escapeExpression('foo='), 'foo=');
|
||||
});
|
||||
it('should not escape SafeString', function() {
|
||||
var string = new Handlebars.SafeString('foo<&"\'>');
|
||||
|
||||
+5
-4
@@ -8,6 +8,9 @@ describe('Visitor', function() {
|
||||
// stub methods are executed
|
||||
var visitor = new Handlebars.Visitor();
|
||||
visitor.accept(Handlebars.parse('{{foo}}{{#foo (bar 1 "1" true undefined null) foo=@data}}{{!comment}}{{> bar }} {{/foo}}'));
|
||||
visitor.accept(Handlebars.parse('{{#> bar }} {{/bar}}'));
|
||||
visitor.accept(Handlebars.parse('{{#* bar }} {{/bar}}'));
|
||||
visitor.accept(Handlebars.parse('{{* bar }}'));
|
||||
});
|
||||
|
||||
it('should traverse to stubs', function() {
|
||||
@@ -40,8 +43,6 @@ describe('Visitor', function() {
|
||||
visitor.accept(Handlebars.parse('{{#foo.bar (foo.bar 1 "2" true) foo=@foo.bar}}{{!comment}}{{> bar }} {{/foo.bar}}'));
|
||||
});
|
||||
|
||||
it('should return undefined');
|
||||
|
||||
describe('mutating', function() {
|
||||
describe('fields', function() {
|
||||
it('should replace value', function() {
|
||||
@@ -49,7 +50,7 @@ describe('Visitor', function() {
|
||||
|
||||
visitor.mutating = true;
|
||||
visitor.StringLiteral = function(string) {
|
||||
return new Handlebars.AST.NumberLiteral(42, string.locInfo);
|
||||
return {type: 'NumberLiteral', value: 42, loc: string.loc};
|
||||
};
|
||||
|
||||
var ast = Handlebars.parse('{{foo foo="foo"}}');
|
||||
@@ -109,7 +110,7 @@ describe('Visitor', function() {
|
||||
|
||||
visitor.mutating = true;
|
||||
visitor.StringLiteral = function(string) {
|
||||
return new Handlebars.AST.NumberLiteral(42, string.locInfo);
|
||||
return {type: 'NumberLiteral', value: 42, loc: string.locInfo};
|
||||
};
|
||||
|
||||
var ast = Handlebars.parse('{{foo "foo"}}');
|
||||
|
||||
+16
-6
@@ -49,12 +49,21 @@ ID [^\s!"#%-,\.\/;->@\[-\^`\{-~]+/{LOOKAHEAD}
|
||||
return 'CONTENT';
|
||||
}
|
||||
|
||||
// nested raw block will create stacked 'raw' condition
|
||||
<raw>"{{{{"/[^/] this.begin('raw'); return 'CONTENT';
|
||||
<raw>"{{{{/"[^\s!"#%-,\.\/;->@\[-\^`\{-~]+/[=}\s\/.]"}}}}" {
|
||||
yytext = yytext.substr(5, yyleng-9);
|
||||
this.popState();
|
||||
return 'END_RAW_BLOCK';
|
||||
// Should be using `this.topState()` below, but it currently
|
||||
// returns the second top instead of the first top. Opened an
|
||||
// issue about it at https://github.com/zaach/jison/issues/291
|
||||
if (this.conditionStack[this.conditionStack.length-1] === 'raw') {
|
||||
return 'CONTENT';
|
||||
} else {
|
||||
yytext = yytext.substr(5, yyleng-9);
|
||||
return 'END_RAW_BLOCK';
|
||||
}
|
||||
}
|
||||
<raw>[^\x00]*?/("{{{{/") { return 'CONTENT'; }
|
||||
<raw>[^\x00]*?/("{{{{") { return 'CONTENT'; }
|
||||
|
||||
<com>[\s\S]*?"--"{RIGHT_STRIP}?"}}" {
|
||||
this.popState();
|
||||
@@ -71,7 +80,8 @@ ID [^\s!"#%-,\.\/;->@\[-\^`\{-~]+/{LOOKAHEAD}
|
||||
return 'CLOSE_RAW_BLOCK';
|
||||
}
|
||||
<mu>"{{"{LEFT_STRIP}?">" return 'OPEN_PARTIAL';
|
||||
<mu>"{{"{LEFT_STRIP}?"#" return 'OPEN_BLOCK';
|
||||
<mu>"{{"{LEFT_STRIP}?"#>" return 'OPEN_PARTIAL_BLOCK';
|
||||
<mu>"{{"{LEFT_STRIP}?"#""*"? return 'OPEN_BLOCK';
|
||||
<mu>"{{"{LEFT_STRIP}?"/" return 'OPEN_ENDBLOCK';
|
||||
<mu>"{{"{LEFT_STRIP}?"^"\s*{RIGHT_STRIP}?"}}" this.popState(); return 'INVERSE';
|
||||
<mu>"{{"{LEFT_STRIP}?\s*"else"\s*{RIGHT_STRIP}?"}}" this.popState(); return 'INVERSE';
|
||||
@@ -88,7 +98,7 @@ ID [^\s!"#%-,\.\/;->@\[-\^`\{-~]+/{LOOKAHEAD}
|
||||
this.popState();
|
||||
return 'COMMENT';
|
||||
}
|
||||
<mu>"{{"{LEFT_STRIP}? return 'OPEN';
|
||||
<mu>"{{"{LEFT_STRIP}?"*"? return 'OPEN';
|
||||
|
||||
<mu>"=" return 'EQUALS';
|
||||
<mu>".." return 'ID';
|
||||
@@ -110,7 +120,7 @@ ID [^\s!"#%-,\.\/;->@\[-\^`\{-~]+/{LOOKAHEAD}
|
||||
|
||||
<mu>{ID} return 'ID';
|
||||
|
||||
<mu>'['[^\]]*']' return 'ID';
|
||||
<mu>'['('\\]'|[^\]])*']' yytext = yytext.replace(/\\([\\\]])/g,'$1'); return 'ID';
|
||||
<mu>. return 'INVALID';
|
||||
|
||||
<INITIAL,mu><<EOF>> return 'EOF';
|
||||
|
||||
+54
-18
@@ -9,7 +9,7 @@ root
|
||||
;
|
||||
|
||||
program
|
||||
: statement* -> new yy.Program($1, null, {}, yy.locInfo(@$))
|
||||
: statement* -> yy.prepareProgram($1)
|
||||
;
|
||||
|
||||
statement
|
||||
@@ -17,16 +17,29 @@ statement
|
||||
| block -> $1
|
||||
| rawBlock -> $1
|
||||
| partial -> $1
|
||||
| partialBlock -> $1
|
||||
| content -> $1
|
||||
| COMMENT -> new yy.CommentStatement(yy.stripComment($1), yy.stripFlags($1, $1), yy.locInfo(@$))
|
||||
;
|
||||
| COMMENT {
|
||||
$$ = {
|
||||
type: 'CommentStatement',
|
||||
value: yy.stripComment($1),
|
||||
strip: yy.stripFlags($1, $1),
|
||||
loc: yy.locInfo(@$)
|
||||
};
|
||||
};
|
||||
|
||||
content
|
||||
: CONTENT -> new yy.ContentStatement($1, yy.locInfo(@$))
|
||||
;
|
||||
: CONTENT {
|
||||
$$ = {
|
||||
type: 'ContentStatement',
|
||||
original: $1,
|
||||
value: $1,
|
||||
loc: yy.locInfo(@$)
|
||||
};
|
||||
};
|
||||
|
||||
rawBlock
|
||||
: openRawBlock content END_RAW_BLOCK -> yy.prepareRawBlock($1, $2, $3, @$)
|
||||
: openRawBlock content+ END_RAW_BLOCK -> yy.prepareRawBlock($1, $2, $3, @$)
|
||||
;
|
||||
|
||||
openRawBlock
|
||||
@@ -39,7 +52,7 @@ block
|
||||
;
|
||||
|
||||
openBlock
|
||||
: OPEN_BLOCK helperName param* hash? blockParams? CLOSE -> { path: $2, params: $3, hash: $4, blockParams: $5, strip: yy.stripFlags($1, $6) }
|
||||
: OPEN_BLOCK helperName param* hash? blockParams? CLOSE -> { open: $1, path: $2, params: $3, hash: $4, blockParams: $5, strip: yy.stripFlags($1, $6) }
|
||||
;
|
||||
|
||||
openInverse
|
||||
@@ -57,7 +70,7 @@ inverseAndProgram
|
||||
inverseChain
|
||||
: openInverseChain program inverseChain? {
|
||||
var inverse = yy.prepareBlock($1, $2, $3, $3, false, @$),
|
||||
program = new yy.Program([inverse], null, {}, yy.locInfo(@$));
|
||||
program = yy.prepareProgram([inverse], $2.loc);
|
||||
program.chained = true;
|
||||
|
||||
$$ = { strip: $1.strip, program: program, chain: true };
|
||||
@@ -77,7 +90,23 @@ mustache
|
||||
;
|
||||
|
||||
partial
|
||||
: OPEN_PARTIAL partialName param* hash? CLOSE -> new yy.PartialStatement($2, $3, $4, yy.stripFlags($1, $5), yy.locInfo(@$))
|
||||
: OPEN_PARTIAL partialName param* hash? CLOSE {
|
||||
$$ = {
|
||||
type: 'PartialStatement',
|
||||
name: $2,
|
||||
params: $3,
|
||||
hash: $4,
|
||||
indent: '',
|
||||
strip: yy.stripFlags($1, $5),
|
||||
loc: yy.locInfo(@$)
|
||||
};
|
||||
}
|
||||
;
|
||||
partialBlock
|
||||
: openPartialBlock program closeBlock -> yy.preparePartialBlock($1, $2, $3, @$)
|
||||
;
|
||||
openPartialBlock
|
||||
: OPEN_PARTIAL_BLOCK partialName param* hash? CLOSE -> { path: $2, params: $3, hash: $4, strip: yy.stripFlags($1, $5) }
|
||||
;
|
||||
|
||||
param
|
||||
@@ -86,15 +115,22 @@ param
|
||||
;
|
||||
|
||||
sexpr
|
||||
: OPEN_SEXPR helperName param* hash? CLOSE_SEXPR -> new yy.SubExpression($2, $3, $4, yy.locInfo(@$))
|
||||
;
|
||||
: OPEN_SEXPR helperName param* hash? CLOSE_SEXPR {
|
||||
$$ = {
|
||||
type: 'SubExpression',
|
||||
path: $2,
|
||||
params: $3,
|
||||
hash: $4,
|
||||
loc: yy.locInfo(@$)
|
||||
};
|
||||
};
|
||||
|
||||
hash
|
||||
: hashSegment+ -> new yy.Hash($1, yy.locInfo(@$))
|
||||
: hashSegment+ -> {type: 'Hash', pairs: $1, loc: yy.locInfo(@$)}
|
||||
;
|
||||
|
||||
hashSegment
|
||||
: ID EQUALS param -> new yy.HashPair(yy.id($1), $3, yy.locInfo(@$))
|
||||
: ID EQUALS param -> {type: 'HashPair', key: yy.id($1), value: $3, loc: yy.locInfo(@$)}
|
||||
;
|
||||
|
||||
blockParams
|
||||
@@ -104,11 +140,11 @@ blockParams
|
||||
helperName
|
||||
: path -> $1
|
||||
| dataName -> $1
|
||||
| STRING -> new yy.StringLiteral($1, yy.locInfo(@$))
|
||||
| NUMBER -> new yy.NumberLiteral($1, yy.locInfo(@$))
|
||||
| BOOLEAN -> new yy.BooleanLiteral($1, yy.locInfo(@$))
|
||||
| UNDEFINED -> new yy.UndefinedLiteral(yy.locInfo(@$))
|
||||
| NULL -> new yy.NullLiteral(yy.locInfo(@$))
|
||||
| STRING -> {type: 'StringLiteral', value: $1, original: $1, loc: yy.locInfo(@$)}
|
||||
| NUMBER -> {type: 'NumberLiteral', value: Number($1), original: Number($1), loc: yy.locInfo(@$)}
|
||||
| BOOLEAN -> {type: 'BooleanLiteral', value: $1 === 'true', original: $1 === 'true', loc: yy.locInfo(@$)}
|
||||
| UNDEFINED -> {type: 'UndefinedLiteral', original: undefined, value: undefined, loc: yy.locInfo(@$)}
|
||||
| NULL -> {type: 'NullLiteral', original: null, value: null, loc: yy.locInfo(@$)}
|
||||
;
|
||||
|
||||
partialName
|
||||
|
||||
@@ -1 +1 @@
|
||||
/* istanbul ignore next */
|
||||
// File ignored in coverage tests via setting in .istanbul.yml
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"globals": {
|
||||
"require": true
|
||||
},
|
||||
"rules": {
|
||||
// Disabling for tests, for now.
|
||||
"no-path-concat": 0,
|
||||
|
||||
"no-var": 0,
|
||||
"no-shadow": 0,
|
||||
"handle-callback-err": 0,
|
||||
"no-console": 0,
|
||||
"no-process-env": 0,
|
||||
"dot-notation": [2, {"allowKeywords": true}]
|
||||
}
|
||||
}
|
||||
+2
-42
@@ -1,25 +1,12 @@
|
||||
var _ = require('underscore'),
|
||||
async = require('async'),
|
||||
git = require('./util/git'),
|
||||
Keen = require('keen.io'),
|
||||
metrics = require('../bench');
|
||||
|
||||
module.exports = function(grunt) {
|
||||
grunt.registerTask('metrics', function() {
|
||||
var done = this.async(),
|
||||
execName = grunt.option('name'),
|
||||
events = {},
|
||||
|
||||
projectId = process.env.KEEN_PROJECTID,
|
||||
writeKey = process.env.KEEN_WRITEKEY,
|
||||
keen;
|
||||
|
||||
if (!execName && projectId && writeKey) {
|
||||
keen = Keen.configure({
|
||||
projectId: projectId,
|
||||
writeKey: writeKey
|
||||
});
|
||||
}
|
||||
events = {};
|
||||
|
||||
async.each(_.keys(metrics), function(name, complete) {
|
||||
if (/^_/.test(name) || (execName && name !== execName)) {
|
||||
@@ -31,33 +18,6 @@ module.exports = function(grunt) {
|
||||
complete();
|
||||
});
|
||||
},
|
||||
function() {
|
||||
if (!keen) {
|
||||
return done();
|
||||
}
|
||||
|
||||
emit(keen, events, function(err, res) {
|
||||
if (err) {
|
||||
throw err;
|
||||
}
|
||||
|
||||
grunt.log.writeln('Metrics recorded.');
|
||||
done();
|
||||
});
|
||||
});
|
||||
done);
|
||||
});
|
||||
};
|
||||
function emit(keen, collections, callback) {
|
||||
git.commitInfo(function(err, info) {
|
||||
_.each(collections, function(collection) {
|
||||
_.each(collection, function(event) {
|
||||
if (info.tagName) {
|
||||
event.tag = info.tagName;
|
||||
}
|
||||
event.sha = info.head;
|
||||
});
|
||||
});
|
||||
|
||||
keen.addEvents(collections, callback);
|
||||
});
|
||||
}
|
||||
|
||||
+1
-1
@@ -6,7 +6,7 @@ module.exports = function(grunt) {
|
||||
|
||||
var cmd = './node_modules/.bin/jison';
|
||||
|
||||
if(process.platform === 'win32'){
|
||||
if (process.platform === 'win32') {
|
||||
cmd = 'node_modules\\.bin\\jison.cmd';
|
||||
}
|
||||
|
||||
|
||||
+15
-7
@@ -15,14 +15,22 @@ module.exports = function(grunt) {
|
||||
git.commitInfo(function(err, info) {
|
||||
grunt.log.writeln('tag: ' + info.tagName);
|
||||
|
||||
var files = [];
|
||||
|
||||
// Publish the master as "latest" and with the commit-id
|
||||
if (info.isMaster) {
|
||||
files.push('-latest');
|
||||
files.push('-' + info.head);
|
||||
}
|
||||
|
||||
// Publish tags by their tag-name
|
||||
if (info.tagName && semver.valid(info.tagName)) {
|
||||
files.push('-' + info.tagName);
|
||||
}
|
||||
|
||||
if (files.length > 0) {
|
||||
initSDK();
|
||||
|
||||
var files = ['-latest', '-' + info.head];
|
||||
if (info.tagName && semver.valid(info.tagName)) {
|
||||
files.push('-' + info.tagName);
|
||||
}
|
||||
|
||||
grunt.log.writeln('publishing files: ' + JSON.stringify(files));
|
||||
publish(fileMap(files), done);
|
||||
} else {
|
||||
// Silently ignore for branches
|
||||
@@ -60,7 +68,7 @@ module.exports = function(grunt) {
|
||||
|
||||
async.forEach(_.keys(files), function(file, callback) {
|
||||
var params = {Bucket: bucket, Key: file, Body: grunt.file.read(files[file])};
|
||||
s3.putObject(params, function(err, data) {
|
||||
s3.putObject(params, function(err) {
|
||||
if (err) {
|
||||
throw err;
|
||||
} else {
|
||||
|
||||
+33
-6
@@ -1,17 +1,21 @@
|
||||
var childProcess = require('child_process'),
|
||||
fs = require('fs');
|
||||
fs = require('fs'),
|
||||
os = require('os');
|
||||
|
||||
module.exports = function(grunt) {
|
||||
grunt.registerTask('test:bin', function() {
|
||||
var done = this.async();
|
||||
|
||||
childProcess.exec('./bin/handlebars -a spec/artifacts/empty.handlebars', function(err, stdout) {
|
||||
// On Windows, the executable handlebars.js file cannot be run directly
|
||||
var prefix = os.type().match(/^Windows/) ? process.argv[0] : '';
|
||||
childProcess.exec(prefix + ' ./bin/handlebars -a spec/artifacts/empty.handlebars', function(err, stdout) {
|
||||
if (err) {
|
||||
throw err;
|
||||
}
|
||||
|
||||
var expected = fs.readFileSync('./spec/expected/empty.amd.js');
|
||||
if (stdout.toString() !== expected.toString()) {
|
||||
var expected = fs.readFileSync('./spec/expected/empty.amd.js').toString().replace(/\r\n/g, '\n');
|
||||
|
||||
if (stdout.toString() !== expected) {
|
||||
throw new Error('Expected binary output differed:\n\n"' + stdout + '"\n\n"' + expected + '"');
|
||||
}
|
||||
|
||||
@@ -32,7 +36,7 @@ module.exports = function(grunt) {
|
||||
grunt.registerTask('test:cov', function() {
|
||||
var done = this.async();
|
||||
|
||||
var runner = childProcess.fork('node_modules/.bin/istanbul', ['cover', '--', './spec/env/runner.js'], {stdio: 'inherit'});
|
||||
var runner = childProcess.fork('node_modules/istanbul/lib/cli.js', ['cover', '--source-map', '--', './spec/env/runner.js'], {stdio: 'inherit'});
|
||||
runner.on('close', function(code) {
|
||||
if (code != 0) {
|
||||
grunt.fatal(code + ' tests failed');
|
||||
@@ -40,5 +44,28 @@ module.exports = function(grunt) {
|
||||
done();
|
||||
});
|
||||
});
|
||||
grunt.registerTask('test', ['test:bin', 'test:cov']);
|
||||
grunt.registerTask('test:min', function() {
|
||||
var done = this.async();
|
||||
|
||||
var runner = childProcess.fork('./spec/env/runner', ['--min'], {stdio: 'inherit'});
|
||||
runner.on('close', function(code) {
|
||||
if (code != 0) {
|
||||
grunt.fatal(code + ' tests failed');
|
||||
}
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
grunt.registerTask('test:check-cov', function() {
|
||||
var done = this.async();
|
||||
|
||||
var runner = childProcess.fork('node_modules/istanbul/lib/cli.js', ['check-coverage', '--statements', '100', '--functions', '100', '--branches', '100', '--lines 100'], {stdio: 'inherit'});
|
||||
runner.on('close', function(code) {
|
||||
if (code != 0) {
|
||||
grunt.fatal('Coverage check failed: ' + code);
|
||||
}
|
||||
done();
|
||||
});
|
||||
});
|
||||
grunt.registerTask('test', ['test:bin', 'test:cov', 'test:check-cov']);
|
||||
};
|
||||
|
||||
+5
-5
@@ -50,7 +50,7 @@ module.exports = {
|
||||
childProcess.exec('git rev-parse --short origin/master', {}, function(err, stdout) {
|
||||
// This will error if master was not checked out but in this case we know we are not master
|
||||
// so we can ignore.
|
||||
if (err && !/Needed a single revision/.test(err.message)) {
|
||||
if (err && !(/Needed a single revision/.test(err.message))) {
|
||||
throw new Error('git.master: ' + err.message);
|
||||
}
|
||||
|
||||
@@ -59,7 +59,7 @@ module.exports = {
|
||||
},
|
||||
|
||||
add: function(path, callback) {
|
||||
childProcess.exec('git add -f ' + path, {}, function(err, stdout) {
|
||||
childProcess.exec('git add -f ' + path, {}, function(err) {
|
||||
if (err) {
|
||||
throw new Error('git.add: ' + err.message);
|
||||
}
|
||||
@@ -68,7 +68,7 @@ module.exports = {
|
||||
});
|
||||
},
|
||||
commit: function(name, callback) {
|
||||
childProcess.exec('git commit --message=' + name, {}, function(err, stdout) {
|
||||
childProcess.exec('git commit --message=' + name, {}, function(err) {
|
||||
if (err) {
|
||||
throw new Error('git.commit: ' + err.message);
|
||||
}
|
||||
@@ -77,7 +77,7 @@ module.exports = {
|
||||
});
|
||||
},
|
||||
tag: function(name, callback) {
|
||||
childProcess.exec('git tag -a --message=' + name + ' ' + name, {}, function(err, stdout, stderr) {
|
||||
childProcess.exec('git tag -a --message=' + name + ' ' + name, {}, function(err) {
|
||||
if (err) {
|
||||
throw new Error('git.tag: ' + err.message);
|
||||
}
|
||||
@@ -98,7 +98,7 @@ module.exports = {
|
||||
});
|
||||
|
||||
var versionTags = tags.filter(function(info) {
|
||||
return /^v/.test(info[0]);
|
||||
return (/^v/.test(info[0]));
|
||||
});
|
||||
|
||||
callback(undefined, versionTags[0] || tags[0]);
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user