Comparing version 0.3.0 to 0.4.0
272
beard.js
@@ -1,188 +0,138 @@ | ||
(function(){ | ||
module.exports = function(cache = {}, lookup = path => path) { | ||
let compiledCache = {}; | ||
let iterator = 0; | ||
var Beard = function(cache){ | ||
this._cache = cache; | ||
} | ||
const Beard = function() {} | ||
var iterator = 0; | ||
var ignores = {}; | ||
var ignoreId = 0; | ||
Beard.prototype = { | ||
render: (template, data = {}) => { | ||
iterator = 0; | ||
return compiled(template, data)(data); | ||
} | ||
}; | ||
var exps = { | ||
extend: (/{extend\s(.*?)}/), | ||
include: (/{include\s(.*?)}/g), | ||
block: (/{block\s+(.[^}]*)}([^]*?){endblock}/g), | ||
ignore: (/{ignore}([^]*?){endignore}/g), | ||
ignoreTemp: (/_ignore_([^]*?)_endignore_/g), | ||
statement: (/\{\s*([^}]+?)\s*\}/g), | ||
operators: (/\s+(and|or|eq|neq|is|isnt|not)\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$/) | ||
}; | ||
const exps = { | ||
extends: (/\{{extends\s\'([^}}]+?)\'\}}/g), | ||
include: (/^include\s\'([^\(]*?)\'$/g), | ||
includeFn: (/^include\((\s?\'([^\(]*?)\'\,\s?\{([^\)]*)\})\)$/g), | ||
block: (/{{block\s+(.[^}]*)}}([^]*?){{endblock}}/g), | ||
statement: (/{{\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$/) | ||
}; | ||
var operators = { | ||
and: ' && ', | ||
or: ' || ', | ||
eq: ' === ', | ||
neq: ' !== ', | ||
not: ' !', | ||
isnt: ' != ', | ||
is: ' == ' | ||
}; | ||
const parse = { | ||
include: (_, path) => `_buffer += compiled("${cache[lookup(path)]}", _data)(_data)`, | ||
includeFn: (_, __, path, data) => `_buffer += compiled("${cache[lookup(path)]}", _data)({${data}})`, | ||
block: (_, varname, content) => `{{:var ${varname} = compiled("${content}", _data)(_data)}}{{:_data["${varname}"] = ${varname}}}`, | ||
if: (_, statement) => `if (${statement}) {`, | ||
elseIf: (_, statement) => `} else if (${statement}) {`, | ||
else: () => '} else {', | ||
end: () => '}', | ||
var parse = { | ||
ignore: function(_, contents) { | ||
ignoreId += 1; | ||
ignores[ignoreId] = contents; | ||
return '_ignore_' + ignoreId + '_endignore_'; | ||
for: (_, key, value, object) => { | ||
if (!value) key = (value = key, 'iterator' + iterator++); | ||
return `for (var ${key} in ${object}){ var ${value} = ${object}[${key}];`; | ||
}, | ||
ignoreTemp: function(_, id) { | ||
return ignores[id]; | ||
}, | ||
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)}];`; | ||
} | ||
}; | ||
operators: function(_, op) { | ||
return operators[op]; | ||
}, | ||
function parser(match, inner) { | ||
const prev = inner; | ||
inner = inner | ||
.replace(exps.include, parse.include) | ||
.replace(exps.includeFn, parse.includeFn) | ||
.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); | ||
if: function(_, statement) { | ||
return 'if (' + statement + ') {'; | ||
}, | ||
return `"; ${(inner === prev && !/^:/.test(inner) ? ' _buffer += ' : '')} ${inner.replace(/\t|\n|\r|^:/, '')}; _buffer += "`; | ||
} | ||
elseIf: function(_, statement) { | ||
return '} else if (' + statement +') {'; | ||
}, | ||
function compiled(str, data) { | ||
let key = hash(str); | ||
else: function() { | ||
return '} else {'; | ||
}, | ||
if (!compiledCache[key]) { | ||
compiledCache[key] = compile(str); | ||
} | ||
for: function(_, key, value, object) { | ||
if (!value) key = (value = key, 'iterator' + iterator++); | ||
return 'for (var ' + key + ' in ' + object + '){' + 'var ' + value + ' = ' + object + '[' + key +'];'; | ||
}, | ||
return compiledCache[key]; | ||
} | ||
each: function(_, iter, value, array) { | ||
if (!value) iter = (value = iter, 'iterator' + iterator++); | ||
var length = 'length' + iterator++; | ||
return 'for (var ' + iter + ' = 0, ' + length + ' = ' + array + '.length; ' + iter + ' < ' + length + '; ' + iter + '++) {' + 'var ' + value + ' = ' + array + '[' + (iter) + '];'; | ||
}, | ||
function hash(str) { | ||
let hash = 5381; | ||
let i = str.length; | ||
end: function() { | ||
return '}'; | ||
while(i) { | ||
hash = (hash * 33) ^ str.charCodeAt(--i); | ||
} | ||
}; | ||
Beard.prototype = { | ||
return hash >>> 0; | ||
} | ||
parser: function(match, inner) { | ||
var prev = inner; | ||
inner = inner | ||
.replace(exps.operators, parse.operators) | ||
.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); | ||
function compile(str) { | ||
let layout; | ||
return '";' + (inner === prev ? ' _buffer += ' : '') + inner.replace(/\t|\n|\r/, '') + '; _buffer += "'; | ||
}, | ||
str = str | ||
.replace(exps.extends, (_, path) => { | ||
layout = cache[lookup(path)]; | ||
return ''; | ||
}) | ||
.replace(exps.block, parse.block) | ||
.replace(exps.statement, parser) | ||
.replace(/_buffer_\s\+=\s"";/g, '') | ||
.replace(/\n/g, '\\n') | ||
.replace(/\t/g, '\\t') | ||
.replace(/\r/g, '\\r'); | ||
parseExtend: function(template, data) { | ||
var matches; | ||
template = template.replace(exps.extend, function(){ | ||
matches = ([]).slice.call(arguments, 0); | ||
return ''; | ||
}); | ||
let fn = ` | ||
function _compiledTemplate(_data){ | ||
var _buffer = ""; | ||
if (matches && matches.length) { | ||
var path = matches[1]; | ||
var view = this._cache[path]; | ||
view = view.replace(exps.ignore, parse.ignore); | ||
view += "{block view}" + this.preRender(template, data) + "{endblock}"; | ||
return view; | ||
} else { | ||
return template; | ||
function _valForEval(val) { | ||
if (typeof val == 'function') return val.toString(); | ||
return JSON.stringify(val); | ||
} | ||
}, | ||
parseInclude: function(template, data) { | ||
return template.replace(exps.include, function(_, path){ | ||
return this.preRender(this._cache[path], data); | ||
}.bind(this)); | ||
}, | ||
for (var prop in _data) { | ||
if (_data.hasOwnProperty(prop)) { | ||
eval("var " + prop + " = " + _valForEval(_data[prop])); | ||
} | ||
} | ||
_buffer += "${str}"; | ||
`; | ||
parseBlock: function(template, data) { | ||
var matches = []; | ||
template = template.replace(exps.block, function(){ | ||
var arr = ([]).slice.call(arguments, 0); | ||
matches.push(arr); | ||
return ''; | ||
}); | ||
if (layout) { | ||
fn += ` | ||
_data['view'] = _buffer; | ||
_buffer = compiled("${layout}", _data)(_data); | ||
`; | ||
} | ||
matches.forEach(function(set){ | ||
// set[1] is the var name; | ||
// set[2] is the var value; | ||
data[set[1]] = this.compile(set[2])(data); | ||
}.bind(this)); | ||
fn += ` | ||
return _buffer; | ||
} | ||
`; | ||
return this.compile(template)(data); | ||
}, | ||
try { | ||
eval(fn); | ||
return _compiledTemplate.bind(_compiledTemplate); | ||
} catch (e) { | ||
throw new Error(`Compilation error: ${fn}`); | ||
} | ||
} | ||
preRender: function(template, data){ | ||
template = template.replace(exps.ignore, parse.ignore); | ||
template = this.parseExtend(template, data); | ||
template = this.parseInclude(template, data); | ||
template = this.parseBlock(template, data); | ||
return this.compile(template)(data); | ||
}, | ||
render: function(template, data){ | ||
template = this.preRender(template, data); | ||
template = template.replace(exps.ignoreTemp, parse.ignoreTemp); | ||
ignoreId = 0; | ||
ignores = {}; | ||
return template; | ||
}, | ||
compile: function(str) { | ||
str = str | ||
.replace(new RegExp('\\\\', 'g'), '\\\\').replace(/"/g, '\\"') | ||
.replace(exps.statement, this.parser) | ||
.replace(/_buffer_\s\+=\s"";/g, '') | ||
.replace(/(\{|\});/g, '$1') | ||
.replace(/\n/g, '\\n') | ||
.replace(/\t/g, '\\t') | ||
.replace(/\r/g, '\\r'); | ||
var fn = ( | ||
'var _buffer = ""; \ | ||
for (var prop in _data_) { \ | ||
if (_data_.hasOwnProperty(prop)) this[prop] = _data_[prop]; \ | ||
} \ | ||
_buffer += "' + str + '"; \ | ||
return _buffer;' | ||
); | ||
try { | ||
return new Function('_data_', fn); | ||
} catch (e) { | ||
throw new Error('Cant compile template:' + fn); | ||
} | ||
} | ||
return new Beard(); | ||
}; | ||
if (typeof module !== 'undefined') { | ||
module.exports = Beard; | ||
} else { | ||
window.Beard = Beard; | ||
} | ||
})(); |
{ | ||
"name": "beard", | ||
"version": "0.3.0", | ||
"description": "More than a mustache.", | ||
"keywords": ["template engine", "node", "browser"], | ||
"repository": "git://github.com/shanebo/beard.git", | ||
"author": "Shane Thacker <shane@steadymade.com>", | ||
"engine": ">= 0.4.1", | ||
"main": "./beard" | ||
"name": "beard", | ||
"version": "0.4.0", | ||
"description": "More than a mustache.", | ||
"keywords": [ | ||
"templating engine", | ||
"node" | ||
], | ||
"repository": "git://github.com/shanebo/beard.git", | ||
"author": "Shane Thacker <shane@steadymade.com>", | ||
"contributors": [ | ||
"Joe Osburn <joe@jnodev.com>" | ||
], | ||
"engine": ">= 0.4.1", | ||
"main": "./beard", | ||
"scripts": { | ||
"test": "mocha" | ||
}, | ||
"devDependencies": { | ||
"mocha": "4.1.0", | ||
"chai": "4.1.2", | ||
"brisky-performance": "1.4.2" | ||
} | ||
} |
@@ -16,32 +16,57 @@ Beard | ||
### Syntax ### | ||
### Install ### | ||
Beard.render(template, view); | ||
`npm install beard` | ||
### Arguments ### | ||
### API ### | ||
``` js | ||
const Beard = require('beard'); | ||
const engine = new Beard(cache, lookup); | ||
engine.render(template, locals); | ||
``` | ||
### Beard Constructor Arguments ### | ||
**cache** - (object) An object literal containing your templates. | ||
**lookup** - (function) A function that accepts the path value and can modify the path value before Beard looks up your template from the cache. E.g., `(path) => '/absolute/cached/path/${path}'`. | ||
### Render Arguments ### | ||
**template** - (string) A string to be parsed and populated by the view object. | ||
**view** - (object) An object of data and/or methods which will populate the template string. | ||
**locals** - (object) An object of data and/or methods which will populate the template string. | ||
### Example ### | ||
var Beard = require('beard'); | ||
``` js | ||
const templates = { | ||
'example': "{{noun}} get {{makeUpperCase('stinky')}}." | ||
}; | ||
var view = { | ||
noun: "Beards", | ||
makeUpperCase: function(str){ | ||
return str.toUpperCase(); | ||
} | ||
}; | ||
const locals = { | ||
noun: "Beards", | ||
makeUpperCase: function(str){ | ||
return str.toUpperCase(); | ||
} | ||
}; | ||
var html = Beard.render('{noun} are {makeUpperCase('awesome')}!', view); | ||
const Beard = require('beard'); | ||
const engine = new Beard(templates); | ||
const result = engine.render("{{include 'example'}}", locals); | ||
console.log(result); // returns 'Beards get STINKY.' | ||
``` | ||
More docs later... | ||
### More docs to come... ### | ||
* cache | ||
* optional cache lookup function | ||
### Thanks to ### | ||
* keeto (Mark Obcena) for the parser/compiler | ||
* shinetech (Danny Brain) for syntax ideas | ||
* shinetech (Danny Brain) for syntax ideas | ||
* joeosburn (Joe Osburn) for the updated compiler, cached compiled functions, tests, and benchmarks |
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
14905
7
343
72
3
1