1 var path = require('path');
2 var wordwrap = require('wordwrap');
4 /* Hack an instance of Argv with process.argv into Argv
6 require('optimist')(['--beeble=1','-z','zizzle']).argv
7 to parse a list of args and
8 require('optimist').argv
9 to get a parsed version of process.argv.
12 var inst = Argv(process.argv.slice(2));
13 Object.keys(inst).forEach(function (key) {
14 Argv[key] = typeof inst[key] == 'function'
15 ? inst[key].bind(inst)
19 var exports = module.exports = Argv;
20 function Argv (args, cwd) {
22 if (!cwd) cwd = process.cwd();
24 self.$0 = process.argv
27 var b = rebase(cwd, x);
28 return x.match(/^\//) && b.length < x.length
34 if (process.argv[1] == process.env._) {
35 self.$0 = process.env._.replace(
36 path.dirname(process.execPath) + '/', ''
40 var flags = { bools : {}, strings : {} };
42 self.boolean = function (bools) {
43 if (!Array.isArray(bools)) {
44 bools = [].slice.call(arguments);
47 bools.forEach(function (name) {
48 flags.bools[name] = true;
54 self.string = function (strings) {
55 if (!Array.isArray(strings)) {
56 strings = [].slice.call(arguments);
59 strings.forEach(function (name) {
60 flags.strings[name] = true;
67 self.alias = function (x, y) {
68 if (typeof x === 'object') {
69 Object.keys(x).forEach(function (key) {
70 self.alias(key, x[key]);
73 else if (Array.isArray(y)) {
74 y.forEach(function (yy) {
79 var zs = (aliases[x] || []).concat(aliases[y] || []).concat(x, y);
80 aliases[x] = zs.filter(function (z) { return z != x });
81 aliases[y] = zs.filter(function (z) { return z != y });
88 self.demand = function (keys) {
89 if (typeof keys == 'number') {
90 if (!demanded._) demanded._ = 0;
93 else if (Array.isArray(keys)) {
94 keys.forEach(function (key) {
99 demanded[keys] = true;
106 self.usage = function (msg, opts) {
107 if (!opts && typeof msg === 'object') {
114 if (opts) self.options(opts);
119 function fail (msg) {
121 if (msg) console.error(msg);
126 self.check = function (f) {
132 self.default = function (key, value) {
133 if (typeof key === 'object') {
134 Object.keys(key).forEach(function (k) {
135 self.default(k, key[k]);
139 defaults[key] = value;
145 var descriptions = {};
146 self.describe = function (key, desc) {
147 if (typeof key === 'object') {
148 Object.keys(key).forEach(function (k) {
149 self.describe(k, key[k]);
153 descriptions[key] = desc;
158 self.parse = function (args) {
159 return Argv(args).argv;
162 self.option = self.options = function (key, opt) {
163 if (typeof key === 'object') {
164 Object.keys(key).forEach(function (k) {
165 self.options(k, key[k]);
169 if (opt.alias) self.alias(key, opt.alias);
170 if (opt.demand) self.demand(key);
171 if (typeof opt.default !== 'undefined') {
172 self.default(key, opt.default);
175 if (opt.boolean || opt.type === 'boolean') {
178 if (opt.string || opt.type === 'string') {
182 var desc = opt.describe || opt.description || opt.desc;
184 self.describe(key, desc);
192 self.wrap = function (cols) {
197 self.showHelp = function (fn) {
198 if (!fn) fn = console.error;
202 self.help = function () {
203 var keys = Object.keys(
204 Object.keys(descriptions)
205 .concat(Object.keys(demanded))
206 .concat(Object.keys(defaults))
207 .reduce(function (acc, key) {
208 if (key !== '_') acc[key] = true;
213 var help = keys.length ? [ 'Options:' ] : [];
216 help.unshift(usage.replace(/\$0/g, self.$0), '');
219 var switches = keys.reduce(function (acc, key) {
220 acc[key] = [ key ].concat(aliases[key] || [])
222 return (sw.length > 1 ? '--' : '-') + sw
229 var switchlen = longest(Object.keys(switches).map(function (s) {
230 return switches[s] || '';
233 var desclen = longest(Object.keys(descriptions).map(function (d) {
234 return descriptions[d] || '';
237 keys.forEach(function (key) {
238 var kswitch = switches[key];
239 var desc = descriptions[key] || '';
242 desc = wordwrap(switchlen + 4, wrap)(desc)
243 .slice(switchlen + 4)
247 var spadding = new Array(
248 Math.max(switchlen - kswitch.length + 3, 0)
251 var dpadding = new Array(
252 Math.max(desclen - desc.length + 1, 0)
257 if (flags.bools[key]) type = '[boolean]';
258 if (flags.strings[key]) type = '[string]';
260 if (!wrap && dpadding.length > 0) {
264 var prelude = ' ' + kswitch + spadding;
271 defaults[key] !== undefined
272 ? '[default: ' + JSON.stringify(defaults[key]) + ']'
275 ].filter(Boolean).join(' ');
277 var body = [ desc, extra ].filter(Boolean).join(' ');
280 var dlines = desc.split('\n');
281 var dlen = dlines.slice(-1)[0].length
282 + (dlines.length === 1 ? prelude.length : 0)
284 body = desc + (dlen + extra.length > wrap - 2
286 + new Array(wrap - extra.length + 1).join(' ')
288 : new Array(wrap - extra.length - dlen + 1).join(' ')
293 help.push(prelude + body);
297 return help.join('\n');
300 Object.defineProperty(self, 'argv', {
305 function parseArgs () {
306 var argv = { _ : [], $0 : self.$0 };
307 Object.keys(flags.bools).forEach(function (key) {
308 setArg(key, defaults[key] || false);
311 function setArg (key, val) {
312 var num = Number(val);
313 var value = typeof val !== 'string' || isNaN(num) ? val : num;
314 if (flags.strings[key]) value = val;
316 setKey(argv, key.split('.'), value);
318 (aliases[key] || []).forEach(function (x) {
323 for (var i = 0; i < args.length; i++) {
327 argv._.push.apply(argv._, args.slice(i + 1));
330 else if (arg.match(/^--.+=/)) {
331 var m = arg.match(/^--([^=]+)=(.*)/);
334 else if (arg.match(/^--no-.+/)) {
335 var key = arg.match(/^--no-(.+)/)[1];
338 else if (arg.match(/^--.+/)) {
339 var key = arg.match(/^--(.+)/)[1];
340 var next = args[i + 1];
341 if (next !== undefined && !next.match(/^-/)
343 && (aliases[key] ? !flags.bools[aliases[key]] : true)) {
347 else if (/true|false/.test(next)) {
348 setArg(key, next === 'true');
355 else if (arg.match(/^-[^-]+/)) {
356 var letters = arg.slice(1,-1).split('');
359 for (var j = 0; j < letters.length; j++) {
360 if (letters[j+1] && letters[j+1].match(/\W/)) {
361 setArg(letters[j], arg.slice(j+2));
366 setArg(letters[j], true);
371 var key = arg.slice(-1)[0];
373 if (args[i+1] && !args[i+1].match(/^-/)
375 && (aliases[key] ? !flags.bools[aliases[key]] : true)) {
376 setArg(key, args[i+1]);
379 else if (args[i+1] && /true|false/.test(args[i+1])) {
380 setArg(key, args[i+1] === 'true');
390 argv._.push(flags.strings['_'] || isNaN(n) ? arg : n);
394 Object.keys(defaults).forEach(function (key) {
395 if (!(key in argv)) {
396 argv[key] = defaults[key];
397 if (key in aliases) {
398 argv[aliases[key]] = defaults[key];
403 if (demanded._ && argv._.length < demanded._) {
404 fail('Not enough non-option arguments: got '
405 + argv._.length + ', need at least ' + demanded._
410 Object.keys(demanded).forEach(function (key) {
411 if (!argv[key]) missing.push(key);
414 if (missing.length) {
415 fail('Missing required arguments: ' + missing.join(', '));
418 checks.forEach(function (f) {
420 if (f(argv) === false) {
421 fail('Argument check failed: ' + f.toString());
432 function longest (xs) {
433 return Math.max.apply(
435 xs.map(function (x) { return x.length })
442 // rebase an absolute path to a relative one with respect to a base directory
443 // exported for tests
444 exports.rebase = rebase;
445 function rebase (base, dir) {
446 var ds = path.normalize(dir).split('/').slice(1);
447 var bs = path.normalize(base).split('/').slice(1);
449 for (var i = 0; ds[i] && ds[i] == bs[i]; i++);
450 ds.splice(0, i); bs.splice(0, i);
452 var p = path.normalize(
453 bs.map(function () { return '..' }).concat(ds).join('/')
454 ).replace(/\/$/,'').replace(/^$/, '.');
455 return p.match(/^[.\/]/) ? p : './' + p;
458 function setKey (obj, keys, value) {
460 keys.slice(0,-1).forEach(function (key) {
461 if (o[key] === undefined) o[key] = {};
465 var key = keys[keys.length - 1];
466 if (o[key] === undefined || typeof o[key] === 'boolean') {
469 else if (Array.isArray(o[key])) {
473 o[key] = [ o[key], value ];