UNPKG

6.2 kBJavaScriptView Raw
1'use strict';
2
3function hasKey(obj, keys) {
4 var o = obj;
5 keys.slice(0, -1).forEach(function (key) {
6 o = o[key] || {};
7 });
8
9 var key = keys[keys.length - 1];
10 return key in o;
11}
12
13function isNumber(x) {
14 if (typeof x === 'number') { return true; }
15 if ((/^0x[0-9a-f]+$/i).test(x)) { return true; }
16 return (/^[-+]?(?:\d+(?:\.\d*)?|\.\d+)(e[-+]?\d+)?$/).test(x);
17}
18
19function isConstructorOrProto(obj, key) {
20 return (key === 'constructor' && typeof obj[key] === 'function') || key === '__proto__';
21}
22
23module.exports = function (args, opts) {
24 if (!opts) { opts = {}; }
25
26 var flags = {
27 bools: {},
28 strings: {},
29 unknownFn: null,
30 };
31
32 if (typeof opts.unknown === 'function') {
33 flags.unknownFn = opts.unknown;
34 }
35
36 if (typeof opts.boolean === 'boolean' && opts.boolean) {
37 flags.allBools = true;
38 } else {
39 [].concat(opts.boolean).filter(Boolean).forEach(function (key) {
40 flags.bools[key] = true;
41 });
42 }
43
44 var aliases = {};
45
46 function aliasIsBoolean(key) {
47 return aliases[key].some(function (x) {
48 return flags.bools[x];
49 });
50 }
51
52 Object.keys(opts.alias || {}).forEach(function (key) {
53 aliases[key] = [].concat(opts.alias[key]);
54 aliases[key].forEach(function (x) {
55 aliases[x] = [key].concat(aliases[key].filter(function (y) {
56 return x !== y;
57 }));
58 });
59 });
60
61 [].concat(opts.string).filter(Boolean).forEach(function (key) {
62 flags.strings[key] = true;
63 if (aliases[key]) {
64 [].concat(aliases[key]).forEach(function (k) {
65 flags.strings[k] = true;
66 });
67 }
68 });
69
70 var defaults = opts.default || {};
71
72 var argv = { _: [] };
73
74 function argDefined(key, arg) {
75 return (flags.allBools && (/^--[^=]+$/).test(arg))
76 || flags.strings[key]
77 || flags.bools[key]
78 || aliases[key];
79 }
80
81 function setKey(obj, keys, value) {
82 var o = obj;
83 for (var i = 0; i < keys.length - 1; i++) {
84 var key = keys[i];
85 if (isConstructorOrProto(o, key)) { return; }
86 if (o[key] === undefined) { o[key] = {}; }
87 if (
88 o[key] === Object.prototype
89 || o[key] === Number.prototype
90 || o[key] === String.prototype
91 ) {
92 o[key] = {};
93 }
94 if (o[key] === Array.prototype) { o[key] = []; }
95 o = o[key];
96 }
97
98 var lastKey = keys[keys.length - 1];
99 if (isConstructorOrProto(o, lastKey)) { return; }
100 if (
101 o === Object.prototype
102 || o === Number.prototype
103 || o === String.prototype
104 ) {
105 o = {};
106 }
107 if (o === Array.prototype) { o = []; }
108 if (o[lastKey] === undefined || flags.bools[lastKey] || typeof o[lastKey] === 'boolean') {
109 o[lastKey] = value;
110 } else if (Array.isArray(o[lastKey])) {
111 o[lastKey].push(value);
112 } else {
113 o[lastKey] = [o[lastKey], value];
114 }
115 }
116
117 function setArg(key, val, arg) {
118 if (arg && flags.unknownFn && !argDefined(key, arg)) {
119 if (flags.unknownFn(arg) === false) { return; }
120 }
121
122 var value = !flags.strings[key] && isNumber(val)
123 ? Number(val)
124 : val;
125 setKey(argv, key.split('.'), value);
126
127 (aliases[key] || []).forEach(function (x) {
128 setKey(argv, x.split('.'), value);
129 });
130 }
131
132 Object.keys(flags.bools).forEach(function (key) {
133 setArg(key, defaults[key] === undefined ? false : defaults[key]);
134 });
135
136 var notFlags = [];
137
138 if (args.indexOf('--') !== -1) {
139 notFlags = args.slice(args.indexOf('--') + 1);
140 args = args.slice(0, args.indexOf('--'));
141 }
142
143 for (var i = 0; i < args.length; i++) {
144 var arg = args[i];
145 var key;
146 var next;
147
148 if ((/^--.+=/).test(arg)) {
149 // Using [\s\S] instead of . because js doesn't support the
150 // 'dotall' regex modifier. See:
151 // http://stackoverflow.com/a/1068308/13216
152 var m = arg.match(/^--([^=]+)=([\s\S]*)$/);
153 key = m[1];
154 var value = m[2];
155 if (flags.bools[key]) {
156 value = value !== 'false';
157 }
158 setArg(key, value, arg);
159 } else if ((/^--no-.+/).test(arg)) {
160 key = arg.match(/^--no-(.+)/)[1];
161 setArg(key, false, arg);
162 } else if ((/^--.+/).test(arg)) {
163 key = arg.match(/^--(.+)/)[1];
164 next = args[i + 1];
165 if (
166 next !== undefined
167 && !(/^(-|--)[^-]/).test(next)
168 && !flags.bools[key]
169 && !flags.allBools
170 && (aliases[key] ? !aliasIsBoolean(key) : true)
171 ) {
172 setArg(key, next, arg);
173 i += 1;
174 } else if ((/^(true|false)$/).test(next)) {
175 setArg(key, next === 'true', arg);
176 i += 1;
177 } else {
178 setArg(key, flags.strings[key] ? '' : true, arg);
179 }
180 } else if ((/^-[^-]+/).test(arg)) {
181 var letters = arg.slice(1, -1).split('');
182
183 var broken = false;
184 for (var j = 0; j < letters.length; j++) {
185 next = arg.slice(j + 2);
186
187 if (next === '-') {
188 setArg(letters[j], next, arg);
189 continue;
190 }
191
192 if ((/[A-Za-z]/).test(letters[j]) && next[0] === '=') {
193 setArg(letters[j], next.slice(1), arg);
194 broken = true;
195 break;
196 }
197
198 if (
199 (/[A-Za-z]/).test(letters[j])
200 && (/-?\d+(\.\d*)?(e-?\d+)?$/).test(next)
201 ) {
202 setArg(letters[j], next, arg);
203 broken = true;
204 break;
205 }
206
207 if (letters[j + 1] && letters[j + 1].match(/\W/)) {
208 setArg(letters[j], arg.slice(j + 2), arg);
209 broken = true;
210 break;
211 } else {
212 setArg(letters[j], flags.strings[letters[j]] ? '' : true, arg);
213 }
214 }
215
216 key = arg.slice(-1)[0];
217 if (!broken && key !== '-') {
218 if (
219 args[i + 1]
220 && !(/^(-|--)[^-]/).test(args[i + 1])
221 && !flags.bools[key]
222 && (aliases[key] ? !aliasIsBoolean(key) : true)
223 ) {
224 setArg(key, args[i + 1], arg);
225 i += 1;
226 } else if (args[i + 1] && (/^(true|false)$/).test(args[i + 1])) {
227 setArg(key, args[i + 1] === 'true', arg);
228 i += 1;
229 } else {
230 setArg(key, flags.strings[key] ? '' : true, arg);
231 }
232 }
233 } else {
234 if (!flags.unknownFn || flags.unknownFn(arg) !== false) {
235 argv._.push(flags.strings._ || !isNumber(arg) ? arg : Number(arg));
236 }
237 if (opts.stopEarly) {
238 argv._.push.apply(argv._, args.slice(i + 1));
239 break;
240 }
241 }
242 }
243
244 Object.keys(defaults).forEach(function (k) {
245 if (!hasKey(argv, k.split('.'))) {
246 setKey(argv, k.split('.'), defaults[k]);
247
248 (aliases[k] || []).forEach(function (x) {
249 setKey(argv, x.split('.'), defaults[k]);
250 });
251 }
252 });
253
254 if (opts['--']) {
255 argv['--'] = notFlags.slice();
256 } else {
257 notFlags.forEach(function (k) {
258 argv._.push(k);
259 });
260 }
261
262 return argv;
263};