123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458 |
- /*!
- * node-sass: lib/index.js
- */
- var path = require('path'),
- clonedeep = require('lodash/cloneDeep'),
- 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 (Object.prototype.hasOwnProperty.call(process.env, '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 (Object.prototype.hasOwnProperty.call(options, '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 = Object.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,
- stats: stats
- };
- if (result.map) {
- payload.map = result.map;
- }
- 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 Object.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;
|