Compare commits

..

170 Commits

Author SHA1 Message Date
Peter Wagenet bc8214cb7c Updated to 1.0.0-rc.3 2013-02-14 21:58:20 -08: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
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
Peter Wagenet 39832c0633 Fix handling of Errors in Chrome 2012-11-02 10:39:49 -07:00
Spike Brehm 2676efb222 overzealous approach to implmenting Handlebars.logger.log and 'log' helper. 2012-10-20 15:26:57 -07:00
Yehuda Katz a927a9b0ad Add block comment syntax:
{{!-- can contain
      {{handlebars expressions}} --}}
2012-10-15 13:51:53 -04:00
Yehuda Katz 8ed7a1e4e6 Merge pull request #339 from mikesherov/each
{{#each}} now supports objects [rebased against master]
2012-10-15 07:49:31 -07:00
Mike Petrovich d89e4b3d33 Added unit tests for #each with objects 2012-10-15 10:27:58 -04:00
Ross Hadden 9589ab8949 Implemented ability to iterate over objects, ala for-in.
Also added the 'key' key to looped objects.
My goal is to make this {{@key}}, but am still working on it.
I would also like to unobtrusively make @key or @index work for arrays.
2012-10-15 10:27:56 -04:00
Yehuda Katz 3212325173 Merge pull request #290 from fancyoung/master
Fix demo bug in README
2012-10-12 23:40:51 -07:00
Yehuda Katz 850304f43b Merge pull request #295 from leshill/escaped_quotes
Escaped quotes in hash arguments
2012-10-12 23:38:18 -07:00
Yehuda Katz ed245c6c64 Merge pull request #334 from anton-rudeshko/master
Added AMD option doc to README
2012-10-10 07:22:17 -07:00
Yehuda Katz 1018d9bfd1 Merge pull request #319 from stvp/master
Bind functions to the context.
2012-10-10 01:05:03 -07:00
Yehuda Katz a1bfc80577 Merge pull request #322 from scribu/patch-1
fix Block Helpers example
2012-10-10 01:04:30 -07:00
Anton Rudeshko 1b197be7fc Added AMD option doc 2012-10-07 06:20:06 -07:00
Cristi Burcă 9e582fa0eb fix Block Helpers example 2012-09-28 03:35:36 +03:00
Les Hill a1c9acb8b1 Escaped single quotes in hash arguments 2012-09-22 08:09:33 -07:00
Tyson Tate 42120d1177 Bind functions to the context properly.
The regression was introduced in 1.0.rc.1.

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

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

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

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

If the intention of the user is to not escape these characters, then
{{{}}} or {{&}} should be used
2012-02-29 11:25:51 -07:00
tomhuda 24e04bad94 JSHint the library and fix a few bugs 2012-02-17 16:51:35 -08:00
Peter Wagenet e6079ca5e2 Merge pull request #177 from kpdecker/master
Rev npm package to 1.0.5beta
2012-02-09 09:56:22 -08:00
31 changed files with 4458 additions and 716 deletions
+3 -1
View File
@@ -1,6 +1,8 @@
dist
vendor
.rvmrc
.DS_Store
lib/handlebars/compiler/parser.js
node_modules
*.sublime-project
*.sublime-workspace
npm-debug.log
+52
View File
@@ -0,0 +1,52 @@
{
"predef": [
"console",
"Ember",
"DS",
"Handlebars",
"Metamorph",
"ember_assert",
"ember_warn",
"ember_deprecate",
"ember_deprecateFunc",
"require",
"suite",
"equal",
"equals",
"test",
"testBoth",
"raises",
"deepEqual",
"start",
"stop",
"ok",
"strictEqual",
"module"
],
"node" : true,
"es5" : true,
"browser" : true,
"boss" : true,
"curly": false,
"debug": false,
"devel": false,
"eqeqeq": true,
"evil": true,
"forin": false,
"immed": false,
"laxbreak": false,
"newcap": true,
"noarg": true,
"noempty": false,
"nonew": false,
"nomen": false,
"onevar": false,
"plusplus": false,
"regexp": false,
"undef": true,
"sub": true,
"strict": false,
"white": false
}
-1
View File
@@ -5,7 +5,6 @@ Gemfile
Gemfile.lock
Rakefile
bench/*
dist/*
spec/*
src/*
vendor/*
+1
View File
@@ -0,0 +1 @@
-cfs
+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"
+11 -11
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.7.0)
rspec-core (~> 2.7.0)
rspec-expectations (~> 2.7.0)
rspec-mocks (~> 2.7.0)
rspec-core (2.7.1)
rspec-expectations (2.7.0)
diff-lcs (~> 1.1.2)
rspec-mocks (2.7.0)
therubyracer (0.9.9)
rake (10.0.3)
rspec (2.12.0)
rspec-core (~> 2.12.0)
rspec-expectations (~> 2.12.0)
rspec-mocks (~> 2.12.0)
rspec-core (2.12.2)
rspec-expectations (2.12.1)
diff-lcs (~> 1.1.3)
rspec-mocks (2.12.1)
therubyracer (0.10.2)
libv8 (~> 3.3.10)
PLATFORMS
@@ -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.
+9 -7
View File
@@ -19,7 +19,7 @@ In general, the syntax of Handlebars.js templates is a superset of Mustache temp
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);
@@ -112,7 +112,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 +124,7 @@ template(context);
// Would render:
//
// <ul>
// <li><a href='/hello-world'>Post!</a></li>
// <li><a href='/posts/hello-world'>Post!</a></li>
// </ul>
```
@@ -137,9 +137,9 @@ gets passed to the helper function.
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);
@@ -220,6 +220,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]
@@ -239,7 +240,7 @@ method and the resulting object may be as normal.
### Optimizations
- Rather than using the full _handlebars.js_ library, implementations that
do not need to compile templates at runtime may include _handlebars.vm.js_
do not need to compile templates at runtime may include _handlebars.runtime.js_
whose min+gzip size is approximately 1k.
- If a helper is known to exist in the target environment they may be defined
using the `--known name` argument may be used to optimize accesses to these
@@ -283,6 +284,7 @@ template(context, {helpers: helpers, partials: partials, data: data})
Known Issues
------------
* Handlebars.js can be cryptic when there's an error while rendering.
* Using a variable, helper, or partial named `class` causes errors in IE browsers. (Instead, use `className`)
Handlebars in the Wild
-----------------
+14 -21
View File
@@ -2,10 +2,10 @@ require "rubygems"
require "bundler/setup"
def compile_parser
system "jison 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 ENV['PATH'].split(':').any? {|folder| File.exists?(folder+'/jison')}
if File.exists?('./node_modules/jison')
compile_parser
else
puts "Jison is not installed. Trying `npm install jison`."
sh "npm install jison -g"
sh "npm install"
compile_parser
end
end
@@ -28,10 +28,17 @@ task :compile => "lib/handlebars/compiler/parser.js"
desc "run the spec suite"
task :spec => [:release] do
system "rspec -cfs spec"
rc = system "rspec -cfs spec"
fail "rspec spec failed with exit code #{$?.exitstatus}" if (rc.nil? || ! rc || $?.exitstatus != 0)
end
task :default => [:compile, :spec]
desc "run the npm test suite"
task :npm_test => [:release] do
rc = system "npm test"
fail "npm test failed with exit code #{$?.exitstatus}" if (rc.nil? || ! rc || $?.exitstatus != 0)
end
task :default => [:compile, :spec, :npm_test]
def remove_exports(string)
match = string.match(%r{^// BEGIN\(BROWSER\)\n(.*)\n^// END\(BROWSER\)}m)
@@ -54,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"
@@ -85,20 +92,6 @@ 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) {
+56 -2
View File
@@ -7,6 +7,23 @@ var optimist = require('optimist')
'description': 'Output File',
'alias': 'output'
},
'a': {
'type': 'boolean',
'description': 'Exports amd style (require.js)',
'alias': 'amd'
},
'c': {
'type': 'string',
'description': 'Exports CommonJS style, path to Handlebars module',
'alias': 'commonjs',
'default': null
},
'h': {
'type': 'string',
'description': 'Path to handlebar.js (only valid for amd-style)',
'alias': 'handlebarPath',
'default': ''
},
'k': {
'type': 'string',
'description': 'Known helpers',
@@ -22,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.',
@@ -31,6 +54,16 @@ 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'
}
})
@@ -78,7 +111,18 @@ if (argv.known) {
var output = [];
if (!argv.simple) {
output.push('(function() {\n var template = Handlebars.template, templates = Handlebars.templates = Handlebars.templates || {};\n');
if (argv.amd) {
output.push('define([\'' + argv.handlebarPath + 'handlebars\'], function(Handlebars) {\n');
} else if (argv.commonjs) {
output.push('var Handlebars = require("' + argv.commonjs + '");');
} else {
output.push('(function() {\n');
}
output.push(' var template = Handlebars.template, templates = ');
output.push(argv.namespace);
output.push(' = ');
output.push(argv.namespace);
output.push(' || {};\n');
}
function processTemplate(template, root) {
var path = template,
@@ -99,6 +143,10 @@ function processTemplate(template, root) {
knownHelpersOnly: argv.o
};
if (argv.data) {
options.data = true;
}
// Clean the template name
if (!root) {
template = basename(template);
@@ -109,6 +157,8 @@ function processTemplate(template, root) {
if (argv.simple) {
output.push(handlebars.precompile(data, options) + '\n');
} else if (argv.partial) {
output.push('Handlebars.partials[\'' + template + '\'] = template(' + handlebars.precompile(data, options) + ');\n');
} else {
output.push('templates[\'' + template + '\'] = template(' + handlebars.precompile(data, options) + ');\n');
}
@@ -121,7 +171,11 @@ argv._.forEach(function(template) {
// Output the content
if (!argv.simple) {
output.push('})();');
if (argv.amd) {
output.push('});');
} else if (!argv.commonjs) {
output.push('})();');
}
}
output = output.join('');
+2201
View File
File diff suppressed because it is too large Load Diff
+321
View File
@@ -0,0 +1,321 @@
/*
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/base.js
/*jshint eqnull:true*/
this.Handlebars = {};
(function(Handlebars) {
Handlebars.VERSION = "1.0.0-rc.3";
Handlebars.COMPILER_REVISION = 2;
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'
};
Handlebars.helpers = {};
Handlebars.partials = {};
Handlebars.registerHelper = function(name, fn, inverse) {
if(inverse) { fn.not = inverse; }
this.helpers[name] = fn;
};
Handlebars.registerPartial = function(name, str) {
this.partials[name] = str;
};
Handlebars.registerHelper('helperMissing', function(arg) {
if(arguments.length === 2) {
return undefined;
} else {
throw new Error("Could not find property '" + 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); }
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) {
var fn = options.fn, inverse = options.inverse;
options.fn = inverse;
options.inverse = fn;
return Handlebars.helpers['if'].call(this, context, options);
});
Handlebars.registerHelper('with', function(context, options) {
return options.fn(context);
});
Handlebars.registerHelper('log', function(context, options) {
var level = options.data && options.data.level != null ? parseInt(options.data.level, 10) : 1;
Handlebars.log(level, context);
});
}(this.Handlebars));
;
// 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();
};
(function() {
var escape = {
"&": "&amp;",
"<": "&lt;",
">": "&gt;",
'"': "&quot;",
"'": "&#x27;",
"`": "&#x60;"
};
var badChars = /[&<>"'`]/g;
var possible = /[&<>"'`]/;
var escapeChar = function(chr) {
return escape[chr] || "&amp;";
};
Handlebars.Utils = {
escapeExpression: function(string) {
// don't escape SafeStrings, since they're already safe
if (string instanceof Handlebars.SafeString) {
return string.toString();
} else if (string == null || string === false) {
return "";
}
if(!possible.test(string)) { return string; }
return string.replace(badChars, escapeChar);
},
isEmpty: function(value) {
if (!value && value !== 0) {
return true;
} else if(Object.prototype.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) {
return Handlebars.VM.program(fn, data);
} else if(programWrapper) {
return programWrapper;
} else {
programWrapper = this.programs[i] = Handlebars.VM.program(fn);
return programWrapper;
}
},
programWithDepth: Handlebars.VM.programWithDepth,
noop: Handlebars.VM.noop,
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(fn, data, $depth) {
var args = Array.prototype.slice.call(arguments, 2);
return function(context, options) {
options = options || {};
return fn.apply(this, [context, options.data || data].concat(args));
};
},
program: function(fn, data) {
return function(context, options) {
options = options || {};
return fn(context, options.data || data);
};
},
noop: function() { return ""; },
invokePartial: function(partial, name, context, helpers, partials, data) {
var options = { helpers: helpers, partials: partials, data: data };
if(partial === undefined) {
throw new Handlebars.Exception("The partial " + name + " could not be found");
} else if(partial instanceof Function) {
return partial(context, options);
} else if (!Handlebars.compile) {
throw new Handlebars.Exception("The partial " + name + " could not be compiled when running in runtime-only mode");
} else {
partials[name] = Handlebars.compile(partial, {data: data !== undefined});
return partials[name](context, options);
}
}
};
Handlebars.template = Handlebars.VM.template;
;
+70 -15
View File
@@ -1,7 +1,17 @@
// BEGIN(BROWSER)
var Handlebars = {};
Handlebars.VERSION = "1.0.beta.5";
/*jshint eqnull:true*/
this.Handlebars = {};
(function(Handlebars) {
Handlebars.VERSION = "1.0.0-rc.3";
Handlebars.COMPILER_REVISION = 2;
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'
};
Handlebars.helpers = {};
Handlebars.partials = {};
@@ -40,29 +50,71 @@ Handlebars.registerHelper('blockHelperMissing', function(context, options) {
return inverse(this);
} else if(type === "[object Array]") {
if(context.length > 0) {
for(var i=0, j=context.length; i<j; i++) {
ret = ret + fn(context[i]);
}
return Handlebars.helpers.each(context, options);
} else {
ret = inverse(this);
return inverse(this);
}
return ret;
} else {
return fn(context);
}
});
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 ret = "";
var i = 0, ret = "", data;
if(context && context.length > 0) {
for(var i=0, j=context.length; i<j; i++) {
ret = ret + fn(context[i]);
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++;
}
}
}
} else {
}
if(i === 0){
ret = inverse(this);
}
return ret;
});
@@ -89,11 +141,14 @@ Handlebars.registerHelper('with', function(context, options) {
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 = Handlebars;
module.exports = this.Handlebars;
+51 -21
View File
@@ -11,21 +11,32 @@ var Handlebars = require('./base');
if(inverse) { this.inverse = new Handlebars.AST.ProgramNode(inverse); }
};
Handlebars.AST.MustacheNode = function(params, hash, unescaped) {
Handlebars.AST.MustacheNode = function(rawParams, hash, unescaped) {
this.type = "mustache";
this.id = params[0];
this.params = params.slice(1);
this.hash = hash;
this.escaped = !unescaped;
this.hash = hash;
var id = this.id = rawParams[0];
var params = this.params = rawParams.slice(1);
// a mustache is an eligible helper if:
// * its id is simple (a single part, not `this` or `..`)
var eligibleHelper = this.eligibleHelper = id.isSimple;
// a mustache is definitely a helper if:
// * it is an eligible helper, and
// * it has at least one parameter or hash segment
this.isHelper = eligibleHelper && (params.length || hash);
// if a mustache is an eligible helper but not a definite
// helper, it is ambiguous, and will be resolved in a later
// pass or at runtime.
};
Handlebars.AST.PartialNode = function(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;
};
var verifyMatch = function(open, close) {
@@ -34,18 +45,16 @@ var Handlebars = require('./base');
}
};
Handlebars.AST.BlockNode = function(mustache, program, close) {
Handlebars.AST.BlockNode = function(mustache, program, inverse, close) {
verifyMatch(mustache.id, close);
this.type = "block";
this.mustache = mustache;
this.program = program;
};
this.inverse = inverse;
Handlebars.AST.InverseNode = function(mustache, program, close) {
verifyMatch(mustache.id, close);
this.type = "inverse";
this.mustache = mustache;
this.program = program;
if (this.inverse && !this.program) {
this.isInverse = true;
}
};
Handlebars.AST.ContentNode = function(string) {
@@ -67,30 +76,51 @@ var Handlebars = require('./base');
for(var i=0,l=parts.length; i<l; i++) {
var part = parts[i];
if(part === "..") { depth++; }
else if(part === "." || part === "this") { this.isScoped = true; }
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); }
}
this.parts = dig;
this.string = dig.join('.');
this.depth = depth;
this.isSimple = (dig.length === 1) && (depth === 0);
// an ID is simple if it only has one part, and that part is not
// `..` or `this`.
this.isSimple = parts.length === 1 && !this.isScoped && depth === 0;
this.stringModeValue = this.string;
};
Handlebars.AST.PartialNameNode = function(name) {
this.type = "PARTIAL_NAME";
this.name = name;
};
Handlebars.AST.DataNode = function(id) {
this.type = "DATA";
this.id = id;
};
Handlebars.AST.StringNode = function(string) {
this.type = "STRING";
this.string = string;
this.stringModeValue = string;
};
Handlebars.AST.IntegerNode = function(integer) {
this.type = "INTEGER";
this.integer = integer;
this.stringModeValue = Number(integer);
};
Handlebars.AST.BooleanNode = function(bool) {
this.type = "BOOLEAN";
this.bool = bool;
this.stringModeValue = bool === "true";
};
Handlebars.AST.CommentNode = function(comment) {
+7 -13
View File
@@ -1,27 +1,21 @@
var handlebars = require("./parser").parser;
var handlebars = require("./parser");
var Handlebars = require("../base");
// 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;
File diff suppressed because it is too large Load Diff
+24 -30
View File
@@ -18,31 +18,17 @@ Handlebars.PrintVisitor.prototype.pad = function(string, newline) {
};
Handlebars.PrintVisitor.prototype.program = function(program) {
var out = this.pad("PROGRAM:"),
var out = "",
statements = program.statements,
inverse = program.inverse,
i, l;
this.padding++;
for(i=0, l=statements.length; i<l; i++) {
out = out + this.accept(statements[i]);
}
this.padding--;
if(inverse) {
out = out + this.pad("{{^}}");
this.padding++;
for(i=0, l=inverse.statements.length; i<l; i++) {
out = out + this.accept(inverse.statements[i]);
}
}
this.padding--;
return out;
};
@@ -52,25 +38,25 @@ Handlebars.PrintVisitor.prototype.block = function(block) {
out = out + this.pad("BLOCK:");
this.padding++;
out = out + this.accept(block.mustache);
out = out + this.accept(block.program);
if (block.program) {
out = out + this.pad("PROGRAM:");
this.padding++;
out = out + this.accept(block.program);
this.padding--;
}
if (block.inverse) {
if (block.program) { this.padding++; }
out = out + this.pad("{{^}}");
this.padding++;
out = out + this.accept(block.inverse);
this.padding--;
if (block.program) { this.padding--; }
}
this.padding--;
return out;
};
Handlebars.PrintVisitor.prototype.inverse = function(block) {
var out = "";
out = out + this.pad("INVERSE:");
this.padding++;
out = out + this.accept(block.mustache);
out = out + this.accept(block.program);
this.padding--;
return out;
};
Handlebars.PrintVisitor.prototype.mustache = function(mustache) {
var params = mustache.params, paramStrings = [], hash;
@@ -86,7 +72,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 + " }}");
};
@@ -125,6 +111,14 @@ Handlebars.PrintVisitor.prototype.ID = function(id) {
}
};
Handlebars.PrintVisitor.prototype.PARTIAL_NAME = function(partialName) {
return "PARTIAL:" + partialName.name;
};
Handlebars.PrintVisitor.prototype.DATA = function(data) {
return "@" + data.id;
};
Handlebars.PrintVisitor.prototype.content = function(content) {
return this.pad("CONTENT[ '" + content.string + "' ]");
};
+23 -3
View File
@@ -20,12 +20,32 @@ Handlebars.VM = {
}
},
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;
};
},
@@ -56,7 +76,7 @@ Handlebars.VM = {
} 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);
partials[name] = Handlebars.compile(partial, {data: data !== undefined});
return partials[name](context, options);
}
}
+9 -10
View File
@@ -1,14 +1,16 @@
var Handlebars = require("./base");
// 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();
@@ -22,6 +24,7 @@ Handlebars.SafeString.prototype.toString = function() {
(function() {
var escape = {
"&": "&amp;",
"<": "&lt;",
">": "&gt;",
'"': "&quot;",
@@ -29,7 +32,7 @@ Handlebars.SafeString.prototype.toString = function() {
"`": "&#x60;"
};
var badChars = /&(?!\w+;)|[<>"'`]/g;
var badChars = /[&<>"'`]/g;
var possible = /[&<>"'`]/;
var escapeChar = function(chr) {
@@ -50,11 +53,7 @@ Handlebars.SafeString.prototype.toString = function() {
},
isEmpty: function(value) {
if (typeof value === "undefined") {
return true;
} else if (value === null) {
return true;
} else if (value === false) {
if (!value && value !== 0) {
return true;
} else if(Object.prototype.toString.call(value) === "[object Array]" && value.length === 0) {
return true;
+15 -5
View File
@@ -1,14 +1,14 @@
{
"name": "handlebars",
"description": "Extension of the Mustache logicless template language",
"version": "1.0.5beta",
"version": "1.0.8",
"homepage": "http://www.handlebarsjs.com/",
"keywords": [
"handlebars mustache template html"
],
"repository": {
"type": "git",
"url": "git://github.com/kpdecker/handlebars.js.git"
"url": "git://github.com/wycats/handlebars.js.git"
},
"engines": {
"node": ">=0.4.7"
@@ -17,9 +17,19 @@
"optimist": "~0.3",
"uglify-js": "~1.2"
},
"devDependencies": {},
"devDependencies": {
"benchmark": "~1.0",
"dust": "~0.3",
"jison": "~0.3",
"mocha": "*",
"mustache": "~0.7.2"
},
"main": "lib/handlebars.js",
"bin": {
"handlebars": "bin/handlebars"
}
}
},
"scripts": {
"test": "node_modules/.bin/mocha -u qunit spec/qunit_spec.js"
},
"optionalDependencies": {}
}
+5 -5
View File
@@ -39,11 +39,11 @@ Module.new do
end
end
js_context["p"] = proc do |str|
js_context["p"] = proc do |this, str|
p str
end
js_context["ok"] = proc do |ok, message|
js_context["ok"] = proc do |this, ok, message|
js_context["$$RSPEC1$$"] = ok
result = js_context.eval("!!$$RSPEC1$$")
@@ -58,7 +58,7 @@ Module.new do
assert result, message
end
js_context["equals"] = proc do |first, second, message|
js_context["equals"] = proc do |this, first, second, message|
js_context["$$RSPEC1$$"] = first
js_context["$$RSPEC2$$"] = second
@@ -77,11 +77,11 @@ Module.new do
js_context["equal"] = js_context["equals"]
js_context["module"] = proc do |name|
js_context["suite"] = proc do |this, name|
test_context.module(name)
end
js_context["test"] = proc do |name, function|
js_context["test"] = proc do |this, name, function|
test_context.test(name, function)
end
+201 -32
View File
@@ -7,11 +7,9 @@ describe "Parser" do
@compiles = true
end
def program(&block)
def root(&block)
ASTBuilder.build do
program do
instance_eval(&block)
end
instance_eval(&block)
end
end
@@ -112,115 +110,143 @@ describe "Parser" do
"ID:#{id}"
end
def data(id)
"@#{id}"
end
def partial_name(name)
"PARTIAL:#{name}"
end
def path(*parts)
"PATH:#{parts.join("/")}"
end
end
it "parses simple mustaches" do
ast_for("{{foo}}").should == program { mustache id("foo") }
ast_for("{{foo}}").should == root { mustache id("foo") }
end
it "parses simple mustaches with data" do
ast_for("{{@foo}}").should == root { mustache data("foo") }
end
it "parses mustaches with paths" do
ast_for("{{foo/bar}}").should == program { mustache path("foo", "bar") }
ast_for("{{foo/bar}}").should == root { mustache path("foo", "bar") }
end
it "parses mustaches with this/foo" do
ast_for("{{this/foo}}").should == program { mustache id("foo") }
ast_for("{{this/foo}}").should == root { mustache id("foo") }
end
it "parses mustaches with - in a path" do
ast_for("{{foo-bar}}").should == program { mustache id("foo-bar") }
ast_for("{{foo-bar}}").should == root { mustache id("foo-bar") }
end
it "parses mustaches with parameters" do
ast_for("{{foo bar}}").should == program { mustache id("foo"), [id("bar")] }
ast_for("{{foo bar}}").should == root { mustache id("foo"), [id("bar")] }
end
it "parses mustaches with hash arguments" do
ast_for("{{foo bar=baz}}").should == program do
ast_for("{{foo bar=baz}}").should == root do
mustache id("foo"), [], hash(["bar", id("baz")])
end
ast_for("{{foo bar=1}}").should == program do
ast_for("{{foo bar=1}}").should == root do
mustache id("foo"), [], hash(["bar", integer("1")])
end
ast_for("{{foo bar=true}}").should == program do
ast_for("{{foo bar=true}}").should == root do
mustache id("foo"), [], hash(["bar", boolean("true")])
end
ast_for("{{foo bar=false}}").should == program do
ast_for("{{foo bar=false}}").should == root do
mustache id("foo"), [], hash(["bar", boolean("false")])
end
ast_for("{{foo bar=baz bat=bam}}").should == program do
ast_for("{{foo bar=@baz}}").should == root do
mustache id("foo"), [], hash(["bar", data("baz")])
end
ast_for("{{foo bar=baz bat=bam}}").should == root do
mustache id("foo"), [], hash(["bar", "ID:baz"], ["bat", "ID:bam"])
end
ast_for("{{foo bar=baz bat=\"bam\"}}").should == program do
ast_for("{{foo bar=baz bat=\"bam\"}}").should == root do
mustache id("foo"), [], hash(["bar", "ID:baz"], ["bat", "\"bam\""])
end
ast_for("{{foo omg bar=baz bat=\"bam\"}}").should == program do
ast_for("{{foo bat='bam'}}").should == root do
mustache id("foo"), [], hash(["bat", "\"bam\""])
end
ast_for("{{foo omg bar=baz bat=\"bam\"}}").should == root do
mustache id("foo"), [id("omg")], hash(["bar", id("baz")], ["bat", string("bam")])
end
ast_for("{{foo omg bar=baz bat=\"bam\" baz=1}}").should == program do
ast_for("{{foo omg bar=baz bat=\"bam\" baz=1}}").should == root do
mustache id("foo"), [id("omg")], hash(["bar", id("baz")], ["bat", string("bam")], ["baz", integer("1")])
end
ast_for("{{foo omg bar=baz bat=\"bam\" baz=true}}").should == program do
ast_for("{{foo omg bar=baz bat=\"bam\" baz=true}}").should == root do
mustache id("foo"), [id("omg")], hash(["bar", id("baz")], ["bat", string("bam")], ["baz", boolean("true")])
end
ast_for("{{foo omg bar=baz bat=\"bam\" baz=false}}").should == program do
ast_for("{{foo omg bar=baz bat=\"bam\" baz=false}}").should == root do
mustache id("foo"), [id("omg")], hash(["bar", id("baz")], ["bat", string("bam")], ["baz", boolean("false")])
end
end
it "parses mustaches with string parameters" do
ast_for("{{foo bar \"baz\" }}").should == program { mustache id("foo"), [id("bar"), string("baz")] }
ast_for("{{foo bar \"baz\" }}").should == root { mustache id("foo"), [id("bar"), string("baz")] }
end
it "parses mustaches with INTEGER parameters" do
ast_for("{{foo 1}}").should == program { mustache id("foo"), [integer("1")] }
ast_for("{{foo 1}}").should == root { mustache id("foo"), [integer("1")] }
end
it "parses mustaches with BOOLEAN parameters" do
ast_for("{{foo true}}").should == program { mustache id("foo"), [boolean("true")] }
ast_for("{{foo false}}").should == program { mustache id("foo"), [boolean("false")] }
ast_for("{{foo true}}").should == root { mustache id("foo"), [boolean("true")] }
ast_for("{{foo false}}").should == root { mustache id("foo"), [boolean("false")] }
end
it "parses mutaches with DATA parameters" do
ast_for("{{foo @bar}}").should == root { mustache id("foo"), [data("bar")] }
end
it "parses contents followed by a mustache" do
ast_for("foo bar {{baz}}").should == program do
ast_for("foo bar {{baz}}").should == root do
content "foo bar "
mustache id("baz")
end
end
it "parses a partial" do
ast_for("{{> foo }}").should == program { partial id("foo") }
ast_for("{{> foo }}").should == root { partial partial_name("foo") }
end
it "parses a partial with context" do
ast_for("{{> foo bar}}").should == program { partial id("foo"), id("bar") }
ast_for("{{> foo bar}}").should == root { partial partial_name("foo"), id("bar") }
end
it "parses a partial with a complex name" do
ast_for("{{> shared/partial}}").should == root { partial partial_name("shared/partial") }
end
it "parses a comment" do
ast_for("{{! this is a comment }}").should == program do
ast_for("{{! this is a comment }}").should == root do
comment " this is a comment "
end
end
it "parses a multi-line comment" do
ast_for("{{!\nthis is a multi-line comment\n}}").should == program do
ast_for("{{!\nthis is a multi-line comment\n}}").should == root do
multiline_comment "this is a multi-line comment"
end
end
it "parses an inverse section" do
ast_for("{{#foo}} bar {{^}} baz {{/foo}}").should == program do
ast_for("{{#foo}} bar {{^}} baz {{/foo}}").should == root do
block do
mustache id("foo")
@@ -235,12 +261,136 @@ describe "Parser" do
end
end
it "parses a standalone inverse section" do
ast_for("{{^foo}}bar{{/foo}}").should == program do
inverted_block do
it "parses an inverse ('else'-style) section" do
ast_for("{{#foo}} bar {{else}} baz {{/foo}}").should == root do
block do
mustache id("foo")
program do
content " bar "
end
inverse do
content " baz "
end
end
end
end
it "parses empty blocks" do
ast_for("{{#foo}}{{/foo}}").should == root do
block do
mustache id("foo")
program do
# empty program
end
end
end
end
it "parses empty blocks with empty inverse section" do
ast_for("{{#foo}}{{^}}{{/foo}}").should == root do
block do
mustache id("foo")
program do
# empty program
end
inverse do
# empty inverse
end
end
end
end
it "parses empty blocks with empty inverse ('else'-style) section" do
ast_for("{{#foo}}{{else}}{{/foo}}").should == root do
block do
mustache id("foo")
program do
# empty program
end
inverse do
# empty inverse
end
end
end
end
it "parses non-empty blocks with empty inverse section" do
ast_for("{{#foo}} bar {{^}}{{/foo}}").should == root do
block do
mustache id("foo")
program do
content " bar "
end
inverse do
# empty inverse
end
end
end
end
it "parses non-empty blocks with empty inverse ('else'-style) section" do
ast_for("{{#foo}} bar {{else}}{{/foo}}").should == root do
block do
mustache id("foo")
program do
content " bar "
end
inverse do
# empty inverse
end
end
end
end
it "parses empty blocks with non-empty inverse section" do
ast_for("{{#foo}}{{^}} bar {{/foo}}").should == root do
block do
mustache id("foo")
program do
# empty program
end
inverse do
content " bar "
end
end
end
end
it "parses empty blocks with non-empty inverse ('else'-style) section" do
ast_for("{{#foo}}{{else}} bar {{/foo}}").should == root do
block do
mustache id("foo")
program do
# empty program
end
inverse do
content " bar "
end
end
end
end
it "parses a standalone inverse section" do
ast_for("{{^foo}}bar{{/foo}}").should == root do
block do
mustache id("foo")
inverse do
content "bar"
end
end
@@ -261,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::FULL_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
+464 -161
View File
File diff suppressed because it is too large Load Diff
+7 -7
View File
@@ -47,15 +47,15 @@ module Handlebars
def self.load_helpers(context)
context["exports"] = nil
context["p"] = proc do |val|
context["p"] = proc do |this, val|
p val if ENV["DEBUG_JS"]
end
context["puts"] = proc do |val|
context["puts"] = proc do |this, val|
puts val if ENV["DEBUG_JS"]
end
context["puts_node"] = proc do |val|
context["puts_node"] = proc do |this, val|
puts context["Handlebars"]["PrintVisitor"].new.accept(val)
puts
end
@@ -82,12 +82,12 @@ module Handlebars
context["CompilerContext"] = {}
CompilerContext = context["CompilerContext"]
CompilerContext["compile"] = proc do |*args|
CompilerContext["compile"] = proc do |this, *args|
template, options = args[0], args[1] || nil
templateSpec = COMPILE_CONTEXT["Handlebars"]["precompile"].call(template, options);
context["Handlebars"]["template"].call(context.eval("(#{templateSpec})"));
end
CompilerContext["compileWithPartial"] = proc do |*args|
CompilerContext["compileWithPartial"] = proc do |this, *args|
template, options = args[0], args[1] || nil
FULL_CONTEXT["Handlebars"]["compile"].call(template, options);
end
@@ -108,7 +108,7 @@ module Handlebars
context["Handlebars"]["logger"]["level"] = ENV["DEBUG_JS"] ? context["Handlebars"]["logger"][ENV["DEBUG_JS"]] : 4
context["Handlebars"]["logger"]["log"] = proc do |level, str|
context["Handlebars"]["logger"]["log"] = proc do |this, level, str|
logger_level = context["Handlebars"]["logger"]["level"].to_i
if logger_level <= level
@@ -133,7 +133,7 @@ module Handlebars
context["Handlebars"]["logger"]["level"] = ENV["DEBUG_JS"] ? context["Handlebars"]["logger"][ENV["DEBUG_JS"]] : 4
context["Handlebars"]["logger"]["log"] = proc do |level, str|
context["Handlebars"]["logger"]["log"] = proc do |this, level, str|
logger_level = context["Handlebars"]["logger"]["level"].to_i
if logger_level <= level
+55 -8
View File
@@ -51,6 +51,15 @@ describe "Tokenizer" do
result[4].should be_token("CONTENT", "{{bar}} ")
end
it "supports escaping multiple delimiters" do
result = tokenize("{{foo}} \\{{bar}} \\{{baz}}")
result.should match_tokens(%w(OPEN ID CLOSE CONTENT CONTENT CONTENT))
result[3].should be_token("CONTENT", " ")
result[4].should be_token("CONTENT", "{{bar}} ")
result[5].should be_token("CONTENT", "{{baz}}")
end
it "supports escaping a triple stash" do
result = tokenize("{{foo}} \\{{{bar}}} {{baz}}")
result.should match_tokens(%w(OPEN ID CLOSE CONTENT CONTENT OPEN ID CLOSE))
@@ -123,24 +132,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
@@ -149,6 +158,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))
@@ -186,6 +207,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))
@@ -198,6 +225,12 @@ 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))
@@ -244,6 +277,20 @@ describe "Tokenizer" do
result[2].should be_token("ID", "omg")
end
it "tokenizes special @ identifiers" do
result = tokenize("{{ @foo }}")
result.should match_tokens %w( OPEN DATA CLOSE )
result[1].should be_token("DATA", "foo")
result = tokenize("{{ foo @bar }}")
result.should match_tokens %w( OPEN ID DATA CLOSE )
result[2].should be_token("DATA", "bar")
result = tokenize("{{ foo bar=@baz }}")
result.should match_tokens %w( OPEN ID ID EQUALS DATA CLOSE )
result[4].should be_token("DATA", "baz")
end
it "does not time out in a mustache with a single } followed by EOF" do
Timeout.timeout(1) { tokenize("{{foo}").should match_tokens(%w(OPEN ID)) }
end
+14 -3
View File
@@ -1,5 +1,5 @@
%x mu emu
%x mu emu com par
%%
@@ -11,15 +11,22 @@
[^\x00]+ { return 'CONTENT'; }
<emu>[^\x00]{2,}?/("{{") { this.popState(); return 'CONTENT'; }
<emu>[^\x00]{2,}?/("{{"|<<EOF>>) {
if(yytext.slice(-1) !== "\\") this.popState();
if(yytext.slice(-1) === "\\") yytext = yytext.substr(0,yyleng-1);
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'; }
@@ -31,12 +38,16 @@
<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>"@"[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>'['[^\]]*']' { 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'; }
+15 -5
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([]); }
;
@@ -18,8 +21,8 @@ statements
;
statement
: openInverse program closeBlock { $$ = new yy.InverseNode($1, $2, $3); }
| openBlock program closeBlock { $$ = new yy.BlockNode($1, $2, $3); }
: openInverse program closeBlock { $$ = new yy.BlockNode($1, $2.inverse, $2, $3); }
| openBlock program closeBlock { $$ = new yy.BlockNode($1, $2, $2.inverse, $3); }
| mustache { $$ = $1; }
| partial { $$ = $1; }
| CONTENT { $$ = new yy.ContentNode($1); }
@@ -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
@@ -58,6 +61,7 @@ inMustache
| path params { $$ = [[$1].concat($2), null]; }
| path hash { $$ = [[$1], $2]; }
| path { $$ = [[$1], null]; }
| DATA { $$ = [[new yy.DataNode($1)], null]; }
;
params
@@ -70,6 +74,7 @@ param
| STRING { $$ = new yy.StringNode($1); }
| INTEGER { $$ = new yy.IntegerNode($1); }
| BOOLEAN { $$ = new yy.BooleanNode($1); }
| DATA { $$ = new yy.DataNode($1); }
;
hash
@@ -86,6 +91,11 @@ hashSegment
| ID EQUALS STRING { $$ = [$1, new yy.StringNode($3)]; }
| ID EQUALS INTEGER { $$ = [$1, new yy.IntegerNode($3)]; }
| ID EQUALS BOOLEAN { $$ = [$1, new yy.BooleanNode($3)]; }
| ID EQUALS DATA { $$ = [$1, new yy.DataNode($3)]; }
;
partialName
: PARTIAL_NAME { $$ = new yy.PartialNameNode($1); }
;
path
+1
View File
@@ -0,0 +1 @@
// BEGIN(BROWSER)
+4
View File
@@ -0,0 +1,4 @@
// END(BROWSER)
module.exports = handlebars;