Compare commits
295 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 | |||
| 09cdc19a21 | |||
| 2cb61c9d5f | |||
| 986d6f4096 | |||
| 1e20373428 | |||
| 17ba258355 | |||
| c53e95414f | |||
| 41e2b7658b | |||
| 79fe7afb65 | |||
| 29e3858d21 | |||
| b5969394f1 | |||
| 904daba10b | |||
| 7cef79288a | |||
| 6ab579bb2a | |||
| 60f8aad5c9 | |||
| a2bb02638b | |||
| 479acfbe9e | |||
| 6b62a2e58e | |||
| a6781265a7 | |||
| 23ff9829d5 | |||
| c70e897f1c | |||
| ded0a1617f | |||
| 88fefc1521 | |||
| 4c056a91ba | |||
| 9f138c007a | |||
| 320c0a6fb5 | |||
| 31f7c25a8f | |||
| c776f171ab | |||
| 6b0b3fa2ca | |||
| 1de6cee1b2 | |||
| 151e6d49c0 | |||
| 4e4920cc78 | |||
| d5544d8969 | |||
| 14ef86f9ca | |||
| a1edab6ef5 | |||
| 5b483ff78d | |||
| 30384789d2 | |||
| 4cf49732a2 | |||
| 0cc6e270f9 | |||
| 06d94fed56 | |||
| 6f94fc0a3b | |||
| 84049bf0c5 | |||
| 2cd0995f21 | |||
| 4fda71364e | |||
| 3702efee5b | |||
| 9218f2e1ef | |||
| 97d5efd94a | |||
| 15e1eb36ca | |||
| 64de5c6812 | |||
| 15500aef70 | |||
| 3f52109c1c | |||
| 84c837869e | |||
| dd777c9872 | |||
| 468fa8b6dd | |||
| e9350f2932 | |||
| 0dbeaf973d | |||
| 3150ef15da | |||
| e20591b49d | |||
| 583141de7c | |||
| 3f96319f10 | |||
| 83b5e10d70 | |||
| 52dea1e70e | |||
| abf37ad739 | |||
| 6136e7f346 | |||
| 4d31df2f30 | |||
| 95d5768e0e | |||
| 7ee0c10c10 | |||
| e1f7f00cd4 | |||
| e5a4889b36 | |||
| 9769045e04 | |||
| b47f2cfcf5 | |||
| 096ac2d552 | |||
| 4e4c016be5 | |||
| 2abe4d404d | |||
| 3b0a3ca1b2 | |||
| 4d7124f6bf | |||
| b6c9f85d22 | |||
| 0966f5172b | |||
| 855eae37d0 | |||
| 820cbb006e | |||
| 63bb459ac6 | |||
| a8cd3201b5 | |||
| 19f1a1f81d | |||
| 782aae95d0 | |||
| 5fd0d4a790 | |||
| 5a306ac745 | |||
| 386c20cc63 | |||
| a3b22ac6bf | |||
| 117357b74e | |||
| db0f03d1b5 | |||
| fd09ff0e1b | |||
| e676e43dc5 | |||
| e75839b185 | |||
| 6a23391a9a | |||
| 80b748ad3e | |||
| fbf8f134fa | |||
| 27d9010974 | |||
| 1173e5a502 | |||
| 1e8409efa6 | |||
| a2687fdf50 | |||
| cb0c45b29f | |||
| 0fe78f379a | |||
| 192887cedc | |||
| 06e2a441db | |||
| 530f1903aa | |||
| 035449fe02 | |||
| 03ef3d4258 | |||
| 7ec25f364a | |||
| 28c4f4b9dd | |||
| 97fe2decc9 | |||
| 54cda3b3ae | |||
| dac055f01e | |||
| 1b4b8ed1da | |||
| 115db76f66 | |||
| a28be7c3c0 | |||
| 84b852c137 | |||
| 2dac9c978a | |||
| 1df1756c71 | |||
| 0d1b0c0493 | |||
| 709d57b3c1 | |||
| 496ebaa1a8 | |||
| 9689ebaa1c | |||
| 3c66068279 | |||
| bf99516e55 | |||
| c03b2f4dfa | |||
| b488af39f1 | |||
| ab64fec1a8 | |||
| 672aeda17f | |||
| 74369f724c | |||
| 57a0ee3d59 | |||
| 86694fd122 | |||
| df0734ae5e | |||
| e880bd464e | |||
| 9e09fe1826 | |||
| b773a7608d | |||
| da3788384b | |||
| 5aacdadff8 | |||
| b7f0d2e1fa | |||
| c207c4e048 | |||
| 623fdad59f | |||
| 6e6acaac0d | |||
| 4c02d3027c | |||
| a532d28d94 | |||
| 84420c1eb4 | |||
| f7b2bd8039 | |||
| c40a9eacb2 | |||
| a74aed15e9 | |||
| 94ba2b95d0 | |||
| b700d91a70 | |||
| e77c961d40 | |||
| 7efa75684e | |||
| 08989cfcdd | |||
| 7e16d6cb0d | |||
| 0af54b1142 | |||
| eb1cda6fdc | |||
| 94e5ab8593 | |||
| 3b8a061200 | |||
| 2f1a454467 | |||
| b7c62d8cc5 | |||
| 6492fe8c23 | |||
| 3d77d172ec | |||
| 87b5d4ee61 | |||
| 182ba044bb | |||
| 0b92db7b0c | |||
| 534d0ebcc3 | |||
| 564afab278 | |||
| 18506e00c9 | |||
| d02c90c0fb | |||
| db47593e34 | |||
| 860853ddb4 | |||
| 827a09114e | |||
| 7c1ce99eba | |||
| e62999f9ec | |||
| 4f5c05ffe9 | |||
| 9ca4f9c606 | |||
| 03310df95e | |||
| 3bc7d7b998 | |||
| 6f580c9beb | |||
| d33408fcbd | |||
| 5f664dd78b | |||
| dcbc3a55a4 | |||
| 497515c02f | |||
| 117239ca7c | |||
| c373d1c6d4 | |||
| df7a3fb8c0 | |||
| 199f3984c6 | |||
| f9ed0d3085 | |||
| 163b3f9e51 | |||
| f5c8484ea0 | |||
| da130f7745 | |||
| b5c4a33ab1 | |||
| a27e366f28 | |||
| 12f8299eb2 | |||
| fa881d4ce8 | |||
| ad615d9061 | |||
| 88ee4757e7 | |||
| ca5c3995fe | |||
| e4929df454 | |||
| 96718441ec | |||
| 8e2416dabb | |||
| 68e647a17e | |||
| 982cb2d188 | |||
| 7538b8556c | |||
| 0f278e1dfc | |||
| c3ff573184 | |||
| f11aaa7b7c | |||
| 6174c3b6f8 | |||
| 7def10195b | |||
| 0f4ef161d1 | |||
| bf4ec5eef7 | |||
| 4a545cc686 | |||
| e2a8bdab3c | |||
| 5de919f2f7 | |||
| cfd51f6579 | |||
| 2022c94a30 | |||
| bb8bb048aa | |||
| b3005f8f9f | |||
| 8f24a2e384 | |||
| e624fb50b7 | |||
| 80078861f2 | |||
| 26fcebe0c7 | |||
| 5ce7232dc3 | |||
| 9a334400f0 | |||
| bcc8d00e89 | |||
| 9d4a94616a | |||
| 7b1ad8aa38 | |||
| adda0569e0 | |||
| d13ae310d3 | |||
| 7f82f27c5f | |||
| 720b133839 | |||
| caded1d06b | |||
| 8275242960 | |||
| 265d6249f7 | |||
| e6b6ae8fdb | |||
| efca3c8ef5 |
+3
-1
@@ -2,8 +2,10 @@ vendor
|
||||
.rvmrc
|
||||
.DS_Store
|
||||
lib/handlebars/compiler/parser.js
|
||||
dist/*.min.js
|
||||
/dist/
|
||||
/tmp/
|
||||
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",
|
||||
@@ -21,18 +13,25 @@
|
||||
"stop",
|
||||
"ok",
|
||||
"strictEqual",
|
||||
"module"
|
||||
"module",
|
||||
"define",
|
||||
|
||||
"describe",
|
||||
"it",
|
||||
"afterEach",
|
||||
"beforeEach"
|
||||
],
|
||||
|
||||
"node" : true,
|
||||
"es5" : true,
|
||||
"browser" : true,
|
||||
"esnext": true,
|
||||
|
||||
"boss" : true,
|
||||
"curly": false,
|
||||
"debug": false,
|
||||
"devel": false,
|
||||
"eqeqeq": true,
|
||||
"eqeqeq": false,
|
||||
"eqnull": true,
|
||||
"evil": true,
|
||||
"forin": false,
|
||||
"immed": false,
|
||||
|
||||
+13
@@ -1,10 +1,23 @@
|
||||
.DS_Store
|
||||
.gitignore
|
||||
.rvmrc
|
||||
.jshintrc
|
||||
.travis.yml
|
||||
.rspec
|
||||
Gemfile
|
||||
Gemfile.lock
|
||||
Rakefile
|
||||
Gruntfile.js
|
||||
*.gemspec
|
||||
*.nuspec
|
||||
bench/*
|
||||
configurations/*
|
||||
components/*
|
||||
dist/cdnjs/*
|
||||
dist/components/*
|
||||
spec/*
|
||||
src/*
|
||||
tasks/*
|
||||
tmp/*
|
||||
publish/*
|
||||
vendor/*
|
||||
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
language: node_js
|
||||
node_js:
|
||||
- "0.8"
|
||||
|
||||
before_install:
|
||||
- npm install -g grunt-cli
|
||||
|
||||
script:
|
||||
- grunt --stack travis
|
||||
|
||||
email:
|
||||
on_failure: change
|
||||
on_success: never
|
||||
env:
|
||||
global:
|
||||
- S3_BUCKET_NAME=builds.handlebarsjs.com
|
||||
- secure: PJaukuvkBBsSDOqbIcNSSMgb96VVEaIt/eq9GPjXPeFbSd3hXgwhwVE62LrqtJO8BaUfX+PzpiQjEl4D5/KBmvlFZ057Hqmy0zmPOT5mDZfJe8Ja5zyvTMb+KkCWN/tjAp8kawHojE04pn6jIpPdwXFnAYwPhaHbATFrmdt9fdg=
|
||||
- secure: mBcGL2tnmiRujJdV/4fxrVd8E8wn6AW9IQKVcMv8tvOc7i5dOzZ39rpBKLuTMRXDtMV1LyLiuKYb1pHj1IyeadEahcLYFfGygF4LG7Yzp4NWHtRzQ7Q8LXaJV7dXDboYCFkn2a8/Rtx1YSVh/sCONf5UoRC+MUIqrj4UiHN9r3s=
|
||||
|
||||
matrix:
|
||||
include:
|
||||
- node_js: "0.10"
|
||||
env:
|
||||
- PUBLISH=true
|
||||
- secure: pLTzghtVll9yGKJI0AaB0uI8GypfWxLTaIB0ZL8//yN3nAEIKMhf/RRilYTsn/rKj2NUa7vt2edYILi3lttOUlCBOwTc9amiRms1W8Lwr/3IdWPeBLvLuH1zNJRm2lBAwU4LBSqaOwhGaxOQr6KHTnWudhNhgOucxpZfvfI/dFw=
|
||||
- secure: yERYCf7AwL11D9uMtacly/THGV8BlzsMmrt+iQVvGA3GaY6QMmfYqf6P6cCH98sH5etd1Y+1e6YrPeMjqI6lyRllT7FptoyOdHulazQe86VQN4sc0EpqMlH088kB7gGjTut9Z+X9ViooT5XEh9WA5jXEI9pXhQJNoIHkWPuwGuY=
|
||||
|
||||
cache:
|
||||
directories:
|
||||
- node_modules
|
||||
@@ -1,5 +0,0 @@
|
||||
source "http://rubygems.org"
|
||||
|
||||
gem "rake"
|
||||
gem "therubyracer", ">= 0.9.8", "< 0.11"
|
||||
gem "rspec"
|
||||
@@ -1,24 +0,0 @@
|
||||
GEM
|
||||
remote: http://rubygems.org/
|
||||
specs:
|
||||
diff-lcs (1.1.3)
|
||||
libv8 (3.3.10.4)
|
||||
rake (10.0.3)
|
||||
rspec (2.12.0)
|
||||
rspec-core (~> 2.12.0)
|
||||
rspec-expectations (~> 2.12.0)
|
||||
rspec-mocks (~> 2.12.0)
|
||||
rspec-core (2.12.2)
|
||||
rspec-expectations (2.12.1)
|
||||
diff-lcs (~> 1.1.3)
|
||||
rspec-mocks (2.12.1)
|
||||
therubyracer (0.10.2)
|
||||
libv8 (~> 3.3.10)
|
||||
|
||||
PLATFORMS
|
||||
ruby
|
||||
|
||||
DEPENDENCIES
|
||||
rake
|
||||
rspec
|
||||
therubyracer (>= 0.9.8, < 0.11)
|
||||
+205
@@ -0,0 +1,205 @@
|
||||
var childProcess = require('child_process');
|
||||
|
||||
module.exports = function(grunt) {
|
||||
|
||||
grunt.initConfig({
|
||||
pkg: grunt.file.readJSON('package.json'),
|
||||
|
||||
jshint: {
|
||||
options: {
|
||||
jshintrc: '.jshintrc'
|
||||
},
|
||||
files: [
|
||||
'dist/**/!(*.min|parser).js'
|
||||
]
|
||||
},
|
||||
|
||||
clean: ['tmp', 'dist', 'lib/handlebars/compiler/parser.js'],
|
||||
|
||||
copy: {
|
||||
dist: {
|
||||
options: {
|
||||
processContent: function(content, path) {
|
||||
return grunt.template.process('/*!\n\n <%= pkg.name %> v<%= pkg.version %>\n\n<%= grunt.file.read("LICENSE") %>\n@license\n*/\n')
|
||||
+ content;
|
||||
}
|
||||
},
|
||||
files: [
|
||||
{expand: true, cwd: 'dist/', src: ['*.js'], dest: 'dist/'}
|
||||
]
|
||||
},
|
||||
cdnjs: {
|
||||
files: [
|
||||
{expand: true, cwd: 'dist/', src: ['*.js'], dest: 'dist/cdnjs'}
|
||||
]
|
||||
},
|
||||
components: {
|
||||
files: [
|
||||
{expand: true, cwd: 'components/', src: ['**'], dest: 'dist/components'},
|
||||
{expand: true, cwd: 'dist/', src: ['*.js'], dest: 'dist/components'}
|
||||
]
|
||||
}
|
||||
},
|
||||
|
||||
packager: {
|
||||
global: {
|
||||
type: 'global',
|
||||
export: 'Handlebars',
|
||||
files: [{
|
||||
cwd: 'lib/',
|
||||
expand: true,
|
||||
src: ['handlebars*.js'],
|
||||
dest: 'dist/'
|
||||
}]
|
||||
},
|
||||
|
||||
amd: {
|
||||
type: 'amd',
|
||||
anonymous: true,
|
||||
files: [{
|
||||
expand: true,
|
||||
cwd: 'lib/',
|
||||
src: '**/!(index).js',
|
||||
dest: 'dist/amd/'
|
||||
}]
|
||||
},
|
||||
|
||||
cjs: {
|
||||
type: 'cjs',
|
||||
files: [{
|
||||
expand: true,
|
||||
cwd: 'lib/',
|
||||
src: '**/!(index).js',
|
||||
dest: 'dist/cjs/'
|
||||
}]
|
||||
}
|
||||
},
|
||||
requirejs: {
|
||||
options: {
|
||||
optimize: "none",
|
||||
baseUrl: "dist/amd/"
|
||||
},
|
||||
dist: {
|
||||
options: {
|
||||
name: "handlebars",
|
||||
out: "dist/handlebars.amd.js"
|
||||
}
|
||||
},
|
||||
runtime: {
|
||||
options: {
|
||||
name: "handlebars.runtime",
|
||||
out: "dist/handlebars.runtime.amd.js"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
uglify: {
|
||||
options: {
|
||||
mangle: true,
|
||||
compress: true,
|
||||
preserveComments: 'some'
|
||||
},
|
||||
dist: {
|
||||
files: [{
|
||||
cwd: 'dist/',
|
||||
expand: true,
|
||||
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", [
|
||||
'parser',
|
||||
'node',
|
||||
'globals',
|
||||
'jshint']);
|
||||
|
||||
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', '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-contrib-watch');
|
||||
grunt.loadNpmTasks('grunt-saucelabs');
|
||||
grunt.loadNpmTasks('es6-module-packager');
|
||||
|
||||
grunt.task.loadTasks('tasks');
|
||||
|
||||
grunt.registerTask('bench', ['metrics']);
|
||||
grunt.registerTask('sauce', process.env.SAUCE_USERNAME ? ['tests', 'connect', 'saucelabs-mocha'] : []);
|
||||
|
||||
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']);
|
||||
};
|
||||
+104
-38
@@ -1,4 +1,5 @@
|
||||
[](https://travis-ci.org/wycats/handlebars.js)
|
||||
[](https://travis-ci.org/wycats/handlebars.js)
|
||||
[](https://saucelabs.com/u/handlebars)
|
||||
|
||||
Handlebars.js
|
||||
=============
|
||||
@@ -11,12 +12,20 @@ keep the view and the code separated like we all know they should be.
|
||||
Checkout the official Handlebars docs site at
|
||||
[http://www.handlebarsjs.com](http://www.handlebarsjs.com).
|
||||
|
||||
|
||||
Installing
|
||||
----------
|
||||
Installing Handlebars is easy. Simply download the package [from 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
|
||||
published to S3. You may download the latest passing master build by grabbing
|
||||
a `handlebars-latest.js` file from the [builds page][builds-page]. When the
|
||||
build is published, it is also available as a `handlebars-gitSHA.js` file on
|
||||
the builds page if you need a version to refer to others.
|
||||
`handlebars-runtime.js` builds are also available.
|
||||
|
||||
**Note**: The S3 builds page is provided as a convenience for the community,
|
||||
but you should not use it for hosting Handlebars in production.
|
||||
|
||||
Usage
|
||||
-----
|
||||
@@ -108,7 +117,7 @@ To display data from descendant contexts, use the `.` character. So, for
|
||||
example, if your data were structured like:
|
||||
|
||||
```js
|
||||
var data = {"person": { "name": "Alan" }, company: {"name": "Rad, Inc." } };
|
||||
var data = {"person": { "name": "Alan" }, "company": {"name": "Rad, Inc." } };
|
||||
```
|
||||
|
||||
You could display the person's name from the top-level context with the
|
||||
@@ -260,13 +269,20 @@ Precompile handlebar templates.
|
||||
Usage: handlebars template...
|
||||
|
||||
Options:
|
||||
-a, --amd Create an AMD format function (allows loading with RequireJS) [boolean]
|
||||
-f, --output Output File [string]
|
||||
-k, --known Known helpers [string]
|
||||
-o, --knownOnly Known helpers only [boolean]
|
||||
-m, --min Minimize output [boolean]
|
||||
-s, --simple Output template function only. [boolean]
|
||||
-r, --root Template root. Base value that will be stripped from template names. [string]
|
||||
-a, --amd Create an AMD format function (allows loading with RequireJS) [boolean]
|
||||
-f, --output Output File [string]
|
||||
-k, --known Known helpers [string]
|
||||
-o, --knownOnly Known helpers only [boolean]
|
||||
-m, --min Minimize output [boolean]
|
||||
-s, --simple Output template function only. [boolean]
|
||||
-r, --root Template root. Base value that will be stripped from template names. [string]
|
||||
-c, --commonjs Exports CommonJS style, path to Handlebars module [string]
|
||||
-h, --handlebarPath Path to handlebar.js (only valid for amd-style) [string]
|
||||
-n, --namespace Template namespace [string]
|
||||
-p, --partial Compiling a partial template [boolean]
|
||||
-d, --data Include data when compiling [boolean]
|
||||
-e, --extension Template extension. [string]
|
||||
-b, --bom Removes the BOM (Byte Order Mark) from the beginning of the templates. [boolean]
|
||||
</pre>
|
||||
|
||||
If using the precompiler's normal mode, the resulting templates will be
|
||||
@@ -289,6 +305,8 @@ normal.
|
||||
helpers for size and speed.
|
||||
- When all helpers are known in advance the `--knownOnly` argument may be used
|
||||
to optimize all block helper references.
|
||||
- Implementations that do not use `@data` variables can improve performance of
|
||||
iteration centric templates by specifying `{data: false}` in the compiler options.
|
||||
|
||||
Supported Environments
|
||||
----------------------
|
||||
@@ -305,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
|
||||
-----------
|
||||
|
||||
@@ -322,8 +342,7 @@ and we will have some benchmarks in the near future.
|
||||
Building
|
||||
--------
|
||||
|
||||
To build handlebars, just run `rake release`, and you will get two files
|
||||
in the `dist` directory.
|
||||
To build handlebars, just run `grunt build`, and the build will output to the `dist` directory.
|
||||
|
||||
|
||||
Upgrading
|
||||
@@ -337,47 +356,57 @@ Known Issues
|
||||
* Using a variable, helper, or partial named `class` causes errors in IE browsers. (Instead, use `className`)
|
||||
|
||||
Handlebars in the Wild
|
||||
-----------------
|
||||
----------------------
|
||||
|
||||
* [Assemble](http://assemble.io), by [@jonschlinkert](https://github.com/jonschlinkert)
|
||||
and [@doowb](https://github.com/doowb), is a static site generator that uses Handlebars.js
|
||||
as its template engine.
|
||||
* [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
|
||||
with 100+ handlebars helpers.
|
||||
* [hbs](http://github.com/donpark/hbs): An Express.js view engine adapter for Handlebars.js,
|
||||
from Don Park.
|
||||
* [jblotus](http://github.com/jblotus) created [http://tryhandlebarsjs.com](http://tryhandlebarsjs.com)
|
||||
for anyone who would like to try out Handlebars.js in their browser.
|
||||
* Don Park wrote an Express.js view engine adapter for Handlebars.js called
|
||||
[hbs](http://github.com/donpark/hbs).
|
||||
* [jQuery plugin](http://71104.github.io/jquery-handlebars/): allows you to use
|
||||
Handlebars.js with [jQuery](http://jquery.com/).
|
||||
* [Lumbar](http://walmartlabs.github.io/lumbar) provides easy module-based template management for
|
||||
handlebars projects.
|
||||
* [sammy.js](http://github.com/quirkey/sammy) by Aaron Quint, a.k.a. quirkey,
|
||||
supports Handlebars.js as one of its template plugins.
|
||||
* [SproutCore](http://www.sproutcore.com) uses Handlebars.js as its main
|
||||
templating engine, extending it with automatic data binding support.
|
||||
* [Ember.js](http://www.emberjs.com) makes Handlebars.js the primary way to
|
||||
structure your views, also with automatic data binding support.
|
||||
* Les Hill (@leshill) wrote a Rails Asset Pipeline gem named
|
||||
[handlebars_assets](http://github.com/leshill/handlebars_assets).
|
||||
* [Gist about Synchronous and asynchronous loading of external handlebars templates](https://gist.github.com/2287070)
|
||||
* [Lumbar](walmartlabs.github.io/lumbar) provides easy module-based template management for handlebars projects.
|
||||
* [YUI](http://yuilibrary.com/yui/docs/handlebars/) implements a port of handlebars
|
||||
* [Swag](https://github.com/elving/swag) by [@elving](https://github.com/elving) is a growing collection of helpers for handlebars.js. Give your handlebars.js templates some swag son!
|
||||
|
||||
Have a project using Handlebars? Send us a [pull request](https://github.com/wycats/handlebars.js/pull/new/master)!
|
||||
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][pull-request]!
|
||||
|
||||
Helping Out
|
||||
-----------
|
||||
|
||||
To build Handlebars.js you'll need a few things installed.
|
||||
|
||||
* Node.js
|
||||
* Ruby
|
||||
* therubyracer, for running tests - `gem install therubyracer`
|
||||
* rspec, for running tests - `gem install rspec`
|
||||
* [Grunt](http://gruntjs.com/getting-started)
|
||||
|
||||
There's a Gemfile in the repo, so you can run `bundle` to install rspec
|
||||
and therubyracer if you've got bundler installed.
|
||||
Project dependencies may be installed via `npm install`.
|
||||
|
||||
To build Handlebars.js from scratch, you'll want to run `rake compile`
|
||||
To build Handlebars.js from scratch, you'll want to run `grunt`
|
||||
in the root of the project. That will build Handlebars and output the
|
||||
results to the dist/ folder. To run tests, run `rake test`. You can also
|
||||
run our set of benchmarks with `rake bench`. Node tests can be run with
|
||||
`npm test` or `rake npm_test`. The default rake target will compile and
|
||||
run both test suites.
|
||||
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`.
|
||||
|
||||
Some environments, notably Windows, have issues running therubyracer. Under these
|
||||
envrionments the `rake compile` and `npm test` should be sufficient to test
|
||||
most handlebars functionality.
|
||||
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).
|
||||
@@ -385,7 +414,44 @@ 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.
|
||||
|
||||
A full release may be completed with the following:
|
||||
|
||||
```
|
||||
yo release:notes patch
|
||||
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.
|
||||
|
||||
License
|
||||
-------
|
||||
Handlebars.js is released under the MIT license.
|
||||
|
||||
[bower-repo]: https://github.com/components/handlebars.js
|
||||
[builds-page]: http://builds.handlebarsjs.com.s3.amazonaws.com/bucket-listing.html?sort=lastmod&sortdir=desc
|
||||
[generator-release]: https://github.com/walmartlabs/generator-release
|
||||
[pull-request]: https://github.com/wycats/handlebars.js/pull/new/master
|
||||
|
||||
@@ -1,136 +0,0 @@
|
||||
require "rubygems"
|
||||
require "bundler/setup"
|
||||
|
||||
def compile_parser
|
||||
system "./node_modules/.bin/jison -m js src/handlebars.yy src/handlebars.l"
|
||||
if $?.success?
|
||||
File.open("lib/handlebars/compiler/parser.js", "w") do |file|
|
||||
file.puts File.read("src/parser-prefix.js") + File.read("handlebars.js") + File.read("src/parser-suffix.js")
|
||||
end
|
||||
|
||||
sh "rm handlebars.js"
|
||||
else
|
||||
fail "Failed to run Jison."
|
||||
end
|
||||
end
|
||||
|
||||
file "lib/handlebars/compiler/parser.js" => ["src/handlebars.yy","src/handlebars.l"] do
|
||||
if File.exists?('./node_modules/jison')
|
||||
compile_parser
|
||||
else
|
||||
puts "Jison is not installed. Trying `npm install jison`."
|
||||
sh "npm install"
|
||||
compile_parser
|
||||
end
|
||||
end
|
||||
|
||||
task :compile => "lib/handlebars/compiler/parser.js"
|
||||
|
||||
desc "run the spec suite"
|
||||
task :spec => [:release] do
|
||||
rc = system "rspec -cfs spec"
|
||||
fail "rspec spec failed with exit code #{$?.exitstatus}" if (rc.nil? || ! rc || $?.exitstatus != 0)
|
||||
end
|
||||
|
||||
desc "run the npm test suite"
|
||||
task :npm_test => [:release] do
|
||||
rc = system "npm test"
|
||||
fail "npm test failed with exit code #{$?.exitstatus}" if (rc.nil? || ! rc || $?.exitstatus != 0)
|
||||
end
|
||||
|
||||
task :default => [:compile, :spec, :npm_test]
|
||||
|
||||
def remove_exports(string)
|
||||
match = string.match(%r{^// BEGIN\(BROWSER\)\n(.*)\n^// END\(BROWSER\)}m)
|
||||
match ? match[1] : string
|
||||
end
|
||||
|
||||
minimal_deps = %w(browser-prefix base compiler/parser compiler/base compiler/ast utils compiler/compiler runtime browser-suffix).map do |file|
|
||||
"lib/handlebars/#{file}.js"
|
||||
end
|
||||
|
||||
runtime_deps = %w(browser-prefix base utils runtime browser-suffix).map do |file|
|
||||
"lib/handlebars/#{file}.js"
|
||||
end
|
||||
|
||||
directory "dist"
|
||||
|
||||
minimal_deps.unshift "dist"
|
||||
|
||||
def build_for_task(task)
|
||||
FileUtils.rm_rf("dist/*") if File.directory?("dist")
|
||||
FileUtils.mkdir_p("dist")
|
||||
|
||||
contents = ["/*\n\n" + File.read('LICENSE') + "\n*/\n"]
|
||||
task.prerequisites.each do |filename|
|
||||
next if filename == "dist"
|
||||
|
||||
contents << "// #{filename}\n" + remove_exports(File.read(filename)) + ";"
|
||||
end
|
||||
|
||||
File.open(task.name, "w") do |file|
|
||||
file.puts contents.join("\n")
|
||||
end
|
||||
end
|
||||
|
||||
file "dist/handlebars.js" => minimal_deps do |task|
|
||||
build_for_task(task)
|
||||
end
|
||||
|
||||
file "dist/handlebars.runtime.js" => runtime_deps do |task|
|
||||
build_for_task(task)
|
||||
end
|
||||
|
||||
task :build => [:compile] do |task|
|
||||
# Since the tests are dependent on this always rebuild.
|
||||
Rake::Task["dist/handlebars.js"].execute
|
||||
end
|
||||
task :runtime => [:compile] do |task|
|
||||
# Since the tests are dependent on this always rebuild.
|
||||
Rake::Task["dist/handlebars.runtime.js"].execute
|
||||
end
|
||||
|
||||
# Updates the various version numbers.
|
||||
task :version => [] do |task|
|
||||
# TODO : Pull from package.json when the version numbers are synced
|
||||
version = File.read("lib/handlebars/base.js").match(/Handlebars.VERSION = "(.*)";/)[1]
|
||||
|
||||
content = File.read("bower.json")
|
||||
File.open("bower.json", "w") do |file|
|
||||
file.puts content.gsub(/"version":.*/, "\"version\": \"#{version}\",")
|
||||
end
|
||||
|
||||
content = File.read("handlebars.js.nuspec")
|
||||
File.open("handlebars.js.nuspec", "w") do |file|
|
||||
file.puts content.gsub(/<version>.*<\/version>/, "<version>#{version}</version>")
|
||||
end
|
||||
end
|
||||
|
||||
task :minify => [] do |task|
|
||||
system "./node_modules/.bin/uglifyjs --comments -o dist/handlebars.min.js dist/handlebars.js"
|
||||
system "./node_modules/.bin/uglifyjs --comments -o dist/handlebars.runtime.min.js dist/handlebars.runtime.js"
|
||||
end
|
||||
|
||||
desc "build the build and runtime version of handlebars"
|
||||
task :release => [:version, :build, :runtime, :minify]
|
||||
|
||||
directory "vendor"
|
||||
|
||||
desc "benchmark against dust.js and mustache.js"
|
||||
task :bench => "vendor" do
|
||||
require "open-uri"
|
||||
|
||||
#if File.directory?("vendor/coffee")
|
||||
#system "cd vendor/coffee && git pull"
|
||||
#else
|
||||
#system "git clone git://github.com/jashkenas/coffee-script.git vendor/coffee"
|
||||
#end
|
||||
|
||||
#if File.directory?("vendor/eco")
|
||||
#system "cd vendor/eco && git pull && npm update"
|
||||
#else
|
||||
#system "git clone git://github.com/sstephenson/eco.git vendor/eco && cd vendor/eco && npm update"
|
||||
#end
|
||||
|
||||
system "node bench/handlebars.js"
|
||||
end
|
||||
@@ -0,0 +1,39 @@
|
||||
var _ = require('underscore'),
|
||||
async = require('async'),
|
||||
fs = require('fs'),
|
||||
zlib = require('zlib');
|
||||
|
||||
module.exports = function(grunt, callback) {
|
||||
var distFiles = fs.readdirSync('dist'),
|
||||
distSizes = {};
|
||||
|
||||
async.each(distFiles, function(file, callback) {
|
||||
var content;
|
||||
try {
|
||||
content = fs.readFileSync('dist/' + file);
|
||||
} catch (err) {
|
||||
if (err.code === 'EISDIR') {
|
||||
callback();
|
||||
return;
|
||||
} else {
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
file = file.replace(/\.js/, '').replace(/\./g, '_');
|
||||
distSizes[file] = content.length;
|
||||
|
||||
zlib.gzip(content, function(err, data) {
|
||||
if (err) {
|
||||
throw err;
|
||||
}
|
||||
|
||||
distSizes[file + '_gz'] = data.length;
|
||||
callback();
|
||||
});
|
||||
},
|
||||
function() {
|
||||
grunt.log.writeln('Distribution sizes: ' + JSON.stringify(distSizes, undefined, 2));
|
||||
callback([distSizes]);
|
||||
});
|
||||
};
|
||||
@@ -1,172 +0,0 @@
|
||||
var BenchWarmer = require("./benchwarmer");
|
||||
Handlebars = require("../lib/handlebars");
|
||||
|
||||
var dust, Mustache, eco;
|
||||
|
||||
try {
|
||||
dust = require("dust");
|
||||
} catch (err) { /* NOP */ }
|
||||
|
||||
try {
|
||||
Mustache = require("mustache");
|
||||
} catch (err) { /* NOP */ }
|
||||
|
||||
try {
|
||||
var ecoExports = require("eco");
|
||||
eco = function(str) {
|
||||
return ecoExports(str);
|
||||
}
|
||||
} catch (err) { /* NOP */ }
|
||||
|
||||
var benchDetails = {
|
||||
string: {
|
||||
context: {},
|
||||
handlebars: "Hello world",
|
||||
dust: "Hello world",
|
||||
mustache: "Hello world",
|
||||
eco: "Hello world"
|
||||
},
|
||||
variables: {
|
||||
context: {name: "Mick", count: 30},
|
||||
handlebars: "Hello {{name}}! You have {{count}} new messages.",
|
||||
dust: "Hello {name}! You have {count} new messages.",
|
||||
mustache: "Hello {{name}}! You have {{count}} new messages.",
|
||||
eco: "Hello <%= @name %>! You have <%= @count %> new messages."
|
||||
},
|
||||
object: {
|
||||
context: { person: { name: "Larry", age: 45 } },
|
||||
handlebars: "{{#with person}}{{name}}{{age}}{{/with}}",
|
||||
dust: "{#person}{name}{age}{/person}",
|
||||
mustache: "{{#person}}{{name}}{{age}}{{/person}}"
|
||||
},
|
||||
array: {
|
||||
context: { names: [{name: "Moe"}, {name: "Larry"}, {name: "Curly"}, {name: "Shemp"}] },
|
||||
handlebars: "{{#each names}}{{name}}{{/each}}",
|
||||
dust: "{#names}{name}{/names}",
|
||||
mustache: "{{#names}}{{name}}{{/names}}",
|
||||
eco: "<% for item in @names: %><%= item.name %><% end %>"
|
||||
},
|
||||
partial: {
|
||||
context: { peeps: [{name: "Moe", count: 15}, {name: "Larry", count: 5}, {name: "Curly", count: 1}] },
|
||||
partials: {
|
||||
mustache: { variables: "Hello {{name}}! You have {{count}} new messages." },
|
||||
handlebars: { variables: "Hello {{name}}! You have {{count}} new messages." }
|
||||
},
|
||||
handlebars: "{{#each peeps}}{{>variables}}{{/each}}",
|
||||
dust: "{#peeps}{>variables/}{/peeps}",
|
||||
mustache: "{{#peeps}}{{>variables}}{{/peeps}}"
|
||||
},
|
||||
recursion: {
|
||||
context: { name: '1', kids: [{ name: '1.1', kids: [{name: '1.1.1', kids: []}] }] },
|
||||
partials: {
|
||||
mustache: { recursion: "{{name}}{{#kids}}{{>recursion}}{{/kids}}" },
|
||||
handlebars: { recursion: "{{name}}{{#each kids}}{{>recursion}}{{/each}}" }
|
||||
},
|
||||
handlebars: "{{name}}{{#each kids}}{{>recursion}}{{/each}}",
|
||||
dust: "{name}{#kids}{>recursion:./}{/kids}",
|
||||
mustache: "{{name}}{{#kids}}{{>recursion}}{{/kids}}"
|
||||
},
|
||||
complex: {
|
||||
handlebars: "<h1>{{header}}</h1>{{#if items}}<ul>{{#each items}}{{#if current}}" +
|
||||
"<li><strong>{{name}}</strong></li>{{^}}" +
|
||||
"<li><a href=\"{{url}}\">{{name}}</a></li>{{/if}}" +
|
||||
"{{/each}}</ul>{{^}}<p>The list is empty.</p>{{/if}}",
|
||||
|
||||
dust: "<h1>{header}</h1>\n" +
|
||||
"{?items}\n" +
|
||||
" <ul>\n" +
|
||||
" {#items}\n" +
|
||||
" {#current}\n" +
|
||||
" <li><strong>{name}</strong></li>\n" +
|
||||
" {:else}\n" +
|
||||
" <li><a href=\"{url}\">{name}</a></li>\n" +
|
||||
" {/current}\n" +
|
||||
" {/items}\n" +
|
||||
" </ul>\n" +
|
||||
"{:else}\n" +
|
||||
" <p>The list is empty.</p>\n" +
|
||||
"{/items}",
|
||||
context: {
|
||||
header: function() {
|
||||
return "Colors";
|
||||
},
|
||||
items: [
|
||||
{name: "red", current: true, url: "#Red"},
|
||||
{name: "green", current: false, url: "#Green"},
|
||||
{name: "blue", current: false, url: "#Blue"}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
handlebarsTemplates = {};
|
||||
ecoTemplates = {};
|
||||
|
||||
var warmer = new BenchWarmer();
|
||||
|
||||
var makeSuite = function(name) {
|
||||
warmer.suite(name, function(bench) {
|
||||
var templateName = name;
|
||||
var details = benchDetails[templateName];
|
||||
var mustachePartials = details.partials && details.partials.mustache;
|
||||
var mustacheSource = details.mustache;
|
||||
var context = details.context;
|
||||
|
||||
var error = function() { throw new Error("EWOT"); };
|
||||
|
||||
if (dust) {
|
||||
bench("dust", function() {
|
||||
dust.render(templateName, context, function(err, out) { });
|
||||
});
|
||||
}
|
||||
|
||||
bench("handlebars", function() {
|
||||
handlebarsTemplates[templateName](context);
|
||||
});
|
||||
|
||||
if (eco) {
|
||||
if(ecoTemplates[templateName]) {
|
||||
bench("eco", function() {
|
||||
ecoTemplates[templateName](context);
|
||||
});
|
||||
} else {
|
||||
bench("eco", error);
|
||||
}
|
||||
}
|
||||
|
||||
if (Mustache && mustacheSource) {
|
||||
bench("mustache", function() {
|
||||
Mustache.to_html(mustacheSource, context, mustachePartials);
|
||||
});
|
||||
} else {
|
||||
bench("mustache", error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
for(var name in benchDetails) {
|
||||
if(benchDetails.hasOwnProperty(name)) {
|
||||
if (dust) {
|
||||
dust.loadSource(dust.compile(benchDetails[name].dust, name));
|
||||
}
|
||||
handlebarsTemplates[name] = Handlebars.compile(benchDetails[name].handlebars);
|
||||
|
||||
if (eco && benchDetails[name].eco) {
|
||||
ecoTemplates[name] = eco(benchDetails[name].eco);
|
||||
}
|
||||
|
||||
var partials = benchDetails[name].partials;
|
||||
if(partials) {
|
||||
for(var partialName in partials.handlebars) {
|
||||
if(partials.handlebars.hasOwnProperty(partialName)) {
|
||||
Handlebars.registerPartial(partialName, partials.handlebars[partialName]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
makeSuite(name);
|
||||
}
|
||||
}
|
||||
|
||||
warmer.bench();
|
||||
@@ -0,0 +1,14 @@
|
||||
var fs = require('fs');
|
||||
|
||||
var metrics = fs.readdirSync(__dirname);
|
||||
metrics.forEach(function(metric) {
|
||||
if (metric === 'index.js' || !/(.*)\.js$/.test(metric)) {
|
||||
return;
|
||||
}
|
||||
|
||||
var name = RegExp.$1;
|
||||
metric = require('./' + name);
|
||||
if (metric instanceof Function) {
|
||||
module.exports[name] = metric;
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,19 @@
|
||||
var _ = require('underscore'),
|
||||
templates = require('./templates');
|
||||
|
||||
module.exports = function(grunt, callback) {
|
||||
// Deferring to here in case we have a build for parser, etc as part of this grunt exec
|
||||
var Handlebars = require('../lib');
|
||||
|
||||
var templateSizes = {};
|
||||
_.each(templates, function(info, template) {
|
||||
var src = info.handlebars,
|
||||
compiled = Handlebars.precompile(src, {}),
|
||||
knownHelpers = Handlebars.precompile(src, {knownHelpersOnly: true, knownHelpers: info.helpers});
|
||||
|
||||
templateSizes[template] = compiled.length;
|
||||
templateSizes['knownOnly_' + template] = knownHelpers.length;
|
||||
});
|
||||
grunt.log.writeln('Precompiled sizes: ' + JSON.stringify(templateSizes, undefined, 2));
|
||||
callback([templateSizes]);
|
||||
};
|
||||
@@ -0,0 +1,12 @@
|
||||
module.exports = {
|
||||
helpers: {
|
||||
foo: function(options) {
|
||||
return '';
|
||||
}
|
||||
},
|
||||
context: {
|
||||
bar: true
|
||||
},
|
||||
|
||||
handlebars: '{{foo person "person" 1 true foo=bar foo="person" foo=1 foo=true}}'
|
||||
};
|
||||
@@ -0,0 +1,7 @@
|
||||
module.exports = {
|
||||
context: { names: [{name: "Moe"}, {name: "Larry"}, {name: "Curly"}, {name: "Shemp"}] },
|
||||
handlebars: "{{#each names}}{{name}}{{/each}}",
|
||||
dust: "{#names}{name}{/names}",
|
||||
mustache: "{{#names}}{{name}}{{/names}}",
|
||||
eco: "<% for item in @names: %><%= item.name %><% end %>"
|
||||
};
|
||||
@@ -0,0 +1,4 @@
|
||||
module.exports = {
|
||||
context: { names: [{name: "Moe"}, {name: "Larry"}, {name: "Curly"}, {name: "Shemp"}] },
|
||||
handlebars: "{{#names}}{{name}}{{/names}}"
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
<h1>{header}</h1>
|
||||
{?items}
|
||||
<ul>
|
||||
{#items}
|
||||
{#current}
|
||||
<li><strong>{name}</strong></li>
|
||||
{:else}
|
||||
<li><a href="{url}">{name}</a></li>
|
||||
{/current}
|
||||
{/items}
|
||||
</ul>
|
||||
{:else}
|
||||
<p>The list is empty.</p>
|
||||
{/items}
|
||||
@@ -0,0 +1,14 @@
|
||||
<h1><%= @header() %></h1>
|
||||
<% if @items.length: %>
|
||||
<ul>
|
||||
<% for item in @items: %>
|
||||
<% if item.current: %>
|
||||
<li><strong><%= item.name %></strong></li>
|
||||
<% else: %>
|
||||
<li><a href="<%= item.url %>"><%= item.name %></a></li>
|
||||
<% end %>
|
||||
<% end %>
|
||||
</ul>
|
||||
<% else: %>
|
||||
<p>The list is empty.</p>
|
||||
<% end %>
|
||||
@@ -0,0 +1,14 @@
|
||||
<h1>{{header}}</h1>
|
||||
{{#if items}}
|
||||
<ul>
|
||||
{{#each items}}
|
||||
{{#if current}}
|
||||
<li><strong>{{name}}</strong></li>
|
||||
{{^}}
|
||||
<li><a href="{{url}}">{{name}}</a></li>
|
||||
{{/if}}
|
||||
{{/each}}
|
||||
</ul>
|
||||
{{^}}
|
||||
<p>The list is empty.</p>
|
||||
{{/if}}
|
||||
@@ -0,0 +1,20 @@
|
||||
var fs = require('fs');
|
||||
|
||||
module.exports = {
|
||||
context: {
|
||||
header: function() {
|
||||
return "Colors";
|
||||
},
|
||||
hasItems: true, // To make things fairer in mustache land due to no `{{if}}` construct on arrays
|
||||
items: [
|
||||
{name: "red", current: true, url: "#Red"},
|
||||
{name: "green", current: false, url: "#Green"},
|
||||
{name: "blue", current: false, url: "#Blue"}
|
||||
]
|
||||
},
|
||||
|
||||
handlebars: fs.readFileSync(__dirname + '/complex.handlebars').toString(),
|
||||
dust: fs.readFileSync(__dirname + '/complex.dust').toString(),
|
||||
eco: fs.readFileSync(__dirname + '/complex.eco').toString(),
|
||||
mustache: fs.readFileSync(__dirname + '/complex.mustache').toString()
|
||||
};
|
||||
@@ -0,0 +1,13 @@
|
||||
<h1>{{header}}</h1>
|
||||
{{#hasItems}}
|
||||
<ul>
|
||||
{{#items}}
|
||||
{{#current}}
|
||||
<li><strong>{{name}}</strong></li>
|
||||
{{/current}}
|
||||
{{^current}}
|
||||
<li><a href="{{url}}">{{name}}</a></li>
|
||||
{{/current}}
|
||||
{{/items}}
|
||||
</ul>
|
||||
{{/hasItems}}
|
||||
@@ -0,0 +1,4 @@
|
||||
module.exports = {
|
||||
context: { names: [{name: "Moe"}, {name: "Larry"}, {name: "Curly"}, {name: "Shemp"}] },
|
||||
handlebars: "{{#each names}}{{@index}}{{name}}{{/each}}"
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
var fs = require('fs');
|
||||
|
||||
var templates = fs.readdirSync(__dirname);
|
||||
templates.forEach(function(template) {
|
||||
if (template === 'index.js' || !/(.*)\.js$/.test(template)) {
|
||||
return;
|
||||
}
|
||||
module.exports[RegExp.$1] = require('./' + RegExp.$1);
|
||||
});
|
||||
@@ -0,0 +1,4 @@
|
||||
module.exports = {
|
||||
context: { person: { name: "Larry", age: 45 } },
|
||||
handlebars: "{{#person}}{{name}}{{age}}{{/person}}"
|
||||
};
|
||||
@@ -0,0 +1,7 @@
|
||||
module.exports = {
|
||||
context: { person: { name: "Larry", age: 45 } },
|
||||
handlebars: "{{#with person}}{{name}}{{age}}{{/with}}",
|
||||
dust: "{#person}{name}{age}{/person}",
|
||||
eco: "<%= @person.name %><%= @person.age %>",
|
||||
mustache: "{{#person}}{{name}}{{age}}{{/person}}"
|
||||
};
|
||||
@@ -0,0 +1,10 @@
|
||||
module.exports = {
|
||||
context: { name: '1', kids: [{ name: '1.1', kids: [{name: '1.1.1', kids: []}] }] },
|
||||
partials: {
|
||||
mustache: { recursion: "{{name}}{{#kids}}{{>recursion}}{{/kids}}" },
|
||||
handlebars: { recursion: "{{name}}{{#each kids}}{{>recursion}}{{/each}}" }
|
||||
},
|
||||
handlebars: "{{name}}{{#each kids}}{{>recursion}}{{/each}}",
|
||||
dust: "{name}{#kids}{>recursion:./}{/kids}",
|
||||
mustache: "{{name}}{{#kids}}{{>recursion}}{{/kids}}"
|
||||
};
|
||||
@@ -0,0 +1,11 @@
|
||||
module.exports = {
|
||||
context: { peeps: [{name: "Moe", count: 15}, {name: "Larry", count: 5}, {name: "Curly", count: 1}] },
|
||||
partials: {
|
||||
mustache: { variables: "Hello {{name}}! You have {{count}} new messages." },
|
||||
handlebars: { variables: "Hello {{name}}! You have {{count}} new messages." }
|
||||
},
|
||||
|
||||
handlebars: "{{#each peeps}}{{>variables}}{{/each}}",
|
||||
dust: "{#peeps}{>variables/}{/peeps}",
|
||||
mustache: "{{#peeps}}{{>variables}}{{/peeps}}"
|
||||
};
|
||||
@@ -0,0 +1,7 @@
|
||||
module.exports = {
|
||||
context: { person: { name: "Larry", age: 45 } },
|
||||
handlebars: "{{person.name}}{{person.age}}{{person.foo}}{{animal.age}}",
|
||||
dust: "{person.name}{person.age}{person.foo}{animal.age}",
|
||||
eco: "<%= @person.name %><%= @person.age %><%= @person.foo %><% if @animal: %><%= @animal.age %><% end %>",
|
||||
mustache: "{{person.name}}{{person.age}}{{person.foo}}{{animal.age}}"
|
||||
};
|
||||
@@ -0,0 +1,7 @@
|
||||
module.exports = {
|
||||
context: {},
|
||||
handlebars: "Hello world",
|
||||
dust: "Hello world",
|
||||
mustache: "Hello world",
|
||||
eco: "Hello world"
|
||||
};
|
||||
@@ -0,0 +1,8 @@
|
||||
module.exports = {
|
||||
context: {name: "Mick", count: 30},
|
||||
handlebars: "Hello {{name}}! You have {{count}} new messages.",
|
||||
dust: "Hello {name}! You have {count} new messages.",
|
||||
mustache: "Hello {{name}}! You have {{count}} new messages.",
|
||||
eco: "Hello <%= @name %>! You have <%= @count %> new messages."
|
||||
};
|
||||
|
||||
@@ -0,0 +1,123 @@
|
||||
var _ = require('underscore'),
|
||||
runner = require('./util/template-runner'),
|
||||
templates = require('./templates'),
|
||||
|
||||
eco, dust, Handlebars, Mustache, eco;
|
||||
|
||||
try {
|
||||
dust = require("dustjs-linkedin");
|
||||
} catch (err) { /* NOP */ }
|
||||
|
||||
try {
|
||||
Mustache = require("mustache");
|
||||
} catch (err) { /* NOP */ }
|
||||
|
||||
try {
|
||||
eco = require("eco");
|
||||
} catch (err) { /* NOP */ }
|
||||
|
||||
function error() {
|
||||
throw new Error("EWOT");
|
||||
}
|
||||
|
||||
function makeSuite(bench, name, template, handlebarsOnly) {
|
||||
// Create aliases to minimize any impact from having to walk up the closure tree.
|
||||
var templateName = name,
|
||||
|
||||
context = template.context,
|
||||
partials = template.partials,
|
||||
|
||||
handlebarsOut,
|
||||
dustOut,
|
||||
ecoOut,
|
||||
mustacheOut;
|
||||
|
||||
var handlebar = Handlebars.compile(template.handlebars, {data: false}),
|
||||
options = {helpers: template.helpers};
|
||||
_.each(template.partials && template.partials.handlebars, function(partial, name) {
|
||||
Handlebars.registerPartial(name, Handlebars.compile(partial, {data: false}));
|
||||
});
|
||||
|
||||
handlebarsOut = handlebar(context, options);
|
||||
bench("handlebars", function() {
|
||||
handlebar(context, options);
|
||||
});
|
||||
|
||||
if (handlebarsOnly) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (dust) {
|
||||
if (template.dust) {
|
||||
dustOut = false;
|
||||
dust.loadSource(dust.compile(template.dust, templateName));
|
||||
|
||||
dust.render(templateName, context, function(err, out) { dustOut = out; });
|
||||
|
||||
bench("dust", function() {
|
||||
dust.render(templateName, context, function(err, out) { });
|
||||
});
|
||||
} else {
|
||||
bench('dust', error);
|
||||
}
|
||||
}
|
||||
|
||||
if (eco) {
|
||||
if (template.eco) {
|
||||
var ecoTemplate = eco.compile(template.eco);
|
||||
|
||||
ecoOut = ecoTemplate(context);
|
||||
|
||||
bench("eco", function() {
|
||||
ecoTemplate(context);
|
||||
});
|
||||
} else {
|
||||
bench("eco", error);
|
||||
}
|
||||
}
|
||||
|
||||
if (Mustache) {
|
||||
var mustacheSource = template.mustache,
|
||||
mustachePartials = partials && partials.mustache;
|
||||
|
||||
if (mustacheSource) {
|
||||
mustacheOut = Mustache.to_html(mustacheSource, context, mustachePartials);
|
||||
|
||||
bench("mustache", function() {
|
||||
Mustache.to_html(mustacheSource, context, mustachePartials);
|
||||
});
|
||||
} else {
|
||||
bench("mustache", error);
|
||||
}
|
||||
}
|
||||
|
||||
// Hack around whitespace until we have whitespace control
|
||||
handlebarsOut = handlebarsOut.replace(/\s/g, '');
|
||||
function compare(b, lang) {
|
||||
if (b == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
b = b.replace(/\s/g, '');
|
||||
|
||||
if (handlebarsOut !== b) {
|
||||
throw new Error('Template output mismatch: ' + name
|
||||
+ '\n\nHandlebars: ' + handlebarsOut
|
||||
+ '\n\n' + lang + ': ' + b);
|
||||
}
|
||||
}
|
||||
|
||||
compare(dustOut, 'dust');
|
||||
compare(ecoOut, 'eco');
|
||||
compare(mustacheOut, 'mustache');
|
||||
}
|
||||
|
||||
module.exports = function(grunt, callback) {
|
||||
// Deferring load incase we are being run inline with the grunt build
|
||||
Handlebars = require('../lib');
|
||||
|
||||
console.log('Execution Throughput');
|
||||
runner(grunt, makeSuite, function(times, scaled) {
|
||||
callback(scaled);
|
||||
});
|
||||
};
|
||||
@@ -1,10 +1,13 @@
|
||||
|
||||
var Benchmark = require("benchmark");
|
||||
var _ = require('underscore'),
|
||||
Benchmark = require("benchmark");
|
||||
|
||||
var BenchWarmer = function(names) {
|
||||
this.benchmarks = [];
|
||||
this.currentBenches = [];
|
||||
this.names = [];
|
||||
this.times = {};
|
||||
this.minimum = Infinity;
|
||||
this.maximum = -Infinity;
|
||||
this.errors = {};
|
||||
};
|
||||
|
||||
@@ -12,28 +15,11 @@ var print = require("sys").print;
|
||||
|
||||
BenchWarmer.prototype = {
|
||||
winners: function(benches) {
|
||||
var result = Benchmark.filter(benches, function(bench) { return bench.cycles; });
|
||||
|
||||
if (result.length > 1) {
|
||||
result.sort(function(a, b) { return b.compare(a); });
|
||||
first = result[0];
|
||||
last = result[result.length - 1];
|
||||
|
||||
var winners = [];
|
||||
|
||||
Benchmark.each(result, function(bench) {
|
||||
if (bench.compare(first) === 0) {
|
||||
winners.push(bench);
|
||||
}
|
||||
});
|
||||
|
||||
return winners;
|
||||
} else {
|
||||
return result;
|
||||
}
|
||||
return Benchmark.filter(benches, 'fastest');
|
||||
},
|
||||
suite: function(suite, fn) {
|
||||
this.suiteName = suite;
|
||||
this.times[suite] = {};
|
||||
this.first = true;
|
||||
|
||||
var self = this;
|
||||
@@ -50,9 +36,7 @@ BenchWarmer.prototype = {
|
||||
var first = this.first, suiteName = this.suiteName, self = this;
|
||||
this.first = false;
|
||||
|
||||
var bench = new Benchmark(function() {
|
||||
fn();
|
||||
}, {
|
||||
var bench = new Benchmark(fn, {
|
||||
name: this.suiteName + ": " + name,
|
||||
onComplete: function() {
|
||||
if(first) { self.startLine(suiteName); }
|
||||
@@ -62,11 +46,80 @@ BenchWarmer.prototype = {
|
||||
self.errors[this.name] = this;
|
||||
}
|
||||
});
|
||||
bench.suiteName = this.suiteName;
|
||||
bench.benchName = name;
|
||||
|
||||
this.benchmarks.push(bench);
|
||||
},
|
||||
bench: function() {
|
||||
var benchSize = 0, names = this.names, self = this, i, l;
|
||||
|
||||
bench: function(callback) {
|
||||
var self = this;
|
||||
|
||||
this.printHeader('ops/msec', true);
|
||||
|
||||
Benchmark.invoke(this.benchmarks, {
|
||||
name: "run",
|
||||
onComplete: function() {
|
||||
self.scaleTimes();
|
||||
|
||||
self.startLine('');
|
||||
|
||||
print('\n');
|
||||
self.printHeader('scaled');
|
||||
_.each(self.scaled, function(value, name) {
|
||||
self.startLine(name);
|
||||
|
||||
_.each(self.names, function(lang) {
|
||||
self.writeValue(value[lang] || '');
|
||||
});
|
||||
});
|
||||
print('\n');
|
||||
|
||||
var errors = false, prop, bench;
|
||||
for(prop in self.errors) {
|
||||
if (self.errors.hasOwnProperty(prop)
|
||||
&& self.errors[prop].error.message !== 'EWOT') {
|
||||
errors = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if(errors) {
|
||||
print("\n\nErrors:\n");
|
||||
for(prop in self.errors) {
|
||||
if (self.errors.hasOwnProperty(prop)
|
||||
&& self.errors[prop].error.message !== 'EWOT') {
|
||||
bench = self.errors[prop];
|
||||
print("\n" + bench.name + ":\n");
|
||||
print(bench.error.message);
|
||||
if(bench.error.stack) {
|
||||
print(bench.error.stack.join("\n"));
|
||||
}
|
||||
print("\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
callback();
|
||||
}
|
||||
});
|
||||
|
||||
print("\n");
|
||||
},
|
||||
|
||||
scaleTimes: function() {
|
||||
var scaled = this.scaled = {};
|
||||
_.each(this.times, function(times, name) {
|
||||
var output = scaled[name] = {};
|
||||
|
||||
_.each(times, function(time, lang) {
|
||||
output[lang] = ((time - this.minimum) / (this.maximum - this.minimum) * 100).toFixed(2);
|
||||
}, this);
|
||||
}, this);
|
||||
},
|
||||
|
||||
printHeader: function(title, winners) {
|
||||
var benchSize = 0, names = this.names, i, l;
|
||||
|
||||
for(i=0, l=names.length; i<l; i++) {
|
||||
var name = names[i];
|
||||
@@ -78,43 +131,21 @@ BenchWarmer.prototype = {
|
||||
this.benchSize = 20;
|
||||
var horSize = 0;
|
||||
|
||||
this.startLine("ops/msec");
|
||||
horSize = horSize + "ops/msec ".length;
|
||||
this.startLine(title);
|
||||
horSize = horSize + this.benchSize;
|
||||
for(i=0, l=names.length; i<l; i++) {
|
||||
print(names[i] + new Array(this.benchSize - names[i].length + 1).join(" "));
|
||||
this.writeValue(names[i]);
|
||||
horSize = horSize + this.benchSize;
|
||||
}
|
||||
|
||||
print("WINNER(S)");
|
||||
horSize = horSize + "WINNER(S)".length;
|
||||
if (winners) {
|
||||
print("WINNER(S)");
|
||||
horSize = horSize + "WINNER(S)".length;
|
||||
}
|
||||
|
||||
print("\n" + new Array(horSize + 1).join("-"));
|
||||
|
||||
Benchmark.invoke(this.benchmarks, {
|
||||
name: "run",
|
||||
onComplete: function() {
|
||||
var errors = false, prop, bench;
|
||||
for(prop in self.errors) { if(self.errors.hasOwnProperty(prop)) { errors = true; break; } }
|
||||
|
||||
if(errors) {
|
||||
print("\n\nErrors:\n");
|
||||
for(prop in self.errors) {
|
||||
if(self.errors.hasOwnProperty(prop)) {
|
||||
bench = self.errors[prop];
|
||||
print("\n" + bench.name + ":\n");
|
||||
print(bench.error.message);
|
||||
if(bench.error.stack) {
|
||||
print(bench.error.stack.join("\n"));
|
||||
}
|
||||
print("\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
print("\n");
|
||||
},
|
||||
|
||||
startLine: function(name) {
|
||||
var winners = Benchmark.map(this.winners(this.currentBenches), function(bench) {
|
||||
return bench.name.split(": ")[1];
|
||||
@@ -124,22 +155,41 @@ BenchWarmer.prototype = {
|
||||
|
||||
print(winners.join(", "));
|
||||
print("\n");
|
||||
var padding = this.nameSize - name.length + 1;
|
||||
name = name + new Array(padding).join(" ");
|
||||
print(name);
|
||||
|
||||
if (name) {
|
||||
this.writeValue(name);
|
||||
}
|
||||
},
|
||||
writeBench: function(bench) {
|
||||
var out;
|
||||
|
||||
if(!bench.error) {
|
||||
var count = bench.hz,
|
||||
moe = count * bench.stats.rme / 100;
|
||||
moe = count * bench.stats.rme / 100,
|
||||
minimum,
|
||||
maximum;
|
||||
|
||||
out = Math.round(count / 1000) + " ±" + Math.round(moe / 1000) + " (" + bench.cycles + ")";
|
||||
count = Math.round(count / 1000);
|
||||
moe = Math.round(moe / 1000);
|
||||
minimum = count - moe;
|
||||
maximum = count + moe;
|
||||
|
||||
out = count + " ±" + moe + " (" + bench.cycles + ")";
|
||||
|
||||
this.times[bench.suiteName][bench.benchName] = count;
|
||||
this.minimum = Math.min(this.minimum, minimum);
|
||||
this.maximum = Math.max(this.maximum, maximum);
|
||||
} else {
|
||||
out = "E";
|
||||
if (bench.error.message === 'EWOT') {
|
||||
out = 'NA';
|
||||
} else {
|
||||
out = 'E';
|
||||
}
|
||||
}
|
||||
this.writeValue(out);
|
||||
},
|
||||
|
||||
writeValue: function(out) {
|
||||
var padding = this.benchSize - out.length + 1;
|
||||
out = out + new Array(padding).join(" ");
|
||||
print(out);
|
||||
@@ -0,0 +1,27 @@
|
||||
var _ = require('underscore'),
|
||||
BenchWarmer = require('./benchwarmer'),
|
||||
templates = require('../templates');
|
||||
|
||||
module.exports = function(grunt, makeSuite, callback) {
|
||||
var warmer = new BenchWarmer();
|
||||
|
||||
var handlebarsOnly = grunt.option('handlebars-only'),
|
||||
grep = grunt.option('grep');
|
||||
if (grep) {
|
||||
grep = new RegExp(grep);
|
||||
}
|
||||
|
||||
_.each(templates, function(template, name) {
|
||||
if (!template.handlebars || (grep && !grep.test(name))) {
|
||||
return;
|
||||
}
|
||||
|
||||
warmer.suite(name, function(bench) {
|
||||
makeSuite(bench, name, template, handlebarsOnly);
|
||||
});
|
||||
});
|
||||
|
||||
warmer.bench(function() {
|
||||
callback && callback(warmer.times, warmer.scaled);
|
||||
});
|
||||
};
|
||||
+12
-3
@@ -70,6 +70,11 @@ var optimist = require('optimist')
|
||||
'description': 'Template extension.',
|
||||
'alias': 'extension',
|
||||
'default': 'handlebars'
|
||||
},
|
||||
'b': {
|
||||
'type': 'boolean',
|
||||
'description': 'Removes the BOM (Byte Order Mark) from the beginning of the templates.',
|
||||
'alias': 'bom'
|
||||
}
|
||||
})
|
||||
|
||||
@@ -97,7 +102,7 @@ var optimist = require('optimist')
|
||||
});
|
||||
|
||||
var fs = require('fs'),
|
||||
handlebars = require('../lib/handlebars'),
|
||||
handlebars = require('../lib'),
|
||||
basename = require('path').basename,
|
||||
uglify = require('uglify-js');
|
||||
|
||||
@@ -122,7 +127,7 @@ extension = new RegExp('\\.' + extension + '$');
|
||||
var output = [];
|
||||
if (!argv.simple) {
|
||||
if (argv.amd) {
|
||||
output.push('define([\'' + argv.handlebarPath + 'handlebars\'], function(Handlebars) {\n');
|
||||
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 {
|
||||
@@ -145,9 +150,13 @@ 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);
|
||||
}
|
||||
|
||||
var options = {
|
||||
knownHelpers: known,
|
||||
knownHelpersOnly: argv.o
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
{
|
||||
"name": "handlebars.js",
|
||||
"version": "1.0.0",
|
||||
"main": "dist/handlebars.js",
|
||||
"ignore": [
|
||||
"node_modules",
|
||||
"components"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"name": "handlebars",
|
||||
"version": "1.2.0",
|
||||
"main": "handlebars.js",
|
||||
"dependencies": {}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"name": "handlebars",
|
||||
"repo": "components/handlebars.js",
|
||||
"version": "1.0.0",
|
||||
"main": "handlebars.js",
|
||||
"scripts": [
|
||||
"handlebars.js"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
{
|
||||
"name": "components/handlebars.js",
|
||||
"description": "Handlebars.js and Mustache are both logicless templating languages that keep the view and the code separated like we all know they should be.",
|
||||
"homepage": "http://handlebarsjs.com",
|
||||
"license": "MIT",
|
||||
"type": "component",
|
||||
"keywords": [
|
||||
"handlebars",
|
||||
"mustache",
|
||||
"html"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Chris Wanstrath",
|
||||
"homepage": "http://chriswanstrath.com"
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
"robloach/component-installer": "*"
|
||||
},
|
||||
"extra": {
|
||||
"component": {
|
||||
"name": "handlebars",
|
||||
"scripts": [
|
||||
"handlebars.js"
|
||||
],
|
||||
"files": [
|
||||
"handlebars.runtime.js"
|
||||
],
|
||||
"shim": {
|
||||
"exports": "Handlebars"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
# -*- encoding: utf-8 -*-
|
||||
require 'json'
|
||||
|
||||
package = JSON.parse(File.read('package.json'))
|
||||
package = JSON.parse(File.read('bower.json'))
|
||||
|
||||
Gem::Specification.new do |gem|
|
||||
gem.name = "handlebars-source"
|
||||
@@ -14,8 +14,8 @@ Gem::Specification.new do |gem|
|
||||
gem.version = package["version"]
|
||||
|
||||
gem.files = [
|
||||
'dist/handlebars.js',
|
||||
'dist/handlebars.runtime.js',
|
||||
'handlebars.js',
|
||||
'handlebars.runtime.js',
|
||||
'lib/handlebars/source.rb'
|
||||
]
|
||||
end
|
||||
@@ -2,7 +2,7 @@
|
||||
<package>
|
||||
<metadata>
|
||||
<id>handlebars.js</id>
|
||||
<version>1.0.0</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>
|
||||
@@ -12,6 +12,6 @@
|
||||
<tags>handlebars mustache template html</tags>
|
||||
</metadata>
|
||||
<files>
|
||||
<file src="dist\handlebars.js" target="Content\Scripts" />
|
||||
<file src="handlebars.js" target="Content\Scripts" />
|
||||
</files>
|
||||
</package>
|
||||
@@ -0,0 +1,11 @@
|
||||
module Handlebars
|
||||
module Source
|
||||
def self.bundled_path
|
||||
File.expand_path("../../../handlebars.js", __FILE__)
|
||||
end
|
||||
|
||||
def self.runtime_bundled_path
|
||||
File.expand_path("../../../handlebars.runtime.js", __FILE__)
|
||||
end
|
||||
end
|
||||
end
|
||||
Vendored
-2278
File diff suppressed because it is too large
Load Diff
Vendored
-362
@@ -1,362 +0,0 @@
|
||||
/*
|
||||
|
||||
Copyright (C) 2011 by Yehuda Katz
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
|
||||
*/
|
||||
|
||||
// lib/handlebars/browser-prefix.js
|
||||
var Handlebars = {};
|
||||
|
||||
(function(Handlebars, undefined) {
|
||||
;
|
||||
// lib/handlebars/base.js
|
||||
|
||||
Handlebars.VERSION = "1.0.0";
|
||||
Handlebars.COMPILER_REVISION = 4;
|
||||
|
||||
Handlebars.REVISION_CHANGES = {
|
||||
1: '<= 1.0.rc.2', // 1.0.rc.2 is actually rev2 but doesn't report it
|
||||
2: '== 1.0.0-rc.3',
|
||||
3: '== 1.0.0-rc.4',
|
||||
4: '>= 1.0.0'
|
||||
};
|
||||
|
||||
Handlebars.helpers = {};
|
||||
Handlebars.partials = {};
|
||||
|
||||
var toString = Object.prototype.toString,
|
||||
functionType = '[object Function]',
|
||||
objectType = '[object Object]';
|
||||
|
||||
Handlebars.registerHelper = function(name, fn, inverse) {
|
||||
if (toString.call(name) === objectType) {
|
||||
if (inverse || fn) { throw new Handlebars.Exception('Arg not supported with multiple helpers'); }
|
||||
Handlebars.Utils.extend(this.helpers, name);
|
||||
} else {
|
||||
if (inverse) { fn.not = inverse; }
|
||||
this.helpers[name] = fn;
|
||||
}
|
||||
};
|
||||
|
||||
Handlebars.registerPartial = function(name, str) {
|
||||
if (toString.call(name) === objectType) {
|
||||
Handlebars.Utils.extend(this.partials, name);
|
||||
} else {
|
||||
this.partials[name] = str;
|
||||
}
|
||||
};
|
||||
|
||||
Handlebars.registerHelper('helperMissing', function(arg) {
|
||||
if(arguments.length === 2) {
|
||||
return undefined;
|
||||
} else {
|
||||
throw new Error("Missing helper: '" + arg + "'");
|
||||
}
|
||||
});
|
||||
|
||||
Handlebars.registerHelper('blockHelperMissing', function(context, options) {
|
||||
var inverse = options.inverse || function() {}, fn = options.fn;
|
||||
|
||||
var type = toString.call(context);
|
||||
|
||||
if(type === functionType) { context = context.call(this); }
|
||||
|
||||
if(context === true) {
|
||||
return fn(this);
|
||||
} else if(context === false || context == null) {
|
||||
return inverse(this);
|
||||
} else if(type === "[object Array]") {
|
||||
if(context.length > 0) {
|
||||
return Handlebars.helpers.each(context, options);
|
||||
} else {
|
||||
return inverse(this);
|
||||
}
|
||||
} else {
|
||||
return fn(context);
|
||||
}
|
||||
});
|
||||
|
||||
Handlebars.K = function() {};
|
||||
|
||||
Handlebars.createFrame = Object.create || function(object) {
|
||||
Handlebars.K.prototype = object;
|
||||
var obj = new Handlebars.K();
|
||||
Handlebars.K.prototype = null;
|
||||
return obj;
|
||||
};
|
||||
|
||||
Handlebars.logger = {
|
||||
DEBUG: 0, INFO: 1, WARN: 2, ERROR: 3, level: 3,
|
||||
|
||||
methodMap: {0: 'debug', 1: 'info', 2: 'warn', 3: 'error'},
|
||||
|
||||
// can be overridden in the host environment
|
||||
log: function(level, obj) {
|
||||
if (Handlebars.logger.level <= level) {
|
||||
var method = Handlebars.logger.methodMap[level];
|
||||
if (typeof console !== 'undefined' && console[method]) {
|
||||
console[method].call(console, obj);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Handlebars.log = function(level, obj) { Handlebars.logger.log(level, obj); };
|
||||
|
||||
Handlebars.registerHelper('each', function(context, options) {
|
||||
var fn = options.fn, inverse = options.inverse;
|
||||
var i = 0, ret = "", data;
|
||||
|
||||
var type = toString.call(context);
|
||||
if(type === functionType) { context = context.call(this); }
|
||||
|
||||
if (options.data) {
|
||||
data = Handlebars.createFrame(options.data);
|
||||
}
|
||||
|
||||
if(context && typeof context === 'object') {
|
||||
if(context instanceof Array){
|
||||
for(var j = context.length; i<j; i++) {
|
||||
if (data) { data.index = i; }
|
||||
ret = ret + fn(context[i], { data: data });
|
||||
}
|
||||
} else {
|
||||
for(var key in context) {
|
||||
if(context.hasOwnProperty(key)) {
|
||||
if(data) { data.key = key; }
|
||||
ret = ret + fn(context[key], {data: data});
|
||||
i++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(i === 0){
|
||||
ret = inverse(this);
|
||||
}
|
||||
|
||||
return ret;
|
||||
});
|
||||
|
||||
Handlebars.registerHelper('if', function(conditional, options) {
|
||||
var type = toString.call(conditional);
|
||||
if(type === functionType) { conditional = conditional.call(this); }
|
||||
|
||||
if(!conditional || Handlebars.Utils.isEmpty(conditional)) {
|
||||
return options.inverse(this);
|
||||
} else {
|
||||
return options.fn(this);
|
||||
}
|
||||
});
|
||||
|
||||
Handlebars.registerHelper('unless', function(conditional, options) {
|
||||
return Handlebars.helpers['if'].call(this, conditional, {fn: options.inverse, inverse: options.fn});
|
||||
});
|
||||
|
||||
Handlebars.registerHelper('with', function(context, options) {
|
||||
var type = toString.call(context);
|
||||
if(type === functionType) { context = context.call(this); }
|
||||
|
||||
if (!Handlebars.Utils.isEmpty(context)) return options.fn(context);
|
||||
});
|
||||
|
||||
Handlebars.registerHelper('log', function(context, options) {
|
||||
var level = options.data && options.data.level != null ? parseInt(options.data.level, 10) : 1;
|
||||
Handlebars.log(level, context);
|
||||
});
|
||||
;
|
||||
// lib/handlebars/utils.js
|
||||
|
||||
var errorProps = ['description', 'fileName', 'lineNumber', 'message', 'name', 'number', 'stack'];
|
||||
|
||||
Handlebars.Exception = function(message) {
|
||||
var tmp = Error.prototype.constructor.apply(this, arguments);
|
||||
|
||||
// Unfortunately errors are not enumerable in Chrome (at least), so `for prop in tmp` doesn't work.
|
||||
for (var idx = 0; idx < errorProps.length; idx++) {
|
||||
this[errorProps[idx]] = tmp[errorProps[idx]];
|
||||
}
|
||||
};
|
||||
Handlebars.Exception.prototype = new Error();
|
||||
|
||||
// Build out our basic SafeString type
|
||||
Handlebars.SafeString = function(string) {
|
||||
this.string = string;
|
||||
};
|
||||
Handlebars.SafeString.prototype.toString = function() {
|
||||
return this.string.toString();
|
||||
};
|
||||
|
||||
var escape = {
|
||||
"&": "&",
|
||||
"<": "<",
|
||||
">": ">",
|
||||
'"': """,
|
||||
"'": "'",
|
||||
"`": "`"
|
||||
};
|
||||
|
||||
var badChars = /[&<>"'`]/g;
|
||||
var possible = /[&<>"'`]/;
|
||||
|
||||
var escapeChar = function(chr) {
|
||||
return escape[chr] || "&";
|
||||
};
|
||||
|
||||
Handlebars.Utils = {
|
||||
extend: function(obj, value) {
|
||||
for(var key in value) {
|
||||
if(value.hasOwnProperty(key)) {
|
||||
obj[key] = value[key];
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
escapeExpression: function(string) {
|
||||
// don't escape SafeStrings, since they're already safe
|
||||
if (string instanceof Handlebars.SafeString) {
|
||||
return string.toString();
|
||||
} else if (string == null || string === false) {
|
||||
return "";
|
||||
}
|
||||
|
||||
// Force a string conversion as this will be done by the append regardless and
|
||||
// the regex test will do this transparently behind the scenes, causing issues if
|
||||
// an object's to string has escaped characters in it.
|
||||
string = string.toString();
|
||||
|
||||
if(!possible.test(string)) { return string; }
|
||||
return string.replace(badChars, escapeChar);
|
||||
},
|
||||
|
||||
isEmpty: function(value) {
|
||||
if (!value && value !== 0) {
|
||||
return true;
|
||||
} else if(toString.call(value) === "[object Array]" && value.length === 0) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
};
|
||||
;
|
||||
// lib/handlebars/runtime.js
|
||||
|
||||
Handlebars.VM = {
|
||||
template: function(templateSpec) {
|
||||
// Just add water
|
||||
var container = {
|
||||
escapeExpression: Handlebars.Utils.escapeExpression,
|
||||
invokePartial: Handlebars.VM.invokePartial,
|
||||
programs: [],
|
||||
program: function(i, fn, data) {
|
||||
var programWrapper = this.programs[i];
|
||||
if(data) {
|
||||
programWrapper = Handlebars.VM.program(i, fn, data);
|
||||
} else if (!programWrapper) {
|
||||
programWrapper = this.programs[i] = Handlebars.VM.program(i, fn);
|
||||
}
|
||||
return programWrapper;
|
||||
},
|
||||
merge: function(param, common) {
|
||||
var ret = param || common;
|
||||
|
||||
if (param && common) {
|
||||
ret = {};
|
||||
Handlebars.Utils.extend(ret, common);
|
||||
Handlebars.Utils.extend(ret, param);
|
||||
}
|
||||
return ret;
|
||||
},
|
||||
programWithDepth: Handlebars.VM.programWithDepth,
|
||||
noop: Handlebars.VM.noop,
|
||||
compilerInfo: null
|
||||
};
|
||||
|
||||
return function(context, options) {
|
||||
options = options || {};
|
||||
var result = templateSpec.call(container, Handlebars, context, options.helpers, options.partials, options.data);
|
||||
|
||||
var compilerInfo = container.compilerInfo || [],
|
||||
compilerRevision = compilerInfo[0] || 1,
|
||||
currentRevision = Handlebars.COMPILER_REVISION;
|
||||
|
||||
if (compilerRevision !== currentRevision) {
|
||||
if (compilerRevision < currentRevision) {
|
||||
var runtimeVersions = Handlebars.REVISION_CHANGES[currentRevision],
|
||||
compilerVersions = Handlebars.REVISION_CHANGES[compilerRevision];
|
||||
throw "Template was precompiled with an older version of Handlebars than the current runtime. "+
|
||||
"Please update your precompiler to a newer version ("+runtimeVersions+") or downgrade your runtime to an older version ("+compilerVersions+").";
|
||||
} else {
|
||||
// Use the embedded version info since the runtime doesn't know about this revision yet
|
||||
throw "Template was precompiled with a newer version of Handlebars than the current runtime. "+
|
||||
"Please update your runtime to a newer version ("+compilerInfo[1]+").";
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
||||
},
|
||||
|
||||
programWithDepth: function(i, fn, data /*, $depth */) {
|
||||
var args = Array.prototype.slice.call(arguments, 3);
|
||||
|
||||
var program = function(context, options) {
|
||||
options = options || {};
|
||||
|
||||
return fn.apply(this, [context, options.data || data].concat(args));
|
||||
};
|
||||
program.program = i;
|
||||
program.depth = args.length;
|
||||
return program;
|
||||
},
|
||||
program: function(i, fn, data) {
|
||||
var program = function(context, options) {
|
||||
options = options || {};
|
||||
|
||||
return fn(context, options.data || data);
|
||||
};
|
||||
program.program = i;
|
||||
program.depth = 0;
|
||||
return program;
|
||||
},
|
||||
noop: function() { return ""; },
|
||||
invokePartial: function(partial, name, context, helpers, partials, data) {
|
||||
var options = { helpers: helpers, partials: partials, data: data };
|
||||
|
||||
if(partial === undefined) {
|
||||
throw new Handlebars.Exception("The partial " + name + " could not be found");
|
||||
} else if(partial instanceof Function) {
|
||||
return partial(context, options);
|
||||
} else if (!Handlebars.compile) {
|
||||
throw new Handlebars.Exception("The partial " + name + " could not be compiled when running in runtime-only mode");
|
||||
} else {
|
||||
partials[name] = Handlebars.compile(partial, {data: data !== undefined});
|
||||
return partials[name](context, options);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Handlebars.template = Handlebars.VM.template;
|
||||
;
|
||||
// lib/handlebars/browser-suffix.js
|
||||
})(Handlebars);
|
||||
;
|
||||
+21
-33
@@ -1,43 +1,31 @@
|
||||
var handlebars = require("./handlebars/base"),
|
||||
/*globals Handlebars: true */
|
||||
import Handlebars from "./handlebars.runtime";
|
||||
|
||||
// Each of these augment the Handlebars object. No need to setup here.
|
||||
// (This is done to easily share code between commonjs and browse envs)
|
||||
utils = require("./handlebars/utils"),
|
||||
compiler = require("./handlebars/compiler"),
|
||||
runtime = require("./handlebars/runtime");
|
||||
// Compiler imports
|
||||
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";
|
||||
|
||||
var _create = Handlebars.create;
|
||||
var create = function() {
|
||||
var hb = handlebars.create();
|
||||
var hb = _create();
|
||||
|
||||
utils.attach(hb);
|
||||
compiler.attach(hb);
|
||||
runtime.attach(hb);
|
||||
hb.compile = function(input, options) {
|
||||
return compile(input, options, hb);
|
||||
};
|
||||
hb.precompile = precompile;
|
||||
|
||||
hb.AST = AST;
|
||||
hb.Compiler = Compiler;
|
||||
hb.JavaScriptCompiler = JavaScriptCompiler;
|
||||
hb.Parser = Parser;
|
||||
hb.parse = parse;
|
||||
|
||||
return hb;
|
||||
};
|
||||
|
||||
var Handlebars = create();
|
||||
Handlebars = create();
|
||||
Handlebars.create = create;
|
||||
|
||||
module.exports = Handlebars; // instantiate an instance
|
||||
|
||||
// Publish a Node.js require() handler for .handlebars and .hbs files
|
||||
if (require.extensions) {
|
||||
var extension = function(module, filename) {
|
||||
var fs = require("fs");
|
||||
var templateString = fs.readFileSync(filename, "utf8");
|
||||
module.exports = Handlebars.compile(templateString);
|
||||
};
|
||||
require.extensions[".handlebars"] = extension;
|
||||
require.extensions[".hbs"] = extension;
|
||||
}
|
||||
|
||||
// BEGIN(BROWSER)
|
||||
|
||||
// END(BROWSER)
|
||||
|
||||
// USAGE:
|
||||
// var handlebars = require('handlebars');
|
||||
|
||||
// var singleton = handlebars.Handlebars,
|
||||
// local = handlebars.create();
|
||||
export default Handlebars;
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
/*globals Handlebars: true */
|
||||
module base from "./handlebars/base";
|
||||
|
||||
// Each of these augment the Handlebars object. No need to setup here.
|
||||
// (This is done to easily share code between commonjs and browse envs)
|
||||
import SafeString from "./handlebars/safe-string";
|
||||
import Exception from "./handlebars/exception";
|
||||
module Utils from "./handlebars/utils";
|
||||
module runtime from "./handlebars/runtime";
|
||||
|
||||
// For compatibility and usage outside of module systems, make the Handlebars object a namespace
|
||||
var create = function() {
|
||||
var hb = new base.HandlebarsEnvironment();
|
||||
|
||||
Utils.extend(hb, base);
|
||||
hb.SafeString = SafeString;
|
||||
hb.Exception = Exception;
|
||||
hb.Utils = Utils;
|
||||
|
||||
hb.VM = runtime;
|
||||
hb.template = function(spec) {
|
||||
return runtime.template(spec, hb);
|
||||
};
|
||||
|
||||
return hb;
|
||||
};
|
||||
|
||||
var Handlebars = create();
|
||||
Handlebars.create = create;
|
||||
|
||||
export default Handlebars;
|
||||
+146
-134
@@ -1,94 +1,167 @@
|
||||
/*jshint eqnull: true */
|
||||
module Utils from "./utils";
|
||||
import Exception from "./exception";
|
||||
|
||||
module.exports.create = function() {
|
||||
export var VERSION = "1.2.0";
|
||||
export var COMPILER_REVISION = 4;
|
||||
|
||||
var Handlebars = {};
|
||||
|
||||
// BEGIN(BROWSER)
|
||||
|
||||
Handlebars.VERSION = "1.0.0";
|
||||
Handlebars.COMPILER_REVISION = 4;
|
||||
|
||||
Handlebars.REVISION_CHANGES = {
|
||||
export var REVISION_CHANGES = {
|
||||
1: '<= 1.0.rc.2', // 1.0.rc.2 is actually rev2 but doesn't report it
|
||||
2: '== 1.0.0-rc.3',
|
||||
3: '== 1.0.0-rc.4',
|
||||
4: '>= 1.0.0'
|
||||
};
|
||||
|
||||
Handlebars.helpers = {};
|
||||
Handlebars.partials = {};
|
||||
|
||||
var toString = Object.prototype.toString,
|
||||
functionType = '[object Function]',
|
||||
var isArray = Utils.isArray,
|
||||
isFunction = Utils.isFunction,
|
||||
toString = Utils.toString,
|
||||
objectType = '[object Object]';
|
||||
|
||||
Handlebars.registerHelper = function(name, fn, inverse) {
|
||||
if (toString.call(name) === objectType) {
|
||||
if (inverse || fn) { throw new Handlebars.Exception('Arg not supported with multiple helpers'); }
|
||||
Handlebars.Utils.extend(this.helpers, name);
|
||||
} else {
|
||||
if (inverse) { fn.not = inverse; }
|
||||
this.helpers[name] = fn;
|
||||
}
|
||||
};
|
||||
export function HandlebarsEnvironment(helpers, partials) {
|
||||
this.helpers = helpers || {};
|
||||
this.partials = partials || {};
|
||||
|
||||
Handlebars.registerPartial = function(name, str) {
|
||||
if (toString.call(name) === objectType) {
|
||||
Handlebars.Utils.extend(this.partials, name);
|
||||
} else {
|
||||
this.partials[name] = str;
|
||||
}
|
||||
};
|
||||
registerDefaultHelpers(this);
|
||||
}
|
||||
|
||||
Handlebars.registerHelper('helperMissing', function(arg) {
|
||||
if(arguments.length === 2) {
|
||||
return undefined;
|
||||
} else {
|
||||
throw new Error("Missing helper: '" + arg + "'");
|
||||
}
|
||||
});
|
||||
HandlebarsEnvironment.prototype = {
|
||||
constructor: HandlebarsEnvironment,
|
||||
|
||||
Handlebars.registerHelper('blockHelperMissing', function(context, options) {
|
||||
var inverse = options.inverse || function() {}, fn = options.fn;
|
||||
logger: logger,
|
||||
log: log,
|
||||
|
||||
var type = toString.call(context);
|
||||
|
||||
if(type === functionType) { context = context.call(this); }
|
||||
|
||||
if(context === true) {
|
||||
return fn(this);
|
||||
} else if(context === false || context == null) {
|
||||
return inverse(this);
|
||||
} else if(type === "[object Array]") {
|
||||
if(context.length > 0) {
|
||||
return Handlebars.helpers.each(context, options);
|
||||
registerHelper: function(name, fn, inverse) {
|
||||
if (toString.call(name) === objectType) {
|
||||
if (inverse || fn) { throw new Exception('Arg not supported with multiple helpers'); }
|
||||
Utils.extend(this.helpers, name);
|
||||
} else {
|
||||
return inverse(this);
|
||||
if (inverse) { fn.not = inverse; }
|
||||
this.helpers[name] = fn;
|
||||
}
|
||||
},
|
||||
|
||||
registerPartial: function(name, str) {
|
||||
if (toString.call(name) === objectType) {
|
||||
Utils.extend(this.partials, name);
|
||||
} else {
|
||||
this.partials[name] = str;
|
||||
}
|
||||
} else {
|
||||
return fn(context);
|
||||
}
|
||||
});
|
||||
|
||||
Handlebars.K = function() {};
|
||||
|
||||
Handlebars.createFrame = Object.create || function(object) {
|
||||
Handlebars.K.prototype = object;
|
||||
var obj = new Handlebars.K();
|
||||
Handlebars.K.prototype = null;
|
||||
return obj;
|
||||
};
|
||||
|
||||
Handlebars.logger = {
|
||||
DEBUG: 0, INFO: 1, WARN: 2, ERROR: 3, level: 3,
|
||||
function registerDefaultHelpers(instance) {
|
||||
instance.registerHelper('helperMissing', function(arg) {
|
||||
if(arguments.length === 2) {
|
||||
return undefined;
|
||||
} else {
|
||||
throw new Error("Missing helper: '" + arg + "'");
|
||||
}
|
||||
});
|
||||
|
||||
methodMap: {0: 'debug', 1: 'info', 2: 'warn', 3: 'error'},
|
||||
instance.registerHelper('blockHelperMissing', function(context, options) {
|
||||
var inverse = options.inverse || function() {}, fn = options.fn;
|
||||
|
||||
if (isFunction(context)) { context = context.call(this); }
|
||||
|
||||
if(context === true) {
|
||||
return fn(this);
|
||||
} else if(context === false || context == null) {
|
||||
return inverse(this);
|
||||
} else if (isArray(context)) {
|
||||
if(context.length > 0) {
|
||||
return instance.helpers.each(context, options);
|
||||
} else {
|
||||
return inverse(this);
|
||||
}
|
||||
} else {
|
||||
return fn(context);
|
||||
}
|
||||
});
|
||||
|
||||
instance.registerHelper('each', function(context, options) {
|
||||
var fn = options.fn, inverse = options.inverse;
|
||||
var i = 0, ret = "", data;
|
||||
|
||||
if (isFunction(context)) { context = context.call(this); }
|
||||
|
||||
if (options.data) {
|
||||
data = createFrame(options.data);
|
||||
}
|
||||
|
||||
if(context && typeof context === 'object') {
|
||||
if (isArray(context)) {
|
||||
for(var j = context.length; i<j; i++) {
|
||||
if (data) {
|
||||
data.index = i;
|
||||
data.first = (i === 0);
|
||||
data.last = (i === (context.length-1));
|
||||
}
|
||||
ret = ret + fn(context[i], { data: data });
|
||||
}
|
||||
} else {
|
||||
for(var key in context) {
|
||||
if(context.hasOwnProperty(key)) {
|
||||
if(data) {
|
||||
data.key = key;
|
||||
data.index = i;
|
||||
data.first = (i === 0);
|
||||
}
|
||||
ret = ret + fn(context[key], {data: data});
|
||||
i++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(i === 0){
|
||||
ret = inverse(this);
|
||||
}
|
||||
|
||||
return ret;
|
||||
});
|
||||
|
||||
instance.registerHelper('if', function(conditional, options) {
|
||||
if (isFunction(conditional)) { conditional = conditional.call(this); }
|
||||
|
||||
// Default behavior is to render the positive path if the value is truthy and not empty.
|
||||
// The `includeZero` option may be set to treat the condtional as purely not empty based on the
|
||||
// behavior of isEmpty. Effectively this determines if 0 is handled by the positive path or negative.
|
||||
if ((!options.hash.includeZero && !conditional) || Utils.isEmpty(conditional)) {
|
||||
return options.inverse(this);
|
||||
} else {
|
||||
return options.fn(this);
|
||||
}
|
||||
});
|
||||
|
||||
instance.registerHelper('unless', function(conditional, options) {
|
||||
return instance.helpers['if'].call(this, conditional, {fn: options.inverse, inverse: options.fn, hash: options.hash});
|
||||
});
|
||||
|
||||
instance.registerHelper('with', function(context, options) {
|
||||
if (isFunction(context)) { context = context.call(this); }
|
||||
|
||||
if (!Utils.isEmpty(context)) return options.fn(context);
|
||||
});
|
||||
|
||||
instance.registerHelper('log', function(context, options) {
|
||||
var level = options.data && options.data.level != null ? parseInt(options.data.level, 10) : 1;
|
||||
instance.log(level, context);
|
||||
});
|
||||
}
|
||||
|
||||
export var logger = {
|
||||
methodMap: { 0: 'debug', 1: 'info', 2: 'warn', 3: 'error' },
|
||||
|
||||
// State enum
|
||||
DEBUG: 0,
|
||||
INFO: 1,
|
||||
WARN: 2,
|
||||
ERROR: 3,
|
||||
level: 3,
|
||||
|
||||
// can be overridden in the host environment
|
||||
log: function(level, obj) {
|
||||
if (Handlebars.logger.level <= level) {
|
||||
var method = Handlebars.logger.methodMap[level];
|
||||
if (logger.level <= level) {
|
||||
var method = logger.methodMap[level];
|
||||
if (typeof console !== 'undefined' && console[method]) {
|
||||
console[method].call(console, obj);
|
||||
}
|
||||
@@ -96,71 +169,10 @@ Handlebars.logger = {
|
||||
}
|
||||
};
|
||||
|
||||
Handlebars.log = function(level, obj) { Handlebars.logger.log(level, obj); };
|
||||
export function log(level, obj) { logger.log(level, obj); }
|
||||
|
||||
Handlebars.registerHelper('each', function(context, options) {
|
||||
var fn = options.fn, inverse = options.inverse;
|
||||
var i = 0, ret = "", data;
|
||||
|
||||
var type = toString.call(context);
|
||||
if(type === functionType) { context = context.call(this); }
|
||||
|
||||
if (options.data) {
|
||||
data = Handlebars.createFrame(options.data);
|
||||
}
|
||||
|
||||
if(context && typeof context === 'object') {
|
||||
if(context instanceof Array){
|
||||
for(var j = context.length; i<j; i++) {
|
||||
if (data) { data.index = i; }
|
||||
ret = ret + fn(context[i], { data: data });
|
||||
}
|
||||
} else {
|
||||
for(var key in context) {
|
||||
if(context.hasOwnProperty(key)) {
|
||||
if(data) { data.key = key; }
|
||||
ret = ret + fn(context[key], {data: data});
|
||||
i++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(i === 0){
|
||||
ret = inverse(this);
|
||||
}
|
||||
|
||||
return ret;
|
||||
});
|
||||
|
||||
Handlebars.registerHelper('if', function(conditional, options) {
|
||||
var type = toString.call(conditional);
|
||||
if(type === functionType) { conditional = conditional.call(this); }
|
||||
|
||||
if(!conditional || Handlebars.Utils.isEmpty(conditional)) {
|
||||
return options.inverse(this);
|
||||
} else {
|
||||
return options.fn(this);
|
||||
}
|
||||
});
|
||||
|
||||
Handlebars.registerHelper('unless', function(conditional, options) {
|
||||
return Handlebars.helpers['if'].call(this, conditional, {fn: options.inverse, inverse: options.fn});
|
||||
});
|
||||
|
||||
Handlebars.registerHelper('with', function(context, options) {
|
||||
var type = toString.call(context);
|
||||
if(type === functionType) { context = context.call(this); }
|
||||
|
||||
if (!Handlebars.Utils.isEmpty(context)) return options.fn(context);
|
||||
});
|
||||
|
||||
Handlebars.registerHelper('log', function(context, options) {
|
||||
var level = options.data && options.data.level != null ? parseInt(options.data.level, 10) : 1;
|
||||
Handlebars.log(level, context);
|
||||
});
|
||||
|
||||
// END(BROWSER)
|
||||
|
||||
return Handlebars;
|
||||
export var createFrame = function(object) {
|
||||
var obj = {};
|
||||
Utils.extend(obj, object);
|
||||
return obj;
|
||||
};
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
var Handlebars = {};
|
||||
|
||||
(function(Handlebars, undefined) {
|
||||
@@ -1 +0,0 @@
|
||||
})(Handlebars);
|
||||
+149
-130
@@ -1,138 +1,157 @@
|
||||
exports.attach = function(Handlebars) {
|
||||
import Exception from "../exception";
|
||||
|
||||
// BEGIN(BROWSER)
|
||||
Handlebars.AST = {};
|
||||
var AST = {
|
||||
ProgramNode: function(statements, inverseStrip, inverse) {
|
||||
this.type = "program";
|
||||
this.statements = statements;
|
||||
this.strip = {};
|
||||
|
||||
Handlebars.AST.ProgramNode = function(statements, inverse) {
|
||||
this.type = "program";
|
||||
this.statements = statements;
|
||||
if(inverse) { this.inverse = new Handlebars.AST.ProgramNode(inverse); }
|
||||
};
|
||||
|
||||
Handlebars.AST.MustacheNode = function(rawParams, hash, unescaped) {
|
||||
this.type = "mustache";
|
||||
this.escaped = !unescaped;
|
||||
this.hash = hash;
|
||||
|
||||
var id = this.id = rawParams[0];
|
||||
var params = this.params = rawParams.slice(1);
|
||||
|
||||
// a mustache is an eligible helper if:
|
||||
// * its id is simple (a single part, not `this` or `..`)
|
||||
var eligibleHelper = this.eligibleHelper = id.isSimple;
|
||||
|
||||
// a mustache is definitely a helper if:
|
||||
// * it is an eligible helper, and
|
||||
// * it has at least one parameter or hash segment
|
||||
this.isHelper = eligibleHelper && (params.length || hash);
|
||||
|
||||
// if a mustache is an eligible helper but not a definite
|
||||
// helper, it is ambiguous, and will be resolved in a later
|
||||
// pass or at runtime.
|
||||
};
|
||||
|
||||
Handlebars.AST.PartialNode = function(partialName, context) {
|
||||
this.type = "partial";
|
||||
this.partialName = partialName;
|
||||
this.context = context;
|
||||
};
|
||||
|
||||
Handlebars.AST.BlockNode = function(mustache, program, inverse, close) {
|
||||
var verifyMatch = function(open, close) {
|
||||
if(open.original !== close.original) {
|
||||
throw new Handlebars.Exception(open.original + " doesn't match " + close.original);
|
||||
if(inverse) {
|
||||
this.inverse = new AST.ProgramNode(inverse, inverseStrip);
|
||||
this.strip.right = inverseStrip.left;
|
||||
} else if (inverseStrip) {
|
||||
this.strip.left = inverseStrip.right;
|
||||
}
|
||||
};
|
||||
},
|
||||
|
||||
verifyMatch(mustache.id, close);
|
||||
this.type = "block";
|
||||
this.mustache = mustache;
|
||||
this.program = program;
|
||||
this.inverse = inverse;
|
||||
MustacheNode: function(rawParams, hash, open, strip) {
|
||||
this.type = "mustache";
|
||||
this.hash = hash;
|
||||
this.strip = strip;
|
||||
|
||||
if (this.inverse && !this.program) {
|
||||
this.isInverse = true;
|
||||
// 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;
|
||||
}
|
||||
};
|
||||
|
||||
Handlebars.AST.ContentNode = function(string) {
|
||||
this.type = "content";
|
||||
this.string = string;
|
||||
};
|
||||
|
||||
Handlebars.AST.HashNode = function(pairs) {
|
||||
this.type = "hash";
|
||||
this.pairs = pairs;
|
||||
};
|
||||
|
||||
Handlebars.AST.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 Handlebars.Exception("Invalid path: " + original); }
|
||||
else if (part === "..") { depth++; }
|
||||
else { this.isScoped = true; }
|
||||
}
|
||||
else { dig.push(part); }
|
||||
}
|
||||
|
||||
this.original = original;
|
||||
this.parts = dig;
|
||||
this.string = dig.join('.');
|
||||
this.depth = depth;
|
||||
|
||||
// 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;
|
||||
};
|
||||
|
||||
Handlebars.AST.PartialNameNode = function(name) {
|
||||
this.type = "PARTIAL_NAME";
|
||||
this.name = name.original;
|
||||
};
|
||||
|
||||
Handlebars.AST.DataNode = function(id) {
|
||||
this.type = "DATA";
|
||||
this.id = id;
|
||||
};
|
||||
|
||||
Handlebars.AST.StringNode = function(string) {
|
||||
this.type = "STRING";
|
||||
this.original =
|
||||
this.string =
|
||||
this.stringModeValue = string;
|
||||
};
|
||||
|
||||
Handlebars.AST.IntegerNode = function(integer) {
|
||||
this.type = "INTEGER";
|
||||
this.original =
|
||||
this.integer = integer;
|
||||
this.stringModeValue = Number(integer);
|
||||
};
|
||||
|
||||
Handlebars.AST.BooleanNode = function(bool) {
|
||||
this.type = "BOOLEAN";
|
||||
this.bool = bool;
|
||||
this.stringModeValue = bool === "true";
|
||||
};
|
||||
|
||||
Handlebars.AST.CommentNode = function(comment) {
|
||||
this.type = "comment";
|
||||
this.comment = comment;
|
||||
};
|
||||
|
||||
// END(BROWSER)
|
||||
|
||||
return Handlebars;
|
||||
};
|
||||
|
||||
// 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,21 +1,12 @@
|
||||
var handlebars = require("./parser");
|
||||
import parser from "./parser";
|
||||
import AST from "./ast";
|
||||
|
||||
exports.attach = function(Handlebars) {
|
||||
|
||||
// BEGIN(BROWSER)
|
||||
|
||||
Handlebars.Parser = handlebars;
|
||||
|
||||
Handlebars.parse = function(input) {
|
||||
export { parser };
|
||||
|
||||
export function parse(input) {
|
||||
// Just return if an already-compile AST was passed in.
|
||||
if(input.constructor === Handlebars.AST.ProgramNode) { return input; }
|
||||
if(input.constructor === AST.ProgramNode) { return input; }
|
||||
|
||||
Handlebars.Parser.yy = Handlebars.AST;
|
||||
return Handlebars.Parser.parse(input);
|
||||
};
|
||||
|
||||
// END(BROWSER)
|
||||
|
||||
return Handlebars;
|
||||
};
|
||||
parser.yy = AST;
|
||||
return parser.parse(input);
|
||||
}
|
||||
|
||||
@@ -1,14 +1,9 @@
|
||||
var compilerbase = require("./base");
|
||||
import Exception from "../exception";
|
||||
import { parse } from "./base";
|
||||
import JavaScriptCompiler from "./javascript-compiler";
|
||||
import AST from "./ast";
|
||||
|
||||
exports.attach = function(Handlebars) {
|
||||
|
||||
compilerbase.attach(Handlebars);
|
||||
|
||||
// BEGIN(BROWSER)
|
||||
|
||||
/*jshint eqnull:true*/
|
||||
var Compiler = Handlebars.Compiler = function() {};
|
||||
var JavaScriptCompiler = Handlebars.JavaScriptCompiler = function() {};
|
||||
export function Compiler() {}
|
||||
|
||||
// the foundHelper register will disambiguate helper lookup from finding a
|
||||
// function in a context. This is necessary for mustache compatibility, which
|
||||
@@ -41,6 +36,7 @@ Compiler.prototype = {
|
||||
|
||||
return out.join("\n");
|
||||
},
|
||||
|
||||
equals: function(other) {
|
||||
var len = this.opcodes.length;
|
||||
if (other.opcodes.length !== len) {
|
||||
@@ -76,6 +72,7 @@ Compiler.prototype = {
|
||||
guid: 0,
|
||||
|
||||
compile: function(program, options) {
|
||||
this.opcodes = [];
|
||||
this.children = [];
|
||||
this.depths = {list: []};
|
||||
this.options = options;
|
||||
@@ -97,20 +94,30 @@ Compiler.prototype = {
|
||||
}
|
||||
}
|
||||
|
||||
return this.program(program);
|
||||
return this.accept(program);
|
||||
},
|
||||
|
||||
accept: function(node) {
|
||||
return this[node.type](node);
|
||||
var strip = node.strip || {},
|
||||
ret;
|
||||
if (strip.left) {
|
||||
this.opcode('strip');
|
||||
}
|
||||
|
||||
ret = this[node.type](node);
|
||||
|
||||
if (strip.right) {
|
||||
this.opcode('strip');
|
||||
}
|
||||
|
||||
return ret;
|
||||
},
|
||||
|
||||
program: function(program) {
|
||||
var statements = program.statements, statement;
|
||||
this.opcodes = [];
|
||||
var statements = program.statements;
|
||||
|
||||
for(var i=0, l=statements.length; i<l; i++) {
|
||||
statement = statements[i];
|
||||
this[statement.type](statement);
|
||||
this.accept(statements[i]);
|
||||
}
|
||||
this.isSimple = l === 1;
|
||||
|
||||
@@ -302,7 +309,7 @@ Compiler.prototype = {
|
||||
DATA: function(data) {
|
||||
this.options.data = true;
|
||||
if (data.id.isScoped || data.id.depth) {
|
||||
throw new Handlebars.Exception('Scoped data references are not supported: ' + data.original);
|
||||
throw new Exception('Scoped data references are not supported: ' + data.original);
|
||||
}
|
||||
|
||||
this.opcode('lookupData');
|
||||
@@ -416,890 +423,46 @@ Compiler.prototype = {
|
||||
}
|
||||
};
|
||||
|
||||
var Literal = function(value) {
|
||||
this.value = value;
|
||||
};
|
||||
|
||||
JavaScriptCompiler.prototype = {
|
||||
// PUBLIC API: You can override these methods in a subclass to provide
|
||||
// alternative compiled forms for name lookup and buffering semantics
|
||||
nameLookup: function(parent, name /* , type*/) {
|
||||
if (/^[0-9]+$/.test(name)) {
|
||||
return parent + "[" + name + "]";
|
||||
} else if (JavaScriptCompiler.isValidJavaScriptVariableName(name)) {
|
||||
return parent + "." + name;
|
||||
}
|
||||
else {
|
||||
return parent + "['" + name + "']";
|
||||
}
|
||||
},
|
||||
|
||||
appendToBuffer: function(string) {
|
||||
if (this.environment.isSimple) {
|
||||
return "return " + string + ";";
|
||||
} else {
|
||||
return {
|
||||
appendToBuffer: true,
|
||||
content: string,
|
||||
toString: function() { return "buffer += " + string + ";"; }
|
||||
};
|
||||
}
|
||||
},
|
||||
|
||||
initializeBuffer: function() {
|
||||
return this.quotedString("");
|
||||
},
|
||||
|
||||
namespace: "Handlebars",
|
||||
// END PUBLIC API
|
||||
|
||||
compile: function(environment, options, context, asObject) {
|
||||
this.environment = environment;
|
||||
this.options = options || {};
|
||||
|
||||
Handlebars.log(Handlebars.logger.DEBUG, this.environment.disassemble() + "\n\n");
|
||||
|
||||
this.name = this.environment.name;
|
||||
this.isChild = !!context;
|
||||
this.context = context || {
|
||||
programs: [],
|
||||
environments: [],
|
||||
aliases: { }
|
||||
};
|
||||
|
||||
this.preamble();
|
||||
|
||||
this.stackSlot = 0;
|
||||
this.stackVars = [];
|
||||
this.registers = { list: [] };
|
||||
this.compileStack = [];
|
||||
this.inlineStack = [];
|
||||
|
||||
this.compileChildren(environment, options);
|
||||
|
||||
var opcodes = environment.opcodes, opcode;
|
||||
|
||||
this.i = 0;
|
||||
|
||||
for(l=opcodes.length; this.i<l; this.i++) {
|
||||
opcode = opcodes[this.i];
|
||||
|
||||
if(opcode.opcode === 'DECLARE') {
|
||||
this[opcode.name] = opcode.value;
|
||||
} else {
|
||||
this[opcode.opcode].apply(this, opcode.args);
|
||||
}
|
||||
}
|
||||
|
||||
return this.createFunctionContext(asObject);
|
||||
},
|
||||
|
||||
nextOpcode: function() {
|
||||
var opcodes = this.environment.opcodes;
|
||||
return opcodes[this.i + 1];
|
||||
},
|
||||
|
||||
eat: function() {
|
||||
this.i = this.i + 1;
|
||||
},
|
||||
|
||||
preamble: function() {
|
||||
var out = [];
|
||||
|
||||
if (!this.isChild) {
|
||||
var namespace = this.namespace;
|
||||
|
||||
var copies = "helpers = this.merge(helpers, " + namespace + ".helpers);";
|
||||
if (this.environment.usePartial) { copies = copies + " partials = this.merge(partials, " + namespace + ".partials);"; }
|
||||
if (this.options.data) { copies = copies + " data = data || {};"; }
|
||||
out.push(copies);
|
||||
} else {
|
||||
out.push('');
|
||||
}
|
||||
|
||||
if (!this.environment.isSimple) {
|
||||
out.push(", buffer = " + this.initializeBuffer());
|
||||
} else {
|
||||
out.push("");
|
||||
}
|
||||
|
||||
// track the last context pushed into place to allow skipping the
|
||||
// getContext opcode when it would be a noop
|
||||
this.lastContext = 0;
|
||||
this.source = out;
|
||||
},
|
||||
|
||||
createFunctionContext: function(asObject) {
|
||||
var locals = this.stackVars.concat(this.registers.list);
|
||||
|
||||
if(locals.length > 0) {
|
||||
this.source[1] = this.source[1] + ", " + locals.join(", ");
|
||||
}
|
||||
|
||||
// Generate minimizer alias mappings
|
||||
if (!this.isChild) {
|
||||
for (var alias in this.context.aliases) {
|
||||
if (this.context.aliases.hasOwnProperty(alias)) {
|
||||
this.source[1] = this.source[1] + ', ' + alias + '=' + this.context.aliases[alias];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (this.source[1]) {
|
||||
this.source[1] = "var " + this.source[1].substring(2) + ";";
|
||||
}
|
||||
|
||||
// Merge children
|
||||
if (!this.isChild) {
|
||||
this.source[1] += '\n' + this.context.programs.join('\n') + '\n';
|
||||
}
|
||||
|
||||
if (!this.environment.isSimple) {
|
||||
this.source.push("return buffer;");
|
||||
}
|
||||
|
||||
var params = this.isChild ? ["depth0", "data"] : ["Handlebars", "depth0", "helpers", "partials", "data"];
|
||||
|
||||
for(var i=0, l=this.environment.depths.list.length; i<l; i++) {
|
||||
params.push("depth" + this.environment.depths.list[i]);
|
||||
}
|
||||
|
||||
// Perform a second pass over the output to merge content when possible
|
||||
var source = this.mergeSource();
|
||||
|
||||
if (!this.isChild) {
|
||||
var revision = Handlebars.COMPILER_REVISION,
|
||||
versions = Handlebars.REVISION_CHANGES[revision];
|
||||
source = "this.compilerInfo = ["+revision+",'"+versions+"'];\n"+source;
|
||||
}
|
||||
|
||||
if (asObject) {
|
||||
params.push(source);
|
||||
|
||||
return Function.apply(this, params);
|
||||
} else {
|
||||
var functionSource = 'function ' + (this.name || '') + '(' + params.join(',') + ') {\n ' + source + '}';
|
||||
Handlebars.log(Handlebars.logger.DEBUG, functionSource + "\n\n");
|
||||
return functionSource;
|
||||
}
|
||||
},
|
||||
mergeSource: function() {
|
||||
// WARN: We are not handling the case where buffer is still populated as the source should
|
||||
// not have buffer append operations as their final action.
|
||||
var source = '',
|
||||
buffer;
|
||||
for (var i = 0, len = this.source.length; i < len; i++) {
|
||||
var line = this.source[i];
|
||||
if (line.appendToBuffer) {
|
||||
if (buffer) {
|
||||
buffer = buffer + '\n + ' + line.content;
|
||||
} else {
|
||||
buffer = line.content;
|
||||
}
|
||||
} else {
|
||||
if (buffer) {
|
||||
source += 'buffer += ' + buffer + ';\n ';
|
||||
buffer = undefined;
|
||||
}
|
||||
source += line + '\n ';
|
||||
}
|
||||
}
|
||||
return source;
|
||||
},
|
||||
|
||||
// [blockValue]
|
||||
//
|
||||
// On stack, before: hash, inverse, program, value
|
||||
// On stack, after: return value of blockHelperMissing
|
||||
//
|
||||
// The purpose of this opcode is to take a block of the form
|
||||
// `{{#foo}}...{{/foo}}`, resolve the value of `foo`, and
|
||||
// replace it on the stack with the result of properly
|
||||
// invoking blockHelperMissing.
|
||||
blockValue: function() {
|
||||
this.context.aliases.blockHelperMissing = 'helpers.blockHelperMissing';
|
||||
|
||||
var params = ["depth0"];
|
||||
this.setupParams(0, params);
|
||||
|
||||
this.replaceStack(function(current) {
|
||||
params.splice(1, 0, current);
|
||||
return "blockHelperMissing.call(" + params.join(", ") + ")";
|
||||
});
|
||||
},
|
||||
|
||||
// [ambiguousBlockValue]
|
||||
//
|
||||
// On stack, before: hash, inverse, program, value
|
||||
// Compiler value, before: lastHelper=value of last found helper, if any
|
||||
// On stack, after, if no lastHelper: same as [blockValue]
|
||||
// On stack, after, if lastHelper: value
|
||||
ambiguousBlockValue: function() {
|
||||
this.context.aliases.blockHelperMissing = 'helpers.blockHelperMissing';
|
||||
|
||||
var params = ["depth0"];
|
||||
this.setupParams(0, params);
|
||||
|
||||
var current = this.topStack();
|
||||
params.splice(1, 0, current);
|
||||
|
||||
// Use the options value generated from the invocation
|
||||
params[params.length-1] = 'options';
|
||||
|
||||
this.source.push("if (!" + this.lastHelper + ") { " + current + " = blockHelperMissing.call(" + params.join(", ") + "); }");
|
||||
},
|
||||
|
||||
// [appendContent]
|
||||
//
|
||||
// On stack, before: ...
|
||||
// On stack, after: ...
|
||||
//
|
||||
// Appends the string value of `content` to the current buffer
|
||||
appendContent: function(content) {
|
||||
this.source.push(this.appendToBuffer(this.quotedString(content)));
|
||||
},
|
||||
|
||||
// [append]
|
||||
//
|
||||
// On stack, before: value, ...
|
||||
// On stack, after: ...
|
||||
//
|
||||
// Coerces `value` to a String and appends it to the current buffer.
|
||||
//
|
||||
// If `value` is truthy, or 0, it is coerced into a string and appended
|
||||
// Otherwise, the empty string is appended
|
||||
append: function() {
|
||||
// Force anything that is inlined onto the stack so we don't have duplication
|
||||
// when we examine local
|
||||
this.flushInline();
|
||||
var local = this.popStack();
|
||||
this.source.push("if(" + local + " || " + local + " === 0) { " + this.appendToBuffer(local) + " }");
|
||||
if (this.environment.isSimple) {
|
||||
this.source.push("else { " + this.appendToBuffer("''") + " }");
|
||||
}
|
||||
},
|
||||
|
||||
// [appendEscaped]
|
||||
//
|
||||
// On stack, before: value, ...
|
||||
// On stack, after: ...
|
||||
//
|
||||
// Escape `value` and append it to the buffer
|
||||
appendEscaped: function() {
|
||||
this.context.aliases.escapeExpression = 'this.escapeExpression';
|
||||
|
||||
this.source.push(this.appendToBuffer("escapeExpression(" + this.popStack() + ")"));
|
||||
},
|
||||
|
||||
// [getContext]
|
||||
//
|
||||
// On stack, before: ...
|
||||
// On stack, after: ...
|
||||
// Compiler value, after: lastContext=depth
|
||||
//
|
||||
// Set the value of the `lastContext` compiler value to the depth
|
||||
getContext: function(depth) {
|
||||
if(this.lastContext !== depth) {
|
||||
this.lastContext = depth;
|
||||
}
|
||||
},
|
||||
|
||||
// [lookupOnContext]
|
||||
//
|
||||
// On stack, before: ...
|
||||
// On stack, after: currentContext[name], ...
|
||||
//
|
||||
// Looks up the value of `name` on the current context and pushes
|
||||
// it onto the stack.
|
||||
lookupOnContext: function(name) {
|
||||
this.push(this.nameLookup('depth' + this.lastContext, name, 'context'));
|
||||
},
|
||||
|
||||
// [pushContext]
|
||||
//
|
||||
// On stack, before: ...
|
||||
// On stack, after: currentContext, ...
|
||||
//
|
||||
// Pushes the value of the current context onto the stack.
|
||||
pushContext: function() {
|
||||
this.pushStackLiteral('depth' + this.lastContext);
|
||||
},
|
||||
|
||||
// [resolvePossibleLambda]
|
||||
//
|
||||
// On stack, before: value, ...
|
||||
// On stack, after: resolved value, ...
|
||||
//
|
||||
// If the `value` is a lambda, replace it on the stack by
|
||||
// the return value of the lambda
|
||||
resolvePossibleLambda: function() {
|
||||
this.context.aliases.functionType = '"function"';
|
||||
|
||||
this.replaceStack(function(current) {
|
||||
return "typeof " + current + " === functionType ? " + current + ".apply(depth0) : " + current;
|
||||
});
|
||||
},
|
||||
|
||||
// [lookup]
|
||||
//
|
||||
// On stack, before: value, ...
|
||||
// On stack, after: value[name], ...
|
||||
//
|
||||
// Replace the value on the stack with the result of looking
|
||||
// up `name` on `value`
|
||||
lookup: function(name) {
|
||||
this.replaceStack(function(current) {
|
||||
return current + " == null || " + current + " === false ? " + current + " : " + this.nameLookup(current, name, 'context');
|
||||
});
|
||||
},
|
||||
|
||||
// [lookupData]
|
||||
//
|
||||
// On stack, before: ...
|
||||
// On stack, after: data[id], ...
|
||||
//
|
||||
// Push the result of looking up `id` on the current data
|
||||
lookupData: function(id) {
|
||||
this.push('data');
|
||||
},
|
||||
|
||||
// [pushStringParam]
|
||||
//
|
||||
// On stack, before: ...
|
||||
// On stack, after: string, currentContext, ...
|
||||
//
|
||||
// This opcode is designed for use in string mode, which
|
||||
// provides the string value of a parameter along with its
|
||||
// depth rather than resolving it immediately.
|
||||
pushStringParam: function(string, type) {
|
||||
this.pushStackLiteral('depth' + this.lastContext);
|
||||
|
||||
this.pushString(type);
|
||||
|
||||
if (typeof string === 'string') {
|
||||
this.pushString(string);
|
||||
} else {
|
||||
this.pushStackLiteral(string);
|
||||
}
|
||||
},
|
||||
|
||||
emptyHash: function() {
|
||||
this.pushStackLiteral('{}');
|
||||
|
||||
if (this.options.stringParams) {
|
||||
this.register('hashTypes', '{}');
|
||||
this.register('hashContexts', '{}');
|
||||
}
|
||||
},
|
||||
pushHash: function() {
|
||||
this.hash = {values: [], types: [], contexts: []};
|
||||
},
|
||||
popHash: function() {
|
||||
var hash = this.hash;
|
||||
this.hash = undefined;
|
||||
|
||||
if (this.options.stringParams) {
|
||||
this.register('hashContexts', '{' + hash.contexts.join(',') + '}');
|
||||
this.register('hashTypes', '{' + hash.types.join(',') + '}');
|
||||
}
|
||||
this.push('{\n ' + hash.values.join(',\n ') + '\n }');
|
||||
},
|
||||
|
||||
// [pushString]
|
||||
//
|
||||
// On stack, before: ...
|
||||
// On stack, after: quotedString(string), ...
|
||||
//
|
||||
// Push a quoted version of `string` onto the stack
|
||||
pushString: function(string) {
|
||||
this.pushStackLiteral(this.quotedString(string));
|
||||
},
|
||||
|
||||
// [push]
|
||||
//
|
||||
// On stack, before: ...
|
||||
// On stack, after: expr, ...
|
||||
//
|
||||
// Push an expression onto the stack
|
||||
push: function(expr) {
|
||||
this.inlineStack.push(expr);
|
||||
return expr;
|
||||
},
|
||||
|
||||
// [pushLiteral]
|
||||
//
|
||||
// On stack, before: ...
|
||||
// On stack, after: value, ...
|
||||
//
|
||||
// Pushes a value onto the stack. This operation prevents
|
||||
// the compiler from creating a temporary variable to hold
|
||||
// it.
|
||||
pushLiteral: function(value) {
|
||||
this.pushStackLiteral(value);
|
||||
},
|
||||
|
||||
// [pushProgram]
|
||||
//
|
||||
// On stack, before: ...
|
||||
// On stack, after: program(guid), ...
|
||||
//
|
||||
// Push a program expression onto the stack. This takes
|
||||
// a compile-time guid and converts it into a runtime-accessible
|
||||
// expression.
|
||||
pushProgram: function(guid) {
|
||||
if (guid != null) {
|
||||
this.pushStackLiteral(this.programExpression(guid));
|
||||
} else {
|
||||
this.pushStackLiteral(null);
|
||||
}
|
||||
},
|
||||
|
||||
// [invokeHelper]
|
||||
//
|
||||
// On stack, before: hash, inverse, program, params..., ...
|
||||
// On stack, after: result of helper invocation
|
||||
//
|
||||
// Pops off the helper's parameters, invokes the helper,
|
||||
// and pushes the helper's return value onto the stack.
|
||||
//
|
||||
// If the helper is not found, `helperMissing` is called.
|
||||
invokeHelper: function(paramSize, name) {
|
||||
this.context.aliases.helperMissing = 'helpers.helperMissing';
|
||||
|
||||
var helper = this.lastHelper = this.setupHelper(paramSize, name, true);
|
||||
var nonHelper = this.nameLookup('depth' + this.lastContext, name, 'context');
|
||||
|
||||
this.push(helper.name + ' || ' + nonHelper);
|
||||
this.replaceStack(function(name) {
|
||||
return name + ' ? ' + name + '.call(' +
|
||||
helper.callParams + ") " + ": helperMissing.call(" +
|
||||
helper.helperMissingParams + ")";
|
||||
});
|
||||
},
|
||||
|
||||
// [invokeKnownHelper]
|
||||
//
|
||||
// On stack, before: hash, inverse, program, params..., ...
|
||||
// On stack, after: result of helper invocation
|
||||
//
|
||||
// This operation is used when the helper is known to exist,
|
||||
// so a `helperMissing` fallback is not required.
|
||||
invokeKnownHelper: function(paramSize, name) {
|
||||
var helper = this.setupHelper(paramSize, name);
|
||||
this.push(helper.name + ".call(" + helper.callParams + ")");
|
||||
},
|
||||
|
||||
// [invokeAmbiguous]
|
||||
//
|
||||
// On stack, before: hash, inverse, program, params..., ...
|
||||
// On stack, after: result of disambiguation
|
||||
//
|
||||
// This operation is used when an expression like `{{foo}}`
|
||||
// is provided, but we don't know at compile-time whether it
|
||||
// is a helper or a path.
|
||||
//
|
||||
// This operation emits more code than the other options,
|
||||
// and can be avoided by passing the `knownHelpers` and
|
||||
// `knownHelpersOnly` flags at compile-time.
|
||||
invokeAmbiguous: function(name, helperCall) {
|
||||
this.context.aliases.functionType = '"function"';
|
||||
|
||||
this.pushStackLiteral('{}'); // Hash value
|
||||
var helper = this.setupHelper(0, name, helperCall);
|
||||
|
||||
var helperName = this.lastHelper = this.nameLookup('helpers', name, 'helper');
|
||||
|
||||
var nonHelper = this.nameLookup('depth' + this.lastContext, name, 'context');
|
||||
var nextStack = this.nextStack();
|
||||
|
||||
this.source.push('if (' + nextStack + ' = ' + helperName + ') { ' + nextStack + ' = ' + nextStack + '.call(' + helper.callParams + '); }');
|
||||
this.source.push('else { ' + nextStack + ' = ' + nonHelper + '; ' + nextStack + ' = typeof ' + nextStack + ' === functionType ? ' + nextStack + '.apply(depth0) : ' + nextStack + '; }');
|
||||
},
|
||||
|
||||
// [invokePartial]
|
||||
//
|
||||
// On stack, before: context, ...
|
||||
// On stack after: result of partial invocation
|
||||
//
|
||||
// This operation pops off a context, invokes a partial with that context,
|
||||
// and pushes the result of the invocation back.
|
||||
invokePartial: function(name) {
|
||||
var params = [this.nameLookup('partials', name, 'partial'), "'" + name + "'", this.popStack(), "helpers", "partials"];
|
||||
|
||||
if (this.options.data) {
|
||||
params.push("data");
|
||||
}
|
||||
|
||||
this.context.aliases.self = "this";
|
||||
this.push("self.invokePartial(" + params.join(", ") + ")");
|
||||
},
|
||||
|
||||
// [assignToHash]
|
||||
//
|
||||
// On stack, before: value, hash, ...
|
||||
// On stack, after: hash, ...
|
||||
//
|
||||
// Pops a value and hash off the stack, assigns `hash[key] = value`
|
||||
// and pushes the hash back onto the stack.
|
||||
assignToHash: function(key) {
|
||||
var value = this.popStack(),
|
||||
context,
|
||||
type;
|
||||
|
||||
if (this.options.stringParams) {
|
||||
type = this.popStack();
|
||||
context = this.popStack();
|
||||
}
|
||||
|
||||
var hash = this.hash;
|
||||
if (context) {
|
||||
hash.contexts.push("'" + key + "': " + context);
|
||||
}
|
||||
if (type) {
|
||||
hash.types.push("'" + key + "': " + type);
|
||||
}
|
||||
hash.values.push("'" + key + "': (" + value + ")");
|
||||
},
|
||||
|
||||
// HELPERS
|
||||
|
||||
compiler: JavaScriptCompiler,
|
||||
|
||||
compileChildren: function(environment, options) {
|
||||
var children = environment.children, child, compiler;
|
||||
|
||||
for(var i=0, l=children.length; i<l; i++) {
|
||||
child = children[i];
|
||||
compiler = new this.compiler();
|
||||
|
||||
var index = this.matchExistingProgram(child);
|
||||
|
||||
if (index == null) {
|
||||
this.context.programs.push(''); // Placeholder to prevent name conflicts for nested children
|
||||
index = this.context.programs.length;
|
||||
child.index = index;
|
||||
child.name = 'program' + index;
|
||||
this.context.programs[index] = compiler.compile(child, options, this.context);
|
||||
this.context.environments[index] = child;
|
||||
} else {
|
||||
child.index = index;
|
||||
child.name = 'program' + index;
|
||||
}
|
||||
}
|
||||
},
|
||||
matchExistingProgram: function(child) {
|
||||
for (var i = 0, len = this.context.environments.length; i < len; i++) {
|
||||
var environment = this.context.environments[i];
|
||||
if (environment && environment.equals(child)) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
programExpression: function(guid) {
|
||||
this.context.aliases.self = "this";
|
||||
|
||||
if(guid == null) {
|
||||
return "self.noop";
|
||||
}
|
||||
|
||||
var child = this.environment.children[guid],
|
||||
depths = child.depths.list, depth;
|
||||
|
||||
var programParams = [child.index, child.name, "data"];
|
||||
|
||||
for(var i=0, l = depths.length; i<l; i++) {
|
||||
depth = depths[i];
|
||||
|
||||
if(depth === 1) { programParams.push("depth0"); }
|
||||
else { programParams.push("depth" + (depth - 1)); }
|
||||
}
|
||||
|
||||
return (depths.length === 0 ? "self.program(" : "self.programWithDepth(") + programParams.join(", ") + ")";
|
||||
},
|
||||
|
||||
register: function(name, val) {
|
||||
this.useRegister(name);
|
||||
this.source.push(name + " = " + val + ";");
|
||||
},
|
||||
|
||||
useRegister: function(name) {
|
||||
if(!this.registers[name]) {
|
||||
this.registers[name] = true;
|
||||
this.registers.list.push(name);
|
||||
}
|
||||
},
|
||||
|
||||
pushStackLiteral: function(item) {
|
||||
return this.push(new Literal(item));
|
||||
},
|
||||
|
||||
pushStack: function(item) {
|
||||
this.flushInline();
|
||||
|
||||
var stack = this.incrStack();
|
||||
if (item) {
|
||||
this.source.push(stack + " = " + item + ";");
|
||||
}
|
||||
this.compileStack.push(stack);
|
||||
return stack;
|
||||
},
|
||||
|
||||
replaceStack: function(callback) {
|
||||
var prefix = '',
|
||||
inline = this.isInline(),
|
||||
stack;
|
||||
|
||||
// If we are currently inline then we want to merge the inline statement into the
|
||||
// replacement statement via ','
|
||||
if (inline) {
|
||||
var top = this.popStack(true);
|
||||
|
||||
if (top instanceof Literal) {
|
||||
// Literals do not need to be inlined
|
||||
stack = top.value;
|
||||
} else {
|
||||
// Get or create the current stack name for use by the inline
|
||||
var name = this.stackSlot ? this.topStackName() : this.incrStack();
|
||||
|
||||
prefix = '(' + this.push(name) + ' = ' + top + '),';
|
||||
stack = this.topStack();
|
||||
}
|
||||
} else {
|
||||
stack = this.topStack();
|
||||
}
|
||||
|
||||
var item = callback.call(this, stack);
|
||||
|
||||
if (inline) {
|
||||
if (this.inlineStack.length || this.compileStack.length) {
|
||||
this.popStack();
|
||||
}
|
||||
this.push('(' + prefix + item + ')');
|
||||
} else {
|
||||
// Prevent modification of the context depth variable. Through replaceStack
|
||||
if (!/^stack/.test(stack)) {
|
||||
stack = this.nextStack();
|
||||
}
|
||||
|
||||
this.source.push(stack + " = (" + prefix + item + ");");
|
||||
}
|
||||
return stack;
|
||||
},
|
||||
|
||||
nextStack: function() {
|
||||
return this.pushStack();
|
||||
},
|
||||
|
||||
incrStack: function() {
|
||||
this.stackSlot++;
|
||||
if(this.stackSlot > this.stackVars.length) { this.stackVars.push("stack" + this.stackSlot); }
|
||||
return this.topStackName();
|
||||
},
|
||||
topStackName: function() {
|
||||
return "stack" + this.stackSlot;
|
||||
},
|
||||
flushInline: function() {
|
||||
var inlineStack = this.inlineStack;
|
||||
if (inlineStack.length) {
|
||||
this.inlineStack = [];
|
||||
for (var i = 0, len = inlineStack.length; i < len; i++) {
|
||||
var entry = inlineStack[i];
|
||||
if (entry instanceof Literal) {
|
||||
this.compileStack.push(entry);
|
||||
} else {
|
||||
this.pushStack(entry);
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
isInline: function() {
|
||||
return this.inlineStack.length;
|
||||
},
|
||||
|
||||
popStack: function(wrapped) {
|
||||
var inline = this.isInline(),
|
||||
item = (inline ? this.inlineStack : this.compileStack).pop();
|
||||
|
||||
if (!wrapped && (item instanceof Literal)) {
|
||||
return item.value;
|
||||
} else {
|
||||
if (!inline) {
|
||||
this.stackSlot--;
|
||||
}
|
||||
return item;
|
||||
}
|
||||
},
|
||||
|
||||
topStack: function(wrapped) {
|
||||
var stack = (this.isInline() ? this.inlineStack : this.compileStack),
|
||||
item = stack[stack.length - 1];
|
||||
|
||||
if (!wrapped && (item instanceof Literal)) {
|
||||
return item.value;
|
||||
} else {
|
||||
return item;
|
||||
}
|
||||
},
|
||||
|
||||
quotedString: function(str) {
|
||||
return '"' + str
|
||||
.replace(/\\/g, '\\\\')
|
||||
.replace(/"/g, '\\"')
|
||||
.replace(/\n/g, '\\n')
|
||||
.replace(/\r/g, '\\r')
|
||||
.replace(/\u2028/g, '\\u2028') // Per Ecma-262 7.3 + 7.8.4
|
||||
.replace(/\u2029/g, '\\u2029') + '"';
|
||||
},
|
||||
|
||||
setupHelper: function(paramSize, name, missingParams) {
|
||||
var params = [];
|
||||
this.setupParams(paramSize, params, missingParams);
|
||||
var foundHelper = this.nameLookup('helpers', name, 'helper');
|
||||
|
||||
return {
|
||||
params: params,
|
||||
name: foundHelper,
|
||||
callParams: ["depth0"].concat(params).join(", "),
|
||||
helperMissingParams: missingParams && ["depth0", this.quotedString(name)].concat(params).join(", ")
|
||||
};
|
||||
},
|
||||
|
||||
// the params and contexts arguments are passed in arrays
|
||||
// to fill in
|
||||
setupParams: function(paramSize, params, useRegister) {
|
||||
var options = [], contexts = [], types = [], param, inverse, program;
|
||||
|
||||
options.push("hash:" + this.popStack());
|
||||
|
||||
inverse = this.popStack();
|
||||
program = this.popStack();
|
||||
|
||||
// Avoid setting fn and inverse if neither are set. This allows
|
||||
// helpers to do a check for `if (options.fn)`
|
||||
if (program || inverse) {
|
||||
if (!program) {
|
||||
this.context.aliases.self = "this";
|
||||
program = "self.noop";
|
||||
}
|
||||
|
||||
if (!inverse) {
|
||||
this.context.aliases.self = "this";
|
||||
inverse = "self.noop";
|
||||
}
|
||||
|
||||
options.push("inverse:" + inverse);
|
||||
options.push("fn:" + program);
|
||||
}
|
||||
|
||||
for(var i=0; i<paramSize; i++) {
|
||||
param = this.popStack();
|
||||
params.push(param);
|
||||
|
||||
if(this.options.stringParams) {
|
||||
types.push(this.popStack());
|
||||
contexts.push(this.popStack());
|
||||
}
|
||||
}
|
||||
|
||||
if (this.options.stringParams) {
|
||||
options.push("contexts:[" + contexts.join(",") + "]");
|
||||
options.push("types:[" + types.join(",") + "]");
|
||||
options.push("hashContexts:hashContexts");
|
||||
options.push("hashTypes:hashTypes");
|
||||
}
|
||||
|
||||
if(this.options.data) {
|
||||
options.push("data:data");
|
||||
}
|
||||
|
||||
options = "{" + options.join(",") + "}";
|
||||
if (useRegister) {
|
||||
this.register('options', options);
|
||||
params.push('options');
|
||||
} else {
|
||||
params.push(options);
|
||||
}
|
||||
return params.join(", ");
|
||||
}
|
||||
};
|
||||
|
||||
var reservedWords = (
|
||||
"break else new var" +
|
||||
" case finally return void" +
|
||||
" catch for switch while" +
|
||||
" continue function this with" +
|
||||
" default if throw" +
|
||||
" delete in try" +
|
||||
" do instanceof typeof" +
|
||||
" abstract enum int short" +
|
||||
" boolean export interface static" +
|
||||
" byte extends long super" +
|
||||
" char final native synchronized" +
|
||||
" class float package throws" +
|
||||
" const goto private transient" +
|
||||
" debugger implements protected volatile" +
|
||||
" double import public let yield"
|
||||
).split(" ");
|
||||
|
||||
var compilerWords = JavaScriptCompiler.RESERVED_WORDS = {};
|
||||
|
||||
for(var i=0, l=reservedWords.length; i<l; i++) {
|
||||
compilerWords[reservedWords[i]] = true;
|
||||
}
|
||||
|
||||
JavaScriptCompiler.isValidJavaScriptVariableName = function(name) {
|
||||
if(!JavaScriptCompiler.RESERVED_WORDS[name] && /^[a-zA-Z_$][0-9a-zA-Z_$]+$/.test(name)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
Handlebars.precompile = function(input, options) {
|
||||
if (input == null || (typeof input !== 'string' && input.constructor !== Handlebars.AST.ProgramNode)) {
|
||||
throw new Handlebars.Exception("You must pass a string or Handlebars AST to Handlebars.precompile. You passed " + input);
|
||||
export function precompile(input, options) {
|
||||
if (input == null || (typeof input !== 'string' && input.constructor !== AST.ProgramNode)) {
|
||||
throw new Exception("You must pass a string or Handlebars AST to Handlebars.precompile. You passed " + input);
|
||||
}
|
||||
|
||||
options = options || {};
|
||||
if (!('data' in options)) {
|
||||
options.data = true;
|
||||
}
|
||||
var ast = Handlebars.parse(input);
|
||||
|
||||
var ast = parse(input);
|
||||
var environment = new Compiler().compile(ast, options);
|
||||
return new JavaScriptCompiler().compile(environment, options);
|
||||
};
|
||||
}
|
||||
|
||||
Handlebars.compile = function(input, options) {
|
||||
if (input == null || (typeof input !== 'string' && input.constructor !== Handlebars.AST.ProgramNode)) {
|
||||
throw new Handlebars.Exception("You must pass a string or Handlebars AST to Handlebars.compile. You passed " + input);
|
||||
export function compile(input, options, env) {
|
||||
if (input == null || (typeof input !== 'string' && input.constructor !== AST.ProgramNode)) {
|
||||
throw new Exception("You must pass a string or Handlebars AST to Handlebars.compile. You passed " + input);
|
||||
}
|
||||
|
||||
options = options || {};
|
||||
|
||||
if (!('data' in options)) {
|
||||
options.data = true;
|
||||
}
|
||||
|
||||
var compiled;
|
||||
function compile() {
|
||||
var ast = Handlebars.parse(input);
|
||||
|
||||
function compileInput() {
|
||||
var ast = parse(input);
|
||||
var environment = new Compiler().compile(ast, options);
|
||||
var templateSpec = new JavaScriptCompiler().compile(environment, options, undefined, true);
|
||||
return Handlebars.template(templateSpec);
|
||||
return env.template(templateSpec);
|
||||
}
|
||||
|
||||
// Template is only compiled on first use and cached after that point.
|
||||
return function(context, options) {
|
||||
if (!compiled) {
|
||||
compiled = compile();
|
||||
compiled = compileInput();
|
||||
}
|
||||
return compiled.call(this, context, options);
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
// END(BROWSER)
|
||||
|
||||
return Handlebars;
|
||||
|
||||
};
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -1,16 +0,0 @@
|
||||
// Each of these module will augment the Handlebars object as it loads. No need to perform addition operations
|
||||
module.exports.attach = function(Handlebars) {
|
||||
|
||||
var visitor = require("./visitor"),
|
||||
printer = require("./printer"),
|
||||
ast = require("./ast"),
|
||||
compiler = require("./compiler");
|
||||
|
||||
visitor.attach(Handlebars);
|
||||
printer.attach(Handlebars);
|
||||
ast.attach(Handlebars);
|
||||
compiler.attach(Handlebars);
|
||||
|
||||
return Handlebars;
|
||||
|
||||
};
|
||||
@@ -0,0 +1,891 @@
|
||||
import { COMPILER_REVISION, REVISION_CHANGES, log } from "../base";
|
||||
|
||||
function Literal(value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
function JavaScriptCompiler() {}
|
||||
|
||||
JavaScriptCompiler.prototype = {
|
||||
// PUBLIC API: You can override these methods in a subclass to provide
|
||||
// alternative compiled forms for name lookup and buffering semantics
|
||||
nameLookup: function(parent, name /* , type*/) {
|
||||
var wrap,
|
||||
ret;
|
||||
if (parent.indexOf('depth') === 0) {
|
||||
wrap = true;
|
||||
}
|
||||
|
||||
if (/^[0-9]+$/.test(name)) {
|
||||
ret = parent + "[" + name + "]";
|
||||
} else if (JavaScriptCompiler.isValidJavaScriptVariableName(name)) {
|
||||
ret = parent + "." + name;
|
||||
}
|
||||
else {
|
||||
ret = parent + "['" + name + "']";
|
||||
}
|
||||
|
||||
if (wrap) {
|
||||
return '(' + parent + ' && ' + ret + ')';
|
||||
} else {
|
||||
return ret;
|
||||
}
|
||||
},
|
||||
|
||||
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 + ";";
|
||||
} else {
|
||||
return {
|
||||
appendToBuffer: true,
|
||||
content: string,
|
||||
toString: function() { return "buffer += " + string + ";"; }
|
||||
};
|
||||
}
|
||||
},
|
||||
|
||||
initializeBuffer: function() {
|
||||
return this.quotedString("");
|
||||
},
|
||||
|
||||
namespace: "Handlebars",
|
||||
// END PUBLIC API
|
||||
|
||||
compile: function(environment, options, context, asObject) {
|
||||
this.environment = environment;
|
||||
this.options = options || {};
|
||||
|
||||
log('debug', this.environment.disassemble() + "\n\n");
|
||||
|
||||
this.name = this.environment.name;
|
||||
this.isChild = !!context;
|
||||
this.context = context || {
|
||||
programs: [],
|
||||
environments: [],
|
||||
aliases: { }
|
||||
};
|
||||
|
||||
this.preamble();
|
||||
|
||||
this.stackSlot = 0;
|
||||
this.stackVars = [];
|
||||
this.registers = { list: [] };
|
||||
this.compileStack = [];
|
||||
this.inlineStack = [];
|
||||
|
||||
this.compileChildren(environment, options);
|
||||
|
||||
var opcodes = environment.opcodes, opcode;
|
||||
|
||||
this.i = 0;
|
||||
|
||||
for(var l=opcodes.length; this.i<l; this.i++) {
|
||||
opcode = opcodes[this.i];
|
||||
|
||||
if(opcode.opcode === 'DECLARE') {
|
||||
this[opcode.name] = opcode.value;
|
||||
} else {
|
||||
this[opcode.opcode].apply(this, opcode.args);
|
||||
}
|
||||
|
||||
// Reset the stripNext flag if it was not set by this operation.
|
||||
if (opcode.opcode !== this.stripNext) {
|
||||
this.stripNext = false;
|
||||
}
|
||||
}
|
||||
|
||||
// Flush any trailing content that might be pending.
|
||||
this.pushSource('');
|
||||
|
||||
return this.createFunctionContext(asObject);
|
||||
},
|
||||
|
||||
preamble: function() {
|
||||
var out = [];
|
||||
|
||||
if (!this.isChild) {
|
||||
var namespace = this.namespace;
|
||||
|
||||
var copies = "helpers = this.merge(helpers, " + namespace + ".helpers);";
|
||||
if (this.environment.usePartial) { copies = copies + " partials = this.merge(partials, " + namespace + ".partials);"; }
|
||||
if (this.options.data) { copies = copies + " data = data || {};"; }
|
||||
out.push(copies);
|
||||
} else {
|
||||
out.push('');
|
||||
}
|
||||
|
||||
if (!this.environment.isSimple) {
|
||||
out.push(", buffer = " + this.initializeBuffer());
|
||||
} else {
|
||||
out.push("");
|
||||
}
|
||||
|
||||
// track the last context pushed into place to allow skipping the
|
||||
// getContext opcode when it would be a noop
|
||||
this.lastContext = 0;
|
||||
this.source = out;
|
||||
},
|
||||
|
||||
createFunctionContext: function(asObject) {
|
||||
var locals = this.stackVars.concat(this.registers.list);
|
||||
|
||||
if(locals.length > 0) {
|
||||
this.source[1] = this.source[1] + ", " + locals.join(", ");
|
||||
}
|
||||
|
||||
// Generate minimizer alias mappings
|
||||
if (!this.isChild) {
|
||||
for (var alias in this.context.aliases) {
|
||||
if (this.context.aliases.hasOwnProperty(alias)) {
|
||||
this.source[1] = this.source[1] + ', ' + alias + '=' + this.context.aliases[alias];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (this.source[1]) {
|
||||
this.source[1] = "var " + this.source[1].substring(2) + ";";
|
||||
}
|
||||
|
||||
// Merge children
|
||||
if (!this.isChild) {
|
||||
this.source[1] += '\n' + this.context.programs.join('\n') + '\n';
|
||||
}
|
||||
|
||||
if (!this.environment.isSimple) {
|
||||
this.pushSource("return buffer;");
|
||||
}
|
||||
|
||||
var params = this.isChild ? ["depth0", "data"] : ["Handlebars", "depth0", "helpers", "partials", "data"];
|
||||
|
||||
for(var i=0, l=this.environment.depths.list.length; i<l; i++) {
|
||||
params.push("depth" + this.environment.depths.list[i]);
|
||||
}
|
||||
|
||||
// Perform a second pass over the output to merge content when possible
|
||||
var source = this.mergeSource();
|
||||
|
||||
if (!this.isChild) {
|
||||
source = this.compilerInfo()+source;
|
||||
}
|
||||
|
||||
if (asObject) {
|
||||
params.push(source);
|
||||
|
||||
return Function.apply(this, params);
|
||||
} else {
|
||||
var functionSource = 'function ' + (this.name || '') + '(' + params.join(',') + ') {\n ' + source + '}';
|
||||
log('debug', functionSource + "\n\n");
|
||||
return functionSource;
|
||||
}
|
||||
},
|
||||
mergeSource: function() {
|
||||
// WARN: We are not handling the case where buffer is still populated as the source should
|
||||
// not have buffer append operations as their final action.
|
||||
var source = '',
|
||||
buffer;
|
||||
for (var i = 0, len = this.source.length; i < len; i++) {
|
||||
var line = this.source[i];
|
||||
if (line.appendToBuffer) {
|
||||
if (buffer) {
|
||||
buffer = buffer + '\n + ' + line.content;
|
||||
} else {
|
||||
buffer = line.content;
|
||||
}
|
||||
} else {
|
||||
if (buffer) {
|
||||
source += 'buffer += ' + buffer + ';\n ';
|
||||
buffer = undefined;
|
||||
}
|
||||
source += line + '\n ';
|
||||
}
|
||||
}
|
||||
return source;
|
||||
},
|
||||
|
||||
// [blockValue]
|
||||
//
|
||||
// On stack, before: hash, inverse, program, value
|
||||
// On stack, after: return value of blockHelperMissing
|
||||
//
|
||||
// The purpose of this opcode is to take a block of the form
|
||||
// `{{#foo}}...{{/foo}}`, resolve the value of `foo`, and
|
||||
// replace it on the stack with the result of properly
|
||||
// invoking blockHelperMissing.
|
||||
blockValue: function() {
|
||||
this.context.aliases.blockHelperMissing = 'helpers.blockHelperMissing';
|
||||
|
||||
var params = ["depth0"];
|
||||
this.setupParams(0, params);
|
||||
|
||||
this.replaceStack(function(current) {
|
||||
params.splice(1, 0, current);
|
||||
return "blockHelperMissing.call(" + params.join(", ") + ")";
|
||||
});
|
||||
},
|
||||
|
||||
// [ambiguousBlockValue]
|
||||
//
|
||||
// On stack, before: hash, inverse, program, value
|
||||
// Compiler value, before: lastHelper=value of last found helper, if any
|
||||
// On stack, after, if no lastHelper: same as [blockValue]
|
||||
// On stack, after, if lastHelper: value
|
||||
ambiguousBlockValue: function() {
|
||||
this.context.aliases.blockHelperMissing = 'helpers.blockHelperMissing';
|
||||
|
||||
var params = ["depth0"];
|
||||
this.setupParams(0, params);
|
||||
|
||||
var current = this.topStack();
|
||||
params.splice(1, 0, current);
|
||||
|
||||
// Use the options value generated from the invocation
|
||||
params[params.length-1] = 'options';
|
||||
|
||||
this.pushSource("if (!" + this.lastHelper + ") { " + current + " = blockHelperMissing.call(" + params.join(", ") + "); }");
|
||||
},
|
||||
|
||||
// [appendContent]
|
||||
//
|
||||
// On stack, before: ...
|
||||
// On stack, after: ...
|
||||
//
|
||||
// Appends the string value of `content` to the current buffer
|
||||
appendContent: function(content) {
|
||||
if (this.pendingContent) {
|
||||
content = this.pendingContent + content;
|
||||
}
|
||||
if (this.stripNext) {
|
||||
content = content.replace(/^\s+/, '');
|
||||
}
|
||||
|
||||
this.pendingContent = content;
|
||||
},
|
||||
|
||||
// [strip]
|
||||
//
|
||||
// On stack, before: ...
|
||||
// On stack, after: ...
|
||||
//
|
||||
// Removes any trailing whitespace from the prior content node and flags
|
||||
// the next operation for stripping if it is a content node.
|
||||
strip: function() {
|
||||
if (this.pendingContent) {
|
||||
this.pendingContent = this.pendingContent.replace(/\s+$/, '');
|
||||
}
|
||||
this.stripNext = 'strip';
|
||||
},
|
||||
|
||||
// [append]
|
||||
//
|
||||
// On stack, before: value, ...
|
||||
// On stack, after: ...
|
||||
//
|
||||
// Coerces `value` to a String and appends it to the current buffer.
|
||||
//
|
||||
// If `value` is truthy, or 0, it is coerced into a string and appended
|
||||
// Otherwise, the empty string is appended
|
||||
append: function() {
|
||||
// Force anything that is inlined onto the stack so we don't have duplication
|
||||
// when we examine local
|
||||
this.flushInline();
|
||||
var local = this.popStack();
|
||||
this.pushSource("if(" + local + " || " + local + " === 0) { " + this.appendToBuffer(local) + " }");
|
||||
if (this.environment.isSimple) {
|
||||
this.pushSource("else { " + this.appendToBuffer("''") + " }");
|
||||
}
|
||||
},
|
||||
|
||||
// [appendEscaped]
|
||||
//
|
||||
// On stack, before: value, ...
|
||||
// On stack, after: ...
|
||||
//
|
||||
// Escape `value` and append it to the buffer
|
||||
appendEscaped: function() {
|
||||
this.context.aliases.escapeExpression = 'this.escapeExpression';
|
||||
|
||||
this.pushSource(this.appendToBuffer("escapeExpression(" + this.popStack() + ")"));
|
||||
},
|
||||
|
||||
// [getContext]
|
||||
//
|
||||
// On stack, before: ...
|
||||
// On stack, after: ...
|
||||
// Compiler value, after: lastContext=depth
|
||||
//
|
||||
// Set the value of the `lastContext` compiler value to the depth
|
||||
getContext: function(depth) {
|
||||
if(this.lastContext !== depth) {
|
||||
this.lastContext = depth;
|
||||
}
|
||||
},
|
||||
|
||||
// [lookupOnContext]
|
||||
//
|
||||
// On stack, before: ...
|
||||
// On stack, after: currentContext[name], ...
|
||||
//
|
||||
// Looks up the value of `name` on the current context and pushes
|
||||
// it onto the stack.
|
||||
lookupOnContext: function(name) {
|
||||
this.push(this.nameLookup('depth' + this.lastContext, name, 'context'));
|
||||
},
|
||||
|
||||
// [pushContext]
|
||||
//
|
||||
// On stack, before: ...
|
||||
// On stack, after: currentContext, ...
|
||||
//
|
||||
// Pushes the value of the current context onto the stack.
|
||||
pushContext: function() {
|
||||
this.pushStackLiteral('depth' + this.lastContext);
|
||||
},
|
||||
|
||||
// [resolvePossibleLambda]
|
||||
//
|
||||
// On stack, before: value, ...
|
||||
// On stack, after: resolved value, ...
|
||||
//
|
||||
// If the `value` is a lambda, replace it on the stack by
|
||||
// the return value of the lambda
|
||||
resolvePossibleLambda: function() {
|
||||
this.context.aliases.functionType = '"function"';
|
||||
|
||||
this.replaceStack(function(current) {
|
||||
return "typeof " + current + " === functionType ? " + current + ".apply(depth0) : " + current;
|
||||
});
|
||||
},
|
||||
|
||||
// [lookup]
|
||||
//
|
||||
// On stack, before: value, ...
|
||||
// On stack, after: value[name], ...
|
||||
//
|
||||
// Replace the value on the stack with the result of looking
|
||||
// up `name` on `value`
|
||||
lookup: function(name) {
|
||||
this.replaceStack(function(current) {
|
||||
return current + " == null || " + current + " === false ? " + current + " : " + this.nameLookup(current, name, 'context');
|
||||
});
|
||||
},
|
||||
|
||||
// [lookupData]
|
||||
//
|
||||
// On stack, before: ...
|
||||
// On stack, after: data, ...
|
||||
//
|
||||
// Push the data lookup operator
|
||||
lookupData: function() {
|
||||
this.push('data');
|
||||
},
|
||||
|
||||
// [pushStringParam]
|
||||
//
|
||||
// On stack, before: ...
|
||||
// On stack, after: string, currentContext, ...
|
||||
//
|
||||
// This opcode is designed for use in string mode, which
|
||||
// provides the string value of a parameter along with its
|
||||
// depth rather than resolving it immediately.
|
||||
pushStringParam: function(string, type) {
|
||||
this.pushStackLiteral('depth' + this.lastContext);
|
||||
|
||||
this.pushString(type);
|
||||
|
||||
if (typeof string === 'string') {
|
||||
this.pushString(string);
|
||||
} else {
|
||||
this.pushStackLiteral(string);
|
||||
}
|
||||
},
|
||||
|
||||
emptyHash: function() {
|
||||
this.pushStackLiteral('{}');
|
||||
|
||||
if (this.options.stringParams) {
|
||||
this.register('hashTypes', '{}');
|
||||
this.register('hashContexts', '{}');
|
||||
}
|
||||
},
|
||||
pushHash: function() {
|
||||
this.hash = {values: [], types: [], contexts: []};
|
||||
},
|
||||
popHash: function() {
|
||||
var hash = this.hash;
|
||||
this.hash = undefined;
|
||||
|
||||
if (this.options.stringParams) {
|
||||
this.register('hashContexts', '{' + hash.contexts.join(',') + '}');
|
||||
this.register('hashTypes', '{' + hash.types.join(',') + '}');
|
||||
}
|
||||
this.push('{\n ' + hash.values.join(',\n ') + '\n }');
|
||||
},
|
||||
|
||||
// [pushString]
|
||||
//
|
||||
// On stack, before: ...
|
||||
// On stack, after: quotedString(string), ...
|
||||
//
|
||||
// Push a quoted version of `string` onto the stack
|
||||
pushString: function(string) {
|
||||
this.pushStackLiteral(this.quotedString(string));
|
||||
},
|
||||
|
||||
// [push]
|
||||
//
|
||||
// On stack, before: ...
|
||||
// On stack, after: expr, ...
|
||||
//
|
||||
// Push an expression onto the stack
|
||||
push: function(expr) {
|
||||
this.inlineStack.push(expr);
|
||||
return expr;
|
||||
},
|
||||
|
||||
// [pushLiteral]
|
||||
//
|
||||
// On stack, before: ...
|
||||
// On stack, after: value, ...
|
||||
//
|
||||
// Pushes a value onto the stack. This operation prevents
|
||||
// the compiler from creating a temporary variable to hold
|
||||
// it.
|
||||
pushLiteral: function(value) {
|
||||
this.pushStackLiteral(value);
|
||||
},
|
||||
|
||||
// [pushProgram]
|
||||
//
|
||||
// On stack, before: ...
|
||||
// On stack, after: program(guid), ...
|
||||
//
|
||||
// Push a program expression onto the stack. This takes
|
||||
// a compile-time guid and converts it into a runtime-accessible
|
||||
// expression.
|
||||
pushProgram: function(guid) {
|
||||
if (guid != null) {
|
||||
this.pushStackLiteral(this.programExpression(guid));
|
||||
} else {
|
||||
this.pushStackLiteral(null);
|
||||
}
|
||||
},
|
||||
|
||||
// [invokeHelper]
|
||||
//
|
||||
// On stack, before: hash, inverse, program, params..., ...
|
||||
// On stack, after: result of helper invocation
|
||||
//
|
||||
// Pops off the helper's parameters, invokes the helper,
|
||||
// and pushes the helper's return value onto the stack.
|
||||
//
|
||||
// If the helper is not found, `helperMissing` is called.
|
||||
invokeHelper: function(paramSize, name) {
|
||||
this.context.aliases.helperMissing = 'helpers.helperMissing';
|
||||
|
||||
var helper = this.lastHelper = this.setupHelper(paramSize, name, true);
|
||||
var nonHelper = this.nameLookup('depth' + this.lastContext, name, 'context');
|
||||
|
||||
this.push(helper.name + ' || ' + nonHelper);
|
||||
this.replaceStack(function(name) {
|
||||
return name + ' ? ' + name + '.call(' +
|
||||
helper.callParams + ") " + ": helperMissing.call(" +
|
||||
helper.helperMissingParams + ")";
|
||||
});
|
||||
},
|
||||
|
||||
// [invokeKnownHelper]
|
||||
//
|
||||
// On stack, before: hash, inverse, program, params..., ...
|
||||
// On stack, after: result of helper invocation
|
||||
//
|
||||
// This operation is used when the helper is known to exist,
|
||||
// so a `helperMissing` fallback is not required.
|
||||
invokeKnownHelper: function(paramSize, name) {
|
||||
var helper = this.setupHelper(paramSize, name);
|
||||
this.push(helper.name + ".call(" + helper.callParams + ")");
|
||||
},
|
||||
|
||||
// [invokeAmbiguous]
|
||||
//
|
||||
// On stack, before: hash, inverse, program, params..., ...
|
||||
// On stack, after: result of disambiguation
|
||||
//
|
||||
// This operation is used when an expression like `{{foo}}`
|
||||
// is provided, but we don't know at compile-time whether it
|
||||
// is a helper or a path.
|
||||
//
|
||||
// This operation emits more code than the other options,
|
||||
// and can be avoided by passing the `knownHelpers` and
|
||||
// `knownHelpersOnly` flags at compile-time.
|
||||
invokeAmbiguous: function(name, helperCall) {
|
||||
this.context.aliases.functionType = '"function"';
|
||||
|
||||
this.pushStackLiteral('{}'); // Hash value
|
||||
var helper = this.setupHelper(0, name, helperCall);
|
||||
|
||||
var helperName = this.lastHelper = this.nameLookup('helpers', name, 'helper');
|
||||
|
||||
var nonHelper = this.nameLookup('depth' + this.lastContext, name, 'context');
|
||||
var nextStack = this.nextStack();
|
||||
|
||||
this.pushSource('if (' + nextStack + ' = ' + helperName + ') { ' + nextStack + ' = ' + nextStack + '.call(' + helper.callParams + '); }');
|
||||
this.pushSource('else { ' + nextStack + ' = ' + nonHelper + '; ' + nextStack + ' = typeof ' + nextStack + ' === functionType ? ' + nextStack + '.call(' + helper.callParams + ') : ' + nextStack + '; }');
|
||||
},
|
||||
|
||||
// [invokePartial]
|
||||
//
|
||||
// On stack, before: context, ...
|
||||
// On stack after: result of partial invocation
|
||||
//
|
||||
// This operation pops off a context, invokes a partial with that context,
|
||||
// and pushes the result of the invocation back.
|
||||
invokePartial: function(name) {
|
||||
var params = [this.nameLookup('partials', name, 'partial'), "'" + name + "'", this.popStack(), "helpers", "partials"];
|
||||
|
||||
if (this.options.data) {
|
||||
params.push("data");
|
||||
}
|
||||
|
||||
this.context.aliases.self = "this";
|
||||
this.push("self.invokePartial(" + params.join(", ") + ")");
|
||||
},
|
||||
|
||||
// [assignToHash]
|
||||
//
|
||||
// On stack, before: value, hash, ...
|
||||
// On stack, after: hash, ...
|
||||
//
|
||||
// Pops a value and hash off the stack, assigns `hash[key] = value`
|
||||
// and pushes the hash back onto the stack.
|
||||
assignToHash: function(key) {
|
||||
var value = this.popStack(),
|
||||
context,
|
||||
type;
|
||||
|
||||
if (this.options.stringParams) {
|
||||
type = this.popStack();
|
||||
context = this.popStack();
|
||||
}
|
||||
|
||||
var hash = this.hash;
|
||||
if (context) {
|
||||
hash.contexts.push("'" + key + "': " + context);
|
||||
}
|
||||
if (type) {
|
||||
hash.types.push("'" + key + "': " + type);
|
||||
}
|
||||
hash.values.push("'" + key + "': (" + value + ")");
|
||||
},
|
||||
|
||||
// HELPERS
|
||||
|
||||
compiler: JavaScriptCompiler,
|
||||
|
||||
compileChildren: function(environment, options) {
|
||||
var children = environment.children, child, compiler;
|
||||
|
||||
for(var i=0, l=children.length; i<l; i++) {
|
||||
child = children[i];
|
||||
compiler = new this.compiler();
|
||||
|
||||
var index = this.matchExistingProgram(child);
|
||||
|
||||
if (index == null) {
|
||||
this.context.programs.push(''); // Placeholder to prevent name conflicts for nested children
|
||||
index = this.context.programs.length;
|
||||
child.index = index;
|
||||
child.name = 'program' + index;
|
||||
this.context.programs[index] = compiler.compile(child, options, this.context);
|
||||
this.context.environments[index] = child;
|
||||
} else {
|
||||
child.index = index;
|
||||
child.name = 'program' + index;
|
||||
}
|
||||
}
|
||||
},
|
||||
matchExistingProgram: function(child) {
|
||||
for (var i = 0, len = this.context.environments.length; i < len; i++) {
|
||||
var environment = this.context.environments[i];
|
||||
if (environment && environment.equals(child)) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
programExpression: function(guid) {
|
||||
this.context.aliases.self = "this";
|
||||
|
||||
if(guid == null) {
|
||||
return "self.noop";
|
||||
}
|
||||
|
||||
var child = this.environment.children[guid],
|
||||
depths = child.depths.list, depth;
|
||||
|
||||
var programParams = [child.index, child.name, "data"];
|
||||
|
||||
for(var i=0, l = depths.length; i<l; i++) {
|
||||
depth = depths[i];
|
||||
|
||||
if(depth === 1) { programParams.push("depth0"); }
|
||||
else { programParams.push("depth" + (depth - 1)); }
|
||||
}
|
||||
|
||||
return (depths.length === 0 ? "self.program(" : "self.programWithDepth(") + programParams.join(", ") + ")";
|
||||
},
|
||||
|
||||
register: function(name, val) {
|
||||
this.useRegister(name);
|
||||
this.pushSource(name + " = " + val + ";");
|
||||
},
|
||||
|
||||
useRegister: function(name) {
|
||||
if(!this.registers[name]) {
|
||||
this.registers[name] = true;
|
||||
this.registers.list.push(name);
|
||||
}
|
||||
},
|
||||
|
||||
pushStackLiteral: function(item) {
|
||||
return this.push(new Literal(item));
|
||||
},
|
||||
|
||||
pushSource: function(source) {
|
||||
if (this.pendingContent) {
|
||||
this.source.push(this.appendToBuffer(this.quotedString(this.pendingContent)));
|
||||
this.pendingContent = undefined;
|
||||
}
|
||||
|
||||
if (source) {
|
||||
this.source.push(source);
|
||||
}
|
||||
},
|
||||
|
||||
pushStack: function(item) {
|
||||
this.flushInline();
|
||||
|
||||
var stack = this.incrStack();
|
||||
if (item) {
|
||||
this.pushSource(stack + " = " + item + ";");
|
||||
}
|
||||
this.compileStack.push(stack);
|
||||
return stack;
|
||||
},
|
||||
|
||||
replaceStack: function(callback) {
|
||||
var prefix = '',
|
||||
inline = this.isInline(),
|
||||
stack;
|
||||
|
||||
// If we are currently inline then we want to merge the inline statement into the
|
||||
// replacement statement via ','
|
||||
if (inline) {
|
||||
var top = this.popStack(true);
|
||||
|
||||
if (top instanceof Literal) {
|
||||
// Literals do not need to be inlined
|
||||
stack = top.value;
|
||||
} else {
|
||||
// Get or create the current stack name for use by the inline
|
||||
var name = this.stackSlot ? this.topStackName() : this.incrStack();
|
||||
|
||||
prefix = '(' + this.push(name) + ' = ' + top + '),';
|
||||
stack = this.topStack();
|
||||
}
|
||||
} else {
|
||||
stack = this.topStack();
|
||||
}
|
||||
|
||||
var item = callback.call(this, stack);
|
||||
|
||||
if (inline) {
|
||||
if (this.inlineStack.length || this.compileStack.length) {
|
||||
this.popStack();
|
||||
}
|
||||
this.push('(' + prefix + item + ')');
|
||||
} else {
|
||||
// Prevent modification of the context depth variable. Through replaceStack
|
||||
if (!/^stack/.test(stack)) {
|
||||
stack = this.nextStack();
|
||||
}
|
||||
|
||||
this.pushSource(stack + " = (" + prefix + item + ");");
|
||||
}
|
||||
return stack;
|
||||
},
|
||||
|
||||
nextStack: function() {
|
||||
return this.pushStack();
|
||||
},
|
||||
|
||||
incrStack: function() {
|
||||
this.stackSlot++;
|
||||
if(this.stackSlot > this.stackVars.length) { this.stackVars.push("stack" + this.stackSlot); }
|
||||
return this.topStackName();
|
||||
},
|
||||
topStackName: function() {
|
||||
return "stack" + this.stackSlot;
|
||||
},
|
||||
flushInline: function() {
|
||||
var inlineStack = this.inlineStack;
|
||||
if (inlineStack.length) {
|
||||
this.inlineStack = [];
|
||||
for (var i = 0, len = inlineStack.length; i < len; i++) {
|
||||
var entry = inlineStack[i];
|
||||
if (entry instanceof Literal) {
|
||||
this.compileStack.push(entry);
|
||||
} else {
|
||||
this.pushStack(entry);
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
isInline: function() {
|
||||
return this.inlineStack.length;
|
||||
},
|
||||
|
||||
popStack: function(wrapped) {
|
||||
var inline = this.isInline(),
|
||||
item = (inline ? this.inlineStack : this.compileStack).pop();
|
||||
|
||||
if (!wrapped && (item instanceof Literal)) {
|
||||
return item.value;
|
||||
} else {
|
||||
if (!inline) {
|
||||
this.stackSlot--;
|
||||
}
|
||||
return item;
|
||||
}
|
||||
},
|
||||
|
||||
topStack: function(wrapped) {
|
||||
var stack = (this.isInline() ? this.inlineStack : this.compileStack),
|
||||
item = stack[stack.length - 1];
|
||||
|
||||
if (!wrapped && (item instanceof Literal)) {
|
||||
return item.value;
|
||||
} else {
|
||||
return item;
|
||||
}
|
||||
},
|
||||
|
||||
quotedString: function(str) {
|
||||
return '"' + str
|
||||
.replace(/\\/g, '\\\\')
|
||||
.replace(/"/g, '\\"')
|
||||
.replace(/\n/g, '\\n')
|
||||
.replace(/\r/g, '\\r')
|
||||
.replace(/\u2028/g, '\\u2028') // Per Ecma-262 7.3 + 7.8.4
|
||||
.replace(/\u2029/g, '\\u2029') + '"';
|
||||
},
|
||||
|
||||
setupHelper: function(paramSize, name, missingParams) {
|
||||
var params = [];
|
||||
this.setupParams(paramSize, params, missingParams);
|
||||
var foundHelper = this.nameLookup('helpers', name, 'helper');
|
||||
|
||||
return {
|
||||
params: params,
|
||||
name: foundHelper,
|
||||
callParams: ["depth0"].concat(params).join(", "),
|
||||
helperMissingParams: missingParams && ["depth0", this.quotedString(name)].concat(params).join(", ")
|
||||
};
|
||||
},
|
||||
|
||||
// the params and contexts arguments are passed in arrays
|
||||
// to fill in
|
||||
setupParams: function(paramSize, params, useRegister) {
|
||||
var options = [], contexts = [], types = [], param, inverse, program;
|
||||
|
||||
options.push("hash:" + this.popStack());
|
||||
|
||||
inverse = this.popStack();
|
||||
program = this.popStack();
|
||||
|
||||
// Avoid setting fn and inverse if neither are set. This allows
|
||||
// helpers to do a check for `if (options.fn)`
|
||||
if (program || inverse) {
|
||||
if (!program) {
|
||||
this.context.aliases.self = "this";
|
||||
program = "self.noop";
|
||||
}
|
||||
|
||||
if (!inverse) {
|
||||
this.context.aliases.self = "this";
|
||||
inverse = "self.noop";
|
||||
}
|
||||
|
||||
options.push("inverse:" + inverse);
|
||||
options.push("fn:" + program);
|
||||
}
|
||||
|
||||
for(var i=0; i<paramSize; i++) {
|
||||
param = this.popStack();
|
||||
params.push(param);
|
||||
|
||||
if(this.options.stringParams) {
|
||||
types.push(this.popStack());
|
||||
contexts.push(this.popStack());
|
||||
}
|
||||
}
|
||||
|
||||
if (this.options.stringParams) {
|
||||
options.push("contexts:[" + contexts.join(",") + "]");
|
||||
options.push("types:[" + types.join(",") + "]");
|
||||
options.push("hashContexts:hashContexts");
|
||||
options.push("hashTypes:hashTypes");
|
||||
}
|
||||
|
||||
if(this.options.data) {
|
||||
options.push("data:data");
|
||||
}
|
||||
|
||||
options = "{" + options.join(",") + "}";
|
||||
if (useRegister) {
|
||||
this.register('options', options);
|
||||
params.push('options');
|
||||
} else {
|
||||
params.push(options);
|
||||
}
|
||||
return params.join(", ");
|
||||
}
|
||||
};
|
||||
|
||||
var reservedWords = (
|
||||
"break else new var" +
|
||||
" case finally return void" +
|
||||
" catch for switch while" +
|
||||
" continue function this with" +
|
||||
" default if throw" +
|
||||
" delete in try" +
|
||||
" do instanceof typeof" +
|
||||
" abstract enum int short" +
|
||||
" boolean export interface static" +
|
||||
" byte extends long super" +
|
||||
" char final native synchronized" +
|
||||
" class float package throws" +
|
||||
" const goto private transient" +
|
||||
" debugger implements protected volatile" +
|
||||
" double import public let yield"
|
||||
).split(" ");
|
||||
|
||||
var compilerWords = JavaScriptCompiler.RESERVED_WORDS = {};
|
||||
|
||||
for(var i=0, l=reservedWords.length; i<l; i++) {
|
||||
compilerWords[reservedWords[i]] = true;
|
||||
}
|
||||
|
||||
JavaScriptCompiler.isValidJavaScriptVariableName = function(name) {
|
||||
if(!JavaScriptCompiler.RESERVED_WORDS[name] && /^[a-zA-Z_$][0-9a-zA-Z_$]+$/.test(name)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
export default JavaScriptCompiler;
|
||||
@@ -1,15 +1,16 @@
|
||||
exports.attach = function(Handlebars) {
|
||||
import Visitor from "./visitor";
|
||||
|
||||
// BEGIN(BROWSER)
|
||||
export function print(ast) {
|
||||
return new PrintVisitor().accept(ast);
|
||||
}
|
||||
|
||||
Handlebars.print = function(ast) {
|
||||
return new Handlebars.PrintVisitor().accept(ast);
|
||||
};
|
||||
export function PrintVisitor() {
|
||||
this.padding = 0;
|
||||
}
|
||||
|
||||
Handlebars.PrintVisitor = function() { this.padding = 0; };
|
||||
Handlebars.PrintVisitor.prototype = new Handlebars.Visitor();
|
||||
PrintVisitor.prototype = new Visitor();
|
||||
|
||||
Handlebars.PrintVisitor.prototype.pad = function(string, newline) {
|
||||
PrintVisitor.prototype.pad = function(string, newline) {
|
||||
var out = "";
|
||||
|
||||
for(var i=0,l=this.padding; i<l; i++) {
|
||||
@@ -22,10 +23,9 @@ Handlebars.PrintVisitor.prototype.pad = function(string, newline) {
|
||||
return out;
|
||||
};
|
||||
|
||||
Handlebars.PrintVisitor.prototype.program = function(program) {
|
||||
PrintVisitor.prototype.program = function(program) {
|
||||
var out = "",
|
||||
statements = program.statements,
|
||||
inverse = program.inverse,
|
||||
i, l;
|
||||
|
||||
for(i=0, l=statements.length; i<l; i++) {
|
||||
@@ -37,7 +37,7 @@ Handlebars.PrintVisitor.prototype.program = function(program) {
|
||||
return out;
|
||||
};
|
||||
|
||||
Handlebars.PrintVisitor.prototype.block = function(block) {
|
||||
PrintVisitor.prototype.block = function(block) {
|
||||
var out = "";
|
||||
|
||||
out = out + this.pad("BLOCK:");
|
||||
@@ -62,7 +62,7 @@ Handlebars.PrintVisitor.prototype.block = function(block) {
|
||||
return out;
|
||||
};
|
||||
|
||||
Handlebars.PrintVisitor.prototype.mustache = function(mustache) {
|
||||
PrintVisitor.prototype.mustache = function(mustache) {
|
||||
var params = mustache.params, paramStrings = [], hash;
|
||||
|
||||
for(var i=0, l=params.length; i<l; i++) {
|
||||
@@ -76,13 +76,13 @@ Handlebars.PrintVisitor.prototype.mustache = function(mustache) {
|
||||
return this.pad("{{ " + this.accept(mustache.id) + " " + params + hash + " }}");
|
||||
};
|
||||
|
||||
Handlebars.PrintVisitor.prototype.partial = function(partial) {
|
||||
PrintVisitor.prototype.partial = function(partial) {
|
||||
var content = this.accept(partial.partialName);
|
||||
if(partial.context) { content = content + " " + this.accept(partial.context); }
|
||||
return this.pad("{{> " + content + " }}");
|
||||
};
|
||||
|
||||
Handlebars.PrintVisitor.prototype.hash = function(hash) {
|
||||
PrintVisitor.prototype.hash = function(hash) {
|
||||
var pairs = hash.pairs;
|
||||
var joinedPairs = [], left, right;
|
||||
|
||||
@@ -95,19 +95,19 @@ Handlebars.PrintVisitor.prototype.hash = function(hash) {
|
||||
return "HASH{" + joinedPairs.join(", ") + "}";
|
||||
};
|
||||
|
||||
Handlebars.PrintVisitor.prototype.STRING = function(string) {
|
||||
PrintVisitor.prototype.STRING = function(string) {
|
||||
return '"' + string.string + '"';
|
||||
};
|
||||
|
||||
Handlebars.PrintVisitor.prototype.INTEGER = function(integer) {
|
||||
PrintVisitor.prototype.INTEGER = function(integer) {
|
||||
return "INTEGER{" + integer.integer + "}";
|
||||
};
|
||||
|
||||
Handlebars.PrintVisitor.prototype.BOOLEAN = function(bool) {
|
||||
PrintVisitor.prototype.BOOLEAN = function(bool) {
|
||||
return "BOOLEAN{" + bool.bool + "}";
|
||||
};
|
||||
|
||||
Handlebars.PrintVisitor.prototype.ID = function(id) {
|
||||
PrintVisitor.prototype.ID = function(id) {
|
||||
var path = id.parts.join("/");
|
||||
if(id.parts.length > 1) {
|
||||
return "PATH:" + path;
|
||||
@@ -116,23 +116,19 @@ Handlebars.PrintVisitor.prototype.ID = function(id) {
|
||||
}
|
||||
};
|
||||
|
||||
Handlebars.PrintVisitor.prototype.PARTIAL_NAME = function(partialName) {
|
||||
PrintVisitor.prototype.PARTIAL_NAME = function(partialName) {
|
||||
return "PARTIAL:" + partialName.name;
|
||||
};
|
||||
|
||||
Handlebars.PrintVisitor.prototype.DATA = function(data) {
|
||||
PrintVisitor.prototype.DATA = function(data) {
|
||||
return "@" + this.accept(data.id);
|
||||
};
|
||||
|
||||
Handlebars.PrintVisitor.prototype.content = function(content) {
|
||||
PrintVisitor.prototype.content = function(content) {
|
||||
return this.pad("CONTENT[ '" + content.string + "' ]");
|
||||
};
|
||||
|
||||
Handlebars.PrintVisitor.prototype.comment = function(comment) {
|
||||
PrintVisitor.prototype.comment = function(comment) {
|
||||
return this.pad("{{! '" + comment.comment + "' }}");
|
||||
};
|
||||
// END(BROWSER)
|
||||
|
||||
return Handlebars;
|
||||
};
|
||||
|
||||
|
||||
@@ -1,18 +1,11 @@
|
||||
exports.attach = function(Handlebars) {
|
||||
function Visitor() {}
|
||||
|
||||
// BEGIN(BROWSER)
|
||||
Visitor.prototype = {
|
||||
constructor: Visitor,
|
||||
|
||||
Handlebars.Visitor = function() {};
|
||||
|
||||
Handlebars.Visitor.prototype = {
|
||||
accept: function(object) {
|
||||
return this[object.type](object);
|
||||
}
|
||||
};
|
||||
|
||||
// END(BROWSER)
|
||||
|
||||
return Handlebars;
|
||||
};
|
||||
|
||||
|
||||
export default Visitor;
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
|
||||
var errorProps = ['description', 'fileName', 'lineNumber', 'message', 'name', 'number', 'stack'];
|
||||
|
||||
function Exception(/* message */) {
|
||||
var tmp = Error.prototype.constructor.apply(this, arguments);
|
||||
|
||||
// Unfortunately errors are not enumerable in Chrome (at least), so `for prop in tmp` doesn't work.
|
||||
for (var idx = 0; idx < errorProps.length; idx++) {
|
||||
this[errorProps[idx]] = tmp[errorProps[idx]];
|
||||
}
|
||||
}
|
||||
|
||||
Exception.prototype = new Error();
|
||||
|
||||
export default Exception;
|
||||
+124
-97
@@ -1,106 +1,133 @@
|
||||
exports.attach = function(Handlebars) {
|
||||
module Utils from "./utils";
|
||||
import Exception from "./exception";
|
||||
import { COMPILER_REVISION, REVISION_CHANGES } from "./base";
|
||||
|
||||
// BEGIN(BROWSER)
|
||||
export function checkRevision(compilerInfo) {
|
||||
var compilerRevision = compilerInfo && compilerInfo[0] || 1,
|
||||
currentRevision = COMPILER_REVISION;
|
||||
|
||||
Handlebars.VM = {
|
||||
template: function(templateSpec) {
|
||||
// Just add water
|
||||
var container = {
|
||||
escapeExpression: Handlebars.Utils.escapeExpression,
|
||||
invokePartial: Handlebars.VM.invokePartial,
|
||||
programs: [],
|
||||
program: function(i, fn, data) {
|
||||
var programWrapper = this.programs[i];
|
||||
if(data) {
|
||||
programWrapper = Handlebars.VM.program(i, fn, data);
|
||||
} else if (!programWrapper) {
|
||||
programWrapper = this.programs[i] = Handlebars.VM.program(i, fn);
|
||||
}
|
||||
return programWrapper;
|
||||
},
|
||||
merge: function(param, common) {
|
||||
var ret = param || common;
|
||||
|
||||
if (param && common) {
|
||||
ret = {};
|
||||
Handlebars.Utils.extend(ret, common);
|
||||
Handlebars.Utils.extend(ret, param);
|
||||
}
|
||||
return ret;
|
||||
},
|
||||
programWithDepth: Handlebars.VM.programWithDepth,
|
||||
noop: Handlebars.VM.noop,
|
||||
compilerInfo: null
|
||||
};
|
||||
|
||||
return function(context, options) {
|
||||
options = options || {};
|
||||
var result = templateSpec.call(container, Handlebars, context, options.helpers, options.partials, options.data);
|
||||
|
||||
var compilerInfo = container.compilerInfo || [],
|
||||
compilerRevision = compilerInfo[0] || 1,
|
||||
currentRevision = Handlebars.COMPILER_REVISION;
|
||||
|
||||
if (compilerRevision !== currentRevision) {
|
||||
if (compilerRevision < currentRevision) {
|
||||
var runtimeVersions = Handlebars.REVISION_CHANGES[currentRevision],
|
||||
compilerVersions = Handlebars.REVISION_CHANGES[compilerRevision];
|
||||
throw "Template was precompiled with an older version of Handlebars than the current runtime. "+
|
||||
"Please update your precompiler to a newer version ("+runtimeVersions+") or downgrade your runtime to an older version ("+compilerVersions+").";
|
||||
} else {
|
||||
// Use the embedded version info since the runtime doesn't know about this revision yet
|
||||
throw "Template was precompiled with a newer version of Handlebars than the current runtime. "+
|
||||
"Please update your runtime to a newer version ("+compilerInfo[1]+").";
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
||||
},
|
||||
|
||||
programWithDepth: function(i, fn, data /*, $depth */) {
|
||||
var args = Array.prototype.slice.call(arguments, 3);
|
||||
|
||||
var program = function(context, options) {
|
||||
options = options || {};
|
||||
|
||||
return fn.apply(this, [context, options.data || data].concat(args));
|
||||
};
|
||||
program.program = i;
|
||||
program.depth = args.length;
|
||||
return program;
|
||||
},
|
||||
program: function(i, fn, data) {
|
||||
var program = function(context, options) {
|
||||
options = options || {};
|
||||
|
||||
return fn(context, options.data || data);
|
||||
};
|
||||
program.program = i;
|
||||
program.depth = 0;
|
||||
return program;
|
||||
},
|
||||
noop: function() { return ""; },
|
||||
invokePartial: function(partial, name, context, helpers, partials, data) {
|
||||
var options = { helpers: helpers, partials: partials, data: data };
|
||||
|
||||
if(partial === undefined) {
|
||||
throw new Handlebars.Exception("The partial " + name + " could not be found");
|
||||
} else if(partial instanceof Function) {
|
||||
return partial(context, options);
|
||||
} else if (!Handlebars.compile) {
|
||||
throw new Handlebars.Exception("The partial " + name + " could not be compiled when running in runtime-only mode");
|
||||
if (compilerRevision !== currentRevision) {
|
||||
if (compilerRevision < currentRevision) {
|
||||
var runtimeVersions = REVISION_CHANGES[currentRevision],
|
||||
compilerVersions = REVISION_CHANGES[compilerRevision];
|
||||
throw new Error("Template was precompiled with an older version of Handlebars than the current runtime. "+
|
||||
"Please update your precompiler to a newer version ("+runtimeVersions+") or downgrade your runtime to an older version ("+compilerVersions+").");
|
||||
} else {
|
||||
partials[name] = Handlebars.compile(partial, {data: data !== undefined});
|
||||
return partials[name](context, options);
|
||||
// Use the embedded version info since the runtime doesn't know about this revision yet
|
||||
throw new Error("Template was precompiled with a newer version of Handlebars than the current runtime. "+
|
||||
"Please update your runtime to a newer version ("+compilerInfo[1]+").");
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
Handlebars.template = Handlebars.VM.template;
|
||||
// TODO: Remove this line and break up compilePartial
|
||||
|
||||
// END(BROWSER)
|
||||
export function template(templateSpec, env) {
|
||||
if (!env) {
|
||||
throw new Error("No environment passed to template");
|
||||
}
|
||||
|
||||
return Handlebars;
|
||||
// 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 {
|
||||
throw new Exception("The partial " + name + " could not be compiled when running in runtime-only mode");
|
||||
}
|
||||
};
|
||||
|
||||
// Just add water
|
||||
var container = {
|
||||
escapeExpression: Utils.escapeExpression,
|
||||
invokePartial: invokePartialWrapper,
|
||||
programs: [],
|
||||
program: function(i, fn, data) {
|
||||
var programWrapper = this.programs[i];
|
||||
if(data) {
|
||||
programWrapper = program(i, fn, data);
|
||||
} else if (!programWrapper) {
|
||||
programWrapper = this.programs[i] = program(i, fn);
|
||||
}
|
||||
return programWrapper;
|
||||
},
|
||||
merge: function(param, common) {
|
||||
var ret = param || common;
|
||||
|
||||
if (param && common && (param !== common)) {
|
||||
ret = {};
|
||||
Utils.extend(ret, common);
|
||||
Utils.extend(ret, param);
|
||||
}
|
||||
return ret;
|
||||
},
|
||||
programWithDepth: env.VM.programWithDepth,
|
||||
noop: env.VM.noop,
|
||||
compilerInfo: null
|
||||
};
|
||||
|
||||
return function(context, options) {
|
||||
options = options || {};
|
||||
var namespace = options.partial ? options : env,
|
||||
helpers,
|
||||
partials;
|
||||
|
||||
if (!options.partial) {
|
||||
helpers = options.helpers;
|
||||
partials = options.partials;
|
||||
}
|
||||
var result = templateSpec.call(
|
||||
container,
|
||||
namespace, context,
|
||||
helpers,
|
||||
partials,
|
||||
options.data);
|
||||
|
||||
if (!options.partial) {
|
||||
env.VM.checkRevision(container.compilerInfo);
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
||||
}
|
||||
|
||||
export function programWithDepth(i, fn, data /*, $depth */) {
|
||||
var args = Array.prototype.slice.call(arguments, 3);
|
||||
|
||||
var prog = function(context, options) {
|
||||
options = options || {};
|
||||
|
||||
return fn.apply(this, [context, options.data || data].concat(args));
|
||||
};
|
||||
prog.program = i;
|
||||
prog.depth = args.length;
|
||||
return prog;
|
||||
}
|
||||
|
||||
export function program(i, fn, data) {
|
||||
var prog = function(context, options) {
|
||||
options = options || {};
|
||||
|
||||
return fn(context, options.data || data);
|
||||
};
|
||||
prog.program = i;
|
||||
prog.depth = 0;
|
||||
return prog;
|
||||
}
|
||||
|
||||
export function invokePartial(partial, name, context, helpers, partials, data) {
|
||||
var options = { partial: true, helpers: helpers, partials: partials, data: data };
|
||||
|
||||
if(partial === undefined) {
|
||||
throw new Exception("The partial " + name + " could not be found");
|
||||
} else if(partial instanceof Function) {
|
||||
return partial(context, options);
|
||||
}
|
||||
}
|
||||
|
||||
export function noop() { return ""; }
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
// Build out our basic SafeString type
|
||||
function SafeString(string) {
|
||||
this.string = string;
|
||||
}
|
||||
|
||||
SafeString.prototype.toString = function() {
|
||||
return "" + this.string;
|
||||
};
|
||||
|
||||
export default SafeString;
|
||||
@@ -1,11 +0,0 @@
|
||||
module Handlebars
|
||||
module Source
|
||||
def self.bundled_path
|
||||
File.expand_path("../../../dist/handlebars.js", __FILE__)
|
||||
end
|
||||
|
||||
def self.runtime_bundled_path
|
||||
File.expand_path("../../../dist/handlebars.runtime.js", __FILE__)
|
||||
end
|
||||
end
|
||||
end
|
||||
+53
-63
@@ -1,28 +1,5 @@
|
||||
exports.attach = function(Handlebars) {
|
||||
|
||||
var toString = Object.prototype.toString;
|
||||
|
||||
// BEGIN(BROWSER)
|
||||
|
||||
var errorProps = ['description', 'fileName', 'lineNumber', 'message', 'name', 'number', 'stack'];
|
||||
|
||||
Handlebars.Exception = function(message) {
|
||||
var tmp = Error.prototype.constructor.apply(this, arguments);
|
||||
|
||||
// Unfortunately errors are not enumerable in Chrome (at least), so `for prop in tmp` doesn't work.
|
||||
for (var idx = 0; idx < errorProps.length; idx++) {
|
||||
this[errorProps[idx]] = tmp[errorProps[idx]];
|
||||
}
|
||||
};
|
||||
Handlebars.Exception.prototype = new Error();
|
||||
|
||||
// Build out our basic SafeString type
|
||||
Handlebars.SafeString = function(string) {
|
||||
this.string = string;
|
||||
};
|
||||
Handlebars.SafeString.prototype.toString = function() {
|
||||
return this.string.toString();
|
||||
};
|
||||
/*jshint -W004 */
|
||||
import SafeString from "./safe-string";
|
||||
|
||||
var escape = {
|
||||
"&": "&",
|
||||
@@ -36,48 +13,61 @@ var escape = {
|
||||
var badChars = /[&<>"'`]/g;
|
||||
var possible = /[&<>"'`]/;
|
||||
|
||||
var escapeChar = function(chr) {
|
||||
function escapeChar(chr) {
|
||||
return escape[chr] || "&";
|
||||
};
|
||||
}
|
||||
|
||||
Handlebars.Utils = {
|
||||
extend: function(obj, value) {
|
||||
for(var key in value) {
|
||||
if(value.hasOwnProperty(key)) {
|
||||
obj[key] = value[key];
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
escapeExpression: function(string) {
|
||||
// don't escape SafeStrings, since they're already safe
|
||||
if (string instanceof Handlebars.SafeString) {
|
||||
return string.toString();
|
||||
} else if (string == null || string === false) {
|
||||
return "";
|
||||
}
|
||||
|
||||
// Force a string conversion as this will be done by the append regardless and
|
||||
// the regex test will do this transparently behind the scenes, causing issues if
|
||||
// an object's to string has escaped characters in it.
|
||||
string = string.toString();
|
||||
|
||||
if(!possible.test(string)) { return string; }
|
||||
return string.replace(badChars, escapeChar);
|
||||
},
|
||||
|
||||
isEmpty: function(value) {
|
||||
if (!value && value !== 0) {
|
||||
return true;
|
||||
} else if(toString.call(value) === "[object Array]" && value.length === 0) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
export function extend(obj, value) {
|
||||
for(var key in value) {
|
||||
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;
|
||||
};
|
||||
|
||||
// END(BROWSER)
|
||||
|
||||
return Handlebars;
|
||||
};
|
||||
export function escapeExpression(string) {
|
||||
// don't escape SafeStrings, since they're already safe
|
||||
if (string instanceof SafeString) {
|
||||
return string.toString();
|
||||
} else if (!string && string !== 0) {
|
||||
return "";
|
||||
}
|
||||
|
||||
// Force a string conversion as this will be done by the append regardless and
|
||||
// the regex test will do this transparently behind the scenes, causing issues if
|
||||
// an object's to string has escaped characters in it.
|
||||
string = "" + string;
|
||||
|
||||
if(!possible.test(string)) { return string; }
|
||||
return string.replace(badChars, escapeChar);
|
||||
}
|
||||
|
||||
export function isEmpty(value) {
|
||||
if (!value && value !== 0) {
|
||||
return true;
|
||||
} else if (isArray(value) && value.length === 0) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
// USAGE:
|
||||
// var handlebars = require('handlebars');
|
||||
|
||||
// var local = handlebars.create();
|
||||
|
||||
var handlebars = require('../dist/cjs/handlebars').default;
|
||||
|
||||
handlebars.Visitor = require('../dist/cjs/handlebars/compiler/visitor').default;
|
||||
|
||||
var printer = require('../dist/cjs/handlebars/compiler/printer');
|
||||
handlebars.PrintVisitor = printer.PrintVisitor;
|
||||
handlebars.print = printer.print;
|
||||
|
||||
module.exports = handlebars;
|
||||
|
||||
// Publish a Node.js require() handler for .handlebars and .hbs files
|
||||
if (typeof require !== 'undefined' && require.extensions) {
|
||||
var extension = function(module, filename) {
|
||||
var fs = require("fs");
|
||||
var templateString = fs.readFileSync(filename, "utf8");
|
||||
module.exports = handlebars.compile(templateString);
|
||||
};
|
||||
require.extensions[".handlebars"] = extension;
|
||||
require.extensions[".hbs"] = extension;
|
||||
}
|
||||
+38
-12
@@ -1,35 +1,61 @@
|
||||
{
|
||||
"name": "handlebars",
|
||||
"description": "Extension of the Mustache logicless template language",
|
||||
"version": "1.0.12",
|
||||
"barename": "handlebars",
|
||||
"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": [
|
||||
"handlebars mustache template html"
|
||||
"handlebars",
|
||||
"mustache",
|
||||
"template",
|
||||
"html"
|
||||
],
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git://github.com/wycats/handlebars.js.git"
|
||||
"url": "https://github.com/wycats/handlebars.js.git"
|
||||
},
|
||||
"author": "Yehuda Katz",
|
||||
"license": "MIT",
|
||||
"readmeFilename": "README.md",
|
||||
"engines": {
|
||||
"node": ">=0.4.7"
|
||||
},
|
||||
"dependencies": {
|
||||
"optimist": "~0.3",
|
||||
"optimist": "~0.3"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"uglify-js": "~2.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"async": "~0.2.9",
|
||||
"aws-sdk": "~1.5.0",
|
||||
"benchmark": "~1.0",
|
||||
"dust": "~0.3",
|
||||
"jison": "~0.3",
|
||||
"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.7.2",
|
||||
"grunt-contrib-requirejs": "~0.4.1",
|
||||
"grunt-contrib-uglify": "~0.2.2",
|
||||
"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"
|
||||
"mustache": "~0.7.2",
|
||||
"semver": "~2.1.0",
|
||||
"underscore": "~1.5.1"
|
||||
},
|
||||
"main": "lib/handlebars.js",
|
||||
"main": "lib/index.js",
|
||||
"bin": {
|
||||
"handlebars": "bin/handlebars"
|
||||
},
|
||||
"scripts": {
|
||||
"test": "node_modules/.bin/mocha -u qunit spec/qunit_spec.js"
|
||||
},
|
||||
"optionalDependencies": {}
|
||||
"test": "grunt"
|
||||
}
|
||||
}
|
||||
|
||||
+81
-1
@@ -1,7 +1,81 @@
|
||||
# Release Notes
|
||||
|
||||
## Development
|
||||
[Commits](https://github.com/wycats/handlebars.js/compare/v1.0.12...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
|
||||
|
||||
- [#642](https://github.com/wycats/handlebars.js/issues/642) - handlebars 1.1.0 are broken with nodejs
|
||||
|
||||
- Fix release notes link - 17ba258
|
||||
|
||||
[Commits](https://github.com/wycats/handlebars.js/compare/v1.1.0...v1.1.1)
|
||||
|
||||
## v1.1.0 - November 3rd, 2013
|
||||
|
||||
- [#628](https://github.com/wycats/handlebars.js/pull/628) - Convert code to ES6 modules ([@kpdecker](https://api.github.com/users/kpdecker))
|
||||
- [#336](https://github.com/wycats/handlebars.js/pull/336) - Add whitespace control syntax ([@kpdecker](https://api.github.com/users/kpdecker))
|
||||
- [#535](https://github.com/wycats/handlebars.js/pull/535) - Fix for probable JIT error under Safari ([@sorentwo](https://api.github.com/users/sorentwo))
|
||||
- [#483](https://github.com/wycats/handlebars.js/issues/483) - Add first and last @ vars to each helper ([@denniskuczynski](https://api.github.com/users/denniskuczynski))
|
||||
- [#557](https://github.com/wycats/handlebars.js/pull/557) - `\\{{foo}}` escaping only works in some situations ([@dmarcotte](https://api.github.com/users/dmarcotte))
|
||||
- [#552](https://github.com/wycats/handlebars.js/pull/552) - Added BOM removal flag. ([@blessenm](https://api.github.com/users/blessenm))
|
||||
- [#543](https://github.com/wycats/handlebars.js/pull/543) - publish passing master builds to s3 ([@fivetanley](https://api.github.com/users/fivetanley))
|
||||
|
||||
- [#608](https://github.com/wycats/handlebars.js/issues/608) - Add `includeZero` flag to `if` conditional
|
||||
- [#498](https://github.com/wycats/handlebars.js/issues/498) - `Handlebars.compile` fails on empty string although a single blank works fine
|
||||
- [#599](https://github.com/wycats/handlebars.js/issues/599) - lambda helpers only receive options if used with arguments
|
||||
- [#592](https://github.com/wycats/handlebars.js/issues/592) - Optimize array and subprogram performance
|
||||
- [#571](https://github.com/wycats/handlebars.js/issues/571) - uglify upgrade breaks compatibility with older versions of node
|
||||
- [#587](https://github.com/wycats/handlebars.js/issues/587) - Partial inside partial breaks?
|
||||
|
||||
|
||||
Compatibility notes:
|
||||
- The project now includes separate artifacts for AMD, CommonJS, and global objects.
|
||||
- AMD: Users may load the bundled `handlebars.amd.js` or `handlebars.runtime.amd.js` files or load individual modules directly. AMD users should also note that the handlebars object is exposed via the `default` field on the imported object. This [gist](https://gist.github.com/wycats/7417be0dc361a69d5916) provides some discussion of possible compatibility shims.
|
||||
- CommonJS/Node: Node loading occurs as normal via `require`
|
||||
- Globals: The `handlebars.js` and `handlebars.runtime.js` files should behave in the same manner as the v1.0.12 / 1.0.0 release.
|
||||
- Build artifacts have been removed from the repository. [npm][npm], [components/handlebars.js][components], [cdnjs][cdnjs], or the [builds page][builds-page] should now be used as the source of built artifacts.
|
||||
- Context-stored helpers are now always passed the `options` hash. Previously no-argument helpers did not have this argument.
|
||||
|
||||
|
||||
[Commits](https://github.com/wycats/handlebars.js/compare/v1.0.12...v1.1.0)
|
||||
|
||||
## v1.0.12 / 1.0.0 - May 31 2013
|
||||
|
||||
@@ -21,6 +95,7 @@ Compatibility notes:
|
||||
- The parser is now stricter on `{{{`, requiring that the end token be `}}}`. Templates that do not
|
||||
follow this convention should add the additional brace value.
|
||||
- Code that relies on global the namespace being muted when custom helpers or partials are passed will need to explicitly pass an `undefined` value for any helpers that should not be available.
|
||||
- The compiler version has changed. Precompiled templates with 1.0.12 or higher must use the 1.0.0 or higher runtime.
|
||||
|
||||
[Commits](https://github.com/wycats/handlebars.js/compare/v1.0.11...v1.0.12)
|
||||
|
||||
@@ -91,3 +166,8 @@ Use:
|
||||
```js
|
||||
template(context, {helpers: helpers, partials: partials, data: data})
|
||||
```
|
||||
|
||||
[builds-page]: http://builds.handlebarsjs.com.s3.amazonaws.com/index.html
|
||||
[cdnjs]: http://cdnjs.com/libraries/handlebars.js/
|
||||
[components]: https://github.com/components/handlebars.js
|
||||
[npm]: https://npmjs.org/package/handlebars
|
||||
|
||||
@@ -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');
|
||||
@@ -1,101 +0,0 @@
|
||||
require "spec_helper"
|
||||
|
||||
class TestContext
|
||||
class TestModule
|
||||
attr_reader :name, :tests
|
||||
|
||||
def initialize(name)
|
||||
@name = name
|
||||
@tests = []
|
||||
end
|
||||
end
|
||||
|
||||
attr_reader :modules
|
||||
|
||||
def initialize
|
||||
@modules = []
|
||||
end
|
||||
|
||||
def module(name)
|
||||
@modules << TestModule.new(name)
|
||||
end
|
||||
|
||||
def test(name, function)
|
||||
@modules.last.tests << [name, function]
|
||||
end
|
||||
end
|
||||
|
||||
test_context = TestContext.new
|
||||
js_context = Handlebars::Spec::CONTEXT
|
||||
|
||||
Module.new do
|
||||
extend Test::Unit::Assertions
|
||||
|
||||
def self.js_backtrace(context)
|
||||
begin
|
||||
context.eval("throw")
|
||||
rescue V8::JSError => e
|
||||
return e.backtrace(:javascript)
|
||||
end
|
||||
end
|
||||
|
||||
js_context["p"] = proc do |this, str|
|
||||
p str
|
||||
end
|
||||
|
||||
js_context["ok"] = proc do |this, ok, message|
|
||||
js_context["$$RSPEC1$$"] = ok
|
||||
|
||||
result = js_context.eval("!!$$RSPEC1$$")
|
||||
|
||||
message ||= "#{ok} was not truthy"
|
||||
|
||||
unless result
|
||||
backtrace = js_backtrace(js_context)
|
||||
message << "\n#{backtrace.join("\n")}"
|
||||
end
|
||||
|
||||
assert result, message
|
||||
end
|
||||
|
||||
js_context["equals"] = proc do |this, first, second, message|
|
||||
js_context["$$RSPEC1$$"] = first
|
||||
js_context["$$RSPEC2$$"] = second
|
||||
|
||||
result = js_context.eval("$$RSPEC1$$ == $$RSPEC2$$")
|
||||
|
||||
additional_message = "#{first.inspect} did not == #{second.inspect}"
|
||||
message = message ? "#{message} (#{additional_message})" : additional_message
|
||||
|
||||
unless result
|
||||
backtrace = js_backtrace(js_context)
|
||||
message << "\n#{backtrace.join("\n")}"
|
||||
end
|
||||
|
||||
assert result, message
|
||||
end
|
||||
|
||||
js_context["equal"] = js_context["equals"]
|
||||
|
||||
js_context["suite"] = proc do |this, name|
|
||||
test_context.module(name)
|
||||
end
|
||||
|
||||
js_context["test"] = proc do |this, name, function|
|
||||
test_context.test(name, function)
|
||||
end
|
||||
|
||||
local = Regexp.escape(File.expand_path(Dir.pwd))
|
||||
qunit_spec = File.expand_path("../qunit_spec.js", __FILE__)
|
||||
js_context.load(qunit_spec.sub(/^#{local}\//, ''))
|
||||
end
|
||||
|
||||
test_context.modules.each do |mod|
|
||||
describe mod.name do
|
||||
mod.tests.each do |name, function|
|
||||
it name do
|
||||
function.call
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
+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");
|
||||
});
|
||||
});
|
||||
});
|
||||
+189
@@ -0,0 +1,189 @@
|
||||
/*global CompilerContext, Handlebars, beforeEach, shouldCompileTo */
|
||||
global.handlebarsEnv = null;
|
||||
|
||||
beforeEach(function() {
|
||||
global.handlebarsEnv = Handlebars.create();
|
||||
});
|
||||
|
||||
describe("basic context", function() {
|
||||
it("most basic", function() {
|
||||
shouldCompileTo("{{foo}}", { foo: "foo" }, "foo");
|
||||
});
|
||||
|
||||
it("escaping", function() {
|
||||
shouldCompileTo("\\{{foo}}", { foo: "food" }, "{{foo}}");
|
||||
shouldCompileTo("content \\{{foo}}", { foo: "food" }, "content {{foo}}");
|
||||
shouldCompileTo("\\\\{{foo}}", { foo: "food" }, "\\food");
|
||||
shouldCompileTo("content \\\\{{foo}}", { foo: "food" }, "content \\food");
|
||||
shouldCompileTo("\\\\ {{foo}}", { foo: "food" }, "\\\\ food");
|
||||
});
|
||||
|
||||
it("compiling with a basic context", function() {
|
||||
shouldCompileTo("Goodbye\n{{cruel}}\n{{world}}!", {cruel: "cruel", world: "world"}, "Goodbye\ncruel\nworld!",
|
||||
"It works if all the required keys are provided");
|
||||
});
|
||||
|
||||
it("compiling with an undefined context", function() {
|
||||
shouldCompileTo("Goodbye\n{{cruel}}\n{{world.bar}}!", undefined, "Goodbye\n\n!");
|
||||
|
||||
shouldCompileTo("{{#unless foo}}Goodbye{{../test}}{{test2}}{{/unless}}", undefined, "Goodbye");
|
||||
});
|
||||
|
||||
it("comments", function() {
|
||||
shouldCompileTo("{{! Goodbye}}Goodbye\n{{cruel}}\n{{world}}!",
|
||||
{cruel: "cruel", world: "world"}, "Goodbye\ncruel\nworld!",
|
||||
"comments are ignored");
|
||||
});
|
||||
|
||||
it("boolean", function() {
|
||||
var string = "{{#goodbye}}GOODBYE {{/goodbye}}cruel {{world}}!";
|
||||
shouldCompileTo(string, {goodbye: true, world: "world"}, "GOODBYE cruel world!",
|
||||
"booleans show the contents when true");
|
||||
|
||||
shouldCompileTo(string, {goodbye: false, world: "world"}, "cruel world!",
|
||||
"booleans do not show the contents when false");
|
||||
});
|
||||
|
||||
it("zeros", function() {
|
||||
shouldCompileTo("num1: {{num1}}, num2: {{num2}}", {num1: 42, num2: 0},
|
||||
"num1: 42, num2: 0");
|
||||
shouldCompileTo("num: {{.}}", 0, "num: 0");
|
||||
shouldCompileTo("num: {{num1/num2}}", {num1: {num2: 0}}, "num: 0");
|
||||
});
|
||||
|
||||
it("newlines", function() {
|
||||
shouldCompileTo("Alan's\nTest", {}, "Alan's\nTest");
|
||||
shouldCompileTo("Alan's\rTest", {}, "Alan's\rTest");
|
||||
});
|
||||
|
||||
it("escaping text", function() {
|
||||
shouldCompileTo("Awesome's", {}, "Awesome's", "text is escaped so that it doesn't get caught on single quotes");
|
||||
shouldCompileTo("Awesome\\", {}, "Awesome\\", "text is escaped so that the closing quote can't be ignored");
|
||||
shouldCompileTo("Awesome\\\\ foo", {}, "Awesome\\\\ foo", "text is escaped so that it doesn't mess up backslashes");
|
||||
shouldCompileTo("Awesome {{foo}}", {foo: '\\'}, "Awesome \\", "text is escaped so that it doesn't mess up backslashes");
|
||||
shouldCompileTo(' " " ', {}, ' " " ', "double quotes never produce invalid javascript");
|
||||
});
|
||||
|
||||
it("escaping expressions", function() {
|
||||
shouldCompileTo("{{{awesome}}}", {awesome: "&\"\\<>"}, '&\"\\<>',
|
||||
"expressions with 3 handlebars aren't escaped");
|
||||
|
||||
shouldCompileTo("{{&awesome}}", {awesome: "&\"\\<>"}, '&\"\\<>',
|
||||
"expressions with {{& handlebars aren't escaped");
|
||||
|
||||
shouldCompileTo("{{awesome}}", {awesome: "&\"'`\\<>"}, '&"'`\\<>',
|
||||
"by default expressions should be escaped");
|
||||
|
||||
shouldCompileTo("{{awesome}}", {awesome: "Escaped, <b> looks like: <b>"}, 'Escaped, <b> looks like: &lt;b&gt;',
|
||||
"escaping should properly handle amperstands");
|
||||
});
|
||||
|
||||
it("functions returning safestrings shouldn't be escaped", function() {
|
||||
var hash = {awesome: function() { return new Handlebars.SafeString("&\"\\<>"); }};
|
||||
shouldCompileTo("{{awesome}}", hash, '&\"\\<>',
|
||||
"functions returning safestrings aren't escaped");
|
||||
});
|
||||
|
||||
it("functions", function() {
|
||||
shouldCompileTo("{{awesome}}", {awesome: function() { return "Awesome"; }}, "Awesome",
|
||||
"functions are called and render their output");
|
||||
shouldCompileTo("{{awesome}}", {awesome: function() { return this.more; }, more: "More awesome"}, "More awesome",
|
||||
"functions are bound to the context");
|
||||
});
|
||||
|
||||
it("functions with context argument", function() {
|
||||
shouldCompileTo("{{awesome frank}}",
|
||||
{awesome: function(context) { return context; },
|
||||
frank: "Frank"},
|
||||
"Frank", "functions are called with context arguments");
|
||||
});
|
||||
|
||||
it("block functions with context argument", function() {
|
||||
shouldCompileTo("{{#awesome 1}}inner {{.}}{{/awesome}}",
|
||||
{awesome: function(context, options) { return options.fn(context); }},
|
||||
"inner 1", "block functions are called with context and options");
|
||||
});
|
||||
|
||||
it("block functions without context argument", function() {
|
||||
shouldCompileTo("{{#awesome}}inner{{/awesome}}",
|
||||
{awesome: function(options) { return options.fn(this); }},
|
||||
"inner", "block functions are called with options");
|
||||
});
|
||||
|
||||
|
||||
it("paths with hyphens", function() {
|
||||
shouldCompileTo("{{foo-bar}}", {"foo-bar": "baz"}, "baz", "Paths can contain hyphens (-)");
|
||||
shouldCompileTo("{{foo.foo-bar}}", {foo: {"foo-bar": "baz"}}, "baz", "Paths can contain hyphens (-)");
|
||||
shouldCompileTo("{{foo/foo-bar}}", {foo: {"foo-bar": "baz"}}, "baz", "Paths can contain hyphens (-)");
|
||||
});
|
||||
|
||||
it("nested paths", function() {
|
||||
shouldCompileTo("Goodbye {{alan/expression}} world!", {alan: {expression: "beautiful"}},
|
||||
"Goodbye beautiful world!", "Nested paths access nested objects");
|
||||
});
|
||||
|
||||
it("nested paths with empty string value", function() {
|
||||
shouldCompileTo("Goodbye {{alan/expression}} world!", {alan: {expression: ""}},
|
||||
"Goodbye world!", "Nested paths access nested objects with empty string");
|
||||
});
|
||||
|
||||
it("literal paths", function() {
|
||||
shouldCompileTo("Goodbye {{[@alan]/expression}} world!", {"@alan": {expression: "beautiful"}},
|
||||
"Goodbye beautiful world!", "Literal paths can be used");
|
||||
shouldCompileTo("Goodbye {{[foo bar]/expression}} world!", {"foo bar": {expression: "beautiful"}},
|
||||
"Goodbye beautiful world!", "Literal paths can be used");
|
||||
});
|
||||
|
||||
it('literal references', function() {
|
||||
shouldCompileTo("Goodbye {{[foo bar]}} world!", {"foo bar": "beautiful"},
|
||||
"Goodbye beautiful world!", "Literal paths can be used");
|
||||
});
|
||||
|
||||
it("that current context path ({{.}}) doesn't hit helpers", function() {
|
||||
shouldCompileTo("test: {{.}}", [null, {helper: "awesome"}], "test: ");
|
||||
});
|
||||
|
||||
it("complex but empty paths", function() {
|
||||
shouldCompileTo("{{person/name}}", {person: {name: null}}, "");
|
||||
shouldCompileTo("{{person/name}}", {person: {}}, "");
|
||||
});
|
||||
|
||||
it("this keyword in paths", function() {
|
||||
var string = "{{#goodbyes}}{{this}}{{/goodbyes}}";
|
||||
var hash = {goodbyes: ["goodbye", "Goodbye", "GOODBYE"]};
|
||||
shouldCompileTo(string, hash, "goodbyeGoodbyeGOODBYE",
|
||||
"This keyword in paths evaluates to current context");
|
||||
|
||||
string = "{{#hellos}}{{this/text}}{{/hellos}}";
|
||||
hash = {hellos: [{text: "hello"}, {text: "Hello"}, {text: "HELLO"}]};
|
||||
shouldCompileTo(string, hash, "helloHelloHELLO", "This keyword evaluates in more complex paths");
|
||||
});
|
||||
|
||||
it("this keyword nested inside path", function() {
|
||||
var string = "{{#hellos}}{{text/this/foo}}{{/hellos}}";
|
||||
shouldThrow(function() {
|
||||
CompilerContext.compile(string);
|
||||
}, Error);
|
||||
});
|
||||
|
||||
it("this keyword in helpers", function() {
|
||||
var helpers = {foo: function(value) {
|
||||
return 'bar ' + value;
|
||||
}};
|
||||
var string = "{{#goodbyes}}{{foo this}}{{/goodbyes}}";
|
||||
var hash = {goodbyes: ["goodbye", "Goodbye", "GOODBYE"]};
|
||||
shouldCompileTo(string, [hash, helpers], "bar goodbyebar Goodbyebar GOODBYE",
|
||||
"This keyword in paths evaluates to current context");
|
||||
|
||||
string = "{{#hellos}}{{foo this/text}}{{/hellos}}";
|
||||
hash = {hellos: [{text: "hello"}, {text: "Hello"}, {text: "HELLO"}]};
|
||||
shouldCompileTo(string, [hash, helpers], "bar hellobar Hellobar HELLO", "This keyword evaluates in more complex paths");
|
||||
});
|
||||
|
||||
it("this keyword nested inside helpers param", function() {
|
||||
var string = "{{#hellos}}{{foo text/this/foo}}{{/hellos}}";
|
||||
shouldThrow(function() {
|
||||
CompilerContext.compile(string);
|
||||
}, Error);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,86 @@
|
||||
/*global CompilerContext, shouldCompileTo */
|
||||
describe('blocks', function() {
|
||||
it("array", function() {
|
||||
var string = "{{#goodbyes}}{{text}}! {{/goodbyes}}cruel {{world}}!";
|
||||
var hash = {goodbyes: [{text: "goodbye"}, {text: "Goodbye"}, {text: "GOODBYE"}], world: "world"};
|
||||
shouldCompileTo(string, hash, "goodbye! Goodbye! GOODBYE! cruel world!",
|
||||
"Arrays iterate over the contents when not empty");
|
||||
|
||||
shouldCompileTo(string, {goodbyes: [], world: "world"}, "cruel world!",
|
||||
"Arrays ignore the contents when empty");
|
||||
|
||||
});
|
||||
|
||||
it("array with @index", function() {
|
||||
var string = "{{#goodbyes}}{{@index}}. {{text}}! {{/goodbyes}}cruel {{world}}!";
|
||||
var hash = {goodbyes: [{text: "goodbye"}, {text: "Goodbye"}, {text: "GOODBYE"}], world: "world"};
|
||||
|
||||
var template = CompilerContext.compile(string);
|
||||
var result = template(hash);
|
||||
|
||||
equal(result, "0. goodbye! 1. Goodbye! 2. GOODBYE! cruel world!", "The @index variable is used");
|
||||
});
|
||||
|
||||
it("empty block", function() {
|
||||
var string = "{{#goodbyes}}{{/goodbyes}}cruel {{world}}!";
|
||||
var hash = {goodbyes: [{text: "goodbye"}, {text: "Goodbye"}, {text: "GOODBYE"}], world: "world"};
|
||||
shouldCompileTo(string, hash, "cruel world!",
|
||||
"Arrays iterate over the contents when not empty");
|
||||
|
||||
shouldCompileTo(string, {goodbyes: [], world: "world"}, "cruel world!",
|
||||
"Arrays ignore the contents when empty");
|
||||
});
|
||||
|
||||
it("block with complex lookup", function() {
|
||||
var string = "{{#goodbyes}}{{text}} cruel {{../name}}! {{/goodbyes}}";
|
||||
var hash = {name: "Alan", goodbyes: [{text: "goodbye"}, {text: "Goodbye"}, {text: "GOODBYE"}]};
|
||||
|
||||
shouldCompileTo(string, hash, "goodbye cruel Alan! Goodbye cruel Alan! GOODBYE cruel Alan! ",
|
||||
"Templates can access variables in contexts up the stack with relative path syntax");
|
||||
});
|
||||
|
||||
it("block with complex lookup using nested context", function() {
|
||||
var string = "{{#goodbyes}}{{text}} cruel {{foo/../name}}! {{/goodbyes}}";
|
||||
|
||||
shouldThrow(function() {
|
||||
CompilerContext.compile(string);
|
||||
}, Error);
|
||||
});
|
||||
|
||||
it("block with deep nested complex lookup", function() {
|
||||
var string = "{{#outer}}Goodbye {{#inner}}cruel {{../../omg}}{{/inner}}{{/outer}}";
|
||||
var hash = {omg: "OMG!", outer: [{ inner: [{ text: "goodbye" }] }] };
|
||||
|
||||
shouldCompileTo(string, hash, "Goodbye cruel OMG!");
|
||||
});
|
||||
|
||||
describe('inverted sections', function() {
|
||||
it("inverted sections with unset value", function() {
|
||||
var string = "{{#goodbyes}}{{this}}{{/goodbyes}}{{^goodbyes}}Right On!{{/goodbyes}}";
|
||||
var hash = {};
|
||||
shouldCompileTo(string, hash, "Right On!", "Inverted section rendered when value isn't set.");
|
||||
});
|
||||
|
||||
it("inverted section with false value", function() {
|
||||
var string = "{{#goodbyes}}{{this}}{{/goodbyes}}{{^goodbyes}}Right On!{{/goodbyes}}";
|
||||
var hash = {goodbyes: false};
|
||||
shouldCompileTo(string, hash, "Right On!", "Inverted section rendered when value is false.");
|
||||
});
|
||||
|
||||
it("inverted section with empty set", function() {
|
||||
var string = "{{#goodbyes}}{{this}}{{/goodbyes}}{{^goodbyes}}Right On!{{/goodbyes}}";
|
||||
var hash = {goodbyes: []};
|
||||
shouldCompileTo(string, hash, "Right On!", "Inverted section rendered when value is empty set.");
|
||||
});
|
||||
|
||||
it("block inverted sections", function() {
|
||||
shouldCompileTo("{{#people}}{{name}}{{^}}{{none}}{{/people}}", {none: "No people"},
|
||||
"No people");
|
||||
});
|
||||
|
||||
it("block inverted sections with empty arrays", function() {
|
||||
shouldCompileTo("{{#people}}{{name}}{{^}}{{none}}{{/people}}", {none: "No people", people: []},
|
||||
"No people");
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,199 @@
|
||||
/*global CompilerContext, shouldCompileTo, compileWithPartials */
|
||||
describe('builtin helpers', function() {
|
||||
describe('#if', function() {
|
||||
it("if", function() {
|
||||
var string = "{{#if goodbye}}GOODBYE {{/if}}cruel {{world}}!";
|
||||
shouldCompileTo(string, {goodbye: true, world: "world"}, "GOODBYE cruel world!",
|
||||
"if with boolean argument shows the contents when true");
|
||||
shouldCompileTo(string, {goodbye: "dummy", world: "world"}, "GOODBYE cruel world!",
|
||||
"if with string argument shows the contents");
|
||||
shouldCompileTo(string, {goodbye: false, world: "world"}, "cruel world!",
|
||||
"if with boolean argument does not show the contents when false");
|
||||
shouldCompileTo(string, {world: "world"}, "cruel world!",
|
||||
"if with undefined does not show the contents");
|
||||
shouldCompileTo(string, {goodbye: ['foo'], world: "world"}, "GOODBYE cruel world!",
|
||||
"if with non-empty array shows the contents");
|
||||
shouldCompileTo(string, {goodbye: [], world: "world"}, "cruel world!",
|
||||
"if with empty array does not show the contents");
|
||||
shouldCompileTo(string, {goodbye: 0, world: "world"}, "cruel world!",
|
||||
"if with zero does not show the contents");
|
||||
shouldCompileTo("{{#if goodbye includeZero=true}}GOODBYE {{/if}}cruel {{world}}!",
|
||||
{goodbye: 0, world: "world"}, "GOODBYE cruel world!",
|
||||
"if with zero does not show the contents");
|
||||
});
|
||||
|
||||
it("if with function argument", function() {
|
||||
var string = "{{#if goodbye}}GOODBYE {{/if}}cruel {{world}}!";
|
||||
shouldCompileTo(string, {goodbye: function() {return true;}, world: "world"}, "GOODBYE cruel world!",
|
||||
"if with function shows the contents when function returns true");
|
||||
shouldCompileTo(string, {goodbye: function() {return this.world;}, world: "world"}, "GOODBYE cruel world!",
|
||||
"if with function shows the contents when function returns string");
|
||||
shouldCompileTo(string, {goodbye: function() {return false;}, world: "world"}, "cruel world!",
|
||||
"if with function does not show the contents when returns false");
|
||||
shouldCompileTo(string, {goodbye: function() {return this.foo;}, world: "world"}, "cruel world!",
|
||||
"if with function does not show the contents when returns undefined");
|
||||
});
|
||||
});
|
||||
|
||||
describe('#with', function() {
|
||||
it("with", function() {
|
||||
var string = "{{#with person}}{{first}} {{last}}{{/with}}";
|
||||
shouldCompileTo(string, {person: {first: "Alan", last: "Johnson"}}, "Alan Johnson");
|
||||
});
|
||||
it("with with function argument", function() {
|
||||
var string = "{{#with person}}{{first}} {{last}}{{/with}}";
|
||||
shouldCompileTo(string, {person: function() { return {first: "Alan", last: "Johnson"};}}, "Alan Johnson");
|
||||
});
|
||||
});
|
||||
|
||||
describe('#each', function() {
|
||||
beforeEach(function() {
|
||||
handlebarsEnv.registerHelper('detectDataInsideEach', function(options) {
|
||||
return options.data && options.data.exclaim;
|
||||
});
|
||||
});
|
||||
|
||||
it("each", function() {
|
||||
var string = "{{#each goodbyes}}{{text}}! {{/each}}cruel {{world}}!";
|
||||
var hash = {goodbyes: [{text: "goodbye"}, {text: "Goodbye"}, {text: "GOODBYE"}], world: "world"};
|
||||
shouldCompileTo(string, hash, "goodbye! Goodbye! GOODBYE! cruel world!",
|
||||
"each with array argument iterates over the contents when not empty");
|
||||
shouldCompileTo(string, {goodbyes: [], world: "world"}, "cruel world!",
|
||||
"each with array argument ignores the contents when empty");
|
||||
});
|
||||
|
||||
it("each with an object and @key", function() {
|
||||
var string = "{{#each goodbyes}}{{@key}}. {{text}}! {{/each}}cruel {{world}}!";
|
||||
var hash = {goodbyes: {"<b>#1</b>": {text: "goodbye"}, 2: {text: "GOODBYE"}}, world: "world"};
|
||||
|
||||
// Object property iteration order is undefined according to ECMA spec,
|
||||
// so we need to check both possible orders
|
||||
// @see http://stackoverflow.com/questions/280713/elements-order-in-a-for-in-loop
|
||||
var actual = compileWithPartials(string, hash);
|
||||
var expected1 = "<b>#1</b>. goodbye! 2. GOODBYE! cruel world!";
|
||||
var expected2 = "2. GOODBYE! <b>#1</b>. goodbye! cruel world!";
|
||||
|
||||
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");
|
||||
});
|
||||
|
||||
it("each with @index", function() {
|
||||
var string = "{{#each goodbyes}}{{@index}}. {{text}}! {{/each}}cruel {{world}}!";
|
||||
var hash = {goodbyes: [{text: "goodbye"}, {text: "Goodbye"}, {text: "GOODBYE"}], world: "world"};
|
||||
|
||||
var template = CompilerContext.compile(string);
|
||||
var result = template(hash);
|
||||
|
||||
equal(result, "0. goodbye! 1. Goodbye! 2. GOODBYE! cruel world!", "The @index variable is used");
|
||||
});
|
||||
|
||||
it("each with nested @index", function() {
|
||||
var string = "{{#each goodbyes}}{{@index}}. {{text}}! {{#each ../goodbyes}}{{@index}} {{/each}}After {{@index}} {{/each}}{{@index}}cruel {{world}}!";
|
||||
var hash = {goodbyes: [{text: "goodbye"}, {text: "Goodbye"}, {text: "GOODBYE"}], world: "world"};
|
||||
|
||||
var template = CompilerContext.compile(string);
|
||||
var result = template(hash);
|
||||
|
||||
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"};
|
||||
|
||||
var template = CompilerContext.compile(string);
|
||||
var result = template(hash);
|
||||
|
||||
equal(result, "goodbye! cruel world!", "The @first variable is used");
|
||||
});
|
||||
|
||||
it("each with nested @first", function() {
|
||||
var string = "{{#each goodbyes}}({{#if @first}}{{text}}! {{/if}}{{#each ../goodbyes}}{{#if @first}}{{text}}!{{/if}}{{/each}}{{#if @first}} {{text}}!{{/if}}) {{/each}}cruel {{world}}!";
|
||||
var hash = {goodbyes: [{text: "goodbye"}, {text: "Goodbye"}, {text: "GOODBYE"}], world: "world"};
|
||||
|
||||
var template = CompilerContext.compile(string);
|
||||
var result = template(hash);
|
||||
|
||||
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"};
|
||||
|
||||
var template = CompilerContext.compile(string);
|
||||
var result = template(hash);
|
||||
|
||||
equal(result, "GOODBYE! cruel world!", "The @last variable is used");
|
||||
});
|
||||
|
||||
it("each with nested @last", function() {
|
||||
var string = "{{#each goodbyes}}({{#if @last}}{{text}}! {{/if}}{{#each ../goodbyes}}{{#if @last}}{{text}}!{{/if}}{{/each}}{{#if @last}} {{text}}!{{/if}}) {{/each}}cruel {{world}}!";
|
||||
var hash = {goodbyes: [{text: "goodbye"}, {text: "Goodbye"}, {text: "GOODBYE"}], world: "world"};
|
||||
|
||||
var template = CompilerContext.compile(string);
|
||||
var result = template(hash);
|
||||
|
||||
equal(result, "(GOODBYE!) (GOODBYE!) (GOODBYE! GOODBYE! GOODBYE!) cruel world!", "The @last variable is used");
|
||||
});
|
||||
|
||||
it("each with function argument", function() {
|
||||
var string = "{{#each goodbyes}}{{text}}! {{/each}}cruel {{world}}!";
|
||||
var hash = {goodbyes: function () { return [{text: "goodbye"}, {text: "Goodbye"}, {text: "GOODBYE"}];}, world: "world"};
|
||||
shouldCompileTo(string, hash, "goodbye! Goodbye! GOODBYE! cruel world!",
|
||||
"each with array function argument iterates over the contents when not empty");
|
||||
shouldCompileTo(string, {goodbyes: [], world: "world"}, "cruel world!",
|
||||
"each with array function argument ignores the contents when empty");
|
||||
});
|
||||
|
||||
it("data passed to helpers", function() {
|
||||
var string = "{{#each letters}}{{this}}{{detectDataInsideEach}}{{/each}}";
|
||||
var hash = {letters: ['a', 'b', 'c']};
|
||||
|
||||
var template = CompilerContext.compile(string);
|
||||
var result = template(hash, {
|
||||
data: {
|
||||
exclaim: '!'
|
||||
}
|
||||
});
|
||||
equal(result, 'a!b!c!', 'should output data');
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
it("#log", function() {
|
||||
|
||||
var string = "{{log blah}}";
|
||||
var hash = { blah: "whee" };
|
||||
|
||||
var levelArg, logArg;
|
||||
handlebarsEnv.log = function(level, arg){ levelArg = level, logArg = arg; };
|
||||
|
||||
shouldCompileTo(string, hash, "", "log should not display");
|
||||
equals(1, levelArg, "should call log with 1");
|
||||
equals("whee", logArg, "should call log with 'whee'");
|
||||
});
|
||||
|
||||
});
|
||||
+239
@@ -0,0 +1,239 @@
|
||||
/*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});
|
||||
|
||||
var helpers = {
|
||||
hello: function(options) {
|
||||
return options.data.adjective + " " + this.noun;
|
||||
}
|
||||
};
|
||||
|
||||
var result = template({noun: "cat"}, {helpers: helpers, data: {adjective: "happy"}});
|
||||
equals("happy cat", result, "Data output by helper");
|
||||
});
|
||||
|
||||
it("data can be looked up via @foo", function() {
|
||||
var template = CompilerContext.compile("{{@hello}}");
|
||||
var result = template({}, { data: { hello: "hello" } });
|
||||
equals("hello", result, "@foo retrieves template data");
|
||||
});
|
||||
|
||||
it("deep @foo triggers automatic top-level data", function() {
|
||||
var template = CompilerContext.compile('{{#let world="world"}}{{#if foo}}{{#if foo}}Hello {{@world}}{{/if}}{{/if}}{{/let}}');
|
||||
|
||||
var helpers = Handlebars.createFrame(handlebarsEnv.helpers);
|
||||
|
||||
helpers.let = function(options) {
|
||||
var frame = Handlebars.createFrame(options.data);
|
||||
|
||||
for (var prop in options.hash) {
|
||||
frame[prop] = options.hash[prop];
|
||||
}
|
||||
return options.fn(this, { data: frame });
|
||||
};
|
||||
|
||||
var result = template({ foo: true }, { helpers: helpers });
|
||||
equals("Hello world", result, "Automatic data was triggered");
|
||||
});
|
||||
|
||||
it("parameter data can be looked up via @foo", function() {
|
||||
var template = CompilerContext.compile("{{hello @world}}");
|
||||
var helpers = {
|
||||
hello: function(noun) {
|
||||
return "Hello " + noun;
|
||||
}
|
||||
};
|
||||
|
||||
var result = template({}, { helpers: helpers, data: { world: "world" } });
|
||||
equals("Hello world", result, "@foo as a parameter retrieves template data");
|
||||
});
|
||||
|
||||
it("hash values can be looked up via @foo", function() {
|
||||
var template = CompilerContext.compile("{{hello noun=@world}}");
|
||||
var helpers = {
|
||||
hello: function(options) {
|
||||
return "Hello " + options.hash.noun;
|
||||
}
|
||||
};
|
||||
|
||||
var result = template({}, { helpers: helpers, data: { world: "world" } });
|
||||
equals("Hello world", result, "@foo as a parameter retrieves template data");
|
||||
});
|
||||
|
||||
it("nested parameter data can be looked up via @foo.bar", function() {
|
||||
var template = CompilerContext.compile("{{hello @world.bar}}");
|
||||
var helpers = {
|
||||
hello: function(noun) {
|
||||
return "Hello " + noun;
|
||||
}
|
||||
};
|
||||
|
||||
var result = template({}, { helpers: helpers, data: { world: {bar: "world" } } });
|
||||
equals("Hello world", result, "@foo as a parameter retrieves template data");
|
||||
});
|
||||
|
||||
it("nested parameter data does not fail with @world.bar", function() {
|
||||
var template = CompilerContext.compile("{{hello @world.bar}}");
|
||||
var helpers = {
|
||||
hello: function(noun) {
|
||||
return "Hello " + noun;
|
||||
}
|
||||
};
|
||||
|
||||
var result = template({}, { helpers: helpers, data: { foo: {bar: "world" } } });
|
||||
equals("Hello undefined", result, "@foo as a parameter retrieves template data");
|
||||
});
|
||||
|
||||
it("parameter data throws when using this scope references", function() {
|
||||
var string = "{{#goodbyes}}{{text}} cruel {{@./name}}! {{/goodbyes}}";
|
||||
|
||||
shouldThrow(function() {
|
||||
CompilerContext.compile(string);
|
||||
}, Error);
|
||||
});
|
||||
|
||||
it("parameter data throws when using parent scope references", function() {
|
||||
var string = "{{#goodbyes}}{{text}} cruel {{@../name}}! {{/goodbyes}}";
|
||||
|
||||
shouldThrow(function() {
|
||||
CompilerContext.compile(string);
|
||||
}, Error);
|
||||
});
|
||||
|
||||
it("parameter data throws when using complex scope references", function() {
|
||||
var string = "{{#goodbyes}}{{text}} cruel {{@foo/../name}}! {{/goodbyes}}";
|
||||
|
||||
shouldThrow(function() {
|
||||
CompilerContext.compile(string);
|
||||
}, Error);
|
||||
});
|
||||
|
||||
it("data is inherited downstream", function() {
|
||||
var template = CompilerContext.compile("{{#let foo=1 bar=2}}{{#let foo=bar.baz}}{{@bar}}{{@foo}}{{/let}}{{@foo}}{{/let}}", { data: true });
|
||||
var helpers = {
|
||||
let: function(options) {
|
||||
var frame = Handlebars.createFrame(options.data);
|
||||
for (var prop in options.hash) {
|
||||
frame[prop] = options.hash[prop];
|
||||
}
|
||||
return options.fn(this, {data: frame});
|
||||
}
|
||||
};
|
||||
|
||||
var result = template({ bar: { baz: "hello world" } }, { helpers: helpers, data: {} });
|
||||
equals("2hello world1", result, "data variables are inherited downstream");
|
||||
});
|
||||
|
||||
it("passing in data to a compiled function that expects data - works with helpers in partials", function() {
|
||||
var template = CompilerContext.compile("{{>my_partial}}", {data: true});
|
||||
|
||||
var partials = {
|
||||
my_partial: CompilerContext.compile("{{hello}}", {data: true})
|
||||
};
|
||||
|
||||
var helpers = {
|
||||
hello: function(options) {
|
||||
return options.data.adjective + " " + this.noun;
|
||||
}
|
||||
};
|
||||
|
||||
var result = template({noun: "cat"}, {helpers: helpers, partials: partials, data: {adjective: "happy"}});
|
||||
equals("happy cat", result, "Data output by helper inside partial");
|
||||
});
|
||||
|
||||
it("passing in data to a compiled function that expects data - works with helpers and parameters", function() {
|
||||
var template = CompilerContext.compile("{{hello world}}", {data: true});
|
||||
|
||||
var helpers = {
|
||||
hello: function(noun, options) {
|
||||
return options.data.adjective + " " + noun + (this.exclaim ? "!" : "");
|
||||
}
|
||||
};
|
||||
|
||||
var result = template({exclaim: true, world: "world"}, {helpers: helpers, data: {adjective: "happy"}});
|
||||
equals("happy world!", result, "Data output by helper");
|
||||
});
|
||||
|
||||
it("passing in data to a compiled function that expects data - works with block helpers", function() {
|
||||
var template = CompilerContext.compile("{{#hello}}{{world}}{{/hello}}", {data: true});
|
||||
|
||||
var helpers = {
|
||||
hello: function(options) {
|
||||
return options.fn(this);
|
||||
},
|
||||
world: function(options) {
|
||||
return options.data.adjective + " world" + (this.exclaim ? "!" : "");
|
||||
}
|
||||
};
|
||||
|
||||
var result = template({exclaim: true}, {helpers: helpers, data: {adjective: "happy"}});
|
||||
equals("happy world!", result, "Data output by helper");
|
||||
});
|
||||
|
||||
it("passing in data to a compiled function that expects data - works with block helpers that use ..", function() {
|
||||
var template = CompilerContext.compile("{{#hello}}{{world ../zomg}}{{/hello}}", {data: true});
|
||||
|
||||
var helpers = {
|
||||
hello: function(options) {
|
||||
return options.fn({exclaim: "?"});
|
||||
},
|
||||
world: function(thing, options) {
|
||||
return options.data.adjective + " " + thing + (this.exclaim || "");
|
||||
}
|
||||
};
|
||||
|
||||
var result = template({exclaim: true, zomg: "world"}, {helpers: helpers, data: {adjective: "happy"}});
|
||||
equals("happy world?", result, "Data output by helper");
|
||||
});
|
||||
|
||||
it("passing in data to a compiled function that expects data - data is passed to with block helpers where children use ..", function() {
|
||||
var template = CompilerContext.compile("{{#hello}}{{world ../zomg}}{{/hello}}", {data: true});
|
||||
|
||||
var helpers = {
|
||||
hello: function(options) {
|
||||
return options.data.accessData + " " + options.fn({exclaim: "?"});
|
||||
},
|
||||
world: function(thing, options) {
|
||||
return options.data.adjective + " " + thing + (this.exclaim || "");
|
||||
}
|
||||
};
|
||||
|
||||
var result = template({exclaim: true, zomg: "world"}, {helpers: helpers, data: {adjective: "happy", accessData: "#win"}});
|
||||
equals("#win happy world?", result, "Data output by helper");
|
||||
});
|
||||
|
||||
it("you can override inherited data when invoking a helper", function() {
|
||||
var template = CompilerContext.compile("{{#hello}}{{world zomg}}{{/hello}}", {data: true});
|
||||
|
||||
var helpers = {
|
||||
hello: function(options) {
|
||||
return options.fn({exclaim: "?", zomg: "world"}, { data: {adjective: "sad"} });
|
||||
},
|
||||
world: function(thing, options) {
|
||||
return options.data.adjective + " " + thing + (this.exclaim || "");
|
||||
}
|
||||
};
|
||||
|
||||
var result = template({exclaim: true, zomg: "planet"}, {helpers: helpers, data: {adjective: "happy"}});
|
||||
equals("sad world?", result, "Overriden data output by helper");
|
||||
});
|
||||
|
||||
|
||||
it("you can override inherited data when invoking a helper with depth", function() {
|
||||
var template = CompilerContext.compile("{{#hello}}{{world ../zomg}}{{/hello}}", {data: true});
|
||||
|
||||
var helpers = {
|
||||
hello: function(options) {
|
||||
return options.fn({exclaim: "?"}, { data: {adjective: "sad"} });
|
||||
},
|
||||
world: function(thing, options) {
|
||||
return options.data.adjective + " " + thing + (this.exclaim || "");
|
||||
}
|
||||
};
|
||||
|
||||
var result = template({exclaim: true, zomg: "world"}, {helpers: helpers, data: {adjective: "happy"}});
|
||||
equals("sad world?", result, "Overriden data output by helper");
|
||||
});
|
||||
|
||||
});
|
||||
Vendored
+23
@@ -0,0 +1,23 @@
|
||||
/*global handlebarsEnv */
|
||||
require('./common');
|
||||
|
||||
var _ = require('underscore'),
|
||||
fs = require('fs'),
|
||||
vm = require('vm');
|
||||
|
||||
global.Handlebars = undefined;
|
||||
vm.runInThisContext(fs.readFileSync(__dirname + '/../../dist/handlebars.js'), 'dist/handlebars.js');
|
||||
|
||||
global.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) {
|
||||
return eval('(' + templateSpec + ')');
|
||||
}
|
||||
Vendored
+48
@@ -0,0 +1,48 @@
|
||||
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);
|
||||
if (result !== expected) {
|
||||
throw new Error("'" + expected + "' should === '" + result + "': " + message);
|
||||
}
|
||||
};
|
||||
|
||||
global.compileWithPartials = function(string, hashOrArray, partials) {
|
||||
var template = CompilerContext[partials ? 'compileWithPartial' : 'compile'](string), ary;
|
||||
if(Object.prototype.toString.call(hashOrArray) === "[object Array]") {
|
||||
ary = [];
|
||||
ary.push(hashOrArray[0]);
|
||||
ary.push({ helpers: hashOrArray[1], partials: hashOrArray[2] });
|
||||
} else {
|
||||
ary = [hashOrArray];
|
||||
}
|
||||
|
||||
return template.apply(this, ary);
|
||||
};
|
||||
|
||||
|
||||
global.equals = global.equal = function(a, 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');
|
||||
}
|
||||
};
|
||||
Vendored
+18
@@ -0,0 +1,18 @@
|
||||
/*global handlebarsEnv */
|
||||
require('./common');
|
||||
|
||||
global.Handlebars = require('../../lib');
|
||||
|
||||
global.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) {
|
||||
return eval('(' + templateSpec + ')');
|
||||
}
|
||||
Vendored
+42
@@ -0,0 +1,42 @@
|
||||
var fs = require('fs'),
|
||||
Mocha = require('mocha'),
|
||||
path = require('path');
|
||||
|
||||
var errors = 0,
|
||||
testDir = path.dirname(__dirname),
|
||||
grep = process.argv[2];
|
||||
|
||||
var files = [ testDir + "/basic.js" ];
|
||||
|
||||
var files = fs.readdirSync(testDir)
|
||||
.filter(function(name) { return (/.*\.js$/).test(name); })
|
||||
.map(function(name) { return testDir + '/' + name; });
|
||||
|
||||
run('./node', function() {
|
||||
run('./browser', function() {
|
||||
run('./runtime', function() {
|
||||
process.exit(errors);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
function run(env, callback) {
|
||||
var mocha = new Mocha();
|
||||
mocha.ui('bdd');
|
||||
mocha.files = files.slice();
|
||||
if (grep) {
|
||||
mocha.grep(grep);
|
||||
}
|
||||
|
||||
files.forEach(function(name) {
|
||||
delete require.cache[name];
|
||||
});
|
||||
|
||||
console.log('Running env: ' + env);
|
||||
require(env);
|
||||
mocha.run(function(errorCount) {
|
||||
errors += errorCount;
|
||||
callback();
|
||||
});
|
||||
}
|
||||
Vendored
+29
@@ -0,0 +1,29 @@
|
||||
/*global handlebarsEnv */
|
||||
require('./common');
|
||||
|
||||
var _ = require('underscore'),
|
||||
fs = require('fs'),
|
||||
vm = require('vm');
|
||||
|
||||
global.Handlebars = undefined;
|
||||
vm.runInThisContext(fs.readFileSync(__dirname + '/../../dist/handlebars.runtime.js'), 'dist/handlebars.runtime.js');
|
||||
|
||||
var compiler = require('../../dist/cjs/handlebars/compiler/compiler');
|
||||
|
||||
global.CompilerContext = {
|
||||
compile: function(template, options) {
|
||||
var templateSpec = compiler.precompile(template, options);
|
||||
return handlebarsEnv.template(safeEval(templateSpec));
|
||||
},
|
||||
compileWithPartial: function(template, options) {
|
||||
// Hack the compiler on to the environment for these specific tests
|
||||
handlebarsEnv.compile = function(template, options) {
|
||||
return compiler.compile(template, options, handlebarsEnv);
|
||||
};
|
||||
return handlebarsEnv.compile(template, options);
|
||||
}
|
||||
};
|
||||
|
||||
function safeEval(templateSpec) {
|
||||
return eval('(' + templateSpec + ')');
|
||||
}
|
||||
@@ -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;
|
||||
});
|
||||
});
|
||||
+528
@@ -0,0 +1,528 @@
|
||||
/*global CompilerContext, Handlebars, shouldCompileTo, shouldCompileToWithPartials, shouldThrow, handlebarsEnv */
|
||||
describe('helpers', function() {
|
||||
it("helper with complex lookup$", function() {
|
||||
var string = "{{#goodbyes}}{{{link ../prefix}}}{{/goodbyes}}";
|
||||
var hash = {prefix: "/root", goodbyes: [{text: "Goodbye", url: "goodbye"}]};
|
||||
var helpers = {link: function(prefix) {
|
||||
return "<a href='" + prefix + "/" + this.url + "'>" + this.text + "</a>";
|
||||
}};
|
||||
shouldCompileTo(string, [hash, helpers], "<a href='/root/goodbye'>Goodbye</a>");
|
||||
});
|
||||
|
||||
it("helper block with complex lookup expression", function() {
|
||||
var string = "{{#goodbyes}}{{../name}}{{/goodbyes}}";
|
||||
var hash = {name: "Alan"};
|
||||
var helpers = {goodbyes: function(options) {
|
||||
var out = "";
|
||||
var byes = ["Goodbye", "goodbye", "GOODBYE"];
|
||||
for (var i = 0,j = byes.length; i < j; i++) {
|
||||
out += byes[i] + " " + options.fn(this) + "! ";
|
||||
}
|
||||
return out;
|
||||
}};
|
||||
shouldCompileTo(string, [hash, helpers], "Goodbye Alan! goodbye Alan! GOODBYE Alan! ");
|
||||
});
|
||||
|
||||
it("helper with complex lookup and nested template", function() {
|
||||
var string = "{{#goodbyes}}{{#link ../prefix}}{{text}}{{/link}}{{/goodbyes}}";
|
||||
var hash = {prefix: '/root', goodbyes: [{text: "Goodbye", url: "goodbye"}]};
|
||||
var helpers = {link: function (prefix, options) {
|
||||
return "<a href='" + prefix + "/" + this.url + "'>" + options.fn(this) + "</a>";
|
||||
}};
|
||||
shouldCompileToWithPartials(string, [hash, helpers], false, "<a href='/root/goodbye'>Goodbye</a>");
|
||||
});
|
||||
|
||||
it("helper with complex lookup and nested template in VM+Compiler", function() {
|
||||
var string = "{{#goodbyes}}{{#link ../prefix}}{{text}}{{/link}}{{/goodbyes}}";
|
||||
var hash = {prefix: '/root', goodbyes: [{text: "Goodbye", url: "goodbye"}]};
|
||||
var helpers = {link: function (prefix, options) {
|
||||
return "<a href='" + prefix + "/" + this.url + "'>" + options.fn(this) + "</a>";
|
||||
}};
|
||||
shouldCompileToWithPartials(string, [hash, helpers], true, "<a href='/root/goodbye'>Goodbye</a>");
|
||||
});
|
||||
|
||||
it("block helper", function() {
|
||||
var string = "{{#goodbyes}}{{text}}! {{/goodbyes}}cruel {{world}}!";
|
||||
var template = CompilerContext.compile(string);
|
||||
|
||||
var result = template({world: "world"}, { helpers: {goodbyes: function(options) { return options.fn({text: "GOODBYE"}); }}});
|
||||
equal(result, "GOODBYE! cruel world!", "Block helper executed");
|
||||
});
|
||||
|
||||
it("block helper staying in the same context", function() {
|
||||
var string = "{{#form}}<p>{{name}}</p>{{/form}}";
|
||||
var template = CompilerContext.compile(string);
|
||||
|
||||
var result = template({name: "Yehuda"}, {helpers: {form: function(options) { return "<form>" + options.fn(this) + "</form>"; } }});
|
||||
equal(result, "<form><p>Yehuda</p></form>", "Block helper executed with current context");
|
||||
});
|
||||
|
||||
it("block helper should have context in this", function() {
|
||||
var source = "<ul>{{#people}}<li>{{#link}}{{name}}{{/link}}</li>{{/people}}</ul>";
|
||||
var link = function(options) {
|
||||
return '<a href="/people/' + this.id + '">' + options.fn(this) + '</a>';
|
||||
};
|
||||
var data = { "people": [
|
||||
{ "name": "Alan", "id": 1 },
|
||||
{ "name": "Yehuda", "id": 2 }
|
||||
]};
|
||||
|
||||
shouldCompileTo(source, [data, {link: link}], "<ul><li><a href=\"/people/1\">Alan</a></li><li><a href=\"/people/2\">Yehuda</a></li></ul>");
|
||||
});
|
||||
|
||||
it("block helper for undefined value", function() {
|
||||
shouldCompileTo("{{#empty}}shouldn't render{{/empty}}", {}, "");
|
||||
});
|
||||
|
||||
it("block helper passing a new context", function() {
|
||||
var string = "{{#form yehuda}}<p>{{name}}</p>{{/form}}";
|
||||
var template = CompilerContext.compile(string);
|
||||
|
||||
var result = template({yehuda: {name: "Yehuda"}}, { helpers: {form: function(context, options) { return "<form>" + options.fn(context) + "</form>"; }}});
|
||||
equal(result, "<form><p>Yehuda</p></form>", "Context variable resolved");
|
||||
});
|
||||
|
||||
it("block helper passing a complex path context", function() {
|
||||
var string = "{{#form yehuda/cat}}<p>{{name}}</p>{{/form}}";
|
||||
var template = CompilerContext.compile(string);
|
||||
|
||||
var result = template({yehuda: {name: "Yehuda", cat: {name: "Harold"}}}, { helpers: {form: function(context, options) { return "<form>" + options.fn(context) + "</form>"; }}});
|
||||
equal(result, "<form><p>Harold</p></form>", "Complex path variable resolved");
|
||||
});
|
||||
|
||||
it("nested block helpers", function() {
|
||||
var string = "{{#form yehuda}}<p>{{name}}</p>{{#link}}Hello{{/link}}{{/form}}";
|
||||
var template = CompilerContext.compile(string);
|
||||
|
||||
var result = template({
|
||||
yehuda: {name: "Yehuda" }
|
||||
}, {
|
||||
helpers: {
|
||||
link: function(options) { return "<a href='" + this.name + "'>" + options.fn(this) + "</a>"; },
|
||||
form: function(context, options) { return "<form>" + options.fn(context) + "</form>"; }
|
||||
}
|
||||
});
|
||||
equal(result, "<form><p>Yehuda</p><a href='Yehuda'>Hello</a></form>", "Both blocks executed");
|
||||
});
|
||||
|
||||
it("block helper inverted sections", function() {
|
||||
var string = "{{#list people}}{{name}}{{^}}<em>Nobody's here</em>{{/list}}";
|
||||
var list = function(context, options) {
|
||||
if (context.length > 0) {
|
||||
var out = "<ul>";
|
||||
for(var i = 0,j=context.length; i < j; i++) {
|
||||
out += "<li>";
|
||||
out += options.fn(context[i]);
|
||||
out += "</li>";
|
||||
}
|
||||
out += "</ul>";
|
||||
return out;
|
||||
} else {
|
||||
return "<p>" + options.inverse(this) + "</p>";
|
||||
}
|
||||
};
|
||||
|
||||
var hash = {people: [{name: "Alan"}, {name: "Yehuda"}]};
|
||||
var empty = {people: []};
|
||||
var rootMessage = {
|
||||
people: [],
|
||||
message: "Nobody's here"
|
||||
};
|
||||
|
||||
var messageString = "{{#list people}}Hello{{^}}{{message}}{{/list}}";
|
||||
|
||||
// the meaning here may be kind of hard to catch, but list.not is always called,
|
||||
// so we should see the output of both
|
||||
shouldCompileTo(string, [hash, { list: list }], "<ul><li>Alan</li><li>Yehuda</li></ul>", "an inverse wrapper is passed in as a new context");
|
||||
shouldCompileTo(string, [empty, { list: list }], "<p><em>Nobody's here</em></p>", "an inverse wrapper can be optionally called");
|
||||
shouldCompileTo(messageString, [rootMessage, { list: list }], "<p>Nobody's here</p>", "the context of an inverse is the parent of the block");
|
||||
});
|
||||
|
||||
describe("helpers hash", function() {
|
||||
it("providing a helpers hash", function() {
|
||||
shouldCompileTo("Goodbye {{cruel}} {{world}}!", [{cruel: "cruel"}, {world: function() { return "world"; }}], "Goodbye cruel world!",
|
||||
"helpers hash is available");
|
||||
|
||||
shouldCompileTo("Goodbye {{#iter}}{{cruel}} {{world}}{{/iter}}!", [{iter: [{cruel: "cruel"}]}, {world: function() { return "world"; }}],
|
||||
"Goodbye cruel world!", "helpers hash is available inside other blocks");
|
||||
});
|
||||
|
||||
it("in cases of conflict, helpers win", function() {
|
||||
shouldCompileTo("{{{lookup}}}", [{lookup: 'Explicit'}, {lookup: function() { return 'helpers'; }}], "helpers",
|
||||
"helpers hash has precedence escaped expansion");
|
||||
shouldCompileTo("{{lookup}}", [{lookup: 'Explicit'}, {lookup: function() { return 'helpers'; }}], "helpers",
|
||||
"helpers hash has precedence simple expansion");
|
||||
});
|
||||
|
||||
it("the helpers hash is available is nested contexts", function() {
|
||||
shouldCompileTo("{{#outer}}{{#inner}}{{helper}}{{/inner}}{{/outer}}",
|
||||
[{'outer': {'inner': {'unused':[]}}}, {'helper': function() { return 'helper'; }}], "helper",
|
||||
"helpers hash is available in nested contexts.");
|
||||
});
|
||||
|
||||
it("the helper hash should augment the global hash", function() {
|
||||
handlebarsEnv.registerHelper('test_helper', function() { return 'found it!'; });
|
||||
|
||||
shouldCompileTo(
|
||||
"{{test_helper}} {{#if cruel}}Goodbye {{cruel}} {{world}}!{{/if}}", [
|
||||
{cruel: "cruel"},
|
||||
{world: function() { return "world!"; }}
|
||||
],
|
||||
"found it! Goodbye cruel world!!");
|
||||
});
|
||||
});
|
||||
|
||||
it("Multiple global helper registration", function() {
|
||||
var helpers = handlebarsEnv.helpers;
|
||||
handlebarsEnv.helpers = {};
|
||||
|
||||
handlebarsEnv.registerHelper({
|
||||
'if': helpers['if'],
|
||||
world: function() { return "world!"; },
|
||||
test_helper: function() { return 'found it!'; }
|
||||
});
|
||||
|
||||
shouldCompileTo(
|
||||
"{{test_helper}} {{#if cruel}}Goodbye {{cruel}} {{world}}!{{/if}}",
|
||||
[{cruel: "cruel"}],
|
||||
"found it! Goodbye cruel world!!");
|
||||
});
|
||||
|
||||
it("negative number literals work", function() {
|
||||
var string = 'Message: {{hello -12}}';
|
||||
var hash = {};
|
||||
var helpers = {hello: function(times) {
|
||||
if(typeof times !== 'number') { times = "NaN"; }
|
||||
return "Hello " + times + " times";
|
||||
}};
|
||||
shouldCompileTo(string, [hash, helpers], "Message: Hello -12 times", "template with a negative integer literal");
|
||||
});
|
||||
|
||||
describe("String literal parameters", function() {
|
||||
it("simple literals work", function() {
|
||||
var string = 'Message: {{hello "world" 12 true false}}';
|
||||
var hash = {};
|
||||
var helpers = {hello: function(param, times, bool1, bool2) {
|
||||
if(typeof times !== 'number') { times = "NaN"; }
|
||||
if(typeof bool1 !== 'boolean') { bool1 = "NaB"; }
|
||||
if(typeof bool2 !== 'boolean') { bool2 = "NaB"; }
|
||||
return "Hello " + param + " " + times + " times: " + bool1 + " " + bool2;
|
||||
}};
|
||||
shouldCompileTo(string, [hash, helpers], "Message: Hello world 12 times: true false", "template with a simple String literal");
|
||||
});
|
||||
|
||||
it("using a quote in the middle of a parameter raises an error", function() {
|
||||
var string = 'Message: {{hello wo"rld"}}';
|
||||
shouldThrow(function() {
|
||||
CompilerContext.compile(string);
|
||||
}, Error);
|
||||
});
|
||||
|
||||
it("escaping a String is possible", function(){
|
||||
var string = 'Message: {{{hello "\\"world\\""}}}';
|
||||
var hash = {};
|
||||
var helpers = {hello: function(param) { return "Hello " + param; }};
|
||||
shouldCompileTo(string, [hash, helpers], "Message: Hello \"world\"", "template with an escaped String literal");
|
||||
});
|
||||
|
||||
it("it works with ' marks", function() {
|
||||
var string = 'Message: {{{hello "Alan\'s world"}}}';
|
||||
var hash = {};
|
||||
var helpers = {hello: function(param) { return "Hello " + param; }};
|
||||
shouldCompileTo(string, [hash, helpers], "Message: Hello Alan's world", "template with a ' mark");
|
||||
});
|
||||
});
|
||||
|
||||
it("negative number literals work", function() {
|
||||
var string = 'Message: {{hello -12}}';
|
||||
var hash = {};
|
||||
var helpers = {hello: function(times) {
|
||||
if(typeof times !== 'number') { times = "NaN"; }
|
||||
return "Hello " + times + " times";
|
||||
}};
|
||||
shouldCompileTo(string, [hash, helpers], "Message: Hello -12 times", "template with a negative integer literal");
|
||||
});
|
||||
|
||||
describe("multiple parameters", function() {
|
||||
it("simple multi-params work", function() {
|
||||
var string = 'Message: {{goodbye cruel world}}';
|
||||
var hash = {cruel: "cruel", world: "world"};
|
||||
var helpers = {goodbye: function(cruel, world) { return "Goodbye " + cruel + " " + world; }};
|
||||
shouldCompileTo(string, [hash, helpers], "Message: Goodbye cruel world", "regular helpers with multiple params");
|
||||
});
|
||||
|
||||
it("block multi-params work", function() {
|
||||
var string = 'Message: {{#goodbye cruel world}}{{greeting}} {{adj}} {{noun}}{{/goodbye}}';
|
||||
var hash = {cruel: "cruel", world: "world"};
|
||||
var helpers = {goodbye: function(cruel, world, options) {
|
||||
return options.fn({greeting: "Goodbye", adj: cruel, noun: world});
|
||||
}};
|
||||
shouldCompileTo(string, [hash, helpers], "Message: Goodbye cruel world", "block helpers with multiple params");
|
||||
});
|
||||
});
|
||||
describe('hash', function() {
|
||||
it("helpers can take an optional hash", function() {
|
||||
var template = CompilerContext.compile('{{goodbye cruel="CRUEL" world="WORLD" times=12}}');
|
||||
|
||||
var helpers = {
|
||||
goodbye: function(options) {
|
||||
return "GOODBYE " + options.hash.cruel + " " + options.hash.world + " " + options.hash.times + " TIMES";
|
||||
}
|
||||
};
|
||||
|
||||
var context = {};
|
||||
|
||||
var result = template(context, {helpers: helpers});
|
||||
equals(result, "GOODBYE CRUEL WORLD 12 TIMES", "Helper output hash");
|
||||
});
|
||||
|
||||
it("helpers can take an optional hash with booleans", function() {
|
||||
var helpers = {
|
||||
goodbye: function(options) {
|
||||
if (options.hash.print === true) {
|
||||
return "GOODBYE " + options.hash.cruel + " " + options.hash.world;
|
||||
} else if (options.hash.print === false) {
|
||||
return "NOT PRINTING";
|
||||
} else {
|
||||
return "THIS SHOULD NOT HAPPEN";
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
var context = {};
|
||||
|
||||
var template = CompilerContext.compile('{{goodbye cruel="CRUEL" world="WORLD" print=true}}');
|
||||
var result = template(context, {helpers: helpers});
|
||||
equals(result, "GOODBYE CRUEL WORLD", "Helper output hash");
|
||||
|
||||
template = CompilerContext.compile('{{goodbye cruel="CRUEL" world="WORLD" print=false}}');
|
||||
result = template(context, {helpers: helpers});
|
||||
equals(result, "NOT PRINTING", "Boolean helper parameter honored");
|
||||
});
|
||||
|
||||
it("block helpers can take an optional hash", function() {
|
||||
var template = CompilerContext.compile('{{#goodbye cruel="CRUEL" times=12}}world{{/goodbye}}');
|
||||
|
||||
var helpers = {
|
||||
goodbye: function(options) {
|
||||
return "GOODBYE " + options.hash.cruel + " " + options.fn(this) + " " + options.hash.times + " TIMES";
|
||||
}
|
||||
};
|
||||
|
||||
var result = template({}, {helpers: helpers});
|
||||
equals(result, "GOODBYE CRUEL world 12 TIMES", "Hash parameters output");
|
||||
});
|
||||
|
||||
it("block helpers can take an optional hash with single quoted stings", function() {
|
||||
var template = CompilerContext.compile("{{#goodbye cruel='CRUEL' times=12}}world{{/goodbye}}");
|
||||
|
||||
var helpers = {
|
||||
goodbye: function(options) {
|
||||
return "GOODBYE " + options.hash.cruel + " " + options.fn(this) + " " + options.hash.times + " TIMES";
|
||||
}
|
||||
};
|
||||
|
||||
var result = template({}, {helpers: helpers});
|
||||
equals(result, "GOODBYE CRUEL world 12 TIMES", "Hash parameters output");
|
||||
});
|
||||
|
||||
it("block helpers can take an optional hash with booleans", function() {
|
||||
var helpers = {
|
||||
goodbye: function(options) {
|
||||
if (options.hash.print === true) {
|
||||
return "GOODBYE " + options.hash.cruel + " " + options.fn(this);
|
||||
} else if (options.hash.print === false) {
|
||||
return "NOT PRINTING";
|
||||
} else {
|
||||
return "THIS SHOULD NOT HAPPEN";
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
var template = CompilerContext.compile('{{#goodbye cruel="CRUEL" print=true}}world{{/goodbye}}');
|
||||
var result = template({}, {helpers: helpers});
|
||||
equals(result, "GOODBYE CRUEL world", "Boolean hash parameter honored");
|
||||
|
||||
template = CompilerContext.compile('{{#goodbye cruel="CRUEL" print=false}}world{{/goodbye}}');
|
||||
result = template({}, {helpers: helpers});
|
||||
equals(result, "NOT PRINTING", "Boolean hash parameter honored");
|
||||
});
|
||||
});
|
||||
|
||||
describe("helperMissing", function() {
|
||||
it("if a context is not found, helperMissing is used", function() {
|
||||
shouldThrow(function() {
|
||||
var template = CompilerContext.compile("{{hello}} {{link_to world}}");
|
||||
template({});
|
||||
}, undefined, /Missing helper: 'link_to'/);
|
||||
});
|
||||
|
||||
it("if a context is not found, custom helperMissing is used", function() {
|
||||
var string = "{{hello}} {{link_to world}}";
|
||||
var context = { hello: "Hello", world: "world" };
|
||||
|
||||
var helpers = {
|
||||
helperMissing: function(helper, context) {
|
||||
if(helper === "link_to") {
|
||||
return new Handlebars.SafeString("<a>" + context + "</a>");
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
shouldCompileTo(string, [context, helpers], "Hello <a>world</a>");
|
||||
});
|
||||
});
|
||||
|
||||
describe("knownHelpers", function() {
|
||||
it("Known helper should render helper", function() {
|
||||
var template = CompilerContext.compile("{{hello}}", {knownHelpers: {"hello" : true}});
|
||||
|
||||
var result = template({}, {helpers: {hello: function() { return "foo"; }}});
|
||||
equal(result, "foo", "'foo' should === '" + result);
|
||||
});
|
||||
|
||||
it("Unknown helper in knownHelpers only mode should be passed as undefined", function() {
|
||||
var template = CompilerContext.compile("{{typeof hello}}", {knownHelpers: {'typeof': true}, knownHelpersOnly: true});
|
||||
|
||||
var result = template({}, {helpers: {'typeof': function(arg) { return typeof arg; }, hello: function() { return "foo"; }}});
|
||||
equal(result, "undefined", "'undefined' should === '" + result);
|
||||
});
|
||||
it("Builtin helpers available in knownHelpers only mode", function() {
|
||||
var template = CompilerContext.compile("{{#unless foo}}bar{{/unless}}", {knownHelpersOnly: true});
|
||||
|
||||
var result = template({});
|
||||
equal(result, "bar", "'bar' should === '" + result);
|
||||
});
|
||||
it("Field lookup works in knownHelpers only mode", function() {
|
||||
var template = CompilerContext.compile("{{foo}}", {knownHelpersOnly: true});
|
||||
|
||||
var result = template({foo: 'bar'});
|
||||
equal(result, "bar", "'bar' should === '" + result);
|
||||
});
|
||||
it("Conditional blocks work in knownHelpers only mode", function() {
|
||||
var template = CompilerContext.compile("{{#foo}}bar{{/foo}}", {knownHelpersOnly: true});
|
||||
|
||||
var result = template({foo: 'baz'});
|
||||
equal(result, "bar", "'bar' should === '" + result);
|
||||
});
|
||||
it("Invert blocks work in knownHelpers only mode", function() {
|
||||
var template = CompilerContext.compile("{{^foo}}bar{{/foo}}", {knownHelpersOnly: true});
|
||||
|
||||
var result = template({foo: false});
|
||||
equal(result, "bar", "'bar' should === '" + result);
|
||||
});
|
||||
it("Functions are bound to the context in knownHelpers only mode", function() {
|
||||
var template = CompilerContext.compile("{{foo}}", {knownHelpersOnly: true});
|
||||
var result = template({foo: function() { return this.bar; }, bar: 'bar'});
|
||||
equal(result, "bar", "'bar' should === '" + result);
|
||||
});
|
||||
it("Unknown helper call in knownHelpers only mode should throw", function() {
|
||||
shouldThrow(function() {
|
||||
CompilerContext.compile("{{typeof hello}}", {knownHelpersOnly: true});
|
||||
}, Error);
|
||||
});
|
||||
});
|
||||
|
||||
describe("blockHelperMissing", function() {
|
||||
it("lambdas are resolved by blockHelperMissing, not handlebars proper", function() {
|
||||
var string = "{{#truthy}}yep{{/truthy}}";
|
||||
var data = { truthy: function() { return true; } };
|
||||
shouldCompileTo(string, data, "yep");
|
||||
});
|
||||
it("lambdas resolved by blockHelperMissing are bound to the context", function() {
|
||||
var string = "{{#truthy}}yep{{/truthy}}";
|
||||
var boundData = { truthy: function() { return this.truthiness(); }, truthiness: function() { return false; } };
|
||||
shouldCompileTo(string, boundData, "");
|
||||
});
|
||||
});
|
||||
|
||||
describe('name conflicts', function() {
|
||||
it("helpers take precedence over same-named context properties", function() {
|
||||
var template = CompilerContext.compile("{{goodbye}} {{cruel world}}");
|
||||
|
||||
var helpers = {
|
||||
goodbye: function() {
|
||||
return this.goodbye.toUpperCase();
|
||||
},
|
||||
|
||||
cruel: function(world) {
|
||||
return "cruel " + world.toUpperCase();
|
||||
}
|
||||
};
|
||||
|
||||
var context = {
|
||||
goodbye: "goodbye",
|
||||
world: "world"
|
||||
};
|
||||
|
||||
var result = template(context, {helpers: helpers});
|
||||
equals(result, "GOODBYE cruel WORLD", "Helper executed");
|
||||
});
|
||||
|
||||
it("helpers take precedence over same-named context properties$", function() {
|
||||
var template = CompilerContext.compile("{{#goodbye}} {{cruel world}}{{/goodbye}}");
|
||||
|
||||
var helpers = {
|
||||
goodbye: function(options) {
|
||||
return this.goodbye.toUpperCase() + options.fn(this);
|
||||
},
|
||||
|
||||
cruel: function(world) {
|
||||
return "cruel " + world.toUpperCase();
|
||||
}
|
||||
};
|
||||
|
||||
var context = {
|
||||
goodbye: "goodbye",
|
||||
world: "world"
|
||||
};
|
||||
|
||||
var result = template(context, {helpers: helpers});
|
||||
equals(result, "GOODBYE cruel WORLD", "Helper executed");
|
||||
});
|
||||
|
||||
it("Scoped names take precedence over helpers", function() {
|
||||
var template = CompilerContext.compile("{{this.goodbye}} {{cruel world}} {{cruel this.goodbye}}");
|
||||
|
||||
var helpers = {
|
||||
goodbye: function() {
|
||||
return this.goodbye.toUpperCase();
|
||||
},
|
||||
|
||||
cruel: function(world) {
|
||||
return "cruel " + world.toUpperCase();
|
||||
}
|
||||
};
|
||||
|
||||
var context = {
|
||||
goodbye: "goodbye",
|
||||
world: "world"
|
||||
};
|
||||
|
||||
var result = template(context, {helpers: helpers});
|
||||
equals(result, "goodbye cruel WORLD cruel GOODBYE", "Helper not executed");
|
||||
});
|
||||
|
||||
it("Scoped names take precedence over block helpers", function() {
|
||||
var template = CompilerContext.compile("{{#goodbye}} {{cruel world}}{{/goodbye}} {{this.goodbye}}");
|
||||
|
||||
var helpers = {
|
||||
goodbye: function(options) {
|
||||
return this.goodbye.toUpperCase() + options.fn(this);
|
||||
},
|
||||
|
||||
cruel: function(world) {
|
||||
return "cruel " + world.toUpperCase();
|
||||
}
|
||||
};
|
||||
|
||||
var context = {
|
||||
goodbye: "goodbye",
|
||||
world: "world"
|
||||
};
|
||||
|
||||
var result = template(context, {helpers: helpers});
|
||||
equals(result, "GOODBYE cruel WORLD goodbye", "Helper executed");
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -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");
|
||||
});
|
||||
});
|
||||
});
|
||||
+174
@@ -0,0 +1,174 @@
|
||||
/*global Handlebars */
|
||||
describe('parser', function() {
|
||||
if (!Handlebars.print) {
|
||||
return;
|
||||
}
|
||||
|
||||
function ast_for(template) {
|
||||
var ast = Handlebars.parse(template);
|
||||
return Handlebars.print(ast);
|
||||
}
|
||||
|
||||
it('parses simple mustaches', function() {
|
||||
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() {
|
||||
equals(ast_for("{{@foo}}"), "{{ @ID:foo [] }}\n");
|
||||
});
|
||||
|
||||
it('parses mustaches with paths', function() {
|
||||
equals(ast_for("{{foo/bar}}"), "{{ PATH:foo/bar [] }}\n");
|
||||
});
|
||||
|
||||
it('parses mustaches with this/foo', function() {
|
||||
equals(ast_for("{{this/foo}}"), "{{ ID:foo [] }}\n");
|
||||
});
|
||||
|
||||
it('parses mustaches with - in a path', function() {
|
||||
equals(ast_for("{{foo-bar}}"), "{{ ID:foo-bar [] }}\n");
|
||||
});
|
||||
|
||||
it('parses mustaches with parameters', function() {
|
||||
equals(ast_for("{{foo bar}}"), "{{ ID:foo [ID:bar] }}\n");
|
||||
});
|
||||
|
||||
it('parses mustaches with string parameters', function() {
|
||||
equals(ast_for("{{foo bar \"baz\" }}"), '{{ ID:foo [ID:bar, "baz"] }}\n');
|
||||
});
|
||||
|
||||
it('parses mustaches with INTEGER parameters', function() {
|
||||
equals(ast_for("{{foo 1}}"), "{{ ID:foo [INTEGER{1}] }}\n");
|
||||
});
|
||||
|
||||
it('parses mustaches with BOOLEAN parameters', function() {
|
||||
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() {
|
||||
equals(ast_for("{{foo @bar}}"), "{{ ID:foo [@ID:bar] }}\n");
|
||||
});
|
||||
|
||||
it('parses mustaches with hash arguments', function() {
|
||||
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");
|
||||
|
||||
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');
|
||||
|
||||
equals(ast_for("{{foo bat='bam'}}"), '{{ ID:foo [] HASH{bat="bam"} }}\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() {
|
||||
equals(ast_for("foo bar {{baz}}"), "CONTENT[ \'foo bar \' ]\n{{ ID:baz [] }}\n");
|
||||
});
|
||||
|
||||
it('parses a partial', function() {
|
||||
equals(ast_for("{{> foo }}"), "{{> PARTIAL:foo }}\n");
|
||||
});
|
||||
|
||||
it('parses a partial with context', function() {
|
||||
equals(ast_for("{{> foo bar}}"), "{{> PARTIAL:foo ID:bar }}\n");
|
||||
});
|
||||
|
||||
it('parses a partial with a complex name', function() {
|
||||
equals(ast_for("{{> shared/partial?.bar}}"), "{{> PARTIAL:shared/partial?.bar }}\n");
|
||||
});
|
||||
|
||||
it('parses a comment', function() {
|
||||
equals(ast_for("{{! this is a comment }}"), "{{! ' this is a comment ' }}\n");
|
||||
});
|
||||
|
||||
it('parses a multi-line comment', function() {
|
||||
equals(ast_for("{{!\nthis is a multi-line comment\n}}"), "{{! \'\nthis is a multi-line comment\n\' }}\n");
|
||||
});
|
||||
|
||||
it('parses an inverse section', function() {
|
||||
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() {
|
||||
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() {
|
||||
equals(ast_for("{{#foo}}{{/foo}}"), "BLOCK:\n {{ ID:foo [] }}\n PROGRAM:\n");
|
||||
});
|
||||
|
||||
it('parses empty blocks with empty inverse section', function() {
|
||||
equals(ast_for("{{#foo}}{{^}}{{/foo}}"), "BLOCK:\n {{ ID:foo [] }}\n PROGRAM:\n");
|
||||
});
|
||||
|
||||
it('parses empty blocks with empty inverse (else-style) section', function() {
|
||||
equals(ast_for("{{#foo}}{{else}}{{/foo}}"), "BLOCK:\n {{ ID:foo [] }}\n PROGRAM:\n");
|
||||
});
|
||||
|
||||
it('parses non-empty blocks with empty inverse section', function() {
|
||||
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() {
|
||||
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() {
|
||||
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() {
|
||||
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() {
|
||||
equals(ast_for("{{^foo}}bar{{/foo}}"), "BLOCK:\n {{ ID:foo [] }}\n {{^}}\n CONTENT[ 'bar' ]\n");
|
||||
});
|
||||
|
||||
it("raises if there's a Parse error", function() {
|
||||
shouldThrow(function() {
|
||||
ast_for("foo{{^}}bar");
|
||||
}, Error, /Parse error on line 1/);
|
||||
shouldThrow(function() {
|
||||
ast_for("{{foo}");
|
||||
}, Error, /Parse error on line 1/);
|
||||
shouldThrow(function() {
|
||||
ast_for("{{foo &}}");
|
||||
}, Error, /Parse error on line 1/);
|
||||
shouldThrow(function() {
|
||||
ast_for("{{#goodbyes}}{{/hellos}}");
|
||||
}, Error, /goodbyes doesn't match hellos/);
|
||||
});
|
||||
|
||||
it('knows how to report the correct line number in errors', function() {
|
||||
shouldThrow(function() {
|
||||
ast_for("hello\nmy\n{{foo}");
|
||||
}, Error, /Parse error on line 3/);
|
||||
shouldThrow(function() {
|
||||
ast_for("hello\n\nmy\n\n{{foo}");
|
||||
}, 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() {
|
||||
shouldThrow(function() {
|
||||
ast_for("\n\nhello\n\nmy\n\n{{foo}");
|
||||
}, Error, /Parse error on line 7/);
|
||||
});
|
||||
|
||||
describe('externally compiled AST', function() {
|
||||
it('can pass through an already-compiled AST', function() {
|
||||
equals(ast_for(new Handlebars.AST.ProgramNode([ new Handlebars.AST.ContentNode("Hello")])), "CONTENT[ \'Hello\' ]\n");
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,426 +0,0 @@
|
||||
require "spec_helper"
|
||||
|
||||
describe "Parser" do
|
||||
let(:handlebars) { @context["Handlebars"] }
|
||||
|
||||
before(:all) do
|
||||
@compiles = true
|
||||
end
|
||||
|
||||
def root(&block)
|
||||
ASTBuilder.build do
|
||||
instance_eval(&block)
|
||||
end
|
||||
end
|
||||
|
||||
def ast_for(string)
|
||||
ast = handlebars.parse(string)
|
||||
handlebars.print(ast)
|
||||
end
|
||||
|
||||
class ASTBuilder
|
||||
def self.build(&block)
|
||||
ret = new
|
||||
ret.evaluate(&block)
|
||||
ret.out
|
||||
end
|
||||
|
||||
attr_reader :out
|
||||
|
||||
def initialize
|
||||
@padding = 0
|
||||
@out = ""
|
||||
end
|
||||
|
||||
def evaluate(&block)
|
||||
instance_eval(&block)
|
||||
end
|
||||
|
||||
def pad(string)
|
||||
@out << (" " * @padding) + string + "\n"
|
||||
end
|
||||
|
||||
def with_padding
|
||||
@padding += 1
|
||||
ret = yield
|
||||
@padding -= 1
|
||||
ret
|
||||
end
|
||||
|
||||
def program
|
||||
pad("PROGRAM:")
|
||||
with_padding { yield }
|
||||
end
|
||||
|
||||
def inverse
|
||||
pad("{{^}}")
|
||||
with_padding { yield }
|
||||
end
|
||||
|
||||
def block
|
||||
pad("BLOCK:")
|
||||
with_padding { yield }
|
||||
end
|
||||
|
||||
def inverted_block
|
||||
pad("INVERSE:")
|
||||
with_padding { yield }
|
||||
end
|
||||
|
||||
def mustache(id, params = [], hash = nil)
|
||||
hash = " #{hash}" if hash
|
||||
pad("{{ #{id} [#{params.join(", ")}]#{hash} }}")
|
||||
end
|
||||
|
||||
def partial(id, context = nil)
|
||||
content = id.dup
|
||||
content << " #{context}" if context
|
||||
pad("{{> #{content} }}")
|
||||
end
|
||||
|
||||
def comment(comment)
|
||||
pad("{{! '#{comment}' }}")
|
||||
end
|
||||
|
||||
def multiline_comment(comment)
|
||||
pad("{{! '\n#{comment}\n' }}")
|
||||
end
|
||||
|
||||
def content(string)
|
||||
pad("CONTENT[ '#{string}' ]")
|
||||
end
|
||||
|
||||
def string(string)
|
||||
string.inspect
|
||||
end
|
||||
|
||||
def integer(string)
|
||||
"INTEGER{#{string}}"
|
||||
end
|
||||
|
||||
def boolean(string)
|
||||
"BOOLEAN{#{string}}"
|
||||
end
|
||||
|
||||
def hash(*pairs)
|
||||
"HASH{" + pairs.map {|k,v| "#{k}=#{v}" }.join(", ") + "}"
|
||||
end
|
||||
|
||||
def id(id)
|
||||
"ID:#{id}"
|
||||
end
|
||||
|
||||
def data(id)
|
||||
"@ID:#{id}"
|
||||
end
|
||||
|
||||
def partial_name(name)
|
||||
"PARTIAL:#{name}"
|
||||
end
|
||||
|
||||
def path(*parts)
|
||||
"PATH:#{parts.join("/")}"
|
||||
end
|
||||
end
|
||||
|
||||
it "parses simple mustaches" do
|
||||
ast_for("{{foo}}").should == root { mustache id("foo") }
|
||||
ast_for("{{foo?}}").should == root { mustache id("foo?") }
|
||||
ast_for("{{foo_}}").should == root { mustache id("foo_") }
|
||||
ast_for("{{foo-}}").should == root { mustache id("foo-") }
|
||||
ast_for("{{foo:}}").should == root { mustache id("foo:") }
|
||||
end
|
||||
|
||||
it "parses simple mustaches with data" do
|
||||
ast_for("{{@foo}}").should == root { mustache data("foo") }
|
||||
end
|
||||
|
||||
it "parses mustaches with paths" do
|
||||
ast_for("{{foo/bar}}").should == root { mustache path("foo", "bar") }
|
||||
end
|
||||
|
||||
it "parses mustaches with this/foo" do
|
||||
ast_for("{{this/foo}}").should == root { mustache id("foo") }
|
||||
end
|
||||
|
||||
it "parses mustaches with - in a path" do
|
||||
ast_for("{{foo-bar}}").should == root { mustache id("foo-bar") }
|
||||
end
|
||||
|
||||
it "parses mustaches with parameters" do
|
||||
ast_for("{{foo bar}}").should == root { mustache id("foo"), [id("bar")] }
|
||||
end
|
||||
|
||||
it "parses mustaches with hash arguments" do
|
||||
ast_for("{{foo bar=baz}}").should == root do
|
||||
mustache id("foo"), [], hash(["bar", id("baz")])
|
||||
end
|
||||
|
||||
ast_for("{{foo bar=1}}").should == root do
|
||||
mustache id("foo"), [], hash(["bar", integer("1")])
|
||||
end
|
||||
|
||||
ast_for("{{foo bar=true}}").should == root do
|
||||
mustache id("foo"), [], hash(["bar", boolean("true")])
|
||||
end
|
||||
|
||||
ast_for("{{foo bar=false}}").should == root do
|
||||
mustache id("foo"), [], hash(["bar", boolean("false")])
|
||||
end
|
||||
|
||||
ast_for("{{foo bar=@baz}}").should == root do
|
||||
mustache id("foo"), [], hash(["bar", data("baz")])
|
||||
end
|
||||
|
||||
ast_for("{{foo bar=baz bat=bam}}").should == root do
|
||||
mustache id("foo"), [], hash(["bar", "ID:baz"], ["bat", "ID:bam"])
|
||||
end
|
||||
|
||||
ast_for("{{foo bar=baz bat=\"bam\"}}").should == root do
|
||||
mustache id("foo"), [], hash(["bar", "ID:baz"], ["bat", "\"bam\""])
|
||||
end
|
||||
|
||||
ast_for("{{foo bat='bam'}}").should == root do
|
||||
mustache id("foo"), [], hash(["bat", "\"bam\""])
|
||||
end
|
||||
|
||||
ast_for("{{foo omg bar=baz bat=\"bam\"}}").should == root do
|
||||
mustache id("foo"), [id("omg")], hash(["bar", id("baz")], ["bat", string("bam")])
|
||||
end
|
||||
|
||||
ast_for("{{foo omg bar=baz bat=\"bam\" baz=1}}").should == root do
|
||||
mustache id("foo"), [id("omg")], hash(["bar", id("baz")], ["bat", string("bam")], ["baz", integer("1")])
|
||||
end
|
||||
|
||||
ast_for("{{foo omg bar=baz bat=\"bam\" baz=true}}").should == root do
|
||||
mustache id("foo"), [id("omg")], hash(["bar", id("baz")], ["bat", string("bam")], ["baz", boolean("true")])
|
||||
end
|
||||
|
||||
ast_for("{{foo omg bar=baz bat=\"bam\" baz=false}}").should == root do
|
||||
mustache id("foo"), [id("omg")], hash(["bar", id("baz")], ["bat", string("bam")], ["baz", boolean("false")])
|
||||
end
|
||||
end
|
||||
|
||||
it "parses mustaches with string parameters" do
|
||||
ast_for("{{foo bar \"baz\" }}").should == root { mustache id("foo"), [id("bar"), string("baz")] }
|
||||
end
|
||||
|
||||
it "parses mustaches with INTEGER parameters" do
|
||||
ast_for("{{foo 1}}").should == root { mustache id("foo"), [integer("1")] }
|
||||
end
|
||||
|
||||
it "parses mustaches with BOOLEAN parameters" do
|
||||
ast_for("{{foo true}}").should == root { mustache id("foo"), [boolean("true")] }
|
||||
ast_for("{{foo false}}").should == root { mustache id("foo"), [boolean("false")] }
|
||||
end
|
||||
|
||||
it "parses mutaches with DATA parameters" do
|
||||
ast_for("{{foo @bar}}").should == root { mustache id("foo"), [data("bar")] }
|
||||
end
|
||||
|
||||
it "parses contents followed by a mustache" do
|
||||
ast_for("foo bar {{baz}}").should == root do
|
||||
content "foo bar "
|
||||
mustache id("baz")
|
||||
end
|
||||
end
|
||||
|
||||
it "parses a partial" do
|
||||
ast_for("{{> foo }}").should == root { partial partial_name("foo") }
|
||||
end
|
||||
|
||||
it "parses a partial with context" do
|
||||
ast_for("{{> foo bar}}").should == root { partial partial_name("foo"), id("bar") }
|
||||
end
|
||||
|
||||
it "parses a partial with a complex name" do
|
||||
ast_for("{{> shared/partial?.bar}}").should == root { partial partial_name("shared/partial?.bar") }
|
||||
end
|
||||
|
||||
it "parses a comment" do
|
||||
ast_for("{{! this is a comment }}").should == root do
|
||||
comment " this is a comment "
|
||||
end
|
||||
end
|
||||
|
||||
it "parses a multi-line comment" do
|
||||
ast_for("{{!\nthis is a multi-line comment\n}}").should == root do
|
||||
multiline_comment "this is a multi-line comment"
|
||||
end
|
||||
end
|
||||
|
||||
it "parses an inverse section" do
|
||||
ast_for("{{#foo}} bar {{^}} baz {{/foo}}").should == root do
|
||||
block do
|
||||
mustache id("foo")
|
||||
|
||||
program do
|
||||
content " bar "
|
||||
end
|
||||
|
||||
inverse do
|
||||
content " baz "
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
it "parses an inverse ('else'-style) section" do
|
||||
ast_for("{{#foo}} bar {{else}} baz {{/foo}}").should == root do
|
||||
block do
|
||||
mustache id("foo")
|
||||
|
||||
program do
|
||||
content " bar "
|
||||
end
|
||||
|
||||
inverse do
|
||||
content " baz "
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
it "parses empty blocks" do
|
||||
ast_for("{{#foo}}{{/foo}}").should == root do
|
||||
block do
|
||||
mustache id("foo")
|
||||
|
||||
program do
|
||||
# empty program
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
it "parses empty blocks with empty inverse section" do
|
||||
ast_for("{{#foo}}{{^}}{{/foo}}").should == root do
|
||||
block do
|
||||
mustache id("foo")
|
||||
|
||||
program do
|
||||
# empty program
|
||||
end
|
||||
|
||||
inverse do
|
||||
# empty inverse
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
it "parses empty blocks with empty inverse ('else'-style) section" do
|
||||
ast_for("{{#foo}}{{else}}{{/foo}}").should == root do
|
||||
block do
|
||||
mustache id("foo")
|
||||
|
||||
program do
|
||||
# empty program
|
||||
end
|
||||
|
||||
inverse do
|
||||
# empty inverse
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
it "parses non-empty blocks with empty inverse section" do
|
||||
ast_for("{{#foo}} bar {{^}}{{/foo}}").should == root do
|
||||
block do
|
||||
mustache id("foo")
|
||||
|
||||
program do
|
||||
content " bar "
|
||||
end
|
||||
|
||||
inverse do
|
||||
# empty inverse
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
it "parses non-empty blocks with empty inverse ('else'-style) section" do
|
||||
ast_for("{{#foo}} bar {{else}}{{/foo}}").should == root do
|
||||
block do
|
||||
mustache id("foo")
|
||||
|
||||
program do
|
||||
content " bar "
|
||||
end
|
||||
|
||||
inverse do
|
||||
# empty inverse
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
it "parses empty blocks with non-empty inverse section" do
|
||||
ast_for("{{#foo}}{{^}} bar {{/foo}}").should == root do
|
||||
block do
|
||||
mustache id("foo")
|
||||
|
||||
program do
|
||||
# empty program
|
||||
end
|
||||
|
||||
inverse do
|
||||
content " bar "
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
it "parses empty blocks with non-empty inverse ('else'-style) section" do
|
||||
ast_for("{{#foo}}{{else}} bar {{/foo}}").should == root do
|
||||
block do
|
||||
mustache id("foo")
|
||||
|
||||
program do
|
||||
# empty program
|
||||
end
|
||||
|
||||
inverse do
|
||||
content " bar "
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
it "parses a standalone inverse section" do
|
||||
ast_for("{{^foo}}bar{{/foo}}").should == root do
|
||||
block do
|
||||
mustache id("foo")
|
||||
|
||||
inverse do
|
||||
content "bar"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
it "raises if there's a Parse error" do
|
||||
lambda { ast_for("{{foo}") }.should raise_error(V8::JSError, /Parse error on line 1/)
|
||||
lambda { ast_for("{{foo &}}")}.should raise_error(V8::JSError, /Parse error on line 1/)
|
||||
lambda { ast_for("{{#goodbyes}}{{/hellos}}") }.should raise_error(V8::JSError, /goodbyes doesn't match hellos/)
|
||||
end
|
||||
|
||||
it "knows how to report the correct line number in errors" do
|
||||
lambda { ast_for("hello\nmy\n{{foo}") }.should raise_error(V8::JSError, /Parse error on line 3/m)
|
||||
lambda { ast_for("hello\n\nmy\n\n{{foo}") }.should raise_error(V8::JSError, /Parse error on line 5/m)
|
||||
end
|
||||
|
||||
it "knows how to report the correct line number in errors when the first character is a newline" do
|
||||
lambda { ast_for("\n\nhello\n\nmy\n\n{{foo}") }.should raise_error(V8::JSError, /Parse error on line 7/m)
|
||||
end
|
||||
|
||||
context "externally compiled AST" do
|
||||
it "can pass through an already-compiled AST" do
|
||||
ast_for(@context.eval('new Handlebars.AST.ProgramNode([ new Handlebars.AST.ContentNode("Hello")]);')).should == root do
|
||||
content "Hello"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,132 @@
|
||||
/*global CompilerContext, shouldCompileTo, shouldCompileToWithPartials */
|
||||
describe('partials', function() {
|
||||
it("basic partials", function() {
|
||||
var string = "Dudes: {{#dudes}}{{> dude}}{{/dudes}}";
|
||||
var partial = "{{name}} ({{url}}) ";
|
||||
var hash = {dudes: [{name: "Yehuda", url: "http://yehuda"}, {name: "Alan", url: "http://alan"}]};
|
||||
shouldCompileToWithPartials(string, [hash, {}, {dude: partial}], true, "Dudes: Yehuda (http://yehuda) Alan (http://alan) ",
|
||||
"Basic partials output based on current context.");
|
||||
});
|
||||
|
||||
it("partials with context", function() {
|
||||
var string = "Dudes: {{>dude dudes}}";
|
||||
var partial = "{{#this}}{{name}} ({{url}}) {{/this}}";
|
||||
var hash = {dudes: [{name: "Yehuda", url: "http://yehuda"}, {name: "Alan", url: "http://alan"}]};
|
||||
shouldCompileToWithPartials(string, [hash, {}, {dude: partial}], true, "Dudes: Yehuda (http://yehuda) Alan (http://alan) ",
|
||||
"Partials can be passed a context");
|
||||
});
|
||||
|
||||
it("partials with undefined context", function() {
|
||||
var string = "Dudes: {{>dude dudes}}";
|
||||
var partial = "{{foo}} Empty";
|
||||
var hash = {};
|
||||
shouldCompileToWithPartials(string, [hash, {}, {dude: partial}], true, "Dudes: Empty");
|
||||
});
|
||||
|
||||
it("partial in a partial", function() {
|
||||
var string = "Dudes: {{#dudes}}{{>dude}}{{/dudes}}";
|
||||
var dude = "{{name}} {{> url}} ";
|
||||
var url = "<a href='{{url}}'>{{url}}</a>";
|
||||
var hash = {dudes: [{name: "Yehuda", url: "http://yehuda"}, {name: "Alan", url: "http://alan"}]};
|
||||
shouldCompileToWithPartials(string, [hash, {}, {dude: dude, url: url}], true, "Dudes: Yehuda <a href='http://yehuda'>http://yehuda</a> Alan <a href='http://alan'>http://alan</a> ", "Partials are rendered inside of other partials");
|
||||
});
|
||||
|
||||
it("rendering undefined partial throws an exception", function() {
|
||||
shouldThrow(function() {
|
||||
var template = CompilerContext.compile("{{> whatever}}");
|
||||
template();
|
||||
}, Handlebars.Exception, 'The partial whatever could not be found');
|
||||
});
|
||||
|
||||
it("rendering template partial in vm mode throws an exception", function() {
|
||||
shouldThrow(function() {
|
||||
var template = CompilerContext.compile("{{> whatever}}");
|
||||
template();
|
||||
}, Handlebars.Exception, 'The partial whatever could not be found');
|
||||
});
|
||||
|
||||
it("rendering function partial in vm mode", function() {
|
||||
var string = "Dudes: {{#dudes}}{{> dude}}{{/dudes}}";
|
||||
var partial = function(context) {
|
||||
return context.name + ' (' + context.url + ') ';
|
||||
};
|
||||
var hash = {dudes: [{name: "Yehuda", url: "http://yehuda"}, {name: "Alan", url: "http://alan"}]};
|
||||
shouldCompileTo(string, [hash, {}, {dude: partial}], "Dudes: Yehuda (http://yehuda) Alan (http://alan) ",
|
||||
"Function partials output based in VM.");
|
||||
});
|
||||
|
||||
it("GH-14: a partial preceding a selector", function() {
|
||||
var string = "Dudes: {{>dude}} {{another_dude}}";
|
||||
var dude = "{{name}}";
|
||||
var hash = {name:"Jeepers", another_dude:"Creepers"};
|
||||
shouldCompileToWithPartials(string, [hash, {}, {dude:dude}], true, "Dudes: Jeepers Creepers", "Regular selectors can follow a partial");
|
||||
});
|
||||
|
||||
it("Partials with slash paths", function() {
|
||||
var string = "Dudes: {{> shared/dude}}";
|
||||
var dude = "{{name}}";
|
||||
var hash = {name:"Jeepers", another_dude:"Creepers"};
|
||||
shouldCompileToWithPartials(string, [hash, {}, {'shared/dude':dude}], true, "Dudes: Jeepers", "Partials can use literal paths");
|
||||
});
|
||||
|
||||
it("Partials with slash and point paths", function() {
|
||||
var string = "Dudes: {{> shared/dude.thing}}";
|
||||
var dude = "{{name}}";
|
||||
var hash = {name:"Jeepers", another_dude:"Creepers"};
|
||||
shouldCompileToWithPartials(string, [hash, {}, {'shared/dude.thing':dude}], true, "Dudes: Jeepers", "Partials can use literal with points in paths");
|
||||
});
|
||||
|
||||
it("Global Partials", function() {
|
||||
handlebarsEnv.registerPartial('global_test', '{{another_dude}}');
|
||||
|
||||
var string = "Dudes: {{> shared/dude}} {{> global_test}}";
|
||||
var dude = "{{name}}";
|
||||
var hash = {name:"Jeepers", another_dude:"Creepers"};
|
||||
shouldCompileToWithPartials(string, [hash, {}, {'shared/dude':dude}], true, "Dudes: Jeepers Creepers", "Partials can use globals or passed");
|
||||
});
|
||||
|
||||
it("Multiple partial registration", function() {
|
||||
handlebarsEnv.registerPartial({
|
||||
'shared/dude': '{{name}}',
|
||||
global_test: '{{another_dude}}'
|
||||
});
|
||||
|
||||
var string = "Dudes: {{> shared/dude}} {{> global_test}}";
|
||||
var hash = {name:"Jeepers", another_dude:"Creepers"};
|
||||
shouldCompileToWithPartials(string, [hash], true, "Dudes: Jeepers Creepers", "Partials can use globals or passed");
|
||||
});
|
||||
|
||||
it("Partials with integer path", function() {
|
||||
var string = "Dudes: {{> 404}}";
|
||||
var dude = "{{name}}";
|
||||
var hash = {name:"Jeepers", another_dude:"Creepers"};
|
||||
shouldCompileToWithPartials(string, [hash, {}, {404:dude}], true, "Dudes: Jeepers", "Partials can use literal paths");
|
||||
});
|
||||
|
||||
it("Partials with complex path", function() {
|
||||
var string = "Dudes: {{> 404/asdf?.bar}}";
|
||||
var dude = "{{name}}";
|
||||
var hash = {name:"Jeepers", another_dude:"Creepers"};
|
||||
shouldCompileToWithPartials(string, [hash, {}, {'404/asdf?.bar':dude}], true, "Dudes: Jeepers", "Partials can use literal paths");
|
||||
});
|
||||
|
||||
it("Partials with escaped", function() {
|
||||
var string = "Dudes: {{> [+404/asdf?.bar]}}";
|
||||
var dude = "{{name}}";
|
||||
var hash = {name:"Jeepers", another_dude:"Creepers"};
|
||||
shouldCompileToWithPartials(string, [hash, {}, {'+404/asdf?.bar':dude}], true, "Dudes: Jeepers", "Partials can use literal paths");
|
||||
});
|
||||
|
||||
it("Partials with string", function() {
|
||||
var string = "Dudes: {{> \"+404/asdf?.bar\"}}";
|
||||
var dude = "{{name}}";
|
||||
var hash = {name:"Jeepers", another_dude:"Creepers"};
|
||||
shouldCompileToWithPartials(string, [hash, {}, {'+404/asdf?.bar':dude}], true, "Dudes: Jeepers", "Partials can use literal paths");
|
||||
});
|
||||
|
||||
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: "); });
|
||||
});
|
||||
-1657
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,130 @@
|
||||
/*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"}]};
|
||||
var string = "{{#books}}{{title}}{{author.name}}{{/books}}";
|
||||
shouldCompileTo(string, data, "The origin of speciesCharles DarwinLazarillo de Tormes",
|
||||
"Renders without an undefined property error");
|
||||
});
|
||||
|
||||
it("GH-150: Inverted sections print when they shouldn't", function() {
|
||||
var string = "{{^set}}not set{{/set}} :: {{#set}}set{{/set}}";
|
||||
|
||||
shouldCompileTo(string, {}, "not set :: ", "inverted sections run when property isn't present in context");
|
||||
shouldCompileTo(string, {set: undefined}, "not set :: ", "inverted sections run when property is undefined");
|
||||
shouldCompileTo(string, {set: false}, "not set :: ", "inverted sections run when property is false");
|
||||
shouldCompileTo(string, {set: true}, " :: set", "inverted sections don't run when property is true");
|
||||
});
|
||||
|
||||
it("GH-158: Using array index twice, breaks the template", function() {
|
||||
var string = "{{arr.[0]}}, {{arr.[1]}}";
|
||||
var data = { "arr": [1,2] };
|
||||
|
||||
shouldCompileTo(string, data, "1, 2", "it works as expected");
|
||||
});
|
||||
|
||||
it("bug reported by @fat where lambdas weren't being properly resolved", function() {
|
||||
var string = "<strong>This is a slightly more complicated {{thing}}.</strong>.\n{{! Just ignore this business. }}\nCheck this out:\n{{#hasThings}}\n<ul>\n{{#things}}\n<li class={{className}}>{{word}}</li>\n{{/things}}</ul>.\n{{/hasThings}}\n{{^hasThings}}\n\n<small>Nothing to check out...</small>\n{{/hasThings}}";
|
||||
var data = {
|
||||
thing: function() {
|
||||
return "blah";
|
||||
},
|
||||
things: [
|
||||
{className: "one", word: "@fat"},
|
||||
{className: "two", word: "@dhg"},
|
||||
{className: "three", word:"@sayrer"}
|
||||
],
|
||||
hasThings: function() {
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
var output = "<strong>This is a slightly more complicated blah.</strong>.\n\nCheck this out:\n\n<ul>\n\n<li class=one>@fat</li>\n\n<li class=two>@dhg</li>\n\n<li class=three>@sayrer</li>\n</ul>.\n\n";
|
||||
shouldCompileTo(string, data, output);
|
||||
});
|
||||
|
||||
it('GH-408: Multiple loops fail', function() {
|
||||
var context = [
|
||||
{ name: "John Doe", location: { city: "Chicago" } },
|
||||
{ name: "Jane Doe", location: { city: "New York"} }
|
||||
];
|
||||
|
||||
var template = CompilerContext.compile('{{#.}}{{name}}{{/.}}{{#.}}{{name}}{{/.}}{{#.}}{{name}}{{/.}}');
|
||||
|
||||
var result = template(context);
|
||||
equals(result, "John DoeJane DoeJohn DoeJane DoeJohn DoeJane Doe", 'It should output multiple times');
|
||||
});
|
||||
|
||||
it('GS-428: Nested if else rendering', function() {
|
||||
var succeedingTemplate = '{{#inverse}} {{#blk}} Unexpected {{/blk}} {{else}} {{#blk}} Expected {{/blk}} {{/inverse}}';
|
||||
var failingTemplate = '{{#inverse}} {{#blk}} Unexpected {{/blk}} {{else}} {{#blk}} Expected {{/blk}} {{/inverse}}';
|
||||
|
||||
var helpers = {
|
||||
blk: function(block) { return block.fn(''); },
|
||||
inverse: function(block) { return block.inverse(''); }
|
||||
};
|
||||
|
||||
shouldCompileTo(succeedingTemplate, [{}, helpers], ' Expected ');
|
||||
shouldCompileTo(failingTemplate, [{}, helpers], ' Expected ');
|
||||
});
|
||||
|
||||
it('GH-458: Scoped this identifier', function() {
|
||||
shouldCompileTo('{{./foo}}', {foo: 'bar'}, 'bar');
|
||||
});
|
||||
|
||||
it('GH-375: Unicode line terminators', function() {
|
||||
shouldCompileTo('\u2028', {}, '\u2028');
|
||||
});
|
||||
|
||||
it('GH-534: Object prototype aliases', function() {
|
||||
Object.prototype[0xD834] = true;
|
||||
|
||||
shouldCompileTo('{{foo}}', { foo: 'bar' }, 'bar');
|
||||
|
||||
delete Object.prototype[0xD834];
|
||||
});
|
||||
|
||||
it('GH-437: Matching escaping', function() {
|
||||
shouldThrow(function() {
|
||||
CompilerContext.compile('{{{a}}');
|
||||
}, Error);
|
||||
shouldThrow(function() {
|
||||
CompilerContext.compile('{{a}}}');
|
||||
}, 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() {
|
||||
var string = "Hello {{name}}. You have just won ${{value}}!{{#in_ca}} Well, ${{taxed_value}}, after taxes.{{/in_ca}}";
|
||||
var data = {
|
||||
"name": "Chris",
|
||||
"value": 10000,
|
||||
"taxed_value": 10000 - (10000 * 0.4),
|
||||
"in_ca": true
|
||||
};
|
||||
|
||||
shouldCompileTo(string, data, "Hello Chris. You have just won $10000! Well, $6000, after taxes.", "the hello world mustache example works");
|
||||
});
|
||||
|
||||
it("Passing falsy values to Handlebars.compile throws an error", function() {
|
||||
shouldThrow(function() {
|
||||
CompilerContext.compile(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('')(), '');
|
||||
});
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,23 @@
|
||||
if (typeof(require) !== 'undefined' && require.extensions[".handlebars"]) {
|
||||
describe('Require', function() {
|
||||
it('Load .handlebars files with require()', function() {
|
||||
var template = require("./artifacts/example_1");
|
||||
equal(template, require("./artifacts/example_1.handlebars"));
|
||||
|
||||
var expected = 'foo\n';
|
||||
var result = template({foo: "foo"});
|
||||
|
||||
equal(result, expected);
|
||||
});
|
||||
|
||||
it('Load .hbs files with require()', function() {
|
||||
var template = require("./artifacts/example_2");
|
||||
equal(template, require("./artifacts/example_2.hbs"));
|
||||
|
||||
var expected = 'Hello, World!\n';
|
||||
var result = template({name: "World"});
|
||||
|
||||
equal(result, expected);
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -1,132 +0,0 @@
|
||||
require "v8"
|
||||
|
||||
# Monkey patches due to bugs in RubyRacer
|
||||
class V8::JSError
|
||||
def initialize(try, to)
|
||||
@to = to
|
||||
begin
|
||||
super(initialize_unsafe(try))
|
||||
rescue Exception => e
|
||||
# Original code does not make an Array here
|
||||
@boundaries = [Boundary.new(:rbframes => e.backtrace)]
|
||||
@value = e
|
||||
super("BUG! please report. JSError#initialize failed!: #{e.message}")
|
||||
end
|
||||
end
|
||||
|
||||
def parse_js_frames(try)
|
||||
raw = @to.rb(try.StackTrace())
|
||||
if raw && !raw.empty?
|
||||
raw.split("\n")[1..-1].tap do |frames|
|
||||
# Original code uses strip!, and the frames are not guaranteed to be strippable
|
||||
frames.each {|frame| frame.strip.chomp!(",")}
|
||||
end
|
||||
else
|
||||
[]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
module Handlebars
|
||||
module Spec
|
||||
def self.js_backtrace(context)
|
||||
begin
|
||||
context.eval("throw")
|
||||
rescue V8::JSError => e
|
||||
return e.backtrace(:javascript)
|
||||
end
|
||||
end
|
||||
|
||||
def self.remove_exports(string)
|
||||
match = string.match(%r{\A(.*?)^// BEGIN\(BROWSER\)\n(.*)\n^// END\(BROWSER\)(.*?)\Z}m)
|
||||
prelines = match ? match[1].count("\n") + 1 : 0
|
||||
ret = match ? match[2] : string
|
||||
("\n" * prelines) + ret
|
||||
end
|
||||
|
||||
def self.load_helpers(context)
|
||||
context["exports"] = nil
|
||||
|
||||
context["p"] = proc do |this, val|
|
||||
p val if ENV["DEBUG_JS"]
|
||||
end
|
||||
|
||||
context["puts"] = proc do |this, val|
|
||||
puts val if ENV["DEBUG_JS"]
|
||||
end
|
||||
|
||||
context["puts_node"] = proc do |this, val|
|
||||
puts context["Handlebars"]["PrintVisitor"].new.accept(val)
|
||||
puts
|
||||
end
|
||||
|
||||
context["puts_caller"] = proc do
|
||||
puts "BACKTRACE:"
|
||||
puts Handlebars::Spec.js_backtrace(context)
|
||||
puts
|
||||
end
|
||||
end
|
||||
|
||||
def self.js_load(context, file)
|
||||
str = File.read(file)
|
||||
context.eval(remove_exports(str), file)
|
||||
end
|
||||
|
||||
CONTEXT = V8::Context.new
|
||||
CONTEXT.instance_eval do |context|
|
||||
Handlebars::Spec.load_helpers(context);
|
||||
|
||||
Handlebars::Spec.js_load(context, 'dist/handlebars.js');
|
||||
|
||||
context["CompilerContext"] = {}
|
||||
CompilerContext = context["CompilerContext"]
|
||||
CompilerContext["compile"] = proc do |this, *args|
|
||||
template, options = args[0], args[1] || nil
|
||||
templateSpec = COMPILE_CONTEXT["Handlebars"]["precompile"].call(template, options);
|
||||
context["Handlebars"]["template"].call(context.eval("(#{templateSpec})"));
|
||||
end
|
||||
CompilerContext["compileWithPartial"] = proc do |this, *args|
|
||||
template, options = args[0], args[1] || nil
|
||||
context["Handlebars"]["compile"].call(template, options);
|
||||
end
|
||||
end
|
||||
|
||||
PARSER_CONTEXT = V8::Context.new
|
||||
PARSER_CONTEXT.instance_eval do |context|
|
||||
Handlebars::Spec.load_helpers(context);
|
||||
|
||||
Handlebars::Spec.js_load(context, 'lib/handlebars/compiler/parser.js');
|
||||
end
|
||||
|
||||
COMPILE_CONTEXT = V8::Context.new
|
||||
COMPILE_CONTEXT.instance_eval do |context|
|
||||
Handlebars::Spec.load_helpers(context);
|
||||
|
||||
Handlebars::Spec.js_load(context, 'dist/handlebars.js');
|
||||
Handlebars::Spec.js_load(context, 'lib/handlebars/compiler/visitor.js');
|
||||
Handlebars::Spec.js_load(context, 'lib/handlebars/compiler/printer.js');
|
||||
|
||||
context["Handlebars"]["logger"]["level"] = ENV["DEBUG_JS"] ? context["Handlebars"]["logger"][ENV["DEBUG_JS"]] : 4
|
||||
|
||||
context["Handlebars"]["logger"]["log"] = proc do |this, level, str|
|
||||
logger_level = context["Handlebars"]["logger"]["level"].to_i
|
||||
|
||||
if logger_level <= level
|
||||
puts str
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
require "test/unit/assertions"
|
||||
|
||||
RSpec.configure do |config|
|
||||
config.include Test::Unit::Assertions
|
||||
|
||||
# Each is required to allow classes to mark themselves as compiler tests
|
||||
config.before(:each) do
|
||||
@context = @compiles ? Handlebars::Spec::COMPILE_CONTEXT : Handlebars::Spec::CONTEXT
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,145 @@
|
||||
describe('string params mode', function() {
|
||||
it("arguments to helpers can be retrieved from options hash in string form", function() {
|
||||
var template = CompilerContext.compile('{{wycats is.a slave.driver}}', {stringParams: true});
|
||||
|
||||
var helpers = {
|
||||
wycats: function(passiveVoice, noun) {
|
||||
return "HELP ME MY BOSS " + passiveVoice + ' ' + noun;
|
||||
}
|
||||
};
|
||||
|
||||
var result = template({}, {helpers: helpers});
|
||||
|
||||
equals(result, "HELP ME MY BOSS is.a slave.driver", "String parameters output");
|
||||
});
|
||||
|
||||
it("when using block form, arguments to helpers can be retrieved from options hash in string form", function() {
|
||||
var template = CompilerContext.compile('{{#wycats is.a slave.driver}}help :({{/wycats}}', {stringParams: true});
|
||||
|
||||
var helpers = {
|
||||
wycats: function(passiveVoice, noun, options) {
|
||||
return "HELP ME MY BOSS " + passiveVoice + ' ' +
|
||||
noun + ': ' + options.fn(this);
|
||||
}
|
||||
};
|
||||
|
||||
var result = template({}, {helpers: helpers});
|
||||
|
||||
equals(result, "HELP ME MY BOSS is.a slave.driver: help :(", "String parameters output");
|
||||
});
|
||||
|
||||
it("when inside a block in String mode, .. passes the appropriate context in the options hash", function() {
|
||||
var template = CompilerContext.compile('{{#with dale}}{{tomdale ../need dad.joke}}{{/with}}', {stringParams: true});
|
||||
|
||||
var helpers = {
|
||||
tomdale: function(desire, noun, options) {
|
||||
return "STOP ME FROM READING HACKER NEWS I " +
|
||||
options.contexts[0][desire] + " " + noun;
|
||||
},
|
||||
|
||||
"with": function(context, options) {
|
||||
return options.fn(options.contexts[0][context]);
|
||||
}
|
||||
};
|
||||
|
||||
var result = template({
|
||||
dale: {},
|
||||
|
||||
need: 'need-a'
|
||||
}, {helpers: helpers});
|
||||
|
||||
equals(result, "STOP ME FROM READING HACKER NEWS I need-a dad.joke", "Proper context variable output");
|
||||
});
|
||||
|
||||
it("information about the types is passed along", function() {
|
||||
var template = CompilerContext.compile('{{tomdale "need" dad.joke true false}}', { stringParams: true });
|
||||
|
||||
var helpers = {
|
||||
tomdale: function(desire, noun, trueBool, falseBool, options) {
|
||||
equal(options.types[0], 'STRING', "the string type is passed");
|
||||
equal(options.types[1], 'ID', "the expression type is passed");
|
||||
equal(options.types[2], 'BOOLEAN', "the expression type is passed");
|
||||
equal(desire, "need", "the string form is passed for strings");
|
||||
equal(noun, "dad.joke", "the string form is passed for expressions");
|
||||
equal(trueBool, true, "raw booleans are passed through");
|
||||
equal(falseBool, false, "raw booleans are passed through");
|
||||
return "Helper called";
|
||||
}
|
||||
};
|
||||
|
||||
var result = template({}, { helpers: helpers });
|
||||
equal(result, "Helper called");
|
||||
});
|
||||
|
||||
it("hash parameters get type information", function() {
|
||||
var template = CompilerContext.compile('{{tomdale he.says desire="need" noun=dad.joke bool=true}}', { stringParams: true });
|
||||
|
||||
var helpers = {
|
||||
tomdale: function(exclamation, options) {
|
||||
equal(exclamation, "he.says");
|
||||
equal(options.types[0], "ID");
|
||||
|
||||
equal(options.hashTypes.desire, "STRING");
|
||||
equal(options.hashTypes.noun, "ID");
|
||||
equal(options.hashTypes.bool, "BOOLEAN");
|
||||
equal(options.hash.desire, "need");
|
||||
equal(options.hash.noun, "dad.joke");
|
||||
equal(options.hash.bool, true);
|
||||
return "Helper called";
|
||||
}
|
||||
};
|
||||
|
||||
var result = template({}, { helpers: helpers });
|
||||
equal(result, "Helper called");
|
||||
});
|
||||
|
||||
it("hash parameters get context information", function() {
|
||||
var template = CompilerContext.compile('{{#with dale}}{{tomdale he.says desire="need" noun=../dad/joke bool=true}}{{/with}}', { stringParams: true });
|
||||
|
||||
var context = {dale: {}};
|
||||
|
||||
var helpers = {
|
||||
tomdale: function(exclamation, options) {
|
||||
equal(exclamation, "he.says");
|
||||
equal(options.types[0], "ID");
|
||||
|
||||
equal(options.contexts.length, 1);
|
||||
equal(options.hashContexts.noun, context);
|
||||
equal(options.hash.desire, "need");
|
||||
equal(options.hash.noun, "dad.joke");
|
||||
equal(options.hash.bool, true);
|
||||
return "Helper called";
|
||||
},
|
||||
"with": function(context, options) {
|
||||
return options.fn(options.contexts[0][context]);
|
||||
}
|
||||
};
|
||||
|
||||
var result = template(context, { helpers: helpers });
|
||||
equal(result, "Helper called");
|
||||
});
|
||||
|
||||
it("when inside a block in String mode, .. passes the appropriate context in the options hash to a block helper", function() {
|
||||
var template = CompilerContext.compile('{{#with dale}}{{#tomdale ../need dad.joke}}wot{{/tomdale}}{{/with}}', {stringParams: true});
|
||||
|
||||
var helpers = {
|
||||
tomdale: function(desire, noun, options) {
|
||||
return "STOP ME FROM READING HACKER NEWS I " +
|
||||
options.contexts[0][desire] + " " + noun + " " +
|
||||
options.fn(this);
|
||||
},
|
||||
|
||||
"with": function(context, options) {
|
||||
return options.fn(options.contexts[0][context]);
|
||||
}
|
||||
};
|
||||
|
||||
var result = template({
|
||||
dale: {},
|
||||
|
||||
need: 'need-a'
|
||||
}, {helpers: helpers});
|
||||
|
||||
equals(result, "STOP ME FROM READING HACKER NEWS I need-a dad.joke wot", "Proper context variable output");
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,367 @@
|
||||
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) {
|
||||
return;
|
||||
}
|
||||
|
||||
function tokenize(template) {
|
||||
var parser = Handlebars.Parser,
|
||||
lexer = parser.lexer;
|
||||
|
||||
lexer.setInput(template);
|
||||
var out = [],
|
||||
token;
|
||||
|
||||
while (token = lexer.lex()) {
|
||||
var result = parser.terminals_[token] || token;
|
||||
if (!result || result === 'EOF' || result === 'INVALID') {
|
||||
break;
|
||||
}
|
||||
out.push({name: result, text: lexer.yytext});
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
it('tokenizes a simple mustache as "OPEN ID CLOSE"', function() {
|
||||
var result = tokenize("{{foo}}");
|
||||
shouldMatchTokens(result, ['OPEN', 'ID', 'CLOSE']);
|
||||
shouldBeToken(result[1], "ID", "foo");
|
||||
});
|
||||
|
||||
it('supports unescaping with &', function() {
|
||||
var result = tokenize("{{&bar}}");
|
||||
shouldMatchTokens(result, ['OPEN', 'ID', 'CLOSE']);
|
||||
|
||||
shouldBeToken(result[0], "OPEN", "{{&");
|
||||
shouldBeToken(result[1], "ID", "bar");
|
||||
});
|
||||
|
||||
it('supports unescaping with {{{', function() {
|
||||
var result = tokenize("{{{bar}}}");
|
||||
shouldMatchTokens(result, ['OPEN_UNESCAPED', 'ID', 'CLOSE_UNESCAPED']);
|
||||
|
||||
shouldBeToken(result[1], "ID", "bar");
|
||||
});
|
||||
|
||||
it('supports escaping delimiters', function() {
|
||||
var result = tokenize("{{foo}} \\{{bar}} {{baz}}");
|
||||
shouldMatchTokens(result, ['OPEN', 'ID', 'CLOSE', 'CONTENT', 'CONTENT', 'OPEN', 'ID', 'CLOSE']);
|
||||
|
||||
shouldBeToken(result[3], "CONTENT", " ");
|
||||
shouldBeToken(result[4], "CONTENT", "{{bar}} ");
|
||||
});
|
||||
|
||||
it('supports escaping multiple delimiters', function() {
|
||||
var result = tokenize("{{foo}} \\{{bar}} \\{{baz}}");
|
||||
shouldMatchTokens(result, ['OPEN', 'ID', 'CLOSE', 'CONTENT', 'CONTENT', 'CONTENT']);
|
||||
|
||||
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}}");
|
||||
shouldMatchTokens(result, ['OPEN', 'ID', 'CLOSE', 'CONTENT', 'CONTENT', 'OPEN', 'ID', 'CLOSE']);
|
||||
|
||||
shouldBeToken(result[4], "CONTENT", "{{{bar}}} ");
|
||||
});
|
||||
|
||||
it('supports escaping escape character', function() {
|
||||
var result = tokenize("{{foo}} \\\\{{bar}} {{baz}}");
|
||||
shouldMatchTokens(result, ['OPEN', 'ID', 'CLOSE', 'CONTENT', 'OPEN', 'ID', 'CLOSE', 'CONTENT', 'OPEN', 'ID', 'CLOSE']);
|
||||
|
||||
shouldBeToken(result[3], "CONTENT", " \\");
|
||||
shouldBeToken(result[5], "ID", "bar");
|
||||
});
|
||||
|
||||
it('supports escaping multiple escape characters', function() {
|
||||
var result = tokenize("{{foo}} \\\\{{bar}} \\\\{{baz}}");
|
||||
shouldMatchTokens(result, ['OPEN', 'ID', 'CLOSE', 'CONTENT', 'OPEN', 'ID', 'CLOSE', 'CONTENT', 'OPEN', 'ID', 'CLOSE']);
|
||||
|
||||
shouldBeToken(result[3], "CONTENT", " \\");
|
||||
shouldBeToken(result[5], "ID", "bar");
|
||||
shouldBeToken(result[7], "CONTENT", " \\");
|
||||
shouldBeToken(result[9], "ID", "baz");
|
||||
});
|
||||
|
||||
it('supports escaped mustaches after escaped escape characters', function() {
|
||||
var result = tokenize("{{foo}} \\\\{{bar}} \\{{baz}}");
|
||||
shouldMatchTokens(result, ['OPEN', 'ID', 'CLOSE', 'CONTENT', 'OPEN', 'ID', 'CLOSE', 'CONTENT', 'CONTENT', 'CONTENT']);
|
||||
|
||||
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}}");
|
||||
shouldMatchTokens(result, ['OPEN', 'ID', 'CLOSE', 'CONTENT', 'OPEN_UNESCAPED', 'ID', 'CLOSE_UNESCAPED', 'CONTENT', 'OPEN', 'ID', 'CLOSE']);
|
||||
|
||||
shouldBeToken(result[3], "CONTENT", " \\");
|
||||
shouldBeToken(result[5], "ID", "bar");
|
||||
});
|
||||
|
||||
it('tokenizes a simple path', function() {
|
||||
var result = tokenize("{{foo/bar}}");
|
||||
shouldMatchTokens(result, ['OPEN', 'ID', 'SEP', 'ID', 'CLOSE']);
|
||||
});
|
||||
|
||||
it('allows dot notation', function() {
|
||||
var result = tokenize("{{foo.bar}}");
|
||||
shouldMatchTokens(result, ['OPEN', '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]}}");
|
||||
shouldMatchTokens(result, ['OPEN', 'ID', 'SEP', 'ID', 'CLOSE']);
|
||||
});
|
||||
|
||||
it('allows multiple path literals on a line with []', function() {
|
||||
var result = tokenize("{{foo.[bar]}}{{foo.[baz]}}");
|
||||
shouldMatchTokens(result, ['OPEN', 'ID', 'SEP', 'ID', 'CLOSE', 'OPEN', 'ID', 'SEP', 'ID', 'CLOSE']);
|
||||
});
|
||||
|
||||
it('tokenizes {{.}} as OPEN ID CLOSE', function() {
|
||||
var result = tokenize("{{.}}");
|
||||
shouldMatchTokens(result, ['OPEN', 'ID', 'CLOSE']);
|
||||
});
|
||||
|
||||
it('tokenizes a path as "OPEN (ID SEP)* ID CLOSE"', function() {
|
||||
var result = tokenize("{{../foo/bar}}");
|
||||
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}}");
|
||||
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}}");
|
||||
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 }}");
|
||||
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 }}");
|
||||
shouldMatchTokens(result, ['OPEN', 'ID', 'ID', 'CLOSE']);
|
||||
shouldBeToken(result[1], "ID", "foo");
|
||||
});
|
||||
|
||||
it('tokenizes raw content as "CONTENT"', function() {
|
||||
var result = tokenize("foo {{ bar }} 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}}");
|
||||
shouldMatchTokens(result, ['OPEN_PARTIAL', 'ID', 'CLOSE']);
|
||||
});
|
||||
|
||||
it('tokenizes a partial with context as "OPEN_PARTIAL ID ID CLOSE"', function() {
|
||||
var result = tokenize("{{> foo bar }}");
|
||||
shouldMatchTokens(result, ['OPEN_PARTIAL', 'ID', 'ID', 'CLOSE']);
|
||||
});
|
||||
|
||||
it('tokenizes a partial without spaces as "OPEN_PARTIAL ID CLOSE"', function() {
|
||||
var result = tokenize("{{>foo}}");
|
||||
shouldMatchTokens(result, ['OPEN_PARTIAL', 'ID', 'CLOSE']);
|
||||
});
|
||||
|
||||
it('tokenizes a partial space at the }); as "OPEN_PARTIAL ID CLOSE"', function() {
|
||||
var result = tokenize("{{>foo }}");
|
||||
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 }}");
|
||||
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 }}");
|
||||
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 }}");
|
||||
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 }}");
|
||||
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}}");
|
||||
shouldMatchTokens(result, ['OPEN_BLOCK', 'ID', 'CLOSE', 'CONTENT', 'OPEN_ENDBLOCK', 'ID', 'CLOSE']);
|
||||
});
|
||||
|
||||
it('tokenizes inverse sections as "OPEN_INVERSE CLOSE"', function() {
|
||||
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}}");
|
||||
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 }}");
|
||||
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 }}");
|
||||
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\" }}");
|
||||
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\' }}");
|
||||
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\" }}");
|
||||
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" }}');
|
||||
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' }}");
|
||||
shouldMatchTokens(result, ['OPEN', 'ID', 'STRING', 'CLOSE']);
|
||||
shouldBeToken(result[2], "STRING", "bar'baz");
|
||||
});
|
||||
|
||||
it('tokenizes numbers', function() {
|
||||
var result = tokenize('{{ foo 1 }}');
|
||||
shouldMatchTokens(result, ['OPEN', 'ID', 'INTEGER', 'CLOSE']);
|
||||
shouldBeToken(result[2], "INTEGER", "1");
|
||||
|
||||
result = tokenize('{{ foo -1 }}');
|
||||
shouldMatchTokens(result, ['OPEN', 'ID', 'INTEGER', 'CLOSE']);
|
||||
shouldBeToken(result[2], "INTEGER", "-1");
|
||||
});
|
||||
|
||||
it('tokenizes booleans', function() {
|
||||
var result = tokenize('{{ foo true }}');
|
||||
shouldMatchTokens(result, ['OPEN', 'ID', 'BOOLEAN', 'CLOSE']);
|
||||
shouldBeToken(result[2], "BOOLEAN", "true");
|
||||
|
||||
result = tokenize('{{ foo false }}');
|
||||
shouldMatchTokens(result, ['OPEN', 'ID', 'BOOLEAN', 'CLOSE']);
|
||||
shouldBeToken(result[2], "BOOLEAN", "false");
|
||||
});
|
||||
|
||||
it('tokenizes hash arguments', function() {
|
||||
var result = tokenize("{{ foo bar=baz }}");
|
||||
shouldMatchTokens(result, ['OPEN', 'ID', 'ID', 'EQUALS', 'ID', 'CLOSE']);
|
||||
|
||||
result = tokenize("{{ foo bar baz=bat }}");
|
||||
shouldMatchTokens(result, ['OPEN', 'ID', 'ID', 'ID', 'EQUALS', 'ID', 'CLOSE']);
|
||||
|
||||
result = tokenize("{{ foo bar baz=1 }}");
|
||||
shouldMatchTokens(result, ['OPEN', 'ID', 'ID', 'ID', 'EQUALS', 'INTEGER', 'CLOSE']);
|
||||
|
||||
result = tokenize("{{ foo bar baz=true }}");
|
||||
shouldMatchTokens(result, ['OPEN', 'ID', 'ID', 'ID', 'EQUALS', 'BOOLEAN', 'CLOSE']);
|
||||
|
||||
result = tokenize("{{ foo bar baz=false }}");
|
||||
shouldMatchTokens(result, ['OPEN', 'ID', 'ID', 'ID', 'EQUALS', 'BOOLEAN', 'CLOSE']);
|
||||
|
||||
result = tokenize("{{ foo bar\n baz=bat }}");
|
||||
shouldMatchTokens(result, ['OPEN', 'ID', 'ID', 'ID', 'EQUALS', 'ID', 'CLOSE']);
|
||||
|
||||
result = tokenize("{{ foo bar baz=\"bat\" }}");
|
||||
shouldMatchTokens(result, ['OPEN', 'ID', 'ID', 'ID', 'EQUALS', 'STRING', 'CLOSE']);
|
||||
|
||||
result = tokenize("{{ foo bar baz=\"bat\" bam=wot }}");
|
||||
shouldMatchTokens(result, ['OPEN', 'ID', 'ID', 'ID', 'EQUALS', 'STRING', 'ID', 'EQUALS', 'ID', 'CLOSE']);
|
||||
|
||||
result = tokenize("{{foo omg bar=baz bat=\"bam\"}}");
|
||||
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 }}");
|
||||
shouldMatchTokens(result, ['OPEN', 'DATA', 'ID', 'CLOSE']);
|
||||
shouldBeToken(result[2], "ID", "foo");
|
||||
|
||||
result = tokenize("{{ foo @bar }}");
|
||||
shouldMatchTokens(result, ['OPEN', 'ID', 'DATA', 'ID', 'CLOSE']);
|
||||
shouldBeToken(result[3], "ID", "bar");
|
||||
|
||||
result = tokenize("{{ foo bar=@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() {
|
||||
shouldMatchTokens(tokenize("{{foo}"), ['OPEN', 'ID']);
|
||||
});
|
||||
|
||||
it('does not time out in a mustache when invalid ID characters are used', function() {
|
||||
shouldMatchTokens(tokenize("{{foo & }}"), ['OPEN', 'ID']);
|
||||
});
|
||||
});
|
||||
@@ -1,322 +0,0 @@
|
||||
require "spec_helper"
|
||||
require "timeout"
|
||||
|
||||
describe "Tokenizer" do
|
||||
let(:parser) { Handlebars::Spec::PARSER_CONTEXT["handlebars"] }
|
||||
let(:lexer) { Handlebars::Spec::PARSER_CONTEXT["handlebars"]["lexer"] }
|
||||
|
||||
Token = Struct.new(:name, :text)
|
||||
|
||||
def tokenize(string)
|
||||
lexer.setInput(string)
|
||||
out = []
|
||||
|
||||
while token = lexer.lex
|
||||
# p token
|
||||
result = parser.terminals_[token] || token
|
||||
# p result
|
||||
break if !result || result == "EOF" || result == "INVALID"
|
||||
out << Token.new(result, lexer.yytext)
|
||||
end
|
||||
|
||||
out
|
||||
end
|
||||
|
||||
RSpec::Matchers.define :match_tokens do |tokens|
|
||||
match do |result|
|
||||
result.map(&:name).should == tokens
|
||||
end
|
||||
end
|
||||
|
||||
RSpec::Matchers.define :be_token do |name, string|
|
||||
match do |token|
|
||||
token.name.should == name
|
||||
token.text.should == string
|
||||
end
|
||||
end
|
||||
|
||||
it "tokenizes a simple mustache as 'OPEN ID CLOSE'" do
|
||||
result = tokenize("{{foo}}")
|
||||
result.should match_tokens(%w(OPEN ID CLOSE))
|
||||
result[1].should be_token("ID", "foo")
|
||||
end
|
||||
|
||||
it "supports unescaping with &" do
|
||||
result = tokenize("{{&bar}}")
|
||||
result.should match_tokens(%w(OPEN ID CLOSE))
|
||||
|
||||
result[0].should be_token("OPEN", "{{&")
|
||||
result[1].should be_token("ID", "bar")
|
||||
end
|
||||
|
||||
it "supports unescaping with {{{" do
|
||||
result = tokenize("{{{bar}}}")
|
||||
result.should match_tokens(%w(OPEN_UNESCAPED ID CLOSE_UNESCAPED))
|
||||
|
||||
result[1].should be_token("ID", "bar")
|
||||
end
|
||||
|
||||
it "supports escaping delimiters" do
|
||||
result = tokenize("{{foo}} \\{{bar}} {{baz}}")
|
||||
result.should match_tokens(%w(OPEN ID CLOSE CONTENT CONTENT OPEN ID CLOSE))
|
||||
|
||||
result[4].should be_token("CONTENT", "{{bar}} ")
|
||||
end
|
||||
|
||||
it "supports escaping multiple delimiters" do
|
||||
result = tokenize("{{foo}} \\{{bar}} \\{{baz}}")
|
||||
result.should match_tokens(%w(OPEN ID CLOSE CONTENT CONTENT CONTENT))
|
||||
|
||||
result[3].should be_token("CONTENT", " ")
|
||||
result[4].should be_token("CONTENT", "{{bar}} ")
|
||||
result[5].should be_token("CONTENT", "{{baz}}")
|
||||
end
|
||||
|
||||
it "supports escaping a triple stash" do
|
||||
result = tokenize("{{foo}} \\{{{bar}}} {{baz}}")
|
||||
result.should match_tokens(%w(OPEN ID CLOSE CONTENT CONTENT OPEN ID CLOSE))
|
||||
|
||||
result[4].should be_token("CONTENT", "{{{bar}}} ")
|
||||
end
|
||||
|
||||
it "tokenizes a simple path" do
|
||||
result = tokenize("{{foo/bar}}")
|
||||
result.should match_tokens(%w(OPEN ID SEP ID CLOSE))
|
||||
end
|
||||
|
||||
it "allows dot notation" do
|
||||
result = tokenize("{{foo.bar}}")
|
||||
result.should match_tokens(%w(OPEN ID SEP ID CLOSE))
|
||||
|
||||
tokenize("{{foo.bar.baz}}").should match_tokens(%w(OPEN ID SEP ID SEP ID CLOSE))
|
||||
end
|
||||
|
||||
it "allows path literals with []" do
|
||||
result = tokenize("{{foo.[bar]}}")
|
||||
result.should match_tokens(%w(OPEN ID SEP ID CLOSE))
|
||||
end
|
||||
|
||||
it "allows multiple path literals on a line with []" do
|
||||
result = tokenize("{{foo.[bar]}}{{foo.[baz]}}")
|
||||
result.should match_tokens(%w(OPEN ID SEP ID CLOSE OPEN ID SEP ID CLOSE))
|
||||
end
|
||||
|
||||
it "tokenizes {{.}} as OPEN ID CLOSE" do
|
||||
result = tokenize("{{.}}")
|
||||
result.should match_tokens(%w(OPEN ID CLOSE))
|
||||
end
|
||||
|
||||
it "tokenizes a path as 'OPEN (ID SEP)* ID CLOSE'" do
|
||||
result = tokenize("{{../foo/bar}}")
|
||||
result.should match_tokens(%w(OPEN ID SEP ID SEP ID CLOSE))
|
||||
result[1].should be_token("ID", "..")
|
||||
end
|
||||
|
||||
it "tokenizes a path with .. as a parent path" do
|
||||
result = tokenize("{{../foo.bar}}")
|
||||
result.should match_tokens(%w(OPEN ID SEP ID SEP ID CLOSE))
|
||||
result[1].should be_token("ID", "..")
|
||||
end
|
||||
|
||||
it "tokenizes a path with this/foo as OPEN ID SEP ID CLOSE" do
|
||||
result = tokenize("{{this/foo}}")
|
||||
result.should match_tokens(%w(OPEN ID SEP ID CLOSE))
|
||||
result[1].should be_token("ID", "this")
|
||||
result[3].should be_token("ID", "foo")
|
||||
end
|
||||
|
||||
it "tokenizes a simple mustache with spaces as 'OPEN ID CLOSE'" do
|
||||
result = tokenize("{{ foo }}")
|
||||
result.should match_tokens(%w(OPEN ID CLOSE))
|
||||
result[1].should be_token("ID", "foo")
|
||||
end
|
||||
|
||||
it "tokenizes a simple mustache with line breaks as 'OPEN ID ID CLOSE'" do
|
||||
result = tokenize("{{ foo \n bar }}")
|
||||
result.should match_tokens(%w(OPEN ID ID CLOSE))
|
||||
result[1].should be_token("ID", "foo")
|
||||
end
|
||||
|
||||
it "tokenizes raw content as 'CONTENT'" do
|
||||
result = tokenize("foo {{ bar }} baz")
|
||||
result.should match_tokens(%w(CONTENT OPEN ID CLOSE CONTENT))
|
||||
result[0].should be_token("CONTENT", "foo ")
|
||||
result[4].should be_token("CONTENT", " baz")
|
||||
end
|
||||
|
||||
it "tokenizes a partial as 'OPEN_PARTIAL ID CLOSE'" do
|
||||
result = tokenize("{{> foo}}")
|
||||
result.should match_tokens(%w(OPEN_PARTIAL ID CLOSE))
|
||||
end
|
||||
|
||||
it "tokenizes a partial with context as 'OPEN_PARTIAL ID ID CLOSE'" do
|
||||
result = tokenize("{{> foo bar }}")
|
||||
result.should match_tokens(%w(OPEN_PARTIAL ID ID CLOSE))
|
||||
end
|
||||
|
||||
it "tokenizes a partial without spaces as 'OPEN_PARTIAL ID CLOSE'" do
|
||||
result = tokenize("{{>foo}}")
|
||||
result.should match_tokens(%w(OPEN_PARTIAL ID CLOSE))
|
||||
end
|
||||
|
||||
it "tokenizes a partial space at the end as 'OPEN_PARTIAL ID CLOSE'" do
|
||||
result = tokenize("{{>foo }}")
|
||||
result.should match_tokens(%w(OPEN_PARTIAL ID CLOSE))
|
||||
end
|
||||
|
||||
it "tokenizes a partial space at the end as 'OPEN_PARTIAL ID CLOSE'" do
|
||||
result = tokenize("{{>foo/bar.baz }}")
|
||||
result.should match_tokens(%w(OPEN_PARTIAL ID SEP ID SEP ID CLOSE))
|
||||
end
|
||||
|
||||
it "tokenizes a comment as 'COMMENT'" do
|
||||
result = tokenize("foo {{! this is a comment }} bar {{ baz }}")
|
||||
result.should match_tokens(%w(CONTENT COMMENT CONTENT OPEN ID CLOSE))
|
||||
result[1].should be_token("COMMENT", " this is a comment ")
|
||||
end
|
||||
|
||||
it "tokenizes a block comment as 'COMMENT'" do
|
||||
result = tokenize("foo {{!-- this is a {{comment}} --}} bar {{ baz }}")
|
||||
result.should match_tokens(%w(CONTENT COMMENT CONTENT OPEN ID CLOSE))
|
||||
result[1].should be_token("COMMENT", " this is a {{comment}} ")
|
||||
end
|
||||
|
||||
it "tokenizes a block comment with whitespace as 'COMMENT'" do
|
||||
result = tokenize("foo {{!-- this is a\n{{comment}}\n--}} bar {{ baz }}")
|
||||
result.should match_tokens(%w(CONTENT COMMENT CONTENT OPEN ID CLOSE))
|
||||
result[1].should be_token("COMMENT", " this is a\n{{comment}}\n")
|
||||
end
|
||||
|
||||
it "tokenizes open and closing blocks as 'OPEN_BLOCK ID CLOSE ... OPEN_ENDBLOCK ID CLOSE'" do
|
||||
result = tokenize("{{#foo}}content{{/foo}}")
|
||||
result.should match_tokens(%w(OPEN_BLOCK ID CLOSE CONTENT OPEN_ENDBLOCK ID CLOSE))
|
||||
end
|
||||
|
||||
it "tokenizes inverse sections as 'OPEN_INVERSE CLOSE'" do
|
||||
tokenize("{{^}}").should match_tokens(%w(OPEN_INVERSE CLOSE))
|
||||
tokenize("{{else}}").should match_tokens(%w(OPEN_INVERSE CLOSE))
|
||||
tokenize("{{ else }}").should match_tokens(%w(OPEN_INVERSE CLOSE))
|
||||
end
|
||||
|
||||
it "tokenizes inverse sections with ID as 'OPEN_INVERSE ID CLOSE'" do
|
||||
result = tokenize("{{^foo}}")
|
||||
result.should match_tokens(%w(OPEN_INVERSE ID CLOSE))
|
||||
result[1].should be_token("ID", "foo")
|
||||
end
|
||||
|
||||
it "tokenizes inverse sections with ID and spaces as 'OPEN_INVERSE ID CLOSE'" do
|
||||
result = tokenize("{{^ foo }}")
|
||||
result.should match_tokens(%w(OPEN_INVERSE ID CLOSE))
|
||||
result[1].should be_token("ID", "foo")
|
||||
end
|
||||
|
||||
it "tokenizes mustaches with params as 'OPEN ID ID ID CLOSE'" do
|
||||
result = tokenize("{{ foo bar baz }}")
|
||||
result.should match_tokens(%w(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")
|
||||
end
|
||||
|
||||
it "tokenizes mustaches with String params as 'OPEN ID ID STRING CLOSE'" do
|
||||
result = tokenize("{{ foo bar \"baz\" }}")
|
||||
result.should match_tokens(%w(OPEN ID ID STRING CLOSE))
|
||||
result[3].should be_token("STRING", "baz")
|
||||
end
|
||||
|
||||
it "tokenizes mustaches with String params using single quotes as 'OPEN ID ID STRING CLOSE'" do
|
||||
result = tokenize("{{ foo bar \'baz\' }}")
|
||||
result.should match_tokens(%w(OPEN ID ID STRING CLOSE))
|
||||
result[3].should be_token("STRING", "baz")
|
||||
end
|
||||
|
||||
it "tokenizes String params with spaces inside as 'STRING'" do
|
||||
result = tokenize("{{ foo bar \"baz bat\" }}")
|
||||
result.should match_tokens(%w(OPEN ID ID STRING CLOSE))
|
||||
result[3].should be_token("STRING", "baz bat")
|
||||
end
|
||||
|
||||
it "tokenizes String params with escapes quotes as 'STRING'" do
|
||||
result = tokenize(%|{{ foo "bar\\"baz" }}|)
|
||||
result.should match_tokens(%w(OPEN ID STRING CLOSE))
|
||||
result[2].should be_token("STRING", %{bar"baz})
|
||||
end
|
||||
|
||||
it "tokenizes String params using single quotes with escapes quotes as 'STRING'" do
|
||||
result = tokenize(%|{{ foo 'bar\\'baz' }}|)
|
||||
result.should match_tokens(%w(OPEN ID STRING CLOSE))
|
||||
result[2].should be_token("STRING", %{bar'baz})
|
||||
end
|
||||
|
||||
it "tokenizes numbers" do
|
||||
result = tokenize(%|{{ foo 1 }}|)
|
||||
result.should match_tokens(%w(OPEN ID INTEGER CLOSE))
|
||||
result[2].should be_token("INTEGER", "1")
|
||||
|
||||
result = tokenize(%|{{ foo -1 }}|)
|
||||
result.should match_tokens(%w(OPEN ID INTEGER CLOSE))
|
||||
result[2].should be_token("INTEGER", "-1")
|
||||
end
|
||||
|
||||
it "tokenizes booleans" do
|
||||
result = tokenize(%|{{ foo true }}|)
|
||||
result.should match_tokens(%w(OPEN ID BOOLEAN CLOSE))
|
||||
result[2].should be_token("BOOLEAN", "true")
|
||||
|
||||
result = tokenize(%|{{ foo false }}|)
|
||||
result.should match_tokens(%w(OPEN ID BOOLEAN CLOSE))
|
||||
result[2].should be_token("BOOLEAN", "false")
|
||||
end
|
||||
|
||||
it "tokenizes hash arguments" do
|
||||
result = tokenize("{{ foo bar=baz }}")
|
||||
result.should match_tokens %w(OPEN ID ID EQUALS ID CLOSE)
|
||||
|
||||
result = tokenize("{{ foo bar baz=bat }}")
|
||||
result.should match_tokens %w(OPEN ID ID ID EQUALS ID CLOSE)
|
||||
|
||||
result = tokenize("{{ foo bar baz=1 }}")
|
||||
result.should match_tokens %w(OPEN ID ID ID EQUALS INTEGER CLOSE)
|
||||
|
||||
result = tokenize("{{ foo bar baz=true }}")
|
||||
result.should match_tokens %w(OPEN ID ID ID EQUALS BOOLEAN CLOSE)
|
||||
|
||||
result = tokenize("{{ foo bar baz=false }}")
|
||||
result.should match_tokens %w(OPEN ID ID ID EQUALS BOOLEAN CLOSE)
|
||||
|
||||
result = tokenize("{{ foo bar\n baz=bat }}")
|
||||
result.should match_tokens %w(OPEN ID ID ID EQUALS ID CLOSE)
|
||||
|
||||
result = tokenize("{{ foo bar baz=\"bat\" }}")
|
||||
result.should match_tokens %w(OPEN ID ID ID EQUALS STRING CLOSE)
|
||||
|
||||
result = tokenize("{{ foo bar baz=\"bat\" bam=wot }}")
|
||||
result.should match_tokens %w(OPEN ID ID ID EQUALS STRING ID EQUALS ID CLOSE)
|
||||
|
||||
result = tokenize("{{foo omg bar=baz bat=\"bam\"}}")
|
||||
result.should match_tokens %w(OPEN ID ID ID EQUALS ID ID EQUALS STRING CLOSE)
|
||||
result[2].should be_token("ID", "omg")
|
||||
end
|
||||
|
||||
it "tokenizes special @ identifiers" do
|
||||
result = tokenize("{{ @foo }}")
|
||||
result.should match_tokens %w( OPEN DATA ID CLOSE )
|
||||
result[2].should be_token("ID", "foo")
|
||||
|
||||
result = tokenize("{{ foo @bar }}")
|
||||
result.should match_tokens %w( OPEN ID DATA ID CLOSE )
|
||||
result[3].should be_token("ID", "bar")
|
||||
|
||||
result = tokenize("{{ foo bar=@baz }}")
|
||||
result.should match_tokens %w( OPEN ID ID EQUALS DATA ID CLOSE )
|
||||
result[5].should be_token("ID", "baz")
|
||||
end
|
||||
|
||||
it "does not time out in a mustache with a single } followed by EOF" do
|
||||
Timeout.timeout(1) { tokenize("{{foo}").should match_tokens(%w(OPEN ID)) }
|
||||
end
|
||||
|
||||
it "does not time out in a mustache when invalid ID characters are used" do
|
||||
Timeout.timeout(1) { tokenize("{{foo & }}").should match_tokens(%w(OPEN ID)) }
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,59 @@
|
||||
/*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");
|
||||
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() {
|
||||
var name = new Handlebars.SafeString("<em>Sean O'Malley</em>");
|
||||
|
||||
shouldCompileTo('{{name}}', [{ name: name }], "<em>Sean O'Malley</em>");
|
||||
});
|
||||
});
|
||||
|
||||
describe('#escapeExpression', function() {
|
||||
it('shouhld escape html', function() {
|
||||
equals(Handlebars.Utils.escapeExpression('foo<&"\'>'), 'foo<&"'>');
|
||||
});
|
||||
it('should not escape SafeString', function() {
|
||||
var string = new Handlebars.SafeString('foo<&"\'>');
|
||||
equals(Handlebars.Utils.escapeExpression(string), 'foo<&"\'>');
|
||||
|
||||
});
|
||||
it('should handle falsy', function() {
|
||||
equals(Handlebars.Utils.escapeExpression(''), '');
|
||||
equals(Handlebars.Utils.escapeExpression(undefined), '');
|
||||
equals(Handlebars.Utils.escapeExpression(null), '');
|
||||
equals(Handlebars.Utils.escapeExpression(false), '');
|
||||
|
||||
equals(Handlebars.Utils.escapeExpression(0), '0');
|
||||
});
|
||||
it('should handle empty objects', function() {
|
||||
equals(Handlebars.Utils.escapeExpression({}), {}.toString());
|
||||
equals(Handlebars.Utils.escapeExpression([]), [].toString());
|
||||
});
|
||||
});
|
||||
|
||||
describe('#isEmpty', function() {
|
||||
it('should not be empty', function() {
|
||||
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() {
|
||||
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);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,62 @@
|
||||
describe('whitespace control', function() {
|
||||
it('should strip whitespace around mustache calls', function() {
|
||||
var hash = {foo: 'bar<'};
|
||||
|
||||
shouldCompileTo(' {{~foo~}} ', hash, 'bar<');
|
||||
shouldCompileTo(' {{~foo}} ', hash, 'bar< ');
|
||||
shouldCompileTo(' {{foo~}} ', hash, ' bar<');
|
||||
|
||||
shouldCompileTo(' {{~&foo~}} ', hash, 'bar<');
|
||||
shouldCompileTo(' {{~{foo}~}} ', hash, 'bar<');
|
||||
});
|
||||
|
||||
describe('blocks', function() {
|
||||
it('should strip whitespace around simple block calls', function() {
|
||||
var hash = {foo: 'bar<'};
|
||||
|
||||
shouldCompileTo(' {{~#if foo~}} bar {{~/if~}} ', hash, 'bar');
|
||||
shouldCompileTo(' {{#if foo~}} bar {{/if~}} ', hash, ' bar ');
|
||||
shouldCompileTo(' {{~#if foo}} bar {{~/if}} ', hash, ' bar ');
|
||||
shouldCompileTo(' {{#if foo}} bar {{/if}} ', hash, ' bar ');
|
||||
});
|
||||
it('should strip whitespace around inverse block calls', function() {
|
||||
var hash = {};
|
||||
|
||||
shouldCompileTo(' {{~^if foo~}} bar {{~/if~}} ', hash, 'bar');
|
||||
shouldCompileTo(' {{^if foo~}} bar {{/if~}} ', hash, ' bar ');
|
||||
shouldCompileTo(' {{~^if foo}} bar {{~/if}} ', hash, ' bar ');
|
||||
shouldCompileTo(' {{^if foo}} bar {{/if}} ', hash, ' bar ');
|
||||
});
|
||||
it('should strip whitespace around complex block calls', function() {
|
||||
var hash = {foo: 'bar<'};
|
||||
|
||||
shouldCompileTo('{{#if foo~}} bar {{~^~}} baz {{~/if}}', hash, 'bar');
|
||||
shouldCompileTo('{{#if foo~}} bar {{^~}} baz {{/if}}', hash, 'bar ');
|
||||
shouldCompileTo('{{#if foo}} bar {{~^~}} baz {{~/if}}', hash, ' bar');
|
||||
shouldCompileTo('{{#if foo}} bar {{^~}} baz {{/if}}', hash, ' bar ');
|
||||
|
||||
shouldCompileTo('{{#if foo~}} bar {{~else~}} baz {{~/if}}', hash, 'bar');
|
||||
|
||||
hash = {};
|
||||
|
||||
shouldCompileTo('{{#if foo~}} bar {{~^~}} baz {{~/if}}', hash, 'baz');
|
||||
shouldCompileTo('{{#if foo}} bar {{~^~}} baz {{/if}}', hash, 'baz ');
|
||||
shouldCompileTo('{{#if foo~}} bar {{~^}} baz {{~/if}}', hash, ' baz');
|
||||
shouldCompileTo('{{#if foo~}} bar {{~^}} baz {{/if}}', hash, ' baz ');
|
||||
|
||||
shouldCompileTo('{{#if foo~}} bar {{~else~}} baz {{~/if}}', hash, 'baz');
|
||||
});
|
||||
});
|
||||
|
||||
it('should strip whitespace around partials', function() {
|
||||
shouldCompileToWithPartials('foo {{~> dude~}} ', [{}, {}, {dude: 'bar'}], true, 'foobar');
|
||||
shouldCompileToWithPartials('foo {{> dude~}} ', [{}, {}, {dude: 'bar'}], true, 'foo bar');
|
||||
shouldCompileToWithPartials('foo {{> dude}} ', [{}, {}, {dude: 'bar'}], true, 'foo bar ');
|
||||
});
|
||||
|
||||
it('should only strip whitespace once', function() {
|
||||
var hash = {foo: 'bar'};
|
||||
|
||||
shouldCompileTo(' {{~foo~}} {{foo}} {{foo}} ', hash, 'barbar bar ');
|
||||
});
|
||||
});
|
||||
+64
-43
@@ -1,49 +1,19 @@
|
||||
|
||||
%x mu emu com
|
||||
|
||||
%%
|
||||
%{
|
||||
|
||||
"\\\\"/("{{") { yytext = "\\"; return 'CONTENT'; }
|
||||
[^\x00]*?/("{{") {
|
||||
if(yytext.slice(-1) !== "\\") this.begin("mu");
|
||||
if(yytext.slice(-1) === "\\") yytext = yytext.substr(0,yyleng-1), this.begin("emu");
|
||||
if(yytext) return 'CONTENT';
|
||||
}
|
||||
function strip(start, end) {
|
||||
return yytext = yytext.substr(start, yyleng-end);
|
||||
}
|
||||
|
||||
[^\x00]+ { return 'CONTENT'; }
|
||||
%}
|
||||
|
||||
<emu>[^\x00]{2,}?/("{{"|<<EOF>>) {
|
||||
if(yytext.slice(-1) !== "\\") this.popState();
|
||||
if(yytext.slice(-1) === "\\") yytext = yytext.substr(0,yyleng-1);
|
||||
return 'CONTENT';
|
||||
}
|
||||
LEFT_STRIP "~"
|
||||
RIGHT_STRIP "~"
|
||||
|
||||
<com>[\s\S]*?"--}}" { yytext = yytext.substr(0, yyleng-4); this.popState(); return 'COMMENT'; }
|
||||
|
||||
<mu>"{{>" { return 'OPEN_PARTIAL'; }
|
||||
<mu>"{{#" { return 'OPEN_BLOCK'; }
|
||||
<mu>"{{/" { return 'OPEN_ENDBLOCK'; }
|
||||
<mu>"{{^" { return 'OPEN_INVERSE'; }
|
||||
<mu>"{{"\s*"else" { return 'OPEN_INVERSE'; }
|
||||
<mu>"{{{" { return 'OPEN_UNESCAPED'; }
|
||||
<mu>"{{&" { return 'OPEN'; }
|
||||
<mu>"{{!--" { this.popState(); this.begin('com'); }
|
||||
<mu>"{{!"[\s\S]*?"}}" { yytext = yytext.substr(3,yyleng-5); this.popState(); return 'COMMENT'; }
|
||||
<mu>"{{" { return 'OPEN'; }
|
||||
|
||||
<mu>"=" { return 'EQUALS'; }
|
||||
<mu>"."/[}\/ ] { return 'ID'; }
|
||||
<mu>".." { return 'ID'; }
|
||||
<mu>[\/.] { return 'SEP'; }
|
||||
<mu>\s+ { /*ignore whitespace*/ }
|
||||
<mu>"}}}" { this.popState(); return 'CLOSE_UNESCAPED'; }
|
||||
<mu>"}}" { this.popState(); return 'CLOSE'; }
|
||||
<mu>'"'("\\"["]|[^"])*'"' { yytext = yytext.substr(1,yyleng-2).replace(/\\"/g,'"'); return 'STRING'; }
|
||||
<mu>"'"("\\"[']|[^'])*"'" { yytext = yytext.substr(1,yyleng-2).replace(/\\'/g,"'"); return 'STRING'; }
|
||||
<mu>"@" { return 'DATA'; }
|
||||
<mu>"true"/[}\s] { return 'BOOLEAN'; }
|
||||
<mu>"false"/[}\s] { return 'BOOLEAN'; }
|
||||
<mu>\-?[0-9]+/[}\s] { return 'INTEGER'; }
|
||||
LOOKAHEAD [=~}\s\/.]
|
||||
LITERAL_LOOKAHEAD [~}\s]
|
||||
|
||||
/*
|
||||
ID is the inverse of control characters.
|
||||
@@ -54,10 +24,61 @@ Control characters ranges:
|
||||
[\[-\^`] [, \, ], ^, `, Exceptions in range: _
|
||||
[\{-~] {, |, }, ~
|
||||
*/
|
||||
<mu>[^\s!"#%-,\.\/;->@\[-\^`\{-~]+/[=}\s\/.] { return 'ID'; }
|
||||
ID [^\s!"#%-,\.\/;->@\[-\^`\{-~]+/{LOOKAHEAD}
|
||||
|
||||
<mu>'['[^\]]*']' { yytext = yytext.substr(1, yyleng-2); return 'ID'; }
|
||||
<mu>. { return 'INVALID'; }
|
||||
%%
|
||||
|
||||
<INITIAL,mu><<EOF>> { return 'EOF'; }
|
||||
[^\x00]*?/("{{") {
|
||||
if(yytext.slice(-2) === "\\\\") {
|
||||
strip(0,1);
|
||||
this.begin("mu");
|
||||
} else if(yytext.slice(-1) === "\\") {
|
||||
strip(0,1);
|
||||
this.begin("emu");
|
||||
} else {
|
||||
this.begin("mu");
|
||||
}
|
||||
if(yytext) return 'CONTENT';
|
||||
}
|
||||
|
||||
[^\x00]+ return 'CONTENT';
|
||||
|
||||
// marks CONTENT up to the next mustache or escaped mustache
|
||||
<emu>[^\x00]{2,}?/("{{"|"\\{{"|"\\\\{{"|<<EOF>>) {
|
||||
this.popState();
|
||||
return 'CONTENT';
|
||||
}
|
||||
|
||||
<com>[\s\S]*?"--}}" strip(0,4); this.popState(); return 'COMMENT';
|
||||
|
||||
<mu>"{{"{LEFT_STRIP}?">" return 'OPEN_PARTIAL';
|
||||
<mu>"{{"{LEFT_STRIP}?"#" return 'OPEN_BLOCK';
|
||||
<mu>"{{"{LEFT_STRIP}?"/" return 'OPEN_ENDBLOCK';
|
||||
<mu>"{{"{LEFT_STRIP}?"^" return 'OPEN_INVERSE';
|
||||
<mu>"{{"{LEFT_STRIP}?\s*"else" return 'OPEN_INVERSE';
|
||||
<mu>"{{"{LEFT_STRIP}?"{" return 'OPEN_UNESCAPED';
|
||||
<mu>"{{"{LEFT_STRIP}?"&" return 'OPEN';
|
||||
<mu>"{{!--" this.popState(); this.begin('com');
|
||||
<mu>"{{!"[\s\S]*?"}}" strip(3,5); this.popState(); return 'COMMENT';
|
||||
<mu>"{{"{LEFT_STRIP}? return 'OPEN';
|
||||
|
||||
<mu>"=" return 'EQUALS';
|
||||
<mu>".." return 'ID';
|
||||
<mu>"."/{LOOKAHEAD} return 'ID';
|
||||
<mu>[\/.] return 'SEP';
|
||||
<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';
|
||||
<mu>"'"("\\"[']|[^'])*"'" yytext = strip(1,2).replace(/\\'/g,"'"); return 'STRING';
|
||||
<mu>"@" return 'DATA';
|
||||
<mu>"true"/{LITERAL_LOOKAHEAD} return 'BOOLEAN';
|
||||
<mu>"false"/{LITERAL_LOOKAHEAD} return 'BOOLEAN';
|
||||
<mu>\-?[0-9]+/{LITERAL_LOOKAHEAD} return 'INTEGER';
|
||||
|
||||
<mu>{ID} return 'ID';
|
||||
|
||||
<mu>'['[^\]]*']' yytext = strip(1,2); return 'ID';
|
||||
<mu>. return 'INVALID';
|
||||
|
||||
<INITIAL,mu><<EOF>> return 'EOF';
|
||||
|
||||
+52
-57
@@ -1,118 +1,113 @@
|
||||
%start root
|
||||
|
||||
%ebnf
|
||||
|
||||
%{
|
||||
|
||||
function stripFlags(open, close) {
|
||||
return {
|
||||
left: open.charAt(2) === '~',
|
||||
right: close.charAt(0) === '~' || close.charAt(1) === '~'
|
||||
};
|
||||
}
|
||||
|
||||
%}
|
||||
|
||||
%%
|
||||
|
||||
root
|
||||
: program EOF { return $1; }
|
||||
: statements EOF { return new yy.ProgramNode($1); }
|
||||
| EOF { return new yy.ProgramNode([]); }
|
||||
;
|
||||
|
||||
program
|
||||
: simpleInverse statements { $$ = new yy.ProgramNode([], $2); }
|
||||
| statements simpleInverse statements { $$ = new yy.ProgramNode($1, $3); }
|
||||
| statements simpleInverse { $$ = new yy.ProgramNode($1, []); }
|
||||
| statements { $$ = new yy.ProgramNode($1); }
|
||||
| simpleInverse { $$ = new yy.ProgramNode([], []); }
|
||||
| "" { $$ = new yy.ProgramNode([]); }
|
||||
: simpleInverse statements -> new yy.ProgramNode([], $1, $2)
|
||||
| statements simpleInverse statements -> new yy.ProgramNode($1, $2, $3)
|
||||
| statements simpleInverse -> new yy.ProgramNode($1, $2, [])
|
||||
| statements -> new yy.ProgramNode($1)
|
||||
| simpleInverse -> new yy.ProgramNode([])
|
||||
| "" -> new yy.ProgramNode([])
|
||||
;
|
||||
|
||||
statements
|
||||
: statement { $$ = [$1]; }
|
||||
: statement -> [$1]
|
||||
| statements statement { $1.push($2); $$ = $1; }
|
||||
;
|
||||
|
||||
statement
|
||||
: openInverse program closeBlock { $$ = new yy.BlockNode($1, $2.inverse, $2, $3); }
|
||||
| openBlock program closeBlock { $$ = new yy.BlockNode($1, $2, $2.inverse, $3); }
|
||||
| mustache { $$ = $1; }
|
||||
| partial { $$ = $1; }
|
||||
| CONTENT { $$ = new yy.ContentNode($1); }
|
||||
| COMMENT { $$ = new yy.CommentNode($1); }
|
||||
: openInverse program closeBlock -> new yy.BlockNode($1, $2.inverse, $2, $3)
|
||||
| openBlock program closeBlock -> new yy.BlockNode($1, $2, $2.inverse, $3)
|
||||
| mustache -> $1
|
||||
| partial -> $1
|
||||
| CONTENT -> new yy.ContentNode($1)
|
||||
| COMMENT -> new yy.CommentNode($1)
|
||||
;
|
||||
|
||||
openBlock
|
||||
: OPEN_BLOCK inMustache CLOSE { $$ = new yy.MustacheNode($2[0], $2[1]); }
|
||||
: OPEN_BLOCK inMustache CLOSE -> new yy.MustacheNode($2[0], $2[1], $1, stripFlags($1, $3))
|
||||
;
|
||||
|
||||
openInverse
|
||||
: OPEN_INVERSE inMustache CLOSE { $$ = new yy.MustacheNode($2[0], $2[1]); }
|
||||
: OPEN_INVERSE inMustache CLOSE -> new yy.MustacheNode($2[0], $2[1], $1, stripFlags($1, $3))
|
||||
;
|
||||
|
||||
closeBlock
|
||||
: OPEN_ENDBLOCK path CLOSE { $$ = $2; }
|
||||
: OPEN_ENDBLOCK path CLOSE -> {path: $2, strip: stripFlags($1, $3)}
|
||||
;
|
||||
|
||||
mustache
|
||||
: OPEN inMustache CLOSE {
|
||||
// Parsing out the '&' escape token at this level saves ~500 bytes after min due to the removal of one parser node.
|
||||
$$ = new yy.MustacheNode($2[0], $2[1], $1[2] === '&');
|
||||
}
|
||||
| OPEN_UNESCAPED inMustache CLOSE_UNESCAPED { $$ = new yy.MustacheNode($2[0], $2[1], true); }
|
||||
// Parsing out the '&' escape token at AST level saves ~500 bytes after min due to the removal of one parser node.
|
||||
// This also allows for handler unification as all mustache node instances can utilize the same handler
|
||||
: OPEN inMustache CLOSE -> new yy.MustacheNode($2[0], $2[1], $1, stripFlags($1, $3))
|
||||
| OPEN_UNESCAPED inMustache CLOSE_UNESCAPED -> new yy.MustacheNode($2[0], $2[1], $1, stripFlags($1, $3))
|
||||
;
|
||||
|
||||
|
||||
partial
|
||||
: OPEN_PARTIAL partialName CLOSE { $$ = new yy.PartialNode($2); }
|
||||
| OPEN_PARTIAL partialName path CLOSE { $$ = new yy.PartialNode($2, $3); }
|
||||
: OPEN_PARTIAL partialName path? CLOSE -> new yy.PartialNode($2, $3, stripFlags($1, $4))
|
||||
;
|
||||
|
||||
simpleInverse
|
||||
: OPEN_INVERSE CLOSE { }
|
||||
: OPEN_INVERSE CLOSE -> stripFlags($1, $2)
|
||||
;
|
||||
|
||||
inMustache
|
||||
: path params hash { $$ = [[$1].concat($2), $3]; }
|
||||
| path params { $$ = [[$1].concat($2), null]; }
|
||||
| path hash { $$ = [[$1], $2]; }
|
||||
| path { $$ = [[$1], null]; }
|
||||
| dataName { $$ = [[$1], null]; }
|
||||
;
|
||||
|
||||
params
|
||||
: params param { $1.push($2); $$ = $1; }
|
||||
| param { $$ = [$1]; }
|
||||
: path param* hash? -> [[$1].concat($2), $3]
|
||||
| dataName -> [[$1], null]
|
||||
;
|
||||
|
||||
param
|
||||
: path { $$ = $1; }
|
||||
| STRING { $$ = new yy.StringNode($1); }
|
||||
| INTEGER { $$ = new yy.IntegerNode($1); }
|
||||
| BOOLEAN { $$ = new yy.BooleanNode($1); }
|
||||
| dataName { $$ = $1; }
|
||||
: path -> $1
|
||||
| STRING -> new yy.StringNode($1)
|
||||
| INTEGER -> new yy.IntegerNode($1)
|
||||
| BOOLEAN -> new yy.BooleanNode($1)
|
||||
| dataName -> $1
|
||||
;
|
||||
|
||||
hash
|
||||
: hashSegments { $$ = new yy.HashNode($1); }
|
||||
;
|
||||
|
||||
hashSegments
|
||||
: hashSegments hashSegment { $1.push($2); $$ = $1; }
|
||||
| hashSegment { $$ = [$1]; }
|
||||
: hashSegment+ -> new yy.HashNode($1)
|
||||
;
|
||||
|
||||
hashSegment
|
||||
: ID EQUALS path { $$ = [$1, $3]; }
|
||||
| ID EQUALS STRING { $$ = [$1, new yy.StringNode($3)]; }
|
||||
| ID EQUALS INTEGER { $$ = [$1, new yy.IntegerNode($3)]; }
|
||||
| ID EQUALS BOOLEAN { $$ = [$1, new yy.BooleanNode($3)]; }
|
||||
| ID EQUALS dataName { $$ = [$1, $3]; }
|
||||
: ID EQUALS param -> [$1, $3]
|
||||
;
|
||||
|
||||
partialName
|
||||
: path { $$ = new yy.PartialNameNode($1); }
|
||||
| STRING { $$ = new yy.PartialNameNode(new yy.StringNode($1)); }
|
||||
| INTEGER { $$ = new yy.PartialNameNode(new yy.IntegerNode($1)); }
|
||||
: path -> new yy.PartialNameNode($1)
|
||||
| STRING -> new yy.PartialNameNode(new yy.StringNode($1))
|
||||
| INTEGER -> new yy.PartialNameNode(new yy.IntegerNode($1))
|
||||
;
|
||||
|
||||
dataName
|
||||
: DATA path { $$ = new yy.DataNode($2); }
|
||||
: DATA path -> new yy.DataNode($2)
|
||||
;
|
||||
|
||||
path
|
||||
: pathSegments { $$ = new yy.IdNode($1); }
|
||||
: pathSegments -> new yy.IdNode($1)
|
||||
;
|
||||
|
||||
pathSegments
|
||||
: pathSegments SEP ID { $1.push({part: $3, separator: $2}); $$ = $1; }
|
||||
| ID { $$ = [{part: $1}]; }
|
||||
| ID -> [{part: $1}]
|
||||
;
|
||||
|
||||
|
||||
@@ -1 +1 @@
|
||||
// BEGIN(BROWSER)
|
||||
/* jshint ignore:start */
|
||||
|
||||
@@ -1,4 +1,2 @@
|
||||
|
||||
// END(BROWSER)
|
||||
|
||||
module.exports = handlebars;
|
||||
export default handlebars;
|
||||
/* jshint ignore:end */
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
var _ = require('underscore'),
|
||||
async = require('async'),
|
||||
git = require('./util/git'),
|
||||
Keen = require('keen.io'),
|
||||
metrics = require('../bench');
|
||||
|
||||
module.exports = function(grunt) {
|
||||
grunt.registerTask('metrics', function() {
|
||||
var done = this.async(),
|
||||
execName = grunt.option('name'),
|
||||
events = {},
|
||||
|
||||
projectId = process.env.KEEN_PROJECTID,
|
||||
writeKey = process.env.KEEN_WRITEKEY,
|
||||
keen;
|
||||
|
||||
if (!execName && projectId && writeKey) {
|
||||
keen = Keen.configure({
|
||||
projectId: projectId,
|
||||
writeKey: writeKey
|
||||
});
|
||||
}
|
||||
|
||||
async.each(_.keys(metrics), function(name, complete) {
|
||||
if (/^_/.test(name) || (execName && name !== execName)) {
|
||||
return complete();
|
||||
}
|
||||
|
||||
metrics[name](grunt, function(data) {
|
||||
events[name] = data;
|
||||
complete();
|
||||
});
|
||||
},
|
||||
function() {
|
||||
if (!keen) {
|
||||
return done();
|
||||
}
|
||||
|
||||
emit(keen, events, function(err, res) {
|
||||
if (err) {
|
||||
throw err;
|
||||
}
|
||||
|
||||
grunt.log.writeln('Metrics recorded.');
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
};
|
||||
function emit(keen, collections, callback) {
|
||||
git.commitInfo(function(err, info) {
|
||||
_.each(collections, function(collection) {
|
||||
_.each(collection, function(event) {
|
||||
if (info.tagName) {
|
||||
event.tag = info.tagName;
|
||||
}
|
||||
event.sha = info.head;
|
||||
});
|
||||
});
|
||||
|
||||
keen.addEvents(collections, callback);
|
||||
});
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user