You can not select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
					
					
						
							477 lines
						
					
					
						
							10 KiB
						
					
					
				
			
		
		
	
	
							477 lines
						
					
					
						
							10 KiB
						
					
					
				/*! | 
						|
 * 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, | 
						|
      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 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; }, | 
						|
};
 | 
						|
 |