Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

razorleaf

Package Overview
Dependencies
Maintainers
1
Versions
96
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

razorleaf - npm Package Compare versions

Comparing version 1.6.0 to 2.0.0

unicode.js

444

compiler.js
"use strict";
var utilities = require("./utilities");
var CodeBlock = utilities.CodeBlock;
function Scope() {
this.used = {};
}
var POSSIBLE_COMMENT = /\/\/|<!--/;
Scope.prototype.createName = function(prefix) {
var name;
var i = 1;
function addPossibleConflicts(possibleConflicts, code) {
// It isn’t possible to refer to a local variable and create a conflict
// in strict mode without clearly (or nearly so) specifying the variable’s name.
// Since we won’t be using any name but output_*, other letter and digit
// characters are not a concern. As for eval – that obviously isn’t possible to work around.
var JS_IDENTIFIER = /(?:[a-zA-Z_]|\\u[0-9a-fA-F])(?:\w|\\u[0-9a-fA-F])*/g;
do {
name = "__" + prefix + i;
i++;
} while(this.used[name]);
var m;
this.used[name] = true;
while (m = JS_IDENTIFIER.exec(code)) {
possibleConflicts[JSON.parse('"' + m + '"')] = true;
}
}
return name;
};
function passThrough(compiler, context, node) {
node.children.forEach(function(child) {
compileNode(compiler, context, child);
});
}
Scope.prototype.generateCode = function() {
var names = Object.keys(this.used);
function Scope() {
this.used = {};
}
if(names.length === 0) {
return "";
Scope.prototype.getName = function(name) {
while (this.used.hasOwnProperty(name)) {
name += "_";
}
return "var " + names.join(", ") + ";";
this.used[name] = true;
return name;
};

@@ -38,42 +46,38 @@

var nodeHandlers = {
root: function() {},
element: function(node, context) {
if(!context.content) {
throw node.unexpected;
var transform = {
root: passThrough,
block: passThrough,
element: function(compiler, context, node) {
var name = node.name.toLowerCase();
var isVoid = voidTags.indexOf(name) !== -1;
var newContext = {
attributes: new CodeBlock().addText("<" + name),
content: !isVoid && new CodeBlock(),
classes: new CodeBlock()
};
node.children.forEach(function(child) {
compileNode(compiler, newContext, child);
});
context.content.addBlock(newContext.attributes);
if (newContext.classes.parts.length) {
context.content.addText(" class=\"");
context.content.addBlock(newContext.classes);
context.content.addText("\"");
}
var isVoid = voidTags.indexOf(node.name) !== -1;
context.content.addText(">");
return {
attributes: new utilities.CodeContext(null, [
{
type: "text",
value: "<" + node.name
}
]),
content: isVoid ? null : new utilities.CodeContext(null),
scope: context.scope,
parent: context,
done: function() {
this.parent.content.addContext(this.attributes);
this.parent.content.addText(">");
context.content.addBlock(newContext.content);
if(!isVoid) {
this.parent.content.addContext(this.content);
this.parent.content.addText("</" + node.name + ">");
}
}
};
},
string: function(node, context) {
if(!context.content) {
throw node.unexpected;
if (!isVoid) {
context.content.addText("</" + name + ">");
}
context.content.addContext(node.content);
},
attribute: function(node, context) {
if(!context.attributes) {
throw node.unexpected;
attribute: function(compiler, context, node) {
if (!context.attributes) {
throw new SyntaxError("Unexpected attribute"); // TODO: Where?
}

@@ -83,229 +87,199 @@

if(node.value !== null) {
if (node.value) {
context.attributes.addText("=\"");
context.attributes.addContext(node.value.content);
context.attributes.addBlock(node.value.value);
context.attributes.addText("\"");
}
},
code: function(node, context) {
return {
content: new utilities.CodeContext(),
scope: context.scope,
parent: context,
done: function() {
this.parent.content.addCode(node.code.trimLeft() + "\n");
if(this.content.parts.length !== 0) {
this.parent.content.addCode("{");
this.parent.content.addContext(this.content);
this.parent.content.addCode("}\n");
} else {
this.parent.content.addContext(this.content);
for (var i = 0; i < node.value.value.parts.length; i++) {
var part = node.value.value.parts[i];
if (part.type === "expression") {
addPossibleConflicts(compiler.possibleConflicts, part.value);
}
}
};
}
};
}
},
string: function(compiler, context, node) {
context.content.addBlock(node.value);
function adjustContext(context, node) {
var handler = nodeHandlers[node.type];
for (var i = 0; i < node.value.parts.length; i++) {
var part = node.value.parts[i];
if(!handler) {
throw new Error("Unknown type: " + node.type);
}
if (part.type === "expression") {
addPossibleConflicts(compiler.possibleConflicts, part.value);
}
}
},
class: function(compiler, context, node) {
if (!context.classes) {
throw new SyntaxError("Unexpected class"); // TODO: Where?
}
return handler(node, context);
}
context.classes.addText(" " + node.value);
},
code: function(compiler, context, node) {
if (node.children.length) {
context.content.addCode(node.code + (POSSIBLE_COMMENT.test(node.code) ? "\n{" : " {"));
function compileNode(node, context) {
var newContext = adjustContext(context, node);
var children = node.children;
var newContext = {
content: context.content
};
if(newContext) {
context = newContext;
}
node.children.forEach(function(child) {
compileNode(compiler, newContext, child);
});
if(children) {
for(var i = 0; i < children.length; i++) {
var child = children[i];
compileNode(child, context);
context.content.addCode("}");
} else {
context.content.addCode(node.code + (POSSIBLE_COMMENT.test(node.code) ? "\n;" : ";"));
}
}
if(newContext) {
newContext.done();
}
}
addPossibleConflicts(compiler.possibleConflicts, node.code);
},
include: function(compiler, context, node) {
var subtree = compiler.options.load(node.template);
function compile(tree) {
var context = {
content: new utilities.CodeContext(),
scope: new Scope(),
done: function() {}
};
compileNode(compiler, context, subtree);
},
if: function(compiler, context, node) {
var condition = POSSIBLE_COMMENT.test(node.condition) ? node.condition + "\n" : node.condition;
context.scope.used.__output = true;
var newContext = {
content: new CodeBlock(),
attributes: context.attributes && new CodeBlock(),
classes: context.classes && new CodeBlock()
};
compileNode(tree, context);
var elseContext;
var staticContent = context.content.generateStatic();
node.children.forEach(function(child) {
compileNode(compiler, newContext, child);
});
if(staticContent !== null) {
return function() {
return staticContent;
};
}
if (node.elif.length) {
node.else = {
children: [
{
type: "if",
condition: node.elif[0].condition,
elif: node.elif.slice(1),
else: node.else,
children: node.elif[0].children
}
]
};
}
var functionBody =
context.scope.generateCode() +
"\n__output = '" +
context.content.generateCode("text") +
"\nreturn __output;";
if (node.else) {
elseContext = {
content: new CodeBlock(),
attributes: context.attributes && new CodeBlock(),
classes: context.classes && new CodeBlock()
};
var compiled = new Function("__util, data", functionBody);
node.else.children.forEach(function(child) {
compileNode(compiler, elseContext, child);
});
}
return function(data) {
return compiled(utilities, data);
};
}
var conditionName = compiler.scope.getName("condition");
(context.attributes || context.content).addCode("var " + conditionName + " = (" + condition + ");");
condition = conditionName;
nodeHandlers.doctype = function(node, context) {
return {
parent: context,
done: function() {
this.parent.content.addText("<!DOCTYPE html>");
if (newContext.attributes && newContext.attributes.parts.length) {
context.attributes.addCode("if (" + condition + ") {");
context.attributes.addBlock(newContext.attributes);
context.attributes.addCode("}");
if (elseContext && elseContext.attributes.parts.length) {
context.attributes.addCode("else {");
context.attributes.addBlock(elseContext.attributes);
context.attributes.addCode("}");
}
}
};
};
nodeHandlers.include = function(node, context) {
return {
attributes: context.attributes,
content: context.content,
scope: context.scope,
parent: context,
done: function() {}
};
};
if (newContext.classes && newContext.classes.parts.length) {
context.classes.addCode("if (" + condition + ") {");
context.classes.addBlock(newContext.classes);
context.classes.addCode("}");
nodeHandlers.block = function(node, context) {
return {
attributes: context.attributes,
content: context.content,
scope: context.scope,
parent: context,
done: function() {}
};
};
if (elseContext && elseContext.classes.parts.length) {
context.classes.addCode("else {");
context.classes.addBlock(elseContext.classes);
context.classes.addCode("}");
}
}
nodeHandlers.extends = function(node, context) {
return {
parent: context,
done: function() {}
};
};
if (newContext.content.parts.length) {
context.content.addCode("if (" + condition + ") {");
context.content.addBlock(newContext.content);
context.content.addCode("}");
nodeHandlers.if = function(node, context) {
var conditionName = context.scope.createName("condition");
if(node.elif.length > 0) {
node.else = {
children: [{
type: "if",
condition: node.elif[0].condition,
children: node.elif[0].children,
elif: node.elif.slice(1),
else: node.else
}]
if (elseContext && elseContext.content.parts.length) {
context.content.addCode("else {");
context.content.addBlock(elseContext.content);
context.content.addCode("}");
}
}
},
for: function(compiler, context, node) {
var newContext = {
content: context.content
};
}
return {
attributes: new utilities.CodeContext(),
content: new utilities.CodeContext(),
scope: context.scope,
parent: context,
done: function() {
var elseContext;
var index = compiler.scope.getName("i");
var collectionName = compiler.scope.getName("collection");
var collection = POSSIBLE_COMMENT.test(node.collection) ? node.collection + "\n" : node.collection;
if(node.else) {
elseContext = {
attributes: new utilities.CodeContext(),
content: new utilities.CodeContext(),
scope: context.scope,
parent: context
};
context.content.addCode("var " + collectionName + " = (" + collection + ");");
context.content.addCode("for (var " + index + " = 0; " + index + " < " + collectionName + ".length; " + index + "++) {");
context.content.addCode("var " + node.variable + " = " + collectionName + "[" + index + "];");
for(var i = 0; i < node.else.children.length; i++) {
var child = node.else.children[i];
node.children.forEach(function(child) {
compileNode(compiler, newContext, child);
});
compileNode(child, elseContext);
}
}
context.content.addCode("}");
}
};
if(this.attributes.parts.length === 0 && (!node.else || elseContext.attributes.parts.length === 0)) {
this.parent.content.addCode(conditionName + " = (" + node.condition + "\n);\n");
} else {
this.parent.attributes.addCode(conditionName + " = (" + node.condition + "\n);\n");
this.parent.attributes.addCode("if(" + conditionName + ") {\n");
this.parent.attributes.addContext(this.attributes);
this.parent.attributes.addCode("}\n");
function compileNode(compiler, context, node) {
var transformer = transform[node.type];
if(node.else && elseContext.attributes.parts.length !== 0) {
this.parent.attributes.addCode("else {\n");
this.parent.attributes.addContext(elseContext.attributes);
this.parent.attributes.addCode("}\n");
}
}
if (!transformer) {
throw new Error("Unknown node type " + node.type + ".");
}
if(this.content.parts.length !== 0 || node.else) {
this.parent.content.addCode("if(" + conditionName + ") {\n");
this.parent.content.addContext(this.content);
this.parent.content.addCode("}\n");
}
transformer(compiler, context, node);
}
if(node.else && elseContext.content.parts.length !== 0) {
this.parent.content.addCode("else {\n");
this.parent.content.addContext(elseContext.content);
this.parent.content.addCode("}\n");
}
function compile(tree, options) {
var scope = new Scope();
this.scope.used[conditionName] = false;
}
var compiler = {
scope: scope,
possibleConflicts: scope.used,
options: options
};
};
nodeHandlers.for = function(node, context) {
var indexName = context.scope.createName("index");
var collectionName = context.scope.createName("collection");
var context = {
content: new CodeBlock()
};
if(context.scope.used[node.variableName]) {
throw new SyntaxError("Name " + node.variableName + " is in use in a containing scope."); // TODO: Rename variable (requires esprima and escodegen as dependencies) or use a wrapping function (slower).
}
compileNode(compiler, context, tree);
context.scope.used[node.variableName] = true;
var outputVariable = scope.getName("output");
return {
content: new utilities.CodeContext(),
scope: context.scope,
parent: context,
done: function() {
this.parent.content.addCode(
collectionName + " = (" + node.collection + "\n);\n" +
"for(" + indexName + " = 0; " + indexName + " < " + collectionName + ".length; " + indexName + "++) {\n" +
node.variableName + " = " + collectionName + "[" + indexName + "];\n"
);
this.parent.content.addContext(this.content);
this.parent.content.addCode("}\n");
var code =
"'use strict';\n\n" +
utilities.escapeAttributeValue + "\n" +
utilities.escapeContent + "\n\n" +
"var " + outputVariable + " = '" + context.content.toCode(outputVariable, "text") +
"\n\nreturn " + outputVariable + ";";
this.scope.used[node.variableName] = false;
this.scope.used[collectionName] = false;
this.scope.used[indexName] = false;
}
};
};
return new Function("data", code);
}
module.exports.constructor = { name: "razorleaf.compiler" };
module.exports.compile = compile;
module.exports.utilities = utilities;
module.exports.nodeHandlers = nodeHandlers;
module.exports.transform = transform;
{
"name": "razorleaf",
"version": "1.6.0",
"version": "2.0.0",
"main": "razorleaf.js",

@@ -10,9 +10,6 @@ "files": [

"utilities.js",
"cli.js"
"unicode.js"
],
"description": "A template engine for HTML",
"keywords": ["template", "bulbasaur"],
"bin": {
"leaf": "cli.js"
},
"keywords": ["template"],
"repository": {

@@ -26,8 +23,3 @@ "type": "git",

"homepage": "https://charmander.me/razorleaf/",
"licenses": [
{
"type": "CC0",
"url": "https://creativecommons.org/publicdomain/zero/1.0/"
}
]
"license": "MIT"
}
"use strict";
var utilities = require("./utilities");
var CodeBlock = utilities.CodeBlock;
var IDENTIFIER_CHARACTER = /[\w-]/;
var JS_IDENTIFIER_CHARACTER = /\w/; // Others are not included for simplicity’s sake.
var HEX = /[\da-fA-F]/;
var DIGIT = /\d/;
var IDENTIFIER = /[\w-]/;
var RECOGNIZABLE = /[!-~]/;
var POSSIBLE_COMMENT = /\/\/|<!--/;
var BLOCK_OR_TEMPLATE_NAME = /\S/;
var JS_IDENTIFIER = /\w/;
function addBlockAction(tree, blockName, action) {
if(tree.blockActions.hasOwnProperty(blockName)) {
tree.blockActions[blockName].push(action);
} else {
tree.blockActions[blockName] = [action];
var singleCharEscapes = {
"\\": "\\\\",
n: "\n",
r: "\r",
t: "\t",
v: "\v",
f: "\f",
b: "\b",
0: "\0"
};
function isExpression(js) {
try {
new Function("'use strict'; (" + js + "\n)");
return true;
} catch (e) {
return false;
}
}
var specialBlocks = {};
function describe(c) {
if (RECOGNIZABLE.test(c)) {
return c;
}
return require("./unicode")[c.charCodeAt(0)] || JSON.stringify(c);
}
var states = {
content: function(c) {
if(c === " ") {
return states.content;
indent: function(parser, c) {
if (c === null && parser.indentString) {
parser.warn("Trailing whitespace");
return;
}
if(c === "\n") {
this.indent = 0;
if (c === "\n") {
if (parser.indentString) {
parser.warn("Whitespace-only line");
}
parser.indentString = "";
return states.indent;
}
if(c === "#") {
return states.comment;
if (c === "\t") {
if (parser.indentType && parser.indentType.indentCharacter !== "\t") {
throw parser.error("Unexpected tab indent; indent was already determined to be " + parser.indentType.name + " by line " + parser.indentType.determined.line + ", character " + parser.indentType.determined.character);
}
} else if (c !== " ") {
if (!parser.indentString) {
parser.indent = 0;
} else if (parser.indentType) {
if (parser.indentType.indentCharacter === "\t") {
var i = parser.indentString.indexOf(" ");
parser.indent = i === -1 ? parser.indentString.length : i;
} else {
var level = parser.indentString.length / parser.indentType.spaces;
if (level !== (level | 0)) {
throw parser.error("Invalid indent level " + level + "; indent was determined to be " + parser.indentType.name + " by line " + parser.indentType.determined.line + ", character " + parser.indentType.determined.character);
}
parser.indent = level;
}
} else {
parser.indent = 1;
if (parser.indentString.charAt(0) === "\t") {
parser.indentType = {
indentCharacter: "\t",
name: "one tab"
};
} else {
parser.indentType = {
indentCharacter: " ",
name: parser.indentString.length + " space" + (parser.indentString.length === 1 ? "" : "s"),
spaces: parser.indentString.length
};
}
parser.indentType.determined = {
line: parser.position.line,
character: parser.position.character
};
}
if (parser.indent > parser.context.indent + 1) {
throw parser.error("Excessive indent " + parser.indent + "; expected " + (parser.context.indent + 1) + " or smaller");
}
while (parser.context.indent >= parser.indent) {
parser.context = parser.context.parent;
}
return states.content(parser, c);
}
if(c === "%") {
this.context = {
type: "code",
code: "",
parent: this.context,
indent: this.indent,
children: [],
unexpected: this.error("Code here is not valid")
};
this.context.parent.children.push(this.context);
return states.code;
parser.indentString += c;
return states.indent;
},
content: function(parser, c) {
if (c === null) {
return;
}
if(c === "\"") {
this.context = {
type: "string",
content: new utilities.CodeContext("escapeContent"),
current: "",
parent: this.context,
unterminated: this.error("Expected end of string before end of input, starting"),
unexpected: this.error("A string here is not valid")
};
this.context.parent.children.push(this.context);
return states.string;
if (parser.context.type === "attribute") {
if (c === "!") {
throw parser.error("Attributes cannot have raw strings for values");
}
if (c !== " " && c !== '"') {
parser.context = parser.context.parent;
}
}
if(c === "!" && this.peek() === "\"") {
this.skip();
this.context = {
type: "string",
content: new utilities.CodeContext(null),
current: "",
parent: this.context,
unterminated: this.error("Expected end of string before end of input, starting"),
unexpected: this.error("A string here is not valid")
};
this.context.parent.children.push(this.context);
if (c === "\n") {
parser.indentString = "";
return states.indent;
}
if (c === " ") {
return states.content;
}
if (c === ".") {
parser.identifier = "";
return states.className;
}
if (c === "!") {
parser.string = new CodeBlock();
parser.escapeFunction = null;
return states.rawString;
}
if (c === '"') {
parser.string = new CodeBlock();
parser.escapeFunction = parser.context.type === "attribute" ? "escapeAttributeValue" : "escapeContent";
return states.string;
}
if(IDENTIFIER_CHARACTER.test(c)) {
this.context = {
name: c,
parent: this.context,
indent: this.indent,
unexpected: this.prepareError()
};
this.context.parent.children.push(this.context);
return states.identifier;
if (c === "#") {
return states.comment;
}
throw this.error("Unexpected " + c);
if (c === "%") {
parser.code = "";
return states.code;
}
if (IDENTIFIER.test(c)) {
parser.identifier = "";
return states.identifier(parser, c);
}
throw parser.error("Unexpected " + describe(c));
},
code: function(c) {
if(c === "\n") {
this.indent = 0;
return states.indent;
comment: function(parser, c) {
if (c === null || c === "\n") {
return states.content(parser, c);
}
this.context.code += c;
return states.comment;
},
code: function(parser, c) {
if (c === null || c === "\n") {
parser.context = {
type: "code",
code: parser.code.trim(),
parent: parser.context,
children: [],
indent: parser.indent,
position: {
line: parser.position.line,
character: parser.position.character
}
};
parser.context.parent.children.push(parser.context);
return states.content(parser, c);
}
parser.code += c;
return states.code;
},
comment: function(c) {
if(c === "\n") {
this.indent = 0;
return states.indent;
identifier: function(parser, c) {
if (c === ":") {
return states.possibleAttribute;
}
return states.comment;
if (c !== null && IDENTIFIER.test(c)) {
parser.identifier += c;
return states.identifier;
}
if (keywords.hasOwnProperty(parser.identifier)) {
return keywords[parser.identifier](parser, c);
}
parser.context = {
type: "element",
name: parser.identifier,
parent: parser.context,
children: [],
indent: parser.indent,
position: {
line: parser.position.line,
character: parser.position.character
}
};
parser.context.parent.children.push(parser.context);
return states.content(parser, c);
},
indent: function(c) {
if(c === "\n") {
this.indent = 0;
return states.indent;
className: function(parser, c) {
if (c !== null && IDENTIFIER.test(c)) {
parser.identifier += c;
return states.className;
}
if(c !== "\t") {
while(this.indent <= this.context.indent) {
this.context = this.context.parent;
if (!parser.identifier) {
throw parser.error("Expected class name");
}
parser.context.children.push({
type: "class",
value: parser.identifier,
parent: parser.context,
position: {
line: parser.position.line,
character: parser.position.character
}
});
if(this.indent > this.context.indent + 1) {
throw this.error("Excessive indent");
return states.content(parser, c);
},
possibleAttribute: function(parser, c) {
if (c !== null && IDENTIFIER.test(c)) {
parser.identifier += ":" + c;
return states.identifier;
}
if (c === ":") {
parser.identifier += ":";
return states.possibleAttribute;
}
parser.context = {
type: "attribute",
name: parser.identifier,
value: null,
parent: parser.context,
position: {
line: parser.position.line,
character: parser.position.character
}
};
return this.pass(states.content);
parser.context.parent.children.push(parser.context);
return states.content;
},
rawString: function(parser, c) {
if (c !== '"') {
throw parser.error("Expected beginning quote of raw string, not " + describe(c));
}
this.indent++;
return states.indent;
return states.string;
},
string: function(c) {
if(c === "\"") {
if(this.context.current) {
this.context.content.addText(this.context.current);
string: function(parser, c) {
if (c === null) {
throw parser.error("Expected end of string before end of file");
}
if (c === '"') {
var string = {
type: "string",
value: parser.string,
parent: parser.context,
position: {
line: parser.position.line,
character: parser.position.character
}
};
if (parser.context.type === "attribute") {
parser.context.value = string;
parser.context = parser.context.parent;
} else {
parser.context.children.push(string);
}
this.context = this.context.parent;
return states.content;
}
if(c === "\\") {
this.context.current += c;
return states.escaped;
if (c === "#") {
return states.stringPound;
}
if(c === "#" && this.peek() === "{") {
if(this.context.current) {
this.context.content.addText(this.context.current);
this.context.current = "";
}
this.context = {
type: "interpolation",
value: "",
parent: this.context,
unterminated: this.error("Expected end of interpolated section before end of input, starting")
};
this.skip();
return states.interpolation;
if (c === "\\") {
return states.escape;
}
this.context.current += c;
if (parser.escapeFunction) {
parser.string.addText(utilities[parser.escapeFunction](c));
} else {
parser.string.addText(c);
}
return states.string;
},
escaped: function(c) {
this.context.current += c;
return states.string;
stringPound: function(parser, c) {
if (c === "{") {
parser.interpolation = "";
return states.interpolation;
}
parser.string.addText("#");
return states.string(parser, c);
},
interpolation: function(c) {
if(c === "\\") {
if(this.peek() === "}") {
this.skip();
this.context.value += "}";
return states.interpolation;
}
} else if(c === "}") {
this.context.parent.content.addExpression(this.context.value);
this.context = this.context.parent;
interpolation: function(parser, c) {
if (c === null) {
throw parser.error("Interpolated section never resolves to a valid JavaScript expression"); // TODO: Where did it start?
}
if (c === "}" && isExpression(parser.interpolation)) {
var interpolation = POSSIBLE_COMMENT.test(parser.interpolation) ? parser.interpolation + "\n" : parser.interpolation;
parser.string.addExpression(parser.escapeFunction, interpolation);
return states.string;
}
this.context.value += c;
parser.interpolation += c;
return states.interpolation;
},
identifier: function(c) {
if(c === ":") {
if(!IDENTIFIER_CHARACTER.test(this.peek())) {
this.context.type = "attribute";
this.context.value = null;
this.context.unexpected = this.context.unexpected("An attribute here is not valid");
escape: function(parser, c) {
if (c === null) {
throw parser.error("Expected escape character");
}
return states.attributeValue;
}
} else if(!IDENTIFIER_CHARACTER.test(c)) {
this.context.type = "element";
this.context.children = [];
this.context.unexpected = this.context.unexpected("An element here is not valid");
if (c === "#" || c === '"') {
parser.string.addText(c);
return states.string;
}
if(specialBlocks.hasOwnProperty(this.context.name)) {
var specialBlock = specialBlocks[this.context.name];
if (c === "x") {
return states.escapeX1;
}
specialBlock.begin.call(this);
if (c === "u") {
return states.escapeU1;
}
return this.pass(specialBlock.initialState);
}
if (singleCharEscapes.hasOwnProperty(c)) {
parser.string.addText(singleCharEscapes[c]);
return states.string;
}
return this.pass(states.content);
// TODO: Allow LineTerminator to be escaped?
return states.string(parser, c);
},
escapeX1: function(parser, c) {
if (c === null || !HEX.test(c)) {
throw parser.error("Expected hexadecimal digit");
}
this.context.name += c;
return states.identifier;
parser.charCode = parseInt(c, 16) << 4;
return states.escapeX2;
},
attributeValue: function(c) {
if(c === " ") {
return states.attributeValue;
escapeX2: function(parser, c) {
if (c === null || !HEX.test(c)) {
throw parser.error("Expected hexadecimal digit");
}
if(c === "!") {
throw this.error("Attributes cannot have raw strings as values");
var escapedCharacter = String.fromCharCode(parser.charCode | parseInt(c, 16));
if (parser.escapeFunction) {
parser.string.addText(utilities[parser.escapeFunction](escapedCharacter));
} else {
parser.string.addText(escapedCharacter);
}
if(c === "\"") {
var attribute = this.context;
return states.string;
},
escapeU1: function(parser, c) {
if (c === null || !HEX.test(c)) {
throw parser.error("Expected hexadecimal digit");
}
attribute.value = this.context = {
type: "string",
content: new utilities.CodeContext("escapeAttributeValue"),
current: "",
parent: attribute.parent,
unterminated: this.error("Expected end of string before end of input, starting")
};
parser.charCode = parseInt(c, 16) << 12;
return states.escapeU2;
},
escapeU2: function(parser, c) {
if (c === null || !HEX.test(c)) {
throw parser.error("Expected hexadecimal digit");
}
return states.string;
parser.charCode |= parseInt(c, 16) << 8;
return states.escapeU3;
},
escapeU3: function(parser, c) {
if (c === null || !HEX.test(c)) {
throw parser.error("Expected hexadecimal digit");
}
this.context = this.context.parent;
parser.charCode |= parseInt(c, 16) << 4;
return states.escapeU4;
},
escapeU4: function(parser, c) {
if (c === null || !HEX.test(c)) {
throw parser.error("Expected hexadecimal digit");
}
return this.pass(states.content);
var escapedCharacter = String.fromCharCode(parser.charCode | parseInt(c, 16));
if (parser.escapeFunction) {
parser.string.addText(utilities[parser.escapeFunction](escapedCharacter));
} else {
parser.string.addText(escapedCharacter);
}
return states.string;
}
};
function parse(template) {
var i;
var c;
var state = states.content;
var keywords = {
doctype: function(parser, c) {
parser.context.children.push({
type: "string",
value: new CodeBlock().addText("<!DOCTYPE html>"),
parent: parser.context,
position: {
line: parser.position.line,
character: parser.position.character
}
});
var line = 1;
var lineStart = 0;
return states.content(parser, c);
},
include: function(parser, c) {
var leadingWhitespace = function(parser, c) {
if (c === " ") {
return leadingWhitespace;
}
var root = {
type: "root",
children: [],
includes: [],
extends: null,
blocks: {},
blockActions: {},
indent: -1
};
if (c !== null && BLOCK_OR_TEMPLATE_NAME.test(c)) {
parser.identifier = "";
return identifier(parser, c);
}
template += "\n";
throw parser.error("Expected name of included template, not " + describe(c));
};
var parser = {
template: template,
context: root,
root: root,
indent: 0,
pass: function(state) {
return state.call(parser, c);
},
peek: function(count) {
return count === undefined ? template.charAt(i + 1) : template.substr(i + 1, count);
},
skip: function(count) {
if(count === undefined) {
count = 1;
var identifier = function(parser, c) {
if (c === null || !BLOCK_OR_TEMPLATE_NAME.test(c)) {
parser.context.children.push({
type: "include",
template: parser.identifier,
parent: parser.context,
position: {
line: parser.position.line,
character: parser.position.character
}
});
return states.content(parser, c);
}
for(var j = 0; j < count; j++) {
i++;
parser.identifier += c;
return identifier;
};
if(template.charAt(i) === "\n") {
parser.beginLine();
}
return leadingWhitespace(parser, c);
},
extends: function(parser, c) {
if (parser.root.children.length || parser.root.extends) {
throw parser.error("extends must appear first in a template");
}
parser.root.children = {
push: function() {
throw parser.error("A template that extends another can only contain block actions directly");
}
},
error: function(message) {
var details = message + " at line " + line + ", character " + (i - lineStart + 1) + ".";
};
return new SyntaxError(details);
},
prepareError: function() {
var location = " at line " + line + ", character " + (i - lineStart + 1) + ".";
parser.root.blockActions = {};
return function(message) {
return new SyntaxError(message + location);
};
},
beginLine: function() {
line++;
lineStart = i + 1;
}
};
var leadingWhitespace = function(parser, c) {
if (c === " ") {
return leadingWhitespace;
}
for(i = 0; i < template.length; i++) {
c = template.charAt(i);
if (c !== null && BLOCK_OR_TEMPLATE_NAME.test(c)) {
parser.identifier = "";
return identifier(parser, c);
}
if(c === "\n") {
parser.beginLine();
}
throw parser.error("Expected name of parent template, not " + describe(c));
};
state = state.call(parser, c);
}
var identifier = function(parser, c) {
if (c === null || !BLOCK_OR_TEMPLATE_NAME.test(c)) {
parser.root.extends = parser.identifier;
switch(state) {
case states.indent:
break;
return states.content(parser, c);
}
case states.string:
case states.interpolation:
throw parser.context.unterminated;
parser.identifier += c;
return identifier;
};
default:
// If this error is thrown, an extension to the parser has most likely parsed incorrectly.
throw new Error("Parsing bug: expected final state to be indent.");
}
return leadingWhitespace(parser, c);
},
block: function(parser, c) {
var leadingWhitespace = function(parser, c) {
if (c === " ") {
return leadingWhitespace;
}
return root;
}
if (c !== null && BLOCK_OR_TEMPLATE_NAME.test(c)) {
parser.identifier = "";
return identifier(parser, c);
}
specialBlocks.doctype = {
begin: function() {
var parser = this;
throw parser.error("Expected name of block, not " + describe(c));
};
this.context.type = "doctype";
delete this.context.name;
var identifier = function(parser, c) {
if (c === null || !BLOCK_OR_TEMPLATE_NAME.test(c)) {
if (parser.root.blocks.hasOwnProperty(parser.identifier)) {
throw parser.error("A block named “" + parser.identifier + "” has already been defined");
}
this.context.children = {
push: function() {
throw parser.error("doctype element cannot have content");
parser.context = {
type: "block",
name: parser.identifier,
parent: parser.context,
children: [],
indent: parser.indent
};
parser.context.parent.children.push(parser.context);
parser.root.blocks[parser.identifier] = parser.context;
return states.content(parser, c);
}
parser.identifier += c;
return identifier;
};
return leadingWhitespace(parser, c);
},
initialState: states.content
};
replace: function(parser, c) {
var leadingWhitespace = function(parser, c) {
if (c === " ") {
return leadingWhitespace;
}
specialBlocks.if = {
begin: function() {
this.context.type = "if";
this.context.condition = "";
this.context.elif = [];
if (c !== null && BLOCK_OR_TEMPLATE_NAME.test(c)) {
parser.identifier = "";
return identifier(parser, c);
}
delete this.context.name;
},
initialState: function whitespace(c) {
if(c !== " ") {
return this.pass(specialBlocks.if.condition);
}
throw parser.error("Expected name of block to replace, not " + describe(c));
};
return whitespace;
},
condition: function condition(c) {
if(c === "\n") {
return this.pass(states.content);
}
var identifier = function(parser, c) {
if (c === null || !BLOCK_OR_TEMPLATE_NAME.test(c)) {
var newBlock = {
type: "block",
name: parser.identifier,
parent: parser.context,
children: [],
indent: parser.indent
};
this.context.condition += c;
return condition;
}
};
var action = function(block) {
block.children = newBlock.children;
};
specialBlocks.elif = {
begin: function() {
this.context.parent.children.pop();
this.context.type = "elif";
this.context.condition = "";
delete this.context.name;
if (parser.root.blockActions.hasOwnProperty(parser.identifier)) {
parser.root.blockActions[parser.identifier].push(action);
} else {
parser.root.blockActions[parser.identifier] = [action];
}
var previous = this.context.parent.children[this.context.parent.children.length - 1];
parser.context = newBlock;
if(!previous || previous.type !== "if" || previous.else) {
throw this.error("Unexpected elif");
}
return states.content(parser, c);
}
previous.elif.push(this.context);
},
initialState: function whitespace(c) {
if(c !== " ") {
return this.pass(specialBlocks.elif.condition);
}
parser.identifier += c;
return identifier;
};
return whitespace;
return leadingWhitespace(parser, c);
},
condition: function condition(c) {
if(c === "\n") {
return this.pass(states.content);
}
if: function(parser, c) {
var condition_ = "";
this.context.condition += c;
return condition;
}
};
var leadingWhitespace = function(parser, c) {
if (c === " ") {
return leadingWhitespace;
}
specialBlocks.else = {
begin: function() {
this.context.parent.children.pop();
if (c === null) {
throw parser.error("Expected condition, not end of file");
}
var previous = this.context.parent.children[this.context.parent.children.length - 1];
return condition(parser, c);
};
if(!previous || previous.type !== "if" || previous.else) {
throw this.error("Unexpected else");
}
var condition = function(parser, c) {
if (c === null || c === "\n") {
parser.context = {
type: "if",
condition: condition_,
elif: [],
else: null,
parent: parser.context,
children: [],
indent: parser.indent,
position: {
line: parser.position.line,
character: parser.position.character
}
};
previous.else = this.context;
this.context.type = "else";
delete this.context.name;
parser.context.parent.children.push(parser.context);
return states.content(parser, c);
}
condition_ += c;
return condition;
};
return leadingWhitespace(parser, c);
},
initialState: function() {
return this.pass(states.content);
}
};
elif: function(parser, c) {
var condition_ = "";
specialBlocks.for = {
begin: function() {
this.context.type = "for";
this.context.variableName = "";
this.context.collection = "";
delete this.context.name;
},
initialState: function whitespace(c) {
if(c !== " ") {
return this.pass(specialBlocks.for.variableName);
var previous = parser.context.children && parser.context.children[parser.context.children.length - 1];
if (!previous || previous.type !== "if" || previous.else) {
throw parser.error("Unexpected elif");
}
return whitespace;
},
variableName: function variableName(c) {
if(!JS_IDENTIFIER_CHARACTER.test(c)) {
if(!this.context.variableName) {
throw this.error("Expected variable name");
var leadingWhitespace = function(parser, c) {
if (c === " ") {
return leadingWhitespace;
}
if(c !== " " || this.peek(3) !== "in ") {
throw this.error("Expected in");
if (c === null) {
throw parser.error("Expected condition, not end of file");
}
this.skip(3);
return condition(parser, c);
};
return specialBlocks.for.whitespace;
}
var condition = function(parser, c) {
if (c === null || c === "\n") {
var elif = {
type: "elif",
condition: condition_,
parent: parser.context,
children: [],
indent: parser.indent,
position: {
line: parser.position.line,
character: parser.position.character
}
};
this.context.variableName += c;
return variableName;
previous.elif.push(elif);
parser.context = elif;
return states.content(parser, c);
}
condition_ += c;
return condition;
};
return leadingWhitespace(parser, c);
},
whitespace: function whitespace(c) {
if(c !== " ") {
return this.pass(specialBlocks.for.collection);
else: function(parser, c) {
var previous = parser.context.children && parser.context.children[parser.context.children.length - 1];
if (!previous || previous.type !== "if" || previous.else) {
throw parser.error("Unexpected else");
}
return whitespace;
previous.else = {
type: "else",
parent: parser.context,
children: [],
indent: parser.indent,
position: {
line: parser.position.line,
character: parser.position.character
}
};
parser.context = previous.else;
return states.content(parser, c);
},
collection: function collection(c) {
if(c === "\n") {
return this.pass(states.content);
}
for: function(parser, c) {
var collection_ = "";
this.context.collection += c;
return collection;
}
};
var leadingWhitespace = function(parser, c) {
if (c === " ") {
return leadingWhitespace;
}
specialBlocks.include = {
begin: function() {
var parser = this;
if (c !== null && JS_IDENTIFIER.test(c)) {
if (DIGIT.test(c)) {
throw parser.error("Expected name of loop variable, not " + describe(c));
}
this.context.type = "include";
this.context.template = "";
delete this.context.name;
parser.identifier = "";
return identifier(parser, c);
}
this.context.children = {
push: function() {
throw parser.error("include element cannot have content");
throw parser.error("Expected name of loop variable, not " + describe(c));
};
var identifier = function(parser, c) {
if (c === null || (!JS_IDENTIFIER.test(c) && c !== " ")) {
throw parser.error("Expected in");
}
if (c === " ") {
return whitespace1;
}
parser.identifier += c;
return identifier;
};
this.root.includes.push(this.context);
},
initialState: function whitespace(c) {
if(c !== " ") {
return this.pass(specialBlocks.include.template);
}
var whitespace1 = function(parser, c) {
if (c === " ") {
return whitespace1;
}
return whitespace;
},
template: function template(c) {
if(c === "\n") {
return this.pass(states.content);
}
if (c === "o") {
return of1;
}
this.context.template += c;
return template;
}
};
throw parser.error("Expected of");
};
specialBlocks.block = {
begin: function() {
this.context.type = "block";
this.context.name = "";
},
initialState: function whitespace(c) {
if(c !== " ") {
var duplicatesExistingNameError = this.prepareError();
this.context.duplicatesExistingName = function() {
return duplicatesExistingNameError("A block named “" + this.name + "” already exists in this context");
};
var of1 = function(parser, c) {
if (c === "f") {
return of2;
}
return this.pass(specialBlocks.block.name);
}
throw parser.error("Expected of");
};
return whitespace;
},
name: function name(c) {
if(c === "\n") {
if(this.root.blocks.hasOwnProperty(this.context.name)) {
throw this.context.duplicatesExistingName();
var of2 = function(parser, c) {
if (c === null) {
throw parser.error("Expected loop collection expression");
}
this.root.blocks[this.context.name] = this.context;
return this.pass(states.content);
}
if (IDENTIFIER.test(c)) {
throw parser.error("Expected of");
}
this.context.name += c;
return name;
if (c === " ") {
return whitespace2;
}
return collection(parser, c);
};
var whitespace2 = function(parser, c) {
if (c === null) {
throw parser.error("Expected loop collection expression");
}
if (c === " ") {
return whitespace2;
}
return collection(parser, c);
};
var collection = function(parser, c) {
if (c === null || c === "\n") {
parser.context = {
type: "for",
variable: parser.identifier,
collection: collection_,
parent: parser.context,
children: [],
indent: parser.indent,
position: {
line: parser.position.line,
character: parser.position.character
}
};
parser.context.parent.children.push(parser.context);
return states.content(parser, c);
}
collection_ += c;
return collection;
};
return leadingWhitespace(parser, c);
}
};
specialBlocks.replace = {
begin: function() {
this.context.parent.children.pop();
this.context.type = "replace-block";
this.context.name = "";
},
initialState: function whitespace(c) {
if(c !== " ") {
var replacesNonExistentError = this.prepareError();
this.context.replacesNonExistentBlock = function() {
return replacesNonExistentError("Block " + this.name + " does not exist in a parent template");
};
function parse(template, options) {
var i;
return this.pass(specialBlocks.replace.name);
var eof = false;
var root = {
type: "root",
children: [],
indent: -1,
extends: null,
blockActions: null,
blocks: {}
};
var parser = Object.seal({
context: root,
root: root,
indentString: "",
indent: null,
indentType: null,
identifier: null,
raw: null,
string: null,
escapeFunction: null,
interpolation: null,
charCode: null,
code: null,
position: {
line: 1,
character: 0
},
error: function(message) {
var where = eof ? "EOF" : "line " + parser.position.line + ", character " + parser.position.character;
return new SyntaxError(message + " at " + where + " in " + options.name + ".");
},
warn: function(message) {
if (options.debug) {
var where = eof ? "EOF" : "line " + parser.position.line + ", character " + parser.position.character;
console.warn("⚠ %s at %s in %s.", message, where, options.name);
}
}
});
return whitespace;
},
name: function name(c) {
var replaceBlock = this.context;
var state = states.indent;
if(c === "\n") {
addBlockAction(this.root, replaceBlock.name, function(block) {
block.children = replaceBlock.children;
});
for (i = 0; i < template.length; i++) {
var c = template.charAt(i);
return this.pass(states.content);
if (c === "\n") {
parser.position.line++;
parser.position.character = 0;
}
this.context.name += c;
return name;
state = state(parser, c);
parser.position.character++;
}
};
specialBlocks.extends = {
begin: function() {
this.context.type = "extends";
eof = true;
state(parser, null);
if(this.root.extends !== null) {
throw this.error("A template cannot extend more than one template");
}
if (root.extends) {
var parentTemplate = options.load(root.extends);
var blockName;
if(this.root.children.length !== 1) {
throw this.error("extends must appear at the beginning of a template");
}
for (blockName in root.blocks) {
if (root.blocks.hasOwnProperty(blockName)) {
if (parentTemplate.blocks.hasOwnProperty(blockName)) {
throw new SyntaxError("Parent template " + root.extends + " already contains a block named “" + blockName + "”.");
}
this.root.extends = "";
delete this.context.name;
},
initialState: function whitespace(c) {
if(c !== " ") {
return this.pass(specialBlocks.extends.template);
parentTemplate.blocks[blockName] = root.blocks[blockName];
}
}
return whitespace;
},
template: function template(c) {
if(c === "\n") {
return this.pass(states.content);
for (blockName in root.blockActions) {
if (root.blockActions.hasOwnProperty(blockName)) {
if (!parentTemplate.blocks.hasOwnProperty(blockName)) {
throw new SyntaxError("There is no block named “" + blockName + "”.");
}
var block = parentTemplate.blocks[blockName];
var actions = root.blockActions[blockName];
for (i = 0; i < actions.length; i++) {
var action = actions[i];
action(block);
}
}
}
this.root.extends += c;
return template;
return parentTemplate;
}
};
return root;
}
module.exports.constructor = { name: "razorleaf.parser" };
module.exports.parse = parse;
module.exports.states = states;
module.exports.specialBlocks = specialBlocks;
module.exports.keywords = keywords;

@@ -6,98 +6,56 @@ "use strict";

function isContained(element) {
while(element.parent) {
element = element.parent;
var path = require("path");
var fs = require("fs");
if(element.type === "block") {
return true;
}
}
function combine() {
var result = {};
return false;
}
for (var i = 0; i < arguments.length; i++) {
var obj = arguments[i];
function loadExtends(tree, visited, options) {
if(tree.extends) {
if(visited.indexOf(tree.extends) !== -1) {
throw new Error("Circular extension: ⤷ " + visited.slice(visited.indexOf(tree.extends)).join(" → ") + " ⤴");
}
for(var i = 0; i < tree.children.length; i++) {
var child = tree.children[i];
if(child.type !== "extends" && child.type !== "replace-block") {
throw child.unexpected;
for (var k in obj) {
if (obj.hasOwnProperty(k)) {
result[k] = obj[k];
}
}
}
var extendTree = parser.parse(options.include(tree.extends));
return result;
}
visited.push(tree.extends);
var newTree = loadExtends(extendTree, visited, options);
loadIncludes(extendTree, visited, options);
visited.pop();
var defaults = {
debug: false,
name: "<Razor Leaf template>"
};
for(var name in tree.blocks) {
if(tree.blocks.hasOwnProperty(name)) {
if(newTree.blocks.hasOwnProperty(name)) {
throw tree.blocks[name].duplicatesExistingName();
}
function compile(template, options) {
options = combine(defaults, options);
newTree.blocks[name] = tree.blocks[name];
}
}
for(var name in newTree.blocks) {
if(tree.blockActions.hasOwnProperty(name)) {
var block = newTree.blocks[name];
tree.blockActions[name].forEach(function(action) {
action(block);
});
}
}
return newTree;
}
return tree;
var tree = parser.parse(template, options);
return compiler.compile(tree, options);
}
function loadIncludes(tree, visited, options) {
tree.includes.forEach(function(include) {
if(visited.indexOf(include.template) !== -1) {
throw new Error("Circular inclusion: ⤷ " + visited.slice(visited.indexOf(include.template)).join(" → ") + " ⤴");
function DirectoryLoader(root, options) {
var loader = this;
var loaderOptions = {
load: function(name) {
return parser.parse(loader.read(name), combine(defaults, loaderOptions, loader.options));
}
};
var includeTree = parser.parse(options.include(include.template));
this.root = root;
visited.push(include.template);
tree = loadExtends(includeTree, visited, options);
loadIncludes(includeTree, visited, options);
visited.pop();
include.children = includeTree.children;
});
this.options = combine(loaderOptions, options);
}
function compile(template, options) {
var tree = parser.parse(template);
DirectoryLoader.prototype.read = function(name) {
return fs.readFileSync(path.join(this.root, name + ".leaf"), "utf-8");
};
tree = loadExtends(tree, [], options);
loadIncludes(tree, [], options);
DirectoryLoader.prototype.load = function(name) {
return compile(this.read(name), this.options);
};
for(var name in tree.blocks) {
if(tree.blockActions.hasOwnProperty(name)) {
var block = tree.blocks[name];
tree.blockActions[name].forEach(function(action) {
action(block);
});
}
}
return compiler.compile(tree);
}
module.exports.constructor = { name: "razorleaf" };
module.exports.compile = compile;
module.exports.utilities = compiler.utilities;
module.exports.DirectoryLoader = DirectoryLoader;

@@ -1,7 +0,7 @@

[![Build Status](https://travis-ci.org/charmander/razorleaf.png)](https://travis-ci.org/charmander/razorleaf)
![Status]
Razor Leaf is a template engine for JavaScript with a convenient
indentation-based syntax. It aims, like [Jade], to reduce the redundancy
inherent in HTML — but with simpler rules, a sparser syntax, and a few further
features not found in larger libraries.
indentation-based syntax. It aims to reduce the redundancy inherent in HTML
with simple rules, a sparse syntax, and a few further features not found
in larger libraries.

@@ -49,3 +49,3 @@ ## Syntax

Strings can also contain interpolated sections, delimited by `#{` and `}`.
Both delimiters can be escaped with a backslash.
`#{` can be escaped with a leading backslash; `}` doesn’t require escaping.

@@ -89,4 +89,3 @@ ```

Hierarchy in Razor Leaf is defined using indentation. Indentation *must* use
tabs, and not spaces. For example:
Hierarchy in Razor Leaf is defined using indentation. For example:

@@ -134,3 +133,3 @@ ```

```
% if(i < 5)
% if (i < 5)
!"#{i}"

@@ -142,4 +141,4 @@ ```

```javascript
if(i < 5) {
__output += i;
if (i < 5) {
output += i;
}

@@ -156,4 +155,4 @@ ```

- **`else`**: Can immediately follow an `if` or an `elif`.
- **`for (identifier) in (collection)`**: Includes its content for each element
in the array or array-like object *`collection`*.
- **`for (identifier) of (collection)`**: Includes its content for each element
of the array or array-like object *`collection`*.
- **`include (name)`**: Loads and includes another template.

@@ -168,2 +167,11 @@ - **`extends (name)`**: Loads another template and replaces its blocks.

### `new razorleaf.DirectoryLoader(root, [options])`
Creates a loader that maps template names to files with the `.leaf` extension
in the directory located at *`root`*.
#### `razorleaf.DirectoryLoader.prototype.load(name)`
Returns a template object loaded from the root directory.
### `razorleaf.compile(template, [options])`

@@ -176,13 +184,6 @@

- **`include(name)`**: A function that should return the template represented
by `name`, as given by any `include` statements in a template. This is
optional if template inclusion is not used.
- **`debug`**: If `true`, warnings will be printed. (In a later version, this will enable error rewriting.)
- **`load(name)`**: A function that returns a parsed template represented by `name`.
This is filled automatically by most loaders.
## leaf
`leaf` is a utility to compile static template files to HTML. It can currently
be passed any number of paths to compile, and will write the result to an HTML
file of the same name. (If the path ends in `.leaf`, it is replaced
with `.html`.)
[Jade]: http://jade-lang.com/
[Status]: https://charmander.me/razorleaf/status.svg
"use strict";
var push = Array.prototype.push;
var amp = /&/g;
var quot = /"/g;
var lt = /</g;
var gt = />/g;
function escapeLiteral(text) {
return text.replace(/\\/g, "\\\\")
.replace(/'/g, "\\'")
.replace(/\r/g, "\\r")
.replace(/\n/g, "\\n")
.replace(/\u2028/g, "\\u2028")
.replace(/\u2029/g, "\\u2029");
}
var utilities = {
escapeAttributeValue: function(value) {
return ("" + value).replace(amp, "&amp;")
.replace(quot, "&quot;");
},
escapeContent: function(content) {
return ("" + content).replace(amp, "&amp;")
.replace(lt, "&lt;")
.replace(gt, "&gt;");
},
CodeContext: CodeContext
};
function escapeAttributeValue(value) {
return ("" + value).replace(/&/g, "&amp;")
.replace(/"/g, "&quot;");
}
function escapeStringLiteral(string) {
var result = "";
var escaped = false;
for(var i = 0; i < string.length; i++) {
var c = string.charAt(i);
if(escaped) {
escaped = false;
result += c;
} else if(c === "\\") {
escaped = true;
result += c;
} else if(c === "\n") {
result += "\\n";
} else if(c === "\r") {
result += "\\r";
} else if(c === "\u2028") {
result += "\\u2028";
} else if(c === "\u2029") {
result += "\\u2029";
} else if(c === "'") {
result += "\\'";
} else {
result += c;
}
}
return result;
function escapeContent(value) {
return ("" + value).replace(/&/g, "&amp;")
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;");
}
function CodeContext(escapeFunction, initialParts) {
this.parts = initialParts || [];
this.escapeFunction = escapeFunction;
function CodeBlock() {
this.parts = [];
}
CodeContext.prototype.addCode = function(code) {
this.parts.push({type: "code", value: code});
CodeBlock.prototype.addText = function(text) {
this.parts.push({
type: "text",
value: text
});
return this;
};
CodeContext.prototype.addText = function(text) {
if(this.escapeFunction) {
text = utilities[this.escapeFunction](text);
}
CodeBlock.prototype.addExpression = function(escapeFunction, expression) {
this.parts.push({
type: "expression",
escapeFunction: escapeFunction,
value: expression
});
this.parts.push({type: "text", value: text});
return this;
};
CodeContext.prototype.addExpression = function(expression) {
this.parts.push({type: "expression", value: expression, escapeFunction: this.escapeFunction});
};
CodeBlock.prototype.addCode = function(code) {
this.parts.push({
type: "code",
value: code
});
CodeContext.prototype.addContext = function(context) {
push.apply(this.parts, context.parts);
return this;
};
CodeContext.prototype.generateStatic = function() {
var isStatic = function(part) {
return part.type === "text";
};
CodeBlock.prototype.addBlock = function(block) {
Array.prototype.push.apply(this.parts, block.parts);
if(!this.parts.every(isStatic)) {
return null;
}
return this.parts.map(function(part) {
return part.value;
}).join("");
return this;
};
CodeContext.prototype.generateCode = function(initial) {
var current = initial || "code";
var generated = "";
CodeBlock.prototype.toCode = function(outputVariable, initialState) {
var code = "";
var currentType = initialState;
for(var i = 0; i < this.parts.length; i++) {
for (var i = 0; i < this.parts.length; i++) {
var part = this.parts[i];
switch(part.type) {
case "code":
if(current === "text") {
generated += "';\n";
} else if(current === "expression") {
generated += ";\n";
}
switch (part.type) {
case "text":
if (currentType === "code") {
code += outputVariable + " += '";
} else if (currentType === "expression") {
code += " + '";
}
generated += part.value;
current = "code";
code += escapeLiteral(part.value);
currentType = "text";
break;
break;
case "text":
if(current === "code") {
generated += "__output += '";
} else if(current === "expression") {
generated += " + '";
}
case "expression":
if (currentType === "code") {
code += outputVariable + " += ";
} else if (currentType === "expression") {
code += " + ";
} else {
code += "' + ";
}
generated += escapeStringLiteral(part.value);
current = "text";
if (part.escapeFunction) {
code += part.escapeFunction + "((" + part.value + "))";
} else {
code += "(" + part.value + ")";
}
break;
case "expression":
if(current === "code") {
generated += "__output += ";
} else if(current === "text") {
generated += "' + ";
} else {
generated += " + ";
}
currentType = "expression";
break;
if(part.escapeFunction) {
generated += "__util." + part.escapeFunction + "((" + part.value + "))";
} else {
generated += "(" + part.value + ")";
}
case "code":
if (currentType === "text") {
code += "';\n";
} else if (currentType === "expression") {
code += ";\n";
}
current = "expression";
code += part.value + "\n";
currentType = "code";
break;
break;
default:
throw new Error("Unknown part type");
default:
throw new Error("Unknown part type " + part.type + ".");
}
}
if(current === "text") {
generated += "';";
} else if(current === "expression") {
generated += ";";
if (currentType === "text") {
code += "';";
} else if (currentType === "expression") {
code += ";";
}
return generated;
return code;
};
module.exports = utilities;
module.exports.constructor = { name: "razorleaf.utilities" };
module.exports.escapeAttributeValue = escapeAttributeValue;
module.exports.escapeContent = escapeContent;
module.exports.CodeBlock = CodeBlock;
SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc