├── .eslintrc.json ├── .gitattributes ├── .github ├── stale.yml └── workflows │ └── helpers_ci.yml ├── .gitignore ├── .mocharc.json ├── .verb.md ├── CHANGELOG ├── LICENSE ├── README.md ├── docs ├── sections.md └── toc.md ├── gulpfile.js ├── index.js ├── lib ├── array.js ├── code.js ├── collection.js ├── comparison.js ├── fs.js ├── html.js ├── i18n.js ├── index.js ├── inflection.js ├── lorem.js ├── match.js ├── math.js ├── misc.js ├── number.js ├── object.js ├── path.js ├── regex.js ├── string.js ├── url.js ├── utils │ ├── createFrame.js │ ├── falsey.js │ ├── fn.js │ ├── handlebarsUtils.js │ ├── html.js │ ├── identity.js │ ├── index.js │ ├── indexOf.js │ ├── inverse.js │ ├── isBlock.js │ ├── isObject.js │ ├── isOptions.js │ ├── isString.js │ ├── isUndefined.js │ ├── odd.js │ ├── options.js │ ├── result.js │ └── value.js └── uuid.js ├── package.json ├── test ├── array.js ├── code.js ├── collection.js ├── comparison.js ├── examples │ └── index.js ├── expected │ ├── object │ │ └── extend.txt │ └── simple.html ├── fixtures │ ├── assets │ │ ├── js │ │ │ ├── one.js │ │ │ └── two.js │ │ └── styles │ │ │ ├── one.css │ │ │ └── two.css │ ├── embedded.md │ ├── index.html │ ├── object │ │ ├── merge.hbs │ │ └── pick.hbs │ ├── read │ │ └── a.txt │ └── simple.md ├── helpers.js ├── html.js ├── i18n.js ├── inflection.js ├── integration │ └── templates.js ├── match.js ├── math.js ├── misc.js ├── mocha.opts ├── number.js ├── object.js ├── path.js ├── string.js ├── subexpressions.js ├── support │ └── index.js ├── url.js ├── utils.js └── uuid.js ├── verbfile.js └── yarn.lock /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "browser": false, 4 | "es6": true, 5 | "node": true, 6 | "mocha": true 7 | }, 8 | 9 | "globals": { 10 | "document": false, 11 | "navigator": false, 12 | "window": false 13 | }, 14 | 15 | "rules": { 16 | "accessor-pairs": 2, 17 | "arrow-spacing": [2, { "before": true, "after": true }], 18 | "block-spacing": [2, "always"], 19 | "brace-style": [2, "1tbs", { "allowSingleLine": true }], 20 | "comma-dangle": [2, "never"], 21 | "comma-spacing": [2, { "before": false, "after": true }], 22 | "comma-style": [2, "last"], 23 | "constructor-super": 2, 24 | "curly": [2, "multi-line"], 25 | "dot-location": [2, "property"], 26 | "eol-last": 2, 27 | "eqeqeq": [2, "allow-null"], 28 | "generator-star-spacing": [2, { "before": true, "after": true }], 29 | "handle-callback-err": [2, "^(err|error)$" ], 30 | "indent": [2, 2, { "SwitchCase": 1 }], 31 | "key-spacing": [2, { "beforeColon": false, "afterColon": true }], 32 | "keyword-spacing": [2, { "before": true, "after": true }], 33 | "new-cap": [2, { "newIsCap": true, "capIsNew": false }], 34 | "new-parens": 2, 35 | "no-array-constructor": 2, 36 | "no-caller": 2, 37 | "no-class-assign": 2, 38 | "no-cond-assign": 2, 39 | "no-const-assign": 2, 40 | "no-control-regex": 2, 41 | "no-debugger": 2, 42 | "no-delete-var": 2, 43 | "no-dupe-args": 2, 44 | "no-dupe-class-members": 2, 45 | "no-dupe-keys": 2, 46 | "no-duplicate-case": 2, 47 | "no-empty-character-class": 2, 48 | "no-eval": 2, 49 | "no-ex-assign": 2, 50 | "no-extend-native": 2, 51 | "no-extra-bind": 2, 52 | "no-extra-boolean-cast": 2, 53 | "no-extra-parens": [2, "functions"], 54 | "no-fallthrough": 2, 55 | "no-floating-decimal": 2, 56 | "no-func-assign": 2, 57 | "no-implied-eval": 2, 58 | "no-inner-declarations": [2, "functions"], 59 | "no-invalid-regexp": 2, 60 | "no-irregular-whitespace": 2, 61 | "no-iterator": 2, 62 | "no-label-var": 2, 63 | "no-labels": 2, 64 | "no-lone-blocks": 2, 65 | "no-mixed-spaces-and-tabs": 2, 66 | "no-multi-spaces": 2, 67 | "no-multi-str": 2, 68 | "no-multiple-empty-lines": [2, { "max": 1 }], 69 | "no-native-reassign": 0, 70 | "no-negated-in-lhs": 2, 71 | "no-new": 2, 72 | "no-new-func": 2, 73 | "no-new-object": 2, 74 | "no-new-require": 2, 75 | "no-new-wrappers": 2, 76 | "no-obj-calls": 2, 77 | "no-octal": 2, 78 | "no-octal-escape": 2, 79 | "no-proto": 0, 80 | "no-redeclare": 2, 81 | "no-regex-spaces": 2, 82 | "no-return-assign": 2, 83 | "no-self-compare": 2, 84 | "no-sequences": 2, 85 | "no-shadow-restricted-names": 2, 86 | "no-spaced-func": 2, 87 | "no-sparse-arrays": 2, 88 | "no-this-before-super": 2, 89 | "no-throw-literal": 2, 90 | "no-trailing-spaces": 0, 91 | "no-undef": 2, 92 | "no-undef-init": 2, 93 | "no-unexpected-multiline": 2, 94 | "no-unneeded-ternary": [2, { "defaultAssignment": false }], 95 | "no-unreachable": 2, 96 | "no-unused-vars": [2, { "vars": "all", "args": "none" }], 97 | "no-useless-call": 0, 98 | "no-with": 2, 99 | "one-var": [0, { "initialized": "never" }], 100 | "operator-linebreak": [0, "after", { "overrides": { "?": "before", ":": "before" } }], 101 | "padded-blocks": [0, "never"], 102 | "quotes": [2, "single", "avoid-escape"], 103 | "radix": 2, 104 | "semi": [2, "always"], 105 | "semi-spacing": [2, { "before": false, "after": true }], 106 | "space-before-blocks": [2, "always"], 107 | "space-before-function-paren": [2, "never"], 108 | "space-in-parens": [2, "never"], 109 | "space-infix-ops": 2, 110 | "space-unary-ops": [ 111 | 2, { 112 | "words": true, 113 | "nonwords": false, 114 | "overrides": { 115 | "typeof": false 116 | } 117 | } 118 | ], 119 | "spaced-comment": [0, "always", { "markers": ["global", "globals", "eslint", "eslint-disable", "*package", "!", ","] }], 120 | "use-isnan": 2, 121 | "valid-typeof": 2, 122 | "wrap-iife": [2, "any"], 123 | "yoda": [2, "never"] 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Enforce Unix newlines 2 | * text eol=lf 3 | 4 | # binaries 5 | *.ai binary 6 | *.psd binary 7 | *.jpg binary 8 | *.gif binary 9 | *.png binary 10 | *.jpeg binary -------------------------------------------------------------------------------- /.github/stale.yml: -------------------------------------------------------------------------------- 1 | # Number of days of inactivity before an issue becomes stale 2 | daysUntilStale: 60 3 | # Number of days of inactivity before a stale issue is closed 4 | daysUntilClose: 7 5 | # Issues with these labels will never be considered stale 6 | exemptLabels: 7 | - pinned 8 | - security 9 | - roadmap 10 | # Label to use when marking an issue as stale 11 | staleLabel: stale 12 | # Comment to post when marking an issue as stale. Set to `false` to disable 13 | markComment: > 14 | This issue has been automatically marked as stale because it has not had 15 | recent activity. It will be closed if no further activity occurs. Thank you 16 | for your contributions. 17 | # Comment to post when closing a stale issue. Set to `false` to disable 18 | closeComment: false 19 | -------------------------------------------------------------------------------- /.github/workflows/helpers_ci.yml: -------------------------------------------------------------------------------- 1 | name: Handlebars-helpers CI 2 | 3 | on: 4 | # Trigger the workflow on push or pull request, 5 | # but only for the master branch (or main) 6 | push: 7 | branches: 8 | - master 9 | - main 10 | pull_request: 11 | branches: 12 | - master 13 | - main 14 | release: 15 | types: 16 | - created 17 | # Allows you to run this workflow manually from the Actions tab 18 | workflow_dispatch: 19 | 20 | jobs: 21 | build-and-test: 22 | 23 | runs-on: ${{ matrix.os }} 24 | 25 | timeout-minutes: 30 # Just in case something goes realy real realy BAD..... 26 | 27 | strategy: 28 | fail-fast: false 29 | matrix: 30 | # See supported Node.js release schedule at https://nodejs.org/en/about/releases/ 31 | node-version: [18.x, 20.x] 32 | os: [ubuntu-20.04, windows-2019] 33 | 34 | steps: 35 | - name: Checkout GITHub code 36 | uses: actions/checkout@v2 37 | 38 | - name: Use Node.js ${{ matrix.node-version }} 39 | uses: actions/setup-node@v2 40 | with: 41 | node-version: ${{ matrix.node-version }} 42 | 43 | - name: Install dependencies 44 | run: yarn install --frozen-lockfile # will run `yarn install --frozen-lockfile` command 45 | 46 | - name: Lint code 47 | run: yarn lint 48 | 49 | - name: Run tests against code 50 | run: yarn test 51 | env: 52 | CI: true 53 | name: Handlebars-helpers CI 54 | 55 | - name: Publish to NPM only on a relase event 56 | if: github.event_name == 'release' && github.event.action == 'created' 57 | uses: JS-DevTools/npm-publish@v1 58 | with: 59 | token: ${{ secrets.NPM_TOKEN }} 60 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # always ignore files 2 | *.DS_Store 3 | .idea 4 | *.sublime-* 5 | 6 | # test related, or directories generated by tests 7 | test/actual 8 | actual 9 | coverage 10 | .nyc* 11 | 12 | # npm 13 | node_modules 14 | npm-debug.log 15 | 16 | # yarn 17 | yarn-error.log 18 | 19 | # misc 20 | _gh_pages 21 | _draft 22 | _drafts 23 | bower_components 24 | vendor 25 | temp 26 | tmp 27 | TODO.md 28 | package-lock.json -------------------------------------------------------------------------------- /.mocharc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extension": ["js"], 3 | "spec": "test/**/*.js" 4 | } -------------------------------------------------------------------------------- /.verb.md: -------------------------------------------------------------------------------- 1 | ## Usage 2 | 3 | The main export returns a function that needs to be called to expose the object of helpers. 4 | 5 | **Get all helpers** 6 | 7 | ```js 8 | var helpers = require('{%= name %}')(); 9 | //=> returns object with all (130+) helpers 10 | ``` 11 | 12 | **Get a specific helper collection** 13 | 14 | Helper collections are exposed as getters, so only the helpers you want will be required and loaded. 15 | 16 | ```js 17 | var helpers = require('{%= name %}'); 18 | var math = helpers.math(); 19 | //=> only the `math` helpers 20 | 21 | var helpers = require('{%= name %}'); 22 | var array = helpers.array(); 23 | //=> only the `collections` helpers 24 | ``` 25 | 26 | **Get multiple helpers collections** 27 | 28 | Helper collections are exposed as getters, so only the helpers you want will be required and loaded. 29 | 30 | ```js 31 | var helpers = require('{%= name %}')(['math', 'string']); 32 | //=> only the `math` and `string` helpers 33 | ``` 34 | 35 | **Optionally pass your own handlebars** 36 | 37 | ```js 38 | var handlebars = require('handlebars'); 39 | var helpers = require('{%= name %}')({ 40 | handlebars: handlebars 41 | }); 42 | 43 | // or for a specific collection 44 | var math = helpers.math({ 45 | handlebars: handlebars 46 | }); 47 | ``` 48 | 49 | ## Helpers 50 | {%= include("docs/toc.md") %} 51 | 52 | *** 53 | 54 | {%= include("docs/sections.md") %} 55 | 56 | *** 57 | 58 | ## Utils 59 | 60 | The following utils are exposed on `.utils`. 61 | 62 | {%= apidocs('lib/utils/index.js') %} 63 | 64 | *** 65 | 66 | [operators]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Arithmetic_Operators 67 | -------------------------------------------------------------------------------- /CHANGELOG: -------------------------------------------------------------------------------- 1 | v0.11.0: 2 | date: "2023-06-21" 3 | changes: 4 | - removed `times` from math helpers 5 | - removed alias `url_encode` and `url_decode` from URL helpers 6 | - fixed decodeURI example, it was a using escape and had the example for encodeURI 7 | v0.10.0: 8 | date: "2017-11-17" 9 | changes: 10 | - adds `unique` to array helpers 11 | - updates `css` helper to ensure that path.join() is not called on an absolute URL. 12 | v0.9.0: 13 | date: "2017-07-03" 14 | changes: 15 | - all unit tests now use assert instead of should 16 | - remove `fileSize` helper in favor of new `bytes` helper, which does the same thing, but returns `B` instead of `byte` or `bytes`. 17 | - JSONParse helper is no longer a block helper. It now returns an object, which can be used as a subexpression to achieve the same behavior as before. 18 | - adds better error handling for path helpers, since node.js errors are terrible. We have a better way to handle errors that will be implemented in a near future release. 19 | - adds inline helper support to `isEmpty`, so it can now be used as an inline or block helper 20 | - adds `raw` helper 21 | - adds regex helpers 22 | - adds inline helper support to most of the comparison helpers, so they can now be used as inline or block helpers 23 | - adds `pluck` helper to array helpers 24 | - adds `prepend` and `append` helpers to string helpers 25 | - adds `isTruthy` and `isFalsey` comparison helpers 26 | - adds `escape` and `url_encode` and `url_decode` URL helpers 27 | - adds `attr` helper to html helpers 28 | - adds `year` helper to date helpers 29 | - adds `typeOf` and `frame` helpers to misc helpers 30 | - adds `abs`, `minus`, `modulo`, `plus`, `times` to math helpers 31 | - moves `ellipsis` helper from `html` helpers to string helpers 32 | - moves `truncate` helper from `html` helpers to string helpers 33 | - moves `reverse` helper from `string` helpers to array helpers 34 | - differentiate `eq` and `is` helpers so that `eq` is strict equality and `is` is not 35 | - removes `mm` helper, use `match` instead 36 | v0.8.4: 37 | date: "2017-07-03" 38 | changes: 39 | - removes strlen helper in favor of fixing the length helper 40 | v0.8.3: 41 | date: "2017-07-03" 42 | changes: 43 | - adds strlen helper 44 | - adds itemAt helper 45 | - clean up code comments for array helpers 46 | v0.8.2: 47 | date: "2017-03-30" 48 | changes: 49 | - documentation updates 50 | - fixes md helper to use sync by default 51 | v0.8.1: 52 | date: "2017-03-30" 53 | changes: 54 | - fixes sorting in withSort helper. see https://github.com/helpers/handlebars-helpers/pull/245 55 | - adds toPath helper 56 | - handle null inputs in number helpers 57 | - adds stripProtocol helper 58 | v0.8.0: 59 | date: "2017-01-25" 60 | changes: 61 | - handle string arguments in list helpers 62 | - adds JSONParse helper as an alias for parseJSON 63 | v0.7.6: 64 | date: "2017-01-08" 65 | changes: 66 | - fixes markdown helpers. see https://github.com/helpers/handlebars-helpers/pull/226 67 | - documentation improvements and other minor fixes 68 | v0.7.0: 69 | date: "2016-07-16" 70 | changes: 71 | - The [or](#or) helper can now take a variable number of arguments 72 | v0.6.0: 73 | date: "2016-05-13" 74 | changes: 75 | - the main export is now a function that takes a name or array of names of helper types to load. Example `helpers(['string', 'array'])` will load only the `string` and `array` helpers 76 | - helper types can alternatively be accessed as methods. example - `helpers.path()` will return all of the path helpers. 77 | - handlebars may be provided by the user. if not provided it will fall back to the `handlebars-helpers` handlebars 78 | - helpers are now as generic as possible, with little to no code related to assemble, grunt, etc. 79 | - helpers are lazy-loaded using getters for improved performance 80 | - Once tests are added for the `md` and `markdown` helpers, we'll have 100% unit test coverage on helpers 81 | v0.3.3: 82 | date: "2013-09-03" 83 | changes: 84 | - Adds fileSize helper. 85 | - Adds startsWith helper. 86 | v0.3.2: 87 | date: "2013-08-20" 88 | changes: 89 | - Adds glob helper. 90 | v0.3.0: 91 | date: "2013-07-30" 92 | changes: 93 | - The project has been refactored, cleaned up, and full documentation has bee put up at http://assemble.io 94 | v0.2.4: 95 | date: "2013-05-11" 96 | changes: 97 | - Adding object globbing utility functions to be used in helpers later. 98 | v0.2.3: 99 | date: "2013-05-11" 100 | changes: 101 | - File globbing added to some helpers. Including md and some file helpers. 102 | v0.2.0: 103 | date: "2013-05-07" 104 | changes: 105 | - A bunch of new tests for markdown and special helpers. 106 | - Refactored most of the rest of the helpers to separate functions from Handlebars registration. 107 | v0.1.32: 108 | date: "2013-05-02" 109 | changes: 110 | - Updates utils and a number of helpers, including value, property, and stringify. 111 | v0.1.31: 112 | date: "2013-04-21" 113 | changes: 114 | - Fixes relative helper 115 | v0.1.30: 116 | date: "2013-04-20" 117 | changes: 118 | - Refactoring helpers-collection module to separate the functions from the Handlebars helper registration process. 119 | v0.1.25: 120 | date: "2013-04-16" 121 | changes: 122 | - Adding defineSection and renderSection helpers to try to get sections populated in a layout from the page. 123 | v0.1.21: 124 | date: "2013-04-07" 125 | changes: 126 | - Add markdown helpers back, add more tests. 127 | v0.1.20: 128 | date: "2013-04-06" 129 | changes: 130 | - Generalized helpers structure, externalized utilities. 131 | v0.1.11: 132 | date: "2013-04-05" 133 | changes: 134 | - New authors and gist helpers, general cleanup and new tests. 135 | v0.1.10: 136 | date: "2013-04-04" 137 | changes: 138 | - Externalized utility javascript from helpers.js 139 | v0.1.8: 140 | date: "2013-03-28" 141 | changes: 142 | - "Gruntfile updated with mocha tests for 71 helpers, bug fixes." 143 | v0.1.7: 144 | date: "2013-03-18" 145 | changes: 146 | - "New path helper 'relative', for resolving relative path from one absolute path to another." 147 | v0.1.3: 148 | date: "2013-03-16" 149 | changes: 150 | - "New helpers, 'formatPhoneNumber' and 'eachProperty'" 151 | v0.1.2: 152 | date: "2013-03-15" 153 | changes: 154 | - "Update README.md with documentation, examples." 155 | v0.1.0: 156 | date: "2013-03-06" 157 | changes: 158 | - "First commit." 159 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2013-2015, 2017, Jon Schlinkert, Brian Woodward. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /docs/sections.md: -------------------------------------------------------------------------------- 1 | {% Object.keys(methods).forEach(function(key) { %} 2 | ## {%= key %} 3 | {%= apidocs("lib/" + key + ".js") %} 4 | {% }) %} 5 | -------------------------------------------------------------------------------- /docs/toc.md: -------------------------------------------------------------------------------- 1 | ## Categories 2 | 3 | Currently **{%= total.helpers %} helpers** in **{%= total.categories %} categories**: 4 | 5 | {% Object.keys(methods).forEach(function(key) { %}{% var file = methods[key] %} 6 | - **{%= anchor(file) %}** ({%= link("code", file.path) %} | {%= link("unit tests", file.test.path) %}) {% }) %} 7 | 8 | ## All helpers 9 | 10 | {% Object.keys(methods).forEach(function(key) { %} 11 | {% var file = methods[key] %} 12 | 13 | ### {%= link(key + ' helpers', '#' + file.stem) %} 14 | Visit the: {%= link("code", file.path) %} | {%= link("unit tests", file.test.path) %} | {%= sectionIssue(file.stem) %}) 15 | {% Object.keys(file.data.methods).forEach(function(k) { %}{% var obj = file.data.methods[k] %} 16 | {%= bullet(obj) %} ({%= codeLink(obj) %} | {%= testLink(obj) %}){% }) %} 17 | {% }) %} -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var gulp = require('gulp'); 4 | var unused = require('gulp-unused'); 5 | 6 | gulp.task('unused', function() { 7 | var utils = require('./lib/utils'); 8 | return gulp.src(['index.js', 'lib/**/*.js']) 9 | .pipe(unused({keys: Object.keys(utils)})); 10 | }); 11 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * handlebars-helpers 3 | * 4 | * Copyright (c) 2013-2017, Jon Schlinkert, Brian Woodward. 5 | * Released under the MIT License. 6 | */ 7 | 8 | 'use strict'; 9 | 10 | var lib = require('./lib/'); 11 | 12 | /** 13 | * Expose helpers 14 | */ 15 | 16 | module.exports = function helpers(groups, options) { 17 | if (typeof groups === 'string') { 18 | groups = [groups]; 19 | } else if (!Array.isArray(groups)) { 20 | options = groups; 21 | groups = null; 22 | } 23 | 24 | options = options || {}; 25 | var hbs = options.handlebars || options.hbs || require('handlebars'); 26 | module.exports.handlebars = hbs; 27 | 28 | if (groups) { 29 | groups.forEach(function(key) { 30 | hbs.registerHelper(lib[key]); 31 | }); 32 | } else { 33 | for (const key in lib) { 34 | const group = lib[key]; 35 | hbs.registerHelper(group); 36 | } 37 | } 38 | 39 | return hbs.helpers; 40 | }; 41 | 42 | /** 43 | * Expose helper groups 44 | */ 45 | for (const key in lib) { 46 | const group = lib[key]; 47 | 48 | module.exports[key] = function(options) { 49 | options = options || {}; 50 | var hbs = options.handlebars || options.hbs || require('handlebars'); 51 | module.exports.handlebars = hbs; 52 | hbs.registerHelper(group); 53 | return group; 54 | }; 55 | } 56 | 57 | /** 58 | * Expose `utils` 59 | */ 60 | 61 | module.exports.utils = require('./lib/utils'); 62 | -------------------------------------------------------------------------------- /lib/code.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var fs = require('fs'); 4 | var path = require('path'); 5 | const codeBlock = require('to-gfm-code-block'); 6 | const htmlTag = require('html-tag'); 7 | var helpers = module.exports; 8 | 9 | /** 10 | * Embed code from an external file as preformatted text. 11 | * 12 | * ```handlebars 13 | * {{embed 'path/to/file.js'}} 14 | * 15 | * {{embed 'path/to/file.hbs' 'html')}} 16 | * ``` 17 | * 18 | * @param {String} `filepath` filepath to the file to embed. 19 | * @param {String} `language` Optionally specify the language to use for syntax highlighting. 20 | * @return {String} 21 | * @api public 22 | */ 23 | 24 | helpers.embed = function embed(filepath, ext) { 25 | ext = typeof ext !== 'string' ? path.extname(filepath).slice(1) : ext; 26 | var code = fs.readFileSync(filepath, 'utf8'); 27 | if (ext === 'markdown' || ext === 'md') { 28 | ext = 'markdown'; 29 | // if the string is markdown, escape backticks 30 | code = code.split('`').join('`'); 31 | } 32 | return codeBlock(code, ext).trim() + '\n'; 33 | }; 34 | 35 | /** 36 | * Embed a GitHub Gist using only the id of the Gist 37 | * 38 | * ```handlebars 39 | * {{gist '12345'}} 40 | * ``` 41 | * @param {String} `id` 42 | * @return {String} 43 | * @api public 44 | */ 45 | 46 | helpers.gist = function(id) { 47 | return htmlTag('script', {src: 'https://gist.github.com/' + id + '.js'}); 48 | }; 49 | 50 | /** 51 | * Generate the HTML for a jsFiddle link with the given `params` 52 | * 53 | * ```handlebars 54 | * {{jsfiddle id='0dfk10ks' tabs='true'}} 55 | * ``` 56 | * @param {Object} `params` 57 | * @return {String} 58 | * @api public 59 | */ 60 | 61 | helpers.jsfiddle = function jsFiddle(options) { 62 | var attr = Object.assign({}, options && options.hash); 63 | 64 | if (typeof attr.id === 'undefined') { 65 | throw new Error('jsfiddle helper expects an `id`'); 66 | } 67 | 68 | attr.id = 'http://jsfiddle.net/' + attr.id; 69 | attr.width = attr.width || '100%'; 70 | attr.height = attr.height || '300'; 71 | attr.skin = attr.skin || '/presentation/'; 72 | attr.tabs = (attr.tabs || 'result,js,html,css') + attr.skin; 73 | attr.src = attr.id + '/embedded/' + attr.tabs; 74 | attr.allowfullscreen = attr.allowfullscreen || 'allowfullscreen'; 75 | attr.frameborder = attr.frameborder || '0'; 76 | 77 | delete attr.tabs; 78 | delete attr.skin; 79 | delete attr.id; 80 | return htmlTag('iframe', attr); 81 | }; 82 | -------------------------------------------------------------------------------- /lib/collection.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var util = require('./utils/handlebarsUtils'); 4 | var object = require('./object'); 5 | var array = require('./array'); 6 | var forEach = array.forEach; 7 | var forOwn = object.forOwn; 8 | var helpers = module.exports; 9 | 10 | /** 11 | * Inline, subexpression, or block helper that returns true (or the block) 12 | * if the given collection is empty, or false (or the inverse block, if 13 | * supplied) if the colleciton is not empty. 14 | * 15 | * ```handlebars 16 | * 17 | * {{#isEmpty array}}AAA{{else}}BBB{{/isEmpty}} 18 | * 19 | * 20 | * 21 | * {{isEmpty array}} 22 | * 23 | * ``` 24 | * @param {Object} `collection` 25 | * @param {Object} `options` 26 | * @return {String} 27 | * @block 28 | * @api public 29 | */ 30 | 31 | helpers.isEmpty = function(collection, options) { 32 | if (!util.isOptions(options)) { 33 | options = collection; 34 | return util.fn(true, this, options); 35 | } 36 | 37 | if (Array.isArray(collection) && !collection.length) { 38 | return util.fn(true, this, options); 39 | } 40 | 41 | var keys = Object.keys(collection); 42 | var isEmpty = typeof collection === 'object' && !keys.length; 43 | return util.value(isEmpty, this, options); 44 | }; 45 | 46 | /** 47 | * Block helper that iterates over an array or object. If 48 | * an array is given, `.forEach` is called, or if an object 49 | * is given, `.forOwn` is called, otherwise the inverse block 50 | * is returned. 51 | * 52 | * @param {Object|Array} `collection` The collection to iterate over 53 | * @param {Object} `options` 54 | * @return {String} 55 | * @block 56 | * @api public 57 | */ 58 | 59 | helpers.iterate = function(collection, options) { 60 | if (Array.isArray(collection)) { 61 | return forEach.apply(null, arguments); 62 | } 63 | if (util.isObject(collection)) { 64 | return forOwn.apply(null, arguments); 65 | } 66 | return options.inverse(this); 67 | }; 68 | -------------------------------------------------------------------------------- /lib/fs.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var fs = require('fs'); 4 | var path = require('path'); 5 | var util = require('./utils/handlebarsUtils'); 6 | var number = require('./number'); 7 | var helpers = module.exports; 8 | const kindOf = require('kind-of'); 9 | const isGlob = require('is-glob'); 10 | const micromatch = require('micromatch'); 11 | 12 | /** 13 | * Helper `fileSize` is deprecated. Use `helper.prettyBytes` instead. 14 | */ 15 | 16 | helpers.fileSize = number.bytes; 17 | 18 | /** 19 | * Read a file from the file system. This is useful in composing 20 | * 'include'-style helpers using sub-expressions. 21 | * 22 | * ```handlebars 23 | * {{read 'a/b/c.js'}} 24 | * {{someHelper (read 'a/b/c.md')}} 25 | * ``` 26 | * @param {String} `filepath` 27 | * @return {String} 28 | * @api public 29 | */ 30 | 31 | helpers.read = function(filepath, options) { 32 | return fs.readFileSync(filepath, 'utf8'); 33 | }; 34 | 35 | /** 36 | * Return an array of files from the given 37 | * directory. 38 | * 39 | * @param {String} `directory` 40 | * @return {Array} 41 | * @api public 42 | */ 43 | 44 | helpers.readdir = function(dir, filter) { 45 | var files = fs.readdirSync(dir); 46 | files = files.map(function(fp) { 47 | return path.join(dir, fp); 48 | }); 49 | if (util.isOptions(filter)) { 50 | return files; 51 | } 52 | if (typeof filter === 'function') { 53 | return filter(files); 54 | } 55 | if (kindOf(filter) === 'regexp') { 56 | return files.filter(function(fp) { 57 | return filter.test(fp); 58 | }); 59 | } 60 | if (isGlob(filter)) { 61 | return files.filter(micromatch.matcher(filter)); 62 | } 63 | if (['isFile', 'isDirectory'].indexOf(filter) !== -1) { 64 | return files.filter(function(fp) { 65 | var stat = fs.statSync(fp); 66 | return stat[filter](); 67 | }); 68 | } 69 | return files; 70 | }; 71 | -------------------------------------------------------------------------------- /lib/html.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var path = require('path'); 4 | var util = require('./utils/handlebarsUtils'); 5 | var html = require('./utils/html'); 6 | var parseAttr = html.parseAttributes; 7 | var helpers = module.exports; 8 | const htmlTag = require('html-tag'); 9 | 10 | /** 11 | * Stringify attributes on the options `hash`. 12 | * 13 | * ```handlebars 14 | * 15 | * 16 | * 34 | * {{css stylesheets}} 35 | * 36 | * 37 | * 38 | * 39 | * ``` 40 | * @param {String|Array} `list` One or more stylesheet urls. 41 | * @return {String} 42 | * @api public 43 | */ 44 | 45 | helpers.css = function(list, options) { 46 | if (arguments.length < 2) { 47 | options = list; 48 | list = []; 49 | } 50 | 51 | var styles = util.arrayify(list); 52 | var assets = ''; 53 | 54 | if (this && this.options) { 55 | assets = this.options.assets || ''; 56 | } 57 | 58 | if (options.hash.href) { 59 | styles = util.arrayify(options.hash.href); 60 | } 61 | 62 | return styles.map(function(item) { 63 | var ext = path.extname(item); 64 | var fp = item; 65 | 66 | if (!/(^\/\/)|(:\/\/)/.test(item)) { 67 | fp = path.posix.join(assets, item); 68 | } 69 | 70 | if (ext === '.less') { 71 | return ``; 72 | } 73 | return ``; 74 | }).join('\n'); 75 | }; 76 | 77 | /** 78 | * Generate one or more `` tags with paths/urls to 79 | * javascript or coffeescript files. 80 | * 81 | * ```handlebars 82 | * {{js scripts}} 83 | * ``` 84 | * @param {Object} `context` 85 | * @return {String} 86 | * @api public 87 | */ 88 | 89 | helpers.js = function(context) { 90 | if (typeof context === 'object' && context.hash) { 91 | var attr = parseAttr(context.hash); 92 | return ``; 93 | } 94 | 95 | if (typeof context === 'string') { 96 | return ``; 97 | } 98 | 99 | context = util.arrayify(context); 100 | return context.map(function(fp) { 101 | return (path.extname(fp) === '.coffee') 102 | ? htmlTag('script', { type: 'text/coffeescript', src: fp }) 103 | : htmlTag('script', { src: fp }); 104 | }).join('\n'); 105 | }; 106 | 107 | /** 108 | * Strip HTML tags from a string, so that only the text nodes 109 | * are preserved. 110 | * 111 | * ```handlebars 112 | * {{sanitize "foo"}} 113 | * 114 | * ``` 115 | * 116 | * @param {String} `str` The string of HTML to sanitize. 117 | * @return {String} 118 | * @api public 119 | */ 120 | 121 | helpers.sanitize = function(str) { 122 | return html.sanitize(str); 123 | }; 124 | 125 | /** 126 | * Block helper for creating unordered lists (`
    `) 127 | * 128 | * @param {Object} `context` 129 | * @param {Object} `options` 130 | * @return {String} 131 | * @block 132 | * @api public 133 | */ 134 | 135 | helpers.ul = function(context, options) { 136 | return ('
      ') + context.map(function(item) { 137 | if (typeof item !== 'string') { 138 | item = options.fn(item); 139 | } 140 | return '
    • ' + item + '
    • '; 141 | }).join('\n') + '
    '; 142 | }; 143 | 144 | /** 145 | * Block helper for creating ordered lists (`
      `) 146 | * 147 | * @param {Object} `context` 148 | * @param {Object} `options` 149 | * @return {String} 150 | * @block 151 | * @api public 152 | */ 153 | 154 | helpers.ol = function(context, options) { 155 | return ('
        ') + context.map(function(item) { 156 | if (typeof item !== 'string') { 157 | item = options.fn(item); 158 | } 159 | return '
      1. ' + item + '
      2. '; 160 | }).join('\n') + '
      '; 161 | }; 162 | 163 | /** 164 | * Returns a `
      ` with a thumbnail linked to a full picture 165 | * 166 | * @param {Object} `context` Object with values/attributes to add to the generated elements: 167 | * @param {String} `context.alt` 168 | * @param {String} `context.src` 169 | * @param {Number} `context.width` 170 | * @param {Number} `context.height` 171 | * @return {String} HTML `
      ` element with image and optional caption/link. 172 | * @contributor: Marie Hogebrandt 173 | * @api public 174 | */ 175 | 176 | helpers.thumbnailImage = function(context) { 177 | var figure = ''; 178 | var image = ''; 179 | 180 | var link = context.full || false; 181 | var imageAttributes = { 182 | alt: context.alt, 183 | src: context.thumbnail, 184 | width: context.size.width, 185 | height: context.size.height 186 | }; 187 | 188 | var figureAttributes = { id: 'image-' + context.id }; 189 | var linkAttributes = { href: link, rel: 'thumbnail' }; 190 | 191 | if (context.classes) { 192 | if (context.classes.image) { 193 | imageAttributes.class = context.classes.image.join(' '); 194 | } 195 | if (context.classes.figure) { 196 | figureAttributes.class = context.classes.figure.join(' '); 197 | } 198 | if (context.classes.link) { 199 | linkAttributes.class = context.classes.link.join(' '); 200 | } 201 | } 202 | 203 | figure += '
      \n'; 204 | image += '\n'; 205 | 206 | if (link) { 207 | figure += '\n' + image + '\n'; 208 | } else { 209 | figure += image; 210 | } 211 | 212 | if (context.caption) { 213 | figure += '
      ' + context.caption + '
      \n'; 214 | } 215 | 216 | figure += '
      '; 217 | return figure; 218 | }; 219 | -------------------------------------------------------------------------------- /lib/i18n.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var util = require('./utils/handlebarsUtils'); 4 | var helpers = module.exports; 5 | const getValue = require('get-value'); 6 | 7 | /** 8 | * i18n helper. See [button-i18n](https://github.com/assemble/buttons) 9 | * for a working example. 10 | * 11 | * @contributor Laurent Goderre 12 | * @param {String} `key` 13 | * @param {Object} `options` 14 | * @return {String} 15 | * @api public 16 | */ 17 | 18 | helpers.i18n = function(prop, locals, options) { 19 | if (util.isOptions(locals)) { 20 | options = locals; 21 | locals = {}; 22 | } 23 | 24 | if (!util.isString(prop)) { 25 | throw new Error('{{i18n}} helper expected "key" to be a string'); 26 | } 27 | 28 | var opts = util.options(this, locals, options); 29 | var context = Object.assign({}, this, opts); 30 | 31 | var lang = context.language || context.lang; 32 | 33 | if (typeof(lang) !== 'string') { 34 | throw new TypeError('{{i18n}} helper expected "language" to be a string'); 35 | } 36 | 37 | var cache = context[lang]; 38 | if (typeof cache === 'undefined') { 39 | throw new Error('{{i18n}} helper cannot find language "' + lang + '"'); 40 | } 41 | 42 | var result = getValue(cache, prop); 43 | if (typeof result === 'undefined') { 44 | throw new Error('{{i18n}} helper cannot find property "' + prop + '" for language "' + lang + '"'); 45 | } 46 | 47 | return result; 48 | }; 49 | -------------------------------------------------------------------------------- /lib/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | array: require('./array'), 5 | code: require('./code'), 6 | collection: require('./collection'), 7 | comparison: require('./comparison'), 8 | //date: require('./date'), 9 | //fs: require('./fs'), 10 | html: require('./html'), 11 | i18n: require('./i18n'), 12 | inflection: require('./inflection'), 13 | //markdown: require('./markdown'), 14 | match: require('./match'), 15 | math: require('./math'), 16 | misc: require('./misc'), 17 | number: require('./number'), 18 | object: require('./object'), 19 | path: require('./path'), 20 | regex: require('./regex'), 21 | string: require('./string'), 22 | url: require('./url'), 23 | uuid: require('./uuid') 24 | }; 25 | -------------------------------------------------------------------------------- /lib/inflection.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var util = require('./utils/handlebarsUtils'); 4 | var helpers = module.exports; 5 | 6 | /** 7 | * Returns either the `singular` or `plural` inflection of a word based on 8 | * the given `count`. 9 | * 10 | * ```handlebars 11 | * {{inflect 0 'string' 'strings'}} 12 | * 13 | * {{inflect 1 'string' 'strings'}} 14 | * 15 | * {{inflect 1 'string' 'strings' true}} 16 | * 17 | * {{inflect 2 'string' 'strings'}} 18 | * 19 | * {{inflect 2 'string' 'strings' true}} 20 | * 21 | * ``` 22 | * @param {Number} `count` 23 | * @param {String} `singular` The singular form 24 | * @param {String} `plural` The plural form 25 | * @param {String} `includeCount` 26 | * @return {String} 27 | * @api public 28 | */ 29 | 30 | helpers.inflect = function(count, singular, plural, includeCount) { 31 | var word = (count > 1 || count === 0) ? plural : singular; 32 | if (includeCount === true) { 33 | return String(count) + ' ' + word; 34 | } else { 35 | return word; 36 | } 37 | }; 38 | 39 | /** 40 | * Returns an ordinalized number as a string. 41 | * 42 | * ```handlebars 43 | * {{ordinalize 1}} 44 | * 45 | * {{ordinalize 21}} 46 | * 47 | * {{ordinalize 29}} 48 | * 49 | * {{ordinalize 22}} 50 | * 51 | * ``` 52 | * 53 | * @param {String} `val` The value to ordinalize. 54 | * @return {String} The ordinalized number 55 | * @api public 56 | */ 57 | 58 | helpers.ordinalize = function(val) { 59 | var num = Math.abs(Math.round(val)); 60 | var str = String(val); 61 | var res = num % 100; 62 | 63 | if (util.indexOf([11, 12, 13], res) >= 0) { 64 | return str + 'th'; 65 | } 66 | 67 | switch (num % 10) { 68 | case 1: 69 | return str + 'st'; 70 | case 2: 71 | return str + 'nd'; 72 | case 3: 73 | return str + 'rd'; 74 | default: { 75 | return str + 'th'; 76 | } 77 | } 78 | }; 79 | -------------------------------------------------------------------------------- /lib/lorem.js: -------------------------------------------------------------------------------- 1 | const lorem = 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus imperdiet, nulla et dictum interdum, nisi lorem egestas odio, vitae scelerisque enim ligula venenatis dolor. Maecenas nisl est, ultrices nec congue eget, auctor vitae massa. Fusce luctus vestibulum augue ut aliquet. Mauris ante ligula, facilisis sed ornare eu, lobortis in odio. Praesent convallis urna a lacus interdum ut hendrerit risus congue. Nunc sagittis dictum nisi, sed ullamcorper ipsum dignissim ac. In at libero sed nunc venenatis imperdiet sed ornare turpis. Donec vitae dui eget tellus gravida venenatis. Integer fringilla congue eros non fermentum. Sed dapibus pulvinar nibh tempor porta. \n\n Crase tempor malesuada magna a vehicula. Nam sollicitudin vel turpis id fermentum. Ut sit amet nisl ac nulla vulputate ultrices vitae vitae urna. Quisque eget odio ac lectus vestibulum faucibus eget in metus. In pellentesque faucibus vestibulum. Nulla at nulla justo, eget luctus tortor. Nulla facilisi. Donec vulputate interdum sollicitudin. Nunc lacinia auctor quam sed pellentesque. Aliquam dui mauris, mattis quis lacus id, pellentesque lobortis odio. \n\n Nullam malesuada erat ut turpis. Suspendisse urna nibh, viverra non, semper suscipit, posuere a, pede. Donec nec justo eget felis facilisis fermentum. Aliquam porttitor mauris sit amet orci. Aenean dignissim pellentesque felis. Phasellus ultrices nulla quis nibh. Quisque a lectus. Donec consectetuer ligula vulputate sem tristique cursus. Nam nulla quam, gravida non, commodo a, sodales sit amet, nisi. Praesent id justo in neque elementum ultrices. Sed malesuada augue eu sapien sodales congue. Nam ut dui. Quisque a lectus. Donec consectetuer ligula vulputate sem tristique cursus. Nam nulla quam, gravida non, commodo a, sodales sit amet, nisi. Proin vel ante a orci tempus eleifend ut et magna. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus luctus urna sed urna ultricies ac tempor dui sagittis. In condimentum facilisis porta. Sed nec diam eu diam mattis viverra. Nulla fringilla, orci ac euismod semper, magna diam porttitor mauris, quis sollicitudin sapien justo in libero. Vestibulum mollis mauris enim. Morbi euismod magna ac lorem rutrum elementum. Donec viverra auctor lobortis. Pellentesque eu est a nulla placerat dignissim. Morbi a enim in magna semper bibendum. Etiam scelerisque, nunc ac egestas consequat, odio nibh euismod nulla, eget auctor orci nibh vel nisi. Aliquam erat volutpat. Sed quis velit. Nulla facilisi. Nulla libero. Vivamus fermentum nibh in augue. Praesent a lacus at urna congue rutrum. Nulla enim eros, porttitor eu, tempus id, varius non, nibh. Vestibulum imperdiet nibh vel magna lacinia ultrices. Sed id ligula quis est convallis tempor. \n\n Aliquam erat volutpat. Integer aliquam ultrices nunc. Ut lectus dui, tincidunt ac, scelerisque ac, ultrices vitae, risus. Donec vehicula augue eu neque. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Mauris ut leo. Cras viverra metus rhoncus sem. Nulla et lectus vestibulum urna fringilla ultrices. Phasellus eu tellus sit amet tortor gravida placerat. Integer sapien est, iaculis in, pretium quis, viverra ac, nunc. Praesent eget sem vel leo ultrices bibendum. Aenean faucibus. Morbi dolor nulla, malesuada eu, pulvinar at, mollis ac, nulla. Curabitur vehicula nisi a magna. Sed nec libero. Phasellus nonummy magna. Sed et libero nec ligula blandit fringilla. Ut pretium tempus gravida. Proin lacinia justo vel ipsum varius eget auctor est iaculis. \n\n Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Fusce luctus vestibulum augue ut aliquet. Nunc sagittis dictum nisi, sed ullamcorper ipsum dignissim ac. In at libero sed nunc venenatis imperdiet sed ornare turpis. Donec vitae dui eget tellus gravida venenatis. Integer fringilla congue eros non fermentum. Sed dapibus pulvinar nibh tempor porta. \n\n Nam dui erat, auctor a, dignissim quis. Aenean dignissim pellentesque felis. Sed gravida ante at nunc dictum placerat. Donec placerat nisl magna, et faucibus arcu condimentum sed. Curabitur et eros ac orci vehicula vestibulum sit amet at nunc. Maecenas non diam cursus, tincidunt nisi vitae, hendrerit enim. Donec vulputate felis id felis dapibus fermentum. \n\n Nullam malesuada erat ut turpis. Suspendisse urna nibh, viverra non, semper suscipit, posuere a, pede. Donec nec justo eget felis facilisis fermentum. Aliquam porttitor mauris sit amet orci. Aenean dignissim pellentesque felis. Phasellus ultrices nulla quis nibh. Quisque a lectus. Donec consectetuer ligula vulputate sem tristique cursus. Nam nulla quam, gravida non, commodo a, sodales sit amet, nisi. Praesent id justo in neque elementum ultrices. Sed malesuada augue eu sapien sodales congue. Nam ut dui. Quisque a lectus. Donec consectetuer ligula vulputate sem tristique cursus. Nam nulla quam, gravida non, commodo a, sodales sit amet, nisi. \n\n Pellentesque sit amet mauris eget lectus commodo viverra. Donec vulputate interdum sollicitudin. Nunc lacinia auctor quam sed pellentesque. Aliquam dui mauris, mattis quis lacus id, pellentesque lobortis odio. Cras ultricies ligula sed magna dictum porta. Curabitur aliquet quam id dui posuere blandit. Nulla porttitor accumsan tincidunt. Donec sollicitudin molestie malesuada. Curabitur arcu erat, accumsan id imperdiet et, porttitor at sem. Sed porttitor lectus nibh. Pellentesque in ipsum id orci porta dapibus. Vestibulum ac diam sit amet quam vehicula elementum sed sit amet dui. \n\n Cras ultricies ligula sed magna dictum porta. Donec rutrum congue leo eget malesuada. Nulla quis lorem ut libero malesuada feugiat. Proin eget tortor risus. Nulla quis lorem ut libero malesuada feugiat. Proin eget tortor risus. Curabitur aliquet quam id dui posuere blandit. Vestibulum ac diam sit amet quam vehicula elementum sed sit amet dui. Pellentesque in ipsum id orci porta dapibus. Curabitur non nulla sit amet nisl tempus convallis quis ac lectus. Donec rutrum congue leo eget malesuada. Vivamus suscipit tortor eget felis porttitor volutpat. Nulla porttitor accumsan tincidunt. \n\n Donec sollicitudin molestie malesuada. Pellentesque in ipsum id orci porta dapibus. Curabitur non nulla sit amet nisl tempus convallis quis ac lectus. Nulla quis lorem ut libero malesuada feugiat. Proin eget tortor risus. Cras ultricies ligula sed magna dictum porta. Donec sollicitudin molestie malesuada. Pellentesque in ipsum id orci porta dapibus. Vestibulum ac diam sit amet quam vehicula elementum sed sit amet dui. Curabitur non nulla sit amet nisl tempus convallis quis ac lectus. Nulla quis lorem ut libero malesuada feugiat. Proin eget tortor risus. Curabitur aliquet quam id dui posuere blandit. \n\n Vestibulum ac diam sit amet quam vehicula elementum sed sit amet dui. Curabitur non nulla sit amet nisl tempus convallis quis ac lectus. Nulla quis lorem ut libero malesuada feugiat. Proin eget tortor risus. Cras ultricies ligula sed magna dictum porta. Donec sollicitudin molestie malesuada. Pellentesque in ipsum id orci porta dapibus. Vestibulum ac diam sit amet quam vehicula elementum sed sit amet dui. Curabitur non nulla sit amet nisl tempus convallis quis ac lectus. Nulla quis lorem ut libero malesuada feugiat. Proin eget tortor risus. \n\n Curabitur aliquet quam id dui posuere blandit. Vestibulum ac diam sit amet quam vehicula elementum sed sit amet dui. Pellentesque in ipsum id orci porta dapibus. Curabitur non nulla sit amet nisl tempus convallis quis ac lectus. Nulla quis lorem ut libero malesuada feugiat. Proin eget tortor risus. Cras ultricies ligula sed magna dictum porta. Donec sollicitudin molestie malesuada. Pellentesque in ipsum id orci porta dapibus. Vestibulum ac diam sit amet quam vehicula elementum sed sit amet dui. Curabitur non nulla sit amet nisl tempus convallis quis ac lectus. Nulla quis lorem ut libero malesuada feugiat. Proin eget tortor risus. Curabitur aliquet quam id dui posuere blandit. Vestibulum ac diam sit amet quam vehicula elementum sed sit amet dui.'; 2 | 3 | module.exports = lorem; 4 | -------------------------------------------------------------------------------- /lib/match.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var util = require('./utils/handlebarsUtils'); 4 | var helpers = module.exports; 5 | const micromatch = require('micromatch'); 6 | 7 | /** 8 | * Returns an array of strings that match the given glob pattern(s). 9 | * Options may be passed on the options hash or locals. 10 | * 11 | * ```handlebars 12 | * {{match (readdir 'foo') '*.js'}} 13 | * {{match (readdir 'foo') (toRegex '\\.js$')}} 14 | * ``` 15 | * @param {Array|String} `files` 16 | * @param {Array|String} `patterns` One or more glob patterns. 17 | * @param {Object} `locals` 18 | * @param {Object} `options` 19 | * @return {Array} Array of matches 20 | * @api public 21 | */ 22 | 23 | helpers.match = function(files, patterns, locals, options) { 24 | var opts = util.options(this, locals, options); 25 | if (typeof patterns === 'string') { 26 | patterns = patterns.split(/, */); 27 | } 28 | return micromatch(files, patterns, opts); 29 | }; 30 | 31 | /** 32 | * Returns true if a filepath contains the given pattern. 33 | * Options may be passed on the options hash or locals. 34 | * 35 | * ```handlebars 36 | * {{isMatch 'foo.md' '*.md'}} 37 | * 38 | * ``` 39 | * 40 | * @param {String} `filepath` 41 | * @param {String} `pattern` 42 | * @param {Object} `options` 43 | * @return {Boolean} 44 | * @api public 45 | */ 46 | 47 | helpers.isMatch = function(files, patterns, locals, options) { 48 | var opts = util.options(this, locals, options); 49 | return micromatch.isMatch(files, patterns, opts); 50 | }; 51 | 52 | /** 53 | * Alias for micromatch helper. Deprecated in v0.9.0. 54 | */ 55 | 56 | helpers.mm = function() { 57 | console.log('the {{mm}} helper is depcrecated and will be removed'); 58 | console.log('in handlebars-helpers v1.0.0, please use the {{match}}'); 59 | console.log('helper instead.'); 60 | return helpers.match.apply(this, arguments); 61 | }; 62 | -------------------------------------------------------------------------------- /lib/math.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var utils = require('./utils'); 4 | var helpers = module.exports; 5 | 6 | /** 7 | * Return the magnitude of `a`. 8 | * 9 | * @param {Number} `a` 10 | * @return {Number} 11 | * @api public 12 | * @example {{ abs 12012.1000 }} -> 12012.1 13 | */ 14 | 15 | helpers.abs = function(num) { 16 | if (isNaN(num)) { 17 | throw new TypeError('expected a number'); 18 | } 19 | return Math.abs(num); 20 | }; 21 | 22 | /** 23 | * Return the sum of `a` plus `b`. 24 | * 25 | * @param {Number} `a` 26 | * @param {Number} `b` 27 | * @return {Number} 28 | * @api public 29 | * @example {{ add 1 2 }} -> 3 30 | */ 31 | 32 | helpers.add = function(a, b) { 33 | if (!isNaN(a) && !isNaN(b)) { 34 | return Number(a) + Number(b); 35 | } 36 | if (typeof a === 'string' && typeof b === 'string') { 37 | return a + b; 38 | } 39 | return ''; 40 | }; 41 | 42 | /** 43 | * Returns the average of all numbers in the given array. 44 | * 45 | * ```handlebars 46 | * {{avg 1 2 3 4 5}} 47 | * 48 | * ``` 49 | * 50 | * @param {Array} `array` Array of numbers to add up. 51 | * @return {Number} 52 | * @api public 53 | * @example {{ avg 1 2 3 4 5 }} -> 3 54 | */ 55 | 56 | helpers.avg = function() { 57 | const args = [].concat.apply([], arguments); 58 | // remove handlebars options object 59 | args.pop(); 60 | return helpers.sum(args) / args.length; 61 | }; 62 | 63 | /** 64 | * Get the `Math.ceil()` of the given value. 65 | * 66 | * @param {Number} `value` 67 | * @return {Number} 68 | * @api public 69 | * @example {{ ceil 1.2 }} -> 2 70 | */ 71 | 72 | helpers.ceil = function(num) { 73 | if (isNaN(num)) { 74 | throw new TypeError('expected a number'); 75 | } 76 | return Math.ceil(num); 77 | }; 78 | 79 | /** 80 | * Divide `a` by `b` 81 | * 82 | * @param {Number} `a` numerator 83 | * @param {Number} `b` denominator 84 | * @api public 85 | * @example {{ divide 10 5 }} -> 2 86 | */ 87 | 88 | helpers.divide = function(a, b) { 89 | if (isNaN(a)) { 90 | throw new TypeError('expected the first argument to be a number'); 91 | } 92 | if (isNaN(b)) { 93 | throw new TypeError('expected the second argument to be a number'); 94 | } 95 | return Number(a) / Number(b); 96 | }; 97 | 98 | /** 99 | * Get the `Math.floor()` of the given value. 100 | * 101 | * @param {Number} `value` 102 | * @return {Number} 103 | * @api public 104 | * @example {{ floor 1.2 }} -> 1 105 | */ 106 | 107 | helpers.floor = function(num) { 108 | if (isNaN(num)) { 109 | throw new TypeError('expected a number'); 110 | } 111 | return Math.floor(num); 112 | }; 113 | 114 | /** 115 | * Return the difference of `a` minus `b`. 116 | * 117 | * @param {Number} `a` 118 | * @param {Number} `b` 119 | * @alias subtract 120 | * @api public 121 | * @example {{ minus 10 5 }} -> 5 122 | */ 123 | 124 | helpers.minus = function(a, b) { 125 | if (isNaN(a)) { 126 | throw new TypeError('expected the first argument to be a number'); 127 | } 128 | if (isNaN(b)) { 129 | throw new TypeError('expected the second argument to be a number'); 130 | } 131 | return Number(a) - Number(b); 132 | }; 133 | 134 | /** 135 | * Get the remainder of a division operation. 136 | * 137 | * @param {Number} `a` 138 | * @param {Number} `b` 139 | * @return {Number} 140 | * @api public 141 | * @example {{ modulo 10 5 }} -> 0 142 | */ 143 | 144 | helpers.modulo = function(a, b) { 145 | if (isNaN(a)) { 146 | throw new TypeError('expected the first argument to be a number'); 147 | } 148 | if (isNaN(b)) { 149 | throw new TypeError('expected the second argument to be a number'); 150 | } 151 | return Number(a) % Number(b); 152 | }; 153 | 154 | /** 155 | * Multiply number `a` by number `b`. 156 | * 157 | * @param {Number} `a` factor 158 | * @param {Number} `b` multiplier 159 | * @return {Number} 160 | * @alias multiply 161 | * @api public 162 | * @example {{ multiply 10 5 }} -> 50 163 | */ 164 | 165 | helpers.multiply = function(a, b) { 166 | if (isNaN(a)) { 167 | throw new TypeError('expected the first argument to be a number'); 168 | } 169 | if (isNaN(b)) { 170 | throw new TypeError('expected the second argument to be a number'); 171 | } 172 | return Number(a) * Number(b); 173 | }; 174 | 175 | /** 176 | * Add `a` by `b`. 177 | * 178 | * @param {Number} `a` factor 179 | * @param {Number} `b` multiplier 180 | * @api public 181 | * @example {{ plus 10 5 }} -> 15 182 | */ 183 | 184 | helpers.plus = function(a, b) { 185 | if (isNaN(a)) { 186 | throw new TypeError('expected the first argument to be a number'); 187 | } 188 | if (isNaN(b)) { 189 | throw new TypeError('expected the second argument to be a number'); 190 | } 191 | return Number(a) + Number(b); 192 | }; 193 | 194 | /** 195 | * Generate a random number between two values 196 | * 197 | * @param {Number} `min` 198 | * @param {Number} `max` 199 | * @return {String} 200 | * @api public 201 | * @example {{ random 0 20 }} -> 10 202 | */ 203 | 204 | helpers.random = function(min, max) { 205 | if (isNaN(min)) { 206 | throw new TypeError('expected minimum to be a number'); 207 | } 208 | if (isNaN(max)) { 209 | throw new TypeError('expected maximum to be a number'); 210 | } 211 | return utils.random(min, max); 212 | }; 213 | 214 | /** 215 | * Get the remainder when `a` is divided by `b`. 216 | * 217 | * @param {Number} `a` a 218 | * @param {Number} `b` b 219 | * @api public 220 | * @example {{ remainder 10 6 }} -> 4 221 | */ 222 | 223 | helpers.remainder = function(a, b) { 224 | return a % b; 225 | }; 226 | 227 | /** 228 | * Round the given number. 229 | * 230 | * @param {Number} `number` 231 | * @return {Number} 232 | * @api public 233 | * @example {{ round 10.3 }} -> 10 234 | */ 235 | 236 | helpers.round = function(num) { 237 | if (isNaN(num)) { 238 | throw new TypeError('expected a number'); 239 | } 240 | return Math.round(num); 241 | }; 242 | 243 | /** 244 | * Return the product of `a` minus `b`. 245 | * 246 | * @param {Number} `a` 247 | * @param {Number} `b` 248 | * @return {Number} 249 | * @alias minus 250 | * @api public 251 | * @example {{ subtract 10 5 }} -> 5 252 | */ 253 | 254 | helpers.subtract = function(a, b) { 255 | if (isNaN(a)) { 256 | throw new TypeError('expected the first argument to be a number'); 257 | } 258 | if (isNaN(b)) { 259 | throw new TypeError('expected the second argument to be a number'); 260 | } 261 | return Number(a) - Number(b); 262 | }; 263 | 264 | /** 265 | * Returns the sum of all numbers in the given array. 266 | * 267 | * ```handlebars 268 | * {{sum '[1, 2, 3, 4, 5]'}} 269 | * 270 | * ``` 271 | * @param {Array} `array` Array of numbers to add up. 272 | * @return {Number} 273 | * @api public 274 | * @example {{ sum [1, 2, 3] }} -> 6 275 | */ 276 | 277 | helpers.sum = function() { 278 | var args = [].concat.apply([], arguments); 279 | var len = args.length; 280 | var sum = 0; 281 | 282 | while (len--) { 283 | if (!isNaN(args[len])) { 284 | sum += Number(args[len]); 285 | } 286 | } 287 | return sum; 288 | }; 289 | -------------------------------------------------------------------------------- /lib/misc.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var util = require('./utils/handlebarsUtils'); 4 | var helpers = module.exports; 5 | const getValue = require('get-value'); 6 | const createFrame = require('./utils/createFrame'); 7 | 8 | /** 9 | * Block helper for exposing private `@` variables on the context 10 | */ 11 | 12 | helpers.frame = function(context, options) { 13 | if (typeof(context) === 'object' && context.hash) { 14 | options = context; 15 | context = options.data; 16 | } 17 | 18 | var frame = createFrame(context); 19 | if (typeof(options) !== 'object') { 20 | options = {}; 21 | } 22 | 23 | // extend the frame with hash arguments 24 | frame.extend(options.hash); 25 | return options.fn(this, { data: frame }); 26 | }; 27 | 28 | /** 29 | * Return the given value of `prop` from `this.options`. 30 | * 31 | * ```handlebars 32 | * 33 | * {{option 'a.b.c'}} 34 | * 35 | * ``` 36 | * @param {String} `prop` 37 | * @return {any} 38 | * @api public 39 | */ 40 | 41 | helpers.option = function(prop, locals, options) { 42 | return getValue(util.options(this, locals, options), prop); 43 | }; 44 | 45 | /** 46 | * Block helper that renders the block without taking any arguments. 47 | * 48 | * @param {Object} `options` 49 | * @return {String} 50 | * @block 51 | * @api public 52 | */ 53 | 54 | helpers.noop = function(options) { 55 | return options.fn(this); 56 | }; 57 | 58 | /** 59 | * Get the native type of the given `value` 60 | * 61 | * ```handlebars 62 | * {{typeOf 1}} 63 | * //=> 'number' 64 | * {{typeOf '1'}} 65 | * //=> 'string' 66 | * {{typeOf 'foo'}} 67 | * //=> 'string' 68 | * ``` 69 | * @param {any} `value` 70 | * @return {String} Returns the type of value. 71 | * @api public 72 | */ 73 | 74 | helpers.typeOf = function(val) { return typeof val; }; 75 | 76 | /** 77 | * Block helper that builds the context for the block 78 | * from the options hash. 79 | * 80 | * @param {Object} `options` Handlebars provided options object. 81 | * @contributor Vladimir Kuznetsov 82 | * @block 83 | * @api public 84 | */ 85 | 86 | helpers.withHash = function(options) { 87 | if (options.hash && Object.keys(options.hash).length) { 88 | return options.fn(options.hash); 89 | } else { 90 | return options.inverse(this); 91 | } 92 | }; 93 | -------------------------------------------------------------------------------- /lib/number.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var util = { 4 | isUndefined: require('./utils/isUndefined') 5 | }; 6 | var helpers = module.exports; 7 | 8 | /** 9 | * Format a number to it's equivalent in bytes. If a string is passed, 10 | * it's length will be formatted and returned. 11 | * 12 | * **Examples:** 13 | * 14 | * - `'foo' => 3 B` 15 | * - `13661855 => 13.66 MB` 16 | * - `825399 => 825.39 kB` 17 | * - `1396 => 1.4 kB` 18 | * 19 | * @param {Number|String} `number` 20 | * @return {String} 21 | * @api public 22 | * @example {{ bytes 1386 1 }} -> 1.4 kB 23 | */ 24 | 25 | helpers.bytes = function(number, precision, options) { 26 | if (number == null) return '0 B'; 27 | 28 | if (isNaN(number)) { 29 | number = number.length; 30 | if (!number) return '0 B'; 31 | } 32 | 33 | if (isNaN(precision)) { 34 | precision = 2; 35 | } 36 | 37 | var abbr = ['B', 'kB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']; 38 | precision = Math.pow(10, precision); 39 | number = Number(number); 40 | 41 | var len = abbr.length - 1; 42 | while (len-- >= 0) { 43 | var size = Math.pow(10, len * 3); 44 | if (size <= (number + 1)) { 45 | number = Math.round(number * precision / size) / precision; 46 | number += ' ' + abbr[len]; 47 | break; 48 | } 49 | } 50 | 51 | return number; 52 | }; 53 | 54 | /** 55 | * Add commas to numbers 56 | * 57 | * @param {Number} `num` 58 | * @return {Number} 59 | * @api public 60 | * @example {{ addCommas 1000000 }} -> 1,000,000 61 | */ 62 | 63 | helpers.addCommas = function(num) { 64 | return num.toString().replace(/(\d)(?=(\d\d\d)+(?!\d))/g, '$1,'); 65 | }; 66 | 67 | /** 68 | * Convert a string or number to a formatted phone number. 69 | * 70 | * @param {Number|String} `num` The phone number to format, e.g. `8005551212` 71 | * @return {Number} Formatted phone number: `(800) 555-1212` 72 | * @source http://bit.ly/QlPmPr 73 | * @api public 74 | * @example {{ phoneNumber 8005551212 }} -> (800) 555-1212 75 | */ 76 | 77 | helpers.phoneNumber = function(num) { 78 | num = num.toString(); 79 | 80 | return '(' + num.substr(0, 3) + ') ' 81 | + num.substr(3, 3) + '-' 82 | + num.substr(6, 4); 83 | }; 84 | 85 | /** 86 | * Abbreviate numbers to the given number of `precision`. This is for 87 | * general numbers, not size in bytes. 88 | * 89 | * @param {Number} `number` 90 | * @param {Number} `precision` 91 | * @return {String} 92 | * @api public 93 | * @example {{ toAbbr 10123 2 }} -> 10.12k 94 | */ 95 | 96 | helpers.toAbbr = function(number, precision) { 97 | if (isNaN(number)) { 98 | number = 0; 99 | } 100 | if (util.isUndefined(precision)) { 101 | precision = 2; 102 | } 103 | 104 | number = Number(number); 105 | // 2 decimal places => 100, 3 => 1000, etc. 106 | precision = Math.pow(10, precision); 107 | var abbr = ['k', 'm', 'b', 't', 'q']; 108 | var len = abbr.length - 1; 109 | 110 | while (len >= 0) { 111 | var size = Math.pow(10, (len + 1) * 3); 112 | if (size <= (number + 1)) { 113 | number = Math.round(number * precision / size) / precision; 114 | number += abbr[len]; 115 | break; 116 | } 117 | len--; 118 | } 119 | return number; 120 | }; 121 | 122 | /** 123 | * Returns a string representing the given number in exponential notation. 124 | * 125 | * ```handlebars 126 | * {{toExponential number digits}}; 127 | * ``` 128 | * @param {Number} `number` 129 | * @param {Number} `fractionDigits` Optional. An integer specifying the number of digits to use after the decimal point. Defaults to as many digits as necessary to specify the number. 130 | * @return {Number} 131 | * @api public 132 | * @example {{ toExponential 10123 2 }} -> 1.01e+4 133 | */ 134 | 135 | helpers.toExponential = function(number, digits) { 136 | if (isNaN(number)) { 137 | number = 0; 138 | } 139 | if (util.isUndefined(digits)) { 140 | digits = 0; 141 | } 142 | return Number(number).toExponential(digits); 143 | }; 144 | 145 | /** 146 | * Formats the given number using fixed-point notation. 147 | * 148 | * ```handlebars 149 | * {{toFixed '1.1234' 2}} 150 | * //=> '1.12' 151 | * ``` 152 | * @param {Number} `number` 153 | * @param {Number} `digits` (Optional) The number of digits to appear after the decimal point; this may be a value between 0 and 20. If this argument is omitted, it is treated as 0. 154 | * @return {String} A string representing the given number using fixed-point notation. 155 | * @api public 156 | * @example {{ toFixed 1.1234 2 }} -> 1.12 157 | */ 158 | 159 | helpers.toFixed = function(number, digits) { 160 | if (isNaN(number)) { 161 | number = 0; 162 | } 163 | if (isNaN(digits)) { 164 | digits = 0; 165 | } 166 | return Number(number).toFixed(digits); 167 | }; 168 | 169 | /** 170 | * @param {Number} `number` 171 | * @return {Number} 172 | * @api public 173 | */ 174 | 175 | helpers.toFloat = function(number) { 176 | return parseFloat(number); 177 | }; 178 | 179 | /** 180 | * @param {Number} `number` 181 | * @return {Number} 182 | * @api public 183 | */ 184 | 185 | helpers.toInt = function(number) { 186 | return parseInt(number, 10); 187 | }; 188 | 189 | /** 190 | * Returns a string representing the `Number` object to the specified precision. 191 | * 192 | * ```handlebars 193 | * {{toPrecision '1.1234' 2}} 194 | * //=> '1.1' 195 | * ``` 196 | * @param {Number} `number` 197 | * @param {Number} `precision` (Optional) An integer specifying the number of significant digits. If precison is not between 1 and 100 (inclusive), it will be coerced to `0`. 198 | * @return {String} A string representing a Number object in fixed-point or exponential notation rounded to precision significant digits. 199 | * @api public 200 | * @example {{toPrecision '1.1234' 2}} -> 1.1 201 | */ 202 | 203 | helpers.toPrecision = function(number, precision) { 204 | if (isNaN(number)) { 205 | number = 0; 206 | } 207 | if (isNaN(precision)) { 208 | precision = 1; 209 | } 210 | return Number(number).toPrecision(precision); 211 | }; 212 | -------------------------------------------------------------------------------- /lib/object.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var hasOwn = Object.hasOwnProperty; 4 | var util = { 5 | isOptions: require('./utils/isOptions'), 6 | isObject: require('./utils/isObject') 7 | }; 8 | var array = require('./array'); 9 | var helpers = module.exports; 10 | const getValue = require('get-value'); 11 | const getObject = require('get-object'); 12 | const createFrame = require('./utils/createFrame'); 13 | 14 | /** 15 | * Extend the context with the properties of other objects. 16 | * A shallow merge is performed to avoid mutating the context. 17 | * 18 | * @param {Object} `objects` One or more objects to extend. 19 | * @return {Object} 20 | * @api public 21 | */ 22 | 23 | helpers.extend = function(/*objects*/) { 24 | var args = [].slice.call(arguments); 25 | var opts = {}; 26 | 27 | if (util.isOptions(args[args.length - 1])) { 28 | // remove handlebars options object 29 | opts = args.pop().hash; 30 | // re-add handlebars options.hash object 31 | args.push(opts); 32 | } 33 | 34 | var context = {}; 35 | for (var i = 0; i < args.length; i++) { 36 | var obj = args[i]; 37 | if (util.isObject(obj)) { 38 | var keys = Object.keys(obj); 39 | for (var j = 0; j < keys.length; j++) { 40 | var key = keys[j]; 41 | context[key] = obj[key]; 42 | } 43 | } 44 | } 45 | 46 | return context; 47 | }; 48 | 49 | /** 50 | * Block helper that iterates over the properties of 51 | * an object, exposing each key and value on the context. 52 | * 53 | * @param {Object} `context` 54 | * @param {Object} `options` 55 | * @return {String} 56 | * @block 57 | * @api public 58 | */ 59 | 60 | helpers.forIn = function(obj, options) { 61 | if (!util.isOptions(options)) { 62 | return obj.inverse(this); 63 | } 64 | 65 | var data = createFrame(options, options.hash); 66 | var result = ''; 67 | 68 | for (var key in obj) { 69 | data.key = key; 70 | result += options.fn(obj[key], { data: data }); 71 | } 72 | return result; 73 | }; 74 | 75 | /** 76 | * Block helper that iterates over the **own** properties of 77 | * an object, exposing each key and value on the context. 78 | * 79 | * @param {Object} `obj` The object to iterate over. 80 | * @param {Object} `options` 81 | * @return {String} 82 | * @block 83 | * @api public 84 | */ 85 | 86 | helpers.forOwn = function(obj, options) { 87 | if (!util.isOptions(options)) { 88 | return obj.inverse(this); 89 | } 90 | 91 | var data = createFrame(options, options.hash); 92 | var result = ''; 93 | 94 | for (var key in obj) { 95 | if (obj.hasOwnProperty(key)) { 96 | data.key = key; 97 | result += options.fn(obj[key], { data: data }); 98 | } 99 | } 100 | return result; 101 | }; 102 | 103 | /** 104 | * Take arguments and, if they are string or number, convert them to a dot-delineated object property path. 105 | * 106 | * @param {String|Number} `prop` The property segments to assemble (can be multiple). 107 | * @return {String} 108 | * @api public 109 | */ 110 | 111 | helpers.toPath = function(/*prop*/) { 112 | var prop = []; 113 | for (var i = 0; i < arguments.length; i++) { 114 | if (typeof arguments[i] === 'string' || typeof arguments[i] === 'number') { 115 | prop.push(arguments[i]); 116 | } 117 | } 118 | return prop.join('.'); 119 | }; 120 | 121 | /** 122 | * Use property paths (`a.b.c`) to get a value or nested value from 123 | * the context. Works as a regular helper or block helper. 124 | * 125 | * @param {String} `prop` The property to get, optionally using dot notation for nested properties. 126 | * @param {Object} `context` The context object 127 | * @param {Object} `options` The handlebars options object, if used as a block helper. 128 | * @return {String} 129 | * @block 130 | * @api public 131 | */ 132 | 133 | helpers.get = function(prop, context, options) { 134 | var val = getValue(context, prop); 135 | if (options && options.fn) { 136 | return val ? options.fn(val) : options.inverse(context); 137 | } 138 | return val; 139 | }; 140 | 141 | /** 142 | * Use property paths (`a.b.c`) to get an object from 143 | * the context. Differs from the `get` helper in that this 144 | * helper will return the actual object, including the 145 | * given property key. Also, this helper does not work as a 146 | * block helper. 147 | * 148 | * @param {String} `prop` The property to get, optionally using dot notation for nested properties. 149 | * @param {Object} `context` The context object 150 | * @return {String} 151 | * @api public 152 | */ 153 | 154 | helpers.getObject = function(prop, context) { 155 | return getObject(context, prop); 156 | }; 157 | 158 | /** 159 | * Return true if `key` is an own, enumerable property 160 | * of the given `context` object. 161 | * 162 | * ```handlebars 163 | * {{hasOwn context key}} 164 | * ``` 165 | * 166 | * @param {String} `key` 167 | * @param {Object} `context` The context object. 168 | * @return {Boolean} 169 | * @api public 170 | */ 171 | 172 | helpers.hasOwn = function(context, key) { 173 | return hasOwn.call(context, key); 174 | }; 175 | 176 | /** 177 | * Return true if `value` is an object. 178 | * 179 | * ```handlebars 180 | * {{isObject 'foo'}} 181 | * //=> false 182 | * ``` 183 | * @param {String} `value` 184 | * @return {Boolean} 185 | * @api public 186 | */ 187 | 188 | helpers.isObject = function(value) { 189 | return typeof value === 'object'; 190 | }; 191 | 192 | /** 193 | * Parses the given string using `JSON.parse`. 194 | * 195 | * ```handlebars 196 | * 197 | * {{JSONparse string}} 198 | * 199 | * ``` 200 | * @param {String} `string` The string to parse 201 | * @contributor github.com/keeganstreet 202 | * @block 203 | * @api public 204 | */ 205 | 206 | helpers.JSONparse = function(str, options) { 207 | return JSON.parse(str); 208 | }; 209 | 210 | /** 211 | * Stringify an object using `JSON.stringify`. 212 | * 213 | * ```handlebars 214 | * 215 | * {{JSONstringify object}} 216 | * 217 | * ``` 218 | * @param {Object} `obj` Object to stringify 219 | * @return {String} 220 | * @api public 221 | */ 222 | 223 | helpers.JSONstringify = function(obj, indent) { 224 | if (isNaN(indent)) { 225 | indent = 0; 226 | } 227 | return JSON.stringify(obj, null, indent); 228 | }; 229 | 230 | /** 231 | * Deeply merge the properties of the given `objects` with the 232 | * context object. 233 | * 234 | * @param {Object} `object` The target object. Pass an empty object to shallow clone. 235 | * @param {Object} `objects` 236 | * @return {Object} 237 | * @api public 238 | */ 239 | 240 | helpers.merge = function(context/*, objects, options*/) { 241 | var args = [].slice.call(arguments); 242 | var opts = {}; 243 | 244 | if (util.isOptions(args[args.length - 1])) { 245 | // remove handlebars options object 246 | opts = args.pop().hash; 247 | // re-add options.hash 248 | args.push(opts); 249 | } 250 | 251 | return Object.assign.apply(null, args); 252 | }; 253 | 254 | /** 255 | * Alias for parseJSON. this will be 256 | * deprecated in a future release 257 | */ 258 | 259 | helpers.parseJSON = helpers.JSONparse; 260 | 261 | /** 262 | * Pick properties from the context object. 263 | * 264 | * @param {Array|String} `properties` One or more properties to pick. 265 | * @param {Object} `context` 266 | * @param {Object} `options` Handlebars options object. 267 | * @return {Object} Returns an object with the picked values. If used as a block helper, the values are passed as context to the inner block. If no values are found, the context is passed to the inverse block. 268 | * @block 269 | * @api public 270 | */ 271 | 272 | helpers.pick = function(props, context, options) { 273 | var keys = array.arrayify(props); 274 | var len = keys.length, i = -1; 275 | var result = {}; 276 | 277 | while (++i < len) { 278 | result = helpers.extend({}, result, getObject(context, keys[i])); 279 | } 280 | 281 | if (options.fn) { 282 | if (Object.keys(result).length) { 283 | return options.fn(result); 284 | } 285 | return options.inverse(context); 286 | } 287 | return result; 288 | }; 289 | 290 | /** 291 | * Alias for JSONstringify. this will be 292 | * deprecated in a future release 293 | */ 294 | 295 | helpers.stringify = helpers.JSONstringify; 296 | -------------------------------------------------------------------------------- /lib/path.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var util = require('./utils/handlebarsUtils'); 4 | var path = require('path'); 5 | const relative = require('relative'); 6 | var helpers = module.exports; 7 | 8 | /** 9 | * Get the directory path segment from the given `filepath`. 10 | * 11 | * ```handlebars 12 | * {{absolute 'docs/toc.md'}} 13 | * 14 | * ``` 15 | * @param {String} `ext` 16 | * @return {String} 17 | * @api public 18 | */ 19 | 20 | helpers.absolute = function(filepath, options) { 21 | options = options || { data: {} }; 22 | var context = util.options(this, options); 23 | var ctx = Object.assign({}, options.data.root, context); 24 | var cwd = ctx.cwd || process.cwd(); 25 | return path.resolve(cwd, filepath); 26 | }; 27 | 28 | /** 29 | * Get the directory path segment from the given `filepath`. 30 | * 31 | * ```handlebars 32 | * {{dirname 'docs/toc.md'}} 33 | * 34 | * ``` 35 | * @param {String} `ext` 36 | * @return {String} 37 | * @api public 38 | */ 39 | 40 | helpers.dirname = function(filepath, options) { 41 | if (typeof filepath !== 'string') { 42 | throw new TypeError(util.expectedType('filepath', 'string', filepath)); 43 | } 44 | return path.dirname(filepath); 45 | }; 46 | 47 | /** 48 | * Get the relative filepath from `a` to `b`. 49 | * 50 | * ```handlebars 51 | * {{relative a b}} 52 | * ``` 53 | * @param {String} `a` 54 | * @param {String} `b` 55 | * @return {String} 56 | * @api public 57 | */ 58 | 59 | helpers.relative = function(a, b) { 60 | if (typeof a !== 'string') { 61 | throw new TypeError(util.expectedType('first path', 'string', a)); 62 | } 63 | if (typeof b !== 'string') { 64 | throw new TypeError(util.expectedType('second path', 'string', b)); 65 | } 66 | return relative(a, b); 67 | }; 68 | 69 | /** 70 | * Get the file extension from the given `filepath`. 71 | * 72 | * ```handlebars 73 | * {{basename 'docs/toc.md'}} 74 | * 75 | * ``` 76 | * @param {String} `ext` 77 | * @return {String} 78 | * @api public 79 | */ 80 | 81 | helpers.basename = function(filepath) { 82 | if (typeof filepath !== 'string') { 83 | throw new TypeError(util.expectedType('filepath', 'string', filepath)); 84 | } 85 | return path.basename(filepath); 86 | }; 87 | 88 | /** 89 | * Get the 'stem' from the given `filepath`. 90 | * 91 | * ```handlebars 92 | * {{stem 'docs/toc.md'}} 93 | * 94 | * ``` 95 | * @param {String} `filepath` 96 | * @return {String} 97 | * @api public 98 | */ 99 | 100 | helpers.stem = function(filepath) { 101 | if (typeof filepath !== 'string') { 102 | throw new TypeError(util.expectedType('filepath', 'string', filepath)); 103 | } 104 | return path.basename(filepath, path.extname(filepath)); 105 | }; 106 | 107 | /** 108 | * Get the file extension from the given `filepath`. 109 | * 110 | * ```handlebars 111 | * {{extname 'docs/toc.md'}} 112 | * 113 | * ``` 114 | * @param {String} `filepath` 115 | * @return {String} 116 | * @api public 117 | */ 118 | 119 | helpers.extname = function(filepath) { 120 | if (typeof filepath !== 'string') { 121 | throw new TypeError(util.expectedType('filepath', 'string', filepath)); 122 | } 123 | return path.extname(filepath); 124 | }; 125 | 126 | /** 127 | * Resolve an absolute path from the given `filepath`. 128 | * 129 | * ```handlebars 130 | * {{resolve 'docs/toc.md'}} 131 | * 132 | * ``` 133 | * @param {String} `filepath` 134 | * @return {String} 135 | * @api public 136 | */ 137 | 138 | helpers.resolve = function(filepath) { 139 | var args = [].slice.call(arguments); 140 | var opts = util.options(this, args.pop()); 141 | var cwd = path.resolve(opts.cwd || process.cwd()); 142 | args.unshift(cwd); 143 | return path.resolve.apply(path, args); 144 | }; 145 | 146 | /** 147 | * Get specific (joined) segments of a file path by passing a 148 | * range of array indices. 149 | * 150 | * ```handlebars 151 | * {{segments 'a/b/c/d' '2' '3'}} 152 | * 153 | * 154 | * {{segments 'a/b/c/d' '1' '3'}} 155 | * 156 | * 157 | * {{segments 'a/b/c/d' '1' '2'}} 158 | * 159 | * ``` 160 | * 161 | * @param {String} `filepath` The file path to split into segments. 162 | * @return {String} Returns a single, joined file path. 163 | * @api public 164 | */ 165 | 166 | helpers.segments = function(filepath, a, b) { 167 | if (typeof filepath !== 'string') { 168 | throw new TypeError(util.expectedType('filepath', 'string', filepath)); 169 | } 170 | var segments = filepath.split(/[\\\/]+/); 171 | return segments.slice(a, b).join('/'); 172 | }; 173 | -------------------------------------------------------------------------------- /lib/regex.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var util = { options: require('./utils/options') }; 4 | var helpers = module.exports; 5 | const kindOf = require('kind-of'); 6 | 7 | /** 8 | * Convert the given string to a regular expression. 9 | * 10 | * ```handlebars 11 | * {{toRegex 'foo'}} 12 | * 13 | * ``` 14 | * @param {String} `str` 15 | * @return {RegExp} 16 | * @api public 17 | * @example {{toRegex 'foo'}} -> /foo/ 18 | */ 19 | 20 | helpers.toRegex = function(str, locals, options) { 21 | var opts = util.options({}, locals, options); 22 | return new RegExp(str, opts.flags); 23 | }; 24 | 25 | /** 26 | * Returns true if the given `str` matches the given regex. A regex can 27 | * be passed on the context, or using the [toRegex](#toregex) helper as a 28 | * subexpression. 29 | * 30 | * ```handlebars 31 | * {{test 'bar' (toRegex 'foo')}} 32 | * 33 | * {{test 'foobar' (toRegex 'foo')}} 34 | * 35 | * {{test 'foobar' (toRegex '^foo$')}} 36 | * 37 | * ``` 38 | * @param {String} `str` 39 | * @return {RegExp} 40 | * @api public 41 | * @example {{test 'foobar' (toRegex 'foo')}} -> true 42 | */ 43 | 44 | helpers.test = function(str, regex) { 45 | if (typeof(str) !== 'string') { 46 | return false; 47 | } 48 | if (kindOf(regex) !== 'regexp') { 49 | throw new TypeError('expected a regular expression'); 50 | } 51 | return regex.test(str); 52 | }; 53 | -------------------------------------------------------------------------------- /lib/url.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var url = require('url'); 4 | var util = { 5 | isString: require('./utils/isString') 6 | }; 7 | var querystring = require('querystring'); 8 | var helpers = module.exports; 9 | 10 | /** 11 | * Encodes a Uniform Resource Identifier (URI) component 12 | * by replacing each instance of certain characters by 13 | * one, two, three, or four escape sequences representing 14 | * the UTF-8 encoding of the character. 15 | * 16 | * @param {String} `str` The un-encoded string 17 | * @return {String} The endcoded string 18 | * @api public 19 | * @example {{ encodeURI 'https://myurl?Hello There' }} -> https%3A%2F%2Fmyurl%3FHello%20There 20 | */ 21 | 22 | helpers.encodeURI = function(str) { 23 | if (util.isString(str)) { 24 | return encodeURIComponent(str); 25 | } 26 | }; 27 | 28 | /** 29 | * Escape the given string by replacing characters with escape sequences. 30 | * Useful for allowing the string to be used in a URL, etc. 31 | * 32 | * @param {String} `str` 33 | * @return {String} Escaped string. 34 | * @api public 35 | * @example {{ escape 'https://myurl?Hello+There' }} -> https%3A%2F%2Fmyurl%3FHello%2BThere 36 | */ 37 | 38 | helpers.escape = function(str) { 39 | if (util.isString(str)) { 40 | return querystring.escape(str); 41 | } 42 | }; 43 | 44 | /** 45 | * Decode a Uniform Resource Identifier (URI) component. 46 | * 47 | * @param {String} `str` 48 | * @return {String} 49 | * @api public 50 | * @example {{ decodeURI 'https://myurl?Hello%20There' }} -> https://myurl?Hello There 51 | */ 52 | 53 | helpers.decodeURI = function(str) { 54 | if (util.isString(str)) { 55 | return decodeURIComponent(str); 56 | } 57 | }; 58 | 59 | /** 60 | * Take a base URL, and a href URL, and resolve them as a 61 | * browser would for an anchor tag. 62 | * 63 | * @param {String} `base` 64 | * @param {String} `href` 65 | * @return {String} 66 | * @api public 67 | * @example {{ urlResolve 'https://myurl' '/api/test' }} -> https://myurl/api/test 68 | */ 69 | 70 | helpers.urlResolve = function(base, href) { 71 | return url.resolve(base, href); 72 | }; 73 | 74 | /** 75 | * Parses a `url` string into an object. 76 | * 77 | * @param {String} `str` URL string 78 | * @return {String} Returns stringified JSON 79 | * @api public 80 | * @example {{ urlParse 'https://myurl/api/test' }} 81 | */ 82 | 83 | helpers.urlParse = function(str) { 84 | return url.parse(str); 85 | }; 86 | 87 | /** 88 | * Strip the query string from the given `url`. 89 | * 90 | * @param {String} `url` 91 | * @return {String} the url without the queryString 92 | * @api public 93 | * @example {{ stripQuerystring 'https://myurl/api/test?foo=bar' }} -> 'https://myurl/api/test' 94 | */ 95 | 96 | helpers.stripQuerystring = function(str) { 97 | if (util.isString(str)) { 98 | return str.split('?')[0]; 99 | } 100 | }; 101 | 102 | /** 103 | * Strip protocol from a `url`. Useful for displaying media that 104 | * may have an 'http' protocol on secure connections. 105 | * 106 | * ```handlebars 107 | * 108 | * {{stripProtocol url}} 109 | * 110 | * ``` 111 | * @param {String} `str` 112 | * @return {String} the url with http protocol stripped 113 | * @api public 114 | * @example {{ stripProtocol 'https://myurl/api/test' }} -> '//myurl/api/test' 115 | */ 116 | 117 | helpers.stripProtocol = function(str) { 118 | if (util.isString(str)) { 119 | var parsed = url.parse(str); 120 | parsed.protocol = ''; 121 | return parsed.format(); 122 | } 123 | }; 124 | -------------------------------------------------------------------------------- /lib/utils/createFrame.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = function createFrame(data) { 4 | if (typeof(data) !== 'object') { 5 | throw new TypeError('createFrame expects data to be an object'); 6 | } 7 | 8 | var frame = Object.assign({}, data); 9 | frame._parent = data; 10 | 11 | frame.extend = function(data) { 12 | Object.assign(this, data); 13 | }; 14 | 15 | if (arguments.length > 1) { 16 | var args = [].slice.call(arguments, 1); 17 | var len = args.length, i = -1; 18 | while (++i < len) { 19 | frame.extend(args[i] || {}); 20 | } 21 | } 22 | return frame; 23 | }; 24 | -------------------------------------------------------------------------------- /lib/utils/falsey.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | function falsey(val, keywords) { 4 | if (!val) return true; 5 | let words = keywords || falsey.keywords; 6 | if (!Array.isArray(words)) words = [words]; 7 | const lower = typeof val === 'string' ? val.toLowerCase() : null; 8 | for (const word of words) { 9 | if (word === val) { 10 | return true; 11 | } 12 | if (word === lower) { 13 | return true; 14 | } 15 | } 16 | return false; 17 | } 18 | 19 | falsey.keywords = [ 20 | '0', 21 | 'false', 22 | 'nada', 23 | 'nil', 24 | 'nay', 25 | 'nah', 26 | 'negative', 27 | 'no', 28 | 'none', 29 | 'nope', 30 | 'nul', 31 | 'null', 32 | 'nix', 33 | 'nyet', 34 | 'uh-uh', 35 | 'veto', 36 | 'zero' 37 | ]; 38 | 39 | module.exports = falsey; 40 | -------------------------------------------------------------------------------- /lib/utils/fn.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const isBlock = require('./isBlock'); 4 | const isOptions = require('./isOptions'); 5 | const fn = require('./fn'); 6 | 7 | /** 8 | * This code was taken directly from handlebars-helpers, 9 | * https://github.com/helpers/handlebars-utils/blob/master/index.js#L398 10 | * 11 | * that was taken directly from handlebars. 12 | * https://github.com/wycats/handlebars.js/blob/b55a120e8222785db3dc00096f6afbf91b656e8a/LICENSE 13 | * Released under the MIT License 14 | * Copyright (C) 2011-2016 by Yehuda Katz 15 | */ 16 | 17 | /** 18 | * Returns the given value or renders the block if it's a block helper. 19 | * 20 | * ```js 21 | * Handlebars.registerHelper('example', function(val, locals, options) { 22 | * return utils.fn(val, locals, options); 23 | * }); 24 | * ``` 25 | * @param {any} `val` 26 | * @param {Object} `options` 27 | * @param {Object} `context` 28 | * @return {String} Either returns the value, or renders the block. 29 | * @api public 30 | */ 31 | 32 | module.exports = function(val, context, options) { 33 | if (isOptions(val)) { 34 | return fn('', val, options); 35 | } 36 | if (isOptions(context)) { 37 | return fn(val, {}, context); 38 | } 39 | return isBlock(options) ? options.fn(context) : val; 40 | }; 41 | -------------------------------------------------------------------------------- /lib/utils/handlebarsUtils.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * This code was taken directly from handlebars-helpers, (extracting some utils to its own file) 5 | * https://github.com/helpers/handlebars-utils/blob/master/index.js#L398 6 | * 7 | * that was taken directly from handlebars. 8 | * https://github.com/wycats/handlebars.js/blob/b55a120e8222785db3dc00096f6afbf91b656e8a/LICENSE 9 | * Released under the MIT License 10 | * Copyright (C) 2011-2016 by Yehuda Katz 11 | */ 12 | 13 | var util = require('util'); 14 | var type = require('typeof-article'); 15 | var utils = exports = module.exports; 16 | 17 | utils.extend = extend; 18 | utils.escapeExpression = escapeExpression; 19 | utils.isEmpty = isEmpty; 20 | utils.createFrame = createFrame; 21 | utils.blockParams = blockParams; 22 | utils.appendContextPath = appendContextPath; 23 | 24 | utils.isObject = require('./isObject'); 25 | utils.isOptions = require('./isOptions'); 26 | utils.isUndefined = require('./isUndefined'); 27 | utils.result = require('./result'); 28 | utils.indexOf = require('./indexOf'); 29 | utils.isBlock = require('./isBlock'); 30 | utils.fn = require('./fn'); 31 | utils.inverse = require('./inverse'); 32 | utils.value = require('./value'); 33 | utils.options = require('./options'); 34 | utils.identity = require('./identity'); 35 | utils.isString = require('./isString'); 36 | 37 | var escape = { 38 | '&': '&', 39 | '<': '<', 40 | '>': '>', 41 | '"': '"', 42 | "'": ''', 43 | '`': '`', 44 | '=': '=' 45 | }; 46 | 47 | var badChars = /[&<>"'`=]/g; 48 | var possible = /[&<>"'`=]/; 49 | 50 | function escapeChar(chr) { 51 | return escape[chr]; 52 | } 53 | 54 | function extend(obj /* , ...source */) { 55 | for (var i = 1; i < arguments.length; i++) { 56 | for (var key in arguments[i]) { 57 | if (Object.prototype.hasOwnProperty.call(arguments[i], key)) { 58 | obj[key] = arguments[i][key]; 59 | } 60 | } 61 | } 62 | 63 | return obj; 64 | } 65 | 66 | var toString = Object.prototype.toString; 67 | 68 | utils.toString = toString; 69 | // Sourced from lodash 70 | // https://github.com/bestiejs/lodash/blob/master/LICENSE.txt 71 | /* eslint-disable func-style */ 72 | var isFunction = function isFunction(value) { 73 | return typeof value === 'function'; 74 | }; 75 | // fallback for older versions of Chrome and Safari 76 | /* istanbul ignore next */ 77 | if (isFunction(/x/)) { 78 | utils.isFunction = isFunction = function(value) { 79 | return typeof value === 'function' && toString.call(value) === '[object Function]'; 80 | }; 81 | } 82 | utils.isFunction = isFunction; 83 | 84 | /* eslint-enable func-style */ 85 | 86 | /* istanbul ignore next */ 87 | var isArray = Array.isArray || function(value) { 88 | return value && typeof value === 'object' 89 | ? toString.call(value) === '[object Array]' 90 | : false; 91 | }; 92 | 93 | utils.isArray = isArray; 94 | 95 | function escapeExpression(string) { 96 | if (typeof string !== 'string') { 97 | // don't escape SafeStrings, since they're already safe 98 | if (string && string.toHTML) { 99 | return string.toHTML(); 100 | } else if (string == null) { 101 | return ''; 102 | } else if (!string) { 103 | return string + ''; 104 | } 105 | 106 | // Force a string conversion as this will be done by the append regardless and 107 | // the regex test will do this transparently behind the scenes, causing issues if 108 | // an object's to string has escaped characters in it. 109 | string = '' + string; 110 | } 111 | 112 | if (!possible.test(string)) { 113 | return string; 114 | } 115 | return string.replace(badChars, escapeChar); 116 | } 117 | 118 | function createFrame(object) { 119 | var frame = extend({}, object); 120 | frame._parent = object; 121 | return frame; 122 | } 123 | 124 | function blockParams(params, ids) { 125 | params.path = ids; 126 | return params; 127 | } 128 | 129 | function appendContextPath(contextPath, id) { 130 | return (contextPath ? contextPath + '.' : '') + id; 131 | } 132 | 133 | // 134 | // The code below this line was not sourced from handlebars 135 | // -------------------------------------------------------- 136 | // 137 | 138 | utils.expectedType = function(param, expected, actual) { 139 | var exp = type.types[expected]; 140 | var val = util.inspect(actual); 141 | return 'expected ' + param + ' to be ' + exp + ' but received ' + type(actual) + ': ' + val; 142 | }; 143 | 144 | /** 145 | * Returns true if an `app` propery is on the context, which means 146 | * the context was created by [assemble][], [templates][], [verb][], 147 | * or any other library that follows this convention. 148 | * 149 | * ```js 150 | * Handlebars.registerHelper('example', function(val, options) { 151 | * var context = options.hash; 152 | * if (utils.isApp(this)) { 153 | * context = Object.assign({}, this.context, context); 154 | * } 155 | * // do stuff 156 | * }); 157 | * ``` 158 | * @param {any} `value` 159 | * @return {Boolean} 160 | * @api public 161 | */ 162 | 163 | utils.isApp = function(thisArg) { 164 | return utils.isObject(thisArg) 165 | && utils.isObject(thisArg.options) 166 | && utils.isObject(thisArg.app); 167 | }; 168 | 169 | /** 170 | * Get the context to use for rendering. 171 | * 172 | * @param {Object} `thisArg` Optional invocation context `this` 173 | * @return {Object} 174 | * @api public 175 | */ 176 | 177 | utils.context = function(thisArg, locals, options) { 178 | if (utils.isOptions(thisArg)) { 179 | return utils.context({}, locals, thisArg); 180 | } 181 | // ensure args are in the correct order 182 | if (utils.isOptions(locals)) { 183 | return utils.context(thisArg, options, locals); 184 | } 185 | var appContext = utils.isApp(thisArg) ? thisArg.context : {}; 186 | options = options || {}; 187 | 188 | // if "options" is not handlebars options, merge it onto locals 189 | if (!utils.isOptions(options)) { 190 | locals = Object.assign({}, locals, options); 191 | } 192 | // merge handlebars root data onto locals if specified on the hash 193 | if (utils.isOptions(options) && options.hash.root === true) { 194 | locals = Object.assign({}, options.data.root, locals); 195 | } 196 | var context = Object.assign({}, appContext, locals, options.hash); 197 | if (!utils.isApp(thisArg)) { 198 | context = Object.assign({}, thisArg, context); 199 | } 200 | if (utils.isApp(thisArg) && thisArg.view && thisArg.view.data) { 201 | context = Object.assign({}, context, thisArg.view.data); 202 | } 203 | return context; 204 | }; 205 | 206 | /** 207 | * Returns true if the given value is "empty". 208 | * 209 | * ```js 210 | * console.log(utils.isEmpty(0)); 211 | * //=> false 212 | * console.log(utils.isEmpty('')); 213 | * //=> true 214 | * console.log(utils.isEmpty([])); 215 | * //=> true 216 | * console.log(utils.isEmpty({})); 217 | * //=> true 218 | * ``` 219 | * @name .isEmpty 220 | * @param {any} `value` 221 | * @return {Boolean} 222 | * @api public 223 | */ 224 | 225 | function isEmpty(val) { 226 | if (val === 0 || typeof val === 'boolean') { 227 | return false; 228 | } 229 | if (val == null) { 230 | return true; 231 | } 232 | if (utils.isObject(val)) { 233 | val = Object.keys(val); 234 | } 235 | if (!val.length) { 236 | return true; 237 | } 238 | return false; 239 | } 240 | 241 | /** 242 | * Cast the given `val` to an array. 243 | * 244 | * ```js 245 | * console.log(utils.arrayify('')); 246 | * //=> [] 247 | * console.log(utils.arrayify('foo')); 248 | * //=> ['foo'] 249 | * console.log(utils.arrayify(['foo'])); 250 | * //=> ['foo'] 251 | * ``` 252 | * @param {any} `val` 253 | * @return {Array} 254 | * @api public 255 | */ 256 | 257 | utils.arrayify = function(val) { 258 | return val != null ? (Array.isArray(val) ? val : [val]) : []; 259 | }; 260 | 261 | /** 262 | * Try to parse the given `string` as JSON. Fails 263 | * gracefully and always returns an object if the value cannot be parsed. 264 | * 265 | * @param {String} `string` 266 | * @return {Object} 267 | * @api public 268 | */ 269 | 270 | utils.tryParse = function(str) { 271 | try { 272 | return JSON.parse(str); 273 | } catch (err) { } 274 | return {}; 275 | }; 276 | -------------------------------------------------------------------------------- /lib/utils/html.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var util = require('./handlebarsUtils'); 4 | var striptags = require('striptags'); 5 | 6 | /** 7 | * Expose `utils` 8 | */ 9 | 10 | var html = module.exports; 11 | 12 | /** 13 | * Remove extra newlines from HTML, respect indentation. 14 | * 15 | * @param {String} html 16 | * @return {String} 17 | * @api public 18 | */ 19 | 20 | html.condense = function(str) { 21 | return str.replace(/(\r\n|\r|\n|\u2028|\u2029) {2,}/g, '\n'); 22 | }; 23 | 24 | /** 25 | * Add a single newline above code comments in HTML 26 | * 27 | * @param {String} `html` 28 | * @return {String} 29 | * @api public 30 | */ 31 | 32 | html.padcomments = function(str) { 33 | return str.replace(/(\s*'), '\n'); 44 | }); 45 | }); 46 | 47 | describe('parseAttributes', function() { 48 | it('should parse attributes', function() { 49 | assert.equal(HTML.parseAttributes({a: 'b', c: 200 }), 'a="b" c="200"'); 50 | }); 51 | }); 52 | 53 | describe('toAttributes', function() { 54 | it('should convert an object hash into html attributes', function() { 55 | var hash = {disabled: true, display: 'hidden', class: 'fade'}; 56 | assert.equal(HTML.toAttributes(hash), ' disabled display="hidden" class="fade"'); 57 | }); 58 | }); 59 | }); 60 | }); 61 | -------------------------------------------------------------------------------- /test/uuid.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | require('mocha'); 4 | const assert = require('assert'); 5 | const uuid = require('../lib/uuid'); 6 | 7 | describe('uuid', function() { 8 | describe('generate', function() { 9 | it('should generate a valid uuid', function() { 10 | assert.match(uuid.uuid(), /^[0-9A-F]{8}-[0-9A-F]{4}-4[0-9A-F]{3}-[89AB][0-9A-F]{3}-[0-9A-F]{12}$/i); 11 | }); 12 | }); 13 | }); 14 | -------------------------------------------------------------------------------- /verbfile.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var fs = require('fs'); 4 | var path = require('path'); 5 | var yaml = require('js-yaml'); 6 | var link = require('markdown-link'); 7 | var exists = require('fs-exists-sync'); 8 | var isValid = require('is-valid-app'); 9 | var through = require('through2'); 10 | var File = require('vinyl'); 11 | 12 | module.exports = function(app) { 13 | if (!isValid(app, 'verbfile')) return; 14 | app.use(require('verb-generate-readme')); 15 | app.on('error', console.log); 16 | 17 | /** 18 | * Helpers 19 | */ 20 | 21 | app.helpers(require('template-helpers')()); 22 | customDocsHelpers(app); 23 | app.helper('yaml', function(str) { 24 | return yaml.safeLoad(str); 25 | }); 26 | 27 | /** 28 | * Tasks 29 | */ 30 | 31 | app.task('data', function(cb) { 32 | app.data({ 33 | before: { 34 | license: 'When this project was created some helpers were sourced from [Swag, by Elving Rodriguez](http://elving.github.com/swag/).' 35 | }, 36 | authors: [ 37 | { 38 | 'name': 'Brian Woodward', 39 | 'url': 'https://github.com/doowb', 40 | 'twitter': 'doowb', 41 | 'username': 'doowb' 42 | }, 43 | { 44 | 'name': 'Jon Schlinkert', 45 | 'url': 'https://github.com/jonschlinkert', 46 | 'twitter': 'jonschlinkert', 47 | 'username': 'jonschlinkert' 48 | } 49 | ] 50 | }); 51 | cb(); 52 | }); 53 | 54 | app.task('toc', function(cb) { 55 | return app.src('lib/*.js') 56 | .pipe(toc(app)); 57 | }); 58 | 59 | app.task('docs', function(cb) { 60 | return app.src('README.md') 61 | .pipe(through.obj(function(file, enc, next) { 62 | file.content = file.content.replace(/^(#{2,}\s*)\[\.([^\]]+)\]/gm, '$1[{{$2}}]'); 63 | next(null, file); 64 | })) 65 | .pipe(app.dest('.')); 66 | }); 67 | 68 | app.preRender(/\.md$/, function(file, next) { 69 | file.options.stripEmpty = false; 70 | next(); 71 | }); 72 | 73 | app.task('default', ['data', 'toc', 'readme', 'docs']); 74 | }; 75 | 76 | function toc(app, options) { 77 | options = options || {}; 78 | var total = { categories: 0, helpers: 0 }; 79 | var methods = {}; 80 | 81 | return through.obj(function(file, enc, next) { 82 | if (typeof file.stem === 'undefined') { 83 | file = new File(file); 84 | } 85 | 86 | file.base = app.cwd; 87 | file.cwd = app.cwd; 88 | 89 | try { 90 | if (file.stem !== 'index') { 91 | file.code = require(file.path); 92 | var newFile = { 93 | methods: require(file.path), 94 | test: { 95 | path: path.join('test', file.basename), 96 | code: {} 97 | }, 98 | code: {}, 99 | path: file.relative, 100 | base: file.base, 101 | cwd: file.cwd, 102 | relative: file.relative, 103 | stem: file.stem, 104 | data: { 105 | methods: {} 106 | } 107 | }; 108 | 109 | var testLine = matchTest(newFile.test.path); 110 | var codeLine = matchCode(newFile.path); 111 | 112 | for (var key in newFile.methods) { 113 | total.helpers++; 114 | 115 | if (newFile.methods.hasOwnProperty(key)) { 116 | newFile.data.methods[key] = { 117 | method: newFile.stem, 118 | stem: key, 119 | code: { 120 | path: newFile.relative, 121 | line: codeLine(key) 122 | }, 123 | test: { 124 | path: newFile.test.path, 125 | line: testLine(key) 126 | } 127 | }; 128 | } 129 | } 130 | 131 | methods[newFile.stem] = newFile; 132 | total.categories++; 133 | } 134 | next(); 135 | } catch (err) { 136 | next(err); 137 | } 138 | }, function(next) { 139 | app.data({total: total}); 140 | app.data({methods: methods}); 141 | next(); 142 | }); 143 | } 144 | 145 | function matchTest(fp) { 146 | if (!fp || !exists(fp)) return function() {}; 147 | var str = fs.readFileSync(fp, 'utf8'); 148 | var lines = str.split('\n'); 149 | return function(method) { 150 | var re = new RegExp('\\s*(describe|it)\\([\'"]\\.?(' + method + ')'); 151 | var len = lines.length, i = -1; 152 | while (++i < len) { 153 | var line = lines[i]; 154 | if (re.test(line)) { 155 | return i + 1; 156 | } 157 | } 158 | return; 159 | }; 160 | } 161 | 162 | function matchCode(fp) { 163 | if (!fp || !exists(fp)) return function() {}; 164 | var str = fs.readFileSync(fp, 'utf8'); 165 | var lines = str.split('\n'); 166 | return function(method) { 167 | var re = new RegExp('^helpers\\.(' + method + ')', 'gm'); 168 | var len = lines.length, i = -1; 169 | while (++i < len) { 170 | var line = lines[i]; 171 | if (re.test(line)) { 172 | return i + 1; 173 | } 174 | } 175 | return; 176 | }; 177 | } 178 | 179 | function customDocsHelpers(app) { 180 | app.helper('bullet', function(file) { 181 | return '- **' + link(file.stem, '#' + file.stem) + '**'; 182 | }); 183 | 184 | app.helper('issue', function(name) { 185 | var repo = app.cache.data.repository; 186 | return '[create issue](https://github.com/' + repo + '/issues/new?title=' + name + '%20helper)'; 187 | }); 188 | 189 | app.helper('sectionIssue', function(section) { 190 | var repo = app.cache.data.repository; 191 | var url = 'https://github.com/' + repo + '/issues?utf8=%E2%9C%93&q=is%3Aissue+is%3Aopen+'; 192 | return '[issues](' + url + section + '+helpers)'; 193 | }); 194 | 195 | app.helper('anchor', function(file) { 196 | return link(file.stem, '#' + file.stem); 197 | }); 198 | 199 | app.helper('codeLink', function(file) { 200 | return codeLink('code', file.code.path, file.code.line); 201 | }); 202 | 203 | app.helper('testLink', function(file) { 204 | var line = file.test.line; 205 | return line ? codeLink('tests', file.test.path, line) : '[no tests]'; 206 | }); 207 | 208 | app.helper('link', function(name, filepath) { 209 | return link(name, filepath); 210 | }); 211 | } 212 | 213 | function codeLink(title, path, start) { 214 | return link(title, path + '#L' + start); 215 | } 216 | --------------------------------------------------------------------------------