Skip to content

Commit

Permalink
add default executable (subcommand) option
Browse files Browse the repository at this point in the history
  • Loading branch information
Qix- committed Jun 21, 2015
1 parent f68ab32 commit a456539
Show file tree
Hide file tree
Showing 5 changed files with 58 additions and 2 deletions.
4 changes: 3 additions & 1 deletion Readme.md
Expand Up @@ -167,13 +167,15 @@ program
.version('0.0.1')
.command('install [name]', 'install one or more packages')
.command('search [query]', 'search with optional query')
.command('list', 'list packages installed')
.command('list', 'list packages installed', {isDefault: true})
.parse(process.argv);
```

When `.command()` is invoked with a description argument, no `.action(callback)` should be called to handle sub-commands, otherwise there will be an error. This tells commander that you're going to use separate executables for sub-commands, much like `git(1)` and other popular tools.
The commander will try to search the executables in the directory of the entry script (like `./examples/pm`) with the name `program-command`, like `pm-install`, `pm-search`.

Options can be passed with the call to `.command()`. Specifying `true` for `opts.noHelp` will remove the option from the generated help output. Specifying `true` for `opts.isDefault` will run the subcommand if no other subcommand is specified.

If the program is designed to be installed globally, make sure the executables have proper modes, like `755`.

### `--harmony`
Expand Down
7 changes: 6 additions & 1 deletion index.js
Expand Up @@ -165,6 +165,7 @@ Command.prototype.command = function(name, desc, opts) {
cmd.description(desc);
this.executables = true;
this._execs[cmd._name] = true;
if (opts.isDefault) this.defaultExecutable = cmd._name;
}

cmd._noHelp = !!opts.noHelp;
Expand Down Expand Up @@ -446,7 +447,7 @@ Command.prototype.parse = function(argv) {
this._name = this._name || basename(argv[1], '.js');

// github-style sub-commands with no sub-command
if (this.executables && argv.length < 3) {
if (this.executables && argv.length < 3 && !this.defaultExecutable) {
// this user needs help
argv.push('--help');
}
Expand All @@ -461,6 +462,10 @@ Command.prototype.parse = function(argv) {
var name = result.args[0];
if (this._execs[name] && typeof this._execs[name] != "function") {
return this.executeSubCommand(argv, args, parsed.unknown);
} else if (this.defaultExecutable) {
// use the default subcommand
args.unshift(name = this.defaultExecutable);
return this.executeSubCommand(argv, args, parsed.unknown);
}

return result;
Expand Down
1 change: 1 addition & 0 deletions test/fixtures/pm
Expand Up @@ -8,4 +8,5 @@ program
.command('search [query]', 'search with optional query')
.command('list', 'list packages installed')
.command('publish', 'publish or update package')
.command('default', 'default command', {noHelp: true, isDefault: true})
.parse(process.argv);
2 changes: 2 additions & 0 deletions test/fixtures/pm-default
@@ -0,0 +1,2 @@
#!/usr/bin/env node
console.log('default');
46 changes: 46 additions & 0 deletions test/test.command.executableSubcommandDefault.js
@@ -0,0 +1,46 @@
var exec = require('child_process').exec
, path = require('path')
, should = require('should');



var bin = path.join(__dirname, './fixtures/pm')
// success case
exec(bin + ' default', function(error, stdout, stderr) {
stdout.should.equal('default\n');
});

// success case (default)
exec(bin, function(error, stdout, stderr) {
stdout.should.equal('default\n');
});

// not exist
exec(bin + ' list', function (error, stdout, stderr) {
//stderr.should.equal('\n pm-list(1) does not exist, try --help\n\n');
// TODO error info are not the same in between <=v0.8 and later version
should.notEqual(0, stderr.length);
});

// success case
exec(bin + ' install', function (error, stdout, stderr) {
stdout.should.equal('install\n');
});

// subcommand bin file with explicit extension
exec(bin + ' publish', function (error, stdout, stderr) {
stdout.should.equal('publish\n');
});

// spawn EACCES
exec(bin + ' search', function (error, stdout, stderr) {
// TODO error info are not the same in between <v0.10 and v0.12
should.notEqual(0, stderr.length);
});

// when `bin` is a symbol link for mocking global install
var bin = path.join(__dirname, './fixtures/pmlink')
// success case
exec(bin + ' install', function (error, stdout, stderr) {
stdout.should.equal('install\n');
});

0 comments on commit a456539

Please sign in to comment.