Comparing version 0.12.1 to 0.13.0
305
index.js
'use strict'; | ||
var isFalsey = require('falsey'); | ||
var getFile = require('get-view'); | ||
var delims = require('delimiter-regex'); | ||
var extend = require('extend-shallow'); | ||
var isObject = require('isobject'); | ||
var utils = require('./utils'); | ||
var regexCache = {}; | ||
/** | ||
* Expose `layouts` | ||
*/ | ||
module.exports = renderLayouts; | ||
/** | ||
* Cache compiled delimiter regex. | ||
* Apply a layout from the `layouts` object to `file.contents`. Layouts will be | ||
* recursively applied until a layout is not defined by the returned file. | ||
* | ||
* If delimiters need to be generated, this ensures that | ||
* runtime compilation only happens once. | ||
*/ | ||
var cache = {}; | ||
/** | ||
* Wrap one or more layouts around `string`. | ||
* ```js | ||
* var applyLayout = require('layouts'); | ||
* var layouts = {}; | ||
* layouts.default = new File({path: 'default', contents: new Buffer('foo\n{% body %}\nbar')}), | ||
* layouts.other = new File({path: 'other', contents: new Buffer('baz\n{% body %}\nqux')}); | ||
* layouts.other.layout = 'default'; | ||
* | ||
* ```js | ||
* renderLayouts(string, layoutName, layouts, options, fn); | ||
* var file = new File({path: 'whatever', contents: new Buffer('inner')}); | ||
* file.layout = 'other'; | ||
* | ||
* applyLayouts(file, layouts); | ||
* console.log(file.contents.toString()); | ||
* // foo | ||
* // bar | ||
* // inner | ||
* // baz | ||
* // qux | ||
* ``` | ||
* | ||
* @param {String} `string` The string to wrap with a layout. | ||
* @param {String} `layoutName` The name (key) of the layout object to use. | ||
* @param {Object} `layouts` Object of layout objects. | ||
* @param {Object} `options` Optionally define a `defaultLayout` (string), pass custom delimiters (`layoutDelims`) to use as the placeholder for the content insertion point, or change the name of the placeholder tag with the `contentTag` option. | ||
* @param {Function} `fn` Optionally pass a function to modify the context as each layout is applied. | ||
* @return {String} Returns the original string wrapped with one or more layouts. | ||
* @param {Object} `file` File object. This can be a plain object or [vinyl][] file. | ||
* @param {Object} `layouts` Object of file objects to use as "layouts". | ||
* @param {Object} `options` | ||
* @return {Object} Returns the original file object with layout(s) applied. | ||
* @api public | ||
*/ | ||
function renderLayouts(str, name, layouts, opts, fn) { | ||
if (isBuffer(str)) { | ||
str = String(str); | ||
module.exports = function applyLayouts(file, layouts, options) { | ||
if (!isObject(file)) { | ||
throw new TypeError('expected file to be an object'); | ||
} | ||
if (typeof file.path !== 'string') { | ||
throw new TypeError('expected file.path to be a string'); | ||
} | ||
if (typeof str !== 'string') { | ||
throw new TypeError('expected content to be a string.'); | ||
var opts = extend({}, options); | ||
var name = getLayoutName(file, opts.defaultLayout); | ||
if (name === false) { | ||
return file; | ||
} | ||
if (typeof name !== 'string') { | ||
throw new TypeError('expected layout name to be a string.'); | ||
} | ||
if (typeof opts === 'function') { | ||
fn = opts; | ||
opts = {}; | ||
if (typeof name === 'undefined') { | ||
throw new TypeError('expected layout name to be a string'); | ||
} | ||
opts = opts || {}; | ||
var layout = {}; | ||
var depth = 0; | ||
opts.tagname = opts.tagname || 'body'; | ||
var regex = createRegex(opts); | ||
var layout; | ||
var prev; | ||
// `view` is the object we'll use to store the result | ||
var view = {options: {}, history: []}; | ||
name = assertLayout(name, opts.defaultLayout); | ||
// recursively resolve layouts | ||
while (name && (prev !== name) && (layout = utils.getView(name, layouts))) { | ||
while (name && (prev !== name) && (layout = getFile(name, layouts))) { | ||
prev = name; | ||
name = resolveLayout(file, layout, opts, regex, name); | ||
} | ||
var delims = opts.layoutDelims; | ||
if (typeof name === 'string' && prev !== name) { | ||
throw new Error('could not find layout "' + name + '"'); | ||
} | ||
// `data` is passed to `wrapLayout` to resolve layouts | ||
// to the values on the data object. | ||
var data = {}; | ||
var tag = opts.contentTag || 'body'; | ||
data[tag] = str; | ||
file.contents = new Buffer(file.content); | ||
return file; | ||
}; | ||
// get info about the current layout | ||
var obj = new Layout({ | ||
name: name, | ||
layout: layout, | ||
before: str, | ||
depth: depth++ | ||
}); | ||
/** | ||
* Resolve the layout to use for the current file. | ||
*/ | ||
// get the delimiter regex | ||
var re = makeDelimiterRegex(delims, tag); | ||
function resolveLayout(file, layout, options, regex, name) { | ||
var val = toString(layout, options); | ||
// ensure that content is a string | ||
var content = (layout.content || layout.contents).toString(); | ||
if (!re.test(content)) { | ||
throw error(re, name, tag); | ||
} | ||
// inject the string into the layout | ||
str = wrapLayout(content, data, re, name, tag); | ||
obj.after = str; | ||
// if a callback is passed, allow it modify the result | ||
if (typeof fn === 'function') { | ||
fn(obj, view, depth); | ||
} | ||
// push info about the layout onto `history` | ||
view.history.push(obj); | ||
// should we recurse again? | ||
// does the `layout` specify another layout? | ||
name = assertLayout(layout.layout, opts.defaultLayout); | ||
if (!regex.test(val)) { | ||
var delims = utils.matchDelims(regex, options.tagname); | ||
throw new Error(`cannot find tag "${delims}" in "${name}"`); | ||
} | ||
if (typeof name === 'string' && prev !== name) { | ||
throw new Error('could not find layout "' + name + '"'); | ||
if (!layout.contents) { | ||
layout.contents = new Buffer(layout.content); | ||
} | ||
view.options = opts; | ||
view.result = str; | ||
return view; | ||
var str = toString(file, options); | ||
file.content = val.replace(regex, str); | ||
return getLayoutName(layout, options.defaultLayout); | ||
} | ||
/** | ||
* Create a new layout in the layout stack. | ||
* | ||
* @param {Object} `view` The layout view object | ||
* @param {Number} `depth` Current stack depth | ||
* Get the contents string from the file object | ||
*/ | ||
function Layout(view) { | ||
this.layout = view.layout; | ||
this.layout.name = view.name; | ||
this.before = view.before; | ||
this.depth = view.depth; | ||
return this; | ||
function toString(file, options) { | ||
var str = (file.content || file.contents || '').toString(); | ||
return options.trim ? str.trim() : str; | ||
} | ||
@@ -144,9 +119,10 @@ | ||
function assertLayout(value, defaultLayout) { | ||
if (value === false || (value && utils.isFalsey(value))) { | ||
return null; | ||
} else if (!value || value === true) { | ||
return defaultLayout || null; | ||
function getLayoutName(file, defaultLayout) { | ||
var name = file.layout; | ||
if (typeof name === 'undefined' || name === true) { | ||
return defaultLayout; | ||
} else if (name === false || name === null || name === 'null' || name === '' || (name && isFalsey(name))) { | ||
return false; | ||
} else { | ||
return value; | ||
return name; | ||
} | ||
@@ -156,112 +132,33 @@ } | ||
/** | ||
* Resolve template strings to the values on the given | ||
* `data` object. | ||
* Create the regex to use for matching | ||
*/ | ||
function wrapLayout(str, data, re, name, tag) { | ||
return str.replace(re, function(_, tagName) { | ||
var m = data[tagName.trim()]; | ||
if (typeof m === 'undefined') { | ||
throw error(re, name, tag); | ||
} | ||
return m; | ||
}); | ||
} | ||
/** | ||
* Make delimiter regex. | ||
* | ||
* @param {Sring|Array|RegExp} `syntax` | ||
* @return {RegExp} | ||
*/ | ||
function makeDelimiterRegex(syntax, tag) { | ||
if (!syntax) { | ||
syntax = makeTag(tag, ['{%', '%}']); | ||
function createRegex(options) { | ||
var opts = extend({}, options); | ||
var layoutDelims = options.delims || options.layoutDelims; | ||
var key = options.tagname; | ||
if (layoutDelims) key += layoutDelims; | ||
var regex; | ||
if (regexCache.hasOwnProperty(key)) { | ||
return regexCache[key]; | ||
} | ||
if (syntax instanceof RegExp) { | ||
return syntax; | ||
if (layoutDelims instanceof RegExp) { | ||
regexCache[key] = layoutDelims; | ||
return layoutDelims; | ||
} | ||
var name = syntax + ''; | ||
if (cache.hasOwnProperty(name)) { | ||
return cache[name]; | ||
if (Array.isArray(layoutDelims)) { | ||
opts.close = layoutDelims[1]; | ||
opts.open = layoutDelims[0]; | ||
} | ||
if (typeof syntax === 'string') { | ||
return new RegExp(syntax, 'g'); | ||
if (typeof layoutDelims === 'string') { | ||
regex = new RegExp(layoutDelims); | ||
regexCache[key] = regex; | ||
return regex; | ||
} | ||
return (cache[name] = utils.delims(syntax)); | ||
opts.flags = 'g'; | ||
opts.close = opts.close || '%}'; | ||
opts.open = opts.open || '{%'; | ||
regex = delims(opts); | ||
regexCache[key] = regex; | ||
return regex; | ||
} | ||
/** | ||
* Cast `val` to a string. | ||
*/ | ||
function makeTag(val, delims) { | ||
return delims[0].trim() | ||
+ ' (' | ||
+ String(val).trim() | ||
+ ') ' | ||
+ delims[1].trim(); | ||
} | ||
/** | ||
* Format an error message | ||
*/ | ||
function error(re, name, tag) { | ||
var delims = matchDelims(re, tag); | ||
return new Error('cannot find "' + delims + '" in "' + name + '"'); | ||
} | ||
/** | ||
* Only used if an error is thrown. Attempts to recreate | ||
* delimiters for the error message. | ||
*/ | ||
var types = { | ||
'{%=': function(str) { | ||
return '{%= ' + str + ' %}'; | ||
}, | ||
'{%-': function(str) { | ||
return '{%- ' + str + ' %}'; | ||
}, | ||
'{%': function(str) { | ||
return '{% ' + str + ' %}'; | ||
}, | ||
'{{': function(str) { | ||
return '{{ ' + str + ' }}'; | ||
}, | ||
'<%': function(str) { | ||
return '<% ' + str + ' %>'; | ||
}, | ||
'<%=': function(str) { | ||
return '<%= ' + str + ' %>'; | ||
}, | ||
'<%-': function(str) { | ||
return '<%- ' + str + ' %>'; | ||
} | ||
}; | ||
function matchDelims(re, str) { | ||
var ch = re.source.slice(0, 4); | ||
if (/[\\]/.test(ch.charAt(0))) { | ||
ch = ch.slice(1); | ||
} | ||
if (!/[-=]/.test(ch.charAt(2))) { | ||
ch = ch.slice(0, 2); | ||
} else { | ||
ch = ch.slice(0, 3); | ||
} | ||
return types[ch](str); | ||
} | ||
/** | ||
* Return true if the given value is a buffer | ||
*/ | ||
function isBuffer(val) { | ||
if (val && val.constructor && typeof val.constructor.isBuffer === 'function') { | ||
return val.constructor.isBuffer(val); | ||
} | ||
return false; | ||
} |
{ | ||
"name": "layouts", | ||
"description": "Wraps templates with layouts. Layouts can use other layouts and be nested to any depth. This can be used 100% standalone to wrap any kind of file with banners, headers or footer content. Use for markdown, HTML, handlebars views, lo-dash templates, etc. Layouts can also be vinyl files.", | ||
"version": "0.12.1", | ||
"version": "0.13.0", | ||
"homepage": "https://github.com/doowb/layouts", | ||
@@ -17,2 +17,3 @@ "author": "Brian Woodward (https://github.com/doowb)", | ||
"index.js", | ||
"LICENSE", | ||
"utils.js" | ||
@@ -28,8 +29,11 @@ ], | ||
"dependencies": { | ||
"delimiter-regex": "^1.3.1", | ||
"delimiter-regex": "^2.0.0", | ||
"extend-shallow": "^2.0.1", | ||
"falsey": "^0.3.0", | ||
"get-view": "^0.1.1", | ||
"get-view": "^0.1.2", | ||
"isobject": "^2.1.0", | ||
"lazy-cache": "^2.0.1" | ||
}, | ||
"devDependencies": { | ||
"arr-union": "^3.1.0", | ||
"gulp": "^3.9.1", | ||
@@ -40,5 +44,5 @@ "gulp-eslint": "^3.0.1", | ||
"gulp-mocha": "^2.2.0", | ||
"lodash": "^4.13.1", | ||
"mocha": "^2.5.3", | ||
"to-vinyl": "^0.2.0" | ||
"to-vinyl": "^0.2.0", | ||
"vinyl": "^1.1.1" | ||
}, | ||
@@ -113,5 +117,5 @@ "keywords": [ | ||
"reflinks": [ | ||
"vinyl", | ||
"verb", | ||
"verb-generate-readme", | ||
"verb" | ||
"vinyl" | ||
], | ||
@@ -118,0 +122,0 @@ "lint": { |
@@ -13,2 +13,5 @@ # layouts [![NPM version](https://img.shields.io/npm/v/layouts.svg?style=flat)](https://www.npmjs.com/package/layouts) [![NPM downloads](https://img.shields.io/npm/dm/layouts.svg?style=flat)](https://npmjs.org/package/layouts) [![Build Status](https://img.shields.io/travis/doowb/layouts.svg?style=flat)](https://travis-ci.org/doowb/layouts) | ||
- [History](#history) | ||
* [0.13.0](#0130) | ||
* [0.12.0](#0120) | ||
* [0.11.0](#0110) | ||
- [About](#about) | ||
@@ -140,14 +143,12 @@ * [Related projects](#related-projects) | ||
### [renderLayouts](index.js#L36) | ||
### [applyLayouts](index.js#L40) | ||
Wrap one or more layouts around `string`. | ||
Apply a layout from the `layouts` object to `file.contents`. Layouts will be recursively applied until a layout is not defined by the returned file. | ||
**Params** | ||
* `string` **{String}**: The string to wrap with a layout. | ||
* `layoutName` **{String}**: The name (key) of the layout object to use. | ||
* `layouts` **{Object}**: Object of layout objects. | ||
* `options` **{Object}**: Optionally define a `defaultLayout` (string), pass custom delimiters (`layoutDelims`) to use as the placeholder for the content insertion point, or change the name of the placeholder tag with the `contentTag` option. | ||
* `fn` **{Function}**: Optionally pass a function to modify the context as each layout is applied. | ||
* `returns` **{String}**: Returns the original string wrapped with one or more layouts. | ||
* `file` **{Object}**: File object. This can be a plain object or [vinyl](http://github.com/gulpjs/vinyl) file. | ||
* `layouts` **{Object}**: Object of file objects to use as "layouts". | ||
* `options` **{Object}** | ||
* `returns` **{Object}**: Returns the original file object with layout(s) applied. | ||
@@ -157,3 +158,18 @@ **Example** | ||
```js | ||
renderLayouts(string, layoutName, layouts, options, fn); | ||
var applyLayout = require('layouts'); | ||
var layouts = {}; | ||
layouts.default = new File({path: 'default', contents: new Buffer('foo\n{% body %}\nbar')}), | ||
layouts.other = new File({path: 'other', contents: new Buffer('baz\n{% body %}\nqux')}); | ||
layouts.other.layout = 'default'; | ||
var file = new File({path: 'whatever', contents: new Buffer('inner')}); | ||
file.layout = 'other'; | ||
applyLayouts(file, layouts); | ||
console.log(file.contents.toString()); | ||
// foo | ||
// bar | ||
// inner | ||
// baz | ||
// qux | ||
``` | ||
@@ -163,8 +179,19 @@ | ||
**v0.12.0** | ||
### 0.13.0 | ||
* _BREAKING CHANGE_ change `options.tag` to `options.contentTag` | ||
**Breaking changes** | ||
* The main `layouts()` function now expects a `file` object as the first argument. This can be an object with `path`, `layout` and `contents` properties, or a valid [vinyl](http://github.com/gulpjs/vinyl) file. See the [API docs](#api) for more details. | ||
### 0.12.0 | ||
**Breaking changes** | ||
* change `options.tag` to `options.contentTag` | ||
**Housekeeping** | ||
* update tests to use `assert` instead of `should` | ||
**v0.11.0** | ||
### 0.11.0 | ||
@@ -177,9 +204,9 @@ * All view objects must now have a `path` property, following [vinyl](http://github.com/gulpjs/vinyl) conventions. | ||
* [assemble](https://www.npmjs.com/package/assemble): Get the rocks out of your socks! Assemble makes you fast at creating web projects… [more](https://github.com/assemble/assemble) | [homepage](https://github.com/assemble/assemble) | ||
* [gulp](https://www.npmjs.com/package/gulp): The streaming build system | [homepage](http://gulpjs.com) | ||
* [handlebars-layouts](https://www.npmjs.com/package/handlebars-layouts): Handlebars helpers which implement layout blocks similar to Jade, Jinja, Swig, and Twig. | [homepage](https://github.com/shannonmoeller/handlebars-layouts) | ||
* [inject-snippet](https://www.npmjs.com/package/inject-snippet): Inject a snippet of code or content into a string. | [homepage](https://github.com/jonschlinkert/inject-snippet) | ||
* [templates](https://www.npmjs.com/package/templates): System for creating and managing template collections, and rendering templates with any node.js template engine… [more](https://github.com/jonschlinkert/templates) | [homepage](https://github.com/jonschlinkert/templates) | ||
* [verb](https://www.npmjs.com/package/verb): Documentation generator for GitHub projects. Verb is extremely powerful, easy to use, and is used… [more](https://github.com/verbose/verb) | [homepage](https://github.com/verbose/verb) | ||
* [vinyl](https://www.npmjs.com/package/vinyl): A virtual file format | [homepage](http://github.com/gulpjs/vinyl) | ||
* [assemble](https://www.npmjs.com/package/assemble): Get the rocks out of your socks! Assemble makes you fast at creating web projects… [more](https://github.com/assemble/assemble) | [homepage](https://github.com/assemble/assemble "Get the rocks out of your socks! Assemble makes you fast at creating web projects. Assemble is used by thousands of projects for rapid prototyping, creating themes, scaffolds, boilerplates, e-books, UI components, API documentation, blogs, building websit") | ||
* [gulp](https://www.npmjs.com/package/gulp): The streaming build system | [homepage](http://gulpjs.com "The streaming build system") | ||
* [handlebars-layouts](https://www.npmjs.com/package/handlebars-layouts): Handlebars helpers which implement layout blocks similar to Jade, Jinja, Swig, and Twig. | [homepage](https://github.com/shannonmoeller/handlebars-layouts "Handlebars helpers which implement layout blocks similar to Jade, Jinja, Swig, and Twig.") | ||
* [inject-snippet](https://www.npmjs.com/package/inject-snippet): Inject a snippet of code or content into a string. | [homepage](https://github.com/jonschlinkert/inject-snippet "Inject a snippet of code or content into a string.") | ||
* [templates](https://www.npmjs.com/package/templates): System for creating and managing template collections, and rendering templates with any node.js template engine… [more](https://github.com/jonschlinkert/templates) | [homepage](https://github.com/jonschlinkert/templates "System for creating and managing template collections, and rendering templates with any node.js template engine. Can be used as the basis for creating a static site generator or blog framework.") | ||
* [verb](https://www.npmjs.com/package/verb): Documentation generator for GitHub projects. Verb is extremely powerful, easy to use, and is used… [more](https://github.com/verbose/verb) | [homepage](https://github.com/verbose/verb "Documentation generator for GitHub projects. Verb is extremely powerful, easy to use, and is used on hundreds of projects of all sizes to generate everything from API docs to readmes.") | ||
* [vinyl](https://www.npmjs.com/package/vinyl): A virtual file format | [homepage](http://github.com/gulpjs/vinyl "A virtual file format") | ||
@@ -222,2 +249,2 @@ ### Contributing | ||
_This file was generated by [verb](https://github.com/verbose/verb), v0.9.0, on July 15, 2016._ | ||
_This file was generated by [verb](https://github.com/verbose/verb), v0.9.0, on July 25, 2016._ |
57
utils.js
@@ -7,6 +7,6 @@ 'use strict'; | ||
var lazy = require('lazy-cache')(require); | ||
var utils = module.exports = require('lazy-cache')(require); | ||
var fn = require; | ||
require = lazy; | ||
require = utils; | ||
require('falsey', 'isFalsey'); | ||
@@ -17,6 +17,55 @@ require('delimiter-regex', 'delims'); | ||
utils.isString = function(val) { | ||
return val && typeof val === 'string'; | ||
}; | ||
/** | ||
* Expose `utils` | ||
* Format an error message | ||
*/ | ||
module.exports = lazy; | ||
utils.error = function(re, tag, name) { | ||
var delims = utils.matchDelims(re, tag); | ||
return 'cannot find tag "' + delims + '" in "' + name + '"'; | ||
}; | ||
/** | ||
* Only used if an error is thrown. Attempts to recreate | ||
* delimiters for the error message. | ||
*/ | ||
var types = { | ||
'{%=': function(str) { | ||
return '{%= ' + str + ' %}'; | ||
}, | ||
'{%-': function(str) { | ||
return '{%- ' + str + ' %}'; | ||
}, | ||
'{%': function(str) { | ||
return '{% ' + str + ' %}'; | ||
}, | ||
'{{': function(str) { | ||
return '{{ ' + str + ' }}'; | ||
}, | ||
'<%': function(str) { | ||
return '<% ' + str + ' %>'; | ||
}, | ||
'<%=': function(str) { | ||
return '<%= ' + str + ' %>'; | ||
}, | ||
'<%-': function(str) { | ||
return '<%- ' + str + ' %>'; | ||
} | ||
}; | ||
utils.matchDelims = function(regex, tagname) { | ||
var ch = regex.source.slice(0, 4); | ||
if (/[\\]/.test(ch.charAt(0))) { | ||
ch = ch.slice(1); | ||
} | ||
if (!/[-=]/.test(ch.charAt(2))) { | ||
ch = ch.slice(0, 2); | ||
} else { | ||
ch = ch.slice(0, 3); | ||
} | ||
return types[ch](tagname); | ||
}; |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
17713
244
6
9
202
1
+ Addedextend-shallow@^2.0.1
+ Addedisobject@^2.1.0
+ Addeddelimiter-regex@2.0.0(transitive)
+ Addedextend-shallow@2.0.1(transitive)
- Removeddelimiter-regex@1.3.1(transitive)
Updateddelimiter-regex@^2.0.0
Updatedget-view@^0.1.2