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; | ||
|  | }; |