2 * node-sass: lib/index.js
5 var path = require('path'),
6 clonedeep = require('lodash.clonedeep'),
7 assign = require('lodash.assign'),
8 sass = require('./extensions');
14 var binding = require('./binding')(sass);
19 * @param {Object} options
23 function getInputFile(options) {
24 return options.file ? path.resolve(options.file) : null;
30 * @param {Object} options
34 function getOutputFile(options) {
35 var outFile = options.outFile;
37 if (!outFile || typeof outFile !== 'string' || (!options.data && !options.file)) {
41 return path.resolve(outFile);
47 * @param {Object} options
51 function getSourceMap(options) {
52 var sourceMap = options.sourceMap;
54 if (sourceMap && typeof sourceMap !== 'string' && options.outFile) {
55 sourceMap = options.outFile + '.map';
58 return sourceMap && typeof sourceMap === 'string' ? path.resolve(sourceMap) : null;
64 * @param {Object} options
68 function getStats(options) {
71 stats.entry = options.file || 'data';
72 stats.start = Date.now();
80 * @param {Object} stats
81 * @param {Object} sourceMap
85 function endStats(stats) {
86 stats.end = Date.now();
87 stats.duration = stats.end - stats.start;
95 * @param {Object} options
99 function getStyle(options) {
107 return styles[options.outputStyle] || 0;
113 * @param {Object} options
117 function getIndentWidth(options) {
118 var width = parseInt(options.indentWidth) || 2;
120 return width > 10 ? 2 : width;
126 * @param {Object} options
130 function getIndentType(options) {
136 return types[options.indentType] || 0;
142 * @param {Object} options
146 function getLinefeed(options) {
154 return feeds[options.linefeed] || '\n';
158 * Build an includePaths string
159 * from the options.includePaths array and the SASS_PATH environment variable
161 * @param {Object} options
165 function buildIncludePaths(options) {
166 options.includePaths = options.includePaths || [];
168 if (process.env.hasOwnProperty('SASS_PATH')) {
169 options.includePaths = options.includePaths.concat(
170 process.env.SASS_PATH.split(path.delimiter)
174 // Preserve the behaviour people have come to expect.
175 // This behaviour was removed from Sass in 3.4 and
177 options.includePaths.unshift(process.cwd());
179 return options.includePaths.join(path.delimiter);
185 * @param {Object} options
189 function getOptions(opts, cb) {
190 if (typeof opts !== 'object') {
191 throw new Error('Invalid: options is not an object.');
193 var options = clonedeep(opts || {});
195 options.sourceComments = options.sourceComments || false;
196 if (options.hasOwnProperty('file')) {
197 options.file = getInputFile(options);
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);
208 // context object represents node-sass environment
209 options.context = { options: options, callback: cb };
212 stats: getStats(options)
219 * Executes a callback and transforms any exception raised into a sass error
221 * @param {Function} callback
222 * @param {Array} arguments
226 function tryCallback(callback, args) {
228 return callback.apply(this, args);
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);
235 return new binding.types.Error('An unexpected error occurred');
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.
245 * @param {String} signature
246 * @param {Function} callback
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 + '"');
258 signature: signature + '(...)',
259 callback: function() {
260 var args = Array.prototype.slice.call(arguments),
264 for (i = list.getLength() - 1; i >= 0; i--) {
265 args.unshift(list.getValue(i));
268 return callback.apply(this, args);
274 signature: signature,
282 * @param {Object} options
286 module.exports.render = function(opts, cb) {
287 var options = getOptions(opts, cb);
289 // options.error and options.success are for libsass binding
290 options.error = function(err) {
291 var payload = assign(new Error(), JSON.parse(err));
294 options.context.callback.call(options.context, payload, null);
298 options.success = function() {
299 var result = options.result;
300 var stats = endStats(result.stats);
308 options.context.callback.call(options.context, null, payload);
312 var importer = options.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);
323 var result = subject.call(options.context, file, prev, done);
325 if (result !== undefined) {
331 options.importer = function(file, prev, bridge) {
332 function done(result) {
333 bridge.success(result === module.exports.NULL ? null : result);
336 var result = importer.call(options.context, file, prev, done);
338 if (result !== undefined) {
345 var functions = clonedeep(options.functions);
348 options.functions = {};
350 Object.keys(functions).forEach(function(subject) {
351 var cb = normalizeFunctionSignature(subject, functions[subject]);
353 options.functions[cb.signature] = function() {
354 var args = Array.prototype.slice.call(arguments),
357 function done(data) {
358 bridge.success(data);
361 var result = tryCallback(cb.callback.bind(options.context), args.concat(done));
371 binding.render(options);
372 } else if (options.file) {
373 binding.renderFile(options);
375 cb({status: 3, message: 'No input specified: provide a file name or a source string to process' });
382 * @param {Object} options
386 module.exports.renderSync = function(opts) {
387 var options = getOptions(opts);
388 var importer = options.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);
397 return result === module.exports.NULL ? null : result;
401 options.importer = function(file, prev) {
402 var result = importer.call(options.context, file, prev);
404 return result === module.exports.NULL ? null : result;
409 var functions = clonedeep(options.functions);
411 if (options.functions) {
412 options.functions = {};
414 Object.keys(functions).forEach(function(signature) {
415 var cb = normalizeFunctionSignature(signature, functions[signature]);
417 options.functions[cb.signature] = function() {
418 return tryCallback(cb.callback.bind(options.context), arguments);
425 status = binding.renderSync(options);
426 } else if (options.file) {
427 status = binding.renderFileSync(options);
429 throw new Error('No input specified: provide a file name or a source string to process');
432 var result = options.result;
435 result.stats = endStats(result.stats);
439 throw assign(new Error(), JSON.parse(result.error));
448 module.exports.info = sass.getVersionInfo(binding);
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;
460 * Polyfill the old API
462 * TODO: remove for 4.0
465 function processSassDeprecationMessage() {
466 console.log('Deprecation warning: `process.sass` is an undocumented internal that will be removed in future versions of Node Sass.');
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; },