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