Compare commits
61 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 096b8ccd2e | |||
| 923993dfb1 | |||
| fcf4e4ef0e | |||
| ae1795f82c | |||
| 2666553383 | |||
| 15af03c8bf | |||
| 97c7d65931 | |||
| bbc7c7d080 | |||
| 1a751b2f6e | |||
| abe9c82e75 | |||
| 956ac95e7a | |||
| 40e1837b14 | |||
| ddea5be2a4 | |||
| b4968bb8d4 | |||
| ca8e34c94d | |||
| 4a94340f42 | |||
| 7d69becd2e | |||
| 1a9b5d4ac6 | |||
| bdc0e6507e | |||
| 93fb25c702 | |||
| 23d41e5617 | |||
| d62620a282 | |||
| 62fdd541f5 | |||
| 7f53c917d9 | |||
| 015eba6687 | |||
| 5b8b54b774 | |||
| efcedf2f24 | |||
| 512daa3189 | |||
| ad3727b3fc | |||
| e2a81fdd60 | |||
| fe7bc0d4bd | |||
| 9ad8b86ece | |||
| 96b42dedea | |||
| 4942324250 | |||
| 0cf5657c43 | |||
| 3daef9d308 | |||
| 7f0ded4926 | |||
| 1041c1d533 | |||
| f06a9dc1f0 | |||
| 45ab2bb218 | |||
| f17cb3ecac | |||
| 86974b2660 | |||
| 57b4ef08b6 | |||
| cc16bc47dd | |||
| 078c1eecba | |||
| eb53f2e844 | |||
| e8954b3488 | |||
| 20b9c3829e | |||
| 709153f7f5 | |||
| d5cc3fbcf1 | |||
| d3cff5c1cd | |||
| 83106e7a5e | |||
| 525e58d318 | |||
| 9d353bd3dd | |||
| 47d13cb23c | |||
| 7c13753bb5 | |||
| affbcbb79e | |||
| 96a45a4a96 | |||
| 6f6eb89bd8 | |||
| eea708a18d | |||
| 508bb2d55d |
@@ -8,3 +8,4 @@ node_modules
|
||||
*.sublime-project
|
||||
*.sublime-workspace
|
||||
npm-debug.log
|
||||
sauce_connect.log*
|
||||
|
||||
@@ -1,14 +1,6 @@
|
||||
{
|
||||
"predef": [
|
||||
"console",
|
||||
"Ember",
|
||||
"DS",
|
||||
"Handlebars",
|
||||
"Metamorph",
|
||||
"ember_assert",
|
||||
"ember_warn",
|
||||
"ember_deprecate",
|
||||
"ember_deprecateFunc",
|
||||
"require",
|
||||
"suite",
|
||||
"equal",
|
||||
@@ -22,10 +14,12 @@
|
||||
"ok",
|
||||
"strictEqual",
|
||||
"module",
|
||||
"define",
|
||||
|
||||
"describe",
|
||||
"it",
|
||||
"afterEach"
|
||||
"afterEach",
|
||||
"beforeEach"
|
||||
],
|
||||
|
||||
"node" : true,
|
||||
|
||||
@@ -18,5 +18,6 @@ dist/components/*
|
||||
spec/*
|
||||
src/*
|
||||
tasks/*
|
||||
tmp/*
|
||||
publish/*
|
||||
vendor/*
|
||||
|
||||
+13
-10
@@ -1,13 +1,12 @@
|
||||
language: node_js
|
||||
node_js:
|
||||
- "0.8"
|
||||
- "0.10"
|
||||
|
||||
before_install:
|
||||
- npm install -g grunt-cli
|
||||
|
||||
script:
|
||||
- grunt --stack default metrics publish:latest
|
||||
- grunt --stack travis
|
||||
|
||||
email:
|
||||
on_failure: change
|
||||
@@ -15,13 +14,17 @@ email:
|
||||
env:
|
||||
global:
|
||||
- S3_BUCKET_NAME=builds.handlebarsjs.com
|
||||
- secure: ! 'PJaukuvkBBsSDOqbIcNSSMgb96VVEaIt/eq9GPjXPeFbSd3hXgwhwVE62Lrq
|
||||
- secure: PJaukuvkBBsSDOqbIcNSSMgb96VVEaIt/eq9GPjXPeFbSd3hXgwhwVE62LrqtJO8BaUfX+PzpiQjEl4D5/KBmvlFZ057Hqmy0zmPOT5mDZfJe8Ja5zyvTMb+KkCWN/tjAp8kawHojE04pn6jIpPdwXFnAYwPhaHbATFrmdt9fdg=
|
||||
- secure: mBcGL2tnmiRujJdV/4fxrVd8E8wn6AW9IQKVcMv8tvOc7i5dOzZ39rpBKLuTMRXDtMV1LyLiuKYb1pHj1IyeadEahcLYFfGygF4LG7Yzp4NWHtRzQ7Q8LXaJV7dXDboYCFkn2a8/Rtx1YSVh/sCONf5UoRC+MUIqrj4UiHN9r3s=
|
||||
|
||||
tJO8BaUfX+PzpiQjEl4D5/KBmvlFZ057Hqmy0zmPOT5mDZfJe8Ja5zyvTMb+
|
||||
matrix:
|
||||
include:
|
||||
- node_js: "0.10"
|
||||
env:
|
||||
- PUBLISH=true
|
||||
- secure: pLTzghtVll9yGKJI0AaB0uI8GypfWxLTaIB0ZL8//yN3nAEIKMhf/RRilYTsn/rKj2NUa7vt2edYILi3lttOUlCBOwTc9amiRms1W8Lwr/3IdWPeBLvLuH1zNJRm2lBAwU4LBSqaOwhGaxOQr6KHTnWudhNhgOucxpZfvfI/dFw=
|
||||
- secure: yERYCf7AwL11D9uMtacly/THGV8BlzsMmrt+iQVvGA3GaY6QMmfYqf6P6cCH98sH5etd1Y+1e6YrPeMjqI6lyRllT7FptoyOdHulazQe86VQN4sc0EpqMlH088kB7gGjTut9Z+X9ViooT5XEh9WA5jXEI9pXhQJNoIHkWPuwGuY=
|
||||
|
||||
KkCWN/tjAp8kawHojE04pn6jIpPdwXFnAYwPhaHbATFrmdt9fdg='
|
||||
- secure: ! 'mBcGL2tnmiRujJdV/4fxrVd8E8wn6AW9IQKVcMv8tvOc7i5dOzZ39rpBKLuT
|
||||
|
||||
MRXDtMV1LyLiuKYb1pHj1IyeadEahcLYFfGygF4LG7Yzp4NWHtRzQ7Q8LXaJ
|
||||
|
||||
V7dXDboYCFkn2a8/Rtx1YSVh/sCONf5UoRC+MUIqrj4UiHN9r3s='
|
||||
cache:
|
||||
directories:
|
||||
- node_modules
|
||||
|
||||
+88
-47
@@ -7,15 +7,14 @@ module.exports = function(grunt) {
|
||||
|
||||
jshint: {
|
||||
options: {
|
||||
jshintrc: '.jshintrc',
|
||||
force: true
|
||||
jshintrc: '.jshintrc'
|
||||
},
|
||||
files: [
|
||||
'lib/**/!(parser).js'
|
||||
'dist/**/!(*.min|parser).js'
|
||||
]
|
||||
},
|
||||
|
||||
clean: ["dist", "lib/handlebars/compiler/parser.js"],
|
||||
clean: ['tmp', 'dist', 'lib/handlebars/compiler/parser.js'],
|
||||
|
||||
copy: {
|
||||
dist: {
|
||||
@@ -42,9 +41,20 @@ module.exports = function(grunt) {
|
||||
}
|
||||
},
|
||||
|
||||
transpile: {
|
||||
packager: {
|
||||
global: {
|
||||
type: 'global',
|
||||
export: 'Handlebars',
|
||||
files: [{
|
||||
cwd: 'lib/',
|
||||
expand: true,
|
||||
src: ['handlebars*.js'],
|
||||
dest: 'dist/'
|
||||
}]
|
||||
},
|
||||
|
||||
amd: {
|
||||
type: "amd",
|
||||
type: 'amd',
|
||||
anonymous: true,
|
||||
files: [{
|
||||
expand: true,
|
||||
@@ -64,21 +74,6 @@ module.exports = function(grunt) {
|
||||
}]
|
||||
}
|
||||
},
|
||||
|
||||
packager: {
|
||||
options: {
|
||||
export: 'Handlebars'
|
||||
},
|
||||
|
||||
global: {
|
||||
files: [{
|
||||
cwd: 'lib/',
|
||||
expand: true,
|
||||
src: ['handlebars*.js'],
|
||||
dest: 'dist/'
|
||||
}]
|
||||
}
|
||||
},
|
||||
requirejs: {
|
||||
options: {
|
||||
optimize: "none",
|
||||
@@ -108,57 +103,103 @@ module.exports = function(grunt) {
|
||||
files: [{
|
||||
cwd: 'dist/',
|
||||
expand: true,
|
||||
src: ['handlebars*.js'],
|
||||
src: ['handlebars*.js', '!*.min.js'],
|
||||
dest: 'dist/',
|
||||
rename: function(dest, src) {
|
||||
return dest + src.replace(/\.js$/, '.min.js');
|
||||
}
|
||||
}]
|
||||
}
|
||||
},
|
||||
|
||||
concat: {
|
||||
tests: {
|
||||
src: ['spec/!(require).js'],
|
||||
dest: 'tmp/tests.js'
|
||||
}
|
||||
},
|
||||
|
||||
connect: {
|
||||
server: {
|
||||
options: {
|
||||
base: '.',
|
||||
hostname: '*',
|
||||
port: 9999
|
||||
}
|
||||
}
|
||||
},
|
||||
'saucelabs-mocha': {
|
||||
all: {
|
||||
options: {
|
||||
build: process.env.TRAVIS_JOB_ID,
|
||||
urls: ['http://localhost:9999/spec/?headless=true'],
|
||||
detailedError: true,
|
||||
concurrency: 2,
|
||||
browsers: [
|
||||
{browserName: 'chrome'},
|
||||
{browserName: 'firefox'},
|
||||
{browserName: 'firefox', version: '3.6'},
|
||||
{browserName: 'safari', version: 7, platform: 'OS X 10.9'},
|
||||
{browserName: 'safari', version: 6, platform: 'OS X 10.8'},
|
||||
{browserName: 'safari', version: 5},
|
||||
{browserName: 'opera', version: 12},
|
||||
{browserName: 'opera', version: 11},
|
||||
{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: 8, platform: 'XP'},
|
||||
{browserName: 'internet explorer', version: 7, platform: 'XP'},
|
||||
{browserName: 'internet explorer', version: 6, platform: 'XP'}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
watch: {
|
||||
scripts: {
|
||||
options: {
|
||||
atBegin: true
|
||||
},
|
||||
|
||||
files: ['src/*', 'lib/**/*.js', 'spec/**/*.js'],
|
||||
tasks: ['build', 'tests', 'test']
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Build a new version of the library
|
||||
this.registerTask('build', "Builds a distributable version of the current project", [
|
||||
'jshint',
|
||||
'clean',
|
||||
'parser',
|
||||
'node',
|
||||
'globals']);
|
||||
'globals',
|
||||
'jshint']);
|
||||
|
||||
this.registerTask('amd', ['transpile:amd', 'requirejs']);
|
||||
this.registerTask('node', ['transpile:cjs']);
|
||||
this.registerTask('globals', ['packager-fork']);
|
||||
this.registerTask('amd', ['packager:amd', 'requirejs']);
|
||||
this.registerTask('node', ['packager:cjs']);
|
||||
this.registerTask('globals', ['packager:global']);
|
||||
this.registerTask('tests', ['concat:tests']);
|
||||
|
||||
this.registerTask('release', 'Build final packages', ['amd', 'uglify', 'copy:dist', 'copy:components', 'copy:cdnjs']);
|
||||
this.registerTask('release', 'Build final packages', ['amd', 'jshint', 'uglify', 'copy:dist', 'copy:components', 'copy:cdnjs']);
|
||||
|
||||
// Load tasks from npm
|
||||
grunt.loadNpmTasks('grunt-contrib-clean');
|
||||
grunt.loadNpmTasks('grunt-contrib-concat');
|
||||
grunt.loadNpmTasks('grunt-contrib-connect');
|
||||
grunt.loadNpmTasks('grunt-contrib-copy');
|
||||
grunt.loadNpmTasks('grunt-contrib-requirejs');
|
||||
grunt.loadNpmTasks('grunt-contrib-jshint');
|
||||
grunt.loadNpmTasks('grunt-contrib-uglify');
|
||||
grunt.loadNpmTasks('grunt-es6-module-transpiler');
|
||||
grunt.loadNpmTasks('grunt-contrib-watch');
|
||||
grunt.loadNpmTasks('grunt-saucelabs');
|
||||
grunt.loadNpmTasks('es6-module-packager');
|
||||
|
||||
grunt.task.loadTasks('tasks');
|
||||
|
||||
grunt.registerTask('packager-fork', function() {
|
||||
// Allows us to run the packager task out of process to work around the multiple
|
||||
// traceur exec issues
|
||||
grunt.util.spawn({grunt: true, args: ['--stack', 'packager'], opts: {stdio: 'inherit'}}, this.async());
|
||||
});
|
||||
grunt.registerTask('test', function() {
|
||||
var done = this.async();
|
||||
|
||||
var runner = childProcess.fork('./spec/env/runner', [], {stdio: 'inherit'});
|
||||
runner.on('close', function(code) {
|
||||
if (code != 0) {
|
||||
grunt.fatal(code + ' tests failed');
|
||||
}
|
||||
done();
|
||||
});
|
||||
});
|
||||
grunt.registerTask('bench', ['metrics']);
|
||||
grunt.registerTask('sauce', process.env.SAUCE_USERNAME ? ['tests', 'connect', 'saucelabs-mocha'] : []);
|
||||
|
||||
grunt.registerTask('default', ['build', 'test', 'release']);
|
||||
grunt.registerTask('travis', process.env.PUBLISH ? ['default', 'sauce', 'metrics', 'publish:latest'] : ['default']);
|
||||
|
||||
grunt.registerTask('dev', ['clean', 'connect', 'watch']);
|
||||
grunt.registerTask('default', ['clean', 'build', 'test', 'release']);
|
||||
};
|
||||
|
||||
+29
-7
@@ -1,5 +1,5 @@
|
||||
[](https://travis-ci.org/wycats/handlebars.js)
|
||||
|
||||
[](https://travis-ci.org/wycats/handlebars.js)
|
||||
[](https://saucelabs.com/u/handlebars)
|
||||
|
||||
Handlebars.js
|
||||
=============
|
||||
@@ -14,9 +14,7 @@ Checkout the official Handlebars docs site at
|
||||
|
||||
Installing
|
||||
----------
|
||||
Installing Handlebars is easy. Simply download the package [from the
|
||||
official site](http://handlebarsjs.com/) and add it to your web pages
|
||||
(you should usually use the most recent version).
|
||||
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).
|
||||
|
||||
Alternatively, if you prefer having the latest version of handlebars from
|
||||
the 'master' branch, passing builds of the 'master' branch are automatically
|
||||
@@ -325,6 +323,8 @@ Handlebars has been designed to work in any ECMAScript 3 environment. This inclu
|
||||
Older versions and other runtimes are likely to work but have not been formally
|
||||
tested.
|
||||
|
||||
[](https://saucelabs.com/u/handlebars)
|
||||
|
||||
Performance
|
||||
-----------
|
||||
|
||||
@@ -364,6 +364,7 @@ Handlebars in the Wild
|
||||
* [CoSchedule](http://coschedule.com) An editorial calendar for WordPress that uses Handlebars.js
|
||||
* [Ember.js](http://www.emberjs.com) makes Handlebars.js the primary way to
|
||||
structure your views, also with automatic data binding support.
|
||||
* [Ghost](https://ghost.org/) Just a blogging platform.
|
||||
* [handlebars_assets](http://github.com/leshill/handlebars_assets): A Rails Asset Pipeline gem
|
||||
from Les Hill (@leshill).
|
||||
* [handlebars-helpers](https://github.com/assemble/handlebars-helpers) is an extensive library
|
||||
@@ -388,7 +389,7 @@ External Resources
|
||||
|
||||
* [Gist about Synchronous and asynchronous loading of external handlebars templates](https://gist.github.com/2287070)
|
||||
|
||||
Have a project using Handlebars? Send us a [pull request](https://github.com/wycats/handlebars.js/pull/new/master)!
|
||||
Have a project using Handlebars? Send us a [pull request][pull-request]!
|
||||
|
||||
Helping Out
|
||||
-----------
|
||||
@@ -405,12 +406,27 @@ in the root of the project. That will build Handlebars and output the
|
||||
results to the dist/ folder. To re-run tests, run `grunt test` or `npm test`.
|
||||
You can also run our set of benchmarks with `grunt bench`.
|
||||
|
||||
The `grunt dev` implements watching for tests and allows for in browser testing at `http://localhost:9999/spec/`.
|
||||
|
||||
If you notice any problems, please report them to the GitHub issue tracker at
|
||||
[http://github.com/wycats/handlebars.js/issues](http://github.com/wycats/handlebars.js/issues).
|
||||
Feel free to contact commondream or wycats through GitHub with any other
|
||||
questions or feature requests. To submit changes fork the project and
|
||||
send a pull request.
|
||||
|
||||
### 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.
|
||||
|
||||
```sh
|
||||
grunt build release
|
||||
export HANDLEBARS_PATH=`pwd`
|
||||
|
||||
cd $emberRepoDir
|
||||
bundle exec rake clean
|
||||
bundle exec rake test
|
||||
```
|
||||
|
||||
### Releasing
|
||||
|
||||
Handlebars utilizes the [release yeoman generator][generator-release] to perform most release tasks.
|
||||
@@ -423,6 +439,10 @@ yo release:release patch
|
||||
npm publish
|
||||
yo release:publish cdnjs handlebars.js dist/cdnjs/
|
||||
yo release:publish components handlebars.js dist/components/
|
||||
|
||||
cd dist/components/
|
||||
gem build handlebars-source.gemspec
|
||||
gem push handlebars-source-*.gem
|
||||
```
|
||||
|
||||
After this point the handlebars site needs to be updated to point to the new version numbers.
|
||||
@@ -431,5 +451,7 @@ License
|
||||
-------
|
||||
Handlebars.js is released under the MIT license.
|
||||
|
||||
[builds-page]: http://builds.handlebarsjs.com.s3.amazonaws.com/index.html
|
||||
[bower-repo]: https://github.com/components/handlebars.js
|
||||
[builds-page]: http://builds.handlebarsjs.com.s3.amazonaws.com/bucket-listing.html?sort=lastmod&sortdir=desc
|
||||
[generator-release]: https://github.com/walmartlabs/generator-release
|
||||
[pull-request]: https://github.com/wycats/handlebars.js/pull/new/master
|
||||
|
||||
+3
-3
@@ -127,7 +127,7 @@ extension = new RegExp('\\.' + extension + '$');
|
||||
var output = [];
|
||||
if (!argv.simple) {
|
||||
if (argv.amd) {
|
||||
output.push('define([\'' + argv.handlebarPath + 'handlebars\'], function(Handlebars) {\n Handlebars = Handlebars["default"];');
|
||||
output.push('define([\'' + argv.handlebarPath + 'handlebars.runtime\'], function(Handlebars) {\n Handlebars = Handlebars["default"];');
|
||||
} else if (argv.commonjs) {
|
||||
output.push('var Handlebars = require("' + argv.commonjs + '");');
|
||||
} else {
|
||||
@@ -150,9 +150,9 @@ function processTemplate(template, root) {
|
||||
processTemplate(path, root || template);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
} else if (extension.test(path)) {
|
||||
var data = fs.readFileSync(path, 'utf8');
|
||||
|
||||
|
||||
if (argv.bom && data.indexOf('\uFEFF') === 0) {
|
||||
data = data.substring(1);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "handlebars",
|
||||
"version": "1.1.1",
|
||||
"version": "1.2.0",
|
||||
"main": "handlebars.js",
|
||||
"dependencies": {}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<package>
|
||||
<metadata>
|
||||
<id>handlebars.js</id>
|
||||
<version>1.1.1</version>
|
||||
<version>1.2.0</version>
|
||||
<authors>handlebars.js Authors</authors>
|
||||
<licenseUrl>https://github.com/wycats/handlebars.js/blob/master/LICENSE</licenseUrl>
|
||||
<projectUrl>https://github.com/wycats/handlebars.js/</projectUrl>
|
||||
|
||||
+2
-1
@@ -1,7 +1,8 @@
|
||||
/*globals Handlebars: true */
|
||||
import Handlebars from "./handlebars.runtime";
|
||||
|
||||
// Compiler imports
|
||||
module AST from "./handlebars/compiler/ast";
|
||||
import AST from "./handlebars/compiler/ast";
|
||||
import { parser as Parser, parse } from "./handlebars/compiler/base";
|
||||
import { Compiler, compile, precompile } from "./handlebars/compiler/compiler";
|
||||
import JavaScriptCompiler from "./handlebars/compiler/javascript-compiler";
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
/*globals Handlebars: true */
|
||||
module base from "./handlebars/base";
|
||||
|
||||
// Each of these augment the Handlebars object. No need to setup here.
|
||||
|
||||
+10
-21
@@ -1,8 +1,7 @@
|
||||
/*globals Exception, Utils */
|
||||
module Utils from "./utils";
|
||||
import Exception from "./exception";
|
||||
|
||||
export var VERSION = "1.1.1";
|
||||
export var VERSION = "1.2.0";
|
||||
export var COMPILER_REVISION = 4;
|
||||
|
||||
export var REVISION_CHANGES = {
|
||||
@@ -12,25 +11,11 @@ export var REVISION_CHANGES = {
|
||||
4: '>= 1.0.0'
|
||||
};
|
||||
|
||||
var toString = Object.prototype.toString,
|
||||
var isArray = Utils.isArray,
|
||||
isFunction = Utils.isFunction,
|
||||
toString = Utils.toString,
|
||||
objectType = '[object Object]';
|
||||
|
||||
// Sourced from lodash
|
||||
// https://github.com/bestiejs/lodash/blob/master/LICENSE.txt
|
||||
var isFunction = function(value) {
|
||||
return typeof value === 'function';
|
||||
};
|
||||
// fallback for older versions of Chrome and Safari
|
||||
if (isFunction(/x/)) {
|
||||
isFunction = function(value) {
|
||||
return typeof value === 'function' && toString.call(value) === '[object Function]';
|
||||
};
|
||||
}
|
||||
|
||||
function isArray(value) {
|
||||
return (value && typeof value === 'object') ? toString.call(value) === '[object Array]' : false;
|
||||
}
|
||||
|
||||
export function HandlebarsEnvironment(helpers, partials) {
|
||||
this.helpers = helpers || {};
|
||||
this.partials = partials || {};
|
||||
@@ -107,7 +92,7 @@ function registerDefaultHelpers(instance) {
|
||||
for(var j = context.length; i<j; i++) {
|
||||
if (data) {
|
||||
data.index = i;
|
||||
data.first = (i === 0)
|
||||
data.first = (i === 0);
|
||||
data.last = (i === (context.length-1));
|
||||
}
|
||||
ret = ret + fn(context[i], { data: data });
|
||||
@@ -115,7 +100,11 @@ function registerDefaultHelpers(instance) {
|
||||
} else {
|
||||
for(var key in context) {
|
||||
if(context.hasOwnProperty(key)) {
|
||||
if(data) { data.key = key; }
|
||||
if(data) {
|
||||
data.key = key;
|
||||
data.index = i;
|
||||
data.first = (i === 0);
|
||||
}
|
||||
ret = ret + fn(context[key], {data: data});
|
||||
i++;
|
||||
}
|
||||
|
||||
+151
-139
@@ -1,145 +1,157 @@
|
||||
import Exception from "../exception";
|
||||
|
||||
export function ProgramNode(statements, inverseStrip, inverse) {
|
||||
this.type = "program";
|
||||
this.statements = statements;
|
||||
this.strip = {};
|
||||
var AST = {
|
||||
ProgramNode: function(statements, inverseStrip, inverse) {
|
||||
this.type = "program";
|
||||
this.statements = statements;
|
||||
this.strip = {};
|
||||
|
||||
if(inverse) {
|
||||
this.inverse = new ProgramNode(inverse, inverseStrip);
|
||||
this.strip.right = inverseStrip.left;
|
||||
} else if (inverseStrip) {
|
||||
this.strip.left = inverseStrip.right;
|
||||
}
|
||||
}
|
||||
|
||||
export function MustacheNode(rawParams, hash, open, strip) {
|
||||
this.type = "mustache";
|
||||
this.hash = hash;
|
||||
this.strip = strip;
|
||||
|
||||
var escapeFlag = open[3] || open[2];
|
||||
this.escaped = escapeFlag !== '{' && escapeFlag !== '&';
|
||||
|
||||
var id = this.id = rawParams[0];
|
||||
var params = this.params = rawParams.slice(1);
|
||||
|
||||
// a mustache is an eligible helper if:
|
||||
// * its id is simple (a single part, not `this` or `..`)
|
||||
var eligibleHelper = this.eligibleHelper = id.isSimple;
|
||||
|
||||
// a mustache is definitely a helper if:
|
||||
// * it is an eligible helper, and
|
||||
// * it has at least one parameter or hash segment
|
||||
this.isHelper = eligibleHelper && (params.length || hash);
|
||||
|
||||
// if a mustache is an eligible helper but not a definite
|
||||
// helper, it is ambiguous, and will be resolved in a later
|
||||
// pass or at runtime.
|
||||
}
|
||||
|
||||
export function PartialNode(partialName, context, strip) {
|
||||
this.type = "partial";
|
||||
this.partialName = partialName;
|
||||
this.context = context;
|
||||
this.strip = strip;
|
||||
}
|
||||
|
||||
export function BlockNode(mustache, program, inverse, close) {
|
||||
if(mustache.id.original !== close.path.original) {
|
||||
throw new Exception(mustache.id.original + " doesn't match " + close.path.original);
|
||||
}
|
||||
|
||||
this.type = "block";
|
||||
this.mustache = mustache;
|
||||
this.program = program;
|
||||
this.inverse = inverse;
|
||||
|
||||
this.strip = {
|
||||
left: mustache.strip.left,
|
||||
right: close.strip.right
|
||||
};
|
||||
|
||||
(program || inverse).strip.left = mustache.strip.right;
|
||||
(inverse || program).strip.right = close.strip.left;
|
||||
|
||||
if (inverse && !program) {
|
||||
this.isInverse = true;
|
||||
}
|
||||
}
|
||||
|
||||
export function ContentNode(string) {
|
||||
this.type = "content";
|
||||
this.string = string;
|
||||
}
|
||||
|
||||
export function HashNode(pairs) {
|
||||
this.type = "hash";
|
||||
this.pairs = pairs;
|
||||
}
|
||||
|
||||
export function IdNode(parts) {
|
||||
this.type = "ID";
|
||||
|
||||
var original = "",
|
||||
dig = [],
|
||||
depth = 0;
|
||||
|
||||
for(var i=0,l=parts.length; i<l; i++) {
|
||||
var part = parts[i].part;
|
||||
original += (parts[i].separator || '') + part;
|
||||
|
||||
if (part === ".." || part === "." || part === "this") {
|
||||
if (dig.length > 0) { throw new Exception("Invalid path: " + original); }
|
||||
else if (part === "..") { depth++; }
|
||||
else { this.isScoped = true; }
|
||||
if(inverse) {
|
||||
this.inverse = new AST.ProgramNode(inverse, inverseStrip);
|
||||
this.strip.right = inverseStrip.left;
|
||||
} else if (inverseStrip) {
|
||||
this.strip.left = inverseStrip.right;
|
||||
}
|
||||
else { dig.push(part); }
|
||||
},
|
||||
|
||||
MustacheNode: function(rawParams, hash, open, strip) {
|
||||
this.type = "mustache";
|
||||
this.hash = hash;
|
||||
this.strip = strip;
|
||||
|
||||
// Open may be a string parsed from the parser or a passed boolean flag
|
||||
if (open != null && open.charAt) {
|
||||
// Must use charAt to support IE pre-10
|
||||
var escapeFlag = open.charAt(3) || open.charAt(2);
|
||||
this.escaped = escapeFlag !== '{' && escapeFlag !== '&';
|
||||
} else {
|
||||
this.escaped = !!open;
|
||||
}
|
||||
|
||||
var id = this.id = rawParams[0];
|
||||
var params = this.params = rawParams.slice(1);
|
||||
|
||||
// a mustache is an eligible helper if:
|
||||
// * its id is simple (a single part, not `this` or `..`)
|
||||
var eligibleHelper = this.eligibleHelper = id.isSimple;
|
||||
|
||||
// a mustache is definitely a helper if:
|
||||
// * it is an eligible helper, and
|
||||
// * it has at least one parameter or hash segment
|
||||
this.isHelper = eligibleHelper && (params.length || hash);
|
||||
|
||||
// if a mustache is an eligible helper but not a definite
|
||||
// helper, it is ambiguous, and will be resolved in a later
|
||||
// pass or at runtime.
|
||||
},
|
||||
|
||||
PartialNode: function(partialName, context, strip) {
|
||||
this.type = "partial";
|
||||
this.partialName = partialName;
|
||||
this.context = context;
|
||||
this.strip = strip;
|
||||
},
|
||||
|
||||
BlockNode: function(mustache, program, inverse, close) {
|
||||
if(mustache.id.original !== close.path.original) {
|
||||
throw new Exception(mustache.id.original + " doesn't match " + close.path.original);
|
||||
}
|
||||
|
||||
this.type = "block";
|
||||
this.mustache = mustache;
|
||||
this.program = program;
|
||||
this.inverse = inverse;
|
||||
|
||||
this.strip = {
|
||||
left: mustache.strip.left,
|
||||
right: close.strip.right
|
||||
};
|
||||
|
||||
(program || inverse).strip.left = mustache.strip.right;
|
||||
(inverse || program).strip.right = close.strip.left;
|
||||
|
||||
if (inverse && !program) {
|
||||
this.isInverse = true;
|
||||
}
|
||||
},
|
||||
|
||||
ContentNode: function(string) {
|
||||
this.type = "content";
|
||||
this.string = string;
|
||||
},
|
||||
|
||||
HashNode: function(pairs) {
|
||||
this.type = "hash";
|
||||
this.pairs = pairs;
|
||||
},
|
||||
|
||||
IdNode: function(parts) {
|
||||
this.type = "ID";
|
||||
|
||||
var original = "",
|
||||
dig = [],
|
||||
depth = 0;
|
||||
|
||||
for(var i=0,l=parts.length; i<l; i++) {
|
||||
var part = parts[i].part;
|
||||
original += (parts[i].separator || '') + part;
|
||||
|
||||
if (part === ".." || part === "." || part === "this") {
|
||||
if (dig.length > 0) { throw new Exception("Invalid path: " + original); }
|
||||
else if (part === "..") { depth++; }
|
||||
else { this.isScoped = true; }
|
||||
}
|
||||
else { dig.push(part); }
|
||||
}
|
||||
|
||||
this.original = original;
|
||||
this.parts = dig;
|
||||
this.string = dig.join('.');
|
||||
this.depth = depth;
|
||||
|
||||
// an ID is simple if it only has one part, and that part is not
|
||||
// `..` or `this`.
|
||||
this.isSimple = parts.length === 1 && !this.isScoped && depth === 0;
|
||||
|
||||
this.stringModeValue = this.string;
|
||||
},
|
||||
|
||||
PartialNameNode: function(name) {
|
||||
this.type = "PARTIAL_NAME";
|
||||
this.name = name.original;
|
||||
},
|
||||
|
||||
DataNode: function(id) {
|
||||
this.type = "DATA";
|
||||
this.id = id;
|
||||
},
|
||||
|
||||
StringNode: function(string) {
|
||||
this.type = "STRING";
|
||||
this.original =
|
||||
this.string =
|
||||
this.stringModeValue = string;
|
||||
},
|
||||
|
||||
IntegerNode: function(integer) {
|
||||
this.type = "INTEGER";
|
||||
this.original =
|
||||
this.integer = integer;
|
||||
this.stringModeValue = Number(integer);
|
||||
},
|
||||
|
||||
BooleanNode: function(bool) {
|
||||
this.type = "BOOLEAN";
|
||||
this.bool = bool;
|
||||
this.stringModeValue = bool === "true";
|
||||
},
|
||||
|
||||
CommentNode: function(comment) {
|
||||
this.type = "comment";
|
||||
this.comment = comment;
|
||||
}
|
||||
};
|
||||
|
||||
this.original = original;
|
||||
this.parts = dig;
|
||||
this.string = dig.join('.');
|
||||
this.depth = depth;
|
||||
|
||||
// an ID is simple if it only has one part, and that part is not
|
||||
// `..` or `this`.
|
||||
this.isSimple = parts.length === 1 && !this.isScoped && depth === 0;
|
||||
|
||||
this.stringModeValue = this.string;
|
||||
}
|
||||
|
||||
export function PartialNameNode(name) {
|
||||
this.type = "PARTIAL_NAME";
|
||||
this.name = name.original;
|
||||
}
|
||||
|
||||
export function DataNode(id) {
|
||||
this.type = "DATA";
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
export function StringNode(string) {
|
||||
this.type = "STRING";
|
||||
this.original =
|
||||
this.string =
|
||||
this.stringModeValue = string;
|
||||
}
|
||||
|
||||
export function IntegerNode(integer) {
|
||||
this.type = "INTEGER";
|
||||
this.original =
|
||||
this.integer = integer;
|
||||
this.stringModeValue = Number(integer);
|
||||
}
|
||||
|
||||
export function BooleanNode(bool) {
|
||||
this.type = "BOOLEAN";
|
||||
this.bool = bool;
|
||||
this.stringModeValue = bool === "true";
|
||||
}
|
||||
|
||||
export function CommentNode(comment) {
|
||||
this.type = "comment";
|
||||
this.comment = comment;
|
||||
}
|
||||
// Must be exported as an object rather than the root of the module as the jison lexer
|
||||
// most modify the object to operate properly.
|
||||
export default AST;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import parser from "./parser";
|
||||
module AST from "./ast";
|
||||
import AST from "./ast";
|
||||
|
||||
export { parser };
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import Exception from "../exception";
|
||||
import { parse } from "./base";
|
||||
import JavaScriptCompiler from "./javascript-compiler";
|
||||
module AST from "./ast";
|
||||
import AST from "./ast";
|
||||
|
||||
export function Compiler() {}
|
||||
|
||||
|
||||
@@ -32,6 +32,12 @@ JavaScriptCompiler.prototype = {
|
||||
}
|
||||
},
|
||||
|
||||
compilerInfo: function() {
|
||||
var revision = COMPILER_REVISION,
|
||||
versions = REVISION_CHANGES[revision];
|
||||
return "this.compilerInfo = ["+revision+",'"+versions+"'];\n";
|
||||
},
|
||||
|
||||
appendToBuffer: function(string) {
|
||||
if (this.environment.isSimple) {
|
||||
return "return " + string + ";";
|
||||
@@ -165,9 +171,7 @@ JavaScriptCompiler.prototype = {
|
||||
var source = this.mergeSource();
|
||||
|
||||
if (!this.isChild) {
|
||||
var revision = COMPILER_REVISION,
|
||||
versions = REVISION_CHANGES[revision];
|
||||
source = "this.compilerInfo = ["+revision+",'"+versions+"'];\n"+source;
|
||||
source = this.compilerInfo()+source;
|
||||
}
|
||||
|
||||
if (asObject) {
|
||||
|
||||
@@ -26,7 +26,6 @@ PrintVisitor.prototype.pad = function(string, newline) {
|
||||
PrintVisitor.prototype.program = function(program) {
|
||||
var out = "",
|
||||
statements = program.statements,
|
||||
inverse = program.inverse,
|
||||
i, l;
|
||||
|
||||
for(i=0, l=statements.length; i<l; i++) {
|
||||
|
||||
+13
-19
@@ -1,9 +1,8 @@
|
||||
/*global Utils */
|
||||
module Utils from "./utils";
|
||||
import Exception from "./exception";
|
||||
import { COMPILER_REVISION, REVISION_CHANGES } from "./base";
|
||||
|
||||
function checkRevision(compilerInfo) {
|
||||
export function checkRevision(compilerInfo) {
|
||||
var compilerRevision = compilerInfo && compilerInfo[0] || 1,
|
||||
currentRevision = COMPILER_REVISION;
|
||||
|
||||
@@ -28,25 +27,20 @@ export function template(templateSpec, env) {
|
||||
throw new Error("No environment passed to template");
|
||||
}
|
||||
|
||||
var invokePartialWrapper;
|
||||
if (env.compile) {
|
||||
invokePartialWrapper = function(partial, name, context, helpers, partials, data) {
|
||||
// TODO : Check this for all inputs and the options handling (partial flag, etc). This feels
|
||||
// like there should be a common exec path
|
||||
var result = invokePartial.apply(this, arguments);
|
||||
if (result) { return result; }
|
||||
// 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.
|
||||
var invokePartialWrapper = function(partial, name, context, helpers, partials, data) {
|
||||
var result = env.VM.invokePartial.apply(this, arguments);
|
||||
if (result != null) { return result; }
|
||||
|
||||
if (env.compile) {
|
||||
var options = { helpers: helpers, partials: partials, data: data };
|
||||
partials[name] = env.compile(partial, { data: data !== undefined }, env);
|
||||
return partials[name](context, options);
|
||||
};
|
||||
} else {
|
||||
invokePartialWrapper = function(partial, name /* , context, helpers, partials, data */) {
|
||||
var result = invokePartial.apply(this, arguments);
|
||||
if (result) { return result; }
|
||||
} else {
|
||||
throw new Exception("The partial " + name + " could not be compiled when running in runtime-only mode");
|
||||
};
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Just add water
|
||||
var container = {
|
||||
@@ -72,8 +66,8 @@ export function template(templateSpec, env) {
|
||||
}
|
||||
return ret;
|
||||
},
|
||||
programWithDepth: programWithDepth,
|
||||
noop: noop,
|
||||
programWithDepth: env.VM.programWithDepth,
|
||||
noop: env.VM.noop,
|
||||
compilerInfo: null
|
||||
};
|
||||
|
||||
@@ -95,7 +89,7 @@ export function template(templateSpec, env) {
|
||||
options.data);
|
||||
|
||||
if (!options.partial) {
|
||||
checkRevision(container.compilerInfo);
|
||||
env.VM.checkRevision(container.compilerInfo);
|
||||
}
|
||||
|
||||
return result;
|
||||
|
||||
+22
-3
@@ -1,7 +1,6 @@
|
||||
/*jshint -W004 */
|
||||
import SafeString from "./safe-string";
|
||||
|
||||
var isArray = Array.isArray;
|
||||
|
||||
var escape = {
|
||||
"&": "&",
|
||||
"<": "<",
|
||||
@@ -20,12 +19,32 @@ function escapeChar(chr) {
|
||||
|
||||
export function extend(obj, value) {
|
||||
for(var key in value) {
|
||||
if(value.hasOwnProperty(key)) {
|
||||
if(Object.prototype.hasOwnProperty.call(value, key)) {
|
||||
obj[key] = value[key];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export var toString = Object.prototype.toString;
|
||||
|
||||
// Sourced from lodash
|
||||
// https://github.com/bestiejs/lodash/blob/master/LICENSE.txt
|
||||
var isFunction = function(value) {
|
||||
return typeof value === 'function';
|
||||
};
|
||||
// fallback for older versions of Chrome and Safari
|
||||
if (isFunction(/x/)) {
|
||||
isFunction = function(value) {
|
||||
return typeof value === 'function' && toString.call(value) === '[object Function]';
|
||||
};
|
||||
}
|
||||
export var isFunction;
|
||||
|
||||
export var isArray = Array.isArray || function(value) {
|
||||
return (value && typeof value === 'object') ? toString.call(value) === '[object Array]' : false;
|
||||
};
|
||||
|
||||
|
||||
export function escapeExpression(string) {
|
||||
// don't escape SafeStrings, since they're already safe
|
||||
if (string instanceof SafeString) {
|
||||
|
||||
+9
-6
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "handlebars",
|
||||
"barename": "handlebars",
|
||||
"version": "1.1.1",
|
||||
"version": "1.2.0",
|
||||
"description": "Handlebars provides the power necessary to let you build semantic templates effectively with no frustration",
|
||||
"homepage": "http://www.handlebarsjs.com/",
|
||||
"keywords": [
|
||||
@@ -15,7 +15,7 @@
|
||||
"url": "https://github.com/wycats/handlebars.js.git"
|
||||
},
|
||||
"author": "Yehuda Katz",
|
||||
"license": "BSD",
|
||||
"license": "MIT",
|
||||
"readmeFilename": "README.md",
|
||||
"engines": {
|
||||
"node": ">=0.4.7"
|
||||
@@ -33,19 +33,22 @@
|
||||
"dustjs-linkedin": "~2.0.2",
|
||||
"eco": "~1.1.0-rc-3",
|
||||
"grunt": "~0.4.1",
|
||||
"grunt-cli": "~0.1.10",
|
||||
"grunt-contrib-clean": "~0.4.1",
|
||||
"grunt-contrib-concat": "~0.3.0",
|
||||
"grunt-contrib-connect": "~0.5.0",
|
||||
"grunt-contrib-copy": "~0.4.1",
|
||||
"grunt-contrib-jshint": "~0.6.3",
|
||||
"grunt-contrib-jshint": "~0.7.2",
|
||||
"grunt-contrib-requirejs": "~0.4.1",
|
||||
"grunt-contrib-uglify": "~0.2.2",
|
||||
"grunt-es6-module-transpiler": "joefiorini/grunt-es6-module-transpiler",
|
||||
"es6-module-packager": "*",
|
||||
"grunt-contrib-watch": "~0.5.3",
|
||||
"grunt-saucelabs": "~4.1.2",
|
||||
"es6-module-packager": "0.x",
|
||||
"jison": "~0.3.0",
|
||||
"keen.io": "0.0.3",
|
||||
"mocha": "*",
|
||||
"mustache": "~0.7.2",
|
||||
"semver": "~2.1.0",
|
||||
"should": "~1.2.2",
|
||||
"underscore": "~1.5.1"
|
||||
},
|
||||
"main": "lib/index.js",
|
||||
|
||||
+37
-1
@@ -2,7 +2,43 @@
|
||||
|
||||
## Development
|
||||
|
||||
[Commits](https://github.com/wycats/handlebars.js/compare/v1.1.1...master)
|
||||
[Commits](https://github.com/wycats/handlebars.js/compare/v1.2.0...master)
|
||||
|
||||
## v1.2.0 - December 23rd, 2013
|
||||
- [#675](https://github.com/wycats/handlebars.js/issues/675) - Cannot compile empty template for partial ([@erwinw](https://api.github.com/users/erwinw))
|
||||
- [#677](https://github.com/wycats/handlebars.js/issues/677) - Triple brace statements fail under IE ([@hamzaCM](https://api.github.com/users/hamzaCM))
|
||||
- [#655](https://github.com/wycats/handlebars.js/issues/655) - Loading Handlebars using bower ([@niki4810](https://api.github.com/users/niki4810))
|
||||
- [#657](https://github.com/wycats/handlebars.js/pull/657) - Fixes issue where cli compiles non handlebars templates ([@chrishoage](https://api.github.com/users/chrishoage))
|
||||
- [#681](https://github.com/wycats/handlebars.js/pull/681) - Adds in-browser testing and Saucelabs CI ([@kpdecker](https://api.github.com/users/kpdecker))
|
||||
- [#661](https://github.com/wycats/handlebars.js/pull/661) - Add @first and @index to #each object iteration ([@cgp](https://api.github.com/users/cgp))
|
||||
- [#650](https://github.com/wycats/handlebars.js/pull/650) - Handlebars is MIT-licensed ([@thomasboyt](https://api.github.com/users/thomasboyt))
|
||||
- [#641](https://github.com/wycats/handlebars.js/pull/641) - Document ember testing process ([@kpdecker](https://api.github.com/users/kpdecker))
|
||||
- [#662](https://github.com/wycats/handlebars.js/issues/662) - handlebars-source 1.1.2 is missing from RubyGems.
|
||||
- [#656](https://github.com/wycats/handlebars.js/issues/656) - Expose COMPILER_REVISION checks as a hook ([@machty](https://api.github.com/users/machty))
|
||||
- [#668](https://github.com/wycats/handlebars.js/issues/668) - Consider publishing handlebars-runtime as a separate module on npm ([@dlmanning](https://api.github.com/users/dlmanning))
|
||||
- [#679](https://github.com/wycats/handlebars.js/issues/679) - Unable to override invokePartial ([@mattbrailsford](https://api.github.com/users/mattbrailsford))
|
||||
- [#646](https://github.com/wycats/handlebars.js/pull/646) - Fix "\\{{" immediately following "\{{" ([@dmarcotte](https://api.github.com/users/dmarcotte))
|
||||
- Allow extend to work with non-prototyped objects - eb53f2e
|
||||
- Add JavascriptCompiler public API tests - 1a751b2
|
||||
- Add AST test coverage for more complex paths - ddea5be
|
||||
- Fix handling of boolean escape in MustacheNode - b4968bb
|
||||
|
||||
Compatibility notes:
|
||||
- `@index` and `@first` are now supported for `each` iteration on objects
|
||||
- `Handlebars.VM.checkRevision` and `Handlebars.JavaScriptCompiler.prototype.compilerInfo` now available to modify the version checking behavior.
|
||||
- Browserify users may link to the runtime library via `require('handlebars/runtime')`
|
||||
|
||||
[Commits](https://github.com/wycats/handlebars.js/compare/v1.1.2...v1.2.0)
|
||||
|
||||
## v1.1.2 - November 5th, 2013
|
||||
|
||||
- [#645](https://github.com/wycats/handlebars.js/issues/645) - 1.1.1 fails under IE8 ([@kpdecker](https://api.github.com/users/kpdecker))
|
||||
- [#644](https://github.com/wycats/handlebars.js/issues/644) - Using precompiled templates (AMD mode) with handlebars.runtime 1.1.1 ([@fddima](https://api.github.com/users/fddima))
|
||||
|
||||
- Add simple binary utility tests - 96a45a4
|
||||
- Fix empty string compilation - eea708a
|
||||
|
||||
[Commits](https://github.com/wycats/handlebars.js/compare/v1.1.1...v1.1.2)
|
||||
|
||||
## v1.1.1 - November 4th, 2013
|
||||
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
// Create a simple path alias to allow browserify to resolve
|
||||
// the runtime on a supported path.
|
||||
module.exports = require('./dist/cjs/handlebars.runtime');
|
||||
+82
@@ -0,0 +1,82 @@
|
||||
/*global Handlebars, handlebarsEnv, shouldThrow */
|
||||
describe('ast', function() {
|
||||
if (!Handlebars.AST) {
|
||||
return;
|
||||
}
|
||||
|
||||
describe('MustacheNode', function() {
|
||||
function testEscape(open, expected) {
|
||||
var mustache = new handlebarsEnv.AST.MustacheNode([{}], {}, open, false);
|
||||
equals(mustache.escaped, expected);
|
||||
}
|
||||
|
||||
it('should store args', function() {
|
||||
var id = {isSimple: true},
|
||||
hash = {},
|
||||
mustache = new handlebarsEnv.AST.MustacheNode([id, 'param1'], hash, '', false);
|
||||
equals(mustache.type, 'mustache');
|
||||
equals(mustache.hash, hash);
|
||||
equals(mustache.escaped, true);
|
||||
equals(mustache.id, id);
|
||||
equals(mustache.params.length, 1);
|
||||
equals(mustache.params[0], 'param1');
|
||||
equals(!!mustache.isHelper, true);
|
||||
});
|
||||
it('should accept token for escape', function() {
|
||||
testEscape('{{', true);
|
||||
testEscape('{{~', true);
|
||||
testEscape('{{#', true);
|
||||
testEscape('{{~#', true);
|
||||
testEscape('{{/', true);
|
||||
testEscape('{{~/', true);
|
||||
testEscape('{{^', true);
|
||||
testEscape('{{~^', true);
|
||||
testEscape('{', true);
|
||||
testEscape('{', true);
|
||||
|
||||
testEscape('{{&', false);
|
||||
testEscape('{{~&', false);
|
||||
testEscape('{{{', false);
|
||||
testEscape('{{~{', false);
|
||||
});
|
||||
it('should accept boolean for escape', function() {
|
||||
testEscape(true, true);
|
||||
testEscape({}, true);
|
||||
|
||||
testEscape(false, false);
|
||||
testEscape(undefined, false);
|
||||
});
|
||||
});
|
||||
describe('BlockNode', function() {
|
||||
it('should throw on mustache mismatch', function() {
|
||||
shouldThrow(function() {
|
||||
new handlebarsEnv.AST.BlockNode({id: {original: 'foo'}}, {}, {}, {path: {original: 'bar'}});
|
||||
}, Handlebars.Exception, "foo doesn't match bar");
|
||||
});
|
||||
});
|
||||
describe('IdNode', function() {
|
||||
it('should throw on invalid path', function() {
|
||||
shouldThrow(function() {
|
||||
new handlebarsEnv.AST.IdNode([
|
||||
{part: 'foo'},
|
||||
{part: '..'},
|
||||
{part: 'bar'}
|
||||
]);
|
||||
}, Handlebars.Exception, "Invalid path: foo..");
|
||||
shouldThrow(function() {
|
||||
new handlebarsEnv.AST.IdNode([
|
||||
{part: 'foo'},
|
||||
{part: '.'},
|
||||
{part: 'bar'}
|
||||
]);
|
||||
}, Handlebars.Exception, "Invalid path: foo.");
|
||||
shouldThrow(function() {
|
||||
new handlebarsEnv.AST.IdNode([
|
||||
{part: 'foo'},
|
||||
{part: 'this'},
|
||||
{part: 'bar'}
|
||||
]);
|
||||
}, Handlebars.Exception, "Invalid path: foothis");
|
||||
});
|
||||
});
|
||||
});
|
||||
+5
-4
@@ -1,3 +1,4 @@
|
||||
/*global CompilerContext, Handlebars, beforeEach, shouldCompileTo */
|
||||
global.handlebarsEnv = null;
|
||||
|
||||
beforeEach(function() {
|
||||
@@ -160,9 +161,9 @@ describe("basic context", function() {
|
||||
|
||||
it("this keyword nested inside path", function() {
|
||||
var string = "{{#hellos}}{{text/this/foo}}{{/hellos}}";
|
||||
(function() {
|
||||
shouldThrow(function() {
|
||||
CompilerContext.compile(string);
|
||||
}).should.throw(Error);
|
||||
}, Error);
|
||||
});
|
||||
|
||||
it("this keyword in helpers", function() {
|
||||
@@ -181,8 +182,8 @@ describe("basic context", function() {
|
||||
|
||||
it("this keyword nested inside helpers param", function() {
|
||||
var string = "{{#hellos}}{{foo text/this/foo}}{{/hellos}}";
|
||||
(function() {
|
||||
shouldThrow(function() {
|
||||
CompilerContext.compile(string);
|
||||
}).should.throw(Error);
|
||||
}, Error);
|
||||
});
|
||||
});
|
||||
|
||||
+2
-2
@@ -42,9 +42,9 @@ describe('blocks', function() {
|
||||
it("block with complex lookup using nested context", function() {
|
||||
var string = "{{#goodbyes}}{{text}} cruel {{foo/../name}}! {{/goodbyes}}";
|
||||
|
||||
(function() {
|
||||
shouldThrow(function() {
|
||||
CompilerContext.compile(string);
|
||||
}).should.throw(Error);
|
||||
}, Error);
|
||||
});
|
||||
|
||||
it("block with deep nested complex lookup", function() {
|
||||
|
||||
+22
-1
@@ -73,7 +73,7 @@ describe('builtin helpers', function() {
|
||||
var expected1 = "<b>#1</b>. goodbye! 2. GOODBYE! cruel world!";
|
||||
var expected2 = "2. GOODBYE! <b>#1</b>. goodbye! cruel world!";
|
||||
|
||||
(actual === expected1 || actual === expected2).should.equal(true, "each with object argument iterates over the contents when not empty");
|
||||
equals(actual === expected1 || actual === expected2, true, "each with object argument iterates over the contents when not empty");
|
||||
shouldCompileTo(string, {goodbyes: [], world: "world"}, "cruel world!",
|
||||
"each with object argument ignores the contents when empty");
|
||||
});
|
||||
@@ -98,6 +98,17 @@ describe('builtin helpers', function() {
|
||||
equal(result, "0. goodbye! 0 1 2 After 0 1. Goodbye! 0 1 2 After 1 2. GOODBYE! 0 1 2 After 2 cruel world!", "The @index variable is used");
|
||||
});
|
||||
|
||||
it("each object with @index", function() {
|
||||
var string = "{{#each goodbyes}}{{@index}}. {{text}}! {{/each}}cruel {{world}}!";
|
||||
var hash = {goodbyes: {'a': {text: "goodbye"}, b: {text: "Goodbye"}, c: {text: "GOODBYE"}}, world: "world"};
|
||||
|
||||
var template = CompilerContext.compile(string);
|
||||
var result = template(hash);
|
||||
|
||||
equal(result, "0. goodbye! 1. Goodbye! 2. GOODBYE! cruel world!", "The @index variable is used");
|
||||
});
|
||||
|
||||
|
||||
it("each with @first", function() {
|
||||
var string = "{{#each goodbyes}}{{#if @first}}{{text}}! {{/if}}{{/each}}cruel {{world}}!";
|
||||
var hash = {goodbyes: [{text: "goodbye"}, {text: "Goodbye"}, {text: "GOODBYE"}], world: "world"};
|
||||
@@ -118,6 +129,16 @@ describe('builtin helpers', function() {
|
||||
equal(result, "(goodbye! goodbye! goodbye!) (goodbye!) (goodbye!) cruel world!", "The @first variable is used");
|
||||
});
|
||||
|
||||
it("each object with @first", function() {
|
||||
var string = "{{#each goodbyes}}{{#if @first}}{{text}}! {{/if}}{{/each}}cruel {{world}}!";
|
||||
var hash = {goodbyes: {'foo': {text: "goodbye"}, bar: {text: "Goodbye"}}, world: "world"};
|
||||
|
||||
var template = CompilerContext.compile(string);
|
||||
var result = template(hash);
|
||||
|
||||
equal(result, "goodbye! cruel world!", "The @first variable is used");
|
||||
});
|
||||
|
||||
it("each with @last", function() {
|
||||
var string = "{{#each goodbyes}}{{#if @last}}{{text}}! {{/if}}{{/each}}cruel {{world}}!";
|
||||
var hash = {goodbyes: [{text: "goodbye"}, {text: "Goodbye"}, {text: "GOODBYE"}], world: "world"};
|
||||
|
||||
+7
-7
@@ -1,4 +1,4 @@
|
||||
/*global CompilerContext */
|
||||
/*global CompilerContext, Handlebars, handlebarsEnv, shouldThrow */
|
||||
describe('data', function() {
|
||||
it("passing in data to a compiled function that expects data - works with helpers", function() {
|
||||
var template = CompilerContext.compile("{{hello}}", {data: true});
|
||||
@@ -88,25 +88,25 @@ describe('data', function() {
|
||||
it("parameter data throws when using this scope references", function() {
|
||||
var string = "{{#goodbyes}}{{text}} cruel {{@./name}}! {{/goodbyes}}";
|
||||
|
||||
(function() {
|
||||
shouldThrow(function() {
|
||||
CompilerContext.compile(string);
|
||||
}).should.throw(Error);
|
||||
}, Error);
|
||||
});
|
||||
|
||||
it("parameter data throws when using parent scope references", function() {
|
||||
var string = "{{#goodbyes}}{{text}} cruel {{@../name}}! {{/goodbyes}}";
|
||||
|
||||
(function() {
|
||||
shouldThrow(function() {
|
||||
CompilerContext.compile(string);
|
||||
}).should.throw(Error);
|
||||
}, Error);
|
||||
});
|
||||
|
||||
it("parameter data throws when using complex scope references", function() {
|
||||
var string = "{{#goodbyes}}{{text}} cruel {{@foo/../name}}! {{/goodbyes}}";
|
||||
|
||||
(function() {
|
||||
shouldThrow(function() {
|
||||
CompilerContext.compile(string);
|
||||
}).should.throw(Error);
|
||||
}, Error);
|
||||
});
|
||||
|
||||
it("data is inherited downstream", function() {
|
||||
|
||||
Vendored
+24
-4
@@ -1,12 +1,12 @@
|
||||
global.should = require('should');
|
||||
|
||||
global.shouldCompileTo = function(string, hashOrArray, expected, message) {
|
||||
shouldCompileToWithPartials(string, hashOrArray, false, expected, message);
|
||||
};
|
||||
|
||||
global.shouldCompileToWithPartials = function(string, hashOrArray, partials, expected, message) {
|
||||
var result = compileWithPartials(string, hashOrArray, partials);
|
||||
result.should.equal(expected, "'" + expected + "' should === '" + result + "': " + message);
|
||||
if (result !== expected) {
|
||||
throw new Error("'" + expected + "' should === '" + result + "': " + message);
|
||||
}
|
||||
};
|
||||
|
||||
global.compileWithPartials = function(string, hashOrArray, partials) {
|
||||
@@ -24,5 +24,25 @@ global.compileWithPartials = function(string, hashOrArray, partials) {
|
||||
|
||||
|
||||
global.equals = global.equal = function(a, b, msg) {
|
||||
a.should.equal(b, msg || '');
|
||||
if (a !== b) {
|
||||
throw new Error("'" + b + "' should === '" + a + "'" + (msg ? ": " + msg : ''));
|
||||
}
|
||||
};
|
||||
|
||||
global.shouldThrow = function(callback, type, msg) {
|
||||
var failed;
|
||||
try {
|
||||
callback();
|
||||
failed = true;
|
||||
} catch (err) {
|
||||
if (type && !(err instanceof type)) {
|
||||
throw new Error('Type failure');
|
||||
}
|
||||
if (msg && !(msg.test ? msg.test(err.message) : msg === err.message)) {
|
||||
throw new Error('Message failure');
|
||||
}
|
||||
}
|
||||
if (failed) {
|
||||
throw new Error('It failed to throw');
|
||||
}
|
||||
};
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
define(['handlebars.runtime'], function(Handlebars) {
|
||||
Handlebars = Handlebars["default"]; var template = Handlebars.template, templates = Handlebars.templates = Handlebars.templates || {};
|
||||
return templates['empty'] = template(function (Handlebars,depth0,helpers,partials,data) {
|
||||
this.compilerInfo = [4,'>= 1.0.0'];
|
||||
helpers = this.merge(helpers, Handlebars.helpers); data = data || {};
|
||||
var buffer = "";
|
||||
|
||||
|
||||
return buffer;
|
||||
});
|
||||
});
|
||||
+10
-10
@@ -1,4 +1,4 @@
|
||||
/*global CompilerContext, shouldCompileTo, shouldCompileToWithPartials */
|
||||
/*global CompilerContext, Handlebars, shouldCompileTo, shouldCompileToWithPartials, shouldThrow, handlebarsEnv */
|
||||
describe('helpers', function() {
|
||||
it("helper with complex lookup$", function() {
|
||||
var string = "{{#goodbyes}}{{{link ../prefix}}}{{/goodbyes}}";
|
||||
@@ -212,10 +212,10 @@ describe('helpers', function() {
|
||||
});
|
||||
|
||||
it("using a quote in the middle of a parameter raises an error", function() {
|
||||
(function() {
|
||||
var string = 'Message: {{hello wo"rld"}}';
|
||||
var string = 'Message: {{hello wo"rld"}}';
|
||||
shouldThrow(function() {
|
||||
CompilerContext.compile(string);
|
||||
}).should.throw(Error);
|
||||
}, Error);
|
||||
});
|
||||
|
||||
it("escaping a String is possible", function(){
|
||||
@@ -351,10 +351,10 @@ describe('helpers', function() {
|
||||
|
||||
describe("helperMissing", function() {
|
||||
it("if a context is not found, helperMissing is used", function() {
|
||||
(function() {
|
||||
shouldThrow(function() {
|
||||
var template = CompilerContext.compile("{{hello}} {{link_to world}}");
|
||||
template({});
|
||||
}).should.throw(/Missing helper: 'link_to'/);
|
||||
}, undefined, /Missing helper: 'link_to'/);
|
||||
});
|
||||
|
||||
it("if a context is not found, custom helperMissing is used", function() {
|
||||
@@ -417,9 +417,9 @@ describe('helpers', function() {
|
||||
equal(result, "bar", "'bar' should === '" + result);
|
||||
});
|
||||
it("Unknown helper call in knownHelpers only mode should throw", function() {
|
||||
(function() {
|
||||
shouldThrow(function() {
|
||||
CompilerContext.compile("{{typeof hello}}", {knownHelpersOnly: true});
|
||||
}).should.throw(Error);
|
||||
}, Error);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -491,7 +491,7 @@ describe('helpers', function() {
|
||||
|
||||
cruel: function(world) {
|
||||
return "cruel " + world.toUpperCase();
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
var context = {
|
||||
@@ -513,7 +513,7 @@ describe('helpers', function() {
|
||||
|
||||
cruel: function(world) {
|
||||
return "cruel " + world.toUpperCase();
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
var context = {
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
<html>
|
||||
<head>
|
||||
<title>Mocha</title>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
|
||||
<link rel="stylesheet" href="/node_modules/mocha/mocha.css" />
|
||||
<style>
|
||||
.headless .suite > h1,
|
||||
.headless .test.pass {
|
||||
display: none;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
// Show only errors in "headless", non-interactive mode.
|
||||
if (/headless=true/.test(location.href)) {
|
||||
document.documentElement.className = 'headless';
|
||||
}
|
||||
</script>
|
||||
<script src="/node_modules/mocha/mocha.js"></script>
|
||||
<script>
|
||||
mocha.setup('bdd');
|
||||
</script>
|
||||
<script src="/dist/handlebars.js"></script>
|
||||
|
||||
<script src="/spec/env/common.js"></script>
|
||||
<script>
|
||||
var CompilerContext = {
|
||||
compile: function(template, options) {
|
||||
var templateSpec = handlebarsEnv.precompile(template, options);
|
||||
return handlebarsEnv.template(safeEval(templateSpec));
|
||||
},
|
||||
compileWithPartial: function(template, options) {
|
||||
return handlebarsEnv.compile(template, options);
|
||||
}
|
||||
};
|
||||
|
||||
function safeEval(templateSpec) {
|
||||
var ret;
|
||||
eval('ret = ' + templateSpec);
|
||||
return ret;
|
||||
}
|
||||
</script>
|
||||
<script src="/tmp/tests.js"></script>
|
||||
<script>
|
||||
onload = function(){
|
||||
// The test harness leaks under FF. We should have decent global leak coverage from other tests
|
||||
if (!navigator.userAgent.match(/Firefox\/([\d.]+)/)) {
|
||||
mocha.checkLeaks();
|
||||
}
|
||||
mocha.run();
|
||||
};
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<div id="mocha"></div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,69 @@
|
||||
/*global Handlebars, beforeEach, handlebarsEnv, shouldCompileTo */
|
||||
describe('javascript-compiler api', function() {
|
||||
if (!Handlebars.JavaScriptCompiler) {
|
||||
return;
|
||||
}
|
||||
|
||||
describe('#nameLookup', function() {
|
||||
var $superName;
|
||||
beforeEach(function() {
|
||||
$superName = handlebarsEnv.JavaScriptCompiler.prototype.nameLookup;
|
||||
});
|
||||
afterEach(function() {
|
||||
handlebarsEnv.JavaScriptCompiler.prototype.nameLookup = $superName;
|
||||
});
|
||||
|
||||
it('should allow override', function() {
|
||||
handlebarsEnv.JavaScriptCompiler.prototype.nameLookup = function(parent, name) {
|
||||
return parent + '.bar_' + name;
|
||||
};
|
||||
shouldCompileTo("{{foo}}", { bar_foo: "food" }, "food");
|
||||
});
|
||||
});
|
||||
describe('#compilerInfo', function() {
|
||||
var $superCheck, $superInfo;
|
||||
beforeEach(function() {
|
||||
$superCheck = handlebarsEnv.VM.checkRevision;
|
||||
$superInfo = handlebarsEnv.JavaScriptCompiler.prototype.compilerInfo;
|
||||
});
|
||||
afterEach(function() {
|
||||
handlebarsEnv.VM.checkRevision = $superCheck;
|
||||
handlebarsEnv.JavaScriptCompiler.prototype.compilerInfo = $superInfo;
|
||||
});
|
||||
it('should allow compilerInfo override', function() {
|
||||
handlebarsEnv.JavaScriptCompiler.prototype.compilerInfo = function() {
|
||||
return 'this.compilerInfo = "crazy";';
|
||||
};
|
||||
handlebarsEnv.VM.checkRevision = function(compilerInfo) {
|
||||
if (compilerInfo !== 'crazy') {
|
||||
throw new Error('It didn\'t work');
|
||||
}
|
||||
};
|
||||
shouldCompileTo("{{foo}} ", { foo: "food" }, "food ");
|
||||
});
|
||||
});
|
||||
describe('buffer', function() {
|
||||
var $superAppend, $superCreate;
|
||||
beforeEach(function() {
|
||||
$superAppend = handlebarsEnv.JavaScriptCompiler.prototype.appendToBuffer;
|
||||
$superCreate = handlebarsEnv.JavaScriptCompiler.prototype.initializeBuffer;
|
||||
});
|
||||
afterEach(function() {
|
||||
handlebarsEnv.JavaScriptCompiler.prototype.appendToBuffer = $superAppend;
|
||||
handlebarsEnv.JavaScriptCompiler.prototype.initializeBuffer = $superCreate;
|
||||
});
|
||||
|
||||
it('should allow init buffer override', function() {
|
||||
handlebarsEnv.JavaScriptCompiler.prototype.initializeBuffer = function() {
|
||||
return this.quotedString('foo_');
|
||||
};
|
||||
shouldCompileTo("{{foo}} ", { foo: "food" }, "foo_food ");
|
||||
});
|
||||
it('should allow append buffer override', function() {
|
||||
handlebarsEnv.JavaScriptCompiler.prototype.appendToBuffer = function(string) {
|
||||
return $superAppend.call(this, string + ' + "_foo"');
|
||||
};
|
||||
shouldCompileTo("{{foo}}", { foo: "food" }, "food_foo");
|
||||
});
|
||||
});
|
||||
});
|
||||
+59
-58
@@ -1,3 +1,4 @@
|
||||
/*global Handlebars */
|
||||
describe('parser', function() {
|
||||
if (!Handlebars.print) {
|
||||
return;
|
||||
@@ -9,165 +10,165 @@ describe('parser', function() {
|
||||
}
|
||||
|
||||
it('parses simple mustaches', function() {
|
||||
ast_for('{{foo}}').should.equal("{{ ID:foo [] }}\n");
|
||||
ast_for('{{foo?}}').should.equal("{{ ID:foo? [] }}\n");
|
||||
ast_for('{{foo_}}').should.equal("{{ ID:foo_ [] }}\n");
|
||||
ast_for('{{foo-}}').should.equal("{{ ID:foo- [] }}\n");
|
||||
ast_for('{{foo:}}').should.equal("{{ ID:foo: [] }}\n");
|
||||
equals(ast_for('{{foo}}'), "{{ ID:foo [] }}\n");
|
||||
equals(ast_for('{{foo?}}'), "{{ ID:foo? [] }}\n");
|
||||
equals(ast_for('{{foo_}}'), "{{ ID:foo_ [] }}\n");
|
||||
equals(ast_for('{{foo-}}'), "{{ ID:foo- [] }}\n");
|
||||
equals(ast_for('{{foo:}}'), "{{ ID:foo: [] }}\n");
|
||||
});
|
||||
|
||||
it('parses simple mustaches with data', function() {
|
||||
ast_for("{{@foo}}").should.equal("{{ @ID:foo [] }}\n");
|
||||
equals(ast_for("{{@foo}}"), "{{ @ID:foo [] }}\n");
|
||||
});
|
||||
|
||||
it('parses mustaches with paths', function() {
|
||||
ast_for("{{foo/bar}}").should.equal("{{ PATH:foo/bar [] }}\n");
|
||||
equals(ast_for("{{foo/bar}}"), "{{ PATH:foo/bar [] }}\n");
|
||||
});
|
||||
|
||||
it('parses mustaches with this/foo', function() {
|
||||
ast_for("{{this/foo}}").should.equal("{{ ID:foo [] }}\n");
|
||||
equals(ast_for("{{this/foo}}"), "{{ ID:foo [] }}\n");
|
||||
});
|
||||
|
||||
it('parses mustaches with - in a path', function() {
|
||||
ast_for("{{foo-bar}}").should.equal("{{ ID:foo-bar [] }}\n");
|
||||
equals(ast_for("{{foo-bar}}"), "{{ ID:foo-bar [] }}\n");
|
||||
});
|
||||
|
||||
it('parses mustaches with parameters', function() {
|
||||
ast_for("{{foo bar}}").should.equal("{{ ID:foo [ID:bar] }}\n");
|
||||
equals(ast_for("{{foo bar}}"), "{{ ID:foo [ID:bar] }}\n");
|
||||
});
|
||||
|
||||
it('parses mustaches with string parameters', function() {
|
||||
ast_for("{{foo bar \"baz\" }}").should.equal('{{ ID:foo [ID:bar, "baz"] }}\n');
|
||||
equals(ast_for("{{foo bar \"baz\" }}"), '{{ ID:foo [ID:bar, "baz"] }}\n');
|
||||
});
|
||||
|
||||
it('parses mustaches with INTEGER parameters', function() {
|
||||
ast_for("{{foo 1}}").should.equal("{{ ID:foo [INTEGER{1}] }}\n");
|
||||
equals(ast_for("{{foo 1}}"), "{{ ID:foo [INTEGER{1}] }}\n");
|
||||
});
|
||||
|
||||
it('parses mustaches with BOOLEAN parameters', function() {
|
||||
ast_for("{{foo true}}").should.equal("{{ ID:foo [BOOLEAN{true}] }}\n");
|
||||
ast_for("{{foo false}}").should.equal("{{ ID:foo [BOOLEAN{false}] }}\n");
|
||||
equals(ast_for("{{foo true}}"), "{{ ID:foo [BOOLEAN{true}] }}\n");
|
||||
equals(ast_for("{{foo false}}"), "{{ ID:foo [BOOLEAN{false}] }}\n");
|
||||
});
|
||||
|
||||
it('parses mutaches with DATA parameters', function() {
|
||||
ast_for("{{foo @bar}}").should.equal("{{ ID:foo [@ID:bar] }}\n");
|
||||
equals(ast_for("{{foo @bar}}"), "{{ ID:foo [@ID:bar] }}\n");
|
||||
});
|
||||
|
||||
it('parses mustaches with hash arguments', function() {
|
||||
ast_for("{{foo bar=baz}}").should.equal("{{ ID:foo [] HASH{bar=ID:baz} }}\n");
|
||||
ast_for("{{foo bar=1}}").should.equal("{{ ID:foo [] HASH{bar=INTEGER{1}} }}\n");
|
||||
ast_for("{{foo bar=true}}").should.equal("{{ ID:foo [] HASH{bar=BOOLEAN{true}} }}\n");
|
||||
ast_for("{{foo bar=false}}").should.equal("{{ ID:foo [] HASH{bar=BOOLEAN{false}} }}\n");
|
||||
ast_for("{{foo bar=@baz}}").should.equal("{{ ID:foo [] HASH{bar=@ID:baz} }}\n");
|
||||
equals(ast_for("{{foo bar=baz}}"), "{{ ID:foo [] HASH{bar=ID:baz} }}\n");
|
||||
equals(ast_for("{{foo bar=1}}"), "{{ ID:foo [] HASH{bar=INTEGER{1}} }}\n");
|
||||
equals(ast_for("{{foo bar=true}}"), "{{ ID:foo [] HASH{bar=BOOLEAN{true}} }}\n");
|
||||
equals(ast_for("{{foo bar=false}}"), "{{ ID:foo [] HASH{bar=BOOLEAN{false}} }}\n");
|
||||
equals(ast_for("{{foo bar=@baz}}"), "{{ ID:foo [] HASH{bar=@ID:baz} }}\n");
|
||||
|
||||
ast_for("{{foo bar=baz bat=bam}}").should.equal("{{ ID:foo [] HASH{bar=ID:baz, bat=ID:bam} }}\n");
|
||||
ast_for("{{foo bar=baz bat=\"bam\"}}").should.equal('{{ ID:foo [] HASH{bar=ID:baz, bat="bam"} }}\n');
|
||||
equals(ast_for("{{foo bar=baz bat=bam}}"), "{{ ID:foo [] HASH{bar=ID:baz, bat=ID:bam} }}\n");
|
||||
equals(ast_for("{{foo bar=baz bat=\"bam\"}}"), '{{ ID:foo [] HASH{bar=ID:baz, bat="bam"} }}\n');
|
||||
|
||||
ast_for("{{foo bat='bam'}}").should.equal('{{ ID:foo [] HASH{bat="bam"} }}\n');
|
||||
equals(ast_for("{{foo bat='bam'}}"), '{{ ID:foo [] HASH{bat="bam"} }}\n');
|
||||
|
||||
ast_for("{{foo omg bar=baz bat=\"bam\"}}").should.equal('{{ ID:foo [ID:omg] HASH{bar=ID:baz, bat="bam"} }}\n');
|
||||
ast_for("{{foo omg bar=baz bat=\"bam\" baz=1}}").should.equal('{{ ID:foo [ID:omg] HASH{bar=ID:baz, bat="bam", baz=INTEGER{1}} }}\n');
|
||||
ast_for("{{foo omg bar=baz bat=\"bam\" baz=true}}").should.equal('{{ ID:foo [ID:omg] HASH{bar=ID:baz, bat="bam", baz=BOOLEAN{true}} }}\n');
|
||||
ast_for("{{foo omg bar=baz bat=\"bam\" baz=false}}").should.equal('{{ ID:foo [ID:omg] HASH{bar=ID:baz, bat="bam", baz=BOOLEAN{false}} }}\n');
|
||||
equals(ast_for("{{foo omg bar=baz bat=\"bam\"}}"), '{{ ID:foo [ID:omg] HASH{bar=ID:baz, bat="bam"} }}\n');
|
||||
equals(ast_for("{{foo omg bar=baz bat=\"bam\" baz=1}}"), '{{ ID:foo [ID:omg] HASH{bar=ID:baz, bat="bam", baz=INTEGER{1}} }}\n');
|
||||
equals(ast_for("{{foo omg bar=baz bat=\"bam\" baz=true}}"), '{{ ID:foo [ID:omg] HASH{bar=ID:baz, bat="bam", baz=BOOLEAN{true}} }}\n');
|
||||
equals(ast_for("{{foo omg bar=baz bat=\"bam\" baz=false}}"), '{{ ID:foo [ID:omg] HASH{bar=ID:baz, bat="bam", baz=BOOLEAN{false}} }}\n');
|
||||
});
|
||||
|
||||
it('parses contents followed by a mustache', function() {
|
||||
ast_for("foo bar {{baz}}").should.equal("CONTENT[ \'foo bar \' ]\n{{ ID:baz [] }}\n");
|
||||
equals(ast_for("foo bar {{baz}}"), "CONTENT[ \'foo bar \' ]\n{{ ID:baz [] }}\n");
|
||||
});
|
||||
|
||||
it('parses a partial', function() {
|
||||
ast_for("{{> foo }}").should.equal("{{> PARTIAL:foo }}\n");
|
||||
equals(ast_for("{{> foo }}"), "{{> PARTIAL:foo }}\n");
|
||||
});
|
||||
|
||||
it('parses a partial with context', function() {
|
||||
ast_for("{{> foo bar}}").should.equal("{{> PARTIAL:foo ID:bar }}\n");
|
||||
equals(ast_for("{{> foo bar}}"), "{{> PARTIAL:foo ID:bar }}\n");
|
||||
});
|
||||
|
||||
it('parses a partial with a complex name', function() {
|
||||
ast_for("{{> shared/partial?.bar}}").should.equal("{{> PARTIAL:shared/partial?.bar }}\n");
|
||||
equals(ast_for("{{> shared/partial?.bar}}"), "{{> PARTIAL:shared/partial?.bar }}\n");
|
||||
});
|
||||
|
||||
it('parses a comment', function() {
|
||||
ast_for("{{! this is a comment }}").should.equal("{{! ' this is a comment ' }}\n");
|
||||
equals(ast_for("{{! this is a comment }}"), "{{! ' this is a comment ' }}\n");
|
||||
});
|
||||
|
||||
it('parses a multi-line comment', function() {
|
||||
ast_for("{{!\nthis is a multi-line comment\n}}").should.equal("{{! \'\nthis is a multi-line comment\n\' }}\n");
|
||||
equals(ast_for("{{!\nthis is a multi-line comment\n}}"), "{{! \'\nthis is a multi-line comment\n\' }}\n");
|
||||
});
|
||||
|
||||
it('parses an inverse section', function() {
|
||||
ast_for("{{#foo}} bar {{^}} baz {{/foo}}").should.equal("BLOCK:\n {{ ID:foo [] }}\n PROGRAM:\n CONTENT[ ' bar ' ]\n {{^}}\n CONTENT[ ' baz ' ]\n");
|
||||
equals(ast_for("{{#foo}} bar {{^}} baz {{/foo}}"), "BLOCK:\n {{ ID:foo [] }}\n PROGRAM:\n CONTENT[ ' bar ' ]\n {{^}}\n CONTENT[ ' baz ' ]\n");
|
||||
});
|
||||
|
||||
it('parses an inverse (else-style) section', function() {
|
||||
ast_for("{{#foo}} bar {{else}} baz {{/foo}}").should.equal("BLOCK:\n {{ ID:foo [] }}\n PROGRAM:\n CONTENT[ ' bar ' ]\n {{^}}\n CONTENT[ ' baz ' ]\n");
|
||||
equals(ast_for("{{#foo}} bar {{else}} baz {{/foo}}"), "BLOCK:\n {{ ID:foo [] }}\n PROGRAM:\n CONTENT[ ' bar ' ]\n {{^}}\n CONTENT[ ' baz ' ]\n");
|
||||
});
|
||||
|
||||
it('parses empty blocks', function() {
|
||||
ast_for("{{#foo}}{{/foo}}").should.equal("BLOCK:\n {{ ID:foo [] }}\n PROGRAM:\n");
|
||||
equals(ast_for("{{#foo}}{{/foo}}"), "BLOCK:\n {{ ID:foo [] }}\n PROGRAM:\n");
|
||||
});
|
||||
|
||||
it('parses empty blocks with empty inverse section', function() {
|
||||
ast_for("{{#foo}}{{^}}{{/foo}}").should.equal("BLOCK:\n {{ ID:foo [] }}\n PROGRAM:\n");
|
||||
equals(ast_for("{{#foo}}{{^}}{{/foo}}"), "BLOCK:\n {{ ID:foo [] }}\n PROGRAM:\n");
|
||||
});
|
||||
|
||||
it('parses empty blocks with empty inverse (else-style) section', function() {
|
||||
ast_for("{{#foo}}{{else}}{{/foo}}").should.equal("BLOCK:\n {{ ID:foo [] }}\n PROGRAM:\n");
|
||||
equals(ast_for("{{#foo}}{{else}}{{/foo}}"), "BLOCK:\n {{ ID:foo [] }}\n PROGRAM:\n");
|
||||
});
|
||||
|
||||
it('parses non-empty blocks with empty inverse section', function() {
|
||||
ast_for("{{#foo}} bar {{^}}{{/foo}}").should.equal("BLOCK:\n {{ ID:foo [] }}\n PROGRAM:\n CONTENT[ ' bar ' ]\n {{^}}\n");
|
||||
equals(ast_for("{{#foo}} bar {{^}}{{/foo}}"), "BLOCK:\n {{ ID:foo [] }}\n PROGRAM:\n CONTENT[ ' bar ' ]\n {{^}}\n");
|
||||
});
|
||||
|
||||
it('parses non-empty blocks with empty inverse (else-style) section', function() {
|
||||
ast_for("{{#foo}} bar {{else}}{{/foo}}").should.equal("BLOCK:\n {{ ID:foo [] }}\n PROGRAM:\n CONTENT[ ' bar ' ]\n {{^}}\n");
|
||||
equals(ast_for("{{#foo}} bar {{else}}{{/foo}}"), "BLOCK:\n {{ ID:foo [] }}\n PROGRAM:\n CONTENT[ ' bar ' ]\n {{^}}\n");
|
||||
});
|
||||
|
||||
it('parses empty blocks with non-empty inverse section', function() {
|
||||
ast_for("{{#foo}}{{^}} bar {{/foo}}").should.equal("BLOCK:\n {{ ID:foo [] }}\n PROGRAM:\n {{^}}\n CONTENT[ ' bar ' ]\n");
|
||||
equals(ast_for("{{#foo}}{{^}} bar {{/foo}}"), "BLOCK:\n {{ ID:foo [] }}\n PROGRAM:\n {{^}}\n CONTENT[ ' bar ' ]\n");
|
||||
});
|
||||
|
||||
it('parses empty blocks with non-empty inverse (else-style) section', function() {
|
||||
ast_for("{{#foo}}{{else}} bar {{/foo}}").should.equal("BLOCK:\n {{ ID:foo [] }}\n PROGRAM:\n {{^}}\n CONTENT[ ' bar ' ]\n");
|
||||
equals(ast_for("{{#foo}}{{else}} bar {{/foo}}"), "BLOCK:\n {{ ID:foo [] }}\n PROGRAM:\n {{^}}\n CONTENT[ ' bar ' ]\n");
|
||||
});
|
||||
|
||||
it('parses a standalone inverse section', function() {
|
||||
ast_for("{{^foo}}bar{{/foo}}").should.equal("BLOCK:\n {{ ID:foo [] }}\n {{^}}\n CONTENT[ 'bar' ]\n");
|
||||
equals(ast_for("{{^foo}}bar{{/foo}}"), "BLOCK:\n {{ ID:foo [] }}\n {{^}}\n CONTENT[ 'bar' ]\n");
|
||||
});
|
||||
|
||||
it("raises if there's a Parse error", function() {
|
||||
(function() {
|
||||
shouldThrow(function() {
|
||||
ast_for("foo{{^}}bar");
|
||||
}).should.throw(/Parse error on line 1/);
|
||||
(function() {
|
||||
}, Error, /Parse error on line 1/);
|
||||
shouldThrow(function() {
|
||||
ast_for("{{foo}");
|
||||
}).should.throw(/Parse error on line 1/);
|
||||
(function() {
|
||||
}, Error, /Parse error on line 1/);
|
||||
shouldThrow(function() {
|
||||
ast_for("{{foo &}}");
|
||||
}).should.throw(/Parse error on line 1/);
|
||||
(function() {
|
||||
}, Error, /Parse error on line 1/);
|
||||
shouldThrow(function() {
|
||||
ast_for("{{#goodbyes}}{{/hellos}}");
|
||||
}).should.throw(/goodbyes doesn't match hellos/);
|
||||
}, Error, /goodbyes doesn't match hellos/);
|
||||
});
|
||||
|
||||
it('knows how to report the correct line number in errors', function() {
|
||||
(function() {
|
||||
shouldThrow(function() {
|
||||
ast_for("hello\nmy\n{{foo}");
|
||||
}).should.throw(/Parse error on line 3/);
|
||||
(function() {
|
||||
}, Error, /Parse error on line 3/);
|
||||
shouldThrow(function() {
|
||||
ast_for("hello\n\nmy\n\n{{foo}");
|
||||
}).should.throw(/Parse error on line 5/);
|
||||
}, Error, /Parse error on line 5/);
|
||||
});
|
||||
|
||||
it('knows how to report the correct line number in errors when the first character is a newline', function() {
|
||||
(function() {
|
||||
shouldThrow(function() {
|
||||
ast_for("\n\nhello\n\nmy\n\n{{foo}");
|
||||
}).should.throw(/Parse error on line 7/);
|
||||
}, Error, /Parse error on line 7/);
|
||||
});
|
||||
|
||||
describe('externally compiled AST', function() {
|
||||
it('can pass through an already-compiled AST', function() {
|
||||
ast_for(new Handlebars.AST.ProgramNode([ new Handlebars.AST.ContentNode("Hello")])).should.equal("CONTENT[ \'Hello\' ]\n");
|
||||
equals(ast_for(new Handlebars.AST.ProgramNode([ new Handlebars.AST.ContentNode("Hello")])), "CONTENT[ \'Hello\' ]\n");
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
+10
-4
@@ -32,17 +32,17 @@ describe('partials', function() {
|
||||
});
|
||||
|
||||
it("rendering undefined partial throws an exception", function() {
|
||||
(function() {
|
||||
shouldThrow(function() {
|
||||
var template = CompilerContext.compile("{{> whatever}}");
|
||||
template();
|
||||
}).should.throw(Handlebars.Exception, 'The partial whatever could not be found');
|
||||
}, Handlebars.Exception, 'The partial whatever could not be found');
|
||||
});
|
||||
|
||||
it("rendering template partial in vm mode throws an exception", function() {
|
||||
(function() {
|
||||
shouldThrow(function() {
|
||||
var template = CompilerContext.compile("{{> whatever}}");
|
||||
template();
|
||||
}).should.throw(Handlebars.Exception, 'The partial whatever could not be found');
|
||||
}, Handlebars.Exception, 'The partial whatever could not be found');
|
||||
});
|
||||
|
||||
it("rendering function partial in vm mode", function() {
|
||||
@@ -123,4 +123,10 @@ describe('partials', function() {
|
||||
var hash = {name:"Jeepers", another_dude:"Creepers"};
|
||||
shouldCompileToWithPartials(string, [hash, {}, {'+404/asdf?.bar':dude}], true, "Dudes: Jeepers", "Partials can use literal paths");
|
||||
});
|
||||
|
||||
it('should handle empty partial', function() {
|
||||
var string = "Dudes: {{#dudes}}{{> dude}}{{/dudes}}";
|
||||
var partial = "";
|
||||
var hash = {dudes: [{name: "Yehuda", url: "http://yehuda"}, {name: "Alan", url: "http://alan"}]};
|
||||
shouldCompileToWithPartials(string, [hash, {}, {dude: partial}], true, "Dudes: "); });
|
||||
});
|
||||
|
||||
+18
-7
@@ -1,4 +1,4 @@
|
||||
/*global CompilerContext, shouldCompileTo */
|
||||
/*global CompilerContext, Handlebars, shouldCompileTo, shouldThrow */
|
||||
describe('Regressions', function() {
|
||||
it("GH-94: Cannot read property of undefined", function() {
|
||||
var data = {"books":[{"title":"The origin of species","author":{"name":"Charles Darwin"}},{"title":"Lazarillo de Tormes"}]};
|
||||
@@ -85,12 +85,19 @@ describe('Regressions', function() {
|
||||
});
|
||||
|
||||
it('GH-437: Matching escaping', function() {
|
||||
(function() {
|
||||
shouldThrow(function() {
|
||||
CompilerContext.compile('{{{a}}');
|
||||
}).should.throw(Error);
|
||||
(function() {
|
||||
}, Error);
|
||||
shouldThrow(function() {
|
||||
CompilerContext.compile('{{a}}}');
|
||||
}).should.throw(Error);
|
||||
}, Error);
|
||||
});
|
||||
|
||||
it("GH-676: Using array in escaping mustache fails", function() {
|
||||
var string = "{{arr}}";
|
||||
var data = { "arr": [1,2] };
|
||||
|
||||
shouldCompileTo(string, data, data.arr.toString(), "it works as expected");
|
||||
});
|
||||
|
||||
it("Mustache man page", function() {
|
||||
@@ -106,14 +113,18 @@ describe('Regressions', function() {
|
||||
});
|
||||
|
||||
it("Passing falsy values to Handlebars.compile throws an error", function() {
|
||||
(function() {
|
||||
shouldThrow(function() {
|
||||
CompilerContext.compile(null);
|
||||
}).should.throw("You must pass a string or Handlebars AST to Handlebars.precompile. You passed null");
|
||||
}, Error, 'You must pass a string or Handlebars AST to Handlebars.precompile. You passed null');
|
||||
});
|
||||
|
||||
if (Handlebars.AST) {
|
||||
it("can pass through an already-compiled AST via compile/precompile", function() {
|
||||
equal(Handlebars.compile(new Handlebars.AST.ProgramNode([ new Handlebars.AST.ContentNode("Hello")]))(), 'Hello');
|
||||
});
|
||||
|
||||
it("can pass through an empty string", function() {
|
||||
equal(Handlebars.compile('')(), '');
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
+4
-4
@@ -2,22 +2,22 @@ if (typeof(require) !== 'undefined' && require.extensions[".handlebars"]) {
|
||||
describe('Require', function() {
|
||||
it('Load .handlebars files with require()', function() {
|
||||
var template = require("./artifacts/example_1");
|
||||
template.should.eql(require("./artifacts/example_1.handlebars"));
|
||||
equal(template, require("./artifacts/example_1.handlebars"));
|
||||
|
||||
var expected = 'foo\n';
|
||||
var result = template({foo: "foo"});
|
||||
|
||||
result.should.equal(expected);
|
||||
equal(result, expected);
|
||||
});
|
||||
|
||||
it('Load .hbs files with require()', function() {
|
||||
var template = require("./artifacts/example_2");
|
||||
template.should.eql(require("./artifacts/example_2.hbs"));
|
||||
equal(template, require("./artifacts/example_2.hbs"));
|
||||
|
||||
var expected = 'Hello, World!\n';
|
||||
var result = template({name: "World"});
|
||||
|
||||
result.should.equal(expected);
|
||||
equal(result, expected);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
+132
-123
@@ -1,13 +1,12 @@
|
||||
var should = require('should');
|
||||
|
||||
should.Assertion.prototype.match_tokens = function(tokens) {
|
||||
this.obj.forEach(function(value, index) {
|
||||
value.name.should.equal(tokens[index]);
|
||||
});
|
||||
};
|
||||
should.Assertion.prototype.be_token = function(name, text) {
|
||||
this.obj.should.eql({name: name, text: text});
|
||||
};
|
||||
function shouldMatchTokens(result, tokens) {
|
||||
for (var index = 0; index < result.length; index++) {
|
||||
equals(result[index].name, tokens[index]);
|
||||
}
|
||||
}
|
||||
function shouldBeToken(result, name, text) {
|
||||
equals(result.name, name);
|
||||
equals(result.text, text);
|
||||
}
|
||||
|
||||
describe('Tokenizer', function() {
|
||||
if (!Handlebars.Parser) {
|
||||
@@ -35,324 +34,334 @@ describe('Tokenizer', function() {
|
||||
|
||||
it('tokenizes a simple mustache as "OPEN ID CLOSE"', function() {
|
||||
var result = tokenize("{{foo}}");
|
||||
result.should.match_tokens(['OPEN', 'ID', 'CLOSE']);
|
||||
result[1].should.be_token("ID", "foo");
|
||||
shouldMatchTokens(result, ['OPEN', 'ID', 'CLOSE']);
|
||||
shouldBeToken(result[1], "ID", "foo");
|
||||
});
|
||||
|
||||
it('supports unescaping with &', function() {
|
||||
var result = tokenize("{{&bar}}");
|
||||
result.should.match_tokens(['OPEN', 'ID', 'CLOSE']);
|
||||
shouldMatchTokens(result, ['OPEN', 'ID', 'CLOSE']);
|
||||
|
||||
result[0].should.be_token("OPEN", "{{&");
|
||||
result[1].should.be_token("ID", "bar");
|
||||
shouldBeToken(result[0], "OPEN", "{{&");
|
||||
shouldBeToken(result[1], "ID", "bar");
|
||||
});
|
||||
|
||||
it('supports unescaping with {{{', function() {
|
||||
var result = tokenize("{{{bar}}}");
|
||||
result.should.match_tokens(['OPEN_UNESCAPED', 'ID', 'CLOSE_UNESCAPED']);
|
||||
shouldMatchTokens(result, ['OPEN_UNESCAPED', 'ID', 'CLOSE_UNESCAPED']);
|
||||
|
||||
result[1].should.be_token("ID", "bar");
|
||||
shouldBeToken(result[1], "ID", "bar");
|
||||
});
|
||||
|
||||
it('supports escaping delimiters', function() {
|
||||
var result = tokenize("{{foo}} \\{{bar}} {{baz}}");
|
||||
result.should.match_tokens(['OPEN', 'ID', 'CLOSE', 'CONTENT', 'CONTENT', 'OPEN', 'ID', 'CLOSE']);
|
||||
shouldMatchTokens(result, ['OPEN', 'ID', 'CLOSE', 'CONTENT', 'CONTENT', 'OPEN', 'ID', 'CLOSE']);
|
||||
|
||||
result[3].should.be_token("CONTENT", " ");
|
||||
result[4].should.be_token("CONTENT", "{{bar}} ");
|
||||
shouldBeToken(result[3], "CONTENT", " ");
|
||||
shouldBeToken(result[4], "CONTENT", "{{bar}} ");
|
||||
});
|
||||
|
||||
it('supports escaping multiple delimiters', function() {
|
||||
var result = tokenize("{{foo}} \\{{bar}} \\{{baz}}");
|
||||
result.should.match_tokens(['OPEN', 'ID', 'CLOSE', 'CONTENT', 'CONTENT', 'CONTENT']);
|
||||
shouldMatchTokens(result, ['OPEN', 'ID', 'CLOSE', 'CONTENT', 'CONTENT', 'CONTENT']);
|
||||
|
||||
result[3].should.be_token("CONTENT", " ");
|
||||
result[4].should.be_token("CONTENT", "{{bar}} ");
|
||||
result[5].should.be_token("CONTENT", "{{baz}}");
|
||||
shouldBeToken(result[3], "CONTENT", " ");
|
||||
shouldBeToken(result[4], "CONTENT", "{{bar}} ");
|
||||
shouldBeToken(result[5], "CONTENT", "{{baz}}");
|
||||
});
|
||||
|
||||
it('supports escaping a triple stash', function() {
|
||||
var result = tokenize("{{foo}} \\{{{bar}}} {{baz}}");
|
||||
result.should.match_tokens(['OPEN', 'ID', 'CLOSE', 'CONTENT', 'CONTENT', 'OPEN', 'ID', 'CLOSE']);
|
||||
shouldMatchTokens(result, ['OPEN', 'ID', 'CLOSE', 'CONTENT', 'CONTENT', 'OPEN', 'ID', 'CLOSE']);
|
||||
|
||||
result[4].should.be_token("CONTENT", "{{{bar}}} ");
|
||||
shouldBeToken(result[4], "CONTENT", "{{{bar}}} ");
|
||||
});
|
||||
|
||||
it('supports escaping escape character', function() {
|
||||
var result = tokenize("{{foo}} \\\\{{bar}} {{baz}}");
|
||||
result.should.match_tokens(['OPEN', 'ID', 'CLOSE', 'CONTENT', 'OPEN', 'ID', 'CLOSE', 'CONTENT', 'OPEN', 'ID', 'CLOSE']);
|
||||
shouldMatchTokens(result, ['OPEN', 'ID', 'CLOSE', 'CONTENT', 'OPEN', 'ID', 'CLOSE', 'CONTENT', 'OPEN', 'ID', 'CLOSE']);
|
||||
|
||||
result[3].should.be_token("CONTENT", " \\");
|
||||
result[5].should.be_token("ID", "bar");
|
||||
shouldBeToken(result[3], "CONTENT", " \\");
|
||||
shouldBeToken(result[5], "ID", "bar");
|
||||
});
|
||||
|
||||
it('supports escaping multiple escape characters', function() {
|
||||
var result = tokenize("{{foo}} \\\\{{bar}} \\\\{{baz}}");
|
||||
result.should.match_tokens(['OPEN', 'ID', 'CLOSE', 'CONTENT', 'OPEN', 'ID', 'CLOSE', 'CONTENT', 'OPEN', 'ID', 'CLOSE']);
|
||||
shouldMatchTokens(result, ['OPEN', 'ID', 'CLOSE', 'CONTENT', 'OPEN', 'ID', 'CLOSE', 'CONTENT', 'OPEN', 'ID', 'CLOSE']);
|
||||
|
||||
result[3].should.be_token("CONTENT", " \\");
|
||||
result[5].should.be_token("ID", "bar");
|
||||
result[7].should.be_token("CONTENT", " \\");
|
||||
result[9].should.be_token("ID", "baz");
|
||||
shouldBeToken(result[3], "CONTENT", " \\");
|
||||
shouldBeToken(result[5], "ID", "bar");
|
||||
shouldBeToken(result[7], "CONTENT", " \\");
|
||||
shouldBeToken(result[9], "ID", "baz");
|
||||
});
|
||||
|
||||
it('supports mixed escaped delimiters and escaped escape characters', function() {
|
||||
it('supports escaped mustaches after escaped escape characters', function() {
|
||||
var result = tokenize("{{foo}} \\\\{{bar}} \\{{baz}}");
|
||||
result.should.match_tokens(['OPEN', 'ID', 'CLOSE', 'CONTENT', 'OPEN', 'ID', 'CLOSE', 'CONTENT', 'CONTENT', 'CONTENT']);
|
||||
shouldMatchTokens(result, ['OPEN', 'ID', 'CLOSE', 'CONTENT', 'OPEN', 'ID', 'CLOSE', 'CONTENT', 'CONTENT', 'CONTENT']);
|
||||
|
||||
result[3].should.be_token("CONTENT", " \\");
|
||||
result[4].should.be_token("OPEN", "{{");
|
||||
result[5].should.be_token("ID", "bar");
|
||||
result[7].should.be_token("CONTENT", " ");
|
||||
result[8].should.be_token("CONTENT", "{{baz}}");
|
||||
shouldBeToken(result[3], "CONTENT", " \\");
|
||||
shouldBeToken(result[4], "OPEN", "{{");
|
||||
shouldBeToken(result[5], "ID", "bar");
|
||||
shouldBeToken(result[7], "CONTENT", " ");
|
||||
shouldBeToken(result[8], "CONTENT", "{{baz}}");
|
||||
});
|
||||
|
||||
it('supports escaped escape characters after escaped mustaches', function() {
|
||||
var result = tokenize("{{foo}} \\{{bar}} \\\\{{baz}}");
|
||||
shouldMatchTokens(result, ['OPEN', 'ID', 'CLOSE', 'CONTENT', 'CONTENT', 'CONTENT', 'OPEN', 'ID', 'CLOSE']);
|
||||
|
||||
shouldBeToken(result[4], "CONTENT", "{{bar}} ");
|
||||
shouldBeToken(result[5], "CONTENT", "\\");
|
||||
shouldBeToken(result[6], "OPEN", "{{");
|
||||
shouldBeToken(result[7], "ID", "baz");
|
||||
});
|
||||
|
||||
it('supports escaped escape character on a triple stash', function() {
|
||||
var result = tokenize("{{foo}} \\\\{{{bar}}} {{baz}}");
|
||||
result.should.match_tokens(['OPEN', 'ID', 'CLOSE', 'CONTENT', 'OPEN_UNESCAPED', 'ID', 'CLOSE_UNESCAPED', 'CONTENT', 'OPEN', 'ID', 'CLOSE']);
|
||||
shouldMatchTokens(result, ['OPEN', 'ID', 'CLOSE', 'CONTENT', 'OPEN_UNESCAPED', 'ID', 'CLOSE_UNESCAPED', 'CONTENT', 'OPEN', 'ID', 'CLOSE']);
|
||||
|
||||
result[3].should.be_token("CONTENT", " \\");
|
||||
result[5].should.be_token("ID", "bar");
|
||||
shouldBeToken(result[3], "CONTENT", " \\");
|
||||
shouldBeToken(result[5], "ID", "bar");
|
||||
});
|
||||
|
||||
it('tokenizes a simple path', function() {
|
||||
var result = tokenize("{{foo/bar}}");
|
||||
result.should.match_tokens(['OPEN', 'ID', 'SEP', 'ID', 'CLOSE']);
|
||||
shouldMatchTokens(result, ['OPEN', 'ID', 'SEP', 'ID', 'CLOSE']);
|
||||
});
|
||||
|
||||
it('allows dot notation', function() {
|
||||
var result = tokenize("{{foo.bar}}");
|
||||
result.should.match_tokens(['OPEN', 'ID', 'SEP', 'ID', 'CLOSE']);
|
||||
shouldMatchTokens(result, ['OPEN', 'ID', 'SEP', 'ID', 'CLOSE']);
|
||||
|
||||
tokenize("{{foo.bar.baz}}").should.match_tokens(['OPEN', 'ID', 'SEP', 'ID', 'SEP', 'ID', 'CLOSE']);
|
||||
shouldMatchTokens(tokenize("{{foo.bar.baz}}"), ['OPEN', 'ID', 'SEP', 'ID', 'SEP', 'ID', 'CLOSE']);
|
||||
});
|
||||
|
||||
it('allows path literals with []', function() {
|
||||
var result = tokenize("{{foo.[bar]}}");
|
||||
result.should.match_tokens(['OPEN', 'ID', 'SEP', 'ID', 'CLOSE']);
|
||||
shouldMatchTokens(result, ['OPEN', 'ID', 'SEP', 'ID', 'CLOSE']);
|
||||
});
|
||||
|
||||
it('allows multiple path literals on a line with []', function() {
|
||||
var result = tokenize("{{foo.[bar]}}{{foo.[baz]}}");
|
||||
result.should.match_tokens(['OPEN', 'ID', 'SEP', 'ID', 'CLOSE', 'OPEN', 'ID', 'SEP', 'ID', 'CLOSE']);
|
||||
shouldMatchTokens(result, ['OPEN', 'ID', 'SEP', 'ID', 'CLOSE', 'OPEN', 'ID', 'SEP', 'ID', 'CLOSE']);
|
||||
});
|
||||
|
||||
it('tokenizes {{.}} as OPEN ID CLOSE', function() {
|
||||
var result = tokenize("{{.}}");
|
||||
result.should.match_tokens(['OPEN', 'ID', 'CLOSE']);
|
||||
shouldMatchTokens(result, ['OPEN', 'ID', 'CLOSE']);
|
||||
});
|
||||
|
||||
it('tokenizes a path as "OPEN (ID SEP)* ID CLOSE"', function() {
|
||||
var result = tokenize("{{../foo/bar}}");
|
||||
result.should.match_tokens(['OPEN', 'ID', 'SEP', 'ID', 'SEP', 'ID', 'CLOSE']);
|
||||
result[1].should.be_token("ID", "..");
|
||||
shouldMatchTokens(result, ['OPEN', 'ID', 'SEP', 'ID', 'SEP', 'ID', 'CLOSE']);
|
||||
shouldBeToken(result[1], "ID", "..");
|
||||
});
|
||||
|
||||
it('tokenizes a path with .. as a parent path', function() {
|
||||
var result = tokenize("{{../foo.bar}}");
|
||||
result.should.match_tokens(['OPEN', 'ID', 'SEP', 'ID', 'SEP', 'ID', 'CLOSE']);
|
||||
result[1].should.be_token("ID", "..");
|
||||
shouldMatchTokens(result, ['OPEN', 'ID', 'SEP', 'ID', 'SEP', 'ID', 'CLOSE']);
|
||||
shouldBeToken(result[1], "ID", "..");
|
||||
});
|
||||
|
||||
it('tokenizes a path with this/foo as OPEN ID SEP ID CLOSE', function() {
|
||||
var result = tokenize("{{this/foo}}");
|
||||
result.should.match_tokens(['OPEN', 'ID', 'SEP', 'ID', 'CLOSE']);
|
||||
result[1].should.be_token("ID", "this");
|
||||
result[3].should.be_token("ID", "foo");
|
||||
shouldMatchTokens(result, ['OPEN', 'ID', 'SEP', 'ID', 'CLOSE']);
|
||||
shouldBeToken(result[1], "ID", "this");
|
||||
shouldBeToken(result[3], "ID", "foo");
|
||||
});
|
||||
|
||||
it('tokenizes a simple mustache with spaces as "OPEN ID CLOSE"', function() {
|
||||
var result = tokenize("{{ foo }}");
|
||||
result.should.match_tokens(['OPEN', 'ID', 'CLOSE']);
|
||||
result[1].should.be_token("ID", "foo");
|
||||
shouldMatchTokens(result, ['OPEN', 'ID', 'CLOSE']);
|
||||
shouldBeToken(result[1], "ID", "foo");
|
||||
});
|
||||
|
||||
it('tokenizes a simple mustache with line breaks as "OPEN ID ID CLOSE"', function() {
|
||||
var result = tokenize("{{ foo \n bar }}");
|
||||
result.should.match_tokens(['OPEN', 'ID', 'ID', 'CLOSE']);
|
||||
result[1].should.be_token("ID", "foo");
|
||||
shouldMatchTokens(result, ['OPEN', 'ID', 'ID', 'CLOSE']);
|
||||
shouldBeToken(result[1], "ID", "foo");
|
||||
});
|
||||
|
||||
it('tokenizes raw content as "CONTENT"', function() {
|
||||
var result = tokenize("foo {{ bar }} baz");
|
||||
result.should.match_tokens(['CONTENT', 'OPEN', 'ID', 'CLOSE', 'CONTENT']);
|
||||
result[0].should.be_token("CONTENT", "foo ");
|
||||
result[4].should.be_token("CONTENT", " baz");
|
||||
shouldMatchTokens(result, ['CONTENT', 'OPEN', 'ID', 'CLOSE', 'CONTENT']);
|
||||
shouldBeToken(result[0], "CONTENT", "foo ");
|
||||
shouldBeToken(result[4], "CONTENT", " baz");
|
||||
});
|
||||
|
||||
it('tokenizes a partial as "OPEN_PARTIAL ID CLOSE"', function() {
|
||||
var result = tokenize("{{> foo}}");
|
||||
result.should.match_tokens(['OPEN_PARTIAL', 'ID', 'CLOSE']);
|
||||
shouldMatchTokens(result, ['OPEN_PARTIAL', 'ID', 'CLOSE']);
|
||||
});
|
||||
|
||||
it('tokenizes a partial with context as "OPEN_PARTIAL ID ID CLOSE"', function() {
|
||||
var result = tokenize("{{> foo bar }}");
|
||||
result.should.match_tokens(['OPEN_PARTIAL', 'ID', 'ID', 'CLOSE']);
|
||||
shouldMatchTokens(result, ['OPEN_PARTIAL', 'ID', 'ID', 'CLOSE']);
|
||||
});
|
||||
|
||||
it('tokenizes a partial without spaces as "OPEN_PARTIAL ID CLOSE"', function() {
|
||||
var result = tokenize("{{>foo}}");
|
||||
result.should.match_tokens(['OPEN_PARTIAL', 'ID', 'CLOSE']);
|
||||
shouldMatchTokens(result, ['OPEN_PARTIAL', 'ID', 'CLOSE']);
|
||||
});
|
||||
|
||||
it('tokenizes a partial space at the }); as "OPEN_PARTIAL ID CLOSE"', function() {
|
||||
var result = tokenize("{{>foo }}");
|
||||
result.should.match_tokens(['OPEN_PARTIAL', 'ID', 'CLOSE']);
|
||||
shouldMatchTokens(result, ['OPEN_PARTIAL', 'ID', 'CLOSE']);
|
||||
});
|
||||
|
||||
it('tokenizes a partial space at the }); as "OPEN_PARTIAL ID CLOSE"', function() {
|
||||
var result = tokenize("{{>foo/bar.baz }}");
|
||||
result.should.match_tokens(['OPEN_PARTIAL', 'ID', 'SEP', 'ID', 'SEP', 'ID', 'CLOSE']);
|
||||
shouldMatchTokens(result, ['OPEN_PARTIAL', 'ID', 'SEP', 'ID', 'SEP', 'ID', 'CLOSE']);
|
||||
});
|
||||
|
||||
it('tokenizes a comment as "COMMENT"', function() {
|
||||
var result = tokenize("foo {{! this is a comment }} bar {{ baz }}");
|
||||
result.should.match_tokens(['CONTENT', 'COMMENT', 'CONTENT', 'OPEN', 'ID', 'CLOSE']);
|
||||
result[1].should.be_token("COMMENT", " this is a comment ");
|
||||
shouldMatchTokens(result, ['CONTENT', 'COMMENT', 'CONTENT', 'OPEN', 'ID', 'CLOSE']);
|
||||
shouldBeToken(result[1], "COMMENT", " this is a comment ");
|
||||
});
|
||||
|
||||
it('tokenizes a block comment as "COMMENT"', function() {
|
||||
var result = tokenize("foo {{!-- this is a {{comment}} --}} bar {{ baz }}");
|
||||
result.should.match_tokens(['CONTENT', 'COMMENT', 'CONTENT', 'OPEN', 'ID', 'CLOSE']);
|
||||
result[1].should.be_token("COMMENT", " this is a {{comment}} ");
|
||||
shouldMatchTokens(result, ['CONTENT', 'COMMENT', 'CONTENT', 'OPEN', 'ID', 'CLOSE']);
|
||||
shouldBeToken(result[1], "COMMENT", " this is a {{comment}} ");
|
||||
});
|
||||
|
||||
it('tokenizes a block comment with whitespace as "COMMENT"', function() {
|
||||
var result = tokenize("foo {{!-- this is a\n{{comment}}\n--}} bar {{ baz }}");
|
||||
result.should.match_tokens(['CONTENT', 'COMMENT', 'CONTENT', 'OPEN', 'ID', 'CLOSE']);
|
||||
result[1].should.be_token("COMMENT", " this is a\n{{comment}}\n");
|
||||
shouldMatchTokens(result, ['CONTENT', 'COMMENT', 'CONTENT', 'OPEN', 'ID', 'CLOSE']);
|
||||
shouldBeToken(result[1], "COMMENT", " this is a\n{{comment}}\n");
|
||||
});
|
||||
|
||||
it('tokenizes open and closing blocks as OPEN_BLOCK, ID, CLOSE ..., OPEN_ENDBLOCK ID CLOSE', function() {
|
||||
var result = tokenize("{{#foo}}content{{/foo}}");
|
||||
result.should.match_tokens(['OPEN_BLOCK', 'ID', 'CLOSE', 'CONTENT', 'OPEN_ENDBLOCK', 'ID', 'CLOSE']);
|
||||
shouldMatchTokens(result, ['OPEN_BLOCK', 'ID', 'CLOSE', 'CONTENT', 'OPEN_ENDBLOCK', 'ID', 'CLOSE']);
|
||||
});
|
||||
|
||||
it('tokenizes inverse sections as "OPEN_INVERSE CLOSE"', function() {
|
||||
tokenize("{{^}}").should.match_tokens(['OPEN_INVERSE', 'CLOSE']);
|
||||
tokenize("{{else}}").should.match_tokens(['OPEN_INVERSE', 'CLOSE']);
|
||||
tokenize("{{ else }}").should.match_tokens(['OPEN_INVERSE', 'CLOSE']);
|
||||
shouldMatchTokens(tokenize("{{^}}"), ['OPEN_INVERSE', 'CLOSE']);
|
||||
shouldMatchTokens(tokenize("{{else}}"), ['OPEN_INVERSE', 'CLOSE']);
|
||||
shouldMatchTokens(tokenize("{{ else }}"), ['OPEN_INVERSE', 'CLOSE']);
|
||||
});
|
||||
|
||||
it('tokenizes inverse sections with ID as "OPEN_INVERSE ID CLOSE"', function() {
|
||||
var result = tokenize("{{^foo}}");
|
||||
result.should.match_tokens(['OPEN_INVERSE', 'ID', 'CLOSE']);
|
||||
result[1].should.be_token("ID", "foo");
|
||||
shouldMatchTokens(result, ['OPEN_INVERSE', 'ID', 'CLOSE']);
|
||||
shouldBeToken(result[1], "ID", "foo");
|
||||
});
|
||||
|
||||
it('tokenizes inverse sections with ID and spaces as "OPEN_INVERSE ID CLOSE"', function() {
|
||||
var result = tokenize("{{^ foo }}");
|
||||
result.should.match_tokens(['OPEN_INVERSE', 'ID', 'CLOSE']);
|
||||
result[1].should.be_token("ID", "foo");
|
||||
shouldMatchTokens(result, ['OPEN_INVERSE', 'ID', 'CLOSE']);
|
||||
shouldBeToken(result[1], "ID", "foo");
|
||||
});
|
||||
|
||||
it('tokenizes mustaches with params as "OPEN ID ID ID CLOSE"', function() {
|
||||
var result = tokenize("{{ foo bar baz }}");
|
||||
result.should.match_tokens(['OPEN', 'ID', 'ID', 'ID', 'CLOSE']);
|
||||
result[1].should.be_token("ID", "foo");
|
||||
result[2].should.be_token("ID", "bar");
|
||||
result[3].should.be_token("ID", "baz");
|
||||
shouldMatchTokens(result, ['OPEN', 'ID', 'ID', 'ID', 'CLOSE']);
|
||||
shouldBeToken(result[1], "ID", "foo");
|
||||
shouldBeToken(result[2], "ID", "bar");
|
||||
shouldBeToken(result[3], "ID", "baz");
|
||||
});
|
||||
|
||||
it('tokenizes mustaches with String params as "OPEN ID ID STRING CLOSE"', function() {
|
||||
var result = tokenize("{{ foo bar \"baz\" }}");
|
||||
result.should.match_tokens(['OPEN', 'ID', 'ID', 'STRING', 'CLOSE']);
|
||||
result[3].should.be_token("STRING", "baz");
|
||||
shouldMatchTokens(result, ['OPEN', 'ID', 'ID', 'STRING', 'CLOSE']);
|
||||
shouldBeToken(result[3], "STRING", "baz");
|
||||
});
|
||||
|
||||
it('tokenizes mustaches with String params using single quotes as "OPEN ID ID STRING CLOSE"', function() {
|
||||
var result = tokenize("{{ foo bar \'baz\' }}");
|
||||
result.should.match_tokens(['OPEN', 'ID', 'ID', 'STRING', 'CLOSE']);
|
||||
result[3].should.be_token("STRING", "baz");
|
||||
shouldMatchTokens(result, ['OPEN', 'ID', 'ID', 'STRING', 'CLOSE']);
|
||||
shouldBeToken(result[3], "STRING", "baz");
|
||||
});
|
||||
|
||||
it('tokenizes String params with spaces inside as "STRING"', function() {
|
||||
var result = tokenize("{{ foo bar \"baz bat\" }}");
|
||||
result.should.match_tokens(['OPEN', 'ID', 'ID', 'STRING', 'CLOSE']);
|
||||
result[3].should.be_token("STRING", "baz bat");
|
||||
shouldMatchTokens(result, ['OPEN', 'ID', 'ID', 'STRING', 'CLOSE']);
|
||||
shouldBeToken(result[3], "STRING", "baz bat");
|
||||
});
|
||||
|
||||
it('tokenizes String params with escapes quotes as STRING', function() {
|
||||
var result = tokenize('{{ foo "bar\\"baz" }}');
|
||||
result.should.match_tokens(['OPEN', 'ID', 'STRING', 'CLOSE']);
|
||||
result[2].should.be_token("STRING", 'bar"baz');
|
||||
shouldMatchTokens(result, ['OPEN', 'ID', 'STRING', 'CLOSE']);
|
||||
shouldBeToken(result[2], "STRING", 'bar"baz');
|
||||
});
|
||||
|
||||
it('tokenizes String params using single quotes with escapes quotes as STRING', function() {
|
||||
var result = tokenize("{{ foo 'bar\\'baz' }}");
|
||||
result.should.match_tokens(['OPEN', 'ID', 'STRING', 'CLOSE']);
|
||||
result[2].should.be_token("STRING", "bar'baz");
|
||||
shouldMatchTokens(result, ['OPEN', 'ID', 'STRING', 'CLOSE']);
|
||||
shouldBeToken(result[2], "STRING", "bar'baz");
|
||||
});
|
||||
|
||||
it('tokenizes numbers', function() {
|
||||
var result = tokenize('{{ foo 1 }}');
|
||||
result.should.match_tokens(['OPEN', 'ID', 'INTEGER', 'CLOSE']);
|
||||
result[2].should.be_token("INTEGER", "1");
|
||||
shouldMatchTokens(result, ['OPEN', 'ID', 'INTEGER', 'CLOSE']);
|
||||
shouldBeToken(result[2], "INTEGER", "1");
|
||||
|
||||
result = tokenize('{{ foo -1 }}');
|
||||
result.should.match_tokens(['OPEN', 'ID', 'INTEGER', 'CLOSE']);
|
||||
result[2].should.be_token("INTEGER", "-1");
|
||||
shouldMatchTokens(result, ['OPEN', 'ID', 'INTEGER', 'CLOSE']);
|
||||
shouldBeToken(result[2], "INTEGER", "-1");
|
||||
});
|
||||
|
||||
it('tokenizes booleans', function() {
|
||||
var result = tokenize('{{ foo true }}');
|
||||
result.should.match_tokens(['OPEN', 'ID', 'BOOLEAN', 'CLOSE']);
|
||||
result[2].should.be_token("BOOLEAN", "true");
|
||||
shouldMatchTokens(result, ['OPEN', 'ID', 'BOOLEAN', 'CLOSE']);
|
||||
shouldBeToken(result[2], "BOOLEAN", "true");
|
||||
|
||||
result = tokenize('{{ foo false }}');
|
||||
result.should.match_tokens(['OPEN', 'ID', 'BOOLEAN', 'CLOSE']);
|
||||
result[2].should.be_token("BOOLEAN", "false");
|
||||
shouldMatchTokens(result, ['OPEN', 'ID', 'BOOLEAN', 'CLOSE']);
|
||||
shouldBeToken(result[2], "BOOLEAN", "false");
|
||||
});
|
||||
|
||||
it('tokenizes hash arguments', function() {
|
||||
var result = tokenize("{{ foo bar=baz }}");
|
||||
result.should.match_tokens(['OPEN', 'ID', 'ID', 'EQUALS', 'ID', 'CLOSE']);
|
||||
shouldMatchTokens(result, ['OPEN', 'ID', 'ID', 'EQUALS', 'ID', 'CLOSE']);
|
||||
|
||||
result = tokenize("{{ foo bar baz=bat }}");
|
||||
result.should.match_tokens(['OPEN', 'ID', 'ID', 'ID', 'EQUALS', 'ID', 'CLOSE']);
|
||||
shouldMatchTokens(result, ['OPEN', 'ID', 'ID', 'ID', 'EQUALS', 'ID', 'CLOSE']);
|
||||
|
||||
result = tokenize("{{ foo bar baz=1 }}");
|
||||
result.should.match_tokens(['OPEN', 'ID', 'ID', 'ID', 'EQUALS', 'INTEGER', 'CLOSE']);
|
||||
shouldMatchTokens(result, ['OPEN', 'ID', 'ID', 'ID', 'EQUALS', 'INTEGER', 'CLOSE']);
|
||||
|
||||
result = tokenize("{{ foo bar baz=true }}");
|
||||
result.should.match_tokens(['OPEN', 'ID', 'ID', 'ID', 'EQUALS', 'BOOLEAN', 'CLOSE']);
|
||||
shouldMatchTokens(result, ['OPEN', 'ID', 'ID', 'ID', 'EQUALS', 'BOOLEAN', 'CLOSE']);
|
||||
|
||||
result = tokenize("{{ foo bar baz=false }}");
|
||||
result.should.match_tokens(['OPEN', 'ID', 'ID', 'ID', 'EQUALS', 'BOOLEAN', 'CLOSE']);
|
||||
shouldMatchTokens(result, ['OPEN', 'ID', 'ID', 'ID', 'EQUALS', 'BOOLEAN', 'CLOSE']);
|
||||
|
||||
result = tokenize("{{ foo bar\n baz=bat }}");
|
||||
result.should.match_tokens(['OPEN', 'ID', 'ID', 'ID', 'EQUALS', 'ID', 'CLOSE']);
|
||||
shouldMatchTokens(result, ['OPEN', 'ID', 'ID', 'ID', 'EQUALS', 'ID', 'CLOSE']);
|
||||
|
||||
result = tokenize("{{ foo bar baz=\"bat\" }}");
|
||||
result.should.match_tokens(['OPEN', 'ID', 'ID', 'ID', 'EQUALS', 'STRING', 'CLOSE']);
|
||||
shouldMatchTokens(result, ['OPEN', 'ID', 'ID', 'ID', 'EQUALS', 'STRING', 'CLOSE']);
|
||||
|
||||
result = tokenize("{{ foo bar baz=\"bat\" bam=wot }}");
|
||||
result.should.match_tokens(['OPEN', 'ID', 'ID', 'ID', 'EQUALS', 'STRING', 'ID', 'EQUALS', 'ID', 'CLOSE']);
|
||||
shouldMatchTokens(result, ['OPEN', 'ID', 'ID', 'ID', 'EQUALS', 'STRING', 'ID', 'EQUALS', 'ID', 'CLOSE']);
|
||||
|
||||
result = tokenize("{{foo omg bar=baz bat=\"bam\"}}");
|
||||
result.should.match_tokens(['OPEN', 'ID', 'ID', 'ID', 'EQUALS', 'ID', 'ID', 'EQUALS', 'STRING', 'CLOSE']);
|
||||
result[2].should.be_token("ID", "omg");
|
||||
shouldMatchTokens(result, ['OPEN', 'ID', 'ID', 'ID', 'EQUALS', 'ID', 'ID', 'EQUALS', 'STRING', 'CLOSE']);
|
||||
shouldBeToken(result[2], "ID", "omg");
|
||||
});
|
||||
|
||||
it('tokenizes special @ identifiers', function() {
|
||||
var result = tokenize("{{ @foo }}");
|
||||
result.should.match_tokens(['OPEN', 'DATA', 'ID', 'CLOSE']);
|
||||
result[2].should.be_token("ID", "foo");
|
||||
shouldMatchTokens(result, ['OPEN', 'DATA', 'ID', 'CLOSE']);
|
||||
shouldBeToken(result[2], "ID", "foo");
|
||||
|
||||
result = tokenize("{{ foo @bar }}");
|
||||
result.should.match_tokens(['OPEN', 'ID', 'DATA', 'ID', 'CLOSE']);
|
||||
result[3].should.be_token("ID", "bar");
|
||||
shouldMatchTokens(result, ['OPEN', 'ID', 'DATA', 'ID', 'CLOSE']);
|
||||
shouldBeToken(result[3], "ID", "bar");
|
||||
|
||||
result = tokenize("{{ foo bar=@baz }}");
|
||||
result.should.match_tokens(['OPEN', 'ID', 'ID', 'EQUALS', 'DATA', 'ID', 'CLOSE']);
|
||||
result[5].should.be_token("ID", "baz");
|
||||
shouldMatchTokens(result, ['OPEN', 'ID', 'ID', 'EQUALS', 'DATA', 'ID', 'CLOSE']);
|
||||
shouldBeToken(result[5], "ID", "baz");
|
||||
});
|
||||
|
||||
it('does not time out in a mustache with a single } followed by EOF', function() {
|
||||
tokenize("{{foo}").should.match_tokens(['OPEN', 'ID']);
|
||||
shouldMatchTokens(tokenize("{{foo}"), ['OPEN', 'ID']);
|
||||
});
|
||||
|
||||
it('does not time out in a mustache when invalid ID characters are used', function() {
|
||||
tokenize("{{foo & }}").should.match_tokens(['OPEN', 'ID']);
|
||||
shouldMatchTokens(tokenize("{{foo & }}"), ['OPEN', 'ID']);
|
||||
});
|
||||
});
|
||||
|
||||
+23
-21
@@ -1,11 +1,13 @@
|
||||
/*global shouldCompileTo */
|
||||
/*global Handlebars, shouldCompileTo */
|
||||
|
||||
describe('utils', function() {
|
||||
describe('#SafeString', function() {
|
||||
it("constructing a safestring from a string and checking its type", function() {
|
||||
var safe = new Handlebars.SafeString("testing 1, 2, 3");
|
||||
safe.should.be.instanceof(Handlebars.SafeString);
|
||||
(safe == "testing 1, 2, 3").should.equal(true, "SafeString is equivalent to its underlying string");
|
||||
if (!(safe instanceof Handlebars.SafeString)) {
|
||||
throw new Error('Must be instance of SafeString');
|
||||
}
|
||||
equals(safe == 'testing 1, 2, 3', true, 'SafeString is equivalent to its underlying string');
|
||||
});
|
||||
|
||||
it("it should not escape SafeString properties", function() {
|
||||
@@ -17,41 +19,41 @@ describe('utils', function() {
|
||||
|
||||
describe('#escapeExpression', function() {
|
||||
it('shouhld escape html', function() {
|
||||
Handlebars.Utils.escapeExpression('foo<&"\'>').should.equal('foo<&"'>');
|
||||
equals(Handlebars.Utils.escapeExpression('foo<&"\'>'), 'foo<&"'>');
|
||||
});
|
||||
it('should not escape SafeString', function() {
|
||||
var string = new Handlebars.SafeString('foo<&"\'>');
|
||||
Handlebars.Utils.escapeExpression(string).should.equal('foo<&"\'>');
|
||||
equals(Handlebars.Utils.escapeExpression(string), 'foo<&"\'>');
|
||||
|
||||
});
|
||||
it('should handle falsy', function() {
|
||||
Handlebars.Utils.escapeExpression('').should.equal('');
|
||||
Handlebars.Utils.escapeExpression(undefined).should.equal('');
|
||||
Handlebars.Utils.escapeExpression(null).should.equal('');
|
||||
Handlebars.Utils.escapeExpression(false).should.equal('');
|
||||
equals(Handlebars.Utils.escapeExpression(''), '');
|
||||
equals(Handlebars.Utils.escapeExpression(undefined), '');
|
||||
equals(Handlebars.Utils.escapeExpression(null), '');
|
||||
equals(Handlebars.Utils.escapeExpression(false), '');
|
||||
|
||||
Handlebars.Utils.escapeExpression(0).should.equal('0');
|
||||
equals(Handlebars.Utils.escapeExpression(0), '0');
|
||||
});
|
||||
it('should handle empty objects', function() {
|
||||
Handlebars.Utils.escapeExpression({}).should.equal({}.toString());
|
||||
Handlebars.Utils.escapeExpression([]).should.equal([].toString());
|
||||
equals(Handlebars.Utils.escapeExpression({}), {}.toString());
|
||||
equals(Handlebars.Utils.escapeExpression([]), [].toString());
|
||||
});
|
||||
});
|
||||
|
||||
describe('#isEmpty', function() {
|
||||
it('should not be empty', function() {
|
||||
Handlebars.Utils.isEmpty(undefined).should.equal(true);
|
||||
Handlebars.Utils.isEmpty(null).should.equal(true);
|
||||
Handlebars.Utils.isEmpty(false).should.equal(true);
|
||||
Handlebars.Utils.isEmpty('').should.equal(true);
|
||||
Handlebars.Utils.isEmpty([]).should.equal(true);
|
||||
equals(Handlebars.Utils.isEmpty(undefined), true);
|
||||
equals(Handlebars.Utils.isEmpty(null), true);
|
||||
equals(Handlebars.Utils.isEmpty(false), true);
|
||||
equals(Handlebars.Utils.isEmpty(''), true);
|
||||
equals(Handlebars.Utils.isEmpty([]), true);
|
||||
});
|
||||
|
||||
it('should be empty', function() {
|
||||
Handlebars.Utils.isEmpty(0).should.equal(false);
|
||||
Handlebars.Utils.isEmpty([1]).should.equal(false);
|
||||
Handlebars.Utils.isEmpty('foo').should.equal(false);
|
||||
Handlebars.Utils.isEmpty({bar: 1}).should.equal(false);
|
||||
equals(Handlebars.Utils.isEmpty(0), false);
|
||||
equals(Handlebars.Utils.isEmpty([1]), false);
|
||||
equals(Handlebars.Utils.isEmpty('foo'), false);
|
||||
equals(Handlebars.Utils.isEmpty({bar: 1}), false);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
+4
-4
@@ -43,9 +43,9 @@ ID [^\s!"#%-,\.\/;->@\[-\^`\{-~]+/{LOOKAHEAD}
|
||||
|
||||
[^\x00]+ return 'CONTENT';
|
||||
|
||||
<emu>[^\x00]{2,}?/("{{"|<<EOF>>) {
|
||||
if(yytext.slice(-1) !== "\\") this.popState();
|
||||
if(yytext.slice(-1) === "\\") strip(0,1);
|
||||
// marks CONTENT up to the next mustache or escaped mustache
|
||||
<emu>[^\x00]{2,}?/("{{"|"\\{{"|"\\\\{{"|<<EOF>>) {
|
||||
this.popState();
|
||||
return 'CONTENT';
|
||||
}
|
||||
|
||||
@@ -66,7 +66,7 @@ ID [^\s!"#%-,\.\/;->@\[-\^`\{-~]+/{LOOKAHEAD}
|
||||
<mu>".." return 'ID';
|
||||
<mu>"."/{LOOKAHEAD} return 'ID';
|
||||
<mu>[\/.] return 'SEP';
|
||||
<mu>\s+ /*ignore whitespace*/
|
||||
<mu>\s+ // ignore whitespace
|
||||
<mu>"}"{RIGHT_STRIP}?"}}" this.popState(); return 'CLOSE_UNESCAPED';
|
||||
<mu>{RIGHT_STRIP}?"}}" this.popState(); return 'CLOSE';
|
||||
<mu>'"'("\\"["]|[^"])*'"' yytext = strip(1,2).replace(/\\"/g,'"'); return 'STRING';
|
||||
|
||||
+3
-2
@@ -6,8 +6,8 @@
|
||||
|
||||
function stripFlags(open, close) {
|
||||
return {
|
||||
left: open[2] === '~',
|
||||
right: close[0] === '~' || close[1] === '~'
|
||||
left: open.charAt(2) === '~',
|
||||
right: close.charAt(0) === '~' || close.charAt(1) === '~'
|
||||
};
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@ function stripFlags(open, close) {
|
||||
|
||||
root
|
||||
: statements EOF { return new yy.ProgramNode($1); }
|
||||
| EOF { return new yy.ProgramNode([]); }
|
||||
;
|
||||
|
||||
program
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
/* jshint ignore:start */
|
||||
@@ -1 +1,2 @@
|
||||
export default handlebars;
|
||||
/* jshint ignore:end */
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
module.exports = function(grunt) {
|
||||
grunt.registerMultiTask('packager', 'Transpiles scripts written using ES6 to ES5.', function() {
|
||||
// Execute in here to prevent traceur private var blowup
|
||||
var Packager = require('es6-module-packager').default,
|
||||
fs = require('fs');
|
||||
|
||||
var options = this.options();
|
||||
this.files.forEach(function(file) {
|
||||
var packager = new Packager(file.src[0], {export: options.export});
|
||||
fs.writeFileSync(file.dest, packager.toLocals());
|
||||
});
|
||||
});
|
||||
};
|
||||
+1
-1
@@ -12,7 +12,7 @@ module.exports = function(grunt) {
|
||||
return;
|
||||
}
|
||||
|
||||
var src = ['handlebars.js', 'src/parser-suffix.js'].map(grunt.file.read).join('');
|
||||
var src = ['src/parser-prefix.js', 'handlebars.js', 'src/parser-suffix.js'].map(grunt.file.read).join('');
|
||||
grunt.file.delete('handlebars.js');
|
||||
|
||||
grunt.file.write('lib/handlebars/compiler/parser.js', src);
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
var childProcess = require('child_process'),
|
||||
fs = require('fs');
|
||||
|
||||
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) {
|
||||
if (err) {
|
||||
throw err;
|
||||
}
|
||||
|
||||
if (stdout.toString() !== fs.readFileSync('./spec/expected/empty.amd.js').toString()) {
|
||||
throw new Error('Expected binary output differed');
|
||||
}
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
grunt.registerTask('test:mocha', function() {
|
||||
var done = this.async();
|
||||
|
||||
var runner = childProcess.fork('./spec/env/runner', [], {stdio: 'inherit'});
|
||||
runner.on('close', function(code) {
|
||||
if (code != 0) {
|
||||
grunt.fatal(code + ' tests failed');
|
||||
}
|
||||
done();
|
||||
});
|
||||
});
|
||||
grunt.registerTask('test', ['test:bin', 'test:mocha']);
|
||||
};
|
||||
Reference in New Issue
Block a user