UNPKG

5.29 kBJavaScriptView Raw
1
2/*!
3 * Stylus - Renderer
4 * Copyright(c) 2010 LearnBoost <dev@learnboost.com>
5 * MIT Licensed
6 */
7
8/**
9 * Module dependencies.
10 */
11
12var Parser = require('./parser')
13 , EventEmitter = require('events').EventEmitter
14 , Evaluator = require('./visitor/evaluator')
15 , Normalizer = require('./visitor/normalizer')
16 , events = new EventEmitter
17 , utils = require('./utils')
18 , nodes = require('./nodes')
19 , join = require('path').join;
20
21/**
22 * Expose `Renderer`.
23 */
24
25module.exports = Renderer;
26
27/**
28 * Initialize a new `Renderer` with the given `str` and `options`.
29 *
30 * @param {String} str
31 * @param {Object} options
32 * @api public
33 */
34
35function Renderer(str, options) {
36 options = options || {};
37 options.globals = options.globals || {};
38 options.functions = options.functions || {};
39 options.imports = [join(__dirname, 'functions')];
40 options.paths = options.paths || [];
41 options.filename = options.filename || 'stylus';
42 options.Evaluator = options.Evaluator || Evaluator;
43 this.options = options;
44 this.str = str;
45 this.events = events;
46};
47
48/**
49 * Inherit from `EventEmitter.prototype`.
50 */
51
52Renderer.prototype.__proto__ = EventEmitter.prototype;
53
54/**
55 * Expose events explicitly.
56 */
57
58module.exports.events = events;
59
60/**
61 * Parse and evaluate AST, then callback `fn(err, css, js)`.
62 *
63 * @param {Function} fn
64 * @api public
65 */
66
67Renderer.prototype.render = function(fn){
68 var parser = this.parser = new Parser(this.str, this.options);
69 try {
70 nodes.filename = this.options.filename;
71 // parse
72 var ast = parser.parse();
73
74 // evaluate
75 this.evaluator = new this.options.Evaluator(ast, this.options);
76 this.nodes = nodes;
77 this.evaluator.renderer = this;
78 ast = this.evaluator.evaluate();
79
80 // normalize
81 var normalizer = new Normalizer(ast, this.options);
82 ast = normalizer.normalize();
83
84 // compile
85 var compiler = this.options.sourcemap
86 ? new (require('./visitor/sourcemapper'))(ast, this.options)
87 : new (require('./visitor/compiler'))(ast, this.options)
88 , css = compiler.compile();
89
90 // expose sourcemap
91 if (this.options.sourcemap) this.sourcemap = compiler.map.toJSON();
92
93 var listeners = this.listeners('end');
94 if (fn) listeners.push(fn);
95 for (var i = 0, len = listeners.length; i < len; i++) {
96 var ret = listeners[i](null, css);
97 if (ret) css = ret;
98 }
99 if (!fn) return css;
100 } catch (err) {
101 var options = {};
102 options.input = err.input || this.str;
103 options.filename = err.filename || this.options.filename;
104 options.lineno = err.lineno || parser.lexer.prev.lineno;
105 options.column = err.column || parser.lexer.prev.column;
106 if (!fn) throw utils.formatException(err, options);
107 fn(utils.formatException(err, options));
108 }
109};
110
111/**
112 * Get dependencies of the compiled file.
113 *
114 * @param {String} [filename]
115 * @return {Array}
116 * @api public
117 */
118
119Renderer.prototype.deps = function(filename){
120 if (filename) this.options.filename = filename;
121
122 var DepsResolver = require('./visitor/deps-resolver')
123 , parser = new Parser(this.str, this.options);
124
125 try {
126 nodes.filename = this.options.filename;
127 // parse
128 var ast = parser.parse()
129 , resolver = new DepsResolver(ast, this.options);
130
131 // resolve dependencies
132 return resolver.resolve();
133 } catch (err) {
134 var options = {};
135 options.input = err.input || this.str;
136 options.filename = err.filename || this.options.filename;
137 options.lineno = err.lineno || parser.lexer.prev.lineno;
138 options.column = err.column || parser.lexer.prev.column;
139 throw utils.formatException(err, options);
140 }
141};
142
143/**
144 * Set option `key` to `val`.
145 *
146 * @param {String} key
147 * @param {Mixed} val
148 * @return {Renderer} for chaining
149 * @api public
150 */
151
152Renderer.prototype.set = function(key, val){
153 this.options[key] = val;
154 return this;
155};
156
157/**
158 * Get option `key`.
159 *
160 * @param {String} key
161 * @return {Mixed} val
162 * @api public
163 */
164
165Renderer.prototype.get = function(key){
166 return this.options[key];
167};
168
169/**
170 * Include the given `path` to the lookup paths array.
171 *
172 * @param {String} path
173 * @return {Renderer} for chaining
174 * @api public
175 */
176
177Renderer.prototype.include = function(path){
178 this.options.paths.push(path);
179 return this;
180};
181
182/**
183 * Use the given `fn`.
184 *
185 * This allows for plugins to alter the renderer in
186 * any way they wish, exposing paths etc.
187 *
188 * @param {Function}
189 * @return {Renderer} for chaining
190 * @api public
191 */
192
193Renderer.prototype.use = function(fn){
194 fn.call(this, this);
195 return this;
196};
197
198/**
199 * Define function or global var with the given `name`. Optionally
200 * the function may accept full expressions, by setting `raw`
201 * to `true`.
202 *
203 * @param {String} name
204 * @param {Function|Node} fn
205 * @return {Renderer} for chaining
206 * @api public
207 */
208
209Renderer.prototype.define = function(name, fn, raw){
210 fn = utils.coerce(fn, raw);
211
212 if (fn.nodeName) {
213 this.options.globals[name] = fn;
214 return this;
215 }
216
217 // function
218 this.options.functions[name] = fn;
219 if (undefined != raw) fn.raw = raw;
220 return this;
221};
222
223/**
224 * Import the given `file`.
225 *
226 * @param {String} file
227 * @return {Renderer} for chaining
228 * @api public
229 */
230
231Renderer.prototype.import = function(file){
232 this.options.imports.push(file);
233 return this;
234};
235
236