pax_global_header 0000666 0000000 0000000 00000000064 13607154272 0014521 g ustar 00root root 0000000 0000000 52 comment=586e672c8bba7db787bc9bfe9a9fde4ec98d5b4f
handlebars.js-4.7.2/ 0000775 0000000 0000000 00000000000 13607154272 0014251 5 ustar 00root root 0000000 0000000 handlebars.js-4.7.2/.eslintignore 0000664 0000000 0000000 00000000560 13607154272 0016755 0 ustar 00root root 0000000 0000000 .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/
/integration-testing/*/dist/
# Third-party or files that must remain unchanged
/spec/expected/
/spec/vendor
# JS-Snippets
src/*.js
handlebars.js-4.7.2/.eslintrc.js 0000664 0000000 0000000 00000003116 13607154272 0016511 0 ustar 00root root 0000000 0000000 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: {}
}
};
handlebars.js-4.7.2/.gitattributes 0000664 0000000 0000000 00000000226 13607154272 0017144 0 ustar 00root root 0000000 0000000 # 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
handlebars.js-4.7.2/.gitignore 0000664 0000000 0000000 00000000424 13607154272 0016241 0 ustar 00root root 0000000 0000000 .rvmrc
.DS_Store
/tmp/
*.sublime-project
*.sublime-workspace
npm-debug.log
sauce_connect.log*
.idea
/yarn-error.log
/yarn.lock
node_modules
/handlebars-release.tgz
.nyc_output
# Generated files
lib/handlebars/compiler/parser.js
/coverage/
/dist/
/integration-testing/*/dist/
handlebars.js-4.7.2/.gitmodules 0000664 0000000 0000000 00000000134 13607154272 0016424 0 ustar 00root root 0000000 0000000 [submodule "spec/mustache"]
path = spec/mustache
url = git://github.com/mustache/spec.git
handlebars.js-4.7.2/.prettierignore 0000664 0000000 0000000 00000000530 13607154272 0017312 0 ustar 00root root 0000000 0000000 .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/
/integration-testing/*/dist/
# Third-party or files that must remain unchanged
/spec/expected/
/spec/vendor
handlebars.js-4.7.2/.travis.yml 0000664 0000000 0000000 00000002566 13607154272 0016373 0 ustar 00root root 0000000 0000000 language: node_js
jobs:
include:
- stage: test
name: check javascript (eslint)
node_js: lts/*
script: npm run lint
- stage: test
name: check formatting (prettier)
node_js: lts/*
script: npm run check-format
- stage: test
name: check typescript definitions (dtslint)
node_js: lts/*
script: npm run dtslint
- stage: test
name: extensive tests and publish to aws
script: npm run extensive-tests-and-publish-to-aws
env:
- S3_BUCKET_NAME=builds.handlebarsjs.com
- secure: ckyEe5dzjdFDjmZ6wIrhGm0CFBEnKq8c1dYptfgVV/Q5/nJFGzu8T0yTjouS/ERxzdT2H327/63VCxhFnLCRHrsh4rlW/rCy4XI3O/0TeMLgFPa4TXkO8359qZ4CB44TBb3NsJyQXNMYdJpPLTCVTMpuiqqkFFOr+6OeggR7ufA=
- secure: Nm4AgSfsgNB21kgKrF9Tl7qVZU8YYREhouQunFracTcZZh2NZ2XH5aHuSiXCj88B13Cr/jGbJKsZ4T3QS3wWYtz6lkyVOx3H3iI+TMtqhD9RM3a7A4O+4vVN8IioB2YjhEu0OKjwgX5gp+0uF+pLEi7Hpj6fupD3AbbL5uYcKg8=
- SAUCE_USERNAME=handlebars
- secure: 1VkLQhbsEug4ZMQ52tTOus/WLvW3Etqe7GbCzZfzsI8d2ygJPjFfzU8fNm4pVVwoTI21MaM5AQq7SVPu8DWN1YbDjJycMdY1zO3DsB9aZBxTal98fIB7ZIUce9r5z2EP6mETrsbYjZkeckzIBI0A4UVa+F2BO4KbRDXP1Db3u3I=
node_js: '10'
- stage: test
name: test with latest nodejs-lts
node_js: lts/*
script: npm run test
- stage: test
name: test with active nodejs
node_js: node
script: npm run test
cache: npm
email:
on_failure: change
on_success: never
git:
depth: 100
handlebars.js-4.7.2/CONTRIBUTING.md 0000664 0000000 0000000 00000013324 13607154272 0016505 0 ustar 00root root 0000000 0000000 # How to Contribute
## Reporting Issues
Please see our [FAQ](https://github.com/wycats/handlebars.js/blob/master/FAQ.md) for common issues that people run into.
Should you run into other issues with the project, please don't hesitate to let us know by filing an [issue][issue]! In general we are going to ask for an example of the problem failing, which can be as simple as a jsfiddle/jsbin/etc. We've put together a jsfiddle [template][jsfiddle] to ease this. (We will keep this link up to date as new releases occur, so feel free to check back here)
Pull requests containing only failing 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 site should be reported on [handlebars-site](https://github.com/wycats/handlebars-site).
## 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/wycats/handlebars.js/issues](http://github.com/wycats/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 travis-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 travis-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 travis does in its build-job, excluding the "integration-test".
- **npm run integration-test** 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).
## Ember testing
The current ember distribution should be tested as part of the handlebars release process. This requires building the `handlebars-source` gem locally and then executing the ember test script.
```sh
npm link
grunt build release
cp dist/*.js $emberRepoDir/bower_components/handlebars/
cd $emberRepoDir
npm link handlebars
npm test
```
## Releasing the latest version
_When releasing a previous version of Handlebars, please look into the CONTRIBUNG.md in the corresponding branch._
Handlebars utilizes the [release yeoman generator][generator-release] to perform most release tasks.
A full release may be completed with the following:
```
npm ci
yo release
npm publish
yo release:publish components handlebars.js dist/components/
cd dist/components/
gem build handlebars-source.gemspec
gem push handlebars-source-*.gem
```
After 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 to point to the new version numbers. The jsfiddle link should be updated to point to the most recent distribution for all instances in our documentation.
[generator-release]: https://github.com/walmartlabs/generator-release
[pull-request]: https://github.com/wycats/handlebars.js/pull/new/master
[issue]: https://github.com/wycats/handlebars.js/issues/new
[jsfiddle]: https://jsfiddle.net/9D88g/180/
handlebars.js-4.7.2/FAQ.md 0000664 0000000 0000000 00000007270 13607154272 0015210 0 ustar 00root root 0000000 0000000 # Frequently Asked Questions
1. How can I file a bug report:
See our guidelines on [reporting issues](https://github.com/wycats/handlebars.js/blob/master/CONTRIBUTING.md#reporting-issues).
1. Why isn't my Mustache template working?
Handlebars deviates from Mustache slightly on a few behaviors. These variations are documented in our [readme](https://github.com/wycats/handlebars.js#differences-between-handlebarsjs-and-mustache).
1. Why is it slower when compiling?
The Handlebars compiler must parse the template and construct a JavaScript program which can then be run. Under some environments such as older mobile devices this can have a performance impact which can be avoided by precompiling. Generally it's recommended that precompilation and the runtime library be used on all clients.
1. Why doesn't this work with Content Security Policy restrictions?
When not using the precompiler, Handlebars generates a dynamic function for each template which can cause issues with pages that have enabled Content Policy. It's recommended that templates are precompiled or the `unsafe-eval` policy is enabled for sites that must generate dynamic templates at runtime.
1. How can I include script tags in my template?
If loading the template via an inlined `
```
It's generally recommended that templates are served through external, precompiled, files, which do not suffer from this issue.
1. Why are my precompiled scripts throwing exceptions?
When using the precompiler, it's important that a supporting version of the Handlebars runtime be loaded on the target page. In version 1.x there were rudimentary checks to compare the version but these did not always work. This is fixed under 2.x but the version checking does not work between these two versions. If you see unexpected errors such as `undefined is not a function` or similar, please verify that the same version is being used for both the precompiler and the client. This can be checked via:
```sh
handlebars --version
```
If using the integrated precompiler and
```javascript
console.log(Handlebars.VERSION);
```
On the client side.
We include the built client libraries in the npm package for those who want to be certain that they are using the same client libraries as the compiler.
Should these match, please file an issue with us, per our [issue filing guidelines](https://github.com/wycats/handlebars.js/blob/master/CONTRIBUTING.md#reporting-issues).
1. Why doesn't IE like the `default` name in the AMD module?
Some browsers such as particular versions of IE treat `default` as a reserved word in JavaScript source files. To safely use this you need to reference this via the `Handlebars['default']` lookup method. This is an unfortunate side effect of the shims necessary to backport the Handlebars ES6 code to all current browsers.
1. How do I load the runtime library when using AMD?
There are two options for loading under AMD environments. The first is to use the `handlebars.runtime.amd.js` file. This may require a [path mapping](https://github.com/wycats/handlebars.js/blob/master/spec/amd-runtime.html#L31) as well as access via the `default` field.
The other option is to load the `handlebars.runtime.js` UMD build, which might not require path configuration and exposes the library as both the module root and the `default` field for compatibility.
If not using ES6 transpilers or accessing submodules in the build the former option should be sufficient for most use cases.
handlebars.js-4.7.2/Gruntfile.js 0000664 0000000 0000000 00000016202 13607154272 0016547 0 ustar 00root root 0000000 0000000 /* 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',
'integration-testing/**/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
}
}
},
'saucelabs-mocha': {
all: {
options: {
build: process.env.TRAVIS_JOB_ID,
urls: [
'http://localhost:9999/spec/?headless=true',
'http://localhost:9999/spec/amd.html?headless=true'
],
detailedError: true,
concurrency: 4,
browsers: [
{ browserName: 'chrome' },
{ browserName: 'firefox', platform: 'Linux' },
// {browserName: 'safari', version: 9, platform: 'OS X 10.11'},
// {browserName: 'safari', version: 8, platform: 'OS X 10.10'},
{
browserName: 'internet explorer',
version: 11,
platform: 'Windows 8.1'
},
{
browserName: 'internet explorer',
version: 10,
platform: 'Windows 8'
}
]
}
},
sanity: {
options: {
build: process.env.TRAVIS_JOB_ID,
urls: [
'http://localhost:9999/spec/umd.html?headless=true',
'http://localhost:9999/spec/amd-runtime.html?headless=true',
'http://localhost:9999/spec/umd-runtime.html?headless=true'
],
detailedError: true,
concurrency: 2,
browsers: [{ browserName: 'chrome' }]
}
}
},
bgShell: {
integrationTests: {
cmd: './integration-testing/run-integration-tests.sh',
bg: false,
fail: true
}
},
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-bg-shell');
grunt.loadNpmTasks('@knappi/grunt-saucelabs');
grunt.loadNpmTasks('grunt-webpack');
grunt.task.loadTasks('tasks');
this.registerTask(
'build',
'Builds a distributable version of the current project',
['parser', 'node', 'globals']
);
this.registerTask('node', ['babel:cjs']);
this.registerTask('globals', ['webpack']);
this.registerTask('release', 'Build final packages', [
'amd',
'uglify',
'test:min',
'copy:dist',
'copy:components',
'copy:cdnjs'
]);
this.registerTask('amd', ['babel:amd', 'requirejs']);
this.registerTask('test', ['test:bin', 'test:cov']);
grunt.registerTask('bench', ['metrics']);
if (process.env.SAUCE_ACCESS_KEY) {
grunt.registerTask('sauce', ['concat:tests', 'connect', 'saucelabs-mocha']);
} else {
grunt.registerTask('sauce', []);
}
// Requires secret properties (saucelabs-credentials etc.) from .travis.yaml
grunt.registerTask('extensive-tests-and-publish-to-aws', [
'default',
'bgShell:integrationTests',
'sauce',
'metrics',
'publish-to-aws'
]);
grunt.registerTask('on-file-change', [
'build',
'amd',
'concat:tests',
'test'
]);
// === Primary tasks ===
grunt.registerTask('dev', ['clean', 'connect', 'watch']);
grunt.registerTask('default', ['clean', 'build', 'test', 'release']);
grunt.registerTask('integration-tests', [
'default',
'bgShell:integrationTests'
]);
};
handlebars.js-4.7.2/LICENSE 0000664 0000000 0000000 00000002047 13607154272 0015261 0 ustar 00root root 0000000 0000000 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
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
handlebars.js-4.7.2/README.markdown 0000664 0000000 0000000 00000021520 13607154272 0016752 0 ustar 00root root 0000000 0000000 [](https://travis-ci.org/wycats/handlebars.js)
[](https://ci.appveyor.com/project/wycats/handlebars-js)
[](https://saucelabs.com/u/handlebars)
Handlebars.js
=============
Handlebars.js is an extension to the [Mustache templating
language](http://mustache.github.com/) created by Chris Wanstrath.
Handlebars.js and Mustache are both logicless templating languages that
keep the view and the code separated like we all know they should be.
Checkout the official Handlebars docs site at
[https://handlebarsjs.com/](https://handlebarsjs.com) and the live demo at [http://tryhandlebarsjs.com/](http://tryhandlebarsjs.com/).
Installing
----------
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).
Once you have a template, use the `Handlebars.compile` method to compile
the template into a function. The generated function takes a context
argument, which will be used to render the template.
```js
var source = "
Hello, my name is {{name}}. I am from {{hometown}}. I have " +
"{{kids.length}} kids:
" +
"
{{#kids}}
{{name}} is {{age}}
{{/kids}}
";
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);
// Would render:
//
Hello, my name is Alan. I am from Somewhere, TX. I have 2 kids:
//
//
Jimmy is 12
//
Sally is 4
//
```
Full documentation and more examples are at [handlebarsjs.com](https://handlebarsjs.com/).
Precompiling Templates
----------------------
Handlebars allows templates to be precompiled and included as javascript code rather than the handlebars template allowing for faster startup time. 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](http://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.
[](https://saucelabs.com/u/handlebars)
Performance
-----------
In a rough performance test, precompiled Handlebars.js templates (in
the original version of Handlebars.js) rendered in about half the
time of Mustache templates. It would be a shame if it were any other
way, since they were precompiled, but the difference in architecture
does have some big performance advantages. Justin Marney, a.k.a.
[gotascii](http://github.com/gotascii), confirmed that with an
[independent test](http://sorescode.com/2010/09/12/benchmarks.html). The
rewritten Handlebars (current version) is faster than the old version,
with many [performance tests](https://travis-ci.org/wycats/handlebars.js/builds/33392182#L538) being 5 to 7 times faster than the Mustache equivalent.
Upgrading
---------
See [release-notes.md](https://github.com/wycats/handlebars.js/blob/master/release-notes.md) for upgrade notes.
Known Issues
------------
See [FAQ.md](https://github.com/wycats/handlebars.js/blob/master/FAQ.md) for known issues and common pitfalls.
Handlebars in the Wild
----------------------
* [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/wycats/handlebars.js/pull/new/master
handlebars.js-4.7.2/appveyor.yml 0000664 0000000 0000000 00000001400 13607154272 0016634 0 ustar 00root root 0000000 0000000 # Test against these versions of Node.js
environment:
matrix:
- nodejs_version: "10"
platform:
- x64
# Install scripts (runs after repo cloning)
install:
# Get the latest stable version of Node.js
- ps: Install-Product node $env:nodejs_version $env:platform
# Clone submodules (mustache spec)
- cmd: git submodule update --init --recursive
# Install modules
- cmd: npm ci
# Post-install test scripts
test_script:
# Output useful info for debugging
- cmd: node --version
- cmd: npm --version
# Run tests
- cmd: npm run test
# Don't actually build
build: off
on_failure:
- cmd: 7z a coverage.zip coverage
- cmd: appveyor PushArtifact coverage.zip
# Set build version format here instead of in the admin panel
version: "{build}"
handlebars.js-4.7.2/bench/ 0000775 0000000 0000000 00000000000 13607154272 0015330 5 ustar 00root root 0000000 0000000 handlebars.js-4.7.2/bench/.eslintrc 0000664 0000000 0000000 00000000354 13607154272 0017156 0 ustar 00root root 0000000 0000000 {
"extends": "prettier",
"globals": {
"require": true
},
"rules": {
// Disabling for tests, for now.
"no-path-concat": 0,
"no-var": 0,
"no-shadow": 0,
"handle-callback-err": 0,
"no-console": 0
}
}
handlebars.js-4.7.2/bench/dist-size.js 0000664 0000000 0000000 00000001652 13607154272 0017605 0 ustar 00root root 0000000 0000000 var async = require('neo-async'),
fs = require('fs'),
zlib = require('zlib');
module.exports = function(grunt, callback) {
var distFiles = fs.readdirSync('dist'),
distSizes = {};
async.each(
distFiles,
function(file, callback) {
var content;
try {
content = fs.readFileSync('dist/' + file);
} catch (err) {
if (err.code === 'EISDIR') {
callback();
return;
} else {
throw err;
}
}
file = file.replace(/\.js/, '').replace(/\./g, '_');
distSizes[file] = content.length;
zlib.gzip(content, function(err, data) {
if (err) {
throw err;
}
distSizes[file + '_gz'] = data.length;
callback();
});
},
function() {
grunt.log.writeln(
'Distribution sizes: ' + JSON.stringify(distSizes, undefined, 2)
);
callback([distSizes]);
}
);
};
handlebars.js-4.7.2/bench/index.js 0000664 0000000 0000000 00000000472 13607154272 0017000 0 ustar 00root root 0000000 0000000 var fs = require('fs');
var metrics = fs.readdirSync(__dirname);
metrics.forEach(function(metric) {
if (metric === 'index.js' || !/(.*)\.js$/.test(metric)) {
return;
}
var name = RegExp.$1;
metric = require('./' + name);
if (metric instanceof Function) {
module.exports[name] = metric;
}
});
handlebars.js-4.7.2/bench/precompile-size.js 0000664 0000000 0000000 00000001406 13607154272 0020776 0 ustar 00root root 0000000 0000000 var _ = require('underscore'),
templates = require('./templates');
module.exports = function(grunt, callback) {
// Deferring to here in case we have a build for parser, etc as part of this grunt exec
var Handlebars = require('../lib');
var templateSizes = {};
_.each(templates, function(info, template) {
var src = info.handlebars,
compiled = Handlebars.precompile(src, {}),
knownHelpers = Handlebars.precompile(src, {
knownHelpersOnly: true,
knownHelpers: info.helpers
});
templateSizes[template] = compiled.length;
templateSizes['knownOnly_' + template] = knownHelpers.length;
});
grunt.log.writeln(
'Precompiled sizes: ' + JSON.stringify(templateSizes, undefined, 2)
);
callback([templateSizes]);
};
handlebars.js-4.7.2/bench/templates/ 0000775 0000000 0000000 00000000000 13607154272 0017326 5 ustar 00root root 0000000 0000000 handlebars.js-4.7.2/bench/templates/arguments.js 0000664 0000000 0000000 00000000315 13607154272 0021670 0 ustar 00root root 0000000 0000000 module.exports = {
helpers: {
foo: function() {
return '';
}
},
context: {
bar: true
},
handlebars:
'{{foo person "person" 1 true foo=bar foo="person" foo=1 foo=true}}'
};
handlebars.js-4.7.2/bench/templates/array-each.js 0000664 0000000 0000000 00000000530 13607154272 0021676 0 ustar 00root root 0000000 0000000 module.exports = {
context: {
names: [
{ name: 'Moe' },
{ name: 'Larry' },
{ name: 'Curly' },
{ name: 'Shemp' }
]
},
handlebars: '{{#each names}}{{name}}{{/each}}',
dust: '{#names}{name}{/names}',
mustache: '{{#names}}{{name}}{{/names}}',
eco: '<% for item in @names: %><%= item.name %><% end %>'
};
handlebars.js-4.7.2/bench/templates/array-mustache.js 0000664 0000000 0000000 00000000311 13607154272 0022604 0 ustar 00root root 0000000 0000000 module.exports = {
context: {
names: [
{ name: 'Moe' },
{ name: 'Larry' },
{ name: 'Curly' },
{ name: 'Shemp' }
]
},
handlebars: '{{#names}}{{name}}{{/names}}'
};
handlebars.js-4.7.2/bench/templates/complex.dust 0000664 0000000 0000000 00000000366 13607154272 0021703 0 ustar 00root root 0000000 0000000
{{#link}}Hello{{/link}}{{/form}}';
var template = CompilerContext.compile(string);
var result = template(
{
yehuda: { name: 'Yehuda' }
},
{
helpers: {
link: function(options) {
return '' + options.fn(this) + '';
},
form: function(context, options) {
return '';
}
}
}
);
equal(
result,
'',
'Both blocks executed'
);
});
it('block helper inverted sections', function() {
var string = "{{#list people}}{{name}}{{^}}Nobody's here{{/list}}";
function list(context, options) {
if (context.length > 0) {
var out = '
';
for (var i = 0, j = context.length; i < j; i++) {
out += '
';
out += options.fn(context[i]);
out += '
';
}
out += '
';
return out;
} else {
return '
' + options.inverse(this) + '
';
}
}
var hash = { people: [{ name: 'Alan' }, { name: 'Yehuda' }] };
var empty = { people: [] };
var rootMessage = {
people: [],
message: "Nobody's here"
};
var messageString = '{{#list people}}Hello{{^}}{{message}}{{/list}}';
// the meaning here may be kind of hard to catch, but list.not is always called,
// so we should see the output of both
shouldCompileTo(
string,
[hash, { list: list }],
'
Alan
Yehuda
',
'an inverse wrapper is passed in as a new context'
);
shouldCompileTo(
string,
[empty, { list: list }],
"
Nobody's here
",
'an inverse wrapper can be optionally called'
);
shouldCompileTo(
messageString,
[rootMessage, { list: list }],
'
Nobody's here
',
'the context of an inverse is the parent of the block'
);
});
it('pathed lambas with parameters', function() {
var hash = {
helper: function() {
return 'winning';
}
};
hash.hash = hash;
var helpers = {
'./helper': function() {
return 'fail';
}
};
shouldCompileTo('{{./helper 1}}', [hash, helpers], 'winning');
shouldCompileTo('{{hash/helper 1}}', [hash, helpers], 'winning');
});
describe('helpers hash', function() {
it('providing a helpers hash', function() {
shouldCompileTo(
'Goodbye {{cruel}} {{world}}!',
[
{ cruel: 'cruel' },
{
world: function() {
return 'world';
}
}
],
'Goodbye cruel world!',
'helpers hash is available'
);
shouldCompileTo(
'Goodbye {{#iter}}{{cruel}} {{world}}{{/iter}}!',
[
{ iter: [{ cruel: 'cruel' }] },
{
world: function() {
return 'world';
}
}
],
'Goodbye cruel world!',
'helpers hash is available inside other blocks'
);
});
it('in cases of conflict, helpers win', function() {
shouldCompileTo(
'{{{lookup}}}',
[
{ lookup: 'Explicit' },
{
lookup: function() {
return 'helpers';
}
}
],
'helpers',
'helpers hash has precedence escaped expansion'
);
shouldCompileTo(
'{{lookup}}',
[
{ lookup: 'Explicit' },
{
lookup: function() {
return 'helpers';
}
}
],
'helpers',
'helpers hash has precedence simple expansion'
);
});
it('the helpers hash is available is nested contexts', function() {
shouldCompileTo(
'{{#outer}}{{#inner}}{{helper}}{{/inner}}{{/outer}}',
[
{ outer: { inner: { unused: [] } } },
{
helper: function() {
return 'helper';
}
}
],
'helper',
'helpers hash is available in nested contexts.'
);
});
it('the helper hash should augment the global hash', function() {
handlebarsEnv.registerHelper('test_helper', function() {
return 'found it!';
});
shouldCompileTo(
'{{test_helper}} {{#if cruel}}Goodbye {{cruel}} {{world}}!{{/if}}',
[
{ cruel: 'cruel' },
{
world: function() {
return 'world!';
}
}
],
'found it! Goodbye cruel world!!'
);
});
});
describe('registration', function() {
it('unregisters', function() {
handlebarsEnv.helpers = {};
handlebarsEnv.registerHelper('foo', function() {
return 'fail';
});
handlebarsEnv.unregisterHelper('foo');
equals(handlebarsEnv.helpers.foo, undefined);
});
it('allows multiple globals', function() {
var helpers = handlebarsEnv.helpers;
handlebarsEnv.helpers = {};
handlebarsEnv.registerHelper({
if: helpers['if'],
world: function() {
return 'world!';
},
testHelper: function() {
return 'found it!';
}
});
shouldCompileTo(
'{{testHelper}} {{#if cruel}}Goodbye {{cruel}} {{world}}!{{/if}}',
[{ cruel: 'cruel' }],
'found it! Goodbye cruel world!!'
);
});
it('fails with multiple and args', function() {
shouldThrow(
function() {
handlebarsEnv.registerHelper(
{
world: function() {
return 'world!';
},
testHelper: function() {
return 'found it!';
}
},
{}
);
},
Error,
'Arg not supported with multiple helpers'
);
});
});
it('decimal number literals work', function() {
var string = 'Message: {{hello -1.2 1.2}}';
var helpers = {
hello: function(times, times2) {
if (typeof times !== 'number') {
times = 'NaN';
}
if (typeof times2 !== 'number') {
times2 = 'NaN';
}
return 'Hello ' + times + ' ' + times2 + ' times';
}
};
shouldCompileTo(
string,
[{}, helpers],
'Message: Hello -1.2 1.2 times',
'template with a negative integer literal'
);
});
it('negative number literals work', function() {
var string = 'Message: {{hello -12}}';
var helpers = {
hello: function(times) {
if (typeof times !== 'number') {
times = 'NaN';
}
return 'Hello ' + times + ' times';
}
};
shouldCompileTo(
string,
[{}, helpers],
'Message: Hello -12 times',
'template with a negative integer literal'
);
});
describe('String literal parameters', function() {
it('simple literals work', function() {
var string = 'Message: {{hello "world" 12 true false}}';
var helpers = {
hello: function(param, times, bool1, bool2) {
if (typeof times !== 'number') {
times = 'NaN';
}
if (typeof bool1 !== 'boolean') {
bool1 = 'NaB';
}
if (typeof bool2 !== 'boolean') {
bool2 = 'NaB';
}
return (
'Hello ' + param + ' ' + times + ' times: ' + bool1 + ' ' + bool2
);
}
};
shouldCompileTo(
string,
[{}, helpers],
'Message: Hello world 12 times: true false',
'template with a simple String literal'
);
});
it('using a quote in the middle of a parameter raises an error', function() {
var string = 'Message: {{hello wo"rld"}}';
shouldThrow(function() {
CompilerContext.compile(string);
}, Error);
});
it('escaping a String is possible', function() {
var string = 'Message: {{{hello "\\"world\\""}}}';
var helpers = {
hello: function(param) {
return 'Hello ' + param;
}
};
shouldCompileTo(
string,
[{}, helpers],
'Message: Hello "world"',
'template with an escaped String literal'
);
});
it("it works with ' marks", function() {
var string = 'Message: {{{hello "Alan\'s world"}}}';
var helpers = {
hello: function(param) {
return 'Hello ' + param;
}
};
shouldCompileTo(
string,
[{}, helpers],
"Message: Hello Alan's world",
"template with a ' mark"
);
});
});
it('negative number literals work', function() {
var string = 'Message: {{hello -12}}';
var helpers = {
hello: function(times) {
if (typeof times !== 'number') {
times = 'NaN';
}
return 'Hello ' + times + ' times';
}
};
shouldCompileTo(
string,
[{}, helpers],
'Message: Hello -12 times',
'template with a negative integer literal'
);
});
describe('multiple parameters', function() {
it('simple multi-params work', function() {
var string = 'Message: {{goodbye cruel world}}';
var hash = { cruel: 'cruel', world: 'world' };
var helpers = {
goodbye: function(cruel, world) {
return 'Goodbye ' + cruel + ' ' + world;
}
};
shouldCompileTo(
string,
[hash, helpers],
'Message: Goodbye cruel world',
'regular helpers with multiple params'
);
});
it('block multi-params work', function() {
var string =
'Message: {{#goodbye cruel world}}{{greeting}} {{adj}} {{noun}}{{/goodbye}}';
var hash = { cruel: 'cruel', world: 'world' };
var helpers = {
goodbye: function(cruel, world, options) {
return options.fn({ greeting: 'Goodbye', adj: cruel, noun: world });
}
};
shouldCompileTo(
string,
[hash, helpers],
'Message: Goodbye cruel world',
'block helpers with multiple params'
);
});
});
describe('hash', function() {
it('helpers can take an optional hash', function() {
var template = CompilerContext.compile(
'{{goodbye cruel="CRUEL" world="WORLD" times=12}}'
);
var helpers = {
goodbye: function(options) {
return (
'GOODBYE ' +
options.hash.cruel +
' ' +
options.hash.world +
' ' +
options.hash.times +
' TIMES'
);
}
};
var context = {};
var result = template(context, { helpers: helpers });
equals(result, 'GOODBYE CRUEL WORLD 12 TIMES', 'Helper output hash');
});
it('helpers can take an optional hash with booleans', function() {
var helpers = {
goodbye: function(options) {
if (options.hash.print === true) {
return 'GOODBYE ' + options.hash.cruel + ' ' + options.hash.world;
} else if (options.hash.print === false) {
return 'NOT PRINTING';
} else {
return 'THIS SHOULD NOT HAPPEN';
}
}
};
var context = {};
var template = CompilerContext.compile(
'{{goodbye cruel="CRUEL" world="WORLD" print=true}}'
);
var result = template(context, { helpers: helpers });
equals(result, 'GOODBYE CRUEL WORLD', 'Helper output hash');
template = CompilerContext.compile(
'{{goodbye cruel="CRUEL" world="WORLD" print=false}}'
);
result = template(context, { helpers: helpers });
equals(result, 'NOT PRINTING', 'Boolean helper parameter honored');
});
it('block helpers can take an optional hash', function() {
var template = CompilerContext.compile(
'{{#goodbye cruel="CRUEL" times=12}}world{{/goodbye}}'
);
var helpers = {
goodbye: function(options) {
return (
'GOODBYE ' +
options.hash.cruel +
' ' +
options.fn(this) +
' ' +
options.hash.times +
' TIMES'
);
}
};
var result = template({}, { helpers: helpers });
equals(result, 'GOODBYE CRUEL world 12 TIMES', 'Hash parameters output');
});
it('block helpers can take an optional hash with single quoted stings', function() {
var template = CompilerContext.compile(
'{{#goodbye cruel="CRUEL" times=12}}world{{/goodbye}}'
);
var helpers = {
goodbye: function(options) {
return (
'GOODBYE ' +
options.hash.cruel +
' ' +
options.fn(this) +
' ' +
options.hash.times +
' TIMES'
);
}
};
var result = template({}, { helpers: helpers });
equals(result, 'GOODBYE CRUEL world 12 TIMES', 'Hash parameters output');
});
it('block helpers can take an optional hash with booleans', function() {
var helpers = {
goodbye: function(options) {
if (options.hash.print === true) {
return 'GOODBYE ' + options.hash.cruel + ' ' + options.fn(this);
} else if (options.hash.print === false) {
return 'NOT PRINTING';
} else {
return 'THIS SHOULD NOT HAPPEN';
}
}
};
var template = CompilerContext.compile(
'{{#goodbye cruel="CRUEL" print=true}}world{{/goodbye}}'
);
var result = template({}, { helpers: helpers });
equals(result, 'GOODBYE CRUEL world', 'Boolean hash parameter honored');
template = CompilerContext.compile(
'{{#goodbye cruel="CRUEL" print=false}}world{{/goodbye}}'
);
result = template({}, { helpers: helpers });
equals(result, 'NOT PRINTING', 'Boolean hash parameter honored');
});
});
describe('helperMissing', function() {
it('if a context is not found, helperMissing is used', function() {
shouldThrow(
function() {
var template = CompilerContext.compile('{{hello}} {{link_to world}}');
template({});
},
undefined,
/Missing helper: "link_to"/
);
});
it('if a context is not found, custom helperMissing is used', function() {
var string = '{{hello}} {{link_to world}}';
var context = { hello: 'Hello', world: 'world' };
var helpers = {
helperMissing: function(mesg, options) {
if (options.name === 'link_to') {
return new Handlebars.SafeString('' + mesg + '');
}
}
};
shouldCompileTo(string, [context, helpers], 'Hello world');
});
it('if a value is not found, custom helperMissing is used', function() {
var string = '{{hello}} {{link_to}}';
var context = { hello: 'Hello', world: 'world' };
var helpers = {
helperMissing: function(options) {
if (options.name === 'link_to') {
return new Handlebars.SafeString('winning');
}
}
};
shouldCompileTo(string, [context, helpers], 'Hello winning');
});
});
describe('knownHelpers', function() {
it('Known helper should render helper', function() {
var template = CompilerContext.compile('{{hello}}', {
knownHelpers: { hello: true }
});
var result = template(
{},
{
helpers: {
hello: function() {
return 'foo';
}
}
}
);
equal(result, 'foo', "'foo' should === '" + result);
});
it('Unknown helper in knownHelpers only mode should be passed as undefined', function() {
var template = CompilerContext.compile('{{typeof hello}}', {
knownHelpers: { typeof: true },
knownHelpersOnly: true
});
var result = template(
{},
{
helpers: {
typeof: function(arg) {
return typeof arg;
},
hello: function() {
return 'foo';
}
}
}
);
equal(result, 'undefined', "'undefined' should === '" + result);
});
it('Builtin helpers available in knownHelpers only mode', function() {
var template = CompilerContext.compile('{{#unless foo}}bar{{/unless}}', {
knownHelpersOnly: true
});
var result = template({});
equal(result, 'bar', "'bar' should === '" + result);
});
it('Field lookup works in knownHelpers only mode', function() {
var template = CompilerContext.compile('{{foo}}', {
knownHelpersOnly: true
});
var result = template({ foo: 'bar' });
equal(result, 'bar', "'bar' should === '" + result);
});
it('Conditional blocks work in knownHelpers only mode', function() {
var template = CompilerContext.compile('{{#foo}}bar{{/foo}}', {
knownHelpersOnly: true
});
var result = template({ foo: 'baz' });
equal(result, 'bar', "'bar' should === '" + result);
});
it('Invert blocks work in knownHelpers only mode', function() {
var template = CompilerContext.compile('{{^foo}}bar{{/foo}}', {
knownHelpersOnly: true
});
var result = template({ foo: false });
equal(result, 'bar', "'bar' should === '" + result);
});
it('Functions are bound to the context in knownHelpers only mode', function() {
var template = CompilerContext.compile('{{foo}}', {
knownHelpersOnly: true
});
var result = template({
foo: function() {
return this.bar;
},
bar: 'bar'
});
equal(result, 'bar', "'bar' should === '" + result);
});
it('Unknown helper call in knownHelpers only mode should throw', function() {
shouldThrow(function() {
CompilerContext.compile('{{typeof hello}}', { knownHelpersOnly: true });
}, Error);
});
});
describe('blockHelperMissing', function() {
it('lambdas are resolved by blockHelperMissing, not handlebars proper', function() {
var string = '{{#truthy}}yep{{/truthy}}';
var data = {
truthy: function() {
return true;
}
};
shouldCompileTo(string, data, 'yep');
});
it('lambdas resolved by blockHelperMissing are bound to the context', function() {
var string = '{{#truthy}}yep{{/truthy}}';
var boundData = {
truthy: function() {
return this.truthiness();
},
truthiness: function() {
return false;
}
};
shouldCompileTo(string, boundData, '');
});
});
describe('name field', function() {
var context = {};
var helpers = {
blockHelperMissing: function() {
return 'missing: ' + arguments[arguments.length - 1].name;
},
helperMissing: function() {
return 'helper missing: ' + arguments[arguments.length - 1].name;
},
helper: function() {
return 'ran: ' + arguments[arguments.length - 1].name;
}
};
it('should include in ambiguous mustache calls', function() {
shouldCompileTo('{{helper}}', [context, helpers], 'ran: helper');
});
it('should include in helper mustache calls', function() {
shouldCompileTo('{{helper 1}}', [context, helpers], 'ran: helper');
});
it('should include in ambiguous block calls', function() {
shouldCompileTo(
'{{#helper}}{{/helper}}',
[context, helpers],
'ran: helper'
);
});
it('should include in simple block calls', function() {
shouldCompileTo(
'{{#./helper}}{{/./helper}}',
[context, helpers],
'missing: ./helper'
);
});
it('should include in helper block calls', function() {
shouldCompileTo(
'{{#helper 1}}{{/helper}}',
[context, helpers],
'ran: helper'
);
});
it('should include in known helper calls', function() {
var template = CompilerContext.compile('{{helper}}', {
knownHelpers: { helper: true },
knownHelpersOnly: true
});
equal(template({}, { helpers: helpers }), 'ran: helper');
});
it('should include full id', function() {
shouldCompileTo(
'{{#foo.helper}}{{/foo.helper}}',
[{ foo: {} }, helpers],
'missing: foo.helper'
);
});
it('should include full id if a hash is passed', function() {
shouldCompileTo(
'{{#foo.helper bar=baz}}{{/foo.helper}}',
[{ foo: {} }, helpers],
'helper missing: foo.helper'
);
});
});
describe('name conflicts', function() {
it('helpers take precedence over same-named context properties', function() {
var template = CompilerContext.compile('{{goodbye}} {{cruel world}}');
var helpers = {
goodbye: function() {
return this.goodbye.toUpperCase();
},
cruel: function(world) {
return 'cruel ' + world.toUpperCase();
}
};
var context = {
goodbye: 'goodbye',
world: 'world'
};
var result = template(context, { helpers: helpers });
equals(result, 'GOODBYE cruel WORLD', 'Helper executed');
});
it('helpers take precedence over same-named context properties$', function() {
var template = CompilerContext.compile(
'{{#goodbye}} {{cruel world}}{{/goodbye}}'
);
var helpers = {
goodbye: function(options) {
return this.goodbye.toUpperCase() + options.fn(this);
},
cruel: function(world) {
return 'cruel ' + world.toUpperCase();
}
};
var context = {
goodbye: 'goodbye',
world: 'world'
};
var result = template(context, { helpers: helpers });
equals(result, 'GOODBYE cruel WORLD', 'Helper executed');
});
it('Scoped names take precedence over helpers', function() {
var template = CompilerContext.compile(
'{{this.goodbye}} {{cruel world}} {{cruel this.goodbye}}'
);
var helpers = {
goodbye: function() {
return this.goodbye.toUpperCase();
},
cruel: function(world) {
return 'cruel ' + world.toUpperCase();
}
};
var context = {
goodbye: 'goodbye',
world: 'world'
};
var result = template(context, { helpers: helpers });
equals(
result,
'goodbye cruel WORLD cruel GOODBYE',
'Helper not executed'
);
});
it('Scoped names take precedence over block helpers', function() {
var template = CompilerContext.compile(
'{{#goodbye}} {{cruel world}}{{/goodbye}} {{this.goodbye}}'
);
var helpers = {
goodbye: function(options) {
return this.goodbye.toUpperCase() + options.fn(this);
},
cruel: function(world) {
return 'cruel ' + world.toUpperCase();
}
};
var context = {
goodbye: 'goodbye',
world: 'world'
};
var result = template(context, { helpers: helpers });
equals(result, 'GOODBYE cruel WORLD goodbye', 'Helper executed');
});
});
describe('block params', function() {
it('should take presedence over context values', function() {
var hash = { value: 'foo' };
var helpers = {
goodbyes: function(options) {
equals(options.fn.blockParams, 1);
return options.fn({ value: 'bar' }, { blockParams: [1, 2] });
}
};
shouldCompileTo(
'{{#goodbyes as |value|}}{{value}}{{/goodbyes}}{{value}}',
[hash, helpers],
'1foo'
);
});
it('should take presedence over helper values', function() {
var hash = {};
var helpers = {
value: function() {
return 'foo';
},
goodbyes: function(options) {
equals(options.fn.blockParams, 1);
return options.fn({}, { blockParams: [1, 2] });
}
};
shouldCompileTo(
'{{#goodbyes as |value|}}{{value}}{{/goodbyes}}{{value}}',
[hash, helpers],
'1foo'
);
});
it('should not take presedence over pathed values', function() {
var hash = { value: 'bar' };
var helpers = {
value: function() {
return 'foo';
},
goodbyes: function(options) {
equals(options.fn.blockParams, 1);
return options.fn(this, { blockParams: [1, 2] });
}
};
shouldCompileTo(
'{{#goodbyes as |value|}}{{./value}}{{/goodbyes}}{{value}}',
[hash, helpers],
'barfoo'
);
});
it('should take presednece over parent block params', function() {
var hash = { value: 'foo' },
value = 1;
var helpers = {
goodbyes: function(options) {
return options.fn(
{ value: 'bar' },
{
blockParams:
options.fn.blockParams === 1 ? [value++, value++] : undefined
}
);
}
};
shouldCompileTo(
'{{#goodbyes as |value|}}{{#goodbyes}}{{value}}{{#goodbyes as |value|}}{{value}}{{/goodbyes}}{{/goodbyes}}{{/goodbyes}}{{value}}',
[hash, helpers],
'13foo'
);
});
it('should allow block params on chained helpers', function() {
var hash = { value: 'foo' };
var helpers = {
goodbyes: function(options) {
equals(options.fn.blockParams, 1);
return options.fn({ value: 'bar' }, { blockParams: [1, 2] });
}
};
shouldCompileTo(
'{{#if bar}}{{else goodbyes as |value|}}{{value}}{{/if}}{{value}}',
[hash, helpers],
'1foo'
);
});
});
describe('built-in helpers malformed arguments ', function() {
it('if helper - too few arguments', function() {
var template = CompilerContext.compile('{{#if}}{{/if}}');
shouldThrow(
function() {
template({});
},
undefined,
/#if requires exactly one argument/
);
});
it('if helper - too many arguments, string', function() {
var template = CompilerContext.compile('{{#if test "string"}}{{/if}}');
shouldThrow(
function() {
template({});
},
undefined,
/#if requires exactly one argument/
);
});
it('if helper - too many arguments, undefined', function() {
var template = CompilerContext.compile('{{#if test undefined}}{{/if}}');
shouldThrow(
function() {
template({});
},
undefined,
/#if requires exactly one argument/
);
});
it('if helper - too many arguments, null', function() {
var template = CompilerContext.compile('{{#if test null}}{{/if}}');
shouldThrow(
function() {
template({});
},
undefined,
/#if requires exactly one argument/
);
});
it('unless helper - too few arguments', function() {
var template = CompilerContext.compile('{{#unless}}{{/unless}}');
shouldThrow(
function() {
template({});
},
undefined,
/#unless requires exactly one argument/
);
});
it('unless helper - too many arguments', function() {
var template = CompilerContext.compile(
'{{#unless test null}}{{/unless}}'
);
shouldThrow(
function() {
template({});
},
undefined,
/#unless requires exactly one argument/
);
});
it('with helper - too few arguments', function() {
var template = CompilerContext.compile('{{#with}}{{/with}}');
shouldThrow(
function() {
template({});
},
undefined,
/#with requires exactly one argument/
);
});
it('with helper - too many arguments', function() {
var template = CompilerContext.compile(
'{{#with test "string"}}{{/with}}'
);
shouldThrow(
function() {
template({});
},
undefined,
/#with requires exactly one argument/
);
});
});
describe('the lookupProperty-option', function() {
it('should be passed to custom helpers', function() {
expectTemplate('{{testHelper}}')
.withHelper('testHelper', function testHelper(options) {
return options.lookupProperty(this, 'testProperty');
})
.withInput({ testProperty: 'abc' })
.toCompileTo('abc');
});
});
});
handlebars.js-4.7.2/spec/index.html 0000664 0000000 0000000 00000005545 13607154272 0017211 0 ustar 00root root 0000000 0000000
Mocha
handlebars.js-4.7.2/spec/javascript-compiler.js 0000664 0000000 0000000 00000007623 13607154272 0021527 0 ustar 00root root 0000000 0000000 describe('javascript-compiler api', function() {
if (!Handlebars.JavaScriptCompiler) {
return;
}
describe('#nameLookup', function() {
var $superName;
beforeEach(function() {
$superName = handlebarsEnv.JavaScriptCompiler.prototype.nameLookup;
});
afterEach(function() {
handlebarsEnv.JavaScriptCompiler.prototype.nameLookup = $superName;
});
it('should allow override', function() {
handlebarsEnv.JavaScriptCompiler.prototype.nameLookup = function(
parent,
name
) {
return parent + '.bar_' + name;
};
/* eslint-disable camelcase */
shouldCompileTo('{{foo}}', { bar_foo: 'food' }, 'food');
/* eslint-enable camelcase */
});
// Tests nameLookup dot vs. bracket behavior. Bracket is required in certain cases
// to avoid errors in older browsers.
it('should handle reserved words', function() {
shouldCompileTo('{{foo}} {{~null~}}', { foo: 'food' }, 'food');
});
});
describe('#compilerInfo', function() {
var $superCheck, $superInfo;
beforeEach(function() {
$superCheck = handlebarsEnv.VM.checkRevision;
$superInfo = handlebarsEnv.JavaScriptCompiler.prototype.compilerInfo;
});
afterEach(function() {
handlebarsEnv.VM.checkRevision = $superCheck;
handlebarsEnv.JavaScriptCompiler.prototype.compilerInfo = $superInfo;
});
it('should allow compilerInfo override', function() {
handlebarsEnv.JavaScriptCompiler.prototype.compilerInfo = function() {
return 'crazy';
};
handlebarsEnv.VM.checkRevision = function(compilerInfo) {
if (compilerInfo !== 'crazy') {
throw new Error("It didn't work");
}
};
shouldCompileTo('{{foo}} ', { foo: 'food' }, 'food ');
});
});
describe('buffer', function() {
var $superAppend, $superCreate;
beforeEach(function() {
handlebarsEnv.JavaScriptCompiler.prototype.forceBuffer = true;
$superAppend = handlebarsEnv.JavaScriptCompiler.prototype.appendToBuffer;
$superCreate =
handlebarsEnv.JavaScriptCompiler.prototype.initializeBuffer;
});
afterEach(function() {
handlebarsEnv.JavaScriptCompiler.prototype.forceBuffer = false;
handlebarsEnv.JavaScriptCompiler.prototype.appendToBuffer = $superAppend;
handlebarsEnv.JavaScriptCompiler.prototype.initializeBuffer = $superCreate;
});
it('should allow init buffer override', function() {
handlebarsEnv.JavaScriptCompiler.prototype.initializeBuffer = function() {
return this.quotedString('foo_');
};
shouldCompileTo('{{foo}} ', { foo: 'food' }, 'foo_food ');
});
it('should allow append buffer override', function() {
handlebarsEnv.JavaScriptCompiler.prototype.appendToBuffer = function(
string
) {
return $superAppend.call(this, [string, ' + "_foo"']);
};
shouldCompileTo('{{foo}}', { foo: 'food' }, 'food_foo');
});
});
describe('#isValidJavaScriptVariableName', function() {
// It is there and accessible and could be used by someone. That's why we don't remove it
// it 4.x. But if we keep it, we add a test
// This test should not encourage you to use the function. It is not needed any more
// and might be removed in 5.0
['test', 'abc123', 'abc_123'].forEach(function(validVariableName) {
it("should return true for '" + validVariableName + "'", function() {
expect(
handlebarsEnv.JavaScriptCompiler.isValidJavaScriptVariableName(
validVariableName
)
).to.be.true();
});
});
[('123test', 'abc()', 'abc.cde')].forEach(function(invalidVariableName) {
it("should return true for '" + invalidVariableName + "'", function() {
expect(
handlebarsEnv.JavaScriptCompiler.isValidJavaScriptVariableName(
invalidVariableName
)
).to.be.false();
});
});
});
});
handlebars.js-4.7.2/spec/mustache/ 0000775 0000000 0000000 00000000000 13607154272 0017014 5 ustar 00root root 0000000 0000000 handlebars.js-4.7.2/spec/parser.js 0000664 0000000 0000000 00000031354 13607154272 0017043 0 ustar 00root root 0000000 0000000 describe('parser', function() {
if (!Handlebars.print) {
return;
}
function astFor(template) {
var ast = Handlebars.parse(template);
return Handlebars.print(ast);
}
it('parses simple mustaches', function() {
equals(astFor('{{123}}'), '{{ NUMBER{123} [] }}\n');
equals(astFor('{{"foo"}}'), '{{ "foo" [] }}\n');
equals(astFor('{{false}}'), '{{ BOOLEAN{false} [] }}\n');
equals(astFor('{{true}}'), '{{ BOOLEAN{true} [] }}\n');
equals(astFor('{{foo}}'), '{{ PATH:foo [] }}\n');
equals(astFor('{{foo?}}'), '{{ PATH:foo? [] }}\n');
equals(astFor('{{foo_}}'), '{{ PATH:foo_ [] }}\n');
equals(astFor('{{foo-}}'), '{{ PATH:foo- [] }}\n');
equals(astFor('{{foo:}}'), '{{ PATH:foo: [] }}\n');
});
it('parses simple mustaches with data', function() {
equals(astFor('{{@foo}}'), '{{ @PATH:foo [] }}\n');
});
it('parses simple mustaches with data paths', function() {
equals(astFor('{{@../foo}}'), '{{ @PATH:foo [] }}\n');
});
it('parses mustaches with paths', function() {
equals(astFor('{{foo/bar}}'), '{{ PATH:foo/bar [] }}\n');
});
it('parses mustaches with this/foo', function() {
equals(astFor('{{this/foo}}'), '{{ PATH:foo [] }}\n');
});
it('parses mustaches with - in a path', function() {
equals(astFor('{{foo-bar}}'), '{{ PATH:foo-bar [] }}\n');
});
it('parses mustaches with escaped [] in a path', function() {
equals(astFor('{{[foo[\\]]}}'), '{{ PATH:foo[] [] }}\n');
});
it('parses escaped \\\\ in path', function() {
equals(astFor('{{[foo\\\\]}}'), '{{ PATH:foo\\ [] }}\n');
});
it('parses mustaches with parameters', function() {
equals(astFor('{{foo bar}}'), '{{ PATH:foo [PATH:bar] }}\n');
});
it('parses mustaches with string parameters', function() {
equals(astFor('{{foo bar "baz" }}'), '{{ PATH:foo [PATH:bar, "baz"] }}\n');
});
it('parses mustaches with NUMBER parameters', function() {
equals(astFor('{{foo 1}}'), '{{ PATH:foo [NUMBER{1}] }}\n');
});
it('parses mustaches with BOOLEAN parameters', function() {
equals(astFor('{{foo true}}'), '{{ PATH:foo [BOOLEAN{true}] }}\n');
equals(astFor('{{foo false}}'), '{{ PATH:foo [BOOLEAN{false}] }}\n');
});
it('parses mustaches with undefined and null paths', function() {
equals(astFor('{{undefined}}'), '{{ UNDEFINED [] }}\n');
equals(astFor('{{null}}'), '{{ NULL [] }}\n');
});
it('parses mustaches with undefined and null parameters', function() {
equals(
astFor('{{foo undefined null}}'),
'{{ PATH:foo [UNDEFINED, NULL] }}\n'
);
});
it('parses mustaches with DATA parameters', function() {
equals(astFor('{{foo @bar}}'), '{{ PATH:foo [@PATH:bar] }}\n');
});
it('parses mustaches with hash arguments', function() {
equals(astFor('{{foo bar=baz}}'), '{{ PATH:foo [] HASH{bar=PATH:baz} }}\n');
equals(astFor('{{foo bar=1}}'), '{{ PATH:foo [] HASH{bar=NUMBER{1}} }}\n');
equals(
astFor('{{foo bar=true}}'),
'{{ PATH:foo [] HASH{bar=BOOLEAN{true}} }}\n'
);
equals(
astFor('{{foo bar=false}}'),
'{{ PATH:foo [] HASH{bar=BOOLEAN{false}} }}\n'
);
equals(
astFor('{{foo bar=@baz}}'),
'{{ PATH:foo [] HASH{bar=@PATH:baz} }}\n'
);
equals(
astFor('{{foo bar=baz bat=bam}}'),
'{{ PATH:foo [] HASH{bar=PATH:baz, bat=PATH:bam} }}\n'
);
equals(
astFor('{{foo bar=baz bat="bam"}}'),
'{{ PATH:foo [] HASH{bar=PATH:baz, bat="bam"} }}\n'
);
equals(astFor("{{foo bat='bam'}}"), '{{ PATH:foo [] HASH{bat="bam"} }}\n');
equals(
astFor('{{foo omg bar=baz bat="bam"}}'),
'{{ PATH:foo [PATH:omg] HASH{bar=PATH:baz, bat="bam"} }}\n'
);
equals(
astFor('{{foo omg bar=baz bat="bam" baz=1}}'),
'{{ PATH:foo [PATH:omg] HASH{bar=PATH:baz, bat="bam", baz=NUMBER{1}} }}\n'
);
equals(
astFor('{{foo omg bar=baz bat="bam" baz=true}}'),
'{{ PATH:foo [PATH:omg] HASH{bar=PATH:baz, bat="bam", baz=BOOLEAN{true}} }}\n'
);
equals(
astFor('{{foo omg bar=baz bat="bam" baz=false}}'),
'{{ PATH:foo [PATH:omg] HASH{bar=PATH:baz, bat="bam", baz=BOOLEAN{false}} }}\n'
);
});
it('parses contents followed by a mustache', function() {
equals(
astFor('foo bar {{baz}}'),
"CONTENT[ 'foo bar ' ]\n{{ PATH:baz [] }}\n"
);
});
it('parses a partial', function() {
equals(astFor('{{> foo }}'), '{{> PARTIAL:foo }}\n');
equals(astFor('{{> "foo" }}'), '{{> PARTIAL:foo }}\n');
equals(astFor('{{> 1 }}'), '{{> PARTIAL:1 }}\n');
});
it('parses a partial with context', function() {
equals(astFor('{{> foo bar}}'), '{{> PARTIAL:foo PATH:bar }}\n');
});
it('parses a partial with hash', function() {
equals(
astFor('{{> foo bar=bat}}'),
'{{> PARTIAL:foo HASH{bar=PATH:bat} }}\n'
);
});
it('parses a partial with context and hash', function() {
equals(
astFor('{{> foo bar bat=baz}}'),
'{{> PARTIAL:foo PATH:bar HASH{bat=PATH:baz} }}\n'
);
});
it('parses a partial with a complex name', function() {
equals(
astFor('{{> shared/partial?.bar}}'),
'{{> PARTIAL:shared/partial?.bar }}\n'
);
});
it('parsers partial blocks', function() {
equals(
astFor('{{#> foo}}bar{{/foo}}'),
"{{> PARTIAL BLOCK:foo PROGRAM:\n CONTENT[ 'bar' ]\n }}\n"
);
});
it('should handle parser block mismatch', function() {
shouldThrow(
function() {
astFor('{{#> goodbyes}}{{/hellos}}');
},
Error,
/goodbyes doesn't match hellos/
);
});
it('parsers partial blocks with arguments', function() {
equals(
astFor('{{#> foo context hash=value}}bar{{/foo}}'),
"{{> PARTIAL BLOCK:foo PATH:context HASH{hash=PATH:value} PROGRAM:\n CONTENT[ 'bar' ]\n }}\n"
);
});
it('parses a comment', function() {
equals(
astFor('{{! this is a comment }}'),
"{{! ' this is a comment ' }}\n"
);
});
it('parses a multi-line comment', function() {
equals(
astFor('{{!\nthis is a multi-line comment\n}}'),
"{{! '\nthis is a multi-line comment\n' }}\n"
);
});
it('parses an inverse section', function() {
equals(
astFor('{{#foo}} bar {{^}} baz {{/foo}}'),
"BLOCK:\n PATH:foo []\n PROGRAM:\n CONTENT[ ' bar ' ]\n {{^}}\n CONTENT[ ' baz ' ]\n"
);
});
it('parses an inverse (else-style) section', function() {
equals(
astFor('{{#foo}} bar {{else}} baz {{/foo}}'),
"BLOCK:\n PATH:foo []\n PROGRAM:\n CONTENT[ ' bar ' ]\n {{^}}\n CONTENT[ ' baz ' ]\n"
);
});
it('parses multiple inverse sections', function() {
equals(
astFor('{{#foo}} bar {{else if bar}}{{else}} baz {{/foo}}'),
"BLOCK:\n PATH:foo []\n PROGRAM:\n CONTENT[ ' bar ' ]\n {{^}}\n BLOCK:\n PATH:if [PATH:bar]\n PROGRAM:\n {{^}}\n CONTENT[ ' baz ' ]\n"
);
});
it('parses empty blocks', function() {
equals(astFor('{{#foo}}{{/foo}}'), 'BLOCK:\n PATH:foo []\n PROGRAM:\n');
});
it('parses empty blocks with empty inverse section', function() {
equals(
astFor('{{#foo}}{{^}}{{/foo}}'),
'BLOCK:\n PATH:foo []\n PROGRAM:\n {{^}}\n'
);
});
it('parses empty blocks with empty inverse (else-style) section', function() {
equals(
astFor('{{#foo}}{{else}}{{/foo}}'),
'BLOCK:\n PATH:foo []\n PROGRAM:\n {{^}}\n'
);
});
it('parses non-empty blocks with empty inverse section', function() {
equals(
astFor('{{#foo}} bar {{^}}{{/foo}}'),
"BLOCK:\n PATH:foo []\n PROGRAM:\n CONTENT[ ' bar ' ]\n {{^}}\n"
);
});
it('parses non-empty blocks with empty inverse (else-style) section', function() {
equals(
astFor('{{#foo}} bar {{else}}{{/foo}}'),
"BLOCK:\n PATH:foo []\n PROGRAM:\n CONTENT[ ' bar ' ]\n {{^}}\n"
);
});
it('parses empty blocks with non-empty inverse section', function() {
equals(
astFor('{{#foo}}{{^}} bar {{/foo}}'),
"BLOCK:\n PATH:foo []\n PROGRAM:\n {{^}}\n CONTENT[ ' bar ' ]\n"
);
});
it('parses empty blocks with non-empty inverse (else-style) section', function() {
equals(
astFor('{{#foo}}{{else}} bar {{/foo}}'),
"BLOCK:\n PATH:foo []\n PROGRAM:\n {{^}}\n CONTENT[ ' bar ' ]\n"
);
});
it('parses a standalone inverse section', function() {
equals(
astFor('{{^foo}}bar{{/foo}}'),
"BLOCK:\n PATH:foo []\n {{^}}\n CONTENT[ 'bar' ]\n"
);
});
it('throws on old inverse section', function() {
shouldThrow(function() {
astFor('{{else foo}}bar{{/foo}}');
}, Error);
});
it('parses block with block params', function() {
equals(
astFor('{{#foo as |bar baz|}}content{{/foo}}'),
"BLOCK:\n PATH:foo []\n PROGRAM:\n BLOCK PARAMS: [ bar baz ]\n CONTENT[ 'content' ]\n"
);
});
it('parses inverse block with block params', function() {
equals(
astFor('{{^foo as |bar baz|}}content{{/foo}}'),
"BLOCK:\n PATH:foo []\n {{^}}\n BLOCK PARAMS: [ bar baz ]\n CONTENT[ 'content' ]\n"
);
});
it('parses chained inverse block with block params', function() {
equals(
astFor('{{#foo}}{{else foo as |bar baz|}}content{{/foo}}'),
"BLOCK:\n PATH:foo []\n PROGRAM:\n {{^}}\n BLOCK:\n PATH:foo []\n PROGRAM:\n BLOCK PARAMS: [ bar baz ]\n CONTENT[ 'content' ]\n"
);
});
it("raises if there's a Parse error", function() {
shouldThrow(
function() {
astFor('foo{{^}}bar');
},
Error,
/Parse error on line 1/
);
shouldThrow(
function() {
astFor('{{foo}');
},
Error,
/Parse error on line 1/
);
shouldThrow(
function() {
astFor('{{foo &}}');
},
Error,
/Parse error on line 1/
);
shouldThrow(
function() {
astFor('{{#goodbyes}}{{/hellos}}');
},
Error,
/goodbyes doesn't match hellos/
);
shouldThrow(
function() {
astFor('{{{{goodbyes}}}} {{{{/hellos}}}}');
},
Error,
/goodbyes doesn't match hellos/
);
});
it('should handle invalid paths', function() {
shouldThrow(
function() {
astFor('{{foo/../bar}}');
},
Error,
/Invalid path: foo\/\.\. - 1:2/
);
shouldThrow(
function() {
astFor('{{foo/./bar}}');
},
Error,
/Invalid path: foo\/\. - 1:2/
);
shouldThrow(
function() {
astFor('{{foo/this/bar}}');
},
Error,
/Invalid path: foo\/this - 1:2/
);
});
it('knows how to report the correct line number in errors', function() {
shouldThrow(
function() {
astFor('hello\nmy\n{{foo}');
},
Error,
/Parse error on line 3/
);
shouldThrow(
function() {
astFor('hello\n\nmy\n\n{{foo}');
},
Error,
/Parse error on line 5/
);
});
it('knows how to report the correct line number in errors when the first character is a newline', function() {
shouldThrow(
function() {
astFor('\n\nhello\n\nmy\n\n{{foo}');
},
Error,
/Parse error on line 7/
);
});
describe('externally compiled AST', function() {
it('can pass through an already-compiled AST', function() {
equals(
astFor({
type: 'Program',
body: [{ type: 'ContentStatement', value: 'Hello' }]
}),
"CONTENT[ 'Hello' ]\n"
);
});
});
describe('directives', function() {
it('should parse block directives', function() {
equals(
astFor('{{#* foo}}{{/foo}}'),
'DIRECTIVE BLOCK:\n PATH:foo []\n PROGRAM:\n'
);
});
it('should parse directives', function() {
equals(astFor('{{* foo}}'), '{{ DIRECTIVE PATH:foo [] }}\n');
});
it('should fail if directives have inverse', function() {
shouldThrow(
function() {
astFor('{{#* foo}}{{^}}{{/foo}}');
},
Error,
/Unexpected inverse/
);
});
});
it('GH1024 - should track program location properly', function() {
var p = Handlebars.parse(
'\n' +
' {{#if foo}}\n' +
' {{bar}}\n' +
' {{else}} {{baz}}\n' +
'\n' +
' {{/if}}\n' +
' '
);
// We really need a deep equals but for now this should be stable...
equals(
JSON.stringify(p.loc),
JSON.stringify({
start: { line: 1, column: 0 },
end: { line: 7, column: 4 }
})
);
equals(
JSON.stringify(p.body[1].program.loc),
JSON.stringify({
start: { line: 2, column: 13 },
end: { line: 4, column: 7 }
})
);
equals(
JSON.stringify(p.body[1].inverse.loc),
JSON.stringify({
start: { line: 4, column: 15 },
end: { line: 6, column: 5 }
})
);
});
});
handlebars.js-4.7.2/spec/partials.js 0000664 0000000 0000000 00000062112 13607154272 0017362 0 ustar 00root root 0000000 0000000 describe('partials', function() {
it('basic partials', function() {
var string = 'Dudes: {{#dudes}}{{> dude}}{{/dudes}}';
var partial = '{{name}} ({{url}}) ';
var hash = {
dudes: [
{ name: 'Yehuda', url: 'http://yehuda' },
{ name: 'Alan', url: 'http://alan' }
]
};
shouldCompileToWithPartials(
string,
[hash, {}, { dude: partial }],
true,
'Dudes: Yehuda (http://yehuda) Alan (http://alan) '
);
shouldCompileToWithPartials(
string,
[hash, {}, { dude: partial }, , false],
true,
'Dudes: Yehuda (http://yehuda) Alan (http://alan) '
);
});
it('dynamic partials', function() {
var string = 'Dudes: {{#dudes}}{{> (partial)}}{{/dudes}}';
var partial = '{{name}} ({{url}}) ';
var hash = {
dudes: [
{ name: 'Yehuda', url: 'http://yehuda' },
{ name: 'Alan', url: 'http://alan' }
]
};
var helpers = {
partial: function() {
return 'dude';
}
};
shouldCompileToWithPartials(
string,
[hash, helpers, { dude: partial }],
true,
'Dudes: Yehuda (http://yehuda) Alan (http://alan) '
);
shouldCompileToWithPartials(
string,
[hash, helpers, { dude: partial }, , false],
true,
'Dudes: Yehuda (http://yehuda) Alan (http://alan) '
);
});
it('failing dynamic partials', function() {
var string = 'Dudes: {{#dudes}}{{> (partial)}}{{/dudes}}';
var partial = '{{name}} ({{url}}) ';
var hash = {
dudes: [
{ name: 'Yehuda', url: 'http://yehuda' },
{ name: 'Alan', url: 'http://alan' }
]
};
var helpers = {
partial: function() {
return 'missing';
}
};
shouldThrow(
function() {
shouldCompileToWithPartials(
string,
[hash, helpers, { dude: partial }],
true,
'Dudes: Yehuda (http://yehuda) Alan (http://alan) '
);
},
Handlebars.Exception,
'The partial missing could not be found'
);
});
it('partials with context', function() {
var string = 'Dudes: {{>dude dudes}}';
var partial = '{{#this}}{{name}} ({{url}}) {{/this}}';
var hash = {
dudes: [
{ name: 'Yehuda', url: 'http://yehuda' },
{ name: 'Alan', url: 'http://alan' }
]
};
shouldCompileToWithPartials(
string,
[hash, {}, { dude: partial }],
true,
'Dudes: Yehuda (http://yehuda) Alan (http://alan) ',
'Partials can be passed a context'
);
});
it('partials with no context', function() {
var partial = '{{name}} ({{url}}) ';
var hash = {
dudes: [
{ name: 'Yehuda', url: 'http://yehuda' },
{ name: 'Alan', url: 'http://alan' }
]
};
shouldCompileToWithPartials(
'Dudes: {{#dudes}}{{>dude}}{{/dudes}}',
[hash, {}, { dude: partial }, { explicitPartialContext: true }],
true,
'Dudes: () () '
);
shouldCompileToWithPartials(
'Dudes: {{#dudes}}{{>dude name="foo"}}{{/dudes}}',
[hash, {}, { dude: partial }, { explicitPartialContext: true }],
true,
'Dudes: foo () foo () '
);
});
it('partials with string context', function() {
var string = 'Dudes: {{>dude "dudes"}}';
var partial = '{{.}}';
var hash = {};
shouldCompileToWithPartials(
string,
[hash, {}, { dude: partial }],
true,
'Dudes: dudes'
);
});
it('partials with undefined context', function() {
var string = 'Dudes: {{>dude dudes}}';
var partial = '{{foo}} Empty';
var hash = {};
shouldCompileToWithPartials(
string,
[hash, {}, { dude: partial }],
true,
'Dudes: Empty'
);
});
it('partials with duplicate parameters', function() {
shouldThrow(
function() {
CompilerContext.compile('Dudes: {{>dude dudes foo bar=baz}}');
},
Error,
'Unsupported number of partial arguments: 2 - 1:7'
);
});
it('partials with parameters', function() {
var string = 'Dudes: {{#dudes}}{{> dude others=..}}{{/dudes}}';
var partial = '{{others.foo}}{{name}} ({{url}}) ';
var hash = {
foo: 'bar',
dudes: [
{ name: 'Yehuda', url: 'http://yehuda' },
{ name: 'Alan', url: 'http://alan' }
]
};
shouldCompileToWithPartials(
string,
[hash, {}, { dude: partial }],
true,
'Dudes: barYehuda (http://yehuda) barAlan (http://alan) ',
'Basic partials output based on current context.'
);
});
it('partial in a partial', function() {
var string = 'Dudes: {{#dudes}}{{>dude}}{{/dudes}}';
var dude = '{{name}} {{> url}} ';
var url = '{{url}}';
var hash = {
dudes: [
{ name: 'Yehuda', url: 'http://yehuda' },
{ name: 'Alan', url: 'http://alan' }
]
};
shouldCompileToWithPartials(
string,
[hash, {}, { dude: dude, url: url }],
true,
'Dudes: Yehuda http://yehuda Alan http://alan ',
'Partials are rendered inside of other partials'
);
});
it('rendering undefined partial throws an exception', function() {
shouldThrow(
function() {
var template = CompilerContext.compile('{{> whatever}}');
template();
},
Handlebars.Exception,
'The partial whatever could not be found'
);
});
it('registering undefined partial throws an exception', function() {
shouldThrow(
function() {
var undef;
handlebarsEnv.registerPartial('undefined_test', undef);
},
Handlebars.Exception,
'Attempting to register a partial called "undefined_test" as undefined'
);
});
it('rendering template partial in vm mode throws an exception', function() {
shouldThrow(
function() {
var template = CompilerContext.compile('{{> whatever}}');
template();
},
Handlebars.Exception,
'The partial whatever could not be found'
);
});
it('rendering function partial in vm mode', function() {
var string = 'Dudes: {{#dudes}}{{> dude}}{{/dudes}}';
function partial(context) {
return context.name + ' (' + context.url + ') ';
}
var hash = {
dudes: [
{ name: 'Yehuda', url: 'http://yehuda' },
{ name: 'Alan', url: 'http://alan' }
]
};
shouldCompileTo(
string,
[hash, {}, { dude: partial }],
'Dudes: Yehuda (http://yehuda) Alan (http://alan) ',
'Function partials output based in VM.'
);
});
it('GH-14: a partial preceding a selector', function() {
var string = 'Dudes: {{>dude}} {{anotherDude}}';
var dude = '{{name}}';
var hash = { name: 'Jeepers', anotherDude: 'Creepers' };
shouldCompileToWithPartials(
string,
[hash, {}, { dude: dude }],
true,
'Dudes: Jeepers Creepers',
'Regular selectors can follow a partial'
);
});
it('Partials with slash paths', function() {
var string = 'Dudes: {{> shared/dude}}';
var dude = '{{name}}';
var hash = { name: 'Jeepers', anotherDude: 'Creepers' };
shouldCompileToWithPartials(
string,
[hash, {}, { 'shared/dude': dude }],
true,
'Dudes: Jeepers',
'Partials can use literal paths'
);
});
it('Partials with slash and point paths', function() {
var string = 'Dudes: {{> shared/dude.thing}}';
var dude = '{{name}}';
var hash = { name: 'Jeepers', anotherDude: 'Creepers' };
shouldCompileToWithPartials(
string,
[hash, {}, { 'shared/dude.thing': dude }],
true,
'Dudes: Jeepers',
'Partials can use literal with points in paths'
);
});
it('Global Partials', function() {
handlebarsEnv.registerPartial('globalTest', '{{anotherDude}}');
var string = 'Dudes: {{> shared/dude}} {{> globalTest}}';
var dude = '{{name}}';
var hash = { name: 'Jeepers', anotherDude: 'Creepers' };
shouldCompileToWithPartials(
string,
[hash, {}, { 'shared/dude': dude }],
true,
'Dudes: Jeepers Creepers',
'Partials can use globals or passed'
);
handlebarsEnv.unregisterPartial('globalTest');
equals(handlebarsEnv.partials.globalTest, undefined);
});
it('Multiple partial registration', function() {
handlebarsEnv.registerPartial({
'shared/dude': '{{name}}',
globalTest: '{{anotherDude}}'
});
var string = 'Dudes: {{> shared/dude}} {{> globalTest}}';
var hash = { name: 'Jeepers', anotherDude: 'Creepers' };
shouldCompileToWithPartials(
string,
[hash],
true,
'Dudes: Jeepers Creepers',
'Partials can use globals or passed'
);
});
it('Partials with integer path', function() {
var string = 'Dudes: {{> 404}}';
var dude = '{{name}}';
var hash = { name: 'Jeepers', anotherDude: 'Creepers' };
shouldCompileToWithPartials(
string,
[hash, {}, { 404: dude }],
true,
'Dudes: Jeepers',
'Partials can use literal paths'
);
});
it('Partials with complex path', function() {
var string = 'Dudes: {{> 404/asdf?.bar}}';
var dude = '{{name}}';
var hash = { name: 'Jeepers', anotherDude: 'Creepers' };
shouldCompileToWithPartials(
string,
[hash, {}, { '404/asdf?.bar': dude }],
true,
'Dudes: Jeepers',
'Partials can use literal paths'
);
});
it('Partials with escaped', function() {
var string = 'Dudes: {{> [+404/asdf?.bar]}}';
var dude = '{{name}}';
var hash = { name: 'Jeepers', anotherDude: 'Creepers' };
shouldCompileToWithPartials(
string,
[hash, {}, { '+404/asdf?.bar': dude }],
true,
'Dudes: Jeepers',
'Partials can use literal paths'
);
});
it('Partials with string', function() {
var string = "Dudes: {{> '+404/asdf?.bar'}}";
var dude = '{{name}}';
var hash = { name: 'Jeepers', anotherDude: 'Creepers' };
shouldCompileToWithPartials(
string,
[hash, {}, { '+404/asdf?.bar': dude }],
true,
'Dudes: Jeepers',
'Partials can use literal paths'
);
});
it('should handle empty partial', function() {
var string = 'Dudes: {{#dudes}}{{> dude}}{{/dudes}}';
var partial = '';
var hash = {
dudes: [
{ name: 'Yehuda', url: 'http://yehuda' },
{ name: 'Alan', url: 'http://alan' }
]
};
shouldCompileToWithPartials(
string,
[hash, {}, { dude: partial }],
true,
'Dudes: '
);
});
it('throw on missing partial', function() {
var compile = handlebarsEnv.compile;
handlebarsEnv.compile = undefined;
shouldThrow(
function() {
shouldCompileTo('{{> dude}}', [{}, {}, { dude: 'fail' }], '');
},
Error,
/The partial dude could not be compiled/
);
handlebarsEnv.compile = compile;
});
describe('partial blocks', function() {
it('should render partial block as default', function() {
shouldCompileToWithPartials(
'{{#> dude}}success{{/dude}}',
[{}, {}, {}],
true,
'success'
);
});
it('should execute default block with proper context', function() {
shouldCompileToWithPartials(
'{{#> dude context}}{{value}}{{/dude}}',
[{ context: { value: 'success' } }, {}, {}],
true,
'success'
);
});
it('should propagate block parameters to default block', function() {
shouldCompileToWithPartials(
'{{#with context as |me|}}{{#> dude}}{{me.value}}{{/dude}}{{/with}}',
[{ context: { value: 'success' } }, {}, {}],
true,
'success'
);
});
it('should not use partial block if partial exists', function() {
shouldCompileToWithPartials(
'{{#> dude}}fail{{/dude}}',
[{}, {}, { dude: 'success' }],
true,
'success'
);
});
it('should render block from partial', function() {
shouldCompileToWithPartials(
'{{#> dude}}success{{/dude}}',
[{}, {}, { dude: '{{> @partial-block }}' }],
true,
'success'
);
});
it('should be able to render the partial-block twice', function() {
shouldCompileToWithPartials(
'{{#> dude}}success{{/dude}}',
[{}, {}, { dude: '{{> @partial-block }} {{> @partial-block }}' }],
true,
'success success'
);
});
it('should render block from partial with context', function() {
shouldCompileToWithPartials(
'{{#> dude}}{{value}}{{/dude}}',
[
{ context: { value: 'success' } },
{},
{ dude: '{{#with context}}{{> @partial-block }}{{/with}}' }
],
true,
'success'
);
});
it('should be able to access the @data frame from a partial-block', function() {
shouldCompileToWithPartials(
'{{#> dude}}in-block: {{@root/value}}{{/dude}}',
[
{ value: 'success' },
{},
{
dude:
'before-block: {{@root/value}} {{> @partial-block }}'
}
],
true,
'before-block: success in-block: success'
);
});
it('should allow the #each-helper to be used along with partial-blocks', function() {
shouldCompileToWithPartials(
'{{#> list value}}value = {{.}}{{/list}}',
[
{ value: ['a', 'b', 'c'] },
{},
{
list:
'{{#each .}}{{> @partial-block}}{{/each}}'
}
],
true,
'value = avalue = bvalue = c'
);
});
it('should render block from partial with context (twice)', function() {
shouldCompileToWithPartials(
'{{#> dude}}{{value}}{{/dude}}',
[
{ context: { value: 'success' } },
{},
{
dude:
'{{#with context}}{{> @partial-block }} {{> @partial-block }}{{/with}}'
}
],
true,
'success success'
);
});
it('should render block from partial with context', function() {
shouldCompileToWithPartials(
'{{#> dude}}{{../context/value}}{{/dude}}',
[
{ context: { value: 'success' } },
{},
{ dude: '{{#with context}}{{> @partial-block }}{{/with}}' }
],
true,
'success'
);
});
it('should render block from partial with block params', function() {
shouldCompileToWithPartials(
'{{#with context as |me|}}{{#> dude}}{{me.value}}{{/dude}}{{/with}}',
[
{ context: { value: 'success' } },
{},
{ dude: '{{> @partial-block }}' }
],
true,
'success'
);
});
it('should render nested partial blocks', function() {
shouldCompileToWithPartials(
'{{#> outer}}{{value}}{{/outer}}',
[
{ value: 'success' },
{},
{
outer:
'{{#> nested}}{{> @partial-block}}{{/nested}}',
nested: '{{> @partial-block}}'
}
],
true,
'success'
);
});
it('should render nested partial blocks at different nesting levels', function() {
shouldCompileToWithPartials(
'{{#> outer}}{{value}}{{/outer}}',
[
{ value: 'success' },
{},
{
outer:
'{{#> nested}}{{> @partial-block}}{{/nested}}{{> @partial-block}}',
nested: '{{> @partial-block}}'
}
],
true,
'successsuccess'
);
});
it('should render nested partial blocks at different nesting levels (twice)', function() {
shouldCompileToWithPartials(
'{{#> outer}}{{value}}{{/outer}}',
[
{ value: 'success' },
{},
{
outer:
'{{#> nested}}{{> @partial-block}} {{> @partial-block}}{{/nested}}{{> @partial-block}}+{{> @partial-block}}',
nested: '{{> @partial-block}}'
}
],
true,
'success successsuccess+success'
);
});
it('should render nested partial blocks (twice at each level)', function() {
shouldCompileToWithPartials(
'{{#> outer}}{{value}}{{/outer}}',
[
{ value: 'success' },
{},
{
outer:
'{{#> nested}}{{> @partial-block}} {{> @partial-block}}{{/nested}}',
nested: '{{> @partial-block}}{{> @partial-block}}'
}
],
true,
'' +
'success successsuccess success' +
''
);
});
});
describe('inline partials', function() {
it('should define inline partials for template', function() {
shouldCompileTo(
'{{#*inline "myPartial"}}success{{/inline}}{{> myPartial}}',
{},
'success'
);
});
it('should overwrite multiple partials in the same template', function() {
shouldCompileTo(
'{{#*inline "myPartial"}}fail{{/inline}}{{#*inline "myPartial"}}success{{/inline}}{{> myPartial}}',
{},
'success'
);
});
it('should define inline partials for block', function() {
shouldCompileTo(
'{{#with .}}{{#*inline "myPartial"}}success{{/inline}}{{> myPartial}}{{/with}}',
{},
'success'
);
shouldThrow(
function() {
shouldCompileTo(
'{{#with .}}{{#*inline "myPartial"}}success{{/inline}}{{/with}}{{> myPartial}}',
{},
'success'
);
},
Error,
/myPartial could not/
);
});
it('should override global partials', function() {
shouldCompileTo(
'{{#*inline "myPartial"}}success{{/inline}}{{> myPartial}}',
{
hash: {},
partials: {
myPartial: function() {
return 'fail';
}
}
},
'success'
);
});
it('should override template partials', function() {
shouldCompileTo(
'{{#*inline "myPartial"}}fail{{/inline}}{{#with .}}{{#*inline "myPartial"}}success{{/inline}}{{> myPartial}}{{/with}}',
{},
'success'
);
});
it('should override partials down the entire stack', function() {
shouldCompileTo(
'{{#with .}}{{#*inline "myPartial"}}success{{/inline}}{{#with .}}{{#with .}}{{> myPartial}}{{/with}}{{/with}}{{/with}}',
{},
'success'
);
});
it('should define inline partials for partial call', function() {
shouldCompileToWithPartials(
'{{#*inline "myPartial"}}success{{/inline}}{{> dude}}',
[{}, {}, { dude: '{{> myPartial }}' }],
true,
'success'
);
});
it('should define inline partials in partial block call', function() {
shouldCompileToWithPartials(
'{{#> dude}}{{#*inline "myPartial"}}success{{/inline}}{{/dude}}',
[{}, {}, { dude: '{{> myPartial }}' }],
true,
'success'
);
});
it('should render nested inline partials', function() {
shouldCompileToWithPartials(
'{{#*inline "outer"}}{{#>inner}}{{>@partial-block}}{{/inner}}{{/inline}}' +
'{{#*inline "inner"}}{{>@partial-block}}{{/inline}}' +
'{{#>outer}}{{value}}{{/outer}}',
[{ value: 'success' }, {}, {}],
true,
'success'
);
});
it('should render nested inline partials with partial-blocks on different nesting levels', function() {
shouldCompileToWithPartials(
'{{#*inline "outer"}}{{#>inner}}{{>@partial-block}}{{/inner}}{{>@partial-block}}{{/inline}}' +
'{{#*inline "inner"}}{{>@partial-block}}{{/inline}}' +
'{{#>outer}}{{value}}{{/outer}}',
[{ value: 'success' }, {}, {}],
true,
'successsuccess'
);
});
it('should render nested inline partials (twice at each level)', function() {
shouldCompileToWithPartials(
'{{#*inline "outer"}}{{#>inner}}{{>@partial-block}} {{>@partial-block}}{{/inner}}{{/inline}}' +
'{{#*inline "inner"}}{{>@partial-block}}{{>@partial-block}}{{/inline}}' +
'{{#>outer}}{{value}}{{/outer}}',
[{ value: 'success' }, {}, {}],
true,
'success successsuccess success'
);
});
});
it('should pass compiler flags', function() {
if (Handlebars.compile) {
var env = Handlebars.create();
env.registerPartial('partial', '{{foo}}');
var template = env.compile('{{foo}} {{> partial}}', { noEscape: true });
equal(template({ foo: '<' }), '< <');
}
});
describe('standalone partials', function() {
it('indented partials', function() {
var string = 'Dudes:\n{{#dudes}}\n {{>dude}}\n{{/dudes}}';
var dude = '{{name}}\n';
var hash = {
dudes: [
{ name: 'Yehuda', url: 'http://yehuda' },
{ name: 'Alan', url: 'http://alan' }
]
};
shouldCompileToWithPartials(
string,
[hash, {}, { dude: dude }],
true,
'Dudes:\n Yehuda\n Alan\n'
);
});
it('nested indented partials', function() {
var string = 'Dudes:\n{{#dudes}}\n {{>dude}}\n{{/dudes}}';
var dude = '{{name}}\n {{> url}}';
var url = '{{url}}!\n';
var hash = {
dudes: [
{ name: 'Yehuda', url: 'http://yehuda' },
{ name: 'Alan', url: 'http://alan' }
]
};
shouldCompileToWithPartials(
string,
[hash, {}, { dude: dude, url: url }],
true,
'Dudes:\n Yehuda\n http://yehuda!\n Alan\n http://alan!\n'
);
});
it('prevent nested indented partials', function() {
var string = 'Dudes:\n{{#dudes}}\n {{>dude}}\n{{/dudes}}';
var dude = '{{name}}\n {{> url}}';
var url = '{{url}}!\n';
var hash = {
dudes: [
{ name: 'Yehuda', url: 'http://yehuda' },
{ name: 'Alan', url: 'http://alan' }
]
};
shouldCompileToWithPartials(
string,
[hash, {}, { dude: dude, url: url }, { preventIndent: true }],
true,
'Dudes:\n Yehuda\n http://yehuda!\n Alan\n http://alan!\n'
);
});
});
describe('compat mode', function() {
it('partials can access parents', function() {
var string = 'Dudes: {{#dudes}}{{> dude}}{{/dudes}}';
var partial = '{{name}} ({{url}}) {{root}} ';
var hash = {
root: 'yes',
dudes: [
{ name: 'Yehuda', url: 'http://yehuda' },
{ name: 'Alan', url: 'http://alan' }
]
};
shouldCompileToWithPartials(
string,
[hash, {}, { dude: partial }, true],
true,
'Dudes: Yehuda (http://yehuda) yes Alan (http://alan) yes '
);
});
it('partials can access parents with custom context', function() {
var string = 'Dudes: {{#dudes}}{{> dude "test"}}{{/dudes}}';
var partial = '{{name}} ({{url}}) {{root}} ';
var hash = {
root: 'yes',
dudes: [
{ name: 'Yehuda', url: 'http://yehuda' },
{ name: 'Alan', url: 'http://alan' }
]
};
shouldCompileToWithPartials(
string,
[hash, {}, { dude: partial }, true],
true,
'Dudes: Yehuda (http://yehuda) yes Alan (http://alan) yes '
);
});
it('partials can access parents without data', function() {
var string = 'Dudes: {{#dudes}}{{> dude}}{{/dudes}}';
var partial = '{{name}} ({{url}}) {{root}} ';
var hash = {
root: 'yes',
dudes: [
{ name: 'Yehuda', url: 'http://yehuda' },
{ name: 'Alan', url: 'http://alan' }
]
};
shouldCompileToWithPartials(
string,
[hash, {}, { dude: partial }, true, false],
true,
'Dudes: Yehuda (http://yehuda) yes Alan (http://alan) yes '
);
});
it('partials inherit compat', function() {
var string = 'Dudes: {{> dude}}';
var partial = '{{#dudes}}{{name}} ({{url}}) {{root}} {{/dudes}}';
var hash = {
root: 'yes',
dudes: [
{ name: 'Yehuda', url: 'http://yehuda' },
{ name: 'Alan', url: 'http://alan' }
]
};
shouldCompileToWithPartials(
string,
[hash, {}, { dude: partial }, true],
true,
'Dudes: Yehuda (http://yehuda) yes Alan (http://alan) yes '
);
});
});
});
handlebars.js-4.7.2/spec/precompiler.js 0000664 0000000 0000000 00000030166 13607154272 0020070 0 ustar 00root root 0000000 0000000 /* eslint-disable no-console */
describe('precompiler', function() {
// NOP Under non-node environments
if (typeof process === 'undefined') {
return;
}
var Handlebars = require('../lib'),
Precompiler = require('../dist/cjs/precompiler'),
fs = require('fs'),
uglify = require('uglify-js');
var log,
logFunction,
errorLog,
errorLogFunction,
precompile,
minify,
emptyTemplate = {
path: __dirname + '/artifacts/empty.handlebars',
name: 'empty',
source: ''
},
file,
content,
writeFileSync;
/**
* Mock the Module.prototype.require-function such that an error is thrown, when "uglify-js" is loaded.
*
* The function cleans up its mess when "callback" is finished
*
* @param {Error} loadError the error that should be thrown if uglify is loaded
* @param {function} callback a callback-function to run when the mock is active.
*/
function mockRequireUglify(loadError, callback) {
var Module = require('module');
var _resolveFilename = Module._resolveFilename;
delete require.cache[require.resolve('uglify-js')];
delete require.cache[require.resolve('../dist/cjs/precompiler')];
Module._resolveFilename = function(request, mod) {
if (request === 'uglify-js') {
throw loadError;
}
return _resolveFilename.call(this, request, mod);
};
try {
callback();
} finally {
Module._resolveFilename = _resolveFilename;
delete require.cache[require.resolve('uglify-js')];
delete require.cache[require.resolve('../dist/cjs/precompiler')];
}
}
beforeEach(function() {
precompile = Handlebars.precompile;
minify = uglify.minify;
writeFileSync = fs.writeFileSync;
// Mock stdout and stderr
logFunction = console.log;
log = '';
console.log = function() {
log += Array.prototype.join.call(arguments, '');
};
errorLogFunction = console.error;
errorLog = '';
console.error = function() {
errorLog += Array.prototype.join.call(arguments, '');
};
fs.writeFileSync = function(_file, _content) {
file = _file;
content = _content;
};
});
afterEach(function() {
Handlebars.precompile = precompile;
uglify.minify = minify;
fs.writeFileSync = writeFileSync;
console.log = logFunction;
console.error = errorLogFunction;
});
it('should output version', function() {
Precompiler.cli({ templates: [], version: true });
equals(log, Handlebars.VERSION);
});
it('should throw if lacking templates', function() {
shouldThrow(
function() {
Precompiler.cli({ templates: [] });
},
Handlebars.Exception,
'Must define at least one template or directory.'
);
});
it('should handle empty/filtered directories', function() {
Precompiler.cli({ hasDirectory: true, templates: [] });
// Success is not throwing
});
it('should throw when combining simple and minimized', function() {
shouldThrow(
function() {
Precompiler.cli({ templates: [__dirname], simple: true, min: true });
},
Handlebars.Exception,
'Unable to minimize simple output'
);
});
it('should throw when combining simple and multiple templates', function() {
shouldThrow(
function() {
Precompiler.cli({
templates: [
__dirname + '/artifacts/empty.handlebars',
__dirname + '/artifacts/empty.handlebars'
],
simple: true
});
},
Handlebars.Exception,
'Unable to output multiple templates in simple mode'
);
});
it('should throw when missing name', function() {
shouldThrow(
function() {
Precompiler.cli({ templates: [{ source: '' }], amd: true });
},
Handlebars.Exception,
'Name missing for template'
);
});
it('should throw when combining simple and directories', function() {
shouldThrow(
function() {
Precompiler.cli({ hasDirectory: true, templates: [1], simple: true });
},
Handlebars.Exception,
'Unable to output multiple templates in simple mode'
);
});
it('should output simple templates', function() {
Handlebars.precompile = function() {
return 'simple';
};
Precompiler.cli({ templates: [emptyTemplate], simple: true });
equal(log, 'simple\n');
});
it('should default to simple templates', function() {
Handlebars.precompile = function() {
return 'simple';
};
Precompiler.cli({ templates: [{ source: '' }] });
equal(log, 'simple\n');
});
it('should output amd templates', function() {
Handlebars.precompile = function() {
return 'amd';
};
Precompiler.cli({ templates: [emptyTemplate], amd: true });
equal(/template\(amd\)/.test(log), true);
});
it('should output multiple amd', function() {
Handlebars.precompile = function() {
return 'amd';
};
Precompiler.cli({
templates: [emptyTemplate, emptyTemplate],
amd: true,
namespace: 'foo'
});
equal(/templates = foo = foo \|\|/.test(log), true);
equal(/return templates/.test(log), true);
equal(/template\(amd\)/.test(log), true);
});
it('should output amd partials', function() {
Handlebars.precompile = function() {
return 'amd';
};
Precompiler.cli({ templates: [emptyTemplate], amd: true, partial: true });
equal(/return Handlebars\.partials\['empty'\]/.test(log), true);
equal(/template\(amd\)/.test(log), true);
});
it('should output multiple amd partials', function() {
Handlebars.precompile = function() {
return 'amd';
};
Precompiler.cli({
templates: [emptyTemplate, emptyTemplate],
amd: true,
partial: true
});
equal(/return Handlebars\.partials\[/.test(log), false);
equal(/template\(amd\)/.test(log), true);
});
it('should output commonjs templates', function() {
Handlebars.precompile = function() {
return 'commonjs';
};
Precompiler.cli({ templates: [emptyTemplate], commonjs: true });
equal(/template\(commonjs\)/.test(log), true);
});
it('should set data flag', function() {
Handlebars.precompile = function(data, options) {
equal(options.data, true);
return 'simple';
};
Precompiler.cli({ templates: [emptyTemplate], simple: true, data: true });
equal(log, 'simple\n');
});
it('should set known helpers', function() {
Handlebars.precompile = function(data, options) {
equal(options.knownHelpers.foo, true);
return 'simple';
};
Precompiler.cli({ templates: [emptyTemplate], simple: true, known: 'foo' });
equal(log, 'simple\n');
});
it('should output to file system', function() {
Handlebars.precompile = function() {
return 'simple';
};
Precompiler.cli({
templates: [emptyTemplate],
simple: true,
output: 'file!'
});
equal(file, 'file!');
equal(content, 'simple\n');
equal(log, '');
});
it('should output minimized templates', function() {
Handlebars.precompile = function() {
return 'amd';
};
uglify.minify = function() {
return { code: 'min' };
};
Precompiler.cli({ templates: [emptyTemplate], min: true });
equal(log, 'min');
});
it('should omit minimization gracefully, if uglify-js is missing', function() {
var error = new Error("Cannot find module 'uglify-js'");
error.code = 'MODULE_NOT_FOUND';
mockRequireUglify(error, function() {
var Precompiler = require('../dist/cjs/precompiler');
Handlebars.precompile = function() {
return 'amd';
};
Precompiler.cli({ templates: [emptyTemplate], min: true });
equal(/template\(amd\)/.test(log), true);
equal(/\n/.test(log), true);
equal(/Code minimization is disabled/.test(errorLog), true);
});
});
it('should fail on errors (other than missing module) while loading uglify-js', function() {
mockRequireUglify(new Error('Mock Error'), function() {
shouldThrow(
function() {
var Precompiler = require('../dist/cjs/precompiler');
Handlebars.precompile = function() {
return 'amd';
};
Precompiler.cli({ templates: [emptyTemplate], min: true });
},
Error,
'Mock Error'
);
});
});
it('should output map', function() {
Precompiler.cli({ templates: [emptyTemplate], map: 'foo.js.map' });
equal(file, 'foo.js.map');
equal(log.match(/sourceMappingURL=/g).length, 1);
});
it('should output map', function() {
Precompiler.cli({
templates: [emptyTemplate],
min: true,
map: 'foo.js.map'
});
equal(file, 'foo.js.map');
equal(log.match(/sourceMappingURL=/g).length, 1);
});
describe('#loadTemplates', function() {
it('should throw on missing template', function(done) {
Precompiler.loadTemplates({ files: ['foo'] }, function(err) {
equal(err.message, 'Unable to open template file "foo"');
done();
});
});
it('should enumerate directories by extension', function(done) {
Precompiler.loadTemplates(
{ files: [__dirname + '/artifacts'], extension: 'hbs' },
function(err, opts) {
equal(opts.templates.length, 1);
equal(opts.templates[0].name, 'example_2');
done(err);
}
);
});
it('should enumerate all templates by extension', function(done) {
Precompiler.loadTemplates(
{ files: [__dirname + '/artifacts'], extension: 'handlebars' },
function(err, opts) {
equal(opts.templates.length, 3);
equal(opts.templates[0].name, 'bom');
equal(opts.templates[1].name, 'empty');
equal(opts.templates[2].name, 'example_1');
done(err);
}
);
});
it('should handle regular expression characters in extensions', function(done) {
Precompiler.loadTemplates(
{ files: [__dirname + '/artifacts'], extension: 'hb(s' },
function(err) {
// Success is not throwing
done(err);
}
);
});
it('should handle BOM', function(done) {
var opts = {
files: [__dirname + '/artifacts/bom.handlebars'],
extension: 'handlebars',
bom: true
};
Precompiler.loadTemplates(opts, function(err, opts) {
equal(opts.templates[0].source, 'a');
done(err);
});
});
it('should handle different root', function(done) {
var opts = {
files: [__dirname + '/artifacts/empty.handlebars'],
simple: true,
root: 'foo/'
};
Precompiler.loadTemplates(opts, function(err, opts) {
equal(opts.templates[0].name, __dirname + '/artifacts/empty');
done(err);
});
});
it('should accept string inputs', function(done) {
var opts = { string: '' };
Precompiler.loadTemplates(opts, function(err, opts) {
equal(opts.templates[0].name, undefined);
equal(opts.templates[0].source, '');
done(err);
});
});
it('should accept string array inputs', function(done) {
var opts = { string: ['', 'bar'], name: ['beep', 'boop'] };
Precompiler.loadTemplates(opts, function(err, opts) {
equal(opts.templates[0].name, 'beep');
equal(opts.templates[0].source, '');
equal(opts.templates[1].name, 'boop');
equal(opts.templates[1].source, 'bar');
done(err);
});
});
it('should accept stdin input', function(done) {
var stdin = require('mock-stdin').stdin();
Precompiler.loadTemplates({ string: '-' }, function(err, opts) {
equal(opts.templates[0].source, 'foo');
done(err);
});
stdin.send('fo');
stdin.send('o');
stdin.end();
});
it('error on name missing', function(done) {
var opts = { string: ['', 'bar'] };
Precompiler.loadTemplates(opts, function(err) {
equal(
err.message,
'Number of names did not match the number of string inputs'
);
done();
});
});
it('should complete when no args are passed', function(done) {
Precompiler.loadTemplates({}, function(err, opts) {
equal(opts.templates.length, 0);
done(err);
});
});
});
});
handlebars.js-4.7.2/spec/regressions.js 0000664 0000000 0000000 00000036244 13607154272 0020115 0 ustar 00root root 0000000 0000000 describe('Regressions', function() {
it('GH-94: Cannot read property of undefined', function() {
var data = {
books: [
{
title: 'The origin of species',
author: {
name: 'Charles Darwin'
}
},
{
title: 'Lazarillo de Tormes'
}
]
};
var string = '{{#books}}{{title}}{{author.name}}{{/books}}';
shouldCompileTo(
string,
data,
'The origin of speciesCharles DarwinLazarillo de Tormes',
'Renders without an undefined property error'
);
});
it("GH-150: Inverted sections print when they shouldn't", function() {
var string = '{{^set}}not set{{/set}} :: {{#set}}set{{/set}}';
shouldCompileTo(
string,
{},
'not set :: ',
"inverted sections run when property isn't present in context"
);
shouldCompileTo(
string,
{ set: undefined },
'not set :: ',
'inverted sections run when property is undefined'
);
shouldCompileTo(
string,
{ set: false },
'not set :: ',
'inverted sections run when property is false'
);
shouldCompileTo(
string,
{ set: true },
' :: set',
"inverted sections don't run when property is true"
);
});
it('GH-158: Using array index twice, breaks the template', function() {
var string = '{{arr.[0]}}, {{arr.[1]}}';
var data = { arr: [1, 2] };
shouldCompileTo(string, data, '1, 2', 'it works as expected');
});
it("bug reported by @fat where lambdas weren't being properly resolved", function() {
var string =
'This is a slightly more complicated {{thing}}..\n' +
'{{! Just ignore this business. }}\n' +
'Check this out:\n' +
'{{#hasThings}}\n' +
'
\n' +
'{{#things}}\n' +
'
{{word}}
\n' +
'{{/things}}
.\n' +
'{{/hasThings}}\n' +
'{{^hasThings}}\n' +
'\n' +
'Nothing to check out...\n' +
'{{/hasThings}}';
var data = {
thing: function() {
return 'blah';
},
things: [
{ className: 'one', word: '@fat' },
{ className: 'two', word: '@dhg' },
{ className: 'three', word: '@sayrer' }
],
hasThings: function() {
return true;
}
};
var output =
'This is a slightly more complicated blah..\n' +
'Check this out:\n' +
'
\n' +
'
@fat
\n' +
'
@dhg
\n' +
'
@sayrer
\n' +
'
.\n';
shouldCompileTo(string, data, output);
});
it('GH-408: Multiple loops fail', function() {
var context = [
{ name: 'John Doe', location: { city: 'Chicago' } },
{ name: 'Jane Doe', location: { city: 'New York' } }
];
var template = CompilerContext.compile(
'{{#.}}{{name}}{{/.}}{{#.}}{{name}}{{/.}}{{#.}}{{name}}{{/.}}'
);
var result = template(context);
equals(
result,
'John DoeJane DoeJohn DoeJane DoeJohn DoeJane Doe',
'It should output multiple times'
);
});
it('GS-428: Nested if else rendering', function() {
var succeedingTemplate =
'{{#inverse}} {{#blk}} Unexpected {{/blk}} {{else}} {{#blk}} Expected {{/blk}} {{/inverse}}';
var failingTemplate =
'{{#inverse}} {{#blk}} Unexpected {{/blk}} {{else}} {{#blk}} Expected {{/blk}} {{/inverse}}';
var helpers = {
blk: function(block) {
return block.fn('');
},
inverse: function(block) {
return block.inverse('');
}
};
shouldCompileTo(succeedingTemplate, [{}, helpers], ' Expected ');
shouldCompileTo(failingTemplate, [{}, helpers], ' Expected ');
});
it('GH-458: Scoped this identifier', function() {
shouldCompileTo('{{./foo}}', { foo: 'bar' }, 'bar');
});
it('GH-375: Unicode line terminators', function() {
shouldCompileTo('\u2028', {}, '\u2028');
});
it('GH-534: Object prototype aliases', function() {
/* eslint-disable no-extend-native */
Object.prototype[0xd834] = true;
shouldCompileTo('{{foo}}', { foo: 'bar' }, 'bar');
delete Object.prototype[0xd834];
/* eslint-enable no-extend-native */
});
it('GH-437: Matching escaping', function() {
shouldThrow(function() {
CompilerContext.compile('{{{a}}');
}, Error);
shouldThrow(function() {
CompilerContext.compile('{{a}}}');
}, Error);
});
it('GH-676: Using array in escaping mustache fails', function() {
var string = '{{arr}}';
var data = { arr: [1, 2] };
shouldCompileTo(string, data, data.arr.toString(), 'it works as expected');
});
it('Mustache man page', function() {
var string =
'Hello {{name}}. You have just won ${{value}}!{{#in_ca}} Well, ${{taxed_value}}, after taxes.{{/in_ca}}';
var data = {
name: 'Chris',
value: 10000,
taxed_value: 10000 - 10000 * 0.4,
in_ca: true
};
shouldCompileTo(
string,
data,
'Hello Chris. You have just won $10000! Well, $6000, after taxes.',
'the hello world mustache example works'
);
});
it('GH-731: zero context rendering', function() {
shouldCompileTo(
'{{#foo}} This is {{bar}} ~ {{/foo}}',
{ foo: 0, bar: 'OK' },
' This is ~ '
);
});
it('GH-820: zero pathed rendering', function() {
shouldCompileTo('{{foo.bar}}', { foo: 0 }, '');
});
it('GH-837: undefined values for helpers', function() {
var helpers = {
str: function(value) {
return value + '';
}
};
shouldCompileTo('{{str bar.baz}}', [{}, helpers], 'undefined');
});
it('GH-926: Depths and de-dupe', function() {
var context = {
name: 'foo',
data: [1],
notData: [1]
};
var template = CompilerContext.compile(
'{{#if dater}}{{#each data}}{{../name}}{{/each}}{{else}}{{#each notData}}{{../name}}{{/each}}{{/if}}'
);
var result = template(context);
equals(result, 'foo');
});
it('GH-1021: Each empty string key', function() {
var data = {
'': 'foo',
name: 'Chris',
value: 10000
};
shouldCompileTo(
'{{#each data}}Key: {{@key}}\n{{/each}}',
{ data: data },
'Key: \nKey: name\nKey: value\n'
);
});
it('GH-1054: Should handle simple safe string responses', function() {
var root = '{{#wrap}}{{>partial}}{{/wrap}}';
var partials = {
partial: '{{#wrap}}{{/wrap}}'
};
var helpers = {
wrap: function(options) {
return new Handlebars.SafeString(options.fn());
}
};
shouldCompileToWithPartials(
root,
[{}, helpers, partials],
true,
''
);
});
it('GH-1065: Sparse arrays', function() {
var array = [];
array[1] = 'foo';
array[3] = 'bar';
shouldCompileTo(
'{{#each array}}{{@index}}{{.}}{{/each}}',
{ array: array },
'1foo3bar'
);
});
it('GH-1093: Undefined helper context', function() {
var obj = { foo: undefined, bar: 'bat' };
var helpers = {
helper: function() {
// It's valid to execute a block against an undefined context, but
// helpers can not do so, so we expect to have an empty object here;
for (var name in this) {
if (Object.prototype.hasOwnProperty.call(this, name)) {
return 'found';
}
}
// And to make IE happy, check for the known string as length is not enumerated.
return this === 'bat' ? 'found' : 'not';
}
};
shouldCompileTo(
'{{#each obj}}{{{helper}}}{{.}}{{/each}}',
[{ obj: obj }, helpers],
'notfoundbat'
);
});
it('should support multiple levels of inline partials', function() {
var string =
'{{#> layout}}{{#*inline "subcontent"}}subcontent{{/inline}}{{/layout}}';
var partials = {
doctype: 'doctype{{> content}}',
layout:
'{{#> doctype}}{{#*inline "content"}}layout{{> subcontent}}{{/inline}}{{/doctype}}'
};
shouldCompileToWithPartials(
string,
[{}, {}, partials],
true,
'doctypelayoutsubcontent'
);
});
it('GH-1089: should support failover content in multiple levels of inline partials', function() {
var string = '{{#> layout}}{{/layout}}';
var partials = {
doctype: 'doctype{{> content}}',
layout:
'{{#> doctype}}{{#*inline "content"}}layout{{#> subcontent}}subcontent{{/subcontent}}{{/inline}}{{/doctype}}'
};
shouldCompileToWithPartials(
string,
[{}, {}, partials],
true,
'doctypelayoutsubcontent'
);
});
it('GH-1099: should support greater than 3 nested levels of inline partials', function() {
var string = '{{#> layout}}Outer{{/layout}}';
var partials = {
layout: '{{#> inner}}Inner{{/inner}}{{> @partial-block }}',
inner: ''
};
shouldCompileToWithPartials(string, [{}, {}, partials], true, 'Outer');
});
it('GH-1135 : Context handling within each iteration', function() {
var obj = { array: [1], name: 'John' };
var helpers = {
myif: function(conditional, options) {
if (conditional) {
return options.fn(this);
} else {
return options.inverse(this);
}
}
};
shouldCompileTo(
'{{#each array}}\n' +
' 1. IF: {{#if true}}{{../name}}-{{../../name}}-{{../../../name}}{{/if}}\n' +
' 2. MYIF: {{#myif true}}{{../name}}={{../../name}}={{../../../name}}{{/myif}}\n' +
'{{/each}}',
[obj, helpers],
' 1. IF: John--\n' + ' 2. MYIF: John==\n'
);
});
it('GH-1186: Support block params for existing programs', function() {
var string =
'{{#*inline "test"}}{{> @partial-block }}{{/inline}}' +
'{{#>test }}{{#each listOne as |item|}}{{ item }}{{/each}}{{/test}}' +
'{{#>test }}{{#each listTwo as |item|}}{{ item }}{{/each}}{{/test}}';
shouldCompileTo(string, { listOne: ['a'], listTwo: ['b'] }, 'ab', '');
});
it('GH-1319: "unless" breaks when "each" value equals "null"', function() {
var string =
'{{#each list}}{{#unless ./prop}}parent={{../value}} {{/unless}}{{/each}}';
shouldCompileTo(
string,
{ value: 'parent', list: [null, 'a'] },
'parent=parent parent=parent ',
''
);
});
it('GH-1341: 4.0.7 release breaks {{#if @partial-block}} usage', function() {
var string = 'template {{>partial}} template';
var partials = {
partialWithBlock:
'{{#if @partial-block}} block {{> @partial-block}} block {{/if}}',
partial: '{{#> partialWithBlock}} partial {{/partialWithBlock}}'
};
shouldCompileToWithPartials(
string,
[{}, {}, partials],
true,
'template block partial block template'
);
});
describe('GH-1561: 4.3.x should still work with precompiled templates from 4.0.0 <= x < 4.3.0', function() {
it('should compile and execute templates', function() {
var newHandlebarsInstance = Handlebars.create();
registerTemplate(newHandlebarsInstance, compiledTemplateVersion7());
newHandlebarsInstance.registerHelper('loud', function(value) {
return value.toUpperCase();
});
var result = newHandlebarsInstance.templates['test.hbs']({
name: 'yehuda'
});
equals(result.trim(), 'YEHUDA');
});
it('should call "helperMissing" if a helper is missing', function() {
var newHandlebarsInstance = Handlebars.create();
shouldThrow(
function() {
registerTemplate(newHandlebarsInstance, compiledTemplateVersion7());
newHandlebarsInstance.templates['test.hbs']({});
},
Handlebars.Exception,
'Missing helper: "loud"'
);
});
it('should pass "options.lookupProperty" to "lookup"-helper, even with old templates', function() {
var newHandlebarsInstance = Handlebars.create();
registerTemplate(
newHandlebarsInstance,
compiledTemplateVersion7_usingLookupHelper()
);
newHandlebarsInstance.templates['test.hbs']({});
expect(
newHandlebarsInstance.templates['test.hbs']({
property: 'a',
test: { a: 'b' }
})
).to.equal('b');
});
function registerTemplate(Handlebars, compileTemplate) {
var template = Handlebars.template,
templates = (Handlebars.templates = Handlebars.templates || {});
templates['test.hbs'] = template(compileTemplate);
}
function compiledTemplateVersion7() {
return {
compiler: [7, '>= 4.0.0'],
main: function(container, depth0, helpers, partials, data) {
return (
container.escapeExpression(
(
helpers.loud ||
(depth0 && depth0.loud) ||
helpers.helperMissing
).call(
depth0 != null ? depth0 : container.nullContext || {},
depth0 != null ? depth0.name : depth0,
{ name: 'loud', hash: {}, data: data }
)
) + '\n\n'
);
},
useData: true
};
}
function compiledTemplateVersion7_usingLookupHelper() {
// This is the compiled version of "{{lookup test property}}"
return {
compiler: [7, '>= 4.0.0'],
main: function(container, depth0, helpers, partials, data) {
return container.escapeExpression(
helpers.lookup.call(
depth0 != null ? depth0 : container.nullContext || {},
depth0 != null ? depth0.test : depth0,
depth0 != null ? depth0.property : depth0,
{
name: 'lookup',
hash: {},
data: data
}
)
);
},
useData: true
};
}
});
it('should allow hash with protected array names', function() {
var obj = { array: [1], name: 'John' };
var helpers = {
helpa: function(options) {
return options.hash.length;
}
};
shouldCompileTo('{{helpa length="foo"}}', [obj, helpers], 'foo');
});
describe('GH-1598: Performance degradation for partials since v4.3.0', function() {
// Do not run test for runs without compiler
if (!Handlebars.compile) {
return;
}
var newHandlebarsInstance;
beforeEach(function() {
newHandlebarsInstance = Handlebars.create();
});
afterEach(function() {
sinon.restore();
});
it('should only compile global partials once', function() {
var templateSpy = sinon.spy(newHandlebarsInstance, 'template');
newHandlebarsInstance.registerPartial({
dude: 'I am a partial'
});
var string = 'Dudes: {{> dude}} {{> dude}}';
newHandlebarsInstance.compile(string)(); // This should compile template + partial once
newHandlebarsInstance.compile(string)(); // This should only compile template
equal(templateSpy.callCount, 3);
sinon.restore();
});
});
describe("GH-1639: TypeError: Cannot read property 'apply' of undefined\" when handlebars version > 4.6.0 (undocumented, deprecated usage)", function() {
it('should treat undefined helpers like non-existing helpers', function() {
expectTemplate('{{foo}}')
.withHelper('foo', undefined)
.withInput({ foo: 'bar' })
.toCompileTo('bar');
});
});
});
handlebars.js-4.7.2/spec/require.js 0000664 0000000 0000000 00000001316 13607154272 0017216 0 ustar 00root root 0000000 0000000 if (typeof require !== 'undefined' && require.extensions['.handlebars']) {
describe('Require', function() {
it('Load .handlebars files with require()', function() {
var template = require('./artifacts/example_1');
equal(template, require('./artifacts/example_1.handlebars'));
var expected = 'foo\n';
var result = template({ foo: 'foo' });
equal(result, expected);
});
it('Load .hbs files with require()', function() {
var template = require('./artifacts/example_2');
equal(template, require('./artifacts/example_2.hbs'));
var expected = 'Hello, World!\n';
var result = template({ name: 'World' });
equal(result, expected);
});
});
}
handlebars.js-4.7.2/spec/runtime.js 0000664 0000000 0000000 00000006430 13607154272 0017227 0 ustar 00root root 0000000 0000000 describe('runtime', function() {
describe('#template', function() {
it('should throw on invalid templates', function() {
shouldThrow(
function() {
Handlebars.template({});
},
Error,
'Unknown template object: object'
);
shouldThrow(
function() {
Handlebars.template();
},
Error,
'Unknown template object: undefined'
);
shouldThrow(
function() {
Handlebars.template('');
},
Error,
'Unknown template object: string'
);
});
it('should throw on version mismatch', function() {
shouldThrow(
function() {
Handlebars.template({
main: {},
compiler: [Handlebars.COMPILER_REVISION + 1]
});
},
Error,
/Template was precompiled with a newer version of Handlebars than the current runtime/
);
shouldThrow(
function() {
Handlebars.template({
main: {},
compiler: [Handlebars.LAST_COMPATIBLE_COMPILER_REVISION - 1]
});
},
Error,
/Template was precompiled with an older version of Handlebars than the current runtime/
);
shouldThrow(
function() {
Handlebars.template({
main: {}
});
},
Error,
/Template was precompiled with an older version of Handlebars than the current runtime/
);
});
});
describe('#child', function() {
if (!Handlebars.compile) {
return;
}
it('should throw for depthed methods without depths', function() {
shouldThrow(
function() {
var template = Handlebars.compile('{{#foo}}{{../bar}}{{/foo}}');
// Calling twice to hit the non-compiled case.
template._setup({});
template._setup({});
template._child(1);
},
Error,
'must pass parent depths'
);
});
it('should throw for block param methods without params', function() {
shouldThrow(
function() {
var template = Handlebars.compile('{{#foo as |foo|}}{{foo}}{{/foo}}');
// Calling twice to hit the non-compiled case.
template._setup({});
template._setup({});
template._child(1);
},
Error,
'must pass block params'
);
});
it('should expose child template', function() {
var template = Handlebars.compile('{{#foo}}bar{{/foo}}');
// Calling twice to hit the non-compiled case.
equal(template._child(1)(), 'bar');
equal(template._child(1)(), 'bar');
});
it('should render depthed content', function() {
var template = Handlebars.compile('{{#foo}}{{../bar}}{{/foo}}');
// Calling twice to hit the non-compiled case.
equal(template._child(1, undefined, [], [{ bar: 'baz' }])(), 'baz');
});
});
describe('#noConflict', function() {
if (!CompilerContext.browser) {
return;
}
it('should reset on no conflict', function() {
var reset = Handlebars;
Handlebars.noConflict();
equal(Handlebars, 'no-conflict');
Handlebars = 'really, none';
reset.noConflict();
equal(Handlebars, 'really, none');
Handlebars = reset;
});
});
});
handlebars.js-4.7.2/spec/security.js 0000664 0000000 0000000 00000033676 13607154272 0017427 0 ustar 00root root 0000000 0000000 describe('security issues', function() {
describe('GH-1495: Prevent Remote Code Execution via constructor', function() {
it('should not allow constructors to be accessed', function() {
expectTemplate('{{lookup (lookup this "constructor") "name"}}')
.withInput({})
.toCompileTo('');
expectTemplate('{{constructor.name}}')
.withInput({})
.toCompileTo('');
});
it('GH-1603: should not allow constructors to be accessed (lookup via toString)', function() {
expectTemplate('{{lookup (lookup this (list "constructor")) "name"}}')
.withInput({})
.withHelper('list', function(element) {
return [element];
})
.toCompileTo('');
});
it('should allow the "constructor" property to be accessed if it is an "ownProperty"', function() {
shouldCompileTo(
'{{constructor.name}}',
{
constructor: {
name: 'here we go'
}
},
'here we go'
);
shouldCompileTo(
'{{lookup (lookup this "constructor") "name"}}',
{
constructor: {
name: 'here we go'
}
},
'here we go'
);
});
it('should allow the "constructor" property to be accessed if it is an "own property"', function() {
shouldCompileTo(
'{{lookup (lookup this "constructor") "name"}}',
{
constructor: {
name: 'here we go'
}
},
'here we go'
);
});
});
describe('GH-1558: Prevent explicit call of helperMissing-helpers', function() {
if (!Handlebars.compile) {
return;
}
describe('without the option "allowExplicitCallOfHelperMissing"', function() {
it('should throw an exception when calling "{{helperMissing}}" ', function() {
shouldThrow(function() {
var template = Handlebars.compile('{{helperMissing}}');
template({});
}, Error);
});
it('should throw an exception when calling "{{#helperMissing}}{{/helperMissing}}" ', function() {
shouldThrow(function() {
var template = Handlebars.compile(
'{{#helperMissing}}{{/helperMissing}}'
);
template({});
}, Error);
});
it('should throw an exception when calling "{{blockHelperMissing "abc" .}}" ', function() {
var functionCalls = [];
expect(function() {
var template = Handlebars.compile('{{blockHelperMissing "abc" .}}');
template({
fn: function() {
functionCalls.push('called');
}
});
}).to.throw(Error);
expect(functionCalls.length).to.equal(0);
});
it('should throw an exception when calling "{{#blockHelperMissing .}}{{/blockHelperMissing}}"', function() {
shouldThrow(function() {
var template = Handlebars.compile(
'{{#blockHelperMissing .}}{{/blockHelperMissing}}'
);
template({
fn: function() {
return 'functionInData';
}
});
}, Error);
});
});
describe('with the option "allowCallsToHelperMissing" set to true', function() {
it('should not throw an exception when calling "{{helperMissing}}" ', function() {
var template = Handlebars.compile('{{helperMissing}}');
template({}, { allowCallsToHelperMissing: true });
});
it('should not throw an exception when calling "{{#helperMissing}}{{/helperMissing}}" ', function() {
var template = Handlebars.compile(
'{{#helperMissing}}{{/helperMissing}}'
);
template({}, { allowCallsToHelperMissing: true });
});
it('should not throw an exception when calling "{{blockHelperMissing "abc" .}}" ', function() {
var functionCalls = [];
var template = Handlebars.compile('{{blockHelperMissing "abc" .}}');
template(
{
fn: function() {
functionCalls.push('called');
}
},
{ allowCallsToHelperMissing: true }
);
equals(functionCalls.length, 1);
});
it('should not throw an exception when calling "{{#blockHelperMissing .}}{{/blockHelperMissing}}"', function() {
var template = Handlebars.compile(
'{{#blockHelperMissing true}}sdads{{/blockHelperMissing}}'
);
template({}, { allowCallsToHelperMissing: true });
});
});
});
describe('GH-1563', function() {
it('should not allow to access constructor after overriding via __defineGetter__', function() {
if ({}.__defineGetter__ == null || {}.__lookupGetter__ == null) {
return this.skip(); // Browser does not support this exploit anyway
}
expectTemplate(
'{{__defineGetter__ "undefined" valueOf }}' +
'{{#with __lookupGetter__ }}' +
'{{__defineGetter__ "propertyIsEnumerable" (this.bind (this.bind 1)) }}' +
'{{constructor.name}}' +
'{{/with}}'
)
.withInput({})
.toThrow(/Missing helper: "__defineGetter__"/);
});
});
describe('GH-1595: dangerous properties', function() {
var templates = [
'{{constructor}}',
'{{__defineGetter__}}',
'{{__defineSetter__}}',
'{{__lookupGetter__}}',
'{{__proto__}}',
'{{lookup this "constructor"}}',
'{{lookup this "__defineGetter__"}}',
'{{lookup this "__defineSetter__"}}',
'{{lookup this "__lookupGetter__"}}',
'{{lookup this "__proto__"}}'
];
templates.forEach(function(template) {
describe('access should be denied to ' + template, function() {
it('by default', function() {
expectTemplate(template)
.withInput({})
.toCompileTo('');
});
it(' with proto-access enabled', function() {
expectTemplate(template)
.withInput({})
.withRuntimeOptions({
allowProtoPropertiesByDefault: true,
allowProtoMethodsByDefault: true
})
.toCompileTo('');
});
});
});
});
describe('GH-1631: disallow access to prototype functions', function() {
function TestClass() {}
TestClass.prototype.aProperty = 'propertyValue';
TestClass.prototype.aMethod = function() {
return 'returnValue';
};
beforeEach(function() {
handlebarsEnv.resetLoggedPropertyAccesses();
});
afterEach(function() {
sinon.restore();
});
describe('control access to prototype methods via "allowedProtoMethods"', function() {
checkProtoMethodAccess({});
describe('in compat mode', function() {
checkProtoMethodAccess({ compat: true });
});
function checkProtoMethodAccess(compileOptions) {
it('should be prohibited by default and log a warning', function() {
var spy = sinon.spy(console, 'error');
expectTemplate('{{aMethod}}')
.withInput(new TestClass())
.withCompileOptions(compileOptions)
.toCompileTo('');
expect(spy.calledOnce).to.be.true();
expect(spy.args[0][0]).to.match(/Handlebars: Access has been denied/);
});
it('should only log the warning once', function() {
var spy = sinon.spy(console, 'error');
expectTemplate('{{aMethod}}')
.withInput(new TestClass())
.withCompileOptions(compileOptions)
.toCompileTo('');
expectTemplate('{{aMethod}}')
.withInput(new TestClass())
.withCompileOptions(compileOptions)
.toCompileTo('');
expect(spy.calledOnce).to.be.true();
expect(spy.args[0][0]).to.match(/Handlebars: Access has been denied/);
});
it('can be allowed, which disables the warning', function() {
var spy = sinon.spy(console, 'error');
expectTemplate('{{aMethod}}')
.withInput(new TestClass())
.withCompileOptions(compileOptions)
.withRuntimeOptions({
allowedProtoMethods: {
aMethod: true
}
})
.toCompileTo('returnValue');
expect(spy.callCount).to.equal(0);
});
it('can be turned on by default, which disables the warning', function() {
var spy = sinon.spy(console, 'error');
expectTemplate('{{aMethod}}')
.withInput(new TestClass())
.withCompileOptions(compileOptions)
.withRuntimeOptions({
allowProtoMethodsByDefault: true
})
.toCompileTo('returnValue');
expect(spy.callCount).to.equal(0);
});
it('can be turned off by default, which disables the warning', function() {
var spy = sinon.spy(console, 'error');
expectTemplate('{{aMethod}}')
.withInput(new TestClass())
.withCompileOptions(compileOptions)
.withRuntimeOptions({
allowProtoMethodsByDefault: false
})
.toCompileTo('');
expect(spy.callCount).to.equal(0);
});
it('can be turned off, if turned on by default', function() {
expectTemplate('{{aMethod}}')
.withInput(new TestClass())
.withCompileOptions(compileOptions)
.withRuntimeOptions({
allowProtoMethodsByDefault: true,
allowedProtoMethods: {
aMethod: false
}
})
.toCompileTo('');
});
}
it('should cause the recursive lookup by default (in "compat" mode)', function() {
expectTemplate('{{#aString}}{{trim}}{{/aString}}')
.withInput({ aString: ' abc ', trim: 'trim' })
.withCompileOptions({ compat: true })
.toCompileTo('trim');
});
it('should not cause the recursive lookup if allowed through options(in "compat" mode)', function() {
expectTemplate('{{#aString}}{{trim}}{{/aString}}')
.withInput({ aString: ' abc ', trim: 'trim' })
.withCompileOptions({ compat: true })
.withRuntimeOptions({
allowedProtoMethods: {
trim: true
}
})
.toCompileTo('abc');
});
});
describe('control access to prototype non-methods via "allowedProtoProperties" and "allowProtoPropertiesByDefault', function() {
checkProtoPropertyAccess({});
describe('in compat-mode', function() {
checkProtoPropertyAccess({ compat: true });
});
function checkProtoPropertyAccess(compileOptions) {
it('should be prohibited by default and log a warning', function() {
var spy = sinon.spy(console, 'error');
expectTemplate('{{aProperty}}')
.withInput(new TestClass())
.withCompileOptions(compileOptions)
.toCompileTo('');
expect(spy.calledOnce).to.be.true();
expect(spy.args[0][0]).to.match(/Handlebars: Access has been denied/);
});
it('can be explicitly prohibited by default, which disables the warning', function() {
var spy = sinon.spy(console, 'error');
expectTemplate('{{aProperty}}')
.withInput(new TestClass())
.withCompileOptions(compileOptions)
.withRuntimeOptions({
allowProtoPropertiesByDefault: false
})
.toCompileTo('');
expect(spy.callCount).to.equal(0);
});
it('can be turned on, which disables the warning', function() {
var spy = sinon.spy(console, 'error');
expectTemplate('{{aProperty}}')
.withInput(new TestClass())
.withCompileOptions(compileOptions)
.withRuntimeOptions({
allowedProtoProperties: {
aProperty: true
}
})
.toCompileTo('propertyValue');
expect(spy.callCount).to.equal(0);
});
it('can be turned on by default, which disables the warning', function() {
var spy = sinon.spy(console, 'error');
expectTemplate('{{aProperty}}')
.withInput(new TestClass())
.withCompileOptions(compileOptions)
.withRuntimeOptions({
allowProtoPropertiesByDefault: true
})
.toCompileTo('propertyValue');
expect(spy.callCount).to.equal(0);
});
it('can be turned off, if turned on by default', function() {
expectTemplate('{{aProperty}}')
.withInput(new TestClass())
.withCompileOptions(compileOptions)
.withRuntimeOptions({
allowProtoPropertiesByDefault: true,
allowedProtoProperties: {
aProperty: false
}
})
.toCompileTo('');
});
}
});
describe('compatibility with old runtimes, that do not provide the function "container.lookupProperty"', function() {
beforeEach(function simulateRuntimeWithoutLookupProperty() {
var oldTemplateMethod = handlebarsEnv.template;
sinon.replace(handlebarsEnv, 'template', function(templateSpec) {
templateSpec.main = wrapToAdjustContainer(templateSpec.main);
return oldTemplateMethod.call(this, templateSpec);
});
});
afterEach(function() {
sinon.restore();
});
it('should work with simple properties', function() {
expectTemplate('{{aProperty}}')
.withInput({ aProperty: 'propertyValue' })
.toCompileTo('propertyValue');
});
it('should work with Array.prototype.length', function() {
expectTemplate('{{anArray.length}}')
.withInput({ anArray: ['a', 'b', 'c'] })
.toCompileTo('3');
});
});
});
});
function wrapToAdjustContainer(precompiledTemplateFunction) {
return function templateFunctionWrapper(container /*, more args */) {
delete container.lookupProperty;
return precompiledTemplateFunction.apply(this, arguments);
};
}
handlebars.js-4.7.2/spec/source-map.js 0000664 0000000 0000000 00000002735 13607154272 0017623 0 ustar 00root root 0000000 0000000 try {
if (typeof define !== 'function' || !define.amd) {
var SourceMap = require('source-map'),
SourceMapConsumer = SourceMap.SourceMapConsumer;
}
} catch (err) {
/* NOP for in browser */
}
describe('source-map', function() {
if (!Handlebars.precompile || !SourceMap) {
return;
}
it('should safely include source map info', function() {
var template = Handlebars.precompile('{{hello}}', {
destName: 'dest.js',
srcName: 'src.hbs'
});
equal(!!template.code, true);
equal(!!template.map, !CompilerContext.browser);
});
it('should map source properly', function() {
var templateSource =
' b{{hello}} \n {{bar}}a {{#block arg hash=(subex 1 subval)}}{{/block}}',
template = Handlebars.precompile(templateSource, {
destName: 'dest.js',
srcName: 'src.hbs'
});
if (template.map) {
var consumer = new SourceMapConsumer(template.map),
lines = template.code.split('\n'),
srcLines = templateSource.split('\n'),
generated = grepLine('" b"', lines),
source = grepLine(' b', srcLines);
var mapped = consumer.originalPositionFor(generated);
equal(mapped.line, source.line);
equal(mapped.column, source.column);
}
});
});
function grepLine(token, lines) {
for (var i = 0; i < lines.length; i++) {
var column = lines[i].indexOf(token);
if (column >= 0) {
return {
line: i + 1,
column: column
};
}
}
}
handlebars.js-4.7.2/spec/spec.js 0000664 0000000 0000000 00000003551 13607154272 0016477 0 ustar 00root root 0000000 0000000 describe('spec', function() {
// NOP Under non-node environments
if (typeof process === 'undefined') {
return;
}
var _ = require('underscore'),
fs = require('fs');
var specDir = __dirname + '/mustache/specs/';
var specs = _.filter(fs.readdirSync(specDir), function(name) {
return /.*\.json$/.test(name);
});
_.each(specs, function(name) {
var spec = require(specDir + name);
_.each(spec.tests, function(test) {
// Our lambda implementation knowingly deviates from the optional Mustace lambda spec
// We also do not support alternative delimeters
if (
name === '~lambdas.json' ||
// We also choose to throw if paritals are not found
(name === 'partials.json' && test.name === 'Failed Lookup') ||
// We nest the entire response from partials, not just the literals
(name === 'partials.json' && test.name === 'Standalone Indentation') ||
/\{\{=/.test(test.template) ||
_.any(test.partials, function(partial) {
return /\{\{=/.test(partial);
})
) {
it.skip(name + ' - ' + test.name);
return;
}
var data = _.clone(test.data);
if (data.lambda) {
// Blergh
/* eslint-disable no-eval */
data.lambda = eval('(' + data.lambda.js + ')');
/* eslint-enable no-eval */
}
it(name + ' - ' + test.name, function() {
if (test.partials) {
shouldCompileToWithPartials(
test.template,
[data, {}, test.partials, true],
true,
test.expected,
test.desc + ' "' + test.template + '"'
);
} else {
shouldCompileTo(
test.template,
[data, {}, {}, true],
test.expected,
test.desc + ' "' + test.template + '"'
);
}
});
});
});
});
handlebars.js-4.7.2/spec/strict.js 0000664 0000000 0000000 00000014000 13607154272 0017044 0 ustar 00root root 0000000 0000000 var Exception = Handlebars.Exception;
describe('strict', function() {
describe('strict mode', function() {
it('should error on missing property lookup', function() {
shouldThrow(
function() {
var template = CompilerContext.compile('{{hello}}', { strict: true });
template({});
},
Exception,
/"hello" not defined in/
);
});
it('should error on missing child', function() {
var template = CompilerContext.compile('{{hello.bar}}', { strict: true });
equals(template({ hello: { bar: 'foo' } }), 'foo');
shouldThrow(
function() {
template({ hello: {} });
},
Exception,
/"bar" not defined in/
);
});
it('should handle explicit undefined', function() {
var template = CompilerContext.compile('{{hello.bar}}', { strict: true });
equals(template({ hello: { bar: undefined } }), '');
});
it('should error on missing property lookup in known helpers mode', function() {
shouldThrow(
function() {
var template = CompilerContext.compile('{{hello}}', {
strict: true,
knownHelpersOnly: true
});
template({});
},
Exception,
/"hello" not defined in/
);
});
it('should error on missing context', function() {
shouldThrow(function() {
var template = CompilerContext.compile('{{hello}}', { strict: true });
template();
}, Error);
});
it('should error on missing data lookup', function() {
var template = CompilerContext.compile('{{@hello}}', { strict: true });
equals(template(undefined, { data: { hello: 'foo' } }), 'foo');
shouldThrow(function() {
template();
}, Error);
});
it('should not run helperMissing for helper calls', function() {
shouldThrow(
function() {
var template = CompilerContext.compile('{{hello foo}}', {
strict: true
});
template({ foo: true });
},
Exception,
/"hello" not defined in/
);
shouldThrow(
function() {
var template = CompilerContext.compile('{{#hello foo}}{{/hello}}', {
strict: true
});
template({ foo: true });
},
Exception,
/"hello" not defined in/
);
});
it('should throw on ambiguous blocks', function() {
shouldThrow(
function() {
var template = CompilerContext.compile('{{#hello}}{{/hello}}', {
strict: true
});
template({});
},
Exception,
/"hello" not defined in/
);
shouldThrow(
function() {
var template = CompilerContext.compile('{{^hello}}{{/hello}}', {
strict: true
});
template({});
},
Exception,
/"hello" not defined in/
);
shouldThrow(
function() {
var template = CompilerContext.compile(
'{{#hello.bar}}{{/hello.bar}}',
{ strict: true }
);
template({ hello: {} });
},
Exception,
/"bar" not defined in/
);
});
it('should allow undefined parameters when passed to helpers', function() {
var template = CompilerContext.compile(
'{{#unless foo}}success{{/unless}}',
{ strict: true }
);
equals(template({}), 'success');
});
it('should allow undefined hash when passed to helpers', function() {
var template = CompilerContext.compile('{{helper value=@foo}}', {
strict: true
});
var helpers = {
helper: function(options) {
equals('value' in options.hash, true);
equals(options.hash.value, undefined);
return 'success';
}
};
equals(template({}, { helpers: helpers }), 'success');
});
it('should show error location on missing property lookup', function() {
shouldThrow(
function() {
var template = CompilerContext.compile('\n\n\n {{hello}}', {
strict: true
});
template({});
},
Exception,
'"hello" not defined in [object Object] - 4:5'
);
});
it('should error contains correct location properties on missing property lookup', function() {
try {
var template = CompilerContext.compile('\n\n\n {{hello}}', {
strict: true
});
template({});
} catch (error) {
equals(error.lineNumber, 4);
equals(error.endLineNumber, 4);
equals(error.column, 5);
equals(error.endColumn, 10);
}
});
});
describe('assume objects', function() {
it('should ignore missing property', function() {
var template = CompilerContext.compile('{{hello}}', {
assumeObjects: true
});
equal(template({}), '');
});
it('should ignore missing child', function() {
var template = CompilerContext.compile('{{hello.bar}}', {
assumeObjects: true
});
equal(template({ hello: {} }), '');
});
it('should error on missing object', function() {
shouldThrow(function() {
var template = CompilerContext.compile('{{hello.bar}}', {
assumeObjects: true
});
template({});
}, Error);
});
it('should error on missing context', function() {
shouldThrow(function() {
var template = CompilerContext.compile('{{hello}}', {
assumeObjects: true
});
template();
}, Error);
});
it('should error on missing data lookup', function() {
shouldThrow(function() {
var template = CompilerContext.compile('{{@hello.bar}}', {
assumeObjects: true
});
template();
}, Error);
});
it('should execute blockHelperMissing', function() {
var template = CompilerContext.compile('{{^hello}}foo{{/hello}}', {
assumeObjects: true
});
equals(template({}), 'foo');
});
});
});
handlebars.js-4.7.2/spec/string-params.js 0000664 0000000 0000000 00000014760 13607154272 0020340 0 ustar 00root root 0000000 0000000 describe('string params mode', function() {
it('arguments to helpers can be retrieved from options hash in string form', function() {
var template = CompilerContext.compile('{{wycats is.a slave.driver}}', {
stringParams: true
});
var helpers = {
wycats: function(passiveVoice, noun) {
return 'HELP ME MY BOSS ' + passiveVoice + ' ' + noun;
}
};
var result = template({}, { helpers: helpers });
equals(
result,
'HELP ME MY BOSS is.a slave.driver',
'String parameters output'
);
});
it('when using block form, arguments to helpers can be retrieved from options hash in string form', function() {
var template = CompilerContext.compile(
'{{#wycats is.a slave.driver}}help :({{/wycats}}',
{ stringParams: true }
);
var helpers = {
wycats: function(passiveVoice, noun, options) {
return (
'HELP ME MY BOSS ' +
passiveVoice +
' ' +
noun +
': ' +
options.fn(this)
);
}
};
var result = template({}, { helpers: helpers });
equals(
result,
'HELP ME MY BOSS is.a slave.driver: help :(',
'String parameters output'
);
});
it('when inside a block in String mode, .. passes the appropriate context in the options hash', function() {
var template = CompilerContext.compile(
'{{#with dale}}{{tomdale ../need dad.joke}}{{/with}}',
{ stringParams: true }
);
var helpers = {
tomdale: function(desire, noun, options) {
return (
'STOP ME FROM READING HACKER NEWS I ' +
options.contexts[0][desire] +
' ' +
noun
);
},
with: function(context, options) {
return options.fn(options.contexts[0][context]);
}
};
var result = template(
{
dale: {},
need: 'need-a'
},
{ helpers: helpers }
);
equals(
result,
'STOP ME FROM READING HACKER NEWS I need-a dad.joke',
'Proper context variable output'
);
});
it('information about the types is passed along', function() {
var template = CompilerContext.compile(
"{{tomdale 'need' dad.joke true false}}",
{ stringParams: true }
);
var helpers = {
tomdale: function(desire, noun, trueBool, falseBool, options) {
equal(options.types[0], 'StringLiteral', 'the string type is passed');
equal(
options.types[1],
'PathExpression',
'the expression type is passed'
);
equal(
options.types[2],
'BooleanLiteral',
'the expression type is passed'
);
equal(desire, 'need', 'the string form is passed for strings');
equal(noun, 'dad.joke', 'the string form is passed for expressions');
equal(trueBool, true, 'raw booleans are passed through');
equal(falseBool, false, 'raw booleans are passed through');
return 'Helper called';
}
};
var result = template({}, { helpers: helpers });
equal(result, 'Helper called');
});
it('hash parameters get type information', function() {
var template = CompilerContext.compile(
"{{tomdale he.says desire='need' noun=dad.joke bool=true}}",
{ stringParams: true }
);
var helpers = {
tomdale: function(exclamation, options) {
equal(exclamation, 'he.says');
equal(options.types[0], 'PathExpression');
equal(options.hashTypes.desire, 'StringLiteral');
equal(options.hashTypes.noun, 'PathExpression');
equal(options.hashTypes.bool, 'BooleanLiteral');
equal(options.hash.desire, 'need');
equal(options.hash.noun, 'dad.joke');
equal(options.hash.bool, true);
return 'Helper called';
}
};
var result = template({}, { helpers: helpers });
equal(result, 'Helper called');
});
it('hash parameters get context information', function() {
var template = CompilerContext.compile(
"{{#with dale}}{{tomdale he.says desire='need' noun=../dad/joke bool=true}}{{/with}}",
{ stringParams: true }
);
var context = { dale: {} };
var helpers = {
tomdale: function(exclamation, options) {
equal(exclamation, 'he.says');
equal(options.types[0], 'PathExpression');
equal(options.contexts.length, 1);
equal(options.hashContexts.noun, context);
equal(options.hash.desire, 'need');
equal(options.hash.noun, 'dad.joke');
equal(options.hash.bool, true);
return 'Helper called';
},
with: function(withContext, options) {
return options.fn(options.contexts[0][withContext]);
}
};
var result = template(context, { helpers: helpers });
equal(result, 'Helper called');
});
it('when inside a block in String mode, .. passes the appropriate context in the options hash to a block helper', function() {
var template = CompilerContext.compile(
'{{#with dale}}{{#tomdale ../need dad.joke}}wot{{/tomdale}}{{/with}}',
{ stringParams: true }
);
var helpers = {
tomdale: function(desire, noun, options) {
return (
'STOP ME FROM READING HACKER NEWS I ' +
options.contexts[0][desire] +
' ' +
noun +
' ' +
options.fn(this)
);
},
with: function(context, options) {
return options.fn(options.contexts[0][context]);
}
};
var result = template(
{
dale: {},
need: 'need-a'
},
{ helpers: helpers }
);
equals(
result,
'STOP ME FROM READING HACKER NEWS I need-a dad.joke wot',
'Proper context variable output'
);
});
it('with nested block ambiguous', function() {
var template = CompilerContext.compile(
'{{#with content}}{{#view}}{{firstName}} {{lastName}}{{/view}}{{/with}}',
{ stringParams: true }
);
var helpers = {
with: function() {
return 'WITH';
},
view: function() {
return 'VIEW';
}
};
var result = template({}, { helpers: helpers });
equals(result, 'WITH');
});
it('should handle DATA', function() {
var template = CompilerContext.compile('{{foo @bar}}', {
stringParams: true
});
var helpers = {
foo: function(bar, options) {
equal(bar, '@bar');
equal(options.types[0], 'PathExpression');
return 'Foo!';
}
};
var result = template({}, { helpers: helpers });
equal(result, 'Foo!');
});
});
handlebars.js-4.7.2/spec/subexpressions.js 0000664 0000000 0000000 00000016571 13607154272 0020647 0 ustar 00root root 0000000 0000000 describe('subexpressions', function() {
it('arg-less helper', function() {
var string = '{{foo (bar)}}!';
var context = {};
var helpers = {
foo: function(val) {
return val + val;
},
bar: function() {
return 'LOL';
}
};
shouldCompileTo(string, [context, helpers], 'LOLLOL!');
});
it('helper w args', function() {
var string = '{{blog (equal a b)}}';
var context = { bar: 'LOL' };
var helpers = {
blog: function(val) {
return 'val is ' + val;
},
equal: function(x, y) {
return x === y;
}
};
shouldCompileTo(string, [context, helpers], 'val is true');
});
it('mixed paths and helpers', function() {
var string = '{{blog baz.bat (equal a b) baz.bar}}';
var context = { bar: 'LOL', baz: { bat: 'foo!', bar: 'bar!' } };
var helpers = {
blog: function(val, that, theOther) {
return 'val is ' + val + ', ' + that + ' and ' + theOther;
},
equal: function(x, y) {
return x === y;
}
};
shouldCompileTo(string, [context, helpers], 'val is foo!, true and bar!');
});
it('supports much nesting', function() {
var string = '{{blog (equal (equal true true) true)}}';
var context = { bar: 'LOL' };
var helpers = {
blog: function(val) {
return 'val is ' + val;
},
equal: function(x, y) {
return x === y;
}
};
shouldCompileTo(string, [context, helpers], 'val is true');
});
it('GH-800 : Complex subexpressions', function() {
var context = { a: 'a', b: 'b', c: { c: 'c' }, d: 'd', e: { e: 'e' } };
var helpers = {
dash: function(a, b) {
return a + '-' + b;
},
concat: function(a, b) {
return a + b;
}
};
shouldCompileTo(
"{{dash 'abc' (concat a b)}}",
[context, helpers],
'abc-ab'
);
shouldCompileTo('{{dash d (concat a b)}}', [context, helpers], 'd-ab');
shouldCompileTo('{{dash c.c (concat a b)}}', [context, helpers], 'c-ab');
shouldCompileTo('{{dash (concat a b) c.c}}', [context, helpers], 'ab-c');
shouldCompileTo('{{dash (concat a e.e) c.c}}', [context, helpers], 'ae-c');
});
it('provides each nested helper invocation its own options hash', function() {
var string = '{{equal (equal true true) true}}';
var lastOptions = null;
var helpers = {
equal: function(x, y, options) {
if (!options || options === lastOptions) {
throw new Error('options hash was reused');
}
lastOptions = options;
return x === y;
}
};
shouldCompileTo(string, [{}, helpers], 'true');
});
it('with hashes', function() {
var string = "{{blog (equal (equal true true) true fun='yes')}}";
var context = { bar: 'LOL' };
var helpers = {
blog: function(val) {
return 'val is ' + val;
},
equal: function(x, y) {
return x === y;
}
};
shouldCompileTo(string, [context, helpers], 'val is true');
});
it('as hashes', function() {
var string = "{{blog fun=(equal (blog fun=1) 'val is 1')}}";
var helpers = {
blog: function(options) {
return 'val is ' + options.hash.fun;
},
equal: function(x, y) {
return x === y;
}
};
shouldCompileTo(string, [{}, helpers], 'val is true');
});
it('multiple subexpressions in a hash', function() {
var string =
'{{input aria-label=(t "Name") placeholder=(t "Example User")}}';
var helpers = {
input: function(options) {
var hash = options.hash;
var ariaLabel = Handlebars.Utils.escapeExpression(hash['aria-label']);
var placeholder = Handlebars.Utils.escapeExpression(hash.placeholder);
return new Handlebars.SafeString(
''
);
},
t: function(defaultString) {
return new Handlebars.SafeString(defaultString);
}
};
shouldCompileTo(
string,
[{}, helpers],
''
);
});
it('multiple subexpressions in a hash with context', function() {
var string =
'{{input aria-label=(t item.field) placeholder=(t item.placeholder)}}';
var context = {
item: {
field: 'Name',
placeholder: 'Example User'
}
};
var helpers = {
input: function(options) {
var hash = options.hash;
var ariaLabel = Handlebars.Utils.escapeExpression(hash['aria-label']);
var placeholder = Handlebars.Utils.escapeExpression(hash.placeholder);
return new Handlebars.SafeString(
''
);
},
t: function(defaultString) {
return new Handlebars.SafeString(defaultString);
}
};
shouldCompileTo(
string,
[context, helpers],
''
);
});
it('in string params mode,', function() {
var template = CompilerContext.compile(
'{{snog (blorg foo x=y) yeah a=b}}',
{ stringParams: true }
);
var helpers = {
snog: function(a, b, options) {
equals(a, 'foo');
equals(
options.types.length,
2,
'string params for outer helper processed correctly'
);
equals(
options.types[0],
'SubExpression',
'string params for outer helper processed correctly'
);
equals(
options.types[1],
'PathExpression',
'string params for outer helper processed correctly'
);
return a + b;
},
blorg: function(a, options) {
equals(
options.types.length,
1,
'string params for inner helper processed correctly'
);
equals(
options.types[0],
'PathExpression',
'string params for inner helper processed correctly'
);
return a;
}
};
var result = template(
{
foo: {},
yeah: {}
},
{ helpers: helpers }
);
equals(result, 'fooyeah');
});
it('as hashes in string params mode', function() {
var template = CompilerContext.compile('{{blog fun=(bork)}}', {
stringParams: true
});
var helpers = {
blog: function(options) {
equals(options.hashTypes.fun, 'SubExpression');
return 'val is ' + options.hash.fun;
},
bork: function() {
return 'BORK';
}
};
var result = template({}, { helpers: helpers });
equals(result, 'val is BORK');
});
it('subexpression functions on the context', function() {
var string = '{{foo (bar)}}!';
var context = {
bar: function() {
return 'LOL';
}
};
var helpers = {
foo: function(val) {
return val + val;
}
};
shouldCompileTo(string, [context, helpers], 'LOLLOL!');
});
it("subexpressions can't just be property lookups", function() {
var string = '{{foo (bar)}}!';
var context = {
bar: 'LOL'
};
var helpers = {
foo: function(val) {
return val + val;
}
};
shouldThrow(function() {
shouldCompileTo(string, [context, helpers], 'LOLLOL!');
});
});
});
handlebars.js-4.7.2/spec/tokenizer.js 0000664 0000000 0000000 00000050102 13607154272 0017551 0 ustar 00root root 0000000 0000000 function shouldMatchTokens(result, tokens) {
for (var index = 0; index < result.length; index++) {
equals(result[index].name, tokens[index]);
}
}
function shouldBeToken(result, name, text) {
equals(result.name, name);
equals(result.text, text);
}
describe('Tokenizer', function() {
if (!Handlebars.Parser) {
return;
}
function tokenize(template) {
var parser = Handlebars.Parser,
lexer = parser.lexer;
lexer.setInput(template);
var out = [],
token;
while ((token = lexer.lex())) {
var result = parser.terminals_[token] || token;
if (!result || result === 'EOF' || result === 'INVALID') {
break;
}
out.push({ name: result, text: lexer.yytext });
}
return out;
}
it('tokenizes a simple mustache as "OPEN ID CLOSE"', function() {
var result = tokenize('{{foo}}');
shouldMatchTokens(result, ['OPEN', 'ID', 'CLOSE']);
shouldBeToken(result[1], 'ID', 'foo');
});
it('supports unescaping with &', function() {
var result = tokenize('{{&bar}}');
shouldMatchTokens(result, ['OPEN', 'ID', 'CLOSE']);
shouldBeToken(result[0], 'OPEN', '{{&');
shouldBeToken(result[1], 'ID', 'bar');
});
it('supports unescaping with {{{', function() {
var result = tokenize('{{{bar}}}');
shouldMatchTokens(result, ['OPEN_UNESCAPED', 'ID', 'CLOSE_UNESCAPED']);
shouldBeToken(result[1], 'ID', 'bar');
});
it('supports escaping delimiters', function() {
var result = tokenize('{{foo}} \\{{bar}} {{baz}}');
shouldMatchTokens(result, [
'OPEN',
'ID',
'CLOSE',
'CONTENT',
'CONTENT',
'OPEN',
'ID',
'CLOSE'
]);
shouldBeToken(result[3], 'CONTENT', ' ');
shouldBeToken(result[4], 'CONTENT', '{{bar}} ');
});
it('supports escaping multiple delimiters', function() {
var result = tokenize('{{foo}} \\{{bar}} \\{{baz}}');
shouldMatchTokens(result, [
'OPEN',
'ID',
'CLOSE',
'CONTENT',
'CONTENT',
'CONTENT'
]);
shouldBeToken(result[3], 'CONTENT', ' ');
shouldBeToken(result[4], 'CONTENT', '{{bar}} ');
shouldBeToken(result[5], 'CONTENT', '{{baz}}');
});
it('supports escaping a triple stash', function() {
var result = tokenize('{{foo}} \\{{{bar}}} {{baz}}');
shouldMatchTokens(result, [
'OPEN',
'ID',
'CLOSE',
'CONTENT',
'CONTENT',
'OPEN',
'ID',
'CLOSE'
]);
shouldBeToken(result[4], 'CONTENT', '{{{bar}}} ');
});
it('supports escaping escape character', function() {
var result = tokenize('{{foo}} \\\\{{bar}} {{baz}}');
shouldMatchTokens(result, [
'OPEN',
'ID',
'CLOSE',
'CONTENT',
'OPEN',
'ID',
'CLOSE',
'CONTENT',
'OPEN',
'ID',
'CLOSE'
]);
shouldBeToken(result[3], 'CONTENT', ' \\');
shouldBeToken(result[5], 'ID', 'bar');
});
it('supports escaping multiple escape characters', function() {
var result = tokenize('{{foo}} \\\\{{bar}} \\\\{{baz}}');
shouldMatchTokens(result, [
'OPEN',
'ID',
'CLOSE',
'CONTENT',
'OPEN',
'ID',
'CLOSE',
'CONTENT',
'OPEN',
'ID',
'CLOSE'
]);
shouldBeToken(result[3], 'CONTENT', ' \\');
shouldBeToken(result[5], 'ID', 'bar');
shouldBeToken(result[7], 'CONTENT', ' \\');
shouldBeToken(result[9], 'ID', 'baz');
});
it('supports escaped mustaches after escaped escape characters', function() {
var result = tokenize('{{foo}} \\\\{{bar}} \\{{baz}}');
shouldMatchTokens(result, [
'OPEN',
'ID',
'CLOSE',
'CONTENT',
'OPEN',
'ID',
'CLOSE',
'CONTENT',
'CONTENT',
'CONTENT'
]);
shouldBeToken(result[3], 'CONTENT', ' \\');
shouldBeToken(result[4], 'OPEN', '{{');
shouldBeToken(result[5], 'ID', 'bar');
shouldBeToken(result[7], 'CONTENT', ' ');
shouldBeToken(result[8], 'CONTENT', '{{baz}}');
});
it('supports escaped escape characters after escaped mustaches', function() {
var result = tokenize('{{foo}} \\{{bar}} \\\\{{baz}}');
shouldMatchTokens(result, [
'OPEN',
'ID',
'CLOSE',
'CONTENT',
'CONTENT',
'CONTENT',
'OPEN',
'ID',
'CLOSE'
]);
shouldBeToken(result[4], 'CONTENT', '{{bar}} ');
shouldBeToken(result[5], 'CONTENT', '\\');
shouldBeToken(result[6], 'OPEN', '{{');
shouldBeToken(result[7], 'ID', 'baz');
});
it('supports escaped escape character on a triple stash', function() {
var result = tokenize('{{foo}} \\\\{{{bar}}} {{baz}}');
shouldMatchTokens(result, [
'OPEN',
'ID',
'CLOSE',
'CONTENT',
'OPEN_UNESCAPED',
'ID',
'CLOSE_UNESCAPED',
'CONTENT',
'OPEN',
'ID',
'CLOSE'
]);
shouldBeToken(result[3], 'CONTENT', ' \\');
shouldBeToken(result[5], 'ID', 'bar');
});
it('tokenizes a simple path', function() {
var result = tokenize('{{foo/bar}}');
shouldMatchTokens(result, ['OPEN', 'ID', 'SEP', 'ID', 'CLOSE']);
});
it('allows dot notation', function() {
var result = tokenize('{{foo.bar}}');
shouldMatchTokens(result, ['OPEN', 'ID', 'SEP', 'ID', 'CLOSE']);
shouldMatchTokens(tokenize('{{foo.bar.baz}}'), [
'OPEN',
'ID',
'SEP',
'ID',
'SEP',
'ID',
'CLOSE'
]);
});
it('allows path literals with []', function() {
var result = tokenize('{{foo.[bar]}}');
shouldMatchTokens(result, ['OPEN', 'ID', 'SEP', 'ID', 'CLOSE']);
});
it('allows multiple path literals on a line with []', function() {
var result = tokenize('{{foo.[bar]}}{{foo.[baz]}}');
shouldMatchTokens(result, [
'OPEN',
'ID',
'SEP',
'ID',
'CLOSE',
'OPEN',
'ID',
'SEP',
'ID',
'CLOSE'
]);
});
it('allows escaped literals in []', function() {
var result = tokenize('{{foo.[bar\\]]}}');
shouldMatchTokens(result, ['OPEN', 'ID', 'SEP', 'ID', 'CLOSE']);
});
it('tokenizes {{.}} as OPEN ID CLOSE', function() {
var result = tokenize('{{.}}');
shouldMatchTokens(result, ['OPEN', 'ID', 'CLOSE']);
});
it('tokenizes a path as "OPEN (ID SEP)* ID CLOSE"', function() {
var result = tokenize('{{../foo/bar}}');
shouldMatchTokens(result, [
'OPEN',
'ID',
'SEP',
'ID',
'SEP',
'ID',
'CLOSE'
]);
shouldBeToken(result[1], 'ID', '..');
});
it('tokenizes a path with .. as a parent path', function() {
var result = tokenize('{{../foo.bar}}');
shouldMatchTokens(result, [
'OPEN',
'ID',
'SEP',
'ID',
'SEP',
'ID',
'CLOSE'
]);
shouldBeToken(result[1], 'ID', '..');
});
it('tokenizes a path with this/foo as OPEN ID SEP ID CLOSE', function() {
var result = tokenize('{{this/foo}}');
shouldMatchTokens(result, ['OPEN', 'ID', 'SEP', 'ID', 'CLOSE']);
shouldBeToken(result[1], 'ID', 'this');
shouldBeToken(result[3], 'ID', 'foo');
});
it('tokenizes a simple mustache with spaces as "OPEN ID CLOSE"', function() {
var result = tokenize('{{ foo }}');
shouldMatchTokens(result, ['OPEN', 'ID', 'CLOSE']);
shouldBeToken(result[1], 'ID', 'foo');
});
it('tokenizes a simple mustache with line breaks as "OPEN ID ID CLOSE"', function() {
var result = tokenize('{{ foo \n bar }}');
shouldMatchTokens(result, ['OPEN', 'ID', 'ID', 'CLOSE']);
shouldBeToken(result[1], 'ID', 'foo');
});
it('tokenizes raw content as "CONTENT"', function() {
var result = tokenize('foo {{ bar }} baz');
shouldMatchTokens(result, ['CONTENT', 'OPEN', 'ID', 'CLOSE', 'CONTENT']);
shouldBeToken(result[0], 'CONTENT', 'foo ');
shouldBeToken(result[4], 'CONTENT', ' baz');
});
it('tokenizes a partial as "OPEN_PARTIAL ID CLOSE"', function() {
var result = tokenize('{{> foo}}');
shouldMatchTokens(result, ['OPEN_PARTIAL', 'ID', 'CLOSE']);
});
it('tokenizes a partial with context as "OPEN_PARTIAL ID ID CLOSE"', function() {
var result = tokenize('{{> foo bar }}');
shouldMatchTokens(result, ['OPEN_PARTIAL', 'ID', 'ID', 'CLOSE']);
});
it('tokenizes a partial without spaces as "OPEN_PARTIAL ID CLOSE"', function() {
var result = tokenize('{{>foo}}');
shouldMatchTokens(result, ['OPEN_PARTIAL', 'ID', 'CLOSE']);
});
it('tokenizes a partial space at the }); as "OPEN_PARTIAL ID CLOSE"', function() {
var result = tokenize('{{>foo }}');
shouldMatchTokens(result, ['OPEN_PARTIAL', 'ID', 'CLOSE']);
});
it('tokenizes a partial space at the }); as "OPEN_PARTIAL ID CLOSE"', function() {
var result = tokenize('{{>foo/bar.baz }}');
shouldMatchTokens(result, [
'OPEN_PARTIAL',
'ID',
'SEP',
'ID',
'SEP',
'ID',
'CLOSE'
]);
});
it('tokenizes partial block declarations', function() {
var result = tokenize('{{#> foo}}');
shouldMatchTokens(result, ['OPEN_PARTIAL_BLOCK', 'ID', 'CLOSE']);
});
it('tokenizes a comment as "COMMENT"', function() {
var result = tokenize('foo {{! this is a comment }} bar {{ baz }}');
shouldMatchTokens(result, [
'CONTENT',
'COMMENT',
'CONTENT',
'OPEN',
'ID',
'CLOSE'
]);
shouldBeToken(result[1], 'COMMENT', '{{! this is a comment }}');
});
it('tokenizes a block comment as "COMMENT"', function() {
var result = tokenize('foo {{!-- this is a {{comment}} --}} bar {{ baz }}');
shouldMatchTokens(result, [
'CONTENT',
'COMMENT',
'CONTENT',
'OPEN',
'ID',
'CLOSE'
]);
shouldBeToken(result[1], 'COMMENT', '{{!-- this is a {{comment}} --}}');
});
it('tokenizes a block comment with whitespace as "COMMENT"', function() {
var result = tokenize(
'foo {{!-- this is a\n{{comment}}\n--}} bar {{ baz }}'
);
shouldMatchTokens(result, [
'CONTENT',
'COMMENT',
'CONTENT',
'OPEN',
'ID',
'CLOSE'
]);
shouldBeToken(result[1], 'COMMENT', '{{!-- this is a\n{{comment}}\n--}}');
});
it('tokenizes open and closing blocks as OPEN_BLOCK, ID, CLOSE ..., OPEN_ENDBLOCK ID CLOSE', function() {
var result = tokenize('{{#foo}}content{{/foo}}');
shouldMatchTokens(result, [
'OPEN_BLOCK',
'ID',
'CLOSE',
'CONTENT',
'OPEN_ENDBLOCK',
'ID',
'CLOSE'
]);
});
it('tokenizes directives', function() {
shouldMatchTokens(tokenize('{{#*foo}}content{{/foo}}'), [
'OPEN_BLOCK',
'ID',
'CLOSE',
'CONTENT',
'OPEN_ENDBLOCK',
'ID',
'CLOSE'
]);
shouldMatchTokens(tokenize('{{*foo}}'), ['OPEN', 'ID', 'CLOSE']);
});
it('tokenizes inverse sections as "INVERSE"', function() {
shouldMatchTokens(tokenize('{{^}}'), ['INVERSE']);
shouldMatchTokens(tokenize('{{else}}'), ['INVERSE']);
shouldMatchTokens(tokenize('{{ else }}'), ['INVERSE']);
});
it('tokenizes inverse sections with ID as "OPEN_INVERSE ID CLOSE"', function() {
var result = tokenize('{{^foo}}');
shouldMatchTokens(result, ['OPEN_INVERSE', 'ID', 'CLOSE']);
shouldBeToken(result[1], 'ID', 'foo');
});
it('tokenizes inverse sections with ID and spaces as "OPEN_INVERSE ID CLOSE"', function() {
var result = tokenize('{{^ foo }}');
shouldMatchTokens(result, ['OPEN_INVERSE', 'ID', 'CLOSE']);
shouldBeToken(result[1], 'ID', 'foo');
});
it('tokenizes mustaches with params as "OPEN ID ID ID CLOSE"', function() {
var result = tokenize('{{ foo bar baz }}');
shouldMatchTokens(result, ['OPEN', 'ID', 'ID', 'ID', 'CLOSE']);
shouldBeToken(result[1], 'ID', 'foo');
shouldBeToken(result[2], 'ID', 'bar');
shouldBeToken(result[3], 'ID', 'baz');
});
it('tokenizes mustaches with String params as "OPEN ID ID STRING CLOSE"', function() {
var result = tokenize('{{ foo bar "baz" }}');
shouldMatchTokens(result, ['OPEN', 'ID', 'ID', 'STRING', 'CLOSE']);
shouldBeToken(result[3], 'STRING', 'baz');
});
it('tokenizes mustaches with String params using single quotes as "OPEN ID ID STRING CLOSE"', function() {
var result = tokenize("{{ foo bar 'baz' }}");
shouldMatchTokens(result, ['OPEN', 'ID', 'ID', 'STRING', 'CLOSE']);
shouldBeToken(result[3], 'STRING', 'baz');
});
it('tokenizes String params with spaces inside as "STRING"', function() {
var result = tokenize('{{ foo bar "baz bat" }}');
shouldMatchTokens(result, ['OPEN', 'ID', 'ID', 'STRING', 'CLOSE']);
shouldBeToken(result[3], 'STRING', 'baz bat');
});
it('tokenizes String params with escapes quotes as STRING', function() {
var result = tokenize('{{ foo "bar\\"baz" }}');
shouldMatchTokens(result, ['OPEN', 'ID', 'STRING', 'CLOSE']);
shouldBeToken(result[2], 'STRING', 'bar"baz');
});
it('tokenizes String params using single quotes with escapes quotes as STRING', function() {
var result = tokenize("{{ foo 'bar\\'baz' }}");
shouldMatchTokens(result, ['OPEN', 'ID', 'STRING', 'CLOSE']);
shouldBeToken(result[2], 'STRING', "bar'baz");
});
it('tokenizes numbers', function() {
var result = tokenize('{{ foo 1 }}');
shouldMatchTokens(result, ['OPEN', 'ID', 'NUMBER', 'CLOSE']);
shouldBeToken(result[2], 'NUMBER', '1');
result = tokenize('{{ foo 1.1 }}');
shouldMatchTokens(result, ['OPEN', 'ID', 'NUMBER', 'CLOSE']);
shouldBeToken(result[2], 'NUMBER', '1.1');
result = tokenize('{{ foo -1 }}');
shouldMatchTokens(result, ['OPEN', 'ID', 'NUMBER', 'CLOSE']);
shouldBeToken(result[2], 'NUMBER', '-1');
result = tokenize('{{ foo -1.1 }}');
shouldMatchTokens(result, ['OPEN', 'ID', 'NUMBER', 'CLOSE']);
shouldBeToken(result[2], 'NUMBER', '-1.1');
});
it('tokenizes booleans', function() {
var result = tokenize('{{ foo true }}');
shouldMatchTokens(result, ['OPEN', 'ID', 'BOOLEAN', 'CLOSE']);
shouldBeToken(result[2], 'BOOLEAN', 'true');
result = tokenize('{{ foo false }}');
shouldMatchTokens(result, ['OPEN', 'ID', 'BOOLEAN', 'CLOSE']);
shouldBeToken(result[2], 'BOOLEAN', 'false');
});
it('tokenizes undefined and null', function() {
var result = tokenize('{{ foo undefined null }}');
shouldMatchTokens(result, ['OPEN', 'ID', 'UNDEFINED', 'NULL', 'CLOSE']);
shouldBeToken(result[2], 'UNDEFINED', 'undefined');
shouldBeToken(result[3], 'NULL', 'null');
});
it('tokenizes hash arguments', function() {
var result = tokenize('{{ foo bar=baz }}');
shouldMatchTokens(result, ['OPEN', 'ID', 'ID', 'EQUALS', 'ID', 'CLOSE']);
result = tokenize('{{ foo bar baz=bat }}');
shouldMatchTokens(result, [
'OPEN',
'ID',
'ID',
'ID',
'EQUALS',
'ID',
'CLOSE'
]);
result = tokenize('{{ foo bar baz=1 }}');
shouldMatchTokens(result, [
'OPEN',
'ID',
'ID',
'ID',
'EQUALS',
'NUMBER',
'CLOSE'
]);
result = tokenize('{{ foo bar baz=true }}');
shouldMatchTokens(result, [
'OPEN',
'ID',
'ID',
'ID',
'EQUALS',
'BOOLEAN',
'CLOSE'
]);
result = tokenize('{{ foo bar baz=false }}');
shouldMatchTokens(result, [
'OPEN',
'ID',
'ID',
'ID',
'EQUALS',
'BOOLEAN',
'CLOSE'
]);
result = tokenize('{{ foo bar\n baz=bat }}');
shouldMatchTokens(result, [
'OPEN',
'ID',
'ID',
'ID',
'EQUALS',
'ID',
'CLOSE'
]);
result = tokenize('{{ foo bar baz="bat" }}');
shouldMatchTokens(result, [
'OPEN',
'ID',
'ID',
'ID',
'EQUALS',
'STRING',
'CLOSE'
]);
result = tokenize('{{ foo bar baz="bat" bam=wot }}');
shouldMatchTokens(result, [
'OPEN',
'ID',
'ID',
'ID',
'EQUALS',
'STRING',
'ID',
'EQUALS',
'ID',
'CLOSE'
]);
result = tokenize('{{foo omg bar=baz bat="bam"}}');
shouldMatchTokens(result, [
'OPEN',
'ID',
'ID',
'ID',
'EQUALS',
'ID',
'ID',
'EQUALS',
'STRING',
'CLOSE'
]);
shouldBeToken(result[2], 'ID', 'omg');
});
it('tokenizes special @ identifiers', function() {
var result = tokenize('{{ @foo }}');
shouldMatchTokens(result, ['OPEN', 'DATA', 'ID', 'CLOSE']);
shouldBeToken(result[2], 'ID', 'foo');
result = tokenize('{{ foo @bar }}');
shouldMatchTokens(result, ['OPEN', 'ID', 'DATA', 'ID', 'CLOSE']);
shouldBeToken(result[3], 'ID', 'bar');
result = tokenize('{{ foo bar=@baz }}');
shouldMatchTokens(result, [
'OPEN',
'ID',
'ID',
'EQUALS',
'DATA',
'ID',
'CLOSE'
]);
shouldBeToken(result[5], 'ID', 'baz');
});
it('does not time out in a mustache with a single } followed by EOF', function() {
shouldMatchTokens(tokenize('{{foo}'), ['OPEN', 'ID']);
});
it('does not time out in a mustache when invalid ID characters are used', function() {
shouldMatchTokens(tokenize('{{foo & }}'), ['OPEN', 'ID']);
});
it('tokenizes subexpressions', function() {
var result = tokenize('{{foo (bar)}}');
shouldMatchTokens(result, [
'OPEN',
'ID',
'OPEN_SEXPR',
'ID',
'CLOSE_SEXPR',
'CLOSE'
]);
shouldBeToken(result[1], 'ID', 'foo');
shouldBeToken(result[3], 'ID', 'bar');
result = tokenize('{{foo (a-x b-y)}}');
shouldMatchTokens(result, [
'OPEN',
'ID',
'OPEN_SEXPR',
'ID',
'ID',
'CLOSE_SEXPR',
'CLOSE'
]);
shouldBeToken(result[1], 'ID', 'foo');
shouldBeToken(result[3], 'ID', 'a-x');
shouldBeToken(result[4], 'ID', 'b-y');
});
it('tokenizes nested subexpressions', function() {
var result = tokenize('{{foo (bar (lol rofl)) (baz)}}');
shouldMatchTokens(result, [
'OPEN',
'ID',
'OPEN_SEXPR',
'ID',
'OPEN_SEXPR',
'ID',
'ID',
'CLOSE_SEXPR',
'CLOSE_SEXPR',
'OPEN_SEXPR',
'ID',
'CLOSE_SEXPR',
'CLOSE'
]);
shouldBeToken(result[3], 'ID', 'bar');
shouldBeToken(result[5], 'ID', 'lol');
shouldBeToken(result[6], 'ID', 'rofl');
shouldBeToken(result[10], 'ID', 'baz');
});
it('tokenizes nested subexpressions: literals', function() {
var result = tokenize(
'{{foo (bar (lol true) false) (baz 1) (blah \'b\') (blorg "c")}}'
);
shouldMatchTokens(result, [
'OPEN',
'ID',
'OPEN_SEXPR',
'ID',
'OPEN_SEXPR',
'ID',
'BOOLEAN',
'CLOSE_SEXPR',
'BOOLEAN',
'CLOSE_SEXPR',
'OPEN_SEXPR',
'ID',
'NUMBER',
'CLOSE_SEXPR',
'OPEN_SEXPR',
'ID',
'STRING',
'CLOSE_SEXPR',
'OPEN_SEXPR',
'ID',
'STRING',
'CLOSE_SEXPR',
'CLOSE'
]);
});
it('tokenizes block params', function() {
var result = tokenize('{{#foo as |bar|}}');
shouldMatchTokens(result, [
'OPEN_BLOCK',
'ID',
'OPEN_BLOCK_PARAMS',
'ID',
'CLOSE_BLOCK_PARAMS',
'CLOSE'
]);
result = tokenize('{{#foo as |bar baz|}}');
shouldMatchTokens(result, [
'OPEN_BLOCK',
'ID',
'OPEN_BLOCK_PARAMS',
'ID',
'ID',
'CLOSE_BLOCK_PARAMS',
'CLOSE'
]);
result = tokenize('{{#foo as | bar baz |}}');
shouldMatchTokens(result, [
'OPEN_BLOCK',
'ID',
'OPEN_BLOCK_PARAMS',
'ID',
'ID',
'CLOSE_BLOCK_PARAMS',
'CLOSE'
]);
result = tokenize('{{#foo as as | bar baz |}}');
shouldMatchTokens(result, [
'OPEN_BLOCK',
'ID',
'ID',
'OPEN_BLOCK_PARAMS',
'ID',
'ID',
'CLOSE_BLOCK_PARAMS',
'CLOSE'
]);
result = tokenize('{{else foo as |bar baz|}}');
shouldMatchTokens(result, [
'OPEN_INVERSE_CHAIN',
'ID',
'OPEN_BLOCK_PARAMS',
'ID',
'ID',
'CLOSE_BLOCK_PARAMS',
'CLOSE'
]);
});
it('tokenizes raw blocks', function() {
var result = tokenize(
'{{{{a}}}} abc {{{{/a}}}} aaa {{{{a}}}} abc {{{{/a}}}}'
);
shouldMatchTokens(result, [
'OPEN_RAW_BLOCK',
'ID',
'CLOSE_RAW_BLOCK',
'CONTENT',
'END_RAW_BLOCK',
'CONTENT',
'OPEN_RAW_BLOCK',
'ID',
'CLOSE_RAW_BLOCK',
'CONTENT',
'END_RAW_BLOCK'
]);
});
});
handlebars.js-4.7.2/spec/track-ids.js 0000664 0000000 0000000 00000026124 13607154272 0017427 0 ustar 00root root 0000000 0000000 describe('track ids', function() {
var context;
beforeEach(function() {
context = { is: { a: 'foo' }, slave: { driver: 'bar' } };
});
it('should not include anything without the flag', function() {
var template = CompilerContext.compile('{{wycats is.a slave.driver}}');
var helpers = {
wycats: function(passiveVoice, noun, options) {
equal(options.ids, undefined);
equal(options.hashIds, undefined);
return 'success';
}
};
equals(template({}, { helpers: helpers }), 'success');
});
it('should include argument ids', function() {
var template = CompilerContext.compile('{{wycats is.a slave.driver}}', {
trackIds: true
});
var helpers = {
wycats: function(passiveVoice, noun, options) {
equal(options.ids[0], 'is.a');
equal(options.ids[1], 'slave.driver');
return (
'HELP ME MY BOSS ' +
options.ids[0] +
':' +
passiveVoice +
' ' +
options.ids[1] +
':' +
noun
);
}
};
equals(
template(context, { helpers: helpers }),
'HELP ME MY BOSS is.a:foo slave.driver:bar'
);
});
it('should include hash ids', function() {
var template = CompilerContext.compile(
'{{wycats bat=is.a baz=slave.driver}}',
{ trackIds: true }
);
var helpers = {
wycats: function(options) {
equal(options.hashIds.bat, 'is.a');
equal(options.hashIds.baz, 'slave.driver');
return (
'HELP ME MY BOSS ' +
options.hashIds.bat +
':' +
options.hash.bat +
' ' +
options.hashIds.baz +
':' +
options.hash.baz
);
}
};
equals(
template(context, { helpers: helpers }),
'HELP ME MY BOSS is.a:foo slave.driver:bar'
);
});
it('should note ../ and ./ references', function() {
var template = CompilerContext.compile(
'{{wycats ./is.a ../slave.driver this.is.a this}}',
{ trackIds: true }
);
var helpers = {
wycats: function(passiveVoice, noun, thiz, thiz2, options) {
equal(options.ids[0], 'is.a');
equal(options.ids[1], '../slave.driver');
equal(options.ids[2], 'is.a');
equal(options.ids[3], '');
return (
'HELP ME MY BOSS ' +
options.ids[0] +
':' +
passiveVoice +
' ' +
options.ids[1] +
':' +
noun
);
}
};
equals(
template(context, { helpers: helpers }),
'HELP ME MY BOSS is.a:foo ../slave.driver:undefined'
);
});
it('should note @data references', function() {
var template = CompilerContext.compile('{{wycats @is.a @slave.driver}}', {
trackIds: true
});
var helpers = {
wycats: function(passiveVoice, noun, options) {
equal(options.ids[0], '@is.a');
equal(options.ids[1], '@slave.driver');
return (
'HELP ME MY BOSS ' +
options.ids[0] +
':' +
passiveVoice +
' ' +
options.ids[1] +
':' +
noun
);
}
};
equals(
template({}, { helpers: helpers, data: context }),
'HELP ME MY BOSS @is.a:foo @slave.driver:bar'
);
});
it('should return null for constants', function() {
var template = CompilerContext.compile('{{wycats 1 "foo" key=false}}', {
trackIds: true
});
var helpers = {
wycats: function(passiveVoice, noun, options) {
equal(options.ids[0], null);
equal(options.ids[1], null);
equal(options.hashIds.key, null);
return (
'HELP ME MY BOSS ' +
passiveVoice +
' ' +
noun +
' ' +
options.hash.key
);
}
};
equals(
template(context, { helpers: helpers }),
'HELP ME MY BOSS 1 foo false'
);
});
it('should return true for subexpressions', function() {
var template = CompilerContext.compile('{{wycats (sub)}}', {
trackIds: true
});
var helpers = {
sub: function() {
return 1;
},
wycats: function(passiveVoice, options) {
equal(options.ids[0], true);
return 'HELP ME MY BOSS ' + passiveVoice;
}
};
equals(template(context, { helpers: helpers }), 'HELP ME MY BOSS 1');
});
it('should use block param paths', function() {
var template = CompilerContext.compile(
'{{#doIt as |is|}}{{wycats is.a slave.driver is}}{{/doIt}}',
{ trackIds: true }
);
var helpers = {
doIt: function(options) {
var blockParams = [this.is];
blockParams.path = ['zomg'];
return options.fn(this, { blockParams: blockParams });
},
wycats: function(passiveVoice, noun, blah, options) {
equal(options.ids[0], 'zomg.a');
equal(options.ids[1], 'slave.driver');
equal(options.ids[2], 'zomg');
return (
'HELP ME MY BOSS ' +
options.ids[0] +
':' +
passiveVoice +
' ' +
options.ids[1] +
':' +
noun
);
}
};
equals(
template(context, { helpers: helpers }),
'HELP ME MY BOSS zomg.a:foo slave.driver:bar'
);
});
describe('builtin helpers', function() {
var helpers = {
blockParams: function(name, options) {
return name + ':' + options.ids[0] + '\n';
},
wycats: function(name, options) {
return name + ':' + options.data.contextPath + '\n';
}
};
describe('#each', function() {
it('should track contextPath for arrays', function() {
var template = CompilerContext.compile(
'{{#each array}}{{wycats name}}{{/each}}',
{ trackIds: true }
);
equals(
template(
{ array: [{ name: 'foo' }, { name: 'bar' }] },
{ helpers: helpers }
),
'foo:array.0\nbar:array.1\n'
);
});
it('should track contextPath for keys', function() {
var template = CompilerContext.compile(
'{{#each object}}{{wycats name}}{{/each}}',
{ trackIds: true }
);
equals(
template(
{ object: { foo: { name: 'foo' }, bar: { name: 'bar' } } },
{ helpers: helpers }
),
'foo:object.foo\nbar:object.bar\n'
);
});
it('should handle nesting', function() {
var template = CompilerContext.compile(
'{{#each .}}{{#each .}}{{wycats name}}{{/each}}{{/each}}',
{ trackIds: true }
);
equals(
template(
{ array: [{ name: 'foo' }, { name: 'bar' }] },
{ helpers: helpers }
),
'foo:.array..0\nbar:.array..1\n'
);
});
it('should handle block params', function() {
var template = CompilerContext.compile(
'{{#each array as |value|}}{{blockParams value.name}}{{/each}}',
{ trackIds: true }
);
equals(
template(
{ array: [{ name: 'foo' }, { name: 'bar' }] },
{ helpers: helpers }
),
'foo:array.0.name\nbar:array.1.name\n'
);
});
});
describe('#with', function() {
it('should track contextPath', function() {
var template = CompilerContext.compile(
'{{#with field}}{{wycats name}}{{/with}}',
{ trackIds: true }
);
equals(
template({ field: { name: 'foo' } }, { helpers: helpers }),
'foo:field\n'
);
});
it('should handle nesting', function() {
var template = CompilerContext.compile(
'{{#with bat}}{{#with field}}{{wycats name}}{{/with}}{{/with}}',
{ trackIds: true }
);
equals(
template({ bat: { field: { name: 'foo' } } }, { helpers: helpers }),
'foo:bat.field\n'
);
});
});
describe('#blockHelperMissing', function() {
it('should track contextPath for arrays', function() {
var template = CompilerContext.compile(
'{{#field}}{{wycats name}}{{/field}}',
{ trackIds: true }
);
equals(
template({ field: [{ name: 'foo' }] }, { helpers: helpers }),
'foo:field.0\n'
);
});
it('should track contextPath for keys', function() {
var template = CompilerContext.compile(
'{{#field}}{{wycats name}}{{/field}}',
{ trackIds: true }
);
equals(
template({ field: { name: 'foo' } }, { helpers: helpers }),
'foo:field\n'
);
});
it('should handle nesting', function() {
var template = CompilerContext.compile(
'{{#bat}}{{#field}}{{wycats name}}{{/field}}{{/bat}}',
{ trackIds: true }
);
equals(
template({ bat: { field: { name: 'foo' } } }, { helpers: helpers }),
'foo:bat.field\n'
);
});
});
});
describe('partials', function() {
var helpers = {
blockParams: function(name, options) {
return name + ':' + options.ids[0] + '\n';
},
wycats: function(name, options) {
return name + ':' + options.data.contextPath + '\n';
}
};
it('should pass track id for basic partial', function() {
var template = CompilerContext.compile(
'Dudes: {{#dudes}}{{> dude}}{{/dudes}}',
{ trackIds: true }
),
hash = {
dudes: [
{ name: 'Yehuda', url: 'http://yehuda' },
{ name: 'Alan', url: 'http://alan' }
]
};
var partials = {
dude: CompilerContext.compile('{{wycats name}}', { trackIds: true })
};
equals(
template(hash, { helpers: helpers, partials: partials }),
'Dudes: Yehuda:dudes.0\nAlan:dudes.1\n'
);
});
it('should pass track id for context partial', function() {
var template = CompilerContext.compile('Dudes: {{> dude dudes}}', {
trackIds: true
}),
hash = {
dudes: [
{ name: 'Yehuda', url: 'http://yehuda' },
{ name: 'Alan', url: 'http://alan' }
]
};
var partials = {
dude: CompilerContext.compile(
'{{#each this}}{{wycats name}}{{/each}}',
{ trackIds: true }
)
};
equals(
template(hash, { helpers: helpers, partials: partials }),
'Dudes: Yehuda:dudes..0\nAlan:dudes..1\n'
);
});
it('should invalidate context for partials with parameters', function() {
var template = CompilerContext.compile(
'Dudes: {{#dudes}}{{> dude . bar="foo"}}{{/dudes}}',
{ trackIds: true }
),
hash = {
dudes: [
{ name: 'Yehuda', url: 'http://yehuda' },
{ name: 'Alan', url: 'http://alan' }
]
};
var partials = {
dude: CompilerContext.compile('{{wycats name}}', { trackIds: true })
};
equals(
template(hash, { helpers: helpers, partials: partials }),
'Dudes: Yehuda:true\nAlan:true\n'
);
});
});
});
handlebars.js-4.7.2/spec/umd-runtime.html 0000664 0000000 0000000 00000005255 13607154272 0020346 0 ustar 00root root 0000000 0000000
Mocha
handlebars.js-4.7.2/spec/umd.html 0000664 0000000 0000000 00000006327 13607154272 0016666 0 ustar 00root root 0000000 0000000
Mocha
handlebars.js-4.7.2/spec/utils.js 0000664 0000000 0000000 00000005457 13607154272 0016714 0 ustar 00root root 0000000 0000000 describe('utils', function() {
describe('#SafeString', function() {
it('constructing a safestring from a string and checking its type', function() {
var safe = new Handlebars.SafeString('testing 1, 2, 3');
if (!(safe instanceof Handlebars.SafeString)) {
throw new Error('Must be instance of SafeString');
}
equals(
safe.toString(),
'testing 1, 2, 3',
'SafeString is equivalent to its underlying string'
);
});
it('it should not escape SafeString properties', function() {
var name = new Handlebars.SafeString('Sean O'Malley');
shouldCompileTo(
'{{name}}',
[{ name: name }],
'Sean O'Malley'
);
});
});
describe('#escapeExpression', function() {
it('shouhld escape html', function() {
equals(
Handlebars.Utils.escapeExpression('foo<&"\'>'),
'foo<&"'>'
);
equals(Handlebars.Utils.escapeExpression('foo='), 'foo=');
});
it('should not escape SafeString', function() {
var string = new Handlebars.SafeString('foo<&"\'>');
equals(Handlebars.Utils.escapeExpression(string), 'foo<&"\'>');
var obj = {
toHTML: function() {
return 'foo<&"\'>';
}
};
equals(Handlebars.Utils.escapeExpression(obj), 'foo<&"\'>');
});
it('should handle falsy', function() {
equals(Handlebars.Utils.escapeExpression(''), '');
equals(Handlebars.Utils.escapeExpression(undefined), '');
equals(Handlebars.Utils.escapeExpression(null), '');
equals(Handlebars.Utils.escapeExpression(false), 'false');
equals(Handlebars.Utils.escapeExpression(0), '0');
});
it('should handle empty objects', function() {
equals(Handlebars.Utils.escapeExpression({}), {}.toString());
equals(Handlebars.Utils.escapeExpression([]), [].toString());
});
});
describe('#isEmpty', function() {
it('should not be empty', function() {
equals(Handlebars.Utils.isEmpty(undefined), true);
equals(Handlebars.Utils.isEmpty(null), true);
equals(Handlebars.Utils.isEmpty(false), true);
equals(Handlebars.Utils.isEmpty(''), true);
equals(Handlebars.Utils.isEmpty([]), true);
});
it('should be empty', function() {
equals(Handlebars.Utils.isEmpty(0), false);
equals(Handlebars.Utils.isEmpty([1]), false);
equals(Handlebars.Utils.isEmpty('foo'), false);
equals(Handlebars.Utils.isEmpty({ bar: 1 }), false);
});
});
describe('#extend', function() {
it('should ignore prototype values', function() {
function A() {
this.a = 1;
}
A.prototype.b = 4;
var b = { b: 2 };
Handlebars.Utils.extend(b, new A());
equals(b.a, 1);
equals(b.b, 2);
});
});
});
handlebars.js-4.7.2/spec/vendor/ 0000775 0000000 0000000 00000000000 13607154272 0016500 5 ustar 00root root 0000000 0000000 handlebars.js-4.7.2/spec/vendor/json2.js 0000664 0000000 0000000 00000042241 13607154272 0020074 0 ustar 00root root 0000000 0000000 /*
json2.js
2014-02-04
Public Domain.
NO WARRANTY EXPRESSED OR IMPLIED. USE AT YOUR OWN RISK.
See http://www.JSON.org/js.html
This code should be minified before deployment.
See http://javascript.crockford.com/jsmin.html
USE YOUR OWN COPY. IT IS EXTREMELY UNWISE TO LOAD CODE FROM SERVERS YOU DO
NOT CONTROL.
This file creates a global JSON object containing two methods: stringify
and parse.
JSON.stringify(value, replacer, space)
value any JavaScript value, usually an object or array.
replacer an optional parameter that determines how object
values are stringified for objects. It can be a
function or an array of strings.
space an optional parameter that specifies the indentation
of nested structures. If it is omitted, the text will
be packed without extra whitespace. If it is a number,
it will specify the number of spaces to indent at each
level. If it is a string (such as '\t' or ' '),
it contains the characters used to indent at each level.
This method produces a JSON text from a JavaScript value.
When an object value is found, if the object contains a toJSON
method, its toJSON method will be called and the result will be
stringified. A toJSON method does not serialize: it returns the
value represented by the name/value pair that should be serialized,
or undefined if nothing should be serialized. The toJSON method
will be passed the key associated with the value, and this will be
bound to the value
For example, this would serialize Dates as ISO strings.
Date.prototype.toJSON = function (key) {
function f(n) {
// Format integers to have at least two digits.
return n < 10 ? '0' + n : n;
}
return this.getUTCFullYear() + '-' +
f(this.getUTCMonth() + 1) + '-' +
f(this.getUTCDate()) + 'T' +
f(this.getUTCHours()) + ':' +
f(this.getUTCMinutes()) + ':' +
f(this.getUTCSeconds()) + 'Z';
};
You can provide an optional replacer method. It will be passed the
key and value of each member, with this bound to the containing
object. The value that is returned from your method will be
serialized. If your method returns undefined, then the member will
be excluded from the serialization.
If the replacer parameter is an array of strings, then it will be
used to select the members to be serialized. It filters the results
such that only members with keys listed in the replacer array are
stringified.
Values that do not have JSON representations, such as undefined or
functions, will not be serialized. Such values in objects will be
dropped; in arrays they will be replaced with null. You can use
a replacer function to replace those with JSON values.
JSON.stringify(undefined) returns undefined.
The optional space parameter produces a stringification of the
value that is filled with line breaks and indentation to make it
easier to read.
If the space parameter is a non-empty string, then that string will
be used for indentation. If the space parameter is a number, then
the indentation will be that many spaces.
Example:
text = JSON.stringify(['e', {pluribus: 'unum'}]);
// text is '["e",{"pluribus":"unum"}]'
text = JSON.stringify(['e', {pluribus: 'unum'}], null, '\t');
// text is '[\n\t"e",\n\t{\n\t\t"pluribus": "unum"\n\t}\n]'
text = JSON.stringify([new Date()], function (key, value) {
return this[key] instanceof Date ?
'Date(' + this[key] + ')' : value;
});
// text is '["Date(---current time---)"]'
JSON.parse(text, reviver)
This method parses a JSON text to produce an object or array.
It can throw a SyntaxError exception.
The optional reviver parameter is a function that can filter and
transform the results. It receives each of the keys and values,
and its return value is used instead of the original value.
If it returns what it received, then the structure is not modified.
If it returns undefined then the member is deleted.
Example:
// Parse the text. Values that look like ISO date strings will
// be converted to Date objects.
myData = JSON.parse(text, function (key, value) {
var a;
if (typeof value === 'string') {
a =
/^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2}(?:\.\d*)?)Z$/.exec(value);
if (a) {
return new Date(Date.UTC(+a[1], +a[2] - 1, +a[3], +a[4],
+a[5], +a[6]));
}
}
return value;
});
myData = JSON.parse('["Date(09/09/2001)"]', function (key, value) {
var d;
if (typeof value === 'string' &&
value.slice(0, 5) === 'Date(' &&
value.slice(-1) === ')') {
d = new Date(value.slice(5, -1));
if (d) {
return d;
}
}
return value;
});
This is a reference implementation. You are free to copy, modify, or
redistribute.
*/
/*jslint evil: true, regexp: true */
/*members "", "\b", "\t", "\n", "\f", "\r", "\"", JSON, "\\", apply,
call, charCodeAt, getUTCDate, getUTCFullYear, getUTCHours,
getUTCMinutes, getUTCMonth, getUTCSeconds, hasOwnProperty, join,
lastIndex, length, parse, prototype, push, replace, slice, stringify,
test, toJSON, toString, valueOf
*/
// Create a JSON object only if one does not already exist. We create the
// methods in a closure to avoid creating global variables.
if (typeof JSON !== 'object') {
JSON = {};
}
(function () {
'use strict';
function f(n) {
// Format integers to have at least two digits.
return n < 10 ? '0' + n : n;
}
if (typeof Date.prototype.toJSON !== 'function') {
Date.prototype.toJSON = function () {
return isFinite(this.valueOf())
? this.getUTCFullYear() + '-' +
f(this.getUTCMonth() + 1) + '-' +
f(this.getUTCDate()) + 'T' +
f(this.getUTCHours()) + ':' +
f(this.getUTCMinutes()) + ':' +
f(this.getUTCSeconds()) + 'Z'
: null;
};
String.prototype.toJSON =
Number.prototype.toJSON =
Boolean.prototype.toJSON = function () {
return this.valueOf();
};
}
var cx,
escapable,
gap,
indent,
meta,
rep;
function quote(string) {
// If the string contains no control characters, no quote characters, and no
// backslash characters, then we can safely slap some quotes around it.
// Otherwise we must also replace the offending characters with safe escape
// sequences.
escapable.lastIndex = 0;
return escapable.test(string) ? '"' + string.replace(escapable, function (a) {
var c = meta[a];
return typeof c === 'string'
? c
: '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4);
}) + '"' : '"' + string + '"';
}
function str(key, holder) {
// Produce a string from holder[key].
var i, // The loop counter.
k, // The member key.
v, // The member value.
length,
mind = gap,
partial,
value = holder[key];
// If the value has a toJSON method, call it to obtain a replacement value.
if (value && typeof value === 'object' &&
typeof value.toJSON === 'function') {
value = value.toJSON(key);
}
// If we were called with a replacer function, then call the replacer to
// obtain a replacement value.
if (typeof rep === 'function') {
value = rep.call(holder, key, value);
}
// What happens next depends on the value's type.
switch (typeof value) {
case 'string':
return quote(value);
case 'number':
// JSON numbers must be finite. Encode non-finite numbers as null.
return isFinite(value) ? String(value) : 'null';
case 'boolean':
case 'null':
// If the value is a boolean or null, convert it to a string. Note:
// typeof null does not produce 'null'. The case is included here in
// the remote chance that this gets fixed someday.
return String(value);
// If the type is 'object', we might be dealing with an object or an array or
// null.
case 'object':
// Due to a specification blunder in ECMAScript, typeof null is 'object',
// so watch out for that case.
if (!value) {
return 'null';
}
// Make an array to hold the partial results of stringifying this object value.
gap += indent;
partial = [];
// Is the value an array?
if (Object.prototype.toString.apply(value) === '[object Array]') {
// The value is an array. Stringify every element. Use null as a placeholder
// for non-JSON values.
length = value.length;
for (i = 0; i < length; i += 1) {
partial[i] = str(i, value) || 'null';
}
// Join all of the elements together, separated with commas, and wrap them in
// brackets.
v = partial.length === 0
? '[]'
: gap
? '[\n' + gap + partial.join(',\n' + gap) + '\n' + mind + ']'
: '[' + partial.join(',') + ']';
gap = mind;
return v;
}
// If the replacer is an array, use it to select the members to be stringified.
if (rep && typeof rep === 'object') {
length = rep.length;
for (i = 0; i < length; i += 1) {
if (typeof rep[i] === 'string') {
k = rep[i];
v = str(k, value);
if (v) {
partial.push(quote(k) + (gap ? ': ' : ':') + v);
}
}
}
} else {
// Otherwise, iterate through all of the keys in the object.
for (k in value) {
if (Object.prototype.hasOwnProperty.call(value, k)) {
v = str(k, value);
if (v) {
partial.push(quote(k) + (gap ? ': ' : ':') + v);
}
}
}
}
// Join all of the member texts together, separated with commas,
// and wrap them in braces.
v = partial.length === 0
? '{}'
: gap
? '{\n' + gap + partial.join(',\n' + gap) + '\n' + mind + '}'
: '{' + partial.join(',') + '}';
gap = mind;
return v;
}
}
// If the JSON object does not yet have a stringify method, give it one.
if (typeof JSON.stringify !== 'function') {
escapable = /[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g;
meta = { // table of character substitutions
'\b': '\\b',
'\t': '\\t',
'\n': '\\n',
'\f': '\\f',
'\r': '\\r',
'"' : '\\"',
'\\': '\\\\'
};
JSON.stringify = function (value, replacer, space) {
// The stringify method takes a value and an optional replacer, and an optional
// space parameter, and returns a JSON text. The replacer can be a function
// that can replace values, or an array of strings that will select the keys.
// A default replacer method can be provided. Use of the space parameter can
// produce text that is more easily readable.
var i;
gap = '';
indent = '';
// If the space parameter is a number, make an indent string containing that
// many spaces.
if (typeof space === 'number') {
for (i = 0; i < space; i += 1) {
indent += ' ';
}
// If the space parameter is a string, it will be used as the indent string.
} else if (typeof space === 'string') {
indent = space;
}
// If there is a replacer, it must be a function or an array.
// Otherwise, throw an error.
rep = replacer;
if (replacer && typeof replacer !== 'function' &&
(typeof replacer !== 'object' ||
typeof replacer.length !== 'number')) {
throw new Error('JSON.stringify');
}
// Make a fake root object containing our value under the key of ''.
// Return the result of stringifying the value.
return str('', {'': value});
};
}
// If the JSON object does not yet have a parse method, give it one.
if (typeof JSON.parse !== 'function') {
cx = /[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g;
JSON.parse = function (text, reviver) {
// The parse method takes a text and an optional reviver function, and returns
// a JavaScript value if the text is a valid JSON text.
var j;
function walk(holder, key) {
// The walk method is used to recursively walk the resulting structure so
// that modifications can be made.
var k, v, value = holder[key];
if (value && typeof value === 'object') {
for (k in value) {
if (Object.prototype.hasOwnProperty.call(value, k)) {
v = walk(value, k);
if (v !== undefined) {
value[k] = v;
} else {
delete value[k];
}
}
}
}
return reviver.call(holder, key, value);
}
// Parsing happens in four stages. In the first stage, we replace certain
// Unicode characters with escape sequences. JavaScript handles many characters
// incorrectly, either silently deleting them, or treating them as line endings.
text = String(text);
cx.lastIndex = 0;
if (cx.test(text)) {
text = text.replace(cx, function (a) {
return '\\u' +
('0000' + a.charCodeAt(0).toString(16)).slice(-4);
});
}
// In the second stage, we run the text against regular expressions that look
// for non-JSON patterns. We are especially concerned with '()' and 'new'
// because they can cause invocation, and '=' because it can cause mutation.
// But just to be safe, we want to reject all unexpected forms.
// We split the second stage into 4 regexp operations in order to work around
// crippling inefficiencies in IE's and Safari's regexp engines. First we
// replace the JSON backslash pairs with '@' (a non-JSON character). Second, we
// replace all simple value tokens with ']' characters. Third, we delete all
// open brackets that follow a colon or comma or that begin the text. Finally,
// we look to see that the remaining characters are only whitespace or ']' or
// ',' or ':' or '{' or '}'. If that is so, then the text is safe for eval.
if (/^[\],:{}\s]*$/
.test(text.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, '@')
.replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']')
.replace(/(?:^|:|,)(?:\s*\[)+/g, ''))) {
// In the third stage we use the eval function to compile the text into a
// JavaScript structure. The '{' operator is subject to a syntactic ambiguity
// in JavaScript: it can begin a block or an object literal. We wrap the text
// in parens to eliminate the ambiguity.
j = eval('(' + text + ')');
// In the optional fourth stage, we recursively walk the new structure, passing
// each name/value pair to a reviver function for possible transformation.
return typeof reviver === 'function'
? walk({'': j}, '')
: j;
}
// If the text is not JSON parseable, then a SyntaxError is thrown.
throw new SyntaxError('JSON.parse');
};
}
}());
handlebars.js-4.7.2/spec/vendor/require.js 0000664 0000000 0000000 00000241377 13607154272 0020530 0 ustar 00root root 0000000 0000000 /** vim: et:ts=4:sw=4:sts=4
* @license RequireJS 2.1.9 Copyright (c) 2010-2012, The Dojo Foundation All Rights Reserved.
* Available via the MIT or new BSD license.
* see: http://github.com/jrburke/requirejs for details
*/
//Not using strict: uneven strict support in browsers, #392, and causes
//problems with requirejs.exec()/transpiler plugins that may not be strict.
/*jslint regexp: true, nomen: true, sloppy: true */
/*global window, navigator, document, importScripts, setTimeout, opera */
var requirejs, require, define;
(function (global) {
var req, s, head, baseElement, dataMain, src,
interactiveScript, currentlyAddingScript, mainScript, subPath,
version = '2.1.9',
commentRegExp = /(\/\*([\s\S]*?)\*\/|([^:]|^)\/\/(.*)$)/mg,
cjsRequireRegExp = /[^.]\s*require\s*\(\s*["']([^'"\s]+)["']\s*\)/g,
jsSuffixRegExp = /\.js$/,
currDirRegExp = /^\.\//,
op = Object.prototype,
ostring = op.toString,
hasOwn = op.hasOwnProperty,
ap = Array.prototype,
apsp = ap.splice,
isBrowser = !!(typeof window !== 'undefined' && typeof navigator !== 'undefined' && window.document),
isWebWorker = !isBrowser && typeof importScripts !== 'undefined',
//PS3 indicates loaded and complete, but need to wait for complete
//specifically. Sequence is 'loading', 'loaded', execution,
// then 'complete'. The UA check is unfortunate, but not sure how
//to feature test w/o causing perf issues.
readyRegExp = isBrowser && navigator.platform === 'PLAYSTATION 3' ?
/^complete$/ : /^(complete|loaded)$/,
defContextName = '_',
//Oh the tragedy, detecting opera. See the usage of isOpera for reason.
isOpera = typeof opera !== 'undefined' && opera.toString() === '[object Opera]',
contexts = {},
cfg = {},
globalDefQueue = [],
useInteractive = false;
function isFunction(it) {
return ostring.call(it) === '[object Function]';
}
function isArray(it) {
return ostring.call(it) === '[object Array]';
}
/**
* Helper function for iterating over an array. If the func returns
* a true value, it will break out of the loop.
*/
function each(ary, func) {
if (ary) {
var i;
for (i = 0; i < ary.length; i += 1) {
if (ary[i] && func(ary[i], i, ary)) {
break;
}
}
}
}
/**
* Helper function for iterating over an array backwards. If the func
* returns a true value, it will break out of the loop.
*/
function eachReverse(ary, func) {
if (ary) {
var i;
for (i = ary.length - 1; i > -1; i -= 1) {
if (ary[i] && func(ary[i], i, ary)) {
break;
}
}
}
}
function hasProp(obj, prop) {
return hasOwn.call(obj, prop);
}
function getOwn(obj, prop) {
return hasProp(obj, prop) && obj[prop];
}
/**
* Cycles over properties in an object and calls a function for each
* property value. If the function returns a truthy value, then the
* iteration is stopped.
*/
function eachProp(obj, func) {
var prop;
for (prop in obj) {
if (hasProp(obj, prop)) {
if (func(obj[prop], prop)) {
break;
}
}
}
}
/**
* Simple function to mix in properties from source into target,
* but only if target does not already have a property of the same name.
*/
function mixin(target, source, force, deepStringMixin) {
if (source) {
eachProp(source, function (value, prop) {
if (force || !hasProp(target, prop)) {
if (deepStringMixin && typeof value !== 'string') {
if (!target[prop]) {
target[prop] = {};
}
mixin(target[prop], value, force, deepStringMixin);
} else {
target[prop] = value;
}
}
});
}
return target;
}
//Similar to Function.prototype.bind, but the 'this' object is specified
//first, since it is easier to read/figure out what 'this' will be.
function bind(obj, fn) {
return function () {
return fn.apply(obj, arguments);
};
}
function scripts() {
return document.getElementsByTagName('script');
}
function defaultOnError(err) {
throw err;
}
//Allow getting a global that expressed in
//dot notation, like 'a.b.c'.
function getGlobal(value) {
if (!value) {
return value;
}
var g = global;
each(value.split('.'), function (part) {
g = g[part];
});
return g;
}
/**
* Constructs an error with a pointer to an URL with more information.
* @param {String} id the error ID that maps to an ID on a web page.
* @param {String} message human readable error.
* @param {Error} [err] the original error, if there is one.
*
* @returns {Error}
*/
function makeError(id, msg, err, requireModules) {
var e = new Error(msg + '\nhttp://requirejs.org/docs/errors.html#' + id);
e.requireType = id;
e.requireModules = requireModules;
if (err) {
e.originalError = err;
}
return e;
}
if (typeof define !== 'undefined') {
//If a define is already in play via another AMD loader,
//do not overwrite.
return;
}
if (typeof requirejs !== 'undefined') {
if (isFunction(requirejs)) {
//Do not overwrite and existing requirejs instance.
return;
}
cfg = requirejs;
requirejs = undefined;
}
//Allow for a require config object
if (typeof require !== 'undefined' && !isFunction(require)) {
//assume it is a config object.
cfg = require;
require = undefined;
}
function newContext(contextName) {
var inCheckLoaded, Module, context, handlers,
checkLoadedTimeoutId,
config = {
//Defaults. Do not set a default for map
//config to speed up normalize(), which
//will run faster if there is no default.
waitSeconds: 7,
baseUrl: './',
paths: {},
pkgs: {},
shim: {},
config: {}
},
registry = {},
//registry of just enabled modules, to speed
//cycle breaking code when lots of modules
//are registered, but not activated.
enabledRegistry = {},
undefEvents = {},
defQueue = [],
defined = {},
urlFetched = {},
requireCounter = 1,
unnormalizedCounter = 1;
/**
* Trims the . and .. from an array of path segments.
* It will keep a leading path segment if a .. will become
* the first path segment, to help with module name lookups,
* which act like paths, but can be remapped. But the end result,
* all paths that use this function should look normalized.
* NOTE: this method MODIFIES the input array.
* @param {Array} ary the array of path segments.
*/
function trimDots(ary) {
var i, part;
for (i = 0; ary[i]; i += 1) {
part = ary[i];
if (part === '.') {
ary.splice(i, 1);
i -= 1;
} else if (part === '..') {
if (i === 1 && (ary[2] === '..' || ary[0] === '..')) {
//End of the line. Keep at least one non-dot
//path segment at the front so it can be mapped
//correctly to disk. Otherwise, there is likely
//no path mapping for a path starting with '..'.
//This can still fail, but catches the most reasonable
//uses of ..
break;
} else if (i > 0) {
ary.splice(i - 1, 2);
i -= 2;
}
}
}
}
/**
* Given a relative module name, like ./something, normalize it to
* a real name that can be mapped to a path.
* @param {String} name the relative name
* @param {String} baseName a real name that the name arg is relative
* to.
* @param {Boolean} applyMap apply the map config to the value. Should
* only be done if this normalization is for a dependency ID.
* @returns {String} normalized name
*/
function normalize(name, baseName, applyMap) {
var pkgName, pkgConfig, mapValue, nameParts, i, j, nameSegment,
foundMap, foundI, foundStarMap, starI,
baseParts = baseName && baseName.split('/'),
normalizedBaseParts = baseParts,
map = config.map,
starMap = map && map['*'];
//Adjust any relative paths.
if (name && name.charAt(0) === '.') {
//If have a base name, try to normalize against it,
//otherwise, assume it is a top-level require that will
//be relative to baseUrl in the end.
if (baseName) {
if (getOwn(config.pkgs, baseName)) {
//If the baseName is a package name, then just treat it as one
//name to concat the name with.
normalizedBaseParts = baseParts = [baseName];
} else {
//Convert baseName to array, and lop off the last part,
//so that . matches that 'directory' and not name of the baseName's
//module. For instance, baseName of 'one/two/three', maps to
//'one/two/three.js', but we want the directory, 'one/two' for
//this normalization.
normalizedBaseParts = baseParts.slice(0, baseParts.length - 1);
}
name = normalizedBaseParts.concat(name.split('/'));
trimDots(name);
//Some use of packages may use a . path to reference the
//'main' module name, so normalize for that.
pkgConfig = getOwn(config.pkgs, (pkgName = name[0]));
name = name.join('/');
if (pkgConfig && name === pkgName + '/' + pkgConfig.main) {
name = pkgName;
}
} else if (name.indexOf('./') === 0) {
// No baseName, so this is ID is resolved relative
// to baseUrl, pull off the leading dot.
name = name.substring(2);
}
}
//Apply map config if available.
if (applyMap && map && (baseParts || starMap)) {
nameParts = name.split('/');
for (i = nameParts.length; i > 0; i -= 1) {
nameSegment = nameParts.slice(0, i).join('/');
if (baseParts) {
//Find the longest baseName segment match in the config.
//So, do joins on the biggest to smallest lengths of baseParts.
for (j = baseParts.length; j > 0; j -= 1) {
mapValue = getOwn(map, baseParts.slice(0, j).join('/'));
//baseName segment has config, find if it has one for
//this name.
if (mapValue) {
mapValue = getOwn(mapValue, nameSegment);
if (mapValue) {
//Match, update name to the new value.
foundMap = mapValue;
foundI = i;
break;
}
}
}
}
if (foundMap) {
break;
}
//Check for a star map match, but just hold on to it,
//if there is a shorter segment match later in a matching
//config, then favor over this star map.
if (!foundStarMap && starMap && getOwn(starMap, nameSegment)) {
foundStarMap = getOwn(starMap, nameSegment);
starI = i;
}
}
if (!foundMap && foundStarMap) {
foundMap = foundStarMap;
foundI = starI;
}
if (foundMap) {
nameParts.splice(0, foundI, foundMap);
name = nameParts.join('/');
}
}
return name;
}
function removeScript(name) {
if (isBrowser) {
each(scripts(), function (scriptNode) {
if (scriptNode.getAttribute('data-requiremodule') === name &&
scriptNode.getAttribute('data-requirecontext') === context.contextName) {
scriptNode.parentNode.removeChild(scriptNode);
return true;
}
});
}
}
function hasPathFallback(id) {
var pathConfig = getOwn(config.paths, id);
if (pathConfig && isArray(pathConfig) && pathConfig.length > 1) {
//Pop off the first array value, since it failed, and
//retry
pathConfig.shift();
context.require.undef(id);
context.require([id]);
return true;
}
}
//Turns a plugin!resource to [plugin, resource]
//with the plugin being undefined if the name
//did not have a plugin prefix.
function splitPrefix(name) {
var prefix,
index = name ? name.indexOf('!') : -1;
if (index > -1) {
prefix = name.substring(0, index);
name = name.substring(index + 1, name.length);
}
return [prefix, name];
}
/**
* Creates a module mapping that includes plugin prefix, module
* name, and path. If parentModuleMap is provided it will
* also normalize the name via require.normalize()
*
* @param {String} name the module name
* @param {String} [parentModuleMap] parent module map
* for the module name, used to resolve relative names.
* @param {Boolean} isNormalized: is the ID already normalized.
* This is true if this call is done for a define() module ID.
* @param {Boolean} applyMap: apply the map config to the ID.
* Should only be true if this map is for a dependency.
*
* @returns {Object}
*/
function makeModuleMap(name, parentModuleMap, isNormalized, applyMap) {
var url, pluginModule, suffix, nameParts,
prefix = null,
parentName = parentModuleMap ? parentModuleMap.name : null,
originalName = name,
isDefine = true,
normalizedName = '';
//If no name, then it means it is a require call, generate an
//internal name.
if (!name) {
isDefine = false;
name = '_@r' + (requireCounter += 1);
}
nameParts = splitPrefix(name);
prefix = nameParts[0];
name = nameParts[1];
if (prefix) {
prefix = normalize(prefix, parentName, applyMap);
pluginModule = getOwn(defined, prefix);
}
//Account for relative paths if there is a base name.
if (name) {
if (prefix) {
if (pluginModule && pluginModule.normalize) {
//Plugin is loaded, use its normalize method.
normalizedName = pluginModule.normalize(name, function (name) {
return normalize(name, parentName, applyMap);
});
} else {
normalizedName = normalize(name, parentName, applyMap);
}
} else {
//A regular module.
normalizedName = normalize(name, parentName, applyMap);
//Normalized name may be a plugin ID due to map config
//application in normalize. The map config values must
//already be normalized, so do not need to redo that part.
nameParts = splitPrefix(normalizedName);
prefix = nameParts[0];
normalizedName = nameParts[1];
isNormalized = true;
url = context.nameToUrl(normalizedName);
}
}
//If the id is a plugin id that cannot be determined if it needs
//normalization, stamp it with a unique ID so two matching relative
//ids that may conflict can be separate.
suffix = prefix && !pluginModule && !isNormalized ?
'_unnormalized' + (unnormalizedCounter += 1) :
'';
return {
prefix: prefix,
name: normalizedName,
parentMap: parentModuleMap,
unnormalized: !!suffix,
url: url,
originalName: originalName,
isDefine: isDefine,
id: (prefix ?
prefix + '!' + normalizedName :
normalizedName) + suffix
};
}
function getModule(depMap) {
var id = depMap.id,
mod = getOwn(registry, id);
if (!mod) {
mod = registry[id] = new context.Module(depMap);
}
return mod;
}
function on(depMap, name, fn) {
var id = depMap.id,
mod = getOwn(registry, id);
if (hasProp(defined, id) &&
(!mod || mod.defineEmitComplete)) {
if (name === 'defined') {
fn(defined[id]);
}
} else {
mod = getModule(depMap);
if (mod.error && name === 'error') {
fn(mod.error);
} else {
mod.on(name, fn);
}
}
}
function onError(err, errback) {
var ids = err.requireModules,
notified = false;
if (errback) {
errback(err);
} else {
each(ids, function (id) {
var mod = getOwn(registry, id);
if (mod) {
//Set error on module, so it skips timeout checks.
mod.error = err;
if (mod.events.error) {
notified = true;
mod.emit('error', err);
}
}
});
if (!notified) {
req.onError(err);
}
}
}
/**
* Internal method to transfer globalQueue items to this context's
* defQueue.
*/
function takeGlobalQueue() {
//Push all the globalDefQueue items into the context's defQueue
if (globalDefQueue.length) {
//Array splice in the values since the context code has a
//local var ref to defQueue, so cannot just reassign the one
//on context.
apsp.apply(defQueue,
[defQueue.length - 1, 0].concat(globalDefQueue));
globalDefQueue = [];
}
}
handlers = {
'require': function (mod) {
if (mod.require) {
return mod.require;
} else {
return (mod.require = context.makeRequire(mod.map));
}
},
'exports': function (mod) {
mod.usingExports = true;
if (mod.map.isDefine) {
if (mod.exports) {
return mod.exports;
} else {
return (mod.exports = defined[mod.map.id] = {});
}
}
},
'module': function (mod) {
if (mod.module) {
return mod.module;
} else {
return (mod.module = {
id: mod.map.id,
uri: mod.map.url,
config: function () {
var c,
pkg = getOwn(config.pkgs, mod.map.id);
// For packages, only support config targeted
// at the main module.
c = pkg ? getOwn(config.config, mod.map.id + '/' + pkg.main) :
getOwn(config.config, mod.map.id);
return c || {};
},
exports: defined[mod.map.id]
});
}
}
};
function cleanRegistry(id) {
//Clean up machinery used for waiting modules.
delete registry[id];
delete enabledRegistry[id];
}
function breakCycle(mod, traced, processed) {
var id = mod.map.id;
if (mod.error) {
mod.emit('error', mod.error);
} else {
traced[id] = true;
each(mod.depMaps, function (depMap, i) {
var depId = depMap.id,
dep = getOwn(registry, depId);
//Only force things that have not completed
//being defined, so still in the registry,
//and only if it has not been matched up
//in the module already.
if (dep && !mod.depMatched[i] && !processed[depId]) {
if (getOwn(traced, depId)) {
mod.defineDep(i, defined[depId]);
mod.check(); //pass false?
} else {
breakCycle(dep, traced, processed);
}
}
});
processed[id] = true;
}
}
function checkLoaded() {
var map, modId, err, usingPathFallback,
waitInterval = config.waitSeconds * 1000,
//It is possible to disable the wait interval by using waitSeconds of 0.
expired = waitInterval && (context.startTime + waitInterval) < new Date().getTime(),
noLoads = [],
reqCalls = [],
stillLoading = false,
needCycleCheck = true;
//Do not bother if this call was a result of a cycle break.
if (inCheckLoaded) {
return;
}
inCheckLoaded = true;
//Figure out the state of all the modules.
eachProp(enabledRegistry, function (mod) {
map = mod.map;
modId = map.id;
//Skip things that are not enabled or in error state.
if (!mod.enabled) {
return;
}
if (!map.isDefine) {
reqCalls.push(mod);
}
if (!mod.error) {
//If the module should be executed, and it has not
//been inited and time is up, remember it.
if (!mod.inited && expired) {
if (hasPathFallback(modId)) {
usingPathFallback = true;
stillLoading = true;
} else {
noLoads.push(modId);
removeScript(modId);
}
} else if (!mod.inited && mod.fetched && map.isDefine) {
stillLoading = true;
if (!map.prefix) {
//No reason to keep looking for unfinished
//loading. If the only stillLoading is a
//plugin resource though, keep going,
//because it may be that a plugin resource
//is waiting on a non-plugin cycle.
return (needCycleCheck = false);
}
}
}
});
if (expired && noLoads.length) {
//If wait time expired, throw error of unloaded modules.
err = makeError('timeout', 'Load timeout for modules: ' + noLoads, null, noLoads);
err.contextName = context.contextName;
return onError(err);
}
//Not expired, check for a cycle.
if (needCycleCheck) {
each(reqCalls, function (mod) {
breakCycle(mod, {}, {});
});
}
//If still waiting on loads, and the waiting load is something
//other than a plugin resource, or there are still outstanding
//scripts, then just try back later.
if ((!expired || usingPathFallback) && stillLoading) {
//Something is still waiting to load. Wait for it, but only
//if a timeout is not already in effect.
if ((isBrowser || isWebWorker) && !checkLoadedTimeoutId) {
checkLoadedTimeoutId = setTimeout(function () {
checkLoadedTimeoutId = 0;
checkLoaded();
}, 50);
}
}
inCheckLoaded = false;
}
Module = function (map) {
this.events = getOwn(undefEvents, map.id) || {};
this.map = map;
this.shim = getOwn(config.shim, map.id);
this.depExports = [];
this.depMaps = [];
this.depMatched = [];
this.pluginMaps = {};
this.depCount = 0;
/* this.exports this.factory
this.depMaps = [],
this.enabled, this.fetched
*/
};
Module.prototype = {
init: function (depMaps, factory, errback, options) {
options = options || {};
//Do not do more inits if already done. Can happen if there
//are multiple define calls for the same module. That is not
//a normal, common case, but it is also not unexpected.
if (this.inited) {
return;
}
this.factory = factory;
if (errback) {
//Register for errors on this module.
this.on('error', errback);
} else if (this.events.error) {
//If no errback already, but there are error listeners
//on this module, set up an errback to pass to the deps.
errback = bind(this, function (err) {
this.emit('error', err);
});
}
//Do a copy of the dependency array, so that
//source inputs are not modified. For example
//"shim" deps are passed in here directly, and
//doing a direct modification of the depMaps array
//would affect that config.
this.depMaps = depMaps && depMaps.slice(0);
this.errback = errback;
//Indicate this module has be initialized
this.inited = true;
this.ignore = options.ignore;
//Could have option to init this module in enabled mode,
//or could have been previously marked as enabled. However,
//the dependencies are not known until init is called. So
//if enabled previously, now trigger dependencies as enabled.
if (options.enabled || this.enabled) {
//Enable this module and dependencies.
//Will call this.check()
this.enable();
} else {
this.check();
}
},
defineDep: function (i, depExports) {
//Because of cycles, defined callback for a given
//export can be called more than once.
if (!this.depMatched[i]) {
this.depMatched[i] = true;
this.depCount -= 1;
this.depExports[i] = depExports;
}
},
fetch: function () {
if (this.fetched) {
return;
}
this.fetched = true;
context.startTime = (new Date()).getTime();
var map = this.map;
//If the manager is for a plugin managed resource,
//ask the plugin to load it now.
if (this.shim) {
context.makeRequire(this.map, {
enableBuildCallback: true
})(this.shim.deps || [], bind(this, function () {
return map.prefix ? this.callPlugin() : this.load();
}));
} else {
//Regular dependency.
return map.prefix ? this.callPlugin() : this.load();
}
},
load: function () {
var url = this.map.url;
//Regular dependency.
if (!urlFetched[url]) {
urlFetched[url] = true;
context.load(this.map.id, url);
}
},
/**
* Checks if the module is ready to define itself, and if so,
* define it.
*/
check: function () {
if (!this.enabled || this.enabling) {
return;
}
var err, cjsModule,
id = this.map.id,
depExports = this.depExports,
exports = this.exports,
factory = this.factory;
if (!this.inited) {
this.fetch();
} else if (this.error) {
this.emit('error', this.error);
} else if (!this.defining) {
//The factory could trigger another require call
//that would result in checking this module to
//define itself again. If already in the process
//of doing that, skip this work.
this.defining = true;
if (this.depCount < 1 && !this.defined) {
if (isFunction(factory)) {
//If there is an error listener, favor passing
//to that instead of throwing an error. However,
//only do it for define()'d modules. require
//errbacks should not be called for failures in
//their callbacks (#699). However if a global
//onError is set, use that.
if ((this.events.error && this.map.isDefine) ||
req.onError !== defaultOnError) {
try {
exports = context.execCb(id, factory, depExports, exports);
} catch (e) {
err = e;
}
} else {
exports = context.execCb(id, factory, depExports, exports);
}
if (this.map.isDefine) {
//If setting exports via 'module' is in play,
//favor that over return value and exports. After that,
//favor a non-undefined return value over exports use.
cjsModule = this.module;
if (cjsModule &&
cjsModule.exports !== undefined &&
//Make sure it is not already the exports value
cjsModule.exports !== this.exports) {
exports = cjsModule.exports;
} else if (exports === undefined && this.usingExports) {
//exports already set the defined value.
exports = this.exports;
}
}
if (err) {
err.requireMap = this.map;
err.requireModules = this.map.isDefine ? [this.map.id] : null;
err.requireType = this.map.isDefine ? 'define' : 'require';
return onError((this.error = err));
}
} else {
//Just a literal value
exports = factory;
}
this.exports = exports;
if (this.map.isDefine && !this.ignore) {
defined[id] = exports;
if (req.onResourceLoad) {
req.onResourceLoad(context, this.map, this.depMaps);
}
}
//Clean up
cleanRegistry(id);
this.defined = true;
}
//Finished the define stage. Allow calling check again
//to allow define notifications below in the case of a
//cycle.
this.defining = false;
if (this.defined && !this.defineEmitted) {
this.defineEmitted = true;
this.emit('defined', this.exports);
this.defineEmitComplete = true;
}
}
},
callPlugin: function () {
var map = this.map,
id = map.id,
//Map already normalized the prefix.
pluginMap = makeModuleMap(map.prefix);
//Mark this as a dependency for this plugin, so it
//can be traced for cycles.
this.depMaps.push(pluginMap);
on(pluginMap, 'defined', bind(this, function (plugin) {
var load, normalizedMap, normalizedMod,
name = this.map.name,
parentName = this.map.parentMap ? this.map.parentMap.name : null,
localRequire = context.makeRequire(map.parentMap, {
enableBuildCallback: true
});
//If current map is not normalized, wait for that
//normalized name to load instead of continuing.
if (this.map.unnormalized) {
//Normalize the ID if the plugin allows it.
if (plugin.normalize) {
name = plugin.normalize(name, function (name) {
return normalize(name, parentName, true);
}) || '';
}
//prefix and name should already be normalized, no need
//for applying map config again either.
normalizedMap = makeModuleMap(map.prefix + '!' + name,
this.map.parentMap);
on(normalizedMap,
'defined', bind(this, function (value) {
this.init([], function () { return value; }, null, {
enabled: true,
ignore: true
});
}));
normalizedMod = getOwn(registry, normalizedMap.id);
if (normalizedMod) {
//Mark this as a dependency for this plugin, so it
//can be traced for cycles.
this.depMaps.push(normalizedMap);
if (this.events.error) {
normalizedMod.on('error', bind(this, function (err) {
this.emit('error', err);
}));
}
normalizedMod.enable();
}
return;
}
load = bind(this, function (value) {
this.init([], function () { return value; }, null, {
enabled: true
});
});
load.error = bind(this, function (err) {
this.inited = true;
this.error = err;
err.requireModules = [id];
//Remove temp unnormalized modules for this module,
//since they will never be resolved otherwise now.
eachProp(registry, function (mod) {
if (mod.map.id.indexOf(id + '_unnormalized') === 0) {
cleanRegistry(mod.map.id);
}
});
onError(err);
});
//Allow plugins to load other code without having to know the
//context or how to 'complete' the load.
load.fromText = bind(this, function (text, textAlt) {
/*jslint evil: true */
var moduleName = map.name,
moduleMap = makeModuleMap(moduleName),
hasInteractive = useInteractive;
//As of 2.1.0, support just passing the text, to reinforce
//fromText only being called once per resource. Still
//support old style of passing moduleName but discard
//that moduleName in favor of the internal ref.
if (textAlt) {
text = textAlt;
}
//Turn off interactive script matching for IE for any define
//calls in the text, then turn it back on at the end.
if (hasInteractive) {
useInteractive = false;
}
//Prime the system by creating a module instance for
//it.
getModule(moduleMap);
//Transfer any config to this other module.
if (hasProp(config.config, id)) {
config.config[moduleName] = config.config[id];
}
try {
req.exec(text);
} catch (e) {
return onError(makeError('fromtexteval',
'fromText eval for ' + id +
' failed: ' + e,
e,
[id]));
}
if (hasInteractive) {
useInteractive = true;
}
//Mark this as a dependency for the plugin
//resource
this.depMaps.push(moduleMap);
//Support anonymous modules.
context.completeLoad(moduleName);
//Bind the value of that module to the value for this
//resource ID.
localRequire([moduleName], load);
});
//Use parentName here since the plugin's name is not reliable,
//could be some weird string with no path that actually wants to
//reference the parentName's path.
plugin.load(map.name, localRequire, load, config);
}));
context.enable(pluginMap, this);
this.pluginMaps[pluginMap.id] = pluginMap;
},
enable: function () {
enabledRegistry[this.map.id] = this;
this.enabled = true;
//Set flag mentioning that the module is enabling,
//so that immediate calls to the defined callbacks
//for dependencies do not trigger inadvertent load
//with the depCount still being zero.
this.enabling = true;
//Enable each dependency
each(this.depMaps, bind(this, function (depMap, i) {
var id, mod, handler;
if (typeof depMap === 'string') {
//Dependency needs to be converted to a depMap
//and wired up to this module.
depMap = makeModuleMap(depMap,
(this.map.isDefine ? this.map : this.map.parentMap),
false,
!this.skipMap);
this.depMaps[i] = depMap;
handler = getOwn(handlers, depMap.id);
if (handler) {
this.depExports[i] = handler(this);
return;
}
this.depCount += 1;
on(depMap, 'defined', bind(this, function (depExports) {
this.defineDep(i, depExports);
this.check();
}));
if (this.errback) {
on(depMap, 'error', bind(this, this.errback));
}
}
id = depMap.id;
mod = registry[id];
//Skip special modules like 'require', 'exports', 'module'
//Also, don't call enable if it is already enabled,
//important in circular dependency cases.
if (!hasProp(handlers, id) && mod && !mod.enabled) {
context.enable(depMap, this);
}
}));
//Enable each plugin that is used in
//a dependency
eachProp(this.pluginMaps, bind(this, function (pluginMap) {
var mod = getOwn(registry, pluginMap.id);
if (mod && !mod.enabled) {
context.enable(pluginMap, this);
}
}));
this.enabling = false;
this.check();
},
on: function (name, cb) {
var cbs = this.events[name];
if (!cbs) {
cbs = this.events[name] = [];
}
cbs.push(cb);
},
emit: function (name, evt) {
each(this.events[name], function (cb) {
cb(evt);
});
if (name === 'error') {
//Now that the error handler was triggered, remove
//the listeners, since this broken Module instance
//can stay around for a while in the registry.
delete this.events[name];
}
}
};
function callGetModule(args) {
//Skip modules already defined.
if (!hasProp(defined, args[0])) {
getModule(makeModuleMap(args[0], null, true)).init(args[1], args[2]);
}
}
function removeListener(node, func, name, ieName) {
//Favor detachEvent because of IE9
//issue, see attachEvent/addEventListener comment elsewhere
//in this file.
if (node.detachEvent && !isOpera) {
//Probably IE. If not it will throw an error, which will be
//useful to know.
if (ieName) {
node.detachEvent(ieName, func);
}
} else {
node.removeEventListener(name, func, false);
}
}
/**
* Given an event from a script node, get the requirejs info from it,
* and then removes the event listeners on the node.
* @param {Event} evt
* @returns {Object}
*/
function getScriptData(evt) {
//Using currentTarget instead of target for Firefox 2.0's sake. Not
//all old browsers will be supported, but this one was easy enough
//to support and still makes sense.
var node = evt.currentTarget || evt.srcElement;
//Remove the listeners once here.
removeListener(node, context.onScriptLoad, 'load', 'onreadystatechange');
removeListener(node, context.onScriptError, 'error');
return {
node: node,
id: node && node.getAttribute('data-requiremodule')
};
}
function intakeDefines() {
var args;
//Any defined modules in the global queue, intake them now.
takeGlobalQueue();
//Make sure any remaining defQueue items get properly processed.
while (defQueue.length) {
args = defQueue.shift();
if (args[0] === null) {
return onError(makeError('mismatch', 'Mismatched anonymous define() module: ' + args[args.length - 1]));
} else {
//args are id, deps, factory. Should be normalized by the
//define() function.
callGetModule(args);
}
}
}
context = {
config: config,
contextName: contextName,
registry: registry,
defined: defined,
urlFetched: urlFetched,
defQueue: defQueue,
Module: Module,
makeModuleMap: makeModuleMap,
nextTick: req.nextTick,
onError: onError,
/**
* Set a configuration for the context.
* @param {Object} cfg config object to integrate.
*/
configure: function (cfg) {
//Make sure the baseUrl ends in a slash.
if (cfg.baseUrl) {
if (cfg.baseUrl.charAt(cfg.baseUrl.length - 1) !== '/') {
cfg.baseUrl += '/';
}
}
//Save off the paths and packages since they require special processing,
//they are additive.
var pkgs = config.pkgs,
shim = config.shim,
objs = {
paths: true,
config: true,
map: true
};
eachProp(cfg, function (value, prop) {
if (objs[prop]) {
if (prop === 'map') {
if (!config.map) {
config.map = {};
}
mixin(config[prop], value, true, true);
} else {
mixin(config[prop], value, true);
}
} else {
config[prop] = value;
}
});
//Merge shim
if (cfg.shim) {
eachProp(cfg.shim, function (value, id) {
//Normalize the structure
if (isArray(value)) {
value = {
deps: value
};
}
if ((value.exports || value.init) && !value.exportsFn) {
value.exportsFn = context.makeShimExports(value);
}
shim[id] = value;
});
config.shim = shim;
}
//Adjust packages if necessary.
if (cfg.packages) {
each(cfg.packages, function (pkgObj) {
var location;
pkgObj = typeof pkgObj === 'string' ? { name: pkgObj } : pkgObj;
location = pkgObj.location;
//Create a brand new object on pkgs, since currentPackages can
//be passed in again, and config.pkgs is the internal transformed
//state for all package configs.
pkgs[pkgObj.name] = {
name: pkgObj.name,
location: location || pkgObj.name,
//Remove leading dot in main, so main paths are normalized,
//and remove any trailing .js, since different package
//envs have different conventions: some use a module name,
//some use a file name.
main: (pkgObj.main || 'main')
.replace(currDirRegExp, '')
.replace(jsSuffixRegExp, '')
};
});
//Done with modifications, assing packages back to context config
config.pkgs = pkgs;
}
//If there are any "waiting to execute" modules in the registry,
//update the maps for them, since their info, like URLs to load,
//may have changed.
eachProp(registry, function (mod, id) {
//If module already has init called, since it is too
//late to modify them, and ignore unnormalized ones
//since they are transient.
if (!mod.inited && !mod.map.unnormalized) {
mod.map = makeModuleMap(id);
}
});
//If a deps array or a config callback is specified, then call
//require with those args. This is useful when require is defined as a
//config object before require.js is loaded.
if (cfg.deps || cfg.callback) {
context.require(cfg.deps || [], cfg.callback);
}
},
makeShimExports: function (value) {
function fn() {
var ret;
if (value.init) {
ret = value.init.apply(global, arguments);
}
return ret || (value.exports && getGlobal(value.exports));
}
return fn;
},
makeRequire: function (relMap, options) {
options = options || {};
function localRequire(deps, callback, errback) {
var id, map, requireMod;
if (options.enableBuildCallback && callback && isFunction(callback)) {
callback.__requireJsBuild = true;
}
if (typeof deps === 'string') {
if (isFunction(callback)) {
//Invalid call
return onError(makeError('requireargs', 'Invalid require call'), errback);
}
//If require|exports|module are requested, get the
//value for them from the special handlers. Caveat:
//this only works while module is being defined.
if (relMap && hasProp(handlers, deps)) {
return handlers[deps](registry[relMap.id]);
}
//Synchronous access to one module. If require.get is
//available (as in the Node adapter), prefer that.
if (req.get) {
return req.get(context, deps, relMap, localRequire);
}
//Normalize module name, if it contains . or ..
map = makeModuleMap(deps, relMap, false, true);
id = map.id;
if (!hasProp(defined, id)) {
return onError(makeError('notloaded', 'Module name "' +
id +
'" has not been loaded yet for context: ' +
contextName +
(relMap ? '' : '. Use require([])')));
}
return defined[id];
}
//Grab defines waiting in the global queue.
intakeDefines();
//Mark all the dependencies as needing to be loaded.
context.nextTick(function () {
//Some defines could have been added since the
//require call, collect them.
intakeDefines();
requireMod = getModule(makeModuleMap(null, relMap));
//Store if map config should be applied to this require
//call for dependencies.
requireMod.skipMap = options.skipMap;
requireMod.init(deps, callback, errback, {
enabled: true
});
checkLoaded();
});
return localRequire;
}
mixin(localRequire, {
isBrowser: isBrowser,
/**
* Converts a module name + .extension into an URL path.
* *Requires* the use of a module name. It does not support using
* plain URLs like nameToUrl.
*/
toUrl: function (moduleNamePlusExt) {
var ext,
index = moduleNamePlusExt.lastIndexOf('.'),
segment = moduleNamePlusExt.split('/')[0],
isRelative = segment === '.' || segment === '..';
//Have a file extension alias, and it is not the
//dots from a relative path.
if (index !== -1 && (!isRelative || index > 1)) {
ext = moduleNamePlusExt.substring(index, moduleNamePlusExt.length);
moduleNamePlusExt = moduleNamePlusExt.substring(0, index);
}
return context.nameToUrl(normalize(moduleNamePlusExt,
relMap && relMap.id, true), ext, true);
},
defined: function (id) {
return hasProp(defined, makeModuleMap(id, relMap, false, true).id);
},
specified: function (id) {
id = makeModuleMap(id, relMap, false, true).id;
return hasProp(defined, id) || hasProp(registry, id);
}
});
//Only allow undef on top level require calls
if (!relMap) {
localRequire.undef = function (id) {
//Bind any waiting define() calls to this context,
//fix for #408
takeGlobalQueue();
var map = makeModuleMap(id, relMap, true),
mod = getOwn(registry, id);
removeScript(id);
delete defined[id];
delete urlFetched[map.url];
delete undefEvents[id];
if (mod) {
//Hold on to listeners in case the
//module will be attempted to be reloaded
//using a different config.
if (mod.events.defined) {
undefEvents[id] = mod.events;
}
cleanRegistry(id);
}
};
}
return localRequire;
},
/**
* Called to enable a module if it is still in the registry
* awaiting enablement. A second arg, parent, the parent module,
* is passed in for context, when this method is overriden by
* the optimizer. Not shown here to keep code compact.
*/
enable: function (depMap) {
var mod = getOwn(registry, depMap.id);
if (mod) {
getModule(depMap).enable();
}
},
/**
* Internal method used by environment adapters to complete a load event.
* A load event could be a script load or just a load pass from a synchronous
* load call.
* @param {String} moduleName the name of the module to potentially complete.
*/
completeLoad: function (moduleName) {
var found, args, mod,
shim = getOwn(config.shim, moduleName) || {},
shExports = shim.exports;
takeGlobalQueue();
while (defQueue.length) {
args = defQueue.shift();
if (args[0] === null) {
args[0] = moduleName;
//If already found an anonymous module and bound it
//to this name, then this is some other anon module
//waiting for its completeLoad to fire.
if (found) {
break;
}
found = true;
} else if (args[0] === moduleName) {
//Found matching define call for this script!
found = true;
}
callGetModule(args);
}
//Do this after the cycle of callGetModule in case the result
//of those calls/init calls changes the registry.
mod = getOwn(registry, moduleName);
if (!found && !hasProp(defined, moduleName) && mod && !mod.inited) {
if (config.enforceDefine && (!shExports || !getGlobal(shExports))) {
if (hasPathFallback(moduleName)) {
return;
} else {
return onError(makeError('nodefine',
'No define call for ' + moduleName,
null,
[moduleName]));
}
} else {
//A script that does not call define(), so just simulate
//the call for it.
callGetModule([moduleName, (shim.deps || []), shim.exportsFn]);
}
}
checkLoaded();
},
/**
* Converts a module name to a file path. Supports cases where
* moduleName may actually be just an URL.
* Note that it **does not** call normalize on the moduleName,
* it is assumed to have already been normalized. This is an
* internal API, not a public one. Use toUrl for the public API.
*/
nameToUrl: function (moduleName, ext, skipExt) {
var paths, pkgs, pkg, pkgPath, syms, i, parentModule, url,
parentPath;
//If a colon is in the URL, it indicates a protocol is used and it is just
//an URL to a file, or if it starts with a slash, contains a query arg (i.e. ?)
//or ends with .js, then assume the user meant to use an url and not a module id.
//The slash is important for protocol-less URLs as well as full paths.
if (req.jsExtRegExp.test(moduleName)) {
//Just a plain path, not module name lookup, so just return it.
//Add extension if it is included. This is a bit wonky, only non-.js things pass
//an extension, this method probably needs to be reworked.
url = moduleName + (ext || '');
} else {
//A module that needs to be converted to a path.
paths = config.paths;
pkgs = config.pkgs;
syms = moduleName.split('/');
//For each module name segment, see if there is a path
//registered for it. Start with most specific name
//and work up from it.
for (i = syms.length; i > 0; i -= 1) {
parentModule = syms.slice(0, i).join('/');
pkg = getOwn(pkgs, parentModule);
parentPath = getOwn(paths, parentModule);
if (parentPath) {
//If an array, it means there are a few choices,
//Choose the one that is desired
if (isArray(parentPath)) {
parentPath = parentPath[0];
}
syms.splice(0, i, parentPath);
break;
} else if (pkg) {
//If module name is just the package name, then looking
//for the main module.
if (moduleName === pkg.name) {
pkgPath = pkg.location + '/' + pkg.main;
} else {
pkgPath = pkg.location;
}
syms.splice(0, i, pkgPath);
break;
}
}
//Join the path parts together, then figure out if baseUrl is needed.
url = syms.join('/');
url += (ext || (/^data\:|\?/.test(url) || skipExt ? '' : '.js'));
url = (url.charAt(0) === '/' || url.match(/^[\w\+\.\-]+:/) ? '' : config.baseUrl) + url;
}
return config.urlArgs ? url +
((url.indexOf('?') === -1 ? '?' : '&') +
config.urlArgs) : url;
},
//Delegates to req.load. Broken out as a separate function to
//allow overriding in the optimizer.
load: function (id, url) {
req.load(context, id, url);
},
/**
* Executes a module callback function. Broken out as a separate function
* solely to allow the build system to sequence the files in the built
* layer in the right sequence.
*
* @private
*/
execCb: function (name, callback, args, exports) {
return callback.apply(exports, args);
},
/**
* callback for script loads, used to check status of loading.
*
* @param {Event} evt the event from the browser for the script
* that was loaded.
*/
onScriptLoad: function (evt) {
//Using currentTarget instead of target for Firefox 2.0's sake. Not
//all old browsers will be supported, but this one was easy enough
//to support and still makes sense.
if (evt.type === 'load' ||
(readyRegExp.test((evt.currentTarget || evt.srcElement).readyState))) {
//Reset interactive script so a script node is not held onto for
//to long.
interactiveScript = null;
//Pull out the name of the module and the context.
var data = getScriptData(evt);
context.completeLoad(data.id);
}
},
/**
* Callback for script errors.
*/
onScriptError: function (evt) {
var data = getScriptData(evt);
if (!hasPathFallback(data.id)) {
return onError(makeError('scripterror', 'Script error for: ' + data.id, evt, [data.id]));
}
}
};
context.require = context.makeRequire();
return context;
}
/**
* Main entry point.
*
* If the only argument to require is a string, then the module that
* is represented by that string is fetched for the appropriate context.
*
* If the first argument is an array, then it will be treated as an array
* of dependency string names to fetch. An optional function callback can
* be specified to execute when all of those dependencies are available.
*
* Make a local req variable to help Caja compliance (it assumes things
* on a require that are not standardized), and to give a short
* name for minification/local scope use.
*/
req = requirejs = function (deps, callback, errback, optional) {
//Find the right context, use default
var context, config,
contextName = defContextName;
// Determine if have config object in the call.
if (!isArray(deps) && typeof deps !== 'string') {
// deps is a config object
config = deps;
if (isArray(callback)) {
// Adjust args if there are dependencies
deps = callback;
callback = errback;
errback = optional;
} else {
deps = [];
}
}
if (config && config.context) {
contextName = config.context;
}
context = getOwn(contexts, contextName);
if (!context) {
context = contexts[contextName] = req.s.newContext(contextName);
}
if (config) {
context.configure(config);
}
return context.require(deps, callback, errback);
};
/**
* Support require.config() to make it easier to cooperate with other
* AMD loaders on globally agreed names.
*/
req.config = function (config) {
return req(config);
};
/**
* Execute something after the current tick
* of the event loop. Override for other envs
* that have a better solution than setTimeout.
* @param {Function} fn function to execute later.
*/
req.nextTick = typeof setTimeout !== 'undefined' ? function (fn) {
setTimeout(fn, 4);
} : function (fn) { fn(); };
/**
* Export require as a global, but only if it does not already exist.
*/
if (!require) {
require = req;
}
req.version = version;
//Used to filter out dependencies that are already paths.
req.jsExtRegExp = /^\/|:|\?|\.js$/;
req.isBrowser = isBrowser;
s = req.s = {
contexts: contexts,
newContext: newContext
};
//Create default context.
req({});
//Exports some context-sensitive methods on global require.
each([
'toUrl',
'undef',
'defined',
'specified'
], function (prop) {
//Reference from contexts instead of early binding to default context,
//so that during builds, the latest instance of the default context
//with its config gets used.
req[prop] = function () {
var ctx = contexts[defContextName];
return ctx.require[prop].apply(ctx, arguments);
};
});
if (isBrowser) {
head = s.head = document.getElementsByTagName('head')[0];
//If BASE tag is in play, using appendChild is a problem for IE6.
//When that browser dies, this can be removed. Details in this jQuery bug:
//http://dev.jquery.com/ticket/2709
baseElement = document.getElementsByTagName('base')[0];
if (baseElement) {
head = s.head = baseElement.parentNode;
}
}
/**
* Any errors that require explicitly generates will be passed to this
* function. Intercept/override it if you want custom error handling.
* @param {Error} err the error object.
*/
req.onError = defaultOnError;
/**
* Creates the node for the load command. Only used in browser envs.
*/
req.createNode = function (config, moduleName, url) {
var node = config.xhtml ?
document.createElementNS('http://www.w3.org/1999/xhtml', 'html:script') :
document.createElement('script');
node.type = config.scriptType || 'text/javascript';
node.charset = 'utf-8';
node.async = true;
return node;
};
/**
* Does the request to load a module for the browser case.
* Make this a separate function to allow other environments
* to override it.
*
* @param {Object} context the require context to find state.
* @param {String} moduleName the name of the module.
* @param {Object} url the URL to the module.
*/
req.load = function (context, moduleName, url) {
var config = (context && context.config) || {},
node;
if (isBrowser) {
//In the browser so use a script tag
node = req.createNode(config, moduleName, url);
node.setAttribute('data-requirecontext', context.contextName);
node.setAttribute('data-requiremodule', moduleName);
//Set up load listener. Test attachEvent first because IE9 has
//a subtle issue in its addEventListener and script onload firings
//that do not match the behavior of all other browsers with
//addEventListener support, which fire the onload event for a
//script right after the script execution. See:
//https://connect.microsoft.com/IE/feedback/details/648057/script-onload-event-is-not-fired-immediately-after-script-execution
//UNFORTUNATELY Opera implements attachEvent but does not follow the script
//script execution mode.
if (node.attachEvent &&
//Check if node.attachEvent is artificially added by custom script or
//natively supported by browser
//read https://github.com/jrburke/requirejs/issues/187
//if we can NOT find [native code] then it must NOT natively supported.
//in IE8, node.attachEvent does not have toString()
//Note the test for "[native code" with no closing brace, see:
//https://github.com/jrburke/requirejs/issues/273
!(node.attachEvent.toString && node.attachEvent.toString().indexOf('[native code') < 0) &&
!isOpera) {
//Probably IE. IE (at least 6-8) do not fire
//script onload right after executing the script, so
//we cannot tie the anonymous define call to a name.
//However, IE reports the script as being in 'interactive'
//readyState at the time of the define call.
useInteractive = true;
node.attachEvent('onreadystatechange', context.onScriptLoad);
//It would be great to add an error handler here to catch
//404s in IE9+. However, onreadystatechange will fire before
//the error handler, so that does not help. If addEventListener
//is used, then IE will fire error before load, but we cannot
//use that pathway given the connect.microsoft.com issue
//mentioned above about not doing the 'script execute,
//then fire the script load event listener before execute
//next script' that other browsers do.
//Best hope: IE10 fixes the issues,
//and then destroys all installs of IE 6-9.
//node.attachEvent('onerror', context.onScriptError);
} else {
node.addEventListener('load', context.onScriptLoad, false);
node.addEventListener('error', context.onScriptError, false);
}
node.src = url;
//For some cache cases in IE 6-8, the script executes before the end
//of the appendChild execution, so to tie an anonymous define
//call to the module name (which is stored on the node), hold on
//to a reference to this node, but clear after the DOM insertion.
currentlyAddingScript = node;
if (baseElement) {
head.insertBefore(node, baseElement);
} else {
head.appendChild(node);
}
currentlyAddingScript = null;
return node;
} else if (isWebWorker) {
try {
//In a web worker, use importScripts. This is not a very
//efficient use of importScripts, importScripts will block until
//its script is downloaded and evaluated. However, if web workers
//are in play, the expectation that a build has been done so that
//only one script needs to be loaded anyway. This may need to be
//reevaluated if other use cases become common.
importScripts(url);
//Account for anonymous modules
context.completeLoad(moduleName);
} catch (e) {
context.onError(makeError('importscripts',
'importScripts failed for ' +
moduleName + ' at ' + url,
e,
[moduleName]));
}
}
};
function getInteractiveScript() {
if (interactiveScript && interactiveScript.readyState === 'interactive') {
return interactiveScript;
}
eachReverse(scripts(), function (script) {
if (script.readyState === 'interactive') {
return (interactiveScript = script);
}
});
return interactiveScript;
}
//Look for a data-main script attribute, which could also adjust the baseUrl.
if (isBrowser && !cfg.skipDataMain) {
//Figure out baseUrl. Get it from the script tag with require.js in it.
eachReverse(scripts(), function (script) {
//Set the 'head' where we can append children by
//using the script's parent.
if (!head) {
head = script.parentNode;
}
//Look for a data-main attribute to set main script for the page
//to load. If it is there, the path to data main becomes the
//baseUrl, if it is not already set.
dataMain = script.getAttribute('data-main');
if (dataMain) {
//Preserve dataMain in case it is a path (i.e. contains '?')
mainScript = dataMain;
//Set final baseUrl if there is not already an explicit one.
if (!cfg.baseUrl) {
//Pull off the directory of data-main for use as the
//baseUrl.
src = mainScript.split('/');
mainScript = src.pop();
subPath = src.length ? src.join('/') + '/' : './';
cfg.baseUrl = subPath;
}
//Strip off any trailing .js since mainScript is now
//like a module name.
mainScript = mainScript.replace(jsSuffixRegExp, '');
//If mainScript is still a path, fall back to dataMain
if (req.jsExtRegExp.test(mainScript)) {
mainScript = dataMain;
}
//Put the data-main script in the files to load.
cfg.deps = cfg.deps ? cfg.deps.concat(mainScript) : [mainScript];
return true;
}
});
}
/**
* The function that handles definitions of modules. Differs from
* require() in that a string for the module should be the first argument,
* and the function to execute after dependencies are loaded should
* return a value to define the module corresponding to the first argument's
* name.
*/
define = function (name, deps, callback) {
var node, context;
//Allow for anonymous modules
if (typeof name !== 'string') {
//Adjust args appropriately
callback = deps;
deps = name;
name = null;
}
//This module may not have dependencies
if (!isArray(deps)) {
callback = deps;
deps = null;
}
//If no name, and callback is a function, then figure out if it a
//CommonJS thing with dependencies.
if (!deps && isFunction(callback)) {
deps = [];
//Remove comments from the callback string,
//look for require calls, and pull them into the dependencies,
//but only if there are function args.
if (callback.length) {
callback
.toString()
.replace(commentRegExp, '')
.replace(cjsRequireRegExp, function (match, dep) {
deps.push(dep);
});
//May be a CommonJS thing even without require calls, but still
//could use exports, and module. Avoid doing exports and module
//work though if it just needs require.
//REQUIRES the function to expect the CommonJS variables in the
//order listed below.
deps = (callback.length === 1 ? ['require'] : ['require', 'exports', 'module']).concat(deps);
}
}
//If in IE 6-8 and hit an anonymous define() call, do the interactive
//work.
if (useInteractive) {
node = currentlyAddingScript || getInteractiveScript();
if (node) {
if (!name) {
name = node.getAttribute('data-requiremodule');
}
context = contexts[node.getAttribute('data-requirecontext')];
}
}
//Always save off evaluating the def call until the script onload handler.
//This allows multiple modules to be in a file without prematurely
//tracing dependencies, and allows for anonymous module support,
//where the module name is not known until the script onload event
//occurs. If no context, use the global queue, and get it processed
//in the onscript load callback.
(context ? context.defQueue : globalDefQueue).push([name, deps, callback]);
};
define.amd = {
jQuery: true
};
/**
* Executes the text. Normally just uses eval, but can be modified
* to use a better, environment-specific call. Only used for transpiling
* loader plugins, not for plain JS modules.
* @param {String} text the text to execute/evaluate.
*/
req.exec = function (text) {
/*jslint evil: true */
return eval(text);
};
//Set up with config info.
req(cfg);
}(this));
handlebars.js-4.7.2/spec/visitor.js 0000664 0000000 0000000 00000011753 13607154272 0017247 0 ustar 00root root 0000000 0000000 describe('Visitor', function() {
if (!Handlebars.Visitor || !Handlebars.print) {
return;
}
it('should provide coverage', function() {
// Simply run the thing and make sure it does not fail and that all of the
// stub methods are executed
var visitor = new Handlebars.Visitor();
visitor.accept(
Handlebars.parse(
'{{foo}}{{#foo (bar 1 "1" true undefined null) foo=@data}}{{!comment}}{{> bar }} {{/foo}}'
)
);
visitor.accept(Handlebars.parse('{{#> bar }} {{/bar}}'));
visitor.accept(Handlebars.parse('{{#* bar }} {{/bar}}'));
visitor.accept(Handlebars.parse('{{* bar }}'));
});
it('should traverse to stubs', function() {
var visitor = new Handlebars.Visitor();
visitor.StringLiteral = function(string) {
equal(string.value, '2');
};
visitor.NumberLiteral = function(number) {
equal(number.value, 1);
};
visitor.BooleanLiteral = function(bool) {
equal(bool.value, true);
equal(this.parents.length, 3);
equal(this.parents[0].type, 'SubExpression');
equal(this.parents[1].type, 'BlockStatement');
equal(this.parents[2].type, 'Program');
};
visitor.PathExpression = function(id) {
equal(/(foo\.)?bar$/.test(id.original), true);
};
visitor.ContentStatement = function(content) {
equal(content.value, ' ');
};
visitor.CommentStatement = function(comment) {
equal(comment.value, 'comment');
};
visitor.accept(
Handlebars.parse(
'{{#foo.bar (foo.bar 1 "2" true) foo=@foo.bar}}{{!comment}}{{> bar }} {{/foo.bar}}'
)
);
});
describe('mutating', function() {
describe('fields', function() {
it('should replace value', function() {
var visitor = new Handlebars.Visitor();
visitor.mutating = true;
visitor.StringLiteral = function(string) {
return { type: 'NumberLiteral', value: 42, loc: string.loc };
};
var ast = Handlebars.parse('{{foo foo="foo"}}');
visitor.accept(ast);
equals(
Handlebars.print(ast),
'{{ PATH:foo [] HASH{foo=NUMBER{42}} }}\n'
);
});
it('should treat undefined resonse as identity', function() {
var visitor = new Handlebars.Visitor();
visitor.mutating = true;
var ast = Handlebars.parse('{{foo foo=42}}');
visitor.accept(ast);
equals(
Handlebars.print(ast),
'{{ PATH:foo [] HASH{foo=NUMBER{42}} }}\n'
);
});
it('should remove false responses', function() {
var visitor = new Handlebars.Visitor();
visitor.mutating = true;
visitor.Hash = function() {
return false;
};
var ast = Handlebars.parse('{{foo foo=42}}');
visitor.accept(ast);
equals(Handlebars.print(ast), '{{ PATH:foo [] }}\n');
});
it('should throw when removing required values', function() {
shouldThrow(
function() {
var visitor = new Handlebars.Visitor();
visitor.mutating = true;
visitor.PathExpression = function() {
return false;
};
var ast = Handlebars.parse('{{foo 42}}');
visitor.accept(ast);
},
Handlebars.Exception,
'MustacheStatement requires path'
);
});
it('should throw when returning non-node responses', function() {
shouldThrow(
function() {
var visitor = new Handlebars.Visitor();
visitor.mutating = true;
visitor.PathExpression = function() {
return {};
};
var ast = Handlebars.parse('{{foo 42}}');
visitor.accept(ast);
},
Handlebars.Exception,
'Unexpected node type "undefined" found when accepting path on MustacheStatement'
);
});
});
describe('arrays', function() {
it('should replace value', function() {
var visitor = new Handlebars.Visitor();
visitor.mutating = true;
visitor.StringLiteral = function(string) {
return { type: 'NumberLiteral', value: 42, loc: string.locInfo };
};
var ast = Handlebars.parse('{{foo "foo"}}');
visitor.accept(ast);
equals(Handlebars.print(ast), '{{ PATH:foo [NUMBER{42}] }}\n');
});
it('should treat undefined resonse as identity', function() {
var visitor = new Handlebars.Visitor();
visitor.mutating = true;
var ast = Handlebars.parse('{{foo 42}}');
visitor.accept(ast);
equals(Handlebars.print(ast), '{{ PATH:foo [NUMBER{42}] }}\n');
});
it('should remove false responses', function() {
var visitor = new Handlebars.Visitor();
visitor.mutating = true;
visitor.NumberLiteral = function() {
return false;
};
var ast = Handlebars.parse('{{foo 42}}');
visitor.accept(ast);
equals(Handlebars.print(ast), '{{ PATH:foo [] }}\n');
});
});
});
});
handlebars.js-4.7.2/spec/whitespace-control.js 0000664 0000000 0000000 00000007424 13607154272 0021362 0 ustar 00root root 0000000 0000000 describe('whitespace control', function() {
it('should strip whitespace around mustache calls', function() {
var hash = { foo: 'bar<' };
shouldCompileTo(' {{~foo~}} ', hash, 'bar<');
shouldCompileTo(' {{~foo}} ', hash, 'bar< ');
shouldCompileTo(' {{foo~}} ', hash, ' bar<');
shouldCompileTo(' {{~&foo~}} ', hash, 'bar<');
shouldCompileTo(' {{~{foo}~}} ', hash, 'bar<');
shouldCompileTo('1\n{{foo~}} \n\n 23\n{{bar}}4', {}, '1\n23\n4');
});
describe('blocks', function() {
it('should strip whitespace around simple block calls', function() {
var hash = { foo: 'bar<' };
shouldCompileTo(' {{~#if foo~}} bar {{~/if~}} ', hash, 'bar');
shouldCompileTo(' {{#if foo~}} bar {{/if~}} ', hash, ' bar ');
shouldCompileTo(' {{~#if foo}} bar {{~/if}} ', hash, ' bar ');
shouldCompileTo(' {{#if foo}} bar {{/if}} ', hash, ' bar ');
shouldCompileTo(
' \n\n{{~#if foo~}} \n\nbar \n\n{{~/if~}}\n\n ',
hash,
'bar'
);
shouldCompileTo(
' a\n\n{{~#if foo~}} \n\nbar \n\n{{~/if~}}\n\na ',
hash,
' abara '
);
});
it('should strip whitespace around inverse block calls', function() {
var hash = {};
shouldCompileTo(' {{~^if foo~}} bar {{~/if~}} ', hash, 'bar');
shouldCompileTo(' {{^if foo~}} bar {{/if~}} ', hash, ' bar ');
shouldCompileTo(' {{~^if foo}} bar {{~/if}} ', hash, ' bar ');
shouldCompileTo(' {{^if foo}} bar {{/if}} ', hash, ' bar ');
shouldCompileTo(
' \n\n{{~^if foo~}} \n\nbar \n\n{{~/if~}}\n\n ',
hash,
'bar'
);
});
it('should strip whitespace around complex block calls', function() {
var hash = { foo: 'bar<' };
shouldCompileTo('{{#if foo~}} bar {{~^~}} baz {{~/if}}', hash, 'bar');
shouldCompileTo('{{#if foo~}} bar {{^~}} baz {{/if}}', hash, 'bar ');
shouldCompileTo('{{#if foo}} bar {{~^~}} baz {{~/if}}', hash, ' bar');
shouldCompileTo('{{#if foo}} bar {{^~}} baz {{/if}}', hash, ' bar ');
shouldCompileTo('{{#if foo~}} bar {{~else~}} baz {{~/if}}', hash, 'bar');
shouldCompileTo(
'\n\n{{~#if foo~}} \n\nbar \n\n{{~^~}} \n\nbaz \n\n{{~/if~}}\n\n',
hash,
'bar'
);
shouldCompileTo(
'\n\n{{~#if foo~}} \n\n{{{foo}}} \n\n{{~^~}} \n\nbaz \n\n{{~/if~}}\n\n',
hash,
'bar<'
);
hash = {};
shouldCompileTo('{{#if foo~}} bar {{~^~}} baz {{~/if}}', hash, 'baz');
shouldCompileTo('{{#if foo}} bar {{~^~}} baz {{/if}}', hash, 'baz ');
shouldCompileTo('{{#if foo~}} bar {{~^}} baz {{~/if}}', hash, ' baz');
shouldCompileTo('{{#if foo~}} bar {{~^}} baz {{/if}}', hash, ' baz ');
shouldCompileTo('{{#if foo~}} bar {{~else~}} baz {{~/if}}', hash, 'baz');
shouldCompileTo(
'\n\n{{~#if foo~}} \n\nbar \n\n{{~^~}} \n\nbaz \n\n{{~/if~}}\n\n',
hash,
'baz'
);
});
});
it('should strip whitespace around partials', function() {
shouldCompileToWithPartials(
'foo {{~> dude~}} ',
[{}, {}, { dude: 'bar' }],
true,
'foobar'
);
shouldCompileToWithPartials(
'foo {{> dude~}} ',
[{}, {}, { dude: 'bar' }],
true,
'foo bar'
);
shouldCompileToWithPartials(
'foo {{> dude}} ',
[{}, {}, { dude: 'bar' }],
true,
'foo bar '
);
shouldCompileToWithPartials(
'foo\n {{~> dude}} ',
[{}, {}, { dude: 'bar' }],
true,
'foobar'
);
shouldCompileToWithPartials(
'foo\n {{> dude}} ',
[{}, {}, { dude: 'bar' }],
true,
'foo\n bar'
);
});
it('should only strip whitespace once', function() {
var hash = { foo: 'bar' };
shouldCompileTo(' {{~foo~}} {{foo}} {{foo}} ', hash, 'barbar bar ');
});
});
handlebars.js-4.7.2/src/ 0000775 0000000 0000000 00000000000 13607154272 0015040 5 ustar 00root root 0000000 0000000 handlebars.js-4.7.2/src/handlebars.l 0000664 0000000 0000000 00000011777 13607154272 0017335 0 ustar 00root root 0000000 0000000
%x mu emu com raw
%{
function strip(start, end) {
return yytext = yytext.substring(start, yyleng - end + start);
}
%}
LEFT_STRIP "~"
RIGHT_STRIP "~"
LOOKAHEAD [=~}\s\/.)|]
LITERAL_LOOKAHEAD [~}\s)]
/*
ID is the inverse of control characters.
Control characters ranges:
[\s] Whitespace
[!"#%-,\./] !, ", #, %, &, ', (, ), *, +, ,, ., /, Exceptions in range: $, -
[;->@] ;, <, =, >, @, Exceptions in range: :, ?
[\[-\^`] [, \, ], ^, `, Exceptions in range: _
[\{-~] {, |, }, ~
*/
ID [^\s!"#%-,\.\/;->@\[-\^`\{-~]+/{LOOKAHEAD}
%%
[^\x00]*?/("{{") {
if(yytext.slice(-2) === "\\\\") {
strip(0,1);
this.begin("mu");
} else if(yytext.slice(-1) === "\\") {
strip(0,1);
this.begin("emu");
} else {
this.begin("mu");
}
if(yytext) return 'CONTENT';
}
[^\x00]+ return 'CONTENT';
// marks CONTENT up to the next mustache or escaped mustache
[^\x00]{2,}?/("{{"|"\\{{"|"\\\\{{"|<>) {
this.popState();
return 'CONTENT';
}
// nested raw block will create stacked 'raw' condition
"{{{{"/[^/] this.begin('raw'); return 'CONTENT';
"{{{{/"[^\s!"#%-,\.\/;->@\[-\^`\{-~]+/[=}\s\/.]"}}}}" {
this.popState();
// Should be using `this.topState()` below, but it currently
// returns the second top instead of the first top. Opened an
// issue about it at https://github.com/zaach/jison/issues/291
if (this.conditionStack[this.conditionStack.length-1] === 'raw') {
return 'CONTENT';
} else {
strip(5, 9);
return 'END_RAW_BLOCK';
}
}
[^\x00]+?/("{{{{") { return 'CONTENT'; }
[\s\S]*?"--"{RIGHT_STRIP}?"}}" {
this.popState();
return 'COMMENT';
}
"(" return 'OPEN_SEXPR';
")" return 'CLOSE_SEXPR';
"{{{{" { return 'OPEN_RAW_BLOCK'; }
"}}}}" {
this.popState();
this.begin('raw');
return 'CLOSE_RAW_BLOCK';
}
"{{"{LEFT_STRIP}?">" return 'OPEN_PARTIAL';
"{{"{LEFT_STRIP}?"#>" return 'OPEN_PARTIAL_BLOCK';
"{{"{LEFT_STRIP}?"#""*"? return 'OPEN_BLOCK';
"{{"{LEFT_STRIP}?"/" return 'OPEN_ENDBLOCK';
"{{"{LEFT_STRIP}?"^"\s*{RIGHT_STRIP}?"}}" this.popState(); return 'INVERSE';
"{{"{LEFT_STRIP}?\s*"else"\s*{RIGHT_STRIP}?"}}" this.popState(); return 'INVERSE';
"{{"{LEFT_STRIP}?"^" return 'OPEN_INVERSE';
"{{"{LEFT_STRIP}?\s*"else" return 'OPEN_INVERSE_CHAIN';
"{{"{LEFT_STRIP}?"{" return 'OPEN_UNESCAPED';
"{{"{LEFT_STRIP}?"&" return 'OPEN';
"{{"{LEFT_STRIP}?"!--" {
this.unput(yytext);
this.popState();
this.begin('com');
}
"{{"{LEFT_STRIP}?"!"[\s\S]*?"}}" {
this.popState();
return 'COMMENT';
}
"{{"{LEFT_STRIP}?"*"? return 'OPEN';
"=" return 'EQUALS';
".." return 'ID';
"."/{LOOKAHEAD} return 'ID';
[\/.] return 'SEP';
\s+ // ignore whitespace
"}"{RIGHT_STRIP}?"}}" this.popState(); return 'CLOSE_UNESCAPED';
{RIGHT_STRIP}?"}}" this.popState(); return 'CLOSE';
'"'("\\"["]|[^"])*'"' yytext = strip(1,2).replace(/\\"/g,'"'); return 'STRING';
"'"("\\"[']|[^'])*"'" yytext = strip(1,2).replace(/\\'/g,"'"); return 'STRING';
"@" return 'DATA';
"true"/{LITERAL_LOOKAHEAD} return 'BOOLEAN';
"false"/{LITERAL_LOOKAHEAD} return 'BOOLEAN';
"undefined"/{LITERAL_LOOKAHEAD} return 'UNDEFINED';
"null"/{LITERAL_LOOKAHEAD} return 'NULL';
\-?[0-9]+(?:\.[0-9]+)?/{LITERAL_LOOKAHEAD} return 'NUMBER';
"as"\s+"|" return 'OPEN_BLOCK_PARAMS';
"|" return 'CLOSE_BLOCK_PARAMS';
{ID} return 'ID';
'['('\\]'|[^\]])*']' yytext = yytext.replace(/\\([\\\]])/g,'$1'); return 'ID';
. return 'INVALID';
<> return 'EOF';
handlebars.js-4.7.2/src/handlebars.yy 0000664 0000000 0000000 00000010253 13607154272 0017527 0 ustar 00root root 0000000 0000000 %start root
%ebnf
%%
root
: program EOF { return $1; }
;
program
: statement* -> yy.prepareProgram($1)
;
statement
: mustache -> $1
| block -> $1
| rawBlock -> $1
| partial -> $1
| partialBlock -> $1
| content -> $1
| COMMENT {
$$ = {
type: 'CommentStatement',
value: yy.stripComment($1),
strip: yy.stripFlags($1, $1),
loc: yy.locInfo(@$)
};
};
content
: CONTENT {
$$ = {
type: 'ContentStatement',
original: $1,
value: $1,
loc: yy.locInfo(@$)
};
};
rawBlock
: openRawBlock content* END_RAW_BLOCK -> yy.prepareRawBlock($1, $2, $3, @$)
;
openRawBlock
: OPEN_RAW_BLOCK helperName param* hash? CLOSE_RAW_BLOCK -> { path: $2, params: $3, hash: $4 }
;
block
: openBlock program inverseChain? closeBlock -> yy.prepareBlock($1, $2, $3, $4, false, @$)
| openInverse program inverseAndProgram? closeBlock -> yy.prepareBlock($1, $2, $3, $4, true, @$)
;
openBlock
: OPEN_BLOCK helperName param* hash? blockParams? CLOSE -> { open: $1, path: $2, params: $3, hash: $4, blockParams: $5, strip: yy.stripFlags($1, $6) }
;
openInverse
: OPEN_INVERSE helperName param* hash? blockParams? CLOSE -> { path: $2, params: $3, hash: $4, blockParams: $5, strip: yy.stripFlags($1, $6) }
;
openInverseChain
: OPEN_INVERSE_CHAIN helperName param* hash? blockParams? CLOSE -> { path: $2, params: $3, hash: $4, blockParams: $5, strip: yy.stripFlags($1, $6) }
;
inverseAndProgram
: INVERSE program -> { strip: yy.stripFlags($1, $1), program: $2 }
;
inverseChain
: openInverseChain program inverseChain? {
var inverse = yy.prepareBlock($1, $2, $3, $3, false, @$),
program = yy.prepareProgram([inverse], $2.loc);
program.chained = true;
$$ = { strip: $1.strip, program: program, chain: true };
}
| inverseAndProgram -> $1
;
closeBlock
: OPEN_ENDBLOCK helperName CLOSE -> {path: $2, strip: yy.stripFlags($1, $3)}
;
mustache
// Parsing out the '&' escape token at AST level saves ~500 bytes after min due to the removal of one parser node.
// This also allows for handler unification as all mustache node instances can utilize the same handler
: OPEN helperName param* hash? CLOSE -> yy.prepareMustache($2, $3, $4, $1, yy.stripFlags($1, $5), @$)
| OPEN_UNESCAPED helperName param* hash? CLOSE_UNESCAPED -> yy.prepareMustache($2, $3, $4, $1, yy.stripFlags($1, $5), @$)
;
partial
: OPEN_PARTIAL partialName param* hash? CLOSE {
$$ = {
type: 'PartialStatement',
name: $2,
params: $3,
hash: $4,
indent: '',
strip: yy.stripFlags($1, $5),
loc: yy.locInfo(@$)
};
}
;
partialBlock
: openPartialBlock program closeBlock -> yy.preparePartialBlock($1, $2, $3, @$)
;
openPartialBlock
: OPEN_PARTIAL_BLOCK partialName param* hash? CLOSE -> { path: $2, params: $3, hash: $4, strip: yy.stripFlags($1, $5) }
;
param
: helperName -> $1
| sexpr -> $1
;
sexpr
: OPEN_SEXPR helperName param* hash? CLOSE_SEXPR {
$$ = {
type: 'SubExpression',
path: $2,
params: $3,
hash: $4,
loc: yy.locInfo(@$)
};
};
hash
: hashSegment+ -> {type: 'Hash', pairs: $1, loc: yy.locInfo(@$)}
;
hashSegment
: ID EQUALS param -> {type: 'HashPair', key: yy.id($1), value: $3, loc: yy.locInfo(@$)}
;
blockParams
: OPEN_BLOCK_PARAMS ID+ CLOSE_BLOCK_PARAMS -> yy.id($2)
;
helperName
: path -> $1
| dataName -> $1
| STRING -> {type: 'StringLiteral', value: $1, original: $1, loc: yy.locInfo(@$)}
| NUMBER -> {type: 'NumberLiteral', value: Number($1), original: Number($1), loc: yy.locInfo(@$)}
| BOOLEAN -> {type: 'BooleanLiteral', value: $1 === 'true', original: $1 === 'true', loc: yy.locInfo(@$)}
| UNDEFINED -> {type: 'UndefinedLiteral', original: undefined, value: undefined, loc: yy.locInfo(@$)}
| NULL -> {type: 'NullLiteral', original: null, value: null, loc: yy.locInfo(@$)}
;
partialName
: helperName -> $1
| sexpr -> $1
;
dataName
: DATA pathSegments -> yy.preparePath(true, $2, @$)
;
path
: pathSegments -> yy.preparePath(false, $1, @$)
;
pathSegments
: pathSegments SEP ID { $1.push({part: yy.id($3), original: $3, separator: $2}); $$ = $1; }
| ID -> [{part: yy.id($1), original: $1}]
;
handlebars.js-4.7.2/src/parser-prefix.js 0000664 0000000 0000000 00000000077 13607154272 0020171 0 ustar 00root root 0000000 0000000 // File ignored in coverage tests via setting in .istanbul.yml
handlebars.js-4.7.2/src/parser-suffix.js 0000664 0000000 0000000 00000000033 13607154272 0020170 0 ustar 00root root 0000000 0000000 export default handlebars;
handlebars.js-4.7.2/tasks/ 0000775 0000000 0000000 00000000000 13607154272 0015376 5 ustar 00root root 0000000 0000000 handlebars.js-4.7.2/tasks/.eslintrc.js 0000664 0000000 0000000 00000000457 13607154272 0017643 0 ustar 00root root 0000000 0000000 module.exports = {
extends: ['../.eslintrc.js'],
parserOptions: {
sourceType: 'module',
ecmaVersion: 2017,
ecmaFeatures: {}
},
rules: {
'no-process-env': 'off',
'prefer-const': 'warn',
'compat/compat': 'off',
'dot-notation': ['error', { allowKeywords: true }]
}
};
handlebars.js-4.7.2/tasks/metrics.js 0000664 0000000 0000000 00000001347 13607154272 0017407 0 ustar 00root root 0000000 0000000 const metrics = require('../bench');
const { createRegisterAsyncTaskFn } = require('./util/async-grunt-task');
module.exports = function(grunt) {
const registerAsyncTask = createRegisterAsyncTaskFn(grunt);
registerAsyncTask('metrics', function() {
const onlyExecuteName = grunt.option('name');
const events = {};
const promises = Object.keys(metrics).map(async name => {
if (/^_/.test(name)) {
return;
}
if (onlyExecuteName != null && name !== onlyExecuteName) {
return;
}
return new Promise(resolve => {
metrics[name](grunt, function(data) {
events[name] = data;
resolve();
});
});
});
return Promise.all(promises);
});
};
handlebars.js-4.7.2/tasks/parser.js 0000664 0000000 0000000 00000001676 13607154272 0017242 0 ustar 00root root 0000000 0000000 const { execFileWithInheritedOutput } = require('./util/exec-file');
const { createRegisterAsyncTaskFn } = require('./util/async-grunt-task');
const OUTPUT_FILE = 'lib/handlebars/compiler/parser.js';
module.exports = function(grunt) {
const registerAsyncTask = createRegisterAsyncTaskFn(grunt);
registerAsyncTask('parser', async () => {
await runJison();
combineWithPrefixAndSuffix();
grunt.log.writeln(`Parser "${OUTPUT_FILE}" created.`);
});
async function runJison() {
await execFileWithInheritedOutput('jison', [
'-m',
'js',
'src/handlebars.yy',
'src/handlebars.l'
]);
}
function combineWithPrefixAndSuffix() {
const combinedParserSourceCode =
grunt.file.read('src/parser-prefix.js') +
grunt.file.read('handlebars.js') +
grunt.file.read('src/parser-suffix.js');
grunt.file.write(OUTPUT_FILE, combinedParserSourceCode);
grunt.file.delete('handlebars.js');
}
};
handlebars.js-4.7.2/tasks/publish-to-aws.js 0000664 0000000 0000000 00000005423 13607154272 0020616 0 ustar 00root root 0000000 0000000 const AWS = require('aws-sdk');
const git = require('./util/git');
const { createRegisterAsyncTaskFn } = require('./util/async-grunt-task');
const semver = require('semver');
module.exports = function(grunt) {
const registerAsyncTask = createRegisterAsyncTaskFn(grunt);
registerAsyncTask('publish-to-aws', async () => {
grunt.log.writeln('remotes: ' + (await git.remotes()));
grunt.log.writeln('branches: ' + (await git.branches()));
const commitInfo = await git.commitInfo();
grunt.log.writeln('tag: ', commitInfo.tagName);
const suffixes = [];
// Publish the master as "latest" and with the commit-id
if (commitInfo.isMaster) {
suffixes.push('-latest');
suffixes.push('-' + commitInfo.headSha);
}
// Publish tags by their tag-name
if (commitInfo.tagName != null && semver.valid(commitInfo.tagName)) {
suffixes.push('-' + commitInfo.tagName);
}
if (suffixes.length > 0) {
initSDK();
grunt.log.writeln(
'publishing file-suffixes: ' + JSON.stringify(suffixes)
);
await publish(suffixes);
}
});
function initSDK() {
const bucket = process.env.S3_BUCKET_NAME,
key = process.env.S3_ACCESS_KEY_ID,
secret = process.env.S3_SECRET_ACCESS_KEY;
if (!bucket || !key || !secret) {
throw new Error('Missing S3 config values');
}
AWS.config.update({ accessKeyId: key, secretAccessKey: secret });
}
async function publish(suffixes) {
const publishPromises = suffixes.map(suffix => publishSuffix(suffix));
return Promise.all(publishPromises);
}
async function publishSuffix(suffix) {
const filenames = [
'handlebars.js',
'handlebars.min.js',
'handlebars.runtime.js',
'handlebars.runtime.min.js'
];
const publishPromises = filenames.map(filename => {
const nameInBucket = getNameInBucket(filename, suffix);
const localFile = getLocalFile(filename);
uploadToBucket(localFile, nameInBucket);
grunt.log.writeln(
`Published ${localFile} to build server (${nameInBucket})`
);
});
return Promise.all(publishPromises);
}
async function uploadToBucket(localFile, nameInBucket) {
const bucket = process.env.S3_BUCKET_NAME;
const uploadParams = {
Bucket: bucket,
Key: nameInBucket,
Body: grunt.file.read(localFile)
};
return s3PutObject(uploadParams);
}
};
function s3PutObject(uploadParams) {
const s3 = new AWS.S3();
return new Promise((resolve, reject) => {
s3.putObject(uploadParams, err => {
if (err != null) {
return reject(err);
}
resolve();
});
});
}
function getNameInBucket(filename, suffix) {
return filename.replace(/\.js$/, suffix + '.js');
}
function getLocalFile(filename) {
return 'dist/' + filename;
}
handlebars.js-4.7.2/tasks/task-tests/ 0000775 0000000 0000000 00000000000 13607154272 0017500 5 ustar 00root root 0000000 0000000 handlebars.js-4.7.2/tasks/task-tests/.eslintrc.js 0000664 0000000 0000000 00000000202 13607154272 0021731 0 ustar 00root root 0000000 0000000 module.exports = {
extends: '../../.eslintrc.js',
env: {
mocha: true
},
parserOptions: {
ecmaVersion: 2018
}
};
handlebars.js-4.7.2/tasks/task-tests/README.md 0000664 0000000 0000000 00000000060 13607154272 0020753 0 ustar 00root root 0000000 0000000 Use `mocha tasks/task-tests` to run these tests
handlebars.js-4.7.2/tasks/task-tests/git.test.js 0000664 0000000 0000000 00000007645 13607154272 0021613 0 ustar 00root root 0000000 0000000 const os = require('os');
const path = require('path');
const fs = require('fs-extra');
const chai = require('chai');
chai.use(require('dirty-chai'));
const git = require('../util/git');
const expect = chai.expect;
const tmpBaseDir = path.join(os.tmpdir(), 'handlebars-task-tests');
const tmpDir = path.join(tmpBaseDir, Date.now().toString(36));
const remoteDir = path.join(tmpDir, 'remote-repo');
const cloneDir = path.join(tmpDir, 'clone-repo');
const oldCwd = process.cwd();
describe('utils/git', function() {
beforeEach(async function() {
await fs.remove(tmpDir);
await createRepositoryThatActsAsRemote();
process.chdir(tmpDir);
await git.git('clone', 'remote-repo', 'clone-repo');
process.chdir(cloneDir);
});
async function createRepositoryThatActsAsRemote() {
await fs.mkdirp(remoteDir);
process.chdir(remoteDir);
await git.git('init');
await fs.writeFile('testfile.txt', 'Testfile');
await git.add('testfile.txt');
await git.commit('commit message');
}
afterEach(function() {
process.chdir(oldCwd);
});
describe('the "remotes"-function', function() {
it('should list all remotes', async function() {
await git.git('remote', 'set-url', 'origin', 'https://test.org/test');
await git.git('remote', 'add', 'second-remote', 'https://test.org/test2');
const result = await git.remotes();
expect(result.trim().split('\n')).to.deep.equal([
'origin\thttps://test.org/test (fetch)',
'origin\thttps://test.org/test (push)',
'second-remote\thttps://test.org/test2 (fetch)',
'second-remote\thttps://test.org/test2 (push)'
]);
});
});
describe('the "branches"-function', function() {
it('should list all branches', async function() {
await git.git('branch', 'test');
await git.git('branch', 'test2');
const result = await git.branches();
expect(result.trim().split('\n')).to.deep.equal([
'* master',
' test',
' test2',
' remotes/origin/HEAD -> origin/master',
' remotes/origin/master'
]);
});
});
describe('the "commitInfo"-function', function() {
it('should list head and master sha', async function() {
const result = await git.commitInfo();
expect(result.masterSha).to.equal(result.headSha);
expect(result.masterSha).to.match(/^[0-9a-f]+$/);
expect(result.headSha).to.match(/^[0-9a-f]+$/);
});
it('should have "isMaster=true" if the master branch is checked out', async function() {
const result = await git.commitInfo();
expect(result.isMaster).to.be.true();
});
it('should have "isMaster=true" if the current commit is the last commit of the master branch', async function() {
await git.git('checkout', '-b', 'new-branch');
const result = await git.commitInfo();
expect(result.isMaster).to.be.true();
});
it('should have "isMaster=false" if the current commit is NOT the last commit of the master branch', async function() {
await git.git('checkout', '-b', 'new-branch');
fs.writeFile('new-file.txt', 'new-file');
await git.add('new-file.txt');
await git.commit('added new file');
const result = await git.commitInfo();
expect(result.isMaster).to.be.false();
});
it('should show the current tag', async function() {
await git.git('tag', 'test-tag');
const result = await git.commitInfo();
expect(result.tagName).to.be.equal('test-tag');
});
it('should show a version tag rather than standard tags', async function() {
await git.git('tag', 'test-tag');
await git.git('tag', 'v1.2');
await git.git('tag', 'test-tag2');
const result = await git.commitInfo();
expect(result.tagName).to.be.equal('v1.2');
});
it('should show no tag if there is no tag', async function() {
const result = await git.commitInfo();
expect(result.tagName).to.be.null();
});
});
});
handlebars.js-4.7.2/tasks/task-tests/mocha.opts 0000664 0000000 0000000 00000000000 13607154272 0021464 0 ustar 00root root 0000000 0000000 handlebars.js-4.7.2/tasks/test-bin.js 0000664 0000000 0000000 00000002744 13607154272 0017470 0 ustar 00root root 0000000 0000000 const childProcess = require('child_process');
const fs = require('fs');
const os = require('os');
const path = require('path');
const chai = require('chai');
chai.use(require('chai-diff'));
const expect = chai.expect;
module.exports = function(grunt) {
grunt.registerTask('test:bin', function() {
const stdout = executeBinHandlebars(
'-a',
'spec/artifacts/empty.handlebars'
);
const expectedOutput = fs.readFileSync(
'./spec/expected/empty.amd.js',
'utf-8'
);
const normalizedOutput = normalizeCrlf(stdout);
const normalizedExpectedOutput = normalizeCrlf(expectedOutput);
expect(normalizedOutput).not.to.be.differentFrom(normalizedExpectedOutput);
});
};
// helper functions
function executeBinHandlebars(...args) {
if (os.platform() === 'win32') {
// On Windows, the executable handlebars.js file cannot be run directly
const nodeJs = process.argv[0];
return execFilesSyncUtf8(nodeJs, ['./bin/handlebars'].concat(args));
}
return execFilesSyncUtf8('./bin/handlebars', args);
}
function execFilesSyncUtf8(command, args) {
const env = process.env;
env.PATH = addPathToNodeJs(env.PATH);
return childProcess.execFileSync(command, args, { encoding: 'utf-8', env });
}
function addPathToNodeJs(pathEnvironment) {
return path.dirname(process.argv0) + path.delimiter + pathEnvironment;
}
function normalizeCrlf(string) {
if (typeof string === 'string') {
return string.replace(/\r\n/g, '\n');
}
return string;
}
handlebars.js-4.7.2/tasks/test-mocha.js 0000664 0000000 0000000 00000001257 13607154272 0020005 0 ustar 00root root 0000000 0000000 const { execNodeJsScriptWithInheritedOutput } = require('./util/exec-file');
const { createRegisterAsyncTaskFn } = require('./util/async-grunt-task');
const nodeJs = process.argv0;
module.exports = function(grunt) {
const registerAsyncTask = createRegisterAsyncTaskFn(grunt);
registerAsyncTask('test:mocha', async () =>
execNodeJsScriptWithInheritedOutput('./spec/env/runner')
);
registerAsyncTask('test:cov', async () =>
execNodeJsScriptWithInheritedOutput('node_modules/nyc/bin/nyc', [
nodeJs,
'./spec/env/runner.js'
])
);
registerAsyncTask('test:min', async () =>
execNodeJsScriptWithInheritedOutput('./spec/env/runner', ['--min'])
);
};
handlebars.js-4.7.2/tasks/util/ 0000775 0000000 0000000 00000000000 13607154272 0016353 5 ustar 00root root 0000000 0000000 handlebars.js-4.7.2/tasks/util/async-grunt-task.js 0000664 0000000 0000000 00000000512 13607154272 0022121 0 ustar 00root root 0000000 0000000 module.exports = { createRegisterAsyncTaskFn };
function createRegisterAsyncTaskFn(grunt) {
return function registerAsyncTask(name, asyncFunction) {
grunt.registerTask(name, function() {
asyncFunction()
.catch(error => {
grunt.fatal(error);
})
.finally(this.async());
});
};
}
handlebars.js-4.7.2/tasks/util/exec-file.js 0000664 0000000 0000000 00000002544 13607154272 0020557 0 ustar 00root root 0000000 0000000 const childProcess = require('child_process');
const fs = require('fs');
const path = require('path');
module.exports = {
execNodeJsScriptWithInheritedOutput,
execFileWithInheritedOutput
};
async function execNodeJsScriptWithInheritedOutput(command, args) {
return new Promise((resolve, reject) => {
const child = childProcess.fork(command, args, { stdio: 'inherit' });
child.on('close', code => {
if (code !== 0) {
reject(new Error(`Child process failed with exit-code ${code}`));
}
resolve();
});
});
}
async function execFileWithInheritedOutput(command, args) {
return new Promise((resolve, reject) => {
const resolvedCommand = preferLocalDependencies(command);
const child = childProcess.spawn(resolvedCommand, args, {
stdio: 'inherit'
});
child.on('exit', code => {
if (code !== 0) {
reject(new Error(`Child process failed with exit-code ${code}`));
}
resolve();
});
});
}
function preferLocalDependencies(command) {
const localCandidate = resolveLocalCandidate(command);
if (fs.existsSync(localCandidate)) {
return localCandidate;
}
return command;
}
function resolveLocalCandidate(command) {
if (process.platform === 'win32') {
return path.join('node_modules', '.bin', command + '.cmd');
}
return path.join('node_modules', '.bin', command);
}
handlebars.js-4.7.2/tasks/util/git.js 0000664 0000000 0000000 00000003360 13607154272 0017476 0 ustar 00root root 0000000 0000000 const childProcess = require('child_process');
module.exports = {
async remotes() {
return git('remote', '-v');
},
async branches() {
return git('branch', '-a');
},
async commitInfo() {
const headSha = await getHeadSha();
const masterSha = await getMasterSha();
return {
headSha,
masterSha,
tagName: await getTagName(),
isMaster: headSha === masterSha
};
},
async add(path) {
return git('add', '-f', path);
},
async commit(message) {
return git('commit', '--message', message);
},
git // visible for testing
};
async function getHeadSha() {
const stdout = await git('rev-parse', '--short', 'HEAD');
return stdout.trim();
}
async function getMasterSha() {
try {
const stdout = await git('rev-parse', '--short', 'origin/master');
return stdout.trim();
} catch (error) {
if (/Needed a single revision/.test(error.message)) {
// Master was not checked out but in this case, so we know we are not master. We can ignore this
return '';
}
throw error;
}
}
async function getTagName() {
const stdout = await git('tag', '-l', '--points-at', 'HEAD');
const trimmedStdout = stdout.trim();
if (trimmedStdout === '') {
return null; // there is no tag
}
const tags = trimmedStdout.split(/\n|\r\n/);
const versionTags = tags.filter(tag => /^v/.test(tag));
if (versionTags[0] != null) {
return versionTags[0];
}
return tags[0];
}
async function git(...args) {
return new Promise((resolve, reject) =>
childProcess.execFile('git', args, (err, stdout) => {
if (err != null) {
return reject(
new Error(`"git ${args.join(' ')}" caused error: ${err.message}`)
);
}
resolve(stdout);
})
);
}
handlebars.js-4.7.2/tasks/version.js 0000664 0000000 0000000 00000003203 13607154272 0017417 0 ustar 00root root 0000000 0000000 const git = require('./util/git');
const semver = require('semver');
const { createRegisterAsyncTaskFn } = require('./util/async-grunt-task');
module.exports = function(grunt) {
const registerAsyncTask = createRegisterAsyncTaskFn(grunt);
registerAsyncTask('version', async () => {
const pkg = grunt.config('pkg');
const version = grunt.option('ver');
if (!semver.valid(version)) {
throw new Error(
'Must provide a version number (Ex: --ver=1.0.0):\n\t' +
version +
'\n\n'
);
}
pkg.version = version;
grunt.config('pkg', pkg);
const replaceSpec = [
{
path: 'lib/handlebars/base.js',
regex: /const VERSION = ['"](.*)['"];/,
replacement: `const VERSION = '${version}';`
},
{
path: 'components/bower.json',
regex: /"version":.*/,
replacement: `"version": "${version}",`
},
{
path: 'components/package.json',
regex: /"version":.*/,
replacement: `"version": "${version}",`
},
{
path: 'components/handlebars.js.nuspec',
regex: /.*<\/version>/,
replacement: `${version}`
}
];
await Promise.all(
replaceSpec.map(replaceSpec =>
replaceAndAdd(
replaceSpec.path,
replaceSpec.regex,
replaceSpec.replacement
)
)
);
grunt.task.run(['default']);
});
async function replaceAndAdd(path, regex, value) {
let content = grunt.file.read(path);
content = content.replace(regex, value);
grunt.file.write(path, content);
await git.add(path);
}
};
handlebars.js-4.7.2/types/ 0000775 0000000 0000000 00000000000 13607154272 0015415 5 ustar 00root root 0000000 0000000 handlebars.js-4.7.2/types/index.d.ts 0000664 0000000 0000000 00000031031 13607154272 0017314 0 ustar 00root root 0000000 0000000 /* These definitions were imported from https://github.com/DefinitelyTyped/DefinitelyTyped
* and includes previous contributions from the DefinitelyTyped community by:
* - Albert Willemsen
* - Boris Yankov
* - Jessica Franco
* - Masahiro Wakame
* - Raanan Weber
* - Sergei Dorogin
* - webbiesdk
* - Andrew Leedham
* - Nils Knappmeier
* For full history prior to their migration to handlebars.js, please see:
* https://github.com/DefinitelyTyped/DefinitelyTyped/commits/1ce60bdc07f10e0b076778c6c953271c072bc894/types/handlebars/index.d.ts
*/
// TypeScript Version: 2.3
declare namespace Handlebars {
export interface TemplateDelegate {
(context: T, options?: RuntimeOptions): string;
}
export type Template = TemplateDelegate|string;
export interface RuntimeOptions {
partial?: boolean;
depths?: any[];
helpers?: { [name: string]: Function };
partials?: { [name: string]: HandlebarsTemplateDelegate };
decorators?: { [name: string]: Function };
data?: any;
blockParams?: any[];
allowCallsToHelperMissing?: boolean;
allowedProtoProperties?: { [name: string]: boolean };
allowedProtoMethods?: { [name: string]: boolean };
allowProtoPropertiesByDefault?: boolean;
allowProtoMethodsByDefault?: boolean;
}
export interface HelperOptions {
fn: TemplateDelegate;
inverse: TemplateDelegate;
hash: any;
data?: any;
}
export interface HelperDelegate {
(context?: any, arg1?: any, arg2?: any, arg3?: any, arg4?: any, arg5?: any, options?: HelperOptions): any;
}
export interface HelperDeclareSpec {
[key: string]: HelperDelegate;
}
export interface ParseOptions {
srcName?: string;
ignoreStandalone?: boolean;
}
export function registerHelper(name: string, fn: HelperDelegate): void;
export function registerHelper(name: HelperDeclareSpec): void;
export function unregisterHelper(name: string): void;
export function registerPartial(name: string, fn: Template): void;
export function registerPartial(spec: { [name: string]: HandlebarsTemplateDelegate }): void;
export function unregisterPartial(name: string): void;
// TODO: replace Function with actual signature
export function registerDecorator(name: string, fn: Function): void;
export function unregisterDecorator(name: string): void;
export function K(): void;
export function createFrame(object: any): any;
export function blockParams(obj: any[], ids: any[]): any[];
export function log(level: number, obj: any): void;
export function parse(input: string, options?: ParseOptions): hbs.AST.Program;
export function parseWithoutProcessing(input: string, options?: ParseOptions): hbs.AST.Program;
export function compile(input: any, options?: CompileOptions): HandlebarsTemplateDelegate;
export function precompile(input: any, options?: PrecompileOptions): TemplateSpecification;
export function template(precompilation: TemplateSpecification): HandlebarsTemplateDelegate;
export function create(): typeof Handlebars;
export const escapeExpression: typeof Utils.escapeExpression;
//export const Utils: typeof hbs.Utils;
export const logger: Logger;
export const templates: HandlebarsTemplates;
export const helpers: { [name: string]: HelperDelegate };
export const partials: { [name: string]: any };
// TODO: replace Function with actual signature
export const decorators: { [name: string]: Function };
export function noConflict(): typeof Handlebars;
export class Exception {
constructor(message: string, node?: hbs.AST.Node);
description: string;
fileName: string;
lineNumber?: any;
endLineNumber?: any;
message: string;
name: string;
number: number;
stack?: string;
column?: any;
endColumn?: any;
}
export class SafeString {
constructor(str: string);
toString(): string;
toHTML(): string;
}
export namespace Utils {
export function escapeExpression(str: string): string;
export function createFrame(object: any): any;
export function blockParams(obj: any[], ids: any[]): any[];
export function isEmpty(obj: any) : boolean;
export function extend(obj: any, ...source: any[]): any;
export function toString(obj: any): string;
export function isArray(obj: any): boolean;
export function isFunction(obj: any): boolean;
}
export namespace AST {
export const helpers: hbs.AST.helpers;
}
interface ICompiler {
accept(node: hbs.AST.Node): void;
Program(program: hbs.AST.Program): void;
BlockStatement(block: hbs.AST.BlockStatement): void;
PartialStatement(partial: hbs.AST.PartialStatement): void;
PartialBlockStatement(partial: hbs.AST.PartialBlockStatement): void;
DecoratorBlock(decorator: hbs.AST.DecoratorBlock): void;
Decorator(decorator: hbs.AST.Decorator): void;
MustacheStatement(mustache: hbs.AST.MustacheStatement): void;
ContentStatement(content: hbs.AST.ContentStatement): void;
CommentStatement(comment?: hbs.AST.CommentStatement): void;
SubExpression(sexpr: hbs.AST.SubExpression): void;
PathExpression(path: hbs.AST.PathExpression): void;
StringLiteral(str: hbs.AST.StringLiteral): void;
NumberLiteral(num: hbs.AST.NumberLiteral): void;
BooleanLiteral(bool: hbs.AST.BooleanLiteral): void;
UndefinedLiteral(): void;
NullLiteral(): void;
Hash(hash: hbs.AST.Hash): void;
}
export class Visitor implements ICompiler {
accept(node: hbs.AST.Node): void;
acceptKey(node: hbs.AST.Node, name: string): void;
acceptArray(arr: hbs.AST.Expression[]): void;
Program(program: hbs.AST.Program): void;
BlockStatement(block: hbs.AST.BlockStatement): void;
PartialStatement(partial: hbs.AST.PartialStatement): void;
PartialBlockStatement(partial: hbs.AST.PartialBlockStatement): void;
DecoratorBlock(decorator: hbs.AST.DecoratorBlock): void;
Decorator(decorator: hbs.AST.Decorator): void;
MustacheStatement(mustache: hbs.AST.MustacheStatement): void;
ContentStatement(content: hbs.AST.ContentStatement): void;
CommentStatement(comment?: hbs.AST.CommentStatement): void;
SubExpression(sexpr: hbs.AST.SubExpression): void;
PathExpression(path: hbs.AST.PathExpression): void;
StringLiteral(str: hbs.AST.StringLiteral): void;
NumberLiteral(num: hbs.AST.NumberLiteral): void;
BooleanLiteral(bool: hbs.AST.BooleanLiteral): void;
UndefinedLiteral(): void;
NullLiteral(): void;
Hash(hash: hbs.AST.Hash): void;
}
export interface ResolvePartialOptions {
name: string;
helpers?: { [name: string]: Function };
partials?: { [name: string]: HandlebarsTemplateDelegate };
decorators?: { [name: string]: Function };
data?: any;
}
export namespace VM {
/**
* @deprecated
*/
export function resolvePartial(partial: HandlebarsTemplateDelegate | undefined, context: any, options: ResolvePartialOptions): HandlebarsTemplateDelegate;
}
}
/**
* Implement this interface on your MVW/MVVM/MVC views such as Backbone.View
**/
interface HandlebarsTemplatable {
template: HandlebarsTemplateDelegate;
}
// NOTE: for backward compatibility of this typing
type HandlebarsTemplateDelegate = Handlebars.TemplateDelegate;
interface HandlebarsTemplates {
[index: string]: HandlebarsTemplateDelegate;
}
interface TemplateSpecification {
}
// for backward compatibility of this typing
type RuntimeOptions = Handlebars.RuntimeOptions;
interface CompileOptions {
data?: boolean;
compat?: boolean;
knownHelpers?: KnownHelpers;
knownHelpersOnly?: boolean;
noEscape?: boolean;
strict?: boolean;
assumeObjects?: boolean;
preventIndent?: boolean;
ignoreStandalone?: boolean;
explicitPartialContext?: boolean;
}
type KnownHelpers = {
[name in BuiltinHelperName | CustomHelperName]: boolean;
};
type BuiltinHelperName =
"helperMissing"|
"blockHelperMissing"|
"each"|
"if"|
"unless"|
"with"|
"log"|
"lookup";
type CustomHelperName = string;
interface PrecompileOptions extends CompileOptions {
srcName?: string;
destName?: string;
}
declare namespace hbs {
// for backward compatibility of this typing
type SafeString = Handlebars.SafeString;
type Utils = typeof Handlebars.Utils;
}
interface Logger {
DEBUG: number;
INFO: number;
WARN: number;
ERROR: number;
level: number;
methodMap: { [level: number]: string };
log(level: number, obj: string): void;
}
type CompilerInfo = [number/* revision */, string /* versions */];
declare namespace hbs {
namespace AST {
interface Node {
type: string;
loc: SourceLocation;
}
interface SourceLocation {
source: string;
start: Position;
end: Position;
}
interface Position {
line: number;
column: number;
}
interface Program extends Node {
body: Statement[];
blockParams: string[];
}
interface Statement extends Node {}
interface MustacheStatement extends Statement {
type: 'MustacheStatement';
path: PathExpression | Literal;
params: Expression[];
hash: Hash;
escaped: boolean;
strip: StripFlags;
}
interface Decorator extends MustacheStatement { }
interface BlockStatement extends Statement {
type: 'BlockStatement';
path: PathExpression;
params: Expression[];
hash: Hash;
program: Program;
inverse: Program;
openStrip: StripFlags;
inverseStrip: StripFlags;
closeStrip: StripFlags;
}
interface DecoratorBlock extends BlockStatement { }
interface PartialStatement extends Statement {
type: 'PartialStatement';
name: PathExpression | SubExpression;
params: Expression[];
hash: Hash;
indent: string;
strip: StripFlags;
}
interface PartialBlockStatement extends Statement {
type: 'PartialBlockStatement';
name: PathExpression | SubExpression;
params: Expression[];
hash: Hash;
program: Program;
openStrip: StripFlags;
closeStrip: StripFlags;
}
interface ContentStatement extends Statement {
type: 'ContentStatement';
value: string;
original: StripFlags;
}
interface CommentStatement extends Statement {
type: 'CommentStatement';
value: string;
strip: StripFlags;
}
interface Expression extends Node {}
interface SubExpression extends Expression {
type: 'SubExpression';
path: PathExpression;
params: Expression[];
hash: Hash;
}
interface PathExpression extends Expression {
type: 'PathExpression';
data: boolean;
depth: number;
parts: string[];
original: string;
}
interface Literal extends Expression {}
interface StringLiteral extends Literal {
type: 'StringLiteral';
value: string;
original: string;
}
interface BooleanLiteral extends Literal {
type: 'BooleanLiteral';
value: boolean;
original: boolean;
}
interface NumberLiteral extends Literal {
type: 'NumberLiteral';
value: number;
original: number;
}
interface UndefinedLiteral extends Literal {
type: 'UndefinedLiteral';
}
interface NullLiteral extends Literal {
type: 'NullLiteral';
}
interface Hash extends Node {
type: 'Hash';
pairs: HashPair[];
}
interface HashPair extends Node {
type: 'HashPair';
key: string;
value: Expression;
}
interface StripFlags {
open: boolean;
close: boolean;
}
interface helpers {
helperExpression(node: Node): boolean;
scopeId(path: PathExpression): boolean;
simpleId(path: PathExpression): boolean;
}
}
}
declare module "handlebars" {
export = Handlebars;
}
declare module "handlebars/runtime" {
export = Handlebars;
}
handlebars.js-4.7.2/types/test.ts 0000664 0000000 0000000 00000020571 13607154272 0016751 0 ustar 00root root 0000000 0000000 /* These test cases were imported from https://github.com/DefinitelyTyped/DefinitelyTyped
* and includes previous contributions from the DefinitelyTyped community.
* For full history prior to their migration to handlebars.js, please see:
* https://github.com/DefinitelyTyped/DefinitelyTyped/commits/1ce60bdc07f10e0b076778c6c953271c072bc894/types/handlebars/handlebars-tests.ts
*/
import * as Handlebars from 'handlebars';
const context = {
author: { firstName: 'Alan', lastName: 'Johnson' },
body: 'I Love Handlebars',
comments: [{
author: { firstName: 'Yehuda', lastName: 'Katz' },
body: 'Me too!'
}]
};
Handlebars.registerHelper('fullName', (person: typeof context.author) => {
return person.firstName + ' ' + person.lastName;
});
Handlebars.registerHelper('agree_button', function(this: any) {
return new Handlebars.SafeString(
''
);
});
const source1 = '
Hello, my name is {{name}}. I am from {{hometown}}. I have ' +
'{{kids.length}} kids: