Initial commit
[yaffs-website] / node_modules / node-sass / lib / index.js
1 /*!
2  * node-sass: lib/index.js
3  */
4
5 var path = require('path'),
6   clonedeep = require('lodash.clonedeep'),
7   assign = require('lodash.assign'),
8   sass = require('./extensions');
9
10 /**
11  * Require binding
12  */
13
14 var binding = require('./binding')(sass);
15
16 /**
17  * Get input file
18  *
19  * @param {Object} options
20  * @api private
21  */
22
23 function getInputFile(options) {
24   return options.file ? path.resolve(options.file) : null;
25 }
26
27 /**
28  * Get output file
29  *
30  * @param {Object} options
31  * @api private
32  */
33
34 function getOutputFile(options) {
35   var outFile = options.outFile;
36
37   if (!outFile || typeof outFile !== 'string' || (!options.data && !options.file)) {
38     return null;
39   }
40
41   return path.resolve(outFile);
42 }
43
44 /**
45  * Get source map
46  *
47  * @param {Object} options
48  * @api private
49  */
50
51 function getSourceMap(options) {
52   var sourceMap = options.sourceMap;
53
54   if (sourceMap && typeof sourceMap !== 'string' && options.outFile) {
55     sourceMap = options.outFile + '.map';
56   }
57
58   return sourceMap && typeof sourceMap === 'string' ? path.resolve(sourceMap) : null;
59 }
60
61 /**
62  * Get stats
63  *
64  * @param {Object} options
65  * @api private
66  */
67
68 function getStats(options) {
69   var stats = {};
70
71   stats.entry = options.file || 'data';
72   stats.start = Date.now();
73
74   return stats;
75 }
76
77 /**
78  * End stats
79  *
80  * @param {Object} stats
81  * @param {Object} sourceMap
82  * @api private
83  */
84
85 function endStats(stats) {
86   stats.end = Date.now();
87   stats.duration = stats.end - stats.start;
88
89   return stats;
90 }
91
92 /**
93  * Get style
94  *
95  * @param {Object} options
96  * @api private
97  */
98
99 function getStyle(options) {
100   var styles = {
101     nested: 0,
102     expanded: 1,
103     compact: 2,
104     compressed: 3
105   };
106
107   return styles[options.outputStyle] || 0;
108 }
109
110 /**
111  * Get indent width
112  *
113  * @param {Object} options
114  * @api private
115  */
116
117 function getIndentWidth(options) {
118   var width = parseInt(options.indentWidth) || 2;
119
120   return width > 10 ? 2 : width;
121 }
122
123 /**
124  * Get indent type
125  *
126  * @param {Object} options
127  * @api private
128  */
129
130 function getIndentType(options) {
131   var types = {
132     space: 0,
133     tab: 1
134   };
135
136   return types[options.indentType] || 0;
137 }
138
139 /**
140  * Get linefeed
141  *
142  * @param {Object} options
143  * @api private
144  */
145
146 function getLinefeed(options) {
147   var feeds = {
148     cr: '\r',
149     crlf: '\r\n',
150     lf: '\n',
151     lfcr: '\n\r'
152   };
153
154   return feeds[options.linefeed] || '\n';
155 }
156
157 /**
158  * Build an includePaths string
159  * from the options.includePaths array and the SASS_PATH environment variable
160  *
161  * @param {Object} options
162  * @api private
163  */
164
165 function buildIncludePaths(options) {
166   options.includePaths = options.includePaths || [];
167
168   if (process.env.hasOwnProperty('SASS_PATH')) {
169     options.includePaths = options.includePaths.concat(
170       process.env.SASS_PATH.split(path.delimiter)
171     );
172   }
173
174   // Preserve the behaviour people have come to expect.
175   // This behaviour was removed from Sass in 3.4 and
176   // LibSass in 3.5.
177   options.includePaths.unshift(process.cwd());
178
179   return options.includePaths.join(path.delimiter);
180 }
181
182 /**
183  * Get options
184  *
185  * @param {Object} options
186  * @api private
187  */
188
189 function getOptions(opts, cb) {
190   if (typeof opts !== 'object') {
191     throw new Error('Invalid: options is not an object.');
192   }
193   var options = clonedeep(opts || {});
194
195   options.sourceComments = options.sourceComments || false;
196   if (options.hasOwnProperty('file')) {
197     options.file = getInputFile(options);
198   }
199   options.outFile = getOutputFile(options);
200   options.includePaths = buildIncludePaths(options);
201   options.precision = parseInt(options.precision) || 5;
202   options.sourceMap = getSourceMap(options);
203   options.style = getStyle(options);
204   options.indentWidth = getIndentWidth(options);
205   options.indentType = getIndentType(options);
206   options.linefeed = getLinefeed(options);
207
208   // context object represents node-sass environment
209   options.context = { options: options, callback: cb };
210
211   options.result = {
212     stats: getStats(options)
213   };
214
215   return options;
216 }
217
218 /**
219  * Executes a callback and transforms any exception raised into a sass error
220  *
221  * @param {Function} callback
222  * @param {Array} arguments
223  * @api private
224  */
225
226 function tryCallback(callback, args) {
227   try {
228     return callback.apply(this, args);
229   } catch (e) {
230     if (typeof e === 'string') {
231       return new binding.types.Error(e);
232     } else if (e instanceof Error) {
233       return new binding.types.Error(e.message);
234     } else {
235       return new binding.types.Error('An unexpected error occurred');
236     }
237   }
238 }
239
240 /**
241  * Normalizes the signature of custom functions to make it possible to just supply the
242  * function name and have the signature default to `fn(...)`. The callback is adjusted
243  * to transform the input sass list into discrete arguments.
244  *
245  * @param {String} signature
246  * @param {Function} callback
247  * @return {Object}
248  * @api private
249  */
250
251 function normalizeFunctionSignature(signature, callback) {
252   if (!/^\*|@warn|@error|@debug|\w+\(.*\)$/.test(signature)) {
253     if (!/\w+/.test(signature)) {
254       throw new Error('Invalid function signature format "' + signature + '"');
255     }
256
257     return {
258       signature: signature + '(...)',
259       callback: function() {
260         var args = Array.prototype.slice.call(arguments),
261           list = args.shift(),
262           i;
263
264         for (i = list.getLength() - 1; i >= 0; i--) {
265           args.unshift(list.getValue(i));
266         }
267
268         return callback.apply(this, args);
269       }
270     };
271   }
272
273   return {
274     signature: signature,
275     callback: callback
276   };
277 }
278
279 /**
280  * Render
281  *
282  * @param {Object} options
283  * @api public
284  */
285
286 module.exports.render = function(opts, cb) {
287   var options = getOptions(opts, cb);
288
289   // options.error and options.success are for libsass binding
290   options.error = function(err) {
291     var payload = assign(new Error(), JSON.parse(err));
292
293     if (cb) {
294       options.context.callback.call(options.context, payload, null);
295     }
296   };
297
298   options.success = function() {
299     var result = options.result;
300     var stats = endStats(result.stats);
301     var payload = {
302       css: result.css,
303       map: result.map,
304       stats: stats
305     };
306
307     if (cb) {
308       options.context.callback.call(options.context, null, payload);
309     }
310   };
311
312   var importer = options.importer;
313
314   if (importer) {
315     if (Array.isArray(importer)) {
316       options.importer = [];
317       importer.forEach(function(subject, index) {
318         options.importer[index] = function(file, prev, bridge) {
319           function done(result) {
320             bridge.success(result === module.exports.NULL ? null : result);
321           }
322
323           var result = subject.call(options.context, file, prev, done);
324
325           if (result !== undefined) {
326             done(result);
327           }
328         };
329       });
330     } else {
331       options.importer = function(file, prev, bridge) {
332         function done(result) {
333           bridge.success(result === module.exports.NULL ? null : result);
334         }
335
336         var result = importer.call(options.context, file, prev, done);
337
338         if (result !== undefined) {
339           done(result);
340         }
341       };
342     }
343   }
344
345   var functions = clonedeep(options.functions);
346
347   if (functions) {
348     options.functions = {};
349
350     Object.keys(functions).forEach(function(subject) {
351       var cb = normalizeFunctionSignature(subject, functions[subject]);
352
353       options.functions[cb.signature] = function() {
354         var args = Array.prototype.slice.call(arguments),
355           bridge = args.pop();
356
357         function done(data) {
358           bridge.success(data);
359         }
360
361         var result = tryCallback(cb.callback.bind(options.context), args.concat(done));
362
363         if (result) {
364           done(result);
365         }
366       };
367     });
368   }
369
370   if (options.data) {
371     binding.render(options);
372   } else if (options.file) {
373     binding.renderFile(options);
374   } else {
375     cb({status: 3, message: 'No input specified: provide a file name or a source string to process' });
376   }
377 };
378
379 /**
380  * Render sync
381  *
382  * @param {Object} options
383  * @api public
384  */
385
386 module.exports.renderSync = function(opts) {
387   var options = getOptions(opts);
388   var importer = options.importer;
389
390   if (importer) {
391     if (Array.isArray(importer)) {
392       options.importer = [];
393       importer.forEach(function(subject, index) {
394         options.importer[index] = function(file, prev) {
395           var result = subject.call(options.context, file, prev);
396
397           return result === module.exports.NULL ? null : result;
398         };
399       });
400     } else {
401       options.importer = function(file, prev) {
402         var result = importer.call(options.context, file, prev);
403
404         return result === module.exports.NULL ? null : result;
405       };
406     }
407   }
408
409   var functions = clonedeep(options.functions);
410
411   if (options.functions) {
412     options.functions = {};
413
414     Object.keys(functions).forEach(function(signature) {
415       var cb = normalizeFunctionSignature(signature, functions[signature]);
416
417       options.functions[cb.signature] = function() {
418         return tryCallback(cb.callback.bind(options.context), arguments);
419       };
420     });
421   }
422
423   var status;
424   if (options.data) {
425     status = binding.renderSync(options);
426   } else if (options.file) {
427     status = binding.renderFileSync(options);
428   } else {
429     throw new Error('No input specified: provide a file name or a source string to process');
430   }
431
432   var result = options.result;
433
434   if (status) {
435     result.stats = endStats(result.stats);
436     return result;
437   }
438
439   throw assign(new Error(), JSON.parse(result.error));
440 };
441
442 /**
443  * API Info
444  *
445  * @api public
446  */
447
448 module.exports.info = sass.getVersionInfo(binding);
449
450 /**
451  * Expose sass types
452  */
453
454 module.exports.types = binding.types;
455 module.exports.TRUE = binding.types.Boolean.TRUE;
456 module.exports.FALSE = binding.types.Boolean.FALSE;
457 module.exports.NULL = binding.types.Null.NULL;
458
459 /**
460  * Polyfill the old API
461  *
462  * TODO: remove for 4.0
463  */
464
465 function processSassDeprecationMessage() {
466   console.log('Deprecation warning: `process.sass` is an undocumented internal that will be removed in future versions of Node Sass.');
467 }
468
469 process.sass = process.sass || {
470   get versionInfo()   { processSassDeprecationMessage(); return module.exports.info; },
471   get binaryName()    { processSassDeprecationMessage(); return sass.getBinaryName(); },
472   get binaryUrl()     { processSassDeprecationMessage(); return sass.getBinaryUrl(); },
473   get binaryPath()    { processSassDeprecationMessage(); return sass.getBinaryPath(); },
474   get getBinaryPath() { processSassDeprecationMessage(); return sass.getBinaryPath; },
475 };