UNPKG

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