'use strict'; /*! * Jade * Copyright(c) 2010 TJ Holowaychuk * MIT Licensed */ /** * Module dependencies. */ var Parser = require('./parser') , Lexer = require('./lexer') , Compiler = require('./compiler') , runtime = require('./runtime') , addWith = require('with') , fs = require('fs'); /** * Expose self closing tags. */ exports.selfClosing = require('void-elements'); /** * Default supported doctypes. */ exports.doctypes = require('./doctypes'); /** * Text filters. */ exports.filters = require('./filters'); /** * Utilities. */ exports.utils = require('./utils'); /** * Expose `Compiler`. */ exports.Compiler = Compiler; /** * Expose `Parser`. */ exports.Parser = Parser; /** * Expose `Lexer`. */ exports.Lexer = Lexer; /** * Nodes. */ exports.nodes = require('./nodes'); /** * Jade runtime helpers. */ exports.runtime = runtime; /** * Template function cache. */ exports.cache = {}; /** * Parse the given `str` of jade and return a function body. * * @param {String} str * @param {Object} options * @return {Object} * @api private */ function parse(str, options){ // 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); } } // Debug compiler if (options.debug) { console.error('\nCompiled Function:\n\n\u001b[90m%s\u001b[0m', js.replace(/^/gm, ' ')); } var globals = []; 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}; } /** * 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 ? JSON.stringify(options.filename) : 'undefined' , fn; str = String(str); var parsed = parse(str, options); if (options.compileDebug !== false) { fn = [ 'var jade_debug = [{ lineno: 1, filename: ' + filename + ' }];' , 'try {' , parsed.body , '} catch (err) {' , ' jade.rethrow(err, jade_debug[0].filename, jade_debug[0].lineno' + (options.compileDebug === true ? ',' + JSON.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 || 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 {String} * @api public */ exports.compileClient = function(str, options){ var options = options || {}; var name = options.name || 'template'; var filename = options.filename ? JSON.stringify(options.filename) : 'undefined'; var fn; str = String(str); if (options.compileDebug) { options.compileDebug = true; fn = [ 'var jade_debug = [{ lineno: 1, filename: ' + filename + ' }];' , 'try {' , parse(str, options).body , '} catch (err) {' , ' jade.rethrow(err, jade_debug[0].filename, jade_debug[0].lineno, ' + JSON.stringify(str) + ');' , '}' ].join('\n'); } else { options.compileDebug = false; fn = parse(str, options).body; } return 'function ' + name + '(locals) {\n' + fn + '\n}'; }; /** * 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 || {}; var key = path + ':string'; options.filename = path; var str = options.cache ? exports.cache[key] || (exports.cache[key] = fs.readFileSync(path, 'utf8')) : fs.readFileSync(path, 'utf8'); return options.cache ? exports.cache[path] || (exports.cache[path] = exports.compile(str, options)) : exports.compile(str, 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'); } var path = options.filename; var tmpl = options.cache ? exports.cache[path] || (exports.cache[path] = exports.compile(str, options)) : exports.compile(str, options); return tmpl(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 || {}; var key = path + ':string'; options.filename = path; var str = options.cache ? exports.cache[key] || (exports.cache[key] = fs.readFileSync(path, 'utf8')) : fs.readFileSync(path, 'utf8'); return exports.render(str, 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){ options = options || {}; var key = path + ':string'; options.filename = path; var str = options.cache ? exports.cache[key] || (exports.cache[key] = fs.readFileSync(path, 'utf8')) : fs.readFileSync(path, 'utf8'); return exports.compileClient(str, options); }; /** * Express support. */ exports.__express = exports.renderFile;