Compare commits
466 Commits
1.0.0.beta.1
...
1.0.0
| Author | SHA1 | Date | |
|---|---|---|---|
| 2a073e0993 | |||
| f63b226b76 | |||
| 16fd601a5c | |||
| ef062adcc2 | |||
| 2024209519 | |||
| d7b345b2da | |||
| 17659b9720 | |||
| 58a0b4f17d | |||
| 61f64e9f04 | |||
| 66f6f94867 | |||
| c540d7186a | |||
| 293672432b | |||
| da2aabe7bd | |||
| 3ddbc5237f | |||
| 503e32208b | |||
| 15186dc9dd | |||
| 2c8dd74997 | |||
| 3d51088d4d | |||
| 822a8911ec | |||
| 5f349913aa | |||
| c1020a0130 | |||
| bff71d79d3 | |||
| 4d95d58e15 | |||
| 47e0aa795a | |||
| 6bf9c6fc13 | |||
| 533be694b5 | |||
| a97820f54b | |||
| f6b6361ad7 | |||
| 10669e0e36 | |||
| 69bcdf36c7 | |||
| 5ba17c9642 | |||
| 7fb9750717 | |||
| c95d9d68ea | |||
| 3aef7f604a | |||
| f5fe6d0bf6 | |||
| 94cc41900b | |||
| d649b3581e | |||
| 229b82bef5 | |||
| 531be77138 | |||
| 4cf0410b7c | |||
| 2ec5a97f4a | |||
| ddc4d31861 | |||
| 85dd39427b | |||
| 8b65fa771e | |||
| fa6cec26bf | |||
| 436973f470 | |||
| a2796784ab | |||
| d355d1f6aa | |||
| 71c5e36268 | |||
| 44ae572e2f | |||
| 7cd9cdb0eb | |||
| 090ee7c59a | |||
| 8a5705a253 | |||
| 344bb9092d | |||
| ef325733bb | |||
| 18798a7546 | |||
| bbce33778e | |||
| fc52a65c70 | |||
| 06e593d32e | |||
| 16a6fe15f2 | |||
| d8d2a6a1d2 | |||
| 4c35f89c2a | |||
| 5c9aa9e4d6 | |||
| ca576c27fc | |||
| 85a21f2359 | |||
| 075fdb8ac4 | |||
| 4429ffa9f3 | |||
| 73f2016a6a | |||
| 381c113d66 | |||
| 0329c852bd | |||
| f6ff5c648b | |||
| 12d68caa58 | |||
| ff32b4e2ad | |||
| c59882cce1 | |||
| 4d66d0c0a6 | |||
| bee0facaca | |||
| af3501636c | |||
| e33d9b4dcf | |||
| 31aaa30fcd | |||
| ec5882a31a | |||
| 671c07e699 | |||
| f4d0092bb8 | |||
| fe74d65f2b | |||
| 9e4b59e815 | |||
| 53de75927c | |||
| bcc15ea5e7 | |||
| 21977633a0 | |||
| e4a0ac4b04 | |||
| 681f1a6396 | |||
| bf30bf9115 | |||
| 948231a31d | |||
| 3657457d81 | |||
| 4bfb137e81 | |||
| 835496f0b6 | |||
| 519156138d | |||
| ac59d6d060 | |||
| baccdb4cfc | |||
| c95b3d6fc5 | |||
| d6f146f8a5 | |||
| 1f00504d25 | |||
| 8b2c1e5d23 | |||
| fa400d9dcb | |||
| 10453d1ef8 | |||
| 30623ce903 | |||
| da5cde5d1f | |||
| 3e86bb0f64 | |||
| 16bfebbf49 | |||
| 1f76b065fc | |||
| e2481cd66e | |||
| 8a05dcb70e | |||
| 261a2deb51 | |||
| 75a4f0d931 | |||
| b74711e63b | |||
| e3454a0187 | |||
| f72cbed479 | |||
| bc8214cb7c | |||
| 7f9e3fea81 | |||
| 1ca7462497 | |||
| 514c9391e0 | |||
| 7c633b604d | |||
| 51e9259fac | |||
| 0844d74fbe | |||
| a48e32bbf6 | |||
| 448e43c41e | |||
| 6fe7f17c89 | |||
| edb6f11b42 | |||
| d7758fe1a9 | |||
| a3376e24b1 | |||
| 6a51198a9b | |||
| a68a5ad5ba | |||
| 6ab92eee6d | |||
| b0c9f3d83b | |||
| b661977966 | |||
| 969b418291 | |||
| 8e9688792b | |||
| 9ac47df7dd | |||
| 6210d0b040 | |||
| 69d46e008b | |||
| f8a346b3ce | |||
| 0f6651bad9 | |||
| 231543feaf | |||
| b3a39ccefd | |||
| 956b0a62a6 | |||
| 94e70043f9 | |||
| bf201a66c2 | |||
| 39cc7c6816 | |||
| e9ac494ffd | |||
| cb50caaf53 | |||
| ae95407775 | |||
| 7277284132 | |||
| d486e7fa47 | |||
| 3c20641a2e | |||
| 5e20c809dc | |||
| 2b875cfb33 | |||
| 228c75669f | |||
| d1fdb82291 | |||
| 247babbe1a | |||
| b1ea697b7d | |||
| 822d98a1ef | |||
| 5ac29dcb50 | |||
| 9453c6d9d2 | |||
| 0c4333ae81 | |||
| ce82bef6f1 | |||
| cd9895061b | |||
| ccd6a22ea5 | |||
| 5e5f0dce9c | |||
| f5079765ff | |||
| 24fc3a2288 | |||
| 8961298859 | |||
| 4f54f62abc | |||
| eaa7e97461 | |||
| 59e30ecf37 | |||
| 3396814797 | |||
| fb9a7aa533 | |||
| 72f33c4615 | |||
| 16f2bf8f9b | |||
| 5f56d6582f | |||
| 76882ff75a | |||
| 3cac2672d0 | |||
| 7a3641df5a | |||
| b3f954b0af | |||
| f671c2e3d7 | |||
| 118594f663 | |||
| f4cad086bb | |||
| 51fd64c334 | |||
| 0ffb2a9681 | |||
| ca22ca57e1 | |||
| e9fe73d7ab | |||
| 28f377d169 | |||
| 02d35262f9 | |||
| 8df8488947 | |||
| 17ee7ffaca | |||
| bf4c813db0 | |||
| 070e12f76f | |||
| 9385666e08 | |||
| dfaf698b44 | |||
| 12f160fb31 | |||
| b58c2dd0ad | |||
| 4bb794d814 | |||
| 7ce13a3c2c | |||
| 19bfbe07f8 | |||
| fd0560b951 | |||
| 4cfda479fa | |||
| eabcc8a002 | |||
| cbfe84f1a9 | |||
| db975b42a0 | |||
| 7963218495 | |||
| bd04901454 | |||
| c3f92c402e | |||
| 53d6b4ca04 | |||
| 39832c0633 | |||
| 2676efb222 | |||
| a927a9b0ad | |||
| 8ed7a1e4e6 | |||
| d89e4b3d33 | |||
| 9589ab8949 | |||
| 3212325173 | |||
| 850304f43b | |||
| ed245c6c64 | |||
| 1018d9bfd1 | |||
| a1bfc80577 | |||
| 1b197be7fc | |||
| 9e582fa0eb | |||
| a1c9acb8b1 | |||
| 42120d1177 | |||
| b5074a88ec | |||
| ce74c36118 | |||
| 26b1438f20 | |||
| b83e5e0ea6 | |||
| f6aea819f3 | |||
| 647ee28ca1 | |||
| d117b50fd3 | |||
| ed8c2b95b8 | |||
| aba2269ddb | |||
| 967c69b2da | |||
| 5a6e4f1ddd | |||
| f55ca6c489 | |||
| 4b73b51e12 | |||
| eccc7c35ca | |||
| 7c4813b417 | |||
| acc04c2826 | |||
| 8e9bd6742f | |||
| 89f5ab8aaf | |||
| dc0426d836 | |||
| 6761d4c6d1 | |||
| 3426969221 | |||
| 9ce3032678 | |||
| 4cf0869330 | |||
| 201917a1be | |||
| 08e580a23b | |||
| 2b3e777340 | |||
| cfc1d534cb | |||
| 4ffe1b8e5a | |||
| d8825b1961 | |||
| f5289a35ec | |||
| fc99d90337 | |||
| eb509a4b8b | |||
| 09ac8ac24b | |||
| adb8486e5f | |||
| 1e151e2030 | |||
| fe7d16de91 | |||
| 075076975d | |||
| 8be16c64f9 | |||
| f79af6bfa3 | |||
| efb1e25690 | |||
| 36b242a710 | |||
| 659be5a690 | |||
| 46c04fa71e | |||
| 5e8be14d78 | |||
| 72e05d623c | |||
| ff1547ea04 | |||
| 7a393a972b | |||
| 92b6c1401a | |||
| 0afc8b58d2 | |||
| 1082ec2414 | |||
| 727eb26cb6 | |||
| 175c6fae0f | |||
| 3486b530be | |||
| bc5efc1767 | |||
| 90adaa3cc8 | |||
| 8786a6c6e2 | |||
| 246325085f | |||
| 68c76c9b2e | |||
| facefe8fed | |||
| 8e70e3b58b | |||
| 730c412925 | |||
| 2418feaf19 | |||
| c2591f428b | |||
| 0872fb6699 | |||
| 98b3327c75 | |||
| 992fdf2071 | |||
| bd9a84a0b7 | |||
| 24e04bad94 | |||
| e6079ca5e2 | |||
| bef909f5cb | |||
| 0b97fea56f | |||
| 2ea95ca08d | |||
| cb9db98642 | |||
| 58e2c2410f | |||
| 6040149cf8 | |||
| 17cdb0091b | |||
| 730c2617af | |||
| 757d5250dc | |||
| 163a92ebe0 | |||
| c79c761460 | |||
| e474e56b80 | |||
| 69307d0e2b | |||
| 9852502636 | |||
| 7a0bf9d332 | |||
| 2e7ea96818 | |||
| 932e2970ad | |||
| 373d10edb1 | |||
| bd0f74fbd9 | |||
| e57e626480 | |||
| cb04822812 | |||
| 647d5adf85 | |||
| 6df51733d3 | |||
| 9c9d92413e | |||
| c6eec286a8 | |||
| f6070f0084 | |||
| 87e6f21765 | |||
| 3afc572f6b | |||
| fea981a3f6 | |||
| 799c7ed3ce | |||
| b2a27347e8 | |||
| 215472dda2 | |||
| 8338bc888a | |||
| 5d15ef9625 | |||
| 42e31ef6db | |||
| deac4f5141 | |||
| 79b730ccf3 | |||
| ec3e5e3914 | |||
| 5f9919dcf7 | |||
| 608aa94fc3 | |||
| 9998d9d9bc | |||
| 924cc3e86c | |||
| 25b8e314af | |||
| 43431d2a78 | |||
| b36776f4c6 | |||
| f23fbb84e2 | |||
| 87599daef1 | |||
| 374a38ed36 | |||
| b501c380af | |||
| 7dd11fa3f2 | |||
| defc2f3403 | |||
| 9a27aa98b7 | |||
| 4458b5fa13 | |||
| 7cd34edcc2 | |||
| 7b18e51bf8 | |||
| cd927e9cda | |||
| b832c85923 | |||
| 33bb89beda | |||
| 33bde69750 | |||
| 2422a6518f | |||
| 0768f07b17 | |||
| 2f3d5977d1 | |||
| 8d38ebd251 | |||
| d630894d9b | |||
| 8f617ca624 | |||
| 57e3bd72a8 | |||
| 6b18873d23 | |||
| c1c455acc8 | |||
| f2f1f5f9bd | |||
| 7212e2b7d7 | |||
| fc84308cc9 | |||
| 91bbc4fd2c | |||
| 696dc2dc36 | |||
| 96571b93c1 | |||
| 3db5fb742e | |||
| c561fdf8b1 | |||
| f7480d5b42 | |||
| 93ebee8faf | |||
| 20350a3c07 | |||
| 8cd4d939f3 | |||
| 98e66f898e | |||
| 85a32a18e2 | |||
| 90648c0e2d | |||
| 6ef3d1316f | |||
| f3a2374dda | |||
| cbadb33d51 | |||
| 17e1b1fab7 | |||
| 6eea3e4d44 | |||
| 48726c7fed | |||
| 47c5cd71ea | |||
| a9bcd369f3 | |||
| b3e70414d8 | |||
| c9d851e9a4 | |||
| 86d71e6fc8 | |||
| 542f98190b | |||
| 16c0907607 | |||
| 292e32292b | |||
| 3c5cac3f3f | |||
| 2f7b724d5a | |||
| 318c08a97e | |||
| d6b97cf34d | |||
| 82e3344aed | |||
| 6349c1a0c7 | |||
| f53737ef23 | |||
| 1a88356dc3 | |||
| 5d4b549dc3 | |||
| d044ada7aa | |||
| 77a20a3a9f | |||
| 9821da8df7 | |||
| 2d538baf72 | |||
| c7e8ddd6b5 | |||
| 6fcebec713 | |||
| 993c793565 | |||
| f8edc59025 | |||
| 3af9de7407 | |||
| 72ea816cf7 | |||
| d641dae5f1 | |||
| ccf32821df | |||
| 8e23ce87d3 | |||
| 5a3522cf2e | |||
| aaec001931 | |||
| 3d9e5070f5 | |||
| 82a1b0e85c | |||
| 600a669d12 | |||
| 5aa12b5e09 | |||
| 6a6edf5ae6 | |||
| aeda7e389f | |||
| 09e79f0587 | |||
| 4a9270aa38 | |||
| 59f5331db4 | |||
| 74bd8cac60 | |||
| 542d64f0be | |||
| 5e611205ac | |||
| 2e77f1c777 | |||
| 8c49721c70 | |||
| 2fc01ff73a | |||
| bba08ad95f | |||
| 034dafddac | |||
| 6aa9a30b02 | |||
| f2dccb753f | |||
| 471f3b9748 | |||
| a927125909 | |||
| 9062cac3f0 | |||
| 059a80661d | |||
| 37e36bf137 | |||
| 3c2086eb2a | |||
| 1ff0b657be | |||
| 17dbfa7004 | |||
| 52900c89f7 | |||
| be8f4f6fb9 | |||
| 1482f1ae72 | |||
| 0f78345d0c | |||
| 4eeda34ad2 | |||
| ed1be562e2 | |||
| 04b359d15b | |||
| f07f70ca42 | |||
| 28d35180bd | |||
| 0e22301e8f | |||
| 4e5cf17311 | |||
| a4ca50533d | |||
| 1aa512d227 | |||
| 2f4644529c | |||
| d109e31f62 | |||
| 5168037b5a | |||
| 92535a9158 | |||
| 4f309bb3aa | |||
| b4c949cc07 | |||
| 90bb568faf | |||
| 60040e60fb | |||
| a00054e26b | |||
| b291a1ad8c | |||
| dec196b4d9 |
+6
-2
@@ -1,5 +1,9 @@
|
||||
dist
|
||||
lib/handlebars/parser.js
|
||||
vendor
|
||||
.rvmrc
|
||||
.DS_Store
|
||||
lib/handlebars/compiler/parser.js
|
||||
dist/*.min.js
|
||||
node_modules
|
||||
*.sublime-project
|
||||
*.sublime-workspace
|
||||
npm-debug.log
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
{
|
||||
"predef": [
|
||||
"console",
|
||||
"Ember",
|
||||
"DS",
|
||||
"Handlebars",
|
||||
"Metamorph",
|
||||
"ember_assert",
|
||||
"ember_warn",
|
||||
"ember_deprecate",
|
||||
"ember_deprecateFunc",
|
||||
"require",
|
||||
"suite",
|
||||
"equal",
|
||||
"equals",
|
||||
"test",
|
||||
"testBoth",
|
||||
"raises",
|
||||
"deepEqual",
|
||||
"start",
|
||||
"stop",
|
||||
"ok",
|
||||
"strictEqual",
|
||||
"module"
|
||||
],
|
||||
|
||||
"node" : true,
|
||||
"es5" : true,
|
||||
"browser" : true,
|
||||
|
||||
"boss" : true,
|
||||
"curly": false,
|
||||
"debug": false,
|
||||
"devel": false,
|
||||
"eqeqeq": true,
|
||||
"evil": true,
|
||||
"forin": false,
|
||||
"immed": false,
|
||||
"laxbreak": false,
|
||||
"newcap": true,
|
||||
"noarg": true,
|
||||
"noempty": false,
|
||||
"nonew": false,
|
||||
"nomen": false,
|
||||
"onevar": false,
|
||||
"plusplus": false,
|
||||
"regexp": false,
|
||||
"undef": true,
|
||||
"sub": true,
|
||||
"strict": false,
|
||||
"white": false
|
||||
}
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
.DS_Store
|
||||
.gitignore
|
||||
.rvmrc
|
||||
Gemfile
|
||||
Gemfile.lock
|
||||
Rakefile
|
||||
bench/*
|
||||
spec/*
|
||||
src/*
|
||||
vendor/*
|
||||
@@ -1,4 +1,5 @@
|
||||
source "http://rubygems.org"
|
||||
|
||||
gem "therubyracer", ">= 0.8.0"
|
||||
gem "rake"
|
||||
gem "therubyracer", ">= 0.9.8", "< 0.11"
|
||||
gem "rspec"
|
||||
|
||||
+15
-11
@@ -1,20 +1,24 @@
|
||||
GEM
|
||||
remote: http://rubygems.org/
|
||||
specs:
|
||||
diff-lcs (1.1.2)
|
||||
rspec (2.1.0)
|
||||
rspec-core (~> 2.1.0)
|
||||
rspec-expectations (~> 2.1.0)
|
||||
rspec-mocks (~> 2.1.0)
|
||||
rspec-core (2.1.0)
|
||||
rspec-expectations (2.1.0)
|
||||
diff-lcs (~> 1.1.2)
|
||||
rspec-mocks (2.1.0)
|
||||
therubyracer (0.8.0)
|
||||
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.8.0)
|
||||
therubyracer (>= 0.9.8, < 0.11)
|
||||
|
||||
@@ -17,4 +17,3 @@ 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.
|
||||
|
||||
|
||||
+274
-103
@@ -1,33 +1,50 @@
|
||||
[](https://travis-ci.org/wycats/handlebars.js)
|
||||
|
||||
Handlebars.js
|
||||
=============
|
||||
|
||||
Handlebars.js is an extension to the [Mustache templating language](http://mustache.github.com/) created by Chris Wanstrath. Handlebars.js and Mustache are both logicless templating languages that keep the view and the code separated like we all know they should be.
|
||||
Handlebars.js is an extension to the [Mustache templating
|
||||
language](http://mustache.github.com/) created by Chris Wanstrath.
|
||||
Handlebars.js and Mustache are both logicless templating languages that
|
||||
keep the view and the code separated like we all know they should be.
|
||||
|
||||
Checkout the official Handlebars docs site at
|
||||
[http://www.handlebarsjs.com](http://www.handlebarsjs.com).
|
||||
|
||||
|
||||
Installing
|
||||
----------
|
||||
Installing Handlebars is easy. Simply [download the package from GitHub](https://github.com/wycats/handlebars.js/archives/master) and add it to your web pages (you should usually use the most recent version).
|
||||
Installing Handlebars is easy. Simply download the package [from the
|
||||
official site](http://handlebarsjs.com/) and add it to your web pages
|
||||
(you should usually use the most recent version).
|
||||
|
||||
Usage
|
||||
-----
|
||||
In general, the syntax of Handlebars.js templates is a superset of Mustache templates. For basic syntax, check out the [Mustache manpage](http://mustache.github.com/mustache.5.html).
|
||||
In general, the syntax of Handlebars.js templates is a superset
|
||||
of Mustache templates. For basic syntax, check out the [Mustache
|
||||
manpage](http://mustache.github.com/mustache.5.html).
|
||||
|
||||
Once you have a template, use the Handlebars.compile method to compile the template into a function. The generated function takes a context argument, which will be used to render the template.
|
||||
Once you have a template, use the `Handlebars.compile` method to compile
|
||||
the template into a function. The generated function takes a context
|
||||
argument, which will be used to render the template.
|
||||
|
||||
var source = "<p>Hello, my name is {{name}}. I am from {{hometown}}. I have " +
|
||||
"{{kids/length}} kids:</p>" +
|
||||
"<ul>{{#kids}}<li>{{name}} is {{age}}</li>{{/kids}}</ul>";
|
||||
var template = Handlebars.compile(source);
|
||||
```js
|
||||
var source = "<p>Hello, my name is {{name}}. I am from {{hometown}}. I have " +
|
||||
"{{kids.length}} kids:</p>" +
|
||||
"<ul>{{#kids}}<li>{{name}} is {{age}}</li>{{/kids}}</ul>";
|
||||
var template = Handlebars.compile(source);
|
||||
|
||||
var data = { "name": "Alan", "hometown": "Somewhere, TX",
|
||||
"kids": [{"name": "Jimmy", "age": "12"}, {"name": "Sally", "age": "4"}]};
|
||||
var result = template(data);
|
||||
var data = { "name": "Alan", "hometown": "Somewhere, TX",
|
||||
"kids": [{"name": "Jimmy", "age": "12"}, {"name": "Sally", "age": "4"}]};
|
||||
var result = template(data);
|
||||
|
||||
// Would render:
|
||||
// <p>Hello, my name is Alan. I am from Somewhere, TX. I have 2 kids:</p>
|
||||
// <ul>
|
||||
// <li>Jimmy is 12</li>
|
||||
// <li>Sally is 4</li>
|
||||
// </ul>
|
||||
// Would render:
|
||||
// <p>Hello, my name is Alan. I am from Somewhere, TX. I have 2 kids:</p>
|
||||
// <ul>
|
||||
// <li>Jimmy is 12</li>
|
||||
// <li>Sally is 4</li>
|
||||
// </ul>
|
||||
```
|
||||
|
||||
|
||||
Registering Helpers
|
||||
@@ -37,22 +54,31 @@ You can register helpers that Handlebars will use when evaluating your
|
||||
template. Here's an example, which assumes that your objects have a URL
|
||||
embedded in them, as well as the text for a link:
|
||||
|
||||
Handlebars.registerHelper('link_to', function(context) {
|
||||
return "<a href='" + context.url + "'>" + context.body + "</a>";
|
||||
});
|
||||
```js
|
||||
Handlebars.registerHelper('link_to', function() {
|
||||
return "<a href='" + this.url + "'>" + this.body + "</a>";
|
||||
});
|
||||
|
||||
var context = { posts: [{url: "/hello-world", body: "Hello World!"}] };
|
||||
var source = "<ul>{{#posts}}<li>{{{link_to this}}}</li></ul>"
|
||||
var context = { posts: [{url: "/hello-world", body: "Hello World!"}] };
|
||||
var source = "<ul>{{#posts}}<li>{{{link_to}}}</li>{{/posts}}</ul>"
|
||||
|
||||
var template = Handlebars.compile(source);
|
||||
template(context);
|
||||
var template = Handlebars.compile(source);
|
||||
template(context);
|
||||
|
||||
// Would render:
|
||||
//
|
||||
// <ul>
|
||||
// <li><a href='/hello-world'>Hello World!</a></li>
|
||||
// </ul>
|
||||
// Would render:
|
||||
//
|
||||
// <ul>
|
||||
// <li><a href='/hello-world'>Hello World!</a></li>
|
||||
// </ul>
|
||||
```
|
||||
|
||||
Helpers take precedence over fields defined on the context. To access a field
|
||||
that is masked by a helper, a path reference may be used. In the example above
|
||||
a field named `link_to` on the `context` object would be referenced using:
|
||||
|
||||
```
|
||||
{{./link_to}}
|
||||
```
|
||||
|
||||
Escaping
|
||||
--------
|
||||
@@ -67,48 +93,67 @@ To explicitly *not* escape the contents, use the triple-mustache
|
||||
|
||||
Differences Between Handlebars.js and Mustache
|
||||
----------------------------------------------
|
||||
Handlebars.js adds a couple of additional features to make writing templates easier and also changes a tiny detail of how partials work.
|
||||
Handlebars.js adds a couple of additional features to make writing
|
||||
templates easier and also changes a tiny detail of how partials work.
|
||||
|
||||
### Paths
|
||||
|
||||
Handlebars.js supports an extended expression syntax that we call paths. Paths are made up of typical expressions and / characters. Expressions allow you to not only display data from the current context, but to display data from contexts that are descendents and ancestors of the current context.
|
||||
Handlebars.js supports an extended expression syntax that we call paths.
|
||||
Paths are made up of typical expressions and . characters. Expressions
|
||||
allow you to not only display data from the current context, but to
|
||||
display data from contexts that are descendants and ancestors of the
|
||||
current context.
|
||||
|
||||
To display data from descendent contexts, use the `/` character. So, for example, if your data were structured like:
|
||||
To display data from descendant contexts, use the `.` character. So, for
|
||||
example, if your data were structured like:
|
||||
|
||||
var data = {"person": { "name": "Alan" }, company: {"name": "Rad, Inc." } };
|
||||
```js
|
||||
var data = {"person": { "name": "Alan" }, company: {"name": "Rad, Inc." } };
|
||||
```
|
||||
|
||||
you could display the person's name from the top-level context with the following expression:
|
||||
You could display the person's name from the top-level context with the
|
||||
following expression:
|
||||
|
||||
{{person/name}}
|
||||
```
|
||||
{{person.name}}
|
||||
```
|
||||
|
||||
Similarly, if already traversed into the person object you could still display the company's name with an expression like `{{../company/name}}`, so:
|
||||
You can backtrack using `../`. For example, if you've already traversed
|
||||
into the person object you could still display the company's name with
|
||||
an expression like `{{../company.name}}`, so:
|
||||
|
||||
{{#person}}{{name}} - {{../company/name}}{{/person}}
|
||||
```
|
||||
{{#person}}{{name}} - {{../company.name}}{{/person}}
|
||||
```
|
||||
|
||||
would render:
|
||||
|
||||
Alan - Rad, Inc.
|
||||
```
|
||||
Alan - Rad, Inc.
|
||||
```
|
||||
|
||||
### Strings
|
||||
|
||||
When calling a helper, you can pass paths or Strings as parameters. For
|
||||
instance:
|
||||
|
||||
Handlebars.registerHelper('link_to', function(title, context) {
|
||||
return "<a href='/posts" + context.id + "'>" + title + "</a>"
|
||||
});
|
||||
```js
|
||||
Handlebars.registerHelper('link_to', function(title, options) {
|
||||
return "<a href='/posts" + this.url + "'>" + title + "!</a>"
|
||||
});
|
||||
|
||||
var context = { posts: [{url: "/hello-world", body: "Hello World!"}] };
|
||||
var source = '<ul>{{#posts}}<li>{{{link_to "Post" this}}}</li></ul>'
|
||||
var context = { posts: [{url: "/hello-world", body: "Hello World!"}] };
|
||||
var source = '<ul>{{#posts}}<li>{{{link_to "Post"}}}</li>{{/posts}}</ul>'
|
||||
|
||||
var template = Handlebars.compile(source);
|
||||
template(context);
|
||||
var template = Handlebars.compile(source);
|
||||
template(context);
|
||||
|
||||
// Would render:
|
||||
//
|
||||
// <ul>
|
||||
// <li><a href='/hello-world'>Post!</a></li>
|
||||
// </ul>
|
||||
// Would render:
|
||||
//
|
||||
// <ul>
|
||||
// <li><a href='/posts/hello-world'>Post!</a></li>
|
||||
// </ul>
|
||||
```
|
||||
|
||||
When you pass a String as a parameter to a helper, the literal String
|
||||
gets passed to the helper function.
|
||||
@@ -116,27 +161,42 @@ gets passed to the helper function.
|
||||
|
||||
### Block Helpers
|
||||
|
||||
Handlebars.js also adds the ability to define block helpers. Block helpers are functions that can be called from anywhere in the template. Here's an example:
|
||||
Handlebars.js also adds the ability to define block helpers. Block
|
||||
helpers are functions that can be called from anywhere in the template.
|
||||
Here's an example:
|
||||
|
||||
var source = "<ul>{{#people}}<li>{{{#link}}}{{name}}{{/link}}</li>{{/people}}</ul>";
|
||||
Handlebars.registerHelper('link', function(context, fn) {
|
||||
return '<a href="/people/' + this.__get__("id") + '">' + fn(this) + '</a>';
|
||||
});
|
||||
var template = Handlebars.compile(source);
|
||||
```js
|
||||
var source = "<ul>{{#people}}<li>{{#link}}{{name}}{{/link}}</li>{{/people}}</ul>";
|
||||
Handlebars.registerHelper('link', function(options) {
|
||||
return '<a href="/people/' + this.id + '">' + options.fn(this) + '</a>';
|
||||
});
|
||||
var template = Handlebars.compile(source);
|
||||
|
||||
var data = { "people": [
|
||||
{ "name": "Alan", "id": 1 },
|
||||
{ "name": "Yehuda", "id": 2 }
|
||||
]};
|
||||
template(data);
|
||||
var data = { "people": [
|
||||
{ "name": "Alan", "id": 1 },
|
||||
{ "name": "Yehuda", "id": 2 }
|
||||
]};
|
||||
template(data);
|
||||
|
||||
// Should render:
|
||||
// <ul>
|
||||
// <li><a href="/people/1">Alan</a></li>
|
||||
// <li><a href="/people/2">Yehuda</a></li>
|
||||
// </ul>
|
||||
// Should render:
|
||||
// <ul>
|
||||
// <li><a href="/people/1">Alan</a></li>
|
||||
// <li><a href="/people/2">Yehuda</a></li>
|
||||
// </ul>
|
||||
```
|
||||
|
||||
Whenever the block helper is called it is given two parameters, the argument that is passed to the helper, or the current context if no argument is passed and the compiled contents of the block. Inside of the block helper the value of `this` is the current context, wrapped to include a method named `__get__` that helps translate paths into values within the helpers.
|
||||
Whenever the block helper is called it is given one or more parameters,
|
||||
any arguments that are passed in the helper in the call and an `options`
|
||||
object containing the `fn` function which executes the block's child.
|
||||
The block's current context may be accessed through `this`.
|
||||
|
||||
Block helpers have the same syntax as mustache sections but should not be
|
||||
confused with one another. Sections are akin to an implicit `each` or
|
||||
`with` statement depending on the input data and helpers are explicit
|
||||
pieces of code that are free to implement whatever behavior they like.
|
||||
The [mustache spec](http://mustache.github.io/mustache.5.html)
|
||||
defines the exact behavior of sections. In the case of name conflicts,
|
||||
helpers are given priority.
|
||||
|
||||
### Partials
|
||||
|
||||
@@ -145,76 +205,187 @@ Handlebars when it encounters a partial (`{{> partialName}}`). Partials
|
||||
can either be String templates or compiled template functions. Here's an
|
||||
example:
|
||||
|
||||
var source = "<ul>{{#people}}<li>{{> link}}</li>{{/people}}</ul>";
|
||||
```js
|
||||
var source = "<ul>{{#people}}<li>{{> link}}</li>{{/people}}</ul>";
|
||||
|
||||
Handlebars.registerPartial('link', '<a href="/people/{{id}}">{{name}}</a>')
|
||||
var template = Handlebars.compile(source);
|
||||
Handlebars.registerPartial('link', '<a href="/people/{{id}}">{{name}}</a>')
|
||||
var template = Handlebars.compile(source);
|
||||
|
||||
var data = { "people": [
|
||||
{ "name": "Alan", "id": 1 },
|
||||
{ "name": "Yehuda", "id": 2 }
|
||||
]};
|
||||
var data = { "people": [
|
||||
{ "name": "Alan", "id": 1 },
|
||||
{ "name": "Yehuda", "id": 2 }
|
||||
]};
|
||||
|
||||
template(data);
|
||||
template(data);
|
||||
|
||||
// Should render:
|
||||
// <ul>
|
||||
// <li><a href="/people/1">Alan</a></li>
|
||||
// <li><a href="/people/2">Yehuda</a></li>
|
||||
// </ul>
|
||||
```
|
||||
|
||||
### Comments
|
||||
|
||||
You can add comments to your templates with the following syntax:
|
||||
|
||||
```js
|
||||
{{! This is a comment }}
|
||||
```
|
||||
|
||||
You can also use real html comments if you want them to end up in the output.
|
||||
|
||||
```html
|
||||
<div>
|
||||
{{! This comment will not end up in the output }}
|
||||
<!-- This comment will show up in the output -->
|
||||
</div>
|
||||
```
|
||||
|
||||
// Should render:
|
||||
// <ul>
|
||||
// <li><a href="/people/1">Alan</a></li>
|
||||
// <li><a href="/people/2">Yehuda</a></li>
|
||||
// </ul>
|
||||
|
||||
Precompiling Templates
|
||||
----------------------
|
||||
|
||||
TODO in the rewrite. This will use RubyRacer and not node.
|
||||
Handlebars allows templates to be precompiled and included as javascript
|
||||
code rather than the handlebars template allowing for faster startup time.
|
||||
|
||||
### Installation
|
||||
The precompiler script may be installed via npm using the `npm install -g handlebars`
|
||||
command.
|
||||
|
||||
### Usage
|
||||
|
||||
<pre>
|
||||
Precompile handlebar templates.
|
||||
Usage: handlebars template...
|
||||
|
||||
Options:
|
||||
-a, --amd Create an AMD format function (allows loading with RequireJS) [boolean]
|
||||
-f, --output Output File [string]
|
||||
-k, --known Known helpers [string]
|
||||
-o, --knownOnly Known helpers only [boolean]
|
||||
-m, --min Minimize output [boolean]
|
||||
-s, --simple Output template function only. [boolean]
|
||||
-r, --root Template root. Base value that will be stripped from template names. [string]
|
||||
</pre>
|
||||
|
||||
If using the precompiler's normal mode, the resulting templates will be
|
||||
stored to the `Handlebars.templates` object using the relative template
|
||||
name sans the extension. These templates may be executed in the same
|
||||
manner as templates.
|
||||
|
||||
If using the simple mode the precompiler will generate a single
|
||||
javascript method. To execute this method it must be passed to the using
|
||||
the `Handlebars.template` method and the resulting object may be as
|
||||
normal.
|
||||
|
||||
### Optimizations
|
||||
|
||||
- Rather than using the full _handlebars.js_ library, implementations that
|
||||
do not need to compile templates at runtime may include _handlebars.runtime.js_
|
||||
whose min+gzip size is approximately 1k.
|
||||
- If a helper is known to exist in the target environment they may be defined
|
||||
using the `--known name` argument may be used to optimize accesses to these
|
||||
helpers for size and speed.
|
||||
- When all helpers are known in advance the `--knownOnly` argument may be used
|
||||
to optimize all block helper references.
|
||||
|
||||
Supported Environments
|
||||
----------------------
|
||||
|
||||
Handlebars has been designed to work in any ECMAScript 3 environment. This includes
|
||||
|
||||
- Node.js
|
||||
- Chrome
|
||||
- Firefox
|
||||
- Safari 5+
|
||||
- Opera 11+
|
||||
- IE 6+
|
||||
|
||||
Older versions and other runtimes are likely to work but have not been formally
|
||||
tested.
|
||||
|
||||
Performance
|
||||
-----------
|
||||
|
||||
In a rough performance test, precompiled Handlebars.js templates (in the original version of Handlebars.js) rendered in about half the time of Mustache templates. It would be a shame if it were any other way, since they were precompiled, but the difference in architecture does have some big performance advantages. Justin Marney, a.k.a. [gotascii](http://github.com/gotascii), confirmed that with an [independent test](http://sorescode.com/2010/09/12/benchmarks.html). The rewritten Handlebars (current version) is faster than the old version, and we will have some benchmarks in the near future.
|
||||
In a rough performance test, precompiled Handlebars.js templates (in
|
||||
the original version of Handlebars.js) rendered in about half the
|
||||
time of Mustache templates. It would be a shame if it were any other
|
||||
way, since they were precompiled, but the difference in architecture
|
||||
does have some big performance advantages. Justin Marney, a.k.a.
|
||||
[gotascii](http://github.com/gotascii), confirmed that with an
|
||||
[independent test](http://sorescode.com/2010/09/12/benchmarks.html). The
|
||||
rewritten Handlebars (current version) is faster than the old version,
|
||||
and we will have some benchmarks in the near future.
|
||||
|
||||
|
||||
Building
|
||||
--------
|
||||
|
||||
To build handlebars, just run `rake release`, and you will get two files
|
||||
in the `dist` directory. The debug version comes with stack trace
|
||||
annotations for webkit browsers, but will slightly increase startup
|
||||
time.
|
||||
in the `dist` directory.
|
||||
|
||||
|
||||
Upgrading
|
||||
---------
|
||||
|
||||
When upgrading from the Handlebars 0.9 series, be aware that the
|
||||
signature for passing custom helpers or partials to templates has
|
||||
changed.
|
||||
|
||||
Instead of:
|
||||
|
||||
template(context, helpers, partials, [data])
|
||||
|
||||
Use:
|
||||
|
||||
template(context, {helpers: helpers, partials: partials, data: data}
|
||||
See [release-notes.md](https://github.com/wycats/handlebars.js/blob/master/release-notes.md) for upgrade notes.
|
||||
|
||||
Known Issues
|
||||
------------
|
||||
* Handlebars.js can be cryptic when there's an error while rendering.
|
||||
|
||||
Handlebars Contrib
|
||||
------------------
|
||||
Alan Johnson, a.k.a. commondream, keeps a repository of useful helpers in the [handlebars-contrib](http://github.com/commondream/handlebars-contrib) repository. Feel free to copy those, or add some of your own.
|
||||
* Using a variable, helper, or partial named `class` causes errors in IE browsers. (Instead, use `className`)
|
||||
|
||||
Handlebars in the Wild
|
||||
-----------------
|
||||
* Don Park wrote an Express.js view engine adapter for Handlebars.js called [hbs](http://github.com/donpark/hbs)
|
||||
* [sammy.js](http://github.com/quirkey/sammy) by Aaron Quint, a.k.a. quirkey, supports Handlebars.js as one of its template plugins.
|
||||
* [jblotus](http://github.com/jblotus) created [http://tryhandlebarsjs.com](http://tryhandlebarsjs.com)
|
||||
for anyone who would like to try out Handlebars.js in their browser.
|
||||
* Don Park wrote an Express.js view engine adapter for Handlebars.js called
|
||||
[hbs](http://github.com/donpark/hbs).
|
||||
* [sammy.js](http://github.com/quirkey/sammy) by Aaron Quint, a.k.a. quirkey,
|
||||
supports Handlebars.js as one of its template plugins.
|
||||
* [SproutCore](http://www.sproutcore.com) uses Handlebars.js as its main
|
||||
templating engine, extending it with automatic data binding support.
|
||||
* [Ember.js](http://www.emberjs.com) makes Handlebars.js the primary way to
|
||||
structure your views, also with automatic data binding support.
|
||||
* Les Hill (@leshill) wrote a Rails Asset Pipeline gem named
|
||||
[handlebars_assets](http://github.com/leshill/handlebars_assets).
|
||||
* [Gist about Synchronous and asynchronous loading of external handlebars templates](https://gist.github.com/2287070)
|
||||
* [Lumbar](walmartlabs.github.io/lumbar) provides easy module-based template management for handlebars projects.
|
||||
* [YUI](http://yuilibrary.com/yui/docs/handlebars/) implements a port of handlebars
|
||||
|
||||
Have a project using Handlebars? Send us a [pull request](https://github.com/wycats/handlebars.js/pull/new/master)!
|
||||
|
||||
Helping Out
|
||||
-----------
|
||||
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.
|
||||
To build Handlebars.js you'll need a few things installed.
|
||||
|
||||
* Node.js
|
||||
* Ruby
|
||||
* therubyracer, for running tests - `gem install therubyracer`
|
||||
* rspec, for running tests - `gem install rspec`
|
||||
|
||||
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.
|
||||
|
||||
License
|
||||
-------
|
||||
Handlebars.js is released under the MIT license.
|
||||
|
||||
|
||||
@@ -1,51 +1,67 @@
|
||||
require "rubygems"
|
||||
require "bundler/setup"
|
||||
|
||||
file "lib/handlebars/parser.js" => ["src/handlebars.yy","src/handlebars.l"] do
|
||||
if ENV['PATH'].split(':').any? {|folder| File.exists?(folder+'/jison')}
|
||||
system "jison src/handlebars.yy src/handlebars.l"
|
||||
File.open("lib/handlebars/parser.js", "w") do |file|
|
||||
file.puts File.read("handlebars.js") + ";"
|
||||
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
|
||||
puts "Jison is not installed. Try running `npm install jison`."
|
||||
fail "Failed to run Jison."
|
||||
end
|
||||
end
|
||||
|
||||
task :compile => "lib/handlebars/parser.js"
|
||||
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
|
||||
system "rspec -cfs spec"
|
||||
rc = system "rspec -cfs spec"
|
||||
fail "rspec spec failed with exit code #{$?.exitstatus}" if (rc.nil? || ! rc || $?.exitstatus != 0)
|
||||
end
|
||||
|
||||
task :default => [:compile, :spec]
|
||||
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(parser base ast visitor utils compiler).map do |file|
|
||||
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
|
||||
|
||||
debug_deps = %w(parser base ast visitor printer utils compiler debug).map do |file|
|
||||
runtime_deps = %w(browser-prefix base utils runtime browser-suffix).map do |file|
|
||||
"lib/handlebars/#{file}.js"
|
||||
end
|
||||
|
||||
directory "dist"
|
||||
|
||||
minimal_deps.unshift "dist"
|
||||
debug_deps.unshift "dist"
|
||||
|
||||
def build_for_task(task)
|
||||
FileUtils.rm_rf("dist/*") if File.directory?("dist")
|
||||
FileUtils.mkdir_p("dist")
|
||||
|
||||
contents = []
|
||||
contents = ["/*\n\n" + File.read('LICENSE') + "\n*/\n"]
|
||||
task.prerequisites.each do |filename|
|
||||
next if filename == "dist"
|
||||
|
||||
@@ -61,47 +77,60 @@ file "dist/handlebars.js" => minimal_deps do |task|
|
||||
build_for_task(task)
|
||||
end
|
||||
|
||||
file "dist/handlebars.debug.js" => debug_deps do |task|
|
||||
file "dist/handlebars.runtime.js" => runtime_deps do |task|
|
||||
build_for_task(task)
|
||||
end
|
||||
|
||||
task :build => [:compile, "dist/handlebars.js"]
|
||||
task :debug => [:compile, "dist/handlebars.debug.js"]
|
||||
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
|
||||
|
||||
desc "build the build and debug versions of handlebars"
|
||||
task :release => [:build, :debug]
|
||||
# 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"
|
||||
File.open("vendor/mustache.js", "w") do |file|
|
||||
file.puts open("https://github.com/janl/mustache.js/raw/master/mustache.js").read
|
||||
file.puts "module.exports = Mustache;"
|
||||
end
|
||||
|
||||
File.open("vendor/benchmark.js", "w") do |file|
|
||||
file.puts open("https://github.com/mathiasbynens/benchmark.js/raw/master/benchmark.js").read
|
||||
end
|
||||
#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/dustjs")
|
||||
system "cd vendor/dustjs && git pull"
|
||||
else
|
||||
system "git clone git://github.com/akdubya/dustjs.git vendor/dustjs"
|
||||
end
|
||||
|
||||
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"
|
||||
else
|
||||
system "git clone git://github.com/sstephenson/eco.git vendor/eco"
|
||||
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
|
||||
|
||||
@@ -112,6 +112,8 @@ BenchWarmer.prototype = {
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
print("\n");
|
||||
},
|
||||
startLine: function(name) {
|
||||
var winners = Benchmark.map(this.winners(this.currentBenches), function(bench) {
|
||||
@@ -131,7 +133,7 @@ BenchWarmer.prototype = {
|
||||
|
||||
if(!bench.error) {
|
||||
var count = bench.hz,
|
||||
moe = count * bench.stats.RME / 100;
|
||||
moe = count * bench.stats.rme / 100;
|
||||
|
||||
out = Math.round(count / 1000) + " ±" + Math.round(moe / 1000) + " (" + bench.cycles + ")";
|
||||
} else {
|
||||
|
||||
+43
-37
@@ -1,23 +1,22 @@
|
||||
require.paths.push("lib");
|
||||
require.paths.push("vendor");
|
||||
require.paths.push("vendor/dustjs/lib");
|
||||
require.paths.push("vendor/coffee/lib");
|
||||
require.paths.push("vendor/eco/lib");
|
||||
|
||||
|
||||
var BenchWarmer = require("./benchwarmer");
|
||||
Handlebars = require("handlebars");
|
||||
Handlebars = require("../lib/handlebars");
|
||||
|
||||
var dust = require("dust");
|
||||
var Mustache = require("mustache");
|
||||
var ecoExports = require("eco");
|
||||
var dust, Mustache, eco;
|
||||
|
||||
eco = function(str) {
|
||||
var module = {};
|
||||
var template = new Function("module", ecoExports.compile(str));
|
||||
template(module);
|
||||
return module.exports;
|
||||
}
|
||||
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: {
|
||||
@@ -116,39 +115,46 @@ var makeSuite = function(name) {
|
||||
|
||||
var error = function() { throw new Error("EWOT"); };
|
||||
|
||||
|
||||
//bench("dust", function() {
|
||||
//dust.render(templateName, context, function(err, out) { });
|
||||
//});
|
||||
if (dust) {
|
||||
bench("dust", function() {
|
||||
dust.render(templateName, context, function(err, out) { });
|
||||
});
|
||||
}
|
||||
|
||||
bench("handlebars", function() {
|
||||
handlebarsTemplates[templateName](context);
|
||||
});
|
||||
|
||||
//if(ecoTemplates[templateName]) {
|
||||
//bench("eco", function() {
|
||||
//ecoTemplates[templateName](context);
|
||||
//});
|
||||
//} else {
|
||||
//bench("eco", error);
|
||||
//}
|
||||
if (eco) {
|
||||
if(ecoTemplates[templateName]) {
|
||||
bench("eco", function() {
|
||||
ecoTemplates[templateName](context);
|
||||
});
|
||||
} else {
|
||||
bench("eco", error);
|
||||
}
|
||||
}
|
||||
|
||||
//if(mustacheSource) {
|
||||
//bench("mustache", function() {
|
||||
//Mustache.to_html(mustacheSource, context, mustachePartials);
|
||||
//});
|
||||
//} else {
|
||||
//bench("mustache", 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)) {
|
||||
dust.loadSource(dust.compile(benchDetails[name].dust, name));
|
||||
if (dust) {
|
||||
dust.loadSource(dust.compile(benchDetails[name].dust, name));
|
||||
}
|
||||
handlebarsTemplates[name] = Handlebars.compile(benchDetails[name].handlebars);
|
||||
|
||||
if(benchDetails[name].eco) { ecoTemplates[name] = eco(benchDetails[name].eco); }
|
||||
if (eco && benchDetails[name].eco) {
|
||||
ecoTemplates[name] = eco(benchDetails[name].eco);
|
||||
}
|
||||
|
||||
var partials = benchDetails[name].partials;
|
||||
if(partials) {
|
||||
|
||||
Executable
+213
@@ -0,0 +1,213 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
var optimist = require('optimist')
|
||||
.usage('Precompile handlebar templates.\nUsage: $0 template...', {
|
||||
'f': {
|
||||
'type': 'string',
|
||||
'description': 'Output File',
|
||||
'alias': 'output'
|
||||
},
|
||||
'a': {
|
||||
'type': 'boolean',
|
||||
'description': 'Exports amd style (require.js)',
|
||||
'alias': 'amd'
|
||||
},
|
||||
'c': {
|
||||
'type': 'string',
|
||||
'description': 'Exports CommonJS style, path to Handlebars module',
|
||||
'alias': 'commonjs',
|
||||
'default': null
|
||||
},
|
||||
'h': {
|
||||
'type': 'string',
|
||||
'description': 'Path to handlebar.js (only valid for amd-style)',
|
||||
'alias': 'handlebarPath',
|
||||
'default': ''
|
||||
},
|
||||
'k': {
|
||||
'type': 'string',
|
||||
'description': 'Known helpers',
|
||||
'alias': 'known'
|
||||
},
|
||||
'o': {
|
||||
'type': 'boolean',
|
||||
'description': 'Known helpers only',
|
||||
'alias': 'knownOnly'
|
||||
},
|
||||
'm': {
|
||||
'type': 'boolean',
|
||||
'description': 'Minimize output',
|
||||
'alias': 'min'
|
||||
},
|
||||
'n': {
|
||||
'type': 'string',
|
||||
'description': 'Template namespace',
|
||||
'alias': 'namespace',
|
||||
'default': 'Handlebars.templates'
|
||||
},
|
||||
's': {
|
||||
'type': 'boolean',
|
||||
'description': 'Output template function only.',
|
||||
'alias': 'simple'
|
||||
},
|
||||
'r': {
|
||||
'type': 'string',
|
||||
'description': 'Template root. Base value that will be stripped from template names.',
|
||||
'alias': 'root'
|
||||
},
|
||||
'p' : {
|
||||
'type': 'boolean',
|
||||
'description': 'Compiling a partial template',
|
||||
'alias': 'partial'
|
||||
},
|
||||
'd' : {
|
||||
'type': 'boolean',
|
||||
'description': 'Include data when compiling',
|
||||
'alias': 'data'
|
||||
},
|
||||
'e': {
|
||||
'type': 'string',
|
||||
'description': 'Template extension.',
|
||||
'alias': 'extension',
|
||||
'default': 'handlebars'
|
||||
}
|
||||
})
|
||||
|
||||
.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';
|
||||
}
|
||||
});
|
||||
|
||||
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);
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"name": "handlebars.js",
|
||||
"version": "1.0.0",
|
||||
"main": "dist/handlebars.js",
|
||||
"ignore": [
|
||||
"node_modules",
|
||||
"components"
|
||||
]
|
||||
}
|
||||
Vendored
+2278
File diff suppressed because it is too large
Load Diff
Vendored
+362
@@ -0,0 +1,362 @@
|
||||
/*
|
||||
|
||||
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);
|
||||
;
|
||||
@@ -0,0 +1,21 @@
|
||||
# -*- encoding: utf-8 -*-
|
||||
require 'json'
|
||||
|
||||
package = JSON.parse(File.read('package.json'))
|
||||
|
||||
Gem::Specification.new do |gem|
|
||||
gem.name = "handlebars-source"
|
||||
gem.authors = ["Yehuda Katz"]
|
||||
gem.email = ["wycats@gmail.com"]
|
||||
gem.date = Time.now.strftime("%Y-%m-%d")
|
||||
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.files = [
|
||||
'dist/handlebars.js',
|
||||
'dist/handlebars.runtime.js',
|
||||
'lib/handlebars/source.rb'
|
||||
]
|
||||
end
|
||||
@@ -0,0 +1,17 @@
|
||||
<?xml version="1.0"?>
|
||||
<package>
|
||||
<metadata>
|
||||
<id>handlebars.js</id>
|
||||
<version>1.0.0</version>
|
||||
<authors>handlebars.js Authors</authors>
|
||||
<licenseUrl>https://github.com/wycats/handlebars.js/blob/master/LICENSE</licenseUrl>
|
||||
<projectUrl>https://github.com/wycats/handlebars.js/</projectUrl>
|
||||
<requireLicenseAcceptance>false</requireLicenseAcceptance>
|
||||
<description>Extension of the Mustache logicless template language</description>
|
||||
<releaseNotes></releaseNotes>
|
||||
<tags>handlebars mustache template html</tags>
|
||||
</metadata>
|
||||
<files>
|
||||
<file src="dist\handlebars.js" target="Content\Scripts" />
|
||||
</files>
|
||||
</package>
|
||||
+35
-7
@@ -1,15 +1,43 @@
|
||||
var Handlebars = require("handlebars/base");
|
||||
module.exports = Handlebars;
|
||||
var handlebars = require("./handlebars/base"),
|
||||
|
||||
require("handlebars/utils");
|
||||
// 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");
|
||||
|
||||
require("handlebars/ast");
|
||||
require("handlebars/printer");
|
||||
require("handlebars/visitor");
|
||||
var create = function() {
|
||||
var hb = handlebars.create();
|
||||
|
||||
require("handlebars/compiler");
|
||||
utils.attach(hb);
|
||||
compiler.attach(hb);
|
||||
runtime.attach(hb);
|
||||
|
||||
return hb;
|
||||
};
|
||||
|
||||
var Handlebars = create();
|
||||
Handlebars.create = create;
|
||||
|
||||
module.exports = Handlebars; // instantiate an instance
|
||||
|
||||
// Publish a Node.js require() handler for .handlebars and .hbs files
|
||||
if (require.extensions) {
|
||||
var extension = function(module, filename) {
|
||||
var fs = require("fs");
|
||||
var templateString = fs.readFileSync(filename, "utf8");
|
||||
module.exports = Handlebars.compile(templateString);
|
||||
};
|
||||
require.extensions[".handlebars"] = extension;
|
||||
require.extensions[".hbs"] = extension;
|
||||
}
|
||||
|
||||
// BEGIN(BROWSER)
|
||||
|
||||
// END(BROWSER)
|
||||
|
||||
// USAGE:
|
||||
// var handlebars = require('handlebars');
|
||||
|
||||
// var singleton = handlebars.Handlebars,
|
||||
// local = handlebars.create();
|
||||
|
||||
@@ -1,93 +0,0 @@
|
||||
var Handlebars = require("handlebars");
|
||||
|
||||
// BEGIN(BROWSER)
|
||||
(function() {
|
||||
|
||||
Handlebars.AST = {};
|
||||
|
||||
Handlebars.AST.ProgramNode = function(statements, inverse) {
|
||||
this.type = "program";
|
||||
this.statements = statements;
|
||||
if(inverse) { this.inverse = new Handlebars.AST.ProgramNode(inverse); }
|
||||
};
|
||||
|
||||
Handlebars.AST.MustacheNode = function(params, hash, unescaped) {
|
||||
this.type = "mustache";
|
||||
this.id = params[0];
|
||||
this.params = params.slice(1);
|
||||
this.hash = hash;
|
||||
this.escaped = !unescaped;
|
||||
};
|
||||
|
||||
Handlebars.AST.PartialNode = function(id, context) {
|
||||
this.type = "partial";
|
||||
|
||||
// TODO: disallow complex IDs
|
||||
|
||||
this.id = id;
|
||||
this.context = context;
|
||||
};
|
||||
|
||||
var verifyMatch = function(open, close) {
|
||||
if(open.original !== close.original) {
|
||||
throw new Handlebars.Exception(open.original + " doesn't match " + close.original);
|
||||
}
|
||||
};
|
||||
|
||||
Handlebars.AST.BlockNode = function(mustache, program, close) {
|
||||
verifyMatch(mustache.id, close);
|
||||
this.type = "block";
|
||||
this.mustache = mustache;
|
||||
this.program = program;
|
||||
};
|
||||
|
||||
Handlebars.AST.InverseNode = function(mustache, program, close) {
|
||||
verifyMatch(mustache.id, close);
|
||||
this.type = "inverse";
|
||||
this.mustache = mustache;
|
||||
this.program = program;
|
||||
};
|
||||
|
||||
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";
|
||||
this.original = parts.join(".");
|
||||
|
||||
var dig = [], depth = 0;
|
||||
|
||||
for(var i=0,l=parts.length; i<l; i++) {
|
||||
var part = parts[i];
|
||||
|
||||
if(part === "..") { depth++; }
|
||||
else if(part === "." || part === "this") { continue; }
|
||||
else { dig.push(part); }
|
||||
}
|
||||
|
||||
this.parts = dig;
|
||||
this.string = dig.join('.');
|
||||
this.depth = depth;
|
||||
this.isSimple = (dig.length === 1) && (depth === 0);
|
||||
};
|
||||
|
||||
Handlebars.AST.StringNode = function(string) {
|
||||
this.type = "STRING";
|
||||
this.string = string;
|
||||
};
|
||||
|
||||
Handlebars.AST.CommentNode = function(comment) {
|
||||
this.type = "comment";
|
||||
this.comment = comment;
|
||||
};
|
||||
|
||||
})();
|
||||
// END(BROWSER)
|
||||
|
||||
+117
-61
@@ -1,50 +1,60 @@
|
||||
var handlebars = require("handlebars/parser").parser;
|
||||
/*jshint eqnull: true */
|
||||
|
||||
module.exports.create = function() {
|
||||
|
||||
// BEGIN(BROWSER)
|
||||
var Handlebars = {};
|
||||
|
||||
Handlebars.VERSION = "1.0.beta.1";
|
||||
// BEGIN(BROWSER)
|
||||
|
||||
Handlebars.Parser = handlebars;
|
||||
Handlebars.VERSION = "1.0.0";
|
||||
Handlebars.COMPILER_REVISION = 4;
|
||||
|
||||
Handlebars.parse = function(string) {
|
||||
Handlebars.Parser.yy = Handlebars.AST;
|
||||
return Handlebars.Parser.parse(string);
|
||||
};
|
||||
|
||||
Handlebars.print = function(ast) {
|
||||
return new Handlebars.PrintVisitor().accept(ast);
|
||||
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(inverse) { fn.not = inverse; }
|
||||
this.helpers[name] = fn;
|
||||
if (toString.call(name) === objectType) {
|
||||
if (inverse || fn) { throw new Handlebars.Exception('Arg not supported with multiple helpers'); }
|
||||
Handlebars.Utils.extend(this.helpers, name);
|
||||
} else {
|
||||
if (inverse) { fn.not = inverse; }
|
||||
this.helpers[name] = fn;
|
||||
}
|
||||
};
|
||||
|
||||
Handlebars.registerPartial = function(name, str) {
|
||||
this.partials[name] = str;
|
||||
if (toString.call(name) === objectType) {
|
||||
Handlebars.Utils.extend(this.partials, name);
|
||||
} else {
|
||||
this.partials[name] = str;
|
||||
}
|
||||
};
|
||||
|
||||
Handlebars.registerHelper('helperMissing', function(arg) {
|
||||
if(arguments.length === 2) {
|
||||
return undefined;
|
||||
} else {
|
||||
throw new Error("Could not find property '" + arg + "'");
|
||||
throw new Error("Missing helper: '" + arg + "'");
|
||||
}
|
||||
});
|
||||
|
||||
Handlebars.registerHelper('blockHelperMissing', function(context, fn, inverse) {
|
||||
inverse = inverse || function() {};
|
||||
Handlebars.registerHelper('blockHelperMissing', function(context, options) {
|
||||
var inverse = options.inverse || function() {}, fn = options.fn;
|
||||
|
||||
var ret = "";
|
||||
var type = Object.prototype.toString.call(context);
|
||||
var type = toString.call(context);
|
||||
|
||||
if(type === "[object Function]") {
|
||||
context = context();
|
||||
}
|
||||
if(type === functionType) { context = context.call(this); }
|
||||
|
||||
if(context === true) {
|
||||
return fn(this);
|
||||
@@ -52,59 +62,105 @@ Handlebars.registerHelper('blockHelperMissing', function(context, fn, inverse) {
|
||||
return inverse(this);
|
||||
} else if(type === "[object Array]") {
|
||||
if(context.length > 0) {
|
||||
for(var i=0, j=context.length; i<j; i++) {
|
||||
ret = ret + fn(context[i]);
|
||||
}
|
||||
return Handlebars.helpers.each(context, options);
|
||||
} else {
|
||||
ret = inverse(this);
|
||||
return inverse(this);
|
||||
}
|
||||
return ret;
|
||||
} else {
|
||||
return fn(context);
|
||||
}
|
||||
}, function(context, fn) {
|
||||
return fn(context);
|
||||
});
|
||||
|
||||
Handlebars.registerHelper('each', function(context, fn, inverse) {
|
||||
var ret = "";
|
||||
Handlebars.K = function() {};
|
||||
|
||||
if(context && context.length > 0) {
|
||||
for(var i=0, j=context.length; i<j; i++) {
|
||||
ret = ret + fn(context[i]);
|
||||
}
|
||||
} else {
|
||||
ret = inverse(this);
|
||||
}
|
||||
return ret;
|
||||
});
|
||||
|
||||
Handlebars.registerHelper('if', function(context, fn, inverse) {
|
||||
if(!context || context == []) {
|
||||
return inverse(this);
|
||||
} else {
|
||||
return fn(this);
|
||||
}
|
||||
});
|
||||
|
||||
Handlebars.registerHelper('unless', function(context, fn, inverse) {
|
||||
return Handlebars.helpers['if'].call(this, context, inverse, fn);
|
||||
});
|
||||
|
||||
Handlebars.registerHelper('with', function(context, fn) {
|
||||
return fn(context);
|
||||
});
|
||||
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,
|
||||
|
||||
// override in the host environment
|
||||
log: function(level, str) {}
|
||||
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, str) { Handlebars.logger.log(level, str); };
|
||||
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);
|
||||
});
|
||||
|
||||
// END(BROWSER)
|
||||
|
||||
module.exports = Handlebars;
|
||||
|
||||
return Handlebars;
|
||||
};
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
var Handlebars = {};
|
||||
|
||||
(function(Handlebars, undefined) {
|
||||
@@ -0,0 +1 @@
|
||||
})(Handlebars);
|
||||
@@ -1,736 +0,0 @@
|
||||
var Handlebars = require("handlebars");
|
||||
|
||||
// BEGIN(BROWSER)
|
||||
Handlebars.Compiler = function() {};
|
||||
Handlebars.JavaScriptCompiler = function() {};
|
||||
|
||||
(function(Compiler, JavaScriptCompiler) {
|
||||
Compiler.OPCODE_MAP = {
|
||||
appendContent: 1,
|
||||
getContext: 2,
|
||||
lookupWithHelpers: 3,
|
||||
lookup: 4,
|
||||
append: 5,
|
||||
invokeMustache: 6,
|
||||
appendEscaped: 7,
|
||||
pushString: 8,
|
||||
truthyOrFallback: 9,
|
||||
functionOrFallback: 10,
|
||||
invokeProgram: 11,
|
||||
invokePartial: 12,
|
||||
push: 13,
|
||||
invokeInverse: 14,
|
||||
assignToHash: 15,
|
||||
pushStringParam: 16
|
||||
};
|
||||
|
||||
Compiler.MULTI_PARAM_OPCODES = {
|
||||
appendContent: 1,
|
||||
getContext: 1,
|
||||
lookupWithHelpers: 1,
|
||||
lookup: 1,
|
||||
invokeMustache: 2,
|
||||
pushString: 1,
|
||||
truthyOrFallback: 1,
|
||||
functionOrFallback: 1,
|
||||
invokeProgram: 2,
|
||||
invokePartial: 1,
|
||||
push: 1,
|
||||
invokeInverse: 1,
|
||||
assignToHash: 1,
|
||||
pushStringParam: 1
|
||||
};
|
||||
|
||||
Compiler.DISASSEMBLE_MAP = {};
|
||||
|
||||
for(var prop in Compiler.OPCODE_MAP) {
|
||||
var value = Compiler.OPCODE_MAP[prop];
|
||||
Compiler.DISASSEMBLE_MAP[value] = prop;
|
||||
}
|
||||
|
||||
Compiler.multiParamSize = function(code) {
|
||||
return Compiler.MULTI_PARAM_OPCODES[Compiler.DISASSEMBLE_MAP[code]];
|
||||
};
|
||||
|
||||
Compiler.prototype = {
|
||||
compiler: Compiler,
|
||||
|
||||
disassemble: function() {
|
||||
var opcodes = this.opcodes, opcode, nextCode;
|
||||
var out = [], str, name, value;
|
||||
|
||||
for(var i=0, l=opcodes.length; i<l; i++) {
|
||||
opcode = opcodes[i];
|
||||
|
||||
if(opcode === 'DECLARE') {
|
||||
name = opcodes[++i];
|
||||
value = opcodes[++i];
|
||||
out.push("DECLARE " + name + " = " + value);
|
||||
} else {
|
||||
str = Compiler.DISASSEMBLE_MAP[opcode];
|
||||
|
||||
var extraParams = Compiler.multiParamSize(opcode);
|
||||
var codes = [];
|
||||
|
||||
for(var j=0; j<extraParams; j++) {
|
||||
nextCode = opcodes[++i];
|
||||
|
||||
if(typeof nextCode === "string") {
|
||||
nextCode = "\"" + nextCode.replace("\n", "\\n") + "\"";
|
||||
}
|
||||
|
||||
codes.push(nextCode);
|
||||
}
|
||||
|
||||
str = str + " " + codes.join(" ");
|
||||
|
||||
out.push(str);
|
||||
}
|
||||
}
|
||||
|
||||
return out.join("\n");
|
||||
},
|
||||
|
||||
guid: 0,
|
||||
|
||||
compile: function(program, options) {
|
||||
this.children = [];
|
||||
this.depths = {list: []};
|
||||
this.options = options || {};
|
||||
return this.program(program);
|
||||
},
|
||||
|
||||
accept: function(node) {
|
||||
return this[node.type](node);
|
||||
},
|
||||
|
||||
program: function(program) {
|
||||
var statements = program.statements, statement;
|
||||
this.opcodes = [];
|
||||
|
||||
for(var i=0, l=statements.length; i<l; i++) {
|
||||
statement = statements[i];
|
||||
this[statement.type](statement);
|
||||
}
|
||||
|
||||
this.depths.list = this.depths.list.sort(function(a, b) {
|
||||
return a - b;
|
||||
});
|
||||
|
||||
return this;
|
||||
},
|
||||
|
||||
compileProgram: function(program) {
|
||||
var result = new this.compiler().compile(program, this.options);
|
||||
var guid = this.guid++;
|
||||
|
||||
this.usePartial = this.usePartial || result.usePartial;
|
||||
|
||||
this.children[guid] = result;
|
||||
|
||||
for(var i=0, l=result.depths.list.length; i<l; i++) {
|
||||
depth = result.depths.list[i];
|
||||
|
||||
if(depth < 2) { continue; }
|
||||
else { this.addDepth(depth - 1); }
|
||||
}
|
||||
|
||||
return guid;
|
||||
},
|
||||
|
||||
block: function(block) {
|
||||
var mustache = block.mustache;
|
||||
var depth, child, inverse, inverseGuid;
|
||||
|
||||
var params = this.setupStackForMustache(mustache);
|
||||
|
||||
var programGuid = this.compileProgram(block.program);
|
||||
|
||||
if(block.program.inverse) {
|
||||
inverseGuid = this.compileProgram(block.program.inverse);
|
||||
this.declare('inverse', inverseGuid);
|
||||
}
|
||||
|
||||
this.opcode('invokeProgram', programGuid, params.length);
|
||||
this.declare('inverse', null);
|
||||
this.opcode('append');
|
||||
},
|
||||
|
||||
inverse: function(block) {
|
||||
this.ID(block.mustache.id);
|
||||
var programGuid = this.compileProgram(block.program);
|
||||
|
||||
this.opcode('invokeInverse', programGuid);
|
||||
this.opcode('append');
|
||||
},
|
||||
|
||||
hash: function(hash) {
|
||||
var pairs = hash.pairs, pair, val;
|
||||
|
||||
this.opcode('push', '{}');
|
||||
|
||||
for(var i=0, l=pairs.length; i<l; i++) {
|
||||
pair = pairs[i];
|
||||
val = pair[1];
|
||||
|
||||
this.accept(val);
|
||||
this.opcode('assignToHash', pair[0]);
|
||||
}
|
||||
},
|
||||
|
||||
partial: function(partial) {
|
||||
var id = partial.id;
|
||||
this.usePartial = true;
|
||||
|
||||
if(partial.context) {
|
||||
this.ID(partial.context);
|
||||
} else {
|
||||
this.opcode('push', 'context');
|
||||
}
|
||||
|
||||
this.opcode('invokePartial', id.original);
|
||||
this.opcode('append');
|
||||
},
|
||||
|
||||
content: function(content) {
|
||||
this.opcode('appendContent', content.string);
|
||||
},
|
||||
|
||||
mustache: function(mustache) {
|
||||
var params = this.setupStackForMustache(mustache);
|
||||
|
||||
this.opcode('invokeMustache', params.length, mustache.id.original);
|
||||
|
||||
if(mustache.escaped) {
|
||||
this.opcode('appendEscaped');
|
||||
} else {
|
||||
this.opcode('append');
|
||||
}
|
||||
},
|
||||
|
||||
ID: function(id) {
|
||||
this.addDepth(id.depth);
|
||||
|
||||
this.opcode('getContext', id.depth);
|
||||
|
||||
this.opcode('lookupWithHelpers', id.parts[0] || null);
|
||||
|
||||
for(var i=1, l=id.parts.length; i<l; i++) {
|
||||
this.opcode('lookup', id.parts[i]);
|
||||
}
|
||||
},
|
||||
|
||||
STRING: function(string) {
|
||||
this.opcode('pushString', string.string);
|
||||
},
|
||||
|
||||
comment: function() {},
|
||||
|
||||
// HELPERS
|
||||
pushParams: function(params) {
|
||||
var i = params.length, param;
|
||||
|
||||
while(i--) {
|
||||
param = params[i];
|
||||
|
||||
if(this.options.stringParams) {
|
||||
if(param.depth) {
|
||||
this.addDepth(param.depth);
|
||||
}
|
||||
|
||||
this.opcode('getContext', param.depth || 0);
|
||||
this.opcode('pushStringParam', param.string);
|
||||
} else {
|
||||
this[param.type](param);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
opcode: function(name, val1, val2) {
|
||||
this.opcodes.push(Compiler.OPCODE_MAP[name]);
|
||||
if(val1 !== undefined) { this.opcodes.push(val1); }
|
||||
if(val2 !== undefined) { this.opcodes.push(val2); }
|
||||
},
|
||||
|
||||
declare: function(name, value) {
|
||||
this.opcodes.push('DECLARE');
|
||||
this.opcodes.push(name);
|
||||
this.opcodes.push(value);
|
||||
},
|
||||
|
||||
addDepth: function(depth) {
|
||||
if(depth === 0) { return; }
|
||||
|
||||
if(!this.depths[depth]) {
|
||||
this.depths[depth] = true;
|
||||
this.depths.list.push(depth);
|
||||
}
|
||||
},
|
||||
|
||||
setupStackForMustache: function(mustache) {
|
||||
var params = mustache.params;
|
||||
|
||||
this.pushParams(params);
|
||||
|
||||
if(mustache.hash) {
|
||||
this.hash(mustache.hash);
|
||||
} else {
|
||||
this.opcode('push', '{}');
|
||||
}
|
||||
|
||||
this.ID(mustache.id);
|
||||
|
||||
return params;
|
||||
}
|
||||
};
|
||||
|
||||
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.RESERVED_WORDS[name] || name.indexOf('-') !== -1) {
|
||||
return parent + "['" + name + "']";
|
||||
} else {
|
||||
return parent + "." + name;
|
||||
}
|
||||
},
|
||||
|
||||
appendToBuffer: function(string) {
|
||||
return "buffer = buffer + " + string + ";";
|
||||
},
|
||||
|
||||
initializeBuffer: function() {
|
||||
return this.quotedString("");
|
||||
},
|
||||
// END PUBLIC API
|
||||
|
||||
compile: function(environment, options) {
|
||||
this.environment = environment;
|
||||
this.options = options || {};
|
||||
|
||||
this.preamble();
|
||||
|
||||
this.stackSlot = 0;
|
||||
this.stackVars = [];
|
||||
this.registers = {list: []};
|
||||
|
||||
this.compileChildren(environment, options);
|
||||
|
||||
Handlebars.log(Handlebars.logger.DEBUG, environment.disassemble() + "\n\n");
|
||||
|
||||
var opcodes = environment.opcodes, opcode, name, declareName, declareVal;
|
||||
|
||||
this.i = 0;
|
||||
|
||||
for(l=opcodes.length; this.i<l; this.i++) {
|
||||
opcode = this.nextOpcode(0);
|
||||
|
||||
if(opcode[0] === 'DECLARE') {
|
||||
this.i = this.i + 2;
|
||||
this[opcode[1]] = opcode[2];
|
||||
} else {
|
||||
this.i = this.i + opcode[1].length;
|
||||
this[opcode[0]].apply(this, opcode[1]);
|
||||
}
|
||||
}
|
||||
|
||||
return this.createFunction();
|
||||
},
|
||||
|
||||
nextOpcode: function(n) {
|
||||
var opcodes = this.environment.opcodes, opcode = opcodes[this.i + n], name, val;
|
||||
var extraParams, codes;
|
||||
|
||||
if(opcode === 'DECLARE') {
|
||||
name = opcodes[this.i + 1];
|
||||
val = opcodes[this.i + 2];
|
||||
return ['DECLARE', name, val];
|
||||
} else {
|
||||
name = Compiler.DISASSEMBLE_MAP[opcode];
|
||||
|
||||
extraParams = Compiler.multiParamSize(opcode);
|
||||
codes = [];
|
||||
|
||||
for(var j=0; j<extraParams; j++) {
|
||||
codes.push(opcodes[this.i + j + 1 + n]);
|
||||
}
|
||||
|
||||
return [name, codes];
|
||||
}
|
||||
},
|
||||
|
||||
eat: function(opcode) {
|
||||
this.i = this.i + opcode.length;
|
||||
},
|
||||
|
||||
preamble: function() {
|
||||
var out = [];
|
||||
out.push("var buffer = " + this.initializeBuffer() + ", currentContext = context");
|
||||
|
||||
var copies = "helpers = helpers || Handlebars.helpers;";
|
||||
if(this.environment.usePartial) { copies = copies + " partials = partials || Handlebars.partials;"; }
|
||||
out.push(copies);
|
||||
|
||||
// track the last context pushed into place to allow skipping the
|
||||
// getContext opcode when it would be a noop
|
||||
this.lastContext = 0;
|
||||
this.source = out;
|
||||
},
|
||||
|
||||
createFunction: function() {
|
||||
var container = {
|
||||
escapeExpression: Handlebars.Utils.escapeExpression,
|
||||
invokePartial: Handlebars.VM.invokePartial,
|
||||
programs: [],
|
||||
program: function(i, helpers, partials, data) {
|
||||
var programWrapper = this.programs[i];
|
||||
if(data) {
|
||||
return Handlebars.VM.program(this.children[i], helpers, partials, data);
|
||||
} else if(programWrapper) {
|
||||
return programWrapper;
|
||||
} else {
|
||||
programWrapper = this.programs[i] = Handlebars.VM.program(this.children[i], helpers, partials);
|
||||
return programWrapper;
|
||||
}
|
||||
},
|
||||
programWithDepth: Handlebars.VM.programWithDepth,
|
||||
noop: Handlebars.VM.noop
|
||||
};
|
||||
var locals = this.stackVars.concat(this.registers.list);
|
||||
|
||||
if(locals.length > 0) {
|
||||
this.source[0] = this.source[0] + ", " + locals.join(", ");
|
||||
}
|
||||
|
||||
this.source[0] = this.source[0] + ";";
|
||||
|
||||
this.source.push("return buffer;");
|
||||
|
||||
var params = ["Handlebars", "context", "helpers", "partials"];
|
||||
|
||||
if(this.options.data) { params.push("data"); }
|
||||
|
||||
for(var i=0, l=this.environment.depths.list.length; i<l; i++) {
|
||||
params.push("depth" + this.environment.depths.list[i]);
|
||||
}
|
||||
|
||||
if(params.length === 4 && !this.environment.usePartial) { params.pop(); }
|
||||
|
||||
params.push(this.source.join("\n"));
|
||||
|
||||
var fn = Function.apply(this, params);
|
||||
fn.displayName = "Handlebars.js";
|
||||
|
||||
Handlebars.log(Handlebars.logger.DEBUG, fn.toString() + "\n\n");
|
||||
|
||||
container.render = fn;
|
||||
|
||||
container.children = this.environment.children;
|
||||
|
||||
return function(context, options, $depth) {
|
||||
try {
|
||||
options = options || {};
|
||||
var args = [Handlebars, context, options.helpers, options.partials, options.data];
|
||||
var depth = Array.prototype.slice.call(arguments, 2);
|
||||
args = args.concat(depth);
|
||||
return container.render.apply(container, args);
|
||||
} catch(e) {
|
||||
throw e;
|
||||
}
|
||||
};
|
||||
},
|
||||
|
||||
appendContent: function(content) {
|
||||
this.source.push(this.appendToBuffer(this.quotedString(content)));
|
||||
},
|
||||
|
||||
append: function() {
|
||||
var local = this.popStack();
|
||||
this.source.push("if(" + local + " || " + local + " === 0) { " + this.appendToBuffer(local) + " }");
|
||||
},
|
||||
|
||||
appendEscaped: function() {
|
||||
var opcode = this.nextOpcode(1), extra = "";
|
||||
|
||||
if(opcode[0] === 'appendContent') {
|
||||
extra = " + " + this.quotedString(opcode[1][0]);
|
||||
this.eat(opcode);
|
||||
}
|
||||
|
||||
this.source.push(this.appendToBuffer("this.escapeExpression(" + this.popStack() + ")" + extra));
|
||||
},
|
||||
|
||||
getContext: function(depth) {
|
||||
if(this.lastContext !== depth) {
|
||||
this.lastContext = depth;
|
||||
|
||||
if(depth === 0) {
|
||||
this.source.push("currentContext = context;");
|
||||
} else {
|
||||
this.source.push("currentContext = depth" + depth + ";");
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
lookupWithHelpers: function(name) {
|
||||
if(name) {
|
||||
var topStack = this.nextStack();
|
||||
|
||||
var toPush = "if('" + name + "' in helpers) { " + topStack +
|
||||
" = " + this.nameLookup('helpers', name, 'helper') +
|
||||
"; } else { " + topStack + " = " +
|
||||
this.nameLookup('currentContext', name, 'context') +
|
||||
"; }";
|
||||
|
||||
this.source.push(toPush);
|
||||
} else {
|
||||
this.pushStack("currentContext");
|
||||
}
|
||||
},
|
||||
|
||||
lookup: function(name) {
|
||||
var topStack = this.topStack();
|
||||
this.source.push(topStack + " = " + this.nameLookup(topStack, name, 'context') + ";");
|
||||
},
|
||||
|
||||
pushStringParam: function(string) {
|
||||
this.pushStack("currentContext");
|
||||
this.pushString(string);
|
||||
},
|
||||
|
||||
pushString: function(string) {
|
||||
this.pushStack(this.quotedString(string));
|
||||
},
|
||||
|
||||
push: function(name) {
|
||||
this.pushStack(name);
|
||||
},
|
||||
|
||||
invokeMustache: function(paramSize, original) {
|
||||
this.populateParams(paramSize, this.quotedString(original), "{}", null, function(nextStack, helperMissingString, id) {
|
||||
this.source.push("else if(" + id + "=== undefined) { " + nextStack + " = helpers.helperMissing.call(" + helperMissingString + "); }");
|
||||
this.source.push("else { " + nextStack + " = " + id + "; }");
|
||||
});
|
||||
},
|
||||
|
||||
invokeProgram: function(guid, paramSize) {
|
||||
var inverse = this.programExpression(this.inverse);
|
||||
var mainProgram = this.programExpression(guid);
|
||||
|
||||
this.populateParams(paramSize, null, mainProgram, inverse, function(nextStack, helperMissingString, id) {
|
||||
this.source.push("else { " + nextStack + " = helpers.blockHelperMissing.call(" + helperMissingString + "); }");
|
||||
});
|
||||
},
|
||||
|
||||
populateParams: function(paramSize, helperId, program, inverse, fn) {
|
||||
var id = this.popStack(), nextStack;
|
||||
var params = [], param, stringParam;
|
||||
|
||||
var hash = this.popStack();
|
||||
|
||||
this.register('tmp1', program);
|
||||
this.source.push('tmp1.hash = ' + hash + ';');
|
||||
|
||||
if(this.options.stringParams) {
|
||||
this.source.push('tmp1.contexts = [];');
|
||||
}
|
||||
|
||||
for(var i=0; i<paramSize; i++) {
|
||||
param = this.popStack();
|
||||
params.push(param);
|
||||
|
||||
if(this.options.stringParams) {
|
||||
this.source.push('tmp1.contexts.push(' + this.popStack() + ');');
|
||||
}
|
||||
}
|
||||
|
||||
if(inverse) {
|
||||
this.source.push('tmp1.fn = tmp1;');
|
||||
this.source.push('tmp1.inverse = ' + inverse + ';');
|
||||
}
|
||||
|
||||
if(this.options.data) {
|
||||
this.source.push('tmp1.data = data;');
|
||||
}
|
||||
|
||||
params.push('tmp1');
|
||||
|
||||
// TODO: This is legacy behavior. Deprecate and remove.
|
||||
if(inverse) {
|
||||
params.push(inverse);
|
||||
}
|
||||
|
||||
this.populateCall(params, id, helperId || id, fn);
|
||||
},
|
||||
|
||||
populateCall: function(params, id, helperId, fn) {
|
||||
var paramString = ["context"].concat(params).join(", ");
|
||||
var helperMissingString = ["context"].concat(helperId).concat(params).join(", ");
|
||||
|
||||
nextStack = this.nextStack();
|
||||
|
||||
this.source.push("if(typeof " + id + " === 'function') { " + nextStack + " = " + id + ".call(" + paramString + "); }");
|
||||
fn.call(this, nextStack, helperMissingString, id);
|
||||
},
|
||||
|
||||
invokeInverse: function(guid) {
|
||||
var program = this.programExpression(guid);
|
||||
|
||||
var blockMissingParams = ["context", this.topStack(), "this.noop", program];
|
||||
this.pushStack("helpers.blockHelperMissing.call(" + blockMissingParams.join(", ") + ")");
|
||||
},
|
||||
|
||||
invokePartial: function(context) {
|
||||
this.pushStack("this.invokePartial(" + this.nameLookup('partials', context, 'partial') + ", '" + context + "', " + this.popStack() + ", helpers, partials);");
|
||||
},
|
||||
|
||||
assignToHash: function(key) {
|
||||
var value = this.popStack();
|
||||
var hash = this.topStack();
|
||||
|
||||
this.source.push(hash + "['" + key + "'] = " + value + ";");
|
||||
},
|
||||
|
||||
// HELPERS
|
||||
|
||||
compiler: JavaScriptCompiler,
|
||||
|
||||
compileChildren: function(environment, options) {
|
||||
var children = environment.children, child, compiler;
|
||||
var compiled = [];
|
||||
|
||||
for(var i=0, l=children.length; i<l; i++) {
|
||||
child = children[i];
|
||||
compiler = new this.compiler();
|
||||
|
||||
compiled[i] = compiler.compile(child, options);
|
||||
}
|
||||
|
||||
environment.rawChildren = children;
|
||||
environment.children = compiled;
|
||||
},
|
||||
|
||||
programExpression: function(guid) {
|
||||
if(guid == null) { return "this.noop"; }
|
||||
|
||||
var programParams = [guid, "helpers", "partials"];
|
||||
|
||||
var depths = this.environment.rawChildren[guid].depths.list;
|
||||
|
||||
if(this.options.data) { programParams.push("data"); }
|
||||
|
||||
for(var i=0, l = depths.length; i<l; i++) {
|
||||
depth = depths[i];
|
||||
|
||||
if(depth === 1) { programParams.push("context"); }
|
||||
else { programParams.push("depth" + (depth - 1)); }
|
||||
}
|
||||
|
||||
if(!this.environment.usePartial) {
|
||||
if(programParams[3]) {
|
||||
programParams[2] = "null";
|
||||
} else {
|
||||
programParams.pop();
|
||||
}
|
||||
}
|
||||
|
||||
if(depths.length === 0) {
|
||||
return "this.program(" + programParams.join(", ") + ")";
|
||||
} else {
|
||||
programParams[0] = "this.children[" + guid + "]";
|
||||
return "this.programWithDepth(" + programParams.join(", ") + ")";
|
||||
}
|
||||
},
|
||||
|
||||
register: function(name, val) {
|
||||
this.useRegister(name);
|
||||
this.source.push(name + " = " + val + ";");
|
||||
},
|
||||
|
||||
useRegister: function(name) {
|
||||
if(!this.registers[name]) {
|
||||
this.registers[name] = true;
|
||||
this.registers.list.push(name);
|
||||
}
|
||||
},
|
||||
|
||||
pushStack: function(item) {
|
||||
this.source.push(this.nextStack() + " = " + item + ";");
|
||||
return "stack" + this.stackSlot;
|
||||
},
|
||||
|
||||
nextStack: function() {
|
||||
this.stackSlot++;
|
||||
if(this.stackSlot > this.stackVars.length) { this.stackVars.push("stack" + this.stackSlot); }
|
||||
return "stack" + this.stackSlot;
|
||||
},
|
||||
|
||||
popStack: function() {
|
||||
return "stack" + this.stackSlot--;
|
||||
},
|
||||
|
||||
topStack: function() {
|
||||
return "stack" + this.stackSlot;
|
||||
},
|
||||
|
||||
quotedString: function(str) {
|
||||
return '"' + str
|
||||
.replace(/\\/g, '\\\\')
|
||||
.replace(/"/g, '\\"')
|
||||
.replace(/\n/g, '\\n')
|
||||
.replace(/\r/g, '\\r') + '"';
|
||||
}
|
||||
};
|
||||
|
||||
var reservedWords = ("break case catch continue default delete do else finally " +
|
||||
"for function if in instanceof new return switch this throw " +
|
||||
"try typeof var void while with null true false").split(" ");
|
||||
|
||||
compilerWords = JavaScriptCompiler.RESERVED_WORDS = {};
|
||||
|
||||
for(var i=0, l=reservedWords.length; i<l; i++) {
|
||||
compilerWords[reservedWords[i]] = true;
|
||||
}
|
||||
|
||||
})(Handlebars.Compiler, Handlebars.JavaScriptCompiler);
|
||||
|
||||
Handlebars.VM = {
|
||||
programWithDepth: function(fn, passedHelpers, passedPartials, passedData, $depth) {
|
||||
var args = Array.prototype.slice.call(arguments, 4);
|
||||
|
||||
return function(context, helpers, partials, data) {
|
||||
var options = {
|
||||
helpers: helpers || passedHelpers,
|
||||
partials: partials || passedPartials,
|
||||
data: data || passedData
|
||||
};
|
||||
|
||||
return fn.apply(this, [context, options].concat(args));
|
||||
};
|
||||
},
|
||||
program: function(fn, helpers, partials, data) {
|
||||
return function(context, h2, p2, d2) {
|
||||
return fn(context, {helpers: h2 || helpers, partials: p2 || partials, data: d2 || data});
|
||||
};
|
||||
},
|
||||
noop: function() { return ""; },
|
||||
compile: function(string, options) {
|
||||
var ast = Handlebars.parse(string);
|
||||
var environment = new Handlebars.Compiler().compile(ast, options);
|
||||
return new Handlebars.JavaScriptCompiler().compile(environment, options);
|
||||
},
|
||||
invokePartial: function(partial, name, context, helpers, partials) {
|
||||
if(partial === undefined) {
|
||||
throw new Handlebars.Exception("The partial " + name + " could not be found");
|
||||
} else if(partial instanceof Function) {
|
||||
return partial(context, {helpers: helpers, partials: partials});
|
||||
} else {
|
||||
partials[name] = Handlebars.VM.compile(partial);
|
||||
return partials[name](context, {helpers: helpers, partials: partials});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Handlebars.compile = Handlebars.VM.compile;
|
||||
// END(BROWSER)
|
||||
|
||||
@@ -0,0 +1,138 @@
|
||||
exports.attach = function(Handlebars) {
|
||||
|
||||
// BEGIN(BROWSER)
|
||||
Handlebars.AST = {};
|
||||
|
||||
Handlebars.AST.ProgramNode = function(statements, inverse) {
|
||||
this.type = "program";
|
||||
this.statements = statements;
|
||||
if(inverse) { this.inverse = new Handlebars.AST.ProgramNode(inverse); }
|
||||
};
|
||||
|
||||
Handlebars.AST.MustacheNode = function(rawParams, hash, unescaped) {
|
||||
this.type = "mustache";
|
||||
this.escaped = !unescaped;
|
||||
this.hash = hash;
|
||||
|
||||
var id = this.id = rawParams[0];
|
||||
var params = this.params = rawParams.slice(1);
|
||||
|
||||
// a mustache is an eligible helper if:
|
||||
// * its id is simple (a single part, not `this` or `..`)
|
||||
var eligibleHelper = this.eligibleHelper = id.isSimple;
|
||||
|
||||
// a mustache is definitely a helper if:
|
||||
// * it is an eligible helper, and
|
||||
// * it has at least one parameter or hash segment
|
||||
this.isHelper = eligibleHelper && (params.length || hash);
|
||||
|
||||
// if a mustache is an eligible helper but not a definite
|
||||
// helper, it is ambiguous, and will be resolved in a later
|
||||
// pass or at runtime.
|
||||
};
|
||||
|
||||
Handlebars.AST.PartialNode = function(partialName, context) {
|
||||
this.type = "partial";
|
||||
this.partialName = partialName;
|
||||
this.context = context;
|
||||
};
|
||||
|
||||
Handlebars.AST.BlockNode = function(mustache, program, inverse, close) {
|
||||
var verifyMatch = function(open, close) {
|
||||
if(open.original !== close.original) {
|
||||
throw new Handlebars.Exception(open.original + " doesn't match " + close.original);
|
||||
}
|
||||
};
|
||||
|
||||
verifyMatch(mustache.id, close);
|
||||
this.type = "block";
|
||||
this.mustache = mustache;
|
||||
this.program = program;
|
||||
this.inverse = inverse;
|
||||
|
||||
if (this.inverse && !this.program) {
|
||||
this.isInverse = 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;
|
||||
};
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
var handlebars = require("./parser");
|
||||
|
||||
exports.attach = function(Handlebars) {
|
||||
|
||||
// BEGIN(BROWSER)
|
||||
|
||||
Handlebars.Parser = handlebars;
|
||||
|
||||
Handlebars.parse = function(input) {
|
||||
|
||||
// Just return if an already-compile AST was passed in.
|
||||
if(input.constructor === Handlebars.AST.ProgramNode) { return input; }
|
||||
|
||||
Handlebars.Parser.yy = Handlebars.AST;
|
||||
return Handlebars.Parser.parse(input);
|
||||
};
|
||||
|
||||
// END(BROWSER)
|
||||
|
||||
return Handlebars;
|
||||
};
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,16 @@
|
||||
// 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;
|
||||
|
||||
};
|
||||
@@ -1,7 +1,11 @@
|
||||
var Handlebars = require("handlebars");
|
||||
require("handlebars/visitor");
|
||||
exports.attach = function(Handlebars) {
|
||||
|
||||
// BEGIN(BROWSER)
|
||||
|
||||
Handlebars.print = function(ast) {
|
||||
return new Handlebars.PrintVisitor().accept(ast);
|
||||
};
|
||||
|
||||
Handlebars.PrintVisitor = function() { this.padding = 0; };
|
||||
Handlebars.PrintVisitor.prototype = new Handlebars.Visitor();
|
||||
|
||||
@@ -19,31 +23,17 @@ Handlebars.PrintVisitor.prototype.pad = function(string, newline) {
|
||||
};
|
||||
|
||||
Handlebars.PrintVisitor.prototype.program = function(program) {
|
||||
var out = this.pad("PROGRAM:"),
|
||||
var out = "",
|
||||
statements = program.statements,
|
||||
inverse = program.inverse,
|
||||
i, l;
|
||||
|
||||
this.padding++;
|
||||
|
||||
for(i=0, l=statements.length; i<l; i++) {
|
||||
out = out + this.accept(statements[i]);
|
||||
}
|
||||
|
||||
this.padding--;
|
||||
|
||||
if(inverse) {
|
||||
out = out + this.pad("{{^}}");
|
||||
|
||||
this.padding++;
|
||||
|
||||
for(i=0, l=inverse.statements.length; i<l; i++) {
|
||||
out = out + this.accept(inverse.statements[i]);
|
||||
}
|
||||
}
|
||||
|
||||
this.padding--;
|
||||
|
||||
return out;
|
||||
};
|
||||
|
||||
@@ -53,25 +43,25 @@ Handlebars.PrintVisitor.prototype.block = function(block) {
|
||||
out = out + this.pad("BLOCK:");
|
||||
this.padding++;
|
||||
out = out + this.accept(block.mustache);
|
||||
out = out + this.accept(block.program);
|
||||
if (block.program) {
|
||||
out = out + this.pad("PROGRAM:");
|
||||
this.padding++;
|
||||
out = out + this.accept(block.program);
|
||||
this.padding--;
|
||||
}
|
||||
if (block.inverse) {
|
||||
if (block.program) { this.padding++; }
|
||||
out = out + this.pad("{{^}}");
|
||||
this.padding++;
|
||||
out = out + this.accept(block.inverse);
|
||||
this.padding--;
|
||||
if (block.program) { this.padding--; }
|
||||
}
|
||||
this.padding--;
|
||||
|
||||
return out;
|
||||
};
|
||||
|
||||
Handlebars.PrintVisitor.prototype.inverse = function(block) {
|
||||
var out = "";
|
||||
|
||||
out = out + this.pad("INVERSE:");
|
||||
this.padding++;
|
||||
out = out + this.accept(block.mustache);
|
||||
out = out + this.accept(block.program);
|
||||
this.padding--;
|
||||
|
||||
return out;
|
||||
};
|
||||
|
||||
|
||||
Handlebars.PrintVisitor.prototype.mustache = function(mustache) {
|
||||
var params = mustache.params, paramStrings = [], hash;
|
||||
|
||||
@@ -87,7 +77,7 @@ Handlebars.PrintVisitor.prototype.mustache = function(mustache) {
|
||||
};
|
||||
|
||||
Handlebars.PrintVisitor.prototype.partial = function(partial) {
|
||||
var content = this.accept(partial.id);
|
||||
var content = this.accept(partial.partialName);
|
||||
if(partial.context) { content = content + " " + this.accept(partial.context); }
|
||||
return this.pad("{{> " + content + " }}");
|
||||
};
|
||||
@@ -109,6 +99,14 @@ Handlebars.PrintVisitor.prototype.STRING = function(string) {
|
||||
return '"' + string.string + '"';
|
||||
};
|
||||
|
||||
Handlebars.PrintVisitor.prototype.INTEGER = function(integer) {
|
||||
return "INTEGER{" + integer.integer + "}";
|
||||
};
|
||||
|
||||
Handlebars.PrintVisitor.prototype.BOOLEAN = function(bool) {
|
||||
return "BOOLEAN{" + bool.bool + "}";
|
||||
};
|
||||
|
||||
Handlebars.PrintVisitor.prototype.ID = function(id) {
|
||||
var path = id.parts.join("/");
|
||||
if(id.parts.length > 1) {
|
||||
@@ -118,6 +116,14 @@ Handlebars.PrintVisitor.prototype.ID = function(id) {
|
||||
}
|
||||
};
|
||||
|
||||
Handlebars.PrintVisitor.prototype.PARTIAL_NAME = function(partialName) {
|
||||
return "PARTIAL:" + partialName.name;
|
||||
};
|
||||
|
||||
Handlebars.PrintVisitor.prototype.DATA = function(data) {
|
||||
return "@" + this.accept(data.id);
|
||||
};
|
||||
|
||||
Handlebars.PrintVisitor.prototype.content = function(content) {
|
||||
return this.pad("CONTENT[ '" + content.string + "' ]");
|
||||
};
|
||||
@@ -127,4 +133,6 @@ Handlebars.PrintVisitor.prototype.comment = function(comment) {
|
||||
};
|
||||
// END(BROWSER)
|
||||
|
||||
exports.PrintVisitor = Handlebars.PrintVisitor;
|
||||
return Handlebars;
|
||||
};
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
var Handlebars = require("handlebars");
|
||||
exports.attach = function(Handlebars) {
|
||||
|
||||
// BEGIN(BROWSER)
|
||||
|
||||
@@ -9,5 +9,10 @@ Handlebars.Visitor.prototype = {
|
||||
return this[object.type](object);
|
||||
}
|
||||
};
|
||||
|
||||
// END(BROWSER)
|
||||
|
||||
return Handlebars;
|
||||
};
|
||||
|
||||
|
||||
@@ -1,29 +0,0 @@
|
||||
var Handlebars = require("handlebars");
|
||||
|
||||
// BEGIN(BROWSER)
|
||||
(function() {
|
||||
var classes = ["Lexer", "PrintVisitor", "Context", "Runtime", "Exception"];
|
||||
var prop;
|
||||
|
||||
for(var i=0, l=classes.length; i<l; i++) {
|
||||
var className = classes[i], klass = Handlebars[className];
|
||||
klass.displayName = "new Handlebars." + className;
|
||||
|
||||
for(prop in klass) {
|
||||
if(klass.hasOwnProperty(prop)) {
|
||||
klass[prop].displayName = "Handlebars." + className + "#" + prop;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for(prop in Handlebars.Utils) {
|
||||
if(Handlebars.Utils.hasOwnProperty(prop)) {
|
||||
Handlebars.Utils[prop].displayName = "Handlebars.Utils." + prop;
|
||||
}
|
||||
}
|
||||
|
||||
Handlebars.parse.displayName = "Handlebars.parse";
|
||||
Handlebars.print.displayName = "Handlebars.print";
|
||||
Handlebars.compile.displayName = "Handlebars.compile";
|
||||
})();
|
||||
// END(BROWSER)
|
||||
@@ -0,0 +1,106 @@
|
||||
exports.attach = function(Handlebars) {
|
||||
|
||||
// BEGIN(BROWSER)
|
||||
|
||||
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;
|
||||
|
||||
// END(BROWSER)
|
||||
|
||||
return Handlebars;
|
||||
|
||||
};
|
||||
@@ -0,0 +1,11 @@
|
||||
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
|
||||
+63
-38
@@ -1,9 +1,20 @@
|
||||
var Handlebars = require("handlebars");
|
||||
exports.attach = function(Handlebars) {
|
||||
|
||||
var toString = Object.prototype.toString;
|
||||
|
||||
// BEGIN(BROWSER)
|
||||
|
||||
var errorProps = ['description', 'fileName', 'lineNumber', 'message', 'name', 'number', 'stack'];
|
||||
|
||||
Handlebars.Exception = function(message) {
|
||||
this.message = 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) {
|
||||
@@ -13,46 +24,60 @@ Handlebars.SafeString.prototype.toString = function() {
|
||||
return this.string.toString();
|
||||
};
|
||||
|
||||
(function() {
|
||||
var escape = {
|
||||
"<": "<",
|
||||
">": ">"
|
||||
};
|
||||
var escape = {
|
||||
"&": "&",
|
||||
"<": "<",
|
||||
">": ">",
|
||||
'"': """,
|
||||
"'": "'",
|
||||
"`": "`"
|
||||
};
|
||||
|
||||
var badChars = /&(?!\w+;)|[<>]/g;
|
||||
var possible = /[&<>]/;
|
||||
var badChars = /[&<>"'`]/g;
|
||||
var possible = /[&<>"'`]/;
|
||||
|
||||
var escapeChar = function(chr) {
|
||||
return escape[chr] || "&"
|
||||
};
|
||||
var escapeChar = function(chr) {
|
||||
return escape[chr] || "&";
|
||||
};
|
||||
|
||||
Handlebars.Utils = {
|
||||
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 "";
|
||||
}
|
||||
|
||||
if(!possible.test(string)) { return string; }
|
||||
return string.replace(badChars, escapeChar);
|
||||
},
|
||||
|
||||
isEmpty: function(value) {
|
||||
if (typeof value === "undefined") {
|
||||
return true;
|
||||
} else if (value === null) {
|
||||
return true;
|
||||
} else if (value === false) {
|
||||
return true;
|
||||
} else if(Object.prototype.toString.call(value) === "[object Array]" && value.length === 0) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
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;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// END(BROWSER)
|
||||
|
||||
return Handlebars;
|
||||
};
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
{
|
||||
"name": "handlebars",
|
||||
"description": "Extension of the Mustache logicless template language",
|
||||
"version": "1.0.12",
|
||||
"homepage": "http://www.handlebarsjs.com/",
|
||||
"keywords": [
|
||||
"handlebars mustache template html"
|
||||
],
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git://github.com/wycats/handlebars.js.git"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.4.7"
|
||||
},
|
||||
"dependencies": {
|
||||
"optimist": "~0.3",
|
||||
"uglify-js": "~2.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"benchmark": "~1.0",
|
||||
"dust": "~0.3",
|
||||
"jison": "~0.3",
|
||||
"mocha": "*",
|
||||
"mustache": "~0.7.2"
|
||||
},
|
||||
"main": "lib/handlebars.js",
|
||||
"bin": {
|
||||
"handlebars": "bin/handlebars"
|
||||
},
|
||||
"scripts": {
|
||||
"test": "node_modules/.bin/mocha -u qunit spec/qunit_spec.js"
|
||||
},
|
||||
"optionalDependencies": {}
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
# Release Notes
|
||||
|
||||
## Development
|
||||
[Commits](https://github.com/wycats/handlebars.js/compare/v1.0.12...master)
|
||||
|
||||
## v1.0.12 / 1.0.0 - May 31 2013
|
||||
|
||||
- [#515](https://github.com/wycats/handlebars.js/issues/515) - Add node require extensions support ([@jjclark1982](https://github.com/jjclark1982))
|
||||
- [#517](https://github.com/wycats/handlebars.js/issues/517) - Fix amd precompiler output with directories ([@blessenm](https://github.com/blessenm))
|
||||
- [#433](https://github.com/wycats/handlebars.js/issues/433) - Add support for unicode ids
|
||||
- [#469](https://github.com/wycats/handlebars.js/issues/469) - Add support for `?` in ids
|
||||
- [#534](https://github.com/wycats/handlebars.js/issues/534) - Protect from object prototype modifications
|
||||
- [#519](https://github.com/wycats/handlebars.js/issues/519) - Fix partials with . name ([@jamesgorrie](https://github.com/jamesgorrie))
|
||||
- [#519](https://github.com/wycats/handlebars.js/issues/519) - Allow ID or strings in partial names
|
||||
- [#437](https://github.com/wycats/handlebars.js/issues/437) - Require matching brace counts in escaped expressions
|
||||
- Merge passed partials and helpers with global namespace values
|
||||
- Add support for complex ids in @data references
|
||||
- Docs updates
|
||||
|
||||
Compatibility notes:
|
||||
- The parser is now stricter on `{{{`, requiring that the end token be `}}}`. Templates that do not
|
||||
follow this convention should add the additional brace value.
|
||||
- Code that relies on global the namespace being muted when custom helpers or partials are passed will need to explicitly pass an `undefined` value for any helpers that should not be available.
|
||||
|
||||
[Commits](https://github.com/wycats/handlebars.js/compare/v1.0.11...v1.0.12)
|
||||
|
||||
## v1.0.11 / 1.0.0-rc4 - May 13 2013
|
||||
|
||||
- [#458](https://github.com/wycats/handlebars.js/issues/458) - Fix `./foo` syntax ([@jpfiset](https://github.com/jpfiset))
|
||||
- [#460](https://github.com/wycats/handlebars.js/issues/460) - Allow `:` in unescaped identifers ([@jpfiset](https://github.com/jpfiset))
|
||||
- [#471](https://github.com/wycats/handlebars.js/issues/471) - Create release notes (These!)
|
||||
- [#456](https://github.com/wycats/handlebars.js/issues/456) - Allow escaping of `\\`
|
||||
- [#211](https://github.com/wycats/handlebars.js/issues/211) - Fix exception in `escapeExpression`
|
||||
- [#375](https://github.com/wycats/handlebars.js/issues/375) - Escape unicode newlines
|
||||
- [#461](https://github.com/wycats/handlebars.js/issues/461) - Do not fail when compiling `""`
|
||||
- [#302](https://github.com/wycats/handlebars.js/issues/302) - Fix sanity check in knownHelpersOnly mode
|
||||
- [#369](https://github.com/wycats/handlebars.js/issues/369) - Allow registration of multiple helpers and partial by passing definition object
|
||||
- Add bower package declaration ([@DevinClark](https://github.com/DevinClark))
|
||||
- Add NuSpec package declaration ([@MikeMayer](https://github.com/MikeMayer))
|
||||
- Handle empty context in `with` ([@thejohnfreeman](https://github.com/thejohnfreeman))
|
||||
- Support custom template extensions in CLI ([@matteoagosti](https://github.com/matteoagosti))
|
||||
- Fix Rhino support ([@broady](https://github.com/broady))
|
||||
- Include contexts in string mode ([@leshill](https://github.com/leshill))
|
||||
- Return precompiled scripts when compiling to AMD ([@JamesMaroney](https://github.com/JamesMaroney))
|
||||
- Docs updates ([@iangreenleaf](https://github.com/iangreenleaf), [@gilesbowkett](https://github.com/gilesbowkett), [@utkarsh2012](https://github.com/utkarsh2012))
|
||||
- Fix `toString` handling under IE and browserify ([@tommydudebreaux](https://github.com/tommydudebreaux))
|
||||
- Add program metadata
|
||||
|
||||
[Commits](https://github.com/wycats/handlebars.js/compare/v1.0.10...v1.0.11)
|
||||
|
||||
## v1.0.10 - Node - Feb 27 2013
|
||||
|
||||
- [#428](https://github.com/wycats/handlebars.js/issues/428) - Fix incorrect rendering of nested programs
|
||||
- Fix exception message ([@tricknotes](https://github.com/tricknotes))
|
||||
- Added negative number literal support
|
||||
- Concert library to single IIFE
|
||||
- Add handlebars-source gemspec ([@machty](https://github.com/machty))
|
||||
|
||||
[Commits](https://github.com/wycats/handlebars.js/compare/v1.0.9...v1.0.10)
|
||||
|
||||
## v1.0.9 - Node - Feb 15 2013
|
||||
|
||||
- Added `Handlebars.create` API in node module for sandboxed instances ([@tommydudebreaux](https://github.com/tommydudebreaux))
|
||||
|
||||
[Commits](https://github.com/wycats/handlebars.js/compare/1.0.0-rc.3...v1.0.9)
|
||||
|
||||
## 1.0.0-rc3 - Browser - Feb 14 2013
|
||||
|
||||
- Prevent use of `this` or `..` in illogical place ([@leshill](https://github.com/leshill))
|
||||
- Allow AST passing for `parse`/`compile`/`precompile` ([@machty](https://github.com/machty))
|
||||
- Optimize generated output by inlining statements where possible
|
||||
- Check compiler version when evaluating templates
|
||||
- Package browser dist in npm package
|
||||
|
||||
[Commits](https://github.com/wycats/handlebars.js/compare/v1.0.8...1.0.0-rc.3)
|
||||
|
||||
## Prior Versions
|
||||
|
||||
When upgrading from the Handlebars 0.9 series, be aware that the
|
||||
signature for passing custom helpers or partials to templates has
|
||||
changed.
|
||||
|
||||
Instead of:
|
||||
|
||||
```js
|
||||
template(context, helpers, partials, [data])
|
||||
```
|
||||
|
||||
Use:
|
||||
|
||||
```js
|
||||
template(context, {helpers: helpers, partials: partials, data: data})
|
||||
```
|
||||
@@ -39,11 +39,11 @@ Module.new do
|
||||
end
|
||||
end
|
||||
|
||||
js_context["p"] = proc do |str|
|
||||
js_context["p"] = proc do |this, str|
|
||||
p str
|
||||
end
|
||||
|
||||
js_context["ok"] = proc do |ok, message|
|
||||
js_context["ok"] = proc do |this, ok, message|
|
||||
js_context["$$RSPEC1$$"] = ok
|
||||
|
||||
result = js_context.eval("!!$$RSPEC1$$")
|
||||
@@ -58,13 +58,14 @@ Module.new do
|
||||
assert result, message
|
||||
end
|
||||
|
||||
js_context["equals"] = proc do |first, second, message|
|
||||
js_context["equals"] = proc do |this, first, second, message|
|
||||
js_context["$$RSPEC1$$"] = first
|
||||
js_context["$$RSPEC2$$"] = second
|
||||
|
||||
result = js_context.eval("$$RSPEC1$$ == $$RSPEC2$$")
|
||||
|
||||
message ||= "#{first} did not == #{second}"
|
||||
additional_message = "#{first.inspect} did not == #{second.inspect}"
|
||||
message = message ? "#{message} (#{additional_message})" : additional_message
|
||||
|
||||
unless result
|
||||
backtrace = js_backtrace(js_context)
|
||||
@@ -76,11 +77,11 @@ Module.new do
|
||||
|
||||
js_context["equal"] = js_context["equals"]
|
||||
|
||||
js_context["module"] = proc do |name|
|
||||
js_context["suite"] = proc do |this, name|
|
||||
test_context.module(name)
|
||||
end
|
||||
|
||||
js_context["test"] = proc do |name, function|
|
||||
js_context["test"] = proc do |this, name, function|
|
||||
test_context.test(name, function)
|
||||
end
|
||||
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
{{foo}}
|
||||
@@ -0,0 +1 @@
|
||||
Hello, {{name}}!
|
||||
+232
-24
@@ -3,11 +3,13 @@ require "spec_helper"
|
||||
describe "Parser" do
|
||||
let(:handlebars) { @context["Handlebars"] }
|
||||
|
||||
def program(&block)
|
||||
before(:all) do
|
||||
@compiles = true
|
||||
end
|
||||
|
||||
def root(&block)
|
||||
ASTBuilder.build do
|
||||
program do
|
||||
instance_eval(&block)
|
||||
end
|
||||
instance_eval(&block)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -92,6 +94,14 @@ describe "Parser" do
|
||||
string.inspect
|
||||
end
|
||||
|
||||
def integer(string)
|
||||
"INTEGER{#{string}}"
|
||||
end
|
||||
|
||||
def boolean(string)
|
||||
"BOOLEAN{#{string}}"
|
||||
end
|
||||
|
||||
def hash(*pairs)
|
||||
"HASH{" + pairs.map {|k,v| "#{k}=#{v}" }.join(", ") + "}"
|
||||
end
|
||||
@@ -100,82 +110,147 @@ describe "Parser" do
|
||||
"ID:#{id}"
|
||||
end
|
||||
|
||||
def data(id)
|
||||
"@ID:#{id}"
|
||||
end
|
||||
|
||||
def partial_name(name)
|
||||
"PARTIAL:#{name}"
|
||||
end
|
||||
|
||||
def path(*parts)
|
||||
"PATH:#{parts.join("/")}"
|
||||
end
|
||||
end
|
||||
|
||||
it "parses simple mustaches" do
|
||||
ast_for("{{foo}}").should == program { mustache id("foo") }
|
||||
ast_for("{{foo}}").should == root { mustache id("foo") }
|
||||
ast_for("{{foo?}}").should == root { mustache id("foo?") }
|
||||
ast_for("{{foo_}}").should == root { mustache id("foo_") }
|
||||
ast_for("{{foo-}}").should == root { mustache id("foo-") }
|
||||
ast_for("{{foo:}}").should == root { mustache id("foo:") }
|
||||
end
|
||||
|
||||
it "parses simple mustaches with data" do
|
||||
ast_for("{{@foo}}").should == root { mustache data("foo") }
|
||||
end
|
||||
|
||||
it "parses mustaches with paths" do
|
||||
ast_for("{{foo/bar}}").should == program { mustache path("foo", "bar") }
|
||||
ast_for("{{foo/bar}}").should == root { mustache path("foo", "bar") }
|
||||
end
|
||||
|
||||
it "parses mustaches with this/foo" do
|
||||
ast_for("{{this/foo}}").should == program { mustache id("foo") }
|
||||
ast_for("{{this/foo}}").should == root { mustache id("foo") }
|
||||
end
|
||||
|
||||
it "parses mustaches with - in a path" do
|
||||
ast_for("{{foo-bar}}").should == program { mustache id("foo-bar") }
|
||||
ast_for("{{foo-bar}}").should == root { mustache id("foo-bar") }
|
||||
end
|
||||
|
||||
it "parses mustaches with parameters" do
|
||||
ast_for("{{foo bar}}").should == program { mustache id("foo"), [id("bar")] }
|
||||
ast_for("{{foo bar}}").should == root { mustache id("foo"), [id("bar")] }
|
||||
end
|
||||
|
||||
it "parses mustaches with hash arguments" do
|
||||
ast_for("{{foo bar=baz}}").should == program do
|
||||
mustache id("foo"), [], hash(["bar", "ID:baz"])
|
||||
ast_for("{{foo bar=baz}}").should == root do
|
||||
mustache id("foo"), [], hash(["bar", id("baz")])
|
||||
end
|
||||
|
||||
ast_for("{{foo bar=baz bat=bam}}").should == program do
|
||||
ast_for("{{foo bar=1}}").should == root do
|
||||
mustache id("foo"), [], hash(["bar", integer("1")])
|
||||
end
|
||||
|
||||
ast_for("{{foo bar=true}}").should == root do
|
||||
mustache id("foo"), [], hash(["bar", boolean("true")])
|
||||
end
|
||||
|
||||
ast_for("{{foo bar=false}}").should == root do
|
||||
mustache id("foo"), [], hash(["bar", boolean("false")])
|
||||
end
|
||||
|
||||
ast_for("{{foo bar=@baz}}").should == root do
|
||||
mustache id("foo"), [], hash(["bar", data("baz")])
|
||||
end
|
||||
|
||||
ast_for("{{foo bar=baz bat=bam}}").should == root do
|
||||
mustache id("foo"), [], hash(["bar", "ID:baz"], ["bat", "ID:bam"])
|
||||
end
|
||||
|
||||
ast_for("{{foo bar=baz bat=\"bam\"}}").should == program do
|
||||
ast_for("{{foo bar=baz bat=\"bam\"}}").should == root do
|
||||
mustache id("foo"), [], hash(["bar", "ID:baz"], ["bat", "\"bam\""])
|
||||
end
|
||||
|
||||
ast_for("{{foo omg bar=baz bat=\"bam\"}}").should == program do
|
||||
ast_for("{{foo bat='bam'}}").should == root do
|
||||
mustache id("foo"), [], hash(["bat", "\"bam\""])
|
||||
end
|
||||
|
||||
ast_for("{{foo omg bar=baz bat=\"bam\"}}").should == root do
|
||||
mustache id("foo"), [id("omg")], hash(["bar", id("baz")], ["bat", string("bam")])
|
||||
end
|
||||
|
||||
ast_for("{{foo omg bar=baz bat=\"bam\" baz=1}}").should == root do
|
||||
mustache id("foo"), [id("omg")], hash(["bar", id("baz")], ["bat", string("bam")], ["baz", integer("1")])
|
||||
end
|
||||
|
||||
ast_for("{{foo omg bar=baz bat=\"bam\" baz=true}}").should == root do
|
||||
mustache id("foo"), [id("omg")], hash(["bar", id("baz")], ["bat", string("bam")], ["baz", boolean("true")])
|
||||
end
|
||||
|
||||
ast_for("{{foo omg bar=baz bat=\"bam\" baz=false}}").should == root do
|
||||
mustache id("foo"), [id("omg")], hash(["bar", id("baz")], ["bat", string("bam")], ["baz", boolean("false")])
|
||||
end
|
||||
end
|
||||
|
||||
it "parses mustaches with string parameters" do
|
||||
ast_for("{{foo bar \"baz\" }}").should == program { mustache id("foo"), [id("bar"), string("baz")] }
|
||||
ast_for("{{foo bar \"baz\" }}").should == root { mustache id("foo"), [id("bar"), string("baz")] }
|
||||
end
|
||||
|
||||
it "parses mustaches with INTEGER parameters" do
|
||||
ast_for("{{foo 1}}").should == root { mustache id("foo"), [integer("1")] }
|
||||
end
|
||||
|
||||
it "parses mustaches with BOOLEAN parameters" do
|
||||
ast_for("{{foo true}}").should == root { mustache id("foo"), [boolean("true")] }
|
||||
ast_for("{{foo false}}").should == root { mustache id("foo"), [boolean("false")] }
|
||||
end
|
||||
|
||||
it "parses mutaches with DATA parameters" do
|
||||
ast_for("{{foo @bar}}").should == root { mustache id("foo"), [data("bar")] }
|
||||
end
|
||||
|
||||
it "parses contents followed by a mustache" do
|
||||
ast_for("foo bar {{baz}}").should == program do
|
||||
ast_for("foo bar {{baz}}").should == root do
|
||||
content "foo bar "
|
||||
mustache id("baz")
|
||||
end
|
||||
end
|
||||
|
||||
it "parses a partial" do
|
||||
ast_for("{{> foo }}").should == program { partial id("foo") }
|
||||
ast_for("{{> foo }}").should == root { partial partial_name("foo") }
|
||||
end
|
||||
|
||||
it "parses a partial with context" do
|
||||
ast_for("{{> foo bar}}").should == program { partial id("foo"), id("bar") }
|
||||
ast_for("{{> foo bar}}").should == root { partial partial_name("foo"), id("bar") }
|
||||
end
|
||||
|
||||
it "parses a partial with a complex name" do
|
||||
ast_for("{{> shared/partial?.bar}}").should == root { partial partial_name("shared/partial?.bar") }
|
||||
end
|
||||
|
||||
it "parses a comment" do
|
||||
ast_for("{{! this is a comment }}").should == program do
|
||||
ast_for("{{! this is a comment }}").should == root do
|
||||
comment " this is a comment "
|
||||
end
|
||||
end
|
||||
|
||||
it "parses a multi-line comment" do
|
||||
ast_for("{{!\nthis is a multi-line comment\n}}").should == program do
|
||||
ast_for("{{!\nthis is a multi-line comment\n}}").should == root do
|
||||
multiline_comment "this is a multi-line comment"
|
||||
end
|
||||
end
|
||||
|
||||
it "parses an inverse section" do
|
||||
ast_for("{{#foo}} bar {{^}} baz {{/foo}}").should == program do
|
||||
ast_for("{{#foo}} bar {{^}} baz {{/foo}}").should == root do
|
||||
block do
|
||||
mustache id("foo")
|
||||
|
||||
@@ -190,12 +265,136 @@ describe "Parser" do
|
||||
end
|
||||
end
|
||||
|
||||
it "parses a standalone inverse section" do
|
||||
ast_for("{{^foo}}bar{{/foo}}").should == program do
|
||||
inverted_block do
|
||||
it "parses an inverse ('else'-style) section" do
|
||||
ast_for("{{#foo}} bar {{else}} baz {{/foo}}").should == root do
|
||||
block do
|
||||
mustache id("foo")
|
||||
|
||||
program do
|
||||
content " bar "
|
||||
end
|
||||
|
||||
inverse do
|
||||
content " baz "
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
it "parses empty blocks" do
|
||||
ast_for("{{#foo}}{{/foo}}").should == root do
|
||||
block do
|
||||
mustache id("foo")
|
||||
|
||||
program do
|
||||
# empty program
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
it "parses empty blocks with empty inverse section" do
|
||||
ast_for("{{#foo}}{{^}}{{/foo}}").should == root do
|
||||
block do
|
||||
mustache id("foo")
|
||||
|
||||
program do
|
||||
# empty program
|
||||
end
|
||||
|
||||
inverse do
|
||||
# empty inverse
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
it "parses empty blocks with empty inverse ('else'-style) section" do
|
||||
ast_for("{{#foo}}{{else}}{{/foo}}").should == root do
|
||||
block do
|
||||
mustache id("foo")
|
||||
|
||||
program do
|
||||
# empty program
|
||||
end
|
||||
|
||||
inverse do
|
||||
# empty inverse
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
it "parses non-empty blocks with empty inverse section" do
|
||||
ast_for("{{#foo}} bar {{^}}{{/foo}}").should == root do
|
||||
block do
|
||||
mustache id("foo")
|
||||
|
||||
program do
|
||||
content " bar "
|
||||
end
|
||||
|
||||
inverse do
|
||||
# empty inverse
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
it "parses non-empty blocks with empty inverse ('else'-style) section" do
|
||||
ast_for("{{#foo}} bar {{else}}{{/foo}}").should == root do
|
||||
block do
|
||||
mustache id("foo")
|
||||
|
||||
program do
|
||||
content " bar "
|
||||
end
|
||||
|
||||
inverse do
|
||||
# empty inverse
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
it "parses empty blocks with non-empty inverse section" do
|
||||
ast_for("{{#foo}}{{^}} bar {{/foo}}").should == root do
|
||||
block do
|
||||
mustache id("foo")
|
||||
|
||||
program do
|
||||
# empty program
|
||||
end
|
||||
|
||||
inverse do
|
||||
content " bar "
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
it "parses empty blocks with non-empty inverse ('else'-style) section" do
|
||||
ast_for("{{#foo}}{{else}} bar {{/foo}}").should == root do
|
||||
block do
|
||||
mustache id("foo")
|
||||
|
||||
program do
|
||||
# empty program
|
||||
end
|
||||
|
||||
inverse do
|
||||
content " bar "
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
it "parses a standalone inverse section" do
|
||||
ast_for("{{^foo}}bar{{/foo}}").should == root do
|
||||
block do
|
||||
mustache id("foo")
|
||||
|
||||
inverse do
|
||||
content "bar"
|
||||
end
|
||||
end
|
||||
@@ -205,6 +404,7 @@ describe "Parser" do
|
||||
it "raises if there's a Parse error" do
|
||||
lambda { ast_for("{{foo}") }.should raise_error(V8::JSError, /Parse error on line 1/)
|
||||
lambda { ast_for("{{foo &}}")}.should raise_error(V8::JSError, /Parse error on line 1/)
|
||||
lambda { ast_for("{{#goodbyes}}{{/hellos}}") }.should raise_error(V8::JSError, /goodbyes doesn't match hellos/)
|
||||
end
|
||||
|
||||
it "knows how to report the correct line number in errors" do
|
||||
@@ -215,4 +415,12 @@ describe "Parser" do
|
||||
it "knows how to report the correct line number in errors when the first character is a newline" do
|
||||
lambda { ast_for("\n\nhello\n\nmy\n\n{{foo}") }.should raise_error(V8::JSError, /Parse error on line 7/m)
|
||||
end
|
||||
|
||||
context "externally compiled AST" do
|
||||
it "can pass through an already-compiled AST" do
|
||||
ast_for(@context.eval('new Handlebars.AST.ProgramNode([ new Handlebars.AST.ContentNode("Hello")]);')).should == root do
|
||||
content "Hello"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
+1094
-223
File diff suppressed because it is too large
Load Diff
+47
-21
@@ -44,24 +44,18 @@ module Handlebars
|
||||
("\n" * prelines) + ret
|
||||
end
|
||||
|
||||
def self.js_load(file)
|
||||
str = File.read(file)
|
||||
CONTEXT.eval(remove_exports(str), file)
|
||||
end
|
||||
|
||||
CONTEXT = V8::Context.new
|
||||
CONTEXT.instance_eval do |context|
|
||||
def self.load_helpers(context)
|
||||
context["exports"] = nil
|
||||
|
||||
context["p"] = proc do |val|
|
||||
context["p"] = proc do |this, val|
|
||||
p val if ENV["DEBUG_JS"]
|
||||
end
|
||||
|
||||
context["puts"] = proc do |val|
|
||||
context["puts"] = proc do |this, val|
|
||||
puts val if ENV["DEBUG_JS"]
|
||||
end
|
||||
|
||||
context["puts_node"] = proc do |val|
|
||||
context["puts_node"] = proc do |this, val|
|
||||
puts context["Handlebars"]["PrintVisitor"].new.accept(val)
|
||||
puts
|
||||
end
|
||||
@@ -71,19 +65,50 @@ module Handlebars
|
||||
puts Handlebars::Spec.js_backtrace(context)
|
||||
puts
|
||||
end
|
||||
end
|
||||
|
||||
Handlebars::Spec.js_load('lib/handlebars/parser.js')
|
||||
Handlebars::Spec.js_load('lib/handlebars/base.js');
|
||||
Handlebars::Spec.js_load('lib/handlebars/ast.js');
|
||||
Handlebars::Spec.js_load('lib/handlebars/visitor.js');
|
||||
Handlebars::Spec.js_load('lib/handlebars/printer.js')
|
||||
Handlebars::Spec.js_load('lib/handlebars/utils.js')
|
||||
Handlebars::Spec.js_load('lib/handlebars/compiler.js')
|
||||
Handlebars::Spec.js_load('lib/handlebars.js')
|
||||
def self.js_load(context, file)
|
||||
str = File.read(file)
|
||||
context.eval(remove_exports(str), file)
|
||||
end
|
||||
|
||||
CONTEXT = V8::Context.new
|
||||
CONTEXT.instance_eval do |context|
|
||||
Handlebars::Spec.load_helpers(context);
|
||||
|
||||
Handlebars::Spec.js_load(context, 'dist/handlebars.js');
|
||||
|
||||
context["CompilerContext"] = {}
|
||||
CompilerContext = context["CompilerContext"]
|
||||
CompilerContext["compile"] = proc do |this, *args|
|
||||
template, options = args[0], args[1] || nil
|
||||
templateSpec = COMPILE_CONTEXT["Handlebars"]["precompile"].call(template, options);
|
||||
context["Handlebars"]["template"].call(context.eval("(#{templateSpec})"));
|
||||
end
|
||||
CompilerContext["compileWithPartial"] = proc do |this, *args|
|
||||
template, options = args[0], args[1] || nil
|
||||
context["Handlebars"]["compile"].call(template, options);
|
||||
end
|
||||
end
|
||||
|
||||
PARSER_CONTEXT = V8::Context.new
|
||||
PARSER_CONTEXT.instance_eval do |context|
|
||||
Handlebars::Spec.load_helpers(context);
|
||||
|
||||
Handlebars::Spec.js_load(context, 'lib/handlebars/compiler/parser.js');
|
||||
end
|
||||
|
||||
COMPILE_CONTEXT = V8::Context.new
|
||||
COMPILE_CONTEXT.instance_eval do |context|
|
||||
Handlebars::Spec.load_helpers(context);
|
||||
|
||||
Handlebars::Spec.js_load(context, 'dist/handlebars.js');
|
||||
Handlebars::Spec.js_load(context, 'lib/handlebars/compiler/visitor.js');
|
||||
Handlebars::Spec.js_load(context, 'lib/handlebars/compiler/printer.js');
|
||||
|
||||
context["Handlebars"]["logger"]["level"] = ENV["DEBUG_JS"] ? context["Handlebars"]["logger"][ENV["DEBUG_JS"]] : 4
|
||||
|
||||
context["Handlebars"]["logger"]["log"] = proc do |level, str|
|
||||
context["Handlebars"]["logger"]["log"] = proc do |this, level, str|
|
||||
logger_level = context["Handlebars"]["logger"]["level"].to_i
|
||||
|
||||
if logger_level <= level
|
||||
@@ -100,7 +125,8 @@ require "test/unit/assertions"
|
||||
RSpec.configure do |config|
|
||||
config.include Test::Unit::Assertions
|
||||
|
||||
config.before(:all) do
|
||||
@context = Handlebars::Spec::CONTEXT
|
||||
# Each is required to allow classes to mark themselves as compiler tests
|
||||
config.before(:each) do
|
||||
@context = @compiles ? Handlebars::Spec::COMPILE_CONTEXT : Handlebars::Spec::CONTEXT
|
||||
end
|
||||
end
|
||||
|
||||
+132
-3
@@ -2,8 +2,8 @@ require "spec_helper"
|
||||
require "timeout"
|
||||
|
||||
describe "Tokenizer" do
|
||||
let(:parser) { @context["handlebars"] }
|
||||
let(:lexer) { @context["handlebars"]["lexer"] }
|
||||
let(:parser) { Handlebars::Spec::PARSER_CONTEXT["handlebars"] }
|
||||
let(:lexer) { Handlebars::Spec::PARSER_CONTEXT["handlebars"]["lexer"] }
|
||||
|
||||
Token = Struct.new(:name, :text)
|
||||
|
||||
@@ -41,6 +41,44 @@ describe "Tokenizer" do
|
||||
result[1].should be_token("ID", "foo")
|
||||
end
|
||||
|
||||
it "supports unescaping with &" do
|
||||
result = tokenize("{{&bar}}")
|
||||
result.should match_tokens(%w(OPEN ID CLOSE))
|
||||
|
||||
result[0].should be_token("OPEN", "{{&")
|
||||
result[1].should be_token("ID", "bar")
|
||||
end
|
||||
|
||||
it "supports unescaping with {{{" do
|
||||
result = tokenize("{{{bar}}}")
|
||||
result.should match_tokens(%w(OPEN_UNESCAPED ID CLOSE_UNESCAPED))
|
||||
|
||||
result[1].should be_token("ID", "bar")
|
||||
end
|
||||
|
||||
it "supports escaping delimiters" do
|
||||
result = tokenize("{{foo}} \\{{bar}} {{baz}}")
|
||||
result.should match_tokens(%w(OPEN ID CLOSE CONTENT CONTENT OPEN ID CLOSE))
|
||||
|
||||
result[4].should be_token("CONTENT", "{{bar}} ")
|
||||
end
|
||||
|
||||
it "supports escaping multiple delimiters" do
|
||||
result = tokenize("{{foo}} \\{{bar}} \\{{baz}}")
|
||||
result.should match_tokens(%w(OPEN ID CLOSE CONTENT CONTENT CONTENT))
|
||||
|
||||
result[3].should be_token("CONTENT", " ")
|
||||
result[4].should be_token("CONTENT", "{{bar}} ")
|
||||
result[5].should be_token("CONTENT", "{{baz}}")
|
||||
end
|
||||
|
||||
it "supports escaping a triple stash" do
|
||||
result = tokenize("{{foo}} \\{{{bar}}} {{baz}}")
|
||||
result.should match_tokens(%w(OPEN ID CLOSE CONTENT CONTENT OPEN ID CLOSE))
|
||||
|
||||
result[4].should be_token("CONTENT", "{{{bar}}} ")
|
||||
end
|
||||
|
||||
it "tokenizes a simple path" do
|
||||
result = tokenize("{{foo/bar}}")
|
||||
result.should match_tokens(%w(OPEN ID SEP ID CLOSE))
|
||||
@@ -53,6 +91,16 @@ describe "Tokenizer" do
|
||||
tokenize("{{foo.bar.baz}}").should match_tokens(%w(OPEN ID SEP ID SEP ID CLOSE))
|
||||
end
|
||||
|
||||
it "allows path literals with []" do
|
||||
result = tokenize("{{foo.[bar]}}")
|
||||
result.should match_tokens(%w(OPEN ID SEP ID CLOSE))
|
||||
end
|
||||
|
||||
it "allows multiple path literals on a line with []" do
|
||||
result = tokenize("{{foo.[bar]}}{{foo.[baz]}}")
|
||||
result.should match_tokens(%w(OPEN ID SEP ID CLOSE OPEN ID SEP ID CLOSE))
|
||||
end
|
||||
|
||||
it "tokenizes {{.}} as OPEN ID CLOSE" do
|
||||
result = tokenize("{{.}}")
|
||||
result.should match_tokens(%w(OPEN ID CLOSE))
|
||||
@@ -77,12 +125,18 @@ describe "Tokenizer" do
|
||||
result[3].should be_token("ID", "foo")
|
||||
end
|
||||
|
||||
it "tokenizes a simple mustahe with spaces as 'OPEN ID CLOSE'" do
|
||||
it "tokenizes a simple mustache with spaces as 'OPEN ID CLOSE'" do
|
||||
result = tokenize("{{ foo }}")
|
||||
result.should match_tokens(%w(OPEN ID CLOSE))
|
||||
result[1].should be_token("ID", "foo")
|
||||
end
|
||||
|
||||
it "tokenizes a simple mustache with line breaks as 'OPEN ID ID CLOSE'" do
|
||||
result = tokenize("{{ foo \n bar }}")
|
||||
result.should match_tokens(%w(OPEN ID ID CLOSE))
|
||||
result[1].should be_token("ID", "foo")
|
||||
end
|
||||
|
||||
it "tokenizes raw content as 'CONTENT'" do
|
||||
result = tokenize("foo {{ bar }} baz")
|
||||
result.should match_tokens(%w(CONTENT OPEN ID CLOSE CONTENT))
|
||||
@@ -110,12 +164,29 @@ describe "Tokenizer" do
|
||||
result.should match_tokens(%w(OPEN_PARTIAL ID CLOSE))
|
||||
end
|
||||
|
||||
it "tokenizes a partial space at the end as 'OPEN_PARTIAL ID CLOSE'" do
|
||||
result = tokenize("{{>foo/bar.baz }}")
|
||||
result.should match_tokens(%w(OPEN_PARTIAL ID SEP ID SEP ID CLOSE))
|
||||
end
|
||||
|
||||
it "tokenizes a comment as 'COMMENT'" do
|
||||
result = tokenize("foo {{! this is a comment }} bar {{ baz }}")
|
||||
result.should match_tokens(%w(CONTENT COMMENT CONTENT OPEN ID CLOSE))
|
||||
result[1].should be_token("COMMENT", " this is a comment ")
|
||||
end
|
||||
|
||||
it "tokenizes a block comment as 'COMMENT'" do
|
||||
result = tokenize("foo {{!-- this is a {{comment}} --}} bar {{ baz }}")
|
||||
result.should match_tokens(%w(CONTENT COMMENT CONTENT OPEN ID CLOSE))
|
||||
result[1].should be_token("COMMENT", " this is a {{comment}} ")
|
||||
end
|
||||
|
||||
it "tokenizes a block comment with whitespace as 'COMMENT'" do
|
||||
result = tokenize("foo {{!-- this is a\n{{comment}}\n--}} bar {{ baz }}")
|
||||
result.should match_tokens(%w(CONTENT COMMENT CONTENT OPEN ID CLOSE))
|
||||
result[1].should be_token("COMMENT", " this is a\n{{comment}}\n")
|
||||
end
|
||||
|
||||
it "tokenizes open and closing blocks as 'OPEN_BLOCK ID CLOSE ... OPEN_ENDBLOCK ID CLOSE'" do
|
||||
result = tokenize("{{#foo}}content{{/foo}}")
|
||||
result.should match_tokens(%w(OPEN_BLOCK ID CLOSE CONTENT OPEN_ENDBLOCK ID CLOSE))
|
||||
@@ -153,6 +224,12 @@ describe "Tokenizer" do
|
||||
result[3].should be_token("STRING", "baz")
|
||||
end
|
||||
|
||||
it "tokenizes mustaches with String params using single quotes as 'OPEN ID ID STRING CLOSE'" do
|
||||
result = tokenize("{{ foo bar \'baz\' }}")
|
||||
result.should match_tokens(%w(OPEN ID ID STRING CLOSE))
|
||||
result[3].should be_token("STRING", "baz")
|
||||
end
|
||||
|
||||
it "tokenizes String params with spaces inside as 'STRING'" do
|
||||
result = tokenize("{{ foo bar \"baz bat\" }}")
|
||||
result.should match_tokens(%w(OPEN ID ID STRING CLOSE))
|
||||
@@ -165,6 +242,32 @@ describe "Tokenizer" do
|
||||
result[2].should be_token("STRING", %{bar"baz})
|
||||
end
|
||||
|
||||
it "tokenizes String params using single quotes with escapes quotes as 'STRING'" do
|
||||
result = tokenize(%|{{ foo 'bar\\'baz' }}|)
|
||||
result.should match_tokens(%w(OPEN ID STRING CLOSE))
|
||||
result[2].should be_token("STRING", %{bar'baz})
|
||||
end
|
||||
|
||||
it "tokenizes numbers" do
|
||||
result = tokenize(%|{{ foo 1 }}|)
|
||||
result.should match_tokens(%w(OPEN ID INTEGER CLOSE))
|
||||
result[2].should be_token("INTEGER", "1")
|
||||
|
||||
result = tokenize(%|{{ foo -1 }}|)
|
||||
result.should match_tokens(%w(OPEN ID INTEGER CLOSE))
|
||||
result[2].should be_token("INTEGER", "-1")
|
||||
end
|
||||
|
||||
it "tokenizes booleans" do
|
||||
result = tokenize(%|{{ foo true }}|)
|
||||
result.should match_tokens(%w(OPEN ID BOOLEAN CLOSE))
|
||||
result[2].should be_token("BOOLEAN", "true")
|
||||
|
||||
result = tokenize(%|{{ foo false }}|)
|
||||
result.should match_tokens(%w(OPEN ID BOOLEAN CLOSE))
|
||||
result[2].should be_token("BOOLEAN", "false")
|
||||
end
|
||||
|
||||
it "tokenizes hash arguments" do
|
||||
result = tokenize("{{ foo bar=baz }}")
|
||||
result.should match_tokens %w(OPEN ID ID EQUALS ID CLOSE)
|
||||
@@ -172,6 +275,18 @@ describe "Tokenizer" do
|
||||
result = tokenize("{{ foo bar baz=bat }}")
|
||||
result.should match_tokens %w(OPEN ID ID ID EQUALS ID CLOSE)
|
||||
|
||||
result = tokenize("{{ foo bar baz=1 }}")
|
||||
result.should match_tokens %w(OPEN ID ID ID EQUALS INTEGER CLOSE)
|
||||
|
||||
result = tokenize("{{ foo bar baz=true }}")
|
||||
result.should match_tokens %w(OPEN ID ID ID EQUALS BOOLEAN CLOSE)
|
||||
|
||||
result = tokenize("{{ foo bar baz=false }}")
|
||||
result.should match_tokens %w(OPEN ID ID ID EQUALS BOOLEAN CLOSE)
|
||||
|
||||
result = tokenize("{{ foo bar\n baz=bat }}")
|
||||
result.should match_tokens %w(OPEN ID ID ID EQUALS ID CLOSE)
|
||||
|
||||
result = tokenize("{{ foo bar baz=\"bat\" }}")
|
||||
result.should match_tokens %w(OPEN ID ID ID EQUALS STRING CLOSE)
|
||||
|
||||
@@ -183,6 +298,20 @@ describe "Tokenizer" do
|
||||
result[2].should be_token("ID", "omg")
|
||||
end
|
||||
|
||||
it "tokenizes special @ identifiers" do
|
||||
result = tokenize("{{ @foo }}")
|
||||
result.should match_tokens %w( OPEN DATA ID CLOSE )
|
||||
result[2].should be_token("ID", "foo")
|
||||
|
||||
result = tokenize("{{ foo @bar }}")
|
||||
result.should match_tokens %w( OPEN ID DATA ID CLOSE )
|
||||
result[3].should be_token("ID", "bar")
|
||||
|
||||
result = tokenize("{{ foo bar=@baz }}")
|
||||
result.should match_tokens %w( OPEN ID ID EQUALS DATA ID CLOSE )
|
||||
result[5].should be_token("ID", "baz")
|
||||
end
|
||||
|
||||
it "does not time out in a mustache with a single } followed by EOF" do
|
||||
Timeout.timeout(1) { tokenize("{{foo}").should match_tokens(%w(OPEN ID)) }
|
||||
end
|
||||
|
||||
+54
-22
@@ -1,31 +1,63 @@
|
||||
|
||||
%x mu
|
||||
%x mu emu com
|
||||
|
||||
%%
|
||||
|
||||
[^\x00]*?/("{{") { this.begin("mu"); if (yytext) return 'CONTENT'; }
|
||||
"\\\\"/("{{") { yytext = "\\"; return 'CONTENT'; }
|
||||
[^\x00]*?/("{{") {
|
||||
if(yytext.slice(-1) !== "\\") this.begin("mu");
|
||||
if(yytext.slice(-1) === "\\") yytext = yytext.substr(0,yyleng-1), this.begin("emu");
|
||||
if(yytext) return 'CONTENT';
|
||||
}
|
||||
|
||||
[^\x00]+ { return 'CONTENT'; }
|
||||
|
||||
<mu>"{{>" { return 'OPEN_PARTIAL'; }
|
||||
<mu>"{{#" { return 'OPEN_BLOCK'; }
|
||||
<mu>"{{/" { return 'OPEN_ENDBLOCK'; }
|
||||
<mu>"{{^" { return 'OPEN_INVERSE'; }
|
||||
<mu>"{{"\s*"else" { return 'OPEN_INVERSE'; }
|
||||
<mu>"{{{" { return 'OPEN_UNESCAPED'; }
|
||||
<mu>"{{&" { return 'OPEN_UNESCAPED'; }
|
||||
<mu>"{{!"[\s\S]*?"}}" { yytext = yytext.substr(3,yyleng-5); this.begin("INITIAL"); return 'COMMENT'; }
|
||||
<mu>"{{" { return 'OPEN'; }
|
||||
<emu>[^\x00]{2,}?/("{{"|<<EOF>>) {
|
||||
if(yytext.slice(-1) !== "\\") this.popState();
|
||||
if(yytext.slice(-1) === "\\") yytext = yytext.substr(0,yyleng-1);
|
||||
return 'CONTENT';
|
||||
}
|
||||
|
||||
<mu>"=" { return 'EQUALS'; }
|
||||
<mu>"."/[} ] { return 'ID'; }
|
||||
<mu>".." { return 'ID'; }
|
||||
<mu>[/.] { return 'SEP'; }
|
||||
<mu>\s+ { /*ignore whitespace*/ }
|
||||
<mu>"}}}" { this.begin("INITIAL"); return 'CLOSE'; }
|
||||
<mu>"}}" { this.begin("INITIAL"); return 'CLOSE'; }
|
||||
<mu>'"'("\\"["]|[^"])*'"' { yytext = yytext.substr(1,yyleng-2).replace(/\\"/g,'"'); return 'STRING'; }
|
||||
<mu>[a-zA-Z0-9_-]+/[=} /.] { return 'ID'; }
|
||||
<mu>. { return 'INVALID'; }
|
||||
<com>[\s\S]*?"--}}" { yytext = yytext.substr(0, yyleng-4); this.popState(); return 'COMMENT'; }
|
||||
|
||||
<INITIAL,mu><<EOF>> { return 'EOF'; }
|
||||
<mu>"{{>" { return 'OPEN_PARTIAL'; }
|
||||
<mu>"{{#" { return 'OPEN_BLOCK'; }
|
||||
<mu>"{{/" { return 'OPEN_ENDBLOCK'; }
|
||||
<mu>"{{^" { return 'OPEN_INVERSE'; }
|
||||
<mu>"{{"\s*"else" { return 'OPEN_INVERSE'; }
|
||||
<mu>"{{{" { return 'OPEN_UNESCAPED'; }
|
||||
<mu>"{{&" { return 'OPEN'; }
|
||||
<mu>"{{!--" { this.popState(); this.begin('com'); }
|
||||
<mu>"{{!"[\s\S]*?"}}" { yytext = yytext.substr(3,yyleng-5); this.popState(); return 'COMMENT'; }
|
||||
<mu>"{{" { return 'OPEN'; }
|
||||
|
||||
<mu>"=" { return 'EQUALS'; }
|
||||
<mu>"."/[}\/ ] { return 'ID'; }
|
||||
<mu>".." { return 'ID'; }
|
||||
<mu>[\/.] { return 'SEP'; }
|
||||
<mu>\s+ { /*ignore whitespace*/ }
|
||||
<mu>"}}}" { this.popState(); return 'CLOSE_UNESCAPED'; }
|
||||
<mu>"}}" { this.popState(); return 'CLOSE'; }
|
||||
<mu>'"'("\\"["]|[^"])*'"' { yytext = yytext.substr(1,yyleng-2).replace(/\\"/g,'"'); return 'STRING'; }
|
||||
<mu>"'"("\\"[']|[^'])*"'" { yytext = yytext.substr(1,yyleng-2).replace(/\\'/g,"'"); return 'STRING'; }
|
||||
<mu>"@" { return 'DATA'; }
|
||||
<mu>"true"/[}\s] { return 'BOOLEAN'; }
|
||||
<mu>"false"/[}\s] { return 'BOOLEAN'; }
|
||||
<mu>\-?[0-9]+/[}\s] { return 'INTEGER'; }
|
||||
|
||||
/*
|
||||
ID is the inverse of control characters.
|
||||
Control characters ranges:
|
||||
[\s] Whitespace
|
||||
[!"#%-,\./] !, ", #, %, &, ', (, ), *, +, ,, ., /, Exceptions in range: $, -
|
||||
[;->@] ;, <, =, >, @, Exceptions in range: :, ?
|
||||
[\[-\^`] [, \, ], ^, `, Exceptions in range: _
|
||||
[\{-~] {, |, }, ~
|
||||
*/
|
||||
<mu>[^\s!"#%-,\.\/;->@\[-\^`\{-~]+/[=}\s\/.] { return 'ID'; }
|
||||
|
||||
<mu>'['[^\]]*']' { yytext = yytext.substr(1, yyleng-2); return 'ID'; }
|
||||
<mu>. { return 'INVALID'; }
|
||||
|
||||
<INITIAL,mu><<EOF>> { return 'EOF'; }
|
||||
|
||||
|
||||
+57
-34
@@ -3,50 +3,56 @@
|
||||
%%
|
||||
|
||||
root
|
||||
: program EOF { return $1 }
|
||||
: program EOF { return $1; }
|
||||
;
|
||||
|
||||
program
|
||||
: statements simpleInverse statements { $$ = new yy.ProgramNode($1, $3) }
|
||||
| statements { $$ = new yy.ProgramNode($1) }
|
||||
| "" { $$ = new yy.ProgramNode([]) }
|
||||
: simpleInverse statements { $$ = new yy.ProgramNode([], $2); }
|
||||
| statements simpleInverse statements { $$ = new yy.ProgramNode($1, $3); }
|
||||
| statements simpleInverse { $$ = new yy.ProgramNode($1, []); }
|
||||
| statements { $$ = new yy.ProgramNode($1); }
|
||||
| simpleInverse { $$ = new yy.ProgramNode([], []); }
|
||||
| "" { $$ = new yy.ProgramNode([]); }
|
||||
;
|
||||
|
||||
statements
|
||||
: statement { $$ = [$1] }
|
||||
| statements statement { $1.push($2); $$ = $1 }
|
||||
: statement { $$ = [$1]; }
|
||||
| statements statement { $1.push($2); $$ = $1; }
|
||||
;
|
||||
|
||||
statement
|
||||
: openInverse program closeBlock { $$ = new yy.InverseNode($1, $2, $3) }
|
||||
| openBlock program closeBlock { $$ = new yy.BlockNode($1, $2, $3) }
|
||||
| mustache { $$ = $1 }
|
||||
| partial { $$ = $1 }
|
||||
| CONTENT { $$ = new yy.ContentNode($1) }
|
||||
| COMMENT { $$ = new yy.CommentNode($1) }
|
||||
: openInverse program closeBlock { $$ = new yy.BlockNode($1, $2.inverse, $2, $3); }
|
||||
| openBlock program closeBlock { $$ = new yy.BlockNode($1, $2, $2.inverse, $3); }
|
||||
| mustache { $$ = $1; }
|
||||
| partial { $$ = $1; }
|
||||
| CONTENT { $$ = new yy.ContentNode($1); }
|
||||
| COMMENT { $$ = new yy.CommentNode($1); }
|
||||
;
|
||||
|
||||
openBlock
|
||||
: OPEN_BLOCK inMustache CLOSE { $$ = new yy.MustacheNode($2[0], $2[1]) }
|
||||
: OPEN_BLOCK inMustache CLOSE { $$ = new yy.MustacheNode($2[0], $2[1]); }
|
||||
;
|
||||
|
||||
openInverse
|
||||
: OPEN_INVERSE inMustache CLOSE { $$ = new yy.MustacheNode($2[0], $2[1]) }
|
||||
: OPEN_INVERSE inMustache CLOSE { $$ = new yy.MustacheNode($2[0], $2[1]); }
|
||||
;
|
||||
|
||||
closeBlock
|
||||
: OPEN_ENDBLOCK path CLOSE { $$ = $2 }
|
||||
: OPEN_ENDBLOCK path CLOSE { $$ = $2; }
|
||||
;
|
||||
|
||||
mustache
|
||||
: OPEN inMustache CLOSE { $$ = new yy.MustacheNode($2[0], $2[1]) }
|
||||
| OPEN_UNESCAPED inMustache CLOSE { $$ = new yy.MustacheNode($2[0], $2[1], true) }
|
||||
: OPEN inMustache CLOSE {
|
||||
// Parsing out the '&' escape token at this level saves ~500 bytes after min due to the removal of one parser node.
|
||||
$$ = new yy.MustacheNode($2[0], $2[1], $1[2] === '&');
|
||||
}
|
||||
| OPEN_UNESCAPED inMustache CLOSE_UNESCAPED { $$ = new yy.MustacheNode($2[0], $2[1], true); }
|
||||
;
|
||||
|
||||
|
||||
partial
|
||||
: OPEN_PARTIAL path CLOSE { $$ = new yy.PartialNode($2) }
|
||||
| OPEN_PARTIAL path path CLOSE { $$ = new yy.PartialNode($2, $3) }
|
||||
: OPEN_PARTIAL partialName CLOSE { $$ = new yy.PartialNode($2); }
|
||||
| OPEN_PARTIAL partialName path CLOSE { $$ = new yy.PartialNode($2, $3); }
|
||||
;
|
||||
|
||||
simpleInverse
|
||||
@@ -54,42 +60,59 @@ simpleInverse
|
||||
;
|
||||
|
||||
inMustache
|
||||
: path params hash { $$ = [[$1].concat($2), $3] }
|
||||
| path params { $$ = [[$1].concat($2), null] }
|
||||
| path hash { $$ = [[$1], $2] }
|
||||
| path { $$ = [[$1], null] }
|
||||
: path params hash { $$ = [[$1].concat($2), $3]; }
|
||||
| path params { $$ = [[$1].concat($2), null]; }
|
||||
| path hash { $$ = [[$1], $2]; }
|
||||
| path { $$ = [[$1], null]; }
|
||||
| dataName { $$ = [[$1], null]; }
|
||||
;
|
||||
|
||||
params
|
||||
: params param { $1.push($2); $$ = $1; }
|
||||
| param { $$ = [$1] }
|
||||
| param { $$ = [$1]; }
|
||||
;
|
||||
|
||||
param
|
||||
: path { $$ = $1 }
|
||||
| STRING { $$ = new yy.StringNode($1) }
|
||||
: path { $$ = $1; }
|
||||
| STRING { $$ = new yy.StringNode($1); }
|
||||
| INTEGER { $$ = new yy.IntegerNode($1); }
|
||||
| BOOLEAN { $$ = new yy.BooleanNode($1); }
|
||||
| dataName { $$ = $1; }
|
||||
;
|
||||
|
||||
hash
|
||||
: hashSegments { $$ = new yy.HashNode($1) }
|
||||
: hashSegments { $$ = new yy.HashNode($1); }
|
||||
;
|
||||
|
||||
hashSegments
|
||||
: hashSegments hashSegment { $1.push($2); $$ = $1 }
|
||||
| hashSegment { $$ = [$1] }
|
||||
: hashSegments hashSegment { $1.push($2); $$ = $1; }
|
||||
| hashSegment { $$ = [$1]; }
|
||||
;
|
||||
|
||||
hashSegment
|
||||
: ID EQUALS path { $$ = [$1, $3] }
|
||||
| ID EQUALS STRING { $$ = [$1, new yy.StringNode($3)] }
|
||||
: ID EQUALS path { $$ = [$1, $3]; }
|
||||
| ID EQUALS STRING { $$ = [$1, new yy.StringNode($3)]; }
|
||||
| ID EQUALS INTEGER { $$ = [$1, new yy.IntegerNode($3)]; }
|
||||
| ID EQUALS BOOLEAN { $$ = [$1, new yy.BooleanNode($3)]; }
|
||||
| ID EQUALS dataName { $$ = [$1, $3]; }
|
||||
;
|
||||
|
||||
partialName
|
||||
: path { $$ = new yy.PartialNameNode($1); }
|
||||
| STRING { $$ = new yy.PartialNameNode(new yy.StringNode($1)); }
|
||||
| INTEGER { $$ = new yy.PartialNameNode(new yy.IntegerNode($1)); }
|
||||
;
|
||||
|
||||
dataName
|
||||
: DATA path { $$ = new yy.DataNode($2); }
|
||||
;
|
||||
|
||||
path
|
||||
: pathSegments { $$ = new yy.IdNode($1) }
|
||||
: pathSegments { $$ = new yy.IdNode($1); }
|
||||
;
|
||||
|
||||
pathSegments
|
||||
: pathSegments SEP ID { $1.push($3); $$ = $1; }
|
||||
| ID { $$ = [$1] }
|
||||
: pathSegments SEP ID { $1.push({part: $3, separator: $2}); $$ = $1; }
|
||||
| ID { $$ = [{part: $1}]; }
|
||||
;
|
||||
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
// BEGIN(BROWSER)
|
||||
@@ -0,0 +1,4 @@
|
||||
|
||||
// END(BROWSER)
|
||||
|
||||
module.exports = handlebars;
|
||||
Reference in New Issue
Block a user