hogan.js
Advanced tools
Comparing version 1.0.2 to 1.0.4-dev
502
lib/hogan.js
@@ -16,500 +16,6 @@ /* | ||
var HoganTemplate = (function () { | ||
// This file is for use with Node.js. See dist/ for browser files. | ||
function constructor(text) { | ||
this.text = text; | ||
} | ||
constructor.prototype = { | ||
// render: replaced by generated code. | ||
r: function (context, partials) { return ''; }, | ||
// variable escaping | ||
v: hoganEscape, | ||
render: function render(context, partials) { | ||
return this.r(context, partials); | ||
}, | ||
// tries to find a partial in the curent scope and render it | ||
rp: function(name, context, partials, indent) { | ||
var partial = partials[name]; | ||
if (!partial) { | ||
return ''; | ||
} | ||
return partial.render(context, partials); | ||
}, | ||
// render a section | ||
rs: function(context, partials, section) { | ||
var buf = '', | ||
tail = context[context.length - 1]; | ||
if (!isArray(tail)) { | ||
buf = section(context, partials); | ||
return buf; | ||
} | ||
for (var i = 0; i < tail.length; i++) { | ||
context.push(tail[i]); | ||
buf += section(context, partials); | ||
context.pop(); | ||
} | ||
return buf; | ||
}, | ||
// maybe start a section | ||
s: function(val, ctx, partials, inverted, start, end) { | ||
var pass; | ||
if (isArray(val) && val.length === 0) { | ||
return false; | ||
} | ||
if (!inverted && typeof val == 'function') { | ||
val = this.ls(val, ctx, partials, start, end); | ||
} | ||
pass = (val === '') || !!val; | ||
if (!inverted && pass && ctx) { | ||
ctx.push((typeof val == 'object') ? val : ctx[ctx.length - 1]); | ||
} | ||
return pass; | ||
}, | ||
// find values with dotted names | ||
d: function(key, ctx, partials, returnFound) { | ||
var names = key.split('.'), | ||
val = this.f(names[0], ctx, partials, returnFound), | ||
cx = null; | ||
if (key === '.' && isArray(ctx[ctx.length - 2])) { | ||
return ctx[ctx.length - 1]; | ||
} | ||
for (var i = 1; i < names.length; i++) { | ||
if (val && typeof val == 'object' && names[i] in val) { | ||
cx = val; | ||
val = val[names[i]]; | ||
} else { | ||
val = ''; | ||
} | ||
} | ||
if (returnFound && !val) { | ||
return false; | ||
} | ||
if (!returnFound && typeof val == 'function') { | ||
ctx.push(cx); | ||
val = this.lv(val, ctx, partials); | ||
ctx.pop(); | ||
} | ||
return val; | ||
}, | ||
// find values with normal names | ||
f: function(key, ctx, partials, returnFound) { | ||
var val = false, | ||
v = null, | ||
found = false; | ||
for (var i = ctx.length - 1; i >= 0; i--) { | ||
v = ctx[i]; | ||
if (v && typeof v == 'object' && key in v) { | ||
val = v[key]; | ||
found = true; | ||
break; | ||
} | ||
} | ||
if (!found) { | ||
return (returnFound) ? false : ""; | ||
} | ||
if (!returnFound && typeof val == 'function') { | ||
val = this.lv(val, ctx, partials); | ||
} | ||
return val; | ||
}, | ||
// higher order templates | ||
ho: function(val, cx, partials, text) { | ||
var t = val.call(cx, text, function(t) { | ||
return Hogan.compile(t).render(cx); | ||
}); | ||
var s = Hogan.compile(t.toString()).render(cx, partials); | ||
this.b = s; | ||
return false; | ||
}, | ||
// higher order template result buffer | ||
b: '', | ||
// lambda replace section | ||
ls: function(val, ctx, partials, start, end) { | ||
var cx = ctx[ctx.length - 1], | ||
t = val.call(cx); | ||
if (val.length > 0) { | ||
return this.ho(val, cx, partials, this.text.substring(start, end)); | ||
} | ||
if (typeof t == 'function') { | ||
return this.ho(t, cx, partials, this.text.substring(start, end)); | ||
} | ||
return t; | ||
}, | ||
// lambda replace variable | ||
lv: function(val, ctx, partials) { | ||
var cx = ctx[ctx.length - 1]; | ||
return Hogan.compile(val.call(cx).toString()).render(cx, partials); | ||
} | ||
}; | ||
var rAmp = /&/g, rLt = /</g, rGt = />/g, rApos =/\'/g, | ||
rQuot = /\"/g, hChars =/[&<>\"\']/; | ||
function hoganEscape(str) { | ||
var s = String(str === null ? '' : str); | ||
return hChars.test(s) ? s.replace(rAmp,'&') | ||
.replace(rLt,'<').replace(rGt,'>') | ||
.replace(rApos,''').replace(rQuot, '"') : s; | ||
} | ||
var isArray = Array.isArray || function(a) { | ||
return Object.prototype.toString.call(a) === '[object Array]'; | ||
}; | ||
return constructor; | ||
})(); | ||
var Hogan = (function () { | ||
// Setup regex assignments | ||
// remove whitespace according to Mustache spec | ||
var rIsWhitespace = /\S/, | ||
rQuot = /\"/g, | ||
rNewline = /\n/g, | ||
rCr = /\r/g, | ||
rSlash = /\\/g, | ||
tagTypes = { | ||
'#': 1, '^': 2, '/': 3, '!': 4, '>': 5, | ||
'<': 6, '=': 7, '_v': 8, '{': 9, '&': 10 | ||
}; | ||
function scan(text) { | ||
var len = text.length, | ||
IN_TEXT = 0, | ||
IN_TAG_TYPE = 1, | ||
IN_TAG = 2, | ||
state = IN_TEXT, | ||
tagType = null, | ||
tag = null, | ||
buf = '', | ||
tokens = [], | ||
seenTag = false, | ||
i = 0, | ||
lineStart = 0, | ||
otag = '{{', | ||
ctag = '}}'; | ||
function addBuf() { | ||
if (buf.length > 0) { | ||
tokens.push(new String(buf)); | ||
buf = ''; | ||
} | ||
} | ||
function lineIsWhitespace() { | ||
var isAllWhitespace = true; | ||
for (var j = lineStart; j < tokens.length; j++) { | ||
isAllWhitespace = | ||
(tokens[j].tag && tagTypes[tokens[j].tag] < tagTypes['_v']) || | ||
(!tokens[j].tag && tokens[j].match(rIsWhitespace) === null); | ||
if (!isAllWhitespace) { | ||
return false; | ||
} | ||
} | ||
return isAllWhitespace; | ||
} | ||
function filterLine(haveSeenTag, noNewLine) { | ||
addBuf(); | ||
if (haveSeenTag && lineIsWhitespace()) { | ||
for (var j = lineStart; j < tokens.length; j++) { | ||
if (!tokens[j].tag) { | ||
tokens.splice(j, 1); | ||
} | ||
} | ||
} else if (!noNewLine) { | ||
tokens.push({tag:'\n'}); | ||
} | ||
seenTag = false; | ||
lineStart = tokens.length; | ||
} | ||
function changeDelimiters(text, index) { | ||
var close = '=' + ctag, | ||
closeIndex = text.indexOf(close, index), | ||
delimiters = trim(text.substring(text.indexOf('=', index) + 1, | ||
closeIndex)).split(' '); | ||
otag = delimiters[0]; | ||
ctag = delimiters[1]; | ||
return closeIndex + close.length - 1; | ||
} | ||
for (i = 0; i < len; i++) { | ||
if (state == IN_TEXT) { | ||
if (tagChange(otag, text, i)) { | ||
--i; | ||
addBuf(); | ||
state = IN_TAG_TYPE; | ||
} else { | ||
if (text.charAt(i) == '\n') { | ||
filterLine(seenTag); | ||
} else { | ||
buf += text.charAt(i); | ||
} | ||
} | ||
} else if (state == IN_TAG_TYPE) { | ||
i += otag.length - 1; | ||
tag = tagTypes[text.charAt(i + 1)]; | ||
tagType = tag ? text.charAt(i + 1) : '_v'; | ||
seenTag = i; | ||
if (tagType == '=') { | ||
i = changeDelimiters(text, i); | ||
state = IN_TEXT; | ||
} else { | ||
if (tag) { | ||
i++; | ||
} | ||
state = IN_TAG; | ||
} | ||
} else { | ||
if (tagChange(ctag, text, i)) { | ||
i += ctag.length - 1; | ||
tokens.push({tag: tagType, n: trim(buf), | ||
i: (tagType == '/') ? seenTag - 1 : i + 1}); | ||
buf = ''; | ||
state = IN_TEXT; | ||
if (tagType == '{') { | ||
i++; | ||
} | ||
} else { | ||
buf += text.charAt(i); | ||
} | ||
} | ||
} | ||
filterLine(seenTag, true); | ||
return tokens; | ||
} | ||
function trim(s) { | ||
if (s.trim) { | ||
return s.trim(); | ||
} | ||
return s.replace(/^\s*|\s*$/g, ''); | ||
} | ||
function tagChange(tag, text, index) { | ||
if (text.charAt(index) != tag.charAt(0)) { | ||
return false; | ||
} | ||
for (var i = 1, l = tag.length; i < l; i++) { | ||
if (text.charAt(index + i) != tag.charAt(i)) { | ||
return false; | ||
} | ||
} | ||
return true; | ||
} | ||
function buildTree(tokens, kind, stack, customTags) { | ||
var instructions = [], | ||
opener = null, | ||
token = null; | ||
while (tokens.length > 0) { | ||
token = tokens.shift(); | ||
if (token.tag == '#' || token.tag == '^' || | ||
isOpener(token, customTags)) { | ||
stack.push(token); | ||
token.nodes = buildTree(tokens, token.tag, stack, customTags); | ||
instructions.push(token); | ||
} else if (token.tag == '/') { | ||
if (stack.length === 0) { | ||
throw new Error('Closing tag without opener: /' + token.n); | ||
} | ||
opener = stack.pop(); | ||
if (token.n != opener.n && !isCloser(token.n, opener.n, customTags)) { | ||
throw new Error('Nesting error: ' + opener.n + ' vs. ' + token.n); | ||
} | ||
opener.end = token.i; | ||
return instructions; | ||
} else { | ||
instructions.push(token); | ||
} | ||
} | ||
if (stack.length > 0) { | ||
throw new Error('missing closing tag: ' + stack.pop().n); | ||
} | ||
return instructions; | ||
} | ||
function isOpener(token, tags) { | ||
for (var i = 0, l = tags.length; i < l; i++) { | ||
if (tags[i].o == token.n) { | ||
token.tag = '#'; | ||
return true; | ||
} | ||
} | ||
} | ||
function isCloser(close, open, tags) { | ||
for (var i = 0, l = tags.length; i < l; i++) { | ||
if (tags[i].c == close && tags[i].o == open) { | ||
return true; | ||
} | ||
} | ||
} | ||
function generate(tree, text, options) { | ||
var code = 'var c = [cx];var b = "";var _ = this;' + | ||
walk(tree) + 'return b;'; | ||
if (options.asString) { | ||
return 'function(cx,p){' + code + ';}'; | ||
} | ||
var template = new HoganTemplate(text); | ||
template.r = new Function('cx', 'p', code); | ||
return template; | ||
} | ||
function esc(s) { | ||
return s.replace(rSlash, '\\\\') | ||
.replace(rQuot, '\\\"') | ||
.replace(rNewline, '\\n') | ||
.replace(rCr, '\\r'); | ||
} | ||
function chooseMethod(s) { | ||
return (~s.indexOf('.')) ? 'd' : 'f'; | ||
} | ||
function walk(tree) { | ||
var code = ''; | ||
for (var i = 0, l = tree.length; i < l; i++) { | ||
var tag = tree[i].tag; | ||
if (tag == '#') { | ||
code += section(tree[i].nodes, tree[i].n, chooseMethod(tree[i].n), | ||
tree[i].i, tree[i].end); | ||
} else if (tag == '^') { | ||
code += invertedSection(tree[i].nodes, tree[i].n, | ||
chooseMethod(tree[i].n)); | ||
} else if (tag == '<' || tag == '>') { | ||
code += partial(tree[i].n); | ||
} else if (tag == '{' || tag == '&') { | ||
code += tripleStache(tree[i].n, chooseMethod(tree[i].n)); | ||
} else if (tag == '\n') { | ||
code += text('\n'); | ||
} else if (tag == '_v') { | ||
code += variable(tree[i].n, chooseMethod(tree[i].n)); | ||
} else if (tag === undefined) { | ||
code += text(tree[i]); | ||
} | ||
} | ||
return code; | ||
} | ||
function section(nodes, id, method, start, end) { | ||
return 'if(_.s(_.' + method + '("' + esc(id) + '",c,p,1),' + | ||
'c,p,0,' + start + ',' + end + ')){' + | ||
'b += _.rs(c,p,' + | ||
'function(c,p){ var b = "";' + | ||
walk(nodes) + | ||
'return b;});c.pop();}' + | ||
'else{b += _.b; _.b = ""};'; | ||
} | ||
function invertedSection(nodes, id, method) { | ||
return 'if (!_.s(_.' + method + '("' + esc(id) + '",c,p,1),c,p,1,0,0)){' + | ||
walk(nodes) + | ||
'};'; | ||
} | ||
function partial(id) { | ||
return 'b += _.rp("' + esc(id) + '",c[c.length - 1],p);'; | ||
} | ||
function tripleStache(id, method) { | ||
return 'b += (_.' + method + '("' + esc(id) + '",c,p,0));'; | ||
} | ||
function variable(id, method) { | ||
return 'b += (_.v(_.' + method + '("' + esc(id) + '",c,p,0)));'; | ||
} | ||
function text(id) { | ||
return 'b += "' + esc(id) + '";'; | ||
} | ||
return ({ | ||
scan: scan, | ||
parse: function(tokens, options) { | ||
options = options || {}; | ||
return buildTree(tokens, '', [], options.sectionTags || []); | ||
}, | ||
cache: {}, | ||
compile: function(text, options) { | ||
// options | ||
// | ||
// asString: false (default) | ||
// | ||
// sectionTags: [{o: '_foo', c: 'foo'}] | ||
// An array of object with o and c fields that indicate names for custom | ||
// section tags. The example above allows parsing of {{_foo}}{{/foo}}. | ||
// | ||
options = options || {}; | ||
var t = this.cache[text]; | ||
if (t) { | ||
return t; | ||
} | ||
t = generate(this.parse(scan(text), options), text, options); | ||
return this.cache[text] = t; | ||
} | ||
}); | ||
})(); | ||
// Export the hogan constructor for Node.js and CommonJS. | ||
if (typeof module !== 'undefined' && module.exports) { | ||
module.exports = Hogan; | ||
module.exports.Template = HoganTemplate; | ||
} else if (typeof exports !== 'undefined') { | ||
exports.Hogan = Hogan; | ||
exports.HoganTemplate = HoganTemplate; | ||
} | ||
// Expose Hogan as an AMD module. | ||
if (typeof define === 'function' && define.amd) { | ||
define(function () { return Hogan; }); | ||
} | ||
var Hogan = require('./compiler'); | ||
Hogan.Template = require('./template').Template; | ||
module.exports = Hogan; |
{ | ||
"name": "hogan.js" | ||
, "description": "A mustache compiler." | ||
, "version": "1.0.2" | ||
, "version": "1.0.4-dev" | ||
, "keywords": ["mustache", "template"] | ||
@@ -13,3 +13,3 @@ , "main": "./lib/hogan.js" | ||
} | ||
, "licenses": [ | ||
, "licenses": [ | ||
{ "type": "Apache-2.0" | ||
@@ -16,0 +16,0 @@ , "url": "http://www.apache.org/licenses/LICENSE-2.0" |
@@ -514,2 +514,17 @@ /* | ||
function testStringPartials() { | ||
var text = "foo{{>mypartial}}baz"; | ||
var partialText = " bar "; | ||
var t = Hogan.compile(text); | ||
var s = t.render({}, {'mypartial': partialText}); | ||
is(s, "foo bar baz", "string partial works."); | ||
} | ||
function testMissingPartials() { | ||
var text = "foo{{>mypartial}} bar"; | ||
var t = Hogan.compile(text); | ||
var s = t.render({}); | ||
is(s, "foo bar", "missing partial works."); | ||
} | ||
function testIndentedStandaloneComment() { | ||
@@ -529,2 +544,47 @@ var text = 'Begin.\n {{! Indented Comment Block! }}\nEnd.'; | ||
} | ||
function testMustacheJSApostrophe() { | ||
var text = '{{apos}}{{control}}'; | ||
var t = Hogan.compile(text); | ||
var s = t.render({'apos':"'", 'control':"X"}); | ||
is(s, ''X', 'Apostrophe is escaped.'); | ||
} | ||
function testMustacheJSArrayOfImplicitPartials() { | ||
var text = 'Here is some stuff!\n{{#numbers}}\n{{>partial}}\n{{/numbers}}\n'; | ||
var partialText = '{{.}}\n'; | ||
var t = Hogan.compile(text); | ||
var s = t.render({numbers:[1,2,3,4]}, {partial: partialText}); | ||
is(s, 'Here is some stuff!\n1\n2\n3\n4\n', 'Partials with implicit iterators work.'); | ||
} | ||
function testMustacheJSArrayOfPartials() { | ||
var text = 'Here is some stuff!\n{{#numbers}}\n{{>partial}}\n{{/numbers}}\n'; | ||
var partialText = '{{i}}\n'; | ||
var t = Hogan.compile(text); | ||
var s = t.render({numbers:[{i:1},{i:2},{i:3},{i:4}]}, {partial: partialText}); | ||
is(s, 'Here is some stuff!\n1\n2\n3\n4\n', 'Partials with arrays work.'); | ||
} | ||
function testMustacheJSArrayOfStrings() { | ||
var text = '{{#strings}}{{.}} {{/strings}}'; | ||
var t = Hogan.compile(text); | ||
var s = t.render({strings:['foo', 'bar', 'baz']}); | ||
is(s, 'foo bar baz ', 'array of strings works with implicit iterators.'); | ||
} | ||
function testMustacheJSUndefinedString() { | ||
var text = 'foo{{bar}}baz'; | ||
var t = Hogan.compile(text); | ||
var s = t.render({bar:undefined}); | ||
is(s, 'foobaz', 'undefined value does not render.'); | ||
} | ||
function testMustacheJSTripleStacheAltDelimiter() { | ||
var text = '{{=<% %>=}}<% foo %> {{foo}} <%{bar}%> {{{bar}}}'; | ||
var t = Hogan.compile(text); | ||
var s = t.render({foo:'yeah', bar:'hmm'}); | ||
is(s, 'yeah {{foo}} hmm {{{bar}}}', 'triple stache inside alternate delimiter works.'); | ||
} | ||
/* shootout benchmark tests */ | ||
@@ -664,3 +724,3 @@ | ||
function testDefaultRenderImpl() { | ||
var ht = new (Hogan.Template || HoganTemplate)(); | ||
var ht = new Hogan.Template(); | ||
is(ht.render() === '', true, 'default renderImpl returns an array.'); | ||
@@ -771,4 +831,12 @@ } | ||
testPartialsAndDelimiters(); | ||
testStringPartials(); | ||
testMissingPartials(); | ||
testIndentedStandaloneComment(); | ||
testNewLineBetweenDelimiterChanges(); | ||
testMustacheJSApostrophe(); | ||
testMustacheJSArrayOfImplicitPartials(); | ||
testMustacheJSArrayOfPartials(); | ||
testMustacheJSArrayOfStrings(); | ||
testMustacheJSUndefinedString(); | ||
testMustacheJSTripleStacheAltDelimiter(); | ||
complete(); | ||
@@ -775,0 +843,0 @@ } |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
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
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
Dynamic require
Supply chain riskDynamic require can indicate the package is performing dangerous or unsafe dynamic code execution.
Found 1 instance in 1 package
No v1
QualityPackage is not semver >=1. This means it is not stable and does not support ^ ranges.
Found 1 instance in 1 package
245094
52
3177
2
8
3