#!/usr/bin/env node var Emitter = require('events').EventEmitter, path = require('path'), Gaze = require('gaze'), meow = require('meow'), replaceExt = require('replace-ext'), stdin = require('get-stdin'), grapher = require('sass-graph'), render = require('../lib/render'); /** * Initialize CLI */ var cli = meow({ pkg: '../package.json', help: [ require('../lib/').info(), '', 'Usage', ' node-sass [options] [output.css]', ' cat | node-sass > output.css', '', 'Example', ' node-sass --output-style compressed foobar.scss foobar.css', ' cat foobar.scss | node-sass --output-style compressed > foobar.css', '', '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)', ' --output-style CSS output style (nested|expanded|compact|compressed)', ' --source-comments Include debug info in output', ' --source-map Emit source map', ' --source-map-embed Embed sourceMappingUrl as data URI', ' --source-map-contents Embed include contents in map', ' --include-path Path to look for imported files', ' --image-path Path to prepend when using the `image-url()` helper', ' --precision The amount of precision allowed in decimal numbers', ' --stdout Print the resulting CSS to stdout', ' --importer Path to custom importer', ' --help Print usage info' ].join('\n') }, { boolean: [ 'indented-syntax', 'omit-source-map-url', 'recursive', 'stdout', 'source-map-embed', 'source-map-contents', 'source-comments' ], string: [ 'image-path', 'include-path', 'output', 'output-style', 'precision' ], alias: { i: 'indented-syntax', o: 'output', w: 'watch', x: 'omit-source-map-url', c: 'source-comments', r: 'recursive' }, default: { 'image-path': '', 'include-path': process.cwd(), 'output-style': 'nested', precision: 5 } }); /** * Check if file is a Sass file * * @param {String} file * @api private */ function isSassFile(file) { return file.match(/\.(sass|scss)/); } /** * Create emitter * * @api private */ function getEmitter() { var emitter = new Emitter(); emitter.on('error', function(err) { console.error(err); if (!options.watch) { process.exit(1); } }); emitter.on('warn', function(data){ console.warn(data); }); emitter.on('log', function(data){ console.log(data); }); emitter.on('done', function() { if (!options.watch) { process.exit; } }); return emitter; } /** * Construct options * * @param {Array} arguments * @param {Object} options * @api private */ function getOptions(args, options) { var dir = options.output ? path.resolve(process.cwd(), options.output) : process.cwd(); options.src = args[0]; options.dest = args[1] ? path.resolve(dir, args[1]) : undefined; if (!options.dest && !options.stdout) { var ext = path.extname(options.src); var out = path.basename(options.src); var suffix = '.css'; if (ext !== suffix) { out = replaceExt(out, suffix); } options.dest = path.join(dir, out); } return options; } /** * Watch * * @param {Object} options * @param {Object} emitter * @api private */ function watch(options, emitter) { var dir = options.watch; var gaze = new Gaze(); if (dir === true) { dir = []; } else if (!Array.isArray(dir)) { dir = [dir]; } dir.push(options.src); dir = dir.map(function(d) { var glob = options.recursive ? '**/*.{sass,scss}' : '*.{sass,scss}'; return isSassFile(d) ? d : path.join(d, glob); }); gaze.add(dir); gaze.on('error', emitter.emit.bind(emitter, 'error')); var graph = grapher.parseDir(options.src, { loadPaths: options.includePath }); gaze.on('changed', function(file) { var files = [file]; graph.visitAncestors(file, function(parent) { files.push(parent); }); files.forEach(function(file) { if (path.basename(file)[0] === '_') return; options = getOptions([path.resolve(file)], options); emitter.emit('warn', '=> changed: ' + file); render(options, emitter); }); }); } /** * Run * * @param {Object} options * @param {Object} emitter * @api private */ function run(options, emitter) { if (!Array.isArray(options.includePath)) { options.includePath = [options.includePath]; } if (options.sourceMap) { if (options.sourceMap === 'true') { options.sourceMap = options.dest + '.map'; } else { options.sourceMap = path.resolve(process.cwd(), options.sourceMap); } } 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(process.cwd(), options.importer)); } } if (options.watch) { watch(options, emitter); } else { render(options, emitter); } } /** * Arguments and options */ var input = cli.input; var options = getOptions(input, cli.flags); var emitter = getEmitter(); /** * Show usage if no arguments are supplied */ if (!input.length && process.stdin.isTTY) { emitter.emit('error', [ 'Provide a Sass file to render', '', ' Example', ' node-sass --output-style compressed foobar.scss foobar.css', ' cat foobar.scss | node-sass --output-style compressed > foobar.css' ].join('\n')); } /** * Apply arguments */ if (options.src) { run(options, emitter); } else if (!process.stdin.isTTY) { stdin(function(data) { options.data = data; options.stdin = true; run(options, emitter); }); } return emitter;