| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412 | #!/usr/bin/env nodevar Emitter = require('events').EventEmitter,  forEach = require('async-foreach').forEach,  Gaze = require('gaze'),  meow = require('meow'),  util = require('util'),  path = require('path'),  glob = require('glob'),  sass = require('../lib'),  render = require('../lib/render'),  watcher = require('../lib/watcher'),  stdout = require('stdout-stream'),  stdin = require('get-stdin'),  fs = require('fs');/** * Initialize CLI */var cli = meow({  pkg: '../package.json',  version: sass.info,  help: [    'Usage:',    '  node-sass [options] <input.scss>',    '  cat <input.scss> | node-sass [options] > output.css',    '',    'Example: Compile foobar.scss to foobar.css',    '  node-sass --output-style compressed foobar.scss > foobar.css',    '  cat foobar.scss | node-sass --output-style compressed > foobar.css',    '',    'Example: Watch the sass directory for changes, compile with sourcemaps to the css directory',    '  node-sass --watch --recursive --output css',    '    --source-map true --source-map-contents sass',    '',    'Options',    '  -w, --watch                Watch a directory or file',    '  -r, --recursive            Recursively watch directories or files',    '  -o, --output               Output directory',    '  -x, --omit-source-map-url  Omit source map URL comment from output',    '  -i, --indented-syntax      Treat data from stdin as sass code (versus scss)',    '  -q, --quiet                Suppress log output except on error',    '  -v, --version              Prints version info',    '  --output-style             CSS output style (nested | expanded | compact | compressed)',    '  --indent-type              Indent type for output CSS (space | tab)',    '  --indent-width             Indent width; number of spaces or tabs (maximum value: 10)',    '  --linefeed                 Linefeed style (cr | crlf | lf | lfcr)',    '  --source-comments          Include debug info in output',    '  --source-map               Emit source map (boolean, or path to output .map file)',    '  --source-map-contents      Embed include contents in map',    '  --source-map-embed         Embed sourceMappingUrl as data URI',    '  --source-map-root          Base path, will be emitted in source-map as is',    '  --include-path             Path to look for imported files',    '  --follow                   Follow symlinked directories',    '  --precision                The amount of precision allowed in decimal numbers',    '  --error-bell               Output a bell character on errors',    '  --importer                 Path to .js file containing custom importer',    '  --functions                Path to .js file containing custom functions',    '  --help                     Print usage info'  ].join('\n')}, {  boolean: [    'error-bell',    'follow',    'indented-syntax',    'omit-source-map-url',    'quiet',    'recursive',    'source-map-embed',    'source-map-contents',    'source-comments',    'watch'  ],  string: [    'functions',    'importer',    'include-path',    'indent-type',    'linefeed',    'output',    'output-style',    'precision',    'source-map-root'  ],  alias: {    c: 'source-comments',    i: 'indented-syntax',    q: 'quiet',    o: 'output',    r: 'recursive',    x: 'omit-source-map-url',    v: 'version',    w: 'watch'  },  default: {    'include-path': process.cwd(),    'indent-type': 'space',    'indent-width': 2,    linefeed: 'lf',    'output-style': 'nested',    precision: 5,    quiet: false,    recursive: true  }});/** * Is a Directory * * @param {String} filePath * @returns {Boolean} * @api private */function isDirectory(filePath) {  var isDir = false;  try {    var absolutePath = path.resolve(filePath);    isDir = fs.statSync(absolutePath).isDirectory();  } catch (e) {    isDir = e.code === 'ENOENT';  }  return isDir;}/** * Get correct glob pattern * * @param {Object} options * @returns {String} * @api private */function globPattern(options) {  return options.recursive ? '**/*.{sass,scss}' : '*.{sass,scss}';}/** * Create emitter * * @api private */function getEmitter() {  var emitter = new Emitter();  emitter.on('error', function(err) {    if (options.errorBell) {      err += '\x07';    }    console.error(err);    if (!options.watch) {      process.exit(1);    }  });  emitter.on('warn', function(data) {    if (!options.quiet) {      console.warn(data);    }  });  emitter.on('info', function(data) {    if (!options.quiet) {      console.info(data);    }  });  emitter.on('log', stdout.write.bind(stdout));  return emitter;}/** * Construct options * * @param {Array} arguments * @param {Object} options * @api private */function getOptions(args, options) {  var cssDir, sassDir, file, mapDir;  options.src = args[0];  if (args[1]) {    options.dest = path.resolve(args[1]);  } else if (options.output) {    options.dest = path.join(      path.resolve(options.output),      [path.basename(options.src, path.extname(options.src)), '.css'].join(''));  // replace ext.  }  if (options.directory) {    sassDir = path.resolve(options.directory);    file = path.relative(sassDir, args[0]);    cssDir = path.resolve(options.output);    options.dest = path.join(cssDir, file).replace(path.extname(file), '.css');  }  if (options.sourceMap) {    if(!options.sourceMapOriginal) {      options.sourceMapOriginal = options.sourceMap;    }    // check if sourceMap path ends with .map to avoid isDirectory false-positive    var sourceMapIsDirectory = options.sourceMapOriginal.indexOf('.map', options.sourceMapOriginal.length - 4) === -1 && isDirectory(options.sourceMapOriginal);    if (options.sourceMapOriginal === 'true') {      options.sourceMap = options.dest + '.map';    } else if (!sourceMapIsDirectory) {      options.sourceMap = path.resolve(options.sourceMapOriginal);    } else if (sourceMapIsDirectory) {      if (!options.directory) {        options.sourceMap = path.resolve(options.sourceMapOriginal, path.basename(options.dest) + '.map');      } else {        sassDir = path.resolve(options.directory);        file = path.relative(sassDir, args[0]);        mapDir = path.resolve(options.sourceMapOriginal);        options.sourceMap = path.join(mapDir, file).replace(path.extname(file), '.css.map');      }    }  }  return options;}/** * Watch * * @param {Object} options * @param {Object} emitter * @api private */function watch(options, emitter) {  var handler = function(files) {    files.added.forEach(function(file) {      var watch = gaze.watched();      Object.keys(watch).forEach(function (dir) {        if (watch[dir].indexOf(file) !== -1) {          gaze.add(file);        }      });    });    files.changed.forEach(function(file) {      if (path.basename(file)[0] !== '_') {        renderFile(file, options, emitter);      }    });    files.removed.forEach(function(file) {      gaze.remove(file);    });  };  var gaze = new Gaze();  gaze.add(watcher.reset(options));  gaze.on('error', emitter.emit.bind(emitter, 'error'));  gaze.on('changed', function(file) {    handler(watcher.changed(file));  });  gaze.on('added', function(file) {    handler(watcher.added(file));  });  gaze.on('deleted', function(file) {    handler(watcher.removed(file));  });}/** * Run * * @param {Object} options * @param {Object} emitter * @api private */function run(options, emitter) {  if (!Array.isArray(options.includePath)) {    options.includePath = [options.includePath];  }  if (options.directory) {    if (!options.output) {      emitter.emit('error', 'An output directory must be specified when compiling a directory');    }    if (!isDirectory(options.output)) {      emitter.emit('error', 'An output directory must be specified when compiling a directory');    }  }  if (options.sourceMapOriginal && options.directory && !isDirectory(options.sourceMapOriginal) && options.sourceMapOriginal !== 'true') {    emitter.emit('error', 'The --source-map option must be either a boolean or directory when compiling a directory');  }  if (options.importer) {    if ((path.resolve(options.importer) === path.normalize(options.importer).replace(/(.+)([\/|\\])$/, '$1'))) {      options.importer = require(options.importer);    } else {      options.importer = require(path.resolve(options.importer));    }  }  if (options.functions) {    if ((path.resolve(options.functions) === path.normalize(options.functions).replace(/(.+)([\/|\\])$/, '$1'))) {      options.functions = require(options.functions);    } else {      options.functions = require(path.resolve(options.functions));    }  }  if (options.watch) {    watch(options, emitter);  } else if (options.directory) {    renderDir(options, emitter);  } else {    render(options, emitter);  }}/** * Render a file * * @param {String} file * @param {Object} options * @param {Object} emitter * @api private */function renderFile(file, options, emitter) {  options = getOptions([path.resolve(file)], options);  if (options.watch && !options.quiet) {    emitter.emit('info', util.format('=> changed: %s', file));  }  render(options, emitter);}/** * Render all sass files in a directory * * @param {Object} options * @param {Object} emitter * @api private */function renderDir(options, emitter) {  var globPath = path.resolve(options.directory, globPattern(options));  glob(globPath, { ignore: '**/_*', follow: options.follow }, function(err, files) {    if (err) {      return emitter.emit('error', util.format('You do not have permission to access this path: %s.', err.path));    } else if (!files.length) {      return emitter.emit('error', 'No input file was found.');    }    forEach(files, function(subject) {      emitter.once('done', this.async());      renderFile(subject, options, emitter);    }, function(successful, arr) {      var outputDir = path.join(process.cwd(), options.output);      if (!options.quiet) {        emitter.emit('info', util.format('Wrote %s CSS files to %s', arr.length, outputDir));      }      process.exit();    });  });}/** * Arguments and options */var options = getOptions(cli.input, cli.flags);var emitter = getEmitter();/** * Show usage if no arguments are supplied */if (!options.src && process.stdin.isTTY) {  emitter.emit('error', [    'Provide a Sass file to render',    '',    'Example: Compile foobar.scss to foobar.css',    '  node-sass --output-style compressed foobar.scss > foobar.css',    '  cat foobar.scss | node-sass --output-style compressed > foobar.css',    '',    'Example: Watch the sass directory for changes, compile with sourcemaps to the css directory',    '  node-sass --watch --recursive --output css',    '    --source-map true --source-map-contents sass',  ].join('\n'));}/** * Apply arguments */if (options.src) {  if (isDirectory(options.src)) {    options.directory = options.src;  }  run(options, emitter);} else if (!process.stdin.isTTY) {  stdin(function(data) {    options.data = data;    options.stdin = true;    run(options, emitter);  });}
 |