haml-coffee
Version:
Haml templates where you can write inline CoffeeScript.
374 lines (341 loc) • 14.5 kB
JavaScript
(function() {
var Code, Comment, Filter, Haml, HamlCoffee, Node, Text, indent, whitespace;
Node = require('./nodes/node');
Text = require('./nodes/text');
Haml = require('./nodes/haml');
Code = require('./nodes/code');
Comment = require('./nodes/comment');
Filter = require('./nodes/filter');
whitespace = require('./util/text').whitespace;
indent = require('./util/text').indent;
module.exports = HamlCoffee = (function() {
function HamlCoffee(options) {
var _base, _base2, _base3, _base4, _base5, _base6, _base7, _base8;
this.options = options != null ? options : {};
if ((_base = this.options).escapeHtml == null) _base.escapeHtml = true;
if ((_base2 = this.options).escapeAttributes == null) {
_base2.escapeAttributes = true;
}
if ((_base3 = this.options).cleanValue == null) _base3.cleanValue = true;
if ((_base4 = this.options).uglify == null) _base4.uglify = false;
if ((_base5 = this.options).basename == null) _base5.basename = false;
if ((_base6 = this.options).format == null) _base6.format = 'html5';
if ((_base7 = this.options).preserveTags == null) {
_base7.preserveTags = 'pre,textarea';
}
if ((_base8 = this.options).selfCloseTags == null) {
_base8.selfCloseTags = 'meta,img,link,br,hr,input,area,param,col,base';
}
}
HamlCoffee.prototype.indentChanged = function() {
return this.currentIndent !== this.previousIndent;
};
HamlCoffee.prototype.isIndent = function() {
return this.currentIndent > this.previousIndent;
};
HamlCoffee.prototype.updateTabSize = function() {
if (this.tabSize === 0) {
return this.tabSize = this.currentIndent - this.previousIndent;
}
};
HamlCoffee.prototype.updateBlockLevel = function() {
this.currentBlockLevel = this.currentIndent / this.tabSize;
if (this.currentBlockLevel - Math.floor(this.currentBlockLevel) > 0) {
throw "Indentation error in line " + this.lineNumber;
}
if ((this.currentIndent - this.previousIndent) / this.tabSize > 1) {
throw "Block level too deep in line " + this.lineNumber;
}
return this.delta = this.previousBlockLevel - this.currentBlockLevel;
};
HamlCoffee.prototype.updateCodeBlockLevel = function(node) {
if (node instanceof Code) {
return this.currentCodeBlockLevel = node.codeBlockLevel + 1;
} else {
return this.currentCodeBlockLevel = node.codeBlockLevel;
}
};
HamlCoffee.prototype.updateParent = function() {
if (this.isIndent()) {
return this.pushParent();
} else {
return this.popParent();
}
};
HamlCoffee.prototype.pushParent = function() {
this.stack.push(this.parentNode);
return this.parentNode = this.node;
};
HamlCoffee.prototype.popParent = function() {
var i, _ref, _results;
_results = [];
for (i = 0, _ref = this.delta - 1; 0 <= _ref ? i <= _ref : i >= _ref; 0 <= _ref ? i++ : i--) {
_results.push(this.parentNode = this.stack.pop());
}
return _results;
};
HamlCoffee.prototype.getNodeOptions = function(override) {
if (override == null) override = {};
return {
parentNode: override.parentNode || this.parentNode,
blockLevel: override.blockLevel || this.currentBlockLevel,
codeBlockLevel: override.codeBlockLevel || this.currentCodeBlockLevel,
escapeHtml: override.escapeHtml || this.options.escapeHtml,
escapeAttributes: override.escapeAttributes || this.options.escapeAttributes,
cleanValue: override.cleanValue || this.options.cleanValue,
format: override.format || this.options.format,
preserveTags: override.preserveTags || this.options.preserveTags,
selfCloseTags: override.selfCloseTags || this.options.selfCloseTags,
uglify: override.uglify || this.options.uglify
};
};
HamlCoffee.prototype.nodeFactory = function(expression) {
var node, options, _ref;
if (expression == null) expression = '';
options = this.getNodeOptions();
if (expression.match(/^:(escaped|preserve|css|javascript|plain|cdata|coffeescript)/)) {
node = new Filter(expression, options);
} else if (expression.match(/^(\/|-#)(.*)/)) {
node = new Comment(expression, options);
} else if (expression.match(/^(-#|-|=|!=|\&=|~)\s*(.*)/)) {
node = new Code(expression, options);
} else if (expression.match(/^(%|#|\.|\!)(.*)/)) {
node = new Haml(expression, options);
} else {
node = new Text(expression, options);
}
if ((_ref = options.parentNode) != null) _ref.addChild(node);
return node;
};
HamlCoffee.prototype.parse = function(source) {
var attributes, expression, line, lines, result, text, ws, _ref;
if (source == null) source = '';
this.line_number = this.previousIndent = this.tabSize = this.currentBlockLevel = this.previousBlockLevel = 0;
this.currentCodeBlockLevel = this.previousCodeBlockLevel = 0;
this.node = null;
this.stack = [];
this.root = this.parentNode = new Node('', this.getNodeOptions());
lines = source.split("\n");
while ((line = lines.shift()) !== void 0) {
if ((this.node instanceof Filter) && !this.exitFilter) {
if (/^(\s)*$/.test(line)) {
this.node.addChild(new Text('', this.getNodeOptions({
parentNode: this.node
})));
} else {
result = line.match(/^(\s*)(.*)/);
ws = result[1];
expression = result[2];
if (this.node.blockLevel >= (ws.length / 2)) {
this.exitFilter = true;
lines.unshift(line);
continue;
}
text = line.match(RegExp("^\\s{" + ((this.node.blockLevel * 2) + 2) + "}(.*)"));
if (text) {
this.node.addChild(new Text(text[1], this.getNodeOptions({
parentNode: this.node
})));
}
}
} else {
this.exitFilter = false;
result = line.match(/^(\s*)(.*)/);
ws = result[1];
expression = result[2];
if (/^(\s)*$/.test(line)) continue;
while (/^%.*[{(]/.test(expression) && !/^(\s*)[-=&!~.%#<]/.test(lines[0]) && /(?:([-\w]+[\w:-]*\w?|'[-\w]+[\w:-]*\w?'|"[-\w]+[\w:-]*\w?")\s*=\s*("(?:\\.|[^"\\])*"|'(?:\\.|[^'\\])*'|[\w@.]+)|(:\w+[\w:-]*\w?|'[-\w]+[\w:-]*\w?'|"[-\w]+[\w:-]*\w?")\s*=>\s*("(?:\\.|[^"\\])*"|'(?:\\.|[^'\\])*'|[^},]+)|(\w+[\w:-]*\w?|'[-\w]+[\w:-]*\w?'|'[-\w]+[\w:-]*\w?'):\s*("(?:\\.|[^"\\])*"|'(?:\\.|[^'\\])*'|[^},]+))/.test(lines[0])) {
attributes = lines.shift();
expression += ' ' + attributes.match(/^(\s*)(.*)/)[2];
this.line_number++;
}
if (expression.match(/(\s)+\|$/)) {
expression = expression.replace(/(\s)+\|$/, ' ');
while ((_ref = lines[0]) != null ? _ref.match(/(\s)+\|$/) : void 0) {
expression += lines.shift().match(/^(\s*)(.*)/)[2].replace(/(\s)+\|$/, '');
this.line_number++;
}
}
this.currentIndent = ws.length;
if (this.indentChanged()) {
this.updateTabSize();
this.updateBlockLevel();
this.updateParent();
this.updateCodeBlockLevel(this.parentNode);
}
this.node = this.nodeFactory(expression);
this.previousBlockLevel = this.currentBlockLevel;
this.previousIndent = this.currentIndent;
}
this.line_number++;
}
return this.evaluate(this.root);
};
HamlCoffee.prototype.evaluate = function(node) {
var child, _i, _len, _ref;
_ref = node.children;
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
child = _ref[_i];
this.evaluate(child);
}
return node.evaluate();
};
HamlCoffee.prototype.render = function(templateName, namespace) {
var segment, segments, template, _i, _len;
if (namespace == null) namespace = 'window.HAML';
template = '';
segments = ("" + namespace + "." + templateName).replace(/(\s|-)+/g, '_').split(/\./);
templateName = this.options.basename ? segments.pop().split(/\/|\\/).pop() : segments.pop();
namespace = segments.shift();
if (segments.length !== 0) {
for (_i = 0, _len = segments.length; _i < _len; _i++) {
segment = segments[_i];
namespace += "." + segment;
template += "" + namespace + " ?= {}\n";
}
} else {
template += "" + namespace + " ?= {}\n";
}
template += "" + namespace + "['" + templateName + "'] = (context) -> ( ->\n";
template += "" + (indent(this.precompile(), 1));
template += ").call(context)";
return template;
};
HamlCoffee.prototype.precompile = function() {
var code, fn;
fn = '';
code = this.createCode();
if (code.indexOf('$e') !== -1) {
if (this.options.customHtmlEscape) {
fn += "$e = " + this.options.customHtmlEscape + "\n";
} else {
fn += "$e = (text, escape) ->\n \"\#{ text }\"\n .replace(/&/g, '&')\n .replace(/</g, '<')\n .replace(/>/g, '>')\n .replace(/\'/g, ''')\n .replace(/\"/g, '"')\n";
}
}
if (code.indexOf('$c') !== -1) {
if (this.options.customCleanValue) {
fn += "$c = " + this.options.customCleanValue + "\n";
} else {
fn += "$c = (text) -> if text is null or text is undefined then '' else text\n";
}
}
if (code.indexOf('$p') !== -1 || code.indexOf('$fp') !== -1) {
if (this.options.customPreserve) {
fn += "$p = " + this.options.customPreserve + "\n";
} else {
fn += "$p = (text) -> text.replace /\\n/g, '
'\n";
}
}
if (code.indexOf('$fp') !== -1) {
if (this.options.customFindAndPreserve) {
fn += "$fp = " + this.options.customFindAndPreserve + "\n";
} else {
fn += "$fp = (text) ->\n text.replace /<(" + (this.options.preserveTags.split(',').join('|')) + ")>([^]*?)<\\/\\1>/g, (str, tag, content) ->\n \"<\#{ tag }>\#{ $p content }</\#{ tag }>\"\n";
}
}
if (code.indexOf('surround') !== -1) {
if (this.options.customSurround) {
fn += "surround = " + this.options.customSurround + "\n";
} else {
fn += "surround = (start, end, fn) -> start + fn() + end\n";
}
}
if (code.indexOf('succeed') !== -1) {
if (this.options.customSucceed) {
fn += "succeed = " + this.options.customSucceed + "\n";
} else {
fn += "succeed = (end, fn) -> fn() + end\n";
}
}
if (code.indexOf('precede') !== -1) {
if (this.options.customPrecede) {
fn += "precede = " + this.options.customPrecede + "\n";
} else {
fn += "precede = (start, fn) -> start + fn()\n";
}
}
fn += "$o = []\n";
fn += "" + code + "\n";
return fn += "return $o.join(\"\\n\")" + (this.convertBooleans(code)) + (this.cleanupWhitespace(code)) + "\n";
};
HamlCoffee.prototype.createCode = function() {
var child, code, line, processors, _i, _j, _len, _len2, _ref, _ref2;
code = [];
this.lines = [];
_ref = this.root.children;
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
child = _ref[_i];
this.lines = this.lines.concat(child.render());
}
this.lines = this.combineText(this.lines);
this.blockLevel = 0;
_ref2 = this.lines;
for (_j = 0, _len2 = _ref2.length; _j < _len2; _j++) {
line = _ref2[_j];
if (line !== null) {
switch (line.type) {
case 'text':
code.push("" + (whitespace(line.cw)) + (this.getBuffer(this.blockLevel)) + ".push \"" + (whitespace(line.hw)) + line.text + "\"");
break;
case 'run':
if (line.block !== 'end') {
code.push("" + (whitespace(line.cw)) + line.code);
} else {
code.push("" + (whitespace(line.cw)) + (line.code.replace('$buffer', this.getBuffer(this.blockLevel))));
this.blockLevel -= 1;
}
break;
case 'insert':
processors = '';
if (line.findAndPreserve) processors += '$fp ';
if (line.preserve) processors += '$p ';
if (line.escape) processors += '$e ';
if (this.options.cleanValue) processors += '$c ';
code.push("" + (whitespace(line.cw)) + (this.getBuffer(this.blockLevel)) + ".push \"" + (whitespace(line.hw)) + "\" + " + processors + line.code);
if (line.block === 'start') {
this.blockLevel += 1;
code.push("" + (whitespace(line.cw + 1)) + (this.getBuffer(this.blockLevel)) + " = []");
}
}
}
}
return code.join('\n');
};
HamlCoffee.prototype.getBuffer = function(level) {
if (level > 0) {
return "$o" + level;
} else {
return '$o';
}
};
HamlCoffee.prototype.combineText = function(lines) {
var combined, line, nextLine;
combined = [];
while ((line = lines.shift()) !== void 0) {
if (line.type === 'text') {
while (lines[0] && lines[0].type === 'text' && line.cw === lines[0].cw) {
nextLine = lines.shift();
line.text += "\\n" + (whitespace(nextLine.hw)) + nextLine.text;
}
}
combined.push(line);
}
return combined;
};
HamlCoffee.prototype.convertBooleans = function(code) {
if (this.options.format === 'xhtml') {
return '.replace(/\\s(\\w+)=\'true\'/mg, " $1=\'$1\'").replace(/\\s(\\w+)=\'false\'/mg, \'\')';
} else {
return '.replace(/\\s(\\w+)=\'true\'/mg, \' $1\').replace(/\\s(\\w+)=\'false\'/mg, \'\')';
}
};
HamlCoffee.prototype.cleanupWhitespace = function(code) {
if (/\u0091|\u0092/.test(code)) {
return ".replace(/[\\s\\n]*\\u0091/mg, '').replace(/\\u0092[\\s\\n]*/mg, '')";
} else {
return '';
}
};
return HamlCoffee;
})();
}).call(this);