1 | var path = require('path');
|
2 | var minimist = require('minimist');
|
3 | var wordwrap = require('wordwrap');
|
4 |
|
5 |
|
6 |
|
7 |
|
8 |
|
9 |
|
10 |
|
11 |
|
12 |
|
13 | var inst = Argv(process.argv.slice(2));
|
14 | Object.keys(inst).forEach(function (key) {
|
15 | Argv[key] = typeof inst[key] == 'function'
|
16 | ? inst[key].bind(inst)
|
17 | : inst[key];
|
18 | });
|
19 |
|
20 | var exports = module.exports = Argv;
|
21 | function Argv (processArgs, cwd) {
|
22 | var self = {};
|
23 | if (!cwd) cwd = process.cwd();
|
24 |
|
25 | self.$0 = process.argv
|
26 | .slice(0,2)
|
27 | .map(function (x) {
|
28 | var b = rebase(cwd, x);
|
29 | return x.match(/^\//) && b.length < x.length
|
30 | ? b : x
|
31 | })
|
32 | .join(' ')
|
33 | ;
|
34 |
|
35 | if (process.env._ != undefined && process.argv[1] == process.env._) {
|
36 | self.$0 = process.env._.replace(
|
37 | path.dirname(process.execPath) + '/', ''
|
38 | );
|
39 | }
|
40 |
|
41 | var options = {
|
42 | boolean: [],
|
43 | string: [],
|
44 | alias: {},
|
45 | default: []
|
46 | };
|
47 |
|
48 | self.boolean = function (bools) {
|
49 | options.boolean.push.apply(options.boolean, [].concat(bools));
|
50 | return self;
|
51 | };
|
52 |
|
53 | self.string = function (strings) {
|
54 | options.string.push.apply(options.string, [].concat(strings));
|
55 | return self;
|
56 | };
|
57 |
|
58 | self.default = function (key, value) {
|
59 | if (typeof key === 'object') {
|
60 | Object.keys(key).forEach(function (k) {
|
61 | self.default(k, key[k]);
|
62 | });
|
63 | }
|
64 | else {
|
65 | options.default[key] = value;
|
66 | }
|
67 | return self;
|
68 | };
|
69 |
|
70 | self.alias = function (x, y) {
|
71 | if (typeof x === 'object') {
|
72 | Object.keys(x).forEach(function (key) {
|
73 | self.alias(key, x[key]);
|
74 | });
|
75 | }
|
76 | else {
|
77 | options.alias[x] = (options.alias[x] || []).concat(y);
|
78 | }
|
79 | return self;
|
80 | };
|
81 |
|
82 | var demanded = {};
|
83 | self.demand = function (keys) {
|
84 | if (typeof keys == 'number') {
|
85 | if (!demanded._) demanded._ = 0;
|
86 | demanded._ += keys;
|
87 | }
|
88 | else if (Array.isArray(keys)) {
|
89 | keys.forEach(function (key) {
|
90 | self.demand(key);
|
91 | });
|
92 | }
|
93 | else {
|
94 | demanded[keys] = true;
|
95 | }
|
96 |
|
97 | return self;
|
98 | };
|
99 |
|
100 | var usage;
|
101 | self.usage = function (msg, opts) {
|
102 | if (!opts && typeof msg === 'object') {
|
103 | opts = msg;
|
104 | msg = null;
|
105 | }
|
106 |
|
107 | usage = msg;
|
108 |
|
109 | if (opts) self.options(opts);
|
110 |
|
111 | return self;
|
112 | };
|
113 |
|
114 | function fail (msg) {
|
115 | self.showHelp();
|
116 | if (msg) console.error(msg);
|
117 | process.exit(1);
|
118 | }
|
119 |
|
120 | var checks = [];
|
121 | self.check = function (f) {
|
122 | checks.push(f);
|
123 | return self;
|
124 | };
|
125 |
|
126 | var descriptions = {};
|
127 | self.describe = function (key, desc) {
|
128 | if (typeof key === 'object') {
|
129 | Object.keys(key).forEach(function (k) {
|
130 | self.describe(k, key[k]);
|
131 | });
|
132 | }
|
133 | else {
|
134 | descriptions[key] = desc;
|
135 | }
|
136 | return self;
|
137 | };
|
138 |
|
139 | self.parse = function (args) {
|
140 | return parseArgs(args);
|
141 | };
|
142 |
|
143 | self.option = self.options = function (key, opt) {
|
144 | if (typeof key === 'object') {
|
145 | Object.keys(key).forEach(function (k) {
|
146 | self.options(k, key[k]);
|
147 | });
|
148 | }
|
149 | else {
|
150 | if (opt.alias) self.alias(key, opt.alias);
|
151 | if (opt.demand) self.demand(key);
|
152 | if (typeof opt.default !== 'undefined') {
|
153 | self.default(key, opt.default);
|
154 | }
|
155 |
|
156 | if (opt.boolean || opt.type === 'boolean') {
|
157 | self.boolean(key);
|
158 | }
|
159 | if (opt.string || opt.type === 'string') {
|
160 | self.string(key);
|
161 | }
|
162 |
|
163 | var desc = opt.describe || opt.description || opt.desc;
|
164 | if (desc) {
|
165 | self.describe(key, desc);
|
166 | }
|
167 | }
|
168 |
|
169 | return self;
|
170 | };
|
171 |
|
172 | var wrap = null;
|
173 | self.wrap = function (cols) {
|
174 | wrap = cols;
|
175 | return self;
|
176 | };
|
177 |
|
178 | self.showHelp = function (fn) {
|
179 | if (!fn) fn = console.error;
|
180 | fn(self.help());
|
181 | };
|
182 |
|
183 | self.help = function () {
|
184 | var keys = Object.keys(
|
185 | Object.keys(descriptions)
|
186 | .concat(Object.keys(demanded))
|
187 | .concat(Object.keys(options.default))
|
188 | .reduce(function (acc, key) {
|
189 | if (key !== '_') acc[key] = true;
|
190 | return acc;
|
191 | }, {})
|
192 | );
|
193 |
|
194 | var help = keys.length ? [ 'Options:' ] : [];
|
195 |
|
196 | if (usage) {
|
197 | help.unshift(usage.replace(/\$0/g, self.$0), '');
|
198 | }
|
199 |
|
200 | var switches = keys.reduce(function (acc, key) {
|
201 | acc[key] = [ key ].concat(options.alias[key] || [])
|
202 | .map(function (sw) {
|
203 | return (sw.length > 1 ? '--' : '-') + sw
|
204 | })
|
205 | .join(', ')
|
206 | ;
|
207 | return acc;
|
208 | }, {});
|
209 |
|
210 | var switchlen = longest(Object.keys(switches).map(function (s) {
|
211 | return switches[s] || '';
|
212 | }));
|
213 |
|
214 | var desclen = longest(Object.keys(descriptions).map(function (d) {
|
215 | return descriptions[d] || '';
|
216 | }));
|
217 |
|
218 | keys.forEach(function (key) {
|
219 | var kswitch = switches[key];
|
220 | var desc = descriptions[key] || '';
|
221 |
|
222 | if (wrap) {
|
223 | desc = wordwrap(switchlen + 4, wrap)(desc)
|
224 | .slice(switchlen + 4)
|
225 | ;
|
226 | }
|
227 |
|
228 | var spadding = new Array(
|
229 | Math.max(switchlen - kswitch.length + 3, 0)
|
230 | ).join(' ');
|
231 |
|
232 | var dpadding = new Array(
|
233 | Math.max(desclen - desc.length + 1, 0)
|
234 | ).join(' ');
|
235 |
|
236 | var type = null;
|
237 |
|
238 | if (options.boolean[key]) type = '[boolean]';
|
239 | if (options.string[key]) type = '[string]';
|
240 |
|
241 | if (!wrap && dpadding.length > 0) {
|
242 | desc += dpadding;
|
243 | }
|
244 |
|
245 | var prelude = ' ' + kswitch + spadding;
|
246 | var extra = [
|
247 | type,
|
248 | demanded[key]
|
249 | ? '[required]'
|
250 | : null
|
251 | ,
|
252 | options.default[key] !== undefined
|
253 | ? '[default: ' + JSON.stringify(options.default[key]) + ']'
|
254 | : null
|
255 | ,
|
256 | ].filter(Boolean).join(' ');
|
257 |
|
258 | var body = [ desc, extra ].filter(Boolean).join(' ');
|
259 |
|
260 | if (wrap) {
|
261 | var dlines = desc.split('\n');
|
262 | var dlen = dlines.slice(-1)[0].length
|
263 | + (dlines.length === 1 ? prelude.length : 0)
|
264 |
|
265 | body = desc + (dlen + extra.length > wrap - 2
|
266 | ? '\n'
|
267 | + new Array(wrap - extra.length + 1).join(' ')
|
268 | + extra
|
269 | : new Array(wrap - extra.length - dlen + 1).join(' ')
|
270 | + extra
|
271 | );
|
272 | }
|
273 |
|
274 | help.push(prelude + body);
|
275 | });
|
276 |
|
277 | help.push('');
|
278 | return help.join('\n');
|
279 | };
|
280 |
|
281 | Object.defineProperty(self, 'argv', {
|
282 | get : function () { return parseArgs(processArgs) },
|
283 | enumerable : true,
|
284 | });
|
285 |
|
286 | function parseArgs (args) {
|
287 | var argv = minimist(args, options);
|
288 | argv.$0 = self.$0;
|
289 |
|
290 | if (demanded._ && argv._.length < demanded._) {
|
291 | fail('Not enough non-option arguments: got '
|
292 | + argv._.length + ', need at least ' + demanded._
|
293 | );
|
294 | }
|
295 |
|
296 | var missing = [];
|
297 | Object.keys(demanded).forEach(function (key) {
|
298 | if (!argv[key]) missing.push(key);
|
299 | });
|
300 |
|
301 | if (missing.length) {
|
302 | fail('Missing required arguments: ' + missing.join(', '));
|
303 | }
|
304 |
|
305 | checks.forEach(function (f) {
|
306 | try {
|
307 | if (f(argv) === false) {
|
308 | fail('Argument check failed: ' + f.toString());
|
309 | }
|
310 | }
|
311 | catch (err) {
|
312 | fail(err)
|
313 | }
|
314 | });
|
315 |
|
316 | return argv;
|
317 | }
|
318 |
|
319 | function longest (xs) {
|
320 | return Math.max.apply(
|
321 | null,
|
322 | xs.map(function (x) { return x.length })
|
323 | );
|
324 | }
|
325 |
|
326 | return self;
|
327 | };
|
328 |
|
329 |
|
330 |
|
331 | exports.rebase = rebase;
|
332 | function rebase (base, dir) {
|
333 | var ds = path.normalize(dir).split('/').slice(1);
|
334 | var bs = path.normalize(base).split('/').slice(1);
|
335 |
|
336 | for (var i = 0; ds[i] && ds[i] == bs[i]; i++);
|
337 | ds.splice(0, i); bs.splice(0, i);
|
338 |
|
339 | var p = path.normalize(
|
340 | bs.map(function () { return '..' }).concat(ds).join('/')
|
341 | ).replace(/\/$/,'').replace(/^$/, '.');
|
342 | return p.match(/^[.\/]/) ? p : './' + p;
|
343 | };
|