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

jade

Package Overview
Dependencies
Maintainers
0
Versions
131
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

jade - npm Package Compare versions

Comparing version 0.6.3 to 0.7.0

lib/nodes/block-comment.js

18

History.md
0.7.0 / 2011-03-04
==================
* Removed `:javascript` filter (it doesn't really do anything special, use `script` tags)
* Added pipe-less text support. Tags that only accept text nodes (`script`, `textarea`, etc) do not require `|`.
* Added `:text` filter for ad-hoc pipe-less
* Added flexible indentation. Tabs, arbitrary number of spaces etc
* Added conditional-comment support. Closes #146
* Added block comment support
* Added rss example
* Added `:stylus` filter
* Added `:discount` filter
* Fixed; auto-detect xml and do not self-close tags. Closes #147
* Fixed whitespace issue. Closes #118
* Fixed attrs. `,`, `=`, and `:` within attr value strings are valid Closes #133
* Fixed; only output "" when code == null. Ex: `span.name= user.name` when undefined or null will not output "undefined". Closes #130
* Fixed; throw on unexpected token instead of hanging
0.6.3 / 2011-02-02

@@ -3,0 +21,0 @@ ==================

514

lib/compiler.js

@@ -12,7 +12,7 @@

var nodes = require('./nodes'),
filters = require('./filters'),
doctypes = require('./doctypes'),
selfClosing = require('./self-closing'),
utils = require('./utils');
var nodes = require('./nodes')
, filters = require('./filters')
, doctypes = require('./doctypes')
, selfClosing = require('./self-closing')
, utils = require('./utils');

@@ -28,4 +28,4 @@ /**

var Compiler = module.exports = function Compiler(node, options) {
this.options = options = options || {};
this.node = node;
this.options = options = options || {};
this.node = node;
};

@@ -38,250 +38,272 @@

Compiler.prototype = {
/**
* Compile parse tree to JavaScript.
*
* @api public
*/
compile: function(){
this.buf = [];
this.visit(this.node);
return this.buf.join('\n');
},
/**
* Buffer the given `str` optionally escaped.
*
* @param {String} str
* @param {Boolean} esc
* @api public
*/
buffer: function(str, esc){
if (esc) str = utils.escape(str);
this.buf.push("buf.push('" + str + "');");
},
/**
* Buffer the given `node`'s lineno.
*
* @param {Node} node
* @api public
*/
line: function(node){
if (node.instrumentLineNumber === false) return;
this.buf.push('__.lineno = ' + node.line + ';');
},
/**
* Visit `node`.
*
* @param {Node} node
* @api public
*/
visit: function(node){
this.line(node);
return this.visitNode(node);
},
/**
* Visit `node`.
*
* @param {Node} node
* @api public
*/
visitNode: function(node){
return this['visit' + node.constructor.name](node);
},
/**
* Visit all nodes in `block`.
*
* @param {Block} block
* @api public
*/
/**
* Compile parse tree to JavaScript.
*
* @api public
*/
compile: function(){
this.buf = [];
this.visit(this.node);
return this.buf.join('\n');
},
/**
* Buffer the given `str` optionally escaped.
*
* @param {String} str
* @param {Boolean} esc
* @api public
*/
buffer: function(str, esc){
if (esc) str = utils.escape(str);
this.buf.push("buf.push('" + str + "');");
},
/**
* Buffer the given `node`'s lineno.
*
* @param {Node} node
* @api public
*/
line: function(node){
if (node.instrumentLineNumber === false) return;
this.buf.push('__.lineno = ' + node.line + ';');
},
/**
* Visit `node`.
*
* @param {Node} node
* @api public
*/
visit: function(node){
this.line(node);
return this.visitNode(node);
},
/**
* Visit `node`.
*
* @param {Node} node
* @api public
*/
visitNode: function(node){
return this['visit' + node.constructor.name](node);
},
/**
* Visit all nodes in `block`.
*
* @param {Block} block
* @api public
*/
visitBlock: function(block){
for (var i = 0, len = block.length; i < len; ++i) {
this.visit(block[i]);
}
},
/**
* Visit `doctype`. Sets terse mode to `true` when html 5
* is used, causing self-closing tags to end with ">" vs "/>",
* and boolean attributes are not mirrored.
*
* @param {Doctype} doctype
* @api public
*/
visitDoctype: function(doctype){
var name = doctype.val;
if ('5' == name) this.terse = true;
doctype = doctypes[name || 'default'];
if (!doctype) throw new Error('unknown doctype "' + name + '"');
this.buffer(doctype);
},
/**
* Visit `tag` buffering tag markup, generating
* attributes, visiting the `tag`'s code and block.
*
* @param {Tag} tag
* @api public
*/
visitTag: function(tag){
var name = tag.name;
visitBlock: function(block){
for (var i = 0, len = block.length; i < len; ++i) {
this.visit(block[i]);
}
},
/**
* Visit `doctype`. Sets terse mode to `true` when html 5
* is used, causing self-closing tags to end with ">" vs "/>",
* and boolean attributes are not mirrored.
*
* @param {Doctype} doctype
* @api public
*/
visitDoctype: function(doctype){
var name = doctype.val;
if ('5' == name) this.terse = true;
doctype = doctypes[name || 'default'];
this.xml = 0 == doctype.indexOf('<?xml');
if (!doctype) throw new Error('unknown doctype "' + name + '"');
this.buffer(doctype);
},
/**
* Visit `tag` buffering tag markup, generating
* attributes, visiting the `tag`'s code and block.
*
* @param {Tag} tag
* @api public
*/
visitTag: function(tag){
var name = tag.name;
if (~selfClosing.indexOf(name)) {
this.buffer('<' + name);
this.visitAttributes(tag.attrs);
this.terse
? this.buffer('>')
: this.buffer('/>');
} else {
// Optimize attributes buffering
if (tag.attrs.length) {
this.buffer('<' + name);
if (tag.attrs.length) this.visitAttributes(tag.attrs);
this.buffer('>');
} else {
this.buffer('<' + name + '>');
}
if (tag.code) this.visitCode(tag.code);
if (tag.text) this.buffer(utils.text(tag.text.join('\\n').trimLeft()));
this.visit(tag.block);
this.buffer('</' + name + '>');
}
},
/**
* Visit `filter`, throwing when the filter does not exist.
*
* @param {Filter} filter
* @api public
*/
visitFilter: function(filter){
var fn = filters[filter.name];
if (!fn) throw new Error('unknown filter ":' + filter.name + '"');
if (filter.block instanceof nodes.Block) {
this.buf.push(fn(filter.block, this, filter.attrs));
} else {
this.buffer(fn(utils.text(filter.block.join('\\n')), filter.attrs));
}
},
/**
* Visit `text` node.
*
* @param {Text} text
* @api public
*/
visitText: function(text){
this.buffer(utils.text(text.join('\\n'), true));
},
/**
* Visit a `comment`, only buffering when the buffer flag is set.
*
* @param {Comment} comment
* @api public
*/
visitComment: function(comment){
if (!comment.buffer) return;
this.buffer('<!--' + utils.escape(comment.val) + '-->');
},
/**
* Visit `code`, respecting buffer / escape flags.
* If the code is followed by a block, wrap it in
* a self-calling function.
*
* @param {Code} code
* @api public
*/
visitCode: function(code){
// Wrap code blocks with {}.
// we only wrap unbuffered code blocks ATM
// since they are usually flow control
if (~selfClosing.indexOf(name) && !this.xml) {
this.buffer('<' + name);
this.visitAttributes(tag.attrs);
this.terse
? this.buffer('>')
: this.buffer('/>');
} else {
// Optimize attributes buffering
if (tag.attrs.length) {
this.buffer('<' + name);
if (tag.attrs.length) this.visitAttributes(tag.attrs);
this.buffer('>');
} else {
this.buffer('<' + name + '>');
}
if (tag.code) this.visitCode(tag.code);
if (tag.text) this.buffer(utils.text(tag.text[0].trimLeft()));
this.visit(tag.block);
this.buffer('</' + name + '>');
}
},
/**
* Visit `filter`, throwing when the filter does not exist.
*
* @param {Filter} filter
* @api public
*/
visitFilter: function(filter){
var fn = filters[filter.name];
if (!fn) throw new Error('unknown filter ":' + filter.name + '"');
if (filter.block instanceof nodes.Block) {
this.buf.push(fn(filter.block, this, filter.attrs));
} else {
this.buffer(fn(utils.text(filter.block.join('\\n')), filter.attrs));
}
},
/**
* Visit `text` node.
*
* @param {Text} text
* @api public
*/
visitText: function(text){
this.buffer(utils.text(text.join('\\n')));
this.buffer('\\n');
},
/**
* Visit a `comment`, only buffering when the buffer flag is set.
*
* @param {Comment} comment
* @api public
*/
visitComment: function(comment){
if (!comment.buffer) return;
this.buffer('<!--' + utils.escape(comment.val) + '-->');
},
/**
* Visit a `BlockComment`.
*
* @param {Comment} comment
* @api public
*/
visitBlockComment: function(comment){
if (0 == comment.val.indexOf('if')) {
this.buffer('<!--[' + comment.val + ']>');
this.visit(comment.block);
this.buffer('<![endif]-->');
} else {
this.buffer('<!--' + comment.val);
this.visit(comment.block);
this.buffer('-->');
}
},
/**
* Visit `code`, respecting buffer / escape flags.
* If the code is followed by a block, wrap it in
* a self-calling function.
*
* @param {Code} code
* @api public
*/
visitCode: function(code){
// Wrap code blocks with {}.
// we only wrap unbuffered code blocks ATM
// since they are usually flow control
// Buffer code
if (code.buffer) {
var val = code.val.trimLeft();
if (code.escape) val = 'escape(' + val + ')';
this.buf.push("buf.push(" + val + ");");
} else {
this.buf.push(code.val);
}
// Buffer code
if (code.buffer) {
var val = code.val.trimLeft();
val = '(' + val + ') == null ? "" : (' + val + ')';
if (code.escape) val = 'escape(' + val + ')';
this.buf.push("buf.push(" + val + ");");
} else {
this.buf.push(code.val);
}
// Block support
if (code.block) {
if (!code.buffer) this.buf.push('{');
this.visit(code.block);
if (!code.buffer) this.buf.push('}');
}
},
/**
* Visit `each` block.
*
* @param {Each} each
* @api public
*/
visitEach: function(each){
this.buf.push(''
+ '// iterate ' + each.obj + '\n'
+ '(function(){\n'
+ ' if (\'number\' == typeof ' + each.obj + '.length) {\n'
+ ' for (var ' + each.key + ' = 0, $$l = ' + each.obj + '.length; ' + each.key + ' < $$l; ' + each.key + '++) {\n'
+ ' var ' + each.val + ' = ' + each.obj + '[' + each.key + '];\n');
// Block support
if (code.block) {
if (!code.buffer) this.buf.push('{');
this.visit(code.block);
if (!code.buffer) this.buf.push('}');
}
},
/**
* Visit `each` block.
*
* @param {Each} each
* @api public
*/
visitEach: function(each){
this.buf.push(''
+ '// iterate ' + each.obj + '\n'
+ '(function(){\n'
+ ' if (\'number\' == typeof ' + each.obj + '.length) {\n'
+ ' for (var ' + each.key + ' = 0, $$l = ' + each.obj + '.length; ' + each.key + ' < $$l; ' + each.key + '++) {\n'
+ ' var ' + each.val + ' = ' + each.obj + '[' + each.key + '];\n');
this.visit(each.block);
this.visit(each.block);
this.buf.push(''
+ ' }\n'
+ ' } else {\n'
+ ' for (var ' + each.key + ' in ' + each.obj + ') {\n'
+ ' var ' + each.val + ' = ' + each.obj + '[' + each.key + '];\n');
this.buf.push(''
+ ' }\n'
+ ' } else {\n'
+ ' for (var ' + each.key + ' in ' + each.obj + ') {\n'
+ ' var ' + each.val + ' = ' + each.obj + '[' + each.key + '];\n');
this.visit(each.block);
this.visit(each.block);
this.buf.push(' }\n }\n}).call(this);\n');
},
/**
* Visit `attrs`.
*
* @param {Array} attrs
* @api public
*/
visitAttributes: function(attrs){
var buf = [],
classes = [];
if (this.terse) buf.push('terse: true');
attrs.forEach(function(attr){
if (attr.name == 'class') {
classes.push('(' + attr.val + ')');
} else {
var pair = "'" + attr.name + "':(" + attr.val + ')';
buf.push(pair);
}
});
if (classes.length) {
classes = classes.join(" + ' ' + ");
buf.push("class: " + classes);
}
this.buf.push("buf.push(attrs({ " + buf.join(', ') + " }));");
this.buf.push(' }\n }\n}).call(this);\n');
},
/**
* Visit `attrs`.
*
* @param {Array} attrs
* @api public
*/
visitAttributes: function(attrs){
var buf = []
, classes = [];
if (this.terse) buf.push('terse: true');
attrs.forEach(function(attr){
if (attr.name == 'class') {
classes.push('(' + attr.val + ')');
} else {
var pair = "'" + attr.name + "':(" + attr.val + ')';
buf.push(pair);
}
});
if (classes.length) {
classes = classes.join(" + ' ' + ");
buf.push("class: " + classes);
}
this.buf.push("buf.push(attrs({ " + buf.join(', ') + " }));");
}
};

@@ -9,11 +9,11 @@

module.exports = {
'5': '<!DOCTYPE html>',
'xml': '<?xml version="1.0" encoding="utf-8" ?>',
'default': '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">',
'transitional': '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">',
'strict': '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">',
'frameset': '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Frameset//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-frameset.dtd">',
'1.1': '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">',
'basic': '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML Basic 1.1//EN" "http://www.w3.org/TR/xhtml-basic/xhtml-basic11.dtd">',
'mobile': '<!DOCTYPE html PUBLIC "-//WAPFORUM//DTD XHTML Mobile 1.2//EN" "http://www.openmobilealliance.org/tech/DTD/xhtml-mobile12.dtd">'
'5': '<!DOCTYPE html>'
, 'xml': '<?xml version="1.0" encoding="utf-8" ?>'
, 'default': '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">'
, 'transitional': '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">'
, 'strict': '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">'
, 'frameset': '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Frameset//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-frameset.dtd">'
, '1.1': '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">'
, 'basic': '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML Basic 1.1//EN" "http://www.w3.org/TR/xhtml-basic/xhtml-basic11.dtd">'
, 'mobile': '<!DOCTYPE html PUBLIC "-//WAPFORUM//DTD XHTML Mobile 1.2//EN" "http://www.openmobilealliance.org/tech/DTD/xhtml-mobile12.dtd">'
};

@@ -9,63 +9,79 @@

module.exports = {
/**
* Wrap text with CDATA block.
*/
cdata: function(str){
return '<![CDATA[\\n' + str + '\\n]]>';
},
/**
* Wrap text with script and CDATA tags.
*/
javascript: function(str){
return '<script type="text/javascript">\\n' + str + '</script>';
},
/**
* Transform sass to css, wrapped in style tags.
*/
sass: function(str){
str = str.replace(/\\n/g, '\n');
var sass = require('sass').render(str).replace(/\n/g, '\\n');
return '<style>' + sass + '</style>';
},
/**
* Transform sass to css, wrapped in style tags.
*/
less: function(str){
var less;
str = str.replace(/\\n/g, '\n');
require('less').render(str, function(err, css){
less = '<style>' + css.replace(/\n/g, '\\n') + '</style>';
});
return less;
},
/**
* Transform markdown to html.
*/
markdown: function(str){
var md = require('markdown');
str = str.replace(/\\n/g, '\n');
return (md.toHTML
? md.toHTML(str)
: md.parse(str)).replace(/\n/g, '\\n').replace(/'/g,'&#39;');
},
/**
* Wrap text with CDATA block.
*/
cdata: function(str){
return '<![CDATA[\\n' + str + '\\n]]>';
},
/**
* Transform sass to css, wrapped in style tags.
*/
sass: function(str){
str = str.replace(/\\n/g, '\n');
var sass = require('sass').render(str).replace(/\n/g, '\\n');
return '<style>' + sass + '</style>';
},
/**
* Transform stylus to css, wrapped in style tags.
*/
stylus: function(str){
var ret;
str = str.replace(/\\n/g, '\n');
var stylus = require('stylus');
stylus(str).render(function(err, css){
if (err) throw err;
ret = css.replace(/\n/g, '\\n');
});
return '<style>' + ret + '</style>';
},
/**
* Transform sass to css, wrapped in style tags.
*/
less: function(str){
var ret;
str = str.replace(/\\n/g, '\n');
require('less').render(str, function(err, css){
if (err) throw err;
ret = '<style>' + css.replace(/\n/g, '\\n') + '</style>';
});
return ret;
},
/**
* Transform markdown to html.
*/
markdown: function(str){
var md = require('markdown');
str = str.replace(/\\n/g, '\n');
return md.parse(str).replace(/\n/g, '\\n').replace(/'/g,'&#39;');
},
/**
* Transform markdown to html.
*/
discount: function(str){
var md = require('discount');
str = str.replace(/\\n/g, '\n');
return md.parse(str).replace(/\n/g, '\\n').replace(/'/g,'&#39;');
},
/**
* Transform coffeescript to javascript.
*/
/**
* Transform coffeescript to javascript.
*/
coffeescript: function(str){
str = str.replace(/\\n/g, '\n');
var js = require('coffee-script').compile(str).replace(/\n/g, '\\n');
return '<script type="text/javascript">\\n' + js + '</script>';
}
coffeescript: function(str){
str = str.replace(/\\n/g, '\n');
var js = require('coffee-script').compile(str).replace(/\n/g, '\\n');
return '<script type="text/javascript">\\n' + js + '</script>';
}
};

@@ -12,5 +12,5 @@

var Parser = require('./parser'),
Compiler = require('./compiler'),
fs = require('fs');
var Parser = require('./parser')
, Compiler = require('./compiler')
, fs = require('fs');

@@ -21,3 +21,3 @@ /**

exports.version = '0.6.3';
exports.version = '0.7.0';

@@ -89,24 +89,24 @@ /**

function attrs(obj){
var buf = [],
terse = obj.terse;
delete obj.terse;
var keys = Object.keys(obj),
len = keys.length;
if (len) {
buf.push('');
for (var i = 0; i < len; ++i) {
var key = keys[i],
val = obj[key];
if (typeof val === 'boolean' || val === '' || val == null) {
if (val) {
terse
? buf.push(key)
: buf.push(key + '="' + key + '"');
}
} else {
buf.push(key + '="' + escape(val) + '"');
}
var buf = []
, terse = obj.terse;
delete obj.terse;
var keys = Object.keys(obj)
, len = keys.length;
if (len) {
buf.push('');
for (var i = 0; i < len; ++i) {
var key = keys[i]
, val = obj[key];
if (typeof val === 'boolean' || val === '' || val == null) {
if (val) {
terse
? buf.push(key)
: buf.push(key + '="' + key + '"');
}
} else {
buf.push(key + '="' + escape(val) + '"');
}
}
return buf.join(' ');
}
return buf.join(' ');
}

@@ -123,7 +123,7 @@

function escape(html){
return String(html)
.replace(/&(?!\w+;)/g, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;');
return String(html)
.replace(/&(?!\w+;)/g, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;');
}

@@ -143,20 +143,20 @@

function rethrow(err, str, filename, lineno){
var start = lineno - 3 > 0
? lineno - 3
: 0;
var start = lineno - 3 > 0
? lineno - 3
: 0;
// Error context
var context = str.split('\n').slice(start, lineno).map(function(line, i){
return ' '
+ (i + start + 1)
+ ". '"
+ line.replace("'", "\\'")
+ "'";
}).join('\n');
// Error context
var context = str.split('\n').slice(start, lineno).map(function(line, i){
return ' '
+ (i + start + 1)
+ ". '"
+ line.replace("'", "\\'")
+ "'";
}).join('\n');
// Alter exception message
err.path = filename;
err.message = (filename || 'Jade') + ':' + lineno
+ '\n' + context + '\n\n' + err.message;
throw err;
// Alter exception message
err.path = filename;
err.message = (filename || 'Jade') + ':' + lineno
+ '\n' + context + '\n\n' + err.message;
throw err;
}

@@ -174,31 +174,31 @@

function parse(str, options){
var filename = options.filename;
try {
// Parse
var parser = new Parser(str, filename);
if (options.debug) parser.debug();
var filename = options.filename;
try {
// Parse
var parser = new Parser(str, filename);
if (options.debug) parser.debug();
// Compile
var compiler = new (options.compiler || Compiler)(parser.parse(), options),
js = compiler.compile();
// Compile
var compiler = new (options.compiler || Compiler)(parser.parse(), options)
, js = compiler.compile();
// Debug compiler
if (options.debug) {
console.log('\n\x1b[1mCompiled Function\x1b[0m:\n\n%s', js.replace(/^/gm, ' '));
}
// Debug compiler
if (options.debug) {
console.log('\n\x1b[1mCompiled Function\x1b[0m:\n\n%s', js.replace(/^/gm, ' '));
}
try {
return ''
+ attrs.toString() + '\n\n'
+ escape.toString() + '\n\n'
+ 'var buf = [];\n'
+ 'with (locals || {}) {' + js + '}'
+ 'return buf.join("");';
} catch (err) {
process.compile(js, filename || 'Jade');
return;
}
try {
return ''
+ attrs.toString() + '\n\n'
+ escape.toString() + '\n\n'
+ 'var buf = [];\n'
+ 'with (locals || {}) {' + js + '}'
+ 'return buf.join("");';
} catch (err) {
rethrow(err, str, filename, parser.lexer.lineno);
process.compile(js, filename || 'Jade');
return;
}
} catch (err) {
rethrow(err, str, filename, parser.lexer.lineno);
}
}

@@ -216,20 +216,20 @@

exports.compile = function(str, options){
var options = options || {},
input = JSON.stringify(str),
filename = options.filename
? JSON.stringify(options.filename)
: 'undefined';
var options = options || {}
, input = JSON.stringify(str)
, filename = options.filename
? JSON.stringify(options.filename)
: 'undefined';
// Reduce closure madness by injecting some locals
var fn = [
'var __ = { lineno: 1, input: ' + input + ', filename: ' + filename + ' };',
rethrow.toString(),
'try {',
parse(String(str), options || {}),
'} catch (err) {',
' rethrow(err, __.input, __.filename, __.lineno);',
'}'
].join('\n');
// Reduce closure madness by injecting some locals
var fn = [
'var __ = { lineno: 1, input: ' + input + ', filename: ' + filename + ' };'
, rethrow.toString()
, 'try {'
, parse(String(str), options || {})
, '} catch (err) {'
, ' rethrow(err, __.input, __.filename, __.lineno);'
, '}'
].join('\n');
return new Function('locals', fn);
return new Function('locals', fn);
};

@@ -255,33 +255,33 @@

exports.render = function(str, options){
var fn,
options = options || {},
filename = options.filename;
var fn
, options = options || {}
, filename = options.filename;
// Accept Buffers
str = String(str);
// Accept Buffers
str = String(str);
// Cache support
if (options.cache) {
if (filename) {
if (cache[filename]) {
fn = cache[filename];
} else {
fn = cache[filename] = new Function('locals', parse(str, options));
}
} else {
throw new Error('filename is required when using the cache option');
}
// Cache support
if (options.cache) {
if (filename) {
if (cache[filename]) {
fn = cache[filename];
} else {
fn = cache[filename] = new Function('locals', parse(str, options));
}
} else {
fn = new Function('locals', parse(str, options));
throw new Error('filename is required when using the cache option');
}
} else {
fn = new Function('locals', parse(str, options));
}
// Render the template
try {
var locals = options.locals || {}
, meta = { lineno: 1 };
locals.__ = meta;
return fn.call(options.scope, locals);
} catch (err) {
rethrow(err, str, filename, meta.lineno);
}
// Render the template
try {
var locals = options.locals || {}
, meta = { lineno: 1 };
locals.__ = meta;
return fn.call(options.scope, locals);
} catch (err) {
rethrow(err, str, filename, meta.lineno);
}
};

@@ -299,25 +299,25 @@

exports.renderFile = function(path, options, fn){
if (typeof options === 'function') {
fn = options;
options = {};
}
options.filename = path;
if (typeof options === 'function') {
fn = options;
options = {};
}
options.filename = path;
// Primed cache
if (options.cache && cache[path]) {
try {
fn(null, exports.render('', options));
} catch (err) {
fn(err);
}
} else {
fs.readFile(path, 'utf8', function(err, str){
if (err) return fn(err);
try {
fn(null, exports.render(str, options));
} catch (err) {
fn(err);
}
});
// Primed cache
if (options.cache && cache[path]) {
try {
fn(null, exports.render('', options));
} catch (err) {
fn(err);
}
} else {
fs.readFile(path, 'utf8', function(err, str){
if (err) return fn(err);
try {
fn(null, exports.render(str, options));
} catch (err) {
fn(err);
}
});
}
};

@@ -16,7 +16,10 @@

var Lexer = module.exports = function Lexer(str) {
this.input = str.replace(/\r\n|\r/g, '\n').replace(/\t/g, ' ');
this.deferredTokens = [];
this.lastIndents = 0;
this.lineno = 1;
this.stash = [];
this.input = str.replace(/\r\n|\r/g, '\n');
this.deferredTokens = [];
this.lastIndents = 0;
this.lineno = 1;
this.stash = [];
this.indentStack = [];
this.indentRe = null;
this.textPipe = true;
};

@@ -29,333 +32,419 @@

Lexer.prototype = {
/**
* Construct a token with the given `type` and `val`.
*
* @param {String} type
* @param {String} val
* @return {Object}
* @api private
*/
tok: function(type, val){
return {
type: type,
line: this.lineno,
val: val
/**
* Construct a token with the given `type` and `val`.
*
* @param {String} type
* @param {String} val
* @return {Object}
* @api private
*/
tok: function(type, val){
return {
type: type
, line: this.lineno
, val: val
}
},
/**
* Consume the given `len` of input.
*
* @param {Number} len
* @api private
*/
consume: function(len){
this.input = this.input.substr(len);
},
/**
* Scan for `type` with the given `regexp`.
*
* @param {String} type
* @param {RegExp} regexp
* @return {Object}
* @api private
*/
scan: function(regexp, type){
var captures;
if (captures = regexp.exec(this.input)) {
this.consume(captures[0].length);
return this.tok(type, captures[1]);
}
},
/**
* Defer the given `tok`.
*
* @param {Object} tok
* @api private
*/
defer: function(tok){
this.deferredTokens.push(tok);
},
/**
* Lookahead `n` tokens.
*
* @param {Number} n
* @return {Object}
* @api private
*/
lookahead: function(n){
var fetch = n - this.stash.length;
while (fetch-- > 0) this.stash.push(this.next);
return this.stash[--n];
},
/**
* Return the indexOf `start` / `end` delimiters.
*
* @param {String} start
* @param {String} end
* @return {Number}
* @api private
*/
indexOfDelimiters: function(start, end){
var str = this.input
, nstart = 0
, nend = 0
, pos = 0;
for (var i = 0, len = str.length; i < len; ++i) {
if (start == str[i]) {
++nstart;
} else if (end == str[i]) {
if (++nend == nstart) {
pos = i;
break;
}
},
/**
* Consume the given `len` of input.
*
* @param {Number} len
* @api private
*/
consume: function(len){
this.input = this.input.substr(len);
},
/**
* Scan for `type` with the given `regexp`.
*
* @param {String} type
* @param {RegExp} regexp
* @return {Object}
* @api private
*/
scan: function(regexp, type){
var captures;
if (captures = regexp.exec(this.input)) {
this.consume(captures[0].length);
return this.tok(type, captures[1]);
}
},
/**
* Defer the given `tok`.
*
* @param {Object} tok
* @api private
*/
defer: function(tok){
this.deferredTokens.push(tok);
},
/**
* Lookahead `n` tokens.
*
* @param {Number} n
* @return {Object}
* @api private
*/
lookahead: function(n){
var fetch = n - this.stash.length;
while (fetch-- > 0) this.stash.push(this.next);
return this.stash[--n];
},
/**
* Return the indexOf `start` / `end` delimiters.
*
* @param {String} start
* @param {String} end
* @return {Number}
* @api private
*/
indexOfDelimiters: function(start, end){
var str = this.input,
nstart = 0,
nend = 0,
pos = 0;
for (var i = 0, len = str.length; i < len; ++i) {
if (start == str[i]) {
++nstart;
} else if (end == str[i]) {
if (++nend == nstart) {
pos = i;
break;
}
}
}
return pos;
},
/**
* Stashed token.
*/
get stashed() {
return this.stash.length
&& this.stash.shift();
},
/**
* Deferred token.
*/
get deferred() {
return this.deferredTokens.length
&& this.deferredTokens.shift();
},
/**
* end-of-source.
*/
get eos() {
if (this.input.length) return;
return this.lastIndents-- > 0
? this.tok('outdent')
: this.tok('eos');
},
/**
* Comment.
*/
get comment() {
var captures;
if (captures = /^ *\/\/(-)?([^\n]+)/.exec(this.input)) {
this.consume(captures[0].length);
var tok = this.tok('comment', captures[2]);
tok.buffer = captures[1] !== '-';
return tok;
}
},
/**
* Tag.
*/
get tag() {
return this.scan(/^(\w[-:\w]*)/, 'tag');
},
/**
* Filter.
*/
get filter() {
return this.scan(/^:(\w+)/, 'filter');
},
/**
* Doctype.
*/
get doctype() {
return this.scan(/^!!! *(\w+)?/, 'doctype');
},
/**
* Id.
*/
get id() {
return this.scan(/^#([\w-]+)/, 'id');
},
/**
* Class.
*/
get className() {
return this.scan(/^\.([\w-]+)/, 'class');
},
/**
* Text.
*/
get text() {
return this.scan(/^(?:\| ?)?([^\n]+)/, 'text');
},
}
}
return pos;
},
/**
* Stashed token.
*/
get stashed() {
return this.stash.length
&& this.stash.shift();
},
/**
* Deferred token.
*/
get deferred() {
return this.deferredTokens.length
&& this.deferredTokens.shift();
},
/**
* end-of-source.
*/
get eos() {
if (this.input.length) return;
if (this.indentStack.length) {
this.indentStack.shift();
return this.tok('outdent');
} else {
return this.tok('eos');
}
},
/**
* Each.
*/
get each() {
var captures;
if (captures = /^- *each *(\w+)(?: *, *(\w+))? * in *([^\n]+)/.exec(this.input)) {
this.consume(captures[0].length);
var tok = this.tok('each', captures[1]);
tok.key = captures[2] || 'index';
tok.code = captures[3];
return tok;
}
},
/**
* Code.
*/
get code() {
var captures;
if (captures = /^(!?=|-)([^\n]+)/.exec(this.input)) {
this.consume(captures[0].length);
var flags = captures[1];
captures[1] = captures[2];
var tok = this.tok('code', captures[1]);
tok.escape = flags[0] === '=';
tok.buffer = flags[0] === '=' || flags[1] === '=';
return tok;
}
},
/**
* Attributes.
*/
get attrs() {
if ('(' == this.input[0]) {
var index = this.indexOfDelimiters('(', ')'),
str = this.input.substr(1, index-1);
this.consume(index + 1);
var tok = this.tok('attrs', str),
attrs = tok.val.split(/ *, *(?=['"\w-]+ *[:=]|[\w-]+ *$)/);
tok.attrs = {};
for (var i = 0, len = attrs.length; i < len; ++i) {
var pair = attrs[i];
// Support = and :
var colon = pair.indexOf(':'),
equal = pair.indexOf('=');
// Boolean
if (colon < 0 && equal < 0) {
var key = pair,
val = true;
} else {
// Split on first = or :
var split = equal >= 0
? equal
: colon;
if (colon >= 0 && colon < equal) split = colon;
var key = pair.substr(0, split),
val = pair.substr(++split, pair.length);
}
tok.attrs[key.trim().replace(/^['"]|['"]$/g, '')] = val;
/**
* Block comment
*/
get blockComment() {
var captures;
if (captures = /^\/([^\n]+)/.exec(this.input)) {
this.consume(captures[0].length);
var tok = this.tok('block-comment', captures[1]);
return tok;
}
},
/**
* Comment.
*/
get comment() {
var captures;
if (captures = /^ *\/\/(-)?([^\n]+)/.exec(this.input)) {
this.consume(captures[0].length);
var tok = this.tok('comment', captures[2]);
tok.buffer = '-' != captures[1];
return tok;
}
},
/**
* Tag.
*/
get tag() {
return this.scan(/^(\w[-:\w]*)/, 'tag');
},
/**
* Filter.
*/
get filter() {
return this.scan(/^:(\w+)/, 'filter');
},
/**
* Doctype.
*/
get doctype() {
return this.scan(/^!!! *(\w+)?/, 'doctype');
},
/**
* Id.
*/
get id() {
return this.scan(/^#([\w-]+)/, 'id');
},
/**
* Class.
*/
get className() {
return this.scan(/^\.([\w-]+)/, 'class');
},
/**
* Text.
*/
get text() {
return this.scan(/^(?:\| ?)?([^\n]+)/, 'text');
},
/**
* Each.
*/
get each() {
var captures;
if (captures = /^- *each *(\w+)(?: *, *(\w+))? * in *([^\n]+)/.exec(this.input)) {
this.consume(captures[0].length);
var tok = this.tok('each', captures[1]);
tok.key = captures[2] || 'index';
tok.code = captures[3];
return tok;
}
},
/**
* Code.
*/
get code() {
var captures;
if (captures = /^(!?=|-)([^\n]+)/.exec(this.input)) {
this.consume(captures[0].length);
var flags = captures[1];
captures[1] = captures[2];
var tok = this.tok('code', captures[1]);
tok.escape = flags[0] === '=';
tok.buffer = flags[0] === '=' || flags[1] === '=';
return tok;
}
},
/**
* Attributes.
*/
get attrs() {
if ('(' == this.input[0]) {
var index = this.indexOfDelimiters('(', ')')
, str = this.input.substr(1, index-1)
, tok = this.tok('attrs')
, len = str.length
, state = 'key'
, key = ''
, val = ''
, c;
this.consume(index + 1);
tok.attrs = {};
function parse(c) {
switch (c) {
case ',':
if ('string' == state) {
val += c;
} else {
state = 'key';
val = val.trim();
tok.attrs[key.trim().replace(/^['"]|['"]$/g, '')] = '' == val
? true
: val;
key = val = '';
}
return tok;
}
},
/**
* Indent.
*/
get indent() {
var captures;
if (captures = /^\n( *)/.exec(this.input)) {
++this.lineno;
this.consume(captures[0].length);
var tok = this.tok('indent', captures[1]),
indents = tok.val.length / 2;
if (this.input[0] === '\n') {
tok.type = 'newline';
return tok;
} else if (indents % 1 !== 0) {
throw new Error('Invalid indentation, got '
+ tok.val.length + ' space'
+ (tok.val.length > 1 ? 's' : '')
+ ', must be a multiple of two.');
} else if (indents === this.lastIndents) {
tok.type = 'newline';
} else if (indents > this.lastIndents + 1) {
throw new Error('Invalid indentation, got '
+ indents + ' expected '
+ (this.lastIndents + 1) + '.');
} else if (indents < this.lastIndents) {
var n = this.lastIndents - indents;
tok.type = 'outdent';
while (--n) {
this.defer(this.tok('outdent'));
}
break;
case ':':
case '=':
if ('string' == state || 'val' == state) {
val += c;
} else {
state = 'val';
}
this.lastIndents = indents;
return tok;
break;
case '"':
case "'":
if ('key' == state) break;
state = 'string' == state
? 'val'
: 'string';
val += c;
break;
default:
switch (state) {
case 'key': key += c; break;
case 'val': val += c; break;
case 'string': val += c; break;
}
}
},
/**
* Return the next token object, or those
* previously stashed by lookahead.
*
* @return {Object}
* @api private
*/
get advance(){
return this.stashed
|| this.next;
},
/**
* Return the next token object.
*
* @return {Object}
* @api private
*/
get next() {
return this.deferred
|| this.eos
|| this.tag
|| this.filter
|| this.each
|| this.code
|| this.doctype
|| this.id
|| this.className
|| this.attrs
|| this.indent
|| this.comment
|| this.text;
}
for (var i = 0; i < len; ++i) {
parse(str[i]);
}
parse(',');
return tok;
}
},
/**
* Indent.
*/
get indent() {
var captures, re;
// established regexp
if (this.indentRe) {
captures = this.indentRe.exec(this.input);
// determine regexp
} else {
// tabs
re = /^\n(\t*) */;
captures = re.exec(this.input);
// spaces
if (captures && !captures[1].length) {
re = /^\n( *)/;
captures = re.exec(this.input);
}
// established
if (captures && captures[1].length) this.indentRe = re;
}
if (captures) {
var tok
, indents = captures[1].length;
++this.lineno;
this.consume(indents + 1);
if (' ' == this.input[0] || '\t' == this.input[0]) {
throw new Error('Invalid indentation, you can use tabs or spaces but not both');
}
// blank line
if ('\n' == this.input[0]) return this.advance;
// outdent
if (this.indentStack.length && indents < this.indentStack[0]) {
while (this.indentStack.length && this.indentStack[0] > indents) {
this.stash.push(this.tok('outdent'));
this.indentStack.shift();
}
tok = this.stash.pop();
// indent
} else if (indents && indents != this.indentStack[0]) {
this.indentStack.unshift(indents);
tok = this.tok('indent');
// newline
} else {
tok = this.tok('newline');
}
return tok;
}
},
/**
* Pipe-less text consumed only when
* textPipe is false;
*/
get pipelessText() {
if (false === this.textPipe) {
if ('\n' == this.input[0]) return;
var i = this.input.indexOf('\n')
, str = this.input.substr(0, i);
if (-1 == i) return;
this.consume(str.length);
return this.tok('text', str);
}
},
/**
* Return the next token object, or those
* previously stashed by lookahead.
*
* @return {Object}
* @api private
*/
get advance(){
return this.stashed
|| this.next;
},
/**
* Return the next token object.
*
* @return {Object}
* @api private
*/
get next() {
return this.deferred
|| this.eos
|| this.pipelessText
|| this.tag
|| this.filter
|| this.each
|| this.code
|| this.doctype
|| this.id
|| this.className
|| this.attrs
|| this.indent
|| this.comment
|| this.blockComment
|| this.text;
}
};

@@ -22,3 +22,3 @@

var Block = module.exports = function Block(node){
if (node) this.push(node);
if (node) this.push(node);
};

@@ -25,0 +25,0 @@

@@ -25,6 +25,6 @@

var Code = module.exports = function Code(val, buffer, escape) {
this.val = val;
this.buffer = buffer;
this.escape = escape;
if (/^ *else/.test(val)) this.instrumentLineNumber = false;
this.val = val;
this.buffer = buffer;
this.escape = escape;
if (/^ *else/.test(val)) this.instrumentLineNumber = false;
};

@@ -31,0 +31,0 @@

@@ -24,4 +24,4 @@

var Comment = module.exports = function Comment(val, buffer) {
this.val = val;
this.buffer = buffer;
this.val = val;
this.buffer = buffer;
};

@@ -28,0 +28,0 @@

@@ -22,3 +22,3 @@

var Doctype = module.exports = function Doctype(val) {
this.val = val;
this.val = val;
};

@@ -25,0 +25,0 @@

@@ -25,6 +25,6 @@

var Each = module.exports = function Each(obj, val, key, block) {
this.obj = obj;
this.val = val;
this.key = key;
this.block = block;
this.obj = obj;
this.val = val;
this.key = key;
this.block = block;
};

@@ -31,0 +31,0 @@

@@ -24,5 +24,5 @@

var Filter = module.exports = function Filter(name, block, attrs) {
this.name = name;
this.block = block;
this.attrs = attrs;
this.name = name;
this.block = block;
this.attrs = attrs;
};

@@ -29,0 +29,0 @@

@@ -16,2 +16,3 @@

exports.Comment = require('./comment');
exports.BlockComment = require('./block-comment');
exports.Doctype = require('./doctype');

@@ -24,5 +24,5 @@

var Tag = module.exports = function Tag(name, block) {
this.name = name;
this.attrs = [];
this.block = block || new Block;
this.name = name;
this.attrs = [];
this.block = block || new Block;
};

@@ -42,4 +42,4 @@

Tag.prototype.setAttribute = function(name, val){
this.attrs.push({ name: name, val: val });
return this;
this.attrs.push({ name: name, val: val });
return this;
};

@@ -55,7 +55,7 @@

Tag.prototype.removeAttribute = function(name){
for (var i = 0, len = this.attrs.length; i < len; ++i) {
if (this.attrs[i] && this.attrs[i].name == name) {
delete this.attrs[i];
}
for (var i = 0, len = this.attrs.length; i < len; ++i) {
if (this.attrs[i] && this.attrs[i].name == name) {
delete this.attrs[i];
}
}
};

@@ -72,7 +72,7 @@

Tag.prototype.getAttribute = function(name){
for (var i = 0, len = this.attrs.length; i < len; ++i) {
if (this.attrs[i] && this.attrs[i].name == name) {
return this.attrs[i].val;
}
for (var i = 0, len = this.attrs.length; i < len; ++i) {
if (this.attrs[i] && this.attrs[i].name == name) {
return this.attrs[i].val;
}
}
};

@@ -79,0 +79,0 @@

@@ -22,3 +22,3 @@

var Text = module.exports = function Text(line) {
if ('string' == typeof line) this.push(line);
if ('string' == typeof line) this.push(line);
};

@@ -25,0 +25,0 @@

@@ -12,4 +12,4 @@

var Lexer = require('./lexer'),
nodes = require('./nodes');
var Lexer = require('./lexer')
, nodes = require('./nodes');

@@ -24,9 +24,15 @@ /**

var Parser = module.exports = function Parser(str, filename){
this.input = str;
this.lexer = new Lexer(str);
this.filename = filename;
var Parser = exports = module.exports = function Parser(str, filename){
this.input = str;
this.lexer = new Lexer(str);
this.filename = filename;
};
/**
* Tags that may not contain tags.
*/
var textOnly = exports.textOnly = ['script', 'textarea', 'style'];
/**
* Parser prototype.

@@ -36,326 +42,365 @@ */

Parser.prototype = {
/**
* Output parse tree to stdout.
*
* @api public
*/
debug: function(){
var lexer = new Lexer(this.input),
tree = require('sys').inspect(this.parse(), false, 12, true);
console.log('\n\x1b[1mParse Tree\x1b[0m:\n');
console.log(tree);
this.lexer = lexer;
},
/**
* Return the next token object.
*
* @return {Object}
* @api private
*/
/**
* Output parse tree to stdout.
*
* @api public
*/
debug: function(){
var lexer = new Lexer(this.input)
, tree = require('sys').inspect(this.parse(), false, 12, true);
console.log('\n\x1b[1mParse Tree\x1b[0m:\n');
console.log(tree);
this.lexer = lexer;
},
/**
* Return the next token object.
*
* @return {Object}
* @api private
*/
get advance(){
return this.lexer.advance;
},
/**
* Single token lookahead.
*
* @return {Object}
* @api private
*/
get peek() {
return this.lookahead(1);
},
/**
* Return lexer lineno.
*
* @return {Number}
* @api private
*/
get line() {
return this.lexer.lineno;
},
/**
* `n` token lookahead.
*
* @param {Number} n
* @return {Object}
* @api private
*/
lookahead: function(n){
return this.lexer.lookahead(n);
},
/**
* Parse input returning a string of js for evaluation.
*
* @return {String}
* @api public
*/
parse: function(){
var block = new nodes.Block;
block.line = this.line;
while (this.peek.type !== 'eos') {
if (this.peek.type === 'newline') {
this.advance;
} else {
block.push(this.parseExpr());
}
}
return block;
},
/**
* Expect the given type, or throw an exception.
*
* @param {String} type
* @api private
*/
expect: function(type){
if (this.peek.type === type) {
return this.advance;
} else {
throw new Error('expected "' + type + '", but got "' + this.peek.type + '"');
}
},
/**
* Accept the given `type`.
*
* @param {String} type
* @api private
*/
accept: function(type){
if (this.peek.type === type) {
return this.advance;
}
},
/**
* tag
* | doctype
* | filter
* | comment
* | text
* | each
* | code
* | id
* | class
*/
parseExpr: function(){
get advance(){
return this.lexer.advance;
},
/**
* Single token lookahead.
*
* @return {Object}
* @api private
*/
get peek() {
return this.lookahead(1);
},
/**
* Return lexer lineno.
*
* @return {Number}
* @api private
*/
get line() {
return this.lexer.lineno;
},
/**
* `n` token lookahead.
*
* @param {Number} n
* @return {Object}
* @api private
*/
lookahead: function(n){
return this.lexer.lookahead(n);
},
/**
* Parse input returning a string of js for evaluation.
*
* @return {String}
* @api public
*/
parse: function(){
var block = new nodes.Block;
block.line = this.line;
while (this.peek.type !== 'eos') {
if (this.peek.type === 'newline') {
this.advance;
} else {
block.push(this.parseExpr());
}
}
return block;
},
/**
* Expect the given type, or throw an exception.
*
* @param {String} type
* @api private
*/
expect: function(type){
if (this.peek.type === type) {
return this.advance;
} else {
throw new Error('expected "' + type + '", but got "' + this.peek.type + '"');
}
},
/**
* Accept the given `type`.
*
* @param {String} type
* @api private
*/
accept: function(type){
if (this.peek.type === type) {
return this.advance;
}
},
/**
* tag
* | doctype
* | filter
* | comment
* | text
* | each
* | code
* | id
* | class
*/
parseExpr: function(){
switch (this.peek.type) {
case 'tag':
return this.parseTag();
case 'doctype':
return this.parseDoctype();
case 'filter':
return this.parseFilter();
case 'comment':
return this.parseComment();
case 'block-comment':
return this.parseBlockComment();
case 'text':
return this.parseText();
case 'each':
return this.parseEach();
case 'code':
return this.parseCode();
case 'id':
case 'class':
var tok = this.advance;
this.lexer.defer(this.lexer.tok('tag', 'div'));
this.lexer.defer(tok);
return this.parseExpr();
default:
throw new Error('unexpected token "' + this.peek.type + '"');
}
},
/**
* Text
*/
parseText: function(){
var tok = this.expect('text')
, node = new nodes.Text(tok.val);
node.line = this.line;
return node;
},
/**
* code
*/
parseCode: function(){
var tok = this.expect('code')
, node = new nodes.Code(tok.val, tok.buffer, tok.escape);
node.line = this.line;
if ('indent' == this.peek.type) {
node.block = this.parseBlock();
}
return node;
},
/**
* block comment
*/
parseBlockComment: function(){
var tok = this.expect('block-comment')
, node = new nodes.BlockComment(tok.val, this.parseBlock());
node.line = this.line;
return node;
},
/**
* comment
*/
parseComment: function(){
var tok = this.expect('comment')
, node = new nodes.Comment(tok.val, tok.buffer);
node.line = this.line;
return node;
},
/**
* doctype
*/
parseDoctype: function(){
var tok = this.expect('doctype')
, node = new nodes.Doctype(tok.val);
node.line = this.line;
return node;
},
/**
* filter attrs? (text | block)
*/
parseFilter: function(){
var block
, tok = this.expect('filter')
, attrs = this.accept('attrs');
if ('text' == tok.val) {
this.lexer.textPipe = false;
block = this.parseTextBlock();
this.lexer.textPipe = true;
return block;
} else if ('text' == this.lookahead(2).type) {
block = this.parseTextBlock();
} else {
block = this.parseBlock();
}
var node = new nodes.Filter(tok.val, block, attrs && attrs.attrs);
node.line = this.line;
return node;
},
/**
* each block
*/
parseEach: function(){
var tok = this.expect('each')
, node = new nodes.Each(tok.code, tok.val, tok.key, this.parseBlock());
node.line = this.line;
return node;
},
/**
* indent (text | newline)* outdent
*/
parseTextBlock: function(){
var text = new nodes.Text;
text.line = this.line;
this.expect('indent');
while ('outdent' != this.peek.type) {
switch (this.peek.type) {
case 'newline':
this.advance;
break;
case 'indent':
text.push(this.parseTextBlock().map(function(text){
return ' ' + text;
}).join('\\n'));
break;
default:
text.push(this.advance.val);
}
}
this.expect('outdent');
return text;
},
/**
* indent expr* outdent
*/
parseBlock: function(){
var block = new nodes.Block;
block.line = this.line;
this.expect('indent');
while ('outdent' != this.peek.type) {
if ('newline' == this.peek.type) {
this.advance;
} else {
block.push(this.parseExpr());
}
}
this.expect('outdent');
return block;
},
/**
* tag (attrs | class | id)* (text | code)? newline* block?
*/
parseTag: function(){
var name = this.advance.val
, tag = new nodes.Tag(name);
tag.line = this.line;
// (attrs | class | id)*
out:
while (true) {
switch (this.peek.type) {
case 'tag':
return this.parseTag();
case 'doctype':
return this.parseDoctype();
case 'filter':
return this.parseFilter();
case 'comment':
return this.parseComment();
case 'text':
return this.parseText();
case 'each':
return this.parseEach();
case 'code':
return this.parseCode();
case 'id':
case 'class':
var tok = this.advance;
this.lexer.defer(this.lexer.tok('tag', 'div'));
this.lexer.defer(tok);
return this.parseExpr();
}
},
/**
* Text
*/
parseText: function(){
var tok = this.expect('text'),
node = new nodes.Text(tok.val);
node.line = this.line;
return node;
},
/**
* code
*/
parseCode: function(){
var tok = this.expect('code'),
node = new nodes.Code(tok.val, tok.buffer, tok.escape);
node.line = this.line;
if ('indent' == this.peek.type) {
node.block = this.parseBlock();
}
return node;
},
/**
* comment
*/
parseComment: function(){
var tok = this.expect('comment'),
node = new nodes.Comment(tok.val, tok.buffer);
node.line = this.line;
return node;
},
/**
* doctype
*/
parseDoctype: function(){
var tok = this.expect('doctype'),
node = new nodes.Doctype(tok.val);
node.line = this.line;
return node;
},
/**
* filter attrs? (text | block)
*/
parseFilter: function(){
var block,
tok = this.expect('filter'),
attrs = this.accept('attrs');
if (this.lookahead(2).type == 'text') {
block = this.parseTextBlock();
} else {
block = this.parseBlock();
}
var node = new nodes.Filter(tok.val, block, attrs && attrs.attrs);
node.line = this.line;
return node;
},
/**
* each block
*/
parseEach: function(){
var tok = this.expect('each'),
node = new nodes.Each(tok.code, tok.val, tok.key, this.parseBlock());
node.line = this.line;
return node;
},
/**
* indent (text | newline)* outdent
*/
parseTextBlock: function(){
var text = new nodes.Text;
text.line = this.line;
this.expect('indent');
while (this.peek.type === 'text' || this.peek.type === 'newline') {
if (this.peek.type === 'newline') {
this.advance;
} else {
text.push(this.advance.val);
case 'id':
case 'class':
var tok = this.advance;
tag.setAttribute(tok.type, "'" + tok.val + "'");
continue;
case 'attrs':
var obj = this.advance.attrs
, names = Object.keys(obj);
for (var i = 0, len = names.length; i < len; ++i) {
var name = names[i]
, val = obj[name];
tag.setAttribute(name, val);
}
continue;
default:
break out;
}
this.expect('outdent');
return text;
},
/**
* indent expr* outdent
*/
parseBlock: function(){
var block = new nodes.Block;
block.line = this.line;
this.expect('indent');
while (this.peek.type !== 'outdent') {
if (this.peek.type === 'newline') {
this.advance;
} else {
block.push(this.parseExpr());
}
}
this.expect('outdent');
return block;
},
/**
* tag (attrs | class | id)* (text | code)? newline* block?
*/
parseTag: function(){
var name = this.advance.val,
tag = new nodes.Tag(name);
tag.line = this.line;
}
// (attrs | class | id)*
out:
while (true) {
switch (this.peek.type) {
case 'id':
case 'class':
var tok = this.advance;
tag.setAttribute(tok.type, "'" + tok.val + "'");
continue;
case 'attrs':
var obj = this.advance.attrs,
names = Object.keys(obj);
for (var i = 0, len = names.length; i < len; ++i) {
var name = names[i],
val = obj[name];
tag.setAttribute(name, val);
}
continue;
default:
break out;
}
}
// (text | code)?
switch (this.peek.type) {
case 'text':
tag.text = this.parseText();
break;
case 'code':
tag.code = this.parseCode();
break;
}
// (text | code)?
switch (this.peek.type) {
case 'text':
tag.text = this.parseText();
break;
case 'code':
tag.code = this.parseCode();
break;
}
// newline*
while (this.peek.type === 'newline') this.advance;
// newline*
while (this.peek.type === 'newline') this.advance;
// Assume newline when tag followed by text
if (this.peek.type === 'text') {
if (tag.text) tag.text.push('\\n');
else tag.text = new nodes.Text('\\n');
}
// Assume newline when tag followed by text
if (this.peek.type === 'text') {
if (tag.text) tag.text.push('\\n');
else tag.text = new nodes.Text('\\n');
}
tag.textOnly = ~textOnly.indexOf(name);
// block?
if (this.peek.type === 'indent') {
var block = this.parseBlock();
if (tag.block) {
for (var i = 0, len = block.length; i < len; ++i) {
tag.block.push(block[i]);
}
} else {
tag.block = block;
}
// block?
if ('indent' == this.peek.type) {
if (tag.textOnly) {
this.lexer.textPipe = false;
tag.block = this.parseTextBlock();
this.lexer.textPipe = true;
} else {
var block = this.parseBlock();
if (tag.block) {
for (var i = 0, len = block.length; i < len; ++i) {
tag.block.push(block[i]);
}
} else {
tag.block = block;
}
return tag;
}
}
return tag;
}
};

@@ -17,10 +17,10 @@

var interpolate = exports.interpolate = function(str){
return str.replace(/(\\)?([#$!]){(.*?)}/g, function(str, escape, flag, code){
return escape
? str
: "' + "
+ ('!' == flag ? '' : 'escape')
+ "(" + code.replace(/\\'/g, "'")
+ ") + '";
});
return str.replace(/(\\)?([#$!]){(.*?)}/g, function(str, escape, flag, code){
return escape
? str
: "' + "
+ ('!' == flag ? '' : 'escape')
+ "(" + code.replace(/\\'/g, "'")
+ ") + '";
});
};

@@ -37,3 +37,3 @@

var escape = exports.escape = function(str) {
return str.replace(/'/g, "\\'");
return str.replace(/'/g, "\\'");
};

@@ -49,4 +49,4 @@

exports.text = function(str, newline){
return interpolate(escape(str + (newline ? '\\n' : '')));
exports.text = function(str){
return interpolate(escape(str));
};
{
"name": "jade",
"description": "Jade template engine",
"version": "0.6.3",
"version": "0.7.0",
"author": "TJ Holowaychuk <tj@vision-media.ca>",

@@ -6,0 +6,0 @@ "main": "./index.js",

@@ -11,2 +11,3 @@

- great readability
- flexible indentation
- code is escaped by default for security

@@ -21,2 +22,3 @@ - contextual error reporting at compile &amp; run time

- transparent iteration over objects, arrays, and even non-enumerables via `- each`
- block comments
- no tag prefix

@@ -37,3 +39,2 @@ - filters

- [php](http://github.com/everzet/jade.php)
- [client-side js](http://github.com/miksago/jade)
- [scala](http://scalate.fusesource.org/versions/snapshot/documentation/scaml-reference.html)

@@ -61,2 +62,6 @@ - [ruby](http://github.com/stonean/slim)

// Compile a function
var fn = jade.compile('string of jade', options);
fn.call(scope, locals);
### Options

@@ -77,7 +82,2 @@

### Indentation
Jade is indentation based, however currently only supports a _2 space_ indent.
Tabs are converted to 2 spaces before they hit the lexer.
### Tags

@@ -167,2 +167,28 @@

Tags that accept _only_ text such as `script`, `style`, and `textarea` do not
need the leading `|` character, for example:
html
head
title Example
script
if (foo) {
bar();
} else {
baz();
}
Alternatively if you decide no inline tags are needed, you may use the `:text` filter:
html
head
title Example
p
:text
You can place any text,
that you like here
even if it is indented really
strange
.
### Comments

@@ -194,2 +220,36 @@

### Block Comments
A block comment is the `/` character followed by a block.
body
/
#content
h1 Example
outputting
<body>
<!--
<div id="content">
<h1>Example</h1>
</div>
-->
</body>
Jade supports conditional-comments as well, for example:
body
/if IE
a(href='http://www.mozilla.com/en-US/firefox/') Get Firefox
outputs:
<body>
<!--[if IE]>
<a href="http://www.mozilla.com/en-US/firefox/">Get Firefox</a>
<![endif]-->
</body>
### Nesting

@@ -369,2 +429,11 @@

When a property is undefined, Jade will output an empty string. For example:
textarea= user.signature
when undefined would normally output "undefined" in your html, however recent
versions of Jade will simply render:
<textarea></textarea>
## bin/jade

@@ -371,0 +440,0 @@

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

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