UNPKG

jade

Version:

Jade template engine

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