Compare commits

...

798 Commits

Author SHA1 Message Date
Nils Knappmeier b25e5c00d3 .gitignore coverage and idea directories 2019-04-11 17:19:03 +02:00
kpdecker 0c6829f8af v1.3.0 2014-01-01 22:10:31 -06:00
kpdecker 2b9f3c195f Update release notes 2014-01-01 22:09:50 -06:00
kpdecker f445b96cb7 Merge branch 'master' of github.com:wycats/handlebars.js 2014-01-01 19:42:16 -06:00
kpdecker e0d0ad5027 Merge branch 'compiler-options' of github.com:blakeembrey/handlebars.js into blakeembrey-compiler-options
Conflicts:
	lib/handlebars/compiler/javascript-compiler.js
2014-01-01 19:38:31 -06:00
Kevin Decker 3f71fde155 Merge pull request #696 from nateirwin/master
Fix for reserved keyword "default"
2014-01-01 17:17:19 -08:00
kpdecker 6e4e1f8404 Include line info in compiler thrown exceptions
Fixes #636
2014-01-01 19:14:51 -06:00
kpdecker 3c85720137 Remove duplication from generated subexpressions 2014-01-01 18:59:21 -06:00
kpdecker 4713abc8f1 Add subexpression benchmark 2014-01-01 18:15:39 -06:00
kpdecker e7e94dc697 Whitespace cleanup 2013-12-31 21:20:59 -06:00
kpdecker c1a93d33e7 Use literal for data lookup 2013-12-31 21:19:00 -06:00
kpdecker cd885bf855 Add stack handling sanity checks 2013-12-31 21:18:48 -06:00
kpdecker ddfe457abf Fix stack id "leak" on replaceStack 2013-12-31 21:17:50 -06:00
kpdecker f4d337d252 Fix incorrect stack pop when replacing literals 2013-12-31 21:17:16 -06:00
kpdecker cbfec5b9e9 Set laxbreak jshint flag 2013-12-31 20:25:46 -06:00
kpdecker af3358d195 Fix multiple hash handling in subexpressions 2013-12-31 16:10:51 -06:00
Nate Irwin 641358a905 Fix for reserved keyword "default" 2013-12-31 15:09:41 -07:00
kpdecker f2df220a1f Add location tracking to sexpr 2013-12-31 13:31:47 -06:00
Kevin Decker a2ca31bb19 Merge pull request #690 from machty/subsexpr
Added support for subexpressions
2013-12-30 16:09:41 -08:00
machty b09333db79 Added support for subexpressions
Handlebars now supports subexpressions.

    {{foo (bar 3)}}

Subexpressions are always evaluated as helpers; if
`3` were omitted from the above example, `bar`
would be invoked as a param-less helper, even
though a top-levell `{{bar}}` would be considered
ambiguous.

The return value of a subexpression helper is
passed in as a parameter of a parent subexpression
helper, even in string params mode. Their type,
as listed in `options.types` or `options.hashTypes`
in string params mode, is "sexpr".

The main conceptual change in the Handlebars code
is that there is a new AST.SexprNode that manages
the params/hash passed into a mustache, as well
as the logic that governs whether that mustache
is a helper invocation, property lookup, etc.
MustacheNode, which used to manage this stuff,
still exists, but only manages things like
white-space stripping and whether the mustache
is escaped or not. So, a MustacheNode _has_
a SexprNode.

The introduction of subexpressions is fully
backwards compatible, but a few things needed
to change about the compiled output of a template
in order to support subexpressions. The main one
is that the options hash is no longer stashed in
a local `options` var before being passed to
either the helper being invoked or the
`helperMissing` fallback. Rather, the options
object is inlined in these cases. This does
mean compiled template sizes will be a little
bit larger, even those that don't make use of
subexpressions, but shouldn't have any noticeable
impact on performance when actually rendering
templates as only one of these inlined objects
will actually get evaluated.
2013-12-30 19:07:04 -05:00
Kevin Decker ac98e7b177 Merge pull request #694 from blakeembrey/compile-env
Make the environment reusable
2013-12-29 15:23:47 -08:00
kpdecker 14d1d4270f Fix ProgramNode parameter handling under IE 2013-12-29 17:16:59 -06:00
Blake Embrey 150e55aa00 Pull options out from param setup to allow easier extension. 2013-12-28 22:31:45 -05:00
Blake Embrey 2f0c96b618 Make the environment reusable. 2013-12-28 21:35:40 -05:00
Yehuda Katz 6c2137a420 Merge pull request #692 from fivetanley/line-numbers
add line numbers to nodes when parsing
2013-12-28 17:12:25 -08:00
Stanley Stuart e1878050b5 add line numbers to nodes when parsing
closes #691
2013-12-28 18:55:36 -06:00
kpdecker 1c0614bd88 v1.2.1 2013-12-26 16:25:42 -06:00
kpdecker ba7a37a3b8 Update release notes 2013-12-26 16:25:17 -06:00
Kevin Decker 77fd7dcc1d Merge pull request #686 from wycats/amd-tests
Falsy AMD module names in version 1.2.0
2013-12-26 13:58:04 -08:00
kpdecker 7311ab0b3f Use quotes for reserved token name
Fixes test exec under IE
2013-12-26 15:39:11 -06:00
kpdecker 4160ff3ee2 Revert unused flag addition 2013-12-26 15:26:12 -06:00
kpdecker 7151d15abb Add AMD tests to sauce tests 2013-12-26 15:20:37 -06:00
kpdecker 19f443f68e Use latest jshint 2013-12-26 15:12:10 -06:00
Kevin Decker 9f1b3bcea0 Merge pull request #685 from blakeembrey/dombars
Add dombars to the projects list
2013-12-25 10:13:13 -08:00
Kevin Decker 2dae244185 Merge pull request #684 from blakeembrey/javascript-variable
Allow any number of trailing characters for valid JavaScript variable
2013-12-25 10:12:11 -08:00
Blake Embrey c62ce32cc9 Add dombars to the projects list. 2013-12-24 16:21:28 -05:00
Blake Embrey f3a90812a6 Allow any number of trailing characters. 2013-12-24 16:14:57 -05:00
kpdecker 096b8ccd2e v1.2.0 2013-12-23 21:39:46 -06:00
kpdecker 923993dfb1 Update release notes 2013-12-23 21:39:23 -06:00
kpdecker fcf4e4ef0e Update release documentation 2013-12-23 21:30:34 -06:00
kpdecker ae1795f82c Update sauce tests for supported versions 2013-12-23 21:24:11 -06:00
kpdecker 2666553383 Cleanup jshint globals a bit 2013-12-23 21:16:25 -06:00
kpdecker 15af03c8bf Remove unused var 2013-12-23 21:16:14 -06:00
kpdecker 97c7d65931 Do not minimize multiple times 2013-12-23 21:16:07 -06:00
kpdecker bbc7c7d080 Expose compilerInfo and checkRevision as APIs
Fixes #656
2013-12-23 21:00:59 -06:00
kpdecker 1a751b2f6e Add JavascriptCompiler public API tests 2013-12-23 20:59:16 -06:00
kpdecker abe9c82e75 Use env.VM to lookup runtime methods
Allows for overrides by 3rd parties

Fixes #679
2013-12-23 19:53:11 -06:00
kpdecker 956ac95e7a Add browserify runtime export
Fixes #668
2013-12-23 19:51:10 -06:00
kpdecker 40e1837b14 Merge branch 'fix-escapes' of github.com:dmarcotte/handlebars.js into dmarcotte-fix-escapes
Conflicts:
	spec/tokenizer.js
2013-12-23 19:22:30 -06:00
kpdecker ddea5be2a4 Add AST test coverage for more complex paths 2013-12-23 19:02:45 -06:00
kpdecker b4968bb8d4 Fix handling of boolean escape in MustacheNode
Fixes issue with Ember compatibility due to direct instantiation of MustacheNode.
2013-12-23 19:02:24 -06:00
Kevin Decker ca8e34c94d Merge pull request #641 from wycats/ember-testing
Document ember testing process
2013-12-23 16:13:22 -08:00
kpdecker 4a94340f42 Add docs on testing ember integration 2013-12-23 18:12:14 -06:00
kpdecker 7d69becd2e Add notes on bower install
Fixes #655
2013-12-23 15:50:07 -06:00
kpdecker 1a9b5d4ac6 Add test case to cover #676
Fixes #676
2013-12-23 15:42:38 -06:00
kpdecker bdc0e6507e Provide default sort for builds link 2013-12-23 15:34:40 -06:00
kpdecker 93fb25c702 Handle empty responses from partials
Fixes #675
2013-12-23 15:33:33 -06:00
kpdecker 23d41e5617 Use watch atBegin command to handle failing tests 2013-12-23 14:26:56 -06:00
kpdecker d62620a282 Implement grunt dev task 2013-12-23 13:13:25 -06:00
Kevin Decker 62fdd541f5 Merge pull request #681 from wycats/sauce-tests
Adds in-browser testing and Saucelabs CI
2013-12-23 11:09:57 -08:00
kpdecker 7f53c917d9 Skip leak check under FF 2013-12-23 12:32:48 -06:00
kpdecker 015eba6687 Hide success messages when running in sauce
This should help debug the firefox issues that only occur when executing against sauce.
2013-12-23 11:57:11 -06:00
kpdecker 5b8b54b774 Add travis build caching 2013-12-23 03:20:13 -06:00
kpdecker efcedf2f24 Optimize travis build with publish option 2013-12-23 03:12:03 -06:00
kpdecker 512daa3189 Create matrix target for 0.10 only 2013-12-23 02:58:02 -06:00
kpdecker ad3727b3fc (Attempt to) Limit sauce tests to one travis build 2013-12-23 02:52:46 -06:00
kpdecker e2a81fdd60 Add selenium badge 2013-12-23 02:46:35 -06:00
kpdecker fe7bc0d4bd Add sauce labs testing support 2013-12-23 02:32:46 -06:00
kpdecker 9ad8b86ece Concat tests together for inbrowser exec 2013-12-23 02:19:09 -06:00
kpdecker 96b42dedea Fix tokenizer tests under IE 2013-12-23 02:19:09 -06:00
kpdecker 4942324250 Move away from should asserts to internal
This is needed as neither Sinon nor Chai support in-browser testing under IE.
2013-12-23 02:19:09 -06:00
kpdecker 0cf5657c43 Use shouldThrow helper for test asserts 2013-12-23 02:19:09 -06:00
kpdecker 3daef9d308 Use charAt rather than string index
Older versions of IE do not support [] access to string contents so charAt must be used.

Fixes #677
2013-12-23 02:18:57 -06:00
kpdecker 7f0ded4926 Relax packager dependency 2013-12-22 18:50:18 -06:00
kpdecker 1041c1d533 Add tests for object @index and @first handling 2013-12-22 18:46:13 -06:00
kpdecker f06a9dc1f0 Use strict comparison for first 2013-12-22 18:45:57 -06:00
kpdecker 45ab2bb218 Merge branch 'patch-1' of github.com:cgp/handlebars.js into cgp-patch-1 2013-12-22 18:27:10 -06:00
kpdecker f17cb3ecac Run jshint on output
Allows us to execute jshint in non-forced mode.
2013-12-01 15:40:08 -06:00
kpdecker 86974b2660 jshint 2013-12-01 14:59:44 -06:00
kpdecker 57b4ef08b6 Use module packager for all transpile tasks 2013-12-01 14:59:38 -06:00
kpdecker cc16bc47dd Add missing semicolon 2013-12-01 11:37:32 -06:00
kpdecker 078c1eecba Export AST as an object rather than module
The parser expects the AST object to be mutable, which modules are not.
2013-12-01 11:37:25 -06:00
kpdecker eb53f2e844 Allow extend to work with non-prototyped objects
ES6 modules do not extend the Object prototype so this blows up under the latest version of the transpiler.
2013-12-01 11:36:28 -06:00
Chris Pall e8954b3488 Update base.js
Give users the index for properties. When I am rendering a radio button I use key-value pairs, but I can't use either as unique identifiers because they likely contain invalid identifier characters. I added "first" as an index as well, but have no particular use case for first. Since there is no way to detect "last", I didn't add that property.
2013-11-22 00:41:25 -05:00
Kevin Decker 20b9c3829e Merge pull request #653 from k-j-kleist/patch-1
Update README.markdown
2013-11-20 09:35:53 -08:00
Kevin Decker 709153f7f5 Merge pull request #650 from thomasboyt/patch-1
Handlebars is MIT-licensed
2013-11-20 09:34:25 -08:00
Kevin Decker d5cc3fbcf1 Merge pull request #657 from chrishoage/master
Fixes issue where cli compiles non handlebars templates
2013-11-20 09:32:48 -08:00
choage d3cff5c1cd Fixes issue where cli compiles non handlebars templates 2013-11-17 16:35:01 -08:00
Karl Johan Kleist 83106e7a5e Update README.markdown
Added "Ghost" (Just a blogging platform) to "Handlebars in the Wild".
2013-11-12 10:41:56 +01:00
Thomas Boyt 525e58d318 Handlebars is MIT-licensed
The package.json list Handlebars as having a BSD license, but the readme says it's MIT licensed
2013-11-08 11:48:31 -05:00
Daniel Marcotte 9d353bd3dd Fix "\\{{" immediately following "\{{"
Escaped-escape mustaches ("\\{{") immediately following escaped
mustaches ("\{{") were being handled incorrectly.

Fix the lookahead to make sure yytext still contains the appropriate
slashes when we pop out of <emu> so they can be handled consistently
by the initial state.
2013-11-06 08:20:49 -08:00
kpdecker 47d13cb23c v1.1.2 2013-11-05 18:10:06 -06:00
kpdecker 7c13753bb5 Update release notes 2013-11-05 18:09:26 -06:00
kpdecker affbcbb79e Unify isArray/isFunction/toString implementations
Restores Array.isArray polyfill for all use cases.

Fixes #645
2013-11-05 18:07:33 -06:00
kpdecker 96a45a4a96 Add simple binary utility tests 2013-11-05 18:02:25 -06:00
kpdecker 6f6eb89bd8 Use handlebars runtime from precompiled amd files
Fixes #644
2013-11-05 18:02:05 -06:00
kpdecker eea708a18d Fix empty string compilation 2013-11-04 22:11:53 -06:00
Stefan Penner 508bb2d55d local grunt-cli 2013-11-04 21:18:57 -05:00
kpdecker 09cdc19a21 v1.1.1 2013-11-04 10:48:59 -06:00
kpdecker 2cb61c9d5f Update release notes 2013-11-04 10:48:12 -06:00
kpdecker 986d6f4096 Fix CLI library loading
Fixes #642
2013-11-04 10:46:50 -06:00
kpdecker 1e20373428 Document release process
Fixes #546
Fixes #640
2013-11-04 00:26:03 -06:00
kpdecker 17ba258355 Fix release notes link 2013-11-03 23:43:45 -06:00
kpdecker c53e95414f Update bower information for components package 2013-11-03 22:20:46 -06:00
kpdecker 41e2b7658b Remove Gruntfile from npm package 2013-11-03 22:02:59 -06:00
kpdecker 79fe7afb65 Fix license output on combined artifacts 2013-11-03 22:01:12 -06:00
kpdecker 29e3858d21 v1.1.0 2013-11-03 21:25:46 -06:00
kpdecker b5969394f1 Update release notes 2013-11-03 20:57:39 -06:00
kpdecker 904daba10b Create cdnjs and components dist dirs 2013-11-03 19:03:49 -06:00
kpdecker 7cef79288a Fix the handlebars-source gem definition 2013-11-03 17:20:47 -06:00
kpdecker 6ab579bb2a Copy components definitions to the dist directory 2013-11-03 17:20:28 -06:00
kpdecker 60f8aad5c9 Fix publish task typo 2013-11-03 16:50:36 -06:00
kpdecker a2bb02638b Publish tag versions from travis build 2013-11-03 16:14:27 -06:00
kpdecker 479acfbe9e Fix typo 2013-11-03 16:04:12 -06:00
kpdecker 6b62a2e58e Log tag on jenkins build 2013-11-03 16:01:55 -06:00
kpdecker a6781265a7 Update version task for generator-release 2013-11-03 16:00:07 -06:00
kpdecker 23ff9829d5 Remove tag target 2013-11-03 15:49:39 -06:00
kpdecker c70e897f1c Move component definitions into sub directory 2013-11-03 15:49:05 -06:00
kpdecker ded0a1617f Protect context-lookups from undefined values
Fixes #166
Fixes #587
2013-11-03 10:58:37 -06:00
kpdecker 88fefc1521 Use grunt rather than explicit test runner for npm 2013-11-03 10:50:50 -06:00
Kevin Decker 4c056a91ba Merge pull request #637 from xtian/clean-parser
Remove generated parser.js as part of clean task.
2013-11-02 17:46:14 -07:00
Christian Wesselhoeft 9f138c007a Remove generated parser.js as part of clean task. 2013-10-28 17:35:48 -07:00
Kevin Decker 320c0a6fb5 Merge pull request #336 from wycats/whitespace-control
Unecessary Whitespace
2013-10-27 09:54:25 -07:00
kpdecker 31f7c25a8f Use ~ rather than () for whitespace control. 2013-10-14 22:50:50 -05:00
kpdecker c776f171ab Implement strip opcode 2013-10-14 22:50:50 -05:00
kpdecker 6b0b3fa2ca Defer content output
Allows for stripping of the content after the fact.
2013-10-14 22:50:50 -05:00
kpdecker 1de6cee1b2 Load strip flags from lex stream 2013-10-14 22:50:50 -05:00
kpdecker 151e6d49c0 Wrap AST nodes in strip opcodes if defined 2013-10-14 22:50:50 -05:00
kpdecker 4e4920cc78 Add strip token parsing to lexer 2013-10-14 22:50:50 -05:00
kpdecker d5544d8969 Fix whitespace 2013-10-14 22:50:50 -05:00
kpdecker 14ef86f9ca Use accept for all compiler traversal 2013-10-14 22:50:50 -05:00
kpdecker a1edab6ef5 Kill unused functions 2013-10-14 22:50:50 -05:00
kpdecker 5b483ff78d Define test cases 2013-10-14 22:50:50 -05:00
kpdecker 30384789d2 Lookahead control classes 2013-10-14 22:50:49 -05:00
kpdecker 4cf49732a2 Push Source helper method 2013-10-14 22:50:49 -05:00
kpdecker 0cc6e270f9 Pass open token to MustacheNode for flag parsing 2013-10-14 22:50:12 -05:00
kpdecker 06d94fed56 Fix argument not found error 2013-10-14 22:49:34 -05:00
kpdecker 6f94fc0a3b Reduce eval scope in test env 2013-10-14 22:49:23 -05:00
kpdecker 84049bf0c5 Add includeZero flag to if conditional
Allows for users who desire non-falsy handling of numbers to utilize if while maintaining the legacy if behavior.

Fixes #608
2013-10-14 21:38:19 -05:00
kpdecker 2cd0995f21 Merge branch 'laurie71-fix-issue-599' 2013-10-14 21:32:17 -05:00
kpdecker 4fda71364e Pass options hash to context function exec
Fixes #599
2013-10-14 21:30:58 -05:00
kpdecker 3702efee5b Merge branch 'fix-issue-599' of github.com:laurie71/handlebars.js into laurie71-fix-issue-599 2013-10-14 20:44:42 -05:00
kpdecker 9218f2e1ef Fix improper stash 2013-10-14 14:49:55 -05:00
kpdecker 97d5efd94a Remove prepublish command
This caused breakages with the npm install/link commands.
2013-10-14 14:38:40 -05:00
kpdecker 15e1eb36ca Fix case typo 2013-10-14 14:38:10 -05:00
kpdecker 64de5c6812 Improve packager error tracking 2013-10-14 12:15:33 -05:00
kpdecker 15500aef70 Access utils methods via modules
Allows for monkey patching (under ES5 systems). This somewhat mirrors
the proposed behavior in https://github.com/square/es6-module-transpiler/issues/37
but applies the behavior via manual code changes rather than compiler
support.
2013-10-14 11:50:18 -05:00
kpdecker 3f52109c1c Cleanup currently unused grunt configs 2013-10-14 11:42:45 -05:00
kpdecker 84c837869e Create release grunt task
Allows for tests to be run slightly faster when developing locally.
2013-10-14 11:15:27 -05:00
Kevin Decker dd777c9872 Merge pull request #557 from dmarcotte/fix-escapes
`\\{{foo}}` escaping only works in some situations
2013-10-13 09:23:50 -07:00
Daniel Marcotte 468fa8b6dd Fix "\\" escaping
Previously, "\\{{foo}}" would only result in the desired "\fooValue" if
it was at the beginning of the file or immediately after a close stache.
2013-10-13 09:03:02 -04:00
kpdecker e9350f2932 Update precompiler for default output
Fixes #563
2013-10-12 17:13:23 -05:00
kpdecker 0dbeaf973d Merge pull request #615 2013-10-12 16:44:42 -05:00
Dennis Kuczynski 3150ef15da Remove uncessary ? operator when returning boolean values for @first and @last 2013-10-12 16:43:46 -05:00
denniskuczynski e20591b49d Add @first and @last data variables to #each helper resolving Issue #483 2013-10-12 16:43:17 -05:00
Kevin Decker 583141de7c Merge pull request #628 from wycats/es6-modules
Convert code to ES6 modules
2013-10-12 14:22:10 -07:00
kpdecker 3f96319f10 Update compat notes for es6-modules 2013-10-12 16:21:52 -05:00
kpdecker 83b5e10d70 Install grunt prior to other actions 2013-10-12 16:10:14 -05:00
kpdecker 52dea1e70e Force rebuild on publish 2013-10-12 15:47:18 -05:00
kpdecker abf37ad739 Update package.json for backwards compatibility 2013-10-12 15:45:28 -05:00
kpdecker 6136e7f346 Do not emit configurations dir in npm 2013-10-12 15:41:58 -05:00
kpdecker 4d31df2f30 Update logger interface to expose prior API 2013-10-12 15:41:38 -05:00
kpdecker 95d5768e0e Expose printer and visitor objects in node 2013-10-12 15:41:15 -05:00
kpdecker 7ee0c10c10 Fix transpiler error in printer.js 2013-10-12 15:40:55 -05:00
kpdecker e1f7f00cd4 Fix dist-size metrics exec 2013-10-09 04:02:49 -07:00
kpdecker e5a4889b36 Fix metrics exec 2013-10-09 04:02:21 -07:00
kpdecker 9769045e04 Cleanup unused var warnings 2013-10-09 03:53:09 -07:00
kpdecker b47f2cfcf5 Additional logging on dist size task 2013-10-09 03:52:45 -07:00
kpdecker 096ac2d552 Record stack on failed travis build 2013-10-09 03:42:26 -07:00
kpdecker 4e4c016be5 Merge branch 'master' into es6-modules 2013-10-09 03:41:58 -07:00
kpdecker 2abe4d404d Generate single amd output file 2013-10-09 03:35:47 -07:00
kpdecker 3b0a3ca1b2 Fix test runners under node 2013-10-09 03:22:05 -07:00
kpdecker 4d7124f6bf Remove global Handlebars references 2013-10-09 03:21:21 -07:00
kpdecker b6c9f85d22 Use template env and compile methods 2013-10-09 03:20:22 -07:00
kpdecker 0966f5172b Defer load packager module
Second change required to work around multiple traceur loading issue.
2013-10-09 02:41:16 -07:00
kpdecker 855eae37d0 Create runtime specific module 2013-10-09 02:38:04 -07:00
kpdecker 820cbb006e Fix concurrent traceur exec error 2013-10-09 02:37:02 -07:00
kpdecker 63bb459ac6 Generate dist file using es6-module-packager 2013-10-09 02:20:52 -07:00
Kevin Decker a8cd3201b5 Merge pull request #622 from twokul/jshint-errors
fixing jshint errors
2013-10-06 12:32:42 -07:00
Alex Navasardyan 19f1a1f81d fixing jshint errors 2013-10-04 14:28:05 -04:00
Kevin Decker 782aae95d0 Merge pull request #620 from justinwalsh/master
Added the CoSchedule project to the readme
2013-10-04 08:34:16 -07:00
kpdecker 5fd0d4a790 Only init amazon SDK when we are publishing 2013-10-04 10:33:40 -05:00
Justin Walsh 5a306ac745 Added the CoSchedule project to the readme 2013-10-03 18:04:11 -05:00
kpdecker 386c20cc63 Unify build target behavior with prior behavior 2013-10-01 21:53:56 -05:00
kpdecker a3b22ac6bf Update jshint for modules 2013-10-01 21:44:08 -05:00
kpdecker 117357b74e Output amd to dist 2013-10-01 21:41:54 -05:00
kpdecker db0f03d1b5 Remove empty file 2013-10-01 21:41:18 -05:00
kpdecker fd09ff0e1b Expose current Handlebars namespace fields 2013-10-01 21:41:09 -05:00
kpdecker e676e43dc5 Use proper default vs. module import semantics 2013-10-01 21:19:49 -05:00
kpdecker e75839b185 Break safe string out into standalone module 2013-10-01 21:18:10 -05:00
kpdecker 6a23391a9a Break exception class out into a standalone module 2013-10-01 21:14:18 -05:00
kpdecker 80b748ad3e Fix global Handlebars references 2013-10-01 00:13:48 -05:00
kpdecker fbf8f134fa Generate parser as es6 module 2013-10-01 00:12:39 -05:00
kpdecker 27d9010974 Update logger for es6 modules 2013-09-30 23:52:44 -05:00
kpdecker 1173e5a502 Update grunt build target 2013-09-28 10:54:03 -05:00
kpdecker 1e8409efa6 Fix merge errors 2013-09-02 18:25:03 -05:00
kpdecker a2687fdf50 Fix cjs output and testing 2013-09-02 18:19:37 -05:00
kpdecker cb0c45b29f Merge branch 'master' into es6-modules
Conflicts:
	Gruntfile.js
	Rakefile
	dist/handlebars.js
	dist/handlebars.runtime.js
	lib/handlebars.js
	lib/handlebars/base.js
	lib/handlebars/runtime.js
	lib/handlebars/utils.js
	package.json
2013-09-02 18:19:18 -05:00
kpdecker 0fe78f379a Output scaled times for throughput 2013-09-02 16:26:57 -05:00
kpdecker 192887cedc Merge commit '87b5d4ee61605b026506e92c9e8873d867c5f150' into es6-modules
Conflicts:
	dist/handlebars.js
	dist/handlebars.runtime.js
	lib/handlebars/base.js
	lib/handlebars/utils.js
2013-09-02 16:19:28 -05:00
kpdecker 06e2a441db Move scaled output to benchwarmer 2013-08-31 12:51:37 -05:00
kpdecker 530f1903aa Merge branch 'else-if' into es6-modules
Conflicts:
	dist/handlebars.js
2013-08-30 18:33:37 -05:00
kpdecker 035449fe02 Add note regarding performance impact of @data 2013-08-29 23:25:32 -05:00
Laurie Harper 03ef3d4258 Test cases for #599 2013-08-28 12:51:54 -04:00
Kevin Decker 7ec25f364a Merge pull request #606 from elving/patch-1
Add Swag to the "Handlebars in the Wild" list.
2013-08-26 14:47:08 -07:00
kpdecker 28c4f4b9dd Output scaled throughput values to console 2013-08-25 19:13:51 -05:00
kpdecker 97fe2decc9 Track min and max time in benchwarmer 2013-08-25 19:12:31 -05:00
kpdecker 54cda3b3ae Handle missing revision error in publish 2013-08-25 19:12:18 -05:00
kpdecker dac055f01e Add git debugging to publish command 2013-08-25 18:55:21 -05:00
kpdecker 1b4b8ed1da Rename to publish latest 2013-08-25 16:12:07 -05:00
kpdecker 115db76f66 Additional logging on git command error messages 2013-08-25 16:11:05 -05:00
kpdecker a28be7c3c0 Default data to off for performance tests 2013-08-25 16:09:29 -05:00
kpdecker 84b852c137 Add data throughput test 2013-08-25 15:57:39 -05:00
kpdecker 2dac9c978a Add paths throughput test 2013-08-25 15:57:29 -05:00
kpdecker 1df1756c71 Add complex templates for eco and mustache 2013-08-25 15:38:17 -05:00
kpdecker 0d1b0c0493 Ensure proper output from tests 2013-08-25 15:29:32 -05:00
kpdecker 709d57b3c1 Add eco object test 2013-08-25 15:28:52 -05:00
kpdecker 496ebaa1a8 Remove unnecessary escapes 2013-08-25 14:59:11 -05:00
kpdecker 9689ebaa1c Add handlebars-only and grep metrics options 2013-08-25 14:58:31 -05:00
kpdecker 3c66068279 Add gzip estimation to dist sizes output 2013-08-25 14:31:20 -05:00
kpdecker bf99516e55 Run all grunt commands as one 2013-08-25 14:28:19 -05:00
kpdecker c03b2f4dfa Import keen lib 2013-08-25 14:17:14 -05:00
kpdecker b488af39f1 Throughput metric tracking 2013-08-25 14:16:12 -05:00
kpdecker ab64fec1a8 Collect size metrics 2013-08-25 14:14:47 -05:00
kpdecker 672aeda17f Metrics collection framework 2013-08-25 14:14:33 -05:00
kpdecker 74369f724c Track benchwarmer times 2013-08-25 14:13:40 -05:00
kpdecker 57a0ee3d59 Remove duplicated tests 2013-08-25 13:46:45 -05:00
kpdecker 86694fd122 Move templates into standalone lib 2013-08-25 13:25:27 -05:00
kpdecker df0734ae5e Move benchwarmer file into util directory 2013-08-25 12:59:08 -05:00
kpdecker e880bd464e Remove rake build assets 2013-08-25 12:57:25 -05:00
kpdecker 9e09fe1826 npm ignore grunt files 2013-08-25 12:51:44 -05:00
kpdecker b773a7608d Use grunt for travis exec 2013-08-25 12:51:34 -05:00
kpdecker da3788384b AWS publish tasks 2013-08-25 12:51:17 -05:00
kpdecker 5aacdadff8 Merge branch 'master' into grunt-build
Conflicts:
	package.json
2013-08-24 23:09:04 -05:00
kpdecker b7f0d2e1fa Merge branch 'bench-fixup' 2013-08-24 22:51:28 -05:00
kpdecker c207c4e048 Rebuild 2013-08-24 22:20:33 -05:00
kpdecker 623fdad59f Simplify falsy handling 2013-08-24 22:20:23 -05:00
kpdecker 6e6acaac0d Unify isFunction/isArray handling 2013-08-24 22:20:12 -05:00
kpdecker 4c02d3027c Optimize partial helper/partial merge handling 2013-08-24 19:52:22 -05:00
kpdecker a532d28d94 Allow custom helpers for benchmark templates 2013-08-24 16:06:09 -05:00
kpdecker 84420c1eb4 Code style 2013-08-24 16:05:51 -05:00
kpdecker f7b2bd8039 Better skipped test handling 2013-08-24 16:05:39 -05:00
kpdecker c40a9eacb2 Differentiate between failures and skipped tests 2013-08-24 16:04:22 -05:00
kpdecker a74aed15e9 Handle edge case in benchmark layouts 2013-08-24 16:03:55 -05:00
kpdecker 94ba2b95d0 Fix fastest test lookup 2013-08-24 16:03:28 -05:00
kpdecker b700d91a70 Fix benchmark dependencies 2013-08-24 16:03:06 -05:00
kpdecker e77c961d40 Add grunt bench command 2013-08-24 16:01:37 -05:00
kpdecker 7efa75684e Version update task 2013-08-24 13:28:39 -05:00
kpdecker 08989cfcdd Merge branch 'master' into grunt-build 2013-08-24 12:13:09 -05:00
kpdecker 7e16d6cb0d Rebuild 2013-08-24 12:11:26 -05:00
kpdecker 0af54b1142 Use extend rather than prototype for createFrame
Using prototype has a large performance impact for the common case of a
sparse set of private variable data points. Rather than incurring the
overhead of creating and walking the prototype tree for this, performing
an extend by copy.
2013-08-24 12:10:10 -05:00
kpdecker eb1cda6fdc jshint 2013-08-24 12:06:25 -05:00
kpdecker 94e5ab8593 Add additional @data tests 2013-08-24 12:05:43 -05:00
Elving Rodriguez 3b8a061200 Add Swag to the "Handlebars in the Wild" list. 2013-08-24 09:32:01 -07:00
kpdecker 2f1a454467 Move parser generation into grunt file 2013-08-17 12:41:04 -05:00
kpdecker b7c62d8cc5 Move test runner into grunt 2013-08-17 12:19:13 -05:00
kpdecker 6492fe8c23 Add jshint to grunt exec 2013-08-17 12:16:48 -05:00
kpdecker 3d77d172ec Move dist dir generation into Grunt 2013-08-17 11:57:50 -05:00
kpdecker 87b5d4ee61 Update npmignore for new files 2013-08-17 09:21:25 -05:00
kpdecker 182ba044bb Update release notes 2013-08-15 10:33:45 -05:00
kpdecker 0b92db7b0c Rebuild 2013-08-15 10:30:15 -05:00
kpdecker 534d0ebcc3 Add test case 2013-08-15 10:29:37 -05:00
kpdecker 564afab278 Merge branch 'master' of github.com:artiee/handlebars.js into artiee-master 2013-08-15 10:24:28 -05:00
Kevin Decker 18506e00c9 Merge pull request #535 from sorentwo/alternate-string-coercion
Use the String(string) form of string coercion
2013-08-15 08:19:54 -07:00
Parker Selbert d02c90c0fb Use the ('' + string) form of string coercion
Using string.toString() will throw errors in current versions of Safari
(6.0.5 currently) for some values. The error is a particularly cryptic
"Type Error: type error", which no indication as to the value that
caused the error. By using the '' + string form of coercion the error
doesn't seem to occur.

Depending on the browser used there is a sizable performance increase
in using the concatenation form of coercion. In instances where there
is not a performance improvement (i.e. Firefox), the speed difference
is entirely negligable. See: http://jsperf.com/convert-to-string-bj/3
2013-08-14 22:11:59 -05:00
Tuomas Palenius db47593e34 Fix #597. If-helper doesn't anymore consider 0 as falsy value. 2013-08-15 06:05:11 +03:00
Kevin Decker 860853ddb4 Update external url 2013-08-14 13:11:01 -04:00
Kevin Decker 827a09114e Merge pull request #594 from assemble/master
adds Assemble and handlebars-helpers projects to README
2013-08-14 10:10:27 -07:00
Jon Schlinkert 7c1ce99eba adds Assemble and handlebars-helpers projects to "Handlebars in the Wild" section of README. Bullets are now ordered alphabetically 2013-08-06 19:20:31 -04:00
kpdecker e62999f9ec Improve uglify compression 2013-07-30 11:08:58 -05:00
kpdecker 4f5c05ffe9 Simplify inverse only block case 2013-07-30 11:08:46 -05:00
kpdecker 9ca4f9c606 Remove unused var 2013-07-30 11:08:26 -05:00
kpdecker 03310df95e Add strip lex helper method 2013-07-29 21:37:25 -05:00
kpdecker 3bc7d7b998 ID lexer control class 2013-07-29 21:30:56 -05:00
Yehuda Katz 6f580c9beb Global Handlebars.compile uses its env
The basic strategy is that there will be a global Handlebars object for
the browser build, and that object will have a `compile` on it which
uses its environment in the compiler.

It will also be possible to glue things together manually by using the
AMD build and passing the environment to `compile` directly. Some of
these details are TBD.
2013-07-26 18:08:32 +00:00
Yehuda Katz d33408fcbd Move more testing mutations into the environment 2013-07-26 17:18:56 +00:00
Yehuda Katz 5f664dd78b Make the Handlebars environment into an object
The idea is that the environment wraps up the mutable stuff in
Handlebars (like the helpers) and that you could theoretically create a
new one at any time and pass it in to Handlebars.template.

Every test makes a new environment and uses it in template compilation.
2013-07-26 16:50:37 +00:00
kpdecker dcbc3a55a4 Prevent nonsensical root {{^}} 2013-07-24 23:50:12 -05:00
kpdecker 497515c02f Remove braces on single line lex statements
Resolves a parsing issue in the 0.4 branch of jison.
2013-07-24 23:43:56 -05:00
kpdecker 117239ca7c Use ebnf iteration/optional inMustache 2013-07-24 23:39:33 -05:00
kpdecker c373d1c6d4 Use ebnf optional for partial path 2013-07-24 23:38:45 -05:00
kpdecker df7a3fb8c0 Use ebnf iteration for hash production 2013-07-24 23:37:43 -05:00
kpdecker 199f3984c6 Use param production for hash value 2013-07-24 23:36:32 -05:00
kpdecker f9ed0d3085 Use arrow syntax for bnf 2013-07-24 21:57:30 -05:00
Kevin Decker 163b3f9e51 Merge pull request #558 from dmarcotte/update-readme
Update readme to reflect latest rake targets
2013-07-23 22:12:40 -07:00
Yehuda Katz f5c8484ea0 Further progress towards modularization.
At this point, I have only 2 fails in the Node build, but I'm doing a
bunch of manual stuff locally and still have a bunch of hacks.
2013-07-24 05:03:27 +00:00
Yehuda Katz da130f7745 Building an AMD file 2013-07-16 19:10:37 +00:00
Kevin Decker b5c4a33ab1 Merge pull request #574 from esneider/patch-1
Added missing quotes
2013-07-14 13:11:29 -07:00
Dario Sneidermanis a27e366f28 Added missing quotes 2013-07-12 23:14:50 -03:00
Yehuda Katz 12f8299eb2 More modularization 2013-07-02 18:57:28 +00:00
Kevin Decker fa881d4ce8 Merge pull request #555 from blessenm/precompiler-doc-fix
Added additional options to the doc.
2013-07-01 21:05:27 -07:00
Kevin Decker ad615d9061 Merge pull request #564 from 71104/master
jQuery plugin
2013-07-01 20:29:37 -07:00
Yehuda Katz 88ee4757e7 Initial work on ES6 modules 2013-07-01 13:59:58 -07:00
Alberto La Rocca ca5c3995fe Announcing jQuery plugin 2013-06-27 20:26:16 +02:00
Daniel Marcotte e4929df454 Update readme to reflect latest rake targets 2013-06-12 09:10:22 -07:00
Blessan Mathew 96718441ec Added additional options to the doc.
Added additional precompilation options to the readme doc.
2013-06-09 21:05:58 +05:30
Stefan Penner 8e2416dabb Merge pull request #554 from tricknotes/modify-keyword-type
Modify keywords type in package.json
2013-06-08 17:45:24 -07:00
Ryunosuke SATO 68e647a17e Modify keywords type in package.json
`keywords` should be an array.
- https://npmjs.org/doc/json.html#keywords
2013-06-08 23:19:55 +09:00
Kevin Decker 982cb2d188 Merge pull request #552 from blessenm/bom-precompilation-option
Added BOM removal flag.
2013-06-05 12:30:50 -07:00
Blessan Mathew 7538b8556c Added BOM removal flag.
Byte order mark is now removed from the beginning of the files when b
flag is used.
2013-06-04 23:06:40 +05:30
kpdecker 0f278e1dfc Rebuild 2013-06-03 23:27:38 -05:00
kpdecker c3ff573184 Break JavascriptCompiler into standalone file 2013-06-03 23:27:35 -05:00
Yehuda Katz f11aaa7b7c Update .travis.yml 2013-06-03 00:56:35 -06:00
kpdecker 6174c3b6f8 Remove unnecessary function 2013-06-02 23:36:34 -05:00
kpdecker 7def10195b Create publish_version rake task 2013-06-02 13:18:54 -05:00
kpdecker 0f4ef161d1 Pull out publish_s3 logic 2013-06-02 13:14:31 -05:00
kpdecker bf4ec5eef7 Move helper method with others to match convention 2013-06-02 13:14:07 -05:00
kpdecker 4a545cc686 Fix publish file mapping 2013-06-02 12:54:43 -05:00
kpdecker e2a8bdab3c Prevent name/dir conflict 2013-06-02 12:28:54 -05:00
kpdecker 5de919f2f7 Fix test reference 2013-06-02 12:26:03 -05:00
kpdecker cfd51f6579 Fix empty repo check 2013-06-02 12:25:07 -05:00
kpdecker 2022c94a30 Update release notes 2013-06-02 12:21:10 -05:00
kpdecker bb8bb048aa Pass args to the rake version task 2013-06-02 12:20:01 -05:00
kpdecker b3005f8f9f Merge dist build targets 2013-06-02 12:11:37 -05:00
kpdecker 8f24a2e384 Pull dist file lookup logic out of publish method 2013-06-02 11:30:22 -05:00
kpdecker e624fb50b7 Proper git commit command 2013-06-02 00:52:35 -05:00
kpdecker 80078861f2 Include minimized files in build 2013-06-02 00:41:33 -05:00
kpdecker 26fcebe0c7 Use short SHA name 2013-06-02 00:41:14 -05:00
kpdecker 5ce7232dc3 Fix merge error 2013-06-02 00:37:18 -05:00
kpdecker 9a334400f0 Merge branch 's3-builds-final' of github.com:fivetanley/handlebars.js
Conflicts:
	Gemfile
	Gemfile.lock
	dist/handlebars.js
	dist/handlebars.runtime.js
2013-06-02 00:29:20 -05:00
kpdecker bcc8d00e89 Remove incorrectly commited test file 2013-06-01 23:57:21 -05:00
kpdecker 9d4a94616a Add should npm declaration 2013-06-01 23:55:20 -05:00
kpdecker 7b1ad8aa38 Remove remaining respec references.
Fixes #544
2013-06-01 23:46:34 -05:00
kpdecker adda0569e0 Refactor qunit unit tests
Allows for testing node, browser, and precompiled modes in the node
tests. Also reorganizes the qunit spec file to provide better
organization.
2013-06-01 23:45:43 -05:00
kpdecker d13ae310d3 Convert parser and tokenizer tests to javascript 2013-06-01 16:25:45 -05:00
kpdecker 7f82f27c5f Add mocha bdd globals to jshint 2013-06-01 13:26:46 -04:00
kpdecker 720b133839 Merge branch 'master' of github.com:wycats/handlebars.js 2013-05-31 15:37:41 -04:00
Kevin Decker caded1d06b Merge pull request #537 from spikebrehm/support-all-the-modules
Support AMD and CommonJS out of the box
2013-05-31 12:37:16 -07:00
kpdecker 8275242960 Preserve license comment on min 2013-05-31 15:24:26 -04:00
Stanley Stuart 265d6249f7 publish passing master builds to s3 2013-05-31 15:08:34 -04:00
Kevin Decker e6b6ae8fdb Add version compat note 2013-05-31 14:29:13 -04:00
kpdecker 2a073e0993 1.0.12 2013-05-31 14:16:53 -04:00
kpdecker f63b226b76 Update to 1.0.0 final 2013-05-31 14:16:45 -04:00
kpdecker 16fd601a5c Merge global and passed helpers and partials 2013-05-31 13:12:06 -04:00
kpdecker ef062adcc2 Move external AST tests into qunit spec 2013-05-31 13:11:22 -04:00
kpdecker 2024209519 Move upgrade notes to release-notes 2013-05-30 15:57:15 -04:00
kpdecker d7b345b2da Allow execution of helpers on the context
Fixes #285
2013-05-30 15:47:30 -04:00
kpdecker 17659b9720 Ignore minimized files from git 2013-05-29 15:31:08 -04:00
kpdecker 58a0b4f17d Require matching braces in escaped expressions
Fixes #437
2013-05-29 15:30:50 -04:00
kpdecker 61f64e9f04 Add minify task to release target 2013-05-29 15:21:19 -04:00
kpdecker 66f6f94867 Update to latest uglify lib 2013-05-29 15:17:10 -04:00
kpdecker c540d7186a Allow function arguments to with and each
Via @mcdan
Fixes #239
2013-05-29 12:23:55 -04:00
kpdecker 293672432b improve error message on missing helper
Via @stefanpenner
Fixes #523
2013-05-29 10:57:48 -04:00
kpdecker da2aabe7bd Allow ID, STRING, or INTEGER for partial name
Fixes #519
2013-05-29 10:46:28 -04:00
kpdecker 3ddbc5237f Protect from object prototype modifications
Fixes #534
2013-05-28 17:03:07 -04:00
kpdecker 503e32208b Improve tracking of original path values 2013-05-27 19:47:17 -04:00
kpdecker 15186dc9dd Add supported environments statement
Fixes #344
2013-05-27 19:03:16 -04:00
kpdecker 2c8dd74997 Add unescaped tokenizer tests 2013-05-27 14:59:45 -05:00
kpdecker 3d51088d4d Fail if jison fails 2013-05-27 14:58:49 -05:00
kpdecker 822a8911ec Add support for complex ids in @data references 2013-05-27 14:40:52 -05:00
Spike Brehm efca3c8ef5 Support AMD and CommonJS out of the box 2013-05-27 11:22:25 -07:00
kpdecker 5f349913aa Update in the wild section
Fixes #246
2013-05-27 12:29:57 -05:00
kpdecker c1020a0130 Add unicode support for ID tokens
Fixes #433
Fixes #469
2013-05-27 11:56:28 -05:00
kpdecker bff71d79d3 Update release notes 2013-05-25 12:32:16 -05:00
kpdecker 4d95d58e15 Rebuild 2013-05-25 12:27:31 -05:00
Kevin Decker 47e0aa795a Merge pull request #526 from jamesgorrie/master
Fixing regex on partial names
2013-05-25 10:07:46 -07:00
kpdecker 6bf9c6fc13 Merge branch 'master' of github.com:wycats/handlebars.js 2013-05-22 08:40:28 -04:00
kpdecker 533be694b5 Use better variable name 2013-05-21 13:12:11 -05:00
kpdecker a97820f54b Update testing notes
Fixes #467
2013-05-21 13:03:41 -05:00
kpdecker f6b6361ad7 Fix typo 2013-05-21 13:03:01 -05:00
Kevin Decker 10669e0e36 Merge pull request #533 from blessenm/amd-precompilation-fix
Fix for the issue #517
2013-05-21 08:46:27 -07:00
Blessan Mathew 69bcdf36c7 Fix for issue #517.
Now when compiiled with amd option and a single input argument, we are
checking to see whether the input is a file or folder.
2013-05-21 20:50:46 +05:30
kpdecker 5ba17c9642 Merge branch 'master' of github.com:wycats/handlebars.js 2013-05-19 11:47:01 -05:00
kpdecker 7fb9750717 Update block helper docs in README
Fixes #231, #259
2013-05-19 11:45:20 -05:00
kpdecker c95d9d68ea Use context rather than arg in README examples 2013-05-19 11:30:26 -05:00
kpdecker 3aef7f604a Misc readme updates 2013-05-19 11:30:04 -05:00
James Gorrie f5fe6d0bf6 Allowed for points to be in partial path name 2013-05-17 16:58:34 +01:00
Kevin Decker 94cc41900b Merge pull request #515 from jjclark1982/master
Allow requiring ".handlebars" files within Node.js scripts
2013-05-14 21:10:14 -07:00
Jesse Clark d649b3581e Only run "require" tests when require() is available 2013-05-15 10:50:01 +10:00
Jesse Clark 229b82bef5 Add unit tests for require() handler 2013-05-15 10:38:45 +10:00
Jesse Clark 531be77138 Publish a Node.js require() handler for handlebars files 2013-05-14 22:26:20 +10:00
kpdecker 4cf0410b7c 1.0.11 2013-05-13 23:09:21 -05:00
kpdecker 2ec5a97f4a Update release notes 2013-05-13 23:08:43 -05:00
kpdecker ddc4d31861 Rev to rc4 2013-05-13 23:07:29 -05:00
kpdecker 85dd39427b Add regex escapes 2013-05-10 01:35:25 -05:00
kpdecker 8b65fa771e Update release notes 2013-05-10 01:24:11 -05:00
kpdecker fa6cec26bf Update client packager versions 2013-05-10 01:02:13 -05:00
kpdecker 436973f470 Version update script 2013-05-10 01:01:55 -05:00
Kevin Decker a2796784ab Merge pull request #505 from MikeMayer/master
Include a .nuspec package file to include handlebars.js on NuGet
2013-05-09 22:37:23 -07:00
Kevin Decker d355d1f6aa Fix links in release notes 2013-05-10 00:35:05 -05:00
Kevin Decker 71c5e36268 Merge pull request #489 from DevinClark/bower-update
Added a component.json file for Twitter's Bower package manager.
2013-05-09 22:29:27 -07:00
kpdecker 44ae572e2f Add release notes
Fixes #471
2013-05-10 00:14:55 -05:00
Kevin Decker 7cd9cdb0eb Merge pull request #513 from tommydudebreaux/master
Using handlebars with browserify causes IE to throw - TypeError: Invalid calling object
2013-05-09 21:03:03 -07:00
Tommy Messbauer 090ee7c59a added local pointer to handlerbars.utils to allow this to browserify properly for IE 2013-05-09 17:17:34 -05:00
MikeMayer 8a5705a253 Explicitly only include the handlebars.js from the dist folder 2013-05-01 09:58:17 -07:00
MikeMayer 344bb9092d Create handlebars.js.nuspec for inclusion in NuGet
Added a .nuspec file for those people who use Visual Studio and NuGet and might want to use handlebars.js deployed from NuGet in their application.
2013-05-01 09:52:55 -07:00
Utkarsh Sengar ef325733bb Added link to a gist for loading handlebars templates (sync and async) in README. 2013-04-28 13:54:47 -05:00
kpdecker 18798a7546 Adjust formatting of bullet list 2013-04-28 13:53:01 -05:00
Kevin Decker bbce33778e Merge pull request #499 from gilesbowkett/paragraph_width
adjusted paragraph width for readability
2013-04-28 11:44:19 -07:00
DevinClark fc52a65c70 Changed component.json to bower.json per Bower 0.9.0 update. 2013-04-25 21:05:21 -05:00
Giles Bowkett 06e593d32e paragraph width 2013-04-25 14:44:52 -07:00
Kevin Decker 16a6fe15f2 Merge pull request #492 from iangreenleaf/patch-1
Change download link in install instructions
2013-04-25 09:39:13 -07:00
Ian Young d8d2a6a1d2 Change download link in install instructions
The install instructions in the readme were still pointing to the GitHub downloads page, which is deprecated and out of date.

Updated to point to the official site, which has a big download button.
2013-04-16 16:32:40 -07:00
Kevin Decker 4c35f89c2a Merge pull request #488 from tnwinc/amd-module-return-values
Amd module return values
2013-04-08 10:41:08 -07:00
DevinClark 5c9aa9e4d6 Added a component.json file for Twitter's Bower package manager.
See [https://github.com/twitter/bower#defining-a-package](Defining a
package) for more information about how the file was generated.
2013-04-08 12:28:13 -05:00
James Maroney ca576c27fc If building AMD module and compiling multiple templates, return the full
templates (or partials) hash from the AMD module
2013-04-08 10:37:18 -04:00
James Maroney 85a21f2359 If building AMD modules, and only one template is being compiled,
return the compiled template from the AMD module
2013-04-08 10:36:43 -04:00
Kevin Decker 075fdb8ac4 Merge pull request #454 from leshill/fix_string_mode_contexts
Add contexts for string mode hash values
2013-04-07 17:21:40 -07:00
kpdecker 4429ffa9f3 Allow multiple partial and helper registration
Fixes #369
2013-04-07 18:04:51 -05:00
kpdecker 73f2016a6a Remove FULL_CONTEXT from spec_helper 2013-04-07 16:50:57 -05:00
kpdecker 381c113d66 Rebuild 2013-04-07 12:41:55 -05:00
kpdecker 0329c852bd Merge branch 'program-metadata' 2013-04-07 12:29:54 -05:00
kpdecker f6ff5c648b Restore knownHelpersOnly throw
Fixes #302
2013-04-06 23:23:40 -05:00
kpdecker 12d68caa58 Allow compilation of empty string
Fixes #461
2013-04-06 23:17:39 -05:00
kpdecker ff32b4e2ad Escape passed regex 2013-04-06 23:14:29 -05:00
kpdecker c59882cce1 Merge branch 'master' of github.com:matteoagosti/handlebars.js into matteoagosti-master
Conflicts:
	bin/handlebars
2013-04-06 23:07:50 -05:00
kpdecker 4d66d0c0a6 Escape unicode newlines in string literals
Fixes #375
2013-04-06 16:42:24 -05:00
kpdecker bee0facaca Rebuild 2013-04-06 15:49:13 -05:00
Kevin Decker af3501636c Merge pull request #462 from broady/master
Fix support for Rhino
2013-04-06 13:48:47 -07:00
kpdecker e33d9b4dcf Add test case for GH-458 2013-04-06 15:28:27 -05:00
kpdecker 31aaa30fcd Rebuild 2013-04-06 15:28:18 -05:00
kpdecker ec5882a31a Merge branch 'master' of github.com:jpfiset/handlebars.js into jpfiset-master 2013-04-06 15:18:30 -05:00
kpdecker 671c07e699 Force toString in escapeExpression
Fixes #211
2013-04-06 14:46:04 -05:00
kpdecker f4d0092bb8 escapeExpression tests 2013-04-06 14:42:33 -05:00
kpdecker fe74d65f2b isEmpty test 2013-04-06 14:26:08 -05:00
kpdecker 9e4b59e815 Add support for \\{ escapes
Fixes #456
2013-03-31 16:27:34 -05:00
Les Hill 53de75927c Add contexts for string mode hash values
Allows for evaluating hash parameters such as ../city in string mode.
2013-03-19 21:41:36 -07:00
kpdecker bcc15ea5e7 Rebuild dist 2013-03-13 21:56:30 -05:00
Kevin Decker 21977633a0 Merge pull request #213 from thejohnfreeman/master
Block helper "with" should check for empty context
2013-03-13 19:54:26 -07:00
Chris Broadfoot e4a0ac4b04 Fix Rhino support 2013-03-04 16:08:00 -08:00
Jean-Pierre Fiset 681f1a6396 Allow colon characters in identifiers
Issue #460
2013-03-04 13:33:13 -05:00
Jean-Pierre Fiset bf30bf9115 Allow accessing properties in current context using paths. This is to
avoid name collisions with registered helper functions.

Issue #458
2013-03-04 13:25:00 -05:00
kpdecker 948231a31d 1.0.10 2013-02-27 07:52:34 -06:00
kpdecker 3657457d81 Merge branch 'master' of github.com:wycats/handlebars.js 2013-02-27 07:49:56 -06:00
Kevin Decker 4bfb137e81 Use master build for travis badge 2013-02-26 17:46:19 -06:00
Yehuda Katz 835496f0b6 Merge pull request #443 from machty/gemified
Added handlebars-source gemspec
2013-02-19 21:12:51 -08:00
machty 519156138d Added handlebars-source gemspec for sharing with 3rd party precompilation libs 2013-02-20 00:04:39 -05:00
kpdecker ac59d6d060 Always rebuild dist files 2013-02-17 04:34:04 -06:00
kpdecker baccdb4cfc Add program metadata
Include program id and depth on the generated wrapper objects. This
allows helpers who are passed these objects to differentiate between
helpers for cases where they may want to cache the generated DOM
structure.
2013-02-17 04:27:42 -06:00
kpdecker c95b3d6fc5 Create new options object for unless nesting
Fixes #343
2013-02-16 16:18:48 -06:00
kpdecker d6f146f8a5 Fix #428 nested if else rendering
The program equality checker was not taking children into account
when determining equality, causing breakages under similar cases.
2013-02-16 16:18:17 -06:00
kpdecker 1f00504d25 Rebuild 2013-02-16 16:02:08 -06:00
Kevin Decker 8b2c1e5d23 Merge pull request #442 from tricknotes/fix-method-name-in-error-message
Fix method name in error message
2013-02-16 14:01:31 -08:00
kpdecker fa400d9dcb Merge branch 'iife' 2013-02-16 15:58:29 -06:00
kpdecker 10453d1ef8 Use local vars 2013-02-16 13:56:19 -06:00
kpdecker 30623ce903 Add explicit SafeString property test 2013-02-16 13:55:55 -06:00
kpdecker da5cde5d1f Move Handlerbars.print into printer file 2013-02-16 13:36:34 -06:00
kpdecker 3e86bb0f64 Remove unnecessary child scopes 2013-02-16 13:08:46 -06:00
kpdecker 16bfebbf49 Use dist output in spec helper 2013-02-16 13:07:56 -06:00
kpdecker 1f76b065fc browser-prefix/suffix templates 2013-02-16 13:07:39 -06:00
kpdecker e2481cd66e Use PARSER_CONTEXT for tokenizer tests 2013-02-16 10:47:42 -06:00
kpdecker 8a05dcb70e Remove unused scope function 2013-02-16 10:47:04 -06:00
Ryunosuke SATO 261a2deb51 Fix method name in error message
- compile -> precompile
2013-02-16 20:14:38 +09:00
kpdecker 75a4f0d931 Negative number literal support
Fixes #422
2013-02-15 20:22:11 -06:00
kpdecker b74711e63b 1.0.9 2013-02-15 19:43:52 -06:00
kpdecker e3454a0187 Restore module scope in base.js 2013-02-15 19:41:10 -06:00
kpdecker f72cbed479 Merge branch 'master' of github.com:tommydudebreaux/handlebars.js into tommydudebreaux-master 2013-02-15 19:24:15 -06:00
Peter Wagenet bc8214cb7c Updated to 1.0.0-rc.3 2013-02-14 21:58:20 -08:00
Tommy Messbauer 7f9e3fea81 tests passing 2013-02-11 23:13:37 -06:00
Tommy Messbauer 1ca7462497 merge 2013-02-11 23:00:47 -06:00
Tommy Messbauer 514c9391e0 restored scope of var verifyMatch 2013-02-11 22:50:23 -06:00
Peter Wagenet 7c633b604d Better error messages for precompiler runtime mismatch 2013-02-09 19:25:53 -08:00
Peter Wagenet 51e9259fac Decouple compiler version checks from release versions 2013-02-08 18:42:20 -08:00
Yehuda Katz 0844d74fbe Merge pull request #429 from machty/extension-support
Open Handlebars compilation to externally-compiled AST
2013-02-07 10:02:38 -08:00
machty a48e32bbf6 Handlebars.parse/precompile/compile can now be passed an already-compiled Handlebars AST. 2013-02-07 02:06:17 -05:00
Yehuda Katz 448e43c41e Merge pull request #424 from wagenet/version-check-v2
Improved precompile template version check
2013-01-30 22:57:24 -08:00
Peter Wagenet 6fe7f17c89 Improved precompile template version check
This check reduces duplicated code as well as also failing if the
template was precompiled on a version before the check was added.
2013-01-30 22:44:30 -08:00
Yehuda Katz edb6f11b42 Merge pull request #420 from wagenet/version-check
Check version when evaluating templates.
2013-01-29 13:29:58 -08:00
Peter Wagenet d7758fe1a9 Check version when evaluating templates.
This makes sure that we throw an error when a template was precompiled
with a version of Handlebars that doesn't match the evaluating version.
2013-01-29 13:29:19 -08:00
kpdecker a3376e24b1 Rebuild 2013-01-21 19:49:16 -06:00
Kevin Decker 6a51198a9b Merge pull request #412 from wycats/inline-ops
Inline ops
2013-01-21 17:47:31 -08:00
Yehuda Katz a68a5ad5ba Merge pull request #414 from leshill/leading_context_only
Only allow `this` or `..` to lead a path
2013-01-21 11:30:19 -08:00
Les Hill 6ab92eee6d Only allow 'this' or '..' to lead a path
Paths like 'outer/../key' raise an exception when compiling.
2013-01-21 11:21:43 -08:00
kpdecker b0c9f3d83b mergeSource helper 2013-01-21 01:03:42 -06:00
kpdecker b661977966 Merge duplicate programs 2013-01-21 00:49:28 -06:00
kpdecker 969b418291 Remove inline option from pushStack 2013-01-21 00:23:08 -06:00
kpdecker 8e9688792b Use push rather than pushStack for inline ops 2013-01-21 00:21:17 -06:00
kpdecker 9ac47df7dd Make replaceStack always inline 2013-01-20 23:58:42 -06:00
kpdecker 6210d0b040 Merge branch 'master' into inline-ops 2013-01-20 21:43:10 -06:00
kpdecker 69d46e008b Rebuild dist 2013-01-20 21:42:37 -06:00
kpdecker f8a346b3ce Add LICENSE to built file
Fixes #411
2013-01-20 21:42:29 -06:00
kpdecker 0f6651bad9 Formatting updates for inlined content 2013-01-20 21:19:39 -06:00
kpdecker 231543feaf Merge consecutive buffer appends 2013-01-20 21:18:49 -06:00
kpdecker b3a39ccefd Use options register for blockHelperMissing 2013-01-20 17:30:40 -06:00
kpdecker 956b0a62a6 Inline known helper calls 2013-01-20 17:05:17 -06:00
kpdecker 94e70043f9 Use stack rather than register for invoke lookup 2013-01-20 17:04:30 -06:00
kpdecker bf201a66c2 Prevent duplication of inline content in append 2013-01-20 17:00:46 -06:00
kpdecker 39cc7c6816 Update 2nd level opcodes to use inlines 2013-01-19 18:33:49 -06:00
kpdecker e9ac494ffd Allow replaceStack to work with the inline stack 2013-01-19 18:21:05 -06:00
kpdecker cb50caaf53 Use inline values for simple terminals 2013-01-19 17:39:13 -06:00
kpdecker ae95407775 Inline stack helper implementation 2013-01-19 17:35:05 -06:00
kpdecker 7277284132 Merge branch 'master' into inline-ops 2013-01-19 17:01:20 -06:00
kpdecker d486e7fa47 Remove unused vars 2013-01-19 17:01:08 -06:00
kpdecker 3c20641a2e Reuse stack var to avoid register 2013-01-19 16:57:54 -06:00
kpdecker 5e20c809dc Output options only once to unknownHelper case 2013-01-19 16:44:43 -06:00
kpdecker 2b875cfb33 Merge branch 'master' into inline-ops 2013-01-19 13:50:41 -06:00
kpdecker 228c75669f Expand literal syntax tests 2013-01-19 13:19:17 -06:00
kpdecker d1fdb82291 Make equals without messages more resilient
When called without a message parameter the tests would fail under some
environments. I'm not clear on what is causing the failure, perhaps
ruby version, but this patch cleans up the issues there.
2013-01-19 13:19:03 -06:00
kpdecker 247babbe1a Empty hash literal output 2013-01-19 12:15:43 -06:00
kpdecker b1ea697b7d Inline foundHelper lookup 2013-01-19 11:39:23 -06:00
kpdecker 822d98a1ef Includ dist in npm package 2013-01-19 02:17:26 -06:00
kpdecker 5ac29dcb50 1.0.8 2013-01-19 02:12:36 -06:00
tomhuda 9453c6d9d2 Update version 2013-01-18 17:13:40 -08:00
tomhuda 0c4333ae81 Remove dist/ from gitignore 2013-01-18 17:03:39 -08:00
tomhuda ce82bef6f1 Boolean("false") === true 2013-01-18 16:42:27 -08:00
tomhuda cd9895061b Must pop the stack extra for hashes 2013-01-18 15:37:09 -08:00
Yehuda Katz ccd6a22ea5 Add support for getting types in string mode
This makes it possible to determine whether an
argument was passed as a string or as a path
when implementing helpers in string mode.
2013-01-16 22:43:25 -08:00
kpdecker 5e5f0dce9c Expand helperMissing test coverage
Fixes #216
2013-01-14 01:01:02 -06:00
kpdecker f5079765ff Update benchmark suite for node 0.8 2013-01-14 00:00:11 -06:00
kpdecker 24fc3a2288 Prevent write on all depth variables 2013-01-13 23:43:29 -06:00
kpdecker 8961298859 Prevent context overwrite by replaceStack
Fixes #408.
2013-01-13 22:35:03 -06:00
kpdecker 4f54f62abc Remove duplicate assignment 2013-01-13 22:31:42 -06:00
kpdecker eaa7e97461 Add namespace arg to handlebars bin
Fixes #361
2013-01-13 18:22:52 -06:00
kpdecker 59e30ecf37 Merge branch 'travis-node-test' 2013-01-13 17:44:24 -06:00
kpdecker 3396814797 Merge branch 'master' of github.com:wycats/handlebars.js 2013-01-13 17:01:45 -06:00
kpdecker fb9a7aa533 Chain npm tests to rspec tests 2013-01-13 17:00:54 -06:00
kpdecker 72f33c4615 Use npm package for jison management 2013-01-13 17:00:08 -06:00
Kevin Decker 16f2bf8f9b Merge pull request #400 from lushchick/master
Removed unnecessary semicolon to pass JSHint for compiled code
2013-01-13 14:01:46 -08:00
kpdecker 5f56d6582f Simplify isEmpty 2013-01-13 15:53:06 -06:00
Kevin Decker 76882ff75a Merge pull request #402 from banks/master
README example typo (properly fixed)
2013-01-13 13:42:06 -08:00
kpdecker 3cac2672d0 Use jsmodule for jison parser
Removes unnecessary commonjs code generated for the parser. This reduces
the size of the parse by about 700bytes and should resolve lookup issues
with browserify and other static analysis tools. See #220
2013-01-13 15:34:27 -06:00
kpdecker 7a3641df5a Cleanup tests
jshint and fixup arg passing
2013-01-13 13:22:44 -06:00
kpdecker b3f954b0af Merge branch 'fix_specs' of github.com:leshill/handlebars.js into leshill-fix_specs 2013-01-13 12:47:18 -06:00
kpdecker f671c2e3d7 Pin rubyracer version to 0.10
This is a temporary fix. See https://github.com/wycats/handlebars.js/pull/405#issuecomment-12013108
2013-01-13 12:43:42 -06:00
kpdecker 118594f663 Update to therubyracer 0.11.1 2013-01-08 11:19:24 -08:00
Les Hill f4cad086bb Fix spec for 8df8488 2013-01-06 09:32:18 -08:00
kpdecker 51fd64c334 Merge remote-tracking branch 'upstream/master' 2012-12-30 17:10:38 -06:00
Paul Banks 0ffb2a9681 Duh. The example WAS right just really confusing due to bad variable naming 2012-12-30 17:52:52 +00:00
Paul Banks ca22ca57e1 Fix typo in readme example 2012-12-30 17:23:11 +00:00
Andrey Lushchick e9fe73d7ab Removed unnecessary semicolon, which is needed to pass JSHint for compiled code 2012-12-27 14:10:23 +02:00
Yehuda Katz 28f377d169 Upgrade to latest TRR 2012-12-23 21:27:44 -08:00
Yehuda Katz 02d35262f9 Fill in missing specs 2012-12-23 21:27:44 -08:00
Yehuda Katz 8df8488947 Merge pull request #347 from spikebrehm/master
Makes Handlebars.log available in both builds and fleshes out `log` helper.
2012-12-23 21:06:57 -08:00
Yehuda Katz 17ee7ffaca Merge pull request #388 from dmarcotte/empty-inverse-blocks
Allow empty blocks around simple inverses
2012-12-23 20:31:46 -08:00
Yehuda Katz bf4c813db0 Merge pull request #389 from leshill/partial_names
Partials can be paths
2012-12-23 20:29:36 -08:00
Yehuda Katz 070e12f76f Merge pull request #390 from eastridge/master
Make {data: true} a default compile option
2012-12-23 20:21:36 -08:00
Yehuda Katz 9385666e08 Get a better error for compile(falsy) 2012-12-23 20:15:18 -08:00
kpdecker dfaf698b44 Merge remote-tracking branch 'upstream/master' 2012-12-19 21:06:59 -06:00
Ryan Eastridge 12f160fb31 Add data = true as default option 2012-12-13 11:42:18 -08:00
Les Hill b58c2dd0ad Test for #84 2012-12-13 11:15:38 -08:00
Les Hill 4bb794d814 Partials can be paths
Allows partials with slashes, a common partial syntax. For example:

    {{> shared/dude}}
2012-12-13 11:15:38 -08:00
Ryan Eastridge 7ce13a3c2c Add failing unit test for private variables 2012-12-12 18:10:18 -08:00
Kevin Decker 19bfbe07f8 Merge pull request #374 from bkaull/master
Adding precompilation of partials
2012-12-11 10:56:56 -08:00
Brian Kaull fd0560b951 Update bin/handlebars 2012-12-11 10:41:50 -08:00
Daniel Marcotte 4cfda479fa Allow empty blocks around simple inverses
Previously, the parser required at least one character of whitespace
to properly interpret empty blocks around simple inverses, which was
non-intuitive and inconsistent with empty block parsing.

Update the parser to allow empty blocks around simple inverses.
2012-12-11 09:47:49 -08:00
kpdecker eabcc8a002 Merge branch 'master' of https://github.com/wycats/handlebars.js
Conflicts:
	package.json
2012-12-02 13:40:37 -08:00
Brian Kaull cbfe84f1a9 Adding partial precompilation
Also allowing to compile based on data getting passed in
2012-11-26 11:51:53 -08:00
Tommy Messbauer db975b42a0 Merged upstream master and ran unit tests 2012-11-26 10:26:15 -06:00
Tommy Messbauer 7963218495 Factory update with tabs to spaces.. sorry :( 2012-11-26 09:39:08 -06:00
Kevin Decker bd04901454 Merge pull request #371 from cpojer/commonjs-export-option
Add CommonJS export option for template compilation.
2012-11-25 09:56:39 -08:00
cpojer c3f92c402e Add CommonJS export option for template compilation. 2012-11-22 12:28:44 +01:00
Matteo Agosti 53d6b4ca04 Supporting custom template extension
Fix missing escape
2012-11-08 12:22:35 +01:00
Peter Wagenet 39832c0633 Fix handling of Errors in Chrome 2012-11-02 10:39:49 -07:00
Spike Brehm 2676efb222 overzealous approach to implmenting Handlebars.logger.log and 'log' helper. 2012-10-20 15:26:57 -07:00
Yehuda Katz a927a9b0ad Add block comment syntax:
{{!-- can contain
      {{handlebars expressions}} --}}
2012-10-15 13:51:53 -04:00
Yehuda Katz 8ed7a1e4e6 Merge pull request #339 from mikesherov/each
{{#each}} now supports objects [rebased against master]
2012-10-15 07:49:31 -07:00
Mike Petrovich d89e4b3d33 Added unit tests for #each with objects 2012-10-15 10:27:58 -04:00
Ross Hadden 9589ab8949 Implemented ability to iterate over objects, ala for-in.
Also added the 'key' key to looped objects.
My goal is to make this {{@key}}, but am still working on it.
I would also like to unobtrusively make @key or @index work for arrays.
2012-10-15 10:27:56 -04:00
Yehuda Katz 3212325173 Merge pull request #290 from fancyoung/master
Fix demo bug in README
2012-10-12 23:40:51 -07:00
Yehuda Katz 850304f43b Merge pull request #295 from leshill/escaped_quotes
Escaped quotes in hash arguments
2012-10-12 23:38:18 -07:00
Yehuda Katz ed245c6c64 Merge pull request #334 from anton-rudeshko/master
Added AMD option doc to README
2012-10-10 07:22:17 -07:00
Yehuda Katz 1018d9bfd1 Merge pull request #319 from stvp/master
Bind functions to the context.
2012-10-10 01:05:03 -07:00
Yehuda Katz a1bfc80577 Merge pull request #322 from scribu/patch-1
fix Block Helpers example
2012-10-10 01:04:30 -07:00
Anton Rudeshko 1b197be7fc Added AMD option doc 2012-10-07 06:20:06 -07:00
Cristi Burcă 9e582fa0eb fix Block Helpers example 2012-09-28 03:35:36 +03:00
Les Hill a1c9acb8b1 Escaped single quotes in hash arguments 2012-09-22 08:09:33 -07:00
Tyson Tate 42120d1177 Bind functions to the context properly.
The regression was introduced in 1.0.rc.1.

This fixes issue #317.
2012-09-19 16:03:46 -07:00
kpdecker b5074a88ec Rev node version to 1.0.7
Per discussion with @wycats. Due to the version history of the node package and the manner in which npm's semver implementation works we need to continue the patch numbers utilized previously.

Once the upstream implementation moves to 1.1 versioning we can sync the implementations again.
2012-09-17 19:27:23 -05:00
Peter Wagenet ce74c36118 Bumped package.json version 2012-09-15 21:11:36 -07:00
Yehuda Katz 26b1438f20 Merge pull request #198 from codekitchen/master
properly handle ampersands when HTML escaping
2012-09-14 20:48:40 -07:00
Yehuda Katz b83e5e0ea6 Merge pull request #297 from karlwestin/fix-reverse-array
Fixed an issue where {{#array}} {{/array}} wouldn't pass in an @index data variable
2012-09-14 20:26:11 -07:00
Yehuda Katz f6aea819f3 Merge pull request #310 from redpie/fix-registerPartial-data
Ensure plain text partials supplied to registerPartials are compiled using data: true if necessary.
2012-09-14 20:25:10 -07:00
Yehuda Katz 647ee28ca1 Merge pull request #313 from redpie/rake-spec-exitcode
Ensure `rake spec` exits with a meaningful exit code
2012-09-14 20:24:29 -07:00
Kiall Mac Innes d117b50fd3 Ensure rake spec exits with a meaningful exit code 2012-09-14 15:27:13 +01:00
kpdecker ed8c2b95b8 Update npm repo to root repo 2012-09-13 17:54:07 -05:00
kpdecker aba2269ddb Add nested hyphen test cases 2012-09-13 17:51:12 -05:00
Kiall Mac Innes 967c69b2da Ensure plain text partials supplied to registerPartials are compiled using data: true if necessary. 2012-09-13 15:04:34 +01:00
tomhuda 5a6e4f1ddd Fix repeated delimiter escaping 2012-09-12 20:44:48 -07:00
Yehuda Katz f55ca6c489 Merge pull request #309 from redpie/use-local-jison
Update Rakefile to use jison from node
2012-09-12 20:05:21 -07:00
Kiall Mac Innes 4b73b51e12 Update Rakefile to use a locally installed version of jison
The means root access is no longer required to build.
2012-09-12 11:15:31 +01:00
Tommy Messbauer eccc7c35ca Finished factory pattern 2012-08-29 15:07:15 -05:00
Tommy Messbauer 7c4813b417 Commiting initial factory code 2012-08-29 12:48:22 -05:00
Karl Westin acc04c2826 Fixed an issue where {{#array}} {{/array}} wouldn't pass in an
@index data variable.

I just thought it would be nice to add in this feature. I'm using
the two of these a little interchangable, so if other people are doing
that as well, it might help usability.
2012-08-27 12:55:55 -07:00
kpdecker 8e9bd6742f Merge remote-tracking branch 'upstream/master' 2012-08-21 12:51:58 -05:00
Kevin Decker 89f5ab8aaf Merge pull request #277 from kpdecker/mocha-tests
Mocha tests

Add and fix node tests running qunit_spec under mocha and revs the node package to the latest.
2012-08-21 10:51:14 -07:00
kpdecker dc0426d836 Merge with upstream/master 2012-08-21 12:49:40 -05:00
Yehuda Katz 6761d4c6d1 Merge pull request #276 from HoffmannP/master
AMD support (second proposal)
2012-08-16 18:00:18 -07:00
Yehuda Katz 3426969221 Merge pull request #291 from leshill/quotes
Recognize bar='baz' hash argument
2012-08-16 17:59:45 -07:00
Les Hill 9ce3032678 Recognize bar='baz' hash argument 2012-08-14 23:12:22 -07:00
fancyoung 4cf0869330 Update README.markdown
fix doc demo bug.
2012-08-14 17:09:36 +08:00
kpdecker 201917a1be Merge remote-tracking branch 'upstream/master' 2012-07-31 13:27:51 -05:00
kpdecker 08e580a23b 1.0.6-2 2012-07-31 11:50:55 -05:00
Peter Wagenet 2b3e777340 Merge pull request #282 from kpdecker/this-param
This param
2012-07-30 21:43:13 -07:00
kpdecker cfc1d534cb Merge with this-param 2012-07-30 21:49:17 -05:00
kpdecker 4ffe1b8e5a Fix handling of this parameters in helpers
Fixes issue introduced in 727eb26cb6
2012-07-30 21:40:25 -05:00
kpdecker d8825b1961 this parameter tests 2012-07-30 21:12:03 -05:00
kpdecker f5289a35ec 1.0.6 2012-07-23 15:40:07 -05:00
kpdecker fc99d90337 Fix loading under node 2012-07-23 13:48:27 -05:00
kpdecker eb509a4b8b Execute mocha test from npm test 2012-07-23 13:47:52 -05:00
kpdecker 09ac8ac24b Make jshint happy(er) 2012-07-23 13:45:53 -05:00
kpdecker adb8486e5f Use suite rather than module for module decl 2012-07-23 13:44:45 -05:00
kpdecker 1e151e2030 Ignore sublime projects 2012-07-23 13:42:38 -05:00
kpdecker fe7d16de91 1.0.6beta 2012-07-23 10:36:13 -05:00
Peter Hoffmann 075076975d cmd flag to compile template with AMD style 2012-07-23 13:45:25 +02:00
tomhuda 8be16c64f9 Add a .rspec 2012-07-12 10:14:21 -07:00
tomhuda f79af6bfa3 Merge branch 'master' of github.com:wycats/handlebars.js 2012-07-12 10:07:10 -07:00
Yehuda Katz efb1e25690 Update for change in TRR API 2012-07-12 08:15:10 -07:00
Yehuda Katz 36b242a710 Document lookupData 2012-07-05 23:10:39 -07:00
Yehuda Katz 659be5a690 Reduce duplication 2012-07-05 23:08:57 -07:00
Yehuda Katz 46c04fa71e Make deep @data trigger the data option 2012-07-05 23:05:55 -07:00
Yehuda Katz 5e8be14d78 Add support for @index in #each 2012-07-05 22:43:50 -07:00
Yehuda Katz 72e05d623c Add support for @data variables 2012-07-05 22:43:05 -07:00
Yehuda Katz ff1547ea04 Merge pull request #262 from naholyr/patch-1
FIX global leak
2012-06-25 07:40:52 -07:00
Nicolas Chambrier 7a393a972b FIX global leak 2012-06-25 17:38:27 +03:00
tomhuda 92b6c1401a Bump version to RC1 2012-05-28 20:05:55 -07:00
tomhuda 0afc8b58d2 Clean up parser tests and AST printer 2012-05-28 19:06:26 -07:00
tomhuda 1082ec2414 Fix a bug where registers were shared 2012-05-28 19:06:08 -07:00
tomhuda 727eb26cb6 Remove unneeded code and add docs
There were a few operations that are no longer
needed, so remove them. Also document all
operations.
2012-05-28 17:35:21 -07:00
tomhuda 175c6fae0f More cleanup
* Make block and inverse use the main helper path
* Eliminate separate inverse AST node
2012-05-28 17:00:41 -07:00
tomhuda 3486b530be Disambiguate more ahead of time 2012-05-28 14:19:48 -07:00
tomhuda bc5efc1767 Remove unneeded binary opcode stuff
It turns out that we don't need to store the
opcodes in a compact way, so don't bother.
2012-05-28 13:28:51 -07:00
Yehuda Katz 90adaa3cc8 Continue work on cleaning up helpers 2012-05-28 13:03:22 -07:00
Yehuda Katz 8786a6c6e2 Start doing earlier work on helpers 2012-05-28 00:15:52 -07:00
Yehuda Katz 246325085f Simple spec for isolation analysis 2012-05-26 22:17:31 -07:00
Yehuda Katz 68c76c9b2e Clean up the compiler a bit 2012-05-26 22:17:22 -07:00
tomhuda facefe8fed Several improvements to compiled output:
* Eliminate legacy support for an options hash
  that doubles as a function. This prevented us
  from building the hash as a literal, and added
  a bunch of code weight
* Create a new "stack literal" construct, that
  allows an opcode to push a literal expression
  onto the stack. This will not allocate a new
  stack variable, and when popped, will simply
  return the literal expression as a String.
2012-05-26 14:51:41 -07:00
tomhuda 8e70e3b58b Don't assume execution in the top context 2012-05-26 14:47:32 -07:00
Yehuda Katz 730c412925 Merge pull request #218 from SlexAxton/master
Adding using a 'class' variable name as a 'known issue'
2012-05-06 20:53:52 -07:00
Peter Wagenet 2418feaf19 Wrap base Handlebars in a closure to prevent var leakage - fixes #205 2012-05-04 15:27:50 -07:00
Alex Sexton c2591f428b Adding warning in about using 'class' as a variable name. 2012-04-11 21:46:28 -05:00
John Freeman 0872fb6699 block helper "with" should check for empty context 2012-03-31 23:09:27 -05:00
Peter Wagenet 98b3327c75 Merge pull request #202 from neomorphic/master
vm -> runtime change in README
2012-03-06 07:17:58 -08:00
Jody Clements 992fdf2071 Update README to reflect vm -> runtime change
Looks like one handlebars.vm.js was missed in the README file during the
change to handlebars.runtime.js
2012-03-06 08:32:01 -05:00
Brian Palmer bd9a84a0b7 properly handle amperstands when HTML escaping
escapeExpression, when given a string like "&gt;", was simply returning
"&gt;", not escaping the amperstand. This is incorrect, and makes it
impossible to have Handlebars properly escape a
string like "Escaped, <b> looks like: &lt;b&gt;"

If the intention of the user is to not escape these characters, then
{{{}}} or {{&}} should be used
2012-02-29 11:25:51 -07:00
tomhuda 24e04bad94 JSHint the library and fix a few bugs 2012-02-17 16:51:35 -08:00
Peter Wagenet e6079ca5e2 Merge pull request #177 from kpdecker/master
Rev npm package to 1.0.5beta
2012-02-09 09:56:22 -08:00
kpdecker bef909f5cb 1.0.5beta 2012-02-09 11:06:11 -06:00
Peter Wagenet 0b97fea56f Fixed error in lexer 2012-02-08 20:49:50 -08:00
Peter Wagenet 2ea95ca08d Fixed whitespace and semi-colons 2012-02-08 20:49:49 -08:00
Peter Wagenet cb9db98642 Merge pull request #165 from spadgos/patch-1
Added a missing `var` statement.
2012-02-08 18:08:26 -08:00
Nick Fisher 58e2c2410f Added a missing var statement. 2012-01-17 07:16:19 +01:00
Yehuda Katz 6040149cf8 I'm an idiot 2011-12-31 16:04:11 -08:00
Yehuda Katz 17cdb0091b Missing some reserved words 2011-12-31 15:41:16 -08:00
Yehuda Katz 730c2617af Differentiate between lambdas in the context (which should have mustache semantics) and helpers (which have Handlebars helper semantics). 2011-12-31 13:51:54 -08:00
Yehuda Katz 757d5250dc Rename "vm" to "runtime" for clarity 2011-12-27 18:15:44 -08:00
Yehuda Katz 163a92ebe0 Update Gemfile.lock with latest TRR with lion support 2011-12-27 17:05:22 -08:00
Yehuda Katz c79c761460 Add support for escaping mustaches 2011-12-27 17:04:59 -08:00
Yehuda Katz e474e56b80 data should be passed through to partials. closes #111. 2011-12-27 15:57:08 -08:00
Yehuda Katz 69307d0e2b Allow function parameters to #if. This was originally disallowed because it is decidedly not idiomatic Handlebars, which would prefer that you use helpers for these cases, not pollute your context objects with functions to call from your template, but enough people asked for it that I'll give in.
I will personally not be using this feature and still strongly recommend that people use helpers in this case.
2011-12-27 15:47:45 -08:00
Yehuda Katz 9852502636 Bump version 2011-12-27 13:34:37 -08:00
Yehuda Katz 7a0bf9d332 Handlebars errors should copy the message over 2011-12-27 13:18:54 -08:00
Yehuda Katz 2e7ea96818 Add Ember.js 2011-12-27 13:13:57 -08:00
Yehuda Katz 932e2970ad Realign lexer actions 2011-12-27 01:23:28 -08:00
Yehuda Katz 373d10edb1 Literal square-bracket path segments cannot have ] in them. TODO: Allow escaped path segments. 2011-12-27 01:21:53 -08:00
Yehuda Katz bd0f74fbd9 Test that the hello world mustache example works 2011-12-27 00:43:30 -08:00
Yehuda Katz e57e626480 Fix bug where inverse section was being passed to subsequent helpers 2011-12-27 00:30:46 -08:00
Yehuda Katz cb04822812 Merge pull request #156 from broady/master
Fix support for Rhino
2011-12-23 16:57:51 -08:00
Yehuda Katz 647d5adf85 Merge pull request #121 from tablatom/master
Added a noEscape option to Handlebars.compile
2011-12-23 16:57:39 -08:00
Yehuda Katz 6df51733d3 Merge pull request #157 from kpdecker/npm-update
Npm update
2011-12-22 23:52:22 -08:00
kpdecker 9c9d92413e Update package version number 2011-12-22 22:52:41 -06:00
kpdecker c6eec286a8 Update package dependencies 2011-12-22 22:52:20 -06:00
Chris Broadfoot f6070f0084 Fix support for Rhino 2011-12-20 10:43:37 +11:00
Yehuda Katz 87e6f21765 Add a status button 2011-12-16 16:13:07 -08:00
Yehuda Katz 3afc572f6b Compile after installing jison 2011-12-16 16:07:54 -08:00
Yehuda Katz fea981a3f6 Try to install jison if it's not already installed 2011-12-16 15:55:12 -08:00
Peter Wagenet 799c7ed3ce Merge pull request #155 from twe4ked/patch-1
Add syntax highlighting to the README.
2011-12-05 22:15:47 -08:00
Odin Dutton b2a27347e8 Add syntax highlighting to the README. 2011-12-06 14:59:48 +10:00
Yehuda Katz 215472dda2 Merge pull request #149 from SlexAxton/patch-1
Update the version!
2011-11-30 12:46:13 -08:00
Alex Sexton 8338bc888a Update the version! 2011-11-30 02:06:49 -06:00
Yehuda Katz 5d15ef9625 Merge pull request #144 from wagenet/rhino-fix
Compatibility with Mozilla Rhino
2011-11-22 23:04:40 -08:00
Mike Moulton 42e31ef6db Escaping a '/' in the ID regex. This was preventing handelbars.js from loading in Rhino. 2011-11-22 22:46:29 -08:00
Yehuda Katz deac4f5141 Merge pull request #135 from leshill/handlebars_assets
Add handlebars_assets to README
2011-11-12 05:51:45 -08:00
Yehuda Katz 79b730ccf3 Merge pull request #132 from steevel/master
Broken code when putting js after generated block.
2011-11-12 05:50:29 -08:00
kpdecker ec3e5e3914 Merge remote-tracking branch 'upstream/master' 2011-11-04 14:15:46 -04:00
Les Hill 5f9919dcf7 Add handlebars_assets to README 2011-11-01 23:20:12 -07:00
Yehuda Katz 608aa94fc3 Update Gemfile.lock 2011-10-28 17:39:49 -07:00
Yehuda Katz 9998d9d9bc Allow subclasses to customize the namespace 2011-10-28 17:39:42 -07:00
Peter Wagenet 924cc3e86c Goodbye poor hack. You were broken before your time. 2011-10-27 22:32:07 -07:00
Steeve Lennmark 25b8e314af Add semicolon to the generated anonymous javascript function since it could break code following it.
Example: (function(){})()/* this is the same as undefined() */(function(){})()
2011-10-27 17:21:46 +02:00
Peter Wagenet 43431d2a78 Added 'log' helper - See #97 2011-10-24 19:41:41 -07:00
Peter Wagenet b36776f4c6 Better errors for 'equals' test 2011-10-24 19:12:54 -07:00
Peter Wagenet f23fbb84e2 Updated Gemfile to therubyracer 0.9.8 - Fixes #126 2011-10-24 17:54:11 -07:00
Peter Wagenet 87599daef1 Merge pull request #127 from SlexAxton/master
Added in comment docs
2011-10-24 17:43:29 -07:00
Peter Wagenet 374a38ed36 Merge pull request #130 from kpdecker/defer-compile
Defer compile
2011-10-24 17:27:54 -07:00
Peter Wagenet b501c380af Handle 'jison' failing in 'rake compile' 2011-10-24 17:27:14 -07:00
kpdecker 7dd11fa3f2 Merge branch 'defer-compile' 2011-10-21 08:34:33 -05:00
kpdecker defc2f3403 Refactor deferred compile to use helper 2011-10-21 08:34:24 -05:00
kpdecker 9a27aa98b7 Merge branch 'defer-compile' 2011-10-21 08:25:30 -05:00
kpdecker 4458b5fa13 Defer compilation of templates until needed. 2011-10-21 08:24:55 -05:00
kpdecker 7cd34edcc2 Defer compilation of templates until needed. 2011-10-20 14:52:21 -07:00
Alex Sexton 7b18e51bf8 Added in docs for template comments 2011-10-09 20:29:03 -05:00
Tom Locke cd927e9cda Add noEscape option to Handlebars.compile 2011-09-26 09:47:30 +01:00
Alan Johnson b832c85923 Forgot === for falsy check. 2011-09-05 14:20:57 -04:00
Alan Johnson 33bb89beda Merge branch 'master' of github.com:wycats/handlebars.js 2011-09-05 11:08:44 -04:00
Alan Johnson 33bde69750 Fixed bug in falsy check on lookups. 2011-09-05 11:08:22 -04:00
Alan Johnson 2422a6518f Merge pull request #116 from jroes/master
Add rake to Gemfile
2011-09-04 05:26:32 -07:00
Alan Johnson 0768f07b17 Merge pull request #117 from kpdecker/precompiled-readme
Precompiled readme
2011-09-04 05:21:06 -07:00
kpdecker 2f3d5977d1 Precompiled templates info in README 2011-09-04 02:33:14 -05:00
kpdecker 8d38ebd251 Fix copy in command line utility 2011-09-04 02:29:25 -05:00
kpdecker d630894d9b Merge remote-tracking branch 'upstream/master' 2011-09-03 17:52:06 -05:00
Jonathan Roes 8f617ca624 Add rake to Gemfile 2011-09-02 21:57:07 -04:00
Alan Johnson 57e3bd72a8 Added link to tryhandlebarsjs.com. 2011-09-02 12:13:42 -03:00
Alan Johnson 6b18873d23 Fixed errors occurring because of undefined properties on nested paths. 2011-09-02 10:56:37 -04:00
Alan Johnson c1c455acc8 Reproduced undefined property error. 2011-09-02 10:47:05 -04:00
Alan Johnson f2f1f5f9bd Wrote a quick test for partials with literal paths. 2011-09-02 10:36:59 -04:00
Alan Johnson 7212e2b7d7 Got more complex literal expressions working. 2011-09-02 09:10:20 -04:00
Alan Johnson fc84308cc9 Got simple literal expressions added into paths. 2011-09-02 09:04:41 -04:00
Alan Johnson 91bbc4fd2c Merge pull request #114 from markmarkoh/patch-1
The missing semicolon is breaking our code when we minify.
2011-09-01 18:49:12 -07:00
Mark DiMarco 696dc2dc36 The missing semicolon is breaking our code when we minify. 2011-09-01 20:37:51 -05:00
Alan Johnson 96571b93c1 Merge pull request #113 from kpdecker/npm
Npm package declaration
2011-09-01 05:15:56 -07:00
Alan Johnson 3db5fb742e Merge pull request #112 from kpdecker/command-line
Enhanced precompiler command line support
2011-09-01 05:15:37 -07:00
kpdecker c561fdf8b1 Merge branch 'npm' 2011-08-22 02:41:59 -05:00
kpdecker f7480d5b42 Add main target to packageinfo 2011-08-22 02:41:53 -05:00
kpdecker 93ebee8faf Exclude directory children from npm 2011-08-22 02:34:04 -05:00
kpdecker 20350a3c07 Merge branch 'command-line' 2011-08-22 02:27:23 -05:00
kpdecker 8cd4d939f3 Use proper uglify package name 2011-08-22 02:27:15 -05:00
kpdecker 98e66f898e Merge branch 'npm' 2011-08-22 02:00:43 -05:00
kpdecker 85a32a18e2 Update package info 2011-08-22 02:00:35 -05:00
kpdecker 90648c0e2d Merge branch 'npm' 2011-08-22 01:52:09 -05:00
kpdecker 6ef3d1316f npmignore file 2011-08-22 01:51:08 -05:00
kpdecker f3a2374dda Add simple output mode. 2011-08-22 01:49:10 -05:00
kpdecker cbadb33d51 Cleanup stored template names. 2011-08-22 01:40:33 -05:00
kpdecker 17e1b1fab7 Output multiple templates, storing into the Handlebars.template object 2011-08-22 01:38:41 -05:00
kpdecker 6eea3e4d44 Examine all passed names in the template check 2011-08-22 01:35:26 -05:00
kpdecker 48726c7fed Minimize js commandline option 2011-08-22 00:15:06 -05:00
kpdecker 47c5cd71ea File output commandline option 2011-08-22 00:14:53 -05:00
kpdecker a9bcd369f3 Known helpers command line options. 2011-08-22 00:11:22 -05:00
kpdecker b3e70414d8 Use optimist to perform validation. 2011-08-22 00:07:35 -05:00
kpdecker c9d851e9a4 Merge branch 'master' of https://github.com/wycats/handlebars.js 2011-08-21 16:29:31 -05:00
kpdecker 86d71e6fc8 Properly init eco for rake bench. 2011-08-21 16:28:57 -05:00
Alan Johnson 542f98190b Added newline to end of benchmark output, just to clean it up. 2011-08-15 23:38:35 -04:00
Alan Johnson 16c0907607 Added some more notes to the README for contributors. 2011-08-15 23:31:29 -04:00
Alan Johnson 292e32292b Updated README. 2011-08-15 23:14:40 -04:00
Alan Johnson 3c5cac3f3f Fixed missing var in compiler code causing compilerWords to be global. 2011-08-15 23:10:27 -04:00
tomhuda 2f7b724d5a Make sure options.hash is an empty {} if no hash is present to eliminate necessary guards in helpers 2011-08-02 16:32:57 -07:00
kpdecker 318c08a97e Do not perform unnecessary self-assign 2011-07-31 21:44:27 -05:00
kpdecker d6b97cf34d Do not buffer for simple programs (1 statement) 2011-07-31 21:44:16 -05:00
kpdecker 82e3344aed Update the eco benchmark handler for the latest API 2011-07-31 21:02:40 -05:00
kpdecker 6349c1a0c7 Optimize the populate call logic for the simple cases 2011-07-31 21:01:40 -05:00
kpdecker f53737ef23 knownHandlers and knownHandlersOnly compile options. 2011-07-31 21:00:44 -05:00
kpdecker 1a88356dc3 Access context objects directly rather than using currentContext var. 2011-07-31 17:17:14 -05:00
kpdecker 5d4b549dc3 Add template call to the bin output 2011-07-31 16:17:57 -05:00
kpdecker d044ada7aa Convert children to nested functions. Reuse identifiers by closure where possible. 2011-07-31 16:17:39 -05:00
kpdecker 77a20a3a9f Alias self -> this 2011-07-31 14:58:37 -05:00
kpdecker 9821da8df7 Move aliases and registers into context object. 2011-07-31 14:10:32 -05:00
kpdecker 2d538baf72 Use x = a[y] || b[y] rather than if exists lookup 2011-07-31 11:27:47 -05:00
kpdecker c7e8ddd6b5 Minimizable id aliases. 2011-07-31 11:25:40 -05:00
kpdecker 6fcebec713 buffer += 2011-07-31 11:21:18 -05:00
kpdecker 993c793565 Avoid eval when running in VM+Compiler mode 2011-07-30 16:55:26 -05:00
kpdecker f8edc59025 Update the bin to use the precompile API 2011-07-30 16:20:26 -05:00
kpdecker 3af9de7407 Update partial testing for the various modes 2011-07-30 16:12:20 -05:00
kpdecker 72ea816cf7 Remove debug code 2011-07-30 16:11:37 -05:00
kpdecker d641dae5f1 Full context and compileWithPartials test helper 2011-07-30 16:02:11 -05:00
kpdecker ccf32821df Throw a handlebars exception when attempting to use template partials in VM mode. 2011-07-30 16:01:43 -05:00
kpdecker 8e23ce87d3 Use CompilerContext.compile for tests rather than Handlebars.compile.
This prevents false negatives on the partial tests.
2011-07-30 15:54:57 -05:00
kpdecker 5a3522cf2e Builder for handlebars.vm.js 2011-07-30 15:26:34 -05:00
kpdecker aaec001931 Split the tests into compiler and vm tests 2011-07-30 15:26:21 -05:00
kpdecker 3d9e5070f5 Move mismatched block test from quint to parser tests. 2011-07-30 15:25:25 -05:00
kpdecker 82a1b0e85c Temporary Handlebars.compile implementation in compiler 2011-07-30 15:24:49 -05:00
kpdecker 600a669d12 Expose Handlebars.precompile method from the compiler. 2011-07-30 15:24:14 -05:00
kpdecker 5aa12b5e09 Rename Handlebars.VM.compile to Handelbars.VM.template 2011-07-30 15:23:01 -05:00
kpdecker 6a6edf5ae6 Simple node template compiler 2011-07-30 12:25:41 -05:00
kpdecker aeda7e389f Ignores update 2011-07-30 12:19:30 -05:00
kpdecker 09e79f0587 Package info 2011-07-30 12:19:17 -05:00
kpdecker 4a9270aa38 Fix module loading within node 2011-07-30 12:18:56 -05:00
kpdecker 59f5331db4 Phase 2 of precompiled extraction:
Use string representations to pass around the function context
2011-07-30 11:23:24 -05:00
kpdecker 74bd8cac60 Phase 1 of precompiled extraction:
Separate template logic from container construction.
2011-07-30 11:10:13 -05:00
kpdecker 542d64f0be Remove unnecessary var declaration. 2011-07-30 10:44:07 -05:00
kpdecker 5e611205ac Create compiler base file. 2011-07-30 10:38:36 -05:00
kpdecker 2e77f1c777 Remove the debug file (Suspect broken Due to missing class references) 2011-07-30 10:33:40 -05:00
kpdecker 8c49721c70 Move visitor into compiler subdir 2011-07-30 10:28:31 -05:00
kpdecker 2fc01ff73a Move art into compiler dir 2011-07-30 10:25:30 -05:00
kpdecker bba08ad95f Move printer into compiler dir 2011-07-30 10:23:37 -05:00
kpdecker 034dafddac Move parser file into compiler subdir 2011-07-30 10:14:59 -05:00
kpdecker 6aa9a30b02 Update node loader for compiler/vm split 2011-07-30 10:12:37 -05:00
kpdecker f2dccb753f Break compiler and vm logic into separate files. 2011-07-30 10:11:13 -05:00
kpdecker 471f3b9748 Reduce scope of global variable 2011-07-29 22:45:16 -05:00
kpdecker a927125909 Can access context variables masked by helpers by scoping with 'this.' 2011-07-29 20:53:47 -05:00
kpdecker 9062cac3f0 Add missing messages in equal tests 2011-07-29 20:45:17 -05:00
tomhuda 059a80661d * Remove legacy support for inverse sections as additional parameters.
* Unify inverse and normal block helpers
* Make Handlebars.Exception inherit from JS Error
2011-07-07 23:09:33 -07:00
Alan Johnson 37e36bf137 Merge pull request #66 from rgrove/fix-empty-array-if
The "if" block helper shouldn't treat empty arrays as truthy
2011-06-27 03:15:50 -07:00
Alan Johnson 3c2086eb2a Merge pull request #98 from Pumpkin/master
Updated README to fix code sample mistake.
2011-06-27 03:11:41 -07:00
inook 1ff0b657be Update README 2011-06-26 10:37:55 +04:00
Yehuda Katz 17dbfa7004 Merge pull request #96 from gleitz/master
Allow boolean calls to work with YUI Compressor
2011-06-22 16:37:26 -07:00
gleitz 52900c89f7 removed references to .boolean, which broke the YUI Compressor 2011-06-22 17:18:07 -04:00
gleitz be8f4f6fb9 Updated the boolean functions to work with YUI compressor. Linted other files for consistency 2011-06-22 13:54:17 -04:00
tomhuda 1482f1ae72 Add BOOLEAN support 2011-06-01 22:57:22 -07:00
tomhuda 0f78345d0c Add support for INTEGER expressions 2011-06-01 22:33:48 -07:00
Yehuda Katz 4eeda34ad2 Merge pull request #79 from schuyler1d/numbers
allow number indexes in paths for array access
2011-05-25 17:08:18 -07:00
Yehuda Katz ed1be562e2 Merge pull request #81 from schuyler1d/google_json_support
support "$" as an ID character: google has a bunch of json that has $t, etc
2011-05-25 17:06:50 -07:00
Yehuda Katz 04b359d15b Merge pull request #86 from jblotus/master
A few bug fixes
2011-05-25 17:06:31 -07:00
Yehuda Katz f07f70ca42 Merge pull request #68 from rgrove/fix-escaping
Add ", ', and ` to the list of chars that need HTML escaping.
2011-05-25 17:05:54 -07:00
James Fuller 28d35180bd fixing partials with int names 2011-05-24 00:40:09 -04:00
James Fuller 0e22301e8f fixed a syntax error in documentation example 2011-05-18 19:26:15 -07:00
Schuyler Duveen 4e5cf17311 support "$" as an ID character: google has a bunch of json that has $t, etc values 2011-05-13 11:40:31 -04:00
wycats a4ca50533d Add support for line-breaks in mustaches 2011-05-12 12:23:15 -07:00
Schuyler Duveen 1aa512d227 allow number indexes in paths for array access 2011-05-10 12:00:40 -04:00
Ryan Grove 2f4644529c Remove / from the list of escaped chars and add `.
It's probably fine not to escape /, since its only danger is in ending
entities (like &amp/). This isn't a problem for us, since the badChars
regex won't allow it and the & will get escaped.

It turns out ` can be used to quote attribute values in IE, so it needs
to be escaped along with " and '.
2011-05-09 15:19:23 -07:00
Ryan Grove d109e31f62 Merge branch 'master' into fix-escaping
Conflicts:
	lib/handlebars/utils.js
2011-05-09 14:59:51 -07:00
Yehuda Katz 5168037b5a Edited README.markdown via GitHub 2011-05-05 11:22:03 -07:00
Yehuda Katz 92535a9158 Edited README.markdown via GitHub 2011-05-05 11:16:22 -07:00
Yehuda Katz 4f309bb3aa Merge pull request #74 from jeroenvandijk/patch-1
Add a link to the Readme to the very nice documentation site
2011-05-05 11:14:29 -07:00
Yehuda Katz b4c949cc07 Merge pull request #73 from nhocki/patch-1
Adding missing ) to the upgrading section...
2011-05-05 11:14:04 -07:00
Jeroen van Dijk 90bb568faf Add a link to the Readme to the very nice documentation site 2011-05-04 01:37:21 -07:00
tomhuda 60040e60fb Update specs and code so that the function passed to block helpers has the same API as regular compiled templates 2011-05-04 00:03:50 -07:00
Nicolás Hock Isaza a00054e26b Adding missing ) to the upgrading section... 2011-05-03 21:09:09 -07:00
Ryan Grove b291a1ad8c Add ", ', and / to the list of chars that need HTML escaping.
Previously, only < and > were escaped. This meant that any Handlebars
template that used user input in an HTML attribute value was wide open
to a trivial XSS exploit. Note that unquoted attribute values are still
open to attack, but this set of characters at least brings Handlebars in
line with other Mustache implementations and other template languages.

See the OWASP XSS prevention cheat sheet (rule #1) for the rationale
behind escaping these characters:

https://www.owasp.org/index.php/XSS_(Cross_Site_Scripting)_Prevention_Cheat_Sheet
2011-04-25 11:15:53 -07:00
Ryan Grove dec196b4d9 The "if" block helper shouldn't treat empty arrays as truthy.
Given the data {foo: []}, the following template previously considered
foo to be truthy when it shouldn't have:

    {{#if foo}}
      You should not see me!
    {{else}}
      You should see me!
    {{/if}}
2011-04-20 16:56:37 -07:00
109 changed files with 9851 additions and 3168 deletions
+10 -2
View File
@@ -1,5 +1,13 @@
dist
lib/handlebars/parser.js
vendor
.rvmrc
.DS_Store
lib/handlebars/compiler/parser.js
/dist/
/tmp/
node_modules
*.sublime-project
*.sublime-workspace
npm-debug.log
sauce_connect.log*
/coverage
/.idea
+51
View File
@@ -0,0 +1,51 @@
{
"predef": [
"console",
"require",
"suite",
"equal",
"equals",
"test",
"testBoth",
"raises",
"deepEqual",
"start",
"stop",
"ok",
"strictEqual",
"module",
"define",
"describe",
"it",
"afterEach",
"beforeEach"
],
"node" : true,
"browser" : true,
"esnext": true,
"boss" : true,
"curly": false,
"debug": false,
"devel": false,
"eqeqeq": false,
"eqnull": true,
"evil": true,
"forin": false,
"immed": false,
"laxbreak": true,
"newcap": true,
"noarg": true,
"noempty": false,
"nonew": false,
"nomen": false,
"onevar": false,
"plusplus": false,
"regexp": false,
"undef": true,
"sub": true,
"strict": false,
"white": false
}
+23
View File
@@ -0,0 +1,23 @@
.DS_Store
.gitignore
.rvmrc
.jshintrc
.travis.yml
.rspec
Gemfile
Gemfile.lock
Rakefile
Gruntfile.js
*.gemspec
*.nuspec
bench/*
configurations/*
components/*
dist/cdnjs/*
dist/components/*
spec/*
src/*
tasks/*
tmp/*
publish/*
vendor/*
+30
View File
@@ -0,0 +1,30 @@
language: node_js
node_js:
- "0.8"
before_install:
- npm install -g grunt-cli
script:
- grunt --stack travis
email:
on_failure: change
on_success: never
env:
global:
- S3_BUCKET_NAME=builds.handlebarsjs.com
- secure: PJaukuvkBBsSDOqbIcNSSMgb96VVEaIt/eq9GPjXPeFbSd3hXgwhwVE62LrqtJO8BaUfX+PzpiQjEl4D5/KBmvlFZ057Hqmy0zmPOT5mDZfJe8Ja5zyvTMb+KkCWN/tjAp8kawHojE04pn6jIpPdwXFnAYwPhaHbATFrmdt9fdg=
- secure: mBcGL2tnmiRujJdV/4fxrVd8E8wn6AW9IQKVcMv8tvOc7i5dOzZ39rpBKLuTMRXDtMV1LyLiuKYb1pHj1IyeadEahcLYFfGygF4LG7Yzp4NWHtRzQ7Q8LXaJV7dXDboYCFkn2a8/Rtx1YSVh/sCONf5UoRC+MUIqrj4UiHN9r3s=
matrix:
include:
- node_js: "0.10"
env:
- PUBLISH=true
- secure: pLTzghtVll9yGKJI0AaB0uI8GypfWxLTaIB0ZL8//yN3nAEIKMhf/RRilYTsn/rKj2NUa7vt2edYILi3lttOUlCBOwTc9amiRms1W8Lwr/3IdWPeBLvLuH1zNJRm2lBAwU4LBSqaOwhGaxOQr6KHTnWudhNhgOucxpZfvfI/dFw=
- secure: yERYCf7AwL11D9uMtacly/THGV8BlzsMmrt+iQVvGA3GaY6QMmfYqf6P6cCH98sH5etd1Y+1e6YrPeMjqI6lyRllT7FptoyOdHulazQe86VQN4sc0EpqMlH088kB7gGjTut9Z+X9ViooT5XEh9WA5jXEI9pXhQJNoIHkWPuwGuY=
cache:
directories:
- node_modules
-4
View File
@@ -1,4 +0,0 @@
source "http://rubygems.org"
gem "therubyracer", ">= 0.8.0"
gem "rspec"
-20
View File
@@ -1,20 +0,0 @@
GEM
remote: http://rubygems.org/
specs:
diff-lcs (1.1.2)
rspec (2.1.0)
rspec-core (~> 2.1.0)
rspec-expectations (~> 2.1.0)
rspec-mocks (~> 2.1.0)
rspec-core (2.1.0)
rspec-expectations (2.1.0)
diff-lcs (~> 1.1.2)
rspec-mocks (2.1.0)
therubyracer (0.8.0)
PLATFORMS
ruby
DEPENDENCIES
rspec
therubyracer (>= 0.8.0)
+205
View File
@@ -0,0 +1,205 @@
var childProcess = require('child_process');
module.exports = function(grunt) {
grunt.initConfig({
pkg: grunt.file.readJSON('package.json'),
jshint: {
options: {
jshintrc: '.jshintrc'
},
files: [
'dist/**/!(*.min|parser).js'
]
},
clean: ['tmp', 'dist', 'lib/handlebars/compiler/parser.js'],
copy: {
dist: {
options: {
processContent: function(content, path) {
return grunt.template.process('/*!\n\n <%= pkg.name %> v<%= pkg.version %>\n\n<%= grunt.file.read("LICENSE") %>\n@license\n*/\n')
+ content;
}
},
files: [
{expand: true, cwd: 'dist/', src: ['*.js'], dest: 'dist/'}
]
},
cdnjs: {
files: [
{expand: true, cwd: 'dist/', src: ['*.js'], dest: 'dist/cdnjs'}
]
},
components: {
files: [
{expand: true, cwd: 'components/', src: ['**'], dest: 'dist/components'},
{expand: true, cwd: 'dist/', src: ['*.js'], dest: 'dist/components'}
]
}
},
packager: {
global: {
type: 'global',
export: 'Handlebars',
files: [{
cwd: 'lib/',
expand: true,
src: ['handlebars*.js'],
dest: 'dist/'
}]
},
amd: {
type: 'amd',
anonymous: true,
files: [{
expand: true,
cwd: 'lib/',
src: '**/!(index).js',
dest: 'dist/amd/'
}]
},
cjs: {
type: 'cjs',
files: [{
expand: true,
cwd: 'lib/',
src: '**/!(index).js',
dest: 'dist/cjs/'
}]
}
},
requirejs: {
options: {
optimize: "none",
baseUrl: "dist/amd/"
},
dist: {
options: {
name: "handlebars",
out: "dist/handlebars.amd.js"
}
},
runtime: {
options: {
name: "handlebars.runtime",
out: "dist/handlebars.runtime.amd.js"
}
}
},
uglify: {
options: {
mangle: true,
compress: true,
preserveComments: 'some'
},
dist: {
files: [{
cwd: 'dist/',
expand: true,
src: ['handlebars*.js', '!*.min.js'],
dest: 'dist/',
rename: function(dest, src) {
return dest + src.replace(/\.js$/, '.min.js');
}
}]
}
},
concat: {
tests: {
src: ['spec/!(require).js'],
dest: 'tmp/tests.js'
}
},
connect: {
server: {
options: {
base: '.',
hostname: '*',
port: 9999
}
}
},
'saucelabs-mocha': {
all: {
options: {
build: process.env.TRAVIS_JOB_ID,
urls: ['http://localhost:9999/spec/?headless=true', 'http://localhost:9999/spec/amd.html?headless=true'],
detailedError: true,
concurrency: 2,
browsers: [
{browserName: 'chrome'},
{browserName: 'firefox'},
{browserName: 'firefox', version: '3.6'},
{browserName: 'safari', version: 7, platform: 'OS X 10.9'},
{browserName: 'safari', version: 6, platform: 'OS X 10.8'},
{browserName: 'safari', version: 5},
{browserName: 'opera', version: 12},
{browserName: 'opera', version: 11},
{browserName: 'internet explorer', version: 11, platform: 'Windows 8.1'},
{browserName: 'internet explorer', version: 10, platform: 'Windows 8'},
{browserName: 'internet explorer', version: 9, platform: 'Windows 7'},
{browserName: 'internet explorer', version: 8, platform: 'XP'},
{browserName: 'internet explorer', version: 7, platform: 'XP'},
{browserName: 'internet explorer', version: 6, platform: 'XP'}
]
}
}
},
watch: {
scripts: {
options: {
atBegin: true
},
files: ['src/*', 'lib/**/*.js', 'spec/**/*.js'],
tasks: ['build', 'amd', 'tests', 'test']
}
}
});
// Build a new version of the library
this.registerTask('build', "Builds a distributable version of the current project", [
'parser',
'node',
'globals',
'jshint']);
this.registerTask('amd', ['packager:amd', 'requirejs']);
this.registerTask('node', ['packager:cjs']);
this.registerTask('globals', ['packager:global']);
this.registerTask('tests', ['concat:tests']);
this.registerTask('release', 'Build final packages', ['amd', 'jshint', 'uglify', 'copy:dist', 'copy:components', 'copy:cdnjs']);
// Load tasks from npm
grunt.loadNpmTasks('grunt-contrib-clean');
grunt.loadNpmTasks('grunt-contrib-concat');
grunt.loadNpmTasks('grunt-contrib-connect');
grunt.loadNpmTasks('grunt-contrib-copy');
grunt.loadNpmTasks('grunt-contrib-requirejs');
grunt.loadNpmTasks('grunt-contrib-jshint');
grunt.loadNpmTasks('grunt-contrib-uglify');
grunt.loadNpmTasks('grunt-contrib-watch');
grunt.loadNpmTasks('grunt-saucelabs');
grunt.loadNpmTasks('es6-module-packager');
grunt.task.loadTasks('tasks');
grunt.registerTask('bench', ['metrics']);
grunt.registerTask('sauce', process.env.SAUCE_USERNAME ? ['tests', 'connect', 'saucelabs-mocha'] : []);
grunt.registerTask('travis', process.env.PUBLISH ? ['default', 'sauce', 'metrics', 'publish:latest'] : ['default']);
grunt.registerTask('dev', ['clean', 'connect', 'watch']);
grunt.registerTask('default', ['clean', 'build', 'test', 'release']);
};
-1
View File
@@ -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.
+343 -105
View File
@@ -1,33 +1,59 @@
[![Travis Build Status](https://travis-ci.org/wycats/handlebars.js.png?branch=master)](https://travis-ci.org/wycats/handlebars.js)
[![Selenium Test Status](https://saucelabs.com/buildstatus/handlebars)](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.
Handlebars.js is an extension to the [Mustache templating
language](http://mustache.github.com/) created by Chris Wanstrath.
Handlebars.js and Mustache are both logicless templating languages that
keep the view and the code separated like we all know they should be.
Checkout the official Handlebars docs site at
[http://www.handlebarsjs.com](http://www.handlebarsjs.com).
Installing
----------
Installing Handlebars is easy. Simply [download the package from GitHub](https://github.com/wycats/handlebars.js/archives/master) and add it to your web pages (you should usually use the most recent version).
Installing Handlebars is easy. Simply download the package [from the official site](http://handlebarsjs.com/) or the [bower repository][bower-repo] and add it to your web pages (you should usually use the most recent version).
Alternatively, if you prefer having the latest version of handlebars from
the 'master' branch, passing builds of the 'master' branch are automatically
published to S3. You may download the latest passing master build by grabbing
a `handlebars-latest.js` file from the [builds page][builds-page]. When the
build is published, it is also available as a `handlebars-gitSHA.js` file on
the builds page if you need a version to refer to others.
`handlebars-runtime.js` builds are also available.
**Note**: The S3 builds page is provided as a convenience for the community,
but you should not use it for hosting Handlebars in production.
Usage
-----
In general, the syntax of Handlebars.js templates is a superset of Mustache templates. For basic syntax, check out the [Mustache manpage](http://mustache.github.com/mustache.5.html).
In general, the syntax of Handlebars.js templates is a superset
of Mustache templates. For basic syntax, check out the [Mustache
manpage](http://mustache.github.com/mustache.5.html).
Once you have a template, use the Handlebars.compile method to compile the template into a function. The generated function takes a context argument, which will be used to render the template.
Once you have a template, use the `Handlebars.compile` method to compile
the template into a function. The generated function takes a context
argument, which will be used to render the template.
var source = "<p>Hello, my name is {{name}}. I am from {{hometown}}. I have " +
"{{kids/length}} kids:</p>" +
"<ul>{{#kids}}<li>{{name}} is {{age}}</li>{{/kids}}</ul>";
var template = Handlebars.compile(source);
```js
var source = "<p>Hello, my name is {{name}}. I am from {{hometown}}. I have " +
"{{kids.length}} kids:</p>" +
"<ul>{{#kids}}<li>{{name}} is {{age}}</li>{{/kids}}</ul>";
var template = Handlebars.compile(source);
var data = { "name": "Alan", "hometown": "Somewhere, TX",
"kids": [{"name": "Jimmy", "age": "12"}, {"name": "Sally", "age": "4"}]};
var result = template(data);
var data = { "name": "Alan", "hometown": "Somewhere, TX",
"kids": [{"name": "Jimmy", "age": "12"}, {"name": "Sally", "age": "4"}]};
var result = template(data);
// Would render:
// <p>Hello, my name is Alan. I am from Somewhere, TX. I have 2 kids:</p>
// <ul>
// <li>Jimmy is 12</li>
// <li>Sally is 4</li>
// </ul>
// Would render:
// <p>Hello, my name is Alan. I am from Somewhere, TX. I have 2 kids:</p>
// <ul>
// <li>Jimmy is 12</li>
// <li>Sally is 4</li>
// </ul>
```
Registering Helpers
@@ -37,22 +63,31 @@ You can register helpers that Handlebars will use when evaluating your
template. Here's an example, which assumes that your objects have a URL
embedded in them, as well as the text for a link:
Handlebars.registerHelper('link_to', function(context) {
return "<a href='" + context.url + "'>" + context.body + "</a>";
});
```js
Handlebars.registerHelper('link_to', function() {
return "<a href='" + this.url + "'>" + this.body + "</a>";
});
var context = { posts: [{url: "/hello-world", body: "Hello World!"}] };
var source = "<ul>{{#posts}}<li>{{{link_to this}}}</li></ul>"
var context = { posts: [{url: "/hello-world", body: "Hello World!"}] };
var source = "<ul>{{#posts}}<li>{{{link_to}}}</li>{{/posts}}</ul>"
var template = Handlebars.compile(source);
template(context);
var template = Handlebars.compile(source);
template(context);
// Would render:
//
// <ul>
// <li><a href='/hello-world'>Hello World!</a></li>
// </ul>
// Would render:
//
// <ul>
// <li><a href='/hello-world'>Hello World!</a></li>
// </ul>
```
Helpers take precedence over fields defined on the context. To access a field
that is masked by a helper, a path reference may be used. In the example above
a field named `link_to` on the `context` object would be referenced using:
```
{{./link_to}}
```
Escaping
--------
@@ -67,48 +102,67 @@ To explicitly *not* escape the contents, use the triple-mustache
Differences Between Handlebars.js and Mustache
----------------------------------------------
Handlebars.js adds a couple of additional features to make writing templates easier and also changes a tiny detail of how partials work.
Handlebars.js adds a couple of additional features to make writing
templates easier and also changes a tiny detail of how partials work.
### Paths
Handlebars.js supports an extended expression syntax that we call paths. Paths are made up of typical expressions and / characters. Expressions allow you to not only display data from the current context, but to display data from contexts that are descendents and ancestors of the current context.
Handlebars.js supports an extended expression syntax that we call paths.
Paths are made up of typical expressions and . characters. Expressions
allow you to not only display data from the current context, but to
display data from contexts that are descendants and ancestors of the
current context.
To display data from descendent contexts, use the `/` character. So, for example, if your data were structured like:
To display data from descendant contexts, use the `.` character. So, for
example, if your data were structured like:
var data = {"person": { "name": "Alan" }, company: {"name": "Rad, Inc." } };
```js
var data = {"person": { "name": "Alan" }, "company": {"name": "Rad, Inc." } };
```
you could display the person's name from the top-level context with the following expression:
You could display the person's name from the top-level context with the
following expression:
{{person/name}}
```
{{person.name}}
```
Similarly, if already traversed into the person object you could still display the company's name with an expression like `{{../company/name}}`, so:
You can backtrack using `../`. For example, if you've already traversed
into the person object you could still display the company's name with
an expression like `{{../company.name}}`, so:
{{#person}}{{name}} - {{../company/name}}{{/person}}
```
{{#person}}{{name}} - {{../company.name}}{{/person}}
```
would render:
Alan - Rad, Inc.
```
Alan - Rad, Inc.
```
### Strings
When calling a helper, you can pass paths or Strings as parameters. For
instance:
Handlebars.registerHelper('link_to', function(title, context) {
return "<a href='/posts" + context.id + "'>" + title + "</a>"
});
```js
Handlebars.registerHelper('link_to', function(title, options) {
return "<a href='/posts" + this.url + "'>" + title + "!</a>"
});
var context = { posts: [{url: "/hello-world", body: "Hello World!"}] };
var source = '<ul>{{#posts}}<li>{{{link_to "Post" this}}}</li></ul>'
var context = { posts: [{url: "/hello-world", body: "Hello World!"}] };
var source = '<ul>{{#posts}}<li>{{{link_to "Post"}}}</li>{{/posts}}</ul>'
var template = Handlebars.compile(source);
template(context);
var template = Handlebars.compile(source);
template(context);
// Would render:
//
// <ul>
// <li><a href='/hello-world'>Post!</a></li>
// </ul>
// Would render:
//
// <ul>
// <li><a href='/posts/hello-world'>Post!</a></li>
// </ul>
```
When you pass a String as a parameter to a helper, the literal String
gets passed to the helper function.
@@ -116,27 +170,42 @@ gets passed to the helper function.
### Block Helpers
Handlebars.js also adds the ability to define block helpers. Block helpers are functions that can be called from anywhere in the template. Here's an example:
Handlebars.js also adds the ability to define block helpers. Block
helpers are functions that can be called from anywhere in the template.
Here's an example:
var source = "<ul>{{#people}}<li>{{{#link}}}{{name}}{{/link}}</li>{{/people}}</ul>";
Handlebars.registerHelper('link', function(context, fn) {
return '<a href="/people/' + this.__get__("id") + '">' + fn(this) + '</a>';
});
var template = Handlebars.compile(source);
```js
var source = "<ul>{{#people}}<li>{{#link}}{{name}}{{/link}}</li>{{/people}}</ul>";
Handlebars.registerHelper('link', function(options) {
return '<a href="/people/' + this.id + '">' + options.fn(this) + '</a>';
});
var template = Handlebars.compile(source);
var data = { "people": [
{ "name": "Alan", "id": 1 },
{ "name": "Yehuda", "id": 2 }
]};
template(data);
var data = { "people": [
{ "name": "Alan", "id": 1 },
{ "name": "Yehuda", "id": 2 }
]};
template(data);
// Should render:
// <ul>
// <li><a href="/people/1">Alan</a></li>
// <li><a href="/people/2">Yehuda</a></li>
// </ul>
// Should render:
// <ul>
// <li><a href="/people/1">Alan</a></li>
// <li><a href="/people/2">Yehuda</a></li>
// </ul>
```
Whenever the block helper is called it is given two parameters, the argument that is passed to the helper, or the current context if no argument is passed and the compiled contents of the block. Inside of the block helper the value of `this` is the current context, wrapped to include a method named `__get__` that helps translate paths into values within the helpers.
Whenever the block helper is called it is given one or more parameters,
any arguments that are passed in the helper in the call and an `options`
object containing the `fn` function which executes the block's child.
The block's current context may be accessed through `this`.
Block helpers have the same syntax as mustache sections but should not be
confused with one another. Sections are akin to an implicit `each` or
`with` statement depending on the input data and helpers are explicit
pieces of code that are free to implement whatever behavior they like.
The [mustache spec](http://mustache.github.io/mustache.5.html)
defines the exact behavior of sections. In the case of name conflicts,
helpers are given priority.
### Partials
@@ -145,76 +214,245 @@ Handlebars when it encounters a partial (`{{> partialName}}`). Partials
can either be String templates or compiled template functions. Here's an
example:
var source = "<ul>{{#people}}<li>{{> link}}</li>{{/people}}</ul>";
```js
var source = "<ul>{{#people}}<li>{{> link}}</li>{{/people}}</ul>";
Handlebars.registerPartial('link', '<a href="/people/{{id}}">{{name}}</a>')
var template = Handlebars.compile(source);
Handlebars.registerPartial('link', '<a href="/people/{{id}}">{{name}}</a>')
var template = Handlebars.compile(source);
var data = { "people": [
{ "name": "Alan", "id": 1 },
{ "name": "Yehuda", "id": 2 }
]};
var data = { "people": [
{ "name": "Alan", "id": 1 },
{ "name": "Yehuda", "id": 2 }
]};
template(data);
template(data);
// Should render:
// <ul>
// <li><a href="/people/1">Alan</a></li>
// <li><a href="/people/2">Yehuda</a></li>
// </ul>
```
### Comments
You can add comments to your templates with the following syntax:
```js
{{! This is a comment }}
```
You can also use real html comments if you want them to end up in the output.
```html
<div>
{{! This comment will not end up in the output }}
<!-- This comment will show up in the output -->
</div>
```
// Should render:
// <ul>
// <li><a href="/people/1">Alan</a></li>
// <li><a href="/people/2">Yehuda</a></li>
// </ul>
Precompiling Templates
----------------------
TODO in the rewrite. This will use RubyRacer and not node.
Handlebars allows templates to be precompiled and included as javascript
code rather than the handlebars template allowing for faster startup time.
### Installation
The precompiler script may be installed via npm using the `npm install -g handlebars`
command.
### Usage
<pre>
Precompile handlebar templates.
Usage: handlebars template...
Options:
-a, --amd Create an AMD format function (allows loading with RequireJS) [boolean]
-f, --output Output File [string]
-k, --known Known helpers [string]
-o, --knownOnly Known helpers only [boolean]
-m, --min Minimize output [boolean]
-s, --simple Output template function only. [boolean]
-r, --root Template root. Base value that will be stripped from template names. [string]
-c, --commonjs Exports CommonJS style, path to Handlebars module [string]
-h, --handlebarPath Path to handlebar.js (only valid for amd-style) [string]
-n, --namespace Template namespace [string]
-p, --partial Compiling a partial template [boolean]
-d, --data Include data when compiling [boolean]
-e, --extension Template extension. [string]
-b, --bom Removes the BOM (Byte Order Mark) from the beginning of the templates. [boolean]
</pre>
If using the precompiler's normal mode, the resulting templates will be
stored to the `Handlebars.templates` object using the relative template
name sans the extension. These templates may be executed in the same
manner as templates.
If using the simple mode the precompiler will generate a single
javascript method. To execute this method it must be passed to the using
the `Handlebars.template` method and the resulting object may be as
normal.
### Optimizations
- Rather than using the full _handlebars.js_ library, implementations that
do not need to compile templates at runtime may include _handlebars.runtime.js_
whose min+gzip size is approximately 1k.
- If a helper is known to exist in the target environment they may be defined
using the `--known name` argument may be used to optimize accesses to these
helpers for size and speed.
- When all helpers are known in advance the `--knownOnly` argument may be used
to optimize all block helper references.
- Implementations that do not use `@data` variables can improve performance of
iteration centric templates by specifying `{data: false}` in the compiler options.
Supported Environments
----------------------
Handlebars has been designed to work in any ECMAScript 3 environment. This includes
- Node.js
- Chrome
- Firefox
- Safari 5+
- Opera 11+
- IE 6+
Older versions and other runtimes are likely to work but have not been formally
tested.
[![Selenium Test Status](https://saucelabs.com/browser-matrix/handlebars.svg)](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.
In a rough performance test, precompiled Handlebars.js templates (in
the original version of Handlebars.js) rendered in about half the
time of Mustache templates. It would be a shame if it were any other
way, since they were precompiled, but the difference in architecture
does have some big performance advantages. Justin Marney, a.k.a.
[gotascii](http://github.com/gotascii), confirmed that with an
[independent test](http://sorescode.com/2010/09/12/benchmarks.html). The
rewritten Handlebars (current version) is faster than the old version,
and we will have some benchmarks in the near future.
Building
--------
To build handlebars, just run `rake release`, and you will get two files
in the `dist` directory. The debug version comes with stack trace
annotations for webkit browsers, but will slightly increase startup
time.
To build handlebars, just run `grunt build`, and the build will output to the `dist` directory.
Upgrading
---------
When upgrading from the Handlebars 0.9 series, be aware that the
signature for passing custom helpers or partials to templates has
changed.
Instead of:
template(context, helpers, partials, [data])
Use:
template(context, {helpers: helpers, partials: partials, data: data}
See [release-notes.md](https://github.com/wycats/handlebars.js/blob/master/release-notes.md) for upgrade notes.
Known Issues
------------
* Handlebars.js can be cryptic when there's an error while rendering.
Handlebars Contrib
------------------
Alan Johnson, a.k.a. commondream, keeps a repository of useful helpers in the [handlebars-contrib](http://github.com/commondream/handlebars-contrib) repository. Feel free to copy those, or add some of your own.
* Using a variable, helper, or partial named `class` causes errors in IE browsers. (Instead, use `className`)
Handlebars in the Wild
-----------------
* Don Park wrote an Express.js view engine adapter for Handlebars.js called [hbs](http://github.com/donpark/hbs)
* [sammy.js](http://github.com/quirkey/sammy) by Aaron Quint, a.k.a. quirkey, supports Handlebars.js as one of its template plugins.
----------------------
* [Assemble](http://assemble.io), by [@jonschlinkert](https://github.com/jonschlinkert)
and [@doowb](https://github.com/doowb), is a static site generator that uses Handlebars.js
as its template engine.
* [CoSchedule](http://coschedule.com) An editorial calendar for WordPress that uses Handlebars.js
* [Ember.js](http://www.emberjs.com) makes Handlebars.js the primary way to
structure your views, also with automatic data binding support.
* [Ghost](https://ghost.org/) Just a blogging platform.
* [handlebars_assets](http://github.com/leshill/handlebars_assets): A Rails Asset Pipeline gem
from Les Hill (@leshill).
* [handlebars-helpers](https://github.com/assemble/handlebars-helpers) is an extensive library
with 100+ handlebars helpers.
* [hbs](http://github.com/donpark/hbs): An Express.js view engine adapter for Handlebars.js,
from Don Park.
* [jblotus](http://github.com/jblotus) created [http://tryhandlebarsjs.com](http://tryhandlebarsjs.com)
for anyone who would like to try out Handlebars.js in their browser.
* [jQuery plugin](http://71104.github.io/jquery-handlebars/): allows you to use
Handlebars.js with [jQuery](http://jquery.com/).
* [Lumbar](http://walmartlabs.github.io/lumbar) provides easy module-based template management for
handlebars projects.
* [sammy.js](http://github.com/quirkey/sammy) by Aaron Quint, a.k.a. quirkey,
supports Handlebars.js as one of its template plugins.
* [SproutCore](http://www.sproutcore.com) uses Handlebars.js as its main
templating engine, extending it with automatic data binding support.
* [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
External Resources
------------------
* [Gist about Synchronous and asynchronous loading of external handlebars templates](https://gist.github.com/2287070)
Have a project using Handlebars? Send us a [pull request][pull-request]!
Helping Out
-----------
If you notice any problems, please report them to the GitHub issue tracker at [http://github.com/wycats/handlebars.js/issues](http://github.com/wycats/handlebars.js/issues). Feel free to contact commondream or wycats through GitHub with any other questions or feature requests. To submit changes fork the project and send a pull request.
To build Handlebars.js you'll need a few things installed.
* Node.js
* [Grunt](http://gruntjs.com/getting-started)
Project dependencies may be installed via `npm install`.
To build Handlebars.js from scratch, you'll want to run `grunt`
in the root of the project. That will build Handlebars and output the
results to the dist/ folder. To re-run tests, run `grunt test` or `npm test`.
You can also run our set of benchmarks with `grunt bench`.
The `grunt dev` implements watching for tests and allows for in browser testing at `http://localhost:9999/spec/`.
If you notice any problems, please report them to the GitHub issue tracker at
[http://github.com/wycats/handlebars.js/issues](http://github.com/wycats/handlebars.js/issues).
Feel free to contact commondream or wycats through GitHub with any other
questions or feature requests. To submit changes fork the project and
send a pull request.
### Ember testing
The current ember distribution should be tested as part of the handlebars release process. This requires building the `handlebars-source` gem locally and then executing the ember test script.
```sh
grunt build release
export HANDLEBARS_PATH=`pwd`
cd $emberRepoDir
bundle exec rake clean
bundle exec rake test
```
### Releasing
Handlebars utilizes the [release yeoman generator][generator-release] to perform most release tasks.
A full release may be completed with the following:
```
yo release:notes patch
yo release:release patch
npm publish
yo release:publish cdnjs handlebars.js dist/cdnjs/
yo release:publish components handlebars.js dist/components/
cd dist/components/
gem build handlebars-source.gemspec
gem push handlebars-source-*.gem
```
After this point the handlebars site needs to be updated to point to the new version numbers.
License
-------
Handlebars.js is released under the MIT license.
[bower-repo]: https://github.com/components/handlebars.js
[builds-page]: http://builds.handlebarsjs.com.s3.amazonaws.com/bucket-listing.html?sort=lastmod&sortdir=desc
[generator-release]: https://github.com/walmartlabs/generator-release
[pull-request]: https://github.com/wycats/handlebars.js/pull/new/master
-107
View File
@@ -1,107 +0,0 @@
require "rubygems"
require "bundler/setup"
file "lib/handlebars/parser.js" => ["src/handlebars.yy","src/handlebars.l"] do
if ENV['PATH'].split(':').any? {|folder| File.exists?(folder+'/jison')}
system "jison src/handlebars.yy src/handlebars.l"
File.open("lib/handlebars/parser.js", "w") do |file|
file.puts File.read("handlebars.js") + ";"
end
sh "rm handlebars.js"
else
puts "Jison is not installed. Try running `npm install jison`."
end
end
task :compile => "lib/handlebars/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(parser base ast visitor utils compiler).map do |file|
"lib/handlebars/#{file}.js"
end
debug_deps = %w(parser base ast visitor printer utils compiler debug).map do |file|
"lib/handlebars/#{file}.js"
end
directory "dist"
minimal_deps.unshift "dist"
debug_deps.unshift "dist"
def build_for_task(task)
FileUtils.rm_rf("dist/*") if File.directory?("dist")
FileUtils.mkdir_p("dist")
contents = []
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.debug.js" => debug_deps do |task|
build_for_task(task)
end
task :build => [:compile, "dist/handlebars.js"]
task :debug => [:compile, "dist/handlebars.debug.js"]
desc "build the build and debug versions of handlebars"
task :release => [:build, :debug]
directory "vendor"
desc "benchmark against dust.js and mustache.js"
task :bench => "vendor" do
require "open-uri"
File.open("vendor/mustache.js", "w") do |file|
file.puts open("https://github.com/janl/mustache.js/raw/master/mustache.js").read
file.puts "module.exports = Mustache;"
end
File.open("vendor/benchmark.js", "w") do |file|
file.puts open("https://github.com/mathiasbynens/benchmark.js/raw/master/benchmark.js").read
end
if File.directory?("vendor/dustjs")
system "cd vendor/dustjs && git pull"
else
system "git clone git://github.com/akdubya/dustjs.git vendor/dustjs"
end
if File.directory?("vendor/coffee")
system "cd vendor/coffee && git pull"
else
system "git clone git://github.com/jashkenas/coffee-script.git vendor/coffee"
end
if File.directory?("vendor/eco")
system "cd vendor/eco && git pull"
else
system "git clone git://github.com/sstephenson/eco.git vendor/eco"
end
system "node bench/handlebars.js"
end
-147
View File
@@ -1,147 +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");
}
}
}
}
});
},
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;
+39
View File
@@ -0,0 +1,39 @@
var _ = require('underscore'),
async = require('async'),
fs = require('fs'),
zlib = require('zlib');
module.exports = function(grunt, callback) {
var distFiles = fs.readdirSync('dist'),
distSizes = {};
async.each(distFiles, function(file, callback) {
var content;
try {
content = fs.readFileSync('dist/' + file);
} catch (err) {
if (err.code === 'EISDIR') {
callback();
return;
} else {
throw err;
}
}
file = file.replace(/\.js/, '').replace(/\./g, '_');
distSizes[file] = content.length;
zlib.gzip(content, function(err, data) {
if (err) {
throw err;
}
distSizes[file + '_gz'] = data.length;
callback();
});
},
function() {
grunt.log.writeln('Distribution sizes: ' + JSON.stringify(distSizes, undefined, 2));
callback([distSizes]);
});
};
-166
View File
@@ -1,166 +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) {
var module = {};
var template = new Function("module", ecoExports.compile(str));
template(module);
return module.exports;
}
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();
+14
View File
@@ -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;
}
});
+19
View File
@@ -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]);
};
+12
View File
@@ -0,0 +1,12 @@
module.exports = {
helpers: {
foo: function(options) {
return '';
}
},
context: {
bar: true
},
handlebars: '{{foo person "person" 1 true foo=bar foo="person" foo=1 foo=true}}'
};
+7
View File
@@ -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 %>"
};
+4
View File
@@ -0,0 +1,4 @@
module.exports = {
context: { names: [{name: "Moe"}, {name: "Larry"}, {name: "Curly"}, {name: "Shemp"}] },
handlebars: "{{#names}}{{name}}{{/names}}"
}
+14
View File
@@ -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}
+14
View File
@@ -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 %>
+14
View File
@@ -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}}
+20
View File
@@ -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()
};
+13
View File
@@ -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}}
+4
View File
@@ -0,0 +1,4 @@
module.exports = {
context: { names: [{name: "Moe"}, {name: "Larry"}, {name: "Curly"}, {name: "Shemp"}] },
handlebars: "{{#each names}}{{@index}}{{name}}{{/each}}"
}
+9
View File
@@ -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);
});
+4
View File
@@ -0,0 +1,4 @@
module.exports = {
context: { person: { name: "Larry", age: 45 } },
handlebars: "{{#person}}{{name}}{{age}}{{/person}}"
};
+7
View File
@@ -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}}"
};
+10
View File
@@ -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}}"
};
+11
View File
@@ -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}}"
};
+7
View File
@@ -0,0 +1,7 @@
module.exports = {
context: { person: { name: "Larry", age: 45 } },
handlebars: "{{person.name}}{{person.age}}{{person.foo}}{{animal.age}}",
dust: "{person.name}{person.age}{person.foo}{animal.age}",
eco: "<%= @person.name %><%= @person.age %><%= @person.foo %><% if @animal: %><%= @animal.age %><% end %>",
mustache: "{{person.name}}{{person.age}}{{person.foo}}{{animal.age}}"
};
+7
View File
@@ -0,0 +1,7 @@
module.exports = {
context: {},
handlebars: "Hello world",
dust: "Hello world",
mustache: "Hello world",
eco: "Hello world"
};
+14
View File
@@ -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;
+8
View File
@@ -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."
};
+123
View File
@@ -0,0 +1,123 @@
var _ = require('underscore'),
runner = require('./util/template-runner'),
templates = require('./templates'),
eco, dust, Handlebars, Mustache, eco;
try {
dust = require("dustjs-linkedin");
} catch (err) { /* NOP */ }
try {
Mustache = require("mustache");
} catch (err) { /* NOP */ }
try {
eco = require("eco");
} catch (err) { /* NOP */ }
function error() {
throw new Error("EWOT");
}
function makeSuite(bench, name, template, handlebarsOnly) {
// Create aliases to minimize any impact from having to walk up the closure tree.
var templateName = name,
context = template.context,
partials = template.partials,
handlebarsOut,
dustOut,
ecoOut,
mustacheOut;
var handlebar = Handlebars.compile(template.handlebars, {data: false}),
options = {helpers: template.helpers};
_.each(template.partials && template.partials.handlebars, function(partial, name) {
Handlebars.registerPartial(name, Handlebars.compile(partial, {data: false}));
});
handlebarsOut = handlebar(context, options);
bench("handlebars", function() {
handlebar(context, options);
});
if (handlebarsOnly) {
return;
}
if (dust) {
if (template.dust) {
dustOut = false;
dust.loadSource(dust.compile(template.dust, templateName));
dust.render(templateName, context, function(err, out) { dustOut = out; });
bench("dust", function() {
dust.render(templateName, context, function(err, out) { });
});
} else {
bench('dust', error);
}
}
if (eco) {
if (template.eco) {
var ecoTemplate = eco.compile(template.eco);
ecoOut = ecoTemplate(context);
bench("eco", function() {
ecoTemplate(context);
});
} else {
bench("eco", error);
}
}
if (Mustache) {
var mustacheSource = template.mustache,
mustachePartials = partials && partials.mustache;
if (mustacheSource) {
mustacheOut = Mustache.to_html(mustacheSource, context, mustachePartials);
bench("mustache", function() {
Mustache.to_html(mustacheSource, context, mustachePartials);
});
} else {
bench("mustache", error);
}
}
// Hack around whitespace until we have whitespace control
handlebarsOut = handlebarsOut.replace(/\s/g, '');
function compare(b, lang) {
if (b == null) {
return;
}
b = b.replace(/\s/g, '');
if (handlebarsOut !== b) {
throw new Error('Template output mismatch: ' + name
+ '\n\nHandlebars: ' + handlebarsOut
+ '\n\n' + lang + ': ' + b);
}
}
compare(dustOut, 'dust');
compare(ecoOut, 'eco');
compare(mustacheOut, 'mustache');
}
module.exports = function(grunt, callback) {
// Deferring load incase we are being run inline with the grunt build
Handlebars = require('../lib');
console.log('Execution Throughput');
runner(grunt, makeSuite, function(times, scaled) {
callback(scaled);
});
};
+199
View File
@@ -0,0 +1,199 @@
var _ = require('underscore'),
Benchmark = require("benchmark");
var BenchWarmer = function(names) {
this.benchmarks = [];
this.currentBenches = [];
this.names = [];
this.times = {};
this.minimum = Infinity;
this.maximum = -Infinity;
this.errors = {};
};
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;
+27
View File
@@ -0,0 +1,27 @@
var _ = require('underscore'),
BenchWarmer = require('./benchwarmer'),
templates = require('../templates');
module.exports = function(grunt, makeSuite, callback) {
var warmer = new BenchWarmer();
var handlebarsOnly = grunt.option('handlebars-only'),
grep = grunt.option('grep');
if (grep) {
grep = new RegExp(grep);
}
_.each(templates, function(template, name) {
if (!template.handlebars || (grep && !grep.test(name))) {
return;
}
warmer.suite(name, function(bench) {
makeSuite(bench, name, template, handlebarsOnly);
});
});
warmer.bench(function() {
callback && callback(warmer.times, warmer.scaled);
});
};
Executable
+222
View File
@@ -0,0 +1,222 @@
#!/usr/bin/env node
var optimist = require('optimist')
.usage('Precompile handlebar templates.\nUsage: $0 template...', {
'f': {
'type': 'string',
'description': 'Output File',
'alias': 'output'
},
'a': {
'type': 'boolean',
'description': 'Exports amd style (require.js)',
'alias': 'amd'
},
'c': {
'type': 'string',
'description': 'Exports CommonJS style, path to Handlebars module',
'alias': 'commonjs',
'default': null
},
'h': {
'type': 'string',
'description': 'Path to handlebar.js (only valid for amd-style)',
'alias': 'handlebarPath',
'default': ''
},
'k': {
'type': 'string',
'description': 'Known helpers',
'alias': 'known'
},
'o': {
'type': 'boolean',
'description': 'Known helpers only',
'alias': 'knownOnly'
},
'm': {
'type': 'boolean',
'description': 'Minimize output',
'alias': 'min'
},
'n': {
'type': 'string',
'description': 'Template namespace',
'alias': 'namespace',
'default': 'Handlebars.templates'
},
's': {
'type': 'boolean',
'description': 'Output template function only.',
'alias': 'simple'
},
'r': {
'type': 'string',
'description': 'Template root. Base value that will be stripped from template names.',
'alias': 'root'
},
'p' : {
'type': 'boolean',
'description': 'Compiling a partial template',
'alias': 'partial'
},
'd' : {
'type': 'boolean',
'description': 'Include data when compiling',
'alias': 'data'
},
'e': {
'type': 'string',
'description': 'Template extension.',
'alias': 'extension',
'default': 'handlebars'
},
'b': {
'type': 'boolean',
'description': 'Removes the BOM (Byte Order Mark) from the beginning of the templates.',
'alias': 'bom'
}
})
.check(function(argv) {
var template = [0];
if (!argv._.length) {
throw 'Must define at least one template or directory.';
}
argv._.forEach(function(template) {
try {
fs.statSync(template);
} catch (err) {
throw 'Unable to open template file "' + template + '"';
}
});
})
.check(function(argv) {
if (argv.simple && argv.min) {
throw 'Unable to minimze simple output';
}
if (argv.simple && (argv._.length !== 1 || fs.statSync(argv._[0]).isDirectory())) {
throw 'Unable to output multiple templates in simple mode';
}
});
var fs = require('fs'),
handlebars = require('../lib'),
basename = require('path').basename,
uglify = require('uglify-js');
var argv = optimist.argv,
template = argv._[0];
// Convert the known list into a hash
var known = {};
if (argv.known && !Array.isArray(argv.known)) {
argv.known = [argv.known];
}
if (argv.known) {
for (var i = 0, len = argv.known.length; i < len; i++) {
known[argv.known[i]] = true;
}
}
// Build file extension pattern
var extension = argv.extension.replace(/[\\^$*+?.():=!|{}\-\[\]]/g, function(arg) { return '\\' + arg; });
extension = new RegExp('\\.' + extension + '$');
var output = [];
if (!argv.simple) {
if (argv.amd) {
output.push('define([\'' + argv.handlebarPath + 'handlebars.runtime\'], function(Handlebars) {\n Handlebars = Handlebars["default"];');
} else if (argv.commonjs) {
output.push('var Handlebars = require("' + argv.commonjs + '");');
} else {
output.push('(function() {\n');
}
output.push(' var template = Handlebars.template, templates = ');
output.push(argv.namespace);
output.push(' = ');
output.push(argv.namespace);
output.push(' || {};\n');
}
function processTemplate(template, root) {
var path = template,
stat = fs.statSync(path);
if (stat.isDirectory()) {
fs.readdirSync(template).map(function(file) {
var path = template + '/' + file;
if (extension.test(path) || fs.statSync(path).isDirectory()) {
processTemplate(path, root || template);
}
});
} else if (extension.test(path)) {
var data = fs.readFileSync(path, 'utf8');
if (argv.bom && data.indexOf('\uFEFF') === 0) {
data = data.substring(1);
}
var options = {
knownHelpers: known,
knownHelpersOnly: argv.o
};
if (argv.data) {
options.data = true;
}
// Clean the template name
if (!root) {
template = basename(template);
} else if (template.indexOf(root) === 0) {
template = template.substring(root.length+1);
}
template = template.replace(extension, '');
if (argv.simple) {
output.push(handlebars.precompile(data, options) + '\n');
} else if (argv.partial) {
if(argv.amd && (argv._.length == 1 && !fs.statSync(argv._[0]).isDirectory())) {
output.push('return ');
}
output.push('Handlebars.partials[\'' + template + '\'] = template(' + handlebars.precompile(data, options) + ');\n');
} else {
if(argv.amd && (argv._.length == 1 && !fs.statSync(argv._[0]).isDirectory())) {
output.push('return ');
}
output.push('templates[\'' + template + '\'] = template(' + handlebars.precompile(data, options) + ');\n');
}
}
}
argv._.forEach(function(template) {
processTemplate(template, argv.root);
});
// Output the content
if (!argv.simple) {
if (argv.amd) {
if(argv._.length > 1 || (argv._.length == 1 && fs.statSync(argv._[0]).isDirectory())) {
if(argv.partial){
output.push('return Handlebars.partials;\n');
} else {
output.push('return templates;\n');
}
}
output.push('});');
} else if (!argv.commonjs) {
output.push('})();');
}
}
output = output.join('');
if (argv.min) {
output = uglify.minify(output, {fromString: true}).code;
}
if (argv.output) {
fs.writeFileSync(argv.output, output, 'utf8');
} else {
console.log(output);
}
+6
View File
@@ -0,0 +1,6 @@
{
"name": "handlebars",
"version": "1.3.0",
"main": "handlebars.js",
"dependencies": {}
}
+9
View File
@@ -0,0 +1,9 @@
{
"name": "handlebars",
"repo": "components/handlebars.js",
"version": "1.0.0",
"main": "handlebars.js",
"scripts": [
"handlebars.js"
]
}
+35
View File
@@ -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"
}
}
}
}
+21
View File
@@ -0,0 +1,21 @@
# -*- 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"]
gem.files = [
'handlebars.js',
'handlebars.runtime.js',
'lib/handlebars/source.rb'
]
end
+17
View File
@@ -0,0 +1,17 @@
<?xml version="1.0"?>
<package>
<metadata>
<id>handlebars.js</id>
<version>1.3.0</version>
<authors>handlebars.js Authors</authors>
<licenseUrl>https://github.com/wycats/handlebars.js/blob/master/LICENSE</licenseUrl>
<projectUrl>https://github.com/wycats/handlebars.js/</projectUrl>
<requireLicenseAcceptance>false</requireLicenseAcceptance>
<description>Extension of the Mustache logicless template language</description>
<releaseNotes></releaseNotes>
<tags>handlebars mustache template html</tags>
</metadata>
<files>
<file src="handlebars.js" target="Content\Scripts" />
</files>
</package>
+11
View File
@@ -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
+27 -9
View File
@@ -1,15 +1,33 @@
var Handlebars = require("handlebars/base");
module.exports = Handlebars;
/*globals Handlebars: true */
import Handlebars from "./handlebars.runtime";
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";
require("handlebars/ast");
require("handlebars/printer");
require("handlebars/visitor");
var _create = Handlebars.create;
var create = function() {
var hb = _create();
require("handlebars/compiler");
hb.compile = function(input, options) {
return compile(input, options, hb);
};
hb.precompile = function (input, options) {
return precompile(input, options, hb);
};
// BEGIN(BROWSER)
hb.AST = AST;
hb.Compiler = Compiler;
hb.JavaScriptCompiler = JavaScriptCompiler;
hb.Parser = Parser;
hb.parse = parse;
// END(BROWSER)
return hb;
};
Handlebars = create();
Handlebars.create = create;
export default Handlebars;
+31
View File
@@ -0,0 +1,31 @@
/*globals Handlebars: true */
module base from "./handlebars/base";
// Each of these augment the Handlebars object. No need to setup here.
// (This is done to easily share code between commonjs and browse envs)
import SafeString from "./handlebars/safe-string";
import Exception from "./handlebars/exception";
module Utils from "./handlebars/utils";
module runtime from "./handlebars/runtime";
// For compatibility and usage outside of module systems, make the Handlebars object a namespace
var create = function() {
var hb = new base.HandlebarsEnvironment();
Utils.extend(hb, base);
hb.SafeString = SafeString;
hb.Exception = Exception;
hb.Utils = Utils;
hb.VM = runtime;
hb.template = function(spec) {
return runtime.template(spec, hb);
};
return hb;
};
var Handlebars = create();
Handlebars.create = create;
export default Handlebars;
-93
View File
@@ -1,93 +0,0 @@
var Handlebars = require("handlebars");
// BEGIN(BROWSER)
(function() {
Handlebars.AST = {};
Handlebars.AST.ProgramNode = function(statements, inverse) {
this.type = "program";
this.statements = statements;
if(inverse) { this.inverse = new Handlebars.AST.ProgramNode(inverse); }
};
Handlebars.AST.MustacheNode = function(params, hash, unescaped) {
this.type = "mustache";
this.id = params[0];
this.params = params.slice(1);
this.hash = hash;
this.escaped = !unescaped;
};
Handlebars.AST.PartialNode = function(id, context) {
this.type = "partial";
// TODO: disallow complex IDs
this.id = id;
this.context = context;
};
var verifyMatch = function(open, close) {
if(open.original !== close.original) {
throw new Handlebars.Exception(open.original + " doesn't match " + close.original);
}
};
Handlebars.AST.BlockNode = function(mustache, program, close) {
verifyMatch(mustache.id, close);
this.type = "block";
this.mustache = mustache;
this.program = program;
};
Handlebars.AST.InverseNode = function(mustache, program, close) {
verifyMatch(mustache.id, close);
this.type = "inverse";
this.mustache = mustache;
this.program = program;
};
Handlebars.AST.ContentNode = function(string) {
this.type = "content";
this.string = string;
};
Handlebars.AST.HashNode = function(pairs) {
this.type = "hash";
this.pairs = pairs;
};
Handlebars.AST.IdNode = function(parts) {
this.type = "ID";
this.original = parts.join(".");
var dig = [], depth = 0;
for(var i=0,l=parts.length; i<l; i++) {
var part = parts[i];
if(part === "..") { depth++; }
else if(part === "." || part === "this") { continue; }
else { dig.push(part); }
}
this.parts = dig;
this.string = dig.join('.');
this.depth = depth;
this.isSimple = (dig.length === 1) && (depth === 0);
};
Handlebars.AST.StringNode = function(string) {
this.type = "STRING";
this.string = string;
};
Handlebars.AST.CommentNode = function(comment) {
this.type = "comment";
this.comment = comment;
};
})();
// END(BROWSER)
+153 -85
View File
@@ -1,110 +1,178 @@
var handlebars = require("handlebars/parser").parser;
module Utils from "./utils";
import Exception from "./exception";
// BEGIN(BROWSER)
var Handlebars = {};
export var VERSION = "1.3.0";
export var COMPILER_REVISION = 4;
Handlebars.VERSION = "1.0.beta.1";
Handlebars.Parser = handlebars;
Handlebars.parse = function(string) {
Handlebars.Parser.yy = Handlebars.AST;
return Handlebars.Parser.parse(string);
export var REVISION_CHANGES = {
1: '<= 1.0.rc.2', // 1.0.rc.2 is actually rev2 but doesn't report it
2: '== 1.0.0-rc.3',
3: '== 1.0.0-rc.4',
4: '>= 1.0.0'
};
Handlebars.print = function(ast) {
return new Handlebars.PrintVisitor().accept(ast);
};
var isArray = Utils.isArray,
isFunction = Utils.isFunction,
toString = Utils.toString,
objectType = '[object Object]';
Handlebars.helpers = {};
Handlebars.partials = {};
export function HandlebarsEnvironment(helpers, partials) {
this.helpers = helpers || {};
this.partials = partials || {};
Handlebars.registerHelper = function(name, fn, inverse) {
if(inverse) { fn.not = inverse; }
this.helpers[name] = fn;
};
registerDefaultHelpers(this);
}
Handlebars.registerPartial = function(name, str) {
this.partials[name] = str;
};
HandlebarsEnvironment.prototype = {
constructor: HandlebarsEnvironment,
Handlebars.registerHelper('helperMissing', function(arg) {
if(arguments.length === 2) {
return undefined;
} else {
throw new Error("Could not find property '" + arg + "'");
logger: logger,
log: log,
registerHelper: function(name, fn, inverse) {
if (toString.call(name) === objectType) {
if (inverse || fn) { throw new Exception('Arg not supported with multiple helpers'); }
Utils.extend(this.helpers, name);
} else {
if (inverse) { fn.not = inverse; }
this.helpers[name] = fn;
}
},
registerPartial: function(name, str) {
if (toString.call(name) === objectType) {
Utils.extend(this.partials, name);
} else {
this.partials[name] = str;
}
}
});
};
Handlebars.registerHelper('blockHelperMissing', function(context, fn, inverse) {
inverse = inverse || function() {};
function registerDefaultHelpers(instance) {
instance.registerHelper('helperMissing', function(arg) {
if(arguments.length === 2) {
return undefined;
} else {
throw new Exception("Missing helper: '" + arg + "'");
}
});
var ret = "";
var type = Object.prototype.toString.call(context);
instance.registerHelper('blockHelperMissing', function(context, options) {
var inverse = options.inverse || function() {}, fn = options.fn;
if(type === "[object Function]") {
context = context();
}
if (isFunction(context)) { 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]);
if(context === true) {
return fn(this);
} else if(context === false || context == null) {
return inverse(this);
} else if (isArray(context)) {
if(context.length > 0) {
return instance.helpers.each(context, options);
} else {
return inverse(this);
}
} else {
return fn(context);
}
});
instance.registerHelper('each', function(context, options) {
var fn = options.fn, inverse = options.inverse;
var i = 0, ret = "", data;
if (isFunction(context)) { context = context.call(this); }
if (options.data) {
data = createFrame(options.data);
}
if(context && typeof context === 'object') {
if (isArray(context)) {
for(var j = context.length; i<j; i++) {
if (data) {
data.index = i;
data.first = (i === 0);
data.last = (i === (context.length-1));
}
ret = ret + fn(context[i], { data: data });
}
} else {
for(var key in context) {
if(context.hasOwnProperty(key)) {
if(data) {
data.key = key;
data.index = i;
data.first = (i === 0);
}
ret = ret + fn(context[key], {data: data});
i++;
}
}
}
}
if(i === 0){
ret = inverse(this);
}
return ret;
} else {
return fn(context);
}
}, function(context, fn) {
return fn(context);
});
});
Handlebars.registerHelper('each', function(context, fn, inverse) {
var ret = "";
instance.registerHelper('if', function(conditional, options) {
if (isFunction(conditional)) { conditional = conditional.call(this); }
if(context && context.length > 0) {
for(var i=0, j=context.length; i<j; i++) {
ret = ret + fn(context[i]);
// Default behavior is to render the positive path if the value is truthy and not empty.
// The `includeZero` option may be set to treat the condtional as purely not empty based on the
// behavior of isEmpty. Effectively this determines if 0 is handled by the positive path or negative.
if ((!options.hash.includeZero && !conditional) || Utils.isEmpty(conditional)) {
return options.inverse(this);
} else {
return options.fn(this);
}
});
instance.registerHelper('unless', function(conditional, options) {
return instance.helpers['if'].call(this, conditional, {fn: options.inverse, inverse: options.fn, hash: options.hash});
});
instance.registerHelper('with', function(context, options) {
if (isFunction(context)) { context = context.call(this); }
if (!Utils.isEmpty(context)) return options.fn(context);
});
instance.registerHelper('log', function(context, options) {
var level = options.data && options.data.level != null ? parseInt(options.data.level, 10) : 1;
instance.log(level, context);
});
}
export var logger = {
methodMap: { 0: 'debug', 1: 'info', 2: 'warn', 3: 'error' },
// State enum
DEBUG: 0,
INFO: 1,
WARN: 2,
ERROR: 3,
level: 3,
// can be overridden in the host environment
log: function(level, obj) {
if (logger.level <= level) {
var method = logger.methodMap[level];
if (typeof console !== 'undefined' && console[method]) {
console[method].call(console, obj);
}
}
} else {
ret = inverse(this);
}
return ret;
});
Handlebars.registerHelper('if', function(context, fn, inverse) {
if(!context || context == []) {
return inverse(this);
} else {
return fn(this);
}
});
Handlebars.registerHelper('unless', function(context, fn, inverse) {
return Handlebars.helpers['if'].call(this, context, inverse, fn);
});
Handlebars.registerHelper('with', function(context, fn) {
return fn(context);
});
Handlebars.logger = {
DEBUG: 0, INFO: 1, WARN: 2, ERROR: 3, level: 3,
// override in the host environment
log: function(level, str) {}
};
Handlebars.log = function(level, str) { Handlebars.logger.log(level, str); };
// END(BROWSER)
module.exports = Handlebars;
export function log(level, obj) { logger.log(level, obj); }
export var createFrame = function(object) {
var obj = {};
Utils.extend(obj, object);
return obj;
};
-736
View File
@@ -1,736 +0,0 @@
var Handlebars = require("handlebars");
// BEGIN(BROWSER)
Handlebars.Compiler = function() {};
Handlebars.JavaScriptCompiler = function() {};
(function(Compiler, JavaScriptCompiler) {
Compiler.OPCODE_MAP = {
appendContent: 1,
getContext: 2,
lookupWithHelpers: 3,
lookup: 4,
append: 5,
invokeMustache: 6,
appendEscaped: 7,
pushString: 8,
truthyOrFallback: 9,
functionOrFallback: 10,
invokeProgram: 11,
invokePartial: 12,
push: 13,
invokeInverse: 14,
assignToHash: 15,
pushStringParam: 16
};
Compiler.MULTI_PARAM_OPCODES = {
appendContent: 1,
getContext: 1,
lookupWithHelpers: 1,
lookup: 1,
invokeMustache: 2,
pushString: 1,
truthyOrFallback: 1,
functionOrFallback: 1,
invokeProgram: 2,
invokePartial: 1,
push: 1,
invokeInverse: 1,
assignToHash: 1,
pushStringParam: 1
};
Compiler.DISASSEMBLE_MAP = {};
for(var prop in Compiler.OPCODE_MAP) {
var value = Compiler.OPCODE_MAP[prop];
Compiler.DISASSEMBLE_MAP[value] = prop;
}
Compiler.multiParamSize = function(code) {
return Compiler.MULTI_PARAM_OPCODES[Compiler.DISASSEMBLE_MAP[code]];
};
Compiler.prototype = {
compiler: Compiler,
disassemble: function() {
var opcodes = this.opcodes, opcode, nextCode;
var out = [], str, name, value;
for(var i=0, l=opcodes.length; i<l; i++) {
opcode = opcodes[i];
if(opcode === 'DECLARE') {
name = opcodes[++i];
value = opcodes[++i];
out.push("DECLARE " + name + " = " + value);
} else {
str = Compiler.DISASSEMBLE_MAP[opcode];
var extraParams = Compiler.multiParamSize(opcode);
var codes = [];
for(var j=0; j<extraParams; j++) {
nextCode = opcodes[++i];
if(typeof nextCode === "string") {
nextCode = "\"" + nextCode.replace("\n", "\\n") + "\"";
}
codes.push(nextCode);
}
str = str + " " + codes.join(" ");
out.push(str);
}
}
return out.join("\n");
},
guid: 0,
compile: function(program, options) {
this.children = [];
this.depths = {list: []};
this.options = options || {};
return this.program(program);
},
accept: function(node) {
return this[node.type](node);
},
program: function(program) {
var statements = program.statements, statement;
this.opcodes = [];
for(var i=0, l=statements.length; i<l; i++) {
statement = statements[i];
this[statement.type](statement);
}
this.depths.list = this.depths.list.sort(function(a, b) {
return a - b;
});
return this;
},
compileProgram: function(program) {
var result = new this.compiler().compile(program, this.options);
var guid = this.guid++;
this.usePartial = this.usePartial || result.usePartial;
this.children[guid] = result;
for(var i=0, l=result.depths.list.length; i<l; i++) {
depth = result.depths.list[i];
if(depth < 2) { continue; }
else { this.addDepth(depth - 1); }
}
return guid;
},
block: function(block) {
var mustache = block.mustache;
var depth, child, inverse, inverseGuid;
var params = this.setupStackForMustache(mustache);
var programGuid = this.compileProgram(block.program);
if(block.program.inverse) {
inverseGuid = this.compileProgram(block.program.inverse);
this.declare('inverse', inverseGuid);
}
this.opcode('invokeProgram', programGuid, params.length);
this.declare('inverse', null);
this.opcode('append');
},
inverse: function(block) {
this.ID(block.mustache.id);
var programGuid = this.compileProgram(block.program);
this.opcode('invokeInverse', programGuid);
this.opcode('append');
},
hash: function(hash) {
var pairs = hash.pairs, pair, val;
this.opcode('push', '{}');
for(var i=0, l=pairs.length; i<l; i++) {
pair = pairs[i];
val = pair[1];
this.accept(val);
this.opcode('assignToHash', pair[0]);
}
},
partial: function(partial) {
var id = partial.id;
this.usePartial = true;
if(partial.context) {
this.ID(partial.context);
} else {
this.opcode('push', 'context');
}
this.opcode('invokePartial', id.original);
this.opcode('append');
},
content: function(content) {
this.opcode('appendContent', content.string);
},
mustache: function(mustache) {
var params = this.setupStackForMustache(mustache);
this.opcode('invokeMustache', params.length, mustache.id.original);
if(mustache.escaped) {
this.opcode('appendEscaped');
} else {
this.opcode('append');
}
},
ID: function(id) {
this.addDepth(id.depth);
this.opcode('getContext', id.depth);
this.opcode('lookupWithHelpers', id.parts[0] || null);
for(var i=1, l=id.parts.length; i<l; i++) {
this.opcode('lookup', id.parts[i]);
}
},
STRING: function(string) {
this.opcode('pushString', string.string);
},
comment: function() {},
// HELPERS
pushParams: function(params) {
var i = params.length, param;
while(i--) {
param = params[i];
if(this.options.stringParams) {
if(param.depth) {
this.addDepth(param.depth);
}
this.opcode('getContext', param.depth || 0);
this.opcode('pushStringParam', param.string);
} else {
this[param.type](param);
}
}
},
opcode: function(name, val1, val2) {
this.opcodes.push(Compiler.OPCODE_MAP[name]);
if(val1 !== undefined) { this.opcodes.push(val1); }
if(val2 !== undefined) { this.opcodes.push(val2); }
},
declare: function(name, value) {
this.opcodes.push('DECLARE');
this.opcodes.push(name);
this.opcodes.push(value);
},
addDepth: function(depth) {
if(depth === 0) { return; }
if(!this.depths[depth]) {
this.depths[depth] = true;
this.depths.list.push(depth);
}
},
setupStackForMustache: function(mustache) {
var params = mustache.params;
this.pushParams(params);
if(mustache.hash) {
this.hash(mustache.hash);
} else {
this.opcode('push', '{}');
}
this.ID(mustache.id);
return params;
}
};
JavaScriptCompiler.prototype = {
// PUBLIC API: You can override these methods in a subclass to provide
// alternative compiled forms for name lookup and buffering semantics
nameLookup: function(parent, name, type) {
if(JavaScriptCompiler.RESERVED_WORDS[name] || name.indexOf('-') !== -1) {
return parent + "['" + name + "']";
} else {
return parent + "." + name;
}
},
appendToBuffer: function(string) {
return "buffer = buffer + " + string + ";";
},
initializeBuffer: function() {
return this.quotedString("");
},
// END PUBLIC API
compile: function(environment, options) {
this.environment = environment;
this.options = options || {};
this.preamble();
this.stackSlot = 0;
this.stackVars = [];
this.registers = {list: []};
this.compileChildren(environment, options);
Handlebars.log(Handlebars.logger.DEBUG, environment.disassemble() + "\n\n");
var opcodes = environment.opcodes, opcode, name, declareName, declareVal;
this.i = 0;
for(l=opcodes.length; this.i<l; this.i++) {
opcode = this.nextOpcode(0);
if(opcode[0] === 'DECLARE') {
this.i = this.i + 2;
this[opcode[1]] = opcode[2];
} else {
this.i = this.i + opcode[1].length;
this[opcode[0]].apply(this, opcode[1]);
}
}
return this.createFunction();
},
nextOpcode: function(n) {
var opcodes = this.environment.opcodes, opcode = opcodes[this.i + n], name, val;
var extraParams, codes;
if(opcode === 'DECLARE') {
name = opcodes[this.i + 1];
val = opcodes[this.i + 2];
return ['DECLARE', name, val];
} else {
name = Compiler.DISASSEMBLE_MAP[opcode];
extraParams = Compiler.multiParamSize(opcode);
codes = [];
for(var j=0; j<extraParams; j++) {
codes.push(opcodes[this.i + j + 1 + n]);
}
return [name, codes];
}
},
eat: function(opcode) {
this.i = this.i + opcode.length;
},
preamble: function() {
var out = [];
out.push("var buffer = " + this.initializeBuffer() + ", currentContext = context");
var copies = "helpers = helpers || Handlebars.helpers;";
if(this.environment.usePartial) { copies = copies + " partials = partials || Handlebars.partials;"; }
out.push(copies);
// track the last context pushed into place to allow skipping the
// getContext opcode when it would be a noop
this.lastContext = 0;
this.source = out;
},
createFunction: function() {
var container = {
escapeExpression: Handlebars.Utils.escapeExpression,
invokePartial: Handlebars.VM.invokePartial,
programs: [],
program: function(i, helpers, partials, data) {
var programWrapper = this.programs[i];
if(data) {
return Handlebars.VM.program(this.children[i], helpers, partials, data);
} else if(programWrapper) {
return programWrapper;
} else {
programWrapper = this.programs[i] = Handlebars.VM.program(this.children[i], helpers, partials);
return programWrapper;
}
},
programWithDepth: Handlebars.VM.programWithDepth,
noop: Handlebars.VM.noop
};
var locals = this.stackVars.concat(this.registers.list);
if(locals.length > 0) {
this.source[0] = this.source[0] + ", " + locals.join(", ");
}
this.source[0] = this.source[0] + ";";
this.source.push("return buffer;");
var params = ["Handlebars", "context", "helpers", "partials"];
if(this.options.data) { params.push("data"); }
for(var i=0, l=this.environment.depths.list.length; i<l; i++) {
params.push("depth" + this.environment.depths.list[i]);
}
if(params.length === 4 && !this.environment.usePartial) { params.pop(); }
params.push(this.source.join("\n"));
var fn = Function.apply(this, params);
fn.displayName = "Handlebars.js";
Handlebars.log(Handlebars.logger.DEBUG, fn.toString() + "\n\n");
container.render = fn;
container.children = this.environment.children;
return function(context, options, $depth) {
try {
options = options || {};
var args = [Handlebars, context, options.helpers, options.partials, options.data];
var depth = Array.prototype.slice.call(arguments, 2);
args = args.concat(depth);
return container.render.apply(container, args);
} catch(e) {
throw e;
}
};
},
appendContent: function(content) {
this.source.push(this.appendToBuffer(this.quotedString(content)));
},
append: function() {
var local = this.popStack();
this.source.push("if(" + local + " || " + local + " === 0) { " + this.appendToBuffer(local) + " }");
},
appendEscaped: function() {
var opcode = this.nextOpcode(1), extra = "";
if(opcode[0] === 'appendContent') {
extra = " + " + this.quotedString(opcode[1][0]);
this.eat(opcode);
}
this.source.push(this.appendToBuffer("this.escapeExpression(" + this.popStack() + ")" + extra));
},
getContext: function(depth) {
if(this.lastContext !== depth) {
this.lastContext = depth;
if(depth === 0) {
this.source.push("currentContext = context;");
} else {
this.source.push("currentContext = depth" + depth + ";");
}
}
},
lookupWithHelpers: function(name) {
if(name) {
var topStack = this.nextStack();
var toPush = "if('" + name + "' in helpers) { " + topStack +
" = " + this.nameLookup('helpers', name, 'helper') +
"; } else { " + topStack + " = " +
this.nameLookup('currentContext', name, 'context') +
"; }";
this.source.push(toPush);
} else {
this.pushStack("currentContext");
}
},
lookup: function(name) {
var topStack = this.topStack();
this.source.push(topStack + " = " + this.nameLookup(topStack, name, 'context') + ";");
},
pushStringParam: function(string) {
this.pushStack("currentContext");
this.pushString(string);
},
pushString: function(string) {
this.pushStack(this.quotedString(string));
},
push: function(name) {
this.pushStack(name);
},
invokeMustache: function(paramSize, original) {
this.populateParams(paramSize, this.quotedString(original), "{}", null, function(nextStack, helperMissingString, id) {
this.source.push("else if(" + id + "=== undefined) { " + nextStack + " = helpers.helperMissing.call(" + helperMissingString + "); }");
this.source.push("else { " + nextStack + " = " + id + "; }");
});
},
invokeProgram: function(guid, paramSize) {
var inverse = this.programExpression(this.inverse);
var mainProgram = this.programExpression(guid);
this.populateParams(paramSize, null, mainProgram, inverse, function(nextStack, helperMissingString, id) {
this.source.push("else { " + nextStack + " = helpers.blockHelperMissing.call(" + helperMissingString + "); }");
});
},
populateParams: function(paramSize, helperId, program, inverse, fn) {
var id = this.popStack(), nextStack;
var params = [], param, stringParam;
var hash = this.popStack();
this.register('tmp1', program);
this.source.push('tmp1.hash = ' + hash + ';');
if(this.options.stringParams) {
this.source.push('tmp1.contexts = [];');
}
for(var i=0; i<paramSize; i++) {
param = this.popStack();
params.push(param);
if(this.options.stringParams) {
this.source.push('tmp1.contexts.push(' + this.popStack() + ');');
}
}
if(inverse) {
this.source.push('tmp1.fn = tmp1;');
this.source.push('tmp1.inverse = ' + inverse + ';');
}
if(this.options.data) {
this.source.push('tmp1.data = data;');
}
params.push('tmp1');
// TODO: This is legacy behavior. Deprecate and remove.
if(inverse) {
params.push(inverse);
}
this.populateCall(params, id, helperId || id, fn);
},
populateCall: function(params, id, helperId, fn) {
var paramString = ["context"].concat(params).join(", ");
var helperMissingString = ["context"].concat(helperId).concat(params).join(", ");
nextStack = this.nextStack();
this.source.push("if(typeof " + id + " === 'function') { " + nextStack + " = " + id + ".call(" + paramString + "); }");
fn.call(this, nextStack, helperMissingString, id);
},
invokeInverse: function(guid) {
var program = this.programExpression(guid);
var blockMissingParams = ["context", this.topStack(), "this.noop", program];
this.pushStack("helpers.blockHelperMissing.call(" + blockMissingParams.join(", ") + ")");
},
invokePartial: function(context) {
this.pushStack("this.invokePartial(" + this.nameLookup('partials', context, 'partial') + ", '" + context + "', " + this.popStack() + ", helpers, partials);");
},
assignToHash: function(key) {
var value = this.popStack();
var hash = this.topStack();
this.source.push(hash + "['" + key + "'] = " + value + ";");
},
// HELPERS
compiler: JavaScriptCompiler,
compileChildren: function(environment, options) {
var children = environment.children, child, compiler;
var compiled = [];
for(var i=0, l=children.length; i<l; i++) {
child = children[i];
compiler = new this.compiler();
compiled[i] = compiler.compile(child, options);
}
environment.rawChildren = children;
environment.children = compiled;
},
programExpression: function(guid) {
if(guid == null) { return "this.noop"; }
var programParams = [guid, "helpers", "partials"];
var depths = this.environment.rawChildren[guid].depths.list;
if(this.options.data) { programParams.push("data"); }
for(var i=0, l = depths.length; i<l; i++) {
depth = depths[i];
if(depth === 1) { programParams.push("context"); }
else { programParams.push("depth" + (depth - 1)); }
}
if(!this.environment.usePartial) {
if(programParams[3]) {
programParams[2] = "null";
} else {
programParams.pop();
}
}
if(depths.length === 0) {
return "this.program(" + programParams.join(", ") + ")";
} else {
programParams[0] = "this.children[" + guid + "]";
return "this.programWithDepth(" + programParams.join(", ") + ")";
}
},
register: function(name, val) {
this.useRegister(name);
this.source.push(name + " = " + val + ";");
},
useRegister: function(name) {
if(!this.registers[name]) {
this.registers[name] = true;
this.registers.list.push(name);
}
},
pushStack: function(item) {
this.source.push(this.nextStack() + " = " + item + ";");
return "stack" + this.stackSlot;
},
nextStack: function() {
this.stackSlot++;
if(this.stackSlot > this.stackVars.length) { this.stackVars.push("stack" + this.stackSlot); }
return "stack" + this.stackSlot;
},
popStack: function() {
return "stack" + this.stackSlot--;
},
topStack: function() {
return "stack" + this.stackSlot;
},
quotedString: function(str) {
return '"' + str
.replace(/\\/g, '\\\\')
.replace(/"/g, '\\"')
.replace(/\n/g, '\\n')
.replace(/\r/g, '\\r') + '"';
}
};
var reservedWords = ("break case catch continue default delete do else finally " +
"for function if in instanceof new return switch this throw " +
"try typeof var void while with null true false").split(" ");
compilerWords = JavaScriptCompiler.RESERVED_WORDS = {};
for(var i=0, l=reservedWords.length; i<l; i++) {
compilerWords[reservedWords[i]] = true;
}
})(Handlebars.Compiler, Handlebars.JavaScriptCompiler);
Handlebars.VM = {
programWithDepth: function(fn, passedHelpers, passedPartials, passedData, $depth) {
var args = Array.prototype.slice.call(arguments, 4);
return function(context, helpers, partials, data) {
var options = {
helpers: helpers || passedHelpers,
partials: partials || passedPartials,
data: data || passedData
};
return fn.apply(this, [context, options].concat(args));
};
},
program: function(fn, helpers, partials, data) {
return function(context, h2, p2, d2) {
return fn(context, {helpers: h2 || helpers, partials: p2 || partials, data: d2 || data});
};
},
noop: function() { return ""; },
compile: function(string, options) {
var ast = Handlebars.parse(string);
var environment = new Handlebars.Compiler().compile(ast, options);
return new Handlebars.JavaScriptCompiler().compile(environment, options);
},
invokePartial: function(partial, name, context, helpers, partials) {
if(partial === undefined) {
throw new Handlebars.Exception("The partial " + name + " could not be found");
} else if(partial instanceof Function) {
return partial(context, {helpers: helpers, partials: partials});
} else {
partials[name] = Handlebars.VM.compile(partial);
return partials[name](context, {helpers: helpers, partials: partials});
}
}
};
Handlebars.compile = Handlebars.VM.compile;
// END(BROWSER)
+226
View File
@@ -0,0 +1,226 @@
import Exception from "../exception";
function LocationInfo(locInfo){
locInfo = locInfo || {};
this.firstLine = locInfo.first_line;
this.firstColumn = locInfo.first_column;
this.lastColumn = locInfo.last_column;
this.lastLine = locInfo.last_line;
}
var AST = {
ProgramNode: function(statements, inverseStrip, inverse, locInfo) {
var inverseLocationInfo, firstInverseNode;
if (arguments.length === 3) {
locInfo = inverse;
inverse = null;
} else if (arguments.length === 2) {
locInfo = inverseStrip;
inverseStrip = null;
}
LocationInfo.call(this, locInfo);
this.type = "program";
this.statements = statements;
this.strip = {};
if(inverse) {
firstInverseNode = inverse[0];
if (firstInverseNode) {
inverseLocationInfo = {
first_line: firstInverseNode.firstLine,
last_line: firstInverseNode.lastLine,
last_column: firstInverseNode.lastColumn,
first_column: firstInverseNode.firstColumn
};
this.inverse = new AST.ProgramNode(inverse, inverseStrip, inverseLocationInfo);
} else {
this.inverse = new AST.ProgramNode(inverse, inverseStrip);
}
this.strip.right = inverseStrip.left;
} else if (inverseStrip) {
this.strip.left = inverseStrip.right;
}
},
MustacheNode: function(rawParams, hash, open, strip, locInfo) {
LocationInfo.call(this, locInfo);
this.type = "mustache";
this.strip = strip;
// Open may be a string parsed from the parser or a passed boolean flag
if (open != null && open.charAt) {
// Must use charAt to support IE pre-10
var escapeFlag = open.charAt(3) || open.charAt(2);
this.escaped = escapeFlag !== '{' && escapeFlag !== '&';
} else {
this.escaped = !!open;
}
if (rawParams instanceof AST.SexprNode) {
this.sexpr = rawParams;
} else {
// Support old AST API
this.sexpr = new AST.SexprNode(rawParams, hash);
}
this.sexpr.isRoot = true;
// Support old AST API that stored this info in MustacheNode
this.id = this.sexpr.id;
this.params = this.sexpr.params;
this.hash = this.sexpr.hash;
this.eligibleHelper = this.sexpr.eligibleHelper;
this.isHelper = this.sexpr.isHelper;
},
SexprNode: function(rawParams, hash, locInfo) {
LocationInfo.call(this, locInfo);
this.type = "sexpr";
this.hash = hash;
var id = this.id = rawParams[0];
var params = this.params = rawParams.slice(1);
// a mustache is an eligible helper if:
// * its id is simple (a single part, not `this` or `..`)
var eligibleHelper = this.eligibleHelper = id.isSimple;
// a mustache is definitely a helper if:
// * it is an eligible helper, and
// * it has at least one parameter or hash segment
this.isHelper = eligibleHelper && (params.length || hash);
// if a mustache is an eligible helper but not a definite
// helper, it is ambiguous, and will be resolved in a later
// pass or at runtime.
},
PartialNode: function(partialName, context, strip, locInfo) {
LocationInfo.call(this, locInfo);
this.type = "partial";
this.partialName = partialName;
this.context = context;
this.strip = strip;
},
BlockNode: function(mustache, program, inverse, close, locInfo) {
LocationInfo.call(this, locInfo);
if(mustache.sexpr.id.original !== close.path.original) {
throw new Exception(mustache.sexpr.id.original + " doesn't match " + close.path.original, this);
}
this.type = 'block';
this.mustache = mustache;
this.program = program;
this.inverse = inverse;
this.strip = {
left: mustache.strip.left,
right: close.strip.right
};
(program || inverse).strip.left = mustache.strip.right;
(inverse || program).strip.right = close.strip.left;
if (inverse && !program) {
this.isInverse = true;
}
},
ContentNode: function(string, locInfo) {
LocationInfo.call(this, locInfo);
this.type = "content";
this.string = string;
},
HashNode: function(pairs, locInfo) {
LocationInfo.call(this, locInfo);
this.type = "hash";
this.pairs = pairs;
},
IdNode: function(parts, locInfo) {
LocationInfo.call(this, locInfo);
this.type = "ID";
var original = "",
dig = [],
depth = 0;
for(var i=0,l=parts.length; i<l; i++) {
var part = parts[i].part;
original += (parts[i].separator || '') + part;
if (part === ".." || part === "." || part === "this") {
if (dig.length > 0) {
throw new Exception("Invalid path: " + original, this);
} else if (part === "..") {
depth++;
} else {
this.isScoped = true;
}
} else {
dig.push(part);
}
}
this.original = original;
this.parts = dig;
this.string = dig.join('.');
this.depth = depth;
// an ID is simple if it only has one part, and that part is not
// `..` or `this`.
this.isSimple = parts.length === 1 && !this.isScoped && depth === 0;
this.stringModeValue = this.string;
},
PartialNameNode: function(name, locInfo) {
LocationInfo.call(this, locInfo);
this.type = "PARTIAL_NAME";
this.name = name.original;
},
DataNode: function(id, locInfo) {
LocationInfo.call(this, locInfo);
this.type = "DATA";
this.id = id;
},
StringNode: function(string, locInfo) {
LocationInfo.call(this, locInfo);
this.type = "STRING";
this.original =
this.string =
this.stringModeValue = string;
},
IntegerNode: function(integer, locInfo) {
LocationInfo.call(this, locInfo);
this.type = "INTEGER";
this.original =
this.integer = integer;
this.stringModeValue = Number(integer);
},
BooleanNode: function(bool, locInfo) {
LocationInfo.call(this, locInfo);
this.type = "BOOLEAN";
this.bool = bool;
this.stringModeValue = bool === "true";
},
CommentNode: function(comment, locInfo) {
LocationInfo.call(this, locInfo);
this.type = "comment";
this.comment = comment;
}
};
// Must be exported as an object rather than the root of the module as the jison lexer
// most modify the object to operate properly.
export default AST;
+12
View File
@@ -0,0 +1,12 @@
import parser from "./parser";
import AST from "./ast";
export { parser };
export function parse(input) {
// Just return if an already-compile AST was passed in.
if(input.constructor === AST.ProgramNode) { return input; }
parser.yy = AST;
return parser.parse(input);
}
+466
View File
@@ -0,0 +1,466 @@
import Exception from "../exception";
export function Compiler() {}
// the foundHelper register will disambiguate helper lookup from finding a
// function in a context. This is necessary for mustache compatibility, which
// requires that context functions in blocks are evaluated by blockHelperMissing,
// and then proceed as if the resulting value was provided to blockHelperMissing.
Compiler.prototype = {
compiler: Compiler,
disassemble: function() {
var opcodes = this.opcodes, opcode, out = [], params, param;
for (var i=0, l=opcodes.length; i<l; i++) {
opcode = opcodes[i];
if (opcode.opcode === 'DECLARE') {
out.push("DECLARE " + opcode.name + "=" + opcode.value);
} else {
params = [];
for (var j=0; j<opcode.args.length; j++) {
param = opcode.args[j];
if (typeof param === "string") {
param = "\"" + param.replace("\n", "\\n") + "\"";
}
params.push(param);
}
out.push(opcode.opcode + " " + params.join(" "));
}
}
return out.join("\n");
},
equals: function(other) {
var len = this.opcodes.length;
if (other.opcodes.length !== len) {
return false;
}
for (var i = 0; i < len; i++) {
var opcode = this.opcodes[i],
otherOpcode = other.opcodes[i];
if (opcode.opcode !== otherOpcode.opcode || opcode.args.length !== otherOpcode.args.length) {
return false;
}
for (var j = 0; j < opcode.args.length; j++) {
if (opcode.args[j] !== otherOpcode.args[j]) {
return false;
}
}
}
len = this.children.length;
if (other.children.length !== len) {
return false;
}
for (i = 0; i < len; i++) {
if (!this.children[i].equals(other.children[i])) {
return false;
}
}
return true;
},
guid: 0,
compile: function(program, options) {
this.opcodes = [];
this.children = [];
this.depths = {list: []};
this.options = options;
// These changes will propagate to the other compiler components
var knownHelpers = this.options.knownHelpers;
this.options.knownHelpers = {
'helperMissing': true,
'blockHelperMissing': true,
'each': true,
'if': true,
'unless': true,
'with': true,
'log': true
};
if (knownHelpers) {
for (var name in knownHelpers) {
this.options.knownHelpers[name] = knownHelpers[name];
}
}
return this.accept(program);
},
accept: function(node) {
var strip = node.strip || {},
ret;
if (strip.left) {
this.opcode('strip');
}
ret = this[node.type](node);
if (strip.right) {
this.opcode('strip');
}
return ret;
},
program: function(program) {
var statements = program.statements;
for(var i=0, l=statements.length; i<l; i++) {
this.accept(statements[i]);
}
this.isSimple = l === 1;
this.depths.list = this.depths.list.sort(function(a, b) {
return a - b;
});
return this;
},
compileProgram: function(program) {
var result = new this.compiler().compile(program, this.options);
var guid = this.guid++, depth;
this.usePartial = this.usePartial || result.usePartial;
this.children[guid] = result;
for(var i=0, l=result.depths.list.length; i<l; i++) {
depth = result.depths.list[i];
if(depth < 2) { continue; }
else { this.addDepth(depth - 1); }
}
return guid;
},
block: function(block) {
var mustache = block.mustache,
program = block.program,
inverse = block.inverse;
if (program) {
program = this.compileProgram(program);
}
if (inverse) {
inverse = this.compileProgram(inverse);
}
var sexpr = mustache.sexpr;
var type = this.classifySexpr(sexpr);
if (type === "helper") {
this.helperSexpr(sexpr, program, inverse);
} else if (type === "simple") {
this.simpleSexpr(sexpr);
// now that the simple mustache is resolved, we need to
// evaluate it by executing `blockHelperMissing`
this.opcode('pushProgram', program);
this.opcode('pushProgram', inverse);
this.opcode('emptyHash');
this.opcode('blockValue');
} else {
this.ambiguousSexpr(sexpr, program, inverse);
// now that the simple mustache is resolved, we need to
// evaluate it by executing `blockHelperMissing`
this.opcode('pushProgram', program);
this.opcode('pushProgram', inverse);
this.opcode('emptyHash');
this.opcode('ambiguousBlockValue');
}
this.opcode('append');
},
hash: function(hash) {
var pairs = hash.pairs, pair, val;
this.opcode('pushHash');
for(var i=0, l=pairs.length; i<l; i++) {
pair = pairs[i];
val = pair[1];
if (this.options.stringParams) {
if(val.depth) {
this.addDepth(val.depth);
}
this.opcode('getContext', val.depth || 0);
this.opcode('pushStringParam', val.stringModeValue, val.type);
if (val.type === 'sexpr') {
// Subexpressions get evaluated and passed in
// in string params mode.
this.sexpr(val);
}
} else {
this.accept(val);
}
this.opcode('assignToHash', pair[0]);
}
this.opcode('popHash');
},
partial: function(partial) {
var partialName = partial.partialName;
this.usePartial = true;
if(partial.context) {
this.ID(partial.context);
} else {
this.opcode('push', 'depth0');
}
this.opcode('invokePartial', partialName.name);
this.opcode('append');
},
content: function(content) {
this.opcode('appendContent', content.string);
},
mustache: function(mustache) {
this.sexpr(mustache.sexpr);
if(mustache.escaped && !this.options.noEscape) {
this.opcode('appendEscaped');
} else {
this.opcode('append');
}
},
ambiguousSexpr: function(sexpr, program, inverse) {
var id = sexpr.id,
name = id.parts[0],
isBlock = program != null || inverse != null;
this.opcode('getContext', id.depth);
this.opcode('pushProgram', program);
this.opcode('pushProgram', inverse);
this.opcode('invokeAmbiguous', name, isBlock);
},
simpleSexpr: function(sexpr) {
var id = sexpr.id;
if (id.type === 'DATA') {
this.DATA(id);
} else if (id.parts.length) {
this.ID(id);
} else {
// Simplified ID for `this`
this.addDepth(id.depth);
this.opcode('getContext', id.depth);
this.opcode('pushContext');
}
this.opcode('resolvePossibleLambda');
},
helperSexpr: function(sexpr, program, inverse) {
var params = this.setupFullMustacheParams(sexpr, program, inverse),
name = sexpr.id.parts[0];
if (this.options.knownHelpers[name]) {
this.opcode('invokeKnownHelper', params.length, name);
} else if (this.options.knownHelpersOnly) {
throw new Exception("You specified knownHelpersOnly, but used the unknown helper " + name, sexpr);
} else {
this.opcode('invokeHelper', params.length, name, sexpr.isRoot);
}
},
sexpr: function(sexpr) {
var type = this.classifySexpr(sexpr);
if (type === "simple") {
this.simpleSexpr(sexpr);
} else if (type === "helper") {
this.helperSexpr(sexpr);
} else {
this.ambiguousSexpr(sexpr);
}
},
ID: function(id) {
this.addDepth(id.depth);
this.opcode('getContext', id.depth);
var name = id.parts[0];
if (!name) {
this.opcode('pushContext');
} else {
this.opcode('lookupOnContext', id.parts[0]);
}
for(var i=1, l=id.parts.length; i<l; i++) {
this.opcode('lookup', id.parts[i]);
}
},
DATA: function(data) {
this.options.data = true;
if (data.id.isScoped || data.id.depth) {
throw new Exception('Scoped data references are not supported: ' + data.original, data);
}
this.opcode('lookupData');
var parts = data.id.parts;
for(var i=0, l=parts.length; i<l; i++) {
this.opcode('lookup', parts[i]);
}
},
STRING: function(string) {
this.opcode('pushString', string.string);
},
INTEGER: function(integer) {
this.opcode('pushLiteral', integer.integer);
},
BOOLEAN: function(bool) {
this.opcode('pushLiteral', bool.bool);
},
comment: function() {},
// HELPERS
opcode: function(name) {
this.opcodes.push({ opcode: name, args: [].slice.call(arguments, 1) });
},
declare: function(name, value) {
this.opcodes.push({ opcode: 'DECLARE', name: name, value: value });
},
addDepth: function(depth) {
if(depth === 0) { return; }
if(!this.depths[depth]) {
this.depths[depth] = true;
this.depths.list.push(depth);
}
},
classifySexpr: function(sexpr) {
var isHelper = sexpr.isHelper;
var isEligible = sexpr.eligibleHelper;
var options = this.options;
// if ambiguous, we can possibly resolve the ambiguity now
if (isEligible && !isHelper) {
var name = sexpr.id.parts[0];
if (options.knownHelpers[name]) {
isHelper = true;
} else if (options.knownHelpersOnly) {
isEligible = false;
}
}
if (isHelper) { return "helper"; }
else if (isEligible) { return "ambiguous"; }
else { return "simple"; }
},
pushParams: function(params) {
var i = params.length, param;
while(i--) {
param = params[i];
if(this.options.stringParams) {
if(param.depth) {
this.addDepth(param.depth);
}
this.opcode('getContext', param.depth || 0);
this.opcode('pushStringParam', param.stringModeValue, param.type);
if (param.type === 'sexpr') {
// Subexpressions get evaluated and passed in
// in string params mode.
this.sexpr(param);
}
} else {
this[param.type](param);
}
}
},
setupFullMustacheParams: function(sexpr, program, inverse) {
var params = sexpr.params;
this.pushParams(params);
this.opcode('pushProgram', program);
this.opcode('pushProgram', inverse);
if (sexpr.hash) {
this.hash(sexpr.hash);
} else {
this.opcode('emptyHash');
}
return params;
}
};
export function precompile(input, options, env) {
if (input == null || (typeof input !== 'string' && input.constructor !== env.AST.ProgramNode)) {
throw new Exception("You must pass a string or Handlebars AST to Handlebars.precompile. You passed " + input);
}
options = options || {};
if (!('data' in options)) {
options.data = true;
}
var ast = env.parse(input);
var environment = new env.Compiler().compile(ast, options);
return new env.JavaScriptCompiler().compile(environment, options);
}
export function compile(input, options, env) {
if (input == null || (typeof input !== 'string' && input.constructor !== env.AST.ProgramNode)) {
throw new Exception("You must pass a string or Handlebars AST to Handlebars.compile. You passed " + input);
}
options = options || {};
if (!('data' in options)) {
options.data = true;
}
var compiled;
function compileInput() {
var ast = env.parse(input);
var environment = new env.Compiler().compile(ast, options);
var templateSpec = new env.JavaScriptCompiler().compile(environment, options, undefined, true);
return env.template(templateSpec);
}
// Template is only compiled on first use and cached after that point.
return function(context, options) {
if (!compiled) {
compiled = compileInput();
}
return compiled.call(this, context, options);
};
}
@@ -0,0 +1,939 @@
import { COMPILER_REVISION, REVISION_CHANGES, log } from "../base";
import Exception from "../exception";
function Literal(value) {
this.value = value;
}
function JavaScriptCompiler() {}
JavaScriptCompiler.prototype = {
// PUBLIC API: You can override these methods in a subclass to provide
// alternative compiled forms for name lookup and buffering semantics
nameLookup: function(parent, name /* , type*/) {
var wrap,
ret;
if (parent.indexOf('depth') === 0) {
wrap = true;
}
if (/^[0-9]+$/.test(name)) {
ret = parent + "[" + name + "]";
} else if (JavaScriptCompiler.isValidJavaScriptVariableName(name)) {
ret = parent + "." + name;
}
else {
ret = parent + "['" + name + "']";
}
if (wrap) {
return '(' + parent + ' && ' + ret + ')';
} else {
return ret;
}
},
compilerInfo: function() {
var revision = COMPILER_REVISION,
versions = REVISION_CHANGES[revision];
return "this.compilerInfo = ["+revision+",'"+versions+"'];\n";
},
appendToBuffer: function(string) {
if (this.environment.isSimple) {
return "return " + string + ";";
} else {
return {
appendToBuffer: true,
content: string,
toString: function() { return "buffer += " + string + ";"; }
};
}
},
initializeBuffer: function() {
return this.quotedString("");
},
namespace: "Handlebars",
// END PUBLIC API
compile: function(environment, options, context, asObject) {
this.environment = environment;
this.options = options || {};
log('debug', this.environment.disassemble() + "\n\n");
this.name = this.environment.name;
this.isChild = !!context;
this.context = context || {
programs: [],
environments: [],
aliases: { }
};
this.preamble();
this.stackSlot = 0;
this.stackVars = [];
this.registers = { list: [] };
this.hashes = [];
this.compileStack = [];
this.inlineStack = [];
this.compileChildren(environment, options);
var opcodes = environment.opcodes, opcode;
this.i = 0;
for(var l=opcodes.length; this.i<l; this.i++) {
opcode = opcodes[this.i];
if(opcode.opcode === 'DECLARE') {
this[opcode.name] = opcode.value;
} else {
this[opcode.opcode].apply(this, opcode.args);
}
// Reset the stripNext flag if it was not set by this operation.
if (opcode.opcode !== this.stripNext) {
this.stripNext = false;
}
}
// Flush any trailing content that might be pending.
this.pushSource('');
if (this.stackSlot || this.inlineStack.length || this.compileStack.length) {
throw new Exception('Compile completed with content left on stack');
}
return this.createFunctionContext(asObject);
},
preamble: function() {
var out = [];
if (!this.isChild) {
var namespace = this.namespace;
var copies = "helpers = this.merge(helpers, " + namespace + ".helpers);";
if (this.environment.usePartial) { copies = copies + " partials = this.merge(partials, " + namespace + ".partials);"; }
if (this.options.data) { copies = copies + " data = data || {};"; }
out.push(copies);
} else {
out.push('');
}
if (!this.environment.isSimple) {
out.push(", buffer = " + this.initializeBuffer());
} else {
out.push("");
}
// track the last context pushed into place to allow skipping the
// getContext opcode when it would be a noop
this.lastContext = 0;
this.source = out;
},
createFunctionContext: function(asObject) {
var locals = this.stackVars.concat(this.registers.list);
if(locals.length > 0) {
this.source[1] = this.source[1] + ", " + locals.join(", ");
}
// Generate minimizer alias mappings
if (!this.isChild) {
for (var alias in this.context.aliases) {
if (this.context.aliases.hasOwnProperty(alias)) {
this.source[1] = this.source[1] + ', ' + alias + '=' + this.context.aliases[alias];
}
}
}
if (this.source[1]) {
this.source[1] = "var " + this.source[1].substring(2) + ";";
}
// Merge children
if (!this.isChild) {
this.source[1] += '\n' + this.context.programs.join('\n') + '\n';
}
if (!this.environment.isSimple) {
this.pushSource("return buffer;");
}
var params = this.isChild ? ["depth0", "data"] : ["Handlebars", "depth0", "helpers", "partials", "data"];
for(var i=0, l=this.environment.depths.list.length; i<l; i++) {
params.push("depth" + this.environment.depths.list[i]);
}
// Perform a second pass over the output to merge content when possible
var source = this.mergeSource();
if (!this.isChild) {
source = this.compilerInfo()+source;
}
if (asObject) {
params.push(source);
return Function.apply(this, params);
} else {
var functionSource = 'function ' + (this.name || '') + '(' + params.join(',') + ') {\n ' + source + '}';
log('debug', functionSource + "\n\n");
return functionSource;
}
},
mergeSource: function() {
// WARN: We are not handling the case where buffer is still populated as the source should
// not have buffer append operations as their final action.
var source = '',
buffer;
for (var i = 0, len = this.source.length; i < len; i++) {
var line = this.source[i];
if (line.appendToBuffer) {
if (buffer) {
buffer = buffer + '\n + ' + line.content;
} else {
buffer = line.content;
}
} else {
if (buffer) {
source += 'buffer += ' + buffer + ';\n ';
buffer = undefined;
}
source += line + '\n ';
}
}
return source;
},
// [blockValue]
//
// On stack, before: hash, inverse, program, value
// On stack, after: return value of blockHelperMissing
//
// The purpose of this opcode is to take a block of the form
// `{{#foo}}...{{/foo}}`, resolve the value of `foo`, and
// replace it on the stack with the result of properly
// invoking blockHelperMissing.
blockValue: function() {
this.context.aliases.blockHelperMissing = 'helpers.blockHelperMissing';
var params = ["depth0"];
this.setupParams(0, params);
this.replaceStack(function(current) {
params.splice(1, 0, current);
return "blockHelperMissing.call(" + params.join(", ") + ")";
});
},
// [ambiguousBlockValue]
//
// On stack, before: hash, inverse, program, value
// Compiler value, before: lastHelper=value of last found helper, if any
// On stack, after, if no lastHelper: same as [blockValue]
// On stack, after, if lastHelper: value
ambiguousBlockValue: function() {
this.context.aliases.blockHelperMissing = 'helpers.blockHelperMissing';
var params = ["depth0"];
this.setupParams(0, params);
var current = this.topStack();
params.splice(1, 0, current);
this.pushSource("if (!" + this.lastHelper + ") { " + current + " = blockHelperMissing.call(" + params.join(", ") + "); }");
},
// [appendContent]
//
// On stack, before: ...
// On stack, after: ...
//
// Appends the string value of `content` to the current buffer
appendContent: function(content) {
if (this.pendingContent) {
content = this.pendingContent + content;
}
if (this.stripNext) {
content = content.replace(/^\s+/, '');
}
this.pendingContent = content;
},
// [strip]
//
// On stack, before: ...
// On stack, after: ...
//
// Removes any trailing whitespace from the prior content node and flags
// the next operation for stripping if it is a content node.
strip: function() {
if (this.pendingContent) {
this.pendingContent = this.pendingContent.replace(/\s+$/, '');
}
this.stripNext = 'strip';
},
// [append]
//
// On stack, before: value, ...
// On stack, after: ...
//
// Coerces `value` to a String and appends it to the current buffer.
//
// If `value` is truthy, or 0, it is coerced into a string and appended
// Otherwise, the empty string is appended
append: function() {
// Force anything that is inlined onto the stack so we don't have duplication
// when we examine local
this.flushInline();
var local = this.popStack();
this.pushSource("if(" + local + " || " + local + " === 0) { " + this.appendToBuffer(local) + " }");
if (this.environment.isSimple) {
this.pushSource("else { " + this.appendToBuffer("''") + " }");
}
},
// [appendEscaped]
//
// On stack, before: value, ...
// On stack, after: ...
//
// Escape `value` and append it to the buffer
appendEscaped: function() {
this.context.aliases.escapeExpression = 'this.escapeExpression';
this.pushSource(this.appendToBuffer("escapeExpression(" + this.popStack() + ")"));
},
// [getContext]
//
// On stack, before: ...
// On stack, after: ...
// Compiler value, after: lastContext=depth
//
// Set the value of the `lastContext` compiler value to the depth
getContext: function(depth) {
if(this.lastContext !== depth) {
this.lastContext = depth;
}
},
// [lookupOnContext]
//
// On stack, before: ...
// On stack, after: currentContext[name], ...
//
// Looks up the value of `name` on the current context and pushes
// it onto the stack.
lookupOnContext: function(name) {
this.push(this.nameLookup('depth' + this.lastContext, name, 'context'));
},
// [pushContext]
//
// On stack, before: ...
// On stack, after: currentContext, ...
//
// Pushes the value of the current context onto the stack.
pushContext: function() {
this.pushStackLiteral('depth' + this.lastContext);
},
// [resolvePossibleLambda]
//
// On stack, before: value, ...
// On stack, after: resolved value, ...
//
// If the `value` is a lambda, replace it on the stack by
// the return value of the lambda
resolvePossibleLambda: function() {
this.context.aliases.functionType = '"function"';
this.replaceStack(function(current) {
return "typeof " + current + " === functionType ? " + current + ".apply(depth0) : " + current;
});
},
// [lookup]
//
// On stack, before: value, ...
// On stack, after: value[name], ...
//
// Replace the value on the stack with the result of looking
// up `name` on `value`
lookup: function(name) {
this.replaceStack(function(current) {
return current + " == null || " + current + " === false ? " + current + " : " + this.nameLookup(current, name, 'context');
});
},
// [lookupData]
//
// On stack, before: ...
// On stack, after: data, ...
//
// Push the data lookup operator
lookupData: function() {
this.pushStackLiteral('data');
},
// [pushStringParam]
//
// On stack, before: ...
// On stack, after: string, currentContext, ...
//
// This opcode is designed for use in string mode, which
// provides the string value of a parameter along with its
// depth rather than resolving it immediately.
pushStringParam: function(string, type) {
this.pushStackLiteral('depth' + this.lastContext);
this.pushString(type);
// If it's a subexpression, the string result
// will be pushed after this opcode.
if (type !== 'sexpr') {
if (typeof string === 'string') {
this.pushString(string);
} else {
this.pushStackLiteral(string);
}
}
},
emptyHash: function() {
this.pushStackLiteral('{}');
if (this.options.stringParams) {
this.push('{}'); // hashContexts
this.push('{}'); // hashTypes
}
},
pushHash: function() {
if (this.hash) {
this.hashes.push(this.hash);
}
this.hash = {values: [], types: [], contexts: []};
},
popHash: function() {
var hash = this.hash;
this.hash = this.hashes.pop();
if (this.options.stringParams) {
this.push('{' + hash.contexts.join(',') + '}');
this.push('{' + hash.types.join(',') + '}');
}
this.push('{\n ' + hash.values.join(',\n ') + '\n }');
},
// [pushString]
//
// On stack, before: ...
// On stack, after: quotedString(string), ...
//
// Push a quoted version of `string` onto the stack
pushString: function(string) {
this.pushStackLiteral(this.quotedString(string));
},
// [push]
//
// On stack, before: ...
// On stack, after: expr, ...
//
// Push an expression onto the stack
push: function(expr) {
this.inlineStack.push(expr);
return expr;
},
// [pushLiteral]
//
// On stack, before: ...
// On stack, after: value, ...
//
// Pushes a value onto the stack. This operation prevents
// the compiler from creating a temporary variable to hold
// it.
pushLiteral: function(value) {
this.pushStackLiteral(value);
},
// [pushProgram]
//
// On stack, before: ...
// On stack, after: program(guid), ...
//
// Push a program expression onto the stack. This takes
// a compile-time guid and converts it into a runtime-accessible
// expression.
pushProgram: function(guid) {
if (guid != null) {
this.pushStackLiteral(this.programExpression(guid));
} else {
this.pushStackLiteral(null);
}
},
// [invokeHelper]
//
// On stack, before: hash, inverse, program, params..., ...
// On stack, after: result of helper invocation
//
// Pops off the helper's parameters, invokes the helper,
// and pushes the helper's return value onto the stack.
//
// If the helper is not found, `helperMissing` is called.
invokeHelper: function(paramSize, name, isRoot) {
this.context.aliases.helperMissing = 'helpers.helperMissing';
this.useRegister('helper');
var helper = this.lastHelper = this.setupHelper(paramSize, name, true);
var nonHelper = this.nameLookup('depth' + this.lastContext, name, 'context');
var lookup = 'helper = ' + helper.name + ' || ' + nonHelper;
if (helper.paramsInit) {
lookup += ',' + helper.paramsInit;
}
this.push(
'('
+ lookup
+ ',helper '
+ '? helper.call(' + helper.callParams + ') '
+ ': helperMissing.call(' + helper.helperMissingParams + '))');
// Always flush subexpressions. This is both to prevent the compounding size issue that
// occurs when the code has to be duplicated for inlining and also to prevent errors
// due to the incorrect options object being passed due to the shared register.
if (!isRoot) {
this.flushInline();
}
},
// [invokeKnownHelper]
//
// On stack, before: hash, inverse, program, params..., ...
// On stack, after: result of helper invocation
//
// This operation is used when the helper is known to exist,
// so a `helperMissing` fallback is not required.
invokeKnownHelper: function(paramSize, name) {
var helper = this.setupHelper(paramSize, name);
this.push(helper.name + ".call(" + helper.callParams + ")");
},
// [invokeAmbiguous]
//
// On stack, before: hash, inverse, program, params..., ...
// On stack, after: result of disambiguation
//
// This operation is used when an expression like `{{foo}}`
// is provided, but we don't know at compile-time whether it
// is a helper or a path.
//
// This operation emits more code than the other options,
// and can be avoided by passing the `knownHelpers` and
// `knownHelpersOnly` flags at compile-time.
invokeAmbiguous: function(name, helperCall) {
this.context.aliases.functionType = '"function"';
this.useRegister('helper');
this.emptyHash();
var helper = this.setupHelper(0, name, helperCall);
var helperName = this.lastHelper = this.nameLookup('helpers', name, 'helper');
var nonHelper = this.nameLookup('depth' + this.lastContext, name, 'context');
var nextStack = this.nextStack();
if (helper.paramsInit) {
this.pushSource(helper.paramsInit);
}
this.pushSource('if (helper = ' + helperName + ') { ' + nextStack + ' = helper.call(' + helper.callParams + '); }');
this.pushSource('else { helper = ' + nonHelper + '; ' + nextStack + ' = typeof helper === functionType ? helper.call(' + helper.callParams + ') : helper; }');
},
// [invokePartial]
//
// On stack, before: context, ...
// On stack after: result of partial invocation
//
// This operation pops off a context, invokes a partial with that context,
// and pushes the result of the invocation back.
invokePartial: function(name) {
var params = [this.nameLookup('partials', name, 'partial'), "'" + name + "'", this.popStack(), "helpers", "partials"];
if (this.options.data) {
params.push("data");
}
this.context.aliases.self = "this";
this.push("self.invokePartial(" + params.join(", ") + ")");
},
// [assignToHash]
//
// On stack, before: value, hash, ...
// On stack, after: hash, ...
//
// Pops a value and hash off the stack, assigns `hash[key] = value`
// and pushes the hash back onto the stack.
assignToHash: function(key) {
var value = this.popStack(),
context,
type;
if (this.options.stringParams) {
type = this.popStack();
context = this.popStack();
}
var hash = this.hash;
if (context) {
hash.contexts.push("'" + key + "': " + context);
}
if (type) {
hash.types.push("'" + key + "': " + type);
}
hash.values.push("'" + key + "': (" + value + ")");
},
// HELPERS
compiler: JavaScriptCompiler,
compileChildren: function(environment, options) {
var children = environment.children, child, compiler;
for(var i=0, l=children.length; i<l; i++) {
child = children[i];
compiler = new this.compiler();
var index = this.matchExistingProgram(child);
if (index == null) {
this.context.programs.push(''); // Placeholder to prevent name conflicts for nested children
index = this.context.programs.length;
child.index = index;
child.name = 'program' + index;
this.context.programs[index] = compiler.compile(child, options, this.context);
this.context.environments[index] = child;
} else {
child.index = index;
child.name = 'program' + index;
}
}
},
matchExistingProgram: function(child) {
for (var i = 0, len = this.context.environments.length; i < len; i++) {
var environment = this.context.environments[i];
if (environment && environment.equals(child)) {
return i;
}
}
},
programExpression: function(guid) {
this.context.aliases.self = "this";
if(guid == null) {
return "self.noop";
}
var child = this.environment.children[guid],
depths = child.depths.list, depth;
var programParams = [child.index, child.name, "data"];
for(var i=0, l = depths.length; i<l; i++) {
depth = depths[i];
if(depth === 1) { programParams.push("depth0"); }
else { programParams.push("depth" + (depth - 1)); }
}
return (depths.length === 0 ? "self.program(" : "self.programWithDepth(") + programParams.join(", ") + ")";
},
register: function(name, val) {
this.useRegister(name);
this.pushSource(name + " = " + val + ";");
},
useRegister: function(name) {
if(!this.registers[name]) {
this.registers[name] = true;
this.registers.list.push(name);
}
},
pushStackLiteral: function(item) {
return this.push(new Literal(item));
},
pushSource: function(source) {
if (this.pendingContent) {
this.source.push(this.appendToBuffer(this.quotedString(this.pendingContent)));
this.pendingContent = undefined;
}
if (source) {
this.source.push(source);
}
},
pushStack: function(item) {
this.flushInline();
var stack = this.incrStack();
if (item) {
this.pushSource(stack + " = " + item + ";");
}
this.compileStack.push(stack);
return stack;
},
replaceStack: function(callback) {
var prefix = '',
inline = this.isInline(),
stack,
createdStack,
usedLiteral;
// If we are currently inline then we want to merge the inline statement into the
// replacement statement via ','
if (inline) {
var top = this.popStack(true);
if (top instanceof Literal) {
// Literals do not need to be inlined
stack = top.value;
usedLiteral = true;
} else {
// Get or create the current stack name for use by the inline
createdStack = !this.stackSlot;
var name = !createdStack ? this.topStackName() : this.incrStack();
prefix = '(' + this.push(name) + ' = ' + top + '),';
stack = this.topStack();
}
} else {
stack = this.topStack();
}
var item = callback.call(this, stack);
if (inline) {
if (!usedLiteral) {
this.popStack();
}
if (createdStack) {
this.stackSlot--;
}
this.push('(' + prefix + item + ')');
} else {
// Prevent modification of the context depth variable. Through replaceStack
if (!/^stack/.test(stack)) {
stack = this.nextStack();
}
this.pushSource(stack + " = (" + prefix + item + ");");
}
return stack;
},
nextStack: function() {
return this.pushStack();
},
incrStack: function() {
this.stackSlot++;
if(this.stackSlot > this.stackVars.length) { this.stackVars.push("stack" + this.stackSlot); }
return this.topStackName();
},
topStackName: function() {
return "stack" + this.stackSlot;
},
flushInline: function() {
var inlineStack = this.inlineStack;
if (inlineStack.length) {
this.inlineStack = [];
for (var i = 0, len = inlineStack.length; i < len; i++) {
var entry = inlineStack[i];
if (entry instanceof Literal) {
this.compileStack.push(entry);
} else {
this.pushStack(entry);
}
}
}
},
isInline: function() {
return this.inlineStack.length;
},
popStack: function(wrapped) {
var inline = this.isInline(),
item = (inline ? this.inlineStack : this.compileStack).pop();
if (!wrapped && (item instanceof Literal)) {
return item.value;
} else {
if (!inline) {
if (!this.stackSlot) {
throw new Exception('Invalid stack pop');
}
this.stackSlot--;
}
return item;
}
},
topStack: function(wrapped) {
var stack = (this.isInline() ? this.inlineStack : this.compileStack),
item = stack[stack.length - 1];
if (!wrapped && (item instanceof Literal)) {
return item.value;
} else {
return item;
}
},
quotedString: function(str) {
return '"' + str
.replace(/\\/g, '\\\\')
.replace(/"/g, '\\"')
.replace(/\n/g, '\\n')
.replace(/\r/g, '\\r')
.replace(/\u2028/g, '\\u2028') // Per Ecma-262 7.3 + 7.8.4
.replace(/\u2029/g, '\\u2029') + '"';
},
setupHelper: function(paramSize, name, missingParams) {
var params = [],
paramsInit = this.setupParams(paramSize, params, missingParams);
var foundHelper = this.nameLookup('helpers', name, 'helper');
return {
params: params,
paramsInit: paramsInit,
name: foundHelper,
callParams: ["depth0"].concat(params).join(", "),
helperMissingParams: missingParams && ["depth0", this.quotedString(name)].concat(params).join(", ")
};
},
setupOptions: function(paramSize, params) {
var options = [], contexts = [], types = [], param, inverse, program;
options.push("hash:" + this.popStack());
if (this.options.stringParams) {
options.push("hashTypes:" + this.popStack());
options.push("hashContexts:" + this.popStack());
}
inverse = this.popStack();
program = this.popStack();
// Avoid setting fn and inverse if neither are set. This allows
// helpers to do a check for `if (options.fn)`
if (program || inverse) {
if (!program) {
this.context.aliases.self = "this";
program = "self.noop";
}
if (!inverse) {
this.context.aliases.self = "this";
inverse = "self.noop";
}
options.push("inverse:" + inverse);
options.push("fn:" + program);
}
for(var i=0; i<paramSize; i++) {
param = this.popStack();
params.push(param);
if(this.options.stringParams) {
types.push(this.popStack());
contexts.push(this.popStack());
}
}
if (this.options.stringParams) {
options.push("contexts:[" + contexts.join(",") + "]");
options.push("types:[" + types.join(",") + "]");
}
if(this.options.data) {
options.push("data:data");
}
return options;
},
// the params and contexts arguments are passed in arrays
// to fill in
setupParams: function(paramSize, params, useRegister) {
var options = '{' + this.setupOptions(paramSize, params).join(',') + '}';
if (useRegister) {
this.useRegister('options');
params.push('options');
return 'options=' + options;
} else {
params.push(options);
return '';
}
}
};
var reservedWords = (
"break else new var" +
" case finally return void" +
" catch for switch while" +
" continue function this with" +
" default if throw" +
" delete in try" +
" do instanceof typeof" +
" abstract enum int short" +
" boolean export interface static" +
" byte extends long super" +
" char final native synchronized" +
" class float package throws" +
" const goto private transient" +
" debugger implements protected volatile" +
" double import public let yield"
).split(" ");
var compilerWords = JavaScriptCompiler.RESERVED_WORDS = {};
for(var i=0, l=reservedWords.length; i<l; i++) {
compilerWords[reservedWords[i]] = true;
}
JavaScriptCompiler.isValidJavaScriptVariableName = function(name) {
if(!JavaScriptCompiler.RESERVED_WORDS[name] && /^[a-zA-Z_$][0-9a-zA-Z_$]*$/.test(name)) {
return true;
}
return false;
};
export default JavaScriptCompiler;
+138
View File
@@ -0,0 +1,138 @@
import Visitor from "./visitor";
export function print(ast) {
return new PrintVisitor().accept(ast);
}
export function PrintVisitor() {
this.padding = 0;
}
PrintVisitor.prototype = new Visitor();
PrintVisitor.prototype.pad = function(string, newline) {
var out = "";
for(var i=0,l=this.padding; i<l; i++) {
out = out + " ";
}
out = out + string;
if(newline !== false) { out = out + "\n"; }
return out;
};
PrintVisitor.prototype.program = function(program) {
var out = "",
statements = program.statements,
i, l;
for(i=0, l=statements.length; i<l; i++) {
out = out + this.accept(statements[i]);
}
this.padding--;
return out;
};
PrintVisitor.prototype.block = function(block) {
var out = "";
out = out + this.pad("BLOCK:");
this.padding++;
out = out + this.accept(block.mustache);
if (block.program) {
out = out + this.pad("PROGRAM:");
this.padding++;
out = out + this.accept(block.program);
this.padding--;
}
if (block.inverse) {
if (block.program) { this.padding++; }
out = out + this.pad("{{^}}");
this.padding++;
out = out + this.accept(block.inverse);
this.padding--;
if (block.program) { this.padding--; }
}
this.padding--;
return out;
};
PrintVisitor.prototype.sexpr = function(sexpr) {
var params = sexpr.params, paramStrings = [], hash;
for(var i=0, l=params.length; i<l; i++) {
paramStrings.push(this.accept(params[i]));
}
params = "[" + paramStrings.join(", ") + "]";
hash = sexpr.hash ? " " + this.accept(sexpr.hash) : "";
return this.accept(sexpr.id) + " " + params + hash;
};
PrintVisitor.prototype.mustache = function(mustache) {
return this.pad("{{ " + this.accept(mustache.sexpr) + " }}");
};
PrintVisitor.prototype.partial = function(partial) {
var content = this.accept(partial.partialName);
if(partial.context) { content = content + " " + this.accept(partial.context); }
return this.pad("{{> " + content + " }}");
};
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 );
}
return "HASH{" + joinedPairs.join(", ") + "}";
};
PrintVisitor.prototype.STRING = function(string) {
return '"' + string.string + '"';
};
PrintVisitor.prototype.INTEGER = function(integer) {
return "INTEGER{" + integer.integer + "}";
};
PrintVisitor.prototype.BOOLEAN = function(bool) {
return "BOOLEAN{" + bool.bool + "}";
};
PrintVisitor.prototype.ID = function(id) {
var path = id.parts.join("/");
if(id.parts.length > 1) {
return "PATH:" + path;
} else {
return "ID:" + path;
}
};
PrintVisitor.prototype.PARTIAL_NAME = function(partialName) {
return "PARTIAL:" + partialName.name;
};
PrintVisitor.prototype.DATA = function(data) {
return "@" + this.accept(data.id);
};
PrintVisitor.prototype.content = function(content) {
return this.pad("CONTENT[ '" + content.string + "' ]");
};
PrintVisitor.prototype.comment = function(comment) {
return this.pad("{{! '" + comment.comment + "' }}");
};
+11
View File
@@ -0,0 +1,11 @@
function Visitor() {}
Visitor.prototype = {
constructor: Visitor,
accept: function(object) {
return this[object.type](object);
}
};
export default Visitor;
-29
View File
@@ -1,29 +0,0 @@
var Handlebars = require("handlebars");
// BEGIN(BROWSER)
(function() {
var classes = ["Lexer", "PrintVisitor", "Context", "Runtime", "Exception"];
var prop;
for(var i=0, l=classes.length; i<l; i++) {
var className = classes[i], klass = Handlebars[className];
klass.displayName = "new Handlebars." + className;
for(prop in klass) {
if(klass.hasOwnProperty(prop)) {
klass[prop].displayName = "Handlebars." + className + "#" + prop;
}
}
}
for(prop in Handlebars.Utils) {
if(Handlebars.Utils.hasOwnProperty(prop)) {
Handlebars.Utils[prop].displayName = "Handlebars.Utils." + prop;
}
}
Handlebars.parse.displayName = "Handlebars.parse";
Handlebars.print.displayName = "Handlebars.print";
Handlebars.compile.displayName = "Handlebars.compile";
})();
// END(BROWSER)
+27
View File
@@ -0,0 +1,27 @@
var errorProps = ['description', 'fileName', 'lineNumber', 'message', 'name', 'number', 'stack'];
function Exception(message, node) {
var line;
if (node && node.firstLine) {
line = node.firstLine;
message += ' - ' + line + ':' + node.firstColumn;
}
var tmp = Error.prototype.constructor.call(this, message);
// Unfortunately errors are not enumerable in Chrome (at least), so `for prop in tmp` doesn't work.
for (var idx = 0; idx < errorProps.length; idx++) {
this[errorProps[idx]] = tmp[errorProps[idx]];
}
if (line) {
this.lineNumber = line;
this.column = node.firstColumn;
}
}
Exception.prototype = new Error();
export default Exception;
-130
View File
@@ -1,130 +0,0 @@
var Handlebars = require("handlebars");
require("handlebars/visitor");
// BEGIN(BROWSER)
Handlebars.PrintVisitor = function() { this.padding = 0; };
Handlebars.PrintVisitor.prototype = new Handlebars.Visitor();
Handlebars.PrintVisitor.prototype.pad = function(string, newline) {
var out = "";
for(var i=0,l=this.padding; i<l; i++) {
out = out + " ";
}
out = out + string;
if(newline !== false) { out = out + "\n"; }
return out;
};
Handlebars.PrintVisitor.prototype.program = function(program) {
var out = this.pad("PROGRAM:"),
statements = program.statements,
inverse = program.inverse,
i, l;
this.padding++;
for(i=0, l=statements.length; i<l; i++) {
out = out + this.accept(statements[i]);
}
this.padding--;
if(inverse) {
out = out + this.pad("{{^}}");
this.padding++;
for(i=0, l=inverse.statements.length; i<l; i++) {
out = out + this.accept(inverse.statements[i]);
}
}
this.padding--;
return out;
};
Handlebars.PrintVisitor.prototype.block = function(block) {
var out = "";
out = out + this.pad("BLOCK:");
this.padding++;
out = out + this.accept(block.mustache);
out = out + this.accept(block.program);
this.padding--;
return out;
};
Handlebars.PrintVisitor.prototype.inverse = function(block) {
var out = "";
out = out + this.pad("INVERSE:");
this.padding++;
out = out + this.accept(block.mustache);
out = out + this.accept(block.program);
this.padding--;
return out;
};
Handlebars.PrintVisitor.prototype.mustache = function(mustache) {
var params = mustache.params, paramStrings = [], hash;
for(var i=0, l=params.length; i<l; i++) {
paramStrings.push(this.accept(params[i]));
}
params = "[" + paramStrings.join(", ") + "]";
hash = mustache.hash ? " " + this.accept(mustache.hash) : "";
return this.pad("{{ " + this.accept(mustache.id) + " " + 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 + " }}");
};
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 );
}
return "HASH{" + joinedPairs.join(", ") + "}";
};
Handlebars.PrintVisitor.prototype.STRING = function(string) {
return '"' + string.string + '"';
};
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.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;
+133
View File
@@ -0,0 +1,133 @@
module Utils from "./utils";
import Exception from "./exception";
import { COMPILER_REVISION, REVISION_CHANGES } from "./base";
export function checkRevision(compilerInfo) {
var compilerRevision = compilerInfo && compilerInfo[0] || 1,
currentRevision = COMPILER_REVISION;
if (compilerRevision !== currentRevision) {
if (compilerRevision < currentRevision) {
var runtimeVersions = REVISION_CHANGES[currentRevision],
compilerVersions = REVISION_CHANGES[compilerRevision];
throw new Exception("Template was precompiled with an older version of Handlebars than the current runtime. "+
"Please update your precompiler to a newer version ("+runtimeVersions+") or downgrade your runtime to an older version ("+compilerVersions+").");
} else {
// Use the embedded version info since the runtime doesn't know about this revision yet
throw new Exception("Template was precompiled with a newer version of Handlebars than the current runtime. "+
"Please update your runtime to a newer version ("+compilerInfo[1]+").");
}
}
}
// TODO: Remove this line and break up compilePartial
export function template(templateSpec, env) {
if (!env) {
throw new Exception("No environment passed to template");
}
// Note: Using env.VM references rather than local var references throughout this section to allow
// for external users to override these as psuedo-supported APIs.
var invokePartialWrapper = function(partial, name, context, helpers, partials, data) {
var result = env.VM.invokePartial.apply(this, arguments);
if (result != null) { return result; }
if (env.compile) {
var options = { helpers: helpers, partials: partials, data: data };
partials[name] = env.compile(partial, { data: data !== undefined }, env);
return partials[name](context, options);
} else {
throw new Exception("The partial " + name + " could not be compiled when running in runtime-only mode");
}
};
// Just add water
var container = {
escapeExpression: Utils.escapeExpression,
invokePartial: invokePartialWrapper,
programs: [],
program: function(i, fn, data) {
var programWrapper = this.programs[i];
if(data) {
programWrapper = program(i, fn, data);
} else if (!programWrapper) {
programWrapper = this.programs[i] = program(i, fn);
}
return programWrapper;
},
merge: function(param, common) {
var ret = param || common;
if (param && common && (param !== common)) {
ret = {};
Utils.extend(ret, common);
Utils.extend(ret, param);
}
return ret;
},
programWithDepth: env.VM.programWithDepth,
noop: env.VM.noop,
compilerInfo: null
};
return function(context, options) {
options = options || {};
var namespace = options.partial ? options : env,
helpers,
partials;
if (!options.partial) {
helpers = options.helpers;
partials = options.partials;
}
var result = templateSpec.call(
container,
namespace, context,
helpers,
partials,
options.data);
if (!options.partial) {
env.VM.checkRevision(container.compilerInfo);
}
return result;
};
}
export function programWithDepth(i, fn, data /*, $depth */) {
var args = Array.prototype.slice.call(arguments, 3);
var prog = function(context, options) {
options = options || {};
return fn.apply(this, [context, options.data || data].concat(args));
};
prog.program = i;
prog.depth = args.length;
return prog;
}
export function program(i, fn, data) {
var prog = function(context, options) {
options = options || {};
return fn(context, options.data || data);
};
prog.program = i;
prog.depth = 0;
return prog;
}
export function invokePartial(partial, name, context, helpers, partials, data) {
var options = { partial: true, helpers: helpers, partials: partials, data: data };
if(partial === undefined) {
throw new Exception("The partial " + name + " could not be found");
} else if(partial instanceof Function) {
return partial(context, options);
}
}
export function noop() { return ""; }
+10
View File
@@ -0,0 +1,10 @@
// Build out our basic SafeString type
function SafeString(string) {
this.string = string;
}
SafeString.prototype.toString = function() {
return "" + this.string;
};
export default SafeString;
+66 -51
View File
@@ -1,58 +1,73 @@
var Handlebars = require("handlebars");
/*jshint -W004 */
import SafeString from "./safe-string";
// BEGIN(BROWSER)
Handlebars.Exception = function(message) {
this.message = message;
var escape = {
"&": "&amp;",
"<": "&lt;",
">": "&gt;",
'"': "&quot;",
"'": "&#x27;",
"`": "&#x60;"
};
// Build out our basic SafeString type
Handlebars.SafeString = function(string) {
this.string = string;
};
Handlebars.SafeString.prototype.toString = function() {
return this.string.toString();
};
var badChars = /[&<>"'`]/g;
var possible = /[&<>"'`]/;
(function() {
var escape = {
"<": "&lt;",
">": "&gt;"
};
function escapeChar(chr) {
return escape[chr] || "&amp;";
}
var badChars = /&(?!\w+;)|[<>]/g;
var possible = /[&<>]/;
var escapeChar = function(chr) {
return escape[chr] || "&amp;"
};
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, value) {
for(var key in value) {
if(Object.prototype.hasOwnProperty.call(value, key)) {
obj[key] = value[key];
}
};
})();
// END(BROWSER)
}
}
export var toString = Object.prototype.toString;
// Sourced from lodash
// https://github.com/bestiejs/lodash/blob/master/LICENSE.txt
var isFunction = function(value) {
return typeof value === 'function';
};
// fallback for older versions of Chrome and Safari
if (isFunction(/x/)) {
isFunction = function(value) {
return typeof value === 'function' && toString.call(value) === '[object Function]';
};
}
export var isFunction;
export var isArray = Array.isArray || function(value) {
return (value && typeof value === 'object') ? toString.call(value) === '[object Array]' : false;
};
export function escapeExpression(string) {
// don't escape SafeStrings, since they're already safe
if (string instanceof SafeString) {
return string.toString();
} else if (!string && string !== 0) {
return "";
}
// Force a string conversion as this will be done by the append regardless and
// the regex test will do this transparently behind the scenes, causing issues if
// an object's to string has escaped characters in it.
string = "" + string;
if(!possible.test(string)) { return string; }
return string.replace(badChars, escapeChar);
}
export function isEmpty(value) {
if (!value && value !== 0) {
return true;
} else if (isArray(value) && value.length === 0) {
return true;
} else {
return false;
}
}
-13
View File
@@ -1,13 +0,0 @@
var Handlebars = require("handlebars");
// BEGIN(BROWSER)
Handlebars.Visitor = function() {};
Handlebars.Visitor.prototype = {
accept: function(object) {
return this[object.type](object);
}
};
// END(BROWSER)
+25
View File
@@ -0,0 +1,25 @@
// USAGE:
// var handlebars = require('handlebars');
// var local = handlebars.create();
var handlebars = require('../dist/cjs/handlebars')["default"];
handlebars.Visitor = require('../dist/cjs/handlebars/compiler/visitor')["default"];
var printer = require('../dist/cjs/handlebars/compiler/printer');
handlebars.PrintVisitor = printer.PrintVisitor;
handlebars.print = printer.print;
module.exports = handlebars;
// Publish a Node.js require() handler for .handlebars and .hbs files
if (typeof require !== 'undefined' && require.extensions) {
var extension = function(module, filename) {
var fs = require("fs");
var templateString = fs.readFileSync(filename, "utf8");
module.exports = handlebars.compile(templateString);
};
require.extensions[".handlebars"] = extension;
require.extensions[".hbs"] = extension;
}
+61
View File
@@ -0,0 +1,61 @@
{
"name": "handlebars",
"barename": "handlebars",
"version": "1.3.0",
"description": "Handlebars provides the power necessary to let you build semantic templates effectively with no frustration",
"homepage": "http://www.handlebarsjs.com/",
"keywords": [
"handlebars",
"mustache",
"template",
"html"
],
"repository": {
"type": "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"
},
"optionalDependencies": {
"uglify-js": "~2.3"
},
"devDependencies": {
"async": "~0.2.9",
"aws-sdk": "~1.5.0",
"benchmark": "~1.0",
"dustjs-linkedin": "~2.0.2",
"eco": "~1.1.0-rc-3",
"grunt": "~0.4.1",
"grunt-cli": "~0.1.10",
"grunt-contrib-clean": "~0.4.1",
"grunt-contrib-concat": "~0.3.0",
"grunt-contrib-connect": "~0.5.0",
"grunt-contrib-copy": "~0.4.1",
"grunt-contrib-jshint": "0.x",
"grunt-contrib-requirejs": "~0.4.1",
"grunt-contrib-uglify": "~0.2.2",
"grunt-contrib-watch": "~0.5.3",
"grunt-saucelabs": "~4.1.2",
"es6-module-packager": "0.x",
"jison": "~0.3.0",
"keen.io": "0.0.3",
"mocha": "*",
"mustache": "~0.7.2",
"semver": "~2.1.0",
"underscore": "~1.5.1"
},
"main": "lib/index.js",
"bin": {
"handlebars": "bin/handlebars"
},
"scripts": {
"test": "grunt"
}
}
+193
View File
@@ -0,0 +1,193 @@
# Release Notes
## Development
[Commits](https://github.com/wycats/handlebars.js/compare/v1.3.0...master)
## 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
+3
View File
@@ -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');
-100
View File
@@ -1,100 +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 |str|
p str
end
js_context["ok"] = proc do |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 |first, second, message|
js_context["$$RSPEC1$$"] = first
js_context["$$RSPEC2$$"] = second
result = js_context.eval("$$RSPEC1$$ == $$RSPEC2$$")
message ||= "#{first} did not == #{second}"
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["module"] = proc do |name|
test_context.module(name)
end
js_context["test"] = proc do |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
+73
View File
@@ -0,0 +1,73 @@
<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/require.js"></script>
<script src="/spec/env/common.js"></script>
<script>
requirejs.config({
paths: {
handlebars: '/dist/handlebars.amd',
tests: '/tmp/tests'
}
});
var CompilerContext = {
compile: function(template, options) {
var templateSpec = handlebarsEnv.precompile(template, options);
return handlebarsEnv.template(safeEval(templateSpec));
},
compileWithPartial: function(template, options) {
return handlebarsEnv.compile(template, options);
}
};
function safeEval(templateSpec) {
var ret;
eval('ret = ' + templateSpec);
return ret;
}
</script>
<script>
onload = function(){
require(['handlebars'], function(Handlebars) {
window.Handlebars = Handlebars['default'];
require(['tests'], function(Handlebars) {
// The test harness leaks under FF. We should have decent global leak coverage from other tests
if (!navigator.userAgent.match(/Firefox\/([\d.]+)/)) {
mocha.checkLeaks();
}
mocha.run();
});
});
};
</script>
</head>
<body>
<div id="mocha"></div>
</body>
</html>
View File
+1
View File
@@ -0,0 +1 @@
{{foo}}
+1
View File
@@ -0,0 +1 @@
Hello, {{name}}!
+276
View File
@@ -0,0 +1,276 @@
/*global Handlebars, handlebarsEnv, shouldThrow */
describe('ast', function() {
if (!Handlebars.AST) {
return;
}
var LOCATION_INFO = {
last_line: 0,
first_line: 0,
first_column: 0,
last_column: 0
};
function testLocationInfoStorage(node){
var properties = [ 'firstLine', 'lastLine', 'firstColumn', 'lastColumn' ],
property,
propertiesLen = properties.length,
i;
for (i = 0; i < propertiesLen; i++){
property = properties[0];
equals(node[property], 0);
}
}
describe('MustacheNode', function() {
function testEscape(open, expected) {
var mustache = new handlebarsEnv.AST.MustacheNode([{}], {}, open, false);
equals(mustache.escaped, expected);
}
it('should store args', function() {
var id = {isSimple: true},
hash = {},
mustache = new handlebarsEnv.AST.MustacheNode([id, 'param1'], hash, '', false, LOCATION_INFO);
equals(mustache.type, 'mustache');
equals(mustache.hash, hash);
equals(mustache.escaped, true);
equals(mustache.id, id);
equals(mustache.params.length, 1);
equals(mustache.params[0], 'param1');
equals(!!mustache.isHelper, true);
testLocationInfoStorage(mustache);
});
it('should accept token for escape', function() {
testEscape('{{', true);
testEscape('{{~', true);
testEscape('{{#', true);
testEscape('{{~#', true);
testEscape('{{/', true);
testEscape('{{~/', true);
testEscape('{{^', true);
testEscape('{{~^', true);
testEscape('{', true);
testEscape('{', true);
testEscape('{{&', false);
testEscape('{{~&', false);
testEscape('{{{', false);
testEscape('{{~{', false);
});
it('should accept boolean for escape', function() {
testEscape(true, true);
testEscape({}, true);
testEscape(false, false);
testEscape(undefined, false);
});
});
describe('BlockNode', function() {
it('should throw on mustache mismatch (old sexpr-less version)', function() {
shouldThrow(function() {
var mustacheNode = new handlebarsEnv.AST.MustacheNode([{ original: 'foo'}], null, '{{', {});
new handlebarsEnv.AST.BlockNode(mustacheNode, {}, {}, {path: {original: 'bar'}});
}, Handlebars.Exception, "foo doesn't match bar");
});
it('should throw on mustache mismatch', function() {
shouldThrow(function() {
var sexprNode = new handlebarsEnv.AST.SexprNode([{ original: 'foo'}], null);
var mustacheNode = new handlebarsEnv.AST.MustacheNode(sexprNode, null, '{{', {});
new handlebarsEnv.AST.BlockNode(mustacheNode, {}, {}, {path: {original: 'bar'}}, {first_line: 2, first_column: 2});
}, Handlebars.Exception, "foo doesn't match bar - 2:2");
});
it('stores location info', function(){
var sexprNode = new handlebarsEnv.AST.SexprNode([{ original: 'foo'}], null);
var mustacheNode = new handlebarsEnv.AST.MustacheNode(sexprNode, null, '{{', {});
var block = new handlebarsEnv.AST.BlockNode(mustacheNode,
{strip: {}}, {strip: {}},
{
strip: {},
path: {original: 'foo'}
},
LOCATION_INFO);
testLocationInfoStorage(block);
});
});
describe('IdNode', function() {
it('should throw on invalid path', function() {
shouldThrow(function() {
new handlebarsEnv.AST.IdNode([
{part: 'foo'},
{part: '..'},
{part: 'bar'}
], {first_line: 1, first_column: 1});
}, Handlebars.Exception, "Invalid path: foo.. - 1:1");
shouldThrow(function() {
new handlebarsEnv.AST.IdNode([
{part: 'foo'},
{part: '.'},
{part: 'bar'}
], {first_line: 1, first_column: 1});
}, Handlebars.Exception, "Invalid path: foo. - 1:1");
shouldThrow(function() {
new handlebarsEnv.AST.IdNode([
{part: 'foo'},
{part: 'this'},
{part: 'bar'}
], {first_line: 1, first_column: 1});
}, Handlebars.Exception, "Invalid path: foothis - 1:1");
});
it('stores location info', function(){
var idNode = new handlebarsEnv.AST.IdNode([], LOCATION_INFO);
testLocationInfoStorage(idNode);
});
});
describe("HashNode", function(){
it('stores location info', function(){
var hash = new handlebarsEnv.AST.HashNode([], LOCATION_INFO);
testLocationInfoStorage(hash);
});
});
describe("ContentNode", function(){
it('stores location info', function(){
var content = new handlebarsEnv.AST.ContentNode("HI", LOCATION_INFO);
testLocationInfoStorage(content);
});
});
describe("CommentNode", function(){
it('stores location info', function(){
var comment = new handlebarsEnv.AST.CommentNode("HI", LOCATION_INFO);
testLocationInfoStorage(comment);
});
});
describe("IntegerNode", function(){
it('stores location info', function(){
var integer = new handlebarsEnv.AST.IntegerNode("6", LOCATION_INFO);
testLocationInfoStorage(integer);
});
});
describe("StringNode", function(){
it('stores location info', function(){
var string = new handlebarsEnv.AST.StringNode("6", LOCATION_INFO);
testLocationInfoStorage(string);
});
});
describe("BooleanNode", function(){
it('stores location info', function(){
var bool = new handlebarsEnv.AST.BooleanNode("true", LOCATION_INFO);
testLocationInfoStorage(bool);
});
});
describe("DataNode", function(){
it('stores location info', function(){
var data = new handlebarsEnv.AST.DataNode("YES", LOCATION_INFO);
testLocationInfoStorage(data);
});
});
describe("PartialNameNode", function(){
it('stores location info', function(){
var pnn = new handlebarsEnv.AST.PartialNameNode({original: "YES"}, LOCATION_INFO);
testLocationInfoStorage(pnn);
});
});
describe("PartialNode", function(){
it('stores location info', function(){
var pn = new handlebarsEnv.AST.PartialNode("so_partial", {}, {}, LOCATION_INFO);
testLocationInfoStorage(pn);
});
});
describe("ProgramNode", function(){
describe("storing location info", function(){
it("stores when `inverse` argument isn't passed", function(){
var pn = new handlebarsEnv.AST.ProgramNode([], LOCATION_INFO);
testLocationInfoStorage(pn);
});
it("stores when `inverse` or `stripInverse` arguments passed", function(){
var pn = new handlebarsEnv.AST.ProgramNode([], {strip: {}}, undefined, LOCATION_INFO);
testLocationInfoStorage(pn);
var clone = {
strip: {},
firstLine: 0,
lastLine: 0,
firstColumn: 0,
lastColumn: 0
};
pn = new handlebarsEnv.AST.ProgramNode([], {strip: {}}, [ clone ], LOCATION_INFO);
testLocationInfoStorage(pn);
// Assert that the newly created ProgramNode has the same location
// information as the inverse
testLocationInfoStorage(pn.inverse);
});
});
});
describe("Line Numbers", function(){
var ast, statements;
function testColumns(node, firstLine, lastLine, firstColumn, lastColumn){
equals(node.firstLine, firstLine);
equals(node.lastLine, lastLine);
equals(node.firstColumn, firstColumn);
equals(node.lastColumn, lastColumn);
}
ast = Handlebars.parse("line 1 {{line1Token}}\n line 2 {{line2token}}\n line 3 {{#blockHelperOnLine3}}\nline 4{{line4token}}\n" +
"line5{{else}}\n{{line6Token}}\n{{/blockHelperOnLine3}}");
statements = ast.statements;
it('gets ContentNode line numbers', function(){
var contentNode = statements[0];
testColumns(contentNode, 1, 1, 0, 7);
});
it('gets MustacheNode line numbers', function(){
var mustacheNode = statements[1];
testColumns(mustacheNode, 1, 1, 7, 21);
});
it('gets line numbers correct when newlines appear', function(){
var secondContentNode = statements[2];
testColumns(secondContentNode, 1, 2, 21, 8);
});
it('gets MustacheNode line numbers correct across newlines', function(){
var secondMustacheNode = statements[3];
testColumns(secondMustacheNode, 2, 2, 8, 22);
});
it('gets the block helper information correct', function(){
var blockHelperNode = statements[5];
testColumns(blockHelperNode, 3, 7, 8, 23);
});
it('correctly records the line numbers of an inverse of a block helper', function(){
var blockHelperNode = statements[5],
inverse = blockHelperNode.inverse;
testColumns(inverse, 5, 6, 13, 0);
});
});
});
+189
View File
@@ -0,0 +1,189 @@
/*global CompilerContext, Handlebars, beforeEach, shouldCompileTo */
global.handlebarsEnv = null;
beforeEach(function() {
global.handlebarsEnv = Handlebars.create();
});
describe("basic context", function() {
it("most basic", function() {
shouldCompileTo("{{foo}}", { foo: "foo" }, "foo");
});
it("escaping", function() {
shouldCompileTo("\\{{foo}}", { foo: "food" }, "{{foo}}");
shouldCompileTo("content \\{{foo}}", { foo: "food" }, "content {{foo}}");
shouldCompileTo("\\\\{{foo}}", { foo: "food" }, "\\food");
shouldCompileTo("content \\\\{{foo}}", { foo: "food" }, "content \\food");
shouldCompileTo("\\\\ {{foo}}", { foo: "food" }, "\\\\ food");
});
it("compiling with a basic context", function() {
shouldCompileTo("Goodbye\n{{cruel}}\n{{world}}!", {cruel: "cruel", world: "world"}, "Goodbye\ncruel\nworld!",
"It works if all the required keys are provided");
});
it("compiling with an undefined context", function() {
shouldCompileTo("Goodbye\n{{cruel}}\n{{world.bar}}!", undefined, "Goodbye\n\n!");
shouldCompileTo("{{#unless foo}}Goodbye{{../test}}{{test2}}{{/unless}}", undefined, "Goodbye");
});
it("comments", function() {
shouldCompileTo("{{! Goodbye}}Goodbye\n{{cruel}}\n{{world}}!",
{cruel: "cruel", world: "world"}, "Goodbye\ncruel\nworld!",
"comments are ignored");
});
it("boolean", function() {
var string = "{{#goodbye}}GOODBYE {{/goodbye}}cruel {{world}}!";
shouldCompileTo(string, {goodbye: true, world: "world"}, "GOODBYE cruel world!",
"booleans show the contents when true");
shouldCompileTo(string, {goodbye: false, world: "world"}, "cruel world!",
"booleans do not show the contents when false");
});
it("zeros", function() {
shouldCompileTo("num1: {{num1}}, num2: {{num2}}", {num1: 42, num2: 0},
"num1: 42, num2: 0");
shouldCompileTo("num: {{.}}", 0, "num: 0");
shouldCompileTo("num: {{num1/num2}}", {num1: {num2: 0}}, "num: 0");
});
it("newlines", function() {
shouldCompileTo("Alan's\nTest", {}, "Alan's\nTest");
shouldCompileTo("Alan's\rTest", {}, "Alan's\rTest");
});
it("escaping text", function() {
shouldCompileTo("Awesome's", {}, "Awesome's", "text is escaped so that it doesn't get caught on single quotes");
shouldCompileTo("Awesome\\", {}, "Awesome\\", "text is escaped so that the closing quote can't be ignored");
shouldCompileTo("Awesome\\\\ foo", {}, "Awesome\\\\ foo", "text is escaped so that it doesn't mess up backslashes");
shouldCompileTo("Awesome {{foo}}", {foo: '\\'}, "Awesome \\", "text is escaped so that it doesn't mess up backslashes");
shouldCompileTo(' " " ', {}, ' " " ', "double quotes never produce invalid javascript");
});
it("escaping expressions", function() {
shouldCompileTo("{{{awesome}}}", {awesome: "&\"\\<>"}, '&\"\\<>',
"expressions with 3 handlebars aren't escaped");
shouldCompileTo("{{&awesome}}", {awesome: "&\"\\<>"}, '&\"\\<>',
"expressions with {{& handlebars aren't escaped");
shouldCompileTo("{{awesome}}", {awesome: "&\"'`\\<>"}, '&amp;&quot;&#x27;&#x60;\\&lt;&gt;',
"by default expressions should be escaped");
shouldCompileTo("{{awesome}}", {awesome: "Escaped, <b> looks like: &lt;b&gt;"}, 'Escaped, &lt;b&gt; looks like: &amp;lt;b&amp;gt;',
"escaping should properly handle amperstands");
});
it("functions returning safestrings shouldn't be escaped", function() {
var hash = {awesome: function() { return new Handlebars.SafeString("&\"\\<>"); }};
shouldCompileTo("{{awesome}}", hash, '&\"\\<>',
"functions returning safestrings aren't escaped");
});
it("functions", function() {
shouldCompileTo("{{awesome}}", {awesome: function() { return "Awesome"; }}, "Awesome",
"functions are called and render their output");
shouldCompileTo("{{awesome}}", {awesome: function() { return this.more; }, more: "More awesome"}, "More awesome",
"functions are bound to the context");
});
it("functions with context argument", function() {
shouldCompileTo("{{awesome frank}}",
{awesome: function(context) { return context; },
frank: "Frank"},
"Frank", "functions are called with context arguments");
});
it("block functions with context argument", function() {
shouldCompileTo("{{#awesome 1}}inner {{.}}{{/awesome}}",
{awesome: function(context, options) { return options.fn(context); }},
"inner 1", "block functions are called with context and options");
});
it("block functions without context argument", function() {
shouldCompileTo("{{#awesome}}inner{{/awesome}}",
{awesome: function(options) { return options.fn(this); }},
"inner", "block functions are called with options");
});
it("paths with hyphens", function() {
shouldCompileTo("{{foo-bar}}", {"foo-bar": "baz"}, "baz", "Paths can contain hyphens (-)");
shouldCompileTo("{{foo.foo-bar}}", {foo: {"foo-bar": "baz"}}, "baz", "Paths can contain hyphens (-)");
shouldCompileTo("{{foo/foo-bar}}", {foo: {"foo-bar": "baz"}}, "baz", "Paths can contain hyphens (-)");
});
it("nested paths", function() {
shouldCompileTo("Goodbye {{alan/expression}} world!", {alan: {expression: "beautiful"}},
"Goodbye beautiful world!", "Nested paths access nested objects");
});
it("nested paths with empty string value", function() {
shouldCompileTo("Goodbye {{alan/expression}} world!", {alan: {expression: ""}},
"Goodbye world!", "Nested paths access nested objects with empty string");
});
it("literal paths", function() {
shouldCompileTo("Goodbye {{[@alan]/expression}} world!", {"@alan": {expression: "beautiful"}},
"Goodbye beautiful world!", "Literal paths can be used");
shouldCompileTo("Goodbye {{[foo bar]/expression}} world!", {"foo bar": {expression: "beautiful"}},
"Goodbye beautiful world!", "Literal paths can be used");
});
it('literal references', function() {
shouldCompileTo("Goodbye {{[foo bar]}} world!", {"foo bar": "beautiful"},
"Goodbye beautiful world!", "Literal paths can be used");
});
it("that current context path ({{.}}) doesn't hit helpers", function() {
shouldCompileTo("test: {{.}}", [null, {helper: "awesome"}], "test: ");
});
it("complex but empty paths", function() {
shouldCompileTo("{{person/name}}", {person: {name: null}}, "");
shouldCompileTo("{{person/name}}", {person: {}}, "");
});
it("this keyword in paths", function() {
var string = "{{#goodbyes}}{{this}}{{/goodbyes}}";
var hash = {goodbyes: ["goodbye", "Goodbye", "GOODBYE"]};
shouldCompileTo(string, hash, "goodbyeGoodbyeGOODBYE",
"This keyword in paths evaluates to current context");
string = "{{#hellos}}{{this/text}}{{/hellos}}";
hash = {hellos: [{text: "hello"}, {text: "Hello"}, {text: "HELLO"}]};
shouldCompileTo(string, hash, "helloHelloHELLO", "This keyword evaluates in more complex paths");
});
it("this keyword nested inside path", function() {
var string = "{{#hellos}}{{text/this/foo}}{{/hellos}}";
shouldThrow(function() {
CompilerContext.compile(string);
}, Error);
});
it("this keyword in helpers", function() {
var helpers = {foo: function(value) {
return 'bar ' + value;
}};
var string = "{{#goodbyes}}{{foo this}}{{/goodbyes}}";
var hash = {goodbyes: ["goodbye", "Goodbye", "GOODBYE"]};
shouldCompileTo(string, [hash, helpers], "bar goodbyebar Goodbyebar GOODBYE",
"This keyword in paths evaluates to current context");
string = "{{#hellos}}{{foo this/text}}{{/hellos}}";
hash = {hellos: [{text: "hello"}, {text: "Hello"}, {text: "HELLO"}]};
shouldCompileTo(string, [hash, helpers], "bar hellobar Hellobar HELLO", "This keyword evaluates in more complex paths");
});
it("this keyword nested inside helpers param", function() {
var string = "{{#hellos}}{{foo text/this/foo}}{{/hellos}}";
shouldThrow(function() {
CompilerContext.compile(string);
}, Error);
});
});
+86
View File
@@ -0,0 +1,86 @@
/*global CompilerContext, shouldCompileTo */
describe('blocks', function() {
it("array", function() {
var string = "{{#goodbyes}}{{text}}! {{/goodbyes}}cruel {{world}}!";
var hash = {goodbyes: [{text: "goodbye"}, {text: "Goodbye"}, {text: "GOODBYE"}], world: "world"};
shouldCompileTo(string, hash, "goodbye! Goodbye! GOODBYE! cruel world!",
"Arrays iterate over the contents when not empty");
shouldCompileTo(string, {goodbyes: [], world: "world"}, "cruel world!",
"Arrays ignore the contents when empty");
});
it("array with @index", function() {
var string = "{{#goodbyes}}{{@index}}. {{text}}! {{/goodbyes}}cruel {{world}}!";
var hash = {goodbyes: [{text: "goodbye"}, {text: "Goodbye"}, {text: "GOODBYE"}], world: "world"};
var template = CompilerContext.compile(string);
var result = template(hash);
equal(result, "0. goodbye! 1. Goodbye! 2. GOODBYE! cruel world!", "The @index variable is used");
});
it("empty block", function() {
var string = "{{#goodbyes}}{{/goodbyes}}cruel {{world}}!";
var hash = {goodbyes: [{text: "goodbye"}, {text: "Goodbye"}, {text: "GOODBYE"}], world: "world"};
shouldCompileTo(string, hash, "cruel world!",
"Arrays iterate over the contents when not empty");
shouldCompileTo(string, {goodbyes: [], world: "world"}, "cruel world!",
"Arrays ignore the contents when empty");
});
it("block with complex lookup", function() {
var string = "{{#goodbyes}}{{text}} cruel {{../name}}! {{/goodbyes}}";
var hash = {name: "Alan", goodbyes: [{text: "goodbye"}, {text: "Goodbye"}, {text: "GOODBYE"}]};
shouldCompileTo(string, hash, "goodbye cruel Alan! Goodbye cruel Alan! GOODBYE cruel Alan! ",
"Templates can access variables in contexts up the stack with relative path syntax");
});
it("block with complex lookup using nested context", function() {
var string = "{{#goodbyes}}{{text}} cruel {{foo/../name}}! {{/goodbyes}}";
shouldThrow(function() {
CompilerContext.compile(string);
}, Error);
});
it("block with deep nested complex lookup", function() {
var string = "{{#outer}}Goodbye {{#inner}}cruel {{../../omg}}{{/inner}}{{/outer}}";
var hash = {omg: "OMG!", outer: [{ inner: [{ text: "goodbye" }] }] };
shouldCompileTo(string, hash, "Goodbye cruel OMG!");
});
describe('inverted sections', function() {
it("inverted sections with unset value", function() {
var string = "{{#goodbyes}}{{this}}{{/goodbyes}}{{^goodbyes}}Right On!{{/goodbyes}}";
var hash = {};
shouldCompileTo(string, hash, "Right On!", "Inverted section rendered when value isn't set.");
});
it("inverted section with false value", function() {
var string = "{{#goodbyes}}{{this}}{{/goodbyes}}{{^goodbyes}}Right On!{{/goodbyes}}";
var hash = {goodbyes: false};
shouldCompileTo(string, hash, "Right On!", "Inverted section rendered when value is false.");
});
it("inverted section with empty set", function() {
var string = "{{#goodbyes}}{{this}}{{/goodbyes}}{{^goodbyes}}Right On!{{/goodbyes}}";
var hash = {goodbyes: []};
shouldCompileTo(string, hash, "Right On!", "Inverted section rendered when value is empty set.");
});
it("block inverted sections", function() {
shouldCompileTo("{{#people}}{{name}}{{^}}{{none}}{{/people}}", {none: "No people"},
"No people");
});
it("block inverted sections with empty arrays", function() {
shouldCompileTo("{{#people}}{{name}}{{^}}{{none}}{{/people}}", {none: "No people", people: []},
"No people");
});
});
});
+199
View File
@@ -0,0 +1,199 @@
/*global CompilerContext, shouldCompileTo, compileWithPartials */
describe('builtin helpers', function() {
describe('#if', function() {
it("if", function() {
var string = "{{#if goodbye}}GOODBYE {{/if}}cruel {{world}}!";
shouldCompileTo(string, {goodbye: true, world: "world"}, "GOODBYE cruel world!",
"if with boolean argument shows the contents when true");
shouldCompileTo(string, {goodbye: "dummy", world: "world"}, "GOODBYE cruel world!",
"if with string argument shows the contents");
shouldCompileTo(string, {goodbye: false, world: "world"}, "cruel world!",
"if with boolean argument does not show the contents when false");
shouldCompileTo(string, {world: "world"}, "cruel world!",
"if with undefined does not show the contents");
shouldCompileTo(string, {goodbye: ['foo'], world: "world"}, "GOODBYE cruel world!",
"if with non-empty array shows the contents");
shouldCompileTo(string, {goodbye: [], world: "world"}, "cruel world!",
"if with empty array does not show the contents");
shouldCompileTo(string, {goodbye: 0, world: "world"}, "cruel world!",
"if with zero does not show the contents");
shouldCompileTo("{{#if goodbye includeZero=true}}GOODBYE {{/if}}cruel {{world}}!",
{goodbye: 0, world: "world"}, "GOODBYE cruel world!",
"if with zero does not show the contents");
});
it("if with function argument", function() {
var string = "{{#if goodbye}}GOODBYE {{/if}}cruel {{world}}!";
shouldCompileTo(string, {goodbye: function() {return true;}, world: "world"}, "GOODBYE cruel world!",
"if with function shows the contents when function returns true");
shouldCompileTo(string, {goodbye: function() {return this.world;}, world: "world"}, "GOODBYE cruel world!",
"if with function shows the contents when function returns string");
shouldCompileTo(string, {goodbye: function() {return false;}, world: "world"}, "cruel world!",
"if with function does not show the contents when returns false");
shouldCompileTo(string, {goodbye: function() {return this.foo;}, world: "world"}, "cruel world!",
"if with function does not show the contents when returns undefined");
});
});
describe('#with', function() {
it("with", function() {
var string = "{{#with person}}{{first}} {{last}}{{/with}}";
shouldCompileTo(string, {person: {first: "Alan", last: "Johnson"}}, "Alan Johnson");
});
it("with with function argument", function() {
var string = "{{#with person}}{{first}} {{last}}{{/with}}";
shouldCompileTo(string, {person: function() { return {first: "Alan", last: "Johnson"};}}, "Alan Johnson");
});
});
describe('#each', function() {
beforeEach(function() {
handlebarsEnv.registerHelper('detectDataInsideEach', function(options) {
return options.data && options.data.exclaim;
});
});
it("each", function() {
var string = "{{#each goodbyes}}{{text}}! {{/each}}cruel {{world}}!";
var hash = {goodbyes: [{text: "goodbye"}, {text: "Goodbye"}, {text: "GOODBYE"}], world: "world"};
shouldCompileTo(string, hash, "goodbye! Goodbye! GOODBYE! cruel world!",
"each with array argument iterates over the contents when not empty");
shouldCompileTo(string, {goodbyes: [], world: "world"}, "cruel world!",
"each with array argument ignores the contents when empty");
});
it("each with an object and @key", function() {
var string = "{{#each goodbyes}}{{@key}}. {{text}}! {{/each}}cruel {{world}}!";
var hash = {goodbyes: {"<b>#1</b>": {text: "goodbye"}, 2: {text: "GOODBYE"}}, world: "world"};
// Object property iteration order is undefined according to ECMA spec,
// so we need to check both possible orders
// @see http://stackoverflow.com/questions/280713/elements-order-in-a-for-in-loop
var actual = compileWithPartials(string, hash);
var expected1 = "&lt;b&gt;#1&lt;/b&gt;. goodbye! 2. GOODBYE! cruel world!";
var expected2 = "2. GOODBYE! &lt;b&gt;#1&lt;/b&gt;. goodbye! cruel world!";
equals(actual === expected1 || actual === expected2, true, "each with object argument iterates over the contents when not empty");
shouldCompileTo(string, {goodbyes: [], world: "world"}, "cruel world!",
"each with object argument ignores the contents when empty");
});
it("each with @index", function() {
var string = "{{#each goodbyes}}{{@index}}. {{text}}! {{/each}}cruel {{world}}!";
var hash = {goodbyes: [{text: "goodbye"}, {text: "Goodbye"}, {text: "GOODBYE"}], world: "world"};
var template = CompilerContext.compile(string);
var result = template(hash);
equal(result, "0. goodbye! 1. Goodbye! 2. GOODBYE! cruel world!", "The @index variable is used");
});
it("each with nested @index", function() {
var string = "{{#each goodbyes}}{{@index}}. {{text}}! {{#each ../goodbyes}}{{@index}} {{/each}}After {{@index}} {{/each}}{{@index}}cruel {{world}}!";
var hash = {goodbyes: [{text: "goodbye"}, {text: "Goodbye"}, {text: "GOODBYE"}], world: "world"};
var template = CompilerContext.compile(string);
var result = template(hash);
equal(result, "0. goodbye! 0 1 2 After 0 1. Goodbye! 0 1 2 After 1 2. GOODBYE! 0 1 2 After 2 cruel world!", "The @index variable is used");
});
it("each object with @index", function() {
var string = "{{#each goodbyes}}{{@index}}. {{text}}! {{/each}}cruel {{world}}!";
var hash = {goodbyes: {'a': {text: "goodbye"}, b: {text: "Goodbye"}, c: {text: "GOODBYE"}}, world: "world"};
var template = CompilerContext.compile(string);
var result = template(hash);
equal(result, "0. goodbye! 1. Goodbye! 2. GOODBYE! cruel world!", "The @index variable is used");
});
it("each with @first", function() {
var string = "{{#each goodbyes}}{{#if @first}}{{text}}! {{/if}}{{/each}}cruel {{world}}!";
var hash = {goodbyes: [{text: "goodbye"}, {text: "Goodbye"}, {text: "GOODBYE"}], world: "world"};
var template = CompilerContext.compile(string);
var result = template(hash);
equal(result, "goodbye! cruel world!", "The @first variable is used");
});
it("each with nested @first", function() {
var string = "{{#each goodbyes}}({{#if @first}}{{text}}! {{/if}}{{#each ../goodbyes}}{{#if @first}}{{text}}!{{/if}}{{/each}}{{#if @first}} {{text}}!{{/if}}) {{/each}}cruel {{world}}!";
var hash = {goodbyes: [{text: "goodbye"}, {text: "Goodbye"}, {text: "GOODBYE"}], world: "world"};
var template = CompilerContext.compile(string);
var result = template(hash);
equal(result, "(goodbye! goodbye! goodbye!) (goodbye!) (goodbye!) cruel world!", "The @first variable is used");
});
it("each object with @first", function() {
var string = "{{#each goodbyes}}{{#if @first}}{{text}}! {{/if}}{{/each}}cruel {{world}}!";
var hash = {goodbyes: {'foo': {text: "goodbye"}, bar: {text: "Goodbye"}}, world: "world"};
var template = CompilerContext.compile(string);
var result = template(hash);
equal(result, "goodbye! cruel world!", "The @first variable is used");
});
it("each with @last", function() {
var string = "{{#each goodbyes}}{{#if @last}}{{text}}! {{/if}}{{/each}}cruel {{world}}!";
var hash = {goodbyes: [{text: "goodbye"}, {text: "Goodbye"}, {text: "GOODBYE"}], world: "world"};
var template = CompilerContext.compile(string);
var result = template(hash);
equal(result, "GOODBYE! cruel world!", "The @last variable is used");
});
it("each with nested @last", function() {
var string = "{{#each goodbyes}}({{#if @last}}{{text}}! {{/if}}{{#each ../goodbyes}}{{#if @last}}{{text}}!{{/if}}{{/each}}{{#if @last}} {{text}}!{{/if}}) {{/each}}cruel {{world}}!";
var hash = {goodbyes: [{text: "goodbye"}, {text: "Goodbye"}, {text: "GOODBYE"}], world: "world"};
var template = CompilerContext.compile(string);
var result = template(hash);
equal(result, "(GOODBYE!) (GOODBYE!) (GOODBYE! GOODBYE! GOODBYE!) cruel world!", "The @last variable is used");
});
it("each with function argument", function() {
var string = "{{#each goodbyes}}{{text}}! {{/each}}cruel {{world}}!";
var hash = {goodbyes: function () { return [{text: "goodbye"}, {text: "Goodbye"}, {text: "GOODBYE"}];}, world: "world"};
shouldCompileTo(string, hash, "goodbye! Goodbye! GOODBYE! cruel world!",
"each with array function argument iterates over the contents when not empty");
shouldCompileTo(string, {goodbyes: [], world: "world"}, "cruel world!",
"each with array function argument ignores the contents when empty");
});
it("data passed to helpers", function() {
var string = "{{#each letters}}{{this}}{{detectDataInsideEach}}{{/each}}";
var hash = {letters: ['a', 'b', 'c']};
var template = CompilerContext.compile(string);
var result = template(hash, {
data: {
exclaim: '!'
}
});
equal(result, 'a!b!c!', 'should output data');
});
});
it("#log", function() {
var string = "{{log blah}}";
var hash = { blah: "whee" };
var levelArg, logArg;
handlebarsEnv.log = function(level, arg){ levelArg = level, logArg = arg; };
shouldCompileTo(string, hash, "", "log should not display");
equals(1, levelArg, "should call log with 1");
equals("whee", logArg, "should call log with 'whee'");
});
});
+239
View File
@@ -0,0 +1,239 @@
/*global CompilerContext, Handlebars, handlebarsEnv, shouldThrow */
describe('data', function() {
it("passing in data to a compiled function that expects data - works with helpers", function() {
var template = CompilerContext.compile("{{hello}}", {data: true});
var helpers = {
hello: function(options) {
return options.data.adjective + " " + this.noun;
}
};
var result = template({noun: "cat"}, {helpers: helpers, data: {adjective: "happy"}});
equals("happy cat", result, "Data output by helper");
});
it("data can be looked up via @foo", function() {
var template = CompilerContext.compile("{{@hello}}");
var result = template({}, { data: { hello: "hello" } });
equals("hello", result, "@foo retrieves template data");
});
it("deep @foo triggers automatic top-level data", function() {
var template = CompilerContext.compile('{{#let world="world"}}{{#if foo}}{{#if foo}}Hello {{@world}}{{/if}}{{/if}}{{/let}}');
var helpers = Handlebars.createFrame(handlebarsEnv.helpers);
helpers.let = function(options) {
var frame = Handlebars.createFrame(options.data);
for (var prop in options.hash) {
frame[prop] = options.hash[prop];
}
return options.fn(this, { data: frame });
};
var result = template({ foo: true }, { helpers: helpers });
equals("Hello world", result, "Automatic data was triggered");
});
it("parameter data can be looked up via @foo", function() {
var template = CompilerContext.compile("{{hello @world}}");
var helpers = {
hello: function(noun) {
return "Hello " + noun;
}
};
var result = template({}, { helpers: helpers, data: { world: "world" } });
equals("Hello world", result, "@foo as a parameter retrieves template data");
});
it("hash values can be looked up via @foo", function() {
var template = CompilerContext.compile("{{hello noun=@world}}");
var helpers = {
hello: function(options) {
return "Hello " + options.hash.noun;
}
};
var result = template({}, { helpers: helpers, data: { world: "world" } });
equals("Hello world", result, "@foo as a parameter retrieves template data");
});
it("nested parameter data can be looked up via @foo.bar", function() {
var template = CompilerContext.compile("{{hello @world.bar}}");
var helpers = {
hello: function(noun) {
return "Hello " + noun;
}
};
var result = template({}, { helpers: helpers, data: { world: {bar: "world" } } });
equals("Hello world", result, "@foo as a parameter retrieves template data");
});
it("nested parameter data does not fail with @world.bar", function() {
var template = CompilerContext.compile("{{hello @world.bar}}");
var helpers = {
hello: function(noun) {
return "Hello " + noun;
}
};
var result = template({}, { helpers: helpers, data: { foo: {bar: "world" } } });
equals("Hello undefined", result, "@foo as a parameter retrieves template data");
});
it("parameter data throws when using this scope references", function() {
var string = "{{#goodbyes}}{{text}} cruel {{@./name}}! {{/goodbyes}}";
shouldThrow(function() {
CompilerContext.compile(string);
}, Error);
});
it("parameter data throws when using parent scope references", function() {
var string = "{{#goodbyes}}{{text}} cruel {{@../name}}! {{/goodbyes}}";
shouldThrow(function() {
CompilerContext.compile(string);
}, Error);
});
it("parameter data throws when using complex scope references", function() {
var string = "{{#goodbyes}}{{text}} cruel {{@foo/../name}}! {{/goodbyes}}";
shouldThrow(function() {
CompilerContext.compile(string);
}, Error);
});
it("data is inherited downstream", function() {
var template = CompilerContext.compile("{{#let foo=1 bar=2}}{{#let foo=bar.baz}}{{@bar}}{{@foo}}{{/let}}{{@foo}}{{/let}}", { data: true });
var helpers = {
let: function(options) {
var frame = Handlebars.createFrame(options.data);
for (var prop in options.hash) {
frame[prop] = options.hash[prop];
}
return options.fn(this, {data: frame});
}
};
var result = template({ bar: { baz: "hello world" } }, { helpers: helpers, data: {} });
equals("2hello world1", result, "data variables are inherited downstream");
});
it("passing in data to a compiled function that expects data - works with helpers in partials", function() {
var template = CompilerContext.compile("{{>my_partial}}", {data: true});
var partials = {
my_partial: CompilerContext.compile("{{hello}}", {data: true})
};
var helpers = {
hello: function(options) {
return options.data.adjective + " " + this.noun;
}
};
var result = template({noun: "cat"}, {helpers: helpers, partials: partials, data: {adjective: "happy"}});
equals("happy cat", result, "Data output by helper inside partial");
});
it("passing in data to a compiled function that expects data - works with helpers and parameters", function() {
var template = CompilerContext.compile("{{hello world}}", {data: true});
var helpers = {
hello: function(noun, options) {
return options.data.adjective + " " + noun + (this.exclaim ? "!" : "");
}
};
var result = template({exclaim: true, world: "world"}, {helpers: helpers, data: {adjective: "happy"}});
equals("happy world!", result, "Data output by helper");
});
it("passing in data to a compiled function that expects data - works with block helpers", function() {
var template = CompilerContext.compile("{{#hello}}{{world}}{{/hello}}", {data: true});
var helpers = {
hello: function(options) {
return options.fn(this);
},
world: function(options) {
return options.data.adjective + " world" + (this.exclaim ? "!" : "");
}
};
var result = template({exclaim: true}, {helpers: helpers, data: {adjective: "happy"}});
equals("happy world!", result, "Data output by helper");
});
it("passing in data to a compiled function that expects data - works with block helpers that use ..", function() {
var template = CompilerContext.compile("{{#hello}}{{world ../zomg}}{{/hello}}", {data: true});
var helpers = {
hello: function(options) {
return options.fn({exclaim: "?"});
},
world: function(thing, options) {
return options.data.adjective + " " + thing + (this.exclaim || "");
}
};
var result = template({exclaim: true, zomg: "world"}, {helpers: helpers, data: {adjective: "happy"}});
equals("happy world?", result, "Data output by helper");
});
it("passing in data to a compiled function that expects data - data is passed to with block helpers where children use ..", function() {
var template = CompilerContext.compile("{{#hello}}{{world ../zomg}}{{/hello}}", {data: true});
var helpers = {
hello: function(options) {
return options.data.accessData + " " + options.fn({exclaim: "?"});
},
world: function(thing, options) {
return options.data.adjective + " " + thing + (this.exclaim || "");
}
};
var result = template({exclaim: true, zomg: "world"}, {helpers: helpers, data: {adjective: "happy", accessData: "#win"}});
equals("#win happy world?", result, "Data output by helper");
});
it("you can override inherited data when invoking a helper", function() {
var template = CompilerContext.compile("{{#hello}}{{world zomg}}{{/hello}}", {data: true});
var helpers = {
hello: function(options) {
return options.fn({exclaim: "?", zomg: "world"}, { data: {adjective: "sad"} });
},
world: function(thing, options) {
return options.data.adjective + " " + thing + (this.exclaim || "");
}
};
var result = template({exclaim: true, zomg: "planet"}, {helpers: helpers, data: {adjective: "happy"}});
equals("sad world?", result, "Overriden data output by helper");
});
it("you can override inherited data when invoking a helper with depth", function() {
var template = CompilerContext.compile("{{#hello}}{{world ../zomg}}{{/hello}}", {data: true});
var helpers = {
hello: function(options) {
return options.fn({exclaim: "?"}, { data: {adjective: "sad"} });
},
world: function(thing, options) {
return options.data.adjective + " " + thing + (this.exclaim || "");
}
};
var result = template({exclaim: true, zomg: "world"}, {helpers: helpers, data: {adjective: "happy"}});
equals("sad world?", result, "Overriden data output by helper");
});
});
+23
View File
@@ -0,0 +1,23 @@
/*global handlebarsEnv */
require('./common');
var _ = require('underscore'),
fs = require('fs'),
vm = require('vm');
global.Handlebars = undefined;
vm.runInThisContext(fs.readFileSync(__dirname + '/../../dist/handlebars.js'), 'dist/handlebars.js');
global.CompilerContext = {
compile: function(template, options) {
var templateSpec = handlebarsEnv.precompile(template, options);
return handlebarsEnv.template(safeEval(templateSpec));
},
compileWithPartial: function(template, options) {
return handlebarsEnv.compile(template, options);
}
};
function safeEval(templateSpec) {
return eval('(' + templateSpec + ')');
}
+48
View File
@@ -0,0 +1,48 @@
global.shouldCompileTo = function(string, hashOrArray, expected, message) {
shouldCompileToWithPartials(string, hashOrArray, false, expected, message);
};
global.shouldCompileToWithPartials = function(string, hashOrArray, partials, expected, message) {
var result = compileWithPartials(string, hashOrArray, partials);
if (result !== expected) {
throw new Error("'" + expected + "' should === '" + result + "': " + message);
}
};
global.compileWithPartials = function(string, hashOrArray, partials) {
var template = CompilerContext[partials ? 'compileWithPartial' : 'compile'](string), ary;
if(Object.prototype.toString.call(hashOrArray) === "[object Array]") {
ary = [];
ary.push(hashOrArray[0]);
ary.push({ helpers: hashOrArray[1], partials: hashOrArray[2] });
} else {
ary = [hashOrArray];
}
return template.apply(this, ary);
};
global.equals = global.equal = function(a, b, msg) {
if (a !== b) {
throw new Error("'" + b + "' should === '" + a + "'" + (msg ? ": " + msg : ''));
}
};
global.shouldThrow = function(callback, type, msg) {
var failed;
try {
callback();
failed = true;
} catch (err) {
if (type && !(err instanceof type)) {
throw new Error('Type failure');
}
if (msg && !(msg.test ? msg.test(err.message) : msg === err.message)) {
throw new Error('Message failure');
}
}
if (failed) {
throw new Error('It failed to throw');
}
};
+18
View File
@@ -0,0 +1,18 @@
/*global handlebarsEnv */
require('./common');
global.Handlebars = require('../../lib');
global.CompilerContext = {
compile: function(template, options) {
var templateSpec = handlebarsEnv.precompile(template, options);
return handlebarsEnv.template(safeEval(templateSpec));
},
compileWithPartial: function(template, options) {
return handlebarsEnv.compile(template, options);
}
};
function safeEval(templateSpec) {
return eval('(' + templateSpec + ')');
}
+2054
View File
File diff suppressed because it is too large Load Diff
+42
View File
@@ -0,0 +1,42 @@
var fs = require('fs'),
Mocha = require('mocha'),
path = require('path');
var errors = 0,
testDir = path.dirname(__dirname),
grep = process.argv[2];
var files = [ testDir + "/basic.js" ];
var files = fs.readdirSync(testDir)
.filter(function(name) { return (/.*\.js$/).test(name); })
.map(function(name) { return testDir + '/' + name; });
run('./node', function() {
run('./browser', function() {
run('./runtime', function() {
process.exit(errors);
});
});
});
function run(env, callback) {
var mocha = new Mocha();
mocha.ui('bdd');
mocha.files = files.slice();
if (grep) {
mocha.grep(grep);
}
files.forEach(function(name) {
delete require.cache[name];
});
console.log('Running env: ' + env);
require(env);
mocha.run(function(errorCount) {
errors += errorCount;
callback();
});
}
+43
View File
@@ -0,0 +1,43 @@
/*global handlebarsEnv */
require('./common');
var _ = require('underscore'),
fs = require('fs'),
vm = require('vm');
global.Handlebars = undefined;
vm.runInThisContext(fs.readFileSync(__dirname + '/../../dist/handlebars.runtime.js'), 'dist/handlebars.runtime.js');
var parse = require('../../dist/cjs/handlebars/compiler/base').parse;
var compiler = require('../../dist/cjs/handlebars/compiler/compiler');
var JavaScriptCompiler = require('../../dist/cjs/handlebars/compiler/javascript-compiler')['default'];
global.CompilerContext = {
compile: function(template, options) {
// Hack the compiler on to the environment for these specific tests
handlebarsEnv.precompile = function(template, options) {
return compiler.precompile(template, options, handlebarsEnv);
};
handlebarsEnv.parse = parse;
handlebarsEnv.Compiler = compiler.Compiler;
handlebarsEnv.JavaScriptCompiler = JavaScriptCompiler;
var templateSpec = handlebarsEnv.precompile(template, options);
return handlebarsEnv.template(safeEval(templateSpec));
},
compileWithPartial: function(template, options) {
// Hack the compiler on to the environment for these specific tests
handlebarsEnv.compile = function(template, options) {
return compiler.compile(template, options, handlebarsEnv);
};
handlebarsEnv.parse = parse;
handlebarsEnv.Compiler = compiler.Compiler;
handlebarsEnv.JavaScriptCompiler = JavaScriptCompiler;
return handlebarsEnv.compile(template, options);
}
};
function safeEval(templateSpec) {
return eval('(' + templateSpec + ')');
}
+11
View File
@@ -0,0 +1,11 @@
define(['handlebars.runtime'], function(Handlebars) {
Handlebars = Handlebars["default"]; var template = Handlebars.template, templates = Handlebars.templates = Handlebars.templates || {};
return templates['empty'] = template(function (Handlebars,depth0,helpers,partials,data) {
this.compilerInfo = [4,'>= 1.0.0'];
helpers = this.merge(helpers, Handlebars.helpers); data = data || {};
var buffer = "";
return buffer;
});
});
+528
View File
@@ -0,0 +1,528 @@
/*global CompilerContext, Handlebars, shouldCompileTo, shouldCompileToWithPartials, shouldThrow, handlebarsEnv */
describe('helpers', function() {
it("helper with complex lookup$", function() {
var string = "{{#goodbyes}}{{{link ../prefix}}}{{/goodbyes}}";
var hash = {prefix: "/root", goodbyes: [{text: "Goodbye", url: "goodbye"}]};
var helpers = {link: function(prefix) {
return "<a href='" + prefix + "/" + this.url + "'>" + this.text + "</a>";
}};
shouldCompileTo(string, [hash, helpers], "<a href='/root/goodbye'>Goodbye</a>");
});
it("helper block with complex lookup expression", function() {
var string = "{{#goodbyes}}{{../name}}{{/goodbyes}}";
var hash = {name: "Alan"};
var helpers = {goodbyes: function(options) {
var out = "";
var byes = ["Goodbye", "goodbye", "GOODBYE"];
for (var i = 0,j = byes.length; i < j; i++) {
out += byes[i] + " " + options.fn(this) + "! ";
}
return out;
}};
shouldCompileTo(string, [hash, helpers], "Goodbye Alan! goodbye Alan! GOODBYE Alan! ");
});
it("helper with complex lookup and nested template", function() {
var string = "{{#goodbyes}}{{#link ../prefix}}{{text}}{{/link}}{{/goodbyes}}";
var hash = {prefix: '/root', goodbyes: [{text: "Goodbye", url: "goodbye"}]};
var helpers = {link: function (prefix, options) {
return "<a href='" + prefix + "/" + this.url + "'>" + options.fn(this) + "</a>";
}};
shouldCompileToWithPartials(string, [hash, helpers], false, "<a href='/root/goodbye'>Goodbye</a>");
});
it("helper with complex lookup and nested template in VM+Compiler", function() {
var string = "{{#goodbyes}}{{#link ../prefix}}{{text}}{{/link}}{{/goodbyes}}";
var hash = {prefix: '/root', goodbyes: [{text: "Goodbye", url: "goodbye"}]};
var helpers = {link: function (prefix, options) {
return "<a href='" + prefix + "/" + this.url + "'>" + options.fn(this) + "</a>";
}};
shouldCompileToWithPartials(string, [hash, helpers], true, "<a href='/root/goodbye'>Goodbye</a>");
});
it("block helper", function() {
var string = "{{#goodbyes}}{{text}}! {{/goodbyes}}cruel {{world}}!";
var template = CompilerContext.compile(string);
var result = template({world: "world"}, { helpers: {goodbyes: function(options) { return options.fn({text: "GOODBYE"}); }}});
equal(result, "GOODBYE! cruel world!", "Block helper executed");
});
it("block helper staying in the same context", function() {
var string = "{{#form}}<p>{{name}}</p>{{/form}}";
var template = CompilerContext.compile(string);
var result = template({name: "Yehuda"}, {helpers: {form: function(options) { return "<form>" + options.fn(this) + "</form>"; } }});
equal(result, "<form><p>Yehuda</p></form>", "Block helper executed with current context");
});
it("block helper should have context in this", function() {
var source = "<ul>{{#people}}<li>{{#link}}{{name}}{{/link}}</li>{{/people}}</ul>";
var link = function(options) {
return '<a href="/people/' + this.id + '">' + options.fn(this) + '</a>';
};
var data = { "people": [
{ "name": "Alan", "id": 1 },
{ "name": "Yehuda", "id": 2 }
]};
shouldCompileTo(source, [data, {link: link}], "<ul><li><a href=\"/people/1\">Alan</a></li><li><a href=\"/people/2\">Yehuda</a></li></ul>");
});
it("block helper for undefined value", function() {
shouldCompileTo("{{#empty}}shouldn't render{{/empty}}", {}, "");
});
it("block helper passing a new context", function() {
var string = "{{#form yehuda}}<p>{{name}}</p>{{/form}}";
var template = CompilerContext.compile(string);
var result = template({yehuda: {name: "Yehuda"}}, { helpers: {form: function(context, options) { return "<form>" + options.fn(context) + "</form>"; }}});
equal(result, "<form><p>Yehuda</p></form>", "Context variable resolved");
});
it("block helper passing a complex path context", function() {
var string = "{{#form yehuda/cat}}<p>{{name}}</p>{{/form}}";
var template = CompilerContext.compile(string);
var result = template({yehuda: {name: "Yehuda", cat: {name: "Harold"}}}, { helpers: {form: function(context, options) { return "<form>" + options.fn(context) + "</form>"; }}});
equal(result, "<form><p>Harold</p></form>", "Complex path variable resolved");
});
it("nested block helpers", function() {
var string = "{{#form yehuda}}<p>{{name}}</p>{{#link}}Hello{{/link}}{{/form}}";
var template = CompilerContext.compile(string);
var result = template({
yehuda: {name: "Yehuda" }
}, {
helpers: {
link: function(options) { return "<a href='" + this.name + "'>" + options.fn(this) + "</a>"; },
form: function(context, options) { return "<form>" + options.fn(context) + "</form>"; }
}
});
equal(result, "<form><p>Yehuda</p><a href='Yehuda'>Hello</a></form>", "Both blocks executed");
});
it("block helper inverted sections", function() {
var string = "{{#list people}}{{name}}{{^}}<em>Nobody's here</em>{{/list}}";
var list = function(context, options) {
if (context.length > 0) {
var out = "<ul>";
for(var i = 0,j=context.length; i < j; i++) {
out += "<li>";
out += options.fn(context[i]);
out += "</li>";
}
out += "</ul>";
return out;
} else {
return "<p>" + options.inverse(this) + "</p>";
}
};
var hash = {people: [{name: "Alan"}, {name: "Yehuda"}]};
var empty = {people: []};
var rootMessage = {
people: [],
message: "Nobody's here"
};
var messageString = "{{#list people}}Hello{{^}}{{message}}{{/list}}";
// the meaning here may be kind of hard to catch, but list.not is always called,
// so we should see the output of both
shouldCompileTo(string, [hash, { list: list }], "<ul><li>Alan</li><li>Yehuda</li></ul>", "an inverse wrapper is passed in as a new context");
shouldCompileTo(string, [empty, { list: list }], "<p><em>Nobody's here</em></p>", "an inverse wrapper can be optionally called");
shouldCompileTo(messageString, [rootMessage, { list: list }], "<p>Nobody&#x27;s here</p>", "the context of an inverse is the parent of the block");
});
describe("helpers hash", function() {
it("providing a helpers hash", function() {
shouldCompileTo("Goodbye {{cruel}} {{world}}!", [{cruel: "cruel"}, {world: function() { return "world"; }}], "Goodbye cruel world!",
"helpers hash is available");
shouldCompileTo("Goodbye {{#iter}}{{cruel}} {{world}}{{/iter}}!", [{iter: [{cruel: "cruel"}]}, {world: function() { return "world"; }}],
"Goodbye cruel world!", "helpers hash is available inside other blocks");
});
it("in cases of conflict, helpers win", function() {
shouldCompileTo("{{{lookup}}}", [{lookup: 'Explicit'}, {lookup: function() { return 'helpers'; }}], "helpers",
"helpers hash has precedence escaped expansion");
shouldCompileTo("{{lookup}}", [{lookup: 'Explicit'}, {lookup: function() { return 'helpers'; }}], "helpers",
"helpers hash has precedence simple expansion");
});
it("the helpers hash is available is nested contexts", function() {
shouldCompileTo("{{#outer}}{{#inner}}{{helper}}{{/inner}}{{/outer}}",
[{'outer': {'inner': {'unused':[]}}}, {'helper': function() { return 'helper'; }}], "helper",
"helpers hash is available in nested contexts.");
});
it("the helper hash should augment the global hash", function() {
handlebarsEnv.registerHelper('test_helper', function() { return 'found it!'; });
shouldCompileTo(
"{{test_helper}} {{#if cruel}}Goodbye {{cruel}} {{world}}!{{/if}}", [
{cruel: "cruel"},
{world: function() { return "world!"; }}
],
"found it! Goodbye cruel world!!");
});
});
it("Multiple global helper registration", function() {
var helpers = handlebarsEnv.helpers;
handlebarsEnv.helpers = {};
handlebarsEnv.registerHelper({
'if': helpers['if'],
world: function() { return "world!"; },
test_helper: function() { return 'found it!'; }
});
shouldCompileTo(
"{{test_helper}} {{#if cruel}}Goodbye {{cruel}} {{world}}!{{/if}}",
[{cruel: "cruel"}],
"found it! Goodbye cruel world!!");
});
it("negative number literals work", function() {
var string = 'Message: {{hello -12}}';
var hash = {};
var helpers = {hello: function(times) {
if(typeof times !== 'number') { times = "NaN"; }
return "Hello " + times + " times";
}};
shouldCompileTo(string, [hash, helpers], "Message: Hello -12 times", "template with a negative integer literal");
});
describe("String literal parameters", function() {
it("simple literals work", function() {
var string = 'Message: {{hello "world" 12 true false}}';
var hash = {};
var helpers = {hello: function(param, times, bool1, bool2) {
if(typeof times !== 'number') { times = "NaN"; }
if(typeof bool1 !== 'boolean') { bool1 = "NaB"; }
if(typeof bool2 !== 'boolean') { bool2 = "NaB"; }
return "Hello " + param + " " + times + " times: " + bool1 + " " + bool2;
}};
shouldCompileTo(string, [hash, helpers], "Message: Hello world 12 times: true false", "template with a simple String literal");
});
it("using a quote in the middle of a parameter raises an error", function() {
var string = 'Message: {{hello wo"rld"}}';
shouldThrow(function() {
CompilerContext.compile(string);
}, Error);
});
it("escaping a String is possible", function(){
var string = 'Message: {{{hello "\\"world\\""}}}';
var hash = {};
var helpers = {hello: function(param) { return "Hello " + param; }};
shouldCompileTo(string, [hash, helpers], "Message: Hello \"world\"", "template with an escaped String literal");
});
it("it works with ' marks", function() {
var string = 'Message: {{{hello "Alan\'s world"}}}';
var hash = {};
var helpers = {hello: function(param) { return "Hello " + param; }};
shouldCompileTo(string, [hash, helpers], "Message: Hello Alan's world", "template with a ' mark");
});
});
it("negative number literals work", function() {
var string = 'Message: {{hello -12}}';
var hash = {};
var helpers = {hello: function(times) {
if(typeof times !== 'number') { times = "NaN"; }
return "Hello " + times + " times";
}};
shouldCompileTo(string, [hash, helpers], "Message: Hello -12 times", "template with a negative integer literal");
});
describe("multiple parameters", function() {
it("simple multi-params work", function() {
var string = 'Message: {{goodbye cruel world}}';
var hash = {cruel: "cruel", world: "world"};
var helpers = {goodbye: function(cruel, world) { return "Goodbye " + cruel + " " + world; }};
shouldCompileTo(string, [hash, helpers], "Message: Goodbye cruel world", "regular helpers with multiple params");
});
it("block multi-params work", function() {
var string = 'Message: {{#goodbye cruel world}}{{greeting}} {{adj}} {{noun}}{{/goodbye}}';
var hash = {cruel: "cruel", world: "world"};
var helpers = {goodbye: function(cruel, world, options) {
return options.fn({greeting: "Goodbye", adj: cruel, noun: world});
}};
shouldCompileTo(string, [hash, helpers], "Message: Goodbye cruel world", "block helpers with multiple params");
});
});
describe('hash', function() {
it("helpers can take an optional hash", function() {
var template = CompilerContext.compile('{{goodbye cruel="CRUEL" world="WORLD" times=12}}');
var helpers = {
goodbye: function(options) {
return "GOODBYE " + options.hash.cruel + " " + options.hash.world + " " + options.hash.times + " TIMES";
}
};
var context = {};
var result = template(context, {helpers: helpers});
equals(result, "GOODBYE CRUEL WORLD 12 TIMES", "Helper output hash");
});
it("helpers can take an optional hash with booleans", function() {
var helpers = {
goodbye: function(options) {
if (options.hash.print === true) {
return "GOODBYE " + options.hash.cruel + " " + options.hash.world;
} else if (options.hash.print === false) {
return "NOT PRINTING";
} else {
return "THIS SHOULD NOT HAPPEN";
}
}
};
var context = {};
var template = CompilerContext.compile('{{goodbye cruel="CRUEL" world="WORLD" print=true}}');
var result = template(context, {helpers: helpers});
equals(result, "GOODBYE CRUEL WORLD", "Helper output hash");
template = CompilerContext.compile('{{goodbye cruel="CRUEL" world="WORLD" print=false}}');
result = template(context, {helpers: helpers});
equals(result, "NOT PRINTING", "Boolean helper parameter honored");
});
it("block helpers can take an optional hash", function() {
var template = CompilerContext.compile('{{#goodbye cruel="CRUEL" times=12}}world{{/goodbye}}');
var helpers = {
goodbye: function(options) {
return "GOODBYE " + options.hash.cruel + " " + options.fn(this) + " " + options.hash.times + " TIMES";
}
};
var result = template({}, {helpers: helpers});
equals(result, "GOODBYE CRUEL world 12 TIMES", "Hash parameters output");
});
it("block helpers can take an optional hash with single quoted stings", function() {
var template = CompilerContext.compile("{{#goodbye cruel='CRUEL' times=12}}world{{/goodbye}}");
var helpers = {
goodbye: function(options) {
return "GOODBYE " + options.hash.cruel + " " + options.fn(this) + " " + options.hash.times + " TIMES";
}
};
var result = template({}, {helpers: helpers});
equals(result, "GOODBYE CRUEL world 12 TIMES", "Hash parameters output");
});
it("block helpers can take an optional hash with booleans", function() {
var helpers = {
goodbye: function(options) {
if (options.hash.print === true) {
return "GOODBYE " + options.hash.cruel + " " + options.fn(this);
} else if (options.hash.print === false) {
return "NOT PRINTING";
} else {
return "THIS SHOULD NOT HAPPEN";
}
}
};
var template = CompilerContext.compile('{{#goodbye cruel="CRUEL" print=true}}world{{/goodbye}}');
var result = template({}, {helpers: helpers});
equals(result, "GOODBYE CRUEL world", "Boolean hash parameter honored");
template = CompilerContext.compile('{{#goodbye cruel="CRUEL" print=false}}world{{/goodbye}}');
result = template({}, {helpers: helpers});
equals(result, "NOT PRINTING", "Boolean hash parameter honored");
});
});
describe("helperMissing", function() {
it("if a context is not found, helperMissing is used", function() {
shouldThrow(function() {
var template = CompilerContext.compile("{{hello}} {{link_to world}}");
template({});
}, undefined, /Missing helper: 'link_to'/);
});
it("if a context is not found, custom helperMissing is used", function() {
var string = "{{hello}} {{link_to world}}";
var context = { hello: "Hello", world: "world" };
var helpers = {
helperMissing: function(helper, context) {
if(helper === "link_to") {
return new Handlebars.SafeString("<a>" + context + "</a>");
}
}
};
shouldCompileTo(string, [context, helpers], "Hello <a>world</a>");
});
});
describe("knownHelpers", function() {
it("Known helper should render helper", function() {
var template = CompilerContext.compile("{{hello}}", {knownHelpers: {"hello" : true}});
var result = template({}, {helpers: {hello: function() { return "foo"; }}});
equal(result, "foo", "'foo' should === '" + result);
});
it("Unknown helper in knownHelpers only mode should be passed as undefined", function() {
var template = CompilerContext.compile("{{typeof hello}}", {knownHelpers: {'typeof': true}, knownHelpersOnly: true});
var result = template({}, {helpers: {'typeof': function(arg) { return typeof arg; }, hello: function() { return "foo"; }}});
equal(result, "undefined", "'undefined' should === '" + result);
});
it("Builtin helpers available in knownHelpers only mode", function() {
var template = CompilerContext.compile("{{#unless foo}}bar{{/unless}}", {knownHelpersOnly: true});
var result = template({});
equal(result, "bar", "'bar' should === '" + result);
});
it("Field lookup works in knownHelpers only mode", function() {
var template = CompilerContext.compile("{{foo}}", {knownHelpersOnly: true});
var result = template({foo: 'bar'});
equal(result, "bar", "'bar' should === '" + result);
});
it("Conditional blocks work in knownHelpers only mode", function() {
var template = CompilerContext.compile("{{#foo}}bar{{/foo}}", {knownHelpersOnly: true});
var result = template({foo: 'baz'});
equal(result, "bar", "'bar' should === '" + result);
});
it("Invert blocks work in knownHelpers only mode", function() {
var template = CompilerContext.compile("{{^foo}}bar{{/foo}}", {knownHelpersOnly: true});
var result = template({foo: false});
equal(result, "bar", "'bar' should === '" + result);
});
it("Functions are bound to the context in knownHelpers only mode", function() {
var template = CompilerContext.compile("{{foo}}", {knownHelpersOnly: true});
var result = template({foo: function() { return this.bar; }, bar: 'bar'});
equal(result, "bar", "'bar' should === '" + result);
});
it("Unknown helper call in knownHelpers only mode should throw", function() {
shouldThrow(function() {
CompilerContext.compile("{{typeof hello}}", {knownHelpersOnly: true});
}, Error);
});
});
describe("blockHelperMissing", function() {
it("lambdas are resolved by blockHelperMissing, not handlebars proper", function() {
var string = "{{#truthy}}yep{{/truthy}}";
var data = { truthy: function() { return true; } };
shouldCompileTo(string, data, "yep");
});
it("lambdas resolved by blockHelperMissing are bound to the context", function() {
var string = "{{#truthy}}yep{{/truthy}}";
var boundData = { truthy: function() { return this.truthiness(); }, truthiness: function() { return false; } };
shouldCompileTo(string, boundData, "");
});
});
describe('name conflicts', function() {
it("helpers take precedence over same-named context properties", function() {
var template = CompilerContext.compile("{{goodbye}} {{cruel world}}");
var helpers = {
goodbye: function() {
return this.goodbye.toUpperCase();
},
cruel: function(world) {
return "cruel " + world.toUpperCase();
}
};
var context = {
goodbye: "goodbye",
world: "world"
};
var result = template(context, {helpers: helpers});
equals(result, "GOODBYE cruel WORLD", "Helper executed");
});
it("helpers take precedence over same-named context properties$", function() {
var template = CompilerContext.compile("{{#goodbye}} {{cruel world}}{{/goodbye}}");
var helpers = {
goodbye: function(options) {
return this.goodbye.toUpperCase() + options.fn(this);
},
cruel: function(world) {
return "cruel " + world.toUpperCase();
}
};
var context = {
goodbye: "goodbye",
world: "world"
};
var result = template(context, {helpers: helpers});
equals(result, "GOODBYE cruel WORLD", "Helper executed");
});
it("Scoped names take precedence over helpers", function() {
var template = CompilerContext.compile("{{this.goodbye}} {{cruel world}} {{cruel this.goodbye}}");
var helpers = {
goodbye: function() {
return this.goodbye.toUpperCase();
},
cruel: function(world) {
return "cruel " + world.toUpperCase();
}
};
var context = {
goodbye: "goodbye",
world: "world"
};
var result = template(context, {helpers: helpers});
equals(result, "goodbye cruel WORLD cruel GOODBYE", "Helper not executed");
});
it("Scoped names take precedence over block helpers", function() {
var template = CompilerContext.compile("{{#goodbye}} {{cruel world}}{{/goodbye}} {{this.goodbye}}");
var helpers = {
goodbye: function(options) {
return this.goodbye.toUpperCase() + options.fn(this);
},
cruel: function(world) {
return "cruel " + world.toUpperCase();
}
};
var context = {
goodbye: "goodbye",
world: "world"
};
var result = template(context, {helpers: helpers});
equals(result, "GOODBYE cruel WORLD goodbye", "Helper executed");
});
});
});
+59
View File
@@ -0,0 +1,59 @@
<html>
<head>
<title>Mocha</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="/node_modules/mocha/mocha.css" />
<style>
.headless .suite > h1,
.headless .test.pass {
display: none;
}
</style>
<script>
// Show only errors in "headless", non-interactive mode.
if (/headless=true/.test(location.href)) {
document.documentElement.className = 'headless';
}
</script>
<script src="/node_modules/mocha/mocha.js"></script>
<script>
mocha.setup('bdd');
</script>
<script src="/dist/handlebars.js"></script>
<script src="/spec/env/common.js"></script>
<script>
var CompilerContext = {
compile: function(template, options) {
var templateSpec = handlebarsEnv.precompile(template, options);
return handlebarsEnv.template(safeEval(templateSpec));
},
compileWithPartial: function(template, options) {
return handlebarsEnv.compile(template, options);
}
};
function safeEval(templateSpec) {
var ret;
eval('ret = ' + templateSpec);
return ret;
}
</script>
<script src="/tmp/tests.js"></script>
<script>
onload = function(){
// The test harness leaks under FF. We should have decent global leak coverage from other tests
if (!navigator.userAgent.match(/Firefox\/([\d.]+)/)) {
mocha.checkLeaks();
}
mocha.run();
};
</script>
</head>
<body>
<div id="mocha"></div>
</body>
</html>
+69
View File
@@ -0,0 +1,69 @@
/*global Handlebars, beforeEach, handlebarsEnv, shouldCompileTo */
describe('javascript-compiler api', function() {
if (!Handlebars.JavaScriptCompiler) {
return;
}
describe('#nameLookup', function() {
var $superName;
beforeEach(function() {
$superName = handlebarsEnv.JavaScriptCompiler.prototype.nameLookup;
});
afterEach(function() {
handlebarsEnv.JavaScriptCompiler.prototype.nameLookup = $superName;
});
it('should allow override', function() {
handlebarsEnv.JavaScriptCompiler.prototype.nameLookup = function(parent, name) {
return parent + '.bar_' + name;
};
shouldCompileTo("{{foo}}", { bar_foo: "food" }, "food");
});
});
describe('#compilerInfo', function() {
var $superCheck, $superInfo;
beforeEach(function() {
$superCheck = handlebarsEnv.VM.checkRevision;
$superInfo = handlebarsEnv.JavaScriptCompiler.prototype.compilerInfo;
});
afterEach(function() {
handlebarsEnv.VM.checkRevision = $superCheck;
handlebarsEnv.JavaScriptCompiler.prototype.compilerInfo = $superInfo;
});
it('should allow compilerInfo override', function() {
handlebarsEnv.JavaScriptCompiler.prototype.compilerInfo = function() {
return 'this.compilerInfo = "crazy";';
};
handlebarsEnv.VM.checkRevision = function(compilerInfo) {
if (compilerInfo !== 'crazy') {
throw new Error('It didn\'t work');
}
};
shouldCompileTo("{{foo}} ", { foo: "food" }, "food ");
});
});
describe('buffer', function() {
var $superAppend, $superCreate;
beforeEach(function() {
$superAppend = handlebarsEnv.JavaScriptCompiler.prototype.appendToBuffer;
$superCreate = handlebarsEnv.JavaScriptCompiler.prototype.initializeBuffer;
});
afterEach(function() {
handlebarsEnv.JavaScriptCompiler.prototype.appendToBuffer = $superAppend;
handlebarsEnv.JavaScriptCompiler.prototype.initializeBuffer = $superCreate;
});
it('should allow init buffer override', function() {
handlebarsEnv.JavaScriptCompiler.prototype.initializeBuffer = function() {
return this.quotedString('foo_');
};
shouldCompileTo("{{foo}} ", { foo: "food" }, "foo_food ");
});
it('should allow append buffer override', function() {
handlebarsEnv.JavaScriptCompiler.prototype.appendToBuffer = function(string) {
return $superAppend.call(this, string + ' + "_foo"');
};
shouldCompileTo("{{foo}}", { foo: "food" }, "food_foo");
});
});
});
Submodule
+1
Submodule spec/mustache added at 83b0721610
+174
View File
@@ -0,0 +1,174 @@
/*global Handlebars */
describe('parser', function() {
if (!Handlebars.print) {
return;
}
function ast_for(template) {
var ast = Handlebars.parse(template);
return Handlebars.print(ast);
}
it('parses simple mustaches', function() {
equals(ast_for('{{foo}}'), "{{ ID:foo [] }}\n");
equals(ast_for('{{foo?}}'), "{{ ID:foo? [] }}\n");
equals(ast_for('{{foo_}}'), "{{ ID:foo_ [] }}\n");
equals(ast_for('{{foo-}}'), "{{ ID:foo- [] }}\n");
equals(ast_for('{{foo:}}'), "{{ ID:foo: [] }}\n");
});
it('parses simple mustaches with data', function() {
equals(ast_for("{{@foo}}"), "{{ @ID:foo [] }}\n");
});
it('parses mustaches with paths', function() {
equals(ast_for("{{foo/bar}}"), "{{ PATH:foo/bar [] }}\n");
});
it('parses mustaches with this/foo', function() {
equals(ast_for("{{this/foo}}"), "{{ ID:foo [] }}\n");
});
it('parses mustaches with - in a path', function() {
equals(ast_for("{{foo-bar}}"), "{{ ID:foo-bar [] }}\n");
});
it('parses mustaches with parameters', function() {
equals(ast_for("{{foo bar}}"), "{{ ID:foo [ID:bar] }}\n");
});
it('parses mustaches with string parameters', function() {
equals(ast_for("{{foo bar \"baz\" }}"), '{{ ID:foo [ID:bar, "baz"] }}\n');
});
it('parses mustaches with INTEGER parameters', function() {
equals(ast_for("{{foo 1}}"), "{{ ID:foo [INTEGER{1}] }}\n");
});
it('parses mustaches with BOOLEAN parameters', function() {
equals(ast_for("{{foo true}}"), "{{ ID:foo [BOOLEAN{true}] }}\n");
equals(ast_for("{{foo false}}"), "{{ ID:foo [BOOLEAN{false}] }}\n");
});
it('parses mutaches with DATA parameters', function() {
equals(ast_for("{{foo @bar}}"), "{{ ID:foo [@ID:bar] }}\n");
});
it('parses mustaches with hash arguments', function() {
equals(ast_for("{{foo bar=baz}}"), "{{ ID:foo [] HASH{bar=ID:baz} }}\n");
equals(ast_for("{{foo bar=1}}"), "{{ ID:foo [] HASH{bar=INTEGER{1}} }}\n");
equals(ast_for("{{foo bar=true}}"), "{{ ID:foo [] HASH{bar=BOOLEAN{true}} }}\n");
equals(ast_for("{{foo bar=false}}"), "{{ ID:foo [] HASH{bar=BOOLEAN{false}} }}\n");
equals(ast_for("{{foo bar=@baz}}"), "{{ ID:foo [] HASH{bar=@ID:baz} }}\n");
equals(ast_for("{{foo bar=baz bat=bam}}"), "{{ ID:foo [] HASH{bar=ID:baz, bat=ID:bam} }}\n");
equals(ast_for("{{foo bar=baz bat=\"bam\"}}"), '{{ ID:foo [] HASH{bar=ID:baz, bat="bam"} }}\n');
equals(ast_for("{{foo bat='bam'}}"), '{{ ID:foo [] HASH{bat="bam"} }}\n');
equals(ast_for("{{foo omg bar=baz bat=\"bam\"}}"), '{{ ID:foo [ID:omg] HASH{bar=ID:baz, bat="bam"} }}\n');
equals(ast_for("{{foo omg bar=baz bat=\"bam\" baz=1}}"), '{{ ID:foo [ID:omg] HASH{bar=ID:baz, bat="bam", baz=INTEGER{1}} }}\n');
equals(ast_for("{{foo omg bar=baz bat=\"bam\" baz=true}}"), '{{ ID:foo [ID:omg] HASH{bar=ID:baz, bat="bam", baz=BOOLEAN{true}} }}\n');
equals(ast_for("{{foo omg bar=baz bat=\"bam\" baz=false}}"), '{{ ID:foo [ID:omg] HASH{bar=ID:baz, bat="bam", baz=BOOLEAN{false}} }}\n');
});
it('parses contents followed by a mustache', function() {
equals(ast_for("foo bar {{baz}}"), "CONTENT[ \'foo bar \' ]\n{{ ID:baz [] }}\n");
});
it('parses a partial', function() {
equals(ast_for("{{> foo }}"), "{{> PARTIAL:foo }}\n");
});
it('parses a partial with context', function() {
equals(ast_for("{{> foo bar}}"), "{{> PARTIAL:foo ID:bar }}\n");
});
it('parses a partial with a complex name', function() {
equals(ast_for("{{> shared/partial?.bar}}"), "{{> PARTIAL:shared/partial?.bar }}\n");
});
it('parses a comment', function() {
equals(ast_for("{{! this is a comment }}"), "{{! ' this is a comment ' }}\n");
});
it('parses a multi-line comment', function() {
equals(ast_for("{{!\nthis is a multi-line comment\n}}"), "{{! \'\nthis is a multi-line comment\n\' }}\n");
});
it('parses an inverse section', function() {
equals(ast_for("{{#foo}} bar {{^}} baz {{/foo}}"), "BLOCK:\n {{ ID:foo [] }}\n PROGRAM:\n CONTENT[ ' bar ' ]\n {{^}}\n CONTENT[ ' baz ' ]\n");
});
it('parses an inverse (else-style) section', function() {
equals(ast_for("{{#foo}} bar {{else}} baz {{/foo}}"), "BLOCK:\n {{ ID:foo [] }}\n PROGRAM:\n CONTENT[ ' bar ' ]\n {{^}}\n CONTENT[ ' baz ' ]\n");
});
it('parses empty blocks', function() {
equals(ast_for("{{#foo}}{{/foo}}"), "BLOCK:\n {{ ID:foo [] }}\n PROGRAM:\n");
});
it('parses empty blocks with empty inverse section', function() {
equals(ast_for("{{#foo}}{{^}}{{/foo}}"), "BLOCK:\n {{ ID:foo [] }}\n PROGRAM:\n");
});
it('parses empty blocks with empty inverse (else-style) section', function() {
equals(ast_for("{{#foo}}{{else}}{{/foo}}"), "BLOCK:\n {{ ID:foo [] }}\n PROGRAM:\n");
});
it('parses non-empty blocks with empty inverse section', function() {
equals(ast_for("{{#foo}} bar {{^}}{{/foo}}"), "BLOCK:\n {{ ID:foo [] }}\n PROGRAM:\n CONTENT[ ' bar ' ]\n {{^}}\n");
});
it('parses non-empty blocks with empty inverse (else-style) section', function() {
equals(ast_for("{{#foo}} bar {{else}}{{/foo}}"), "BLOCK:\n {{ ID:foo [] }}\n PROGRAM:\n CONTENT[ ' bar ' ]\n {{^}}\n");
});
it('parses empty blocks with non-empty inverse section', function() {
equals(ast_for("{{#foo}}{{^}} bar {{/foo}}"), "BLOCK:\n {{ ID:foo [] }}\n PROGRAM:\n {{^}}\n CONTENT[ ' bar ' ]\n");
});
it('parses empty blocks with non-empty inverse (else-style) section', function() {
equals(ast_for("{{#foo}}{{else}} bar {{/foo}}"), "BLOCK:\n {{ ID:foo [] }}\n PROGRAM:\n {{^}}\n CONTENT[ ' bar ' ]\n");
});
it('parses a standalone inverse section', function() {
equals(ast_for("{{^foo}}bar{{/foo}}"), "BLOCK:\n {{ ID:foo [] }}\n {{^}}\n CONTENT[ 'bar' ]\n");
});
it("raises if there's a Parse error", function() {
shouldThrow(function() {
ast_for("foo{{^}}bar");
}, Error, /Parse error on line 1/);
shouldThrow(function() {
ast_for("{{foo}");
}, Error, /Parse error on line 1/);
shouldThrow(function() {
ast_for("{{foo &}}");
}, Error, /Parse error on line 1/);
shouldThrow(function() {
ast_for("{{#goodbyes}}{{/hellos}}");
}, Error, /goodbyes doesn't match hellos/);
});
it('knows how to report the correct line number in errors', function() {
shouldThrow(function() {
ast_for("hello\nmy\n{{foo}");
}, Error, /Parse error on line 3/);
shouldThrow(function() {
ast_for("hello\n\nmy\n\n{{foo}");
}, Error, /Parse error on line 5/);
});
it('knows how to report the correct line number in errors when the first character is a newline', function() {
shouldThrow(function() {
ast_for("\n\nhello\n\nmy\n\n{{foo}");
}, Error, /Parse error on line 7/);
});
describe('externally compiled AST', function() {
it('can pass through an already-compiled AST', function() {
equals(ast_for(new Handlebars.AST.ProgramNode([ new Handlebars.AST.ContentNode("Hello")])), "CONTENT[ \'Hello\' ]\n");
});
});
});
-218
View File
@@ -1,218 +0,0 @@
require "spec_helper"
describe "Parser" do
let(:handlebars) { @context["Handlebars"] }
def program(&block)
ASTBuilder.build do
program do
instance_eval(&block)
end
end
end
def ast_for(string)
ast = handlebars.parse(string)
handlebars.print(ast)
end
class ASTBuilder
def self.build(&block)
ret = new
ret.evaluate(&block)
ret.out
end
attr_reader :out
def initialize
@padding = 0
@out = ""
end
def evaluate(&block)
instance_eval(&block)
end
def pad(string)
@out << (" " * @padding) + string + "\n"
end
def with_padding
@padding += 1
ret = yield
@padding -= 1
ret
end
def program
pad("PROGRAM:")
with_padding { yield }
end
def inverse
pad("{{^}}")
with_padding { yield }
end
def block
pad("BLOCK:")
with_padding { yield }
end
def inverted_block
pad("INVERSE:")
with_padding { yield }
end
def mustache(id, params = [], hash = nil)
hash = " #{hash}" if hash
pad("{{ #{id} [#{params.join(", ")}]#{hash} }}")
end
def partial(id, context = nil)
content = id.dup
content << " #{context}" if context
pad("{{> #{content} }}")
end
def comment(comment)
pad("{{! '#{comment}' }}")
end
def multiline_comment(comment)
pad("{{! '\n#{comment}\n' }}")
end
def content(string)
pad("CONTENT[ '#{string}' ]")
end
def string(string)
string.inspect
end
def hash(*pairs)
"HASH{" + pairs.map {|k,v| "#{k}=#{v}" }.join(", ") + "}"
end
def id(id)
"ID:#{id}"
end
def path(*parts)
"PATH:#{parts.join("/")}"
end
end
it "parses simple mustaches" do
ast_for("{{foo}}").should == program { mustache id("foo") }
end
it "parses mustaches with paths" do
ast_for("{{foo/bar}}").should == program { mustache path("foo", "bar") }
end
it "parses mustaches with this/foo" do
ast_for("{{this/foo}}").should == program { mustache id("foo") }
end
it "parses mustaches with - in a path" do
ast_for("{{foo-bar}}").should == program { mustache id("foo-bar") }
end
it "parses mustaches with parameters" do
ast_for("{{foo bar}}").should == program { mustache id("foo"), [id("bar")] }
end
it "parses mustaches with hash arguments" do
ast_for("{{foo bar=baz}}").should == program do
mustache id("foo"), [], hash(["bar", "ID:baz"])
end
ast_for("{{foo bar=baz bat=bam}}").should == program do
mustache id("foo"), [], hash(["bar", "ID:baz"], ["bat", "ID:bam"])
end
ast_for("{{foo bar=baz bat=\"bam\"}}").should == program do
mustache id("foo"), [], hash(["bar", "ID:baz"], ["bat", "\"bam\""])
end
ast_for("{{foo omg bar=baz bat=\"bam\"}}").should == program do
mustache id("foo"), [id("omg")], hash(["bar", id("baz")], ["bat", string("bam")])
end
end
it "parses mustaches with string parameters" do
ast_for("{{foo bar \"baz\" }}").should == program { mustache id("foo"), [id("bar"), string("baz")] }
end
it "parses contents followed by a mustache" do
ast_for("foo bar {{baz}}").should == program do
content "foo bar "
mustache id("baz")
end
end
it "parses a partial" do
ast_for("{{> foo }}").should == program { partial id("foo") }
end
it "parses a partial with context" do
ast_for("{{> foo bar}}").should == program { partial id("foo"), id("bar") }
end
it "parses a comment" do
ast_for("{{! this is a comment }}").should == program do
comment " this is a comment "
end
end
it "parses a multi-line comment" do
ast_for("{{!\nthis is a multi-line comment\n}}").should == program do
multiline_comment "this is a multi-line comment"
end
end
it "parses an inverse section" do
ast_for("{{#foo}} bar {{^}} baz {{/foo}}").should == program do
block do
mustache id("foo")
program do
content " bar "
end
inverse do
content " baz "
end
end
end
end
it "parses a standalone inverse section" do
ast_for("{{^foo}}bar{{/foo}}").should == program do
inverted_block do
mustache id("foo")
program do
content "bar"
end
end
end
end
it "raises if there's a Parse error" do
lambda { ast_for("{{foo}") }.should raise_error(V8::JSError, /Parse error on line 1/)
lambda { ast_for("{{foo &}}")}.should raise_error(V8::JSError, /Parse error on line 1/)
end
it "knows how to report the correct line number in errors" do
lambda { ast_for("hello\nmy\n{{foo}") }.should raise_error(V8::JSError, /Parse error on line 3/m)
lambda { ast_for("hello\n\nmy\n\n{{foo}") }.should raise_error(V8::JSError, /Parse error on line 5/m)
end
it "knows how to report the correct line number in errors when the first character is a newline" do
lambda { ast_for("\n\nhello\n\nmy\n\n{{foo}") }.should raise_error(V8::JSError, /Parse error on line 7/m)
end
end
+132
View File
@@ -0,0 +1,132 @@
/*global CompilerContext, shouldCompileTo, shouldCompileToWithPartials */
describe('partials', function() {
it("basic partials", function() {
var string = "Dudes: {{#dudes}}{{> dude}}{{/dudes}}";
var partial = "{{name}} ({{url}}) ";
var hash = {dudes: [{name: "Yehuda", url: "http://yehuda"}, {name: "Alan", url: "http://alan"}]};
shouldCompileToWithPartials(string, [hash, {}, {dude: partial}], true, "Dudes: Yehuda (http://yehuda) Alan (http://alan) ",
"Basic partials output based on current context.");
});
it("partials with context", function() {
var string = "Dudes: {{>dude dudes}}";
var partial = "{{#this}}{{name}} ({{url}}) {{/this}}";
var hash = {dudes: [{name: "Yehuda", url: "http://yehuda"}, {name: "Alan", url: "http://alan"}]};
shouldCompileToWithPartials(string, [hash, {}, {dude: partial}], true, "Dudes: Yehuda (http://yehuda) Alan (http://alan) ",
"Partials can be passed a context");
});
it("partials with undefined context", function() {
var string = "Dudes: {{>dude dudes}}";
var partial = "{{foo}} Empty";
var hash = {};
shouldCompileToWithPartials(string, [hash, {}, {dude: partial}], true, "Dudes: Empty");
});
it("partial in a partial", function() {
var string = "Dudes: {{#dudes}}{{>dude}}{{/dudes}}";
var dude = "{{name}} {{> url}} ";
var url = "<a href='{{url}}'>{{url}}</a>";
var hash = {dudes: [{name: "Yehuda", url: "http://yehuda"}, {name: "Alan", url: "http://alan"}]};
shouldCompileToWithPartials(string, [hash, {}, {dude: dude, url: url}], true, "Dudes: Yehuda <a href='http://yehuda'>http://yehuda</a> Alan <a href='http://alan'>http://alan</a> ", "Partials are rendered inside of other partials");
});
it("rendering undefined partial throws an exception", function() {
shouldThrow(function() {
var template = CompilerContext.compile("{{> whatever}}");
template();
}, Handlebars.Exception, 'The partial whatever could not be found');
});
it("rendering template partial in vm mode throws an exception", function() {
shouldThrow(function() {
var template = CompilerContext.compile("{{> whatever}}");
template();
}, Handlebars.Exception, 'The partial whatever could not be found');
});
it("rendering function partial in vm mode", function() {
var string = "Dudes: {{#dudes}}{{> dude}}{{/dudes}}";
var partial = function(context) {
return context.name + ' (' + context.url + ') ';
};
var hash = {dudes: [{name: "Yehuda", url: "http://yehuda"}, {name: "Alan", url: "http://alan"}]};
shouldCompileTo(string, [hash, {}, {dude: partial}], "Dudes: Yehuda (http://yehuda) Alan (http://alan) ",
"Function partials output based in VM.");
});
it("GH-14: a partial preceding a selector", function() {
var string = "Dudes: {{>dude}} {{another_dude}}";
var dude = "{{name}}";
var hash = {name:"Jeepers", another_dude:"Creepers"};
shouldCompileToWithPartials(string, [hash, {}, {dude:dude}], true, "Dudes: Jeepers Creepers", "Regular selectors can follow a partial");
});
it("Partials with slash paths", function() {
var string = "Dudes: {{> shared/dude}}";
var dude = "{{name}}";
var hash = {name:"Jeepers", another_dude:"Creepers"};
shouldCompileToWithPartials(string, [hash, {}, {'shared/dude':dude}], true, "Dudes: Jeepers", "Partials can use literal paths");
});
it("Partials with slash and point paths", function() {
var string = "Dudes: {{> shared/dude.thing}}";
var dude = "{{name}}";
var hash = {name:"Jeepers", another_dude:"Creepers"};
shouldCompileToWithPartials(string, [hash, {}, {'shared/dude.thing':dude}], true, "Dudes: Jeepers", "Partials can use literal with points in paths");
});
it("Global Partials", function() {
handlebarsEnv.registerPartial('global_test', '{{another_dude}}');
var string = "Dudes: {{> shared/dude}} {{> global_test}}";
var dude = "{{name}}";
var hash = {name:"Jeepers", another_dude:"Creepers"};
shouldCompileToWithPartials(string, [hash, {}, {'shared/dude':dude}], true, "Dudes: Jeepers Creepers", "Partials can use globals or passed");
});
it("Multiple partial registration", function() {
handlebarsEnv.registerPartial({
'shared/dude': '{{name}}',
global_test: '{{another_dude}}'
});
var string = "Dudes: {{> shared/dude}} {{> global_test}}";
var hash = {name:"Jeepers", another_dude:"Creepers"};
shouldCompileToWithPartials(string, [hash], true, "Dudes: Jeepers Creepers", "Partials can use globals or passed");
});
it("Partials with integer path", function() {
var string = "Dudes: {{> 404}}";
var dude = "{{name}}";
var hash = {name:"Jeepers", another_dude:"Creepers"};
shouldCompileToWithPartials(string, [hash, {}, {404:dude}], true, "Dudes: Jeepers", "Partials can use literal paths");
});
it("Partials with complex path", function() {
var string = "Dudes: {{> 404/asdf?.bar}}";
var dude = "{{name}}";
var hash = {name:"Jeepers", another_dude:"Creepers"};
shouldCompileToWithPartials(string, [hash, {}, {'404/asdf?.bar':dude}], true, "Dudes: Jeepers", "Partials can use literal paths");
});
it("Partials with escaped", function() {
var string = "Dudes: {{> [+404/asdf?.bar]}}";
var dude = "{{name}}";
var hash = {name:"Jeepers", another_dude:"Creepers"};
shouldCompileToWithPartials(string, [hash, {}, {'+404/asdf?.bar':dude}], true, "Dudes: Jeepers", "Partials can use literal paths");
});
it("Partials with string", function() {
var string = "Dudes: {{> \"+404/asdf?.bar\"}}";
var dude = "{{name}}";
var hash = {name:"Jeepers", another_dude:"Creepers"};
shouldCompileToWithPartials(string, [hash, {}, {'+404/asdf?.bar':dude}], true, "Dudes: Jeepers", "Partials can use literal paths");
});
it('should handle empty partial', function() {
var string = "Dudes: {{#dudes}}{{> dude}}{{/dudes}}";
var partial = "";
var hash = {dudes: [{name: "Yehuda", url: "http://yehuda"}, {name: "Alan", url: "http://alan"}]};
shouldCompileToWithPartials(string, [hash, {}, {dude: partial}], true, "Dudes: "); });
});
-786
View File
@@ -1,786 +0,0 @@
module("basic context");
Handlebars.registerHelper('helperMissing', function(helper, context) {
if(helper === "link_to") {
return new Handlebars.SafeString("<a>" + context + "</a>");
}
});
var shouldCompileTo = function(string, hashOrArray, expected, message) {
var template = Handlebars.compile(string), ary;
if(Object.prototype.toString.call(hashOrArray) === "[object Array]") {
helpers = hashOrArray[1];
if(helpers) {
for(var prop in Handlebars.helpers) {
helpers[prop] = Handlebars.helpers[prop];
}
}
ary = [];
ary.push(hashOrArray[0]);
ary.push({ helpers: hashOrArray[1], partials: hashOrArray[2] });
} else {
ary = [hashOrArray];
}
result = template.apply(this, ary);
equal(result, expected, "'" + expected + "' should === '" + result + "': " + message);
};
var shouldThrow = function(fn, exception, message) {
var caught = false;
try {
fn();
}
catch (e) {
if (e instanceof exception) {
caught = true;
}
}
ok(caught, message || null);
}
test("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");
});
test("comments", function() {
shouldCompileTo("{{! Goodbye}}Goodbye\n{{cruel}}\n{{world}}!",
{cruel: "cruel", world: "world"}, "Goodbye\ncruel\nworld!",
"comments are ignored");
});
test("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");
});
test("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");
});
test("newlines", function() {
shouldCompileTo("Alan's\nTest", {}, "Alan's\nTest");
shouldCompileTo("Alan's\rTest", {}, "Alan's\rTest");
});
test("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");
});
test("escaping expressions", function() {
shouldCompileTo("{{{awesome}}}", {awesome: "&\"\\<>"}, '&\"\\<>',
"expressions with 3 handlebars aren't escaped");
shouldCompileTo("{{awesome}}", {awesome: "&\"\\<>"}, '&amp;\"\\&lt;&gt;',
"by default expressions should be escaped");
shouldCompileTo("{{&awesome}}", {awesome: "&\"\\<>"}, '&\"\\<>',
"expressions with {{& handlebars aren't escaped");
});
test("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");
});
test("functions", function() {
shouldCompileTo("{{awesome}}", {awesome: function() { return "Awesome"; }}, "Awesome",
"functions are called and render their output");
});
test("functions with context argument", function() {
shouldCompileTo("{{awesome frank}}",
{awesome: function(context) { return context; },
frank: "Frank"},
"Frank", "functions are called with context arguments");
});
test("paths with hyphens", function() {
shouldCompileTo("{{foo-bar}}", {"foo-bar": "baz"}, "baz", "Paths can contain hyphens (-)");
});
test("nested paths", function() {
shouldCompileTo("Goodbye {{alan/expression}} world!", {alan: {expression: "beautiful"}},
"Goodbye beautiful world!", "Nested paths access nested objects");
});
test("nested paths with empty string value", function() {
shouldCompileTo("Goodbye {{alan/expression}} world!", {alan: {expression: ""}},
"Goodbye world!", "Nested paths access nested objects with empty string");
});
test("--- TODO --- bad idea nested paths", function() {
return;
var hash = {goodbyes: [{text: "goodbye"}, {text: "Goodbye"}, {text: "GOODBYE"}], world: "world"};
shouldThrow(function() {
Handlebars.compile("{{#goodbyes}}{{../name/../name}}{{/goodbyes}}")(hash);
}, Handlebars.Exception,
"Cannot jump (..) into previous context after moving into a context.");
var string = "{{#goodbyes}}{{.././world}} {{/goodbyes}}";
shouldCompileTo(string, hash, "world world world ", "Same context (.) is ignored in paths");
});
test("that current context path ({{.}}) doesn't hit fallback", function() {
shouldCompileTo("test: {{.}}", [null, {helper: "awesome"}], "test: ");
});
test("complex but empty paths", function() {
shouldCompileTo("{{person/name}}", {person: {name: null}}, "");
shouldCompileTo("{{person/name}}", {person: {}}, "");
});
test("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");
});
module("inverted sections");
test("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.");
});
test("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.");
});
test("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.");
});
test("inverted section using result of function call", function() {
var string = "{{goodbyes}}{{^goodbyes}}Right On!{{/goodbyes}}";
var hash = {goodbyes: function() { return false; }}
shouldCompileTo(string, hash, "Right On!", "Inverted section rendered when result of function in expression is false.");
});
module("blocks");
test("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");
});
test("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");
});
test("incorrectly matched blocks", function() {
var string = "{{#goodbyes}}{{/hellos}}";
shouldThrow(function() {
Handlebars.compile(string);
}, Handlebars.Exception, "Incorrectly matched blocks return an exception at compile time.");
});
test("nested iteration", function() {
});
test("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");
});
test("helper with complex lookup", function() {
var string = "{{#goodbyes}}{{{link ../prefix}}}{{/goodbyes}}"
var hash = {prefix: "/root", goodbyes: [{text: "Goodbye", url: "goodbye"}]};
var fallback = {link: function(prefix) {
return "<a href='" + prefix + "/" + this.url + "'>" + this.text + "</a>"
}};
shouldCompileTo(string, [hash, fallback], "<a href='/root/goodbye'>Goodbye</a>")
});
test("helper block with complex lookup expression", function() {
var string = "{{#goodbyes}}{{../name}}{{/goodbyes}}"
var hash = {name: "Alan"};
var fallback = {goodbyes: function(fn) {
var out = "";
var byes = ["Goodbye", "goodbye", "GOODBYE"];
for (var i = 0,j = byes.length; i < j; i++) {
out += byes[i] + " " + fn(this) + "! ";
}
return out;
}};
shouldCompileTo(string, [hash, fallback], "Goodbye Alan! goodbye Alan! GOODBYE Alan! ");
});
test("helper with complex lookup and nested template", function() {
var string = "{{#goodbyes}}{{#link ../prefix}}{{text}}{{/link}}{{/goodbyes}}";
var hash = {prefix: '/root', goodbyes: [{text: "Goodbye", url: "goodbye"}]};
var fallback = {link: function (prefix, fn) {
return "<a href='" + prefix + "/" + this.url + "'>" + fn(this) + "</a>";
}};
shouldCompileTo(string, [hash, fallback], "<a href='/root/goodbye'>Goodbye</a>")
});
test("block with deep nested complex lookup", function() {
var string = "{{#outer}}Goodbye {{#inner}}cruel {{../../omg}}{{/inner}}{{/outer}}";
var hash = {omg: "OMG!", outer: [{ inner: [{ text: "goodbye" }] }] };
shouldCompileTo(string, hash, "Goodbye cruel OMG!");
});
test("block helper", function() {
var string = "{{#goodbyes}}{{text}}! {{/goodbyes}}cruel {{world}}!";
var template = Handlebars.compile(string);
result = template({goodbyes: function(fn) { return fn({text: "GOODBYE"}); }, world: "world"});
equal(result, "GOODBYE! cruel world!");
});
test("block helper staying in the same context", function() {
var string = "{{#form}}<p>{{name}}</p>{{/form}}"
var template = Handlebars.compile(string);
result = template({form: function(fn) { return "<form>" + fn(this) + "</form>" }, name: "Yehuda"});
equal(result, "<form><p>Yehuda</p></form>");
});
test("block helper should have context in this", function() {
var source = "<ul>{{#people}}<li>{{#link}}{{name}}{{/link}}</li>{{/people}}</ul>";
var link = function(fn) {
return '<a href="/people/' + this.id + '">' + fn(this) + '</a>';
};
var data = { "people": [
{ "name": "Alan", "id": 1 },
{ "name": "Yehuda", "id": 2 }
]};
shouldCompileTo(source, [data, {link: link}], "<ul><li><a href=\"/people/1\">Alan</a></li><li><a href=\"/people/2\">Yehuda</a></li></ul>");
});
test("block helper for undefined value", function() {
shouldCompileTo("{{#empty}}shouldn't render{{/empty}}", {}, "");
});
test("block helper passing a new context", function() {
var string = "{{#form yehuda}}<p>{{name}}</p>{{/form}}"
var template = Handlebars.compile(string);
result = template({form: function(context, fn) { return "<form>" + fn(context) + "</form>" }, yehuda: {name: "Yehuda"}});
equal(result, "<form><p>Yehuda</p></form>");
});
test("block helper passing a complex path context", function() {
var string = "{{#form yehuda/cat}}<p>{{name}}</p>{{/form}}"
var template = Handlebars.compile(string);
result = template({form: function(context, fn) { return "<form>" + fn(context) + "</form>" }, yehuda: {name: "Yehuda", cat: {name: "Harold"}}});
equal(result, "<form><p>Harold</p></form>");
});
test("nested block helpers", function() {
var string = "{{#form yehuda}}<p>{{name}}</p>{{#link}}Hello{{/link}}{{/form}}"
var template = Handlebars.compile(string);
result = template({
form: function(context, fn) { return "<form>" + fn(context) + "</form>" },
yehuda: {name: "Yehuda",
link: function(fn) { return "<a href='" + this.name + "'>" + fn(this) + "</a>"; }
}
});
equal(result, "<form><p>Yehuda</p><a href='Yehuda'>Hello</a></form>");
});
test("block inverted sections", function() {
shouldCompileTo("{{#people}}{{name}}{{^}}{{none}}{{/people}}", {none: "No people"},
"No people");
});
test("block inverted sections with empty arrays", function() {
shouldCompileTo("{{#people}}{{name}}{{^}}{{none}}{{/people}}", {none: "No people", people: []},
"No people");
});
test("block helper inverted sections", function() {
var string = "{{#list people}}{{name}}{{^}}<em>Nobody's here</em>{{/list}}"
var list = function(context, fn, inverse) {
if (context.length > 0) {
var out = "<ul>";
for(var i = 0,j=context.length; i < j; i++) {
out += "<li>";
out += fn(context[i]);
out += "</li>";
}
out += "</ul>";
return out;
} else {
return "<p>" + inverse(this) + "</p>";
}
};
var hash = {list: list, people: [{name: "Alan"}, {name: "Yehuda"}]};
var empty = {list: list, people: []};
var rootMessage = {
list: function(context, fn, inverse) { if(context.length === 0) { return "<p>" + inverse(this) + "</p>"; } },
people: [],
message: "Nobody's here"
}
var messageString = "{{#list people}}Hello{{^}}{{message}}{{/list}}";
// the meaning here may be kind of hard to catch, but list.not is always called,
// so we should see the output of both
shouldCompileTo(string, hash, "<ul><li>Alan</li><li>Yehuda</li></ul>", "an inverse wrapper is passed in as a new context");
shouldCompileTo(string, empty, "<p><em>Nobody's here</em></p>", "an inverse wrapper can be optionally called");
shouldCompileTo(messageString, rootMessage, "<p>Nobody's here</p>", "the context of an inverse is the parent of the block");
});
module("fallback hash");
test("providing a fallback hash", function() {
shouldCompileTo("Goodbye {{cruel}} {{world}}!", [{cruel: "cruel"}, {world: "world"}], "Goodbye cruel world!",
"Fallback hash is available");
shouldCompileTo("Goodbye {{#iter}}{{cruel}} {{world}}{{/iter}}!", [{iter: [{cruel: "cruel"}]}, {world: "world"}],
"Goodbye cruel world!", "Fallback hash is available inside other blocks");
});
test("in cases of conflict, the explicit hash wins", function() {
});
test("the fallback hash is available is nested contexts", function() {
});
module("partials");
test("basic partials", function() {
var string = "Dudes: {{#dudes}}{{> dude}}{{/dudes}}";
var partial = "{{name}} ({{url}}) ";
var hash = {dudes: [{name: "Yehuda", url: "http://yehuda"}, {name: "Alan", url: "http://alan"}]};
shouldCompileTo(string, [hash, {}, {dude: partial}], "Dudes: Yehuda (http://yehuda) Alan (http://alan) ",
"Basic partials output based on current context.");
});
test("partials with context", function() {
var string = "Dudes: {{>dude dudes}}";
var partial = "{{#this}}{{name}} ({{url}}) {{/this}}";
var hash = {dudes: [{name: "Yehuda", url: "http://yehuda"}, {name: "Alan", url: "http://alan"}]};
shouldCompileTo(string, [hash, {}, {dude: partial}], "Dudes: Yehuda (http://yehuda) Alan (http://alan) ",
"Partials can be passed a context");
});
test("partial in a partial", function() {
var string = "Dudes: {{#dudes}}{{>dude}}{{/dudes}}";
var dude = "{{name}} {{> url}} ";
var url = "<a href='{{url}}'>{{url}}</a>";
var hash = {dudes: [{name: "Yehuda", url: "http://yehuda"}, {name: "Alan", url: "http://alan"}]};
shouldCompileTo(string, [hash, {}, {dude: dude, url: url}], "Dudes: Yehuda <a href='http://yehuda'>http://yehuda</a> Alan <a href='http://alan'>http://alan</a> ", "Partials are rendered inside of other partials");
});
test("rendering undefined partial throws an exception", function() {
shouldThrow(function() {
var template = Handlebars.compile("{{> whatever}}");
template();
}, Handlebars.Exception, "Should throw exception");
});
test("GH-14: a partial preceding a selector", function() {
var string = "Dudes: {{>dude}} {{another_dude}}";
var dude = "{{name}}";
var hash = {name:"Jeepers", another_dude:"Creepers"};
shouldCompileTo(string, [hash, {}, {dude:dude}], "Dudes: Jeepers Creepers", "Regular selectors can follow a partial");
});
module("String literal parameters");
test("simple literals work", function() {
var string = 'Message: {{hello "world"}}';
var hash = {};
var fallback = {hello: function(param) { return "Hello " + param; }}
shouldCompileTo(string, [hash, fallback], "Message: Hello world", "template with a simple String literal");
});
test("using a quote in the middle of a parameter raises an error", function() {
shouldThrow(function() {
var string = 'Message: {{hello wo"rld"}}';
Handlebars.compile(string);
}, Error, "should throw exception");
});
test("escaping a String is possible", function(){
var string = 'Message: {{hello "\\"world\\""}}';
var hash = {}
var fallback = {hello: function(param) { return "Hello " + param; }}
shouldCompileTo(string, [hash, fallback], "Message: Hello \"world\"", "template with an escaped String literal");
});
test("it works with ' marks", function() {
var string = 'Message: {{hello "Alan\'s world"}}';
var hash = {}
var fallback = {hello: function(param) { return "Hello " + param; }}
shouldCompileTo(string, [hash, fallback], "Message: Hello Alan's world", "template with a ' mark");
});
module("multiple parameters");
test("simple multi-params work", function() {
var string = 'Message: {{goodbye cruel world}}';
var hash = {cruel: "cruel", world: "world"}
var fallback = {goodbye: function(cruel, world) { return "Goodbye " + cruel + " " + world; }}
shouldCompileTo(string, [hash, fallback], "Message: Goodbye cruel world", "regular helpers with multiple params");
});
test("block multi-params work", function() {
var string = 'Message: {{#goodbye cruel world}}{{greeting}} {{adj}} {{noun}}{{/goodbye}}';
var hash = {cruel: "cruel", world: "world"}
var fallback = {goodbye: function(cruel, world, fn) {
return fn({greeting: "Goodbye", adj: cruel, noun: world});
}}
shouldCompileTo(string, [hash, fallback], "Message: Goodbye cruel world", "block helpers with multiple params");
})
module("safestring");
test("constructing a safestring from a string and checking its type", function() {
var safe = new Handlebars.SafeString("testing 1, 2, 3");
ok(safe instanceof Handlebars.SafeString, "SafeString is an instance of Handlebars.SafeString");
equal(safe, "testing 1, 2, 3", "SafeString is equivalent to its underlying string");
});
module("helperMissing");
test("if a context is not found, helperMissing is used", function() {
var string = "{{hello}} {{link_to world}}"
var context = { hello: "Hello", world: "world" };
shouldCompileTo(string, context, "Hello <a>world</a>")
});
module("built-in helpers");
test("with", function() {
var string = "{{#with person}}{{first}} {{last}}{{/with}}";
shouldCompileTo(string, {person: {first: "Alan", last: "Johnson"}}, "Alan Johnson");
});
test("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");
});
test("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");
});
test("overriding property lookup", function() {
});
test("passing in data to a compiled function that expects data - works with helpers", function() {
var template = Handlebars.compile("{{hello}}", {data: true});
var helpers = {
hello: function(options) {
return options.data.adjective + " " + this.noun;
}
};
var result = template({noun: "cat"}, {helpers: helpers, data: {adjective: "happy"}});
equals("happy cat", result);
});
test("passing in data to a compiled function that expects data - works with helpers and parameters", function() {
var template = Handlebars.compile("{{hello world}}", {data: true});
var helpers = {
hello: function(noun, options) {
return options.data.adjective + " " + noun + (this.exclaim ? "!" : "");
}
};
var result = template({exclaim: true, world: "world"}, {helpers: helpers, data: {adjective: "happy"}});
equals("happy world!", result);
});
test("passing in data to a compiled function that expects data - works with block helpers", function() {
var template = Handlebars.compile("{{#hello}}{{world}}{{/hello}}", {data: true});
var helpers = {
hello: function(fn) {
return fn(this);
},
world: function(options) {
return options.data.adjective + " world" + (this.exclaim ? "!" : "");
}
};
var result = template({exclaim: true}, {helpers: helpers, data: {adjective: "happy"}});
equals("happy world!", result);
});
test("passing in data to a compiled function that expects data - works with block helpers that use ..", function() {
var template = Handlebars.compile("{{#hello}}{{world ../zomg}}{{/hello}}", {data: true});
var helpers = {
hello: function(fn) {
return fn({exclaim: "?"});
},
world: function(thing, options) {
return options.data.adjective + " " + thing + (this.exclaim || "");
}
};
var result = template({exclaim: true, zomg: "world"}, {helpers: helpers, data: {adjective: "happy"}});
equals("happy world?", result);
});
test("passing in data to a compiled function that expects data - data is passed to with block helpers where children use ..", function() {
var template = Handlebars.compile("{{#hello}}{{world ../zomg}}{{/hello}}", {data: true});
var helpers = {
hello: function(fn, inverse) {
return fn.data.accessData + " " + fn({exclaim: "?"});
},
world: function(thing, options) {
return options.data.adjective + " " + thing + (this.exclaim || "");
}
};
var result = template({exclaim: true, zomg: "world"}, {helpers: helpers, data: {adjective: "happy", accessData: "#win"}});
equals("#win happy world?", result);
});
test("you can override inherited data when invoking a helper", function() {
var template = Handlebars.compile("{{#hello}}{{world zomg}}{{/hello}}", {data: true});
var helpers = {
hello: function(fn) {
return fn({exclaim: "?", zomg: "world"}, null, null, {adjective: "sad"});
},
world: function(thing, options) {
return options.data.adjective + " " + thing + (this.exclaim || "");
}
};
var result = template({exclaim: true, zomg: "planet"}, {helpers: helpers, data: {adjective: "happy"}});
equals("sad world?", result);
});
test("you can override inherited data when invoking a helper with depth", function() {
var template = Handlebars.compile("{{#hello}}{{world ../zomg}}{{/hello}}", {data: true});
var helpers = {
hello: function(fn) {
return fn({exclaim: "?"}, null, null, {adjective: "sad"});
},
world: function(thing, options) {
return options.data.adjective + " " + thing + (this.exclaim || "");
}
};
var result = template({exclaim: true, zomg: "world"}, {helpers: helpers, data: {adjective: "happy"}});
equals("sad world?", result);
});
test("helpers take precedence over same-named context properties", function() {
var template = Handlebars.compile("{{goodbye}} {{cruel world}}");
var helpers = {
goodbye: function() {
return this.goodbye.toUpperCase();
}
};
var context = {
cruel: function(world) {
return "cruel " + world.toUpperCase();
},
goodbye: "goodbye",
world: "world"
};
var result = template(context, {helpers: helpers});
equals(result, "GOODBYE cruel WORLD");
});
test("helpers take precedence over same-named context properties", function() {
var template = Handlebars.compile("{{#goodbye}} {{cruel world}}{{/goodbye}}");
var helpers = {
goodbye: function(fn) {
return this.goodbye.toUpperCase() + fn(this);
}
};
var context = {
cruel: function(world) {
return "cruel " + world.toUpperCase();
},
goodbye: "goodbye",
world: "world"
};
var result = template(context, {helpers: helpers});
equals(result, "GOODBYE cruel WORLD");
});
test("helpers can take an optional hash", function() {
var template = Handlebars.compile('{{goodbye cruel="CRUEL" world="WORLD"}}');
var helpers = {
goodbye: function(options) {
return "GOODBYE " + options.hash.cruel + " " + options.hash.world;
}
};
var context = {};
var result = template(context, {helpers: helpers});
equals(result, "GOODBYE CRUEL WORLD");
});
test("block helpers can take an optional hash", function() {
var template = Handlebars.compile('{{#goodbye cruel="CRUEL"}}world{{/goodbye}}');
var helpers = {
goodbye: function(options) {
return "GOODBYE " + options.hash.cruel + " " + options.fn(this);
}
};
var result = template({}, {helpers: helpers});
equals(result, "GOODBYE CRUEL world");
});
test("arguments to helpers can be retrieved from options hash in string form", function() {
var template = Handlebars.compile('{{wycats is.a slave.driver}}', {stringParams: true});
var helpers = {
wycats: function(passiveVoice, noun, options) {
return "HELP ME MY BOSS " + passiveVoice + ' ' + noun;
}
};
var result = template({}, {helpers: helpers});
equals(result, "HELP ME MY BOSS is.a slave.driver");
});
test("when using block form, arguments to helpers can be retrieved from options hash in string form", function() {
var template = Handlebars.compile('{{#wycats is.a slave.driver}}help :({{/wycats}}', {stringParams: true});
var helpers = {
wycats: function(passiveVoice, noun, options) {
return "HELP ME MY BOSS " + passiveVoice + ' ' +
noun + ': ' + options.fn(this);
}
};
var result = template({}, {helpers: helpers});
equals(result, "HELP ME MY BOSS is.a slave.driver: help :(");
});
test("when inside a block in String mode, .. passes the appropriate context in the options hash", function() {
var template = Handlebars.compile('{{#with dale}}{{tomdale ../need dad.joke}}{{/with}}', {stringParams: true});
var helpers = {
tomdale: function(desire, noun, options) {
return "STOP ME FROM READING HACKER NEWS I " +
options.contexts[0][desire] + " " + noun;
},
"with": function(context, options) {
return options.fn(options.contexts[0][context]);
}
};
var result = template({
dale: {},
need: 'need-a'
}, {helpers: helpers});
equals(result, "STOP ME FROM READING HACKER NEWS I need-a dad.joke");
});
test("when inside a block in String mode, .. passes the appropriate context in the options hash to a block helper", function() {
var template = Handlebars.compile('{{#with dale}}{{#tomdale ../need dad.joke}}wot{{/tomdale}}{{/with}}', {stringParams: true});
var helpers = {
tomdale: function(desire, noun, options) {
return "STOP ME FROM READING HACKER NEWS I " +
options.contexts[0][desire] + " " + noun + " " +
options.fn(this);
},
"with": function(context, options) {
return options.fn(options.contexts[0][context]);
}
};
var result = template({
dale: {},
need: 'need-a'
}, {helpers: helpers});
equals(result, "STOP ME FROM READING HACKER NEWS I need-a dad.joke wot");
});
+130
View File
@@ -0,0 +1,130 @@
/*global CompilerContext, Handlebars, shouldCompileTo, shouldThrow */
describe('Regressions', function() {
it("GH-94: Cannot read property of undefined", function() {
var data = {"books":[{"title":"The origin of species","author":{"name":"Charles Darwin"}},{"title":"Lazarillo de Tormes"}]};
var string = "{{#books}}{{title}}{{author.name}}{{/books}}";
shouldCompileTo(string, data, "The origin of speciesCharles DarwinLazarillo de Tormes",
"Renders without an undefined property error");
});
it("GH-150: Inverted sections print when they shouldn't", function() {
var string = "{{^set}}not set{{/set}} :: {{#set}}set{{/set}}";
shouldCompileTo(string, {}, "not set :: ", "inverted sections run when property isn't present in context");
shouldCompileTo(string, {set: undefined}, "not set :: ", "inverted sections run when property is undefined");
shouldCompileTo(string, {set: false}, "not set :: ", "inverted sections run when property is false");
shouldCompileTo(string, {set: true}, " :: set", "inverted sections don't run when property is true");
});
it("GH-158: Using array index twice, breaks the template", function() {
var string = "{{arr.[0]}}, {{arr.[1]}}";
var data = { "arr": [1,2] };
shouldCompileTo(string, data, "1, 2", "it works as expected");
});
it("bug reported by @fat where lambdas weren't being properly resolved", function() {
var string = "<strong>This is a slightly more complicated {{thing}}.</strong>.\n{{! Just ignore this business. }}\nCheck this out:\n{{#hasThings}}\n<ul>\n{{#things}}\n<li class={{className}}>{{word}}</li>\n{{/things}}</ul>.\n{{/hasThings}}\n{{^hasThings}}\n\n<small>Nothing to check out...</small>\n{{/hasThings}}";
var data = {
thing: function() {
return "blah";
},
things: [
{className: "one", word: "@fat"},
{className: "two", word: "@dhg"},
{className: "three", word:"@sayrer"}
],
hasThings: function() {
return true;
}
};
var output = "<strong>This is a slightly more complicated blah.</strong>.\n\nCheck this out:\n\n<ul>\n\n<li class=one>@fat</li>\n\n<li class=two>@dhg</li>\n\n<li class=three>@sayrer</li>\n</ul>.\n\n";
shouldCompileTo(string, data, output);
});
it('GH-408: Multiple loops fail', function() {
var context = [
{ name: "John Doe", location: { city: "Chicago" } },
{ name: "Jane Doe", location: { city: "New York"} }
];
var template = CompilerContext.compile('{{#.}}{{name}}{{/.}}{{#.}}{{name}}{{/.}}{{#.}}{{name}}{{/.}}');
var result = template(context);
equals(result, "John DoeJane DoeJohn DoeJane DoeJohn DoeJane Doe", 'It should output multiple times');
});
it('GS-428: Nested if else rendering', function() {
var succeedingTemplate = '{{#inverse}} {{#blk}} Unexpected {{/blk}} {{else}} {{#blk}} Expected {{/blk}} {{/inverse}}';
var failingTemplate = '{{#inverse}} {{#blk}} Unexpected {{/blk}} {{else}} {{#blk}} Expected {{/blk}} {{/inverse}}';
var helpers = {
blk: function(block) { return block.fn(''); },
inverse: function(block) { return block.inverse(''); }
};
shouldCompileTo(succeedingTemplate, [{}, helpers], ' Expected ');
shouldCompileTo(failingTemplate, [{}, helpers], ' Expected ');
});
it('GH-458: Scoped this identifier', function() {
shouldCompileTo('{{./foo}}', {foo: 'bar'}, 'bar');
});
it('GH-375: Unicode line terminators', function() {
shouldCompileTo('\u2028', {}, '\u2028');
});
it('GH-534: Object prototype aliases', function() {
Object.prototype[0xD834] = true;
shouldCompileTo('{{foo}}', { foo: 'bar' }, 'bar');
delete Object.prototype[0xD834];
});
it('GH-437: Matching escaping', function() {
shouldThrow(function() {
CompilerContext.compile('{{{a}}');
}, Error);
shouldThrow(function() {
CompilerContext.compile('{{a}}}');
}, Error);
});
it("GH-676: Using array in escaping mustache fails", function() {
var string = "{{arr}}";
var data = { "arr": [1,2] };
shouldCompileTo(string, data, data.arr.toString(), "it works as expected");
});
it("Mustache man page", function() {
var string = "Hello {{name}}. You have just won ${{value}}!{{#in_ca}} Well, ${{taxed_value}}, after taxes.{{/in_ca}}";
var data = {
"name": "Chris",
"value": 10000,
"taxed_value": 10000 - (10000 * 0.4),
"in_ca": true
};
shouldCompileTo(string, data, "Hello Chris. You have just won $10000! Well, $6000, after taxes.", "the hello world mustache example works");
});
it("Passing falsy values to Handlebars.compile throws an error", function() {
shouldThrow(function() {
CompilerContext.compile(null);
}, Error, 'You must pass a string or Handlebars AST to Handlebars.precompile. You passed null');
});
if (Handlebars.AST) {
it("can pass through an already-compiled AST via compile/precompile", function() {
equal(Handlebars.compile(new Handlebars.AST.ProgramNode([ new Handlebars.AST.ContentNode("Hello")]))(), 'Hello');
});
it("can pass through an empty string", function() {
equal(Handlebars.compile('')(), '');
});
}
});
+23
View File
@@ -0,0 +1,23 @@
if (typeof(require) !== 'undefined' && require.extensions[".handlebars"]) {
describe('Require', function() {
it('Load .handlebars files with require()', function() {
var template = require("./artifacts/example_1");
equal(template, require("./artifacts/example_1.handlebars"));
var expected = 'foo\n';
var result = template({foo: "foo"});
equal(result, expected);
});
it('Load .hbs files with require()', function() {
var template = require("./artifacts/example_2");
equal(template, require("./artifacts/example_2.hbs"));
var expected = 'Hello, World!\n';
var result = template({name: "World"});
equal(result, expected);
});
});
}
-106
View File
@@ -1,106 +0,0 @@
require "v8"
# Monkey patches due to bugs in RubyRacer
class V8::JSError
def initialize(try, to)
@to = to
begin
super(initialize_unsafe(try))
rescue Exception => e
# Original code does not make an Array here
@boundaries = [Boundary.new(:rbframes => e.backtrace)]
@value = e
super("BUG! please report. JSError#initialize failed!: #{e.message}")
end
end
def parse_js_frames(try)
raw = @to.rb(try.StackTrace())
if raw && !raw.empty?
raw.split("\n")[1..-1].tap do |frames|
# Original code uses strip!, and the frames are not guaranteed to be strippable
frames.each {|frame| frame.strip.chomp!(",")}
end
else
[]
end
end
end
module Handlebars
module Spec
def self.js_backtrace(context)
begin
context.eval("throw")
rescue V8::JSError => e
return e.backtrace(:javascript)
end
end
def self.remove_exports(string)
match = string.match(%r{\A(.*?)^// BEGIN\(BROWSER\)\n(.*)\n^// END\(BROWSER\)(.*?)\Z}m)
prelines = match ? match[1].count("\n") + 1 : 0
ret = match ? match[2] : string
("\n" * prelines) + ret
end
def self.js_load(file)
str = File.read(file)
CONTEXT.eval(remove_exports(str), file)
end
CONTEXT = V8::Context.new
CONTEXT.instance_eval do |context|
context["exports"] = nil
context["p"] = proc do |val|
p val if ENV["DEBUG_JS"]
end
context["puts"] = proc do |val|
puts val if ENV["DEBUG_JS"]
end
context["puts_node"] = proc do |val|
puts context["Handlebars"]["PrintVisitor"].new.accept(val)
puts
end
context["puts_caller"] = proc do
puts "BACKTRACE:"
puts Handlebars::Spec.js_backtrace(context)
puts
end
Handlebars::Spec.js_load('lib/handlebars/parser.js')
Handlebars::Spec.js_load('lib/handlebars/base.js');
Handlebars::Spec.js_load('lib/handlebars/ast.js');
Handlebars::Spec.js_load('lib/handlebars/visitor.js');
Handlebars::Spec.js_load('lib/handlebars/printer.js')
Handlebars::Spec.js_load('lib/handlebars/utils.js')
Handlebars::Spec.js_load('lib/handlebars/compiler.js')
Handlebars::Spec.js_load('lib/handlebars.js')
context["Handlebars"]["logger"]["level"] = ENV["DEBUG_JS"] ? context["Handlebars"]["logger"][ENV["DEBUG_JS"]] : 4
context["Handlebars"]["logger"]["log"] = proc do |level, str|
logger_level = context["Handlebars"]["logger"]["level"].to_i
if logger_level <= level
puts str
end
end
end
end
end
require "test/unit/assertions"
RSpec.configure do |config|
config.include Test::Unit::Assertions
config.before(:all) do
@context = Handlebars::Spec::CONTEXT
end
end
+161
View File
@@ -0,0 +1,161 @@
describe('string params mode', function() {
it("arguments to helpers can be retrieved from options hash in string form", function() {
var template = CompilerContext.compile('{{wycats is.a slave.driver}}', {stringParams: true});
var helpers = {
wycats: function(passiveVoice, noun) {
return "HELP ME MY BOSS " + passiveVoice + ' ' + noun;
}
};
var result = template({}, {helpers: helpers});
equals(result, "HELP ME MY BOSS is.a slave.driver", "String parameters output");
});
it("when using block form, arguments to helpers can be retrieved from options hash in string form", function() {
var template = CompilerContext.compile('{{#wycats is.a slave.driver}}help :({{/wycats}}', {stringParams: true});
var helpers = {
wycats: function(passiveVoice, noun, options) {
return "HELP ME MY BOSS " + passiveVoice + ' ' +
noun + ': ' + options.fn(this);
}
};
var result = template({}, {helpers: helpers});
equals(result, "HELP ME MY BOSS is.a slave.driver: help :(", "String parameters output");
});
it("when inside a block in String mode, .. passes the appropriate context in the options hash", function() {
var template = CompilerContext.compile('{{#with dale}}{{tomdale ../need dad.joke}}{{/with}}', {stringParams: true});
var helpers = {
tomdale: function(desire, noun, options) {
return "STOP ME FROM READING HACKER NEWS I " +
options.contexts[0][desire] + " " + noun;
},
"with": function(context, options) {
return options.fn(options.contexts[0][context]);
}
};
var result = template({
dale: {},
need: 'need-a'
}, {helpers: helpers});
equals(result, "STOP ME FROM READING HACKER NEWS I need-a dad.joke", "Proper context variable output");
});
it("information about the types is passed along", function() {
var template = CompilerContext.compile('{{tomdale "need" dad.joke true false}}', { stringParams: true });
var helpers = {
tomdale: function(desire, noun, trueBool, falseBool, options) {
equal(options.types[0], 'STRING', "the string type is passed");
equal(options.types[1], 'ID', "the expression type is passed");
equal(options.types[2], 'BOOLEAN', "the expression type is passed");
equal(desire, "need", "the string form is passed for strings");
equal(noun, "dad.joke", "the string form is passed for expressions");
equal(trueBool, true, "raw booleans are passed through");
equal(falseBool, false, "raw booleans are passed through");
return "Helper called";
}
};
var result = template({}, { helpers: helpers });
equal(result, "Helper called");
});
it("hash parameters get type information", function() {
var template = CompilerContext.compile('{{tomdale he.says desire="need" noun=dad.joke bool=true}}', { stringParams: true });
var helpers = {
tomdale: function(exclamation, options) {
equal(exclamation, "he.says");
equal(options.types[0], "ID");
equal(options.hashTypes.desire, "STRING");
equal(options.hashTypes.noun, "ID");
equal(options.hashTypes.bool, "BOOLEAN");
equal(options.hash.desire, "need");
equal(options.hash.noun, "dad.joke");
equal(options.hash.bool, true);
return "Helper called";
}
};
var result = template({}, { helpers: helpers });
equal(result, "Helper called");
});
it("hash parameters get context information", function() {
var template = CompilerContext.compile('{{#with dale}}{{tomdale he.says desire="need" noun=../dad/joke bool=true}}{{/with}}', { stringParams: true });
var context = {dale: {}};
var helpers = {
tomdale: function(exclamation, options) {
equal(exclamation, "he.says");
equal(options.types[0], "ID");
equal(options.contexts.length, 1);
equal(options.hashContexts.noun, context);
equal(options.hash.desire, "need");
equal(options.hash.noun, "dad.joke");
equal(options.hash.bool, true);
return "Helper called";
},
"with": function(context, options) {
return options.fn(options.contexts[0][context]);
}
};
var result = template(context, { helpers: helpers });
equal(result, "Helper called");
});
it("when inside a block in String mode, .. passes the appropriate context in the options hash to a block helper", function() {
var template = CompilerContext.compile('{{#with dale}}{{#tomdale ../need dad.joke}}wot{{/tomdale}}{{/with}}', {stringParams: true});
var helpers = {
tomdale: function(desire, noun, options) {
return "STOP ME FROM READING HACKER NEWS I " +
options.contexts[0][desire] + " " + noun + " " +
options.fn(this);
},
"with": function(context, options) {
return options.fn(options.contexts[0][context]);
}
};
var result = template({
dale: {},
need: 'need-a'
}, {helpers: helpers});
equals(result, "STOP ME FROM READING HACKER NEWS I need-a dad.joke wot", "Proper context variable output");
});
it("with nested block ambiguous", function() {
var template = CompilerContext.compile('{{#with content}}{{#view}}{{firstName}} {{lastName}}{{/view}}{{/with}}', {stringParams: true});
var helpers = {
with: function(options) {
return "WITH";
},
view: function() {
return "VIEW";
}
};
var result = template({}, {helpers: helpers});
equals(result, "WITH");
});
});
+166
View File
@@ -0,0 +1,166 @@
/*global CompilerContext, shouldCompileTo */
describe('subexpressions', function() {
it("arg-less helper", function() {
var string = "{{foo (bar)}}!";
var context = {};
var helpers = {
foo: function(val) {
return val+val;
},
bar: function() {
return "LOL";
}
};
shouldCompileTo(string, [context, helpers], "LOLLOL!");
});
it("helper w args", function() {
var string = '{{blog (equal a b)}}';
var context = { bar: "LOL" };
var helpers = {
blog: function(val) {
return "val is " + val;
},
equal: function(x, y) {
return x === y;
}
};
shouldCompileTo(string, [context, helpers], "val is true");
});
it("supports much nesting", function() {
var string = '{{blog (equal (equal true true) true)}}';
var context = { bar: "LOL" };
var helpers = {
blog: function(val) {
return "val is " + val;
},
equal: function(x, y) {
return x === y;
}
};
shouldCompileTo(string, [context, helpers], "val is true");
});
it("provides each nested helper invocation its own options hash", function() {
var string = '{{equal (equal true true) true}}';
var lastOptions = null;
var helpers = {
equal: function(x, y, options) {
if (!options || options === lastOptions) {
throw new Error("options hash was reused");
}
lastOptions = options;
return x === y;
}
};
shouldCompileTo(string, [{}, helpers], "true");
});
it("with hashes", function() {
var string = '{{blog (equal (equal true true) true fun="yes")}}';
var context = { bar: "LOL" };
var helpers = {
blog: function(val) {
return "val is " + val;
},
equal: function(x, y) {
return x === y;
}
};
shouldCompileTo(string, [context, helpers], "val is true");
});
it("as hashes", function() {
var string = '{{blog fun=(equal (blog fun=1) "val is 1")}}';
var helpers = {
blog: function(options) {
return "val is " + options.hash.fun;
},
equal: function(x, y) {
return x === y;
}
};
shouldCompileTo(string, [{}, helpers], "val is true");
});
it("in string params mode,", function() {
var template = CompilerContext.compile('{{snog (blorg foo x=y) yeah a=b}}', {stringParams: true});
var helpers = {
snog: function(a, b, options) {
equals(a, 'foo');
equals(options.types.length, 2, "string params for outer helper processed correctly");
equals(options.types[0], 'sexpr', "string params for outer helper processed correctly");
equals(options.types[1], 'ID', "string params for outer helper processed correctly");
return a + b;
},
blorg: function(a, options) {
equals(options.types.length, 1, "string params for inner helper processed correctly");
equals(options.types[0], 'ID', "string params for inner helper processed correctly");
return a;
}
};
var result = template({
foo: {},
yeah: {}
}, {helpers: helpers});
equals(result, "fooyeah");
});
it("as hashes in string params mode", function() {
var template = CompilerContext.compile('{{blog fun=(bork)}}', {stringParams: true});
var helpers = {
blog: function(options) {
equals(options.hashTypes.fun, 'sexpr');
return "val is " + options.hash.fun;
},
bork: function() {
return "BORK";
}
};
var result = template({}, {helpers: helpers});
equals(result, "val is BORK");
});
it("subexpression functions on the context", function() {
var string = "{{foo (bar)}}!";
var context = {
bar: function() {
return "LOL";
}
};
var helpers = {
foo: function(val) {
return val+val;
}
};
shouldCompileTo(string, [context, helpers], "LOLLOL!");
});
it("subexpressions can't just be property lookups", function() {
var string = "{{foo (bar)}}!";
var context = {
bar: "LOL"
};
var helpers = {
foo: function(val) {
return val+val;
}
};
shouldThrow(function() {
shouldCompileTo(string, [context, helpers], "LOLLOL!");
});
});
});
+394
View File
@@ -0,0 +1,394 @@
function shouldMatchTokens(result, tokens) {
for (var index = 0; index < result.length; index++) {
equals(result[index].name, tokens[index]);
}
}
function shouldBeToken(result, name, text) {
equals(result.name, name);
equals(result.text, text);
}
describe('Tokenizer', function() {
if (!Handlebars.Parser) {
return;
}
function tokenize(template) {
var parser = Handlebars.Parser,
lexer = parser.lexer;
lexer.setInput(template);
var out = [],
token;
while (token = lexer.lex()) {
var result = parser.terminals_[token] || token;
if (!result || result === 'EOF' || result === 'INVALID') {
break;
}
out.push({name: result, text: lexer.yytext});
}
return out;
}
it('tokenizes a simple mustache as "OPEN ID CLOSE"', function() {
var result = tokenize("{{foo}}");
shouldMatchTokens(result, ['OPEN', 'ID', 'CLOSE']);
shouldBeToken(result[1], "ID", "foo");
});
it('supports unescaping with &', function() {
var result = tokenize("{{&bar}}");
shouldMatchTokens(result, ['OPEN', 'ID', 'CLOSE']);
shouldBeToken(result[0], "OPEN", "{{&");
shouldBeToken(result[1], "ID", "bar");
});
it('supports unescaping with {{{', function() {
var result = tokenize("{{{bar}}}");
shouldMatchTokens(result, ['OPEN_UNESCAPED', 'ID', 'CLOSE_UNESCAPED']);
shouldBeToken(result[1], "ID", "bar");
});
it('supports escaping delimiters', function() {
var result = tokenize("{{foo}} \\{{bar}} {{baz}}");
shouldMatchTokens(result, ['OPEN', 'ID', 'CLOSE', 'CONTENT', 'CONTENT', 'OPEN', 'ID', 'CLOSE']);
shouldBeToken(result[3], "CONTENT", " ");
shouldBeToken(result[4], "CONTENT", "{{bar}} ");
});
it('supports escaping multiple delimiters', function() {
var result = tokenize("{{foo}} \\{{bar}} \\{{baz}}");
shouldMatchTokens(result, ['OPEN', 'ID', 'CLOSE', 'CONTENT', 'CONTENT', 'CONTENT']);
shouldBeToken(result[3], "CONTENT", " ");
shouldBeToken(result[4], "CONTENT", "{{bar}} ");
shouldBeToken(result[5], "CONTENT", "{{baz}}");
});
it('supports escaping a triple stash', function() {
var result = tokenize("{{foo}} \\{{{bar}}} {{baz}}");
shouldMatchTokens(result, ['OPEN', 'ID', 'CLOSE', 'CONTENT', 'CONTENT', 'OPEN', 'ID', 'CLOSE']);
shouldBeToken(result[4], "CONTENT", "{{{bar}}} ");
});
it('supports escaping escape character', function() {
var result = tokenize("{{foo}} \\\\{{bar}} {{baz}}");
shouldMatchTokens(result, ['OPEN', 'ID', 'CLOSE', 'CONTENT', 'OPEN', 'ID', 'CLOSE', 'CONTENT', 'OPEN', 'ID', 'CLOSE']);
shouldBeToken(result[3], "CONTENT", " \\");
shouldBeToken(result[5], "ID", "bar");
});
it('supports escaping multiple escape characters', function() {
var result = tokenize("{{foo}} \\\\{{bar}} \\\\{{baz}}");
shouldMatchTokens(result, ['OPEN', 'ID', 'CLOSE', 'CONTENT', 'OPEN', 'ID', 'CLOSE', 'CONTENT', 'OPEN', 'ID', 'CLOSE']);
shouldBeToken(result[3], "CONTENT", " \\");
shouldBeToken(result[5], "ID", "bar");
shouldBeToken(result[7], "CONTENT", " \\");
shouldBeToken(result[9], "ID", "baz");
});
it('supports escaped mustaches after escaped escape characters', function() {
var result = tokenize("{{foo}} \\\\{{bar}} \\{{baz}}");
shouldMatchTokens(result, ['OPEN', 'ID', 'CLOSE', 'CONTENT', 'OPEN', 'ID', 'CLOSE', 'CONTENT', 'CONTENT', 'CONTENT']);
shouldBeToken(result[3], "CONTENT", " \\");
shouldBeToken(result[4], "OPEN", "{{");
shouldBeToken(result[5], "ID", "bar");
shouldBeToken(result[7], "CONTENT", " ");
shouldBeToken(result[8], "CONTENT", "{{baz}}");
});
it('supports escaped escape characters after escaped mustaches', function() {
var result = tokenize("{{foo}} \\{{bar}} \\\\{{baz}}");
shouldMatchTokens(result, ['OPEN', 'ID', 'CLOSE', 'CONTENT', 'CONTENT', 'CONTENT', 'OPEN', 'ID', 'CLOSE']);
shouldBeToken(result[4], "CONTENT", "{{bar}} ");
shouldBeToken(result[5], "CONTENT", "\\");
shouldBeToken(result[6], "OPEN", "{{");
shouldBeToken(result[7], "ID", "baz");
});
it('supports escaped escape character on a triple stash', function() {
var result = tokenize("{{foo}} \\\\{{{bar}}} {{baz}}");
shouldMatchTokens(result, ['OPEN', 'ID', 'CLOSE', 'CONTENT', 'OPEN_UNESCAPED', 'ID', 'CLOSE_UNESCAPED', 'CONTENT', 'OPEN', 'ID', 'CLOSE']);
shouldBeToken(result[3], "CONTENT", " \\");
shouldBeToken(result[5], "ID", "bar");
});
it('tokenizes a simple path', function() {
var result = tokenize("{{foo/bar}}");
shouldMatchTokens(result, ['OPEN', 'ID', 'SEP', 'ID', 'CLOSE']);
});
it('allows dot notation', function() {
var result = tokenize("{{foo.bar}}");
shouldMatchTokens(result, ['OPEN', 'ID', 'SEP', 'ID', 'CLOSE']);
shouldMatchTokens(tokenize("{{foo.bar.baz}}"), ['OPEN', 'ID', 'SEP', 'ID', 'SEP', 'ID', 'CLOSE']);
});
it('allows path literals with []', function() {
var result = tokenize("{{foo.[bar]}}");
shouldMatchTokens(result, ['OPEN', 'ID', 'SEP', 'ID', 'CLOSE']);
});
it('allows multiple path literals on a line with []', function() {
var result = tokenize("{{foo.[bar]}}{{foo.[baz]}}");
shouldMatchTokens(result, ['OPEN', 'ID', 'SEP', 'ID', 'CLOSE', 'OPEN', 'ID', 'SEP', 'ID', 'CLOSE']);
});
it('tokenizes {{.}} as OPEN ID CLOSE', function() {
var result = tokenize("{{.}}");
shouldMatchTokens(result, ['OPEN', 'ID', 'CLOSE']);
});
it('tokenizes a path as "OPEN (ID SEP)* ID CLOSE"', function() {
var result = tokenize("{{../foo/bar}}");
shouldMatchTokens(result, ['OPEN', 'ID', 'SEP', 'ID', 'SEP', 'ID', 'CLOSE']);
shouldBeToken(result[1], "ID", "..");
});
it('tokenizes a path with .. as a parent path', function() {
var result = tokenize("{{../foo.bar}}");
shouldMatchTokens(result, ['OPEN', 'ID', 'SEP', 'ID', 'SEP', 'ID', 'CLOSE']);
shouldBeToken(result[1], "ID", "..");
});
it('tokenizes a path with this/foo as OPEN ID SEP ID CLOSE', function() {
var result = tokenize("{{this/foo}}");
shouldMatchTokens(result, ['OPEN', 'ID', 'SEP', 'ID', 'CLOSE']);
shouldBeToken(result[1], "ID", "this");
shouldBeToken(result[3], "ID", "foo");
});
it('tokenizes a simple mustache with spaces as "OPEN ID CLOSE"', function() {
var result = tokenize("{{ foo }}");
shouldMatchTokens(result, ['OPEN', 'ID', 'CLOSE']);
shouldBeToken(result[1], "ID", "foo");
});
it('tokenizes a simple mustache with line breaks as "OPEN ID ID CLOSE"', function() {
var result = tokenize("{{ foo \n bar }}");
shouldMatchTokens(result, ['OPEN', 'ID', 'ID', 'CLOSE']);
shouldBeToken(result[1], "ID", "foo");
});
it('tokenizes raw content as "CONTENT"', function() {
var result = tokenize("foo {{ bar }} baz");
shouldMatchTokens(result, ['CONTENT', 'OPEN', 'ID', 'CLOSE', 'CONTENT']);
shouldBeToken(result[0], "CONTENT", "foo ");
shouldBeToken(result[4], "CONTENT", " baz");
});
it('tokenizes a partial as "OPEN_PARTIAL ID CLOSE"', function() {
var result = tokenize("{{> foo}}");
shouldMatchTokens(result, ['OPEN_PARTIAL', 'ID', 'CLOSE']);
});
it('tokenizes a partial with context as "OPEN_PARTIAL ID ID CLOSE"', function() {
var result = tokenize("{{> foo bar }}");
shouldMatchTokens(result, ['OPEN_PARTIAL', 'ID', 'ID', 'CLOSE']);
});
it('tokenizes a partial without spaces as "OPEN_PARTIAL ID CLOSE"', function() {
var result = tokenize("{{>foo}}");
shouldMatchTokens(result, ['OPEN_PARTIAL', 'ID', 'CLOSE']);
});
it('tokenizes a partial space at the }); as "OPEN_PARTIAL ID CLOSE"', function() {
var result = tokenize("{{>foo }}");
shouldMatchTokens(result, ['OPEN_PARTIAL', 'ID', 'CLOSE']);
});
it('tokenizes a partial space at the }); as "OPEN_PARTIAL ID CLOSE"', function() {
var result = tokenize("{{>foo/bar.baz }}");
shouldMatchTokens(result, ['OPEN_PARTIAL', 'ID', 'SEP', 'ID', 'SEP', 'ID', 'CLOSE']);
});
it('tokenizes a comment as "COMMENT"', function() {
var result = tokenize("foo {{! this is a comment }} bar {{ baz }}");
shouldMatchTokens(result, ['CONTENT', 'COMMENT', 'CONTENT', 'OPEN', 'ID', 'CLOSE']);
shouldBeToken(result[1], "COMMENT", " this is a comment ");
});
it('tokenizes a block comment as "COMMENT"', function() {
var result = tokenize("foo {{!-- this is a {{comment}} --}} bar {{ baz }}");
shouldMatchTokens(result, ['CONTENT', 'COMMENT', 'CONTENT', 'OPEN', 'ID', 'CLOSE']);
shouldBeToken(result[1], "COMMENT", " this is a {{comment}} ");
});
it('tokenizes a block comment with whitespace as "COMMENT"', function() {
var result = tokenize("foo {{!-- this is a\n{{comment}}\n--}} bar {{ baz }}");
shouldMatchTokens(result, ['CONTENT', 'COMMENT', 'CONTENT', 'OPEN', 'ID', 'CLOSE']);
shouldBeToken(result[1], "COMMENT", " this is a\n{{comment}}\n");
});
it('tokenizes open and closing blocks as OPEN_BLOCK, ID, CLOSE ..., OPEN_ENDBLOCK ID CLOSE', function() {
var result = tokenize("{{#foo}}content{{/foo}}");
shouldMatchTokens(result, ['OPEN_BLOCK', 'ID', 'CLOSE', 'CONTENT', 'OPEN_ENDBLOCK', 'ID', 'CLOSE']);
});
it('tokenizes inverse sections as "OPEN_INVERSE CLOSE"', function() {
shouldMatchTokens(tokenize("{{^}}"), ['OPEN_INVERSE', 'CLOSE']);
shouldMatchTokens(tokenize("{{else}}"), ['OPEN_INVERSE', 'CLOSE']);
shouldMatchTokens(tokenize("{{ else }}"), ['OPEN_INVERSE', 'CLOSE']);
});
it('tokenizes inverse sections with ID as "OPEN_INVERSE ID CLOSE"', function() {
var result = tokenize("{{^foo}}");
shouldMatchTokens(result, ['OPEN_INVERSE', 'ID', 'CLOSE']);
shouldBeToken(result[1], "ID", "foo");
});
it('tokenizes inverse sections with ID and spaces as "OPEN_INVERSE ID CLOSE"', function() {
var result = tokenize("{{^ foo }}");
shouldMatchTokens(result, ['OPEN_INVERSE', 'ID', 'CLOSE']);
shouldBeToken(result[1], "ID", "foo");
});
it('tokenizes mustaches with params as "OPEN ID ID ID CLOSE"', function() {
var result = tokenize("{{ foo bar baz }}");
shouldMatchTokens(result, ['OPEN', 'ID', 'ID', 'ID', 'CLOSE']);
shouldBeToken(result[1], "ID", "foo");
shouldBeToken(result[2], "ID", "bar");
shouldBeToken(result[3], "ID", "baz");
});
it('tokenizes mustaches with String params as "OPEN ID ID STRING CLOSE"', function() {
var result = tokenize("{{ foo bar \"baz\" }}");
shouldMatchTokens(result, ['OPEN', 'ID', 'ID', 'STRING', 'CLOSE']);
shouldBeToken(result[3], "STRING", "baz");
});
it('tokenizes mustaches with String params using single quotes as "OPEN ID ID STRING CLOSE"', function() {
var result = tokenize("{{ foo bar \'baz\' }}");
shouldMatchTokens(result, ['OPEN', 'ID', 'ID', 'STRING', 'CLOSE']);
shouldBeToken(result[3], "STRING", "baz");
});
it('tokenizes String params with spaces inside as "STRING"', function() {
var result = tokenize("{{ foo bar \"baz bat\" }}");
shouldMatchTokens(result, ['OPEN', 'ID', 'ID', 'STRING', 'CLOSE']);
shouldBeToken(result[3], "STRING", "baz bat");
});
it('tokenizes String params with escapes quotes as STRING', function() {
var result = tokenize('{{ foo "bar\\"baz" }}');
shouldMatchTokens(result, ['OPEN', 'ID', 'STRING', 'CLOSE']);
shouldBeToken(result[2], "STRING", 'bar"baz');
});
it('tokenizes String params using single quotes with escapes quotes as STRING', function() {
var result = tokenize("{{ foo 'bar\\'baz' }}");
shouldMatchTokens(result, ['OPEN', 'ID', 'STRING', 'CLOSE']);
shouldBeToken(result[2], "STRING", "bar'baz");
});
it('tokenizes numbers', function() {
var result = tokenize('{{ foo 1 }}');
shouldMatchTokens(result, ['OPEN', 'ID', 'INTEGER', 'CLOSE']);
shouldBeToken(result[2], "INTEGER", "1");
result = tokenize('{{ foo -1 }}');
shouldMatchTokens(result, ['OPEN', 'ID', 'INTEGER', 'CLOSE']);
shouldBeToken(result[2], "INTEGER", "-1");
});
it('tokenizes booleans', function() {
var result = tokenize('{{ foo true }}');
shouldMatchTokens(result, ['OPEN', 'ID', 'BOOLEAN', 'CLOSE']);
shouldBeToken(result[2], "BOOLEAN", "true");
result = tokenize('{{ foo false }}');
shouldMatchTokens(result, ['OPEN', 'ID', 'BOOLEAN', 'CLOSE']);
shouldBeToken(result[2], "BOOLEAN", "false");
});
it('tokenizes hash arguments', function() {
var result = tokenize("{{ foo bar=baz }}");
shouldMatchTokens(result, ['OPEN', 'ID', 'ID', 'EQUALS', 'ID', 'CLOSE']);
result = tokenize("{{ foo bar baz=bat }}");
shouldMatchTokens(result, ['OPEN', 'ID', 'ID', 'ID', 'EQUALS', 'ID', 'CLOSE']);
result = tokenize("{{ foo bar baz=1 }}");
shouldMatchTokens(result, ['OPEN', 'ID', 'ID', 'ID', 'EQUALS', 'INTEGER', 'CLOSE']);
result = tokenize("{{ foo bar baz=true }}");
shouldMatchTokens(result, ['OPEN', 'ID', 'ID', 'ID', 'EQUALS', 'BOOLEAN', 'CLOSE']);
result = tokenize("{{ foo bar baz=false }}");
shouldMatchTokens(result, ['OPEN', 'ID', 'ID', 'ID', 'EQUALS', 'BOOLEAN', 'CLOSE']);
result = tokenize("{{ foo bar\n baz=bat }}");
shouldMatchTokens(result, ['OPEN', 'ID', 'ID', 'ID', 'EQUALS', 'ID', 'CLOSE']);
result = tokenize("{{ foo bar baz=\"bat\" }}");
shouldMatchTokens(result, ['OPEN', 'ID', 'ID', 'ID', 'EQUALS', 'STRING', 'CLOSE']);
result = tokenize("{{ foo bar baz=\"bat\" bam=wot }}");
shouldMatchTokens(result, ['OPEN', 'ID', 'ID', 'ID', 'EQUALS', 'STRING', 'ID', 'EQUALS', 'ID', 'CLOSE']);
result = tokenize("{{foo omg bar=baz bat=\"bam\"}}");
shouldMatchTokens(result, ['OPEN', 'ID', 'ID', 'ID', 'EQUALS', 'ID', 'ID', 'EQUALS', 'STRING', 'CLOSE']);
shouldBeToken(result[2], "ID", "omg");
});
it('tokenizes special @ identifiers', function() {
var result = tokenize("{{ @foo }}");
shouldMatchTokens(result, ['OPEN', 'DATA', 'ID', 'CLOSE']);
shouldBeToken(result[2], "ID", "foo");
result = tokenize("{{ foo @bar }}");
shouldMatchTokens(result, ['OPEN', 'ID', 'DATA', 'ID', 'CLOSE']);
shouldBeToken(result[3], "ID", "bar");
result = tokenize("{{ foo bar=@baz }}");
shouldMatchTokens(result, ['OPEN', 'ID', 'ID', 'EQUALS', 'DATA', 'ID', 'CLOSE']);
shouldBeToken(result[5], "ID", "baz");
});
it('does not time out in a mustache with a single } followed by EOF', function() {
shouldMatchTokens(tokenize("{{foo}"), ['OPEN', 'ID']);
});
it('does not time out in a mustache when invalid ID characters are used', function() {
shouldMatchTokens(tokenize("{{foo & }}"), ['OPEN', 'ID']);
});
it('tokenizes subexpressions', function() {
var result = tokenize("{{foo (bar)}}");
shouldMatchTokens(result, ['OPEN', 'ID', 'OPEN_SEXPR', 'ID', 'CLOSE_SEXPR', 'CLOSE']);
shouldBeToken(result[1], "ID", "foo");
shouldBeToken(result[3], "ID", "bar");
result = tokenize("{{foo (a-x b-y)}}");
shouldMatchTokens(result, ['OPEN', 'ID', 'OPEN_SEXPR', 'ID', 'ID', 'CLOSE_SEXPR', 'CLOSE']);
shouldBeToken(result[1], "ID", "foo");
shouldBeToken(result[3], "ID", "a-x");
shouldBeToken(result[4], "ID", "b-y");
});
it('tokenizes nested subexpressions', function() {
var result = tokenize("{{foo (bar (lol rofl)) (baz)}}");
shouldMatchTokens(result, ['OPEN', 'ID', 'OPEN_SEXPR', 'ID', 'OPEN_SEXPR', 'ID', 'ID', 'CLOSE_SEXPR', 'CLOSE_SEXPR', 'OPEN_SEXPR', 'ID', 'CLOSE_SEXPR', 'CLOSE']);
shouldBeToken(result[3], "ID", "bar");
shouldBeToken(result[5], "ID", "lol");
shouldBeToken(result[6], "ID", "rofl");
shouldBeToken(result[10], "ID", "baz");
});
it('tokenizes nested subexpressions: literals', function() {
var result = tokenize("{{foo (bar (lol true) false) (baz 1) (blah 'b') (blorg \"c\")}}");
shouldMatchTokens(result, ['OPEN', 'ID', 'OPEN_SEXPR', 'ID', 'OPEN_SEXPR', 'ID', 'BOOLEAN', 'CLOSE_SEXPR', 'BOOLEAN', 'CLOSE_SEXPR', 'OPEN_SEXPR', 'ID', 'INTEGER', 'CLOSE_SEXPR', 'OPEN_SEXPR', 'ID', 'STRING', 'CLOSE_SEXPR', 'OPEN_SEXPR', 'ID', 'STRING', 'CLOSE_SEXPR', 'CLOSE']);
});
});
-193
View File
@@ -1,193 +0,0 @@
require "spec_helper"
require "timeout"
describe "Tokenizer" do
let(:parser) { @context["handlebars"] }
let(:lexer) { @context["handlebars"]["lexer"] }
Token = Struct.new(:name, :text)
def tokenize(string)
lexer.setInput(string)
out = []
while token = lexer.lex
# p token
result = parser.terminals_[token] || token
# p result
break if !result || result == "EOF" || result == "INVALID"
out << Token.new(result, lexer.yytext)
end
out
end
RSpec::Matchers.define :match_tokens do |tokens|
match do |result|
result.map(&:name).should == tokens
end
end
RSpec::Matchers.define :be_token do |name, string|
match do |token|
token.name.should == name
token.text.should == string
end
end
it "tokenizes a simple mustache as 'OPEN ID CLOSE'" do
result = tokenize("{{foo}}")
result.should match_tokens(%w(OPEN ID CLOSE))
result[1].should be_token("ID", "foo")
end
it "tokenizes a simple path" do
result = tokenize("{{foo/bar}}")
result.should match_tokens(%w(OPEN ID SEP ID CLOSE))
end
it "allows dot notation" do
result = tokenize("{{foo.bar}}")
result.should match_tokens(%w(OPEN ID SEP ID CLOSE))
tokenize("{{foo.bar.baz}}").should match_tokens(%w(OPEN ID SEP ID SEP ID CLOSE))
end
it "tokenizes {{.}} as OPEN ID CLOSE" do
result = tokenize("{{.}}")
result.should match_tokens(%w(OPEN ID CLOSE))
end
it "tokenizes a path as 'OPEN (ID SEP)* ID CLOSE'" do
result = tokenize("{{../foo/bar}}")
result.should match_tokens(%w(OPEN ID SEP ID SEP ID CLOSE))
result[1].should be_token("ID", "..")
end
it "tokenizes a path with .. as a parent path" do
result = tokenize("{{../foo.bar}}")
result.should match_tokens(%w(OPEN ID SEP ID SEP ID CLOSE))
result[1].should be_token("ID", "..")
end
it "tokenizes a path with this/foo as OPEN ID SEP ID CLOSE" do
result = tokenize("{{this/foo}}")
result.should match_tokens(%w(OPEN ID SEP ID CLOSE))
result[1].should be_token("ID", "this")
result[3].should be_token("ID", "foo")
end
it "tokenizes a simple mustahe with spaces as 'OPEN ID CLOSE'" do
result = tokenize("{{ foo }}")
result.should match_tokens(%w(OPEN ID CLOSE))
result[1].should be_token("ID", "foo")
end
it "tokenizes raw content as 'CONTENT'" do
result = tokenize("foo {{ bar }} baz")
result.should match_tokens(%w(CONTENT OPEN ID CLOSE CONTENT))
result[0].should be_token("CONTENT", "foo ")
result[4].should be_token("CONTENT", " baz")
end
it "tokenizes a partial as 'OPEN_PARTIAL ID CLOSE'" do
result = tokenize("{{> foo}}")
result.should match_tokens(%w(OPEN_PARTIAL ID CLOSE))
end
it "tokenizes a partial with context as 'OPEN_PARTIAL ID ID CLOSE'" do
result = tokenize("{{> foo bar }}")
result.should match_tokens(%w(OPEN_PARTIAL ID ID CLOSE))
end
it "tokenizes a partial without spaces as 'OPEN_PARTIAL ID CLOSE'" do
result = tokenize("{{>foo}}")
result.should match_tokens(%w(OPEN_PARTIAL ID CLOSE))
end
it "tokenizes a partial space at the end as 'OPEN_PARTIAL ID CLOSE'" do
result = tokenize("{{>foo }}")
result.should match_tokens(%w(OPEN_PARTIAL ID CLOSE))
end
it "tokenizes a comment as 'COMMENT'" do
result = tokenize("foo {{! this is a comment }} bar {{ baz }}")
result.should match_tokens(%w(CONTENT COMMENT CONTENT OPEN ID CLOSE))
result[1].should be_token("COMMENT", " this is a comment ")
end
it "tokenizes open and closing blocks as 'OPEN_BLOCK ID CLOSE ... OPEN_ENDBLOCK ID CLOSE'" do
result = tokenize("{{#foo}}content{{/foo}}")
result.should match_tokens(%w(OPEN_BLOCK ID CLOSE CONTENT OPEN_ENDBLOCK ID CLOSE))
end
it "tokenizes inverse sections as 'OPEN_INVERSE CLOSE'" do
tokenize("{{^}}").should match_tokens(%w(OPEN_INVERSE CLOSE))
tokenize("{{else}}").should match_tokens(%w(OPEN_INVERSE CLOSE))
tokenize("{{ else }}").should match_tokens(%w(OPEN_INVERSE CLOSE))
end
it "tokenizes inverse sections with ID as 'OPEN_INVERSE ID CLOSE'" do
result = tokenize("{{^foo}}")
result.should match_tokens(%w(OPEN_INVERSE ID CLOSE))
result[1].should be_token("ID", "foo")
end
it "tokenizes inverse sections with ID and spaces as 'OPEN_INVERSE ID CLOSE'" do
result = tokenize("{{^ foo }}")
result.should match_tokens(%w(OPEN_INVERSE ID CLOSE))
result[1].should be_token("ID", "foo")
end
it "tokenizes mustaches with params as 'OPEN ID ID ID CLOSE'" do
result = tokenize("{{ foo bar baz }}")
result.should match_tokens(%w(OPEN ID ID ID CLOSE))
result[1].should be_token("ID", "foo")
result[2].should be_token("ID", "bar")
result[3].should be_token("ID", "baz")
end
it "tokenizes mustaches with String params as 'OPEN ID ID STRING CLOSE'" do
result = tokenize("{{ foo bar \"baz\" }}")
result.should match_tokens(%w(OPEN ID ID STRING CLOSE))
result[3].should be_token("STRING", "baz")
end
it "tokenizes String params with spaces inside as 'STRING'" do
result = tokenize("{{ foo bar \"baz bat\" }}")
result.should match_tokens(%w(OPEN ID ID STRING CLOSE))
result[3].should be_token("STRING", "baz bat")
end
it "tokenizes String params with escapes quotes as 'STRING'" do
result = tokenize(%|{{ foo "bar\\"baz" }}|)
result.should match_tokens(%w(OPEN ID STRING CLOSE))
result[2].should be_token("STRING", %{bar"baz})
end
it "tokenizes hash arguments" do
result = tokenize("{{ foo bar=baz }}")
result.should match_tokens %w(OPEN ID ID EQUALS ID CLOSE)
result = tokenize("{{ foo bar baz=bat }}")
result.should match_tokens %w(OPEN ID ID ID EQUALS ID CLOSE)
result = tokenize("{{ foo bar baz=\"bat\" }}")
result.should match_tokens %w(OPEN ID ID ID EQUALS STRING CLOSE)
result = tokenize("{{ foo bar baz=\"bat\" bam=wot }}")
result.should match_tokens %w(OPEN ID ID ID EQUALS STRING ID EQUALS ID CLOSE)
result = tokenize("{{foo omg bar=baz bat=\"bam\"}}")
result.should match_tokens %w(OPEN ID ID ID EQUALS ID ID EQUALS STRING CLOSE)
result[2].should be_token("ID", "omg")
end
it "does not time out in a mustache with a single } followed by EOF" do
Timeout.timeout(1) { tokenize("{{foo}").should match_tokens(%w(OPEN ID)) }
end
it "does not time out in a mustache when invalid ID characters are used" do
Timeout.timeout(1) { tokenize("{{foo & }}").should match_tokens(%w(OPEN ID)) }
end
end
+59
View File
@@ -0,0 +1,59 @@
/*global Handlebars, shouldCompileTo */
describe('utils', function() {
describe('#SafeString', function() {
it("constructing a safestring from a string and checking its type", function() {
var safe = new Handlebars.SafeString("testing 1, 2, 3");
if (!(safe instanceof Handlebars.SafeString)) {
throw new Error('Must be instance of SafeString');
}
equals(safe == 'testing 1, 2, 3', true, 'SafeString is equivalent to its underlying string');
});
it("it should not escape SafeString properties", function() {
var name = new Handlebars.SafeString("<em>Sean O&#x27;Malley</em>");
shouldCompileTo('{{name}}', [{ name: name }], "<em>Sean O&#x27;Malley</em>");
});
});
describe('#escapeExpression', function() {
it('shouhld escape html', function() {
equals(Handlebars.Utils.escapeExpression('foo<&"\'>'), 'foo&lt;&amp;&quot;&#x27;&gt;');
});
it('should not escape SafeString', function() {
var string = new Handlebars.SafeString('foo<&"\'>');
equals(Handlebars.Utils.escapeExpression(string), 'foo<&"\'>');
});
it('should handle falsy', function() {
equals(Handlebars.Utils.escapeExpression(''), '');
equals(Handlebars.Utils.escapeExpression(undefined), '');
equals(Handlebars.Utils.escapeExpression(null), '');
equals(Handlebars.Utils.escapeExpression(false), '');
equals(Handlebars.Utils.escapeExpression(0), '0');
});
it('should handle empty objects', function() {
equals(Handlebars.Utils.escapeExpression({}), {}.toString());
equals(Handlebars.Utils.escapeExpression([]), [].toString());
});
});
describe('#isEmpty', function() {
it('should not be empty', function() {
equals(Handlebars.Utils.isEmpty(undefined), true);
equals(Handlebars.Utils.isEmpty(null), true);
equals(Handlebars.Utils.isEmpty(false), true);
equals(Handlebars.Utils.isEmpty(''), true);
equals(Handlebars.Utils.isEmpty([]), true);
});
it('should be empty', function() {
equals(Handlebars.Utils.isEmpty(0), false);
equals(Handlebars.Utils.isEmpty([1]), false);
equals(Handlebars.Utils.isEmpty('foo'), false);
equals(Handlebars.Utils.isEmpty({bar: 1}), false);
});
});
});
+62
View File
@@ -0,0 +1,62 @@
describe('whitespace control', function() {
it('should strip whitespace around mustache calls', function() {
var hash = {foo: 'bar<'};
shouldCompileTo(' {{~foo~}} ', hash, 'bar&lt;');
shouldCompileTo(' {{~foo}} ', hash, 'bar&lt; ');
shouldCompileTo(' {{foo~}} ', hash, ' bar&lt;');
shouldCompileTo(' {{~&foo~}} ', hash, 'bar<');
shouldCompileTo(' {{~{foo}~}} ', hash, 'bar<');
});
describe('blocks', function() {
it('should strip whitespace around simple block calls', function() {
var hash = {foo: 'bar<'};
shouldCompileTo(' {{~#if foo~}} bar {{~/if~}} ', hash, 'bar');
shouldCompileTo(' {{#if foo~}} bar {{/if~}} ', hash, ' bar ');
shouldCompileTo(' {{~#if foo}} bar {{~/if}} ', hash, ' bar ');
shouldCompileTo(' {{#if foo}} bar {{/if}} ', hash, ' bar ');
});
it('should strip whitespace around inverse block calls', function() {
var hash = {};
shouldCompileTo(' {{~^if foo~}} bar {{~/if~}} ', hash, 'bar');
shouldCompileTo(' {{^if foo~}} bar {{/if~}} ', hash, ' bar ');
shouldCompileTo(' {{~^if foo}} bar {{~/if}} ', hash, ' bar ');
shouldCompileTo(' {{^if foo}} bar {{/if}} ', hash, ' bar ');
});
it('should strip whitespace around complex block calls', function() {
var hash = {foo: 'bar<'};
shouldCompileTo('{{#if foo~}} bar {{~^~}} baz {{~/if}}', hash, 'bar');
shouldCompileTo('{{#if foo~}} bar {{^~}} baz {{/if}}', hash, 'bar ');
shouldCompileTo('{{#if foo}} bar {{~^~}} baz {{~/if}}', hash, ' bar');
shouldCompileTo('{{#if foo}} bar {{^~}} baz {{/if}}', hash, ' bar ');
shouldCompileTo('{{#if foo~}} bar {{~else~}} baz {{~/if}}', hash, 'bar');
hash = {};
shouldCompileTo('{{#if foo~}} bar {{~^~}} baz {{~/if}}', hash, 'baz');
shouldCompileTo('{{#if foo}} bar {{~^~}} baz {{/if}}', hash, 'baz ');
shouldCompileTo('{{#if foo~}} bar {{~^}} baz {{~/if}}', hash, ' baz');
shouldCompileTo('{{#if foo~}} bar {{~^}} baz {{/if}}', hash, ' baz ');
shouldCompileTo('{{#if foo~}} bar {{~else~}} baz {{~/if}}', hash, 'baz');
});
});
it('should strip whitespace around partials', function() {
shouldCompileToWithPartials('foo {{~> dude~}} ', [{}, {}, {dude: 'bar'}], true, 'foobar');
shouldCompileToWithPartials('foo {{> dude~}} ', [{}, {}, {dude: 'bar'}], true, 'foo bar');
shouldCompileToWithPartials('foo {{> dude}} ', [{}, {}, {dude: 'bar'}], true, 'foo bar ');
});
it('should only strip whitespace once', function() {
var hash = {foo: 'bar'};
shouldCompileTo(' {{~foo~}} {{foo}} {{foo}} ', hash, 'barbar bar ');
});
});
+79 -23
View File
@@ -1,31 +1,87 @@
%x mu
%x mu emu com
%{
function strip(start, end) {
return yytext = yytext.substr(start, yyleng-end);
}
%}
LEFT_STRIP "~"
RIGHT_STRIP "~"
LOOKAHEAD [=~}\s\/.)]
LITERAL_LOOKAHEAD [~}\s)]
/*
ID is the inverse of control characters.
Control characters ranges:
[\s] Whitespace
[!"#%-,\./] !, ", #, %, &, ', (, ), *, +, ,, ., /, Exceptions in range: $, -
[;->@] ;, <, =, >, @, Exceptions in range: :, ?
[\[-\^`] [, \, ], ^, `, Exceptions in range: _
[\{-~] {, |, }, ~
*/
ID [^\s!"#%-,\.\/;->@\[-\^`\{-~]+/{LOOKAHEAD}
%%
[^\x00]*?/("{{") { this.begin("mu"); if (yytext) return 'CONTENT'; }
[^\x00]+ { return 'CONTENT'; }
[^\x00]*?/("{{") {
if(yytext.slice(-2) === "\\\\") {
strip(0,1);
this.begin("mu");
} else if(yytext.slice(-1) === "\\") {
strip(0,1);
this.begin("emu");
} else {
this.begin("mu");
}
if(yytext) return 'CONTENT';
}
<mu>"{{>" { return 'OPEN_PARTIAL'; }
<mu>"{{#" { return 'OPEN_BLOCK'; }
<mu>"{{/" { return 'OPEN_ENDBLOCK'; }
<mu>"{{^" { return 'OPEN_INVERSE'; }
<mu>"{{"\s*"else" { return 'OPEN_INVERSE'; }
<mu>"{{{" { return 'OPEN_UNESCAPED'; }
<mu>"{{&" { return 'OPEN_UNESCAPED'; }
<mu>"{{!"[\s\S]*?"}}" { yytext = yytext.substr(3,yyleng-5); this.begin("INITIAL"); return 'COMMENT'; }
<mu>"{{" { return 'OPEN'; }
[^\x00]+ return 'CONTENT';
<mu>"=" { return 'EQUALS'; }
<mu>"."/[} ] { return 'ID'; }
<mu>".." { return 'ID'; }
<mu>[/.] { return 'SEP'; }
<mu>\s+ { /*ignore whitespace*/ }
<mu>"}}}" { this.begin("INITIAL"); return 'CLOSE'; }
<mu>"}}" { this.begin("INITIAL"); return 'CLOSE'; }
<mu>'"'("\\"["]|[^"])*'"' { yytext = yytext.substr(1,yyleng-2).replace(/\\"/g,'"'); return 'STRING'; }
<mu>[a-zA-Z0-9_-]+/[=} /.] { return 'ID'; }
<mu>. { return 'INVALID'; }
// marks CONTENT up to the next mustache or escaped mustache
<emu>[^\x00]{2,}?/("{{"|"\\{{"|"\\\\{{"|<<EOF>>) {
this.popState();
return 'CONTENT';
}
<INITIAL,mu><<EOF>> { return 'EOF'; }
<com>[\s\S]*?"--}}" strip(0,4); this.popState(); return 'COMMENT';
<mu>"(" return 'OPEN_SEXPR';
<mu>")" return 'CLOSE_SEXPR';
<mu>"{{"{LEFT_STRIP}?">" return 'OPEN_PARTIAL';
<mu>"{{"{LEFT_STRIP}?"#" return 'OPEN_BLOCK';
<mu>"{{"{LEFT_STRIP}?"/" return 'OPEN_ENDBLOCK';
<mu>"{{"{LEFT_STRIP}?"^" return 'OPEN_INVERSE';
<mu>"{{"{LEFT_STRIP}?\s*"else" return 'OPEN_INVERSE';
<mu>"{{"{LEFT_STRIP}?"{" return 'OPEN_UNESCAPED';
<mu>"{{"{LEFT_STRIP}?"&" return 'OPEN';
<mu>"{{!--" this.popState(); this.begin('com');
<mu>"{{!"[\s\S]*?"}}" strip(3,5); this.popState(); return 'COMMENT';
<mu>"{{"{LEFT_STRIP}? return 'OPEN';
<mu>"=" return 'EQUALS';
<mu>".." return 'ID';
<mu>"."/{LOOKAHEAD} return 'ID';
<mu>[\/.] return 'SEP';
<mu>\s+ // ignore whitespace
<mu>"}"{RIGHT_STRIP}?"}}" this.popState(); return 'CLOSE_UNESCAPED';
<mu>{RIGHT_STRIP}?"}}" this.popState(); return 'CLOSE';
<mu>'"'("\\"["]|[^"])*'"' yytext = strip(1,2).replace(/\\"/g,'"'); return 'STRING';
<mu>"'"("\\"[']|[^'])*"'" yytext = strip(1,2).replace(/\\'/g,"'"); return 'STRING';
<mu>"@" return 'DATA';
<mu>"true"/{LITERAL_LOOKAHEAD} return 'BOOLEAN';
<mu>"false"/{LITERAL_LOOKAHEAD} return 'BOOLEAN';
<mu>\-?[0-9]+/{LITERAL_LOOKAHEAD} return 'INTEGER';
<mu>{ID} return 'ID';
<mu>'['[^\]]*']' yytext = strip(1,2); return 'ID';
<mu>. return 'INVALID';
<INITIAL,mu><<EOF>> return 'EOF';

Some files were not shown because too many files have changed in this diff Show More