/*! * Stylus - Renderer * Copyright (c) Automattic * MIT Licensed */ /** * Module dependencies. */ var Parser = require('./parser') , EventEmitter = require('events').EventEmitter , Evaluator = require('./visitor/evaluator') , Normalizer = require('./visitor/normalizer') , events = new EventEmitter , utils = require('./utils') , nodes = require('./nodes') , join = require('path').join; /** * Expose `Renderer`. */ module.exports = Renderer; /** * Initialize a new `Renderer` with the given `str` and `options`. * * @param {String} str * @param {Object} options * @api public */ function Renderer(str, options) { options = options || {}; options.globals = options.globals || {}; options.functions = options.functions || {}; options.use = options.use || []; options.use = Array.isArray(options.use) ? options.use : [options.use]; options.imports = [join(__dirname, 'functions')]; options.paths = options.paths || []; options.filename = options.filename || 'stylus'; options.Evaluator = options.Evaluator || Evaluator; this.options = options; this.str = str; this.events = events; }; /** * Inherit from `EventEmitter.prototype`. */ Renderer.prototype.__proto__ = EventEmitter.prototype; /** * Expose events explicitly. */ module.exports.events = events; /** * Parse and evaluate AST, then callback `fn(err, css, js)`. * * @param {Function} fn * @api public */ Renderer.prototype.render = function(fn){ var parser = this.parser = new Parser(this.str, this.options); // use plugin(s) for (var i = 0, len = this.options.use.length; i < len; i++) { this.use(this.options.use[i]); } try { nodes.filename = this.options.filename; // parse var ast = parser.parse(); // evaluate this.evaluator = new this.options.Evaluator(ast, this.options); this.nodes = nodes; this.evaluator.renderer = this; ast = this.evaluator.evaluate(); // normalize var normalizer = new Normalizer(ast, this.options); ast = normalizer.normalize(); // compile var compiler = this.options.sourcemap ? new (require('./visitor/sourcemapper'))(ast, this.options) : new (require('./visitor/compiler'))(ast, this.options) , css = compiler.compile(); // expose sourcemap if (this.options.sourcemap) this.sourcemap = compiler.map.toJSON(); } catch (err) { var options = {}; options.input = err.input || this.str; options.filename = err.filename || this.options.filename; options.lineno = err.lineno || parser.lexer.lineno; options.column = err.column || parser.lexer.column; if (!fn) throw utils.formatException(err, options); return fn(utils.formatException(err, options)); } // fire `end` event var listeners = this.listeners('end'); if (fn) listeners.push(fn); for (var i = 0, len = listeners.length; i < len; i++) { var ret = listeners[i](null, css); if (ret) css = ret; } if (!fn) return css; }; /** * Get dependencies of the compiled file. * * @param {String} [filename] * @return {Array} * @api public */ Renderer.prototype.deps = function(filename){ if (filename) this.options.filename = filename; var DepsResolver = require('./visitor/deps-resolver') , parser = new Parser(this.str, this.options); try { nodes.filename = this.options.filename; // parse var ast = parser.parse() , resolver = new DepsResolver(ast, this.options); // resolve dependencies return resolver.resolve(); } catch (err) { var options = {}; options.input = err.input || this.str; options.filename = err.filename || this.options.filename; options.lineno = err.lineno || parser.lexer.lineno; options.column = err.column || parser.lexer.column; throw utils.formatException(err, options); } }; /** * Set option `key` to `val`. * * @param {String} key * @param {Mixed} val * @return {Renderer} for chaining * @api public */ Renderer.prototype.set = function(key, val){ this.options[key] = val; return this; }; /** * Get option `key`. * * @param {String} key * @return {Mixed} val * @api public */ Renderer.prototype.get = function(key){ return this.options[key]; }; /** * Include the given `path` to the lookup paths array. * * @param {String} path * @return {Renderer} for chaining * @api public */ Renderer.prototype.include = function(path){ this.options.paths.push(path); return this; }; /** * Use the given `fn`. * * This allows for plugins to alter the renderer in * any way they wish, exposing paths etc. * * @param {Function} * @return {Renderer} for chaining * @api public */ Renderer.prototype.use = function(fn){ fn.call(this, this); return this; }; /** * Define function or global var with the given `name`. Optionally * the function may accept full expressions, by setting `raw` * to `true`. * * @param {String} name * @param {Function|Node} fn * @return {Renderer} for chaining * @api public */ Renderer.prototype.define = function(name, fn, raw){ fn = utils.coerce(fn, raw); if (fn.nodeName) { this.options.globals[name] = fn; return this; } // function this.options.functions[name] = fn; if (undefined != raw) fn.raw = raw; return this; }; /** * Import the given `file`. * * @param {String} file * @return {Renderer} for chaining * @api public */ Renderer.prototype.import = function(file){ this.options.imports.push(file); return this; };