d2-ui
Version:
2,343 lines (1,941 loc) • 75 kB
JavaScript
(function() {
// CommonJS require()
function require(p){
var path = require.resolve(p)
, mod = require.modules[path];
if (!mod) throw new Error('failed to require "' + p + '"');
if (!mod.exports) {
mod.exports = {};
mod.call(mod.exports, mod, mod.exports, require.relative(path));
}
return mod.exports;
}
require.modules = {};
require.resolve = function (path){
var orig = path
, reg = path + '.js'
, index = path + '/index.js';
return require.modules[reg] && reg
|| require.modules[index] && index
|| orig;
};
require.register = function (path, fn){
require.modules[path] = fn;
};
require.relative = function (parent) {
return function(p){
if ('.' != p.charAt(0)) return require(p);
var path = parent.split('/')
, segs = p.split('/');
path.pop();
for (var i = 0; i < segs.length; i++) {
var seg = segs[i];
if ('..' == seg) path.pop();
else if ('.' != seg) path.push(seg);
}
return require(path.join('/'));
};
};
require.register("compiler.js", function(module, exports, require){
/*!
* Jade - Compiler
* Copyright(c) 2010 TJ Holowaychuk <tj@vision-media.ca>
* MIT Licensed
*/
/**
* Module dependencies.
*/
var nodes = require('./nodes')
, filters = require('./filters')
, doctypes = require('./doctypes')
, selfClosing = require('./self-closing')
, runtime = require('./runtime')
, utils = require('./utils');
if (!Object.keys) {
Object.keys = function(obj){
var arr = [];
for (var key in obj) {
if (obj.hasOwnProperty(key)) {
arr.push(key);
}
}
return arr;
}
}
if (!String.prototype.trimLeft) {
String.prototype.trimLeft = function(){
return this.replace(/^\s+/, '');
}
}
/**
* Initialize `Compiler` with the given `node`.
*
* @param {Node} node
* @param {Object} options
* @api public
*/
var Compiler = module.exports = function Compiler(node, options) {
this.options = options = options || {};
this.node = node;
this.hasCompiledDoctype = false;
this.hasCompiledTag = false;
this.pp = options.pretty || false;
this.debug = false !== options.compileDebug;
this.indents = 0;
this.parentIndents = 0;
if (options.doctype) this.setDoctype(options.doctype);
};
/**
* Compiler prototype.
*/
Compiler.prototype = {
/**
* Compile parse tree to JavaScript.
*
* @api public
*/
compile: function(){
this.buf = ['var interp;'];
if (this.pp) this.buf.push("var __indent = [];");
this.lastBufferedIdx = -1;
this.visit(this.node);
return this.buf.join('\n');
},
/**
* Sets the default doctype `name`. 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 {string} name
* @api public
*/
setDoctype: function(name){
var doctype = doctypes[(name || 'default').toLowerCase()];
doctype = doctype || '<!DOCTYPE ' + name + '>';
this.doctype = doctype;
this.terse = '5' == name || 'html' == name;
this.xml = 0 == this.doctype.indexOf('<?xml');
},
/**
* Buffer the given `str` optionally escaped.
*
* @param {String} str
* @param {Boolean} esc
* @api public
*/
buffer: function(str, esc){
if (esc) str = utils.escape(str);
if (this.lastBufferedIdx == this.buf.length) {
this.lastBuffered += str;
this.buf[this.lastBufferedIdx - 1] = "buf.push('" + this.lastBuffered + "');"
} else {
this.buf.push("buf.push('" + str + "');");
this.lastBuffered = str;
this.lastBufferedIdx = this.buf.length;
}
},
/**
* Buffer an indent based on the current `indent`
* property and an additional `offset`.
*
* @param {Number} offset
* @param {Boolean} newline
* @api public
*/
prettyIndent: function(offset, newline){
offset = offset || 0;
newline = newline ? '\\n' : '';
this.buffer(newline + Array(this.indents + offset).join(' '));
if (this.parentIndents)
this.buf.push("buf.push.apply(buf, __indent);");
},
/**
* Visit `node`.
*
* @param {Node} node
* @api public
*/
visit: function(node){
var debug = this.debug;
if (debug) {
this.buf.push('__jade.unshift({ lineno: ' + node.line
+ ', filename: ' + (node.filename
? JSON.stringify(node.filename)
: '__jade[0].filename')
+ ' });');
}
// Massive hack to fix our context
// stack for - else[ if] etc
if (false === node.debug && this.debug) {
this.buf.pop();
this.buf.pop();
}
this.visitNode(node);
if (debug) this.buf.push('__jade.shift();');
},
/**
* Visit `node`.
*
* @param {Node} node
* @api public
*/
visitNode: function(node){
var name = node.constructor.name
|| node.constructor.toString().match(/function ([^(\s]+)()/)[1];
return this['visit' + name](node);
},
/**
* Visit case `node`.
*
* @param {Literal} node
* @api public
*/
visitCase: function(node){
var _ = this.withinCase;
this.withinCase = true;
this.buf.push('switch (' + node.expr + '){');
this.visit(node.block);
this.buf.push('}');
this.withinCase = _;
},
/**
* Visit when `node`.
*
* @param {Literal} node
* @api public
*/
visitWhen: function(node){
if ('default' == node.expr) {
this.buf.push('default:');
} else {
this.buf.push('case ' + node.expr + ':');
}
this.visit(node.block);
this.buf.push(' break;');
},
/**
* Visit literal `node`.
*
* @param {Literal} node
* @api public
*/
visitLiteral: function(node){
var str = node.str.replace(/\n/g, '\\\\n');
this.buffer(str);
},
/**
* Visit all nodes in `block`.
*
* @param {Block} block
* @api public
*/
visitBlock: function(block){
var len = block.nodes.length
, escape = this.escape
, pp = this.pp
// Block keyword has a special meaning in mixins
if (this.parentIndents && block.mode) {
if (pp) this.buf.push("__indent.push('" + Array(this.indents + 1).join(' ') + "');")
this.buf.push('block && block();');
if (pp) this.buf.push("__indent.pop();")
return;
}
// Pretty print multi-line text
if (pp && len > 1 && !escape && block.nodes[0].isText && block.nodes[1].isText)
this.prettyIndent(1, true);
for (var i = 0; i < len; ++i) {
// Pretty print text
if (pp && i > 0 && !escape && block.nodes[i].isText && block.nodes[i-1].isText)
this.prettyIndent(1, false);
this.visit(block.nodes[i]);
// Multiple text nodes are separated by newlines
if (block.nodes[i+1] && block.nodes[i].isText && block.nodes[i+1].isText)
this.buffer('\\n');
}
},
/**
* 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){
if (doctype && (doctype.val || !this.doctype)) {
this.setDoctype(doctype.val || 'default');
}
if (this.doctype) this.buffer(this.doctype);
this.hasCompiledDoctype = true;
},
/**
* Visit `mixin`, generating a function that
* may be called within the template.
*
* @param {Mixin} mixin
* @api public
*/
visitMixin: function(mixin){
var name = mixin.name.replace(/-/g, '_') + '_mixin'
, args = mixin.args || ''
, block = mixin.block
, attrs = mixin.attrs
, pp = this.pp;
if (mixin.call) {
if (pp) this.buf.push("__indent.push('" + Array(this.indents + 1).join(' ') + "');")
if (block || attrs.length) {
this.buf.push(name + '.call({');
if (block) {
this.buf.push('block: function(){');
// Render block with no indents, dynamically added when rendered
this.parentIndents++;
var _indents = this.indents;
this.indents = 0;
this.visit(mixin.block);
this.indents = _indents;
this.parentIndents--;
if (attrs.length) {
this.buf.push('},');
} else {
this.buf.push('}');
}
}
if (attrs.length) {
var val = this.attrs(attrs);
if (val.inherits) {
this.buf.push('attributes: merge({' + val.buf
+ '}, attributes), escaped: merge(' + val.escaped + ', escaped, true)');
} else {
this.buf.push('attributes: {' + val.buf + '}, escaped: ' + val.escaped);
}
}
if (args) {
this.buf.push('}, ' + args + ');');
} else {
this.buf.push('});');
}
} else {
this.buf.push(name + '(' + args + ');');
}
if (pp) this.buf.push("__indent.pop();")
} else {
this.buf.push('var ' + name + ' = function(' + args + '){');
this.buf.push('var block = this.block, attributes = this.attributes || {}, escaped = this.escaped || {};');
this.parentIndents++;
this.visit(block);
this.parentIndents--;
this.buf.push('};');
}
},
/**
* Visit `tag` buffering tag markup, generating
* attributes, visiting the `tag`'s code and block.
*
* @param {Tag} tag
* @api public
*/
visitTag: function(tag){
this.indents++;
var name = tag.name
, pp = this.pp;
if (tag.buffer) name = "' + (" + name + ") + '";
if (!this.hasCompiledTag) {
if (!this.hasCompiledDoctype && 'html' == name) {
this.visitDoctype();
}
this.hasCompiledTag = true;
}
// pretty print
if (pp && !tag.isInline())
this.prettyIndent(0, true);
if ((~selfClosing.indexOf(name) || tag.selfClosing) && !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);
this.escape = 'pre' == tag.name;
this.visit(tag.block);
// pretty print
if (pp && !tag.isInline() && 'pre' != tag.name && !tag.canInline())
this.prettyIndent(0, true);
this.buffer('</' + name + '>');
}
this.indents--;
},
/**
* Visit `filter`, throwing when the filter does not exist.
*
* @param {Filter} filter
* @api public
*/
visitFilter: function(filter){
var fn = filters[filter.name];
// unknown filter
if (!fn) {
if (filter.isASTFilter) {
throw new Error('unknown ast filter "' + filter.name + ':"');
} else {
throw new Error('unknown filter ":' + filter.name + '"');
}
}
if (filter.isASTFilter) {
this.buf.push(fn(filter.block, this, filter.attrs));
} else {
var text = filter.block.nodes.map(function(node){ return node.val }).join('\n');
filter.attrs = filter.attrs || {};
filter.attrs.filename = this.options.filename;
this.buffer(utils.text(fn(text, filter.attrs)));
}
},
/**
* Visit `text` node.
*
* @param {Text} text
* @api public
*/
visitText: function(text){
text = utils.text(text.val.replace(/\\/g, '\\\\'));
if (this.escape) text = escape(text);
this.buffer(text);
},
/**
* Visit a `comment`, only buffering when the buffer flag is set.
*
* @param {Comment} comment
* @api public
*/
visitComment: function(comment){
if (!comment.buffer) return;
if (this.pp) this.prettyIndent(1, true);
this.buffer('<!--' + utils.escape(comment.val) + '-->');
},
/**
* Visit a `BlockComment`.
*
* @param {Comment} comment
* @api public
*/
visitBlockComment: function(comment){
if (!comment.buffer) return;
if (0 == comment.val.trim().indexOf('if')) {
this.buffer('<!--[' + comment.val.trim() + ']>');
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();
this.buf.push('var __val__ = ' + val);
val = 'null == __val__ ? "" : __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');
this.visit(each.block);
this.buf.push(''
+ ' }\n'
+ ' } else {\n'
+ ' for (var ' + each.key + ' in ' + each.obj + ') {\n'
+ ' if (' + each.obj + '.hasOwnProperty(' + each.key + ')){'
+ ' var ' + each.val + ' = ' + each.obj + '[' + each.key + '];\n');
this.visit(each.block);
this.buf.push(' }\n');
this.buf.push(' }\n }\n}).call(this);\n');
},
/**
* Visit `attrs`.
*
* @param {Array} attrs
* @api public
*/
visitAttributes: function(attrs){
var val = this.attrs(attrs);
if (val.inherits) {
this.buf.push("buf.push(attrs(merge({ " + val.buf +
" }, attributes), merge(" + val.escaped + ", escaped, true)));");
} else if (val.constant) {
eval('var buf={' + val.buf + '};');
this.buffer(runtime.attrs(buf, JSON.parse(val.escaped)), true);
} else {
this.buf.push("buf.push(attrs({ " + val.buf + " }, " + val.escaped + "));");
}
},
/**
* Compile attributes.
*/
attrs: function(attrs){
var buf = []
, classes = []
, escaped = {}
, constant = attrs.every(function(attr){ return isConstant(attr.val) })
, inherits = false;
if (this.terse) buf.push('terse: true');
attrs.forEach(function(attr){
if (attr.name == 'attributes') return inherits = true;
escaped[attr.name] = attr.escaped;
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);
}
return {
buf: buf.join(', ').replace('class:', '"class":'),
escaped: JSON.stringify(escaped),
inherits: inherits,
constant: constant
};
}
};
/**
* Check if expression can be evaluated to a constant
*
* @param {String} expression
* @return {Boolean}
* @api private
*/
function isConstant(val){
// Check strings/literals
if (/^ *("([^"\\]*(\\.[^"\\]*)*)"|'([^'\\]*(\\.[^'\\]*)*)'|true|false|null|undefined) *$/i.test(val))
return true;
// Check numbers
if (!isNaN(Number(val)))
return true;
// Check arrays
var matches;
if (matches = /^ *\[(.*)\] *$/.exec(val))
return matches[1].split(',').every(isConstant);
return false;
}
/**
* Escape the given string of `html`.
*
* @param {String} html
* @return {String}
* @api private
*/
function escape(html){
return String(html)
.replace(/&(?!\w+;)/g, '&')
.replace(/</g, '<')
.replace(/>/g, '>')
.replace(/"/g, '"');
}
}); // module: compiler.js
require.register("doctypes.js", function(module, exports, require){
/*!
* Jade - doctypes
* Copyright(c) 2010 TJ Holowaychuk <tj@vision-media.ca>
* MIT Licensed
*/
module.exports = {
'5': '<!DOCTYPE html>'
, 'default': '<!DOCTYPE html>'
, 'xml': '<?xml version="1.0" encoding="utf-8" ?>'
, '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">'
};
}); // module: doctypes.js
require.register("filters.js", function(module, exports, require){
/*!
* Jade - filters
* Copyright(c) 2010 TJ Holowaychuk <tj@vision-media.ca>
* MIT Licensed
*/
module.exports = {
/**
* 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 type="text/css">' + sass + '</style>';
},
/**
* Transform stylus to css, wrapped in style tags.
*/
stylus: function(str, options){
var ret;
str = str.replace(/\\n/g, '\n');
var stylus = require('stylus');
stylus(str, options).render(function(err, css){
if (err) throw err;
ret = css.replace(/\n/g, '\\n');
});
return '<style type="text/css">' + ret + '</style>';
},
/**
* Transform less 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 type="text/css">' + css.replace(/\n/g, '\\n') + '</style>';
});
return ret;
},
/**
* Transform markdown to html.
*/
markdown: function(str){
var md;
// support markdown / discount
try {
md = require('markdown');
} catch (err){
try {
md = require('discount');
} catch (err) {
try {
md = require('markdown-js');
} catch (err) {
try {
md = require('marked');
} catch (err) {
throw new
Error('Cannot find markdown library, install markdown, discount, or marked.');
}
}
}
}
str = str.replace(/\\n/g, '\n');
return md.parse(str).replace(/\n/g, '\\n').replace(/'/g,''');
},
/**
* Transform coffeescript to javascript.
*/
coffeescript: function(str){
str = str.replace(/\\n/g, '\n');
var js = require('coffee-script').compile(str).replace(/\\/g, '\\\\').replace(/\n/g, '\\n');
return '<script type="text/javascript">\\n' + js + '</script>';
}
};
}); // module: filters.js
require.register("inline-tags.js", function(module, exports, require){
/*!
* Jade - inline tags
* Copyright(c) 2010 TJ Holowaychuk <tj@vision-media.ca>
* MIT Licensed
*/
module.exports = [
'a'
, 'abbr'
, 'acronym'
, 'b'
, 'br'
, 'code'
, 'em'
, 'font'
, 'i'
, 'img'
, 'ins'
, 'kbd'
, 'map'
, 'samp'
, 'small'
, 'span'
, 'strong'
, 'sub'
, 'sup'
];
}); // module: inline-tags.js
require.register("jade.js", function(module, exports, require){
/*!
* Jade
* Copyright(c) 2010 TJ Holowaychuk <tj@vision-media.ca>
* MIT Licensed
*/
/**
* Module dependencies.
*/
var Parser = require('./parser')
, Lexer = require('./lexer')
, Compiler = require('./compiler')
, runtime = require('./runtime')
/**
* Library version.
*/
exports.version = '0.26.1';
/**
* Expose self closing tags.
*/
exports.selfClosing = require('./self-closing');
/**
* Default supported doctypes.
*/
exports.doctypes = require('./doctypes');
/**
* Text filters.
*/
exports.filters = require('./filters');
/**
* Utilities.
*/
exports.utils = require('./utils');
/**
* Expose `Compiler`.
*/
exports.Compiler = Compiler;
/**
* Expose `Parser`.
*/
exports.Parser = Parser;
/**
* Expose `Lexer`.
*/
exports.Lexer = Lexer;
/**
* Nodes.
*/
exports.nodes = require('./nodes');
/**
* Jade runtime helpers.
*/
exports.runtime = runtime;
/**
* Template function cache.
*/
exports.cache = {};
/**
* Parse the given `str` of jade and return a function body.
*
* @param {String} str
* @param {Object} options
* @return {String}
* @api private
*/
function parse(str, options){
try {
// Parse
var parser = new Parser(str, options.filename, options);
// Compile
var compiler = new (options.compiler || Compiler)(parser.parse(), options)
, js = compiler.compile();
// Debug compiler
if (options.debug) {
console.error('\nCompiled Function:\n\n\033[90m%s\033[0m', js.replace(/^/gm, ' '));
}
return ''
+ 'var buf = [];\n'
+ (options.self
? 'var self = locals || {};\n' + js
: 'with (locals || {}) {\n' + js + '\n}\n')
+ 'return buf.join("");';
} catch (err) {
parser = parser.context();
runtime.rethrow(err, parser.filename, parser.lexer.lineno);
}
}
/**
* Compile a `Function` representation of the given jade `str`.
*
* Options:
*
* - `compileDebug` when `false` debugging code is stripped from the compiled template
* - `client` when `true` the helper functions `escape()` etc will reference `jade.escape()`
* for use with the Jade client-side runtime.js
*
* @param {String} str
* @param {Options} options
* @return {Function}
* @api public
*/
exports.compile = function(str, options){
var options = options || {}
, client = options.client
, filename = options.filename
? JSON.stringify(options.filename)
: 'undefined'
, fn;
if (options.compileDebug !== false) {
fn = [
'var __jade = [{ lineno: 1, filename: ' + filename + ' }];'
, 'try {'
, parse(String(str), options)
, '} catch (err) {'
, ' rethrow(err, __jade[0].filename, __jade[0].lineno);'
, '}'
].join('\n');
} else {
fn = parse(String(str), options);
}
if (client) {
fn = 'attrs = attrs || jade.attrs; escape = escape || jade.escape; rethrow = rethrow || jade.rethrow; merge = merge || jade.merge;\n' + fn;
}
fn = new Function('locals, attrs, escape, rethrow, merge', fn);
if (client) return fn;
return function(locals){
return fn(locals, runtime.attrs, runtime.escape, runtime.rethrow, runtime.merge);
};
};
/**
* Render the given `str` of jade and invoke
* the callback `fn(err, str)`.
*
* Options:
*
* - `cache` enable template caching
* - `filename` filename required for `include` / `extends` and caching
*
* @param {String} str
* @param {Object|Function} options or fn
* @param {Function} fn
* @api public
*/
exports.render = function(str, options, fn){
// swap args
if ('function' == typeof options) {
fn = options, options = {};
}
// cache requires .filename
if (options.cache && !options.filename) {
return fn(new Error('the "filename" option is required for caching'));
}
try {
var path = options.filename;
var tmpl = options.cache
? exports.cache[path] || (exports.cache[path] = exports.compile(str, options))
: exports.compile(str, options);
fn(null, tmpl(options));
} catch (err) {
fn(err);
}
};
/**
* Render a Jade file at the given `path` and callback `fn(err, str)`.
*
* @param {String} path
* @param {Object|Function} options or callback
* @param {Function} fn
* @api public
*/
exports.renderFile = function(path, options, fn){
var key = path + ':string';
if ('function' == typeof options) {
fn = options, options = {};
}
try {
options.filename = path;
var str = options.cache
? exports.cache[key] || (exports.cache[key] = fs.readFileSync(path, 'utf8'))
: fs.readFileSync(path, 'utf8');
exports.render(str, options, fn);
} catch (err) {
fn(err);
}
};
/**
* Express support.
*/
exports.__express = exports.renderFile;
}); // module: jade.js
require.register("lexer.js", function(module, exports, require){
/*!
* Jade - Lexer
* Copyright(c) 2010 TJ Holowaychuk <tj@vision-media.ca>
* MIT Licensed
*/
/**
* Initialize `Lexer` with the given `str`.
*
* Options:
*
* - `colons` allow colons for attr delimiters
*
* @param {String} str
* @param {Object} options
* @api private
*/
var Lexer = module.exports = function Lexer(str, options) {
options = options || {};
this.input = str.replace(/\r\n|\r/g, '\n');
this.colons = options.colons;
this.deferredTokens = [];
this.lastIndents = 0;
this.lineno = 1;
this.stash = [];
this.indentStack = [];
this.indentRe = null;
this.pipeless = false;
};
/**
* Lexer prototype.
*/
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
}
},
/**
* 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.charAt(i)) {
++nstart;
} else if (end == str.charAt(i)) {
if (++nend == nstart) {
pos = i;
break;
}
}
}
return pos;
},
/**
* Stashed token.
*/
stashed: function() {
return this.stash.length
&& this.stash.shift();
},
/**
* Deferred token.
*/
deferred: function() {
return this.deferredTokens.length
&& this.deferredTokens.shift();
},
/**
* end-of-source.
*/
eos: function() {
if (this.input.length) return;
if (this.indentStack.length) {
this.indentStack.shift();
return this.tok('outdent');
} else {
return this.tok('eos');
}
},
/**
* Blank line.
*/
blank: function() {
var captures;
if (captures = /^\n *\n/.exec(this.input)) {
this.consume(captures[0].length - 1);
if (this.pipeless) return this.tok('text', '');
return this.next();
}
},
/**
* Comment.
*/
comment: function() {
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;
}
},
/**
* Interpolated tag.
*/
interpolation: function() {
var captures;
if (captures = /^#\{(.*?)\}/.exec(this.input)) {
this.consume(captures[0].length);
return this.tok('interpolation', captures[1]);
}
},
/**
* Tag.
*/
tag: function() {
var captures;
if (captures = /^(\w[-:\w]*)(\/?)/.exec(this.input)) {
this.consume(captures[0].length);
var tok, name = captures[1];
if (':' == name[name.length - 1]) {
name = name.slice(0, -1);
tok = this.tok('tag', name);
this.defer(this.tok(':'));
while (' ' == this.input[0]) this.input = this.input.substr(1);
} else {
tok = this.tok('tag', name);
}
tok.selfClosing = !! captures[2];
return tok;
}
},
/**
* Filter.
*/
filter: function() {
return this.scan(/^:(\w+)/, 'filter');
},
/**
* Doctype.
*/
doctype: function() {
return this.scan(/^(?:!!!|doctype) *([^\n]+)?/, 'doctype');
},
/**
* Id.
*/
id: function() {
return this.scan(/^#([\w-]+)/, 'id');
},
/**
* Class.
*/
className: function() {
return this.scan(/^\.([\w-]+)/, 'class');
},
/**
* Text.
*/
text: function() {
return this.scan(/^(?:\| ?| ?)?([^\n]+)/, 'text');
},
/**
* Extends.
*/
"extends": function() {
return this.scan(/^extends? +([^\n]+)/, 'extends');
},
/**
* Block prepend.
*/
prepend: function() {
var captures;
if (captures = /^prepend +([^\n]+)/.exec(this.input)) {
this.consume(captures[0].length);
var mode = 'prepend'
, name = captures[1]
, tok = this.tok('block', name);
tok.mode = mode;
return tok;
}
},
/**
* Block append.
*/
append: function() {
var captures;
if (captures = /^append +([^\n]+)/.exec(this.input)) {
this.consume(captures[0].length);
var mode = 'append'
, name = captures[1]
, tok = this.tok('block', name);
tok.mode = mode;
return tok;
}
},
/**
* Block.
*/
block: function() {
var captures;
if (captures = /^block\b *(?:(prepend|append) +)?([^\n]*)/.exec(this.input)) {
this.consume(captures[0].length);
var mode = captures[1] || 'replace'
, name = captures[2]
, tok = this.tok('block', name);
tok.mode = mode;
return tok;
}
},
/**
* Yield.
*/
yield: function() {
return this.scan(/^yield */, 'yield');
},
/**
* Include.
*/
include: function() {
return this.scan(/^include +([^\n]+)/, 'include');
},
/**
* Case.
*/
"case": function() {
return this.scan(/^case +([^\n]+)/, 'case');
},
/**
* When.
*/
when: function() {
return this.scan(/^when +([^:\n]+)/, 'when');
},
/**
* Default.
*/
"default": function() {
return this.scan(/^default */, 'default');
},
/**
* Assignment.
*/
assignment: function() {
var captures;
if (captures = /^(\w+) += *([^;\n]+)( *;? *)/.exec(this.input)) {
this.consume(captures[0].length);
var name = captures[1]
, val = captures[2];
return this.tok('code', 'var ' + name + ' = (' + val + ');');
}
},
/**
* Call mixin.
*/
call: function(){
var captures;
if (captures = /^\+([-\w]+)/.exec(this.input)) {
this.consume(captures[0].length);
var tok = this.tok('call', captures[1]);
// Check for args (not attributes)
if (captures = /^ *\((.*?)\)/.exec(this.input)) {
if (!/^ *[-\w]+ *=/.test(captures[1])) {
this.consume(captures[0].length);
tok.args = captures[1];
}
}
return tok;
}
},
/**
* Mixin.
*/
mixin: function(){
var captures;
if (captures = /^mixin +([-\w]+)(?: *\((.*)\))?/.exec(this.input)) {
this.consume(captures[0].length);
var tok = this.tok('mixin', captures[1]);
tok.args = captures[2];
return tok;
}
},
/**
* Conditional.
*/
conditional: function() {
var captures;
if (captures = /^(if|unless|else if|else)\b([^\n]*)/.exec(this.input)) {
this.consume(captures[0].length);
var type = captures[1]
, js = captures[2];
switch (type) {
case 'if': js = 'if (' + js + ')'; break;
case 'unless': js = 'if (!(' + js + '))'; break;
case 'else if': js = 'else if (' + js + ')'; break;
case 'else': js = 'else'; break;
}
return this.tok('code', js);
}
},
/**
* While.
*/
"while": function() {
var captures;
if (captures = /^while +([^\n]+)/.exec(this.input)) {
this.consume(captures[0].length);
return this.tok('code', 'while (' + captures[1] + ')');
}
},
/**
* Each.
*/
each: function() {
var captures;
if (captures = /^(?:- *)?(?:each|for) +(\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.
*/
code: function() {
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.
*/
attrs: function() {
if ('(' == this.input.charAt(0)) {
var index = this.indexOfDelimiters('(', ')')
, str = this.input.substr(1, index-1)
, tok = this.tok('attrs')
, len = str.length
, colons = this.colons
, states = ['key']
, escapedAttr
, key = ''
, val = ''
, quote
, c
, p;
function state(){
return states[states.length - 1];
}
function interpolate(attr) {
return attr.replace(/#\{([^}]+)\}/g, function(_, expr){
return quote + " + (" + expr + ") + " + quote;
});
}
this.consume(index + 1);
tok.attrs = {};
tok.escaped = {};
function parse(c) {
var real = c;
// TODO: remove when people fix ":"
if (colons && ':' == c) c = '=';
switch (c) {
case ',':
case '\n':
switch (state()) {
case 'expr':
case 'array':
case 'string':
case 'object':
val += c;
break;
default:
states.push('key');
val = val.trim();
key = key.trim();
if ('' == key) return;
key = key.replace(/^['"]|['"]$/g, '').replace('!', '');
tok.escaped[key] = escapedAttr;
tok.attrs[key] = '' == val
? true
: interpolate(val);
key = val = '';
}
break;
case '=':
switch (state()) {
case 'key char':
key += real;
break;
case 'val':
case 'expr':
case 'array':
case 'string':
case 'object':
val += real;
break;
default:
escapedAttr = '!' != p;
states.push('val');
}
break;
case '(':
if ('val' == state()
|| 'expr' == state()) states.push('expr');
val += c;
break;
case ')':
if ('expr' == state()
|| 'val' == state()) states.pop();
val += c;
break;
case '{':
if ('val' == state()) states.push('object');
val += c;
break;
case '}':
if ('object' == state()) states.pop();
val += c;
break;
case '[':
if ('val' == state()) states.push('array');
val += c;
break;
case ']':
if ('array' == state()) states.pop();
val += c;
break;
case '"':
case "'":
switch (state()) {
case 'key':
states.push('key char');
break;
case 'key char':
states.pop();
break;
case 'string':
if (c == quote) states.pop();
val += c;
break;
default:
states.push('string');
val += c;
quote = c;
}
break;
case '':
break;
default:
switch (state()) {
case 'key':
case 'key char':
key += c;
break;
default:
val += c;
}
}
p = c;
}
for (var i = 0; i < len; ++i) {
parse(str.charAt(i));
}
parse(',');
if ('/' == this.input.charAt(0)) {
this.consume(1);
tok.selfClosing = true;
}
return tok;
}
},
/**
* Indent | Outdent | Newline.
*/
indent: function() {
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.tok('newline');
// 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', indents);
// newline
} else {
tok = this.tok('newline');
}
return tok;
}
},
/**
* Pipe-less text consumed only when
* pipeless is true;
*/
pipelessText: function() {
if (this.pipeless) {
if ('\n' == this.input[0]) return;
var i = this.input.indexOf('\n');
if (-1 == i) i = this.input.length;
var str = this.input.substr(0, i);
this.consume(str.length);
return this.tok('text', str);
}
},
/**
* ':'
*/
colon: function() {
return this.scan(/^: */, ':');
},
/**
* Return the next token object, or those
* previously stashed by lookahead.
*
* @return {Object}
* @api private
*/
advance: function(){
return this.stashed()
|| this.next();
},
/**
* Return the next token object.
*
* @return {Object}
* @api private
*/
next: function() {
return this.deferred()
|| this.blank()
|| this.eos()
|| this.pipelessText()
|| this.yield()
|| this.doctype()
|| this.interpolation()
|| this["case"]()
|| this.when()
|| this["default"]()
|| this["extends"]()
|| this.append()
|| this.prepend()
|| this.block()
|| this.include()
|| this.mixin()
|| this.call()
|| this.conditional()
|| this.each()
|| this["while"]()
|| this.assignment()
|| this.tag()
|| this.filter()
|| this.code()
|| this.id()
|| this.className()
|| this.attrs()
|| this.indent()
|| this.comment()
|| this.colon()
|| this.text();
}
};
}); // module: lexer.js
require.register("nodes/attrs.js", function(module, exports, require){
/*!
* Jade - nodes - Attrs
* Copyright(c) 2010 TJ Holowaychuk <tj@vision-media.ca>
* MIT Licensed
*/
/**
* Module dependencies.
*/
var Node = require('./node'),
Block = require('./block');
/**
* Initialize a `Attrs` node.
*
* @api public
*/
var Attrs = module.exports = function Attrs() {
this.attrs = [];
};
/**
* Inherit from `Node`.
*/
Attrs.prototype = new Node;
Attrs.prototype.constructor = Attrs;
/**
* Set attribute `name` to `val`, keep in mind these become
* part of a raw js object literal, so to quote a value you must
* '"quote me"', otherwise or example 'user.name' is literal JavaScript.
*
* @param {String} name
* @param {String} val
* @param {Boolean} escaped
* @return {Tag} for chaining
* @api public
*/
Attrs.prototype.setAttribute = function(name, val, escaped){
this.attrs.push({ name: name, val: val, escaped: escaped });
return this;
};
/**
* Remove attribute `name` when present.
*
* @param {String} name
* @api public
*/
Attrs.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];
}
}
};
/**
* Get attribute value by `name`.
*
* @param {String} name
* @return {String}
* @api public
*/
Attrs.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;
}
}
};
}); // module: nodes/attrs.js
require.register("nodes/block-comment.js", function(module, exports, require){
/*!
* Jade - nodes - BlockComment
* Copyright(c) 2010 TJ Holowaychuk <tj@vision-media.ca>
* MIT Licensed
*/
/**
* Module dependencies.
*/
var Node = require('./node');
/**
* Initialize a `BlockComment` with the given `block`.
*
* @param {String} val
* @param {Block} block
* @param {Boolean} buffer
* @api public
*/
var BlockComment = module.exports = function BlockComment(val, block, buffer) {
this.block = block;
this.val = val;
this.buffer = buffer;
};
/**
* Inherit from `Node`.
*/
BlockComment.prototype = new Node;
BlockComment.prototype.constructor = BlockComment;
}); // module: nodes/block-comment.js
require.register("nodes/block.js", function(module, exports, require){
/*!
* Jade - nodes - Block
* Copyright(c) 2010 TJ Holowaychuk <tj@vision-media.ca>
* MIT Licensed
*/
/**
* Module dependencies.
*/
var Node = require('./node');
/**
* Initialize a new `Block` with an optional `node`.
*
* @param {Node} node
* @api public
*/
var Block = module.exports = function Block(node){
this.nodes = [];
if (node) this.push(node);
};
/**
* Inherit from `Node`.
*/
Block.prototype = new Node;
Block.prototype.constructor = Block;
/**
* Block flag.
*/
Block.prototype.isBlock = true;
/**
* Replace the nodes in `other` with the nodes
* in `this` block.
*
* @param {Block} other
* @api private
*/
Block.prototype.replace = function(other){
other.nodes = this.nodes;
};
/**
* Pust the given `node`.
*
* @param {Node} node
* @return {Number}
* @api public
*/
Block.prototype.push = function(node){
return this.nodes.push(node);
};
/**
* Check if this block is empty.
*
* @return {Boolean}
* @api public
*/
Block.prototype.isEmpty = function(){
return 0 == this.nodes.length;
};
/**
* Unshift the given `node`.
*
* @param {Node} node
* @return {Number}
* @api public
*/
Block.prototype.unshift = function(node){
return this.nodes.unshift(node);
};
/**
* Return the "last" block, or the first `yield` node.
*
* @return {Block}
* @api private
*/
Block.prototype.includeBlock = function(){
var ret = this
, node;
for (var i = 0, len = this.nodes.length; i < len; ++i) {
node = this.nodes[i];
if (node.yield) return node;
else if (node.textOnly) continue;
else if (node.includeBlock) ret = node.includeBlock();
else if (node.block && !node.block.isEmpty()) ret = node.block.includeBlock();
}
return ret;
};
/**
* Return a clone of this block.
*
* @return {Block}
* @api private
*/
Block.prototype.clone = function(){
var clone = new Block;
for (var i = 0, len = this.nodes.length; i < len; ++i) {
clone.push(this.nodes[i].clone());
}
return clone;
};
}); // module: nodes/block.js
require.register("nodes/case.js", function(module, exports, require){
/*!
* Jade - nodes - Case
* Copyright(c) 2010 TJ Holowaychuk <tj@vision-media.ca>
* MIT Licensed
*/
/**
* Module dependencies.
*/
var Node = require('./node');
/**
* Initialize a new `Case` with `expr`.
*
* @param {String} expr
* @api public
*/
var Case = exports = module.exports = function Case(expr, block){
this.expr = expr;
this.block = block;
};
/**
* Inherit from `Node`.
*/
Case.prototype = new Node;
Case.prototype.constructor = Case;
var When = exports.When = function When(expr, block){
this.expr = expr;
this.block = block;
this.debug = false;
};
/**
* Inherit from `Node`.
*/
When.prototype = new Node;
When.prototype.constructor = When;
}); // module: nodes/case.js
require.register("nodes/code.js", function(module, exports, require){
/*!
* Jade - nodes - Code
* Copyright(c) 2010 TJ Holowaychuk <tj@vision-media.ca>
* MIT Licensed
*/
/**
* Module dependencies.
*/
var Node = require('./node');
/**
* Initialize a `Code` node with the given code `val`.
* Code may also be optionally buffered and escaped.
*
* @param {String} val
* @param {Boolean} buffer
* @param {Boolean} escape
* @api public
*/
var Code = module.exports = function Code(val, buffer, escape) {
this.val = val;
this.buffer = buffer;
this.escape = escape;
if (val.match(/^ *else/)) this.debug = false;
};
/**
* Inherit from `Node`.
*/
Code.prototype = new Node;
Code.prototype.constructor = Code;
}); // module: nodes/code.js
require.register("nodes/comment.js", function(module, exports, require){
/*!
* Jade - nodes - Comment
* Copyright(c) 2010 TJ Holowaychuk <tj@vision-media.ca>
* MIT Licensed
*/
/**
* Module dependencies.
*/
var Node = require('./node');
/**
* Initialize a `Comment` with the given `val`, optionally `buffer`,
* otherwise the comment may render in the output.
*
* @param {String} val
* @param {Boolean} buffer
* @api public
*/
var Comment = module.exports = function Comment(val, buffer) {
this.val = val;
this.buffer = buffer;
};
/**
* Inherit from `Node`.
*/
Comment.prototype = new Node;
Comment.prototype.constructor = Comment;
}); // module: nodes/comment.js
require.register("nodes/doctype.js", function(module, exports, require){
/*!
* Jade - nodes - Doctype
* Copyright(c) 2010 TJ Holowaychuk <tj@vision-media.ca>
* MIT Licensed
*/
/**
* Module dependencies.
*/
var Node = require('./node');
/**
* Initialize a `Doctype` with the given `val`.
*
* @param {String} val
* @api public
*/
var Doctype = module.exports = function Doctype(val) {
this.val = val;
};
/**
* Inherit from `Node`.
*/
Doctype.prototype = new Node;
Doctype.prototype.constructor = Doctype;
}); // module: nodes/doctype.js
require.register("nodes/each.js", function(module, exports, require){
/*!
* Jade - nodes - Each
* Copyright(c) 2010 TJ Holowaychuk <tj@vision-media.ca>
* MIT Licensed
*/
/**
* Module dependencies.
*/
var Node = require('./node');
/**
* Initialize an `Each` node, representing iteration
*
* @param {String} obj
* @param {String} val
* @param {String} key
* @param {Block} block
* @api public
*/
var Each = module.exports = function Each(obj, val, key, block) {
this.obj = obj;
this.val = val;
this.key = key;
this.block = block;
};
/**
* Inherit from `Node`.
*/
Each.prototype = new Node;
Each.prototype.constructor = Each;
}); // module: nodes/each.js
require.register("nodes/filter.js", function(module, exports, require){
/*!
* Jade - nodes - Filter
* Copyright(c) 2010 TJ Holowaychuk <tj@vision-media.ca>
* MIT Licensed
*/
/**
* Module dependencies.
*/
var Node = require('./node')
, Block = require('./block');
/**
* Initialize a `Filter` node with the given
* filter `name` and `block`.
*
* @param {String} name
* @param {Block|Node} block
* @api public
*/
var Filter = module.exports = function Filter(name, block, attrs) {
this.name = name;
this.block = block;
this.attrs = attrs;
this.isASTFilter = !block.nodes.every(function(node){ return node.isText });
};
/**
* Inherit from `Node`.
*/
Filter.prototype = new Node;
Filter.prototype.constructor = Filter;
}); // module: nodes/filter.j