ignite-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From anovi...@apache.org
Subject [23/52] [partial] incubator-ignite git commit: # ignite-843 WIP.
Date Fri, 08 May 2015 11:36:48 GMT
http://git-wip-us.apache.org/repos/asf/incubator-ignite/blob/0ef79031/modules/webconfig/nodejs/node_modules/jade/jade.js
----------------------------------------------------------------------
diff --git a/modules/webconfig/nodejs/node_modules/jade/jade.js b/modules/webconfig/nodejs/node_modules/jade/jade.js
new file mode 100644
index 0000000..6b9f7f4
--- /dev/null
+++ b/modules/webconfig/nodejs/node_modules/jade/jade.js
@@ -0,0 +1,7602 @@
+!function(e){if("object"==typeof exports&&"undefined"!=typeof module)module.exports=e();else if("function"==typeof define&&define.amd)define([],e);else{var f;"undefined"!=typeof window?f=window:"undefined"!=typeof global?f=global:"undefined"!=typeof self&&(f=self),f.jade=e()}}(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){
+'use strict';
+
+var nodes = require('./nodes');
+var filters = require('./filters');
+var doctypes = require('./doctypes');
+var runtime = require('./runtime');
+var utils = require('./utils');
+var selfClosing = require('void-elements');
+var parseJSExpression = require('character-parser').parseMax;
+var constantinople = require('constantinople');
+
+function isConstant(src) {
+  return constantinople(src, {jade: runtime, 'jade_interp': undefined});
+}
+function toConstant(src) {
+  return constantinople.toConstant(src, {jade: runtime, 'jade_interp': undefined});
+}
+function errorAtNode(node, error) {
+  error.line = node.line;
+  error.filename = node.filename;
+  return error;
+}
+
+/**
+ * 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;
+  if (this.pp && typeof this.pp !== 'string') {
+    this.pp = '  ';
+  }
+  this.debug = false !== options.compileDebug;
+  this.indents = 0;
+  this.parentIndents = 0;
+  this.terse = false;
+  this.mixins = {};
+  this.dynamicMixins = false;
+  if (options.doctype) this.setDoctype(options.doctype);
+};
+
+/**
+ * Compiler prototype.
+ */
+
+Compiler.prototype = {
+
+  /**
+   * Compile parse tree to JavaScript.
+   *
+   * @api public
+   */
+
+  compile: function(){
+    this.buf = [];
+    if (this.pp) this.buf.push("var jade_indent = [];");
+    this.lastBufferedIdx = -1;
+    this.visit(this.node);
+    if (!this.dynamicMixins) {
+      // if there are no dynamic mixins we can remove any un-used mixins
+      var mixinNames = Object.keys(this.mixins);
+      for (var i = 0; i < mixinNames.length; i++) {
+        var mixin = this.mixins[mixinNames[i]];
+        if (!mixin.used) {
+          for (var x = 0; x < mixin.instances.length; x++) {
+            for (var y = mixin.instances[x].start; y < mixin.instances[x].end; y++) {
+              this.buf[y] = '';
+            }
+          }
+        }
+      }
+    }
+    return this.buf.join('\n');
+  },
+
+  /**
+   * Sets the default doctype `name`. Sets terse mode to `true` when
+   * html 5 is used, causing self-closing tags to end with ">" vs "/>",
+   * and boolean attributes are not mirrored.
+   *
+   * @param {string} name
+   * @api public
+   */
+
+  setDoctype: function(name){
+    this.doctype = doctypes[name.toLowerCase()] || '<!DOCTYPE ' + name + '>';
+    this.terse = this.doctype.toLowerCase() == '<!doctype html>';
+    this.xml = 0 == this.doctype.indexOf('<?xml');
+  },
+
+  /**
+   * Buffer the given `str` exactly as is or with interpolation
+   *
+   * @param {String} str
+   * @param {Boolean} interpolate
+   * @api public
+   */
+
+  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 {
+          var rest = match[3];
+          var range = parseJSExpression(rest);
+          var code = ('!' == match[2] ? '' : 'jade.escape') + "((jade_interp = " + range.src + ") == null ? '' : jade_interp)";
+          this.bufferExpression(code);
+          this.buffer(rest.substr(range.end + 1), true);
+          return;
+        }
+      }
+    }
+
+    str = utils.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;
+    }
+  },
+
+  /**
+   * Buffer the given `src` so it is evaluated at run time
+   *
+   * @param {String} src
+   * @api public
+   */
+
+  bufferExpression: function (src) {
+    if (isConstant(src)) {
+      return this.buffer(toConstant(src) + '', false)
+    }
+    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;
+    }
+  },
+
+  /**
+   * Buffer an indent based on the current `indent`
+   * property and an additional `offset`.
+   *
+   * @param {Number} offset
+   * @param {Boolean} newline
+   * @api public
+   */
+
+  prettyIndent: function(offset, newline){
+    offset = offset || 0;
+    newline = newline ? '\n' : '';
+    this.buffer(newline + Array(this.indents + offset).join(this.pp));
+    if (this.parentIndents)
+      this.buf.push("buf.push.apply(buf, jade_indent);");
+  },
+
+  /**
+   * Visit `node`.
+   *
+   * @param {Node} node
+   * @api public
+   */
+
+  visit: function(node){
+    var debug = this.debug;
+
+    if (debug) {
+      this.buf.push('jade_debug.unshift({ lineno: ' + node.line
+        + ', filename: ' + (node.filename
+          ? utils.stringify(node.filename)
+          : 'jade_debug[0].filename')
+        + ' });');
+    }
+
+    // Massive hack to fix our context
+    // stack for - else[ if] etc
+    if (false === node.debug && this.debug) {
+      this.buf.pop();
+      this.buf.pop();
+    }
+
+    this.visitNode(node);
+
+    if (debug) this.buf.push('jade_debug.shift();');
+  },
+
+  /**
+   * Visit `node`.
+   *
+   * @param {Node} node
+   * @api public
+   */
+
+  visitNode: function(node){
+    return this['visit' + node.type](node);
+  },
+
+  /**
+   * Visit case `node`.
+   *
+   * @param {Literal} node
+   * @api public
+   */
+
+  visitCase: function(node){
+    var _ = this.withinCase;
+    this.withinCase = true;
+    this.buf.push('switch (' + node.expr + '){');
+    this.visit(node.block);
+    this.buf.push('}');
+    this.withinCase = _;
+  },
+
+  /**
+   * Visit when `node`.
+   *
+   * @param {Literal} node
+   * @api public
+   */
+
+  visitWhen: function(node){
+    if ('default' == node.expr) {
+      this.buf.push('default:');
+    } else {
+      this.buf.push('case ' + node.expr + ':');
+    }
+    if (node.block) {
+      this.visit(node.block);
+      this.buf.push('  break;');
+    }
+  },
+
+  /**
+   * Visit literal `node`.
+   *
+   * @param {Literal} node
+   * @api public
+   */
+
+  visitLiteral: function(node){
+    this.buffer(node.str);
+  },
+
+  /**
+   * Visit all nodes in `block`.
+   *
+   * @param {Block} block
+   * @api public
+   */
+
+  visitBlock: function(block){
+    var len = block.nodes.length
+      , escape = this.escape
+      , pp = this.pp
+
+    // Pretty print multi-line text
+    if (pp && len > 1 && !escape && block.nodes[0].isText && block.nodes[1].isText)
+      this.prettyIndent(1, true);
+
+    for (var i = 0; i < len; ++i) {
+      // Pretty print text
+      if (pp && i > 0 && !escape && block.nodes[i].isText && block.nodes[i-1].isText)
+        this.prettyIndent(1, false);
+
+      this.visit(block.nodes[i]);
+      // Multiple text nodes are separated by newlines
+      if (block.nodes[i+1] && block.nodes[i].isText && block.nodes[i+1].isText)
+        this.buffer('\n');
+    }
+  },
+
+  /**
+   * Visit a mixin's `block` keyword.
+   *
+   * @param {MixinBlock} block
+   * @api public
+   */
+
+  visitMixinBlock: function(block){
+    if (this.pp) this.buf.push("jade_indent.push('" + Array(this.indents + 1).join(this.pp) + "');");
+    this.buf.push('block && block();');
+    if (this.pp) this.buf.push("jade_indent.pop();");
+  },
+
+  /**
+   * Visit `doctype`. Sets terse mode to `true` when html 5
+   * is used, causing self-closing tags to end with ">" vs "/>",
+   * and boolean attributes are not mirrored.
+   *
+   * @param {Doctype} doctype
+   * @api public
+   */
+
+  visitDoctype: function(doctype){
+    if (doctype && (doctype.val || !this.doctype)) {
+      this.setDoctype(doctype.val || 'default');
+    }
+
+    if (this.doctype) this.buffer(this.doctype);
+    this.hasCompiledDoctype = true;
+  },
+
+  /**
+   * Visit `mixin`, generating a function that
+   * may be called within the template.
+   *
+   * @param {Mixin} mixin
+   * @api public
+   */
+
+  visitMixin: function(mixin){
+    var name = 'jade_mixins[';
+    var args = mixin.args || '';
+    var block = mixin.block;
+    var attrs = mixin.attrs;
+    var attrsBlocks = mixin.attributeBlocks.slice();
+    var pp = this.pp;
+    var dynamic = mixin.name[0]==='#';
+    var key = mixin.name;
+    if (dynamic) this.dynamicMixins = true;
+    name += (dynamic ? mixin.name.substr(2,mixin.name.length-3):'"'+mixin.name+'"')+']';
+
+    this.mixins[key] = this.mixins[key] || {used: false, instances: []};
+    if (mixin.call) {
+      this.mixins[key].used = true;
+      if (pp) this.buf.push("jade_indent.push('" + Array(this.indents + 1).join(pp) + "');")
+      if (block || attrs.length || attrsBlocks.length) {
+
+        this.buf.push(name + '.call({');
+
+        if (block) {
+          this.buf.push('block: function(){');
+
+          // Render block with no indents, dynamically added when rendered
+          this.parentIndents++;
+          var _indents = this.indents;
+          this.indents = 0;
+          this.visit(mixin.block);
+          this.indents = _indents;
+          this.parentIndents--;
+
+          if (attrs.length || attrsBlocks.length) {
+            this.buf.push('},');
+          } else {
+            this.buf.push('}');
+          }
+        }
+
+        if (attrsBlocks.length) {
+          if (attrs.length) {
+            var val = this.attrs(attrs);
+            attrsBlocks.unshift(val);
+          }
+          this.buf.push('attributes: jade.merge([' + attrsBlocks.join(',') + '])');
+        } else if (attrs.length) {
+          var val = this.attrs(attrs);
+          this.buf.push('attributes: ' + val);
+        }
+
+        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 {
+      var mixin_start = this.buf.length;
+      args = args ? args.split(',') : [];
+      var rest;
+      if (args.length && /^\.\.\./.test(args[args.length - 1].trim())) {
+        rest = args.pop().trim().replace(/^\.\.\./, '');
+      }
+      this.buf.push(name + ' = function(' + args.join(',') + '){');
+      this.buf.push('var block = (this && this.block), attributes = (this && this.attributes) || {};');
+      if (rest) {
+        this.buf.push('var ' + rest + ' = [];');
+        this.buf.push('for (jade_interp = ' + args.length + '; jade_interp < arguments.length; jade_interp++) {');
+        this.buf.push('  ' + rest + '.push(arguments[jade_interp]);');
+        this.buf.push('}');
+      }
+      this.parentIndents++;
+      this.visit(block);
+      this.parentIndents--;
+      this.buf.push('};');
+      var mixin_end = this.buf.length;
+      this.mixins[key].instances.push({start: mixin_start, end: mixin_end});
+    }
+  },
+
+  /**
+   * Visit `tag` buffering tag markup, generating
+   * attributes, visiting the `tag`'s code and block.
+   *
+   * @param {Tag} tag
+   * @api public
+   */
+
+  visitTag: function(tag){
+    this.indents++;
+    var name = tag.name
+      , pp = this.pp
+      , self = this;
+
+    function bufferName() {
+      if (tag.buffer) self.bufferExpression(name);
+      else self.buffer(name);
+    }
+
+    if ('pre' == tag.name) this.escape = true;
+
+    if (!this.hasCompiledTag) {
+      if (!this.hasCompiledDoctype && 'html' == name) {
+        this.visitDoctype();
+      }
+      this.hasCompiledTag = true;
+    }
+
+    // pretty print
+    if (pp && !tag.isInline())
+      this.prettyIndent(0, true);
+
+    if (tag.selfClosing || (!this.xml && selfClosing[tag.name])) {
+      this.buffer('<');
+      bufferName();
+      this.visitAttributes(tag.attrs, tag.attributeBlocks.slice());
+      this.terse
+        ? this.buffer('>')
+        : this.buffer('/>');
+      // if it is non-empty throw an error
+      if (tag.block &&
+          !(tag.block.type === 'Block' && tag.block.nodes.length === 0) &&
+          tag.block.nodes.some(function (tag) {
+            return tag.type !== 'Text' || !/^\s*$/.test(tag.val)
+          })) {
+        throw errorAtNode(tag, new Error(name + ' is self closing and should not have content.'));
+      }
+    } else {
+      // Optimize attributes buffering
+      this.buffer('<');
+      bufferName();
+      this.visitAttributes(tag.attrs, tag.attributeBlocks.slice());
+      this.buffer('>');
+      if (tag.code) this.visitCode(tag.code);
+      this.visit(tag.block);
+
+      // pretty print
+      if (pp && !tag.isInline() && 'pre' != tag.name && !tag.canInline())
+        this.prettyIndent(0, true);
+
+      this.buffer('</');
+      bufferName();
+      this.buffer('>');
+    }
+
+    if ('pre' == tag.name) this.escape = false;
+
+    this.indents--;
+  },
+
+  /**
+   * Visit `filter`, throwing when the filter does not exist.
+   *
+   * @param {Filter} filter
+   * @api public
+   */
+
+  visitFilter: function(filter){
+    var text = filter.block.nodes.map(
+      function(node){ return node.val; }
+    ).join('\n');
+    filter.attrs.filename = this.options.filename;
+    try {
+      this.buffer(filters(filter.name, text, filter.attrs), true);
+    } catch (err) {
+      throw errorAtNode(filter, err);
+    }
+  },
+
+  /**
+   * Visit `text` node.
+   *
+   * @param {Text} text
+   * @api public
+   */
+
+  visitText: function(text){
+    this.buffer(text.val, true);
+  },
+
+  /**
+   * Visit a `comment`, only buffering when the buffer flag is set.
+   *
+   * @param {Comment} comment
+   * @api public
+   */
+
+  visitComment: function(comment){
+    if (!comment.buffer) return;
+    if (this.pp) this.prettyIndent(1, true);
+    this.buffer('<!--' + comment.val + '-->');
+  },
+
+  /**
+   * Visit a `BlockComment`.
+   *
+   * @param {Comment} comment
+   * @api public
+   */
+
+  visitBlockComment: function(comment){
+    if (!comment.buffer) return;
+    if (this.pp) this.prettyIndent(1, true);
+    this.buffer('<!--' + comment.val);
+    this.visit(comment.block);
+    if (this.pp) this.prettyIndent(1, true);
+    this.buffer('-->');
+  },
+
+  /**
+   * Visit `code`, respecting buffer / escape flags.
+   * If the code is followed by a block, wrap it in
+   * a self-calling function.
+   *
+   * @param {Code} code
+   * @api public
+   */
+
+  visitCode: function(code){
+    // Wrap code blocks with {}.
+    // we only wrap unbuffered code blocks ATM
+    // since they are usually flow control
+
+    // Buffer code
+    if (code.buffer) {
+      var val = code.val.trim();
+      val = 'null == (jade_interp = '+val+') ? "" : jade_interp';
+      if (code.escape) val = 'jade.escape(' + val + ')';
+      this.bufferExpression(val);
+    } else {
+      this.buf.push(code.val);
+    }
+
+    // Block support
+    if (code.block) {
+      if (!code.buffer) this.buf.push('{');
+      this.visit(code.block);
+      if (!code.buffer) this.buf.push('}');
+    }
+  },
+
+  /**
+   * Visit `each` block.
+   *
+   * @param {Each} each
+   * @api public
+   */
+
+  visitEach: function(each){
+    this.buf.push(''
+      + '// iterate ' + each.obj + '\n'
+      + ';(function(){\n'
+      + '  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');
+  },
+
+  /**
+   * Visit `attrs`.
+   *
+   * @param {Array} attrs
+   * @api public
+   */
+
+  visitAttributes: function(attrs, attributeBlocks){
+    if (attributeBlocks.length) {
+      if (attrs.length) {
+        var val = this.attrs(attrs);
+        attributeBlocks.unshift(val);
+      }
+      this.bufferExpression('jade.attrs(jade.merge([' + attributeBlocks.join(',') + ']), ' + utils.stringify(this.terse) + ')');
+    } else if (attrs.length) {
+      this.attrs(attrs, true);
+    }
+  },
+
+  /**
+   * Compile attributes.
+   */
+
+  attrs: function(attrs, buffer){
+    var buf = [];
+    var classes = [];
+    var classEscaping = [];
+
+    attrs.forEach(function(attr){
+      var key = attr.name;
+      var escaped = attr.escaped;
+
+      if (key === 'class') {
+        classes.push(attr.val);
+        classEscaping.push(attr.escaped);
+      } else if (isConstant(attr.val)) {
+        if (buffer) {
+          this.buffer(runtime.attr(key, toConstant(attr.val), escaped, this.terse));
+        } else {
+          var val = toConstant(attr.val);
+          if (key === 'style') val = runtime.style(val);
+          if (escaped && !(key.indexOf('data') === 0 && typeof val !== 'string')) {
+            val = runtime.escape(val);
+          }
+          buf.push(utils.stringify(key) + ': ' + utils.stringify(val));
+        }
+      } else {
+        if (buffer) {
+          this.bufferExpression('jade.attr("' + key + '", ' + attr.val + ', ' + utils.stringify(escaped) + ', ' + utils.stringify(this.terse) + ')');
+        } else {
+          var val = attr.val;
+          if (key === 'style') {
+            val = 'jade.style(' + val + ')';
+          }
+          if (escaped && !(key.indexOf('data') === 0)) {
+            val = 'jade.escape(' + val + ')';
+          } else if (escaped) {
+            val = '(typeof (jade_interp = ' + val + ') == "string" ? jade.escape(jade_interp) : jade_interp)';
+          }
+          buf.push(utils.stringify(key) + ': ' + val);
+        }
+      }
+    }.bind(this));
+    if (buffer) {
+      if (classes.every(isConstant)) {
+        this.buffer(runtime.cls(classes.map(toConstant), classEscaping));
+      } else {
+        this.bufferExpression('jade.cls([' + classes.join(',') + '], ' + utils.stringify(classEscaping) + ')');
+      }
+    } else if (classes.length) {
+      if (classes.every(isConstant)) {
+        classes = utils.stringify(runtime.joinClasses(classes.map(toConstant).map(runtime.joinClasses).map(function (cls, i) {
+          return classEscaping[i] ? runtime.escape(cls) : cls;
+        })));
+      } else {
+        classes = '(jade_interp = ' + utils.stringify(classEscaping) + ',' +
+          ' jade.joinClasses([' + classes.join(',') + '].map(jade.joinClasses).map(function (cls, i) {' +
+          '   return jade_interp[i] ? jade.escape(cls) : cls' +
+          ' }))' +
+          ')';
+      }
+      if (classes.length)
+        buf.push('"class": ' + classes);
+    }
+    return '{' + buf.join(',') + '}';
+  }
+};
+
+},{"./doctypes":2,"./filters":3,"./nodes":16,"./runtime":24,"./utils":25,"character-parser":29,"constantinople":30,"void-elements":34}],2:[function(require,module,exports){
+'use strict';
+
+module.exports = {
+    'default': '<!DOCTYPE html>'
+  , 'xml': '<?xml version="1.0" encoding="utf-8" ?>'
+  , 'transitional': '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">'
+  , 'strict': '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">'
+  , 'frameset': '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Frameset//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-frameset.dtd">'
+  , '1.1': '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">'
+  , 'basic': '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML Basic 1.1//EN" "http://www.w3.org/TR/xhtml-basic/xhtml-basic11.dtd">'
+  , 'mobile': '<!DOCTYPE html PUBLIC "-//WAPFORUM//DTD XHTML Mobile 1.2//EN" "http://www.openmobilealliance.org/tech/DTD/xhtml-mobile12.dtd">'
+};
+},{}],3:[function(require,module,exports){
+'use strict';
+
+module.exports = filter;
+function filter(name, str, options) {
+  if (typeof filter[name] === 'function') {
+    return filter[name](str, options);
+  } else {
+    throw new Error('unknown filter ":' + name + '"');
+  }
+}
+
+},{}],4:[function(require,module,exports){
+'use strict';
+
+/*!
+ * Jade
+ * Copyright(c) 2010 TJ Holowaychuk <tj@vision-media.ca>
+ * MIT Licensed
+ */
+
+/**
+ * Module dependencies.
+ */
+
+var Parser = require('./parser')
+  , Lexer = require('./lexer')
+  , Compiler = require('./compiler')
+  , runtime = require('./runtime')
+  , addWith = require('with')
+  , fs = require('fs')
+  , utils = require('./utils');
+
+/**
+ * Expose self closing tags.
+ */
+
+// FIXME: either stop exporting selfClosing in v2 or export the new object
+// form
+exports.selfClosing = Object.keys(require('void-elements'));
+
+/**
+ * Default supported doctypes.
+ */
+
+exports.doctypes = require('./doctypes');
+
+/**
+ * Text filters.
+ */
+
+exports.filters = require('./filters');
+
+/**
+ * Utilities.
+ */
+
+exports.utils = utils;
+
+/**
+ * Expose `Compiler`.
+ */
+
+exports.Compiler = Compiler;
+
+/**
+ * Expose `Parser`.
+ */
+
+exports.Parser = Parser;
+
+/**
+ * Expose `Lexer`.
+ */
+
+exports.Lexer = Lexer;
+
+/**
+ * Nodes.
+ */
+
+exports.nodes = require('./nodes');
+
+/**
+ * Jade runtime helpers.
+ */
+
+exports.runtime = runtime;
+
+/**
+ * Template function cache.
+ */
+
+exports.cache = {};
+
+/**
+ * Parse the given `str` of jade and return a function body.
+ *
+ * @param {String} str
+ * @param {Object} options
+ * @return {Object}
+ * @api private
+ */
+
+function parse(str, options){
+
+  if (options.lexer) {
+    console.warn('Using `lexer` as a local in render() is deprecated and '
+               + 'will be interpreted as an option in Jade 2.0.0');
+  }
+
+  // Parse
+  var parser = new (options.parser || Parser)(str, options.filename, options);
+  var tokens;
+  try {
+    // Parse
+    tokens = parser.parse();
+  } catch (err) {
+    parser = parser.context();
+    runtime.rethrow(err, parser.filename, parser.lexer.lineno, parser.input);
+  }
+
+  // Compile
+  var compiler = new (options.compiler || Compiler)(tokens, options);
+  var js;
+  try {
+    js = compiler.compile();
+  } catch (err) {
+    if (err.line && (err.filename || !options.filename)) {
+      runtime.rethrow(err, err.filename, err.line, parser.input);
+    } else {
+      if (err instanceof Error) {
+        err.message += '\n\nPlease report this entire error and stack trace to https://github.com/jadejs/jade/issues';
+      }
+      throw err;
+    }
+  }
+
+  // Debug compiler
+  if (options.debug) {
+    console.error('\nCompiled Function:\n\n\u001b[90m%s\u001b[0m', js.replace(/^/gm, '  '));
+  }
+
+  var globals = [];
+
+  if (options.globals) {
+    globals = options.globals.slice();
+  }
+
+  globals.push('jade');
+  globals.push('jade_mixins');
+  globals.push('jade_interp');
+  globals.push('jade_debug');
+  globals.push('buf');
+
+  var body = ''
+    + 'var buf = [];\n'
+    + 'var jade_mixins = {};\n'
+    + 'var jade_interp;\n'
+    + (options.self
+      ? 'var self = locals || {};\n' + js
+      : addWith('locals || {}', '\n' + js, globals)) + ';'
+    + 'return buf.join("");';
+  return {body: body, dependencies: parser.dependencies};
+}
+
+/**
+ * Get the template from a string or a file, either compiled on-the-fly or
+ * read from cache (if enabled), and cache the template if needed.
+ *
+ * If `str` is not set, the file specified in `options.filename` will be read.
+ *
+ * If `options.cache` is true, this function reads the file from
+ * `options.filename` so it must be set prior to calling this function.
+ *
+ * @param {Object} options
+ * @param {String=} str
+ * @return {Function}
+ * @api private
+ */
+function handleTemplateCache (options, str) {
+  var key = options.filename;
+  if (options.cache && exports.cache[key]) {
+    return exports.cache[key];
+  } else {
+    if (str === undefined) str = fs.readFileSync(options.filename, 'utf8');
+    var templ = exports.compile(str, options);
+    if (options.cache) exports.cache[key] = templ;
+    return templ;
+  }
+}
+
+/**
+ * Compile a `Function` representation of the given jade `str`.
+ *
+ * Options:
+ *
+ *   - `compileDebug` when `false` debugging code is stripped from the compiled
+       template, when it is explicitly `true`, the source code is included in
+       the compiled template for better accuracy.
+ *   - `filename` used to improve errors when `compileDebug` is not `false` and to resolve imports/extends
+ *
+ * @param {String} str
+ * @param {Options} options
+ * @return {Function}
+ * @api public
+ */
+
+exports.compile = function(str, options){
+  var options = options || {}
+    , filename = options.filename
+      ? utils.stringify(options.filename)
+      : 'undefined'
+    , fn;
+
+  str = String(str);
+
+  var parsed = parse(str, options);
+  if (options.compileDebug !== false) {
+    fn = [
+        'var jade_debug = [{ lineno: 1, filename: ' + filename + ' }];'
+      , 'try {'
+      , parsed.body
+      , '} catch (err) {'
+      , '  jade.rethrow(err, jade_debug[0].filename, jade_debug[0].lineno' + (options.compileDebug === true ? ',' + utils.stringify(str) : '') + ');'
+      , '}'
+    ].join('\n');
+  } else {
+    fn = parsed.body;
+  }
+  fn = new Function('locals, jade', fn)
+  var res = function(locals){ return fn(locals, Object.create(runtime)) };
+  if (options.client) {
+    res.toString = function () {
+      var err = new Error('The `client` option is deprecated, use the `jade.compileClient` method instead');
+      err.name = 'Warning';
+      console.error(err.stack || /* istanbul ignore next */ err.message);
+      return exports.compileClient(str, options);
+    };
+  }
+  res.dependencies = parsed.dependencies;
+  return res;
+};
+
+/**
+ * Compile a JavaScript source representation of the given jade `str`.
+ *
+ * Options:
+ *
+ *   - `compileDebug` When it is `true`, the source code is included in
+ *     the compiled template for better error messages.
+ *   - `filename` used to improve errors when `compileDebug` is not `true` and to resolve imports/extends
+ *   - `name` the name of the resulting function (defaults to "template")
+ *
+ * @param {String} str
+ * @param {Options} options
+ * @return {Object}
+ * @api public
+ */
+
+exports.compileClientWithDependenciesTracked = function(str, options){
+  var options = options || {};
+  var name = options.name || 'template';
+  var filename = options.filename ? utils.stringify(options.filename) : 'undefined';
+  var fn;
+
+  str = String(str);
+  options.compileDebug = options.compileDebug ? true : false;
+  var parsed = parse(str, options);
+  if (options.compileDebug) {
+    fn = [
+        'var jade_debug = [{ lineno: 1, filename: ' + filename + ' }];'
+      , 'try {'
+      , parsed.body
+      , '} catch (err) {'
+      , '  jade.rethrow(err, jade_debug[0].filename, jade_debug[0].lineno, ' + utils.stringify(str) + ');'
+      , '}'
+    ].join('\n');
+  } else {
+    fn = parsed.body;
+  }
+
+  return {body: 'function ' + name + '(locals) {\n' + fn + '\n}', dependencies: parsed.dependencies};
+};
+
+/**
+ * Compile a JavaScript source representation of the given jade `str`.
+ *
+ * Options:
+ *
+ *   - `compileDebug` When it is `true`, the source code is included in
+ *     the compiled template for better error messages.
+ *   - `filename` used to improve errors when `compileDebug` is not `true` and to resolve imports/extends
+ *   - `name` the name of the resulting function (defaults to "template")
+ *
+ * @param {String} str
+ * @param {Options} options
+ * @return {String}
+ * @api public
+ */
+exports.compileClient = function (str, options) {
+  return exports.compileClientWithDependenciesTracked(str, options).body;
+};
+
+/**
+ * Compile a `Function` representation of the given jade file.
+ *
+ * Options:
+ *
+ *   - `compileDebug` when `false` debugging code is stripped from the compiled
+       template, when it is explicitly `true`, the source code is included in
+       the compiled template for better accuracy.
+ *
+ * @param {String} path
+ * @param {Options} options
+ * @return {Function}
+ * @api public
+ */
+exports.compileFile = function (path, options) {
+  options = options || {};
+  options.filename = path;
+  return handleTemplateCache(options);
+};
+
+/**
+ * Render the given `str` of jade.
+ *
+ * Options:
+ *
+ *   - `cache` enable template caching
+ *   - `filename` filename required for `include` / `extends` and caching
+ *
+ * @param {String} str
+ * @param {Object|Function} options or fn
+ * @param {Function|undefined} fn
+ * @returns {String}
+ * @api public
+ */
+
+exports.render = function(str, options, fn){
+  // support callback API
+  if ('function' == typeof options) {
+    fn = options, options = undefined;
+  }
+  if (typeof fn === 'function') {
+    var res
+    try {
+      res = exports.render(str, options);
+    } catch (ex) {
+      return fn(ex);
+    }
+    return fn(null, res);
+  }
+
+  options = options || {};
+
+  // cache requires .filename
+  if (options.cache && !options.filename) {
+    throw new Error('the "filename" option is required for caching');
+  }
+
+  return handleTemplateCache(options, str)(options);
+};
+
+/**
+ * Render a Jade file at the given `path`.
+ *
+ * @param {String} path
+ * @param {Object|Function} options or callback
+ * @param {Function|undefined} fn
+ * @returns {String}
+ * @api public
+ */
+
+exports.renderFile = function(path, options, fn){
+  // support callback API
+  if ('function' == typeof options) {
+    fn = options, options = undefined;
+  }
+  if (typeof fn === 'function') {
+    var res
+    try {
+      res = exports.renderFile(path, options);
+    } catch (ex) {
+      return fn(ex);
+    }
+    return fn(null, res);
+  }
+
+  options = options || {};
+
+  options.filename = path;
+  return handleTemplateCache(options)(options);
+};
+
+
+/**
+ * Compile a Jade file at the given `path` for use on the client.
+ *
+ * @param {String} path
+ * @param {Object} options
+ * @returns {String}
+ * @api public
+ */
+
+exports.compileFileClient = function(path, options){
+  var key = path + ':client';
+  options = options || {};
+
+  options.filename = path;
+
+  if (options.cache && exports.cache[key]) {
+      return exports.cache[key];
+  }
+
+  var str = fs.readFileSync(options.filename, 'utf8');
+  var out = exports.compileClient(str, options);
+  if (options.cache) exports.cache[key] = out;
+  return out;
+};
+
+/**
+ * Express support.
+ */
+
+exports.__express = exports.renderFile;
+
+},{"./compiler":1,"./doctypes":2,"./filters":3,"./lexer":6,"./nodes":16,"./parser":23,"./runtime":24,"./utils":25,"fs":26,"void-elements":34,"with":35}],5:[function(require,module,exports){
+'use strict';
+
+module.exports = [
+    'a'
+  , 'abbr'
+  , 'acronym'
+  , 'b'
+  , 'br'
+  , 'code'
+  , 'em'
+  , 'font'
+  , 'i'
+  , 'img'
+  , 'ins'
+  , 'kbd'
+  , 'map'
+  , 'samp'
+  , 'small'
+  , 'span'
+  , 'strong'
+  , 'sub'
+  , 'sup'
+];
+},{}],6:[function(require,module,exports){
+'use strict';
+
+var utils = require('./utils');
+var characterParser = require('character-parser');
+
+
+/**
+ * Initialize `Lexer` with the given `str`.
+ *
+ * @param {String} str
+ * @param {String} filename
+ * @api private
+ */
+
+var Lexer = module.exports = function Lexer(str, filename) {
+  this.input = str.replace(/\r\n|\r/g, '\n');
+  this.filename = filename;
+  this.deferredTokens = [];
+  this.lastIndents = 0;
+  this.lineno = 1;
+  this.stash = [];
+  this.indentStack = [];
+  this.indentRe = null;
+  this.pipeless = false;
+};
+
+
+function assertExpression(exp) {
+  //this verifies that a JavaScript expression is valid
+  Function('', 'return (' + exp + ')');
+}
+function assertNestingCorrect(exp) {
+  //this verifies that code is properly nested, but allows
+  //invalid JavaScript such as the contents of `attributes`
+  var res = characterParser(exp)
+  if (res.isNesting()) {
+    throw new Error('Nesting must match on expression `' + exp + '`')
+  }
+}
+
+/**
+ * Lexer prototype.
+ */
+
+Lexer.prototype = {
+
+  /**
+   * Construct a token with the given `type` and `val`.
+   *
+   * @param {String} type
+   * @param {String} val
+   * @return {Object}
+   * @api private
+   */
+
+  tok: function(type, val){
+    return {
+        type: type
+      , line: this.lineno
+      , val: val
+    }
+  },
+
+  /**
+   * Consume the given `len` of input.
+   *
+   * @param {Number} len
+   * @api private
+   */
+
+  consume: function(len){
+    this.input = this.input.substr(len);
+  },
+
+  /**
+   * Scan for `type` with the given `regexp`.
+   *
+   * @param {String} type
+   * @param {RegExp} regexp
+   * @return {Object}
+   * @api private
+   */
+
+  scan: function(regexp, type){
+    var captures;
+    if (captures = regexp.exec(this.input)) {
+      this.consume(captures[0].length);
+      return this.tok(type, captures[1]);
+    }
+  },
+
+  /**
+   * Defer the given `tok`.
+   *
+   * @param {Object} tok
+   * @api private
+   */
+
+  defer: function(tok){
+    this.deferredTokens.push(tok);
+  },
+
+  /**
+   * Lookahead `n` tokens.
+   *
+   * @param {Number} n
+   * @return {Object}
+   * @api private
+   */
+
+  lookahead: function(n){
+    var fetch = n - this.stash.length;
+    while (fetch-- > 0) this.stash.push(this.next());
+    return this.stash[--n];
+  },
+
+  /**
+   * Return the indexOf `(` 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 = characterParser.parseMax(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');
+    }
+  },
+
+  /**
+   * 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();
+    }
+  },
+
+  /**
+   * 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];
+      this.pipeless = true;
+      return tok;
+    }
+  },
+
+  /**
+   * Interpolated tag.
+   */
+
+  interpolation: function() {
+    if (/^#\{/.test(this.input)) {
+      var match = this.bracketExpression(1);
+
+      this.consume(match.end + 1);
+      return this.tok('interpolation', match.src);
+    }
+  },
+
+  /**
+   * 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(':'));
+        if (this.input[0] !== ' ') {
+          console.warn('Warning: space required after `:` on line ' + this.lineno +
+              ' of jade file "' + this.filename + '"');
+        }
+        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() {
+    var tok = this.scan(/^:([\w\-]+)/, 'filter');
+    if (tok) {
+      this.pipeless = true;
+      return tok;
+    }
+  },
+
+  /**
+   * Doctype.
+   */
+
+  doctype: function() {
+    if (this.scan(/^!!! *([^\n]+)?/, 'doctype')) {
+      throw new Error('`!!!` is deprecated, you must now use `doctype`');
+    }
+    var node = this.scan(/^(?:doctype) *([^\n]+)?/, 'doctype');
+    if (node && node.val && node.val.trim() === '5') {
+      throw new Error('`doctype 5` is deprecated, you must now use `doctype html`');
+    }
+    return node;
+  },
+
+  /**
+   * Id.
+   */
+
+  id: function() {
+    return this.scan(/^#([\w-]+)/, 'id');
+  },
+
+  /**
+   * Class.
+   */
+
+  className: function() {
+    return this.scan(/^\.([\w-]+)/, 'class');
+  },
+
+  /**
+   * Text.
+   */
+
+  text: function() {
+    return this.scan(/^(?:\| ?| )([^\n]+)/, 'text') ||
+      this.scan(/^\|?( )/, 'text') ||
+      this.scan(/^(<[^\n]*)/, 'text');
+  },
+
+  textFail: function () {
+    var tok;
+    if (tok = this.scan(/^([^\.\n][^\n]+)/, 'text')) {
+      console.warn('Warning: missing space before text for line ' + this.lineno +
+          ' of jade file "' + this.filename + '"');
+      return tok;
+    }
+  },
+
+  /**
+   * Dot.
+   */
+
+  dot: function() {
+    var match;
+    if (match = this.scan(/^\./, 'dot')) {
+      this.pipeless = true;
+      return match;
+    }
+  },
+
+  /**
+   * Extends.
+   */
+
+  "extends": function() {
+    return this.scan(/^extends? +([^\n]+)/, 'extends');
+  },
+
+  /**
+   * Block prepend.
+   */
+
+  prepend: function() {
+    var captures;
+    if (captures = /^prepend +([^\n]+)/.exec(this.input)) {
+      this.consume(captures[0].length);
+      var mode = 'prepend'
+        , name = captures[1]
+        , tok = this.tok('block', name);
+      tok.mode = mode;
+      return tok;
+    }
+  },
+
+  /**
+   * Block append.
+   */
+
+  append: function() {
+    var captures;
+    if (captures = /^append +([^\n]+)/.exec(this.input)) {
+      this.consume(captures[0].length);
+      var mode = 'append'
+        , name = captures[1]
+        , tok = this.tok('block', name);
+      tok.mode = mode;
+      return tok;
+    }
+  },
+
+  /**
+   * Block.
+   */
+
+  block: function() {
+    var captures;
+    if (captures = /^block\b *(?:(prepend|append) +)?([^\n]+)/.exec(this.input)) {
+      this.consume(captures[0].length);
+      var mode = captures[1] || 'replace'
+        , name = captures[2]
+        , tok = this.tok('block', name);
+
+      tok.mode = mode;
+      return tok;
+    }
+  },
+
+  /**
+   * Mixin Block.
+   */
+
+  mixinBlock: function() {
+    var captures;
+    if (captures = /^block[ \t]*(\n|$)/.exec(this.input)) {
+      this.consume(captures[0].length - captures[1].length);
+      return this.tok('mixin-block');
+    }
+  },
+
+  /**
+   * Yield.
+   */
+
+  'yield': function() {
+    return this.scan(/^yield */, 'yield');
+  },
+
+  /**
+   * Include.
+   */
+
+  include: function() {
+    return this.scan(/^include +([^\n]+)/, 'include');
+  },
+
+  /**
+   * Include with filter
+   */
+
+  includeFiltered: function() {
+    var captures;
+    if (captures = /^include:([\w\-]+)([\( ])/.exec(this.input)) {
+      this.consume(captures[0].length - 1);
+      var filter = captures[1];
+      var attrs = captures[2] === '(' ? this.attrs() : null;
+      if (!(captures[2] === ' ' || this.input[0] === ' ')) {
+        throw new Error('expected space after include:filter but got ' + utils.stringify(this.input[0]));
+      }
+      captures = /^ *([^\n]+)/.exec(this.input);
+      if (!captures || captures[1].trim() === '') {
+        throw new Error('missing path for include:filter');
+      }
+      this.consume(captures[0].length);
+      var path = captures[1];
+      var tok = this.tok('include', path);
+      tok.filter = filter;
+      tok.attrs = attrs;
+      return tok;
+    }
+  },
+
+  /**
+   * Case.
+   */
+
+  "case": function() {
+    return this.scan(/^case +([^\n]+)/, 'case');
+  },
+
+  /**
+   * When.
+   */
+
+  when: function() {
+    return this.scan(/^when +([^:\n]+)/, 'when');
+  },
+
+  /**
+   * Default.
+   */
+
+  "default": function() {
+    return this.scan(/^default */, 'default');
+  },
+
+  /**
+   * Call mixin.
+   */
+
+  call: function(){
+
+    var tok, captures;
+    if (captures = /^\+(\s*)(([-\w]+)|(#\{))/.exec(this.input)) {
+      // try to consume simple or interpolated call
+      if (captures[3]) {
+        // simple call
+        this.consume(captures[0].length);
+        tok = this.tok('call', captures[3]);
+      } else {
+        // interpolated call
+        var match = this.bracketExpression(2 + captures[1].length);
+        this.consume(match.end + 1);
+        assertExpression(match.src);
+        tok = this.tok('call', '#{'+match.src+'}');
+      }
+
+      // Check for args (not attributes)
+      if (captures = /^ *\(/.exec(this.input)) {
+        var range = this.bracketExpression(captures[0].length - 1);
+        if (!/^\s*[-\w]+ *=/.test(range.src)) { // not attributes
+          this.consume(range.end + 1);
+          tok.args = range.src;
+        }
+        if (tok.args) {
+          assertExpression('[' + tok.args + ']');
+        }
+      }
+
+      return tok;
+    }
+  },
+
+  /**
+   * Mixin.
+   */
+
+  mixin: function(){
+    var captures;
+    if (captures = /^mixin +([-\w]+)(?: *\((.*)\))? */.exec(this.input)) {
+      this.consume(captures[0].length);
+      var tok = this.tok('mixin', captures[1]);
+      tok.args = captures[2];
+      return tok;
+    }
+  },
+
+  /**
+   * Conditional.
+   */
+
+  conditional: function() {
+    var captures;
+    if (captures = /^(if|unless|else if|else)\b([^\n]*)/.exec(this.input)) {
+      this.consume(captures[0].length);
+      var type = captures[1]
+      var js = captures[2];
+      var isIf = false;
+      var isElse = false;
+
+      switch (type) {
+        case 'if':
+          assertExpression(js)
+          js = 'if (' + js + ')';
+          isIf = true;
+          break;
+        case 'unless':
+          assertExpression(js)
+          js = 'if (!(' + js + '))';
+          isIf = true;
+          break;
+        case 'else if':
+          assertExpression(js)
+          js = 'else if (' + js + ')';
+          isIf = true;
+          isElse = true;
+          break;
+        case 'else':
+          if (js && js.trim()) {
+            throw new Error('`else` cannot have a condition, perhaps you meant `else if`');
+          }
+          js = 'else';
+          isElse = true;
+          break;
+      }
+      var tok = this.tok('code', js);
+      tok.isElse = isElse;
+      tok.isIf = isIf;
+      tok.requiresBlock = true;
+      return tok;
+    }
+  },
+
+  /**
+   * While.
+   */
+
+  "while": function() {
+    var captures;
+    if (captures = /^while +([^\n]+)/.exec(this.input)) {
+      this.consume(captures[0].length);
+      assertExpression(captures[1])
+      var tok = this.tok('code', 'while (' + captures[1] + ')');
+      tok.requiresBlock = true;
+      return tok;
+    }
+  },
+
+  /**
+   * Each.
+   */
+
+  each: function() {
+    var captures;
+    if (captures = /^(?:- *)?(?:each|for) +([a-zA-Z_$][\w$]*)(?: *, *([a-zA-Z_$][\w$]*))? * in *([^\n]+)/.exec(this.input)) {
+      this.consume(captures[0].length);
+      var tok = this.tok('each', captures[1]);
+      tok.key = captures[2] || '$index';
+      assertExpression(captures[3])
+      tok.code = captures[3];
+      return tok;
+    }
+  },
+
+  /**
+   * Code.
+   */
+
+  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) === '=';
+      if (tok.buffer) assertExpression(captures[1])
+      return tok;
+    }
+  },
+
+  /**
+   * Attributes.
+   */
+
+  attrs: function() {
+    if ('(' == this.input.charAt(0)) {
+      var index = this.bracketExpression().end
+        , str = this.input.substr(1, index-1)
+        , tok = this.tok('attrs');
+
+      assertNestingCorrect(str);
+
+      var quote = '';
+      var interpolate = function (attr) {
+        return attr.replace(/(\\)?#\{(.+)/g, function(_, escape, expr){
+          if (escape) return _;
+          try {
+            var range = characterParser.parseMax(expr);
+            if (expr[range.end] !== '}') return _.substr(0, 2) + interpolate(_.substr(2));
+            assertExpression(range.src)
+            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 = [];
+
+      var escapedAttr = true
+      var key = '';
+      var val = '';
+      var interpolatable = '';
+      var state = characterParser.defaultState();
+      var loc = 'key';
+      var isEndOfAttribute = function (i) {
+        if (key.trim() === '') return false;
+        if (i === str.length) return true;
+        if (loc === 'key') {
+          if (str[i] === ' ' || str[i] === '\n') {
+            for (var x = i; x < str.length; x++) {
+              if (str[x] != ' ' && str[x] != '\n') {
+                if (str[x] === '=' || str[x] === '!' || str[x] === ',') return false;
+                else return true;
+              }
+            }
+          }
+          return str[i] === ','
+        } else if (loc === 'value' && !state.isNesting()) {
+          try {
+            assertExpression(val);
+            if (str[i] === ' ' || str[i] === '\n') {
+              for (var x = i; x < str.length; x++) {
+                if (str[x] != ' ' && str[x] != '\n') {
+                  if (characterParser.isPunctuator(str[x]) && str[x] != '"' && str[x] != "'") return false;
+                  else return true;
+                }
+              }
+            }
+            return str[i] === ',';
+          } catch (ex) {
+            return false;
+          }
+        }
+      }
+
+      this.lineno += str.split("\n").length - 1;
+
+      for (var i = 0; i <= str.length; i++) {
+        if (isEndOfAttribute(i)) {
+          val = val.trim();
+          if (val) assertExpression(val)
+          key = key.trim();
+          key = key.replace(/^['"]|['"]$/g, '');
+          tok.attrs.push({
+            name: key,
+            val: '' == val ? true : val,
+            escaped: escapedAttr
+          });
+          key = val = '';
+          loc = 'key';
+          escapedAttr = false;
+        } else {
+          switch (loc) {
+            case 'key-char':
+              if (str[i] === quote) {
+                loc = 'key';
+                if (i + 1 < str.length && [' ', ',', '!', '=', '\n'].indexOf(str[i + 1]) === -1)
+                  throw new Error('Unexpected character ' + str[i + 1] + ' expected ` `, `\\n`, `,`, `!` or `=`');
+              } else {
+                key += str[i];
+              }
+              break;
+            case 'key':
+              if (key === '' && (str[i] === '"' || str[i] === "'")) {
+                loc = 'key-char';
+                quote = str[i];
+              } else if (str[i] === '!' || str[i] === '=') {
+                escapedAttr = str[i] !== '!';
+                if (str[i] === '!') i++;
+                if (str[i] !== '=') throw new Error('Unexpected character ' + str[i] + ' expected `=`');
+                loc = 'value';
+                state = characterParser.defaultState();
+              } else {
+                key += str[i]
+              }
+              break;
+            case 'value':
+              state = characterParser.parseChar(str[i], state);
+              if (state.isString()) {
+                loc = 'string';
+                quote = str[i];
+                interpolatable = str[i];
+              } else {
+                val += str[i];
+              }
+              break;
+            case 'string':
+              state = characterParser.parseChar(str[i], state);
+              interpolatable += str[i];
+              if (!state.isString()) {
+                loc = 'value';
+                val += interpolate(interpolatable);
+              }
+              break;
+          }
+        }
+      }
+
+      if ('/' == this.input.charAt(0)) {
+        this.consume(1);
+        tok.selfClosing = true;
+      }
+
+      return tok;
+    }
+  },
+
+  /**
+   * &attributes block
+   */
+  attributesBlock: function () {
+    var captures;
+    if (/^&attributes\b/.test(this.input)) {
+      this.consume(11);
+      var args = this.bracketExpression();
+      this.consume(args.end + 1);
+      return this.tok('&attributes', args.src);
+    }
+  },
+
+  /**
+   * Indent | Outdent | Newline.
+   */
+
+  indent: function() {
+    var captures, re;
+
+    // established regexp
+    if (this.indentRe) {
+      captures = this.indentRe.exec(this.input);
+    // determine regexp
+    } else {
+      // tabs
+      re = /^\n(\t*) */;
+      captures = re.exec(this.input);
+
+      // spaces
+      if (captures && !captures[1].length) {
+        re = /^\n( *)/;
+        captures = re.exec(this.input);
+      }
+
+      // established
+      if (captures && captures[1].length) this.indentRe = re;
+    }
+
+    if (captures) {
+      var tok
+        , indents = captures[1].length;
+
+      ++this.lineno;
+      this.consume(indents + 1);
+
+      if (' ' == this.input[0] || '\t' == this.input[0]) {
+        throw new Error('Invalid indentation, you can use tabs or spaces but not both');
+      }
+
+      // blank line
+      if ('\n' == this.input[0]) {
+        this.pipeless = false;
+        return this.tok('newline');
+      }
+
+      // outdent
+      if (this.indentStack.length && indents < this.indentStack[0]) {
+        while (this.indentStack.length && this.indentStack[0] > indents) {
+          this.stash.push(this.tok('outdent'));
+          this.indentStack.shift();
+        }
+        tok = this.stash.pop();
+      // indent
+      } else if (indents && indents != this.indentStack[0]) {
+        this.indentStack.unshift(indents);
+        tok = this.tok('indent', indents);
+      // newline
+      } else {
+        tok = this.tok('newline');
+      }
+
+      this.pipeless = false;
+      return tok;
+    }
+  },
+
+  /**
+   * Pipe-less text consumed only when
+   * pipeless is true;
+   */
+
+  pipelessText: function() {
+    if (!this.pipeless) return;
+    var captures, re;
+
+    // established regexp
+    if (this.indentRe) {
+      captures = this.indentRe.exec(this.input);
+    // determine regexp
+    } else {
+      // tabs
+      re = /^\n(\t*) */;
+      captures = re.exec(this.input);
+
+      // spaces
+      if (captures && !captures[1].length) {
+        re = /^\n( *)/;
+        captures = re.exec(this.input);
+      }
+
+      // established
+      if (captures && captures[1].length) this.indentRe = re;
+    }
+
+    var indents = captures && captures[1].length;
+    if (indents && (this.indentStack.length === 0 || indents > this.indentStack[0])) {
+      var indent = captures[1];
+      var line;
+      var tokens = [];
+      var isMatch;
+      do {
+        // text has `\n` as a prefix
+        var i = this.input.substr(1).indexOf('\n');
+        if (-1 == i) i = this.input.length - 1;
+        var str = this.input.substr(1, i);
+        isMatch = str.substr(0, indent.length) === indent || !str.trim();
+        if (isMatch) {
+          // consume test along with `\n` prefix if match
+          this.consume(str.length + 1);
+          tokens.push(str.substr(indent.length));
+        }
+      } while(this.input.length && isMatch);
+      while (this.input.length === 0 && tokens[tokens.length - 1] === '') tokens.pop();
+      return this.tok('pipeless-text', tokens);
+    }
+  },
+
+  /**
+   * ':'
+   */
+
+  colon: function() {
+    var good = /^: +/.test(this.input);
+    var res = this.scan(/^: */, ':');
+    if (res && !good) {
+      console.warn('Warning: space required after `:` on line ' + this.lineno +
+          ' of jade file "' + this.filename + '"');
+    }
+    return res;
+  },
+
+  fail: function () {
+    throw new Error('unexpected text ' + this.input.substr(0, 5));
+  },
+
+  /**
+   * 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.mixinBlock()
+      || this.include()
+      || this.includeFiltered()
+      || this.mixin()
+      || this.call()
+      || this.conditional()
+      || this.each()
+      || this["while"]()
+      || this.tag()
+      || this.filter()
+      || this.code()
+      || this.id()
+      || this.className()
+      || this.attrs()
+      || this.attributesBlock()
+      || this.indent()
+      || this.text()
+      || this.comment()
+      || this.colon()
+      || this.dot()
+      || this.textFail()
+      || this.fail();
+  }
+};
+
+},{"./utils":25,"character-parser":29}],7:[function(require,module,exports){
+'use strict';
+
+var Node = require('./node');
+
+/**
+ * Initialize a `Attrs` node.
+ *
+ * @api public
+ */
+
+var Attrs = module.exports = function Attrs() {
+  this.attributeNames = [];
+  this.attrs = [];
+  this.attributeBlocks = [];
+};
+
+// Inherit from `Node`.
+Attrs.prototype = Object.create(Node.prototype);
+Attrs.prototype.constructor = Attrs;
+
+Attrs.prototype.type = '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){
+  if (name !== 'class' && this.attributeNames.indexOf(name) !== -1) {
+    throw new Error('Duplicate attribute "' + name + '" is not allowed.');
+  }
+  this.attributeNames.push(name);
+  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){
+  var err = new Error('attrs.removeAttribute is deprecated and will be removed in v2.0.0');
+  console.warn(err.stack);
+
+  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){
+  var err = new Error('attrs.getAttribute is deprecated and will be removed in v2.0.0');
+  console.warn(err.stack);
+
+  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;
+    }
+  }
+};
+
+Attrs.prototype.addAttributes = function (src) {
+  this.attributeBlocks.push(src);
+};
+
+},{"./node":20}],8:[function(require,module,exports){
+'use strict';
+
+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 = Object.create(Node.prototype);
+BlockComment.prototype.constructor = BlockComment;
+
+BlockComment.prototype.type = 'BlockComment';
+
+},{"./node":20}],9:[function(require,module,exports){
+'use strict';
+
+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 = Object.create(Node.prototype);
+Block.prototype.constructor = Block;
+
+Block.prototype.type = '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){
+  var err = new Error('block.replace is deprecated and will be removed in v2.0.0');
+  console.warn(err.stack);
+
+  other.nodes = this.nodes;
+};
+
+/**
+ * Push 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 err = new Error('block.clone is deprecated and will be removed in v2.0.0');
+  console.warn(err.stack);
+
+  var clone = new Block;
+  for (var i = 0, len = this.nodes.length; i < len; ++i) {
+    clone.push(this.nodes[i].clone());
+  }
+  return clone;
+};
+
+},{"./node":20}],10:[function(require,module,exports){
+'use strict';
+
+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 = Object.create(Node.prototype);
+Case.prototype.constructor = Case;
+
+Case.prototype.type = 'Case';
+
+var When = exports.When = function When(expr, block){
+  this.expr = expr;
+  this.block = block;
+  this.debug = false;
+};
+
+// Inherit from `Node`.
+When.prototype = Object.create(Node.prototype);
+When.prototype.constructor = When;
+
+When.prototype.type = 'When';
+
+},{"./node":20}],11:[function(require,module,exports){
+'use strict';
+
+var Node = require('./node');
+
+/**
+ * Initialize a `Code` node with the given code `val`.
+ * Code may also be optionally buffered and escaped.
+ *
+ * @param {String} val
+ * @param {Boolean} buffer
+ * @param {Boolean} escape
+ * @api public
+ */
+
+var Code = module.exports = function Code(val, buffer, escape) {
+  this.val = val;
+  this.buffer = buffer;
+  this.escape = escape;
+  if (val.match(/^ *else/)) this.debug = false;
+};
+
+// Inherit from `Node`.
+Code.prototype = Object.create(Node.prototype);
+Code.prototype.constructor = Code;
+
+Code.prototype.type = 'Code'; // prevent the minifiers removing this
+},{"./node":20}],12:[function(require,module,exports){
+'use strict';
+
+var Node = require('./node');
+
+/**
+ * Initialize a `Comment` with the given `val`, optionally `buffer`,
+ * otherwise the comment may render in the output.
+ *
+ * @param {String} val
+ * @param {Boolean} buffer
+ * @api public
+ */
+
+var Comment = module.exports = function Comment(val, buffer) {
+  this.val = val;
+  this.buffer = buffer;
+};
+
+// Inherit from `Node`.
+Comment.prototype = Object.create(Node.prototype);
+Comment.prototype.constructor = Comment;
+
+Comment.prototype.type = 'Comment';
+
+},{"./node":20}],13:[function(require,module,exports){
+'use strict';
+
+var Node = require('./node');
+
+/**
+ * Initialize a `Doctype` with the given `val`. 
+ *
+ * @param {String} val
+ * @api public
+ */
+
+var Doctype = module.exports = function Doctype(val) {
+  this.val = val;
+};
+
+// Inherit from `Node`.
+Doctype.prototype = Object.create(Node.prototype);
+Doctype.prototype.constructor = Doctype;
+
+Doctype.prototype.type = 'Doctype';
+
+},{"./node":20}],14:[function(require,module,exports){
+'use strict';
+
+var Node = require('./node');
+
+/**
+ * Initialize an `Each` node, representing iteration
+ *
+ * @param {String} obj
+ * @param {String} val
+ * @param {String} key
+ * @param {Block} block
+ * @api public
+ */
+
+var Each = module.exports = function Each(obj, val, key, block) {
+  this.obj = obj;
+  this.val = val;
+  this.key = key;
+  this.block = block;
+};
+
+// Inherit from `Node`.
+Each.prototype = Object.create(Node.prototype);
+Each.prototype.constructor = Each;
+
+Each.prototype.type = 'Each';
+
+},{"./node":20}],15:[function(require,module,exports){
+'use strict';
+
+var Node = require('./node');
+
+/**
+ * Initialize a `Filter` node with the given
+ * filter `name` and `block`.
+ *
+ * @param {String} name
+ * @param {Block|Node} block
+ * @api public
+ */
+
+var Filter = module.exports = function Filter(name, block, attrs) {
+  this.name = name;
+  this.block = block;
+  this.attrs = attrs;
+};
+
+// Inherit from `Node`.
+Filter.prototype = Object.create(Node.prototype);
+Filter.prototype.constructor = Filter;
+
+Filter.prototype.type = 'Filter';
+
+},{"./node":20}],16:[function(require,module,exports){
+'use strict';
+
+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.MixinBlock = require('./mixin-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');
+
+},{"./block":9,"./block-comment":8,"./case":10,"./code":11,"./comment":12,"./doctype":13,"./each":14,"./filter":15,"./literal":17,"./mixin":19,"./mixin-block":18,"./node":20,"./tag":21,"./text":22}],17:[function(require,module,exports){
+'use strict';
+
+var Node = require('./node');
+
+/**
+ * Initialize a `Literal` node with the given `str.
+ *
+ * @param {String} str
+ * @api public
+ */
+
+var Literal = module.exports = function Literal(str) {
+  this.str = str;
+};
+
+// Inherit from `Node`.
+Literal.prototype = Object.create(Node.prototype);
+Literal.prototype.constructor = Literal;
+
+Literal.prototype.type = 'Literal';
+
+},{"./node":20}],18:[function(require,module,exports){
+'use strict';
+
+var Node = require('./node');
+
+/**
+ * Initialize a new `Block` with an optional `node`.
+ *
+ * @param {Node} node
+ * @api public
+ */
+
+var MixinBlock = module.exports = function MixinBlock(){};
+
+// Inherit from `Node`.
+MixinBlock.prototype = Object.create(Node.prototype);
+MixinBlock.prototype.constructor = MixinBlock;
+
+MixinBlock.prototype.type = 'MixinBlock';
+
+},{"./node":20}],19:[function(require,module,exports){
+'use strict';
+
+var Attrs = require('./attrs');
+
+/**
+ * Initialize a new `Mixin` with `name` and `block`.
+ *
+ * @param {String} name
+ * @param {String} args
+ * @param {Block} block
+ * @api public
+ */
+
+var Mixin = module.exports = function Mixin(name, args, block, call){
+  Attrs.call(this);
+  this.name = name;
+  this.args = args;
+  this.block = block;
+  this.call = call;
+};
+
+// Inherit from `Attrs`.
+Mixin.prototype = Object.create(Attrs.prototype);
+Mixin.prototype.constructor = Mixin;
+
+Mixin.prototype.type = 'Mixin';
+
+},{"./attrs":7}],20:[function(require,module,exports){
+'use strict';
+
+var Node = module.exports = function Node(){};
+
+/**
+ * Clone this node (return itself)
+ *
+ * @return {Node}
+ * @api private
+ */
+
+Node.prototype.clone = function(){
+  var err = new Error('node.clone is deprecated and will be removed in v2.0.0');
+  console.warn(err.stack);
+  return this;
+};
+
+Node.prototype.type = '';
+
+},{}],21:[function(require,module,exports){
+'use strict';
+
+var Attrs = require('./attrs');
+var Block = require('./block');
+var inlineTags = require('../inline-tags');
+
+/**
+ * Initialize a `Tag` node with the given tag `name` and optional `block`.
+ *
+ * @param {String} name
+ * @param {Block} block
+ * @api public
+ */
+
+var Tag = module.exports = function Tag(name, block) {
+  Attrs.call(this);
+  this.name = name;
+  this.block = block || new Block;
+};
+
+// Inherit from `Attrs`.
+Tag.prototype = Object.create(Attrs.prototype);
+Tag.prototype.constructor = Tag;
+
+Tag.prototype.type = 'Tag';
+
+/**
+ * Clone this tag.
+ *
+ * @return {Tag}
+ * @api private
+ */
+
+Tag.prototype.clone = function(){
+  var err = new Error('tag.clone is deprecated and will be removed in v2.0.0');
+  console.warn(err.stack);
+
+  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.
+ *
+ * @return {Boolean}
+ * @api private
+ */
+
+Tag.prototype.isInline = function(){
+  return ~inlineTags.indexOf(this.name);
+};
+
+/**
+ * Check if this tag's contents can be inlined.  Used for pretty printing.
+ *
+ * @return {Boolean}
+ * @api private
+ */
+
+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 true;
+  }
+
+  // Mixed tag
+  return false;
+};
+
+},{"../inline-tags":5,"./attrs":7,"./block":9}],22:[function(require,module,exports){
+'use strict';
+
+var Node = require('./node');
+
+/**
+ * Initialize a `Text` node with optional `line`.
+ *
+ * @param {String} line
+ * @api public
+ */
+
+var Text = module.exports = function Text(line) {
+  this.val = line;
+};
+
+// Inherit from `Node`.
+Text.prototype = Object.create(Node.prototype);
+Text.prototype.constructor = Text;
+
+Text.prototype.type = 'Text';
+
+/**
+ * Flag as text.
+ */
+
+Text.prototype.isText = true;
+},{"./node":20}],23:[function(require,module,exports){
+'use strict';
+
+var Lexer = require('./lexer');
+var nodes = require('./nodes');
+var utils = require('./utils');
+var filters = require('./filters');
+var path = require('path');
+var constantinople = require('constantinople');
+var parseJSExpression = require('character-parser').parseMax;
+var extname = path.extname;
+
+/**
+ * Initialize `Parser` with the given input `str` and `filename`.
+ *
+ * @param {String} str
+ * @param {String} filename
+ * @param {Object} options
+ * @api public
+ */
+
+var Parser = exports = module.exports = function Parser(str, filename, options){
+  //Strip any UTF-8 BOM off of the start of `str`, if it exists.
+  this.input = str.replace(/^\uFEFF/, '');
+  this.lexer = new Lexer(this.input, filename);
+  this.filename = filename;
+  this.blocks = {};
+  this.mixins = {};
+  this.options = options;
+  this.contexts = [this];
+  this.inMixin = 0;
+  this.dependencies = [];
+  this.inBlock = 0;
+};
+
+/**
+ * Parser prototype.
+ */
+
+Parser.prototype = {
+
+  /**
+   * Save original constructor
+   */
+
+  constructor: Parser,
+
+  /**
+   * Push `parser` onto the context stack,
+   * or pop and return a `Parser`.
+   */
+
+  context: function(parser){
+    if (parser) {
+      this.contexts.push(parser);
+    } else {
+      return this.contexts.pop();
+    }
+  },
+
+  /**
+   * Return the next token object.
+   *
+   * @return {Object}
+   * @api private
+   */
+
+  advance: function(){
+    return this.lexer.advance();
+  },
+
+  /**
+   * Single token lookahead.
+   *
+   * @return {Object}
+   * @api private
+   */
+
+  peek: function() {
+    return this.lookahead(1);
+  },
+
+  /**
+   * Return lexer lineno.
+   *
+   * @return {Number}
+   * @api private
+   */
+
+  line: function() {
+    return this.lexer.lineno;
+  },
+
+  /**
+   * `n` token lookahead.
+   *
+   * @param {Number} n
+   * @return {Object}
+   * @api private
+   */
+
+  lookahead: function(n){
+    return this.lexer.lookahead(n);
+  },
+
+  /**
+   * Parse input returning a string of js for evaluation.
+   *
+   * @return {String}
+   * @api public
+   */
+
+  parse: function(){
+    var block = new nodes.Block, parser;
+    block.line = 0;
+    block.filename = this.filename;
+
+    while ('eos' != this.peek().type) {
+      if ('newline' == this.peek().type) {
+        this.advance();
+      } else {
+        var next = this.peek();
+        var expr = this.parseExpr();
+        expr.filename = expr.filename || this.filename;
+        expr.line = next.line;
+        block.push(expr);
+      }
+    }
+
+    if (parser = this.extending) {
+      this.context(parser);
+      var ast = parser.parse();
+      this.context();
+
+      // hoist mixins
+      for (var name in this.mixins)
+        ast.unshift(this.mixins[name]);
+      return ast;
+    }
+
+    if (!this.extending && !this.included && Object.keys(this.blocks).length){
+      var blocks = [];
+      utils.walkAST(block, function (node) {
+        if (node.type === 'Block' && node.name) {
+          blocks.push(node.name);
+        }
+      });
+      Object.keys(this.blocks).forEach(function (name) {
+        if (blocks.indexOf(name) === -1 && !this.blocks[name].isSubBlock) {
+          console.warn('Warning: Unexpected block "'
+                       + name
+                       + '" '
+                       + ' on line '
+                       + this.blocks[name].line
+                       + ' of '
+                       + (this.blocks[name].filename)
+                       + '. This block is never used. This warning will be an error in v2.0.0');
+        }
+      }.bind(this));
+    }
+
+    return block;
+  },
+
+  /**
+   * Expect the given type, or throw an exception.
+   *
+   * @param {String} type
+   * @api private
+   */
+
+  expect: function(type){
+    if (this.peek().type === type) {
+      return this.advance();
+    } else {
+      throw new Error('expected "' + type + '", but got "' + this.peek().type + '"');
+    }
+  },
+
+  /**
+   * Accept the given `type`.
+   *
+   * @param {String} type
+   * @api private
+   */
+
+  accept: function(type){
+    if (this.peek().type === type) {
+      return this.advance();
+    }
+  },
+
+  /**
+   *   tag
+   * | doctype
+   * | mixin
+   * | include
+   * | filter
+   * | comment
+   * | text
+   * | each
+   * | code
+   * | yield
+   * | id
+   * | class
+   * | interpolation
+   */
+
+  parseExpr: function(){
+    switch (this.peek().type) {
+      case 'tag':
+        return this.parseTag();
+      case 'mixin':
+        return this.parseMixin();
+      case 'block':
+        return this.parseBlock();
+      case 'mixin-block':
+        return this.parseMixinBlock();
+      case 'case':
+        return this.parseCase();
+      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 + '"');
+    }
+  },
+
+  /**
+   * Text
+   */
+
+  parseText: function(){
+    var tok = this.expect('text');
+    var tokens = this.parseInlineTagsInText(tok.val);
+    if (tokens.length === 1) return tokens[0];
+    var node = new nodes.Block;
+    for (var i = 0; i < tokens.length; i++) {
+      node.push(tokens[i]);
+    };
+    return node;
+  },
+
+  /**
+   *   ':' expr
+   * | block
+   */
+
+  parseBlockExpansion: function(){
+    if (':' == this.peek().type) {
+      this.advance();
+      return new nodes.Block(this.parseExpr());
+    } else {
+      return this.block();
+    }
+  },
+
+  /**
+   * case
+   */
+
+  parseCase: function(){
+    var val = this.expect('case').val;
+    var node = new nodes.Case(val);
+    node.line = this.line();
+
+    var block = new nodes.Block;
+    block.line = this.line();
+    block.filename = this.filename;
+    this.expect('indent');
+    while ('outdent' != this.peek().type) {
+      switch (this.peek().type) {
+        case 'comment':
+        case 'newline':
+          this.advance();
+          break;
+        case 'when':
+          block.push(this.parseWhen());
+          break;
+        case 'default':
+          block.push(this.parseDefault());
+          break;
+        default:
+          throw new Error('Unexpected token "' + this.peek().type
+                          + '", expected "when", "default" or "newline"');
+      }
+    }
+    this.expect('outdent');
+
+    node.block = block;
+
+    return node;
+  },
+
+  /**
+   * when
+   */
+
+  parseWhen: function(){
+    var val = this.expect('when').val;
+    if (this.peek().type !== 'newline')
+      return new nodes.Case.When(val, this.parseBlockExpansion());
+    else
+      return new nodes.Case.When(val);
+  },
+
+  /**
+   * default
+   */
+
+  parseDefault: function(){
+    this.expect('default');
+    return new nodes.Case.When('default', this.parseBlockExpansion());
+  },
+
+  /**
+   * code
+   */
+
+  parseCode: function(afterIf){
+    var tok = this.expect('code');
+    var node = new nodes.Code(tok.val, tok.buffer, tok.escape);
+    var block;
+    node.line = this.line();
+
+    // throw an error if an else does not have an if
+    if (tok.isElse && !tok.hasIf) {
+      throw new Error('Unexpected else without if');
+    }
+
+    // handle block
+    block = 'indent' == this.peek().type;
+    if (block) {
+      node.block = this.block();
+    }
+
+    // handle missing block
+    if (tok.requiresBlock && !block) {
+      node.block = new nodes.Block();
+    }
+
+    // mark presense of if for future elses
+    if (tok.isIf && this.peek().isElse) {
+      this.peek().hasIf = true;
+    } else if (tok.isIf && this.peek().type === 'newline' && this.lookahead(2).isElse) {
+      this.lookahead(2).hasIf = true;
+    }
+
+    return node;
+  },
+
+  /**
+   * comment
+   */
+
+  parseComment: function(){
+    var tok = this.expect('comment');
+    var node;
+
+    var block;
+    if (block = this.parseTextBlock()) {
+      node = new nodes.BlockComment(tok.val, block, tok.buffer);
+    } else {
+      node = new nodes.Comment(tok.val, tok.buffer);
+    }
+
+    node.line = this.line();
+    return node;
+  },
+
+  /**
+   * doctype
+   */
+
+  parseDoctype: function(){
+    var tok = this.expect('doctype');
+    var node = new nodes.Doctype(tok.val);
+    node.line = this.line();
+    return node;
+  },
+
+  /**
+   * filter attrs? text-block
+   */
+
+  parseFilter: function(){
+    var tok = this.expect('filter');
+    var attrs = this.accept('attrs');
+    var block;
+
+    block = this.parseTextBlock() || new nodes.Block();
+
+    var options = {};
+    if (attrs) {
+      attrs.attrs.forEach(function (attribute) {
+        options[attribute.name] = constantinople.toConstant(attribute.val);
+      });
+    }
+
+    var node = new nodes.Filter(tok.val, block, options);
+    node.line = this.line();
+    return node;
+  },
+
+  /**
+   * each block
+   */
+
+  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;
+  },
+
+  /**
+   * 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
+   */
+
+  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;
+  },
+
+  /**
+   * 'extends' name
+   */
+
+  parseExtends: function(){
+    var fs = require('fs');
+
+    var path = this.resolvePath(this.expect('extends').val.trim(), 'extends');
+    if ('.jade' != path.substr(-5)) path += '.jade';
+
+    this.dependencies.push(path);
+    var str = fs.readFileSync(path, 'utf8');
+    var parser = new this.constructor(str, path, this.options);
+    parser.dependencies = this.dependencies;
+
+    parser.blocks = this.blocks;
+    parser.included = this.included;
+    parser.contexts = this.contexts;
+    this.extending = parser;
+
+    // TODO: null node
+    return new nodes.Literal('');
+  },
+
+  /**
+   * 'block' name block
+   */
+
+  parseBlock: function(){
+    var block = this.expect('block');
+    var mode = block.mode;
+    var name = block.val.trim();
+
+    var line = block.line;
+
+    this.inBlock++;
+    block = 'indent' == this.peek().type
+      ? this.block()
+      : new nodes.Block(new nodes.Literal(''));
+    this.inBlock--;
+    block.name = name;
+    block.line = line;
+
+    var prev = this.blocks[name] || {prepended: [], appended: []}
+    if (prev.mode === 'replace') return this.blocks[name] = prev;
+
+    var allNodes = prev.prepended.concat(block.nodes).concat(prev.appended);
+
+    switch (mode) {
+      case 'append':
+        prev.appended = prev.parser === this ?
+                        prev.appended.concat(block.nodes) :
+                        block.nodes.concat(prev.appended);
+        break;
+      case 'prepend':
+        prev.prepended = prev.parser === this ?
+                         block.nodes.concat(prev.prepended) :
+                         prev.prepended.concat(block.nodes);
+        break;
+    }
+    block.nodes = allNodes;
+    block.appended = prev.appended;
+    block.prepended = prev.prepended;
+    block.mode = mode;
+    block.parser = this;
+
+    block.isSubBlock = this.inBlock > 0;
+
+    return this.blocks[name] = block;
+  },
+
+  parseMixinBlock: function () {
+    var block = this.expect('mixin-block');
+    if (!this.inMixin) {
+      throw new Error('Anonymous blocks are not allowed unless they are part of a mixin.');
+    }
+    return new nodes.MixinBlock();
+  },
+
+  /**
+   * include block?
+   */
+
+  parseInclude: function(){
+    var fs = require('fs');
+    var tok = this.expect('include');
+
+    var path = this.resolvePath(tok.val.trim(), 'include');
+    this.dependencies.push(path);
+    // has-filter
+    if (tok.filter) {
+      var str = fs.readFileSync(path, 'utf8').replace(/\r/g, '');
+      var options = {filename: path};
+      if (tok.attrs) {
+        tok.attrs.attrs.forEach(function (attribute) {
+          options[attribute.name] = constantinople.toConstant(attribute.val);
+        });
+      }
+      str = filters(tok.filter, str, options);
+      return new nodes.Literal(str);
+    }
+
+    // non-jade
+    if ('.jade' != path.substr(-5)) {
+      var str = fs.readFileSync(path, 'utf8').replace(/\r/g, '');
+      return new nodes.Literal(str);
+    }
+
+    var str = fs.readFileSync(path, 'utf8');
+    var parser = new this.constructor(str, path, this.options);
+    parser.dependencies = this.dependencies;
+
+    parser.blocks = utils.merge({}, this.blocks);
+    parser.included = true;
+
+    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;
+  },
+
+  /**
+   * call ident block
+   */
+
+  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.code) {
+      mixin.block.push(mixin.code);
+      mixin.code = null;
+    }
+    if (mixin.block.isEmpty()) mixin.block = null;
+    return mixin;
+  },
+
+  /**
+   * mixin block
+   */
+
+  parseMixin: function(){
+    var tok = this.expect('mixin');
+    var name = tok.val;
+    var args = tok.args;
+    var mixin;
+
+    // definition
+    if ('indent' == this.peek().type) {
+      this.inMixin++;
+      mixin = new nodes.Mixin(name, args, this.block(), false);
+      this.mixins[name] = mixin;
+      this.inMixin--;
+      return mixin;
+    // call
+    } else {
+      return new nodes.Mixin(name, args, null, true);
+    }
+  },
+
+  parseInlineTagsInText: function (str) {
+    var line = this.line();
+
+    var match = /(\\)?#\[((?:.|\n)*)$/.exec(str);
+    if (match) {
+      if (match[1]) { // escape
+        var text = new nodes.Text(str.substr(0, match.index) + '#[');
+        text.line = line;
+        var rest = this.parseInlineTagsInText(match[2]);
+        if (rest[0].type === 'Text') {
+          text.val += rest[0].val;
+          rest.shift();
+        }
+        return [text].concat(rest);
+      } else {
+        var text = new nodes.Text(str.substr(0, match.index));
+        text.line = line;
+        var buffer = [text];
+        var rest = match[2];
+        var range = parseJSExpression(rest);
+        var inner = new Parser(range.src, this.filename, this.options);
+        buffer.push(inner.parse());
+        return buffer.concat(this.parseInlineTagsInText(rest.substr(range.end + 1)));
+      }
+    } else {
+      var text = new nodes.Text(str);
+      text.line = line;
+      return [text];
+    }
+  },
+
+  /**
+   * indent (text | newline)* outdent
+   */
+
+  parseTextBlock: function(){
+    var block = new nodes.Block;
+    block.line = this.line();
+    var body = this.peek();
+    if (body.type !== 'pipeless-text') return;
+    this.advance();
+    block.nodes = body.val.reduce(function (accumulator, text) {
+      return accumulator.concat(this.parseInlineTagsInText(text));
+    }.bind(this), []);
+    return block;
+  },
+
+  /**
+   * indent expr* outdent
+   */
+
+  block: function(){
+    var block = new nodes.Block;
+    block.line = this.line();
+    block.filename = this.filename;
+    this.expect('indent');
+    while ('outdent' != this.peek().type) {
+      if ('newline' == this.peek().type) {
+        this.advance();
+      } else {
+        var expr = this.parseExpr();
+        expr.filename = this.filename;
+        block.push(expr);
+      }
+    }
+    this.expect('outdent');
+    return block;
+  },
+
+  /**
+   * interpolation (attrs | class | id)* (text | code | ':')? newline* block?
+   */
+
+  parseInterpolation: function(){
+    var tok = this.advance();
+    var tag = new nodes.Tag(tok.val);
+    tag.buffer = true;
+    return this.tag(tag);
+  },
+
+  /**
+   * tag (attrs | class | id)* (text | code | ':')? newline* block?
+   */
+
+  parseTag: function(){
+    var tok = this.advance();
+    var tag = new nodes.Tag(tok.val);
+
+    tag.selfClosing = tok.selfClosing;
+
+    return this.tag(tag);
+  },
+
+  /**
+   * Parse tag.
+   */
+
+  tag: function(tag){
+    tag.line = this.line();
+
+    var seenAttrs = false;
+    // (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':
+            if (seenAttrs) {
+              console.warn(this.filename + ', line ' + this.peek().line + ':\nYou should not have jade tags with multiple attributes.');
+            }
+            seenAttrs = true;
+            var tok = this.advance();
+            var attrs = tok.attrs;
+
+            if (tok.selfClosing) tag.selfClosing = true;
+
+            for (var i = 0; i < attrs.length; i++) {
+              tag.se

<TRUNCATED>


Mime
View raw message