2.0_beta sync to rsa
[framework/web/web-ui-fw.git] / build-tools / lib / optimist / index.js
1 var path = require('path');
2 var wordwrap = require('wordwrap');
3
4 /*  Hack an instance of Argv with process.argv into Argv
5     so people can do
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.
10 */
11
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)
16         : inst[key];
17 });
18
19 var exports = module.exports = Argv;
20 function Argv (args, cwd) {
21     var self = {};
22     if (!cwd) cwd = process.cwd();
23     
24     self.$0 = process.argv
25         .slice(0,2)
26         .map(function (x) {
27             var b = rebase(cwd, x);
28             return x.match(/^\//) && b.length < x.length
29                 ? b : x
30         })
31         .join(' ')
32     ;
33     
34     if (process.argv[1] == process.env._) {
35         self.$0 = process.env._.replace(
36             path.dirname(process.execPath) + '/', ''
37         );
38     }
39     
40     var flags = { bools : {}, strings : {} };
41     
42     self.boolean = function (bools) {
43         if (!Array.isArray(bools)) {
44             bools = [].slice.call(arguments);
45         }
46         
47         bools.forEach(function (name) {
48             flags.bools[name] = true;
49         });
50         
51         return self;
52     };
53     
54     self.string = function (strings) {
55         if (!Array.isArray(strings)) {
56             strings = [].slice.call(arguments);
57         }
58         
59         strings.forEach(function (name) {
60             flags.strings[name] = true;
61         });
62         
63         return self;
64     };
65     
66     var aliases = {};
67     self.alias = function (x, y) {
68         if (typeof x === 'object') {
69             Object.keys(x).forEach(function (key) {
70                 self.alias(key, x[key]);
71             });
72         }
73         else if (Array.isArray(y)) {
74             y.forEach(function (yy) {
75                 self.alias(x, yy);
76             });
77         }
78         else {
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 });
82         }
83         
84         return self;
85     };
86     
87     var demanded = {};
88     self.demand = function (keys) {
89         if (typeof keys == 'number') {
90             if (!demanded._) demanded._ = 0;
91             demanded._ += keys;
92         }
93         else if (Array.isArray(keys)) {
94             keys.forEach(function (key) {
95                 self.demand(key);
96             });
97         }
98         else {
99             demanded[keys] = true;
100         }
101         
102         return self;
103     };
104     
105     var usage;
106     self.usage = function (msg, opts) {
107         if (!opts && typeof msg === 'object') {
108             opts = msg;
109             msg = null;
110         }
111         
112         usage = msg;
113         
114         if (opts) self.options(opts);
115         
116         return self;
117     };
118     
119     function fail (msg) {
120         self.showHelp();
121         if (msg) console.error(msg);
122         process.exit(1);
123     }
124     
125     var checks = [];
126     self.check = function (f) {
127         checks.push(f);
128         return self;
129     };
130     
131     var defaults = {};
132     self.default = function (key, value) {
133         if (typeof key === 'object') {
134             Object.keys(key).forEach(function (k) {
135                 self.default(k, key[k]);
136             });
137         }
138         else {
139             defaults[key] = value;
140         }
141         
142         return self;
143     };
144     
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]);
150             });
151         }
152         else {
153             descriptions[key] = desc;
154         }
155         return self;
156     };
157     
158     self.parse = function (args) {
159         return Argv(args).argv;
160     };
161     
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]);
166             });
167         }
168         else {
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);
173             }
174             
175             if (opt.boolean || opt.type === 'boolean') {
176                 self.boolean(key);
177             }
178             if (opt.string || opt.type === 'string') {
179                 self.string(key);
180             }
181             
182             var desc = opt.describe || opt.description || opt.desc;
183             if (desc) {
184                 self.describe(key, desc);
185             }
186         }
187         
188         return self;
189     };
190     
191     var wrap = null;
192     self.wrap = function (cols) {
193         wrap = cols;
194         return self;
195     };
196     
197     self.showHelp = function (fn) {
198         if (!fn) fn = console.error;
199         fn(self.help());
200     };
201     
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;
209                 return acc;
210             }, {})
211         );
212         
213         var help = keys.length ? [ 'Options:' ] : [];
214         
215         if (usage) {
216             help.unshift(usage.replace(/\$0/g, self.$0), '');
217         }
218         
219         var switches = keys.reduce(function (acc, key) {
220             acc[key] = [ key ].concat(aliases[key] || [])
221                 .map(function (sw) {
222                     return (sw.length > 1 ? '--' : '-') + sw
223                 })
224                 .join(', ')
225             ;
226             return acc;
227         }, {});
228         
229         var switchlen = longest(Object.keys(switches).map(function (s) {
230             return switches[s] || '';
231         }));
232         
233         var desclen = longest(Object.keys(descriptions).map(function (d) { 
234             return descriptions[d] || '';
235         }));
236         
237         keys.forEach(function (key) {
238             var kswitch = switches[key];
239             var desc = descriptions[key] || '';
240             
241             if (wrap) {
242                 desc = wordwrap(switchlen + 4, wrap)(desc)
243                     .slice(switchlen + 4)
244                 ;
245             }
246             
247             var spadding = new Array(
248                 Math.max(switchlen - kswitch.length + 3, 0)
249             ).join(' ');
250             
251             var dpadding = new Array(
252                 Math.max(desclen - desc.length + 1, 0)
253             ).join(' ');
254             
255             var type = null;
256             
257             if (flags.bools[key]) type = '[boolean]';
258             if (flags.strings[key]) type = '[string]';
259             
260             if (!wrap && dpadding.length > 0) {
261                 desc += dpadding;
262             }
263             
264             var prelude = '  ' + kswitch + spadding;
265             var extra = [
266                 type,
267                 demanded[key]
268                     ? '[required]'
269                     : null
270                 ,
271                 defaults[key] !== undefined
272                     ? '[default: ' + JSON.stringify(defaults[key]) + ']'
273                     : null
274                 ,
275             ].filter(Boolean).join('  ');
276             
277             var body = [ desc, extra ].filter(Boolean).join('  ');
278             
279             if (wrap) {
280                 var dlines = desc.split('\n');
281                 var dlen = dlines.slice(-1)[0].length
282                     + (dlines.length === 1 ? prelude.length : 0)
283                 
284                 body = desc + (dlen + extra.length > wrap - 2
285                     ? '\n'
286                         + new Array(wrap - extra.length + 1).join(' ')
287                         + extra
288                     : new Array(wrap - extra.length - dlen + 1).join(' ')
289                         + extra
290                 );
291             }
292             
293             help.push(prelude + body);
294         });
295         
296         help.push('');
297         return help.join('\n');
298     };
299     
300     Object.defineProperty(self, 'argv', {
301         get : parseArgs,
302         enumerable : true,
303     });
304     
305     function parseArgs () {
306         var argv = { _ : [], $0 : self.$0 };
307         Object.keys(flags.bools).forEach(function (key) {
308             setArg(key, defaults[key] || false);
309         });
310         
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;
315             
316             setKey(argv, key.split('.'), value);
317             
318             (aliases[key] || []).forEach(function (x) {
319                 argv[x] = argv[key];
320             });
321         }
322         
323         for (var i = 0; i < args.length; i++) {
324             var arg = args[i];
325             
326             if (arg === '--') {
327                 argv._.push.apply(argv._, args.slice(i + 1));
328                 break;
329             }
330             else if (arg.match(/^--.+=/)) {
331                 var m = arg.match(/^--([^=]+)=(.*)/);
332                 setArg(m[1], m[2]);
333             }
334             else if (arg.match(/^--no-.+/)) {
335                 var key = arg.match(/^--no-(.+)/)[1];
336                 setArg(key, false);
337             }
338             else if (arg.match(/^--.+/)) {
339                 var key = arg.match(/^--(.+)/)[1];
340                 var next = args[i + 1];
341                 if (next !== undefined && !next.match(/^-/)
342                 && !flags.bools[key]
343                 && (aliases[key] ? !flags.bools[aliases[key]] : true)) {
344                     setArg(key, next);
345                     i++;
346                 }
347                 else if (/true|false/.test(next)) {
348                     setArg(key, next === 'true');
349                     i++;
350                 }
351                 else {
352                     setArg(key, true);
353                 }
354             }
355             else if (arg.match(/^-[^-]+/)) {
356                 var letters = arg.slice(1,-1).split('');
357                 
358                 var broken = false;
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));
362                         broken = true;
363                         break;
364                     }
365                     else {
366                         setArg(letters[j], true);
367                     }
368                 }
369                 
370                 if (!broken) {
371                     var key = arg.slice(-1)[0];
372                     
373                     if (args[i+1] && !args[i+1].match(/^-/)
374                     && !flags.bools[key]
375                     && (aliases[key] ? !flags.bools[aliases[key]] : true)) {
376                         setArg(key, args[i+1]);
377                         i++;
378                     }
379                     else if (args[i+1] && /true|false/.test(args[i+1])) {
380                         setArg(key, args[i+1] === 'true');
381                         i++;
382                     }
383                     else {
384                         setArg(key, true);
385                     }
386                 }
387             }
388             else {
389                 var n = Number(arg);
390                 argv._.push(flags.strings['_'] || isNaN(n) ? arg : n);
391             }
392         }
393         
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];
399                 }
400             }
401         });
402         
403         if (demanded._ && argv._.length < demanded._) {
404             fail('Not enough non-option arguments: got '
405                 + argv._.length + ', need at least ' + demanded._
406             );
407         }
408         
409         var missing = [];
410         Object.keys(demanded).forEach(function (key) {
411             if (!argv[key]) missing.push(key);
412         });
413         
414         if (missing.length) {
415             fail('Missing required arguments: ' + missing.join(', '));
416         }
417         
418         checks.forEach(function (f) {
419             try {
420                 if (f(argv) === false) {
421                     fail('Argument check failed: ' + f.toString());
422                 }
423             }
424             catch (err) {
425                 fail(err)
426             }
427         });
428         
429         return argv;
430     }
431     
432     function longest (xs) {
433         return Math.max.apply(
434             null,
435             xs.map(function (x) { return x.length })
436         );
437     }
438     
439     return self;
440 };
441
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);
448     
449     for (var i = 0; ds[i] && ds[i] == bs[i]; i++);
450     ds.splice(0, i); bs.splice(0, i);
451     
452     var p = path.normalize(
453         bs.map(function () { return '..' }).concat(ds).join('/')
454     ).replace(/\/$/,'').replace(/^$/, '.');
455     return p.match(/^[.\/]/) ? p : './' + p;
456 };
457
458 function setKey (obj, keys, value) {
459     var o = obj;
460     keys.slice(0,-1).forEach(function (key) {
461         if (o[key] === undefined) o[key] = {};
462         o = o[key];
463     });
464     
465     var key = keys[keys.length - 1];
466     if (o[key] === undefined || typeof o[key] === 'boolean') {
467         o[key] = value;
468     }
469     else if (Array.isArray(o[key])) {
470         o[key].push(value);
471     }
472     else {
473         o[key] = [ o[key], value ];
474     }
475 }