Compare commits
541 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 1eb2b04aa1 | |||
| 2e75199664 | |||
| 0670f656fb | |||
| 4d17e3c209 | |||
| 58fb258016 | |||
| ed3ae9af59 | |||
| 64d5481c00 | |||
| cee52ea68f | |||
| d33b362d97 | |||
| c5acea75aa | |||
| 4f01f650dc | |||
| 3c869866c8 | |||
| ca1486b960 | |||
| dfca676d63 | |||
| 09d3c74c7f | |||
| c98147b31f | |||
| b41459055a | |||
| 84342b6215 | |||
| 24d63b6966 | |||
| 36f00db073 | |||
| 64f422b57f | |||
| dc9a2ed7eb | |||
| 980c38cebf | |||
| 31f6fd7131 | |||
| c9886fdc0f | |||
| 501a640330 | |||
| 477a26913a | |||
| 4ce474d3e4 | |||
| 3e6bf8b0f6 | |||
| e5aeca648a | |||
| 2b3fdf7ea8 | |||
| 0c7c37df6a | |||
| cde008b49f | |||
| 84d646bb5d | |||
| ccd803ff15 | |||
| 0528b91644 | |||
| 35b196d069 | |||
| 529e2b6796 | |||
| 3531e04117 | |||
| c3fde1c01c | |||
| 67b3f40eb2 | |||
| 19ce981296 | |||
| f4b8c5260c | |||
| cf343a1949 | |||
| 3ce105ae89 | |||
| ae44e071d8 | |||
| 2f14fa57e5 | |||
| f2b5519e76 | |||
| cc5de5df3c | |||
| d05245b5f5 | |||
| 084e8fe1b7 | |||
| 9f07a34955 | |||
| 6f22ec1e01 | |||
| c71ca34fd3 | |||
| 85b67d7f3d | |||
| 9e3f824bf5 | |||
| b94656a31f | |||
| 3cea85b2b0 | |||
| 4d0e3c98eb | |||
| cb22ee5681 | |||
| 9ab31e1fc6 | |||
| 1bd8d38c3c | |||
| 59607aa9ab | |||
| 48263be204 | |||
| 9f8110fe15 | |||
| 0edce6e1d1 | |||
| d95725d5fd | |||
| c98613b711 | |||
| 625b00e1da | |||
| b695aaec7c | |||
| f6dc5ad716 | |||
| 867322adf2 | |||
| 3288f525f3 | |||
| f2a2914d4f | |||
| e93d516672 | |||
| 2a47d66ee9 | |||
| 50c657f138 | |||
| 6070179009 | |||
| 4a4dcf1657 | |||
| 6efcaaf8d1 | |||
| f4af8e06b6 | |||
| 63c5eb7a82 | |||
| 248ad1acae | |||
| 905f99b18c | |||
| 11f85089cc | |||
| 5f1bf78555 | |||
| f374939cfd | |||
| eee2c4d4f2 | |||
| e11c1beb72 | |||
| 2bdfd2a31d | |||
| 271106d43f | |||
| 1fb7b51ee6 | |||
| c90cfe247c | |||
| 107aa4a66a | |||
| 9a1eda2caf | |||
| b5a5c76ceb | |||
| 4aad72d223 | |||
| 704961b378 | |||
| 054627adb0 | |||
| 058e7495f9 | |||
| 059532295d | |||
| 0c084399e2 | |||
| efb17b80a6 | |||
| 7890c7dc89 | |||
| b79e31e5a5 | |||
| 7172d167dd | |||
| 3cdf14d294 | |||
| efad61b1b7 | |||
| 2a20976e6a | |||
| 3638874ddc | |||
| f419838362 | |||
| 8ee73b9d13 | |||
| 3dcc49927b | |||
| e10eb537ab | |||
| 8f09ea5f40 | |||
| 72c1262394 | |||
| a14c689fa6 | |||
| e143849f8a | |||
| 2e7a3bd702 | |||
| 356ea1a909 | |||
| 085e5e1937 | |||
| a601b24a73 | |||
| 04c312649b | |||
| fea46dbb8c | |||
| b61c75512a | |||
| 1da136490b | |||
| d55f9966b9 | |||
| d33ae150cd | |||
| 8347ee2591 | |||
| 55782f08c5 | |||
| 058c0fb6ba | |||
| 10168cb0a9 | |||
| 66538cc7fa | |||
| 7dd64e5e92 | |||
| 674d07fd03 | |||
| 5999baf746 | |||
| fd5dfc861c | |||
| 8493822b62 | |||
| c86ac60876 | |||
| c68620caf9 | |||
| a5ff1f3d22 | |||
| b22a150a37 | |||
| 6da3c31494 | |||
| c72a8d5e84 | |||
| d00d8b6776 | |||
| db651e7ff4 | |||
| 16f135835e | |||
| fcec69ae2c | |||
| 306feb497c | |||
| a9f76e1475 | |||
| fe3b4cc98e | |||
| 14b7ef9066 | |||
| 9b14dc40a5 | |||
| 954253305a | |||
| 7a7ad74ff1 | |||
| d6e86af41a | |||
| 71ed8b5e93 | |||
| 7a6f706dc4 | |||
| dd365eaede | |||
| bc8f6792e8 | |||
| 2812fe2775 | |||
| b0e7298523 | |||
| 6ca06d4115 | |||
| 8f07bbabea | |||
| 4679b1d931 | |||
| cea57c05e2 | |||
| 311be67bde | |||
| 88c52ded2e | |||
| 5a0bcb932d | |||
| 49fcf10de2 | |||
| ace2896ec8 | |||
| 9df919083d | |||
| 103e5f8409 | |||
| a14daca1c6 | |||
| 33921beaa0 | |||
| d4cfe90959 | |||
| 051618c024 | |||
| fe4880feaa | |||
| 363cb4b0fb | |||
| e290ec24f1 | |||
| 45ae86a248 | |||
| f90981adf6 | |||
| 93a3725480 | |||
| 1b8c0e3c76 | |||
| 1b0b44a5b7 | |||
| f9e0272513 | |||
| 13633e7896 | |||
| 5659db4877 | |||
| cb80f46843 | |||
| f650b0d69e | |||
| d841ad5962 | |||
| fe9e93fc4d | |||
| 1a91fbda40 | |||
| 91aa96da7d | |||
| db7c9f3565 | |||
| 76b47e3393 | |||
| d385e2cab6 | |||
| 0f328f375e | |||
| c73aceb3d3 | |||
| daeecf825c | |||
| 607748af3b | |||
| 7197289364 | |||
| fc60f16475 | |||
| 08fab253ef | |||
| 3d9685c6a2 | |||
| 6d996ef270 | |||
| 55c7cbbbfa | |||
| e9f3a169c9 | |||
| 70b27bcef6 | |||
| a1df862f06 | |||
| 0c6829f8af | |||
| 2b9f3c195f | |||
| f445b96cb7 | |||
| e0d0ad5027 | |||
| 3f71fde155 | |||
| 6e4e1f8404 | |||
| 3c85720137 | |||
| 4713abc8f1 | |||
| e7e94dc697 | |||
| c1a93d33e7 | |||
| cd885bf855 | |||
| ddfe457abf | |||
| f4d337d252 | |||
| cbfec5b9e9 | |||
| af3358d195 | |||
| 641358a905 | |||
| f2df220a1f | |||
| a2ca31bb19 | |||
| b09333db79 | |||
| ac98e7b177 | |||
| 14d1d4270f | |||
| 150e55aa00 | |||
| 2f0c96b618 | |||
| 6c2137a420 | |||
| e1878050b5 | |||
| 1c0614bd88 | |||
| ba7a37a3b8 | |||
| 77fd7dcc1d | |||
| 7311ab0b3f | |||
| 4160ff3ee2 | |||
| 7151d15abb | |||
| 19f443f68e | |||
| 9f1b3bcea0 | |||
| 2dae244185 | |||
| c62ce32cc9 | |||
| f3a90812a6 | |||
| 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 |
+4
-1
@@ -2,8 +2,11 @@ vendor
|
||||
.rvmrc
|
||||
.DS_Store
|
||||
lib/handlebars/compiler/parser.js
|
||||
dist/*.min.js
|
||||
/dist/
|
||||
/tmp/
|
||||
/coverage/
|
||||
node_modules
|
||||
*.sublime-project
|
||||
*.sublime-workspace
|
||||
npm-debug.log
|
||||
sauce_connect.log*
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
[submodule "spec/mustache"]
|
||||
path = spec/mustache
|
||||
url = git://github.com/mustache/spec.git
|
||||
@@ -0,0 +1,2 @@
|
||||
instrumentation:
|
||||
excludes: ['**/spec/**']
|
||||
@@ -1,14 +1,6 @@
|
||||
{
|
||||
"predef": [
|
||||
"console",
|
||||
"Ember",
|
||||
"DS",
|
||||
"Handlebars",
|
||||
"Metamorph",
|
||||
"ember_assert",
|
||||
"ember_warn",
|
||||
"ember_deprecate",
|
||||
"ember_deprecateFunc",
|
||||
"require",
|
||||
"suite",
|
||||
"equal",
|
||||
@@ -21,22 +13,29 @@
|
||||
"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,
|
||||
"laxbreak": false,
|
||||
"laxbreak": true,
|
||||
"newcap": true,
|
||||
"noarg": true,
|
||||
"noempty": 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/*
|
||||
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
language: node_js
|
||||
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: ckyEe5dzjdFDjmZ6wIrhGm0CFBEnKq8c1dYptfgVV/Q5/nJFGzu8T0yTjouS/ERxzdT2H327/63VCxhFnLCRHrsh4rlW/rCy4XI3O/0TeMLgFPa4TXkO8359qZ4CB44TBb3NsJyQXNMYdJpPLTCVTMpuiqqkFFOr+6OeggR7ufA=
|
||||
- secure: Nm4AgSfsgNB21kgKrF9Tl7qVZU8YYREhouQunFracTcZZh2NZ2XH5aHuSiXCj88B13Cr/jGbJKsZ4T3QS3wWYtz6lkyVOx3H3iI+TMtqhD9RM3a7A4O+4vVN8IioB2YjhEu0OKjwgX5gp+0uF+pLEi7Hpj6fupD3AbbL5uYcKg8=
|
||||
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
|
||||
@@ -0,0 +1,78 @@
|
||||
# How to Contribute
|
||||
|
||||
## Reporting Issues
|
||||
|
||||
Please see our [FAQ](https://github.com/wycats/handlebars.js/blob/master/FAQ.md) for common issues that people run into.
|
||||
|
||||
Should you run into other issues with the project, please don't hesitate to let us know by filing an [issue][issue]! In general we are going to ask for an example of the problem failing, which can be as simple as a jsfiddle/jsbin/etc. We've put together a jsfiddle [template][jsfiddle] to ease this. (We will keep this link up to date as new releases occur, so feel free to check back here)
|
||||
|
||||
Pull requests containing only failing thats demonstrating the issue are welcomed and this also helps ensure that your issue won't regress in the future once it's fixed.
|
||||
|
||||
Documentation issues on the handlebarsjs.com site should be reported on [handlebars-site](https://github.com/wycats/handlebars-site).
|
||||
|
||||
## Pull Requests
|
||||
|
||||
We also accept [pull requests][pull-request]!
|
||||
|
||||
Generally we like to see pull requests that
|
||||
- Maintain the existing code style
|
||||
- Are focused on a single change (i.e. avoid large refactoring or style adjustments in untouched code if not the primary goal of the pull request)
|
||||
- Have [good commit messages](http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html)
|
||||
- Have tests
|
||||
- Don't significantly decrease the current code coverage (see coverage/lcov-report/index.html)
|
||||
|
||||
## Building
|
||||
|
||||
To build Handlebars.js you'll need a few things installed.
|
||||
|
||||
* Node.js
|
||||
* [Grunt](http://gruntjs.com/getting-started)
|
||||
|
||||
Project dependencies may be installed via `npm install`.
|
||||
|
||||
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 re-run tests, run `grunt test` or `npm test`.
|
||||
You can also run our set of benchmarks with `grunt bench`.
|
||||
|
||||
The `grunt dev` implements watching for tests and allows for in browser testing at `http://localhost:9999/spec/`.
|
||||
|
||||
If you notice any problems, please report them to the GitHub issue tracker at
|
||||
[http://github.com/wycats/handlebars.js/issues](http://github.com/wycats/handlebars.js/issues).
|
||||
|
||||
## 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
|
||||
npm link
|
||||
grunt build release
|
||||
cp dist/*.js $emberRepoDir/bower_components/handlebars/
|
||||
|
||||
cd $emberRepoDir
|
||||
npm link handlebars
|
||||
npm 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
|
||||
npm publish
|
||||
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. The jsfiddle link should be updated to point to the most recent distribution for all instances in our documentation.
|
||||
|
||||
[generator-release]: https://github.com/walmartlabs/generator-release
|
||||
[pull-request]: https://github.com/wycats/handlebars.js/pull/new/master
|
||||
[issue]: https://github.com/wycats/handlebars.js/issues/new
|
||||
[jsfiddle]: http://jsfiddle.net/9D88g/25/
|
||||
@@ -0,0 +1,60 @@
|
||||
# Frequently Asked Questions
|
||||
|
||||
1. How can I file a bug report:
|
||||
|
||||
See our guidelines on [reporting issues](https://github.com/wycats/handlebars.js/blob/master/CONTRIBUTING.md#reporting-issues).
|
||||
|
||||
1. Why isn't my Mustache template working?
|
||||
|
||||
Handlebars deviates from Mustache slightly on a few behaviors. These variations are documented in our [readme](https://github.com/wycats/handlebars.js#differences-between-handlebarsjs-and-mustache).
|
||||
|
||||
1. Why is it slower when compiling?
|
||||
|
||||
The Handlebars compiler must parse the template and construct a JavaScript program which can then be run. Under some environments such as older mobile devices this can have a performance impact which can be avoided by precompiling. Generally it's recommended that precompilation and the runtime library be used on all clients.
|
||||
|
||||
1. Why doesn't this work with Content Security Policy restrictions?
|
||||
|
||||
When not using the precompiler, Handlebars generates a dynamic function for each template which can cause issues with pages that have enabled Content Policy. It's recommended that templates are precompiled or the `unsafe-eval` policy is enabled for sites that must generate dynamic templates at runtime.
|
||||
|
||||
1. How can I include script tags in my template?
|
||||
|
||||
If loading the template via an inlined `<script type="text/x-handlebars">` tag then you may need to break up the script tag with an empty comment to avoid browser parser errors:
|
||||
|
||||
```html
|
||||
<script type="text/x-handlebars">
|
||||
foo
|
||||
<scr{{!}}ipt src="bar"></scr{{!}}ipt>
|
||||
</script>
|
||||
```
|
||||
|
||||
It's generally recommended that templates are served through external, precompiled, files, which do not suffer from this issue.
|
||||
|
||||
1. Why are my precompiled scripts throwing exceptions?
|
||||
|
||||
When using the precompiler, it's important that a supporting version of the Handlebars runtime be loaded on the target page. In version 1.x there were rudimentary checks to compare the version but these did not always work. This is fixed under 2.x but the version checking does not work between these two versions. If you see unexpected errors such as `undefined is not a function` or similar, please verify that the same version is being used for both the precompiler and the client. This can be checked via:
|
||||
|
||||
```sh
|
||||
handlebars --version
|
||||
```
|
||||
If using the integrated precompiler and
|
||||
|
||||
```javascript
|
||||
console.log(Handlebars.VERSION);
|
||||
```
|
||||
On the client side.
|
||||
|
||||
We include the built client libraries in the npm package for those who want to be certain that they are using the same client libraries as the compiler.
|
||||
|
||||
Should these match, please file an issue with us, per our [issue filing guidelines](https://github.com/wycats/handlebars.js/blob/master/CONTRIBUTING.md#reporting-issues).
|
||||
|
||||
1. Why doesn't IE like the `default` name in the AMD module?
|
||||
|
||||
Some browsers such as particular versions of IE treat `default` as a reserved word in JavaScript source files. To safely use this you need to reference this via the `Handlebars['default']` lookup method. This is an unfortunate side effect of the shims necessary to backport the Handlebars ES6 code to all current browsers.
|
||||
|
||||
1. How do I load the runtime library when using AMD?
|
||||
|
||||
There are two options for loading under AMD environments. The first is to use the `handlebars.runtime.amd.js` file. This may require a [path mapping](https://github.com/wycats/handlebars.js/blob/master/spec/amd-runtime.html#L31) as well as access via the `default` field.
|
||||
|
||||
The other option is to load the `handlebars.runtime.js` UMD build, which might not require path configuration and exposes the library as both the module root and the `default` field for compatibility.
|
||||
|
||||
If not using ES6 transpilers or accessing submodules in the build the former option should be sufficent for most use cases.
|
||||
@@ -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)
|
||||
+209
@@ -0,0 +1,209 @@
|
||||
var childProcess = require('child_process');
|
||||
|
||||
module.exports = function(grunt) {
|
||||
|
||||
grunt.initConfig({
|
||||
pkg: grunt.file.readJSON('package.json'),
|
||||
|
||||
jshint: {
|
||||
options: {
|
||||
jshintrc: '.jshintrc'
|
||||
},
|
||||
files: [
|
||||
'dist/cjs/**/!(*.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: 'umd',
|
||||
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', 'http://localhost:9999/spec/amd.html?headless=true'],
|
||||
detailedError: true,
|
||||
concurrency: 2,
|
||||
browsers: [
|
||||
{browserName: 'chrome'},
|
||||
{browserName: 'firefox'},
|
||||
{browserName: 'safari', version: 7, platform: 'OS X 10.9'},
|
||||
{browserName: 'safari', version: 6, platform: 'OS X 10.8'},
|
||||
{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'}
|
||||
]
|
||||
}
|
||||
},
|
||||
sanity: {
|
||||
options: {
|
||||
build: process.env.TRAVIS_JOB_ID,
|
||||
urls: ['http://localhost:9999/spec/umd.html?headless=true', 'http://localhost:9999/spec/amd-runtime.html?headless=true', 'http://localhost:9999/spec/umd-runtime.html?headless=true'],
|
||||
detailedError: true,
|
||||
concurrency: 2,
|
||||
browsers: [
|
||||
{browserName: 'chrome'}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
watch: {
|
||||
scripts: {
|
||||
options: {
|
||||
atBegin: true
|
||||
},
|
||||
|
||||
files: ['src/*', 'lib/**/*.js', 'spec/**/*.js'],
|
||||
tasks: ['build', 'amd', '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']);
|
||||
};
|
||||
@@ -1,4 +1,4 @@
|
||||
Copyright (C) 2011 by Yehuda Katz
|
||||
Copyright (C) 2011-2014 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
|
||||
|
||||
+81
-67
@@ -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
|
||||
-----
|
||||
@@ -56,11 +65,11 @@ embedded in them, as well as the text for a link:
|
||||
|
||||
```js
|
||||
Handlebars.registerHelper('link_to', function() {
|
||||
return "<a href='" + this.url + "'>" + this.body + "</a>";
|
||||
return new Handlebars.SafeString("<a href='" + Handlebars.Utils.escapeExpression(this.url) + "'>" + Handlebars.Utils.escapeExpression(this.body) + "</a>");
|
||||
});
|
||||
|
||||
var context = { posts: [{url: "/hello-world", body: "Hello World!"}] };
|
||||
var source = "<ul>{{#posts}}<li>{{{link_to}}}</li>{{/posts}}</ul>"
|
||||
var source = "<ul>{{#posts}}<li>{{link_to}}</li>{{/posts}}</ul>"
|
||||
|
||||
var template = Handlebars.compile(source);
|
||||
template(context);
|
||||
@@ -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
|
||||
@@ -123,7 +132,7 @@ into the person object you could still display the company's name with
|
||||
an expression like `{{../company.name}}`, so:
|
||||
|
||||
```
|
||||
{{#person}}{{name}} - {{../company.name}}{{/person}}
|
||||
{{#with person}}{{name}} - {{../company.name}}{{/person}}
|
||||
```
|
||||
|
||||
would render:
|
||||
@@ -243,6 +252,14 @@ You can also use real html comments if you want them to end up in the output.
|
||||
```
|
||||
|
||||
|
||||
### Compatibility
|
||||
|
||||
There are a few Mustache behaviors that Handlebars does not implement.
|
||||
- Handlebars deviates from Mustache slightly in that it does not perform recursive lookup by default. The compile time `compat` flag must be set to enable this functionality. Users should note that there is a performance cost for enabling this flag. The exact cost varies by template, but it's recommended that performance sensitive operations should avoid this mode and instead opt for explicit path references.
|
||||
- The optional Mustache-style lambdas are not supported. Instead Handlebars provides it's own lambda resolution that follows the behaviors of helpers.
|
||||
- Alternative delimeters are not supported.
|
||||
|
||||
|
||||
Precompiling Templates
|
||||
----------------------
|
||||
|
||||
@@ -260,13 +277,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
|
||||
@@ -275,9 +299,8 @@ name sans the extension. These templates may be executed in the same
|
||||
manner as templates.
|
||||
|
||||
If using the simple mode the precompiler will generate a single
|
||||
javascript method. To execute this method it must be passed to the using
|
||||
the `Handlebars.template` method and the resulting object may be as
|
||||
normal.
|
||||
javascript method. To execute this method it must be passed to
|
||||
the `Handlebars.template` method and the resulting object may be used as normal.
|
||||
|
||||
### Optimizations
|
||||
|
||||
@@ -289,6 +312,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
|
||||
----------------------
|
||||
@@ -303,7 +328,9 @@ Handlebars has been designed to work in any ECMAScript 3 environment. This inclu
|
||||
- IE 6+
|
||||
|
||||
Older versions and other runtimes are likely to work but have not been formally
|
||||
tested.
|
||||
tested. The compiler requires `JSON.stringify` to be implemented natively or via a polyfill. If using the precompiler this is not necessary.
|
||||
|
||||
[](https://saucelabs.com/u/handlebars)
|
||||
|
||||
Performance
|
||||
-----------
|
||||
@@ -316,14 +343,7 @@ does have some big performance advantages. Justin Marney, a.k.a.
|
||||
[gotascii](http://github.com/gotascii), confirmed that with an
|
||||
[independent test](http://sorescode.com/2010/09/12/benchmarks.html). The
|
||||
rewritten Handlebars (current version) is faster than the old version,
|
||||
and we will have some benchmarks in the near future.
|
||||
|
||||
|
||||
Building
|
||||
--------
|
||||
|
||||
To build handlebars, just run `rake release`, and you will get two files
|
||||
in the `dist` directory.
|
||||
with many [performance tests](https://travis-ci.org/wycats/handlebars.js/builds/33392182#L538) being 5 to 7 times faster than the Mustache equivalent.
|
||||
|
||||
|
||||
Upgrading
|
||||
@@ -333,59 +353,53 @@ See [release-notes.md](https://github.com/wycats/handlebars.js/blob/master/relea
|
||||
|
||||
Known Issues
|
||||
------------
|
||||
* Handlebars.js can be cryptic when there's an error while rendering.
|
||||
* Using a variable, helper, or partial named `class` causes errors in IE browsers. (Instead, use `className`)
|
||||
|
||||
See [FAQ.md](https://github.com/wycats/handlebars.js/blob/master/FAQ.md) for known issues and common pitfalls.
|
||||
|
||||
|
||||
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.
|
||||
* [koa-hbs](https://github.com/jwilm/koa-hbs): [koa](https://github.com/koajs/koa) generator based
|
||||
renderer for Handlebars.js.
|
||||
* [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!
|
||||
* [DOMBars](https://github.com/blakeembrey/dombars) is a DOM-based templating engine built on the Handlebars parser and runtime
|
||||
|
||||
Have a project using Handlebars? Send us a [pull request](https://github.com/wycats/handlebars.js/pull/new/master)!
|
||||
External Resources
|
||||
------------------
|
||||
|
||||
Helping Out
|
||||
-----------
|
||||
To build Handlebars.js you'll need a few things installed.
|
||||
* [Gist about Synchronous and asynchronous loading of external handlebars templates](https://gist.github.com/2287070)
|
||||
|
||||
* Node.js
|
||||
* Ruby
|
||||
* therubyracer, for running tests - `gem install therubyracer`
|
||||
* rspec, for running tests - `gem install rspec`
|
||||
|
||||
There's a Gemfile in the repo, so you can run `bundle` to install rspec
|
||||
and therubyracer if you've got bundler installed.
|
||||
|
||||
To build Handlebars.js from scratch, you'll want to run `rake compile`
|
||||
in the root of the project. That will build Handlebars and output the
|
||||
results to the dist/ folder. To run tests, run `rake 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.
|
||||
|
||||
Some environments, notably Windows, have issues running therubyracer. Under these
|
||||
envrionments the `rake compile` and `npm test` should be sufficient to test
|
||||
most handlebars functionality.
|
||||
|
||||
If you notice any problems, please report them to the GitHub issue tracker at
|
||||
[http://github.com/wycats/handlebars.js/issues](http://github.com/wycats/handlebars.js/issues).
|
||||
Feel free to contact commondream or wycats through GitHub with any other
|
||||
questions or feature requests. To submit changes fork the project and
|
||||
send a pull request.
|
||||
Have a project using Handlebars? Send us a [pull request][pull-request]!
|
||||
|
||||
License
|
||||
-------
|
||||
Handlebars.js is released under the MIT license.
|
||||
|
||||
[bower-repo]: https://github.com/components/handlebars.js
|
||||
[builds-page]: http://builds.handlebarsjs.com.s3.amazonaws.com/bucket-listing.html?sort=lastmod&sortdir=desc
|
||||
[pull-request]: https://github.com/wycats/handlebars.js/pull/new/master
|
||||
|
||||
@@ -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,6 @@
|
||||
module.exports = {
|
||||
context: { names: [{name: "Moe"}, {name: "Larry"}, {name: "Curly"}, {name: "Shemp"}], foo: 'bar' },
|
||||
handlebars: "{{#each names}}{{../foo}}{{/each}}",
|
||||
mustache: "{{#names}}{{foo}}{{/names}}",
|
||||
eco: "<% for item in @names: %><%= @foo %><% end %>"
|
||||
};
|
||||
@@ -0,0 +1,6 @@
|
||||
module.exports = {
|
||||
context: { names: [{bat: 'foo', name: ["Moe"]}, {bat: 'foo', name: ["Larry"]}, {bat: 'foo', name: ["Curly"]}, {bat: 'foo', name: ["Shemp"]}], foo: 'bar' },
|
||||
handlebars: "{{#each names}}{{#each name}}{{../bat}}{{../../foo}}{{/each}}{{/each}}",
|
||||
mustache: "{{#names}}{{#name}}{{bat}}{{foo}}{{/name}}{{/names}}",
|
||||
eco: "<% for item in @names: %><% for child in item.name: %><%= item.bat %><%= @foo %><% end %><% end %>"
|
||||
};
|
||||
@@ -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: {bar: {baz: "Larry"}}, age: 45 } },
|
||||
handlebars: "{{person.name.bar.baz}}{{person.age}}{{person.foo}}{{animal.age}}",
|
||||
dust: "{person.name.bar.baz}{person.age}{person.foo}{animal.age}",
|
||||
eco: "<%= @person.name.bar.baz %><%= @person.age %><%= @person.foo %><% if @animal: %><%= @animal.age %><% end %>",
|
||||
mustache: "{{person.name.bar.baz}}{{person.age}}{{person.foo}}{{animal.age}}"
|
||||
};
|
||||
@@ -0,0 +1,7 @@
|
||||
module.exports = {
|
||||
context: {},
|
||||
handlebars: "Hello world",
|
||||
dust: "Hello world",
|
||||
mustache: "Hello world",
|
||||
eco: "Hello world"
|
||||
};
|
||||
@@ -0,0 +1,14 @@
|
||||
module.exports = {
|
||||
helpers: {
|
||||
echo: function(value) {
|
||||
return 'foo ' + value;
|
||||
},
|
||||
header: function() {
|
||||
return "Colors";
|
||||
}
|
||||
},
|
||||
handlebars: "{{echo (header)}}",
|
||||
eco: "<%= @echo(@header()) %>"
|
||||
};
|
||||
|
||||
module.exports.context = module.exports.helpers;
|
||||
@@ -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,131 @@
|
||||
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,
|
||||
compatOut,
|
||||
dustOut,
|
||||
ecoOut,
|
||||
mustacheOut;
|
||||
|
||||
var handlebar = Handlebars.compile(template.handlebars, {data: false}),
|
||||
compat = Handlebars.compile(template.handlebars, {data: false, compat: true}),
|
||||
options = {helpers: template.helpers};
|
||||
_.each(template.partials && template.partials.handlebars, function(partial, name) {
|
||||
Handlebars.registerPartial(name, Handlebars.compile(partial, {data: false}));
|
||||
});
|
||||
|
||||
handlebarsOut = handlebar(context, options);
|
||||
bench("handlebars", function() {
|
||||
handlebar(context, options);
|
||||
});
|
||||
|
||||
compatOut = compat(context, options);
|
||||
bench("compat", function() {
|
||||
compat(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(compatOut, 'compat');
|
||||
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);
|
||||
});
|
||||
};
|
||||
+17
-135
@@ -22,7 +22,7 @@ var optimist = require('optimist')
|
||||
'type': 'string',
|
||||
'description': 'Path to handlebar.js (only valid for amd-style)',
|
||||
'alias': 'handlebarPath',
|
||||
'default': ''
|
||||
'default': ''
|
||||
},
|
||||
'k': {
|
||||
'type': 'string',
|
||||
@@ -70,144 +70,26 @@ 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'
|
||||
},
|
||||
'v': {
|
||||
'type': 'boolean',
|
||||
'description': 'Prints the current compiler version',
|
||||
'alias': 'version'
|
||||
}
|
||||
})
|
||||
|
||||
.check(function(argv) {
|
||||
var template = [0];
|
||||
if (!argv._.length) {
|
||||
throw 'Must define at least one template or directory.';
|
||||
}
|
||||
|
||||
argv._.forEach(function(template) {
|
||||
try {
|
||||
fs.statSync(template);
|
||||
} catch (err) {
|
||||
throw 'Unable to open template file "' + template + '"';
|
||||
}
|
||||
});
|
||||
})
|
||||
.check(function(argv) {
|
||||
if (argv.simple && argv.min) {
|
||||
throw 'Unable to minimze simple output';
|
||||
}
|
||||
if (argv.simple && (argv._.length !== 1 || fs.statSync(argv._[0]).isDirectory())) {
|
||||
throw 'Unable to output multiple templates in simple mode';
|
||||
if (argv.version) {
|
||||
return;
|
||||
}
|
||||
});
|
||||
|
||||
var fs = require('fs'),
|
||||
handlebars = require('../lib/handlebars'),
|
||||
basename = require('path').basename,
|
||||
uglify = require('uglify-js');
|
||||
|
||||
var argv = optimist.argv,
|
||||
template = argv._[0];
|
||||
|
||||
// Convert the known list into a hash
|
||||
var known = {};
|
||||
if (argv.known && !Array.isArray(argv.known)) {
|
||||
argv.known = [argv.known];
|
||||
}
|
||||
if (argv.known) {
|
||||
for (var i = 0, len = argv.known.length; i < len; i++) {
|
||||
known[argv.known[i]] = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Build file extension pattern
|
||||
var extension = argv.extension.replace(/[\\^$*+?.():=!|{}\-\[\]]/g, function(arg) { return '\\' + arg; });
|
||||
extension = new RegExp('\\.' + extension + '$');
|
||||
|
||||
var output = [];
|
||||
if (!argv.simple) {
|
||||
if (argv.amd) {
|
||||
output.push('define([\'' + argv.handlebarPath + 'handlebars\'], function(Handlebars) {\n');
|
||||
} else if (argv.commonjs) {
|
||||
output.push('var Handlebars = require("' + argv.commonjs + '");');
|
||||
} else {
|
||||
output.push('(function() {\n');
|
||||
}
|
||||
output.push(' var template = Handlebars.template, templates = ');
|
||||
output.push(argv.namespace);
|
||||
output.push(' = ');
|
||||
output.push(argv.namespace);
|
||||
output.push(' || {};\n');
|
||||
}
|
||||
function processTemplate(template, root) {
|
||||
var path = template,
|
||||
stat = fs.statSync(path);
|
||||
if (stat.isDirectory()) {
|
||||
fs.readdirSync(template).map(function(file) {
|
||||
var path = template + '/' + file;
|
||||
|
||||
if (extension.test(path) || fs.statSync(path).isDirectory()) {
|
||||
processTemplate(path, root || template);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
var data = fs.readFileSync(path, 'utf8');
|
||||
|
||||
var options = {
|
||||
knownHelpers: known,
|
||||
knownHelpersOnly: argv.o
|
||||
};
|
||||
|
||||
if (argv.data) {
|
||||
options.data = true;
|
||||
}
|
||||
|
||||
// Clean the template name
|
||||
if (!root) {
|
||||
template = basename(template);
|
||||
} else if (template.indexOf(root) === 0) {
|
||||
template = template.substring(root.length+1);
|
||||
}
|
||||
template = template.replace(extension, '');
|
||||
|
||||
if (argv.simple) {
|
||||
output.push(handlebars.precompile(data, options) + '\n');
|
||||
} else if (argv.partial) {
|
||||
if(argv.amd && (argv._.length == 1 && !fs.statSync(argv._[0]).isDirectory())) {
|
||||
output.push('return ');
|
||||
}
|
||||
output.push('Handlebars.partials[\'' + template + '\'] = template(' + handlebars.precompile(data, options) + ');\n');
|
||||
} else {
|
||||
if(argv.amd && (argv._.length == 1 && !fs.statSync(argv._[0]).isDirectory())) {
|
||||
output.push('return ');
|
||||
}
|
||||
output.push('templates[\'' + template + '\'] = template(' + handlebars.precompile(data, options) + ');\n');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
argv._.forEach(function(template) {
|
||||
processTemplate(template, argv.root);
|
||||
});
|
||||
|
||||
// Output the content
|
||||
if (!argv.simple) {
|
||||
if (argv.amd) {
|
||||
if(argv._.length > 1 || (argv._.length == 1 && fs.statSync(argv._[0]).isDirectory())) {
|
||||
if(argv.partial){
|
||||
output.push('return Handlebars.partials;\n');
|
||||
} else {
|
||||
output.push('return templates;\n');
|
||||
}
|
||||
}
|
||||
output.push('});');
|
||||
} else if (!argv.commonjs) {
|
||||
output.push('})();');
|
||||
}
|
||||
}
|
||||
output = output.join('');
|
||||
|
||||
if (argv.min) {
|
||||
output = uglify.minify(output, {fromString: true}).code;
|
||||
}
|
||||
|
||||
if (argv.output) {
|
||||
fs.writeFileSync(argv.output, output, 'utf8');
|
||||
} else {
|
||||
console.log(output);
|
||||
}
|
||||
var argv = optimist.argv;
|
||||
argv.templates = argv._;
|
||||
delete argv._;
|
||||
return require('../lib/precompiler').cli(argv);
|
||||
|
||||
@@ -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": "2.0.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"
|
||||
@@ -11,11 +11,12 @@ Gem::Specification.new do |gem|
|
||||
gem.description = %q{Handlebars.js source code wrapper for (pre)compilation gems.}
|
||||
gem.summary = %q{Handlebars.js source code wrapper}
|
||||
gem.homepage = "https://github.com/wycats/handlebars.js/"
|
||||
gem.version = package["version"]
|
||||
gem.version = package["version"].sub! "-", "."
|
||||
gem.license = "MIT"
|
||||
|
||||
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>2.0.0</version>
|
||||
<authors>handlebars.js Authors</authors>
|
||||
<licenseUrl>https://github.com/wycats/handlebars.js/blob/master/LICENSE</licenseUrl>
|
||||
<projectUrl>https://github.com/wycats/handlebars.js/</projectUrl>
|
||||
@@ -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);
|
||||
;
|
||||
+24
-32
@@ -1,43 +1,35 @@
|
||||
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 = function (input, options) {
|
||||
return precompile(input, options, hb);
|
||||
};
|
||||
|
||||
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
|
||||
Handlebars['default'] = Handlebars;
|
||||
|
||||
// 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,34 @@
|
||||
/*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.escapeExpression = Utils.escapeExpression;
|
||||
|
||||
hb.VM = runtime;
|
||||
hb.template = function(spec) {
|
||||
return runtime.template(spec, hb);
|
||||
};
|
||||
|
||||
return hb;
|
||||
};
|
||||
|
||||
var Handlebars = create();
|
||||
Handlebars.create = create;
|
||||
|
||||
Handlebars['default'] = Handlebars;
|
||||
|
||||
export default Handlebars;
|
||||
+206
-143
@@ -1,166 +1,229 @@
|
||||
/*jshint eqnull: true */
|
||||
module Utils from "./utils";
|
||||
import Exception from "./exception";
|
||||
|
||||
module.exports.create = function() {
|
||||
export var VERSION = "2.0.0";
|
||||
export var COMPILER_REVISION = 6;
|
||||
|
||||
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'
|
||||
4: '== 1.x.x',
|
||||
5: '== 2.0.0-alpha.x',
|
||||
6: '>= 2.0.0-beta.1'
|
||||
};
|
||||
|
||||
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) {
|
||||
if (toString.call(name) === objectType) {
|
||||
if (fn) { throw new Exception('Arg not supported with multiple helpers'); }
|
||||
Utils.extend(this.helpers, name);
|
||||
} else {
|
||||
this.helpers[name] = fn;
|
||||
}
|
||||
},
|
||||
unregisterHelper: function(name) {
|
||||
delete this.helpers[name];
|
||||
},
|
||||
|
||||
registerPartial: function(name, partial) {
|
||||
if (toString.call(name) === objectType) {
|
||||
Utils.extend(this.partials, name);
|
||||
} else {
|
||||
this.partials[name] = partial;
|
||||
}
|
||||
},
|
||||
unregisterPartial: function(name) {
|
||||
delete this.partials[name];
|
||||
}
|
||||
};
|
||||
|
||||
function registerDefaultHelpers(instance) {
|
||||
instance.registerHelper('helperMissing', function(/* [args, ]options */) {
|
||||
if(arguments.length === 1) {
|
||||
// A missing field in a {{foo}} constuct.
|
||||
return undefined;
|
||||
} else {
|
||||
// Someone is actually trying to call something, blow up.
|
||||
throw new Exception("Missing helper: '" + arguments[arguments.length-1].name + "'");
|
||||
}
|
||||
});
|
||||
|
||||
instance.registerHelper('blockHelperMissing', function(context, options) {
|
||||
var inverse = options.inverse,
|
||||
fn = options.fn;
|
||||
|
||||
if(context === true) {
|
||||
return fn(this);
|
||||
} else if(context === false || context == null) {
|
||||
return inverse(this);
|
||||
}
|
||||
} else {
|
||||
return fn(context);
|
||||
}
|
||||
});
|
||||
} else if (isArray(context)) {
|
||||
if(context.length > 0) {
|
||||
if (options.ids) {
|
||||
options.ids = [options.name];
|
||||
}
|
||||
|
||||
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 });
|
||||
return instance.helpers.each(context, options);
|
||||
} else {
|
||||
return inverse(this);
|
||||
}
|
||||
} else {
|
||||
for(var key in context) {
|
||||
if(context.hasOwnProperty(key)) {
|
||||
if(data) { data.key = key; }
|
||||
ret = ret + fn(context[key], {data: data});
|
||||
i++;
|
||||
if (options.data && options.ids) {
|
||||
var data = createFrame(options.data);
|
||||
data.contextPath = Utils.appendContextPath(options.data.contextPath, options.name);
|
||||
options = {data: data};
|
||||
}
|
||||
|
||||
return fn(context, options);
|
||||
}
|
||||
});
|
||||
|
||||
instance.registerHelper('each', function(context, options) {
|
||||
if (!options) {
|
||||
throw new Exception('Must pass iterator to #each');
|
||||
}
|
||||
|
||||
var fn = options.fn, inverse = options.inverse;
|
||||
var i = 0, ret = "", data;
|
||||
|
||||
var contextPath;
|
||||
if (options.data && options.ids) {
|
||||
contextPath = Utils.appendContextPath(options.data.contextPath, options.ids[0]) + '.';
|
||||
}
|
||||
|
||||
if (isFunction(context)) { context = context.call(this); }
|
||||
|
||||
if (options.data) {
|
||||
data = createFrame(options.data);
|
||||
}
|
||||
|
||||
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));
|
||||
|
||||
if (contextPath) {
|
||||
data.contextPath = contextPath + i;
|
||||
}
|
||||
}
|
||||
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);
|
||||
|
||||
if (contextPath) {
|
||||
data.contextPath = contextPath + key;
|
||||
}
|
||||
}
|
||||
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); }
|
||||
|
||||
var fn = options.fn;
|
||||
|
||||
if (!Utils.isEmpty(context)) {
|
||||
if (options.data && options.ids) {
|
||||
var data = createFrame(options.data);
|
||||
data.contextPath = Utils.appendContextPath(options.data.contextPath, options.ids[0]);
|
||||
options = {data:data};
|
||||
}
|
||||
|
||||
return fn(context, options);
|
||||
} else {
|
||||
return options.inverse(this);
|
||||
}
|
||||
});
|
||||
|
||||
instance.registerHelper('log', function(message, options) {
|
||||
var level = options.data && options.data.level != null ? parseInt(options.data.level, 10) : 1;
|
||||
instance.log(level, message);
|
||||
});
|
||||
|
||||
instance.registerHelper('lookup', function(obj, field) {
|
||||
return obj && obj[field];
|
||||
});
|
||||
}
|
||||
|
||||
export 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, message) {
|
||||
if (logger.level <= level) {
|
||||
var method = logger.methodMap[level];
|
||||
if (typeof console !== 'undefined' && console[method]) {
|
||||
console[method].call(console, message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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 log = logger.log;
|
||||
|
||||
export var createFrame = function(object) {
|
||||
var frame = Utils.extend({}, object);
|
||||
frame._parent = object;
|
||||
return frame;
|
||||
};
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
var Handlebars = {};
|
||||
|
||||
(function(Handlebars, undefined) {
|
||||
@@ -1 +0,0 @@
|
||||
})(Handlebars);
|
||||
+202
-127
@@ -1,138 +1,213 @@
|
||||
exports.attach = function(Handlebars) {
|
||||
import Exception from "../exception";
|
||||
|
||||
// BEGIN(BROWSER)
|
||||
Handlebars.AST = {};
|
||||
function LocationInfo(locInfo) {
|
||||
locInfo = locInfo || {};
|
||||
this.firstLine = locInfo.first_line;
|
||||
this.firstColumn = locInfo.first_column;
|
||||
this.lastColumn = locInfo.last_column;
|
||||
this.lastLine = locInfo.last_line;
|
||||
}
|
||||
|
||||
Handlebars.AST.ProgramNode = function(statements, inverse) {
|
||||
this.type = "program";
|
||||
this.statements = statements;
|
||||
if(inverse) { this.inverse = new Handlebars.AST.ProgramNode(inverse); }
|
||||
};
|
||||
var AST = {
|
||||
ProgramNode: function(statements, strip, locInfo) {
|
||||
LocationInfo.call(this, locInfo);
|
||||
this.type = "program";
|
||||
this.statements = statements;
|
||||
this.strip = strip;
|
||||
},
|
||||
|
||||
Handlebars.AST.MustacheNode = function(rawParams, hash, unescaped) {
|
||||
this.type = "mustache";
|
||||
this.escaped = !unescaped;
|
||||
this.hash = hash;
|
||||
MustacheNode: function(rawParams, hash, open, strip, locInfo) {
|
||||
LocationInfo.call(this, locInfo);
|
||||
this.type = "mustache";
|
||||
this.strip = strip;
|
||||
|
||||
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);
|
||||
// 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;
|
||||
}
|
||||
};
|
||||
|
||||
verifyMatch(mustache.id, close);
|
||||
this.type = "block";
|
||||
this.mustache = mustache;
|
||||
this.program = program;
|
||||
this.inverse = inverse;
|
||||
if (rawParams instanceof AST.SexprNode) {
|
||||
this.sexpr = rawParams;
|
||||
} else {
|
||||
// Support old AST API
|
||||
this.sexpr = new AST.SexprNode(rawParams, hash);
|
||||
}
|
||||
|
||||
if (this.inverse && !this.program) {
|
||||
this.isInverse = true;
|
||||
// Support old AST API that stored this info in MustacheNode
|
||||
this.id = this.sexpr.id;
|
||||
this.params = this.sexpr.params;
|
||||
this.hash = this.sexpr.hash;
|
||||
this.eligibleHelper = this.sexpr.eligibleHelper;
|
||||
this.isHelper = this.sexpr.isHelper;
|
||||
},
|
||||
|
||||
SexprNode: function(rawParams, hash, locInfo) {
|
||||
LocationInfo.call(this, locInfo);
|
||||
|
||||
this.type = "sexpr";
|
||||
this.hash = hash;
|
||||
|
||||
var id = this.id = rawParams[0];
|
||||
var params = this.params = rawParams.slice(1);
|
||||
|
||||
// a mustache is definitely a helper if:
|
||||
// * it is an eligible helper, and
|
||||
// * it has at least one parameter or hash segment
|
||||
this.isHelper = !!(params.length || hash);
|
||||
|
||||
// a mustache is an eligible helper if:
|
||||
// * its id is simple (a single part, not `this` or `..`)
|
||||
this.eligibleHelper = this.isHelper || id.isSimple;
|
||||
|
||||
// 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, hash, strip, locInfo) {
|
||||
LocationInfo.call(this, locInfo);
|
||||
this.type = "partial";
|
||||
this.partialName = partialName;
|
||||
this.context = context;
|
||||
this.hash = hash;
|
||||
this.strip = strip;
|
||||
|
||||
this.strip.inlineStandalone = true;
|
||||
},
|
||||
|
||||
BlockNode: function(mustache, program, inverse, strip, locInfo) {
|
||||
LocationInfo.call(this, locInfo);
|
||||
|
||||
this.type = 'block';
|
||||
this.mustache = mustache;
|
||||
this.program = program;
|
||||
this.inverse = inverse;
|
||||
this.strip = strip;
|
||||
|
||||
if (inverse && !program) {
|
||||
this.isInverse = true;
|
||||
}
|
||||
},
|
||||
|
||||
RawBlockNode: function(mustache, content, close, locInfo) {
|
||||
LocationInfo.call(this, locInfo);
|
||||
|
||||
if (mustache.sexpr.id.original !== close) {
|
||||
throw new Exception(mustache.sexpr.id.original + " doesn't match " + close, this);
|
||||
}
|
||||
|
||||
content = new AST.ContentNode(content, locInfo);
|
||||
|
||||
this.type = 'block';
|
||||
this.mustache = mustache;
|
||||
this.program = new AST.ProgramNode([content], {}, locInfo);
|
||||
},
|
||||
|
||||
ContentNode: function(string, locInfo) {
|
||||
LocationInfo.call(this, locInfo);
|
||||
this.type = "content";
|
||||
this.original = this.string = string;
|
||||
},
|
||||
|
||||
HashNode: function(pairs, locInfo) {
|
||||
LocationInfo.call(this, locInfo);
|
||||
this.type = "hash";
|
||||
this.pairs = pairs;
|
||||
},
|
||||
|
||||
IdNode: function(parts, locInfo) {
|
||||
LocationInfo.call(this, locInfo);
|
||||
this.type = "ID";
|
||||
|
||||
var original = "",
|
||||
dig = [],
|
||||
depth = 0,
|
||||
depthString = '';
|
||||
|
||||
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, this);
|
||||
} else if (part === "..") {
|
||||
depth++;
|
||||
depthString += '../';
|
||||
} else {
|
||||
this.isScoped = true;
|
||||
}
|
||||
} else {
|
||||
dig.push(part);
|
||||
}
|
||||
}
|
||||
|
||||
this.original = original;
|
||||
this.parts = dig;
|
||||
this.string = dig.join('.');
|
||||
this.depth = depth;
|
||||
this.idName = depthString + this.string;
|
||||
|
||||
// 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, locInfo) {
|
||||
LocationInfo.call(this, locInfo);
|
||||
this.type = "PARTIAL_NAME";
|
||||
this.name = name.original;
|
||||
},
|
||||
|
||||
DataNode: function(id, locInfo) {
|
||||
LocationInfo.call(this, locInfo);
|
||||
this.type = "DATA";
|
||||
this.id = id;
|
||||
this.stringModeValue = id.stringModeValue;
|
||||
this.idName = '@' + id.stringModeValue;
|
||||
},
|
||||
|
||||
StringNode: function(string, locInfo) {
|
||||
LocationInfo.call(this, locInfo);
|
||||
this.type = "STRING";
|
||||
this.original =
|
||||
this.string =
|
||||
this.stringModeValue = string;
|
||||
},
|
||||
|
||||
NumberNode: function(number, locInfo) {
|
||||
LocationInfo.call(this, locInfo);
|
||||
this.type = "NUMBER";
|
||||
this.original =
|
||||
this.number = number;
|
||||
this.stringModeValue = Number(number);
|
||||
},
|
||||
|
||||
BooleanNode: function(bool, locInfo) {
|
||||
LocationInfo.call(this, locInfo);
|
||||
this.type = "BOOLEAN";
|
||||
this.bool = bool;
|
||||
this.stringModeValue = bool === "true";
|
||||
},
|
||||
|
||||
CommentNode: function(comment, locInfo) {
|
||||
LocationInfo.call(this, locInfo);
|
||||
this.type = "comment";
|
||||
this.comment = comment;
|
||||
|
||||
this.strip = {
|
||||
inlineStandalone: true
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
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,18 @@
|
||||
var handlebars = require("./parser");
|
||||
import parser from "./parser";
|
||||
import AST from "./ast";
|
||||
module Helpers from "./helpers";
|
||||
import { extend } from "../utils";
|
||||
|
||||
exports.attach = function(Handlebars) {
|
||||
export { parser };
|
||||
|
||||
// BEGIN(BROWSER)
|
||||
|
||||
Handlebars.Parser = handlebars;
|
||||
|
||||
Handlebars.parse = function(input) {
|
||||
var yy = {};
|
||||
extend(yy, Helpers, AST);
|
||||
|
||||
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);
|
||||
};
|
||||
parser.yy = yy;
|
||||
|
||||
// END(BROWSER)
|
||||
|
||||
return Handlebars;
|
||||
};
|
||||
return parser.parse(input);
|
||||
}
|
||||
|
||||
+158
-1012
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,186 @@
|
||||
import Exception from "../exception";
|
||||
|
||||
export function stripFlags(open, close) {
|
||||
return {
|
||||
left: open.charAt(2) === '~',
|
||||
right: close.charAt(close.length-3) === '~'
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
export function prepareBlock(mustache, program, inverseAndProgram, close, inverted, locInfo) {
|
||||
/*jshint -W040 */
|
||||
if (mustache.sexpr.id.original !== close.path.original) {
|
||||
throw new Exception(mustache.sexpr.id.original + ' doesn\'t match ' + close.path.original, mustache);
|
||||
}
|
||||
|
||||
var inverse = inverseAndProgram && inverseAndProgram.program;
|
||||
|
||||
var strip = {
|
||||
left: mustache.strip.left,
|
||||
right: close.strip.right,
|
||||
|
||||
// Determine the standalone candiacy. Basically flag our content as being possibly standalone
|
||||
// so our parent can determine if we actually are standalone
|
||||
openStandalone: isNextWhitespace(program.statements),
|
||||
closeStandalone: isPrevWhitespace((inverse || program).statements)
|
||||
};
|
||||
|
||||
if (mustache.strip.right) {
|
||||
omitRight(program.statements, null, true);
|
||||
}
|
||||
|
||||
if (inverse) {
|
||||
var inverseStrip = inverseAndProgram.strip;
|
||||
|
||||
if (inverseStrip.left) {
|
||||
omitLeft(program.statements, null, true);
|
||||
}
|
||||
if (inverseStrip.right) {
|
||||
omitRight(inverse.statements, null, true);
|
||||
}
|
||||
if (close.strip.left) {
|
||||
omitLeft(inverse.statements, null, true);
|
||||
}
|
||||
|
||||
// Find standalone else statments
|
||||
if (isPrevWhitespace(program.statements)
|
||||
&& isNextWhitespace(inverse.statements)) {
|
||||
|
||||
omitLeft(program.statements);
|
||||
omitRight(inverse.statements);
|
||||
}
|
||||
} else {
|
||||
if (close.strip.left) {
|
||||
omitLeft(program.statements, null, true);
|
||||
}
|
||||
}
|
||||
|
||||
if (inverted) {
|
||||
return new this.BlockNode(mustache, inverse, program, strip, locInfo);
|
||||
} else {
|
||||
return new this.BlockNode(mustache, program, inverse, strip, locInfo);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export function prepareProgram(statements, isRoot) {
|
||||
for (var i = 0, l = statements.length; i < l; i++) {
|
||||
var current = statements[i],
|
||||
strip = current.strip;
|
||||
|
||||
if (!strip) {
|
||||
continue;
|
||||
}
|
||||
|
||||
var _isPrevWhitespace = isPrevWhitespace(statements, i, isRoot, current.type === 'partial'),
|
||||
_isNextWhitespace = isNextWhitespace(statements, i, isRoot),
|
||||
|
||||
openStandalone = strip.openStandalone && _isPrevWhitespace,
|
||||
closeStandalone = strip.closeStandalone && _isNextWhitespace,
|
||||
inlineStandalone = strip.inlineStandalone && _isPrevWhitespace && _isNextWhitespace;
|
||||
|
||||
if (strip.right) {
|
||||
omitRight(statements, i, true);
|
||||
}
|
||||
if (strip.left) {
|
||||
omitLeft(statements, i, true);
|
||||
}
|
||||
|
||||
if (inlineStandalone) {
|
||||
omitRight(statements, i);
|
||||
|
||||
if (omitLeft(statements, i)) {
|
||||
// If we are on a standalone node, save the indent info for partials
|
||||
if (current.type === 'partial') {
|
||||
current.indent = (/([ \t]+$)/).exec(statements[i-1].original) ? RegExp.$1 : '';
|
||||
}
|
||||
}
|
||||
}
|
||||
if (openStandalone) {
|
||||
omitRight((current.program || current.inverse).statements);
|
||||
|
||||
// Strip out the previous content node if it's whitespace only
|
||||
omitLeft(statements, i);
|
||||
}
|
||||
if (closeStandalone) {
|
||||
// Always strip the next node
|
||||
omitRight(statements, i);
|
||||
|
||||
omitLeft((current.inverse || current.program).statements);
|
||||
}
|
||||
}
|
||||
|
||||
return statements;
|
||||
}
|
||||
|
||||
function isPrevWhitespace(statements, i, isRoot) {
|
||||
if (i === undefined) {
|
||||
i = statements.length;
|
||||
}
|
||||
|
||||
// Nodes that end with newlines are considered whitespace (but are special
|
||||
// cased for strip operations)
|
||||
var prev = statements[i-1],
|
||||
sibling = statements[i-2];
|
||||
if (!prev) {
|
||||
return isRoot;
|
||||
}
|
||||
|
||||
if (prev.type === 'content') {
|
||||
return (sibling || !isRoot ? (/\r?\n\s*?$/) : (/(^|\r?\n)\s*?$/)).test(prev.original);
|
||||
}
|
||||
}
|
||||
function isNextWhitespace(statements, i, isRoot) {
|
||||
if (i === undefined) {
|
||||
i = -1;
|
||||
}
|
||||
|
||||
var next = statements[i+1],
|
||||
sibling = statements[i+2];
|
||||
if (!next) {
|
||||
return isRoot;
|
||||
}
|
||||
|
||||
if (next.type === 'content') {
|
||||
return (sibling || !isRoot ? (/^\s*?\r?\n/) : (/^\s*?(\r?\n|$)/)).test(next.original);
|
||||
}
|
||||
}
|
||||
|
||||
// Marks the node to the right of the position as omitted.
|
||||
// I.e. {{foo}}' ' will mark the ' ' node as omitted.
|
||||
//
|
||||
// If i is undefined, then the first child will be marked as such.
|
||||
//
|
||||
// If mulitple is truthy then all whitespace will be stripped out until non-whitespace
|
||||
// content is met.
|
||||
function omitRight(statements, i, multiple) {
|
||||
var current = statements[i == null ? 0 : i + 1];
|
||||
if (!current || current.type !== 'content' || (!multiple && current.rightStripped)) {
|
||||
return;
|
||||
}
|
||||
|
||||
var original = current.string;
|
||||
current.string = current.string.replace(multiple ? (/^\s+/) : (/^[ \t]*\r?\n?/), '');
|
||||
current.rightStripped = current.string !== original;
|
||||
}
|
||||
|
||||
// Marks the node to the left of the position as omitted.
|
||||
// I.e. ' '{{foo}} will mark the ' ' node as omitted.
|
||||
//
|
||||
// If i is undefined then the last child will be marked as such.
|
||||
//
|
||||
// If mulitple is truthy then all whitespace will be stripped out until non-whitespace
|
||||
// content is met.
|
||||
function omitLeft(statements, i, multiple) {
|
||||
var current = statements[i == null ? statements.length - 1 : i - 1];
|
||||
if (!current || current.type !== 'content' || (!multiple && current.leftStripped)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// We omit the last node if it's whitespace only and not preceeded by a non-content node.
|
||||
var original = current.string;
|
||||
current.string = current.string.replace(multiple ? (/\s+$/) : (/[ \t]+$/), '');
|
||||
current.leftStripped = current.string !== original;
|
||||
return current.leftStripped;
|
||||
}
|
||||
@@ -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,962 @@
|
||||
import { COMPILER_REVISION, REVISION_CHANGES } from "../base";
|
||||
import Exception from "../exception";
|
||||
|
||||
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*/) {
|
||||
if (JavaScriptCompiler.isValidJavaScriptVariableName(name)) {
|
||||
return parent + "." + name;
|
||||
} else {
|
||||
return parent + "['" + name + "']";
|
||||
}
|
||||
},
|
||||
depthedLookup: function(name) {
|
||||
this.aliases.lookup = 'this.lookup';
|
||||
|
||||
return 'lookup(depths, "' + name + '")';
|
||||
},
|
||||
|
||||
compilerInfo: function() {
|
||||
var revision = COMPILER_REVISION,
|
||||
versions = REVISION_CHANGES[revision];
|
||||
return [revision, versions];
|
||||
},
|
||||
|
||||
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;
|
||||
this.stringParams = this.options.stringParams;
|
||||
this.trackIds = this.options.trackIds;
|
||||
this.precompile = !asObject;
|
||||
|
||||
this.name = this.environment.name;
|
||||
this.isChild = !!context;
|
||||
this.context = context || {
|
||||
programs: [],
|
||||
environments: []
|
||||
};
|
||||
|
||||
this.preamble();
|
||||
|
||||
this.stackSlot = 0;
|
||||
this.stackVars = [];
|
||||
this.aliases = {};
|
||||
this.registers = { list: [] };
|
||||
this.hashes = [];
|
||||
this.compileStack = [];
|
||||
this.inlineStack = [];
|
||||
|
||||
this.compileChildren(environment, options);
|
||||
|
||||
this.useDepths = this.useDepths || environment.depths.list.length || this.options.compat;
|
||||
|
||||
var opcodes = environment.opcodes,
|
||||
opcode,
|
||||
i,
|
||||
l;
|
||||
|
||||
for (i = 0, l = opcodes.length; i < l; i++) {
|
||||
opcode = opcodes[i];
|
||||
|
||||
this[opcode.opcode].apply(this, opcode.args);
|
||||
}
|
||||
|
||||
// Flush any trailing content that might be pending.
|
||||
this.pushSource('');
|
||||
|
||||
/* istanbul ignore next */
|
||||
if (this.stackSlot || this.inlineStack.length || this.compileStack.length) {
|
||||
throw new Exception('Compile completed with content left on stack');
|
||||
}
|
||||
|
||||
var fn = this.createFunctionContext(asObject);
|
||||
if (!this.isChild) {
|
||||
var ret = {
|
||||
compiler: this.compilerInfo(),
|
||||
main: fn
|
||||
};
|
||||
var programs = this.context.programs;
|
||||
for (i = 0, l = programs.length; i < l; i++) {
|
||||
if (programs[i]) {
|
||||
ret[i] = programs[i];
|
||||
}
|
||||
}
|
||||
|
||||
if (this.environment.usePartial) {
|
||||
ret.usePartial = true;
|
||||
}
|
||||
if (this.options.data) {
|
||||
ret.useData = true;
|
||||
}
|
||||
if (this.useDepths) {
|
||||
ret.useDepths = true;
|
||||
}
|
||||
if (this.options.compat) {
|
||||
ret.compat = true;
|
||||
}
|
||||
|
||||
if (!asObject) {
|
||||
ret.compiler = JSON.stringify(ret.compiler);
|
||||
ret = this.objectLiteral(ret);
|
||||
}
|
||||
|
||||
return ret;
|
||||
} else {
|
||||
return fn;
|
||||
}
|
||||
},
|
||||
|
||||
preamble: function() {
|
||||
// track the last context pushed into place to allow skipping the
|
||||
// getContext opcode when it would be a noop
|
||||
this.lastContext = 0;
|
||||
this.source = [];
|
||||
},
|
||||
|
||||
createFunctionContext: function(asObject) {
|
||||
var varDeclarations = '';
|
||||
|
||||
var locals = this.stackVars.concat(this.registers.list);
|
||||
if(locals.length > 0) {
|
||||
varDeclarations += ", " + locals.join(", ");
|
||||
}
|
||||
|
||||
// Generate minimizer alias mappings
|
||||
for (var alias in this.aliases) {
|
||||
if (this.aliases.hasOwnProperty(alias)) {
|
||||
varDeclarations += ', ' + alias + '=' + this.aliases[alias];
|
||||
}
|
||||
}
|
||||
|
||||
var params = ["depth0", "helpers", "partials", "data"];
|
||||
|
||||
if (this.useDepths) {
|
||||
params.push('depths');
|
||||
}
|
||||
|
||||
// Perform a second pass over the output to merge content when possible
|
||||
var source = this.mergeSource(varDeclarations);
|
||||
|
||||
if (asObject) {
|
||||
params.push(source);
|
||||
|
||||
return Function.apply(this, params);
|
||||
} else {
|
||||
return 'function(' + params.join(',') + ') {\n ' + source + '}';
|
||||
}
|
||||
},
|
||||
mergeSource: function(varDeclarations) {
|
||||
var source = '',
|
||||
buffer,
|
||||
appendOnly = !this.forceBuffer,
|
||||
appendFirst;
|
||||
|
||||
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) {
|
||||
if (!source) {
|
||||
appendFirst = true;
|
||||
source = buffer + ';\n ';
|
||||
} else {
|
||||
source += 'buffer += ' + buffer + ';\n ';
|
||||
}
|
||||
buffer = undefined;
|
||||
}
|
||||
source += line + '\n ';
|
||||
|
||||
if (!this.environment.isSimple) {
|
||||
appendOnly = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (appendOnly) {
|
||||
if (buffer || !source) {
|
||||
source += 'return ' + (buffer || '""') + ';\n';
|
||||
}
|
||||
} else {
|
||||
varDeclarations += ", buffer = " + (appendFirst ? '' : this.initializeBuffer());
|
||||
if (buffer) {
|
||||
source += 'return buffer + ' + buffer + ';\n';
|
||||
} else {
|
||||
source += 'return buffer;\n';
|
||||
}
|
||||
}
|
||||
|
||||
if (varDeclarations) {
|
||||
source = 'var ' + varDeclarations.substring(2) + (appendFirst ? '' : ';\n ') + source;
|
||||
}
|
||||
|
||||
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
|
||||
// `{{#this.foo}}...{{/this.foo}}`, resolve the value of `foo`, and
|
||||
// replace it on the stack with the result of properly
|
||||
// invoking blockHelperMissing.
|
||||
blockValue: function(name) {
|
||||
this.aliases.blockHelperMissing = 'helpers.blockHelperMissing';
|
||||
|
||||
var params = [this.contextName(0)];
|
||||
this.setupParams(name, 0, params);
|
||||
|
||||
var blockName = this.popStack();
|
||||
params.splice(1, 0, blockName);
|
||||
|
||||
this.push('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.aliases.blockHelperMissing = 'helpers.blockHelperMissing';
|
||||
|
||||
// We're being a bit cheeky and reusing the options value from the prior exec
|
||||
var params = [this.contextName(0)];
|
||||
this.setupParams('', 0, params, true);
|
||||
|
||||
this.flushInline();
|
||||
|
||||
var current = this.topStack();
|
||||
params.splice(1, 0, current);
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
this.pendingContent = 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.pushSource('if (' + local + ' != null) { ' + 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.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) {
|
||||
this.lastContext = depth;
|
||||
},
|
||||
|
||||
// [pushContext]
|
||||
//
|
||||
// On stack, before: ...
|
||||
// On stack, after: currentContext, ...
|
||||
//
|
||||
// Pushes the value of the current context onto the stack.
|
||||
pushContext: function() {
|
||||
this.pushStackLiteral(this.contextName(this.lastContext));
|
||||
},
|
||||
|
||||
// [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(parts, falsy, scoped) {
|
||||
/*jshint -W083 */
|
||||
var i = 0,
|
||||
len = parts.length;
|
||||
|
||||
if (!scoped && this.options.compat && !this.lastContext) {
|
||||
// The depthed query is expected to handle the undefined logic for the root level that
|
||||
// is implemented below, so we evaluate that directly in compat mode
|
||||
this.push(this.depthedLookup(parts[i++]));
|
||||
} else {
|
||||
this.pushContext();
|
||||
}
|
||||
|
||||
for (; i < len; i++) {
|
||||
this.replaceStack(function(current) {
|
||||
var lookup = this.nameLookup(current, parts[i], 'context');
|
||||
// We want to ensure that zero and false are handled properly if the context (falsy flag)
|
||||
// needs to have the special handling for these values.
|
||||
if (!falsy) {
|
||||
return ' != null ? ' + lookup + ' : ' + current;
|
||||
} else {
|
||||
// Otherwise we can use generic falsy handling
|
||||
return ' && ' + lookup;
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
// [lookupData]
|
||||
//
|
||||
// On stack, before: ...
|
||||
// On stack, after: data, ...
|
||||
//
|
||||
// Push the data lookup operator
|
||||
lookupData: function(depth, parts) {
|
||||
/*jshint -W083 */
|
||||
if (!depth) {
|
||||
this.pushStackLiteral('data');
|
||||
} else {
|
||||
this.pushStackLiteral('this.data(data, ' + depth + ')');
|
||||
}
|
||||
|
||||
var len = parts.length;
|
||||
for (var i = 0; i < len; i++) {
|
||||
this.replaceStack(function(current) {
|
||||
return ' && ' + this.nameLookup(current, parts[i], 'data');
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
// [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.aliases.lambda = 'this.lambda';
|
||||
|
||||
this.push('lambda(' + this.popStack() + ', ' + this.contextName(0) + ')');
|
||||
},
|
||||
|
||||
// [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.pushContext();
|
||||
this.pushString(type);
|
||||
|
||||
// If it's a subexpression, the string result
|
||||
// will be pushed after this opcode.
|
||||
if (type !== 'sexpr') {
|
||||
if (typeof string === 'string') {
|
||||
this.pushString(string);
|
||||
} else {
|
||||
this.pushStackLiteral(string);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
emptyHash: function() {
|
||||
this.pushStackLiteral('{}');
|
||||
|
||||
if (this.trackIds) {
|
||||
this.push('{}'); // hashIds
|
||||
}
|
||||
if (this.stringParams) {
|
||||
this.push('{}'); // hashContexts
|
||||
this.push('{}'); // hashTypes
|
||||
}
|
||||
},
|
||||
pushHash: function() {
|
||||
if (this.hash) {
|
||||
this.hashes.push(this.hash);
|
||||
}
|
||||
this.hash = {values: [], types: [], contexts: [], ids: []};
|
||||
},
|
||||
popHash: function() {
|
||||
var hash = this.hash;
|
||||
this.hash = this.hashes.pop();
|
||||
|
||||
if (this.trackIds) {
|
||||
this.push('{' + hash.ids.join(',') + '}');
|
||||
}
|
||||
if (this.stringParams) {
|
||||
this.push('{' + hash.contexts.join(',') + '}');
|
||||
this.push('{' + 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, isSimple) {
|
||||
this.aliases.helperMissing = 'helpers.helperMissing';
|
||||
|
||||
var nonHelper = this.popStack();
|
||||
var helper = this.setupHelper(paramSize, name);
|
||||
|
||||
var lookup = (isSimple ? helper.name + ' || ' : '') + nonHelper + ' || helperMissing';
|
||||
this.push('((' + lookup + ').call(' + helper.callParams + '))');
|
||||
},
|
||||
|
||||
// [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.aliases.functionType = '"function"';
|
||||
this.aliases.helperMissing = 'helpers.helperMissing';
|
||||
this.useRegister('helper');
|
||||
|
||||
var nonHelper = this.popStack();
|
||||
|
||||
this.emptyHash();
|
||||
var helper = this.setupHelper(0, name, helperCall);
|
||||
|
||||
var helperName = this.lastHelper = this.nameLookup('helpers', name, 'helper');
|
||||
|
||||
this.push(
|
||||
'((helper = (helper = ' + helperName + ' || ' + nonHelper + ') != null ? helper : helperMissing'
|
||||
+ (helper.paramsInit ? '),(' + helper.paramsInit : '') + '),'
|
||||
+ '(typeof helper === functionType ? helper.call(' + helper.callParams + ') : helper))');
|
||||
},
|
||||
|
||||
// [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, indent) {
|
||||
var params = [this.nameLookup('partials', name, 'partial'), "'" + indent + "'", "'" + name + "'", this.popStack(), this.popStack(), "helpers", "partials"];
|
||||
|
||||
if (this.options.data) {
|
||||
params.push("data");
|
||||
} else if (this.options.compat) {
|
||||
params.push('undefined');
|
||||
}
|
||||
if (this.options.compat) {
|
||||
params.push('depths');
|
||||
}
|
||||
|
||||
this.push("this.invokePartial(" + params.join(", ") + ")");
|
||||
},
|
||||
|
||||
// [assignToHash]
|
||||
//
|
||||
// On stack, before: value, ..., hash, ...
|
||||
// On stack, after: ..., hash, ...
|
||||
//
|
||||
// Pops a value off the stack and assigns it to the current hash
|
||||
assignToHash: function(key) {
|
||||
var value = this.popStack(),
|
||||
context,
|
||||
type,
|
||||
id;
|
||||
|
||||
if (this.trackIds) {
|
||||
id = this.popStack();
|
||||
}
|
||||
if (this.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);
|
||||
}
|
||||
if (id) {
|
||||
hash.ids.push("'" + key + "': " + id);
|
||||
}
|
||||
hash.values.push("'" + key + "': (" + value + ")");
|
||||
},
|
||||
|
||||
pushId: function(type, name) {
|
||||
if (type === 'ID' || type === 'DATA') {
|
||||
this.pushString(name);
|
||||
} else if (type === 'sexpr') {
|
||||
this.pushStackLiteral('true');
|
||||
} else {
|
||||
this.pushStackLiteral('null');
|
||||
}
|
||||
},
|
||||
|
||||
// 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.precompile);
|
||||
this.context.environments[index] = child;
|
||||
|
||||
this.useDepths = this.useDepths || compiler.useDepths;
|
||||
} 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) {
|
||||
var child = this.environment.children[guid],
|
||||
depths = child.depths.list,
|
||||
useDepths = this.useDepths,
|
||||
depth;
|
||||
|
||||
var programParams = [child.index, 'data'];
|
||||
|
||||
if (useDepths) {
|
||||
programParams.push('depths');
|
||||
}
|
||||
|
||||
return 'this.program(' + programParams.join(', ') + ')';
|
||||
},
|
||||
|
||||
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();
|
||||
this.pushSource(stack + " = " + item + ";");
|
||||
this.compileStack.push(stack);
|
||||
return stack;
|
||||
},
|
||||
|
||||
replaceStack: function(callback) {
|
||||
var prefix = '',
|
||||
inline = this.isInline(),
|
||||
stack,
|
||||
createdStack,
|
||||
usedLiteral;
|
||||
|
||||
/* istanbul ignore next */
|
||||
if (!this.isInline()) {
|
||||
throw new Exception('replaceStack on non-inline');
|
||||
}
|
||||
|
||||
// We want to merge the inline statement into the replacement statement via ','
|
||||
var top = this.popStack(true);
|
||||
|
||||
if (top instanceof Literal) {
|
||||
// Literals do not need to be inlined
|
||||
prefix = stack = top.value;
|
||||
usedLiteral = true;
|
||||
} else {
|
||||
// Get or create the current stack name for use by the inline
|
||||
createdStack = !this.stackSlot;
|
||||
var name = !createdStack ? this.topStackName() : this.incrStack();
|
||||
|
||||
prefix = '(' + this.push(name) + ' = ' + top + ')';
|
||||
stack = this.topStack();
|
||||
}
|
||||
|
||||
var item = callback.call(this, stack);
|
||||
|
||||
if (!usedLiteral) {
|
||||
this.popStack();
|
||||
}
|
||||
if (createdStack) {
|
||||
this.stackSlot--;
|
||||
}
|
||||
this.push('(' + prefix + item + ')');
|
||||
},
|
||||
|
||||
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) {
|
||||
/* istanbul ignore next */
|
||||
if (!this.stackSlot) {
|
||||
throw new Exception('Invalid stack pop');
|
||||
}
|
||||
this.stackSlot--;
|
||||
}
|
||||
return item;
|
||||
}
|
||||
},
|
||||
|
||||
topStack: function() {
|
||||
var stack = (this.isInline() ? this.inlineStack : this.compileStack),
|
||||
item = stack[stack.length - 1];
|
||||
|
||||
if (item instanceof Literal) {
|
||||
return item.value;
|
||||
} else {
|
||||
return item;
|
||||
}
|
||||
},
|
||||
|
||||
contextName: function(context) {
|
||||
if (this.useDepths && context) {
|
||||
return 'depths[' + context + ']';
|
||||
} else {
|
||||
return 'depth' + context;
|
||||
}
|
||||
},
|
||||
|
||||
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') + '"';
|
||||
},
|
||||
|
||||
objectLiteral: function(obj) {
|
||||
var pairs = [];
|
||||
|
||||
for (var key in obj) {
|
||||
if (obj.hasOwnProperty(key)) {
|
||||
pairs.push(this.quotedString(key) + ':' + obj[key]);
|
||||
}
|
||||
}
|
||||
|
||||
return '{' + pairs.join(',') + '}';
|
||||
},
|
||||
|
||||
setupHelper: function(paramSize, name, blockHelper) {
|
||||
var params = [],
|
||||
paramsInit = this.setupParams(name, paramSize, params, blockHelper);
|
||||
var foundHelper = this.nameLookup('helpers', name, 'helper');
|
||||
|
||||
return {
|
||||
params: params,
|
||||
paramsInit: paramsInit,
|
||||
name: foundHelper,
|
||||
callParams: [this.contextName(0)].concat(params).join(", ")
|
||||
};
|
||||
},
|
||||
|
||||
setupOptions: function(helper, paramSize, params) {
|
||||
var options = {}, contexts = [], types = [], ids = [], param, inverse, program;
|
||||
|
||||
options.name = this.quotedString(helper);
|
||||
options.hash = this.popStack();
|
||||
|
||||
if (this.trackIds) {
|
||||
options.hashIds = this.popStack();
|
||||
}
|
||||
if (this.stringParams) {
|
||||
options.hashTypes = this.popStack();
|
||||
options.hashContexts = 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) {
|
||||
program = 'this.noop';
|
||||
}
|
||||
|
||||
if (!inverse) {
|
||||
inverse = 'this.noop';
|
||||
}
|
||||
|
||||
options.fn = program;
|
||||
options.inverse = inverse;
|
||||
}
|
||||
|
||||
// The parameters go on to the stack in order (making sure that they are evaluated in order)
|
||||
// so we need to pop them off the stack in reverse order
|
||||
var i = paramSize;
|
||||
while (i--) {
|
||||
param = this.popStack();
|
||||
params[i] = param;
|
||||
|
||||
if (this.trackIds) {
|
||||
ids[i] = this.popStack();
|
||||
}
|
||||
if (this.stringParams) {
|
||||
types[i] = this.popStack();
|
||||
contexts[i] = this.popStack();
|
||||
}
|
||||
}
|
||||
|
||||
if (this.trackIds) {
|
||||
options.ids = "[" + ids.join(",") + "]";
|
||||
}
|
||||
if (this.stringParams) {
|
||||
options.types = "[" + types.join(",") + "]";
|
||||
options.contexts = "[" + contexts.join(",") + "]";
|
||||
}
|
||||
|
||||
if (this.options.data) {
|
||||
options.data = "data";
|
||||
}
|
||||
|
||||
return options;
|
||||
},
|
||||
|
||||
// the params and contexts arguments are passed in arrays
|
||||
// to fill in
|
||||
setupParams: function(helperName, paramSize, params, useRegister) {
|
||||
var options = this.objectLiteral(this.setupOptions(helperName, paramSize, params));
|
||||
|
||||
if (useRegister) {
|
||||
this.useRegister('options');
|
||||
params.push('options');
|
||||
return 'options=' + options;
|
||||
} else {
|
||||
params.push(options);
|
||||
return '';
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
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) {
|
||||
return !JavaScriptCompiler.RESERVED_WORDS[name] && /^[a-zA-Z_$][0-9a-zA-Z_$]*$/.test(name);
|
||||
};
|
||||
|
||||
export default JavaScriptCompiler;
|
||||
@@ -1,31 +1,29 @@
|
||||
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) {
|
||||
var out = "";
|
||||
|
||||
for(var i=0,l=this.padding; i<l; i++) {
|
||||
out = out + " ";
|
||||
}
|
||||
|
||||
out = out + string;
|
||||
|
||||
if(newline !== false) { out = out + "\n"; }
|
||||
out = out + string + "\n";
|
||||
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 +35,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,8 +60,8 @@ Handlebars.PrintVisitor.prototype.block = function(block) {
|
||||
return out;
|
||||
};
|
||||
|
||||
Handlebars.PrintVisitor.prototype.mustache = function(mustache) {
|
||||
var params = mustache.params, paramStrings = [], hash;
|
||||
PrintVisitor.prototype.sexpr = function(sexpr) {
|
||||
var params = sexpr.params, paramStrings = [], hash;
|
||||
|
||||
for(var i=0, l=params.length; i<l; i++) {
|
||||
paramStrings.push(this.accept(params[i]));
|
||||
@@ -71,18 +69,27 @@ Handlebars.PrintVisitor.prototype.mustache = function(mustache) {
|
||||
|
||||
params = "[" + paramStrings.join(", ") + "]";
|
||||
|
||||
hash = mustache.hash ? " " + this.accept(mustache.hash) : "";
|
||||
hash = sexpr.hash ? " " + this.accept(sexpr.hash) : "";
|
||||
|
||||
return this.pad("{{ " + this.accept(mustache.id) + " " + params + hash + " }}");
|
||||
return this.accept(sexpr.id) + " " + params + hash;
|
||||
};
|
||||
|
||||
Handlebars.PrintVisitor.prototype.partial = function(partial) {
|
||||
PrintVisitor.prototype.mustache = function(mustache) {
|
||||
return this.pad("{{ " + this.accept(mustache.sexpr) + " }}");
|
||||
};
|
||||
|
||||
PrintVisitor.prototype.partial = function(partial) {
|
||||
var content = this.accept(partial.partialName);
|
||||
if(partial.context) { content = content + " " + this.accept(partial.context); }
|
||||
if(partial.context) {
|
||||
content += " " + this.accept(partial.context);
|
||||
}
|
||||
if (partial.hash) {
|
||||
content += " " + this.accept(partial.hash);
|
||||
}
|
||||
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 +102,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) {
|
||||
return "INTEGER{" + integer.integer + "}";
|
||||
PrintVisitor.prototype.NUMBER = function(number) {
|
||||
return "NUMBER{" + number.number + "}";
|
||||
};
|
||||
|
||||
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 +123,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,27 @@
|
||||
|
||||
var errorProps = ['description', 'fileName', 'lineNumber', 'message', 'name', 'number', 'stack'];
|
||||
|
||||
function Exception(message, node) {
|
||||
var line;
|
||||
if (node && node.firstLine) {
|
||||
line = node.firstLine;
|
||||
|
||||
message += ' - ' + line + ':' + node.firstColumn;
|
||||
}
|
||||
|
||||
var tmp = Error.prototype.constructor.call(this, message);
|
||||
|
||||
// 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]];
|
||||
}
|
||||
|
||||
if (line) {
|
||||
this.lineNumber = line;
|
||||
this.column = node.firstColumn;
|
||||
}
|
||||
}
|
||||
|
||||
Exception.prototype = new Error();
|
||||
|
||||
export default Exception;
|
||||
+181
-97
@@ -1,106 +1,190 @@
|
||||
exports.attach = function(Handlebars) {
|
||||
module Utils from "./utils";
|
||||
import Exception from "./exception";
|
||||
import { COMPILER_REVISION, REVISION_CHANGES, createFrame } 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 Exception("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 Exception("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) {
|
||||
/* istanbul ignore next */
|
||||
if (!env) {
|
||||
throw new Exception("No environment passed to template");
|
||||
}
|
||||
if (!templateSpec || !templateSpec.main) {
|
||||
throw new Exception('Unknown template object: ' + typeof templateSpec);
|
||||
}
|
||||
|
||||
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.
|
||||
env.VM.checkRevision(templateSpec.compiler);
|
||||
|
||||
};
|
||||
var invokePartialWrapper = function(partial, indent, name, context, hash, helpers, partials, data, depths) {
|
||||
if (hash) {
|
||||
context = Utils.extend({}, context, hash);
|
||||
}
|
||||
|
||||
var result = env.VM.invokePartial.call(this, partial, name, context, helpers, partials, data, depths);
|
||||
|
||||
if (result == null && env.compile) {
|
||||
var options = { helpers: helpers, partials: partials, data: data, depths: depths };
|
||||
partials[name] = env.compile(partial, { data: data !== undefined, compat: templateSpec.compat }, env);
|
||||
result = partials[name](context, options);
|
||||
}
|
||||
if (result != null) {
|
||||
if (indent) {
|
||||
var lines = result.split('\n');
|
||||
for (var i = 0, l = lines.length; i < l; i++) {
|
||||
if (!lines[i] && i + 1 === l) {
|
||||
break;
|
||||
}
|
||||
|
||||
lines[i] = indent + lines[i];
|
||||
}
|
||||
result = lines.join('\n');
|
||||
}
|
||||
return result;
|
||||
} else {
|
||||
throw new Exception("The partial " + name + " could not be compiled when running in runtime-only mode");
|
||||
}
|
||||
};
|
||||
|
||||
// Just add water
|
||||
var container = {
|
||||
lookup: function(depths, name) {
|
||||
var len = depths.length;
|
||||
for (var i = 0; i < len; i++) {
|
||||
if (depths[i] && depths[i][name] != null) {
|
||||
return depths[i][name];
|
||||
}
|
||||
}
|
||||
},
|
||||
lambda: function(current, context) {
|
||||
return typeof current === 'function' ? current.call(context) : current;
|
||||
},
|
||||
|
||||
escapeExpression: Utils.escapeExpression,
|
||||
invokePartial: invokePartialWrapper,
|
||||
|
||||
fn: function(i) {
|
||||
return templateSpec[i];
|
||||
},
|
||||
|
||||
programs: [],
|
||||
program: function(i, data, depths) {
|
||||
var programWrapper = this.programs[i],
|
||||
fn = this.fn(i);
|
||||
if (data || depths) {
|
||||
programWrapper = program(this, i, fn, data, depths);
|
||||
} else if (!programWrapper) {
|
||||
programWrapper = this.programs[i] = program(this, i, fn);
|
||||
}
|
||||
return programWrapper;
|
||||
},
|
||||
|
||||
data: function(data, depth) {
|
||||
while (data && depth--) {
|
||||
data = data._parent;
|
||||
}
|
||||
return data;
|
||||
},
|
||||
merge: function(param, common) {
|
||||
var ret = param || common;
|
||||
|
||||
if (param && common && (param !== common)) {
|
||||
ret = Utils.extend({}, common, param);
|
||||
}
|
||||
|
||||
return ret;
|
||||
},
|
||||
|
||||
noop: env.VM.noop,
|
||||
compilerInfo: templateSpec.compiler
|
||||
};
|
||||
|
||||
var ret = function(context, options) {
|
||||
options = options || {};
|
||||
var data = options.data;
|
||||
|
||||
ret._setup(options);
|
||||
if (!options.partial && templateSpec.useData) {
|
||||
data = initData(context, data);
|
||||
}
|
||||
var depths;
|
||||
if (templateSpec.useDepths) {
|
||||
depths = options.depths ? [context].concat(options.depths) : [context];
|
||||
}
|
||||
|
||||
return templateSpec.main.call(container, context, container.helpers, container.partials, data, depths);
|
||||
};
|
||||
ret.isTop = true;
|
||||
|
||||
ret._setup = function(options) {
|
||||
if (!options.partial) {
|
||||
container.helpers = container.merge(options.helpers, env.helpers);
|
||||
|
||||
if (templateSpec.usePartial) {
|
||||
container.partials = container.merge(options.partials, env.partials);
|
||||
}
|
||||
} else {
|
||||
container.helpers = options.helpers;
|
||||
container.partials = options.partials;
|
||||
}
|
||||
};
|
||||
|
||||
ret._child = function(i, data, depths) {
|
||||
if (templateSpec.useDepths && !depths) {
|
||||
throw new Exception('must pass parent depths');
|
||||
}
|
||||
|
||||
return program(container, i, templateSpec[i], data, depths);
|
||||
};
|
||||
return ret;
|
||||
}
|
||||
|
||||
export function program(container, i, fn, data, depths) {
|
||||
var prog = function(context, options) {
|
||||
options = options || {};
|
||||
|
||||
return fn.call(container, context, container.helpers, container.partials, options.data || data, depths && [context].concat(depths));
|
||||
};
|
||||
prog.program = i;
|
||||
prog.depth = depths ? depths.length : 0;
|
||||
return prog;
|
||||
}
|
||||
|
||||
export function invokePartial(partial, name, context, helpers, partials, data, depths) {
|
||||
var options = { partial: true, helpers: helpers, partials: partials, data: data, depths: depths };
|
||||
|
||||
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 ""; }
|
||||
|
||||
function initData(context, data) {
|
||||
if (!data || !('root' in data)) {
|
||||
data = data ? createFrame(data) : {};
|
||||
data.root = context;
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
+65
-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,73 @@ var escape = {
|
||||
var badChars = /[&<>"'`]/g;
|
||||
var possible = /[&<>"'`]/;
|
||||
|
||||
var escapeChar = function(chr) {
|
||||
return escape[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];
|
||||
export function extend(obj /* , ...source */) {
|
||||
for (var i = 1; i < arguments.length; i++) {
|
||||
for (var key in arguments[i]) {
|
||||
if (Object.prototype.hasOwnProperty.call(arguments[i], key)) {
|
||||
obj[key] = arguments[i][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;
|
||||
}
|
||||
}
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
||||
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
|
||||
/* istanbul ignore next */
|
||||
if (isFunction(/x/)) {
|
||||
isFunction = function(value) {
|
||||
return typeof value === 'function' && toString.call(value) === '[object Function]';
|
||||
};
|
||||
}
|
||||
export var isFunction;
|
||||
|
||||
/* istanbul ignore next */
|
||||
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 == null) {
|
||||
return "";
|
||||
} else if (!string) {
|
||||
return string + '';
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
|
||||
export function appendContextPath(contextPath, id) {
|
||||
return (contextPath ? contextPath + '.' : '') + id;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
// 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
|
||||
/* istanbul ignore else */
|
||||
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;
|
||||
}
|
||||
@@ -0,0 +1,144 @@
|
||||
|
||||
var fs = require('fs'),
|
||||
Handlebars = require('./index'),
|
||||
basename = require('path').basename,
|
||||
uglify = require('uglify-js');
|
||||
|
||||
module.exports.cli = function(opts) {
|
||||
if (opts.version) {
|
||||
console.log(Handlebars.VERSION);
|
||||
return;
|
||||
}
|
||||
|
||||
var template = [0];
|
||||
if (!opts.templates.length) {
|
||||
throw new Handlebars.Exception('Must define at least one template or directory.');
|
||||
}
|
||||
|
||||
opts.templates.forEach(function(template) {
|
||||
try {
|
||||
fs.statSync(template);
|
||||
} catch (err) {
|
||||
throw new Handlebars.Exception('Unable to open template file "' + template + '"');
|
||||
}
|
||||
});
|
||||
|
||||
if (opts.simple && opts.min) {
|
||||
throw new Handlebars.Exception('Unable to minimze simple output');
|
||||
}
|
||||
if (opts.simple && (opts.templates.length !== 1 || fs.statSync(opts.templates[0]).isDirectory())) {
|
||||
throw new Handlebars.Exception('Unable to output multiple templates in simple mode');
|
||||
}
|
||||
|
||||
// Convert the known list into a hash
|
||||
var known = {};
|
||||
if (opts.known && !Array.isArray(opts.known)) {
|
||||
opts.known = [opts.known];
|
||||
}
|
||||
if (opts.known) {
|
||||
for (var i = 0, len = opts.known.length; i < len; i++) {
|
||||
known[opts.known[i]] = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Build file extension pattern
|
||||
var extension = opts.extension.replace(/[\\^$*+?.():=!|{}\-\[\]]/g, function(arg) { return '\\' + arg; });
|
||||
extension = new RegExp('\\.' + extension + '$');
|
||||
|
||||
var output = [];
|
||||
if (!opts.simple) {
|
||||
if (opts.amd) {
|
||||
output.push('define([\'' + opts.handlebarPath + 'handlebars.runtime\'], function(Handlebars) {\n Handlebars = Handlebars["default"];');
|
||||
} else if (opts.commonjs) {
|
||||
output.push('var Handlebars = require("' + opts.commonjs + '");');
|
||||
} else {
|
||||
output.push('(function() {\n');
|
||||
}
|
||||
output.push(' var template = Handlebars.template, templates = ');
|
||||
output.push(opts.namespace);
|
||||
output.push(' = ');
|
||||
output.push(opts.namespace);
|
||||
output.push(' || {};\n');
|
||||
}
|
||||
function processTemplate(template, root) {
|
||||
var path = template,
|
||||
stat = fs.statSync(path);
|
||||
if (stat.isDirectory()) {
|
||||
fs.readdirSync(template).map(function(file) {
|
||||
var path = template + '/' + file;
|
||||
|
||||
if (extension.test(path) || fs.statSync(path).isDirectory()) {
|
||||
processTemplate(path, root || template);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
var data = fs.readFileSync(path, 'utf8');
|
||||
|
||||
if (opts.bom && data.indexOf('\uFEFF') === 0) {
|
||||
data = data.substring(1);
|
||||
}
|
||||
|
||||
var options = {
|
||||
knownHelpers: known,
|
||||
knownHelpersOnly: opts.o
|
||||
};
|
||||
|
||||
if (opts.data) {
|
||||
options.data = true;
|
||||
}
|
||||
|
||||
// Clean the template name
|
||||
if (!root) {
|
||||
template = basename(template);
|
||||
} else if (template.indexOf(root) === 0) {
|
||||
template = template.substring(root.length+1);
|
||||
}
|
||||
template = template.replace(extension, '');
|
||||
|
||||
if (opts.simple) {
|
||||
output.push(Handlebars.precompile(data, options) + '\n');
|
||||
} else if (opts.partial) {
|
||||
if(opts.amd && (opts.templates.length == 1 && !fs.statSync(opts.templates[0]).isDirectory())) {
|
||||
output.push('return ');
|
||||
}
|
||||
output.push('Handlebars.partials[\'' + template + '\'] = template(' + Handlebars.precompile(data, options) + ');\n');
|
||||
} else {
|
||||
if(opts.amd && (opts.templates.length == 1 && !fs.statSync(opts.templates[0]).isDirectory())) {
|
||||
output.push('return ');
|
||||
}
|
||||
output.push('templates[\'' + template + '\'] = template(' + Handlebars.precompile(data, options) + ');\n');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
opts.templates.forEach(function(template) {
|
||||
processTemplate(template, opts.root);
|
||||
});
|
||||
|
||||
// Output the content
|
||||
if (!opts.simple) {
|
||||
if (opts.amd) {
|
||||
if(opts.templates.length > 1 || (opts.templates.length == 1 && fs.statSync(opts.templates[0]).isDirectory())) {
|
||||
if(opts.partial){
|
||||
output.push('return Handlebars.partials;\n');
|
||||
} else {
|
||||
output.push('return templates;\n');
|
||||
}
|
||||
}
|
||||
output.push('});');
|
||||
} else if (!opts.commonjs) {
|
||||
output.push('})();');
|
||||
}
|
||||
}
|
||||
output = output.join('');
|
||||
|
||||
if (opts.min) {
|
||||
output = uglify.minify(output, {fromString: true}).code;
|
||||
}
|
||||
|
||||
if (opts.output) {
|
||||
fs.writeFileSync(opts.output, output, 'utf8');
|
||||
} else {
|
||||
console.log(output);
|
||||
}
|
||||
};
|
||||
+40
-13
@@ -1,35 +1,62 @@
|
||||
{
|
||||
"name": "handlebars",
|
||||
"description": "Extension of the Mustache logicless template language",
|
||||
"version": "1.0.12",
|
||||
"barename": "handlebars",
|
||||
"version": "2.0.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",
|
||||
"mocha": "*",
|
||||
"mustache": "~0.7.2"
|
||||
"dustjs-linkedin": "~2.0.2",
|
||||
"eco": "~1.1.0-rc-3",
|
||||
"es6-module-packager": "1.x",
|
||||
"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.x",
|
||||
"grunt-contrib-requirejs": "~0.4.1",
|
||||
"grunt-contrib-uglify": "~0.2.2",
|
||||
"grunt-contrib-watch": "~0.5.3",
|
||||
"grunt-saucelabs": "8.x",
|
||||
"istanbul": "^0.3.0",
|
||||
"jison": "~0.3.0",
|
||||
"keen.io": "0.0.3",
|
||||
"mocha": "~1.20.0",
|
||||
"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"
|
||||
}
|
||||
}
|
||||
|
||||
+211
-1
@@ -1,7 +1,211 @@
|
||||
# Release Notes
|
||||
|
||||
## Development
|
||||
[Commits](https://github.com/wycats/handlebars.js/compare/v1.0.12...master)
|
||||
|
||||
[Commits](https://github.com/wycats/handlebars.js/compare/v2.0.0...master)
|
||||
|
||||
## v2.0.0 - September 1st, 2014
|
||||
- Update jsfiddle to 2.0.0-beta.1 - 0670f65
|
||||
- Add contrib note regarding handlebarsjs.com docs - 4d17e3c
|
||||
- Play nice with gemspec version numbers - 64d5481
|
||||
|
||||
[Commits](https://github.com/wycats/handlebars.js/compare/v2.0.0-beta.1...v2.0.0)
|
||||
|
||||
## v2.0.0-beta.1 - August 26th, 2014
|
||||
- [#787](https://github.com/wycats/handlebars.js/pull/787) - Remove whitespace surrounding standalone statements ([@kpdecker](https://api.github.com/users/kpdecker))
|
||||
- [#827](https://github.com/wycats/handlebars.js/issues/827) - Render false literal as “false” ([@scoot557](https://api.github.com/users/scoot557))
|
||||
- [#767](https://github.com/wycats/handlebars.js/issues/767) - Subexpressions bug with hash and context ([@evensoul](https://api.github.com/users/evensoul))
|
||||
- Changes to 0/undefined handling
|
||||
- [#731](https://github.com/wycats/handlebars.js/pull/731) - Strange behavior for {{#foo}} {{bar}} {{/foo}} when foo is 0 ([@kpdecker](https://api.github.com/users/kpdecker))
|
||||
- [#820](https://github.com/wycats/handlebars.js/issues/820) - strange behavior for {{foo.bar}} when foo is 0 or null or false ([@zordius](https://api.github.com/users/zordius))
|
||||
- [#837](https://github.com/wycats/handlebars.js/issues/837) - Strange input for custom helper ( foo.bar == false when foo is undefined ) ([@zordius](https://api.github.com/users/zordius))
|
||||
- [#819](https://github.com/wycats/handlebars.js/pull/819) - Implement recursive field lookup ([@kpdecker](https://api.github.com/users/kpdecker))
|
||||
- [#764](https://github.com/wycats/handlebars.js/issues/764) - This reference not working for helpers ([@kpdecker](https://api.github.com/users/kpdecker))
|
||||
- [#773](https://github.com/wycats/handlebars.js/issues/773) - Implicit parameters in {{#each}} introduces a peculiarity in helpers calling convention ([@Bertrand](https://api.github.com/users/Bertrand))
|
||||
- [#783](https://github.com/wycats/handlebars.js/issues/783) - helperMissing and consistency for different expression types ([@ErisDS](https://api.github.com/users/ErisDS))
|
||||
- [#795](https://github.com/wycats/handlebars.js/pull/795) - Turn the precompile script into a wrapper around a module. ([@jwietelmann](https://api.github.com/users/jwietelmann))
|
||||
- [#823](https://github.com/wycats/handlebars.js/pull/823) - Support inverse sections on the with helper ([@dan-manges](https://api.github.com/users/dan-manges))
|
||||
- [#834](https://github.com/wycats/handlebars.js/pull/834) - Refactor blocks, programs and inverses ([@mmun](https://api.github.com/users/mmun))
|
||||
- [#852](https://github.com/wycats/handlebars.js/issues/852) - {{foo~}} space control behavior is different from older version ([@zordius](https://api.github.com/users/zordius))
|
||||
- [#835](https://github.com/wycats/handlebars.js/issues/835) - Templates overwritten if file is loaded twice
|
||||
|
||||
- Expose escapeExpression on the root object - 980c38c
|
||||
- Remove nested function eval in blockHelperMissing - 6f22ec1
|
||||
- Fix compiler program de-duping - 9e3f824
|
||||
|
||||
Compatibility notes:
|
||||
- The default build now outputs a generic UMD wrapper. This should be transparent change but may cause issues in some environments.
|
||||
- Runtime compatibility breaks in both directions. Ensure that both compiler and client are upgraded to 2.0.0-beta.1 or higher at the same time.
|
||||
- `programWithDepth` has been removed an instead an array of context values is passed to fields needing depth lookups.
|
||||
- `false` values are now printed to output rather than silently dropped
|
||||
- Lines containing only block statements and whitespace are now removed. This matches the Mustache spec but may cause issues with code that expects whitespace to exist but would not otherwise.
|
||||
- Partials that are standalone will now indent their rendered content
|
||||
- `AST.ProgramNode`'s signature has changed.
|
||||
- Numerious methods/features removed from psuedo-API classes
|
||||
- `JavaScriptCompiler.register`
|
||||
- `JavaScriptCompiler.replaceStack` no longer supports non-inline replace
|
||||
- `Compiler.disassemble`
|
||||
- `DECLARE` opcode
|
||||
- `strip` opcode
|
||||
- `lookup` opcode
|
||||
- Content nodes may have their `string` values mutated over time. `original` field provides the unmodified value.
|
||||
- Removed unused `Handlebars.registerHelper` `inverse` parameter
|
||||
- `each` helper requires iterator parameter
|
||||
|
||||
[Commits](https://github.com/wycats/handlebars.js/compare/v2.0.0-alpha.4...v2.0.0-beta.1)
|
||||
|
||||
## v2.0.0-alpha.4 - May 19th, 2014
|
||||
- Expose setup wrappers for compiled templates - 3638874
|
||||
|
||||
[Commits](https://github.com/wycats/handlebars.js/compare/v2.0.0-alpha.3...v2.0.0-alpha.4)
|
||||
|
||||
## v2.0.0-alpha.3 - May 19th, 2014
|
||||
- [#797](https://github.com/wycats/handlebars.js/pull/797) - Pass full helper ID to helperMissing when options are provided ([@tomdale](https://api.github.com/users/tomdale))
|
||||
- [#793](https://github.com/wycats/handlebars.js/pull/793) - Ensure isHelper is coerced to a boolean ([@mmun](https://api.github.com/users/mmun))
|
||||
- Refactor template init logic - 085e5e1
|
||||
|
||||
[Commits](https://github.com/wycats/handlebars.js/compare/v2.0.0-alpha.2...v2.0.0-alpha.3)
|
||||
|
||||
## v2.0.0-alpha.2 - March 6th, 2014
|
||||
- [#756](https://github.com/wycats/handlebars.js/pull/756) - fix bug in IE<=8 (no Array::map), closes #751 ([@jenseng](https://api.github.com/users/jenseng))
|
||||
- [#749](https://github.com/wycats/handlebars.js/pull/749) - properly handle multiple subexpressions in the same hash, fixes #748 ([@jenseng](https://api.github.com/users/jenseng))
|
||||
- [#743](https://github.com/wycats/handlebars.js/issues/743) - subexpression confusion/problem? ([@waynedpj](https://api.github.com/users/waynedpj))
|
||||
- [#746](https://github.com/wycats/handlebars.js/issues/746) - [CLI] support `handlebars --version` ([@apfelbox](https://api.github.com/users/apfelbox))
|
||||
- [#747](https://github.com/wycats/handlebars.js/pull/747) - updated grunt-saucelabs, failing tests revealed ([@Jonahss](https://api.github.com/users/Jonahss))
|
||||
- Make JSON a requirement for the compiler. - 058c0fb
|
||||
- Temporarily kill the AWS publish CI step - 8347ee2
|
||||
|
||||
Compatibility notes:
|
||||
- A JSON polyfill is required to run the compiler under IE8 and below. It's recommended that the precompiler be used in lieu of running the compiler on these legacy environments.
|
||||
|
||||
[Commits](https://github.com/wycats/handlebars.js/compare/v2.0.0-alpha.1...v2.0.0-alpha.2)
|
||||
|
||||
## v2.0.0-alpha.1 - February 10th, 2014
|
||||
- [#182](https://github.com/wycats/handlebars.js/pull/182) - Allow passing hash parameters to partials ([@kpdecker](https://api.github.com/users/kpdecker))
|
||||
- [#392](https://github.com/wycats/handlebars.js/pull/392) - Access to root context in partials and helpers ([@kpdecker](https://api.github.com/users/kpdecker))
|
||||
- [#472](https://github.com/wycats/handlebars.js/issues/472) - Helpers cannot have decimal parameters ([@kayleg](https://api.github.com/users/kayleg))
|
||||
- [#569](https://github.com/wycats/handlebars.js/pull/569) - Unable to lookup array values using @index ([@kpdecker](https://api.github.com/users/kpdecker))
|
||||
- [#491](https://github.com/wycats/handlebars.js/pull/491) - For nested helpers: get the @ variables of the outer helper from the inner one ([@kpdecker](https://api.github.com/users/kpdecker))
|
||||
- [#669](https://github.com/wycats/handlebars.js/issues/669) - Ability to unregister a helper ([@dbachrach](https://api.github.com/users/dbachrach))
|
||||
- [#730](https://github.com/wycats/handlebars.js/pull/730) - Raw block helpers ([@kpdecker](https://api.github.com/users/kpdecker))
|
||||
- [#634](https://github.com/wycats/handlebars.js/pull/634) - It would be great to have the helper name passed to `blockHelperMissing` ([@kpdecker](https://api.github.com/users/kpdecker))
|
||||
- [#729](https://github.com/wycats/handlebars.js/pull/729) - Convert template spec to object literal ([@kpdecker](https://api.github.com/users/kpdecker))
|
||||
|
||||
- [#658](https://github.com/wycats/handlebars.js/issues/658) - Depthed helpers do not work after an upgrade from 1.0.0 ([@xibxor](https://api.github.com/users/xibxor))
|
||||
- [#671](https://github.com/wycats/handlebars.js/issues/671) - Crashes on no-parameter {{#each}} ([@stepancheg](https://api.github.com/users/stepancheg))
|
||||
- [#689](https://github.com/wycats/handlebars.js/issues/689) - broken template precompilation ([@AAS](https://api.github.com/users/AAS))
|
||||
- [#698](https://github.com/wycats/handlebars.js/pull/698) - Fix parser generation under windows ([@osiris43](https://api.github.com/users/osiris43))
|
||||
- [#699](https://github.com/wycats/handlebars.js/issues/699) - @DATA not compiles to invalid JS in stringParams mode ([@kpdecker](https://api.github.com/users/kpdecker))
|
||||
- [#705](https://github.com/wycats/handlebars.js/issues/705) - 1.3.0 can not be wrapped in an IIFE ([@craigteegarden](https://api.github.com/users/craigteegarden))
|
||||
- [#706](https://github.com/wycats/handlebars.js/pull/706) - README: Use with helper instead of relying on blockHelperMissing ([@scottgonzalez](https://api.github.com/users/scottgonzalez))
|
||||
|
||||
- [#700](https://github.com/wycats/handlebars.js/pull/700) - Remove redundant conditions ([@blakeembrey](https://api.github.com/users/blakeembrey))
|
||||
- [#704](https://github.com/wycats/handlebars.js/pull/704) - JavaScript Compiler Cleanup ([@blakeembrey](https://api.github.com/users/blakeembrey))
|
||||
|
||||
Compatibility notes:
|
||||
- `helperMissing` helper no longer has the indexed name argument. Helper name is now available via `options.name`.
|
||||
- Precompiler output has changed, which breaks compatibility with prior versions of the runtime and precompiled output.
|
||||
- `JavaScriptCompiler.compilerInfo` now returns generic objects rather than javascript source.
|
||||
- AST changes
|
||||
- INTEGER -> NUMBER
|
||||
- Additional PartialNode hash parameter
|
||||
- New RawBlockNode type
|
||||
- Data frames now have a `_parent` field. This is internal but is enumerable for performance/compatability reasons.
|
||||
|
||||
[Commits](https://github.com/wycats/handlebars.js/compare/v1.3.0...v2.0.0-alpha.1)
|
||||
|
||||
## v1.3.0 - January 1st, 2014
|
||||
- [#690](https://github.com/wycats/handlebars.js/pull/690) - Added support for subexpressions ([@machty](https://api.github.com/users/machty))
|
||||
- [#696](https://github.com/wycats/handlebars.js/pull/696) - Fix for reserved keyword "default" ([@nateirwin](https://api.github.com/users/nateirwin))
|
||||
- [#692](https://github.com/wycats/handlebars.js/pull/692) - add line numbers to nodes when parsing ([@fivetanley](https://api.github.com/users/fivetanley))
|
||||
- [#695](https://github.com/wycats/handlebars.js/pull/695) - Pull options out from param setup to allow easier extension ([@blakeembrey](https://api.github.com/users/blakeembrey))
|
||||
- [#694](https://github.com/wycats/handlebars.js/pull/694) - Make the environment reusable ([@blakeembrey](https://api.github.com/users/blakeembrey))
|
||||
- [#636](https://github.com/wycats/handlebars.js/issues/636) - Print line and column of errors ([@sgronblo](https://api.github.com/users/sgronblo))
|
||||
- Use literal for data lookup - c1a93d3
|
||||
- Add stack handling sanity checks - cd885bf
|
||||
- Fix stack id "leak" on replaceStack - ddfe457
|
||||
- Fix incorrect stack pop when replacing literals - f4d337d
|
||||
|
||||
[Commits](https://github.com/wycats/handlebars.js/compare/v1.2.1...v1.3.0)
|
||||
|
||||
## v1.2.1 - December 26th, 2013
|
||||
- [#684](https://github.com/wycats/handlebars.js/pull/684) - Allow any number of trailing characters for valid JavaScript variable ([@blakeembrey](https://api.github.com/users/blakeembrey))
|
||||
- [#686](https://github.com/wycats/handlebars.js/pull/686) - Falsy AMD module names in version 1.2.0 ([@kpdecker](https://api.github.com/users/kpdecker))
|
||||
|
||||
[Commits](https://github.com/wycats/handlebars.js/compare/v1.2.0...v1.2.1)
|
||||
|
||||
## 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 +225,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 +296,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
|
||||
@@ -0,0 +1,89 @@
|
||||
<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="/spec/env/json2.js"></script>
|
||||
<script src="/spec/env/require.js"></script>
|
||||
|
||||
<script src="/spec/env/common.js"></script>
|
||||
<script>
|
||||
requirejs.config({
|
||||
paths: {
|
||||
'handlebars.runtime': '/dist/handlebars.runtime.amd'
|
||||
}
|
||||
});
|
||||
</script>
|
||||
<script>
|
||||
onload = function(){
|
||||
require(['handlebars.runtime'], function(Handlebars) {
|
||||
describe('runtime', function() {
|
||||
it('should load', function() {
|
||||
equal(!!Handlebars['default'].template, true);
|
||||
equal(!!Handlebars['default'].VERSION, true);
|
||||
});
|
||||
});
|
||||
|
||||
mocha.globals(['mochaResults'])
|
||||
// The test harness leaks under FF. We should have decent global leak coverage from other tests
|
||||
if (!navigator.userAgent.match(/Firefox\/([\d.]+)/)) {
|
||||
mocha.checkLeaks();
|
||||
}
|
||||
var runner = mocha.run();
|
||||
|
||||
//Reporting for saucelabs
|
||||
var failedTests = [];
|
||||
runner.on('end', function(){
|
||||
window.mochaResults = runner.stats;
|
||||
window.mochaResults.reports = failedTests;
|
||||
});
|
||||
|
||||
runner.on('fail', logFailure);
|
||||
|
||||
function logFailure(test, err){
|
||||
|
||||
var flattenTitles = function(test){
|
||||
var titles = [];
|
||||
while (test.parent.title){
|
||||
titles.push(test.parent.title);
|
||||
test = test.parent;
|
||||
}
|
||||
return titles.reverse();
|
||||
};
|
||||
|
||||
failedTests.push({
|
||||
name: test.title,
|
||||
result: false,
|
||||
message: err.message,
|
||||
stack: err.stack,
|
||||
titles: flattenTitles(test)
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<div id="mocha"></div>
|
||||
</body>
|
||||
</html>
|
||||
+109
@@ -0,0 +1,109 @@
|
||||
<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="/spec/env/json2.js"></script>
|
||||
<script src="/spec/env/require.js"></script>
|
||||
|
||||
<script src="/spec/env/common.js"></script>
|
||||
<script>
|
||||
requirejs.config({
|
||||
paths: {
|
||||
handlebars: '/dist/handlebars.amd',
|
||||
tests: '/tmp/tests'
|
||||
}
|
||||
});
|
||||
|
||||
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) {
|
||||
try {
|
||||
var ret;
|
||||
eval('ret = ' + templateSpec);
|
||||
return ret;
|
||||
} catch (err) {
|
||||
console.error(templateSpec);
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<script>
|
||||
onload = function(){
|
||||
|
||||
require(['handlebars'], function(Handlebars) {
|
||||
window.Handlebars = Handlebars['default'];
|
||||
|
||||
require(['tests'], function(Handlebars) {
|
||||
mocha.globals(['mochaResults'])
|
||||
// The test harness leaks under FF. We should have decent global leak coverage from other tests
|
||||
if (!navigator.userAgent.match(/Firefox\/([\d.]+)/)) {
|
||||
mocha.checkLeaks();
|
||||
}
|
||||
var runner = mocha.run();
|
||||
|
||||
//Reporting for saucelabs
|
||||
var failedTests = [];
|
||||
runner.on('end', function(){
|
||||
window.mochaResults = runner.stats;
|
||||
window.mochaResults.reports = failedTests;
|
||||
});
|
||||
|
||||
runner.on('fail', logFailure);
|
||||
|
||||
function logFailure(test, err){
|
||||
|
||||
var flattenTitles = function(test){
|
||||
var titles = [];
|
||||
while (test.parent.title){
|
||||
titles.push(test.parent.title);
|
||||
test = test.parent;
|
||||
}
|
||||
return titles.reverse();
|
||||
};
|
||||
|
||||
failedTests.push({
|
||||
name: test.title,
|
||||
result: false,
|
||||
message: err.message,
|
||||
stack: err.stack,
|
||||
titles: flattenTitles(test)
|
||||
});
|
||||
};
|
||||
});
|
||||
});
|
||||
};
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<div id="mocha"></div>
|
||||
</body>
|
||||
</html>
|
||||
+373
@@ -0,0 +1,373 @@
|
||||
/*global Handlebars, handlebarsEnv, shouldThrow */
|
||||
describe('ast', function() {
|
||||
if (!Handlebars.AST) {
|
||||
return;
|
||||
}
|
||||
|
||||
var LOCATION_INFO = {
|
||||
last_line: 0,
|
||||
first_line: 0,
|
||||
first_column: 0,
|
||||
last_column: 0
|
||||
};
|
||||
|
||||
function testLocationInfoStorage(node){
|
||||
var properties = [ 'firstLine', 'lastLine', 'firstColumn', 'lastColumn' ],
|
||||
property,
|
||||
propertiesLen = properties.length,
|
||||
i;
|
||||
|
||||
for (i = 0; i < propertiesLen; i++){
|
||||
property = properties[0];
|
||||
equals(node[property], 0);
|
||||
}
|
||||
}
|
||||
|
||||
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, LOCATION_INFO);
|
||||
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);
|
||||
testLocationInfoStorage(mustache);
|
||||
});
|
||||
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() {
|
||||
handlebarsEnv.parse("\n {{#foo}}{{/bar}}");
|
||||
}, Handlebars.Exception, "foo doesn't match bar - 2:2");
|
||||
});
|
||||
|
||||
it('stores location info', function(){
|
||||
var sexprNode = new handlebarsEnv.AST.SexprNode([{ original: 'foo'}], null);
|
||||
var mustacheNode = new handlebarsEnv.AST.MustacheNode(sexprNode, null, '{{', {});
|
||||
var block = new handlebarsEnv.AST.BlockNode(mustacheNode,
|
||||
{statements: [], strip: {}}, {statements: [], strip: {}},
|
||||
{
|
||||
strip: {},
|
||||
path: {original: 'foo'}
|
||||
},
|
||||
LOCATION_INFO);
|
||||
testLocationInfoStorage(block);
|
||||
});
|
||||
});
|
||||
describe('IdNode', function() {
|
||||
it('should throw on invalid path', function() {
|
||||
shouldThrow(function() {
|
||||
new handlebarsEnv.AST.IdNode([
|
||||
{part: 'foo'},
|
||||
{part: '..'},
|
||||
{part: 'bar'}
|
||||
], {first_line: 1, first_column: 1});
|
||||
}, Handlebars.Exception, "Invalid path: foo.. - 1:1");
|
||||
shouldThrow(function() {
|
||||
new handlebarsEnv.AST.IdNode([
|
||||
{part: 'foo'},
|
||||
{part: '.'},
|
||||
{part: 'bar'}
|
||||
], {first_line: 1, first_column: 1});
|
||||
}, Handlebars.Exception, "Invalid path: foo. - 1:1");
|
||||
shouldThrow(function() {
|
||||
new handlebarsEnv.AST.IdNode([
|
||||
{part: 'foo'},
|
||||
{part: 'this'},
|
||||
{part: 'bar'}
|
||||
], {first_line: 1, first_column: 1});
|
||||
}, Handlebars.Exception, "Invalid path: foothis - 1:1");
|
||||
});
|
||||
|
||||
it('stores location info', function(){
|
||||
var idNode = new handlebarsEnv.AST.IdNode([], LOCATION_INFO);
|
||||
testLocationInfoStorage(idNode);
|
||||
});
|
||||
});
|
||||
|
||||
describe("HashNode", function(){
|
||||
|
||||
it('stores location info', function(){
|
||||
var hash = new handlebarsEnv.AST.HashNode([], LOCATION_INFO);
|
||||
testLocationInfoStorage(hash);
|
||||
});
|
||||
});
|
||||
|
||||
describe("ContentNode", function(){
|
||||
|
||||
it('stores location info', function(){
|
||||
var content = new handlebarsEnv.AST.ContentNode("HI", LOCATION_INFO);
|
||||
testLocationInfoStorage(content);
|
||||
});
|
||||
});
|
||||
|
||||
describe("CommentNode", function(){
|
||||
|
||||
it('stores location info', function(){
|
||||
var comment = new handlebarsEnv.AST.CommentNode("HI", LOCATION_INFO);
|
||||
testLocationInfoStorage(comment);
|
||||
});
|
||||
});
|
||||
|
||||
describe("NumberNode", function(){
|
||||
|
||||
it('stores location info', function(){
|
||||
var integer = new handlebarsEnv.AST.NumberNode("6", LOCATION_INFO);
|
||||
testLocationInfoStorage(integer);
|
||||
});
|
||||
});
|
||||
|
||||
describe("StringNode", function(){
|
||||
|
||||
it('stores location info', function(){
|
||||
var string = new handlebarsEnv.AST.StringNode("6", LOCATION_INFO);
|
||||
testLocationInfoStorage(string);
|
||||
});
|
||||
});
|
||||
|
||||
describe("BooleanNode", function(){
|
||||
|
||||
it('stores location info', function(){
|
||||
var bool = new handlebarsEnv.AST.BooleanNode("true", LOCATION_INFO);
|
||||
testLocationInfoStorage(bool);
|
||||
});
|
||||
});
|
||||
|
||||
describe("DataNode", function(){
|
||||
|
||||
it('stores location info', function(){
|
||||
var data = new handlebarsEnv.AST.DataNode("YES", LOCATION_INFO);
|
||||
testLocationInfoStorage(data);
|
||||
});
|
||||
});
|
||||
|
||||
describe("PartialNameNode", function(){
|
||||
|
||||
it('stores location info', function(){
|
||||
var pnn = new handlebarsEnv.AST.PartialNameNode({original: "YES"}, LOCATION_INFO);
|
||||
testLocationInfoStorage(pnn);
|
||||
});
|
||||
});
|
||||
|
||||
describe("PartialNode", function(){
|
||||
|
||||
it('stores location info', function(){
|
||||
var pn = new handlebarsEnv.AST.PartialNode("so_partial", {}, {}, {}, LOCATION_INFO);
|
||||
testLocationInfoStorage(pn);
|
||||
});
|
||||
});
|
||||
|
||||
describe('ProgramNode', function(){
|
||||
it('storing location info', function(){
|
||||
var pn = new handlebarsEnv.AST.ProgramNode([], {}, LOCATION_INFO);
|
||||
testLocationInfoStorage(pn);
|
||||
});
|
||||
});
|
||||
|
||||
describe("Line Numbers", function(){
|
||||
var ast, statements;
|
||||
|
||||
function testColumns(node, firstLine, lastLine, firstColumn, lastColumn){
|
||||
equals(node.firstLine, firstLine);
|
||||
equals(node.lastLine, lastLine);
|
||||
equals(node.firstColumn, firstColumn);
|
||||
equals(node.lastColumn, lastColumn);
|
||||
}
|
||||
|
||||
ast = Handlebars.parse("line 1 {{line1Token}}\n line 2 {{line2token}}\n line 3 {{#blockHelperOnLine3}}\nline 4{{line4token}}\n" +
|
||||
"line5{{else}}\n{{line6Token}}\n{{/blockHelperOnLine3}}");
|
||||
statements = ast.statements;
|
||||
|
||||
it('gets ContentNode line numbers', function(){
|
||||
var contentNode = statements[0];
|
||||
testColumns(contentNode, 1, 1, 0, 7);
|
||||
});
|
||||
|
||||
it('gets MustacheNode line numbers', function(){
|
||||
var mustacheNode = statements[1];
|
||||
testColumns(mustacheNode, 1, 1, 7, 21);
|
||||
});
|
||||
|
||||
it('gets line numbers correct when newlines appear', function(){
|
||||
testColumns(statements[2], 1, 2, 21, 8);
|
||||
});
|
||||
|
||||
it('gets MustacheNode line numbers correct across newlines', function(){
|
||||
var secondMustacheNode = statements[3];
|
||||
testColumns(secondMustacheNode, 2, 2, 8, 22);
|
||||
});
|
||||
|
||||
it('gets the block helper information correct', function(){
|
||||
var blockHelperNode = statements[5];
|
||||
testColumns(blockHelperNode, 3, 7, 8, 23);
|
||||
});
|
||||
|
||||
it('correctly records the line numbers the program of a block helper', function(){
|
||||
var blockHelperNode = statements[5],
|
||||
program = blockHelperNode.program;
|
||||
|
||||
testColumns(program, 3, 5, 8, 5);
|
||||
});
|
||||
|
||||
it('correctly records the line numbers of an inverse of a block helper', function(){
|
||||
var blockHelperNode = statements[5],
|
||||
inverse = blockHelperNode.inverse;
|
||||
|
||||
testColumns(inverse, 5, 7, 5, 0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('standalone flags', function(){
|
||||
describe('mustache', function() {
|
||||
it('does not mark mustaches as standalone', function() {
|
||||
var ast = Handlebars.parse(' {{comment}} ');
|
||||
equals(!!ast.statements[0].string, true);
|
||||
equals(!!ast.statements[2].string, true);
|
||||
});
|
||||
});
|
||||
describe('blocks', function() {
|
||||
it('marks block mustaches as standalone', function() {
|
||||
var ast = Handlebars.parse(' {{# comment}} \nfoo\n {{else}} \n bar \n {{/comment}} '),
|
||||
block = ast.statements[1];
|
||||
|
||||
equals(ast.statements[0].string, '');
|
||||
|
||||
equals(block.program.statements[0].string, 'foo\n');
|
||||
equals(block.inverse.statements[0].string, ' bar \n');
|
||||
|
||||
equals(ast.statements[2].string, '');
|
||||
});
|
||||
it('marks initial block mustaches as standalone', function() {
|
||||
var ast = Handlebars.parse('{{# comment}} \nfoo\n {{/comment}}'),
|
||||
block = ast.statements[0];
|
||||
|
||||
equals(block.program.statements[0].string, 'foo\n');
|
||||
});
|
||||
it('marks mustaches with children as standalone', function() {
|
||||
var ast = Handlebars.parse('{{# comment}} \n{{foo}}\n {{/comment}}'),
|
||||
block = ast.statements[0];
|
||||
|
||||
equals(block.program.statements[0].string, '');
|
||||
equals(block.program.statements[1].id.original, 'foo');
|
||||
equals(block.program.statements[2].string, '\n');
|
||||
});
|
||||
it('marks nested block mustaches as standalone', function() {
|
||||
var ast = Handlebars.parse('{{#foo}} \n{{# comment}} \nfoo\n {{else}} \n bar \n {{/comment}} \n{{/foo}}'),
|
||||
statements = ast.statements[0].program.statements,
|
||||
block = statements[1];
|
||||
|
||||
equals(statements[0].string, '');
|
||||
|
||||
equals(block.program.statements[0].string, 'foo\n');
|
||||
equals(block.inverse.statements[0].string, ' bar \n');
|
||||
|
||||
equals(statements[0].string, '');
|
||||
});
|
||||
it('does not mark nested block mustaches as standalone', function() {
|
||||
var ast = Handlebars.parse('{{#foo}} {{# comment}} \nfoo\n {{else}} \n bar \n {{/comment}} {{/foo}}'),
|
||||
statements = ast.statements[0].program.statements,
|
||||
block = statements[1];
|
||||
|
||||
equals(statements[0].omit, undefined);
|
||||
|
||||
equals(block.program.statements[0].string, ' \nfoo\n');
|
||||
equals(block.inverse.statements[0].string, ' bar \n ');
|
||||
|
||||
equals(statements[0].omit, undefined);
|
||||
});
|
||||
it('does not mark nested initial block mustaches as standalone', function() {
|
||||
var ast = Handlebars.parse('{{#foo}}{{# comment}} \nfoo\n {{else}} \n bar \n {{/comment}}{{/foo}}'),
|
||||
statements = ast.statements[0].program.statements,
|
||||
block = statements[0];
|
||||
|
||||
equals(block.program.statements[0].string, ' \nfoo\n');
|
||||
equals(block.inverse.statements[0].string, ' bar \n ');
|
||||
|
||||
equals(statements[0].omit, undefined);
|
||||
});
|
||||
|
||||
it('marks column 0 block mustaches as standalone', function() {
|
||||
var ast = Handlebars.parse('test\n{{# comment}} \nfoo\n {{else}} \n bar \n {{/comment}} '),
|
||||
block = ast.statements[1];
|
||||
|
||||
equals(ast.statements[0].omit, undefined);
|
||||
|
||||
equals(block.program.statements[0].string, 'foo\n');
|
||||
equals(block.inverse.statements[0].string, ' bar \n');
|
||||
|
||||
equals(ast.statements[2].string, '');
|
||||
});
|
||||
});
|
||||
describe('partials', function() {
|
||||
it('marks partial as standalone', function() {
|
||||
var ast = Handlebars.parse('{{> partial }} ');
|
||||
equals(ast.statements[1].string, '');
|
||||
});
|
||||
it('marks indented partial as standalone', function() {
|
||||
var ast = Handlebars.parse(' {{> partial }} ');
|
||||
equals(ast.statements[0].string, '');
|
||||
equals(ast.statements[1].indent, ' ');
|
||||
equals(ast.statements[2].string, '');
|
||||
});
|
||||
it('marks those around content as not standalone', function() {
|
||||
var ast = Handlebars.parse('a{{> partial }}');
|
||||
equals(ast.statements[0].omit, undefined);
|
||||
|
||||
ast = Handlebars.parse('{{> partial }}a');
|
||||
equals(ast.statements[1].omit, undefined);
|
||||
});
|
||||
});
|
||||
describe('comments', function() {
|
||||
it('marks comment as standalone', function() {
|
||||
var ast = Handlebars.parse('{{! comment }} ');
|
||||
equals(ast.statements[1].string, '');
|
||||
});
|
||||
it('marks indented comment as standalone', function() {
|
||||
var ast = Handlebars.parse(' {{! comment }} ');
|
||||
equals(ast.statements[0].string, '');
|
||||
equals(ast.statements[2].string, '');
|
||||
});
|
||||
it('marks those around content as not standalone', function() {
|
||||
var ast = Handlebars.parse('a{{! comment }}');
|
||||
equals(ast.statements[0].omit, undefined);
|
||||
|
||||
ast = Handlebars.parse('{{! comment }}a');
|
||||
equals(ast.statements[1].omit, undefined);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
+225
@@ -0,0 +1,225 @@
|
||||
/*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('false', function() {
|
||||
shouldCompileTo('val1: {{val1}}, val2: {{val2}}', {val1: false, val2: new Boolean(false)}, 'val1: false, val2: false');
|
||||
shouldCompileTo('val: {{.}}', false, 'val: false');
|
||||
shouldCompileTo('val: {{val1/val2}}', {val1: {val2: false}}, 'val: false');
|
||||
|
||||
shouldCompileTo('val1: {{{val1}}}, val2: {{{val2}}}', {val1: false, val2: new Boolean(false)}, 'val1: false, val2: false');
|
||||
shouldCompileTo('val: {{{val1/val2}}}', {val1: {val2: false}}, 'val: false');
|
||||
});
|
||||
|
||||
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("pathed functions with context argument", function() {
|
||||
shouldCompileTo("{{bar.awesome frank}}",
|
||||
{bar: {awesome: function(context) { return context; }},
|
||||
frank: "Frank"},
|
||||
"Frank", "functions are called with context arguments");
|
||||
});
|
||||
it("depthed functions with context argument", function() {
|
||||
shouldCompileTo("{{#with frank}}{{../awesome .}}{{/with}}",
|
||||
{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("depthed block functions with context argument", function() {
|
||||
shouldCompileTo("{{#with value}}{{#../awesome 1}}inner {{.}}{{/../awesome}}{{/with}}",
|
||||
{value: true, 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("pathed block functions without context argument", function() {
|
||||
shouldCompileTo("{{#foo.awesome}}inner{{/foo.awesome}}",
|
||||
{foo: {awesome: function(options) { return this; }}},
|
||||
"inner", "block functions are called with options");
|
||||
});
|
||||
it("depthed block functions without context argument", function() {
|
||||
shouldCompileTo("{{#with value}}{{#../awesome}}inner{{/../awesome}}{{/with}}",
|
||||
{value: true, awesome: function(options) { return 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);
|
||||
});
|
||||
});
|
||||
+133
@@ -0,0 +1,133 @@
|
||||
/*global CompilerContext, shouldCompileTo, shouldThrow */
|
||||
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 without data', function() {
|
||||
var string = '{{#goodbyes}}{{text}}{{/goodbyes}} {{#goodbyes}}{{text}}{{/goodbyes}}';
|
||||
var hash = {goodbyes: [{text: 'goodbye'}, {text: 'Goodbye'}, {text: 'GOODBYE'}], world: 'world'};
|
||||
shouldCompileTo(string, [hash,,,,false], 'goodbyeGoodbyeGOODBYE goodbyeGoodbyeGOODBYE');
|
||||
});
|
||||
|
||||
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('multiple blocks with complex lookup', function() {
|
||||
var string = '{{#goodbyes}}{{../name}}{{../name}}{{/goodbyes}}';
|
||||
var hash = {name: 'Alan', goodbyes: [{text: 'goodbye'}, {text: 'Goodbye'}, {text: 'GOODBYE'}]};
|
||||
|
||||
shouldCompileTo(string, hash, 'AlanAlanAlanAlanAlanAlan');
|
||||
});
|
||||
|
||||
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 {{../sibling}} {{../../omg}}{{/inner}}{{/outer}}";
|
||||
var hash = {omg: "OMG!", outer: [{ sibling: 'sad', inner: [{ text: "goodbye" }] }] };
|
||||
|
||||
shouldCompileTo(string, hash, "Goodbye cruel sad 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");
|
||||
});
|
||||
});
|
||||
|
||||
describe('standalone sections', function() {
|
||||
it('block standalone else sections', function() {
|
||||
shouldCompileTo('{{#people}}\n{{name}}\n{{^}}\n{{none}}\n{{/people}}\n', {none: 'No people'},
|
||||
'No people\n');
|
||||
shouldCompileTo('{{#none}}\n{{.}}\n{{^}}\n{{none}}\n{{/none}}\n', {none: 'No people'},
|
||||
'No people\n');
|
||||
shouldCompileTo('{{#people}}\n{{name}}\n{{^}}\n{{none}}\n{{/people}}\n', {none: 'No people'},
|
||||
'No people\n');
|
||||
});
|
||||
it('should handle nesting', function() {
|
||||
shouldCompileTo('{{#data}}\n{{#if true}}\n{{.}}\n{{/if}}\n{{/data}}\nOK.', {data: [1, 3, 5]}, '1\n3\n5\nOK.');
|
||||
});
|
||||
});
|
||||
|
||||
describe('compat mode', function() {
|
||||
it("block with deep recursive lookup lookup", function() {
|
||||
var string = "{{#outer}}Goodbye {{#inner}}cruel {{omg}}{{/inner}}{{/outer}}";
|
||||
var hash = {omg: "OMG!", outer: [{ inner: [{ text: "goodbye" }] }] };
|
||||
|
||||
shouldCompileTo(string, [hash, undefined, undefined, true], "Goodbye cruel OMG!");
|
||||
});
|
||||
it("block with deep recursive pathed lookup", function() {
|
||||
var string = "{{#outer}}Goodbye {{#inner}}cruel {{omg.yes}}{{/inner}}{{/outer}}";
|
||||
var hash = {omg: {yes: "OMG!"}, outer: [{ inner: [{ yes: 'no', text: "goodbye" }] }] };
|
||||
|
||||
shouldCompileTo(string, [hash, undefined, undefined, true], "Goodbye cruel OMG!");
|
||||
});
|
||||
it("block with missed recursive lookup", function() {
|
||||
var string = "{{#outer}}Goodbye {{#inner}}cruel {{omg.yes}}{{/inner}}{{/outer}}";
|
||||
var hash = {omg: {no: "OMG!"}, outer: [{ inner: [{ yes: 'no', text: "goodbye" }] }] };
|
||||
|
||||
shouldCompileTo(string, [hash, undefined, undefined, true], "Goodbye cruel ");
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,314 @@
|
||||
/*global CompilerContext, shouldCompileTo, shouldThrow, compileWithPartials, handlebarsEnv */
|
||||
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");
|
||||
});
|
||||
it("with with else", function() {
|
||||
var string = "{{#with person}}Person is present{{else}}Person is not present{{/with}}";
|
||||
shouldCompileTo(string, {}, "Person is not present");
|
||||
});
|
||||
});
|
||||
|
||||
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 without data', function() {
|
||||
var string = '{{#each goodbyes}}{{text}}! {{/each}}cruel {{world}}!';
|
||||
var hash = {goodbyes: [{text: 'goodbye'}, {text: 'Goodbye'}, {text: 'GOODBYE'}], world: 'world'};
|
||||
shouldCompileTo(string, [hash,,,,false], 'goodbye! Goodbye! GOODBYE! cruel world!');
|
||||
|
||||
hash = {goodbyes: 'cruel', world: 'world'};
|
||||
shouldCompileTo('{{#each .}}{{.}}{{/each}}', [hash,,,,false], 'cruelworld');
|
||||
});
|
||||
|
||||
it('each without context', function() {
|
||||
var string = '{{#each goodbyes}}{{text}}! {{/each}}cruel {{world}}!';
|
||||
shouldCompileTo(string, [,,,,], 'cruel !');
|
||||
});
|
||||
|
||||
it("each with an object and @key", function() {
|
||||
var string = "{{#each goodbyes}}{{@key}}. {{text}}! {{/each}}cruel {{world}}!";
|
||||
|
||||
function Clazz() {
|
||||
this['<b>#1</b>'] = {text: 'goodbye'};
|
||||
this[2] = {text: 'GOODBYE'};
|
||||
}
|
||||
Clazz.prototype.foo = 'fail';
|
||||
var hash = {goodbyes: new Clazz(), 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("each on implicit context", function() {
|
||||
shouldThrow(function() {
|
||||
var template = CompilerContext.compile("{{#each}}{{text}}! {{/each}}cruel world!");
|
||||
template({});
|
||||
}, handlebarsEnv.Exception, 'Must pass iterator to #each');
|
||||
});
|
||||
});
|
||||
|
||||
describe("#log", function() {
|
||||
if (typeof console === 'undefined') {
|
||||
return;
|
||||
}
|
||||
|
||||
var info,
|
||||
error;
|
||||
beforeEach(function() {
|
||||
info = console.info;
|
||||
error = console.error;
|
||||
});
|
||||
afterEach(function() {
|
||||
console.info = info;
|
||||
console.error = error;
|
||||
});
|
||||
|
||||
it('should call logger at default level', 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'");
|
||||
});
|
||||
it('should call logger at data level', function() {
|
||||
var string = "{{log blah}}";
|
||||
var hash = { blah: "whee" };
|
||||
|
||||
var levelArg, logArg;
|
||||
handlebarsEnv.log = function(level, arg){
|
||||
levelArg = level;
|
||||
logArg = arg;
|
||||
};
|
||||
|
||||
shouldCompileTo(string, [hash,,,,{level: '03'}], "");
|
||||
equals(3, levelArg);
|
||||
equals("whee", logArg);
|
||||
});
|
||||
it('should not output to console', function() {
|
||||
var string = "{{log blah}}";
|
||||
var hash = { blah: "whee" };
|
||||
|
||||
console.info = function() {
|
||||
throw new Error();
|
||||
};
|
||||
|
||||
shouldCompileTo(string, hash, "", "log should not display");
|
||||
});
|
||||
it('should log at data level', function() {
|
||||
var string = "{{log blah}}";
|
||||
var hash = { blah: "whee" };
|
||||
var called;
|
||||
|
||||
console.error = function(log) {
|
||||
equals("whee", log);
|
||||
called = true;
|
||||
};
|
||||
|
||||
shouldCompileTo(string, [hash,,,,{level: '03'}], "");
|
||||
equals(true, called);
|
||||
});
|
||||
it('should handle missing logger', function() {
|
||||
var string = "{{log blah}}";
|
||||
var hash = { blah: "whee" };
|
||||
|
||||
console.error = undefined;
|
||||
|
||||
shouldCompileTo(string, [hash,,,,{level: '03'}], "");
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
describe('#lookup', function() {
|
||||
it('should lookup arbitrary content', function() {
|
||||
var string = '{{#each goodbyes}}{{lookup ../data .}}{{/each}}',
|
||||
hash = {goodbyes: [0, 1], data: ['foo', 'bar']};
|
||||
|
||||
var template = CompilerContext.compile(string);
|
||||
var result = template(hash);
|
||||
|
||||
equal(result, 'foobar');
|
||||
});
|
||||
it('should not fail on undefined value', function() {
|
||||
var string = '{{#each goodbyes}}{{lookup ../bar .}}{{/each}}',
|
||||
hash = {goodbyes: [0, 1], data: ['foo', 'bar']};
|
||||
|
||||
var template = CompilerContext.compile(string);
|
||||
var result = template(hash);
|
||||
|
||||
equal(result, '');
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,70 @@
|
||||
/*global Handlebars, shouldThrow */
|
||||
|
||||
describe('compiler', function() {
|
||||
if (!Handlebars.compile) {
|
||||
return;
|
||||
}
|
||||
|
||||
describe('#equals', function() {
|
||||
function compile(string) {
|
||||
var ast = Handlebars.parse(string);
|
||||
return new Handlebars.Compiler().compile(ast, {});
|
||||
}
|
||||
|
||||
it('should treat as equal', function() {
|
||||
equal(compile('foo').equals(compile('foo')), true);
|
||||
equal(compile('{{foo}}').equals(compile('{{foo}}')), true);
|
||||
equal(compile('{{foo.bar}}').equals(compile('{{foo.bar}}')), true);
|
||||
equal(compile('{{foo.bar baz "foo" true false bat=1}}').equals(compile('{{foo.bar baz "foo" true false bat=1}}')), true);
|
||||
equal(compile('{{foo.bar (baz bat=1)}}').equals(compile('{{foo.bar (baz bat=1)}}')), true);
|
||||
equal(compile('{{#foo}} {{/foo}}').equals(compile('{{#foo}} {{/foo}}')), true);
|
||||
});
|
||||
it('should treat as not equal', function() {
|
||||
equal(compile('foo').equals(compile('bar')), false);
|
||||
equal(compile('{{foo}}').equals(compile('{{bar}}')), false);
|
||||
equal(compile('{{foo.bar}}').equals(compile('{{bar.bar}}')), false);
|
||||
equal(compile('{{foo.bar baz bat=1}}').equals(compile('{{foo.bar bar bat=1}}')), false);
|
||||
equal(compile('{{foo.bar (baz bat=1)}}').equals(compile('{{foo.bar (bar bat=1)}}')), false);
|
||||
equal(compile('{{#foo}} {{/foo}}').equals(compile('{{#bar}} {{/bar}}')), false);
|
||||
equal(compile('{{#foo}} {{/foo}}').equals(compile('{{#foo}} {{foo}}{{/foo}}')), false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('#compile', function() {
|
||||
it('should fail with invalid input', function() {
|
||||
shouldThrow(function() {
|
||||
Handlebars.compile(null);
|
||||
}, Error, 'You must pass a string or Handlebars AST to Handlebars.compile. You passed null');
|
||||
shouldThrow(function() {
|
||||
Handlebars.compile({});
|
||||
}, Error, 'You must pass a string or Handlebars AST to Handlebars.compile. You passed [object Object]');
|
||||
});
|
||||
|
||||
it('can utilize AST instance', 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('')(), '');
|
||||
});
|
||||
});
|
||||
|
||||
describe('#precompile', function() {
|
||||
it('should fail with invalid input', function() {
|
||||
shouldThrow(function() {
|
||||
Handlebars.precompile(null);
|
||||
}, Error, 'You must pass a string or Handlebars AST to Handlebars.precompile. You passed null');
|
||||
shouldThrow(function() {
|
||||
Handlebars.precompile({});
|
||||
}, Error, 'You must pass a string or Handlebars AST to Handlebars.precompile. You passed [object Object]');
|
||||
});
|
||||
|
||||
it('can utilize AST instance', function() {
|
||||
equal(/return "Hello"/.test(Handlebars.precompile(new Handlebars.AST.ProgramNode([ new Handlebars.AST.ContentNode("Hello")]), {})), true);
|
||||
});
|
||||
|
||||
it("can pass through an empty string", function() {
|
||||
equal(/return ""/.test(Handlebars.precompile('')), true);
|
||||
});
|
||||
});
|
||||
});
|
||||
+257
@@ -0,0 +1,257 @@
|
||||
/*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 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");
|
||||
});
|
||||
|
||||
describe('@root', function() {
|
||||
it('the root context can be looked up via @root', function() {
|
||||
var template = CompilerContext.compile('{{@root.foo}}');
|
||||
var result = template({foo: 'hello'}, { data: {} });
|
||||
equals('hello', result);
|
||||
|
||||
result = template({foo: 'hello'}, {});
|
||||
equals('hello', result);
|
||||
});
|
||||
it('passed root values take priority', function() {
|
||||
var template = CompilerContext.compile('{{@root.foo}}');
|
||||
var result = template({}, { data: {root: {foo: 'hello'} } });
|
||||
equals('hello', result);
|
||||
});
|
||||
});
|
||||
|
||||
describe('nesting', function() {
|
||||
it('the root context can be looked up via @root', function() {
|
||||
var template = CompilerContext.compile('{{#helper}}{{#helper}}{{@./depth}} {{@../depth}} {{@../../depth}}{{/helper}}{{/helper}}');
|
||||
var result = template({foo: 'hello'}, {
|
||||
helpers: {
|
||||
helper: function(options) {
|
||||
var frame = Handlebars.createFrame(options.data);
|
||||
frame.depth = options.data.depth + 1;
|
||||
return options.fn(this, {data: frame});
|
||||
}
|
||||
},
|
||||
data: {
|
||||
depth: 0
|
||||
}
|
||||
});
|
||||
equals('2 1 0', result);
|
||||
});
|
||||
});
|
||||
});
|
||||
Vendored
+28
@@ -0,0 +1,28 @@
|
||||
/*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) {
|
||||
try {
|
||||
return eval('(' + templateSpec + ')');
|
||||
} catch (err) {
|
||||
console.error(templateSpec);
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
Vendored
+57
@@ -0,0 +1,57 @@
|
||||
/*global CompilerContext, compileWithPartials, shouldCompileToWithPartials */
|
||||
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("'" + result + "' should === '" + expected + "': " + message);
|
||||
}
|
||||
};
|
||||
|
||||
global.compileWithPartials = function(string, hashOrArray, partials) {
|
||||
var template,
|
||||
ary,
|
||||
options;
|
||||
if(Object.prototype.toString.call(hashOrArray) === "[object Array]") {
|
||||
ary = [];
|
||||
ary.push(hashOrArray[0]);
|
||||
ary.push({ helpers: hashOrArray[1], partials: hashOrArray[2] });
|
||||
options = {compat: hashOrArray[3]};
|
||||
if (hashOrArray[4] != null) {
|
||||
options.data = !!hashOrArray[4];
|
||||
ary[1].data = hashOrArray[4];
|
||||
}
|
||||
} else {
|
||||
ary = [hashOrArray];
|
||||
}
|
||||
|
||||
template = CompilerContext[partials ? 'compileWithPartial' : 'compile'](string, options);
|
||||
return template.apply(this, ary);
|
||||
};
|
||||
|
||||
|
||||
global.equals = global.equal = function(a, b, msg) {
|
||||
if (a !== b) {
|
||||
throw new Error("'" + a + "' should === '" + b + "'" + (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)) {
|
||||
equal(msg, err.message);
|
||||
}
|
||||
}
|
||||
if (failed) {
|
||||
throw new Error('It failed to throw');
|
||||
}
|
||||
};
|
||||
Vendored
+489
@@ -0,0 +1,489 @@
|
||||
/*
|
||||
json2.js
|
||||
2014-02-04
|
||||
|
||||
Public Domain.
|
||||
|
||||
NO WARRANTY EXPRESSED OR IMPLIED. USE AT YOUR OWN RISK.
|
||||
|
||||
See http://www.JSON.org/js.html
|
||||
|
||||
|
||||
This code should be minified before deployment.
|
||||
See http://javascript.crockford.com/jsmin.html
|
||||
|
||||
USE YOUR OWN COPY. IT IS EXTREMELY UNWISE TO LOAD CODE FROM SERVERS YOU DO
|
||||
NOT CONTROL.
|
||||
|
||||
|
||||
This file creates a global JSON object containing two methods: stringify
|
||||
and parse.
|
||||
|
||||
JSON.stringify(value, replacer, space)
|
||||
value any JavaScript value, usually an object or array.
|
||||
|
||||
replacer an optional parameter that determines how object
|
||||
values are stringified for objects. It can be a
|
||||
function or an array of strings.
|
||||
|
||||
space an optional parameter that specifies the indentation
|
||||
of nested structures. If it is omitted, the text will
|
||||
be packed without extra whitespace. If it is a number,
|
||||
it will specify the number of spaces to indent at each
|
||||
level. If it is a string (such as '\t' or ' '),
|
||||
it contains the characters used to indent at each level.
|
||||
|
||||
This method produces a JSON text from a JavaScript value.
|
||||
|
||||
When an object value is found, if the object contains a toJSON
|
||||
method, its toJSON method will be called and the result will be
|
||||
stringified. A toJSON method does not serialize: it returns the
|
||||
value represented by the name/value pair that should be serialized,
|
||||
or undefined if nothing should be serialized. The toJSON method
|
||||
will be passed the key associated with the value, and this will be
|
||||
bound to the value
|
||||
|
||||
For example, this would serialize Dates as ISO strings.
|
||||
|
||||
Date.prototype.toJSON = function (key) {
|
||||
function f(n) {
|
||||
// Format integers to have at least two digits.
|
||||
return n < 10 ? '0' + n : n;
|
||||
}
|
||||
|
||||
return this.getUTCFullYear() + '-' +
|
||||
f(this.getUTCMonth() + 1) + '-' +
|
||||
f(this.getUTCDate()) + 'T' +
|
||||
f(this.getUTCHours()) + ':' +
|
||||
f(this.getUTCMinutes()) + ':' +
|
||||
f(this.getUTCSeconds()) + 'Z';
|
||||
};
|
||||
|
||||
You can provide an optional replacer method. It will be passed the
|
||||
key and value of each member, with this bound to the containing
|
||||
object. The value that is returned from your method will be
|
||||
serialized. If your method returns undefined, then the member will
|
||||
be excluded from the serialization.
|
||||
|
||||
If the replacer parameter is an array of strings, then it will be
|
||||
used to select the members to be serialized. It filters the results
|
||||
such that only members with keys listed in the replacer array are
|
||||
stringified.
|
||||
|
||||
Values that do not have JSON representations, such as undefined or
|
||||
functions, will not be serialized. Such values in objects will be
|
||||
dropped; in arrays they will be replaced with null. You can use
|
||||
a replacer function to replace those with JSON values.
|
||||
JSON.stringify(undefined) returns undefined.
|
||||
|
||||
The optional space parameter produces a stringification of the
|
||||
value that is filled with line breaks and indentation to make it
|
||||
easier to read.
|
||||
|
||||
If the space parameter is a non-empty string, then that string will
|
||||
be used for indentation. If the space parameter is a number, then
|
||||
the indentation will be that many spaces.
|
||||
|
||||
Example:
|
||||
|
||||
text = JSON.stringify(['e', {pluribus: 'unum'}]);
|
||||
// text is '["e",{"pluribus":"unum"}]'
|
||||
|
||||
|
||||
text = JSON.stringify(['e', {pluribus: 'unum'}], null, '\t');
|
||||
// text is '[\n\t"e",\n\t{\n\t\t"pluribus": "unum"\n\t}\n]'
|
||||
|
||||
text = JSON.stringify([new Date()], function (key, value) {
|
||||
return this[key] instanceof Date ?
|
||||
'Date(' + this[key] + ')' : value;
|
||||
});
|
||||
// text is '["Date(---current time---)"]'
|
||||
|
||||
|
||||
JSON.parse(text, reviver)
|
||||
This method parses a JSON text to produce an object or array.
|
||||
It can throw a SyntaxError exception.
|
||||
|
||||
The optional reviver parameter is a function that can filter and
|
||||
transform the results. It receives each of the keys and values,
|
||||
and its return value is used instead of the original value.
|
||||
If it returns what it received, then the structure is not modified.
|
||||
If it returns undefined then the member is deleted.
|
||||
|
||||
Example:
|
||||
|
||||
// Parse the text. Values that look like ISO date strings will
|
||||
// be converted to Date objects.
|
||||
|
||||
myData = JSON.parse(text, function (key, value) {
|
||||
var a;
|
||||
if (typeof value === 'string') {
|
||||
a =
|
||||
/^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2}(?:\.\d*)?)Z$/.exec(value);
|
||||
if (a) {
|
||||
return new Date(Date.UTC(+a[1], +a[2] - 1, +a[3], +a[4],
|
||||
+a[5], +a[6]));
|
||||
}
|
||||
}
|
||||
return value;
|
||||
});
|
||||
|
||||
myData = JSON.parse('["Date(09/09/2001)"]', function (key, value) {
|
||||
var d;
|
||||
if (typeof value === 'string' &&
|
||||
value.slice(0, 5) === 'Date(' &&
|
||||
value.slice(-1) === ')') {
|
||||
d = new Date(value.slice(5, -1));
|
||||
if (d) {
|
||||
return d;
|
||||
}
|
||||
}
|
||||
return value;
|
||||
});
|
||||
|
||||
|
||||
This is a reference implementation. You are free to copy, modify, or
|
||||
redistribute.
|
||||
*/
|
||||
|
||||
/*jslint evil: true, regexp: true */
|
||||
|
||||
/*members "", "\b", "\t", "\n", "\f", "\r", "\"", JSON, "\\", apply,
|
||||
call, charCodeAt, getUTCDate, getUTCFullYear, getUTCHours,
|
||||
getUTCMinutes, getUTCMonth, getUTCSeconds, hasOwnProperty, join,
|
||||
lastIndex, length, parse, prototype, push, replace, slice, stringify,
|
||||
test, toJSON, toString, valueOf
|
||||
*/
|
||||
|
||||
|
||||
// Create a JSON object only if one does not already exist. We create the
|
||||
// methods in a closure to avoid creating global variables.
|
||||
|
||||
if (typeof JSON !== 'object') {
|
||||
JSON = {};
|
||||
}
|
||||
|
||||
(function () {
|
||||
'use strict';
|
||||
|
||||
function f(n) {
|
||||
// Format integers to have at least two digits.
|
||||
return n < 10 ? '0' + n : n;
|
||||
}
|
||||
|
||||
if (typeof Date.prototype.toJSON !== 'function') {
|
||||
|
||||
Date.prototype.toJSON = function () {
|
||||
|
||||
return isFinite(this.valueOf())
|
||||
? this.getUTCFullYear() + '-' +
|
||||
f(this.getUTCMonth() + 1) + '-' +
|
||||
f(this.getUTCDate()) + 'T' +
|
||||
f(this.getUTCHours()) + ':' +
|
||||
f(this.getUTCMinutes()) + ':' +
|
||||
f(this.getUTCSeconds()) + 'Z'
|
||||
: null;
|
||||
};
|
||||
|
||||
String.prototype.toJSON =
|
||||
Number.prototype.toJSON =
|
||||
Boolean.prototype.toJSON = function () {
|
||||
return this.valueOf();
|
||||
};
|
||||
}
|
||||
|
||||
var cx,
|
||||
escapable,
|
||||
gap,
|
||||
indent,
|
||||
meta,
|
||||
rep;
|
||||
|
||||
|
||||
function quote(string) {
|
||||
|
||||
// If the string contains no control characters, no quote characters, and no
|
||||
// backslash characters, then we can safely slap some quotes around it.
|
||||
// Otherwise we must also replace the offending characters with safe escape
|
||||
// sequences.
|
||||
|
||||
escapable.lastIndex = 0;
|
||||
return escapable.test(string) ? '"' + string.replace(escapable, function (a) {
|
||||
var c = meta[a];
|
||||
return typeof c === 'string'
|
||||
? c
|
||||
: '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4);
|
||||
}) + '"' : '"' + string + '"';
|
||||
}
|
||||
|
||||
|
||||
function str(key, holder) {
|
||||
|
||||
// Produce a string from holder[key].
|
||||
|
||||
var i, // The loop counter.
|
||||
k, // The member key.
|
||||
v, // The member value.
|
||||
length,
|
||||
mind = gap,
|
||||
partial,
|
||||
value = holder[key];
|
||||
|
||||
// If the value has a toJSON method, call it to obtain a replacement value.
|
||||
|
||||
if (value && typeof value === 'object' &&
|
||||
typeof value.toJSON === 'function') {
|
||||
value = value.toJSON(key);
|
||||
}
|
||||
|
||||
// If we were called with a replacer function, then call the replacer to
|
||||
// obtain a replacement value.
|
||||
|
||||
if (typeof rep === 'function') {
|
||||
value = rep.call(holder, key, value);
|
||||
}
|
||||
|
||||
// What happens next depends on the value's type.
|
||||
|
||||
switch (typeof value) {
|
||||
case 'string':
|
||||
return quote(value);
|
||||
|
||||
case 'number':
|
||||
|
||||
// JSON numbers must be finite. Encode non-finite numbers as null.
|
||||
|
||||
return isFinite(value) ? String(value) : 'null';
|
||||
|
||||
case 'boolean':
|
||||
case 'null':
|
||||
|
||||
// If the value is a boolean or null, convert it to a string. Note:
|
||||
// typeof null does not produce 'null'. The case is included here in
|
||||
// the remote chance that this gets fixed someday.
|
||||
|
||||
return String(value);
|
||||
|
||||
// If the type is 'object', we might be dealing with an object or an array or
|
||||
// null.
|
||||
|
||||
case 'object':
|
||||
|
||||
// Due to a specification blunder in ECMAScript, typeof null is 'object',
|
||||
// so watch out for that case.
|
||||
|
||||
if (!value) {
|
||||
return 'null';
|
||||
}
|
||||
|
||||
// Make an array to hold the partial results of stringifying this object value.
|
||||
|
||||
gap += indent;
|
||||
partial = [];
|
||||
|
||||
// Is the value an array?
|
||||
|
||||
if (Object.prototype.toString.apply(value) === '[object Array]') {
|
||||
|
||||
// The value is an array. Stringify every element. Use null as a placeholder
|
||||
// for non-JSON values.
|
||||
|
||||
length = value.length;
|
||||
for (i = 0; i < length; i += 1) {
|
||||
partial[i] = str(i, value) || 'null';
|
||||
}
|
||||
|
||||
// Join all of the elements together, separated with commas, and wrap them in
|
||||
// brackets.
|
||||
|
||||
v = partial.length === 0
|
||||
? '[]'
|
||||
: gap
|
||||
? '[\n' + gap + partial.join(',\n' + gap) + '\n' + mind + ']'
|
||||
: '[' + partial.join(',') + ']';
|
||||
gap = mind;
|
||||
return v;
|
||||
}
|
||||
|
||||
// If the replacer is an array, use it to select the members to be stringified.
|
||||
|
||||
if (rep && typeof rep === 'object') {
|
||||
length = rep.length;
|
||||
for (i = 0; i < length; i += 1) {
|
||||
if (typeof rep[i] === 'string') {
|
||||
k = rep[i];
|
||||
v = str(k, value);
|
||||
if (v) {
|
||||
partial.push(quote(k) + (gap ? ': ' : ':') + v);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
||||
// Otherwise, iterate through all of the keys in the object.
|
||||
|
||||
for (k in value) {
|
||||
if (Object.prototype.hasOwnProperty.call(value, k)) {
|
||||
v = str(k, value);
|
||||
if (v) {
|
||||
partial.push(quote(k) + (gap ? ': ' : ':') + v);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Join all of the member texts together, separated with commas,
|
||||
// and wrap them in braces.
|
||||
|
||||
v = partial.length === 0
|
||||
? '{}'
|
||||
: gap
|
||||
? '{\n' + gap + partial.join(',\n' + gap) + '\n' + mind + '}'
|
||||
: '{' + partial.join(',') + '}';
|
||||
gap = mind;
|
||||
return v;
|
||||
}
|
||||
}
|
||||
|
||||
// If the JSON object does not yet have a stringify method, give it one.
|
||||
|
||||
if (typeof JSON.stringify !== 'function') {
|
||||
escapable = /[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g;
|
||||
meta = { // table of character substitutions
|
||||
'\b': '\\b',
|
||||
'\t': '\\t',
|
||||
'\n': '\\n',
|
||||
'\f': '\\f',
|
||||
'\r': '\\r',
|
||||
'"' : '\\"',
|
||||
'\\': '\\\\'
|
||||
};
|
||||
JSON.stringify = function (value, replacer, space) {
|
||||
|
||||
// The stringify method takes a value and an optional replacer, and an optional
|
||||
// space parameter, and returns a JSON text. The replacer can be a function
|
||||
// that can replace values, or an array of strings that will select the keys.
|
||||
// A default replacer method can be provided. Use of the space parameter can
|
||||
// produce text that is more easily readable.
|
||||
|
||||
var i;
|
||||
gap = '';
|
||||
indent = '';
|
||||
|
||||
// If the space parameter is a number, make an indent string containing that
|
||||
// many spaces.
|
||||
|
||||
if (typeof space === 'number') {
|
||||
for (i = 0; i < space; i += 1) {
|
||||
indent += ' ';
|
||||
}
|
||||
|
||||
// If the space parameter is a string, it will be used as the indent string.
|
||||
|
||||
} else if (typeof space === 'string') {
|
||||
indent = space;
|
||||
}
|
||||
|
||||
// If there is a replacer, it must be a function or an array.
|
||||
// Otherwise, throw an error.
|
||||
|
||||
rep = replacer;
|
||||
if (replacer && typeof replacer !== 'function' &&
|
||||
(typeof replacer !== 'object' ||
|
||||
typeof replacer.length !== 'number')) {
|
||||
throw new Error('JSON.stringify');
|
||||
}
|
||||
|
||||
// Make a fake root object containing our value under the key of ''.
|
||||
// Return the result of stringifying the value.
|
||||
|
||||
return str('', {'': value});
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
// If the JSON object does not yet have a parse method, give it one.
|
||||
|
||||
if (typeof JSON.parse !== 'function') {
|
||||
cx = /[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g;
|
||||
JSON.parse = function (text, reviver) {
|
||||
|
||||
// The parse method takes a text and an optional reviver function, and returns
|
||||
// a JavaScript value if the text is a valid JSON text.
|
||||
|
||||
var j;
|
||||
|
||||
function walk(holder, key) {
|
||||
|
||||
// The walk method is used to recursively walk the resulting structure so
|
||||
// that modifications can be made.
|
||||
|
||||
var k, v, value = holder[key];
|
||||
if (value && typeof value === 'object') {
|
||||
for (k in value) {
|
||||
if (Object.prototype.hasOwnProperty.call(value, k)) {
|
||||
v = walk(value, k);
|
||||
if (v !== undefined) {
|
||||
value[k] = v;
|
||||
} else {
|
||||
delete value[k];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return reviver.call(holder, key, value);
|
||||
}
|
||||
|
||||
|
||||
// Parsing happens in four stages. In the first stage, we replace certain
|
||||
// Unicode characters with escape sequences. JavaScript handles many characters
|
||||
// incorrectly, either silently deleting them, or treating them as line endings.
|
||||
|
||||
text = String(text);
|
||||
cx.lastIndex = 0;
|
||||
if (cx.test(text)) {
|
||||
text = text.replace(cx, function (a) {
|
||||
return '\\u' +
|
||||
('0000' + a.charCodeAt(0).toString(16)).slice(-4);
|
||||
});
|
||||
}
|
||||
|
||||
// In the second stage, we run the text against regular expressions that look
|
||||
// for non-JSON patterns. We are especially concerned with '()' and 'new'
|
||||
// because they can cause invocation, and '=' because it can cause mutation.
|
||||
// But just to be safe, we want to reject all unexpected forms.
|
||||
|
||||
// We split the second stage into 4 regexp operations in order to work around
|
||||
// crippling inefficiencies in IE's and Safari's regexp engines. First we
|
||||
// replace the JSON backslash pairs with '@' (a non-JSON character). Second, we
|
||||
// replace all simple value tokens with ']' characters. Third, we delete all
|
||||
// open brackets that follow a colon or comma or that begin the text. Finally,
|
||||
// we look to see that the remaining characters are only whitespace or ']' or
|
||||
// ',' or ':' or '{' or '}'. If that is so, then the text is safe for eval.
|
||||
|
||||
if (/^[\],:{}\s]*$/
|
||||
.test(text.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, '@')
|
||||
.replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']')
|
||||
.replace(/(?:^|:|,)(?:\s*\[)+/g, ''))) {
|
||||
|
||||
// In the third stage we use the eval function to compile the text into a
|
||||
// JavaScript structure. The '{' operator is subject to a syntactic ambiguity
|
||||
// in JavaScript: it can begin a block or an object literal. We wrap the text
|
||||
// in parens to eliminate the ambiguity.
|
||||
|
||||
j = eval('(' + text + ')');
|
||||
|
||||
// In the optional fourth stage, we recursively walk the new structure, passing
|
||||
// each name/value pair to a reviver function for possible transformation.
|
||||
|
||||
return typeof reviver === 'function'
|
||||
? walk({'': j}, '')
|
||||
: j;
|
||||
}
|
||||
|
||||
// If the text is not JSON parseable, then a SyntaxError is thrown.
|
||||
|
||||
throw new SyntaxError('JSON.parse');
|
||||
};
|
||||
}
|
||||
}());
|
||||
Vendored
+23
@@ -0,0 +1,23 @@
|
||||
/*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) {
|
||||
try {
|
||||
return eval('(' + templateSpec + ')');
|
||||
} catch (err) {
|
||||
console.error(templateSpec);
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
Vendored
+2054
File diff suppressed because it is too large
Load Diff
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
+48
@@ -0,0 +1,48 @@
|
||||
/*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 parse = require('../../dist/cjs/handlebars/compiler/base').parse;
|
||||
var compiler = require('../../dist/cjs/handlebars/compiler/compiler');
|
||||
var JavaScriptCompiler = require('../../dist/cjs/handlebars/compiler/javascript-compiler')['default'];
|
||||
|
||||
global.CompilerContext = {
|
||||
compile: function(template, options) {
|
||||
// Hack the compiler on to the environment for these specific tests
|
||||
handlebarsEnv.precompile = function(template, options) {
|
||||
return compiler.precompile(template, options, handlebarsEnv);
|
||||
};
|
||||
handlebarsEnv.parse = parse;
|
||||
handlebarsEnv.Compiler = compiler.Compiler;
|
||||
handlebarsEnv.JavaScriptCompiler = JavaScriptCompiler;
|
||||
|
||||
var templateSpec = handlebarsEnv.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);
|
||||
};
|
||||
handlebarsEnv.parse = parse;
|
||||
handlebarsEnv.Compiler = compiler.Compiler;
|
||||
handlebarsEnv.JavaScriptCompiler = JavaScriptCompiler;
|
||||
|
||||
return handlebarsEnv.compile(template, options);
|
||||
}
|
||||
};
|
||||
|
||||
function safeEval(templateSpec) {
|
||||
try {
|
||||
return eval('(' + templateSpec + ')');
|
||||
} catch (err) {
|
||||
console.error(templateSpec);
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
define(['handlebars.runtime'], function(Handlebars) {
|
||||
Handlebars = Handlebars["default"]; var template = Handlebars.template, templates = Handlebars.templates = Handlebars.templates || {};
|
||||
return templates['empty'] = template({"compiler":[6,">= 2.0.0-beta.1"],"main":function(depth0,helpers,partials,data) {
|
||||
return "";
|
||||
},"useData":true});
|
||||
});
|
||||
+656
@@ -0,0 +1,656 @@
|
||||
/*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 for raw block gets raw content", function() {
|
||||
var string = "{{{{raw}}}} {{test}} {{{{/raw}}}}";
|
||||
var hash = { test: "hello" };
|
||||
var helpers = { raw: function(options) {
|
||||
return options.fn();
|
||||
} };
|
||||
shouldCompileTo(string, [hash, helpers], " {{test}} ",
|
||||
"raw block helper gets raw content");
|
||||
});
|
||||
|
||||
it("helper for raw block gets parameters", function() {
|
||||
var string = "{{{{raw 1 2 3}}}} {{test}} {{{{/raw}}}}";
|
||||
var hash = { test: "hello" };
|
||||
var helpers = { raw: function(a, b, c, options) {
|
||||
return options.fn() + a + b + c;
|
||||
} };
|
||||
shouldCompileTo(string, [hash, helpers], " {{test}} 123",
|
||||
"raw block helper gets raw content");
|
||||
});
|
||||
|
||||
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");
|
||||
});
|
||||
|
||||
it('pathed lambas with parameters', function() {
|
||||
var hash = {
|
||||
helper: function() {
|
||||
return 'winning';
|
||||
}
|
||||
};
|
||||
hash.hash = hash;
|
||||
var helpers = {
|
||||
'./helper': function() {
|
||||
return 'fail';
|
||||
}
|
||||
};
|
||||
shouldCompileTo('{{./helper 1}}', [hash, helpers], 'winning');
|
||||
shouldCompileTo('{{hash/helper 1}}', [hash, helpers], 'winning');
|
||||
});
|
||||
|
||||
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!!");
|
||||
});
|
||||
});
|
||||
|
||||
describe('registration', function() {
|
||||
it('unregisters', function() {
|
||||
var helpers = handlebarsEnv.helpers;
|
||||
handlebarsEnv.helpers = {};
|
||||
|
||||
handlebarsEnv.registerHelper('foo', function() {
|
||||
return 'fail';
|
||||
});
|
||||
handlebarsEnv.unregisterHelper('foo');
|
||||
equals(handlebarsEnv.helpers.foo, undefined);
|
||||
});
|
||||
|
||||
it('allows multiple globals', 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('fails with multiple and args', function() {
|
||||
shouldThrow(function() {
|
||||
handlebarsEnv.registerHelper({
|
||||
world: function() { return "world!"; },
|
||||
test_helper: function() { return 'found it!'; }
|
||||
}, {});
|
||||
}, Error, 'Arg not supported with multiple helpers');
|
||||
});
|
||||
});
|
||||
|
||||
it("decimal number literals work", function() {
|
||||
var string = 'Message: {{hello -1.2 1.2}}';
|
||||
var hash = {};
|
||||
var helpers = {hello: function(times, times2) {
|
||||
if(typeof times !== 'number') { times = "NaN"; }
|
||||
if(typeof times2 !== 'number') { times2 = "NaN"; }
|
||||
return "Hello " + times + " " + times2 + " times";
|
||||
}};
|
||||
shouldCompileTo(string, [hash, helpers], "Message: Hello -1.2 1.2 times", "template with a negative integer literal");
|
||||
});
|
||||
|
||||
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(mesg, options) {
|
||||
if(options.name === "link_to") {
|
||||
return new Handlebars.SafeString("<a>" + mesg + "</a>");
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
shouldCompileTo(string, [context, helpers], "Hello <a>world</a>");
|
||||
});
|
||||
|
||||
it("if a value is not found, custom helperMissing is used", function() {
|
||||
var string = "{{hello}} {{link_to}}";
|
||||
var context = { hello: "Hello", world: "world" };
|
||||
|
||||
var helpers = {
|
||||
helperMissing: function(options) {
|
||||
if(options.name === "link_to") {
|
||||
return new Handlebars.SafeString("<a>winning</a>");
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
shouldCompileTo(string, [context, helpers], "Hello <a>winning</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 field', function() {
|
||||
var context = {};
|
||||
var helpers = {
|
||||
blockHelperMissing: function() {
|
||||
return 'missing: ' + arguments[arguments.length-1].name;
|
||||
},
|
||||
helperMissing: function() {
|
||||
return 'helper missing: ' + arguments[arguments.length-1].name;
|
||||
},
|
||||
helper: function() {
|
||||
return 'ran: ' + arguments[arguments.length-1].name;
|
||||
}
|
||||
};
|
||||
|
||||
it('should include in ambiguous mustache calls', function() {
|
||||
shouldCompileTo('{{helper}}', [context, helpers], 'ran: helper');
|
||||
});
|
||||
it('should include in helper mustache calls', function() {
|
||||
shouldCompileTo('{{helper 1}}', [context, helpers], 'ran: helper');
|
||||
});
|
||||
it('should include in ambiguous block calls', function() {
|
||||
shouldCompileTo('{{#helper}}{{/helper}}', [context, helpers], 'ran: helper');
|
||||
});
|
||||
it('should include in simple block calls', function() {
|
||||
shouldCompileTo('{{#./helper}}{{/./helper}}', [context, helpers], 'missing: ./helper');
|
||||
});
|
||||
it('should include in helper block calls', function() {
|
||||
shouldCompileTo('{{#helper 1}}{{/helper}}', [context, helpers], 'ran: helper');
|
||||
});
|
||||
it('should include in known helper calls', function() {
|
||||
var template = CompilerContext.compile("{{helper}}", {knownHelpers: {'helper': true}, knownHelpersOnly: true});
|
||||
|
||||
equal(template({}, {helpers: helpers}), 'ran: helper');
|
||||
});
|
||||
|
||||
it('should include full id', function() {
|
||||
shouldCompileTo('{{#foo.helper}}{{/foo.helper}}', [{foo: {}}, helpers], 'missing: foo.helper');
|
||||
});
|
||||
|
||||
it('should include full id if a hash is passed', function() {
|
||||
shouldCompileTo('{{#foo.helper bar=baz}}{{/foo.helper}}', [{foo: {}}, helpers], 'helper missing: foo.helper');
|
||||
});
|
||||
});
|
||||
|
||||
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,95 @@
|
||||
<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/json2.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) {
|
||||
try {
|
||||
var ret;
|
||||
eval('ret = ' + templateSpec);
|
||||
return ret;
|
||||
} catch (err) {
|
||||
console.error(templateSpec);
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<script src="/tmp/tests.js"></script>
|
||||
<script>
|
||||
onload = function(){
|
||||
mocha.globals(['mochaResults'])
|
||||
// The test harness leaks under FF. We should have decent global leak coverage from other tests
|
||||
if (!navigator.userAgent.match(/Firefox\/([\d.]+)/)) {
|
||||
mocha.checkLeaks();
|
||||
}
|
||||
var runner = mocha.run();
|
||||
|
||||
//Reporting for saucelabs
|
||||
var failedTests = [];
|
||||
runner.on('end', function(){
|
||||
window.mochaResults = runner.stats;
|
||||
window.mochaResults.reports = failedTests;
|
||||
});
|
||||
|
||||
runner.on('fail', logFailure);
|
||||
|
||||
function logFailure(test, err){
|
||||
|
||||
var flattenTitles = function(test){
|
||||
var titles = [];
|
||||
while (test.parent.title){
|
||||
titles.push(test.parent.title);
|
||||
test = test.parent;
|
||||
}
|
||||
return titles.reverse();
|
||||
};
|
||||
|
||||
failedTests.push({
|
||||
name: test.title,
|
||||
result: false,
|
||||
message: err.message,
|
||||
stack: err.stack,
|
||||
titles: flattenTitles(test)
|
||||
});
|
||||
};
|
||||
};
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<div id="mocha"></div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,71 @@
|
||||
/*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 '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() {
|
||||
handlebarsEnv.JavaScriptCompiler.prototype.forceBuffer = true;
|
||||
$superAppend = handlebarsEnv.JavaScriptCompiler.prototype.appendToBuffer;
|
||||
$superCreate = handlebarsEnv.JavaScriptCompiler.prototype.initializeBuffer;
|
||||
});
|
||||
afterEach(function() {
|
||||
handlebarsEnv.JavaScriptCompiler.prototype.forceBuffer = false;
|
||||
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");
|
||||
});
|
||||
});
|
||||
});
|
||||
Submodule
+1
Submodule spec/mustache added at 72233f3ffd
+193
@@ -0,0 +1,193 @@
|
||||
/*global Handlebars, shouldThrow */
|
||||
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 simple mustaches with data paths', 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 NUMBER parameters', function() {
|
||||
equals(ast_for("{{foo 1}}"), "{{ ID:foo [NUMBER{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=NUMBER{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=NUMBER{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 hash', function() {
|
||||
equals(ast_for("{{> foo bar=bat}}"), "{{> PARTIAL:foo HASH{bar=ID:bat} }}\n");
|
||||
});
|
||||
|
||||
it('parses a partial with context and hash', function() {
|
||||
equals(ast_for("{{> foo bar bat=baz}}"), "{{> PARTIAL:foo ID:bar HASH{bat=ID:baz} }}\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 {{^}}\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 {{^}}\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('parses a standalone inverse section', function() {
|
||||
equals(ast_for("{{else 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/);
|
||||
|
||||
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");
|
||||
});
|
||||
});
|
||||
});
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user