| var path = require('path'); |
| var minimist = require('minimist'); |
| var wordwrap = require('wordwrap'); |
| |
| /* Hack an instance of Argv with process.argv into Argv |
| so people can do |
| require('optimist')(['--beeble=1','-z','zizzle']).argv |
| to parse a list of args and |
| require('optimist').argv |
| to get a parsed version of process.argv. |
| */ |
| |
| var inst = Argv(process.argv.slice(2)); |
| Object.keys(inst).forEach(function (key) { |
| Argv[key] = typeof inst[key] == 'function' |
| ? inst[key].bind(inst) |
| : inst[key]; |
| }); |
| |
| var exports = module.exports = Argv; |
| function Argv (processArgs, cwd) { |
| var self = {}; |
| if (!cwd) cwd = process.cwd(); |
| |
| self.$0 = process.argv |
| .slice(0,2) |
| .map(function (x) { |
| var b = rebase(cwd, x); |
| return x.match(/^\//) && b.length < x.length |
| ? b : x |
| }) |
| .join(' ') |
| ; |
| |
| if (process.env._ != undefined && process.argv[1] == process.env._) { |
| self.$0 = process.env._.replace( |
| path.dirname(process.execPath) + '/', '' |
| ); |
| } |
| |
| var options = { |
| boolean: [], |
| string: [], |
| alias: {}, |
| default: [] |
| }; |
| |
| self.boolean = function (bools) { |
| options.boolean.push.apply(options.boolean, [].concat(bools)); |
| return self; |
| }; |
| |
| self.string = function (strings) { |
| options.string.push.apply(options.string, [].concat(strings)); |
| return self; |
| }; |
| |
| self.default = function (key, value) { |
| if (typeof key === 'object') { |
| Object.keys(key).forEach(function (k) { |
| self.default(k, key[k]); |
| }); |
| } |
| else { |
| options.default[key] = value; |
| } |
| return self; |
| }; |
| |
| self.alias = function (x, y) { |
| if (typeof x === 'object') { |
| Object.keys(x).forEach(function (key) { |
| self.alias(key, x[key]); |
| }); |
| } |
| else { |
| options.alias[x] = (options.alias[x] || []).concat(y); |
| } |
| return self; |
| }; |
| |
| var demanded = {}; |
| self.demand = function (keys) { |
| if (typeof keys == 'number') { |
| if (!demanded._) demanded._ = 0; |
| demanded._ += keys; |
| } |
| else if (Array.isArray(keys)) { |
| keys.forEach(function (key) { |
| self.demand(key); |
| }); |
| } |
| else { |
| demanded[keys] = true; |
| } |
| |
| return self; |
| }; |
| |
| var usage; |
| self.usage = function (msg, opts) { |
| if (!opts && typeof msg === 'object') { |
| opts = msg; |
| msg = null; |
| } |
| |
| usage = msg; |
| |
| if (opts) self.options(opts); |
| |
| return self; |
| }; |
| |
| function fail (msg) { |
| self.showHelp(); |
| if (msg) console.error(msg); |
| process.exit(1); |
| } |
| |
| var checks = []; |
| self.check = function (f) { |
| checks.push(f); |
| return self; |
| }; |
| |
| var descriptions = {}; |
| self.describe = function (key, desc) { |
| if (typeof key === 'object') { |
| Object.keys(key).forEach(function (k) { |
| self.describe(k, key[k]); |
| }); |
| } |
| else { |
| descriptions[key] = desc; |
| } |
| return self; |
| }; |
| |
| self.parse = function (args) { |
| return parseArgs(args); |
| }; |
| |
| self.option = self.options = function (key, opt) { |
| if (typeof key === 'object') { |
| Object.keys(key).forEach(function (k) { |
| self.options(k, key[k]); |
| }); |
| } |
| else { |
| if (opt.alias) self.alias(key, opt.alias); |
| if (opt.demand) self.demand(key); |
| if (typeof opt.default !== 'undefined') { |
| self.default(key, opt.default); |
| } |
| |
| if (opt.boolean || opt.type === 'boolean') { |
| self.boolean(key); |
| } |
| if (opt.string || opt.type === 'string') { |
| self.string(key); |
| } |
| |
| var desc = opt.describe || opt.description || opt.desc; |
| if (desc) { |
| self.describe(key, desc); |
| } |
| } |
| |
| return self; |
| }; |
| |
| var wrap = null; |
| self.wrap = function (cols) { |
| wrap = cols; |
| return self; |
| }; |
| |
| self.showHelp = function (fn) { |
| if (!fn) fn = console.error; |
| fn(self.help()); |
| }; |
| |
| self.help = function () { |
| var keys = Object.keys( |
| Object.keys(descriptions) |
| .concat(Object.keys(demanded)) |
| .concat(Object.keys(options.default)) |
| .reduce(function (acc, key) { |
| if (key !== '_') acc[key] = true; |
| return acc; |
| }, {}) |
| ); |
| |
| var help = keys.length ? [ 'Options:' ] : []; |
| |
| if (usage) { |
| help.unshift(usage.replace(/\$0/g, self.$0), ''); |
| } |
| |
| var switches = keys.reduce(function (acc, key) { |
| acc[key] = [ key ].concat(options.alias[key] || []) |
| .map(function (sw) { |
| return (sw.length > 1 ? '--' : '-') + sw |
| }) |
| .join(', ') |
| ; |
| return acc; |
| }, {}); |
| |
| var switchlen = longest(Object.keys(switches).map(function (s) { |
| return switches[s] || ''; |
| })); |
| |
| var desclen = longest(Object.keys(descriptions).map(function (d) { |
| return descriptions[d] || ''; |
| })); |
| |
| keys.forEach(function (key) { |
| var kswitch = switches[key]; |
| var desc = descriptions[key] || ''; |
| |
| if (wrap) { |
| desc = wordwrap(switchlen + 4, wrap)(desc) |
| .slice(switchlen + 4) |
| ; |
| } |
| |
| var spadding = new Array( |
| Math.max(switchlen - kswitch.length + 3, 0) |
| ).join(' '); |
| |
| var dpadding = new Array( |
| Math.max(desclen - desc.length + 1, 0) |
| ).join(' '); |
| |
| var type = null; |
| |
| if (options.boolean[key]) type = '[boolean]'; |
| if (options.string[key]) type = '[string]'; |
| |
| if (!wrap && dpadding.length > 0) { |
| desc += dpadding; |
| } |
| |
| var prelude = ' ' + kswitch + spadding; |
| var extra = [ |
| type, |
| demanded[key] |
| ? '[required]' |
| : null |
| , |
| options.default[key] !== undefined |
| ? '[default: ' + JSON.stringify(options.default[key]) + ']' |
| : null |
| , |
| ].filter(Boolean).join(' '); |
| |
| var body = [ desc, extra ].filter(Boolean).join(' '); |
| |
| if (wrap) { |
| var dlines = desc.split('\n'); |
| var dlen = dlines.slice(-1)[0].length |
| + (dlines.length === 1 ? prelude.length : 0) |
| |
| body = desc + (dlen + extra.length > wrap - 2 |
| ? '\n' |
| + new Array(wrap - extra.length + 1).join(' ') |
| + extra |
| : new Array(wrap - extra.length - dlen + 1).join(' ') |
| + extra |
| ); |
| } |
| |
| help.push(prelude + body); |
| }); |
| |
| help.push(''); |
| return help.join('\n'); |
| }; |
| |
| Object.defineProperty(self, 'argv', { |
| get : function () { return parseArgs(processArgs) }, |
| enumerable : true, |
| }); |
| |
| function parseArgs (args) { |
| var argv = minimist(args, options); |
| argv.$0 = self.$0; |
| |
| if (demanded._ && argv._.length < demanded._) { |
| fail('Not enough non-option arguments: got ' |
| + argv._.length + ', need at least ' + demanded._ |
| ); |
| } |
| |
| var missing = []; |
| Object.keys(demanded).forEach(function (key) { |
| if (!argv[key]) missing.push(key); |
| }); |
| |
| if (missing.length) { |
| fail('Missing required arguments: ' + missing.join(', ')); |
| } |
| |
| checks.forEach(function (f) { |
| try { |
| if (f(argv) === false) { |
| fail('Argument check failed: ' + f.toString()); |
| } |
| } |
| catch (err) { |
| fail(err) |
| } |
| }); |
| |
| return argv; |
| } |
| |
| function longest (xs) { |
| return Math.max.apply( |
| null, |
| xs.map(function (x) { return x.length }) |
| ); |
| } |
| |
| return self; |
| }; |
| |
| // rebase an absolute path to a relative one with respect to a base directory |
| // exported for tests |
| exports.rebase = rebase; |
| function rebase (base, dir) { |
| var ds = path.normalize(dir).split('/').slice(1); |
| var bs = path.normalize(base).split('/').slice(1); |
| |
| for (var i = 0; ds[i] && ds[i] == bs[i]; i++); |
| ds.splice(0, i); bs.splice(0, i); |
| |
| var p = path.normalize( |
| bs.map(function () { return '..' }).concat(ds).join('/') |
| ).replace(/\/$/,'').replace(/^$/, '.'); |
| return p.match(/^[.\/]/) ? p : './' + p; |
| }; |