| /*! |
| * node-sass: lib/index.js |
| */ |
| |
| var path = require('path'), |
| clonedeep = require('lodash/cloneDeep'), |
| assign = require('lodash/assign'), |
| sass = require('./extensions'); |
| |
| /** |
| * Require binding |
| */ |
| |
| var binding = require('./binding')(sass); |
| |
| /** |
| * Get input file |
| * |
| * @param {Object} options |
| * @api private |
| */ |
| |
| function getInputFile(options) { |
| return options.file ? path.resolve(options.file) : null; |
| } |
| |
| /** |
| * Get output file |
| * |
| * @param {Object} options |
| * @api private |
| */ |
| |
| function getOutputFile(options) { |
| var outFile = options.outFile; |
| |
| if (!outFile || typeof outFile !== 'string' || (!options.data && !options.file)) { |
| return null; |
| } |
| |
| return path.resolve(outFile); |
| } |
| |
| /** |
| * Get source map |
| * |
| * @param {Object} options |
| * @api private |
| */ |
| |
| function getSourceMap(options) { |
| var sourceMap = options.sourceMap; |
| |
| if (sourceMap && typeof sourceMap !== 'string' && options.outFile) { |
| sourceMap = options.outFile + '.map'; |
| } |
| |
| return sourceMap && typeof sourceMap === 'string' ? path.resolve(sourceMap) : null; |
| } |
| |
| /** |
| * Get stats |
| * |
| * @param {Object} options |
| * @api private |
| */ |
| |
| function getStats(options) { |
| var stats = {}; |
| |
| stats.entry = options.file || 'data'; |
| stats.start = Date.now(); |
| |
| return stats; |
| } |
| |
| /** |
| * End stats |
| * |
| * @param {Object} stats |
| * @param {Object} sourceMap |
| * @api private |
| */ |
| |
| function endStats(stats) { |
| stats.end = Date.now(); |
| stats.duration = stats.end - stats.start; |
| |
| return stats; |
| } |
| |
| /** |
| * Get style |
| * |
| * @param {Object} options |
| * @api private |
| */ |
| |
| function getStyle(options) { |
| var styles = { |
| nested: 0, |
| expanded: 1, |
| compact: 2, |
| compressed: 3 |
| }; |
| |
| return styles[options.outputStyle] || 0; |
| } |
| |
| /** |
| * Get indent width |
| * |
| * @param {Object} options |
| * @api private |
| */ |
| |
| function getIndentWidth(options) { |
| var width = parseInt(options.indentWidth) || 2; |
| |
| return width > 10 ? 2 : width; |
| } |
| |
| /** |
| * Get indent type |
| * |
| * @param {Object} options |
| * @api private |
| */ |
| |
| function getIndentType(options) { |
| var types = { |
| space: 0, |
| tab: 1 |
| }; |
| |
| return types[options.indentType] || 0; |
| } |
| |
| /** |
| * Get linefeed |
| * |
| * @param {Object} options |
| * @api private |
| */ |
| |
| function getLinefeed(options) { |
| var feeds = { |
| cr: '\r', |
| crlf: '\r\n', |
| lf: '\n', |
| lfcr: '\n\r' |
| }; |
| |
| return feeds[options.linefeed] || '\n'; |
| } |
| |
| /** |
| * Build an includePaths string |
| * from the options.includePaths array and the SASS_PATH environment variable |
| * |
| * @param {Object} options |
| * @api private |
| */ |
| |
| function buildIncludePaths(options) { |
| options.includePaths = options.includePaths || []; |
| |
| if (process.env.hasOwnProperty('SASS_PATH')) { |
| options.includePaths = options.includePaths.concat( |
| process.env.SASS_PATH.split(path.delimiter) |
| ); |
| } |
| |
| // Preserve the behaviour people have come to expect. |
| // This behaviour was removed from Sass in 3.4 and |
| // LibSass in 3.5. |
| options.includePaths.unshift(process.cwd()); |
| |
| return options.includePaths.join(path.delimiter); |
| } |
| |
| /** |
| * Get options |
| * |
| * @param {Object} options |
| * @api private |
| */ |
| |
| function getOptions(opts, cb) { |
| if (typeof opts !== 'object') { |
| throw new Error('Invalid: options is not an object.'); |
| } |
| var options = clonedeep(opts || {}); |
| |
| options.sourceComments = options.sourceComments || false; |
| if (options.hasOwnProperty('file')) { |
| options.file = getInputFile(options); |
| } |
| options.outFile = getOutputFile(options); |
| options.includePaths = buildIncludePaths(options); |
| options.precision = parseInt(options.precision) || 5; |
| options.sourceMap = getSourceMap(options); |
| options.style = getStyle(options); |
| options.indentWidth = getIndentWidth(options); |
| options.indentType = getIndentType(options); |
| options.linefeed = getLinefeed(options); |
| |
| // context object represents node-sass environment |
| options.context = { options: options, callback: cb }; |
| |
| options.result = { |
| stats: getStats(options) |
| }; |
| |
| return options; |
| } |
| |
| /** |
| * Executes a callback and transforms any exception raised into a sass error |
| * |
| * @param {Function} callback |
| * @param {Array} arguments |
| * @api private |
| */ |
| |
| function tryCallback(callback, args) { |
| try { |
| return callback.apply(this, args); |
| } catch (e) { |
| if (typeof e === 'string') { |
| return new binding.types.Error(e); |
| } else if (e instanceof Error) { |
| return new binding.types.Error(e.message); |
| } else { |
| return new binding.types.Error('An unexpected error occurred'); |
| } |
| } |
| } |
| |
| /** |
| * Normalizes the signature of custom functions to make it possible to just supply the |
| * function name and have the signature default to `fn(...)`. The callback is adjusted |
| * to transform the input sass list into discrete arguments. |
| * |
| * @param {String} signature |
| * @param {Function} callback |
| * @return {Object} |
| * @api private |
| */ |
| |
| function normalizeFunctionSignature(signature, callback) { |
| if (!/^\*|@warn|@error|@debug|\w+\(.*\)$/.test(signature)) { |
| if (!/\w+/.test(signature)) { |
| throw new Error('Invalid function signature format "' + signature + '"'); |
| } |
| |
| return { |
| signature: signature + '(...)', |
| callback: function() { |
| var args = Array.prototype.slice.call(arguments), |
| list = args.shift(), |
| i; |
| |
| for (i = list.getLength() - 1; i >= 0; i--) { |
| args.unshift(list.getValue(i)); |
| } |
| |
| return callback.apply(this, args); |
| } |
| }; |
| } |
| |
| return { |
| signature: signature, |
| callback: callback |
| }; |
| } |
| |
| /** |
| * Render |
| * |
| * @param {Object} options |
| * @api public |
| */ |
| |
| module.exports.render = function(opts, cb) { |
| var options = getOptions(opts, cb); |
| |
| // options.error and options.success are for libsass binding |
| options.error = function(err) { |
| var payload = assign(new Error(), JSON.parse(err)); |
| |
| if (cb) { |
| options.context.callback.call(options.context, payload, null); |
| } |
| }; |
| |
| options.success = function() { |
| var result = options.result; |
| var stats = endStats(result.stats); |
| var payload = { |
| css: result.css, |
| map: result.map, |
| stats: stats |
| }; |
| |
| if (cb) { |
| options.context.callback.call(options.context, null, payload); |
| } |
| }; |
| |
| var importer = options.importer; |
| |
| if (importer) { |
| if (Array.isArray(importer)) { |
| options.importer = []; |
| importer.forEach(function(subject, index) { |
| options.importer[index] = function(file, prev, bridge) { |
| function done(result) { |
| bridge.success(result === module.exports.NULL ? null : result); |
| } |
| |
| var result = subject.call(options.context, file, prev, done); |
| |
| if (result !== undefined) { |
| done(result); |
| } |
| }; |
| }); |
| } else { |
| options.importer = function(file, prev, bridge) { |
| function done(result) { |
| bridge.success(result === module.exports.NULL ? null : result); |
| } |
| |
| var result = importer.call(options.context, file, prev, done); |
| |
| if (result !== undefined) { |
| done(result); |
| } |
| }; |
| } |
| } |
| |
| var functions = clonedeep(options.functions); |
| |
| if (functions) { |
| options.functions = {}; |
| |
| Object.keys(functions).forEach(function(subject) { |
| var cb = normalizeFunctionSignature(subject, functions[subject]); |
| |
| options.functions[cb.signature] = function() { |
| var args = Array.prototype.slice.call(arguments), |
| bridge = args.pop(); |
| |
| function done(data) { |
| bridge.success(data); |
| } |
| |
| var result = tryCallback(cb.callback.bind(options.context), args.concat(done)); |
| |
| if (result) { |
| done(result); |
| } |
| }; |
| }); |
| } |
| |
| if (options.data) { |
| binding.render(options); |
| } else if (options.file) { |
| binding.renderFile(options); |
| } else { |
| cb({status: 3, message: 'No input specified: provide a file name or a source string to process' }); |
| } |
| }; |
| |
| /** |
| * Render sync |
| * |
| * @param {Object} options |
| * @api public |
| */ |
| |
| module.exports.renderSync = function(opts) { |
| var options = getOptions(opts); |
| var importer = options.importer; |
| |
| if (importer) { |
| if (Array.isArray(importer)) { |
| options.importer = []; |
| importer.forEach(function(subject, index) { |
| options.importer[index] = function(file, prev) { |
| var result = subject.call(options.context, file, prev); |
| |
| return result === module.exports.NULL ? null : result; |
| }; |
| }); |
| } else { |
| options.importer = function(file, prev) { |
| var result = importer.call(options.context, file, prev); |
| |
| return result === module.exports.NULL ? null : result; |
| }; |
| } |
| } |
| |
| var functions = clonedeep(options.functions); |
| |
| if (options.functions) { |
| options.functions = {}; |
| |
| Object.keys(functions).forEach(function(signature) { |
| var cb = normalizeFunctionSignature(signature, functions[signature]); |
| |
| options.functions[cb.signature] = function() { |
| return tryCallback(cb.callback.bind(options.context), arguments); |
| }; |
| }); |
| } |
| |
| var status; |
| if (options.data) { |
| status = binding.renderSync(options); |
| } else if (options.file) { |
| status = binding.renderFileSync(options); |
| } else { |
| throw new Error('No input specified: provide a file name or a source string to process'); |
| } |
| |
| var result = options.result; |
| |
| if (status) { |
| result.stats = endStats(result.stats); |
| return result; |
| } |
| |
| throw assign(new Error(), JSON.parse(result.error)); |
| }; |
| |
| /** |
| * API Info |
| * |
| * @api public |
| */ |
| |
| module.exports.info = sass.getVersionInfo(binding); |
| |
| /** |
| * Expose sass types |
| */ |
| |
| module.exports.types = binding.types; |
| module.exports.TRUE = binding.types.Boolean.TRUE; |
| module.exports.FALSE = binding.types.Boolean.FALSE; |
| module.exports.NULL = binding.types.Null.NULL; |
| |
| /** |
| * Polyfill the old API |
| * |
| * TODO: remove for 4.0 |
| */ |
| |
| function processSassDeprecationMessage() { |
| console.log('Deprecation warning: `process.sass` is an undocumented internal that will be removed in future versions of Node Sass.'); |
| } |
| |
| process.sass = process.sass || { |
| get versionInfo() { processSassDeprecationMessage(); return module.exports.info; }, |
| get binaryName() { processSassDeprecationMessage(); return sass.getBinaryName(); }, |
| get binaryUrl() { processSassDeprecationMessage(); return sass.getBinaryUrl(); }, |
| get binaryPath() { processSassDeprecationMessage(); return sass.getBinaryPath(); }, |
| get getBinaryPath() { processSassDeprecationMessage(); return sass.getBinaryPath; }, |
| }; |