Compare commits

...

196 Commits

Author SHA1 Message Date
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
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
kpdecker 8e9bd6742f Merge remote-tracking branch 'upstream/master' 2012-08-21 12:51:58 -05: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
kpdecker cfc1d534cb Merge with this-param 2012-07-30 21:49:17 -05:00
kpdecker f5289a35ec 1.0.6 2012-07-23 15:40:07 -05:00
John Freeman 0872fb6699 block helper "with" should check for empty context 2012-03-31 23:09:27 -05:00
38 changed files with 4996 additions and 1333 deletions
-1
View File
@@ -1,4 +1,3 @@
dist
vendor
.rvmrc
.DS_Store
-1
View File
@@ -5,7 +5,6 @@ Gemfile
Gemfile.lock
Rakefile
bench/*
dist/*
spec/*
src/*
vendor/*
+1 -1
View File
@@ -1,5 +1,5 @@
source "http://rubygems.org"
gem "rake"
gem "therubyracer", ">= 0.9.8"
gem "therubyracer", ">= 0.9.8", "< 0.11"
gem "rspec"
+10 -10
View File
@@ -3,16 +3,16 @@ GEM
specs:
diff-lcs (1.1.3)
libv8 (3.3.10.4)
rake (0.9.2.2)
rspec (2.11.0)
rspec-core (~> 2.11.0)
rspec-expectations (~> 2.11.0)
rspec-mocks (~> 2.11.0)
rspec-core (2.11.0)
rspec-expectations (2.11.1)
rake (10.0.3)
rspec (2.12.0)
rspec-core (~> 2.12.0)
rspec-expectations (~> 2.12.0)
rspec-mocks (~> 2.12.0)
rspec-core (2.12.2)
rspec-expectations (2.12.1)
diff-lcs (~> 1.1.3)
rspec-mocks (2.11.1)
therubyracer (0.10.1)
rspec-mocks (2.12.1)
therubyracer (0.10.2)
libv8 (~> 3.3.10)
PLATFORMS
@@ -21,4 +21,4 @@ PLATFORMS
DEPENDENCIES
rake
rspec
therubyracer (>= 0.9.8)
therubyracer (>= 0.9.8, < 0.11)
-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.
+89 -36
View File
@@ -1,25 +1,35 @@
[![Build Status](https://secure.travis-ci.org/wycats/handlebars.js.png)](http://travis-ci.org/wycats/handlebars.js)
[![Build Status](https://travis-ci.org/wycats/handlebars.js.png?branch=master)](https://travis-ci.org/wycats/handlebars.js)
Handlebars.js
=============
Handlebars.js is an extension to the [Mustache templating language](http://mustache.github.com/) created by Chris Wanstrath. Handlebars.js and Mustache are both logicless templating languages that keep the view and the code separated like we all know they should be.
Handlebars.js is an extension to the [Mustache templating
language](http://mustache.github.com/) created by Chris Wanstrath.
Handlebars.js and Mustache are both logicless templating languages that
keep the view and the code separated like we all know they should be.
Checkout the official Handlebars docs site at [http://www.handlebarsjs.com](http://www.handlebarsjs.com).
Checkout the official Handlebars docs site at
[http://www.handlebarsjs.com](http://www.handlebarsjs.com).
Installing
----------
Installing Handlebars is easy. Simply [download the package from GitHub](https://github.com/wycats/handlebars.js/archives/master) and add it to your web pages (you should usually use the most recent version).
Installing Handlebars is easy. Simply download the package [from the
official site](http://handlebarsjs.com/) and add it to your web pages
(you should usually use the most recent version).
Usage
-----
In general, the syntax of Handlebars.js templates is a superset of Mustache templates. For basic syntax, check out the [Mustache manpage](http://mustache.github.com/mustache.5.html).
In general, the syntax of Handlebars.js templates is a superset
of Mustache templates. For basic syntax, check out the [Mustache
manpage](http://mustache.github.com/mustache.5.html).
Once you have a template, use the Handlebars.compile method to compile the template into a function. The generated function takes a context argument, which will be used to render the template.
Once you have a template, use the Handlebars.compile method to compile
the template into a function. The generated function takes a context
argument, which will be used to render the template.
```js
var source = "<p>Hello, my name is {{name}}. I am from {{hometown}}. I have " +
var source = "<p>Hello, my name is {{name}}. I am from {{hometown}}. I have " +
"{{kids.length}} kids:</p>" +
"<ul>{{#kids}}<li>{{name}} is {{age}}</li>{{/kids}}</ul>";
var template = Handlebars.compile(source);
@@ -75,25 +85,34 @@ 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 descendents and ancestors of the
current context.
To display data from descendent contexts, use the `.` character. So, for example, if your data were structured like:
To display data from descendent contexts, use the `.` character. So, for
example, if your data were structured like:
```js
var data = {"person": { "name": "Alan" }, company: {"name": "Rad, Inc." } };
```
you could display the person's name from the top-level context with the following expression:
you could display the person's name from the top-level context with the
following expression:
```
{{person.name}}
```
You can backtrack using `../`. For example, if you've already traversed into the person object you could still display the company's name with an expression like `{{../company.name}}`, so:
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}}
@@ -112,7 +131,7 @@ instance:
```js
Handlebars.registerHelper('link_to', function(title, context) {
return "<a href='/posts" + context.id + "'>" + title + "</a>"
return "<a href='/posts" + context.url + "'>" + title + "!</a>"
});
var context = { posts: [{url: "/hello-world", body: "Hello World!"}] };
@@ -124,7 +143,7 @@ template(context);
// Would render:
//
// <ul>
// <li><a href='/hello-world'>Post!</a></li>
// <li><a href='/posts/hello-world'>Post!</a></li>
// </ul>
```
@@ -134,12 +153,14 @@ 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:
```js
var source = "<ul>{{#people}}<li>{{{#link}}}{{name}}{{/link}}</li>{{/people}}</ul>";
Handlebars.registerHelper('link', function(context, fn) {
return '<a href="/people/' + this.__get__("id") + '">' + fn(this) + '</a>';
var 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);
@@ -156,7 +177,12 @@ template(data);
// </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 two parameters, the
argument that is passed to the helper, or the current context if no
argument is passed and the compiled contents of the block. Inside of
the block helper the value of `this` is the current context, wrapped to
include a method named `__get__` that helps translate paths into values
within the helpers.
### Partials
@@ -220,6 +246,7 @@ 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]
@@ -228,13 +255,15 @@ Options:
-r, --root Template root. Base value that will be stripped from template names. [string]
</pre>
If using the precompiler's normal mode, the resulting templates will be stored
to the `Handlebars.templates` object using the relative template name sans the
extension. These templates may be executed in the same manner as templates.
If using the 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.
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
@@ -251,7 +280,15 @@ method and the resulting object may be as normal.
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
@@ -287,13 +324,19 @@ Known Issues
Handlebars in the Wild
-----------------
* [jblotus](http://github.com/jblotus) created [http://tryhandlebarsjs.com](http://tryhandlebarsjs.com) for anyone who would
like to try out Handlebars.js in their browser.
* Don Park wrote an Express.js view engine adapter for Handlebars.js called [hbs](http://github.com/donpark/hbs).
* [sammy.js](http://github.com/quirkey/sammy) by Aaron Quint, a.k.a. quirkey, supports Handlebars.js as one of its template plugins.
* [SproutCore](http://www.sproutcore.com) uses Handlebars.js as its main templating engine, extending it with automatic data binding support.
* [Ember.js](http://www.emberjs.com) makes Handlebars.js the primary way to structure your views, also with automatic data binding support.
* Les Hill (@leshill) wrote a Rails Asset Pipeline gem named [handlebars_assets](http://github.com/leshill/handlebars_assets).
* [jblotus](http://github.com/jblotus) created [http://tryhandlebarsjs.com](http://tryhandlebarsjs.com)
for anyone who would like to try out Handlebars.js in their browser.
* Don Park wrote an Express.js view engine adapter for Handlebars.js called
[hbs](http://github.com/donpark/hbs).
* [sammy.js](http://github.com/quirkey/sammy) by Aaron Quint, a.k.a. quirkey,
supports Handlebars.js as one of its template plugins.
* [SproutCore](http://www.sproutcore.com) uses Handlebars.js as its main
templating engine, extending it with automatic data binding support.
* [Ember.js](http://www.emberjs.com) makes Handlebars.js the primary way to
structure your views, also with automatic data binding support.
* Les Hill (@leshill) wrote a Rails Asset Pipeline gem named
handlebars_assets](http://github.com/leshill/handlebars_assets).
* [Gist about Synchronous and asynchronous loading of external handlebars templates](https://gist.github.com/2287070)
Helping Out
-----------
@@ -305,12 +348,22 @@ To build Handlebars.js you'll need a few things installed.
* therubyracer, for running tests - `gem install therubyracer`
* rspec, for running tests - `gem install rspec`
There's a Gemfile in the repo, so you can run `bundle` to install rspec and therubyracer if you've got bundler installed.
There's a Gemfile in the repo, so you can run `bundle` to install rspec
and therubyracer if you've got bundler installed.
To build Handlebars.js from scratch, you'll want to run `rake compile` in the root of the project. That will build Handlebars and output the results to the dist/ folder. To run tests, run `rake spec`. You can also run our set of benchmarks with `rake bench`.
To build Handlebars.js from scratch, you'll want to run `rake compile`
in the root of the project. That will build Handlebars and output the
results to the dist/ folder. To run tests, run `rake spec`. You can also
run our set of benchmarks with `rake bench`.
If you notice any problems, please report them to the GitHub issue tracker at [http://github.com/wycats/handlebars.js/issues](http://github.com/wycats/handlebars.js/issues). Feel free to contact commondream or wycats through GitHub with any other questions or feature requests. To submit changes fork the project and send a pull request.
If you notice any problems, please report
them to the GitHub issue tracker at
[http://github.com/wycats/handlebars.js/issues](http://github.com/wycats/handlebars.js/issues).
Feel free to contact commondream or wycats through GitHub with any other
questions or feature requests. To submit changes fork the project and
send a pull request.
License
-------
Handlebars.js is released under the MIT license.
+39 -25
View File
@@ -2,10 +2,10 @@ require "rubygems"
require "bundler/setup"
def compile_parser
system "./node_modules/jison/lib/jison/cli-wrapper.js src/handlebars.yy src/handlebars.l"
system "./node_modules/.bin/jison -m js src/handlebars.yy src/handlebars.l"
if $?.success?
File.open("lib/handlebars/compiler/parser.js", "w") do |file|
file.puts File.read("handlebars.js") + ";"
file.puts File.read("src/parser-prefix.js") + File.read("handlebars.js") + File.read("src/parser-suffix.js")
end
sh "rm handlebars.js"
@@ -15,11 +15,11 @@ def compile_parser
end
file "lib/handlebars/compiler/parser.js" => ["src/handlebars.yy","src/handlebars.l"] do
if File.exists?('./node_modules/jison/lib/jison/cli-wrapper.js')
if File.exists?('./node_modules/jison')
compile_parser
else
puts "Jison is not installed. Trying `npm install jison`."
sh "npm install jison"
sh "npm install"
compile_parser
end
end
@@ -32,18 +32,24 @@ task :spec => [:release] do
fail "rspec spec failed with exit code #{$?.exitstatus}" if (rc.nil? || ! rc || $?.exitstatus != 0)
end
task :default => [:compile, :spec]
desc "run the npm test suite"
task :npm_test => [:release] do
rc = system "npm test"
fail "npm test failed with exit code #{$?.exitstatus}" if (rc.nil? || ! rc || $?.exitstatus != 0)
end
task :default => [:compile, :spec, :npm_test]
def remove_exports(string)
match = string.match(%r{^// BEGIN\(BROWSER\)\n(.*)\n^// END\(BROWSER\)}m)
match ? match[1] : string
end
minimal_deps = %w(base compiler/parser compiler/base compiler/ast utils compiler/compiler runtime).map do |file|
minimal_deps = %w(browser-prefix base compiler/parser compiler/base compiler/ast utils compiler/compiler runtime browser-suffix).map do |file|
"lib/handlebars/#{file}.js"
end
runtime_deps = %w(base utils runtime).map do |file|
runtime_deps = %w(browser-prefix base utils runtime browser-suffix).map do |file|
"lib/handlebars/#{file}.js"
end
@@ -55,7 +61,7 @@ def build_for_task(task)
FileUtils.rm_rf("dist/*") if File.directory?("dist")
FileUtils.mkdir_p("dist")
contents = []
contents = ["/*\n\n" + File.read('LICENSE') + "\n*/\n"]
task.prerequisites.each do |filename|
next if filename == "dist"
@@ -75,31 +81,39 @@ file "dist/handlebars.runtime.js" => runtime_deps do |task|
build_for_task(task)
end
task :build => [:compile, "dist/handlebars.js"]
task :runtime => [:compile, "dist/handlebars.runtime.js"]
task :build => [:compile] do |task|
# Since the tests are dependent on this always rebuild.
Rake::Task["dist/handlebars.js"].execute
end
task :runtime => [:compile] do |task|
# Since the tests are dependent on this always rebuild.
Rake::Task["dist/handlebars.runtime.js"].execute
end
# Updates the various version numbers.
task :version => [] do |task|
# TODO : Pull from package.json when the version numbers are synced
version = File.read("lib/handlebars/base.js").match(/Handlebars.VERSION = "(.*)";/)[1]
content = File.read("bower.json")
File.open("bower.json", "w") do |file|
file.puts content.gsub(/"version":.*/, "\"version\": \"#{version}\",")
end
content = File.read("handlebars.js.nuspec")
File.open("handlebars.js.nuspec", "w") do |file|
file.puts content.gsub(/<version>.*<\/version>/, "<version>#{version}</version>")
end
end
desc "build the build and runtime version of handlebars"
task :release => [:build, :runtime]
task :release => [:version, :build, :runtime]
directory "vendor"
desc "benchmark against dust.js and mustache.js"
task :bench => "vendor" do
require "open-uri"
#File.open("vendor/mustache.js", "w") do |file|
#file.puts open("https://github.com/janl/mustache.js/raw/master/mustache.js").read
#file.puts "module.exports = Mustache;"
#end
File.open("vendor/benchmark.js", "w") do |file|
file.puts open("https://raw.github.com/bestiejs/benchmark.js/master/benchmark.js").read
end
#if File.directory?("vendor/dustjs")
#system "cd vendor/dustjs && git pull"
#else
#system "git clone git://github.com/akdubya/dustjs.git vendor/dustjs"
#end
#if File.directory?("vendor/coffee")
#system "cd vendor/coffee && git pull"
+1 -1
View File
@@ -133,7 +133,7 @@ BenchWarmer.prototype = {
if(!bench.error) {
var count = bench.hz,
moe = count * bench.stats.RME / 100;
moe = count * bench.stats.rme / 100;
out = Math.round(count / 1000) + " ±" + Math.round(moe / 1000) + " (" + bench.cycles + ")";
} else {
+43 -34
View File
@@ -1,20 +1,22 @@
require.paths.push("lib");
require.paths.push("vendor");
require.paths.push("vendor/dustjs/lib");
require.paths.push("vendor/coffee/lib");
require.paths.push("vendor/eco/lib");
var BenchWarmer = require("./benchwarmer");
Handlebars = require("handlebars");
Handlebars = require("../lib/handlebars");
var dust = require("dust");
var Mustache = require("mustache");
var ecoExports = require("eco");
var dust, Mustache, eco;
eco = function(str) {
return ecoExports(str);
}
try {
dust = require("dust");
} catch (err) { /* NOP */ }
try {
Mustache = require("mustache");
} catch (err) { /* NOP */ }
try {
var ecoExports = require("eco");
eco = function(str) {
return ecoExports(str);
}
} catch (err) { /* NOP */ }
var benchDetails = {
string: {
@@ -113,39 +115,46 @@ var makeSuite = function(name) {
var error = function() { throw new Error("EWOT"); };
//bench("dust", function() {
//dust.render(templateName, context, function(err, out) { });
//});
if (dust) {
bench("dust", function() {
dust.render(templateName, context, function(err, out) { });
});
}
bench("handlebars", function() {
handlebarsTemplates[templateName](context);
});
//if(ecoTemplates[templateName]) {
//bench("eco", function() {
//ecoTemplates[templateName](context);
//});
//} else {
//bench("eco", error);
//}
if (eco) {
if(ecoTemplates[templateName]) {
bench("eco", function() {
ecoTemplates[templateName](context);
});
} else {
bench("eco", error);
}
}
//if(mustacheSource) {
//bench("mustache", function() {
//Mustache.to_html(mustacheSource, context, mustachePartials);
//});
//} else {
//bench("mustache", error);
//}
if (Mustache && mustacheSource) {
bench("mustache", function() {
Mustache.to_html(mustacheSource, context, mustachePartials);
});
} else {
bench("mustache", error);
}
});
}
for(var name in benchDetails) {
if(benchDetails.hasOwnProperty(name)) {
dust.loadSource(dust.compile(benchDetails[name].dust, name));
if (dust) {
dust.loadSource(dust.compile(benchDetails[name].dust, name));
}
handlebarsTemplates[name] = Handlebars.compile(benchDetails[name].handlebars);
if(benchDetails[name].eco) { ecoTemplates[name] = eco(benchDetails[name].eco); }
if (eco && benchDetails[name].eco) {
ecoTemplates[name] = eco(benchDetails[name].eco);
}
var partials = benchDetails[name].partials;
if(partials) {
+58 -5
View File
@@ -12,11 +12,17 @@ var optimist = require('optimist')
'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': ''
'default': ''
},
'k': {
'type': 'string',
@@ -33,6 +39,12 @@ var optimist = require('optimist')
'description': 'Minimize output',
'alias': 'min'
},
'n': {
'type': 'string',
'description': 'Template namespace',
'alias': 'namespace',
'default': 'Handlebars.templates'
},
's': {
'type': 'boolean',
'description': 'Output template function only.',
@@ -42,6 +54,22 @@ var optimist = require('optimist')
'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'
}
})
@@ -87,14 +115,24 @@ if (argv.known) {
}
}
// Build file extension pattern
var extension = argv.extension.replace(/[\\^$*+?.():=!|{}\-\[\]]/g, function(arg) { return '\\' + arg; });
extension = new RegExp('\\.' + extension + '$');
var output = [];
if (!argv.simple) {
if (argv.amd) {
output.push('define([\'' + argv.handlebarPath + 'handlebars\'], function(Handlebars) {\n');
} else if (argv.commonjs) {
output.push('var Handlebars = require("' + argv.commonjs + '");');
} else {
output.push('(function() {\n');
}
output.push(' var template = Handlebars.template, templates = Handlebars.templates = Handlebars.templates || {};\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,
@@ -103,7 +141,7 @@ function processTemplate(template, root) {
fs.readdirSync(template).map(function(file) {
var path = template + '/' + file;
if (/\.handlebars$/.test(path) || fs.statSync(path).isDirectory()) {
if (extension.test(path) || fs.statSync(path).isDirectory()) {
processTemplate(path, root || template);
}
});
@@ -115,17 +153,25 @@ function processTemplate(template, root) {
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(/\.handlebars$/, '');
template = template.replace(extension, '');
if (argv.simple) {
output.push(handlebars.precompile(data, options) + '\n');
} else if (argv.partial) {
if(argv.amd && argv._.length == 1){ output.push('return '); }
output.push('Handlebars.partials[\'' + template + '\'] = template(' + handlebars.precompile(data, options) + ');\n');
} else {
if(argv.amd && argv._.length == 1){ output.push('return '); }
output.push('templates[\'' + template + '\'] = template(' + handlebars.precompile(data, options) + ');\n');
}
}
@@ -138,8 +184,15 @@ argv._.forEach(function(template) {
// Output the content
if (!argv.simple) {
if (argv.amd) {
if(argv._.length > 1){
if(argv.partial){
output.push('return Handlebars.partials;\n');
} else {
output.push('return templates;\n');
}
}
output.push('});');
} else {
} else if (!argv.commonjs) {
output.push('})();');
}
}
+9
View File
@@ -0,0 +1,9 @@
{
"name": "handlebars.js",
"version": "1.0.0-rc.4",
"main": "dist/handlebars.js",
"ignore": [
"node_modules",
"components"
]
}
+2239
View File
File diff suppressed because it is too large Load Diff
+345
View File
@@ -0,0 +1,345 @@
/*
Copyright (C) 2011 by Yehuda Katz
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
// lib/handlebars/browser-prefix.js
var Handlebars = {};
(function(Handlebars, undefined) {
;
// lib/handlebars/base.js
Handlebars.VERSION = "1.0.0-rc.4";
Handlebars.COMPILER_REVISION = 3;
Handlebars.REVISION_CHANGES = {
1: '<= 1.0.rc.2', // 1.0.rc.2 is actually rev2 but doesn't report it
2: '== 1.0.0-rc.3',
3: '>= 1.0.0-rc.4'
};
Handlebars.helpers = {};
Handlebars.partials = {};
var toString = Object.prototype.toString,
functionType = '[object Function]',
objectType = '[object Object]';
Handlebars.registerHelper = function(name, fn, inverse) {
if (toString.call(name) === objectType) {
if (inverse || fn) { throw new Handlebars.Exception('Arg not supported with multiple helpers'); }
Handlebars.Utils.extend(this.helpers, name);
} else {
if (inverse) { fn.not = inverse; }
this.helpers[name] = fn;
}
};
Handlebars.registerPartial = function(name, str) {
if (toString.call(name) === objectType) {
Handlebars.Utils.extend(this.partials, name);
} else {
this.partials[name] = str;
}
};
Handlebars.registerHelper('helperMissing', function(arg) {
if(arguments.length === 2) {
return undefined;
} else {
throw new Error("Could not find property '" + arg + "'");
}
});
Handlebars.registerHelper('blockHelperMissing', function(context, options) {
var inverse = options.inverse || function() {}, fn = options.fn;
var type = toString.call(context);
if(type === functionType) { context = context.call(this); }
if(context === true) {
return fn(this);
} else if(context === false || context == null) {
return inverse(this);
} else if(type === "[object Array]") {
if(context.length > 0) {
return Handlebars.helpers.each(context, options);
} else {
return inverse(this);
}
} else {
return fn(context);
}
});
Handlebars.K = function() {};
Handlebars.createFrame = Object.create || function(object) {
Handlebars.K.prototype = object;
var obj = new Handlebars.K();
Handlebars.K.prototype = null;
return obj;
};
Handlebars.logger = {
DEBUG: 0, INFO: 1, WARN: 2, ERROR: 3, level: 3,
methodMap: {0: 'debug', 1: 'info', 2: 'warn', 3: 'error'},
// can be overridden in the host environment
log: function(level, obj) {
if (Handlebars.logger.level <= level) {
var method = Handlebars.logger.methodMap[level];
if (typeof console !== 'undefined' && console[method]) {
console[method].call(console, obj);
}
}
}
};
Handlebars.log = function(level, obj) { Handlebars.logger.log(level, obj); };
Handlebars.registerHelper('each', function(context, options) {
var fn = options.fn, inverse = options.inverse;
var i = 0, ret = "", data;
if (options.data) {
data = Handlebars.createFrame(options.data);
}
if(context && typeof context === 'object') {
if(context instanceof Array){
for(var j = context.length; i<j; i++) {
if (data) { data.index = i; }
ret = ret + fn(context[i], { data: data });
}
} else {
for(var key in context) {
if(context.hasOwnProperty(key)) {
if(data) { data.key = key; }
ret = ret + fn(context[key], {data: data});
i++;
}
}
}
}
if(i === 0){
ret = inverse(this);
}
return ret;
});
Handlebars.registerHelper('if', function(context, options) {
var type = toString.call(context);
if(type === functionType) { context = context.call(this); }
if(!context || Handlebars.Utils.isEmpty(context)) {
return options.inverse(this);
} else {
return options.fn(this);
}
});
Handlebars.registerHelper('unless', function(context, options) {
return Handlebars.helpers['if'].call(this, context, {fn: options.inverse, inverse: options.fn});
});
Handlebars.registerHelper('with', function(context, options) {
if (!Handlebars.Utils.isEmpty(context)) return options.fn(context);
});
Handlebars.registerHelper('log', function(context, options) {
var level = options.data && options.data.level != null ? parseInt(options.data.level, 10) : 1;
Handlebars.log(level, context);
});
;
// lib/handlebars/utils.js
var errorProps = ['description', 'fileName', 'lineNumber', 'message', 'name', 'number', 'stack'];
Handlebars.Exception = function(message) {
var tmp = Error.prototype.constructor.apply(this, arguments);
// Unfortunately errors are not enumerable in Chrome (at least), so `for prop in tmp` doesn't work.
for (var idx = 0; idx < errorProps.length; idx++) {
this[errorProps[idx]] = tmp[errorProps[idx]];
}
};
Handlebars.Exception.prototype = new Error();
// Build out our basic SafeString type
Handlebars.SafeString = function(string) {
this.string = string;
};
Handlebars.SafeString.prototype.toString = function() {
return this.string.toString();
};
var escape = {
"&": "&amp;",
"<": "&lt;",
">": "&gt;",
'"': "&quot;",
"'": "&#x27;",
"`": "&#x60;"
};
var badChars = /[&<>"'`]/g;
var possible = /[&<>"'`]/;
var escapeChar = function(chr) {
return escape[chr] || "&amp;";
};
Handlebars.Utils = {
extend: function(obj, value) {
for(var key in value) {
if(value.hasOwnProperty(key)) {
obj[key] = value[key];
}
}
},
escapeExpression: function(string) {
// don't escape SafeStrings, since they're already safe
if (string instanceof Handlebars.SafeString) {
return string.toString();
} else if (string == null || string === false) {
return "";
}
// Force a string conversion as this will be done by the append regardless and
// the regex test will do this transparently behind the scenes, causing issues if
// an object's to string has escaped characters in it.
string = string.toString();
if(!possible.test(string)) { return string; }
return string.replace(badChars, escapeChar);
},
isEmpty: function(value) {
if (!value && value !== 0) {
return true;
} else if(toString.call(value) === "[object Array]" && value.length === 0) {
return true;
} else {
return false;
}
}
};
;
// lib/handlebars/runtime.js
Handlebars.VM = {
template: function(templateSpec) {
// Just add water
var container = {
escapeExpression: Handlebars.Utils.escapeExpression,
invokePartial: Handlebars.VM.invokePartial,
programs: [],
program: function(i, fn, data) {
var programWrapper = this.programs[i];
if(data) {
programWrapper = Handlebars.VM.program(i, fn, data);
} else if (!programWrapper) {
programWrapper = this.programs[i] = Handlebars.VM.program(i, fn);
}
return programWrapper;
},
programWithDepth: Handlebars.VM.programWithDepth,
noop: Handlebars.VM.noop,
compilerInfo: null
};
return function(context, options) {
options = options || {};
var result = templateSpec.call(container, Handlebars, context, options.helpers, options.partials, options.data);
var compilerInfo = container.compilerInfo || [],
compilerRevision = compilerInfo[0] || 1,
currentRevision = Handlebars.COMPILER_REVISION;
if (compilerRevision !== currentRevision) {
if (compilerRevision < currentRevision) {
var runtimeVersions = Handlebars.REVISION_CHANGES[currentRevision],
compilerVersions = Handlebars.REVISION_CHANGES[compilerRevision];
throw "Template was precompiled with an older version of Handlebars than the current runtime. "+
"Please update your precompiler to a newer version ("+runtimeVersions+") or downgrade your runtime to an older version ("+compilerVersions+").";
} else {
// Use the embedded version info since the runtime doesn't know about this revision yet
throw "Template was precompiled with a newer version of Handlebars than the current runtime. "+
"Please update your runtime to a newer version ("+compilerInfo[1]+").";
}
}
return result;
};
},
programWithDepth: function(i, fn, data /*, $depth */) {
var args = Array.prototype.slice.call(arguments, 3);
var program = function(context, options) {
options = options || {};
return fn.apply(this, [context, options.data || data].concat(args));
};
program.program = i;
program.depth = args.length;
return program;
},
program: function(i, fn, data) {
var program = function(context, options) {
options = options || {};
return fn(context, options.data || data);
};
program.program = i;
program.depth = 0;
return program;
},
noop: function() { return ""; },
invokePartial: function(partial, name, context, helpers, partials, data) {
var options = { helpers: helpers, partials: partials, data: data };
if(partial === undefined) {
throw new Handlebars.Exception("The partial " + name + " could not be found");
} else if(partial instanceof Function) {
return partial(context, options);
} else if (!Handlebars.compile) {
throw new Handlebars.Exception("The partial " + name + " could not be compiled when running in runtime-only mode");
} else {
partials[name] = Handlebars.compile(partial, {data: data !== undefined});
return partials[name](context, options);
}
}
};
Handlebars.template = Handlebars.VM.template;
;
// lib/handlebars/browser-suffix.js
})(Handlebars);
;
+21
View File
@@ -0,0 +1,21 @@
# -*- encoding: utf-8 -*-
require 'json'
package = JSON.parse(File.read('package.json'))
Gem::Specification.new do |gem|
gem.name = "handlebars-source"
gem.authors = ["Yehuda Katz"]
gem.email = ["wycats@gmail.com"]
gem.date = Time.now.strftime("%Y-%m-%d")
gem.description = %q{Handlebars.js source code wrapper for (pre)compilation gems.}
gem.summary = %q{Handlebars.js source code wrapper}
gem.homepage = "https://github.com/wycats/handlebars.js/"
gem.version = package["version"]
gem.files = [
'dist/handlebars.js',
'dist/handlebars.runtime.js',
'lib/handlebars/source.rb'
]
end
+17
View File
@@ -0,0 +1,17 @@
<?xml version="1.0"?>
<package>
<metadata>
<id>handlebars.js</id>
<version>1.0.0-rc.4</version>
<authors>handlebars.js Authors</authors>
<licenseUrl>https://github.com/wycats/handlebars.js/blob/master/LICENSE</licenseUrl>
<projectUrl>https://github.com/wycats/handlebars.js/</projectUrl>
<requireLicenseAcceptance>false</requireLicenseAcceptance>
<description>Extension of the Mustache logicless template language</description>
<releaseNotes></releaseNotes>
<tags>handlebars mustache template html</tags>
</metadata>
<files>
<file src="dist\handlebars.js" target="Content\Scripts" />
</files>
</package>
+23 -5
View File
@@ -1,14 +1,32 @@
var Handlebars = require("./handlebars/base");
module.exports = Handlebars;
var handlebars = require("./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)
require("./handlebars/utils");
utils = require("./handlebars/utils"),
compiler = require("./handlebars/compiler"),
runtime = require("./handlebars/runtime");
require("./handlebars/compiler");
require("./handlebars/runtime");
var create = function() {
var hb = handlebars.create();
utils.attach(hb);
compiler.attach(hb);
runtime.attach(hb);
return hb;
};
var Handlebars = create();
Handlebars.create = create;
module.exports = Handlebars; // instantiate an instance
// BEGIN(BROWSER)
// END(BROWSER)
// USAGE:
// var handlebars = require('handlebars');
// var singleton = handlebars.Handlebars,
// local = handlebars.create();
+73 -30
View File
@@ -1,22 +1,43 @@
/*jshint eqnull: true */
module.exports.create = function() {
var Handlebars = {};
// BEGIN(BROWSER)
/*jshint eqnull:true*/
this.Handlebars = {};
Handlebars.VERSION = "1.0.0-rc.4";
Handlebars.COMPILER_REVISION = 3;
(function(Handlebars) {
Handlebars.VERSION = "1.0.rc.1";
Handlebars.REVISION_CHANGES = {
1: '<= 1.0.rc.2', // 1.0.rc.2 is actually rev2 but doesn't report it
2: '== 1.0.0-rc.3',
3: '>= 1.0.0-rc.4'
};
Handlebars.helpers = {};
Handlebars.partials = {};
var toString = Object.prototype.toString,
functionType = '[object Function]',
objectType = '[object Object]';
Handlebars.registerHelper = function(name, fn, inverse) {
if(inverse) { fn.not = inverse; }
this.helpers[name] = fn;
if (toString.call(name) === objectType) {
if (inverse || fn) { throw new Handlebars.Exception('Arg not supported with multiple helpers'); }
Handlebars.Utils.extend(this.helpers, name);
} else {
if (inverse) { fn.not = inverse; }
this.helpers[name] = fn;
}
};
Handlebars.registerPartial = function(name, str) {
this.partials[name] = str;
if (toString.call(name) === objectType) {
Handlebars.Utils.extend(this.partials, name);
} else {
this.partials[name] = str;
}
};
Handlebars.registerHelper('helperMissing', function(arg) {
@@ -27,13 +48,9 @@ Handlebars.registerHelper('helperMissing', function(arg) {
}
});
var toString = Object.prototype.toString, functionType = "[object Function]";
Handlebars.registerHelper('blockHelperMissing', function(context, options) {
var inverse = options.inverse || function() {}, fn = options.fn;
var ret = "";
var type = toString.call(context);
if(type === functionType) { context = context.call(this); }
@@ -62,22 +79,53 @@ Handlebars.createFrame = Object.create || function(object) {
return obj;
};
Handlebars.logger = {
DEBUG: 0, INFO: 1, WARN: 2, ERROR: 3, level: 3,
methodMap: {0: 'debug', 1: 'info', 2: 'warn', 3: 'error'},
// can be overridden in the host environment
log: function(level, obj) {
if (Handlebars.logger.level <= level) {
var method = Handlebars.logger.methodMap[level];
if (typeof console !== 'undefined' && console[method]) {
console[method].call(console, obj);
}
}
}
};
Handlebars.log = function(level, obj) { Handlebars.logger.log(level, obj); };
Handlebars.registerHelper('each', function(context, options) {
var fn = options.fn, inverse = options.inverse;
var ret = "", data;
var i = 0, ret = "", data;
if (options.data) {
data = Handlebars.createFrame(options.data);
}
if(context && context.length > 0) {
for(var i=0, j=context.length; i<j; i++) {
if (data) { data.index = i; }
ret = ret + fn(context[i], { data: data });
if(context && typeof context === 'object') {
if(context instanceof Array){
for(var j = context.length; i<j; i++) {
if (data) { data.index = i; }
ret = ret + fn(context[i], { data: data });
}
} else {
for(var key in context) {
if(context.hasOwnProperty(key)) {
if(data) { data.key = key; }
ret = ret + fn(context[key], {data: data});
i++;
}
}
}
} else {
}
if(i === 0){
ret = inverse(this);
}
return ret;
});
@@ -93,24 +141,19 @@ Handlebars.registerHelper('if', function(context, options) {
});
Handlebars.registerHelper('unless', function(context, options) {
var fn = options.fn, inverse = options.inverse;
options.fn = inverse;
options.inverse = fn;
return Handlebars.helpers['if'].call(this, context, options);
return Handlebars.helpers['if'].call(this, context, {fn: options.inverse, inverse: options.fn});
});
Handlebars.registerHelper('with', function(context, options) {
return options.fn(context);
if (!Handlebars.Utils.isEmpty(context)) return options.fn(context);
});
Handlebars.registerHelper('log', function(context) {
Handlebars.log(context);
Handlebars.registerHelper('log', function(context, options) {
var level = options.data && options.data.level != null ? parseInt(options.data.level, 10) : 1;
Handlebars.log(level, context);
});
}(this.Handlebars));
// END(BROWSER)
module.exports = this.Handlebars;
return Handlebars;
};
+3
View File
@@ -0,0 +1,3 @@
var Handlebars = {};
(function(Handlebars, undefined) {
+1
View File
@@ -0,0 +1 @@
})(Handlebars);
+103 -93
View File
@@ -1,123 +1,133 @@
var Handlebars = require('./base');
exports.attach = function(Handlebars) {
// BEGIN(BROWSER)
(function() {
Handlebars.AST = {};
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.ProgramNode = function(statements, inverse) {
this.type = "program";
this.statements = statements;
if(inverse) { this.inverse = new Handlebars.AST.ProgramNode(inverse); }
};
Handlebars.AST.MustacheNode = function(rawParams, hash, unescaped) {
this.type = "mustache";
this.escaped = !unescaped;
this.hash = hash;
Handlebars.AST.MustacheNode = function(rawParams, hash, unescaped) {
this.type = "mustache";
this.escaped = !unescaped;
this.hash = hash;
var id = this.id = rawParams[0];
var params = this.params = rawParams.slice(1);
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 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);
// 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.
};
// if a mustache is an eligible helper but not a definite
// helper, it is ambiguous, and will be resolved in a later
// pass or at runtime.
};
Handlebars.AST.PartialNode = function(id, context) {
this.type = "partial";
// TODO: disallow complex IDs
this.id = id;
this.context = context;
};
Handlebars.AST.PartialNode = function(partialName, context) {
this.type = "partial";
this.partialName = partialName;
this.context = context;
};
Handlebars.AST.BlockNode = function(mustache, program, inverse, close) {
var verifyMatch = function(open, close) {
if(open.original !== close.original) {
throw new Handlebars.Exception(open.original + " doesn't match " + close.original);
}
};
Handlebars.AST.BlockNode = function(mustache, program, inverse, close) {
verifyMatch(mustache.id, close);
this.type = "block";
this.mustache = mustache;
this.program = program;
this.inverse = inverse;
verifyMatch(mustache.id, close);
this.type = "block";
this.mustache = mustache;
this.program = program;
this.inverse = inverse;
if (this.inverse && !this.program) {
this.isInverse = true;
if (this.inverse && !this.program) {
this.isInverse = true;
}
};
Handlebars.AST.ContentNode = function(string) {
this.type = "content";
this.string = string;
};
Handlebars.AST.HashNode = function(pairs) {
this.type = "hash";
this.pairs = pairs;
};
Handlebars.AST.IdNode = function(parts) {
this.type = "ID";
this.original = parts.join(".");
var dig = [], depth = 0;
for(var i=0,l=parts.length; i<l; i++) {
var part = parts[i];
if (part === ".." || part === "." || part === "this") {
if (dig.length > 0) { throw new Handlebars.Exception("Invalid path: " + this.original); }
else if (part === "..") { depth++; }
else { this.isScoped = true; }
}
};
else { dig.push(part); }
}
Handlebars.AST.ContentNode = function(string) {
this.type = "content";
this.string = string;
};
this.parts = dig;
this.string = dig.join('.');
this.depth = depth;
Handlebars.AST.HashNode = function(pairs) {
this.type = "hash";
this.pairs = pairs;
};
// 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;
Handlebars.AST.IdNode = function(parts) {
this.type = "ID";
this.original = parts.join(".");
this.stringModeValue = this.string;
};
var dig = [], depth = 0;
Handlebars.AST.PartialNameNode = function(name) {
this.type = "PARTIAL_NAME";
this.name = name;
};
for(var i=0,l=parts.length; i<l; i++) {
var part = parts[i];
Handlebars.AST.DataNode = function(id) {
this.type = "DATA";
this.id = id;
};
if(part === "..") { depth++; }
else if(part === "." || part === "this") { this.isScoped = true; }
else { dig.push(part); }
}
Handlebars.AST.StringNode = function(string) {
this.type = "STRING";
this.string = string;
this.stringModeValue = string;
};
this.parts = dig;
this.string = dig.join('.');
this.depth = depth;
Handlebars.AST.IntegerNode = function(integer) {
this.type = "INTEGER";
this.integer = integer;
this.stringModeValue = Number(integer);
};
// 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;
};
Handlebars.AST.BooleanNode = function(bool) {
this.type = "BOOLEAN";
this.bool = bool;
this.stringModeValue = bool === "true";
};
Handlebars.AST.DataNode = function(id) {
this.type = "DATA";
this.id = id;
};
Handlebars.AST.CommentNode = function(comment) {
this.type = "comment";
this.comment = comment;
};
Handlebars.AST.StringNode = function(string) {
this.type = "STRING";
this.string = string;
};
Handlebars.AST.IntegerNode = function(integer) {
this.type = "INTEGER";
this.integer = integer;
};
Handlebars.AST.BooleanNode = function(bool) {
this.type = "BOOLEAN";
this.bool = bool;
};
Handlebars.AST.CommentNode = function(comment) {
this.type = "comment";
this.comment = comment;
};
})();
// END(BROWSER)
return Handlebars;
};
+12 -18
View File
@@ -1,27 +1,21 @@
var handlebars = require("./parser").parser;
var Handlebars = require("../base");
var handlebars = require("./parser");
exports.attach = function(Handlebars) {
// BEGIN(BROWSER)
Handlebars.Parser = handlebars;
Handlebars.parse = function(string) {
Handlebars.parse = function(input) {
// Just return if an already-compile AST was passed in.
if(input.constructor === Handlebars.AST.ProgramNode) { return input; }
Handlebars.Parser.yy = Handlebars.AST;
return Handlebars.Parser.parse(string);
return Handlebars.Parser.parse(input);
};
Handlebars.print = function(ast) {
return new Handlebars.PrintVisitor().accept(ast);
};
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;
return Handlebars;
};
File diff suppressed because it is too large Load Diff
+14 -5
View File
@@ -1,7 +1,16 @@
// Each of these module will augment the Handlebars object as it loads. No need to perform addition operations
module.exports = require("./base");
require("./visitor");
require("./printer");
module.exports.attach = function(Handlebars) {
require("./ast");
require("./compiler");
var visitor = require("./visitor"),
printer = require("./printer"),
ast = require("./ast"),
compiler = require("./compiler");
visitor.attach(Handlebars);
printer.attach(Handlebars);
ast.attach(Handlebars);
compiler.attach(Handlebars);
return Handlebars;
};
+14 -3
View File
@@ -1,6 +1,11 @@
var Handlebars = require("./base");
exports.attach = function(Handlebars) {
// BEGIN(BROWSER)
Handlebars.print = function(ast) {
return new Handlebars.PrintVisitor().accept(ast);
};
Handlebars.PrintVisitor = function() { this.padding = 0; };
Handlebars.PrintVisitor.prototype = new Handlebars.Visitor();
@@ -72,7 +77,7 @@ Handlebars.PrintVisitor.prototype.mustache = function(mustache) {
};
Handlebars.PrintVisitor.prototype.partial = function(partial) {
var content = this.accept(partial.id);
var content = this.accept(partial.partialName);
if(partial.context) { content = content + " " + this.accept(partial.context); }
return this.pad("{{> " + content + " }}");
};
@@ -111,6 +116,10 @@ Handlebars.PrintVisitor.prototype.ID = function(id) {
}
};
Handlebars.PrintVisitor.prototype.PARTIAL_NAME = function(partialName) {
return "PARTIAL:" + partialName.name;
};
Handlebars.PrintVisitor.prototype.DATA = function(data) {
return "@" + data.id;
};
@@ -124,4 +133,6 @@ Handlebars.PrintVisitor.prototype.comment = function(comment) {
};
// END(BROWSER)
exports.PrintVisitor = Handlebars.PrintVisitor;
return Handlebars;
};
+6 -1
View File
@@ -1,4 +1,4 @@
var Handlebars = require("./base");
exports.attach = function(Handlebars) {
// BEGIN(BROWSER)
@@ -9,5 +9,10 @@ Handlebars.Visitor.prototype = {
return this[object.type](object);
}
};
// END(BROWSER)
return Handlebars;
};
+42 -14
View File
@@ -1,6 +1,7 @@
var Handlebars = require("./base");
exports.attach = function(Handlebars) {
// BEGIN(BROWSER)
Handlebars.VM = {
template: function(templateSpec) {
// Just add water
@@ -11,39 +12,63 @@ Handlebars.VM = {
program: function(i, fn, data) {
var programWrapper = this.programs[i];
if(data) {
return Handlebars.VM.program(fn, data);
} else if(programWrapper) {
return programWrapper;
} else {
programWrapper = this.programs[i] = Handlebars.VM.program(fn);
return programWrapper;
programWrapper = Handlebars.VM.program(i, fn, data);
} else if (!programWrapper) {
programWrapper = this.programs[i] = Handlebars.VM.program(i, fn);
}
return programWrapper;
},
programWithDepth: Handlebars.VM.programWithDepth,
noop: Handlebars.VM.noop
noop: Handlebars.VM.noop,
compilerInfo: null
};
return function(context, options) {
options = options || {};
return templateSpec.call(container, Handlebars, context, options.helpers, options.partials, options.data);
var result = templateSpec.call(container, Handlebars, context, options.helpers, options.partials, options.data);
var compilerInfo = container.compilerInfo || [],
compilerRevision = compilerInfo[0] || 1,
currentRevision = Handlebars.COMPILER_REVISION;
if (compilerRevision !== currentRevision) {
if (compilerRevision < currentRevision) {
var runtimeVersions = Handlebars.REVISION_CHANGES[currentRevision],
compilerVersions = Handlebars.REVISION_CHANGES[compilerRevision];
throw "Template was precompiled with an older version of Handlebars than the current runtime. "+
"Please update your precompiler to a newer version ("+runtimeVersions+") or downgrade your runtime to an older version ("+compilerVersions+").";
} else {
// Use the embedded version info since the runtime doesn't know about this revision yet
throw "Template was precompiled with a newer version of Handlebars than the current runtime. "+
"Please update your runtime to a newer version ("+compilerInfo[1]+").";
}
}
return result;
};
},
programWithDepth: function(fn, data, $depth) {
var args = Array.prototype.slice.call(arguments, 2);
programWithDepth: function(i, fn, data /*, $depth */) {
var args = Array.prototype.slice.call(arguments, 3);
return function(context, options) {
var program = function(context, options) {
options = options || {};
return fn.apply(this, [context, options.data || data].concat(args));
};
program.program = i;
program.depth = args.length;
return program;
},
program: function(fn, data) {
return function(context, options) {
program: function(i, fn, data) {
var program = function(context, options) {
options = options || {};
return fn(context, options.data || data);
};
program.program = i;
program.depth = 0;
return program;
},
noop: function() { return ""; },
invokePartial: function(partial, name, context, helpers, partials, data) {
@@ -66,3 +91,6 @@ Handlebars.template = Handlebars.VM.template;
// END(BROWSER)
return Handlebars;
};
+11
View File
@@ -0,0 +1,11 @@
module Handlebars
module Source
def self.bundled_path
File.expand_path("../../../dist/handlebars.js", __FILE__)
end
def self.runtime_bundled_path
File.expand_path("../../../dist/handlebars.runtime.js", __FILE__)
end
end
end
+59 -45
View File
@@ -1,14 +1,18 @@
var Handlebars = require("./base");
exports.attach = function(Handlebars) {
var toString = Object.prototype.toString;
// BEGIN(BROWSER)
var errorProps = ['description', 'fileName', 'lineNumber', 'message', 'name', 'number', 'stack'];
Handlebars.Exception = function(message) {
var tmp = Error.prototype.constructor.apply(this, arguments);
for (var p in tmp) {
if (tmp.hasOwnProperty(p)) { this[p] = tmp[p]; }
// 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]];
}
this.message = tmp.message;
};
Handlebars.Exception.prototype = new Error();
@@ -20,50 +24,60 @@ Handlebars.SafeString.prototype.toString = function() {
return this.string.toString();
};
(function() {
var escape = {
"&": "&amp;",
"<": "&lt;",
">": "&gt;",
'"': "&quot;",
"'": "&#x27;",
"`": "&#x60;"
};
var escape = {
"&": "&amp;",
"<": "&lt;",
">": "&gt;",
'"': "&quot;",
"'": "&#x27;",
"`": "&#x60;"
};
var badChars = /[&<>"'`]/g;
var possible = /[&<>"'`]/;
var badChars = /[&<>"'`]/g;
var possible = /[&<>"'`]/;
var escapeChar = function(chr) {
return escape[chr] || "&amp;";
};
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;
Handlebars.Utils = {
extend: function(obj, value) {
for(var key in value) {
if(value.hasOwnProperty(key)) {
obj[key] = value[key];
}
}
};
})();
},
escapeExpression: function(string) {
// don't escape SafeStrings, since they're already safe
if (string instanceof Handlebars.SafeString) {
return string.toString();
} else if (string == null || string === false) {
return "";
}
// Force a string conversion as this will be done by the append regardless and
// the regex test will do this transparently behind the scenes, causing issues if
// an object's to string has escaped characters in it.
string = string.toString();
if(!possible.test(string)) { return string; }
return string.replace(badChars, escapeChar);
},
isEmpty: function(value) {
if (!value && value !== 0) {
return true;
} else if(toString.call(value) === "[object Array]" && value.length === 0) {
return true;
} else {
return false;
}
}
};
// END(BROWSER)
return Handlebars;
};
+6 -2
View File
@@ -1,7 +1,7 @@
{
"name": "handlebars",
"description": "Extension of the Mustache logicless template language",
"version": "1.0.0-rc.1",
"version": "1.0.11",
"homepage": "http://www.handlebarsjs.com/",
"keywords": [
"handlebars mustache template html"
@@ -18,7 +18,11 @@
"uglify-js": "~1.2"
},
"devDependencies": {
"mocha": "*"
"benchmark": "~1.0",
"dust": "~0.3",
"jison": "~0.3",
"mocha": "*",
"mustache": "~0.7.2"
},
"main": "lib/handlebars.js",
"bin": {
+55
View File
@@ -0,0 +1,55 @@
# Release Notes
## Development
[Commits](https://github.com/wycats/handlebars.js/compare/v1.0.11...master)
## 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...master)
## 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)
+153 -2
View File
@@ -114,6 +114,10 @@ describe "Parser" do
"@#{id}"
end
def partial_name(name)
"PARTIAL:#{name}"
end
def path(*parts)
"PATH:#{parts.join("/")}"
end
@@ -218,11 +222,15 @@ describe "Parser" do
end
it "parses a partial" do
ast_for("{{> foo }}").should == root { partial id("foo") }
ast_for("{{> foo }}").should == root { partial partial_name("foo") }
end
it "parses a partial with context" do
ast_for("{{> foo bar}}").should == root { partial id("foo"), id("bar") }
ast_for("{{> foo bar}}").should == root { partial partial_name("foo"), id("bar") }
end
it "parses a partial with a complex name" do
ast_for("{{> shared/partial}}").should == root { partial partial_name("shared/partial") }
end
it "parses a comment" do
@@ -253,6 +261,130 @@ describe "Parser" do
end
end
it "parses an inverse ('else'-style) section" do
ast_for("{{#foo}} bar {{else}} baz {{/foo}}").should == root do
block do
mustache id("foo")
program do
content " bar "
end
inverse do
content " baz "
end
end
end
end
it "parses empty blocks" do
ast_for("{{#foo}}{{/foo}}").should == root do
block do
mustache id("foo")
program do
# empty program
end
end
end
end
it "parses empty blocks with empty inverse section" do
ast_for("{{#foo}}{{^}}{{/foo}}").should == root do
block do
mustache id("foo")
program do
# empty program
end
inverse do
# empty inverse
end
end
end
end
it "parses empty blocks with empty inverse ('else'-style) section" do
ast_for("{{#foo}}{{else}}{{/foo}}").should == root do
block do
mustache id("foo")
program do
# empty program
end
inverse do
# empty inverse
end
end
end
end
it "parses non-empty blocks with empty inverse section" do
ast_for("{{#foo}} bar {{^}}{{/foo}}").should == root do
block do
mustache id("foo")
program do
content " bar "
end
inverse do
# empty inverse
end
end
end
end
it "parses non-empty blocks with empty inverse ('else'-style) section" do
ast_for("{{#foo}} bar {{else}}{{/foo}}").should == root do
block do
mustache id("foo")
program do
content " bar "
end
inverse do
# empty inverse
end
end
end
end
it "parses empty blocks with non-empty inverse section" do
ast_for("{{#foo}}{{^}} bar {{/foo}}").should == root do
block do
mustache id("foo")
program do
# empty program
end
inverse do
content " bar "
end
end
end
end
it "parses empty blocks with non-empty inverse ('else'-style) section" do
ast_for("{{#foo}}{{else}} bar {{/foo}}").should == root do
block do
mustache id("foo")
program do
# empty program
end
inverse do
content " bar "
end
end
end
end
it "parses a standalone inverse section" do
ast_for("{{^foo}}bar{{/foo}}").should == root do
block do
@@ -279,4 +411,23 @@ describe "Parser" do
it "knows how to report the correct line number in errors when the first character is a newline" do
lambda { ast_for("\n\nhello\n\nmy\n\n{{foo}") }.should raise_error(V8::JSError, /Parse error on line 7/m)
end
context "externally compiled AST" do
it "can pass through an already-compiled AST" do
ast_for(@context.eval('new Handlebars.AST.ProgramNode([ new Handlebars.AST.ContentNode("Hello")]);')).should == root do
content "Hello"
end
end
it "can pass through an already-compiled AST via compile/precompile" do
@context = Handlebars::Spec::CONTEXT
code = 'Handlebars.compile(new Handlebars.AST.ProgramNode([ new Handlebars.AST.ContentNode("Hello")]))();'
@context.eval(code).should == "Hello"
code = @context.eval 'Handlebars.precompile(new Handlebars.AST.ProgramNode([ new Handlebars.AST.ContentNode("Hello")]))'
@context.eval("(#{code})(this)").should == "Hello"
end
end
end
+340 -36
View File
@@ -20,27 +20,33 @@ if (!Handlebars) {
return Handlebars.compile(template, options);
}
};
} else {
var _equal = equal;
equals = equal = function(a, b, msg) {
// Allow exec with missing message params
_equal(a, b, msg || '');
};
}
suite("basic context");
Handlebars.registerHelper('helperMissing', function(helper, context) {
if(helper === "link_to") {
return new Handlebars.SafeString("<a>" + context + "</a>");
}
});
function shouldCompileTo(string, hashOrArray, expected, message) {
shouldCompileToWithPartials(string, hashOrArray, false, expected, message);
}
function shouldCompileToWithPartials(string, hashOrArray, partials, expected, message) {
var result = compileWithPartials(string, hashOrArray, partials);
equal(result, expected, "'" + expected + "' should === '" + result + "': " + message);
}
function compileWithPartials(string, hashOrArray, partials) {
var template = CompilerContext[partials ? 'compileWithPartial' : 'compile'](string), ary;
if(Object.prototype.toString.call(hashOrArray) === "[object Array]") {
var helpers = hashOrArray[1];
if(helpers) {
for(var prop in Handlebars.helpers) {
helpers[prop] = Handlebars.helpers[prop];
helpers[prop] = helpers[prop] || Handlebars.helpers[prop];
}
}
@@ -51,18 +57,31 @@ function shouldCompileToWithPartials(string, hashOrArray, partials, expected, me
ary = [hashOrArray];
}
var result = template.apply(this, ary);
equal(result, expected, "'" + expected + "' should === '" + result + "': " + message);
return template.apply(this, ary);
}
function shouldThrow(fn, exception, message) {
var caught = false;
var caught = false,
exType, exMessage;
if (exception instanceof Array) {
exType = exception[0];
exMessage = exception[1];
} else if (typeof exception === 'string') {
exType = Error;
exMessage = exception;
} else {
exType = exception;
}
try {
fn();
}
catch (e) {
if (e instanceof exception) {
caught = true;
if (e instanceof exType) {
if (!exMessage || e.message === exMessage) {
caught = true;
}
}
}
@@ -73,6 +92,12 @@ test("most basic", function() {
shouldCompileTo("{{foo}}", { foo: "foo" }, "foo");
});
test("escaping", function() {
shouldCompileTo("\\{{foo}}", { foo: "food" }, "{{foo}}");
shouldCompileTo("\\\\{{foo}}", { foo: "food" }, "\\food");
shouldCompileTo("\\\\ {{foo}}", { foo: "food" }, "\\\\ food");
});
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");
@@ -136,6 +161,8 @@ test("functions returning safestrings shouldn't be escaped", function() {
test("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");
});
test("paths with hyphens", function() {
@@ -155,20 +182,15 @@ test("nested paths with empty string value", function() {
});
test("literal paths", function() {
shouldCompileTo("Goodbye {{[@alan]/expression}} world!", {"@alan": {expression: "beautiful"}},
"Goodbye beautiful world!", "Literal paths can be used");
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");
});
test("--- TODO --- bad idea nested paths", function() {
return;
var hash = {goodbyes: [{text: "goodbye"}, {text: "Goodbye"}, {text: "GOODBYE"}], world: "world"};
shouldThrow(function() {
CompilerContext.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('literal references', function() {
shouldCompileTo("Goodbye {{[foo bar]}} world!", {"foo bar": "beautiful"},
"Goodbye beautiful world!", "Literal paths can be used");
});
test("that current context path ({{.}}) doesn't hit helpers", function() {
@@ -191,8 +213,15 @@ test("this keyword in paths", function() {
shouldCompileTo(string, hash, "helloHelloHELLO", "This keyword evaluates in more complex paths");
});
test("this keyword nested inside path", function() {
var string = "{{#hellos}}{{text/this/foo}}{{/hellos}}";
shouldThrow(function() {
CompilerContext.compile(string);
}, Error, "Should throw exception");
});
test("this keyword in helpers", function() {
var helpers = {foo: function(value, options) {
var helpers = {foo: function(value) {
return 'bar ' + value;
}};
var string = "{{#goodbyes}}{{foo this}}{{/goodbyes}}";
@@ -205,6 +234,13 @@ test("this keyword in helpers", function() {
shouldCompileTo(string, [hash, helpers], "bar hellobar Hellobar HELLO", "This keyword evaluates in more complex paths");
});
test("this keyword nested inside helpers param", function() {
var string = "{{#hellos}}{{foo text/this/foo}}{{/hellos}}";
shouldThrow(function() {
CompilerContext.compile(string);
}, Error, "Should throw exception");
});
suite("inverted sections");
test("inverted sections with unset value", function() {
@@ -270,6 +306,14 @@ test("block with complex lookup", function() {
"Templates can access variables in contexts up the stack with relative path syntax");
});
test("block with complex lookup using nested context", function() {
var string = "{{#goodbyes}}{{text}} cruel {{foo/../name}}! {{/goodbyes}}";
shouldThrow(function() {
CompilerContext.compile(string);
}, Error, "Should throw exception");
});
test("helper with complex lookup$", function() {
var string = "{{#goodbyes}}{{{link ../prefix}}}{{/goodbyes}}";
var hash = {prefix: "/root", goodbyes: [{text: "Goodbye", url: "goodbye"}]};
@@ -435,12 +479,38 @@ test("providing a helpers hash", function() {
"Goodbye cruel world!", "helpers hash is available inside other blocks");
});
test("in cases of conflict, the explicit hash wins", function() {
test("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");
});
test("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.");
});
test("Multiple global helper registration", function() {
var helpers = Handlebars.helpers;
try {
Handlebars.helpers = {};
Handlebars.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!!");
} finally {
if (helpers) {
Handlebars.helpers = helpers;
}
}
});
suite("partials");
@@ -473,14 +543,14 @@ test("rendering undefined partial throws an exception", function() {
shouldThrow(function() {
var template = CompilerContext.compile("{{> whatever}}");
template();
}, Handlebars.Exception, "Should throw exception");
}, [Handlebars.Exception, 'The partial whatever could not be found'], "Should throw exception");
});
test("rendering template partial in vm mode throws an exception", function() {
shouldThrow(function() {
var template = CompilerContext.compile("{{> whatever}}");
template();
}, Handlebars.Exception, "Should throw exception");
}, [Handlebars.Exception, 'The partial whatever could not be found'], "Should throw exception");
});
test("rendering function partial in vm mode", function() {
@@ -500,13 +570,32 @@ test("GH-14: a partial preceding a selector", function() {
shouldCompileToWithPartials(string, [hash, {}, {dude:dude}], true, "Dudes: Jeepers Creepers", "Regular selectors can follow a partial");
});
test("Partials with literal paths", function() {
var string = "Dudes: {{> [dude]}}";
test("Partials with slash paths", function() {
var string = "Dudes: {{> shared/dude}}";
var dude = "{{name}}";
var hash = {name:"Jeepers", another_dude:"Creepers"};
shouldCompileToWithPartials(string, [hash, {}, {dude:dude}], true, "Dudes: Jeepers", "Partials can use literal paths");
shouldCompileToWithPartials(string, [hash, {}, {'shared/dude':dude}], true, "Dudes: Jeepers", "Partials can use literal paths");
});
test("Multiple partial registration", function() {
Handlebars.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");
});
test("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");
});
suite("String literal parameters");
test("simple literals work", function() {
@@ -520,6 +609,16 @@ test("simple literals work", function() {
}};
shouldCompileTo(string, [hash, helpers], "Message: Hello world 12 times: true false", "template with a simple String literal");
});
test("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");
});
test("using a quote in the middle of a parameter raises an error", function() {
shouldThrow(function() {
@@ -568,13 +667,34 @@ test("constructing a safestring from a string and checking its type", function()
equal(safe, "testing 1, 2, 3", "SafeString is equivalent to its underlying string");
});
test("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>");
});
suite("helperMissing");
test("if a context is not found, helperMissing is used", function() {
shouldThrow(function() {
var template = CompilerContext.compile("{{hello}} {{link_to world}}");
template({});
}, [Error, "Could not find property 'link_to'"], "Should throw exception");
});
test("if a context is not found, custom helperMissing is used", function() {
var string = "{{hello}} {{link_to world}}";
var context = { hello: "Hello", world: "world" };
shouldCompileTo(string, context, "Hello <a>world</a>");
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>");
});
suite("knownHelpers");
@@ -616,6 +736,16 @@ test("Invert blocks work in knownHelpers only mode", function() {
var result = template({foo: false});
equal(result, "bar", "'bar' should === '" + result);
});
test("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);
});
test("Unknown helper call in knownHelpers only mode should throw", function() {
shouldThrow(function() {
CompilerContext.compile("{{typeof hello}}", {knownHelpersOnly: true});
}, Error, 'specified knownHelpersOnly');
});
suite("blockHelperMissing");
@@ -624,6 +754,11 @@ test("lambdas are resolved by blockHelperMissing, not handlebars proper", functi
var data = { truthy: function() { return true; } };
shouldCompileTo(string, data, "yep");
});
test("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, "");
});
var teardown;
suite("built-in helpers", {
@@ -673,6 +808,22 @@ test("each", function() {
"each with array argument ignores the contents when empty");
});
test("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!";
ok(actual === expected1 || actual === expected2, "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");
});
test("each with @index", function() {
var string = "{{#each goodbyes}}{{@index}}. {{text}}! {{/each}}cruel {{world}}!";
var hash = {goodbyes: [{text: "goodbye"}, {text: "Goodbye"}, {text: "GOODBYE"}], world: "world"};
@@ -683,16 +834,34 @@ test("each with @index", function() {
equal(result, "0. goodbye! 1. Goodbye! 2. GOODBYE! cruel world!", "The @index variable is used");
});
test("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');
});
Handlebars.registerHelper('detectDataInsideEach', function(options) {
return options.data && options.data.exclaim;
});
test("log", function() {
var string = "{{log blah}}";
var hash = { blah: "whee" };
var logArg;
var levelArg, logArg;
var originalLog = Handlebars.log;
Handlebars.log = function(arg){ logArg = arg; };
Handlebars.log = function(level, arg){ levelArg = level, logArg = arg; };
teardown = function(){ Handlebars.log = originalLog; };
shouldCompileTo(string, hash, "", "log should not display");
equals(1, levelArg, "should call log with 1");
equals("whee", logArg, "should call log with 'whee'");
});
@@ -1070,7 +1239,7 @@ test("arguments to helpers can be retrieved from options hash in string form", f
var template = CompilerContext.compile('{{wycats is.a slave.driver}}', {stringParams: true});
var helpers = {
wycats: function(passiveVoice, noun, options) {
wycats: function(passiveVoice, noun) {
return "HELP ME MY BOSS " + passiveVoice + ' ' + noun;
}
};
@@ -1118,6 +1287,74 @@ test("when inside a block in String mode, .. passes the appropriate context in t
equals(result, "STOP ME FROM READING HACKER NEWS I need-a dad.joke", "Proper context variable output");
});
test("in string mode, 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");
});
test("in string mode, 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");
});
test("in string mode, 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");
});
test("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});
@@ -1198,3 +1435,70 @@ test("bug reported by @fat where lambdas weren't being properly resolved", funct
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);
});
test("Passing falsy values to Handlebars.compile throws an error", function() {
shouldThrow(function() {
CompilerContext.compile(null);
}, "You must pass a string or Handlebars AST to Handlebars.precompile. You passed null");
});
test('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');
});
test('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 ');
});
test('GH-458: Scoped this identifier', function() {
shouldCompileTo('{{./foo}}', {foo: 'bar'}, 'bar');
});
test('GH-375: Unicode line terminators', function() {
shouldCompileTo('\u2028', {}, '\u2028');
});
suite('Utils');
test('escapeExpression', function() {
equal(Handlebars.Utils.escapeExpression('foo<&"\'>'), 'foo&lt;&amp;&quot;&#x27;&gt;');
equal(Handlebars.Utils.escapeExpression(new Handlebars.SafeString('foo<&"\'>')), 'foo<&"\'>');
equal(Handlebars.Utils.escapeExpression(''), '');
equal(Handlebars.Utils.escapeExpression(undefined), '');
equal(Handlebars.Utils.escapeExpression(null), '');
equal(Handlebars.Utils.escapeExpression(false), '');
equal(Handlebars.Utils.escapeExpression(0), '0');
equal(Handlebars.Utils.escapeExpression({}), {}.toString());
equal(Handlebars.Utils.escapeExpression([]), [].toString());
});
test('isEmpty', function() {
equal(Handlebars.Utils.isEmpty(undefined), true);
equal(Handlebars.Utils.isEmpty(null), true);
equal(Handlebars.Utils.isEmpty(false), true);
equal(Handlebars.Utils.isEmpty(''), true);
equal(Handlebars.Utils.isEmpty([]), true);
equal(Handlebars.Utils.isEmpty(0), false);
equal(Handlebars.Utils.isEmpty([1]), false);
equal(Handlebars.Utils.isEmpty('foo'), false);
equal(Handlebars.Utils.isEmpty({bar: 1}), false);
});
+10 -35
View File
@@ -76,9 +76,7 @@ module Handlebars
CONTEXT.instance_eval do |context|
Handlebars::Spec.load_helpers(context);
Handlebars::Spec.js_load(context, 'lib/handlebars/base.js');
Handlebars::Spec.js_load(context, 'lib/handlebars/utils.js');
Handlebars::Spec.js_load(context, 'lib/handlebars/runtime.js');
Handlebars::Spec.js_load(context, 'dist/handlebars.js');
context["CompilerContext"] = {}
CompilerContext = context["CompilerContext"]
@@ -89,47 +87,24 @@ module Handlebars
end
CompilerContext["compileWithPartial"] = proc do |this, *args|
template, options = args[0], args[1] || nil
FULL_CONTEXT["Handlebars"]["compile"].call(template, options);
context["Handlebars"]["compile"].call(template, options);
end
end
PARSER_CONTEXT = V8::Context.new
PARSER_CONTEXT.instance_eval do |context|
Handlebars::Spec.load_helpers(context);
Handlebars::Spec.js_load(context, 'lib/handlebars/compiler/parser.js');
end
COMPILE_CONTEXT = V8::Context.new
COMPILE_CONTEXT.instance_eval do |context|
Handlebars::Spec.load_helpers(context);
Handlebars::Spec.js_load(context, 'lib/handlebars/base.js');
Handlebars::Spec.js_load(context, 'lib/handlebars/utils.js');
Handlebars::Spec.js_load(context, 'lib/handlebars/compiler/parser.js');
Handlebars::Spec.js_load(context, 'lib/handlebars/compiler/base.js');
Handlebars::Spec.js_load(context, 'lib/handlebars/compiler/ast.js');
Handlebars::Spec.js_load(context, 'dist/handlebars.js');
Handlebars::Spec.js_load(context, 'lib/handlebars/compiler/visitor.js');
Handlebars::Spec.js_load(context, 'lib/handlebars/compiler/printer.js');
Handlebars::Spec.js_load(context, 'lib/handlebars/compiler/compiler.js');
context["Handlebars"]["logger"]["level"] = ENV["DEBUG_JS"] ? context["Handlebars"]["logger"][ENV["DEBUG_JS"]] : 4
context["Handlebars"]["logger"]["log"] = proc do |this, level, str|
logger_level = context["Handlebars"]["logger"]["level"].to_i
if logger_level <= level
puts str
end
end
end
FULL_CONTEXT = V8::Context.new
FULL_CONTEXT.instance_eval do |context|
Handlebars::Spec.load_helpers(context);
Handlebars::Spec.js_load(context, 'lib/handlebars/base.js');
Handlebars::Spec.js_load(context, 'lib/handlebars/utils.js');
Handlebars::Spec.js_load(context, 'lib/handlebars/compiler/parser.js');
Handlebars::Spec.js_load(context, 'lib/handlebars/compiler/base.js');
Handlebars::Spec.js_load(context, 'lib/handlebars/compiler/ast.js');
Handlebars::Spec.js_load(context, 'lib/handlebars/compiler/visitor.js');
Handlebars::Spec.js_load(context, 'lib/handlebars/compiler/printer.js');
Handlebars::Spec.js_load(context, 'lib/handlebars/compiler/compiler.js');
Handlebars::Spec.js_load(context, 'lib/handlebars/runtime.js');
context["Handlebars"]["logger"]["level"] = ENV["DEBUG_JS"] ? context["Handlebars"]["logger"][ENV["DEBUG_JS"]] : 4
+38 -13
View File
@@ -2,12 +2,9 @@ require "spec_helper"
require "timeout"
describe "Tokenizer" do
let(:parser) { @context["handlebars"] }
let(:lexer) { @context["handlebars"]["lexer"] }
let(:parser) { Handlebars::Spec::PARSER_CONTEXT["handlebars"] }
let(:lexer) { Handlebars::Spec::PARSER_CONTEXT["handlebars"]["lexer"] }
before(:all) do
@compiles = true
end
Token = Struct.new(:name, :text)
def tokenize(string)
@@ -132,24 +129,24 @@ describe "Tokenizer" do
result[4].should be_token("CONTENT", " baz")
end
it "tokenizes a partial as 'OPEN_PARTIAL ID CLOSE'" do
it "tokenizes a partial as 'OPEN_PARTIAL PARTIAL_NAME CLOSE'" do
result = tokenize("{{> foo}}")
result.should match_tokens(%w(OPEN_PARTIAL ID CLOSE))
result.should match_tokens(%w(OPEN_PARTIAL PARTIAL_NAME CLOSE))
end
it "tokenizes a partial with context as 'OPEN_PARTIAL ID ID CLOSE'" do
it "tokenizes a partial with context as 'OPEN_PARTIAL PARTIAL_NAME ID CLOSE'" do
result = tokenize("{{> foo bar }}")
result.should match_tokens(%w(OPEN_PARTIAL ID ID CLOSE))
result.should match_tokens(%w(OPEN_PARTIAL PARTIAL_NAME ID CLOSE))
end
it "tokenizes a partial without spaces as 'OPEN_PARTIAL ID CLOSE'" do
it "tokenizes a partial without spaces as 'OPEN_PARTIAL PARTIAL_NAME CLOSE'" do
result = tokenize("{{>foo}}")
result.should match_tokens(%w(OPEN_PARTIAL ID CLOSE))
result.should match_tokens(%w(OPEN_PARTIAL PARTIAL_NAME CLOSE))
end
it "tokenizes a partial space at the end as 'OPEN_PARTIAL ID CLOSE'" do
it "tokenizes a partial space at the end as 'OPEN_PARTIAL PARTIAL_NAME CLOSE'" do
result = tokenize("{{>foo }}")
result.should match_tokens(%w(OPEN_PARTIAL ID CLOSE))
result.should match_tokens(%w(OPEN_PARTIAL PARTIAL_NAME CLOSE))
end
it "tokenizes a comment as 'COMMENT'" do
@@ -158,6 +155,18 @@ describe "Tokenizer" do
result[1].should be_token("COMMENT", " this is a comment ")
end
it "tokenizes a block comment as 'COMMENT'" do
result = tokenize("foo {{!-- this is a {{comment}} --}} bar {{ baz }}")
result.should match_tokens(%w(CONTENT COMMENT CONTENT OPEN ID CLOSE))
result[1].should be_token("COMMENT", " this is a {{comment}} ")
end
it "tokenizes a block comment with whitespace as 'COMMENT'" do
result = tokenize("foo {{!-- this is a\n{{comment}}\n--}} bar {{ baz }}")
result.should match_tokens(%w(CONTENT COMMENT CONTENT OPEN ID CLOSE))
result[1].should be_token("COMMENT", " this is a\n{{comment}}\n")
end
it "tokenizes open and closing blocks as 'OPEN_BLOCK ID CLOSE ... OPEN_ENDBLOCK ID CLOSE'" do
result = tokenize("{{#foo}}content{{/foo}}")
result.should match_tokens(%w(OPEN_BLOCK ID CLOSE CONTENT OPEN_ENDBLOCK ID CLOSE))
@@ -195,6 +204,12 @@ describe "Tokenizer" do
result[3].should be_token("STRING", "baz")
end
it "tokenizes mustaches with String params using single quotes as 'OPEN ID ID STRING CLOSE'" do
result = tokenize("{{ foo bar \'baz\' }}")
result.should match_tokens(%w(OPEN ID ID STRING CLOSE))
result[3].should be_token("STRING", "baz")
end
it "tokenizes String params with spaces inside as 'STRING'" do
result = tokenize("{{ foo bar \"baz bat\" }}")
result.should match_tokens(%w(OPEN ID ID STRING CLOSE))
@@ -207,10 +222,20 @@ describe "Tokenizer" do
result[2].should be_token("STRING", %{bar"baz})
end
it "tokenizes String params using single quotes with escapes quotes as 'STRING'" do
result = tokenize(%|{{ foo 'bar\\'baz' }}|)
result.should match_tokens(%w(OPEN ID STRING CLOSE))
result[2].should be_token("STRING", %{bar'baz})
end
it "tokenizes numbers" do
result = tokenize(%|{{ foo 1 }}|)
result.should match_tokens(%w(OPEN ID INTEGER CLOSE))
result[2].should be_token("INTEGER", "1")
result = tokenize(%|{{ foo -1 }}|)
result.should match_tokens(%w(OPEN ID INTEGER CLOSE))
result[2].should be_token("INTEGER", "-1")
end
it "tokenizes booleans" do
+12 -6
View File
@@ -1,8 +1,9 @@
%x mu emu
%x mu emu com par
%%
"\\\\"/("{{") { yytext = "\\"; return 'CONTENT'; }
[^\x00]*?/("{{") {
if(yytext.slice(-1) !== "\\") this.begin("mu");
if(yytext.slice(-1) === "\\") yytext = yytext.substr(0,yyleng-1), this.begin("emu");
@@ -17,32 +18,37 @@
return 'CONTENT';
}
<mu>"{{>" { return 'OPEN_PARTIAL'; }
<com>[\s\S]*?"--}}" { yytext = yytext.substr(0, yyleng-4); this.popState(); return 'COMMENT'; }
<mu>"{{>" { this.begin("par"); 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>"{{!--" { this.popState(); this.begin('com'); }
<mu>"{{!"[\s\S]*?"}}" { yytext = yytext.substr(3,yyleng-5); this.popState(); return 'COMMENT'; }
<mu>"{{" { return 'OPEN'; }
<mu>"=" { return 'EQUALS'; }
<mu>"."/[} ] { return 'ID'; }
<mu>"."/[}/ ] { return 'ID'; }
<mu>".." { return 'ID'; }
<mu>[\/.] { return 'SEP'; }
<mu>\s+ { /*ignore whitespace*/ }
<mu>"}}}" { this.popState(); return 'CLOSE'; }
<mu>"}}" { this.popState(); return 'CLOSE'; }
<mu>'"'("\\"["]|[^"])*'"' { yytext = yytext.substr(1,yyleng-2).replace(/\\"/g,'"'); return 'STRING'; }
<mu>"'"("\\"[']|[^'])*"'" { yytext = yytext.substr(1,yyleng-2).replace(/\\"/g,'"'); return 'STRING'; }
<mu>"'"("\\"[']|[^'])*"'" { yytext = yytext.substr(1,yyleng-2).replace(/\\'/g,"'"); return 'STRING'; }
<mu>"@"[a-zA-Z]+ { yytext = yytext.substr(1); return 'DATA'; }
<mu>"true"/[}\s] { return 'BOOLEAN'; }
<mu>"false"/[}\s] { return 'BOOLEAN'; }
<mu>[0-9]+/[}\s] { return 'INTEGER'; }
<mu>[a-zA-Z0-9_$-]+/[=}\s\/.] { return 'ID'; }
<mu>\-?[0-9]+/[}\s] { return 'INTEGER'; }
<mu>[a-zA-Z0-9_$:\-]+/[=}\s\/.] { return 'ID'; }
<mu>'['[^\]]*']' { yytext = yytext.substr(1, yyleng-2); return 'ID'; }
<mu>. { return 'INVALID'; }
<par>\s+ { /*ignore whitespace*/ }
<par>[a-zA-Z0-9_$\-\/]+ { this.popState(); return 'PARTIAL_NAME'; }
<INITIAL,mu><<EOF>> { return 'EOF'; }
+10 -3
View File
@@ -7,8 +7,11 @@ root
;
program
: statements simpleInverse statements { $$ = new yy.ProgramNode($1, $3); }
: simpleInverse statements { $$ = new yy.ProgramNode([], $2); }
| statements simpleInverse statements { $$ = new yy.ProgramNode($1, $3); }
| statements simpleInverse { $$ = new yy.ProgramNode($1, []); }
| statements { $$ = new yy.ProgramNode($1); }
| simpleInverse { $$ = new yy.ProgramNode([], []); }
| "" { $$ = new yy.ProgramNode([]); }
;
@@ -45,8 +48,8 @@ mustache
partial
: OPEN_PARTIAL path CLOSE { $$ = new yy.PartialNode($2); }
| OPEN_PARTIAL path path CLOSE { $$ = new yy.PartialNode($2, $3); }
: OPEN_PARTIAL partialName CLOSE { $$ = new yy.PartialNode($2); }
| OPEN_PARTIAL partialName path CLOSE { $$ = new yy.PartialNode($2, $3); }
;
simpleInverse
@@ -91,6 +94,10 @@ hashSegment
| ID EQUALS DATA { $$ = [$1, new yy.DataNode($3)]; }
;
partialName
: PARTIAL_NAME { $$ = new yy.PartialNameNode($1); }
;
path
: pathSegments { $$ = new yy.IdNode($1); }
;
+1
View File
@@ -0,0 +1 @@
// BEGIN(BROWSER)
+4
View File
@@ -0,0 +1,4 @@
// END(BROWSER)
module.exports = handlebars;