Skip to content

Commit

Permalink
A first cut at an evented exec.
Browse files Browse the repository at this point in the history
  • Loading branch information
mde committed May 20, 2012
1 parent 447c396 commit 1e5bb7c
Showing 1 changed file with 166 additions and 129 deletions.
295 changes: 166 additions & 129 deletions lib/utils/index.js
Expand Up @@ -22,48 +22,51 @@ var exec = require('child_process').exec
, EventEmitter = require('events').EventEmitter
, fileUtils = require('./file')
, utils
, Exec;
, Exec
, _mix, _trim, _truncate;

/**
@name jake
@namespace jake
*/
utils = new (function () {
var _mix = function (targ, src, merge, includeProto) {
for (var p in src) {
// Don't copy stuff from the prototype
if (src.hasOwnProperty(p) || includeProto) {
if (merge &&
// Assumes the source property is an Object you can
// actually recurse down into
(typeof src[p] == 'object') &&
(src[p] !== null) &&
!(src[p] instanceof Array)) {
// Create the source property if it doesn't exist
// TODO: What if it's something weird like a String or Number?
if (typeof targ[p] == 'undefined') {
targ[p] = {};
}
_mix(targ[p], src[p], merge, includeProto); // Recurse
}
// If it's not a merge-copy, just set and forget
else {
targ[p] = src[p];
}
}
_mix = function (targ, src, merge, includeProto) {
for (var p in src) {
// Don't copy stuff from the prototype
if (src.hasOwnProperty(p) || includeProto) {
if (merge &&
// Assumes the source property is an Object you can
// actually recurse down into
(typeof src[p] == 'object') &&
(src[p] !== null) &&
!(src[p] instanceof Array)) {
// Create the source property if it doesn't exist
// TODO: What if it's something weird like a String or Number?
if (typeof targ[p] == 'undefined') {
targ[p] = {};
}
_mix(targ[p], src[p], merge, includeProto); // Recurse
}

, _trim = function (s) {
var str = s || '';
return str.replace(/^\s*|\s*$/g, '');
// If it's not a merge-copy, just set and forget
else {
targ[p] = src[p];
}
}
}
};

_trim = function (s) {
var str = s || '';
return str.replace(/^\s*|\s*$/g, '');
};

_truncate = function (s) {
var str = s ? s.replace(/\n$/, '') : '';
return str;
};

, _truncate = function (s) {
var str = s ? s.replace(/\n$/, '') : '';
return str;
};


/**
@name jake
@namespace jake
*/
utils = new (function () {
/**
@name jake.exec
@static
Expand Down Expand Up @@ -92,93 +95,13 @@ utils = new (function () {
}
jake.exec(cmds, callback, {stdout: true});
*/
this.exec = function () {
var args
, arg
, list
, callback
, opts = {}
, printStdout = false
, printStderr = false
, breakOnError = true
, run;

args = Array.prototype.slice.call(arguments);

// The list of shell-commands to run (make a copy)
list = args.shift().slice();

// Get optional callback or opts
while((arg = args.shift())) {
if (typeof arg == 'function') {
callback = arg;
}
else if (typeof arg == 'object') {
opts = arg;
}
}

// Override default options
printStdout = !!opts.stdout;
printStderr = !!opts.stderr;
breakOnError = typeof opts.breakOnError != 'undefined' ?
opts.breakOnError : true;

run = function () {
var sh
, cmd
, args
, next = list.shift()
, errData = '';
// Keep running as long as there are commands in the array
if (next) {
// Ganking part of Node's child_process.exec to get cmdline args parsed
cmd = '/bin/sh';
args = ['-c', next];
if (process.platform == 'win32') {
cmd = 'cmd';
args = ['/c', next];
}

// Spawn a child-process, set up output
sh = spawn(cmd, args);
// Out
if (printStdout) {
sh.stdout.on('data', function (data) {
console.log(_truncate(data.toString()));
});
}
// Err
sh.stderr.on('data', function (data) {
var d = data.toString();
if (printStderr) {
console.error(_truncate(d));
}
// Accumulate the error-data so we can use it as the
// stack if the process exits with an error
errData += d;
});
// Exit, handle err or run next
sh.on('exit', function (code) {
var msg;
if (breakOnError && code != 0) {
msg = errData || 'Process exited with error.';
msg = _trim(msg);
fail(msg, code);
}
else {
run();
}
});
}
else {
if (typeof callback == 'function') {
callback();
}
}
};
this.exec = function (a, b, c) {
var e = new Exec(a, b, c);
e.run();
};

run();
this.createExec = function (a, b, c) {
return new Exec(a, b, c);
};

this.objectToString = function (object) {
Expand Down Expand Up @@ -239,20 +162,134 @@ utils = new (function () {

})();

Exec = function (cmds, opts) {
var options = opts || {};
this.cmds = cmds;
this.config = {
stdout: false
, stderr: false
Exec = function () {
var args
, arg
, cmds
, callback
, opts = {};

args = Array.prototype.slice.call(arguments);

cmds = args.shift();
// Arrayize if passed a single string command
if (typeof cmds == 'string') {
cmds = [cmds];
}
// Make a copy if it's an actual list
else {
cmds = cmds.slice();
}

// Get optional callback or opts
while((arg = args.shift())) {
if (typeof arg == 'function') {
callback = arg;
}
else if (typeof arg == 'object') {
opts = arg;
}
}

// Backward-compat shim
if (typeof opts.stdout != 'undefined') {
opts.printStdout = opts.stdout;
}
if (typeof opts.stderr != 'undefined') {
opts.printStderr = opts.stderr;
}

this._cmds = cmds;
this._callback = callback;
this._config = {
printStdout: false
, printStderr: false
, breakOnError: true
};
utils.mixin(this.config, options);
utils.mixin(this._config, opts);
};
Exec.prototype = new EventEmitter();
Exec.prototype.constructor = Exec;

utils.mixin(Exec.prototype, new (function () {

var _run = function () {
var self = this
, sh
, cmd
, args
, next = this._cmds.shift()
, config = this._config
, errData = '';
// Keep running as long as there are commands in the array
if (next) {
this.emit('cmdStart', next);

// Ganking part of Node's child_process.exec to get cmdline args parsed
cmd = '/bin/sh';
args = ['-c', next];
if (process.platform == 'win32') {
cmd = 'cmd';
args = ['/c', next];
}

// Spawn a child-process, set up output
sh = spawn(cmd, args);
// Out
if (config.printStdout) {
sh.stdout.on('data', function (data) {
console.log(_truncate(data.toString()));
self.emit('stdout', data);
});
}
// Err
sh.stderr.on('data', function (data) {
var d = data.toString();
if (config.printStderr) {
console.error(_truncate(d));
}
self.emit('stderr', data);
// Accumulate the error-data so we can use it as the
// stack if the process exits with an error
errData += d;
});
// Exit, handle err or run next
sh.on('exit', function (code) {
var msg;
if (code != 0) {
msg = errData || 'Process exited with error.';
msg = _trim(msg);
self.emit('error', msg, code);
}
if (code != 0 && config.breakOnError) {
// If there are no error-handlers, fail out. Otherwise,
// silently stop
if (!self._events || !self._events.error ||
(Array.isArray(self._events.error) && !self._events.error.length)) {
fail(msg, code);
}
}
else {
self.emit('cmdEnd');
_run.call(self);
}
});
}
else {
self.emit('end');
if (typeof self._callback == 'function') {
self._callback();
}
}
};

this.append = function (cmd) {
this._cmds.push(cmd);
};

this.run = function () {
_run.call(this);
};

})());

Expand Down

0 comments on commit 1e5bb7c

Please sign in to comment.