344 lines
		
	
	
		
			9.3 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			344 lines
		
	
	
		
			9.3 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| 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;
 | |
| };
 |