Commit 3d44ce88 by Paul C Pederson

CLI: Adds support for multi-file compilation.

parent 2d22d9c7
...@@ -459,7 +459,9 @@ Output will be saved with the same name as input SASS file into the current work ...@@ -459,7 +459,9 @@ Output will be saved with the same name as input SASS file into the current work
--help Print usage info --help Print usage info
``` ```
Note `--importer` takes the (absolute or relative to pwd) path to a js file, which needs to have a default `module.exports` set to the importer function. See our test [fixtures](https://github.com/sass/node-sass/tree/974f93e76ddd08ea850e3e663cfe64bb6a059dd3/test/fixtures/extras) for example. Pass a directory as the input to compile multiple files. For example: `node-sass sass/ -o css/`. The `--output` flag is required and must be a directory when using multi-file compilation.
Also, note `--importer` takes the (absolute or relative to pwd) path to a js file, which needs to have a default `module.exports` set to the importer function. See our test [fixtures](https://github.com/sass/node-sass/tree/974f93e76ddd08ea850e3e663cfe64bb6a059dd3/test/fixtures/extras) for example.
## Post-install Build ## Post-install Build
......
#!/usr/bin/env node #!/usr/bin/env node
var Emitter = require('events').EventEmitter, var Emitter = require('events').EventEmitter,
forEach = require('async-foreach').forEach,
Gaze = require('gaze'), Gaze = require('gaze'),
grapher = require('sass-graph'), grapher = require('sass-graph'),
meow = require('meow'), meow = require('meow'),
util = require('util'),
path = require('path'), path = require('path'),
glob = require('glob'),
render = require('../lib/render'), render = require('../lib/render'),
stdin = require('get-stdin'); stdin = require('get-stdin'),
fs = require('fs');
/** /**
* Initialize CLI * Initialize CLI
...@@ -88,14 +92,34 @@ var cli = meow({ ...@@ -88,14 +92,34 @@ var cli = meow({
}); });
/** /**
* Check if file is a Sass file * Is a Directory
* *
* @param {String} file * @param {String} filePath
* @returns {Boolean}
* @api private * @api private
*/ */
function isSassFile(file) { function isDirectory(filePath) {
return file.match(/\.(sass|scss)/); var isDir = false;
try {
var absolutePath = path.resolve(process.cwd(), filePath);
isDir = fs.lstatSync(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}';
} }
/** /**
...@@ -123,7 +147,7 @@ function getEmitter() { ...@@ -123,7 +147,7 @@ function getEmitter() {
}); });
emitter.on('done', function() { emitter.on('done', function() {
if (!options.watch) { if (!options.watch && !options.directory) {
process.exit; process.exit;
} }
}); });
...@@ -150,6 +174,13 @@ function getOptions(args, options) { ...@@ -150,6 +174,13 @@ function getOptions(args, options) {
[path.basename(options.src, path.extname(options.src)), '.css'].join('')); // replace ext. [path.basename(options.src, path.extname(options.src)), '.css'].join('')); // replace ext.
} }
if (options.directory) {
var sassDir = path.resolve(process.cwd(), options.directory);
var file = path.relative(sassDir, args[0]);
var cssDir = path.resolve(process.cwd(), options.output);
options.dest = path.join(cssDir, file).replace(path.extname(file), '.css');
}
return options; return options;
} }
...@@ -162,8 +193,7 @@ function getOptions(args, options) { ...@@ -162,8 +193,7 @@ function getOptions(args, options) {
*/ */
function watch(options, emitter) { function watch(options, emitter) {
var glob = options.recursive ? '**/*.{sass,scss}' : '*.{sass,scss}'; var src = options.directory ? path.join(options.directory, globPattern(options)) : options.src;
var src = isSassFile(options.src) ? options.src : path.join(options.src, glob);
var graph = grapher.parseDir(path.resolve(path.dirname(src)), { loadPaths: options.includePath }); var graph = grapher.parseDir(path.resolve(path.dirname(src)), { loadPaths: options.includePath });
var watch = []; var watch = [];
...@@ -181,12 +211,10 @@ function watch(options, emitter) { ...@@ -181,12 +211,10 @@ function watch(options, emitter) {
graph.visitAncestors(file, function(parent) { graph.visitAncestors(file, function(parent) {
files.push(parent); files.push(parent);
}); });
files.forEach(function(file) { files.forEach(function(file) {
if (path.basename(file)[0] === '_') return; if (path.basename(file)[0] !== '_') {
options = getOptions([path.resolve(file)], options); renderFile(file, options, emitter);
emitter.emit('warn', '=> changed: ' + file); }
render(options, emitter);
}); });
}); });
} }
...@@ -204,6 +232,15 @@ function run(options, emitter) { ...@@ -204,6 +232,15 @@ function run(options, emitter) {
options.includePath = [options.includePath]; options.includePath = [options.includePath];
} }
if (options.directory) {
if (!options.output) {
emitter.emit('error', 'Specify an output directory when using multi-file compilation');
}
if (!isDirectory(options.output)) {
emitter.emit('error', util.format('Multi-file compilation: requires destination %s to be a directory.', options.output));
}
}
if (options.sourceMap) { if (options.sourceMap) {
if (options.sourceMap === 'true') { if (options.sourceMap === 'true') {
if (options.dest) { if (options.dest) {
...@@ -235,12 +272,56 @@ function run(options, emitter) { ...@@ -235,12 +272,56 @@ function run(options, emitter) {
if (options.watch) { if (options.watch) {
watch(options, emitter); watch(options, emitter);
} else if (options.directory) {
renderDir(options, emitter);
} else { } else {
render(options, emitter); 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) {
emitter.emit('warn', 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(process.cwd(), options.directory, globPattern(options));
glob(globPath, {ignore: '**/_*'}, 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);
emitter.emit('warn', util.format('Wrote %s CSS files to %s', arr.lenth, outputDir));
process.exit;
});
});
}
/**
* Arguments and options * Arguments and options
*/ */
...@@ -266,6 +347,9 @@ if (!options.src && process.stdin.isTTY) { ...@@ -266,6 +347,9 @@ if (!options.src && process.stdin.isTTY) {
*/ */
if (options.src) { if (options.src) {
if (isDirectory(options.src)){
options.directory = options.src;
}
run(options, emitter); run(options, emitter);
} else if (!process.stdin.isTTY) { } else if (!process.stdin.isTTY) {
stdin(function(data) { stdin(function(data) {
......
...@@ -45,9 +45,11 @@ ...@@ -45,9 +45,11 @@
"style" "style"
], ],
"dependencies": { "dependencies": {
"async-foreach": "^0.1.3",
"chalk": "^1.0.0", "chalk": "^1.0.0",
"gaze": "^0.5.1", "gaze": "^0.5.1",
"get-stdin": "^4.0.1", "get-stdin": "^4.0.1",
"glob": "^5.0.3",
"meow": "^3.1.0", "meow": "^3.1.0",
"mkdirp": "^0.5.0", "mkdirp": "^0.5.0",
"nan": "^1.7.0", "nan": "^1.7.0",
...@@ -62,6 +64,7 @@ ...@@ -62,6 +64,7 @@
"jscoverage": "^0.5.9", "jscoverage": "^0.5.9",
"jshint": "^2.6.3", "jshint": "^2.6.3",
"mocha": "^2.2.1", "mocha": "^2.2.1",
"mocha-lcov-reporter": "^0.0.2" "mocha-lcov-reporter": "^0.0.2",
"rimraf": "^2.3.2"
} }
} }
...@@ -2,6 +2,8 @@ var assert = require('assert'), ...@@ -2,6 +2,8 @@ var assert = require('assert'),
fs = require('fs'), fs = require('fs'),
path = require('path'), path = require('path'),
read = require('fs').readFileSync, read = require('fs').readFileSync,
glob = require('glob'),
rimraf = require('rimraf'),
stream = require('stream'), stream = require('stream'),
spawn = require('cross-spawn'), spawn = require('cross-spawn'),
cli = path.join(__dirname, '..', 'bin', 'node-sass'), cli = path.join(__dirname, '..', 'bin', 'node-sass'),
...@@ -301,6 +303,88 @@ describe('cli', function() { ...@@ -301,6 +303,88 @@ describe('cli', function() {
}); });
}); });
describe('node-sass sass/ --output css/', function() {
it('should create the output directory', function(done) {
var src = fixture('input-directory/sass');
var dest = fixture('input-directory/css');
var bin = spawn(cli, [src, '--output', dest]);
bin.once('close', function() {
assert(fs.existsSync(dest));
rimraf.sync(dest);
done();
});
});
it('it should compile all files in the folder', function(done) {
var src = fixture('input-directory/sass');
var dest = fixture('input-directory/css');
var bin = spawn(cli, [src, '--output', dest]);
bin.once('close', function() {
var files = fs.readdirSync(dest).sort();
assert.deepEqual(files, ['one.css', 'two.css', 'nested'].sort());
var nestedFiles = fs.readdirSync(path.join(dest, 'nested'));
assert.deepEqual(nestedFiles, ['three.css']);
rimraf.sync(dest);
done();
});
});
it('should skip files with an underscore', function(done) {
var src = fixture('input-directory/sass');
var dest = fixture('input-directory/css');
var bin = spawn(cli, [src, '--output', dest]);
bin.once('close', function() {
var files = fs.readdirSync(dest);
assert.equal(files.indexOf('_skipped.css'), -1);
rimraf.sync(dest);
done();
});
});
it('should ignore nested files if --recursive false', function(done) {
var src = fixture('input-directory/sass');
var dest = fixture('input-directory/css');
var bin = spawn(cli, [
src, '--output', dest,
'--recursive', false
]);
bin.once('close', function() {
var files = fs.readdirSync(dest);
assert.deepEqual(files, ['one.css', 'two.css']);
rimraf.sync(dest);
done();
});
});
it('should error if no output directory is provided', function(done) {
var src = fixture('input-directory/sass');
var bin = spawn(cli, [src]);
bin.once('close', function(code) {
assert(code !== 0);
assert.equal(glob.sync(fixture('input-directory/**/*.css')).length, 0);
done();
});
});
it('should error if output directory is not a directory', function(done) {
var src = fixture('input-directory/sass');
var dest = fixture('input-directory/sass/one.scss');
var bin = spawn(cli, [src, '--output', dest]);
bin.once('close', function(code) {
assert(code !== 0);
assert.equal(glob.sync(fixture('input-directory/**/*.css')).length, 0);
done();
});
});
});
describe('node-sass in.scss --output path/to/file/out.css', function() { describe('node-sass in.scss --output path/to/file/out.css', function() {
it('should create the output directory', function(done) { it('should create the output directory', function(done) {
var src = fixture('output-directory/index.scss'); var src = fixture('output-directory/index.scss');
......
#navbar {
width: 80%;
height: 23px;
}
#navbar ul {
list-style-type: none;
}
#navbar li {
float: left;
a {
font-weight: bold;
}
}
#navbar {
width: 80%;
height: 23px;
}
#navbar ul {
list-style-type: none;
}
#navbar li {
float: left;
a {
font-weight: bold;
}
}
#navbar {
width: 80%;
height: 23px;
}
#navbar ul {
list-style-type: none;
}
#navbar li {
float: left;
a {
font-weight: bold;
}
}
#navbar {
width: 80%;
height: 23px;
}
#navbar ul {
list-style-type: none;
}
#navbar li {
float: left;
a {
font-weight: bold;
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment