Compare commits
1059 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 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 | |||
| cee52ea68f | |||
| d33b362d97 | |||
| c5acea75aa | |||
| 4f01f650dc | |||
| 3c869866c8 | |||
| ca1486b960 | |||
| dfca676d63 | |||
| 09d3c74c7f | |||
| c98147b31f | |||
| b41459055a | |||
| 84342b6215 | |||
| 24d63b6966 | |||
| 36f00db073 | |||
| 64f422b57f | |||
| dc9a2ed7eb | |||
| 980c38cebf | |||
| 31f6fd7131 | |||
| c9886fdc0f | |||
| 501a640330 | |||
| 477a26913a | |||
| 4ce474d3e4 | |||
| 3e6bf8b0f6 | |||
| e5aeca648a | |||
| 2b3fdf7ea8 | |||
| 0c7c37df6a | |||
| cde008b49f | |||
| 84d646bb5d | |||
| ccd803ff15 | |||
| 0528b91644 | |||
| 35b196d069 | |||
| 529e2b6796 | |||
| 3531e04117 | |||
| c3fde1c01c | |||
| 67b3f40eb2 | |||
| 19ce981296 | |||
| f4b8c5260c | |||
| cf343a1949 | |||
| 3ce105ae89 | |||
| ae44e071d8 | |||
| 2f14fa57e5 | |||
| f2b5519e76 | |||
| cc5de5df3c | |||
| d05245b5f5 | |||
| 084e8fe1b7 | |||
| 9f07a34955 | |||
| 6f22ec1e01 | |||
| c71ca34fd3 | |||
| 85b67d7f3d | |||
| 9e3f824bf5 | |||
| b94656a31f | |||
| 3cea85b2b0 | |||
| 4d0e3c98eb | |||
| cb22ee5681 | |||
| 9ab31e1fc6 | |||
| 1bd8d38c3c | |||
| 59607aa9ab | |||
| 48263be204 | |||
| 9f8110fe15 | |||
| 0edce6e1d1 | |||
| d95725d5fd | |||
| c98613b711 | |||
| 625b00e1da | |||
| b695aaec7c | |||
| f6dc5ad716 | |||
| 867322adf2 | |||
| 3288f525f3 | |||
| f2a2914d4f | |||
| e93d516672 | |||
| 2a47d66ee9 | |||
| 50c657f138 | |||
| 6070179009 | |||
| 4a4dcf1657 | |||
| 6efcaaf8d1 | |||
| f4af8e06b6 | |||
| 63c5eb7a82 | |||
| 248ad1acae | |||
| 905f99b18c | |||
| 11f85089cc | |||
| 5f1bf78555 | |||
| f374939cfd | |||
| eee2c4d4f2 | |||
| e11c1beb72 | |||
| 2bdfd2a31d | |||
| 271106d43f | |||
| 1fb7b51ee6 | |||
| c90cfe247c | |||
| 107aa4a66a | |||
| 9a1eda2caf | |||
| b5a5c76ceb | |||
| 4aad72d223 | |||
| 704961b378 | |||
| 054627adb0 | |||
| 058e7495f9 | |||
| 059532295d | |||
| 0c084399e2 | |||
| efb17b80a6 | |||
| 7890c7dc89 | |||
| b79e31e5a5 | |||
| 7172d167dd | |||
| 3cdf14d294 | |||
| efad61b1b7 | |||
| 2a20976e6a | |||
| 3638874ddc | |||
| f419838362 | |||
| 8ee73b9d13 | |||
| 3dcc49927b | |||
| e10eb537ab | |||
| 8f09ea5f40 | |||
| 72c1262394 | |||
| a14c689fa6 | |||
| e143849f8a | |||
| 2e7a3bd702 | |||
| 356ea1a909 | |||
| 085e5e1937 | |||
| a601b24a73 | |||
| 04c312649b | |||
| fea46dbb8c | |||
| b61c75512a | |||
| 1da136490b | |||
| d55f9966b9 | |||
| d33ae150cd | |||
| 8347ee2591 | |||
| 55782f08c5 | |||
| 058c0fb6ba | |||
| 10168cb0a9 | |||
| 66538cc7fa | |||
| 7dd64e5e92 | |||
| 674d07fd03 | |||
| 5999baf746 | |||
| fd5dfc861c | |||
| 8493822b62 | |||
| c86ac60876 | |||
| c68620caf9 | |||
| a5ff1f3d22 | |||
| b22a150a37 | |||
| 6da3c31494 | |||
| c72a8d5e84 | |||
| d00d8b6776 | |||
| db651e7ff4 | |||
| 16f135835e | |||
| fcec69ae2c | |||
| 306feb497c | |||
| a9f76e1475 | |||
| fe3b4cc98e | |||
| 14b7ef9066 | |||
| 9b14dc40a5 | |||
| 954253305a | |||
| 7a7ad74ff1 | |||
| d6e86af41a | |||
| 71ed8b5e93 | |||
| 7a6f706dc4 | |||
| dd365eaede | |||
| bc8f6792e8 | |||
| 2812fe2775 | |||
| b0e7298523 | |||
| 6ca06d4115 | |||
| 8f07bbabea | |||
| 4679b1d931 | |||
| cea57c05e2 | |||
| 311be67bde | |||
| 88c52ded2e | |||
| 5a0bcb932d | |||
| 49fcf10de2 | |||
| ace2896ec8 | |||
| 9df919083d | |||
| 103e5f8409 | |||
| a14daca1c6 | |||
| 33921beaa0 | |||
| d4cfe90959 | |||
| 051618c024 | |||
| fe4880feaa | |||
| 363cb4b0fb | |||
| e290ec24f1 | |||
| 45ae86a248 | |||
| f90981adf6 | |||
| 93a3725480 | |||
| 1b8c0e3c76 | |||
| 1b0b44a5b7 | |||
| f9e0272513 | |||
| 13633e7896 | |||
| 5659db4877 | |||
| cb80f46843 | |||
| f650b0d69e | |||
| d841ad5962 | |||
| fe9e93fc4d | |||
| 1a91fbda40 | |||
| 91aa96da7d | |||
| db7c9f3565 | |||
| 76b47e3393 | |||
| d385e2cab6 | |||
| 0f328f375e | |||
| c73aceb3d3 | |||
| daeecf825c | |||
| 607748af3b | |||
| 7197289364 | |||
| fc60f16475 | |||
| 08fab253ef | |||
| 3d9685c6a2 | |||
| 6d996ef270 | |||
| 55c7cbbbfa | |||
| e9f3a169c9 | |||
| 70b27bcef6 | |||
| a1df862f06 | |||
| 0c6829f8af | |||
| 2b9f3c195f | |||
| f445b96cb7 | |||
| e0d0ad5027 | |||
| 3f71fde155 | |||
| 6e4e1f8404 | |||
| 3c85720137 | |||
| 4713abc8f1 | |||
| e7e94dc697 | |||
| c1a93d33e7 | |||
| cd885bf855 | |||
| ddfe457abf | |||
| f4d337d252 | |||
| cbfec5b9e9 | |||
| af3358d195 | |||
| 641358a905 | |||
| f2df220a1f | |||
| a2ca31bb19 | |||
| b09333db79 | |||
| ac98e7b177 | |||
| 14d1d4270f | |||
| 150e55aa00 | |||
| 2f0c96b618 | |||
| 6c2137a420 | |||
| e1878050b5 | |||
| 1c0614bd88 | |||
| ba7a37a3b8 | |||
| 77fd7dcc1d | |||
| 7311ab0b3f | |||
| 4160ff3ee2 | |||
| 7151d15abb | |||
| 19f443f68e | |||
| 9f1b3bcea0 | |||
| 2dae244185 | |||
| c62ce32cc9 | |||
| f3a90812a6 | |||
| 096b8ccd2e | |||
| 923993dfb1 | |||
| fcf4e4ef0e | |||
| ae1795f82c | |||
| 2666553383 | |||
| 15af03c8bf | |||
| 97c7d65931 | |||
| bbc7c7d080 | |||
| 1a751b2f6e | |||
| abe9c82e75 | |||
| 956ac95e7a | |||
| 40e1837b14 | |||
| ddea5be2a4 | |||
| b4968bb8d4 | |||
| ca8e34c94d | |||
| 4a94340f42 | |||
| 7d69becd2e | |||
| 1a9b5d4ac6 | |||
| bdc0e6507e | |||
| 93fb25c702 | |||
| 23d41e5617 | |||
| d62620a282 | |||
| 62fdd541f5 | |||
| 7f53c917d9 | |||
| 015eba6687 | |||
| 5b8b54b774 | |||
| efcedf2f24 | |||
| 512daa3189 | |||
| ad3727b3fc | |||
| e2a81fdd60 | |||
| fe7bc0d4bd | |||
| 9ad8b86ece | |||
| 96b42dedea | |||
| 4942324250 | |||
| 0cf5657c43 | |||
| 3daef9d308 | |||
| 7f0ded4926 | |||
| 1041c1d533 | |||
| f06a9dc1f0 | |||
| 45ab2bb218 | |||
| f17cb3ecac | |||
| 86974b2660 | |||
| 57b4ef08b6 | |||
| cc16bc47dd | |||
| 078c1eecba | |||
| eb53f2e844 | |||
| e8954b3488 | |||
| 20b9c3829e | |||
| 709153f7f5 | |||
| d5cc3fbcf1 | |||
| d3cff5c1cd | |||
| 83106e7a5e | |||
| 525e58d318 | |||
| 9d353bd3dd | |||
| 47d13cb23c | |||
| 7c13753bb5 | |||
| affbcbb79e | |||
| 96a45a4a96 | |||
| 6f6eb89bd8 | |||
| eea708a18d | |||
| 508bb2d55d | |||
| 09cdc19a21 | |||
| 2cb61c9d5f | |||
| 986d6f4096 | |||
| 1e20373428 | |||
| 17ba258355 | |||
| c53e95414f | |||
| 41e2b7658b | |||
| 79fe7afb65 | |||
| 29e3858d21 | |||
| b5969394f1 | |||
| 904daba10b | |||
| 7cef79288a | |||
| 6ab579bb2a | |||
| 60f8aad5c9 | |||
| a2bb02638b | |||
| 479acfbe9e | |||
| 6b62a2e58e | |||
| a6781265a7 | |||
| 23ff9829d5 | |||
| c70e897f1c | |||
| ded0a1617f | |||
| 88fefc1521 | |||
| 4c056a91ba | |||
| 9f138c007a | |||
| 320c0a6fb5 | |||
| 31f7c25a8f | |||
| c776f171ab | |||
| 6b0b3fa2ca | |||
| 1de6cee1b2 | |||
| 151e6d49c0 | |||
| 4e4920cc78 | |||
| d5544d8969 | |||
| 14ef86f9ca | |||
| a1edab6ef5 | |||
| 5b483ff78d | |||
| 30384789d2 | |||
| 4cf49732a2 | |||
| 0cc6e270f9 | |||
| 06d94fed56 | |||
| 6f94fc0a3b | |||
| 84049bf0c5 | |||
| 2cd0995f21 | |||
| 4fda71364e | |||
| 3702efee5b | |||
| 9218f2e1ef | |||
| 97d5efd94a | |||
| 15e1eb36ca | |||
| 64de5c6812 | |||
| 15500aef70 | |||
| 3f52109c1c | |||
| 84c837869e | |||
| dd777c9872 | |||
| 468fa8b6dd | |||
| e9350f2932 | |||
| 0dbeaf973d | |||
| 3150ef15da | |||
| e20591b49d | |||
| 583141de7c | |||
| 3f96319f10 | |||
| 83b5e10d70 | |||
| 52dea1e70e | |||
| abf37ad739 | |||
| 6136e7f346 | |||
| 4d31df2f30 | |||
| 95d5768e0e | |||
| 7ee0c10c10 | |||
| e1f7f00cd4 | |||
| e5a4889b36 | |||
| 9769045e04 | |||
| b47f2cfcf5 | |||
| 096ac2d552 | |||
| 4e4c016be5 | |||
| 2abe4d404d | |||
| 3b0a3ca1b2 | |||
| 4d7124f6bf | |||
| b6c9f85d22 | |||
| 0966f5172b | |||
| 855eae37d0 | |||
| 820cbb006e | |||
| 63bb459ac6 | |||
| a8cd3201b5 | |||
| 19f1a1f81d | |||
| 782aae95d0 | |||
| 5fd0d4a790 | |||
| 5a306ac745 | |||
| 386c20cc63 | |||
| a3b22ac6bf | |||
| 117357b74e | |||
| db0f03d1b5 | |||
| fd09ff0e1b | |||
| e676e43dc5 | |||
| e75839b185 | |||
| 6a23391a9a | |||
| 80b748ad3e | |||
| fbf8f134fa | |||
| 27d9010974 | |||
| 1173e5a502 | |||
| 1e8409efa6 | |||
| a2687fdf50 | |||
| cb0c45b29f | |||
| 0fe78f379a | |||
| 192887cedc | |||
| 06e2a441db | |||
| 530f1903aa | |||
| 035449fe02 | |||
| 03ef3d4258 | |||
| 7ec25f364a | |||
| 28c4f4b9dd | |||
| 97fe2decc9 | |||
| 54cda3b3ae | |||
| dac055f01e | |||
| 1b4b8ed1da | |||
| 115db76f66 | |||
| a28be7c3c0 | |||
| 84b852c137 | |||
| 2dac9c978a | |||
| 1df1756c71 | |||
| 0d1b0c0493 | |||
| 709d57b3c1 | |||
| 496ebaa1a8 | |||
| 9689ebaa1c | |||
| 3c66068279 | |||
| bf99516e55 | |||
| c03b2f4dfa | |||
| b488af39f1 | |||
| ab64fec1a8 | |||
| 672aeda17f | |||
| 74369f724c | |||
| 57a0ee3d59 | |||
| 86694fd122 | |||
| df0734ae5e | |||
| e880bd464e | |||
| 9e09fe1826 | |||
| b773a7608d | |||
| da3788384b | |||
| 5aacdadff8 | |||
| b7f0d2e1fa | |||
| c207c4e048 | |||
| 623fdad59f | |||
| 6e6acaac0d | |||
| 4c02d3027c | |||
| a532d28d94 | |||
| 84420c1eb4 | |||
| f7b2bd8039 | |||
| c40a9eacb2 | |||
| a74aed15e9 | |||
| 94ba2b95d0 | |||
| b700d91a70 | |||
| e77c961d40 | |||
| 7efa75684e | |||
| 08989cfcdd | |||
| 7e16d6cb0d | |||
| 0af54b1142 | |||
| eb1cda6fdc | |||
| 94e5ab8593 | |||
| 3b8a061200 | |||
| 2f1a454467 | |||
| b7c62d8cc5 | |||
| 6492fe8c23 | |||
| 3d77d172ec | |||
| 87b5d4ee61 | |||
| 182ba044bb | |||
| 0b92db7b0c | |||
| 534d0ebcc3 | |||
| 564afab278 | |||
| 18506e00c9 | |||
| d02c90c0fb | |||
| db47593e34 | |||
| 860853ddb4 | |||
| 827a09114e | |||
| 7c1ce99eba | |||
| e62999f9ec | |||
| 4f5c05ffe9 | |||
| 9ca4f9c606 | |||
| 03310df95e | |||
| 3bc7d7b998 | |||
| 6f580c9beb | |||
| d33408fcbd | |||
| 5f664dd78b | |||
| dcbc3a55a4 | |||
| 497515c02f | |||
| 117239ca7c | |||
| c373d1c6d4 | |||
| df7a3fb8c0 | |||
| 199f3984c6 | |||
| f9ed0d3085 | |||
| 163b3f9e51 | |||
| f5c8484ea0 | |||
| da130f7745 | |||
| b5c4a33ab1 | |||
| a27e366f28 | |||
| 12f8299eb2 | |||
| fa881d4ce8 | |||
| ad615d9061 | |||
| 88ee4757e7 | |||
| ca5c3995fe | |||
| e4929df454 | |||
| 96718441ec | |||
| 8e2416dabb | |||
| 68e647a17e | |||
| 982cb2d188 | |||
| 7538b8556c | |||
| 0f278e1dfc | |||
| c3ff573184 | |||
| f11aaa7b7c | |||
| 6174c3b6f8 | |||
| 7def10195b | |||
| 0f4ef161d1 | |||
| bf4ec5eef7 | |||
| 4a545cc686 | |||
| e2a8bdab3c | |||
| 5de919f2f7 | |||
| cfd51f6579 | |||
| 2022c94a30 | |||
| bb8bb048aa | |||
| b3005f8f9f | |||
| 8f24a2e384 | |||
| e624fb50b7 | |||
| 80078861f2 | |||
| 26fcebe0c7 | |||
| 5ce7232dc3 | |||
| 9a334400f0 | |||
| bcc8d00e89 | |||
| 9d4a94616a | |||
| 7b1ad8aa38 | |||
| adda0569e0 | |||
| d13ae310d3 | |||
| 7f82f27c5f | |||
| 720b133839 | |||
| caded1d06b | |||
| 8275242960 | |||
| 265d6249f7 | |||
| e6b6ae8fdb | |||
| 2a073e0993 | |||
| f63b226b76 | |||
| 16fd601a5c | |||
| ef062adcc2 | |||
| 2024209519 | |||
| d7b345b2da | |||
| 17659b9720 | |||
| 58a0b4f17d | |||
| 61f64e9f04 | |||
| 66f6f94867 | |||
| c540d7186a | |||
| 293672432b | |||
| da2aabe7bd | |||
| 3ddbc5237f | |||
| 503e32208b | |||
| 15186dc9dd | |||
| 2c8dd74997 | |||
| 3d51088d4d | |||
| 822a8911ec | |||
| efca3c8ef5 | |||
| 5f349913aa | |||
| c1020a0130 | |||
| bff71d79d3 | |||
| 4d95d58e15 | |||
| 47e0aa795a | |||
| 6bf9c6fc13 | |||
| 533be694b5 | |||
| a97820f54b | |||
| f6b6361ad7 | |||
| 10669e0e36 | |||
| 69bcdf36c7 | |||
| 5ba17c9642 | |||
| 7fb9750717 | |||
| c95d9d68ea | |||
| 3aef7f604a | |||
| f5fe6d0bf6 | |||
| 94cc41900b | |||
| d649b3581e | |||
| 229b82bef5 | |||
| 531be77138 | |||
| 4cf0410b7c | |||
| 2ec5a97f4a | |||
| ddc4d31861 | |||
| 85dd39427b | |||
| 8b65fa771e | |||
| fa6cec26bf | |||
| 436973f470 | |||
| a2796784ab | |||
| d355d1f6aa | |||
| 71c5e36268 | |||
| 44ae572e2f | |||
| 7cd9cdb0eb | |||
| 090ee7c59a | |||
| 8a5705a253 | |||
| 344bb9092d | |||
| ef325733bb | |||
| 18798a7546 | |||
| bbce33778e | |||
| fc52a65c70 | |||
| 06e593d32e | |||
| 16a6fe15f2 | |||
| d8d2a6a1d2 | |||
| 4c35f89c2a | |||
| 5c9aa9e4d6 | |||
| ca576c27fc | |||
| 85a21f2359 | |||
| 075fdb8ac4 | |||
| 4429ffa9f3 | |||
| 73f2016a6a | |||
| 381c113d66 | |||
| 0329c852bd | |||
| f6ff5c648b | |||
| 12d68caa58 | |||
| ff32b4e2ad | |||
| c59882cce1 | |||
| 4d66d0c0a6 | |||
| bee0facaca | |||
| af3501636c | |||
| e33d9b4dcf | |||
| 31aaa30fcd | |||
| ec5882a31a | |||
| 671c07e699 | |||
| f4d0092bb8 | |||
| fe74d65f2b | |||
| 9e4b59e815 | |||
| 53de75927c | |||
| bcc15ea5e7 | |||
| 21977633a0 | |||
| e4a0ac4b04 | |||
| 681f1a6396 | |||
| bf30bf9115 | |||
| 948231a31d | |||
| 3657457d81 | |||
| 4bfb137e81 | |||
| 835496f0b6 | |||
| 519156138d | |||
| ac59d6d060 | |||
| baccdb4cfc | |||
| c95b3d6fc5 | |||
| d6f146f8a5 | |||
| 1f00504d25 | |||
| 8b2c1e5d23 | |||
| fa400d9dcb | |||
| 10453d1ef8 | |||
| 30623ce903 | |||
| da5cde5d1f | |||
| 3e86bb0f64 | |||
| 16bfebbf49 | |||
| 1f76b065fc | |||
| e2481cd66e | |||
| 8a05dcb70e | |||
| 261a2deb51 | |||
| 75a4f0d931 | |||
| b74711e63b | |||
| e3454a0187 | |||
| f72cbed479 | |||
| bc8214cb7c | |||
| 7f9e3fea81 | |||
| 1ca7462497 | |||
| 514c9391e0 | |||
| 7c633b604d | |||
| 51e9259fac | |||
| 0844d74fbe | |||
| a48e32bbf6 | |||
| 448e43c41e | |||
| 6fe7f17c89 | |||
| edb6f11b42 | |||
| d7758fe1a9 | |||
| a3376e24b1 | |||
| 6a51198a9b | |||
| a68a5ad5ba | |||
| 6ab92eee6d | |||
| b0c9f3d83b | |||
| b661977966 | |||
| 969b418291 | |||
| 8e9688792b | |||
| 9ac47df7dd | |||
| 6210d0b040 | |||
| 69d46e008b | |||
| f8a346b3ce | |||
| 0f6651bad9 | |||
| 231543feaf | |||
| b3a39ccefd | |||
| 956b0a62a6 | |||
| 94e70043f9 | |||
| bf201a66c2 | |||
| 39cc7c6816 | |||
| e9ac494ffd | |||
| cb50caaf53 | |||
| ae95407775 | |||
| 7277284132 | |||
| d486e7fa47 | |||
| 3c20641a2e | |||
| 5e20c809dc | |||
| 2b875cfb33 | |||
| 228c75669f | |||
| d1fdb82291 | |||
| 247babbe1a | |||
| b1ea697b7d | |||
| 822d98a1ef | |||
| 5ac29dcb50 | |||
| 9453c6d9d2 | |||
| 0c4333ae81 | |||
| ce82bef6f1 | |||
| cd9895061b | |||
| ccd6a22ea5 | |||
| 5e5f0dce9c | |||
| f5079765ff | |||
| 24fc3a2288 | |||
| 8961298859 | |||
| 4f54f62abc | |||
| eaa7e97461 | |||
| 59e30ecf37 | |||
| 3396814797 | |||
| fb9a7aa533 | |||
| 72f33c4615 | |||
| 16f2bf8f9b | |||
| 5f56d6582f | |||
| 76882ff75a | |||
| 3cac2672d0 | |||
| 7a3641df5a | |||
| b3f954b0af | |||
| f671c2e3d7 | |||
| 118594f663 | |||
| f4cad086bb | |||
| 51fd64c334 | |||
| 0ffb2a9681 | |||
| ca22ca57e1 | |||
| e9fe73d7ab | |||
| 28f377d169 | |||
| 02d35262f9 | |||
| 8df8488947 | |||
| 17ee7ffaca | |||
| bf4c813db0 | |||
| 070e12f76f | |||
| 9385666e08 | |||
| dfaf698b44 | |||
| 12f160fb31 | |||
| b58c2dd0ad | |||
| 4bb794d814 | |||
| 7ce13a3c2c | |||
| 19bfbe07f8 | |||
| fd0560b951 | |||
| 4cfda479fa | |||
| eabcc8a002 | |||
| cbfe84f1a9 | |||
| db975b42a0 | |||
| 7963218495 | |||
| bd04901454 | |||
| c3f92c402e | |||
| 53d6b4ca04 | |||
| 39832c0633 | |||
| 2676efb222 | |||
| a927a9b0ad | |||
| 8ed7a1e4e6 | |||
| d89e4b3d33 | |||
| 9589ab8949 | |||
| 3212325173 | |||
| 850304f43b | |||
| ed245c6c64 | |||
| 1018d9bfd1 | |||
| a1bfc80577 | |||
| 1b197be7fc | |||
| 9e582fa0eb | |||
| a1c9acb8b1 | |||
| 42120d1177 | |||
| b5074a88ec | |||
| ce74c36118 | |||
| 26b1438f20 | |||
| b83e5e0ea6 | |||
| f6aea819f3 | |||
| 647ee28ca1 | |||
| d117b50fd3 | |||
| ed8c2b95b8 | |||
| aba2269ddb | |||
| 967c69b2da | |||
| 5a6e4f1ddd | |||
| f55ca6c489 | |||
| 4b73b51e12 | |||
| eccc7c35ca | |||
| 7c4813b417 | |||
| acc04c2826 | |||
| 8e9bd6742f | |||
| 89f5ab8aaf | |||
| dc0426d836 | |||
| 6761d4c6d1 | |||
| 3426969221 | |||
| 9ce3032678 | |||
| 4cf0869330 | |||
| 201917a1be | |||
| 2b3e777340 | |||
| 075076975d | |||
| 0872fb6699 | |||
| bd9a84a0b7 |
@@ -0,0 +1,200 @@
|
||||
{
|
||||
"globals": {
|
||||
"self": false
|
||||
},
|
||||
"env": {
|
||||
"node": true
|
||||
},
|
||||
"ecmaFeatures": {
|
||||
// Enabling features that can be implemented without polyfills. Want to avoid polyfills at this time.
|
||||
"arrowFunctions": true,
|
||||
"blockBindings": true,
|
||||
"defaultParams": true,
|
||||
"destructuring": true,
|
||||
"modules": true,
|
||||
"objectLiteralComputedProperties": true,
|
||||
"objectLiteralDuplicateProperties": true,
|
||||
"objectLiteralShorthandMethods": true,
|
||||
"objectLiteralShorthandProperties": true,
|
||||
"restParams": true,
|
||||
"spread": true,
|
||||
"templateStrings": true
|
||||
},
|
||||
"rules": {
|
||||
// Possible Errors //
|
||||
//-----------------//
|
||||
|
||||
"comma-dangle": [2, "never"],
|
||||
"no-cond-assign": [2, "except-parens"],
|
||||
|
||||
// Allow for debugging
|
||||
"no-console": 1,
|
||||
|
||||
"no-constant-condition": 2,
|
||||
"no-control-regex": 2,
|
||||
|
||||
// Allow for debugging
|
||||
"no-debugger": 1,
|
||||
|
||||
"no-dupe-args": 2,
|
||||
"no-dupe-keys": 2,
|
||||
"no-duplicate-case": 2,
|
||||
"no-empty": 2,
|
||||
"no-empty-character-class": 2,
|
||||
"no-ex-assign": 2,
|
||||
"no-extra-boolean-cast": 2,
|
||||
"no-extra-parens": 0,
|
||||
"no-extra-semi": 2,
|
||||
"no-func-assign": 0,
|
||||
|
||||
// Stylistic... might consider disallowing in the future
|
||||
"no-inner-declarations": 0,
|
||||
|
||||
"no-invalid-regexp": 2,
|
||||
"no-irregular-whitespace": 2,
|
||||
"no-negated-in-lhs": 2,
|
||||
"no-obj-calls": 2,
|
||||
"no-regex-spaces": 2,
|
||||
"quote-props": [2, "as-needed", {"keywords": true}],
|
||||
"no-sparse-arrays": 0,
|
||||
|
||||
// Optimizer and coverage will handle/highlight this and can be useful for debugging
|
||||
"no-unreachable": 1,
|
||||
|
||||
"use-isnan": 2,
|
||||
"valid-jsdoc": 0,
|
||||
"valid-typeof": 2,
|
||||
|
||||
|
||||
// Best Practices //
|
||||
//----------------//
|
||||
"block-scoped-var": 0,
|
||||
"complexity": 0,
|
||||
"consistent-return": 0,
|
||||
"curly": 2,
|
||||
"default-case": 1,
|
||||
"dot-notation": [2, {"allowKeywords": false}],
|
||||
"eqeqeq": 0,
|
||||
"guard-for-in": 1,
|
||||
"no-alert": 2,
|
||||
"no-caller": 2,
|
||||
"no-div-regex": 1,
|
||||
"no-else-return": 0,
|
||||
"no-empty-label": 2,
|
||||
"no-eq-null": 0,
|
||||
"no-eval": 2,
|
||||
"no-extend-native": 2,
|
||||
"no-extra-bind": 2,
|
||||
"no-fallthrough": 2,
|
||||
"no-floating-decimal": 2,
|
||||
"no-implied-eval": 2,
|
||||
"no-iterator": 2,
|
||||
"no-labels": 2,
|
||||
"no-lone-blocks": 2,
|
||||
"no-loop-func": 2,
|
||||
"no-multi-spaces": 2,
|
||||
"no-multi-str": 1,
|
||||
"no-native-reassign": 2,
|
||||
"no-new": 2,
|
||||
"no-new-func": 2,
|
||||
"no-new-wrappers": 2,
|
||||
"no-octal": 2,
|
||||
"no-octal-escape": 2,
|
||||
"no-param-reassign": 0,
|
||||
"no-process-env": 2,
|
||||
"no-proto": 2,
|
||||
"no-redeclare": 2,
|
||||
"no-return-assign": 2,
|
||||
"no-script-url": 2,
|
||||
"no-self-compare": 2,
|
||||
"no-sequences": 2,
|
||||
"no-throw-literal": 2,
|
||||
"no-unused-expressions": 2,
|
||||
"no-void": 0,
|
||||
"no-warning-comments": 1,
|
||||
"no-with": 2,
|
||||
"radix": 2,
|
||||
"vars-on-top": 0,
|
||||
"wrap-iife": 2,
|
||||
"yoda": 0,
|
||||
|
||||
|
||||
// Strict //
|
||||
//--------//
|
||||
"strict": 0,
|
||||
|
||||
|
||||
// Variables //
|
||||
//-----------//
|
||||
"no-catch-shadow": 2,
|
||||
"no-delete-var": 2,
|
||||
"no-label-var": 2,
|
||||
"no-shadow": 0,
|
||||
"no-shadow-restricted-names": 2,
|
||||
"no-undef": 2,
|
||||
"no-undef-init": 2,
|
||||
"no-undefined": 0,
|
||||
"no-unused-vars": [2, {"vars": "all", "args": "after-used"}],
|
||||
"no-use-before-define": [2, "nofunc"],
|
||||
|
||||
|
||||
// Node.js //
|
||||
//---------//
|
||||
// Others left to environment defaults
|
||||
"no-mixed-requires": 0,
|
||||
|
||||
|
||||
// Stylistic //
|
||||
//-----------//
|
||||
"indent": 0,
|
||||
"brace-style": [2, "1tbs", {"allowSingleLine": true}],
|
||||
"camelcase": 2,
|
||||
"comma-spacing": [2, {"before": false, "after": true}],
|
||||
"comma-style": [2, "last"],
|
||||
"consistent-this": [1, "self"],
|
||||
"eol-last": 2,
|
||||
"func-names": 0,
|
||||
"func-style": [2, "declaration"],
|
||||
"key-spacing": [2, {
|
||||
"beforeColon": false,
|
||||
"afterColon": true
|
||||
}],
|
||||
"max-nested-callbacks": 0,
|
||||
"new-cap": 2,
|
||||
"new-parens": 2,
|
||||
"newline-after-var": 0,
|
||||
"no-array-constructor": 2,
|
||||
"no-continue": 0,
|
||||
"no-inline-comments": 0,
|
||||
"no-lonely-if": 2,
|
||||
"no-mixed-spaces-and-tabs": 2,
|
||||
"no-multiple-empty-lines": 0,
|
||||
"no-nested-ternary": 1,
|
||||
"no-new-object": 2,
|
||||
"no-spaced-func": 2,
|
||||
"no-ternary": 0,
|
||||
"no-trailing-spaces": 2,
|
||||
"no-underscore-dangle": 0,
|
||||
"no-extra-parens": [2, "functions"],
|
||||
"one-var": 0,
|
||||
"operator-assignment": 0,
|
||||
"padded-blocks": 0,
|
||||
"quote-props": 0,
|
||||
"quotes": [2, "single", "avoid-escape"],
|
||||
"semi": 2,
|
||||
"semi-spacing": [2, {"before": false, "after": true}],
|
||||
"sort-vars": 0,
|
||||
"space-after-keywords": [2, "always"],
|
||||
"space-before-blocks": [2, "always"],
|
||||
"space-before-function-paren": [2, {"anonymous": "never", "named": "never"}],
|
||||
"space-in-brackets": 0,
|
||||
"space-in-parens": [2, "never"],
|
||||
"space-infix-ops": 2,
|
||||
"space-return-throw-case": 2,
|
||||
"space-unary-ops": 2,
|
||||
"spaced-comment": [2, "always", {"markers": [","]}],
|
||||
"wrap-regex": 1,
|
||||
|
||||
"no-var": 1
|
||||
}
|
||||
}
|
||||
+5
-1
@@ -1,8 +1,12 @@
|
||||
dist
|
||||
vendor
|
||||
.rvmrc
|
||||
.DS_Store
|
||||
lib/handlebars/compiler/parser.js
|
||||
/dist/
|
||||
/tmp/
|
||||
/coverage/
|
||||
node_modules
|
||||
*.sublime-project
|
||||
*.sublime-workspace
|
||||
npm-debug.log
|
||||
sauce_connect.log*
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
[submodule "spec/mustache"]
|
||||
path = spec/mustache
|
||||
url = git://github.com/mustache/spec.git
|
||||
@@ -0,0 +1,2 @@
|
||||
instrumentation:
|
||||
excludes: ['**/spec/**']
|
||||
@@ -1,52 +0,0 @@
|
||||
{
|
||||
"predef": [
|
||||
"console",
|
||||
"Ember",
|
||||
"DS",
|
||||
"Handlebars",
|
||||
"Metamorph",
|
||||
"ember_assert",
|
||||
"ember_warn",
|
||||
"ember_deprecate",
|
||||
"ember_deprecateFunc",
|
||||
"require",
|
||||
"suite",
|
||||
"equal",
|
||||
"equals",
|
||||
"test",
|
||||
"testBoth",
|
||||
"raises",
|
||||
"deepEqual",
|
||||
"start",
|
||||
"stop",
|
||||
"ok",
|
||||
"strictEqual",
|
||||
"module"
|
||||
],
|
||||
|
||||
"node" : true,
|
||||
"es5" : true,
|
||||
"browser" : true,
|
||||
|
||||
"boss" : true,
|
||||
"curly": false,
|
||||
"debug": false,
|
||||
"devel": false,
|
||||
"eqeqeq": true,
|
||||
"evil": true,
|
||||
"forin": false,
|
||||
"immed": false,
|
||||
"laxbreak": false,
|
||||
"newcap": true,
|
||||
"noarg": true,
|
||||
"noempty": false,
|
||||
"nonew": false,
|
||||
"nomen": false,
|
||||
"onevar": false,
|
||||
"plusplus": false,
|
||||
"regexp": false,
|
||||
"undef": true,
|
||||
"sub": true,
|
||||
"strict": false,
|
||||
"white": false
|
||||
}
|
||||
+15
-1
@@ -1,11 +1,25 @@
|
||||
.DS_Store
|
||||
.gitignore
|
||||
.rvmrc
|
||||
.eslintrc
|
||||
.travis.yml
|
||||
.rspec
|
||||
Gemfile
|
||||
Gemfile.lock
|
||||
Rakefile
|
||||
Gruntfile.js
|
||||
*.gemspec
|
||||
*.nuspec
|
||||
*.log
|
||||
bench/*
|
||||
dist/*
|
||||
configurations/*
|
||||
components/*
|
||||
coverage/*
|
||||
dist/cdnjs/*
|
||||
dist/components/*
|
||||
spec/*
|
||||
src/*
|
||||
tasks/*
|
||||
tmp/*
|
||||
publish/*
|
||||
vendor/*
|
||||
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
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
|
||||
|
||||
git:
|
||||
depth: 100
|
||||
@@ -0,0 +1,80 @@
|
||||
# How to Contribute
|
||||
|
||||
## Reporting Issues
|
||||
|
||||
Please see our [FAQ](https://github.com/wycats/handlebars.js/blob/master/FAQ.md) for common issues that people run into.
|
||||
|
||||
Should you run into other issues with the project, please don't hesitate to let us know by filing an [issue][issue]! In general we are going to ask for an example of the problem failing, which can be as simple as a jsfiddle/jsbin/etc. We've put together a jsfiddle [template][jsfiddle] to ease this. (We will keep this link up to date as new releases occur, so feel free to check back here)
|
||||
|
||||
Pull requests containing only failing thats demonstrating the issue are welcomed and this also helps ensure that your issue won't regress in the future once it's fixed.
|
||||
|
||||
Documentation issues on the handlebarsjs.com site should be reported on [handlebars-site](https://github.com/wycats/handlebars-site).
|
||||
|
||||
## Pull Requests
|
||||
|
||||
We also accept [pull requests][pull-request]!
|
||||
|
||||
Generally we like to see pull requests that
|
||||
- Maintain the existing code style
|
||||
- Are focused on a single change (i.e. avoid large refactoring or style adjustments in untouched code if not the primary goal of the pull request)
|
||||
- Have [good commit messages](http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html)
|
||||
- Have tests
|
||||
- Don't significantly decrease the current code coverage (see coverage/lcov-report/index.html)
|
||||
|
||||
## Building
|
||||
|
||||
To build Handlebars.js you'll need a few things installed.
|
||||
|
||||
* Node.js
|
||||
* [Grunt](http://gruntjs.com/getting-started)
|
||||
|
||||
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`.
|
||||
|
||||
To build Handlebars.js from scratch, you'll want to run `grunt`
|
||||
in the root of the project. That will build Handlebars and output the
|
||||
results to the dist/ folder. To re-run tests, run `grunt test` or `npm test`.
|
||||
You can also run our set of benchmarks with `grunt bench`.
|
||||
|
||||
The `grunt dev` implements watching for tests and allows for in browser testing at `http://localhost:9999/spec/`.
|
||||
|
||||
If you notice any problems, please report them to the GitHub issue tracker at
|
||||
[http://github.com/wycats/handlebars.js/issues](http://github.com/wycats/handlebars.js/issues).
|
||||
|
||||
## Ember testing
|
||||
|
||||
The current ember distribution should be tested as part of the handlebars release process. This requires building the `handlebars-source` gem locally and then executing the ember test script.
|
||||
|
||||
```sh
|
||||
npm link
|
||||
grunt build release
|
||||
cp dist/*.js $emberRepoDir/bower_components/handlebars/
|
||||
|
||||
cd $emberRepoDir
|
||||
npm link handlebars
|
||||
npm test
|
||||
```
|
||||
|
||||
## Releasing
|
||||
|
||||
Handlebars utilizes the [release yeoman generator][generator-release] to perform most release tasks.
|
||||
|
||||
A full release may be completed with the following:
|
||||
|
||||
```
|
||||
yo release
|
||||
npm publish
|
||||
yo release:publish components handlebars.js dist/components/
|
||||
|
||||
cd dist/components/
|
||||
gem build handlebars-source.gemspec
|
||||
gem push handlebars-source-*.gem
|
||||
```
|
||||
|
||||
After this point the handlebars site needs to be updated to point to the new version numbers. The jsfiddle link should be updated to point to the most recent distribution for all instances in our documentation.
|
||||
|
||||
[generator-release]: https://github.com/walmartlabs/generator-release
|
||||
[pull-request]: https://github.com/wycats/handlebars.js/pull/new/master
|
||||
[issue]: https://github.com/wycats/handlebars.js/issues/new
|
||||
[jsfiddle]: http://jsfiddle.net/9D88g/46/
|
||||
@@ -0,0 +1,60 @@
|
||||
# Frequently Asked Questions
|
||||
|
||||
1. How can I file a bug report:
|
||||
|
||||
See our guidelines on [reporting issues](https://github.com/wycats/handlebars.js/blob/master/CONTRIBUTING.md#reporting-issues).
|
||||
|
||||
1. Why isn't my Mustache template working?
|
||||
|
||||
Handlebars deviates from Mustache slightly on a few behaviors. These variations are documented in our [readme](https://github.com/wycats/handlebars.js#differences-between-handlebarsjs-and-mustache).
|
||||
|
||||
1. Why is it slower when compiling?
|
||||
|
||||
The Handlebars compiler must parse the template and construct a JavaScript program which can then be run. Under some environments such as older mobile devices this can have a performance impact which can be avoided by precompiling. Generally it's recommended that precompilation and the runtime library be used on all clients.
|
||||
|
||||
1. Why doesn't this work with Content Security Policy restrictions?
|
||||
|
||||
When not using the precompiler, Handlebars generates a dynamic function for each template which can cause issues with pages that have enabled Content Policy. It's recommended that templates are precompiled or the `unsafe-eval` policy is enabled for sites that must generate dynamic templates at runtime.
|
||||
|
||||
1. How can I include script tags in my template?
|
||||
|
||||
If loading the template via an inlined `<script type="text/x-handlebars">` tag then you may need to break up the script tag with an empty comment to avoid browser parser errors:
|
||||
|
||||
```html
|
||||
<script type="text/x-handlebars">
|
||||
foo
|
||||
<scr{{!}}ipt src="bar"></scr{{!}}ipt>
|
||||
</script>
|
||||
```
|
||||
|
||||
It's generally recommended that templates are served through external, precompiled, files, which do not suffer from this issue.
|
||||
|
||||
1. Why are my precompiled scripts throwing exceptions?
|
||||
|
||||
When using the precompiler, it's important that a supporting version of the Handlebars runtime be loaded on the target page. In version 1.x there were rudimentary checks to compare the version but these did not always work. This is fixed under 2.x but the version checking does not work between these two versions. If you see unexpected errors such as `undefined is not a function` or similar, please verify that the same version is being used for both the precompiler and the client. This can be checked via:
|
||||
|
||||
```sh
|
||||
handlebars --version
|
||||
```
|
||||
If using the integrated precompiler and
|
||||
|
||||
```javascript
|
||||
console.log(Handlebars.VERSION);
|
||||
```
|
||||
On the client side.
|
||||
|
||||
We include the built client libraries in the npm package for those who want to be certain that they are using the same client libraries as the compiler.
|
||||
|
||||
Should these match, please file an issue with us, per our [issue filing guidelines](https://github.com/wycats/handlebars.js/blob/master/CONTRIBUTING.md#reporting-issues).
|
||||
|
||||
1. Why doesn't IE like the `default` name in the AMD module?
|
||||
|
||||
Some browsers such as particular versions of IE treat `default` as a reserved word in JavaScript source files. To safely use this you need to reference this via the `Handlebars['default']` lookup method. This is an unfortunate side effect of the shims necessary to backport the Handlebars ES6 code to all current browsers.
|
||||
|
||||
1. How do I load the runtime library when using AMD?
|
||||
|
||||
There are two options for loading under AMD environments. The first is to use the `handlebars.runtime.amd.js` file. This may require a [path mapping](https://github.com/wycats/handlebars.js/blob/master/spec/amd-runtime.html#L31) as well as access via the `default` field.
|
||||
|
||||
The other option is to load the `handlebars.runtime.js` UMD build, which might not require path configuration and exposes the library as both the module root and the `default` field for compatibility.
|
||||
|
||||
If not using ES6 transpilers or accessing submodules in the build the former option should be sufficient for most use cases.
|
||||
@@ -1,5 +0,0 @@
|
||||
source "http://rubygems.org"
|
||||
|
||||
gem "rake"
|
||||
gem "therubyracer", ">= 0.9.8"
|
||||
gem "rspec"
|
||||
@@ -1,24 +0,0 @@
|
||||
GEM
|
||||
remote: http://rubygems.org/
|
||||
specs:
|
||||
diff-lcs (1.1.3)
|
||||
libv8 (3.3.10.4)
|
||||
rake (0.9.2.2)
|
||||
rspec (2.11.0)
|
||||
rspec-core (~> 2.11.0)
|
||||
rspec-expectations (~> 2.11.0)
|
||||
rspec-mocks (~> 2.11.0)
|
||||
rspec-core (2.11.0)
|
||||
rspec-expectations (2.11.1)
|
||||
diff-lcs (~> 1.1.3)
|
||||
rspec-mocks (2.11.1)
|
||||
therubyracer (0.10.1)
|
||||
libv8 (~> 3.3.10)
|
||||
|
||||
PLATFORMS
|
||||
ruby
|
||||
|
||||
DEPENDENCIES
|
||||
rake
|
||||
rspec
|
||||
therubyracer (>= 0.9.8)
|
||||
+238
@@ -0,0 +1,238 @@
|
||||
/* eslint-disable no-process-env */
|
||||
module.exports = function(grunt) {
|
||||
|
||||
grunt.initConfig({
|
||||
pkg: grunt.file.readJSON('package.json'),
|
||||
|
||||
eslint: {
|
||||
options: {
|
||||
},
|
||||
files: [
|
||||
'*.js',
|
||||
'bench/**/*.js',
|
||||
'tasks/**/*.js',
|
||||
'lib/**/!(*.min|parser).js',
|
||||
'spec/**/!(*.amd|json2|require).js'
|
||||
]
|
||||
},
|
||||
|
||||
clean: ['tmp', 'dist', 'lib/handlebars/compiler/parser.js'],
|
||||
|
||||
copy: {
|
||||
dist: {
|
||||
options: {
|
||||
processContent: function(content) {
|
||||
return grunt.template.process('/*!\n\n <%= pkg.name %> v<%= pkg.version %>\n\n<%= grunt.file.read("LICENSE") %>\n@license\n*/\n')
|
||||
+ content;
|
||||
}
|
||||
},
|
||||
files: [
|
||||
{expand: true, cwd: 'dist/', src: ['*.js'], dest: 'dist/'}
|
||||
]
|
||||
},
|
||||
cdnjs: {
|
||||
files: [
|
||||
{expand: true, cwd: 'dist/', src: ['*.js'], dest: 'dist/cdnjs'}
|
||||
]
|
||||
},
|
||||
components: {
|
||||
files: [
|
||||
{expand: true, cwd: 'components/', src: ['**'], dest: 'dist/components'},
|
||||
{expand: true, cwd: 'dist/', src: ['*.js'], dest: 'dist/components'}
|
||||
]
|
||||
}
|
||||
},
|
||||
|
||||
babel: {
|
||||
options: {
|
||||
sourceMaps: 'inline',
|
||||
loose: ['es6.modules'],
|
||||
auxiliaryCommentBefore: 'istanbul ignore next'
|
||||
},
|
||||
amd: {
|
||||
options: {
|
||||
modules: 'amd'
|
||||
},
|
||||
files: [{
|
||||
expand: true,
|
||||
cwd: 'lib/',
|
||||
src: '**/!(index).js',
|
||||
dest: 'dist/amd/'
|
||||
}]
|
||||
},
|
||||
|
||||
cjs: {
|
||||
options: {
|
||||
modules: 'common'
|
||||
},
|
||||
files: [{
|
||||
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: {
|
||||
options: {
|
||||
optimize: 'none',
|
||||
baseUrl: 'dist/amd/'
|
||||
},
|
||||
dist: {
|
||||
options: {
|
||||
name: 'handlebars',
|
||||
out: 'dist/handlebars.amd.js'
|
||||
}
|
||||
},
|
||||
runtime: {
|
||||
options: {
|
||||
name: 'handlebars.runtime',
|
||||
out: 'dist/handlebars.runtime.amd.js'
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
uglify: {
|
||||
options: {
|
||||
mangle: true,
|
||||
compress: true,
|
||||
preserveComments: 'some'
|
||||
},
|
||||
dist: {
|
||||
files: [{
|
||||
cwd: 'dist/',
|
||||
expand: true,
|
||||
src: ['handlebars*.js', '!*.min.js'],
|
||||
dest: 'dist/',
|
||||
rename: function(dest, src) {
|
||||
return dest + src.replace(/\.js$/, '.min.js');
|
||||
}
|
||||
}]
|
||||
}
|
||||
},
|
||||
|
||||
concat: {
|
||||
tests: {
|
||||
src: ['spec/!(require).js'],
|
||||
dest: 'tmp/tests.js'
|
||||
}
|
||||
},
|
||||
|
||||
connect: {
|
||||
server: {
|
||||
options: {
|
||||
base: '.',
|
||||
hostname: '*',
|
||||
port: 9999
|
||||
}
|
||||
}
|
||||
},
|
||||
'saucelabs-mocha': {
|
||||
all: {
|
||||
options: {
|
||||
build: process.env.TRAVIS_JOB_ID,
|
||||
urls: ['http://localhost:9999/spec/?headless=true', 'http://localhost:9999/spec/amd.html?headless=true'],
|
||||
detailedError: true,
|
||||
concurrency: 4,
|
||||
browsers: [
|
||||
{browserName: 'chrome'},
|
||||
{browserName: 'firefox', platform: 'Linux'},
|
||||
{browserName: 'safari', version: 7, platform: 'OS X 10.9'},
|
||||
{browserName: 'safari', version: 6, platform: 'OS X 10.8'},
|
||||
{browserName: 'internet explorer', version: 11, platform: 'Windows 8.1'},
|
||||
{browserName: 'internet explorer', version: 10, platform: 'Windows 8'},
|
||||
{browserName: 'internet explorer', version: 9, platform: 'Windows 7'}
|
||||
]
|
||||
}
|
||||
},
|
||||
sanity: {
|
||||
options: {
|
||||
build: process.env.TRAVIS_JOB_ID,
|
||||
urls: ['http://localhost:9999/spec/umd.html?headless=true', 'http://localhost:9999/spec/amd-runtime.html?headless=true', 'http://localhost:9999/spec/umd-runtime.html?headless=true'],
|
||||
detailedError: true,
|
||||
concurrency: 2,
|
||||
browsers: [
|
||||
{browserName: 'chrome'}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
watch: {
|
||||
scripts: {
|
||||
options: {
|
||||
atBegin: true
|
||||
},
|
||||
|
||||
files: ['src/*', 'lib/**/*.js', 'spec/**/*.js'],
|
||||
tasks: ['build', 'amd', 'tests', 'test']
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Build a new version of the library
|
||||
this.registerTask('build', 'Builds a distributable version of the current project', [
|
||||
'eslint',
|
||||
'parser',
|
||||
'node',
|
||||
'globals']);
|
||||
|
||||
this.registerTask('amd', ['babel:amd', 'requirejs']);
|
||||
this.registerTask('node', ['babel:cjs']);
|
||||
this.registerTask('globals', ['webpack']);
|
||||
this.registerTask('tests', ['concat:tests']);
|
||||
|
||||
this.registerTask('release', 'Build final packages', ['eslint', 'amd', 'uglify', 'copy:dist', 'copy:components', 'copy:cdnjs']);
|
||||
|
||||
// Load tasks from npm
|
||||
grunt.loadNpmTasks('grunt-contrib-clean');
|
||||
grunt.loadNpmTasks('grunt-contrib-concat');
|
||||
grunt.loadNpmTasks('grunt-contrib-connect');
|
||||
grunt.loadNpmTasks('grunt-contrib-copy');
|
||||
grunt.loadNpmTasks('grunt-contrib-requirejs');
|
||||
grunt.loadNpmTasks('grunt-contrib-uglify');
|
||||
grunt.loadNpmTasks('grunt-contrib-watch');
|
||||
grunt.loadNpmTasks('grunt-babel');
|
||||
grunt.loadNpmTasks('grunt-eslint');
|
||||
grunt.loadNpmTasks('grunt-saucelabs');
|
||||
grunt.loadNpmTasks('grunt-webpack');
|
||||
|
||||
grunt.task.loadTasks('tasks');
|
||||
|
||||
grunt.registerTask('bench', ['metrics']);
|
||||
grunt.registerTask('sauce', process.env.SAUCE_USERNAME ? ['tests', 'connect', 'saucelabs-mocha'] : []);
|
||||
|
||||
grunt.registerTask('travis', process.env.PUBLISH ? ['default', 'sauce', 'metrics', 'publish:latest'] : ['default']);
|
||||
|
||||
grunt.registerTask('dev', ['clean', 'connect', 'watch']);
|
||||
grunt.registerTask('default', ['clean', 'build', 'test', 'release']);
|
||||
};
|
||||
@@ -1,4 +1,4 @@
|
||||
Copyright (C) 2011 by Yehuda Katz
|
||||
Copyright (C) 2011-2015 by Yehuda Katz
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
@@ -17,4 +17,3 @@ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
|
||||
|
||||
+99
-251
@@ -1,25 +1,34 @@
|
||||
[](http://travis-ci.org/wycats/handlebars.js)
|
||||
[](https://travis-ci.org/wycats/handlebars.js)
|
||||
[](https://saucelabs.com/u/handlebars)
|
||||
|
||||
Handlebars.js
|
||||
=============
|
||||
|
||||
Handlebars.js is an extension to the [Mustache templating language](http://mustache.github.com/) created by Chris Wanstrath. Handlebars.js and Mustache are both logicless templating languages that keep the view and the code separated like we all know they should be.
|
||||
|
||||
Checkout the official Handlebars docs site at [http://www.handlebarsjs.com](http://www.handlebarsjs.com).
|
||||
Handlebars.js is an extension to the [Mustache templating
|
||||
language](http://mustache.github.com/) created by Chris Wanstrath.
|
||||
Handlebars.js and Mustache are both logicless templating languages that
|
||||
keep the view and the code separated like we all know they should be.
|
||||
|
||||
Checkout the official Handlebars docs site at
|
||||
[http://www.handlebarsjs.com](http://www.handlebarsjs.com) and the live demo at [http://tryhandlebarsjs.com/](http://tryhandlebarsjs.com/).
|
||||
|
||||
Installing
|
||||
----------
|
||||
Installing Handlebars is easy. Simply [download the package from GitHub](https://github.com/wycats/handlebars.js/archives/master) and add it to your web pages (you should usually use the most recent version).
|
||||
|
||||
See our [installation documentation](http://handlebarsjs.com/installation.html).
|
||||
|
||||
Usage
|
||||
-----
|
||||
In general, the syntax of Handlebars.js templates is a superset of Mustache templates. For basic syntax, check out the [Mustache manpage](http://mustache.github.com/mustache.5.html).
|
||||
In general, the syntax of Handlebars.js templates is a superset
|
||||
of Mustache templates. For basic syntax, check out the [Mustache
|
||||
manpage](http://mustache.github.com/mustache.5.html).
|
||||
|
||||
Once you have a template, use the Handlebars.compile method to compile the template into a function. The generated function takes a context argument, which will be used to render the template.
|
||||
Once you have a template, use the `Handlebars.compile` method to compile
|
||||
the template into a function. The generated function takes a context
|
||||
argument, which will be used to render the template.
|
||||
|
||||
```js
|
||||
var source = "<p>Hello, my name is {{name}}. I am from {{hometown}}. I have " +
|
||||
var source = "<p>Hello, my name is {{name}}. I am from {{hometown}}. I have " +
|
||||
"{{kids.length}} kids:</p>" +
|
||||
"<ul>{{#kids}}<li>{{name}} is {{age}}</li>{{/kids}}</ul>";
|
||||
var template = Handlebars.compile(source);
|
||||
@@ -36,281 +45,120 @@ var result = template(data);
|
||||
// </ul>
|
||||
```
|
||||
|
||||
|
||||
Registering Helpers
|
||||
-------------------
|
||||
|
||||
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(context) {
|
||||
return "<a href='" + context.url + "'>" + context.body + "</a>";
|
||||
});
|
||||
|
||||
var context = { posts: [{url: "/hello-world", body: "Hello World!"}] };
|
||||
var source = "<ul>{{#posts}}<li>{{{link_to this}}}</li>{{/posts}}</ul>"
|
||||
|
||||
var template = Handlebars.compile(source);
|
||||
template(context);
|
||||
|
||||
// Would render:
|
||||
//
|
||||
// <ul>
|
||||
// <li><a href='/hello-world'>Hello World!</a></li>
|
||||
// </ul>
|
||||
```
|
||||
|
||||
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.
|
||||
|
||||
|
||||
Differences Between Handlebars.js and Mustache
|
||||
----------------------------------------------
|
||||
Handlebars.js adds a couple of additional features to make writing templates easier and also changes a tiny detail of how partials work.
|
||||
|
||||
### Paths
|
||||
|
||||
Handlebars.js supports an extended expression syntax that we call paths. Paths are made up of typical expressions and . characters. Expressions allow you to not only display data from the current context, but to display data from contexts that are descendents and ancestors of the current context.
|
||||
|
||||
To display data from descendent 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:
|
||||
|
||||
```
|
||||
{{#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, context) {
|
||||
return "<a href='/posts" + context.id + "'>" + title + "</a>"
|
||||
});
|
||||
|
||||
var context = { posts: [{url: "/hello-world", body: "Hello World!"}] };
|
||||
var source = '<ul>{{#posts}}<li>{{{link_to "Post" this}}}</li>{{/posts}}</ul>'
|
||||
|
||||
var template = Handlebars.compile(source);
|
||||
template(context);
|
||||
|
||||
// Would render:
|
||||
//
|
||||
// <ul>
|
||||
// <li><a href='/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(context, fn) {
|
||||
return '<a href="/people/' + this.__get__("id") + '">' + 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 two parameters, the argument that is passed to the helper, or the current context if no argument is passed and the compiled contents of the block. Inside of the block helper the value of `this` is the current context, wrapped to include a method named `__get__` that helps translate paths into values within the helpers.
|
||||
|
||||
### 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>
|
||||
```
|
||||
|
||||
Full documentation and more examples are at [handlebarsjs.com](http://handlebarsjs.com/).
|
||||
|
||||
Precompiling Templates
|
||||
----------------------
|
||||
|
||||
Handlebars allows templates to be precompiled and included as javascript
|
||||
code rather than the handlebars template allowing for faster startup time.
|
||||
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](http://handlebarsjs.com/precompilation.html).
|
||||
|
||||
### Installation
|
||||
The precompiler script may be installed via npm using the `npm install -g handlebars`
|
||||
command.
|
||||
Differences Between Handlebars.js and Mustache
|
||||
----------------------------------------------
|
||||
Handlebars.js adds a couple of additional features to make writing
|
||||
templates easier and also changes a tiny detail of how partials work.
|
||||
|
||||
### Usage
|
||||
- [Nested Paths](http://handlebarsjs.com/#paths)
|
||||
- [Helpers](http://handlebarsjs.com/#helpers)
|
||||
- [Block Expressions](http://handlebarsjs.com/#block-expressions)
|
||||
- [Literal Values](http://handlebarsjs.com/#literals)
|
||||
- [Delimited Comments](http://handlebarsjs.com/#comments)
|
||||
|
||||
<pre>
|
||||
Precompile handlebar templates.
|
||||
Usage: handlebars template...
|
||||
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](http://mustache.github.io/mustache.5.html) defines the exact behavior of sections. In the case of name conflicts, helpers are given priority.
|
||||
|
||||
Options:
|
||||
-f, --output Output File [string]
|
||||
-k, --known Known helpers [string]
|
||||
-o, --knownOnly Known helpers only [boolean]
|
||||
-m, --min Minimize output [boolean]
|
||||
-s, --simple Output template function only. [boolean]
|
||||
-r, --root Template root. Base value that will be stripped from template names. [string]
|
||||
</pre>
|
||||
### Compatibility
|
||||
|
||||
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.
|
||||
There are a few Mustache behaviors that Handlebars does not implement.
|
||||
- Handlebars deviates from Mustache slightly in that it does not perform recursive lookup by default. The compile time `compat` flag must be set to enable this functionality. Users should note that there is a performance cost for enabling this flag. The exact cost varies by template, but it's recommended that performance sensitive operations should avoid this mode and instead opt for explicit path references.
|
||||
- The optional Mustache-style lambdas are not supported. Instead Handlebars provides its own lambda resolution that follows the behaviors of helpers.
|
||||
- Alternative delimiters are not supported.
|
||||
|
||||
If using the simple mode the precompiler will generate a single javascript method.
|
||||
To execute this method it must be passed to the using the `Handlebars.template`
|
||||
method and the resulting object may be as normal.
|
||||
|
||||
### Optimizations
|
||||
Supported Environments
|
||||
----------------------
|
||||
|
||||
- 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.
|
||||
Handlebars has been designed to work in any ECMAScript 3 environment. This includes
|
||||
|
||||
- Node.js
|
||||
- Chrome
|
||||
- Firefox
|
||||
- Safari 5+
|
||||
- Opera 11+
|
||||
- IE 6+
|
||||
|
||||
Older versions and other runtimes are likely to work but have not been formally
|
||||
tested. 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
|
||||
-----------
|
||||
|
||||
In a rough performance test, precompiled Handlebars.js templates (in the original version of Handlebars.js) rendered in about half the time of Mustache templates. It would be a shame if it were any other way, since they were precompiled, but the difference in architecture does have some big performance advantages. Justin Marney, a.k.a. [gotascii](http://github.com/gotascii), confirmed that with an [independent test](http://sorescode.com/2010/09/12/benchmarks.html). The rewritten Handlebars (current version) is faster than the old version, and we will have some benchmarks in the near future.
|
||||
|
||||
|
||||
Building
|
||||
--------
|
||||
|
||||
To build handlebars, just run `rake release`, and you will get two files
|
||||
in the `dist` directory.
|
||||
In a rough performance test, precompiled Handlebars.js templates (in
|
||||
the original version of Handlebars.js) rendered in about half the
|
||||
time of Mustache templates. It would be a shame if it were any other
|
||||
way, since they were precompiled, but the difference in architecture
|
||||
does have some big performance advantages. Justin Marney, a.k.a.
|
||||
[gotascii](http://github.com/gotascii), confirmed that with an
|
||||
[independent test](http://sorescode.com/2010/09/12/benchmarks.html). The
|
||||
rewritten Handlebars (current version) is faster than the old version,
|
||||
with many [performance tests](https://travis-ci.org/wycats/handlebars.js/builds/33392182#L538) being 5 to 7 times faster than the Mustache equivalent.
|
||||
|
||||
|
||||
Upgrading
|
||||
---------
|
||||
|
||||
When upgrading from the Handlebars 0.9 series, be aware that the
|
||||
signature for passing custom helpers or partials to templates has
|
||||
changed.
|
||||
|
||||
Instead of:
|
||||
|
||||
```js
|
||||
template(context, helpers, partials, [data])
|
||||
```
|
||||
|
||||
Use:
|
||||
|
||||
```js
|
||||
template(context, {helpers: helpers, partials: partials, data: data})
|
||||
```
|
||||
See [release-notes.md](https://github.com/wycats/handlebars.js/blob/master/release-notes.md) for upgrade notes.
|
||||
|
||||
Known Issues
|
||||
------------
|
||||
* Handlebars.js can be cryptic when there's an error while rendering.
|
||||
* Using a variable, helper, or partial named `class` causes errors in IE browsers. (Instead, use `className`)
|
||||
|
||||
See [FAQ.md](https://github.com/wycats/handlebars.js/blob/master/FAQ.md) for known issues and common pitfalls.
|
||||
|
||||
|
||||
Handlebars in the Wild
|
||||
-----------------
|
||||
* [jblotus](http://github.com/jblotus) created [http://tryhandlebarsjs.com](http://tryhandlebarsjs.com) for anyone who would
|
||||
like to try out Handlebars.js in their browser.
|
||||
* Don Park wrote an Express.js view engine adapter for Handlebars.js called [hbs](http://github.com/donpark/hbs).
|
||||
* [sammy.js](http://github.com/quirkey/sammy) by Aaron Quint, a.k.a. quirkey, supports Handlebars.js as one of its template plugins.
|
||||
* [SproutCore](http://www.sproutcore.com) uses Handlebars.js as its main templating engine, extending it with automatic data binding support.
|
||||
* [Ember.js](http://www.emberjs.com) makes Handlebars.js the primary way to structure your views, also with automatic data binding support.
|
||||
* Les Hill (@leshill) wrote a Rails Asset Pipeline gem named [handlebars_assets](http://github.com/leshill/handlebars_assets).
|
||||
----------------------
|
||||
|
||||
Helping Out
|
||||
-----------
|
||||
To build Handlebars.js you'll need a few things installed.
|
||||
* [Assemble](http://assemble.io), by [@jonschlinkert](https://github.com/jonschlinkert)
|
||||
and [@doowb](https://github.com/doowb), is a static site generator that uses Handlebars.js
|
||||
as its template engine.
|
||||
* [CoSchedule](http://coschedule.com) An editorial calendar for WordPress that uses Handlebars.js
|
||||
* [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
|
||||
structure your views, also with automatic data binding support.
|
||||
* [Ghost](https://ghost.org/) Just a blogging platform.
|
||||
* [handlebars_assets](http://github.com/leshill/handlebars_assets): A Rails Asset Pipeline gem
|
||||
from Les Hill (@leshill).
|
||||
* [handlebars-helpers](https://github.com/assemble/handlebars-helpers) is an extensive library
|
||||
with 100+ handlebars helpers.
|
||||
* [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,
|
||||
from Don Park.
|
||||
* [koa-hbs](https://github.com/jwilm/koa-hbs): [koa](https://github.com/koajs/koa) generator based
|
||||
renderer for Handlebars.js.
|
||||
* [jblotus](http://github.com/jblotus) created [http://tryhandlebarsjs.com](http://tryhandlebarsjs.com)
|
||||
for anyone who would like to try out Handlebars.js in their browser.
|
||||
* [jQuery plugin](http://71104.github.io/jquery-handlebars/): allows you to use
|
||||
Handlebars.js with [jQuery](http://jquery.com/).
|
||||
* [Lumbar](http://walmartlabs.github.io/lumbar) provides easy module-based template management for
|
||||
handlebars projects.
|
||||
* [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,
|
||||
supports Handlebars.js as one of its template plugins.
|
||||
* [SproutCore](http://www.sproutcore.com) uses Handlebars.js as its main
|
||||
templating engine, extending it with automatic data binding support.
|
||||
* [YUI](http://yuilibrary.com/yui/docs/handlebars/) implements a port of handlebars
|
||||
* [Swag](https://github.com/elving/swag) by [@elving](https://github.com/elving) is a growing collection of helpers for handlebars.js. Give your handlebars.js templates some swag son!
|
||||
* [DOMBars](https://github.com/blakeembrey/dombars) is a DOM-based templating engine built on the Handlebars parser and runtime
|
||||
|
||||
* Node.js
|
||||
* Jison, for building the compiler - `npm install jison`
|
||||
* Ruby
|
||||
* therubyracer, for running tests - `gem install therubyracer`
|
||||
* rspec, for running tests - `gem install rspec`
|
||||
External Resources
|
||||
------------------
|
||||
|
||||
There's a Gemfile in the repo, so you can run `bundle` to install rspec and therubyracer if you've got bundler installed.
|
||||
* [Gist about Synchronous and asynchronous loading of external handlebars templates](https://gist.github.com/2287070)
|
||||
|
||||
To build Handlebars.js from scratch, you'll want to run `rake compile` in the root of the project. That will build Handlebars and output the results to the dist/ folder. To run tests, run `rake spec`. You can also run our set of benchmarks with `rake bench`.
|
||||
|
||||
If you notice any problems, please report them to the GitHub issue tracker at [http://github.com/wycats/handlebars.js/issues](http://github.com/wycats/handlebars.js/issues). Feel free to contact commondream or wycats through GitHub with any other questions or feature requests. To submit changes fork the project and send a pull request.
|
||||
Have a project using Handlebars? Send us a [pull request][pull-request]!
|
||||
|
||||
License
|
||||
-------
|
||||
Handlebars.js is released under the MIT license.
|
||||
|
||||
[pull-request]: https://github.com/wycats/handlebars.js/pull/new/master
|
||||
|
||||
@@ -1,116 +0,0 @@
|
||||
require "rubygems"
|
||||
require "bundler/setup"
|
||||
|
||||
def compile_parser
|
||||
system "jison src/handlebars.yy src/handlebars.l"
|
||||
if $?.success?
|
||||
File.open("lib/handlebars/compiler/parser.js", "w") do |file|
|
||||
file.puts File.read("handlebars.js") + ";"
|
||||
end
|
||||
|
||||
sh "rm handlebars.js"
|
||||
else
|
||||
puts "Failed to run Jison."
|
||||
end
|
||||
end
|
||||
|
||||
file "lib/handlebars/compiler/parser.js" => ["src/handlebars.yy","src/handlebars.l"] do
|
||||
if ENV['PATH'].split(':').any? {|folder| File.exists?(folder+'/jison')}
|
||||
compile_parser
|
||||
else
|
||||
puts "Jison is not installed. Trying `npm install jison`."
|
||||
sh "npm install jison -g"
|
||||
compile_parser
|
||||
end
|
||||
end
|
||||
|
||||
task :compile => "lib/handlebars/compiler/parser.js"
|
||||
|
||||
desc "run the spec suite"
|
||||
task :spec => [:release] do
|
||||
system "rspec -cfs spec"
|
||||
end
|
||||
|
||||
task :default => [:compile, :spec]
|
||||
|
||||
def remove_exports(string)
|
||||
match = string.match(%r{^// BEGIN\(BROWSER\)\n(.*)\n^// END\(BROWSER\)}m)
|
||||
match ? match[1] : string
|
||||
end
|
||||
|
||||
minimal_deps = %w(base compiler/parser compiler/base compiler/ast utils compiler/compiler runtime).map do |file|
|
||||
"lib/handlebars/#{file}.js"
|
||||
end
|
||||
|
||||
runtime_deps = %w(base utils runtime).map do |file|
|
||||
"lib/handlebars/#{file}.js"
|
||||
end
|
||||
|
||||
directory "dist"
|
||||
|
||||
minimal_deps.unshift "dist"
|
||||
|
||||
def build_for_task(task)
|
||||
FileUtils.rm_rf("dist/*") if File.directory?("dist")
|
||||
FileUtils.mkdir_p("dist")
|
||||
|
||||
contents = []
|
||||
task.prerequisites.each do |filename|
|
||||
next if filename == "dist"
|
||||
|
||||
contents << "// #{filename}\n" + remove_exports(File.read(filename)) + ";"
|
||||
end
|
||||
|
||||
File.open(task.name, "w") do |file|
|
||||
file.puts contents.join("\n")
|
||||
end
|
||||
end
|
||||
|
||||
file "dist/handlebars.js" => minimal_deps do |task|
|
||||
build_for_task(task)
|
||||
end
|
||||
|
||||
file "dist/handlebars.runtime.js" => runtime_deps do |task|
|
||||
build_for_task(task)
|
||||
end
|
||||
|
||||
task :build => [:compile, "dist/handlebars.js"]
|
||||
task :runtime => [:compile, "dist/handlebars.runtime.js"]
|
||||
|
||||
desc "build the build and runtime version of handlebars"
|
||||
task :release => [:build, :runtime]
|
||||
|
||||
directory "vendor"
|
||||
|
||||
desc "benchmark against dust.js and mustache.js"
|
||||
task :bench => "vendor" do
|
||||
require "open-uri"
|
||||
#File.open("vendor/mustache.js", "w") do |file|
|
||||
#file.puts open("https://github.com/janl/mustache.js/raw/master/mustache.js").read
|
||||
#file.puts "module.exports = Mustache;"
|
||||
#end
|
||||
|
||||
File.open("vendor/benchmark.js", "w") do |file|
|
||||
file.puts open("https://raw.github.com/bestiejs/benchmark.js/master/benchmark.js").read
|
||||
end
|
||||
|
||||
#if File.directory?("vendor/dustjs")
|
||||
#system "cd vendor/dustjs && git pull"
|
||||
#else
|
||||
#system "git clone git://github.com/akdubya/dustjs.git vendor/dustjs"
|
||||
#end
|
||||
|
||||
#if File.directory?("vendor/coffee")
|
||||
#system "cd vendor/coffee && git pull"
|
||||
#else
|
||||
#system "git clone git://github.com/jashkenas/coffee-script.git vendor/coffee"
|
||||
#end
|
||||
|
||||
#if File.directory?("vendor/eco")
|
||||
#system "cd vendor/eco && git pull && npm update"
|
||||
#else
|
||||
#system "git clone git://github.com/sstephenson/eco.git vendor/eco && cd vendor/eco && npm update"
|
||||
#end
|
||||
|
||||
system "node bench/handlebars.js"
|
||||
end
|
||||
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"globals": {
|
||||
"require": true
|
||||
},
|
||||
"rules": {
|
||||
// Disabling for tests, for now.
|
||||
"no-path-concat": 0,
|
||||
|
||||
"no-var": 0,
|
||||
"no-shadow": 0,
|
||||
"handle-callback-err": 0,
|
||||
"no-console": 0
|
||||
}
|
||||
}
|
||||
@@ -1,149 +0,0 @@
|
||||
|
||||
var Benchmark = require("benchmark");
|
||||
|
||||
var BenchWarmer = function(names) {
|
||||
this.benchmarks = [];
|
||||
this.currentBenches = [];
|
||||
this.names = [];
|
||||
this.errors = {};
|
||||
};
|
||||
|
||||
var print = require("sys").print;
|
||||
|
||||
BenchWarmer.prototype = {
|
||||
winners: function(benches) {
|
||||
var result = Benchmark.filter(benches, function(bench) { return bench.cycles; });
|
||||
|
||||
if (result.length > 1) {
|
||||
result.sort(function(a, b) { return b.compare(a); });
|
||||
first = result[0];
|
||||
last = result[result.length - 1];
|
||||
|
||||
var winners = [];
|
||||
|
||||
Benchmark.each(result, function(bench) {
|
||||
if (bench.compare(first) === 0) {
|
||||
winners.push(bench);
|
||||
}
|
||||
});
|
||||
|
||||
return winners;
|
||||
} else {
|
||||
return result;
|
||||
}
|
||||
},
|
||||
suite: function(suite, fn) {
|
||||
this.suiteName = suite;
|
||||
this.first = true;
|
||||
|
||||
var self = this;
|
||||
|
||||
fn(function(name, benchFn) {
|
||||
self.push(name, benchFn);
|
||||
});
|
||||
},
|
||||
push: function(name, fn) {
|
||||
if(this.names.indexOf(name) == -1) {
|
||||
this.names.push(name);
|
||||
}
|
||||
|
||||
var first = this.first, suiteName = this.suiteName, self = this;
|
||||
this.first = false;
|
||||
|
||||
var bench = new Benchmark(function() {
|
||||
fn();
|
||||
}, {
|
||||
name: this.suiteName + ": " + name,
|
||||
onComplete: function() {
|
||||
if(first) { self.startLine(suiteName); }
|
||||
self.writeBench(bench);
|
||||
self.currentBenches.push(bench);
|
||||
}, onError: function() {
|
||||
self.errors[this.name] = this;
|
||||
}
|
||||
});
|
||||
|
||||
this.benchmarks.push(bench);
|
||||
},
|
||||
bench: function() {
|
||||
var benchSize = 0, names = this.names, self = this, i, l;
|
||||
|
||||
for(i=0, l=names.length; i<l; i++) {
|
||||
var name = names[i];
|
||||
|
||||
if(benchSize < name.length) { benchSize = name.length; }
|
||||
}
|
||||
|
||||
this.nameSize = benchSize + 2;
|
||||
this.benchSize = 20;
|
||||
var horSize = 0;
|
||||
|
||||
this.startLine("ops/msec");
|
||||
horSize = horSize + "ops/msec ".length;
|
||||
for(i=0, l=names.length; i<l; i++) {
|
||||
print(names[i] + new Array(this.benchSize - names[i].length + 1).join(" "));
|
||||
horSize = horSize + this.benchSize;
|
||||
}
|
||||
|
||||
print("WINNER(S)");
|
||||
horSize = horSize + "WINNER(S)".length;
|
||||
|
||||
print("\n" + new Array(horSize + 1).join("-"));
|
||||
|
||||
Benchmark.invoke(this.benchmarks, {
|
||||
name: "run",
|
||||
onComplete: function() {
|
||||
var errors = false, prop, bench;
|
||||
for(prop in self.errors) { if(self.errors.hasOwnProperty(prop)) { errors = true; break; } }
|
||||
|
||||
if(errors) {
|
||||
print("\n\nErrors:\n");
|
||||
for(prop in self.errors) {
|
||||
if(self.errors.hasOwnProperty(prop)) {
|
||||
bench = self.errors[prop];
|
||||
print("\n" + bench.name + ":\n");
|
||||
print(bench.error.message);
|
||||
if(bench.error.stack) {
|
||||
print(bench.error.stack.join("\n"));
|
||||
}
|
||||
print("\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
print("\n");
|
||||
},
|
||||
startLine: function(name) {
|
||||
var winners = Benchmark.map(this.winners(this.currentBenches), function(bench) {
|
||||
return bench.name.split(": ")[1];
|
||||
});
|
||||
|
||||
this.currentBenches = [];
|
||||
|
||||
print(winners.join(", "));
|
||||
print("\n");
|
||||
var padding = this.nameSize - name.length + 1;
|
||||
name = name + new Array(padding).join(" ");
|
||||
print(name);
|
||||
},
|
||||
writeBench: function(bench) {
|
||||
var out;
|
||||
|
||||
if(!bench.error) {
|
||||
var count = bench.hz,
|
||||
moe = count * bench.stats.RME / 100;
|
||||
|
||||
out = Math.round(count / 1000) + " ±" + Math.round(moe / 1000) + " (" + bench.cycles + ")";
|
||||
} else {
|
||||
out = "E";
|
||||
}
|
||||
|
||||
var padding = this.benchSize - out.length + 1;
|
||||
out = out + new Array(padding).join(" ");
|
||||
print(out);
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = BenchWarmer;
|
||||
@@ -0,0 +1,38 @@
|
||||
var async = require('async'),
|
||||
fs = require('fs'),
|
||||
zlib = require('zlib');
|
||||
|
||||
module.exports = function(grunt, callback) {
|
||||
var distFiles = fs.readdirSync('dist'),
|
||||
distSizes = {};
|
||||
|
||||
async.each(distFiles, function(file, callback) {
|
||||
var content;
|
||||
try {
|
||||
content = fs.readFileSync('dist/' + file);
|
||||
} catch (err) {
|
||||
if (err.code === 'EISDIR') {
|
||||
callback();
|
||||
return;
|
||||
} else {
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
file = file.replace(/\.js/, '').replace(/\./g, '_');
|
||||
distSizes[file] = content.length;
|
||||
|
||||
zlib.gzip(content, function(err, data) {
|
||||
if (err) {
|
||||
throw err;
|
||||
}
|
||||
|
||||
distSizes[file + '_gz'] = data.length;
|
||||
callback();
|
||||
});
|
||||
},
|
||||
function() {
|
||||
grunt.log.writeln('Distribution sizes: ' + JSON.stringify(distSizes, undefined, 2));
|
||||
callback([distSizes]);
|
||||
});
|
||||
};
|
||||
@@ -1,163 +0,0 @@
|
||||
require.paths.push("lib");
|
||||
require.paths.push("vendor");
|
||||
require.paths.push("vendor/dustjs/lib");
|
||||
require.paths.push("vendor/coffee/lib");
|
||||
require.paths.push("vendor/eco/lib");
|
||||
|
||||
|
||||
var BenchWarmer = require("./benchwarmer");
|
||||
Handlebars = require("handlebars");
|
||||
|
||||
var dust = require("dust");
|
||||
var Mustache = require("mustache");
|
||||
var ecoExports = require("eco");
|
||||
|
||||
eco = function(str) {
|
||||
return ecoExports(str);
|
||||
}
|
||||
|
||||
var benchDetails = {
|
||||
string: {
|
||||
context: {},
|
||||
handlebars: "Hello world",
|
||||
dust: "Hello world",
|
||||
mustache: "Hello world",
|
||||
eco: "Hello world"
|
||||
},
|
||||
variables: {
|
||||
context: {name: "Mick", count: 30},
|
||||
handlebars: "Hello {{name}}! You have {{count}} new messages.",
|
||||
dust: "Hello {name}! You have {count} new messages.",
|
||||
mustache: "Hello {{name}}! You have {{count}} new messages.",
|
||||
eco: "Hello <%= @name %>! You have <%= @count %> new messages."
|
||||
},
|
||||
object: {
|
||||
context: { person: { name: "Larry", age: 45 } },
|
||||
handlebars: "{{#with person}}{{name}}{{age}}{{/with}}",
|
||||
dust: "{#person}{name}{age}{/person}",
|
||||
mustache: "{{#person}}{{name}}{{age}}{{/person}}"
|
||||
},
|
||||
array: {
|
||||
context: { names: [{name: "Moe"}, {name: "Larry"}, {name: "Curly"}, {name: "Shemp"}] },
|
||||
handlebars: "{{#each names}}{{name}}{{/each}}",
|
||||
dust: "{#names}{name}{/names}",
|
||||
mustache: "{{#names}}{{name}}{{/names}}",
|
||||
eco: "<% for item in @names: %><%= item.name %><% end %>"
|
||||
},
|
||||
partial: {
|
||||
context: { peeps: [{name: "Moe", count: 15}, {name: "Larry", count: 5}, {name: "Curly", count: 1}] },
|
||||
partials: {
|
||||
mustache: { variables: "Hello {{name}}! You have {{count}} new messages." },
|
||||
handlebars: { variables: "Hello {{name}}! You have {{count}} new messages." }
|
||||
},
|
||||
handlebars: "{{#each peeps}}{{>variables}}{{/each}}",
|
||||
dust: "{#peeps}{>variables/}{/peeps}",
|
||||
mustache: "{{#peeps}}{{>variables}}{{/peeps}}"
|
||||
},
|
||||
recursion: {
|
||||
context: { name: '1', kids: [{ name: '1.1', kids: [{name: '1.1.1', kids: []}] }] },
|
||||
partials: {
|
||||
mustache: { recursion: "{{name}}{{#kids}}{{>recursion}}{{/kids}}" },
|
||||
handlebars: { recursion: "{{name}}{{#each kids}}{{>recursion}}{{/each}}" }
|
||||
},
|
||||
handlebars: "{{name}}{{#each kids}}{{>recursion}}{{/each}}",
|
||||
dust: "{name}{#kids}{>recursion:./}{/kids}",
|
||||
mustache: "{{name}}{{#kids}}{{>recursion}}{{/kids}}"
|
||||
},
|
||||
complex: {
|
||||
handlebars: "<h1>{{header}}</h1>{{#if items}}<ul>{{#each items}}{{#if current}}" +
|
||||
"<li><strong>{{name}}</strong></li>{{^}}" +
|
||||
"<li><a href=\"{{url}}\">{{name}}</a></li>{{/if}}" +
|
||||
"{{/each}}</ul>{{^}}<p>The list is empty.</p>{{/if}}",
|
||||
|
||||
dust: "<h1>{header}</h1>\n" +
|
||||
"{?items}\n" +
|
||||
" <ul>\n" +
|
||||
" {#items}\n" +
|
||||
" {#current}\n" +
|
||||
" <li><strong>{name}</strong></li>\n" +
|
||||
" {:else}\n" +
|
||||
" <li><a href=\"{url}\">{name}</a></li>\n" +
|
||||
" {/current}\n" +
|
||||
" {/items}\n" +
|
||||
" </ul>\n" +
|
||||
"{:else}\n" +
|
||||
" <p>The list is empty.</p>\n" +
|
||||
"{/items}",
|
||||
context: {
|
||||
header: function() {
|
||||
return "Colors";
|
||||
},
|
||||
items: [
|
||||
{name: "red", current: true, url: "#Red"},
|
||||
{name: "green", current: false, url: "#Green"},
|
||||
{name: "blue", current: false, url: "#Blue"}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
handlebarsTemplates = {};
|
||||
ecoTemplates = {};
|
||||
|
||||
var warmer = new BenchWarmer();
|
||||
|
||||
var makeSuite = function(name) {
|
||||
warmer.suite(name, function(bench) {
|
||||
var templateName = name;
|
||||
var details = benchDetails[templateName];
|
||||
var mustachePartials = details.partials && details.partials.mustache;
|
||||
var mustacheSource = details.mustache;
|
||||
var context = details.context;
|
||||
|
||||
var error = function() { throw new Error("EWOT"); };
|
||||
|
||||
|
||||
//bench("dust", function() {
|
||||
//dust.render(templateName, context, function(err, out) { });
|
||||
//});
|
||||
|
||||
bench("handlebars", function() {
|
||||
handlebarsTemplates[templateName](context);
|
||||
});
|
||||
|
||||
//if(ecoTemplates[templateName]) {
|
||||
//bench("eco", function() {
|
||||
//ecoTemplates[templateName](context);
|
||||
//});
|
||||
//} else {
|
||||
//bench("eco", error);
|
||||
//}
|
||||
|
||||
//if(mustacheSource) {
|
||||
//bench("mustache", function() {
|
||||
//Mustache.to_html(mustacheSource, context, mustachePartials);
|
||||
//});
|
||||
//} else {
|
||||
//bench("mustache", error);
|
||||
//}
|
||||
});
|
||||
}
|
||||
|
||||
for(var name in benchDetails) {
|
||||
if(benchDetails.hasOwnProperty(name)) {
|
||||
dust.loadSource(dust.compile(benchDetails[name].dust, name));
|
||||
handlebarsTemplates[name] = Handlebars.compile(benchDetails[name].handlebars);
|
||||
|
||||
if(benchDetails[name].eco) { ecoTemplates[name] = eco(benchDetails[name].eco); }
|
||||
|
||||
var partials = benchDetails[name].partials;
|
||||
if(partials) {
|
||||
for(var partialName in partials.handlebars) {
|
||||
if(partials.handlebars.hasOwnProperty(partialName)) {
|
||||
Handlebars.registerPartial(partialName, partials.handlebars[partialName]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
makeSuite(name);
|
||||
}
|
||||
}
|
||||
|
||||
warmer.bench();
|
||||
@@ -0,0 +1,14 @@
|
||||
var fs = require('fs');
|
||||
|
||||
var metrics = fs.readdirSync(__dirname);
|
||||
metrics.forEach(function(metric) {
|
||||
if (metric === 'index.js' || !(/(.*)\.js$/.test(metric))) {
|
||||
return;
|
||||
}
|
||||
|
||||
var name = RegExp.$1;
|
||||
metric = require('./' + name);
|
||||
if (metric instanceof Function) {
|
||||
module.exports[name] = metric;
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,19 @@
|
||||
var _ = require('underscore'),
|
||||
templates = require('./templates');
|
||||
|
||||
module.exports = function(grunt, callback) {
|
||||
// Deferring to here in case we have a build for parser, etc as part of this grunt exec
|
||||
var Handlebars = require('../lib');
|
||||
|
||||
var templateSizes = {};
|
||||
_.each(templates, function(info, template) {
|
||||
var src = info.handlebars,
|
||||
compiled = Handlebars.precompile(src, {}),
|
||||
knownHelpers = Handlebars.precompile(src, {knownHelpersOnly: true, knownHelpers: info.helpers});
|
||||
|
||||
templateSizes[template] = compiled.length;
|
||||
templateSizes['knownOnly_' + template] = knownHelpers.length;
|
||||
});
|
||||
grunt.log.writeln('Precompiled sizes: ' + JSON.stringify(templateSizes, undefined, 2));
|
||||
callback([templateSizes]);
|
||||
};
|
||||
@@ -0,0 +1,12 @@
|
||||
module.exports = {
|
||||
helpers: {
|
||||
foo: function() {
|
||||
return '';
|
||||
}
|
||||
},
|
||||
context: {
|
||||
bar: true
|
||||
},
|
||||
|
||||
handlebars: '{{foo person "person" 1 true foo=bar foo="person" foo=1 foo=true}}'
|
||||
};
|
||||
@@ -0,0 +1,7 @@
|
||||
module.exports = {
|
||||
context: { names: [{name: 'Moe'}, {name: 'Larry'}, {name: 'Curly'}, {name: 'Shemp'}] },
|
||||
handlebars: '{{#each names}}{{name}}{{/each}}',
|
||||
dust: '{#names}{name}{/names}',
|
||||
mustache: '{{#names}}{{name}}{{/names}}',
|
||||
eco: '<% for item in @names: %><%= item.name %><% end %>'
|
||||
};
|
||||
@@ -0,0 +1,4 @@
|
||||
module.exports = {
|
||||
context: { names: [{name: 'Moe'}, {name: 'Larry'}, {name: 'Curly'}, {name: 'Shemp'}] },
|
||||
handlebars: '{{#names}}{{name}}{{/names}}'
|
||||
};
|
||||
@@ -0,0 +1,14 @@
|
||||
<h1>{header}</h1>
|
||||
{?items}
|
||||
<ul>
|
||||
{#items}
|
||||
{#current}
|
||||
<li><strong>{name}</strong></li>
|
||||
{:else}
|
||||
<li><a href="{url}">{name}</a></li>
|
||||
{/current}
|
||||
{/items}
|
||||
</ul>
|
||||
{:else}
|
||||
<p>The list is empty.</p>
|
||||
{/items}
|
||||
@@ -0,0 +1,14 @@
|
||||
<h1><%= @header() %></h1>
|
||||
<% if @items.length: %>
|
||||
<ul>
|
||||
<% for item in @items: %>
|
||||
<% if item.current: %>
|
||||
<li><strong><%= item.name %></strong></li>
|
||||
<% else: %>
|
||||
<li><a href="<%= item.url %>"><%= item.name %></a></li>
|
||||
<% end %>
|
||||
<% end %>
|
||||
</ul>
|
||||
<% else: %>
|
||||
<p>The list is empty.</p>
|
||||
<% end %>
|
||||
@@ -0,0 +1,14 @@
|
||||
<h1>{{header}}</h1>
|
||||
{{#if items}}
|
||||
<ul>
|
||||
{{#each items}}
|
||||
{{#if current}}
|
||||
<li><strong>{{name}}</strong></li>
|
||||
{{^}}
|
||||
<li><a href="{{url}}">{{name}}</a></li>
|
||||
{{/if}}
|
||||
{{/each}}
|
||||
</ul>
|
||||
{{^}}
|
||||
<p>The list is empty.</p>
|
||||
{{/if}}
|
||||
@@ -0,0 +1,20 @@
|
||||
var fs = require('fs');
|
||||
|
||||
module.exports = {
|
||||
context: {
|
||||
header: function() {
|
||||
return 'Colors';
|
||||
},
|
||||
hasItems: true, // To make things fairer in mustache land due to no `{{if}}` construct on arrays
|
||||
items: [
|
||||
{name: 'red', current: true, url: '#Red'},
|
||||
{name: 'green', current: false, url: '#Green'},
|
||||
{name: 'blue', current: false, url: '#Blue'}
|
||||
]
|
||||
},
|
||||
|
||||
handlebars: fs.readFileSync(__dirname + '/complex.handlebars').toString(),
|
||||
dust: fs.readFileSync(__dirname + '/complex.dust').toString(),
|
||||
eco: fs.readFileSync(__dirname + '/complex.eco').toString(),
|
||||
mustache: fs.readFileSync(__dirname + '/complex.mustache').toString()
|
||||
};
|
||||
@@ -0,0 +1,13 @@
|
||||
<h1>{{header}}</h1>
|
||||
{{#hasItems}}
|
||||
<ul>
|
||||
{{#items}}
|
||||
{{#current}}
|
||||
<li><strong>{{name}}</strong></li>
|
||||
{{/current}}
|
||||
{{^current}}
|
||||
<li><a href="{{url}}">{{name}}</a></li>
|
||||
{{/current}}
|
||||
{{/items}}
|
||||
</ul>
|
||||
{{/hasItems}}
|
||||
@@ -0,0 +1,4 @@
|
||||
module.exports = {
|
||||
context: { names: [{name: 'Moe'}, {name: 'Larry'}, {name: 'Curly'}, {name: 'Shemp'}] },
|
||||
handlebars: '{{#each names}}{{@index}}{{name}}{{/each}}'
|
||||
};
|
||||
@@ -0,0 +1,6 @@
|
||||
module.exports = {
|
||||
context: { names: [{name: 'Moe'}, {name: 'Larry'}, {name: 'Curly'}, {name: 'Shemp'}], foo: 'bar' },
|
||||
handlebars: '{{#each names}}{{../foo}}{{/each}}',
|
||||
mustache: '{{#names}}{{foo}}{{/names}}',
|
||||
eco: '<% for item in @names: %><%= @foo %><% end %>'
|
||||
};
|
||||
@@ -0,0 +1,6 @@
|
||||
module.exports = {
|
||||
context: { names: [{bat: 'foo', name: ['Moe']}, {bat: 'foo', name: ['Larry']}, {bat: 'foo', name: ['Curly']}, {bat: 'foo', name: ['Shemp']}], foo: 'bar' },
|
||||
handlebars: '{{#each names}}{{#each name}}{{../bat}}{{../../foo}}{{/each}}{{/each}}',
|
||||
mustache: '{{#names}}{{#name}}{{bat}}{{foo}}{{/name}}{{/names}}',
|
||||
eco: '<% for item in @names: %><% for child in item.name: %><%= item.bat %><%= @foo %><% end %><% end %>'
|
||||
};
|
||||
@@ -0,0 +1,9 @@
|
||||
var fs = require('fs');
|
||||
|
||||
var templates = fs.readdirSync(__dirname);
|
||||
templates.forEach(function(template) {
|
||||
if (template === 'index.js' || !(/(.*)\.js$/.test(template))) {
|
||||
return;
|
||||
}
|
||||
module.exports[RegExp.$1] = require('./' + RegExp.$1);
|
||||
});
|
||||
@@ -0,0 +1,4 @@
|
||||
module.exports = {
|
||||
context: { person: { name: 'Larry', age: 45 } },
|
||||
handlebars: '{{#person}}{{name}}{{age}}{{/person}}'
|
||||
};
|
||||
@@ -0,0 +1,7 @@
|
||||
module.exports = {
|
||||
context: { person: { name: 'Larry', age: 45 } },
|
||||
handlebars: '{{#with person}}{{name}}{{age}}{{/with}}',
|
||||
dust: '{#person}{name}{age}{/person}',
|
||||
eco: '<%= @person.name %><%= @person.age %>',
|
||||
mustache: '{{#person}}{{name}}{{age}}{{/person}}'
|
||||
};
|
||||
@@ -0,0 +1,10 @@
|
||||
module.exports = {
|
||||
context: { name: '1', kids: [{ name: '1.1', kids: [{name: '1.1.1', kids: []}] }] },
|
||||
partials: {
|
||||
mustache: { recursion: '{{name}}{{#kids}}{{>recursion}}{{/kids}}' },
|
||||
handlebars: { recursion: '{{name}}{{#each kids}}{{>recursion}}{{/each}}' }
|
||||
},
|
||||
handlebars: '{{name}}{{#each kids}}{{>recursion}}{{/each}}',
|
||||
dust: '{name}{#kids}{>recursion:./}{/kids}',
|
||||
mustache: '{{name}}{{#kids}}{{>recursion}}{{/kids}}'
|
||||
};
|
||||
@@ -0,0 +1,11 @@
|
||||
module.exports = {
|
||||
context: { peeps: [{name: 'Moe', count: 15}, {name: 'Larry', count: 5}, {name: 'Curly', count: 1}] },
|
||||
partials: {
|
||||
mustache: { variables: 'Hello {{name}}! You have {{count}} new messages.' },
|
||||
handlebars: { variables: 'Hello {{name}}! You have {{count}} new messages.' }
|
||||
},
|
||||
|
||||
handlebars: '{{#each peeps}}{{>variables}}{{/each}}',
|
||||
dust: '{#peeps}{>variables/}{/peeps}',
|
||||
mustache: '{{#peeps}}{{>variables}}{{/peeps}}'
|
||||
};
|
||||
@@ -0,0 +1,7 @@
|
||||
module.exports = {
|
||||
context: { person: { name: {bar: {baz: 'Larry'}}, age: 45 } },
|
||||
handlebars: '{{person.name.bar.baz}}{{person.age}}{{person.foo}}{{animal.age}}',
|
||||
dust: '{person.name.bar.baz}{person.age}{person.foo}{animal.age}',
|
||||
eco: '<%= @person.name.bar.baz %><%= @person.age %><%= @person.foo %><% if @animal: %><%= @animal.age %><% end %>',
|
||||
mustache: '{{person.name.bar.baz}}{{person.age}}{{person.foo}}{{animal.age}}'
|
||||
};
|
||||
@@ -0,0 +1,7 @@
|
||||
module.exports = {
|
||||
context: {},
|
||||
handlebars: 'Hello world',
|
||||
dust: 'Hello world',
|
||||
mustache: 'Hello world',
|
||||
eco: 'Hello world'
|
||||
};
|
||||
@@ -0,0 +1,14 @@
|
||||
module.exports = {
|
||||
helpers: {
|
||||
echo: function(value) {
|
||||
return 'foo ' + value;
|
||||
},
|
||||
header: function() {
|
||||
return 'Colors';
|
||||
}
|
||||
},
|
||||
handlebars: '{{echo (header)}}',
|
||||
eco: '<%= @echo(@header()) %>'
|
||||
};
|
||||
|
||||
module.exports.context = module.exports.helpers;
|
||||
@@ -0,0 +1,8 @@
|
||||
module.exports = {
|
||||
context: {name: 'Mick', count: 30},
|
||||
handlebars: 'Hello {{name}}! You have {{count}} new messages.',
|
||||
dust: 'Hello {name}! You have {count} new messages.',
|
||||
mustache: 'Hello {{name}}! You have {{count}} new messages.',
|
||||
eco: 'Hello <%= @name %>! You have <%= @count %> new messages.'
|
||||
};
|
||||
|
||||
@@ -0,0 +1,130 @@
|
||||
var _ = require('underscore'),
|
||||
runner = require('./util/template-runner'),
|
||||
|
||||
eco, dust, Handlebars, Mustache;
|
||||
|
||||
try {
|
||||
dust = require('dustjs-linkedin');
|
||||
} catch (err) { /* NOP */ }
|
||||
|
||||
try {
|
||||
Mustache = require('mustache');
|
||||
} catch (err) { /* NOP */ }
|
||||
|
||||
try {
|
||||
eco = require('eco');
|
||||
} catch (err) { /* NOP */ }
|
||||
|
||||
function error() {
|
||||
throw new Error('EWOT');
|
||||
}
|
||||
|
||||
function makeSuite(bench, name, template, handlebarsOnly) {
|
||||
// Create aliases to minimize any impact from having to walk up the closure tree.
|
||||
var templateName = name,
|
||||
|
||||
context = template.context,
|
||||
partials = template.partials,
|
||||
|
||||
handlebarsOut,
|
||||
compatOut,
|
||||
dustOut,
|
||||
ecoOut,
|
||||
mustacheOut;
|
||||
|
||||
var handlebar = Handlebars.compile(template.handlebars, {data: false}),
|
||||
compat = Handlebars.compile(template.handlebars, {data: false, compat: true}),
|
||||
options = {helpers: template.helpers};
|
||||
_.each(template.partials && template.partials.handlebars, function(partial, partialName) {
|
||||
Handlebars.registerPartial(partialName, Handlebars.compile(partial, {data: false}));
|
||||
});
|
||||
|
||||
handlebarsOut = handlebar(context, options);
|
||||
bench('handlebars', function() {
|
||||
handlebar(context, options);
|
||||
});
|
||||
|
||||
compatOut = compat(context, options);
|
||||
bench('compat', function() {
|
||||
compat(context, options);
|
||||
});
|
||||
|
||||
if (handlebarsOnly) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (dust) {
|
||||
if (template.dust) {
|
||||
dustOut = false;
|
||||
dust.loadSource(dust.compile(template.dust, templateName));
|
||||
|
||||
dust.render(templateName, context, function(err, out) { dustOut = out; });
|
||||
|
||||
bench('dust', function() {
|
||||
dust.render(templateName, context, function() {});
|
||||
});
|
||||
} else {
|
||||
bench('dust', error);
|
||||
}
|
||||
}
|
||||
|
||||
if (eco) {
|
||||
if (template.eco) {
|
||||
var ecoTemplate = eco.compile(template.eco);
|
||||
|
||||
ecoOut = ecoTemplate(context);
|
||||
|
||||
bench('eco', function() {
|
||||
ecoTemplate(context);
|
||||
});
|
||||
} else {
|
||||
bench('eco', error);
|
||||
}
|
||||
}
|
||||
|
||||
if (Mustache) {
|
||||
var mustacheSource = template.mustache,
|
||||
mustachePartials = partials && partials.mustache;
|
||||
|
||||
if (mustacheSource) {
|
||||
mustacheOut = Mustache.to_html(mustacheSource, context, mustachePartials);
|
||||
|
||||
bench('mustache', function() {
|
||||
Mustache.to_html(mustacheSource, context, mustachePartials);
|
||||
});
|
||||
} else {
|
||||
bench('mustache', error);
|
||||
}
|
||||
}
|
||||
|
||||
// Hack around whitespace until we have whitespace control
|
||||
handlebarsOut = handlebarsOut.replace(/\s/g, '');
|
||||
function compare(b, lang) {
|
||||
if (b == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
b = b.replace(/\s/g, '');
|
||||
|
||||
if (handlebarsOut !== b) {
|
||||
throw new Error('Template output mismatch: ' + name
|
||||
+ '\n\nHandlebars: ' + handlebarsOut
|
||||
+ '\n\n' + lang + ': ' + b);
|
||||
}
|
||||
}
|
||||
|
||||
compare(compatOut, 'compat');
|
||||
compare(dustOut, 'dust');
|
||||
compare(ecoOut, 'eco');
|
||||
compare(mustacheOut, 'mustache');
|
||||
}
|
||||
|
||||
module.exports = function(grunt, callback) {
|
||||
// Deferring load incase we are being run inline with the grunt build
|
||||
Handlebars = require('../lib');
|
||||
|
||||
console.log('Execution Throughput');
|
||||
runner(grunt, makeSuite, function(times, scaled) {
|
||||
callback(scaled);
|
||||
});
|
||||
};
|
||||
@@ -0,0 +1,197 @@
|
||||
var _ = require('underscore'),
|
||||
Benchmark = require('benchmark');
|
||||
|
||||
function BenchWarmer() {
|
||||
this.benchmarks = [];
|
||||
this.currentBenches = [];
|
||||
this.names = [];
|
||||
this.times = {};
|
||||
this.minimum = Infinity;
|
||||
this.maximum = -Infinity;
|
||||
this.errors = {};
|
||||
}
|
||||
|
||||
var print = require('sys').print;
|
||||
|
||||
BenchWarmer.prototype = {
|
||||
winners: function(benches) {
|
||||
return Benchmark.filter(benches, 'fastest');
|
||||
},
|
||||
suite: function(suite, fn) {
|
||||
this.suiteName = suite;
|
||||
this.times[suite] = {};
|
||||
this.first = true;
|
||||
|
||||
var self = this;
|
||||
|
||||
fn(function(name, benchFn) {
|
||||
self.push(name, benchFn);
|
||||
});
|
||||
},
|
||||
push: function(name, fn) {
|
||||
if (this.names.indexOf(name) == -1) {
|
||||
this.names.push(name);
|
||||
}
|
||||
|
||||
var first = this.first, suiteName = this.suiteName, self = this;
|
||||
this.first = false;
|
||||
|
||||
var bench = new Benchmark(fn, {
|
||||
name: this.suiteName + ': ' + name,
|
||||
onComplete: function() {
|
||||
if (first) { self.startLine(suiteName); }
|
||||
self.writeBench(bench);
|
||||
self.currentBenches.push(bench);
|
||||
}, onError: function() {
|
||||
self.errors[this.name] = this;
|
||||
}
|
||||
});
|
||||
bench.suiteName = this.suiteName;
|
||||
bench.benchName = name;
|
||||
|
||||
this.benchmarks.push(bench);
|
||||
},
|
||||
|
||||
bench: function(callback) {
|
||||
var self = this;
|
||||
|
||||
this.printHeader('ops/msec', true);
|
||||
|
||||
Benchmark.invoke(this.benchmarks, {
|
||||
name: 'run',
|
||||
onComplete: function() {
|
||||
self.scaleTimes();
|
||||
|
||||
self.startLine('');
|
||||
|
||||
print('\n');
|
||||
self.printHeader('scaled');
|
||||
_.each(self.scaled, function(value, name) {
|
||||
self.startLine(name);
|
||||
|
||||
_.each(self.names, function(lang) {
|
||||
self.writeValue(value[lang] || '');
|
||||
});
|
||||
});
|
||||
print('\n');
|
||||
|
||||
var errors = false, prop, bench;
|
||||
for (prop in self.errors) {
|
||||
if (self.errors.hasOwnProperty(prop)
|
||||
&& self.errors[prop].error.message !== 'EWOT') {
|
||||
errors = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (errors) {
|
||||
print('\n\nErrors:\n');
|
||||
for (prop in self.errors) {
|
||||
if (self.errors.hasOwnProperty(prop)
|
||||
&& self.errors[prop].error.message !== 'EWOT') {
|
||||
bench = self.errors[prop];
|
||||
print('\n' + bench.name + ':\n');
|
||||
print(bench.error.message);
|
||||
if (bench.error.stack) {
|
||||
print(bench.error.stack.join('\n'));
|
||||
}
|
||||
print('\n');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
callback();
|
||||
}
|
||||
});
|
||||
|
||||
print('\n');
|
||||
},
|
||||
|
||||
scaleTimes: function() {
|
||||
var scaled = this.scaled = {};
|
||||
_.each(this.times, function(times, name) {
|
||||
var output = scaled[name] = {};
|
||||
|
||||
_.each(times, function(time, lang) {
|
||||
output[lang] = ((time - this.minimum) / (this.maximum - this.minimum) * 100).toFixed(2);
|
||||
}, this);
|
||||
}, this);
|
||||
},
|
||||
|
||||
printHeader: function(title, winners) {
|
||||
var benchSize = 0, names = this.names, i, l;
|
||||
|
||||
for (i = 0, l = names.length; i < l; i++) {
|
||||
var name = names[i];
|
||||
|
||||
if (benchSize < name.length) { benchSize = name.length; }
|
||||
}
|
||||
|
||||
this.nameSize = benchSize + 2;
|
||||
this.benchSize = 20;
|
||||
var horSize = 0;
|
||||
|
||||
this.startLine(title);
|
||||
horSize = horSize + this.benchSize;
|
||||
for (i = 0, l = names.length; i < l; i++) {
|
||||
this.writeValue(names[i]);
|
||||
horSize = horSize + this.benchSize;
|
||||
}
|
||||
|
||||
if (winners) {
|
||||
print('WINNER(S)');
|
||||
horSize = horSize + 'WINNER(S)'.length;
|
||||
}
|
||||
|
||||
print('\n' + new Array(horSize + 1).join('-'));
|
||||
},
|
||||
|
||||
startLine: function(name) {
|
||||
var winners = Benchmark.map(this.winners(this.currentBenches), function(bench) {
|
||||
return bench.name.split(': ')[1];
|
||||
});
|
||||
|
||||
this.currentBenches = [];
|
||||
|
||||
print(winners.join(', '));
|
||||
print('\n');
|
||||
|
||||
if (name) {
|
||||
this.writeValue(name);
|
||||
}
|
||||
},
|
||||
writeBench: function(bench) {
|
||||
var out;
|
||||
|
||||
if (!bench.error) {
|
||||
var count = bench.hz,
|
||||
moe = count * bench.stats.rme / 100,
|
||||
minimum,
|
||||
maximum;
|
||||
|
||||
count = Math.round(count / 1000);
|
||||
moe = Math.round(moe / 1000);
|
||||
minimum = count - moe;
|
||||
maximum = count + moe;
|
||||
|
||||
out = count + ' ±' + moe + ' (' + bench.cycles + ')';
|
||||
|
||||
this.times[bench.suiteName][bench.benchName] = count;
|
||||
this.minimum = Math.min(this.minimum, minimum);
|
||||
this.maximum = Math.max(this.maximum, maximum);
|
||||
} else if (bench.error.message === 'EWOT') {
|
||||
out = 'NA';
|
||||
} else {
|
||||
out = 'E';
|
||||
}
|
||||
this.writeValue(out);
|
||||
},
|
||||
|
||||
writeValue: function(out) {
|
||||
var padding = this.benchSize - out.length + 1;
|
||||
out = out + new Array(padding).join(' ');
|
||||
print(out);
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = BenchWarmer;
|
||||
@@ -0,0 +1,29 @@
|
||||
var _ = require('underscore'),
|
||||
BenchWarmer = require('./benchwarmer'),
|
||||
templates = require('../templates');
|
||||
|
||||
module.exports = function(grunt, makeSuite, callback) {
|
||||
var warmer = new BenchWarmer();
|
||||
|
||||
var handlebarsOnly = grunt.option('handlebars-only'),
|
||||
grep = grunt.option('grep');
|
||||
if (grep) {
|
||||
grep = new RegExp(grep);
|
||||
}
|
||||
|
||||
_.each(templates, function(template, name) {
|
||||
if (!template.handlebars || (grep && !grep.test(name))) {
|
||||
return;
|
||||
}
|
||||
|
||||
warmer.suite(name, function(bench) {
|
||||
makeSuite(bench, name, template, handlebarsOnly);
|
||||
});
|
||||
});
|
||||
|
||||
warmer.bench(function() {
|
||||
if (callback) {
|
||||
callback(warmer.times, warmer.scaled);
|
||||
}
|
||||
});
|
||||
};
|
||||
+82
-93
@@ -1,12 +1,33 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
var optimist = require('optimist')
|
||||
.usage('Precompile handlebar templates.\nUsage: $0 template...', {
|
||||
.usage('Precompile handlebar templates.\nUsage: $0 [template|directory]...', {
|
||||
'f': {
|
||||
'type': 'string',
|
||||
'description': 'Output File',
|
||||
'alias': 'output'
|
||||
},
|
||||
'map': {
|
||||
'type': 'string',
|
||||
'description': 'Source Map File'
|
||||
},
|
||||
'a': {
|
||||
'type': 'boolean',
|
||||
'description': 'Exports amd style (require.js)',
|
||||
'alias': 'amd'
|
||||
},
|
||||
'c': {
|
||||
'type': 'string',
|
||||
'description': 'Exports CommonJS style, path to Handlebars module',
|
||||
'alias': 'commonjs',
|
||||
'default': null
|
||||
},
|
||||
'h': {
|
||||
'type': 'string',
|
||||
'description': 'Path to handlebar.js (only valid for amd-style)',
|
||||
'alias': 'handlebarPath',
|
||||
'default': ''
|
||||
},
|
||||
'k': {
|
||||
'type': 'string',
|
||||
'description': 'Known helpers',
|
||||
@@ -22,118 +43,86 @@ var optimist = require('optimist')
|
||||
'description': 'Minimize output',
|
||||
'alias': 'min'
|
||||
},
|
||||
'n': {
|
||||
'type': 'string',
|
||||
'description': 'Template namespace',
|
||||
'alias': 'namespace',
|
||||
'default': 'Handlebars.templates'
|
||||
},
|
||||
's': {
|
||||
'type': 'boolean',
|
||||
'description': 'Output template function only.',
|
||||
'alias': 'simple'
|
||||
},
|
||||
'N': {
|
||||
'type': 'string',
|
||||
'description': 'Name of passed string templates. Optional if running in a simple mode. Required when operating on multiple templates.',
|
||||
'alias': 'name'
|
||||
},
|
||||
'i': {
|
||||
'type': 'string',
|
||||
'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.',
|
||||
'alias': 'string'
|
||||
},
|
||||
'r': {
|
||||
'type': 'string',
|
||||
'description': 'Template root. Base value that will be stripped from template names.',
|
||||
'alias': 'root'
|
||||
},
|
||||
'p': {
|
||||
'type': 'boolean',
|
||||
'description': 'Compiling a partial template',
|
||||
'alias': 'partial'
|
||||
},
|
||||
'd': {
|
||||
'type': 'boolean',
|
||||
'description': 'Include data when compiling',
|
||||
'alias': 'data'
|
||||
},
|
||||
'e': {
|
||||
'type': 'string',
|
||||
'description': 'Template extension.',
|
||||
'alias': 'extension',
|
||||
'default': 'handlebars'
|
||||
},
|
||||
'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'
|
||||
}
|
||||
})
|
||||
|
||||
.wrap(120)
|
||||
.check(function(argv) {
|
||||
var template = [0];
|
||||
if (!argv._.length) {
|
||||
throw 'Must define at least one template or directory.';
|
||||
}
|
||||
|
||||
argv._.forEach(function(template) {
|
||||
try {
|
||||
fs.statSync(template);
|
||||
} catch (err) {
|
||||
throw 'Unable to open template file "' + template + '"';
|
||||
}
|
||||
});
|
||||
})
|
||||
.check(function(argv) {
|
||||
if (argv.simple && argv.min) {
|
||||
throw 'Unable to minimze simple output';
|
||||
}
|
||||
if (argv.simple && (argv._.length !== 1 || fs.statSync(argv._[0]).isDirectory())) {
|
||||
throw 'Unable to output multiple templates in simple mode';
|
||||
if (argv.version) {
|
||||
return;
|
||||
}
|
||||
});
|
||||
|
||||
var fs = require('fs'),
|
||||
handlebars = require('../lib/handlebars'),
|
||||
basename = require('path').basename,
|
||||
uglify = require('uglify-js');
|
||||
|
||||
var argv = optimist.argv,
|
||||
template = argv._[0];
|
||||
var argv = optimist.argv;
|
||||
argv.files = argv._;
|
||||
delete argv._;
|
||||
|
||||
// Convert the known list into a hash
|
||||
var known = {};
|
||||
if (argv.known && !Array.isArray(argv.known)) {
|
||||
argv.known = [argv.known];
|
||||
}
|
||||
if (argv.known) {
|
||||
for (var i = 0, len = argv.known.length; i < len; i++) {
|
||||
known[argv.known[i]] = true;
|
||||
var Precompiler = require('../dist/cjs/precompiler');
|
||||
Precompiler.loadTemplates(argv, function(err, opts) {
|
||||
if (err) {
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
var output = [];
|
||||
if (!argv.simple) {
|
||||
output.push('(function() {\n var template = Handlebars.template, templates = Handlebars.templates = Handlebars.templates || {};\n');
|
||||
}
|
||||
function processTemplate(template, root) {
|
||||
var path = template,
|
||||
stat = fs.statSync(path);
|
||||
if (stat.isDirectory()) {
|
||||
fs.readdirSync(template).map(function(file) {
|
||||
var path = template + '/' + file;
|
||||
|
||||
if (/\.handlebars$/.test(path) || fs.statSync(path).isDirectory()) {
|
||||
processTemplate(path, root || template);
|
||||
}
|
||||
});
|
||||
if (opts.help || (!opts.templates.length && !opts.version)) {
|
||||
optimist.showHelp();
|
||||
} else {
|
||||
var data = fs.readFileSync(path, 'utf8');
|
||||
|
||||
var options = {
|
||||
knownHelpers: known,
|
||||
knownHelpersOnly: argv.o
|
||||
};
|
||||
|
||||
// Clean the template name
|
||||
if (!root) {
|
||||
template = basename(template);
|
||||
} else if (template.indexOf(root) === 0) {
|
||||
template = template.substring(root.length+1);
|
||||
}
|
||||
template = template.replace(/\.handlebars$/, '');
|
||||
|
||||
if (argv.simple) {
|
||||
output.push(handlebars.precompile(data, options) + '\n');
|
||||
} else {
|
||||
output.push('templates[\'' + template + '\'] = template(' + handlebars.precompile(data, options) + ');\n');
|
||||
}
|
||||
Precompiler.cli(opts);
|
||||
}
|
||||
}
|
||||
|
||||
argv._.forEach(function(template) {
|
||||
processTemplate(template, argv.root);
|
||||
});
|
||||
|
||||
// Output the content
|
||||
if (!argv.simple) {
|
||||
output.push('})();');
|
||||
}
|
||||
output = output.join('');
|
||||
|
||||
if (argv.min) {
|
||||
var ast = uglify.parser.parse(output);
|
||||
ast = uglify.uglify.ast_mangle(ast);
|
||||
ast = uglify.uglify.ast_squeeze(ast);
|
||||
output = uglify.uglify.gen_code(ast);
|
||||
}
|
||||
|
||||
if (argv.output) {
|
||||
fs.writeFileSync(argv.output, output, 'utf8');
|
||||
} else {
|
||||
console.log(output);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"name": "handlebars",
|
||||
"version": "4.0.2",
|
||||
"main": "handlebars.js",
|
||||
"license": "MIT",
|
||||
"dependencies": {}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"name": "handlebars",
|
||||
"repo": "components/handlebars.js",
|
||||
"version": "1.0.0",
|
||||
"main": "handlebars.js",
|
||||
"scripts": [
|
||||
"handlebars.js"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
{
|
||||
"name": "components/handlebars.js",
|
||||
"description": "Handlebars.js and Mustache are both logicless templating languages that keep the view and the code separated like we all know they should be.",
|
||||
"homepage": "http://handlebarsjs.com",
|
||||
"license": "MIT",
|
||||
"type": "component",
|
||||
"keywords": [
|
||||
"handlebars",
|
||||
"mustache",
|
||||
"html"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Chris Wanstrath",
|
||||
"homepage": "http://chriswanstrath.com"
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
"robloach/component-installer": "*"
|
||||
},
|
||||
"extra": {
|
||||
"component": {
|
||||
"name": "handlebars",
|
||||
"scripts": [
|
||||
"handlebars.js"
|
||||
],
|
||||
"files": [
|
||||
"handlebars.runtime.js"
|
||||
],
|
||||
"shim": {
|
||||
"exports": "Handlebars"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
# -*- encoding: utf-8 -*-
|
||||
require 'json'
|
||||
|
||||
package = JSON.parse(File.read('bower.json'))
|
||||
|
||||
Gem::Specification.new do |gem|
|
||||
gem.name = "handlebars-source"
|
||||
gem.authors = ["Yehuda Katz"]
|
||||
gem.email = ["wycats@gmail.com"]
|
||||
gem.date = Time.now.strftime("%Y-%m-%d")
|
||||
gem.description = %q{Handlebars.js source code wrapper for (pre)compilation gems.}
|
||||
gem.summary = %q{Handlebars.js source code wrapper}
|
||||
gem.homepage = "https://github.com/wycats/handlebars.js/"
|
||||
gem.version = package["version"].sub "-", "."
|
||||
gem.license = "MIT"
|
||||
|
||||
gem.files = [
|
||||
'handlebars.js',
|
||||
'handlebars.runtime.js',
|
||||
'lib/handlebars/source.rb'
|
||||
]
|
||||
end
|
||||
@@ -0,0 +1,17 @@
|
||||
<?xml version="1.0"?>
|
||||
<package>
|
||||
<metadata>
|
||||
<id>handlebars.js</id>
|
||||
<version>4.0.2</version>
|
||||
<authors>handlebars.js Authors</authors>
|
||||
<licenseUrl>https://github.com/wycats/handlebars.js/blob/master/LICENSE</licenseUrl>
|
||||
<projectUrl>https://github.com/wycats/handlebars.js/</projectUrl>
|
||||
<requireLicenseAcceptance>false</requireLicenseAcceptance>
|
||||
<description>Extension of the Mustache logicless template language</description>
|
||||
<releaseNotes></releaseNotes>
|
||||
<tags>handlebars mustache template html</tags>
|
||||
</metadata>
|
||||
<files>
|
||||
<file src="handlebars.js" target="Content\Scripts" />
|
||||
</files>
|
||||
</package>
|
||||
@@ -0,0 +1,11 @@
|
||||
module Handlebars
|
||||
module Source
|
||||
def self.bundled_path
|
||||
File.expand_path("../../../handlebars.js", __FILE__)
|
||||
end
|
||||
|
||||
def self.runtime_bundled_path
|
||||
File.expand_path("../../../handlebars.runtime.js", __FILE__)
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,316 @@
|
||||
# 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);
|
||||
```
|
||||
|
||||
|
||||
### 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;
|
||||
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.
|
||||
|
||||
```javascript
|
||||
function MyCompiler() {
|
||||
Handlebars.JavaScriptCompiler.apply(this, arguments);
|
||||
}
|
||||
MyCompiler.prototype = Object.create(Handlebars.JavaScriptCompiler);
|
||||
|
||||
MyCompiler.nameLookup = function(parent, name, type) {
|
||||
if (type === 'partial') {
|
||||
return 'MyPartialList[' + JSON.stringify(name) ']';
|
||||
} else {
|
||||
return Handlebars.JavaScriptCompiler.prototype.nameLookup.call(this, parent, name, type);
|
||||
}
|
||||
};
|
||||
|
||||
var env = Handlebars.create();
|
||||
env.JavaScriptCompiler = MyCompiler;
|
||||
env.compile('my template');
|
||||
```
|
||||
@@ -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 syntaxs are derivitives 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/wycats/handlebars.js/blob/master/lib/handlebars/decorators/inline.js) implementation provides an example of decorators being used for both metadata and wrapping behaviors.
|
||||
+36
-9
@@ -1,14 +1,41 @@
|
||||
var Handlebars = require("./handlebars/base");
|
||||
module.exports = Handlebars;
|
||||
import runtime from './handlebars.runtime';
|
||||
|
||||
// Each of these augment the Handlebars object. No need to setup here.
|
||||
// (This is done to easily share code between commonjs and browse envs)
|
||||
require("./handlebars/utils");
|
||||
// Compiler imports
|
||||
import AST from './handlebars/compiler/ast';
|
||||
import { parser as Parser, parse } from './handlebars/compiler/base';
|
||||
import { Compiler, compile, precompile } from './handlebars/compiler/compiler';
|
||||
import JavaScriptCompiler from './handlebars/compiler/javascript-compiler';
|
||||
import Visitor from './handlebars/compiler/visitor';
|
||||
|
||||
require("./handlebars/compiler");
|
||||
require("./handlebars/runtime");
|
||||
import noConflict from './handlebars/no-conflict';
|
||||
|
||||
// BEGIN(BROWSER)
|
||||
let _create = runtime.create;
|
||||
function create() {
|
||||
let hb = _create();
|
||||
|
||||
// END(BROWSER)
|
||||
hb.compile = function(input, options) {
|
||||
return compile(input, options, hb);
|
||||
};
|
||||
hb.precompile = function(input, options) {
|
||||
return precompile(input, options, hb);
|
||||
};
|
||||
|
||||
hb.AST = AST;
|
||||
hb.Compiler = Compiler;
|
||||
hb.JavaScriptCompiler = JavaScriptCompiler;
|
||||
hb.Parser = Parser;
|
||||
hb.parse = parse;
|
||||
|
||||
return hb;
|
||||
}
|
||||
|
||||
let inst = create();
|
||||
inst.create = create;
|
||||
|
||||
noConflict(inst);
|
||||
|
||||
inst.Visitor = Visitor;
|
||||
|
||||
inst['default'] = inst;
|
||||
|
||||
export default inst;
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
import * as base from './handlebars/base';
|
||||
|
||||
// Each of these augment the Handlebars object. No need to setup here.
|
||||
// (This is done to easily share code between commonjs and browse envs)
|
||||
import SafeString from './handlebars/safe-string';
|
||||
import Exception from './handlebars/exception';
|
||||
import * as Utils from './handlebars/utils';
|
||||
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
|
||||
function create() {
|
||||
let hb = new base.HandlebarsEnvironment();
|
||||
|
||||
Utils.extend(hb, base);
|
||||
hb.SafeString = SafeString;
|
||||
hb.Exception = Exception;
|
||||
hb.Utils = Utils;
|
||||
hb.escapeExpression = Utils.escapeExpression;
|
||||
|
||||
hb.VM = runtime;
|
||||
hb.template = function(spec) {
|
||||
return runtime.template(spec, hb);
|
||||
};
|
||||
|
||||
return hb;
|
||||
}
|
||||
|
||||
let inst = create();
|
||||
inst.create = create;
|
||||
|
||||
noConflict(inst);
|
||||
|
||||
inst['default'] = inst;
|
||||
|
||||
export default inst;
|
||||
+63
-104
@@ -1,119 +1,78 @@
|
||||
// BEGIN(BROWSER)
|
||||
import {createFrame, extend, toString} from './utils';
|
||||
import Exception from './exception';
|
||||
import {registerDefaultHelpers} from './helpers';
|
||||
import {registerDefaultDecorators} from './decorators';
|
||||
import logger from './logger';
|
||||
|
||||
/*jshint eqnull:true*/
|
||||
this.Handlebars = {};
|
||||
export const VERSION = '4.0.2';
|
||||
export const COMPILER_REVISION = 7;
|
||||
|
||||
(function(Handlebars) {
|
||||
|
||||
Handlebars.VERSION = "1.0.rc.1";
|
||||
|
||||
Handlebars.helpers = {};
|
||||
Handlebars.partials = {};
|
||||
|
||||
Handlebars.registerHelper = function(name, fn, inverse) {
|
||||
if(inverse) { fn.not = inverse; }
|
||||
this.helpers[name] = fn;
|
||||
export const REVISION_CHANGES = {
|
||||
1: '<= 1.0.rc.2', // 1.0.rc.2 is actually rev2 but doesn't report it
|
||||
2: '== 1.0.0-rc.3',
|
||||
3: '== 1.0.0-rc.4',
|
||||
4: '== 1.x.x',
|
||||
5: '== 2.0.0-alpha.x',
|
||||
6: '>= 2.0.0-beta.1',
|
||||
7: '>= 4.0.0'
|
||||
};
|
||||
|
||||
Handlebars.registerPartial = function(name, str) {
|
||||
this.partials[name] = str;
|
||||
};
|
||||
const objectType = '[object Object]';
|
||||
|
||||
Handlebars.registerHelper('helperMissing', function(arg) {
|
||||
if(arguments.length === 2) {
|
||||
return undefined;
|
||||
} else {
|
||||
throw new Error("Could not find property '" + arg + "'");
|
||||
}
|
||||
});
|
||||
export function HandlebarsEnvironment(helpers, partials, decorators) {
|
||||
this.helpers = helpers || {};
|
||||
this.partials = partials || {};
|
||||
this.decorators = decorators || {};
|
||||
|
||||
var toString = Object.prototype.toString, functionType = "[object Function]";
|
||||
registerDefaultHelpers(this);
|
||||
registerDefaultDecorators(this);
|
||||
}
|
||||
|
||||
Handlebars.registerHelper('blockHelperMissing', function(context, options) {
|
||||
var inverse = options.inverse || function() {}, fn = options.fn;
|
||||
HandlebarsEnvironment.prototype = {
|
||||
constructor: HandlebarsEnvironment,
|
||||
|
||||
logger: logger,
|
||||
log: logger.log,
|
||||
|
||||
var ret = "";
|
||||
var type = toString.call(context);
|
||||
|
||||
if(type === functionType) { context = context.call(this); }
|
||||
|
||||
if(context === true) {
|
||||
return fn(this);
|
||||
} else if(context === false || context == null) {
|
||||
return inverse(this);
|
||||
} else if(type === "[object Array]") {
|
||||
if(context.length > 0) {
|
||||
for(var i=0, j=context.length; i<j; i++) {
|
||||
ret = ret + fn(context[i]);
|
||||
}
|
||||
registerHelper: function(name, fn) {
|
||||
if (toString.call(name) === objectType) {
|
||||
if (fn) { throw new Exception('Arg not supported with multiple helpers'); }
|
||||
extend(this.helpers, name);
|
||||
} else {
|
||||
ret = inverse(this);
|
||||
this.helpers[name] = fn;
|
||||
}
|
||||
return ret;
|
||||
} else {
|
||||
return fn(context);
|
||||
},
|
||||
unregisterHelper: function(name) {
|
||||
delete this.helpers[name];
|
||||
},
|
||||
|
||||
registerPartial: function(name, partial) {
|
||||
if (toString.call(name) === objectType) {
|
||||
extend(this.partials, name);
|
||||
} else {
|
||||
if (typeof partial === 'undefined') {
|
||||
throw new Exception('Attempting to register a partial as undefined');
|
||||
}
|
||||
this.partials[name] = partial;
|
||||
}
|
||||
},
|
||||
unregisterPartial: function(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];
|
||||
}
|
||||
});
|
||||
|
||||
Handlebars.K = function() {};
|
||||
|
||||
Handlebars.createFrame = Object.create || function(object) {
|
||||
Handlebars.K.prototype = object;
|
||||
var obj = new Handlebars.K();
|
||||
Handlebars.K.prototype = null;
|
||||
return obj;
|
||||
};
|
||||
|
||||
Handlebars.registerHelper('each', function(context, options) {
|
||||
var fn = options.fn, inverse = options.inverse;
|
||||
var ret = "", data;
|
||||
|
||||
if (options.data) {
|
||||
data = Handlebars.createFrame(options.data);
|
||||
}
|
||||
|
||||
if(context && context.length > 0) {
|
||||
for(var i=0, j=context.length; i<j; i++) {
|
||||
if (data) { data.index = i; }
|
||||
ret = ret + fn(context[i], { data: data });
|
||||
}
|
||||
} else {
|
||||
ret = inverse(this);
|
||||
}
|
||||
return ret;
|
||||
});
|
||||
|
||||
Handlebars.registerHelper('if', function(context, options) {
|
||||
var type = toString.call(context);
|
||||
if(type === functionType) { context = context.call(this); }
|
||||
|
||||
if(!context || Handlebars.Utils.isEmpty(context)) {
|
||||
return options.inverse(this);
|
||||
} else {
|
||||
return options.fn(this);
|
||||
}
|
||||
});
|
||||
|
||||
Handlebars.registerHelper('unless', function(context, options) {
|
||||
var fn = options.fn, inverse = options.inverse;
|
||||
options.fn = inverse;
|
||||
options.inverse = fn;
|
||||
|
||||
return Handlebars.helpers['if'].call(this, context, options);
|
||||
});
|
||||
|
||||
Handlebars.registerHelper('with', function(context, options) {
|
||||
return options.fn(context);
|
||||
});
|
||||
|
||||
Handlebars.registerHelper('log', function(context) {
|
||||
Handlebars.log(context);
|
||||
});
|
||||
|
||||
}(this.Handlebars));
|
||||
|
||||
// END(BROWSER)
|
||||
|
||||
module.exports = this.Handlebars;
|
||||
export let log = logger.log;
|
||||
|
||||
export {createFrame, logger};
|
||||
|
||||
+19
-114
@@ -1,123 +1,28 @@
|
||||
var Handlebars = require('./base');
|
||||
|
||||
// BEGIN(BROWSER)
|
||||
(function() {
|
||||
|
||||
Handlebars.AST = {};
|
||||
|
||||
Handlebars.AST.ProgramNode = function(statements, inverse) {
|
||||
this.type = "program";
|
||||
this.statements = statements;
|
||||
if(inverse) { this.inverse = new Handlebars.AST.ProgramNode(inverse); }
|
||||
};
|
||||
|
||||
Handlebars.AST.MustacheNode = function(rawParams, hash, unescaped) {
|
||||
this.type = "mustache";
|
||||
this.escaped = !unescaped;
|
||||
this.hash = hash;
|
||||
|
||||
var id = this.id = rawParams[0];
|
||||
var params = this.params = rawParams.slice(1);
|
||||
|
||||
// a mustache is an eligible helper if:
|
||||
// * its id is simple (a single part, not `this` or `..`)
|
||||
var eligibleHelper = this.eligibleHelper = id.isSimple;
|
||||
|
||||
let AST = {
|
||||
// Public API used to evaluate derived attributes regarding AST nodes
|
||||
helpers: {
|
||||
// a mustache is definitely a helper if:
|
||||
// * it is an eligible helper, and
|
||||
// * it has at least one parameter or hash segment
|
||||
this.isHelper = eligibleHelper && (params.length || hash);
|
||||
helperExpression: function(node) {
|
||||
return (node.type === 'SubExpression')
|
||||
|| ((node.type === 'MustacheStatement' || node.type === 'BlockStatement')
|
||||
&& !!((node.params && node.params.length) || node.hash));
|
||||
},
|
||||
|
||||
// if a mustache is an eligible helper but not a definite
|
||||
// helper, it is ambiguous, and will be resolved in a later
|
||||
// pass or at runtime.
|
||||
};
|
||||
|
||||
Handlebars.AST.PartialNode = function(id, context) {
|
||||
this.type = "partial";
|
||||
|
||||
// TODO: disallow complex IDs
|
||||
|
||||
this.id = id;
|
||||
this.context = context;
|
||||
};
|
||||
|
||||
var verifyMatch = function(open, close) {
|
||||
if(open.original !== close.original) {
|
||||
throw new Handlebars.Exception(open.original + " doesn't match " + close.original);
|
||||
}
|
||||
};
|
||||
|
||||
Handlebars.AST.BlockNode = function(mustache, program, inverse, close) {
|
||||
verifyMatch(mustache.id, close);
|
||||
this.type = "block";
|
||||
this.mustache = mustache;
|
||||
this.program = program;
|
||||
this.inverse = inverse;
|
||||
|
||||
if (this.inverse && !this.program) {
|
||||
this.isInverse = true;
|
||||
}
|
||||
};
|
||||
|
||||
Handlebars.AST.ContentNode = function(string) {
|
||||
this.type = "content";
|
||||
this.string = string;
|
||||
};
|
||||
|
||||
Handlebars.AST.HashNode = function(pairs) {
|
||||
this.type = "hash";
|
||||
this.pairs = pairs;
|
||||
};
|
||||
|
||||
Handlebars.AST.IdNode = function(parts) {
|
||||
this.type = "ID";
|
||||
this.original = parts.join(".");
|
||||
|
||||
var dig = [], depth = 0;
|
||||
|
||||
for(var i=0,l=parts.length; i<l; i++) {
|
||||
var part = parts[i];
|
||||
|
||||
if(part === "..") { depth++; }
|
||||
else if(part === "." || part === "this") { this.isScoped = true; }
|
||||
else { dig.push(part); }
|
||||
}
|
||||
|
||||
this.parts = dig;
|
||||
this.string = dig.join('.');
|
||||
this.depth = depth;
|
||||
scopedId: function(path) {
|
||||
return (/^\.|this\b/).test(path.original);
|
||||
},
|
||||
|
||||
// an ID is simple if it only has one part, and that part is not
|
||||
// `..` or `this`.
|
||||
this.isSimple = parts.length === 1 && !this.isScoped && depth === 0;
|
||||
};
|
||||
simpleId: function(path) {
|
||||
return path.parts.length === 1 && !AST.helpers.scopedId(path) && !path.depth;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Handlebars.AST.DataNode = function(id) {
|
||||
this.type = "DATA";
|
||||
this.id = id;
|
||||
};
|
||||
|
||||
Handlebars.AST.StringNode = function(string) {
|
||||
this.type = "STRING";
|
||||
this.string = string;
|
||||
};
|
||||
|
||||
Handlebars.AST.IntegerNode = function(integer) {
|
||||
this.type = "INTEGER";
|
||||
this.integer = integer;
|
||||
};
|
||||
|
||||
Handlebars.AST.BooleanNode = function(bool) {
|
||||
this.type = "BOOLEAN";
|
||||
this.bool = bool;
|
||||
};
|
||||
|
||||
Handlebars.AST.CommentNode = function(comment) {
|
||||
this.type = "comment";
|
||||
this.comment = comment;
|
||||
};
|
||||
|
||||
})();
|
||||
// END(BROWSER)
|
||||
|
||||
// Must be exported as an object rather than the root of the module as the jison lexer
|
||||
// must modify the object to operate properly.
|
||||
export default AST;
|
||||
|
||||
@@ -1,27 +1,24 @@
|
||||
var handlebars = require("./parser").parser;
|
||||
var Handlebars = require("../base");
|
||||
import parser from './parser';
|
||||
import WhitespaceControl from './whitespace-control';
|
||||
import * as Helpers from './helpers';
|
||||
import { extend } from '../utils';
|
||||
|
||||
// BEGIN(BROWSER)
|
||||
Handlebars.Parser = handlebars;
|
||||
export { parser };
|
||||
|
||||
Handlebars.parse = function(string) {
|
||||
Handlebars.Parser.yy = Handlebars.AST;
|
||||
return Handlebars.Parser.parse(string);
|
||||
};
|
||||
let yy = {};
|
||||
extend(yy, Helpers);
|
||||
|
||||
Handlebars.print = function(ast) {
|
||||
return new Handlebars.PrintVisitor().accept(ast);
|
||||
};
|
||||
export function parse(input, options) {
|
||||
// Just return if an already-compiled AST was passed in.
|
||||
if (input.type === 'Program') { return input; }
|
||||
|
||||
Handlebars.logger = {
|
||||
DEBUG: 0, INFO: 1, WARN: 2, ERROR: 3, level: 3,
|
||||
parser.yy = yy;
|
||||
|
||||
// override in the host environment
|
||||
log: function(level, str) {}
|
||||
};
|
||||
// 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);
|
||||
};
|
||||
|
||||
Handlebars.log = function(level, str) { Handlebars.logger.log(level, str); };
|
||||
|
||||
// END(BROWSER)
|
||||
|
||||
module.exports = Handlebars;
|
||||
let strip = new WhitespaceControl(options);
|
||||
return strip.accept(parser.parse(input));
|
||||
}
|
||||
|
||||
@@ -0,0 +1,168 @@
|
||||
/* global define */
|
||||
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 asusme 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 = [];
|
||||
|
||||
for (let key in obj) {
|
||||
if (obj.hasOwnProperty(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;
|
||||
|
||||
+480
-988
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,212 @@
|
||||
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.substr(1, token.length - 2);
|
||||
} else {
|
||||
return token;
|
||||
}
|
||||
}
|
||||
|
||||
export function stripFlags(open, close) {
|
||||
return {
|
||||
open: open.charAt(2) === '~',
|
||||
close: close.charAt(close.length - 3) === '~'
|
||||
};
|
||||
}
|
||||
|
||||
export function stripComment(comment) {
|
||||
return comment.replace(/^\{\{~?\!-?-?/, '')
|
||||
.replace(/-?-?~?\}\}$/, '');
|
||||
}
|
||||
|
||||
export function preparePath(data, parts, loc) {
|
||||
loc = this.locInfo(loc);
|
||||
|
||||
let original = data ? '@' : '',
|
||||
dig = [],
|
||||
depth = 0,
|
||||
depthString = '';
|
||||
|
||||
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++;
|
||||
depthString += '../';
|
||||
}
|
||||
} else {
|
||||
dig.push(part);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
type: 'PathExpression',
|
||||
data,
|
||||
depth,
|
||||
parts: dig,
|
||||
original,
|
||||
loc
|
||||
};
|
||||
}
|
||||
|
||||
export function prepareMustache(path, params, hash, open, strip, locInfo) {
|
||||
// Must use charAt to support IE pre-10
|
||||
let escapeFlag = open.charAt(3) || open.charAt(2),
|
||||
escaped = escapeFlag !== '{' && escapeFlag !== '&';
|
||||
|
||||
let decorator = (/\*/.test(open));
|
||||
return {
|
||||
type: decorator ? 'Decorator' : 'MustacheStatement',
|
||||
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
|
||||
};
|
||||
|
||||
return {
|
||||
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);
|
||||
}
|
||||
|
||||
let decorator = (/\*/.test(openBlock.open));
|
||||
|
||||
program.blockParams = openBlock.blockParams;
|
||||
|
||||
let inverse,
|
||||
inverseStrip;
|
||||
|
||||
if (inverseAndProgram) {
|
||||
if (decorator) {
|
||||
throw new Exception('Unexpected inverse block on decorator', inverseAndProgram);
|
||||
}
|
||||
|
||||
if (inverseAndProgram.chain) {
|
||||
inverseAndProgram.program.body[0].closeStrip = close.strip;
|
||||
}
|
||||
|
||||
inverseStrip = inverseAndProgram.strip;
|
||||
inverse = inverseAndProgram.program;
|
||||
}
|
||||
|
||||
if (inverted) {
|
||||
inverted = inverse;
|
||||
inverse = program;
|
||||
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;
|
||||
|
||||
/* istanbul ignore else */
|
||||
if (firstLoc && lastLoc) {
|
||||
loc = {
|
||||
source: firstLoc.source,
|
||||
start: {
|
||||
line: firstLoc.start.line,
|
||||
column: firstLoc.start.column
|
||||
},
|
||||
end: {
|
||||
line: lastLoc.end.line,
|
||||
column: lastLoc.end.column
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
type: 'Program',
|
||||
body: statements,
|
||||
strip: {},
|
||||
loc: loc
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
export function preparePartialBlock(open, program, close, locInfo) {
|
||||
validateClose(open, close);
|
||||
|
||||
return {
|
||||
type: 'PartialBlockStatement',
|
||||
name: open.path,
|
||||
params: open.params,
|
||||
hash: open.hash,
|
||||
program,
|
||||
openStrip: open.strip,
|
||||
closeStrip: close && close.strip,
|
||||
loc: this.locInfo(locInfo)
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
// Each of these module will augment the Handlebars object as it loads. No need to perform addition operations
|
||||
module.exports = require("./base");
|
||||
require("./visitor");
|
||||
require("./printer");
|
||||
|
||||
require("./ast");
|
||||
require("./compiler");
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,30 +1,43 @@
|
||||
var Handlebars = require("./base");
|
||||
/* eslint-disable new-cap */
|
||||
import Visitor from './visitor';
|
||||
|
||||
// BEGIN(BROWSER)
|
||||
Handlebars.PrintVisitor = function() { this.padding = 0; };
|
||||
Handlebars.PrintVisitor.prototype = new Handlebars.Visitor();
|
||||
export function print(ast) {
|
||||
return new PrintVisitor().accept(ast);
|
||||
}
|
||||
|
||||
Handlebars.PrintVisitor.prototype.pad = function(string, newline) {
|
||||
var out = "";
|
||||
export function PrintVisitor() {
|
||||
this.padding = 0;
|
||||
}
|
||||
|
||||
for(var i=0,l=this.padding; i<l; i++) {
|
||||
out = out + " ";
|
||||
PrintVisitor.prototype = new Visitor();
|
||||
|
||||
PrintVisitor.prototype.pad = function(string) {
|
||||
let out = '';
|
||||
|
||||
for (let i = 0, l = this.padding; i < l; i++) {
|
||||
out += ' ';
|
||||
}
|
||||
|
||||
out = out + string;
|
||||
|
||||
if(newline !== false) { out = out + "\n"; }
|
||||
out += string + '\n';
|
||||
return out;
|
||||
};
|
||||
|
||||
Handlebars.PrintVisitor.prototype.program = function(program) {
|
||||
var out = "",
|
||||
statements = program.statements,
|
||||
inverse = program.inverse,
|
||||
PrintVisitor.prototype.Program = function(program) {
|
||||
let out = '',
|
||||
body = program.body,
|
||||
i, l;
|
||||
|
||||
for(i=0, l=statements.length; i<l; i++) {
|
||||
out = out + this.accept(statements[i]);
|
||||
if (program.blockParams) {
|
||||
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--;
|
||||
@@ -32,23 +45,31 @@ Handlebars.PrintVisitor.prototype.program = function(program) {
|
||||
return out;
|
||||
};
|
||||
|
||||
Handlebars.PrintVisitor.prototype.block = function(block) {
|
||||
var out = "";
|
||||
PrintVisitor.prototype.MustacheStatement = function(mustache) {
|
||||
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++;
|
||||
out = out + this.accept(block.mustache);
|
||||
out += this.pad(this.SubExpression(block));
|
||||
if (block.program) {
|
||||
out = out + this.pad("PROGRAM:");
|
||||
out += this.pad('PROGRAM:');
|
||||
this.padding++;
|
||||
out = out + this.accept(block.program);
|
||||
out += this.accept(block.program);
|
||||
this.padding--;
|
||||
}
|
||||
if (block.inverse) {
|
||||
if (block.program) { this.padding++; }
|
||||
out = out + this.pad("{{^}}");
|
||||
out += this.pad('{{^}}');
|
||||
this.padding++;
|
||||
out = out + this.accept(block.inverse);
|
||||
out += this.accept(block.inverse);
|
||||
this.padding--;
|
||||
if (block.program) { this.padding--; }
|
||||
}
|
||||
@@ -57,71 +78,94 @@ Handlebars.PrintVisitor.prototype.block = function(block) {
|
||||
return out;
|
||||
};
|
||||
|
||||
Handlebars.PrintVisitor.prototype.mustache = function(mustache) {
|
||||
var params = mustache.params, paramStrings = [], hash;
|
||||
PrintVisitor.prototype.PartialStatement = function(partial) {
|
||||
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]));
|
||||
}
|
||||
|
||||
params = "[" + paramStrings.join(", ") + "]";
|
||||
params = '[' + paramStrings.join(', ') + ']';
|
||||
|
||||
hash = mustache.hash ? " " + this.accept(mustache.hash) : "";
|
||||
hash = sexpr.hash ? ' ' + this.accept(sexpr.hash) : '';
|
||||
|
||||
return this.pad("{{ " + this.accept(mustache.id) + " " + params + hash + " }}");
|
||||
return this.accept(sexpr.path) + ' ' + params + hash;
|
||||
};
|
||||
|
||||
Handlebars.PrintVisitor.prototype.partial = function(partial) {
|
||||
var content = this.accept(partial.id);
|
||||
if(partial.context) { content = content + " " + this.accept(partial.context); }
|
||||
return this.pad("{{> " + content + " }}");
|
||||
PrintVisitor.prototype.PathExpression = function(id) {
|
||||
let path = id.parts.join('/');
|
||||
return (id.data ? '@' : '') + 'PATH:' + path;
|
||||
};
|
||||
|
||||
Handlebars.PrintVisitor.prototype.hash = function(hash) {
|
||||
var pairs = hash.pairs;
|
||||
var joinedPairs = [], left, right;
|
||||
|
||||
for(var i=0, l=pairs.length; i<l; i++) {
|
||||
left = pairs[i][0];
|
||||
right = this.accept(pairs[i][1]);
|
||||
joinedPairs.push( left + "=" + right );
|
||||
PrintVisitor.prototype.StringLiteral = function(string) {
|
||||
return '"' + string.value + '"';
|
||||
};
|
||||
|
||||
PrintVisitor.prototype.NumberLiteral = function(number) {
|
||||
return 'NUMBER{' + number.value + '}';
|
||||
};
|
||||
|
||||
PrintVisitor.prototype.BooleanLiteral = function(bool) {
|
||||
return 'BOOLEAN{' + bool.value + '}';
|
||||
};
|
||||
|
||||
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(', ') + '}';
|
||||
};
|
||||
|
||||
Handlebars.PrintVisitor.prototype.STRING = function(string) {
|
||||
return '"' + string.string + '"';
|
||||
PrintVisitor.prototype.HashPair = function(pair) {
|
||||
return pair.key + '=' + this.accept(pair.value);
|
||||
};
|
||||
|
||||
Handlebars.PrintVisitor.prototype.INTEGER = function(integer) {
|
||||
return "INTEGER{" + integer.integer + "}";
|
||||
};
|
||||
|
||||
Handlebars.PrintVisitor.prototype.BOOLEAN = function(bool) {
|
||||
return "BOOLEAN{" + bool.bool + "}";
|
||||
};
|
||||
|
||||
Handlebars.PrintVisitor.prototype.ID = function(id) {
|
||||
var path = id.parts.join("/");
|
||||
if(id.parts.length > 1) {
|
||||
return "PATH:" + path;
|
||||
} else {
|
||||
return "ID:" + path;
|
||||
}
|
||||
};
|
||||
|
||||
Handlebars.PrintVisitor.prototype.DATA = function(data) {
|
||||
return "@" + data.id;
|
||||
};
|
||||
|
||||
Handlebars.PrintVisitor.prototype.content = function(content) {
|
||||
return this.pad("CONTENT[ '" + content.string + "' ]");
|
||||
};
|
||||
|
||||
Handlebars.PrintVisitor.prototype.comment = function(comment) {
|
||||
return this.pad("{{! '" + comment.comment + "' }}");
|
||||
};
|
||||
// END(BROWSER)
|
||||
|
||||
exports.PrintVisitor = Handlebars.PrintVisitor;
|
||||
/* eslint-enable new-cap */
|
||||
|
||||
@@ -1,13 +1,129 @@
|
||||
var Handlebars = require("./base");
|
||||
import Exception from '../exception';
|
||||
|
||||
// BEGIN(BROWSER)
|
||||
function Visitor() {
|
||||
this.parents = [];
|
||||
}
|
||||
|
||||
Handlebars.Visitor = function() {};
|
||||
Visitor.prototype = {
|
||||
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--;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
Handlebars.Visitor.prototype = {
|
||||
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');
|
||||
}
|
||||
};
|
||||
// END(BROWSER)
|
||||
|
||||
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;
|
||||
|
||||
@@ -0,0 +1,216 @@
|
||||
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 preceeded 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,6 @@
|
||||
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;
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
|
||||
const errorProps = ['description', 'fileName', 'lineNumber', 'message', 'name', 'number', 'stack'];
|
||||
|
||||
function Exception(message, node) {
|
||||
let loc = node && node.loc,
|
||||
line,
|
||||
column;
|
||||
if (loc) {
|
||||
line = loc.start.line;
|
||||
column = loc.start.column;
|
||||
|
||||
message += ' - ' + line + ':' + column;
|
||||
}
|
||||
|
||||
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.
|
||||
for (let idx = 0; idx < errorProps.length; idx++) {
|
||||
this[errorProps[idx]] = tmp[errorProps[idx]];
|
||||
}
|
||||
|
||||
/* istanbul ignore else */
|
||||
if (Error.captureStackTrace) {
|
||||
Error.captureStackTrace(this, Exception);
|
||||
}
|
||||
|
||||
if (loc) {
|
||||
this.lineNumber = line;
|
||||
this.column = column;
|
||||
}
|
||||
}
|
||||
|
||||
Exception.prototype = new Error();
|
||||
|
||||
export default Exception;
|
||||
@@ -0,0 +1,17 @@
|
||||
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);
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
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,83 @@
|
||||
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) {
|
||||
// Don't iterate over undefined values since we can't execute blocks against them
|
||||
// in non-strict (js) mode.
|
||||
if (context[field] == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
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++) {
|
||||
execIteration(i, i, i === context.length - 1);
|
||||
}
|
||||
} else {
|
||||
let priorKey;
|
||||
|
||||
for (let key in context) {
|
||||
if (context.hasOwnProperty(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,13 @@
|
||||
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,20 @@
|
||||
import {isEmpty, isFunction} from '../utils';
|
||||
|
||||
export default function(instance) {
|
||||
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) || 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});
|
||||
});
|
||||
}
|
||||
@@ -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,5 @@
|
||||
export default function(instance) {
|
||||
instance.registerHelper('lookup', function(obj, field) {
|
||||
return obj && obj[field];
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
import {appendContextPath, blockParams, createFrame, isEmpty, isFunction} from '../utils';
|
||||
|
||||
export default function(instance) {
|
||||
instance.registerHelper('with', function(context, options) {
|
||||
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,33 @@
|
||||
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 = logger.methodMap.indexOf(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];
|
||||
if (!console[method]) { // eslint-disable-line no-console
|
||||
method = 'log';
|
||||
}
|
||||
console[method](...message); // eslint-disable-line no-console
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export default logger;
|
||||
@@ -0,0 +1,12 @@
|
||||
/* global window */
|
||||
export default function(Handlebars) {
|
||||
/* istanbul ignore next */
|
||||
let root = typeof global !== 'undefined' ? global : window,
|
||||
$Handlebars = root.Handlebars;
|
||||
/* istanbul ignore next */
|
||||
Handlebars.noConflict = function() {
|
||||
if (root.Handlebars === Handlebars) {
|
||||
root.Handlebars = $Handlebars;
|
||||
}
|
||||
};
|
||||
}
|
||||
+260
-60
@@ -1,68 +1,268 @@
|
||||
var Handlebars = require("./base");
|
||||
import * as Utils from './utils';
|
||||
import Exception from './exception';
|
||||
import { COMPILER_REVISION, REVISION_CHANGES, createFrame } from './base';
|
||||
|
||||
// BEGIN(BROWSER)
|
||||
Handlebars.VM = {
|
||||
template: function(templateSpec) {
|
||||
// Just add water
|
||||
var container = {
|
||||
escapeExpression: Handlebars.Utils.escapeExpression,
|
||||
invokePartial: Handlebars.VM.invokePartial,
|
||||
programs: [],
|
||||
program: function(i, fn, data) {
|
||||
var programWrapper = this.programs[i];
|
||||
if(data) {
|
||||
return Handlebars.VM.program(fn, data);
|
||||
} else if(programWrapper) {
|
||||
return programWrapper;
|
||||
} else {
|
||||
programWrapper = this.programs[i] = Handlebars.VM.program(fn);
|
||||
return programWrapper;
|
||||
}
|
||||
},
|
||||
programWithDepth: Handlebars.VM.programWithDepth,
|
||||
noop: Handlebars.VM.noop
|
||||
};
|
||||
export function checkRevision(compilerInfo) {
|
||||
const compilerRevision = compilerInfo && compilerInfo[0] || 1,
|
||||
currentRevision = COMPILER_REVISION;
|
||||
|
||||
return function(context, options) {
|
||||
options = options || {};
|
||||
return templateSpec.call(container, Handlebars, context, options.helpers, options.partials, options.data);
|
||||
};
|
||||
},
|
||||
|
||||
programWithDepth: function(fn, data, $depth) {
|
||||
var args = Array.prototype.slice.call(arguments, 2);
|
||||
|
||||
return function(context, options) {
|
||||
options = options || {};
|
||||
|
||||
return fn.apply(this, [context, options.data || data].concat(args));
|
||||
};
|
||||
},
|
||||
program: function(fn, data) {
|
||||
return function(context, options) {
|
||||
options = options || {};
|
||||
|
||||
return fn(context, options.data || data);
|
||||
};
|
||||
},
|
||||
noop: function() { return ""; },
|
||||
invokePartial: function(partial, name, context, helpers, partials, data) {
|
||||
var options = { helpers: helpers, partials: partials, data: data };
|
||||
|
||||
if(partial === undefined) {
|
||||
throw new Handlebars.Exception("The partial " + name + " could not be found");
|
||||
} else if(partial instanceof Function) {
|
||||
return partial(context, options);
|
||||
} else if (!Handlebars.compile) {
|
||||
throw new Handlebars.Exception("The partial " + name + " could not be compiled when running in runtime-only mode");
|
||||
if (compilerRevision !== currentRevision) {
|
||||
if (compilerRevision < currentRevision) {
|
||||
const runtimeVersions = REVISION_CHANGES[currentRevision],
|
||||
compilerVersions = REVISION_CHANGES[compilerRevision];
|
||||
throw new Exception('Template was precompiled with an older version of Handlebars than the current runtime. ' +
|
||||
'Please update your precompiler to a newer version (' + runtimeVersions + ') or downgrade your runtime to an older version (' + compilerVersions + ').');
|
||||
} else {
|
||||
partials[name] = Handlebars.compile(partial);
|
||||
return partials[name](context, options);
|
||||
// Use the embedded version info since the runtime doesn't know about this revision yet
|
||||
throw new Exception('Template was precompiled with a newer version of Handlebars than the current runtime. ' +
|
||||
'Please update your runtime to a newer version (' + compilerInfo[1] + ').');
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
Handlebars.template = Handlebars.VM.template;
|
||||
export function template(templateSpec, env) {
|
||||
/* istanbul ignore next */
|
||||
if (!env) {
|
||||
throw new Exception('No environment passed to template');
|
||||
}
|
||||
if (!templateSpec || !templateSpec.main) {
|
||||
throw new Exception('Unknown template object: ' + typeof templateSpec);
|
||||
}
|
||||
|
||||
// END(BROWSER)
|
||||
templateSpec.main.decorator = templateSpec.main_d;
|
||||
|
||||
// Note: Using env.VM references rather than local var references throughout this section to allow
|
||||
// for external users to override these as psuedo-supported APIs.
|
||||
env.VM.checkRevision(templateSpec.compiler);
|
||||
|
||||
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 result = env.VM.invokePartial.call(this, partial, context, options);
|
||||
|
||||
if (result == null && env.compile) {
|
||||
options.partials[options.name] = env.compile(partial, templateSpec.compilerOptions, env);
|
||||
result = options.partials[options.name](context, options);
|
||||
}
|
||||
if (result != null) {
|
||||
if (options.indent) {
|
||||
let lines = result.split('\n');
|
||||
for (let i = 0, l = lines.length; i < l; i++) {
|
||||
if (!lines[i] && i + 1 === l) {
|
||||
break;
|
||||
}
|
||||
|
||||
lines[i] = options.indent + lines[i];
|
||||
}
|
||||
result = lines.join('\n');
|
||||
}
|
||||
return result;
|
||||
} else {
|
||||
throw new Exception('The partial ' + options.name + ' could not be compiled when running in runtime-only mode');
|
||||
}
|
||||
}
|
||||
|
||||
// Just add water
|
||||
let container = {
|
||||
strict: function(obj, name) {
|
||||
if (!(name in obj)) {
|
||||
throw new Exception('"' + name + '" not defined in ' + obj);
|
||||
}
|
||||
return obj[name];
|
||||
},
|
||||
lookup: function(depths, name) {
|
||||
const len = depths.length;
|
||||
for (let i = 0; i < len; i++) {
|
||||
if (depths[i] && depths[i][name] != null) {
|
||||
return depths[i][name];
|
||||
}
|
||||
}
|
||||
},
|
||||
lambda: function(current, context) {
|
||||
return typeof current === 'function' ? current.call(context) : current;
|
||||
},
|
||||
|
||||
escapeExpression: Utils.escapeExpression,
|
||||
invokePartial: invokePartialWrapper,
|
||||
|
||||
fn: function(i) {
|
||||
let ret = templateSpec[i];
|
||||
ret.decorator = templateSpec[i + '_d'];
|
||||
return ret;
|
||||
},
|
||||
|
||||
programs: [],
|
||||
program: function(i, data, declaredBlockParams, blockParams, depths) {
|
||||
let programWrapper = this.programs[i],
|
||||
fn = this.fn(i);
|
||||
if (data || depths || blockParams || declaredBlockParams) {
|
||||
programWrapper = wrapProgram(this, i, fn, data, declaredBlockParams, blockParams, depths);
|
||||
} else if (!programWrapper) {
|
||||
programWrapper = this.programs[i] = wrapProgram(this, i, fn);
|
||||
}
|
||||
return programWrapper;
|
||||
},
|
||||
|
||||
data: function(value, depth) {
|
||||
while (value && depth--) {
|
||||
value = value._parent;
|
||||
}
|
||||
return value;
|
||||
},
|
||||
merge: function(param, common) {
|
||||
let obj = param || common;
|
||||
|
||||
if (param && common && (param !== common)) {
|
||||
obj = Utils.extend({}, common, param);
|
||||
}
|
||||
|
||||
return obj;
|
||||
},
|
||||
|
||||
noop: env.VM.noop,
|
||||
compilerInfo: templateSpec.compiler
|
||||
};
|
||||
|
||||
function ret(context, options = {}) {
|
||||
let data = options.data;
|
||||
|
||||
ret._setup(options);
|
||||
if (!options.partial && templateSpec.useData) {
|
||||
data = initData(context, data);
|
||||
}
|
||||
let depths,
|
||||
blockParams = templateSpec.useBlockParams ? [] : undefined;
|
||||
if (templateSpec.useDepths) {
|
||||
if (options.depths) {
|
||||
depths = context !== options.depths[0] ? [context].concat(options.depths) : options.depths;
|
||||
} else {
|
||||
depths = [context];
|
||||
}
|
||||
}
|
||||
|
||||
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._setup = function(options) {
|
||||
if (!options.partial) {
|
||||
container.helpers = container.merge(options.helpers, env.helpers);
|
||||
|
||||
if (templateSpec.usePartial) {
|
||||
container.partials = container.merge(options.partials, env.partials);
|
||||
}
|
||||
if (templateSpec.usePartial || templateSpec.useDecorators) {
|
||||
container.decorators = container.merge(options.decorators, env.decorators);
|
||||
}
|
||||
} else {
|
||||
container.helpers = options.helpers;
|
||||
container.partials = options.partials;
|
||||
container.decorators = options.decorators;
|
||||
}
|
||||
};
|
||||
|
||||
ret._child = function(i, data, blockParams, depths) {
|
||||
if (templateSpec.useBlockParams && !blockParams) {
|
||||
throw new Exception('must pass block params');
|
||||
}
|
||||
if (templateSpec.useDepths && !depths) {
|
||||
throw new Exception('must pass parent depths');
|
||||
}
|
||||
|
||||
return wrapProgram(container, i, templateSpec[i], data, 0, blockParams, depths);
|
||||
};
|
||||
return ret;
|
||||
}
|
||||
|
||||
export function wrapProgram(container, i, fn, data, declaredBlockParams, blockParams, depths) {
|
||||
function prog(context, options = {}) {
|
||||
let currentDepths = depths;
|
||||
if (depths && context !== depths[0]) {
|
||||
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);
|
||||
|
||||
prog.program = i;
|
||||
prog.depth = depths ? depths.length : 0;
|
||||
prog.blockParams = declaredBlockParams || 0;
|
||||
return prog;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
export function invokePartial(partial, context, options) {
|
||||
options.partial = true;
|
||||
if (options.ids) {
|
||||
options.data.contextPath = options.ids[0] || options.data.contextPath;
|
||||
}
|
||||
|
||||
let partialBlock;
|
||||
if (options.fn && options.fn !== noop) {
|
||||
partialBlock = options.data['partial-block'] = options.fn;
|
||||
|
||||
if (partialBlock.partials) {
|
||||
options.partials = Utils.extend({}, options.partials, partialBlock.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);
|
||||
}
|
||||
}
|
||||
|
||||
export function noop() { return ''; }
|
||||
|
||||
function initData(context, data) {
|
||||
if (!data || !('root' in data)) {
|
||||
data = data ? createFrame(data) : {};
|
||||
data.root = context;
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
// Build out our basic SafeString type
|
||||
function SafeString(string) {
|
||||
this.string = string;
|
||||
}
|
||||
|
||||
SafeString.prototype.toString = SafeString.prototype.toHTML = function() {
|
||||
return '' + this.string;
|
||||
};
|
||||
|
||||
export default SafeString;
|
||||
+101
-61
@@ -1,68 +1,108 @@
|
||||
var Handlebars = require("./base");
|
||||
|
||||
// BEGIN(BROWSER)
|
||||
Handlebars.Exception = function(message) {
|
||||
var tmp = Error.prototype.constructor.apply(this, arguments);
|
||||
|
||||
for (var p in tmp) {
|
||||
if (tmp.hasOwnProperty(p)) { this[p] = tmp[p]; }
|
||||
}
|
||||
|
||||
this.message = tmp.message;
|
||||
};
|
||||
Handlebars.Exception.prototype = new Error();
|
||||
|
||||
// Build out our basic SafeString type
|
||||
Handlebars.SafeString = function(string) {
|
||||
this.string = string;
|
||||
};
|
||||
Handlebars.SafeString.prototype.toString = function() {
|
||||
return this.string.toString();
|
||||
const escape = {
|
||||
'&': '&',
|
||||
'<': '<',
|
||||
'>': '>',
|
||||
'"': '"',
|
||||
"'": ''',
|
||||
'`': '`',
|
||||
'=': '='
|
||||
};
|
||||
|
||||
(function() {
|
||||
var escape = {
|
||||
"<": "<",
|
||||
">": ">",
|
||||
'"': """,
|
||||
"'": "'",
|
||||
"`": "`"
|
||||
};
|
||||
const badChars = /[&<>"'`=]/g,
|
||||
possible = /[&<>"'`=]/;
|
||||
|
||||
var badChars = /&(?!\w+;)|[<>"'`]/g;
|
||||
var possible = /[&<>"'`]/;
|
||||
function escapeChar(chr) {
|
||||
return escape[chr];
|
||||
}
|
||||
|
||||
var escapeChar = function(chr) {
|
||||
return escape[chr] || "&";
|
||||
};
|
||||
|
||||
Handlebars.Utils = {
|
||||
escapeExpression: function(string) {
|
||||
// don't escape SafeStrings, since they're already safe
|
||||
if (string instanceof Handlebars.SafeString) {
|
||||
return string.toString();
|
||||
} else if (string == null || string === false) {
|
||||
return "";
|
||||
}
|
||||
|
||||
if(!possible.test(string)) { return string; }
|
||||
return string.replace(badChars, escapeChar);
|
||||
},
|
||||
|
||||
isEmpty: function(value) {
|
||||
if (typeof value === "undefined") {
|
||||
return true;
|
||||
} else if (value === null) {
|
||||
return true;
|
||||
} else if (value === false) {
|
||||
return true;
|
||||
} else if(Object.prototype.toString.call(value) === "[object Array]" && value.length === 0) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
export function extend(obj /* , ...source */) {
|
||||
for (let i = 1; i < arguments.length; i++) {
|
||||
for (let key in arguments[i]) {
|
||||
if (Object.prototype.hasOwnProperty.call(arguments[i], key)) {
|
||||
obj[key] = arguments[i][key];
|
||||
}
|
||||
}
|
||||
};
|
||||
})();
|
||||
// END(BROWSER)
|
||||
}
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
||||
export let toString = Object.prototype.toString;
|
||||
|
||||
// Sourced from lodash
|
||||
// https://github.com/bestiejs/lodash/blob/master/LICENSE.txt
|
||||
/* eslint-disable func-style */
|
||||
let isFunction = function(value) {
|
||||
return typeof value === 'function';
|
||||
};
|
||||
// fallback for older versions of Chrome and Safari
|
||||
/* istanbul ignore next */
|
||||
if (isFunction(/x/)) {
|
||||
isFunction = function(value) {
|
||||
return typeof value === 'function' && toString.call(value) === '[object Function]';
|
||||
};
|
||||
}
|
||||
export {isFunction};
|
||||
/* eslint-enable func-style */
|
||||
|
||||
/* istanbul ignore next */
|
||||
export const isArray = 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) {
|
||||
if (typeof string !== 'string') {
|
||||
// don't escape SafeStrings, since they're already safe
|
||||
if (string && string.toHTML) {
|
||||
return string.toHTML();
|
||||
} else if (string == null) {
|
||||
return '';
|
||||
} else if (!string) {
|
||||
return string + '';
|
||||
}
|
||||
|
||||
// Force a string conversion as this will be done by the append regardless and
|
||||
// the regex test will do this transparently behind the scenes, causing issues if
|
||||
// an object's to string has escaped characters in it.
|
||||
string = '' + string;
|
||||
}
|
||||
|
||||
if (!possible.test(string)) { return string; }
|
||||
return string.replace(badChars, escapeChar);
|
||||
}
|
||||
|
||||
export function isEmpty(value) {
|
||||
if (!value && value !== 0) {
|
||||
return true;
|
||||
} else if (isArray(value) && value.length === 0) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
export function 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) {
|
||||
return (contextPath ? contextPath + '.' : '') + id;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
// USAGE:
|
||||
// var handlebars = require('handlebars');
|
||||
/* eslint-disable no-var */
|
||||
|
||||
// var local = handlebars.create();
|
||||
|
||||
var handlebars = require('../dist/cjs/handlebars')['default'];
|
||||
|
||||
var printer = require('../dist/cjs/handlebars/compiler/printer');
|
||||
handlebars.PrintVisitor = printer.PrintVisitor;
|
||||
handlebars.print = printer.print;
|
||||
|
||||
module.exports = handlebars;
|
||||
|
||||
// Publish a Node.js require() handler for .handlebars and .hbs files
|
||||
function extension(module, filename) {
|
||||
var fs = require('fs');
|
||||
var templateString = fs.readFileSync(filename, 'utf8');
|
||||
module.exports = handlebars.compile(templateString);
|
||||
}
|
||||
/* istanbul ignore else */
|
||||
if (typeof require !== 'undefined' && require.extensions) {
|
||||
require.extensions['.handlebars'] = extension;
|
||||
require.extensions['.hbs'] = extension;
|
||||
}
|
||||
@@ -0,0 +1,276 @@
|
||||
/* eslint-disable no-console */
|
||||
import Async from 'async';
|
||||
import fs from 'fs';
|
||||
import * as Handlebars from './handlebars';
|
||||
import {basename} from 'path';
|
||||
import {SourceMapConsumer, SourceNode} from 'source-map';
|
||||
import uglify from 'uglify-js';
|
||||
|
||||
module.exports.loadTemplates = function(opts, callback) {
|
||||
loadStrings(opts, function(err, strings) {
|
||||
if (err) {
|
||||
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) {
|
||||
if (opts.version) {
|
||||
console.log(Handlebars.VERSION);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!opts.templates.length && !opts.hasDirectory) {
|
||||
throw new Handlebars.Exception('Must define at least one template or directory.');
|
||||
}
|
||||
|
||||
if (opts.simple && opts.min) {
|
||||
throw new Handlebars.Exception('Unable to minimize simple output');
|
||||
}
|
||||
|
||||
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
|
||||
let known = {};
|
||||
if (opts.known && !Array.isArray(opts.known)) {
|
||||
opts.known = [opts.known];
|
||||
}
|
||||
if (opts.known) {
|
||||
for (let i = 0, len = opts.known.length; i < len; i++) {
|
||||
known[opts.known[i]] = true;
|
||||
}
|
||||
}
|
||||
|
||||
const objectName = opts.partial ? 'Handlebars.partials' : 'templates';
|
||||
|
||||
let output = new SourceNode();
|
||||
if (!opts.simple) {
|
||||
if (opts.amd) {
|
||||
output.add('define([\'' + opts.handlebarPath + 'handlebars.runtime\'], function(Handlebars) {\n Handlebars = Handlebars["default"];');
|
||||
} else if (opts.commonjs) {
|
||||
output.add('var Handlebars = require("' + opts.commonjs + '");');
|
||||
} else {
|
||||
output.add('(function() {\n');
|
||||
}
|
||||
output.add(' var template = Handlebars.template, templates = ');
|
||||
if (opts.namespace) {
|
||||
output.add(opts.namespace);
|
||||
output.add(' = ');
|
||||
output.add(opts.namespace);
|
||||
output.add(' || ');
|
||||
}
|
||||
output.add('{};\n');
|
||||
}
|
||||
|
||||
opts.templates.forEach(function(template) {
|
||||
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
|
||||
if (!opts.simple) {
|
||||
if (opts.amd) {
|
||||
if (multiple) {
|
||||
output.add(['return ', objectName, ';\n']);
|
||||
}
|
||||
output.add('});');
|
||||
} else if (!opts.commonjs) {
|
||||
output.add('})();');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (opts.map) {
|
||||
output.add('\n//# sourceMappingURL=' + opts.map + '\n');
|
||||
}
|
||||
|
||||
output = output.toStringWithSourceMap();
|
||||
output.map = output.map + '';
|
||||
|
||||
if (opts.min) {
|
||||
output = uglify.minify(output.code, {
|
||||
fromString: true,
|
||||
|
||||
outSourceMap: opts.map,
|
||||
inSourceMap: JSON.parse(output.map)
|
||||
});
|
||||
if (opts.map) {
|
||||
output.code += '\n//# sourceMappingURL=' + opts.map + '\n';
|
||||
}
|
||||
}
|
||||
|
||||
if (opts.map) {
|
||||
fs.writeFileSync(opts.map, output.map, 'utf8');
|
||||
}
|
||||
output = output.code;
|
||||
|
||||
if (opts.output) {
|
||||
fs.writeFileSync(opts.output, output, 'utf8');
|
||||
} else {
|
||||
console.log(output);
|
||||
}
|
||||
};
|
||||
|
||||
function arrayCast(value) {
|
||||
value = value != null ? value : [];
|
||||
if (!Array.isArray(value)) {
|
||||
value = [value];
|
||||
}
|
||||
return value;
|
||||
}
|
||||
+54
-10
@@ -1,31 +1,75 @@
|
||||
{
|
||||
"name": "handlebars",
|
||||
"description": "Extension of the Mustache logicless template language",
|
||||
"version": "1.0.6-2",
|
||||
"barename": "handlebars",
|
||||
"version": "4.0.2",
|
||||
"description": "Handlebars provides the power necessary to let you build semantic templates effectively with no frustration",
|
||||
"homepage": "http://www.handlebarsjs.com/",
|
||||
"keywords": [
|
||||
"handlebars mustache template html"
|
||||
"handlebars",
|
||||
"mustache",
|
||||
"template",
|
||||
"html"
|
||||
],
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git://github.com/kpdecker/handlebars.js.git"
|
||||
"url": "https://github.com/wycats/handlebars.js.git"
|
||||
},
|
||||
"author": "Yehuda Katz",
|
||||
"license": "MIT",
|
||||
"readmeFilename": "README.md",
|
||||
"engines": {
|
||||
"node": ">=0.4.7"
|
||||
},
|
||||
"dependencies": {
|
||||
"optimist": "~0.3",
|
||||
"uglify-js": "~1.2"
|
||||
"async": "^1.4.0",
|
||||
"optimist": "^0.6.1",
|
||||
"source-map": "^0.4.4"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"uglify-js": "~2.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"mocha": "*"
|
||||
"aws-sdk": "^2.1.49",
|
||||
"babel-loader": "^5.0.0",
|
||||
"babel-runtime": "^5.1.10",
|
||||
"benchmark": "~1.0",
|
||||
"dustjs-linkedin": "^2.0.2",
|
||||
"eco": "~1.1.0-rc-3",
|
||||
"grunt": "~0.4.1",
|
||||
"grunt-babel": "^5.0.0",
|
||||
"grunt-cli": "~0.1.10",
|
||||
"grunt-contrib-clean": "0.x",
|
||||
"grunt-contrib-concat": "0.x",
|
||||
"grunt-contrib-connect": "0.x",
|
||||
"grunt-contrib-copy": "0.x",
|
||||
"grunt-contrib-requirejs": "0.x",
|
||||
"grunt-contrib-uglify": "0.x",
|
||||
"grunt-contrib-watch": "0.x",
|
||||
"grunt-eslint": "^17.1.0",
|
||||
"grunt-saucelabs": "8.x",
|
||||
"grunt-webpack": "^1.0.8",
|
||||
"istanbul": "^0.3.0",
|
||||
"jison": "~0.3.0",
|
||||
"mocha": "~1.20.0",
|
||||
"mock-stdin": "^0.3.0",
|
||||
"mustache": "^2.1.3",
|
||||
"semver": "^5.0.1",
|
||||
"underscore": "^1.5.1"
|
||||
},
|
||||
"main": "lib/handlebars.js",
|
||||
"main": "lib/index.js",
|
||||
"bin": {
|
||||
"handlebars": "bin/handlebars"
|
||||
},
|
||||
"scripts": {
|
||||
"test": "node_modules/.bin/mocha -u qunit spec/qunit_spec.js"
|
||||
"test": "grunt"
|
||||
},
|
||||
"optionalDependencies": {}
|
||||
"jspm": {
|
||||
"main": "handlebars",
|
||||
"directories": {
|
||||
"lib": "dist/amd"
|
||||
},
|
||||
"buildConfig": {
|
||||
"minify": true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,431 @@
|
||||
# Release Notes
|
||||
|
||||
## Development
|
||||
|
||||
[Commits](https://github.com/wycats/handlebars.js/compare/v4.0.2...master)
|
||||
|
||||
## v4.0.2 - September 4th, 2015
|
||||
- [#1089](https://github.com/wycats/handlebars.js/issues/1089) - "Failover content" not working in multiple levels of inline partials ([@michaellopez](https://api.github.com/users/michaellopez))
|
||||
|
||||
[Commits](https://github.com/wycats/handlebars.js/compare/v4.0.1...v4.0.2)
|
||||
|
||||
## v4.0.1 - September 2nd, 2015
|
||||
- Fix failure when using decorators in partials - 05b82a2
|
||||
|
||||
[Commits](https://github.com/wycats/handlebars.js/compare/v4.0.0...v4.0.1)
|
||||
|
||||
## v4.0.0 - September 1st, 2015
|
||||
- [#1082](https://github.com/wycats/handlebars.js/pull/1082) - Decorators and Inline Partials ([@kpdecker](https://api.github.com/users/kpdecker))
|
||||
- [#1076](https://github.com/wycats/handlebars.js/pull/1076) - Implement partial blocks ([@kpdecker](https://api.github.com/users/kpdecker))
|
||||
- [#1087](https://github.com/wycats/handlebars.js/pull/1087) - Fix #each when last object entry has empty key ([@denniskuczynski](https://api.github.com/users/denniskuczynski))
|
||||
- [#1084](https://github.com/wycats/handlebars.js/pull/1084) - Bump uglify version to fix vulnerability ([@John-Steidley](https://api.github.com/users/John-Steidley))
|
||||
- [#1068](https://github.com/wycats/handlebars.js/pull/1068) - Fix typo ([@0xack13](https://api.github.com/users/0xack13))
|
||||
- [#1060](https://github.com/wycats/handlebars.js/pull/1060) - #1056 Fixed grammar for nested raw blocks ([@ericbn](https://api.github.com/users/ericbn))
|
||||
- [#1052](https://github.com/wycats/handlebars.js/pull/1052) - Updated year in License ([@maqnouch](https://api.github.com/users/maqnouch))
|
||||
- [#1037](https://github.com/wycats/handlebars.js/pull/1037) - Fix minor typos in README ([@tomxtobin](https://api.github.com/users/tomxtobin))
|
||||
- [#1032](https://github.com/wycats/handlebars.js/issues/1032) - Is it possible to render a partial without the parent scope? ([@aputinski](https://api.github.com/users/aputinski))
|
||||
- [#1019](https://github.com/wycats/handlebars.js/pull/1019) - Fixes typo in tests ([@aymerick](https://api.github.com/users/aymerick))
|
||||
- [#1016](https://github.com/wycats/handlebars.js/issues/1016) - Version mis-match ([@mayankdedhia](https://api.github.com/users/mayankdedhia))
|
||||
- [#1023](https://github.com/wycats/handlebars.js/issues/1023) - is it possible for nested custom helpers to communicate between each other?
|
||||
- [#893](https://github.com/wycats/handlebars.js/issues/893) - [Proposal] Section blocks.
|
||||
- [#792](https://github.com/wycats/handlebars.js/issues/792) - feature request: inline partial definitions
|
||||
- [#583](https://github.com/wycats/handlebars.js/issues/583) - Parent path continues to drill down depth with multiple conditionals
|
||||
- [#404](https://github.com/wycats/handlebars.js/issues/404) - Add named child helpers that can be referenced by block helpers
|
||||
- Escape = in HTML content - [83b8e84](https://github.com/wycats/handlebars.js/commit/83b8e84)
|
||||
- Drop AST constructors in favor of JSON - [95d84ba](https://github.com/wycats/handlebars.js/commit/95d84ba)
|
||||
- Pass container rather than exec as context - [9a2d1d6](https://github.com/wycats/handlebars.js/commit/9a2d1d6)
|
||||
- Add ignoreStandalone compiler option - [ea3a5a1](https://github.com/wycats/handlebars.js/commit/ea3a5a1)
|
||||
- Ignore empty when iterating on sparse arrays - [06d515a](https://github.com/wycats/handlebars.js/commit/06d515a)
|
||||
- Add support for string and stdin precompilation - [0de8dac](https://github.com/wycats/handlebars.js/commit/0de8dac)
|
||||
- Simplify object assignment generation logic - [77e6bfc](https://github.com/wycats/handlebars.js/commit/77e6bfc)
|
||||
- Bulletproof AST.helpers.helperExpression - [93b0760](https://github.com/wycats/handlebars.js/commit/93b0760)
|
||||
- Always return string responses - [8e868ab](https://github.com/wycats/handlebars.js/commit/8e868ab)
|
||||
- Pass undefined fields to helpers in strict mode - [5d4b8da](https://github.com/wycats/handlebars.js/commit/5d4b8da)
|
||||
- Avoid depth creation when context remains the same - [279e038](https://github.com/wycats/handlebars.js/commit/279e038)
|
||||
- Improve logging API - [9a49d35](https://github.com/wycats/handlebars.js/commit/9a49d35)
|
||||
- Fix with operator in no @data mode - [231a8d7](https://github.com/wycats/handlebars.js/commit/231a8d7)
|
||||
- Allow empty key name in each iteration - [1bb640b](https://github.com/wycats/handlebars.js/commit/1bb640b)
|
||||
- Add with block parameter support - [2a85106](https://github.com/wycats/handlebars.js/commit/2a85106)
|
||||
- Fix escaping of non-javascript identifiers - [410141c](https://github.com/wycats/handlebars.js/commit/410141c)
|
||||
- Fix location information for programs - [93faffa](https://github.com/wycats/handlebars.js/commit/93faffa)
|
||||
|
||||
Compatibility notes:
|
||||
- Depthed paths are now conditional pushed on to the stack. If the helper uses the same context, then a new stack is not created. This leads to behavior the better matches expectations for helpers like `if` that do not seem to alter the context. Any instances of `../` in templates will need to be checked for the correct behavior under 4.0.0. In general templates will either reduce the number of `../` instances or leave them as is. See [#1028](https://github.com/wycats/handlebars.js/issues/1028).
|
||||
- The `=` character is now HTML escaped. This closes a potential exploit case when using unquoted attributes, i.e. `<div foo={{bar}}>`. In general it's recommended that attributes always be quoted when their values are generated from a mustache to avoid any potential exploit surfaces.
|
||||
- AST constructors have been dropped in favor of plain old javascript objects
|
||||
- The runtime version has been increased. Precompiled templates will need to use runtime of at least 4.0.0.
|
||||
|
||||
[Commits](https://github.com/wycats/handlebars.js/compare/v3.0.3...v4.0.0)
|
||||
|
||||
## v3.0.3 - April 28th, 2015
|
||||
- [#1004](https://github.com/wycats/handlebars.js/issues/1004) - Latest version breaks with RequireJS (global is undefined) ([@boskee](https://api.github.com/users/boskee))
|
||||
|
||||
[Commits](https://github.com/wycats/handlebars.js/compare/v3.0.2...v3.0.3)
|
||||
|
||||
## v3.0.2 - April 20th, 2015
|
||||
- [#998](https://github.com/wycats/handlebars.js/pull/998) - Add full support for es6 ([@kpdecker](https://api.github.com/users/kpdecker))
|
||||
- [#994](https://github.com/wycats/handlebars.js/issues/994) - Access Handlebars.Visitor in browser ([@tamlyn](https://api.github.com/users/tamlyn))
|
||||
- [#990](https://github.com/wycats/handlebars.js/issues/990) - Allow passing null/undefined literals subexpressions ([@blimmer](https://api.github.com/users/blimmer))
|
||||
- [#989](https://github.com/wycats/handlebars.js/issues/989) - Source-map error with requirejs ([@SteppeEagle](https://api.github.com/users/SteppeEagle))
|
||||
- [#967](https://github.com/wycats/handlebars.js/issues/967) - can't access "this" property ([@75lb](https://api.github.com/users/75lb))
|
||||
- Use captureStackTrace for error handler - a009a97
|
||||
- Ignore branches tested without coverage monitoring - 37a664b
|
||||
|
||||
[Commits](https://github.com/wycats/handlebars.js/compare/v3.0.1...v3.0.2)
|
||||
|
||||
## v3.0.1 - March 24th, 2015
|
||||
- [#984](https://github.com/wycats/handlebars.js/pull/984) - Adding documentation for passing arguments into partials ([@johneke](https://api.github.com/users/johneke))
|
||||
- [#973](https://github.com/wycats/handlebars.js/issues/973) - version 3 is slower than version 2 ([@elover](https://api.github.com/users/elover))
|
||||
- [#966](https://github.com/wycats/handlebars.js/issues/966) - "handlebars --version" does not work with v3.0.0 ([@abloomston](https://api.github.com/users/abloomston))
|
||||
- [#964](https://github.com/wycats/handlebars.js/pull/964) - default is a reserved word ([@grassick](https://api.github.com/users/grassick))
|
||||
- [#962](https://github.com/wycats/handlebars.js/pull/962) - Add dashbars' link on README. ([@pismute](https://api.github.com/users/pismute))
|
||||
|
||||
[Commits](https://github.com/wycats/handlebars.js/compare/v3.0.0...v3.0.1)
|
||||
|
||||
## v3.0.0 - February 10th, 2015
|
||||
- [#941](https://github.com/wycats/handlebars.js/pull/941) - Add support for dynamic partial names ([@kpdecker](https://api.github.com/users/kpdecker))
|
||||
- [#940](https://github.com/wycats/handlebars.js/pull/940) - Add missing reserved words so compiler knows to use array syntax: ([@mattflaschen](https://api.github.com/users/mattflaschen))
|
||||
- [#938](https://github.com/wycats/handlebars.js/pull/938) - Fix example using #with helper ([@diwo](https://api.github.com/users/diwo))
|
||||
- [#930](https://github.com/wycats/handlebars.js/pull/930) - Add parent tracking and mutation to AST visitors ([@kpdecker](https://api.github.com/users/kpdecker))
|
||||
- [#926](https://github.com/wycats/handlebars.js/issues/926) - Depthed lookups fail when program duplicator runs ([@kpdecker](https://api.github.com/users/kpdecker))
|
||||
- [#918](https://github.com/wycats/handlebars.js/pull/918) - Add instructions for 'spec/mustache' to CONTRIBUTING.md, fix a few typos ([@oneeman](https://api.github.com/users/oneeman))
|
||||
- [#915](https://github.com/wycats/handlebars.js/pull/915) - Ast update ([@kpdecker](https://api.github.com/users/kpdecker))
|
||||
- [#910](https://github.com/wycats/handlebars.js/issues/910) - Different behavior of {{@last}} when {{#each}} in {{#each}} ([@zordius](https://api.github.com/users/zordius))
|
||||
- [#907](https://github.com/wycats/handlebars.js/issues/907) - Implement named helper variable references ([@kpdecker](https://api.github.com/users/kpdecker))
|
||||
- [#906](https://github.com/wycats/handlebars.js/pull/906) - Add parser support for block params ([@mmun](https://api.github.com/users/mmun))
|
||||
- [#903](https://github.com/wycats/handlebars.js/issues/903) - Only provide aliases for multiple use calls ([@kpdecker](https://api.github.com/users/kpdecker))
|
||||
- [#902](https://github.com/wycats/handlebars.js/pull/902) - Generate Source Maps ([@kpdecker](https://api.github.com/users/kpdecker))
|
||||
- [#901](https://github.com/wycats/handlebars.js/issues/901) - Still escapes with noEscape enabled on isolated Handlebars environment ([@zedknight](https://api.github.com/users/zedknight))
|
||||
- [#896](https://github.com/wycats/handlebars.js/pull/896) - Simplify BlockNode by removing intermediate MustacheNode ([@mmun](https://api.github.com/users/mmun))
|
||||
- [#892](https://github.com/wycats/handlebars.js/pull/892) - Implement parser for else chaining of helpers ([@kpdecker](https://api.github.com/users/kpdecker))
|
||||
- [#889](https://github.com/wycats/handlebars.js/issues/889) - Consider extensible parser API ([@kpdecker](https://api.github.com/users/kpdecker))
|
||||
- [#887](https://github.com/wycats/handlebars.js/issues/887) - Handlebars.noConflict() option? ([@bradvogel](https://api.github.com/users/bradvogel))
|
||||
- [#886](https://github.com/wycats/handlebars.js/issues/886) - Add SafeString to context (or use duck-typing) ([@dominicbarnes](https://api.github.com/users/dominicbarnes))
|
||||
- [#870](https://github.com/wycats/handlebars.js/pull/870) - Registering undefined partial throws exception. ([@max-b](https://api.github.com/users/max-b))
|
||||
- [#866](https://github.com/wycats/handlebars.js/issues/866) - comments don't respect whitespace control ([@75lb](https://api.github.com/users/75lb))
|
||||
- [#863](https://github.com/wycats/handlebars.js/pull/863) - + jsDelivr CDN info ([@tomByrer](https://api.github.com/users/tomByrer))
|
||||
- [#858](https://github.com/wycats/handlebars.js/issues/858) - Disable new default auto-indent at included partials ([@majodev](https://api.github.com/users/majodev))
|
||||
- [#856](https://github.com/wycats/handlebars.js/pull/856) - jspm compatibility ([@MajorBreakfast](https://api.github.com/users/MajorBreakfast))
|
||||
- [#805](https://github.com/wycats/handlebars.js/issues/805) - Request: "strict" lookups ([@nzakas](https://api.github.com/users/nzakas))
|
||||
|
||||
- Export the default object for handlebars/runtime - 5594416
|
||||
- Lookup partials when undefined - 617dd57
|
||||
|
||||
Compatibility notes:
|
||||
- Runtime breaking changes. Must match 3.x runtime and precompiler.
|
||||
- The AST has been upgraded to a public API.
|
||||
- There are a number of changes to this, but the format is now documented in docs/compiler-api.md
|
||||
- The Visitor API has been expanded to support mutation and provide a base implementation
|
||||
- The `JavaScriptCompiler` APIs have been formalized and documented. As part of the sourcemap handling these should be updated to return arrays for concatenation.
|
||||
- `JavaScriptCompiler.namespace` has been removed as it was unused.
|
||||
- `SafeString` is now duck typed on `toHTML`
|
||||
|
||||
New Features:
|
||||
- noConflict
|
||||
- Source Maps
|
||||
- Block Params
|
||||
- Strict Mode
|
||||
- @last and other each changes
|
||||
- Chained else blocks
|
||||
- @data methods can now have helper parameters passed to them
|
||||
- Dynamic partials
|
||||
|
||||
[Commits](https://github.com/wycats/handlebars.js/compare/v2.0.0...v3.0.0)
|
||||
|
||||
## v2.0.0 - September 1st, 2014
|
||||
- Update jsfiddle to 2.0.0-beta.1 - 0670f65
|
||||
- Add contrib note regarding handlebarsjs.com docs - 4d17e3c
|
||||
- Play nice with gemspec version numbers - 64d5481
|
||||
|
||||
[Commits](https://github.com/wycats/handlebars.js/compare/v2.0.0-beta.1...v2.0.0)
|
||||
|
||||
## v2.0.0-beta.1 - August 26th, 2014
|
||||
- [#787](https://github.com/wycats/handlebars.js/pull/787) - Remove whitespace surrounding standalone statements ([@kpdecker](https://api.github.com/users/kpdecker))
|
||||
- [#827](https://github.com/wycats/handlebars.js/issues/827) - Render false literal as “false” ([@scoot557](https://api.github.com/users/scoot557))
|
||||
- [#767](https://github.com/wycats/handlebars.js/issues/767) - Subexpressions bug with hash and context ([@evensoul](https://api.github.com/users/evensoul))
|
||||
- Changes to 0/undefined handling
|
||||
- [#731](https://github.com/wycats/handlebars.js/pull/731) - Strange behavior for {{#foo}} {{bar}} {{/foo}} when foo is 0 ([@kpdecker](https://api.github.com/users/kpdecker))
|
||||
- [#820](https://github.com/wycats/handlebars.js/issues/820) - strange behavior for {{foo.bar}} when foo is 0 or null or false ([@zordius](https://api.github.com/users/zordius))
|
||||
- [#837](https://github.com/wycats/handlebars.js/issues/837) - Strange input for custom helper ( foo.bar == false when foo is undefined ) ([@zordius](https://api.github.com/users/zordius))
|
||||
- [#819](https://github.com/wycats/handlebars.js/pull/819) - Implement recursive field lookup ([@kpdecker](https://api.github.com/users/kpdecker))
|
||||
- [#764](https://github.com/wycats/handlebars.js/issues/764) - This reference not working for helpers ([@kpdecker](https://api.github.com/users/kpdecker))
|
||||
- [#773](https://github.com/wycats/handlebars.js/issues/773) - Implicit parameters in {{#each}} introduces a peculiarity in helpers calling convention ([@Bertrand](https://api.github.com/users/Bertrand))
|
||||
- [#783](https://github.com/wycats/handlebars.js/issues/783) - helperMissing and consistency for different expression types ([@ErisDS](https://api.github.com/users/ErisDS))
|
||||
- [#795](https://github.com/wycats/handlebars.js/pull/795) - Turn the precompile script into a wrapper around a module. ([@jwietelmann](https://api.github.com/users/jwietelmann))
|
||||
- [#823](https://github.com/wycats/handlebars.js/pull/823) - Support inverse sections on the with helper ([@dan-manges](https://api.github.com/users/dan-manges))
|
||||
- [#834](https://github.com/wycats/handlebars.js/pull/834) - Refactor blocks, programs and inverses ([@mmun](https://api.github.com/users/mmun))
|
||||
- [#852](https://github.com/wycats/handlebars.js/issues/852) - {{foo~}} space control behavior is different from older version ([@zordius](https://api.github.com/users/zordius))
|
||||
- [#835](https://github.com/wycats/handlebars.js/issues/835) - Templates overwritten if file is loaded twice
|
||||
|
||||
- Expose escapeExpression on the root object - 980c38c
|
||||
- Remove nested function eval in blockHelperMissing - 6f22ec1
|
||||
- Fix compiler program de-duping - 9e3f824
|
||||
|
||||
Compatibility notes:
|
||||
- The default build now outputs a generic UMD wrapper. This should be transparent change but may cause issues in some environments.
|
||||
- Runtime compatibility breaks in both directions. Ensure that both compiler and client are upgraded to 2.0.0-beta.1 or higher at the same time.
|
||||
- `programWithDepth` has been removed an instead an array of context values is passed to fields needing depth lookups.
|
||||
- `false` values are now printed to output rather than silently dropped
|
||||
- Lines containing only block statements and whitespace are now removed. This matches the Mustache spec but may cause issues with code that expects whitespace to exist but would not otherwise.
|
||||
- Partials that are standalone will now indent their rendered content
|
||||
- `AST.ProgramNode`'s signature has changed.
|
||||
- Numerious methods/features removed from psuedo-API classes
|
||||
- `JavaScriptCompiler.register`
|
||||
- `JavaScriptCompiler.replaceStack` no longer supports non-inline replace
|
||||
- `Compiler.disassemble`
|
||||
- `DECLARE` opcode
|
||||
- `strip` opcode
|
||||
- `lookup` opcode
|
||||
- Content nodes may have their `string` values mutated over time. `original` field provides the unmodified value.
|
||||
- Removed unused `Handlebars.registerHelper` `inverse` parameter
|
||||
- `each` helper requires iterator parameter
|
||||
|
||||
[Commits](https://github.com/wycats/handlebars.js/compare/v2.0.0-alpha.4...v2.0.0-beta.1)
|
||||
|
||||
## v2.0.0-alpha.4 - May 19th, 2014
|
||||
- Expose setup wrappers for compiled templates - 3638874
|
||||
|
||||
[Commits](https://github.com/wycats/handlebars.js/compare/v2.0.0-alpha.3...v2.0.0-alpha.4)
|
||||
|
||||
## v2.0.0-alpha.3 - May 19th, 2014
|
||||
- [#797](https://github.com/wycats/handlebars.js/pull/797) - Pass full helper ID to helperMissing when options are provided ([@tomdale](https://api.github.com/users/tomdale))
|
||||
- [#793](https://github.com/wycats/handlebars.js/pull/793) - Ensure isHelper is coerced to a boolean ([@mmun](https://api.github.com/users/mmun))
|
||||
- Refactor template init logic - 085e5e1
|
||||
|
||||
[Commits](https://github.com/wycats/handlebars.js/compare/v2.0.0-alpha.2...v2.0.0-alpha.3)
|
||||
|
||||
## v2.0.0-alpha.2 - March 6th, 2014
|
||||
- [#756](https://github.com/wycats/handlebars.js/pull/756) - fix bug in IE<=8 (no Array::map), closes #751 ([@jenseng](https://api.github.com/users/jenseng))
|
||||
- [#749](https://github.com/wycats/handlebars.js/pull/749) - properly handle multiple subexpressions in the same hash, fixes #748 ([@jenseng](https://api.github.com/users/jenseng))
|
||||
- [#743](https://github.com/wycats/handlebars.js/issues/743) - subexpression confusion/problem? ([@waynedpj](https://api.github.com/users/waynedpj))
|
||||
- [#746](https://github.com/wycats/handlebars.js/issues/746) - [CLI] support `handlebars --version` ([@apfelbox](https://api.github.com/users/apfelbox))
|
||||
- [#747](https://github.com/wycats/handlebars.js/pull/747) - updated grunt-saucelabs, failing tests revealed ([@Jonahss](https://api.github.com/users/Jonahss))
|
||||
- Make JSON a requirement for the compiler. - 058c0fb
|
||||
- Temporarily kill the AWS publish CI step - 8347ee2
|
||||
|
||||
Compatibility notes:
|
||||
- A JSON polyfill is required to run the compiler under IE8 and below. It's recommended that the precompiler be used in lieu of running the compiler on these legacy environments.
|
||||
|
||||
[Commits](https://github.com/wycats/handlebars.js/compare/v2.0.0-alpha.1...v2.0.0-alpha.2)
|
||||
|
||||
## v2.0.0-alpha.1 - February 10th, 2014
|
||||
- [#182](https://github.com/wycats/handlebars.js/pull/182) - Allow passing hash parameters to partials ([@kpdecker](https://api.github.com/users/kpdecker))
|
||||
- [#392](https://github.com/wycats/handlebars.js/pull/392) - Access to root context in partials and helpers ([@kpdecker](https://api.github.com/users/kpdecker))
|
||||
- [#472](https://github.com/wycats/handlebars.js/issues/472) - Helpers cannot have decimal parameters ([@kayleg](https://api.github.com/users/kayleg))
|
||||
- [#569](https://github.com/wycats/handlebars.js/pull/569) - Unable to lookup array values using @index ([@kpdecker](https://api.github.com/users/kpdecker))
|
||||
- [#491](https://github.com/wycats/handlebars.js/pull/491) - For nested helpers: get the @ variables of the outer helper from the inner one ([@kpdecker](https://api.github.com/users/kpdecker))
|
||||
- [#669](https://github.com/wycats/handlebars.js/issues/669) - Ability to unregister a helper ([@dbachrach](https://api.github.com/users/dbachrach))
|
||||
- [#730](https://github.com/wycats/handlebars.js/pull/730) - Raw block helpers ([@kpdecker](https://api.github.com/users/kpdecker))
|
||||
- [#634](https://github.com/wycats/handlebars.js/pull/634) - It would be great to have the helper name passed to `blockHelperMissing` ([@kpdecker](https://api.github.com/users/kpdecker))
|
||||
- [#729](https://github.com/wycats/handlebars.js/pull/729) - Convert template spec to object literal ([@kpdecker](https://api.github.com/users/kpdecker))
|
||||
|
||||
- [#658](https://github.com/wycats/handlebars.js/issues/658) - Depthed helpers do not work after an upgrade from 1.0.0 ([@xibxor](https://api.github.com/users/xibxor))
|
||||
- [#671](https://github.com/wycats/handlebars.js/issues/671) - Crashes on no-parameter {{#each}} ([@stepancheg](https://api.github.com/users/stepancheg))
|
||||
- [#689](https://github.com/wycats/handlebars.js/issues/689) - broken template precompilation ([@AAS](https://api.github.com/users/AAS))
|
||||
- [#698](https://github.com/wycats/handlebars.js/pull/698) - Fix parser generation under windows ([@osiris43](https://api.github.com/users/osiris43))
|
||||
- [#699](https://github.com/wycats/handlebars.js/issues/699) - @DATA not compiles to invalid JS in stringParams mode ([@kpdecker](https://api.github.com/users/kpdecker))
|
||||
- [#705](https://github.com/wycats/handlebars.js/issues/705) - 1.3.0 can not be wrapped in an IIFE ([@craigteegarden](https://api.github.com/users/craigteegarden))
|
||||
- [#706](https://github.com/wycats/handlebars.js/pull/706) - README: Use with helper instead of relying on blockHelperMissing ([@scottgonzalez](https://api.github.com/users/scottgonzalez))
|
||||
|
||||
- [#700](https://github.com/wycats/handlebars.js/pull/700) - Remove redundant conditions ([@blakeembrey](https://api.github.com/users/blakeembrey))
|
||||
- [#704](https://github.com/wycats/handlebars.js/pull/704) - JavaScript Compiler Cleanup ([@blakeembrey](https://api.github.com/users/blakeembrey))
|
||||
|
||||
Compatibility notes:
|
||||
- `helperMissing` helper no longer has the indexed name argument. Helper name is now available via `options.name`.
|
||||
- Precompiler output has changed, which breaks compatibility with prior versions of the runtime and precompiled output.
|
||||
- `JavaScriptCompiler.compilerInfo` now returns generic objects rather than javascript source.
|
||||
- AST changes
|
||||
- INTEGER -> NUMBER
|
||||
- Additional PartialNode hash parameter
|
||||
- New RawBlockNode type
|
||||
- Data frames now have a `_parent` field. This is internal but is enumerable for performance/compatability reasons.
|
||||
|
||||
[Commits](https://github.com/wycats/handlebars.js/compare/v1.3.0...v2.0.0-alpha.1)
|
||||
|
||||
## v1.3.0 - January 1st, 2014
|
||||
- [#690](https://github.com/wycats/handlebars.js/pull/690) - Added support for subexpressions ([@machty](https://api.github.com/users/machty))
|
||||
- [#696](https://github.com/wycats/handlebars.js/pull/696) - Fix for reserved keyword "default" ([@nateirwin](https://api.github.com/users/nateirwin))
|
||||
- [#692](https://github.com/wycats/handlebars.js/pull/692) - add line numbers to nodes when parsing ([@fivetanley](https://api.github.com/users/fivetanley))
|
||||
- [#695](https://github.com/wycats/handlebars.js/pull/695) - Pull options out from param setup to allow easier extension ([@blakeembrey](https://api.github.com/users/blakeembrey))
|
||||
- [#694](https://github.com/wycats/handlebars.js/pull/694) - Make the environment reusable ([@blakeembrey](https://api.github.com/users/blakeembrey))
|
||||
- [#636](https://github.com/wycats/handlebars.js/issues/636) - Print line and column of errors ([@sgronblo](https://api.github.com/users/sgronblo))
|
||||
- Use literal for data lookup - c1a93d3
|
||||
- Add stack handling sanity checks - cd885bf
|
||||
- Fix stack id "leak" on replaceStack - ddfe457
|
||||
- Fix incorrect stack pop when replacing literals - f4d337d
|
||||
|
||||
[Commits](https://github.com/wycats/handlebars.js/compare/v1.2.1...v1.3.0)
|
||||
|
||||
## v1.2.1 - December 26th, 2013
|
||||
- [#684](https://github.com/wycats/handlebars.js/pull/684) - Allow any number of trailing characters for valid JavaScript variable ([@blakeembrey](https://api.github.com/users/blakeembrey))
|
||||
- [#686](https://github.com/wycats/handlebars.js/pull/686) - Falsy AMD module names in version 1.2.0 ([@kpdecker](https://api.github.com/users/kpdecker))
|
||||
|
||||
[Commits](https://github.com/wycats/handlebars.js/compare/v1.2.0...v1.2.1)
|
||||
|
||||
## v1.2.0 - December 23rd, 2013
|
||||
- [#675](https://github.com/wycats/handlebars.js/issues/675) - Cannot compile empty template for partial ([@erwinw](https://api.github.com/users/erwinw))
|
||||
- [#677](https://github.com/wycats/handlebars.js/issues/677) - Triple brace statements fail under IE ([@hamzaCM](https://api.github.com/users/hamzaCM))
|
||||
- [#655](https://github.com/wycats/handlebars.js/issues/655) - Loading Handlebars using bower ([@niki4810](https://api.github.com/users/niki4810))
|
||||
- [#657](https://github.com/wycats/handlebars.js/pull/657) - Fixes issue where cli compiles non handlebars templates ([@chrishoage](https://api.github.com/users/chrishoage))
|
||||
- [#681](https://github.com/wycats/handlebars.js/pull/681) - Adds in-browser testing and Saucelabs CI ([@kpdecker](https://api.github.com/users/kpdecker))
|
||||
- [#661](https://github.com/wycats/handlebars.js/pull/661) - Add @first and @index to #each object iteration ([@cgp](https://api.github.com/users/cgp))
|
||||
- [#650](https://github.com/wycats/handlebars.js/pull/650) - Handlebars is MIT-licensed ([@thomasboyt](https://api.github.com/users/thomasboyt))
|
||||
- [#641](https://github.com/wycats/handlebars.js/pull/641) - Document ember testing process ([@kpdecker](https://api.github.com/users/kpdecker))
|
||||
- [#662](https://github.com/wycats/handlebars.js/issues/662) - handlebars-source 1.1.2 is missing from RubyGems.
|
||||
- [#656](https://github.com/wycats/handlebars.js/issues/656) - Expose COMPILER_REVISION checks as a hook ([@machty](https://api.github.com/users/machty))
|
||||
- [#668](https://github.com/wycats/handlebars.js/issues/668) - Consider publishing handlebars-runtime as a separate module on npm ([@dlmanning](https://api.github.com/users/dlmanning))
|
||||
- [#679](https://github.com/wycats/handlebars.js/issues/679) - Unable to override invokePartial ([@mattbrailsford](https://api.github.com/users/mattbrailsford))
|
||||
- [#646](https://github.com/wycats/handlebars.js/pull/646) - Fix "\\{{" immediately following "\{{" ([@dmarcotte](https://api.github.com/users/dmarcotte))
|
||||
- Allow extend to work with non-prototyped objects - eb53f2e
|
||||
- Add JavascriptCompiler public API tests - 1a751b2
|
||||
- Add AST test coverage for more complex paths - ddea5be
|
||||
- Fix handling of boolean escape in MustacheNode - b4968bb
|
||||
|
||||
Compatibility notes:
|
||||
- `@index` and `@first` are now supported for `each` iteration on objects
|
||||
- `Handlebars.VM.checkRevision` and `Handlebars.JavaScriptCompiler.prototype.compilerInfo` now available to modify the version checking behavior.
|
||||
- Browserify users may link to the runtime library via `require('handlebars/runtime')`
|
||||
|
||||
[Commits](https://github.com/wycats/handlebars.js/compare/v1.1.2...v1.2.0)
|
||||
|
||||
## v1.1.2 - November 5th, 2013
|
||||
|
||||
- [#645](https://github.com/wycats/handlebars.js/issues/645) - 1.1.1 fails under IE8 ([@kpdecker](https://api.github.com/users/kpdecker))
|
||||
- [#644](https://github.com/wycats/handlebars.js/issues/644) - Using precompiled templates (AMD mode) with handlebars.runtime 1.1.1 ([@fddima](https://api.github.com/users/fddima))
|
||||
|
||||
- Add simple binary utility tests - 96a45a4
|
||||
- Fix empty string compilation - eea708a
|
||||
|
||||
[Commits](https://github.com/wycats/handlebars.js/compare/v1.1.1...v1.1.2)
|
||||
|
||||
## v1.1.1 - November 4th, 2013
|
||||
|
||||
- [#642](https://github.com/wycats/handlebars.js/issues/642) - handlebars 1.1.0 are broken with nodejs
|
||||
|
||||
- Fix release notes link - 17ba258
|
||||
|
||||
[Commits](https://github.com/wycats/handlebars.js/compare/v1.1.0...v1.1.1)
|
||||
|
||||
## v1.1.0 - November 3rd, 2013
|
||||
|
||||
- [#628](https://github.com/wycats/handlebars.js/pull/628) - Convert code to ES6 modules ([@kpdecker](https://api.github.com/users/kpdecker))
|
||||
- [#336](https://github.com/wycats/handlebars.js/pull/336) - Add whitespace control syntax ([@kpdecker](https://api.github.com/users/kpdecker))
|
||||
- [#535](https://github.com/wycats/handlebars.js/pull/535) - Fix for probable JIT error under Safari ([@sorentwo](https://api.github.com/users/sorentwo))
|
||||
- [#483](https://github.com/wycats/handlebars.js/issues/483) - Add first and last @ vars to each helper ([@denniskuczynski](https://api.github.com/users/denniskuczynski))
|
||||
- [#557](https://github.com/wycats/handlebars.js/pull/557) - `\\{{foo}}` escaping only works in some situations ([@dmarcotte](https://api.github.com/users/dmarcotte))
|
||||
- [#552](https://github.com/wycats/handlebars.js/pull/552) - Added BOM removal flag. ([@blessenm](https://api.github.com/users/blessenm))
|
||||
- [#543](https://github.com/wycats/handlebars.js/pull/543) - publish passing master builds to s3 ([@fivetanley](https://api.github.com/users/fivetanley))
|
||||
|
||||
- [#608](https://github.com/wycats/handlebars.js/issues/608) - Add `includeZero` flag to `if` conditional
|
||||
- [#498](https://github.com/wycats/handlebars.js/issues/498) - `Handlebars.compile` fails on empty string although a single blank works fine
|
||||
- [#599](https://github.com/wycats/handlebars.js/issues/599) - lambda helpers only receive options if used with arguments
|
||||
- [#592](https://github.com/wycats/handlebars.js/issues/592) - Optimize array and subprogram performance
|
||||
- [#571](https://github.com/wycats/handlebars.js/issues/571) - uglify upgrade breaks compatibility with older versions of node
|
||||
- [#587](https://github.com/wycats/handlebars.js/issues/587) - Partial inside partial breaks?
|
||||
|
||||
|
||||
Compatibility notes:
|
||||
- The project now includes separate artifacts for AMD, CommonJS, and global objects.
|
||||
- AMD: Users may load the bundled `handlebars.amd.js` or `handlebars.runtime.amd.js` files or load individual modules directly. AMD users should also note that the handlebars object is exposed via the `default` field on the imported object. This [gist](https://gist.github.com/wycats/7417be0dc361a69d5916) provides some discussion of possible compatibility shims.
|
||||
- CommonJS/Node: Node loading occurs as normal via `require`
|
||||
- Globals: The `handlebars.js` and `handlebars.runtime.js` files should behave in the same manner as the v1.0.12 / 1.0.0 release.
|
||||
- Build artifacts have been removed from the repository. [npm][npm], [components/handlebars.js][components], [cdnjs][cdnjs], or the [builds page][builds-page] should now be used as the source of built artifacts.
|
||||
- Context-stored helpers are now always passed the `options` hash. Previously no-argument helpers did not have this argument.
|
||||
|
||||
|
||||
[Commits](https://github.com/wycats/handlebars.js/compare/v1.0.12...v1.1.0)
|
||||
|
||||
## v1.0.12 / 1.0.0 - May 31 2013
|
||||
|
||||
- [#515](https://github.com/wycats/handlebars.js/issues/515) - Add node require extensions support ([@jjclark1982](https://github.com/jjclark1982))
|
||||
- [#517](https://github.com/wycats/handlebars.js/issues/517) - Fix amd precompiler output with directories ([@blessenm](https://github.com/blessenm))
|
||||
- [#433](https://github.com/wycats/handlebars.js/issues/433) - Add support for unicode ids
|
||||
- [#469](https://github.com/wycats/handlebars.js/issues/469) - Add support for `?` in ids
|
||||
- [#534](https://github.com/wycats/handlebars.js/issues/534) - Protect from object prototype modifications
|
||||
- [#519](https://github.com/wycats/handlebars.js/issues/519) - Fix partials with . name ([@jamesgorrie](https://github.com/jamesgorrie))
|
||||
- [#519](https://github.com/wycats/handlebars.js/issues/519) - Allow ID or strings in partial names
|
||||
- [#437](https://github.com/wycats/handlebars.js/issues/437) - Require matching brace counts in escaped expressions
|
||||
- Merge passed partials and helpers with global namespace values
|
||||
- Add support for complex ids in @data references
|
||||
- Docs updates
|
||||
|
||||
Compatibility notes:
|
||||
- The parser is now stricter on `{{{`, requiring that the end token be `}}}`. Templates that do not
|
||||
follow this convention should add the additional brace value.
|
||||
- Code that relies on global the namespace being muted when custom helpers or partials are passed will need to explicitly pass an `undefined` value for any helpers that should not be available.
|
||||
- The compiler version has changed. Precompiled templates with 1.0.12 or higher must use the 1.0.0 or higher runtime.
|
||||
|
||||
[Commits](https://github.com/wycats/handlebars.js/compare/v1.0.11...v1.0.12)
|
||||
|
||||
## v1.0.11 / 1.0.0-rc4 - May 13 2013
|
||||
|
||||
- [#458](https://github.com/wycats/handlebars.js/issues/458) - Fix `./foo` syntax ([@jpfiset](https://github.com/jpfiset))
|
||||
- [#460](https://github.com/wycats/handlebars.js/issues/460) - Allow `:` in unescaped identifers ([@jpfiset](https://github.com/jpfiset))
|
||||
- [#471](https://github.com/wycats/handlebars.js/issues/471) - Create release notes (These!)
|
||||
- [#456](https://github.com/wycats/handlebars.js/issues/456) - Allow escaping of `\\`
|
||||
- [#211](https://github.com/wycats/handlebars.js/issues/211) - Fix exception in `escapeExpression`
|
||||
- [#375](https://github.com/wycats/handlebars.js/issues/375) - Escape unicode newlines
|
||||
- [#461](https://github.com/wycats/handlebars.js/issues/461) - Do not fail when compiling `""`
|
||||
- [#302](https://github.com/wycats/handlebars.js/issues/302) - Fix sanity check in knownHelpersOnly mode
|
||||
- [#369](https://github.com/wycats/handlebars.js/issues/369) - Allow registration of multiple helpers and partial by passing definition object
|
||||
- Add bower package declaration ([@DevinClark](https://github.com/DevinClark))
|
||||
- Add NuSpec package declaration ([@MikeMayer](https://github.com/MikeMayer))
|
||||
- Handle empty context in `with` ([@thejohnfreeman](https://github.com/thejohnfreeman))
|
||||
- Support custom template extensions in CLI ([@matteoagosti](https://github.com/matteoagosti))
|
||||
- Fix Rhino support ([@broady](https://github.com/broady))
|
||||
- Include contexts in string mode ([@leshill](https://github.com/leshill))
|
||||
- Return precompiled scripts when compiling to AMD ([@JamesMaroney](https://github.com/JamesMaroney))
|
||||
- Docs updates ([@iangreenleaf](https://github.com/iangreenleaf), [@gilesbowkett](https://github.com/gilesbowkett), [@utkarsh2012](https://github.com/utkarsh2012))
|
||||
- Fix `toString` handling under IE and browserify ([@tommydudebreaux](https://github.com/tommydudebreaux))
|
||||
- Add program metadata
|
||||
|
||||
[Commits](https://github.com/wycats/handlebars.js/compare/v1.0.10...v1.0.11)
|
||||
|
||||
## v1.0.10 - Node - Feb 27 2013
|
||||
|
||||
- [#428](https://github.com/wycats/handlebars.js/issues/428) - Fix incorrect rendering of nested programs
|
||||
- Fix exception message ([@tricknotes](https://github.com/tricknotes))
|
||||
- Added negative number literal support
|
||||
- Concert library to single IIFE
|
||||
- Add handlebars-source gemspec ([@machty](https://github.com/machty))
|
||||
|
||||
[Commits](https://github.com/wycats/handlebars.js/compare/v1.0.9...v1.0.10)
|
||||
|
||||
## v1.0.9 - Node - Feb 15 2013
|
||||
|
||||
- Added `Handlebars.create` API in node module for sandboxed instances ([@tommydudebreaux](https://github.com/tommydudebreaux))
|
||||
|
||||
[Commits](https://github.com/wycats/handlebars.js/compare/1.0.0-rc.3...v1.0.9)
|
||||
|
||||
## 1.0.0-rc3 - Browser - Feb 14 2013
|
||||
|
||||
- Prevent use of `this` or `..` in illogical place ([@leshill](https://github.com/leshill))
|
||||
- Allow AST passing for `parse`/`compile`/`precompile` ([@machty](https://github.com/machty))
|
||||
- Optimize generated output by inlining statements where possible
|
||||
- Check compiler version when evaluating templates
|
||||
- Package browser dist in npm package
|
||||
|
||||
[Commits](https://github.com/wycats/handlebars.js/compare/v1.0.8...1.0.0-rc.3)
|
||||
|
||||
## Prior Versions
|
||||
|
||||
When upgrading from the Handlebars 0.9 series, be aware that the
|
||||
signature for passing custom helpers or partials to templates has
|
||||
changed.
|
||||
|
||||
Instead of:
|
||||
|
||||
```js
|
||||
template(context, helpers, partials, [data])
|
||||
```
|
||||
|
||||
Use:
|
||||
|
||||
```js
|
||||
template(context, {helpers: helpers, partials: partials, data: data})
|
||||
```
|
||||
|
||||
[builds-page]: http://builds.handlebarsjs.com.s3.amazonaws.com/index.html
|
||||
[cdnjs]: http://cdnjs.com/libraries/handlebars.js/
|
||||
[components]: https://github.com/components/handlebars.js
|
||||
[npm]: https://npmjs.org/package/handlebars
|
||||
@@ -0,0 +1,3 @@
|
||||
// Create a simple path alias to allow browserify to resolve
|
||||
// the runtime on a supported path.
|
||||
module.exports = require('./dist/cjs/handlebars.runtime')['default'];
|
||||
@@ -0,0 +1,36 @@
|
||||
{
|
||||
"globals": {
|
||||
"CompilerContext": true,
|
||||
"Handlebars": true,
|
||||
"handlebarsEnv": true,
|
||||
|
||||
"shouldCompileTo": true,
|
||||
"shouldCompileToWithPartials": true,
|
||||
"shouldThrow": 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,
|
||||
"strictEqual": true,
|
||||
"define": true
|
||||
},
|
||||
"env": {
|
||||
"mocha": true
|
||||
},
|
||||
"rules": {
|
||||
// Disabling for tests, for now.
|
||||
"no-path-concat": 0,
|
||||
|
||||
"no-var": 0
|
||||
}
|
||||
}
|
||||
@@ -1,101 +0,0 @@
|
||||
require "spec_helper"
|
||||
|
||||
class TestContext
|
||||
class TestModule
|
||||
attr_reader :name, :tests
|
||||
|
||||
def initialize(name)
|
||||
@name = name
|
||||
@tests = []
|
||||
end
|
||||
end
|
||||
|
||||
attr_reader :modules
|
||||
|
||||
def initialize
|
||||
@modules = []
|
||||
end
|
||||
|
||||
def module(name)
|
||||
@modules << TestModule.new(name)
|
||||
end
|
||||
|
||||
def test(name, function)
|
||||
@modules.last.tests << [name, function]
|
||||
end
|
||||
end
|
||||
|
||||
test_context = TestContext.new
|
||||
js_context = Handlebars::Spec::CONTEXT
|
||||
|
||||
Module.new do
|
||||
extend Test::Unit::Assertions
|
||||
|
||||
def self.js_backtrace(context)
|
||||
begin
|
||||
context.eval("throw")
|
||||
rescue V8::JSError => e
|
||||
return e.backtrace(:javascript)
|
||||
end
|
||||
end
|
||||
|
||||
js_context["p"] = proc do |this, str|
|
||||
p str
|
||||
end
|
||||
|
||||
js_context["ok"] = proc do |this, ok, message|
|
||||
js_context["$$RSPEC1$$"] = ok
|
||||
|
||||
result = js_context.eval("!!$$RSPEC1$$")
|
||||
|
||||
message ||= "#{ok} was not truthy"
|
||||
|
||||
unless result
|
||||
backtrace = js_backtrace(js_context)
|
||||
message << "\n#{backtrace.join("\n")}"
|
||||
end
|
||||
|
||||
assert result, message
|
||||
end
|
||||
|
||||
js_context["equals"] = proc do |this, first, second, message|
|
||||
js_context["$$RSPEC1$$"] = first
|
||||
js_context["$$RSPEC2$$"] = second
|
||||
|
||||
result = js_context.eval("$$RSPEC1$$ == $$RSPEC2$$")
|
||||
|
||||
additional_message = "#{first.inspect} did not == #{second.inspect}"
|
||||
message = message ? "#{message} (#{additional_message})" : additional_message
|
||||
|
||||
unless result
|
||||
backtrace = js_backtrace(js_context)
|
||||
message << "\n#{backtrace.join("\n")}"
|
||||
end
|
||||
|
||||
assert result, message
|
||||
end
|
||||
|
||||
js_context["equal"] = js_context["equals"]
|
||||
|
||||
js_context["suite"] = proc do |this, name|
|
||||
test_context.module(name)
|
||||
end
|
||||
|
||||
js_context["test"] = proc do |this, name, function|
|
||||
test_context.test(name, function)
|
||||
end
|
||||
|
||||
local = Regexp.escape(File.expand_path(Dir.pwd))
|
||||
qunit_spec = File.expand_path("../qunit_spec.js", __FILE__)
|
||||
js_context.load(qunit_spec.sub(/^#{local}\//, ''))
|
||||
end
|
||||
|
||||
test_context.modules.each do |mod|
|
||||
describe mod.name do
|
||||
mod.tests.each do |name, function|
|
||||
it name do
|
||||
function.call
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,105 @@
|
||||
<html>
|
||||
<head>
|
||||
<title>Mocha</title>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
|
||||
<link rel="stylesheet" href="/node_modules/mocha/mocha.css" />
|
||||
<style>
|
||||
.headless .suite > h1,
|
||||
.headless .test.pass {
|
||||
display: none;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
// Show only errors in "headless", non-interactive mode.
|
||||
if (/headless=true/.test(location.href)) {
|
||||
document.documentElement.className = 'headless';
|
||||
}
|
||||
</script>
|
||||
<script src="/node_modules/mocha/mocha.js"></script>
|
||||
<script>
|
||||
mocha.setup('bdd');
|
||||
</script>
|
||||
|
||||
<script src="/spec/env/json2.js"></script>
|
||||
<script src="/spec/env/require.js"></script>
|
||||
|
||||
<script src="/spec/env/common.js"></script>
|
||||
<script>
|
||||
var requireFailure;
|
||||
|
||||
requirejs.config({
|
||||
paths: {
|
||||
'handlebars.runtime': '/dist/handlebars.runtime.amd'
|
||||
}
|
||||
});
|
||||
requirejs.onError = function (err) {
|
||||
requireFailure = err;
|
||||
};
|
||||
</script>
|
||||
<script>
|
||||
onload = function(){
|
||||
require(['handlebars.runtime'], function(Handlebars) {
|
||||
describe('runtime', function() {
|
||||
it('should load', function() {
|
||||
equal(!!Handlebars['default'].template, true);
|
||||
equal(!!Handlebars['default'].VERSION, true);
|
||||
});
|
||||
});
|
||||
|
||||
mocha.globals(['mochaResults'])
|
||||
// The test harness leaks under FF. We should have decent global leak coverage from other tests
|
||||
if (!navigator.userAgent.match(/Firefox\/([\d.]+)/)) {
|
||||
mocha.checkLeaks();
|
||||
}
|
||||
var runner = mocha.run();
|
||||
|
||||
//Reporting for saucelabs
|
||||
var failedTests = [];
|
||||
runner.on('end', function(){
|
||||
window.mochaResults = runner.stats;
|
||||
window.mochaResults.reports = failedTests;
|
||||
});
|
||||
|
||||
runner.on('fail', logFailure);
|
||||
|
||||
// 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){
|
||||
|
||||
var flattenTitles = function(test){
|
||||
var titles = [];
|
||||
while (test.parent.title){
|
||||
titles.push(test.parent.title);
|
||||
test = test.parent;
|
||||
}
|
||||
return titles.reverse();
|
||||
};
|
||||
|
||||
failedTests.push({
|
||||
name: test.title,
|
||||
result: false,
|
||||
message: err.message,
|
||||
stack: err.stack,
|
||||
titles: flattenTitles(test)
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<div id="mocha"></div>
|
||||
</body>
|
||||
</html>
|
||||
+125
@@ -0,0 +1,125 @@
|
||||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Mocha</title>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
|
||||
<link rel="stylesheet" href="/node_modules/mocha/mocha.css" />
|
||||
<style>
|
||||
.headless .suite > h1,
|
||||
.headless .test.pass {
|
||||
display: none;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
// Show only errors in "headless", non-interactive mode.
|
||||
if (/headless=true/.test(location.href)) {
|
||||
document.documentElement.className = 'headless';
|
||||
}
|
||||
</script>
|
||||
<script src="/node_modules/mocha/mocha.js"></script>
|
||||
<script>
|
||||
mocha.setup('bdd');
|
||||
</script>
|
||||
|
||||
<script src="/spec/env/json2.js"></script>
|
||||
<script src="/spec/env/require.js"></script>
|
||||
|
||||
<script src="/spec/env/common.js"></script>
|
||||
<script>
|
||||
var requireFailure;
|
||||
|
||||
requirejs.config({
|
||||
paths: {
|
||||
handlebars: '/dist/handlebars.amd',
|
||||
tests: '/tmp/tests'
|
||||
}
|
||||
});
|
||||
requirejs.onError = function (err) {
|
||||
requireFailure = err;
|
||||
};
|
||||
|
||||
var CompilerContext = {
|
||||
compile: function(template, options) {
|
||||
var templateSpec = handlebarsEnv.precompile(template, options);
|
||||
return handlebarsEnv.template(safeEval(templateSpec));
|
||||
},
|
||||
compileWithPartial: function(template, options) {
|
||||
return handlebarsEnv.compile(template, options);
|
||||
}
|
||||
};
|
||||
|
||||
function safeEval(templateSpec) {
|
||||
try {
|
||||
var ret;
|
||||
eval('ret = ' + templateSpec);
|
||||
return ret;
|
||||
} catch (err) {
|
||||
console.error(templateSpec);
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<script>
|
||||
onload = function(){
|
||||
|
||||
require(['handlebars'], function(Handlebars) {
|
||||
window.Handlebars = Handlebars['default'];
|
||||
|
||||
require(['tests'], function(Handlebars) {
|
||||
mocha.globals(['mochaResults'])
|
||||
// The test harness leaks under FF. We should have decent global leak coverage from other tests
|
||||
if (!navigator.userAgent.match(/Firefox\/([\d.]+)/)) {
|
||||
mocha.checkLeaks();
|
||||
}
|
||||
var runner = mocha.run();
|
||||
|
||||
//Reporting for saucelabs
|
||||
var failedTests = [];
|
||||
runner.on('end', function(){
|
||||
window.mochaResults = runner.stats;
|
||||
window.mochaResults.reports = failedTests;
|
||||
});
|
||||
|
||||
runner.on('fail', logFailure);
|
||||
|
||||
// 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 flattenTitles(test){
|
||||
var titles = [];
|
||||
while (test.parent.title){
|
||||
titles.push(test.parent.title);
|
||||
test = test.parent;
|
||||
}
|
||||
return titles.reverse();
|
||||
}
|
||||
|
||||
failedTests.push({
|
||||
name: test.title,
|
||||
result: false,
|
||||
message: err.message,
|
||||
stack: err.stack,
|
||||
titles: flattenTitles(test)
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<div id="mocha"></div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1 @@
|
||||
a
|
||||
@@ -0,0 +1 @@
|
||||
{{foo}}
|
||||
@@ -0,0 +1 @@
|
||||
Hello, {{name}}!
|
||||
+244
@@ -0,0 +1,244 @@
|
||||
describe('ast', function() {
|
||||
if (!Handlebars.AST) {
|
||||
return;
|
||||
}
|
||||
|
||||
var AST = Handlebars.AST;
|
||||
|
||||
describe('BlockStatement', function() {
|
||||
it('should throw on mustache mismatch', function() {
|
||||
shouldThrow(function() {
|
||||
handlebarsEnv.parse('\n {{#foo}}{{/bar}}');
|
||||
}, Handlebars.Exception, "foo doesn't match bar - 2:5");
|
||||
});
|
||||
});
|
||||
|
||||
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);
|
||||
|
||||
equals(AST.helpers.helperExpression({type: 'PartialStatement'}), 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('Line Numbers', function() {
|
||||
var ast, body;
|
||||
|
||||
function testColumns(node, firstLine, lastLine, firstColumn, lastColumn) {
|
||||
equals(node.loc.start.line, firstLine);
|
||||
equals(node.loc.start.column, firstColumn);
|
||||
equals(node.loc.end.line, lastLine);
|
||||
equals(node.loc.end.column, lastColumn);
|
||||
}
|
||||
|
||||
ast = Handlebars.parse(
|
||||
'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
|
||||
body = ast.body;
|
||||
|
||||
it('gets ContentNode line numbers', function() {
|
||||
var contentNode = body[0];
|
||||
testColumns(contentNode, 1, 1, 0, 7);
|
||||
});
|
||||
|
||||
it('gets MustacheStatement line numbers', function() {
|
||||
var mustacheNode = body[1];
|
||||
testColumns(mustacheNode, 1, 1, 7, 21);
|
||||
});
|
||||
|
||||
it('gets line numbers correct when newlines appear', function() {
|
||||
testColumns(body[2], 1, 2, 21, 8);
|
||||
});
|
||||
|
||||
it('gets MustacheStatement line numbers correct across newlines', function() {
|
||||
var secondMustacheStatement = body[3];
|
||||
testColumns(secondMustacheStatement, 2, 2, 8, 22);
|
||||
});
|
||||
|
||||
it('gets the block helper information correct', function() {
|
||||
var blockHelperNode = body[5];
|
||||
testColumns(blockHelperNode, 3, 7, 8, 23);
|
||||
});
|
||||
|
||||
it('correctly records the line numbers the program of a block helper', function() {
|
||||
var blockHelperNode = body[5],
|
||||
program = blockHelperNode.program;
|
||||
|
||||
testColumns(program, 3, 5, 31, 5);
|
||||
});
|
||||
|
||||
it('correctly records the line numbers of an inverse of a block helper', function() {
|
||||
var blockHelperNode = body[5],
|
||||
inverse = blockHelperNode.inverse;
|
||||
|
||||
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('mustache', function() {
|
||||
it('does not mark mustaches as standalone', function() {
|
||||
var ast = Handlebars.parse(' {{comment}} ');
|
||||
equals(!!ast.body[0].value, true);
|
||||
equals(!!ast.body[2].value, true);
|
||||
});
|
||||
});
|
||||
describe('blocks', function() {
|
||||
it('marks block mustaches as standalone', function() {
|
||||
var ast = Handlebars.parse(' {{# comment}} \nfoo\n {{else}} \n bar \n {{/comment}} '),
|
||||
block = ast.body[1];
|
||||
|
||||
equals(ast.body[0].value, '');
|
||||
|
||||
equals(block.program.body[0].value, 'foo\n');
|
||||
equals(block.inverse.body[0].value, ' bar \n');
|
||||
|
||||
equals(ast.body[2].value, '');
|
||||
});
|
||||
it('marks initial block mustaches as standalone', function() {
|
||||
var ast = Handlebars.parse('{{# comment}} \nfoo\n {{/comment}}'),
|
||||
block = ast.body[0];
|
||||
|
||||
equals(block.program.body[0].value, 'foo\n');
|
||||
});
|
||||
it('marks mustaches with children as standalone', function() {
|
||||
var ast = Handlebars.parse('{{# comment}} \n{{foo}}\n {{/comment}}'),
|
||||
block = ast.body[0];
|
||||
|
||||
equals(block.program.body[0].value, '');
|
||||
equals(block.program.body[1].path.original, 'foo');
|
||||
equals(block.program.body[2].value, '\n');
|
||||
});
|
||||
it('marks nested block mustaches as standalone', function() {
|
||||
var ast = Handlebars.parse('{{#foo}} \n{{# comment}} \nfoo\n {{else}} \n bar \n {{/comment}} \n{{/foo}}'),
|
||||
body = ast.body[0].program.body,
|
||||
block = body[1];
|
||||
|
||||
equals(body[0].value, '');
|
||||
|
||||
equals(block.program.body[0].value, 'foo\n');
|
||||
equals(block.inverse.body[0].value, ' bar \n');
|
||||
|
||||
equals(body[0].value, '');
|
||||
});
|
||||
it('does not mark nested block mustaches as standalone', function() {
|
||||
var ast = Handlebars.parse('{{#foo}} {{# comment}} \nfoo\n {{else}} \n bar \n {{/comment}} {{/foo}}'),
|
||||
body = ast.body[0].program.body,
|
||||
block = body[1];
|
||||
|
||||
equals(body[0].omit, undefined);
|
||||
|
||||
equals(block.program.body[0].value, ' \nfoo\n');
|
||||
equals(block.inverse.body[0].value, ' bar \n ');
|
||||
|
||||
equals(body[0].omit, undefined);
|
||||
});
|
||||
it('does not mark nested initial block mustaches as standalone', function() {
|
||||
var ast = Handlebars.parse('{{#foo}}{{# comment}} \nfoo\n {{else}} \n bar \n {{/comment}}{{/foo}}'),
|
||||
body = ast.body[0].program.body,
|
||||
block = body[0];
|
||||
|
||||
equals(block.program.body[0].value, ' \nfoo\n');
|
||||
equals(block.inverse.body[0].value, ' bar \n ');
|
||||
|
||||
equals(body[0].omit, undefined);
|
||||
});
|
||||
|
||||
it('marks column 0 block mustaches as standalone', function() {
|
||||
var ast = Handlebars.parse('test\n{{# comment}} \nfoo\n {{else}} \n bar \n {{/comment}} '),
|
||||
block = ast.body[1];
|
||||
|
||||
equals(ast.body[0].omit, undefined);
|
||||
|
||||
equals(block.program.body[0].value, 'foo\n');
|
||||
equals(block.inverse.body[0].value, ' bar \n');
|
||||
|
||||
equals(ast.body[2].value, '');
|
||||
});
|
||||
});
|
||||
describe('partials', function() {
|
||||
it('marks partial as standalone', function() {
|
||||
var ast = Handlebars.parse('{{> partial }} ');
|
||||
equals(ast.body[1].value, '');
|
||||
});
|
||||
it('marks indented partial as standalone', function() {
|
||||
var ast = Handlebars.parse(' {{> partial }} ');
|
||||
equals(ast.body[0].value, '');
|
||||
equals(ast.body[1].indent, ' ');
|
||||
equals(ast.body[2].value, '');
|
||||
});
|
||||
it('marks those around content as not standalone', function() {
|
||||
var ast = Handlebars.parse('a{{> partial }}');
|
||||
equals(ast.body[0].omit, undefined);
|
||||
|
||||
ast = Handlebars.parse('{{> partial }}a');
|
||||
equals(ast.body[1].omit, undefined);
|
||||
});
|
||||
});
|
||||
describe('comments', function() {
|
||||
it('marks comment as standalone', function() {
|
||||
var ast = Handlebars.parse('{{! comment }} ');
|
||||
equals(ast.body[1].value, '');
|
||||
});
|
||||
it('marks indented comment as standalone', function() {
|
||||
var ast = Handlebars.parse(' {{! comment }} ');
|
||||
equals(ast.body[0].value, '');
|
||||
equals(ast.body[2].value, '');
|
||||
});
|
||||
it('marks those around content as not standalone', function() {
|
||||
var ast = Handlebars.parse('a{{! comment }}');
|
||||
equals(ast.body[0].omit, undefined);
|
||||
|
||||
ast = Handlebars.parse('{{! comment }}a');
|
||||
equals(ast.body[1].omit, undefined);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
+305
@@ -0,0 +1,305 @@
|
||||
global.handlebarsEnv = null;
|
||||
|
||||
beforeEach(function() {
|
||||
global.handlebarsEnv = Handlebars.create();
|
||||
});
|
||||
|
||||
describe('basic context', function() {
|
||||
it('most basic', function() {
|
||||
shouldCompileTo('{{foo}}', { foo: 'foo' }, 'foo');
|
||||
});
|
||||
|
||||
it('escaping', function() {
|
||||
shouldCompileTo('\\{{foo}}', { foo: 'food' }, '{{foo}}');
|
||||
shouldCompileTo('content \\{{foo}}', { foo: 'food' }, 'content {{foo}}');
|
||||
shouldCompileTo('\\\\{{foo}}', { foo: 'food' }, '\\food');
|
||||
shouldCompileTo('content \\\\{{foo}}', { foo: 'food' }, 'content \\food');
|
||||
shouldCompileTo('\\\\ {{foo}}', { foo: 'food' }, '\\\\ food');
|
||||
});
|
||||
|
||||
it('compiling with a basic context', function() {
|
||||
shouldCompileTo('Goodbye\n{{cruel}}\n{{world}}!', {cruel: 'cruel', world: 'world'}, 'Goodbye\ncruel\nworld!',
|
||||
'It works if all the required keys are provided');
|
||||
});
|
||||
|
||||
it('compiling with a string context', function() {
|
||||
shouldCompileTo('{{.}}{{length}}', 'bye', 'bye3');
|
||||
});
|
||||
|
||||
it('compiling with an undefined context', function() {
|
||||
shouldCompileTo('Goodbye\n{{cruel}}\n{{world.bar}}!', undefined, 'Goodbye\n\n!');
|
||||
|
||||
shouldCompileTo('{{#unless foo}}Goodbye{{../test}}{{test2}}{{/unless}}', undefined, 'Goodbye');
|
||||
});
|
||||
|
||||
it('comments', function() {
|
||||
shouldCompileTo('{{! Goodbye}}Goodbye\n{{cruel}}\n{{world}}!',
|
||||
{cruel: 'cruel', world: 'world'}, 'Goodbye\ncruel\nworld!',
|
||||
'comments are ignored');
|
||||
|
||||
shouldCompileTo(' {{~! comment ~}} blah', {}, 'blah');
|
||||
shouldCompileTo(' {{~!-- long-comment --~}} blah', {}, 'blah');
|
||||
shouldCompileTo(' {{! comment ~}} blah', {}, ' blah');
|
||||
shouldCompileTo(' {{!-- long-comment --~}} blah', {}, ' blah');
|
||||
shouldCompileTo(' {{~! comment}} blah', {}, ' blah');
|
||||
shouldCompileTo(' {{~!-- long-comment --}} blah', {}, ' blah');
|
||||
});
|
||||
|
||||
it('boolean', function() {
|
||||
var string = '{{#goodbye}}GOODBYE {{/goodbye}}cruel {{world}}!';
|
||||
shouldCompileTo(string, {goodbye: true, world: 'world'}, 'GOODBYE cruel world!',
|
||||
'booleans show the contents when true');
|
||||
|
||||
shouldCompileTo(string, {goodbye: false, world: 'world'}, 'cruel world!',
|
||||
'booleans do not show the contents when false');
|
||||
});
|
||||
|
||||
it('zeros', function() {
|
||||
shouldCompileTo('num1: {{num1}}, num2: {{num2}}', {num1: 42, num2: 0},
|
||||
'num1: 42, num2: 0');
|
||||
shouldCompileTo('num: {{.}}', 0, 'num: 0');
|
||||
shouldCompileTo('num: {{num1/num2}}', {num1: {num2: 0}}, 'num: 0');
|
||||
});
|
||||
it('false', function() {
|
||||
/* eslint-disable no-new-wrappers */
|
||||
shouldCompileTo('val1: {{val1}}, val2: {{val2}}', {val1: false, val2: new Boolean(false)}, 'val1: false, val2: false');
|
||||
shouldCompileTo('val: {{.}}', false, 'val: false');
|
||||
shouldCompileTo('val: {{val1/val2}}', {val1: {val2: false}}, 'val: false');
|
||||
|
||||
shouldCompileTo('val1: {{{val1}}}, val2: {{{val2}}}', {val1: false, val2: new Boolean(false)}, 'val1: false, val2: false');
|
||||
shouldCompileTo('val: {{{val1/val2}}}', {val1: {val2: false}}, 'val: false');
|
||||
/* eslint-enable */
|
||||
});
|
||||
|
||||
it('should handle undefined and null', function() {
|
||||
shouldCompileTo('{{awesome undefined null}}',
|
||||
{
|
||||
awesome: function(_undefined, _null, options) {
|
||||
return (_undefined === undefined) + ' ' + (_null === null) + ' ' + (typeof options);
|
||||
}
|
||||
},
|
||||
'true true object');
|
||||
shouldCompileTo('{{undefined}}',
|
||||
{
|
||||
'undefined': function() {
|
||||
return 'undefined!';
|
||||
}
|
||||
},
|
||||
'undefined!');
|
||||
shouldCompileTo('{{null}}',
|
||||
{
|
||||
'null': function() {
|
||||
return 'null!';
|
||||
}
|
||||
},
|
||||
'null!');
|
||||
});
|
||||
|
||||
it('newlines', function() {
|
||||
shouldCompileTo("Alan's\nTest", {}, "Alan's\nTest");
|
||||
shouldCompileTo("Alan's\rTest", {}, "Alan's\rTest");
|
||||
});
|
||||
|
||||
it('escaping text', function() {
|
||||
shouldCompileTo("Awesome's", {}, "Awesome's", "text is escaped so that it doesn't get caught on single quotes");
|
||||
shouldCompileTo('Awesome\\', {}, 'Awesome\\', "text is escaped so that the closing quote can't be ignored");
|
||||
shouldCompileTo('Awesome\\\\ foo', {}, 'Awesome\\\\ foo', "text is escaped so that it doesn't mess up backslashes");
|
||||
shouldCompileTo('Awesome {{foo}}', {foo: '\\'}, 'Awesome \\', "text is escaped so that it doesn't mess up backslashes");
|
||||
shouldCompileTo(" ' ' ", {}, " ' ' ", 'double quotes never produce invalid javascript');
|
||||
});
|
||||
|
||||
it('escaping expressions', function() {
|
||||
shouldCompileTo('{{{awesome}}}', {awesome: '&\'\\<>'}, '&\'\\<>',
|
||||
"expressions with 3 handlebars aren't escaped");
|
||||
|
||||
shouldCompileTo('{{&awesome}}', {awesome: '&\'\\<>'}, '&\'\\<>',
|
||||
"expressions with {{& handlebars aren't escaped");
|
||||
|
||||
shouldCompileTo('{{awesome}}', {awesome: "&\"'`\\<>"}, '&"'`\\<>',
|
||||
'by default expressions should be escaped');
|
||||
|
||||
shouldCompileTo('{{awesome}}', {awesome: 'Escaped, <b> looks like: <b>'}, 'Escaped, <b> looks like: &lt;b&gt;',
|
||||
'escaping should properly handle amperstands');
|
||||
});
|
||||
|
||||
it("functions returning safestrings shouldn't be escaped", function() {
|
||||
var hash = {awesome: function() { return new Handlebars.SafeString('&\'\\<>'); }};
|
||||
shouldCompileTo('{{awesome}}', hash, '&\'\\<>',
|
||||
"functions returning safestrings aren't escaped");
|
||||
});
|
||||
|
||||
it('functions', function() {
|
||||
shouldCompileTo('{{awesome}}', {awesome: function() { return 'Awesome'; }}, 'Awesome',
|
||||
'functions are called and render their output');
|
||||
shouldCompileTo('{{awesome}}', {awesome: function() { return this.more; }, more: 'More awesome'}, 'More awesome',
|
||||
'functions are bound to the context');
|
||||
});
|
||||
|
||||
it('functions with context argument', function() {
|
||||
shouldCompileTo('{{awesome frank}}',
|
||||
{awesome: function(context) { return context; },
|
||||
frank: 'Frank'},
|
||||
'Frank', 'functions are called with context arguments');
|
||||
});
|
||||
it('pathed functions with context argument', function() {
|
||||
shouldCompileTo('{{bar.awesome frank}}',
|
||||
{bar: {awesome: function(context) { return context; }},
|
||||
frank: 'Frank'},
|
||||
'Frank', 'functions are called with context arguments');
|
||||
});
|
||||
it('depthed functions with context argument', function() {
|
||||
shouldCompileTo('{{#with frank}}{{../awesome .}}{{/with}}',
|
||||
{awesome: function(context) { return context; },
|
||||
frank: 'Frank'},
|
||||
'Frank', 'functions are called with context arguments');
|
||||
});
|
||||
|
||||
it('block functions with context argument', function() {
|
||||
shouldCompileTo('{{#awesome 1}}inner {{.}}{{/awesome}}',
|
||||
{awesome: function(context, options) { return options.fn(context); }},
|
||||
'inner 1', 'block functions are called with context and options');
|
||||
});
|
||||
|
||||
it('depthed block functions with context argument', function() {
|
||||
shouldCompileTo('{{#with value}}{{#../awesome 1}}inner {{.}}{{/../awesome}}{{/with}}',
|
||||
{value: true, awesome: function(context, options) { return options.fn(context); }},
|
||||
'inner 1', 'block functions are called with context and options');
|
||||
});
|
||||
|
||||
it('block functions without context argument', function() {
|
||||
shouldCompileTo('{{#awesome}}inner{{/awesome}}',
|
||||
{awesome: function(options) { return options.fn(this); }},
|
||||
'inner', 'block functions are called with options');
|
||||
});
|
||||
it('pathed block functions without context argument', function() {
|
||||
shouldCompileTo('{{#foo.awesome}}inner{{/foo.awesome}}',
|
||||
{foo: {awesome: function() { return this; }}},
|
||||
'inner', 'block functions are called with options');
|
||||
});
|
||||
it('depthed block functions without context argument', function() {
|
||||
shouldCompileTo('{{#with value}}{{#../awesome}}inner{{/../awesome}}{{/with}}',
|
||||
{value: true, awesome: function() { return this; }},
|
||||
'inner', 'block functions are called with options');
|
||||
});
|
||||
|
||||
|
||||
it('paths with hyphens', function() {
|
||||
shouldCompileTo('{{foo-bar}}', {'foo-bar': 'baz'}, 'baz', 'Paths can contain hyphens (-)');
|
||||
shouldCompileTo('{{foo.foo-bar}}', {foo: {'foo-bar': 'baz'}}, 'baz', 'Paths can contain hyphens (-)');
|
||||
shouldCompileTo('{{foo/foo-bar}}', {foo: {'foo-bar': 'baz'}}, 'baz', 'Paths can contain hyphens (-)');
|
||||
});
|
||||
|
||||
it('nested paths', function() {
|
||||
shouldCompileTo('Goodbye {{alan/expression}} world!', {alan: {expression: 'beautiful'}},
|
||||
'Goodbye beautiful world!', 'Nested paths access nested objects');
|
||||
});
|
||||
|
||||
it('nested paths with empty string value', function() {
|
||||
shouldCompileTo('Goodbye {{alan/expression}} world!', {alan: {expression: ''}},
|
||||
'Goodbye world!', 'Nested paths access nested objects with empty string');
|
||||
});
|
||||
|
||||
it('literal paths', function() {
|
||||
shouldCompileTo('Goodbye {{[@alan]/expression}} world!', {'@alan': {expression: 'beautiful'}},
|
||||
'Goodbye beautiful world!', 'Literal paths can be used');
|
||||
shouldCompileTo('Goodbye {{[foo bar]/expression}} world!', {'foo bar': {expression: 'beautiful'}},
|
||||
'Goodbye beautiful world!', 'Literal paths can be used');
|
||||
});
|
||||
|
||||
it('literal references', function() {
|
||||
shouldCompileTo('Goodbye {{[foo bar]}} world!', {'foo bar': 'beautiful'}, 'Goodbye beautiful world!');
|
||||
shouldCompileTo('Goodbye {{"foo bar"}} world!', {'foo bar': 'beautiful'}, 'Goodbye beautiful world!');
|
||||
shouldCompileTo("Goodbye {{'foo bar'}} world!", {'foo bar': 'beautiful'}, 'Goodbye beautiful world!');
|
||||
shouldCompileTo('Goodbye {{"foo[bar"}} world!', {'foo[bar': 'beautiful'}, 'Goodbye beautiful world!');
|
||||
shouldCompileTo('Goodbye {{"foo\'bar"}} world!', {"foo'bar": 'beautiful'}, 'Goodbye beautiful world!');
|
||||
shouldCompileTo("Goodbye {{'foo\"bar'}} world!", {'foo"bar': 'beautiful'}, 'Goodbye beautiful world!');
|
||||
});
|
||||
|
||||
it("that current context path ({{.}}) doesn't hit helpers", function() {
|
||||
shouldCompileTo('test: {{.}}', [null, {helper: 'awesome'}], 'test: ');
|
||||
});
|
||||
|
||||
it('complex but empty paths', function() {
|
||||
shouldCompileTo('{{person/name}}', {person: {name: null}}, '');
|
||||
shouldCompileTo('{{person/name}}', {person: {}}, '');
|
||||
});
|
||||
|
||||
it('this keyword in paths', function() {
|
||||
var string = '{{#goodbyes}}{{this}}{{/goodbyes}}';
|
||||
var hash = {goodbyes: ['goodbye', 'Goodbye', 'GOODBYE']};
|
||||
shouldCompileTo(string, hash, 'goodbyeGoodbyeGOODBYE',
|
||||
'This keyword in paths evaluates to current context');
|
||||
|
||||
string = '{{#hellos}}{{this/text}}{{/hellos}}';
|
||||
hash = {hellos: [{text: 'hello'}, {text: 'Hello'}, {text: 'HELLO'}]};
|
||||
shouldCompileTo(string, hash, 'helloHelloHELLO', 'This keyword evaluates in more complex paths');
|
||||
});
|
||||
|
||||
it('this keyword nested inside path', function() {
|
||||
shouldThrow(function() {
|
||||
CompilerContext.compile('{{#hellos}}{{text/this/foo}}{{/hellos}}');
|
||||
}, Error, 'Invalid path: text/this - 1:13');
|
||||
|
||||
shouldCompileTo('{{[this]}}', {'this': 'bar'}, 'bar');
|
||||
shouldCompileTo('{{text/[this]}}', {text: {'this': 'bar'}}, 'bar');
|
||||
});
|
||||
|
||||
it('this keyword in helpers', function() {
|
||||
var helpers = {foo: function(value) {
|
||||
return 'bar ' + value;
|
||||
}};
|
||||
var string = '{{#goodbyes}}{{foo this}}{{/goodbyes}}';
|
||||
var hash = {goodbyes: ['goodbye', 'Goodbye', 'GOODBYE']};
|
||||
shouldCompileTo(string, [hash, helpers], 'bar goodbyebar Goodbyebar GOODBYE',
|
||||
'This keyword in paths evaluates to current context');
|
||||
|
||||
string = '{{#hellos}}{{foo this/text}}{{/hellos}}';
|
||||
hash = {hellos: [{text: 'hello'}, {text: 'Hello'}, {text: 'HELLO'}]};
|
||||
shouldCompileTo(string, [hash, helpers], 'bar hellobar Hellobar HELLO', 'This keyword evaluates in more complex paths');
|
||||
});
|
||||
|
||||
it('this keyword nested inside helpers param', function() {
|
||||
var string = '{{#hellos}}{{foo text/this/foo}}{{/hellos}}';
|
||||
shouldThrow(function() {
|
||||
CompilerContext.compile(string);
|
||||
}, Error, 'Invalid path: text/this - 1:17');
|
||||
|
||||
shouldCompileTo(
|
||||
'{{foo [this]}}',
|
||||
{foo: function(value) { return value; }, 'this': 'bar'},
|
||||
'bar');
|
||||
shouldCompileTo(
|
||||
'{{foo text/[this]}}',
|
||||
{foo: function(value) { return value; }, text: {'this': 'bar'}},
|
||||
'bar');
|
||||
});
|
||||
|
||||
it('pass string literals', function() {
|
||||
shouldCompileTo('{{"foo"}}', {}, '');
|
||||
shouldCompileTo('{{"foo"}}', { foo: 'bar' }, 'bar');
|
||||
shouldCompileTo('{{#"foo"}}{{.}}{{/"foo"}}', { foo: ['bar', 'baz'] }, 'barbaz');
|
||||
});
|
||||
|
||||
it('pass number literals', function() {
|
||||
shouldCompileTo('{{12}}', {}, '');
|
||||
shouldCompileTo('{{12}}', { '12': 'bar' }, 'bar');
|
||||
shouldCompileTo('{{12.34}}', {}, '');
|
||||
shouldCompileTo('{{12.34}}', { '12.34': 'bar' }, 'bar');
|
||||
shouldCompileTo('{{12.34 1}}', { '12.34': function(arg) { return 'bar' + arg; } }, 'bar1');
|
||||
});
|
||||
|
||||
it('pass boolean literals', function() {
|
||||
shouldCompileTo('{{true}}', {}, '');
|
||||
shouldCompileTo('{{true}}', { '': 'foo' }, '');
|
||||
shouldCompileTo('{{false}}', { 'false': 'foo' }, 'foo');
|
||||
});
|
||||
|
||||
it('should handle literals in subexpression', function() {
|
||||
var helpers = {
|
||||
foo: function(arg) {
|
||||
return arg;
|
||||
}
|
||||
};
|
||||
shouldCompileTo('{{foo (false)}}', [{ 'false': function() { return 'bar'; } }, helpers], 'bar');
|
||||
});
|
||||
});
|
||||
+347
@@ -0,0 +1,347 @@
|
||||
describe('blocks', function() {
|
||||
it('array', function() {
|
||||
var string = '{{#goodbyes}}{{text}}! {{/goodbyes}}cruel {{world}}!';
|
||||
var hash = {goodbyes: [{text: 'goodbye'}, {text: 'Goodbye'}, {text: 'GOODBYE'}], world: 'world'};
|
||||
shouldCompileTo(string, hash, 'goodbye! Goodbye! GOODBYE! cruel world!',
|
||||
'Arrays iterate over the contents when not empty');
|
||||
|
||||
shouldCompileTo(string, {goodbyes: [], world: 'world'}, 'cruel world!',
|
||||
'Arrays ignore the contents when empty');
|
||||
});
|
||||
|
||||
it('array without data', function() {
|
||||
var string = '{{#goodbyes}}{{text}}{{/goodbyes}} {{#goodbyes}}{{text}}{{/goodbyes}}';
|
||||
var hash = {goodbyes: [{text: 'goodbye'}, {text: 'Goodbye'}, {text: 'GOODBYE'}], world: 'world'};
|
||||
shouldCompileTo(string, [hash,,, false], 'goodbyeGoodbyeGOODBYE goodbyeGoodbyeGOODBYE');
|
||||
});
|
||||
|
||||
it('array with @index', function() {
|
||||
var string = '{{#goodbyes}}{{@index}}. {{text}}! {{/goodbyes}}cruel {{world}}!';
|
||||
var hash = {goodbyes: [{text: 'goodbye'}, {text: 'Goodbye'}, {text: 'GOODBYE'}], world: 'world'};
|
||||
|
||||
var template = CompilerContext.compile(string);
|
||||
var result = template(hash);
|
||||
|
||||
equal(result, '0. goodbye! 1. Goodbye! 2. GOODBYE! cruel world!', 'The @index variable is used');
|
||||
});
|
||||
|
||||
it('empty block', function() {
|
||||
var string = '{{#goodbyes}}{{/goodbyes}}cruel {{world}}!';
|
||||
var hash = {goodbyes: [{text: 'goodbye'}, {text: 'Goodbye'}, {text: 'GOODBYE'}], world: 'world'};
|
||||
shouldCompileTo(string, hash, 'cruel world!',
|
||||
'Arrays iterate over the contents when not empty');
|
||||
|
||||
shouldCompileTo(string, {goodbyes: [], world: 'world'}, 'cruel world!',
|
||||
'Arrays ignore the contents when empty');
|
||||
});
|
||||
|
||||
it('block with complex lookup', function() {
|
||||
var string = '{{#goodbyes}}{{text}} cruel {{../name}}! {{/goodbyes}}';
|
||||
var hash = {name: 'Alan', goodbyes: [{text: 'goodbye'}, {text: 'Goodbye'}, {text: 'GOODBYE'}]};
|
||||
|
||||
shouldCompileTo(string, hash, 'goodbye cruel Alan! Goodbye cruel Alan! GOODBYE cruel Alan! ',
|
||||
'Templates can access variables in contexts up the stack with relative path syntax');
|
||||
});
|
||||
|
||||
it('multiple blocks with complex lookup', function() {
|
||||
var string = '{{#goodbyes}}{{../name}}{{../name}}{{/goodbyes}}';
|
||||
var hash = {name: 'Alan', goodbyes: [{text: 'goodbye'}, {text: 'Goodbye'}, {text: 'GOODBYE'}]};
|
||||
|
||||
shouldCompileTo(string, hash, 'AlanAlanAlanAlanAlanAlan');
|
||||
});
|
||||
|
||||
it('block with complex lookup using nested context', function() {
|
||||
var string = '{{#goodbyes}}{{text}} cruel {{foo/../name}}! {{/goodbyes}}';
|
||||
|
||||
shouldThrow(function() {
|
||||
CompilerContext.compile(string);
|
||||
}, Error);
|
||||
});
|
||||
|
||||
it('block with deep nested complex lookup', function() {
|
||||
var string = '{{#outer}}Goodbye {{#inner}}cruel {{../sibling}} {{../../omg}}{{/inner}}{{/outer}}';
|
||||
var hash = {omg: 'OMG!', outer: [{ sibling: 'sad', inner: [{ text: 'goodbye' }] }] };
|
||||
|
||||
shouldCompileTo(string, hash, 'Goodbye cruel sad OMG!');
|
||||
});
|
||||
|
||||
it('works with cached blocks', function() {
|
||||
var template = CompilerContext.compile('{{#each person}}{{#with .}}{{first}} {{last}}{{/with}}{{/each}}', {data: false});
|
||||
|
||||
var result = template({person: [{first: 'Alan', last: 'Johnson'}, {first: 'Alan', last: 'Johnson'}]});
|
||||
equals(result, 'Alan JohnsonAlan Johnson');
|
||||
});
|
||||
|
||||
describe('inverted sections', function() {
|
||||
it('inverted sections with unset value', function() {
|
||||
var string = '{{#goodbyes}}{{this}}{{/goodbyes}}{{^goodbyes}}Right On!{{/goodbyes}}';
|
||||
var hash = {};
|
||||
shouldCompileTo(string, hash, 'Right On!', "Inverted section rendered when value isn't set.");
|
||||
});
|
||||
|
||||
it('inverted section with false value', function() {
|
||||
var string = '{{#goodbyes}}{{this}}{{/goodbyes}}{{^goodbyes}}Right On!{{/goodbyes}}';
|
||||
var hash = {goodbyes: false};
|
||||
shouldCompileTo(string, hash, 'Right On!', 'Inverted section rendered when value is false.');
|
||||
});
|
||||
|
||||
it('inverted section with empty set', function() {
|
||||
var string = '{{#goodbyes}}{{this}}{{/goodbyes}}{{^goodbyes}}Right On!{{/goodbyes}}';
|
||||
var hash = {goodbyes: []};
|
||||
shouldCompileTo(string, hash, 'Right On!', 'Inverted section rendered when value is empty set.');
|
||||
});
|
||||
|
||||
it('block inverted sections', function() {
|
||||
shouldCompileTo('{{#people}}{{name}}{{^}}{{none}}{{/people}}', {none: 'No people'},
|
||||
'No people');
|
||||
});
|
||||
it('chained inverted sections', function() {
|
||||
shouldCompileTo('{{#people}}{{name}}{{else if none}}{{none}}{{/people}}', {none: 'No people'},
|
||||
'No people');
|
||||
shouldCompileTo('{{#people}}{{name}}{{else if nothere}}fail{{else unless nothere}}{{none}}{{/people}}', {none: 'No people'},
|
||||
'No people');
|
||||
shouldCompileTo('{{#people}}{{name}}{{else if none}}{{none}}{{else}}fail{{/people}}', {none: 'No people'},
|
||||
'No people');
|
||||
});
|
||||
it('chained inverted sections with mismatch', function() {
|
||||
shouldThrow(function() {
|
||||
shouldCompileTo('{{#people}}{{name}}{{else if none}}{{none}}{{/if}}', {none: 'No people'},
|
||||
'No people');
|
||||
}, Error);
|
||||
});
|
||||
|
||||
it('block inverted sections with empty arrays', function() {
|
||||
shouldCompileTo('{{#people}}{{name}}{{^}}{{none}}{{/people}}', {none: 'No people', people: []},
|
||||
'No people');
|
||||
});
|
||||
});
|
||||
|
||||
describe('standalone sections', function() {
|
||||
it('block standalone else sections', function() {
|
||||
shouldCompileTo('{{#people}}\n{{name}}\n{{^}}\n{{none}}\n{{/people}}\n', {none: 'No people'},
|
||||
'No people\n');
|
||||
shouldCompileTo('{{#none}}\n{{.}}\n{{^}}\n{{none}}\n{{/none}}\n', {none: 'No people'},
|
||||
'No people\n');
|
||||
shouldCompileTo('{{#people}}\n{{name}}\n{{^}}\n{{none}}\n{{/people}}\n', {none: 'No people'},
|
||||
'No people\n');
|
||||
});
|
||||
it('block standalone else sections can be disabled', function() {
|
||||
shouldCompileTo(
|
||||
'{{#people}}\n{{name}}\n{{^}}\n{{none}}\n{{/people}}\n',
|
||||
[{none: 'No people'}, {}, {}, {ignoreStandalone: true}],
|
||||
'\nNo people\n\n');
|
||||
shouldCompileTo(
|
||||
'{{#none}}\n{{.}}\n{{^}}\nFail\n{{/none}}\n',
|
||||
[{none: 'No people'}, {}, {}, {ignoreStandalone: true}],
|
||||
'\nNo people\n\n');
|
||||
});
|
||||
it('block standalone chained else sections', function() {
|
||||
shouldCompileTo('{{#people}}\n{{name}}\n{{else if none}}\n{{none}}\n{{/people}}\n', {none: 'No people'},
|
||||
'No people\n');
|
||||
shouldCompileTo('{{#people}}\n{{name}}\n{{else if none}}\n{{none}}\n{{^}}\n{{/people}}\n', {none: 'No people'},
|
||||
'No people\n');
|
||||
});
|
||||
it('should handle nesting', function() {
|
||||
shouldCompileTo('{{#data}}\n{{#if true}}\n{{.}}\n{{/if}}\n{{/data}}\nOK.', {data: [1, 3, 5]}, '1\n3\n5\nOK.');
|
||||
});
|
||||
});
|
||||
|
||||
describe('compat mode', function() {
|
||||
it('block with deep recursive lookup lookup', function() {
|
||||
var string = '{{#outer}}Goodbye {{#inner}}cruel {{omg}}{{/inner}}{{/outer}}';
|
||||
var hash = {omg: 'OMG!', outer: [{ inner: [{ text: 'goodbye' }] }] };
|
||||
|
||||
shouldCompileTo(string, [hash, undefined, undefined, true], 'Goodbye cruel OMG!');
|
||||
});
|
||||
it('block with deep recursive pathed lookup', function() {
|
||||
var string = '{{#outer}}Goodbye {{#inner}}cruel {{omg.yes}}{{/inner}}{{/outer}}';
|
||||
var hash = {omg: {yes: 'OMG!'}, outer: [{ inner: [{ yes: 'no', text: 'goodbye' }] }] };
|
||||
|
||||
shouldCompileTo(string, [hash, undefined, undefined, true], 'Goodbye cruel OMG!');
|
||||
});
|
||||
it('block with missed recursive lookup', function() {
|
||||
var string = '{{#outer}}Goodbye {{#inner}}cruel {{omg.yes}}{{/inner}}{{/outer}}';
|
||||
var hash = {omg: {no: 'OMG!'}, outer: [{ inner: [{ yes: 'no', text: 'goodbye' }] }] };
|
||||
|
||||
shouldCompileTo(string, [hash, undefined, undefined, true], 'Goodbye cruel ');
|
||||
});
|
||||
});
|
||||
|
||||
describe('decorators', function() {
|
||||
it('should apply mustache decorators', function() {
|
||||
var helpers = {
|
||||
helper: function(options) {
|
||||
return options.fn.run;
|
||||
}
|
||||
};
|
||||
var decorators = {
|
||||
decorator: function(fn) {
|
||||
fn.run = 'success';
|
||||
return fn;
|
||||
}
|
||||
};
|
||||
shouldCompileTo(
|
||||
'{{#helper}}{{*decorator}}{{/helper}}',
|
||||
{hash: {}, helpers: helpers, decorators: decorators},
|
||||
'success');
|
||||
});
|
||||
it('should apply allow undefined return', function() {
|
||||
var helpers = {
|
||||
helper: function(options) {
|
||||
return options.fn() + options.fn.run;
|
||||
}
|
||||
};
|
||||
var decorators = {
|
||||
decorator: function(fn) {
|
||||
fn.run = 'cess';
|
||||
}
|
||||
};
|
||||
shouldCompileTo(
|
||||
'{{#helper}}{{*decorator}}suc{{/helper}}',
|
||||
{hash: {}, helpers: helpers, decorators: decorators},
|
||||
'success');
|
||||
});
|
||||
|
||||
it('should apply block decorators', function() {
|
||||
var helpers = {
|
||||
helper: function(options) {
|
||||
return options.fn.run;
|
||||
}
|
||||
};
|
||||
var decorators = {
|
||||
decorator: function(fn, props, container, options) {
|
||||
fn.run = options.fn();
|
||||
return fn;
|
||||
}
|
||||
};
|
||||
shouldCompileTo(
|
||||
'{{#helper}}{{#*decorator}}success{{/decorator}}{{/helper}}',
|
||||
{hash: {}, helpers: helpers, decorators: decorators},
|
||||
'success');
|
||||
});
|
||||
it('should support nested decorators', function() {
|
||||
var helpers = {
|
||||
helper: function(options) {
|
||||
return options.fn.run;
|
||||
}
|
||||
};
|
||||
var decorators = {
|
||||
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();
|
||||
}
|
||||
};
|
||||
shouldCompileTo(
|
||||
'{{#helper}}{{#*decorator}}{{#*nested}}suc{{/nested}}cess{{/decorator}}{{/helper}}',
|
||||
{hash: {}, helpers: helpers, decorators: decorators},
|
||||
'success');
|
||||
});
|
||||
|
||||
it('should apply multiple decorators', function() {
|
||||
var helpers = {
|
||||
helper: function(options) {
|
||||
return options.fn.run;
|
||||
}
|
||||
};
|
||||
var decorators = {
|
||||
decorator: function(fn, props, container, options) {
|
||||
fn.run = (fn.run || '') + options.fn();
|
||||
return fn;
|
||||
}
|
||||
};
|
||||
shouldCompileTo(
|
||||
'{{#helper}}{{#*decorator}}suc{{/decorator}}{{#*decorator}}cess{{/decorator}}{{/helper}}',
|
||||
{hash: {}, helpers: helpers, decorators: decorators},
|
||||
'success');
|
||||
});
|
||||
|
||||
it('should access parent variables', function() {
|
||||
var helpers = {
|
||||
helper: function(options) {
|
||||
return options.fn.run;
|
||||
}
|
||||
};
|
||||
var decorators = {
|
||||
decorator: function(fn, props, container, options) {
|
||||
fn.run = options.args;
|
||||
return fn;
|
||||
}
|
||||
};
|
||||
shouldCompileTo(
|
||||
'{{#helper}}{{*decorator foo}}{{/helper}}',
|
||||
{hash: {'foo': 'success'}, helpers: helpers, decorators: decorators},
|
||||
'success');
|
||||
});
|
||||
it('should work with root program', function() {
|
||||
var run;
|
||||
var decorators = {
|
||||
decorator: function(fn, props, container, options) {
|
||||
equals(options.args[0], 'success');
|
||||
run = true;
|
||||
return fn;
|
||||
}
|
||||
};
|
||||
shouldCompileTo(
|
||||
'{{*decorator "success"}}',
|
||||
{hash: {'foo': 'success'}, decorators: decorators},
|
||||
'');
|
||||
equals(run, true);
|
||||
});
|
||||
it('should fail when accessing variables from root', function() {
|
||||
var run;
|
||||
var decorators = {
|
||||
decorator: function(fn, props, container, options) {
|
||||
equals(options.args[0], undefined);
|
||||
run = true;
|
||||
return fn;
|
||||
}
|
||||
};
|
||||
shouldCompileTo(
|
||||
'{{*decorator foo}}',
|
||||
{hash: {'foo': 'fail'}, decorators: decorators},
|
||||
'');
|
||||
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');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,435 @@
|
||||
describe('builtin helpers', function() {
|
||||
describe('#if', function() {
|
||||
it('if', function() {
|
||||
var string = '{{#if goodbye}}GOODBYE {{/if}}cruel {{world}}!';
|
||||
shouldCompileTo(string, {goodbye: true, world: 'world'}, 'GOODBYE cruel world!',
|
||||
'if with boolean argument shows the contents when true');
|
||||
shouldCompileTo(string, {goodbye: 'dummy', world: 'world'}, 'GOODBYE cruel world!',
|
||||
'if with string argument shows the contents');
|
||||
shouldCompileTo(string, {goodbye: false, world: 'world'}, 'cruel world!',
|
||||
'if with boolean argument does not show the contents when false');
|
||||
shouldCompileTo(string, {world: 'world'}, 'cruel world!',
|
||||
'if with undefined does not show the contents');
|
||||
shouldCompileTo(string, {goodbye: ['foo'], world: 'world'}, 'GOODBYE cruel world!',
|
||||
'if with non-empty array shows the contents');
|
||||
shouldCompileTo(string, {goodbye: [], world: 'world'}, 'cruel world!',
|
||||
'if with empty array does not show the contents');
|
||||
shouldCompileTo(string, {goodbye: 0, world: 'world'}, 'cruel world!',
|
||||
'if with zero does not show the contents');
|
||||
shouldCompileTo('{{#if goodbye includeZero=true}}GOODBYE {{/if}}cruel {{world}}!',
|
||||
{goodbye: 0, world: 'world'}, 'GOODBYE cruel world!',
|
||||
'if with zero does not show the contents');
|
||||
});
|
||||
|
||||
it('if with function argument', function() {
|
||||
var string = '{{#if goodbye}}GOODBYE {{/if}}cruel {{world}}!';
|
||||
shouldCompileTo(string, {goodbye: function() {return true; }, world: 'world'}, 'GOODBYE cruel world!',
|
||||
'if with function shows the contents when function returns true');
|
||||
shouldCompileTo(string, {goodbye: function() {return this.world; }, world: 'world'}, 'GOODBYE cruel world!',
|
||||
'if with function shows the contents when function returns string');
|
||||
shouldCompileTo(string, {goodbye: function() {return false; }, world: 'world'}, 'cruel world!',
|
||||
'if with function does not show the contents when returns false');
|
||||
shouldCompileTo(string, {goodbye: function() {return this.foo; }, world: 'world'}, 'cruel world!',
|
||||
'if with function does not show the contents when returns undefined');
|
||||
});
|
||||
|
||||
it('should not change the depth list', function() {
|
||||
var string = '{{#with foo}}{{#if goodbye}}GOODBYE cruel {{../world}}!{{/if}}{{/with}}';
|
||||
shouldCompileTo(string, {foo: {goodbye: true}, world: 'world'}, 'GOODBYE cruel world!');
|
||||
});
|
||||
});
|
||||
|
||||
describe('#with', function() {
|
||||
it('with', function() {
|
||||
var string = '{{#with person}}{{first}} {{last}}{{/with}}';
|
||||
shouldCompileTo(string, {person: {first: 'Alan', last: 'Johnson'}}, 'Alan Johnson');
|
||||
});
|
||||
it('with with function argument', function() {
|
||||
var string = '{{#with person}}{{first}} {{last}}{{/with}}';
|
||||
shouldCompileTo(string, {person: function() { return {first: 'Alan', last: 'Johnson'}; }}, 'Alan Johnson');
|
||||
});
|
||||
it('with with else', function() {
|
||||
var string = '{{#with person}}Person is present{{else}}Person is not present{{/with}}';
|
||||
shouldCompileTo(string, {}, 'Person is not present');
|
||||
});
|
||||
it('with provides block parameter', function() {
|
||||
var string = '{{#with person as |foo|}}{{foo.first}} {{last}}{{/with}}';
|
||||
shouldCompileTo(string, {person: {first: 'Alan', last: 'Johnson'}}, 'Alan Johnson');
|
||||
});
|
||||
it('works when data is disabled', function() {
|
||||
var template = CompilerContext.compile('{{#with person as |foo|}}{{foo.first}} {{last}}{{/with}}', {data: false});
|
||||
|
||||
var result = template({person: {first: 'Alan', last: 'Johnson'}});
|
||||
equals(result, 'Alan Johnson');
|
||||
});
|
||||
});
|
||||
|
||||
describe('#each', function() {
|
||||
beforeEach(function() {
|
||||
handlebarsEnv.registerHelper('detectDataInsideEach', function(options) {
|
||||
return options.data && options.data.exclaim;
|
||||
});
|
||||
});
|
||||
|
||||
it('each', function() {
|
||||
var string = '{{#each goodbyes}}{{text}}! {{/each}}cruel {{world}}!';
|
||||
var hash = {goodbyes: [{text: 'goodbye'}, {text: 'Goodbye'}, {text: 'GOODBYE'}], world: 'world'};
|
||||
shouldCompileTo(string, hash, 'goodbye! Goodbye! GOODBYE! cruel world!',
|
||||
'each with array argument iterates over the contents when not empty');
|
||||
shouldCompileTo(string, {goodbyes: [], world: 'world'}, 'cruel world!',
|
||||
'each with array argument ignores the contents when empty');
|
||||
});
|
||||
|
||||
it('each without data', function() {
|
||||
var string = '{{#each goodbyes}}{{text}}! {{/each}}cruel {{world}}!';
|
||||
var hash = {goodbyes: [{text: 'goodbye'}, {text: 'Goodbye'}, {text: 'GOODBYE'}], world: 'world'};
|
||||
shouldCompileTo(string, [hash,,,, false], 'goodbye! Goodbye! GOODBYE! cruel world!');
|
||||
|
||||
hash = {goodbyes: 'cruel', world: 'world'};
|
||||
shouldCompileTo('{{#each .}}{{.}}{{/each}}', [hash,,,, false], 'cruelworld');
|
||||
});
|
||||
|
||||
it('each without context', function() {
|
||||
var string = '{{#each goodbyes}}{{text}}! {{/each}}cruel {{world}}!';
|
||||
shouldCompileTo(string, [,,,, ], 'cruel !');
|
||||
});
|
||||
|
||||
it('each with an object and @key', function() {
|
||||
var string = '{{#each goodbyes}}{{@key}}. {{text}}! {{/each}}cruel {{world}}!';
|
||||
|
||||
function Clazz() {
|
||||
this['<b>#1</b>'] = {text: 'goodbye'};
|
||||
this[2] = {text: 'GOODBYE'};
|
||||
}
|
||||
Clazz.prototype.foo = 'fail';
|
||||
var hash = {goodbyes: new Clazz(), world: 'world'};
|
||||
|
||||
// Object property iteration order is undefined according to ECMA spec,
|
||||
// so we need to check both possible orders
|
||||
// @see http://stackoverflow.com/questions/280713/elements-order-in-a-for-in-loop
|
||||
var actual = compileWithPartials(string, hash);
|
||||
var expected1 = '<b>#1</b>. goodbye! 2. GOODBYE! cruel world!';
|
||||
var expected2 = '2. GOODBYE! <b>#1</b>. goodbye! cruel world!';
|
||||
|
||||
equals(actual === expected1 || actual === expected2, true, 'each with object argument iterates over the contents when not empty');
|
||||
shouldCompileTo(string, {goodbyes: {}, world: 'world'}, 'cruel world!');
|
||||
});
|
||||
|
||||
it('each with @index', function() {
|
||||
var string = '{{#each goodbyes}}{{@index}}. {{text}}! {{/each}}cruel {{world}}!';
|
||||
var hash = {goodbyes: [{text: 'goodbye'}, {text: 'Goodbye'}, {text: 'GOODBYE'}], world: 'world'};
|
||||
|
||||
var template = CompilerContext.compile(string);
|
||||
var result = template(hash);
|
||||
|
||||
equal(result, '0. goodbye! 1. Goodbye! 2. GOODBYE! cruel world!', 'The @index variable is used');
|
||||
});
|
||||
|
||||
it('each with nested @index', function() {
|
||||
var string = '{{#each goodbyes}}{{@index}}. {{text}}! {{#each ../goodbyes}}{{@index}} {{/each}}After {{@index}} {{/each}}{{@index}}cruel {{world}}!';
|
||||
var hash = {goodbyes: [{text: 'goodbye'}, {text: 'Goodbye'}, {text: 'GOODBYE'}], world: 'world'};
|
||||
|
||||
var template = CompilerContext.compile(string);
|
||||
var result = template(hash);
|
||||
|
||||
equal(result, '0. goodbye! 0 1 2 After 0 1. Goodbye! 0 1 2 After 1 2. GOODBYE! 0 1 2 After 2 cruel world!', 'The @index variable is used');
|
||||
});
|
||||
|
||||
it('each with block params', function() {
|
||||
var string = '{{#each goodbyes as |value index|}}{{index}}. {{value.text}}! {{#each ../goodbyes as |childValue childIndex|}} {{index}} {{childIndex}}{{/each}} After {{index}} {{/each}}{{index}}cruel {{world}}!';
|
||||
var hash = {goodbyes: [{text: 'goodbye'}, {text: 'Goodbye'}], world: 'world'};
|
||||
|
||||
var template = CompilerContext.compile(string);
|
||||
var result = template(hash);
|
||||
|
||||
equal(result, '0. goodbye! 0 0 0 1 After 0 1. Goodbye! 1 0 1 1 After 1 cruel world!');
|
||||
});
|
||||
|
||||
it('each object with @index', function() {
|
||||
var string = '{{#each goodbyes}}{{@index}}. {{text}}! {{/each}}cruel {{world}}!';
|
||||
var hash = {goodbyes: {'a': {text: 'goodbye'}, b: {text: 'Goodbye'}, c: {text: 'GOODBYE'}}, world: 'world'};
|
||||
|
||||
var template = CompilerContext.compile(string);
|
||||
var result = template(hash);
|
||||
|
||||
equal(result, '0. goodbye! 1. Goodbye! 2. GOODBYE! cruel world!', 'The @index variable is used');
|
||||
});
|
||||
|
||||
|
||||
it('each with @first', function() {
|
||||
var string = '{{#each goodbyes}}{{#if @first}}{{text}}! {{/if}}{{/each}}cruel {{world}}!';
|
||||
var hash = {goodbyes: [{text: 'goodbye'}, {text: 'Goodbye'}, {text: 'GOODBYE'}], world: 'world'};
|
||||
|
||||
var template = CompilerContext.compile(string);
|
||||
var result = template(hash);
|
||||
|
||||
equal(result, 'goodbye! cruel world!', 'The @first variable is used');
|
||||
});
|
||||
|
||||
it('each with nested @first', function() {
|
||||
var string = '{{#each goodbyes}}({{#if @first}}{{text}}! {{/if}}{{#each ../goodbyes}}{{#if @first}}{{text}}!{{/if}}{{/each}}{{#if @first}} {{text}}!{{/if}}) {{/each}}cruel {{world}}!';
|
||||
var hash = {goodbyes: [{text: 'goodbye'}, {text: 'Goodbye'}, {text: 'GOODBYE'}], world: 'world'};
|
||||
|
||||
var template = CompilerContext.compile(string);
|
||||
var result = template(hash);
|
||||
|
||||
equal(result, '(goodbye! goodbye! goodbye!) (goodbye!) (goodbye!) cruel world!', 'The @first variable is used');
|
||||
});
|
||||
|
||||
it('each object with @first', function() {
|
||||
var string = '{{#each goodbyes}}{{#if @first}}{{text}}! {{/if}}{{/each}}cruel {{world}}!';
|
||||
var hash = {goodbyes: {'foo': {text: 'goodbye'}, bar: {text: 'Goodbye'}}, world: 'world'};
|
||||
|
||||
var template = CompilerContext.compile(string);
|
||||
var result = template(hash);
|
||||
|
||||
equal(result, 'goodbye! cruel world!', 'The @first variable is used');
|
||||
});
|
||||
|
||||
it('each with @last', function() {
|
||||
var string = '{{#each goodbyes}}{{#if @last}}{{text}}! {{/if}}{{/each}}cruel {{world}}!';
|
||||
var hash = {goodbyes: [{text: 'goodbye'}, {text: 'Goodbye'}, {text: 'GOODBYE'}], world: 'world'};
|
||||
|
||||
var template = CompilerContext.compile(string);
|
||||
var result = template(hash);
|
||||
|
||||
equal(result, 'GOODBYE! cruel world!', 'The @last variable is used');
|
||||
});
|
||||
|
||||
it('each object with @last', function() {
|
||||
var string = '{{#each goodbyes}}{{#if @last}}{{text}}! {{/if}}{{/each}}cruel {{world}}!';
|
||||
var hash = {goodbyes: {'foo': {text: 'goodbye'}, bar: {text: 'Goodbye'}}, world: 'world'};
|
||||
|
||||
var template = CompilerContext.compile(string);
|
||||
var result = template(hash);
|
||||
|
||||
equal(result, 'Goodbye! cruel world!', 'The @last variable is used');
|
||||
});
|
||||
|
||||
it('each with nested @last', function() {
|
||||
var string = '{{#each goodbyes}}({{#if @last}}{{text}}! {{/if}}{{#each ../goodbyes}}{{#if @last}}{{text}}!{{/if}}{{/each}}{{#if @last}} {{text}}!{{/if}}) {{/each}}cruel {{world}}!';
|
||||
var hash = {goodbyes: [{text: 'goodbye'}, {text: 'Goodbye'}, {text: 'GOODBYE'}], world: 'world'};
|
||||
|
||||
var template = CompilerContext.compile(string);
|
||||
var result = template(hash);
|
||||
|
||||
equal(result, '(GOODBYE!) (GOODBYE!) (GOODBYE! GOODBYE! GOODBYE!) cruel world!', 'The @last variable is used');
|
||||
});
|
||||
|
||||
it('each with function argument', function() {
|
||||
var string = '{{#each goodbyes}}{{text}}! {{/each}}cruel {{world}}!';
|
||||
var hash = {goodbyes: function() { return [{text: 'goodbye'}, {text: 'Goodbye'}, {text: 'GOODBYE'}]; }, world: 'world'};
|
||||
shouldCompileTo(string, hash, 'goodbye! Goodbye! GOODBYE! cruel world!',
|
||||
'each with array function argument iterates over the contents when not empty');
|
||||
shouldCompileTo(string, {goodbyes: [], world: 'world'}, 'cruel world!',
|
||||
'each with array function argument ignores the contents when empty');
|
||||
});
|
||||
|
||||
it('each object when last key is an empty string', function() {
|
||||
var string = '{{#each goodbyes}}{{@index}}. {{text}}! {{/each}}cruel {{world}}!';
|
||||
var hash = {goodbyes: {'a': {text: 'goodbye'}, b: {text: 'Goodbye'}, '': {text: 'GOODBYE'}}, world: 'world'};
|
||||
|
||||
var template = CompilerContext.compile(string);
|
||||
var result = template(hash);
|
||||
|
||||
equal(result, '0. goodbye! 1. Goodbye! 2. GOODBYE! cruel world!', 'Empty string key is not skipped');
|
||||
});
|
||||
|
||||
it('data passed to helpers', function() {
|
||||
var string = '{{#each letters}}{{this}}{{detectDataInsideEach}}{{/each}}';
|
||||
var hash = {letters: ['a', 'b', 'c']};
|
||||
|
||||
var template = CompilerContext.compile(string);
|
||||
var result = template(hash, {
|
||||
data: {
|
||||
exclaim: '!'
|
||||
}
|
||||
});
|
||||
equal(result, 'a!b!c!', 'should output data');
|
||||
});
|
||||
|
||||
it('each on implicit context', function() {
|
||||
shouldThrow(function() {
|
||||
var template = CompilerContext.compile('{{#each}}{{text}}! {{/each}}cruel world!');
|
||||
template({});
|
||||
}, handlebarsEnv.Exception, 'Must pass iterator to #each');
|
||||
});
|
||||
});
|
||||
|
||||
describe('#log', function() {
|
||||
/* eslint-disable no-console */
|
||||
if (typeof console === 'undefined') {
|
||||
return;
|
||||
}
|
||||
|
||||
var $log,
|
||||
$info,
|
||||
$error;
|
||||
beforeEach(function() {
|
||||
$log = console.log;
|
||||
$info = console.info;
|
||||
$error = console.error;
|
||||
});
|
||||
afterEach(function() {
|
||||
console.log = $log;
|
||||
console.info = $info;
|
||||
console.error = $error;
|
||||
});
|
||||
|
||||
it('should call logger at default level', function() {
|
||||
var string = '{{log blah}}';
|
||||
var hash = { blah: 'whee' };
|
||||
|
||||
var levelArg, logArg;
|
||||
handlebarsEnv.log = function(level, arg) {
|
||||
levelArg = level;
|
||||
logArg = arg;
|
||||
};
|
||||
|
||||
shouldCompileTo(string, hash, '', 'log should not display');
|
||||
equals(1, levelArg, 'should call log with 1');
|
||||
equals('whee', logArg, "should call log with 'whee'");
|
||||
});
|
||||
it('should call logger at data level', function() {
|
||||
var string = '{{log blah}}';
|
||||
var hash = { blah: 'whee' };
|
||||
|
||||
var levelArg, logArg;
|
||||
handlebarsEnv.log = function(level, arg) {
|
||||
levelArg = level;
|
||||
logArg = arg;
|
||||
};
|
||||
|
||||
shouldCompileTo(string, [hash,,,, {level: '03'}], '');
|
||||
equals('03', levelArg);
|
||||
equals('whee', logArg);
|
||||
});
|
||||
it('should output to info', function() {
|
||||
var string = '{{log blah}}';
|
||||
var hash = { blah: 'whee' };
|
||||
var called;
|
||||
|
||||
console.info = function(info) {
|
||||
equals('whee', info);
|
||||
called = true;
|
||||
};
|
||||
console.log = function(log) {
|
||||
equals('whee', log);
|
||||
called = true;
|
||||
};
|
||||
|
||||
shouldCompileTo(string, hash, '');
|
||||
equals(true, called);
|
||||
});
|
||||
it('should log at data level', function() {
|
||||
var string = '{{log blah}}';
|
||||
var hash = { blah: 'whee' };
|
||||
var called;
|
||||
|
||||
console.error = function(log) {
|
||||
equals('whee', log);
|
||||
called = true;
|
||||
};
|
||||
|
||||
shouldCompileTo(string, [hash,,,, {level: '03'}], '');
|
||||
equals(true, called);
|
||||
});
|
||||
it('should handle missing logger', function() {
|
||||
var string = '{{log blah}}';
|
||||
var hash = { blah: 'whee' },
|
||||
called = false;
|
||||
|
||||
console.error = undefined;
|
||||
console.log = function(log) {
|
||||
equals('whee', log);
|
||||
called = true;
|
||||
};
|
||||
|
||||
shouldCompileTo(string, [hash,,,, {level: '03'}], '');
|
||||
equals(true, called);
|
||||
});
|
||||
|
||||
it('should handle string log levels', function() {
|
||||
var string = '{{log blah}}';
|
||||
var hash = { blah: 'whee' };
|
||||
var called;
|
||||
|
||||
console.error = function(log) {
|
||||
equals('whee', log);
|
||||
called = true;
|
||||
};
|
||||
|
||||
shouldCompileTo(string, [hash,,,, {level: 'error'}], '');
|
||||
equals(true, called);
|
||||
|
||||
called = false;
|
||||
|
||||
shouldCompileTo(string, [hash,,,, {level: 'ERROR'}], '');
|
||||
equals(true, called);
|
||||
});
|
||||
it('should handle hash log levels', function() {
|
||||
var string = '{{log blah level="error"}}';
|
||||
var hash = { blah: 'whee' };
|
||||
var called;
|
||||
|
||||
console.error = function(log) {
|
||||
equals('whee', log);
|
||||
called = true;
|
||||
};
|
||||
|
||||
shouldCompileTo(string, hash, '');
|
||||
equals(true, called);
|
||||
});
|
||||
it('should handle hash log levels', function() {
|
||||
var string = '{{log blah level="debug"}}';
|
||||
var hash = { blah: 'whee' };
|
||||
var called = false;
|
||||
|
||||
console.info = console.log = console.error = console.debug = function(log) {
|
||||
equals('whee', log);
|
||||
called = true;
|
||||
};
|
||||
|
||||
shouldCompileTo(string, hash, '');
|
||||
equals(false, called);
|
||||
});
|
||||
it('should pass multiple log arguments', function() {
|
||||
var string = '{{log blah "foo" 1}}';
|
||||
var hash = { blah: 'whee' };
|
||||
var called;
|
||||
|
||||
console.info = console.log = function(log1, log2, log3) {
|
||||
equals('whee', log1);
|
||||
equals('foo', log2);
|
||||
equals(1, log3);
|
||||
called = true;
|
||||
};
|
||||
|
||||
shouldCompileTo(string, hash, '');
|
||||
equals(true, called);
|
||||
});
|
||||
/* eslint-enable no-console */
|
||||
});
|
||||
|
||||
|
||||
describe('#lookup', function() {
|
||||
it('should lookup arbitrary content', function() {
|
||||
var string = '{{#each goodbyes}}{{lookup ../data .}}{{/each}}',
|
||||
hash = {goodbyes: [0, 1], data: ['foo', 'bar']};
|
||||
|
||||
var template = CompilerContext.compile(string);
|
||||
var result = template(hash);
|
||||
|
||||
equal(result, 'foobar');
|
||||
});
|
||||
it('should not fail on undefined value', function() {
|
||||
var string = '{{#each goodbyes}}{{lookup ../bar .}}{{/each}}',
|
||||
hash = {goodbyes: [0, 1], data: ['foo', 'bar']};
|
||||
|
||||
var template = CompilerContext.compile(string);
|
||||
var result = template(hash);
|
||||
|
||||
equal(result, '');
|
||||
});
|
||||
});
|
||||
});
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user