Skip to content

Commit

Permalink
Make commands only available in certain contexts
Browse files Browse the repository at this point in the history
  • Loading branch information
mrvisser committed May 10, 2014
1 parent 39bdb52 commit a19d071
Show file tree
Hide file tree
Showing 10 changed files with 117 additions and 11 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ Currently:
* Environment support for stateful CLI sessions
* Up / down functionality for command history
* `clear` command to clear the terminal window
* Make commands only available in certain contexts

Planned:

Expand Down
2 changes: 1 addition & 1 deletion index.js
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ Corporal.prototype.start = function(callback) {
} else {
// If there is a configuration for command contexts, all we need to do is make sure that the
// internal commands are always available (i.e., clear, help and quit)
commandContexts = {};
commandContexts = self._options.commandContexts;
commandContexts['*'] = commandContexts['*'] || {};
commandContexts['*'].commands = commandContexts['*'].commands || [];
commandContexts['*'].commands = _.union(commandContexts['*'].commands, _.keys(internalCommands));
Expand Down
21 changes: 12 additions & 9 deletions lib/util.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ var domain = require('domain');
var readcommand = require('readcommand');
var sprintf = require('sprintf-js').sprintf;

var commandDomain = domain.create();

/**
* Determine if the given value is defined (i.e., isn't `null` or `undefined`)
*/
Expand Down Expand Up @@ -45,32 +47,33 @@ var doCommandLoop = module.exports.doCommandLoop = function(session, errorHandle
}

// Wrap the invocation into a domain to catch any errors that are thrown from the command
var commandDomain = domain.create();
commandDomain.on('error', function(err) {
return _handleError(commandDomain, err, session, errorHandlers, next);
commandDomain = domain.create();
commandDomain.once('error', function(err) {
return _handleError(err, session, errorHandlers, next);
});

commandDomain.bind(command.invoke)(session, args, function(err) {
commandDomain.enter();
command.invoke(session, args, function(err) {
if (err) {
// Pass any error to the error handler
return _handleError(commandDomain, err, session, errorHandlers, next);
return _handleError(err, session, errorHandlers, next);
} else if (session._quit) {
// Check if the session has been quit. If so, we call back to the master
// and stop the command loop
commandDomain.dispose();
commandDomain.exit();
return callback();
}

// We didn't quit, prompt for the next command
commandDomain.dispose();
commandDomain.exit();
return next();
});
});
};

function _handleError(domain, err, session, errorHandlers, next) {
function _handleError(err, session, errorHandlers, next) {
// Get rid of the domain that caught the error
domain.dispose();
commandDomain.exit();

// Get the first applicable handler
var handlersForType = _.chain(errorHandlers)
Expand Down
6 changes: 6 additions & 0 deletions tests/commands/command-contexts/available-in-contexta.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
module.exports = {
'description': 'available_in_contexta',
'invoke': function(session, args, callback) {
return callback();
}
};
6 changes: 6 additions & 0 deletions tests/commands/command-contexts/available-in-contextb.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
module.exports = {
'description': 'available_in_contextb',
'invoke': function(session, args, callback) {
return callback();
}
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
module.exports = {
'description': 'available_in_default_context',
'invoke': function(session, args, callback) {
return callback();
}
};
7 changes: 7 additions & 0 deletions tests/commands/command-contexts/switch-context.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
module.exports = {
'description': 'switch-context',
'invoke': function(session, args, callback) {
session.commands().ctx(args[0]);
return callback();
}
};
63 changes: 63 additions & 0 deletions tests/test.js
Original file line number Diff line number Diff line change
Expand Up @@ -262,6 +262,69 @@ describe('Error Handling', function() {
});
});

describe('Command Contexts', function() {
it('only makes commands available that are scoped to the current context', function(callback) {
var runner = _createRunner({
'commands': _commandDir('command-contexts'),
'contexts': {
'': ['available-in-default-context'],
'*': ['switch-context'],
'contexta': ['available-in-contexta'],
'contextb': ['available-in-contextb']
}
});

runner.start(function() {
// Ensure only the internal, * commands and those specified for the default context are available in the default context
runner.exec('help', function(data) {
assert.notEqual(data.indexOf('available-in-default-context:'), -1);
assert.notEqual(data.indexOf('switch-context :'), -1);
assert.notEqual(data.indexOf('clear :'), -1);
assert.notEqual(data.indexOf('help :'), -1);
assert.notEqual(data.indexOf('quit :'), -1);
assert.strictEqual(data.indexOf('contexta'), -1);
assert.strictEqual(data.indexOf('contextb'), -1);

// Ensure we can't invoke any of the commands out of context
runner.exec('available-in-contexta', function(data) {
assert.notEqual(data.indexOf('Invalid command'), -1);

// Ensure we can invoke the default context command
runner.exec('available-in-default-context', function(data) {
assert.strictEqual(data.indexOf('Invalid command'), -1);

// Switch contexts
runner.exec('switch-context contexta', function(data) {

// Ensure we only get internal, * and contexta commands
runner.exec('help', function(data) {
assert.notEqual(data.indexOf('available-in-contexta:'), -1);
assert.notEqual(data.indexOf('switch-context :'), -1);
assert.notEqual(data.indexOf('clear :'), -1);
assert.notEqual(data.indexOf('help :'), -1);
assert.notEqual(data.indexOf('quit :'), -1);
assert.strictEqual(data.indexOf('default'), -1);
assert.strictEqual(data.indexOf('contextb'), -1);

// Ensure we can't invoke the default context command
runner.exec('available-in-default-context', function(data) {
assert.notEqual(data.indexOf('Invalid command'), -1);

// Ensure we can now invoke contexta
runner.exec('available-in-contexta', function(data) {
assert.strictEqual(data.indexOf('Invalid command'), -1);
return callback();
});
});
});
});
});
});
});
});
});
});

/*!
* Creates a runner and keeps track of it to be closed after the
* test.
Expand Down
10 changes: 9 additions & 1 deletion tests/util/internal/runner.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,21 @@ var Corporal = require('../../../index');
var TypeAError = require('./errors/TypeAError');
var TypeBError = require('./errors/TypeBError');

var commandContexts = null;
if (argv.contexts) {
commandContexts = {};
_.each(argv.contexts, function(commandNames, contextName) {
commandContexts[contextName] = {'commands': commandNames.split(',')};
});
}

var corporal = new Corporal({
'commands': argv.commands,
'commandContexts': commandContexts,
'disabled': _.isString(argv.disabled) ? argv.disabled.split(',') : null,
'env': JSON.parse(argv.env)
});


/*!
* Verify string precedence over all the things
*/
Expand Down
6 changes: 6 additions & 0 deletions tests/util/runner.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,12 @@ Runner.prototype.start = function(callback) {
args.push('--disabled', self._options.disabled.join(','));
}

if (self._options.contexts) {
_.each(self._options.contexts, function(commandNames, contextName) {
args.push(util.format('--contexts.%s', contextName), commandNames.join(','));
});
}

if (process.env['CORPORAL_TEST_VERBOSE']) {
console.log('spawn: %s', JSON.stringify(_.union('node', args), null, 2));
}
Expand Down

0 comments on commit a19d071

Please sign in to comment.