Comparing version 0.1.6 to 0.1.7
@@ -9,6 +9,5 @@ 'use strict'; | ||
var path = require('path') | ||
var mkdirp = require('mkdirp') | ||
var exec = require('child_process').exec | ||
var EventEmitter = require('events').EventEmitter | ||
var runEdges = require('../lib/update/run-edges') | ||
var EdgeRunner = require('../lib/update/edge-runner') | ||
var forwardEvents = require('../lib/forward-events') | ||
@@ -21,7 +20,4 @@ var Output = require('./output') | ||
var SOME_UTD = 'Those are already up-to-date: %s' | ||
var CMD_FAIL = 'command failed, code %d: %s' | ||
var CMD_SIGFAIL = 'command failed, signal %s: %s' | ||
var DRY_REM_ORPHAN = 'Would remove orphan: %s' | ||
var REM_ORPHAN = 'Removing orphan: %s' | ||
var SIG_ABORT = 'aborting on %s, recipe process will detach' | ||
@@ -31,107 +27,156 @@ var SIGS = ['SIGINT', 'SIGHUP', 'SIGTERM', 'SIGQUIT'] | ||
function updateGraph(data, opts) { | ||
if (!data) throw errors.invalidArg('data', data) | ||
if (!opts) opts = {} | ||
var ev = new EventEmitter() | ||
return new UpdateGraphTask(data, opts) | ||
} | ||
util.inherits(UpdateGraphTask, EventEmitter) | ||
function UpdateGraphTask(data, opts) { | ||
this._data = data | ||
this._dryRun = opts['dry-run'] || false | ||
this._robot = opts['robot'] || false | ||
this._shy = opts['shy'] || false | ||
this._edgeRunner = new EdgeRunner(data.cmds, this._getEdgeRunnerOpts()) | ||
this._runCount = 0 | ||
this._output = new Output(this._dryRun) | ||
var self = this | ||
unlinkOrphans(data, opts, function (err) { | ||
if (err) return helpers.bailoutEv(ev, err) | ||
var uev = update(data, opts) | ||
var sigInfo = registerSigs(function onSignal(signal) { | ||
uev.emit('signal', signal) | ||
if (err) return helpers.bailoutEv(self, err) | ||
self._safeUpdate() | ||
}) | ||
} | ||
UpdateGraphTask.prototype._getEdgeRunnerOpts = function () { | ||
if (!this._dryRun) return null | ||
return {exec: this._dryExec.bind(this), mkdirP: dryMkdirP.bind(null)} | ||
} | ||
UpdateGraphTask.prototype._safeUpdate = function () { | ||
var self = this | ||
var sigInfo = registerSigs(function onSignal(signal) { | ||
if (signal !== 'SIGINT') self._runEdges.abort(signal) | ||
self._edgeRunner.abort(signal) | ||
}) | ||
this._update(function (errored, signal) { | ||
unregisterSigs(sigInfo) | ||
if (self._dryRun) return self.emit('finish') | ||
self._finalize(signal) | ||
}) | ||
} | ||
UpdateGraphTask.prototype._finalize = function (signal) { | ||
var self = this | ||
mkdirp(path.dirname(common.LOG_PATH), function (err) { | ||
if (err) return helpers.bailoutEv(self, err) | ||
var s = self._data.log.save(fs.createWriteStream(common.LOG_PATH)) | ||
s.end(function () { | ||
self.emit('finish', signal) | ||
}) | ||
forwardEvents(ev, uev, function (errored, signal) { | ||
unregisterSigs(sigInfo) | ||
if (opts['dry-run']) return ev.emit('finish') | ||
mkdirp(path.dirname(common.LOG_PATH), function (err) { | ||
if (err) return helpers.bailoutEv(ev, err) | ||
var s = data.log.save(fs.createWriteStream(common.LOG_PATH)) | ||
s.end(function () { | ||
ev.emit('finish', signal) | ||
}) | ||
}) | ||
}) | ||
}) | ||
return ev | ||
} | ||
function update(data, opts) { | ||
var res | ||
var ev = new EventEmitter() | ||
if (opts['robot']) console.log(' e %d', data.edges.length) | ||
if (data.edges.length === 0) return alreadyUpToDate(ev, data, opts) | ||
var st = {data: data, runCount: 0, opts: opts | ||
, sigints: {}, stopFns: {} | ||
, dirs: {}, output: new Output(opts['dry-run'])} | ||
st.updateMessage = opts['robot'] ? updateRobotMessage : updateMessage | ||
var reFn = opts['dry-run'] ? dryRunEdge : runEdge | ||
var re = reFn.bind(null, st) | ||
st.updateMessage(st, null) | ||
var reOpts = {concurrency: os.cpus().length, shy: opts.shy} | ||
res = runEdges(data.edges, re, reOpts) | ||
ev.on('signal', function (signal) { | ||
if (signal !== 'SIGINT') { | ||
res.abort(signal) | ||
for (var j in st.stopFns) st.stopFns[j](signal) | ||
return | ||
} | ||
for (var i in st.sigints) st.sigints[i] = true | ||
}) | ||
return forwardEvents(ev, res, function (errored, signal) { | ||
st.output.endUpdate() | ||
UpdateGraphTask.prototype._update = function (cb) { | ||
if (this._robot) console.log(' e %d', this._data.edges.length) | ||
if (this._data.edges.length === 0) return this._alreadyUpToDate() | ||
if (this._robot) this._updateMessage = this._updateRobotMessage | ||
var reFn = this._runEdge.bind(this) | ||
var reOpts = {concurrency: os.cpus().length, shy: this._shy} | ||
this._updateMessage(null) | ||
var self = this | ||
var res = runEdges(this._data.edges, reFn, reOpts) | ||
forwardEvents(this, res, function (errored, signal) { | ||
if (!errored) { | ||
if (opts['robot']) console.log(' D') | ||
else console.log('Done.') | ||
self._updateMessage(null) | ||
} | ||
ev.emit('finish', signal) | ||
}, function () { st.output.endUpdate() }) | ||
self._output.endUpdate() | ||
cb(errored, signal) | ||
}, function () { self._output.endUpdate() }) | ||
} | ||
function runEdge(st, edge, cb) { | ||
st.sigints[edge.index] = false | ||
var cmd = st.data.cmds[edge.index] | ||
mkEdgeDirs(st, edge, function (err) { | ||
if (err) return cb(err) | ||
var stopped = false | ||
exec(cmd, function (err, stdout, stderr) { | ||
if (stopped) return | ||
delete st.stopFns[edge.index] | ||
var sigint = st.sigints[edge.index] | ||
delete st.sigints[edge.index] | ||
if (!err) st.runCount++ | ||
st.updateMessage(st, edge) | ||
if (stdout.length > 0 || stderr.length > 0) { | ||
st.output.endUpdate() | ||
process.stdout.write(stdout) | ||
process.stderr.write(stderr) | ||
} | ||
if (err) { | ||
var message | ||
if (err.code) message = util.format(CMD_FAIL, err.code, cmd) | ||
else message = util.format(CMD_SIGFAIL, err.signal, cmd) | ||
var nerr = new Error(message) | ||
if ((err.signal === 'SIGINT' || err.code === 130) && sigint) | ||
nerr.signal = 'SIGINT' | ||
return cb(nerr) | ||
} | ||
edge.outFiles.forEach(function (file) { | ||
st.data.log.update(file.path, st.data.imps[file.path]) | ||
}) | ||
return cb(null) | ||
}) | ||
st.stopFns[edge.index] = function stopRunEdge (signal) { | ||
delete st.stopFns[edge.index] | ||
stopped = true | ||
st.updateMessage(st, edge) | ||
var msg = util.format(SIG_ABORT, signal) | ||
return cb(new Error(msg)) | ||
} | ||
UpdateGraphTask.prototype._runEdge = function (edge, cb) { | ||
var self = this | ||
this._edgeRunner.run(edge, function (err, stdout, stderr) { | ||
var res = {err: err, stdout: stdout, stderr: stderr} | ||
self._doneRunEdge(edge, res, cb) | ||
}) | ||
} | ||
function dryRunEdge(st, edge, cb) { | ||
process.nextTick(function () { | ||
st.runCount++ | ||
st.updateMessage(st, edge) | ||
return cb(null) | ||
UpdateGraphTask.prototype._doneRunEdge = function (edge, res, cb) { | ||
this._updateMessage(edge) | ||
if (res.stdout.length > 0 || res.stderr.length > 0) { | ||
this._output.endUpdate() | ||
process.stdout.write(res.stdout) | ||
process.stderr.write(res.stderr) | ||
} | ||
if (res.err) return cb(res.err) | ||
this._runCount++ | ||
var self = this | ||
edge.outFiles.forEach(function (file) { | ||
self._data.log.update(file.path, self._data.imps[file.path]) | ||
}) | ||
return cb(null) | ||
} | ||
UpdateGraphTask.prototype._alreadyUpToDate = function () { | ||
if (!this._robot) { | ||
if (this._data.cliRefs.length === 0) { | ||
console.log(common.EVERYTHING_UTD) | ||
} else { | ||
var list = this._data.cliRefs.map(function (ref) { | ||
return ref.value | ||
}).join(', ') | ||
console.log(util.format(SOME_UTD, list)) | ||
} | ||
} | ||
process.nextTick(this.emit.bind(this, 'finish')) | ||
} | ||
UpdateGraphTask.prototype._updateRobotMessage = function (edge) { | ||
if (!edge) return | ||
var name = edge.trans.ast.recipeName | ||
var inFiles = edge.inFiles.map(pathOf).join(' ') | ||
var outFiles = edge.outFiles.map(pathOf).join(' ') | ||
var modifier = this._dryRun ? 'w' : ' ' | ||
var message = util.format('%sU %d %s %s -- %s', modifier, this._runCount | ||
, name, inFiles, outFiles) | ||
console.log(message) | ||
} | ||
UpdateGraphTask.prototype._updateMessage = function (edge) { | ||
var perc = (this._runCount / this._data.edges.length) | ||
var label | ||
if (edge) { | ||
var name = edge.trans.ast.recipeName | ||
var inFiles = edge.inFiles.map(pathOf).join(' ') | ||
var outFiles = edge.outFiles.map(pathOf).join(' ') | ||
label = util.format('%s %s -> %s', name, inFiles, outFiles) | ||
} else if (this._runCount === this._data.edges.length) { | ||
label = 'Done.' | ||
} else { | ||
label = 'Updating...' | ||
} | ||
var percStr = helpers.pad((perc * 100).toFixed(1), 5) | ||
var message = util.format('[%s%] %s', percStr, label) | ||
this._output.update(message) | ||
} | ||
UpdateGraphTask.prototype._dryExec = function (cmd, opts, cb) { | ||
if (!cb) { | ||
cb = opts | ||
opts = {} | ||
} | ||
var tpl = this._robot ? 'wR %s\n' : 'would run: %s\n' | ||
var stdout = util.format(tpl, cmd) | ||
process.nextTick(cb.bind(null, null, stdout, '')) | ||
} | ||
function dryMkdirP(dir, opts, cb) { | ||
if (!cb) { | ||
cb = opts | ||
opts = {} | ||
} | ||
process.nextTick(cb.bind(null, null)) | ||
} | ||
function unlinkOrphans(data, opts, cb) { | ||
@@ -162,19 +207,2 @@ if (typeof cb !== 'function') throw errors.invalidArg('cb', cb) | ||
function alreadyUpToDate(ev, data, opts) { | ||
if (opts['robot']) { | ||
console.log(' D') | ||
} else { | ||
if (data.cliRefs.length === 0) { | ||
console.log(common.EVERYTHING_UTD) | ||
} else { | ||
var list = data.cliRefs.map(function (ref) { | ||
return ref.value | ||
}).join(', ') | ||
console.log(util.format(SOME_UTD, list)) | ||
} | ||
} | ||
process.nextTick(ev.emit.bind(ev, 'finish')) | ||
return ev | ||
} | ||
function registerSigs(fn) { | ||
@@ -195,44 +223,4 @@ var info = {sigs: {}} | ||
function mkEdgeDirs(st, edge, cb) { | ||
;(function next(i) { | ||
if (i >= edge.outFiles.length) return cb(null) | ||
var file = edge.outFiles[i] | ||
var dir = path.dirname(file.path) | ||
if (st.dirs.hasOwnProperty(dir)) return next(i + 1) | ||
mkdirp(path.dirname(file.path), function (err) { | ||
if (err) return cb(err) | ||
st.dirs[dir] = true | ||
return next(i + 1) | ||
}) | ||
})(0) | ||
} | ||
function updateRobotMessage(st, edge) { | ||
if (!edge) return | ||
var name = edge.trans.ast.recipeName | ||
var inFiles = edge.inFiles.map(pathOf).join(' ') | ||
var outFiles = edge.outFiles.map(pathOf).join(' ') | ||
var modifier = st.opts['dry-run'] ? 'w' : ' ' | ||
var message = util.format('%sU %d %s %s -- %s', modifier, st.runCount | ||
, name, inFiles, outFiles) | ||
console.log(message) | ||
} | ||
function updateMessage(st, edge) { | ||
var perc = (st.runCount / st.data.edges.length) | ||
var label = '' | ||
if (edge) { | ||
var name = edge.trans.ast.recipeName | ||
var inFiles = edge.inFiles.map(pathOf).join(' ') | ||
var outFiles = edge.outFiles.map(pathOf).join(' ') | ||
label = util.format('%s %s -> %s', name, inFiles, outFiles) | ||
} | ||
var action = st.opts['dry-run'] ? 'Would update' : 'Updating' | ||
var percStr = helpers.pad((perc * 100).toFixed(1), 5) | ||
var message = util.format('%s... %s% %s', action, percStr, label) | ||
st.output.update(message) | ||
} | ||
function pathOf(file) { | ||
return file.path | ||
} |
@@ -13,3 +13,4 @@ 'use strict'; | ||
function invalidArg(name, value) { | ||
var err = new Error(util.format(INVALID_ARG, name, value)) | ||
var iv = util.inspect(value, {colors: true}) | ||
var err = new Error(util.format(INVALID_ARG, name, iv)) | ||
err.name = 'InvalidArgError' | ||
@@ -16,0 +17,0 @@ err.propertyName = name |
@@ -35,3 +35,3 @@ 'use strict'; | ||
forwardEvents.noErr = function noErr (to, from, onSuccess, augmentError) { | ||
forwardEvents(to, from, function noErrFinish() { | ||
return forwardEvents(to, from, function noErrFinish() { | ||
var args = Array.prototype.slice.call(arguments) | ||
@@ -38,0 +38,0 @@ var errored = args.shift() |
@@ -56,2 +56,3 @@ 'use strict'; | ||
Graph.File = File | ||
function File(graph, path) { | ||
@@ -64,2 +65,3 @@ Object.defineProperty(this, 'graph', {value: graph}) | ||
Graph.Edge = Edge | ||
function Edge(graph, index, trans, outFiles) { | ||
@@ -66,0 +68,0 @@ if (outFiles.length < 1) throw new Error(AT_LEAST_OUT) |
@@ -14,3 +14,3 @@ 'use strict'; | ||
throw errors.invalidArg('opts', opts) | ||
if (!opts) opts = { } | ||
if (!opts) opts = {} | ||
if (opts.concurrency && | ||
@@ -51,19 +51,23 @@ (typeof opts.concurrency !== 'number' || opts.concurrency < 1)) | ||
this._runEdge(edge, function edgeRun(err) { | ||
self._pending-- | ||
if (err) { | ||
self.emit('error', err) | ||
if (err.signal) self.abort(err.signal) | ||
else if (self._shy) self.abort() | ||
} else { | ||
self._isEdgeDone[edge.index] = true | ||
} | ||
if (!self._abort) { | ||
var nextEdges = getReadyOutEdges(self._isEdgeDone, edge.outFiles) | ||
return self._processEdges(nextEdges) | ||
} | ||
if (self._pending > 0) return | ||
self.emit('finish', self._signal) | ||
self._edgeRun(err, edge) | ||
}) | ||
} | ||
RunEdgesTask.prototype._edgeRun = function (err, edge) { | ||
this._pending-- | ||
if (err) { | ||
this.emit('error', err) | ||
if (err.signal) this.abort(err.signal) | ||
else if (this._shy) this.abort() | ||
} else { | ||
this._isEdgeDone[edge.index] = true | ||
} | ||
if (!this._abort) { | ||
var nextEdges = getReadyOutEdges(this._isEdgeDone, edge.outFiles) | ||
return this._processEdges(nextEdges) | ||
} | ||
if (this._pending > 0) return | ||
this.emit('finish', this._signal) | ||
} | ||
RunEdgesTask.prototype.abort = function (signal) { | ||
@@ -70,0 +74,0 @@ this._abort = true |
{ | ||
"name": "mekano", | ||
"version": "0.1.6", | ||
"version": "0.1.7", | ||
"description": "maintain, update and regenerate groups of files", | ||
@@ -5,0 +5,0 @@ "main": "index.js", |
118915
59
2829