Compare commits
613 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| ca0d385a6c | |||
| 520e1d5f08 | |||
| 02423780a9 | |||
| be92d2f254 | |||
| 443a613b3a | |||
| 83ee5908f2 | |||
| 8dc3d2517b | |||
| 668c4fb878 | |||
| c65c6cce3f | |||
| 3d3796c1e9 | |||
| 075b354a3b | |||
| 30dbf04781 | |||
| e3a54485db | |||
| 8e23642ea2 | |||
| 88ac06875f | |||
| c68bc08a0d | |||
| 6cfbc2653a | |||
| b65135acef | |||
| e2f63da5c0 | |||
| 78e7e28ff9 | |||
| 03d387bf8e | |||
| e0f50b4eec | |||
| 9ed9418488 | |||
| ef0fc290b9 | |||
| edc65b5c19 | |||
| 715f4af179 | |||
| 3bd0fa8b32 | |||
| c295ef085f | |||
| c1ad3c8057 | |||
| af92e32822 | |||
| 2954e7ea66 | |||
| 8eefee56ff | |||
| fd93073146 | |||
| a9a8e40321 | |||
| e66aed5b99 | |||
| 7d4d170ce4 | |||
| eb860c0899 | |||
| b6d3de7123 | |||
| f058970169 | |||
| 77825f8d35 | |||
| 3789a30955 | |||
| e6ad93ea01 | |||
| 2bf4fc6fd3 | |||
| b64202bc91 | |||
| c2f1e62031 | |||
| 08e9a11a34 | |||
| 1fd2edee2a | |||
| 3c9c2f5cf2 | |||
| 16487a088e | |||
| 309d2b49a1 | |||
| 645ac73844 | |||
| b454b02147 | |||
| 7adc19ab40 | |||
| 9dd8d10e12 | |||
| 4671c4b383 | |||
| e46baa1fe2 | |||
| c491b4ea25 | |||
| 738391a064 | |||
| 80c4516fda | |||
| d79212a662 | |||
| b440c38886 | |||
| 2dba7eee3f | |||
| c9789691af | |||
| 9278f217e0 | |||
| d78cc73d3c | |||
| 4de51fe26b | |||
| a32d05f2fc | |||
| ad63f5189f | |||
| 586e672c8b | |||
| f0c6c4cc1f | |||
| a4fd391ba1 | |||
| 9d5aa363cf | |||
| 14ba3d0c43 | |||
| 4cddfe7017 | |||
| f152dfc892 | |||
| 3c1e252169 | |||
| 0d5c807017 | |||
| 1f0834b1a2 | |||
| 575d8772e2 | |||
| 7af1c12db6 | |||
| 91a1b5d2f4 | |||
| 770d746e60 | |||
| d7f0dcf2bb | |||
| 187d611e8c | |||
| d337f40d0e | |||
| d03b6ecfc4 | |||
| 164b7ff0de | |||
| ac4655ee95 | |||
| 14b621caf5 | |||
| 1ec1737d24 | |||
| 1ebce2b53c | |||
| 3a5b65e02b | |||
| dde108e283 | |||
| dc5495216d | |||
| d1fb07b32b | |||
| 04b19848cc | |||
| c40d9f33ad | |||
| 8901c28e9a | |||
| e97685e989 | |||
| e913dc5f12 | |||
| 1f61f21250 | |||
| 587e7a3e63 | |||
| edcc84f292 | |||
| 23d58e79bb | |||
| c819c8b533 | |||
| 827c9d0747 | |||
| f7f05d7558 | |||
| 1988878087 | |||
| 886ba86c2f | |||
| 0817dad7e7 | |||
| 93516a0b07 | |||
| 93e284ed9b | |||
| c02b05fa81 | |||
| 8de121d21c | |||
| 6914090086 | |||
| d54137810a | |||
| c2ac79c970 | |||
| 7ef86173ab | |||
| b75e3e1f40 | |||
| 5e9d17f8fa | |||
| b24797da01 | |||
| a243067883 | |||
| 088e61812a | |||
| 7052e88068 | |||
| b8913fcc65 | |||
| 62ed3c25c7 | |||
| 7fcf9d24f8 | |||
| c76ded8f0f | |||
| a15d01d383 | |||
| 8e1cce7918 | |||
| def79fd5d6 | |||
| fd2e5c983d | |||
| 8d5530ee2c | |||
| 2ab261eab7 | |||
| 143ac806da | |||
| f1752fe66f | |||
| aaab6099f2 | |||
| feb60f85c9 | |||
| 0b593bfe5d | |||
| 2e53fba68f | |||
| ba570c42cf | |||
| 0440af2147 | |||
| b793350fec | |||
| 26d0f7a80d | |||
| b7eada0149 | |||
| b8e769fcb6 | |||
| 9cb31653a4 | |||
| c53f340374 | |||
| 059b330579 | |||
| 8f6047cdfd | |||
| cf7545ef5a | |||
| c958cc8955 | |||
| b250b2d53a | |||
| ff4d827c09 | |||
| e4738491b3 | |||
| 54f7e11b28 | |||
| 8742bde701 | |||
| 2357140c68 | |||
| c5cbeac039 | |||
| 213c0bbe3c | |||
| 050cca0866 | |||
| 2d5579078b | |||
| e3639e2407 | |||
| c2a17c7b30 | |||
| 1266838829 | |||
| 64ecb9ea84 | |||
| 93444c58db | |||
| d148d7cb1f | |||
| a89081d440 | |||
| bba65605b1 | |||
| 06b7224ed9 | |||
| 2078c727c6 | |||
| fff3e40402 | |||
| 35bcd4405f | |||
| 00b4f2ff98 | |||
| e47b5ec192 | |||
| c55a7bed78 | |||
| 164c7ceea4 | |||
| 6ab48d8def | |||
| 8ac20285f6 | |||
| 888750ec27 | |||
| 133b96a2ff | |||
| f119497312 | |||
| 62b64ecc3d | |||
| a57b6824e0 | |||
| a26633f204 | |||
| f9cce4dd02 | |||
| 16572cd848 | |||
| f98c6a5425 | |||
| fde409ace6 | |||
| 9aaace76dc | |||
| 5eb59a5302 | |||
| 10b5fcf92e | |||
| dd0144c771 | |||
| 594f1e30dd | |||
| 871acccbb1 | |||
| cd38583216 | |||
| c454d946e3 | |||
| 9cfb5ddad9 | |||
| be44246221 | |||
| 002561be94 | |||
| 3fb6687013 | |||
| f691db546e | |||
| 25b2e11dd9 | |||
| e5c39375ac | |||
| aef72870b6 | |||
| 684f1032c6 | |||
| 7840ab66a5 | |||
| 4108b8330b | |||
| 445ae12fa4 | |||
| 5cedd62d7b | |||
| 40fb115e53 | |||
| b2e2cfe56d | |||
| 037bfbf4d9 | |||
| 048f2ce7d2 | |||
| b92589a3b0 | |||
| 1c62d4c5d0 | |||
| 7caca944b1 | |||
| 7bd34fb466 | |||
| b02e9a25ee | |||
| f1c8b2e2a2 | |||
| dbc50ac705 | |||
| c6a8fc1c04 | |||
| 42841c41a4 | |||
| 56fc6768d1 | |||
| ee3022228b | |||
| 05e6293bb3 | |||
| 2db0d123c8 | |||
| edc6220d51 | |||
| bacd473fe6 | |||
| 27ac1ee396 | |||
| 78dd89c13a | |||
| 6b87c21fc4 | |||
| 8d22e6f501 | |||
| 3c970cc9c1 | |||
| abba3c7526 | |||
| 4bf1c4ff66 | |||
| 41b6a11d11 | |||
| 2d28f920b0 | |||
| 29b174468d | |||
| d130ed2bc1 | |||
| 2145c14994 | |||
| 8359722e5d | |||
| a1d864d4a7 | |||
| 0ddff8b388 | |||
| 288e986161 | |||
| 30df8a1ac7 | |||
| cda544bca9 | |||
| 69c6ca528d | |||
| a4e39bdfd0 | |||
| b86b9189fa | |||
| d3d39423a3 | |||
| 73d5637564 | |||
| 7729aa956b | |||
| 8947dd077c | |||
| 1e954ddf3c | |||
| 1ac131e652 | |||
| 59548b4bdc | |||
| 21386b6474 | |||
| 79309659e1 | |||
| 5b76f041b3 | |||
| 1ed163f4ae | |||
| 670ec6fafb | |||
| 2e935df8bf | |||
| 0e953d1db5 | |||
| 5ec78a8c70 | |||
| 5333f316c3 | |||
| 8a836e2272 | |||
| cc554a5813 | |||
| ed879a6068 | |||
| 2e21e2bc9a | |||
| bdfdbea09c | |||
| b50ef03823 | |||
| 6e6269fcd7 | |||
| 7378f854c3 | |||
| fed5818876 | |||
| 0e81f0a082 | |||
| a00c598266 | |||
| 606fa55b0a | |||
| 8e09f0ee4e | |||
| c8f4b570c1 | |||
| b617375219 | |||
| 63a8e0caa2 | |||
| 5a164d0ca5 | |||
| 01b0f656bb | |||
| 406f2ee01f | |||
| a023cb4dd9 | |||
| c7dc353956 | |||
| ad3037cf54 | |||
| 44ce5f40aa | |||
| 3385de0921 | |||
| abc160ae8e | |||
| 5ee91cb58a | |||
| ef9e0dc2ab | |||
| 0d0c4d1bbd | |||
| 8ff49cef52 | |||
| 1b885d496a | |||
| 45e14b7b45 | |||
| 1379e8f50c | |||
| 6272c15074 | |||
| 959ee557b0 | |||
| 577b760b50 | |||
| 8c19874497 | |||
| 8fc16367b0 | |||
| 400916c225 | |||
| fee23342b1 | |||
| 32d6363841 | |||
| 20c965cd65 | |||
| 6c9f98c025 | |||
| c393c81561 | |||
| 205c61cfb1 | |||
| ddaff8ea29 | |||
| 685cf92bcb | |||
| 7a6c228592 | |||
| 0a3b3c2831 | |||
| 251ec3b128 | |||
| c21118d04b | |||
| 0f00f31f6b | |||
| 1dc417f1a5 | |||
| 9f59de9657 | |||
| 3406b95cfd | |||
| 7b48a284e0 | |||
| 98a6717e9e | |||
| b7c95e9feb | |||
| b1e561ee26 | |||
| 690ae2edc7 | |||
| 496ed8a25f | |||
| d20f02d10d | |||
| 33b53ef989 | |||
| f08d48764d | |||
| 9365b82900 | |||
| bf091777a9 | |||
| 94c840b6ed | |||
| 861d6f7b8d | |||
| fffb5a985f | |||
| 08781798f5 | |||
| 641fe335df | |||
| 733b7c8752 | |||
| 9a86e40970 | |||
| efad390ffc | |||
| 3696d23752 | |||
| a5a3ab01d3 | |||
| c83354101e | |||
| 7b1fdf814c | |||
| c7b28a65da | |||
| 9a27693fae | |||
| 2d9a5151e8 | |||
| 3934b3ab92 | |||
| 05b82a203e | |||
| f71f4e759e | |||
| 6e9198b207 | |||
| cc8ed7a9e7 | |||
| 454d20e3b2 | |||
| 0aef72cb8e | |||
| bff5fab8f9 | |||
| f97b99e0cd | |||
| 6c07367cd6 | |||
| 83b8e846a3 | |||
| b0d217e13d | |||
| c13c7df73d | |||
| b63d74a7b3 | |||
| e7a64f018c | |||
| 0f5061e445 | |||
| 7c896a074d | |||
| e70430dd2e | |||
| 9d4353c35c | |||
| c727e1f97c | |||
| 6c45f49b24 | |||
| 495cd05a7e | |||
| 452afbf2ff | |||
| 408192ba9f | |||
| 2a4a5447f5 | |||
| 1c274088c1 | |||
| 0b5e82e1c3 | |||
| 233caf3f7c | |||
| 91ffd32cad | |||
| 2571dd8e8e | |||
| 729d9fdffe | |||
| a634cd3219 | |||
| 95d84badca | |||
| 9a2d1d6009 | |||
| 08093d72f0 | |||
| 958273c2e6 | |||
| 1c2b74e537 | |||
| b85cad8df6 | |||
| d21de5d569 | |||
| 519fac5081 | |||
| 624a4c71aa | |||
| 3ce04ddd23 | |||
| ea3a5a1eb6 | |||
| 269dd492bb | |||
| 2ae0ef6eef | |||
| 06d515a89d | |||
| 0de8dac702 | |||
| d716fd01c5 | |||
| 77e6bfc5a1 | |||
| 00f74420f9 | |||
| a62cbad95a | |||
| ba31ef8ae4 | |||
| a44fe470b4 | |||
| bb9831b73e | |||
| c532b89af6 | |||
| 72eb10ca4b | |||
| bd643ce12b | |||
| 93b07605cd | |||
| 85750f8d0a | |||
| fac71ce700 | |||
| 324d615726 | |||
| c3cbaa25a4 | |||
| 9b1f9c7e44 | |||
| 8e868ab225 | |||
| 5d4b8da344 | |||
| 279e038ba7 | |||
| 1c08771215 | |||
| 9f265b9761 | |||
| 0aa54f49de | |||
| 1f11cc0186 | |||
| 9a49d35023 | |||
| b664997dc3 | |||
| ac82842cb6 | |||
| efddc3c09c | |||
| f3e8b18925 | |||
| 15b55a307b | |||
| 231a8d7256 | |||
| e2ba22eaad | |||
| 1bb640be41 | |||
| 2a851067b9 | |||
| 410141c31e | |||
| 060c087e62 | |||
| 5acf77a99a | |||
| fe057168ae | |||
| c83c363a58 | |||
| aa7a45b443 | |||
| a458fe26f4 | |||
| 2f9495c1df | |||
| b9fe7ce618 | |||
| 868ef4b309 | |||
| 8c09b3c896 | |||
| 9bbc177fd5 | |||
| 93faffa549 | |||
| d2fb3a4906 | |||
| d0805e9cfa | |||
| 2c1d509c6c | |||
| 569f288551 | |||
| d0d2168ec2 | |||
| 8cba84df11 | |||
| 00bfdd782f | |||
| afd595bd6e | |||
| 891f48b7e9 | |||
| 0bf79ea5c0 | |||
| 972f521718 | |||
| 4a40fc8e2f | |||
| fc13400b6f | |||
| 6ecf509385 | |||
| d945089565 | |||
| 2f0b86665f | |||
| d48e840513 | |||
| 4bed826d0e | |||
| 0263aa48bc | |||
| ecf60ab1bf | |||
| c6ab044fa8 | |||
| fdc94207a3 | |||
| e3d3eda2e1 | |||
| 2a02261a5b | |||
| a009a97a2e | |||
| 54e743a09a | |||
| a7160a8d27 | |||
| dfd141cd35 | |||
| 37a664bf64 | |||
| c699d0b82f | |||
| 2d149e7797 | |||
| 81a4d50955 | |||
| e15af4c849 | |||
| afe730e059 | |||
| 40894e619b | |||
| 1d401a2abc | |||
| a10d907a65 | |||
| fed86c01ad | |||
| ab96073c6b | |||
| 64ab232d72 | |||
| 8c357b9243 | |||
| 71690ae39d | |||
| 3c9ff77e68 | |||
| 5ede36b8fd | |||
| c15f90e513 | |||
| 0099e16a01 | |||
| a8848d0af9 | |||
| ca6152d2f7 | |||
| 55dc82d938 | |||
| 39121cf8f5 | |||
| 07f27843dc | |||
| 5cc326d425 | |||
| f857471cc8 | |||
| fd8484798c | |||
| 810d86d474 | |||
| cfbef2585d | |||
| 95421271e3 | |||
| 752a28f24c | |||
| 05bda7ed0c | |||
| 1f6a94424d | |||
| 8626fa8bbe | |||
| 9f8daf9076 | |||
| 884bf15536 | |||
| 999da739a6 | |||
| d567d9c345 | |||
| cb51b82b8e | |||
| b0b522b4f8 | |||
| 3cf47abe19 | |||
| b7488acf89 | |||
| c9d723e0fb | |||
| c5fe2527b3 | |||
| 9c40c07941 | |||
| 9c7641f63f | |||
| 96a5cf5ba1 | |||
| 4b2146b12d | |||
| 6650957f79 | |||
| ec798a7c44 | |||
| b764fb1ded | |||
| 0a9fc171b0 | |||
| 9c9a972a0c | |||
| b474630c7d | |||
| f147050513 | |||
| daa8f3dd05 | |||
| be0ba75925 | |||
| 9ff3daf785 | |||
| 8babe05953 | |||
| 396795c983 | |||
| 9e907e6785 | |||
| 6d2239d8ac | |||
| 59e80c5103 | |||
| 69b1bdd32e | |||
| c05fad8b18 | |||
| 203df9d5b7 | |||
| 0d396ccd30 | |||
| ae8c1d229f | |||
| e7f51c2248 | |||
| 3238645f26 | |||
| f84f76f006 | |||
| 96c1300f15 | |||
| b6ba2dcdb0 | |||
| 5b0efc7e91 | |||
| d4070c3667 | |||
| a655aedb5c | |||
| 928ba56b95 | |||
| 8a6796e5c0 | |||
| 95b23095c0 | |||
| ffc9fb5007 | |||
| 6a7a8c803d | |||
| f990cf0064 | |||
| 1124908d2a | |||
| e1cba432a6 | |||
| 5c921cafeb | |||
| 697bbe59ca | |||
| 2a4819d75c | |||
| 5cfe6a0be1 | |||
| df421f1e4b | |||
| 61dd721ca2 | |||
| 3a9440f954 | |||
| e4c874d1af | |||
| 5594416121 | |||
| c4b3c901a5 | |||
| 617dd57162 | |||
| d044a8c99d | |||
| bf993528d1 | |||
| b8a9f7264d | |||
| b3b5b35889 | |||
| 80b5b24cce | |||
| 4a2afa9d02 | |||
| 4415f3151d | |||
| bf8cc50798 | |||
| ac208b1bcd | |||
| 8dfeabdd16 | |||
| 6d7696ef66 | |||
| 8e6e9fbc60 | |||
| 1429587dd4 | |||
| 0dbeeb4503 | |||
| 3ee0682247 | |||
| 249f559104 | |||
| ea6b0be910 | |||
| e779ecf12d | |||
| 01a22e61df | |||
| c8af90f697 | |||
| b5f03c95ab | |||
| d595ebc0e2 | |||
| d47e4dd1f7 | |||
| 83bcbee222 | |||
| 30034c75e7 | |||
| d3bd1a523d | |||
| 9665379cdc | |||
| a7000f8129 | |||
| a5d0491d57 | |||
| 12e3d210e8 | |||
| 632fadcea3 | |||
| 7c220b9af5 | |||
| f30b3ea329 | |||
| 9fbf7aa753 | |||
| 3e1840a75d | |||
| 4282668d47 | |||
| 4aab967796 | |||
| b87e601063 | |||
| a0e2e200d9 | |||
| 3133af2a21 | |||
| 5fe7ef63d8 | |||
| 62ed555b2c | |||
| 8aa2a34a8d | |||
| aaa590d846 | |||
| 1eb2b04aa1 | |||
| 2e75199664 | |||
| 0670f656fb | |||
| 4d17e3c209 | |||
| 58fb258016 | |||
| ed3ae9af59 | |||
| 64d5481c00 |
@@ -0,0 +1,14 @@
|
|||||||
|
root = true
|
||||||
|
|
||||||
|
[*.js]
|
||||||
|
indent_size = 2
|
||||||
|
insert_final_newline = true
|
||||||
|
trim_trailing_whitespace = true
|
||||||
|
|
||||||
|
[spec/expected/*.js]
|
||||||
|
insert_final_newline = false
|
||||||
|
|
||||||
|
[*.yml]
|
||||||
|
indent_size = 2
|
||||||
|
insert_final_newline = true
|
||||||
|
trim_trailing_whitespace = true
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
.rvmrc
|
||||||
|
.DS_Store
|
||||||
|
/tmp/
|
||||||
|
*.sublime-project
|
||||||
|
*.sublime-workspace
|
||||||
|
npm-debug.log
|
||||||
|
.idea
|
||||||
|
yarn-error.log
|
||||||
|
node_modules
|
||||||
|
/handlebars-release.tgz
|
||||||
|
.nyc_output
|
||||||
|
|
||||||
|
# Generated files
|
||||||
|
lib/handlebars/compiler/parser.js
|
||||||
|
/coverage/
|
||||||
|
/dist/
|
||||||
|
/tests/integration/*/dist/
|
||||||
|
|
||||||
|
# Third-party or files that must remain unchanged
|
||||||
|
/spec/expected/
|
||||||
|
/spec/vendor
|
||||||
|
|
||||||
|
# JS-Snippets
|
||||||
|
src/*.js
|
||||||
@@ -0,0 +1,66 @@
|
|||||||
|
module.exports = {
|
||||||
|
extends: ['eslint:recommended', 'plugin:compat/recommended', 'prettier'],
|
||||||
|
globals: {
|
||||||
|
self: false
|
||||||
|
},
|
||||||
|
env: {
|
||||||
|
node: true,
|
||||||
|
es6: true
|
||||||
|
},
|
||||||
|
rules: {
|
||||||
|
'no-console': 'warn',
|
||||||
|
|
||||||
|
// temporarily disabled until the violating places are fixed.
|
||||||
|
'no-func-assign': 'off',
|
||||||
|
'no-sparse-arrays': 'off',
|
||||||
|
|
||||||
|
// Best Practices //
|
||||||
|
//----------------//
|
||||||
|
'default-case': 'warn',
|
||||||
|
'guard-for-in': 'warn',
|
||||||
|
'no-alert': 'error',
|
||||||
|
'no-caller': 'error',
|
||||||
|
'no-div-regex': 'warn',
|
||||||
|
'no-eval': 'error',
|
||||||
|
'no-extend-native': 'error',
|
||||||
|
'no-extra-bind': 'error',
|
||||||
|
'no-floating-decimal': 'error',
|
||||||
|
'no-implied-eval': 'error',
|
||||||
|
'no-iterator': 'error',
|
||||||
|
'no-labels': 'error',
|
||||||
|
'no-lone-blocks': 'error',
|
||||||
|
'no-loop-func': 'error',
|
||||||
|
'no-multi-str': 'warn',
|
||||||
|
'no-global-assign': 'error',
|
||||||
|
'no-new': 'error',
|
||||||
|
'no-new-func': 'error',
|
||||||
|
'no-new-wrappers': 'error',
|
||||||
|
'no-octal-escape': 'error',
|
||||||
|
'no-process-env': 'error',
|
||||||
|
'no-proto': 'error',
|
||||||
|
'no-return-assign': 'error',
|
||||||
|
'no-script-url': 'error',
|
||||||
|
'no-self-compare': 'error',
|
||||||
|
'no-sequences': 'error',
|
||||||
|
'no-throw-literal': 'error',
|
||||||
|
'no-unused-expressions': 'error',
|
||||||
|
'no-warning-comments': 'warn',
|
||||||
|
'no-with': 'error',
|
||||||
|
radix: 'error',
|
||||||
|
|
||||||
|
// Variables //
|
||||||
|
//-----------//
|
||||||
|
'no-label-var': 'error',
|
||||||
|
'no-undef-init': 'error',
|
||||||
|
'no-use-before-define': ['error', 'nofunc'],
|
||||||
|
|
||||||
|
// ECMAScript 6 //
|
||||||
|
//--------------//
|
||||||
|
'no-var': 'error'
|
||||||
|
},
|
||||||
|
parserOptions: {
|
||||||
|
sourceType: 'module',
|
||||||
|
ecmaVersion: 6,
|
||||||
|
ecmaFeatures: {}
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
# Handlebars-template fixtures in test cases need deterministic eol
|
||||||
|
*.handlebars text eol=lf
|
||||||
|
*.hbs text eol=lf
|
||||||
|
|
||||||
|
# Lexer files as well
|
||||||
|
*.l text eol=lf
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
version: 2
|
||||||
|
updates:
|
||||||
|
- package-ecosystem: npm
|
||||||
|
directory: "/"
|
||||||
|
open-pull-requests-limit: 0
|
||||||
|
schedule:
|
||||||
|
interval: weekly
|
||||||
|
allow:
|
||||||
|
- dependency-type: production
|
||||||
@@ -0,0 +1,89 @@
|
|||||||
|
name: CI
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- master
|
||||||
|
pull_request: {}
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
lint:
|
||||||
|
name: Lint
|
||||||
|
runs-on: 'ubuntu-latest'
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v2
|
||||||
|
|
||||||
|
- name: Setup Node.js
|
||||||
|
uses: actions/setup-node@v2
|
||||||
|
with:
|
||||||
|
node-version: '16'
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: npm ci
|
||||||
|
|
||||||
|
- name: Lint
|
||||||
|
run: npm run lint
|
||||||
|
|
||||||
|
test:
|
||||||
|
name: Test (Node)
|
||||||
|
runs-on: ${{ matrix.operating-system }}
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
operating-system: ['ubuntu-latest', 'windows-latest']
|
||||||
|
# https://nodejs.org/en/about/releases/
|
||||||
|
node-version: ['10', '12', '14', '16', '18', '20']
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v2
|
||||||
|
with:
|
||||||
|
submodules: true
|
||||||
|
|
||||||
|
- name: Setup Node.js
|
||||||
|
uses: actions/setup-node@v2
|
||||||
|
with:
|
||||||
|
node-version: ${{ matrix.node-version }}
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: npm ci
|
||||||
|
|
||||||
|
- name: Test
|
||||||
|
run: npm run test
|
||||||
|
|
||||||
|
- name: Test (Integration)
|
||||||
|
# https://github.com/webpack/webpack/issues/14532
|
||||||
|
if: ${{ matrix.node-version != '18' && matrix.node-version != '20' }}
|
||||||
|
run: |
|
||||||
|
cd ./tests/integration/rollup-test && ./test.sh && cd -
|
||||||
|
cd ./tests/integration/webpack-babel-test && ./test.sh && cd -
|
||||||
|
cd ./tests/integration/webpack-test && ./test.sh && cd -
|
||||||
|
|
||||||
|
browser:
|
||||||
|
name: Test (Browser)
|
||||||
|
runs-on: 'ubuntu-20.04'
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v2
|
||||||
|
with:
|
||||||
|
submodules: true
|
||||||
|
|
||||||
|
- name: Setup Node.js
|
||||||
|
uses: actions/setup-node@v2
|
||||||
|
with:
|
||||||
|
node-version: '16'
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: npm ci
|
||||||
|
|
||||||
|
- name: Install Playwright
|
||||||
|
run: |
|
||||||
|
npx playwright install-deps
|
||||||
|
npx playwright install
|
||||||
|
|
||||||
|
- name: Build
|
||||||
|
run: npx grunt prepare
|
||||||
|
|
||||||
|
- name: Test
|
||||||
|
run: npm run test:browser
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
name: Release
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- master
|
||||||
|
tags:
|
||||||
|
- '*'
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
publish-aws-s3:
|
||||||
|
name: Publish to AWS S3
|
||||||
|
runs-on: 'ubuntu-latest'
|
||||||
|
environment: 'builds.handlebarsjs.com.s3.amazonaws.com'
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v2
|
||||||
|
with:
|
||||||
|
submodules: true
|
||||||
|
|
||||||
|
- name: Setup Node.js
|
||||||
|
uses: actions/setup-node@v2
|
||||||
|
with:
|
||||||
|
node-version: '16'
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: npm ci
|
||||||
|
|
||||||
|
- name: Publish
|
||||||
|
run: |
|
||||||
|
git config --global user.email "release@handlebarsjs.com"
|
||||||
|
git config --global user.name "handlebars-lang"
|
||||||
|
npm run publish:aws
|
||||||
|
env:
|
||||||
|
S3_BUCKET_NAME: "builds.handlebarsjs.com"
|
||||||
|
S3_ACCESS_KEY_ID: ${{ secrets.S3_ACCESS_KEY_ID }}
|
||||||
|
S3_SECRET_ACCESS_KEY: ${{ secrets.S3_SECRET_ACCESS_KEY }}
|
||||||
+13
-6
@@ -1,12 +1,19 @@
|
|||||||
vendor
|
|
||||||
.rvmrc
|
.rvmrc
|
||||||
.DS_Store
|
.DS_Store
|
||||||
lib/handlebars/compiler/parser.js
|
|
||||||
/dist/
|
|
||||||
/tmp/
|
/tmp/
|
||||||
/coverage/
|
|
||||||
node_modules
|
|
||||||
*.sublime-project
|
*.sublime-project
|
||||||
*.sublime-workspace
|
*.sublime-workspace
|
||||||
npm-debug.log
|
npm-debug.log
|
||||||
sauce_connect.log*
|
.idea
|
||||||
|
/yarn-error.log
|
||||||
|
/yarn.lock
|
||||||
|
node_modules
|
||||||
|
/handlebars-release.tgz
|
||||||
|
.nyc_output
|
||||||
|
|
||||||
|
# Generated files
|
||||||
|
lib/handlebars/compiler/parser.js
|
||||||
|
/coverage/
|
||||||
|
/dist/
|
||||||
|
/tests/integration/*/dist/
|
||||||
|
/spec/tmp/*
|
||||||
|
|||||||
+1
-1
@@ -1,3 +1,3 @@
|
|||||||
[submodule "spec/mustache"]
|
[submodule "spec/mustache"]
|
||||||
path = spec/mustache
|
path = spec/mustache
|
||||||
url = git://github.com/mustache/spec.git
|
url = https://github.com/mustache/spec.git
|
||||||
|
|||||||
@@ -1,2 +0,0 @@
|
|||||||
instrumentation:
|
|
||||||
excludes: ['**/spec/**']
|
|
||||||
@@ -1,51 +0,0 @@
|
|||||||
{
|
|
||||||
"predef": [
|
|
||||||
"console",
|
|
||||||
"require",
|
|
||||||
"suite",
|
|
||||||
"equal",
|
|
||||||
"equals",
|
|
||||||
"test",
|
|
||||||
"testBoth",
|
|
||||||
"raises",
|
|
||||||
"deepEqual",
|
|
||||||
"start",
|
|
||||||
"stop",
|
|
||||||
"ok",
|
|
||||||
"strictEqual",
|
|
||||||
"module",
|
|
||||||
"define",
|
|
||||||
|
|
||||||
"describe",
|
|
||||||
"it",
|
|
||||||
"afterEach",
|
|
||||||
"beforeEach"
|
|
||||||
],
|
|
||||||
|
|
||||||
"node" : true,
|
|
||||||
"browser" : true,
|
|
||||||
"esnext": true,
|
|
||||||
|
|
||||||
"boss" : true,
|
|
||||||
"curly": false,
|
|
||||||
"debug": false,
|
|
||||||
"devel": false,
|
|
||||||
"eqeqeq": false,
|
|
||||||
"eqnull": true,
|
|
||||||
"evil": true,
|
|
||||||
"forin": false,
|
|
||||||
"immed": false,
|
|
||||||
"laxbreak": true,
|
|
||||||
"newcap": true,
|
|
||||||
"noarg": true,
|
|
||||||
"noempty": false,
|
|
||||||
"nonew": false,
|
|
||||||
"nomen": false,
|
|
||||||
"onevar": false,
|
|
||||||
"plusplus": false,
|
|
||||||
"regexp": false,
|
|
||||||
"undef": true,
|
|
||||||
"sub": true,
|
|
||||||
"strict": false,
|
|
||||||
"white": false
|
|
||||||
}
|
|
||||||
-23
@@ -1,23 +0,0 @@
|
|||||||
.DS_Store
|
|
||||||
.gitignore
|
|
||||||
.rvmrc
|
|
||||||
.jshintrc
|
|
||||||
.travis.yml
|
|
||||||
.rspec
|
|
||||||
Gemfile
|
|
||||||
Gemfile.lock
|
|
||||||
Rakefile
|
|
||||||
Gruntfile.js
|
|
||||||
*.gemspec
|
|
||||||
*.nuspec
|
|
||||||
bench/*
|
|
||||||
configurations/*
|
|
||||||
components/*
|
|
||||||
dist/cdnjs/*
|
|
||||||
dist/components/*
|
|
||||||
spec/*
|
|
||||||
src/*
|
|
||||||
tasks/*
|
|
||||||
tmp/*
|
|
||||||
publish/*
|
|
||||||
vendor/*
|
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
.rvmrc
|
||||||
|
.DS_Store
|
||||||
|
/tmp/
|
||||||
|
*.sublime-project
|
||||||
|
*.sublime-workspace
|
||||||
|
npm-debug.log
|
||||||
|
sauce_connect.log*
|
||||||
|
.idea
|
||||||
|
yarn-error.log
|
||||||
|
node_modules
|
||||||
|
/handlebars-release.tgz
|
||||||
|
.nyc_output
|
||||||
|
|
||||||
|
# Generated files
|
||||||
|
lib/handlebars/compiler/parser.js
|
||||||
|
/coverage/
|
||||||
|
/dist/
|
||||||
|
/tests/integration/*/dist/
|
||||||
|
|
||||||
|
# Third-party or files that must remain unchanged
|
||||||
|
/spec/expected/
|
||||||
|
/spec/vendor
|
||||||
-23
@@ -1,23 +0,0 @@
|
|||||||
language: node_js
|
|
||||||
before_install:
|
|
||||||
- npm install -g grunt-cli
|
|
||||||
script:
|
|
||||||
- grunt --stack travis
|
|
||||||
email:
|
|
||||||
on_failure: change
|
|
||||||
on_success: never
|
|
||||||
env:
|
|
||||||
global:
|
|
||||||
- S3_BUCKET_NAME=builds.handlebarsjs.com
|
|
||||||
- secure: ckyEe5dzjdFDjmZ6wIrhGm0CFBEnKq8c1dYptfgVV/Q5/nJFGzu8T0yTjouS/ERxzdT2H327/63VCxhFnLCRHrsh4rlW/rCy4XI3O/0TeMLgFPa4TXkO8359qZ4CB44TBb3NsJyQXNMYdJpPLTCVTMpuiqqkFFOr+6OeggR7ufA=
|
|
||||||
- secure: Nm4AgSfsgNB21kgKrF9Tl7qVZU8YYREhouQunFracTcZZh2NZ2XH5aHuSiXCj88B13Cr/jGbJKsZ4T3QS3wWYtz6lkyVOx3H3iI+TMtqhD9RM3a7A4O+4vVN8IioB2YjhEu0OKjwgX5gp+0uF+pLEi7Hpj6fupD3AbbL5uYcKg8=
|
|
||||||
matrix:
|
|
||||||
include:
|
|
||||||
- node_js: '0.10'
|
|
||||||
env:
|
|
||||||
- PUBLISH=true
|
|
||||||
- secure: pLTzghtVll9yGKJI0AaB0uI8GypfWxLTaIB0ZL8//yN3nAEIKMhf/RRilYTsn/rKj2NUa7vt2edYILi3lttOUlCBOwTc9amiRms1W8Lwr/3IdWPeBLvLuH1zNJRm2lBAwU4LBSqaOwhGaxOQr6KHTnWudhNhgOucxpZfvfI/dFw=
|
|
||||||
- secure: yERYCf7AwL11D9uMtacly/THGV8BlzsMmrt+iQVvGA3GaY6QMmfYqf6P6cCH98sH5etd1Y+1e6YrPeMjqI6lyRllT7FptoyOdHulazQe86VQN4sc0EpqMlH088kB7gGjTut9Z+X9ViooT5XEh9WA5jXEI9pXhQJNoIHkWPuwGuY=
|
|
||||||
cache:
|
|
||||||
directories:
|
|
||||||
- node_modules
|
|
||||||
+153
-28
@@ -2,17 +2,26 @@
|
|||||||
|
|
||||||
## Reporting Issues
|
## Reporting Issues
|
||||||
|
|
||||||
Please see our [FAQ](https://github.com/wycats/handlebars.js/blob/master/FAQ.md) for common issues that people run into.
|
Please see our [FAQ](https://github.com/handlebars-lang/handlebars.js/blob/master/FAQ.md) for common issues that people run into.
|
||||||
|
|
||||||
Should you run into other issues with the project, please don't hesitate to let us know by filing an [issue][issue]! In general we are going to ask for an example of the problem failing, which can be as simple as a jsfiddle/jsbin/etc. We've put together a jsfiddle [template][jsfiddle] to ease this. (We will keep this link up to date as new releases occur, so feel free to check back here)
|
Should you run into other issues with the project, please don't hesitate to let us know by filing an [issue][issue]! In general we are going to ask for an example of the problem failing, which can be as simple as a jsfiddle/jsbin/etc. We've put together a jsfiddle [template][jsfiddle] to ease this. (We will keep this link up to date as new releases occur, so feel free to check back here)
|
||||||
|
|
||||||
Pull requests containing only failing thats demonstrating the issue are welcomed and this also helps ensure that your issue won't regress in the future once it's fixed.
|
Pull requests containing only failing tests demonstrating the issue are welcomed and this also helps ensure that your issue won't regress in the future once it's fixed.
|
||||||
|
|
||||||
|
Documentation issues on the [handlebarsjs.com](https://handlebarsjs.com) site should be reported on [handlebars-lang/docs](https://github.com/handlebars-lang/docs).
|
||||||
|
|
||||||
|
## Branches
|
||||||
|
|
||||||
|
- The branch `4.x` contains the currently released version. Bugfixes should be made in this branch.
|
||||||
|
- The branch `master` contains the next version. A release date is not yet specified. Maintainers
|
||||||
|
should merge the branch `4.x` into the master branch regularly.
|
||||||
|
|
||||||
## Pull Requests
|
## Pull Requests
|
||||||
|
|
||||||
We also accept [pull requests][pull-request]!
|
We also accept [pull requests][pull-request]!
|
||||||
|
|
||||||
Generally we like to see pull requests that
|
Generally we like to see pull requests that
|
||||||
|
|
||||||
- Maintain the existing code style
|
- Maintain the existing code style
|
||||||
- Are focused on a single change (i.e. avoid large refactoring or style adjustments in untouched code if not the primary goal of the pull request)
|
- Are focused on a single change (i.e. avoid large refactoring or style adjustments in untouched code if not the primary goal of the pull request)
|
||||||
- Have [good commit messages](http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html)
|
- Have [good commit messages](http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html)
|
||||||
@@ -23,8 +32,10 @@ Generally we like to see pull requests that
|
|||||||
|
|
||||||
To build Handlebars.js you'll need a few things installed.
|
To build Handlebars.js you'll need a few things installed.
|
||||||
|
|
||||||
* Node.js
|
- Node.js
|
||||||
* [Grunt](http://gruntjs.com/getting-started)
|
- [Grunt](http://gruntjs.com/getting-started)
|
||||||
|
|
||||||
|
Before building, you need to make sure that the Git submodule `spec/mustache` is included (i.e. the directory `spec/mustache` should not be empty). To include it, if using Git version 1.6.5 or newer, use `git clone --recursive` rather than `git clone`. Or, if you already cloned without `--recursive`, use `git submodule update --init`.
|
||||||
|
|
||||||
Project dependencies may be installed via `npm install`.
|
Project dependencies may be installed via `npm install`.
|
||||||
|
|
||||||
@@ -36,42 +47,156 @@ You can also run our set of benchmarks with `grunt bench`.
|
|||||||
The `grunt dev` implements watching for tests and allows for in browser testing at `http://localhost:9999/spec/`.
|
The `grunt dev` implements watching for tests and allows for in browser testing at `http://localhost:9999/spec/`.
|
||||||
|
|
||||||
If you notice any problems, please report them to the GitHub issue tracker at
|
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).
|
[http://github.com/handlebars-lang/handlebars.js/issues](http://github.com/handlebars-lang/handlebars.js/issues).
|
||||||
|
|
||||||
## Ember testing
|
## Running Tests
|
||||||
|
|
||||||
The current ember distribution should be tested as part of the handlebars release process. This requires building the `handlebars-source` gem locally and then executing the ember test script.
|
To run tests locally, first install all dependencies.
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
npm link
|
npm install
|
||||||
grunt build release
|
```
|
||||||
cp dist/*.js $emberRepoDir/bower_components/handlebars/
|
|
||||||
|
|
||||||
cd $emberRepoDir
|
Clone the mustache specs into the spec/mustache folder.
|
||||||
npm link handlebars
|
|
||||||
|
```sh
|
||||||
|
cd spec
|
||||||
|
rm -r mustache
|
||||||
|
git clone https://github.com/mustache/spec.git mustache
|
||||||
|
```
|
||||||
|
|
||||||
|
From the root directory, run the tests.
|
||||||
|
|
||||||
|
```sh
|
||||||
npm test
|
npm test
|
||||||
```
|
```
|
||||||
|
|
||||||
## Releasing
|
## Linting and Formatting
|
||||||
|
|
||||||
Handlebars utilizes the [release yeoman generator][generator-release] to perform most release tasks.
|
Handlebars uses `eslint` to enforce best-practices and `prettier` to auto-format files.
|
||||||
|
We do linting and formatting in two phases:
|
||||||
|
|
||||||
A full release may be completed with the following:
|
- Committed files are linted and formatted in a pre-commit hook. In this stage eslint-errors are forbidden,
|
||||||
|
while warnings are allowed.
|
||||||
|
- The GitHub CI job also lints all files and checks if they are formatted correctly. In this stage, warnings
|
||||||
|
are forbidden.
|
||||||
|
|
||||||
```
|
You can use the following scripts to make sure that the CI job does not fail:
|
||||||
yo release
|
|
||||||
npm publish
|
|
||||||
yo release:publish cdnjs handlebars.js dist/cdnjs/
|
|
||||||
yo release:publish components handlebars.js dist/components/
|
|
||||||
|
|
||||||
cd dist/components/
|
- **npm run lint** will run `eslint` and fail on warnings
|
||||||
gem build handlebars-source.gemspec
|
- **npm run format** will run `prettier` on all files
|
||||||
gem push handlebars-source-*.gem
|
- **npm run check-before-pull-request** will perform all most checks that our CI job does in its build-job, excluding the "integration-test".
|
||||||
```
|
- **npm run test:integration** will run integration tests (using old NodeJS versions and integrations with webpack, babel and so on)
|
||||||
|
These tests only work on a Linux-machine with `nvm` installed (for running tests in multiple versions of NodeJS).
|
||||||
|
|
||||||
After this point the handlebars site needs to be updated to point to the new version numbers. The jsfiddle link should be updated to point to the most recent distribution for all instances in our documentation.
|
## Releasing the latest version
|
||||||
|
|
||||||
|
Before attempting the release Handlebars, please make sure that you have the following authorizations:
|
||||||
|
|
||||||
|
- Push-access to [handlebars-lang/handlebars.js](https://github.com/handlebars-lang/handlebars.js/)
|
||||||
|
- Publishing rights on npmjs.com for the [handlebars](https://www.npmjs.com/package/handlebars) package
|
||||||
|
- Publishing rights on rubygems for the [handlebars-source](https://rubygems.org/gems/handlebars-source) package
|
||||||
|
- Push-access to the repo for legacy package managers: [components/handlebars.js](https://github.com/components/handlebars.js)
|
||||||
|
- Push-access to the production-repo of the handlebars site: [handlebars-lang/docs](https://github.com/handlebars-lang/docs)
|
||||||
|
|
||||||
|
_When releasing a previous version of Handlebars, please look into the CONTRIBUNG.md in the corresponding branch._
|
||||||
|
|
||||||
|
A full release via Docker may be completed with the following:
|
||||||
|
|
||||||
|
1. Create a `Dockerfile` in this folder for releasing
|
||||||
|
```Dockerfile
|
||||||
|
FROM node:10-slim
|
||||||
|
|
||||||
|
ENV EDITOR=vim
|
||||||
|
|
||||||
|
# Update stretch repositories
|
||||||
|
RUN sed -i -e 's/deb.debian.org/archive.debian.org/g' \
|
||||||
|
-e 's|security.debian.org|archive.debian.org/|g' \
|
||||||
|
-e '/stretch-updates/d' /etc/apt/sources.list
|
||||||
|
|
||||||
|
# Install release dependencies
|
||||||
|
RUN apt-get update
|
||||||
|
RUN apt-get install -y git vim
|
||||||
|
|
||||||
|
# Work around deprecated npm dependency install via unauthenticated git-protocol:
|
||||||
|
# https://github.com/kpdecker/generator-release/blob/87aab9b84c9f083635c3fcc822f18acce1f48736/package.json#L31
|
||||||
|
RUN git config --system url."https://github.com/".insteadOf git://github.com/
|
||||||
|
|
||||||
|
# Configure git
|
||||||
|
RUN git config --system user.email "release@handlebarsjs.com"
|
||||||
|
RUN git config --system user.name "handlebars-lang"
|
||||||
|
|
||||||
|
RUN mkdir /home/node/.config
|
||||||
|
RUN mkdir /home/node/.ssh
|
||||||
|
RUN mkdir /home/node/tmp
|
||||||
|
|
||||||
|
# Generate config for yo generator-release:
|
||||||
|
# https://github.com/kpdecker/generator-release#example
|
||||||
|
# You have to add a valid GitHub OAuth token!
|
||||||
|
RUN echo "module.exports = {\n auth: 'oauth',\n token: 'GitHub OAuth token'\n};" > /home/node/.config/generator-release
|
||||||
|
RUN chown -R node:node /home/node/.config
|
||||||
|
|
||||||
|
# Add the generated key to GitHub: https://github.com/settings/keys
|
||||||
|
RUN ssh-keygen -q -t ed25519 -N '' -f /home/node/.ssh/id_ed25519 -C "release@handlebarsjs.com"
|
||||||
|
RUN chmod 0600 /home/node/.ssh/id_ed25519*
|
||||||
|
RUN chown node:node /home/node/.ssh/id_ed25519*
|
||||||
|
```
|
||||||
|
2. Build and run the Docker image
|
||||||
|
```bash
|
||||||
|
docker build --tag handlebars:release .
|
||||||
|
docker run --rm --interactive --tty \
|
||||||
|
--volume $PWD:/app \
|
||||||
|
--workdir /app \
|
||||||
|
--user $(id -u):$(id -g) \
|
||||||
|
--env NPM_CONFIG_PREFIX=/home/node/.npm-global \
|
||||||
|
handlebars:release bash -c 'export PATH=$PATH:/home/node/.npm-global/bin; bash'
|
||||||
|
```
|
||||||
|
* Add SSH key to GitHub: `cat /home/node/.ssh/id_ed25519.pub` (https://github.com/settings/keys)
|
||||||
|
* Add GitHub API token: `vi /home/node/.config/generator-release`
|
||||||
|
* Execute the following steps:
|
||||||
|
```bash
|
||||||
|
npm ci
|
||||||
|
npm install -g yo@1 grunt@1 generator-release
|
||||||
|
npm run release
|
||||||
|
yo release
|
||||||
|
npm login
|
||||||
|
npm publish
|
||||||
|
yo release:publish components handlebars.js dist/components/
|
||||||
|
```
|
||||||
|
6. Publish Ruby `handlebars-source` gem:
|
||||||
|
```bash
|
||||||
|
docker run --rm --interactive --tty \
|
||||||
|
--volume $PWD:/app \
|
||||||
|
--workdir /app \
|
||||||
|
--user $(id -u):$(id -g) \
|
||||||
|
ruby:3.2-slim bash
|
||||||
|
```
|
||||||
|
* Execute the following steps:
|
||||||
|
```bash
|
||||||
|
cd dist/components/
|
||||||
|
gem build handlebars-source.gemspec
|
||||||
|
gem push handlebars-source-*.gem
|
||||||
|
```
|
||||||
|
|
||||||
|
### After the release
|
||||||
|
|
||||||
|
After the release, you should check that all places have really been updated. Especially verify that the `latest`-tags
|
||||||
|
in those places still point to the latest version
|
||||||
|
|
||||||
|
- [The npm-package](https://www.npmjs.com/package/handlebars) (check latest-tag)
|
||||||
|
- [The bower package](https://github.com/components/handlebars.js) (check the package.json)
|
||||||
|
- [The AWS S3 Bucket](https://s3.amazonaws.com/builds.handlebarsjs.com) (check latest-tag)
|
||||||
|
- [RubyGems](https://rubygems.org/gems/handlebars-source)
|
||||||
|
|
||||||
|
When everything is OK, the **handlebars site** needs to be updated.
|
||||||
|
|
||||||
|
Go to the master branch of the repo [handlebars-lang/docs](https://github.com/handlebars-lang/docs/tree/master)
|
||||||
|
and make a minimal change to the README. This will invoke a github-action that redeploys
|
||||||
|
the site, fetching the latest version-number from the npm-registry.
|
||||||
|
(note that the default-branch of this repo is not the master and regular changes are done
|
||||||
|
in the `handlebars-lang/docs`-repo).
|
||||||
|
|
||||||
[generator-release]: https://github.com/walmartlabs/generator-release
|
[generator-release]: https://github.com/walmartlabs/generator-release
|
||||||
[pull-request]: https://github.com/wycats/handlebars.js/pull/new/master
|
[pull-request]: https://github.com/handlebars-lang/handlebars.js/pull/new/master
|
||||||
[issue]: https://github.com/wycats/handlebars.js/issues/new
|
[issue]: https://github.com/handlebars-lang/handlebars.js/issues/new
|
||||||
[jsfiddle]: http://jsfiddle.net/9D88g/11/
|
[jsfiddle]: https://jsfiddle.net/9D88g/180/
|
||||||
|
|||||||
@@ -2,11 +2,11 @@
|
|||||||
|
|
||||||
1. How can I file a bug report:
|
1. How can I file a bug report:
|
||||||
|
|
||||||
See our guidelines on [reporting issues](https://github.com/wycats/handlebars.js/blob/master/CONTRIBUTING.md#reporting-issues).
|
See our guidelines on [reporting issues](https://github.com/handlebars-lang/handlebars.js/blob/master/CONTRIBUTING.md#reporting-issues).
|
||||||
|
|
||||||
1. Why isn't my Mustache template working?
|
1. Why isn't my Mustache template working?
|
||||||
|
|
||||||
Handlebars deviates from Mustache slightly on a few behaviors. These variations are documented in our [readme](https://github.com/wycats/handlebars.js#differences-between-handlebarsjs-and-mustache).
|
Handlebars deviates from Mustache slightly on a few behaviors. These variations are documented in our [readme](https://github.com/handlebars-lang/handlebars.js#differences-between-handlebarsjs-and-mustache).
|
||||||
|
|
||||||
1. Why is it slower when compiling?
|
1. Why is it slower when compiling?
|
||||||
|
|
||||||
@@ -36,16 +36,18 @@
|
|||||||
```sh
|
```sh
|
||||||
handlebars --version
|
handlebars --version
|
||||||
```
|
```
|
||||||
|
|
||||||
If using the integrated precompiler and
|
If using the integrated precompiler and
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
console.log(Handlebars.VERSION);
|
console.log(Handlebars.VERSION);
|
||||||
```
|
```
|
||||||
|
|
||||||
On the client side.
|
On the client side.
|
||||||
|
|
||||||
We include the built client libraries in the npm package for those who want to be certain that they are using the same client libraries as the compiler.
|
We include the built client libraries in the npm package for those who want to be certain that they are using the same client libraries as the compiler.
|
||||||
|
|
||||||
Should these match, please file an issue with us, per our [issue filing guidelines](https://github.com/wycats/handlebars.js/blob/master/CONTRIBUTING.md#reporting-issues).
|
Should these match, please file an issue with us, per our [issue filing guidelines](https://github.com/handlebars-lang/handlebars.js/blob/master/CONTRIBUTING.md#reporting-issues).
|
||||||
|
|
||||||
1. Why doesn't IE like the `default` name in the AMD module?
|
1. Why doesn't IE like the `default` name in the AMD module?
|
||||||
|
|
||||||
@@ -53,8 +55,8 @@
|
|||||||
|
|
||||||
1. How do I load the runtime library when using AMD?
|
1. How do I load the runtime library when using AMD?
|
||||||
|
|
||||||
There are two options for loading under AMD environments. The first is to use the `handlebars.runtime.amd.js` file. This may require a [path mapping](https://github.com/wycats/handlebars.js/blob/master/spec/amd-runtime.html#L31) as well as access via the `default` field.
|
There are two options for loading under AMD environments. The first is to use the `handlebars.runtime.amd.js` file. This may require a [path mapping](https://github.com/handlebars-lang/handlebars.js/blob/master/spec/amd-runtime.html#L31) as well as access via the `default` field.
|
||||||
|
|
||||||
The other option is to load the `handlebars.runtime.js` UMD build, which might not require path configuration and exposes the library as both the module root and the `default` field for compatibility.
|
The other option is to load the `handlebars.runtime.js` UMD build, which might not require path configuration and exposes the library as both the module root and the `default` field for compatibility.
|
||||||
|
|
||||||
If not using ES6 transpilers or accessing submodules in the build the former option should be sufficent for most use cases.
|
If not using ES6 transpilers or accessing submodules in the build the former option should be sufficient for most use cases.
|
||||||
|
|||||||
+140
-112
@@ -1,94 +1,129 @@
|
|||||||
var childProcess = require('child_process');
|
/* eslint-disable no-process-env */
|
||||||
|
|
||||||
module.exports = function(grunt) {
|
module.exports = function(grunt) {
|
||||||
|
|
||||||
grunt.initConfig({
|
grunt.initConfig({
|
||||||
pkg: grunt.file.readJSON('package.json'),
|
pkg: grunt.file.readJSON('package.json'),
|
||||||
|
|
||||||
jshint: {
|
clean: [
|
||||||
options: {
|
'tmp',
|
||||||
jshintrc: '.jshintrc'
|
'dist',
|
||||||
},
|
'lib/handlebars/compiler/parser.js',
|
||||||
files: [
|
'/tests/integration/**/node_modules'
|
||||||
'dist/cjs/**/!(*.min|parser).js'
|
],
|
||||||
]
|
|
||||||
},
|
|
||||||
|
|
||||||
clean: ['tmp', 'dist', 'lib/handlebars/compiler/parser.js'],
|
|
||||||
|
|
||||||
copy: {
|
copy: {
|
||||||
dist: {
|
dist: {
|
||||||
options: {
|
options: {
|
||||||
processContent: function(content, path) {
|
processContent: function(content) {
|
||||||
return grunt.template.process('/*!\n\n <%= pkg.name %> v<%= pkg.version %>\n\n<%= grunt.file.read("LICENSE") %>\n@license\n*/\n')
|
return (
|
||||||
+ content;
|
grunt.template.process(
|
||||||
|
'/**!\n\n @license\n <%= pkg.name %> v<%= pkg.version %>\n\n<%= grunt.file.read("LICENSE") %>\n*/\n'
|
||||||
|
) + content
|
||||||
|
);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
files: [
|
files: [{ expand: true, cwd: 'dist/', src: ['*.js'], dest: 'dist/' }]
|
||||||
{expand: true, cwd: 'dist/', src: ['*.js'], dest: 'dist/'}
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
cdnjs: {
|
cdnjs: {
|
||||||
files: [
|
files: [
|
||||||
{expand: true, cwd: 'dist/', src: ['*.js'], dest: 'dist/cdnjs'}
|
{ expand: true, cwd: 'dist/', src: ['*.js'], dest: 'dist/cdnjs' }
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
components: {
|
components: {
|
||||||
files: [
|
files: [
|
||||||
{expand: true, cwd: 'components/', src: ['**'], dest: 'dist/components'},
|
{
|
||||||
{expand: true, cwd: 'dist/', src: ['*.js'], dest: 'dist/components'}
|
expand: true,
|
||||||
|
cwd: 'components/',
|
||||||
|
src: ['**'],
|
||||||
|
dest: 'dist/components'
|
||||||
|
},
|
||||||
|
{ expand: true, cwd: 'dist/', src: ['*.js'], dest: 'dist/components' }
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
packager: {
|
babel: {
|
||||||
global: {
|
options: {
|
||||||
type: 'umd',
|
sourceMaps: 'inline',
|
||||||
export: 'Handlebars',
|
loose: ['es6.modules'],
|
||||||
files: [{
|
auxiliaryCommentBefore: 'istanbul ignore next'
|
||||||
cwd: 'lib/',
|
|
||||||
expand: true,
|
|
||||||
src: ['handlebars*.js'],
|
|
||||||
dest: 'dist/'
|
|
||||||
}]
|
|
||||||
},
|
},
|
||||||
|
|
||||||
amd: {
|
amd: {
|
||||||
type: 'amd',
|
options: {
|
||||||
anonymous: true,
|
modules: 'amd'
|
||||||
files: [{
|
},
|
||||||
expand: true,
|
files: [
|
||||||
cwd: 'lib/',
|
{
|
||||||
src: '**/!(index).js',
|
expand: true,
|
||||||
dest: 'dist/amd/'
|
cwd: 'lib/',
|
||||||
}]
|
src: '**/!(index).js',
|
||||||
|
dest: 'dist/amd/'
|
||||||
|
}
|
||||||
|
]
|
||||||
},
|
},
|
||||||
|
|
||||||
cjs: {
|
cjs: {
|
||||||
type: 'cjs',
|
options: {
|
||||||
files: [{
|
modules: 'common'
|
||||||
expand: true,
|
},
|
||||||
cwd: 'lib/',
|
files: [
|
||||||
src: '**/!(index).js',
|
{
|
||||||
dest: 'dist/cjs/'
|
cwd: 'lib/',
|
||||||
}]
|
expand: true,
|
||||||
|
src: '**/!(index).js',
|
||||||
|
dest: 'dist/cjs/'
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
webpack: {
|
||||||
|
options: {
|
||||||
|
context: __dirname,
|
||||||
|
module: {
|
||||||
|
loaders: [
|
||||||
|
// the optional 'runtime' transformer tells babel to require the runtime instead of inlining it.
|
||||||
|
{
|
||||||
|
test: /\.jsx?$/,
|
||||||
|
exclude: /node_modules/,
|
||||||
|
loader:
|
||||||
|
'babel-loader?optional=runtime&loose=es6.modules&auxiliaryCommentBefore=istanbul%20ignore%20next'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
output: {
|
||||||
|
path: 'dist/',
|
||||||
|
library: 'Handlebars',
|
||||||
|
libraryTarget: 'umd'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
handlebars: {
|
||||||
|
entry: './lib/handlebars.js',
|
||||||
|
output: {
|
||||||
|
filename: 'handlebars.js'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
runtime: {
|
||||||
|
entry: './lib/handlebars.runtime.js',
|
||||||
|
output: {
|
||||||
|
filename: 'handlebars.runtime.js'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
requirejs: {
|
requirejs: {
|
||||||
options: {
|
options: {
|
||||||
optimize: "none",
|
optimize: 'none',
|
||||||
baseUrl: "dist/amd/"
|
baseUrl: 'dist/amd/'
|
||||||
},
|
},
|
||||||
dist: {
|
dist: {
|
||||||
options: {
|
options: {
|
||||||
name: "handlebars",
|
name: 'handlebars',
|
||||||
out: "dist/handlebars.amd.js"
|
out: 'dist/handlebars.amd.js'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
runtime: {
|
runtime: {
|
||||||
options: {
|
options: {
|
||||||
name: "handlebars.runtime",
|
name: 'handlebars.runtime',
|
||||||
out: "dist/handlebars.runtime.amd.js"
|
out: 'dist/handlebars.runtime.amd.js'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -97,18 +132,20 @@ module.exports = function(grunt) {
|
|||||||
options: {
|
options: {
|
||||||
mangle: true,
|
mangle: true,
|
||||||
compress: true,
|
compress: true,
|
||||||
preserveComments: 'some'
|
preserveComments: /(?:^!|@(?:license|preserve|cc_on))/
|
||||||
},
|
},
|
||||||
dist: {
|
dist: {
|
||||||
files: [{
|
files: [
|
||||||
cwd: 'dist/',
|
{
|
||||||
expand: true,
|
cwd: 'dist/',
|
||||||
src: ['handlebars*.js', '!*.min.js'],
|
expand: true,
|
||||||
dest: 'dist/',
|
src: ['handlebars*.js', '!*.min.js'],
|
||||||
rename: function(dest, src) {
|
dest: 'dist/',
|
||||||
return dest + src.replace(/\.js$/, '.min.js');
|
rename: function(dest, src) {
|
||||||
|
return dest + src.replace(/\.js$/, '.min.js');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}]
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -128,34 +165,10 @@ module.exports = function(grunt) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
'saucelabs-mocha': {
|
|
||||||
all: {
|
shell: {
|
||||||
options: {
|
integrationTests: {
|
||||||
build: process.env.TRAVIS_JOB_ID,
|
command: './tests/integration/run-integration-tests.sh'
|
||||||
urls: ['http://localhost:9999/spec/?headless=true', 'http://localhost:9999/spec/amd.html?headless=true'],
|
|
||||||
detailedError: true,
|
|
||||||
concurrency: 2,
|
|
||||||
browsers: [
|
|
||||||
{browserName: 'chrome'},
|
|
||||||
{browserName: 'firefox'},
|
|
||||||
{browserName: 'safari', version: 7, platform: 'OS X 10.9'},
|
|
||||||
{browserName: 'safari', version: 6, platform: 'OS X 10.8'},
|
|
||||||
{browserName: 'internet explorer', version: 11, platform: 'Windows 8.1'},
|
|
||||||
{browserName: 'internet explorer', version: 10, platform: 'Windows 8'},
|
|
||||||
{browserName: 'internet explorer', version: 9, platform: 'Windows 7'}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
sanity: {
|
|
||||||
options: {
|
|
||||||
build: process.env.TRAVIS_JOB_ID,
|
|
||||||
urls: ['http://localhost:9999/spec/umd.html?headless=true', 'http://localhost:9999/spec/amd-runtime.html?headless=true', 'http://localhost:9999/spec/umd-runtime.html?headless=true'],
|
|
||||||
detailedError: true,
|
|
||||||
concurrency: 2,
|
|
||||||
browsers: [
|
|
||||||
{browserName: 'chrome'}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -166,44 +179,59 @@ module.exports = function(grunt) {
|
|||||||
},
|
},
|
||||||
|
|
||||||
files: ['src/*', 'lib/**/*.js', 'spec/**/*.js'],
|
files: ['src/*', 'lib/**/*.js', 'spec/**/*.js'],
|
||||||
tasks: ['build', 'amd', 'tests', 'test']
|
tasks: ['on-file-change']
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Build a new version of the library
|
|
||||||
this.registerTask('build', "Builds a distributable version of the current project", [
|
|
||||||
'parser',
|
|
||||||
'node',
|
|
||||||
'globals',
|
|
||||||
'jshint']);
|
|
||||||
|
|
||||||
this.registerTask('amd', ['packager:amd', 'requirejs']);
|
|
||||||
this.registerTask('node', ['packager:cjs']);
|
|
||||||
this.registerTask('globals', ['packager:global']);
|
|
||||||
this.registerTask('tests', ['concat:tests']);
|
|
||||||
|
|
||||||
this.registerTask('release', 'Build final packages', ['amd', 'jshint', 'uglify', 'copy:dist', 'copy:components', 'copy:cdnjs']);
|
|
||||||
|
|
||||||
// Load tasks from npm
|
// Load tasks from npm
|
||||||
grunt.loadNpmTasks('grunt-contrib-clean');
|
grunt.loadNpmTasks('grunt-contrib-clean');
|
||||||
grunt.loadNpmTasks('grunt-contrib-concat');
|
grunt.loadNpmTasks('grunt-contrib-concat');
|
||||||
grunt.loadNpmTasks('grunt-contrib-connect');
|
grunt.loadNpmTasks('grunt-contrib-connect');
|
||||||
grunt.loadNpmTasks('grunt-contrib-copy');
|
grunt.loadNpmTasks('grunt-contrib-copy');
|
||||||
grunt.loadNpmTasks('grunt-contrib-requirejs');
|
grunt.loadNpmTasks('grunt-contrib-requirejs');
|
||||||
grunt.loadNpmTasks('grunt-contrib-jshint');
|
|
||||||
grunt.loadNpmTasks('grunt-contrib-uglify');
|
grunt.loadNpmTasks('grunt-contrib-uglify');
|
||||||
grunt.loadNpmTasks('grunt-contrib-watch');
|
grunt.loadNpmTasks('grunt-contrib-watch');
|
||||||
grunt.loadNpmTasks('grunt-saucelabs');
|
grunt.loadNpmTasks('grunt-babel');
|
||||||
grunt.loadNpmTasks('es6-module-packager');
|
grunt.loadNpmTasks('grunt-shell');
|
||||||
|
grunt.loadNpmTasks('grunt-webpack');
|
||||||
|
|
||||||
grunt.task.loadTasks('tasks');
|
grunt.task.loadTasks('tasks');
|
||||||
|
|
||||||
grunt.registerTask('bench', ['metrics']);
|
grunt.registerTask('node', ['babel:cjs']);
|
||||||
grunt.registerTask('sauce', process.env.SAUCE_USERNAME ? ['tests', 'connect', 'saucelabs-mocha'] : []);
|
grunt.registerTask('amd', ['babel:amd', 'requirejs']);
|
||||||
|
grunt.registerTask('globals', ['webpack']);
|
||||||
|
grunt.registerTask('release', 'Build final packages', [
|
||||||
|
'uglify',
|
||||||
|
'test:min',
|
||||||
|
'copy:dist',
|
||||||
|
'copy:components',
|
||||||
|
'copy:cdnjs'
|
||||||
|
]);
|
||||||
|
|
||||||
grunt.registerTask('travis', process.env.PUBLISH ? ['default', 'sauce', 'metrics', 'publish:latest'] : ['default']);
|
// Requires secret properties from .travis.yaml
|
||||||
|
grunt.registerTask('extensive-tests-and-publish-to-aws', [
|
||||||
|
'default',
|
||||||
|
'shell:integrationTests',
|
||||||
|
'metrics',
|
||||||
|
'publish-to-aws'
|
||||||
|
]);
|
||||||
|
|
||||||
|
grunt.registerTask('on-file-change', ['build', 'concat:tests', 'test']);
|
||||||
|
|
||||||
|
// === Primary tasks ===
|
||||||
grunt.registerTask('dev', ['clean', 'connect', 'watch']);
|
grunt.registerTask('dev', ['clean', 'connect', 'watch']);
|
||||||
grunt.registerTask('default', ['clean', 'build', 'test', 'release']);
|
grunt.registerTask('default', ['clean', 'build', 'test', 'release']);
|
||||||
|
grunt.registerTask('test', ['test:bin', 'test:cov']);
|
||||||
|
grunt.registerTask('bench', ['metrics']);
|
||||||
|
grunt.registerTask('prepare', ['build', 'concat:tests']);
|
||||||
|
grunt.registerTask(
|
||||||
|
'build',
|
||||||
|
'Builds a distributable version of the current project',
|
||||||
|
['parser', 'node', 'amd', 'globals']
|
||||||
|
);
|
||||||
|
grunt.registerTask('integration-tests', [
|
||||||
|
'default',
|
||||||
|
'shell:integrationTests'
|
||||||
|
]);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
Copyright (C) 2011-2014 by Yehuda Katz
|
Copyright (C) 2011-2019 by Yehuda Katz
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
|||||||
+34
-272
@@ -1,37 +1,29 @@
|
|||||||
[](https://travis-ci.org/wycats/handlebars.js)
|
[](https://github.com/handlebars-lang/handlebars.js/actions/workflows/ci.yml)
|
||||||
[](https://saucelabs.com/u/handlebars)
|
[](https://www.jsdelivr.com/package/npm/handlebars)
|
||||||
|
[](https://www.npmjs.com/package/handlebars)
|
||||||
|
[](https://www.npmjs.com/package/handlebars)
|
||||||
|
[](https://bundlephobia.com/package/handlebars)
|
||||||
|
[](https://packagephobia.com/result?p=handlebars)
|
||||||
|
|
||||||
Handlebars.js
|
Handlebars.js
|
||||||
=============
|
=============
|
||||||
|
|
||||||
Handlebars.js is an extension to the [Mustache templating
|
Handlebars provides the power necessary to let you build **semantic templates** effectively with no frustration.
|
||||||
language](http://mustache.github.com/) created by Chris Wanstrath.
|
Handlebars is largely compatible with Mustache templates. In most cases it is possible to swap out Mustache with Handlebars and continue using your current templates.
|
||||||
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
|
Checkout the official Handlebars docs site at
|
||||||
[http://www.handlebarsjs.com](http://www.handlebarsjs.com).
|
[handlebarsjs.com](https://handlebarsjs.com) and try our [live demo](https://handlebarsjs.com/playground.html).
|
||||||
|
|
||||||
Installing
|
Installing
|
||||||
----------
|
----------
|
||||||
Installing Handlebars is easy. Simply download the package [from the official site](http://handlebarsjs.com/) or the [bower repository][bower-repo] and add it to your web pages (you should usually use the most recent version).
|
|
||||||
|
|
||||||
Alternatively, if you prefer having the latest version of handlebars from
|
See our [installation documentation](https://handlebarsjs.com/installation/).
|
||||||
the 'master' branch, passing builds of the 'master' branch are automatically
|
|
||||||
published to S3. You may download the latest passing master build by grabbing
|
|
||||||
a `handlebars-latest.js` file from the [builds page][builds-page]. When the
|
|
||||||
build is published, it is also available as a `handlebars-gitSHA.js` file on
|
|
||||||
the builds page if you need a version to refer to others.
|
|
||||||
`handlebars-runtime.js` builds are also available.
|
|
||||||
|
|
||||||
**Note**: The S3 builds page is provided as a convenience for the community,
|
|
||||||
but you should not use it for hosting Handlebars in production.
|
|
||||||
|
|
||||||
Usage
|
Usage
|
||||||
-----
|
-----
|
||||||
In general, the syntax of Handlebars.js templates is a superset
|
In general, the syntax of Handlebars.js templates is a superset
|
||||||
of Mustache templates. For basic syntax, check out the [Mustache
|
of Mustache templates. For basic syntax, check out the [Mustache
|
||||||
manpage](http://mustache.github.com/mustache.5.html).
|
manpage](https://mustache.github.io/mustache.5.html).
|
||||||
|
|
||||||
Once you have a template, use the `Handlebars.compile` method to compile
|
Once you have a template, use the `Handlebars.compile` method to compile
|
||||||
the template into a function. The generated function takes a context
|
the template into a function. The generated function takes a context
|
||||||
@@ -55,266 +47,34 @@ var result = template(data);
|
|||||||
// </ul>
|
// </ul>
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Full documentation and more examples are at [handlebarsjs.com](https://handlebarsjs.com/).
|
||||||
|
|
||||||
Registering Helpers
|
Precompiling Templates
|
||||||
-------------------
|
----------------------
|
||||||
|
|
||||||
You can register helpers that Handlebars will use when evaluating your
|
|
||||||
template. Here's an example, which assumes that your objects have a URL
|
|
||||||
embedded in them, as well as the text for a link:
|
|
||||||
|
|
||||||
```js
|
|
||||||
Handlebars.registerHelper('link_to', function() {
|
|
||||||
return new Handlebars.SafeString("<a href='" + Handlebars.Utils.escapeExpression(this.url) + "'>" + Handlebars.Utils.escapeExpression(this.body) + "</a>");
|
|
||||||
});
|
|
||||||
|
|
||||||
var context = { posts: [{url: "/hello-world", body: "Hello World!"}] };
|
|
||||||
var source = "<ul>{{#posts}}<li>{{link_to}}</li>{{/posts}}</ul>"
|
|
||||||
|
|
||||||
var template = Handlebars.compile(source);
|
|
||||||
template(context);
|
|
||||||
|
|
||||||
// Would render:
|
|
||||||
//
|
|
||||||
// <ul>
|
|
||||||
// <li><a href='/hello-world'>Hello World!</a></li>
|
|
||||||
// </ul>
|
|
||||||
```
|
|
||||||
|
|
||||||
Helpers take precedence over fields defined on the context. To access a field
|
|
||||||
that is masked by a helper, a path reference may be used. In the example above
|
|
||||||
a field named `link_to` on the `context` object would be referenced using:
|
|
||||||
|
|
||||||
```
|
|
||||||
{{./link_to}}
|
|
||||||
```
|
|
||||||
|
|
||||||
Escaping
|
|
||||||
--------
|
|
||||||
|
|
||||||
By default, the `{{expression}}` syntax will escape its contents. This
|
|
||||||
helps to protect you against accidental XSS problems caused by malicious
|
|
||||||
data passed from the server as JSON.
|
|
||||||
|
|
||||||
To explicitly *not* escape the contents, use the triple-mustache
|
|
||||||
(`{{{}}}`). You have seen this used in the above example.
|
|
||||||
|
|
||||||
|
Handlebars allows templates to be precompiled and included as javascript code rather than the handlebars template allowing for faster startup time. Full details are located [here](https://handlebarsjs.com/installation/precompilation.html).
|
||||||
|
|
||||||
Differences Between Handlebars.js and Mustache
|
Differences Between Handlebars.js and Mustache
|
||||||
----------------------------------------------
|
----------------------------------------------
|
||||||
Handlebars.js adds a couple of additional features to make writing
|
Handlebars.js adds a couple of additional features to make writing
|
||||||
templates easier and also changes a tiny detail of how partials work.
|
templates easier and also changes a tiny detail of how partials work.
|
||||||
|
|
||||||
### Paths
|
- [Nested Paths](https://handlebarsjs.com/guide/expressions.html#path-expressions)
|
||||||
|
- [Helpers](https://handlebarsjs.com/guide/expressions.html#helpers)
|
||||||
Handlebars.js supports an extended expression syntax that we call paths.
|
- [Block Expressions](https://handlebarsjs.com/guide/block-helpers.html#basic-blocks)
|
||||||
Paths are made up of typical expressions and . characters. Expressions
|
- [Literal Values](https://handlebarsjs.com/guide/expressions.html#literal-segments)
|
||||||
allow you to not only display data from the current context, but to
|
- [Delimited Comments](https://handlebarsjs.com/guide/#template-comments)
|
||||||
display data from contexts that are descendants and ancestors of the
|
|
||||||
current context.
|
|
||||||
|
|
||||||
To display data from descendant contexts, use the `.` character. So, for
|
|
||||||
example, if your data were structured like:
|
|
||||||
|
|
||||||
```js
|
|
||||||
var data = {"person": { "name": "Alan" }, "company": {"name": "Rad, Inc." } };
|
|
||||||
```
|
|
||||||
|
|
||||||
You could display the person's name from the top-level context with the
|
|
||||||
following expression:
|
|
||||||
|
|
||||||
```
|
|
||||||
{{person.name}}
|
|
||||||
```
|
|
||||||
|
|
||||||
You can backtrack using `../`. For example, if you've already traversed
|
|
||||||
into the person object you could still display the company's name with
|
|
||||||
an expression like `{{../company.name}}`, so:
|
|
||||||
|
|
||||||
```
|
|
||||||
{{#with person}}{{name}} - {{../company.name}}{{/person}}
|
|
||||||
```
|
|
||||||
|
|
||||||
would render:
|
|
||||||
|
|
||||||
```
|
|
||||||
Alan - Rad, Inc.
|
|
||||||
```
|
|
||||||
|
|
||||||
### Strings
|
|
||||||
|
|
||||||
When calling a helper, you can pass paths or Strings as parameters. For
|
|
||||||
instance:
|
|
||||||
|
|
||||||
```js
|
|
||||||
Handlebars.registerHelper('link_to', function(title, options) {
|
|
||||||
return "<a href='/posts" + this.url + "'>" + title + "!</a>"
|
|
||||||
});
|
|
||||||
|
|
||||||
var context = { posts: [{url: "/hello-world", body: "Hello World!"}] };
|
|
||||||
var source = '<ul>{{#posts}}<li>{{{link_to "Post"}}}</li>{{/posts}}</ul>'
|
|
||||||
|
|
||||||
var template = Handlebars.compile(source);
|
|
||||||
template(context);
|
|
||||||
|
|
||||||
// Would render:
|
|
||||||
//
|
|
||||||
// <ul>
|
|
||||||
// <li><a href='/posts/hello-world'>Post!</a></li>
|
|
||||||
// </ul>
|
|
||||||
```
|
|
||||||
|
|
||||||
When you pass a String as a parameter to a helper, the literal String
|
|
||||||
gets passed to the helper function.
|
|
||||||
|
|
||||||
|
|
||||||
### Block Helpers
|
|
||||||
|
|
||||||
Handlebars.js also adds the ability to define block helpers. Block
|
|
||||||
helpers are functions that can be called from anywhere in the template.
|
|
||||||
Here's an example:
|
|
||||||
|
|
||||||
```js
|
|
||||||
var source = "<ul>{{#people}}<li>{{#link}}{{name}}{{/link}}</li>{{/people}}</ul>";
|
|
||||||
Handlebars.registerHelper('link', function(options) {
|
|
||||||
return '<a href="/people/' + this.id + '">' + options.fn(this) + '</a>';
|
|
||||||
});
|
|
||||||
var template = Handlebars.compile(source);
|
|
||||||
|
|
||||||
var data = { "people": [
|
|
||||||
{ "name": "Alan", "id": 1 },
|
|
||||||
{ "name": "Yehuda", "id": 2 }
|
|
||||||
]};
|
|
||||||
template(data);
|
|
||||||
|
|
||||||
// Should render:
|
|
||||||
// <ul>
|
|
||||||
// <li><a href="/people/1">Alan</a></li>
|
|
||||||
// <li><a href="/people/2">Yehuda</a></li>
|
|
||||||
// </ul>
|
|
||||||
```
|
|
||||||
|
|
||||||
Whenever the block helper is called it is given one or more parameters,
|
|
||||||
any arguments that are passed 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
|
|
||||||
|
|
||||||
You can register additional templates as partials, which will be used by
|
|
||||||
Handlebars when it encounters a partial (`{{> partialName}}`). Partials
|
|
||||||
can either be String templates or compiled template functions. Here's an
|
|
||||||
example:
|
|
||||||
|
|
||||||
```js
|
|
||||||
var source = "<ul>{{#people}}<li>{{> link}}</li>{{/people}}</ul>";
|
|
||||||
|
|
||||||
Handlebars.registerPartial('link', '<a href="/people/{{id}}">{{name}}</a>')
|
|
||||||
var template = Handlebars.compile(source);
|
|
||||||
|
|
||||||
var data = { "people": [
|
|
||||||
{ "name": "Alan", "id": 1 },
|
|
||||||
{ "name": "Yehuda", "id": 2 }
|
|
||||||
]};
|
|
||||||
|
|
||||||
template(data);
|
|
||||||
|
|
||||||
// Should render:
|
|
||||||
// <ul>
|
|
||||||
// <li><a href="/people/1">Alan</a></li>
|
|
||||||
// <li><a href="/people/2">Yehuda</a></li>
|
|
||||||
// </ul>
|
|
||||||
```
|
|
||||||
|
|
||||||
### 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>
|
|
||||||
```
|
|
||||||
|
|
||||||
|
Block expressions have the same syntax as mustache sections but should not be confused with one another. Sections are akin to an implicit `each` or `with` statement depending on the input data and helpers are explicit pieces of code that are free to implement whatever behavior they like. The [mustache spec](https://mustache.github.io/mustache.5.html) defines the exact behavior of sections. In the case of name conflicts, helpers are given priority.
|
||||||
|
|
||||||
### Compatibility
|
### Compatibility
|
||||||
|
|
||||||
There are a few Mustache behaviors that Handlebars does not implement.
|
There are a few Mustache behaviors that Handlebars does not implement.
|
||||||
- Handlebars deviates from Mustache slightly in that it does not perform recursive lookup by default. The compile time `compat` flag must be set to enable this functionality. Users should note that there is a performance cost for enabling this flag. The exact cost varies by template, but it's recommended that performance sensitive operations should avoid this mode and instead opt for explicit path references.
|
- Handlebars deviates from Mustache slightly in that it does not perform recursive lookup by default. The compile time `compat` flag must be set to enable this functionality. Users should note that there is a performance cost for enabling this flag. The exact cost varies by template, but it's recommended that performance sensitive operations should avoid this mode and instead opt for explicit path references.
|
||||||
- The optional Mustache-style lambdas are not supported. Instead Handlebars provides it's own lambda resolution that follows the behaviors of helpers.
|
- The optional Mustache-style lambdas are not supported. Instead Handlebars provides its own lambda resolution that follows the behaviors of helpers.
|
||||||
- Alternative delimeters are not supported.
|
- Alternative delimiters are not supported.
|
||||||
|
|
||||||
|
|
||||||
Precompiling Templates
|
|
||||||
----------------------
|
|
||||||
|
|
||||||
Handlebars allows templates to be precompiled and included as javascript
|
|
||||||
code rather than the handlebars template allowing for faster startup time.
|
|
||||||
|
|
||||||
### Installation
|
|
||||||
The precompiler script may be installed via npm using the `npm install -g handlebars`
|
|
||||||
command.
|
|
||||||
|
|
||||||
### Usage
|
|
||||||
|
|
||||||
<pre>
|
|
||||||
Precompile handlebar templates.
|
|
||||||
Usage: handlebars template...
|
|
||||||
|
|
||||||
Options:
|
|
||||||
-a, --amd Create an AMD format function (allows loading with RequireJS) [boolean]
|
|
||||||
-f, --output Output File [string]
|
|
||||||
-k, --known Known helpers [string]
|
|
||||||
-o, --knownOnly Known helpers only [boolean]
|
|
||||||
-m, --min Minimize output [boolean]
|
|
||||||
-s, --simple Output template function only. [boolean]
|
|
||||||
-r, --root Template root. Base value that will be stripped from template names. [string]
|
|
||||||
-c, --commonjs Exports CommonJS style, path to Handlebars module [string]
|
|
||||||
-h, --handlebarPath Path to handlebar.js (only valid for amd-style) [string]
|
|
||||||
-n, --namespace Template namespace [string]
|
|
||||||
-p, --partial Compiling a partial template [boolean]
|
|
||||||
-d, --data Include data when compiling [boolean]
|
|
||||||
-e, --extension Template extension. [string]
|
|
||||||
-b, --bom Removes the BOM (Byte Order Mark) from the beginning of the templates. [boolean]
|
|
||||||
</pre>
|
|
||||||
|
|
||||||
If using the precompiler's normal mode, the resulting templates will be
|
|
||||||
stored to the `Handlebars.templates` object using the relative template
|
|
||||||
name sans the extension. These templates may be executed in the same
|
|
||||||
manner as templates.
|
|
||||||
|
|
||||||
If using the simple mode the precompiler will generate a single
|
|
||||||
javascript method. To execute this method it must be passed to
|
|
||||||
the `Handlebars.template` method and the resulting object may be used as normal.
|
|
||||||
|
|
||||||
### Optimizations
|
|
||||||
|
|
||||||
- Rather than using the full _handlebars.js_ library, implementations that
|
|
||||||
do not need to compile templates at runtime may include _handlebars.runtime.js_
|
|
||||||
whose min+gzip size is approximately 1k.
|
|
||||||
- If a helper is known to exist in the target environment they may be defined
|
|
||||||
using the `--known name` argument may be used to optimize accesses to these
|
|
||||||
helpers for size and speed.
|
|
||||||
- When all helpers are known in advance the `--knownOnly` argument may be used
|
|
||||||
to optimize all block helper references.
|
|
||||||
- Implementations that do not use `@data` variables can improve performance of
|
|
||||||
iteration centric templates by specifying `{data: false}` in the compiler options.
|
|
||||||
|
|
||||||
Supported Environments
|
Supported Environments
|
||||||
----------------------
|
----------------------
|
||||||
|
|
||||||
@@ -330,8 +90,6 @@ Handlebars has been designed to work in any ECMAScript 3 environment. This inclu
|
|||||||
Older versions and other runtimes are likely to work but have not been formally
|
Older versions and other runtimes are likely to work but have not been formally
|
||||||
tested. The compiler requires `JSON.stringify` to be implemented natively or via a polyfill. If using the precompiler this is not necessary.
|
tested. The compiler requires `JSON.stringify` to be implemented natively or via a polyfill. If using the precompiler this is not necessary.
|
||||||
|
|
||||||
[](https://saucelabs.com/u/handlebars)
|
|
||||||
|
|
||||||
Performance
|
Performance
|
||||||
-----------
|
-----------
|
||||||
|
|
||||||
@@ -343,18 +101,18 @@ does have some big performance advantages. Justin Marney, a.k.a.
|
|||||||
[gotascii](http://github.com/gotascii), confirmed that with an
|
[gotascii](http://github.com/gotascii), confirmed that with an
|
||||||
[independent test](http://sorescode.com/2010/09/12/benchmarks.html). The
|
[independent test](http://sorescode.com/2010/09/12/benchmarks.html). The
|
||||||
rewritten Handlebars (current version) is faster than the old version,
|
rewritten Handlebars (current version) is faster than the old version,
|
||||||
with many [performance tests](https://travis-ci.org/wycats/handlebars.js/builds/33392182#L538) being 5 to 7 times faster than the Mustache equivalent.
|
with many performance tests being 5 to 7 times faster than the Mustache equivalent.
|
||||||
|
|
||||||
|
|
||||||
Upgrading
|
Upgrading
|
||||||
---------
|
---------
|
||||||
|
|
||||||
See [release-notes.md](https://github.com/wycats/handlebars.js/blob/master/release-notes.md) for upgrade notes.
|
See [release-notes.md](https://github.com/handlebars-lang/handlebars.js/blob/master/release-notes.md) for upgrade notes.
|
||||||
|
|
||||||
Known Issues
|
Known Issues
|
||||||
------------
|
------------
|
||||||
|
|
||||||
See [FAQ.md](https://github.com/wycats/handlebars.js/blob/master/FAQ.md) for known issues and common pitfalls.
|
See [FAQ.md](https://github.com/handlebars-lang/handlebars.js/blob/master/FAQ.md) for known issues and common pitfalls.
|
||||||
|
|
||||||
|
|
||||||
Handlebars in the Wild
|
Handlebars in the Wild
|
||||||
@@ -363,7 +121,9 @@ Handlebars in the Wild
|
|||||||
* [Assemble](http://assemble.io), by [@jonschlinkert](https://github.com/jonschlinkert)
|
* [Assemble](http://assemble.io), by [@jonschlinkert](https://github.com/jonschlinkert)
|
||||||
and [@doowb](https://github.com/doowb), is a static site generator that uses Handlebars.js
|
and [@doowb](https://github.com/doowb), is a static site generator that uses Handlebars.js
|
||||||
as its template engine.
|
as its template engine.
|
||||||
|
* [Cory](https://github.com/leo/cory), by [@leo](https://github.com/leo), is another tiny static site generator
|
||||||
* [CoSchedule](http://coschedule.com) An editorial calendar for WordPress that uses Handlebars.js
|
* [CoSchedule](http://coschedule.com) An editorial calendar for WordPress that uses Handlebars.js
|
||||||
|
* [dashbars](https://github.com/pismute/dashbars) A modern helper library for Handlebars.js.
|
||||||
* [Ember.js](http://www.emberjs.com) makes Handlebars.js the primary way to
|
* [Ember.js](http://www.emberjs.com) makes Handlebars.js the primary way to
|
||||||
structure your views, also with automatic data binding support.
|
structure your views, also with automatic data binding support.
|
||||||
* [Ghost](https://ghost.org/) Just a blogging platform.
|
* [Ghost](https://ghost.org/) Just a blogging platform.
|
||||||
@@ -371,6 +131,7 @@ Handlebars in the Wild
|
|||||||
from Les Hill (@leshill).
|
from Les Hill (@leshill).
|
||||||
* [handlebars-helpers](https://github.com/assemble/handlebars-helpers) is an extensive library
|
* [handlebars-helpers](https://github.com/assemble/handlebars-helpers) is an extensive library
|
||||||
with 100+ handlebars helpers.
|
with 100+ handlebars helpers.
|
||||||
|
* [handlebars-layouts](https://github.com/shannonmoeller/handlebars-layouts) is a set of helpers which implement extendible and embeddable layout blocks as seen in other popular templating languages.
|
||||||
* [hbs](http://github.com/donpark/hbs): An Express.js view engine adapter for Handlebars.js,
|
* [hbs](http://github.com/donpark/hbs): An Express.js view engine adapter for Handlebars.js,
|
||||||
from Don Park.
|
from Don Park.
|
||||||
* [koa-hbs](https://github.com/jwilm/koa-hbs): [koa](https://github.com/koajs/koa) generator based
|
* [koa-hbs](https://github.com/jwilm/koa-hbs): [koa](https://github.com/koajs/koa) generator based
|
||||||
@@ -381,13 +142,16 @@ Handlebars in the Wild
|
|||||||
Handlebars.js with [jQuery](http://jquery.com/).
|
Handlebars.js with [jQuery](http://jquery.com/).
|
||||||
* [Lumbar](http://walmartlabs.github.io/lumbar) provides easy module-based template management for
|
* [Lumbar](http://walmartlabs.github.io/lumbar) provides easy module-based template management for
|
||||||
handlebars projects.
|
handlebars projects.
|
||||||
|
* [Marionette.Handlebars](https://github.com/hashchange/marionette.handlebars) adds support for Handlebars and Mustache templates to Marionette.
|
||||||
* [sammy.js](http://github.com/quirkey/sammy) by Aaron Quint, a.k.a. quirkey,
|
* [sammy.js](http://github.com/quirkey/sammy) by Aaron Quint, a.k.a. quirkey,
|
||||||
supports Handlebars.js as one of its template plugins.
|
supports Handlebars.js as one of its template plugins.
|
||||||
* [SproutCore](http://www.sproutcore.com) uses Handlebars.js as its main
|
* [SproutCore](http://www.sproutcore.com) uses Handlebars.js as its main
|
||||||
templating engine, extending it with automatic data binding support.
|
templating engine, extending it with automatic data binding support.
|
||||||
* [YUI](http://yuilibrary.com/yui/docs/handlebars/) implements a port of handlebars
|
* [YUI](http://yuilibrary.com/yui/docs/handlebars/) implements a port of handlebars
|
||||||
* [Swag](https://github.com/elving/swag) by [@elving](https://github.com/elving) is a growing collection of helpers for handlebars.js. Give your handlebars.js templates some swag son!
|
* [Swag](https://github.com/elving/swag) by [@elving](https://github.com/elving) is a growing collection of helpers for handlebars.js. Give your handlebars.js templates some swag son!
|
||||||
* [DOMBars](https://github.com/blakeembrey/dombars) is a DOM-based templating engine built on the Handlebars parser and runtime
|
* [DOMBars](https://github.com/blakeembrey/dombars) is a DOM-based templating engine built on the Handlebars parser and runtime **DEPRECATED**
|
||||||
|
* [promised-handlebars](https://github.com/nknapp/promised-handlebars) is a wrapper for Handlebars that allows helpers to return Promises.
|
||||||
|
* [just-handlebars-helpers](https://github.com/leapfrogtechnology/just-handlebars-helpers) A fully tested lightweight package with common Handlebars helpers.
|
||||||
|
|
||||||
External Resources
|
External Resources
|
||||||
------------------
|
------------------
|
||||||
@@ -400,6 +164,4 @@ License
|
|||||||
-------
|
-------
|
||||||
Handlebars.js is released under the MIT license.
|
Handlebars.js is released under the MIT license.
|
||||||
|
|
||||||
[bower-repo]: https://github.com/components/handlebars.js
|
[pull-request]: https://github.com/handlebars-lang/handlebars.js/pull/new/master
|
||||||
[builds-page]: http://builds.handlebarsjs.com.s3.amazonaws.com/bucket-listing.html?sort=lastmod&sortdir=desc
|
|
||||||
[pull-request]: https://github.com/wycats/handlebars.js/pull/new/master
|
|
||||||
|
|||||||
@@ -1,12 +0,0 @@
|
|||||||
module.exports = {
|
|
||||||
helpers: {
|
|
||||||
foo: function(options) {
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
},
|
|
||||||
context: {
|
|
||||||
bar: true
|
|
||||||
},
|
|
||||||
|
|
||||||
handlebars: '{{foo person "person" 1 true foo=bar foo="person" foo=1 foo=true}}'
|
|
||||||
};
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
module.exports = {
|
|
||||||
context: { names: [{name: "Moe"}, {name: "Larry"}, {name: "Curly"}, {name: "Shemp"}] },
|
|
||||||
handlebars: "{{#each names}}{{name}}{{/each}}",
|
|
||||||
dust: "{#names}{name}{/names}",
|
|
||||||
mustache: "{{#names}}{{name}}{{/names}}",
|
|
||||||
eco: "<% for item in @names: %><%= item.name %><% end %>"
|
|
||||||
};
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
module.exports = {
|
|
||||||
context: { names: [{name: "Moe"}, {name: "Larry"}, {name: "Curly"}, {name: "Shemp"}] },
|
|
||||||
handlebars: "{{#names}}{{name}}{{/names}}"
|
|
||||||
}
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
module.exports = {
|
|
||||||
context: { names: [{name: "Moe"}, {name: "Larry"}, {name: "Curly"}, {name: "Shemp"}] },
|
|
||||||
handlebars: "{{#each names}}{{@index}}{{name}}{{/each}}"
|
|
||||||
}
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
module.exports = {
|
|
||||||
context: { names: [{name: "Moe"}, {name: "Larry"}, {name: "Curly"}, {name: "Shemp"}], foo: 'bar' },
|
|
||||||
handlebars: "{{#each names}}{{../foo}}{{/each}}",
|
|
||||||
mustache: "{{#names}}{{foo}}{{/names}}",
|
|
||||||
eco: "<% for item in @names: %><%= @foo %><% end %>"
|
|
||||||
};
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
module.exports = {
|
|
||||||
context: { names: [{bat: 'foo', name: ["Moe"]}, {bat: 'foo', name: ["Larry"]}, {bat: 'foo', name: ["Curly"]}, {bat: 'foo', name: ["Shemp"]}], foo: 'bar' },
|
|
||||||
handlebars: "{{#each names}}{{#each name}}{{../bat}}{{../../foo}}{{/each}}{{/each}}",
|
|
||||||
mustache: "{{#names}}{{#name}}{{bat}}{{foo}}{{/name}}{{/names}}",
|
|
||||||
eco: "<% for item in @names: %><% for child in item.name: %><%= item.bat %><%= @foo %><% end %><% end %>"
|
|
||||||
};
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
module.exports = {
|
|
||||||
context: { person: { name: "Larry", age: 45 } },
|
|
||||||
handlebars: "{{#person}}{{name}}{{age}}{{/person}}"
|
|
||||||
};
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
module.exports = {
|
|
||||||
context: { person: { name: "Larry", age: 45 } },
|
|
||||||
handlebars: "{{#with person}}{{name}}{{age}}{{/with}}",
|
|
||||||
dust: "{#person}{name}{age}{/person}",
|
|
||||||
eco: "<%= @person.name %><%= @person.age %>",
|
|
||||||
mustache: "{{#person}}{{name}}{{age}}{{/person}}"
|
|
||||||
};
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
module.exports = {
|
|
||||||
context: { name: '1', kids: [{ name: '1.1', kids: [{name: '1.1.1', kids: []}] }] },
|
|
||||||
partials: {
|
|
||||||
mustache: { recursion: "{{name}}{{#kids}}{{>recursion}}{{/kids}}" },
|
|
||||||
handlebars: { recursion: "{{name}}{{#each kids}}{{>recursion}}{{/each}}" }
|
|
||||||
},
|
|
||||||
handlebars: "{{name}}{{#each kids}}{{>recursion}}{{/each}}",
|
|
||||||
dust: "{name}{#kids}{>recursion:./}{/kids}",
|
|
||||||
mustache: "{{name}}{{#kids}}{{>recursion}}{{/kids}}"
|
|
||||||
};
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
module.exports = {
|
|
||||||
context: { peeps: [{name: "Moe", count: 15}, {name: "Larry", count: 5}, {name: "Curly", count: 1}] },
|
|
||||||
partials: {
|
|
||||||
mustache: { variables: "Hello {{name}}! You have {{count}} new messages." },
|
|
||||||
handlebars: { variables: "Hello {{name}}! You have {{count}} new messages." }
|
|
||||||
},
|
|
||||||
|
|
||||||
handlebars: "{{#each peeps}}{{>variables}}{{/each}}",
|
|
||||||
dust: "{#peeps}{>variables/}{/peeps}",
|
|
||||||
mustache: "{{#peeps}}{{>variables}}{{/peeps}}"
|
|
||||||
};
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
module.exports = {
|
|
||||||
context: { person: { name: {bar: {baz: "Larry"}}, age: 45 } },
|
|
||||||
handlebars: "{{person.name.bar.baz}}{{person.age}}{{person.foo}}{{animal.age}}",
|
|
||||||
dust: "{person.name.bar.baz}{person.age}{person.foo}{animal.age}",
|
|
||||||
eco: "<%= @person.name.bar.baz %><%= @person.age %><%= @person.foo %><% if @animal: %><%= @animal.age %><% end %>",
|
|
||||||
mustache: "{{person.name.bar.baz}}{{person.age}}{{person.foo}}{{animal.age}}"
|
|
||||||
};
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
module.exports = {
|
|
||||||
context: {},
|
|
||||||
handlebars: "Hello world",
|
|
||||||
dust: "Hello world",
|
|
||||||
mustache: "Hello world",
|
|
||||||
eco: "Hello world"
|
|
||||||
};
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
module.exports = {
|
|
||||||
context: {name: "Mick", count: 30},
|
|
||||||
handlebars: "Hello {{name}}! You have {{count}} new messages.",
|
|
||||||
dust: "Hello {name}! You have {count} new messages.",
|
|
||||||
mustache: "Hello {{name}}! You have {{count}} new messages.",
|
|
||||||
eco: "Hello <%= @name %>! You have <%= @count %> new messages."
|
|
||||||
};
|
|
||||||
|
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
module.exports = {
|
||||||
|
rules: {
|
||||||
|
'no-console': 0,
|
||||||
|
'no-var': 0
|
||||||
|
}
|
||||||
|
};
|
||||||
+172
-91
@@ -1,95 +1,176 @@
|
|||||||
#!/usr/bin/env node
|
#!/usr/bin/env node
|
||||||
|
|
||||||
var optimist = require('optimist')
|
var argv = parseArgs({
|
||||||
.usage('Precompile handlebar templates.\nUsage: $0 template...', {
|
'f': {
|
||||||
'f': {
|
'type': 'string',
|
||||||
'type': 'string',
|
'description': 'Output File',
|
||||||
'description': 'Output File',
|
'alias': 'output'
|
||||||
'alias': 'output'
|
},
|
||||||
},
|
'map': {
|
||||||
'a': {
|
'type': 'string',
|
||||||
'type': 'boolean',
|
'description': 'Source Map File'
|
||||||
'description': 'Exports amd style (require.js)',
|
},
|
||||||
'alias': 'amd'
|
'a': {
|
||||||
},
|
'type': 'boolean',
|
||||||
'c': {
|
'description': 'Exports amd style (require.js)',
|
||||||
'type': 'string',
|
'alias': 'amd'
|
||||||
'description': 'Exports CommonJS style, path to Handlebars module',
|
},
|
||||||
'alias': 'commonjs',
|
'c': {
|
||||||
'default': null
|
'type': 'string',
|
||||||
},
|
'description': 'Exports CommonJS style, path to Handlebars module',
|
||||||
'h': {
|
'alias': 'commonjs',
|
||||||
'type': 'string',
|
'default': null
|
||||||
'description': 'Path to handlebar.js (only valid for amd-style)',
|
},
|
||||||
'alias': 'handlebarPath',
|
'h': {
|
||||||
'default': ''
|
'type': 'string',
|
||||||
},
|
'description': 'Path to handlebar.js (only valid for amd-style)',
|
||||||
'k': {
|
'alias': 'handlebarPath',
|
||||||
'type': 'string',
|
'default': ''
|
||||||
'description': 'Known helpers',
|
},
|
||||||
'alias': 'known'
|
'k': {
|
||||||
},
|
'type': 'string',
|
||||||
'o': {
|
'description': 'Known helpers',
|
||||||
'type': 'boolean',
|
'alias': 'known'
|
||||||
'description': 'Known helpers only',
|
},
|
||||||
'alias': 'knownOnly'
|
'o': {
|
||||||
},
|
'type': 'boolean',
|
||||||
'm': {
|
'description': 'Known helpers only',
|
||||||
'type': 'boolean',
|
'alias': 'knownOnly'
|
||||||
'description': 'Minimize output',
|
},
|
||||||
'alias': 'min'
|
'm': {
|
||||||
},
|
'type': 'boolean',
|
||||||
'n': {
|
'description': 'Minimize output',
|
||||||
'type': 'string',
|
'alias': 'min'
|
||||||
'description': 'Template namespace',
|
},
|
||||||
'alias': 'namespace',
|
'n': {
|
||||||
'default': 'Handlebars.templates'
|
'type': 'string',
|
||||||
},
|
'description': 'Template namespace',
|
||||||
's': {
|
'alias': 'namespace',
|
||||||
'type': 'boolean',
|
'default': 'Handlebars.templates'
|
||||||
'description': 'Output template function only.',
|
},
|
||||||
'alias': 'simple'
|
's': {
|
||||||
},
|
'type': 'boolean',
|
||||||
'r': {
|
'description': 'Output template function only.',
|
||||||
'type': 'string',
|
'alias': 'simple'
|
||||||
'description': 'Template root. Base value that will be stripped from template names.',
|
},
|
||||||
'alias': 'root'
|
'N': {
|
||||||
},
|
'type': 'string',
|
||||||
'p' : {
|
'description': 'Name of passed string templates. Optional if running in a simple mode. Required when operating on multiple templates.',
|
||||||
'type': 'boolean',
|
'alias': 'name'
|
||||||
'description': 'Compiling a partial template',
|
},
|
||||||
'alias': 'partial'
|
'i': {
|
||||||
},
|
'type': 'string',
|
||||||
'd' : {
|
'description': 'Generates a template from the passed CLI argument.\n"-" is treated as a special value and causes stdin to be read for the template value.',
|
||||||
'type': 'boolean',
|
'alias': 'string'
|
||||||
'description': 'Include data when compiling',
|
},
|
||||||
'alias': 'data'
|
'r': {
|
||||||
},
|
'type': 'string',
|
||||||
'e': {
|
'description': 'Template root. Base value that will be stripped from template names.',
|
||||||
'type': 'string',
|
'alias': 'root'
|
||||||
'description': 'Template extension.',
|
},
|
||||||
'alias': 'extension',
|
'p': {
|
||||||
'default': 'handlebars'
|
'type': 'boolean',
|
||||||
},
|
'description': 'Compiling a partial template',
|
||||||
'b': {
|
'alias': 'partial'
|
||||||
'type': 'boolean',
|
},
|
||||||
'description': 'Removes the BOM (Byte Order Mark) from the beginning of the templates.',
|
'd': {
|
||||||
'alias': 'bom'
|
'type': 'boolean',
|
||||||
},
|
'description': 'Include data when compiling',
|
||||||
'v': {
|
'alias': 'data'
|
||||||
'type': 'boolean',
|
},
|
||||||
'description': 'Prints the current compiler version',
|
'e': {
|
||||||
'alias': 'version'
|
'type': 'string',
|
||||||
}
|
'description': 'Template extension.',
|
||||||
})
|
'alias': 'extension',
|
||||||
|
'default': 'handlebars'
|
||||||
|
},
|
||||||
|
'b': {
|
||||||
|
'type': 'boolean',
|
||||||
|
'description': 'Removes the BOM (Byte Order Mark) from the beginning of the templates.',
|
||||||
|
'alias': 'bom'
|
||||||
|
},
|
||||||
|
'v': {
|
||||||
|
'type': 'boolean',
|
||||||
|
'description': 'Prints the current compiler version',
|
||||||
|
'alias': 'version'
|
||||||
|
},
|
||||||
|
'help': {
|
||||||
|
'type': 'boolean',
|
||||||
|
'description': 'Outputs this message'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
.check(function(argv) {
|
argv.files = argv._;
|
||||||
if (argv.version) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
var argv = optimist.argv;
|
|
||||||
argv.templates = argv._;
|
|
||||||
delete argv._;
|
delete argv._;
|
||||||
return require('../lib/precompiler').cli(argv);
|
|
||||||
|
var Precompiler = require('../dist/cjs/precompiler');
|
||||||
|
Precompiler.loadTemplates(argv, function(err, opts) {
|
||||||
|
|
||||||
|
if (err) {
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (opts.help || (!opts.templates.length && !opts.version)) {
|
||||||
|
printUsage(argv._spec, 120);
|
||||||
|
} else {
|
||||||
|
Precompiler.cli(opts);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
function pad(n) {
|
||||||
|
var str = '';
|
||||||
|
while (str.length < n) {
|
||||||
|
str += ' ';
|
||||||
|
}
|
||||||
|
return str;
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseArgs(spec) {
|
||||||
|
var opts = { alias: {}, boolean: [], default: {}, string: [] };
|
||||||
|
|
||||||
|
Object.keys(spec).forEach(function (arg) {
|
||||||
|
var opt = spec[arg];
|
||||||
|
opts[opt.type].push(arg);
|
||||||
|
if ('alias' in opt) opts.alias[arg] = opt.alias;
|
||||||
|
if ('default' in opt) opts.default[arg] = opt.default;
|
||||||
|
});
|
||||||
|
|
||||||
|
var argv = require('minimist')(process.argv.slice(2), opts);
|
||||||
|
argv._spec = spec;
|
||||||
|
return argv;
|
||||||
|
}
|
||||||
|
|
||||||
|
function printUsage(spec, wrap) {
|
||||||
|
var wordwrap = require('wordwrap');
|
||||||
|
|
||||||
|
console.log('Precompile handlebar templates.');
|
||||||
|
console.log('Usage: handlebars [template|directory]...');
|
||||||
|
|
||||||
|
var opts = [];
|
||||||
|
var width = 0;
|
||||||
|
Object.keys(spec).forEach(function (arg) {
|
||||||
|
var opt = spec[arg];
|
||||||
|
|
||||||
|
var name = (arg.length === 1 ? '-' : '--') + arg;
|
||||||
|
if ('alias' in opt) name += ', --' + opt.alias;
|
||||||
|
|
||||||
|
var meta = '[' + opt.type + ']';
|
||||||
|
if ('default' in opt) meta += ' [default: ' + JSON.stringify(opt.default) + ']';
|
||||||
|
|
||||||
|
opts.push({ name: name, desc: opt.description, meta: meta });
|
||||||
|
if (name.length > width) width = name.length;
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log('Options:');
|
||||||
|
opts.forEach(function (opt) {
|
||||||
|
var desc = wordwrap(width + 4, wrap + 1)(opt.desc);
|
||||||
|
|
||||||
|
console.log(' %s%s%s%s%s',
|
||||||
|
opt.name,
|
||||||
|
pad(width - opt.name.length + 2),
|
||||||
|
desc.slice(width + 4),
|
||||||
|
pad(wrap - opt.meta.length - desc.split(/\n/).pop().length),
|
||||||
|
opt.meta
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "handlebars",
|
"name": "handlebars",
|
||||||
"version": "2.0.0-beta.1",
|
"version": "4.7.8",
|
||||||
"main": "handlebars.js",
|
"main": "handlebars.js",
|
||||||
|
"license": "MIT",
|
||||||
"dependencies": {}
|
"dependencies": {}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,8 +10,8 @@ Gem::Specification.new do |gem|
|
|||||||
gem.date = Time.now.strftime("%Y-%m-%d")
|
gem.date = Time.now.strftime("%Y-%m-%d")
|
||||||
gem.description = %q{Handlebars.js source code wrapper for (pre)compilation gems.}
|
gem.description = %q{Handlebars.js source code wrapper for (pre)compilation gems.}
|
||||||
gem.summary = %q{Handlebars.js source code wrapper}
|
gem.summary = %q{Handlebars.js source code wrapper}
|
||||||
gem.homepage = "https://github.com/wycats/handlebars.js/"
|
gem.homepage = "https://github.com/handlebars-lang/handlebars.js/"
|
||||||
gem.version = package["version"]
|
gem.version = package["version"].sub "-", "."
|
||||||
gem.license = "MIT"
|
gem.license = "MIT"
|
||||||
|
|
||||||
gem.files = [
|
gem.files = [
|
||||||
|
|||||||
@@ -2,10 +2,10 @@
|
|||||||
<package>
|
<package>
|
||||||
<metadata>
|
<metadata>
|
||||||
<id>handlebars.js</id>
|
<id>handlebars.js</id>
|
||||||
<version>2.0.0-beta.1</version>
|
<version>4.7.8</version>
|
||||||
<authors>handlebars.js Authors</authors>
|
<authors>handlebars.js Authors</authors>
|
||||||
<licenseUrl>https://github.com/wycats/handlebars.js/blob/master/LICENSE</licenseUrl>
|
<licenseUrl>https://github.com/handlebars-lang/handlebars.js/blob/master/LICENSE</licenseUrl>
|
||||||
<projectUrl>https://github.com/wycats/handlebars.js/</projectUrl>
|
<projectUrl>https://github.com/handlebars-lang/handlebars.js/</projectUrl>
|
||||||
<requireLicenseAcceptance>false</requireLicenseAcceptance>
|
<requireLicenseAcceptance>false</requireLicenseAcceptance>
|
||||||
<description>Extension of the Mustache logicless template language</description>
|
<description>Extension of the Mustache logicless template language</description>
|
||||||
<releaseNotes></releaseNotes>
|
<releaseNotes></releaseNotes>
|
||||||
|
|||||||
@@ -0,0 +1,20 @@
|
|||||||
|
{
|
||||||
|
"name": "handlebars",
|
||||||
|
"version": "4.7.8",
|
||||||
|
"license": "MIT",
|
||||||
|
"jspm": {
|
||||||
|
"main": "handlebars",
|
||||||
|
"shim": {
|
||||||
|
"handlebars": {
|
||||||
|
"exports": "Handlebars"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"files": [
|
||||||
|
"handlebars.js",
|
||||||
|
"handlebars.runtime.js"
|
||||||
|
],
|
||||||
|
"buildConfig": {
|
||||||
|
"minify": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,366 @@
|
|||||||
|
# Handlebars Compiler APIs
|
||||||
|
|
||||||
|
There are a number of formal APIs that tool implementors may interact with.
|
||||||
|
|
||||||
|
## AST
|
||||||
|
|
||||||
|
Other tools may interact with the formal AST as defined below. Any JSON structure matching this pattern may be used and passed into the `compile` and `precompile` methods in the same way as the text for a template.
|
||||||
|
|
||||||
|
AST structures may be generated either with the `Handlebars.parse` method and then manipulated, via the `Handlebars.AST` objects of the same name, or constructed manually as a generic JavaScript object matching the structure defined below.
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
var ast = Handlebars.parse(myTemplate);
|
||||||
|
|
||||||
|
// Modify ast
|
||||||
|
|
||||||
|
Handlebars.precompile(ast);
|
||||||
|
```
|
||||||
|
|
||||||
|
### Parsing
|
||||||
|
|
||||||
|
There are two primary APIs that are used to parse an existing template into the AST:
|
||||||
|
|
||||||
|
#### parseWithoutProcessing
|
||||||
|
|
||||||
|
`Handlebars.parseWithoutProcessing` is the primary mechanism to turn a raw template string into the Handlebars AST described in this document. No processing is done on the resulting AST which makes this ideal for codemod (for source to source transformation) tooling.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
```js
|
||||||
|
let ast = Handlebars.parseWithoutProcessing(myTemplate);
|
||||||
|
```
|
||||||
|
|
||||||
|
#### parse
|
||||||
|
|
||||||
|
`Handlebars.parse` will parse the template with `parseWithoutProcessing` (see above) then it will update the AST to strip extraneous whitespace. The whitespace stripping functionality handles two distinct situations:
|
||||||
|
|
||||||
|
* Removes whitespace around dynamic statements that are on a line by themselves (aka "stand alone")
|
||||||
|
* Applies "whitespace control" characters (i.e. `~`) by truncating the `ContentStatement` `value` property appropriately (e.g. `\n\n{{~foo}}` would have a `ContentStatement` with a `value` of `''`)
|
||||||
|
|
||||||
|
`Handlebars.parse` is used internally by `Handlebars.precompile` and `Handlebars.compile`.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
```js
|
||||||
|
let ast = Handlebars.parse(myTemplate);
|
||||||
|
```
|
||||||
|
|
||||||
|
### Basic
|
||||||
|
|
||||||
|
```java
|
||||||
|
interface Node {
|
||||||
|
type: string;
|
||||||
|
loc: SourceLocation | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface SourceLocation {
|
||||||
|
source: string | null;
|
||||||
|
start: Position;
|
||||||
|
end: Position;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Position {
|
||||||
|
line: uint >= 1;
|
||||||
|
column: uint >= 0;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Programs
|
||||||
|
|
||||||
|
```java
|
||||||
|
interface Program <: Node {
|
||||||
|
type: "Program";
|
||||||
|
body: [ Statement ];
|
||||||
|
|
||||||
|
blockParams: [ string ];
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Statements
|
||||||
|
|
||||||
|
```java
|
||||||
|
interface Statement <: Node { }
|
||||||
|
|
||||||
|
interface MustacheStatement <: Statement {
|
||||||
|
type: "MustacheStatement";
|
||||||
|
|
||||||
|
path: PathExpression | Literal;
|
||||||
|
params: [ Expression ];
|
||||||
|
hash: Hash;
|
||||||
|
|
||||||
|
escaped: boolean;
|
||||||
|
strip: StripFlags | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface BlockStatement <: Statement {
|
||||||
|
type: "BlockStatement";
|
||||||
|
path: PathExpression | Literal;
|
||||||
|
params: [ Expression ];
|
||||||
|
hash: Hash;
|
||||||
|
|
||||||
|
program: Program | null;
|
||||||
|
inverse: Program | null;
|
||||||
|
|
||||||
|
openStrip: StripFlags | null;
|
||||||
|
inverseStrip: StripFlags | null;
|
||||||
|
closeStrip: StripFlags | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface PartialStatement <: Statement {
|
||||||
|
type: "PartialStatement";
|
||||||
|
name: PathExpression | SubExpression;
|
||||||
|
params: [ Expression ];
|
||||||
|
hash: Hash;
|
||||||
|
|
||||||
|
indent: string;
|
||||||
|
strip: StripFlags | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface PartialBlockStatement <: Statement {
|
||||||
|
type: "PartialBlockStatement";
|
||||||
|
name: PathExpression | SubExpression;
|
||||||
|
params: [ Expression ];
|
||||||
|
hash: Hash;
|
||||||
|
|
||||||
|
program: Program | null;
|
||||||
|
|
||||||
|
indent: string;
|
||||||
|
openStrip: StripFlags | null;
|
||||||
|
closeStrip: StripFlags | null;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
`name` will be a `SubExpression` when tied to a dynamic partial, i.e. `{{> (foo) }}`, otherwise this is a path or literal whose `original` value is used to lookup the desired partial.
|
||||||
|
|
||||||
|
|
||||||
|
```java
|
||||||
|
interface ContentStatement <: Statement {
|
||||||
|
type: "ContentStatement";
|
||||||
|
value: string;
|
||||||
|
original: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface CommentStatement <: Statement {
|
||||||
|
type: "CommentStatement";
|
||||||
|
value: string;
|
||||||
|
|
||||||
|
strip: StripFlags | null;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
```java
|
||||||
|
interface Decorator <: Statement {
|
||||||
|
type: "Decorator";
|
||||||
|
|
||||||
|
path: PathExpression | Literal;
|
||||||
|
params: [ Expression ];
|
||||||
|
hash: Hash;
|
||||||
|
|
||||||
|
strip: StripFlags | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface DecoratorBlock <: Statement {
|
||||||
|
type: "DecoratorBlock";
|
||||||
|
path: PathExpression | Literal;
|
||||||
|
params: [ Expression ];
|
||||||
|
hash: Hash;
|
||||||
|
|
||||||
|
program: Program | null;
|
||||||
|
|
||||||
|
openStrip: StripFlags | null;
|
||||||
|
closeStrip: StripFlags | null;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Decorator paths only utilize the `path.original` value and as a consequence do not support depthed evaluation.
|
||||||
|
|
||||||
|
### Expressions
|
||||||
|
|
||||||
|
```java
|
||||||
|
interface Expression <: Node { }
|
||||||
|
```
|
||||||
|
|
||||||
|
##### SubExpressions
|
||||||
|
|
||||||
|
```java
|
||||||
|
interface SubExpression <: Expression {
|
||||||
|
type: "SubExpression";
|
||||||
|
path: PathExpression;
|
||||||
|
params: [ Expression ];
|
||||||
|
hash: Hash;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
##### Paths
|
||||||
|
|
||||||
|
```java
|
||||||
|
interface PathExpression <: Expression {
|
||||||
|
type: "PathExpression";
|
||||||
|
data: boolean;
|
||||||
|
depth: uint >= 0;
|
||||||
|
parts: [ string ];
|
||||||
|
original: string;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- `data` is true when the given expression is a `@data` reference.
|
||||||
|
- `depth` is an integer representation of which context the expression references. `0` represents the current context, `1` would be `../`, etc.
|
||||||
|
- `parts` is an array of the names in the path. `foo.bar` would be `['foo', 'bar']`. Scope references, `.`, `..`, and `this` should be omitted from this array.
|
||||||
|
- `original` is the path as entered by the user. Separator and scope references are left untouched.
|
||||||
|
|
||||||
|
|
||||||
|
##### Literals
|
||||||
|
|
||||||
|
```java
|
||||||
|
interface Literal <: Expression { }
|
||||||
|
|
||||||
|
interface StringLiteral <: Literal {
|
||||||
|
type: "StringLiteral";
|
||||||
|
value: string;
|
||||||
|
original: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface BooleanLiteral <: Literal {
|
||||||
|
type: "BooleanLiteral";
|
||||||
|
value: boolean;
|
||||||
|
original: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface NumberLiteral <: Literal {
|
||||||
|
type: "NumberLiteral";
|
||||||
|
value: number;
|
||||||
|
original: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface UndefinedLiteral <: Literal {
|
||||||
|
type: "UndefinedLiteral";
|
||||||
|
}
|
||||||
|
|
||||||
|
interface NullLiteral <: Literal {
|
||||||
|
type: "NullLiteral";
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
### Miscellaneous
|
||||||
|
|
||||||
|
```java
|
||||||
|
interface Hash <: Node {
|
||||||
|
type: "Hash";
|
||||||
|
pairs: [ HashPair ];
|
||||||
|
}
|
||||||
|
|
||||||
|
interface HashPair <: Node {
|
||||||
|
type: "HashPair";
|
||||||
|
key: string;
|
||||||
|
value: Expression;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface StripFlags {
|
||||||
|
open: boolean;
|
||||||
|
close: boolean;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
`StripFlags` are used to signify whitespace control character that may have been entered on a given statement.
|
||||||
|
|
||||||
|
## AST Visitor
|
||||||
|
|
||||||
|
`Handlebars.Visitor` is available as a base class for general interaction with AST structures. This will by default traverse the entire tree and individual methods may be overridden to provide specific responses to particular nodes.
|
||||||
|
|
||||||
|
Recording all referenced partial names:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
var Visitor = Handlebars.Visitor;
|
||||||
|
|
||||||
|
function ImportScanner() {
|
||||||
|
this.partials = [];
|
||||||
|
}
|
||||||
|
ImportScanner.prototype = new Visitor();
|
||||||
|
|
||||||
|
ImportScanner.prototype.PartialStatement = function(partial) {
|
||||||
|
this.partials.push({request: partial.name.original});
|
||||||
|
|
||||||
|
Visitor.prototype.PartialStatement.call(this, partial);
|
||||||
|
};
|
||||||
|
|
||||||
|
var scanner = new ImportScanner();
|
||||||
|
scanner.accept(ast);
|
||||||
|
```
|
||||||
|
|
||||||
|
The current node's ancestors will be maintained in the `parents` array, with the most recent parent listed first.
|
||||||
|
|
||||||
|
The visitor may also be configured to operate in mutation mode by setting the `mutation` field to true. When in this mode, handler methods may return any valid AST node and it will replace the one they are currently operating on. Returning `false` will remove the given value (if valid) and returning `undefined` will leave the node in tact. This return structure only apply to mutation mode and non-mutation mode visitors are free to return whatever values they wish.
|
||||||
|
|
||||||
|
Implementors that may need to support mutation mode are encouraged to utilize the `acceptKey`, `acceptRequired` and `acceptArray` helpers which provide the conditional overwrite behavior as well as implement sanity checks where pertinent.
|
||||||
|
|
||||||
|
## JavaScript Compiler
|
||||||
|
|
||||||
|
The `Handlebars.JavaScriptCompiler` object has a number of methods that may be customized to alter the output of the compiler:
|
||||||
|
|
||||||
|
- `nameLookup(parent, name, type)`
|
||||||
|
Used to generate the code to resolve a give path component.
|
||||||
|
|
||||||
|
- `parent` is the existing code in the path resolution
|
||||||
|
- `name` is the current path component
|
||||||
|
- `type` is the type of name being evaluated. May be one of `context`, `data`, `helper`, `decorator`, or `partial`.
|
||||||
|
|
||||||
|
Note that this does not impact dynamic partials, which implementors need to be aware of. Overriding `VM.resolvePartial` may be required to support dynamic cases.
|
||||||
|
|
||||||
|
- `depthedLookup(name)`
|
||||||
|
Used to generate code that resolves parameters within any context in the stack. Is only used in `compat` mode.
|
||||||
|
|
||||||
|
- `compilerInfo()`
|
||||||
|
Allows for custom compiler flags used in the runtime version checking logic.
|
||||||
|
|
||||||
|
- `appendToBuffer(source, location, explicit)`
|
||||||
|
Allows for code buffer emitting code. Defaults behavior is string concatenation.
|
||||||
|
|
||||||
|
- `source` is the source code whose result is to be appending
|
||||||
|
- `location` is the location of the source in the source map.
|
||||||
|
- `explicit` is a flag signaling that the emit operation must occur, vs. the lazy evaled options otherwise.
|
||||||
|
|
||||||
|
- `initializeBuffer()`
|
||||||
|
Allows for buffers other than the default string buffer to be used. Generally needs to be paired with a custom `appendToBuffer` implementation.
|
||||||
|
|
||||||
|
### Example for the compiler api.
|
||||||
|
|
||||||
|
This example changes all lookups of properties are performed by a helper (`lookupLowerCase`) which looks for `test` if `{{Test}}` occurs in the template. This is just to illustrate how compiler behavior can be change.
|
||||||
|
|
||||||
|
There is also [a jsfiddle with this code](https://jsfiddle.net/9D88g/162/) if you want to play around with it.
|
||||||
|
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
function MyCompiler() {
|
||||||
|
Handlebars.JavaScriptCompiler.apply(this, arguments);
|
||||||
|
}
|
||||||
|
MyCompiler.prototype = new Handlebars.JavaScriptCompiler();
|
||||||
|
|
||||||
|
// Use this compile to compile BlockStatment-Blocks
|
||||||
|
MyCompiler.prototype.compiler = MyCompiler
|
||||||
|
|
||||||
|
MyCompiler.prototype.nameLookup = function(parent, name, type) {
|
||||||
|
if (type === 'context') {
|
||||||
|
return this.source.functionCall('helpers.lookupLowerCase', '', [parent, JSON.stringify(name)])
|
||||||
|
} else {
|
||||||
|
return Handlebars.JavaScriptCompiler.prototype.nameLookup.call(this, parent, name, type);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var env = Handlebars.create();
|
||||||
|
env.registerHelper('lookupLowerCase', function(parent, name) {
|
||||||
|
return parent[name.toLowerCase()]
|
||||||
|
})
|
||||||
|
|
||||||
|
env.JavaScriptCompiler = MyCompiler;
|
||||||
|
|
||||||
|
var template = env.compile('{{#each Test}} ({{Value}}) {{/each}}');
|
||||||
|
console.log(template({
|
||||||
|
test: [
|
||||||
|
{value: 'a'},
|
||||||
|
{value: 'b'},
|
||||||
|
{value: 'c'}
|
||||||
|
]
|
||||||
|
}));
|
||||||
|
```
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
# Decorators
|
||||||
|
|
||||||
|
Decorators allow for blocks to be annotated with metadata or wrapped in functionality prior to execution of the block. This may be used to communicate with the containing helper or to setup a particular state in the system prior to running the block.
|
||||||
|
|
||||||
|
Decorators are registered through similar methods as helpers, `registerDecorators` and `unregisterDecorators`. These can then be referenced via the friendly name in the template using the `{{* decorator}}` and `{{#* decorator}}{/decorator}}` syntaxes. These syntaxes are derivatives of the normal mustache syntax and as such have all of the same argument and whitespace behaviors.
|
||||||
|
|
||||||
|
Decorators are executed when the block program is instantiated and are passed `(program, props, container, context, data, blockParams, depths)`.
|
||||||
|
|
||||||
|
- `program`: The block to wrap
|
||||||
|
- `props`: Object used to set metadata on the final function. Any values set on this object will be set on the function, regardless of if the original function is replaced or not. Metadata should be applied using this object as values applied to `program` may be masked by subsequent decorators that may wrap `program`.
|
||||||
|
- `container`: The current runtime container
|
||||||
|
- `context`: The current context. Since the decorator is run before the block that contains it, this is the parent context.
|
||||||
|
- `data`: The current `@data` values
|
||||||
|
- `blockParams`: The current block parameters stack
|
||||||
|
- `depths`: The current context stack
|
||||||
|
|
||||||
|
Decorators may set values on `props` or return a modified function that wraps `program` in particular behaviors. If the decorator returns nothing, then `program` is left unaltered.
|
||||||
|
|
||||||
|
The [inline partial](https://github.com/handlebars-lang/handlebars.js/blob/master/lib/handlebars/decorators/inline.js) implementation provides an example of decorators being used for both metadata and wrapping behaviors.
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
module.exports = {
|
||||||
|
env: {
|
||||||
|
// Handlebars should not use node or browser-specific APIs
|
||||||
|
'shared-node-browser': true,
|
||||||
|
node: false,
|
||||||
|
browser: false
|
||||||
|
}
|
||||||
|
};
|
||||||
+26
-15
@@ -1,20 +1,26 @@
|
|||||||
/*globals Handlebars: true */
|
import runtime from './handlebars.runtime';
|
||||||
import Handlebars from "./handlebars.runtime";
|
|
||||||
|
|
||||||
// Compiler imports
|
// Compiler imports
|
||||||
import AST from "./handlebars/compiler/ast";
|
import AST from './handlebars/compiler/ast';
|
||||||
import { parser as Parser, parse } from "./handlebars/compiler/base";
|
import {
|
||||||
import { Compiler, compile, precompile } from "./handlebars/compiler/compiler";
|
parser as Parser,
|
||||||
import JavaScriptCompiler from "./handlebars/compiler/javascript-compiler";
|
parse,
|
||||||
|
parseWithoutProcessing
|
||||||
|
} from './handlebars/compiler/base';
|
||||||
|
import { Compiler, compile, precompile } from './handlebars/compiler/compiler';
|
||||||
|
import JavaScriptCompiler from './handlebars/compiler/javascript-compiler';
|
||||||
|
import Visitor from './handlebars/compiler/visitor';
|
||||||
|
|
||||||
var _create = Handlebars.create;
|
import noConflict from './handlebars/no-conflict';
|
||||||
var create = function() {
|
|
||||||
var hb = _create();
|
let _create = runtime.create;
|
||||||
|
function create() {
|
||||||
|
let hb = _create();
|
||||||
|
|
||||||
hb.compile = function(input, options) {
|
hb.compile = function(input, options) {
|
||||||
return compile(input, options, hb);
|
return compile(input, options, hb);
|
||||||
};
|
};
|
||||||
hb.precompile = function (input, options) {
|
hb.precompile = function(input, options) {
|
||||||
return precompile(input, options, hb);
|
return precompile(input, options, hb);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -23,13 +29,18 @@ var create = function() {
|
|||||||
hb.JavaScriptCompiler = JavaScriptCompiler;
|
hb.JavaScriptCompiler = JavaScriptCompiler;
|
||||||
hb.Parser = Parser;
|
hb.Parser = Parser;
|
||||||
hb.parse = parse;
|
hb.parse = parse;
|
||||||
|
hb.parseWithoutProcessing = parseWithoutProcessing;
|
||||||
|
|
||||||
return hb;
|
return hb;
|
||||||
};
|
}
|
||||||
|
|
||||||
Handlebars = create();
|
let inst = create();
|
||||||
Handlebars.create = create;
|
inst.create = create;
|
||||||
|
|
||||||
Handlebars['default'] = Handlebars;
|
noConflict(inst);
|
||||||
|
|
||||||
export default Handlebars;
|
inst.Visitor = Visitor;
|
||||||
|
|
||||||
|
inst['default'] = inst;
|
||||||
|
|
||||||
|
export default inst;
|
||||||
|
|||||||
+16
-13
@@ -1,16 +1,17 @@
|
|||||||
/*globals Handlebars: true */
|
import * as base from './handlebars/base';
|
||||||
module base from "./handlebars/base";
|
|
||||||
|
|
||||||
// Each of these augment the Handlebars object. No need to setup here.
|
// Each of these augment the Handlebars object. No need to setup here.
|
||||||
// (This is done to easily share code between commonjs and browse envs)
|
// (This is done to easily share code between commonjs and browse envs)
|
||||||
import SafeString from "./handlebars/safe-string";
|
import SafeString from './handlebars/safe-string';
|
||||||
import Exception from "./handlebars/exception";
|
import Exception from './handlebars/exception';
|
||||||
module Utils from "./handlebars/utils";
|
import * as Utils from './handlebars/utils';
|
||||||
module runtime from "./handlebars/runtime";
|
import * as runtime from './handlebars/runtime';
|
||||||
|
|
||||||
|
import noConflict from './handlebars/no-conflict';
|
||||||
|
|
||||||
// For compatibility and usage outside of module systems, make the Handlebars object a namespace
|
// For compatibility and usage outside of module systems, make the Handlebars object a namespace
|
||||||
var create = function() {
|
function create() {
|
||||||
var hb = new base.HandlebarsEnvironment();
|
let hb = new base.HandlebarsEnvironment();
|
||||||
|
|
||||||
Utils.extend(hb, base);
|
Utils.extend(hb, base);
|
||||||
hb.SafeString = SafeString;
|
hb.SafeString = SafeString;
|
||||||
@@ -24,11 +25,13 @@ var create = function() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return hb;
|
return hb;
|
||||||
};
|
}
|
||||||
|
|
||||||
var Handlebars = create();
|
let inst = create();
|
||||||
Handlebars.create = create;
|
inst.create = create;
|
||||||
|
|
||||||
Handlebars['default'] = Handlebars;
|
noConflict(inst);
|
||||||
|
|
||||||
export default Handlebars;
|
inst['default'] = inst;
|
||||||
|
|
||||||
|
export default inst;
|
||||||
|
|||||||
+53
-188
@@ -1,40 +1,48 @@
|
|||||||
module Utils from "./utils";
|
import { createFrame, extend, toString } from './utils';
|
||||||
import Exception from "./exception";
|
import Exception from './exception';
|
||||||
|
import { registerDefaultHelpers } from './helpers';
|
||||||
|
import { registerDefaultDecorators } from './decorators';
|
||||||
|
import logger from './logger';
|
||||||
|
import { resetLoggedProperties } from './internal/proto-access';
|
||||||
|
|
||||||
export var VERSION = "2.0.0-beta.1";
|
export const VERSION = '4.7.8';
|
||||||
export var COMPILER_REVISION = 6;
|
export const COMPILER_REVISION = 8;
|
||||||
|
export const LAST_COMPATIBLE_COMPILER_REVISION = 7;
|
||||||
|
|
||||||
export var REVISION_CHANGES = {
|
export const REVISION_CHANGES = {
|
||||||
1: '<= 1.0.rc.2', // 1.0.rc.2 is actually rev2 but doesn't report it
|
1: '<= 1.0.rc.2', // 1.0.rc.2 is actually rev2 but doesn't report it
|
||||||
2: '== 1.0.0-rc.3',
|
2: '== 1.0.0-rc.3',
|
||||||
3: '== 1.0.0-rc.4',
|
3: '== 1.0.0-rc.4',
|
||||||
4: '== 1.x.x',
|
4: '== 1.x.x',
|
||||||
5: '== 2.0.0-alpha.x',
|
5: '== 2.0.0-alpha.x',
|
||||||
6: '>= 2.0.0-beta.1'
|
6: '>= 2.0.0-beta.1',
|
||||||
|
7: '>= 4.0.0 <4.3.0',
|
||||||
|
8: '>= 4.3.0'
|
||||||
};
|
};
|
||||||
|
|
||||||
var isArray = Utils.isArray,
|
const objectType = '[object Object]';
|
||||||
isFunction = Utils.isFunction,
|
|
||||||
toString = Utils.toString,
|
|
||||||
objectType = '[object Object]';
|
|
||||||
|
|
||||||
export function HandlebarsEnvironment(helpers, partials) {
|
export function HandlebarsEnvironment(helpers, partials, decorators) {
|
||||||
this.helpers = helpers || {};
|
this.helpers = helpers || {};
|
||||||
this.partials = partials || {};
|
this.partials = partials || {};
|
||||||
|
this.decorators = decorators || {};
|
||||||
|
|
||||||
registerDefaultHelpers(this);
|
registerDefaultHelpers(this);
|
||||||
|
registerDefaultDecorators(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
HandlebarsEnvironment.prototype = {
|
HandlebarsEnvironment.prototype = {
|
||||||
constructor: HandlebarsEnvironment,
|
constructor: HandlebarsEnvironment,
|
||||||
|
|
||||||
logger: logger,
|
logger: logger,
|
||||||
log: log,
|
log: logger.log,
|
||||||
|
|
||||||
registerHelper: function(name, fn) {
|
registerHelper: function(name, fn) {
|
||||||
if (toString.call(name) === objectType) {
|
if (toString.call(name) === objectType) {
|
||||||
if (fn) { throw new Exception('Arg not supported with multiple helpers'); }
|
if (fn) {
|
||||||
Utils.extend(this.helpers, name);
|
throw new Exception('Arg not supported with multiple helpers');
|
||||||
|
}
|
||||||
|
extend(this.helpers, name);
|
||||||
} else {
|
} else {
|
||||||
this.helpers[name] = fn;
|
this.helpers[name] = fn;
|
||||||
}
|
}
|
||||||
@@ -43,187 +51,44 @@ HandlebarsEnvironment.prototype = {
|
|||||||
delete this.helpers[name];
|
delete this.helpers[name];
|
||||||
},
|
},
|
||||||
|
|
||||||
registerPartial: function(name, str) {
|
registerPartial: function(name, partial) {
|
||||||
if (toString.call(name) === objectType) {
|
if (toString.call(name) === objectType) {
|
||||||
Utils.extend(this.partials, name);
|
extend(this.partials, name);
|
||||||
} else {
|
} else {
|
||||||
this.partials[name] = str;
|
if (typeof partial === 'undefined') {
|
||||||
|
throw new Exception(
|
||||||
|
`Attempting to register a partial called "${name}" as undefined`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
this.partials[name] = partial;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
unregisterPartial: function(name) {
|
unregisterPartial: function(name) {
|
||||||
delete this.partials[name];
|
delete this.partials[name];
|
||||||
|
},
|
||||||
|
|
||||||
|
registerDecorator: function(name, fn) {
|
||||||
|
if (toString.call(name) === objectType) {
|
||||||
|
if (fn) {
|
||||||
|
throw new Exception('Arg not supported with multiple decorators');
|
||||||
|
}
|
||||||
|
extend(this.decorators, name);
|
||||||
|
} else {
|
||||||
|
this.decorators[name] = fn;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
unregisterDecorator: function(name) {
|
||||||
|
delete this.decorators[name];
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* Reset the memory of illegal property accesses that have already been logged.
|
||||||
|
* @deprecated should only be used in handlebars test-cases
|
||||||
|
*/
|
||||||
|
resetLoggedPropertyAccesses() {
|
||||||
|
resetLoggedProperties();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
function registerDefaultHelpers(instance) {
|
export let log = logger.log;
|
||||||
instance.registerHelper('helperMissing', function(/* [args, ]options */) {
|
|
||||||
if(arguments.length === 1) {
|
|
||||||
// A missing field in a {{foo}} constuct.
|
|
||||||
return undefined;
|
|
||||||
} else {
|
|
||||||
// Someone is actually trying to call something, blow up.
|
|
||||||
throw new Exception("Missing helper: '" + arguments[arguments.length-1].name + "'");
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
instance.registerHelper('blockHelperMissing', function(context, options) {
|
export { createFrame, logger };
|
||||||
var inverse = options.inverse,
|
|
||||||
fn = options.fn;
|
|
||||||
|
|
||||||
if(context === true) {
|
|
||||||
return fn(this);
|
|
||||||
} else if(context === false || context == null) {
|
|
||||||
return inverse(this);
|
|
||||||
} else if (isArray(context)) {
|
|
||||||
if(context.length > 0) {
|
|
||||||
if (options.ids) {
|
|
||||||
options.ids = [options.name];
|
|
||||||
}
|
|
||||||
|
|
||||||
return instance.helpers.each(context, options);
|
|
||||||
} else {
|
|
||||||
return inverse(this);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (options.data && options.ids) {
|
|
||||||
var data = createFrame(options.data);
|
|
||||||
data.contextPath = Utils.appendContextPath(options.data.contextPath, options.name);
|
|
||||||
options = {data: data};
|
|
||||||
}
|
|
||||||
|
|
||||||
return fn(context, options);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
instance.registerHelper('each', function(context, options) {
|
|
||||||
if (!options) {
|
|
||||||
throw new Exception('Must pass iterator to #each');
|
|
||||||
}
|
|
||||||
|
|
||||||
var fn = options.fn, inverse = options.inverse;
|
|
||||||
var i = 0, ret = "", data;
|
|
||||||
|
|
||||||
var contextPath;
|
|
||||||
if (options.data && options.ids) {
|
|
||||||
contextPath = Utils.appendContextPath(options.data.contextPath, options.ids[0]) + '.';
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isFunction(context)) { context = context.call(this); }
|
|
||||||
|
|
||||||
if (options.data) {
|
|
||||||
data = createFrame(options.data);
|
|
||||||
}
|
|
||||||
|
|
||||||
if(context && typeof context === 'object') {
|
|
||||||
if (isArray(context)) {
|
|
||||||
for(var j = context.length; i<j; i++) {
|
|
||||||
if (data) {
|
|
||||||
data.index = i;
|
|
||||||
data.first = (i === 0);
|
|
||||||
data.last = (i === (context.length-1));
|
|
||||||
|
|
||||||
if (contextPath) {
|
|
||||||
data.contextPath = contextPath + i;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ret = ret + fn(context[i], { data: data });
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
for(var key in context) {
|
|
||||||
if(context.hasOwnProperty(key)) {
|
|
||||||
if(data) {
|
|
||||||
data.key = key;
|
|
||||||
data.index = i;
|
|
||||||
data.first = (i === 0);
|
|
||||||
|
|
||||||
if (contextPath) {
|
|
||||||
data.contextPath = contextPath + key;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ret = ret + fn(context[key], {data: data});
|
|
||||||
i++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if(i === 0){
|
|
||||||
ret = inverse(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
return ret;
|
|
||||||
});
|
|
||||||
|
|
||||||
instance.registerHelper('if', function(conditional, options) {
|
|
||||||
if (isFunction(conditional)) { conditional = conditional.call(this); }
|
|
||||||
|
|
||||||
// Default behavior is to render the positive path if the value is truthy and not empty.
|
|
||||||
// The `includeZero` option may be set to treat the condtional as purely not empty based on the
|
|
||||||
// behavior of isEmpty. Effectively this determines if 0 is handled by the positive path or negative.
|
|
||||||
if ((!options.hash.includeZero && !conditional) || Utils.isEmpty(conditional)) {
|
|
||||||
return options.inverse(this);
|
|
||||||
} else {
|
|
||||||
return options.fn(this);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
instance.registerHelper('unless', function(conditional, options) {
|
|
||||||
return instance.helpers['if'].call(this, conditional, {fn: options.inverse, inverse: options.fn, hash: options.hash});
|
|
||||||
});
|
|
||||||
|
|
||||||
instance.registerHelper('with', function(context, options) {
|
|
||||||
if (isFunction(context)) { context = context.call(this); }
|
|
||||||
|
|
||||||
var fn = options.fn;
|
|
||||||
|
|
||||||
if (!Utils.isEmpty(context)) {
|
|
||||||
if (options.data && options.ids) {
|
|
||||||
var data = createFrame(options.data);
|
|
||||||
data.contextPath = Utils.appendContextPath(options.data.contextPath, options.ids[0]);
|
|
||||||
options = {data:data};
|
|
||||||
}
|
|
||||||
|
|
||||||
return fn(context, options);
|
|
||||||
} else {
|
|
||||||
return options.inverse(this);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
instance.registerHelper('log', function(context, options) {
|
|
||||||
var level = options.data && options.data.level != null ? parseInt(options.data.level, 10) : 1;
|
|
||||||
instance.log(level, context);
|
|
||||||
});
|
|
||||||
|
|
||||||
instance.registerHelper('lookup', function(obj, field) {
|
|
||||||
return obj && obj[field];
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export var logger = {
|
|
||||||
methodMap: { 0: 'debug', 1: 'info', 2: 'warn', 3: 'error' },
|
|
||||||
|
|
||||||
// State enum
|
|
||||||
DEBUG: 0,
|
|
||||||
INFO: 1,
|
|
||||||
WARN: 2,
|
|
||||||
ERROR: 3,
|
|
||||||
level: 3,
|
|
||||||
|
|
||||||
// can be overridden in the host environment
|
|
||||||
log: function(level, obj) {
|
|
||||||
if (logger.level <= level) {
|
|
||||||
var method = logger.methodMap[level];
|
|
||||||
if (typeof console !== 'undefined' && console[method]) {
|
|
||||||
console[method].call(console, obj);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export var log = logger.log;
|
|
||||||
|
|
||||||
export var createFrame = function(object) {
|
|
||||||
var frame = Utils.extend({}, object);
|
|
||||||
frame._parent = object;
|
|
||||||
return frame;
|
|
||||||
};
|
|
||||||
|
|||||||
+20
-201
@@ -1,213 +1,32 @@
|
|||||||
import Exception from "../exception";
|
let AST = {
|
||||||
|
// Public API used to evaluate derived attributes regarding AST nodes
|
||||||
function LocationInfo(locInfo) {
|
helpers: {
|
||||||
locInfo = locInfo || {};
|
|
||||||
this.firstLine = locInfo.first_line;
|
|
||||||
this.firstColumn = locInfo.first_column;
|
|
||||||
this.lastColumn = locInfo.last_column;
|
|
||||||
this.lastLine = locInfo.last_line;
|
|
||||||
}
|
|
||||||
|
|
||||||
var AST = {
|
|
||||||
ProgramNode: function(statements, strip, locInfo) {
|
|
||||||
LocationInfo.call(this, locInfo);
|
|
||||||
this.type = "program";
|
|
||||||
this.statements = statements;
|
|
||||||
this.strip = strip;
|
|
||||||
},
|
|
||||||
|
|
||||||
MustacheNode: function(rawParams, hash, open, strip, locInfo) {
|
|
||||||
LocationInfo.call(this, locInfo);
|
|
||||||
this.type = "mustache";
|
|
||||||
this.strip = strip;
|
|
||||||
|
|
||||||
// Open may be a string parsed from the parser or a passed boolean flag
|
|
||||||
if (open != null && open.charAt) {
|
|
||||||
// Must use charAt to support IE pre-10
|
|
||||||
var escapeFlag = open.charAt(3) || open.charAt(2);
|
|
||||||
this.escaped = escapeFlag !== '{' && escapeFlag !== '&';
|
|
||||||
} else {
|
|
||||||
this.escaped = !!open;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (rawParams instanceof AST.SexprNode) {
|
|
||||||
this.sexpr = rawParams;
|
|
||||||
} else {
|
|
||||||
// Support old AST API
|
|
||||||
this.sexpr = new AST.SexprNode(rawParams, hash);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Support old AST API that stored this info in MustacheNode
|
|
||||||
this.id = this.sexpr.id;
|
|
||||||
this.params = this.sexpr.params;
|
|
||||||
this.hash = this.sexpr.hash;
|
|
||||||
this.eligibleHelper = this.sexpr.eligibleHelper;
|
|
||||||
this.isHelper = this.sexpr.isHelper;
|
|
||||||
},
|
|
||||||
|
|
||||||
SexprNode: function(rawParams, hash, locInfo) {
|
|
||||||
LocationInfo.call(this, locInfo);
|
|
||||||
|
|
||||||
this.type = "sexpr";
|
|
||||||
this.hash = hash;
|
|
||||||
|
|
||||||
var id = this.id = rawParams[0];
|
|
||||||
var params = this.params = rawParams.slice(1);
|
|
||||||
|
|
||||||
// a mustache is definitely a helper if:
|
// a mustache is definitely a helper if:
|
||||||
// * it is an eligible helper, and
|
// * it is an eligible helper, and
|
||||||
// * it has at least one parameter or hash segment
|
// * it has at least one parameter or hash segment
|
||||||
this.isHelper = !!(params.length || hash);
|
helperExpression: function(node) {
|
||||||
|
return (
|
||||||
|
node.type === 'SubExpression' ||
|
||||||
|
((node.type === 'MustacheStatement' ||
|
||||||
|
node.type === 'BlockStatement') &&
|
||||||
|
!!((node.params && node.params.length) || node.hash))
|
||||||
|
);
|
||||||
|
},
|
||||||
|
|
||||||
// a mustache is an eligible helper if:
|
scopedId: function(path) {
|
||||||
// * its id is simple (a single part, not `this` or `..`)
|
return /^\.|this\b/.test(path.original);
|
||||||
this.eligibleHelper = this.isHelper || id.isSimple;
|
},
|
||||||
|
|
||||||
// if a mustache is an eligible helper but not a definite
|
|
||||||
// helper, it is ambiguous, and will be resolved in a later
|
|
||||||
// pass or at runtime.
|
|
||||||
},
|
|
||||||
|
|
||||||
PartialNode: function(partialName, context, hash, strip, locInfo) {
|
|
||||||
LocationInfo.call(this, locInfo);
|
|
||||||
this.type = "partial";
|
|
||||||
this.partialName = partialName;
|
|
||||||
this.context = context;
|
|
||||||
this.hash = hash;
|
|
||||||
this.strip = strip;
|
|
||||||
|
|
||||||
this.strip.inlineStandalone = true;
|
|
||||||
},
|
|
||||||
|
|
||||||
BlockNode: function(mustache, program, inverse, strip, locInfo) {
|
|
||||||
LocationInfo.call(this, locInfo);
|
|
||||||
|
|
||||||
this.type = 'block';
|
|
||||||
this.mustache = mustache;
|
|
||||||
this.program = program;
|
|
||||||
this.inverse = inverse;
|
|
||||||
this.strip = strip;
|
|
||||||
|
|
||||||
if (inverse && !program) {
|
|
||||||
this.isInverse = true;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
RawBlockNode: function(mustache, content, close, locInfo) {
|
|
||||||
LocationInfo.call(this, locInfo);
|
|
||||||
|
|
||||||
if (mustache.sexpr.id.original !== close) {
|
|
||||||
throw new Exception(mustache.sexpr.id.original + " doesn't match " + close, this);
|
|
||||||
}
|
|
||||||
|
|
||||||
content = new AST.ContentNode(content, locInfo);
|
|
||||||
|
|
||||||
this.type = 'block';
|
|
||||||
this.mustache = mustache;
|
|
||||||
this.program = new AST.ProgramNode([content], {}, locInfo);
|
|
||||||
},
|
|
||||||
|
|
||||||
ContentNode: function(string, locInfo) {
|
|
||||||
LocationInfo.call(this, locInfo);
|
|
||||||
this.type = "content";
|
|
||||||
this.original = this.string = string;
|
|
||||||
},
|
|
||||||
|
|
||||||
HashNode: function(pairs, locInfo) {
|
|
||||||
LocationInfo.call(this, locInfo);
|
|
||||||
this.type = "hash";
|
|
||||||
this.pairs = pairs;
|
|
||||||
},
|
|
||||||
|
|
||||||
IdNode: function(parts, locInfo) {
|
|
||||||
LocationInfo.call(this, locInfo);
|
|
||||||
this.type = "ID";
|
|
||||||
|
|
||||||
var original = "",
|
|
||||||
dig = [],
|
|
||||||
depth = 0,
|
|
||||||
depthString = '';
|
|
||||||
|
|
||||||
for(var i=0,l=parts.length; i<l; i++) {
|
|
||||||
var part = parts[i].part;
|
|
||||||
original += (parts[i].separator || '') + part;
|
|
||||||
|
|
||||||
if (part === ".." || part === "." || part === "this") {
|
|
||||||
if (dig.length > 0) {
|
|
||||||
throw new Exception("Invalid path: " + original, this);
|
|
||||||
} else if (part === "..") {
|
|
||||||
depth++;
|
|
||||||
depthString += '../';
|
|
||||||
} else {
|
|
||||||
this.isScoped = true;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
dig.push(part);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
this.original = original;
|
|
||||||
this.parts = dig;
|
|
||||||
this.string = dig.join('.');
|
|
||||||
this.depth = depth;
|
|
||||||
this.idName = depthString + this.string;
|
|
||||||
|
|
||||||
// an ID is simple if it only has one part, and that part is not
|
// an ID is simple if it only has one part, and that part is not
|
||||||
// `..` or `this`.
|
// `..` or `this`.
|
||||||
this.isSimple = parts.length === 1 && !this.isScoped && depth === 0;
|
simpleId: function(path) {
|
||||||
|
return (
|
||||||
this.stringModeValue = this.string;
|
path.parts.length === 1 && !AST.helpers.scopedId(path) && !path.depth
|
||||||
},
|
);
|
||||||
|
}
|
||||||
PartialNameNode: function(name, locInfo) {
|
|
||||||
LocationInfo.call(this, locInfo);
|
|
||||||
this.type = "PARTIAL_NAME";
|
|
||||||
this.name = name.original;
|
|
||||||
},
|
|
||||||
|
|
||||||
DataNode: function(id, locInfo) {
|
|
||||||
LocationInfo.call(this, locInfo);
|
|
||||||
this.type = "DATA";
|
|
||||||
this.id = id;
|
|
||||||
this.stringModeValue = id.stringModeValue;
|
|
||||||
this.idName = '@' + id.stringModeValue;
|
|
||||||
},
|
|
||||||
|
|
||||||
StringNode: function(string, locInfo) {
|
|
||||||
LocationInfo.call(this, locInfo);
|
|
||||||
this.type = "STRING";
|
|
||||||
this.original =
|
|
||||||
this.string =
|
|
||||||
this.stringModeValue = string;
|
|
||||||
},
|
|
||||||
|
|
||||||
NumberNode: function(number, locInfo) {
|
|
||||||
LocationInfo.call(this, locInfo);
|
|
||||||
this.type = "NUMBER";
|
|
||||||
this.original =
|
|
||||||
this.number = number;
|
|
||||||
this.stringModeValue = Number(number);
|
|
||||||
},
|
|
||||||
|
|
||||||
BooleanNode: function(bool, locInfo) {
|
|
||||||
LocationInfo.call(this, locInfo);
|
|
||||||
this.type = "BOOLEAN";
|
|
||||||
this.bool = bool;
|
|
||||||
this.stringModeValue = bool === "true";
|
|
||||||
},
|
|
||||||
|
|
||||||
CommentNode: function(comment, locInfo) {
|
|
||||||
LocationInfo.call(this, locInfo);
|
|
||||||
this.type = "comment";
|
|
||||||
this.comment = comment;
|
|
||||||
|
|
||||||
this.strip = {
|
|
||||||
inlineStandalone: true
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
// Must be exported as an object rather than the root of the module as the jison lexer
|
// Must be exported as an object rather than the root of the module as the jison lexer
|
||||||
// most modify the object to operate properly.
|
// must modify the object to operate properly.
|
||||||
export default AST;
|
export default AST;
|
||||||
|
|||||||
@@ -1,18 +1,34 @@
|
|||||||
import parser from "./parser";
|
import parser from './parser';
|
||||||
import AST from "./ast";
|
import WhitespaceControl from './whitespace-control';
|
||||||
module Helpers from "./helpers";
|
import * as Helpers from './helpers';
|
||||||
import { extend } from "../utils";
|
import { extend } from '../utils';
|
||||||
|
|
||||||
export { parser };
|
export { parser };
|
||||||
|
|
||||||
var yy = {};
|
let yy = {};
|
||||||
extend(yy, Helpers, AST);
|
extend(yy, Helpers);
|
||||||
|
|
||||||
export function parse(input) {
|
export function parseWithoutProcessing(input, options) {
|
||||||
// Just return if an already-compile AST was passed in.
|
// Just return if an already-compiled AST was passed in.
|
||||||
if (input.constructor === AST.ProgramNode) { return input; }
|
if (input.type === 'Program') {
|
||||||
|
return input;
|
||||||
|
}
|
||||||
|
|
||||||
parser.yy = yy;
|
parser.yy = yy;
|
||||||
|
|
||||||
return parser.parse(input);
|
// Altering the shared object here, but this is ok as parser is a sync operation
|
||||||
|
yy.locInfo = function(locInfo) {
|
||||||
|
return new yy.SourceLocation(options && options.srcName, locInfo);
|
||||||
|
};
|
||||||
|
|
||||||
|
let ast = parser.parse(input);
|
||||||
|
|
||||||
|
return ast;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function parse(input, options) {
|
||||||
|
let ast = parseWithoutProcessing(input, options);
|
||||||
|
let strip = new WhitespaceControl(options);
|
||||||
|
|
||||||
|
return strip.accept(ast);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,171 @@
|
|||||||
|
/* global define, require */
|
||||||
|
import { isArray } from '../utils';
|
||||||
|
|
||||||
|
let SourceNode;
|
||||||
|
|
||||||
|
try {
|
||||||
|
/* istanbul ignore next */
|
||||||
|
if (typeof define !== 'function' || !define.amd) {
|
||||||
|
// We don't support this in AMD environments. For these environments, we assume that
|
||||||
|
// they are running on the browser and thus have no need for the source-map library.
|
||||||
|
let SourceMap = require('source-map');
|
||||||
|
SourceNode = SourceMap.SourceNode;
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
/* NOP */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* istanbul ignore if: tested but not covered in istanbul due to dist build */
|
||||||
|
if (!SourceNode) {
|
||||||
|
SourceNode = function(line, column, srcFile, chunks) {
|
||||||
|
this.src = '';
|
||||||
|
if (chunks) {
|
||||||
|
this.add(chunks);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
/* istanbul ignore next */
|
||||||
|
SourceNode.prototype = {
|
||||||
|
add: function(chunks) {
|
||||||
|
if (isArray(chunks)) {
|
||||||
|
chunks = chunks.join('');
|
||||||
|
}
|
||||||
|
this.src += chunks;
|
||||||
|
},
|
||||||
|
prepend: function(chunks) {
|
||||||
|
if (isArray(chunks)) {
|
||||||
|
chunks = chunks.join('');
|
||||||
|
}
|
||||||
|
this.src = chunks + this.src;
|
||||||
|
},
|
||||||
|
toStringWithSourceMap: function() {
|
||||||
|
return { code: this.toString() };
|
||||||
|
},
|
||||||
|
toString: function() {
|
||||||
|
return this.src;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function castChunk(chunk, codeGen, loc) {
|
||||||
|
if (isArray(chunk)) {
|
||||||
|
let ret = [];
|
||||||
|
|
||||||
|
for (let i = 0, len = chunk.length; i < len; i++) {
|
||||||
|
ret.push(codeGen.wrap(chunk[i], loc));
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
} else if (typeof chunk === 'boolean' || typeof chunk === 'number') {
|
||||||
|
// Handle primitives that the SourceNode will throw up on
|
||||||
|
return chunk + '';
|
||||||
|
}
|
||||||
|
return chunk;
|
||||||
|
}
|
||||||
|
|
||||||
|
function CodeGen(srcFile) {
|
||||||
|
this.srcFile = srcFile;
|
||||||
|
this.source = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
CodeGen.prototype = {
|
||||||
|
isEmpty() {
|
||||||
|
return !this.source.length;
|
||||||
|
},
|
||||||
|
prepend: function(source, loc) {
|
||||||
|
this.source.unshift(this.wrap(source, loc));
|
||||||
|
},
|
||||||
|
push: function(source, loc) {
|
||||||
|
this.source.push(this.wrap(source, loc));
|
||||||
|
},
|
||||||
|
|
||||||
|
merge: function() {
|
||||||
|
let source = this.empty();
|
||||||
|
this.each(function(line) {
|
||||||
|
source.add([' ', line, '\n']);
|
||||||
|
});
|
||||||
|
return source;
|
||||||
|
},
|
||||||
|
|
||||||
|
each: function(iter) {
|
||||||
|
for (let i = 0, len = this.source.length; i < len; i++) {
|
||||||
|
iter(this.source[i]);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
empty: function() {
|
||||||
|
let loc = this.currentLocation || { start: {} };
|
||||||
|
return new SourceNode(loc.start.line, loc.start.column, this.srcFile);
|
||||||
|
},
|
||||||
|
wrap: function(chunk, loc = this.currentLocation || { start: {} }) {
|
||||||
|
if (chunk instanceof SourceNode) {
|
||||||
|
return chunk;
|
||||||
|
}
|
||||||
|
|
||||||
|
chunk = castChunk(chunk, this, loc);
|
||||||
|
|
||||||
|
return new SourceNode(
|
||||||
|
loc.start.line,
|
||||||
|
loc.start.column,
|
||||||
|
this.srcFile,
|
||||||
|
chunk
|
||||||
|
);
|
||||||
|
},
|
||||||
|
|
||||||
|
functionCall: function(fn, type, params) {
|
||||||
|
params = this.generateList(params);
|
||||||
|
return this.wrap([fn, type ? '.' + type + '(' : '(', params, ')']);
|
||||||
|
},
|
||||||
|
|
||||||
|
quotedString: function(str) {
|
||||||
|
return (
|
||||||
|
'"' +
|
||||||
|
(str + '')
|
||||||
|
.replace(/\\/g, '\\\\')
|
||||||
|
.replace(/"/g, '\\"')
|
||||||
|
.replace(/\n/g, '\\n')
|
||||||
|
.replace(/\r/g, '\\r')
|
||||||
|
.replace(/\u2028/g, '\\u2028') // Per Ecma-262 7.3 + 7.8.4
|
||||||
|
.replace(/\u2029/g, '\\u2029') +
|
||||||
|
'"'
|
||||||
|
);
|
||||||
|
},
|
||||||
|
|
||||||
|
objectLiteral: function(obj) {
|
||||||
|
let pairs = [];
|
||||||
|
|
||||||
|
Object.keys(obj).forEach(key => {
|
||||||
|
let value = castChunk(obj[key], this);
|
||||||
|
if (value !== 'undefined') {
|
||||||
|
pairs.push([this.quotedString(key), ':', value]);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let ret = this.generateList(pairs);
|
||||||
|
ret.prepend('{');
|
||||||
|
ret.add('}');
|
||||||
|
return ret;
|
||||||
|
},
|
||||||
|
|
||||||
|
generateList: function(entries) {
|
||||||
|
let ret = this.empty();
|
||||||
|
|
||||||
|
for (let i = 0, len = entries.length; i < len; i++) {
|
||||||
|
if (i) {
|
||||||
|
ret.add(',');
|
||||||
|
}
|
||||||
|
|
||||||
|
ret.add(castChunk(entries[i], this));
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
},
|
||||||
|
|
||||||
|
generateArray: function(entries) {
|
||||||
|
let ret = this.generateList(entries);
|
||||||
|
ret.prepend('[');
|
||||||
|
ret.add(']');
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export default CodeGen;
|
||||||
+362
-219
@@ -1,7 +1,10 @@
|
|||||||
import Exception from "../exception";
|
/* eslint-disable new-cap */
|
||||||
import {isArray} from "../utils";
|
|
||||||
|
|
||||||
var slice = [].slice;
|
import Exception from '../exception';
|
||||||
|
import { isArray, indexOf, extend } from '../utils';
|
||||||
|
import AST from './ast';
|
||||||
|
|
||||||
|
const slice = [].slice;
|
||||||
|
|
||||||
export function Compiler() {}
|
export function Compiler() {}
|
||||||
|
|
||||||
@@ -14,15 +17,18 @@ Compiler.prototype = {
|
|||||||
compiler: Compiler,
|
compiler: Compiler,
|
||||||
|
|
||||||
equals: function(other) {
|
equals: function(other) {
|
||||||
var len = this.opcodes.length;
|
let len = this.opcodes.length;
|
||||||
if (other.opcodes.length !== len) {
|
if (other.opcodes.length !== len) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (var i = 0; i < len; i++) {
|
for (let i = 0; i < len; i++) {
|
||||||
var opcode = this.opcodes[i],
|
let opcode = this.opcodes[i],
|
||||||
otherOpcode = other.opcodes[i];
|
otherOpcode = other.opcodes[i];
|
||||||
if (opcode.opcode !== otherOpcode.opcode || !argEquals(opcode.args, otherOpcode.args)) {
|
if (
|
||||||
|
opcode.opcode !== otherOpcode.opcode ||
|
||||||
|
!argEquals(opcode.args, otherOpcode.args)
|
||||||
|
) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -30,7 +36,7 @@ Compiler.prototype = {
|
|||||||
// We know that length is the same between the two arrays because they are directly tied
|
// We know that length is the same between the two arrays because they are directly tied
|
||||||
// to the opcode behavior above.
|
// to the opcode behavior above.
|
||||||
len = this.children.length;
|
len = this.children.length;
|
||||||
for (i = 0; i < len; i++) {
|
for (let i = 0; i < len; i++) {
|
||||||
if (!this.children[i].equals(other.children[i])) {
|
if (!this.children[i].equals(other.children[i])) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -42,100 +48,99 @@ Compiler.prototype = {
|
|||||||
guid: 0,
|
guid: 0,
|
||||||
|
|
||||||
compile: function(program, options) {
|
compile: function(program, options) {
|
||||||
|
this.sourceNode = [];
|
||||||
this.opcodes = [];
|
this.opcodes = [];
|
||||||
this.children = [];
|
this.children = [];
|
||||||
this.depths = {list: []};
|
|
||||||
this.options = options;
|
this.options = options;
|
||||||
this.stringParams = options.stringParams;
|
this.stringParams = options.stringParams;
|
||||||
this.trackIds = options.trackIds;
|
this.trackIds = options.trackIds;
|
||||||
|
|
||||||
// These changes will propagate to the other compiler components
|
options.blockParams = options.blockParams || [];
|
||||||
var knownHelpers = this.options.knownHelpers;
|
|
||||||
this.options.knownHelpers = {
|
options.knownHelpers = extend(
|
||||||
'helperMissing': true,
|
Object.create(null),
|
||||||
'blockHelperMissing': true,
|
{
|
||||||
'each': true,
|
helperMissing: true,
|
||||||
'if': true,
|
blockHelperMissing: true,
|
||||||
'unless': true,
|
each: true,
|
||||||
'with': true,
|
if: true,
|
||||||
'log': true,
|
unless: true,
|
||||||
'lookup': true
|
with: true,
|
||||||
};
|
log: true,
|
||||||
if (knownHelpers) {
|
lookup: true
|
||||||
for (var name in knownHelpers) {
|
},
|
||||||
this.options.knownHelpers[name] = knownHelpers[name];
|
options.knownHelpers
|
||||||
}
|
);
|
||||||
}
|
|
||||||
|
|
||||||
return this.accept(program);
|
return this.accept(program);
|
||||||
},
|
},
|
||||||
|
|
||||||
accept: function(node) {
|
|
||||||
return this[node.type](node);
|
|
||||||
},
|
|
||||||
|
|
||||||
program: function(program) {
|
|
||||||
var statements = program.statements;
|
|
||||||
|
|
||||||
for(var i=0, l=statements.length; i<l; i++) {
|
|
||||||
this.accept(statements[i]);
|
|
||||||
}
|
|
||||||
this.isSimple = l === 1;
|
|
||||||
|
|
||||||
this.depths.list = this.depths.list.sort(function(a, b) {
|
|
||||||
return a - b;
|
|
||||||
});
|
|
||||||
|
|
||||||
return this;
|
|
||||||
},
|
|
||||||
|
|
||||||
compileProgram: function(program) {
|
compileProgram: function(program) {
|
||||||
var result = new this.compiler().compile(program, this.options);
|
let childCompiler = new this.compiler(), // eslint-disable-line new-cap
|
||||||
var guid = this.guid++, depth;
|
result = childCompiler.compile(program, this.options),
|
||||||
|
guid = this.guid++;
|
||||||
|
|
||||||
this.usePartial = this.usePartial || result.usePartial;
|
this.usePartial = this.usePartial || result.usePartial;
|
||||||
|
|
||||||
this.children[guid] = result;
|
this.children[guid] = result;
|
||||||
|
this.useDepths = this.useDepths || result.useDepths;
|
||||||
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;
|
return guid;
|
||||||
},
|
},
|
||||||
|
|
||||||
block: function(block) {
|
accept: function(node) {
|
||||||
var mustache = block.mustache,
|
/* istanbul ignore next: Sanity code */
|
||||||
program = block.program,
|
if (!this[node.type]) {
|
||||||
inverse = block.inverse;
|
throw new Exception('Unknown type: ' + node.type, node);
|
||||||
|
|
||||||
if (program) {
|
|
||||||
program = this.compileProgram(program);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (inverse) {
|
this.sourceNode.unshift(node);
|
||||||
inverse = this.compileProgram(inverse);
|
let ret = this[node.type](node);
|
||||||
|
this.sourceNode.shift();
|
||||||
|
return ret;
|
||||||
|
},
|
||||||
|
|
||||||
|
Program: function(program) {
|
||||||
|
this.options.blockParams.unshift(program.blockParams);
|
||||||
|
|
||||||
|
let body = program.body,
|
||||||
|
bodyLength = body.length;
|
||||||
|
for (let i = 0; i < bodyLength; i++) {
|
||||||
|
this.accept(body[i]);
|
||||||
}
|
}
|
||||||
|
|
||||||
var sexpr = mustache.sexpr;
|
this.options.blockParams.shift();
|
||||||
var type = this.classifySexpr(sexpr);
|
|
||||||
|
|
||||||
if (type === "helper") {
|
this.isSimple = bodyLength === 1;
|
||||||
this.helperSexpr(sexpr, program, inverse);
|
this.blockParams = program.blockParams ? program.blockParams.length : 0;
|
||||||
} else if (type === "simple") {
|
|
||||||
this.simpleSexpr(sexpr);
|
return this;
|
||||||
|
},
|
||||||
|
|
||||||
|
BlockStatement: function(block) {
|
||||||
|
transformLiteralToPath(block);
|
||||||
|
|
||||||
|
let program = block.program,
|
||||||
|
inverse = block.inverse;
|
||||||
|
|
||||||
|
program = program && this.compileProgram(program);
|
||||||
|
inverse = inverse && this.compileProgram(inverse);
|
||||||
|
|
||||||
|
let type = this.classifySexpr(block);
|
||||||
|
|
||||||
|
if (type === 'helper') {
|
||||||
|
this.helperSexpr(block, program, inverse);
|
||||||
|
} else if (type === 'simple') {
|
||||||
|
this.simpleSexpr(block);
|
||||||
|
|
||||||
// now that the simple mustache is resolved, we need to
|
// now that the simple mustache is resolved, we need to
|
||||||
// evaluate it by executing `blockHelperMissing`
|
// evaluate it by executing `blockHelperMissing`
|
||||||
this.opcode('pushProgram', program);
|
this.opcode('pushProgram', program);
|
||||||
this.opcode('pushProgram', inverse);
|
this.opcode('pushProgram', inverse);
|
||||||
this.opcode('emptyHash');
|
this.opcode('emptyHash');
|
||||||
this.opcode('blockValue', sexpr.id.original);
|
this.opcode('blockValue', block.path.original);
|
||||||
} else {
|
} else {
|
||||||
this.ambiguousSexpr(sexpr, program, inverse);
|
this.ambiguousSexpr(block, program, inverse);
|
||||||
|
|
||||||
// now that the simple mustache is resolved, we need to
|
// now that the simple mustache is resolved, we need to
|
||||||
// evaluate it by executing `blockHelperMissing`
|
// evaluate it by executing `blockHelperMissing`
|
||||||
@@ -148,174 +153,239 @@ Compiler.prototype = {
|
|||||||
this.opcode('append');
|
this.opcode('append');
|
||||||
},
|
},
|
||||||
|
|
||||||
hash: function(hash) {
|
DecoratorBlock(decorator) {
|
||||||
var pairs = hash.pairs, i, l;
|
let program = decorator.program && this.compileProgram(decorator.program);
|
||||||
|
let params = this.setupFullMustacheParams(decorator, program, undefined),
|
||||||
|
path = decorator.path;
|
||||||
|
|
||||||
this.opcode('pushHash');
|
this.useDecorators = true;
|
||||||
|
this.opcode('registerDecorator', params.length, path.original);
|
||||||
for(i=0, l=pairs.length; i<l; i++) {
|
|
||||||
this.pushParam(pairs[i][1]);
|
|
||||||
}
|
|
||||||
while(i--) {
|
|
||||||
this.opcode('assignToHash', pairs[i][0]);
|
|
||||||
}
|
|
||||||
this.opcode('popHash');
|
|
||||||
},
|
},
|
||||||
|
|
||||||
partial: function(partial) {
|
PartialStatement: function(partial) {
|
||||||
var partialName = partial.partialName;
|
|
||||||
this.usePartial = true;
|
this.usePartial = true;
|
||||||
|
|
||||||
if (partial.hash) {
|
let program = partial.program;
|
||||||
this.accept(partial.hash);
|
if (program) {
|
||||||
} else {
|
program = this.compileProgram(partial.program);
|
||||||
this.opcode('push', 'undefined');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (partial.context) {
|
let params = partial.params;
|
||||||
this.accept(partial.context);
|
if (params.length > 1) {
|
||||||
} else {
|
throw new Exception(
|
||||||
this.opcode('getContext', 0);
|
'Unsupported number of partial arguments: ' + params.length,
|
||||||
this.opcode('pushContext');
|
partial
|
||||||
|
);
|
||||||
|
} else if (!params.length) {
|
||||||
|
if (this.options.explicitPartialContext) {
|
||||||
|
this.opcode('pushLiteral', 'undefined');
|
||||||
|
} else {
|
||||||
|
params.push({ type: 'PathExpression', parts: [], depth: 0 });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.opcode('invokePartial', partialName.name, partial.indent || '');
|
let partialName = partial.name.original,
|
||||||
|
isDynamic = partial.name.type === 'SubExpression';
|
||||||
|
if (isDynamic) {
|
||||||
|
this.accept(partial.name);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.setupFullMustacheParams(partial, program, undefined, true);
|
||||||
|
|
||||||
|
let indent = partial.indent || '';
|
||||||
|
if (this.options.preventIndent && indent) {
|
||||||
|
this.opcode('appendContent', indent);
|
||||||
|
indent = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
this.opcode('invokePartial', isDynamic, partialName, indent);
|
||||||
this.opcode('append');
|
this.opcode('append');
|
||||||
},
|
},
|
||||||
|
PartialBlockStatement: function(partialBlock) {
|
||||||
content: function(content) {
|
this.PartialStatement(partialBlock);
|
||||||
if (content.string) {
|
|
||||||
this.opcode('appendContent', content.string);
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
|
||||||
mustache: function(mustache) {
|
MustacheStatement: function(mustache) {
|
||||||
this.sexpr(mustache.sexpr);
|
this.SubExpression(mustache);
|
||||||
|
|
||||||
if(mustache.escaped && !this.options.noEscape) {
|
if (mustache.escaped && !this.options.noEscape) {
|
||||||
this.opcode('appendEscaped');
|
this.opcode('appendEscaped');
|
||||||
} else {
|
} else {
|
||||||
this.opcode('append');
|
this.opcode('append');
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
Decorator(decorator) {
|
||||||
ambiguousSexpr: function(sexpr, program, inverse) {
|
this.DecoratorBlock(decorator);
|
||||||
var id = sexpr.id,
|
|
||||||
name = id.parts[0],
|
|
||||||
isBlock = program != null || inverse != null;
|
|
||||||
|
|
||||||
this.opcode('getContext', id.depth);
|
|
||||||
|
|
||||||
this.opcode('pushProgram', program);
|
|
||||||
this.opcode('pushProgram', inverse);
|
|
||||||
|
|
||||||
this.ID(id);
|
|
||||||
|
|
||||||
this.opcode('invokeAmbiguous', name, isBlock);
|
|
||||||
},
|
},
|
||||||
|
|
||||||
simpleSexpr: function(sexpr) {
|
ContentStatement: function(content) {
|
||||||
var id = sexpr.id;
|
if (content.value) {
|
||||||
|
this.opcode('appendContent', content.value);
|
||||||
if (id.type === 'DATA') {
|
|
||||||
this.DATA(id);
|
|
||||||
} else if (id.parts.length) {
|
|
||||||
this.ID(id);
|
|
||||||
} else {
|
|
||||||
// Simplified ID for `this`
|
|
||||||
this.addDepth(id.depth);
|
|
||||||
this.opcode('getContext', id.depth);
|
|
||||||
this.opcode('pushContext');
|
|
||||||
}
|
|
||||||
|
|
||||||
this.opcode('resolvePossibleLambda');
|
|
||||||
},
|
|
||||||
|
|
||||||
helperSexpr: function(sexpr, program, inverse) {
|
|
||||||
var params = this.setupFullMustacheParams(sexpr, program, inverse),
|
|
||||||
id = sexpr.id,
|
|
||||||
name = id.parts[0];
|
|
||||||
|
|
||||||
if (this.options.knownHelpers[name]) {
|
|
||||||
this.opcode('invokeKnownHelper', params.length, name);
|
|
||||||
} else if (this.options.knownHelpersOnly) {
|
|
||||||
throw new Exception("You specified knownHelpersOnly, but used the unknown helper " + name, sexpr);
|
|
||||||
} else {
|
|
||||||
id.falsy = true;
|
|
||||||
|
|
||||||
this.ID(id);
|
|
||||||
this.opcode('invokeHelper', params.length, id.original, id.isSimple);
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
sexpr: function(sexpr) {
|
CommentStatement: function() {},
|
||||||
var type = this.classifySexpr(sexpr);
|
|
||||||
|
|
||||||
if (type === "simple") {
|
SubExpression: function(sexpr) {
|
||||||
|
transformLiteralToPath(sexpr);
|
||||||
|
let type = this.classifySexpr(sexpr);
|
||||||
|
|
||||||
|
if (type === 'simple') {
|
||||||
this.simpleSexpr(sexpr);
|
this.simpleSexpr(sexpr);
|
||||||
} else if (type === "helper") {
|
} else if (type === 'helper') {
|
||||||
this.helperSexpr(sexpr);
|
this.helperSexpr(sexpr);
|
||||||
} else {
|
} else {
|
||||||
this.ambiguousSexpr(sexpr);
|
this.ambiguousSexpr(sexpr);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
ambiguousSexpr: function(sexpr, program, inverse) {
|
||||||
|
let path = sexpr.path,
|
||||||
|
name = path.parts[0],
|
||||||
|
isBlock = program != null || inverse != null;
|
||||||
|
|
||||||
ID: function(id) {
|
this.opcode('getContext', path.depth);
|
||||||
this.addDepth(id.depth);
|
|
||||||
this.opcode('getContext', id.depth);
|
|
||||||
|
|
||||||
var name = id.parts[0];
|
this.opcode('pushProgram', program);
|
||||||
if (!name) {
|
this.opcode('pushProgram', inverse);
|
||||||
// Context reference, i.e. `{{foo .}}` or `{{foo ..}}`
|
|
||||||
this.opcode('pushContext');
|
path.strict = true;
|
||||||
|
this.accept(path);
|
||||||
|
|
||||||
|
this.opcode('invokeAmbiguous', name, isBlock);
|
||||||
|
},
|
||||||
|
|
||||||
|
simpleSexpr: function(sexpr) {
|
||||||
|
let path = sexpr.path;
|
||||||
|
path.strict = true;
|
||||||
|
this.accept(path);
|
||||||
|
this.opcode('resolvePossibleLambda');
|
||||||
|
},
|
||||||
|
|
||||||
|
helperSexpr: function(sexpr, program, inverse) {
|
||||||
|
let params = this.setupFullMustacheParams(sexpr, program, inverse),
|
||||||
|
path = sexpr.path,
|
||||||
|
name = path.parts[0];
|
||||||
|
|
||||||
|
if (this.options.knownHelpers[name]) {
|
||||||
|
this.opcode('invokeKnownHelper', params.length, name);
|
||||||
|
} else if (this.options.knownHelpersOnly) {
|
||||||
|
throw new Exception(
|
||||||
|
'You specified knownHelpersOnly, but used the unknown helper ' + name,
|
||||||
|
sexpr
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
this.opcode('lookupOnContext', id.parts, id.falsy, id.isScoped);
|
path.strict = true;
|
||||||
|
path.falsy = true;
|
||||||
|
|
||||||
|
this.accept(path);
|
||||||
|
this.opcode(
|
||||||
|
'invokeHelper',
|
||||||
|
params.length,
|
||||||
|
path.original,
|
||||||
|
AST.helpers.simpleId(path)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
DATA: function(data) {
|
PathExpression: function(path) {
|
||||||
this.options.data = true;
|
this.addDepth(path.depth);
|
||||||
this.opcode('lookupData', data.id.depth, data.id.parts);
|
this.opcode('getContext', path.depth);
|
||||||
|
|
||||||
|
let name = path.parts[0],
|
||||||
|
scoped = AST.helpers.scopedId(path),
|
||||||
|
blockParamId = !path.depth && !scoped && this.blockParamIndex(name);
|
||||||
|
|
||||||
|
if (blockParamId) {
|
||||||
|
this.opcode('lookupBlockParam', blockParamId, path.parts);
|
||||||
|
} else if (!name) {
|
||||||
|
// Context reference, i.e. `{{foo .}}` or `{{foo ..}}`
|
||||||
|
this.opcode('pushContext');
|
||||||
|
} else if (path.data) {
|
||||||
|
this.options.data = true;
|
||||||
|
this.opcode('lookupData', path.depth, path.parts, path.strict);
|
||||||
|
} else {
|
||||||
|
this.opcode(
|
||||||
|
'lookupOnContext',
|
||||||
|
path.parts,
|
||||||
|
path.falsy,
|
||||||
|
path.strict,
|
||||||
|
scoped
|
||||||
|
);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
STRING: function(string) {
|
StringLiteral: function(string) {
|
||||||
this.opcode('pushString', string.string);
|
this.opcode('pushString', string.value);
|
||||||
},
|
},
|
||||||
|
|
||||||
NUMBER: function(number) {
|
NumberLiteral: function(number) {
|
||||||
this.opcode('pushLiteral', number.number);
|
this.opcode('pushLiteral', number.value);
|
||||||
},
|
},
|
||||||
|
|
||||||
BOOLEAN: function(bool) {
|
BooleanLiteral: function(bool) {
|
||||||
this.opcode('pushLiteral', bool.bool);
|
this.opcode('pushLiteral', bool.value);
|
||||||
},
|
},
|
||||||
|
|
||||||
comment: function() {},
|
UndefinedLiteral: function() {
|
||||||
|
this.opcode('pushLiteral', 'undefined');
|
||||||
|
},
|
||||||
|
|
||||||
|
NullLiteral: function() {
|
||||||
|
this.opcode('pushLiteral', 'null');
|
||||||
|
},
|
||||||
|
|
||||||
|
Hash: function(hash) {
|
||||||
|
let pairs = hash.pairs,
|
||||||
|
i = 0,
|
||||||
|
l = pairs.length;
|
||||||
|
|
||||||
|
this.opcode('pushHash');
|
||||||
|
|
||||||
|
for (; i < l; i++) {
|
||||||
|
this.pushParam(pairs[i].value);
|
||||||
|
}
|
||||||
|
while (i--) {
|
||||||
|
this.opcode('assignToHash', pairs[i].key);
|
||||||
|
}
|
||||||
|
this.opcode('popHash');
|
||||||
|
},
|
||||||
|
|
||||||
// HELPERS
|
// HELPERS
|
||||||
opcode: function(name) {
|
opcode: function(name) {
|
||||||
this.opcodes.push({ opcode: name, args: slice.call(arguments, 1) });
|
this.opcodes.push({
|
||||||
|
opcode: name,
|
||||||
|
args: slice.call(arguments, 1),
|
||||||
|
loc: this.sourceNode[0].loc
|
||||||
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
addDepth: function(depth) {
|
addDepth: function(depth) {
|
||||||
if(depth === 0) { return; }
|
if (!depth) {
|
||||||
|
return;
|
||||||
if(!this.depths[depth]) {
|
|
||||||
this.depths[depth] = true;
|
|
||||||
this.depths.list.push(depth);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.useDepths = true;
|
||||||
},
|
},
|
||||||
|
|
||||||
classifySexpr: function(sexpr) {
|
classifySexpr: function(sexpr) {
|
||||||
var isHelper = sexpr.isHelper;
|
let isSimple = AST.helpers.simpleId(sexpr.path);
|
||||||
var isEligible = sexpr.eligibleHelper;
|
|
||||||
var options = this.options;
|
let isBlockParam = isSimple && !!this.blockParamIndex(sexpr.path.parts[0]);
|
||||||
|
|
||||||
|
// a mustache is an eligible helper if:
|
||||||
|
// * its id is simple (a single part, not `this` or `..`)
|
||||||
|
let isHelper = !isBlockParam && AST.helpers.helperExpression(sexpr);
|
||||||
|
|
||||||
|
// 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.
|
||||||
|
let isEligible = !isBlockParam && (isHelper || isSimple);
|
||||||
|
|
||||||
// if ambiguous, we can possibly resolve the ambiguity now
|
// if ambiguous, we can possibly resolve the ambiguity now
|
||||||
// An eligible helper is one that does not have a complex path, i.e. `this.foo`, `../foo` etc.
|
// An eligible helper is one that does not have a complex path, i.e. `this.foo`, `../foo` etc.
|
||||||
if (isEligible && !isHelper) {
|
if (isEligible && !isHelper) {
|
||||||
var name = sexpr.id.parts[0];
|
let name = sexpr.path.parts[0],
|
||||||
|
options = this.options;
|
||||||
if (options.knownHelpers[name]) {
|
if (options.knownHelpers[name]) {
|
||||||
isHelper = true;
|
isHelper = true;
|
||||||
} else if (options.knownHelpersOnly) {
|
} else if (options.knownHelpersOnly) {
|
||||||
@@ -323,58 +393,105 @@ Compiler.prototype = {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isHelper) { return "helper"; }
|
if (isHelper) {
|
||||||
else if (isEligible) { return "ambiguous"; }
|
return 'helper';
|
||||||
else { return "simple"; }
|
} else if (isEligible) {
|
||||||
|
return 'ambiguous';
|
||||||
|
} else {
|
||||||
|
return 'simple';
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
pushParams: function(params) {
|
pushParams: function(params) {
|
||||||
for(var i=0, l=params.length; i<l; i++) {
|
for (let i = 0, l = params.length; i < l; i++) {
|
||||||
this.pushParam(params[i]);
|
this.pushParam(params[i]);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
pushParam: function(val) {
|
pushParam: function(val) {
|
||||||
|
let value = val.value != null ? val.value : val.original || '';
|
||||||
|
|
||||||
if (this.stringParams) {
|
if (this.stringParams) {
|
||||||
if(val.depth) {
|
if (value.replace) {
|
||||||
|
value = value.replace(/^(\.?\.\/)*/g, '').replace(/\//g, '.');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (val.depth) {
|
||||||
this.addDepth(val.depth);
|
this.addDepth(val.depth);
|
||||||
}
|
}
|
||||||
this.opcode('getContext', val.depth || 0);
|
this.opcode('getContext', val.depth || 0);
|
||||||
this.opcode('pushStringParam', val.stringModeValue, val.type);
|
this.opcode('pushStringParam', value, val.type);
|
||||||
|
|
||||||
if (val.type === 'sexpr') {
|
if (val.type === 'SubExpression') {
|
||||||
// Subexpressions get evaluated and passed in
|
// SubExpressions get evaluated and passed in
|
||||||
// in string params mode.
|
// in string params mode.
|
||||||
this.sexpr(val);
|
this.accept(val);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (this.trackIds) {
|
if (this.trackIds) {
|
||||||
this.opcode('pushId', val.type, val.idName || val.stringModeValue);
|
let blockParamIndex;
|
||||||
|
if (val.parts && !AST.helpers.scopedId(val) && !val.depth) {
|
||||||
|
blockParamIndex = this.blockParamIndex(val.parts[0]);
|
||||||
|
}
|
||||||
|
if (blockParamIndex) {
|
||||||
|
let blockParamChild = val.parts.slice(1).join('.');
|
||||||
|
this.opcode('pushId', 'BlockParam', blockParamIndex, blockParamChild);
|
||||||
|
} else {
|
||||||
|
value = val.original || value;
|
||||||
|
if (value.replace) {
|
||||||
|
value = value
|
||||||
|
.replace(/^this(?:\.|$)/, '')
|
||||||
|
.replace(/^\.\//, '')
|
||||||
|
.replace(/^\.$/, '');
|
||||||
|
}
|
||||||
|
|
||||||
|
this.opcode('pushId', val.type, value);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
this.accept(val);
|
this.accept(val);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
setupFullMustacheParams: function(sexpr, program, inverse) {
|
setupFullMustacheParams: function(sexpr, program, inverse, omitEmpty) {
|
||||||
var params = sexpr.params;
|
let params = sexpr.params;
|
||||||
this.pushParams(params);
|
this.pushParams(params);
|
||||||
|
|
||||||
this.opcode('pushProgram', program);
|
this.opcode('pushProgram', program);
|
||||||
this.opcode('pushProgram', inverse);
|
this.opcode('pushProgram', inverse);
|
||||||
|
|
||||||
if (sexpr.hash) {
|
if (sexpr.hash) {
|
||||||
this.hash(sexpr.hash);
|
this.accept(sexpr.hash);
|
||||||
} else {
|
} else {
|
||||||
this.opcode('emptyHash');
|
this.opcode('emptyHash', omitEmpty);
|
||||||
}
|
}
|
||||||
|
|
||||||
return params;
|
return params;
|
||||||
|
},
|
||||||
|
|
||||||
|
blockParamIndex: function(name) {
|
||||||
|
for (
|
||||||
|
let depth = 0, len = this.options.blockParams.length;
|
||||||
|
depth < len;
|
||||||
|
depth++
|
||||||
|
) {
|
||||||
|
let blockParams = this.options.blockParams[depth],
|
||||||
|
param = blockParams && indexOf(blockParams, name);
|
||||||
|
if (blockParams && param >= 0) {
|
||||||
|
return [depth, param];
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export function precompile(input, options, env) {
|
export function precompile(input, options, env) {
|
||||||
if (input == null || (typeof input !== 'string' && input.constructor !== env.AST.ProgramNode)) {
|
if (
|
||||||
throw new Exception("You must pass a string or Handlebars AST to Handlebars.precompile. You passed " + input);
|
input == null ||
|
||||||
|
(typeof input !== 'string' && input.type !== 'Program')
|
||||||
|
) {
|
||||||
|
throw new Exception(
|
||||||
|
'You must pass a string or Handlebars AST to Handlebars.precompile. You passed ' +
|
||||||
|
input
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
options = options || {};
|
options = options || {};
|
||||||
@@ -385,18 +502,23 @@ export function precompile(input, options, env) {
|
|||||||
options.useDepths = true;
|
options.useDepths = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
var ast = env.parse(input);
|
let ast = env.parse(input, options),
|
||||||
var environment = new env.Compiler().compile(ast, options);
|
environment = new env.Compiler().compile(ast, options);
|
||||||
return new env.JavaScriptCompiler().compile(environment, options);
|
return new env.JavaScriptCompiler().compile(environment, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function compile(input, options, env) {
|
export function compile(input, options = {}, env) {
|
||||||
if (input == null || (typeof input !== 'string' && input.constructor !== env.AST.ProgramNode)) {
|
if (
|
||||||
throw new Exception("You must pass a string or Handlebars AST to Handlebars.compile. You passed " + input);
|
input == null ||
|
||||||
|
(typeof input !== 'string' && input.type !== 'Program')
|
||||||
|
) {
|
||||||
|
throw new Exception(
|
||||||
|
'You must pass a string or Handlebars AST to Handlebars.compile. You passed ' +
|
||||||
|
input
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
options = options || {};
|
options = extend({}, options);
|
||||||
|
|
||||||
if (!('data' in options)) {
|
if (!('data' in options)) {
|
||||||
options.data = true;
|
options.data = true;
|
||||||
}
|
}
|
||||||
@@ -404,33 +526,38 @@ export function compile(input, options, env) {
|
|||||||
options.useDepths = true;
|
options.useDepths = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
var compiled;
|
let compiled;
|
||||||
|
|
||||||
function compileInput() {
|
function compileInput() {
|
||||||
var ast = env.parse(input);
|
let ast = env.parse(input, options),
|
||||||
var environment = new env.Compiler().compile(ast, options);
|
environment = new env.Compiler().compile(ast, options),
|
||||||
var templateSpec = new env.JavaScriptCompiler().compile(environment, options, undefined, true);
|
templateSpec = new env.JavaScriptCompiler().compile(
|
||||||
|
environment,
|
||||||
|
options,
|
||||||
|
undefined,
|
||||||
|
true
|
||||||
|
);
|
||||||
return env.template(templateSpec);
|
return env.template(templateSpec);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Template is only compiled on first use and cached after that point.
|
// Template is only compiled on first use and cached after that point.
|
||||||
var ret = function(context, options) {
|
function ret(context, execOptions) {
|
||||||
if (!compiled) {
|
if (!compiled) {
|
||||||
compiled = compileInput();
|
compiled = compileInput();
|
||||||
}
|
}
|
||||||
return compiled.call(this, context, options);
|
return compiled.call(this, context, execOptions);
|
||||||
|
}
|
||||||
|
ret._setup = function(setupOptions) {
|
||||||
|
if (!compiled) {
|
||||||
|
compiled = compileInput();
|
||||||
|
}
|
||||||
|
return compiled._setup(setupOptions);
|
||||||
};
|
};
|
||||||
ret._setup = function(options) {
|
ret._child = function(i, data, blockParams, depths) {
|
||||||
if (!compiled) {
|
if (!compiled) {
|
||||||
compiled = compileInput();
|
compiled = compileInput();
|
||||||
}
|
}
|
||||||
return compiled._setup(options);
|
return compiled._child(i, data, blockParams, depths);
|
||||||
};
|
|
||||||
ret._child = function(i, data, depths) {
|
|
||||||
if (!compiled) {
|
|
||||||
compiled = compileInput();
|
|
||||||
}
|
|
||||||
return compiled._child(i, data, depths);
|
|
||||||
};
|
};
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
@@ -441,7 +568,7 @@ function argEquals(a, b) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (isArray(a) && isArray(b) && a.length === b.length) {
|
if (isArray(a) && isArray(b) && a.length === b.length) {
|
||||||
for (var i = 0; i < a.length; i++) {
|
for (let i = 0; i < a.length; i++) {
|
||||||
if (!argEquals(a[i], b[i])) {
|
if (!argEquals(a[i], b[i])) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -449,3 +576,19 @@ function argEquals(a, b) {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function transformLiteralToPath(sexpr) {
|
||||||
|
if (!sexpr.path.parts) {
|
||||||
|
let literal = sexpr.path;
|
||||||
|
// Casting to string here to make false and 0 literal values play nicely with the rest
|
||||||
|
// of the system.
|
||||||
|
sexpr.path = {
|
||||||
|
type: 'PathExpression',
|
||||||
|
data: false,
|
||||||
|
depth: 0,
|
||||||
|
parts: [literal.original + ''],
|
||||||
|
original: literal.original + '',
|
||||||
|
loc: literal.loc
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
+185
-152
@@ -1,186 +1,219 @@
|
|||||||
import Exception from "../exception";
|
import Exception from '../exception';
|
||||||
|
|
||||||
|
function validateClose(open, close) {
|
||||||
|
close = close.path ? close.path.original : close;
|
||||||
|
|
||||||
|
if (open.path.original !== close) {
|
||||||
|
let errorNode = { loc: open.path.loc };
|
||||||
|
|
||||||
|
throw new Exception(
|
||||||
|
open.path.original + " doesn't match " + close,
|
||||||
|
errorNode
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function SourceLocation(source, locInfo) {
|
||||||
|
this.source = source;
|
||||||
|
this.start = {
|
||||||
|
line: locInfo.first_line,
|
||||||
|
column: locInfo.first_column
|
||||||
|
};
|
||||||
|
this.end = {
|
||||||
|
line: locInfo.last_line,
|
||||||
|
column: locInfo.last_column
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function id(token) {
|
||||||
|
if (/^\[.*\]$/.test(token)) {
|
||||||
|
return token.substring(1, token.length - 1);
|
||||||
|
} else {
|
||||||
|
return token;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export function stripFlags(open, close) {
|
export function stripFlags(open, close) {
|
||||||
return {
|
return {
|
||||||
left: open.charAt(2) === '~',
|
open: open.charAt(2) === '~',
|
||||||
right: close.charAt(close.length-3) === '~'
|
close: close.charAt(close.length - 3) === '~'
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function stripComment(comment) {
|
||||||
|
return comment.replace(/^\{\{~?!-?-?/, '').replace(/-?-?~?\}\}$/, '');
|
||||||
|
}
|
||||||
|
|
||||||
export function prepareBlock(mustache, program, inverseAndProgram, close, inverted, locInfo) {
|
export function preparePath(data, parts, loc) {
|
||||||
/*jshint -W040 */
|
loc = this.locInfo(loc);
|
||||||
if (mustache.sexpr.id.original !== close.path.original) {
|
|
||||||
throw new Exception(mustache.sexpr.id.original + ' doesn\'t match ' + close.path.original, mustache);
|
let original = data ? '@' : '',
|
||||||
|
dig = [],
|
||||||
|
depth = 0;
|
||||||
|
|
||||||
|
for (let i = 0, l = parts.length; i < l; i++) {
|
||||||
|
let part = parts[i].part,
|
||||||
|
// If we have [] syntax then we do not treat path references as operators,
|
||||||
|
// i.e. foo.[this] resolves to approximately context.foo['this']
|
||||||
|
isLiteral = parts[i].original !== part;
|
||||||
|
original += (parts[i].separator || '') + part;
|
||||||
|
|
||||||
|
if (!isLiteral && (part === '..' || part === '.' || part === 'this')) {
|
||||||
|
if (dig.length > 0) {
|
||||||
|
throw new Exception('Invalid path: ' + original, { loc });
|
||||||
|
} else if (part === '..') {
|
||||||
|
depth++;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
dig.push(part);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var inverse = inverseAndProgram && inverseAndProgram.program;
|
return {
|
||||||
|
type: 'PathExpression',
|
||||||
|
data,
|
||||||
|
depth,
|
||||||
|
parts: dig,
|
||||||
|
original,
|
||||||
|
loc
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
var strip = {
|
export function prepareMustache(path, params, hash, open, strip, locInfo) {
|
||||||
left: mustache.strip.left,
|
// Must use charAt to support IE pre-10
|
||||||
right: close.strip.right,
|
let escapeFlag = open.charAt(3) || open.charAt(2),
|
||||||
|
escaped = escapeFlag !== '{' && escapeFlag !== '&';
|
||||||
|
|
||||||
// Determine the standalone candiacy. Basically flag our content as being possibly standalone
|
let decorator = /\*/.test(open);
|
||||||
// so our parent can determine if we actually are standalone
|
return {
|
||||||
openStandalone: isNextWhitespace(program.statements),
|
type: decorator ? 'Decorator' : 'MustacheStatement',
|
||||||
closeStandalone: isPrevWhitespace((inverse || program).statements)
|
path,
|
||||||
|
params,
|
||||||
|
hash,
|
||||||
|
escaped,
|
||||||
|
strip,
|
||||||
|
loc: this.locInfo(locInfo)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function prepareRawBlock(openRawBlock, contents, close, locInfo) {
|
||||||
|
validateClose(openRawBlock, close);
|
||||||
|
|
||||||
|
locInfo = this.locInfo(locInfo);
|
||||||
|
let program = {
|
||||||
|
type: 'Program',
|
||||||
|
body: contents,
|
||||||
|
strip: {},
|
||||||
|
loc: locInfo
|
||||||
};
|
};
|
||||||
|
|
||||||
if (mustache.strip.right) {
|
return {
|
||||||
omitRight(program.statements, null, true);
|
type: 'BlockStatement',
|
||||||
|
path: openRawBlock.path,
|
||||||
|
params: openRawBlock.params,
|
||||||
|
hash: openRawBlock.hash,
|
||||||
|
program,
|
||||||
|
openStrip: {},
|
||||||
|
inverseStrip: {},
|
||||||
|
closeStrip: {},
|
||||||
|
loc: locInfo
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function prepareBlock(
|
||||||
|
openBlock,
|
||||||
|
program,
|
||||||
|
inverseAndProgram,
|
||||||
|
close,
|
||||||
|
inverted,
|
||||||
|
locInfo
|
||||||
|
) {
|
||||||
|
if (close && close.path) {
|
||||||
|
validateClose(openBlock, close);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (inverse) {
|
let decorator = /\*/.test(openBlock.open);
|
||||||
var inverseStrip = inverseAndProgram.strip;
|
|
||||||
|
|
||||||
if (inverseStrip.left) {
|
program.blockParams = openBlock.blockParams;
|
||||||
omitLeft(program.statements, null, true);
|
|
||||||
}
|
let inverse, inverseStrip;
|
||||||
if (inverseStrip.right) {
|
|
||||||
omitRight(inverse.statements, null, true);
|
if (inverseAndProgram) {
|
||||||
}
|
if (decorator) {
|
||||||
if (close.strip.left) {
|
throw new Exception(
|
||||||
omitLeft(inverse.statements, null, true);
|
'Unexpected inverse block on decorator',
|
||||||
|
inverseAndProgram
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Find standalone else statments
|
if (inverseAndProgram.chain) {
|
||||||
if (isPrevWhitespace(program.statements)
|
inverseAndProgram.program.body[0].closeStrip = close.strip;
|
||||||
&& isNextWhitespace(inverse.statements)) {
|
}
|
||||||
|
|
||||||
omitLeft(program.statements);
|
inverseStrip = inverseAndProgram.strip;
|
||||||
omitRight(inverse.statements);
|
inverse = inverseAndProgram.program;
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (close.strip.left) {
|
|
||||||
omitLeft(program.statements, null, true);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (inverted) {
|
if (inverted) {
|
||||||
return new this.BlockNode(mustache, inverse, program, strip, locInfo);
|
inverted = inverse;
|
||||||
} else {
|
inverse = program;
|
||||||
return new this.BlockNode(mustache, program, inverse, strip, locInfo);
|
program = inverted;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
type: decorator ? 'DecoratorBlock' : 'BlockStatement',
|
||||||
|
path: openBlock.path,
|
||||||
|
params: openBlock.params,
|
||||||
|
hash: openBlock.hash,
|
||||||
|
program,
|
||||||
|
inverse,
|
||||||
|
openStrip: openBlock.strip,
|
||||||
|
inverseStrip,
|
||||||
|
closeStrip: close && close.strip,
|
||||||
|
loc: this.locInfo(locInfo)
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function prepareProgram(statements, loc) {
|
||||||
|
if (!loc && statements.length) {
|
||||||
|
const firstLoc = statements[0].loc,
|
||||||
|
lastLoc = statements[statements.length - 1].loc;
|
||||||
|
|
||||||
export function prepareProgram(statements, isRoot) {
|
/* istanbul ignore else */
|
||||||
for (var i = 0, l = statements.length; i < l; i++) {
|
if (firstLoc && lastLoc) {
|
||||||
var current = statements[i],
|
loc = {
|
||||||
strip = current.strip;
|
source: firstLoc.source,
|
||||||
|
start: {
|
||||||
if (!strip) {
|
line: firstLoc.start.line,
|
||||||
continue;
|
column: firstLoc.start.column
|
||||||
}
|
},
|
||||||
|
end: {
|
||||||
var _isPrevWhitespace = isPrevWhitespace(statements, i, isRoot, current.type === 'partial'),
|
line: lastLoc.end.line,
|
||||||
_isNextWhitespace = isNextWhitespace(statements, i, isRoot),
|
column: lastLoc.end.column
|
||||||
|
|
||||||
openStandalone = strip.openStandalone && _isPrevWhitespace,
|
|
||||||
closeStandalone = strip.closeStandalone && _isNextWhitespace,
|
|
||||||
inlineStandalone = strip.inlineStandalone && _isPrevWhitespace && _isNextWhitespace;
|
|
||||||
|
|
||||||
if (strip.right) {
|
|
||||||
omitRight(statements, i, true);
|
|
||||||
}
|
|
||||||
if (strip.left) {
|
|
||||||
omitLeft(statements, i, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (inlineStandalone) {
|
|
||||||
omitRight(statements, i);
|
|
||||||
|
|
||||||
if (omitLeft(statements, i)) {
|
|
||||||
// If we are on a standalone node, save the indent info for partials
|
|
||||||
if (current.type === 'partial') {
|
|
||||||
current.indent = (/([ \t]+$)/).exec(statements[i-1].original) ? RegExp.$1 : '';
|
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
}
|
|
||||||
if (openStandalone) {
|
|
||||||
omitRight((current.program || current.inverse).statements);
|
|
||||||
|
|
||||||
// Strip out the previous content node if it's whitespace only
|
|
||||||
omitLeft(statements, i);
|
|
||||||
}
|
|
||||||
if (closeStandalone) {
|
|
||||||
// Always strip the next node
|
|
||||||
omitRight(statements, i);
|
|
||||||
|
|
||||||
omitLeft((current.inverse || current.program).statements);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return statements;
|
return {
|
||||||
|
type: 'Program',
|
||||||
|
body: statements,
|
||||||
|
strip: {},
|
||||||
|
loc: loc
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function isPrevWhitespace(statements, i, isRoot) {
|
export function preparePartialBlock(open, program, close, locInfo) {
|
||||||
if (i === undefined) {
|
validateClose(open, close);
|
||||||
i = statements.length;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Nodes that end with newlines are considered whitespace (but are special
|
return {
|
||||||
// cased for strip operations)
|
type: 'PartialBlockStatement',
|
||||||
var prev = statements[i-1],
|
name: open.path,
|
||||||
sibling = statements[i-2];
|
params: open.params,
|
||||||
if (!prev) {
|
hash: open.hash,
|
||||||
return isRoot;
|
program,
|
||||||
}
|
openStrip: open.strip,
|
||||||
|
closeStrip: close && close.strip,
|
||||||
if (prev.type === 'content') {
|
loc: this.locInfo(locInfo)
|
||||||
return (sibling || !isRoot ? (/\r?\n\s*?$/) : (/(^|\r?\n)\s*?$/)).test(prev.original);
|
};
|
||||||
}
|
|
||||||
}
|
|
||||||
function isNextWhitespace(statements, i, isRoot) {
|
|
||||||
if (i === undefined) {
|
|
||||||
i = -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
var next = statements[i+1],
|
|
||||||
sibling = statements[i+2];
|
|
||||||
if (!next) {
|
|
||||||
return isRoot;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (next.type === 'content') {
|
|
||||||
return (sibling || !isRoot ? (/^\s*?\r?\n/) : (/^\s*?(\r?\n|$)/)).test(next.original);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Marks the node to the right of the position as omitted.
|
|
||||||
// I.e. {{foo}}' ' will mark the ' ' node as omitted.
|
|
||||||
//
|
|
||||||
// If i is undefined, then the first child will be marked as such.
|
|
||||||
//
|
|
||||||
// If mulitple is truthy then all whitespace will be stripped out until non-whitespace
|
|
||||||
// content is met.
|
|
||||||
function omitRight(statements, i, multiple) {
|
|
||||||
var current = statements[i == null ? 0 : i + 1];
|
|
||||||
if (!current || current.type !== 'content' || (!multiple && current.rightStripped)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var original = current.string;
|
|
||||||
current.string = current.string.replace(multiple ? (/^\s+/) : (/^[ \t]*\r?\n?/), '');
|
|
||||||
current.rightStripped = current.string !== original;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Marks the node to the left of the position as omitted.
|
|
||||||
// I.e. ' '{{foo}} will mark the ' ' node as omitted.
|
|
||||||
//
|
|
||||||
// If i is undefined then the last child will be marked as such.
|
|
||||||
//
|
|
||||||
// If mulitple is truthy then all whitespace will be stripped out until non-whitespace
|
|
||||||
// content is met.
|
|
||||||
function omitLeft(statements, i, multiple) {
|
|
||||||
var current = statements[i == null ? statements.length - 1 : i - 1];
|
|
||||||
if (!current || current.type !== 'content' || (!multiple && current.leftStripped)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// We omit the last node if it's whitespace only and not preceeded by a non-content node.
|
|
||||||
var original = current.string;
|
|
||||||
current.string = current.string.replace(multiple ? (/\s+$/) : (/[ \t]+$/), '');
|
|
||||||
current.leftStripped = current.string !== original;
|
|
||||||
return current.leftStripped;
|
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -1,4 +1,5 @@
|
|||||||
import Visitor from "./visitor";
|
/* eslint-disable new-cap */
|
||||||
|
import Visitor from './visitor';
|
||||||
|
|
||||||
export function print(ast) {
|
export function print(ast) {
|
||||||
return new PrintVisitor().accept(ast);
|
return new PrintVisitor().accept(ast);
|
||||||
@@ -11,23 +12,33 @@ export function PrintVisitor() {
|
|||||||
PrintVisitor.prototype = new Visitor();
|
PrintVisitor.prototype = new Visitor();
|
||||||
|
|
||||||
PrintVisitor.prototype.pad = function(string) {
|
PrintVisitor.prototype.pad = function(string) {
|
||||||
var out = "";
|
let out = '';
|
||||||
|
|
||||||
for(var i=0,l=this.padding; i<l; i++) {
|
for (let i = 0, l = this.padding; i < l; i++) {
|
||||||
out = out + " ";
|
out += ' ';
|
||||||
}
|
}
|
||||||
|
|
||||||
out = out + string + "\n";
|
out += string + '\n';
|
||||||
return out;
|
return out;
|
||||||
};
|
};
|
||||||
|
|
||||||
PrintVisitor.prototype.program = function(program) {
|
PrintVisitor.prototype.Program = function(program) {
|
||||||
var out = "",
|
let out = '',
|
||||||
statements = program.statements,
|
body = program.body,
|
||||||
i, l;
|
i,
|
||||||
|
l;
|
||||||
|
|
||||||
for(i=0, l=statements.length; i<l; i++) {
|
if (program.blockParams) {
|
||||||
out = out + this.accept(statements[i]);
|
let blockParams = 'BLOCK PARAMS: [';
|
||||||
|
for (i = 0, l = program.blockParams.length; i < l; i++) {
|
||||||
|
blockParams += ' ' + program.blockParams[i];
|
||||||
|
}
|
||||||
|
blockParams += ' ]';
|
||||||
|
out += this.pad(blockParams);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (i = 0, l = body.length; i < l; i++) {
|
||||||
|
out += this.accept(body[i]);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.padding--;
|
this.padding--;
|
||||||
@@ -35,107 +46,133 @@ PrintVisitor.prototype.program = function(program) {
|
|||||||
return out;
|
return out;
|
||||||
};
|
};
|
||||||
|
|
||||||
PrintVisitor.prototype.block = function(block) {
|
PrintVisitor.prototype.MustacheStatement = function(mustache) {
|
||||||
var out = "";
|
return this.pad('{{ ' + this.SubExpression(mustache) + ' }}');
|
||||||
|
};
|
||||||
|
PrintVisitor.prototype.Decorator = function(mustache) {
|
||||||
|
return this.pad('{{ DIRECTIVE ' + this.SubExpression(mustache) + ' }}');
|
||||||
|
};
|
||||||
|
|
||||||
out = out + this.pad("BLOCK:");
|
PrintVisitor.prototype.BlockStatement = PrintVisitor.prototype.DecoratorBlock = function(
|
||||||
|
block
|
||||||
|
) {
|
||||||
|
let out = '';
|
||||||
|
|
||||||
|
out += this.pad(
|
||||||
|
(block.type === 'DecoratorBlock' ? 'DIRECTIVE ' : '') + 'BLOCK:'
|
||||||
|
);
|
||||||
this.padding++;
|
this.padding++;
|
||||||
out = out + this.accept(block.mustache);
|
out += this.pad(this.SubExpression(block));
|
||||||
if (block.program) {
|
if (block.program) {
|
||||||
out = out + this.pad("PROGRAM:");
|
out += this.pad('PROGRAM:');
|
||||||
this.padding++;
|
this.padding++;
|
||||||
out = out + this.accept(block.program);
|
out += this.accept(block.program);
|
||||||
this.padding--;
|
this.padding--;
|
||||||
}
|
}
|
||||||
if (block.inverse) {
|
if (block.inverse) {
|
||||||
if (block.program) { this.padding++; }
|
if (block.program) {
|
||||||
out = out + this.pad("{{^}}");
|
this.padding++;
|
||||||
|
}
|
||||||
|
out += this.pad('{{^}}');
|
||||||
this.padding++;
|
this.padding++;
|
||||||
out = out + this.accept(block.inverse);
|
out += this.accept(block.inverse);
|
||||||
this.padding--;
|
this.padding--;
|
||||||
if (block.program) { this.padding--; }
|
if (block.program) {
|
||||||
|
this.padding--;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
this.padding--;
|
this.padding--;
|
||||||
|
|
||||||
return out;
|
return out;
|
||||||
};
|
};
|
||||||
|
|
||||||
PrintVisitor.prototype.sexpr = function(sexpr) {
|
PrintVisitor.prototype.PartialStatement = function(partial) {
|
||||||
var params = sexpr.params, paramStrings = [], hash;
|
let content = 'PARTIAL:' + partial.name.original;
|
||||||
|
if (partial.params[0]) {
|
||||||
|
content += ' ' + this.accept(partial.params[0]);
|
||||||
|
}
|
||||||
|
if (partial.hash) {
|
||||||
|
content += ' ' + this.accept(partial.hash);
|
||||||
|
}
|
||||||
|
return this.pad('{{> ' + content + ' }}');
|
||||||
|
};
|
||||||
|
PrintVisitor.prototype.PartialBlockStatement = function(partial) {
|
||||||
|
let content = 'PARTIAL BLOCK:' + partial.name.original;
|
||||||
|
if (partial.params[0]) {
|
||||||
|
content += ' ' + this.accept(partial.params[0]);
|
||||||
|
}
|
||||||
|
if (partial.hash) {
|
||||||
|
content += ' ' + this.accept(partial.hash);
|
||||||
|
}
|
||||||
|
|
||||||
for(var i=0, l=params.length; i<l; i++) {
|
content += ' ' + this.pad('PROGRAM:');
|
||||||
|
this.padding++;
|
||||||
|
content += this.accept(partial.program);
|
||||||
|
this.padding--;
|
||||||
|
|
||||||
|
return this.pad('{{> ' + content + ' }}');
|
||||||
|
};
|
||||||
|
|
||||||
|
PrintVisitor.prototype.ContentStatement = function(content) {
|
||||||
|
return this.pad("CONTENT[ '" + content.value + "' ]");
|
||||||
|
};
|
||||||
|
|
||||||
|
PrintVisitor.prototype.CommentStatement = function(comment) {
|
||||||
|
return this.pad("{{! '" + comment.value + "' }}");
|
||||||
|
};
|
||||||
|
|
||||||
|
PrintVisitor.prototype.SubExpression = function(sexpr) {
|
||||||
|
let params = sexpr.params,
|
||||||
|
paramStrings = [],
|
||||||
|
hash;
|
||||||
|
|
||||||
|
for (let i = 0, l = params.length; i < l; i++) {
|
||||||
paramStrings.push(this.accept(params[i]));
|
paramStrings.push(this.accept(params[i]));
|
||||||
}
|
}
|
||||||
|
|
||||||
params = "[" + paramStrings.join(", ") + "]";
|
params = '[' + paramStrings.join(', ') + ']';
|
||||||
|
|
||||||
hash = sexpr.hash ? " " + this.accept(sexpr.hash) : "";
|
hash = sexpr.hash ? ' ' + this.accept(sexpr.hash) : '';
|
||||||
|
|
||||||
return this.accept(sexpr.id) + " " + params + hash;
|
return this.accept(sexpr.path) + ' ' + params + hash;
|
||||||
};
|
};
|
||||||
|
|
||||||
PrintVisitor.prototype.mustache = function(mustache) {
|
PrintVisitor.prototype.PathExpression = function(id) {
|
||||||
return this.pad("{{ " + this.accept(mustache.sexpr) + " }}");
|
let path = id.parts.join('/');
|
||||||
|
return (id.data ? '@' : '') + 'PATH:' + path;
|
||||||
};
|
};
|
||||||
|
|
||||||
PrintVisitor.prototype.partial = function(partial) {
|
PrintVisitor.prototype.StringLiteral = function(string) {
|
||||||
var content = this.accept(partial.partialName);
|
return '"' + string.value + '"';
|
||||||
if(partial.context) {
|
|
||||||
content += " " + this.accept(partial.context);
|
|
||||||
}
|
|
||||||
if (partial.hash) {
|
|
||||||
content += " " + this.accept(partial.hash);
|
|
||||||
}
|
|
||||||
return this.pad("{{> " + content + " }}");
|
|
||||||
};
|
};
|
||||||
|
|
||||||
PrintVisitor.prototype.hash = function(hash) {
|
PrintVisitor.prototype.NumberLiteral = function(number) {
|
||||||
var pairs = hash.pairs;
|
return 'NUMBER{' + number.value + '}';
|
||||||
var joinedPairs = [], left, right;
|
};
|
||||||
|
|
||||||
for(var i=0, l=pairs.length; i<l; i++) {
|
PrintVisitor.prototype.BooleanLiteral = function(bool) {
|
||||||
left = pairs[i][0];
|
return 'BOOLEAN{' + bool.value + '}';
|
||||||
right = this.accept(pairs[i][1]);
|
};
|
||||||
joinedPairs.push( left + "=" + right );
|
|
||||||
|
PrintVisitor.prototype.UndefinedLiteral = function() {
|
||||||
|
return 'UNDEFINED';
|
||||||
|
};
|
||||||
|
|
||||||
|
PrintVisitor.prototype.NullLiteral = function() {
|
||||||
|
return 'NULL';
|
||||||
|
};
|
||||||
|
|
||||||
|
PrintVisitor.prototype.Hash = function(hash) {
|
||||||
|
let pairs = hash.pairs,
|
||||||
|
joinedPairs = [];
|
||||||
|
|
||||||
|
for (let i = 0, l = pairs.length; i < l; i++) {
|
||||||
|
joinedPairs.push(this.accept(pairs[i]));
|
||||||
}
|
}
|
||||||
|
|
||||||
return "HASH{" + joinedPairs.join(", ") + "}";
|
return 'HASH{' + joinedPairs.join(', ') + '}';
|
||||||
};
|
};
|
||||||
|
PrintVisitor.prototype.HashPair = function(pair) {
|
||||||
PrintVisitor.prototype.STRING = function(string) {
|
return pair.key + '=' + this.accept(pair.value);
|
||||||
return '"' + string.string + '"';
|
|
||||||
};
|
};
|
||||||
|
/* eslint-enable new-cap */
|
||||||
PrintVisitor.prototype.NUMBER = function(number) {
|
|
||||||
return "NUMBER{" + number.number + "}";
|
|
||||||
};
|
|
||||||
|
|
||||||
PrintVisitor.prototype.BOOLEAN = function(bool) {
|
|
||||||
return "BOOLEAN{" + bool.bool + "}";
|
|
||||||
};
|
|
||||||
|
|
||||||
PrintVisitor.prototype.ID = function(id) {
|
|
||||||
var path = id.parts.join("/");
|
|
||||||
if(id.parts.length > 1) {
|
|
||||||
return "PATH:" + path;
|
|
||||||
} else {
|
|
||||||
return "ID:" + path;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
PrintVisitor.prototype.PARTIAL_NAME = function(partialName) {
|
|
||||||
return "PARTIAL:" + partialName.name;
|
|
||||||
};
|
|
||||||
|
|
||||||
PrintVisitor.prototype.DATA = function(data) {
|
|
||||||
return "@" + this.accept(data.id);
|
|
||||||
};
|
|
||||||
|
|
||||||
PrintVisitor.prototype.content = function(content) {
|
|
||||||
return this.pad("CONTENT[ '" + content.string + "' ]");
|
|
||||||
};
|
|
||||||
|
|
||||||
PrintVisitor.prototype.comment = function(comment) {
|
|
||||||
return this.pad("{{! '" + comment.comment + "' }}");
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,11 +1,136 @@
|
|||||||
function Visitor() {}
|
import Exception from '../exception';
|
||||||
|
|
||||||
|
function Visitor() {
|
||||||
|
this.parents = [];
|
||||||
|
}
|
||||||
|
|
||||||
Visitor.prototype = {
|
Visitor.prototype = {
|
||||||
constructor: Visitor,
|
constructor: Visitor,
|
||||||
|
mutating: false,
|
||||||
|
|
||||||
|
// Visits a given value. If mutating, will replace the value if necessary.
|
||||||
|
acceptKey: function(node, name) {
|
||||||
|
let value = this.accept(node[name]);
|
||||||
|
if (this.mutating) {
|
||||||
|
// Hacky sanity check: This may have a few false positives for type for the helper
|
||||||
|
// methods but will generally do the right thing without a lot of overhead.
|
||||||
|
if (value && !Visitor.prototype[value.type]) {
|
||||||
|
throw new Exception(
|
||||||
|
'Unexpected node type "' +
|
||||||
|
value.type +
|
||||||
|
'" found when accepting ' +
|
||||||
|
name +
|
||||||
|
' on ' +
|
||||||
|
node.type
|
||||||
|
);
|
||||||
|
}
|
||||||
|
node[name] = value;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// Performs an accept operation with added sanity check to ensure
|
||||||
|
// required keys are not removed.
|
||||||
|
acceptRequired: function(node, name) {
|
||||||
|
this.acceptKey(node, name);
|
||||||
|
|
||||||
|
if (!node[name]) {
|
||||||
|
throw new Exception(node.type + ' requires ' + name);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// Traverses a given array. If mutating, empty respnses will be removed
|
||||||
|
// for child elements.
|
||||||
|
acceptArray: function(array) {
|
||||||
|
for (let i = 0, l = array.length; i < l; i++) {
|
||||||
|
this.acceptKey(array, i);
|
||||||
|
|
||||||
|
if (!array[i]) {
|
||||||
|
array.splice(i, 1);
|
||||||
|
i--;
|
||||||
|
l--;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
accept: function(object) {
|
accept: function(object) {
|
||||||
return this[object.type](object);
|
if (!object) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* istanbul ignore next: Sanity code */
|
||||||
|
if (!this[object.type]) {
|
||||||
|
throw new Exception('Unknown type: ' + object.type, object);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.current) {
|
||||||
|
this.parents.unshift(this.current);
|
||||||
|
}
|
||||||
|
this.current = object;
|
||||||
|
|
||||||
|
let ret = this[object.type](object);
|
||||||
|
|
||||||
|
this.current = this.parents.shift();
|
||||||
|
|
||||||
|
if (!this.mutating || ret) {
|
||||||
|
return ret;
|
||||||
|
} else if (ret !== false) {
|
||||||
|
return object;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
Program: function(program) {
|
||||||
|
this.acceptArray(program.body);
|
||||||
|
},
|
||||||
|
|
||||||
|
MustacheStatement: visitSubExpression,
|
||||||
|
Decorator: visitSubExpression,
|
||||||
|
|
||||||
|
BlockStatement: visitBlock,
|
||||||
|
DecoratorBlock: visitBlock,
|
||||||
|
|
||||||
|
PartialStatement: visitPartial,
|
||||||
|
PartialBlockStatement: function(partial) {
|
||||||
|
visitPartial.call(this, partial);
|
||||||
|
|
||||||
|
this.acceptKey(partial, 'program');
|
||||||
|
},
|
||||||
|
|
||||||
|
ContentStatement: function(/* content */) {},
|
||||||
|
CommentStatement: function(/* comment */) {},
|
||||||
|
|
||||||
|
SubExpression: visitSubExpression,
|
||||||
|
|
||||||
|
PathExpression: function(/* path */) {},
|
||||||
|
|
||||||
|
StringLiteral: function(/* string */) {},
|
||||||
|
NumberLiteral: function(/* number */) {},
|
||||||
|
BooleanLiteral: function(/* bool */) {},
|
||||||
|
UndefinedLiteral: function(/* literal */) {},
|
||||||
|
NullLiteral: function(/* literal */) {},
|
||||||
|
|
||||||
|
Hash: function(hash) {
|
||||||
|
this.acceptArray(hash.pairs);
|
||||||
|
},
|
||||||
|
HashPair: function(pair) {
|
||||||
|
this.acceptRequired(pair, 'value');
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
function visitSubExpression(mustache) {
|
||||||
|
this.acceptRequired(mustache, 'path');
|
||||||
|
this.acceptArray(mustache.params);
|
||||||
|
this.acceptKey(mustache, 'hash');
|
||||||
|
}
|
||||||
|
function visitBlock(block) {
|
||||||
|
visitSubExpression.call(this, block);
|
||||||
|
|
||||||
|
this.acceptKey(block, 'program');
|
||||||
|
this.acceptKey(block, 'inverse');
|
||||||
|
}
|
||||||
|
function visitPartial(partial) {
|
||||||
|
this.acceptRequired(partial, 'name');
|
||||||
|
this.acceptArray(partial.params);
|
||||||
|
this.acceptKey(partial, 'hash');
|
||||||
|
}
|
||||||
|
|
||||||
export default Visitor;
|
export default Visitor;
|
||||||
|
|||||||
@@ -0,0 +1,234 @@
|
|||||||
|
import Visitor from './visitor';
|
||||||
|
|
||||||
|
function WhitespaceControl(options = {}) {
|
||||||
|
this.options = options;
|
||||||
|
}
|
||||||
|
WhitespaceControl.prototype = new Visitor();
|
||||||
|
|
||||||
|
WhitespaceControl.prototype.Program = function(program) {
|
||||||
|
const doStandalone = !this.options.ignoreStandalone;
|
||||||
|
|
||||||
|
let isRoot = !this.isRootSeen;
|
||||||
|
this.isRootSeen = true;
|
||||||
|
|
||||||
|
let body = program.body;
|
||||||
|
for (let i = 0, l = body.length; i < l; i++) {
|
||||||
|
let current = body[i],
|
||||||
|
strip = this.accept(current);
|
||||||
|
|
||||||
|
if (!strip) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let _isPrevWhitespace = isPrevWhitespace(body, i, isRoot),
|
||||||
|
_isNextWhitespace = isNextWhitespace(body, i, isRoot),
|
||||||
|
openStandalone = strip.openStandalone && _isPrevWhitespace,
|
||||||
|
closeStandalone = strip.closeStandalone && _isNextWhitespace,
|
||||||
|
inlineStandalone =
|
||||||
|
strip.inlineStandalone && _isPrevWhitespace && _isNextWhitespace;
|
||||||
|
|
||||||
|
if (strip.close) {
|
||||||
|
omitRight(body, i, true);
|
||||||
|
}
|
||||||
|
if (strip.open) {
|
||||||
|
omitLeft(body, i, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (doStandalone && inlineStandalone) {
|
||||||
|
omitRight(body, i);
|
||||||
|
|
||||||
|
if (omitLeft(body, i)) {
|
||||||
|
// If we are on a standalone node, save the indent info for partials
|
||||||
|
if (current.type === 'PartialStatement') {
|
||||||
|
// Pull out the whitespace from the final line
|
||||||
|
current.indent = /([ \t]+$)/.exec(body[i - 1].original)[1];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (doStandalone && openStandalone) {
|
||||||
|
omitRight((current.program || current.inverse).body);
|
||||||
|
|
||||||
|
// Strip out the previous content node if it's whitespace only
|
||||||
|
omitLeft(body, i);
|
||||||
|
}
|
||||||
|
if (doStandalone && closeStandalone) {
|
||||||
|
// Always strip the next node
|
||||||
|
omitRight(body, i);
|
||||||
|
|
||||||
|
omitLeft((current.inverse || current.program).body);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return program;
|
||||||
|
};
|
||||||
|
|
||||||
|
WhitespaceControl.prototype.BlockStatement = WhitespaceControl.prototype.DecoratorBlock = WhitespaceControl.prototype.PartialBlockStatement = function(
|
||||||
|
block
|
||||||
|
) {
|
||||||
|
this.accept(block.program);
|
||||||
|
this.accept(block.inverse);
|
||||||
|
|
||||||
|
// Find the inverse program that is involed with whitespace stripping.
|
||||||
|
let program = block.program || block.inverse,
|
||||||
|
inverse = block.program && block.inverse,
|
||||||
|
firstInverse = inverse,
|
||||||
|
lastInverse = inverse;
|
||||||
|
|
||||||
|
if (inverse && inverse.chained) {
|
||||||
|
firstInverse = inverse.body[0].program;
|
||||||
|
|
||||||
|
// Walk the inverse chain to find the last inverse that is actually in the chain.
|
||||||
|
while (lastInverse.chained) {
|
||||||
|
lastInverse = lastInverse.body[lastInverse.body.length - 1].program;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let strip = {
|
||||||
|
open: block.openStrip.open,
|
||||||
|
close: block.closeStrip.close,
|
||||||
|
|
||||||
|
// Determine the standalone candiacy. Basically flag our content as being possibly standalone
|
||||||
|
// so our parent can determine if we actually are standalone
|
||||||
|
openStandalone: isNextWhitespace(program.body),
|
||||||
|
closeStandalone: isPrevWhitespace((firstInverse || program).body)
|
||||||
|
};
|
||||||
|
|
||||||
|
if (block.openStrip.close) {
|
||||||
|
omitRight(program.body, null, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (inverse) {
|
||||||
|
let inverseStrip = block.inverseStrip;
|
||||||
|
|
||||||
|
if (inverseStrip.open) {
|
||||||
|
omitLeft(program.body, null, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (inverseStrip.close) {
|
||||||
|
omitRight(firstInverse.body, null, true);
|
||||||
|
}
|
||||||
|
if (block.closeStrip.open) {
|
||||||
|
omitLeft(lastInverse.body, null, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find standalone else statments
|
||||||
|
if (
|
||||||
|
!this.options.ignoreStandalone &&
|
||||||
|
isPrevWhitespace(program.body) &&
|
||||||
|
isNextWhitespace(firstInverse.body)
|
||||||
|
) {
|
||||||
|
omitLeft(program.body);
|
||||||
|
omitRight(firstInverse.body);
|
||||||
|
}
|
||||||
|
} else if (block.closeStrip.open) {
|
||||||
|
omitLeft(program.body, null, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
return strip;
|
||||||
|
};
|
||||||
|
|
||||||
|
WhitespaceControl.prototype.Decorator = WhitespaceControl.prototype.MustacheStatement = function(
|
||||||
|
mustache
|
||||||
|
) {
|
||||||
|
return mustache.strip;
|
||||||
|
};
|
||||||
|
|
||||||
|
WhitespaceControl.prototype.PartialStatement = WhitespaceControl.prototype.CommentStatement = function(
|
||||||
|
node
|
||||||
|
) {
|
||||||
|
/* istanbul ignore next */
|
||||||
|
let strip = node.strip || {};
|
||||||
|
return {
|
||||||
|
inlineStandalone: true,
|
||||||
|
open: strip.open,
|
||||||
|
close: strip.close
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
function isPrevWhitespace(body, i, isRoot) {
|
||||||
|
if (i === undefined) {
|
||||||
|
i = body.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Nodes that end with newlines are considered whitespace (but are special
|
||||||
|
// cased for strip operations)
|
||||||
|
let prev = body[i - 1],
|
||||||
|
sibling = body[i - 2];
|
||||||
|
if (!prev) {
|
||||||
|
return isRoot;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (prev.type === 'ContentStatement') {
|
||||||
|
return (sibling || !isRoot ? /\r?\n\s*?$/ : /(^|\r?\n)\s*?$/).test(
|
||||||
|
prev.original
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function isNextWhitespace(body, i, isRoot) {
|
||||||
|
if (i === undefined) {
|
||||||
|
i = -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
let next = body[i + 1],
|
||||||
|
sibling = body[i + 2];
|
||||||
|
if (!next) {
|
||||||
|
return isRoot;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (next.type === 'ContentStatement') {
|
||||||
|
return (sibling || !isRoot ? /^\s*?\r?\n/ : /^\s*?(\r?\n|$)/).test(
|
||||||
|
next.original
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Marks the node to the right of the position as omitted.
|
||||||
|
// I.e. {{foo}}' ' will mark the ' ' node as omitted.
|
||||||
|
//
|
||||||
|
// If i is undefined, then the first child will be marked as such.
|
||||||
|
//
|
||||||
|
// If mulitple is truthy then all whitespace will be stripped out until non-whitespace
|
||||||
|
// content is met.
|
||||||
|
function omitRight(body, i, multiple) {
|
||||||
|
let current = body[i == null ? 0 : i + 1];
|
||||||
|
if (
|
||||||
|
!current ||
|
||||||
|
current.type !== 'ContentStatement' ||
|
||||||
|
(!multiple && current.rightStripped)
|
||||||
|
) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let original = current.value;
|
||||||
|
current.value = current.value.replace(
|
||||||
|
multiple ? /^\s+/ : /^[ \t]*\r?\n?/,
|
||||||
|
''
|
||||||
|
);
|
||||||
|
current.rightStripped = current.value !== original;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Marks the node to the left of the position as omitted.
|
||||||
|
// I.e. ' '{{foo}} will mark the ' ' node as omitted.
|
||||||
|
//
|
||||||
|
// If i is undefined then the last child will be marked as such.
|
||||||
|
//
|
||||||
|
// If mulitple is truthy then all whitespace will be stripped out until non-whitespace
|
||||||
|
// content is met.
|
||||||
|
function omitLeft(body, i, multiple) {
|
||||||
|
let current = body[i == null ? body.length - 1 : i - 1];
|
||||||
|
if (
|
||||||
|
!current ||
|
||||||
|
current.type !== 'ContentStatement' ||
|
||||||
|
(!multiple && current.leftStripped)
|
||||||
|
) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// We omit the last node if it's whitespace only and not preceded by a non-content node.
|
||||||
|
let original = current.value;
|
||||||
|
current.value = current.value.replace(multiple ? /\s+$/ : /[ \t]+$/, '');
|
||||||
|
current.leftStripped = current.value !== original;
|
||||||
|
return current.leftStripped;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default WhitespaceControl;
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
import registerInline from './decorators/inline';
|
||||||
|
|
||||||
|
export function registerDefaultDecorators(instance) {
|
||||||
|
registerInline(instance);
|
||||||
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
import { extend } from '../utils';
|
||||||
|
|
||||||
|
export default function(instance) {
|
||||||
|
instance.registerDecorator('inline', function(fn, props, container, options) {
|
||||||
|
let ret = fn;
|
||||||
|
if (!props.partials) {
|
||||||
|
props.partials = {};
|
||||||
|
ret = function(context, options) {
|
||||||
|
// Create a new partials stack frame prior to exec.
|
||||||
|
let original = container.partials;
|
||||||
|
container.partials = extend({}, original, props.partials);
|
||||||
|
let ret = fn(context, options);
|
||||||
|
container.partials = original;
|
||||||
|
return ret;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
props.partials[options.args[0]] = options.fn;
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
});
|
||||||
|
}
|
||||||
+52
-11
@@ -1,24 +1,65 @@
|
|||||||
|
const errorProps = [
|
||||||
var errorProps = ['description', 'fileName', 'lineNumber', 'message', 'name', 'number', 'stack'];
|
'description',
|
||||||
|
'fileName',
|
||||||
|
'lineNumber',
|
||||||
|
'endLineNumber',
|
||||||
|
'message',
|
||||||
|
'name',
|
||||||
|
'number',
|
||||||
|
'stack'
|
||||||
|
];
|
||||||
|
|
||||||
function Exception(message, node) {
|
function Exception(message, node) {
|
||||||
var line;
|
let loc = node && node.loc,
|
||||||
if (node && node.firstLine) {
|
line,
|
||||||
line = node.firstLine;
|
endLineNumber,
|
||||||
|
column,
|
||||||
|
endColumn;
|
||||||
|
|
||||||
message += ' - ' + line + ':' + node.firstColumn;
|
if (loc) {
|
||||||
|
line = loc.start.line;
|
||||||
|
endLineNumber = loc.end.line;
|
||||||
|
column = loc.start.column;
|
||||||
|
endColumn = loc.end.column;
|
||||||
|
|
||||||
|
message += ' - ' + line + ':' + column;
|
||||||
}
|
}
|
||||||
|
|
||||||
var tmp = Error.prototype.constructor.call(this, message);
|
let tmp = Error.prototype.constructor.call(this, message);
|
||||||
|
|
||||||
// Unfortunately errors are not enumerable in Chrome (at least), so `for prop in tmp` doesn't work.
|
// 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++) {
|
for (let idx = 0; idx < errorProps.length; idx++) {
|
||||||
this[errorProps[idx]] = tmp[errorProps[idx]];
|
this[errorProps[idx]] = tmp[errorProps[idx]];
|
||||||
}
|
}
|
||||||
|
|
||||||
if (line) {
|
/* istanbul ignore else */
|
||||||
this.lineNumber = line;
|
if (Error.captureStackTrace) {
|
||||||
this.column = node.firstColumn;
|
Error.captureStackTrace(this, Exception);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (loc) {
|
||||||
|
this.lineNumber = line;
|
||||||
|
this.endLineNumber = endLineNumber;
|
||||||
|
|
||||||
|
// Work around issue under safari where we can't directly set the column value
|
||||||
|
/* istanbul ignore next */
|
||||||
|
if (Object.defineProperty) {
|
||||||
|
Object.defineProperty(this, 'column', {
|
||||||
|
value: column,
|
||||||
|
enumerable: true
|
||||||
|
});
|
||||||
|
Object.defineProperty(this, 'endColumn', {
|
||||||
|
value: endColumn,
|
||||||
|
enumerable: true
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
this.column = column;
|
||||||
|
this.endColumn = endColumn;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (nop) {
|
||||||
|
/* Ignore if the browser is very particular */
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,26 @@
|
|||||||
|
import registerBlockHelperMissing from './helpers/block-helper-missing';
|
||||||
|
import registerEach from './helpers/each';
|
||||||
|
import registerHelperMissing from './helpers/helper-missing';
|
||||||
|
import registerIf from './helpers/if';
|
||||||
|
import registerLog from './helpers/log';
|
||||||
|
import registerLookup from './helpers/lookup';
|
||||||
|
import registerWith from './helpers/with';
|
||||||
|
|
||||||
|
export function registerDefaultHelpers(instance) {
|
||||||
|
registerBlockHelperMissing(instance);
|
||||||
|
registerEach(instance);
|
||||||
|
registerHelperMissing(instance);
|
||||||
|
registerIf(instance);
|
||||||
|
registerLog(instance);
|
||||||
|
registerLookup(instance);
|
||||||
|
registerWith(instance);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function moveHelperToHooks(instance, helperName, keepHelper) {
|
||||||
|
if (instance.helpers[helperName]) {
|
||||||
|
instance.hooks[helperName] = instance.helpers[helperName];
|
||||||
|
if (!keepHelper) {
|
||||||
|
delete instance.helpers[helperName];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
import { appendContextPath, createFrame, isArray } from '../utils';
|
||||||
|
|
||||||
|
export default function(instance) {
|
||||||
|
instance.registerHelper('blockHelperMissing', function(context, options) {
|
||||||
|
let inverse = options.inverse,
|
||||||
|
fn = options.fn;
|
||||||
|
|
||||||
|
if (context === true) {
|
||||||
|
return fn(this);
|
||||||
|
} else if (context === false || context == null) {
|
||||||
|
return inverse(this);
|
||||||
|
} else if (isArray(context)) {
|
||||||
|
if (context.length > 0) {
|
||||||
|
if (options.ids) {
|
||||||
|
options.ids = [options.name];
|
||||||
|
}
|
||||||
|
|
||||||
|
return instance.helpers.each(context, options);
|
||||||
|
} else {
|
||||||
|
return inverse(this);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (options.data && options.ids) {
|
||||||
|
let data = createFrame(options.data);
|
||||||
|
data.contextPath = appendContextPath(
|
||||||
|
options.data.contextPath,
|
||||||
|
options.name
|
||||||
|
);
|
||||||
|
options = { data: data };
|
||||||
|
}
|
||||||
|
|
||||||
|
return fn(context, options);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -0,0 +1,101 @@
|
|||||||
|
import {
|
||||||
|
appendContextPath,
|
||||||
|
blockParams,
|
||||||
|
createFrame,
|
||||||
|
isArray,
|
||||||
|
isFunction
|
||||||
|
} from '../utils';
|
||||||
|
import Exception from '../exception';
|
||||||
|
|
||||||
|
export default function(instance) {
|
||||||
|
instance.registerHelper('each', function(context, options) {
|
||||||
|
if (!options) {
|
||||||
|
throw new Exception('Must pass iterator to #each');
|
||||||
|
}
|
||||||
|
|
||||||
|
let fn = options.fn,
|
||||||
|
inverse = options.inverse,
|
||||||
|
i = 0,
|
||||||
|
ret = '',
|
||||||
|
data,
|
||||||
|
contextPath;
|
||||||
|
|
||||||
|
if (options.data && options.ids) {
|
||||||
|
contextPath =
|
||||||
|
appendContextPath(options.data.contextPath, options.ids[0]) + '.';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isFunction(context)) {
|
||||||
|
context = context.call(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options.data) {
|
||||||
|
data = createFrame(options.data);
|
||||||
|
}
|
||||||
|
|
||||||
|
function execIteration(field, index, last) {
|
||||||
|
if (data) {
|
||||||
|
data.key = field;
|
||||||
|
data.index = index;
|
||||||
|
data.first = index === 0;
|
||||||
|
data.last = !!last;
|
||||||
|
|
||||||
|
if (contextPath) {
|
||||||
|
data.contextPath = contextPath + field;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ret =
|
||||||
|
ret +
|
||||||
|
fn(context[field], {
|
||||||
|
data: data,
|
||||||
|
blockParams: blockParams(
|
||||||
|
[context[field], field],
|
||||||
|
[contextPath + field, null]
|
||||||
|
)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (context && typeof context === 'object') {
|
||||||
|
if (isArray(context)) {
|
||||||
|
for (let j = context.length; i < j; i++) {
|
||||||
|
if (i in context) {
|
||||||
|
execIteration(i, i, i === context.length - 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (typeof Symbol === 'function' && context[Symbol.iterator]) {
|
||||||
|
const newContext = [];
|
||||||
|
const iterator = context[Symbol.iterator]();
|
||||||
|
for (let it = iterator.next(); !it.done; it = iterator.next()) {
|
||||||
|
newContext.push(it.value);
|
||||||
|
}
|
||||||
|
context = newContext;
|
||||||
|
for (let j = context.length; i < j; i++) {
|
||||||
|
execIteration(i, i, i === context.length - 1);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
let priorKey;
|
||||||
|
|
||||||
|
Object.keys(context).forEach(key => {
|
||||||
|
// We're running the iterations one step out of sync so we can detect
|
||||||
|
// the last iteration without have to scan the object twice and create
|
||||||
|
// an itermediate keys array.
|
||||||
|
if (priorKey !== undefined) {
|
||||||
|
execIteration(priorKey, i - 1);
|
||||||
|
}
|
||||||
|
priorKey = key;
|
||||||
|
i++;
|
||||||
|
});
|
||||||
|
if (priorKey !== undefined) {
|
||||||
|
execIteration(priorKey, i - 1, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (i === 0) {
|
||||||
|
ret = inverse(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
import Exception from '../exception';
|
||||||
|
|
||||||
|
export default function(instance) {
|
||||||
|
instance.registerHelper('helperMissing', function(/* [args, ]options */) {
|
||||||
|
if (arguments.length === 1) {
|
||||||
|
// A missing field in a {{foo}} construct.
|
||||||
|
return undefined;
|
||||||
|
} else {
|
||||||
|
// Someone is actually trying to call something, blow up.
|
||||||
|
throw new Exception(
|
||||||
|
'Missing helper: "' + arguments[arguments.length - 1].name + '"'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
import { isEmpty, isFunction } from '../utils';
|
||||||
|
import Exception from '../exception';
|
||||||
|
|
||||||
|
export default function(instance) {
|
||||||
|
instance.registerHelper('if', function(conditional, options) {
|
||||||
|
if (arguments.length != 2) {
|
||||||
|
throw new Exception('#if requires exactly one argument');
|
||||||
|
}
|
||||||
|
if (isFunction(conditional)) {
|
||||||
|
conditional = conditional.call(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Default behavior is to render the positive path if the value is truthy and not empty.
|
||||||
|
// The `includeZero` option may be set to treat the condtional as purely not empty based on the
|
||||||
|
// behavior of isEmpty. Effectively this determines if 0 is handled by the positive path or negative.
|
||||||
|
if ((!options.hash.includeZero && !conditional) || isEmpty(conditional)) {
|
||||||
|
return options.inverse(this);
|
||||||
|
} else {
|
||||||
|
return options.fn(this);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
instance.registerHelper('unless', function(conditional, options) {
|
||||||
|
if (arguments.length != 2) {
|
||||||
|
throw new Exception('#unless requires exactly one argument');
|
||||||
|
}
|
||||||
|
return instance.helpers['if'].call(this, conditional, {
|
||||||
|
fn: options.inverse,
|
||||||
|
inverse: options.fn,
|
||||||
|
hash: options.hash
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
export default function(instance) {
|
||||||
|
instance.registerHelper('log', function(/* message, options */) {
|
||||||
|
let args = [undefined],
|
||||||
|
options = arguments[arguments.length - 1];
|
||||||
|
for (let i = 0; i < arguments.length - 1; i++) {
|
||||||
|
args.push(arguments[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
let level = 1;
|
||||||
|
if (options.hash.level != null) {
|
||||||
|
level = options.hash.level;
|
||||||
|
} else if (options.data && options.data.level != null) {
|
||||||
|
level = options.data.level;
|
||||||
|
}
|
||||||
|
args[0] = level;
|
||||||
|
|
||||||
|
instance.log(...args);
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
export default function(instance) {
|
||||||
|
instance.registerHelper('lookup', function(obj, field, options) {
|
||||||
|
if (!obj) {
|
||||||
|
// Note for 5.0: Change to "obj == null" in 5.0
|
||||||
|
return obj;
|
||||||
|
}
|
||||||
|
return options.lookupProperty(obj, field);
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
import {
|
||||||
|
appendContextPath,
|
||||||
|
blockParams,
|
||||||
|
createFrame,
|
||||||
|
isEmpty,
|
||||||
|
isFunction
|
||||||
|
} from '../utils';
|
||||||
|
import Exception from '../exception';
|
||||||
|
|
||||||
|
export default function(instance) {
|
||||||
|
instance.registerHelper('with', function(context, options) {
|
||||||
|
if (arguments.length != 2) {
|
||||||
|
throw new Exception('#with requires exactly one argument');
|
||||||
|
}
|
||||||
|
if (isFunction(context)) {
|
||||||
|
context = context.call(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
let fn = options.fn;
|
||||||
|
|
||||||
|
if (!isEmpty(context)) {
|
||||||
|
let data = options.data;
|
||||||
|
if (options.data && options.ids) {
|
||||||
|
data = createFrame(options.data);
|
||||||
|
data.contextPath = appendContextPath(
|
||||||
|
options.data.contextPath,
|
||||||
|
options.ids[0]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return fn(context, {
|
||||||
|
data: data,
|
||||||
|
blockParams: blockParams([context], [data && data.contextPath])
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
return options.inverse(this);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
import { extend } from '../utils';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new object with "null"-prototype to avoid truthy results on prototype properties.
|
||||||
|
* The resulting object can be used with "object[property]" to check if a property exists
|
||||||
|
* @param {...object} sources a varargs parameter of source objects that will be merged
|
||||||
|
* @returns {object}
|
||||||
|
*/
|
||||||
|
export function createNewLookupObject(...sources) {
|
||||||
|
return extend(Object.create(null), ...sources);
|
||||||
|
}
|
||||||
@@ -0,0 +1,70 @@
|
|||||||
|
import { createNewLookupObject } from './create-new-lookup-object';
|
||||||
|
import logger from '../logger';
|
||||||
|
|
||||||
|
const loggedProperties = Object.create(null);
|
||||||
|
|
||||||
|
export function createProtoAccessControl(runtimeOptions) {
|
||||||
|
let defaultMethodWhiteList = Object.create(null);
|
||||||
|
defaultMethodWhiteList['constructor'] = false;
|
||||||
|
defaultMethodWhiteList['__defineGetter__'] = false;
|
||||||
|
defaultMethodWhiteList['__defineSetter__'] = false;
|
||||||
|
defaultMethodWhiteList['__lookupGetter__'] = false;
|
||||||
|
|
||||||
|
let defaultPropertyWhiteList = Object.create(null);
|
||||||
|
// eslint-disable-next-line no-proto
|
||||||
|
defaultPropertyWhiteList['__proto__'] = false;
|
||||||
|
|
||||||
|
return {
|
||||||
|
properties: {
|
||||||
|
whitelist: createNewLookupObject(
|
||||||
|
defaultPropertyWhiteList,
|
||||||
|
runtimeOptions.allowedProtoProperties
|
||||||
|
),
|
||||||
|
defaultValue: runtimeOptions.allowProtoPropertiesByDefault
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
whitelist: createNewLookupObject(
|
||||||
|
defaultMethodWhiteList,
|
||||||
|
runtimeOptions.allowedProtoMethods
|
||||||
|
),
|
||||||
|
defaultValue: runtimeOptions.allowProtoMethodsByDefault
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function resultIsAllowed(result, protoAccessControl, propertyName) {
|
||||||
|
if (typeof result === 'function') {
|
||||||
|
return checkWhiteList(protoAccessControl.methods, propertyName);
|
||||||
|
} else {
|
||||||
|
return checkWhiteList(protoAccessControl.properties, propertyName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function checkWhiteList(protoAccessControlForType, propertyName) {
|
||||||
|
if (protoAccessControlForType.whitelist[propertyName] !== undefined) {
|
||||||
|
return protoAccessControlForType.whitelist[propertyName] === true;
|
||||||
|
}
|
||||||
|
if (protoAccessControlForType.defaultValue !== undefined) {
|
||||||
|
return protoAccessControlForType.defaultValue;
|
||||||
|
}
|
||||||
|
logUnexpecedPropertyAccessOnce(propertyName);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function logUnexpecedPropertyAccessOnce(propertyName) {
|
||||||
|
if (loggedProperties[propertyName] !== true) {
|
||||||
|
loggedProperties[propertyName] = true;
|
||||||
|
logger.log(
|
||||||
|
'error',
|
||||||
|
`Handlebars: Access has been denied to resolve the property "${propertyName}" because it is not an "own property" of its parent.\n` +
|
||||||
|
`You can add a runtime option to disable the check or this warning:\n` +
|
||||||
|
`See https://handlebarsjs.com/api-reference/runtime-options.html#options-to-control-prototype-access for details`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function resetLoggedProperties() {
|
||||||
|
Object.keys(loggedProperties).forEach(propertyName => {
|
||||||
|
delete loggedProperties[propertyName];
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
export function wrapHelper(helper, transformOptionsFn) {
|
||||||
|
if (typeof helper !== 'function') {
|
||||||
|
// This should not happen, but apparently it does in https://github.com/wycats/handlebars.js/issues/1639
|
||||||
|
// We try to make the wrapper least-invasive by not wrapping it, if the helper is not a function.
|
||||||
|
return helper;
|
||||||
|
}
|
||||||
|
let wrapper = function(/* dynamic arguments */) {
|
||||||
|
const options = arguments[arguments.length - 1];
|
||||||
|
arguments[arguments.length - 1] = transformOptionsFn(options);
|
||||||
|
return helper.apply(this, arguments);
|
||||||
|
};
|
||||||
|
return wrapper;
|
||||||
|
}
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
import { indexOf } from './utils';
|
||||||
|
|
||||||
|
let logger = {
|
||||||
|
methodMap: ['debug', 'info', 'warn', 'error'],
|
||||||
|
level: 'info',
|
||||||
|
|
||||||
|
// Maps a given level value to the `methodMap` indexes above.
|
||||||
|
lookupLevel: function(level) {
|
||||||
|
if (typeof level === 'string') {
|
||||||
|
let levelMap = indexOf(logger.methodMap, level.toLowerCase());
|
||||||
|
if (levelMap >= 0) {
|
||||||
|
level = levelMap;
|
||||||
|
} else {
|
||||||
|
level = parseInt(level, 10);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return level;
|
||||||
|
},
|
||||||
|
|
||||||
|
// Can be overridden in the host environment
|
||||||
|
log: function(level, ...message) {
|
||||||
|
level = logger.lookupLevel(level);
|
||||||
|
|
||||||
|
if (
|
||||||
|
typeof console !== 'undefined' &&
|
||||||
|
logger.lookupLevel(logger.level) <= level
|
||||||
|
) {
|
||||||
|
let method = logger.methodMap[level];
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
if (!console[method]) {
|
||||||
|
method = 'log';
|
||||||
|
}
|
||||||
|
console[method](...message); // eslint-disable-line no-console
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export default logger;
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
/* global globalThis */
|
||||||
|
export default function(Handlebars) {
|
||||||
|
/* istanbul ignore next */
|
||||||
|
// https://mathiasbynens.be/notes/globalthis
|
||||||
|
(function() {
|
||||||
|
if (typeof globalThis === 'object') return;
|
||||||
|
Object.prototype.__defineGetter__('__magic__', function() {
|
||||||
|
return this;
|
||||||
|
});
|
||||||
|
__magic__.globalThis = __magic__; // eslint-disable-line no-undef
|
||||||
|
delete Object.prototype.__magic__;
|
||||||
|
})();
|
||||||
|
|
||||||
|
const $Handlebars = globalThis.Handlebars;
|
||||||
|
|
||||||
|
/* istanbul ignore next */
|
||||||
|
Handlebars.noConflict = function() {
|
||||||
|
if (globalThis.Handlebars === Handlebars) {
|
||||||
|
globalThis.Handlebars = $Handlebars;
|
||||||
|
}
|
||||||
|
return Handlebars;
|
||||||
|
};
|
||||||
|
}
|
||||||
+336
-76
@@ -1,76 +1,150 @@
|
|||||||
module Utils from "./utils";
|
import * as Utils from './utils';
|
||||||
import Exception from "./exception";
|
import Exception from './exception';
|
||||||
import { COMPILER_REVISION, REVISION_CHANGES, createFrame } from "./base";
|
import {
|
||||||
|
COMPILER_REVISION,
|
||||||
|
createFrame,
|
||||||
|
LAST_COMPATIBLE_COMPILER_REVISION,
|
||||||
|
REVISION_CHANGES
|
||||||
|
} from './base';
|
||||||
|
import { moveHelperToHooks } from './helpers';
|
||||||
|
import { wrapHelper } from './internal/wrapHelper';
|
||||||
|
import {
|
||||||
|
createProtoAccessControl,
|
||||||
|
resultIsAllowed
|
||||||
|
} from './internal/proto-access';
|
||||||
|
|
||||||
export function checkRevision(compilerInfo) {
|
export function checkRevision(compilerInfo) {
|
||||||
var compilerRevision = compilerInfo && compilerInfo[0] || 1,
|
const compilerRevision = (compilerInfo && compilerInfo[0]) || 1,
|
||||||
currentRevision = COMPILER_REVISION;
|
currentRevision = COMPILER_REVISION;
|
||||||
|
|
||||||
if (compilerRevision !== currentRevision) {
|
if (
|
||||||
if (compilerRevision < currentRevision) {
|
compilerRevision >= LAST_COMPATIBLE_COMPILER_REVISION &&
|
||||||
var runtimeVersions = REVISION_CHANGES[currentRevision],
|
compilerRevision <= COMPILER_REVISION
|
||||||
compilerVersions = REVISION_CHANGES[compilerRevision];
|
) {
|
||||||
throw new Exception("Template was precompiled with an older version of Handlebars than the current runtime. "+
|
return;
|
||||||
"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
|
if (compilerRevision < LAST_COMPATIBLE_COMPILER_REVISION) {
|
||||||
throw new Exception("Template was precompiled with a newer version of Handlebars than the current runtime. "+
|
const runtimeVersions = REVISION_CHANGES[currentRevision],
|
||||||
"Please update your runtime to a newer version ("+compilerInfo[1]+").");
|
compilerVersions = REVISION_CHANGES[compilerRevision];
|
||||||
}
|
throw new Exception(
|
||||||
|
'Template was precompiled with an older version of Handlebars than the current runtime. ' +
|
||||||
|
'Please update your precompiler to a newer version (' +
|
||||||
|
runtimeVersions +
|
||||||
|
') or downgrade your runtime to an older version (' +
|
||||||
|
compilerVersions +
|
||||||
|
').'
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
// Use the embedded version info since the runtime doesn't know about this revision yet
|
||||||
|
throw new Exception(
|
||||||
|
'Template was precompiled with a newer version of Handlebars than the current runtime. ' +
|
||||||
|
'Please update your runtime to a newer version (' +
|
||||||
|
compilerInfo[1] +
|
||||||
|
').'
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Remove this line and break up compilePartial
|
|
||||||
|
|
||||||
export function template(templateSpec, env) {
|
export function template(templateSpec, env) {
|
||||||
/* istanbul ignore next */
|
/* istanbul ignore next */
|
||||||
if (!env) {
|
if (!env) {
|
||||||
throw new Exception("No environment passed to template");
|
throw new Exception('No environment passed to template');
|
||||||
}
|
}
|
||||||
if (!templateSpec || !templateSpec.main) {
|
if (!templateSpec || !templateSpec.main) {
|
||||||
throw new Exception('Unknown template object: ' + typeof templateSpec);
|
throw new Exception('Unknown template object: ' + typeof templateSpec);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
templateSpec.main.decorator = templateSpec.main_d;
|
||||||
|
|
||||||
// Note: Using env.VM references rather than local var references throughout this section to allow
|
// Note: Using env.VM references rather than local var references throughout this section to allow
|
||||||
// for external users to override these as psuedo-supported APIs.
|
// for external users to override these as pseudo-supported APIs.
|
||||||
env.VM.checkRevision(templateSpec.compiler);
|
env.VM.checkRevision(templateSpec.compiler);
|
||||||
|
|
||||||
var invokePartialWrapper = function(partial, indent, name, context, hash, helpers, partials, data, depths) {
|
// backwards compatibility for precompiled templates with compiler-version 7 (<4.3.0)
|
||||||
if (hash) {
|
const templateWasPrecompiledWithCompilerV7 =
|
||||||
context = Utils.extend({}, context, hash);
|
templateSpec.compiler && templateSpec.compiler[0] === 7;
|
||||||
}
|
|
||||||
|
|
||||||
var result = env.VM.invokePartial.call(this, partial, name, context, helpers, partials, data, depths);
|
function invokePartialWrapper(partial, context, options) {
|
||||||
|
if (options.hash) {
|
||||||
|
context = Utils.extend({}, context, options.hash);
|
||||||
|
if (options.ids) {
|
||||||
|
options.ids[0] = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
partial = env.VM.resolvePartial.call(this, partial, context, options);
|
||||||
|
|
||||||
|
let extendedOptions = Utils.extend({}, options, {
|
||||||
|
hooks: this.hooks,
|
||||||
|
protoAccessControl: this.protoAccessControl
|
||||||
|
});
|
||||||
|
|
||||||
|
let result = env.VM.invokePartial.call(
|
||||||
|
this,
|
||||||
|
partial,
|
||||||
|
context,
|
||||||
|
extendedOptions
|
||||||
|
);
|
||||||
|
|
||||||
if (result == null && env.compile) {
|
if (result == null && env.compile) {
|
||||||
var options = { helpers: helpers, partials: partials, data: data, depths: depths };
|
options.partials[options.name] = env.compile(
|
||||||
partials[name] = env.compile(partial, { data: data !== undefined, compat: templateSpec.compat }, env);
|
partial,
|
||||||
result = partials[name](context, options);
|
templateSpec.compilerOptions,
|
||||||
|
env
|
||||||
|
);
|
||||||
|
result = options.partials[options.name](context, extendedOptions);
|
||||||
}
|
}
|
||||||
if (result != null) {
|
if (result != null) {
|
||||||
if (indent) {
|
if (options.indent) {
|
||||||
var lines = result.split('\n');
|
let lines = result.split('\n');
|
||||||
for (var i = 0, l = lines.length; i < l; i++) {
|
for (let i = 0, l = lines.length; i < l; i++) {
|
||||||
if (!lines[i] && i + 1 === l) {
|
if (!lines[i] && i + 1 === l) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
lines[i] = indent + lines[i];
|
lines[i] = options.indent + lines[i];
|
||||||
}
|
}
|
||||||
result = lines.join('\n');
|
result = lines.join('\n');
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
} else {
|
} else {
|
||||||
throw new Exception("The partial " + name + " could not be compiled when running in runtime-only mode");
|
throw new Exception(
|
||||||
|
'The partial ' +
|
||||||
|
options.name +
|
||||||
|
' could not be compiled when running in runtime-only mode'
|
||||||
|
);
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
// Just add water
|
// Just add water
|
||||||
var container = {
|
let container = {
|
||||||
|
strict: function(obj, name, loc) {
|
||||||
|
if (!obj || !(name in obj)) {
|
||||||
|
throw new Exception('"' + name + '" not defined in ' + obj, {
|
||||||
|
loc: loc
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return container.lookupProperty(obj, name);
|
||||||
|
},
|
||||||
|
lookupProperty: function(parent, propertyName) {
|
||||||
|
let result = parent[propertyName];
|
||||||
|
if (result == null) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
if (Object.prototype.hasOwnProperty.call(parent, propertyName)) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (resultIsAllowed(result, container.protoAccessControl, propertyName)) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
},
|
||||||
lookup: function(depths, name) {
|
lookup: function(depths, name) {
|
||||||
var len = depths.length;
|
const len = depths.length;
|
||||||
for (var i = 0; i < len; i++) {
|
for (let i = 0; i < len; i++) {
|
||||||
if (depths[i] && depths[i][name] != null) {
|
let result = depths[i] && container.lookupProperty(depths[i], name);
|
||||||
|
if (result != null) {
|
||||||
return depths[i][name];
|
return depths[i][name];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -83,103 +157,258 @@ export function template(templateSpec, env) {
|
|||||||
invokePartial: invokePartialWrapper,
|
invokePartial: invokePartialWrapper,
|
||||||
|
|
||||||
fn: function(i) {
|
fn: function(i) {
|
||||||
return templateSpec[i];
|
let ret = templateSpec[i];
|
||||||
|
ret.decorator = templateSpec[i + '_d'];
|
||||||
|
return ret;
|
||||||
},
|
},
|
||||||
|
|
||||||
programs: [],
|
programs: [],
|
||||||
program: function(i, data, depths) {
|
program: function(i, data, declaredBlockParams, blockParams, depths) {
|
||||||
var programWrapper = this.programs[i],
|
let programWrapper = this.programs[i],
|
||||||
fn = this.fn(i);
|
fn = this.fn(i);
|
||||||
if (data || depths) {
|
if (data || depths || blockParams || declaredBlockParams) {
|
||||||
programWrapper = program(this, i, fn, data, depths);
|
programWrapper = wrapProgram(
|
||||||
|
this,
|
||||||
|
i,
|
||||||
|
fn,
|
||||||
|
data,
|
||||||
|
declaredBlockParams,
|
||||||
|
blockParams,
|
||||||
|
depths
|
||||||
|
);
|
||||||
} else if (!programWrapper) {
|
} else if (!programWrapper) {
|
||||||
programWrapper = this.programs[i] = program(this, i, fn);
|
programWrapper = this.programs[i] = wrapProgram(this, i, fn);
|
||||||
}
|
}
|
||||||
return programWrapper;
|
return programWrapper;
|
||||||
},
|
},
|
||||||
|
|
||||||
data: function(data, depth) {
|
data: function(value, depth) {
|
||||||
while (data && depth--) {
|
while (value && depth--) {
|
||||||
data = data._parent;
|
value = value._parent;
|
||||||
}
|
}
|
||||||
return data;
|
return value;
|
||||||
},
|
},
|
||||||
merge: function(param, common) {
|
mergeIfNeeded: function(param, common) {
|
||||||
var ret = param || common;
|
let obj = param || common;
|
||||||
|
|
||||||
if (param && common && (param !== common)) {
|
if (param && common && param !== common) {
|
||||||
ret = Utils.extend({}, common, param);
|
obj = Utils.extend({}, common, param);
|
||||||
}
|
}
|
||||||
|
|
||||||
return ret;
|
return obj;
|
||||||
},
|
},
|
||||||
|
// An empty object to use as replacement for null-contexts
|
||||||
|
nullContext: Object.seal({}),
|
||||||
|
|
||||||
noop: env.VM.noop,
|
noop: env.VM.noop,
|
||||||
compilerInfo: templateSpec.compiler
|
compilerInfo: templateSpec.compiler
|
||||||
};
|
};
|
||||||
|
|
||||||
var ret = function(context, options) {
|
function ret(context, options = {}) {
|
||||||
options = options || {};
|
let data = options.data;
|
||||||
var data = options.data;
|
|
||||||
|
|
||||||
ret._setup(options);
|
ret._setup(options);
|
||||||
if (!options.partial && templateSpec.useData) {
|
if (!options.partial && templateSpec.useData) {
|
||||||
data = initData(context, data);
|
data = initData(context, data);
|
||||||
}
|
}
|
||||||
var depths;
|
let depths,
|
||||||
|
blockParams = templateSpec.useBlockParams ? [] : undefined;
|
||||||
if (templateSpec.useDepths) {
|
if (templateSpec.useDepths) {
|
||||||
depths = options.depths ? [context].concat(options.depths) : [context];
|
if (options.depths) {
|
||||||
|
depths =
|
||||||
|
context != options.depths[0]
|
||||||
|
? [context].concat(options.depths)
|
||||||
|
: options.depths;
|
||||||
|
} else {
|
||||||
|
depths = [context];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return templateSpec.main.call(container, context, container.helpers, container.partials, data, depths);
|
function main(context /*, options*/) {
|
||||||
};
|
return (
|
||||||
|
'' +
|
||||||
|
templateSpec.main(
|
||||||
|
container,
|
||||||
|
context,
|
||||||
|
container.helpers,
|
||||||
|
container.partials,
|
||||||
|
data,
|
||||||
|
blockParams,
|
||||||
|
depths
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
main = executeDecorators(
|
||||||
|
templateSpec.main,
|
||||||
|
main,
|
||||||
|
container,
|
||||||
|
options.depths || [],
|
||||||
|
data,
|
||||||
|
blockParams
|
||||||
|
);
|
||||||
|
return main(context, options);
|
||||||
|
}
|
||||||
|
|
||||||
ret.isTop = true;
|
ret.isTop = true;
|
||||||
|
|
||||||
ret._setup = function(options) {
|
ret._setup = function(options) {
|
||||||
if (!options.partial) {
|
if (!options.partial) {
|
||||||
container.helpers = container.merge(options.helpers, env.helpers);
|
let mergedHelpers = Utils.extend({}, env.helpers, options.helpers);
|
||||||
|
wrapHelpersToPassLookupProperty(mergedHelpers, container);
|
||||||
|
container.helpers = mergedHelpers;
|
||||||
|
|
||||||
if (templateSpec.usePartial) {
|
if (templateSpec.usePartial) {
|
||||||
container.partials = container.merge(options.partials, env.partials);
|
// Use mergeIfNeeded here to prevent compiling global partials multiple times
|
||||||
|
container.partials = container.mergeIfNeeded(
|
||||||
|
options.partials,
|
||||||
|
env.partials
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
if (templateSpec.usePartial || templateSpec.useDecorators) {
|
||||||
|
container.decorators = Utils.extend(
|
||||||
|
{},
|
||||||
|
env.decorators,
|
||||||
|
options.decorators
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
container.hooks = {};
|
||||||
|
container.protoAccessControl = createProtoAccessControl(options);
|
||||||
|
|
||||||
|
let keepHelperInHelpers =
|
||||||
|
options.allowCallsToHelperMissing ||
|
||||||
|
templateWasPrecompiledWithCompilerV7;
|
||||||
|
moveHelperToHooks(container, 'helperMissing', keepHelperInHelpers);
|
||||||
|
moveHelperToHooks(container, 'blockHelperMissing', keepHelperInHelpers);
|
||||||
} else {
|
} else {
|
||||||
|
container.protoAccessControl = options.protoAccessControl; // internal option
|
||||||
container.helpers = options.helpers;
|
container.helpers = options.helpers;
|
||||||
container.partials = options.partials;
|
container.partials = options.partials;
|
||||||
|
container.decorators = options.decorators;
|
||||||
|
container.hooks = options.hooks;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
ret._child = function(i, data, depths) {
|
ret._child = function(i, data, blockParams, depths) {
|
||||||
|
if (templateSpec.useBlockParams && !blockParams) {
|
||||||
|
throw new Exception('must pass block params');
|
||||||
|
}
|
||||||
if (templateSpec.useDepths && !depths) {
|
if (templateSpec.useDepths && !depths) {
|
||||||
throw new Exception('must pass parent depths');
|
throw new Exception('must pass parent depths');
|
||||||
}
|
}
|
||||||
|
|
||||||
return program(container, i, templateSpec[i], data, depths);
|
return wrapProgram(
|
||||||
|
container,
|
||||||
|
i,
|
||||||
|
templateSpec[i],
|
||||||
|
data,
|
||||||
|
0,
|
||||||
|
blockParams,
|
||||||
|
depths
|
||||||
|
);
|
||||||
};
|
};
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function program(container, i, fn, data, depths) {
|
export function wrapProgram(
|
||||||
var prog = function(context, options) {
|
container,
|
||||||
options = options || {};
|
i,
|
||||||
|
fn,
|
||||||
|
data,
|
||||||
|
declaredBlockParams,
|
||||||
|
blockParams,
|
||||||
|
depths
|
||||||
|
) {
|
||||||
|
function prog(context, options = {}) {
|
||||||
|
let currentDepths = depths;
|
||||||
|
if (
|
||||||
|
depths &&
|
||||||
|
context != depths[0] &&
|
||||||
|
!(context === container.nullContext && depths[0] === null)
|
||||||
|
) {
|
||||||
|
currentDepths = [context].concat(depths);
|
||||||
|
}
|
||||||
|
|
||||||
|
return fn(
|
||||||
|
container,
|
||||||
|
context,
|
||||||
|
container.helpers,
|
||||||
|
container.partials,
|
||||||
|
options.data || data,
|
||||||
|
blockParams && [options.blockParams].concat(blockParams),
|
||||||
|
currentDepths
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
prog = executeDecorators(fn, prog, container, depths, data, blockParams);
|
||||||
|
|
||||||
return fn.call(container, context, container.helpers, container.partials, options.data || data, depths && [context].concat(depths));
|
|
||||||
};
|
|
||||||
prog.program = i;
|
prog.program = i;
|
||||||
prog.depth = depths ? depths.length : 0;
|
prog.depth = depths ? depths.length : 0;
|
||||||
|
prog.blockParams = declaredBlockParams || 0;
|
||||||
return prog;
|
return prog;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function invokePartial(partial, name, context, helpers, partials, data, depths) {
|
/**
|
||||||
var options = { partial: true, helpers: helpers, partials: partials, data: data, depths: depths };
|
* This is currently part of the official API, therefore implementation details should not be changed.
|
||||||
|
*/
|
||||||
|
export function resolvePartial(partial, context, options) {
|
||||||
|
if (!partial) {
|
||||||
|
if (options.name === '@partial-block') {
|
||||||
|
partial = options.data['partial-block'];
|
||||||
|
} else {
|
||||||
|
partial = options.partials[options.name];
|
||||||
|
}
|
||||||
|
} else if (!partial.call && !options.name) {
|
||||||
|
// This is a dynamic partial that returned a string
|
||||||
|
options.name = partial;
|
||||||
|
partial = options.partials[partial];
|
||||||
|
}
|
||||||
|
return partial;
|
||||||
|
}
|
||||||
|
|
||||||
if(partial === undefined) {
|
export function invokePartial(partial, context, options) {
|
||||||
throw new Exception("The partial " + name + " could not be found");
|
// Use the current closure context to save the partial-block if this partial
|
||||||
} else if(partial instanceof Function) {
|
const currentPartialBlock = options.data && options.data['partial-block'];
|
||||||
|
options.partial = true;
|
||||||
|
if (options.ids) {
|
||||||
|
options.data.contextPath = options.ids[0] || options.data.contextPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
let partialBlock;
|
||||||
|
if (options.fn && options.fn !== noop) {
|
||||||
|
options.data = createFrame(options.data);
|
||||||
|
// Wrapper function to get access to currentPartialBlock from the closure
|
||||||
|
let fn = options.fn;
|
||||||
|
partialBlock = options.data['partial-block'] = function partialBlockWrapper(
|
||||||
|
context,
|
||||||
|
options = {}
|
||||||
|
) {
|
||||||
|
// Restore the partial-block from the closure for the execution of the block
|
||||||
|
// i.e. the part inside the block of the partial call.
|
||||||
|
options.data = createFrame(options.data);
|
||||||
|
options.data['partial-block'] = currentPartialBlock;
|
||||||
|
return fn(context, options);
|
||||||
|
};
|
||||||
|
if (fn.partials) {
|
||||||
|
options.partials = Utils.extend({}, options.partials, fn.partials);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (partial === undefined && partialBlock) {
|
||||||
|
partial = partialBlock;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (partial === undefined) {
|
||||||
|
throw new Exception('The partial ' + options.name + ' could not be found');
|
||||||
|
} else if (partial instanceof Function) {
|
||||||
return partial(context, options);
|
return partial(context, options);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function noop() { return ""; }
|
export function noop() {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
function initData(context, data) {
|
function initData(context, data) {
|
||||||
if (!data || !('root' in data)) {
|
if (!data || !('root' in data)) {
|
||||||
@@ -188,3 +417,34 @@ function initData(context, data) {
|
|||||||
}
|
}
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function executeDecorators(fn, prog, container, depths, data, blockParams) {
|
||||||
|
if (fn.decorator) {
|
||||||
|
let props = {};
|
||||||
|
prog = fn.decorator(
|
||||||
|
prog,
|
||||||
|
props,
|
||||||
|
container,
|
||||||
|
depths && depths[0],
|
||||||
|
data,
|
||||||
|
blockParams,
|
||||||
|
depths
|
||||||
|
);
|
||||||
|
Utils.extend(prog, props);
|
||||||
|
}
|
||||||
|
return prog;
|
||||||
|
}
|
||||||
|
|
||||||
|
function wrapHelpersToPassLookupProperty(mergedHelpers, container) {
|
||||||
|
Object.keys(mergedHelpers).forEach(helperName => {
|
||||||
|
let helper = mergedHelpers[helperName];
|
||||||
|
mergedHelpers[helperName] = passLookupPropertyOption(helper, container);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function passLookupPropertyOption(helper, container) {
|
||||||
|
const lookupProperty = container.lookupProperty;
|
||||||
|
return wrapHelper(helper, options => {
|
||||||
|
return Utils.extend({ lookupProperty }, options);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|||||||
@@ -3,8 +3,8 @@ function SafeString(string) {
|
|||||||
this.string = string;
|
this.string = string;
|
||||||
}
|
}
|
||||||
|
|
||||||
SafeString.prototype.toString = function() {
|
SafeString.prototype.toString = SafeString.prototype.toHTML = function() {
|
||||||
return "" + this.string;
|
return '' + this.string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export default SafeString;
|
export default SafeString;
|
||||||
|
|||||||
+65
-34
@@ -1,25 +1,23 @@
|
|||||||
/*jshint -W004 */
|
const escape = {
|
||||||
import SafeString from "./safe-string";
|
'&': '&',
|
||||||
|
'<': '<',
|
||||||
var escape = {
|
'>': '>',
|
||||||
"&": "&",
|
'"': '"',
|
||||||
"<": "<",
|
"'": ''',
|
||||||
">": ">",
|
'`': '`',
|
||||||
'"': """,
|
'=': '='
|
||||||
"'": "'",
|
|
||||||
"`": "`"
|
|
||||||
};
|
};
|
||||||
|
|
||||||
var badChars = /[&<>"'`]/g;
|
const badChars = /[&<>"'`=]/g,
|
||||||
var possible = /[&<>"'`]/;
|
possible = /[&<>"'`=]/;
|
||||||
|
|
||||||
function escapeChar(chr) {
|
function escapeChar(chr) {
|
||||||
return escape[chr];
|
return escape[chr];
|
||||||
}
|
}
|
||||||
|
|
||||||
export function extend(obj /* , ...source */) {
|
export function extend(obj /* , ...source */) {
|
||||||
for (var i = 1; i < arguments.length; i++) {
|
for (let i = 1; i < arguments.length; i++) {
|
||||||
for (var key in arguments[i]) {
|
for (let key in arguments[i]) {
|
||||||
if (Object.prototype.hasOwnProperty.call(arguments[i], key)) {
|
if (Object.prototype.hasOwnProperty.call(arguments[i], key)) {
|
||||||
obj[key] = arguments[i][key];
|
obj[key] = arguments[i][key];
|
||||||
}
|
}
|
||||||
@@ -29,44 +27,66 @@ export function extend(obj /* , ...source */) {
|
|||||||
return obj;
|
return obj;
|
||||||
}
|
}
|
||||||
|
|
||||||
export var toString = Object.prototype.toString;
|
export let toString = Object.prototype.toString;
|
||||||
|
|
||||||
// Sourced from lodash
|
// Sourced from lodash
|
||||||
// https://github.com/bestiejs/lodash/blob/master/LICENSE.txt
|
// https://github.com/bestiejs/lodash/blob/master/LICENSE.txt
|
||||||
var isFunction = function(value) {
|
/* eslint-disable func-style */
|
||||||
|
let isFunction = function(value) {
|
||||||
return typeof value === 'function';
|
return typeof value === 'function';
|
||||||
};
|
};
|
||||||
// fallback for older versions of Chrome and Safari
|
// fallback for older versions of Chrome and Safari
|
||||||
/* istanbul ignore next */
|
/* istanbul ignore next */
|
||||||
if (isFunction(/x/)) {
|
if (isFunction(/x/)) {
|
||||||
isFunction = function(value) {
|
isFunction = function(value) {
|
||||||
return typeof value === 'function' && toString.call(value) === '[object Function]';
|
return (
|
||||||
|
typeof value === 'function' &&
|
||||||
|
toString.call(value) === '[object Function]'
|
||||||
|
);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
export var isFunction;
|
export { isFunction };
|
||||||
|
/* eslint-enable func-style */
|
||||||
|
|
||||||
/* istanbul ignore next */
|
/* istanbul ignore next */
|
||||||
export var isArray = Array.isArray || function(value) {
|
export const isArray =
|
||||||
return (value && typeof value === 'object') ? toString.call(value) === '[object Array]' : false;
|
Array.isArray ||
|
||||||
};
|
function(value) {
|
||||||
|
return value && typeof value === 'object'
|
||||||
|
? toString.call(value) === '[object Array]'
|
||||||
|
: false;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Older IE versions do not directly support indexOf so we must implement our own, sadly.
|
||||||
|
export function indexOf(array, value) {
|
||||||
|
for (let i = 0, len = array.length; i < len; i++) {
|
||||||
|
if (array[i] === value) {
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
export function escapeExpression(string) {
|
export function escapeExpression(string) {
|
||||||
// don't escape SafeStrings, since they're already safe
|
if (typeof string !== 'string') {
|
||||||
if (string instanceof SafeString) {
|
// don't escape SafeStrings, since they're already safe
|
||||||
return string.toString();
|
if (string && string.toHTML) {
|
||||||
} else if (string == null) {
|
return string.toHTML();
|
||||||
return "";
|
} else if (string == null) {
|
||||||
} else if (!string) {
|
return '';
|
||||||
return string + '';
|
} else if (!string) {
|
||||||
|
return string + '';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Force a string conversion as this will be done by the append regardless and
|
||||||
|
// the regex test will do this transparently behind the scenes, causing issues if
|
||||||
|
// an object's to string has escaped characters in it.
|
||||||
|
string = '' + string;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Force a string conversion as this will be done by the append regardless and
|
if (!possible.test(string)) {
|
||||||
// the regex test will do this transparently behind the scenes, causing issues if
|
return string;
|
||||||
// an object's to string has escaped characters in it.
|
}
|
||||||
string = "" + string;
|
|
||||||
|
|
||||||
if(!possible.test(string)) { return string; }
|
|
||||||
return string.replace(badChars, escapeChar);
|
return string.replace(badChars, escapeChar);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -80,6 +100,17 @@ export function isEmpty(value) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function createFrame(object) {
|
||||||
|
let frame = extend({}, object);
|
||||||
|
frame._parent = object;
|
||||||
|
return frame;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function blockParams(params, ids) {
|
||||||
|
params.path = ids;
|
||||||
|
return params;
|
||||||
|
}
|
||||||
|
|
||||||
export function appendContextPath(contextPath, id) {
|
export function appendContextPath(contextPath, id) {
|
||||||
return (contextPath ? contextPath + '.' : '') + id;
|
return (contextPath ? contextPath + '.' : '') + id;
|
||||||
}
|
}
|
||||||
|
|||||||
+10
-10
@@ -1,11 +1,11 @@
|
|||||||
// USAGE:
|
// USAGE:
|
||||||
// var handlebars = require('handlebars');
|
// var handlebars = require('handlebars');
|
||||||
|
/* eslint-env node */
|
||||||
|
/* eslint-disable no-var */
|
||||||
|
|
||||||
// var local = handlebars.create();
|
// var local = handlebars.create();
|
||||||
|
|
||||||
var handlebars = require('../dist/cjs/handlebars')["default"];
|
var handlebars = require('../dist/cjs/handlebars')['default'];
|
||||||
|
|
||||||
handlebars.Visitor = require('../dist/cjs/handlebars/compiler/visitor')["default"];
|
|
||||||
|
|
||||||
var printer = require('../dist/cjs/handlebars/compiler/printer');
|
var printer = require('../dist/cjs/handlebars/compiler/printer');
|
||||||
handlebars.PrintVisitor = printer.PrintVisitor;
|
handlebars.PrintVisitor = printer.PrintVisitor;
|
||||||
@@ -14,13 +14,13 @@ handlebars.print = printer.print;
|
|||||||
module.exports = handlebars;
|
module.exports = handlebars;
|
||||||
|
|
||||||
// Publish a Node.js require() handler for .handlebars and .hbs files
|
// Publish a Node.js require() handler for .handlebars and .hbs files
|
||||||
|
function extension(module, filename) {
|
||||||
|
var fs = require('fs');
|
||||||
|
var templateString = fs.readFileSync(filename, 'utf8');
|
||||||
|
module.exports = handlebars.compile(templateString);
|
||||||
|
}
|
||||||
/* istanbul ignore else */
|
/* istanbul ignore else */
|
||||||
if (typeof require !== 'undefined' && require.extensions) {
|
if (typeof require !== 'undefined' && require.extensions) {
|
||||||
var extension = function(module, filename) {
|
require.extensions['.handlebars'] = extension;
|
||||||
var fs = require("fs");
|
require.extensions['.hbs'] = extension;
|
||||||
var templateString = fs.readFileSync(filename, "utf8");
|
|
||||||
module.exports = handlebars.compile(templateString);
|
|
||||||
};
|
|
||||||
require.extensions[".handlebars"] = extension;
|
|
||||||
require.extensions[".hbs"] = extension;
|
|
||||||
}
|
}
|
||||||
|
|||||||
+289
-92
@@ -1,8 +1,154 @@
|
|||||||
|
/* eslint-env node */
|
||||||
|
/* eslint-disable no-console */
|
||||||
|
import Async from 'neo-async';
|
||||||
|
import fs from 'fs';
|
||||||
|
import * as Handlebars from './handlebars';
|
||||||
|
import { basename } from 'path';
|
||||||
|
import { SourceMapConsumer, SourceNode } from 'source-map';
|
||||||
|
|
||||||
var fs = require('fs'),
|
module.exports.loadTemplates = function(opts, callback) {
|
||||||
Handlebars = require('./index'),
|
loadStrings(opts, function(err, strings) {
|
||||||
basename = require('path').basename,
|
if (err) {
|
||||||
uglify = require('uglify-js');
|
callback(err);
|
||||||
|
} else {
|
||||||
|
loadFiles(opts, function(err, files) {
|
||||||
|
if (err) {
|
||||||
|
callback(err);
|
||||||
|
} else {
|
||||||
|
opts.templates = strings.concat(files);
|
||||||
|
callback(undefined, opts);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
function loadStrings(opts, callback) {
|
||||||
|
let strings = arrayCast(opts.string),
|
||||||
|
names = arrayCast(opts.name);
|
||||||
|
|
||||||
|
if (names.length !== strings.length && strings.length > 1) {
|
||||||
|
return callback(
|
||||||
|
new Handlebars.Exception(
|
||||||
|
'Number of names did not match the number of string inputs'
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Async.map(
|
||||||
|
strings,
|
||||||
|
function(string, callback) {
|
||||||
|
if (string !== '-') {
|
||||||
|
callback(undefined, string);
|
||||||
|
} else {
|
||||||
|
// Load from stdin
|
||||||
|
let buffer = '';
|
||||||
|
process.stdin.setEncoding('utf8');
|
||||||
|
|
||||||
|
process.stdin.on('data', function(chunk) {
|
||||||
|
buffer += chunk;
|
||||||
|
});
|
||||||
|
process.stdin.on('end', function() {
|
||||||
|
callback(undefined, buffer);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
function(err, strings) {
|
||||||
|
strings = strings.map((string, index) => ({
|
||||||
|
name: names[index],
|
||||||
|
path: names[index],
|
||||||
|
source: string
|
||||||
|
}));
|
||||||
|
callback(err, strings);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function loadFiles(opts, callback) {
|
||||||
|
// Build file extension pattern
|
||||||
|
let extension = (opts.extension || 'handlebars').replace(
|
||||||
|
/[\\^$*+?.():=!|{}\-[\]]/g,
|
||||||
|
function(arg) {
|
||||||
|
return '\\' + arg;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
extension = new RegExp('\\.' + extension + '$');
|
||||||
|
|
||||||
|
let ret = [],
|
||||||
|
queue = (opts.files || []).map(template => ({ template, root: opts.root }));
|
||||||
|
Async.whilst(
|
||||||
|
() => queue.length,
|
||||||
|
function(callback) {
|
||||||
|
let { template: path, root } = queue.shift();
|
||||||
|
|
||||||
|
fs.stat(path, function(err, stat) {
|
||||||
|
if (err) {
|
||||||
|
return callback(
|
||||||
|
new Handlebars.Exception(`Unable to open template file "${path}"`)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (stat.isDirectory()) {
|
||||||
|
opts.hasDirectory = true;
|
||||||
|
|
||||||
|
fs.readdir(path, function(err, children) {
|
||||||
|
/* istanbul ignore next : Race condition that being too lazy to test */
|
||||||
|
if (err) {
|
||||||
|
return callback(err);
|
||||||
|
}
|
||||||
|
children.forEach(function(file) {
|
||||||
|
let childPath = path + '/' + file;
|
||||||
|
|
||||||
|
if (
|
||||||
|
extension.test(childPath) ||
|
||||||
|
fs.statSync(childPath).isDirectory()
|
||||||
|
) {
|
||||||
|
queue.push({ template: childPath, root: root || path });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
callback();
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
fs.readFile(path, 'utf8', function(err, data) {
|
||||||
|
/* istanbul ignore next : Race condition that being too lazy to test */
|
||||||
|
if (err) {
|
||||||
|
return callback(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (opts.bom && data.indexOf('\uFEFF') === 0) {
|
||||||
|
data = data.substring(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clean the template name
|
||||||
|
let name = path;
|
||||||
|
if (!root) {
|
||||||
|
name = basename(name);
|
||||||
|
} else if (name.indexOf(root) === 0) {
|
||||||
|
name = name.substring(root.length + 1);
|
||||||
|
}
|
||||||
|
name = name.replace(extension, '');
|
||||||
|
|
||||||
|
ret.push({
|
||||||
|
path: path,
|
||||||
|
name: name,
|
||||||
|
source: data
|
||||||
|
});
|
||||||
|
|
||||||
|
callback();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
function(err) {
|
||||||
|
if (err) {
|
||||||
|
callback(err);
|
||||||
|
} else {
|
||||||
|
callback(undefined, ret);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
module.exports.cli = function(opts) {
|
module.exports.cli = function(opts) {
|
||||||
if (opts.version) {
|
if (opts.version) {
|
||||||
@@ -10,135 +156,186 @@ module.exports.cli = function(opts) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var template = [0];
|
if (!opts.templates.length && !opts.hasDirectory) {
|
||||||
if (!opts.templates.length) {
|
throw new Handlebars.Exception(
|
||||||
throw new Handlebars.Exception('Must define at least one template or directory.');
|
'Must define at least one template or directory.'
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
opts.templates.forEach(function(template) {
|
|
||||||
try {
|
|
||||||
fs.statSync(template);
|
|
||||||
} catch (err) {
|
|
||||||
throw new Handlebars.Exception('Unable to open template file "' + template + '"');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if (opts.simple && opts.min) {
|
if (opts.simple && opts.min) {
|
||||||
throw new Handlebars.Exception('Unable to minimze simple output');
|
throw new Handlebars.Exception('Unable to minimize simple output');
|
||||||
}
|
}
|
||||||
if (opts.simple && (opts.templates.length !== 1 || fs.statSync(opts.templates[0]).isDirectory())) {
|
|
||||||
throw new Handlebars.Exception('Unable to output multiple templates in simple mode');
|
const multiple = opts.templates.length !== 1 || opts.hasDirectory;
|
||||||
|
if (opts.simple && multiple) {
|
||||||
|
throw new Handlebars.Exception(
|
||||||
|
'Unable to output multiple templates in simple mode'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Force simple mode if we have only one template and it's unnamed.
|
||||||
|
if (
|
||||||
|
!opts.amd &&
|
||||||
|
!opts.commonjs &&
|
||||||
|
opts.templates.length === 1 &&
|
||||||
|
!opts.templates[0].name
|
||||||
|
) {
|
||||||
|
opts.simple = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Convert the known list into a hash
|
// Convert the known list into a hash
|
||||||
var known = {};
|
let known = {};
|
||||||
if (opts.known && !Array.isArray(opts.known)) {
|
if (opts.known && !Array.isArray(opts.known)) {
|
||||||
opts.known = [opts.known];
|
opts.known = [opts.known];
|
||||||
}
|
}
|
||||||
if (opts.known) {
|
if (opts.known) {
|
||||||
for (var i = 0, len = opts.known.length; i < len; i++) {
|
for (let i = 0, len = opts.known.length; i < len; i++) {
|
||||||
known[opts.known[i]] = true;
|
known[opts.known[i]] = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Build file extension pattern
|
const objectName = opts.partial ? 'Handlebars.partials' : 'templates';
|
||||||
var extension = opts.extension.replace(/[\\^$*+?.():=!|{}\-\[\]]/g, function(arg) { return '\\' + arg; });
|
|
||||||
extension = new RegExp('\\.' + extension + '$');
|
|
||||||
|
|
||||||
var output = [];
|
let output = new SourceNode();
|
||||||
if (!opts.simple) {
|
if (!opts.simple) {
|
||||||
if (opts.amd) {
|
if (opts.amd) {
|
||||||
output.push('define([\'' + opts.handlebarPath + 'handlebars.runtime\'], function(Handlebars) {\n Handlebars = Handlebars["default"];');
|
output.add(
|
||||||
|
"define(['" +
|
||||||
|
opts.handlebarPath +
|
||||||
|
'handlebars.runtime\'], function(Handlebars) {\n Handlebars = Handlebars["default"];'
|
||||||
|
);
|
||||||
} else if (opts.commonjs) {
|
} else if (opts.commonjs) {
|
||||||
output.push('var Handlebars = require("' + opts.commonjs + '");');
|
output.add('var Handlebars = require("' + opts.commonjs + '");');
|
||||||
} else {
|
} else {
|
||||||
output.push('(function() {\n');
|
output.add('(function() {\n');
|
||||||
}
|
}
|
||||||
output.push(' var template = Handlebars.template, templates = ');
|
output.add(' var template = Handlebars.template, templates = ');
|
||||||
output.push(opts.namespace);
|
if (opts.namespace) {
|
||||||
output.push(' = ');
|
output.add(opts.namespace);
|
||||||
output.push(opts.namespace);
|
output.add(' = ');
|
||||||
output.push(' || {};\n');
|
output.add(opts.namespace);
|
||||||
}
|
output.add(' || ');
|
||||||
function processTemplate(template, root) {
|
|
||||||
var path = template,
|
|
||||||
stat = fs.statSync(path);
|
|
||||||
if (stat.isDirectory()) {
|
|
||||||
fs.readdirSync(template).map(function(file) {
|
|
||||||
var path = template + '/' + file;
|
|
||||||
|
|
||||||
if (extension.test(path) || fs.statSync(path).isDirectory()) {
|
|
||||||
processTemplate(path, root || template);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
var data = fs.readFileSync(path, 'utf8');
|
|
||||||
|
|
||||||
if (opts.bom && data.indexOf('\uFEFF') === 0) {
|
|
||||||
data = data.substring(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
var options = {
|
|
||||||
knownHelpers: known,
|
|
||||||
knownHelpersOnly: opts.o
|
|
||||||
};
|
|
||||||
|
|
||||||
if (opts.data) {
|
|
||||||
options.data = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Clean the template name
|
|
||||||
if (!root) {
|
|
||||||
template = basename(template);
|
|
||||||
} else if (template.indexOf(root) === 0) {
|
|
||||||
template = template.substring(root.length+1);
|
|
||||||
}
|
|
||||||
template = template.replace(extension, '');
|
|
||||||
|
|
||||||
if (opts.simple) {
|
|
||||||
output.push(Handlebars.precompile(data, options) + '\n');
|
|
||||||
} else if (opts.partial) {
|
|
||||||
if(opts.amd && (opts.templates.length == 1 && !fs.statSync(opts.templates[0]).isDirectory())) {
|
|
||||||
output.push('return ');
|
|
||||||
}
|
|
||||||
output.push('Handlebars.partials[\'' + template + '\'] = template(' + Handlebars.precompile(data, options) + ');\n');
|
|
||||||
} else {
|
|
||||||
if(opts.amd && (opts.templates.length == 1 && !fs.statSync(opts.templates[0]).isDirectory())) {
|
|
||||||
output.push('return ');
|
|
||||||
}
|
|
||||||
output.push('templates[\'' + template + '\'] = template(' + Handlebars.precompile(data, options) + ');\n');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
output.add('{};\n');
|
||||||
}
|
}
|
||||||
|
|
||||||
opts.templates.forEach(function(template) {
|
opts.templates.forEach(function(template) {
|
||||||
processTemplate(template, opts.root);
|
let options = {
|
||||||
|
knownHelpers: known,
|
||||||
|
knownHelpersOnly: opts.o
|
||||||
|
};
|
||||||
|
|
||||||
|
if (opts.map) {
|
||||||
|
options.srcName = template.path;
|
||||||
|
}
|
||||||
|
if (opts.data) {
|
||||||
|
options.data = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
let precompiled = Handlebars.precompile(template.source, options);
|
||||||
|
|
||||||
|
// If we are generating a source map, we have to reconstruct the SourceNode object
|
||||||
|
if (opts.map) {
|
||||||
|
let consumer = new SourceMapConsumer(precompiled.map);
|
||||||
|
precompiled = SourceNode.fromStringWithSourceMap(
|
||||||
|
precompiled.code,
|
||||||
|
consumer
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (opts.simple) {
|
||||||
|
output.add([precompiled, '\n']);
|
||||||
|
} else {
|
||||||
|
if (!template.name) {
|
||||||
|
throw new Handlebars.Exception('Name missing for template');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (opts.amd && !multiple) {
|
||||||
|
output.add('return ');
|
||||||
|
}
|
||||||
|
output.add([
|
||||||
|
objectName,
|
||||||
|
"['",
|
||||||
|
template.name,
|
||||||
|
"'] = template(",
|
||||||
|
precompiled,
|
||||||
|
');\n'
|
||||||
|
]);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Output the content
|
// Output the content
|
||||||
if (!opts.simple) {
|
if (!opts.simple) {
|
||||||
if (opts.amd) {
|
if (opts.amd) {
|
||||||
if(opts.templates.length > 1 || (opts.templates.length == 1 && fs.statSync(opts.templates[0]).isDirectory())) {
|
if (multiple) {
|
||||||
if(opts.partial){
|
output.add(['return ', objectName, ';\n']);
|
||||||
output.push('return Handlebars.partials;\n');
|
|
||||||
} else {
|
|
||||||
output.push('return templates;\n');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
output.push('});');
|
output.add('});');
|
||||||
} else if (!opts.commonjs) {
|
} else if (!opts.commonjs) {
|
||||||
output.push('})();');
|
output.add('})();');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
output = output.join('');
|
|
||||||
|
if (opts.map) {
|
||||||
|
output.add('\n//# sourceMappingURL=' + opts.map + '\n');
|
||||||
|
}
|
||||||
|
|
||||||
|
output = output.toStringWithSourceMap();
|
||||||
|
output.map = output.map + '';
|
||||||
|
|
||||||
if (opts.min) {
|
if (opts.min) {
|
||||||
output = uglify.minify(output, {fromString: true}).code;
|
output = minify(output, opts.map);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (opts.map) {
|
||||||
|
fs.writeFileSync(opts.map, output.map, 'utf8');
|
||||||
|
}
|
||||||
|
output = output.code;
|
||||||
|
|
||||||
if (opts.output) {
|
if (opts.output) {
|
||||||
fs.writeFileSync(opts.output, output, 'utf8');
|
fs.writeFileSync(opts.output, output, 'utf8');
|
||||||
} else {
|
} else {
|
||||||
console.log(output);
|
console.log(output);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
function arrayCast(value) {
|
||||||
|
value = value != null ? value : [];
|
||||||
|
if (!Array.isArray(value)) {
|
||||||
|
value = [value];
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Run uglify to minify the compiled template, if uglify exists in the dependencies.
|
||||||
|
*
|
||||||
|
* We are using `require` instead of `import` here, because es6-modules do not allow
|
||||||
|
* dynamic imports and uglify-js is an optional dependency. Since we are inside NodeJS here, this
|
||||||
|
* should not be a problem.
|
||||||
|
*
|
||||||
|
* @param {string} output the compiled template
|
||||||
|
* @param {string} sourceMapFile the file to write the source map to.
|
||||||
|
*/
|
||||||
|
function minify(output, sourceMapFile) {
|
||||||
|
try {
|
||||||
|
// Try to resolve uglify-js in order to see if it does exist
|
||||||
|
require.resolve('uglify-js');
|
||||||
|
} catch (e) {
|
||||||
|
if (e.code !== 'MODULE_NOT_FOUND') {
|
||||||
|
// Something else seems to be wrong
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
// it does not exist!
|
||||||
|
console.error(
|
||||||
|
'Code minimization is disabled due to missing uglify-js dependency'
|
||||||
|
);
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
return require('uglify-js').minify(output.code, {
|
||||||
|
sourceMap: {
|
||||||
|
content: output.map,
|
||||||
|
url: sourceMapFile
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,9 @@
|
|||||||
|
module.exports = {
|
||||||
|
'check-coverage': true,
|
||||||
|
branches: 100,
|
||||||
|
lines: 100,
|
||||||
|
functions: 100,
|
||||||
|
statements: 100,
|
||||||
|
exclude: ['**/spec/**', '**/handlebars/compiler/parser.js'],
|
||||||
|
reporter: 'html'
|
||||||
|
};
|
||||||
Generated
+27479
File diff suppressed because it is too large
Load Diff
+99
-27
@@ -1,9 +1,9 @@
|
|||||||
{
|
{
|
||||||
"name": "handlebars",
|
"name": "handlebars",
|
||||||
"barename": "handlebars",
|
"barename": "handlebars",
|
||||||
"version": "2.0.0-beta.1",
|
"version": "4.7.8",
|
||||||
"description": "Handlebars provides the power necessary to let you build semantic templates effectively with no frustration",
|
"description": "Handlebars provides the power necessary to let you build semantic templates effectively with no frustration",
|
||||||
"homepage": "http://www.handlebarsjs.com/",
|
"homepage": "https://handlebarsjs.com/",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"handlebars",
|
"handlebars",
|
||||||
"mustache",
|
"mustache",
|
||||||
@@ -12,7 +12,7 @@
|
|||||||
],
|
],
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/wycats/handlebars.js.git"
|
"url": "https://github.com/handlebars-lang/handlebars.js.git"
|
||||||
},
|
},
|
||||||
"author": "Yehuda Katz",
|
"author": "Yehuda Katz",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
@@ -21,42 +21,114 @@
|
|||||||
"node": ">=0.4.7"
|
"node": ">=0.4.7"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"optimist": "~0.3"
|
"minimist": "^1.2.5",
|
||||||
|
"neo-async": "^2.6.2",
|
||||||
|
"source-map": "^0.6.1",
|
||||||
|
"wordwrap": "^1.0.0"
|
||||||
},
|
},
|
||||||
"optionalDependencies": {
|
"optionalDependencies": {
|
||||||
"uglify-js": "~2.3"
|
"uglify-js": "^3.1.4"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"async": "~0.2.9",
|
"@playwright/test": "^1.17.1",
|
||||||
"aws-sdk": "~1.5.0",
|
"aws-sdk": "^2.1.49",
|
||||||
|
"babel-loader": "^5.0.0",
|
||||||
|
"babel-runtime": "^5.1.10",
|
||||||
"benchmark": "~1.0",
|
"benchmark": "~1.0",
|
||||||
"dustjs-linkedin": "~2.0.2",
|
"chai": "^4.2.0",
|
||||||
|
"concurrently": "^5.0.0",
|
||||||
|
"dirty-chai": "^2.0.1",
|
||||||
|
"dtslint": "^0.5.5",
|
||||||
|
"dustjs-linkedin": "^2.0.2",
|
||||||
"eco": "~1.1.0-rc-3",
|
"eco": "~1.1.0-rc-3",
|
||||||
"es6-module-packager": "1.x",
|
"eslint": "^6.7.2",
|
||||||
"grunt": "~0.4.1",
|
"eslint-config-prettier": "^6.7.0",
|
||||||
"grunt-cli": "~0.1.10",
|
"eslint-plugin-compat": "^3.13.0",
|
||||||
"grunt-contrib-clean": "~0.4.1",
|
"eslint-plugin-es5": "^1.4.1",
|
||||||
"grunt-contrib-concat": "~0.3.0",
|
"fs-extra": "^8.1.0",
|
||||||
"grunt-contrib-connect": "~0.5.0",
|
"grunt": "^1.0.4",
|
||||||
"grunt-contrib-copy": "~0.4.1",
|
"grunt-babel": "^5.0.0",
|
||||||
"grunt-contrib-jshint": "0.x",
|
"grunt-cli": "^1",
|
||||||
"grunt-contrib-requirejs": "~0.4.1",
|
"grunt-contrib-clean": "^1",
|
||||||
"grunt-contrib-uglify": "~0.2.2",
|
"grunt-contrib-concat": "^1",
|
||||||
"grunt-contrib-watch": "~0.5.3",
|
"grunt-contrib-connect": "^1",
|
||||||
"grunt-saucelabs": "8.x",
|
"grunt-contrib-copy": "^1",
|
||||||
"istanbul": "^0.3.0",
|
"grunt-contrib-requirejs": "^1",
|
||||||
|
"grunt-contrib-uglify": "^1",
|
||||||
|
"grunt-contrib-watch": "^1.1.0",
|
||||||
|
"grunt-shell": "^4.0.0",
|
||||||
|
"grunt-webpack": "^1.0.8",
|
||||||
|
"husky": "^3.1.0",
|
||||||
"jison": "~0.3.0",
|
"jison": "~0.3.0",
|
||||||
"keen.io": "0.0.3",
|
"lint-staged": "^9.5.0",
|
||||||
"mocha": "~1.20.0",
|
"mocha": "^5",
|
||||||
"mustache": "~0.7.2",
|
"mock-stdin": "^0.3.0",
|
||||||
"semver": "~2.1.0",
|
"mustache": "^2.1.3",
|
||||||
"underscore": "~1.5.1"
|
"nyc": "^14.1.1",
|
||||||
|
"prettier": "^1.19.1",
|
||||||
|
"semver": "^5.0.1",
|
||||||
|
"sinon": "^7.5.0",
|
||||||
|
"typescript": "^3.4.3",
|
||||||
|
"underscore": "^1.5.1",
|
||||||
|
"webpack": "^1.12.6",
|
||||||
|
"webpack-dev-server": "^1.12.1"
|
||||||
},
|
},
|
||||||
"main": "lib/index.js",
|
"main": "lib/index.js",
|
||||||
|
"types": "types/index.d.ts",
|
||||||
|
"browser": "./dist/cjs/handlebars.js",
|
||||||
"bin": {
|
"bin": {
|
||||||
"handlebars": "bin/handlebars"
|
"handlebars": "bin/handlebars"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"test": "grunt"
|
"build": "grunt build",
|
||||||
|
"release": "npm run build && grunt release",
|
||||||
|
"format": "prettier --write '**/*.js' && eslint --fix .",
|
||||||
|
"lint": "npm run lint:eslint && npm run lint:prettier && npm run lint:types",
|
||||||
|
"lint:eslint": "eslint --max-warnings 0 .",
|
||||||
|
"lint:prettier": "prettier --check '**/*.js'",
|
||||||
|
"lint:types": "dtslint types",
|
||||||
|
"test": "npm run test:mocha",
|
||||||
|
"test:mocha": "grunt build && grunt test",
|
||||||
|
"test:browser": "playwright test --config tests/browser/playwright.config.js tests/browser/spec.js",
|
||||||
|
"test:integration": "grunt integration-tests",
|
||||||
|
"test:serve": "grunt connect:server:keepalive",
|
||||||
|
"extensive-tests-and-publish-to-aws": "npx mocha tasks/tests/ && grunt --stack extensive-tests-and-publish-to-aws",
|
||||||
|
"--- combined tasks ---": "",
|
||||||
|
"check-before-pull-request": "concurrently --kill-others-on-fail npm:lint npm:test"
|
||||||
|
},
|
||||||
|
"jspm": {
|
||||||
|
"main": "handlebars",
|
||||||
|
"directories": {
|
||||||
|
"lib": "dist/amd"
|
||||||
|
},
|
||||||
|
"buildConfig": {
|
||||||
|
"minify": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"files": [
|
||||||
|
"bin",
|
||||||
|
"dist/*.js",
|
||||||
|
"dist/amd/**/*.js",
|
||||||
|
"dist/cjs/**/*.js",
|
||||||
|
"lib",
|
||||||
|
"release-notes.md",
|
||||||
|
"runtime.js",
|
||||||
|
"types/*.d.ts",
|
||||||
|
"runtime.d.ts"
|
||||||
|
],
|
||||||
|
"husky": {
|
||||||
|
"hooks": {
|
||||||
|
"pre-commit": "lint-staged"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"lint-staged": {
|
||||||
|
"*.{js,css,json}": [
|
||||||
|
"prettier --write",
|
||||||
|
"git add"
|
||||||
|
],
|
||||||
|
"*.js": [
|
||||||
|
"eslint --fix",
|
||||||
|
"git add"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,5 @@
|
|||||||
|
module.exports = {
|
||||||
|
tabWidth: 2,
|
||||||
|
semi: true,
|
||||||
|
singleQuote: true
|
||||||
|
};
|
||||||
+885
-79
File diff suppressed because it is too large
Load Diff
Vendored
+5
@@ -0,0 +1,5 @@
|
|||||||
|
import Handlebars = require('handlebars')
|
||||||
|
|
||||||
|
declare module "handlebars/runtime" {
|
||||||
|
|
||||||
|
}
|
||||||
+1
-1
@@ -1,3 +1,3 @@
|
|||||||
// Create a simple path alias to allow browserify to resolve
|
// Create a simple path alias to allow browserify to resolve
|
||||||
// the runtime on a supported path.
|
// the runtime on a supported path.
|
||||||
module.exports = require('./dist/cjs/handlebars.runtime');
|
module.exports = require('./dist/cjs/handlebars.runtime')['default'];
|
||||||
|
|||||||
@@ -0,0 +1,47 @@
|
|||||||
|
{
|
||||||
|
"extends": [
|
||||||
|
"../.eslintrc.js",
|
||||||
|
"plugin:es5/no-es2015",
|
||||||
|
"prettier"
|
||||||
|
],
|
||||||
|
"plugins": [
|
||||||
|
"es5"
|
||||||
|
],
|
||||||
|
"globals": {
|
||||||
|
"CompilerContext": true,
|
||||||
|
"Handlebars": true,
|
||||||
|
"handlebarsEnv": true,
|
||||||
|
"shouldCompileTo": true,
|
||||||
|
"shouldCompileToWithPartials": true,
|
||||||
|
"shouldThrow": true,
|
||||||
|
"expectTemplate": true,
|
||||||
|
"compileWithPartials": true,
|
||||||
|
"console": true,
|
||||||
|
"require": true,
|
||||||
|
"suite": true,
|
||||||
|
"equal": true,
|
||||||
|
"equals": true,
|
||||||
|
"test": true,
|
||||||
|
"testBoth": true,
|
||||||
|
"raises": true,
|
||||||
|
"deepEqual": true,
|
||||||
|
"start": true,
|
||||||
|
"stop": true,
|
||||||
|
"ok": true,
|
||||||
|
"sinon": true,
|
||||||
|
"strictEqual": true,
|
||||||
|
"define": true,
|
||||||
|
"expect": true,
|
||||||
|
"chai": true
|
||||||
|
},
|
||||||
|
"env": {
|
||||||
|
"mocha": true
|
||||||
|
},
|
||||||
|
"rules": {
|
||||||
|
// Disabling for tests, for now.
|
||||||
|
"no-path-concat": "off",
|
||||||
|
|
||||||
|
"no-var": "off",
|
||||||
|
"dot-notation": "off"
|
||||||
|
}
|
||||||
|
}
|
||||||
+23
-4
@@ -18,21 +18,29 @@
|
|||||||
document.documentElement.className = 'headless';
|
document.documentElement.className = 'headless';
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
<script src="/node_modules/sinon/pkg/sinon.js"></script>
|
||||||
|
<script src="/node_modules/chai/chai.js"></script>
|
||||||
|
<script src="/node_modules/dirty-chai/lib/dirty-chai.js"></script>
|
||||||
<script src="/node_modules/mocha/mocha.js"></script>
|
<script src="/node_modules/mocha/mocha.js"></script>
|
||||||
<script>
|
<script>
|
||||||
|
window.expect = chai.expect;
|
||||||
mocha.setup('bdd');
|
mocha.setup('bdd');
|
||||||
</script>
|
</script>
|
||||||
|
<script src="/spec/vendor/json2.js"></script>
|
||||||
<script src="/spec/env/json2.js"></script>
|
<script src="/spec/vendor/require.js"></script>
|
||||||
<script src="/spec/env/require.js"></script>
|
|
||||||
|
|
||||||
<script src="/spec/env/common.js"></script>
|
<script src="/spec/env/common.js"></script>
|
||||||
<script>
|
<script>
|
||||||
|
var requireFailure;
|
||||||
|
|
||||||
requirejs.config({
|
requirejs.config({
|
||||||
paths: {
|
paths: {
|
||||||
'handlebars.runtime': '/dist/handlebars.runtime.amd'
|
'handlebars.runtime': '/dist/handlebars.runtime.amd'
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
requirejs.onError = function (err) {
|
||||||
|
requireFailure = err;
|
||||||
|
};
|
||||||
</script>
|
</script>
|
||||||
<script>
|
<script>
|
||||||
onload = function(){
|
onload = function(){
|
||||||
@@ -51,7 +59,7 @@
|
|||||||
}
|
}
|
||||||
var runner = mocha.run();
|
var runner = mocha.run();
|
||||||
|
|
||||||
//Reporting for saucelabs
|
// Reporting to test-runner
|
||||||
var failedTests = [];
|
var failedTests = [];
|
||||||
runner.on('end', function(){
|
runner.on('end', function(){
|
||||||
window.mochaResults = runner.stats;
|
window.mochaResults = runner.stats;
|
||||||
@@ -60,6 +68,17 @@
|
|||||||
|
|
||||||
runner.on('fail', logFailure);
|
runner.on('fail', logFailure);
|
||||||
|
|
||||||
|
// Inject any require initilizer failures into the first test so they are properly
|
||||||
|
// reported.
|
||||||
|
if (requireFailure) {
|
||||||
|
runner.on('hook end', function(hook){
|
||||||
|
if (requireFailure) {
|
||||||
|
runner.uncaught(requireFailure);
|
||||||
|
requireFailure = undefined;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
function logFailure(test, err){
|
function logFailure(test, err){
|
||||||
|
|
||||||
var flattenTitles = function(test){
|
var flattenTitles = function(test){
|
||||||
|
|||||||
+27
-8
@@ -1,3 +1,4 @@
|
|||||||
|
<!doctype html>
|
||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<title>Mocha</title>
|
<title>Mocha</title>
|
||||||
@@ -18,22 +19,30 @@
|
|||||||
document.documentElement.className = 'headless';
|
document.documentElement.className = 'headless';
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
<script src="/node_modules/sinon/pkg/sinon.js"></script>
|
||||||
|
<script src="/node_modules/chai/chai.js"></script>
|
||||||
|
<script src="/node_modules/dirty-chai/lib/dirty-chai.js"></script>
|
||||||
<script src="/node_modules/mocha/mocha.js"></script>
|
<script src="/node_modules/mocha/mocha.js"></script>
|
||||||
<script>
|
<script>
|
||||||
|
window.expect = chai.expect;
|
||||||
mocha.setup('bdd');
|
mocha.setup('bdd');
|
||||||
</script>
|
</script>
|
||||||
|
<script src="/spec/vendor/json2.js"></script>
|
||||||
<script src="/spec/env/json2.js"></script>
|
<script src="/spec/vendor/require.js"></script>
|
||||||
<script src="/spec/env/require.js"></script>
|
|
||||||
|
|
||||||
<script src="/spec/env/common.js"></script>
|
<script src="/spec/env/common.js"></script>
|
||||||
<script>
|
<script>
|
||||||
|
var requireFailure;
|
||||||
|
|
||||||
requirejs.config({
|
requirejs.config({
|
||||||
paths: {
|
paths: {
|
||||||
handlebars: '/dist/handlebars.amd',
|
handlebars: '/dist/handlebars.amd',
|
||||||
tests: '/tmp/tests'
|
tests: '/tmp/tests'
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
requirejs.onError = function (err) {
|
||||||
|
requireFailure = err;
|
||||||
|
};
|
||||||
|
|
||||||
var CompilerContext = {
|
var CompilerContext = {
|
||||||
compile: function(template, options) {
|
compile: function(template, options) {
|
||||||
@@ -70,7 +79,7 @@
|
|||||||
}
|
}
|
||||||
var runner = mocha.run();
|
var runner = mocha.run();
|
||||||
|
|
||||||
//Reporting for saucelabs
|
// Reporting to test-runner
|
||||||
var failedTests = [];
|
var failedTests = [];
|
||||||
runner.on('end', function(){
|
runner.on('end', function(){
|
||||||
window.mochaResults = runner.stats;
|
window.mochaResults = runner.stats;
|
||||||
@@ -79,16 +88,26 @@
|
|||||||
|
|
||||||
runner.on('fail', logFailure);
|
runner.on('fail', logFailure);
|
||||||
|
|
||||||
function logFailure(test, err){
|
// Inject any require initilizer failures into the first test so they are properly
|
||||||
|
// reported.
|
||||||
|
if (requireFailure) {
|
||||||
|
runner.on('hook end', function(hook){
|
||||||
|
if (requireFailure) {
|
||||||
|
runner.uncaught(requireFailure);
|
||||||
|
requireFailure = undefined;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
var flattenTitles = function(test){
|
function logFailure(test, err){
|
||||||
|
function flattenTitles(test){
|
||||||
var titles = [];
|
var titles = [];
|
||||||
while (test.parent.title){
|
while (test.parent.title){
|
||||||
titles.push(test.parent.title);
|
titles.push(test.parent.title);
|
||||||
test = test.parent;
|
test = test.parent;
|
||||||
}
|
}
|
||||||
return titles.reverse();
|
return titles.reverse();
|
||||||
};
|
}
|
||||||
|
|
||||||
failedTests.push({
|
failedTests.push({
|
||||||
name: test.title,
|
name: test.title,
|
||||||
@@ -97,7 +116,7 @@
|
|||||||
stack: err.stack,
|
stack: err.stack,
|
||||||
titles: flattenTitles(test)
|
titles: flattenTitles(test)
|
||||||
});
|
});
|
||||||
};
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
a
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
{{#someHelper true}}
|
||||||
|
<div>Some known helper</div>
|
||||||
|
{{#anotherHelper true}}
|
||||||
|
<div>Another known helper</div>
|
||||||
|
{{/anotherHelper}}
|
||||||
|
{{/someHelper}}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
<div>This is a test</div>
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
<div>Test Partial</div>
|
||||||
+330
-272
@@ -1,373 +1,431 @@
|
|||||||
/*global Handlebars, handlebarsEnv, shouldThrow */
|
|
||||||
describe('ast', function() {
|
describe('ast', function() {
|
||||||
if (!Handlebars.AST) {
|
if (!Handlebars.AST) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var LOCATION_INFO = {
|
var AST = Handlebars.AST;
|
||||||
last_line: 0,
|
|
||||||
first_line: 0,
|
|
||||||
first_column: 0,
|
|
||||||
last_column: 0
|
|
||||||
};
|
|
||||||
|
|
||||||
function testLocationInfoStorage(node){
|
describe('BlockStatement', function() {
|
||||||
var properties = [ 'firstLine', 'lastLine', 'firstColumn', 'lastColumn' ],
|
|
||||||
property,
|
|
||||||
propertiesLen = properties.length,
|
|
||||||
i;
|
|
||||||
|
|
||||||
for (i = 0; i < propertiesLen; i++){
|
|
||||||
property = properties[0];
|
|
||||||
equals(node[property], 0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
describe('MustacheNode', function() {
|
|
||||||
function testEscape(open, expected) {
|
|
||||||
var mustache = new handlebarsEnv.AST.MustacheNode([{}], {}, open, false);
|
|
||||||
equals(mustache.escaped, expected);
|
|
||||||
}
|
|
||||||
|
|
||||||
it('should store args', function() {
|
|
||||||
var id = {isSimple: true},
|
|
||||||
hash = {},
|
|
||||||
mustache = new handlebarsEnv.AST.MustacheNode([id, 'param1'], hash, '', false, LOCATION_INFO);
|
|
||||||
equals(mustache.type, 'mustache');
|
|
||||||
equals(mustache.hash, hash);
|
|
||||||
equals(mustache.escaped, true);
|
|
||||||
equals(mustache.id, id);
|
|
||||||
equals(mustache.params.length, 1);
|
|
||||||
equals(mustache.params[0], 'param1');
|
|
||||||
equals(!!mustache.isHelper, true);
|
|
||||||
testLocationInfoStorage(mustache);
|
|
||||||
});
|
|
||||||
it('should accept token for escape', function() {
|
|
||||||
testEscape('{{', true);
|
|
||||||
testEscape('{{~', true);
|
|
||||||
testEscape('{{#', true);
|
|
||||||
testEscape('{{~#', true);
|
|
||||||
testEscape('{{/', true);
|
|
||||||
testEscape('{{~/', true);
|
|
||||||
testEscape('{{^', true);
|
|
||||||
testEscape('{{~^', true);
|
|
||||||
testEscape('{', true);
|
|
||||||
testEscape('{', true);
|
|
||||||
|
|
||||||
testEscape('{{&', false);
|
|
||||||
testEscape('{{~&', false);
|
|
||||||
testEscape('{{{', false);
|
|
||||||
testEscape('{{~{', false);
|
|
||||||
});
|
|
||||||
it('should accept boolean for escape', function() {
|
|
||||||
testEscape(true, true);
|
|
||||||
testEscape({}, true);
|
|
||||||
|
|
||||||
testEscape(false, false);
|
|
||||||
testEscape(undefined, false);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
describe('BlockNode', function() {
|
|
||||||
it('should throw on mustache mismatch', function() {
|
it('should throw on mustache mismatch', function() {
|
||||||
shouldThrow(function() {
|
shouldThrow(
|
||||||
handlebarsEnv.parse("\n {{#foo}}{{/bar}}");
|
function() {
|
||||||
}, Handlebars.Exception, "foo doesn't match bar - 2:2");
|
handlebarsEnv.parse('\n {{#foo}}{{/bar}}');
|
||||||
});
|
},
|
||||||
|
Handlebars.Exception,
|
||||||
it('stores location info', function(){
|
"foo doesn't match bar - 2:5"
|
||||||
var sexprNode = new handlebarsEnv.AST.SexprNode([{ original: 'foo'}], null);
|
);
|
||||||
var mustacheNode = new handlebarsEnv.AST.MustacheNode(sexprNode, null, '{{', {});
|
|
||||||
var block = new handlebarsEnv.AST.BlockNode(mustacheNode,
|
|
||||||
{statements: [], strip: {}}, {statements: [], strip: {}},
|
|
||||||
{
|
|
||||||
strip: {},
|
|
||||||
path: {original: 'foo'}
|
|
||||||
},
|
|
||||||
LOCATION_INFO);
|
|
||||||
testLocationInfoStorage(block);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
describe('IdNode', function() {
|
|
||||||
it('should throw on invalid path', function() {
|
|
||||||
shouldThrow(function() {
|
|
||||||
new handlebarsEnv.AST.IdNode([
|
|
||||||
{part: 'foo'},
|
|
||||||
{part: '..'},
|
|
||||||
{part: 'bar'}
|
|
||||||
], {first_line: 1, first_column: 1});
|
|
||||||
}, Handlebars.Exception, "Invalid path: foo.. - 1:1");
|
|
||||||
shouldThrow(function() {
|
|
||||||
new handlebarsEnv.AST.IdNode([
|
|
||||||
{part: 'foo'},
|
|
||||||
{part: '.'},
|
|
||||||
{part: 'bar'}
|
|
||||||
], {first_line: 1, first_column: 1});
|
|
||||||
}, Handlebars.Exception, "Invalid path: foo. - 1:1");
|
|
||||||
shouldThrow(function() {
|
|
||||||
new handlebarsEnv.AST.IdNode([
|
|
||||||
{part: 'foo'},
|
|
||||||
{part: 'this'},
|
|
||||||
{part: 'bar'}
|
|
||||||
], {first_line: 1, first_column: 1});
|
|
||||||
}, Handlebars.Exception, "Invalid path: foothis - 1:1");
|
|
||||||
});
|
|
||||||
|
|
||||||
it('stores location info', function(){
|
|
||||||
var idNode = new handlebarsEnv.AST.IdNode([], LOCATION_INFO);
|
|
||||||
testLocationInfoStorage(idNode);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("HashNode", function(){
|
describe('helpers', function() {
|
||||||
|
describe('#helperExpression', function() {
|
||||||
|
it('should handle mustache statements', function() {
|
||||||
|
equals(
|
||||||
|
AST.helpers.helperExpression({
|
||||||
|
type: 'MustacheStatement',
|
||||||
|
params: [],
|
||||||
|
hash: undefined
|
||||||
|
}),
|
||||||
|
false
|
||||||
|
);
|
||||||
|
equals(
|
||||||
|
AST.helpers.helperExpression({
|
||||||
|
type: 'MustacheStatement',
|
||||||
|
params: [1],
|
||||||
|
hash: undefined
|
||||||
|
}),
|
||||||
|
true
|
||||||
|
);
|
||||||
|
equals(
|
||||||
|
AST.helpers.helperExpression({
|
||||||
|
type: 'MustacheStatement',
|
||||||
|
params: [],
|
||||||
|
hash: {}
|
||||||
|
}),
|
||||||
|
true
|
||||||
|
);
|
||||||
|
});
|
||||||
|
it('should handle block statements', function() {
|
||||||
|
equals(
|
||||||
|
AST.helpers.helperExpression({
|
||||||
|
type: 'BlockStatement',
|
||||||
|
params: [],
|
||||||
|
hash: undefined
|
||||||
|
}),
|
||||||
|
false
|
||||||
|
);
|
||||||
|
equals(
|
||||||
|
AST.helpers.helperExpression({
|
||||||
|
type: 'BlockStatement',
|
||||||
|
params: [1],
|
||||||
|
hash: undefined
|
||||||
|
}),
|
||||||
|
true
|
||||||
|
);
|
||||||
|
equals(
|
||||||
|
AST.helpers.helperExpression({
|
||||||
|
type: 'BlockStatement',
|
||||||
|
params: [],
|
||||||
|
hash: {}
|
||||||
|
}),
|
||||||
|
true
|
||||||
|
);
|
||||||
|
});
|
||||||
|
it('should handle subexpressions', function() {
|
||||||
|
equals(AST.helpers.helperExpression({ type: 'SubExpression' }), true);
|
||||||
|
});
|
||||||
|
it('should work with non-helper nodes', function() {
|
||||||
|
equals(AST.helpers.helperExpression({ type: 'Program' }), false);
|
||||||
|
|
||||||
it('stores location info', function(){
|
equals(
|
||||||
var hash = new handlebarsEnv.AST.HashNode([], LOCATION_INFO);
|
AST.helpers.helperExpression({ type: 'PartialStatement' }),
|
||||||
testLocationInfoStorage(hash);
|
false
|
||||||
|
);
|
||||||
|
equals(
|
||||||
|
AST.helpers.helperExpression({ type: 'ContentStatement' }),
|
||||||
|
false
|
||||||
|
);
|
||||||
|
equals(
|
||||||
|
AST.helpers.helperExpression({ type: 'CommentStatement' }),
|
||||||
|
false
|
||||||
|
);
|
||||||
|
|
||||||
|
equals(AST.helpers.helperExpression({ type: 'PathExpression' }), false);
|
||||||
|
|
||||||
|
equals(AST.helpers.helperExpression({ type: 'StringLiteral' }), false);
|
||||||
|
equals(AST.helpers.helperExpression({ type: 'NumberLiteral' }), false);
|
||||||
|
equals(AST.helpers.helperExpression({ type: 'BooleanLiteral' }), false);
|
||||||
|
equals(
|
||||||
|
AST.helpers.helperExpression({ type: 'UndefinedLiteral' }),
|
||||||
|
false
|
||||||
|
);
|
||||||
|
equals(AST.helpers.helperExpression({ type: 'NullLiteral' }), false);
|
||||||
|
|
||||||
|
equals(AST.helpers.helperExpression({ type: 'Hash' }), false);
|
||||||
|
equals(AST.helpers.helperExpression({ type: 'HashPair' }), false);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("ContentNode", function(){
|
describe('Line Numbers', function() {
|
||||||
|
var ast, body;
|
||||||
|
|
||||||
it('stores location info', function(){
|
function testColumns(node, firstLine, lastLine, firstColumn, lastColumn) {
|
||||||
var content = new handlebarsEnv.AST.ContentNode("HI", LOCATION_INFO);
|
equals(node.loc.start.line, firstLine);
|
||||||
testLocationInfoStorage(content);
|
equals(node.loc.start.column, firstColumn);
|
||||||
});
|
equals(node.loc.end.line, lastLine);
|
||||||
});
|
equals(node.loc.end.column, lastColumn);
|
||||||
|
|
||||||
describe("CommentNode", function(){
|
|
||||||
|
|
||||||
it('stores location info', function(){
|
|
||||||
var comment = new handlebarsEnv.AST.CommentNode("HI", LOCATION_INFO);
|
|
||||||
testLocationInfoStorage(comment);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("NumberNode", function(){
|
|
||||||
|
|
||||||
it('stores location info', function(){
|
|
||||||
var integer = new handlebarsEnv.AST.NumberNode("6", LOCATION_INFO);
|
|
||||||
testLocationInfoStorage(integer);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("StringNode", function(){
|
|
||||||
|
|
||||||
it('stores location info', function(){
|
|
||||||
var string = new handlebarsEnv.AST.StringNode("6", LOCATION_INFO);
|
|
||||||
testLocationInfoStorage(string);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("BooleanNode", function(){
|
|
||||||
|
|
||||||
it('stores location info', function(){
|
|
||||||
var bool = new handlebarsEnv.AST.BooleanNode("true", LOCATION_INFO);
|
|
||||||
testLocationInfoStorage(bool);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("DataNode", function(){
|
|
||||||
|
|
||||||
it('stores location info', function(){
|
|
||||||
var data = new handlebarsEnv.AST.DataNode("YES", LOCATION_INFO);
|
|
||||||
testLocationInfoStorage(data);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("PartialNameNode", function(){
|
|
||||||
|
|
||||||
it('stores location info', function(){
|
|
||||||
var pnn = new handlebarsEnv.AST.PartialNameNode({original: "YES"}, LOCATION_INFO);
|
|
||||||
testLocationInfoStorage(pnn);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("PartialNode", function(){
|
|
||||||
|
|
||||||
it('stores location info', function(){
|
|
||||||
var pn = new handlebarsEnv.AST.PartialNode("so_partial", {}, {}, {}, LOCATION_INFO);
|
|
||||||
testLocationInfoStorage(pn);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('ProgramNode', function(){
|
|
||||||
it('storing location info', function(){
|
|
||||||
var pn = new handlebarsEnv.AST.ProgramNode([], {}, LOCATION_INFO);
|
|
||||||
testLocationInfoStorage(pn);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("Line Numbers", function(){
|
|
||||||
var ast, statements;
|
|
||||||
|
|
||||||
function testColumns(node, firstLine, lastLine, firstColumn, lastColumn){
|
|
||||||
equals(node.firstLine, firstLine);
|
|
||||||
equals(node.lastLine, lastLine);
|
|
||||||
equals(node.firstColumn, firstColumn);
|
|
||||||
equals(node.lastColumn, lastColumn);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ast = Handlebars.parse("line 1 {{line1Token}}\n line 2 {{line2token}}\n line 3 {{#blockHelperOnLine3}}\nline 4{{line4token}}\n" +
|
/* eslint-disable no-multi-spaces */
|
||||||
"line5{{else}}\n{{line6Token}}\n{{/blockHelperOnLine3}}");
|
ast = Handlebars.parse(
|
||||||
statements = ast.statements;
|
'line 1 {{line1Token}}\n' + // 1
|
||||||
|
' line 2 {{line2token}}\n' + // 2
|
||||||
|
' line 3 {{#blockHelperOnLine3}}\n' + // 3
|
||||||
|
'line 4{{line4token}}\n' + // 4
|
||||||
|
'line5{{else}}\n' + // 5
|
||||||
|
'{{line6Token}}\n' + // 6
|
||||||
|
'{{/blockHelperOnLine3}}\n' + // 7
|
||||||
|
'{{#open}}\n' + // 8
|
||||||
|
'{{else inverse}}\n' + // 9
|
||||||
|
'{{else}}\n' + // 10
|
||||||
|
'{{/open}}'
|
||||||
|
); // 11
|
||||||
|
/* eslint-enable no-multi-spaces */
|
||||||
|
body = ast.body;
|
||||||
|
|
||||||
it('gets ContentNode line numbers', function(){
|
it('gets ContentNode line numbers', function() {
|
||||||
var contentNode = statements[0];
|
var contentNode = body[0];
|
||||||
testColumns(contentNode, 1, 1, 0, 7);
|
testColumns(contentNode, 1, 1, 0, 7);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('gets MustacheNode line numbers', function(){
|
it('gets MustacheStatement line numbers', function() {
|
||||||
var mustacheNode = statements[1];
|
var mustacheNode = body[1];
|
||||||
testColumns(mustacheNode, 1, 1, 7, 21);
|
testColumns(mustacheNode, 1, 1, 7, 21);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('gets line numbers correct when newlines appear', function(){
|
it('gets line numbers correct when newlines appear', function() {
|
||||||
testColumns(statements[2], 1, 2, 21, 8);
|
testColumns(body[2], 1, 2, 21, 8);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('gets MustacheNode line numbers correct across newlines', function(){
|
it('gets MustacheStatement line numbers correct across newlines', function() {
|
||||||
var secondMustacheNode = statements[3];
|
var secondMustacheStatement = body[3];
|
||||||
testColumns(secondMustacheNode, 2, 2, 8, 22);
|
testColumns(secondMustacheStatement, 2, 2, 8, 22);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('gets the block helper information correct', function(){
|
it('gets the block helper information correct', function() {
|
||||||
var blockHelperNode = statements[5];
|
var blockHelperNode = body[5];
|
||||||
testColumns(blockHelperNode, 3, 7, 8, 23);
|
testColumns(blockHelperNode, 3, 7, 8, 23);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('correctly records the line numbers the program of a block helper', function(){
|
it('correctly records the line numbers the program of a block helper', function() {
|
||||||
var blockHelperNode = statements[5],
|
var blockHelperNode = body[5],
|
||||||
program = blockHelperNode.program;
|
program = blockHelperNode.program;
|
||||||
|
|
||||||
testColumns(program, 3, 5, 8, 5);
|
testColumns(program, 3, 5, 31, 5);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('correctly records the line numbers of an inverse of a block helper', function(){
|
it('correctly records the line numbers of an inverse of a block helper', function() {
|
||||||
var blockHelperNode = statements[5],
|
var blockHelperNode = body[5],
|
||||||
inverse = blockHelperNode.inverse;
|
inverse = blockHelperNode.inverse;
|
||||||
|
|
||||||
testColumns(inverse, 5, 7, 5, 0);
|
testColumns(inverse, 5, 7, 13, 0);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('correctly records the line number of chained inverses', function() {
|
||||||
|
var chainInverseNode = body[7];
|
||||||
|
|
||||||
|
testColumns(chainInverseNode.program, 8, 9, 9, 0);
|
||||||
|
testColumns(chainInverseNode.inverse, 9, 10, 16, 0);
|
||||||
|
testColumns(chainInverseNode.inverse.body[0].program, 9, 10, 16, 0);
|
||||||
|
testColumns(chainInverseNode.inverse.body[0].inverse, 10, 11, 8, 0);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('standalone flags', function(){
|
describe('whitespace control', function() {
|
||||||
|
describe('parse', function() {
|
||||||
|
it('mustache', function() {
|
||||||
|
var ast = Handlebars.parse(' {{~comment~}} ');
|
||||||
|
|
||||||
|
equals(ast.body[0].value, '');
|
||||||
|
equals(ast.body[2].value, '');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('block statements', function() {
|
||||||
|
var ast = Handlebars.parse(' {{# comment~}} \nfoo\n {{~/comment}}');
|
||||||
|
|
||||||
|
equals(ast.body[0].value, '');
|
||||||
|
equals(ast.body[1].program.body[0].value, 'foo');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('parseWithoutProcessing', function() {
|
||||||
|
it('mustache', function() {
|
||||||
|
var ast = Handlebars.parseWithoutProcessing(' {{~comment~}} ');
|
||||||
|
|
||||||
|
equals(ast.body[0].value, ' ');
|
||||||
|
equals(ast.body[2].value, ' ');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('block statements', function() {
|
||||||
|
var ast = Handlebars.parseWithoutProcessing(
|
||||||
|
' {{# comment~}} \nfoo\n {{~/comment}}'
|
||||||
|
);
|
||||||
|
|
||||||
|
equals(ast.body[0].value, ' ');
|
||||||
|
equals(ast.body[1].program.body[0].value, ' \nfoo\n ');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('standalone flags', function() {
|
||||||
describe('mustache', function() {
|
describe('mustache', function() {
|
||||||
it('does not mark mustaches as standalone', function() {
|
it('does not mark mustaches as standalone', function() {
|
||||||
var ast = Handlebars.parse(' {{comment}} ');
|
var ast = Handlebars.parse(' {{comment}} ');
|
||||||
equals(!!ast.statements[0].string, true);
|
equals(!!ast.body[0].value, true);
|
||||||
equals(!!ast.statements[2].string, true);
|
equals(!!ast.body[2].value, true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
describe('blocks - parseWithoutProcessing', function() {
|
||||||
|
it('block mustaches', function() {
|
||||||
|
var ast = Handlebars.parseWithoutProcessing(
|
||||||
|
' {{# comment}} \nfoo\n {{else}} \n bar \n {{/comment}} '
|
||||||
|
),
|
||||||
|
block = ast.body[1];
|
||||||
|
|
||||||
|
equals(ast.body[0].value, ' ');
|
||||||
|
|
||||||
|
equals(block.program.body[0].value, ' \nfoo\n ');
|
||||||
|
equals(block.inverse.body[0].value, ' \n bar \n ');
|
||||||
|
|
||||||
|
equals(ast.body[2].value, ' ');
|
||||||
|
});
|
||||||
|
it('initial block mustaches', function() {
|
||||||
|
var ast = Handlebars.parseWithoutProcessing(
|
||||||
|
'{{# comment}} \nfoo\n {{/comment}}'
|
||||||
|
),
|
||||||
|
block = ast.body[0];
|
||||||
|
|
||||||
|
equals(block.program.body[0].value, ' \nfoo\n ');
|
||||||
|
});
|
||||||
|
it('mustaches with children', function() {
|
||||||
|
var ast = Handlebars.parseWithoutProcessing(
|
||||||
|
'{{# comment}} \n{{foo}}\n {{/comment}}'
|
||||||
|
),
|
||||||
|
block = ast.body[0];
|
||||||
|
|
||||||
|
equals(block.program.body[0].value, ' \n');
|
||||||
|
equals(block.program.body[1].path.original, 'foo');
|
||||||
|
equals(block.program.body[2].value, '\n ');
|
||||||
|
});
|
||||||
|
it('nested block mustaches', function() {
|
||||||
|
var ast = Handlebars.parseWithoutProcessing(
|
||||||
|
'{{#foo}} \n{{# comment}} \nfoo\n {{else}} \n bar \n {{/comment}} \n{{/foo}}'
|
||||||
|
),
|
||||||
|
body = ast.body[0].program.body,
|
||||||
|
block = body[1];
|
||||||
|
|
||||||
|
equals(body[0].value, ' \n');
|
||||||
|
|
||||||
|
equals(block.program.body[0].value, ' \nfoo\n ');
|
||||||
|
equals(block.inverse.body[0].value, ' \n bar \n ');
|
||||||
|
});
|
||||||
|
it('column 0 block mustaches', function() {
|
||||||
|
var ast = Handlebars.parseWithoutProcessing(
|
||||||
|
'test\n{{# comment}} \nfoo\n {{else}} \n bar \n {{/comment}} '
|
||||||
|
),
|
||||||
|
block = ast.body[1];
|
||||||
|
|
||||||
|
equals(ast.body[0].omit, undefined);
|
||||||
|
|
||||||
|
equals(block.program.body[0].value, ' \nfoo\n ');
|
||||||
|
equals(block.inverse.body[0].value, ' \n bar \n ');
|
||||||
|
|
||||||
|
equals(ast.body[2].value, ' ');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
describe('blocks', function() {
|
describe('blocks', function() {
|
||||||
it('marks block mustaches as standalone', function() {
|
it('marks block mustaches as standalone', function() {
|
||||||
var ast = Handlebars.parse(' {{# comment}} \nfoo\n {{else}} \n bar \n {{/comment}} '),
|
var ast = Handlebars.parse(
|
||||||
block = ast.statements[1];
|
' {{# comment}} \nfoo\n {{else}} \n bar \n {{/comment}} '
|
||||||
|
),
|
||||||
|
block = ast.body[1];
|
||||||
|
|
||||||
equals(ast.statements[0].string, '');
|
equals(ast.body[0].value, '');
|
||||||
|
|
||||||
equals(block.program.statements[0].string, 'foo\n');
|
equals(block.program.body[0].value, 'foo\n');
|
||||||
equals(block.inverse.statements[0].string, ' bar \n');
|
equals(block.inverse.body[0].value, ' bar \n');
|
||||||
|
|
||||||
equals(ast.statements[2].string, '');
|
equals(ast.body[2].value, '');
|
||||||
});
|
});
|
||||||
it('marks initial block mustaches as standalone', function() {
|
it('marks initial block mustaches as standalone', function() {
|
||||||
var ast = Handlebars.parse('{{# comment}} \nfoo\n {{/comment}}'),
|
var ast = Handlebars.parse('{{# comment}} \nfoo\n {{/comment}}'),
|
||||||
block = ast.statements[0];
|
block = ast.body[0];
|
||||||
|
|
||||||
equals(block.program.statements[0].string, 'foo\n');
|
equals(block.program.body[0].value, 'foo\n');
|
||||||
});
|
});
|
||||||
it('marks mustaches with children as standalone', function() {
|
it('marks mustaches with children as standalone', function() {
|
||||||
var ast = Handlebars.parse('{{# comment}} \n{{foo}}\n {{/comment}}'),
|
var ast = Handlebars.parse('{{# comment}} \n{{foo}}\n {{/comment}}'),
|
||||||
block = ast.statements[0];
|
block = ast.body[0];
|
||||||
|
|
||||||
equals(block.program.statements[0].string, '');
|
equals(block.program.body[0].value, '');
|
||||||
equals(block.program.statements[1].id.original, 'foo');
|
equals(block.program.body[1].path.original, 'foo');
|
||||||
equals(block.program.statements[2].string, '\n');
|
equals(block.program.body[2].value, '\n');
|
||||||
});
|
});
|
||||||
it('marks nested block mustaches as standalone', function() {
|
it('marks nested block mustaches as standalone', function() {
|
||||||
var ast = Handlebars.parse('{{#foo}} \n{{# comment}} \nfoo\n {{else}} \n bar \n {{/comment}} \n{{/foo}}'),
|
var ast = Handlebars.parse(
|
||||||
statements = ast.statements[0].program.statements,
|
'{{#foo}} \n{{# comment}} \nfoo\n {{else}} \n bar \n {{/comment}} \n{{/foo}}'
|
||||||
block = statements[1];
|
),
|
||||||
|
body = ast.body[0].program.body,
|
||||||
|
block = body[1];
|
||||||
|
|
||||||
equals(statements[0].string, '');
|
equals(body[0].value, '');
|
||||||
|
|
||||||
equals(block.program.statements[0].string, 'foo\n');
|
equals(block.program.body[0].value, 'foo\n');
|
||||||
equals(block.inverse.statements[0].string, ' bar \n');
|
equals(block.inverse.body[0].value, ' bar \n');
|
||||||
|
|
||||||
equals(statements[0].string, '');
|
equals(body[0].value, '');
|
||||||
});
|
});
|
||||||
it('does not mark nested block mustaches as standalone', function() {
|
it('does not mark nested block mustaches as standalone', function() {
|
||||||
var ast = Handlebars.parse('{{#foo}} {{# comment}} \nfoo\n {{else}} \n bar \n {{/comment}} {{/foo}}'),
|
var ast = Handlebars.parse(
|
||||||
statements = ast.statements[0].program.statements,
|
'{{#foo}} {{# comment}} \nfoo\n {{else}} \n bar \n {{/comment}} {{/foo}}'
|
||||||
block = statements[1];
|
),
|
||||||
|
body = ast.body[0].program.body,
|
||||||
|
block = body[1];
|
||||||
|
|
||||||
equals(statements[0].omit, undefined);
|
equals(body[0].omit, undefined);
|
||||||
|
|
||||||
equals(block.program.statements[0].string, ' \nfoo\n');
|
equals(block.program.body[0].value, ' \nfoo\n');
|
||||||
equals(block.inverse.statements[0].string, ' bar \n ');
|
equals(block.inverse.body[0].value, ' bar \n ');
|
||||||
|
|
||||||
equals(statements[0].omit, undefined);
|
equals(body[0].omit, undefined);
|
||||||
});
|
});
|
||||||
it('does not mark nested initial block mustaches as standalone', function() {
|
it('does not mark nested initial block mustaches as standalone', function() {
|
||||||
var ast = Handlebars.parse('{{#foo}}{{# comment}} \nfoo\n {{else}} \n bar \n {{/comment}}{{/foo}}'),
|
var ast = Handlebars.parse(
|
||||||
statements = ast.statements[0].program.statements,
|
'{{#foo}}{{# comment}} \nfoo\n {{else}} \n bar \n {{/comment}}{{/foo}}'
|
||||||
block = statements[0];
|
),
|
||||||
|
body = ast.body[0].program.body,
|
||||||
|
block = body[0];
|
||||||
|
|
||||||
equals(block.program.statements[0].string, ' \nfoo\n');
|
equals(block.program.body[0].value, ' \nfoo\n');
|
||||||
equals(block.inverse.statements[0].string, ' bar \n ');
|
equals(block.inverse.body[0].value, ' bar \n ');
|
||||||
|
|
||||||
equals(statements[0].omit, undefined);
|
equals(body[0].omit, undefined);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('marks column 0 block mustaches as standalone', function() {
|
it('marks column 0 block mustaches as standalone', function() {
|
||||||
var ast = Handlebars.parse('test\n{{# comment}} \nfoo\n {{else}} \n bar \n {{/comment}} '),
|
var ast = Handlebars.parse(
|
||||||
block = ast.statements[1];
|
'test\n{{# comment}} \nfoo\n {{else}} \n bar \n {{/comment}} '
|
||||||
|
),
|
||||||
|
block = ast.body[1];
|
||||||
|
|
||||||
equals(ast.statements[0].omit, undefined);
|
equals(ast.body[0].omit, undefined);
|
||||||
|
|
||||||
equals(block.program.statements[0].string, 'foo\n');
|
equals(block.program.body[0].value, 'foo\n');
|
||||||
equals(block.inverse.statements[0].string, ' bar \n');
|
equals(block.inverse.body[0].value, ' bar \n');
|
||||||
|
|
||||||
equals(ast.statements[2].string, '');
|
equals(ast.body[2].value, '');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
describe('partials - parseWithoutProcessing', function() {
|
||||||
|
it('simple partial', function() {
|
||||||
|
var ast = Handlebars.parseWithoutProcessing('{{> partial }} ');
|
||||||
|
equals(ast.body[1].value, ' ');
|
||||||
|
});
|
||||||
|
it('indented partial', function() {
|
||||||
|
var ast = Handlebars.parseWithoutProcessing(' {{> partial }} ');
|
||||||
|
equals(ast.body[0].value, ' ');
|
||||||
|
equals(ast.body[1].indent, '');
|
||||||
|
equals(ast.body[2].value, ' ');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
describe('partials', function() {
|
describe('partials', function() {
|
||||||
it('marks partial as standalone', function() {
|
it('marks partial as standalone', function() {
|
||||||
var ast = Handlebars.parse('{{> partial }} ');
|
var ast = Handlebars.parse('{{> partial }} ');
|
||||||
equals(ast.statements[1].string, '');
|
equals(ast.body[1].value, '');
|
||||||
});
|
});
|
||||||
it('marks indented partial as standalone', function() {
|
it('marks indented partial as standalone', function() {
|
||||||
var ast = Handlebars.parse(' {{> partial }} ');
|
var ast = Handlebars.parse(' {{> partial }} ');
|
||||||
equals(ast.statements[0].string, '');
|
equals(ast.body[0].value, '');
|
||||||
equals(ast.statements[1].indent, ' ');
|
equals(ast.body[1].indent, ' ');
|
||||||
equals(ast.statements[2].string, '');
|
equals(ast.body[2].value, '');
|
||||||
});
|
});
|
||||||
it('marks those around content as not standalone', function() {
|
it('marks those around content as not standalone', function() {
|
||||||
var ast = Handlebars.parse('a{{> partial }}');
|
var ast = Handlebars.parse('a{{> partial }}');
|
||||||
equals(ast.statements[0].omit, undefined);
|
equals(ast.body[0].omit, undefined);
|
||||||
|
|
||||||
ast = Handlebars.parse('{{> partial }}a');
|
ast = Handlebars.parse('{{> partial }}a');
|
||||||
equals(ast.statements[1].omit, undefined);
|
equals(ast.body[1].omit, undefined);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
describe('comments - parseWithoutProcessing', function() {
|
||||||
|
it('simple comment', function() {
|
||||||
|
var ast = Handlebars.parseWithoutProcessing('{{! comment }} ');
|
||||||
|
equals(ast.body[1].value, ' ');
|
||||||
|
});
|
||||||
|
it('indented comment', function() {
|
||||||
|
var ast = Handlebars.parseWithoutProcessing(' {{! comment }} ');
|
||||||
|
equals(ast.body[0].value, ' ');
|
||||||
|
equals(ast.body[2].value, ' ');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
describe('comments', function() {
|
describe('comments', function() {
|
||||||
it('marks comment as standalone', function() {
|
it('marks comment as standalone', function() {
|
||||||
var ast = Handlebars.parse('{{! comment }} ');
|
var ast = Handlebars.parse('{{! comment }} ');
|
||||||
equals(ast.statements[1].string, '');
|
equals(ast.body[1].value, '');
|
||||||
});
|
});
|
||||||
it('marks indented comment as standalone', function() {
|
it('marks indented comment as standalone', function() {
|
||||||
var ast = Handlebars.parse(' {{! comment }} ');
|
var ast = Handlebars.parse(' {{! comment }} ');
|
||||||
equals(ast.statements[0].string, '');
|
equals(ast.body[0].value, '');
|
||||||
equals(ast.statements[2].string, '');
|
equals(ast.body[2].value, '');
|
||||||
});
|
});
|
||||||
it('marks those around content as not standalone', function() {
|
it('marks those around content as not standalone', function() {
|
||||||
var ast = Handlebars.parse('a{{! comment }}');
|
var ast = Handlebars.parse('a{{! comment }}');
|
||||||
equals(ast.statements[0].omit, undefined);
|
equals(ast.body[0].omit, undefined);
|
||||||
|
|
||||||
ast = Handlebars.parse('{{! comment }}a');
|
ast = Handlebars.parse('{{! comment }}a');
|
||||||
equals(ast.statements[1].omit, undefined);
|
equals(ast.body[1].omit, undefined);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
+523
-152
@@ -1,225 +1,596 @@
|
|||||||
/*global CompilerContext, Handlebars, beforeEach, shouldCompileTo */
|
|
||||||
global.handlebarsEnv = null;
|
global.handlebarsEnv = null;
|
||||||
|
|
||||||
beforeEach(function() {
|
beforeEach(function() {
|
||||||
global.handlebarsEnv = Handlebars.create();
|
global.handlebarsEnv = Handlebars.create();
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("basic context", function() {
|
describe('basic context', function() {
|
||||||
it("most basic", function() {
|
it('most basic', function() {
|
||||||
shouldCompileTo("{{foo}}", { foo: "foo" }, "foo");
|
expectTemplate('{{foo}}')
|
||||||
|
.withInput({ foo: 'foo' })
|
||||||
|
.toCompileTo('foo');
|
||||||
});
|
});
|
||||||
|
|
||||||
it("escaping", function() {
|
it('escaping', function() {
|
||||||
shouldCompileTo("\\{{foo}}", { foo: "food" }, "{{foo}}");
|
expectTemplate('\\{{foo}}')
|
||||||
shouldCompileTo("content \\{{foo}}", { foo: "food" }, "content {{foo}}");
|
.withInput({ foo: 'food' })
|
||||||
shouldCompileTo("\\\\{{foo}}", { foo: "food" }, "\\food");
|
.toCompileTo('{{foo}}');
|
||||||
shouldCompileTo("content \\\\{{foo}}", { foo: "food" }, "content \\food");
|
|
||||||
shouldCompileTo("\\\\ {{foo}}", { foo: "food" }, "\\\\ food");
|
expectTemplate('content \\{{foo}}')
|
||||||
|
.withInput({ foo: 'food' })
|
||||||
|
.toCompileTo('content {{foo}}');
|
||||||
|
|
||||||
|
expectTemplate('\\\\{{foo}}')
|
||||||
|
.withInput({ foo: 'food' })
|
||||||
|
.toCompileTo('\\food');
|
||||||
|
|
||||||
|
expectTemplate('content \\\\{{foo}}')
|
||||||
|
.withInput({ foo: 'food' })
|
||||||
|
.toCompileTo('content \\food');
|
||||||
|
|
||||||
|
expectTemplate('\\\\ {{foo}}')
|
||||||
|
.withInput({ foo: 'food' })
|
||||||
|
.toCompileTo('\\\\ food');
|
||||||
});
|
});
|
||||||
|
|
||||||
it("compiling with a basic context", function() {
|
it('compiling with a basic context', function() {
|
||||||
shouldCompileTo("Goodbye\n{{cruel}}\n{{world}}!", {cruel: "cruel", world: "world"}, "Goodbye\ncruel\nworld!",
|
expectTemplate('Goodbye\n{{cruel}}\n{{world}}!')
|
||||||
"It works if all the required keys are provided");
|
.withInput({
|
||||||
|
cruel: 'cruel',
|
||||||
|
world: 'world'
|
||||||
|
})
|
||||||
|
.withMessage('It works if all the required keys are provided')
|
||||||
|
.toCompileTo('Goodbye\ncruel\nworld!');
|
||||||
});
|
});
|
||||||
|
|
||||||
it("compiling with an undefined context", function() {
|
it('compiling with a string context', function() {
|
||||||
shouldCompileTo("Goodbye\n{{cruel}}\n{{world.bar}}!", undefined, "Goodbye\n\n!");
|
expectTemplate('{{.}}{{length}}')
|
||||||
|
.withInput('bye')
|
||||||
shouldCompileTo("{{#unless foo}}Goodbye{{../test}}{{test2}}{{/unless}}", undefined, "Goodbye");
|
.toCompileTo('bye3');
|
||||||
});
|
});
|
||||||
|
|
||||||
it("comments", function() {
|
it('compiling with an undefined context', function() {
|
||||||
shouldCompileTo("{{! Goodbye}}Goodbye\n{{cruel}}\n{{world}}!",
|
expectTemplate('Goodbye\n{{cruel}}\n{{world.bar}}!')
|
||||||
{cruel: "cruel", world: "world"}, "Goodbye\ncruel\nworld!",
|
.withInput(undefined)
|
||||||
"comments are ignored");
|
.toCompileTo('Goodbye\n\n!');
|
||||||
|
|
||||||
|
expectTemplate('{{#unless foo}}Goodbye{{../test}}{{test2}}{{/unless}}')
|
||||||
|
.withInput(undefined)
|
||||||
|
.toCompileTo('Goodbye');
|
||||||
});
|
});
|
||||||
|
|
||||||
it("boolean", function() {
|
it('comments', function() {
|
||||||
var string = "{{#goodbye}}GOODBYE {{/goodbye}}cruel {{world}}!";
|
expectTemplate('{{! Goodbye}}Goodbye\n{{cruel}}\n{{world}}!')
|
||||||
shouldCompileTo(string, {goodbye: true, world: "world"}, "GOODBYE cruel world!",
|
.withInput({
|
||||||
"booleans show the contents when true");
|
cruel: 'cruel',
|
||||||
|
world: 'world'
|
||||||
|
})
|
||||||
|
.withMessage('comments are ignored')
|
||||||
|
.toCompileTo('Goodbye\ncruel\nworld!');
|
||||||
|
|
||||||
shouldCompileTo(string, {goodbye: false, world: "world"}, "cruel world!",
|
expectTemplate(' {{~! comment ~}} blah').toCompileTo('blah');
|
||||||
"booleans do not show the contents when false");
|
|
||||||
|
expectTemplate(' {{~!-- long-comment --~}} blah').toCompileTo(
|
||||||
|
'blah'
|
||||||
|
);
|
||||||
|
|
||||||
|
expectTemplate(' {{! comment ~}} blah').toCompileTo(' blah');
|
||||||
|
|
||||||
|
expectTemplate(' {{!-- long-comment --~}} blah').toCompileTo(
|
||||||
|
' blah'
|
||||||
|
);
|
||||||
|
|
||||||
|
expectTemplate(' {{~! comment}} blah').toCompileTo(' blah');
|
||||||
|
|
||||||
|
expectTemplate(' {{~!-- long-comment --}} blah').toCompileTo(
|
||||||
|
' blah'
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("zeros", function() {
|
it('boolean', function() {
|
||||||
shouldCompileTo("num1: {{num1}}, num2: {{num2}}", {num1: 42, num2: 0},
|
var string = '{{#goodbye}}GOODBYE {{/goodbye}}cruel {{world}}!';
|
||||||
"num1: 42, num2: 0");
|
expectTemplate(string)
|
||||||
shouldCompileTo("num: {{.}}", 0, "num: 0");
|
.withInput({
|
||||||
shouldCompileTo("num: {{num1/num2}}", {num1: {num2: 0}}, "num: 0");
|
goodbye: true,
|
||||||
|
world: 'world'
|
||||||
|
})
|
||||||
|
.withMessage('booleans show the contents when true')
|
||||||
|
.toCompileTo('GOODBYE cruel world!');
|
||||||
|
|
||||||
|
expectTemplate(string)
|
||||||
|
.withInput({
|
||||||
|
goodbye: false,
|
||||||
|
world: 'world'
|
||||||
|
})
|
||||||
|
.withMessage('booleans do not show the contents when false')
|
||||||
|
.toCompileTo('cruel world!');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('zeros', function() {
|
||||||
|
expectTemplate('num1: {{num1}}, num2: {{num2}}')
|
||||||
|
.withInput({
|
||||||
|
num1: 42,
|
||||||
|
num2: 0
|
||||||
|
})
|
||||||
|
.toCompileTo('num1: 42, num2: 0');
|
||||||
|
|
||||||
|
expectTemplate('num: {{.}}')
|
||||||
|
.withInput(0)
|
||||||
|
.toCompileTo('num: 0');
|
||||||
|
|
||||||
|
expectTemplate('num: {{num1/num2}}')
|
||||||
|
.withInput({ num1: { num2: 0 } })
|
||||||
|
.toCompileTo('num: 0');
|
||||||
|
});
|
||||||
|
|
||||||
it('false', function() {
|
it('false', function() {
|
||||||
shouldCompileTo('val1: {{val1}}, val2: {{val2}}', {val1: false, val2: new Boolean(false)}, 'val1: false, val2: false');
|
/* eslint-disable no-new-wrappers */
|
||||||
shouldCompileTo('val: {{.}}', false, 'val: false');
|
expectTemplate('val1: {{val1}}, val2: {{val2}}')
|
||||||
shouldCompileTo('val: {{val1/val2}}', {val1: {val2: false}}, 'val: false');
|
.withInput({
|
||||||
|
val1: false,
|
||||||
|
val2: new Boolean(false)
|
||||||
|
})
|
||||||
|
.toCompileTo('val1: false, val2: false');
|
||||||
|
|
||||||
shouldCompileTo('val1: {{{val1}}}, val2: {{{val2}}}', {val1: false, val2: new Boolean(false)}, 'val1: false, val2: false');
|
expectTemplate('val: {{.}}')
|
||||||
shouldCompileTo('val: {{{val1/val2}}}', {val1: {val2: false}}, 'val: false');
|
.withInput(false)
|
||||||
|
.toCompileTo('val: false');
|
||||||
|
|
||||||
|
expectTemplate('val: {{val1/val2}}')
|
||||||
|
.withInput({ val1: { val2: false } })
|
||||||
|
.toCompileTo('val: false');
|
||||||
|
|
||||||
|
expectTemplate('val1: {{{val1}}}, val2: {{{val2}}}')
|
||||||
|
.withInput({
|
||||||
|
val1: false,
|
||||||
|
val2: new Boolean(false)
|
||||||
|
})
|
||||||
|
.toCompileTo('val1: false, val2: false');
|
||||||
|
|
||||||
|
expectTemplate('val: {{{val1/val2}}}')
|
||||||
|
.withInput({ val1: { val2: false } })
|
||||||
|
.toCompileTo('val: false');
|
||||||
|
/* eslint-enable */
|
||||||
});
|
});
|
||||||
|
|
||||||
it("newlines", function() {
|
it('should handle undefined and null', function() {
|
||||||
shouldCompileTo("Alan's\nTest", {}, "Alan's\nTest");
|
expectTemplate('{{awesome undefined null}}')
|
||||||
shouldCompileTo("Alan's\rTest", {}, "Alan's\rTest");
|
.withInput({
|
||||||
|
awesome: function(_undefined, _null, options) {
|
||||||
|
return (
|
||||||
|
(_undefined === undefined) +
|
||||||
|
' ' +
|
||||||
|
(_null === null) +
|
||||||
|
' ' +
|
||||||
|
typeof options
|
||||||
|
);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.toCompileTo('true true object');
|
||||||
|
|
||||||
|
expectTemplate('{{undefined}}')
|
||||||
|
.withInput({
|
||||||
|
undefined: function() {
|
||||||
|
return 'undefined!';
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.toCompileTo('undefined!');
|
||||||
|
|
||||||
|
expectTemplate('{{null}}')
|
||||||
|
.withInput({
|
||||||
|
null: function() {
|
||||||
|
return 'null!';
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.toCompileTo('null!');
|
||||||
});
|
});
|
||||||
|
|
||||||
it("escaping text", function() {
|
it('newlines', function() {
|
||||||
shouldCompileTo("Awesome's", {}, "Awesome's", "text is escaped so that it doesn't get caught on single quotes");
|
expectTemplate("Alan's\nTest").toCompileTo("Alan's\nTest");
|
||||||
shouldCompileTo("Awesome\\", {}, "Awesome\\", "text is escaped so that the closing quote can't be ignored");
|
|
||||||
shouldCompileTo("Awesome\\\\ foo", {}, "Awesome\\\\ foo", "text is escaped so that it doesn't mess up backslashes");
|
expectTemplate("Alan's\rTest").toCompileTo("Alan's\rTest");
|
||||||
shouldCompileTo("Awesome {{foo}}", {foo: '\\'}, "Awesome \\", "text is escaped so that it doesn't mess up backslashes");
|
|
||||||
shouldCompileTo(' " " ', {}, ' " " ', "double quotes never produce invalid javascript");
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("escaping expressions", function() {
|
it('escaping text', function() {
|
||||||
shouldCompileTo("{{{awesome}}}", {awesome: "&\"\\<>"}, '&\"\\<>',
|
expectTemplate("Awesome's")
|
||||||
"expressions with 3 handlebars aren't escaped");
|
.withMessage(
|
||||||
|
"text is escaped so that it doesn't get caught on single quotes"
|
||||||
|
)
|
||||||
|
.toCompileTo("Awesome's");
|
||||||
|
|
||||||
shouldCompileTo("{{&awesome}}", {awesome: "&\"\\<>"}, '&\"\\<>',
|
expectTemplate('Awesome\\')
|
||||||
"expressions with {{& handlebars aren't escaped");
|
.withMessage("text is escaped so that the closing quote can't be ignored")
|
||||||
|
.toCompileTo('Awesome\\');
|
||||||
|
|
||||||
shouldCompileTo("{{awesome}}", {awesome: "&\"'`\\<>"}, '&"'`\\<>',
|
expectTemplate('Awesome\\\\ foo')
|
||||||
"by default expressions should be escaped");
|
.withMessage("text is escaped so that it doesn't mess up backslashes")
|
||||||
|
.toCompileTo('Awesome\\\\ foo');
|
||||||
|
|
||||||
shouldCompileTo("{{awesome}}", {awesome: "Escaped, <b> looks like: <b>"}, 'Escaped, <b> looks like: &lt;b&gt;',
|
expectTemplate('Awesome {{foo}}')
|
||||||
"escaping should properly handle amperstands");
|
.withInput({ foo: '\\' })
|
||||||
|
.withMessage("text is escaped so that it doesn't mess up backslashes")
|
||||||
|
.toCompileTo('Awesome \\');
|
||||||
|
|
||||||
|
expectTemplate(" ' ' ")
|
||||||
|
.withMessage('double quotes never produce invalid javascript')
|
||||||
|
.toCompileTo(" ' ' ");
|
||||||
|
});
|
||||||
|
|
||||||
|
it('escaping expressions', function() {
|
||||||
|
expectTemplate('{{{awesome}}}')
|
||||||
|
.withInput({ awesome: "&'\\<>" })
|
||||||
|
.withMessage("expressions with 3 handlebars aren't escaped")
|
||||||
|
.toCompileTo("&'\\<>");
|
||||||
|
|
||||||
|
expectTemplate('{{&awesome}}')
|
||||||
|
.withInput({ awesome: "&'\\<>" })
|
||||||
|
.withMessage("expressions with {{& handlebars aren't escaped")
|
||||||
|
.toCompileTo("&'\\<>");
|
||||||
|
|
||||||
|
expectTemplate('{{awesome}}')
|
||||||
|
.withInput({ awesome: '&"\'`\\<>' })
|
||||||
|
.withMessage('by default expressions should be escaped')
|
||||||
|
.toCompileTo('&"'`\\<>');
|
||||||
|
|
||||||
|
expectTemplate('{{awesome}}')
|
||||||
|
.withInput({ awesome: 'Escaped, <b> looks like: <b>' })
|
||||||
|
.withMessage('escaping should properly handle amperstands')
|
||||||
|
.toCompileTo('Escaped, <b> looks like: &lt;b&gt;');
|
||||||
});
|
});
|
||||||
|
|
||||||
it("functions returning safestrings shouldn't be escaped", function() {
|
it("functions returning safestrings shouldn't be escaped", function() {
|
||||||
var hash = {awesome: function() { return new Handlebars.SafeString("&\"\\<>"); }};
|
expectTemplate('{{awesome}}')
|
||||||
shouldCompileTo("{{awesome}}", hash, '&\"\\<>',
|
.withInput({
|
||||||
"functions returning safestrings aren't escaped");
|
awesome: function() {
|
||||||
|
return new Handlebars.SafeString("&'\\<>");
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.withMessage("functions returning safestrings aren't escaped")
|
||||||
|
.toCompileTo("&'\\<>");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("functions", function() {
|
it('functions', function() {
|
||||||
shouldCompileTo("{{awesome}}", {awesome: function() { return "Awesome"; }}, "Awesome",
|
expectTemplate('{{awesome}}')
|
||||||
"functions are called and render their output");
|
.withInput({
|
||||||
shouldCompileTo("{{awesome}}", {awesome: function() { return this.more; }, more: "More awesome"}, "More awesome",
|
awesome: function() {
|
||||||
"functions are bound to the context");
|
return 'Awesome';
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.withMessage('functions are called and render their output')
|
||||||
|
.toCompileTo('Awesome');
|
||||||
|
|
||||||
|
expectTemplate('{{awesome}}')
|
||||||
|
.withInput({
|
||||||
|
awesome: function() {
|
||||||
|
return this.more;
|
||||||
|
},
|
||||||
|
more: 'More awesome'
|
||||||
|
})
|
||||||
|
.withMessage('functions are bound to the context')
|
||||||
|
.toCompileTo('More awesome');
|
||||||
});
|
});
|
||||||
|
|
||||||
it("functions with context argument", function() {
|
it('functions with context argument', function() {
|
||||||
shouldCompileTo("{{awesome frank}}",
|
expectTemplate('{{awesome frank}}')
|
||||||
{awesome: function(context) { return context; },
|
.withInput({
|
||||||
frank: "Frank"},
|
awesome: function(context) {
|
||||||
"Frank", "functions are called with context arguments");
|
return context;
|
||||||
});
|
},
|
||||||
it("pathed functions with context argument", function() {
|
frank: 'Frank'
|
||||||
shouldCompileTo("{{bar.awesome frank}}",
|
})
|
||||||
{bar: {awesome: function(context) { return context; }},
|
.withMessage('functions are called with context arguments')
|
||||||
frank: "Frank"},
|
.toCompileTo('Frank');
|
||||||
"Frank", "functions are called with context arguments");
|
|
||||||
});
|
|
||||||
it("depthed functions with context argument", function() {
|
|
||||||
shouldCompileTo("{{#with frank}}{{../awesome .}}{{/with}}",
|
|
||||||
{awesome: function(context) { return context; },
|
|
||||||
frank: "Frank"},
|
|
||||||
"Frank", "functions are called with context arguments");
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("block functions with context argument", function() {
|
it('pathed functions with context argument', function() {
|
||||||
shouldCompileTo("{{#awesome 1}}inner {{.}}{{/awesome}}",
|
expectTemplate('{{bar.awesome frank}}')
|
||||||
{awesome: function(context, options) { return options.fn(context); }},
|
.withInput({
|
||||||
"inner 1", "block functions are called with context and options");
|
bar: {
|
||||||
|
awesome: function(context) {
|
||||||
|
return context;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
frank: 'Frank'
|
||||||
|
})
|
||||||
|
.withMessage('functions are called with context arguments')
|
||||||
|
.toCompileTo('Frank');
|
||||||
});
|
});
|
||||||
|
|
||||||
it("depthed block functions with context argument", function() {
|
it('depthed functions with context argument', function() {
|
||||||
shouldCompileTo("{{#with value}}{{#../awesome 1}}inner {{.}}{{/../awesome}}{{/with}}",
|
expectTemplate('{{#with frank}}{{../awesome .}}{{/with}}')
|
||||||
{value: true, awesome: function(context, options) { return options.fn(context); }},
|
.withInput({
|
||||||
"inner 1", "block functions are called with context and options");
|
awesome: function(context) {
|
||||||
|
return context;
|
||||||
|
},
|
||||||
|
frank: 'Frank'
|
||||||
|
})
|
||||||
|
.withMessage('functions are called with context arguments')
|
||||||
|
.toCompileTo('Frank');
|
||||||
});
|
});
|
||||||
|
|
||||||
it("block functions without context argument", function() {
|
it('block functions with context argument', function() {
|
||||||
shouldCompileTo("{{#awesome}}inner{{/awesome}}",
|
expectTemplate('{{#awesome 1}}inner {{.}}{{/awesome}}')
|
||||||
{awesome: function(options) { return options.fn(this); }},
|
.withInput({
|
||||||
"inner", "block functions are called with options");
|
awesome: function(context, options) {
|
||||||
});
|
return options.fn(context);
|
||||||
it("pathed block functions without context argument", function() {
|
}
|
||||||
shouldCompileTo("{{#foo.awesome}}inner{{/foo.awesome}}",
|
})
|
||||||
{foo: {awesome: function(options) { return this; }}},
|
.withMessage('block functions are called with context and options')
|
||||||
"inner", "block functions are called with options");
|
.toCompileTo('inner 1');
|
||||||
});
|
|
||||||
it("depthed block functions without context argument", function() {
|
|
||||||
shouldCompileTo("{{#with value}}{{#../awesome}}inner{{/../awesome}}{{/with}}",
|
|
||||||
{value: true, awesome: function(options) { return this; }},
|
|
||||||
"inner", "block functions are called with options");
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('depthed block functions with context argument', function() {
|
||||||
it("paths with hyphens", function() {
|
expectTemplate(
|
||||||
shouldCompileTo("{{foo-bar}}", {"foo-bar": "baz"}, "baz", "Paths can contain hyphens (-)");
|
'{{#with value}}{{#../awesome 1}}inner {{.}}{{/../awesome}}{{/with}}'
|
||||||
shouldCompileTo("{{foo.foo-bar}}", {foo: {"foo-bar": "baz"}}, "baz", "Paths can contain hyphens (-)");
|
)
|
||||||
shouldCompileTo("{{foo/foo-bar}}", {foo: {"foo-bar": "baz"}}, "baz", "Paths can contain hyphens (-)");
|
.withInput({
|
||||||
|
value: true,
|
||||||
|
awesome: function(context, options) {
|
||||||
|
return options.fn(context);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.withMessage('block functions are called with context and options')
|
||||||
|
.toCompileTo('inner 1');
|
||||||
});
|
});
|
||||||
|
|
||||||
it("nested paths", function() {
|
it('block functions without context argument', function() {
|
||||||
shouldCompileTo("Goodbye {{alan/expression}} world!", {alan: {expression: "beautiful"}},
|
expectTemplate('{{#awesome}}inner{{/awesome}}')
|
||||||
"Goodbye beautiful world!", "Nested paths access nested objects");
|
.withInput({
|
||||||
|
awesome: function(options) {
|
||||||
|
return options.fn(this);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.withMessage('block functions are called with options')
|
||||||
|
.toCompileTo('inner');
|
||||||
});
|
});
|
||||||
|
|
||||||
it("nested paths with empty string value", function() {
|
it('pathed block functions without context argument', function() {
|
||||||
shouldCompileTo("Goodbye {{alan/expression}} world!", {alan: {expression: ""}},
|
expectTemplate('{{#foo.awesome}}inner{{/foo.awesome}}')
|
||||||
"Goodbye world!", "Nested paths access nested objects with empty string");
|
.withInput({
|
||||||
|
foo: {
|
||||||
|
awesome: function() {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.withMessage('block functions are called with options')
|
||||||
|
.toCompileTo('inner');
|
||||||
});
|
});
|
||||||
|
|
||||||
it("literal paths", function() {
|
it('depthed block functions without context argument', function() {
|
||||||
shouldCompileTo("Goodbye {{[@alan]/expression}} world!", {"@alan": {expression: "beautiful"}},
|
expectTemplate(
|
||||||
"Goodbye beautiful world!", "Literal paths can be used");
|
'{{#with value}}{{#../awesome}}inner{{/../awesome}}{{/with}}'
|
||||||
shouldCompileTo("Goodbye {{[foo bar]/expression}} world!", {"foo bar": {expression: "beautiful"}},
|
)
|
||||||
"Goodbye beautiful world!", "Literal paths can be used");
|
.withInput({
|
||||||
|
value: true,
|
||||||
|
awesome: function() {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.withMessage('block functions are called with options')
|
||||||
|
.toCompileTo('inner');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('paths with hyphens', function() {
|
||||||
|
expectTemplate('{{foo-bar}}')
|
||||||
|
.withInput({ 'foo-bar': 'baz' })
|
||||||
|
.withMessage('Paths can contain hyphens (-)')
|
||||||
|
.toCompileTo('baz');
|
||||||
|
|
||||||
|
expectTemplate('{{foo.foo-bar}}')
|
||||||
|
.withInput({ foo: { 'foo-bar': 'baz' } })
|
||||||
|
.withMessage('Paths can contain hyphens (-)')
|
||||||
|
.toCompileTo('baz');
|
||||||
|
|
||||||
|
expectTemplate('{{foo/foo-bar}}')
|
||||||
|
.withInput({ foo: { 'foo-bar': 'baz' } })
|
||||||
|
.withMessage('Paths can contain hyphens (-)')
|
||||||
|
.toCompileTo('baz');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('nested paths', function() {
|
||||||
|
expectTemplate('Goodbye {{alan/expression}} world!')
|
||||||
|
.withInput({ alan: { expression: 'beautiful' } })
|
||||||
|
.withMessage('Nested paths access nested objects')
|
||||||
|
.toCompileTo('Goodbye beautiful world!');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('nested paths with empty string value', function() {
|
||||||
|
expectTemplate('Goodbye {{alan/expression}} world!')
|
||||||
|
.withInput({ alan: { expression: '' } })
|
||||||
|
.withMessage('Nested paths access nested objects with empty string')
|
||||||
|
.toCompileTo('Goodbye world!');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('literal paths', function() {
|
||||||
|
expectTemplate('Goodbye {{[@alan]/expression}} world!')
|
||||||
|
.withInput({ '@alan': { expression: 'beautiful' } })
|
||||||
|
.withMessage('Literal paths can be used')
|
||||||
|
.toCompileTo('Goodbye beautiful world!');
|
||||||
|
|
||||||
|
expectTemplate('Goodbye {{[foo bar]/expression}} world!')
|
||||||
|
.withInput({ 'foo bar': { expression: 'beautiful' } })
|
||||||
|
.withMessage('Literal paths can be used')
|
||||||
|
.toCompileTo('Goodbye beautiful world!');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('literal references', function() {
|
it('literal references', function() {
|
||||||
shouldCompileTo("Goodbye {{[foo bar]}} world!", {"foo bar": "beautiful"},
|
expectTemplate('Goodbye {{[foo bar]}} world!')
|
||||||
"Goodbye beautiful world!", "Literal paths can be used");
|
.withInput({ 'foo bar': 'beautiful' })
|
||||||
|
.toCompileTo('Goodbye beautiful world!');
|
||||||
|
|
||||||
|
expectTemplate('Goodbye {{"foo bar"}} world!')
|
||||||
|
.withInput({ 'foo bar': 'beautiful' })
|
||||||
|
.toCompileTo('Goodbye beautiful world!');
|
||||||
|
|
||||||
|
expectTemplate("Goodbye {{'foo bar'}} world!")
|
||||||
|
.withInput({ 'foo bar': 'beautiful' })
|
||||||
|
.toCompileTo('Goodbye beautiful world!');
|
||||||
|
|
||||||
|
expectTemplate('Goodbye {{"foo[bar"}} world!')
|
||||||
|
.withInput({ 'foo[bar': 'beautiful' })
|
||||||
|
.toCompileTo('Goodbye beautiful world!');
|
||||||
|
|
||||||
|
expectTemplate('Goodbye {{"foo\'bar"}} world!')
|
||||||
|
.withInput({ "foo'bar": 'beautiful' })
|
||||||
|
.toCompileTo('Goodbye beautiful world!');
|
||||||
|
|
||||||
|
expectTemplate("Goodbye {{'foo\"bar'}} world!")
|
||||||
|
.withInput({ 'foo"bar': 'beautiful' })
|
||||||
|
.toCompileTo('Goodbye beautiful world!');
|
||||||
});
|
});
|
||||||
|
|
||||||
it("that current context path ({{.}}) doesn't hit helpers", function() {
|
it("that current context path ({{.}}) doesn't hit helpers", function() {
|
||||||
shouldCompileTo("test: {{.}}", [null, {helper: "awesome"}], "test: ");
|
expectTemplate('test: {{.}}')
|
||||||
|
.withInput(null)
|
||||||
|
.withHelpers({ helper: 'awesome' })
|
||||||
|
.toCompileTo('test: ');
|
||||||
});
|
});
|
||||||
|
|
||||||
it("complex but empty paths", function() {
|
it('complex but empty paths', function() {
|
||||||
shouldCompileTo("{{person/name}}", {person: {name: null}}, "");
|
expectTemplate('{{person/name}}')
|
||||||
shouldCompileTo("{{person/name}}", {person: {}}, "");
|
.withInput({ person: { name: null } })
|
||||||
|
.toCompileTo('');
|
||||||
|
|
||||||
|
expectTemplate('{{person/name}}')
|
||||||
|
.withInput({ person: {} })
|
||||||
|
.toCompileTo('');
|
||||||
});
|
});
|
||||||
|
|
||||||
it("this keyword in paths", function() {
|
it('this keyword in paths', function() {
|
||||||
var string = "{{#goodbyes}}{{this}}{{/goodbyes}}";
|
expectTemplate('{{#goodbyes}}{{this}}{{/goodbyes}}')
|
||||||
var hash = {goodbyes: ["goodbye", "Goodbye", "GOODBYE"]};
|
.withInput({ goodbyes: ['goodbye', 'Goodbye', 'GOODBYE'] })
|
||||||
shouldCompileTo(string, hash, "goodbyeGoodbyeGOODBYE",
|
.withMessage('This keyword in paths evaluates to current context')
|
||||||
"This keyword in paths evaluates to current context");
|
.toCompileTo('goodbyeGoodbyeGOODBYE');
|
||||||
|
|
||||||
string = "{{#hellos}}{{this/text}}{{/hellos}}";
|
expectTemplate('{{#hellos}}{{this/text}}{{/hellos}}')
|
||||||
hash = {hellos: [{text: "hello"}, {text: "Hello"}, {text: "HELLO"}]};
|
.withInput({
|
||||||
shouldCompileTo(string, hash, "helloHelloHELLO", "This keyword evaluates in more complex paths");
|
hellos: [{ text: 'hello' }, { text: 'Hello' }, { text: 'HELLO' }]
|
||||||
|
})
|
||||||
|
.withMessage('This keyword evaluates in more complex paths')
|
||||||
|
.toCompileTo('helloHelloHELLO');
|
||||||
});
|
});
|
||||||
|
|
||||||
it("this keyword nested inside path", function() {
|
it('this keyword nested inside path', function() {
|
||||||
var string = "{{#hellos}}{{text/this/foo}}{{/hellos}}";
|
expectTemplate('{{#hellos}}{{text/this/foo}}{{/hellos}}').toThrow(
|
||||||
shouldThrow(function() {
|
Error,
|
||||||
CompilerContext.compile(string);
|
'Invalid path: text/this - 1:13'
|
||||||
}, Error);
|
);
|
||||||
|
|
||||||
|
expectTemplate('{{[this]}}')
|
||||||
|
.withInput({ this: 'bar' })
|
||||||
|
.toCompileTo('bar');
|
||||||
|
|
||||||
|
expectTemplate('{{text/[this]}}')
|
||||||
|
.withInput({ text: { this: 'bar' } })
|
||||||
|
.toCompileTo('bar');
|
||||||
});
|
});
|
||||||
|
|
||||||
it("this keyword in helpers", function() {
|
it('this keyword in helpers', function() {
|
||||||
var helpers = {foo: function(value) {
|
var helpers = {
|
||||||
|
foo: function(value) {
|
||||||
return 'bar ' + value;
|
return 'bar ' + value;
|
||||||
}};
|
}
|
||||||
var string = "{{#goodbyes}}{{foo this}}{{/goodbyes}}";
|
};
|
||||||
var hash = {goodbyes: ["goodbye", "Goodbye", "GOODBYE"]};
|
|
||||||
shouldCompileTo(string, [hash, helpers], "bar goodbyebar Goodbyebar GOODBYE",
|
|
||||||
"This keyword in paths evaluates to current context");
|
|
||||||
|
|
||||||
string = "{{#hellos}}{{foo this/text}}{{/hellos}}";
|
expectTemplate('{{#goodbyes}}{{foo this}}{{/goodbyes}}')
|
||||||
hash = {hellos: [{text: "hello"}, {text: "Hello"}, {text: "HELLO"}]};
|
.withInput({ goodbyes: ['goodbye', 'Goodbye', 'GOODBYE'] })
|
||||||
shouldCompileTo(string, [hash, helpers], "bar hellobar Hellobar HELLO", "This keyword evaluates in more complex paths");
|
.withHelpers(helpers)
|
||||||
|
.withMessage('This keyword in paths evaluates to current context')
|
||||||
|
.toCompileTo('bar goodbyebar Goodbyebar GOODBYE');
|
||||||
|
|
||||||
|
expectTemplate('{{#hellos}}{{foo this/text}}{{/hellos}}')
|
||||||
|
.withInput({
|
||||||
|
hellos: [{ text: 'hello' }, { text: 'Hello' }, { text: 'HELLO' }]
|
||||||
|
})
|
||||||
|
.withHelpers(helpers)
|
||||||
|
.withMessage('This keyword evaluates in more complex paths')
|
||||||
|
.toCompileTo('bar hellobar Hellobar HELLO');
|
||||||
});
|
});
|
||||||
|
|
||||||
it("this keyword nested inside helpers param", function() {
|
it('this keyword nested inside helpers param', function() {
|
||||||
var string = "{{#hellos}}{{foo text/this/foo}}{{/hellos}}";
|
expectTemplate('{{#hellos}}{{foo text/this/foo}}{{/hellos}}').toThrow(
|
||||||
shouldThrow(function() {
|
Error,
|
||||||
CompilerContext.compile(string);
|
'Invalid path: text/this - 1:17'
|
||||||
}, Error);
|
);
|
||||||
|
|
||||||
|
expectTemplate('{{foo [this]}}')
|
||||||
|
.withInput({
|
||||||
|
foo: function(value) {
|
||||||
|
return value;
|
||||||
|
},
|
||||||
|
this: 'bar'
|
||||||
|
})
|
||||||
|
.toCompileTo('bar');
|
||||||
|
|
||||||
|
expectTemplate('{{foo text/[this]}}')
|
||||||
|
.withInput({
|
||||||
|
foo: function(value) {
|
||||||
|
return value;
|
||||||
|
},
|
||||||
|
text: { this: 'bar' }
|
||||||
|
})
|
||||||
|
.toCompileTo('bar');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('pass string literals', function() {
|
||||||
|
expectTemplate('{{"foo"}}').toCompileTo('');
|
||||||
|
|
||||||
|
expectTemplate('{{"foo"}}')
|
||||||
|
.withInput({ foo: 'bar' })
|
||||||
|
.toCompileTo('bar');
|
||||||
|
|
||||||
|
expectTemplate('{{#"foo"}}{{.}}{{/"foo"}}')
|
||||||
|
.withInput({
|
||||||
|
foo: ['bar', 'baz']
|
||||||
|
})
|
||||||
|
.toCompileTo('barbaz');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('pass number literals', function() {
|
||||||
|
expectTemplate('{{12}}').toCompileTo('');
|
||||||
|
|
||||||
|
expectTemplate('{{12}}')
|
||||||
|
.withInput({ '12': 'bar' })
|
||||||
|
.toCompileTo('bar');
|
||||||
|
|
||||||
|
expectTemplate('{{12.34}}').toCompileTo('');
|
||||||
|
|
||||||
|
expectTemplate('{{12.34}}')
|
||||||
|
.withInput({ '12.34': 'bar' })
|
||||||
|
.toCompileTo('bar');
|
||||||
|
|
||||||
|
expectTemplate('{{12.34 1}}')
|
||||||
|
.withInput({
|
||||||
|
'12.34': function(arg) {
|
||||||
|
return 'bar' + arg;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.toCompileTo('bar1');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('pass boolean literals', function() {
|
||||||
|
expectTemplate('{{true}}').toCompileTo('');
|
||||||
|
|
||||||
|
expectTemplate('{{true}}')
|
||||||
|
.withInput({ '': 'foo' })
|
||||||
|
.toCompileTo('');
|
||||||
|
|
||||||
|
expectTemplate('{{false}}')
|
||||||
|
.withInput({ false: 'foo' })
|
||||||
|
.toCompileTo('foo');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle literals in subexpression', function() {
|
||||||
|
expectTemplate('{{foo (false)}}')
|
||||||
|
.withInput({
|
||||||
|
false: function() {
|
||||||
|
return 'bar';
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.withHelper('foo', function(arg) {
|
||||||
|
return arg;
|
||||||
|
})
|
||||||
|
.toCompileTo('bar');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
+409
-86
@@ -1,133 +1,456 @@
|
|||||||
/*global CompilerContext, shouldCompileTo, shouldThrow */
|
|
||||||
describe('blocks', function() {
|
describe('blocks', function() {
|
||||||
it("array", function() {
|
it('array', function() {
|
||||||
var string = "{{#goodbyes}}{{text}}! {{/goodbyes}}cruel {{world}}!";
|
var string = '{{#goodbyes}}{{text}}! {{/goodbyes}}cruel {{world}}!';
|
||||||
var hash = {goodbyes: [{text: "goodbye"}, {text: "Goodbye"}, {text: "GOODBYE"}], world: "world"};
|
|
||||||
shouldCompileTo(string, hash, "goodbye! Goodbye! GOODBYE! cruel world!",
|
|
||||||
"Arrays iterate over the contents when not empty");
|
|
||||||
|
|
||||||
shouldCompileTo(string, {goodbyes: [], world: "world"}, "cruel world!",
|
expectTemplate(string)
|
||||||
"Arrays ignore the contents when empty");
|
.withInput({
|
||||||
|
goodbyes: [
|
||||||
|
{ text: 'goodbye' },
|
||||||
|
{ text: 'Goodbye' },
|
||||||
|
{ text: 'GOODBYE' }
|
||||||
|
],
|
||||||
|
world: 'world'
|
||||||
|
})
|
||||||
|
.withMessage('Arrays iterate over the contents when not empty')
|
||||||
|
.toCompileTo('goodbye! Goodbye! GOODBYE! cruel world!');
|
||||||
|
|
||||||
|
expectTemplate(string)
|
||||||
|
.withInput({
|
||||||
|
goodbyes: [],
|
||||||
|
world: 'world'
|
||||||
|
})
|
||||||
|
.withMessage('Arrays ignore the contents when empty')
|
||||||
|
.toCompileTo('cruel world!');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('array without data', function() {
|
it('array without data', function() {
|
||||||
var string = '{{#goodbyes}}{{text}}{{/goodbyes}} {{#goodbyes}}{{text}}{{/goodbyes}}';
|
expectTemplate(
|
||||||
var hash = {goodbyes: [{text: 'goodbye'}, {text: 'Goodbye'}, {text: 'GOODBYE'}], world: 'world'};
|
'{{#goodbyes}}{{text}}{{/goodbyes}} {{#goodbyes}}{{text}}{{/goodbyes}}'
|
||||||
shouldCompileTo(string, [hash,,,,false], 'goodbyeGoodbyeGOODBYE goodbyeGoodbyeGOODBYE');
|
)
|
||||||
|
.withInput({
|
||||||
|
goodbyes: [
|
||||||
|
{ text: 'goodbye' },
|
||||||
|
{ text: 'Goodbye' },
|
||||||
|
{ text: 'GOODBYE' }
|
||||||
|
],
|
||||||
|
world: 'world'
|
||||||
|
})
|
||||||
|
.withCompileOptions({ compat: false })
|
||||||
|
.toCompileTo('goodbyeGoodbyeGOODBYE goodbyeGoodbyeGOODBYE');
|
||||||
});
|
});
|
||||||
|
|
||||||
it("array with @index", function() {
|
it('array with @index', function() {
|
||||||
var string = "{{#goodbyes}}{{@index}}. {{text}}! {{/goodbyes}}cruel {{world}}!";
|
expectTemplate(
|
||||||
var hash = {goodbyes: [{text: "goodbye"}, {text: "Goodbye"}, {text: "GOODBYE"}], world: "world"};
|
'{{#goodbyes}}{{@index}}. {{text}}! {{/goodbyes}}cruel {{world}}!'
|
||||||
|
)
|
||||||
var template = CompilerContext.compile(string);
|
.withInput({
|
||||||
var result = template(hash);
|
goodbyes: [
|
||||||
|
{ text: 'goodbye' },
|
||||||
equal(result, "0. goodbye! 1. Goodbye! 2. GOODBYE! cruel world!", "The @index variable is used");
|
{ text: 'Goodbye' },
|
||||||
});
|
{ text: 'GOODBYE' }
|
||||||
|
],
|
||||||
it("empty block", function() {
|
world: 'world'
|
||||||
var string = "{{#goodbyes}}{{/goodbyes}}cruel {{world}}!";
|
})
|
||||||
var hash = {goodbyes: [{text: "goodbye"}, {text: "Goodbye"}, {text: "GOODBYE"}], world: "world"};
|
.withMessage('The @index variable is used')
|
||||||
shouldCompileTo(string, hash, "cruel world!",
|
.toCompileTo('0. goodbye! 1. Goodbye! 2. GOODBYE! cruel world!');
|
||||||
"Arrays iterate over the contents when not empty");
|
|
||||||
|
|
||||||
shouldCompileTo(string, {goodbyes: [], world: "world"}, "cruel world!",
|
|
||||||
"Arrays ignore the contents when empty");
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("block with complex lookup", function() {
|
it('empty block', function() {
|
||||||
var string = "{{#goodbyes}}{{text}} cruel {{../name}}! {{/goodbyes}}";
|
var string = '{{#goodbyes}}{{/goodbyes}}cruel {{world}}!';
|
||||||
var hash = {name: "Alan", goodbyes: [{text: "goodbye"}, {text: "Goodbye"}, {text: "GOODBYE"}]};
|
|
||||||
|
|
||||||
shouldCompileTo(string, hash, "goodbye cruel Alan! Goodbye cruel Alan! GOODBYE cruel Alan! ",
|
expectTemplate(string)
|
||||||
"Templates can access variables in contexts up the stack with relative path syntax");
|
.withInput({
|
||||||
|
goodbyes: [
|
||||||
|
{ text: 'goodbye' },
|
||||||
|
{ text: 'Goodbye' },
|
||||||
|
{ text: 'GOODBYE' }
|
||||||
|
],
|
||||||
|
world: 'world'
|
||||||
|
})
|
||||||
|
.withMessage('Arrays iterate over the contents when not empty')
|
||||||
|
.toCompileTo('cruel world!');
|
||||||
|
|
||||||
|
expectTemplate(string)
|
||||||
|
.withInput({
|
||||||
|
goodbyes: [],
|
||||||
|
world: 'world'
|
||||||
|
})
|
||||||
|
.withMessage('Arrays ignore the contents when empty')
|
||||||
|
.toCompileTo('cruel world!');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('block with complex lookup', function() {
|
||||||
|
expectTemplate('{{#goodbyes}}{{text}} cruel {{../name}}! {{/goodbyes}}')
|
||||||
|
.withInput({
|
||||||
|
name: 'Alan',
|
||||||
|
goodbyes: [
|
||||||
|
{ text: 'goodbye' },
|
||||||
|
{ text: 'Goodbye' },
|
||||||
|
{ text: 'GOODBYE' }
|
||||||
|
]
|
||||||
|
})
|
||||||
|
.withMessage(
|
||||||
|
'Templates can access variables in contexts up the stack with relative path syntax'
|
||||||
|
)
|
||||||
|
.toCompileTo(
|
||||||
|
'goodbye cruel Alan! Goodbye cruel Alan! GOODBYE cruel Alan! '
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('multiple blocks with complex lookup', function() {
|
it('multiple blocks with complex lookup', function() {
|
||||||
var string = '{{#goodbyes}}{{../name}}{{../name}}{{/goodbyes}}';
|
expectTemplate('{{#goodbyes}}{{../name}}{{../name}}{{/goodbyes}}')
|
||||||
var hash = {name: 'Alan', goodbyes: [{text: 'goodbye'}, {text: 'Goodbye'}, {text: 'GOODBYE'}]};
|
.withInput({
|
||||||
|
name: 'Alan',
|
||||||
shouldCompileTo(string, hash, 'AlanAlanAlanAlanAlanAlan');
|
goodbyes: [
|
||||||
|
{ text: 'goodbye' },
|
||||||
|
{ text: 'Goodbye' },
|
||||||
|
{ text: 'GOODBYE' }
|
||||||
|
]
|
||||||
|
})
|
||||||
|
.toCompileTo('AlanAlanAlanAlanAlanAlan');
|
||||||
});
|
});
|
||||||
|
|
||||||
it("block with complex lookup using nested context", function() {
|
it('block with complex lookup using nested context', function() {
|
||||||
var string = "{{#goodbyes}}{{text}} cruel {{foo/../name}}! {{/goodbyes}}";
|
expectTemplate(
|
||||||
|
'{{#goodbyes}}{{text}} cruel {{foo/../name}}! {{/goodbyes}}'
|
||||||
shouldThrow(function() {
|
).toThrow(Error);
|
||||||
CompilerContext.compile(string);
|
|
||||||
}, Error);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("block with deep nested complex lookup", function() {
|
it('block with deep nested complex lookup', function() {
|
||||||
var string = "{{#outer}}Goodbye {{#inner}}cruel {{../sibling}} {{../../omg}}{{/inner}}{{/outer}}";
|
expectTemplate(
|
||||||
var hash = {omg: "OMG!", outer: [{ sibling: 'sad', inner: [{ text: "goodbye" }] }] };
|
'{{#outer}}Goodbye {{#inner}}cruel {{../sibling}} {{../../omg}}{{/inner}}{{/outer}}'
|
||||||
|
)
|
||||||
|
.withInput({
|
||||||
|
omg: 'OMG!',
|
||||||
|
outer: [{ sibling: 'sad', inner: [{ text: 'goodbye' }] }]
|
||||||
|
})
|
||||||
|
.toCompileTo('Goodbye cruel sad OMG!');
|
||||||
|
});
|
||||||
|
|
||||||
shouldCompileTo(string, hash, "Goodbye cruel sad OMG!");
|
it('works with cached blocks', function() {
|
||||||
|
expectTemplate(
|
||||||
|
'{{#each person}}{{#with .}}{{first}} {{last}}{{/with}}{{/each}}'
|
||||||
|
)
|
||||||
|
.withCompileOptions({ data: false })
|
||||||
|
.withInput({
|
||||||
|
person: [
|
||||||
|
{ first: 'Alan', last: 'Johnson' },
|
||||||
|
{ first: 'Alan', last: 'Johnson' }
|
||||||
|
]
|
||||||
|
})
|
||||||
|
.toCompileTo('Alan JohnsonAlan Johnson');
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('inverted sections', function() {
|
describe('inverted sections', function() {
|
||||||
it("inverted sections with unset value", function() {
|
it('inverted sections with unset value', function() {
|
||||||
var string = "{{#goodbyes}}{{this}}{{/goodbyes}}{{^goodbyes}}Right On!{{/goodbyes}}";
|
expectTemplate(
|
||||||
var hash = {};
|
'{{#goodbyes}}{{this}}{{/goodbyes}}{{^goodbyes}}Right On!{{/goodbyes}}'
|
||||||
shouldCompileTo(string, hash, "Right On!", "Inverted section rendered when value isn't set.");
|
)
|
||||||
|
.withMessage("Inverted section rendered when value isn't set.")
|
||||||
|
.toCompileTo('Right On!');
|
||||||
});
|
});
|
||||||
|
|
||||||
it("inverted section with false value", function() {
|
it('inverted section with false value', function() {
|
||||||
var string = "{{#goodbyes}}{{this}}{{/goodbyes}}{{^goodbyes}}Right On!{{/goodbyes}}";
|
expectTemplate(
|
||||||
var hash = {goodbyes: false};
|
'{{#goodbyes}}{{this}}{{/goodbyes}}{{^goodbyes}}Right On!{{/goodbyes}}'
|
||||||
shouldCompileTo(string, hash, "Right On!", "Inverted section rendered when value is false.");
|
)
|
||||||
|
.withInput({ goodbyes: false })
|
||||||
|
.withMessage('Inverted section rendered when value is false.')
|
||||||
|
.toCompileTo('Right On!');
|
||||||
});
|
});
|
||||||
|
|
||||||
it("inverted section with empty set", function() {
|
it('inverted section with empty set', function() {
|
||||||
var string = "{{#goodbyes}}{{this}}{{/goodbyes}}{{^goodbyes}}Right On!{{/goodbyes}}";
|
expectTemplate(
|
||||||
var hash = {goodbyes: []};
|
'{{#goodbyes}}{{this}}{{/goodbyes}}{{^goodbyes}}Right On!{{/goodbyes}}'
|
||||||
shouldCompileTo(string, hash, "Right On!", "Inverted section rendered when value is empty set.");
|
)
|
||||||
|
.withInput({ goodbyes: [] })
|
||||||
|
.withMessage('Inverted section rendered when value is empty set.')
|
||||||
|
.toCompileTo('Right On!');
|
||||||
});
|
});
|
||||||
|
|
||||||
it("block inverted sections", function() {
|
it('block inverted sections', function() {
|
||||||
shouldCompileTo("{{#people}}{{name}}{{^}}{{none}}{{/people}}", {none: "No people"},
|
expectTemplate('{{#people}}{{name}}{{^}}{{none}}{{/people}}')
|
||||||
"No people");
|
.withInput({ none: 'No people' })
|
||||||
|
.toCompileTo('No people');
|
||||||
});
|
});
|
||||||
|
|
||||||
it("block inverted sections with empty arrays", function() {
|
it('chained inverted sections', function() {
|
||||||
shouldCompileTo("{{#people}}{{name}}{{^}}{{none}}{{/people}}", {none: "No people", people: []},
|
expectTemplate('{{#people}}{{name}}{{else if none}}{{none}}{{/people}}')
|
||||||
"No people");
|
.withInput({ none: 'No people' })
|
||||||
|
.toCompileTo('No people');
|
||||||
|
|
||||||
|
expectTemplate(
|
||||||
|
'{{#people}}{{name}}{{else if nothere}}fail{{else unless nothere}}{{none}}{{/people}}'
|
||||||
|
)
|
||||||
|
.withInput({ none: 'No people' })
|
||||||
|
.toCompileTo('No people');
|
||||||
|
|
||||||
|
expectTemplate(
|
||||||
|
'{{#people}}{{name}}{{else if none}}{{none}}{{else}}fail{{/people}}'
|
||||||
|
)
|
||||||
|
.withInput({ none: 'No people' })
|
||||||
|
.toCompileTo('No people');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('chained inverted sections with mismatch', function() {
|
||||||
|
expectTemplate(
|
||||||
|
'{{#people}}{{name}}{{else if none}}{{none}}{{/if}}'
|
||||||
|
).toThrow(Error);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('block inverted sections with empty arrays', function() {
|
||||||
|
expectTemplate('{{#people}}{{name}}{{^}}{{none}}{{/people}}')
|
||||||
|
.withInput({
|
||||||
|
none: 'No people',
|
||||||
|
people: []
|
||||||
|
})
|
||||||
|
.toCompileTo('No people');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('standalone sections', function() {
|
describe('standalone sections', function() {
|
||||||
it('block standalone else sections', function() {
|
it('block standalone else sections', function() {
|
||||||
shouldCompileTo('{{#people}}\n{{name}}\n{{^}}\n{{none}}\n{{/people}}\n', {none: 'No people'},
|
expectTemplate('{{#people}}\n{{name}}\n{{^}}\n{{none}}\n{{/people}}\n')
|
||||||
'No people\n');
|
.withInput({ none: 'No people' })
|
||||||
shouldCompileTo('{{#none}}\n{{.}}\n{{^}}\n{{none}}\n{{/none}}\n', {none: 'No people'},
|
.toCompileTo('No people\n');
|
||||||
'No people\n');
|
|
||||||
shouldCompileTo('{{#people}}\n{{name}}\n{{^}}\n{{none}}\n{{/people}}\n', {none: 'No people'},
|
expectTemplate('{{#none}}\n{{.}}\n{{^}}\n{{none}}\n{{/none}}\n')
|
||||||
'No people\n');
|
.withInput({ none: 'No people' })
|
||||||
|
.toCompileTo('No people\n');
|
||||||
|
|
||||||
|
expectTemplate('{{#people}}\n{{name}}\n{{^}}\n{{none}}\n{{/people}}\n')
|
||||||
|
.withInput({ none: 'No people' })
|
||||||
|
.toCompileTo('No people\n');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('block standalone else sections can be disabled', function() {
|
||||||
|
expectTemplate('{{#people}}\n{{name}}\n{{^}}\n{{none}}\n{{/people}}\n')
|
||||||
|
.withInput({ none: 'No people' })
|
||||||
|
.withCompileOptions({ ignoreStandalone: true })
|
||||||
|
.toCompileTo('\nNo people\n\n');
|
||||||
|
|
||||||
|
expectTemplate('{{#none}}\n{{.}}\n{{^}}\nFail\n{{/none}}\n')
|
||||||
|
.withInput({ none: 'No people' })
|
||||||
|
.withCompileOptions({ ignoreStandalone: true })
|
||||||
|
.toCompileTo('\nNo people\n\n');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('block standalone chained else sections', function() {
|
||||||
|
expectTemplate(
|
||||||
|
'{{#people}}\n{{name}}\n{{else if none}}\n{{none}}\n{{/people}}\n'
|
||||||
|
)
|
||||||
|
.withInput({ none: 'No people' })
|
||||||
|
.toCompileTo('No people\n');
|
||||||
|
|
||||||
|
expectTemplate(
|
||||||
|
'{{#people}}\n{{name}}\n{{else if none}}\n{{none}}\n{{^}}\n{{/people}}\n'
|
||||||
|
)
|
||||||
|
.withInput({ none: 'No people' })
|
||||||
|
.toCompileTo('No people\n');
|
||||||
|
});
|
||||||
|
|
||||||
it('should handle nesting', function() {
|
it('should handle nesting', function() {
|
||||||
shouldCompileTo('{{#data}}\n{{#if true}}\n{{.}}\n{{/if}}\n{{/data}}\nOK.', {data: [1, 3, 5]}, '1\n3\n5\nOK.');
|
expectTemplate('{{#data}}\n{{#if true}}\n{{.}}\n{{/if}}\n{{/data}}\nOK.')
|
||||||
|
.withInput({
|
||||||
|
data: [1, 3, 5]
|
||||||
|
})
|
||||||
|
.toCompileTo('1\n3\n5\nOK.');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('compat mode', function() {
|
describe('compat mode', function() {
|
||||||
it("block with deep recursive lookup lookup", function() {
|
it('block with deep recursive lookup lookup', function() {
|
||||||
var string = "{{#outer}}Goodbye {{#inner}}cruel {{omg}}{{/inner}}{{/outer}}";
|
expectTemplate(
|
||||||
var hash = {omg: "OMG!", outer: [{ inner: [{ text: "goodbye" }] }] };
|
'{{#outer}}Goodbye {{#inner}}cruel {{omg}}{{/inner}}{{/outer}}'
|
||||||
|
)
|
||||||
shouldCompileTo(string, [hash, undefined, undefined, true], "Goodbye cruel OMG!");
|
.withInput({ omg: 'OMG!', outer: [{ inner: [{ text: 'goodbye' }] }] })
|
||||||
|
.withCompileOptions({ compat: true })
|
||||||
|
.toCompileTo('Goodbye cruel OMG!');
|
||||||
});
|
});
|
||||||
it("block with deep recursive pathed lookup", function() {
|
|
||||||
var string = "{{#outer}}Goodbye {{#inner}}cruel {{omg.yes}}{{/inner}}{{/outer}}";
|
|
||||||
var hash = {omg: {yes: "OMG!"}, outer: [{ inner: [{ yes: 'no', text: "goodbye" }] }] };
|
|
||||||
|
|
||||||
shouldCompileTo(string, [hash, undefined, undefined, true], "Goodbye cruel OMG!");
|
it('block with deep recursive pathed lookup', function() {
|
||||||
|
expectTemplate(
|
||||||
|
'{{#outer}}Goodbye {{#inner}}cruel {{omg.yes}}{{/inner}}{{/outer}}'
|
||||||
|
)
|
||||||
|
.withInput({
|
||||||
|
omg: { yes: 'OMG!' },
|
||||||
|
outer: [{ inner: [{ yes: 'no', text: 'goodbye' }] }]
|
||||||
|
})
|
||||||
|
.withCompileOptions({ compat: true })
|
||||||
|
.toCompileTo('Goodbye cruel OMG!');
|
||||||
});
|
});
|
||||||
it("block with missed recursive lookup", function() {
|
|
||||||
var string = "{{#outer}}Goodbye {{#inner}}cruel {{omg.yes}}{{/inner}}{{/outer}}";
|
|
||||||
var hash = {omg: {no: "OMG!"}, outer: [{ inner: [{ yes: 'no', text: "goodbye" }] }] };
|
|
||||||
|
|
||||||
shouldCompileTo(string, [hash, undefined, undefined, true], "Goodbye cruel ");
|
it('block with missed recursive lookup', function() {
|
||||||
|
expectTemplate(
|
||||||
|
'{{#outer}}Goodbye {{#inner}}cruel {{omg.yes}}{{/inner}}{{/outer}}'
|
||||||
|
)
|
||||||
|
.withInput({
|
||||||
|
omg: { no: 'OMG!' },
|
||||||
|
outer: [{ inner: [{ yes: 'no', text: 'goodbye' }] }]
|
||||||
|
})
|
||||||
|
.withCompileOptions({ compat: true })
|
||||||
|
.toCompileTo('Goodbye cruel ');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('decorators', function() {
|
||||||
|
it('should apply mustache decorators', function() {
|
||||||
|
expectTemplate('{{#helper}}{{*decorator}}{{/helper}}')
|
||||||
|
.withHelper('helper', function(options) {
|
||||||
|
return options.fn.run;
|
||||||
|
})
|
||||||
|
.withDecorator('decorator', function(fn) {
|
||||||
|
fn.run = 'success';
|
||||||
|
return fn;
|
||||||
|
})
|
||||||
|
.toCompileTo('success');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should apply allow undefined return', function() {
|
||||||
|
expectTemplate('{{#helper}}{{*decorator}}suc{{/helper}}')
|
||||||
|
.withHelper('helper', function(options) {
|
||||||
|
return options.fn() + options.fn.run;
|
||||||
|
})
|
||||||
|
.withDecorator('decorator', function(fn) {
|
||||||
|
fn.run = 'cess';
|
||||||
|
})
|
||||||
|
.toCompileTo('success');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should apply block decorators', function() {
|
||||||
|
expectTemplate(
|
||||||
|
'{{#helper}}{{#*decorator}}success{{/decorator}}{{/helper}}'
|
||||||
|
)
|
||||||
|
.withHelper('helper', function(options) {
|
||||||
|
return options.fn.run;
|
||||||
|
})
|
||||||
|
.withDecorator('decorator', function(fn, props, container, options) {
|
||||||
|
fn.run = options.fn();
|
||||||
|
return fn;
|
||||||
|
})
|
||||||
|
.toCompileTo('success');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should support nested decorators', function() {
|
||||||
|
expectTemplate(
|
||||||
|
'{{#helper}}{{#*decorator}}{{#*nested}}suc{{/nested}}cess{{/decorator}}{{/helper}}'
|
||||||
|
)
|
||||||
|
.withHelper('helper', function(options) {
|
||||||
|
return options.fn.run;
|
||||||
|
})
|
||||||
|
.withDecorators({
|
||||||
|
decorator: function(fn, props, container, options) {
|
||||||
|
fn.run = options.fn.nested + options.fn();
|
||||||
|
return fn;
|
||||||
|
},
|
||||||
|
nested: function(fn, props, container, options) {
|
||||||
|
props.nested = options.fn();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.toCompileTo('success');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should apply multiple decorators', function() {
|
||||||
|
expectTemplate(
|
||||||
|
'{{#helper}}{{#*decorator}}suc{{/decorator}}{{#*decorator}}cess{{/decorator}}{{/helper}}'
|
||||||
|
)
|
||||||
|
.withHelper('helper', function(options) {
|
||||||
|
return options.fn.run;
|
||||||
|
})
|
||||||
|
.withDecorator('decorator', function(fn, props, container, options) {
|
||||||
|
fn.run = (fn.run || '') + options.fn();
|
||||||
|
return fn;
|
||||||
|
})
|
||||||
|
.toCompileTo('success');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should access parent variables', function() {
|
||||||
|
expectTemplate('{{#helper}}{{*decorator foo}}{{/helper}}')
|
||||||
|
.withHelper('helper', function(options) {
|
||||||
|
return options.fn.run;
|
||||||
|
})
|
||||||
|
.withDecorator('decorator', function(fn, props, container, options) {
|
||||||
|
fn.run = options.args;
|
||||||
|
return fn;
|
||||||
|
})
|
||||||
|
.withInput({ foo: 'success' })
|
||||||
|
.toCompileTo('success');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should work with root program', function() {
|
||||||
|
var run;
|
||||||
|
expectTemplate('{{*decorator "success"}}')
|
||||||
|
.withDecorator('decorator', function(fn, props, container, options) {
|
||||||
|
equals(options.args[0], 'success');
|
||||||
|
run = true;
|
||||||
|
return fn;
|
||||||
|
})
|
||||||
|
.withInput({ foo: 'success' })
|
||||||
|
.toCompileTo('');
|
||||||
|
equals(run, true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should fail when accessing variables from root', function() {
|
||||||
|
var run;
|
||||||
|
expectTemplate('{{*decorator foo}}')
|
||||||
|
.withDecorator('decorator', function(fn, props, container, options) {
|
||||||
|
equals(options.args[0], undefined);
|
||||||
|
run = true;
|
||||||
|
return fn;
|
||||||
|
})
|
||||||
|
.withInput({ foo: 'fail' })
|
||||||
|
.toCompileTo('');
|
||||||
|
equals(run, true);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('registration', function() {
|
||||||
|
it('unregisters', function() {
|
||||||
|
handlebarsEnv.decorators = {};
|
||||||
|
|
||||||
|
handlebarsEnv.registerDecorator('foo', function() {
|
||||||
|
return 'fail';
|
||||||
|
});
|
||||||
|
|
||||||
|
equals(!!handlebarsEnv.decorators.foo, true);
|
||||||
|
handlebarsEnv.unregisterDecorator('foo');
|
||||||
|
equals(handlebarsEnv.decorators.foo, undefined);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('allows multiple globals', function() {
|
||||||
|
handlebarsEnv.decorators = {};
|
||||||
|
|
||||||
|
handlebarsEnv.registerDecorator({
|
||||||
|
foo: function() {},
|
||||||
|
bar: function() {}
|
||||||
|
});
|
||||||
|
|
||||||
|
equals(!!handlebarsEnv.decorators.foo, true);
|
||||||
|
equals(!!handlebarsEnv.decorators.bar, true);
|
||||||
|
handlebarsEnv.unregisterDecorator('foo');
|
||||||
|
handlebarsEnv.unregisterDecorator('bar');
|
||||||
|
equals(handlebarsEnv.decorators.foo, undefined);
|
||||||
|
equals(handlebarsEnv.decorators.bar, undefined);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('fails with multiple and args', function() {
|
||||||
|
shouldThrow(
|
||||||
|
function() {
|
||||||
|
handlebarsEnv.registerDecorator(
|
||||||
|
{
|
||||||
|
world: function() {
|
||||||
|
return 'world!';
|
||||||
|
},
|
||||||
|
testHelper: function() {
|
||||||
|
return 'found it!';
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{}
|
||||||
|
);
|
||||||
|
},
|
||||||
|
Error,
|
||||||
|
'Arg not supported with multiple decorators'
|
||||||
|
);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
+654
-202
@@ -1,52 +1,185 @@
|
|||||||
/*global CompilerContext, shouldCompileTo, shouldThrow, compileWithPartials, handlebarsEnv */
|
|
||||||
describe('builtin helpers', function() {
|
describe('builtin helpers', function() {
|
||||||
describe('#if', function() {
|
describe('#if', function() {
|
||||||
it("if", function() {
|
it('if', function() {
|
||||||
var string = "{{#if goodbye}}GOODBYE {{/if}}cruel {{world}}!";
|
var string = '{{#if goodbye}}GOODBYE {{/if}}cruel {{world}}!';
|
||||||
shouldCompileTo(string, {goodbye: true, world: "world"}, "GOODBYE cruel world!",
|
|
||||||
"if with boolean argument shows the contents when true");
|
expectTemplate(string)
|
||||||
shouldCompileTo(string, {goodbye: "dummy", world: "world"}, "GOODBYE cruel world!",
|
.withInput({
|
||||||
"if with string argument shows the contents");
|
goodbye: true,
|
||||||
shouldCompileTo(string, {goodbye: false, world: "world"}, "cruel world!",
|
world: 'world'
|
||||||
"if with boolean argument does not show the contents when false");
|
})
|
||||||
shouldCompileTo(string, {world: "world"}, "cruel world!",
|
.withMessage('if with boolean argument shows the contents when true')
|
||||||
"if with undefined does not show the contents");
|
.toCompileTo('GOODBYE cruel world!');
|
||||||
shouldCompileTo(string, {goodbye: ['foo'], world: "world"}, "GOODBYE cruel world!",
|
|
||||||
"if with non-empty array shows the contents");
|
expectTemplate(string)
|
||||||
shouldCompileTo(string, {goodbye: [], world: "world"}, "cruel world!",
|
.withInput({
|
||||||
"if with empty array does not show the contents");
|
goodbye: 'dummy',
|
||||||
shouldCompileTo(string, {goodbye: 0, world: "world"}, "cruel world!",
|
world: 'world'
|
||||||
"if with zero does not show the contents");
|
})
|
||||||
shouldCompileTo("{{#if goodbye includeZero=true}}GOODBYE {{/if}}cruel {{world}}!",
|
.withMessage('if with string argument shows the contents')
|
||||||
{goodbye: 0, world: "world"}, "GOODBYE cruel world!",
|
.toCompileTo('GOODBYE cruel world!');
|
||||||
"if with zero does not show the contents");
|
|
||||||
|
expectTemplate(string)
|
||||||
|
.withInput({
|
||||||
|
goodbye: false,
|
||||||
|
world: 'world'
|
||||||
|
})
|
||||||
|
.withMessage(
|
||||||
|
'if with boolean argument does not show the contents when false'
|
||||||
|
)
|
||||||
|
.toCompileTo('cruel world!');
|
||||||
|
|
||||||
|
expectTemplate(string)
|
||||||
|
.withInput({ world: 'world' })
|
||||||
|
.withMessage('if with undefined does not show the contents')
|
||||||
|
.toCompileTo('cruel world!');
|
||||||
|
|
||||||
|
expectTemplate(string)
|
||||||
|
.withInput({
|
||||||
|
goodbye: ['foo'],
|
||||||
|
world: 'world'
|
||||||
|
})
|
||||||
|
.withMessage('if with non-empty array shows the contents')
|
||||||
|
.toCompileTo('GOODBYE cruel world!');
|
||||||
|
|
||||||
|
expectTemplate(string)
|
||||||
|
.withInput({
|
||||||
|
goodbye: [],
|
||||||
|
world: 'world'
|
||||||
|
})
|
||||||
|
.withMessage('if with empty array does not show the contents')
|
||||||
|
.toCompileTo('cruel world!');
|
||||||
|
|
||||||
|
expectTemplate(string)
|
||||||
|
.withInput({
|
||||||
|
goodbye: 0,
|
||||||
|
world: 'world'
|
||||||
|
})
|
||||||
|
.withMessage('if with zero does not show the contents')
|
||||||
|
.toCompileTo('cruel world!');
|
||||||
|
|
||||||
|
expectTemplate(
|
||||||
|
'{{#if goodbye includeZero=true}}GOODBYE {{/if}}cruel {{world}}!'
|
||||||
|
)
|
||||||
|
.withInput({
|
||||||
|
goodbye: 0,
|
||||||
|
world: 'world'
|
||||||
|
})
|
||||||
|
.withMessage('if with zero does not show the contents')
|
||||||
|
.toCompileTo('GOODBYE cruel world!');
|
||||||
});
|
});
|
||||||
|
|
||||||
it("if with function argument", function() {
|
it('if with function argument', function() {
|
||||||
var string = "{{#if goodbye}}GOODBYE {{/if}}cruel {{world}}!";
|
var string = '{{#if goodbye}}GOODBYE {{/if}}cruel {{world}}!';
|
||||||
shouldCompileTo(string, {goodbye: function() {return true;}, world: "world"}, "GOODBYE cruel world!",
|
|
||||||
"if with function shows the contents when function returns true");
|
expectTemplate(string)
|
||||||
shouldCompileTo(string, {goodbye: function() {return this.world;}, world: "world"}, "GOODBYE cruel world!",
|
.withInput({
|
||||||
"if with function shows the contents when function returns string");
|
goodbye: function() {
|
||||||
shouldCompileTo(string, {goodbye: function() {return false;}, world: "world"}, "cruel world!",
|
return true;
|
||||||
"if with function does not show the contents when returns false");
|
},
|
||||||
shouldCompileTo(string, {goodbye: function() {return this.foo;}, world: "world"}, "cruel world!",
|
world: 'world'
|
||||||
"if with function does not show the contents when returns undefined");
|
})
|
||||||
|
.withMessage(
|
||||||
|
'if with function shows the contents when function returns true'
|
||||||
|
)
|
||||||
|
.toCompileTo('GOODBYE cruel world!');
|
||||||
|
|
||||||
|
expectTemplate(string)
|
||||||
|
.withInput({
|
||||||
|
goodbye: function() {
|
||||||
|
return this.world;
|
||||||
|
},
|
||||||
|
world: 'world'
|
||||||
|
})
|
||||||
|
.withMessage(
|
||||||
|
'if with function shows the contents when function returns string'
|
||||||
|
)
|
||||||
|
.toCompileTo('GOODBYE cruel world!');
|
||||||
|
|
||||||
|
expectTemplate(string)
|
||||||
|
.withInput({
|
||||||
|
goodbye: function() {
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
world: 'world'
|
||||||
|
})
|
||||||
|
.withMessage(
|
||||||
|
'if with function does not show the contents when returns false'
|
||||||
|
)
|
||||||
|
.toCompileTo('cruel world!');
|
||||||
|
|
||||||
|
expectTemplate(string)
|
||||||
|
.withInput({
|
||||||
|
goodbye: function() {
|
||||||
|
return this.foo;
|
||||||
|
},
|
||||||
|
world: 'world'
|
||||||
|
})
|
||||||
|
.withMessage(
|
||||||
|
'if with function does not show the contents when returns undefined'
|
||||||
|
)
|
||||||
|
.toCompileTo('cruel world!');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not change the depth list', function() {
|
||||||
|
expectTemplate(
|
||||||
|
'{{#with foo}}{{#if goodbye}}GOODBYE cruel {{../world}}!{{/if}}{{/with}}'
|
||||||
|
)
|
||||||
|
.withInput({
|
||||||
|
foo: { goodbye: true },
|
||||||
|
world: 'world'
|
||||||
|
})
|
||||||
|
.toCompileTo('GOODBYE cruel world!');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('#with', function() {
|
describe('#with', function() {
|
||||||
it("with", function() {
|
it('with', function() {
|
||||||
var string = "{{#with person}}{{first}} {{last}}{{/with}}";
|
expectTemplate('{{#with person}}{{first}} {{last}}{{/with}}')
|
||||||
shouldCompileTo(string, {person: {first: "Alan", last: "Johnson"}}, "Alan Johnson");
|
.withInput({
|
||||||
|
person: {
|
||||||
|
first: 'Alan',
|
||||||
|
last: 'Johnson'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.toCompileTo('Alan Johnson');
|
||||||
});
|
});
|
||||||
it("with with function argument", function() {
|
|
||||||
var string = "{{#with person}}{{first}} {{last}}{{/with}}";
|
it('with with function argument', function() {
|
||||||
shouldCompileTo(string, {person: function() { return {first: "Alan", last: "Johnson"};}}, "Alan Johnson");
|
expectTemplate('{{#with person}}{{first}} {{last}}{{/with}}')
|
||||||
|
.withInput({
|
||||||
|
person: function() {
|
||||||
|
return {
|
||||||
|
first: 'Alan',
|
||||||
|
last: 'Johnson'
|
||||||
|
};
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.toCompileTo('Alan Johnson');
|
||||||
});
|
});
|
||||||
it("with with else", function() {
|
|
||||||
var string = "{{#with person}}Person is present{{else}}Person is not present{{/with}}";
|
it('with with else', function() {
|
||||||
shouldCompileTo(string, {}, "Person is not present");
|
expectTemplate(
|
||||||
|
'{{#with person}}Person is present{{else}}Person is not present{{/with}}'
|
||||||
|
).toCompileTo('Person is not present');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('with provides block parameter', function() {
|
||||||
|
expectTemplate('{{#with person as |foo|}}{{foo.first}} {{last}}{{/with}}')
|
||||||
|
.withInput({
|
||||||
|
person: {
|
||||||
|
first: 'Alan',
|
||||||
|
last: 'Johnson'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.toCompileTo('Alan Johnson');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('works when data is disabled', function() {
|
||||||
|
expectTemplate('{{#with person as |foo|}}{{foo.first}} {{last}}{{/with}}')
|
||||||
|
.withInput({ person: { first: 'Alan', last: 'Johnson' } })
|
||||||
|
.withCompileOptions({ data: false })
|
||||||
|
.toCompileTo('Alan Johnson');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -57,258 +190,577 @@ describe('builtin helpers', function() {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it("each", function() {
|
it('each', function() {
|
||||||
var string = "{{#each goodbyes}}{{text}}! {{/each}}cruel {{world}}!";
|
var string = '{{#each goodbyes}}{{text}}! {{/each}}cruel {{world}}!';
|
||||||
var hash = {goodbyes: [{text: "goodbye"}, {text: "Goodbye"}, {text: "GOODBYE"}], world: "world"};
|
|
||||||
shouldCompileTo(string, hash, "goodbye! Goodbye! GOODBYE! cruel world!",
|
expectTemplate(string)
|
||||||
"each with array argument iterates over the contents when not empty");
|
.withInput({
|
||||||
shouldCompileTo(string, {goodbyes: [], world: "world"}, "cruel world!",
|
goodbyes: [
|
||||||
"each with array argument ignores the contents when empty");
|
{ text: 'goodbye' },
|
||||||
|
{ text: 'Goodbye' },
|
||||||
|
{ text: 'GOODBYE' }
|
||||||
|
],
|
||||||
|
world: 'world'
|
||||||
|
})
|
||||||
|
.withMessage(
|
||||||
|
'each with array argument iterates over the contents when not empty'
|
||||||
|
)
|
||||||
|
.toCompileTo('goodbye! Goodbye! GOODBYE! cruel world!');
|
||||||
|
|
||||||
|
expectTemplate(string)
|
||||||
|
.withInput({
|
||||||
|
goodbyes: [],
|
||||||
|
world: 'world'
|
||||||
|
})
|
||||||
|
.withMessage('each with array argument ignores the contents when empty')
|
||||||
|
.toCompileTo('cruel world!');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('each without data', function() {
|
it('each without data', function() {
|
||||||
var string = '{{#each goodbyes}}{{text}}! {{/each}}cruel {{world}}!';
|
expectTemplate('{{#each goodbyes}}{{text}}! {{/each}}cruel {{world}}!')
|
||||||
var hash = {goodbyes: [{text: 'goodbye'}, {text: 'Goodbye'}, {text: 'GOODBYE'}], world: 'world'};
|
.withInput({
|
||||||
shouldCompileTo(string, [hash,,,,false], 'goodbye! Goodbye! GOODBYE! cruel world!');
|
goodbyes: [
|
||||||
|
{ text: 'goodbye' },
|
||||||
|
{ text: 'Goodbye' },
|
||||||
|
{ text: 'GOODBYE' }
|
||||||
|
],
|
||||||
|
world: 'world'
|
||||||
|
})
|
||||||
|
.withRuntimeOptions({ data: false })
|
||||||
|
.withCompileOptions({ data: false })
|
||||||
|
.toCompileTo('goodbye! Goodbye! GOODBYE! cruel world!');
|
||||||
|
|
||||||
hash = {goodbyes: 'cruel', world: 'world'};
|
expectTemplate('{{#each .}}{{.}}{{/each}}')
|
||||||
shouldCompileTo('{{#each .}}{{.}}{{/each}}', [hash,,,,false], 'cruelworld');
|
.withInput({ goodbyes: 'cruel', world: 'world' })
|
||||||
|
.withRuntimeOptions({ data: false })
|
||||||
|
.withCompileOptions({ data: false })
|
||||||
|
.toCompileTo('cruelworld');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('each without context', function() {
|
it('each without context', function() {
|
||||||
var string = '{{#each goodbyes}}{{text}}! {{/each}}cruel {{world}}!';
|
expectTemplate('{{#each goodbyes}}{{text}}! {{/each}}cruel {{world}}!')
|
||||||
shouldCompileTo(string, [,,,,], 'cruel !');
|
.withInput(undefined)
|
||||||
|
.toCompileTo('cruel !');
|
||||||
});
|
});
|
||||||
|
|
||||||
it("each with an object and @key", function() {
|
it('each with an object and @key', function() {
|
||||||
var string = "{{#each goodbyes}}{{@key}}. {{text}}! {{/each}}cruel {{world}}!";
|
var string =
|
||||||
|
'{{#each goodbyes}}{{@key}}. {{text}}! {{/each}}cruel {{world}}!';
|
||||||
|
|
||||||
function Clazz() {
|
function Clazz() {
|
||||||
this['<b>#1</b>'] = {text: 'goodbye'};
|
this['<b>#1</b>'] = { text: 'goodbye' };
|
||||||
this[2] = {text: 'GOODBYE'};
|
this[2] = { text: 'GOODBYE' };
|
||||||
}
|
}
|
||||||
Clazz.prototype.foo = 'fail';
|
Clazz.prototype.foo = 'fail';
|
||||||
var hash = {goodbyes: new Clazz(), world: 'world'};
|
var hash = { goodbyes: new Clazz(), world: 'world' };
|
||||||
|
|
||||||
// Object property iteration order is undefined according to ECMA spec,
|
// Object property iteration order is undefined according to ECMA spec,
|
||||||
// so we need to check both possible orders
|
// so we need to check both possible orders
|
||||||
// @see http://stackoverflow.com/questions/280713/elements-order-in-a-for-in-loop
|
// @see http://stackoverflow.com/questions/280713/elements-order-in-a-for-in-loop
|
||||||
var actual = compileWithPartials(string, hash);
|
var actual = compileWithPartials(string, hash);
|
||||||
var expected1 = "<b>#1</b>. goodbye! 2. GOODBYE! cruel world!";
|
var expected1 =
|
||||||
var expected2 = "2. GOODBYE! <b>#1</b>. goodbye! cruel world!";
|
'<b>#1</b>. goodbye! 2. GOODBYE! cruel world!';
|
||||||
|
var expected2 =
|
||||||
|
'2. GOODBYE! <b>#1</b>. goodbye! cruel world!';
|
||||||
|
|
||||||
equals(actual === expected1 || actual === expected2, true, "each with object argument iterates over the contents when not empty");
|
equals(
|
||||||
shouldCompileTo(string, {goodbyes: [], world: "world"}, "cruel world!",
|
actual === expected1 || actual === expected2,
|
||||||
"each with object argument ignores the contents when empty");
|
true,
|
||||||
|
'each with object argument iterates over the contents when not empty'
|
||||||
|
);
|
||||||
|
|
||||||
|
expectTemplate(string)
|
||||||
|
.withInput({
|
||||||
|
goodbyes: {},
|
||||||
|
world: 'world'
|
||||||
|
})
|
||||||
|
.toCompileTo('cruel world!');
|
||||||
});
|
});
|
||||||
|
|
||||||
it("each with @index", function() {
|
it('each with @index', function() {
|
||||||
var string = "{{#each goodbyes}}{{@index}}. {{text}}! {{/each}}cruel {{world}}!";
|
expectTemplate(
|
||||||
var hash = {goodbyes: [{text: "goodbye"}, {text: "Goodbye"}, {text: "GOODBYE"}], world: "world"};
|
'{{#each goodbyes}}{{@index}}. {{text}}! {{/each}}cruel {{world}}!'
|
||||||
|
)
|
||||||
var template = CompilerContext.compile(string);
|
.withInput({
|
||||||
var result = template(hash);
|
goodbyes: [
|
||||||
|
{ text: 'goodbye' },
|
||||||
equal(result, "0. goodbye! 1. Goodbye! 2. GOODBYE! cruel world!", "The @index variable is used");
|
{ text: 'Goodbye' },
|
||||||
|
{ text: 'GOODBYE' }
|
||||||
|
],
|
||||||
|
world: 'world'
|
||||||
|
})
|
||||||
|
.withMessage('The @index variable is used')
|
||||||
|
.toCompileTo('0. goodbye! 1. Goodbye! 2. GOODBYE! cruel world!');
|
||||||
});
|
});
|
||||||
|
|
||||||
it("each with nested @index", function() {
|
it('each with nested @index', function() {
|
||||||
var string = "{{#each goodbyes}}{{@index}}. {{text}}! {{#each ../goodbyes}}{{@index}} {{/each}}After {{@index}} {{/each}}{{@index}}cruel {{world}}!";
|
expectTemplate(
|
||||||
var hash = {goodbyes: [{text: "goodbye"}, {text: "Goodbye"}, {text: "GOODBYE"}], world: "world"};
|
'{{#each goodbyes}}{{@index}}. {{text}}! {{#each ../goodbyes}}{{@index}} {{/each}}After {{@index}} {{/each}}{{@index}}cruel {{world}}!'
|
||||||
|
)
|
||||||
var template = CompilerContext.compile(string);
|
.withInput({
|
||||||
var result = template(hash);
|
goodbyes: [
|
||||||
|
{ text: 'goodbye' },
|
||||||
equal(result, "0. goodbye! 0 1 2 After 0 1. Goodbye! 0 1 2 After 1 2. GOODBYE! 0 1 2 After 2 cruel world!", "The @index variable is used");
|
{ text: 'Goodbye' },
|
||||||
|
{ text: 'GOODBYE' }
|
||||||
|
],
|
||||||
|
world: 'world'
|
||||||
|
})
|
||||||
|
.withMessage('The @index variable is used')
|
||||||
|
.toCompileTo(
|
||||||
|
'0. goodbye! 0 1 2 After 0 1. Goodbye! 0 1 2 After 1 2. GOODBYE! 0 1 2 After 2 cruel world!'
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("each object with @index", function() {
|
it('each with block params', function() {
|
||||||
var string = "{{#each goodbyes}}{{@index}}. {{text}}! {{/each}}cruel {{world}}!";
|
expectTemplate(
|
||||||
var hash = {goodbyes: {'a': {text: "goodbye"}, b: {text: "Goodbye"}, c: {text: "GOODBYE"}}, world: "world"};
|
'{{#each goodbyes as |value index|}}{{index}}. {{value.text}}! {{#each ../goodbyes as |childValue childIndex|}} {{index}} {{childIndex}}{{/each}} After {{index}} {{/each}}{{index}}cruel {{world}}!'
|
||||||
|
)
|
||||||
var template = CompilerContext.compile(string);
|
.withInput({
|
||||||
var result = template(hash);
|
goodbyes: [{ text: 'goodbye' }, { text: 'Goodbye' }],
|
||||||
|
world: 'world'
|
||||||
equal(result, "0. goodbye! 1. Goodbye! 2. GOODBYE! cruel world!", "The @index variable is used");
|
})
|
||||||
|
.toCompileTo(
|
||||||
|
'0. goodbye! 0 0 0 1 After 0 1. Goodbye! 1 0 1 1 After 1 cruel world!'
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('each with block params and strict compilation', function() {
|
||||||
it("each with @first", function() {
|
expectTemplate(
|
||||||
var string = "{{#each goodbyes}}{{#if @first}}{{text}}! {{/if}}{{/each}}cruel {{world}}!";
|
'{{#each goodbyes as |value index|}}{{index}}. {{value.text}}!{{/each}}'
|
||||||
var hash = {goodbyes: [{text: "goodbye"}, {text: "Goodbye"}, {text: "GOODBYE"}], world: "world"};
|
)
|
||||||
|
.withCompileOptions({ strict: true })
|
||||||
var template = CompilerContext.compile(string);
|
.withInput({ goodbyes: [{ text: 'goodbye' }, { text: 'Goodbye' }] })
|
||||||
var result = template(hash);
|
.toCompileTo('0. goodbye!1. Goodbye!');
|
||||||
|
|
||||||
equal(result, "goodbye! cruel world!", "The @first variable is used");
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("each with nested @first", function() {
|
it('each object with @index', function() {
|
||||||
var string = "{{#each goodbyes}}({{#if @first}}{{text}}! {{/if}}{{#each ../goodbyes}}{{#if @first}}{{text}}!{{/if}}{{/each}}{{#if @first}} {{text}}!{{/if}}) {{/each}}cruel {{world}}!";
|
expectTemplate(
|
||||||
var hash = {goodbyes: [{text: "goodbye"}, {text: "Goodbye"}, {text: "GOODBYE"}], world: "world"};
|
'{{#each goodbyes}}{{@index}}. {{text}}! {{/each}}cruel {{world}}!'
|
||||||
|
)
|
||||||
var template = CompilerContext.compile(string);
|
.withInput({
|
||||||
var result = template(hash);
|
goodbyes: {
|
||||||
|
a: { text: 'goodbye' },
|
||||||
equal(result, "(goodbye! goodbye! goodbye!) (goodbye!) (goodbye!) cruel world!", "The @first variable is used");
|
b: { text: 'Goodbye' },
|
||||||
|
c: { text: 'GOODBYE' }
|
||||||
|
},
|
||||||
|
world: 'world'
|
||||||
|
})
|
||||||
|
.withMessage('The @index variable is used')
|
||||||
|
.toCompileTo('0. goodbye! 1. Goodbye! 2. GOODBYE! cruel world!');
|
||||||
});
|
});
|
||||||
|
|
||||||
it("each object with @first", function() {
|
it('each with @first', function() {
|
||||||
var string = "{{#each goodbyes}}{{#if @first}}{{text}}! {{/if}}{{/each}}cruel {{world}}!";
|
expectTemplate(
|
||||||
var hash = {goodbyes: {'foo': {text: "goodbye"}, bar: {text: "Goodbye"}}, world: "world"};
|
'{{#each goodbyes}}{{#if @first}}{{text}}! {{/if}}{{/each}}cruel {{world}}!'
|
||||||
|
)
|
||||||
var template = CompilerContext.compile(string);
|
.withInput({
|
||||||
var result = template(hash);
|
goodbyes: [
|
||||||
|
{ text: 'goodbye' },
|
||||||
equal(result, "goodbye! cruel world!", "The @first variable is used");
|
{ text: 'Goodbye' },
|
||||||
|
{ text: 'GOODBYE' }
|
||||||
|
],
|
||||||
|
world: 'world'
|
||||||
|
})
|
||||||
|
.withMessage('The @first variable is used')
|
||||||
|
.toCompileTo('goodbye! cruel world!');
|
||||||
});
|
});
|
||||||
|
|
||||||
it("each with @last", function() {
|
it('each with nested @first', function() {
|
||||||
var string = "{{#each goodbyes}}{{#if @last}}{{text}}! {{/if}}{{/each}}cruel {{world}}!";
|
expectTemplate(
|
||||||
var hash = {goodbyes: [{text: "goodbye"}, {text: "Goodbye"}, {text: "GOODBYE"}], world: "world"};
|
'{{#each goodbyes}}({{#if @first}}{{text}}! {{/if}}{{#each ../goodbyes}}{{#if @first}}{{text}}!{{/if}}{{/each}}{{#if @first}} {{text}}!{{/if}}) {{/each}}cruel {{world}}!'
|
||||||
|
)
|
||||||
var template = CompilerContext.compile(string);
|
.withInput({
|
||||||
var result = template(hash);
|
goodbyes: [
|
||||||
|
{ text: 'goodbye' },
|
||||||
equal(result, "GOODBYE! cruel world!", "The @last variable is used");
|
{ text: 'Goodbye' },
|
||||||
|
{ text: 'GOODBYE' }
|
||||||
|
],
|
||||||
|
world: 'world'
|
||||||
|
})
|
||||||
|
.withMessage('The @first variable is used')
|
||||||
|
.toCompileTo(
|
||||||
|
'(goodbye! goodbye! goodbye!) (goodbye!) (goodbye!) cruel world!'
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("each with nested @last", function() {
|
it('each object with @first', function() {
|
||||||
var string = "{{#each goodbyes}}({{#if @last}}{{text}}! {{/if}}{{#each ../goodbyes}}{{#if @last}}{{text}}!{{/if}}{{/each}}{{#if @last}} {{text}}!{{/if}}) {{/each}}cruel {{world}}!";
|
expectTemplate(
|
||||||
var hash = {goodbyes: [{text: "goodbye"}, {text: "Goodbye"}, {text: "GOODBYE"}], world: "world"};
|
'{{#each goodbyes}}{{#if @first}}{{text}}! {{/if}}{{/each}}cruel {{world}}!'
|
||||||
|
)
|
||||||
var template = CompilerContext.compile(string);
|
.withInput({
|
||||||
var result = template(hash);
|
goodbyes: { foo: { text: 'goodbye' }, bar: { text: 'Goodbye' } },
|
||||||
|
world: 'world'
|
||||||
equal(result, "(GOODBYE!) (GOODBYE!) (GOODBYE! GOODBYE! GOODBYE!) cruel world!", "The @last variable is used");
|
})
|
||||||
|
.withMessage('The @first variable is used')
|
||||||
|
.toCompileTo('goodbye! cruel world!');
|
||||||
});
|
});
|
||||||
|
|
||||||
it("each with function argument", function() {
|
it('each with @last', function() {
|
||||||
var string = "{{#each goodbyes}}{{text}}! {{/each}}cruel {{world}}!";
|
expectTemplate(
|
||||||
var hash = {goodbyes: function () { return [{text: "goodbye"}, {text: "Goodbye"}, {text: "GOODBYE"}];}, world: "world"};
|
'{{#each goodbyes}}{{#if @last}}{{text}}! {{/if}}{{/each}}cruel {{world}}!'
|
||||||
shouldCompileTo(string, hash, "goodbye! Goodbye! GOODBYE! cruel world!",
|
)
|
||||||
"each with array function argument iterates over the contents when not empty");
|
.withInput({
|
||||||
shouldCompileTo(string, {goodbyes: [], world: "world"}, "cruel world!",
|
goodbyes: [
|
||||||
"each with array function argument ignores the contents when empty");
|
{ text: 'goodbye' },
|
||||||
|
{ text: 'Goodbye' },
|
||||||
|
{ text: 'GOODBYE' }
|
||||||
|
],
|
||||||
|
world: 'world'
|
||||||
|
})
|
||||||
|
.withMessage('The @last variable is used')
|
||||||
|
.toCompileTo('GOODBYE! cruel world!');
|
||||||
});
|
});
|
||||||
|
|
||||||
it("data passed to helpers", function() {
|
it('each object with @last', function() {
|
||||||
var string = "{{#each letters}}{{this}}{{detectDataInsideEach}}{{/each}}";
|
expectTemplate(
|
||||||
var hash = {letters: ['a', 'b', 'c']};
|
'{{#each goodbyes}}{{#if @last}}{{text}}! {{/if}}{{/each}}cruel {{world}}!'
|
||||||
|
)
|
||||||
|
.withInput({
|
||||||
|
goodbyes: { foo: { text: 'goodbye' }, bar: { text: 'Goodbye' } },
|
||||||
|
world: 'world'
|
||||||
|
})
|
||||||
|
.withMessage('The @last variable is used')
|
||||||
|
.toCompileTo('Goodbye! cruel world!');
|
||||||
|
});
|
||||||
|
|
||||||
var template = CompilerContext.compile(string);
|
it('each with nested @last', function() {
|
||||||
var result = template(hash, {
|
expectTemplate(
|
||||||
data: {
|
'{{#each goodbyes}}({{#if @last}}{{text}}! {{/if}}{{#each ../goodbyes}}{{#if @last}}{{text}}!{{/if}}{{/each}}{{#if @last}} {{text}}!{{/if}}) {{/each}}cruel {{world}}!'
|
||||||
exclaim: '!'
|
)
|
||||||
|
.withInput({
|
||||||
|
goodbyes: [
|
||||||
|
{ text: 'goodbye' },
|
||||||
|
{ text: 'Goodbye' },
|
||||||
|
{ text: 'GOODBYE' }
|
||||||
|
],
|
||||||
|
world: 'world'
|
||||||
|
})
|
||||||
|
.withMessage('The @last variable is used')
|
||||||
|
.toCompileTo(
|
||||||
|
'(GOODBYE!) (GOODBYE!) (GOODBYE! GOODBYE! GOODBYE!) cruel world!'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('each with function argument', function() {
|
||||||
|
var string = '{{#each goodbyes}}{{text}}! {{/each}}cruel {{world}}!';
|
||||||
|
|
||||||
|
expectTemplate(string)
|
||||||
|
.withInput({
|
||||||
|
goodbyes: function() {
|
||||||
|
return [
|
||||||
|
{ text: 'goodbye' },
|
||||||
|
{ text: 'Goodbye' },
|
||||||
|
{ text: 'GOODBYE' }
|
||||||
|
];
|
||||||
|
},
|
||||||
|
world: 'world'
|
||||||
|
})
|
||||||
|
.withMessage(
|
||||||
|
'each with array function argument iterates over the contents when not empty'
|
||||||
|
)
|
||||||
|
.toCompileTo('goodbye! Goodbye! GOODBYE! cruel world!');
|
||||||
|
|
||||||
|
expectTemplate(string)
|
||||||
|
.withInput({
|
||||||
|
goodbyes: [],
|
||||||
|
world: 'world'
|
||||||
|
})
|
||||||
|
.withMessage(
|
||||||
|
'each with array function argument ignores the contents when empty'
|
||||||
|
)
|
||||||
|
.toCompileTo('cruel world!');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('each object when last key is an empty string', function() {
|
||||||
|
expectTemplate(
|
||||||
|
'{{#each goodbyes}}{{@index}}. {{text}}! {{/each}}cruel {{world}}!'
|
||||||
|
)
|
||||||
|
.withInput({
|
||||||
|
goodbyes: {
|
||||||
|
a: { text: 'goodbye' },
|
||||||
|
b: { text: 'Goodbye' },
|
||||||
|
'': { text: 'GOODBYE' }
|
||||||
|
},
|
||||||
|
world: 'world'
|
||||||
|
})
|
||||||
|
.withMessage('Empty string key is not skipped')
|
||||||
|
.toCompileTo('0. goodbye! 1. Goodbye! 2. GOODBYE! cruel world!');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('data passed to helpers', function() {
|
||||||
|
expectTemplate(
|
||||||
|
'{{#each letters}}{{this}}{{detectDataInsideEach}}{{/each}}'
|
||||||
|
)
|
||||||
|
.withInput({ letters: ['a', 'b', 'c'] })
|
||||||
|
.withMessage('should output data')
|
||||||
|
.withRuntimeOptions({
|
||||||
|
data: {
|
||||||
|
exclaim: '!'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.toCompileTo('a!b!c!');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('each on implicit context', function() {
|
||||||
|
expectTemplate('{{#each}}{{text}}! {{/each}}cruel world!').toThrow(
|
||||||
|
handlebarsEnv.Exception,
|
||||||
|
'Must pass iterator to #each'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (global.Symbol && global.Symbol.iterator) {
|
||||||
|
it('each on iterable', function() {
|
||||||
|
function Iterator(arr) {
|
||||||
|
this.arr = arr;
|
||||||
|
this.index = 0;
|
||||||
}
|
}
|
||||||
});
|
Iterator.prototype.next = function() {
|
||||||
equal(result, 'a!b!c!', 'should output data');
|
var value = this.arr[this.index];
|
||||||
});
|
var done = this.index === this.arr.length;
|
||||||
|
if (!done) {
|
||||||
|
this.index++;
|
||||||
|
}
|
||||||
|
return { value: value, done: done };
|
||||||
|
};
|
||||||
|
function Iterable(arr) {
|
||||||
|
this.arr = arr;
|
||||||
|
}
|
||||||
|
Iterable.prototype[global.Symbol.iterator] = function() {
|
||||||
|
return new Iterator(this.arr);
|
||||||
|
};
|
||||||
|
var string = '{{#each goodbyes}}{{text}}! {{/each}}cruel {{world}}!';
|
||||||
|
|
||||||
it("each on implicit context", function() {
|
expectTemplate(string)
|
||||||
shouldThrow(function() {
|
.withInput({
|
||||||
var template = CompilerContext.compile("{{#each}}{{text}}! {{/each}}cruel world!");
|
goodbyes: new Iterable([
|
||||||
template({});
|
{ text: 'goodbye' },
|
||||||
}, handlebarsEnv.Exception, 'Must pass iterator to #each');
|
{ text: 'Goodbye' },
|
||||||
});
|
{ text: 'GOODBYE' }
|
||||||
|
]),
|
||||||
|
world: 'world'
|
||||||
|
})
|
||||||
|
.withMessage(
|
||||||
|
'each with array argument iterates over the contents when not empty'
|
||||||
|
)
|
||||||
|
.toCompileTo('goodbye! Goodbye! GOODBYE! cruel world!');
|
||||||
|
|
||||||
|
expectTemplate(string)
|
||||||
|
.withInput({
|
||||||
|
goodbyes: new Iterable([]),
|
||||||
|
world: 'world'
|
||||||
|
})
|
||||||
|
.withMessage(
|
||||||
|
'each with array argument ignores the contents when empty'
|
||||||
|
)
|
||||||
|
.toCompileTo('cruel world!');
|
||||||
|
});
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("#log", function() {
|
describe('#log', function() {
|
||||||
|
/* eslint-disable no-console */
|
||||||
if (typeof console === 'undefined') {
|
if (typeof console === 'undefined') {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var info,
|
var $log, $info, $error;
|
||||||
error;
|
|
||||||
beforeEach(function() {
|
beforeEach(function() {
|
||||||
info = console.info;
|
$log = console.log;
|
||||||
error = console.error;
|
$info = console.info;
|
||||||
|
$error = console.error;
|
||||||
});
|
});
|
||||||
afterEach(function() {
|
afterEach(function() {
|
||||||
console.info = info;
|
console.log = $log;
|
||||||
console.error = error;
|
console.info = $info;
|
||||||
|
console.error = $error;
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should call logger at default level', function() {
|
it('should call logger at default level', function() {
|
||||||
var string = "{{log blah}}";
|
|
||||||
var hash = { blah: "whee" };
|
|
||||||
|
|
||||||
var levelArg, logArg;
|
var levelArg, logArg;
|
||||||
handlebarsEnv.log = function(level, arg){
|
handlebarsEnv.log = function(level, arg) {
|
||||||
levelArg = level;
|
levelArg = level;
|
||||||
logArg = arg;
|
logArg = arg;
|
||||||
};
|
};
|
||||||
|
|
||||||
shouldCompileTo(string, hash, "", "log should not display");
|
expectTemplate('{{log blah}}')
|
||||||
equals(1, levelArg, "should call log with 1");
|
.withInput({ blah: 'whee' })
|
||||||
equals("whee", logArg, "should call log with 'whee'");
|
.withMessage('log should not display')
|
||||||
|
.toCompileTo('');
|
||||||
|
equals(1, levelArg, 'should call log with 1');
|
||||||
|
equals('whee', logArg, "should call log with 'whee'");
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should call logger at data level', function() {
|
it('should call logger at data level', function() {
|
||||||
var string = "{{log blah}}";
|
|
||||||
var hash = { blah: "whee" };
|
|
||||||
|
|
||||||
var levelArg, logArg;
|
var levelArg, logArg;
|
||||||
handlebarsEnv.log = function(level, arg){
|
handlebarsEnv.log = function(level, arg) {
|
||||||
levelArg = level;
|
levelArg = level;
|
||||||
logArg = arg;
|
logArg = arg;
|
||||||
};
|
};
|
||||||
|
|
||||||
shouldCompileTo(string, [hash,,,,{level: '03'}], "");
|
expectTemplate('{{log blah}}')
|
||||||
equals(3, levelArg);
|
.withInput({ blah: 'whee' })
|
||||||
equals("whee", logArg);
|
.withRuntimeOptions({ data: { level: '03' } })
|
||||||
|
.withCompileOptions({ data: true })
|
||||||
|
.toCompileTo('');
|
||||||
|
equals('03', levelArg);
|
||||||
|
equals('whee', logArg);
|
||||||
});
|
});
|
||||||
it('should not output to console', function() {
|
|
||||||
var string = "{{log blah}}";
|
|
||||||
var hash = { blah: "whee" };
|
|
||||||
|
|
||||||
console.info = function() {
|
it('should output to info', function() {
|
||||||
throw new Error();
|
var called;
|
||||||
|
|
||||||
|
console.info = function(info) {
|
||||||
|
equals('whee', info);
|
||||||
|
called = true;
|
||||||
|
console.info = $info;
|
||||||
|
console.log = $log;
|
||||||
|
};
|
||||||
|
console.log = function(log) {
|
||||||
|
equals('whee', log);
|
||||||
|
called = true;
|
||||||
|
console.info = $info;
|
||||||
|
console.log = $log;
|
||||||
};
|
};
|
||||||
|
|
||||||
shouldCompileTo(string, hash, "", "log should not display");
|
expectTemplate('{{log blah}}')
|
||||||
|
.withInput({ blah: 'whee' })
|
||||||
|
.toCompileTo('');
|
||||||
|
equals(true, called);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should log at data level', function() {
|
it('should log at data level', function() {
|
||||||
var string = "{{log blah}}";
|
|
||||||
var hash = { blah: "whee" };
|
|
||||||
var called;
|
var called;
|
||||||
|
|
||||||
console.error = function(log) {
|
console.error = function(log) {
|
||||||
equals("whee", log);
|
equals('whee', log);
|
||||||
|
called = true;
|
||||||
|
console.error = $error;
|
||||||
|
};
|
||||||
|
|
||||||
|
expectTemplate('{{log blah}}')
|
||||||
|
.withInput({ blah: 'whee' })
|
||||||
|
.withRuntimeOptions({ data: { level: '03' } })
|
||||||
|
.withCompileOptions({ data: true })
|
||||||
|
.toCompileTo('');
|
||||||
|
equals(true, called);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle missing logger', function() {
|
||||||
|
var called = false;
|
||||||
|
|
||||||
|
console.error = undefined;
|
||||||
|
console.log = function(log) {
|
||||||
|
equals('whee', log);
|
||||||
|
called = true;
|
||||||
|
console.log = $log;
|
||||||
|
};
|
||||||
|
|
||||||
|
expectTemplate('{{log blah}}')
|
||||||
|
.withInput({ blah: 'whee' })
|
||||||
|
.withRuntimeOptions({ data: { level: '03' } })
|
||||||
|
.withCompileOptions({ data: true })
|
||||||
|
.toCompileTo('');
|
||||||
|
equals(true, called);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle string log levels', function() {
|
||||||
|
var called;
|
||||||
|
|
||||||
|
console.error = function(log) {
|
||||||
|
equals('whee', log);
|
||||||
called = true;
|
called = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
shouldCompileTo(string, [hash,,,,{level: '03'}], "");
|
expectTemplate('{{log blah}}')
|
||||||
|
.withInput({ blah: 'whee' })
|
||||||
|
.withRuntimeOptions({ data: { level: 'error' } })
|
||||||
|
.withCompileOptions({ data: true })
|
||||||
|
.toCompileTo('');
|
||||||
|
equals(true, called);
|
||||||
|
|
||||||
|
called = false;
|
||||||
|
|
||||||
|
expectTemplate('{{log blah}}')
|
||||||
|
.withInput({ blah: 'whee' })
|
||||||
|
.withRuntimeOptions({ data: { level: 'ERROR' } })
|
||||||
|
.withCompileOptions({ data: true })
|
||||||
|
.toCompileTo('');
|
||||||
equals(true, called);
|
equals(true, called);
|
||||||
});
|
});
|
||||||
it('should handle missing logger', function() {
|
|
||||||
var string = "{{log blah}}";
|
|
||||||
var hash = { blah: "whee" };
|
|
||||||
|
|
||||||
console.error = undefined;
|
it('should handle hash log levels', function() {
|
||||||
|
var called;
|
||||||
|
|
||||||
shouldCompileTo(string, [hash,,,,{level: '03'}], "");
|
console.error = function(log) {
|
||||||
|
equals('whee', log);
|
||||||
|
called = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
expectTemplate('{{log blah level="error"}}')
|
||||||
|
.withInput({ blah: 'whee' })
|
||||||
|
.toCompileTo('');
|
||||||
|
equals(true, called);
|
||||||
});
|
});
|
||||||
});
|
|
||||||
|
|
||||||
|
it('should handle hash log levels', function() {
|
||||||
|
var called = false;
|
||||||
|
|
||||||
|
console.info = console.log = console.error = console.debug = function() {
|
||||||
|
called = true;
|
||||||
|
console.info = console.log = console.error = console.debug = $log;
|
||||||
|
};
|
||||||
|
|
||||||
|
expectTemplate('{{log blah level="debug"}}')
|
||||||
|
.withInput({ blah: 'whee' })
|
||||||
|
.toCompileTo('');
|
||||||
|
equals(false, called);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should pass multiple log arguments', function() {
|
||||||
|
var called;
|
||||||
|
|
||||||
|
console.info = console.log = function(log1, log2, log3) {
|
||||||
|
equals('whee', log1);
|
||||||
|
equals('foo', log2);
|
||||||
|
equals(1, log3);
|
||||||
|
called = true;
|
||||||
|
console.log = $log;
|
||||||
|
};
|
||||||
|
|
||||||
|
expectTemplate('{{log blah "foo" 1}}')
|
||||||
|
.withInput({ blah: 'whee' })
|
||||||
|
.toCompileTo('');
|
||||||
|
equals(true, called);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should pass zero log arguments', function() {
|
||||||
|
var called;
|
||||||
|
|
||||||
|
console.info = console.log = function() {
|
||||||
|
expect(arguments.length).to.equal(0);
|
||||||
|
called = true;
|
||||||
|
console.log = $log;
|
||||||
|
};
|
||||||
|
|
||||||
|
expectTemplate('{{log}}')
|
||||||
|
.withInput({ blah: 'whee' })
|
||||||
|
.toCompileTo('');
|
||||||
|
expect(called).to.be.true();
|
||||||
|
});
|
||||||
|
/* eslint-enable no-console */
|
||||||
|
});
|
||||||
|
|
||||||
describe('#lookup', function() {
|
describe('#lookup', function() {
|
||||||
it('should lookup arbitrary content', function() {
|
it('should lookup arbitrary content', function() {
|
||||||
var string = '{{#each goodbyes}}{{lookup ../data .}}{{/each}}',
|
expectTemplate('{{#each goodbyes}}{{lookup ../data .}}{{/each}}')
|
||||||
hash = {goodbyes: [0, 1], data: ['foo', 'bar']};
|
.withInput({ goodbyes: [0, 1], data: ['foo', 'bar'] })
|
||||||
|
.toCompileTo('foobar');
|
||||||
var template = CompilerContext.compile(string);
|
|
||||||
var result = template(hash);
|
|
||||||
|
|
||||||
equal(result, 'foobar');
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should not fail on undefined value', function() {
|
it('should not fail on undefined value', function() {
|
||||||
var string = '{{#each goodbyes}}{{lookup ../bar .}}{{/each}}',
|
expectTemplate('{{#each goodbyes}}{{lookup ../bar .}}{{/each}}')
|
||||||
hash = {goodbyes: [0, 1], data: ['foo', 'bar']};
|
.withInput({ goodbyes: [0, 1], data: ['foo', 'bar'] })
|
||||||
|
.toCompileTo('');
|
||||||
var template = CompilerContext.compile(string);
|
|
||||||
var result = template(hash);
|
|
||||||
|
|
||||||
equal(result, '');
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
+143
-25
@@ -1,5 +1,3 @@
|
|||||||
/*global Handlebars, shouldThrow */
|
|
||||||
|
|
||||||
describe('compiler', function() {
|
describe('compiler', function() {
|
||||||
if (!Handlebars.compile) {
|
if (!Handlebars.compile) {
|
||||||
return;
|
return;
|
||||||
@@ -15,55 +13,175 @@ describe('compiler', function() {
|
|||||||
equal(compile('foo').equals(compile('foo')), true);
|
equal(compile('foo').equals(compile('foo')), true);
|
||||||
equal(compile('{{foo}}').equals(compile('{{foo}}')), true);
|
equal(compile('{{foo}}').equals(compile('{{foo}}')), true);
|
||||||
equal(compile('{{foo.bar}}').equals(compile('{{foo.bar}}')), true);
|
equal(compile('{{foo.bar}}').equals(compile('{{foo.bar}}')), true);
|
||||||
equal(compile('{{foo.bar baz "foo" true false bat=1}}').equals(compile('{{foo.bar baz "foo" true false bat=1}}')), true);
|
equal(
|
||||||
equal(compile('{{foo.bar (baz bat=1)}}').equals(compile('{{foo.bar (baz bat=1)}}')), true);
|
compile('{{foo.bar baz "foo" true false bat=1}}').equals(
|
||||||
equal(compile('{{#foo}} {{/foo}}').equals(compile('{{#foo}} {{/foo}}')), true);
|
compile('{{foo.bar baz "foo" true false bat=1}}')
|
||||||
|
),
|
||||||
|
true
|
||||||
|
);
|
||||||
|
equal(
|
||||||
|
compile('{{foo.bar (baz bat=1)}}').equals(
|
||||||
|
compile('{{foo.bar (baz bat=1)}}')
|
||||||
|
),
|
||||||
|
true
|
||||||
|
);
|
||||||
|
equal(
|
||||||
|
compile('{{#foo}} {{/foo}}').equals(compile('{{#foo}} {{/foo}}')),
|
||||||
|
true
|
||||||
|
);
|
||||||
});
|
});
|
||||||
it('should treat as not equal', function() {
|
it('should treat as not equal', function() {
|
||||||
equal(compile('foo').equals(compile('bar')), false);
|
equal(compile('foo').equals(compile('bar')), false);
|
||||||
equal(compile('{{foo}}').equals(compile('{{bar}}')), false);
|
equal(compile('{{foo}}').equals(compile('{{bar}}')), false);
|
||||||
equal(compile('{{foo.bar}}').equals(compile('{{bar.bar}}')), false);
|
equal(compile('{{foo.bar}}').equals(compile('{{bar.bar}}')), false);
|
||||||
equal(compile('{{foo.bar baz bat=1}}').equals(compile('{{foo.bar bar bat=1}}')), false);
|
equal(
|
||||||
equal(compile('{{foo.bar (baz bat=1)}}').equals(compile('{{foo.bar (bar bat=1)}}')), false);
|
compile('{{foo.bar baz bat=1}}').equals(
|
||||||
equal(compile('{{#foo}} {{/foo}}').equals(compile('{{#bar}} {{/bar}}')), false);
|
compile('{{foo.bar bar bat=1}}')
|
||||||
equal(compile('{{#foo}} {{/foo}}').equals(compile('{{#foo}} {{foo}}{{/foo}}')), false);
|
),
|
||||||
|
false
|
||||||
|
);
|
||||||
|
equal(
|
||||||
|
compile('{{foo.bar (baz bat=1)}}').equals(
|
||||||
|
compile('{{foo.bar (bar bat=1)}}')
|
||||||
|
),
|
||||||
|
false
|
||||||
|
);
|
||||||
|
equal(
|
||||||
|
compile('{{#foo}} {{/foo}}').equals(compile('{{#bar}} {{/bar}}')),
|
||||||
|
false
|
||||||
|
);
|
||||||
|
equal(
|
||||||
|
compile('{{#foo}} {{/foo}}').equals(
|
||||||
|
compile('{{#foo}} {{foo}}{{/foo}}')
|
||||||
|
),
|
||||||
|
false
|
||||||
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('#compile', function() {
|
describe('#compile', function() {
|
||||||
it('should fail with invalid input', function() {
|
it('should fail with invalid input', function() {
|
||||||
shouldThrow(function() {
|
shouldThrow(
|
||||||
Handlebars.compile(null);
|
function() {
|
||||||
}, Error, 'You must pass a string or Handlebars AST to Handlebars.compile. You passed null');
|
Handlebars.compile(null);
|
||||||
shouldThrow(function() {
|
},
|
||||||
Handlebars.compile({});
|
Error,
|
||||||
}, Error, 'You must pass a string or Handlebars AST to Handlebars.compile. You passed [object Object]');
|
'You must pass a string or Handlebars AST to Handlebars.compile. You passed null'
|
||||||
|
);
|
||||||
|
shouldThrow(
|
||||||
|
function() {
|
||||||
|
Handlebars.compile({});
|
||||||
|
},
|
||||||
|
Error,
|
||||||
|
'You must pass a string or Handlebars AST to Handlebars.compile. You passed [object Object]'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should include the location in the error (row and column)', function() {
|
||||||
|
try {
|
||||||
|
Handlebars.compile(' \n {{#if}}\n{{/def}}')();
|
||||||
|
equal(
|
||||||
|
true,
|
||||||
|
false,
|
||||||
|
'Statement must throw exception. This line should not be executed.'
|
||||||
|
);
|
||||||
|
} catch (err) {
|
||||||
|
equal(
|
||||||
|
err.message,
|
||||||
|
"if doesn't match def - 2:5",
|
||||||
|
'Checking error message'
|
||||||
|
);
|
||||||
|
if (Object.getOwnPropertyDescriptor(err, 'column').writable) {
|
||||||
|
// In Safari 8, the column-property is read-only. This means that even if it is set with defineProperty,
|
||||||
|
// its value won't change (https://github.com/jquery/esprima/issues/1290#issuecomment-132455482)
|
||||||
|
// Since this was neither working in Handlebars 3 nor in 4.0.5, we only check the column for other browsers.
|
||||||
|
equal(err.column, 5, 'Checking error column');
|
||||||
|
}
|
||||||
|
equal(err.lineNumber, 2, 'Checking error row');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should include the location as enumerable property', function() {
|
||||||
|
try {
|
||||||
|
Handlebars.compile(' \n {{#if}}\n{{/def}}')();
|
||||||
|
equal(
|
||||||
|
true,
|
||||||
|
false,
|
||||||
|
'Statement must throw exception. This line should not be executed.'
|
||||||
|
);
|
||||||
|
} catch (err) {
|
||||||
|
equal(
|
||||||
|
Object.prototype.propertyIsEnumerable.call(err, 'column'),
|
||||||
|
true,
|
||||||
|
'Checking error column'
|
||||||
|
);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
it('can utilize AST instance', function() {
|
it('can utilize AST instance', function() {
|
||||||
equal(Handlebars.compile(new Handlebars.AST.ProgramNode([ new Handlebars.AST.ContentNode("Hello")], {}))(), 'Hello');
|
equal(
|
||||||
|
Handlebars.compile({
|
||||||
|
type: 'Program',
|
||||||
|
body: [{ type: 'ContentStatement', value: 'Hello' }]
|
||||||
|
})(),
|
||||||
|
'Hello'
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("can pass through an empty string", function() {
|
it('can pass through an empty string', function() {
|
||||||
equal(Handlebars.compile('')(), '');
|
equal(Handlebars.compile('')(), '');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should not modify the options.data property(GH-1327)', function() {
|
||||||
|
var options = { data: [{ a: 'foo' }, { a: 'bar' }] };
|
||||||
|
Handlebars.compile('{{#each data}}{{@index}}:{{a}} {{/each}}', options)();
|
||||||
|
equal(
|
||||||
|
JSON.stringify(options, 0, 2),
|
||||||
|
JSON.stringify({ data: [{ a: 'foo' }, { a: 'bar' }] }, 0, 2)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not modify the options.knownHelpers property(GH-1327)', function() {
|
||||||
|
var options = { knownHelpers: {} };
|
||||||
|
Handlebars.compile('{{#each data}}{{@index}}:{{a}} {{/each}}', options)();
|
||||||
|
equal(
|
||||||
|
JSON.stringify(options, 0, 2),
|
||||||
|
JSON.stringify({ knownHelpers: {} }, 0, 2)
|
||||||
|
);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('#precompile', function() {
|
describe('#precompile', function() {
|
||||||
it('should fail with invalid input', function() {
|
it('should fail with invalid input', function() {
|
||||||
shouldThrow(function() {
|
shouldThrow(
|
||||||
Handlebars.precompile(null);
|
function() {
|
||||||
}, Error, 'You must pass a string or Handlebars AST to Handlebars.precompile. You passed null');
|
Handlebars.precompile(null);
|
||||||
shouldThrow(function() {
|
},
|
||||||
Handlebars.precompile({});
|
Error,
|
||||||
}, Error, 'You must pass a string or Handlebars AST to Handlebars.precompile. You passed [object Object]');
|
'You must pass a string or Handlebars AST to Handlebars.precompile. You passed null'
|
||||||
|
);
|
||||||
|
shouldThrow(
|
||||||
|
function() {
|
||||||
|
Handlebars.precompile({});
|
||||||
|
},
|
||||||
|
Error,
|
||||||
|
'You must pass a string or Handlebars AST to Handlebars.precompile. You passed [object Object]'
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('can utilize AST instance', function() {
|
it('can utilize AST instance', function() {
|
||||||
equal(/return "Hello"/.test(Handlebars.precompile(new Handlebars.AST.ProgramNode([ new Handlebars.AST.ContentNode("Hello")]), {})), true);
|
equal(
|
||||||
|
/return "Hello"/.test(
|
||||||
|
Handlebars.precompile({
|
||||||
|
type: 'Program',
|
||||||
|
body: [{ type: 'ContentStatement', value: 'Hello' }]
|
||||||
|
})
|
||||||
|
),
|
||||||
|
true
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("can pass through an empty string", function() {
|
it('can pass through an empty string', function() {
|
||||||
equal(/return ""/.test(Handlebars.precompile('')), true);
|
equal(/return ""/.test(Handlebars.precompile('')), true);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
+217
-196
@@ -1,257 +1,278 @@
|
|||||||
/*global CompilerContext, Handlebars, handlebarsEnv, shouldThrow */
|
|
||||||
describe('data', function() {
|
describe('data', function() {
|
||||||
it("passing in data to a compiled function that expects data - works with helpers", function() {
|
it('passing in data to a compiled function that expects data - works with helpers', function() {
|
||||||
var template = CompilerContext.compile("{{hello}}", {data: true});
|
expectTemplate('{{hello}}')
|
||||||
|
.withCompileOptions({ data: true })
|
||||||
var helpers = {
|
.withHelper('hello', function(options) {
|
||||||
hello: function(options) {
|
return options.data.adjective + ' ' + this.noun;
|
||||||
return options.data.adjective + " " + this.noun;
|
})
|
||||||
}
|
.withRuntimeOptions({ data: { adjective: 'happy' } })
|
||||||
};
|
.withInput({ noun: 'cat' })
|
||||||
|
.withMessage('Data output by helper')
|
||||||
var result = template({noun: "cat"}, {helpers: helpers, data: {adjective: "happy"}});
|
.toCompileTo('happy cat');
|
||||||
equals("happy cat", result, "Data output by helper");
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("data can be looked up via @foo", function() {
|
it('data can be looked up via @foo', function() {
|
||||||
var template = CompilerContext.compile("{{@hello}}");
|
expectTemplate('{{@hello}}')
|
||||||
var result = template({}, { data: { hello: "hello" } });
|
.withRuntimeOptions({ data: { hello: 'hello' } })
|
||||||
equals("hello", result, "@foo retrieves template data");
|
.withMessage('@foo retrieves template data')
|
||||||
|
.toCompileTo('hello');
|
||||||
});
|
});
|
||||||
|
|
||||||
it("deep @foo triggers automatic top-level data", function() {
|
it('deep @foo triggers automatic top-level data', function() {
|
||||||
var template = CompilerContext.compile('{{#let world="world"}}{{#if foo}}{{#if foo}}Hello {{@world}}{{/if}}{{/if}}{{/let}}');
|
|
||||||
|
|
||||||
var helpers = Handlebars.createFrame(handlebarsEnv.helpers);
|
var helpers = Handlebars.createFrame(handlebarsEnv.helpers);
|
||||||
|
|
||||||
helpers.let = function(options) {
|
helpers.let = function(options) {
|
||||||
var frame = Handlebars.createFrame(options.data);
|
var frame = Handlebars.createFrame(options.data);
|
||||||
|
|
||||||
for (var prop in options.hash) {
|
for (var prop in options.hash) {
|
||||||
frame[prop] = options.hash[prop];
|
if (prop in options.hash) {
|
||||||
|
frame[prop] = options.hash[prop];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return options.fn(this, { data: frame });
|
return options.fn(this, { data: frame });
|
||||||
};
|
};
|
||||||
|
|
||||||
var result = template({ foo: true }, { helpers: helpers });
|
expectTemplate(
|
||||||
equals("Hello world", result, "Automatic data was triggered");
|
'{{#let world="world"}}{{#if foo}}{{#if foo}}Hello {{@world}}{{/if}}{{/if}}{{/let}}'
|
||||||
|
)
|
||||||
|
.withInput({ foo: true })
|
||||||
|
.withHelpers(helpers)
|
||||||
|
.withMessage('Automatic data was triggered')
|
||||||
|
.toCompileTo('Hello world');
|
||||||
});
|
});
|
||||||
|
|
||||||
it("parameter data can be looked up via @foo", function() {
|
it('parameter data can be looked up via @foo', function() {
|
||||||
var template = CompilerContext.compile("{{hello @world}}");
|
expectTemplate('{{hello @world}}')
|
||||||
var helpers = {
|
.withRuntimeOptions({ data: { world: 'world' } })
|
||||||
hello: function(noun) {
|
.withHelper('hello', function(noun) {
|
||||||
return "Hello " + noun;
|
return 'Hello ' + noun;
|
||||||
}
|
})
|
||||||
};
|
.withMessage('@foo as a parameter retrieves template data')
|
||||||
|
.toCompileTo('Hello world');
|
||||||
var result = template({}, { helpers: helpers, data: { world: "world" } });
|
|
||||||
equals("Hello world", result, "@foo as a parameter retrieves template data");
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("hash values can be looked up via @foo", function() {
|
it('hash values can be looked up via @foo', function() {
|
||||||
var template = CompilerContext.compile("{{hello noun=@world}}");
|
expectTemplate('{{hello noun=@world}}')
|
||||||
var helpers = {
|
.withRuntimeOptions({ data: { world: 'world' } })
|
||||||
hello: function(options) {
|
.withHelper('hello', function(options) {
|
||||||
return "Hello " + options.hash.noun;
|
return 'Hello ' + options.hash.noun;
|
||||||
}
|
})
|
||||||
};
|
.withMessage('@foo as a parameter retrieves template data')
|
||||||
|
.toCompileTo('Hello world');
|
||||||
var result = template({}, { helpers: helpers, data: { world: "world" } });
|
|
||||||
equals("Hello world", result, "@foo as a parameter retrieves template data");
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("nested parameter data can be looked up via @foo.bar", function() {
|
it('nested parameter data can be looked up via @foo.bar', function() {
|
||||||
var template = CompilerContext.compile("{{hello @world.bar}}");
|
expectTemplate('{{hello @world.bar}}')
|
||||||
var helpers = {
|
.withRuntimeOptions({ data: { world: { bar: 'world' } } })
|
||||||
hello: function(noun) {
|
.withHelper('hello', function(noun) {
|
||||||
return "Hello " + noun;
|
return 'Hello ' + noun;
|
||||||
}
|
})
|
||||||
};
|
.withMessage('@foo as a parameter retrieves template data')
|
||||||
|
.toCompileTo('Hello world');
|
||||||
var result = template({}, { helpers: helpers, data: { world: {bar: "world" } } });
|
|
||||||
equals("Hello world", result, "@foo as a parameter retrieves template data");
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("nested parameter data does not fail with @world.bar", function() {
|
it('nested parameter data does not fail with @world.bar', function() {
|
||||||
var template = CompilerContext.compile("{{hello @world.bar}}");
|
expectTemplate('{{hello @world.bar}}')
|
||||||
var helpers = {
|
.withRuntimeOptions({ data: { foo: { bar: 'world' } } })
|
||||||
hello: function(noun) {
|
.withHelper('hello', function(noun) {
|
||||||
return "Hello " + noun;
|
return 'Hello ' + noun;
|
||||||
}
|
})
|
||||||
};
|
.withMessage('@foo as a parameter retrieves template data')
|
||||||
|
.toCompileTo('Hello undefined');
|
||||||
var result = template({}, { helpers: helpers, data: { foo: {bar: "world" } } });
|
|
||||||
equals("Hello undefined", result, "@foo as a parameter retrieves template data");
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("parameter data throws when using complex scope references", function() {
|
it('parameter data throws when using complex scope references', function() {
|
||||||
var string = "{{#goodbyes}}{{text}} cruel {{@foo/../name}}! {{/goodbyes}}";
|
expectTemplate(
|
||||||
|
'{{#goodbyes}}{{text}} cruel {{@foo/../name}}! {{/goodbyes}}'
|
||||||
shouldThrow(function() {
|
).toThrow(Error);
|
||||||
CompilerContext.compile(string);
|
|
||||||
}, Error);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("data is inherited downstream", function() {
|
it('data can be functions', function() {
|
||||||
var template = CompilerContext.compile("{{#let foo=1 bar=2}}{{#let foo=bar.baz}}{{@bar}}{{@foo}}{{/let}}{{@foo}}{{/let}}", { data: true });
|
expectTemplate('{{@hello}}')
|
||||||
var helpers = {
|
.withRuntimeOptions({
|
||||||
let: function(options) {
|
data: {
|
||||||
|
hello: function() {
|
||||||
|
return 'hello';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.toCompileTo('hello');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('data can be functions with params', function() {
|
||||||
|
expectTemplate('{{@hello "hello"}}')
|
||||||
|
.withRuntimeOptions({
|
||||||
|
data: {
|
||||||
|
hello: function(arg) {
|
||||||
|
return arg;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.toCompileTo('hello');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('data is inherited downstream', function() {
|
||||||
|
expectTemplate(
|
||||||
|
'{{#let foo=1 bar=2}}{{#let foo=bar.baz}}{{@bar}}{{@foo}}{{/let}}{{@foo}}{{/let}}'
|
||||||
|
)
|
||||||
|
.withInput({ bar: { baz: 'hello world' } })
|
||||||
|
.withCompileOptions({ data: true })
|
||||||
|
.withHelper('let', function(options) {
|
||||||
var frame = Handlebars.createFrame(options.data);
|
var frame = Handlebars.createFrame(options.data);
|
||||||
for (var prop in options.hash) {
|
for (var prop in options.hash) {
|
||||||
frame[prop] = options.hash[prop];
|
if (prop in options.hash) {
|
||||||
|
frame[prop] = options.hash[prop];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return options.fn(this, {data: frame});
|
return options.fn(this, { data: frame });
|
||||||
}
|
})
|
||||||
};
|
.withRuntimeOptions({ data: {} })
|
||||||
|
.withMessage('data variables are inherited downstream')
|
||||||
var result = template({ bar: { baz: "hello world" } }, { helpers: helpers, data: {} });
|
.toCompileTo('2hello world1');
|
||||||
equals("2hello world1", result, "data variables are inherited downstream");
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("passing in data to a compiled function that expects data - works with helpers in partials", function() {
|
it('passing in data to a compiled function that expects data - works with helpers in partials', function() {
|
||||||
var template = CompilerContext.compile("{{>my_partial}}", {data: true});
|
expectTemplate('{{>myPartial}}')
|
||||||
|
.withCompileOptions({ data: true })
|
||||||
var partials = {
|
.withPartial('myPartial', '{{hello}}')
|
||||||
my_partial: CompilerContext.compile("{{hello}}", {data: true})
|
.withHelper('hello', function(options) {
|
||||||
};
|
return options.data.adjective + ' ' + this.noun;
|
||||||
|
})
|
||||||
var helpers = {
|
.withInput({ noun: 'cat' })
|
||||||
hello: function(options) {
|
.withRuntimeOptions({ data: { adjective: 'happy' } })
|
||||||
return options.data.adjective + " " + this.noun;
|
.withMessage('Data output by helper inside partial')
|
||||||
}
|
.toCompileTo('happy cat');
|
||||||
};
|
|
||||||
|
|
||||||
var result = template({noun: "cat"}, {helpers: helpers, partials: partials, data: {adjective: "happy"}});
|
|
||||||
equals("happy cat", result, "Data output by helper inside partial");
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("passing in data to a compiled function that expects data - works with helpers and parameters", function() {
|
it('passing in data to a compiled function that expects data - works with helpers and parameters', function() {
|
||||||
var template = CompilerContext.compile("{{hello world}}", {data: true});
|
expectTemplate('{{hello world}}')
|
||||||
|
.withCompileOptions({ data: true })
|
||||||
var helpers = {
|
.withHelper('hello', function(noun, options) {
|
||||||
hello: function(noun, options) {
|
return options.data.adjective + ' ' + noun + (this.exclaim ? '!' : '');
|
||||||
return options.data.adjective + " " + noun + (this.exclaim ? "!" : "");
|
})
|
||||||
}
|
.withInput({ exclaim: true, world: 'world' })
|
||||||
};
|
.withRuntimeOptions({ data: { adjective: 'happy' } })
|
||||||
|
.withMessage('Data output by helper')
|
||||||
var result = template({exclaim: true, world: "world"}, {helpers: helpers, data: {adjective: "happy"}});
|
.toCompileTo('happy world!');
|
||||||
equals("happy world!", result, "Data output by helper");
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("passing in data to a compiled function that expects data - works with block helpers", function() {
|
it('passing in data to a compiled function that expects data - works with block helpers', function() {
|
||||||
var template = CompilerContext.compile("{{#hello}}{{world}}{{/hello}}", {data: true});
|
expectTemplate('{{#hello}}{{world}}{{/hello}}')
|
||||||
|
.withCompileOptions({
|
||||||
var helpers = {
|
data: true
|
||||||
hello: function(options) {
|
})
|
||||||
|
.withHelper('hello', function(options) {
|
||||||
return options.fn(this);
|
return options.fn(this);
|
||||||
},
|
})
|
||||||
world: function(options) {
|
.withHelper('world', function(options) {
|
||||||
return options.data.adjective + " world" + (this.exclaim ? "!" : "");
|
return options.data.adjective + ' world' + (this.exclaim ? '!' : '');
|
||||||
}
|
})
|
||||||
};
|
.withInput({ exclaim: true })
|
||||||
|
.withRuntimeOptions({ data: { adjective: 'happy' } })
|
||||||
var result = template({exclaim: true}, {helpers: helpers, data: {adjective: "happy"}});
|
.withMessage('Data output by helper')
|
||||||
equals("happy world!", result, "Data output by helper");
|
.toCompileTo('happy world!');
|
||||||
});
|
});
|
||||||
|
|
||||||
it("passing in data to a compiled function that expects data - works with block helpers that use ..", function() {
|
it('passing in data to a compiled function that expects data - works with block helpers that use ..', function() {
|
||||||
var template = CompilerContext.compile("{{#hello}}{{world ../zomg}}{{/hello}}", {data: true});
|
expectTemplate('{{#hello}}{{world ../zomg}}{{/hello}}')
|
||||||
|
.withCompileOptions({ data: true })
|
||||||
var helpers = {
|
.withHelper('hello', function(options) {
|
||||||
hello: function(options) {
|
return options.fn({ exclaim: '?' });
|
||||||
return options.fn({exclaim: "?"});
|
})
|
||||||
},
|
.withHelper('world', function(thing, options) {
|
||||||
world: function(thing, options) {
|
return options.data.adjective + ' ' + thing + (this.exclaim || '');
|
||||||
return options.data.adjective + " " + thing + (this.exclaim || "");
|
})
|
||||||
}
|
.withInput({ exclaim: true, zomg: 'world' })
|
||||||
};
|
.withRuntimeOptions({ data: { adjective: 'happy' } })
|
||||||
|
.withMessage('Data output by helper')
|
||||||
var result = template({exclaim: true, zomg: "world"}, {helpers: helpers, data: {adjective: "happy"}});
|
.toCompileTo('happy world?');
|
||||||
equals("happy world?", result, "Data output by helper");
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("passing in data to a compiled function that expects data - data is passed to with block helpers where children use ..", function() {
|
it('passing in data to a compiled function that expects data - data is passed to with block helpers where children use ..', function() {
|
||||||
var template = CompilerContext.compile("{{#hello}}{{world ../zomg}}{{/hello}}", {data: true});
|
expectTemplate('{{#hello}}{{world ../zomg}}{{/hello}}')
|
||||||
|
.withCompileOptions({ data: true })
|
||||||
var helpers = {
|
.withHelper('hello', function(options) {
|
||||||
hello: function(options) {
|
return options.data.accessData + ' ' + options.fn({ exclaim: '?' });
|
||||||
return options.data.accessData + " " + options.fn({exclaim: "?"});
|
})
|
||||||
},
|
.withHelper('world', function(thing, options) {
|
||||||
world: function(thing, options) {
|
return options.data.adjective + ' ' + thing + (this.exclaim || '');
|
||||||
return options.data.adjective + " " + thing + (this.exclaim || "");
|
})
|
||||||
}
|
.withInput({ exclaim: true, zomg: 'world' })
|
||||||
};
|
.withRuntimeOptions({ data: { adjective: 'happy', accessData: '#win' } })
|
||||||
|
.withMessage('Data output by helper')
|
||||||
var result = template({exclaim: true, zomg: "world"}, {helpers: helpers, data: {adjective: "happy", accessData: "#win"}});
|
.toCompileTo('#win happy world?');
|
||||||
equals("#win happy world?", result, "Data output by helper");
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("you can override inherited data when invoking a helper", function() {
|
it('you can override inherited data when invoking a helper', function() {
|
||||||
var template = CompilerContext.compile("{{#hello}}{{world zomg}}{{/hello}}", {data: true});
|
expectTemplate('{{#hello}}{{world zomg}}{{/hello}}')
|
||||||
|
.withCompileOptions({ data: true })
|
||||||
var helpers = {
|
.withHelper('hello', function(options) {
|
||||||
hello: function(options) {
|
return options.fn(
|
||||||
return options.fn({exclaim: "?", zomg: "world"}, { data: {adjective: "sad"} });
|
{ exclaim: '?', zomg: 'world' },
|
||||||
},
|
{ data: { adjective: 'sad' } }
|
||||||
world: function(thing, options) {
|
);
|
||||||
return options.data.adjective + " " + thing + (this.exclaim || "");
|
})
|
||||||
}
|
.withHelper('world', function(thing, options) {
|
||||||
};
|
return options.data.adjective + ' ' + thing + (this.exclaim || '');
|
||||||
|
})
|
||||||
var result = template({exclaim: true, zomg: "planet"}, {helpers: helpers, data: {adjective: "happy"}});
|
.withInput({ exclaim: true, zomg: 'planet' })
|
||||||
equals("sad world?", result, "Overriden data output by helper");
|
.withRuntimeOptions({ data: { adjective: 'happy' } })
|
||||||
|
.withMessage('Overriden data output by helper')
|
||||||
|
.toCompileTo('sad world?');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('you can override inherited data when invoking a helper with depth', function() {
|
||||||
it("you can override inherited data when invoking a helper with depth", function() {
|
expectTemplate('{{#hello}}{{world ../zomg}}{{/hello}}')
|
||||||
var template = CompilerContext.compile("{{#hello}}{{world ../zomg}}{{/hello}}", {data: true});
|
.withCompileOptions({ data: true })
|
||||||
|
.withHelper('hello', function(options) {
|
||||||
var helpers = {
|
return options.fn({ exclaim: '?' }, { data: { adjective: 'sad' } });
|
||||||
hello: function(options) {
|
})
|
||||||
return options.fn({exclaim: "?"}, { data: {adjective: "sad"} });
|
.withHelper('world', function(thing, options) {
|
||||||
},
|
return options.data.adjective + ' ' + thing + (this.exclaim || '');
|
||||||
world: function(thing, options) {
|
})
|
||||||
return options.data.adjective + " " + thing + (this.exclaim || "");
|
.withInput({ exclaim: true, zomg: 'world' })
|
||||||
}
|
.withRuntimeOptions({ data: { adjective: 'happy' } })
|
||||||
};
|
.withMessage('Overriden data output by helper')
|
||||||
|
.toCompileTo('sad world?');
|
||||||
var result = template({exclaim: true, zomg: "world"}, {helpers: helpers, data: {adjective: "happy"}});
|
|
||||||
equals("sad world?", result, "Overriden data output by helper");
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('@root', function() {
|
describe('@root', function() {
|
||||||
it('the root context can be looked up via @root', function() {
|
it('the root context can be looked up via @root', function() {
|
||||||
var template = CompilerContext.compile('{{@root.foo}}');
|
expectTemplate('{{@root.foo}}')
|
||||||
var result = template({foo: 'hello'}, { data: {} });
|
.withInput({ foo: 'hello' })
|
||||||
equals('hello', result);
|
.withRuntimeOptions({ data: {} })
|
||||||
|
.toCompileTo('hello');
|
||||||
|
|
||||||
result = template({foo: 'hello'}, {});
|
expectTemplate('{{@root.foo}}')
|
||||||
equals('hello', result);
|
.withInput({ foo: 'hello' })
|
||||||
|
.toCompileTo('hello');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('passed root values take priority', function() {
|
it('passed root values take priority', function() {
|
||||||
var template = CompilerContext.compile('{{@root.foo}}');
|
expectTemplate('{{@root.foo}}')
|
||||||
var result = template({}, { data: {root: {foo: 'hello'} } });
|
.withInput({ foo: 'should not be used' })
|
||||||
equals('hello', result);
|
.withRuntimeOptions({ data: { root: { foo: 'hello' } } })
|
||||||
|
.toCompileTo('hello');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('nesting', function() {
|
describe('nesting', function() {
|
||||||
it('the root context can be looked up via @root', function() {
|
it('the root context can be looked up via @root', function() {
|
||||||
var template = CompilerContext.compile('{{#helper}}{{#helper}}{{@./depth}} {{@../depth}} {{@../../depth}}{{/helper}}{{/helper}}');
|
expectTemplate(
|
||||||
var result = template({foo: 'hello'}, {
|
'{{#helper}}{{#helper}}{{@./depth}} {{@../depth}} {{@../../depth}}{{/helper}}{{/helper}}'
|
||||||
helpers: {
|
)
|
||||||
helper: function(options) {
|
.withInput({ foo: 'hello' })
|
||||||
var frame = Handlebars.createFrame(options.data);
|
.withHelper('helper', function(options) {
|
||||||
frame.depth = options.data.depth + 1;
|
var frame = Handlebars.createFrame(options.data);
|
||||||
return options.fn(this, {data: frame});
|
frame.depth = options.data.depth + 1;
|
||||||
|
return options.fn(this, { data: frame });
|
||||||
|
})
|
||||||
|
.withRuntimeOptions({
|
||||||
|
data: {
|
||||||
|
depth: 0
|
||||||
}
|
}
|
||||||
},
|
})
|
||||||
data: {
|
.toCompileTo('2 1 0');
|
||||||
depth: 0
|
|
||||||
}
|
|
||||||
});
|
|
||||||
equals('2 1 0', result);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
Vendored
+25
-6
@@ -1,14 +1,31 @@
|
|||||||
/*global handlebarsEnv */
|
|
||||||
require('./common');
|
require('./common');
|
||||||
|
|
||||||
var _ = require('underscore'),
|
var fs = require('fs'),
|
||||||
fs = require('fs'),
|
vm = require('vm');
|
||||||
vm = require('vm');
|
|
||||||
|
|
||||||
global.Handlebars = undefined;
|
var chai = require('chai');
|
||||||
vm.runInThisContext(fs.readFileSync(__dirname + '/../../dist/handlebars.js'), 'dist/handlebars.js');
|
var dirtyChai = require('dirty-chai');
|
||||||
|
|
||||||
|
chai.use(dirtyChai);
|
||||||
|
global.expect = chai.expect;
|
||||||
|
|
||||||
|
global.sinon = require('sinon');
|
||||||
|
|
||||||
|
global.Handlebars = 'no-conflict';
|
||||||
|
|
||||||
|
var filename = 'dist/handlebars.js';
|
||||||
|
if (global.minimizedTest) {
|
||||||
|
filename = 'dist/handlebars.min.js';
|
||||||
|
}
|
||||||
|
var distHandlebars = fs.readFileSync(
|
||||||
|
require.resolve('../../' + filename),
|
||||||
|
'utf-8'
|
||||||
|
);
|
||||||
|
vm.runInThisContext(distHandlebars, filename);
|
||||||
|
|
||||||
global.CompilerContext = {
|
global.CompilerContext = {
|
||||||
|
browser: true,
|
||||||
|
|
||||||
compile: function(template, options) {
|
compile: function(template, options) {
|
||||||
var templateSpec = handlebarsEnv.precompile(template, options);
|
var templateSpec = handlebarsEnv.precompile(template, options);
|
||||||
return handlebarsEnv.template(safeEval(templateSpec));
|
return handlebarsEnv.template(safeEval(templateSpec));
|
||||||
@@ -19,10 +36,12 @@ global.CompilerContext = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
function safeEval(templateSpec) {
|
function safeEval(templateSpec) {
|
||||||
|
/* eslint-disable no-eval, no-console */
|
||||||
try {
|
try {
|
||||||
return eval('(' + templateSpec + ')');
|
return eval('(' + templateSpec + ')');
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error(templateSpec);
|
console.error(templateSpec);
|
||||||
throw err;
|
throw err;
|
||||||
}
|
}
|
||||||
|
/* eslint-enable no-eval, no-console */
|
||||||
}
|
}
|
||||||
|
|||||||
Vendored
+201
-19
@@ -1,24 +1,65 @@
|
|||||||
/*global CompilerContext, compileWithPartials, shouldCompileToWithPartials */
|
var global = (function() {
|
||||||
|
return this;
|
||||||
|
})();
|
||||||
|
|
||||||
|
var AssertError;
|
||||||
|
if (Error.captureStackTrace) {
|
||||||
|
AssertError = function AssertError(message, caller) {
|
||||||
|
Error.prototype.constructor.call(this, message);
|
||||||
|
this.message = message;
|
||||||
|
|
||||||
|
if (Error.captureStackTrace) {
|
||||||
|
Error.captureStackTrace(this, caller || AssertError);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
AssertError.prototype = new Error();
|
||||||
|
} else {
|
||||||
|
AssertError = Error;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated Use "expectTemplate(template)...toCompileTo(output)" instead
|
||||||
|
*/
|
||||||
global.shouldCompileTo = function(string, hashOrArray, expected, message) {
|
global.shouldCompileTo = function(string, hashOrArray, expected, message) {
|
||||||
shouldCompileToWithPartials(string, hashOrArray, false, expected, message);
|
shouldCompileToWithPartials(string, hashOrArray, false, expected, message);
|
||||||
};
|
};
|
||||||
|
|
||||||
global.shouldCompileToWithPartials = function(string, hashOrArray, partials, expected, message) {
|
/**
|
||||||
|
* @deprecated Use "expectTemplate(template)...toCompileTo(output)" instead
|
||||||
|
*/
|
||||||
|
global.shouldCompileToWithPartials = function shouldCompileToWithPartials(
|
||||||
|
string,
|
||||||
|
hashOrArray,
|
||||||
|
partials,
|
||||||
|
expected,
|
||||||
|
message
|
||||||
|
) {
|
||||||
var result = compileWithPartials(string, hashOrArray, partials);
|
var result = compileWithPartials(string, hashOrArray, partials);
|
||||||
if (result !== expected) {
|
if (result !== expected) {
|
||||||
throw new Error("'" + result + "' should === '" + expected + "': " + message);
|
throw new AssertError(
|
||||||
|
"'" + result + "' should === '" + expected + "': " + message,
|
||||||
|
shouldCompileToWithPartials
|
||||||
|
);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated Use "expectTemplate(template)...toCompileTo(output)" instead
|
||||||
|
*/
|
||||||
global.compileWithPartials = function(string, hashOrArray, partials) {
|
global.compileWithPartials = function(string, hashOrArray, partials) {
|
||||||
var template,
|
var template, ary, options;
|
||||||
ary,
|
if (hashOrArray && hashOrArray.hash) {
|
||||||
options;
|
ary = [hashOrArray.hash, hashOrArray];
|
||||||
if(Object.prototype.toString.call(hashOrArray) === "[object Array]") {
|
delete hashOrArray.hash;
|
||||||
|
} else if (Object.prototype.toString.call(hashOrArray) === '[object Array]') {
|
||||||
ary = [];
|
ary = [];
|
||||||
ary.push(hashOrArray[0]);
|
ary.push(hashOrArray[0]); // input
|
||||||
ary.push({ helpers: hashOrArray[1], partials: hashOrArray[2] });
|
ary.push({ helpers: hashOrArray[1], partials: hashOrArray[2] });
|
||||||
options = {compat: hashOrArray[3]};
|
options =
|
||||||
|
typeof hashOrArray[3] === 'object'
|
||||||
|
? hashOrArray[3]
|
||||||
|
: { compat: hashOrArray[3] };
|
||||||
if (hashOrArray[4] != null) {
|
if (hashOrArray[4] != null) {
|
||||||
options.data = !!hashOrArray[4];
|
options.data = !!hashOrArray[4];
|
||||||
ary[1].data = hashOrArray[4];
|
ary[1].data = hashOrArray[4];
|
||||||
@@ -27,31 +68,172 @@ global.compileWithPartials = function(string, hashOrArray, partials) {
|
|||||||
ary = [hashOrArray];
|
ary = [hashOrArray];
|
||||||
}
|
}
|
||||||
|
|
||||||
template = CompilerContext[partials ? 'compileWithPartial' : 'compile'](string, options);
|
template = CompilerContext[partials ? 'compileWithPartial' : 'compile'](
|
||||||
|
string,
|
||||||
|
options
|
||||||
|
);
|
||||||
return template.apply(this, ary);
|
return template.apply(this, ary);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
global.equals = global.equal = function(a, b, msg) {
|
* @deprecated Use chai's expect-style API instead (`expect(actualValue).to.equal(expectedValue)`)
|
||||||
|
* @see https://www.chaijs.com/api/bdd/
|
||||||
|
*/
|
||||||
|
global.equals = global.equal = function equals(a, b, msg) {
|
||||||
if (a !== b) {
|
if (a !== b) {
|
||||||
throw new Error("'" + a + "' should === '" + b + "'" + (msg ? ": " + msg : ''));
|
throw new AssertError(
|
||||||
|
"'" + a + "' should === '" + b + "'" + (msg ? ': ' + msg : ''),
|
||||||
|
equals
|
||||||
|
);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated Use chai's expect-style API instead (`expect(actualValue).to.equal(expectedValue)`)
|
||||||
|
* @see https://www.chaijs.com/api/bdd/#method_throw
|
||||||
|
*/
|
||||||
global.shouldThrow = function(callback, type, msg) {
|
global.shouldThrow = function(callback, type, msg) {
|
||||||
var failed;
|
var failed;
|
||||||
try {
|
try {
|
||||||
callback();
|
callback();
|
||||||
failed = true;
|
failed = true;
|
||||||
} catch (err) {
|
} catch (caught) {
|
||||||
if (type && !(err instanceof type)) {
|
if (type && !(caught instanceof type)) {
|
||||||
throw new Error('Type failure');
|
throw new AssertError('Type failure: ' + caught);
|
||||||
}
|
}
|
||||||
if (msg && !(msg.test ? msg.test(err.message) : msg === err.message)) {
|
if (
|
||||||
equal(msg, err.message);
|
msg &&
|
||||||
|
!(msg.test ? msg.test(caught.message) : msg === caught.message)
|
||||||
|
) {
|
||||||
|
throw new AssertError(
|
||||||
|
'Throw mismatch: Expected ' +
|
||||||
|
caught.message +
|
||||||
|
' to match ' +
|
||||||
|
msg +
|
||||||
|
'\n\n' +
|
||||||
|
caught.stack,
|
||||||
|
shouldThrow
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (failed) {
|
if (failed) {
|
||||||
throw new Error('It failed to throw');
|
throw new AssertError('It failed to throw', shouldThrow);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
global.expectTemplate = function(templateAsString) {
|
||||||
|
return new HandlebarsTestBench(templateAsString);
|
||||||
|
};
|
||||||
|
|
||||||
|
function HandlebarsTestBench(templateAsString) {
|
||||||
|
this.templateAsString = templateAsString;
|
||||||
|
this.helpers = {};
|
||||||
|
this.partials = {};
|
||||||
|
this.decorators = {};
|
||||||
|
this.input = {};
|
||||||
|
this.message =
|
||||||
|
'Template' + templateAsString + ' does not evaluate to expected output';
|
||||||
|
this.compileOptions = {};
|
||||||
|
this.runtimeOptions = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
HandlebarsTestBench.prototype.withInput = function(input) {
|
||||||
|
this.input = input;
|
||||||
|
return this;
|
||||||
|
};
|
||||||
|
|
||||||
|
HandlebarsTestBench.prototype.withHelper = function(name, helperFunction) {
|
||||||
|
this.helpers[name] = helperFunction;
|
||||||
|
return this;
|
||||||
|
};
|
||||||
|
|
||||||
|
HandlebarsTestBench.prototype.withHelpers = function(helperFunctions) {
|
||||||
|
var self = this;
|
||||||
|
Object.keys(helperFunctions).forEach(function(name) {
|
||||||
|
self.withHelper(name, helperFunctions[name]);
|
||||||
|
});
|
||||||
|
return this;
|
||||||
|
};
|
||||||
|
|
||||||
|
HandlebarsTestBench.prototype.withPartial = function(name, partialAsString) {
|
||||||
|
this.partials[name] = partialAsString;
|
||||||
|
return this;
|
||||||
|
};
|
||||||
|
|
||||||
|
HandlebarsTestBench.prototype.withPartials = function(partials) {
|
||||||
|
var self = this;
|
||||||
|
Object.keys(partials).forEach(function(name) {
|
||||||
|
self.withPartial(name, partials[name]);
|
||||||
|
});
|
||||||
|
return this;
|
||||||
|
};
|
||||||
|
|
||||||
|
HandlebarsTestBench.prototype.withDecorator = function(
|
||||||
|
name,
|
||||||
|
decoratorFunction
|
||||||
|
) {
|
||||||
|
this.decorators[name] = decoratorFunction;
|
||||||
|
return this;
|
||||||
|
};
|
||||||
|
|
||||||
|
HandlebarsTestBench.prototype.withDecorators = function(decorators) {
|
||||||
|
var self = this;
|
||||||
|
Object.keys(decorators).forEach(function(name) {
|
||||||
|
self.withDecorator(name, decorators[name]);
|
||||||
|
});
|
||||||
|
return this;
|
||||||
|
};
|
||||||
|
|
||||||
|
HandlebarsTestBench.prototype.withCompileOptions = function(compileOptions) {
|
||||||
|
this.compileOptions = compileOptions;
|
||||||
|
return this;
|
||||||
|
};
|
||||||
|
|
||||||
|
HandlebarsTestBench.prototype.withRuntimeOptions = function(runtimeOptions) {
|
||||||
|
this.runtimeOptions = runtimeOptions;
|
||||||
|
return this;
|
||||||
|
};
|
||||||
|
|
||||||
|
HandlebarsTestBench.prototype.withMessage = function(message) {
|
||||||
|
this.message = message;
|
||||||
|
return this;
|
||||||
|
};
|
||||||
|
|
||||||
|
HandlebarsTestBench.prototype.toCompileTo = function(expectedOutputAsString) {
|
||||||
|
expect(this._compileAndExecute()).to.equal(
|
||||||
|
expectedOutputAsString,
|
||||||
|
this.message
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
// see chai "to.throw" (https://www.chaijs.com/api/bdd/#method_throw)
|
||||||
|
HandlebarsTestBench.prototype.toThrow = function(errorLike, errMsgMatcher) {
|
||||||
|
var self = this;
|
||||||
|
expect(function() {
|
||||||
|
self._compileAndExecute();
|
||||||
|
}).to.throw(errorLike, errMsgMatcher, this.message);
|
||||||
|
};
|
||||||
|
|
||||||
|
HandlebarsTestBench.prototype._compileAndExecute = function() {
|
||||||
|
var compile =
|
||||||
|
Object.keys(this.partials).length > 0
|
||||||
|
? CompilerContext.compileWithPartial
|
||||||
|
: CompilerContext.compile;
|
||||||
|
|
||||||
|
var combinedRuntimeOptions = this._combineRuntimeOptions();
|
||||||
|
|
||||||
|
var template = compile(this.templateAsString, this.compileOptions);
|
||||||
|
return template(this.input, combinedRuntimeOptions);
|
||||||
|
};
|
||||||
|
|
||||||
|
HandlebarsTestBench.prototype._combineRuntimeOptions = function() {
|
||||||
|
var self = this;
|
||||||
|
var combinedRuntimeOptions = {};
|
||||||
|
Object.keys(this.runtimeOptions).forEach(function(key) {
|
||||||
|
combinedRuntimeOptions[key] = self.runtimeOptions[key];
|
||||||
|
});
|
||||||
|
combinedRuntimeOptions.helpers = this.helpers;
|
||||||
|
combinedRuntimeOptions.partials = this.partials;
|
||||||
|
combinedRuntimeOptions.decorators = this.decorators;
|
||||||
|
return combinedRuntimeOptions;
|
||||||
|
};
|
||||||
|
|||||||
Vendored
+10
-1
@@ -1,6 +1,13 @@
|
|||||||
/*global handlebarsEnv */
|
|
||||||
require('./common');
|
require('./common');
|
||||||
|
|
||||||
|
var chai = require('chai');
|
||||||
|
var dirtyChai = require('dirty-chai');
|
||||||
|
|
||||||
|
chai.use(dirtyChai);
|
||||||
|
global.expect = chai.expect;
|
||||||
|
|
||||||
|
global.sinon = require('sinon');
|
||||||
|
|
||||||
global.Handlebars = require('../../lib');
|
global.Handlebars = require('../../lib');
|
||||||
|
|
||||||
global.CompilerContext = {
|
global.CompilerContext = {
|
||||||
@@ -14,10 +21,12 @@ global.CompilerContext = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
function safeEval(templateSpec) {
|
function safeEval(templateSpec) {
|
||||||
|
/* eslint-disable no-eval, no-console */
|
||||||
try {
|
try {
|
||||||
return eval('(' + templateSpec + ')');
|
return eval('(' + templateSpec + ')');
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error(templateSpec);
|
console.error(templateSpec);
|
||||||
throw err;
|
throw err;
|
||||||
}
|
}
|
||||||
|
/* eslint-enable no-eval, no-console */
|
||||||
}
|
}
|
||||||
|
|||||||
Vendored
+34
-13
@@ -1,25 +1,46 @@
|
|||||||
|
/* eslint-disable no-console */
|
||||||
var fs = require('fs'),
|
var fs = require('fs'),
|
||||||
Mocha = require('mocha'),
|
Mocha = require('mocha'),
|
||||||
path = require('path');
|
path = require('path');
|
||||||
|
|
||||||
var errors = 0,
|
var errors = 0,
|
||||||
testDir = path.dirname(__dirname),
|
testDir = path.dirname(__dirname),
|
||||||
grep = process.argv[2];
|
grep = process.argv[2];
|
||||||
|
|
||||||
var files = [ testDir + "/basic.js" ];
|
// Lazy hack, but whatever
|
||||||
|
if (grep === '--min') {
|
||||||
|
global.minimizedTest = true;
|
||||||
|
grep = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
var files = fs.readdirSync(testDir)
|
var files = fs
|
||||||
.filter(function(name) { return (/.*\.js$/).test(name); })
|
.readdirSync(testDir)
|
||||||
.map(function(name) { return testDir + '/' + name; });
|
.filter(function(name) {
|
||||||
|
return /.*\.js$/.test(name);
|
||||||
|
})
|
||||||
|
.map(function(name) {
|
||||||
|
return testDir + path.sep + name;
|
||||||
|
});
|
||||||
|
|
||||||
run('./node', function() {
|
if (global.minimizedTest) {
|
||||||
run('./browser', function() {
|
run('./runtime', function() {
|
||||||
run('./runtime', function() {
|
run('./browser', function() {
|
||||||
|
/* eslint-disable no-process-exit */
|
||||||
process.exit(errors);
|
process.exit(errors);
|
||||||
|
/* eslint-enable no-process-exit */
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
} else {
|
||||||
|
run('./runtime', function() {
|
||||||
|
run('./browser', function() {
|
||||||
|
run('./node', function() {
|
||||||
|
/* eslint-disable no-process-exit */
|
||||||
|
process.exit(errors);
|
||||||
|
/* eslint-enable no-process-exit */
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
function run(env, callback) {
|
function run(env, callback) {
|
||||||
var mocha = new Mocha();
|
var mocha = new Mocha();
|
||||||
|
|||||||
Vendored
+33
-11
@@ -1,22 +1,42 @@
|
|||||||
/*global handlebarsEnv */
|
|
||||||
require('./common');
|
require('./common');
|
||||||
|
|
||||||
var _ = require('underscore'),
|
var fs = require('fs'),
|
||||||
fs = require('fs'),
|
vm = require('vm');
|
||||||
vm = require('vm');
|
|
||||||
|
|
||||||
global.Handlebars = undefined;
|
var chai = require('chai');
|
||||||
vm.runInThisContext(fs.readFileSync(__dirname + '/../../dist/handlebars.runtime.js'), 'dist/handlebars.runtime.js');
|
var dirtyChai = require('dirty-chai');
|
||||||
|
|
||||||
|
chai.use(dirtyChai);
|
||||||
|
global.expect = chai.expect;
|
||||||
|
|
||||||
|
global.sinon = require('sinon');
|
||||||
|
|
||||||
|
global.Handlebars = 'no-conflict';
|
||||||
|
|
||||||
|
var filename = 'dist/handlebars.runtime.js';
|
||||||
|
if (global.minimizedTest) {
|
||||||
|
filename = 'dist/handlebars.runtime.min.js';
|
||||||
|
}
|
||||||
|
vm.runInThisContext(
|
||||||
|
fs.readFileSync(__dirname + '/../../' + filename),
|
||||||
|
filename
|
||||||
|
);
|
||||||
|
|
||||||
var parse = require('../../dist/cjs/handlebars/compiler/base').parse;
|
var parse = require('../../dist/cjs/handlebars/compiler/base').parse;
|
||||||
var compiler = require('../../dist/cjs/handlebars/compiler/compiler');
|
var compiler = require('../../dist/cjs/handlebars/compiler/compiler');
|
||||||
var JavaScriptCompiler = require('../../dist/cjs/handlebars/compiler/javascript-compiler')['default'];
|
var JavaScriptCompiler = require('../../dist/cjs/handlebars/compiler/javascript-compiler');
|
||||||
|
|
||||||
global.CompilerContext = {
|
global.CompilerContext = {
|
||||||
|
browser: true,
|
||||||
|
|
||||||
compile: function(template, options) {
|
compile: function(template, options) {
|
||||||
// Hack the compiler on to the environment for these specific tests
|
// Hack the compiler on to the environment for these specific tests
|
||||||
handlebarsEnv.precompile = function(template, options) {
|
handlebarsEnv.precompile = function(precompileTemplate, precompileOptions) {
|
||||||
return compiler.precompile(template, options, handlebarsEnv);
|
return compiler.precompile(
|
||||||
|
precompileTemplate,
|
||||||
|
precompileOptions,
|
||||||
|
handlebarsEnv
|
||||||
|
);
|
||||||
};
|
};
|
||||||
handlebarsEnv.parse = parse;
|
handlebarsEnv.parse = parse;
|
||||||
handlebarsEnv.Compiler = compiler.Compiler;
|
handlebarsEnv.Compiler = compiler.Compiler;
|
||||||
@@ -27,8 +47,8 @@ global.CompilerContext = {
|
|||||||
},
|
},
|
||||||
compileWithPartial: function(template, options) {
|
compileWithPartial: function(template, options) {
|
||||||
// Hack the compiler on to the environment for these specific tests
|
// Hack the compiler on to the environment for these specific tests
|
||||||
handlebarsEnv.compile = function(template, options) {
|
handlebarsEnv.compile = function(compileTemplate, compileOptions) {
|
||||||
return compiler.compile(template, options, handlebarsEnv);
|
return compiler.compile(compileTemplate, compileOptions, handlebarsEnv);
|
||||||
};
|
};
|
||||||
handlebarsEnv.parse = parse;
|
handlebarsEnv.parse = parse;
|
||||||
handlebarsEnv.Compiler = compiler.Compiler;
|
handlebarsEnv.Compiler = compiler.Compiler;
|
||||||
@@ -39,10 +59,12 @@ global.CompilerContext = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
function safeEval(templateSpec) {
|
function safeEval(templateSpec) {
|
||||||
|
/* eslint-disable no-eval, no-console */
|
||||||
try {
|
try {
|
||||||
return eval('(' + templateSpec + ')');
|
return eval('(' + templateSpec + ')');
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error(templateSpec);
|
console.error(templateSpec);
|
||||||
throw err;
|
throw err;
|
||||||
}
|
}
|
||||||
|
/* eslint-enable no-eval, no-console */
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,6 @@
|
|||||||
|
define(['handlebars.runtime'], function(Handlebars) {
|
||||||
|
Handlebars = Handlebars["default"]; var template = Handlebars.template, templates = Handlebars.templates = Handlebars.templates || {};
|
||||||
|
return templates['bom'] = template({"compiler":[8,">= 4.3.0"],"main":function(container,depth0,helpers,partials,data) {
|
||||||
|
return "a";
|
||||||
|
},"useData":true});
|
||||||
|
});
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user