beard
Advanced tools
Comparing version 0.5.3 to 0.5.4
341
beard.js
const fs = require('fs'); | ||
const exts = '(.brd$|.brd.html$)'; | ||
const exts = '(.beard$)'; | ||
const traversy = require('traversy'); | ||
const normalize = require('path').normalize; | ||
function hash(str) { | ||
let hash = 5381; | ||
let i = str.length; | ||
while (i) hash = (hash * 33) ^ str.charCodeAt(--i); | ||
return hash >>> 0; | ||
} | ||
class Beard { | ||
constructor(opts = {}) { | ||
if (!opts.hasOwnProperty('cache')) opts.cache = true; | ||
if (!opts.hasOwnProperty('asset')) opts.asset = path => false; | ||
opts.templates = opts.templates || {}; | ||
this.opts = opts; | ||
this.fnCache = {}; | ||
this.pathMap = {}; | ||
const cleanWhitespace = str => str.replace(/\s+/g, ' ').trim(); | ||
module.exports = function(opts = {}) { | ||
opts.cache = opts.cache != undefined ? opts.cache : true; | ||
opts.templates = opts.templates || {}; | ||
let fnCache = {}; | ||
let pathMap = {}; | ||
let iterator = 0; | ||
const Beard = function() { | ||
if (opts.root) { | ||
const regex = new RegExp(`(^${opts.root}|.brd$|.brd.html$)`, 'g'); | ||
traversy(opts.root, exts, (path) => { | ||
if (this.opts.root) { | ||
const regex = new RegExp(`(^${this.opts.root}|.beard$)`, 'g'); | ||
traversy(this.opts.root, exts, (path) => { | ||
const key = path.replace(regex, ''); | ||
const body = fs.readFileSync(path, 'utf8'); | ||
opts.templates[key] = opts.cache ? cleanWhitespace(body) : body; | ||
pathMap[key] = path; | ||
this.opts.templates[key] = this.opts.cache ? cleanWhitespace(body) : body; | ||
this.pathMap[key] = path; | ||
}); | ||
@@ -35,177 +26,183 @@ } | ||
Beard.prototype = { | ||
render: (path, data = {}) => { | ||
iterator = 0; | ||
let context = { | ||
globals: {}, | ||
locals: [data], | ||
path: null | ||
}; | ||
return compiled(path, '/')(context); | ||
} | ||
}; | ||
function resolvePath(path, parentPath) { | ||
if (path.startsWith('/')) { | ||
return path; | ||
compiled(path, context) { | ||
context.path = resolvePath(path, context.path); | ||
if (this.opts.cache) { | ||
const str = this.opts.templates[context.path]; | ||
const key = hash(context.path); | ||
if (!this.fnCache[key]) this.fnCache[key] = compile(str, context.path); | ||
return this.fnCache[key]; | ||
} else { | ||
const currentDir = parentPath.replace(/\/[^\/]+$/, ''); | ||
return normalize(`${currentDir}/${path}`); | ||
const str = fs.readFileSync(this.pathMap[context.path], 'utf8'); | ||
return compile(str, context.path); | ||
} | ||
} | ||
const exps = { | ||
extends: (/\{{extends\s\'([^}}]+?)\'\}}/g), | ||
include: (/^include\s\'([^\(]*?)\'$/g), | ||
includeFn: (/^include\((\s?\'([^\(]*?)\'\,\s?\{([^\)]*)\})\)$/g), | ||
block: (/^block\s+(.[^}]*)/g), | ||
blockEnd: (/^endblock$/g), | ||
encode: (/^\:(.*)/), | ||
comment: (/^\*.*\*$/g), | ||
statement: (/{{\s*([\S\s(?!}})]+?)\s*}}/g), | ||
if: (/^if\s+([^]*)$/), | ||
elseIf: (/^else\s+if\s+([^]*)$/), | ||
else: (/^else$/), | ||
for: (/^for\s+([$A-Za-z_][0-9A-Za-z_]*)(?:\s*,\s*([$A-Za-z_][0-9A-Za-z_]*))?\s+in\s+(.*)$/), | ||
each: (/^each\s+([$A-Za-z_][0-9A-Za-z_]*)(?:\s*,\s*([$A-Za-z_][0-9A-Za-z_]*))?\s+in\s(.*)$/), | ||
end: (/^end$/) | ||
}; | ||
asset(p, path) { | ||
const absolutePath = resolvePath(p, path); | ||
return this.opts.asset(absolutePath) || absolutePath; | ||
} | ||
const parse = { | ||
include: (_, includePath) => `_capture(compiled("${includePath}", path)(_context));`, | ||
includeFn: (_, __, includePath, data) => `_context.locals.push({${data}}); _capture(compiled("${includePath}", path)(_context)); _context.locals.pop();`, | ||
block: (_, blockname) => `_blockName = "${blockname}"; _blockCapture = "";`, | ||
blockEnd: () => 'eval(`var ${_blockName} = _blockCapture`); _context.globals[_blockName] = _blockCapture; _blockName = null;', | ||
encode: (_, statement) => `_encode(${statement});`, | ||
comment: () => '', | ||
if: (_, statement) => `if (${statement}) {`, | ||
elseIf: (_, statement) => `} else if (${statement}) {`, | ||
else: () => '} else {', | ||
end: () => '}', | ||
for: (_, key, value, object) => { | ||
if (!value) key = (value = key, 'iterator' + iterator++); | ||
return `for (var ${key} in ${object}){ var ${value} = ${object}[${key}];`; | ||
}, | ||
each: (_, iter, value, array) => { | ||
if (!value) iter = (value = iter, 'iterator' + iterator++); | ||
const length = 'length' + iterator++; | ||
return `for (var ${iter} = 0, ${length} = ${array}.length; ${iter} < ${length}; ${iter}++) { var ${value} = ${array}[${(iter)}];`; | ||
render(path, data = {}) { | ||
const context = { | ||
globals: {}, | ||
locals: [data], | ||
compiled: this.compiled.bind(this), | ||
asset: this.asset.bind(this), | ||
path: '' | ||
} | ||
}; | ||
return this.compiled(path, context)(context); | ||
} | ||
} | ||
function parser(match, inner) { | ||
const prev = inner; | ||
inner = inner | ||
.replace(exps.include, parse.include) | ||
.replace(exps.includeFn, parse.includeFn) | ||
.replace(exps.block, parse.block) | ||
.replace(exps.blockEnd, parse.blockEnd) | ||
.replace(exps.encode, parse.encode) | ||
.replace(exps.comment, parse.comment) | ||
.replace(exps.end, parse.end) | ||
.replace(exps.else, parse.else) | ||
.replace(exps.elseIf, parse.elseIf) | ||
.replace(exps.if, parse.if) | ||
.replace(exps.each, parse.each) | ||
.replace(exps.for, parse.for); | ||
const cleanWhitespace = str => str.replace(/\s+/g, ' ').trim(); | ||
const getDir = path => path.replace(/\/[^\/]+$/, ''); | ||
const reducer = (inner, tag) => inner.replace(exps[tag], parse[tag]); | ||
const uniqueIterator = value => Math.random().toString().substring(2); | ||
const tags = [ | ||
'include', 'block', 'blockEnd', | ||
'asset', 'put', 'encode', 'comment', | ||
'if', 'exists', 'elseIf', 'else', | ||
'for', 'each', 'end' | ||
]; | ||
const middle = inner === prev && !/^:/.test(inner) | ||
? `_capture(${inner});` | ||
: inner.replace(/\t|\n|\r|^:/, ''); | ||
const exps = { | ||
extends: (/\{{extends\s\'([^}}]+?)\'\}}/g), | ||
include: (/^include\s\'([^\(]*?)\'(\s*,\s+([\s\S]+))?$/m), | ||
asset: (/^asset\s+\'(.+)\'$/), | ||
put: (/^put\s+(.+)$/), | ||
exists: (/^exists\s+(.+)$/), | ||
block: (/^block\s+(.[^}]*)/), | ||
blockEnd: (/^endblock$/), | ||
encode: (/^\:(.*)/), | ||
comment: (/^\*.*\*$/), | ||
statement: (/{{\s*([\S\s(?!}})]+?)\s*}}(?!\})/g), | ||
if: (/^if\s+([^]*)$/), | ||
elseIf: (/^else\s+if\s+([^]*)$/), | ||
else: (/^else$/), | ||
for: (/^for\s+([$A-Za-z_][0-9A-Za-z_]*)(?:\s*,\s*([$A-Za-z_][0-9A-Za-z_]*))?\s+in\s+(.*)$/), | ||
each: (/^each\s+([$A-Za-z_][0-9A-Za-z_]*)(?:\s*,\s*([$A-Za-z_][0-9A-Za-z_]*))?\s+in\s(.*)$/), | ||
end: (/^end$/) | ||
}; | ||
return `"); ${middle} _capture("`; | ||
} | ||
function resolvePath(path, parentPath) { | ||
return path.startsWith('/') | ||
? path | ||
: normalize(`${getDir(parentPath)}/${path}`); | ||
} | ||
function compiled(path, parentPath) { | ||
const fullPath = resolvePath(path, parentPath); | ||
if (opts.cache) { | ||
const str = opts.templates[fullPath]; | ||
const key = hash(fullPath); | ||
if (!fnCache[key]) fnCache[key] = compile(str, fullPath); | ||
return fnCache[key]; | ||
} else { | ||
const str = fs.readFileSync(pathMap[fullPath], 'utf8'); | ||
return compile(str, fullPath); | ||
function hash(str) { | ||
let hash = 5381; | ||
let i = str.length; | ||
while (i) hash = (hash * 33) ^ str.charCodeAt(--i); | ||
return hash >>> 0; | ||
} | ||
const parse = { | ||
block: (_, blockname) => `_blockName = "${blockname}"; _blockCapture = "";`, | ||
blockEnd: () => 'eval(`var ${_blockName} = _blockCapture`); _context.globals[_blockName] = _blockCapture; _blockName = null;', | ||
asset: (_, path) => `_capture(_context.asset("${path}", _context.path));`, | ||
put: (_, varname) => `_capture(typeof ${varname} !== "undefined" ? ${varname} : "");`, | ||
exists: (_, varname) => `if (typeof ${varname} !== "undefined") {`, | ||
encode: (_, statement) => `_encode(${statement});`, | ||
comment: () => '', | ||
if: (_, statement) => `if (${statement}) {`, | ||
elseIf: (_, statement) => `} else if (${statement}) {`, | ||
else: () => '} else {', | ||
end: () => '}', | ||
include: (_, includePath, __, data) => { | ||
data = data || '{}'; | ||
return ` | ||
_context.locals.push(Object.assign(_context.locals[_context.locals.length - 1], ${data})); | ||
_capture(_context.compiled("${includePath}", _context)(_context)); | ||
_context.locals.pop(); | ||
`; | ||
}, | ||
for: (_, key, value, object) => { | ||
if (!value) { | ||
value = key; | ||
key = `_iterator_${uniqueIterator(value)}`; | ||
} | ||
return `for (var ${key} in ${object}) { var ${value} = ${object}[${key}];`; | ||
}, | ||
each: (_, iter, value, array) => { | ||
if (!value) { | ||
value = iter; | ||
iter = `_iterator_${uniqueIterator(value)}`; | ||
} | ||
const length = `_iterator_${uniqueIterator(value)}`; | ||
return `for (var ${iter} = 0, ${length} = ${array}.length; ${iter} < ${length}; ${iter}++) { var ${value} = ${array}[${iter}];`; | ||
} | ||
}; | ||
function compile(str, path) { | ||
let layout = ''; | ||
function parser(match, inner) { | ||
const prev = inner; | ||
inner = tags.reduce(reducer, inner); | ||
const middle = inner === prev && !/^:/.test(inner) | ||
? `_capture(${inner});` | ||
: inner.replace(/\t|\n|\r|^:/, ''); | ||
return `"); ${middle} _capture("`; | ||
} | ||
str = str | ||
.replace(exps.extends, (_, path) => { | ||
layout = ` | ||
_context.globals.view = _buffer; | ||
_buffer = compiled('${path}', path)(_context); | ||
`; | ||
return ''; | ||
}) | ||
.replace(new RegExp('\\\\', 'g'), '\\\\').replace(/"/g, '\\"') | ||
.replace(exps.statement, parser); | ||
function compile(str, context) { | ||
let layout = ''; | ||
const fn = ` | ||
function _compiledFn(_context){ | ||
var path = '${path}'; | ||
var _buffer = ''; | ||
var _blockName; | ||
var _blockCapture; | ||
str = str | ||
.replace(exps.extends, (_, path) => { | ||
layout = ` | ||
_context.globals.view = _buffer; | ||
_buffer = _context.compiled('${path}', _context)(_context); | ||
`; | ||
return ''; | ||
}) | ||
.replace(new RegExp('\\\\', 'g'), '\\\\').replace(/"/g, '\\"') | ||
.replace(exps.statement, parser); | ||
function _capture(str) { | ||
if (_blockName) { | ||
_blockCapture += str; | ||
} else { | ||
_buffer += str; | ||
} | ||
} | ||
const fn = ` | ||
var _buffer = ''; | ||
var _blockName; | ||
var _blockCapture; | ||
function _encode(str) { | ||
_capture(str | ||
.replace(/&(?!\\w+;)/g, '&') | ||
.replace(/\</g, '<') | ||
.replace(/\>/g, '>') | ||
.replace(/\"/g, '"') | ||
.replace(/\'/g, ''') | ||
.replace(/\\//g, '/')); | ||
function _capture(str) { | ||
if (_blockName) { | ||
_blockCapture += str; | ||
} else { | ||
_buffer += str; | ||
} | ||
} | ||
function exists(varname) { | ||
return eval('typeof ' + varname + ' !== "undefined";'); | ||
} | ||
function _encode(str) { | ||
_capture(str | ||
.replace(/&(?!\\w+;)/g, '&') | ||
.replace(/\</g, '<') | ||
.replace(/\>/g, '>') | ||
.replace(/\"/g, '"') | ||
.replace(/\'/g, ''') | ||
.replace(/\\//g, '/')); | ||
} | ||
function put(varname) { | ||
return exists(varname) | ||
? eval(varname) | ||
: ''; | ||
for (var prop in _context.globals) { | ||
if (_context.globals.hasOwnProperty(prop)) { | ||
eval('var ' + prop + ' = _context.globals[prop]'); | ||
} | ||
} | ||
for (var prop in _context.globals) { | ||
if (_context.globals.hasOwnProperty(prop)) { | ||
eval('var ' + prop + ' = _context.globals[prop]'); | ||
} | ||
var _locals = _context.locals[_context.locals.length - 1]; | ||
for (var prop in _locals) { | ||
if (_locals.hasOwnProperty(prop)) { | ||
eval('var ' + prop + ' = _locals[prop]'); | ||
} | ||
} | ||
var _locals = _context.locals[_context.locals.length - 1]; | ||
for (var prop in _locals) { | ||
if (_locals.hasOwnProperty(prop)) { | ||
eval('var ' + prop + ' = _locals[prop]'); | ||
} | ||
} | ||
_capture("${str}"); | ||
${layout} | ||
return _buffer; | ||
`.replace(/_capture\(""\);(\s+)?/g, ''); | ||
_capture("${str}"); | ||
${layout} | ||
return _buffer; | ||
} | ||
`.replace(/_capture\(""\);(\s+)?/g, ''); | ||
try { | ||
eval(cleanWhitespace(fn)); | ||
return _compiledFn.bind(_compiledFn); | ||
} catch (e) { | ||
throw new Error(`Compilation error: ${fn}`); | ||
} | ||
try { | ||
return new Function('_context', cleanWhitespace(fn)); | ||
} catch (e) { | ||
throw new Error(`Compilation error: ${fn}`); | ||
} | ||
} | ||
return new Beard(); | ||
}; | ||
module.exports = opts => new Beard(opts); |
{ | ||
"name": "beard", | ||
"version": "0.5.3", | ||
"version": "0.5.4", | ||
"description": "More than a mustache.", | ||
@@ -5,0 +5,0 @@ "license": "MIT", |
@@ -41,2 +41,3 @@ # Beard | ||
- **cache** (boolean) - Set to `false` to disable caching of template files. Defaults to `true`. | ||
- **asset** (function) - Callback used for `asset` tag. Looks up asset paths. See asset example below. | ||
@@ -73,3 +74,7 @@ ### Render Arguments | ||
{{include 'template'}} | ||
{{include('template', {arg: 'val', arg2: 'val2'})}} | ||
{{include 'template', {arg: 'val', arg2: 'val2'}}} | ||
{{include 'template', { | ||
arg1: 'val1', | ||
arg2: 'val2' | ||
}}} | ||
``` | ||
@@ -106,4 +111,41 @@ | ||
### asset | ||
Assets are used to reference external files. You can control and modify the behavior of the tag via | ||
the `assets` callback option. | ||
``` | ||
engine = beard({ | ||
asset: path => '/assets/' + path | ||
}); | ||
``` | ||
Used in a template: | ||
``` | ||
<html> | ||
<head><link rel="stylesheet" type="text/css" href="{{asset 'styles.css'}}"></head> | ||
</html> | ||
``` | ||
Returns: | ||
``` | ||
<html> | ||
<head><link rel="stylesheet" type="text/css" href="/assets/styles.css"></head> | ||
</html> | ||
``` | ||
### put | ||
The put tag outputs a local variable or a block, or an empty string if the value doesn't exist. | ||
``` | ||
{{put foo}} | ||
``` | ||
This will output the value of `foo`, if defined, or a blank string if not. Conversely, accessing it | ||
directly, such as `{{foo}}` would raise an error if it were undefined. | ||
### block | ||
Makes content available as variable name. | ||
Make content available for rendering in any context (such as an extended layout or an included partial.) | ||
@@ -130,2 +172,31 @@ ``` | ||
You can also conditionally check if a block is set. | ||
``` | ||
{{block cart}} | ||
everything you have put in your cart... | ||
{{endblock}} | ||
// another template | ||
{{exists cart}} | ||
{{cart}} | ||
{{else}} | ||
Your cart is empty. | ||
{{end}} | ||
``` | ||
Will returns: | ||
``` | ||
everything you have put in your cart... | ||
``` | ||
Using `put` is a simple way to output the block content if you are unsure if it has been set: | ||
``` | ||
{{put header}} | ||
``` | ||
This will output an empty string if the header has not been set. | ||
### conditionals | ||
@@ -166,2 +237,2 @@ | ||
Released under [MIT license](http://en.wikipedia.org/wiki/MIT_License). | ||
Released under [MIT license](http://en.wikipedia.org/wiki/MIT_License). |
Sorry, the diff of this file is not supported yet
Uses eval
Supply chain riskPackage uses eval() which is a dangerous function. This prevents the code from running in certain environments and increases the risk that the code may contain exploits or malicious behavior.
Found 1 instance in 1 package
Uses eval
Supply chain riskPackage uses eval() which is a dangerous function. This prevents the code from running in certain environments and increases the risk that the code may contain exploits or malicious behavior.
Found 1 instance in 1 package
14371
246
234