diff --git a/.gitignore b/.gitignore index c2658d7..af0a646 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ node_modules/ +test/css/ diff --git a/bin/autoless b/bin/autoless index 2bdca97..9e12a28 100755 --- a/bin/autoless +++ b/bin/autoless @@ -1,15 +1,14 @@ #!/usr/bin/env nodejs -var path = require('path') - , watch = require('watch') - , growl = require('growl') - , program = require('commander') - , less = require('less') - , Manager = require('../lib/manager'); +var path = require('path'); +var watch = require('watch'); +var growl = require('growl'); +var program = require('commander'); +var less = require('less'); +var Manager = require('../lib/manager'); program .usage('[options] [destination_dir]') - .option('--ignore ', 'Pattern for ignored files', RegExp) .option('--interval ', 'How often files are checked for changes', 100) .option('--no-watch', "Compile what needs to be compiled and exit") .parse(process.argv); @@ -21,14 +20,31 @@ var monitorOptions = { interval: program.interval }; +var statusChar = { + 'success': '✓', + 'error': '✖', + 'skipped': 'o' +}; + +var colors = { + 'success': 32, + 'error': 31, + 'skipped': 36 +}; + var manager = new Manager(srcDir, dstDir); -function log(file, dstFile, result) { - console.log(file + ' -> ' + dstFile + ': ' + result.toUpperCase()); +function color(status, str) { + return '\u001b[' + colors[status] + 'm' + str + '\u001b[0m'; +} + +function log(status, file, dstFile) { + dstFile = dstFile || ''; + console.log(color(status, '%s %s -> %s'), statusChar[status], file, dstFile); } -function notify(file, dstFile, result) { - growl(file + '\n-> ' + dstFile, { +function notify(result, file, dstFile) { + growl(file + ' -> ' + dstFile, { title: 'LESS', image: path.join(__dirname, '../images', result + '.svg') }); @@ -37,12 +53,11 @@ function notify(file, dstFile, result) { // FIXME: when https://github.com/mikeal/watch/issues/40 is fixed, use // filter in monitorOptions instead function fileFilter(file) { - return /\.less$/.test(file) && !(program.ignore && program.ignore.test(file)); + return /\.less$/.test(file); } function check(file) { if (fileFilter(file)) { - console.log(file); manager.check(file); } } @@ -56,8 +71,8 @@ watch.createMonitor(srcDir, monitorOptions, function(monitor) { console.log('Finding files...'); manager.addFiles(files, function() { - manager.compileAll(function(err) { - if (!program.watch) process.exit(err ? 1 : 0); + manager.compileAll(function(errors) { + if (!program.watch) process.exit(errors); monitor.on('changed', check); monitor.on('created', check); diff --git a/lib/lessfile.js b/lib/lessfile.js index 814d0f3..af97d09 100644 --- a/lib/lessfile.js +++ b/lib/lessfile.js @@ -1,6 +1,6 @@ -var fs = require('fs') - , path = require('path') - , less = require('less'); +var fs = require('fs'); +var path = require('path'); +var less = require('less'); function LessFile(file, dstFile) { this.file = file; diff --git a/lib/manager.js b/lib/manager.js index 7016ff6..117ffa3 100644 --- a/lib/manager.js +++ b/lib/manager.js @@ -1,6 +1,6 @@ -var path = require('path') - , EventEmitter = require('events').EventEmitter - , LessFile = require('./lessfile'); +var path = require('path'); +var EventEmitter = require('events').EventEmitter; +var LessFile = require('./lessfile'); function Manager(srcDir, dstDir) { this.srcDir = srcDir; @@ -13,14 +13,11 @@ function Manager(srcDir, dstDir) { Manager.prototype = new EventEmitter(); Manager.prototype.update = function(file, callback) { - var self = this - , lessFile = this.files[file]; + var self = this, lessFile = this.files[file]; if (!lessFile) { - var match = file.match(/^(.*)(\.[^\.]*)$/) - , base = match[1] - , ext = match[2] - , dstFile = path.join(this.dstDir, base.slice(this.srcDir.length) + '.css'); + var base = file.match(/^(.*)\.[^\.]*$/)[1] + var dstFile = path.join(this.dstDir, base.slice(this.srcDir.length) + '.css'); lessFile = this.files[file] = new LessFile(file, dstFile); } @@ -34,8 +31,7 @@ Manager.prototype.update = function(file, callback) { }; Manager.prototype.addFiles = function(files, callback) { - var self = this - , pending = 0; + var self = this, pending = 0; function done(err) { --pending; @@ -48,55 +44,53 @@ Manager.prototype.addFiles = function(files, callback) { }); }; +Manager.prototype._check = function(file, recurse, callback) { + var self = this, lessFile = this.files[file], pending = 0; + + function done(err) { + --pending; + if (callback && pending === 0) callback(); + } + + if (!this.dependencies[file]) { + lessFile.compile(function(err) { + self.emit('checked', err ? 'error' : 'success', lessFile.file, lessFile.dstFile); + if (err) self.emit('lessError', err); + if (callback) callback(err); + }); + } else { + self.emit('checked', 'skipped', lessFile.file); + if (recurse) { + Object.keys(self.dependencies[file]).forEach(function(file) { + ++pending; + self._check(file, recurse, done); + }); + } else if (callback) { + callback(); + } + } +}; + Manager.prototype.compileAll = function(callback) { - var self = this - , pending = 0 - , errors = null; + var self = this, pending = 0, errors = 0; function done(err, lessFile) { - self.emit('checked', lessFile.file, lessFile.dstFile, err ? 'error' : 'success'); - if (err) { - self.emit('lessError', err); - errors = true; - } + if (err) ++errors; --pending; if (pending === 0) callback(errors); } for (var file in this.files) { - var lessFile = this.files[file]; - if (!this.dependencies[file]) { - ++pending; - lessFile.compile(done); - } else { - self.emit('checked', lessFile.file, lessFile.dstFile, 'skipped'); - } + ++pending; + self._check(file, false, done); }; }; Manager.prototype.check = function(file, callback) { - var self = this - , pending = 0; + var self = this; - function done(err, lessFile) { - --pending; - if (pending === 0) callback(); - } - - this.update(file, function(err, lessFile) { - if (!self.dependencies[file]) { - lessFile.compile(function(err, lessFile) { - self.emit('checked', lessFile.file, lessFile.dstFile, err ? 'error' : 'success'); - if (err) self.emit('lessError', err); - if (callback) callback(); - }); - } else { - self.emit('checked', lessFile.file, lessFile.dstFile, 'skipped'); - Object.keys(self.dependencies[file]).forEach(function(file) { - ++pending; - self.check(file, done); - }); - } + this.update(file, function(err) { + self._check(file, true, callback); }); }; diff --git a/test/css/empty b/test/css/empty new file mode 100644 index 0000000..e69de29 diff --git a/test/lessfile_test.js b/test/lessfile_test.js index e7b8c89..fceecd5 100644 --- a/test/lessfile_test.js +++ b/test/lessfile_test.js @@ -53,7 +53,7 @@ describe("LessFile", function() { it("saves css file", function(done) { lessFile.compile(function(err) { fs.existsSync('test/less/main.css').should.be.true; - fs.readFileSync('test/less/main.css').should.include('text-decoration'); + fs.readFileSync('test/less/main.css', 'utf-8').should.include('text-decoration'); }); done(); }); diff --git a/test/manager_test.js b/test/manager_test.js index 18506fd..a155f42 100644 --- a/test/manager_test.js +++ b/test/manager_test.js @@ -53,6 +53,17 @@ describe("Manager", function() { done(); }); }); + + it("fires checked event for each file", function(done) { + var spy = sinon.spy(); + manager.on("checked", spy); + + manager.compileAll(function() { + spy.calledWith('success', 'test/less/main.less', 'test/css/main.css').should.be.true; + spy.calledWith('skipped', 'test/less/common.less').should.be.true; + done(); + }); + }) }); describe("when errors", function() { @@ -66,6 +77,16 @@ describe("Manager", function() { done(); }); }); + + it("fires checked event for each file", function(done) { + var spy = sinon.spy(); + manager.on("checked", spy); + + manager.compileAll(function() { + spy.calledWith('error', 'test/less/error.less', 'test/css/error.css').should.be.true; + done(); + }); + }) }); }); @@ -89,6 +110,16 @@ describe("Manager", function() { done(); }); }); + + it("fires checked event for the file", function(done) { + var spy = sinon.spy(); + manager.on("checked", spy); + + manager.check('test/less/main.less', function() { + spy.alwaysCalledWith('success', 'test/less/main.less', 'test/css/main.css').should.be.true; + done(); + }); + }) }); describe("when file is imported", function() { @@ -100,13 +131,24 @@ describe("Manager", function() { }); }); - it("checks its dependencies", function(done) { - var spy = sinon.spy(manager, 'check'); + it("compiles its dependencies", function(done) { + var spy = sinon.spy(manager.files['test/less/main.less'], 'compile'); manager.check('test/less/common.less', function() { - spy.calledWith('test/less/main.less').should.be.true; + spy.called.should.be.true; done(); }); }); + + it("fires checked event for files", function(done) { + var spy = sinon.spy(); + manager.on("checked", spy); + + manager.check('test/less/common.less', function() { + spy.calledWith('success', 'test/less/main.less', 'test/css/main.css').should.be.true; + spy.calledWith('skipped', 'test/less/common.less').should.be.true; + done(); + }); + }) }); }); });