Compare commits

...

1613 Commits

Author SHA1 Message Date
Jakob Linskeseder ca0d385a6c Coerce noEscape-statements to a string
Also removed `chai-diff`-dependency, since
it is unmaintained and not that useful.

Fixes #1838
2023-08-29 23:45:05 +02:00
Jakob Linskeseder 520e1d5f08 Fix links in README
This commit backports changes from `master`.
2023-08-02 23:37:05 +02:00
Jakob Linskeseder 02423780a9 Fix link to homepage
When visiting the www-variant, a `SSL_ERROR_INTERNAL_ERROR_ALERT`
error is displayed by the browser.
2023-08-02 23:35:42 +02:00
Jakob Linskeseder be92d2f254 Backport: publish latest and tags to AWS S3
In order to automate publishing 4.x releases to AWS S3,
we also need this workflow on the 4.x branch.

See #1972
2023-08-02 23:09:34 +02:00
Jakob Linskeseder 443a613b3a Add guideline for pushing to RubyGems 2023-08-02 22:56:15 +02:00
Nils Knappmeier 83ee5908f2 add links to publish requirements 2023-08-01 23:24:13 +02:00
Jakob Linskeseder 8dc3d2517b v4.7.8 2023-08-01 23:06:18 +02:00
Jakob Linskeseder 668c4fb878 Fix browser tests in CI pipeline 2023-07-23 20:46:46 +02:00
Jakob Linskeseder c65c6cce3f Test on Node 18
* Replaced outdated `grunt-bg-shell`-package to get rid of
  coffee-script warnings

Backports parts of 785a63e0a8.
2022-10-21 16:48:14 +02:00
Marnix Kok 3d3796c1e9 Make library compatible with workers
When using Handlebars in a Cloudflare Worker, an environment
in which the `window` and `global` objects both don't exist,
an error is thrown about `window` being undefined.

According to the ECMA specification, only `self` is available
in a worker. Since we support old runtimes in our 4.x branch,
we can't use `globalThis` but have to use a polyfill.
2022-10-16 22:17:50 +02:00
Jakob Linskeseder 075b354a3b Fix sync issue with npm lock-file
```
npm ERR! `npm ci` can only install packages when your package.json and package-lock.json or npm-shrinkwrap.json are in sync. Please update your lock file with `npm install` before continuing.
npm ERR!
npm ERR! Invalid: lock file's acorn@5.7.4 does not satisfy acorn@7.4.1
npm ERR! Missing: acorn@5.7.4 from lock file
```
2022-10-16 21:55:05 +02:00
arturmakaranka 30dbf04781 Fix compiling of each block params in strict mode
Fixes #1820
Closes #1821
2022-05-25 22:18:36 +02:00
Jakob Linskeseder e3a54485db Fix bundler issue with webpack 5
As explained in issue #1844 and in issue
https://github.com/webpack/webpack/issues/15007#issuecomment-996615250,
the way we used the `browser`-field was wrong.

The main reason for using the `browser`-field is the requirement of
`require('fs')` in the main-entry-file.
The workaround for this was using `require('handlebars/lib/handlebars')`,
but now it will also work via `require('handlebars')` for bundlers that
respect the `browser`-field.

The `"./runtime"`-config was removed, because it didn't have any effect.
In order to detect regressions, the webpack-integration test was
extended to test with different webpack versions.

Fixes #1174
Closes #1844
2022-05-17 21:19:48 +02:00
Jakob Linskeseder 8e23642ea2 Fix integration-tests issue with npm >= 7
Fixes the following error when running integration-tests:
```
npm ERR! code ERESOLVE
npm ERR! ERESOLVE unable to resolve dependency tree
npm ERR!
npm ERR! While resolving: webpack-test@1.0.0
npm ERR! Found: handlebars@5.0.0-alpha.1
npm ERR! node_modules/handlebars
npm ERR!   dev handlebars@"file:../../.." from the root project
npm ERR!
npm ERR! Could not resolve dependency:
npm ERR! peer handlebars@">= 1.3.0 < 5" from handlebars-loader@1.7.1
npm ERR! node_modules/handlebars-loader
npm ERR!   dev handlebars-loader@"^1.7.1" from the root project
npm ERR!
```
2022-05-17 21:19:48 +02:00
James Wilson 88ac06875f use https instead of git for mustache submodule 2022-03-19 19:19:27 +01:00
Jakob Linskeseder c68bc08a0d Fix typo 2022-01-08 00:39:29 +01:00
Nils Knappmeier 6cfbc2653a Use eslint-env "shared-node-browser" for shared handlebars code 2022-01-07 18:15:48 +01:00
Jakob Linskeseder b65135acef Replace shields.io with faster badgen.net
Badgen is a faster successor of shields.io:
https://github.com/badgen/badgen.net

Backport from `master`-branch.
2021-12-30 00:58:48 +01:00
Jakob Linskeseder e2f63da5c0 Don't create security alerts for dev-dependencies 2021-12-29 22:48:01 +01:00
Jakob Linskeseder 78e7e28ff9 Replace custom test-server with Grunt connect 2021-12-29 21:35:16 +01:00
Jakob Linskeseder 03d387bf8e Fix rollup warning when importing Handlebars as ESM
Closes #1696
2021-12-29 21:35:16 +01:00
Jakob Linskeseder e0f50b4eec Add integration-tests to CI workflow 2021-12-29 21:35:16 +01:00
Jakob Linskeseder 9ed9418488 Replace Saucelabs with Playwright
Also reorganized npm scripts.
2021-12-29 21:35:16 +01:00
Jakob Linskeseder ef0fc290b9 Move print-script.js-file in tests-folder
Also removed it from published files, since the script doesn't seem to be used.

Fourth part of reorganizing and cleaning up test-folders.
2021-12-29 21:35:16 +01:00
Jakob Linskeseder edc65b5c19 Move integration-testing-folder in tests-folder
Third part of reorganizing and cleaning up test-folders.
2021-12-29 21:35:16 +01:00
Jakob Linskeseder 715f4af179 Remove redundant task-part in task-tests-folder
Second part of reorganizing and cleaning up test-folders.
2021-12-29 21:35:16 +01:00
Jakob Linskeseder 3bd0fa8b32 Move bench-folder in tests-folder
First part of reorganizing and cleaning up test-folders.
2021-12-29 21:35:16 +01:00
Jakob Linskeseder c295ef085f Update repository URL 2021-12-03 23:12:20 +01:00
Jakob Linskeseder c1ad3c8057 Update contributing guidelines 2021-12-03 23:12:20 +01:00
Jakob Linskeseder af92e32822 Run CI tests on more versions 2021-12-03 23:12:20 +01:00
Jakob Linskeseder 2954e7ea66 Don't rely on Node.js global object
If `global` is used and handlebars is compiled for browser
usage without a Node.js `global` polyfill, handlebars
fails with a `global is undefined` error.

Fixes #1593
2021-12-03 21:57:55 +01:00
Jay Linski 8eefee56ff Replace Travis CI with GitHub action (#1772) 2021-12-03 19:04:01 +01:00
Shon Frazier fd93073146 Fix Mustache links
...to point to 'https://mustache.github.io/...'
2021-11-23 23:58:57 +01:00
Nils Knappmeier a9a8e40321 v4.7.7 2021-02-15 10:36:07 +01:00
Nils Knappmeier e66aed5b99 Update release notes 2021-02-15 10:35:33 +01:00
Nils Knappmeier 7d4d170ce4 disable IE in Saucelabs tests
The SauceLabs IE setup has problems and does not work
with the endpoints that we use to run them. This is a Saucelabs problem, but it has been there for a year now. Until we use another API, I will simply remove the browsers
2021-02-15 09:59:41 +01:00
Nils Knappmeier eb860c0899 fix weird error in integration tests
somehow the variable "$i" gets overwritten in integration-testing/multi-nodejs-test/test.sh:20, which is why we now rename it to a more meaningful variable
2021-02-15 09:50:16 +01:00
Nils Knappmeier b6d3de7123 fix: check prototype property access in strict-mode (#1736) 2021-02-13 13:41:40 +01:00
Nils Knappmeier f058970169 fix: escape property names in compat mode (#1736) 2021-02-13 13:40:53 +01:00
John Boehr 77825f8d35 refator: In spec tests, use expectTemplate over equals and shouldThrow (#1683) 2020-05-05 00:04:33 +02:00
Hannah Wolfe 3789a30955 chore: start testing on Node.js 12 and 13
- make sure we're aware of all compatibility issues
2020-04-03 20:05:19 +01:00
Hannah Wolfe e6ad93ea01 v4.7.6 2020-04-03 18:56:22 +01:00
Hannah Wolfe 2bf4fc6fd3 Update release notes 2020-04-03 18:55:39 +01:00
Hannah Wolfe b64202bc91 Update release-notes.md
- indicate changes have been reverted
2020-04-03 18:47:56 +01:00
Douglas Christopher Wilson c2f1e62031 Switch cmd parser to latest minimist 2020-04-03 18:44:30 +01:00
Hannah Wolfe 08e9a11a34 Revert "chore: set Node.js compatibility to v6+"
This reverts commit 309d2b49a1.
2020-04-03 18:44:30 +01:00
Hannah Wolfe 1fd2edee2a v4.7.5 2020-04-02 20:09:49 +01:00
Hannah Wolfe 3c9c2f5cf2 Update release notes 2020-04-02 20:09:16 +01:00
Hannah Wolfe 16487a088e chore: downgrade yargs to v14
- allows us to support Node v6 and v8 for now
2020-04-02 19:28:36 +01:00
Hannah Wolfe 309d2b49a1 chore: set Node.js compatibility to v6+
- drop support for versions less than Node v6 via engines
- update integration tests to also test newer versions
2020-04-02 19:28:35 +01:00
Nils Knappmeier 645ac73844 test: fix integration tests
- They should fail, if any test fails
- Use "set -e" instead of "|| exit 1", because it suffices to be specified
  at the top of each file
2020-04-02 19:25:37 +01:00
Nils Knappmeier b454b02147 docs: update release-docs in CONTRIBUTING.md
- remove ember testing
- add docs for updating the website.
- add list of required accesses to publish
2020-04-02 14:10:47 +02:00
Hannah Wolfe 7adc19ab40 v4.7.4 2020-04-01 18:20:30 +01:00
Hannah Wolfe 9dd8d10e12 Update release notes 2020-04-01 18:19:34 +01:00
Hannah Wolfe 4671c4b383 Use tmp directory for files written during tests
- Add spec/tmp directory with .gitkeep file to indicate the folder is intentional
- Add the folder contents to .gitignore
- Use this folder to output the sourcemap file during bin tests. This file is a sideeffect of the main test
2020-04-01 18:04:14 +01:00
DeeDeeG e46baa1fe2 tasks/test-bin.js: Delete duplicate test
Per comment by @ErisDS on the GitHub PR:
https://github.com/wycats/handlebars.js/pull/1666#issuecomment-606805248
2020-04-01 17:25:35 +01:00
Hannah Wolfe c491b4ea25 Revert "Update release-notes.md"
This reverts commit 738391a064.
Think this needs to be left to the release tool :|
2020-04-01 17:21:20 +01:00
Hannah Wolfe 738391a064 Update release-notes.md 2020-03-31 17:05:33 +01:00
Aori Nevo 80c4516fda chore: add unit tests for cli options (#1666)
- adds full unit tests for all cli options
- demonstrates that nothing changes between minimist and yargs except a minor order change in the --help text
- proves b9c4b253e works the same as before

Co-authored-by: fabb <fabb@users.noreply.github.com>
2020-03-31 16:54:11 +01:00
Avi Vahl d79212a662 fix: migrate from optimist to yargs (#1666)
closes #1658

- adapted code from master to latest yargs (`.option` calls).

```
4.x:
found 188 vulnerabilities (169 low, 4 moderate, 14 high, 1 critical) in 5815 scanned packages

4.x with this PR:
found 32 vulnerabilities (17 low, 1 moderate, 13 high, 1 critical) in 5829 scanned packages
```
2020-03-31 16:54:11 +01:00
Avi Vahl b440c38886 chore: ignore external @types in tests
- some indirect dependencies install @types packages which are not compatible with the older typescript.
- adjusted test's tsconfig to not pick these up automatically, as the actual .d.ts does not depend on these external types.
2020-03-31 16:53:54 +01:00
Eli Skeggs 2dba7eee3f docs: fix comparison link 2020-03-05 15:10:10 +01:00
Nils Knappmeier c9789691af v4.7.3 2020-02-05 06:10:33 +01:00
Nils Knappmeier 9278f217e0 Update release notes 2020-02-05 06:10:09 +01:00
roydukkey d78cc73d3c Fixes spelling and punctuation
(cherry picked from commit fd3ca85d13)
2020-02-05 06:06:42 +01:00
ismailjones 4de51fe26b Add Type Definition for Handlebars.VERSION, Fixes #1647 2020-02-04 23:32:10 +01:00
papasmile a32d05f2fc Include Type Definition for runtime.js in Package 2020-02-04 21:58:49 +01:00
Nils Knappmeier ad63f5189f chore: add missing "await" in aws-s3 publishing code
closes #1644
2020-01-21 21:52:23 +01:00
Nils Knappmeier 586e672c8b v4.7.2 2020-01-13 21:53:14 +01:00
Nils Knappmeier f0c6c4cc1f Update release notes 2020-01-13 21:52:50 +01:00
Nils Knappmeier a4fd391ba1 chore: execute saucelabs-task only if access-key exists
- up to now, the existance of the SAUCE_USERNAME was checked
  but this variable is even present in pull-requests from other
  repos. This means that builds fail, because the access key
  is not there.
  This change looks for SAUCE_ACCESS_KEY instead, which is
  a secure variable, only present in build originating from
  the handlebars.js repo.
2020-01-13 21:47:51 +01:00
Nils Knappmeier 9d5aa363cf fix: don't wrap helpers that are not functions
- helpers should always be a function, but in #1639 one seems to
  be undefined. This was not a problem before 4.6 because helpers
  weren't wrapped then.
  Now, we must take care only to wrap helpers (when adding
  the "lookupProperty" function to the options), if they
  are really functions.
2020-01-13 21:39:01 +01:00
Nils Knappmeier 14ba3d0c43 v4.7.1 2020-01-12 13:21:08 +01:00
Nils Knappmeier 4cddfe7017 Update release notes 2020-01-12 13:20:37 +01:00
Nils Knappmeier f152dfc892 fix: fix log output in case of illegal property access
- fix link url to handlebarsjs.com
2020-01-12 13:09:19 +01:00
Nils Knappmeier 3c1e252169 fix: log error for illegal property access only once per property 2020-01-12 13:06:56 +01:00
Nils Knappmeier 0d5c807017 v4.7.0 2020-01-10 17:24:06 +01:00
Nils Knappmeier 1f0834b1a2 Update release notes 2020-01-10 17:23:31 +01:00
Nils Knappmeier 575d8772e2 fix: use "logger" instead of console.error
... to be graceful with older browser without "console"
2020-01-10 17:06:57 +01:00
Nils Knappmeier 7af1c12db6 feat: default options for controlling proto access
This commmit adds the runtime options
- `allowProtoPropertiesByDefault` (boolean, default: false) and
- `allowProtoMethodsByDefault` (boolean, default: false)`
which can be used to allow access to prototype properties and
functions in general.

Specific properties and methods can still be disabled from access
via `allowedProtoProperties` and `allowedProtoMethods` by
setting the corresponding values to false.

The methods `constructor`, `__defineGetter__`, `__defineSetter__`, `__lookupGetter__`
and the property `__proto__` will be disabled, even if the allow...ByDefault-options
are set to true. In order to allow access to those properties and methods, they have
to be explicitly set to true in the 'allowedProto...'-options.

A warning is logged when the a proto-access it attempted and denied
by default (i.e. if no option is set by the user to make the access
decision explicit)
2020-01-10 16:55:45 +01:00
Nils Knappmeier 91a1b5d2f4 v4.6.0 2020-01-08 23:45:15 +01:00
Nils Knappmeier 770d746e60 Update release notes 2020-01-08 23:44:46 +01:00
Nils Knappmeier d7f0dcf2bb refactor: fix typo in private test method 2020-01-08 23:17:23 +01:00
Nils Knappmeier 187d611e8c test: add path to nodeJs when running test:bin
- this allows the test to be run in a debugger
  without the complete PATH
2020-01-08 23:17:23 +01:00
Nils Knappmeier d337f40d0e test: show diff when test:bin fails 2020-01-08 23:17:23 +01:00
Nils Knappmeier d03b6ecfc4 feat: access control to prototype properties via whitelist
Disallow access to prototype properties and methods by default.
Access to properties is always checked via
`Object.prototype.hasOwnProperty.call(parent, propertyName)`.

New runtime options:
- **allowedProtoMethods**: a string-to-boolean map of property-names that are allowed if they are methods of the parent object.
- **allowedProtoProperties**: a string-to-boolean map of property-names that are allowed if they are properties but not methods of the parent object.

```js
const template = handlebars.compile('{{aString.trim}}')
const result = template({ aString: '  abc  ' })
// result is empty, because trim is defined at String prototype
```

```js
const template = handlebars.compile('{{aString.trim}}')
const result = template({ aString: '  abc  ' }, {
  allowedProtoMethods: {
    trim: true
  }
})
// result = 'abc'
```

Implementation details: The method now "container.lookupProperty"
handles the prototype-checks and the white-lists. It is used in
- JavaScriptCompiler#nameLookup
- The "lookup"-helper (passed to all helpers as "options.lookupProperty")
- The "lookup" function at the container, which is used for recursive lookups in "compat" mode

Compatibility:
- **Old precompiled templates work with new runtimes**: The "options.lookupPropery"-function is passed to the helper by a wrapper, not by the compiled templated.
- **New templates work with old runtimes**: The template contains a function that is used as fallback if the "lookupProperty"-function cannot be found at the container. However, the runtime-options "allowedProtoProperties" and "allowedProtoMethods" only work with the newest runtime.

BREAKING CHANGE:
- access to prototype properties is forbidden completely by default
2020-01-08 23:17:23 +01:00
Nils Knappmeier 164b7ff0de chore: ignore .nyc_output 2019-12-14 18:41:06 +01:00
Nils Knappmeier ac4655ee95 chore: disable "dot-notation" rule
- I don't think it makes much sense. In some cases,
  it is more readable to wrap property access in quotes,
  some times not, but there is no universal rule
  for that.
- a promises "catch"-function should not be wrapped, but it has to
  be, if "allow-keywords" is set to false
- an "if"-helper should be wrapped, but it is not allowed to be
  if "allow-keywords" is set to true (default).
2019-12-14 18:41:06 +01:00
Nils Knappmeier 14b621caf5 test/style: remove or hide unused code in git.js, add tests 2019-12-14 18:41:06 +01:00
Nils Knappmeier 1ec1737d24 test/style: refactor remaining grunt tasks to use promises instead of callbacks 2019-12-14 18:41:06 +01:00
Nils Knappmeier 1ebce2b53c test/style: use nyc instead of istanbul, npm audit fix 2019-12-14 18:41:06 +01:00
Nils Knappmeier 3a5b65e02b test/style: refactor parser task 2019-12-14 18:41:06 +01:00
Nils Knappmeier dde108e283 test/style: refactor test-task to make it more readable 2019-12-14 18:41:06 +01:00
Nils Knappmeier dc5495216d chore: change eslint-rules for tasks/
- use es2017 rules as NodeJS supports it today
- add "prefer-const"
2019-12-14 18:41:06 +01:00
Marcos Marado d1fb07b32b Update (C) year in the LICENSE file
Welcome to 2019!
2019-12-08 16:21:38 +01:00
Nils Knappmeier 04b19848cc chore: try to fix saucelabs credentials (#1627) 2019-12-08 16:17:15 +01:00
Nils Knappmeier c40d9f33ad chore: active linting and formatting on commit 2019-12-05 08:01:33 +01:00
Nils Knappmeier 8901c28e9a chore: fix task name in build 2019-12-03 22:59:04 +01:00
Nils Knappmeier e97685e989 style: reformat all files using prettier 2019-12-03 22:37:15 +01:00
Nils Knappmeier e913dc5f12 chore: restructure build commands
- move dtslint (checkTypes) away from Gruntfile.js
  (as it disturbs debugging `grunt`)
  and call it directly as npm-script in travis-ci
- re-group task definition in Gruntfile.js
- use a multi-job travis build to
  run linting, format, dtslint and tests
  in parallel
- run only "npm test" on appveyor
- rename Grunt-tasks for be more descriptive
- SAUCE_USERNAME is not a secret variable anymore
  it can be easily guessed anyway and the
  [secure] value messes up with a lot of the
  log output

- linting on commit disable during transition
2019-12-03 22:21:30 +01:00
Nils Knappmeier 1f61f21250 chore: configure prettier and eslint
- add prettier to do formatting
- add eslint-config-prettier to
  disable rules conflicting with prettier
- remove eslint from grunt workflow
- use lint-stage to lint and format
  on precommit
- use eslint and prettier in travis directly
- remove rules that are already part of
  the "recommended" ruleset

That rational is that eslint and prettier should be run in
Travis-CI, on commit and as IDE integration (highlighting
errors directlry). They don't need to be run along with
test-cases. Getting linting errors when running the tests
because of missing semicolons is just annoying, but doesn't
help the overall code-quality.
2019-12-02 22:40:05 +01:00
Nils Knappmeier 587e7a3e63 remove yarn.lock 2019-12-02 22:40:04 +01:00
ole-martin edcc84f292 Update readme.md with updated links (#1620)
* Update readme.md with updated links

Fix http->https where possible and update links doc links

* Update README.markdown

* Fix precompilation link
2019-12-02 22:03:52 +01:00
ole-martin 23d58e79bb fix(runtime.js): partials compile not caching (#1600)
Reintroduce "merge" function, no called "mergeIfNeeded", that only creates a new partials
object if both "env.partials" and "options.partials" are set.

closes #1598
2019-11-18 21:21:03 +01:00
Nils Knappmeier c819c8b533 v4.5.3 2019-11-18 08:10:26 +01:00
Nils Knappmeier 827c9d0747 Update release notes 2019-11-18 08:09:48 +01:00
Nils Knappmeier f7f05d7558 fix: add "no-prototype-builtins" eslint-rule and fix all occurences 2019-11-18 07:33:45 +01:00
Nils Knappmeier 1988878087 fix: add more properties required to be enumerable
- __defineGetter__, __defineSetter__, __lookupGetter__, __proto__
2019-11-18 07:33:45 +01:00
Nils Knappmeier 886ba86c2f test/chore: add chai/expect and sinon to "runtime"-environment 2019-11-17 22:18:20 +01:00
Nils Knappmeier 0817dad7e7 test: add sinon as global variable to eslint in the specs 2019-11-15 17:22:29 +01:00
Nils Knappmeier 93516a0b07 test: add sinon.js for spies, deprecate current assertions
- the goal is to get overall cleaner test.
- chai and sinon are standard libraries for testing
  assertions and spies. We should not use custom ones.
2019-11-15 16:38:28 +01:00
Nils Knappmeier 93e284ed9b chore: add chai and dirty-chai for better test assertions 2019-11-15 16:38:28 +01:00
Nils Knappmeier c02b05fa81 fix: use !== 0 instead of != 0 2019-11-15 16:38:28 +01:00
Nils Knappmeier 8de121d21c v4.5.2 2019-11-13 22:07:27 +01:00
Nils Knappmeier 6914090086 Update release notes 2019-11-13 22:06:51 +01:00
Nils Knappmeier d54137810a fix: use String(field) in lookup when checking for "constructor"
closes #1603
2019-11-13 22:02:05 +01:00
Nils Knappmeier c2ac79c970 test: add fluent API for testing Handlebars
use "expectTemplate(template)....toCompileTo(output)"
2019-11-13 22:02:05 +01:00
Nils Knappmeier 7ef86173ab v4.5.1 2019-10-29 05:42:23 +01:00
Nils Knappmeier b75e3e1f40 Update release notes 2019-10-29 05:41:43 +01:00
Nils Knappmeier 5e9d17f8fa fix: move "eslint-plugin-compat" to devDependencies 2019-10-29 05:17:03 +01:00
Nils Knappmeier b24797da01 v4.5.0 2019-10-28 19:47:51 +01:00
Nils Knappmeier a243067883 Update release notes 2019-10-28 19:47:08 +01:00
Nils Knappmeier 088e61812a chore: add eslint-plugin-compat and eslint-plugin-es5
- tests are not compiled with babel and must thus be in
  es5
- we don't use polyfills, so we need to make sure no
  functions aren't used that are not supported by popular browsers. (like Object.assign in Safari and IE11)
- Boths are errors that usually only appear when running
  tests in SauceLabs, which happens only on _after_
  merging a PR.
2019-10-28 19:17:57 +01:00
Kabir Baidhya 7052e88068 Resolve deprecation warning message from eslint while running eslint (#1586)
* Resolve eslint deprecation warning

  There was a DeprecationWarning message from eslint saying that `ecmaFeatures` property has 
  been deprecated.
  Moved it under the `parserOptions` as per recommended here - 
  https://eslint.org/docs/user-guide/migrating-to-2.0.0.
* Set escmaVersion = 6
* Use ES6 built-in global variables
* Remove flags in favor of ecmaVersion
2019-10-28 18:32:49 +01:00
Kabir Baidhya b8913fcc65 Add missing types for the Exception class properties (#1583)
* Convert Exception to a class
* Add node param type declaration
* Test the types
2019-10-28 15:58:59 +01:00
Robert Jackson 62ed3c25c7 Add Handlebars.parseWithoutProcessing (#1584)
When authoring tooling that parses Handlebars files and emits Handlebars
files, you often want to preserve the **exact** formatting of the input.
The changes in this commit add a new method to the `Handlebars`
namespace: `parseWithoutProcessing`. Unlike, `Handlebars.parse` (which
will mutate the parsed AST to apply whitespace control) this method will
parse the template and return it directly (**without** processing
😉).

For example, parsing the following template:

```hbs
 {{#foo}}
   {{~bar~}} {{baz~}}
 {{/foo}}
```

Using `Handlebars.parse`, the AST returned would have truncated the
following whitespace:

* The whitespace prior to the `{{#foo}}`
* The newline following `{{#foo}}`
* The leading whitespace before `{{~bar~}}`
* The whitespace between `{{~bar~}}` and `{{baz~}}`
* The newline after `{{baz~}}`
* The whitespace prior to the `{{/foo}}`

When `Handlebars.parse` is used from  `Handlebars.precompile` or
`Handlebars.compile`, this whitespace stripping is **very** important
(these behaviors are intentional, and generally lead to better rendered
output).

When the same template is parsed with
`Handlebars.parseWithoutProcessing` none of those modifications to the
AST are made. This enables "codemod tooling" (e.g. `prettier` and
`ember-template-recast`) to preserve the **exact** initial formatting.
Prior to these changes, those tools would have to _manually_ reconstruct
the whitespace that is lost prior to emitting source.
2019-10-28 00:28:28 +01:00
kpdecker 7fcf9d24f8 Use objects for hash value tracking
The use of arrays was incorrect for the data type and causing problems when hash keys conflicted with array behaviors.

Fixes #1194

(cherry picked from commit 768ddbd661)
2019-10-27 17:27:37 +01:00
Hannah Wolfe c76ded8f0f fix: add guard to if & unless helpers (#1549)
fixes #1548

- add a guard to show readable syntax error for if / unless helper
- prevents against 3 different errors that can be generated by different systax errors
2019-10-27 15:41:35 +01:00
Nils Knappmeier a15d01d383 Merge branch '4.4.x' into 4.x 2019-10-22 00:00:18 +02:00
Nils Knappmeier 8e1cce7918 v4.4.5 2019-10-20 23:08:10 +02:00
Nils Knappmeier def79fd5d6 Update release notes 2019-10-20 23:07:22 +02:00
Nils Knappmeier fd2e5c983d fix: non-eager matching raw-block-contents
In 4.4.4 the block-contents was matched with an eager match, which means
that with multiple raw-blocks of the same kind, the block was spanned
over the first ending-tag until the last one.
This commit replaces this by a non-eager match.

closes #1579
2019-10-20 22:57:13 +02:00
Nils Knappmeier 8d5530ee2c fix: non-eager matching raw-block-contents
In 4.4.4 the block-contents was matched with an eager match, which means
that with multiple raw-blocks of the same kind, the block was spanned
over the first ending-tag until the last one.
This commit replaces this by a non-eager match.

closes #1579
2019-10-20 22:56:24 +02:00
Nils Knappmeier 2ab261eab7 v4.4.4 2019-10-20 21:33:00 +02:00
Nils Knappmeier 143ac806da Update release notes 2019-10-20 21:32:09 +02:00
Nils Knappmeier f1752fe66f fix: prevent zero length tokens in raw-blocks (#1577)
This comment ensures that the lexer is not stuck on zero-length tokens, in raw-blocks like {{{{a}}}} {{{{/a}}}}
2019-10-20 21:24:55 +02:00
Nils Knappmeier aaab6099f2 fix: prevent zero length tokens in raw-blocks (#1577)
This comment ensures that the lexer is not stuck on zero-length tokens, in raw-blocks like {{{{a}}}} {{{{/a}}}}
2019-10-20 21:23:02 +02:00
sohibe feb60f85c9 show source location for the strict lookup exceptions 2019-10-09 06:50:04 +02:00
Nils Knappmeier 0b593bfe5d chore: link to s3 bucket with https, add "npm ci" to build instructions 2019-10-08 22:19:01 +02:00
Nils Knappmeier 2e53fba68f v4.4.3 2019-10-08 22:05:35 +02:00
Nils Knappmeier ba570c42cf Update release notes 2019-10-08 22:04:57 +02:00
Alexander Schramm 0440af2147 added missing type fields in typings and tests for them 2019-10-08 19:44:06 +02:00
Nils Knappmeier b793350fec v4.4.2 2019-10-02 22:44:36 +02:00
Nils Knappmeier 26d0f7a80d Update release notes 2019-10-02 22:44:03 +02:00
Nils Knappmeier b7eada0149 chore: fix grunt-saucelabs dependency 2019-10-02 22:42:52 +02:00
Nils Knappmeier b8e769fcb6 v4.4.1 2019-10-02 21:52:34 +02:00
Nils Knappmeier 9cb31653a4 Update release notes 2019-10-02 21:51:56 +02:00
Nils Knappmeier c53f340374 chore: use @knappi/grunt-saucelabs instead of github-dependency
- mostly because installing dependencies from github takes really
  long in this case.
2019-10-02 21:46:16 +02:00
Nils Knappmeier 059b330579 v4.4.0 2019-09-29 15:29:13 +02:00
Nils Knappmeier 8f6047cdfd Update release notes 2019-09-29 15:28:36 +02:00
antelle cf7545ef5a Added support for iterable objects in {{#each}} helper (#1557)
* Added support for iterable object in {{#each}} helper
Currently {{#each}} helper supports either arrays, or objects,
however nowadays you can define custom iterable objects by overriding
a special method called Symbol.iterator, which results in empty result
being rendered.
* improved a test for iterables in {{#each}} returning empty result
* #each helper: using ES5 instead of generator functions in tests
* #each helper: using ES5 in the helper itself
2019-09-29 14:57:47 +02:00
Nils Knappmeier c958cc8955 v4.3.4 2019-09-28 13:25:05 +02:00
Nils Knappmeier b250b2d53a Update release notes 2019-09-28 13:24:18 +02:00
Nils Knappmeier ff4d827c09 fix: harden "propertyIsEnumerable"-check
- "container" is an internal object that is most likely
  not accessible through templateing (unlike the proto of "Object", which might be.)
  In order to prevent overriding this method, we
  use "propertyIsEnumerable" from the constructor.
2019-09-28 10:36:49 +02:00
Nils Knappmeier e4738491b3 v4.3.3 2019-09-27 07:46:55 +02:00
Nils Knappmeier 54f7e11b28 Update release notes 2019-09-27 07:46:12 +02:00
Nils Knappmeier 8742bde701 fix test case for browsers that do not support __defineGetter__ 2019-09-27 00:09:04 +02:00
Nils Knappmeier 2357140c68 v4.3.2 2019-09-26 23:58:48 +02:00
Nils Knappmeier c5cbeac039 Update release notes 2019-09-26 23:58:13 +02:00
Nils Knappmeier 213c0bbe3c Use Object.prototype.propertyIsEnumerable to check for constructors
- context.propertyIsEnumerable can be replaced
  via __definedGetter__
- This is a fix specific to counter a known RCE exploit.
  Other fixes will follow.

closes #1563
2019-09-26 23:55:24 +02:00
Nils Knappmeier 050cca0866 v4.3.1 2019-09-25 00:32:32 +02:00
Nils Knappmeier 2d5579078b Update release notes 2019-09-25 00:31:48 +02:00
Nils Knappmeier e3639e2407 fix saucelabs tests (internet explorer) 2019-09-25 00:18:02 +02:00
Nils Knappmeier c2a17c7b30 fix saucelabs tests (internet explorer) 2019-09-25 00:00:51 +02:00
Nils Knappmeier 1266838829 do not break on precompiled templates from Handlebars >=4.0.0 <4.3.0
- The version-range above have compiler version 7 and
  precompiled templates expecte the (block)
  HelperMissing-functions in "helpers" and not in "container.hooks".
- Handlebars now accepts precompiled templates of version 7.
- If a precompiled template with version 7 is loaded,
  the (block)HelperMissing-functions are kept in "helpers"
2019-09-24 23:45:09 +02:00
Nils Knappmeier 64ecb9ea84 add test for #1560 2019-09-24 22:28:52 +02:00
Matt Hinchliffe 93444c58db Ensure allowCallsToHelperMissing runtime option is optional in typings 2019-09-24 22:27:16 +02:00
Nils Knappmeier d148d7cb1f check "allowCallsToHelperMissing" 2019-09-24 08:37:37 +02:00
Nils Knappmeier a89081d440 v4.3.0 2019-09-24 08:04:35 +02:00
Nils Knappmeier bba65605b1 Update release notes 2019-09-24 08:03:52 +02:00
Nils Knappmeier 06b7224ed9 adjust compiler revision 2019-09-24 07:31:19 +02:00
Nils Knappmeier 2078c727c6 Disallow calling "helperMissing" and "blockHelperMissing" directly
closes #1558
2019-09-24 07:31:19 +02:00
Nils Knappmeier fff3e40402 v4.2.1 2019-09-20 19:40:48 +02:00
Nils Knappmeier 35bcd4405f Update release notes 2019-09-20 19:40:10 +02:00
Caleb Mazalevskis 00b4f2ff98 Fix some small typos. 2019-09-20 19:01:03 +02:00
Nils Knappmeier e47b5ec192 integration-tests for various webpack-scenarios
related to #1553

- registering helpers on an instance retrieved via
  `import`, compiling the template on an instance
   retrieved via `require`
- using `@roundingwellos/babel-plugin-handlebars-inline-precompile` to load plugins inline
2019-09-20 18:58:17 +02:00
Paul Falgout c55a7bed78 fix: use cjs for browser fields
We should not be using pre-built files for the `browser`.  Resolves https://github.com/wycats/handlebars.js/issues/1553
2019-09-20 18:54:42 +02:00
Nils Knappmeier 164c7ceea4 v4.2.0 2019-09-03 21:58:07 +02:00
Nils Knappmeier 6ab48d8def Update release notes 2019-09-03 21:57:30 +02:00
Nils Knappmeier 8ac20285f6 Merge pull request #1534 from AndrewLeedham/4.x
Add VM TS mapping
2019-09-03 21:45:31 +02:00
Nils Knappmeier 888750ec27 fix typings of resolvePartial-options
- derived from the object structure seen in the debugger

closes #1534
2019-09-03 21:40:21 +02:00
Andrew Leedham 133b96a2ff Add "Handlebars.VM.resolvePartial" to type definitions
- Handlebars.VM is actually not part of the API,
  but Handlebars.VM.resolvePartial is mentioned
  in the documentation and is thus now treated
  as part of the API.

Closes #1534
2019-09-03 21:33:35 +02:00
Nils Knappmeier f119497312 chore: attempt to fix saucelabs problems with custom lib
- custom "grunt-saucelabs"
- which uses custom "sauce-tunnel"
- which uses current "sauce connect proxy"
2019-08-31 23:58:21 +02:00
Nils Knappmeier 62b64ecc3d chore: add comment to integration test 2019-08-31 23:53:28 +02:00
Nils Knappmeier a57b6824e0 add webpack test 2019-08-31 21:00:14 +02:00
Nils Knappmeier a26633f204 chore: fix integration tests 2019-08-31 19:27:11 +02:00
Nils Knappmeier f9cce4dd02 chore: add framework for various integration tests 2019-08-31 13:18:47 +02:00
Nils Knappmeier 16572cd848 feat: enhance typescript definition for knownHelpers
- Add support for custom helpers while keeping
  the list of builtin-helpers to maintain for
  autocompletion support

closes #1544
2019-08-29 22:53:09 +02:00
Nils Knappmeier f98c6a5425 v4.1.2-0 2019-08-25 18:06:35 +02:00
Nils Knappmeier fde409ace6 Update release notes 2019-08-25 18:05:56 +02:00
Nils Knappmeier 9aaace76dc Merge pull request #1540 from ouijan/browser-resolution
added browser to package.json, resolves #1102
2019-08-25 17:48:26 +02:00
Toby Harris 5eb59a5302 added browser to package.json, resolves #1102 2019-08-22 10:36:13 +10:00
Nils Knappmeier 10b5fcf92e v4.1.2 2019-04-13 16:19:22 +02:00
Nils Knappmeier dd0144c771 Update release notes 2019-04-13 16:18:37 +02:00
Nils Knappmeier 594f1e30dd chore: add missing typescript dependency, add package-lock.json
I suspect that the current problems with
saucelabs are due to a change transitive
dependency, but I'm not sure. I'm not
adding the package-lock.json to ensure
that this does not happen in the future.
2019-04-13 16:04:43 +02:00
Nils Knappmeier 871acccbb1 test: remove safari from saucelabs 2019-04-13 15:55:52 +02:00
Nils Knappmeier cd38583216 fix: prevent RCE through the "lookup"-helper
- The "lookup" helper could also be used to run a remote code execution
  by manipulating the template. The same check as for regular
  path queries now also is done in the "lookup"-helper
2019-04-11 23:08:57 +02:00
Nils Knappmeier c454d946e3 Merge pull request #1515 from zimmi88/4.x-typings-lint
Port over linting and test for typings
2019-03-27 15:11:23 +01:00
Nils Knappmeier 9cfb5ddad9 Merge pull request #1516 from phil-davis/revert-double-release-notes
Revert duplicate "Update release notes"
2019-03-20 22:53:21 +01:00
Phil Davis be44246221 Remove triplicate of v4.0.12 release notes 2019-03-20 18:19:30 +05:45
Phil Davis 002561be94 Revert "Update release notes"
This reverts commit 25b2e11dd9.
2019-03-20 18:10:47 +05:45
Timothy Lindvall 3fb6687013 Port over linting and test for typings
- Move typings from lib/ to types/.
- Add dtslint for validating types.
- Use grunt-bg-shell to call out to dtslint during build step.
2019-03-19 16:07:05 -07:00
Nils Knappmeier f691db546e v4.1.1 2019-03-16 22:28:38 +01:00
Nils Knappmeier 25b2e11dd9 Update release notes 2019-03-16 22:28:03 +01:00
Nils Knappmeier e5c39375ac Update release notes 2019-03-16 22:24:34 +01:00
Nils Knappmeier aef72870b6 Merge pull request #1511 from wycats/saucelabs
Fix Saucelabs tests
2019-03-15 23:16:23 +01:00
Nils Knappmeier 684f1032c6 chore: reactivate saucelabs-tests 2019-03-15 23:01:49 +01:00
Nils Knappmeier 7840ab66a5 test: make security testcase internet explorer compatible
Internet Explorer does not support the 'class Testclass {}' notation,
and tests are not compiled using babel.

closes #1497
2019-03-15 23:01:49 +01:00
Nils Knappmeier 4108b8330b Merge pull request #1504 from liqiang372/deprecate-substr-method
deprecate substr method and use existing strip function in grammar
2019-03-14 20:23:58 +01:00
Qiang Li 445ae12fa4 deprecate substr method and use existing strip function in grammar 2019-03-13 14:24:19 -07:00
Nils Knappmeier 5cedd62d7b fix: add "runtime.d.ts" to allow "require('handlebars/runtime')"
see #1499
2019-02-22 00:30:51 +01:00
Nils Knappmeier 40fb115e53 Revert "chore: re-activate saucelabs"
This reverts commit b2e2cfe56d.
2019-02-19 10:49:24 +01:00
Nils Knappmeier b2e2cfe56d chore: re-activate saucelabs 2019-02-19 10:31:42 +01:00
Nils Knappmeier 037bfbf4d9 Merge pull request #1500 from wycats/neo-async
Use `neo-async` instead of `async
2019-02-19 09:49:49 +01:00
Nils Knappmeier 048f2ce7d2 refactor: replace "async" with "neo-async"
The main reason is that neo-async takes a lot less space due to the missing lodash-dependency.
The other is speed.

closes #1431
2019-02-18 19:26:38 +01:00
Nils Knappmeier b92589a3b0 test: add test for NodeJS compatibility
The test is a simple addition to the existing tests. It should ensure
that the built Handlebars artifact only uses language features that are
available in old versions of NodeJS. A simple program and the
precompiler are started with NodeJS 0.10 to 11
2019-02-18 19:20:39 +01:00
Nils Knappmeier 1c62d4c5d0 Merge branch 'issue-1495' into 4.x 2019-02-07 11:03:35 +01:00
Nils Knappmeier 7caca944b1 v4.1.0 2019-02-07 10:46:32 +01:00
Nils Knappmeier 7bd34fb466 Update release notes 2019-02-07 10:46:01 +01:00
Nils Knappmeier b02e9a25ee test: run appveyor tests in Node 10 2019-02-07 10:19:53 +01:00
Nils Knappmeier f1c8b2e2a2 chore: disable sauce-labs
Related to #1497
2019-02-07 10:19:53 +01:00
Nils Knappmeier dbc50ac705 chore: bump version of grunt-saucelabs 2019-02-07 10:19:53 +01:00
Nils Knappmeier c6a8fc1c04 chore: add .idea and yarn-error.log to .gitignore 2019-02-07 10:19:53 +01:00
Nils Knappmeier 42841c41a4 fix: disallow access to the constructor in templates to prevent RCE
This commit fixes a Remote Code Execution (RCE) reported by
npm-security. Access to non-enumerable "constructor"-properties
is now prohibited by the compiled template-code, because this
the first step on the way to creating and execution arbitrary
JavaScript code.
The vulnerability affects systems where an attacker is allowed to
inject templates into the Handlebars setup.
Further details of the attack may be disclosed by npm-security.

Closes #1267
Closes #1495
2019-02-07 10:19:53 +01:00
Nils Knappmeier 56fc6768d1 test: run appveyor tests in Node 10 2019-02-07 10:14:44 +01:00
Nils Knappmeier ee3022228b chore: disable sauce-labs
Related to #1497
2019-02-07 10:04:17 +01:00
Nils Knappmeier 05e6293bb3 chore: bump version of grunt-saucelabs 2019-02-07 09:53:09 +01:00
Nils Knappmeier 2db0d123c8 chore: add .idea and yarn-error.log to .gitignore 2019-02-07 08:49:52 +01:00
Nils Knappmeier edc6220d51 fix: disallow access to the constructor in templates to prevent RCE
This commit fixes a Remote Code Execution (RCE) reported by
npm-security. Access to non-enumerable "constructor"-properties
is now prohibited by the compiled template-code, because this
the first step on the way to creating and execution arbitrary
JavaScript code.
The vulnerability affects systems where an attacker is allowed to
inject templates into the Handlebars setup.
Further details of the attack may be disclosed by npm-security.

Closes #1267
Closes #1495
2019-02-07 08:49:41 +01:00
Nils Knappmeier bacd473fe6 chore: fix components/handlebars package.json and auto-update on release 2019-01-02 01:07:05 +01:00
Timothy Lindvall 27ac1ee396 Feat: Import TypeScript typings
- Import typings from DefinitelyTyped into repo.
- Update typings header to cite contributors from history and git blame.
- Update package.json to add typings field.
2018-12-21 23:27:58 +01:00
Nils Knappmeier 78dd89c13a chore: Use node 10 to build handlebars
Node 10 is LTS now...
2018-12-15 23:40:08 +01:00
Nils Knappmeier 6b87c21fc4 chore/doc: Add more release docs 2018-12-15 23:37:40 +01:00
Nils Knappmeier 8d22e6f501 v4.0.12 2018-09-04 20:44:38 +02:00
Nils Knappmeier 3c970cc9c1 Update release notes 2018-09-04 20:44:07 +02:00
Nils Knappmeier abba3c7526 Update release notes 2018-09-04 20:37:53 +02:00
Nils Knappmeier 4bf1c4ff66 Update release notes 2018-09-04 20:36:06 +02:00
Nils Knappmeier 41b6a11d11 Merge branch '4.x' of github.com:wycats/handlebars.js into 4.x 2018-09-04 20:08:57 +02:00
Nils Knappmeier 2d28f920b0 bump grunt-plugin-dependencies to 1.x versions 2018-09-04 18:53:01 +02:00
Nils Knappmeier 29b174468d style: omit linting error caused by removing "if" 2018-09-04 18:53:01 +02:00
Nils Knappmeier d130ed2bc1 chore: bump various dependencies
- grunt -> 1
- grunt-contrib-watch -> 1
- mocha -> 5
  - in common.js: define "global" , see mochajs/mocha#1159
  - in builtins.js: revert "console.log" to old value just
    after using the mock (in log-helper tests), because
    mocha calls "console.log" on failure, before
    "afterEach"
2018-09-04 18:53:01 +02:00
Nils Knappmeier 2145c14994 bump grunt-plugin-dependencies to 1.x versions 2018-09-04 00:18:46 +02:00
Nils Knappmeier 8359722e5d style: omit linting error caused by removing "if" 2018-09-04 00:16:01 +02:00
Nils Knappmeier a1d864d4a7 chore: bump various dependencies
- grunt -> 1
- grunt-contrib-watch -> 1
- mocha -> 5
  - in common.js: define "global" , see mochajs/mocha#1159
  - in builtins.js: revert "console.log" to old value just
    after using the mock (in log-helper tests), because
    mocha calls "console.log" on failure, before
    "afterEach"
2018-09-04 00:15:02 +02:00
Qiang Li 0ddff8b388 unnecessary check
(cherry picked from commit e1fa310)
2018-05-31 23:16:26 +02:00
Nils Knappmeier 288e986161 Docs: Document branches in the CONTRIBUTING guide 2018-05-31 23:15:24 +02:00
Nils Knappmeier 30df8a1ac7 Testcase for accessing @root from a partial-block
related to #1445
2018-05-31 23:12:08 +02:00
Nils Knappmeier cda544bca9 Add package.json to components shim
This is an attempt to provide a valid package.json-file to the shim
repository for bower, in order to support `bower-away`

see components/handlebars.js#24
2017-11-28 22:13:17 +01:00
aaharu 69c6ca528d Use files field 2017-11-26 02:08:20 +09:00
Nils Knappmeier a4e39bdfd0 Fix release-notes (links to contributors` pages) 2017-11-09 10:46:39 +01:00
Nils Knappmeier b86b9189fa Fix release-notes (links to github-repo) 2017-11-09 10:44:20 +01:00
tim d3d39423a3 upgrade uglify-js 2017-10-21 23:04:23 +02:00
Nils Knappmeier 73d5637564 Update dependencies "async" to 2.5.0 and "source-map" to 0.6.1 2017-10-21 16:09:33 +02:00
Nils Knappmeier 7729aa956b Update grunt-eslint to 20.1.0 2017-10-21 15:42:27 +02:00
Nils Knappmeier 8947dd077c Update jsfiddle to 4.0.11 2017-10-17 23:15:53 +02:00
Nils Knappmeier 1e954ddf3c v4.0.11 2017-10-17 22:52:25 +02:00
Nils Knappmeier 1ac131e652 Update release notes 2017-10-17 22:51:42 +02:00
Nils Knappmeier 59548b4bdc Extend compiler-api example by replacing child-compiler
closes #1376

(cherry picked from commit ce3cd8a)
2017-10-17 22:30:31 +02:00
Marcos Marado 21386b6474 Update (C) year in the LICENSE file
Welcome to 2017!

(cherry picked from commit 33773c2)
2017-10-17 22:21:41 +02:00
Nils Knappmeier 79309659e1 Gracefully handle missing uglify-js dependency
closes #1391

uglify-js is an optional dependency and should be treated as such.
This commit gracefully handles MODULE_NOT_FOUND errors while loading
uglify.

- Check for existing uglify-js (and load uglify-js) only if minification
  was activated
- Use "require.resolve" to check if uglify exists. Otherwise, a missing
  dependency of uglify-js would cause the same behavior as missing
  uglify-js. (Only a warning, no error)
- The code to load and run uglify is put into a single for readability
  purposes
- Tests use a mockup Module._resolveFilename to simulate the missing module.
  This function is used by both "require" and "require.resolve", so both
  are mocked equally.

(cherry picked from commit d5caa56)
2017-10-17 22:18:56 +02:00
Nils Knappmeier 5b76f041b3 Fix build on Windows
Closes #1233

- Handle path-separators properly. Use "path.sep" instead of "/".
  Or use "require.resolve()" if possible
- Use "execFile" instead of "exec" to run the Handlebars executable.
  This prevents problems due to (missing) shell escaping.
- Use explicit call to "node" in order to run the executable on Windows.
- Add "appveyor"-CI in order to run regular tests on Windows.
2017-08-23 22:29:30 +02:00
Nils Knappmeier 1ed163f4ae Update jsfiddle-link to 4.0.10 2017-05-21 14:15:39 +02:00
Nils Knappmeier 670ec6fafb v4.0.10 2017-05-21 14:11:27 +02:00
Nils Knappmeier 2e935df8bf Update release notes 2017-05-21 14:10:50 +02:00
Nils Knappmeier 0e953d1db5 Replace "Object.assign" (not support in IE) by "util/extend" 2017-05-21 14:02:35 +02:00
Nils Knappmeier 5ec78a8c70 v4.0.9 2017-05-21 13:39:05 +02:00
Nils Knappmeier 5333f316c3 Update release notes 2017-05-21 13:38:28 +02:00
Nils Knappmeier 8a836e2272 Handlebars.compile() does not modify "options" anymore
Fixes #1327

- This commit creates a shallow copy of the "options" passed to
  Handlebars.compile() in order to prevent modifications
- Note that "new Handlebars.Compiler().compile(..., options)" still
  modify the options object. This might change in the future, if
  anybody needs a fix for that.
2017-05-21 13:20:48 +02:00
Luiz Américo cc554a5813 Fix build in windows
(cherry picked from commit 275ab37)
2017-05-15 00:11:59 +02:00
Nils Knappmeier ed879a6068 Ensure LF line-edings in handlebars-template fixtures (*.hbs)
Fixes #1331
2017-05-14 23:57:07 +02:00
Nils Knappmeier 2e21e2bc9a Run integration test with node handlebars -a ... on Windows
Fixes #1233

NodeJS files cannot be executed directly on Windows.
2017-05-14 23:37:24 +02:00
Nils Knappmeier bdfdbea09c Ensure LF line-edings in lexer-files (*.l)
Related to #1233
2017-05-14 22:49:19 +02:00
Nils Knappmeier b50ef03823 Force LF line-endings for spec/artifacts
Fixes #1331
2017-05-14 14:43:14 +02:00
Nils Knappmeier 6e6269fcd7 Use istanbul/lib/cli.js instead of node_modules/.bin/istanbul
Fixes #1331

Due to the way, "bin"-files are distributed into the node_modules/.bin
directory on Windows, the task "test:cov" did not work on Windows.
This commit uses the node-script directly.
2017-05-14 14:43:14 +02:00
Nils Knappmeier 7378f854c3 Publish valid semver task independently of the branch 2017-05-13 00:57:46 +02:00
Nils Knappmeier fed5818876 v4.0.8 2017-05-02 22:55:44 +02:00
Nils Knappmeier 0e81f0a082 Update release notes 2017-05-02 22:55:08 +02:00
Nils Knappmeier a00c598266 Allow partial-blocks to be executed without "options"
Closes #1341

If the @partial-block is called as parameter of a helper (like in
{{#if @partial-block}}...{{/if}}, the partialBlockWrapper is executed
without "options"-parameter. It should still work in without an error
in such a case.
2017-05-02 22:48:15 +02:00
Nils Knappmeier 606fa55b0a v4.0.7 2017-04-29 22:52:09 +02:00
Nils Knappmeier 8e09f0ee4e Update release-notes for 4.0.7 2017-04-29 22:47:39 +02:00
Nils Knappmeier c8f4b570c1 Fix context-stack when calling block-helpers on null values
Fixes #1319

Original behaviour:
- When a block-helper was called on a null-context, an empty object was used
  as context instead. (#1093)
- The runtime verifies that whether the current context equals the
  last context and adds the current context to the stack, if it is not.
  This is done, so that inside a block-helper, the ".." path can be used
  to go back to the parent element.
- If the helper is called on a "null" element, the context was added, even
  though it shouldn't be, because the "null != {}"

Fix:
- The commit replaces "null" by the identifiable "container.nullContext"
  instead of "{}". "nullContext" is a sealed empty object.
- An additional check in the runtime verifies that the context is
  only added to the stack, if it is not the nullContext.

Backwards compatibility within 4.0.x-versions:
- This commit changes the compiler and compiled templates would not work
  with runtime-versions 4.0.0 - 4.0.6, because of the "nullContext"
  property. That's way, the compiled code reads
  "(container.nullContext || {})" so that the behavior will degrade
  gracefully with older runtime versions: Everything else will work
  fine, but GH-1319 will still be broken, if you use a newer compiler
  with a pre 4.0.7 runtime.
2017-03-25 15:00:58 +01:00
Tobias Bieniek b617375219 Parser: Change suffix to use ES6 default module export
- This export will be transpiled by Babel for the cjs distribution,
  but will enable others to  use a pure ES6 module distribution
- Instanbul: Ignore "parser.js" for coverage reporting. This file was ignored before
  via annotation, but this has no effect anymore due to the above change
- Remove istanbul annotation from `parser-prefix` (@nknapp)

Squashed by @nknapp

(cherry picked from commit 508347e)
2017-02-24 08:35:48 +01:00
Nils Knappmeier 63a8e0caa2 Add more tests for partial-blocks and inline partials
- Multiple partial-blocks at different nesting levels
- Calling partial-blocks twice with nested partial-blocks
- Calling the partial-block from within the #each-helper
- nested inline partials with partial-blocks on different nesting levels
- nested inline partials (twice at each level)
2017-02-14 22:18:30 +01:00
Nils Knappmeier 5a164d0ca5 Fix for #1252: Using @partial-block twice in a template not possible
Fixes #1252
- This fix treats partial-blocks more like closures and uses the closure-context of
  the "invokePartial"-function to store the @partial-block for the partial.
- Adds a tes for the fix
2017-02-14 22:18:30 +01:00
Joonas Lahtinen 01b0f656bb Avoid duplicate "sourceMappingURL=" lines.
Avoid duplicate // sourceMappingURL=... lines when minifying AND
generating a map. UglifyJS2 will write the line when minifying.

(cherry picked from commit 660a117)
2017-02-14 22:01:13 +01:00
Travis Nelson 406f2ee01f require('sys') is deprecated, using 'util' instead
(node:30288) DeprecationWarning: sys is deprecated. Use util instead.

(cherry picked from commit 9a36966 by @travnels)
2017-01-01 20:47:59 +01:00
Nils Knappmeier a023cb4dd9 Make "column"-property of Errors enumerable
Fixes #1284

Appearently, there is a use-case of stringifying the error in order to
evaluated its properties on another system. There was a regression
from 4.0.5  to 4.0.6 that the column-property of compilation errors
was not  enumerable anymore in 4.0.6 (due to  commit 20c965c) and
thus was not included in the output of "JSON.stringify".
2016-12-30 16:33:32 +01:00
Nils Knappmeier c7dc353956 Testcase to verify that compile-errors have a column-property
Related to #1284

The test ensures that the property is there, because it is important to
some people.
2016-12-30 16:33:32 +01:00
Lon Ingram ad3037cf54 v4.0.6 (again)
Missed some versions in the repo that needed bumping.
2016-11-12 12:32:45 -06:00
Lon Ingram 44ce5f40aa Update release notes 2016-11-12 10:40:57 -06:00
Lon Ingram 3385de0921 v4.0.6 2016-11-11 12:04:25 -06:00
Lon Ingram abc160ae8e Use XML-like tags in test instead of bizarre dot delimiters 2016-11-11 12:01:16 -06:00
Lon Ingram 5ee91cb58a Add test reproducing #1185 2016-11-11 12:01:16 -06:00
Lon Ingram ef9e0dc2ab Walk up data frames for nested @partial-block
The root cause of #1218 is that `invokePartial` creates a stack of data frames
for nested partial blocks, but `resolvePartial` always uses the value at top of
the stack without "popping" it. The result is an infinite recursive loop, as
references to `@partial-block` in the partial at the top of the stack resolve to
itself.

So, walk up the stack of data frames when evaluating. This is accomplished by
1) setting the `partial-block` property to `noop` after use and
2) using `_parent['partial-block']` if `partial-block` is `noop`

Fix #1218
2016-11-11 12:01:16 -06:00
Kabir 0d0c4d1bbd Add a new lightweight package based on handlebars in the README
Add a new package [just-handlebars-helpers](https://github.com/leapfrogtechnology/just-handlebars-helpers) on the list. 

This is a lightweight package that offers a collection of common handlebars helpers. And it is [fully tested](https://codecov.io/github/leapfrogtechnology/just-handlebars-helpers)
2016-11-11 12:01:16 -06:00
Charles O'Farrell 8ff49cef52 Ensure that existing blockParams and depths are respected on dupe programs
Fixes #1186
2016-11-11 12:01:16 -06:00
Mr. Leo 1b885d496a Added cory 2016-11-11 12:01:16 -06:00
Gennadiy Litvinyuk 45e14b7b45 Preserve License info in Closure Compiler
To preserve license info in Closure Compiler the license has to be JSDoc-comment (not simple comment) and have @license before license text
2016-11-11 12:01:16 -06:00
Paul Falgout 1379e8f50c Contributing doc fix: failing thats -> failing tests 2016-11-11 12:01:16 -06:00
Tim Wang 6272c15074 Update license date 2016-11-11 12:01:16 -06:00
kpdecker 959ee557b0 Update jsfiddle to point to latest 2016-11-11 12:01:16 -06:00
Anders D. Johnson 577b760b50 Fix typos on decorators-api.md. 2016-11-11 12:01:16 -06:00
kpdecker 8c19874497 Drop extra Error params
This was causing a difficult to diagnose failure under IE and doesn’t give us enough value to justify the change.
2016-11-11 12:01:16 -06:00
Ryan Lewis 8fc16367b0 Add documentation for running tests to contributing.md
lawnsea: this contains the changes from 8a9c79b
2016-11-11 12:01:16 -06:00
kpdecker 400916c225 Avoid error in older browsers in test
The tests are run through the transpiler and just reverting the user of template literal is easier than adding transpiler to the test stack.
2016-11-11 12:01:16 -06:00
kpdecker fee23342b1 Update target browser test versions 2016-11-11 12:01:16 -06:00
kpdecker 32d6363841 Exclude coverage check in exception conditional 2016-11-11 12:01:16 -06:00
kpdecker 20c965cd65 Fix throw when creating exception object in Safari
https://github.com/jquery/esprima/issues/1290
2016-11-11 12:01:16 -06:00
kpdecker 6c9f98c025 Update build for modern node versions 2016-11-11 12:01:16 -06:00
kpdecker c393c81561 Relax depth check for context push
Fixes #1135
2016-11-11 12:01:16 -06:00
kpdecker 205c61cfb1 v4.0.5 2015-11-19 23:06:54 -06:00
kpdecker ddaff8ea29 Update release notes 2015-11-19 23:06:29 -06:00
kpdecker 685cf92bcb Return current handlebars instance from noConflict
Fixes wycats/handlebars-site#131
2015-11-19 22:58:26 -06:00
kpdecker 7a6c228592 Add webpack to dev dependency to support npm 3 2015-11-19 22:55:03 -06:00
kpdecker 0a3b3c2831 Further relax uglify dependency 2015-11-19 22:54:39 -06:00
kpdecker 251ec3b128 Work around uglify preserveComments some issue
Root cause: https://github.com/gruntjs/grunt-contrib-uglify/issues/366

Fixes #1129
2015-11-19 22:54:20 -06:00
kpdecker c21118d04b Include tests for minimized artifacts 2015-11-19 22:53:31 -06:00
Kevin Decker 0f00f31f6b Merge pull request #1132 from plynchnlm/master
Update uglify-js to avoid vulnerability
2015-11-18 01:13:47 -06:00
Paul Lynch 1dc417f1a5 Update uglify-js to avoid vulnerability 2015-11-16 18:23:25 -05:00
kpdecker 9f59de9657 Fix lint errors under latest eslint 2015-10-31 13:32:43 -05:00
Kevin Decker 3406b95cfd Merge pull request #1127 from victorwpbastos/master
Tag DOMBars as deprecated
2015-10-31 12:25:38 -05:00
Victor Bastos 7b48a284e0 Tag DOMBars as deprecated 2015-10-31 13:51:14 -02:00
kpdecker 98a6717e9e Add print-script helper script 2015-10-30 12:21:15 -05:00
kpdecker b7c95e9feb v4.0.4 2015-10-29 01:54:17 -05:00
kpdecker b1e561ee26 Update release notes 2015-10-29 01:53:40 -05:00
Kevin Decker 690ae2edc7 Merge pull request #1125 from nknapp/patch-2
Add promised-handlebars to "in-the-wild"-list
2015-10-29 01:50:17 -05:00
Nils Knappmeier 496ed8a25f Add promised-handlebars to "in-the-wild"-list 2015-10-28 18:00:16 +01:00
Kevin Decker d20f02d10d Merge pull request #1121 from shinypb/undefined_partial_error_message
Include partial name in 'undefined partial' exception message
2015-10-19 12:01:39 -05:00
Mark Christian 33b53ef989 Use template string for error message and double-quotes for quoting partial name 2015-10-19 09:44:53 -07:00
Mark Christian f08d48764d Include partial name in 'undefined partial' exception message 2015-10-19 09:22:54 -07:00
kpdecker 9365b82900 v4.0.3 2015-09-23 22:41:14 -05:00
kpdecker bf091777a9 Update release notes 2015-09-23 22:40:45 -05:00
kpdecker 94c840b6ed Create data frame for @partial-block
Fixes #1099
2015-09-23 22:08:59 -05:00
kpdecker 861d6f7b8d Fix tests under IE 2015-09-23 21:59:58 -05:00
kpdecker fffb5a985f Fix iteration over undefined values
Allow for iteration on undefined values, but special case undefined and null to prevent rendering errors when not running in strict mode.

Fixes #1093
2015-09-23 21:17:57 -05:00
kpdecker 08781798f5 Allow for escaped ] characters in [] IDs
Allows for ] literal characters to be used within [] IDs by prefixing them with the \ character. `\` literal at the end of the  may be referenced by the `\\` sequence if conflicting. Under most circumstances the `\\` sequence will continue to work.

Potentially breaking change for users of [] ids that have `\\` anywhere in the id or `\` at the end of the id.

Fixes #1092
2015-09-16 16:15:01 -05:00
kpdecker 641fe335df Update travis to test node 0.12 and 4.0.0 2015-09-16 16:15:01 -05:00
Kevin Decker 733b7c8752 Merge pull request #1091 from nikolas/patch-2
fix typo in release notes
2015-09-04 15:18:48 -05:00
Kevin Decker 9a86e40970 Merge pull request #1090 from nikolas/patch-1
grammar fixes in 4.0.0 release notes
2015-09-04 15:18:25 -05:00
Nik Nyby efad390ffc fix typo in release notes 2015-09-04 15:53:31 -04:00
Nik Nyby 3696d23752 grammar fixes in 4.0.0 release notes 2015-09-04 15:47:52 -04:00
kpdecker a5a3ab01d3 v4.0.2 2015-09-04 09:13:16 -05:00
kpdecker c83354101e Update release notes 2015-09-04 09:12:48 -05:00
kpdecker 7b1fdf814c Fix use of decorators within partials
If a decorator is used within a partial but not in the calling template, the hash is not passed in. For now error on the side of always including as just assigning values has minimal overhead.

Fixes #1089
2015-09-04 09:09:17 -05:00
kpdecker c7b28a65da v4.0.1 2015-09-02 21:21:36 -05:00
kpdecker 9a27693fae Update release notes 2015-09-02 21:21:06 -05:00
kpdecker 2d9a5151e8 Add source map flag for istanbul fork 2015-09-02 21:20:03 -05:00
kpdecker 3934b3ab92 Remove duplicate dependency 2015-09-02 21:19:53 -05:00
kpdecker 05b82a203e Fix failure when using decorators in partials 2015-09-02 20:43:54 -05:00
kpdecker f71f4e759e Better assert message in shouldThrow 2015-09-02 20:42:49 -05:00
kpdecker 6e9198b207 Remove duplicated release notes 2015-09-02 20:42:23 -05:00
kpdecker cc8ed7a9e7 Update subset of npm packages 2015-09-02 11:40:59 -05:00
kpdecker 454d20e3b2 Remove keen code
This hasn’t been running and is horribly out of date.
2015-09-02 11:40:35 -05:00
kpdecker 0aef72cb8e Update to latest eslint 2015-09-01 17:56:32 -05:00
kpdecker bff5fab8f9 v4.0.0 2015-09-01 08:19:14 -05:00
kpdecker f97b99e0cd Update release notes 2015-09-01 08:18:41 -05:00
kpdecker 6c07367cd6 Update release notes 2015-09-01 08:15:48 -05:00
kpdecker 83b8e846a3 Escape = in HTML content
There was a potential XSS exploit when using unquoted attributes that this should help reduce.

Fixes #1083
2015-09-01 01:44:35 -05:00
kpdecker b0d217e13d Rev runtime compiler revision 2015-09-01 01:43:00 -05:00
kpdecker c13c7df73d Add basic decorators docs
Fixes #1088
2015-09-01 01:29:25 -05:00
kpdecker b63d74a7b3 Add explicitPartialContext compiler flag
Fixes #1032
2015-09-01 01:05:00 -05:00
kpdecker e7a64f018c Merge branch 'decorators' 2015-09-01 00:48:20 -05:00
Kevin Decker 0f5061e445 Merge pull request #1087 from denniskuczynski/each_object_empty_string_key
Fix #each when last object entry has empty key
2015-08-30 17:27:52 -05:00
Dennis Kuczynski 7c896a074d Fix #each when last object entry has empty key 2015-08-30 10:43:24 -04:00
Kevin Decker e70430dd2e Merge pull request #1084 from blendlabs/master
Bump uglify version to avoid vulnerability flag.
2015-08-25 12:38:11 -05:00
John Steidley 9d4353c35c Bump uglify version to fix vulnerability 2015-08-24 17:06:40 -07:00
Kevin Decker c727e1f97c Merge pull request #1076 from wycats/partial-block
Implement partial blocks
2015-08-23 23:45:17 -05:00
kpdecker 6c45f49b24 Implement decorator helper method 2015-08-22 11:13:08 -07:00
kpdecker 495cd05a7e Implement inline partials
Allows for partials to be defined within the current template to allow for localized code reuse as well as for conditional behavior within nested partials.

Fixes #1018
2015-08-22 11:13:08 -07:00
kpdecker 452afbf2ff Implement block decorators
These allow for a given block to be wrapped in helper methods or metadata and allow for more control over the current container and method before the code is run.
2015-08-22 10:59:34 -07:00
kpdecker 408192ba9f Add decorator parsing 2015-08-22 10:59:34 -07:00
kpdecker 2a4a5447f5 Implement decorator environment and registration 2015-08-22 10:59:34 -07:00
kpdecker 1c274088c1 Update AST doc for partial block 2015-08-22 10:59:08 -07:00
kpdecker 0b5e82e1c3 Add whitespace control to partial block statements 2015-08-22 10:59:08 -07:00
kpdecker 233caf3f7c Create validateClose helper method
Avoid duplicating the logic needed to check for close block mismatches.
2015-08-22 10:59:08 -07:00
kpdecker 91ffd32cad Implement partial blocks
This allows for failover for missing partials as well as limited templating ability through the `{{> @partial-block }}` partial special case.

Partial fix for #1018
2015-08-22 10:59:08 -07:00
kpdecker 2571dd8e8e Improve sanity checks in compiler and visitor 2015-08-22 10:58:44 -07:00
Kevin Decker 729d9fdffe Merge pull request #1081 from hashchange/patch-1
Add Marionette.Handlebars to "in the Wild" list
2015-08-21 18:17:56 -07:00
hashchange a634cd3219 Add Marionette.Handlebars to "in the Wild" list 2015-08-21 13:32:20 +02:00
kpdecker 95d84badca Drop AST constructors in favor of JSON
These were little more than object literal statements that were less clear due to their use of index-based arguments.

Fixes #1077
2015-08-18 23:57:27 -07:00
kpdecker 9a2d1d6009 Pass container rather than exec as context
There is no real need for us to do `.call(container` other than for backwards compatibility with legacy versions. Using the 4.x release as a chance to optimize this behavior.
2015-08-18 23:54:04 -07:00
kpdecker 08093d72f0 Remove unused parameters 2015-08-18 23:14:09 -07:00
kpdecker 958273c2e6 Add object option to test runner 2015-08-18 23:13:15 -07:00
kpdecker 1c2b74e537 Run node tests last
This covers all of the test cases and generally have better stack traces so we want to have these featured more prominently.
2015-08-18 23:06:23 -07:00
kpdecker b85cad8df6 Fix travis config typo 2015-08-14 18:53:08 -05:00
kpdecker d21de5d569 Increase travis git window to avoid test failures
https://twitter.com/travisci/status/288390896339267584
2015-08-14 15:45:41 -05:00
kpdecker 519fac5081 Link to try handlebars from readme 2015-08-14 15:20:45 -05:00
kpdecker 624a4c71aa Cleanup stack traces for test assertions 2015-08-14 15:20:09 -05:00
kpdecker 3ce04ddd23 Include inline source maps in babel build 2015-08-14 15:19:37 -05:00
kpdecker ea3a5a1eb6 Add ignoreStandalone compiler option
Fixes #1072
2015-08-13 01:51:17 -05:00
kpdecker 269dd492bb Link to installation docs 2015-08-12 22:37:26 -05:00
kpdecker 2ae0ef6eef Add license information to bower.json
Fixes #1074
2015-08-12 02:45:51 -05:00
kpdecker 06d515a89d Ignore empty when iterating on sparse arrays
Fixes #1065
2015-08-04 12:40:33 -05:00
kpdecker 0de8dac702 Add support for string and stdin precompilation
Fixes #1071
2015-08-04 12:34:28 -05:00
kpdecker d716fd01c5 Remove no-shadow rule 2015-08-04 12:33:19 -05:00
kpdecker 77e6bfc5a1 Simplify object assignment generation logic 2015-08-04 12:33:05 -05:00
kpdecker 00f74420f9 Convert precompiler template loading to async 2015-08-04 10:55:51 -05:00
kpdecker a62cbad95a Refactor precompiler API into two phase
Load templates and then parse them in a distinct operation. This will allow us to use other input sources such as stdin and strings.
2015-08-04 09:26:19 -05:00
kpdecker ba31ef8ae4 Increase sauce test concurrency 2015-08-03 23:43:13 -05:00
kpdecker a44fe470b4 Include doctype in amd harness 2015-08-03 23:43:00 -05:00
kpdecker bb9831b73e Specify platform for firefox in sauce tests
Works around what appears to be an init issue within Sauce.
2015-08-03 23:42:50 -05:00
kpdecker c532b89af6 Fix parser declaration under amd builds 2015-08-03 22:45:39 -05:00
kpdecker 72eb10ca4b Revert "Pull sauce tests out of CI"
This reverts commit e2ba22eaad.
2015-08-03 22:27:25 -05:00
kpdecker bd643ce12b Pull out duplicated documentation from readme
Fixes #1006
2015-08-03 21:13:47 -05:00
kpdecker 93b07605cd Bulletproof AST.helpers.helperExpression
Avoid undefined values and potential false positives from other type values such as partials.

Fixes #1055
2015-08-03 20:31:57 -05:00
kpdecker 85750f8d0a Use += in printer 2015-08-03 18:51:46 -05:00
kpdecker fac71ce700 Fix incorrect variable removal 2015-08-03 17:59:23 -05:00
kpdecker 324d615726 Enforce 100% code coverage 2015-08-03 17:49:47 -05:00
kpdecker c3cbaa25a4 Fix partial handling with different context 2015-08-03 17:27:38 -05:00
kpdecker 9b1f9c7e44 Style updates 2015-08-03 17:03:37 -05:00
kpdecker 8e868ab225 Always return string responses
Certain optimizations for simple templates could result in objects returned by helpers returned rather than their string representation, resulting in some odd edge cases. This ensures that strings are always returned from the API for consistency.

Fixes #1054.
2015-08-03 16:08:23 -05:00
kpdecker 5d4b8da344 Pass undefined fields to helpers in strict mode
This allows for `{{helper foo}}` to still operate under strict mode when `foo` is not defined on the context. This allows helpers to perform whatever existence checks they please so patterns like `{{#if foo}}{{foo}}{{/if}}` can be used to protect against missing values.

Fixes #1063
2015-08-03 15:59:53 -05:00
kpdecker 279e038ba7 Avoid depth creation when context remains the same
Creating a new depth value seems to confuse users as they don’t expect things like `if` to require multiple `..` to break out of. With the change, we avoid pushing a context to the depth list if it’s already on the top of the stack, effectively removing cases where `.` and `..` are the same object and multiple `..` references are required.

This is a breaking change and all templates that utilize `..` will have to check their usage and confirm that this does not break desired behavior. Helper authors now need to take care to return the same context value whenever it is conceptually the same and to avoid behaviors that may execute children under the current context in some situations and under different contexts under other situations.

Fixes #1028
2015-08-03 15:15:09 -05:00
kpdecker 1c08771215 Fix track id handling in partials
Fixes #914
2015-08-03 12:16:02 -05:00
kpdecker 9f265b9761 Handle this references properly in track id mode 2015-08-03 12:13:24 -05:00
kpdecker 0aa54f49de Avoid log output in test 2015-08-03 12:13:00 -05:00
kpdecker 1f11cc0186 Remove out of date TODO 2015-08-03 12:12:30 -05:00
kpdecker 9a49d35023 Improve logging API
Adds multiple variable support and the ability to set statement level logging semantics.

This breaks that logger API, cleaning up the manner in which enums are set, but the other behaviors are backwards compatible.

Fixes #956
2015-08-03 11:37:49 -05:00
kpdecker b664997dc3 Move logger into separate module 2015-08-01 22:03:11 -05:00
kpdecker ac82842cb6 Add rest params to es6 supported list 2015-08-01 22:01:41 -05:00
kpdecker efddc3c09c Increase code coverage 2015-08-01 22:01:16 -05:00
kpdecker f3e8b18925 Add istanbul ignore to babel boilerplate 2015-08-01 21:46:45 -05:00
kpdecker 15b55a307b Move helpers into separate modules 2015-08-01 17:54:47 -05:00
kpdecker 231a8d7256 Fix with operator in no @data mode 2015-08-01 16:29:45 -05:00
kpdecker e2ba22eaad Pull sauce tests out of CI
Disabling these until #1069 can resolve whatever the root issue is.
2015-08-01 16:08:57 -05:00
kpdecker 1bb640be41 Allow empty key name in each iteration
Fixes #1021
2015-08-01 16:04:40 -05:00
kpdecker 2a851067b9 Add with block parameter support
Fixes #1042
2015-08-01 15:48:28 -05:00
kpdecker 410141c31e Fix escaping of non-javascript identifiers
The ‘ character would cause invalid javascript to be generated as it was not properly escaped. Switching to JSON.stringify safely handles all potential unescaped cases.
2015-08-01 15:48:28 -05:00
Kevin Decker 060c087e62 Merge pull request #1068 from 0xack13/master
Fix typo
2015-07-30 09:53:25 -05:00
Saleh Batati 5acf77a99a Merge pull request #1 from 0xack13/0xack13-patch-1-fix-typo
Fix typo
2015-07-30 06:08:11 +03:00
Saleh Batati fe057168ae Fix typo 2015-07-30 06:02:51 +03:00
Kevin Decker c83c363a58 Merge pull request #1060 from ericbn/issue-1056
#1056 Fixed grammar for nested raw blocks
2015-07-20 10:49:14 +03:00
Eric Nielsen aa7a45b443 Added comment about Jison's topState() 2015-07-19 12:34:44 -03:00
kpdecker a458fe26f4 Update jsfiddle link to 3.0.3 2015-07-19 11:28:43 +03:00
Eric Nielsen 2f9495c1df Added spec message 2015-07-16 15:15:06 -03:00
Eric Nielsen b9fe7ce618 #1056 Added spec for nested raw block 2015-07-15 14:45:53 -03:00
Eric Nielsen 868ef4b309 #1056 Fixed grammar for nested raw blocks 2015-07-15 14:45:43 -03:00
Kevin Decker 8c09b3c896 Merge pull request #1052 from maqnouch/patch-1
Updated year in License
2015-07-02 23:40:03 +02:00
AQNOUCH Mohammed 9bbc177fd5 Updated year in License 2015-07-02 15:53:10 +00:00
kpdecker 93faffa549 Fix location information for programs
There appears to be a bug in our use of jison causing the parent location information to be reported to programs. I wasn’t able to work through what might be causing this so instead using the location information of the statements collection to generate the proper location information.

This is a bit of a hack but we are very far behind on the Jison release train and upgrading will likely be a less than pleasant task that doesn’t provide us much benefit.

Fixes #1024
2015-06-26 16:58:34 -05:00
Kevin Decker d2fb3a4906 Merge pull request #1037 from tomxtobin/minor-readme-typo-fixes
Fix minor typos in README
2015-06-09 06:07:35 -04:00
Tom X. Tobin d0805e9cfa Fix minor typos in README 2015-06-08 18:37:41 -04:00
Kevin Decker 2c1d509c6c Merge pull request #1019 from aymerick/aymerick-patch-typo-test
Fixes typo in tests
2015-05-06 11:13:43 -05:00
kpdecker 569f288551 Add tests for string contexts
Fixes #1013
2015-05-06 10:48:23 -05:00
Aymerick d0d2168ec2 Fixes typo in tests
mutache => mustache
2015-05-06 14:03:14 +02:00
Kevin Decker 8cba84df11 Merge pull request #1015 from aymerick/fix-test
Fix tokenizer test for double quoted strings
2015-05-05 10:23:16 -05:00
kpdecker 00bfdd782f Fix VERSION update script
This broke after updating to es6 formatted code.

Fixes #1016
2015-05-05 10:20:56 -05:00
Aymerick afd595bd6e Fix tokenizer test for double quoted strings
There is two consecutive tests with the same input data: "{{ foo bar \'baz\' }}"

I suppose the first test should be about testing double quoted string.
2015-05-01 11:35:25 +02:00
kpdecker 891f48b7e9 v3.0.3 2015-04-28 14:52:04 -05:00
kpdecker 0bf79ea5c0 Update release notes 2015-04-28 14:51:32 -05:00
kpdecker 972f521718 Move noConflict implementation to module
DRYs the code to avoid escapes like #1004

Fixes #1004
2015-04-27 10:19:50 -05:00
kpdecker 4a40fc8e2f NPM ignore log files 2015-04-27 10:19:50 -05:00
kpdecker fc13400b6f Remove jshint completely 2015-04-27 10:19:49 -05:00
Kevin Decker 6ecf509385 Merge pull request #1008 from shannonmoeller/patch-1
Added handlebars-layouts to the in-the-wild list
2015-04-25 09:54:25 -05:00
Shannon Moeller d945089565 Added handlebars-layouts to the in-the-wild list
I've written a set of helpers which implement layout blocks similar to Jade, Jinja, Swig, Twig, and others. It seems to be gaining some popularity (around 10k npm downloads per month at present) and I would be honored to have it included in the in-the-wild list in case it may prove useful to others.
2015-04-24 22:29:40 -04:00
kpdecker 2f0b86665f v3.0.2 2015-04-20 03:11:12 -05:00
kpdecker d48e840513 Update release notes 2015-04-20 03:10:49 -05:00
kpdecker 4bed826d0e Update for let and optional parameters 2015-04-20 02:38:28 -05:00
kpdecker 0263aa48bc Run the precompiler module through es6 toolchain 2015-04-20 02:25:53 -05:00
kpdecker ecf60ab1bf Enable additional es6 features in eslint 2015-04-20 02:24:49 -05:00
Kevin Decker c6ab044fa8 Merge pull request #998 from wycats/babel
Add full support for es6
2015-04-16 17:12:40 -05:00
kpdecker fdc94207a3 Fix UMD build target generation 2015-04-16 16:54:30 -05:00
kpdecker e3d3eda2e1 Add full support for es6
Converts the tool chain to use babel, eslint, and webpack vs. the previous proprietary solutions.

Additionally begins enforcing additional linting concerns as well as updates the code to reflect these rules.

Fixes #855
Fixes #993
2015-04-16 16:43:01 -05:00
kpdecker 2a02261a5b Fix IE test freakout 2015-04-14 22:57:15 -05:00
kpdecker a009a97a2e Use captureStackTrace for error handler
Provides more stable error stack reporting when running under v8 environments.
2015-04-14 01:14:18 -05:00
kpdecker 54e743a09a Allow this references in literal statements
Fixes #967
2015-04-14 01:12:39 -05:00
kpdecker a7160a8d27 Expose Visitor in full browser build
Fixes #994
2015-04-13 21:25:53 -05:00
kpdecker dfd141cd35 Allow undefined and null in helper names 2015-04-13 21:18:53 -05:00
kpdecker 37a664bf64 Ignore branches tested without coverage monitoring 2015-04-07 23:38:05 -05:00
kpdecker c699d0b82f Remove vestigial code 2015-04-07 23:37:46 -05:00
kpdecker 2d149e7797 Add undefined and null literal support
This adds the UndefinedLiteral and NullLiteral to AST.

Fixes #990
2015-04-07 23:37:38 -05:00
kpdecker 81a4d50955 Avoid source-map import under AMD
Fixes #989
2015-04-07 22:35:14 -05:00
kpdecker e15af4c849 Add require.js error reporting in tests
Adds test coverage for #989
2015-04-07 21:51:50 -05:00
kpdecker afe730e059 v3.0.1 2015-03-24 14:22:38 -05:00
kpdecker 40894e619b Update release notes 2015-03-24 14:22:22 -05:00
Kevin Decker 1d401a2abc Merge pull request #984 from johneke/master
Adding documentation for passing arguments into partials
2015-03-24 14:20:22 -05:00
johneke a10d907a65 Update README.markdown 2015-03-18 22:55:26 -04:00
johneke fed86c01ad Adding documentation for parameters in partials 2015-03-18 20:23:15 -04:00
kpdecker ab96073c6b Optimize hot path in escapeExpression
Avoid deoptimizations in v8 due to the duct type check on string instances.

Partial fix for #973
2015-03-16 22:06:01 -05:00
kpdecker 64ab232d72 Fix --version flag
Fixes #966
2015-02-28 19:58:23 -06:00
Kevin Decker 8c357b9243 Merge pull request #964 from grassick/patch-1
default is a reserved word
2015-02-28 16:26:39 -06:00
grassick 71690ae39d default is a reserved word
Quoting it to fix in older browsers, especially Android 2.3.x.
2015-02-18 10:57:02 -05:00
Kevin Decker 3c9ff77e68 Merge pull request #962 from pismute/master
Add dashbars' link on README.
2015-02-16 09:01:07 -06:00
Changwoo Park 5ede36b8fd Add dashbars' link on README. 2015-02-16 19:29:13 +09:00
kpdecker c15f90e513 Fix typo 2015-02-10 20:40:53 -06:00
kpdecker 0099e16a01 v3.0.0 2015-02-10 00:14:55 -06:00
kpdecker a8848d0af9 Update release notes 2015-02-10 00:14:42 -06:00
kpdecker ca6152d2f7 Merge branch 'marcioj-boolean-literals' 2015-02-09 23:55:25 -06:00
kpdecker 55dc82d938 Update AST docs for literal helper names 2015-02-09 23:55:01 -06:00
kpdecker 39121cf8f5 Handle all potential literal values
Adds support for literal helper names in a few missing cases such as block expressions and subexpressions.
2015-02-09 23:54:46 -06:00
Marcio Junior 07f27843dc Transform literals to path expressions in mustache nodes 2015-02-08 19:31:38 -02:00
Marcio Junior 5cc326d425 Test compilation of literal values in mustaches 2015-02-08 19:31:38 -02:00
Marcio Junior f857471cc8 Boolean literals can be passed to mustaches 2015-02-08 19:31:38 -02:00
kpdecker fd8484798c Fix console.log cleanup 2015-02-07 14:31:54 -06:00
kpdecker 810d86d474 Fix global leak in test 2015-02-07 13:28:27 -06:00
kpdecker cfbef2585d Fail over to console.log if lacking console method
This improves logger resiliency under older browsers.
2015-02-07 13:23:10 -06:00
kpdecker 95421271e3 Change default log level to info
Eventually we want to fix this API so that you can provide per-instance levels, etc but for now this is a quick fix.

Fixes wycats/handlebars-site#91
2015-02-07 12:31:19 -06:00
Kevin Decker 752a28f24c Merge pull request #943 from ogennadi/patch-2
Properly formatted dot
2015-01-21 06:16:21 -06:00
Kevin Decker 05bda7ed0c Merge pull request #944 from ogennadi/patch-3
Clarified a long sentence
2015-01-21 06:15:58 -06:00
Oge Nnadi 1f6a94424d Clarified a long sentence 2015-01-20 11:01:11 -08:00
Oge Nnadi 8626fa8bbe Properly formatted dot 2015-01-20 10:59:38 -08:00
Kevin Decker 9f8daf9076 Merge pull request #941 from wycats/dynamic-partial
Add support for dynamic partial names
2015-01-18 20:13:25 -06:00
kpdecker 884bf15536 Avoid direct references to sexpr in statements
This allows us to avoid creating unnecessary AST nodes and avoids things like isHelper.

Side effect of these changes is that @data functions can now have data parameters passed to them.
2015-01-18 17:27:27 -06:00
kpdecker 999da739a6 Update for proposed SubExpression dependency break
Avoids parsing against SubExpressions and instead inlines the content that a subexpression otherwise would have. This can still be based via duck typing so should not add much overhead to the compiler.
2015-01-18 14:21:04 -06:00
kpdecker d567d9c345 Update PartialExpression AST docs 2015-01-18 13:29:15 -06:00
kpdecker cb51b82b8e Add support for dynamic partial names
Uses the subexpression syntax to allow for dynamic partial lookups. Ex:

```
{{> (helper) }}
```

Fixes #933
2015-01-18 13:23:45 -06:00
kpdecker b0b522b4f8 Add tests for partial comment whitespace control 2015-01-17 19:19:30 -06:00
kpdecker 3cf47abe19 Include error info when throwing type error 2015-01-17 19:19:30 -06:00
Kevin Decker b7488acf89 Merge pull request #940 from mattflaschen/addMissingReservedWords
Add missing reserved words so compiler knows to use array syntax:
2015-01-17 18:32:58 -06:00
Matthew Flaschen c9d723e0fb Add missing reserved words so compiler knows to use array syntax:
* await
* null
* true
* false

IE 8 was failing to compile Handlebars-generated source code
because it had helpers.null.

I came up with this list by diffing
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Lexical_grammar#Keywords
against the ones Handlebars already had.

I added two corresponding tests for nameLookup.
2015-01-17 19:04:38 -05:00
kpdecker c5fe2527b3 Ignore coverage dir in npm package
Fixes #937
2015-01-17 14:51:49 -06:00
Kevin Decker 9c40c07941 Merge pull request #938 from diwo/patch-1
Fix example using #with helper
2015-01-15 10:57:23 -08:00
Dickson Wong 9c7641f63f Fix example using #with helper
Example using #with helper should close tag using {{/with}}
2015-01-14 15:46:54 -08:00
Kevin Decker 96a5cf5ba1 Merge pull request #932 from wycats/strict
Implement strict and assumeObject modes
2014-12-31 19:28:05 -06:00
Kevin Decker 4b2146b12d Merge pull request #930 from wycats/visitor-update
Add parent tracking and mutation to AST visitors
2014-12-29 16:59:21 -06:00
kpdecker 6650957f79 Implement strict and assumeObject modes
Causes templates to throw when lookup fields are not defined within the context. Strict mode will throw when any field is omitted. assumeObjects mode maintains the existing behavior of outputting an empty response when fields are not defined but allows for performance/size optimizations by not doing safety checks for intermediate objects in the lookup chain.

Strict mode effectively disables the helperMissing and inverse handling for blockHelperMissing as templates will throw rather than call those features for missing data fields.

Fixes #651
Fixes #805
2014-12-27 14:59:36 -06:00
kpdecker ec798a7c44 Merge branch 'master' into visitor-update 2014-12-27 13:30:20 -06:00
kpdecker b764fb1ded Fix sourcemap test 2014-12-27 13:17:55 -06:00
kpdecker 0a9fc171b0 Fix block param evaluation under older IE 2014-12-27 13:13:52 -06:00
kpdecker 9c9a972a0c Safely handle source map in browser tests 2014-12-27 13:01:13 -06:00
kpdecker b474630c7d Strip unnecessary whitespace in template output 2014-12-27 12:58:28 -06:00
kpdecker f147050513 Update dependencies 2014-12-27 12:12:21 -06:00
kpdecker daa8f3dd05 Fix duplicate return statement on simple templates 2014-12-27 10:05:05 -06:00
kpdecker be0ba75925 Fix case-insensitive file system compile issue 2014-12-26 11:34:48 -06:00
kpdecker 9ff3daf785 Add parent tracking and mutation to AST visitors
Fixes #916
2014-12-26 11:32:30 -06:00
kpdecker 8babe05953 Bump code coverage 2014-12-26 11:12:47 -06:00
kpdecker 396795c983 Implement block parameters
Fixes #907
2014-12-26 00:31:57 -06:00
kpdecker 9e907e6785 Expose AST helpers in public API 2014-12-21 22:32:50 -06:00
kpdecker 6d2239d8ac Update fix for #926 for 3.0 codeline 2014-12-17 00:40:28 -06:00
kpdecker 59e80c5103 Track depths on deduped children
Fixes a very specific error case where deduped children won’t receive the depths object due to it being omitted by the caller when optimizing.

Fixes #926
2014-12-16 17:59:32 -06:00
kpdecker 69b1bdd32e Avoid unnecessary this. reference 2014-12-16 12:57:46 -06:00
kpdecker c05fad8b18 Fix remaining depth arg 2014-12-16 12:57:46 -06:00
kpdecker 203df9d5b7 Remove unused vars and add jshint checking 2014-12-16 12:57:46 -06:00
kpdecker 0d396ccd30 Remove Compiler.depths parameter
This is no longer necessary with the depths array tracking and adds compiler overhead.
2014-12-16 12:57:46 -06:00
Kevin Decker ae8c1d229f Merge pull request #918 from oneeman/submodule-instr-in-contributing
Add instructions for 'spec/mustache' to CONTRIBUTING.md, fix a few typos
2014-12-08 11:39:22 -08:00
Or Neeman e7f51c2248 Add instructions for 'spec/mustache' to CONTRIBUTING.md, fix a few typos elsewere 2014-12-07 09:00:56 -06:00
kpdecker 3238645f26 Allow blockParams on chained inverse statements 2014-11-29 18:38:22 -06:00
kpdecker f84f76f006 Add basic docs for JavaScriptCompiler override API 2014-11-29 18:20:11 -06:00
kpdecker 96c1300f15 Drop unused JavaScriptCompiler.namespace param 2014-11-29 18:11:58 -06:00
kpdecker b6ba2dcdb0 Move compileProgram out of visitor methods 2014-11-29 18:02:58 -06:00
kpdecker 5b0efc7e91 Track source node via accept call rather vs opcode 2014-11-29 18:02:58 -06:00
Kevin Decker d4070c3667 Merge pull request #915 from wycats/ast-update
Ast update
2014-11-29 18:02:12 -06:00
kpdecker a655aedb5c s/Subexpression/SubExpression/ 2014-11-29 17:59:09 -06:00
kpdecker 928ba56b95 Rework strip flags to make clearer at in AST level
Rather than keeping state in the AST, which requires some gymnastics, we create a separate visitor flow which does the top down iteration necessary to calculate all of the state needed for proper whitespace control evaluation.
2014-11-28 23:13:06 -06:00
kpdecker 8a6796e5c0 Move Jison parsing out of AST into helpers 2014-11-28 17:26:52 -06:00
kpdecker 95b23095c0 First crack at compiler API docs 2014-11-28 16:55:53 -06:00
kpdecker ffc9fb5007 Add original to BooleanLiteral 2014-11-28 16:55:41 -06:00
kpdecker 6a7a8c803d Rename AST objects to match type names 2014-11-28 14:42:24 -06:00
kpdecker f990cf0064 Treat partial exec in a manner closer to helpers
This helps unify the code handling and will also be needed to support string/id tracking on partials.
2014-11-27 09:11:03 -06:00
kpdecker 1124908d2a Update subexpression and hash AST constructs 2014-11-27 08:37:48 -06:00
kpdecker e1cba432a6 Simplify Path and Sexpr calculated flags 2014-11-27 07:32:15 -06:00
kpdecker 5c921cafeb Replace DataNode and IdNode with PathNode
This is a breaking change for string mode users as there is no longer a distinct type for data parameters. Instead data consumers should look for the @ prefix value.
2014-11-26 20:36:36 -06:00
kpdecker 697bbe59ca Update literal ast nodes for new spec 2014-11-26 19:22:09 -06:00
kpdecker 2a4819d75c Update statement node ASTs 2014-11-26 19:04:18 -06:00
kpdecker 5cfe6a0be1 Update MustacheNode for new AST structure 2014-11-26 09:31:46 -06:00
kpdecker df421f1e4b Update ProgramNode to better match SpiderMonkey 2014-11-26 09:18:33 -06:00
kpdecker 61dd721ca2 Update AST location info to match SpiderMonkey
Part of #889
2014-11-26 09:01:03 -06:00
kpdecker 3a9440f954 Make each helper data uniform
Provide @key and @last value for all forms of iteration.

Fixes #910
2014-11-26 08:02:55 -06:00
kpdecker e4c874d1af Update to latest es6-module-packager 2014-11-26 08:00:23 -06:00
kpdecker 5594416121 Export the default object for handlebars/runtime
Maintains consistency with the prior export pattern, while avoids the knarly default loading issues.
2014-11-26 07:49:29 -06:00
kpdecker c4b3c901a5 Provide Handlebars.noConflict implementation
Allows for users who are loading via a global pattern to avoid conflicting with other instances of the library.

Fixes #887
2014-11-26 07:48:45 -06:00
kpdecker 617dd57162 Lookup partials when undefined 2014-11-25 23:15:11 -06:00
kpdecker d044a8c99d Optimize variable declaration 2014-11-25 23:15:11 -06:00
Kevin Decker bf993528d1 Merge pull request #906 from mmun/block-params
Add parser support for block params
2014-11-12 21:36:25 -06:00
Martin Muñoz b8a9f7264d Add parser support for block params 2014-11-11 21:35:10 -05:00
Kevin Decker b3b5b35889 Merge pull request #856 from MajorBreakfast/patch-1
jspm compatibility
2014-11-11 15:05:10 -06:00
MajorBreakfast 80b5b24cce jspm compatibility
This will enable compatibilty with jspm as soon as https://github.com/wycats/handlebars.js/issues/855 has been addressed.
2014-11-10 15:12:58 +01:00
kpdecker 4a2afa9d02 Propagate options for client compiled partials
Fixes #901
2014-11-08 19:06:40 -06:00
kpdecker 4415f3151d Merge branch 'mmun-remove-block-mustache' 2014-11-08 18:57:19 -06:00
kpdecker bf8cc50798 Merge branch 'remove-block-mustache' of github.com:mmun/handlebars.js into mmun-remove-block-mustache
Conflicts:
	lib/handlebars/compiler/helpers.js
	spec/parser.js
2014-11-08 18:57:06 -06:00
kpdecker ac208b1bcd Only provide aliases for multiple use calls
Fixes #903
2014-11-08 18:53:43 -06:00
kpdecker 8dfeabdd16 Fix missing coverage 2014-11-08 18:11:29 -06:00
kpdecker 6d7696ef66 Remove unnecessary test stub 2014-11-08 18:09:15 -06:00
Kevin Decker 8e6e9fbc60 Merge pull request #902 from wycats/source-map
Generate Source Maps
2014-11-08 18:04:56 -06:00
kpdecker 1429587dd4 Provide default mapping for boilerplate code 2014-11-08 17:50:01 -06:00
kpdecker 0dbeeb4503 Add source map output support to the CLI 2014-11-08 17:50:01 -06:00
kpdecker 3ee0682247 Generate source maps
Allow the precompiler to generate source maps when the srcFile parameter is passed.

This refactors large chunks of the code generation pipeline, allowing metadata to be associated with code chunks as well as breaking out much of the code generation logic into a separate helper.
2014-11-08 17:50:01 -06:00
kpdecker 249f559104 Include location information in all opcodes 2014-11-08 17:50:01 -06:00
Kevin Decker ea6b0be910 Merge pull request #892 from wycats/else-if
Implement parser for else chaining of helpers
2014-11-08 17:49:14 -06:00
Kevin Decker e779ecf12d Merge pull request #890 from wycats/to-html-safe
Use toHTML vs. instanceof checks for SafeString
2014-11-08 17:47:25 -06:00
kpdecker 01a22e61df Use toHTML vs. instanceof checks for SafeString
Allows for us to play nicely in environments such as Node that could have multiple versions of the library loaded. Also allows for implementors to provide their own behavior, provided they know what they are doing.

Fixes #886
2014-11-08 17:46:53 -06:00
Kevin Decker c8af90f697 Merge pull request #863 from tomByrer/patch-1
+ jsDelivr CDN info
2014-11-08 17:44:22 -06:00
Kevin Decker b5f03c95ab Merge pull request #870 from max-b/master
Registering undefined partial throws exception.
2014-11-08 17:43:41 -06:00
kpdecker d595ebc0e2 Simplify program generation logic 2014-11-08 14:36:17 -06:00
kpdecker d47e4dd1f7 Bump test coverage 2014-11-08 14:35:23 -06:00
kpdecker 83bcbee222 Ignore unused code paths
We don’t want to remove these as the generic code may need it in the future, but these code paths are not triggered through the existing language constructs.
2014-11-08 14:35:23 -06:00
kpdecker 30034c75e7 Merge setupParams and setupOptions 2014-11-06 09:43:43 -06:00
kpdecker d3bd1a523d Simplify flushInline implementation 2014-11-05 23:23:43 -06:00
kpdecker 9665379cdc Use terinary operator for inline appends
Allows for append operations to avoid breaking inline chain.
2014-11-05 22:28:12 -06:00
kpdecker a7000f8129 Provide stubbed visitor implementation
Part of #889
2014-11-05 21:57:09 -06:00
Martin Muñoz a5d0491d57 Remove RawBlockNode in favor of prepareRawBlock helper 2014-11-05 11:00:39 -05:00
Martin Muñoz 12e3d210e8 Simplify BlockNode by removing intermediate MustacheNode 2014-11-02 18:19:20 -05:00
kpdecker 632fadcea3 Add preventIndent option for partials
This disables the standalone partial indent behavior required by the Mustache spec and allows for users to utilize partials in the same manner as under 1.x.

Fixes #858
2014-11-02 12:19:56 -06:00
kpdecker 7c220b9af5 Add test for whitespace control w/ partial indent
Verifies the behaviors discussed in #858
2014-11-02 12:09:37 -06:00
kpdecker f30b3ea329 Allow whitespace control on comments
This changes the call signature for the CommentNode constructor, which is a potentially breaking change for AST users.

Fixes #866
2014-11-02 11:55:52 -06:00
kpdecker 9fbf7aa753 Fix help message for CLI
Fixes #895
2014-11-02 11:08:42 -06:00
Kevin Decker 3e1840a75d Merge pull request #874 from ogwiz2/patch-1
Capitalization change in comments for consistency
2014-10-27 23:12:25 -05:00
kpdecker 4282668d47 Implement parser for else chaining of helpers
Allows users to chain multiple helpers together using their inverse callbacks. I.e.

```
{{#if foo}}
{{else if bar}}
{{else}}
{{/if}}
```

The control flow here effectively causes the helpers to be nested. The above is actually syntactic sugar for this:

```
{{#if foo}}
{{else}}
  {{#if bar}}
  {{else}}
  {{/if}}
{{/if}}
```

Any helper may be used in this manner, the only requirement is they support normal calls and inverse calls.

Introduces a breaking change in that `{{else foo}}` may no longer be used as a root level operator. Instead `{{^foo}}` must be used.

Fixes #72.
2014-10-27 19:17:16 -05:00
Alex Jeng 4aab967796 Capitalization change in comments for consistency 2014-09-23 11:59:30 -07:00
Maxb b87e601063 Fixing indentation in spec/partials.js 2014-09-19 11:46:02 -07:00
Maxb a0e2e200d9 Registering undefined partial throws exception. 2014-09-19 11:35:16 -07:00
Kevin Decker 3133af2a21 Merge pull request #869 from jonsadka/patch-1
Update FAQ.md
2014-09-19 01:02:40 -05:00
Jon Sadka 5fe7ef63d8 Update FAQ.md
Fixed misspelling of sufficent to sufficient on Line 60
2014-09-18 22:08:51 -07:00
tomByrer 62ed555b2c + jsDelivr CDN info
Thanks to libgrabber, every time a [new release is posted](https://github.com/components/handlebars.js/releases), jsDelivr will find it & automatically upload it.  If you change your file structure, please updated the [include section](https://github.com/jsdelivr/jsdelivr/blob/master/files/handlebarsjs/update.json) or ask us to do it.

I was going to link CDNJS, but they [haven't uploaded to v2.0.0](http://cdnjs.com/libraries/handlebars.js).  Nothing against those cats; we [use their network](http://www.jsdelivr.com/network.php).
2014-09-06 08:08:23 -06:00
kpdecker 8aa2a34a8d Update jsfiddle link 2014-09-02 00:14:10 -05:00
kpdecker aaa590d846 Fix non-prerelease handling for gemspec 2014-09-01 21:42:45 -05:00
kpdecker 1eb2b04aa1 v2.0.0 2014-09-01 21:28:59 -05:00
kpdecker 2e75199664 Update release notes 2014-09-01 21:28:51 -05:00
kpdecker 0670f656fb Update jsfiddle to 2.0.0-beta.1 2014-08-27 09:27:57 -05:00
kpdecker 4d17e3c209 Add contrib note regarding handlebarsjs.com docs 2014-08-27 01:49:52 -05:00
kpdecker 58fb258016 Cleanup var names slightly 2014-08-27 01:49:23 -05:00
kpdecker ed3ae9af59 Drop cdnjs release step 2014-08-26 19:38:40 -05:00
kpdecker 64d5481c00 Play nice with gemspec version numbers 2014-08-26 19:38:32 -05:00
kpdecker cee52ea68f v2.0.0-beta.1 2014-08-26 18:32:15 -05:00
kpdecker d33b362d97 Update release notes 2014-08-26 18:27:11 -05:00
Kevin Decker c5acea75aa Update FAQ.md 2014-08-25 23:37:40 -05:00
kpdecker 4f01f650dc Render false literal as “false”
Fixes #827
2014-08-25 23:35:43 -05:00
kpdecker 3c869866c8 Update FAQ with comment on UMD vs. AMD build
Fixes #796
2014-08-25 22:55:05 -05:00
kpdecker ca1486b960 Prune unused code 2014-08-25 22:39:17 -05:00
kpdecker dfca676d63 Fix test path 2014-08-25 22:39:01 -05:00
kpdecker 09d3c74c7f Convert build to expose UMD from the default files 2014-08-25 22:06:15 -05:00
kpdecker c98147b31f Move uglify include past conditional 2014-08-25 22:05:38 -05:00
kpdecker b41459055a Expose default compatibility wrapper for umd build 2014-08-25 22:04:11 -05:00
kpdecker 84342b6215 Add template isTop flag 2014-08-25 21:15:26 -05:00
kpdecker 24d63b6966 Update Ember testing instructions 2014-08-25 21:14:56 -05:00
kpdecker 36f00db073 Fix compiler version tag 2014-08-25 21:13:31 -05:00
kpdecker 64f422b57f Rev runtime version 2014-08-25 13:49:20 -05:00
kpdecker dc9a2ed7eb Refactor content blocks to ignore lines
We can simplify our previous standalone determination logic by merging content blocks again.

Fixes #854
2014-08-25 13:27:28 -05:00
kpdecker 980c38cebf Expose escapeExpression on the root object 2014-08-25 00:34:19 -05:00
kpdecker 31f6fd7131 Merge mustache compatibility sections 2014-08-25 00:34:05 -05:00
kpdecker c9886fdc0f FAQ tweaks 2014-08-25 00:33:46 -05:00
kpdecker 501a640330 Allow passing depths to _child 2014-08-25 00:32:57 -05:00
kpdecker 477a26913a Optimize compiler opcode call 2014-08-24 18:16:11 -05:00
kpdecker 4ce474d3e4 Update issue filing guidelines 2014-08-24 15:56:27 -05:00
kpdecker 3e6bf8b0f6 Link to current performance tests in readme 2014-08-24 15:45:10 -05:00
kpdecker e5aeca648a Create CONTRIBUTING.md
Cleans up some of the process focused content in the README and also adds nice message to github issue tracker, etc.
2014-08-24 15:28:22 -05:00
kpdecker 2b3fdf7ea8 Additional test coverage cleanup
Also fixes the template._child implementation which broke with the depthed work.
2014-08-23 18:52:30 -05:00
kpdecker 0c7c37df6a Restore helperMissing for ambiguous statements
Fixes #318 (regression)
Partial fix for #783
2014-08-23 18:26:57 -05:00
kpdecker cde008b49f Cleanup from code coverage report 2014-08-23 17:44:45 -05:00
kpdecker 84d646bb5d Move strip processing into AST helper logic
We already have to track these behaviors for the standalone parsing and rather than having two whitespace pruning implementations this moves all of the behavior into one place.

Fixes #852
2014-08-23 17:44:20 -05:00
kpdecker ccd803ff15 Replace string value rather than add omit flag 2014-08-23 16:37:30 -05:00
kpdecker 0528b91644 Add original value tracking to ContentNode 2014-08-23 16:33:14 -05:00
kpdecker 35b196d069 Merge branch 'mmun-refactor-parser' 2014-08-23 16:29:50 -05:00
kpdecker 529e2b6796 Merge branch 'refactor-parser' of github.com:mmun/handlebars.js into mmun-refactor-parser
Conflicts:
	lib/handlebars/compiler/ast.js
	spec/ast.js
	src/handlebars.yy
2014-08-23 16:29:22 -05:00
kpdecker 3531e04117 Optimize replaceStack for inline methods
Only use case was with inline input so most of this code was unnecessary.
2014-08-23 07:43:09 -05:00
kpdecker c3fde1c01c Remove unnecessary stack variable from blockHelper 2014-08-23 07:42:24 -05:00
kpdecker 67b3f40eb2 Merge branch 'evensoul-master' 2014-08-23 07:36:16 -05:00
kpdecker 19ce981296 Do not flush subexpressions
They are no longer duplicated with the new helper calling pattern and this also introduced stack corruption issues due to improper value lookups.

Fixes #767
Fixes #768
2014-08-23 07:36:01 -05:00
Marko Zabcic f4b8c5260c added test multiple subexpressions in a hash with context 2014-08-23 06:56:22 -05:00
Kevin Decker cf343a1949 Merge pull request #849 from wycats/helper-undefined
Fix undefined handling for pathed lookups
2014-08-15 02:07:24 -05:00
kpdecker 3ce105ae89 Fix undefined handling for pathed lookups
Fixes #837
2014-08-15 01:45:50 -05:00
kpdecker ae44e071d8 Test case for #837 2014-08-15 01:45:50 -05:00
kpdecker 2f14fa57e5 Fix/simplify tests under sauce 2014-08-15 01:02:26 -05:00
kpdecker f2b5519e76 Strip dead code from javascript compiler 2014-08-15 00:40:07 -05:00
kpdecker cc5de5df3c Fix compat partial exec without data 2014-08-15 00:38:23 -05:00
kpdecker d05245b5f5 Make depthed lookup call non-literal
Prevents duplicate calls from being made.
2014-08-15 00:37:57 -05:00
kpdecker 084e8fe1b7 Provide better error on unexpected template
Fixes #806
2014-08-15 00:06:40 -05:00
kpdecker 9f07a34955 Additional test coverage 2014-08-15 00:05:37 -05:00
kpdecker 6f22ec1e01 Remove nested function eval in blockHelperMissing
This only worked when a function returned a function, which was then evaluated. This seems like unexpected behavior so this was dropped.
2014-08-14 23:10:17 -05:00
kpdecker c71ca34fd3 Add FAQ entry for .default export
Fixes #824
2014-08-14 22:00:59 -05:00
kpdecker 85b67d7f3d Use public url for spec submodule 2014-08-14 12:31:01 -05:00
kpdecker 9e3f824bf5 Fix compiler program de-duping 2014-08-14 12:27:39 -05:00
kpdecker b94656a31f Drop unused Compiler.disassemble method 2014-08-14 12:27:23 -05:00
kpdecker 3cea85b2b0 Drop unused DECLARE opcode 2014-08-14 12:27:02 -05:00
kpdecker 4d0e3c98eb Drop unused registerHelper inverse parameter 2014-08-14 12:25:58 -05:00
kpdecker cb22ee5681 Increase test coverage a touch 2014-08-14 12:25:35 -05:00
kpdecker 9ab31e1fc6 Add istanbul code coverage 2014-08-14 01:11:55 -05:00
kpdecker 1bd8d38c3c Update compatibility documentation
Provides an official statement regarding the issues discussed in #425
2014-08-14 00:48:19 -05:00
kpdecker 59607aa9ab Add mustache spec to tests 2014-08-14 00:48:19 -05:00
Kevin Decker 48263be204 Merge pull request #848 from wycats/recursive-partial
Implement partial recursive lookup
2014-08-14 00:48:01 -05:00
kpdecker 9f8110fe15 Implement partial recursive lookup 2014-08-14 00:29:25 -05:00
kpdecker 0edce6e1d1 Inherit compat flag for partials 2014-08-14 00:29:25 -05:00
Kevin Decker d95725d5fd Merge pull request #819 from wycats/compat-mode
Implement recursive field lookup
2014-08-13 21:14:32 -05:00
kpdecker c98613b711 Implement recursive field lookup in compat mode
Provides the mustache behavior of recursive lookup without the use of depthed paths.

Note that this does incur a fairly dramatic performance penalty for depthed queries.
2014-08-13 20:46:42 -05:00
kpdecker 625b00e1da Avoid depth construction overhead for non-depthed 2014-08-13 14:51:33 -05:00
kpdecker b695aaec7c Use depths array rather than passing array args
Approximately doubles the throughput performance of depthed templates and clears the way for doing recursive path lookups in pathed mode.
2014-08-13 14:11:53 -05:00
kpdecker f6dc5ad716 Create contextName helper for context lookup 2014-08-13 11:41:27 -05:00
Kevin Decker 867322adf2 Merge pull request #787 from wycats/standalone-mustaches
Remove whitespace surrounding standalone statements
2014-08-13 09:16:08 -05:00
Kevin Decker 3288f525f3 Merge pull request #846 from espadrine/patch-1
Readme: Use an SVG badge.
2014-08-12 20:49:08 -05:00
kpdecker f2a2914d4f Add additional tests for standalone partials 2014-08-12 15:38:50 -05:00
kpdecker e93d516672 Render indent for standalone partials 2014-08-12 14:41:58 -05:00
kpdecker 2a47d66ee9 Drop omitted content tags from generated output 2014-08-12 14:41:58 -05:00
kpdecker 50c657f138 Flag standalone nodes in the AST 2014-08-12 14:41:58 -05:00
kpdecker 6070179009 Track root status in ProgramNode constructor 2014-08-12 14:41:58 -05:00
kpdecker 4a4dcf1657 Parse context sections by line 2014-08-12 14:41:58 -05:00
kpdecker 6efcaaf8d1 Update to latest saucelabs lib 2014-08-12 14:32:19 -05:00
kpdecker f4af8e06b6 Allow compat flag in test specs 2014-08-11 20:10:12 -05:00
kpdecker 63c5eb7a82 Remove unused JSCompiler.register method 2014-08-11 20:10:12 -05:00
kpdecker 248ad1acae Fix type in README
Fixes #843
2014-08-11 20:08:29 -05:00
kpdecker 905f99b18c Minor test cleanup 2014-08-11 20:08:29 -05:00
Thaddee Tyl 11f85089cc Readme: Use an SVG badge.
Using the up-to-date badge design.
2014-08-08 17:23:38 +02:00
Kevin Decker 5f1bf78555 Merge pull request #833 from mmun/patch-1
Lock mocha dependency to ~1.20.0
2014-07-29 20:38:52 -05:00
Kevin Decker f374939cfd Merge pull request #823 from fractal-surfing/with-inverse
Support inverse sections on the with helper
2014-07-29 09:25:45 -05:00
Martin Muñoz eee2c4d4f2 Refactor blocks, programs and inverses 2014-07-29 02:45:50 -04:00
Martin Muñoz e11c1beb72 Lock mocha dependency to ~1.20.0 2014-07-29 02:22:35 -04:00
Dan Manges 2bdfd2a31d Support inverse sections on the with helper 2014-07-16 19:37:01 +00:00
kpdecker 271106d43f Do not lookup pathed helpers on the helper stack
Fixes #764
2014-07-12 12:50:54 -05:00
kpdecker 1fb7b51ee6 Fix rendering of paths that resolve to zero
Fixes #820
2014-07-12 11:43:45 -05:00
kpdecker c90cfe247c Use ID lookup for ambiguous helper evaluation
Final change for #731
2014-07-07 00:20:02 -05:00
kpdecker 107aa4a66a Add test case for GH-731 2014-07-07 00:01:17 -05:00
Kevin Decker 9a1eda2caf Merge pull request #731 from wycats/path-lookup
Strange behavior for {{#foo}} {{bar}} {{/foo}} when foo is 0
2014-07-06 23:56:33 -05:00
kpdecker b5a5c76ceb Rework lookup null protector logic
- Move the lookup null protection out of `nameLookup` and into that contexts that are aware of the needs for falsy vs. not displayed values.
- Optimize lookup for nested path operations

Fixes #731
2014-07-06 23:41:15 -05:00
kpdecker 4aad72d223 Move lambda resolution to runtime
This has a very positive impact on precompiled output size, particularly for known-helpers cases, and little or no impact on the throughput numbers.
2014-07-06 12:53:01 -05:00
kpdecker 704961b378 Add additional paths to path throughput test 2014-07-06 11:37:24 -05:00
kpdecker 054627adb0 Move precompiler test filter into describe
This allows us to play nicely with in-browser tests
2014-07-05 15:03:18 -05:00
kpdecker 058e7495f9 Add precompiler version FAQ entry.
Resolves #789
2014-07-05 14:45:13 -05:00
kpdecker 059532295d Move precompiler to safer location
Attempts to avoid some of the Node vs. ES6 confusion between the different styles within the application.

Also adds some simple tests in addition to the integration test.
2014-07-05 14:39:28 -05:00
Kevin Decker 0c084399e2 Merge pull request #795 from jwietelmann/master
Turn the precompile script into a wrapper around a module.
2014-07-05 12:30:24 -05:00
Kevin Decker efb17b80a6 Update FAQ styling 2014-07-05 12:22:28 -05:00
kpdecker 7890c7dc89 Add basic FAQ 2014-07-05 12:21:39 -05:00
kpdecker b79e31e5a5 Remove disassemble log statement
Fixes #772
2014-06-28 18:44:11 -07:00
kpdecker 7172d167dd Provide clear throw on {{#each}}
Fixes #773
2014-05-27 10:54:28 -04:00
kpdecker 3cdf14d294 Add test case for Issue #800
This isn’t failing in master but this is a useful test to prevent regressions.
2014-05-27 09:32:29 -04:00
kpdecker efad61b1b7 v2.0.0-alpha.4 2014-05-19 23:15:50 -05:00
kpdecker 2a20976e6a Update release notes 2014-05-19 23:15:18 -05:00
kpdecker 3638874ddc Expose setup wrappers for compiled templates 2014-05-19 23:08:34 -05:00
kpdecker f419838362 v2.0.0-alpha.3 2014-05-19 22:29:25 -05:00
kpdecker 8ee73b9d13 Update release notes 2014-05-19 22:29:08 -05:00
kpdecker 3dcc49927b Drop travis 0.8 build support
The date-now dependency has dropped support and that version is sufficiently old that we shouldn’t have any issues not testing against it anymore.
2014-05-19 22:02:57 -05:00
Kevin Decker e10eb537ab Merge pull request #797 from tomdale/full-helper-names
Pass full helper ID to helperMissing when options are provided
2014-05-19 21:59:20 -05:00
Kevin Decker 8f09ea5f40 Merge pull request #793 from mmun/ast-tweaks
Ensure isHelper is coerced to a boolean
2014-05-19 21:56:24 -05:00
Tom Dale 72c1262394 Pass full id to helperMissing 2014-05-13 13:24:25 +01:00
Joel Wietelmann a14c689fa6 Stupid line ending 2014-05-10 14:11:07 -05:00
Joel Wietelmann e143849f8a Knocking the indent back to what it was to make for a less scary-looking pull request 2014-05-10 14:10:05 -05:00
Joel Wietelmann 2e7a3bd702 Turning CLI precompiler into a wrapper around a module. 2014-05-10 14:07:15 -05:00
Martin Muñoz 356ea1a909 Ensure isHelper is coerced to a boolean 2014-05-05 01:51:29 -04:00
kpdecker 085e5e1937 Refactor template init logic 2014-04-13 07:21:08 -05:00
kpdecker a601b24a73 Update travis exec for S3 push 2014-03-29 13:27:12 -05:00
kpdecker 04c312649b v2.0.0-alpha.2 2014-03-06 01:26:26 -06:00
kpdecker fea46dbb8c Update release notes 2014-03-06 01:26:15 -06:00
kpdecker b61c75512a Remove log in test 2014-03-05 21:11:47 -06:00
kpdecker 1da136490b Add version option to compiler
Fixes #746
2014-03-05 18:32:23 -06:00
kpdecker d55f9966b9 Update escaping in link_to example 2014-03-05 18:27:48 -06:00
Kevin Decker d33ae150cd Merge pull request #740 from SimonCropp/patch-1
Update README.markdown
2014-03-05 18:24:59 -06:00
kpdecker 8347ee2591 Temporarily kill the AWS publish CI step 2014-03-05 18:17:46 -06:00
kpdecker 55782f08c5 Fix evaluation of paths and subexprs
Fixes #743
2014-03-05 17:49:39 -06:00
kpdecker 058c0fb6ba Make JSON a requirement for the compiler.
Include the polyfill for testing under IE8 and below and document this.
2014-03-05 17:15:23 -06:00
kpdecker 10168cb0a9 Remove keyword that chokes up IE tests 2014-03-05 15:07:19 -06:00
kpdecker 66538cc7fa Drop opera tests
The Sauce implementation doesn't seem to work properly.
2014-03-05 15:03:13 -06:00
Kevin Decker 7dd64e5e92 Merge pull request #749 from jenseng/option_subexpressions_fix
properly handle multiple subexpressions in the same hash, fixes #748
2014-03-05 14:38:26 -06:00
Kevin Decker 674d07fd03 Merge pull request #756 from jenseng/mapfix
fix bug in IE<=8 (no Array::map), closes #751
2014-03-05 14:36:57 -06:00
Jon Jensen 5999baf746 fix bug in IE<=8 (no Array::map), closes #751
looks like this broke in 2812fe27

also s/this.i/var i/, since the functions that needed it were removed in
a1edab6ef
2014-03-04 22:18:50 -07:00
Kevin Decker fd5dfc861c Merge pull request #747 from Jonahss/master
updated grunt-saucelabs, failing tests revealed
2014-02-24 19:20:08 -06:00
Jon Jensen 8493822b62 properly handle multiple subexpressions in the same hash, fixes #748
push all hash params before popping any so as to avoid the last stackN var
stomping previous ones
2014-02-20 17:37:25 -06:00
jonahss c86ac60876 updated grunt-saucelabs, failing tests revealed 2014-02-18 16:14:31 -08:00
Simon Cropp c68620caf9 Update README.markdown 2014-02-15 22:05:38 +11:00
kpdecker a5ff1f3d22 vv2.0.0-alpha.1 2014-02-10 02:11:07 -06:00
kpdecker b22a150a37 Update release notes 2014-02-10 02:01:20 -06:00
kpdecker 6da3c31494 Update compiler revision info 2014-02-10 01:50:32 -06:00
kpdecker c72a8d5e84 Add child accessor API 2014-02-09 22:48:38 -06:00
kpdecker d00d8b6776 Better docs on how to file issues 2014-02-09 22:47:40 -06:00
Kevin Decker db651e7ff4 Merge pull request #491 from wycats/data-depth
For nested helpers: get the @ variables of the outer helper from the inner one
2014-02-09 19:16:47 -06:00
kpdecker 16f135835e Add support for depthed resolution of data fields 2014-02-09 18:35:22 -06:00
Kevin Decker fcec69ae2c Merge pull request #569 from wycats/lookup-helper
Unable to lookup array values using @index
2014-02-09 17:50:28 -06:00
kpdecker 306feb497c Implement lookup helper 2014-02-09 17:22:18 -06:00
kpdecker a9f76e1475 jshint changes 2014-02-09 17:21:49 -06:00
Kevin Decker fe3b4cc98e Merge pull request #730 from wycats/jezell-master
Raw block helpers
2014-02-09 15:27:59 -06:00
kpdecker 14b7ef9066 Make raw blocks operate like blocks 2014-02-09 15:11:12 -06:00
Jesse Ezell 9b14dc40a5 raw block helpers 2014-02-09 14:41:32 -06:00
Jesse Ezell 954253305a Add {{{{ }}}} for raw blocks 2014-02-09 14:41:32 -06:00
kpdecker 7a7ad74ff1 Optimize buffer generate first and all edge cases 2014-02-09 14:24:18 -06:00
kpdecker d6e86af41a Update to latest packager version
Fixes #705
2014-02-09 13:09:38 -06:00
kpdecker 71ed8b5e93 Merge branch 'master' of github.com:wycats/handlebars.js 2014-02-09 12:20:25 -06:00
kpdecker 7a6f706dc4 Add initial copyright year 2014-02-09 12:19:55 -06:00
PatrickJS dd365eaede update copyright year 2014-02-09 12:19:27 -06:00
Kevin Decker bc8f6792e8 Merge pull request #729 from wycats/precompile-literal
Convert template spec to object literal
2014-02-08 20:01:52 -06:00
kpdecker 2812fe2775 Convert template spec to object literal
This allows for metadata to be associated with the template and a simplification of the template init logic.
2014-02-06 23:05:45 -08:00
kpdecker b0e7298523 Remove unnecessary conditional 2014-02-06 23:01:43 -08:00
kpdecker 6ca06d4115 Add depth benchmark cases 2014-02-06 22:50:12 -08:00
kpdecker 8f07bbabea Add quotes to exception 2014-01-20 00:55:53 -06:00
kpdecker 4679b1d931 Include content on binary output test failure 2014-01-18 09:29:47 -06:00
kpdecker cea57c05e2 Optimize initData for root defined case 2014-01-18 09:23:11 -06:00
kpdecker 311be67bde Add partial and helper unregister APIs
Fixes #669
2014-01-18 09:22:29 -06:00
Kevin Decker 88c52ded2e Merge pull request #712 from wycats/track-ids
Add trackIds compiler flag
2014-01-17 21:36:01 -08:00
kpdecker 5a0bcb932d Fix handler execution in nondata/nonid mode 2014-01-17 23:24:35 -06:00
kpdecker 49fcf10de2 Add contextPath tracking in builtin helpers 2014-01-17 23:15:18 -06:00
kpdecker ace2896ec8 Add trackIds compiler flag
Allows helpers that care about where a particular field came from derive this data while maintaining backward compatibility with existing helpers.
2014-01-17 23:15:17 -06:00
kpdecker 9df919083d Create track ids test stub 2014-01-17 23:15:17 -06:00
kpdecker 103e5f8409 Only lint 1 set of release content 2014-01-17 23:11:35 -06:00
kpdecker a14daca1c6 Simplify sauce test environments 2014-01-17 23:11:13 -06:00
kpdecker 33921beaa0 Fix missing parameters for pathed mustaches
Fixes #658
2014-01-17 23:09:59 -06:00
kpdecker d4cfe90959 Allow decimal number values
Fixes #472
2014-01-17 20:42:02 -06:00
kpdecker 051618c024 Always process explicitly passed files in CLI
Fixes #689
2014-01-17 20:16:10 -06:00
kpdecker fe4880feaa Allow implicit context iteration with each
Fixes #671
2014-01-17 20:13:00 -06:00
Kevin Decker 363cb4b0fb Merge pull request #182 from wycats/partial-hash
Context different for partial
2014-01-17 16:07:02 -08:00
kpdecker e290ec24f1 Test more concrete behavior in partial hash test 2014-01-17 17:51:52 -06:00
kpdecker 45ae86a248 Implement partial hash evaluation 2014-01-17 17:51:52 -06:00
kpdecker f90981adf6 Add partial hash parser support 2014-01-17 17:51:52 -06:00
Kevin Decker 93a3725480 Merge pull request #711 from wycats/git-tag
Attempt to resolve git tag using describe
2014-01-17 15:51:30 -08:00
kpdecker 1b8c0e3c76 Use git describe to lookup tag 2014-01-17 17:43:48 -06:00
kpdecker 1b0b44a5b7 Remove apt git install 2014-01-17 17:18:11 -06:00
Kevin Decker f9e0272513 Merge pull request #704 from blakeembrey/clean-compiler
JavaScript Compiler Cleanup
2014-01-17 12:49:52 -08:00
Blake Embrey 13633e7896 Improve usefulness of extend util, properly use namespace property, update setup options to use a hash helper. 2014-01-16 21:24:26 +10:00
Kevin Decker 5659db4877 Merge pull request #634 from wycats/name-option
It would be great to have the helper name passed to `blockHelperMissing`
2014-01-15 08:42:32 -08:00
Kevin Decker cb80f46843 Merge pull request #392 from wycats/root-data
Access to root context in partials and helpers
2014-01-15 08:42:11 -08:00
kpdecker f650b0d69e Expose the initial context via @root 2014-01-15 09:41:53 -06:00
Kevin Decker d841ad5962 Merge pull request #707 from wycats/git-fix
Attempt manual install of git in travis
2014-01-15 07:13:51 -08:00
kpdecker fe9e93fc4d Attempt manual install of git in travis 2014-01-15 08:57:21 -06:00
Yehuda Katz 1a91fbda40 Merge pull request #706 from scottgonzalez/readme-with
README: Use with helper instead of relying on blockHelperMissing
2014-01-14 12:39:20 -08:00
Scott González 91aa96da7d README: Use with helper instead of relying on blockHelperMissing 2014-01-14 15:35:27 -05:00
Kevin Decker db7c9f3565 Merge pull request #703 from nbibler/patch-1
Add MIT license identification to the Ruby gemspec
2014-01-09 23:04:00 -08:00
Nathaniel Bibler 76b47e3393 Add MIT license identification to the Ruby gemspec 2014-01-08 22:05:16 -05:00
kpdecker d385e2cab6 Cleanup now unused code in jscompiler 2014-01-06 16:07:56 -06:00
kpdecker 0f328f375e Include name option for all helper calls
All helper calls will have access to `options.name` which is the first id value of the mustache operation.

As part of this the helperMissing call has been simplified to remove the indexed name in order to optimize the call. This is a breaking change.

Fixes #634
2014-01-06 16:07:56 -06:00
kpdecker c73aceb3d3 Refactor out pushParams method
Simplifies hash and param push logic
2014-01-06 14:41:23 -06:00
kpdecker daeecf825c Track stringParams mode in local state vars 2014-01-06 02:55:13 -06:00
kpdecker 607748af3b Add better logging of test compile errors 2014-01-06 02:45:10 -06:00
kpdecker 7197289364 Update release instructions 2014-01-06 02:45:09 -06:00
kpdecker fc60f16475 Fix DATA value in stringParams mode
Fixes #699
2014-01-06 02:45:09 -06:00
Kevin Decker 08fab253ef Merge pull request #700 from blakeembrey/valid-js-variable
Remove redundant conditions
2014-01-05 21:15:54 -08:00
Blake Embrey 3d9685c6a2 Remove redundant conditions. 2014-01-05 12:14:24 -05:00
kpdecker 6d996ef270 Simplify ambiguous code
Remove if conditional in favor of boolean failover.
2014-01-04 09:19:58 -06:00
Kevin Decker 55c7cbbbfa Merge pull request #697 from jwilm/handlebars-in-wild-addition
Add koa-hbs to "handlebars in the wild" list
2014-01-03 11:33:47 -08:00
Kevin Decker e9f3a169c9 Merge pull request #698 from osiris43/master
Fix for issue #673
2014-01-03 10:53:18 -08:00
Brett Bim 70b27bcef6 Fix for issue #673 - Parser task did not execute correctly on windows environments because of forward/backward slash issues 2014-01-02 11:36:01 -06:00
Joe Wilm a1df862f06 Add koa-hbs to "handlebars in the wild" list 2014-01-01 20:21:27 -08:00
kpdecker 0c6829f8af v1.3.0 2014-01-01 22:10:31 -06:00
kpdecker 2b9f3c195f Update release notes 2014-01-01 22:09:50 -06:00
kpdecker f445b96cb7 Merge branch 'master' of github.com:wycats/handlebars.js 2014-01-01 19:42:16 -06:00
kpdecker e0d0ad5027 Merge branch 'compiler-options' of github.com:blakeembrey/handlebars.js into blakeembrey-compiler-options
Conflicts:
	lib/handlebars/compiler/javascript-compiler.js
2014-01-01 19:38:31 -06:00
Kevin Decker 3f71fde155 Merge pull request #696 from nateirwin/master
Fix for reserved keyword "default"
2014-01-01 17:17:19 -08:00
kpdecker 6e4e1f8404 Include line info in compiler thrown exceptions
Fixes #636
2014-01-01 19:14:51 -06:00
kpdecker 3c85720137 Remove duplication from generated subexpressions 2014-01-01 18:59:21 -06:00
kpdecker 4713abc8f1 Add subexpression benchmark 2014-01-01 18:15:39 -06:00
kpdecker e7e94dc697 Whitespace cleanup 2013-12-31 21:20:59 -06:00
kpdecker c1a93d33e7 Use literal for data lookup 2013-12-31 21:19:00 -06:00
kpdecker cd885bf855 Add stack handling sanity checks 2013-12-31 21:18:48 -06:00
kpdecker ddfe457abf Fix stack id "leak" on replaceStack 2013-12-31 21:17:50 -06:00
kpdecker f4d337d252 Fix incorrect stack pop when replacing literals 2013-12-31 21:17:16 -06:00
kpdecker cbfec5b9e9 Set laxbreak jshint flag 2013-12-31 20:25:46 -06:00
kpdecker af3358d195 Fix multiple hash handling in subexpressions 2013-12-31 16:10:51 -06:00
Nate Irwin 641358a905 Fix for reserved keyword "default" 2013-12-31 15:09:41 -07:00
kpdecker f2df220a1f Add location tracking to sexpr 2013-12-31 13:31:47 -06:00
Kevin Decker a2ca31bb19 Merge pull request #690 from machty/subsexpr
Added support for subexpressions
2013-12-30 16:09:41 -08:00
machty b09333db79 Added support for subexpressions
Handlebars now supports subexpressions.

    {{foo (bar 3)}}

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    {{#if foo}}
      You should not see me!
    {{else}}
      You should see me!
    {{/if}}
2011-04-20 16:56:37 -07:00
236 changed files with 50567 additions and 3261 deletions
+14
View File
@@ -0,0 +1,14 @@
root = true
[*.js]
indent_size = 2
insert_final_newline = true
trim_trailing_whitespace = true
[spec/expected/*.js]
insert_final_newline = false
[*.yml]
indent_size = 2
insert_final_newline = true
trim_trailing_whitespace = true
+24
View File
@@ -0,0 +1,24 @@
.rvmrc
.DS_Store
/tmp/
*.sublime-project
*.sublime-workspace
npm-debug.log
.idea
yarn-error.log
node_modules
/handlebars-release.tgz
.nyc_output
# Generated files
lib/handlebars/compiler/parser.js
/coverage/
/dist/
/tests/integration/*/dist/
# Third-party or files that must remain unchanged
/spec/expected/
/spec/vendor
# JS-Snippets
src/*.js
+66
View File
@@ -0,0 +1,66 @@
module.exports = {
extends: ['eslint:recommended', 'plugin:compat/recommended', 'prettier'],
globals: {
self: false
},
env: {
node: true,
es6: true
},
rules: {
'no-console': 'warn',
// temporarily disabled until the violating places are fixed.
'no-func-assign': 'off',
'no-sparse-arrays': 'off',
// Best Practices //
//----------------//
'default-case': 'warn',
'guard-for-in': 'warn',
'no-alert': 'error',
'no-caller': 'error',
'no-div-regex': 'warn',
'no-eval': 'error',
'no-extend-native': 'error',
'no-extra-bind': 'error',
'no-floating-decimal': 'error',
'no-implied-eval': 'error',
'no-iterator': 'error',
'no-labels': 'error',
'no-lone-blocks': 'error',
'no-loop-func': 'error',
'no-multi-str': 'warn',
'no-global-assign': 'error',
'no-new': 'error',
'no-new-func': 'error',
'no-new-wrappers': 'error',
'no-octal-escape': 'error',
'no-process-env': 'error',
'no-proto': 'error',
'no-return-assign': 'error',
'no-script-url': 'error',
'no-self-compare': 'error',
'no-sequences': 'error',
'no-throw-literal': 'error',
'no-unused-expressions': 'error',
'no-warning-comments': 'warn',
'no-with': 'error',
radix: 'error',
// Variables //
//-----------//
'no-label-var': 'error',
'no-undef-init': 'error',
'no-use-before-define': ['error', 'nofunc'],
// ECMAScript 6 //
//--------------//
'no-var': 'error'
},
parserOptions: {
sourceType: 'module',
ecmaVersion: 6,
ecmaFeatures: {}
}
};
+6
View File
@@ -0,0 +1,6 @@
# Handlebars-template fixtures in test cases need deterministic eol
*.handlebars text eol=lf
*.hbs text eol=lf
# Lexer files as well
*.l text eol=lf
+9
View File
@@ -0,0 +1,9 @@
version: 2
updates:
- package-ecosystem: npm
directory: "/"
open-pull-requests-limit: 0
schedule:
interval: weekly
allow:
- dependency-type: production
+89
View File
@@ -0,0 +1,89 @@
name: CI
on:
push:
branches:
- master
pull_request: {}
jobs:
lint:
name: Lint
runs-on: 'ubuntu-latest'
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Setup Node.js
uses: actions/setup-node@v2
with:
node-version: '16'
- name: Install dependencies
run: npm ci
- name: Lint
run: npm run lint
test:
name: Test (Node)
runs-on: ${{ matrix.operating-system }}
strategy:
fail-fast: false
matrix:
operating-system: ['ubuntu-latest', 'windows-latest']
# https://nodejs.org/en/about/releases/
node-version: ['10', '12', '14', '16', '18', '20']
steps:
- name: Checkout
uses: actions/checkout@v2
with:
submodules: true
- name: Setup Node.js
uses: actions/setup-node@v2
with:
node-version: ${{ matrix.node-version }}
- name: Install dependencies
run: npm ci
- name: Test
run: npm run test
- name: Test (Integration)
# https://github.com/webpack/webpack/issues/14532
if: ${{ matrix.node-version != '18' && matrix.node-version != '20' }}
run: |
cd ./tests/integration/rollup-test && ./test.sh && cd -
cd ./tests/integration/webpack-babel-test && ./test.sh && cd -
cd ./tests/integration/webpack-test && ./test.sh && cd -
browser:
name: Test (Browser)
runs-on: 'ubuntu-20.04'
steps:
- name: Checkout
uses: actions/checkout@v2
with:
submodules: true
- name: Setup Node.js
uses: actions/setup-node@v2
with:
node-version: '16'
- name: Install dependencies
run: npm ci
- name: Install Playwright
run: |
npx playwright install-deps
npx playwright install
- name: Build
run: npx grunt prepare
- name: Test
run: npm run test:browser
+38
View File
@@ -0,0 +1,38 @@
name: Release
on:
workflow_dispatch:
push:
branches:
- master
tags:
- '*'
jobs:
publish-aws-s3:
name: Publish to AWS S3
runs-on: 'ubuntu-latest'
environment: 'builds.handlebarsjs.com.s3.amazonaws.com'
steps:
- name: Checkout
uses: actions/checkout@v2
with:
submodules: true
- name: Setup Node.js
uses: actions/setup-node@v2
with:
node-version: '16'
- name: Install dependencies
run: npm ci
- name: Publish
run: |
git config --global user.email "release@handlebarsjs.com"
git config --global user.name "handlebars-lang"
npm run publish:aws
env:
S3_BUCKET_NAME: "builds.handlebarsjs.com"
S3_ACCESS_KEY_ID: ${{ secrets.S3_ACCESS_KEY_ID }}
S3_SECRET_ACCESS_KEY: ${{ secrets.S3_SECRET_ACCESS_KEY }}
+17 -3
View File
@@ -1,5 +1,19 @@
dist
lib/handlebars/parser.js
vendor
.rvmrc
.DS_Store
/tmp/
*.sublime-project
*.sublime-workspace
npm-debug.log
.idea
/yarn-error.log
/yarn.lock
node_modules
/handlebars-release.tgz
.nyc_output
# Generated files
lib/handlebars/compiler/parser.js
/coverage/
/dist/
/tests/integration/*/dist/
/spec/tmp/*
+3
View File
@@ -0,0 +1,3 @@
[submodule "spec/mustache"]
path = spec/mustache
url = https://github.com/mustache/spec.git
+22
View File
@@ -0,0 +1,22 @@
.rvmrc
.DS_Store
/tmp/
*.sublime-project
*.sublime-workspace
npm-debug.log
sauce_connect.log*
.idea
yarn-error.log
node_modules
/handlebars-release.tgz
.nyc_output
# Generated files
lib/handlebars/compiler/parser.js
/coverage/
/dist/
/tests/integration/*/dist/
# Third-party or files that must remain unchanged
/spec/expected/
/spec/vendor
+202
View File
@@ -0,0 +1,202 @@
# How to Contribute
## Reporting Issues
Please see our [FAQ](https://github.com/handlebars-lang/handlebars.js/blob/master/FAQ.md) for common issues that people run into.
Should you run into other issues with the project, please don't hesitate to let us know by filing an [issue][issue]! In general we are going to ask for an example of the problem failing, which can be as simple as a jsfiddle/jsbin/etc. We've put together a jsfiddle [template][jsfiddle] to ease this. (We will keep this link up to date as new releases occur, so feel free to check back here)
Pull requests containing only failing tests demonstrating the issue are welcomed and this also helps ensure that your issue won't regress in the future once it's fixed.
Documentation issues on the [handlebarsjs.com](https://handlebarsjs.com) site should be reported on [handlebars-lang/docs](https://github.com/handlebars-lang/docs).
## Branches
- The branch `4.x` contains the currently released version. Bugfixes should be made in this branch.
- The branch `master` contains the next version. A release date is not yet specified. Maintainers
should merge the branch `4.x` into the master branch regularly.
## Pull Requests
We also accept [pull requests][pull-request]!
Generally we like to see pull requests that
- Maintain the existing code style
- Are focused on a single change (i.e. avoid large refactoring or style adjustments in untouched code if not the primary goal of the pull request)
- Have [good commit messages](http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html)
- Have tests
- Don't significantly decrease the current code coverage (see coverage/lcov-report/index.html)
## Building
To build Handlebars.js you'll need a few things installed.
- Node.js
- [Grunt](http://gruntjs.com/getting-started)
Before building, you need to make sure that the Git submodule `spec/mustache` is included (i.e. the directory `spec/mustache` should not be empty). To include it, if using Git version 1.6.5 or newer, use `git clone --recursive` rather than `git clone`. Or, if you already cloned without `--recursive`, use `git submodule update --init`.
Project dependencies may be installed via `npm install`.
To build Handlebars.js from scratch, you'll want to run `grunt`
in the root of the project. That will build Handlebars and output the
results to the dist/ folder. To re-run tests, run `grunt test` or `npm test`.
You can also run our set of benchmarks with `grunt bench`.
The `grunt dev` implements watching for tests and allows for in browser testing at `http://localhost:9999/spec/`.
If you notice any problems, please report them to the GitHub issue tracker at
[http://github.com/handlebars-lang/handlebars.js/issues](http://github.com/handlebars-lang/handlebars.js/issues).
## Running Tests
To run tests locally, first install all dependencies.
```sh
npm install
```
Clone the mustache specs into the spec/mustache folder.
```sh
cd spec
rm -r mustache
git clone https://github.com/mustache/spec.git mustache
```
From the root directory, run the tests.
```sh
npm test
```
## Linting and Formatting
Handlebars uses `eslint` to enforce best-practices and `prettier` to auto-format files.
We do linting and formatting in two phases:
- Committed files are linted and formatted in a pre-commit hook. In this stage eslint-errors are forbidden,
while warnings are allowed.
- The GitHub CI job also lints all files and checks if they are formatted correctly. In this stage, warnings
are forbidden.
You can use the following scripts to make sure that the CI job does not fail:
- **npm run lint** will run `eslint` and fail on warnings
- **npm run format** will run `prettier` on all files
- **npm run check-before-pull-request** will perform all most checks that our CI job does in its build-job, excluding the "integration-test".
- **npm run test:integration** will run integration tests (using old NodeJS versions and integrations with webpack, babel and so on)
These tests only work on a Linux-machine with `nvm` installed (for running tests in multiple versions of NodeJS).
## Releasing the latest version
Before attempting the release Handlebars, please make sure that you have the following authorizations:
- Push-access to [handlebars-lang/handlebars.js](https://github.com/handlebars-lang/handlebars.js/)
- Publishing rights on npmjs.com for the [handlebars](https://www.npmjs.com/package/handlebars) package
- Publishing rights on rubygems for the [handlebars-source](https://rubygems.org/gems/handlebars-source) package
- Push-access to the repo for legacy package managers: [components/handlebars.js](https://github.com/components/handlebars.js)
- Push-access to the production-repo of the handlebars site: [handlebars-lang/docs](https://github.com/handlebars-lang/docs)
_When releasing a previous version of Handlebars, please look into the CONTRIBUNG.md in the corresponding branch._
A full release via Docker may be completed with the following:
1. Create a `Dockerfile` in this folder for releasing
```Dockerfile
FROM node:10-slim
ENV EDITOR=vim
# Update stretch repositories
RUN sed -i -e 's/deb.debian.org/archive.debian.org/g' \
-e 's|security.debian.org|archive.debian.org/|g' \
-e '/stretch-updates/d' /etc/apt/sources.list
# Install release dependencies
RUN apt-get update
RUN apt-get install -y git vim
# Work around deprecated npm dependency install via unauthenticated git-protocol:
# https://github.com/kpdecker/generator-release/blob/87aab9b84c9f083635c3fcc822f18acce1f48736/package.json#L31
RUN git config --system url."https://github.com/".insteadOf git://github.com/
# Configure git
RUN git config --system user.email "release@handlebarsjs.com"
RUN git config --system user.name "handlebars-lang"
RUN mkdir /home/node/.config
RUN mkdir /home/node/.ssh
RUN mkdir /home/node/tmp
# Generate config for yo generator-release:
# https://github.com/kpdecker/generator-release#example
# You have to add a valid GitHub OAuth token!
RUN echo "module.exports = {\n auth: 'oauth',\n token: 'GitHub OAuth token'\n};" > /home/node/.config/generator-release
RUN chown -R node:node /home/node/.config
# Add the generated key to GitHub: https://github.com/settings/keys
RUN ssh-keygen -q -t ed25519 -N '' -f /home/node/.ssh/id_ed25519 -C "release@handlebarsjs.com"
RUN chmod 0600 /home/node/.ssh/id_ed25519*
RUN chown node:node /home/node/.ssh/id_ed25519*
```
2. Build and run the Docker image
```bash
docker build --tag handlebars:release .
docker run --rm --interactive --tty \
--volume $PWD:/app \
--workdir /app \
--user $(id -u):$(id -g) \
--env NPM_CONFIG_PREFIX=/home/node/.npm-global \
handlebars:release bash -c 'export PATH=$PATH:/home/node/.npm-global/bin; bash'
```
* Add SSH key to GitHub: `cat /home/node/.ssh/id_ed25519.pub` (https://github.com/settings/keys)
* Add GitHub API token: `vi /home/node/.config/generator-release`
* Execute the following steps:
```bash
npm ci
npm install -g yo@1 grunt@1 generator-release
npm run release
yo release
npm login
npm publish
yo release:publish components handlebars.js dist/components/
```
6. Publish Ruby `handlebars-source` gem:
```bash
docker run --rm --interactive --tty \
--volume $PWD:/app \
--workdir /app \
--user $(id -u):$(id -g) \
ruby:3.2-slim bash
```
* Execute the following steps:
```bash
cd dist/components/
gem build handlebars-source.gemspec
gem push handlebars-source-*.gem
```
### After the release
After the release, you should check that all places have really been updated. Especially verify that the `latest`-tags
in those places still point to the latest version
- [The npm-package](https://www.npmjs.com/package/handlebars) (check latest-tag)
- [The bower package](https://github.com/components/handlebars.js) (check the package.json)
- [The AWS S3 Bucket](https://s3.amazonaws.com/builds.handlebarsjs.com) (check latest-tag)
- [RubyGems](https://rubygems.org/gems/handlebars-source)
When everything is OK, the **handlebars site** needs to be updated.
Go to the master branch of the repo [handlebars-lang/docs](https://github.com/handlebars-lang/docs/tree/master)
and make a minimal change to the README. This will invoke a github-action that redeploys
the site, fetching the latest version-number from the npm-registry.
(note that the default-branch of this repo is not the master and regular changes are done
in the `handlebars-lang/docs`-repo).
[generator-release]: https://github.com/walmartlabs/generator-release
[pull-request]: https://github.com/handlebars-lang/handlebars.js/pull/new/master
[issue]: https://github.com/handlebars-lang/handlebars.js/issues/new
[jsfiddle]: https://jsfiddle.net/9D88g/180/
+62
View File
@@ -0,0 +1,62 @@
# Frequently Asked Questions
1. How can I file a bug report:
See our guidelines on [reporting issues](https://github.com/handlebars-lang/handlebars.js/blob/master/CONTRIBUTING.md#reporting-issues).
1. Why isn't my Mustache template working?
Handlebars deviates from Mustache slightly on a few behaviors. These variations are documented in our [readme](https://github.com/handlebars-lang/handlebars.js#differences-between-handlebarsjs-and-mustache).
1. Why is it slower when compiling?
The Handlebars compiler must parse the template and construct a JavaScript program which can then be run. Under some environments such as older mobile devices this can have a performance impact which can be avoided by precompiling. Generally it's recommended that precompilation and the runtime library be used on all clients.
1. Why doesn't this work with Content Security Policy restrictions?
When not using the precompiler, Handlebars generates a dynamic function for each template which can cause issues with pages that have enabled Content Policy. It's recommended that templates are precompiled or the `unsafe-eval` policy is enabled for sites that must generate dynamic templates at runtime.
1. How can I include script tags in my template?
If loading the template via an inlined `<script type="text/x-handlebars">` tag then you may need to break up the script tag with an empty comment to avoid browser parser errors:
```html
<script type="text/x-handlebars">
foo
<scr{{!}}ipt src="bar"></scr{{!}}ipt>
</script>
```
It's generally recommended that templates are served through external, precompiled, files, which do not suffer from this issue.
1. Why are my precompiled scripts throwing exceptions?
When using the precompiler, it's important that a supporting version of the Handlebars runtime be loaded on the target page. In version 1.x there were rudimentary checks to compare the version but these did not always work. This is fixed under 2.x but the version checking does not work between these two versions. If you see unexpected errors such as `undefined is not a function` or similar, please verify that the same version is being used for both the precompiler and the client. This can be checked via:
```sh
handlebars --version
```
If using the integrated precompiler and
```javascript
console.log(Handlebars.VERSION);
```
On the client side.
We include the built client libraries in the npm package for those who want to be certain that they are using the same client libraries as the compiler.
Should these match, please file an issue with us, per our [issue filing guidelines](https://github.com/handlebars-lang/handlebars.js/blob/master/CONTRIBUTING.md#reporting-issues).
1. Why doesn't IE like the `default` name in the AMD module?
Some browsers such as particular versions of IE treat `default` as a reserved word in JavaScript source files. To safely use this you need to reference this via the `Handlebars['default']` lookup method. This is an unfortunate side effect of the shims necessary to backport the Handlebars ES6 code to all current browsers.
1. How do I load the runtime library when using AMD?
There are two options for loading under AMD environments. The first is to use the `handlebars.runtime.amd.js` file. This may require a [path mapping](https://github.com/handlebars-lang/handlebars.js/blob/master/spec/amd-runtime.html#L31) as well as access via the `default` field.
The other option is to load the `handlebars.runtime.js` UMD build, which might not require path configuration and exposes the library as both the module root and the `default` field for compatibility.
If not using ES6 transpilers or accessing submodules in the build the former option should be sufficient for most use cases.
-4
View File
@@ -1,4 +0,0 @@
source "http://rubygems.org"
gem "therubyracer", ">= 0.8.0"
gem "rspec"
-20
View File
@@ -1,20 +0,0 @@
GEM
remote: http://rubygems.org/
specs:
diff-lcs (1.1.2)
rspec (2.1.0)
rspec-core (~> 2.1.0)
rspec-expectations (~> 2.1.0)
rspec-mocks (~> 2.1.0)
rspec-core (2.1.0)
rspec-expectations (2.1.0)
diff-lcs (~> 1.1.2)
rspec-mocks (2.1.0)
therubyracer (0.8.0)
PLATFORMS
ruby
DEPENDENCIES
rspec
therubyracer (>= 0.8.0)
+237
View File
@@ -0,0 +1,237 @@
/* eslint-disable no-process-env */
module.exports = function(grunt) {
grunt.initConfig({
pkg: grunt.file.readJSON('package.json'),
clean: [
'tmp',
'dist',
'lib/handlebars/compiler/parser.js',
'/tests/integration/**/node_modules'
],
copy: {
dist: {
options: {
processContent: function(content) {
return (
grunt.template.process(
'/**!\n\n @license\n <%= pkg.name %> v<%= pkg.version %>\n\n<%= grunt.file.read("LICENSE") %>\n*/\n'
) + content
);
}
},
files: [{ expand: true, cwd: 'dist/', src: ['*.js'], dest: 'dist/' }]
},
cdnjs: {
files: [
{ expand: true, cwd: 'dist/', src: ['*.js'], dest: 'dist/cdnjs' }
]
},
components: {
files: [
{
expand: true,
cwd: 'components/',
src: ['**'],
dest: 'dist/components'
},
{ expand: true, cwd: 'dist/', src: ['*.js'], dest: 'dist/components' }
]
}
},
babel: {
options: {
sourceMaps: 'inline',
loose: ['es6.modules'],
auxiliaryCommentBefore: 'istanbul ignore next'
},
amd: {
options: {
modules: 'amd'
},
files: [
{
expand: true,
cwd: 'lib/',
src: '**/!(index).js',
dest: 'dist/amd/'
}
]
},
cjs: {
options: {
modules: 'common'
},
files: [
{
cwd: 'lib/',
expand: true,
src: '**/!(index).js',
dest: 'dist/cjs/'
}
]
}
},
webpack: {
options: {
context: __dirname,
module: {
loaders: [
// the optional 'runtime' transformer tells babel to require the runtime instead of inlining it.
{
test: /\.jsx?$/,
exclude: /node_modules/,
loader:
'babel-loader?optional=runtime&loose=es6.modules&auxiliaryCommentBefore=istanbul%20ignore%20next'
}
]
},
output: {
path: 'dist/',
library: 'Handlebars',
libraryTarget: 'umd'
}
},
handlebars: {
entry: './lib/handlebars.js',
output: {
filename: 'handlebars.js'
}
},
runtime: {
entry: './lib/handlebars.runtime.js',
output: {
filename: 'handlebars.runtime.js'
}
}
},
requirejs: {
options: {
optimize: 'none',
baseUrl: 'dist/amd/'
},
dist: {
options: {
name: 'handlebars',
out: 'dist/handlebars.amd.js'
}
},
runtime: {
options: {
name: 'handlebars.runtime',
out: 'dist/handlebars.runtime.amd.js'
}
}
},
uglify: {
options: {
mangle: true,
compress: true,
preserveComments: /(?:^!|@(?:license|preserve|cc_on))/
},
dist: {
files: [
{
cwd: 'dist/',
expand: true,
src: ['handlebars*.js', '!*.min.js'],
dest: 'dist/',
rename: function(dest, src) {
return dest + src.replace(/\.js$/, '.min.js');
}
}
]
}
},
concat: {
tests: {
src: ['spec/!(require).js'],
dest: 'tmp/tests.js'
}
},
connect: {
server: {
options: {
base: '.',
hostname: '*',
port: 9999
}
}
},
shell: {
integrationTests: {
command: './tests/integration/run-integration-tests.sh'
}
},
watch: {
scripts: {
options: {
atBegin: true
},
files: ['src/*', 'lib/**/*.js', 'spec/**/*.js'],
tasks: ['on-file-change']
}
}
});
// Load tasks from npm
grunt.loadNpmTasks('grunt-contrib-clean');
grunt.loadNpmTasks('grunt-contrib-concat');
grunt.loadNpmTasks('grunt-contrib-connect');
grunt.loadNpmTasks('grunt-contrib-copy');
grunt.loadNpmTasks('grunt-contrib-requirejs');
grunt.loadNpmTasks('grunt-contrib-uglify');
grunt.loadNpmTasks('grunt-contrib-watch');
grunt.loadNpmTasks('grunt-babel');
grunt.loadNpmTasks('grunt-shell');
grunt.loadNpmTasks('grunt-webpack');
grunt.task.loadTasks('tasks');
grunt.registerTask('node', ['babel:cjs']);
grunt.registerTask('amd', ['babel:amd', 'requirejs']);
grunt.registerTask('globals', ['webpack']);
grunt.registerTask('release', 'Build final packages', [
'uglify',
'test:min',
'copy:dist',
'copy:components',
'copy:cdnjs'
]);
// Requires secret properties from .travis.yaml
grunt.registerTask('extensive-tests-and-publish-to-aws', [
'default',
'shell:integrationTests',
'metrics',
'publish-to-aws'
]);
grunt.registerTask('on-file-change', ['build', 'concat:tests', 'test']);
// === Primary tasks ===
grunt.registerTask('dev', ['clean', 'connect', 'watch']);
grunt.registerTask('default', ['clean', 'build', 'test', 'release']);
grunt.registerTask('test', ['test:bin', 'test:cov']);
grunt.registerTask('bench', ['metrics']);
grunt.registerTask('prepare', ['build', 'concat:tests']);
grunt.registerTask(
'build',
'Builds a distributable version of the current project',
['parser', 'node', 'amd', 'globals']
);
grunt.registerTask('integration-tests', [
'default',
'shell:integrationTests'
]);
};
+1 -2
View File
@@ -1,4 +1,4 @@
Copyright (C) 2011 by Yehuda Katz
Copyright (C) 2011-2019 by Yehuda Katz
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
@@ -17,4 +17,3 @@ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
+129 -182
View File
@@ -1,220 +1,167 @@
[![CI Build Status](https://github.com/handlebars-lang/handlebars.js/actions/workflows/ci.yml/badge.svg)](https://github.com/handlebars-lang/handlebars.js/actions/workflows/ci.yml)
[![jsDelivr Hits](https://data.jsdelivr.com/v1/package/npm/handlebars/badge?style=rounded)](https://www.jsdelivr.com/package/npm/handlebars)
[![npm downloads](https://badgen.net/npm/dm/handlebars)](https://www.npmjs.com/package/handlebars)
[![npm version](https://badgen.net/npm/v/handlebars)](https://www.npmjs.com/package/handlebars)
[![Bundle size](https://badgen.net/bundlephobia/minzip/handlebars?label=minified%20%2B%20gzipped)](https://bundlephobia.com/package/handlebars)
[![Install size](https://packagephobia.com/badge?p=handlebars)](https://packagephobia.com/result?p=handlebars)
Handlebars.js
=============
Handlebars.js is an extension to the [Mustache templating language](http://mustache.github.com/) created by Chris Wanstrath. Handlebars.js and Mustache are both logicless templating languages that keep the view and the code separated like we all know they should be.
Handlebars provides the power necessary to let you build **semantic templates** effectively with no frustration.
Handlebars is largely compatible with Mustache templates. In most cases it is possible to swap out Mustache with Handlebars and continue using your current templates.
Checkout the official Handlebars docs site at
[handlebarsjs.com](https://handlebarsjs.com) and try our [live demo](https://handlebarsjs.com/playground.html).
Installing
----------
Installing Handlebars is easy. Simply [download the package from GitHub](https://github.com/wycats/handlebars.js/archives/master) and add it to your web pages (you should usually use the most recent version).
See our [installation documentation](https://handlebarsjs.com/installation/).
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](https://mustache.github.io/mustache.5.html).
Once you have a template, use the Handlebars.compile method to compile the template into a function. The generated function takes a context argument, which will be used to render the template.
Once you have a template, use the `Handlebars.compile` method to compile
the template into a function. The generated function takes a context
argument, which will be used to render the template.
var source = "<p>Hello, my name is {{name}}. I am from {{hometown}}. I have " +
"{{kids/length}} kids:</p>" +
"<ul>{{#kids}}<li>{{name}} is {{age}}</li>{{/kids}}</ul>";
var template = Handlebars.compile(source);
```js
var source = "<p>Hello, my name is {{name}}. I am from {{hometown}}. I have " +
"{{kids.length}} kids:</p>" +
"<ul>{{#kids}}<li>{{name}} is {{age}}</li>{{/kids}}</ul>";
var template = Handlebars.compile(source);
var data = { "name": "Alan", "hometown": "Somewhere, TX",
"kids": [{"name": "Jimmy", "age": "12"}, {"name": "Sally", "age": "4"}]};
var result = template(data);
var data = { "name": "Alan", "hometown": "Somewhere, TX",
"kids": [{"name": "Jimmy", "age": "12"}, {"name": "Sally", "age": "4"}]};
var result = template(data);
// Would render:
// <p>Hello, my name is Alan. I am from Somewhere, TX. I have 2 kids:</p>
// <ul>
// <li>Jimmy is 12</li>
// <li>Sally is 4</li>
// </ul>
// Would render:
// <p>Hello, my name is Alan. I am from Somewhere, TX. I have 2 kids:</p>
// <ul>
// <li>Jimmy is 12</li>
// <li>Sally is 4</li>
// </ul>
```
Registering Helpers
-------------------
You can register helpers that Handlebars will use when evaluating your
template. Here's an example, which assumes that your objects have a URL
embedded in them, as well as the text for a link:
Handlebars.registerHelper('link_to', function(context) {
return "<a href='" + context.url + "'>" + context.body + "</a>";
});
var context = { posts: [{url: "/hello-world", body: "Hello World!"}] };
var source = "<ul>{{#posts}}<li>{{{link_to this}}}</li></ul>"
var template = Handlebars.compile(source);
template(context);
// Would render:
//
// <ul>
// <li><a href='/hello-world'>Hello World!</a></li>
// </ul>
Escaping
--------
By default, the `{{expression}}` syntax will escape its contents. This
helps to protect you against accidental XSS problems caused by malicious
data passed from the server as JSON.
To explicitly *not* escape the contents, use the triple-mustache
(`{{{}}}`). You have seen this used in the above example.
Differences Between Handlebars.js and Mustache
----------------------------------------------
Handlebars.js adds a couple of additional features to make writing templates easier and also changes a tiny detail of how partials work.
### Paths
Handlebars.js supports an extended expression syntax that we call paths. Paths are made up of typical expressions and / characters. Expressions allow you to not only display data from the current context, but to display data from contexts that are descendents and ancestors of the current context.
To display data from descendent contexts, use the `/` character. So, for example, if your data were structured like:
var data = {"person": { "name": "Alan" }, company: {"name": "Rad, Inc." } };
you could display the person's name from the top-level context with the following expression:
{{person/name}}
Similarly, if already traversed into the person object you could still display the company's name with an expression like `{{../company/name}}`, so:
{{#person}}{{name}} - {{../company/name}}{{/person}}
would render:
Alan - Rad, Inc.
### Strings
When calling a helper, you can pass paths or Strings as parameters. For
instance:
Handlebars.registerHelper('link_to', function(title, context) {
return "<a href='/posts" + context.id + "'>" + title + "</a>"
});
var context = { posts: [{url: "/hello-world", body: "Hello World!"}] };
var source = '<ul>{{#posts}}<li>{{{link_to "Post" this}}}</li></ul>'
var template = Handlebars.compile(source);
template(context);
// Would render:
//
// <ul>
// <li><a href='/hello-world'>Post!</a></li>
// </ul>
When you pass a String as a parameter to a helper, the literal String
gets passed to the helper function.
### Block Helpers
Handlebars.js also adds the ability to define block helpers. Block helpers are functions that can be called from anywhere in the template. Here's an example:
var source = "<ul>{{#people}}<li>{{{#link}}}{{name}}{{/link}}</li>{{/people}}</ul>";
Handlebars.registerHelper('link', function(context, fn) {
return '<a href="/people/' + this.__get__("id") + '">' + fn(this) + '</a>';
});
var template = Handlebars.compile(source);
var data = { "people": [
{ "name": "Alan", "id": 1 },
{ "name": "Yehuda", "id": 2 }
]};
template(data);
// Should render:
// <ul>
// <li><a href="/people/1">Alan</a></li>
// <li><a href="/people/2">Yehuda</a></li>
// </ul>
Whenever the block helper is called it is given two parameters, the argument that is passed to the helper, or the current context if no argument is passed and the compiled contents of the block. Inside of the block helper the value of `this` is the current context, wrapped to include a method named `__get__` that helps translate paths into values within the helpers.
### Partials
You can register additional templates as partials, which will be used by
Handlebars when it encounters a partial (`{{> partialName}}`). Partials
can either be String templates or compiled template functions. Here's an
example:
var source = "<ul>{{#people}}<li>{{> link}}</li>{{/people}}</ul>";
Handlebars.registerPartial('link', '<a href="/people/{{id}}">{{name}}</a>')
var template = Handlebars.compile(source);
var data = { "people": [
{ "name": "Alan", "id": 1 },
{ "name": "Yehuda", "id": 2 }
]};
template(data);
// Should render:
// <ul>
// <li><a href="/people/1">Alan</a></li>
// <li><a href="/people/2">Yehuda</a></li>
// </ul>
Full documentation and more examples are at [handlebarsjs.com](https://handlebarsjs.com/).
Precompiling Templates
----------------------
TODO in the rewrite. This will use RubyRacer and not node.
Handlebars allows templates to be precompiled and included as javascript code rather than the handlebars template allowing for faster startup time. Full details are located [here](https://handlebarsjs.com/installation/precompilation.html).
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.
- [Nested Paths](https://handlebarsjs.com/guide/expressions.html#path-expressions)
- [Helpers](https://handlebarsjs.com/guide/expressions.html#helpers)
- [Block Expressions](https://handlebarsjs.com/guide/block-helpers.html#basic-blocks)
- [Literal Values](https://handlebarsjs.com/guide/expressions.html#literal-segments)
- [Delimited Comments](https://handlebarsjs.com/guide/#template-comments)
Block expressions have the same syntax as mustache sections but should not be confused with one another. Sections are akin to an implicit `each` or `with` statement depending on the input data and helpers are explicit pieces of code that are free to implement whatever behavior they like. The [mustache spec](https://mustache.github.io/mustache.5.html) defines the exact behavior of sections. In the case of name conflicts, helpers are given priority.
### Compatibility
There are a few Mustache behaviors that Handlebars does not implement.
- Handlebars deviates from Mustache slightly in that it does not perform recursive lookup by default. The compile time `compat` flag must be set to enable this functionality. Users should note that there is a performance cost for enabling this flag. The exact cost varies by template, but it's recommended that performance sensitive operations should avoid this mode and instead opt for explicit path references.
- The optional Mustache-style lambdas are not supported. Instead Handlebars provides its own lambda resolution that follows the behaviors of helpers.
- Alternative delimiters are not supported.
Supported Environments
----------------------
Handlebars has been designed to work in any ECMAScript 3 environment. This includes
- Node.js
- Chrome
- Firefox
- Safari 5+
- Opera 11+
- IE 6+
Older versions and other runtimes are likely to work but have not been formally
tested. The compiler requires `JSON.stringify` to be implemented natively or via a polyfill. If using the precompiler this is not necessary.
Performance
-----------
In a rough performance test, precompiled Handlebars.js templates (in the original version of Handlebars.js) rendered in about half the time of Mustache templates. It would be a shame if it were any other way, since they were precompiled, but the difference in architecture does have some big performance advantages. Justin Marney, a.k.a. [gotascii](http://github.com/gotascii), confirmed that with an [independent test](http://sorescode.com/2010/09/12/benchmarks.html). The rewritten Handlebars (current version) is faster than the old version, and we will have some benchmarks in the near future.
Building
--------
To build handlebars, just run `rake release`, and you will get two files
in the `dist` directory. The debug version comes with stack trace
annotations for webkit browsers, but will slightly increase startup
time.
In a rough performance test, precompiled Handlebars.js templates (in
the original version of Handlebars.js) rendered in about half the
time of Mustache templates. It would be a shame if it were any other
way, since they were precompiled, but the difference in architecture
does have some big performance advantages. Justin Marney, a.k.a.
[gotascii](http://github.com/gotascii), confirmed that with an
[independent test](http://sorescode.com/2010/09/12/benchmarks.html). The
rewritten Handlebars (current version) is faster than the old version,
with many performance tests being 5 to 7 times faster than the Mustache equivalent.
Upgrading
---------
When upgrading from the Handlebars 0.9 series, be aware that the
signature for passing custom helpers or partials to templates has
changed.
Instead of:
template(context, helpers, partials, [data])
Use:
template(context, {helpers: helpers, partials: partials, data: data}
See [release-notes.md](https://github.com/handlebars-lang/handlebars.js/blob/master/release-notes.md) for upgrade notes.
Known Issues
------------
* Handlebars.js can be cryptic when there's an error while rendering.
Handlebars Contrib
------------------
Alan Johnson, a.k.a. commondream, keeps a repository of useful helpers in the [handlebars-contrib](http://github.com/commondream/handlebars-contrib) repository. Feel free to copy those, or add some of your own.
See [FAQ.md](https://github.com/handlebars-lang/handlebars.js/blob/master/FAQ.md) for known issues and common pitfalls.
Handlebars in the Wild
-----------------
* Don Park wrote an Express.js view engine adapter for Handlebars.js called [hbs](http://github.com/donpark/hbs)
* [sammy.js](http://github.com/quirkey/sammy) by Aaron Quint, a.k.a. quirkey, supports Handlebars.js as one of its template plugins.
----------------------
Helping Out
-----------
If you notice any problems, please report them to the GitHub issue tracker at [http://github.com/wycats/handlebars.js/issues](http://github.com/wycats/handlebars.js/issues). Feel free to contact commondream or wycats through GitHub with any other questions or feature requests. To submit changes fork the project and send a pull request.
* [Assemble](http://assemble.io), by [@jonschlinkert](https://github.com/jonschlinkert)
and [@doowb](https://github.com/doowb), is a static site generator that uses Handlebars.js
as its template engine.
* [Cory](https://github.com/leo/cory), by [@leo](https://github.com/leo), is another tiny static site generator
* [CoSchedule](http://coschedule.com) An editorial calendar for WordPress that uses Handlebars.js
* [dashbars](https://github.com/pismute/dashbars) A modern helper library for Handlebars.js.
* [Ember.js](http://www.emberjs.com) makes Handlebars.js the primary way to
structure your views, also with automatic data binding support.
* [Ghost](https://ghost.org/) Just a blogging platform.
* [handlebars_assets](http://github.com/leshill/handlebars_assets): A Rails Asset Pipeline gem
from Les Hill (@leshill).
* [handlebars-helpers](https://github.com/assemble/handlebars-helpers) is an extensive library
with 100+ handlebars helpers.
* [handlebars-layouts](https://github.com/shannonmoeller/handlebars-layouts) is a set of helpers which implement extendible and embeddable layout blocks as seen in other popular templating languages.
* [hbs](http://github.com/donpark/hbs): An Express.js view engine adapter for Handlebars.js,
from Don Park.
* [koa-hbs](https://github.com/jwilm/koa-hbs): [koa](https://github.com/koajs/koa) generator based
renderer for Handlebars.js.
* [jblotus](http://github.com/jblotus) created [http://tryhandlebarsjs.com](http://tryhandlebarsjs.com)
for anyone who would like to try out Handlebars.js in their browser.
* [jQuery plugin](http://71104.github.io/jquery-handlebars/): allows you to use
Handlebars.js with [jQuery](http://jquery.com/).
* [Lumbar](http://walmartlabs.github.io/lumbar) provides easy module-based template management for
handlebars projects.
* [Marionette.Handlebars](https://github.com/hashchange/marionette.handlebars) adds support for Handlebars and Mustache templates to Marionette.
* [sammy.js](http://github.com/quirkey/sammy) by Aaron Quint, a.k.a. quirkey,
supports Handlebars.js as one of its template plugins.
* [SproutCore](http://www.sproutcore.com) uses Handlebars.js as its main
templating engine, extending it with automatic data binding support.
* [YUI](http://yuilibrary.com/yui/docs/handlebars/) implements a port of handlebars
* [Swag](https://github.com/elving/swag) by [@elving](https://github.com/elving) is a growing collection of helpers for handlebars.js. Give your handlebars.js templates some swag son!
* [DOMBars](https://github.com/blakeembrey/dombars) is a DOM-based templating engine built on the Handlebars parser and runtime **DEPRECATED**
* [promised-handlebars](https://github.com/nknapp/promised-handlebars) is a wrapper for Handlebars that allows helpers to return Promises.
* [just-handlebars-helpers](https://github.com/leapfrogtechnology/just-handlebars-helpers) A fully tested lightweight package with common Handlebars helpers.
External Resources
------------------
* [Gist about Synchronous and asynchronous loading of external handlebars templates](https://gist.github.com/2287070)
Have a project using Handlebars? Send us a [pull request][pull-request]!
License
-------
Handlebars.js is released under the MIT license.
[pull-request]: https://github.com/handlebars-lang/handlebars.js/pull/new/master
-107
View File
@@ -1,107 +0,0 @@
require "rubygems"
require "bundler/setup"
file "lib/handlebars/parser.js" => ["src/handlebars.yy","src/handlebars.l"] do
if ENV['PATH'].split(':').any? {|folder| File.exists?(folder+'/jison')}
system "jison src/handlebars.yy src/handlebars.l"
File.open("lib/handlebars/parser.js", "w") do |file|
file.puts File.read("handlebars.js") + ";"
end
sh "rm handlebars.js"
else
puts "Jison is not installed. Try running `npm install jison`."
end
end
task :compile => "lib/handlebars/parser.js"
desc "run the spec suite"
task :spec => [:release] do
system "rspec -cfs spec"
end
task :default => [:compile, :spec]
def remove_exports(string)
match = string.match(%r{^// BEGIN\(BROWSER\)\n(.*)\n^// END\(BROWSER\)}m)
match ? match[1] : string
end
minimal_deps = %w(parser base ast visitor utils compiler).map do |file|
"lib/handlebars/#{file}.js"
end
debug_deps = %w(parser base ast visitor printer utils compiler debug).map do |file|
"lib/handlebars/#{file}.js"
end
directory "dist"
minimal_deps.unshift "dist"
debug_deps.unshift "dist"
def build_for_task(task)
FileUtils.rm_rf("dist/*") if File.directory?("dist")
FileUtils.mkdir_p("dist")
contents = []
task.prerequisites.each do |filename|
next if filename == "dist"
contents << "// #{filename}\n" + remove_exports(File.read(filename)) + ";"
end
File.open(task.name, "w") do |file|
file.puts contents.join("\n")
end
end
file "dist/handlebars.js" => minimal_deps do |task|
build_for_task(task)
end
file "dist/handlebars.debug.js" => debug_deps do |task|
build_for_task(task)
end
task :build => [:compile, "dist/handlebars.js"]
task :debug => [:compile, "dist/handlebars.debug.js"]
desc "build the build and debug versions of handlebars"
task :release => [:build, :debug]
directory "vendor"
desc "benchmark against dust.js and mustache.js"
task :bench => "vendor" do
require "open-uri"
File.open("vendor/mustache.js", "w") do |file|
file.puts open("https://github.com/janl/mustache.js/raw/master/mustache.js").read
file.puts "module.exports = Mustache;"
end
File.open("vendor/benchmark.js", "w") do |file|
file.puts open("https://github.com/mathiasbynens/benchmark.js/raw/master/benchmark.js").read
end
if File.directory?("vendor/dustjs")
system "cd vendor/dustjs && git pull"
else
system "git clone git://github.com/akdubya/dustjs.git vendor/dustjs"
end
if File.directory?("vendor/coffee")
system "cd vendor/coffee && git pull"
else
system "git clone git://github.com/jashkenas/coffee-script.git vendor/coffee"
end
if File.directory?("vendor/eco")
system "cd vendor/eco && git pull"
else
system "git clone git://github.com/sstephenson/eco.git vendor/eco"
end
system "node bench/handlebars.js"
end
-147
View File
@@ -1,147 +0,0 @@
var Benchmark = require("benchmark");
var BenchWarmer = function(names) {
this.benchmarks = [];
this.currentBenches = [];
this.names = [];
this.errors = {};
};
var print = require("sys").print;
BenchWarmer.prototype = {
winners: function(benches) {
var result = Benchmark.filter(benches, function(bench) { return bench.cycles; });
if (result.length > 1) {
result.sort(function(a, b) { return b.compare(a); });
first = result[0];
last = result[result.length - 1];
var winners = [];
Benchmark.each(result, function(bench) {
if (bench.compare(first) === 0) {
winners.push(bench);
}
});
return winners;
} else {
return result;
}
},
suite: function(suite, fn) {
this.suiteName = suite;
this.first = true;
var self = this;
fn(function(name, benchFn) {
self.push(name, benchFn);
});
},
push: function(name, fn) {
if(this.names.indexOf(name) == -1) {
this.names.push(name);
}
var first = this.first, suiteName = this.suiteName, self = this;
this.first = false;
var bench = new Benchmark(function() {
fn();
}, {
name: this.suiteName + ": " + name,
onComplete: function() {
if(first) { self.startLine(suiteName); }
self.writeBench(bench);
self.currentBenches.push(bench);
}, onError: function() {
self.errors[this.name] = this;
}
});
this.benchmarks.push(bench);
},
bench: function() {
var benchSize = 0, names = this.names, self = this, i, l;
for(i=0, l=names.length; i<l; i++) {
var name = names[i];
if(benchSize < name.length) { benchSize = name.length; }
}
this.nameSize = benchSize + 2;
this.benchSize = 20;
var horSize = 0;
this.startLine("ops/msec");
horSize = horSize + "ops/msec ".length;
for(i=0, l=names.length; i<l; i++) {
print(names[i] + new Array(this.benchSize - names[i].length + 1).join(" "));
horSize = horSize + this.benchSize;
}
print("WINNER(S)");
horSize = horSize + "WINNER(S)".length;
print("\n" + new Array(horSize + 1).join("-"));
Benchmark.invoke(this.benchmarks, {
name: "run",
onComplete: function() {
var errors = false, prop, bench;
for(prop in self.errors) { if(self.errors.hasOwnProperty(prop)) { errors = true; break; } }
if(errors) {
print("\n\nErrors:\n");
for(prop in self.errors) {
if(self.errors.hasOwnProperty(prop)) {
bench = self.errors[prop];
print("\n" + bench.name + ":\n");
print(bench.error.message);
if(bench.error.stack) {
print(bench.error.stack.join("\n"));
}
print("\n");
}
}
}
}
});
},
startLine: function(name) {
var winners = Benchmark.map(this.winners(this.currentBenches), function(bench) {
return bench.name.split(": ")[1];
});
this.currentBenches = [];
print(winners.join(", "));
print("\n");
var padding = this.nameSize - name.length + 1;
name = name + new Array(padding).join(" ");
print(name);
},
writeBench: function(bench) {
var out;
if(!bench.error) {
var count = bench.hz,
moe = count * bench.stats.RME / 100;
out = Math.round(count / 1000) + " ±" + Math.round(moe / 1000) + " (" + bench.cycles + ")";
} else {
out = "E";
}
var padding = this.benchSize - out.length + 1;
out = out + new Array(padding).join(" ");
print(out);
}
};
module.exports = BenchWarmer;
-166
View File
@@ -1,166 +0,0 @@
require.paths.push("lib");
require.paths.push("vendor");
require.paths.push("vendor/dustjs/lib");
require.paths.push("vendor/coffee/lib");
require.paths.push("vendor/eco/lib");
var BenchWarmer = require("./benchwarmer");
Handlebars = require("handlebars");
var dust = require("dust");
var Mustache = require("mustache");
var ecoExports = require("eco");
eco = function(str) {
var module = {};
var template = new Function("module", ecoExports.compile(str));
template(module);
return module.exports;
}
var benchDetails = {
string: {
context: {},
handlebars: "Hello world",
dust: "Hello world",
mustache: "Hello world",
eco: "Hello world"
},
variables: {
context: {name: "Mick", count: 30},
handlebars: "Hello {{name}}! You have {{count}} new messages.",
dust: "Hello {name}! You have {count} new messages.",
mustache: "Hello {{name}}! You have {{count}} new messages.",
eco: "Hello <%= @name %>! You have <%= @count %> new messages."
},
object: {
context: { person: { name: "Larry", age: 45 } },
handlebars: "{{#with person}}{{name}}{{age}}{{/with}}",
dust: "{#person}{name}{age}{/person}",
mustache: "{{#person}}{{name}}{{age}}{{/person}}"
},
array: {
context: { names: [{name: "Moe"}, {name: "Larry"}, {name: "Curly"}, {name: "Shemp"}] },
handlebars: "{{#each names}}{{name}}{{/each}}",
dust: "{#names}{name}{/names}",
mustache: "{{#names}}{{name}}{{/names}}",
eco: "<% for item in @names: %><%= item.name %><% end %>"
},
partial: {
context: { peeps: [{name: "Moe", count: 15}, {name: "Larry", count: 5}, {name: "Curly", count: 1}] },
partials: {
mustache: { variables: "Hello {{name}}! You have {{count}} new messages." },
handlebars: { variables: "Hello {{name}}! You have {{count}} new messages." }
},
handlebars: "{{#each peeps}}{{>variables}}{{/each}}",
dust: "{#peeps}{>variables/}{/peeps}",
mustache: "{{#peeps}}{{>variables}}{{/peeps}}"
},
recursion: {
context: { name: '1', kids: [{ name: '1.1', kids: [{name: '1.1.1', kids: []}] }] },
partials: {
mustache: { recursion: "{{name}}{{#kids}}{{>recursion}}{{/kids}}" },
handlebars: { recursion: "{{name}}{{#each kids}}{{>recursion}}{{/each}}" }
},
handlebars: "{{name}}{{#each kids}}{{>recursion}}{{/each}}",
dust: "{name}{#kids}{>recursion:./}{/kids}",
mustache: "{{name}}{{#kids}}{{>recursion}}{{/kids}}"
},
complex: {
handlebars: "<h1>{{header}}</h1>{{#if items}}<ul>{{#each items}}{{#if current}}" +
"<li><strong>{{name}}</strong></li>{{^}}" +
"<li><a href=\"{{url}}\">{{name}}</a></li>{{/if}}" +
"{{/each}}</ul>{{^}}<p>The list is empty.</p>{{/if}}",
dust: "<h1>{header}</h1>\n" +
"{?items}\n" +
" <ul>\n" +
" {#items}\n" +
" {#current}\n" +
" <li><strong>{name}</strong></li>\n" +
" {:else}\n" +
" <li><a href=\"{url}\">{name}</a></li>\n" +
" {/current}\n" +
" {/items}\n" +
" </ul>\n" +
"{:else}\n" +
" <p>The list is empty.</p>\n" +
"{/items}",
context: {
header: function() {
return "Colors";
},
items: [
{name: "red", current: true, url: "#Red"},
{name: "green", current: false, url: "#Green"},
{name: "blue", current: false, url: "#Blue"}
]
}
}
};
handlebarsTemplates = {};
ecoTemplates = {};
var warmer = new BenchWarmer();
var makeSuite = function(name) {
warmer.suite(name, function(bench) {
var templateName = name;
var details = benchDetails[templateName];
var mustachePartials = details.partials && details.partials.mustache;
var mustacheSource = details.mustache;
var context = details.context;
var error = function() { throw new Error("EWOT"); };
//bench("dust", function() {
//dust.render(templateName, context, function(err, out) { });
//});
bench("handlebars", function() {
handlebarsTemplates[templateName](context);
});
//if(ecoTemplates[templateName]) {
//bench("eco", function() {
//ecoTemplates[templateName](context);
//});
//} else {
//bench("eco", error);
//}
//if(mustacheSource) {
//bench("mustache", function() {
//Mustache.to_html(mustacheSource, context, mustachePartials);
//});
//} else {
//bench("mustache", error);
//}
});
}
for(var name in benchDetails) {
if(benchDetails.hasOwnProperty(name)) {
dust.loadSource(dust.compile(benchDetails[name].dust, name));
handlebarsTemplates[name] = Handlebars.compile(benchDetails[name].handlebars);
if(benchDetails[name].eco) { ecoTemplates[name] = eco(benchDetails[name].eco); }
var partials = benchDetails[name].partials;
if(partials) {
for(var partialName in partials.handlebars) {
if(partials.handlebars.hasOwnProperty(partialName)) {
Handlebars.registerPartial(partialName, partials.handlebars[partialName]);
}
}
}
makeSuite(name);
}
}
warmer.bench();
+6
View File
@@ -0,0 +1,6 @@
module.exports = {
rules: {
'no-console': 0,
'no-var': 0
}
};
Executable
+176
View File
@@ -0,0 +1,176 @@
#!/usr/bin/env node
var argv = parseArgs({
'f': {
'type': 'string',
'description': 'Output File',
'alias': 'output'
},
'map': {
'type': 'string',
'description': 'Source Map File'
},
'a': {
'type': 'boolean',
'description': 'Exports amd style (require.js)',
'alias': 'amd'
},
'c': {
'type': 'string',
'description': 'Exports CommonJS style, path to Handlebars module',
'alias': 'commonjs',
'default': null
},
'h': {
'type': 'string',
'description': 'Path to handlebar.js (only valid for amd-style)',
'alias': 'handlebarPath',
'default': ''
},
'k': {
'type': 'string',
'description': 'Known helpers',
'alias': 'known'
},
'o': {
'type': 'boolean',
'description': 'Known helpers only',
'alias': 'knownOnly'
},
'm': {
'type': 'boolean',
'description': 'Minimize output',
'alias': 'min'
},
'n': {
'type': 'string',
'description': 'Template namespace',
'alias': 'namespace',
'default': 'Handlebars.templates'
},
's': {
'type': 'boolean',
'description': 'Output template function only.',
'alias': 'simple'
},
'N': {
'type': 'string',
'description': 'Name of passed string templates. Optional if running in a simple mode. Required when operating on multiple templates.',
'alias': 'name'
},
'i': {
'type': 'string',
'description': 'Generates a template from the passed CLI argument.\n"-" is treated as a special value and causes stdin to be read for the template value.',
'alias': 'string'
},
'r': {
'type': 'string',
'description': 'Template root. Base value that will be stripped from template names.',
'alias': 'root'
},
'p': {
'type': 'boolean',
'description': 'Compiling a partial template',
'alias': 'partial'
},
'd': {
'type': 'boolean',
'description': 'Include data when compiling',
'alias': 'data'
},
'e': {
'type': 'string',
'description': 'Template extension.',
'alias': 'extension',
'default': 'handlebars'
},
'b': {
'type': 'boolean',
'description': 'Removes the BOM (Byte Order Mark) from the beginning of the templates.',
'alias': 'bom'
},
'v': {
'type': 'boolean',
'description': 'Prints the current compiler version',
'alias': 'version'
},
'help': {
'type': 'boolean',
'description': 'Outputs this message'
}
});
argv.files = argv._;
delete argv._;
var Precompiler = require('../dist/cjs/precompiler');
Precompiler.loadTemplates(argv, function(err, opts) {
if (err) {
throw err;
}
if (opts.help || (!opts.templates.length && !opts.version)) {
printUsage(argv._spec, 120);
} else {
Precompiler.cli(opts);
}
});
function pad(n) {
var str = '';
while (str.length < n) {
str += ' ';
}
return str;
}
function parseArgs(spec) {
var opts = { alias: {}, boolean: [], default: {}, string: [] };
Object.keys(spec).forEach(function (arg) {
var opt = spec[arg];
opts[opt.type].push(arg);
if ('alias' in opt) opts.alias[arg] = opt.alias;
if ('default' in opt) opts.default[arg] = opt.default;
});
var argv = require('minimist')(process.argv.slice(2), opts);
argv._spec = spec;
return argv;
}
function printUsage(spec, wrap) {
var wordwrap = require('wordwrap');
console.log('Precompile handlebar templates.');
console.log('Usage: handlebars [template|directory]...');
var opts = [];
var width = 0;
Object.keys(spec).forEach(function (arg) {
var opt = spec[arg];
var name = (arg.length === 1 ? '-' : '--') + arg;
if ('alias' in opt) name += ', --' + opt.alias;
var meta = '[' + opt.type + ']';
if ('default' in opt) meta += ' [default: ' + JSON.stringify(opt.default) + ']';
opts.push({ name: name, desc: opt.description, meta: meta });
if (name.length > width) width = name.length;
});
console.log('Options:');
opts.forEach(function (opt) {
var desc = wordwrap(width + 4, wrap + 1)(opt.desc);
console.log(' %s%s%s%s%s',
opt.name,
pad(width - opt.name.length + 2),
desc.slice(width + 4),
pad(wrap - opt.meta.length - desc.split(/\n/).pop().length),
opt.meta
);
});
}
+7
View File
@@ -0,0 +1,7 @@
{
"name": "handlebars",
"version": "4.7.8",
"main": "handlebars.js",
"license": "MIT",
"dependencies": {}
}
+9
View File
@@ -0,0 +1,9 @@
{
"name": "handlebars",
"repo": "components/handlebars.js",
"version": "1.0.0",
"main": "handlebars.js",
"scripts": [
"handlebars.js"
]
}
+35
View File
@@ -0,0 +1,35 @@
{
"name": "components/handlebars.js",
"description": "Handlebars.js and Mustache are both logicless templating languages that keep the view and the code separated like we all know they should be.",
"homepage": "http://handlebarsjs.com",
"license": "MIT",
"type": "component",
"keywords": [
"handlebars",
"mustache",
"html"
],
"authors": [
{
"name": "Chris Wanstrath",
"homepage": "http://chriswanstrath.com"
}
],
"require": {
"robloach/component-installer": "*"
},
"extra": {
"component": {
"name": "handlebars",
"scripts": [
"handlebars.js"
],
"files": [
"handlebars.runtime.js"
],
"shim": {
"exports": "Handlebars"
}
}
}
}
+22
View File
@@ -0,0 +1,22 @@
# -*- encoding: utf-8 -*-
require 'json'
package = JSON.parse(File.read('bower.json'))
Gem::Specification.new do |gem|
gem.name = "handlebars-source"
gem.authors = ["Yehuda Katz"]
gem.email = ["wycats@gmail.com"]
gem.date = Time.now.strftime("%Y-%m-%d")
gem.description = %q{Handlebars.js source code wrapper for (pre)compilation gems.}
gem.summary = %q{Handlebars.js source code wrapper}
gem.homepage = "https://github.com/handlebars-lang/handlebars.js/"
gem.version = package["version"].sub "-", "."
gem.license = "MIT"
gem.files = [
'handlebars.js',
'handlebars.runtime.js',
'lib/handlebars/source.rb'
]
end
+17
View File
@@ -0,0 +1,17 @@
<?xml version="1.0"?>
<package>
<metadata>
<id>handlebars.js</id>
<version>4.7.8</version>
<authors>handlebars.js Authors</authors>
<licenseUrl>https://github.com/handlebars-lang/handlebars.js/blob/master/LICENSE</licenseUrl>
<projectUrl>https://github.com/handlebars-lang/handlebars.js/</projectUrl>
<requireLicenseAcceptance>false</requireLicenseAcceptance>
<description>Extension of the Mustache logicless template language</description>
<releaseNotes></releaseNotes>
<tags>handlebars mustache template html</tags>
</metadata>
<files>
<file src="handlebars.js" target="Content\Scripts" />
</files>
</package>
+11
View File
@@ -0,0 +1,11 @@
module Handlebars
module Source
def self.bundled_path
File.expand_path("../../../handlebars.js", __FILE__)
end
def self.runtime_bundled_path
File.expand_path("../../../handlebars.runtime.js", __FILE__)
end
end
end
+20
View File
@@ -0,0 +1,20 @@
{
"name": "handlebars",
"version": "4.7.8",
"license": "MIT",
"jspm": {
"main": "handlebars",
"shim": {
"handlebars": {
"exports": "Handlebars"
}
},
"files": [
"handlebars.js",
"handlebars.runtime.js"
],
"buildConfig": {
"minify": true
}
}
}
+366
View File
@@ -0,0 +1,366 @@
# Handlebars Compiler APIs
There are a number of formal APIs that tool implementors may interact with.
## AST
Other tools may interact with the formal AST as defined below. Any JSON structure matching this pattern may be used and passed into the `compile` and `precompile` methods in the same way as the text for a template.
AST structures may be generated either with the `Handlebars.parse` method and then manipulated, via the `Handlebars.AST` objects of the same name, or constructed manually as a generic JavaScript object matching the structure defined below.
```javascript
var ast = Handlebars.parse(myTemplate);
// Modify ast
Handlebars.precompile(ast);
```
### Parsing
There are two primary APIs that are used to parse an existing template into the AST:
#### parseWithoutProcessing
`Handlebars.parseWithoutProcessing` is the primary mechanism to turn a raw template string into the Handlebars AST described in this document. No processing is done on the resulting AST which makes this ideal for codemod (for source to source transformation) tooling.
Example:
```js
let ast = Handlebars.parseWithoutProcessing(myTemplate);
```
#### parse
`Handlebars.parse` will parse the template with `parseWithoutProcessing` (see above) then it will update the AST to strip extraneous whitespace. The whitespace stripping functionality handles two distinct situations:
* Removes whitespace around dynamic statements that are on a line by themselves (aka "stand alone")
* Applies "whitespace control" characters (i.e. `~`) by truncating the `ContentStatement` `value` property appropriately (e.g. `\n\n{{~foo}}` would have a `ContentStatement` with a `value` of `''`)
`Handlebars.parse` is used internally by `Handlebars.precompile` and `Handlebars.compile`.
Example:
```js
let ast = Handlebars.parse(myTemplate);
```
### Basic
```java
interface Node {
type: string;
loc: SourceLocation | null;
}
interface SourceLocation {
source: string | null;
start: Position;
end: Position;
}
interface Position {
line: uint >= 1;
column: uint >= 0;
}
```
### Programs
```java
interface Program <: Node {
type: "Program";
body: [ Statement ];
blockParams: [ string ];
}
```
### Statements
```java
interface Statement <: Node { }
interface MustacheStatement <: Statement {
type: "MustacheStatement";
path: PathExpression | Literal;
params: [ Expression ];
hash: Hash;
escaped: boolean;
strip: StripFlags | null;
}
interface BlockStatement <: Statement {
type: "BlockStatement";
path: PathExpression | Literal;
params: [ Expression ];
hash: Hash;
program: Program | null;
inverse: Program | null;
openStrip: StripFlags | null;
inverseStrip: StripFlags | null;
closeStrip: StripFlags | null;
}
interface PartialStatement <: Statement {
type: "PartialStatement";
name: PathExpression | SubExpression;
params: [ Expression ];
hash: Hash;
indent: string;
strip: StripFlags | null;
}
interface PartialBlockStatement <: Statement {
type: "PartialBlockStatement";
name: PathExpression | SubExpression;
params: [ Expression ];
hash: Hash;
program: Program | null;
indent: string;
openStrip: StripFlags | null;
closeStrip: StripFlags | null;
}
```
`name` will be a `SubExpression` when tied to a dynamic partial, i.e. `{{> (foo) }}`, otherwise this is a path or literal whose `original` value is used to lookup the desired partial.
```java
interface ContentStatement <: Statement {
type: "ContentStatement";
value: string;
original: string;
}
interface CommentStatement <: Statement {
type: "CommentStatement";
value: string;
strip: StripFlags | null;
}
```
```java
interface Decorator <: Statement {
type: "Decorator";
path: PathExpression | Literal;
params: [ Expression ];
hash: Hash;
strip: StripFlags | null;
}
interface DecoratorBlock <: Statement {
type: "DecoratorBlock";
path: PathExpression | Literal;
params: [ Expression ];
hash: Hash;
program: Program | null;
openStrip: StripFlags | null;
closeStrip: StripFlags | null;
}
```
Decorator paths only utilize the `path.original` value and as a consequence do not support depthed evaluation.
### Expressions
```java
interface Expression <: Node { }
```
##### SubExpressions
```java
interface SubExpression <: Expression {
type: "SubExpression";
path: PathExpression;
params: [ Expression ];
hash: Hash;
}
```
##### Paths
```java
interface PathExpression <: Expression {
type: "PathExpression";
data: boolean;
depth: uint >= 0;
parts: [ string ];
original: string;
}
```
- `data` is true when the given expression is a `@data` reference.
- `depth` is an integer representation of which context the expression references. `0` represents the current context, `1` would be `../`, etc.
- `parts` is an array of the names in the path. `foo.bar` would be `['foo', 'bar']`. Scope references, `.`, `..`, and `this` should be omitted from this array.
- `original` is the path as entered by the user. Separator and scope references are left untouched.
##### Literals
```java
interface Literal <: Expression { }
interface StringLiteral <: Literal {
type: "StringLiteral";
value: string;
original: string;
}
interface BooleanLiteral <: Literal {
type: "BooleanLiteral";
value: boolean;
original: boolean;
}
interface NumberLiteral <: Literal {
type: "NumberLiteral";
value: number;
original: number;
}
interface UndefinedLiteral <: Literal {
type: "UndefinedLiteral";
}
interface NullLiteral <: Literal {
type: "NullLiteral";
}
```
### Miscellaneous
```java
interface Hash <: Node {
type: "Hash";
pairs: [ HashPair ];
}
interface HashPair <: Node {
type: "HashPair";
key: string;
value: Expression;
}
interface StripFlags {
open: boolean;
close: boolean;
}
```
`StripFlags` are used to signify whitespace control character that may have been entered on a given statement.
## AST Visitor
`Handlebars.Visitor` is available as a base class for general interaction with AST structures. This will by default traverse the entire tree and individual methods may be overridden to provide specific responses to particular nodes.
Recording all referenced partial names:
```javascript
var Visitor = Handlebars.Visitor;
function ImportScanner() {
this.partials = [];
}
ImportScanner.prototype = new Visitor();
ImportScanner.prototype.PartialStatement = function(partial) {
this.partials.push({request: partial.name.original});
Visitor.prototype.PartialStatement.call(this, partial);
};
var scanner = new ImportScanner();
scanner.accept(ast);
```
The current node's ancestors will be maintained in the `parents` array, with the most recent parent listed first.
The visitor may also be configured to operate in mutation mode by setting the `mutation` field to true. When in this mode, handler methods may return any valid AST node and it will replace the one they are currently operating on. Returning `false` will remove the given value (if valid) and returning `undefined` will leave the node in tact. This return structure only apply to mutation mode and non-mutation mode visitors are free to return whatever values they wish.
Implementors that may need to support mutation mode are encouraged to utilize the `acceptKey`, `acceptRequired` and `acceptArray` helpers which provide the conditional overwrite behavior as well as implement sanity checks where pertinent.
## JavaScript Compiler
The `Handlebars.JavaScriptCompiler` object has a number of methods that may be customized to alter the output of the compiler:
- `nameLookup(parent, name, type)`
Used to generate the code to resolve a give path component.
- `parent` is the existing code in the path resolution
- `name` is the current path component
- `type` is the type of name being evaluated. May be one of `context`, `data`, `helper`, `decorator`, or `partial`.
Note that this does not impact dynamic partials, which implementors need to be aware of. Overriding `VM.resolvePartial` may be required to support dynamic cases.
- `depthedLookup(name)`
Used to generate code that resolves parameters within any context in the stack. Is only used in `compat` mode.
- `compilerInfo()`
Allows for custom compiler flags used in the runtime version checking logic.
- `appendToBuffer(source, location, explicit)`
Allows for code buffer emitting code. Defaults behavior is string concatenation.
- `source` is the source code whose result is to be appending
- `location` is the location of the source in the source map.
- `explicit` is a flag signaling that the emit operation must occur, vs. the lazy evaled options otherwise.
- `initializeBuffer()`
Allows for buffers other than the default string buffer to be used. Generally needs to be paired with a custom `appendToBuffer` implementation.
### Example for the compiler api.
This example changes all lookups of properties are performed by a helper (`lookupLowerCase`) which looks for `test` if `{{Test}}` occurs in the template. This is just to illustrate how compiler behavior can be change.
There is also [a jsfiddle with this code](https://jsfiddle.net/9D88g/162/) if you want to play around with it.
```javascript
function MyCompiler() {
Handlebars.JavaScriptCompiler.apply(this, arguments);
}
MyCompiler.prototype = new Handlebars.JavaScriptCompiler();
// Use this compile to compile BlockStatment-Blocks
MyCompiler.prototype.compiler = MyCompiler
MyCompiler.prototype.nameLookup = function(parent, name, type) {
if (type === 'context') {
return this.source.functionCall('helpers.lookupLowerCase', '', [parent, JSON.stringify(name)])
} else {
return Handlebars.JavaScriptCompiler.prototype.nameLookup.call(this, parent, name, type);
}
}
var env = Handlebars.create();
env.registerHelper('lookupLowerCase', function(parent, name) {
return parent[name.toLowerCase()]
})
env.JavaScriptCompiler = MyCompiler;
var template = env.compile('{{#each Test}} ({{Value}}) {{/each}}');
console.log(template({
test: [
{value: 'a'},
{value: 'b'},
{value: 'c'}
]
}));
```
+19
View File
@@ -0,0 +1,19 @@
# Decorators
Decorators allow for blocks to be annotated with metadata or wrapped in functionality prior to execution of the block. This may be used to communicate with the containing helper or to setup a particular state in the system prior to running the block.
Decorators are registered through similar methods as helpers, `registerDecorators` and `unregisterDecorators`. These can then be referenced via the friendly name in the template using the `{{* decorator}}` and `{{#* decorator}}{/decorator}}` syntaxes. These syntaxes are derivatives of the normal mustache syntax and as such have all of the same argument and whitespace behaviors.
Decorators are executed when the block program is instantiated and are passed `(program, props, container, context, data, blockParams, depths)`.
- `program`: The block to wrap
- `props`: Object used to set metadata on the final function. Any values set on this object will be set on the function, regardless of if the original function is replaced or not. Metadata should be applied using this object as values applied to `program` may be masked by subsequent decorators that may wrap `program`.
- `container`: The current runtime container
- `context`: The current context. Since the decorator is run before the block that contains it, this is the parent context.
- `data`: The current `@data` values
- `blockParams`: The current block parameters stack
- `depths`: The current context stack
Decorators may set values on `props` or return a modified function that wraps `program` in particular behaviors. If the decorator returns nothing, then `program` is left unaltered.
The [inline partial](https://github.com/handlebars-lang/handlebars.js/blob/master/lib/handlebars/decorators/inline.js) implementation provides an example of decorators being used for both metadata and wrapping behaviors.
+8
View File
@@ -0,0 +1,8 @@
module.exports = {
env: {
// Handlebars should not use node or browser-specific APIs
'shared-node-browser': true,
node: false,
browser: false
}
};
+40 -9
View File
@@ -1,15 +1,46 @@
var Handlebars = require("handlebars/base");
module.exports = Handlebars;
import runtime from './handlebars.runtime';
require("handlebars/utils");
// Compiler imports
import AST from './handlebars/compiler/ast';
import {
parser as Parser,
parse,
parseWithoutProcessing
} from './handlebars/compiler/base';
import { Compiler, compile, precompile } from './handlebars/compiler/compiler';
import JavaScriptCompiler from './handlebars/compiler/javascript-compiler';
import Visitor from './handlebars/compiler/visitor';
require("handlebars/ast");
require("handlebars/printer");
require("handlebars/visitor");
import noConflict from './handlebars/no-conflict';
require("handlebars/compiler");
let _create = runtime.create;
function create() {
let hb = _create();
// BEGIN(BROWSER)
hb.compile = function(input, options) {
return compile(input, options, hb);
};
hb.precompile = function(input, options) {
return precompile(input, options, hb);
};
// END(BROWSER)
hb.AST = AST;
hb.Compiler = Compiler;
hb.JavaScriptCompiler = JavaScriptCompiler;
hb.Parser = Parser;
hb.parse = parse;
hb.parseWithoutProcessing = parseWithoutProcessing;
return hb;
}
let inst = create();
inst.create = create;
noConflict(inst);
inst.Visitor = Visitor;
inst['default'] = inst;
export default inst;
+37
View File
@@ -0,0 +1,37 @@
import * as base from './handlebars/base';
// Each of these augment the Handlebars object. No need to setup here.
// (This is done to easily share code between commonjs and browse envs)
import SafeString from './handlebars/safe-string';
import Exception from './handlebars/exception';
import * as Utils from './handlebars/utils';
import * as runtime from './handlebars/runtime';
import noConflict from './handlebars/no-conflict';
// For compatibility and usage outside of module systems, make the Handlebars object a namespace
function create() {
let hb = new base.HandlebarsEnvironment();
Utils.extend(hb, base);
hb.SafeString = SafeString;
hb.Exception = Exception;
hb.Utils = Utils;
hb.escapeExpression = Utils.escapeExpression;
hb.VM = runtime;
hb.template = function(spec) {
return runtime.template(spec, hb);
};
return hb;
}
let inst = create();
inst.create = create;
noConflict(inst);
inst['default'] = inst;
export default inst;
-93
View File
@@ -1,93 +0,0 @@
var Handlebars = require("handlebars");
// BEGIN(BROWSER)
(function() {
Handlebars.AST = {};
Handlebars.AST.ProgramNode = function(statements, inverse) {
this.type = "program";
this.statements = statements;
if(inverse) { this.inverse = new Handlebars.AST.ProgramNode(inverse); }
};
Handlebars.AST.MustacheNode = function(params, hash, unescaped) {
this.type = "mustache";
this.id = params[0];
this.params = params.slice(1);
this.hash = hash;
this.escaped = !unescaped;
};
Handlebars.AST.PartialNode = function(id, context) {
this.type = "partial";
// TODO: disallow complex IDs
this.id = id;
this.context = context;
};
var verifyMatch = function(open, close) {
if(open.original !== close.original) {
throw new Handlebars.Exception(open.original + " doesn't match " + close.original);
}
};
Handlebars.AST.BlockNode = function(mustache, program, close) {
verifyMatch(mustache.id, close);
this.type = "block";
this.mustache = mustache;
this.program = program;
};
Handlebars.AST.InverseNode = function(mustache, program, close) {
verifyMatch(mustache.id, close);
this.type = "inverse";
this.mustache = mustache;
this.program = program;
};
Handlebars.AST.ContentNode = function(string) {
this.type = "content";
this.string = string;
};
Handlebars.AST.HashNode = function(pairs) {
this.type = "hash";
this.pairs = pairs;
};
Handlebars.AST.IdNode = function(parts) {
this.type = "ID";
this.original = parts.join(".");
var dig = [], depth = 0;
for(var i=0,l=parts.length; i<l; i++) {
var part = parts[i];
if(part === "..") { depth++; }
else if(part === "." || part === "this") { continue; }
else { dig.push(part); }
}
this.parts = dig;
this.string = dig.join('.');
this.depth = depth;
this.isSimple = (dig.length === 1) && (depth === 0);
};
Handlebars.AST.StringNode = function(string) {
this.type = "STRING";
this.string = string;
};
Handlebars.AST.CommentNode = function(comment) {
this.type = "comment";
this.comment = comment;
};
})();
// END(BROWSER)
+76 -92
View File
@@ -1,110 +1,94 @@
var handlebars = require("handlebars/parser").parser;
import { createFrame, extend, toString } from './utils';
import Exception from './exception';
import { registerDefaultHelpers } from './helpers';
import { registerDefaultDecorators } from './decorators';
import logger from './logger';
import { resetLoggedProperties } from './internal/proto-access';
// BEGIN(BROWSER)
var Handlebars = {};
export const VERSION = '4.7.8';
export const COMPILER_REVISION = 8;
export const LAST_COMPATIBLE_COMPILER_REVISION = 7;
Handlebars.VERSION = "1.0.beta.1";
Handlebars.Parser = handlebars;
Handlebars.parse = function(string) {
Handlebars.Parser.yy = Handlebars.AST;
return Handlebars.Parser.parse(string);
export const REVISION_CHANGES = {
1: '<= 1.0.rc.2', // 1.0.rc.2 is actually rev2 but doesn't report it
2: '== 1.0.0-rc.3',
3: '== 1.0.0-rc.4',
4: '== 1.x.x',
5: '== 2.0.0-alpha.x',
6: '>= 2.0.0-beta.1',
7: '>= 4.0.0 <4.3.0',
8: '>= 4.3.0'
};
Handlebars.print = function(ast) {
return new Handlebars.PrintVisitor().accept(ast);
};
const objectType = '[object Object]';
Handlebars.helpers = {};
Handlebars.partials = {};
export function HandlebarsEnvironment(helpers, partials, decorators) {
this.helpers = helpers || {};
this.partials = partials || {};
this.decorators = decorators || {};
Handlebars.registerHelper = function(name, fn, inverse) {
if(inverse) { fn.not = inverse; }
this.helpers[name] = fn;
};
registerDefaultHelpers(this);
registerDefaultDecorators(this);
}
Handlebars.registerPartial = function(name, str) {
this.partials[name] = str;
};
HandlebarsEnvironment.prototype = {
constructor: HandlebarsEnvironment,
Handlebars.registerHelper('helperMissing', function(arg) {
if(arguments.length === 2) {
return undefined;
} else {
throw new Error("Could not find property '" + arg + "'");
}
});
logger: logger,
log: logger.log,
Handlebars.registerHelper('blockHelperMissing', function(context, fn, inverse) {
inverse = inverse || function() {};
var ret = "";
var type = Object.prototype.toString.call(context);
if(type === "[object Function]") {
context = context();
}
if(context === true) {
return fn(this);
} else if(context === false || context == null) {
return inverse(this);
} else if(type === "[object Array]") {
if(context.length > 0) {
for(var i=0, j=context.length; i<j; i++) {
ret = ret + fn(context[i]);
registerHelper: function(name, fn) {
if (toString.call(name) === objectType) {
if (fn) {
throw new Exception('Arg not supported with multiple helpers');
}
extend(this.helpers, name);
} else {
ret = inverse(this);
this.helpers[name] = fn;
}
return ret;
} else {
return fn(context);
}
}, function(context, fn) {
return fn(context);
});
},
unregisterHelper: function(name) {
delete this.helpers[name];
},
Handlebars.registerHelper('each', function(context, fn, inverse) {
var ret = "";
if(context && context.length > 0) {
for(var i=0, j=context.length; i<j; i++) {
ret = ret + fn(context[i]);
registerPartial: function(name, partial) {
if (toString.call(name) === objectType) {
extend(this.partials, name);
} else {
if (typeof partial === 'undefined') {
throw new Exception(
`Attempting to register a partial called "${name}" as undefined`
);
}
this.partials[name] = partial;
}
} else {
ret = inverse(this);
},
unregisterPartial: function(name) {
delete this.partials[name];
},
registerDecorator: function(name, fn) {
if (toString.call(name) === objectType) {
if (fn) {
throw new Exception('Arg not supported with multiple decorators');
}
extend(this.decorators, name);
} else {
this.decorators[name] = fn;
}
},
unregisterDecorator: function(name) {
delete this.decorators[name];
},
/**
* Reset the memory of illegal property accesses that have already been logged.
* @deprecated should only be used in handlebars test-cases
*/
resetLoggedPropertyAccesses() {
resetLoggedProperties();
}
return ret;
});
Handlebars.registerHelper('if', function(context, fn, inverse) {
if(!context || context == []) {
return inverse(this);
} else {
return fn(this);
}
});
Handlebars.registerHelper('unless', function(context, fn, inverse) {
return Handlebars.helpers['if'].call(this, context, inverse, fn);
});
Handlebars.registerHelper('with', function(context, fn) {
return fn(context);
});
Handlebars.logger = {
DEBUG: 0, INFO: 1, WARN: 2, ERROR: 3, level: 3,
// override in the host environment
log: function(level, str) {}
};
Handlebars.log = function(level, str) { Handlebars.logger.log(level, str); };
// END(BROWSER)
module.exports = Handlebars;
export let log = logger.log;
export { createFrame, logger };
-736
View File
@@ -1,736 +0,0 @@
var Handlebars = require("handlebars");
// BEGIN(BROWSER)
Handlebars.Compiler = function() {};
Handlebars.JavaScriptCompiler = function() {};
(function(Compiler, JavaScriptCompiler) {
Compiler.OPCODE_MAP = {
appendContent: 1,
getContext: 2,
lookupWithHelpers: 3,
lookup: 4,
append: 5,
invokeMustache: 6,
appendEscaped: 7,
pushString: 8,
truthyOrFallback: 9,
functionOrFallback: 10,
invokeProgram: 11,
invokePartial: 12,
push: 13,
invokeInverse: 14,
assignToHash: 15,
pushStringParam: 16
};
Compiler.MULTI_PARAM_OPCODES = {
appendContent: 1,
getContext: 1,
lookupWithHelpers: 1,
lookup: 1,
invokeMustache: 2,
pushString: 1,
truthyOrFallback: 1,
functionOrFallback: 1,
invokeProgram: 2,
invokePartial: 1,
push: 1,
invokeInverse: 1,
assignToHash: 1,
pushStringParam: 1
};
Compiler.DISASSEMBLE_MAP = {};
for(var prop in Compiler.OPCODE_MAP) {
var value = Compiler.OPCODE_MAP[prop];
Compiler.DISASSEMBLE_MAP[value] = prop;
}
Compiler.multiParamSize = function(code) {
return Compiler.MULTI_PARAM_OPCODES[Compiler.DISASSEMBLE_MAP[code]];
};
Compiler.prototype = {
compiler: Compiler,
disassemble: function() {
var opcodes = this.opcodes, opcode, nextCode;
var out = [], str, name, value;
for(var i=0, l=opcodes.length; i<l; i++) {
opcode = opcodes[i];
if(opcode === 'DECLARE') {
name = opcodes[++i];
value = opcodes[++i];
out.push("DECLARE " + name + " = " + value);
} else {
str = Compiler.DISASSEMBLE_MAP[opcode];
var extraParams = Compiler.multiParamSize(opcode);
var codes = [];
for(var j=0; j<extraParams; j++) {
nextCode = opcodes[++i];
if(typeof nextCode === "string") {
nextCode = "\"" + nextCode.replace("\n", "\\n") + "\"";
}
codes.push(nextCode);
}
str = str + " " + codes.join(" ");
out.push(str);
}
}
return out.join("\n");
},
guid: 0,
compile: function(program, options) {
this.children = [];
this.depths = {list: []};
this.options = options || {};
return this.program(program);
},
accept: function(node) {
return this[node.type](node);
},
program: function(program) {
var statements = program.statements, statement;
this.opcodes = [];
for(var i=0, l=statements.length; i<l; i++) {
statement = statements[i];
this[statement.type](statement);
}
this.depths.list = this.depths.list.sort(function(a, b) {
return a - b;
});
return this;
},
compileProgram: function(program) {
var result = new this.compiler().compile(program, this.options);
var guid = this.guid++;
this.usePartial = this.usePartial || result.usePartial;
this.children[guid] = result;
for(var i=0, l=result.depths.list.length; i<l; i++) {
depth = result.depths.list[i];
if(depth < 2) { continue; }
else { this.addDepth(depth - 1); }
}
return guid;
},
block: function(block) {
var mustache = block.mustache;
var depth, child, inverse, inverseGuid;
var params = this.setupStackForMustache(mustache);
var programGuid = this.compileProgram(block.program);
if(block.program.inverse) {
inverseGuid = this.compileProgram(block.program.inverse);
this.declare('inverse', inverseGuid);
}
this.opcode('invokeProgram', programGuid, params.length);
this.declare('inverse', null);
this.opcode('append');
},
inverse: function(block) {
this.ID(block.mustache.id);
var programGuid = this.compileProgram(block.program);
this.opcode('invokeInverse', programGuid);
this.opcode('append');
},
hash: function(hash) {
var pairs = hash.pairs, pair, val;
this.opcode('push', '{}');
for(var i=0, l=pairs.length; i<l; i++) {
pair = pairs[i];
val = pair[1];
this.accept(val);
this.opcode('assignToHash', pair[0]);
}
},
partial: function(partial) {
var id = partial.id;
this.usePartial = true;
if(partial.context) {
this.ID(partial.context);
} else {
this.opcode('push', 'context');
}
this.opcode('invokePartial', id.original);
this.opcode('append');
},
content: function(content) {
this.opcode('appendContent', content.string);
},
mustache: function(mustache) {
var params = this.setupStackForMustache(mustache);
this.opcode('invokeMustache', params.length, mustache.id.original);
if(mustache.escaped) {
this.opcode('appendEscaped');
} else {
this.opcode('append');
}
},
ID: function(id) {
this.addDepth(id.depth);
this.opcode('getContext', id.depth);
this.opcode('lookupWithHelpers', id.parts[0] || null);
for(var i=1, l=id.parts.length; i<l; i++) {
this.opcode('lookup', id.parts[i]);
}
},
STRING: function(string) {
this.opcode('pushString', string.string);
},
comment: function() {},
// HELPERS
pushParams: function(params) {
var i = params.length, param;
while(i--) {
param = params[i];
if(this.options.stringParams) {
if(param.depth) {
this.addDepth(param.depth);
}
this.opcode('getContext', param.depth || 0);
this.opcode('pushStringParam', param.string);
} else {
this[param.type](param);
}
}
},
opcode: function(name, val1, val2) {
this.opcodes.push(Compiler.OPCODE_MAP[name]);
if(val1 !== undefined) { this.opcodes.push(val1); }
if(val2 !== undefined) { this.opcodes.push(val2); }
},
declare: function(name, value) {
this.opcodes.push('DECLARE');
this.opcodes.push(name);
this.opcodes.push(value);
},
addDepth: function(depth) {
if(depth === 0) { return; }
if(!this.depths[depth]) {
this.depths[depth] = true;
this.depths.list.push(depth);
}
},
setupStackForMustache: function(mustache) {
var params = mustache.params;
this.pushParams(params);
if(mustache.hash) {
this.hash(mustache.hash);
} else {
this.opcode('push', '{}');
}
this.ID(mustache.id);
return params;
}
};
JavaScriptCompiler.prototype = {
// PUBLIC API: You can override these methods in a subclass to provide
// alternative compiled forms for name lookup and buffering semantics
nameLookup: function(parent, name, type) {
if(JavaScriptCompiler.RESERVED_WORDS[name] || name.indexOf('-') !== -1) {
return parent + "['" + name + "']";
} else {
return parent + "." + name;
}
},
appendToBuffer: function(string) {
return "buffer = buffer + " + string + ";";
},
initializeBuffer: function() {
return this.quotedString("");
},
// END PUBLIC API
compile: function(environment, options) {
this.environment = environment;
this.options = options || {};
this.preamble();
this.stackSlot = 0;
this.stackVars = [];
this.registers = {list: []};
this.compileChildren(environment, options);
Handlebars.log(Handlebars.logger.DEBUG, environment.disassemble() + "\n\n");
var opcodes = environment.opcodes, opcode, name, declareName, declareVal;
this.i = 0;
for(l=opcodes.length; this.i<l; this.i++) {
opcode = this.nextOpcode(0);
if(opcode[0] === 'DECLARE') {
this.i = this.i + 2;
this[opcode[1]] = opcode[2];
} else {
this.i = this.i + opcode[1].length;
this[opcode[0]].apply(this, opcode[1]);
}
}
return this.createFunction();
},
nextOpcode: function(n) {
var opcodes = this.environment.opcodes, opcode = opcodes[this.i + n], name, val;
var extraParams, codes;
if(opcode === 'DECLARE') {
name = opcodes[this.i + 1];
val = opcodes[this.i + 2];
return ['DECLARE', name, val];
} else {
name = Compiler.DISASSEMBLE_MAP[opcode];
extraParams = Compiler.multiParamSize(opcode);
codes = [];
for(var j=0; j<extraParams; j++) {
codes.push(opcodes[this.i + j + 1 + n]);
}
return [name, codes];
}
},
eat: function(opcode) {
this.i = this.i + opcode.length;
},
preamble: function() {
var out = [];
out.push("var buffer = " + this.initializeBuffer() + ", currentContext = context");
var copies = "helpers = helpers || Handlebars.helpers;";
if(this.environment.usePartial) { copies = copies + " partials = partials || Handlebars.partials;"; }
out.push(copies);
// track the last context pushed into place to allow skipping the
// getContext opcode when it would be a noop
this.lastContext = 0;
this.source = out;
},
createFunction: function() {
var container = {
escapeExpression: Handlebars.Utils.escapeExpression,
invokePartial: Handlebars.VM.invokePartial,
programs: [],
program: function(i, helpers, partials, data) {
var programWrapper = this.programs[i];
if(data) {
return Handlebars.VM.program(this.children[i], helpers, partials, data);
} else if(programWrapper) {
return programWrapper;
} else {
programWrapper = this.programs[i] = Handlebars.VM.program(this.children[i], helpers, partials);
return programWrapper;
}
},
programWithDepth: Handlebars.VM.programWithDepth,
noop: Handlebars.VM.noop
};
var locals = this.stackVars.concat(this.registers.list);
if(locals.length > 0) {
this.source[0] = this.source[0] + ", " + locals.join(", ");
}
this.source[0] = this.source[0] + ";";
this.source.push("return buffer;");
var params = ["Handlebars", "context", "helpers", "partials"];
if(this.options.data) { params.push("data"); }
for(var i=0, l=this.environment.depths.list.length; i<l; i++) {
params.push("depth" + this.environment.depths.list[i]);
}
if(params.length === 4 && !this.environment.usePartial) { params.pop(); }
params.push(this.source.join("\n"));
var fn = Function.apply(this, params);
fn.displayName = "Handlebars.js";
Handlebars.log(Handlebars.logger.DEBUG, fn.toString() + "\n\n");
container.render = fn;
container.children = this.environment.children;
return function(context, options, $depth) {
try {
options = options || {};
var args = [Handlebars, context, options.helpers, options.partials, options.data];
var depth = Array.prototype.slice.call(arguments, 2);
args = args.concat(depth);
return container.render.apply(container, args);
} catch(e) {
throw e;
}
};
},
appendContent: function(content) {
this.source.push(this.appendToBuffer(this.quotedString(content)));
},
append: function() {
var local = this.popStack();
this.source.push("if(" + local + " || " + local + " === 0) { " + this.appendToBuffer(local) + " }");
},
appendEscaped: function() {
var opcode = this.nextOpcode(1), extra = "";
if(opcode[0] === 'appendContent') {
extra = " + " + this.quotedString(opcode[1][0]);
this.eat(opcode);
}
this.source.push(this.appendToBuffer("this.escapeExpression(" + this.popStack() + ")" + extra));
},
getContext: function(depth) {
if(this.lastContext !== depth) {
this.lastContext = depth;
if(depth === 0) {
this.source.push("currentContext = context;");
} else {
this.source.push("currentContext = depth" + depth + ";");
}
}
},
lookupWithHelpers: function(name) {
if(name) {
var topStack = this.nextStack();
var toPush = "if('" + name + "' in helpers) { " + topStack +
" = " + this.nameLookup('helpers', name, 'helper') +
"; } else { " + topStack + " = " +
this.nameLookup('currentContext', name, 'context') +
"; }";
this.source.push(toPush);
} else {
this.pushStack("currentContext");
}
},
lookup: function(name) {
var topStack = this.topStack();
this.source.push(topStack + " = " + this.nameLookup(topStack, name, 'context') + ";");
},
pushStringParam: function(string) {
this.pushStack("currentContext");
this.pushString(string);
},
pushString: function(string) {
this.pushStack(this.quotedString(string));
},
push: function(name) {
this.pushStack(name);
},
invokeMustache: function(paramSize, original) {
this.populateParams(paramSize, this.quotedString(original), "{}", null, function(nextStack, helperMissingString, id) {
this.source.push("else if(" + id + "=== undefined) { " + nextStack + " = helpers.helperMissing.call(" + helperMissingString + "); }");
this.source.push("else { " + nextStack + " = " + id + "; }");
});
},
invokeProgram: function(guid, paramSize) {
var inverse = this.programExpression(this.inverse);
var mainProgram = this.programExpression(guid);
this.populateParams(paramSize, null, mainProgram, inverse, function(nextStack, helperMissingString, id) {
this.source.push("else { " + nextStack + " = helpers.blockHelperMissing.call(" + helperMissingString + "); }");
});
},
populateParams: function(paramSize, helperId, program, inverse, fn) {
var id = this.popStack(), nextStack;
var params = [], param, stringParam;
var hash = this.popStack();
this.register('tmp1', program);
this.source.push('tmp1.hash = ' + hash + ';');
if(this.options.stringParams) {
this.source.push('tmp1.contexts = [];');
}
for(var i=0; i<paramSize; i++) {
param = this.popStack();
params.push(param);
if(this.options.stringParams) {
this.source.push('tmp1.contexts.push(' + this.popStack() + ');');
}
}
if(inverse) {
this.source.push('tmp1.fn = tmp1;');
this.source.push('tmp1.inverse = ' + inverse + ';');
}
if(this.options.data) {
this.source.push('tmp1.data = data;');
}
params.push('tmp1');
// TODO: This is legacy behavior. Deprecate and remove.
if(inverse) {
params.push(inverse);
}
this.populateCall(params, id, helperId || id, fn);
},
populateCall: function(params, id, helperId, fn) {
var paramString = ["context"].concat(params).join(", ");
var helperMissingString = ["context"].concat(helperId).concat(params).join(", ");
nextStack = this.nextStack();
this.source.push("if(typeof " + id + " === 'function') { " + nextStack + " = " + id + ".call(" + paramString + "); }");
fn.call(this, nextStack, helperMissingString, id);
},
invokeInverse: function(guid) {
var program = this.programExpression(guid);
var blockMissingParams = ["context", this.topStack(), "this.noop", program];
this.pushStack("helpers.blockHelperMissing.call(" + blockMissingParams.join(", ") + ")");
},
invokePartial: function(context) {
this.pushStack("this.invokePartial(" + this.nameLookup('partials', context, 'partial') + ", '" + context + "', " + this.popStack() + ", helpers, partials);");
},
assignToHash: function(key) {
var value = this.popStack();
var hash = this.topStack();
this.source.push(hash + "['" + key + "'] = " + value + ";");
},
// HELPERS
compiler: JavaScriptCompiler,
compileChildren: function(environment, options) {
var children = environment.children, child, compiler;
var compiled = [];
for(var i=0, l=children.length; i<l; i++) {
child = children[i];
compiler = new this.compiler();
compiled[i] = compiler.compile(child, options);
}
environment.rawChildren = children;
environment.children = compiled;
},
programExpression: function(guid) {
if(guid == null) { return "this.noop"; }
var programParams = [guid, "helpers", "partials"];
var depths = this.environment.rawChildren[guid].depths.list;
if(this.options.data) { programParams.push("data"); }
for(var i=0, l = depths.length; i<l; i++) {
depth = depths[i];
if(depth === 1) { programParams.push("context"); }
else { programParams.push("depth" + (depth - 1)); }
}
if(!this.environment.usePartial) {
if(programParams[3]) {
programParams[2] = "null";
} else {
programParams.pop();
}
}
if(depths.length === 0) {
return "this.program(" + programParams.join(", ") + ")";
} else {
programParams[0] = "this.children[" + guid + "]";
return "this.programWithDepth(" + programParams.join(", ") + ")";
}
},
register: function(name, val) {
this.useRegister(name);
this.source.push(name + " = " + val + ";");
},
useRegister: function(name) {
if(!this.registers[name]) {
this.registers[name] = true;
this.registers.list.push(name);
}
},
pushStack: function(item) {
this.source.push(this.nextStack() + " = " + item + ";");
return "stack" + this.stackSlot;
},
nextStack: function() {
this.stackSlot++;
if(this.stackSlot > this.stackVars.length) { this.stackVars.push("stack" + this.stackSlot); }
return "stack" + this.stackSlot;
},
popStack: function() {
return "stack" + this.stackSlot--;
},
topStack: function() {
return "stack" + this.stackSlot;
},
quotedString: function(str) {
return '"' + str
.replace(/\\/g, '\\\\')
.replace(/"/g, '\\"')
.replace(/\n/g, '\\n')
.replace(/\r/g, '\\r') + '"';
}
};
var reservedWords = ("break case catch continue default delete do else finally " +
"for function if in instanceof new return switch this throw " +
"try typeof var void while with null true false").split(" ");
compilerWords = JavaScriptCompiler.RESERVED_WORDS = {};
for(var i=0, l=reservedWords.length; i<l; i++) {
compilerWords[reservedWords[i]] = true;
}
})(Handlebars.Compiler, Handlebars.JavaScriptCompiler);
Handlebars.VM = {
programWithDepth: function(fn, passedHelpers, passedPartials, passedData, $depth) {
var args = Array.prototype.slice.call(arguments, 4);
return function(context, helpers, partials, data) {
var options = {
helpers: helpers || passedHelpers,
partials: partials || passedPartials,
data: data || passedData
};
return fn.apply(this, [context, options].concat(args));
};
},
program: function(fn, helpers, partials, data) {
return function(context, h2, p2, d2) {
return fn(context, {helpers: h2 || helpers, partials: p2 || partials, data: d2 || data});
};
},
noop: function() { return ""; },
compile: function(string, options) {
var ast = Handlebars.parse(string);
var environment = new Handlebars.Compiler().compile(ast, options);
return new Handlebars.JavaScriptCompiler().compile(environment, options);
},
invokePartial: function(partial, name, context, helpers, partials) {
if(partial === undefined) {
throw new Handlebars.Exception("The partial " + name + " could not be found");
} else if(partial instanceof Function) {
return partial(context, {helpers: helpers, partials: partials});
} else {
partials[name] = Handlebars.VM.compile(partial);
return partials[name](context, {helpers: helpers, partials: partials});
}
}
};
Handlebars.compile = Handlebars.VM.compile;
// END(BROWSER)
+32
View File
@@ -0,0 +1,32 @@
let AST = {
// Public API used to evaluate derived attributes regarding AST nodes
helpers: {
// a mustache is definitely a helper if:
// * it is an eligible helper, and
// * it has at least one parameter or hash segment
helperExpression: function(node) {
return (
node.type === 'SubExpression' ||
((node.type === 'MustacheStatement' ||
node.type === 'BlockStatement') &&
!!((node.params && node.params.length) || node.hash))
);
},
scopedId: function(path) {
return /^\.|this\b/.test(path.original);
},
// an ID is simple if it only has one part, and that part is not
// `..` or `this`.
simpleId: function(path) {
return (
path.parts.length === 1 && !AST.helpers.scopedId(path) && !path.depth
);
}
}
};
// Must be exported as an object rather than the root of the module as the jison lexer
// must modify the object to operate properly.
export default AST;
+34
View File
@@ -0,0 +1,34 @@
import parser from './parser';
import WhitespaceControl from './whitespace-control';
import * as Helpers from './helpers';
import { extend } from '../utils';
export { parser };
let yy = {};
extend(yy, Helpers);
export function parseWithoutProcessing(input, options) {
// Just return if an already-compiled AST was passed in.
if (input.type === 'Program') {
return input;
}
parser.yy = yy;
// Altering the shared object here, but this is ok as parser is a sync operation
yy.locInfo = function(locInfo) {
return new yy.SourceLocation(options && options.srcName, locInfo);
};
let ast = parser.parse(input);
return ast;
}
export function parse(input, options) {
let ast = parseWithoutProcessing(input, options);
let strip = new WhitespaceControl(options);
return strip.accept(ast);
}
+171
View File
@@ -0,0 +1,171 @@
/* global define, require */
import { isArray } from '../utils';
let SourceNode;
try {
/* istanbul ignore next */
if (typeof define !== 'function' || !define.amd) {
// We don't support this in AMD environments. For these environments, we assume that
// they are running on the browser and thus have no need for the source-map library.
let SourceMap = require('source-map');
SourceNode = SourceMap.SourceNode;
}
} catch (err) {
/* NOP */
}
/* istanbul ignore if: tested but not covered in istanbul due to dist build */
if (!SourceNode) {
SourceNode = function(line, column, srcFile, chunks) {
this.src = '';
if (chunks) {
this.add(chunks);
}
};
/* istanbul ignore next */
SourceNode.prototype = {
add: function(chunks) {
if (isArray(chunks)) {
chunks = chunks.join('');
}
this.src += chunks;
},
prepend: function(chunks) {
if (isArray(chunks)) {
chunks = chunks.join('');
}
this.src = chunks + this.src;
},
toStringWithSourceMap: function() {
return { code: this.toString() };
},
toString: function() {
return this.src;
}
};
}
function castChunk(chunk, codeGen, loc) {
if (isArray(chunk)) {
let ret = [];
for (let i = 0, len = chunk.length; i < len; i++) {
ret.push(codeGen.wrap(chunk[i], loc));
}
return ret;
} else if (typeof chunk === 'boolean' || typeof chunk === 'number') {
// Handle primitives that the SourceNode will throw up on
return chunk + '';
}
return chunk;
}
function CodeGen(srcFile) {
this.srcFile = srcFile;
this.source = [];
}
CodeGen.prototype = {
isEmpty() {
return !this.source.length;
},
prepend: function(source, loc) {
this.source.unshift(this.wrap(source, loc));
},
push: function(source, loc) {
this.source.push(this.wrap(source, loc));
},
merge: function() {
let source = this.empty();
this.each(function(line) {
source.add([' ', line, '\n']);
});
return source;
},
each: function(iter) {
for (let i = 0, len = this.source.length; i < len; i++) {
iter(this.source[i]);
}
},
empty: function() {
let loc = this.currentLocation || { start: {} };
return new SourceNode(loc.start.line, loc.start.column, this.srcFile);
},
wrap: function(chunk, loc = this.currentLocation || { start: {} }) {
if (chunk instanceof SourceNode) {
return chunk;
}
chunk = castChunk(chunk, this, loc);
return new SourceNode(
loc.start.line,
loc.start.column,
this.srcFile,
chunk
);
},
functionCall: function(fn, type, params) {
params = this.generateList(params);
return this.wrap([fn, type ? '.' + type + '(' : '(', params, ')']);
},
quotedString: function(str) {
return (
'"' +
(str + '')
.replace(/\\/g, '\\\\')
.replace(/"/g, '\\"')
.replace(/\n/g, '\\n')
.replace(/\r/g, '\\r')
.replace(/\u2028/g, '\\u2028') // Per Ecma-262 7.3 + 7.8.4
.replace(/\u2029/g, '\\u2029') +
'"'
);
},
objectLiteral: function(obj) {
let pairs = [];
Object.keys(obj).forEach(key => {
let value = castChunk(obj[key], this);
if (value !== 'undefined') {
pairs.push([this.quotedString(key), ':', value]);
}
});
let ret = this.generateList(pairs);
ret.prepend('{');
ret.add('}');
return ret;
},
generateList: function(entries) {
let ret = this.empty();
for (let i = 0, len = entries.length; i < len; i++) {
if (i) {
ret.add(',');
}
ret.add(castChunk(entries[i], this));
}
return ret;
},
generateArray: function(entries) {
let ret = this.generateList(entries);
ret.prepend('[');
ret.add(']');
return ret;
}
};
export default CodeGen;
+594
View File
@@ -0,0 +1,594 @@
/* eslint-disable new-cap */
import Exception from '../exception';
import { isArray, indexOf, extend } from '../utils';
import AST from './ast';
const slice = [].slice;
export function Compiler() {}
// the foundHelper register will disambiguate helper lookup from finding a
// function in a context. This is necessary for mustache compatibility, which
// requires that context functions in blocks are evaluated by blockHelperMissing,
// and then proceed as if the resulting value was provided to blockHelperMissing.
Compiler.prototype = {
compiler: Compiler,
equals: function(other) {
let len = this.opcodes.length;
if (other.opcodes.length !== len) {
return false;
}
for (let i = 0; i < len; i++) {
let opcode = this.opcodes[i],
otherOpcode = other.opcodes[i];
if (
opcode.opcode !== otherOpcode.opcode ||
!argEquals(opcode.args, otherOpcode.args)
) {
return false;
}
}
// We know that length is the same between the two arrays because they are directly tied
// to the opcode behavior above.
len = this.children.length;
for (let i = 0; i < len; i++) {
if (!this.children[i].equals(other.children[i])) {
return false;
}
}
return true;
},
guid: 0,
compile: function(program, options) {
this.sourceNode = [];
this.opcodes = [];
this.children = [];
this.options = options;
this.stringParams = options.stringParams;
this.trackIds = options.trackIds;
options.blockParams = options.blockParams || [];
options.knownHelpers = extend(
Object.create(null),
{
helperMissing: true,
blockHelperMissing: true,
each: true,
if: true,
unless: true,
with: true,
log: true,
lookup: true
},
options.knownHelpers
);
return this.accept(program);
},
compileProgram: function(program) {
let childCompiler = new this.compiler(), // eslint-disable-line new-cap
result = childCompiler.compile(program, this.options),
guid = this.guid++;
this.usePartial = this.usePartial || result.usePartial;
this.children[guid] = result;
this.useDepths = this.useDepths || result.useDepths;
return guid;
},
accept: function(node) {
/* istanbul ignore next: Sanity code */
if (!this[node.type]) {
throw new Exception('Unknown type: ' + node.type, node);
}
this.sourceNode.unshift(node);
let ret = this[node.type](node);
this.sourceNode.shift();
return ret;
},
Program: function(program) {
this.options.blockParams.unshift(program.blockParams);
let body = program.body,
bodyLength = body.length;
for (let i = 0; i < bodyLength; i++) {
this.accept(body[i]);
}
this.options.blockParams.shift();
this.isSimple = bodyLength === 1;
this.blockParams = program.blockParams ? program.blockParams.length : 0;
return this;
},
BlockStatement: function(block) {
transformLiteralToPath(block);
let program = block.program,
inverse = block.inverse;
program = program && this.compileProgram(program);
inverse = inverse && this.compileProgram(inverse);
let type = this.classifySexpr(block);
if (type === 'helper') {
this.helperSexpr(block, program, inverse);
} else if (type === 'simple') {
this.simpleSexpr(block);
// now that the simple mustache is resolved, we need to
// evaluate it by executing `blockHelperMissing`
this.opcode('pushProgram', program);
this.opcode('pushProgram', inverse);
this.opcode('emptyHash');
this.opcode('blockValue', block.path.original);
} else {
this.ambiguousSexpr(block, program, inverse);
// now that the simple mustache is resolved, we need to
// evaluate it by executing `blockHelperMissing`
this.opcode('pushProgram', program);
this.opcode('pushProgram', inverse);
this.opcode('emptyHash');
this.opcode('ambiguousBlockValue');
}
this.opcode('append');
},
DecoratorBlock(decorator) {
let program = decorator.program && this.compileProgram(decorator.program);
let params = this.setupFullMustacheParams(decorator, program, undefined),
path = decorator.path;
this.useDecorators = true;
this.opcode('registerDecorator', params.length, path.original);
},
PartialStatement: function(partial) {
this.usePartial = true;
let program = partial.program;
if (program) {
program = this.compileProgram(partial.program);
}
let params = partial.params;
if (params.length > 1) {
throw new Exception(
'Unsupported number of partial arguments: ' + params.length,
partial
);
} else if (!params.length) {
if (this.options.explicitPartialContext) {
this.opcode('pushLiteral', 'undefined');
} else {
params.push({ type: 'PathExpression', parts: [], depth: 0 });
}
}
let partialName = partial.name.original,
isDynamic = partial.name.type === 'SubExpression';
if (isDynamic) {
this.accept(partial.name);
}
this.setupFullMustacheParams(partial, program, undefined, true);
let indent = partial.indent || '';
if (this.options.preventIndent && indent) {
this.opcode('appendContent', indent);
indent = '';
}
this.opcode('invokePartial', isDynamic, partialName, indent);
this.opcode('append');
},
PartialBlockStatement: function(partialBlock) {
this.PartialStatement(partialBlock);
},
MustacheStatement: function(mustache) {
this.SubExpression(mustache);
if (mustache.escaped && !this.options.noEscape) {
this.opcode('appendEscaped');
} else {
this.opcode('append');
}
},
Decorator(decorator) {
this.DecoratorBlock(decorator);
},
ContentStatement: function(content) {
if (content.value) {
this.opcode('appendContent', content.value);
}
},
CommentStatement: function() {},
SubExpression: function(sexpr) {
transformLiteralToPath(sexpr);
let type = this.classifySexpr(sexpr);
if (type === 'simple') {
this.simpleSexpr(sexpr);
} else if (type === 'helper') {
this.helperSexpr(sexpr);
} else {
this.ambiguousSexpr(sexpr);
}
},
ambiguousSexpr: function(sexpr, program, inverse) {
let path = sexpr.path,
name = path.parts[0],
isBlock = program != null || inverse != null;
this.opcode('getContext', path.depth);
this.opcode('pushProgram', program);
this.opcode('pushProgram', inverse);
path.strict = true;
this.accept(path);
this.opcode('invokeAmbiguous', name, isBlock);
},
simpleSexpr: function(sexpr) {
let path = sexpr.path;
path.strict = true;
this.accept(path);
this.opcode('resolvePossibleLambda');
},
helperSexpr: function(sexpr, program, inverse) {
let params = this.setupFullMustacheParams(sexpr, program, inverse),
path = sexpr.path,
name = path.parts[0];
if (this.options.knownHelpers[name]) {
this.opcode('invokeKnownHelper', params.length, name);
} else if (this.options.knownHelpersOnly) {
throw new Exception(
'You specified knownHelpersOnly, but used the unknown helper ' + name,
sexpr
);
} else {
path.strict = true;
path.falsy = true;
this.accept(path);
this.opcode(
'invokeHelper',
params.length,
path.original,
AST.helpers.simpleId(path)
);
}
},
PathExpression: function(path) {
this.addDepth(path.depth);
this.opcode('getContext', path.depth);
let name = path.parts[0],
scoped = AST.helpers.scopedId(path),
blockParamId = !path.depth && !scoped && this.blockParamIndex(name);
if (blockParamId) {
this.opcode('lookupBlockParam', blockParamId, path.parts);
} else if (!name) {
// Context reference, i.e. `{{foo .}}` or `{{foo ..}}`
this.opcode('pushContext');
} else if (path.data) {
this.options.data = true;
this.opcode('lookupData', path.depth, path.parts, path.strict);
} else {
this.opcode(
'lookupOnContext',
path.parts,
path.falsy,
path.strict,
scoped
);
}
},
StringLiteral: function(string) {
this.opcode('pushString', string.value);
},
NumberLiteral: function(number) {
this.opcode('pushLiteral', number.value);
},
BooleanLiteral: function(bool) {
this.opcode('pushLiteral', bool.value);
},
UndefinedLiteral: function() {
this.opcode('pushLiteral', 'undefined');
},
NullLiteral: function() {
this.opcode('pushLiteral', 'null');
},
Hash: function(hash) {
let pairs = hash.pairs,
i = 0,
l = pairs.length;
this.opcode('pushHash');
for (; i < l; i++) {
this.pushParam(pairs[i].value);
}
while (i--) {
this.opcode('assignToHash', pairs[i].key);
}
this.opcode('popHash');
},
// HELPERS
opcode: function(name) {
this.opcodes.push({
opcode: name,
args: slice.call(arguments, 1),
loc: this.sourceNode[0].loc
});
},
addDepth: function(depth) {
if (!depth) {
return;
}
this.useDepths = true;
},
classifySexpr: function(sexpr) {
let isSimple = AST.helpers.simpleId(sexpr.path);
let isBlockParam = isSimple && !!this.blockParamIndex(sexpr.path.parts[0]);
// a mustache is an eligible helper if:
// * its id is simple (a single part, not `this` or `..`)
let isHelper = !isBlockParam && AST.helpers.helperExpression(sexpr);
// if a mustache is an eligible helper but not a definite
// helper, it is ambiguous, and will be resolved in a later
// pass or at runtime.
let isEligible = !isBlockParam && (isHelper || isSimple);
// if ambiguous, we can possibly resolve the ambiguity now
// An eligible helper is one that does not have a complex path, i.e. `this.foo`, `../foo` etc.
if (isEligible && !isHelper) {
let name = sexpr.path.parts[0],
options = this.options;
if (options.knownHelpers[name]) {
isHelper = true;
} else if (options.knownHelpersOnly) {
isEligible = false;
}
}
if (isHelper) {
return 'helper';
} else if (isEligible) {
return 'ambiguous';
} else {
return 'simple';
}
},
pushParams: function(params) {
for (let i = 0, l = params.length; i < l; i++) {
this.pushParam(params[i]);
}
},
pushParam: function(val) {
let value = val.value != null ? val.value : val.original || '';
if (this.stringParams) {
if (value.replace) {
value = value.replace(/^(\.?\.\/)*/g, '').replace(/\//g, '.');
}
if (val.depth) {
this.addDepth(val.depth);
}
this.opcode('getContext', val.depth || 0);
this.opcode('pushStringParam', value, val.type);
if (val.type === 'SubExpression') {
// SubExpressions get evaluated and passed in
// in string params mode.
this.accept(val);
}
} else {
if (this.trackIds) {
let blockParamIndex;
if (val.parts && !AST.helpers.scopedId(val) && !val.depth) {
blockParamIndex = this.blockParamIndex(val.parts[0]);
}
if (blockParamIndex) {
let blockParamChild = val.parts.slice(1).join('.');
this.opcode('pushId', 'BlockParam', blockParamIndex, blockParamChild);
} else {
value = val.original || value;
if (value.replace) {
value = value
.replace(/^this(?:\.|$)/, '')
.replace(/^\.\//, '')
.replace(/^\.$/, '');
}
this.opcode('pushId', val.type, value);
}
}
this.accept(val);
}
},
setupFullMustacheParams: function(sexpr, program, inverse, omitEmpty) {
let params = sexpr.params;
this.pushParams(params);
this.opcode('pushProgram', program);
this.opcode('pushProgram', inverse);
if (sexpr.hash) {
this.accept(sexpr.hash);
} else {
this.opcode('emptyHash', omitEmpty);
}
return params;
},
blockParamIndex: function(name) {
for (
let depth = 0, len = this.options.blockParams.length;
depth < len;
depth++
) {
let blockParams = this.options.blockParams[depth],
param = blockParams && indexOf(blockParams, name);
if (blockParams && param >= 0) {
return [depth, param];
}
}
}
};
export function precompile(input, options, env) {
if (
input == null ||
(typeof input !== 'string' && input.type !== 'Program')
) {
throw new Exception(
'You must pass a string or Handlebars AST to Handlebars.precompile. You passed ' +
input
);
}
options = options || {};
if (!('data' in options)) {
options.data = true;
}
if (options.compat) {
options.useDepths = true;
}
let ast = env.parse(input, options),
environment = new env.Compiler().compile(ast, options);
return new env.JavaScriptCompiler().compile(environment, options);
}
export function compile(input, options = {}, env) {
if (
input == null ||
(typeof input !== 'string' && input.type !== 'Program')
) {
throw new Exception(
'You must pass a string or Handlebars AST to Handlebars.compile. You passed ' +
input
);
}
options = extend({}, options);
if (!('data' in options)) {
options.data = true;
}
if (options.compat) {
options.useDepths = true;
}
let compiled;
function compileInput() {
let ast = env.parse(input, options),
environment = new env.Compiler().compile(ast, options),
templateSpec = new env.JavaScriptCompiler().compile(
environment,
options,
undefined,
true
);
return env.template(templateSpec);
}
// Template is only compiled on first use and cached after that point.
function ret(context, execOptions) {
if (!compiled) {
compiled = compileInput();
}
return compiled.call(this, context, execOptions);
}
ret._setup = function(setupOptions) {
if (!compiled) {
compiled = compileInput();
}
return compiled._setup(setupOptions);
};
ret._child = function(i, data, blockParams, depths) {
if (!compiled) {
compiled = compileInput();
}
return compiled._child(i, data, blockParams, depths);
};
return ret;
}
function argEquals(a, b) {
if (a === b) {
return true;
}
if (isArray(a) && isArray(b) && a.length === b.length) {
for (let i = 0; i < a.length; i++) {
if (!argEquals(a[i], b[i])) {
return false;
}
}
return true;
}
}
function transformLiteralToPath(sexpr) {
if (!sexpr.path.parts) {
let literal = sexpr.path;
// Casting to string here to make false and 0 literal values play nicely with the rest
// of the system.
sexpr.path = {
type: 'PathExpression',
data: false,
depth: 0,
parts: [literal.original + ''],
original: literal.original + '',
loc: literal.loc
};
}
}
+219
View File
@@ -0,0 +1,219 @@
import Exception from '../exception';
function validateClose(open, close) {
close = close.path ? close.path.original : close;
if (open.path.original !== close) {
let errorNode = { loc: open.path.loc };
throw new Exception(
open.path.original + " doesn't match " + close,
errorNode
);
}
}
export function SourceLocation(source, locInfo) {
this.source = source;
this.start = {
line: locInfo.first_line,
column: locInfo.first_column
};
this.end = {
line: locInfo.last_line,
column: locInfo.last_column
};
}
export function id(token) {
if (/^\[.*\]$/.test(token)) {
return token.substring(1, token.length - 1);
} else {
return token;
}
}
export function stripFlags(open, close) {
return {
open: open.charAt(2) === '~',
close: close.charAt(close.length - 3) === '~'
};
}
export function stripComment(comment) {
return comment.replace(/^\{\{~?!-?-?/, '').replace(/-?-?~?\}\}$/, '');
}
export function preparePath(data, parts, loc) {
loc = this.locInfo(loc);
let original = data ? '@' : '',
dig = [],
depth = 0;
for (let i = 0, l = parts.length; i < l; i++) {
let part = parts[i].part,
// If we have [] syntax then we do not treat path references as operators,
// i.e. foo.[this] resolves to approximately context.foo['this']
isLiteral = parts[i].original !== part;
original += (parts[i].separator || '') + part;
if (!isLiteral && (part === '..' || part === '.' || part === 'this')) {
if (dig.length > 0) {
throw new Exception('Invalid path: ' + original, { loc });
} else if (part === '..') {
depth++;
}
} else {
dig.push(part);
}
}
return {
type: 'PathExpression',
data,
depth,
parts: dig,
original,
loc
};
}
export function prepareMustache(path, params, hash, open, strip, locInfo) {
// Must use charAt to support IE pre-10
let escapeFlag = open.charAt(3) || open.charAt(2),
escaped = escapeFlag !== '{' && escapeFlag !== '&';
let decorator = /\*/.test(open);
return {
type: decorator ? 'Decorator' : 'MustacheStatement',
path,
params,
hash,
escaped,
strip,
loc: this.locInfo(locInfo)
};
}
export function prepareRawBlock(openRawBlock, contents, close, locInfo) {
validateClose(openRawBlock, close);
locInfo = this.locInfo(locInfo);
let program = {
type: 'Program',
body: contents,
strip: {},
loc: locInfo
};
return {
type: 'BlockStatement',
path: openRawBlock.path,
params: openRawBlock.params,
hash: openRawBlock.hash,
program,
openStrip: {},
inverseStrip: {},
closeStrip: {},
loc: locInfo
};
}
export function prepareBlock(
openBlock,
program,
inverseAndProgram,
close,
inverted,
locInfo
) {
if (close && close.path) {
validateClose(openBlock, close);
}
let decorator = /\*/.test(openBlock.open);
program.blockParams = openBlock.blockParams;
let inverse, inverseStrip;
if (inverseAndProgram) {
if (decorator) {
throw new Exception(
'Unexpected inverse block on decorator',
inverseAndProgram
);
}
if (inverseAndProgram.chain) {
inverseAndProgram.program.body[0].closeStrip = close.strip;
}
inverseStrip = inverseAndProgram.strip;
inverse = inverseAndProgram.program;
}
if (inverted) {
inverted = inverse;
inverse = program;
program = inverted;
}
return {
type: decorator ? 'DecoratorBlock' : 'BlockStatement',
path: openBlock.path,
params: openBlock.params,
hash: openBlock.hash,
program,
inverse,
openStrip: openBlock.strip,
inverseStrip,
closeStrip: close && close.strip,
loc: this.locInfo(locInfo)
};
}
export function prepareProgram(statements, loc) {
if (!loc && statements.length) {
const firstLoc = statements[0].loc,
lastLoc = statements[statements.length - 1].loc;
/* istanbul ignore else */
if (firstLoc && lastLoc) {
loc = {
source: firstLoc.source,
start: {
line: firstLoc.start.line,
column: firstLoc.start.column
},
end: {
line: lastLoc.end.line,
column: lastLoc.end.column
}
};
}
}
return {
type: 'Program',
body: statements,
strip: {},
loc: loc
};
}
export function preparePartialBlock(open, program, close, locInfo) {
validateClose(open, close);
return {
type: 'PartialBlockStatement',
name: open.path,
params: open.params,
hash: open.hash,
program,
openStrip: open.strip,
closeStrip: close && close.strip,
loc: this.locInfo(locInfo)
};
}
File diff suppressed because it is too large Load Diff
+178
View File
@@ -0,0 +1,178 @@
/* eslint-disable new-cap */
import Visitor from './visitor';
export function print(ast) {
return new PrintVisitor().accept(ast);
}
export function PrintVisitor() {
this.padding = 0;
}
PrintVisitor.prototype = new Visitor();
PrintVisitor.prototype.pad = function(string) {
let out = '';
for (let i = 0, l = this.padding; i < l; i++) {
out += ' ';
}
out += string + '\n';
return out;
};
PrintVisitor.prototype.Program = function(program) {
let out = '',
body = program.body,
i,
l;
if (program.blockParams) {
let blockParams = 'BLOCK PARAMS: [';
for (i = 0, l = program.blockParams.length; i < l; i++) {
blockParams += ' ' + program.blockParams[i];
}
blockParams += ' ]';
out += this.pad(blockParams);
}
for (i = 0, l = body.length; i < l; i++) {
out += this.accept(body[i]);
}
this.padding--;
return out;
};
PrintVisitor.prototype.MustacheStatement = function(mustache) {
return this.pad('{{ ' + this.SubExpression(mustache) + ' }}');
};
PrintVisitor.prototype.Decorator = function(mustache) {
return this.pad('{{ DIRECTIVE ' + this.SubExpression(mustache) + ' }}');
};
PrintVisitor.prototype.BlockStatement = PrintVisitor.prototype.DecoratorBlock = function(
block
) {
let out = '';
out += this.pad(
(block.type === 'DecoratorBlock' ? 'DIRECTIVE ' : '') + 'BLOCK:'
);
this.padding++;
out += this.pad(this.SubExpression(block));
if (block.program) {
out += this.pad('PROGRAM:');
this.padding++;
out += this.accept(block.program);
this.padding--;
}
if (block.inverse) {
if (block.program) {
this.padding++;
}
out += this.pad('{{^}}');
this.padding++;
out += this.accept(block.inverse);
this.padding--;
if (block.program) {
this.padding--;
}
}
this.padding--;
return out;
};
PrintVisitor.prototype.PartialStatement = function(partial) {
let content = 'PARTIAL:' + partial.name.original;
if (partial.params[0]) {
content += ' ' + this.accept(partial.params[0]);
}
if (partial.hash) {
content += ' ' + this.accept(partial.hash);
}
return this.pad('{{> ' + content + ' }}');
};
PrintVisitor.prototype.PartialBlockStatement = function(partial) {
let content = 'PARTIAL BLOCK:' + partial.name.original;
if (partial.params[0]) {
content += ' ' + this.accept(partial.params[0]);
}
if (partial.hash) {
content += ' ' + this.accept(partial.hash);
}
content += ' ' + this.pad('PROGRAM:');
this.padding++;
content += this.accept(partial.program);
this.padding--;
return this.pad('{{> ' + content + ' }}');
};
PrintVisitor.prototype.ContentStatement = function(content) {
return this.pad("CONTENT[ '" + content.value + "' ]");
};
PrintVisitor.prototype.CommentStatement = function(comment) {
return this.pad("{{! '" + comment.value + "' }}");
};
PrintVisitor.prototype.SubExpression = function(sexpr) {
let params = sexpr.params,
paramStrings = [],
hash;
for (let i = 0, l = params.length; i < l; i++) {
paramStrings.push(this.accept(params[i]));
}
params = '[' + paramStrings.join(', ') + ']';
hash = sexpr.hash ? ' ' + this.accept(sexpr.hash) : '';
return this.accept(sexpr.path) + ' ' + params + hash;
};
PrintVisitor.prototype.PathExpression = function(id) {
let path = id.parts.join('/');
return (id.data ? '@' : '') + 'PATH:' + path;
};
PrintVisitor.prototype.StringLiteral = function(string) {
return '"' + string.value + '"';
};
PrintVisitor.prototype.NumberLiteral = function(number) {
return 'NUMBER{' + number.value + '}';
};
PrintVisitor.prototype.BooleanLiteral = function(bool) {
return 'BOOLEAN{' + bool.value + '}';
};
PrintVisitor.prototype.UndefinedLiteral = function() {
return 'UNDEFINED';
};
PrintVisitor.prototype.NullLiteral = function() {
return 'NULL';
};
PrintVisitor.prototype.Hash = function(hash) {
let pairs = hash.pairs,
joinedPairs = [];
for (let i = 0, l = pairs.length; i < l; i++) {
joinedPairs.push(this.accept(pairs[i]));
}
return 'HASH{' + joinedPairs.join(', ') + '}';
};
PrintVisitor.prototype.HashPair = function(pair) {
return pair.key + '=' + this.accept(pair.value);
};
/* eslint-enable new-cap */
+136
View File
@@ -0,0 +1,136 @@
import Exception from '../exception';
function Visitor() {
this.parents = [];
}
Visitor.prototype = {
constructor: Visitor,
mutating: false,
// Visits a given value. If mutating, will replace the value if necessary.
acceptKey: function(node, name) {
let value = this.accept(node[name]);
if (this.mutating) {
// Hacky sanity check: This may have a few false positives for type for the helper
// methods but will generally do the right thing without a lot of overhead.
if (value && !Visitor.prototype[value.type]) {
throw new Exception(
'Unexpected node type "' +
value.type +
'" found when accepting ' +
name +
' on ' +
node.type
);
}
node[name] = value;
}
},
// Performs an accept operation with added sanity check to ensure
// required keys are not removed.
acceptRequired: function(node, name) {
this.acceptKey(node, name);
if (!node[name]) {
throw new Exception(node.type + ' requires ' + name);
}
},
// Traverses a given array. If mutating, empty respnses will be removed
// for child elements.
acceptArray: function(array) {
for (let i = 0, l = array.length; i < l; i++) {
this.acceptKey(array, i);
if (!array[i]) {
array.splice(i, 1);
i--;
l--;
}
}
},
accept: function(object) {
if (!object) {
return;
}
/* istanbul ignore next: Sanity code */
if (!this[object.type]) {
throw new Exception('Unknown type: ' + object.type, object);
}
if (this.current) {
this.parents.unshift(this.current);
}
this.current = object;
let ret = this[object.type](object);
this.current = this.parents.shift();
if (!this.mutating || ret) {
return ret;
} else if (ret !== false) {
return object;
}
},
Program: function(program) {
this.acceptArray(program.body);
},
MustacheStatement: visitSubExpression,
Decorator: visitSubExpression,
BlockStatement: visitBlock,
DecoratorBlock: visitBlock,
PartialStatement: visitPartial,
PartialBlockStatement: function(partial) {
visitPartial.call(this, partial);
this.acceptKey(partial, 'program');
},
ContentStatement: function(/* content */) {},
CommentStatement: function(/* comment */) {},
SubExpression: visitSubExpression,
PathExpression: function(/* path */) {},
StringLiteral: function(/* string */) {},
NumberLiteral: function(/* number */) {},
BooleanLiteral: function(/* bool */) {},
UndefinedLiteral: function(/* literal */) {},
NullLiteral: function(/* literal */) {},
Hash: function(hash) {
this.acceptArray(hash.pairs);
},
HashPair: function(pair) {
this.acceptRequired(pair, 'value');
}
};
function visitSubExpression(mustache) {
this.acceptRequired(mustache, 'path');
this.acceptArray(mustache.params);
this.acceptKey(mustache, 'hash');
}
function visitBlock(block) {
visitSubExpression.call(this, block);
this.acceptKey(block, 'program');
this.acceptKey(block, 'inverse');
}
function visitPartial(partial) {
this.acceptRequired(partial, 'name');
this.acceptArray(partial.params);
this.acceptKey(partial, 'hash');
}
export default Visitor;
@@ -0,0 +1,234 @@
import Visitor from './visitor';
function WhitespaceControl(options = {}) {
this.options = options;
}
WhitespaceControl.prototype = new Visitor();
WhitespaceControl.prototype.Program = function(program) {
const doStandalone = !this.options.ignoreStandalone;
let isRoot = !this.isRootSeen;
this.isRootSeen = true;
let body = program.body;
for (let i = 0, l = body.length; i < l; i++) {
let current = body[i],
strip = this.accept(current);
if (!strip) {
continue;
}
let _isPrevWhitespace = isPrevWhitespace(body, i, isRoot),
_isNextWhitespace = isNextWhitespace(body, i, isRoot),
openStandalone = strip.openStandalone && _isPrevWhitespace,
closeStandalone = strip.closeStandalone && _isNextWhitespace,
inlineStandalone =
strip.inlineStandalone && _isPrevWhitespace && _isNextWhitespace;
if (strip.close) {
omitRight(body, i, true);
}
if (strip.open) {
omitLeft(body, i, true);
}
if (doStandalone && inlineStandalone) {
omitRight(body, i);
if (omitLeft(body, i)) {
// If we are on a standalone node, save the indent info for partials
if (current.type === 'PartialStatement') {
// Pull out the whitespace from the final line
current.indent = /([ \t]+$)/.exec(body[i - 1].original)[1];
}
}
}
if (doStandalone && openStandalone) {
omitRight((current.program || current.inverse).body);
// Strip out the previous content node if it's whitespace only
omitLeft(body, i);
}
if (doStandalone && closeStandalone) {
// Always strip the next node
omitRight(body, i);
omitLeft((current.inverse || current.program).body);
}
}
return program;
};
WhitespaceControl.prototype.BlockStatement = WhitespaceControl.prototype.DecoratorBlock = WhitespaceControl.prototype.PartialBlockStatement = function(
block
) {
this.accept(block.program);
this.accept(block.inverse);
// Find the inverse program that is involed with whitespace stripping.
let program = block.program || block.inverse,
inverse = block.program && block.inverse,
firstInverse = inverse,
lastInverse = inverse;
if (inverse && inverse.chained) {
firstInverse = inverse.body[0].program;
// Walk the inverse chain to find the last inverse that is actually in the chain.
while (lastInverse.chained) {
lastInverse = lastInverse.body[lastInverse.body.length - 1].program;
}
}
let strip = {
open: block.openStrip.open,
close: block.closeStrip.close,
// Determine the standalone candiacy. Basically flag our content as being possibly standalone
// so our parent can determine if we actually are standalone
openStandalone: isNextWhitespace(program.body),
closeStandalone: isPrevWhitespace((firstInverse || program).body)
};
if (block.openStrip.close) {
omitRight(program.body, null, true);
}
if (inverse) {
let inverseStrip = block.inverseStrip;
if (inverseStrip.open) {
omitLeft(program.body, null, true);
}
if (inverseStrip.close) {
omitRight(firstInverse.body, null, true);
}
if (block.closeStrip.open) {
omitLeft(lastInverse.body, null, true);
}
// Find standalone else statments
if (
!this.options.ignoreStandalone &&
isPrevWhitespace(program.body) &&
isNextWhitespace(firstInverse.body)
) {
omitLeft(program.body);
omitRight(firstInverse.body);
}
} else if (block.closeStrip.open) {
omitLeft(program.body, null, true);
}
return strip;
};
WhitespaceControl.prototype.Decorator = WhitespaceControl.prototype.MustacheStatement = function(
mustache
) {
return mustache.strip;
};
WhitespaceControl.prototype.PartialStatement = WhitespaceControl.prototype.CommentStatement = function(
node
) {
/* istanbul ignore next */
let strip = node.strip || {};
return {
inlineStandalone: true,
open: strip.open,
close: strip.close
};
};
function isPrevWhitespace(body, i, isRoot) {
if (i === undefined) {
i = body.length;
}
// Nodes that end with newlines are considered whitespace (but are special
// cased for strip operations)
let prev = body[i - 1],
sibling = body[i - 2];
if (!prev) {
return isRoot;
}
if (prev.type === 'ContentStatement') {
return (sibling || !isRoot ? /\r?\n\s*?$/ : /(^|\r?\n)\s*?$/).test(
prev.original
);
}
}
function isNextWhitespace(body, i, isRoot) {
if (i === undefined) {
i = -1;
}
let next = body[i + 1],
sibling = body[i + 2];
if (!next) {
return isRoot;
}
if (next.type === 'ContentStatement') {
return (sibling || !isRoot ? /^\s*?\r?\n/ : /^\s*?(\r?\n|$)/).test(
next.original
);
}
}
// Marks the node to the right of the position as omitted.
// I.e. {{foo}}' ' will mark the ' ' node as omitted.
//
// If i is undefined, then the first child will be marked as such.
//
// If mulitple is truthy then all whitespace will be stripped out until non-whitespace
// content is met.
function omitRight(body, i, multiple) {
let current = body[i == null ? 0 : i + 1];
if (
!current ||
current.type !== 'ContentStatement' ||
(!multiple && current.rightStripped)
) {
return;
}
let original = current.value;
current.value = current.value.replace(
multiple ? /^\s+/ : /^[ \t]*\r?\n?/,
''
);
current.rightStripped = current.value !== original;
}
// Marks the node to the left of the position as omitted.
// I.e. ' '{{foo}} will mark the ' ' node as omitted.
//
// If i is undefined then the last child will be marked as such.
//
// If mulitple is truthy then all whitespace will be stripped out until non-whitespace
// content is met.
function omitLeft(body, i, multiple) {
let current = body[i == null ? body.length - 1 : i - 1];
if (
!current ||
current.type !== 'ContentStatement' ||
(!multiple && current.leftStripped)
) {
return;
}
// We omit the last node if it's whitespace only and not preceded by a non-content node.
let original = current.value;
current.value = current.value.replace(multiple ? /\s+$/ : /[ \t]+$/, '');
current.leftStripped = current.value !== original;
return current.leftStripped;
}
export default WhitespaceControl;
-29
View File
@@ -1,29 +0,0 @@
var Handlebars = require("handlebars");
// BEGIN(BROWSER)
(function() {
var classes = ["Lexer", "PrintVisitor", "Context", "Runtime", "Exception"];
var prop;
for(var i=0, l=classes.length; i<l; i++) {
var className = classes[i], klass = Handlebars[className];
klass.displayName = "new Handlebars." + className;
for(prop in klass) {
if(klass.hasOwnProperty(prop)) {
klass[prop].displayName = "Handlebars." + className + "#" + prop;
}
}
}
for(prop in Handlebars.Utils) {
if(Handlebars.Utils.hasOwnProperty(prop)) {
Handlebars.Utils[prop].displayName = "Handlebars.Utils." + prop;
}
}
Handlebars.parse.displayName = "Handlebars.parse";
Handlebars.print.displayName = "Handlebars.print";
Handlebars.compile.displayName = "Handlebars.compile";
})();
// END(BROWSER)
+5
View File
@@ -0,0 +1,5 @@
import registerInline from './decorators/inline';
export function registerDefaultDecorators(instance) {
registerInline(instance);
}
+22
View File
@@ -0,0 +1,22 @@
import { extend } from '../utils';
export default function(instance) {
instance.registerDecorator('inline', function(fn, props, container, options) {
let ret = fn;
if (!props.partials) {
props.partials = {};
ret = function(context, options) {
// Create a new partials stack frame prior to exec.
let original = container.partials;
container.partials = extend({}, original, props.partials);
let ret = fn(context, options);
container.partials = original;
return ret;
};
}
props.partials[options.args[0]] = options.fn;
return ret;
});
}
+68
View File
@@ -0,0 +1,68 @@
const errorProps = [
'description',
'fileName',
'lineNumber',
'endLineNumber',
'message',
'name',
'number',
'stack'
];
function Exception(message, node) {
let loc = node && node.loc,
line,
endLineNumber,
column,
endColumn;
if (loc) {
line = loc.start.line;
endLineNumber = loc.end.line;
column = loc.start.column;
endColumn = loc.end.column;
message += ' - ' + line + ':' + column;
}
let tmp = Error.prototype.constructor.call(this, message);
// Unfortunately errors are not enumerable in Chrome (at least), so `for prop in tmp` doesn't work.
for (let idx = 0; idx < errorProps.length; idx++) {
this[errorProps[idx]] = tmp[errorProps[idx]];
}
/* istanbul ignore else */
if (Error.captureStackTrace) {
Error.captureStackTrace(this, Exception);
}
try {
if (loc) {
this.lineNumber = line;
this.endLineNumber = endLineNumber;
// Work around issue under safari where we can't directly set the column value
/* istanbul ignore next */
if (Object.defineProperty) {
Object.defineProperty(this, 'column', {
value: column,
enumerable: true
});
Object.defineProperty(this, 'endColumn', {
value: endColumn,
enumerable: true
});
} else {
this.column = column;
this.endColumn = endColumn;
}
}
} catch (nop) {
/* Ignore if the browser is very particular */
}
}
Exception.prototype = new Error();
export default Exception;
+26
View File
@@ -0,0 +1,26 @@
import registerBlockHelperMissing from './helpers/block-helper-missing';
import registerEach from './helpers/each';
import registerHelperMissing from './helpers/helper-missing';
import registerIf from './helpers/if';
import registerLog from './helpers/log';
import registerLookup from './helpers/lookup';
import registerWith from './helpers/with';
export function registerDefaultHelpers(instance) {
registerBlockHelperMissing(instance);
registerEach(instance);
registerHelperMissing(instance);
registerIf(instance);
registerLog(instance);
registerLookup(instance);
registerWith(instance);
}
export function moveHelperToHooks(instance, helperName, keepHelper) {
if (instance.helpers[helperName]) {
instance.hooks[helperName] = instance.helpers[helperName];
if (!keepHelper) {
delete instance.helpers[helperName];
}
}
}
@@ -0,0 +1,35 @@
import { appendContextPath, createFrame, isArray } from '../utils';
export default function(instance) {
instance.registerHelper('blockHelperMissing', function(context, options) {
let inverse = options.inverse,
fn = options.fn;
if (context === true) {
return fn(this);
} else if (context === false || context == null) {
return inverse(this);
} else if (isArray(context)) {
if (context.length > 0) {
if (options.ids) {
options.ids = [options.name];
}
return instance.helpers.each(context, options);
} else {
return inverse(this);
}
} else {
if (options.data && options.ids) {
let data = createFrame(options.data);
data.contextPath = appendContextPath(
options.data.contextPath,
options.name
);
options = { data: data };
}
return fn(context, options);
}
});
}
+101
View File
@@ -0,0 +1,101 @@
import {
appendContextPath,
blockParams,
createFrame,
isArray,
isFunction
} from '../utils';
import Exception from '../exception';
export default function(instance) {
instance.registerHelper('each', function(context, options) {
if (!options) {
throw new Exception('Must pass iterator to #each');
}
let fn = options.fn,
inverse = options.inverse,
i = 0,
ret = '',
data,
contextPath;
if (options.data && options.ids) {
contextPath =
appendContextPath(options.data.contextPath, options.ids[0]) + '.';
}
if (isFunction(context)) {
context = context.call(this);
}
if (options.data) {
data = createFrame(options.data);
}
function execIteration(field, index, last) {
if (data) {
data.key = field;
data.index = index;
data.first = index === 0;
data.last = !!last;
if (contextPath) {
data.contextPath = contextPath + field;
}
}
ret =
ret +
fn(context[field], {
data: data,
blockParams: blockParams(
[context[field], field],
[contextPath + field, null]
)
});
}
if (context && typeof context === 'object') {
if (isArray(context)) {
for (let j = context.length; i < j; i++) {
if (i in context) {
execIteration(i, i, i === context.length - 1);
}
}
} else if (typeof Symbol === 'function' && context[Symbol.iterator]) {
const newContext = [];
const iterator = context[Symbol.iterator]();
for (let it = iterator.next(); !it.done; it = iterator.next()) {
newContext.push(it.value);
}
context = newContext;
for (let j = context.length; i < j; i++) {
execIteration(i, i, i === context.length - 1);
}
} else {
let priorKey;
Object.keys(context).forEach(key => {
// We're running the iterations one step out of sync so we can detect
// the last iteration without have to scan the object twice and create
// an itermediate keys array.
if (priorKey !== undefined) {
execIteration(priorKey, i - 1);
}
priorKey = key;
i++;
});
if (priorKey !== undefined) {
execIteration(priorKey, i - 1, true);
}
}
}
if (i === 0) {
ret = inverse(this);
}
return ret;
});
}
+15
View File
@@ -0,0 +1,15 @@
import Exception from '../exception';
export default function(instance) {
instance.registerHelper('helperMissing', function(/* [args, ]options */) {
if (arguments.length === 1) {
// A missing field in a {{foo}} construct.
return undefined;
} else {
// Someone is actually trying to call something, blow up.
throw new Exception(
'Missing helper: "' + arguments[arguments.length - 1].name + '"'
);
}
});
}
+33
View File
@@ -0,0 +1,33 @@
import { isEmpty, isFunction } from '../utils';
import Exception from '../exception';
export default function(instance) {
instance.registerHelper('if', function(conditional, options) {
if (arguments.length != 2) {
throw new Exception('#if requires exactly one argument');
}
if (isFunction(conditional)) {
conditional = conditional.call(this);
}
// Default behavior is to render the positive path if the value is truthy and not empty.
// The `includeZero` option may be set to treat the condtional as purely not empty based on the
// behavior of isEmpty. Effectively this determines if 0 is handled by the positive path or negative.
if ((!options.hash.includeZero && !conditional) || isEmpty(conditional)) {
return options.inverse(this);
} else {
return options.fn(this);
}
});
instance.registerHelper('unless', function(conditional, options) {
if (arguments.length != 2) {
throw new Exception('#unless requires exactly one argument');
}
return instance.helpers['if'].call(this, conditional, {
fn: options.inverse,
inverse: options.fn,
hash: options.hash
});
});
}
+19
View File
@@ -0,0 +1,19 @@
export default function(instance) {
instance.registerHelper('log', function(/* message, options */) {
let args = [undefined],
options = arguments[arguments.length - 1];
for (let i = 0; i < arguments.length - 1; i++) {
args.push(arguments[i]);
}
let level = 1;
if (options.hash.level != null) {
level = options.hash.level;
} else if (options.data && options.data.level != null) {
level = options.data.level;
}
args[0] = level;
instance.log(...args);
});
}
+9
View File
@@ -0,0 +1,9 @@
export default function(instance) {
instance.registerHelper('lookup', function(obj, field, options) {
if (!obj) {
// Note for 5.0: Change to "obj == null" in 5.0
return obj;
}
return options.lookupProperty(obj, field);
});
}
+39
View File
@@ -0,0 +1,39 @@
import {
appendContextPath,
blockParams,
createFrame,
isEmpty,
isFunction
} from '../utils';
import Exception from '../exception';
export default function(instance) {
instance.registerHelper('with', function(context, options) {
if (arguments.length != 2) {
throw new Exception('#with requires exactly one argument');
}
if (isFunction(context)) {
context = context.call(this);
}
let fn = options.fn;
if (!isEmpty(context)) {
let data = options.data;
if (options.data && options.ids) {
data = createFrame(options.data);
data.contextPath = appendContextPath(
options.data.contextPath,
options.ids[0]
);
}
return fn(context, {
data: data,
blockParams: blockParams([context], [data && data.contextPath])
});
} else {
return options.inverse(this);
}
});
}
@@ -0,0 +1,11 @@
import { extend } from '../utils';
/**
* Create a new object with "null"-prototype to avoid truthy results on prototype properties.
* The resulting object can be used with "object[property]" to check if a property exists
* @param {...object} sources a varargs parameter of source objects that will be merged
* @returns {object}
*/
export function createNewLookupObject(...sources) {
return extend(Object.create(null), ...sources);
}
+70
View File
@@ -0,0 +1,70 @@
import { createNewLookupObject } from './create-new-lookup-object';
import logger from '../logger';
const loggedProperties = Object.create(null);
export function createProtoAccessControl(runtimeOptions) {
let defaultMethodWhiteList = Object.create(null);
defaultMethodWhiteList['constructor'] = false;
defaultMethodWhiteList['__defineGetter__'] = false;
defaultMethodWhiteList['__defineSetter__'] = false;
defaultMethodWhiteList['__lookupGetter__'] = false;
let defaultPropertyWhiteList = Object.create(null);
// eslint-disable-next-line no-proto
defaultPropertyWhiteList['__proto__'] = false;
return {
properties: {
whitelist: createNewLookupObject(
defaultPropertyWhiteList,
runtimeOptions.allowedProtoProperties
),
defaultValue: runtimeOptions.allowProtoPropertiesByDefault
},
methods: {
whitelist: createNewLookupObject(
defaultMethodWhiteList,
runtimeOptions.allowedProtoMethods
),
defaultValue: runtimeOptions.allowProtoMethodsByDefault
}
};
}
export function resultIsAllowed(result, protoAccessControl, propertyName) {
if (typeof result === 'function') {
return checkWhiteList(protoAccessControl.methods, propertyName);
} else {
return checkWhiteList(protoAccessControl.properties, propertyName);
}
}
function checkWhiteList(protoAccessControlForType, propertyName) {
if (protoAccessControlForType.whitelist[propertyName] !== undefined) {
return protoAccessControlForType.whitelist[propertyName] === true;
}
if (protoAccessControlForType.defaultValue !== undefined) {
return protoAccessControlForType.defaultValue;
}
logUnexpecedPropertyAccessOnce(propertyName);
return false;
}
function logUnexpecedPropertyAccessOnce(propertyName) {
if (loggedProperties[propertyName] !== true) {
loggedProperties[propertyName] = true;
logger.log(
'error',
`Handlebars: Access has been denied to resolve the property "${propertyName}" because it is not an "own property" of its parent.\n` +
`You can add a runtime option to disable the check or this warning:\n` +
`See https://handlebarsjs.com/api-reference/runtime-options.html#options-to-control-prototype-access for details`
);
}
}
export function resetLoggedProperties() {
Object.keys(loggedProperties).forEach(propertyName => {
delete loggedProperties[propertyName];
});
}
+13
View File
@@ -0,0 +1,13 @@
export function wrapHelper(helper, transformOptionsFn) {
if (typeof helper !== 'function') {
// This should not happen, but apparently it does in https://github.com/wycats/handlebars.js/issues/1639
// We try to make the wrapper least-invasive by not wrapping it, if the helper is not a function.
return helper;
}
let wrapper = function(/* dynamic arguments */) {
const options = arguments[arguments.length - 1];
arguments[arguments.length - 1] = transformOptionsFn(options);
return helper.apply(this, arguments);
};
return wrapper;
}
+39
View File
@@ -0,0 +1,39 @@
import { indexOf } from './utils';
let logger = {
methodMap: ['debug', 'info', 'warn', 'error'],
level: 'info',
// Maps a given level value to the `methodMap` indexes above.
lookupLevel: function(level) {
if (typeof level === 'string') {
let levelMap = indexOf(logger.methodMap, level.toLowerCase());
if (levelMap >= 0) {
level = levelMap;
} else {
level = parseInt(level, 10);
}
}
return level;
},
// Can be overridden in the host environment
log: function(level, ...message) {
level = logger.lookupLevel(level);
if (
typeof console !== 'undefined' &&
logger.lookupLevel(logger.level) <= level
) {
let method = logger.methodMap[level];
// eslint-disable-next-line no-console
if (!console[method]) {
method = 'log';
}
console[method](...message); // eslint-disable-line no-console
}
}
};
export default logger;
+23
View File
@@ -0,0 +1,23 @@
/* global globalThis */
export default function(Handlebars) {
/* istanbul ignore next */
// https://mathiasbynens.be/notes/globalthis
(function() {
if (typeof globalThis === 'object') return;
Object.prototype.__defineGetter__('__magic__', function() {
return this;
});
__magic__.globalThis = __magic__; // eslint-disable-line no-undef
delete Object.prototype.__magic__;
})();
const $Handlebars = globalThis.Handlebars;
/* istanbul ignore next */
Handlebars.noConflict = function() {
if (globalThis.Handlebars === Handlebars) {
globalThis.Handlebars = $Handlebars;
}
return Handlebars;
};
}
-130
View File
@@ -1,130 +0,0 @@
var Handlebars = require("handlebars");
require("handlebars/visitor");
// BEGIN(BROWSER)
Handlebars.PrintVisitor = function() { this.padding = 0; };
Handlebars.PrintVisitor.prototype = new Handlebars.Visitor();
Handlebars.PrintVisitor.prototype.pad = function(string, newline) {
var out = "";
for(var i=0,l=this.padding; i<l; i++) {
out = out + " ";
}
out = out + string;
if(newline !== false) { out = out + "\n"; }
return out;
};
Handlebars.PrintVisitor.prototype.program = function(program) {
var out = this.pad("PROGRAM:"),
statements = program.statements,
inverse = program.inverse,
i, l;
this.padding++;
for(i=0, l=statements.length; i<l; i++) {
out = out + this.accept(statements[i]);
}
this.padding--;
if(inverse) {
out = out + this.pad("{{^}}");
this.padding++;
for(i=0, l=inverse.statements.length; i<l; i++) {
out = out + this.accept(inverse.statements[i]);
}
}
this.padding--;
return out;
};
Handlebars.PrintVisitor.prototype.block = function(block) {
var out = "";
out = out + this.pad("BLOCK:");
this.padding++;
out = out + this.accept(block.mustache);
out = out + this.accept(block.program);
this.padding--;
return out;
};
Handlebars.PrintVisitor.prototype.inverse = function(block) {
var out = "";
out = out + this.pad("INVERSE:");
this.padding++;
out = out + this.accept(block.mustache);
out = out + this.accept(block.program);
this.padding--;
return out;
};
Handlebars.PrintVisitor.prototype.mustache = function(mustache) {
var params = mustache.params, paramStrings = [], hash;
for(var i=0, l=params.length; i<l; i++) {
paramStrings.push(this.accept(params[i]));
}
params = "[" + paramStrings.join(", ") + "]";
hash = mustache.hash ? " " + this.accept(mustache.hash) : "";
return this.pad("{{ " + this.accept(mustache.id) + " " + params + hash + " }}");
};
Handlebars.PrintVisitor.prototype.partial = function(partial) {
var content = this.accept(partial.id);
if(partial.context) { content = content + " " + this.accept(partial.context); }
return this.pad("{{> " + content + " }}");
};
Handlebars.PrintVisitor.prototype.hash = function(hash) {
var pairs = hash.pairs;
var joinedPairs = [], left, right;
for(var i=0, l=pairs.length; i<l; i++) {
left = pairs[i][0];
right = this.accept(pairs[i][1]);
joinedPairs.push( left + "=" + right );
}
return "HASH{" + joinedPairs.join(", ") + "}";
};
Handlebars.PrintVisitor.prototype.STRING = function(string) {
return '"' + string.string + '"';
};
Handlebars.PrintVisitor.prototype.ID = function(id) {
var path = id.parts.join("/");
if(id.parts.length > 1) {
return "PATH:" + path;
} else {
return "ID:" + path;
}
};
Handlebars.PrintVisitor.prototype.content = function(content) {
return this.pad("CONTENT[ '" + content.string + "' ]");
};
Handlebars.PrintVisitor.prototype.comment = function(comment) {
return this.pad("{{! '" + comment.comment + "' }}");
};
// END(BROWSER)
exports.PrintVisitor = Handlebars.PrintVisitor;
+450
View File
@@ -0,0 +1,450 @@
import * as Utils from './utils';
import Exception from './exception';
import {
COMPILER_REVISION,
createFrame,
LAST_COMPATIBLE_COMPILER_REVISION,
REVISION_CHANGES
} from './base';
import { moveHelperToHooks } from './helpers';
import { wrapHelper } from './internal/wrapHelper';
import {
createProtoAccessControl,
resultIsAllowed
} from './internal/proto-access';
export function checkRevision(compilerInfo) {
const compilerRevision = (compilerInfo && compilerInfo[0]) || 1,
currentRevision = COMPILER_REVISION;
if (
compilerRevision >= LAST_COMPATIBLE_COMPILER_REVISION &&
compilerRevision <= COMPILER_REVISION
) {
return;
}
if (compilerRevision < LAST_COMPATIBLE_COMPILER_REVISION) {
const runtimeVersions = REVISION_CHANGES[currentRevision],
compilerVersions = REVISION_CHANGES[compilerRevision];
throw new Exception(
'Template was precompiled with an older version of Handlebars than the current runtime. ' +
'Please update your precompiler to a newer version (' +
runtimeVersions +
') or downgrade your runtime to an older version (' +
compilerVersions +
').'
);
} else {
// Use the embedded version info since the runtime doesn't know about this revision yet
throw new Exception(
'Template was precompiled with a newer version of Handlebars than the current runtime. ' +
'Please update your runtime to a newer version (' +
compilerInfo[1] +
').'
);
}
}
export function template(templateSpec, env) {
/* istanbul ignore next */
if (!env) {
throw new Exception('No environment passed to template');
}
if (!templateSpec || !templateSpec.main) {
throw new Exception('Unknown template object: ' + typeof templateSpec);
}
templateSpec.main.decorator = templateSpec.main_d;
// Note: Using env.VM references rather than local var references throughout this section to allow
// for external users to override these as pseudo-supported APIs.
env.VM.checkRevision(templateSpec.compiler);
// backwards compatibility for precompiled templates with compiler-version 7 (<4.3.0)
const templateWasPrecompiledWithCompilerV7 =
templateSpec.compiler && templateSpec.compiler[0] === 7;
function invokePartialWrapper(partial, context, options) {
if (options.hash) {
context = Utils.extend({}, context, options.hash);
if (options.ids) {
options.ids[0] = true;
}
}
partial = env.VM.resolvePartial.call(this, partial, context, options);
let extendedOptions = Utils.extend({}, options, {
hooks: this.hooks,
protoAccessControl: this.protoAccessControl
});
let result = env.VM.invokePartial.call(
this,
partial,
context,
extendedOptions
);
if (result == null && env.compile) {
options.partials[options.name] = env.compile(
partial,
templateSpec.compilerOptions,
env
);
result = options.partials[options.name](context, extendedOptions);
}
if (result != null) {
if (options.indent) {
let lines = result.split('\n');
for (let i = 0, l = lines.length; i < l; i++) {
if (!lines[i] && i + 1 === l) {
break;
}
lines[i] = options.indent + lines[i];
}
result = lines.join('\n');
}
return result;
} else {
throw new Exception(
'The partial ' +
options.name +
' could not be compiled when running in runtime-only mode'
);
}
}
// Just add water
let container = {
strict: function(obj, name, loc) {
if (!obj || !(name in obj)) {
throw new Exception('"' + name + '" not defined in ' + obj, {
loc: loc
});
}
return container.lookupProperty(obj, name);
},
lookupProperty: function(parent, propertyName) {
let result = parent[propertyName];
if (result == null) {
return result;
}
if (Object.prototype.hasOwnProperty.call(parent, propertyName)) {
return result;
}
if (resultIsAllowed(result, container.protoAccessControl, propertyName)) {
return result;
}
return undefined;
},
lookup: function(depths, name) {
const len = depths.length;
for (let i = 0; i < len; i++) {
let result = depths[i] && container.lookupProperty(depths[i], name);
if (result != null) {
return depths[i][name];
}
}
},
lambda: function(current, context) {
return typeof current === 'function' ? current.call(context) : current;
},
escapeExpression: Utils.escapeExpression,
invokePartial: invokePartialWrapper,
fn: function(i) {
let ret = templateSpec[i];
ret.decorator = templateSpec[i + '_d'];
return ret;
},
programs: [],
program: function(i, data, declaredBlockParams, blockParams, depths) {
let programWrapper = this.programs[i],
fn = this.fn(i);
if (data || depths || blockParams || declaredBlockParams) {
programWrapper = wrapProgram(
this,
i,
fn,
data,
declaredBlockParams,
blockParams,
depths
);
} else if (!programWrapper) {
programWrapper = this.programs[i] = wrapProgram(this, i, fn);
}
return programWrapper;
},
data: function(value, depth) {
while (value && depth--) {
value = value._parent;
}
return value;
},
mergeIfNeeded: function(param, common) {
let obj = param || common;
if (param && common && param !== common) {
obj = Utils.extend({}, common, param);
}
return obj;
},
// An empty object to use as replacement for null-contexts
nullContext: Object.seal({}),
noop: env.VM.noop,
compilerInfo: templateSpec.compiler
};
function ret(context, options = {}) {
let data = options.data;
ret._setup(options);
if (!options.partial && templateSpec.useData) {
data = initData(context, data);
}
let depths,
blockParams = templateSpec.useBlockParams ? [] : undefined;
if (templateSpec.useDepths) {
if (options.depths) {
depths =
context != options.depths[0]
? [context].concat(options.depths)
: options.depths;
} else {
depths = [context];
}
}
function main(context /*, options*/) {
return (
'' +
templateSpec.main(
container,
context,
container.helpers,
container.partials,
data,
blockParams,
depths
)
);
}
main = executeDecorators(
templateSpec.main,
main,
container,
options.depths || [],
data,
blockParams
);
return main(context, options);
}
ret.isTop = true;
ret._setup = function(options) {
if (!options.partial) {
let mergedHelpers = Utils.extend({}, env.helpers, options.helpers);
wrapHelpersToPassLookupProperty(mergedHelpers, container);
container.helpers = mergedHelpers;
if (templateSpec.usePartial) {
// Use mergeIfNeeded here to prevent compiling global partials multiple times
container.partials = container.mergeIfNeeded(
options.partials,
env.partials
);
}
if (templateSpec.usePartial || templateSpec.useDecorators) {
container.decorators = Utils.extend(
{},
env.decorators,
options.decorators
);
}
container.hooks = {};
container.protoAccessControl = createProtoAccessControl(options);
let keepHelperInHelpers =
options.allowCallsToHelperMissing ||
templateWasPrecompiledWithCompilerV7;
moveHelperToHooks(container, 'helperMissing', keepHelperInHelpers);
moveHelperToHooks(container, 'blockHelperMissing', keepHelperInHelpers);
} else {
container.protoAccessControl = options.protoAccessControl; // internal option
container.helpers = options.helpers;
container.partials = options.partials;
container.decorators = options.decorators;
container.hooks = options.hooks;
}
};
ret._child = function(i, data, blockParams, depths) {
if (templateSpec.useBlockParams && !blockParams) {
throw new Exception('must pass block params');
}
if (templateSpec.useDepths && !depths) {
throw new Exception('must pass parent depths');
}
return wrapProgram(
container,
i,
templateSpec[i],
data,
0,
blockParams,
depths
);
};
return ret;
}
export function wrapProgram(
container,
i,
fn,
data,
declaredBlockParams,
blockParams,
depths
) {
function prog(context, options = {}) {
let currentDepths = depths;
if (
depths &&
context != depths[0] &&
!(context === container.nullContext && depths[0] === null)
) {
currentDepths = [context].concat(depths);
}
return fn(
container,
context,
container.helpers,
container.partials,
options.data || data,
blockParams && [options.blockParams].concat(blockParams),
currentDepths
);
}
prog = executeDecorators(fn, prog, container, depths, data, blockParams);
prog.program = i;
prog.depth = depths ? depths.length : 0;
prog.blockParams = declaredBlockParams || 0;
return prog;
}
/**
* This is currently part of the official API, therefore implementation details should not be changed.
*/
export function resolvePartial(partial, context, options) {
if (!partial) {
if (options.name === '@partial-block') {
partial = options.data['partial-block'];
} else {
partial = options.partials[options.name];
}
} else if (!partial.call && !options.name) {
// This is a dynamic partial that returned a string
options.name = partial;
partial = options.partials[partial];
}
return partial;
}
export function invokePartial(partial, context, options) {
// Use the current closure context to save the partial-block if this partial
const currentPartialBlock = options.data && options.data['partial-block'];
options.partial = true;
if (options.ids) {
options.data.contextPath = options.ids[0] || options.data.contextPath;
}
let partialBlock;
if (options.fn && options.fn !== noop) {
options.data = createFrame(options.data);
// Wrapper function to get access to currentPartialBlock from the closure
let fn = options.fn;
partialBlock = options.data['partial-block'] = function partialBlockWrapper(
context,
options = {}
) {
// Restore the partial-block from the closure for the execution of the block
// i.e. the part inside the block of the partial call.
options.data = createFrame(options.data);
options.data['partial-block'] = currentPartialBlock;
return fn(context, options);
};
if (fn.partials) {
options.partials = Utils.extend({}, options.partials, fn.partials);
}
}
if (partial === undefined && partialBlock) {
partial = partialBlock;
}
if (partial === undefined) {
throw new Exception('The partial ' + options.name + ' could not be found');
} else if (partial instanceof Function) {
return partial(context, options);
}
}
export function noop() {
return '';
}
function initData(context, data) {
if (!data || !('root' in data)) {
data = data ? createFrame(data) : {};
data.root = context;
}
return data;
}
function executeDecorators(fn, prog, container, depths, data, blockParams) {
if (fn.decorator) {
let props = {};
prog = fn.decorator(
prog,
props,
container,
depths && depths[0],
data,
blockParams,
depths
);
Utils.extend(prog, props);
}
return prog;
}
function wrapHelpersToPassLookupProperty(mergedHelpers, container) {
Object.keys(mergedHelpers).forEach(helperName => {
let helper = mergedHelpers[helperName];
mergedHelpers[helperName] = passLookupPropertyOption(helper, container);
});
}
function passLookupPropertyOption(helper, container) {
const lookupProperty = container.lookupProperty;
return wrapHelper(helper, options => {
return Utils.extend({ lookupProperty }, options);
});
}
+10
View File
@@ -0,0 +1,10 @@
// Build out our basic SafeString type
function SafeString(string) {
this.string = string;
}
SafeString.prototype.toString = SafeString.prototype.toHTML = function() {
return '' + this.string;
};
export default SafeString;
+109 -51
View File
@@ -1,58 +1,116 @@
var Handlebars = require("handlebars");
// BEGIN(BROWSER)
Handlebars.Exception = function(message) {
this.message = message;
const escape = {
'&': '&amp;',
'<': '&lt;',
'>': '&gt;',
'"': '&quot;',
"'": '&#x27;',
'`': '&#x60;',
'=': '&#x3D;'
};
// Build out our basic SafeString type
Handlebars.SafeString = function(string) {
this.string = string;
};
Handlebars.SafeString.prototype.toString = function() {
return this.string.toString();
};
const badChars = /[&<>"'`=]/g,
possible = /[&<>"'`=]/;
(function() {
var escape = {
"<": "&lt;",
">": "&gt;"
};
function escapeChar(chr) {
return escape[chr];
}
var badChars = /&(?!\w+;)|[<>]/g;
var possible = /[&<>]/;
var escapeChar = function(chr) {
return escape[chr] || "&amp;"
};
Handlebars.Utils = {
escapeExpression: function(string) {
// don't escape SafeStrings, since they're already safe
if (string instanceof Handlebars.SafeString) {
return string.toString();
} else if (string == null || string === false) {
return "";
}
if(!possible.test(string)) { return string; }
return string.replace(badChars, escapeChar);
},
isEmpty: function(value) {
if (typeof value === "undefined") {
return true;
} else if (value === null) {
return true;
} else if (value === false) {
return true;
} else if(Object.prototype.toString.call(value) === "[object Array]" && value.length === 0) {
return true;
} else {
return false;
export function extend(obj /* , ...source */) {
for (let i = 1; i < arguments.length; i++) {
for (let key in arguments[i]) {
if (Object.prototype.hasOwnProperty.call(arguments[i], key)) {
obj[key] = arguments[i][key];
}
}
};
})();
// END(BROWSER)
}
return obj;
}
export let toString = Object.prototype.toString;
// Sourced from lodash
// https://github.com/bestiejs/lodash/blob/master/LICENSE.txt
/* eslint-disable func-style */
let isFunction = function(value) {
return typeof value === 'function';
};
// fallback for older versions of Chrome and Safari
/* istanbul ignore next */
if (isFunction(/x/)) {
isFunction = function(value) {
return (
typeof value === 'function' &&
toString.call(value) === '[object Function]'
);
};
}
export { isFunction };
/* eslint-enable func-style */
/* istanbul ignore next */
export const isArray =
Array.isArray ||
function(value) {
return value && typeof value === 'object'
? toString.call(value) === '[object Array]'
: false;
};
// Older IE versions do not directly support indexOf so we must implement our own, sadly.
export function indexOf(array, value) {
for (let i = 0, len = array.length; i < len; i++) {
if (array[i] === value) {
return i;
}
}
return -1;
}
export function escapeExpression(string) {
if (typeof string !== 'string') {
// don't escape SafeStrings, since they're already safe
if (string && string.toHTML) {
return string.toHTML();
} else if (string == null) {
return '';
} else if (!string) {
return string + '';
}
// Force a string conversion as this will be done by the append regardless and
// the regex test will do this transparently behind the scenes, causing issues if
// an object's to string has escaped characters in it.
string = '' + string;
}
if (!possible.test(string)) {
return string;
}
return string.replace(badChars, escapeChar);
}
export function isEmpty(value) {
if (!value && value !== 0) {
return true;
} else if (isArray(value) && value.length === 0) {
return true;
} else {
return false;
}
}
export function createFrame(object) {
let frame = extend({}, object);
frame._parent = object;
return frame;
}
export function blockParams(params, ids) {
params.path = ids;
return params;
}
export function appendContextPath(contextPath, id) {
return (contextPath ? contextPath + '.' : '') + id;
}
-13
View File
@@ -1,13 +0,0 @@
var Handlebars = require("handlebars");
// BEGIN(BROWSER)
Handlebars.Visitor = function() {};
Handlebars.Visitor.prototype = {
accept: function(object) {
return this[object.type](object);
}
};
// END(BROWSER)
+26
View File
@@ -0,0 +1,26 @@
// USAGE:
// var handlebars = require('handlebars');
/* eslint-env node */
/* eslint-disable no-var */
// var local = handlebars.create();
var handlebars = require('../dist/cjs/handlebars')['default'];
var printer = require('../dist/cjs/handlebars/compiler/printer');
handlebars.PrintVisitor = printer.PrintVisitor;
handlebars.print = printer.print;
module.exports = handlebars;
// Publish a Node.js require() handler for .handlebars and .hbs files
function extension(module, filename) {
var fs = require('fs');
var templateString = fs.readFileSync(filename, 'utf8');
module.exports = handlebars.compile(templateString);
}
/* istanbul ignore else */
if (typeof require !== 'undefined' && require.extensions) {
require.extensions['.handlebars'] = extension;
require.extensions['.hbs'] = extension;
}
+341
View File
@@ -0,0 +1,341 @@
/* eslint-env node */
/* eslint-disable no-console */
import Async from 'neo-async';
import fs from 'fs';
import * as Handlebars from './handlebars';
import { basename } from 'path';
import { SourceMapConsumer, SourceNode } from 'source-map';
module.exports.loadTemplates = function(opts, callback) {
loadStrings(opts, function(err, strings) {
if (err) {
callback(err);
} else {
loadFiles(opts, function(err, files) {
if (err) {
callback(err);
} else {
opts.templates = strings.concat(files);
callback(undefined, opts);
}
});
}
});
};
function loadStrings(opts, callback) {
let strings = arrayCast(opts.string),
names = arrayCast(opts.name);
if (names.length !== strings.length && strings.length > 1) {
return callback(
new Handlebars.Exception(
'Number of names did not match the number of string inputs'
)
);
}
Async.map(
strings,
function(string, callback) {
if (string !== '-') {
callback(undefined, string);
} else {
// Load from stdin
let buffer = '';
process.stdin.setEncoding('utf8');
process.stdin.on('data', function(chunk) {
buffer += chunk;
});
process.stdin.on('end', function() {
callback(undefined, buffer);
});
}
},
function(err, strings) {
strings = strings.map((string, index) => ({
name: names[index],
path: names[index],
source: string
}));
callback(err, strings);
}
);
}
function loadFiles(opts, callback) {
// Build file extension pattern
let extension = (opts.extension || 'handlebars').replace(
/[\\^$*+?.():=!|{}\-[\]]/g,
function(arg) {
return '\\' + arg;
}
);
extension = new RegExp('\\.' + extension + '$');
let ret = [],
queue = (opts.files || []).map(template => ({ template, root: opts.root }));
Async.whilst(
() => queue.length,
function(callback) {
let { template: path, root } = queue.shift();
fs.stat(path, function(err, stat) {
if (err) {
return callback(
new Handlebars.Exception(`Unable to open template file "${path}"`)
);
}
if (stat.isDirectory()) {
opts.hasDirectory = true;
fs.readdir(path, function(err, children) {
/* istanbul ignore next : Race condition that being too lazy to test */
if (err) {
return callback(err);
}
children.forEach(function(file) {
let childPath = path + '/' + file;
if (
extension.test(childPath) ||
fs.statSync(childPath).isDirectory()
) {
queue.push({ template: childPath, root: root || path });
}
});
callback();
});
} else {
fs.readFile(path, 'utf8', function(err, data) {
/* istanbul ignore next : Race condition that being too lazy to test */
if (err) {
return callback(err);
}
if (opts.bom && data.indexOf('\uFEFF') === 0) {
data = data.substring(1);
}
// Clean the template name
let name = path;
if (!root) {
name = basename(name);
} else if (name.indexOf(root) === 0) {
name = name.substring(root.length + 1);
}
name = name.replace(extension, '');
ret.push({
path: path,
name: name,
source: data
});
callback();
});
}
});
},
function(err) {
if (err) {
callback(err);
} else {
callback(undefined, ret);
}
}
);
}
module.exports.cli = function(opts) {
if (opts.version) {
console.log(Handlebars.VERSION);
return;
}
if (!opts.templates.length && !opts.hasDirectory) {
throw new Handlebars.Exception(
'Must define at least one template or directory.'
);
}
if (opts.simple && opts.min) {
throw new Handlebars.Exception('Unable to minimize simple output');
}
const multiple = opts.templates.length !== 1 || opts.hasDirectory;
if (opts.simple && multiple) {
throw new Handlebars.Exception(
'Unable to output multiple templates in simple mode'
);
}
// Force simple mode if we have only one template and it's unnamed.
if (
!opts.amd &&
!opts.commonjs &&
opts.templates.length === 1 &&
!opts.templates[0].name
) {
opts.simple = true;
}
// Convert the known list into a hash
let known = {};
if (opts.known && !Array.isArray(opts.known)) {
opts.known = [opts.known];
}
if (opts.known) {
for (let i = 0, len = opts.known.length; i < len; i++) {
known[opts.known[i]] = true;
}
}
const objectName = opts.partial ? 'Handlebars.partials' : 'templates';
let output = new SourceNode();
if (!opts.simple) {
if (opts.amd) {
output.add(
"define(['" +
opts.handlebarPath +
'handlebars.runtime\'], function(Handlebars) {\n Handlebars = Handlebars["default"];'
);
} else if (opts.commonjs) {
output.add('var Handlebars = require("' + opts.commonjs + '");');
} else {
output.add('(function() {\n');
}
output.add(' var template = Handlebars.template, templates = ');
if (opts.namespace) {
output.add(opts.namespace);
output.add(' = ');
output.add(opts.namespace);
output.add(' || ');
}
output.add('{};\n');
}
opts.templates.forEach(function(template) {
let options = {
knownHelpers: known,
knownHelpersOnly: opts.o
};
if (opts.map) {
options.srcName = template.path;
}
if (opts.data) {
options.data = true;
}
let precompiled = Handlebars.precompile(template.source, options);
// If we are generating a source map, we have to reconstruct the SourceNode object
if (opts.map) {
let consumer = new SourceMapConsumer(precompiled.map);
precompiled = SourceNode.fromStringWithSourceMap(
precompiled.code,
consumer
);
}
if (opts.simple) {
output.add([precompiled, '\n']);
} else {
if (!template.name) {
throw new Handlebars.Exception('Name missing for template');
}
if (opts.amd && !multiple) {
output.add('return ');
}
output.add([
objectName,
"['",
template.name,
"'] = template(",
precompiled,
');\n'
]);
}
});
// Output the content
if (!opts.simple) {
if (opts.amd) {
if (multiple) {
output.add(['return ', objectName, ';\n']);
}
output.add('});');
} else if (!opts.commonjs) {
output.add('})();');
}
}
if (opts.map) {
output.add('\n//# sourceMappingURL=' + opts.map + '\n');
}
output = output.toStringWithSourceMap();
output.map = output.map + '';
if (opts.min) {
output = minify(output, opts.map);
}
if (opts.map) {
fs.writeFileSync(opts.map, output.map, 'utf8');
}
output = output.code;
if (opts.output) {
fs.writeFileSync(opts.output, output, 'utf8');
} else {
console.log(output);
}
};
function arrayCast(value) {
value = value != null ? value : [];
if (!Array.isArray(value)) {
value = [value];
}
return value;
}
/**
* Run uglify to minify the compiled template, if uglify exists in the dependencies.
*
* We are using `require` instead of `import` here, because es6-modules do not allow
* dynamic imports and uglify-js is an optional dependency. Since we are inside NodeJS here, this
* should not be a problem.
*
* @param {string} output the compiled template
* @param {string} sourceMapFile the file to write the source map to.
*/
function minify(output, sourceMapFile) {
try {
// Try to resolve uglify-js in order to see if it does exist
require.resolve('uglify-js');
} catch (e) {
if (e.code !== 'MODULE_NOT_FOUND') {
// Something else seems to be wrong
throw e;
}
// it does not exist!
console.error(
'Code minimization is disabled due to missing uglify-js dependency'
);
return output;
}
return require('uglify-js').minify(output.code, {
sourceMap: {
content: output.map,
url: sourceMapFile
}
});
}
+9
View File
@@ -0,0 +1,9 @@
module.exports = {
'check-coverage': true,
branches: 100,
lines: 100,
functions: 100,
statements: 100,
exclude: ['**/spec/**', '**/handlebars/compiler/parser.js'],
reporter: 'html'
};
+27479
View File
File diff suppressed because it is too large Load Diff
+134
View File
@@ -0,0 +1,134 @@
{
"name": "handlebars",
"barename": "handlebars",
"version": "4.7.8",
"description": "Handlebars provides the power necessary to let you build semantic templates effectively with no frustration",
"homepage": "https://handlebarsjs.com/",
"keywords": [
"handlebars",
"mustache",
"template",
"html"
],
"repository": {
"type": "git",
"url": "https://github.com/handlebars-lang/handlebars.js.git"
},
"author": "Yehuda Katz",
"license": "MIT",
"readmeFilename": "README.md",
"engines": {
"node": ">=0.4.7"
},
"dependencies": {
"minimist": "^1.2.5",
"neo-async": "^2.6.2",
"source-map": "^0.6.1",
"wordwrap": "^1.0.0"
},
"optionalDependencies": {
"uglify-js": "^3.1.4"
},
"devDependencies": {
"@playwright/test": "^1.17.1",
"aws-sdk": "^2.1.49",
"babel-loader": "^5.0.0",
"babel-runtime": "^5.1.10",
"benchmark": "~1.0",
"chai": "^4.2.0",
"concurrently": "^5.0.0",
"dirty-chai": "^2.0.1",
"dtslint": "^0.5.5",
"dustjs-linkedin": "^2.0.2",
"eco": "~1.1.0-rc-3",
"eslint": "^6.7.2",
"eslint-config-prettier": "^6.7.0",
"eslint-plugin-compat": "^3.13.0",
"eslint-plugin-es5": "^1.4.1",
"fs-extra": "^8.1.0",
"grunt": "^1.0.4",
"grunt-babel": "^5.0.0",
"grunt-cli": "^1",
"grunt-contrib-clean": "^1",
"grunt-contrib-concat": "^1",
"grunt-contrib-connect": "^1",
"grunt-contrib-copy": "^1",
"grunt-contrib-requirejs": "^1",
"grunt-contrib-uglify": "^1",
"grunt-contrib-watch": "^1.1.0",
"grunt-shell": "^4.0.0",
"grunt-webpack": "^1.0.8",
"husky": "^3.1.0",
"jison": "~0.3.0",
"lint-staged": "^9.5.0",
"mocha": "^5",
"mock-stdin": "^0.3.0",
"mustache": "^2.1.3",
"nyc": "^14.1.1",
"prettier": "^1.19.1",
"semver": "^5.0.1",
"sinon": "^7.5.0",
"typescript": "^3.4.3",
"underscore": "^1.5.1",
"webpack": "^1.12.6",
"webpack-dev-server": "^1.12.1"
},
"main": "lib/index.js",
"types": "types/index.d.ts",
"browser": "./dist/cjs/handlebars.js",
"bin": {
"handlebars": "bin/handlebars"
},
"scripts": {
"build": "grunt build",
"release": "npm run build && grunt release",
"format": "prettier --write '**/*.js' && eslint --fix .",
"lint": "npm run lint:eslint && npm run lint:prettier && npm run lint:types",
"lint:eslint": "eslint --max-warnings 0 .",
"lint:prettier": "prettier --check '**/*.js'",
"lint:types": "dtslint types",
"test": "npm run test:mocha",
"test:mocha": "grunt build && grunt test",
"test:browser": "playwright test --config tests/browser/playwright.config.js tests/browser/spec.js",
"test:integration": "grunt integration-tests",
"test:serve": "grunt connect:server:keepalive",
"extensive-tests-and-publish-to-aws": "npx mocha tasks/tests/ && grunt --stack extensive-tests-and-publish-to-aws",
"--- combined tasks ---": "",
"check-before-pull-request": "concurrently --kill-others-on-fail npm:lint npm:test"
},
"jspm": {
"main": "handlebars",
"directories": {
"lib": "dist/amd"
},
"buildConfig": {
"minify": true
}
},
"files": [
"bin",
"dist/*.js",
"dist/amd/**/*.js",
"dist/cjs/**/*.js",
"lib",
"release-notes.md",
"runtime.js",
"types/*.d.ts",
"runtime.d.ts"
],
"husky": {
"hooks": {
"pre-commit": "lint-staged"
}
},
"lint-staged": {
"*.{js,css,json}": [
"prettier --write",
"git add"
],
"*.js": [
"eslint --fix",
"git add"
]
}
}
+5
View File
@@ -0,0 +1,5 @@
module.exports = {
tabWidth: 2,
semi: true,
singleQuote: true
};
+1102
View File
File diff suppressed because it is too large Load Diff
+5
View File
@@ -0,0 +1,5 @@
import Handlebars = require('handlebars')
declare module "handlebars/runtime" {
}
+3
View File
@@ -0,0 +1,3 @@
// Create a simple path alias to allow browserify to resolve
// the runtime on a supported path.
module.exports = require('./dist/cjs/handlebars.runtime')['default'];
+47
View File
@@ -0,0 +1,47 @@
{
"extends": [
"../.eslintrc.js",
"plugin:es5/no-es2015",
"prettier"
],
"plugins": [
"es5"
],
"globals": {
"CompilerContext": true,
"Handlebars": true,
"handlebarsEnv": true,
"shouldCompileTo": true,
"shouldCompileToWithPartials": true,
"shouldThrow": true,
"expectTemplate": true,
"compileWithPartials": true,
"console": true,
"require": true,
"suite": true,
"equal": true,
"equals": true,
"test": true,
"testBoth": true,
"raises": true,
"deepEqual": true,
"start": true,
"stop": true,
"ok": true,
"sinon": true,
"strictEqual": true,
"define": true,
"expect": true,
"chai": true
},
"env": {
"mocha": true
},
"rules": {
// Disabling for tests, for now.
"no-path-concat": "off",
"no-var": "off",
"dot-notation": "off"
}
}
-100
View File
@@ -1,100 +0,0 @@
require "spec_helper"
class TestContext
class TestModule
attr_reader :name, :tests
def initialize(name)
@name = name
@tests = []
end
end
attr_reader :modules
def initialize
@modules = []
end
def module(name)
@modules << TestModule.new(name)
end
def test(name, function)
@modules.last.tests << [name, function]
end
end
test_context = TestContext.new
js_context = Handlebars::Spec::CONTEXT
Module.new do
extend Test::Unit::Assertions
def self.js_backtrace(context)
begin
context.eval("throw")
rescue V8::JSError => e
return e.backtrace(:javascript)
end
end
js_context["p"] = proc do |str|
p str
end
js_context["ok"] = proc do |ok, message|
js_context["$$RSPEC1$$"] = ok
result = js_context.eval("!!$$RSPEC1$$")
message ||= "#{ok} was not truthy"
unless result
backtrace = js_backtrace(js_context)
message << "\n#{backtrace.join("\n")}"
end
assert result, message
end
js_context["equals"] = proc do |first, second, message|
js_context["$$RSPEC1$$"] = first
js_context["$$RSPEC2$$"] = second
result = js_context.eval("$$RSPEC1$$ == $$RSPEC2$$")
message ||= "#{first} did not == #{second}"
unless result
backtrace = js_backtrace(js_context)
message << "\n#{backtrace.join("\n")}"
end
assert result, message
end
js_context["equal"] = js_context["equals"]
js_context["module"] = proc do |name|
test_context.module(name)
end
js_context["test"] = proc do |name, function|
test_context.test(name, function)
end
local = Regexp.escape(File.expand_path(Dir.pwd))
qunit_spec = File.expand_path("../qunit_spec.js", __FILE__)
js_context.load(qunit_spec.sub(/^#{local}\//, ''))
end
test_context.modules.each do |mod|
describe mod.name do
mod.tests.each do |name, function|
it name do
function.call
end
end
end
end
+108
View File
@@ -0,0 +1,108 @@
<html>
<head>
<title>Mocha</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="/node_modules/mocha/mocha.css" />
<style>
.headless .suite > h1,
.headless .test.pass {
display: none;
}
</style>
<script>
// Show only errors in "headless", non-interactive mode.
if (/headless=true/.test(location.href)) {
document.documentElement.className = 'headless';
}
</script>
<script src="/node_modules/sinon/pkg/sinon.js"></script>
<script src="/node_modules/chai/chai.js"></script>
<script src="/node_modules/dirty-chai/lib/dirty-chai.js"></script>
<script src="/node_modules/mocha/mocha.js"></script>
<script>
window.expect = chai.expect;
mocha.setup('bdd');
</script>
<script src="/spec/vendor/json2.js"></script>
<script src="/spec/vendor/require.js"></script>
<script src="/spec/env/common.js"></script>
<script>
var requireFailure;
requirejs.config({
paths: {
'handlebars.runtime': '/dist/handlebars.runtime.amd'
}
});
requirejs.onError = function (err) {
requireFailure = err;
};
</script>
<script>
onload = function(){
require(['handlebars.runtime'], function(Handlebars) {
describe('runtime', function() {
it('should load', function() {
equal(!!Handlebars['default'].template, true);
equal(!!Handlebars['default'].VERSION, true);
});
});
mocha.globals(['mochaResults'])
// The test harness leaks under FF. We should have decent global leak coverage from other tests
if (!navigator.userAgent.match(/Firefox\/([\d.]+)/)) {
mocha.checkLeaks();
}
var runner = mocha.run();
// Reporting to test-runner
var failedTests = [];
runner.on('end', function(){
window.mochaResults = runner.stats;
window.mochaResults.reports = failedTests;
});
runner.on('fail', logFailure);
// Inject any require initilizer failures into the first test so they are properly
// reported.
if (requireFailure) {
runner.on('hook end', function(hook){
if (requireFailure) {
runner.uncaught(requireFailure);
requireFailure = undefined;
}
});
}
function logFailure(test, err){
var flattenTitles = function(test){
var titles = [];
while (test.parent.title){
titles.push(test.parent.title);
test = test.parent;
}
return titles.reverse();
};
failedTests.push({
name: test.title,
result: false,
message: err.message,
stack: err.stack,
titles: flattenTitles(test)
});
}
});
};
</script>
</head>
<body>
<div id="mocha"></div>
</body>
</html>
+128
View File
@@ -0,0 +1,128 @@
<!doctype html>
<html>
<head>
<title>Mocha</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="/node_modules/mocha/mocha.css" />
<style>
.headless .suite > h1,
.headless .test.pass {
display: none;
}
</style>
<script>
// Show only errors in "headless", non-interactive mode.
if (/headless=true/.test(location.href)) {
document.documentElement.className = 'headless';
}
</script>
<script src="/node_modules/sinon/pkg/sinon.js"></script>
<script src="/node_modules/chai/chai.js"></script>
<script src="/node_modules/dirty-chai/lib/dirty-chai.js"></script>
<script src="/node_modules/mocha/mocha.js"></script>
<script>
window.expect = chai.expect;
mocha.setup('bdd');
</script>
<script src="/spec/vendor/json2.js"></script>
<script src="/spec/vendor/require.js"></script>
<script src="/spec/env/common.js"></script>
<script>
var requireFailure;
requirejs.config({
paths: {
handlebars: '/dist/handlebars.amd',
tests: '/tmp/tests'
}
});
requirejs.onError = function (err) {
requireFailure = err;
};
var CompilerContext = {
compile: function(template, options) {
var templateSpec = handlebarsEnv.precompile(template, options);
return handlebarsEnv.template(safeEval(templateSpec));
},
compileWithPartial: function(template, options) {
return handlebarsEnv.compile(template, options);
}
};
function safeEval(templateSpec) {
try {
var ret;
eval('ret = ' + templateSpec);
return ret;
} catch (err) {
console.error(templateSpec);
throw err;
}
}
</script>
<script>
onload = function(){
require(['handlebars'], function(Handlebars) {
window.Handlebars = Handlebars['default'];
require(['tests'], function(Handlebars) {
mocha.globals(['mochaResults'])
// The test harness leaks under FF. We should have decent global leak coverage from other tests
if (!navigator.userAgent.match(/Firefox\/([\d.]+)/)) {
mocha.checkLeaks();
}
var runner = mocha.run();
// Reporting to test-runner
var failedTests = [];
runner.on('end', function(){
window.mochaResults = runner.stats;
window.mochaResults.reports = failedTests;
});
runner.on('fail', logFailure);
// Inject any require initilizer failures into the first test so they are properly
// reported.
if (requireFailure) {
runner.on('hook end', function(hook){
if (requireFailure) {
runner.uncaught(requireFailure);
requireFailure = undefined;
}
});
}
function logFailure(test, err){
function flattenTitles(test){
var titles = [];
while (test.parent.title){
titles.push(test.parent.title);
test = test.parent;
}
return titles.reverse();
}
failedTests.push({
name: test.title,
result: false,
message: err.message,
stack: err.stack,
titles: flattenTitles(test)
});
}
});
});
};
</script>
</head>
<body>
<div id="mocha"></div>
</body>
</html>
+1
View File
@@ -0,0 +1 @@
a
View File
+1
View File
@@ -0,0 +1 @@
{{foo}}
+1
View File
@@ -0,0 +1 @@
Hello, {{name}}!
+6
View File
@@ -0,0 +1,6 @@
{{#someHelper true}}
<div>Some known helper</div>
{{#anotherHelper true}}
<div>Another known helper</div>
{{/anotherHelper}}
{{/someHelper}}
+1
View File
@@ -0,0 +1 @@
<div>This is a test</div>
@@ -0,0 +1 @@
<div>Test Partial</div>
+431
View File
@@ -0,0 +1,431 @@
describe('ast', function() {
if (!Handlebars.AST) {
return;
}
var AST = Handlebars.AST;
describe('BlockStatement', function() {
it('should throw on mustache mismatch', function() {
shouldThrow(
function() {
handlebarsEnv.parse('\n {{#foo}}{{/bar}}');
},
Handlebars.Exception,
"foo doesn't match bar - 2:5"
);
});
});
describe('helpers', function() {
describe('#helperExpression', function() {
it('should handle mustache statements', function() {
equals(
AST.helpers.helperExpression({
type: 'MustacheStatement',
params: [],
hash: undefined
}),
false
);
equals(
AST.helpers.helperExpression({
type: 'MustacheStatement',
params: [1],
hash: undefined
}),
true
);
equals(
AST.helpers.helperExpression({
type: 'MustacheStatement',
params: [],
hash: {}
}),
true
);
});
it('should handle block statements', function() {
equals(
AST.helpers.helperExpression({
type: 'BlockStatement',
params: [],
hash: undefined
}),
false
);
equals(
AST.helpers.helperExpression({
type: 'BlockStatement',
params: [1],
hash: undefined
}),
true
);
equals(
AST.helpers.helperExpression({
type: 'BlockStatement',
params: [],
hash: {}
}),
true
);
});
it('should handle subexpressions', function() {
equals(AST.helpers.helperExpression({ type: 'SubExpression' }), true);
});
it('should work with non-helper nodes', function() {
equals(AST.helpers.helperExpression({ type: 'Program' }), false);
equals(
AST.helpers.helperExpression({ type: 'PartialStatement' }),
false
);
equals(
AST.helpers.helperExpression({ type: 'ContentStatement' }),
false
);
equals(
AST.helpers.helperExpression({ type: 'CommentStatement' }),
false
);
equals(AST.helpers.helperExpression({ type: 'PathExpression' }), false);
equals(AST.helpers.helperExpression({ type: 'StringLiteral' }), false);
equals(AST.helpers.helperExpression({ type: 'NumberLiteral' }), false);
equals(AST.helpers.helperExpression({ type: 'BooleanLiteral' }), false);
equals(
AST.helpers.helperExpression({ type: 'UndefinedLiteral' }),
false
);
equals(AST.helpers.helperExpression({ type: 'NullLiteral' }), false);
equals(AST.helpers.helperExpression({ type: 'Hash' }), false);
equals(AST.helpers.helperExpression({ type: 'HashPair' }), false);
});
});
});
describe('Line Numbers', function() {
var ast, body;
function testColumns(node, firstLine, lastLine, firstColumn, lastColumn) {
equals(node.loc.start.line, firstLine);
equals(node.loc.start.column, firstColumn);
equals(node.loc.end.line, lastLine);
equals(node.loc.end.column, lastColumn);
}
/* eslint-disable no-multi-spaces */
ast = Handlebars.parse(
'line 1 {{line1Token}}\n' + // 1
' line 2 {{line2token}}\n' + // 2
' line 3 {{#blockHelperOnLine3}}\n' + // 3
'line 4{{line4token}}\n' + // 4
'line5{{else}}\n' + // 5
'{{line6Token}}\n' + // 6
'{{/blockHelperOnLine3}}\n' + // 7
'{{#open}}\n' + // 8
'{{else inverse}}\n' + // 9
'{{else}}\n' + // 10
'{{/open}}'
); // 11
/* eslint-enable no-multi-spaces */
body = ast.body;
it('gets ContentNode line numbers', function() {
var contentNode = body[0];
testColumns(contentNode, 1, 1, 0, 7);
});
it('gets MustacheStatement line numbers', function() {
var mustacheNode = body[1];
testColumns(mustacheNode, 1, 1, 7, 21);
});
it('gets line numbers correct when newlines appear', function() {
testColumns(body[2], 1, 2, 21, 8);
});
it('gets MustacheStatement line numbers correct across newlines', function() {
var secondMustacheStatement = body[3];
testColumns(secondMustacheStatement, 2, 2, 8, 22);
});
it('gets the block helper information correct', function() {
var blockHelperNode = body[5];
testColumns(blockHelperNode, 3, 7, 8, 23);
});
it('correctly records the line numbers the program of a block helper', function() {
var blockHelperNode = body[5],
program = blockHelperNode.program;
testColumns(program, 3, 5, 31, 5);
});
it('correctly records the line numbers of an inverse of a block helper', function() {
var blockHelperNode = body[5],
inverse = blockHelperNode.inverse;
testColumns(inverse, 5, 7, 13, 0);
});
it('correctly records the line number of chained inverses', function() {
var chainInverseNode = body[7];
testColumns(chainInverseNode.program, 8, 9, 9, 0);
testColumns(chainInverseNode.inverse, 9, 10, 16, 0);
testColumns(chainInverseNode.inverse.body[0].program, 9, 10, 16, 0);
testColumns(chainInverseNode.inverse.body[0].inverse, 10, 11, 8, 0);
});
});
describe('whitespace control', function() {
describe('parse', function() {
it('mustache', function() {
var ast = Handlebars.parse(' {{~comment~}} ');
equals(ast.body[0].value, '');
equals(ast.body[2].value, '');
});
it('block statements', function() {
var ast = Handlebars.parse(' {{# comment~}} \nfoo\n {{~/comment}}');
equals(ast.body[0].value, '');
equals(ast.body[1].program.body[0].value, 'foo');
});
});
describe('parseWithoutProcessing', function() {
it('mustache', function() {
var ast = Handlebars.parseWithoutProcessing(' {{~comment~}} ');
equals(ast.body[0].value, ' ');
equals(ast.body[2].value, ' ');
});
it('block statements', function() {
var ast = Handlebars.parseWithoutProcessing(
' {{# comment~}} \nfoo\n {{~/comment}}'
);
equals(ast.body[0].value, ' ');
equals(ast.body[1].program.body[0].value, ' \nfoo\n ');
});
});
});
describe('standalone flags', function() {
describe('mustache', function() {
it('does not mark mustaches as standalone', function() {
var ast = Handlebars.parse(' {{comment}} ');
equals(!!ast.body[0].value, true);
equals(!!ast.body[2].value, true);
});
});
describe('blocks - parseWithoutProcessing', function() {
it('block mustaches', function() {
var ast = Handlebars.parseWithoutProcessing(
' {{# comment}} \nfoo\n {{else}} \n bar \n {{/comment}} '
),
block = ast.body[1];
equals(ast.body[0].value, ' ');
equals(block.program.body[0].value, ' \nfoo\n ');
equals(block.inverse.body[0].value, ' \n bar \n ');
equals(ast.body[2].value, ' ');
});
it('initial block mustaches', function() {
var ast = Handlebars.parseWithoutProcessing(
'{{# comment}} \nfoo\n {{/comment}}'
),
block = ast.body[0];
equals(block.program.body[0].value, ' \nfoo\n ');
});
it('mustaches with children', function() {
var ast = Handlebars.parseWithoutProcessing(
'{{# comment}} \n{{foo}}\n {{/comment}}'
),
block = ast.body[0];
equals(block.program.body[0].value, ' \n');
equals(block.program.body[1].path.original, 'foo');
equals(block.program.body[2].value, '\n ');
});
it('nested block mustaches', function() {
var ast = Handlebars.parseWithoutProcessing(
'{{#foo}} \n{{# comment}} \nfoo\n {{else}} \n bar \n {{/comment}} \n{{/foo}}'
),
body = ast.body[0].program.body,
block = body[1];
equals(body[0].value, ' \n');
equals(block.program.body[0].value, ' \nfoo\n ');
equals(block.inverse.body[0].value, ' \n bar \n ');
});
it('column 0 block mustaches', function() {
var ast = Handlebars.parseWithoutProcessing(
'test\n{{# comment}} \nfoo\n {{else}} \n bar \n {{/comment}} '
),
block = ast.body[1];
equals(ast.body[0].omit, undefined);
equals(block.program.body[0].value, ' \nfoo\n ');
equals(block.inverse.body[0].value, ' \n bar \n ');
equals(ast.body[2].value, ' ');
});
});
describe('blocks', function() {
it('marks block mustaches as standalone', function() {
var ast = Handlebars.parse(
' {{# comment}} \nfoo\n {{else}} \n bar \n {{/comment}} '
),
block = ast.body[1];
equals(ast.body[0].value, '');
equals(block.program.body[0].value, 'foo\n');
equals(block.inverse.body[0].value, ' bar \n');
equals(ast.body[2].value, '');
});
it('marks initial block mustaches as standalone', function() {
var ast = Handlebars.parse('{{# comment}} \nfoo\n {{/comment}}'),
block = ast.body[0];
equals(block.program.body[0].value, 'foo\n');
});
it('marks mustaches with children as standalone', function() {
var ast = Handlebars.parse('{{# comment}} \n{{foo}}\n {{/comment}}'),
block = ast.body[0];
equals(block.program.body[0].value, '');
equals(block.program.body[1].path.original, 'foo');
equals(block.program.body[2].value, '\n');
});
it('marks nested block mustaches as standalone', function() {
var ast = Handlebars.parse(
'{{#foo}} \n{{# comment}} \nfoo\n {{else}} \n bar \n {{/comment}} \n{{/foo}}'
),
body = ast.body[0].program.body,
block = body[1];
equals(body[0].value, '');
equals(block.program.body[0].value, 'foo\n');
equals(block.inverse.body[0].value, ' bar \n');
equals(body[0].value, '');
});
it('does not mark nested block mustaches as standalone', function() {
var ast = Handlebars.parse(
'{{#foo}} {{# comment}} \nfoo\n {{else}} \n bar \n {{/comment}} {{/foo}}'
),
body = ast.body[0].program.body,
block = body[1];
equals(body[0].omit, undefined);
equals(block.program.body[0].value, ' \nfoo\n');
equals(block.inverse.body[0].value, ' bar \n ');
equals(body[0].omit, undefined);
});
it('does not mark nested initial block mustaches as standalone', function() {
var ast = Handlebars.parse(
'{{#foo}}{{# comment}} \nfoo\n {{else}} \n bar \n {{/comment}}{{/foo}}'
),
body = ast.body[0].program.body,
block = body[0];
equals(block.program.body[0].value, ' \nfoo\n');
equals(block.inverse.body[0].value, ' bar \n ');
equals(body[0].omit, undefined);
});
it('marks column 0 block mustaches as standalone', function() {
var ast = Handlebars.parse(
'test\n{{# comment}} \nfoo\n {{else}} \n bar \n {{/comment}} '
),
block = ast.body[1];
equals(ast.body[0].omit, undefined);
equals(block.program.body[0].value, 'foo\n');
equals(block.inverse.body[0].value, ' bar \n');
equals(ast.body[2].value, '');
});
});
describe('partials - parseWithoutProcessing', function() {
it('simple partial', function() {
var ast = Handlebars.parseWithoutProcessing('{{> partial }} ');
equals(ast.body[1].value, ' ');
});
it('indented partial', function() {
var ast = Handlebars.parseWithoutProcessing(' {{> partial }} ');
equals(ast.body[0].value, ' ');
equals(ast.body[1].indent, '');
equals(ast.body[2].value, ' ');
});
});
describe('partials', function() {
it('marks partial as standalone', function() {
var ast = Handlebars.parse('{{> partial }} ');
equals(ast.body[1].value, '');
});
it('marks indented partial as standalone', function() {
var ast = Handlebars.parse(' {{> partial }} ');
equals(ast.body[0].value, '');
equals(ast.body[1].indent, ' ');
equals(ast.body[2].value, '');
});
it('marks those around content as not standalone', function() {
var ast = Handlebars.parse('a{{> partial }}');
equals(ast.body[0].omit, undefined);
ast = Handlebars.parse('{{> partial }}a');
equals(ast.body[1].omit, undefined);
});
});
describe('comments - parseWithoutProcessing', function() {
it('simple comment', function() {
var ast = Handlebars.parseWithoutProcessing('{{! comment }} ');
equals(ast.body[1].value, ' ');
});
it('indented comment', function() {
var ast = Handlebars.parseWithoutProcessing(' {{! comment }} ');
equals(ast.body[0].value, ' ');
equals(ast.body[2].value, ' ');
});
});
describe('comments', function() {
it('marks comment as standalone', function() {
var ast = Handlebars.parse('{{! comment }} ');
equals(ast.body[1].value, '');
});
it('marks indented comment as standalone', function() {
var ast = Handlebars.parse(' {{! comment }} ');
equals(ast.body[0].value, '');
equals(ast.body[2].value, '');
});
it('marks those around content as not standalone', function() {
var ast = Handlebars.parse('a{{! comment }}');
equals(ast.body[0].omit, undefined);
ast = Handlebars.parse('{{! comment }}a');
equals(ast.body[1].omit, undefined);
});
});
});
});
+596
View File
@@ -0,0 +1,596 @@
global.handlebarsEnv = null;
beforeEach(function() {
global.handlebarsEnv = Handlebars.create();
});
describe('basic context', function() {
it('most basic', function() {
expectTemplate('{{foo}}')
.withInput({ foo: 'foo' })
.toCompileTo('foo');
});
it('escaping', function() {
expectTemplate('\\{{foo}}')
.withInput({ foo: 'food' })
.toCompileTo('{{foo}}');
expectTemplate('content \\{{foo}}')
.withInput({ foo: 'food' })
.toCompileTo('content {{foo}}');
expectTemplate('\\\\{{foo}}')
.withInput({ foo: 'food' })
.toCompileTo('\\food');
expectTemplate('content \\\\{{foo}}')
.withInput({ foo: 'food' })
.toCompileTo('content \\food');
expectTemplate('\\\\ {{foo}}')
.withInput({ foo: 'food' })
.toCompileTo('\\\\ food');
});
it('compiling with a basic context', function() {
expectTemplate('Goodbye\n{{cruel}}\n{{world}}!')
.withInput({
cruel: 'cruel',
world: 'world'
})
.withMessage('It works if all the required keys are provided')
.toCompileTo('Goodbye\ncruel\nworld!');
});
it('compiling with a string context', function() {
expectTemplate('{{.}}{{length}}')
.withInput('bye')
.toCompileTo('bye3');
});
it('compiling with an undefined context', function() {
expectTemplate('Goodbye\n{{cruel}}\n{{world.bar}}!')
.withInput(undefined)
.toCompileTo('Goodbye\n\n!');
expectTemplate('{{#unless foo}}Goodbye{{../test}}{{test2}}{{/unless}}')
.withInput(undefined)
.toCompileTo('Goodbye');
});
it('comments', function() {
expectTemplate('{{! Goodbye}}Goodbye\n{{cruel}}\n{{world}}!')
.withInput({
cruel: 'cruel',
world: 'world'
})
.withMessage('comments are ignored')
.toCompileTo('Goodbye\ncruel\nworld!');
expectTemplate(' {{~! comment ~}} blah').toCompileTo('blah');
expectTemplate(' {{~!-- long-comment --~}} blah').toCompileTo(
'blah'
);
expectTemplate(' {{! comment ~}} blah').toCompileTo(' blah');
expectTemplate(' {{!-- long-comment --~}} blah').toCompileTo(
' blah'
);
expectTemplate(' {{~! comment}} blah').toCompileTo(' blah');
expectTemplate(' {{~!-- long-comment --}} blah').toCompileTo(
' blah'
);
});
it('boolean', function() {
var string = '{{#goodbye}}GOODBYE {{/goodbye}}cruel {{world}}!';
expectTemplate(string)
.withInput({
goodbye: true,
world: 'world'
})
.withMessage('booleans show the contents when true')
.toCompileTo('GOODBYE cruel world!');
expectTemplate(string)
.withInput({
goodbye: false,
world: 'world'
})
.withMessage('booleans do not show the contents when false')
.toCompileTo('cruel world!');
});
it('zeros', function() {
expectTemplate('num1: {{num1}}, num2: {{num2}}')
.withInput({
num1: 42,
num2: 0
})
.toCompileTo('num1: 42, num2: 0');
expectTemplate('num: {{.}}')
.withInput(0)
.toCompileTo('num: 0');
expectTemplate('num: {{num1/num2}}')
.withInput({ num1: { num2: 0 } })
.toCompileTo('num: 0');
});
it('false', function() {
/* eslint-disable no-new-wrappers */
expectTemplate('val1: {{val1}}, val2: {{val2}}')
.withInput({
val1: false,
val2: new Boolean(false)
})
.toCompileTo('val1: false, val2: false');
expectTemplate('val: {{.}}')
.withInput(false)
.toCompileTo('val: false');
expectTemplate('val: {{val1/val2}}')
.withInput({ val1: { val2: false } })
.toCompileTo('val: false');
expectTemplate('val1: {{{val1}}}, val2: {{{val2}}}')
.withInput({
val1: false,
val2: new Boolean(false)
})
.toCompileTo('val1: false, val2: false');
expectTemplate('val: {{{val1/val2}}}')
.withInput({ val1: { val2: false } })
.toCompileTo('val: false');
/* eslint-enable */
});
it('should handle undefined and null', function() {
expectTemplate('{{awesome undefined null}}')
.withInput({
awesome: function(_undefined, _null, options) {
return (
(_undefined === undefined) +
' ' +
(_null === null) +
' ' +
typeof options
);
}
})
.toCompileTo('true true object');
expectTemplate('{{undefined}}')
.withInput({
undefined: function() {
return 'undefined!';
}
})
.toCompileTo('undefined!');
expectTemplate('{{null}}')
.withInput({
null: function() {
return 'null!';
}
})
.toCompileTo('null!');
});
it('newlines', function() {
expectTemplate("Alan's\nTest").toCompileTo("Alan's\nTest");
expectTemplate("Alan's\rTest").toCompileTo("Alan's\rTest");
});
it('escaping text', function() {
expectTemplate("Awesome's")
.withMessage(
"text is escaped so that it doesn't get caught on single quotes"
)
.toCompileTo("Awesome's");
expectTemplate('Awesome\\')
.withMessage("text is escaped so that the closing quote can't be ignored")
.toCompileTo('Awesome\\');
expectTemplate('Awesome\\\\ foo')
.withMessage("text is escaped so that it doesn't mess up backslashes")
.toCompileTo('Awesome\\\\ foo');
expectTemplate('Awesome {{foo}}')
.withInput({ foo: '\\' })
.withMessage("text is escaped so that it doesn't mess up backslashes")
.toCompileTo('Awesome \\');
expectTemplate(" ' ' ")
.withMessage('double quotes never produce invalid javascript')
.toCompileTo(" ' ' ");
});
it('escaping expressions', function() {
expectTemplate('{{{awesome}}}')
.withInput({ awesome: "&'\\<>" })
.withMessage("expressions with 3 handlebars aren't escaped")
.toCompileTo("&'\\<>");
expectTemplate('{{&awesome}}')
.withInput({ awesome: "&'\\<>" })
.withMessage("expressions with {{& handlebars aren't escaped")
.toCompileTo("&'\\<>");
expectTemplate('{{awesome}}')
.withInput({ awesome: '&"\'`\\<>' })
.withMessage('by default expressions should be escaped')
.toCompileTo('&amp;&quot;&#x27;&#x60;\\&lt;&gt;');
expectTemplate('{{awesome}}')
.withInput({ awesome: 'Escaped, <b> looks like: &lt;b&gt;' })
.withMessage('escaping should properly handle amperstands')
.toCompileTo('Escaped, &lt;b&gt; looks like: &amp;lt;b&amp;gt;');
});
it("functions returning safestrings shouldn't be escaped", function() {
expectTemplate('{{awesome}}')
.withInput({
awesome: function() {
return new Handlebars.SafeString("&'\\<>");
}
})
.withMessage("functions returning safestrings aren't escaped")
.toCompileTo("&'\\<>");
});
it('functions', function() {
expectTemplate('{{awesome}}')
.withInput({
awesome: function() {
return 'Awesome';
}
})
.withMessage('functions are called and render their output')
.toCompileTo('Awesome');
expectTemplate('{{awesome}}')
.withInput({
awesome: function() {
return this.more;
},
more: 'More awesome'
})
.withMessage('functions are bound to the context')
.toCompileTo('More awesome');
});
it('functions with context argument', function() {
expectTemplate('{{awesome frank}}')
.withInput({
awesome: function(context) {
return context;
},
frank: 'Frank'
})
.withMessage('functions are called with context arguments')
.toCompileTo('Frank');
});
it('pathed functions with context argument', function() {
expectTemplate('{{bar.awesome frank}}')
.withInput({
bar: {
awesome: function(context) {
return context;
}
},
frank: 'Frank'
})
.withMessage('functions are called with context arguments')
.toCompileTo('Frank');
});
it('depthed functions with context argument', function() {
expectTemplate('{{#with frank}}{{../awesome .}}{{/with}}')
.withInput({
awesome: function(context) {
return context;
},
frank: 'Frank'
})
.withMessage('functions are called with context arguments')
.toCompileTo('Frank');
});
it('block functions with context argument', function() {
expectTemplate('{{#awesome 1}}inner {{.}}{{/awesome}}')
.withInput({
awesome: function(context, options) {
return options.fn(context);
}
})
.withMessage('block functions are called with context and options')
.toCompileTo('inner 1');
});
it('depthed block functions with context argument', function() {
expectTemplate(
'{{#with value}}{{#../awesome 1}}inner {{.}}{{/../awesome}}{{/with}}'
)
.withInput({
value: true,
awesome: function(context, options) {
return options.fn(context);
}
})
.withMessage('block functions are called with context and options')
.toCompileTo('inner 1');
});
it('block functions without context argument', function() {
expectTemplate('{{#awesome}}inner{{/awesome}}')
.withInput({
awesome: function(options) {
return options.fn(this);
}
})
.withMessage('block functions are called with options')
.toCompileTo('inner');
});
it('pathed block functions without context argument', function() {
expectTemplate('{{#foo.awesome}}inner{{/foo.awesome}}')
.withInput({
foo: {
awesome: function() {
return this;
}
}
})
.withMessage('block functions are called with options')
.toCompileTo('inner');
});
it('depthed block functions without context argument', function() {
expectTemplate(
'{{#with value}}{{#../awesome}}inner{{/../awesome}}{{/with}}'
)
.withInput({
value: true,
awesome: function() {
return this;
}
})
.withMessage('block functions are called with options')
.toCompileTo('inner');
});
it('paths with hyphens', function() {
expectTemplate('{{foo-bar}}')
.withInput({ 'foo-bar': 'baz' })
.withMessage('Paths can contain hyphens (-)')
.toCompileTo('baz');
expectTemplate('{{foo.foo-bar}}')
.withInput({ foo: { 'foo-bar': 'baz' } })
.withMessage('Paths can contain hyphens (-)')
.toCompileTo('baz');
expectTemplate('{{foo/foo-bar}}')
.withInput({ foo: { 'foo-bar': 'baz' } })
.withMessage('Paths can contain hyphens (-)')
.toCompileTo('baz');
});
it('nested paths', function() {
expectTemplate('Goodbye {{alan/expression}} world!')
.withInput({ alan: { expression: 'beautiful' } })
.withMessage('Nested paths access nested objects')
.toCompileTo('Goodbye beautiful world!');
});
it('nested paths with empty string value', function() {
expectTemplate('Goodbye {{alan/expression}} world!')
.withInput({ alan: { expression: '' } })
.withMessage('Nested paths access nested objects with empty string')
.toCompileTo('Goodbye world!');
});
it('literal paths', function() {
expectTemplate('Goodbye {{[@alan]/expression}} world!')
.withInput({ '@alan': { expression: 'beautiful' } })
.withMessage('Literal paths can be used')
.toCompileTo('Goodbye beautiful world!');
expectTemplate('Goodbye {{[foo bar]/expression}} world!')
.withInput({ 'foo bar': { expression: 'beautiful' } })
.withMessage('Literal paths can be used')
.toCompileTo('Goodbye beautiful world!');
});
it('literal references', function() {
expectTemplate('Goodbye {{[foo bar]}} world!')
.withInput({ 'foo bar': 'beautiful' })
.toCompileTo('Goodbye beautiful world!');
expectTemplate('Goodbye {{"foo bar"}} world!')
.withInput({ 'foo bar': 'beautiful' })
.toCompileTo('Goodbye beautiful world!');
expectTemplate("Goodbye {{'foo bar'}} world!")
.withInput({ 'foo bar': 'beautiful' })
.toCompileTo('Goodbye beautiful world!');
expectTemplate('Goodbye {{"foo[bar"}} world!')
.withInput({ 'foo[bar': 'beautiful' })
.toCompileTo('Goodbye beautiful world!');
expectTemplate('Goodbye {{"foo\'bar"}} world!')
.withInput({ "foo'bar": 'beautiful' })
.toCompileTo('Goodbye beautiful world!');
expectTemplate("Goodbye {{'foo\"bar'}} world!")
.withInput({ 'foo"bar': 'beautiful' })
.toCompileTo('Goodbye beautiful world!');
});
it("that current context path ({{.}}) doesn't hit helpers", function() {
expectTemplate('test: {{.}}')
.withInput(null)
.withHelpers({ helper: 'awesome' })
.toCompileTo('test: ');
});
it('complex but empty paths', function() {
expectTemplate('{{person/name}}')
.withInput({ person: { name: null } })
.toCompileTo('');
expectTemplate('{{person/name}}')
.withInput({ person: {} })
.toCompileTo('');
});
it('this keyword in paths', function() {
expectTemplate('{{#goodbyes}}{{this}}{{/goodbyes}}')
.withInput({ goodbyes: ['goodbye', 'Goodbye', 'GOODBYE'] })
.withMessage('This keyword in paths evaluates to current context')
.toCompileTo('goodbyeGoodbyeGOODBYE');
expectTemplate('{{#hellos}}{{this/text}}{{/hellos}}')
.withInput({
hellos: [{ text: 'hello' }, { text: 'Hello' }, { text: 'HELLO' }]
})
.withMessage('This keyword evaluates in more complex paths')
.toCompileTo('helloHelloHELLO');
});
it('this keyword nested inside path', function() {
expectTemplate('{{#hellos}}{{text/this/foo}}{{/hellos}}').toThrow(
Error,
'Invalid path: text/this - 1:13'
);
expectTemplate('{{[this]}}')
.withInput({ this: 'bar' })
.toCompileTo('bar');
expectTemplate('{{text/[this]}}')
.withInput({ text: { this: 'bar' } })
.toCompileTo('bar');
});
it('this keyword in helpers', function() {
var helpers = {
foo: function(value) {
return 'bar ' + value;
}
};
expectTemplate('{{#goodbyes}}{{foo this}}{{/goodbyes}}')
.withInput({ goodbyes: ['goodbye', 'Goodbye', 'GOODBYE'] })
.withHelpers(helpers)
.withMessage('This keyword in paths evaluates to current context')
.toCompileTo('bar goodbyebar Goodbyebar GOODBYE');
expectTemplate('{{#hellos}}{{foo this/text}}{{/hellos}}')
.withInput({
hellos: [{ text: 'hello' }, { text: 'Hello' }, { text: 'HELLO' }]
})
.withHelpers(helpers)
.withMessage('This keyword evaluates in more complex paths')
.toCompileTo('bar hellobar Hellobar HELLO');
});
it('this keyword nested inside helpers param', function() {
expectTemplate('{{#hellos}}{{foo text/this/foo}}{{/hellos}}').toThrow(
Error,
'Invalid path: text/this - 1:17'
);
expectTemplate('{{foo [this]}}')
.withInput({
foo: function(value) {
return value;
},
this: 'bar'
})
.toCompileTo('bar');
expectTemplate('{{foo text/[this]}}')
.withInput({
foo: function(value) {
return value;
},
text: { this: 'bar' }
})
.toCompileTo('bar');
});
it('pass string literals', function() {
expectTemplate('{{"foo"}}').toCompileTo('');
expectTemplate('{{"foo"}}')
.withInput({ foo: 'bar' })
.toCompileTo('bar');
expectTemplate('{{#"foo"}}{{.}}{{/"foo"}}')
.withInput({
foo: ['bar', 'baz']
})
.toCompileTo('barbaz');
});
it('pass number literals', function() {
expectTemplate('{{12}}').toCompileTo('');
expectTemplate('{{12}}')
.withInput({ '12': 'bar' })
.toCompileTo('bar');
expectTemplate('{{12.34}}').toCompileTo('');
expectTemplate('{{12.34}}')
.withInput({ '12.34': 'bar' })
.toCompileTo('bar');
expectTemplate('{{12.34 1}}')
.withInput({
'12.34': function(arg) {
return 'bar' + arg;
}
})
.toCompileTo('bar1');
});
it('pass boolean literals', function() {
expectTemplate('{{true}}').toCompileTo('');
expectTemplate('{{true}}')
.withInput({ '': 'foo' })
.toCompileTo('');
expectTemplate('{{false}}')
.withInput({ false: 'foo' })
.toCompileTo('foo');
});
it('should handle literals in subexpression', function() {
expectTemplate('{{foo (false)}}')
.withInput({
false: function() {
return 'bar';
}
})
.withHelper('foo', function(arg) {
return arg;
})
.toCompileTo('bar');
});
});
+456
View File
@@ -0,0 +1,456 @@
describe('blocks', function() {
it('array', function() {
var string = '{{#goodbyes}}{{text}}! {{/goodbyes}}cruel {{world}}!';
expectTemplate(string)
.withInput({
goodbyes: [
{ text: 'goodbye' },
{ text: 'Goodbye' },
{ text: 'GOODBYE' }
],
world: 'world'
})
.withMessage('Arrays iterate over the contents when not empty')
.toCompileTo('goodbye! Goodbye! GOODBYE! cruel world!');
expectTemplate(string)
.withInput({
goodbyes: [],
world: 'world'
})
.withMessage('Arrays ignore the contents when empty')
.toCompileTo('cruel world!');
});
it('array without data', function() {
expectTemplate(
'{{#goodbyes}}{{text}}{{/goodbyes}} {{#goodbyes}}{{text}}{{/goodbyes}}'
)
.withInput({
goodbyes: [
{ text: 'goodbye' },
{ text: 'Goodbye' },
{ text: 'GOODBYE' }
],
world: 'world'
})
.withCompileOptions({ compat: false })
.toCompileTo('goodbyeGoodbyeGOODBYE goodbyeGoodbyeGOODBYE');
});
it('array with @index', function() {
expectTemplate(
'{{#goodbyes}}{{@index}}. {{text}}! {{/goodbyes}}cruel {{world}}!'
)
.withInput({
goodbyes: [
{ text: 'goodbye' },
{ text: 'Goodbye' },
{ text: 'GOODBYE' }
],
world: 'world'
})
.withMessage('The @index variable is used')
.toCompileTo('0. goodbye! 1. Goodbye! 2. GOODBYE! cruel world!');
});
it('empty block', function() {
var string = '{{#goodbyes}}{{/goodbyes}}cruel {{world}}!';
expectTemplate(string)
.withInput({
goodbyes: [
{ text: 'goodbye' },
{ text: 'Goodbye' },
{ text: 'GOODBYE' }
],
world: 'world'
})
.withMessage('Arrays iterate over the contents when not empty')
.toCompileTo('cruel world!');
expectTemplate(string)
.withInput({
goodbyes: [],
world: 'world'
})
.withMessage('Arrays ignore the contents when empty')
.toCompileTo('cruel world!');
});
it('block with complex lookup', function() {
expectTemplate('{{#goodbyes}}{{text}} cruel {{../name}}! {{/goodbyes}}')
.withInput({
name: 'Alan',
goodbyes: [
{ text: 'goodbye' },
{ text: 'Goodbye' },
{ text: 'GOODBYE' }
]
})
.withMessage(
'Templates can access variables in contexts up the stack with relative path syntax'
)
.toCompileTo(
'goodbye cruel Alan! Goodbye cruel Alan! GOODBYE cruel Alan! '
);
});
it('multiple blocks with complex lookup', function() {
expectTemplate('{{#goodbyes}}{{../name}}{{../name}}{{/goodbyes}}')
.withInput({
name: 'Alan',
goodbyes: [
{ text: 'goodbye' },
{ text: 'Goodbye' },
{ text: 'GOODBYE' }
]
})
.toCompileTo('AlanAlanAlanAlanAlanAlan');
});
it('block with complex lookup using nested context', function() {
expectTemplate(
'{{#goodbyes}}{{text}} cruel {{foo/../name}}! {{/goodbyes}}'
).toThrow(Error);
});
it('block with deep nested complex lookup', function() {
expectTemplate(
'{{#outer}}Goodbye {{#inner}}cruel {{../sibling}} {{../../omg}}{{/inner}}{{/outer}}'
)
.withInput({
omg: 'OMG!',
outer: [{ sibling: 'sad', inner: [{ text: 'goodbye' }] }]
})
.toCompileTo('Goodbye cruel sad OMG!');
});
it('works with cached blocks', function() {
expectTemplate(
'{{#each person}}{{#with .}}{{first}} {{last}}{{/with}}{{/each}}'
)
.withCompileOptions({ data: false })
.withInput({
person: [
{ first: 'Alan', last: 'Johnson' },
{ first: 'Alan', last: 'Johnson' }
]
})
.toCompileTo('Alan JohnsonAlan Johnson');
});
describe('inverted sections', function() {
it('inverted sections with unset value', function() {
expectTemplate(
'{{#goodbyes}}{{this}}{{/goodbyes}}{{^goodbyes}}Right On!{{/goodbyes}}'
)
.withMessage("Inverted section rendered when value isn't set.")
.toCompileTo('Right On!');
});
it('inverted section with false value', function() {
expectTemplate(
'{{#goodbyes}}{{this}}{{/goodbyes}}{{^goodbyes}}Right On!{{/goodbyes}}'
)
.withInput({ goodbyes: false })
.withMessage('Inverted section rendered when value is false.')
.toCompileTo('Right On!');
});
it('inverted section with empty set', function() {
expectTemplate(
'{{#goodbyes}}{{this}}{{/goodbyes}}{{^goodbyes}}Right On!{{/goodbyes}}'
)
.withInput({ goodbyes: [] })
.withMessage('Inverted section rendered when value is empty set.')
.toCompileTo('Right On!');
});
it('block inverted sections', function() {
expectTemplate('{{#people}}{{name}}{{^}}{{none}}{{/people}}')
.withInput({ none: 'No people' })
.toCompileTo('No people');
});
it('chained inverted sections', function() {
expectTemplate('{{#people}}{{name}}{{else if none}}{{none}}{{/people}}')
.withInput({ none: 'No people' })
.toCompileTo('No people');
expectTemplate(
'{{#people}}{{name}}{{else if nothere}}fail{{else unless nothere}}{{none}}{{/people}}'
)
.withInput({ none: 'No people' })
.toCompileTo('No people');
expectTemplate(
'{{#people}}{{name}}{{else if none}}{{none}}{{else}}fail{{/people}}'
)
.withInput({ none: 'No people' })
.toCompileTo('No people');
});
it('chained inverted sections with mismatch', function() {
expectTemplate(
'{{#people}}{{name}}{{else if none}}{{none}}{{/if}}'
).toThrow(Error);
});
it('block inverted sections with empty arrays', function() {
expectTemplate('{{#people}}{{name}}{{^}}{{none}}{{/people}}')
.withInput({
none: 'No people',
people: []
})
.toCompileTo('No people');
});
});
describe('standalone sections', function() {
it('block standalone else sections', function() {
expectTemplate('{{#people}}\n{{name}}\n{{^}}\n{{none}}\n{{/people}}\n')
.withInput({ none: 'No people' })
.toCompileTo('No people\n');
expectTemplate('{{#none}}\n{{.}}\n{{^}}\n{{none}}\n{{/none}}\n')
.withInput({ none: 'No people' })
.toCompileTo('No people\n');
expectTemplate('{{#people}}\n{{name}}\n{{^}}\n{{none}}\n{{/people}}\n')
.withInput({ none: 'No people' })
.toCompileTo('No people\n');
});
it('block standalone else sections can be disabled', function() {
expectTemplate('{{#people}}\n{{name}}\n{{^}}\n{{none}}\n{{/people}}\n')
.withInput({ none: 'No people' })
.withCompileOptions({ ignoreStandalone: true })
.toCompileTo('\nNo people\n\n');
expectTemplate('{{#none}}\n{{.}}\n{{^}}\nFail\n{{/none}}\n')
.withInput({ none: 'No people' })
.withCompileOptions({ ignoreStandalone: true })
.toCompileTo('\nNo people\n\n');
});
it('block standalone chained else sections', function() {
expectTemplate(
'{{#people}}\n{{name}}\n{{else if none}}\n{{none}}\n{{/people}}\n'
)
.withInput({ none: 'No people' })
.toCompileTo('No people\n');
expectTemplate(
'{{#people}}\n{{name}}\n{{else if none}}\n{{none}}\n{{^}}\n{{/people}}\n'
)
.withInput({ none: 'No people' })
.toCompileTo('No people\n');
});
it('should handle nesting', function() {
expectTemplate('{{#data}}\n{{#if true}}\n{{.}}\n{{/if}}\n{{/data}}\nOK.')
.withInput({
data: [1, 3, 5]
})
.toCompileTo('1\n3\n5\nOK.');
});
});
describe('compat mode', function() {
it('block with deep recursive lookup lookup', function() {
expectTemplate(
'{{#outer}}Goodbye {{#inner}}cruel {{omg}}{{/inner}}{{/outer}}'
)
.withInput({ omg: 'OMG!', outer: [{ inner: [{ text: 'goodbye' }] }] })
.withCompileOptions({ compat: true })
.toCompileTo('Goodbye cruel OMG!');
});
it('block with deep recursive pathed lookup', function() {
expectTemplate(
'{{#outer}}Goodbye {{#inner}}cruel {{omg.yes}}{{/inner}}{{/outer}}'
)
.withInput({
omg: { yes: 'OMG!' },
outer: [{ inner: [{ yes: 'no', text: 'goodbye' }] }]
})
.withCompileOptions({ compat: true })
.toCompileTo('Goodbye cruel OMG!');
});
it('block with missed recursive lookup', function() {
expectTemplate(
'{{#outer}}Goodbye {{#inner}}cruel {{omg.yes}}{{/inner}}{{/outer}}'
)
.withInput({
omg: { no: 'OMG!' },
outer: [{ inner: [{ yes: 'no', text: 'goodbye' }] }]
})
.withCompileOptions({ compat: true })
.toCompileTo('Goodbye cruel ');
});
});
describe('decorators', function() {
it('should apply mustache decorators', function() {
expectTemplate('{{#helper}}{{*decorator}}{{/helper}}')
.withHelper('helper', function(options) {
return options.fn.run;
})
.withDecorator('decorator', function(fn) {
fn.run = 'success';
return fn;
})
.toCompileTo('success');
});
it('should apply allow undefined return', function() {
expectTemplate('{{#helper}}{{*decorator}}suc{{/helper}}')
.withHelper('helper', function(options) {
return options.fn() + options.fn.run;
})
.withDecorator('decorator', function(fn) {
fn.run = 'cess';
})
.toCompileTo('success');
});
it('should apply block decorators', function() {
expectTemplate(
'{{#helper}}{{#*decorator}}success{{/decorator}}{{/helper}}'
)
.withHelper('helper', function(options) {
return options.fn.run;
})
.withDecorator('decorator', function(fn, props, container, options) {
fn.run = options.fn();
return fn;
})
.toCompileTo('success');
});
it('should support nested decorators', function() {
expectTemplate(
'{{#helper}}{{#*decorator}}{{#*nested}}suc{{/nested}}cess{{/decorator}}{{/helper}}'
)
.withHelper('helper', function(options) {
return options.fn.run;
})
.withDecorators({
decorator: function(fn, props, container, options) {
fn.run = options.fn.nested + options.fn();
return fn;
},
nested: function(fn, props, container, options) {
props.nested = options.fn();
}
})
.toCompileTo('success');
});
it('should apply multiple decorators', function() {
expectTemplate(
'{{#helper}}{{#*decorator}}suc{{/decorator}}{{#*decorator}}cess{{/decorator}}{{/helper}}'
)
.withHelper('helper', function(options) {
return options.fn.run;
})
.withDecorator('decorator', function(fn, props, container, options) {
fn.run = (fn.run || '') + options.fn();
return fn;
})
.toCompileTo('success');
});
it('should access parent variables', function() {
expectTemplate('{{#helper}}{{*decorator foo}}{{/helper}}')
.withHelper('helper', function(options) {
return options.fn.run;
})
.withDecorator('decorator', function(fn, props, container, options) {
fn.run = options.args;
return fn;
})
.withInput({ foo: 'success' })
.toCompileTo('success');
});
it('should work with root program', function() {
var run;
expectTemplate('{{*decorator "success"}}')
.withDecorator('decorator', function(fn, props, container, options) {
equals(options.args[0], 'success');
run = true;
return fn;
})
.withInput({ foo: 'success' })
.toCompileTo('');
equals(run, true);
});
it('should fail when accessing variables from root', function() {
var run;
expectTemplate('{{*decorator foo}}')
.withDecorator('decorator', function(fn, props, container, options) {
equals(options.args[0], undefined);
run = true;
return fn;
})
.withInput({ foo: 'fail' })
.toCompileTo('');
equals(run, true);
});
describe('registration', function() {
it('unregisters', function() {
handlebarsEnv.decorators = {};
handlebarsEnv.registerDecorator('foo', function() {
return 'fail';
});
equals(!!handlebarsEnv.decorators.foo, true);
handlebarsEnv.unregisterDecorator('foo');
equals(handlebarsEnv.decorators.foo, undefined);
});
it('allows multiple globals', function() {
handlebarsEnv.decorators = {};
handlebarsEnv.registerDecorator({
foo: function() {},
bar: function() {}
});
equals(!!handlebarsEnv.decorators.foo, true);
equals(!!handlebarsEnv.decorators.bar, true);
handlebarsEnv.unregisterDecorator('foo');
handlebarsEnv.unregisterDecorator('bar');
equals(handlebarsEnv.decorators.foo, undefined);
equals(handlebarsEnv.decorators.bar, undefined);
});
it('fails with multiple and args', function() {
shouldThrow(
function() {
handlebarsEnv.registerDecorator(
{
world: function() {
return 'world!';
},
testHelper: function() {
return 'found it!';
}
},
{}
);
},
Error,
'Arg not supported with multiple decorators'
);
});
});
});
});
+766
View File
@@ -0,0 +1,766 @@
describe('builtin helpers', function() {
describe('#if', function() {
it('if', function() {
var string = '{{#if goodbye}}GOODBYE {{/if}}cruel {{world}}!';
expectTemplate(string)
.withInput({
goodbye: true,
world: 'world'
})
.withMessage('if with boolean argument shows the contents when true')
.toCompileTo('GOODBYE cruel world!');
expectTemplate(string)
.withInput({
goodbye: 'dummy',
world: 'world'
})
.withMessage('if with string argument shows the contents')
.toCompileTo('GOODBYE cruel world!');
expectTemplate(string)
.withInput({
goodbye: false,
world: 'world'
})
.withMessage(
'if with boolean argument does not show the contents when false'
)
.toCompileTo('cruel world!');
expectTemplate(string)
.withInput({ world: 'world' })
.withMessage('if with undefined does not show the contents')
.toCompileTo('cruel world!');
expectTemplate(string)
.withInput({
goodbye: ['foo'],
world: 'world'
})
.withMessage('if with non-empty array shows the contents')
.toCompileTo('GOODBYE cruel world!');
expectTemplate(string)
.withInput({
goodbye: [],
world: 'world'
})
.withMessage('if with empty array does not show the contents')
.toCompileTo('cruel world!');
expectTemplate(string)
.withInput({
goodbye: 0,
world: 'world'
})
.withMessage('if with zero does not show the contents')
.toCompileTo('cruel world!');
expectTemplate(
'{{#if goodbye includeZero=true}}GOODBYE {{/if}}cruel {{world}}!'
)
.withInput({
goodbye: 0,
world: 'world'
})
.withMessage('if with zero does not show the contents')
.toCompileTo('GOODBYE cruel world!');
});
it('if with function argument', function() {
var string = '{{#if goodbye}}GOODBYE {{/if}}cruel {{world}}!';
expectTemplate(string)
.withInput({
goodbye: function() {
return true;
},
world: 'world'
})
.withMessage(
'if with function shows the contents when function returns true'
)
.toCompileTo('GOODBYE cruel world!');
expectTemplate(string)
.withInput({
goodbye: function() {
return this.world;
},
world: 'world'
})
.withMessage(
'if with function shows the contents when function returns string'
)
.toCompileTo('GOODBYE cruel world!');
expectTemplate(string)
.withInput({
goodbye: function() {
return false;
},
world: 'world'
})
.withMessage(
'if with function does not show the contents when returns false'
)
.toCompileTo('cruel world!');
expectTemplate(string)
.withInput({
goodbye: function() {
return this.foo;
},
world: 'world'
})
.withMessage(
'if with function does not show the contents when returns undefined'
)
.toCompileTo('cruel world!');
});
it('should not change the depth list', function() {
expectTemplate(
'{{#with foo}}{{#if goodbye}}GOODBYE cruel {{../world}}!{{/if}}{{/with}}'
)
.withInput({
foo: { goodbye: true },
world: 'world'
})
.toCompileTo('GOODBYE cruel world!');
});
});
describe('#with', function() {
it('with', function() {
expectTemplate('{{#with person}}{{first}} {{last}}{{/with}}')
.withInput({
person: {
first: 'Alan',
last: 'Johnson'
}
})
.toCompileTo('Alan Johnson');
});
it('with with function argument', function() {
expectTemplate('{{#with person}}{{first}} {{last}}{{/with}}')
.withInput({
person: function() {
return {
first: 'Alan',
last: 'Johnson'
};
}
})
.toCompileTo('Alan Johnson');
});
it('with with else', function() {
expectTemplate(
'{{#with person}}Person is present{{else}}Person is not present{{/with}}'
).toCompileTo('Person is not present');
});
it('with provides block parameter', function() {
expectTemplate('{{#with person as |foo|}}{{foo.first}} {{last}}{{/with}}')
.withInput({
person: {
first: 'Alan',
last: 'Johnson'
}
})
.toCompileTo('Alan Johnson');
});
it('works when data is disabled', function() {
expectTemplate('{{#with person as |foo|}}{{foo.first}} {{last}}{{/with}}')
.withInput({ person: { first: 'Alan', last: 'Johnson' } })
.withCompileOptions({ data: false })
.toCompileTo('Alan Johnson');
});
});
describe('#each', function() {
beforeEach(function() {
handlebarsEnv.registerHelper('detectDataInsideEach', function(options) {
return options.data && options.data.exclaim;
});
});
it('each', function() {
var string = '{{#each goodbyes}}{{text}}! {{/each}}cruel {{world}}!';
expectTemplate(string)
.withInput({
goodbyes: [
{ text: 'goodbye' },
{ text: 'Goodbye' },
{ text: 'GOODBYE' }
],
world: 'world'
})
.withMessage(
'each with array argument iterates over the contents when not empty'
)
.toCompileTo('goodbye! Goodbye! GOODBYE! cruel world!');
expectTemplate(string)
.withInput({
goodbyes: [],
world: 'world'
})
.withMessage('each with array argument ignores the contents when empty')
.toCompileTo('cruel world!');
});
it('each without data', function() {
expectTemplate('{{#each goodbyes}}{{text}}! {{/each}}cruel {{world}}!')
.withInput({
goodbyes: [
{ text: 'goodbye' },
{ text: 'Goodbye' },
{ text: 'GOODBYE' }
],
world: 'world'
})
.withRuntimeOptions({ data: false })
.withCompileOptions({ data: false })
.toCompileTo('goodbye! Goodbye! GOODBYE! cruel world!');
expectTemplate('{{#each .}}{{.}}{{/each}}')
.withInput({ goodbyes: 'cruel', world: 'world' })
.withRuntimeOptions({ data: false })
.withCompileOptions({ data: false })
.toCompileTo('cruelworld');
});
it('each without context', function() {
expectTemplate('{{#each goodbyes}}{{text}}! {{/each}}cruel {{world}}!')
.withInput(undefined)
.toCompileTo('cruel !');
});
it('each with an object and @key', function() {
var string =
'{{#each goodbyes}}{{@key}}. {{text}}! {{/each}}cruel {{world}}!';
function Clazz() {
this['<b>#1</b>'] = { text: 'goodbye' };
this[2] = { text: 'GOODBYE' };
}
Clazz.prototype.foo = 'fail';
var hash = { goodbyes: new Clazz(), world: 'world' };
// Object property iteration order is undefined according to ECMA spec,
// so we need to check both possible orders
// @see http://stackoverflow.com/questions/280713/elements-order-in-a-for-in-loop
var actual = compileWithPartials(string, hash);
var expected1 =
'&lt;b&gt;#1&lt;/b&gt;. goodbye! 2. GOODBYE! cruel world!';
var expected2 =
'2. GOODBYE! &lt;b&gt;#1&lt;/b&gt;. goodbye! cruel world!';
equals(
actual === expected1 || actual === expected2,
true,
'each with object argument iterates over the contents when not empty'
);
expectTemplate(string)
.withInput({
goodbyes: {},
world: 'world'
})
.toCompileTo('cruel world!');
});
it('each with @index', function() {
expectTemplate(
'{{#each goodbyes}}{{@index}}. {{text}}! {{/each}}cruel {{world}}!'
)
.withInput({
goodbyes: [
{ text: 'goodbye' },
{ text: 'Goodbye' },
{ text: 'GOODBYE' }
],
world: 'world'
})
.withMessage('The @index variable is used')
.toCompileTo('0. goodbye! 1. Goodbye! 2. GOODBYE! cruel world!');
});
it('each with nested @index', function() {
expectTemplate(
'{{#each goodbyes}}{{@index}}. {{text}}! {{#each ../goodbyes}}{{@index}} {{/each}}After {{@index}} {{/each}}{{@index}}cruel {{world}}!'
)
.withInput({
goodbyes: [
{ text: 'goodbye' },
{ text: 'Goodbye' },
{ text: 'GOODBYE' }
],
world: 'world'
})
.withMessage('The @index variable is used')
.toCompileTo(
'0. goodbye! 0 1 2 After 0 1. Goodbye! 0 1 2 After 1 2. GOODBYE! 0 1 2 After 2 cruel world!'
);
});
it('each with block params', function() {
expectTemplate(
'{{#each goodbyes as |value index|}}{{index}}. {{value.text}}! {{#each ../goodbyes as |childValue childIndex|}} {{index}} {{childIndex}}{{/each}} After {{index}} {{/each}}{{index}}cruel {{world}}!'
)
.withInput({
goodbyes: [{ text: 'goodbye' }, { text: 'Goodbye' }],
world: 'world'
})
.toCompileTo(
'0. goodbye! 0 0 0 1 After 0 1. Goodbye! 1 0 1 1 After 1 cruel world!'
);
});
it('each with block params and strict compilation', function() {
expectTemplate(
'{{#each goodbyes as |value index|}}{{index}}. {{value.text}}!{{/each}}'
)
.withCompileOptions({ strict: true })
.withInput({ goodbyes: [{ text: 'goodbye' }, { text: 'Goodbye' }] })
.toCompileTo('0. goodbye!1. Goodbye!');
});
it('each object with @index', function() {
expectTemplate(
'{{#each goodbyes}}{{@index}}. {{text}}! {{/each}}cruel {{world}}!'
)
.withInput({
goodbyes: {
a: { text: 'goodbye' },
b: { text: 'Goodbye' },
c: { text: 'GOODBYE' }
},
world: 'world'
})
.withMessage('The @index variable is used')
.toCompileTo('0. goodbye! 1. Goodbye! 2. GOODBYE! cruel world!');
});
it('each with @first', function() {
expectTemplate(
'{{#each goodbyes}}{{#if @first}}{{text}}! {{/if}}{{/each}}cruel {{world}}!'
)
.withInput({
goodbyes: [
{ text: 'goodbye' },
{ text: 'Goodbye' },
{ text: 'GOODBYE' }
],
world: 'world'
})
.withMessage('The @first variable is used')
.toCompileTo('goodbye! cruel world!');
});
it('each with nested @first', function() {
expectTemplate(
'{{#each goodbyes}}({{#if @first}}{{text}}! {{/if}}{{#each ../goodbyes}}{{#if @first}}{{text}}!{{/if}}{{/each}}{{#if @first}} {{text}}!{{/if}}) {{/each}}cruel {{world}}!'
)
.withInput({
goodbyes: [
{ text: 'goodbye' },
{ text: 'Goodbye' },
{ text: 'GOODBYE' }
],
world: 'world'
})
.withMessage('The @first variable is used')
.toCompileTo(
'(goodbye! goodbye! goodbye!) (goodbye!) (goodbye!) cruel world!'
);
});
it('each object with @first', function() {
expectTemplate(
'{{#each goodbyes}}{{#if @first}}{{text}}! {{/if}}{{/each}}cruel {{world}}!'
)
.withInput({
goodbyes: { foo: { text: 'goodbye' }, bar: { text: 'Goodbye' } },
world: 'world'
})
.withMessage('The @first variable is used')
.toCompileTo('goodbye! cruel world!');
});
it('each with @last', function() {
expectTemplate(
'{{#each goodbyes}}{{#if @last}}{{text}}! {{/if}}{{/each}}cruel {{world}}!'
)
.withInput({
goodbyes: [
{ text: 'goodbye' },
{ text: 'Goodbye' },
{ text: 'GOODBYE' }
],
world: 'world'
})
.withMessage('The @last variable is used')
.toCompileTo('GOODBYE! cruel world!');
});
it('each object with @last', function() {
expectTemplate(
'{{#each goodbyes}}{{#if @last}}{{text}}! {{/if}}{{/each}}cruel {{world}}!'
)
.withInput({
goodbyes: { foo: { text: 'goodbye' }, bar: { text: 'Goodbye' } },
world: 'world'
})
.withMessage('The @last variable is used')
.toCompileTo('Goodbye! cruel world!');
});
it('each with nested @last', function() {
expectTemplate(
'{{#each goodbyes}}({{#if @last}}{{text}}! {{/if}}{{#each ../goodbyes}}{{#if @last}}{{text}}!{{/if}}{{/each}}{{#if @last}} {{text}}!{{/if}}) {{/each}}cruel {{world}}!'
)
.withInput({
goodbyes: [
{ text: 'goodbye' },
{ text: 'Goodbye' },
{ text: 'GOODBYE' }
],
world: 'world'
})
.withMessage('The @last variable is used')
.toCompileTo(
'(GOODBYE!) (GOODBYE!) (GOODBYE! GOODBYE! GOODBYE!) cruel world!'
);
});
it('each with function argument', function() {
var string = '{{#each goodbyes}}{{text}}! {{/each}}cruel {{world}}!';
expectTemplate(string)
.withInput({
goodbyes: function() {
return [
{ text: 'goodbye' },
{ text: 'Goodbye' },
{ text: 'GOODBYE' }
];
},
world: 'world'
})
.withMessage(
'each with array function argument iterates over the contents when not empty'
)
.toCompileTo('goodbye! Goodbye! GOODBYE! cruel world!');
expectTemplate(string)
.withInput({
goodbyes: [],
world: 'world'
})
.withMessage(
'each with array function argument ignores the contents when empty'
)
.toCompileTo('cruel world!');
});
it('each object when last key is an empty string', function() {
expectTemplate(
'{{#each goodbyes}}{{@index}}. {{text}}! {{/each}}cruel {{world}}!'
)
.withInput({
goodbyes: {
a: { text: 'goodbye' },
b: { text: 'Goodbye' },
'': { text: 'GOODBYE' }
},
world: 'world'
})
.withMessage('Empty string key is not skipped')
.toCompileTo('0. goodbye! 1. Goodbye! 2. GOODBYE! cruel world!');
});
it('data passed to helpers', function() {
expectTemplate(
'{{#each letters}}{{this}}{{detectDataInsideEach}}{{/each}}'
)
.withInput({ letters: ['a', 'b', 'c'] })
.withMessage('should output data')
.withRuntimeOptions({
data: {
exclaim: '!'
}
})
.toCompileTo('a!b!c!');
});
it('each on implicit context', function() {
expectTemplate('{{#each}}{{text}}! {{/each}}cruel world!').toThrow(
handlebarsEnv.Exception,
'Must pass iterator to #each'
);
});
if (global.Symbol && global.Symbol.iterator) {
it('each on iterable', function() {
function Iterator(arr) {
this.arr = arr;
this.index = 0;
}
Iterator.prototype.next = function() {
var value = this.arr[this.index];
var done = this.index === this.arr.length;
if (!done) {
this.index++;
}
return { value: value, done: done };
};
function Iterable(arr) {
this.arr = arr;
}
Iterable.prototype[global.Symbol.iterator] = function() {
return new Iterator(this.arr);
};
var string = '{{#each goodbyes}}{{text}}! {{/each}}cruel {{world}}!';
expectTemplate(string)
.withInput({
goodbyes: new Iterable([
{ text: 'goodbye' },
{ text: 'Goodbye' },
{ text: 'GOODBYE' }
]),
world: 'world'
})
.withMessage(
'each with array argument iterates over the contents when not empty'
)
.toCompileTo('goodbye! Goodbye! GOODBYE! cruel world!');
expectTemplate(string)
.withInput({
goodbyes: new Iterable([]),
world: 'world'
})
.withMessage(
'each with array argument ignores the contents when empty'
)
.toCompileTo('cruel world!');
});
}
});
describe('#log', function() {
/* eslint-disable no-console */
if (typeof console === 'undefined') {
return;
}
var $log, $info, $error;
beforeEach(function() {
$log = console.log;
$info = console.info;
$error = console.error;
});
afterEach(function() {
console.log = $log;
console.info = $info;
console.error = $error;
});
it('should call logger at default level', function() {
var levelArg, logArg;
handlebarsEnv.log = function(level, arg) {
levelArg = level;
logArg = arg;
};
expectTemplate('{{log blah}}')
.withInput({ blah: 'whee' })
.withMessage('log should not display')
.toCompileTo('');
equals(1, levelArg, 'should call log with 1');
equals('whee', logArg, "should call log with 'whee'");
});
it('should call logger at data level', function() {
var levelArg, logArg;
handlebarsEnv.log = function(level, arg) {
levelArg = level;
logArg = arg;
};
expectTemplate('{{log blah}}')
.withInput({ blah: 'whee' })
.withRuntimeOptions({ data: { level: '03' } })
.withCompileOptions({ data: true })
.toCompileTo('');
equals('03', levelArg);
equals('whee', logArg);
});
it('should output to info', function() {
var called;
console.info = function(info) {
equals('whee', info);
called = true;
console.info = $info;
console.log = $log;
};
console.log = function(log) {
equals('whee', log);
called = true;
console.info = $info;
console.log = $log;
};
expectTemplate('{{log blah}}')
.withInput({ blah: 'whee' })
.toCompileTo('');
equals(true, called);
});
it('should log at data level', function() {
var called;
console.error = function(log) {
equals('whee', log);
called = true;
console.error = $error;
};
expectTemplate('{{log blah}}')
.withInput({ blah: 'whee' })
.withRuntimeOptions({ data: { level: '03' } })
.withCompileOptions({ data: true })
.toCompileTo('');
equals(true, called);
});
it('should handle missing logger', function() {
var called = false;
console.error = undefined;
console.log = function(log) {
equals('whee', log);
called = true;
console.log = $log;
};
expectTemplate('{{log blah}}')
.withInput({ blah: 'whee' })
.withRuntimeOptions({ data: { level: '03' } })
.withCompileOptions({ data: true })
.toCompileTo('');
equals(true, called);
});
it('should handle string log levels', function() {
var called;
console.error = function(log) {
equals('whee', log);
called = true;
};
expectTemplate('{{log blah}}')
.withInput({ blah: 'whee' })
.withRuntimeOptions({ data: { level: 'error' } })
.withCompileOptions({ data: true })
.toCompileTo('');
equals(true, called);
called = false;
expectTemplate('{{log blah}}')
.withInput({ blah: 'whee' })
.withRuntimeOptions({ data: { level: 'ERROR' } })
.withCompileOptions({ data: true })
.toCompileTo('');
equals(true, called);
});
it('should handle hash log levels', function() {
var called;
console.error = function(log) {
equals('whee', log);
called = true;
};
expectTemplate('{{log blah level="error"}}')
.withInput({ blah: 'whee' })
.toCompileTo('');
equals(true, called);
});
it('should handle hash log levels', function() {
var called = false;
console.info = console.log = console.error = console.debug = function() {
called = true;
console.info = console.log = console.error = console.debug = $log;
};
expectTemplate('{{log blah level="debug"}}')
.withInput({ blah: 'whee' })
.toCompileTo('');
equals(false, called);
});
it('should pass multiple log arguments', function() {
var called;
console.info = console.log = function(log1, log2, log3) {
equals('whee', log1);
equals('foo', log2);
equals(1, log3);
called = true;
console.log = $log;
};
expectTemplate('{{log blah "foo" 1}}')
.withInput({ blah: 'whee' })
.toCompileTo('');
equals(true, called);
});
it('should pass zero log arguments', function() {
var called;
console.info = console.log = function() {
expect(arguments.length).to.equal(0);
called = true;
console.log = $log;
};
expectTemplate('{{log}}')
.withInput({ blah: 'whee' })
.toCompileTo('');
expect(called).to.be.true();
});
/* eslint-enable no-console */
});
describe('#lookup', function() {
it('should lookup arbitrary content', function() {
expectTemplate('{{#each goodbyes}}{{lookup ../data .}}{{/each}}')
.withInput({ goodbyes: [0, 1], data: ['foo', 'bar'] })
.toCompileTo('foobar');
});
it('should not fail on undefined value', function() {
expectTemplate('{{#each goodbyes}}{{lookup ../bar .}}{{/each}}')
.withInput({ goodbyes: [0, 1], data: ['foo', 'bar'] })
.toCompileTo('');
});
});
});
+188
View File
@@ -0,0 +1,188 @@
describe('compiler', function() {
if (!Handlebars.compile) {
return;
}
describe('#equals', function() {
function compile(string) {
var ast = Handlebars.parse(string);
return new Handlebars.Compiler().compile(ast, {});
}
it('should treat as equal', function() {
equal(compile('foo').equals(compile('foo')), true);
equal(compile('{{foo}}').equals(compile('{{foo}}')), true);
equal(compile('{{foo.bar}}').equals(compile('{{foo.bar}}')), true);
equal(
compile('{{foo.bar baz "foo" true false bat=1}}').equals(
compile('{{foo.bar baz "foo" true false bat=1}}')
),
true
);
equal(
compile('{{foo.bar (baz bat=1)}}').equals(
compile('{{foo.bar (baz bat=1)}}')
),
true
);
equal(
compile('{{#foo}} {{/foo}}').equals(compile('{{#foo}} {{/foo}}')),
true
);
});
it('should treat as not equal', function() {
equal(compile('foo').equals(compile('bar')), false);
equal(compile('{{foo}}').equals(compile('{{bar}}')), false);
equal(compile('{{foo.bar}}').equals(compile('{{bar.bar}}')), false);
equal(
compile('{{foo.bar baz bat=1}}').equals(
compile('{{foo.bar bar bat=1}}')
),
false
);
equal(
compile('{{foo.bar (baz bat=1)}}').equals(
compile('{{foo.bar (bar bat=1)}}')
),
false
);
equal(
compile('{{#foo}} {{/foo}}').equals(compile('{{#bar}} {{/bar}}')),
false
);
equal(
compile('{{#foo}} {{/foo}}').equals(
compile('{{#foo}} {{foo}}{{/foo}}')
),
false
);
});
});
describe('#compile', function() {
it('should fail with invalid input', function() {
shouldThrow(
function() {
Handlebars.compile(null);
},
Error,
'You must pass a string or Handlebars AST to Handlebars.compile. You passed null'
);
shouldThrow(
function() {
Handlebars.compile({});
},
Error,
'You must pass a string or Handlebars AST to Handlebars.compile. You passed [object Object]'
);
});
it('should include the location in the error (row and column)', function() {
try {
Handlebars.compile(' \n {{#if}}\n{{/def}}')();
equal(
true,
false,
'Statement must throw exception. This line should not be executed.'
);
} catch (err) {
equal(
err.message,
"if doesn't match def - 2:5",
'Checking error message'
);
if (Object.getOwnPropertyDescriptor(err, 'column').writable) {
// In Safari 8, the column-property is read-only. This means that even if it is set with defineProperty,
// its value won't change (https://github.com/jquery/esprima/issues/1290#issuecomment-132455482)
// Since this was neither working in Handlebars 3 nor in 4.0.5, we only check the column for other browsers.
equal(err.column, 5, 'Checking error column');
}
equal(err.lineNumber, 2, 'Checking error row');
}
});
it('should include the location as enumerable property', function() {
try {
Handlebars.compile(' \n {{#if}}\n{{/def}}')();
equal(
true,
false,
'Statement must throw exception. This line should not be executed.'
);
} catch (err) {
equal(
Object.prototype.propertyIsEnumerable.call(err, 'column'),
true,
'Checking error column'
);
}
});
it('can utilize AST instance', function() {
equal(
Handlebars.compile({
type: 'Program',
body: [{ type: 'ContentStatement', value: 'Hello' }]
})(),
'Hello'
);
});
it('can pass through an empty string', function() {
equal(Handlebars.compile('')(), '');
});
it('should not modify the options.data property(GH-1327)', function() {
var options = { data: [{ a: 'foo' }, { a: 'bar' }] };
Handlebars.compile('{{#each data}}{{@index}}:{{a}} {{/each}}', options)();
equal(
JSON.stringify(options, 0, 2),
JSON.stringify({ data: [{ a: 'foo' }, { a: 'bar' }] }, 0, 2)
);
});
it('should not modify the options.knownHelpers property(GH-1327)', function() {
var options = { knownHelpers: {} };
Handlebars.compile('{{#each data}}{{@index}}:{{a}} {{/each}}', options)();
equal(
JSON.stringify(options, 0, 2),
JSON.stringify({ knownHelpers: {} }, 0, 2)
);
});
});
describe('#precompile', function() {
it('should fail with invalid input', function() {
shouldThrow(
function() {
Handlebars.precompile(null);
},
Error,
'You must pass a string or Handlebars AST to Handlebars.precompile. You passed null'
);
shouldThrow(
function() {
Handlebars.precompile({});
},
Error,
'You must pass a string or Handlebars AST to Handlebars.precompile. You passed [object Object]'
);
});
it('can utilize AST instance', function() {
equal(
/return "Hello"/.test(
Handlebars.precompile({
type: 'Program',
body: [{ type: 'ContentStatement', value: 'Hello' }]
})
),
true
);
});
it('can pass through an empty string', function() {
equal(/return ""/.test(Handlebars.precompile('')), true);
});
});
});
+278
View File
@@ -0,0 +1,278 @@
describe('data', function() {
it('passing in data to a compiled function that expects data - works with helpers', function() {
expectTemplate('{{hello}}')
.withCompileOptions({ data: true })
.withHelper('hello', function(options) {
return options.data.adjective + ' ' + this.noun;
})
.withRuntimeOptions({ data: { adjective: 'happy' } })
.withInput({ noun: 'cat' })
.withMessage('Data output by helper')
.toCompileTo('happy cat');
});
it('data can be looked up via @foo', function() {
expectTemplate('{{@hello}}')
.withRuntimeOptions({ data: { hello: 'hello' } })
.withMessage('@foo retrieves template data')
.toCompileTo('hello');
});
it('deep @foo triggers automatic top-level data', function() {
var helpers = Handlebars.createFrame(handlebarsEnv.helpers);
helpers.let = function(options) {
var frame = Handlebars.createFrame(options.data);
for (var prop in options.hash) {
if (prop in options.hash) {
frame[prop] = options.hash[prop];
}
}
return options.fn(this, { data: frame });
};
expectTemplate(
'{{#let world="world"}}{{#if foo}}{{#if foo}}Hello {{@world}}{{/if}}{{/if}}{{/let}}'
)
.withInput({ foo: true })
.withHelpers(helpers)
.withMessage('Automatic data was triggered')
.toCompileTo('Hello world');
});
it('parameter data can be looked up via @foo', function() {
expectTemplate('{{hello @world}}')
.withRuntimeOptions({ data: { world: 'world' } })
.withHelper('hello', function(noun) {
return 'Hello ' + noun;
})
.withMessage('@foo as a parameter retrieves template data')
.toCompileTo('Hello world');
});
it('hash values can be looked up via @foo', function() {
expectTemplate('{{hello noun=@world}}')
.withRuntimeOptions({ data: { world: 'world' } })
.withHelper('hello', function(options) {
return 'Hello ' + options.hash.noun;
})
.withMessage('@foo as a parameter retrieves template data')
.toCompileTo('Hello world');
});
it('nested parameter data can be looked up via @foo.bar', function() {
expectTemplate('{{hello @world.bar}}')
.withRuntimeOptions({ data: { world: { bar: 'world' } } })
.withHelper('hello', function(noun) {
return 'Hello ' + noun;
})
.withMessage('@foo as a parameter retrieves template data')
.toCompileTo('Hello world');
});
it('nested parameter data does not fail with @world.bar', function() {
expectTemplate('{{hello @world.bar}}')
.withRuntimeOptions({ data: { foo: { bar: 'world' } } })
.withHelper('hello', function(noun) {
return 'Hello ' + noun;
})
.withMessage('@foo as a parameter retrieves template data')
.toCompileTo('Hello undefined');
});
it('parameter data throws when using complex scope references', function() {
expectTemplate(
'{{#goodbyes}}{{text}} cruel {{@foo/../name}}! {{/goodbyes}}'
).toThrow(Error);
});
it('data can be functions', function() {
expectTemplate('{{@hello}}')
.withRuntimeOptions({
data: {
hello: function() {
return 'hello';
}
}
})
.toCompileTo('hello');
});
it('data can be functions with params', function() {
expectTemplate('{{@hello "hello"}}')
.withRuntimeOptions({
data: {
hello: function(arg) {
return arg;
}
}
})
.toCompileTo('hello');
});
it('data is inherited downstream', function() {
expectTemplate(
'{{#let foo=1 bar=2}}{{#let foo=bar.baz}}{{@bar}}{{@foo}}{{/let}}{{@foo}}{{/let}}'
)
.withInput({ bar: { baz: 'hello world' } })
.withCompileOptions({ data: true })
.withHelper('let', function(options) {
var frame = Handlebars.createFrame(options.data);
for (var prop in options.hash) {
if (prop in options.hash) {
frame[prop] = options.hash[prop];
}
}
return options.fn(this, { data: frame });
})
.withRuntimeOptions({ data: {} })
.withMessage('data variables are inherited downstream')
.toCompileTo('2hello world1');
});
it('passing in data to a compiled function that expects data - works with helpers in partials', function() {
expectTemplate('{{>myPartial}}')
.withCompileOptions({ data: true })
.withPartial('myPartial', '{{hello}}')
.withHelper('hello', function(options) {
return options.data.adjective + ' ' + this.noun;
})
.withInput({ noun: 'cat' })
.withRuntimeOptions({ data: { adjective: 'happy' } })
.withMessage('Data output by helper inside partial')
.toCompileTo('happy cat');
});
it('passing in data to a compiled function that expects data - works with helpers and parameters', function() {
expectTemplate('{{hello world}}')
.withCompileOptions({ data: true })
.withHelper('hello', function(noun, options) {
return options.data.adjective + ' ' + noun + (this.exclaim ? '!' : '');
})
.withInput({ exclaim: true, world: 'world' })
.withRuntimeOptions({ data: { adjective: 'happy' } })
.withMessage('Data output by helper')
.toCompileTo('happy world!');
});
it('passing in data to a compiled function that expects data - works with block helpers', function() {
expectTemplate('{{#hello}}{{world}}{{/hello}}')
.withCompileOptions({
data: true
})
.withHelper('hello', function(options) {
return options.fn(this);
})
.withHelper('world', function(options) {
return options.data.adjective + ' world' + (this.exclaim ? '!' : '');
})
.withInput({ exclaim: true })
.withRuntimeOptions({ data: { adjective: 'happy' } })
.withMessage('Data output by helper')
.toCompileTo('happy world!');
});
it('passing in data to a compiled function that expects data - works with block helpers that use ..', function() {
expectTemplate('{{#hello}}{{world ../zomg}}{{/hello}}')
.withCompileOptions({ data: true })
.withHelper('hello', function(options) {
return options.fn({ exclaim: '?' });
})
.withHelper('world', function(thing, options) {
return options.data.adjective + ' ' + thing + (this.exclaim || '');
})
.withInput({ exclaim: true, zomg: 'world' })
.withRuntimeOptions({ data: { adjective: 'happy' } })
.withMessage('Data output by helper')
.toCompileTo('happy world?');
});
it('passing in data to a compiled function that expects data - data is passed to with block helpers where children use ..', function() {
expectTemplate('{{#hello}}{{world ../zomg}}{{/hello}}')
.withCompileOptions({ data: true })
.withHelper('hello', function(options) {
return options.data.accessData + ' ' + options.fn({ exclaim: '?' });
})
.withHelper('world', function(thing, options) {
return options.data.adjective + ' ' + thing + (this.exclaim || '');
})
.withInput({ exclaim: true, zomg: 'world' })
.withRuntimeOptions({ data: { adjective: 'happy', accessData: '#win' } })
.withMessage('Data output by helper')
.toCompileTo('#win happy world?');
});
it('you can override inherited data when invoking a helper', function() {
expectTemplate('{{#hello}}{{world zomg}}{{/hello}}')
.withCompileOptions({ data: true })
.withHelper('hello', function(options) {
return options.fn(
{ exclaim: '?', zomg: 'world' },
{ data: { adjective: 'sad' } }
);
})
.withHelper('world', function(thing, options) {
return options.data.adjective + ' ' + thing + (this.exclaim || '');
})
.withInput({ exclaim: true, zomg: 'planet' })
.withRuntimeOptions({ data: { adjective: 'happy' } })
.withMessage('Overriden data output by helper')
.toCompileTo('sad world?');
});
it('you can override inherited data when invoking a helper with depth', function() {
expectTemplate('{{#hello}}{{world ../zomg}}{{/hello}}')
.withCompileOptions({ data: true })
.withHelper('hello', function(options) {
return options.fn({ exclaim: '?' }, { data: { adjective: 'sad' } });
})
.withHelper('world', function(thing, options) {
return options.data.adjective + ' ' + thing + (this.exclaim || '');
})
.withInput({ exclaim: true, zomg: 'world' })
.withRuntimeOptions({ data: { adjective: 'happy' } })
.withMessage('Overriden data output by helper')
.toCompileTo('sad world?');
});
describe('@root', function() {
it('the root context can be looked up via @root', function() {
expectTemplate('{{@root.foo}}')
.withInput({ foo: 'hello' })
.withRuntimeOptions({ data: {} })
.toCompileTo('hello');
expectTemplate('{{@root.foo}}')
.withInput({ foo: 'hello' })
.toCompileTo('hello');
});
it('passed root values take priority', function() {
expectTemplate('{{@root.foo}}')
.withInput({ foo: 'should not be used' })
.withRuntimeOptions({ data: { root: { foo: 'hello' } } })
.toCompileTo('hello');
});
});
describe('nesting', function() {
it('the root context can be looked up via @root', function() {
expectTemplate(
'{{#helper}}{{#helper}}{{@./depth}} {{@../depth}} {{@../../depth}}{{/helper}}{{/helper}}'
)
.withInput({ foo: 'hello' })
.withHelper('helper', function(options) {
var frame = Handlebars.createFrame(options.data);
frame.depth = options.data.depth + 1;
return options.fn(this, { data: frame });
})
.withRuntimeOptions({
data: {
depth: 0
}
})
.toCompileTo('2 1 0');
});
});
});
+47
View File
@@ -0,0 +1,47 @@
require('./common');
var fs = require('fs'),
vm = require('vm');
var chai = require('chai');
var dirtyChai = require('dirty-chai');
chai.use(dirtyChai);
global.expect = chai.expect;
global.sinon = require('sinon');
global.Handlebars = 'no-conflict';
var filename = 'dist/handlebars.js';
if (global.minimizedTest) {
filename = 'dist/handlebars.min.js';
}
var distHandlebars = fs.readFileSync(
require.resolve('../../' + filename),
'utf-8'
);
vm.runInThisContext(distHandlebars, filename);
global.CompilerContext = {
browser: true,
compile: function(template, options) {
var templateSpec = handlebarsEnv.precompile(template, options);
return handlebarsEnv.template(safeEval(templateSpec));
},
compileWithPartial: function(template, options) {
return handlebarsEnv.compile(template, options);
}
};
function safeEval(templateSpec) {
/* eslint-disable no-eval, no-console */
try {
return eval('(' + templateSpec + ')');
} catch (err) {
console.error(templateSpec);
throw err;
}
/* eslint-enable no-eval, no-console */
}
+239
View File
@@ -0,0 +1,239 @@
var global = (function() {
return this;
})();
var AssertError;
if (Error.captureStackTrace) {
AssertError = function AssertError(message, caller) {
Error.prototype.constructor.call(this, message);
this.message = message;
if (Error.captureStackTrace) {
Error.captureStackTrace(this, caller || AssertError);
}
};
AssertError.prototype = new Error();
} else {
AssertError = Error;
}
/**
* @deprecated Use "expectTemplate(template)...toCompileTo(output)" instead
*/
global.shouldCompileTo = function(string, hashOrArray, expected, message) {
shouldCompileToWithPartials(string, hashOrArray, false, expected, message);
};
/**
* @deprecated Use "expectTemplate(template)...toCompileTo(output)" instead
*/
global.shouldCompileToWithPartials = function shouldCompileToWithPartials(
string,
hashOrArray,
partials,
expected,
message
) {
var result = compileWithPartials(string, hashOrArray, partials);
if (result !== expected) {
throw new AssertError(
"'" + result + "' should === '" + expected + "': " + message,
shouldCompileToWithPartials
);
}
};
/**
* @deprecated Use "expectTemplate(template)...toCompileTo(output)" instead
*/
global.compileWithPartials = function(string, hashOrArray, partials) {
var template, ary, options;
if (hashOrArray && hashOrArray.hash) {
ary = [hashOrArray.hash, hashOrArray];
delete hashOrArray.hash;
} else if (Object.prototype.toString.call(hashOrArray) === '[object Array]') {
ary = [];
ary.push(hashOrArray[0]); // input
ary.push({ helpers: hashOrArray[1], partials: hashOrArray[2] });
options =
typeof hashOrArray[3] === 'object'
? hashOrArray[3]
: { compat: hashOrArray[3] };
if (hashOrArray[4] != null) {
options.data = !!hashOrArray[4];
ary[1].data = hashOrArray[4];
}
} else {
ary = [hashOrArray];
}
template = CompilerContext[partials ? 'compileWithPartial' : 'compile'](
string,
options
);
return template.apply(this, ary);
};
/**
* @deprecated Use chai's expect-style API instead (`expect(actualValue).to.equal(expectedValue)`)
* @see https://www.chaijs.com/api/bdd/
*/
global.equals = global.equal = function equals(a, b, msg) {
if (a !== b) {
throw new AssertError(
"'" + a + "' should === '" + b + "'" + (msg ? ': ' + msg : ''),
equals
);
}
};
/**
* @deprecated Use chai's expect-style API instead (`expect(actualValue).to.equal(expectedValue)`)
* @see https://www.chaijs.com/api/bdd/#method_throw
*/
global.shouldThrow = function(callback, type, msg) {
var failed;
try {
callback();
failed = true;
} catch (caught) {
if (type && !(caught instanceof type)) {
throw new AssertError('Type failure: ' + caught);
}
if (
msg &&
!(msg.test ? msg.test(caught.message) : msg === caught.message)
) {
throw new AssertError(
'Throw mismatch: Expected ' +
caught.message +
' to match ' +
msg +
'\n\n' +
caught.stack,
shouldThrow
);
}
}
if (failed) {
throw new AssertError('It failed to throw', shouldThrow);
}
};
global.expectTemplate = function(templateAsString) {
return new HandlebarsTestBench(templateAsString);
};
function HandlebarsTestBench(templateAsString) {
this.templateAsString = templateAsString;
this.helpers = {};
this.partials = {};
this.decorators = {};
this.input = {};
this.message =
'Template' + templateAsString + ' does not evaluate to expected output';
this.compileOptions = {};
this.runtimeOptions = {};
}
HandlebarsTestBench.prototype.withInput = function(input) {
this.input = input;
return this;
};
HandlebarsTestBench.prototype.withHelper = function(name, helperFunction) {
this.helpers[name] = helperFunction;
return this;
};
HandlebarsTestBench.prototype.withHelpers = function(helperFunctions) {
var self = this;
Object.keys(helperFunctions).forEach(function(name) {
self.withHelper(name, helperFunctions[name]);
});
return this;
};
HandlebarsTestBench.prototype.withPartial = function(name, partialAsString) {
this.partials[name] = partialAsString;
return this;
};
HandlebarsTestBench.prototype.withPartials = function(partials) {
var self = this;
Object.keys(partials).forEach(function(name) {
self.withPartial(name, partials[name]);
});
return this;
};
HandlebarsTestBench.prototype.withDecorator = function(
name,
decoratorFunction
) {
this.decorators[name] = decoratorFunction;
return this;
};
HandlebarsTestBench.prototype.withDecorators = function(decorators) {
var self = this;
Object.keys(decorators).forEach(function(name) {
self.withDecorator(name, decorators[name]);
});
return this;
};
HandlebarsTestBench.prototype.withCompileOptions = function(compileOptions) {
this.compileOptions = compileOptions;
return this;
};
HandlebarsTestBench.prototype.withRuntimeOptions = function(runtimeOptions) {
this.runtimeOptions = runtimeOptions;
return this;
};
HandlebarsTestBench.prototype.withMessage = function(message) {
this.message = message;
return this;
};
HandlebarsTestBench.prototype.toCompileTo = function(expectedOutputAsString) {
expect(this._compileAndExecute()).to.equal(
expectedOutputAsString,
this.message
);
};
// see chai "to.throw" (https://www.chaijs.com/api/bdd/#method_throw)
HandlebarsTestBench.prototype.toThrow = function(errorLike, errMsgMatcher) {
var self = this;
expect(function() {
self._compileAndExecute();
}).to.throw(errorLike, errMsgMatcher, this.message);
};
HandlebarsTestBench.prototype._compileAndExecute = function() {
var compile =
Object.keys(this.partials).length > 0
? CompilerContext.compileWithPartial
: CompilerContext.compile;
var combinedRuntimeOptions = this._combineRuntimeOptions();
var template = compile(this.templateAsString, this.compileOptions);
return template(this.input, combinedRuntimeOptions);
};
HandlebarsTestBench.prototype._combineRuntimeOptions = function() {
var self = this;
var combinedRuntimeOptions = {};
Object.keys(this.runtimeOptions).forEach(function(key) {
combinedRuntimeOptions[key] = self.runtimeOptions[key];
});
combinedRuntimeOptions.helpers = this.helpers;
combinedRuntimeOptions.partials = this.partials;
combinedRuntimeOptions.decorators = this.decorators;
return combinedRuntimeOptions;
};
+32
View File
@@ -0,0 +1,32 @@
require('./common');
var chai = require('chai');
var dirtyChai = require('dirty-chai');
chai.use(dirtyChai);
global.expect = chai.expect;
global.sinon = require('sinon');
global.Handlebars = require('../../lib');
global.CompilerContext = {
compile: function(template, options) {
var templateSpec = handlebarsEnv.precompile(template, options);
return handlebarsEnv.template(safeEval(templateSpec));
},
compileWithPartial: function(template, options) {
return handlebarsEnv.compile(template, options);
}
};
function safeEval(templateSpec) {
/* eslint-disable no-eval, no-console */
try {
return eval('(' + templateSpec + ')');
} catch (err) {
console.error(templateSpec);
throw err;
}
/* eslint-enable no-eval, no-console */
}
+63
View File
@@ -0,0 +1,63 @@
/* eslint-disable no-console */
var fs = require('fs'),
Mocha = require('mocha'),
path = require('path');
var errors = 0,
testDir = path.dirname(__dirname),
grep = process.argv[2];
// Lazy hack, but whatever
if (grep === '--min') {
global.minimizedTest = true;
grep = undefined;
}
var files = fs
.readdirSync(testDir)
.filter(function(name) {
return /.*\.js$/.test(name);
})
.map(function(name) {
return testDir + path.sep + name;
});
if (global.minimizedTest) {
run('./runtime', function() {
run('./browser', function() {
/* eslint-disable no-process-exit */
process.exit(errors);
/* eslint-enable no-process-exit */
});
});
} else {
run('./runtime', function() {
run('./browser', function() {
run('./node', function() {
/* eslint-disable no-process-exit */
process.exit(errors);
/* eslint-enable no-process-exit */
});
});
});
}
function run(env, callback) {
var mocha = new Mocha();
mocha.ui('bdd');
mocha.files = files.slice();
if (grep) {
mocha.grep(grep);
}
files.forEach(function(name) {
delete require.cache[name];
});
console.log('Running env: ' + env);
require(env);
mocha.run(function(errorCount) {
errors += errorCount;
callback();
});
}
+70
View File
@@ -0,0 +1,70 @@
require('./common');
var fs = require('fs'),
vm = require('vm');
var chai = require('chai');
var dirtyChai = require('dirty-chai');
chai.use(dirtyChai);
global.expect = chai.expect;
global.sinon = require('sinon');
global.Handlebars = 'no-conflict';
var filename = 'dist/handlebars.runtime.js';
if (global.minimizedTest) {
filename = 'dist/handlebars.runtime.min.js';
}
vm.runInThisContext(
fs.readFileSync(__dirname + '/../../' + filename),
filename
);
var parse = require('../../dist/cjs/handlebars/compiler/base').parse;
var compiler = require('../../dist/cjs/handlebars/compiler/compiler');
var JavaScriptCompiler = require('../../dist/cjs/handlebars/compiler/javascript-compiler');
global.CompilerContext = {
browser: true,
compile: function(template, options) {
// Hack the compiler on to the environment for these specific tests
handlebarsEnv.precompile = function(precompileTemplate, precompileOptions) {
return compiler.precompile(
precompileTemplate,
precompileOptions,
handlebarsEnv
);
};
handlebarsEnv.parse = parse;
handlebarsEnv.Compiler = compiler.Compiler;
handlebarsEnv.JavaScriptCompiler = JavaScriptCompiler;
var templateSpec = handlebarsEnv.precompile(template, options);
return handlebarsEnv.template(safeEval(templateSpec));
},
compileWithPartial: function(template, options) {
// Hack the compiler on to the environment for these specific tests
handlebarsEnv.compile = function(compileTemplate, compileOptions) {
return compiler.compile(compileTemplate, compileOptions, handlebarsEnv);
};
handlebarsEnv.parse = parse;
handlebarsEnv.Compiler = compiler.Compiler;
handlebarsEnv.JavaScriptCompiler = JavaScriptCompiler;
return handlebarsEnv.compile(template, options);
}
};
function safeEval(templateSpec) {
/* eslint-disable no-eval, no-console */
try {
return eval('(' + templateSpec + ')');
} catch (err) {
console.error(templateSpec);
throw err;
}
/* eslint-enable no-eval, no-console */
}
+6
View File
@@ -0,0 +1,6 @@
define(['handlebars.runtime'], function(Handlebars) {
Handlebars = Handlebars["default"]; var template = Handlebars.template, templates = Handlebars.templates = Handlebars.templates || {};
return templates['bom'] = template({"compiler":[8,">= 4.3.0"],"main":function(container,depth0,helpers,partials,data) {
return "a";
},"useData":true});
});

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