diff --git a/jade.js b/jade.js index 39c1c252b..a13780018 100644 --- a/jade.js +++ b/jade.js @@ -1,833 +1,744 @@ -var jade = (function() { +(function(e){if("function"==typeof bootstrap)bootstrap("jade",e);else if("object"==typeof exports)module.exports=e();else if("function"==typeof define&&define.amd)define(e);else if("undefined"!=typeof ses){if(!ses.ok())return;ses.makeJade=e}else"undefined"!=typeof window?window.jade=e():global.jade=e()})(function(){var define,ses,bootstrap,module,exports; +return (function(e,t,n){function i(n,s){if(!t[n]){if(!e[n]){var o=typeof require=="function"&&require;if(!s&&o)return o(n,!0);if(r)return r(n,!0);throw new Error("Cannot find module '"+n+"'")}var u=t[n]={exports:{}};e[n][0].call(u.exports,function(t){var r=e[n][1][t];return i(r?r:t)},u,u.exports)}return t[n].exports}var r=typeof require=="function"&&require;for(var s=0;s + * MIT Licensed + */ -require.modules = {}; +/** + * Lame Array.isArray() polyfill for now. + */ -require.resolve = function (path){ - var orig = path - , reg = path + '.js' - , index = path + '/index.js'; - return require.modules[reg] && reg - || require.modules[index] && index - || orig; +if (!Array.isArray) { + Array.isArray = function(arr){ + return '[object Array]' == Object.prototype.toString.call(arr); }; +} -require.register = function (path, fn){ - require.modules[path] = fn; - }; +/** + * Lame Object.keys() polyfill for now. + */ -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); +if (!Object.keys) { + Object.keys = function(obj){ + var arr = []; + for (var key in obj) { + if (obj.hasOwnProperty(key)) { + arr.push(key); } + } + return arr; + } +} - return require(path.join('/')); - }; - }; +/** + * Merge two attribute objects giving precedence + * to values in object `b`. Classes are special-cased + * allowing for arrays and merging/joining appropriately + * resulting in a string. + * + * @param {Object} a + * @param {Object} b + * @return {Object} a + * @api private + */ +exports.merge = function merge(a, b) { + var ac = a['class']; + var bc = b['class']; -require.register("compiler.js", function(module, exports, require){ + if (ac || bc) { + ac = ac || []; + bc = bc || []; + if (!Array.isArray(ac)) ac = [ac]; + if (!Array.isArray(bc)) bc = [bc]; + ac = ac.filter(nulls); + bc = bc.filter(nulls); + a['class'] = ac.concat(bc).join(' '); + } -/*! - * Jade - Compiler - * Copyright(c) 2010 TJ Holowaychuk - * MIT Licensed - */ + for (var key in b) { + if (key != 'class') { + a[key] = b[key]; + } + } + + return a; +}; /** - * Module dependencies. + * Filter null `val`s. + * + * @param {Mixed} val + * @return {Mixed} + * @api private */ -var nodes = require('./nodes') - , filters = require('./filters') - , doctypes = require('./doctypes') - , selfClosing = require('./self-closing') - , runtime = require('./runtime') - , utils = require('./utils') - , parseJSExpression = require('character-parser').parseMax +function nulls(val) { + return val != null; +} + +/** + * Render the given attributes object. + * + * @param {Object} obj + * @param {Object} escaped + * @return {String} + * @api private + */ +exports.attrs = function attrs(obj, escaped){ + var buf = [] + , terse = obj.terse; - if (!Object.keys) { - Object.keys = function(obj){ - var arr = []; - for (var key in obj) { - if (obj.hasOwnProperty(key)) { - arr.push(key); - } - } - return arr; - } - } + delete obj.terse; + var keys = Object.keys(obj) + , len = keys.length; - if (!String.prototype.trimLeft) { - String.prototype.trimLeft = function(){ - return this.replace(/^\s+/, ''); - } - } + if (len) { + buf.push(''); + for (var i = 0; i < len; ++i) { + var key = keys[i] + , val = obj[key]; + if ('boolean' == typeof val || null == val) { + if (val) { + terse + ? buf.push(key) + : buf.push(key + '="' + key + '"'); + } + } else if (0 == key.indexOf('data') && 'string' != typeof val) { + buf.push(key + "='" + JSON.stringify(val) + "'"); + } else if ('class' == key && Array.isArray(val)) { + buf.push(key + '="' + exports.escape(val.join(' ')) + '"'); + } else if (escaped && escaped[key]) { + buf.push(key + '="' + exports.escape(val) + '"'); + } else { + buf.push(key + '="' + val + '"'); + } + } + } + return buf.join(' '); +}; /** - * Initialize `Compiler` with the given `node`. + * Escape the given string of `html`. * - * @param {Node} node - * @param {Object} options - * @api public + * @param {String} html + * @return {String} + * @api private */ -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); +exports.escape = function escape(html){ + return String(html) + .replace(/&/g, '&') + .replace(//g, '>') + .replace(/"/g, '"'); }; /** - * Compiler prototype. + * Re-throw the given `err` in context to the + * the jade in `filename` at the given `lineno`. + * + * @param {Error} err + * @param {String} filename + * @param {String} lineno + * @api private */ -Compiler.prototype = { +exports.rethrow = function rethrow(err, filename, lineno){ + if (!filename) throw err; + if (typeof window != 'undefined') throw err; - /** - * Compile parse tree to JavaScript. - * - * @api public - */ + var context = 3 + , str = require('fs').readFileSync(filename, 'utf8') + , lines = str.split('\n') + , start = Math.max(lineno - context, 0) + , end = Math.min(lines.length, lineno + context); - compile: function(){ - this.buf = []; - if (this.pp) this.buf.push("jade.indent = [];"); - this.lastBufferedIdx = -1; - this.visit(this.node); - return this.buf.join('\n'); - }, + // Error context + var context = lines.slice(start, end).map(function(line, i){ + var curr = i + start + 1; + return (curr == lineno ? ' > ' : ' ') + + curr + + '| ' + + line; + }).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 - */ + // Alter exception message + err.path = filename; + err.message = (filename || 'Jade') + ':' + lineno + + '\n' + context + '\n\n' + err.message; + throw err; +}; - setDoctype: function(name){ - name = (name && name.toLowerCase()) || 'default'; - this.doctype = doctypes[name] || ''; - this.terse = this.doctype.toLowerCase() == ''; - this.xml = 0 == this.doctype.indexOf(' + * MIT Licensed + */ - buffer: function (str, interpolate) { - var self = this; - if (interpolate) { - var match = /(\\)?([#!]){((?:.|\n)*)$/.exec(str); - if (match) { - this.buffer(str.substr(0, match.index), false); - if (match[1]) { // escape - this.buffer(match[2] + '{', false); - this.buffer(match[3], true); - return; - } else { - try { - var rest = match[3]; - var range = parseJSExpression(rest); - var code = ('!' == match[2] ? '' : 'jade.escape') + "((jade.interp = " + range.src + ") == null ? '' : jade.interp)"; - } catch (ex) { - throw ex; - //didn't match, just as if escaped - this.buffer(match[2] + '{', false); - this.buffer(match[3], true); - return; - } - this.bufferExpression(code); - this.buffer(rest.substr(range.end + 1), true); - return; - } - } - } +module.exports = [ + 'meta' + , 'img' + , 'link' + , 'input' + , 'source' + , 'area' + , 'base' + , 'col' + , 'br' + , 'hr' +]; +},{}],4:[function(require,module,exports){ - str = JSON.stringify(str); - str = str.substr(1, str.length - 2); +/*! + * Jade - doctypes + * Copyright(c) 2010 TJ Holowaychuk + * MIT Licensed + */ - if (this.lastBufferedIdx == this.buf.length) { - if (this.lastBufferedType === 'code') this.lastBuffered += ' + "'; - this.lastBufferedType = 'text'; - this.lastBuffered += str; - this.buf[this.lastBufferedIdx - 1] = 'buf.push(' + this.bufferStartChar + this.lastBuffered + '");' - } else { - this.buf.push('buf.push("' + str + '");'); - this.lastBufferedType = 'text'; - this.bufferStartChar = '"'; - this.lastBuffered = str; - this.lastBufferedIdx = this.buf.length; - } - }, +module.exports = { + '5': '' + , 'default': '' + , 'xml': '' + , 'transitional': '' + , 'strict': '' + , 'frameset': '' + , '1.1': '' + , 'basic': '' + , 'mobile': '' +}; +},{}],5:[function(require,module,exports){ - /** - * Buffer the given `src` so it is evaluated at run time - * - * @param {String} src - * @api public - */ +/*! + * Jade - utils + * Copyright(c) 2010 TJ Holowaychuk + * MIT Licensed + */ - bufferExpression: function (src) { - if (this.lastBufferedIdx == this.buf.length) { - if (this.lastBufferedType === 'text') this.lastBuffered += '"'; - this.lastBufferedType = 'code'; - this.lastBuffered += ' + (' + src + ')'; - this.buf[this.lastBufferedIdx - 1] = 'buf.push(' + this.bufferStartChar + this.lastBuffered + ');' - } else { - this.buf.push('buf.push(' + src + ');'); - this.lastBufferedType = 'code'; - this.bufferStartChar = ''; - this.lastBuffered = '(' + src + ')'; - this.lastBufferedIdx = this.buf.length; - } - }, +/** + * Merge `b` into `a`. + * + * @param {Object} a + * @param {Object} b + * @return {Object} + * @api public + */ - /** - * Buffer an indent based on the current `indent` - * property and an additional `offset`. - * - * @param {Number} offset - * @param {Boolean} newline - * @api public - */ +exports.merge = function(a, b) { + for (var key in b) a[key] = b[key]; + return a; +}; - 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, jade.indent);"); - }, - /** - * Visit `node`. - * - * @param {Node} node - * @api public - */ +},{}],6:[function(require,module,exports){ +/*! + * Jade + * Copyright(c) 2010 TJ Holowaychuk + * MIT Licensed + */ - visit: function(node){ - var debug = this.debug; +/** + * Module dependencies. + */ - if (debug) { - this.buf.push('jade.debug.unshift({ lineno: ' + node.line - + ', filename: ' + (node.filename - ? JSON.stringify(node.filename) - : 'jade.debug[0].filename') - + ' });'); - } +var Parser = require('./parser') + , Lexer = require('./lexer') + , Compiler = require('./compiler') + , runtime = require('./runtime') + , fs = require('fs'); - // Massive hack to fix our context - // stack for - else[ if] etc - if (false === node.debug && this.debug) { - this.buf.pop(); - this.buf.pop(); - } +/** + * Library version. + */ - this.visitNode(node); +exports.version = '0.30.0'; - if (debug) this.buf.push('jade.debug.shift();'); - }, +/** + * Expose self closing tags. + */ - /** - * Visit `node`. - * - * @param {Node} node - * @api public - */ +exports.selfClosing = require('./self-closing'); - visitNode: function(node){ - var name = node.constructor.name - || node.constructor.toString().match(/function ([^(\s]+)()/)[1]; - return this['visit' + name](node); - }, +/** + * Default supported doctypes. + */ - /** - * Visit case `node`. - * - * @param {Literal} node - * @api public - */ +exports.doctypes = require('./doctypes'); - visitCase: function(node){ - var _ = this.withinCase; - this.withinCase = true; - this.buf.push('switch (' + node.expr + '){'); - this.visit(node.block); - this.buf.push('}'); - this.withinCase = _; - }, +/** + * Text filters. + */ - /** - * Visit when `node`. - * - * @param {Literal} node - * @api public - */ +exports.filters = require('./filters'); - 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;'); - }, +/** + * Utilities. + */ - /** - * Visit literal `node`. - * - * @param {Literal} node - * @api public - */ +exports.utils = require('./utils'); - visitLiteral: function(node){ - this.buffer(node.str); - }, +/** + * Expose `Compiler`. + */ - /** - * Visit all nodes in `block`. - * - * @param {Block} block - * @api public - */ +exports.Compiler = Compiler; - visitBlock: function(block){ - var len = block.nodes.length - , escape = this.escape - , pp = this.pp +/** + * Expose `Parser`. + */ - // Block keyword has a special meaning in mixins - if (this.parentIndents && block.mode) { - if (pp) this.buf.push("jade.indent.push('" + Array(this.indents + 1).join(' ') + "');") - this.buf.push('block && block();'); - if (pp) this.buf.push("jade.indent.pop();") - return; - } +exports.Parser = Parser; - // Pretty print multi-line text - if (pp && len > 1 && !escape && block.nodes[0].isText && block.nodes[1].isText) - this.prettyIndent(1, true); +/** + * Expose `Lexer`. + */ - 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); +exports.Lexer = Lexer; - 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'); - } - }, +/** + * Nodes. + */ - /** - * 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 - */ +exports.nodes = require('./nodes'); - visitDoctype: function(doctype){ - if (doctype && (doctype.val || !this.doctype)) { - this.setDoctype(doctype.val || 'default'); - } +/** + * Jade runtime helpers. + */ - if (this.doctype) this.buffer(this.doctype); - this.hasCompiledDoctype = true; - }, +exports.runtime = runtime; - /** - * 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; +/** + * Template function cache. + */ - if (mixin.call) { - if (pp) this.buf.push("jade.indent.push('" + Array(this.indents + 1).join(' ') + "');") - if (block || attrs.length) { +exports.cache = {}; - this.buf.push(name + '.call({'); +/** + * Parse the given `str` of jade and return a function body. + * + * @param {String} str + * @param {Object} options + * @return {String} + * @api private + */ - if (block) { - this.buf.push('block: function(){'); +function parse(str, options){ + try { + // Parse + var parser = new Parser(str, options.filename, options); - // 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--; + // Compile + var compiler = new (options.compiler || Compiler)(parser.parse(), options) + , js = compiler.compile(); - if (attrs.length) { - this.buf.push('},'); - } else { - this.buf.push('}'); - } - } + // Debug compiler + if (options.debug) { + console.error('\nCompiled Function:\n\n\033[90m%s\033[0m', js.replace(/^/gm, ' ')); + } - if (attrs.length) { - var val = this.attrs(attrs); - if (val.inherits) { - this.buf.push('attributes: jade.merge({' + val.buf - + '}, attributes), escaped: jade.merge(' + val.escaped + ', escaped, true)'); - } else { - this.buf.push('attributes: {' + val.buf + '}, escaped: ' + val.escaped); - } - } + 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); + } +} - if (args) { - this.buf.push('}, ' + args + ');'); - } else { - this.buf.push('});'); - } +/** + * Strip any UTF-8 BOM off of the start of `str`, if it exists. + * + * @param {String} str + * @return {String} + * @api private + */ - } else { - this.buf.push(name + '(' + args + ');'); - } - if (pp) this.buf.push("jade.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('};'); - } - }, +function stripBOM(str){ + return 0xFEFF == str.charCodeAt(0) + ? str.substring(1) + : str; +} - /** - * Visit `tag` buffering tag markup, generating - * attributes, visiting the `tag`'s code and block. - * - * @param {Tag} tag - * @api public - */ +/** + * Compile a `Function` representation of the given jade `str`. + * + * Options: + * + * - `compileDebug` when `false` debugging code is stripped from the compiled template + * - `filename` used to improve errors when `compileDebug` is not `false` + * + * @param {String} str + * @param {Options} options + * @return {Function} + * @api public + */ - visitTag: function(tag){ - this.indents++; - var name = tag.name - , pp = this.pp - , self = this; +exports.compile = function(str, options){ + var options = options || {} + , filename = options.filename + ? JSON.stringify(options.filename) + : 'undefined' + , fn; - function bufferName() { - if (tag.buffer) self.bufferExpression(name); - else self.buffer(name); - } + str = stripBOM(String(str)); - if (!this.hasCompiledTag) { - if (!this.hasCompiledDoctype && 'html' == name) { - this.visitDoctype(); - } - this.hasCompiledTag = true; - } + if (options.compileDebug !== false) { + fn = [ + 'jade.debug = [{ lineno: 1, filename: ' + filename + ' }];' + , 'try {' + , parse(str, options) + , '} catch (err) {' + , ' jade.rethrow(err, jade.debug[0].filename, jade.debug[0].lineno);' + , '}' + ].join('\n'); + } else { + fn = parse(str, options); + } - // pretty print - if (pp && !tag.isInline()) - this.prettyIndent(0, true); + if (options.client) return new Function('locals', fn) + fn = new Function('locals, jade', fn) + return function(locals){ return fn(locals, Object.create(runtime)) } +}; - if ((~selfClosing.indexOf(name) || tag.selfClosing) && !this.xml) { - this.buffer('<'); - bufferName(); - this.visitAttributes(tag.attrs); - this.terse - ? this.buffer('>') - : this.buffer('/>'); - } else { - // Optimize attributes buffering - if (tag.attrs.length) { - this.buffer('<'); - bufferName(); - if (tag.attrs.length) this.visitAttributes(tag.attrs); - this.buffer('>'); - } else { - this.buffer('<'); - bufferName(); - this.buffer('>'); - } - if (tag.code) this.visitCode(tag.code); - this.escape = 'pre' == tag.name; - this.visit(tag.block); +/** + * 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 + */ - // pretty print - if (pp && !tag.isInline() && 'pre' != tag.name && !tag.canInline()) - this.prettyIndent(0, true); +exports.render = function(str, options, fn){ + // swap args + if ('function' == typeof options) { + fn = options, options = {}; + } - this.buffer(''); - } - this.indents--; - }, + // cache requires .filename + if (options.cache && !options.filename) { + return fn(new Error('the "filename" option is required for caching')); + } - /** - * Visit `filter`, throwing when the filter does not exist. - * - * @param {Filter} filter - * @api public - */ + 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); + } +}; - visitFilter: function(filter){ - 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(filters(filter.name, text, filter.attrs), true); - }, +/** + * 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 + */ - /** - * Visit `text` node. - * - * @param {Text} text - * @api public - */ +exports.renderFile = function(path, options, fn){ + var key = path + ':string'; - visitText: function(text){ - this.buffer(text.val, true); - }, + if ('function' == typeof options) { + fn = options, options = {}; + } - /** - * Visit a `comment`, only buffering when the buffer flag is set. - * - * @param {Comment} comment - * @api public - */ + 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); + } +}; - visitComment: function(comment){ - if (!comment.buffer) return; - if (this.pp) this.prettyIndent(1, true); - this.buffer(''); - }, +/** + * Express support. + */ - /** - * Visit a `BlockComment`. - * - * @param {Comment} comment - * @api public - */ +exports.__express = exports.renderFile; - visitBlockComment: function(comment){ - if (!comment.buffer) return; - if (0 == comment.val.trim().indexOf('if')) { - this.buffer(''); - } else { - this.buffer(''); - } - }, +},{"fs":1,"./parser":7,"./lexer":8,"./compiler":9,"./runtime":2,"./self-closing":3,"./doctypes":4,"./utils":5,"./filters":10,"./nodes":11}],10:[function(require,module,exports){ +/*! + * Jade - filters + * Copyright(c) 2010 TJ Holowaychuk + * MIT Licensed + */ - /** - * 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 - */ +module.exports = filter; +function filter(name, str, options) { + if (typeof filter[name] === 'function') { + var res = filter[name](str, options); + } else { + throw new Error('unknown filter ":' + name + '"'); + } + return res; +} +filter.exists = function (name, str, options) { + return typeof filter[name] === 'function'; +}; - visitCode: function(code){ - // Wrap code blocks with {}. - // we only wrap unbuffered code blocks ATM - // since they are usually flow control +},{}],12:[function(require,module,exports){ +// shim for using process in browser - // Buffer code - if (code.buffer) { - var val = code.val.trimLeft(); - val = 'null == (jade.interp = '+val+') ? "" : jade.interp'; - if (code.escape) val = 'jade.escape(' + val + ')'; - this.bufferExpression(val); - } else { - this.buf.push(code.val); +var process = module.exports = {}; + +process.nextTick = (function () { + var canSetImmediate = typeof window !== 'undefined' + && window.setImmediate; + var canPost = typeof window !== 'undefined' + && window.postMessage && window.addEventListener + ; + + if (canSetImmediate) { + return function (f) { return window.setImmediate(f) }; } - // Block support - if (code.block) { - if (!code.buffer) this.buf.push('{'); - this.visit(code.block); - if (!code.buffer) this.buf.push('}'); + if (canPost) { + var queue = []; + window.addEventListener('message', function (ev) { + if (ev.source === window && ev.data === 'process-tick') { + ev.stopPropagation(); + if (queue.length > 0) { + var fn = queue.shift(); + fn(); + } + } + }, true); + + return function nextTick(fn) { + queue.push(fn); + window.postMessage('process-tick', '*'); + }; } - }, - /** - * Visit `each` block. - * - * @param {Each} each - * @api public - */ + return function nextTick(fn) { + setTimeout(fn, 0); + }; +})(); - visitEach: function(each){ - this.buf.push('' - + '// iterate ' + each.obj + '\n' - + ';(function(){\n' - + ' var $$obj = ' + each.obj + ';\n' - + ' if (\'number\' == typeof $$obj.length) {\n'); +process.title = 'browser'; +process.browser = true; +process.env = {}; +process.argv = []; - if (each.alternative) { - this.buf.push(' if ($$obj.length) {'); - } +process.binding = function (name) { + throw new Error('process.binding is not supported'); +} - this.buf.push('' - + ' for (var ' + each.key + ' = 0, $$l = $$obj.length; ' + each.key + ' < $$l; ' + each.key + '++) {\n' - + ' var ' + each.val + ' = $$obj[' + each.key + '];\n'); +// TODO(shtylman) +process.cwd = function () { return '/' }; +process.chdir = function (dir) { + throw new Error('process.chdir is not supported'); +}; - this.visit(each.block); +},{}],13:[function(require,module,exports){ +(function(process){function filter (xs, fn) { + var res = []; + for (var i = 0; i < xs.length; i++) { + if (fn(xs[i], i, xs)) res.push(xs[i]); + } + return res; +} - this.buf.push(' }\n'); +// resolves . and .. elements in a path array with directory names there +// must be no slashes, empty elements, or device names (c:\) in the array +// (so also no leading and trailing slashes - it does not distinguish +// relative and absolute paths) +function normalizeArray(parts, allowAboveRoot) { + // if the path tries to go above the root, `up` ends up > 0 + var up = 0; + for (var i = parts.length; i >= 0; i--) { + var last = parts[i]; + if (last == '.') { + parts.splice(i, 1); + } else if (last === '..') { + parts.splice(i, 1); + up++; + } else if (up) { + parts.splice(i, 1); + up--; + } + } - if (each.alternative) { - this.buf.push(' } else {'); - this.visit(each.alternative); - this.buf.push(' }'); + // if the path is allowed to go above the root, restore leading ..s + if (allowAboveRoot) { + for (; up--; up) { + parts.unshift('..'); } + } - this.buf.push('' - + ' } else {\n' - + ' var $$l = 0;\n' - + ' for (var ' + each.key + ' in $$obj) {\n' - + ' $$l++;' - + ' if ($$obj.hasOwnProperty(' + each.key + ')){' - + ' var ' + each.val + ' = $$obj[' + each.key + '];\n'); + return parts; +} - this.visit(each.block); +// Regex to split a filename into [*, dir, basename, ext] +// posix version +var splitPathRe = /^(.+\/(?!$)|\/)?((?:.+?)?(\.[^.]*)?)$/; - this.buf.push(' }\n'); +// path.resolve([from ...], to) +// posix version +exports.resolve = function() { +var resolvedPath = '', + resolvedAbsolute = false; - this.buf.push(' }\n'); - if (each.alternative) { - this.buf.push(' if ($$l === 0) {'); - this.visit(each.alternative); - this.buf.push(' }'); - } - this.buf.push(' }\n}).call(this);\n'); - }, +for (var i = arguments.length; i >= -1 && !resolvedAbsolute; i--) { + var path = (i >= 0) + ? arguments[i] + : process.cwd(); - /** - * Visit `attrs`. - * - * @param {Array} attrs - * @api public - */ + // Skip empty and invalid entries + if (typeof path !== 'string' || !path) { + continue; + } - visitAttributes: function(attrs){ - var val = this.attrs(attrs); - if (val.inherits) { - this.bufferExpression("jade.attrs(jade.merge({ " + val.buf + - " }, attributes), jade.merge(" + val.escaped + ", escaped, true))"); - } else if (val.constant) { - eval('var buf={' + val.buf + '};'); - this.buffer(runtime.attrs(buf, JSON.parse(val.escaped))); - } else { - this.bufferExpression("jade.attrs({ " + val.buf + " }, " + val.escaped + ")"); - } - }, + resolvedPath = path + '/' + resolvedPath; + resolvedAbsolute = path.charAt(0) === '/'; +} - /** - * Compile attributes. - */ +// At this point the path should be resolved to a full absolute path, but +// handle relative paths to be safe (might happen when process.cwd() fails) - attrs: function(attrs){ - var buf = [] - , classes = [] - , escaped = {} - , constant = attrs.every(function(attr){ return isConstant(attr.val) }) - , inherits = false; +// Normalize the path +resolvedPath = normalizeArray(filter(resolvedPath.split('/'), function(p) { + return !!p; + }), !resolvedAbsolute).join('/'); - if (this.terse) buf.push('terse: true'); + return ((resolvedAbsolute ? '/' : '') + resolvedPath) || '.'; +}; - 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); - } - }); +// path.normalize(path) +// posix version +exports.normalize = function(path) { +var isAbsolute = path.charAt(0) === '/', + trailingSlash = path.slice(-1) === '/'; - if (classes.length) { - classes = classes.join(" + ' ' + "); - buf.push('"class": ' + classes); - } +// Normalize the path +path = normalizeArray(filter(path.split('/'), function(p) { + return !!p; + }), !isAbsolute).join('/'); - return { - buf: buf.join(', '), - escaped: JSON.stringify(escaped), - inherits: inherits, - constant: constant - }; + if (!path && !isAbsolute) { + path = '.'; + } + if (path && trailingSlash) { + path += '/'; } + + return (isAbsolute ? '/' : '') + path; }; -/** - * 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; +// posix version +exports.join = function() { + var paths = Array.prototype.slice.call(arguments, 0); + return exports.normalize(filter(paths, function(p, index) { + return p && typeof p === 'string'; + }).join('/')); +}; - // Check numbers - if (!isNaN(Number(val))) - return true; - // Check arrays - var matches; - if (matches = /^ *\[(.*)\] *$/.exec(val)) - return matches[1].split(',').every(isConstant); +exports.dirname = function(path) { + var dir = splitPathRe.exec(path)[1] || ''; + var isWindows = false; + if (!dir) { + // No dirname + return '.'; + } else if (dir.length === 1 || + (isWindows && dir.length <= 3 && dir.charAt(1) === ':')) { + // It is just a slash or a drive letter with a slash + return dir; + } else { + // It is a full dirname, strip trailing slash + return dir.substring(0, dir.length - 1); + } +}; - return false; -} -}); // module: compiler.js -require.register("doctypes.js", function(module, exports, require){ +exports.basename = function(path, ext) { + var f = splitPathRe.exec(path)[2] || ''; + // TODO: make this comparison case-insensitive on windows? + if (ext && f.substr(-1 * ext.length) === ext) { + f = f.substr(0, f.length - ext.length); + } + return f; +}; -/*! - * Jade - doctypes - * Copyright(c) 2010 TJ Holowaychuk - * MIT Licensed - */ -module.exports = { - '5': '' - , 'default': '' - , 'xml': '' - , 'transitional': '' - , 'strict': '' - , 'frameset': '' - , '1.1': '' - , 'basic': '' - , 'mobile': '' +exports.extname = function(path) { + return splitPathRe.exec(path)[3] || ''; }; -}); // module: doctypes.js -require.register("filters.js", function(module, exports, require){ -/*! - * Jade - filters - * Copyright(c) 2010 TJ Holowaychuk - * MIT Licensed - */ +exports.relative = function(from, to) { + from = exports.resolve(from).substr(1); + to = exports.resolve(to).substr(1); -var transformers = require('transformers'); + function trim(arr) { + var start = 0; + for (; start < arr.length; start++) { + if (arr[start] !== '') break; + } -module.exports = filter; -function filter(name, str, options) { - if (typeof filter[name] === 'function') { - var res = filter[name](str, options); - } else if (transformers[name]) { - var res = transformers[name].renderSync(str, options); - if (transformers[name].outputFormat === 'js') { - res = ''; - } else if (transformers[name].outputFormat === 'css') { - res = ''; - } else if (transformers[name].outputFormat === 'xml') { - res = res.replace(/'/g, '''); + var end = arr.length - 1; + for (; end >= 0; end--) { + if (arr[end] !== '') break; } - } else { - throw new Error('unknown filter ":' + name + '"'); - } - return res; -} -filter.exists = function (name, str, options) { - return typeof filter[name] === 'function' || transformers[name]; -}; -}); // module: filters.js + if (start > end) return []; + return arr.slice(start, end - start + 1); + } -require.register("inline-tags.js", function(module, exports, require){ + var fromParts = trim(from.split('/')); + var toParts = trim(to.split('/')); -/*! - * Jade - inline tags - * Copyright(c) 2010 TJ Holowaychuk - * MIT Licensed - */ + var length = Math.min(fromParts.length, toParts.length); + var samePartsLength = length; + for (var i = 0; i < length; i++) { + if (fromParts[i] !== toParts[i]) { + samePartsLength = i; + break; + } + } -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 + var outputParts = []; + for (var i = samePartsLength; i < fromParts.length; i++) { + outputParts.push('..'); + } + + outputParts = outputParts.concat(toParts.slice(samePartsLength)); + + return outputParts.join('/'); +}; -require.register("jade.js", function(module, exports, require){ +})(require("__browserify_process")) +},{"__browserify_process":12}],7:[function(require,module,exports){ /*! - * Jade + * Jade - Parser * Copyright(c) 2010 TJ Holowaychuk * MIT Licensed */ @@ -836,2736 +747,3110 @@ require.register("jade.js", function(module, exports, require){ * Module dependencies. */ -var Parser = require('./parser') - , Lexer = require('./lexer') - , Compiler = require('./compiler') - , runtime = require('./runtime') +var Lexer = require('./lexer') + , nodes = require('./nodes') + , utils = require('./utils') + , filters = require('./filters') + , path = require('path') + , extname = path.extname; /** - * Library version. + * Initialize `Parser` with the given input `str` and `filename`. + * + * @param {String} str + * @param {String} filename + * @param {Object} options + * @api public */ -exports.version = '0.30.0'; +var Parser = exports = module.exports = function Parser(str, filename, options){ + this.input = str; + this.lexer = new Lexer(str, options); + this.filename = filename; + this.blocks = []; + this.mixins = {}; + this.options = options; + this.contexts = [this]; +}; /** - * Expose self closing tags. + * Tags that may not contain tags. */ -exports.selfClosing = require('./self-closing'); +var textOnly = exports.textOnly = ['script', 'style']; /** - * Default supported doctypes. + * Parser prototype. */ -exports.doctypes = require('./doctypes'); +Parser.prototype = { -/** - * Text filters. - */ + /** + * Push `parser` onto the context stack, + * or pop and return a `Parser`. + */ -exports.filters = require('./filters'); + context: function(parser){ + if (parser) { + this.contexts.push(parser); + } else { + return this.contexts.pop(); + } + }, -/** - * Utilities. - */ + /** + * Return the next token object. + * + * @return {Object} + * @api private + */ -exports.utils = require('./utils'); + advance: function(){ + return this.lexer.advance(); + }, -/** - * Expose `Compiler`. - */ + /** + * Skip `n` tokens. + * + * @param {Number} n + * @api private + */ -exports.Compiler = Compiler; + skip: function(n){ + while (n--) this.advance(); + }, -/** - * Expose `Parser`. - */ + /** + * Single token lookahead. + * + * @return {Object} + * @api private + */ -exports.Parser = Parser; + peek: function() { + return this.lookahead(1); + }, -/** - * Expose `Lexer`. - */ + /** + * Return lexer lineno. + * + * @return {Number} + * @api private + */ -exports.Lexer = Lexer; + line: function() { + return this.lexer.lineno; + }, -/** - * Nodes. - */ + /** + * `n` token lookahead. + * + * @param {Number} n + * @return {Object} + * @api private + */ -exports.nodes = require('./nodes'); + lookahead: function(n){ + return this.lexer.lookahead(n); + }, -/** - * Jade runtime helpers. - */ + /** + * Parse input returning a string of js for evaluation. + * + * @return {String} + * @api public + */ -exports.runtime = runtime; + parse: function(){ + var block = new nodes.Block, parser; + block.line = this.line(); -/** - * Template function cache. - */ + while ('eos' != this.peek().type) { + if ('newline' == this.peek().type) { + this.advance(); + } else { + block.push(this.parseExpr()); + } + } -exports.cache = {}; + if (parser = this.extending) { + this.context(parser); + var ast = parser.parse(); + this.context(); -/** - * Parse the given `str` of jade and return a function body. - * - * @param {String} str - * @param {Object} options - * @return {String} - * @api private - */ + // hoist mixins + for (var name in this.mixins) + ast.unshift(this.mixins[name]); + return ast; + } else { + this.handleBlocks(); + } -function parse(str, options){ - try { - // Parse - var parser = new Parser(str, options.filename, options); + return block; + }, - // Compile - var compiler = new (options.compiler || Compiler)(parser.parse(), options) - , js = compiler.compile(); + /** + * Handle blocks, appends and prepends. Must be called from must deep parser (which parses template that does not extends another template). + * @api private + */ - // Debug compiler - if (options.debug) { - console.error('\nCompiled Function:\n\n\033[90m%s\033[0m', js.replace(/^/gm, ' ')); + handleBlocks: function() { + this.blocks.reverse(); + var blocksHash = {}; // blockName: block object + for (var i in this.blocks) { + if (!( ({}).hasOwnProperty.call(blocksHash, [this.blocks[i].name]) )) { // just safe call to blocksHash.hasOwnProperty + blocksHash[this.blocks[i].name] = this.blocks[i]; + } else { + switch (this.blocks[i].mode) { + case 'append': + blocksHash[this.blocks[i].name].nodes = blocksHash[this.blocks[i].name].nodes.concat(this.blocks[i].nodes); + break; + case 'prepend': + blocksHash[this.blocks[i].name].nodes = this.blocks[i].nodes.concat(blocksHash[this.blocks[i].name].nodes); + break; + default: + blocksHash[this.blocks[i].name].nodes = this.blocks[i].nodes; + } + this.blocks[i] = blocksHash[this.blocks[i].name]; + } } + }, - 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); - } -} - -/** - * Strip any UTF-8 BOM off of the start of `str`, if it exists. - * - * @param {String} str - * @return {String} - * @api private - */ + /** + * Expect the given type, or throw an exception. + * + * @param {String} type + * @api private + */ -function stripBOM(str){ - return 0xFEFF == str.charCodeAt(0) - ? str.substring(1) - : str; -} + expect: function(type){ + if (this.peek().type === type) { + return this.advance(); + } else { + throw new Error('expected "' + type + '", but got "' + this.peek().type + '"'); + } + }, -/** - * Compile a `Function` representation of the given jade `str`. - * - * Options: - * - * - `compileDebug` when `false` debugging code is stripped from the compiled template - * - `filename` used to improve errors when `compileDebug` is not `false` - * - * @param {String} str - * @param {Options} options - * @return {Function} - * @api public - */ + /** + * Accept the given `type`. + * + * @param {String} type + * @api private + */ -exports.compile = function(str, options){ - var options = options || {} - , filename = options.filename - ? JSON.stringify(options.filename) - : 'undefined' - , fn; + accept: function(type){ + if (this.peek().type === type) { + return this.advance(); + } + }, - str = stripBOM(String(str)); + /** + * tag + * | doctype + * | mixin + * | include + * | filter + * | comment + * | text + * | each + * | code + * | yield + * | id + * | class + * | interpolation + */ - if (options.compileDebug !== false) { - fn = [ - 'jade.debug = [{ lineno: 1, filename: ' + filename + ' }];' - , 'try {' - , parse(str, options) - , '} catch (err) {' - , ' jade.rethrow(err, jade.debug[0].filename, jade.debug[0].lineno);' - , '}' - ].join('\n'); - } else { - fn = parse(str, options); - } + parseExpr: function(){ + switch (this.peek().type) { + case 'tag': + return this.parseTag(); + case 'mixin': + return this.parseMixin(); + case 'block': + return this.parseBlock(); + case 'case': + return this.parseCase(); + case 'when': + return this.parseWhen(); + case 'default': + return this.parseDefault(); + case 'extends': + return this.parseExtends(); + case 'include': + return this.parseInclude(); + 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 'call': + return this.parseCall(); + case 'interpolation': + return this.parseInterpolation(); + case 'yield': + this.advance(); + var block = new nodes.Block; + block.yield = true; + return block; + 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 + '"'); + } + }, - if (options.client) return new Function('locals', fn) - fn = new Function('locals, jade', fn) - return function(locals){ return fn(locals, Object.create(runtime)) } -}; + /** + * Text + */ -/** - * 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 - */ + parseText: function(){ + var tok = this.expect('text'); + var node = new nodes.Text(tok.val); + node.line = this.line(); + return node; + }, -exports.render = function(str, options, fn){ - // swap args - if ('function' == typeof options) { - fn = options, options = {}; - } + /** + * ':' expr + * | block + */ - // cache requires .filename - if (options.cache && !options.filename) { - return fn(new Error('the "filename" option is required for caching')); - } + parseBlockExpansion: function(){ + if (':' == this.peek().type) { + this.advance(); + return new nodes.Block(this.parseExpr()); + } else { + return this.block(); + } + }, - 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); - } -}; + /** + * case + */ -/** - * 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 - */ + parseCase: function(){ + var val = this.expect('case').val; + var node = new nodes.Case(val); + node.line = this.line(); + node.block = this.block(); + return node; + }, -exports.renderFile = function(path, options, fn){ - var key = path + ':string'; + /** + * when + */ - if ('function' == typeof options) { - fn = options, options = {}; - } + parseWhen: function(){ + var val = this.expect('when').val + return new nodes.Case.When(val, this.parseBlockExpansion()); + }, - 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); - } -}; + /** + * default + */ -/** - * Express support. - */ + parseDefault: function(){ + this.expect('default'); + return new nodes.Case.When('default', this.parseBlockExpansion()); + }, -exports.__express = exports.renderFile; + /** + * code + */ -}); // module: jade.js + parseCode: function(){ + var tok = this.expect('code'); + var node = new nodes.Code(tok.val, tok.buffer, tok.escape); + var block; + var i = 1; + node.line = this.line(); + while (this.lookahead(i) && 'newline' == this.lookahead(i).type) ++i; + block = 'indent' == this.lookahead(i).type; + if (block) { + this.skip(i-1); + node.block = this.block(); + } + return node; + }, -require.register("lexer.js", function(module, exports, require){ -/*! - * Jade - Lexer - * Copyright(c) 2010 TJ Holowaychuk - * MIT Licensed - */ + /** + * comment + */ -var utils = require('./utils'); -var parseJSExpression = require('character-parser').parseMax; + parseComment: function(){ + var tok = this.expect('comment'); + var node; -/** - * 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; -}; + if ('indent' == this.peek().type) { + node = new nodes.BlockComment(tok.val, this.block(), tok.buffer); + } else { + node = new nodes.Comment(tok.val, tok.buffer); + } -/** - * Lexer prototype. - */ + node.line = this.line(); + return node; + }, -Lexer.prototype = { - /** - * Construct a token with the given `type` and `val`. - * - * @param {String} type - * @param {String} val - * @return {Object} - * @api private + * doctype */ - - tok: function(type, val){ - return { - type: type - , line: this.lineno - , val: val - } + + parseDoctype: function(){ + var tok = this.expect('doctype'); + var node = new nodes.Doctype(tok.val); + node.line = this.line(); + return node; }, - + /** - * Consume the given `len` of input. - * - * @param {Number} len - * @api private + * filter attrs? text-block */ - - consume: function(len){ - this.input = this.input.substr(len); + + parseFilter: function(){ + var tok = this.expect('filter'); + var attrs = this.accept('attrs'); + var block; + + this.lexer.pipeless = true; + block = this.parseTextBlock(); + this.lexer.pipeless = false; + + var node = new nodes.Filter(tok.val, block, attrs && attrs.attrs); + node.line = this.line(); + return node; }, - + /** - * Scan for `type` with the given `regexp`. - * - * @param {String} type - * @param {RegExp} regexp - * @return {Object} - * @api private + * each block */ - - scan: function(regexp, type){ - var captures; - if (captures = regexp.exec(this.input)) { - this.consume(captures[0].length); - return this.tok(type, captures[1]); + + parseEach: function(){ + var tok = this.expect('each'); + var node = new nodes.Each(tok.code, tok.val, tok.key); + node.line = this.line(); + node.block = this.block(); + if (this.peek().type == 'code' && this.peek().val == 'else') { + this.advance(); + node.alternative = this.block(); } + return node; }, - + /** - * Defer the given `tok`. + * Resolves a path relative to the template for use in + * includes and extends * - * @param {Object} tok + * @param {String} path + * @param {String} purpose Used in error messages. + * @return {String} * @api private */ - - defer: function(tok){ - this.deferredTokens.push(tok); + + resolvePath: function (path, purpose) { + var p = require('path'); + var dirname = p.dirname; + var basename = p.basename; + var join = p.join; + + if (path[0] !== '/' && !this.filename) + throw new Error('the "filename" option is required to use "' + purpose + '" with "relative" paths'); + + if (path[0] === '/' && !this.options.basedir) + throw new Error('the "basedir" option is required to use "' + purpose + '" with "absolute" paths'); + + path = join(path[0] === '/' ? this.options.basedir : dirname(this.filename), path); + + if (basename(path).indexOf('.') === -1) path += '.jade'; + + return path; }, - + /** - * Lookahead `n` tokens. - * - * @param {Number} n - * @return {Object} - * @api private + * 'extends' name */ - - lookahead: function(n){ - var fetch = n - this.stash.length; - while (fetch-- > 0) this.stash.push(this.next()); - return this.stash[--n]; + + parseExtends: function(){ + var fs = require('fs'); + + var path = this.resolvePath(this.expect('extends').val.trim(), 'extends'); + if ('.jade' != path.substr(-5)) path += '.jade'; + + var str = fs.readFileSync(path, 'utf8'); + var parser = new Parser(str, path, this.options); + + parser.blocks = this.blocks.reverse(); + parser.contexts = this.contexts; + this.extending = parser; + + // TODO: null node + return new nodes.Literal(''); }, - + /** - * Return the indexOf `(` or `{` or `[` / `)` or `}` or `]` delimiters. - * - * @return {Number} - * @api private + * 'block' name block */ - - bracketExpression: function(skip){ - skip = skip || 0; - var start = this.input[skip]; - if (start != '(' && start != '{' && start != '[') throw new Error('unrecognized start character'); - var end = ({'(': ')', '{': '}', '[': ']'})[start]; - var range = parseJSExpression(this.input, {start: skip + 1}); - if (this.input[range.end] !== end) throw new Error('start character ' + start + ' does not match end character ' + this.input[range.end]); - return range; + + parseBlock: function(){ + var block = this.expect('block'); + var mode = block.mode; + var name = block.val.trim(); + + block = 'indent' == this.peek().type + ? this.block() + : new nodes.Block(new nodes.Literal('')); + + block.mode = mode; + block.name = name; + this.blocks.push(block); + return block; }, - + /** - * Stashed token. + * include block? */ - - stashed: function() { - return this.stash.length - && this.stash.shift(); + + parseInclude: function(){ + var fs = require('fs'); + + var path = this.resolvePath(this.expect('include').val.trim(), 'include'); + + // non-jade + if ('.jade' != path.substr(-5)) { + var str = fs.readFileSync(path, 'utf8').replace(/\r/g, ''); + var ext = extname(path).slice(1); + if (filters.exists(ext)) str = filters(ext, str, { filename: path }); + return new nodes.Literal(str); + } + + var str = fs.readFileSync(path, 'utf8'); + var parser = new Parser(str, path, this.options); + parser.blocks = utils.merge([], this.blocks); + + parser.mixins = this.mixins; + + this.context(parser); + var ast = parser.parse(); + this.context(); + ast.filename = path; + + if ('indent' == this.peek().type) { + ast.includeBlock().push(this.block()); + } + + return ast; }, - + /** - * Deferred token. + * call ident block */ - - deferred: function() { - return this.deferredTokens.length - && this.deferredTokens.shift(); + + parseCall: function(){ + var tok = this.expect('call'); + var name = tok.val; + var args = tok.args; + var mixin = new nodes.Mixin(name, args, new nodes.Block, true); + + this.tag(mixin); + if (mixin.block.isEmpty()) mixin.block = null; + return mixin; }, - + /** - * end-of-source. + * mixin block */ - - 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); - ++this.lineno; - if (this.pipeless) return this.tok('text', ''); - return this.next(); - } - }, + parseMixin: function(){ + var tok = this.expect('mixin'); + var name = tok.val; + var args = tok.args; + var mixin; - /** - * 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; + // definition + if ('indent' == this.peek().type) { + mixin = new nodes.Mixin(name, args, this.block(), false); + this.mixins[name] = mixin; + return mixin; + // call + } else { + return new nodes.Mixin(name, args, null, true); } }, /** - * Interpolated tag. + * indent (text | newline)* outdent */ - interpolation: function() { - if (/^#\{/.test(this.input)) { - var match; - try { - match = this.bracketExpression(1); - } catch (ex) { - return;//not an interpolation expression, just an unmatched open interpolation + parseTextBlock: function(){ + var block = new nodes.Block; + block.line = this.line(); + var spaces = this.expect('indent').val; + if (null == this._spaces) this._spaces = spaces; + var indent = Array(spaces - this._spaces + 1).join(' '); + while ('outdent' != this.peek().type) { + switch (this.peek().type) { + case 'newline': + this.advance(); + break; + case 'indent': + this.parseTextBlock().nodes.forEach(function(node){ + block.push(node); + }); + break; + default: + var text = new nodes.Text(indent + this.advance().val); + text.line = this.line(); + block.push(text); } - - this.consume(match.end + 1); - return this.tok('interpolation', match.src); } + + if (spaces == this._spaces) this._spaces = null; + this.expect('outdent'); + return block; }, /** - * Tag. + * indent expr* outdent */ - - 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); + + block: 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 { - tok = this.tok('tag', name); + block.push(this.parseExpr()); } - tok.selfClosing = !! captures[2]; - return tok; } - }, - - /** - * Filter. - */ - - filter: function() { - return this.scan(/^:(\w+)/, 'filter'); - }, - - /** - * Doctype. - */ - - doctype: function() { - return this.scan(/^(?:!!!|doctype) *([^\n]+)?/, 'doctype'); + this.expect('outdent'); + return block; }, /** - * Id. - */ - - id: function() { - return this.scan(/^#([\w-]+)/, 'id'); - }, - - /** - * Class. - */ - - className: function() { - return this.scan(/^\.([\w-]+)/, 'class'); - }, - - /** - * Text. + * interpolation (attrs | class | id)* (text | code | ':')? newline* block? */ - - text: function() { - return this.scan(/^(?:\| ?| ?)?([^\n]+)/, 'text'); - }, - /** - * Extends. - */ - - "extends": function() { - return this.scan(/^extends? +([^\n]+)/, 'extends'); + parseInterpolation: function(){ + var tok = this.advance(); + var tag = new nodes.Tag(tok.val); + tag.buffer = true; + return this.tag(tag); }, /** - * 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. + * tag (attrs | class | id)* (text | code | ':')? newline* block? */ - - 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); + parseTag: function(){ + // ast-filter look-ahead + var i = 2; + if ('attrs' == this.lookahead(i).type) ++i; - tok.mode = mode; - return tok; - } - }, + var tok = this.advance(); + var tag = new nodes.Tag(tok.val); - /** - * Yield. - */ - - yield: function() { - return this.scan(/^yield */, 'yield'); - }, + tag.selfClosing = tok.selfClosing; - /** - * Include. - */ - - include: function() { - return this.scan(/^include +([^\n]+)/, 'include'); + return this.tag(tag); }, /** - * Case. + * Parse tag. */ - - "case": function() { - return this.scan(/^case +([^\n]+)/, 'case'); - }, - /** - * When. - */ - - when: function() { - return this.scan(/^when +([^:\n]+)/, 'when'); - }, + tag: function(tag){ + var dot; - /** - * Default. - */ - - "default": function() { - return this.scan(/^default */, 'default'); - }, + tag.line = this.line(); - /** - * 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 + ');'); + // (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 tok = this.advance() + , obj = tok.attrs + , escaped = tok.escaped + , names = Object.keys(obj); + + if (tok.selfClosing) tag.selfClosing = true; + + for (var i = 0, len = names.length; i < len; ++i) { + var name = names[i] + , val = obj[name]; + tag.setAttribute(name, val, escaped[name]); + } + continue; + default: + break out; + } + } + + // check immediate '.' + if ('.' == this.peek().val) { + dot = tag.textOnly = true; + this.advance(); } - }, - /** - * Call mixin. - */ - - call: function(){ - var captures; - if (captures = /^\+([-\w]+)/.exec(this.input)) { - this.consume(captures[0].length); - var tok = this.tok('call', captures[1]); + // (text | code | ':')? + switch (this.peek().type) { + case 'text': + tag.block.push(this.parseText()); + break; + case 'code': + tag.code = this.parseCode(); + break; + case ':': + this.advance(); + tag.block = new nodes.Block; + tag.block.push(this.parseExpr()); + break; + } - // Check for args (not attributes) - if (captures = /^ *\(/.exec(this.input)) { - try { - var range = this.bracketExpression(captures[0].length - 1); - if (!/^ *[-\w]+ *=/.test(range.src)) { // not attributes - this.consume(range.end + 1); - tok.args = range.src; + // newline* + while ('newline' == this.peek().type) this.advance(); + + tag.textOnly = tag.textOnly || ~textOnly.indexOf(tag.name); + + // script special-case + if ('script' == tag.name) { + var type = tag.getAttribute('type'); + if (!dot && type && 'text/javascript' != type.replace(/^['"]|['"]$/g, '')) { + tag.textOnly = false; + } + } + + // block? + if ('indent' == this.peek().type) { + if (tag.textOnly) { + this.lexer.pipeless = true; + tag.block = this.parseTextBlock(); + this.lexer.pipeless = false; + } else { + var block = this.block(); + if (tag.block) { + for (var i = 0, len = block.nodes.length; i < len; ++i) { + tag.block.push(block.nodes[i]); } - } catch (ex) { - //not a bracket expcetion, just unmatched open parens + } else { + tag.block = block; } } - - return tok; } - }, - /** - * Mixin. - */ + return tag; + } +}; - 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; - } - }, +},{"path":13,"fs":1,"./lexer":8,"./utils":5,"./filters":10,"./nodes":11}],11:[function(require,module,exports){ + +/*! + * Jade - nodes + * Copyright(c) 2010 TJ Holowaychuk + * MIT Licensed + */ + +exports.Node = require('./node'); +exports.Tag = require('./tag'); +exports.Code = require('./code'); +exports.Each = require('./each'); +exports.Case = require('./case'); +exports.Text = require('./text'); +exports.Block = require('./block'); +exports.Mixin = require('./mixin'); +exports.Filter = require('./filter'); +exports.Comment = require('./comment'); +exports.Literal = require('./literal'); +exports.BlockComment = require('./block-comment'); +exports.Doctype = require('./doctype'); + +},{"./node":14,"./tag":15,"./each":16,"./code":17,"./text":18,"./case":19,"./block":20,"./mixin":21,"./filter":22,"./comment":23,"./literal":24,"./block-comment":25,"./doctype":26}],14:[function(require,module,exports){ + +/*! + * Jade - nodes - Node + * Copyright(c) 2010 TJ Holowaychuk + * MIT Licensed + */ + +/** + * Initialize a `Node`. + * + * @api public + */ + +var Node = module.exports = function Node(){}; + +/** + * Clone this node (return itself) + * + * @return {Node} + * @api private + */ + +Node.prototype.clone = function(){ + return this; +}; + +},{}],9:[function(require,module,exports){ +(function(){/*! + * Jade - Compiler + * Copyright(c) 2010 TJ Holowaychuk + * MIT Licensed + */ + +/** + * Module dependencies. + */ + +var nodes = require('./nodes') + , filters = require('./filters') + , doctypes = require('./doctypes') + , selfClosing = require('./self-closing') + , runtime = require('./runtime') + , utils = require('./utils') + , parseJSExpression = require('character-parser').parseMax + + +/** + * 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 = { /** - * Conditional. + * Compile parse tree to JavaScript. + * + * @api public */ - - 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); - } + compile: function(){ + this.buf = []; + if (this.pp) this.buf.push("jade.indent = [];"); + this.lastBufferedIdx = -1; + this.visit(this.node); + return this.buf.join('\n'); }, /** - * While. + * 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 */ - - "while": function() { - var captures; - if (captures = /^while +([^\n]+)/.exec(this.input)) { - this.consume(captures[0].length); - return this.tok('code', 'while (' + captures[1] + ')'); - } + + setDoctype: function(name){ + name = name || 'default'; + this.doctype = doctypes[name.toLowerCase()] || ''; + this.terse = this.doctype.toLowerCase() == ''; + this.xml = 0 == this.doctype.indexOf(' 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 - * 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 - * 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 - * 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(); - if (ret.yield) return ret; - } - - 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 - * 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; + buffer: function (str, interpolate) { + var self = this; + if (interpolate) { + var match = /(\\)?([#!]){((?:.|\n)*)$/.exec(str); + if (match) { + this.buffer(str.substr(0, match.index), false); + if (match[1]) { // escape + this.buffer(match[2] + '{', false); + this.buffer(match[3], true); + return; + } else { + try { + var rest = match[3]; + var range = parseJSExpression(rest); + var code = ('!' == match[2] ? '' : 'jade.escape') + "((jade.interp = " + range.src + ") == null ? '' : jade.interp)"; + } catch (ex) { + throw ex; + //didn't match, just as if escaped + this.buffer(match[2] + '{', false); + this.buffer(match[3], true); + return; + } + this.bufferExpression(code); + this.buffer(rest.substr(range.end + 1), true); + return; + } + } + } + str = JSON.stringify(str); + str = str.substr(1, str.length - 2); + if (this.lastBufferedIdx == this.buf.length) { + if (this.lastBufferedType === 'code') this.lastBuffered += ' + "'; + this.lastBufferedType = 'text'; + this.lastBuffered += str; + this.buf[this.lastBufferedIdx - 1] = 'buf.push(' + this.bufferStartChar + this.lastBuffered + '");' + } else { + this.buf.push('buf.push("' + str + '");'); + this.lastBufferedType = 'text'; + this.bufferStartChar = '"'; + this.lastBuffered = str; + this.lastBufferedIdx = this.buf.length; + } + }, -}); // module: nodes/case.js + /** + * Buffer the given `src` so it is evaluated at run time + * + * @param {String} src + * @api public + */ -require.register("nodes/code.js", function(module, exports, require){ + bufferExpression: function (src) { + if (this.lastBufferedIdx == this.buf.length) { + if (this.lastBufferedType === 'text') this.lastBuffered += '"'; + this.lastBufferedType = 'code'; + this.lastBuffered += ' + (' + src + ')'; + this.buf[this.lastBufferedIdx - 1] = 'buf.push(' + this.bufferStartChar + this.lastBuffered + ');' + } else { + this.buf.push('buf.push(' + src + ');'); + this.lastBufferedType = 'code'; + this.bufferStartChar = ''; + this.lastBuffered = '(' + src + ')'; + this.lastBufferedIdx = this.buf.length; + } + }, -/*! - * Jade - nodes - Code - * Copyright(c) 2010 TJ Holowaychuk - * MIT Licensed - */ + /** + * Buffer an indent based on the current `indent` + * property and an additional `offset`. + * + * @param {Number} offset + * @param {Boolean} newline + * @api public + */ -/** - * Module dependencies. - */ + 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, jade.indent);"); + }, -var Node = require('./node'); + /** + * Visit `node`. + * + * @param {Node} node + * @api public + */ -/** - * 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 - */ + visit: function(node){ + var debug = this.debug; -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; -}; + if (debug) { + this.buf.push('jade.debug.unshift({ lineno: ' + node.line + + ', filename: ' + (node.filename + ? JSON.stringify(node.filename) + : 'jade.debug[0].filename') + + ' });'); + } -/** - * Inherit from `Node`. - */ + // Massive hack to fix our context + // stack for - else[ if] etc + if (false === node.debug && this.debug) { + this.buf.pop(); + this.buf.pop(); + } -Code.prototype = new Node; -Code.prototype.constructor = Code; + this.visitNode(node); -}); // module: nodes/code.js + if (debug) this.buf.push('jade.debug.shift();'); + }, -require.register("nodes/comment.js", function(module, exports, require){ + /** + * Visit `node`. + * + * @param {Node} node + * @api public + */ -/*! - * Jade - nodes - Comment - * Copyright(c) 2010 TJ Holowaychuk - * MIT Licensed - */ + visitNode: function(node){ + var name = node.constructor.name + || node.constructor.toString().match(/function ([^(\s]+)()/)[1]; + return this['visit' + name](node); + }, -/** - * Module dependencies. - */ + /** + * Visit case `node`. + * + * @param {Literal} node + * @api public + */ -var Node = require('./node'); + visitCase: function(node){ + var _ = this.withinCase; + this.withinCase = true; + this.buf.push('switch (' + node.expr + '){'); + this.visit(node.block); + this.buf.push('}'); + this.withinCase = _; + }, -/** - * 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 - */ + /** + * Visit when `node`. + * + * @param {Literal} node + * @api public + */ -var Comment = module.exports = function Comment(val, buffer) { - this.val = val; - this.buffer = buffer; -}; + 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;'); + }, -/** - * Inherit from `Node`. - */ + /** + * Visit literal `node`. + * + * @param {Literal} node + * @api public + */ -Comment.prototype = new Node; -Comment.prototype.constructor = Comment; + visitLiteral: function(node){ + this.buffer(node.str); + }, -}); // module: nodes/comment.js + /** + * Visit all nodes in `block`. + * + * @param {Block} block + * @api public + */ -require.register("nodes/doctype.js", function(module, exports, require){ + visitBlock: function(block){ + var len = block.nodes.length + , escape = this.escape + , pp = this.pp -/*! - * Jade - nodes - Doctype - * Copyright(c) 2010 TJ Holowaychuk - * MIT Licensed - */ + // Block keyword has a special meaning in mixins + if (this.parentIndents && block.mode) { + if (pp) this.buf.push("jade.indent.push('" + Array(this.indents + 1).join(' ') + "');") + this.buf.push('block && block();'); + if (pp) this.buf.push("jade.indent.pop();") + return; + } -/** - * Module dependencies. - */ + // Pretty print multi-line text + if (pp && len > 1 && !escape && block.nodes[0].isText && block.nodes[1].isText) + this.prettyIndent(1, true); -var Node = require('./node'); + 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); -/** - * Initialize a `Doctype` with the given `val`. - * - * @param {String} val - * @api public - */ + 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'); + } + }, -var Doctype = module.exports = function Doctype(val) { - this.val = val; -}; + /** + * 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 + */ -/** - * Inherit from `Node`. - */ + visitDoctype: function(doctype){ + if (doctype && (doctype.val || !this.doctype)) { + this.setDoctype(doctype.val || 'default'); + } -Doctype.prototype = new Node; -Doctype.prototype.constructor = Doctype; + if (this.doctype) this.buffer(this.doctype); + this.hasCompiledDoctype = true; + }, -}); // module: nodes/doctype.js + /** + * Visit `mixin`, generating a function that + * may be called within the template. + * + * @param {Mixin} mixin + * @api public + */ -require.register("nodes/each.js", function(module, exports, require){ + visitMixin: function(mixin){ + var name = mixin.name.replace(/-/g, '_') + '_mixin' + , args = mixin.args || '' + , block = mixin.block + , attrs = mixin.attrs + , pp = this.pp; -/*! - * Jade - nodes - Each - * Copyright(c) 2010 TJ Holowaychuk - * MIT Licensed - */ + if (mixin.call) { + if (pp) this.buf.push("jade.indent.push('" + Array(this.indents + 1).join(' ') + "');") + if (block || attrs.length) { -/** - * Module dependencies. - */ + this.buf.push(name + '.call({'); -var Node = require('./node'); + if (block) { + this.buf.push('block: function(){'); -/** - * Initialize an `Each` node, representing iteration - * - * @param {String} obj - * @param {String} val - * @param {String} key - * @param {Block} block - * @api public - */ + // 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--; -var Each = module.exports = function Each(obj, val, key, block) { - this.obj = obj; - this.val = val; - this.key = key; - this.block = block; -}; + if (attrs.length) { + this.buf.push('},'); + } else { + this.buf.push('}'); + } + } -/** - * Inherit from `Node`. - */ + if (attrs.length) { + var val = this.attrs(attrs); + if (val.inherits) { + this.buf.push('attributes: jade.merge({' + val.buf + + '}, attributes), escaped: jade.merge(' + val.escaped + ', escaped, true)'); + } else { + this.buf.push('attributes: {' + val.buf + '}, escaped: ' + val.escaped); + } + } -Each.prototype = new Node; -Each.prototype.constructor = Each; + if (args) { + this.buf.push('}, ' + args + ');'); + } else { + this.buf.push('});'); + } -}); // module: nodes/each.js + } else { + this.buf.push(name + '(' + args + ');'); + } + if (pp) this.buf.push("jade.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('};'); + } + }, -require.register("nodes/filter.js", function(module, exports, require){ + /** + * Visit `tag` buffering tag markup, generating + * attributes, visiting the `tag`'s code and block. + * + * @param {Tag} tag + * @api public + */ -/*! - * Jade - nodes - Filter - * Copyright(c) 2010 TJ Holowaychuk - * MIT Licensed - */ + visitTag: function(tag){ + this.indents++; + var name = tag.name + , pp = this.pp + , self = this; -/** - * Module dependencies. - */ + function bufferName() { + if (tag.buffer) self.bufferExpression(name); + else self.buffer(name); + } -var Node = require('./node') - , Block = require('./block'); + if (!this.hasCompiledTag) { + if (!this.hasCompiledDoctype && 'html' == name) { + this.visitDoctype(); + } + this.hasCompiledTag = true; + } -/** - * Initialize a `Filter` node with the given - * filter `name` and `block`. - * - * @param {String} name - * @param {Block|Node} block - * @api public - */ + // pretty print + if (pp && !tag.isInline()) + this.prettyIndent(0, true); -var Filter = module.exports = function Filter(name, block, attrs) { - this.name = name; - this.block = block; - this.attrs = attrs; -}; + if ((~selfClosing.indexOf(name) || tag.selfClosing) && !this.xml) { + this.buffer('<'); + bufferName(); + this.visitAttributes(tag.attrs); + this.terse + ? this.buffer('>') + : this.buffer('/>'); + } else { + // Optimize attributes buffering + if (tag.attrs.length) { + this.buffer('<'); + bufferName(); + if (tag.attrs.length) this.visitAttributes(tag.attrs); + this.buffer('>'); + } else { + this.buffer('<'); + bufferName(); + this.buffer('>'); + } + if (tag.code) this.visitCode(tag.code); + this.escape = 'pre' == tag.name; + this.visit(tag.block); -/** - * Inherit from `Node`. - */ + // pretty print + if (pp && !tag.isInline() && 'pre' != tag.name && !tag.canInline()) + this.prettyIndent(0, true); -Filter.prototype = new Node; -Filter.prototype.constructor = Filter; + this.buffer(''); + } + this.indents--; + }, -}); // module: nodes/filter.js + /** + * Visit `filter`, throwing when the filter does not exist. + * + * @param {Filter} filter + * @api public + */ -require.register("nodes/index.js", function(module, exports, require){ + visitFilter: function(filter){ + 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(filters(filter.name, text, filter.attrs), true); + }, -/*! - * Jade - nodes - * Copyright(c) 2010 TJ Holowaychuk - * MIT Licensed - */ + /** + * Visit `text` node. + * + * @param {Text} text + * @api public + */ -exports.Node = require('./node'); -exports.Tag = require('./tag'); -exports.Code = require('./code'); -exports.Each = require('./each'); -exports.Case = require('./case'); -exports.Text = require('./text'); -exports.Block = require('./block'); -exports.Mixin = require('./mixin'); -exports.Filter = require('./filter'); -exports.Comment = require('./comment'); -exports.Literal = require('./literal'); -exports.BlockComment = require('./block-comment'); -exports.Doctype = require('./doctype'); + visitText: function(text){ + this.buffer(text.val, true); + }, -}); // module: nodes/index.js + /** + * Visit a `comment`, only buffering when the buffer flag is set. + * + * @param {Comment} comment + * @api public + */ -require.register("nodes/literal.js", function(module, exports, require){ + visitComment: function(comment){ + if (!comment.buffer) return; + if (this.pp) this.prettyIndent(1, true); + this.buffer(''); + }, -/*! - * Jade - nodes - Literal - * Copyright(c) 2010 TJ Holowaychuk - * MIT Licensed - */ + /** + * Visit a `BlockComment`. + * + * @param {Comment} comment + * @api public + */ -/** - * Module dependencies. - */ + visitBlockComment: function(comment){ + if (!comment.buffer) return; + if (0 == comment.val.trim().indexOf('if')) { + this.buffer(''); + } else { + this.buffer(''); + } + }, -var Node = require('./node'); + /** + * 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 + */ -/** - * Initialize a `Literal` node with the given `str. - * - * @param {String} str - * @api public - */ + visitCode: function(code){ + // Wrap code blocks with {}. + // we only wrap unbuffered code blocks ATM + // since they are usually flow control -var Literal = module.exports = function Literal(str) { - this.str = str; -}; + // Buffer code + if (code.buffer) { + var val = code.val.trimLeft(); + val = 'null == (jade.interp = '+val+') ? "" : jade.interp'; + if (code.escape) val = 'jade.escape(' + val + ')'; + this.bufferExpression(val); + } else { + this.buf.push(code.val); + } -/** - * Inherit from `Node`. - */ + // Block support + if (code.block) { + if (!code.buffer) this.buf.push('{'); + this.visit(code.block); + if (!code.buffer) this.buf.push('}'); + } + }, -Literal.prototype = new Node; -Literal.prototype.constructor = Literal; + /** + * Visit `each` block. + * + * @param {Each} each + * @api public + */ + visitEach: function(each){ + this.buf.push('' + + '// iterate ' + each.obj + '\n' + + ';(function(){\n' + + ' var $$obj = ' + each.obj + ';\n' + + ' if (\'number\' == typeof $$obj.length) {\n'); -}); // module: nodes/literal.js + if (each.alternative) { + this.buf.push(' if ($$obj.length) {'); + } -require.register("nodes/mixin.js", function(module, exports, require){ + this.buf.push('' + + ' for (var ' + each.key + ' = 0, $$l = $$obj.length; ' + each.key + ' < $$l; ' + each.key + '++) {\n' + + ' var ' + each.val + ' = $$obj[' + each.key + '];\n'); -/*! - * Jade - nodes - Mixin - * Copyright(c) 2010 TJ Holowaychuk - * MIT Licensed - */ + this.visit(each.block); -/** - * Module dependencies. - */ + this.buf.push(' }\n'); -var Attrs = require('./attrs'); + if (each.alternative) { + this.buf.push(' } else {'); + this.visit(each.alternative); + this.buf.push(' }'); + } -/** - * Initialize a new `Mixin` with `name` and `block`. - * - * @param {String} name - * @param {String} args - * @param {Block} block - * @api public - */ + this.buf.push('' + + ' } else {\n' + + ' var $$l = 0;\n' + + ' for (var ' + each.key + ' in $$obj) {\n' + + ' $$l++;' + + ' var ' + each.val + ' = $$obj[' + each.key + '];\n'); -var Mixin = module.exports = function Mixin(name, args, block, call){ - this.name = name; - this.args = args; - this.block = block; - this.attrs = []; - this.call = call; -}; + this.visit(each.block); -/** - * Inherit from `Attrs`. - */ + this.buf.push(' }\n'); + if (each.alternative) { + this.buf.push(' if ($$l === 0) {'); + this.visit(each.alternative); + this.buf.push(' }'); + } + this.buf.push(' }\n}).call(this);\n'); + }, -Mixin.prototype = new Attrs; -Mixin.prototype.constructor = Mixin; + /** + * Visit `attrs`. + * + * @param {Array} attrs + * @api public + */ + visitAttributes: function(attrs){ + var val = this.attrs(attrs); + if (val.inherits) { + this.bufferExpression("jade.attrs(jade.merge({ " + val.buf + + " }, attributes), jade.merge(" + val.escaped + ", escaped, true))"); + } else if (val.constant) { + eval('var buf={' + val.buf + '};'); + this.buffer(runtime.attrs(buf, JSON.parse(val.escaped))); + } else { + this.bufferExpression("jade.attrs({ " + val.buf + " }, " + val.escaped + ")"); + } + }, + /** + * Compile attributes. + */ -}); // module: nodes/mixin.js + attrs: function(attrs){ + var buf = [] + , classes = [] + , escaped = {} + , constant = attrs.every(function(attr){ return isConstant(attr.val) }) + , inherits = false; -require.register("nodes/node.js", function(module, exports, require){ + if (this.terse) buf.push('terse: true'); -/*! - * Jade - nodes - Node - * Copyright(c) 2010 TJ Holowaychuk - * MIT Licensed - */ + 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); + } + }); -/** - * Initialize a `Node`. - * - * @api public - */ + if (classes.length) { + classes = classes.join(" + ' ' + "); + buf.push('"class": ' + classes); + } -var Node = module.exports = function Node(){}; + return { + buf: buf.join(', '), + escaped: JSON.stringify(escaped), + inherits: inherits, + constant: constant + }; + } +}; /** - * Clone this node (return itself) + * Check if expression can be evaluated to a constant * - * @return {Node} + * @param {String} expression + * @return {Boolean} * @api private */ -Node.prototype.clone = function(){ - return this; -}; +function isConstant(val){ + // Check strings/literals + if (/^ *("([^"\\]*(\\.[^"\\]*)*)"|'([^'\\]*(\\.[^'\\]*)*)'|true|false|null|undefined) *$/i.test(val)) + return true; -}); // module: nodes/node.js + // Check numbers + if (!isNaN(Number(val))) + return true; + + // Check arrays + var matches; + if (matches = /^ *\[(.*)\] *$/.exec(val)) + return matches[1].split(',').every(isConstant); -require.register("nodes/tag.js", function(module, exports, require){ + return false; +} +})() +},{"./doctypes":4,"./self-closing":3,"./runtime":2,"./filters":10,"./utils":5,"./nodes":11,"character-parser":27}],8:[function(require,module,exports){ /*! - * Jade - nodes - Tag + * Jade - Lexer * Copyright(c) 2010 TJ Holowaychuk * MIT Licensed */ -/** - * Module dependencies. - */ - -var Attrs = require('./attrs'), - Block = require('./block'), - inlineTags = require('../inline-tags'); +var utils = require('./utils'); +var parseJSExpression = require('character-parser').parseMax; /** - * Initialize a `Tag` node with the given tag `name` and optional `block`. + * Initialize `Lexer` with the given `str`. * - * @param {String} name - * @param {Block} block - * @api public - */ - -var Tag = module.exports = function Tag(name, block) { - this.name = name; - this.attrs = []; - this.block = block || new Block; -}; - -/** - * Inherit from `Attrs`. - */ - -Tag.prototype = new Attrs; -Tag.prototype.constructor = Tag; - - -/** - * Clone this tag. + * Options: * - * @return {Tag} - * @api private - */ - -Tag.prototype.clone = function(){ - var clone = new Tag(this.name, this.block.clone()); - clone.line = this.line; - clone.attrs = this.attrs; - clone.textOnly = this.textOnly; - return clone; -}; - -/** - * Check if this tag is an inline tag. + * - `colons` allow colons for attr delimiters * - * @return {Boolean} + * @param {String} str + * @param {Object} options * @api private */ -Tag.prototype.isInline = function(){ - return ~inlineTags.indexOf(this.name); +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; }; /** - * Check if this tag's contents can be inlined. Used for pretty printing. - * - * @return {Boolean} - * @api private + * Lexer prototype. */ -Tag.prototype.canInline = function(){ - var nodes = this.block.nodes; - - function isInline(node){ - // Recurse if the node is a block - if (node.isBlock) return node.nodes.every(isInline); - return node.isText || (node.isInline && node.isInline()); - } +Lexer.prototype = { - // Empty tag - if (!nodes.length) return true; + /** + * Construct a token with the given `type` and `val`. + * + * @param {String} type + * @param {String} val + * @return {Object} + * @api private + */ - // Text-only or inline-only tag - if (1 == nodes.length) return isInline(nodes[0]); + tok: function(type, val){ + return { + type: type + , line: this.lineno + , val: val + } + }, - // Multi-line inline-only tag - if (this.block.nodes.every(isInline)) { - for (var i = 1, len = nodes.length; i < len; ++i) { - if (nodes[i-1].isText && nodes[i].isText) - return false; + /** + * 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]); } - return true; - } + }, - // Mixed tag - return false; -}; -}); // module: nodes/tag.js - -require.register("nodes/text.js", function(module, exports, require){ + /** + * 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 `(` or `{` or `[` / `)` or `}` or `]` delimiters. + * + * @return {Number} + * @api private + */ + + bracketExpression: function(skip){ + skip = skip || 0; + var start = this.input[skip]; + if (start != '(' && start != '{' && start != '[') throw new Error('unrecognized start character'); + var end = ({'(': ')', '{': '}', '[': ']'})[start]; + var range = parseJSExpression(this.input, {start: skip + 1}); + if (this.input[range.end] !== end) throw new Error('start character ' + start + ' does not match end character ' + this.input[range.end]); + return range; + }, + + /** + * 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'); + } + }, -/*! - * Jade - nodes - Text - * Copyright(c) 2010 TJ Holowaychuk - * MIT Licensed - */ + /** + * Blank line. + */ + + blank: function() { + var captures; + if (captures = /^\n *\n/.exec(this.input)) { + this.consume(captures[0].length - 1); + ++this.lineno; + if (this.pipeless) return this.tok('text', ''); + return this.next(); + } + }, -/** - * Module dependencies. - */ + /** + * 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; + } + }, -var Node = require('./node'); + /** + * Interpolated tag. + */ -/** - * Initialize a `Text` node with optional `line`. - * - * @param {String} line - * @api public - */ + interpolation: function() { + if (/^#\{/.test(this.input)) { + var match; + try { + match = this.bracketExpression(1); + } catch (ex) { + return;//not an interpolation expression, just an unmatched open interpolation + } -var Text = module.exports = function Text(line) { - this.val = ''; - if ('string' == typeof line) this.val = line; -}; + this.consume(match.end + 1); + return this.tok('interpolation', match.src); + } + }, -/** - * Inherit from `Node`. - */ + /** + * 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'); + }, -Text.prototype = new Node; -Text.prototype.constructor = Text; + /** + * 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'); + }, -/** - * Flag as text. - */ + /** + * 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; + } + }, -Text.prototype.isText = true; -}); // module: nodes/text.js + /** + * 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); -require.register("parser.js", function(module, exports, require){ -/*! - * Jade - Parser - * Copyright(c) 2010 TJ Holowaychuk - * MIT Licensed - */ + tok.mode = mode; + return tok; + } + }, -/** - * Module dependencies. - */ + /** + * Yield. + */ + + yield: function() { + return this.scan(/^yield */, 'yield'); + }, -var Lexer = require('./lexer') - , nodes = require('./nodes') - , utils = require('./utils') - , filters = require('./filters') - , path = require('path') - , extname = path.extname; + /** + * Include. + */ + + include: function() { + return this.scan(/^include +([^\n]+)/, 'include'); + }, -/** - * Initialize `Parser` with the given input `str` and `filename`. - * - * @param {String} str - * @param {String} filename - * @param {Object} options - * @api public - */ + /** + * Case. + */ + + "case": function() { + return this.scan(/^case +([^\n]+)/, 'case'); + }, -var Parser = exports = module.exports = function Parser(str, filename, options){ - this.input = str; - this.lexer = new Lexer(str, options); - this.filename = filename; - this.blocks = []; - this.mixins = {}; - this.options = options; - this.contexts = [this]; -}; + /** + * When. + */ + + when: function() { + return this.scan(/^when +([^:\n]+)/, 'when'); + }, -/** - * Tags that may not contain tags. - */ + /** + * Default. + */ + + "default": function() { + return this.scan(/^default */, 'default'); + }, -var textOnly = exports.textOnly = ['script', 'style']; + /** + * 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 + ');'); + } + }, -/** - * Parser prototype. - */ + /** + * Call mixin. + */ + + call: function(){ + var captures; + if (captures = /^\+([-\w]+)/.exec(this.input)) { + this.consume(captures[0].length); + var tok = this.tok('call', captures[1]); -Parser.prototype = { + // Check for args (not attributes) + if (captures = /^ *\(/.exec(this.input)) { + try { + var range = this.bracketExpression(captures[0].length - 1); + if (!/^ *[-\w]+ *=/.test(range.src)) { // not attributes + this.consume(range.end + 1); + tok.args = range.src; + } + } catch (ex) { + //not a bracket expcetion, just unmatched open parens + } + } + + return tok; + } + }, /** - * Push `parser` onto the context stack, - * or pop and return a `Parser`. + * Mixin. */ - context: function(parser){ - if (parser) { - this.contexts.push(parser); - } else { - return this.contexts.pop(); + 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; } }, /** - * Return the next token object. - * - * @return {Object} - * @api private + * 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]; - advance: function(){ - return this.lexer.advance(); + 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); + } }, /** - * Skip `n` tokens. - * - * @param {Number} n - * @api private + * While. */ - - skip: function(n){ - while (n--) this.advance(); + + "while": function() { + var captures; + if (captures = /^while +([^\n]+)/.exec(this.input)) { + this.consume(captures[0].length); + return this.tok('code', 'while (' + captures[1] + ')'); + } }, /** - * Single token lookahead. - * - * @return {Object} - * @api private + * Each. */ - - peek: function() { - return this.lookahead(1); + + 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; + } }, - + /** - * Return lexer lineno. - * - * @return {Number} - * @api private + * Code. */ - - line: function() { - return this.lexer.lineno; + + code: function() { + var captures; + if (captures = /^(!?=|-)[ \t]*([^\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.charAt(0) === '='; + tok.buffer = flags.charAt(0) === '=' || flags.charAt(1) === '='; + return tok; + } }, - + /** - * `n` token lookahead. - * - * @param {Number} n - * @return {Object} - * @api private + * Attributes. */ + + attrs: function() { + if ('(' == this.input.charAt(0)) { + var index = this.bracketExpression().end + , 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(_, escape, expr){ + if (escape) return _; + try { + var range = parseJSExpression(expr); + if (expr[range.end] !== '}') return _.substr(0, 2) + interpolate(_.substr(2)); + return quote + " + (" + range.src + ") + " + quote + interpolate(expr.substr(range.end + 1)); + } catch (ex) { + return _.substr(0, 2) + interpolate(_.substr(2)); + } + }); + } + + 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; + } - lookahead: function(n){ - return this.lexer.lookahead(n); + return tok; + } }, - + /** - * Parse input returning a string of js for evaluation. - * - * @return {String} - * @api public + * Indent | Outdent | Newline. */ + + indent: function() { + var captures, re; - parse: function(){ - var block = new nodes.Block, parser; - block.line = this.line(); + // established regexp + if (this.indentRe) { + captures = this.indentRe.exec(this.input); + // determine regexp + } else { + // tabs + re = /^\n(\t*) */; + captures = re.exec(this.input); - while ('eos' != this.peek().type) { - if ('newline' == this.peek().type) { - this.advance(); - } else { - block.push(this.parseExpr()); + // spaces + if (captures && !captures[1].length) { + re = /^\n( *)/; + captures = re.exec(this.input); } + + // established + if (captures && captures[1].length) this.indentRe = re; } - if (parser = this.extending) { - this.context(parser); - var ast = parser.parse(); - this.context(); + if (captures) { + var tok + , indents = captures[1].length; - // hoist mixins - for (var name in this.mixins) - ast.unshift(this.mixins[name]); - return ast; - } else { - this.handleBlocks(); - } + ++this.lineno; + this.consume(indents + 1); - return block; - }, + if (' ' == this.input[0] || '\t' == this.input[0]) { + throw new Error('Invalid indentation, you can use tabs or spaces but not both'); + } - /** - * Handle blocks, appends and prepends. Must be called from must deep parser (which parses template that does not extends another template). - * @api private - */ + // blank line + if ('\n' == this.input[0]) return this.tok('newline'); - handleBlocks: function() { - this.blocks.reverse(); - var blocksHash = {}; // blockName: block object - for (var i in this.blocks) { - if (!( ({}).hasOwnProperty.call(blocksHash, [this.blocks[i].name]) )) { // just safe call to blocksHash.hasOwnProperty - blocksHash[this.blocks[i].name] = this.blocks[i]; - } else { - switch (this.blocks[i].mode) { - case 'append': - blocksHash[this.blocks[i].name].nodes = blocksHash[this.blocks[i].name].nodes.concat(this.blocks[i].nodes); - break; - case 'prepend': - blocksHash[this.blocks[i].name].nodes = this.blocks[i].nodes.concat(blocksHash[this.blocks[i].name].nodes); - break; - default: - blocksHash[this.blocks[i].name].nodes = this.blocks[i].nodes; + // 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(); } - this.blocks[i] = blocksHash[this.blocks[i].name]; + 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; } }, /** - * Expect the given type, or throw an exception. - * - * @param {String} type - * @api private + * Pipe-less text consumed only when + * pipeless is true; */ - expect: function(type){ - if (this.peek().type === type) { - return this.advance(); - } else { - throw new Error('expected "' + type + '", but got "' + this.peek().type + '"'); + 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); } }, /** - * Accept the given `type`. - * - * @param {String} type - * @api private + * ':' */ - accept: function(type){ - if (this.peek().type === type) { - return this.advance(); - } + colon: function() { + return this.scan(/^: */, ':'); }, /** - * tag - * | doctype - * | mixin - * | include - * | filter - * | comment - * | text - * | each - * | code - * | yield - * | id - * | class - * | interpolation + * Return the next token object, or those + * previously stashed by lookahead. + * + * @return {Object} + * @api private */ - - parseExpr: function(){ - switch (this.peek().type) { - case 'tag': - return this.parseTag(); - case 'mixin': - return this.parseMixin(); - case 'block': - return this.parseBlock(); - case 'case': - return this.parseCase(); - case 'when': - return this.parseWhen(); - case 'default': - return this.parseDefault(); - case 'extends': - return this.parseExtends(); - case 'include': - return this.parseInclude(); - 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 'call': - return this.parseCall(); - case 'interpolation': - return this.parseInterpolation(); - case 'yield': - this.advance(); - var block = new nodes.Block; - block.yield = true; - return block; - 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 + '"'); - } + + advance: function(){ + return this.stashed() + || this.next(); }, - + /** - * Text + * 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(); + } +}; - parseText: function(){ - var tok = this.expect('text'); - var node = new nodes.Text(tok.val); - node.line = this.line(); - return node; - }, +},{"./utils":5,"character-parser":27}],27:[function(require,module,exports){ +exports = (module.exports = parse); +exports.parse = parse; +function parse(src, state, options) { + options = options || {}; + state = state || exports.defaultState(); + var start = options.start || 0; + var end = options.end || src.length; + var index = start; + while (index < end) { + if (state.roundDepth < 0 || state.curlyDepth < 0 || state.squareDepth < 0) { + throw new SyntaxError('Mismatched Bracket: ' + src[index - 1]); + } + exports.parseChar(src[index++], state); + } + return state; +} + +exports.parseMax = parseMax; +function parseMax(src, options) { + options = options || {}; + var start = options.start || 0; + var index = start; + var state = exports.defaultState(); + while (state.roundDepth >= 0 && state.curlyDepth >= 0 && state.squareDepth >= 0) { + if (index >= src.length) { + throw new Error('The end of the string was reached with no closing bracket found.'); + } + exports.parseChar(src[index++], state); + } + var end = index - 1; + return { + start: start, + end: end, + src: src.substring(start, end) + }; +} + +exports.parseUntil = parseUntil; +function parseUntil(src, delimiter, options) { + options = options || {}; + var includeLineComment = options.includeLineComment || false; + var start = options.start || 0; + var index = start; + var state = exports.defaultState(); + while (state.singleQuote || state.doubleQuote || state.regexp || state.blockComment || + (!includeLineComment && state.lineComment) || !startsWith(src, delimiter, index)) { + exports.parseChar(src[index++], state); + } + var end = index; + return { + start: start, + end: end, + src: src.substring(start, end) + }; +} - /** - * ':' expr - * | block - */ - parseBlockExpansion: function(){ - if (':' == this.peek().type) { - this.advance(); - return new nodes.Block(this.parseExpr()); +exports.parseChar = parseChar; +function parseChar(character, state) { + if (character.length !== 1) throw new Error('Character must be a string of length 1'); + state = state || defaultState(); + var wasComment = state.blockComment || state.lineComment; + var lastChar = state.history ? state.history[0] : ''; + if (state.lineComment) { + if (character === '\n') { + state.lineComment = false; + } + } else if (state.blockComment) { + if (state.lastChar === '*' && character === '/') { + state.blockComment = false; + } + } else if (state.singleQuote) { + if (character === '\'' && !state.escaped) { + state.singleQuote = false; + } else if (character === '\\' && !state.escaped) { + state.escaped = true; } else { - return this.block(); + state.escaped = false; } - }, + } else if (state.doubleQuote) { + if (character === '"' && !state.escaped) { + state.doubleQuote = false; + } else if (character === '\\' && !state.escaped) { + state.escaped = true; + } else { + state.escaped = false; + } + } else if (state.regexp) { + if (character === '/' && !state.escaped) { + state.regexp = false; + } else if (character === '\\' && !state.escaped) { + state.escaped = true; + } else { + state.escaped = false; + } + } else if (lastChar === '/' && character === '/') { + history = history.substr(1); + state.lineComment = true; + } else if (lastChar === '/' && character === '*') { + history = history.substr(1); + state.blockComment = true; + } else if (character === '/') { + //could be start of regexp or divide sign + var history = state.history.replace(/^\s*/, ''); + if (history[0] === ')') { + //unless its an `if`, `while`, `for` or `with` it's a divide + //this is probably best left though + } else if (history[0] === '}') { + //unless it's a function expression, it's a regexp + //this is probably best left though + } else if (isPunctuator(history[0])) { + state.regexp = true; + } else if (/^\w+\b/.test(history) && isKeyword(/^\w+\b/.exec(history)[0])) { + state.regexp = true; + } else { + // assume it's divide + } + } else if (character === '\'') { + state.singleQuote = true; + } else if (character === '"') { + state.doubleQuote = true; + } else if (character === '(') { + state.roundDepth++; + } else if (character === ')') { + state.roundDepth--; + } else if (character === '{') { + state.curlyDepth++; + } else if (character === '}') { + state.curlyDepth--; + } else if (character === '[') { + state.squareDepth++; + } else if (character === ']') { + state.squareDepth--; + } + if (!state.blockComment && !state.lineComment && !wasComment) state.history = character + state.history; + return state; +} - /** - * case - */ +exports.defaultState = defaultState; +function defaultState() { + return { + lineComment: false, + blockComment: false, - parseCase: function(){ - var val = this.expect('case').val; - var node = new nodes.Case(val); - node.line = this.line(); - node.block = this.block(); - return node; - }, + singleQuote: false, + doubleQuote: false, + regexp: false, + escaped: false, - /** - * when - */ + roundDepth: 0, + curlyDepth: 0, + squareDepth: 0, - parseWhen: function(){ - var val = this.expect('when').val - return new nodes.Case.When(val, this.parseBlockExpansion()); - }, + history: '' + }; +} - /** - * default - */ +function startsWith(str, start, i) { + return str.substr(i || 0, start.length) === start; +} - parseDefault: function(){ - this.expect('default'); - return new nodes.Case.When('default', this.parseBlockExpansion()); - }, +function isPunctuator(c) { + var code = c.charCodeAt(0) + + switch (code) { + case 46: // . dot + case 40: // ( open bracket + case 41: // ) close bracket + case 59: // ; semicolon + case 44: // , comma + case 123: // { open curly brace + case 125: // } close curly brace + case 91: // [ + case 93: // ] + case 58: // : + case 63: // ? + case 126: // ~ + case 37: // % + case 38: // & + case 42: // *: + case 43: // + + case 45: // - + case 47: // / + case 60: // < + case 62: // > + case 94: // ^ + case 124: // | + case 33: // ! + case 61: // = + return true; + default: + return false; + } +} +function isKeyword(id) { + return (id === 'if') || (id === 'in') || (id === 'do') || (id === 'var') || (id === 'for') || (id === 'new') || + (id === 'try') || (id === 'let') || (id === 'this') || (id === 'else') || (id === 'case') || + (id === 'void') || (id === 'with') || (id === 'enum') || (id === 'while') || (id === 'break') || (id === 'catch') || + (id === 'throw') || (id === 'const') || (id === 'yield') || (id === 'class') || (id === 'super') || + (id === 'return') || (id === 'typeof') || (id === 'delete') || (id === 'switch') || (id === 'export') || + (id === 'import') || (id === 'default') || (id === 'finally') || (id === 'extends') || (id === 'function') || + (id === 'continue') || (id === 'debugger') || (id === 'package') || (id === 'private') || (id === 'interface') || + (id === 'instanceof') || (id === 'implements') || (id === 'protected') || (id === 'public') || (id === 'static') || + (id === 'yield') || (id === 'let'); +} - /** - * code - */ +},{}],15:[function(require,module,exports){ - parseCode: function(){ - var tok = this.expect('code'); - var node = new nodes.Code(tok.val, tok.buffer, tok.escape); - var block; - var i = 1; - node.line = this.line(); - while (this.lookahead(i) && 'newline' == this.lookahead(i).type) ++i; - block = 'indent' == this.lookahead(i).type; - if (block) { - this.skip(i-1); - node.block = this.block(); - } - return node; - }, +/*! + * Jade - nodes - Tag + * Copyright(c) 2010 TJ Holowaychuk + * MIT Licensed + */ - /** - * comment - */ +/** + * Module dependencies. + */ - parseComment: function(){ - var tok = this.expect('comment'); - var node; +var Attrs = require('./attrs'), + Block = require('./block'), + inlineTags = require('../inline-tags'); - if ('indent' == this.peek().type) { - node = new nodes.BlockComment(tok.val, this.block(), tok.buffer); - } else { - node = new nodes.Comment(tok.val, tok.buffer); - } +/** + * Initialize a `Tag` node with the given tag `name` and optional `block`. + * + * @param {String} name + * @param {Block} block + * @api public + */ - node.line = this.line(); - return node; - }, +var Tag = module.exports = function Tag(name, block) { + this.name = name; + this.attrs = []; + this.block = block || new Block; +}; - /** - * doctype - */ +/** + * Inherit from `Attrs`. + */ - parseDoctype: function(){ - var tok = this.expect('doctype'); - var node = new nodes.Doctype(tok.val); - node.line = this.line(); - return node; - }, +Tag.prototype.__proto__ = Attrs.prototype; - /** - * filter attrs? text-block - */ +/** + * Clone this tag. + * + * @return {Tag} + * @api private + */ - parseFilter: function(){ - var tok = this.expect('filter'); - var attrs = this.accept('attrs'); - var block; +Tag.prototype.clone = function(){ + var clone = new Tag(this.name, this.block.clone()); + clone.line = this.line; + clone.attrs = this.attrs; + clone.textOnly = this.textOnly; + return clone; +}; - this.lexer.pipeless = true; - block = this.parseTextBlock(); - this.lexer.pipeless = false; +/** + * Check if this tag is an inline tag. + * + * @return {Boolean} + * @api private + */ - var node = new nodes.Filter(tok.val, block, attrs && attrs.attrs); - node.line = this.line(); - return node; - }, +Tag.prototype.isInline = function(){ + return ~inlineTags.indexOf(this.name); +}; - /** - * each block - */ +/** + * Check if this tag's contents can be inlined. Used for pretty printing. + * + * @return {Boolean} + * @api private + */ - parseEach: function(){ - var tok = this.expect('each'); - var node = new nodes.Each(tok.code, tok.val, tok.key); - node.line = this.line(); - node.block = this.block(); - if (this.peek().type == 'code' && this.peek().val == 'else') { - this.advance(); - node.alternative = this.block(); +Tag.prototype.canInline = function(){ + var nodes = this.block.nodes; + + function isInline(node){ + // Recurse if the node is a block + if (node.isBlock) return node.nodes.every(isInline); + return node.isText || (node.isInline && node.isInline()); + } + + // Empty tag + if (!nodes.length) return true; + + // Text-only or inline-only tag + if (1 == nodes.length) return isInline(nodes[0]); + + // Multi-line inline-only tag + if (this.block.nodes.every(isInline)) { + for (var i = 1, len = nodes.length; i < len; ++i) { + if (nodes[i-1].isText && nodes[i].isText) + return false; } - return node; - }, + return true; + } + + // Mixed tag + return false; +}; +},{"./block":20,"./attrs":28,"../inline-tags":29}],18:[function(require,module,exports){ - /** - * Resolves a path relative to the template for use in - * includes and extends - * - * @param {String} path - * @param {String} purpose Used in error messages. - * @return {String} - * @api private - */ +/*! + * Jade - nodes - Text + * Copyright(c) 2010 TJ Holowaychuk + * MIT Licensed + */ - resolvePath: function (path, purpose) { - var p = require('path'); - var dirname = p.dirname; - var basename = p.basename; - var join = p.join; +/** + * Module dependencies. + */ - if (path[0] !== '/' && !this.filename) - throw new Error('the "filename" option is required to use "' + purpose + '" with "relative" paths'); +var Node = require('./node'); - if (path[0] === '/' && !this.options.basedir) - throw new Error('the "basedir" option is required to use "' + purpose + '" with "absolute" paths'); +/** + * Initialize a `Text` node with optional `line`. + * + * @param {String} line + * @api public + */ + +var Text = module.exports = function Text(line) { + this.val = ''; + if ('string' == typeof line) this.val = line; +}; + +/** + * Inherit from `Node`. + */ + +Text.prototype.__proto__ = Node.prototype; + +/** + * Flag as text. + */ + +Text.prototype.isText = true; +},{"./node":14}],16:[function(require,module,exports){ + +/*! + * Jade - nodes - Each + * Copyright(c) 2010 TJ Holowaychuk + * MIT Licensed + */ + +/** + * Module dependencies. + */ + +var Node = require('./node'); - path = join(path[0] === '/' ? this.options.basedir : dirname(this.filename), path); +/** + * Initialize an `Each` node, representing iteration + * + * @param {String} obj + * @param {String} val + * @param {String} key + * @param {Block} block + * @api public + */ - if (basename(path).indexOf('.') === -1) path += '.jade'; +var Each = module.exports = function Each(obj, val, key, block) { + this.obj = obj; + this.val = val; + this.key = key; + this.block = block; +}; - return path; - }, +/** + * Inherit from `Node`. + */ - /** - * 'extends' name - */ +Each.prototype.__proto__ = Node.prototype; +},{"./node":14}],19:[function(require,module,exports){ - parseExtends: function(){ - var fs = require('fs'); +/*! + * Jade - nodes - Case + * Copyright(c) 2010 TJ Holowaychuk + * MIT Licensed + */ - var path = this.resolvePath(this.expect('extends').val.trim(), 'extends'); - if ('.jade' != path.substr(-5)) path += '.jade'; +/** + * Module dependencies. + */ - var str = fs.readFileSync(path, 'utf8'); - var parser = new Parser(str, path, this.options); +var Node = require('./node'); - parser.blocks = this.blocks.reverse(); - parser.contexts = this.contexts; - this.extending = parser; +/** + * Initialize a new `Case` with `expr`. + * + * @param {String} expr + * @api public + */ - // TODO: null node - return new nodes.Literal(''); - }, +var Case = exports = module.exports = function Case(expr, block){ + this.expr = expr; + this.block = block; +}; - /** - * 'block' name block - */ +/** + * Inherit from `Node`. + */ - parseBlock: function(){ - var block = this.expect('block'); - var mode = block.mode; - var name = block.val.trim(); +Case.prototype.__proto__ = Node.prototype; - block = 'indent' == this.peek().type - ? this.block() - : new nodes.Block(new nodes.Literal('')); +var When = exports.When = function When(expr, block){ + this.expr = expr; + this.block = block; + this.debug = false; +}; - block.mode = mode; - block.name = name; - this.blocks.push(block); - return block; - }, +/** + * Inherit from `Node`. + */ - /** - * include block? - */ +When.prototype.__proto__ = Node.prototype; - parseInclude: function(){ - var fs = require('fs'); - var path = this.resolvePath(this.expect('include').val.trim(), 'include'); +},{"./node":14}],22:[function(require,module,exports){ - // non-jade - if ('.jade' != path.substr(-5)) { - var str = fs.readFileSync(path, 'utf8').replace(/\r/g, ''); - var ext = extname(path).slice(1); - if (filters.exists(ext)) str = filters(ext, str, { filename: path }); - return new nodes.Literal(str); - } +/*! + * Jade - nodes - Filter + * Copyright(c) 2010 TJ Holowaychuk + * MIT Licensed + */ - var str = fs.readFileSync(path, 'utf8'); - var parser = new Parser(str, path, this.options); - parser.blocks = utils.merge([], this.blocks); +/** + * Module dependencies. + */ - parser.mixins = this.mixins; +var Node = require('./node') + , Block = require('./block'); - this.context(parser); - var ast = parser.parse(); - this.context(); - ast.filename = path; +/** + * Initialize a `Filter` node with the given + * filter `name` and `block`. + * + * @param {String} name + * @param {Block|Node} block + * @api public + */ - if ('indent' == this.peek().type) { - ast.includeBlock().push(this.block()); - } +var Filter = module.exports = function Filter(name, block, attrs) { + this.name = name; + this.block = block; + this.attrs = attrs; +}; - return ast; - }, +/** + * Inherit from `Node`. + */ - /** - * call ident block - */ +Filter.prototype.__proto__ = Node.prototype; +},{"./block":20,"./node":14}],20:[function(require,module,exports){ - parseCall: function(){ - var tok = this.expect('call'); - var name = tok.val; - var args = tok.args; - var mixin = new nodes.Mixin(name, args, new nodes.Block, true); +/*! + * Jade - nodes - Block + * Copyright(c) 2010 TJ Holowaychuk + * MIT Licensed + */ - this.tag(mixin); - if (mixin.block.isEmpty()) mixin.block = null; - return mixin; - }, +/** + * Module dependencies. + */ - /** - * mixin block - */ +var Node = require('./node'); - parseMixin: function(){ - var tok = this.expect('mixin'); - var name = tok.val; - var args = tok.args; - var mixin; +/** + * Initialize a new `Block` with an optional `node`. + * + * @param {Node} node + * @api public + */ - // definition - if ('indent' == this.peek().type) { - mixin = new nodes.Mixin(name, args, this.block(), false); - this.mixins[name] = mixin; - return mixin; - // call - } else { - return new nodes.Mixin(name, args, null, true); - } - }, +var Block = module.exports = function Block(node){ + this.nodes = []; + if (node) this.push(node); +}; - /** - * indent (text | newline)* outdent - */ +/** + * Inherit from `Node`. + */ - parseTextBlock: function(){ - var block = new nodes.Block; - block.line = this.line(); - var spaces = this.expect('indent').val; - if (null == this._spaces) this._spaces = spaces; - var indent = Array(spaces - this._spaces + 1).join(' '); - while ('outdent' != this.peek().type) { - switch (this.peek().type) { - case 'newline': - this.advance(); - break; - case 'indent': - this.parseTextBlock().nodes.forEach(function(node){ - block.push(node); - }); - break; - default: - var text = new nodes.Text(indent + this.advance().val); - text.line = this.line(); - block.push(text); - } - } +Block.prototype.__proto__ = Node.prototype; - if (spaces == this._spaces) this._spaces = null; - this.expect('outdent'); - return block; - }, +/** + * Block flag. + */ - /** - * indent expr* outdent - */ +Block.prototype.isBlock = true; - block: 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; - }, +/** + * Replace the nodes in `other` with the nodes + * in `this` block. + * + * @param {Block} other + * @api private + */ - /** - * interpolation (attrs | class | id)* (text | code | ':')? newline* block? - */ +Block.prototype.replace = function(other){ + other.nodes = this.nodes; +}; - parseInterpolation: function(){ - var tok = this.advance(); - var tag = new nodes.Tag(tok.val); - tag.buffer = true; - return this.tag(tag); - }, +/** + * Pust the given `node`. + * + * @param {Node} node + * @return {Number} + * @api public + */ - /** - * tag (attrs | class | id)* (text | code | ':')? newline* block? - */ +Block.prototype.push = function(node){ + return this.nodes.push(node); +}; - parseTag: function(){ - // ast-filter look-ahead - var i = 2; - if ('attrs' == this.lookahead(i).type) ++i; +/** + * Check if this block is empty. + * + * @return {Boolean} + * @api public + */ - var tok = this.advance(); - var tag = new nodes.Tag(tok.val); +Block.prototype.isEmpty = function(){ + return 0 == this.nodes.length; +}; - tag.selfClosing = tok.selfClosing; +/** + * Unshift the given `node`. + * + * @param {Node} node + * @return {Number} + * @api public + */ - return this.tag(tag); - }, +Block.prototype.unshift = function(node){ + return this.nodes.unshift(node); +}; - /** - * Parse tag. - */ +/** + * Return the "last" block, or the first `yield` node. + * + * @return {Block} + * @api private + */ - tag: function(tag){ - var dot; +Block.prototype.includeBlock = function(){ + var ret = this + , node; - tag.line = this.line(); + 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(); + if (ret.yield) return ret; + } - // (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 tok = this.advance() - , obj = tok.attrs - , escaped = tok.escaped - , names = Object.keys(obj); + return ret; +}; - if (tok.selfClosing) tag.selfClosing = true; +/** + * Return a clone of this block. + * + * @return {Block} + * @api private + */ - for (var i = 0, len = names.length; i < len; ++i) { - var name = names[i] - , val = obj[name]; - tag.setAttribute(name, val, escaped[name]); - } - continue; - default: - break out; - } - } +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; +}; - // check immediate '.' - if ('.' == this.peek().val) { - dot = tag.textOnly = true; - this.advance(); - } - // (text | code | ':')? - switch (this.peek().type) { - case 'text': - tag.block.push(this.parseText()); - break; - case 'code': - tag.code = this.parseCode(); - break; - case ':': - this.advance(); - tag.block = new nodes.Block; - tag.block.push(this.parseExpr()); - break; - } +},{"./node":14}],17:[function(require,module,exports){ - // newline* - while ('newline' == this.peek().type) this.advance(); +/*! + * Jade - nodes - Code + * Copyright(c) 2010 TJ Holowaychuk + * MIT Licensed + */ - tag.textOnly = tag.textOnly || ~textOnly.indexOf(tag.name); +/** + * Module dependencies. + */ - // script special-case - if ('script' == tag.name) { - var type = tag.getAttribute('type'); - if (!dot && type && 'text/javascript' != type.replace(/^['"]|['"]$/g, '')) { - tag.textOnly = false; - } - } +var Node = require('./node'); - // block? - if ('indent' == this.peek().type) { - if (tag.textOnly) { - this.lexer.pipeless = true; - tag.block = this.parseTextBlock(); - this.lexer.pipeless = false; - } else { - var block = this.block(); - if (tag.block) { - for (var i = 0, len = block.nodes.length; i < len; ++i) { - tag.block.push(block.nodes[i]); - } - } else { - tag.block = block; - } - } - } +/** + * 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 + */ - return tag; - } +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; }; -}); // module: parser.js +/** + * Inherit from `Node`. + */ -require.register("runtime.js", function(module, exports, require){ +Code.prototype.__proto__ = Node.prototype; +},{"./node":14}],21:[function(require,module,exports){ /*! - * Jade - runtime + * Jade - nodes - Mixin * Copyright(c) 2010 TJ Holowaychuk * MIT Licensed */ /** - * Lame Array.isArray() polyfill for now. + * Module dependencies. */ -if (!Array.isArray) { - Array.isArray = function(arr){ - return '[object Array]' == Object.prototype.toString.call(arr); - }; -} +var Attrs = require('./attrs'); /** - * Lame Object.keys() polyfill for now. + * Initialize a new `Mixin` with `name` and `block`. + * + * @param {String} name + * @param {String} args + * @param {Block} block + * @api public */ -if (!Object.keys) { - Object.keys = function(obj){ - var arr = []; - for (var key in obj) { - if (obj.hasOwnProperty(key)) { - arr.push(key); - } - } - return arr; - } -} +var Mixin = module.exports = function Mixin(name, args, block, call){ + this.name = name; + this.args = args; + this.block = block; + this.attrs = []; + this.call = call; +}; /** - * Merge two attribute objects giving precedence - * to values in object `b`. Classes are special-cased - * allowing for arrays and merging/joining appropriately - * resulting in a string. - * - * @param {Object} a - * @param {Object} b - * @return {Object} a - * @api private + * Inherit from `Attrs`. */ -exports.merge = function merge(a, b) { - var ac = a['class']; - var bc = b['class']; +Mixin.prototype.__proto__ = Attrs.prototype; - if (ac || bc) { - ac = ac || []; - bc = bc || []; - if (!Array.isArray(ac)) ac = [ac]; - if (!Array.isArray(bc)) bc = [bc]; - ac = ac.filter(nulls); - bc = bc.filter(nulls); - a['class'] = ac.concat(bc).join(' '); - } - for (var key in b) { - if (key != 'class') { - a[key] = b[key]; - } - } +},{"./attrs":28}],23:[function(require,module,exports){ + +/*! + * Jade - nodes - Comment + * Copyright(c) 2010 TJ Holowaychuk + * 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 + */ - return a; +var Comment = module.exports = function Comment(val, buffer) { + this.val = val; + this.buffer = buffer; }; /** - * Filter null `val`s. - * - * @param {Mixed} val - * @return {Mixed} - * @api private + * Inherit from `Node`. */ -function nulls(val) { - return val != null; -} +Comment.prototype.__proto__ = Node.prototype; +},{"./node":14}],26:[function(require,module,exports){ + +/*! + * Jade - nodes - Doctype + * Copyright(c) 2010 TJ Holowaychuk + * MIT Licensed + */ /** - * Render the given attributes object. + * Module dependencies. + */ + +var Node = require('./node'); + +/** + * Initialize a `Doctype` with the given `val`. * - * @param {Object} obj - * @param {Object} escaped - * @return {String} - * @api private + * @param {String} val + * @api public */ -exports.attrs = function attrs(obj, escaped){ - var buf = [] - , terse = obj.terse; +var Doctype = module.exports = function Doctype(val) { + this.val = val; +}; - delete obj.terse; - var keys = Object.keys(obj) - , len = keys.length; +/** + * Inherit from `Node`. + */ - if (len) { - buf.push(''); - for (var i = 0; i < len; ++i) { - var key = keys[i] - , val = obj[key]; +Doctype.prototype.__proto__ = Node.prototype; +},{"./node":14}],24:[function(require,module,exports){ - if ('boolean' == typeof val || null == val) { - if (val) { - terse - ? buf.push(key) - : buf.push(key + '="' + key + '"'); - } - } else if (0 == key.indexOf('data') && 'string' != typeof val) { - buf.push(key + "='" + JSON.stringify(val) + "'"); - } else if ('class' == key && Array.isArray(val)) { - buf.push(key + '="' + exports.escape(val.join(' ')) + '"'); - } else if (escaped && escaped[key]) { - buf.push(key + '="' + exports.escape(val) + '"'); - } else { - buf.push(key + '="' + val + '"'); - } - } - } +/*! + * Jade - nodes - Literal + * Copyright(c) 2010 TJ Holowaychuk + * MIT Licensed + */ - return buf.join(' '); -}; +/** + * Module dependencies. + */ + +var Node = require('./node'); /** - * Escape the given string of `html`. + * Initialize a `Literal` node with the given `str. * - * @param {String} html - * @return {String} - * @api private + * @param {String} str + * @api public */ -exports.escape = function escape(html){ - return String(html) - .replace(/&/g, '&') - .replace(//g, '>') - .replace(/"/g, '"'); +var Literal = module.exports = function Literal(str) { + this.str = str; }; /** - * Re-throw the given `err` in context to the - * the jade in `filename` at the given `lineno`. - * - * @param {Error} err - * @param {String} filename - * @param {String} lineno - * @api private + * Inherit from `Node`. */ -exports.rethrow = function rethrow(err, filename, lineno){ - if (!filename) throw err; - if (typeof window != 'undefined') throw err; +Literal.prototype.__proto__ = Node.prototype; - var context = 3 - , str = require('fs').readFileSync(filename, 'utf8') - , lines = str.split('\n') - , start = Math.max(lineno - context, 0) - , end = Math.min(lines.length, lineno + context); +},{"./node":14}],25:[function(require,module,exports){ - // Error context - var context = lines.slice(start, end).map(function(line, i){ - var curr = i + start + 1; - return (curr == lineno ? ' > ' : ' ') - + curr - + '| ' - + line; - }).join('\n'); +/*! + * Jade - nodes - BlockComment + * Copyright(c) 2010 TJ Holowaychuk + * MIT Licensed + */ - // Alter exception message - err.path = filename; - err.message = (filename || 'Jade') + ':' + lineno - + '\n' + context + '\n\n' + err.message; - throw err; +/** + * 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; }; -}); // module: runtime.js +/** + * Inherit from `Node`. + */ -require.register("self-closing.js", function(module, exports, require){ +BlockComment.prototype.__proto__ = Node.prototype; +},{"./node":14}],29:[function(require,module,exports){ /*! - * Jade - self closing tags + * Jade - inline tags * Copyright(c) 2010 TJ Holowaychuk * MIT Licensed */ module.exports = [ - 'meta' - , 'img' - , 'link' - , 'input' - , 'source' - , 'area' - , 'base' - , 'col' + 'a' + , 'abbr' + , 'acronym' + , 'b' , 'br' - , 'hr' + , 'code' + , 'em' + , 'font' + , 'i' + , 'img' + , 'ins' + , 'kbd' + , 'map' + , 'samp' + , 'small' + , 'span' + , 'strong' + , 'sub' + , 'sup' ]; -}); // module: self-closing.js - -require.register("utils.js", function(module, exports, require){ +},{}],28:[function(require,module,exports){ /*! - * Jade - utils + * Jade - nodes - Attrs * Copyright(c) 2010 TJ Holowaychuk * MIT Licensed */ /** - * Merge `b` into `a`. + * Module dependencies. + */ + +var Node = require('./node'), + Block = require('./block'); + +/** + * Initialize a `Attrs` node. * - * @param {Object} a - * @param {Object} b - * @return {Object} * @api public */ -exports.merge = function(a, b) { - for (var key in b) a[key] = b[key]; - return a; +var Attrs = module.exports = function Attrs() { + this.attrs = []; }; +/** + * Inherit from `Node`. + */ -}); // module: utils.js +Attrs.prototype.__proto__ = Node.prototype; -return require("jade"); -})(); +/** + * 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; + } + } +}; + +},{"./node":14,"./block":20}]},{},[6])(6) +}); +; \ No newline at end of file diff --git a/jade.min.js b/jade.min.js index e21252824..796abe53a 100644 --- a/jade.min.js +++ b/jade.min.js @@ -1,2 +1,2 @@ -var jade=function(){function require(p){var path=require.resolve(p),mod=require.modules[path];if(!mod)throw new Error('failed to require "'+p+'"');return mod.exports||(mod.exports={},mod.call(mod.exports,mod,mod.exports,require.relative(path))),mod.exports}return require.modules={},require.resolve=function(path){var orig=path,reg=path+".js",index=path+"/index.js";return require.modules[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",this.terse=this.doctype.toLowerCase()=="",this.xml=0==this.doctype.indexOf("1&&!escape&&block.nodes[0].isText&&block.nodes[1].isText&&this.prettyIndent(1,!0);for(var i=0;i0&&!escape&&block.nodes[i].isText&&block.nodes[i-1].isText&&this.prettyIndent(1,!1),this.visit(block.nodes[i]),block.nodes[i+1]&&block.nodes[i].isText&&block.nodes[i+1].isText&&this.buffer("\n")},visitDoctype:function(doctype){doctype&&(doctype.val||!this.doctype)&&this.setDoctype(doctype.val||"default"),this.doctype&&this.buffer(this.doctype),this.hasCompiledDoctype=!0},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){pp&&this.buf.push("jade.indent.push('"+Array(this.indents+1).join(" ")+"');");if(block||attrs.length){this.buf.push(name+".call({");if(block){this.buf.push("block: function(){"),this.parentIndents++;var _indents=this.indents;this.indents=0,this.visit(mixin.block),this.indents=_indents,this.parentIndents--,attrs.length?this.buf.push("},"):this.buf.push("}")}if(attrs.length){var val=this.attrs(attrs);val.inherits?this.buf.push("attributes: jade.merge({"+val.buf+"}, attributes), escaped: jade.merge("+val.escaped+", escaped, true)"):this.buf.push("attributes: {"+val.buf+"}, escaped: "+val.escaped)}args?this.buf.push("}, "+args+");"):this.buf.push("});")}else this.buf.push(name+"("+args+");");pp&&this.buf.push("jade.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("};")},visitTag:function(tag){function bufferName(){tag.buffer?self.bufferExpression(name):self.buffer(name)}this.indents++;var name=tag.name,pp=this.pp,self=this;this.hasCompiledTag||(!this.hasCompiledDoctype&&"html"==name&&this.visitDoctype(),this.hasCompiledTag=!0),pp&&!tag.isInline()&&this.prettyIndent(0,!0),(~selfClosing.indexOf(name)||tag.selfClosing)&&!this.xml?(this.buffer("<"),bufferName(),this.visitAttributes(tag.attrs),this.terse?this.buffer(">"):this.buffer("/>")):(tag.attrs.length?(this.buffer("<"),bufferName(),tag.attrs.length&&this.visitAttributes(tag.attrs),this.buffer(">")):(this.buffer("<"),bufferName(),this.buffer(">")),tag.code&&this.visitCode(tag.code),this.escape="pre"==tag.name,this.visit(tag.block),pp&&!tag.isInline()&&"pre"!=tag.name&&!tag.canInline()&&this.prettyIndent(0,!0),this.buffer("")),this.indents--},visitFilter:function(filter){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(filters(filter.name,text,filter.attrs),!0)},visitText:function(text){this.buffer(text.val,!0)},visitComment:function(comment){if(!comment.buffer)return;this.pp&&this.prettyIndent(1,!0),this.buffer("")},visitBlockComment:function(comment){if(!comment.buffer)return;0==comment.val.trim().indexOf("if")?(this.buffer("")):(this.buffer(""))},visitCode:function(code){if(code.buffer){var val=code.val.trimLeft();val="null == (jade.interp = "+val+') ? "" : jade.interp',code.escape&&(val="jade.escape("+val+")"),this.bufferExpression(val)}else this.buf.push(code.val);code.block&&(code.buffer||this.buf.push("{"),this.visit(code.block),code.buffer||this.buf.push("}"))},visitEach:function(each){this.buf.push("// iterate "+each.obj+"\n"+";(function(){\n"+" var $$obj = "+each.obj+";\n"+" if ('number' == typeof $$obj.length) {\n"),each.alternative&&this.buf.push(" if ($$obj.length) {"),this.buf.push(" for (var "+each.key+" = 0, $$l = $$obj.length; "+each.key+" < $$l; "+each.key+"++) {\n"+" var "+each.val+" = $$obj["+each.key+"];\n"),this.visit(each.block),this.buf.push(" }\n"),each.alternative&&(this.buf.push(" } else {"),this.visit(each.alternative),this.buf.push(" }")),this.buf.push(" } else {\n var $$l = 0;\n for (var "+each.key+" in $$obj) {\n"+" $$l++;"+" if ($$obj.hasOwnProperty("+each.key+")){"+" var "+each.val+" = $$obj["+each.key+"];\n"),this.visit(each.block),this.buf.push(" }\n"),this.buf.push(" }\n"),each.alternative&&(this.buf.push(" if ($$l === 0) {"),this.visit(each.alternative),this.buf.push(" }")),this.buf.push(" }\n}).call(this);\n")},visitAttributes:function(attrs){var val=this.attrs(attrs);val.inherits?this.bufferExpression("jade.attrs(jade.merge({ "+val.buf+" }, attributes), jade.merge("+val.escaped+", escaped, true))"):val.constant?(eval("var buf={"+val.buf+"};"),this.buffer(runtime.attrs(buf,JSON.parse(val.escaped)))):this.bufferExpression("jade.attrs({ "+val.buf+" }, "+val.escaped+")")},attrs:function(attrs){var buf=[],classes=[],escaped={},constant=attrs.every(function(attr){return isConstant(attr.val)}),inherits=!1;return this.terse&&buf.push("terse: true"),attrs.forEach(function(attr){if(attr.name=="attributes")return inherits=!0;escaped[attr.name]=attr.escaped;if(attr.name=="class")classes.push("("+attr.val+")");else{var pair="'"+attr.name+"':("+attr.val+")";buf.push(pair)}}),classes.length&&(classes=classes.join(" + ' ' + "),buf.push('"class": '+classes)),{buf:buf.join(", "),escaped:JSON.stringify(escaped),inherits:inherits,constant:constant}}}}),require.register("doctypes.js",function(module,exports,require){module.exports={5:"","default":"",xml:'',transitional:'',strict:'',frameset:'',1.1:'',basic:'',mobile:''}}),require.register("filters.js",function(module,exports,require){function filter(name,str,options){if(typeof filter[name]=="function")var res=filter[name](str,options);else{if(!transformers[name])throw new Error('unknown filter ":'+name+'"');var res=transformers[name].renderSync(str,options);transformers[name].outputFormat==="js"?res='":transformers[name].outputFormat==="css"?res='":transformers[name].outputFormat==="xml"&&(res=res.replace(/'/g,"'"))}return res}var transformers=require("transformers");module.exports=filter,filter.exists=function(name,str,options){return typeof filter[name]=="function"||transformers[name]}}),require.register("inline-tags.js",function(module,exports,require){module.exports=["a","abbr","acronym","b","br","code","em","font","i","img","ins","kbd","map","samp","small","span","strong","sub","sup"]}),require.register("jade.js",function(module,exports,require){function parse(str,options){try{var parser=new Parser(str,options.filename,options),compiler=new(options.compiler||Compiler)(parser.parse(),options),js=compiler.compile();return options.debug&&console.error("\nCompiled Function:\n\n%s",js.replace(/^/gm," ")),"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)}}function stripBOM(str){return 65279==str.charCodeAt(0)?str.substring(1):str}var Parser=require("./parser"),Lexer=require("./lexer"),Compiler=require("./compiler"),runtime=require("./runtime");exports.version="0.30.0",exports.selfClosing=require("./self-closing"),exports.doctypes=require("./doctypes"),exports.filters=require("./filters"),exports.utils=require("./utils"),exports.Compiler=Compiler,exports.Parser=Parser,exports.Lexer=Lexer,exports.nodes=require("./nodes"),exports.runtime=runtime,exports.cache={},exports.compile=function(str,options){var options=options||{},filename=options.filename?JSON.stringify(options.filename):"undefined",fn;return str=stripBOM(String(str)),options.compileDebug!==!1?fn=["jade.debug = [{ lineno: 1, filename: "+filename+" }];","try {",parse(str,options),"} catch (err) {"," jade.rethrow(err, jade.debug[0].filename, jade.debug[0].lineno);","}"].join("\n"):fn=parse(str,options),options.client?new Function("locals",fn):(fn=new Function("locals, jade",fn),function(locals){return fn(locals,Object.create(runtime))})},exports.render=function(str,options,fn){"function"==typeof options&&(fn=options,options={});if(options.cache&&!options.filename)return fn(new Error('the "filename" option is required for caching'));try{var path=options.filename,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)}},exports.renderFile=function(path,options,fn){var key=path+":string";"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)}},exports.__express=exports.renderFile}),require.register("lexer.js",function(module,exports,require){var utils=require("./utils"),parseJSExpression=require("character-parser").parseMax,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=!1};Lexer.prototype={tok:function(type,val){return{type:type,line:this.lineno,val:val}},consume:function(len){this.input=this.input.substr(len)},scan:function(regexp,type){var captures;if(captures=regexp.exec(this.input))return this.consume(captures[0].length),this.tok(type,captures[1])},defer:function(tok){this.deferredTokens.push(tok)},lookahead:function(n){var fetch=n-this.stash.length;while(fetch-->0)this.stash.push(this.next());return this.stash[--n]},bracketExpression:function(skip){skip=skip||0;var start=this.input[skip];if(start!="("&&start!="{"&&start!="[")throw new Error("unrecognized start character");var end={"(":")","{":"}","[":"]"}[start],range=parseJSExpression(this.input,{start:skip+1});if(this.input[range.end]!==end)throw new Error("start character "+start+" does not match end character "+this.input[range.end]);return range},stashed:function(){return this.stash.length&&this.stash.shift()},deferred:function(){return this.deferredTokens.length&&this.deferredTokens.shift()},eos:function(){if(this.input.length)return;return this.indentStack.length?(this.indentStack.shift(),this.tok("outdent")):this.tok("eos")},blank:function(){var captures;if(captures=/^\n *\n/.exec(this.input))return this.consume(captures[0].length-1),++this.lineno,this.pipeless?this.tok("text",""):this.next()},comment:function(){var captures;if(captures=/^ *\/\/(-)?([^\n]*)/.exec(this.input)){this.consume(captures[0].length);var tok=this.tok("comment",captures[2]);return tok.buffer="-"!=captures[1],tok}},interpolation:function(){if(/^#\{/.test(this.input)){var match;try{match=this.bracketExpression(1)}catch(ex){return}return this.consume(match.end+1),this.tok("interpolation",match.src)}},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);return tok.selfClosing=!!captures[2],tok}},filter:function(){return this.scan(/^:(\w+)/,"filter")},doctype:function(){return this.scan(/^(?:!!!|doctype) *([^\n]+)?/,"doctype")},id:function(){return this.scan(/^#([\w-]+)/,"id")},className:function(){return this.scan(/^\.([\w-]+)/,"class")},text:function(){return this.scan(/^(?:\| ?| ?)?([^\n]+)/,"text")},"extends":function(){return this.scan(/^extends? +([^\n]+)/,"extends")},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);return tok.mode=mode,tok}},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);return tok.mode=mode,tok}},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);return tok.mode=mode,tok}},yield:function(){return this.scan(/^yield */,"yield")},include:function(){return this.scan(/^include +([^\n]+)/,"include")},"case":function(){return this.scan(/^case +([^\n]+)/,"case")},when:function(){return this.scan(/^when +([^:\n]+)/,"when")},"default":function(){return this.scan(/^default */,"default")},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:function(){var captures;if(captures=/^\+([-\w]+)/.exec(this.input)){this.consume(captures[0].length);var tok=this.tok("call",captures[1]);if(captures=/^ *\(/.exec(this.input))try{var range=this.bracketExpression(captures[0].length-1);/^ *[-\w]+ *=/.test(range.src)||(this.consume(range.end+1),tok.args=range.src)}catch(ex){}return tok}},mixin:function(){var captures;if(captures=/^mixin +([-\w]+)(?: *\((.*)\))?/.exec(this.input)){this.consume(captures[0].length);var tok=this.tok("mixin",captures[1]);return tok.args=captures[2],tok}},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"}return this.tok("code",js)}},"while":function(){var captures;if(captures=/^while +([^\n]+)/.exec(this.input))return this.consume(captures[0].length),this.tok("code","while ("+captures[1]+")")},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]);return tok.key=captures[2]||"$index",tok.code=captures[3],tok}},code:function(){var captures;if(captures=/^(!?=|-)[ \t]*([^\n]+)/.exec(this.input)){this.consume(captures[0].length);var flags=captures[1];captures[1]=captures[2];var tok=this.tok("code",captures[1]);return tok.escape=flags.charAt(0)==="=",tok.buffer=flags.charAt(0)==="="||flags.charAt(1)==="=",tok}},attrs:function(){if("("==this.input.charAt(0)){var index=this.bracketExpression().end,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(_,escape,expr){if(escape)return _;try{var range=parseJSExpression(expr);return expr[range.end]!=="}"?_.substr(0,2)+interpolate(_.substr(2)):quote+" + ("+range.src+") + "+quote+interpolate(expr.substr(range.end+1))}catch(ex){return _.substr(0,2)+interpolate(_.substr(2))}})}this.consume(index+1),tok.attrs={},tok.escaped={};function parse(c){var real=c;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?!0: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"(":("val"==state()||"expr"==state())&&states.push("expr"),val+=c;break;case")":("expr"==state()||"val"==state())&&states.pop(),val+=c;break;case"{":"val"==state()&&states.push("object"),val+=c;break;case"}":"object"==state()&&states.pop(),val+=c;break;case"[":"val"==state()&&states.push("array"),val+=c;break;case"]":"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":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;iindents)this.stash.push(this.tok("outdent")),this.indentStack.shift();tok=this.stash.pop()}else indents&&indents!=this.indentStack[0]?(this.indentStack.unshift(indents),tok=this.tok("indent",indents)):tok=this.tok("newline");return tok}},pipelessText:function(){if(this.pipeless){if("\n"==this.input[0])return;var i=this.input.indexOf("\n");-1==i&&(i=this.input.length);var str=this.input.substr(0,i);return this.consume(str.length),this.tok("text",str)}},colon:function(){return this.scan(/^: */,":")},advance:function(){return this.stashed()||this.next()},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()}}}),require.register("nodes/attrs.js",function(module,exports,require){var Node=require("./node"),Block=require("./block"),Attrs=module.exports=function Attrs(){this.attrs=[]};Attrs.prototype=new Node,Attrs.prototype.constructor=Attrs,Attrs.prototype.setAttribute=function(name,val,escaped){return this.attrs.push({name:name,val:val,escaped:escaped}),this},Attrs.prototype.removeAttribute=function(name){for(var i=0,len=this.attrs.length;i/g,">").replace(/"/g,""")},exports.rethrow=function rethrow(err,filename,lineno){if(!filename)throw err;if(typeof window!="undefined")throw err;var context=3,str=require("fs").readFileSync(filename,"utf8"),lines=str.split("\n"),start=Math.max(lineno-context,0),end=Math.min(lines.length,lineno+context),context=lines.slice(start,end).map(function(line,i){var curr=i+start+1;return(curr==lineno?" > ":" ")+curr+"| "+line}).join("\n");throw err.path=filename,err.message=(filename||"Jade")+":"+lineno+"\n"+context+"\n\n"+err.message,err}}),require.register("self-closing.js",function(module,exports,require){module.exports=["meta","img","link","input","source","area","base","col","br","hr"]}),require.register("utils.js",function(module,exports,require){exports.merge=function(a,b){for(var key in b)a[key]=b[key];return a}}),require("jade")}(); \ No newline at end of file +(function(e){if("function"==typeof bootstrap)bootstrap("jade",e);else if("object"==typeof exports)module.exports=e();else if("function"==typeof define&&define.amd)define(e);else if("undefined"!=typeof ses){if(!ses.ok())return;ses.makeJade=e}else"undefined"!=typeof window?window.jade=e():global.jade=e()})(function(){var define,ses,bootstrap,module,exports;return function(e,t,n){function i(n,s){if(!t[n]){if(!e[n]){var o=typeof require=="function"&&require;if(!s&&o)return o(n,!0);if(r)return r(n,!0);throw new Error("Cannot find module '"+n+"'")}var u=t[n]={exports:{}};e[n][0].call(u.exports,function(t){var r=e[n][1][t];return i(r?r:t)},u,u.exports)}return t[n].exports}var r=typeof require=="function"&&require;for(var s=0;s/g,">").replace(/"/g,""")};exports.rethrow=function rethrow(err,filename,lineno){if(!filename)throw err;if(typeof window!="undefined")throw err;var context=3,str=require("fs").readFileSync(filename,"utf8"),lines=str.split("\n"),start=Math.max(lineno-context,0),end=Math.min(lines.length,lineno+context);var context=lines.slice(start,end).map(function(line,i){var curr=i+start+1;return(curr==lineno?" > ":" ")+curr+"| "+line}).join("\n");err.path=filename;err.message=(filename||"Jade")+":"+lineno+"\n"+context+"\n\n"+err.message;throw err}},{fs:1}],3:[function(require,module,exports){module.exports=["meta","img","link","input","source","area","base","col","br","hr"]},{}],4:[function(require,module,exports){module.exports={5:"","default":"",xml:'',transitional:'',strict:'',frameset:'',1.1:'',basic:'',mobile:''}},{}],5:[function(require,module,exports){exports.merge=function(a,b){for(var key in b)a[key]=b[key];return a}},{}],6:[function(require,module,exports){var Parser=require("./parser"),Lexer=require("./lexer"),Compiler=require("./compiler"),runtime=require("./runtime"),fs=require("fs");exports.version="0.30.0";exports.selfClosing=require("./self-closing");exports.doctypes=require("./doctypes");exports.filters=require("./filters");exports.utils=require("./utils");exports.Compiler=Compiler;exports.Parser=Parser;exports.Lexer=Lexer;exports.nodes=require("./nodes");exports.runtime=runtime;exports.cache={};function parse(str,options){try{var parser=new Parser(str,options.filename,options);var compiler=new(options.compiler||Compiler)(parser.parse(),options),js=compiler.compile();if(options.debug){console.error("\nCompiled Function:\n\n%s",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)}}function stripBOM(str){return 65279==str.charCodeAt(0)?str.substring(1):str}exports.compile=function(str,options){var options=options||{},filename=options.filename?JSON.stringify(options.filename):"undefined",fn;str=stripBOM(String(str));if(options.compileDebug!==false){fn=["jade.debug = [{ lineno: 1, filename: "+filename+" }];","try {",parse(str,options),"} catch (err) {"," jade.rethrow(err, jade.debug[0].filename, jade.debug[0].lineno);","}"].join("\n")}else{fn=parse(str,options)}if(options.client)return new Function("locals",fn);fn=new Function("locals, jade",fn);return function(locals){return fn(locals,Object.create(runtime))}};exports.render=function(str,options,fn){if("function"==typeof options){fn=options,options={}}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)}};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)}};exports.__express=exports.renderFile},{fs:1,"./parser":7,"./lexer":8,"./compiler":9,"./runtime":2,"./self-closing":3,"./doctypes":4,"./utils":5,"./filters":10,"./nodes":11}],10:[function(require,module,exports){module.exports=filter;function filter(name,str,options){if(typeof filter[name]==="function"){var res=filter[name](str,options)}else{throw new Error('unknown filter ":'+name+'"')}return res}filter.exists=function(name,str,options){return typeof filter[name]==="function"}},{}],12:[function(require,module,exports){var process=module.exports={};process.nextTick=function(){var canSetImmediate=typeof window!=="undefined"&&window.setImmediate;var canPost=typeof window!=="undefined"&&window.postMessage&&window.addEventListener;if(canSetImmediate){return function(f){return window.setImmediate(f)}}if(canPost){var queue=[];window.addEventListener("message",function(ev){if(ev.source===window&&ev.data==="process-tick"){ev.stopPropagation();if(queue.length>0){var fn=queue.shift();fn()}}},true);return function nextTick(fn){queue.push(fn);window.postMessage("process-tick","*")}}return function nextTick(fn){setTimeout(fn,0)}}();process.title="browser";process.browser=true;process.env={};process.argv=[];process.binding=function(name){throw new Error("process.binding is not supported")};process.cwd=function(){return"/"};process.chdir=function(dir){throw new Error("process.chdir is not supported")}},{}],13:[function(require,module,exports){(function(process){function filter(xs,fn){var res=[];for(var i=0;i=0;i--){var last=parts[i];if(last=="."){parts.splice(i,1)}else if(last===".."){parts.splice(i,1);up++}else if(up){parts.splice(i,1);up--}}if(allowAboveRoot){for(;up--;up){parts.unshift("..")}}return parts}var splitPathRe=/^(.+\/(?!$)|\/)?((?:.+?)?(\.[^.]*)?)$/;exports.resolve=function(){var resolvedPath="",resolvedAbsolute=false;for(var i=arguments.length;i>=-1&&!resolvedAbsolute;i--){var path=i>=0?arguments[i]:process.cwd();if(typeof path!=="string"||!path){continue}resolvedPath=path+"/"+resolvedPath;resolvedAbsolute=path.charAt(0)==="/"}resolvedPath=normalizeArray(filter(resolvedPath.split("/"),function(p){return!!p}),!resolvedAbsolute).join("/");return(resolvedAbsolute?"/":"")+resolvedPath||"."};exports.normalize=function(path){var isAbsolute=path.charAt(0)==="/",trailingSlash=path.slice(-1)==="/";path=normalizeArray(filter(path.split("/"),function(p){return!!p}),!isAbsolute).join("/");if(!path&&!isAbsolute){path="."}if(path&&trailingSlash){path+="/"}return(isAbsolute?"/":"")+path};exports.join=function(){var paths=Array.prototype.slice.call(arguments,0);return exports.normalize(filter(paths,function(p,index){return p&&typeof p==="string"}).join("/"))};exports.dirname=function(path){var dir=splitPathRe.exec(path)[1]||"";var isWindows=false;if(!dir){return"."}else if(dir.length===1||isWindows&&dir.length<=3&&dir.charAt(1)===":"){return dir}else{return dir.substring(0,dir.length-1)}};exports.basename=function(path,ext){var f=splitPathRe.exec(path)[2]||"";if(ext&&f.substr(-1*ext.length)===ext){f=f.substr(0,f.length-ext.length)}return f};exports.extname=function(path){return splitPathRe.exec(path)[3]||""};exports.relative=function(from,to){from=exports.resolve(from).substr(1);to=exports.resolve(to).substr(1);function trim(arr){var start=0;for(;start=0;end--){if(arr[end]!=="")break}if(start>end)return[];return arr.slice(start,end-start+1)}var fromParts=trim(from.split("/"));var toParts=trim(to.split("/"));var length=Math.min(fromParts.length,toParts.length);var samePartsLength=length;for(var i=0;i";this.terse=this.doctype.toLowerCase()=="";this.xml=0==this.doctype.indexOf("1&&!escape&&block.nodes[0].isText&&block.nodes[1].isText)this.prettyIndent(1,true);for(var i=0;i0&&!escape&&block.nodes[i].isText&&block.nodes[i-1].isText)this.prettyIndent(1,false);this.visit(block.nodes[i]);if(block.nodes[i+1]&&block.nodes[i].isText&&block.nodes[i+1].isText)this.buffer("\n")}},visitDoctype:function(doctype){if(doctype&&(doctype.val||!this.doctype)){this.setDoctype(doctype.val||"default")}if(this.doctype)this.buffer(this.doctype);this.hasCompiledDoctype=true},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("jade.indent.push('"+Array(this.indents+1).join(" ")+"');");if(block||attrs.length){this.buf.push(name+".call({");if(block){this.buf.push("block: function(){");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: jade.merge({"+val.buf+"}, attributes), escaped: jade.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("jade.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("};")}},visitTag:function(tag){this.indents++;var name=tag.name,pp=this.pp,self=this;function bufferName(){if(tag.buffer)self.bufferExpression(name);else self.buffer(name)}if(!this.hasCompiledTag){if(!this.hasCompiledDoctype&&"html"==name){this.visitDoctype()}this.hasCompiledTag=true}if(pp&&!tag.isInline())this.prettyIndent(0,true);if((~selfClosing.indexOf(name)||tag.selfClosing)&&!this.xml){this.buffer("<");bufferName();this.visitAttributes(tag.attrs);this.terse?this.buffer(">"):this.buffer("/>")}else{if(tag.attrs.length){this.buffer("<");bufferName();if(tag.attrs.length)this.visitAttributes(tag.attrs);this.buffer(">")}else{this.buffer("<");bufferName();this.buffer(">")}if(tag.code)this.visitCode(tag.code);this.escape="pre"==tag.name;this.visit(tag.block);if(pp&&!tag.isInline()&&"pre"!=tag.name&&!tag.canInline())this.prettyIndent(0,true);this.buffer("")}this.indents--},visitFilter:function(filter){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(filters(filter.name,text,filter.attrs),true)},visitText:function(text){this.buffer(text.val,true)},visitComment:function(comment){if(!comment.buffer)return;if(this.pp)this.prettyIndent(1,true);this.buffer("")},visitBlockComment:function(comment){if(!comment.buffer)return;if(0==comment.val.trim().indexOf("if")){this.buffer("")}else{this.buffer("")}},visitCode:function(code){if(code.buffer){var val=code.val.trimLeft();val="null == (jade.interp = "+val+') ? "" : jade.interp';if(code.escape)val="jade.escape("+val+")";this.bufferExpression(val)}else{this.buf.push(code.val)}if(code.block){if(!code.buffer)this.buf.push("{");this.visit(code.block);if(!code.buffer)this.buf.push("}")}},visitEach:function(each){this.buf.push(""+"// iterate "+each.obj+"\n"+";(function(){\n"+" var $$obj = "+each.obj+";\n"+" if ('number' == typeof $$obj.length) {\n");if(each.alternative){this.buf.push(" if ($$obj.length) {")}this.buf.push(""+" for (var "+each.key+" = 0, $$l = $$obj.length; "+each.key+" < $$l; "+each.key+"++) {\n"+" var "+each.val+" = $$obj["+each.key+"];\n");this.visit(each.block);this.buf.push(" }\n");if(each.alternative){this.buf.push(" } else {");this.visit(each.alternative);this.buf.push(" }")}this.buf.push(""+" } else {\n"+" var $$l = 0;\n"+" for (var "+each.key+" in $$obj) {\n"+" $$l++;"+" var "+each.val+" = $$obj["+each.key+"];\n");this.visit(each.block);this.buf.push(" }\n");if(each.alternative){this.buf.push(" if ($$l === 0) {");this.visit(each.alternative);this.buf.push(" }")}this.buf.push(" }\n}).call(this);\n")},visitAttributes:function(attrs){var val=this.attrs(attrs);if(val.inherits){this.bufferExpression("jade.attrs(jade.merge({ "+val.buf+" }, attributes), jade.merge("+val.escaped+", escaped, true))")}else if(val.constant){eval("var buf={"+val.buf+"};");this.buffer(runtime.attrs(buf,JSON.parse(val.escaped)))}else{this.bufferExpression("jade.attrs({ "+val.buf+" }, "+val.escaped+")")}},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(", "),escaped:JSON.stringify(escaped),inherits:inherits,constant:constant}}};function isConstant(val){if(/^ *("([^"\\]*(\\.[^"\\]*)*)"|'([^'\\]*(\\.[^'\\]*)*)'|true|false|null|undefined) *$/i.test(val))return true;if(!isNaN(Number(val)))return true;var matches;if(matches=/^ *\[(.*)\] *$/.exec(val))return matches[1].split(",").every(isConstant);return false}})()},{"./doctypes":4,"./self-closing":3,"./runtime":2,"./filters":10,"./utils":5,"./nodes":11,"character-parser":27}],8:[function(require,module,exports){var utils=require("./utils");var parseJSExpression=require("character-parser").parseMax;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={tok:function(type,val){return{type:type,line:this.lineno,val:val}},consume:function(len){this.input=this.input.substr(len)},scan:function(regexp,type){var captures;if(captures=regexp.exec(this.input)){this.consume(captures[0].length);return this.tok(type,captures[1])}},defer:function(tok){this.deferredTokens.push(tok)},lookahead:function(n){var fetch=n-this.stash.length;while(fetch-->0)this.stash.push(this.next());return this.stash[--n]},bracketExpression:function(skip){skip=skip||0;var start=this.input[skip];if(start!="("&&start!="{"&&start!="[")throw new Error("unrecognized start character");var end={"(":")","{":"}","[":"]"}[start];var range=parseJSExpression(this.input,{start:skip+1});if(this.input[range.end]!==end)throw new Error("start character "+start+" does not match end character "+this.input[range.end]);return range},stashed:function(){return this.stash.length&&this.stash.shift()},deferred:function(){return this.deferredTokens.length&&this.deferredTokens.shift()},eos:function(){if(this.input.length)return;if(this.indentStack.length){this.indentStack.shift();return this.tok("outdent")}else{return this.tok("eos")}},blank:function(){var captures;if(captures=/^\n *\n/.exec(this.input)){this.consume(captures[0].length-1);++this.lineno;if(this.pipeless)return this.tok("text","");return this.next()}},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}},interpolation:function(){if(/^#\{/.test(this.input)){var match;try{match=this.bracketExpression(1)}catch(ex){return}this.consume(match.end+1);return this.tok("interpolation",match.src)}},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:function(){return this.scan(/^:(\w+)/,"filter")},doctype:function(){return this.scan(/^(?:!!!|doctype) *([^\n]+)?/,"doctype")},id:function(){return this.scan(/^#([\w-]+)/,"id")},className:function(){return this.scan(/^\.([\w-]+)/,"class")},text:function(){return this.scan(/^(?:\| ?| ?)?([^\n]+)/,"text")},"extends":function(){return this.scan(/^extends? +([^\n]+)/,"extends")},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}},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: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:function(){return this.scan(/^yield */,"yield")},include:function(){return this.scan(/^include +([^\n]+)/,"include")},"case":function(){return this.scan(/^case +([^\n]+)/,"case")},when:function(){return this.scan(/^when +([^:\n]+)/,"when")},"default":function(){return this.scan(/^default */,"default")},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:function(){var captures;if(captures=/^\+([-\w]+)/.exec(this.input)){this.consume(captures[0].length);var tok=this.tok("call",captures[1]);if(captures=/^ *\(/.exec(this.input)){try{var range=this.bracketExpression(captures[0].length-1);if(!/^ *[-\w]+ *=/.test(range.src)){this.consume(range.end+1);tok.args=range.src}}catch(ex){}}return tok}},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: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":function(){var captures;if(captures=/^while +([^\n]+)/.exec(this.input)){this.consume(captures[0].length);return this.tok("code","while ("+captures[1]+")")}},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:function(){var captures;if(captures=/^(!?=|-)[ \t]*([^\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.charAt(0)==="=";tok.buffer=flags.charAt(0)==="="||flags.charAt(1)==="=";return tok}},attrs:function(){if("("==this.input.charAt(0)){var index=this.bracketExpression().end,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(_,escape,expr){if(escape)return _;try{var range=parseJSExpression(expr);if(expr[range.end]!=="}")return _.substr(0,2)+interpolate(_.substr(2));return quote+" + ("+range.src+") + "+quote+interpolate(expr.substr(range.end+1))}catch(ex){return _.substr(0,2)+interpolate(_.substr(2))}})}this.consume(index+1);tok.attrs={};tok.escaped={};function parse(c){var real=c;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;iindents){this.stash.push(this.tok("outdent"));this.indentStack.shift()}tok=this.stash.pop()}else if(indents&&indents!=this.indentStack[0]){this.indentStack.unshift(indents);tok=this.tok("indent",indents)}else{tok=this.tok("newline")}return tok}},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(/^: */,":")},advance:function(){return this.stashed()||this.next()},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()}}},{"./utils":5,"character-parser":27}],27:[function(require,module,exports){exports=module.exports=parse;exports.parse=parse;function parse(src,state,options){options=options||{};state=state||exports.defaultState();var start=options.start||0;var end=options.end||src.length;var index=start;while(index=0&&state.curlyDepth>=0&&state.squareDepth>=0){if(index>=src.length){throw new Error("The end of the string was reached with no closing bracket found.")}exports.parseChar(src[index++],state)}var end=index-1;return{start:start,end:end,src:src.substring(start,end)}}exports.parseUntil=parseUntil;function parseUntil(src,delimiter,options){options=options||{};var includeLineComment=options.includeLineComment||false;var start=options.start||0;var index=start;var state=exports.defaultState();while(state.singleQuote||state.doubleQuote||state.regexp||state.blockComment||!includeLineComment&&state.lineComment||!startsWith(src,delimiter,index)){exports.parseChar(src[index++],state)}var end=index;return{start:start,end:end,src:src.substring(start,end)}}exports.parseChar=parseChar;function parseChar(character,state){if(character.length!==1)throw new Error("Character must be a string of length 1");state=state||defaultState();var wasComment=state.blockComment||state.lineComment;var lastChar=state.history?state.history[0]:"";if(state.lineComment){if(character==="\n"){state.lineComment=false}}else if(state.blockComment){if(state.lastChar==="*"&&character==="/"){state.blockComment=false}}else if(state.singleQuote){if(character==="'"&&!state.escaped){state.singleQuote=false}else if(character==="\\"&&!state.escaped){state.escaped=true}else{state.escaped=false}}else if(state.doubleQuote){if(character==='"'&&!state.escaped){state.doubleQuote=false}else if(character==="\\"&&!state.escaped){state.escaped=true}else{state.escaped=false}}else if(state.regexp){if(character==="/"&&!state.escaped){state.regexp=false}else if(character==="\\"&&!state.escaped){state.escaped=true}else{state.escaped=false}}else if(lastChar==="/"&&character==="/"){history=history.substr(1);state.lineComment=true}else if(lastChar==="/"&&character==="*"){history=history.substr(1);state.blockComment=true}else if(character==="/"){var history=state.history.replace(/^\s*/,"");if(history[0]===")"){}else if(history[0]==="}"){}else if(isPunctuator(history[0])){state.regexp=true}else if(/^\w+\b/.test(history)&&isKeyword(/^\w+\b/.exec(history)[0])){state.regexp=true}else{}}else if(character==="'"){state.singleQuote=true}else if(character==='"'){state.doubleQuote=true}else if(character==="("){state.roundDepth++}else if(character===")"){state.roundDepth--}else if(character==="{"){state.curlyDepth++}else if(character==="}"){state.curlyDepth--}else if(character==="["){state.squareDepth++}else if(character==="]"){state.squareDepth--}if(!state.blockComment&&!state.lineComment&&!wasComment)state.history=character+state.history;return state}exports.defaultState=defaultState;function defaultState(){return{lineComment:false,blockComment:false,singleQuote:false,doubleQuote:false,regexp:false,escaped:false,roundDepth:0,curlyDepth:0,squareDepth:0,history:""}}function startsWith(str,start,i){return str.substr(i||0,start.length)===start}function isPunctuator(c){var code=c.charCodeAt(0);switch(code){case 46:case 40:case 41:case 59:case 44:case 123:case 125:case 91:case 93:case 58:case 63:case 126:case 37:case 38:case 42:case 43:case 45:case 47:case 60:case 62:case 94:case 124:case 33:case 61:return true;default:return false}}function isKeyword(id){return id==="if"||id==="in"||id==="do"||id==="var"||id==="for"||id==="new"||id==="try"||id==="let"||id==="this"||id==="else"||id==="case"||id==="void"||id==="with"||id==="enum"||id==="while"||id==="break"||id==="catch"||id==="throw"||id==="const"||id==="yield"||id==="class"||id==="super"||id==="return"||id==="typeof"||id==="delete"||id==="switch"||id==="export"||id==="import"||id==="default"||id==="finally"||id==="extends"||id==="function"||id==="continue"||id==="debugger"||id==="package"||id==="private"||id==="interface"||id==="instanceof"||id==="implements"||id==="protected"||id==="public"||id==="static"||id==="yield"||id==="let"}},{}],15:[function(require,module,exports){var Attrs=require("./attrs"),Block=require("./block"),inlineTags=require("../inline-tags");var Tag=module.exports=function Tag(name,block){this.name=name;this.attrs=[];this.block=block||new Block};Tag.prototype.__proto__=Attrs.prototype;Tag.prototype.clone=function(){var clone=new Tag(this.name,this.block.clone());clone.line=this.line;clone.attrs=this.attrs;clone.textOnly=this.textOnly;return clone};Tag.prototype.isInline=function(){return~inlineTags.indexOf(this.name)};Tag.prototype.canInline=function(){var nodes=this.block.nodes;function isInline(node){if(node.isBlock)return node.nodes.every(isInline);return node.isText||node.isInline&&node.isInline()}if(!nodes.length)return true;if(1==nodes.length)return isInline(nodes[0]);if(this.block.nodes.every(isInline)){for(var i=1,len=nodes.length;i + * MIT Licensed + */ + +module.exports = filter; +function filter(name, str, options) { + if (typeof filter[name] === 'function') { + var res = filter[name](str, options); + } else { + throw new Error('unknown filter ":' + name + '"'); + } + return res; +} +filter.exists = function (name, str, options) { + return typeof filter[name] === 'function'; +}; diff --git a/lib/jade.js b/lib/jade.js index c64770594..ec9489f8d 100644 --- a/lib/jade.js +++ b/lib/jade.js @@ -12,9 +12,7 @@ var Parser = require('./parser') , Lexer = require('./lexer') , Compiler = require('./compiler') , runtime = require('./runtime') -// if node , fs = require('fs'); -// end /** * Library version. diff --git a/package.json b/package.json index aa6e7b2e5..b35a83d6b 100644 --- a/package.json +++ b/package.json @@ -24,7 +24,8 @@ "uubench": "*", "should": "*", "less": "*", - "uglify-js": "*" + "uglify-js": "*", + "browserify": "~2.13.2" }, "component": { "scripts": { @@ -33,6 +34,16 @@ }, "scripts": { "test": "mocha -R spec", - "prepublish": "npm prune" + "prepublish": "npm prune", + "build": "npm run compile && npm run minify", + "compile": "npm run compile-full && npm run compile-runtime", + "compile-full": "browserify ./lib/jade.js --standalone jade -x ./node_modules/transformers > jade.js", + "compile-runtime": "browserify ./lib/runtime.js --standalone jade > runtime.js", + "minify": "npm run minify-full && npm run minify-runtime", + "minify-full": "uglifyjs < jade.js > jade.min.js", + "minify-runtime": "uglifyjs < runtime.js > runtime.min.js" + }, + "browser": { + "./lib/filters.js": "./lib/filters-client.js" } } diff --git a/runtime.js b/runtime.js index 0f5490778..0f3a025dc 100644 --- a/runtime.js +++ b/runtime.js @@ -1,5 +1,6 @@ +(function(e){if("function"==typeof bootstrap)bootstrap("jade",e);else if("object"==typeof exports)module.exports=e();else if("function"==typeof define&&define.amd)define(e);else if("undefined"!=typeof ses){if(!ses.ok())return;ses.makeJade=e}else"undefined"!=typeof window?window.jade=e():global.jade=e()})(function(){var define,ses,bootstrap,module,exports; +return (function(e,t,n){function i(n,s){if(!t[n]){if(!e[n]){var o=typeof require=="function"&&require;if(!s&&o)return o(n,!0);if(r)return r(n,!0);throw new Error("Cannot find module '"+n+"'")}var u=t[n]={exports:{}};e[n][0].call(u.exports,function(t){var r=e[n][1][t];return i(r?r:t)},u,u.exports)}return t[n].exports}var r=typeof require=="function"&&require;for(var s=0;s @@ -133,7 +134,7 @@ exports.attrs = function attrs(obj, escaped){ exports.escape = function escape(html){ return String(html) - .replace(/&(?!(\w+|\#\d+);)/g, '&') + .replace(/&/g, '&') .replace(//g, '>') .replace(/"/g, '"'); @@ -151,6 +152,7 @@ exports.escape = function escape(html){ exports.rethrow = function rethrow(err, filename, lineno){ if (!filename) throw err; + if (typeof window != 'undefined') throw err; var context = 3 , str = require('fs').readFileSync(filename, 'utf8') @@ -174,6 +176,9 @@ exports.rethrow = function rethrow(err, filename, lineno){ throw err; }; - return exports; +},{"fs":2}],2:[function(require,module,exports){ +// nothing to see here... no file methods for the browser -})({}); +},{}]},{},[1])(1) +}); +; \ No newline at end of file diff --git a/runtime.min.js b/runtime.min.js index 1714efb00..9b5fd6c04 100644 --- a/runtime.min.js +++ b/runtime.min.js @@ -1 +1 @@ -jade=function(exports){Array.isArray||(Array.isArray=function(arr){return"[object Array]"==Object.prototype.toString.call(arr)}),Object.keys||(Object.keys=function(obj){var arr=[];for(var key in obj)obj.hasOwnProperty(key)&&arr.push(key);return arr}),exports.merge=function merge(a,b){var ac=a["class"],bc=b["class"];if(ac||bc)ac=ac||[],bc=bc||[],Array.isArray(ac)||(ac=[ac]),Array.isArray(bc)||(bc=[bc]),ac=ac.filter(nulls),bc=bc.filter(nulls),a["class"]=ac.concat(bc).join(" ");for(var key in b)key!="class"&&(a[key]=b[key]);return a};function nulls(val){return val!=null}return exports.attrs=function attrs(obj,escaped){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/g,">").replace(/"/g,""")},exports.rethrow=function rethrow(err,filename,lineno){if(!filename)throw err;var context=3,str=require("fs").readFileSync(filename,"utf8"),lines=str.split("\n"),start=Math.max(lineno-context,0),end=Math.min(lines.length,lineno+context),context=lines.slice(start,end).map(function(line,i){var curr=i+start+1;return(curr==lineno?" > ":" ")+curr+"| "+line}).join("\n");throw err.path=filename,err.message=(filename||"Jade")+":"+lineno+"\n"+context+"\n\n"+err.message,err},exports}({}); \ No newline at end of file +(function(e){if("function"==typeof bootstrap)bootstrap("jade",e);else if("object"==typeof exports)module.exports=e();else if("function"==typeof define&&define.amd)define(e);else if("undefined"!=typeof ses){if(!ses.ok())return;ses.makeJade=e}else"undefined"!=typeof window?window.jade=e():global.jade=e()})(function(){var define,ses,bootstrap,module,exports;return function(e,t,n){function i(n,s){if(!t[n]){if(!e[n]){var o=typeof require=="function"&&require;if(!s&&o)return o(n,!0);if(r)return r(n,!0);throw new Error("Cannot find module '"+n+"'")}var u=t[n]={exports:{}};e[n][0].call(u.exports,function(t){var r=e[n][1][t];return i(r?r:t)},u,u.exports)}return t[n].exports}var r=typeof require=="function"&&require;for(var s=0;s/g,">").replace(/"/g,""")};exports.rethrow=function rethrow(err,filename,lineno){if(!filename)throw err;if(typeof window!="undefined")throw err;var context=3,str=require("fs").readFileSync(filename,"utf8"),lines=str.split("\n"),start=Math.max(lineno-context,0),end=Math.min(lines.length,lineno+context);var context=lines.slice(start,end).map(function(line,i){var curr=i+start+1;return(curr==lineno?" > ":" ")+curr+"| "+line}).join("\n");err.path=filename;err.message=(filename||"Jade")+":"+lineno+"\n"+context+"\n\n"+err.message;throw err}},{fs:2}],2:[function(require,module,exports){},{}]},{},[1])(1)}); \ No newline at end of file diff --git a/support/compile.js b/support/compile.js deleted file mode 100644 index 827263c13..000000000 --- a/support/compile.js +++ /dev/null @@ -1,175 +0,0 @@ -/** - * Module dependencies. - */ - -var fs = require('fs'); - -/** - * Arguments. - */ - -var args = process.argv.slice(2) - , pending = args.length - , files = {}; - -console.log(''); - -// parse arguments - -args.forEach(function(file){ - var mod = file.replace('lib/', ''); - fs.readFile(file, 'utf8', function(err, js){ - if (err) throw err; - console.log(' \033[90mcompile : \033[0m\033[36m%s\033[0m', file); - files[file] = parse(js); - --pending || compile(); - }); -}); - -/** - * Parse the given `js`. - */ - -function parse(js) { - return parseInheritance(parseConditionals(js)); -} - -/** - * Parse __proto__. - */ - -function parseInheritance(js) { - return js - .replace(/^ *(\w+)\.prototype\.__proto__ * = *(\w+)\.prototype *;?/gm, function(_, child, parent){ - return child + '.prototype = new ' + parent + ';\n' - + child + '.prototype.constructor = '+ child + ';\n'; - }); -} - -/** - * Parse the given `js`, currently supporting: - * - * 'if' ['node' | 'browser'] - * 'end' - * - */ - -function parseConditionals(js) { - var lines = js.split('\n') - , len = lines.length - , buffer = true - , browser = false - , buf = [] - , line - , cond; - - for (var i = 0; i < len; ++i) { - line = lines[i]; - if (/^ *\/\/ *if *(node|browser)/gm.exec(line)) { - cond = RegExp.$1; - buffer = browser = 'browser' == cond; - } else if (/^ *\/\/ *end/.test(line)) { - buffer = true; - browser = false; - } else if (browser) { - buf.push(line.replace(/^( *)\/\//, '$1')); - } else if (buffer) { - buf.push(line); - } - } - - return buf.join('\n'); -} - -/** - * Compile the files. - */ - -function compile() { - var buf = ''; - buf += 'var jade = (function() {\n'; - buf += '\n// CommonJS require()\n\n'; - buf += browser.require + '\n\n'; - buf += 'require.modules = {};\n\n'; - buf += 'require.resolve = ' + browser.resolve + ';\n\n'; - buf += 'require.register = ' + browser.register + ';\n\n'; - buf += 'require.relative = ' + browser.relative + ';\n\n'; - args.forEach(function(file){ - var js = files[file]; - file = file.replace('lib/', ''); - buf += '\nrequire.register("' + file + '", function(module, exports, require){\n'; - buf += js; - buf += '\n}); // module: ' + file + '\n'; - }); - buf += '\nreturn require("jade");\n'; - buf += '})();\n'; - fs.writeFile('jade.js', buf, function(err){ - if (err) throw err; - console.log(' \033[90m create : \033[0m\033[36m%s\033[0m', 'jade.js'); - console.log(); - }); -} - -// refactored version of weepy's -// https://github.com/weepy/brequire/blob/master/browser/brequire.js - -var browser = { - - /** - * Require a module. - */ - - 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; - }, - - /** - * Resolve module path. - */ - - resolve: function(path){ - var orig = path - , reg = path + '.js' - , index = path + '/index.js'; - return require.modules[reg] && reg - || require.modules[index] && index - || orig; - }, - - /** - * Return relative 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('/')); - }; - }, - - /** - * Register a module. - */ - - register: function(path, fn){ - require.modules[path] = fn; - } -}; diff --git a/support/foot.js b/support/foot.js deleted file mode 100644 index 92f4251e5..000000000 --- a/support/foot.js +++ /dev/null @@ -1,4 +0,0 @@ - - return exports; - -})({}); \ No newline at end of file diff --git a/support/head.js b/support/head.js deleted file mode 100644 index 7af13101c..000000000 --- a/support/head.js +++ /dev/null @@ -1,2 +0,0 @@ - -jade = (function(exports){ \ No newline at end of file diff --git a/test/browser/index.html b/test/browser/index.html new file mode 100644 index 000000000..a4c4f5d94 --- /dev/null +++ b/test/browser/index.html @@ -0,0 +1,10 @@ +
\ No newline at end of file diff --git a/test/browser/index.jade b/test/browser/index.jade new file mode 100644 index 000000000..2ec6f00ea --- /dev/null +++ b/test/browser/index.jade @@ -0,0 +1,20 @@ +!!! 5 +html + head + body + textarea#input(placeholder='write jade here', style='width: 100%; min-height: 400px;'). + p + author + != name + pre(style='background: #ECECEC;width: 100%; min-height: 400px;') + code#output + script(src='../../jade.js') + script + var input = document.getElementById('input'); + var output = document.getElementById('output'); + setInterval(function () { + jade.render(input.value, {name: 'Forbes Lindesay', pretty: true}, function (err, res) { + if (err) throw err; + output.textContent = res; + }) + }, 500) \ No newline at end of file