nexpect.js | |
---|---|
/*
* nexpect.js: Top-level include for the `nexpect` module.
*
* (C) 2011, Elijah Insua, Marak Squires, Charlie Robbins.
*
*/
var spawn = require('child_process').spawn;
function chain (context) {
return { | |
function expect (str)@str {string} Output to assert on the target streamAdds a one-time assertion to the | expect: function (str) {
var _expect = function _expect (data) {
return data.indexOf(str) > -1;
};
_expect.shift = true;
context.queue.push(_expect);
return chain(context);
}, |
function wait (str)@str {string} Output to assert on the target streamAdds an assertion to the | wait: function (str) {
var _wait = function _wait (data) {
return data.indexOf(str) > -1;
};
_wait.shift = false;
context.queue.push(_wait);
return chain(context);
}, |
function sendline (line)@line {string} Output to write to the child process.Adds a write line to | sendline: function (line) {
var _sendline = function _sendline () {
context.process.stdin.write(line + '\n');
if (context.verbose) {
process.stdout.write(line + '\n');
}
};
_sendline.shift = true;
context.queue.push(_sendline);
return chain(context);
}, |
function run (callback)@callback {function} Continuation to respond to when completeRuns the | run: function (callback) {
var errState = null,
responded = false,
stdout = []; |
onError Helper function to respond to the callback with a specified error. Kills the child process if necessary. | function onError (err, kill) {
if (errState || responded) {
return;
}
errState = err;
responded = true;
if (kill) {
try { context.process.kill() }
catch (ex) { }
}
callback(err);
}
|
validateFnType Helper function to validate the | function validateFnType (currentFn) {
if (typeof currentFn !== 'function') { |
If the | onError(new Error('Cannot process non-function on nexpect stack.'), true);
return false;
}
else if (['_expect', '_sendline', '_wait'].indexOf(currentFn.name) === -1) { |
If the | onError(new Error('Unexpected context function name: ' + currentFn.name), true);
return false;
}
return true;
}
|
evalContext Core evaluation logic that evaluates the next function in
| function evalContext (data, name) {
var currentFn = context.queue[0];
if (!currentFn || (name === '_expect' && currentFn.name === '_expect')) { |
If there is nothing left on the context or we are trying to
evaluate two consecutive | return;
}
if (currentFn.shift) {
context.queue.shift();
}
if (!validateFnType(currentFn)) {
return;
}
if (currentFn.name === '_expect') { |
If this is an | return currentFn(data) === true
? evalContext(data, '_expect')
: onError(new Error(data + ' was not expected..'), true);
}
else if (currentFn.name === '_wait') { |
If this is a | if (currentFn(data) === true) {
context.queue.shift();
evalContext(data, '_expect');
}
}
else if (currentFn.name === '_sendline') { |
If the | currentFn();
}
}
|
onLine Preprocesses the
| function onLine (data) {
data = data.toString();
if (context.stripColors) {
data = data.replace(/\u001b\[\d{0,2}m/g, '');
}
if (context.ignoreCase) {
data = data.toLowerCase();
}
var lines = data.split('\n').filter(function (line) { return line.length > 0 });
stdout = stdout.concat(lines);
while (lines.length > 0) {
evalContext(lines.shift(), null);
}
}
|
flushQueue Helper function which flushes any remaining functions from
| function flushQueue () {
var currentFn = context.queue.shift(),
lastLine = stdout[stdout.length - 1];
if (!lastLine) {
onError(new Error('No data from child with non-empty queue.'))
return false;
}
else if (context.queue.length > 0) {
onError(new Error('Non-empty queue on spawn exit.'));
return false;
}
else if (!validateFnType(currentFn)) {
return false;
}
else if (currentFn.name === '_sendline') {
onError(new Error('Cannot call sendline after the process has exited'));
return false;
}
else if (currentFn.name === '_wait' || currentFn.name === '_expect') {
if (currentFn(lastLine) !== true) {
onError(new Error(lastLine + ' was not expected..'));
return false
}
}
return true;
}
|
onData Helper function for writing any data from a stream
to | function onData (data) {
process.stdout.write(data);
} |
Spawn the child process and begin processing the target stream for this chain. | context.process = spawn(context.command, context.params);
context.process[context.stream].on('data', onLine);
if (context.verbose) {
context.process.stdout.on('data', onData);
context.process.stderr.on('data', onData);
}
|
When the process exits, check the output | context.process.on('exit', function (code, signal) {
if (code === 127) { |
If the response code is | return onError(new Error('Command not found: ' + context.command));
}
else if (context.queue.length && !flushQueue()) {
return;
}
callback(null, stdout);
});
return context.process;
}
};
}; |
function nspawn (command, [params], [options])@command {string|Array} Command or complete set of params to spawn@params {Array} Optional Argv to pass to the child process@options {Object} Optional Additional options for spawning the child process.Top-level entry point for | function nspawn (command, params, options) {
if (arguments.length === 2) {
if (Array.isArray(arguments[1])) {
options = {};
}
else {
options = arguments[1];
params = null;
}
}
if (Array.isArray(command)) {
params = command;
command = params.shift();
}
else if (typeof command === 'string') {
command = command.split(' ');
params = params || command.slice(1);
command = command[0];
}
options = options || {};
context = {
command: command,
params: params,
queue: [],
stream: options.stream || 'stdout',
verbose: options.verbose,
stripColors: options.stripColors,
ignoreCase: options.ignoreCase
};
return chain(context);
}; |
Export the core | module.exports.spawn = nspawn;
module.exports.nspawn = {
spawn: nspawn
};
|