254 lines
6.6 KiB
JavaScript
254 lines
6.6 KiB
JavaScript
|
||
var fs = require('fs');
|
||
var path = require('path');
|
||
var nopt = require('nopt');
|
||
var util = require('./util');
|
||
var events = require('events');
|
||
|
||
var collectable = require('./actions/collectable');
|
||
var commandable = require('./actions/commandable');
|
||
|
||
module.exports = noptify;
|
||
noptify.Noptify = Noptify;
|
||
|
||
// noptify is a little wrapper around `nopt` module adding a more expressive,
|
||
// commander-like, API and few helpers.
|
||
//
|
||
// Examples
|
||
//
|
||
// var program = noptify(process.argv, { program: 'name' })
|
||
// .version('0.0.1')
|
||
// .option('port', '-p', 'Port to listen on (default: 35729)', Number)
|
||
// .option('pid', 'Path to the generated PID file', String)
|
||
//
|
||
// var opts = program.parse();
|
||
//
|
||
// Returns an instance of `Noptify`
|
||
function noptify(args, options) {
|
||
return new Noptify(args, options);
|
||
}
|
||
|
||
// Noptify provides the API to parse out option, shorthands and generate the
|
||
// proper generic help output.
|
||
//
|
||
// - args - The Array of arguments to parse (default: `process.argv`);
|
||
// - options - An hash of options with the following properties
|
||
// - program - The program name to use in usage output
|
||
//
|
||
// Every noptify instance is created with two options, `-h, --help` and `-v,
|
||
// --version`.
|
||
function Noptify(args, options) {
|
||
events.EventEmitter.call(this);
|
||
options = this.options = options || {};
|
||
this.args = args || process.argv;
|
||
this._program = options.program || (path.basename(this.args[this.args[0] === 'node' ? 1 : 0]));
|
||
|
||
this._shorthands = {};
|
||
this._commands = {};
|
||
this._routes = [];
|
||
this._steps = [];
|
||
this.nopt = {};
|
||
|
||
this.option('help', '-h', 'Show help usage');
|
||
this.option('version', '-v', 'Show package version');
|
||
}
|
||
|
||
util.inherits(Noptify, events.EventEmitter);
|
||
|
||
// Inherits from each actions' mixins
|
||
//
|
||
// XXX: consider making it optional? with a `.use()` method?
|
||
util.extend(Noptify.prototype, collectable);
|
||
util.extend(Noptify.prototype, commandable);
|
||
|
||
|
||
// Parse the provided options and shorthands, pass them through `nopt` and
|
||
// return the result.
|
||
//
|
||
// When `opts.help` is set, the help output is displayed and `help`
|
||
// event is emitted. The process exists with `0` status, the help output is
|
||
// automatically displayed and the `help` event is emitted.
|
||
//
|
||
// Examples
|
||
//
|
||
// var program = noptify(['foo', '--help'])
|
||
// .on('help', function() {
|
||
// console.log('Examples');
|
||
// console.log('');
|
||
// console.log(' foo bar --baz > foo.txt');
|
||
// });
|
||
//
|
||
// var opts = program.parse();
|
||
// // ... Help output ...
|
||
// // ... Custom help output ...
|
||
// // ... Exit ...
|
||
//
|
||
//
|
||
Noptify.prototype.parse = function parse(argv, position) {
|
||
argv = argv || this.args;
|
||
var options = this._options.reduce(function(opts, opt) {
|
||
opts[opt.name] = opt.type;
|
||
return opts;
|
||
}, {});
|
||
|
||
this._options.forEach(function(opt) {
|
||
if(!opt.shorthand) return;
|
||
this.shorthand(opt.shorthand, '--' + opt.name);
|
||
}, this);
|
||
|
||
var opts = nopt(options, this._shorthands, argv, position);
|
||
if(opts.version) {
|
||
console.log(this._version);
|
||
process.exit(0);
|
||
}
|
||
|
||
var registered = this.registered(opts.argv.remain);
|
||
if(opts.help) {
|
||
if(registered && registered.help) {
|
||
registered.help().emit('help');
|
||
} else {
|
||
this.help().emit('help');
|
||
}
|
||
|
||
process.exit(0);
|
||
}
|
||
|
||
this.nopt = opts;
|
||
|
||
// check remaining args and registered command, for each match, remove the
|
||
// argument from remaining args and trigger the handler, ideally the handler
|
||
// can be another subprogram, for now simple functions
|
||
|
||
if(this.routeCommand) process.nextTick(this.routeCommand.bind(this));
|
||
if(this.stdin && this._readFromStdin) process.nextTick(this.stdin.bind(this));
|
||
return opts;
|
||
};
|
||
|
||
// Define the program version.
|
||
Noptify.prototype.version = function version(ver) {
|
||
this._version = ver;
|
||
return this;
|
||
};
|
||
|
||
// Define the program property.
|
||
Noptify.prototype.program = function program(value) {
|
||
this._program = value || '';
|
||
return this;
|
||
};
|
||
|
||
// Define `name` option with optional shorthands, optional description and optional type.
|
||
Noptify.prototype.option = function option(name, shorthand, description, type) {
|
||
this._options = this._options || [];
|
||
if(!description) {
|
||
description = shorthand;
|
||
shorthand = '';
|
||
}
|
||
|
||
if(!type) {
|
||
if(typeof description === 'function') {
|
||
type = description;
|
||
description = shorthand;
|
||
shorthand = '';
|
||
} else {
|
||
type = String;
|
||
}
|
||
}
|
||
|
||
shorthand = shorthand.replace(/^-*/, ''),
|
||
|
||
this._options.push({
|
||
name: name,
|
||
shorthand: shorthand.replace(/^-*/, ''),
|
||
description: description || (name + ': ' + type.name),
|
||
usage: (shorthand ? '-' + shorthand + ', ': '' ) + '--' + name,
|
||
type: type
|
||
});
|
||
|
||
return this;
|
||
};
|
||
|
||
// Stores the given `shorthands` Hash of options to be `parse()`-d by nopt
|
||
// later on.
|
||
|
||
Noptify.prototype.shorthand =
|
||
Noptify.prototype.shorthands = function shorthands(options, value) {
|
||
if(typeof options === 'string' && value) {
|
||
this._shorthands[options] = value;
|
||
return this;
|
||
}
|
||
|
||
Object.keys(options).forEach(function(shorthand) {
|
||
this._shorthands[shorthand] = options[shorthand];
|
||
}, this);
|
||
return this;
|
||
};
|
||
|
||
// Simply output to stdout the Usage and Help output.
|
||
Noptify.prototype.help = function help() {
|
||
var buf = '';
|
||
buf += '\n Usage: ' + this._program + ' [options]';
|
||
buf += '\n';
|
||
buf += '\n Options:\n';
|
||
|
||
var maxln = Math.max.apply(Math, this._options.map(function(opts) {
|
||
return opts.usage.length;
|
||
}));
|
||
|
||
var options = this._options.map(function(opts) {
|
||
return ' ' + pad(opts.usage, maxln + 5) + '\t- ' + opts.description;
|
||
});
|
||
|
||
buf += options.join('\n');
|
||
|
||
// part of help input ? --list-shorthands ?
|
||
var shorthands = Object.keys(this._shorthands);
|
||
if(shorthands.length) {
|
||
buf += '\n\n Shorthands:\n';
|
||
maxln = Math.max.apply(Math, Object.keys(this._shorthands).map(function(key) {
|
||
return key.length;
|
||
}));
|
||
buf += Object.keys(this._shorthands).map(function(shorthand) {
|
||
return ' --' + pad(shorthand, maxln + 1) + '\t\t' + this._shorthands[shorthand];
|
||
}, this).join('\n');
|
||
}
|
||
|
||
buf += '\n';
|
||
|
||
console.log(buf);
|
||
return this;
|
||
};
|
||
|
||
// Helpers
|
||
|
||
// command API
|
||
|
||
Noptify.prototype.run = function run(fn) {
|
||
if(fn) {
|
||
this._steps.push(fn);
|
||
return this;
|
||
}
|
||
|
||
if(!this.nopt.argv) return this.parse();
|
||
|
||
var steps = this._steps;
|
||
var self = this;
|
||
(function next(step) {
|
||
if(!step) return;
|
||
var async = /function\s*\(\w+/.test(step + '');
|
||
if(!async) {
|
||
step();
|
||
return next(steps.shift());
|
||
}
|
||
|
||
step(function(err) {
|
||
if(err) return self.emit('error', err);
|
||
next(steps.shift());
|
||
});
|
||
})(steps.shift());
|
||
};
|
||
|
||
function pad(str, max) {
|
||
var ln = max - str.length;
|
||
return ln > 0 ? str + new Array(ln).join(' ') : str;
|
||
}
|