version app + node.exe + nw.exe
This commit is contained in:
723
node_modules/jade/lib/compiler.js
generated
vendored
Normal file
723
node_modules/jade/lib/compiler.js
generated
vendored
Normal file
@@ -0,0 +1,723 @@
|
||||
'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(new jade.DebugItem( ' + node.line
|
||||
+ ', ' + (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(/^\.\.\./, '');
|
||||
}
|
||||
// we need use jade_interp here for v8: https://code.google.com/p/v8/issues/detail?id=4165
|
||||
// once fixed, use this: this.buf.push(name + ' = function(' + args.join(',') + '){');
|
||||
this.buf.push(name + ' = jade_interp = 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(',') + '}';
|
||||
}
|
||||
};
|
||||
12
node_modules/jade/lib/doctypes.js
generated
vendored
Normal file
12
node_modules/jade/lib/doctypes.js
generated
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
'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">'
|
||||
};
|
||||
10
node_modules/jade/lib/filters-client.js
generated
vendored
Normal file
10
node_modules/jade/lib/filters-client.js
generated
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
'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 + '"');
|
||||
}
|
||||
}
|
||||
96
node_modules/jade/lib/filters.js
generated
vendored
Normal file
96
node_modules/jade/lib/filters.js
generated
vendored
Normal file
@@ -0,0 +1,96 @@
|
||||
'use strict';
|
||||
|
||||
var transformers = require('transformers');
|
||||
var jstransformer = require('jstransformer');
|
||||
var uglify = require('uglify-js');
|
||||
var CleanCSS = require('clean-css');
|
||||
|
||||
var warned = {};
|
||||
var alternatives = {
|
||||
uglifyJS: 'uglify-js',
|
||||
uglify: 'uglify-js',
|
||||
uglifyCSS: 'clean-css',
|
||||
'uglify-css': 'clean-css' ,
|
||||
uglifyJSON: 'json',
|
||||
'uglify-json': 'json',
|
||||
live: 'livescript',
|
||||
LiveScript: 'livescript',
|
||||
ls: 'livescript',
|
||||
// TODO: remove if we add support for coffeekup
|
||||
coffeekup: 'coffeecup',
|
||||
// The `style` transformer is not the same as the `stylus` jstransformer
|
||||
styl: 'stylus',
|
||||
coffee: 'coffee-script',
|
||||
coffeescript: 'coffee-script',
|
||||
coffeeScript: 'coffee-script',
|
||||
// these marker transformers haven't made sense in a long time
|
||||
css: 'verbatim',
|
||||
js: 'verbatim',
|
||||
};
|
||||
var deprecated = ['jqtpl', 'jazz'];
|
||||
function getMarkdownImplementation() {
|
||||
var implementations = ['marked', 'supermarked', 'markdown-js', 'markdown'];
|
||||
while (implementations.length) {
|
||||
try {
|
||||
require(implementations[0]);
|
||||
return implementations[0];
|
||||
} catch (ex) {
|
||||
implementations.shift();
|
||||
}
|
||||
}
|
||||
return 'markdown-it';
|
||||
}
|
||||
|
||||
module.exports = filter;
|
||||
function filter(name, str, options) {
|
||||
if (typeof filter[name] === 'function') {
|
||||
return filter[name](str, options);
|
||||
} else {
|
||||
var tr;
|
||||
try {
|
||||
tr = jstransformer(require('jstransformer-' + name));
|
||||
} catch (ex) {}
|
||||
if (tr) {
|
||||
// TODO: we may want to add a way for people to separately specify "locals"
|
||||
var result = tr.render(str, options, options).body;
|
||||
if (options && options.minify) {
|
||||
try {
|
||||
switch (tr.outputFormat) {
|
||||
case 'js':
|
||||
result = uglify.minify(result, {fromString: true}).code;
|
||||
break;
|
||||
case 'css':
|
||||
result = new CleanCSS().minify(result).styles;
|
||||
break;
|
||||
}
|
||||
} catch (ex) {
|
||||
// better to fail to minify than output nothing
|
||||
}
|
||||
}
|
||||
return result;
|
||||
} else if (transformers[name]) {
|
||||
if (!warned[name]) {
|
||||
warned[name] = true;
|
||||
if (name === 'md' || name === 'markdown') {
|
||||
var implementation = getMarkdownImplementation();
|
||||
console.log('Transformers.' + name + ' is deprecated, you must replace the :' +
|
||||
name + ' jade filter, with :' +
|
||||
implementation + ' and install jstransformer-' +
|
||||
implementation + ' before you update to jade@2.0.0.');
|
||||
} else if (alternatives[name]) {
|
||||
console.log('Transformers.' + name + ' is deprecated, you must replace the :' +
|
||||
name + ' jade filter, with :' +
|
||||
alternatives[name] + ' and install jstransformer-' +
|
||||
alternatives[name] + ' before you update to jade@2.0.0.');
|
||||
} else {
|
||||
console.log('Transformers.' + name + ' is deprecated, to continue using the :' +
|
||||
name + ' jade filter after jade@2.0.0, you will need to install jstransformer-' +
|
||||
name.toLowerCase() + '.');
|
||||
}
|
||||
}
|
||||
return transformers[name].renderSync(str, options);
|
||||
} else {
|
||||
throw new Error('unknown filter ":' + name + '"');
|
||||
}
|
||||
}
|
||||
}
|
||||
418
node_modules/jade/lib/index.js
generated
vendored
Normal file
418
node_modules/jade/lib/index.js
generated
vendored
Normal file
@@ -0,0 +1,418 @@
|
||||
'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 = [ new jade.DebugItem( 1, ' + 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 = [ new jade.DebugItem( 1, ' + 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 = function(path, options, fn) {
|
||||
if(options.compileDebug == undefined && process.env.NODE_ENV === 'production') {
|
||||
options.compileDebug = false;
|
||||
}
|
||||
exports.renderFile(path, options, fn);
|
||||
}
|
||||
23
node_modules/jade/lib/inline-tags.js
generated
vendored
Normal file
23
node_modules/jade/lib/inline-tags.js
generated
vendored
Normal file
@@ -0,0 +1,23 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = [
|
||||
'a'
|
||||
, 'abbr'
|
||||
, 'acronym'
|
||||
, 'b'
|
||||
, 'br'
|
||||
, 'code'
|
||||
, 'em'
|
||||
, 'font'
|
||||
, 'i'
|
||||
, 'img'
|
||||
, 'ins'
|
||||
, 'kbd'
|
||||
, 'map'
|
||||
, 'samp'
|
||||
, 'small'
|
||||
, 'span'
|
||||
, 'strong'
|
||||
, 'sub'
|
||||
, 'sup'
|
||||
];
|
||||
949
node_modules/jade/lib/lexer.js
generated
vendored
Normal file
949
node_modules/jade/lib/lexer.js
generated
vendored
Normal file
@@ -0,0 +1,949 @@
|
||||
'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;
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* Block code.
|
||||
*/
|
||||
|
||||
blockCode: function() {
|
||||
var captures;
|
||||
if (captures = /^-\n/.exec(this.input)) {
|
||||
this.consume(captures[0].length - 1);
|
||||
var tok = this.tok('blockCode');
|
||||
this.pipeless = true;
|
||||
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);
|
||||
++this.lineno;
|
||||
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.blockCode()
|
||||
|| this.code()
|
||||
|| this.id()
|
||||
|| this.className()
|
||||
|| this.attrs()
|
||||
|| this.attributesBlock()
|
||||
|| this.indent()
|
||||
|| this.text()
|
||||
|| this.comment()
|
||||
|| this.colon()
|
||||
|| this.dot()
|
||||
|| this.textFail()
|
||||
|| this.fail();
|
||||
}
|
||||
};
|
||||
83
node_modules/jade/lib/nodes/attrs.js
generated
vendored
Normal file
83
node_modules/jade/lib/nodes/attrs.js
generated
vendored
Normal file
@@ -0,0 +1,83 @@
|
||||
'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);
|
||||
};
|
||||
24
node_modules/jade/lib/nodes/block-comment.js
generated
vendored
Normal file
24
node_modules/jade/lib/nodes/block-comment.js
generated
vendored
Normal file
@@ -0,0 +1,24 @@
|
||||
'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';
|
||||
118
node_modules/jade/lib/nodes/block.js
generated
vendored
Normal file
118
node_modules/jade/lib/nodes/block.js
generated
vendored
Normal file
@@ -0,0 +1,118 @@
|
||||
'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;
|
||||
};
|
||||
33
node_modules/jade/lib/nodes/case.js
generated
vendored
Normal file
33
node_modules/jade/lib/nodes/case.js
generated
vendored
Normal file
@@ -0,0 +1,33 @@
|
||||
'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';
|
||||
26
node_modules/jade/lib/nodes/code.js
generated
vendored
Normal file
26
node_modules/jade/lib/nodes/code.js
generated
vendored
Normal file
@@ -0,0 +1,26 @@
|
||||
'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
|
||||
23
node_modules/jade/lib/nodes/comment.js
generated
vendored
Normal file
23
node_modules/jade/lib/nodes/comment.js
generated
vendored
Normal file
@@ -0,0 +1,23 @@
|
||||
'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';
|
||||
20
node_modules/jade/lib/nodes/doctype.js
generated
vendored
Normal file
20
node_modules/jade/lib/nodes/doctype.js
generated
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
'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';
|
||||
26
node_modules/jade/lib/nodes/each.js
generated
vendored
Normal file
26
node_modules/jade/lib/nodes/each.js
generated
vendored
Normal file
@@ -0,0 +1,26 @@
|
||||
'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';
|
||||
24
node_modules/jade/lib/nodes/filter.js
generated
vendored
Normal file
24
node_modules/jade/lib/nodes/filter.js
generated
vendored
Normal file
@@ -0,0 +1,24 @@
|
||||
'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';
|
||||
16
node_modules/jade/lib/nodes/index.js
generated
vendored
Normal file
16
node_modules/jade/lib/nodes/index.js
generated
vendored
Normal file
@@ -0,0 +1,16 @@
|
||||
'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');
|
||||
20
node_modules/jade/lib/nodes/literal.js
generated
vendored
Normal file
20
node_modules/jade/lib/nodes/literal.js
generated
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
'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';
|
||||
18
node_modules/jade/lib/nodes/mixin-block.js
generated
vendored
Normal file
18
node_modules/jade/lib/nodes/mixin-block.js
generated
vendored
Normal file
@@ -0,0 +1,18 @@
|
||||
'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';
|
||||
26
node_modules/jade/lib/nodes/mixin.js
generated
vendored
Normal file
26
node_modules/jade/lib/nodes/mixin.js
generated
vendored
Normal file
@@ -0,0 +1,26 @@
|
||||
'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';
|
||||
18
node_modules/jade/lib/nodes/node.js
generated
vendored
Normal file
18
node_modules/jade/lib/nodes/node.js
generated
vendored
Normal file
@@ -0,0 +1,18 @@
|
||||
'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 = '';
|
||||
89
node_modules/jade/lib/nodes/tag.js
generated
vendored
Normal file
89
node_modules/jade/lib/nodes/tag.js
generated
vendored
Normal file
@@ -0,0 +1,89 @@
|
||||
'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;
|
||||
};
|
||||
26
node_modules/jade/lib/nodes/text.js
generated
vendored
Normal file
26
node_modules/jade/lib/nodes/text.js
generated
vendored
Normal file
@@ -0,0 +1,26 @@
|
||||
'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;
|
||||
846
node_modules/jade/lib/parser.js
generated
vendored
Normal file
846
node_modules/jade/lib/parser.js
generated
vendored
Normal file
@@ -0,0 +1,846 @@
|
||||
'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 'blockCode':
|
||||
return this.parseBlockCode();
|
||||
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;
|
||||
},
|
||||
|
||||
/**
|
||||
* block code
|
||||
*/
|
||||
|
||||
parseBlockCode: function(){
|
||||
var tok = this.expect('blockCode');
|
||||
var node;
|
||||
var body = this.peek();
|
||||
var text;
|
||||
if (body.type === 'pipeless-text') {
|
||||
this.advance();
|
||||
text = body.val.join('\n');
|
||||
} else {
|
||||
text = '';
|
||||
}
|
||||
node = new nodes.Code(text, false, false);
|
||||
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.setAttribute(attrs[i].name, attrs[i].val, attrs[i].escaped);
|
||||
}
|
||||
continue;
|
||||
case '&attributes':
|
||||
var tok = this.advance();
|
||||
tag.addAttributes(tok.val);
|
||||
break;
|
||||
default:
|
||||
break out;
|
||||
}
|
||||
}
|
||||
|
||||
// check immediate '.'
|
||||
if ('dot' == this.peek().type) {
|
||||
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;
|
||||
case 'newline':
|
||||
case 'indent':
|
||||
case 'outdent':
|
||||
case 'eos':
|
||||
case 'pipeless-text':
|
||||
break;
|
||||
default:
|
||||
throw new Error('Unexpected token `' + this.peek().type + '` expected `text`, `code`, `:`, `newline` or `eos`')
|
||||
}
|
||||
|
||||
// newline*
|
||||
while ('newline' == this.peek().type) this.advance();
|
||||
|
||||
// block?
|
||||
if (tag.textOnly) {
|
||||
tag.block = this.parseTextBlock() || new nodes.Block();
|
||||
} else if ('indent' == this.peek().type) {
|
||||
var block = this.block();
|
||||
for (var i = 0, len = block.nodes.length; i < len; ++i) {
|
||||
tag.block.push(block.nodes[i]);
|
||||
}
|
||||
}
|
||||
|
||||
return tag;
|
||||
}
|
||||
};
|
||||
246
node_modules/jade/lib/runtime.js
generated
vendored
Normal file
246
node_modules/jade/lib/runtime.js
generated
vendored
Normal file
@@ -0,0 +1,246 @@
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* 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) {
|
||||
if (arguments.length === 1) {
|
||||
var attrs = a[0];
|
||||
for (var i = 1; i < a.length; i++) {
|
||||
attrs = merge(attrs, a[i]);
|
||||
}
|
||||
return attrs;
|
||||
}
|
||||
var ac = a['class'];
|
||||
var bc = b['class'];
|
||||
|
||||
if (ac || bc) {
|
||||
ac = ac || [];
|
||||
bc = bc || [];
|
||||
if (!Array.isArray(ac)) ac = [ac];
|
||||
if (!Array.isArray(bc)) bc = [bc];
|
||||
a['class'] = ac.concat(bc).filter(nulls);
|
||||
}
|
||||
|
||||
for (var key in b) {
|
||||
if (key != 'class') {
|
||||
a[key] = b[key];
|
||||
}
|
||||
}
|
||||
|
||||
return a;
|
||||
};
|
||||
|
||||
/**
|
||||
* Filter null `val`s.
|
||||
*
|
||||
* @param {*} val
|
||||
* @return {Boolean}
|
||||
* @api private
|
||||
*/
|
||||
|
||||
function nulls(val) {
|
||||
return val != null && val !== '';
|
||||
}
|
||||
|
||||
/**
|
||||
* join array as classes.
|
||||
*
|
||||
* @param {*} val
|
||||
* @return {String}
|
||||
*/
|
||||
exports.joinClasses = joinClasses;
|
||||
function joinClasses(val) {
|
||||
return (Array.isArray(val) ? val.map(joinClasses) :
|
||||
(val && typeof val === 'object') ? Object.keys(val).filter(function (key) { return val[key]; }) :
|
||||
[val]).filter(nulls).join(' ');
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the given classes.
|
||||
*
|
||||
* @param {Array} classes
|
||||
* @param {Array.<Boolean>} escaped
|
||||
* @return {String}
|
||||
*/
|
||||
exports.cls = function cls(classes, escaped) {
|
||||
var buf = [];
|
||||
for (var i = 0; i < classes.length; i++) {
|
||||
if (escaped && escaped[i]) {
|
||||
buf.push(exports.escape(joinClasses([classes[i]])));
|
||||
} else {
|
||||
buf.push(joinClasses(classes[i]));
|
||||
}
|
||||
}
|
||||
var text = joinClasses(buf);
|
||||
if (text.length) {
|
||||
return ' class="' + text + '"';
|
||||
} else {
|
||||
return '';
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
exports.style = function (val) {
|
||||
if (val && typeof val === 'object') {
|
||||
return Object.keys(val).map(function (style) {
|
||||
return style + ':' + val[style];
|
||||
}).join(';');
|
||||
} else {
|
||||
return val;
|
||||
}
|
||||
};
|
||||
/**
|
||||
* Render the given attribute.
|
||||
*
|
||||
* @param {String} key
|
||||
* @param {String} val
|
||||
* @param {Boolean} escaped
|
||||
* @param {Boolean} terse
|
||||
* @return {String}
|
||||
*/
|
||||
exports.attr = function attr(key, val, escaped, terse) {
|
||||
if (key === 'style') {
|
||||
val = exports.style(val);
|
||||
}
|
||||
if ('boolean' == typeof val || null == val) {
|
||||
if (val) {
|
||||
return ' ' + (terse ? key : key + '="' + key + '"');
|
||||
} else {
|
||||
return '';
|
||||
}
|
||||
} else if (0 == key.indexOf('data') && 'string' != typeof val) {
|
||||
if (JSON.stringify(val).indexOf('&') !== -1) {
|
||||
console.warn('Since Jade 2.0.0, ampersands (`&`) in data attributes ' +
|
||||
'will be escaped to `&`');
|
||||
};
|
||||
if (val && typeof val.toISOString === 'function') {
|
||||
console.warn('Jade will eliminate the double quotes around dates in ' +
|
||||
'ISO form after 2.0.0');
|
||||
}
|
||||
return ' ' + key + "='" + JSON.stringify(val).replace(/'/g, ''') + "'";
|
||||
} else if (escaped) {
|
||||
if (val && typeof val.toISOString === 'function') {
|
||||
console.warn('Jade will stringify dates in ISO form after 2.0.0');
|
||||
}
|
||||
return ' ' + key + '="' + exports.escape(val) + '"';
|
||||
} else {
|
||||
if (val && typeof val.toISOString === 'function') {
|
||||
console.warn('Jade will stringify dates in ISO form after 2.0.0');
|
||||
}
|
||||
return ' ' + key + '="' + val + '"';
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Render the given attributes object.
|
||||
*
|
||||
* @param {Object} obj
|
||||
* @param {Object} escaped
|
||||
* @return {String}
|
||||
*/
|
||||
exports.attrs = function attrs(obj, terse){
|
||||
var buf = [];
|
||||
|
||||
var keys = Object.keys(obj);
|
||||
|
||||
if (keys.length) {
|
||||
for (var i = 0; i < keys.length; ++i) {
|
||||
var key = keys[i]
|
||||
, val = obj[key];
|
||||
|
||||
if ('class' == key) {
|
||||
if (val = joinClasses(val)) {
|
||||
buf.push(' ' + key + '="' + val + '"');
|
||||
}
|
||||
} else {
|
||||
buf.push(exports.attr(key, val, false, terse));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return buf.join('');
|
||||
};
|
||||
|
||||
/**
|
||||
* Escape the given string of `html`.
|
||||
*
|
||||
* @param {String} html
|
||||
* @return {String}
|
||||
* @api private
|
||||
*/
|
||||
|
||||
var jade_encode_html_rules = {
|
||||
'&': '&',
|
||||
'<': '<',
|
||||
'>': '>',
|
||||
'"': '"'
|
||||
};
|
||||
var jade_match_html = /[&<>"]/g;
|
||||
|
||||
function jade_encode_char(c) {
|
||||
return jade_encode_html_rules[c] || c;
|
||||
}
|
||||
|
||||
exports.escape = jade_escape;
|
||||
function jade_escape(html){
|
||||
var result = String(html).replace(jade_match_html, jade_encode_char);
|
||||
if (result === '' + html) return html;
|
||||
else return result;
|
||||
};
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
|
||||
exports.rethrow = function rethrow(err, filename, lineno, str){
|
||||
if (!(err instanceof Error)) throw err;
|
||||
if ((typeof window != 'undefined' || !filename) && !str) {
|
||||
err.message += ' on line ' + lineno;
|
||||
throw err;
|
||||
}
|
||||
try {
|
||||
str = str || require('fs').readFileSync(filename, 'utf8')
|
||||
} catch (ex) {
|
||||
rethrow(err, null, lineno)
|
||||
}
|
||||
var context = 3
|
||||
, lines = str.split('\n')
|
||||
, start = Math.max(lineno - context, 0)
|
||||
, end = Math.min(lines.length, lineno + context);
|
||||
|
||||
// Error context
|
||||
var context = lines.slice(start, end).map(function(line, i){
|
||||
var curr = i + start + 1;
|
||||
return (curr == lineno ? ' > ' : ' ')
|
||||
+ curr
|
||||
+ '| '
|
||||
+ line;
|
||||
}).join('\n');
|
||||
|
||||
// Alter exception message
|
||||
err.path = filename;
|
||||
err.message = (filename || 'Jade') + ':' + lineno
|
||||
+ '\n' + context + '\n\n' + err.message;
|
||||
throw err;
|
||||
};
|
||||
|
||||
exports.DebugItem = function DebugItem(lineno, filename) {
|
||||
this.lineno = lineno;
|
||||
this.filename = filename;
|
||||
}
|
||||
53
node_modules/jade/lib/utils.js
generated
vendored
Normal file
53
node_modules/jade/lib/utils.js
generated
vendored
Normal file
@@ -0,0 +1,53 @@
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* Merge `b` into `a`.
|
||||
*
|
||||
* @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;
|
||||
};
|
||||
|
||||
exports.stringify = function(str) {
|
||||
return JSON.stringify(str)
|
||||
.replace(/\u2028/g, '\\u2028')
|
||||
.replace(/\u2029/g, '\\u2029');
|
||||
};
|
||||
|
||||
exports.walkAST = function walkAST(ast, before, after) {
|
||||
before && before(ast);
|
||||
switch (ast.type) {
|
||||
case 'Block':
|
||||
ast.nodes.forEach(function (node) {
|
||||
walkAST(node, before, after);
|
||||
});
|
||||
break;
|
||||
case 'Case':
|
||||
case 'Each':
|
||||
case 'Mixin':
|
||||
case 'Tag':
|
||||
case 'When':
|
||||
case 'Code':
|
||||
ast.block && walkAST(ast.block, before, after);
|
||||
break;
|
||||
case 'Attrs':
|
||||
case 'BlockComment':
|
||||
case 'Comment':
|
||||
case 'Doctype':
|
||||
case 'Filter':
|
||||
case 'Literal':
|
||||
case 'MixinBlock':
|
||||
case 'Text':
|
||||
break;
|
||||
default:
|
||||
throw new Error('Unexpected node type ' + ast.type);
|
||||
break;
|
||||
}
|
||||
after && after(ast);
|
||||
};
|
||||
Reference in New Issue
Block a user