1 | var abbrev = require('abbrev')
|
2 | const debug = require('./debug')
|
3 | const defaultTypeDefs = require('./type-defs')
|
4 |
|
5 | function nopt (args, { types, shorthands, typeDefs, invalidHandler }) {
|
6 | debug(types, shorthands, args, typeDefs)
|
7 |
|
8 | var data = {}
|
9 | var argv = {
|
10 | remain: [],
|
11 | cooked: args,
|
12 | original: args.slice(0),
|
13 | }
|
14 |
|
15 | parse(args, data, argv.remain, { typeDefs, types, shorthands })
|
16 |
|
17 |
|
18 | clean(data, { types, typeDefs, invalidHandler })
|
19 | data.argv = argv
|
20 |
|
21 | Object.defineProperty(data.argv, 'toString', {
|
22 | value: function () {
|
23 | return this.original.map(JSON.stringify).join(' ')
|
24 | },
|
25 | enumerable: false,
|
26 | })
|
27 |
|
28 | return data
|
29 | }
|
30 |
|
31 | function clean (data, { types, typeDefs, invalidHandler }) {
|
32 | const StringType = typeDefs.String.type
|
33 | const NumberType = typeDefs.Number.type
|
34 | const ArrayType = typeDefs.Array.type
|
35 | const BooleanType = typeDefs.Boolean.type
|
36 | const DateType = typeDefs.Date.type
|
37 |
|
38 | var remove = {}
|
39 | var typeDefault = [false, true, null, StringType, ArrayType]
|
40 |
|
41 | Object.keys(data).forEach(function (k) {
|
42 | if (k === 'argv') {
|
43 | return
|
44 | }
|
45 | var val = data[k]
|
46 | var isArray = Array.isArray(val)
|
47 | var type = types[k]
|
48 | if (!isArray) {
|
49 | val = [val]
|
50 | }
|
51 | if (!type) {
|
52 | type = typeDefault
|
53 | }
|
54 | if (type === ArrayType) {
|
55 | type = typeDefault.concat(ArrayType)
|
56 | }
|
57 | if (!Array.isArray(type)) {
|
58 | type = [type]
|
59 | }
|
60 |
|
61 | debug('val=%j', val)
|
62 | debug('types=', type)
|
63 | val = val.map(function (v) {
|
64 |
|
65 | if (typeof v === 'string') {
|
66 | debug('string %j', v)
|
67 | v = v.trim()
|
68 | if ((v === 'null' && ~type.indexOf(null))
|
69 | || (v === 'true' &&
|
70 | (~type.indexOf(true) || ~type.indexOf(BooleanType)))
|
71 | || (v === 'false' &&
|
72 | (~type.indexOf(false) || ~type.indexOf(BooleanType)))) {
|
73 | v = JSON.parse(v)
|
74 | debug('jsonable %j', v)
|
75 | } else if (~type.indexOf(NumberType) && !isNaN(v)) {
|
76 | debug('convert to number', v)
|
77 | v = +v
|
78 | } else if (~type.indexOf(DateType) && !isNaN(Date.parse(v))) {
|
79 | debug('convert to date', v)
|
80 | v = new Date(v)
|
81 | }
|
82 | }
|
83 |
|
84 | if (!Object.prototype.hasOwnProperty.call(types, k)) {
|
85 | return v
|
86 | }
|
87 |
|
88 |
|
89 | if (v === false && ~type.indexOf(null) &&
|
90 | !(~type.indexOf(false) || ~type.indexOf(BooleanType))) {
|
91 | v = null
|
92 | }
|
93 |
|
94 | var d = {}
|
95 | d[k] = v
|
96 | debug('prevalidated val', d, v, types[k])
|
97 | if (!validate(d, k, v, types[k], { typeDefs })) {
|
98 | if (invalidHandler) {
|
99 | invalidHandler(k, v, types[k], data)
|
100 | } else if (invalidHandler !== false) {
|
101 | debug('invalid: ' + k + '=' + v, types[k])
|
102 | }
|
103 | return remove
|
104 | }
|
105 | debug('validated v', d, v, types[k])
|
106 | return d[k]
|
107 | }).filter(function (v) {
|
108 | return v !== remove
|
109 | })
|
110 |
|
111 |
|
112 |
|
113 | if (!val.length && type.indexOf(ArrayType) === -1) {
|
114 | debug('VAL HAS NO LENGTH, DELETE IT', val, k, type.indexOf(ArrayType))
|
115 | delete data[k]
|
116 | } else if (isArray) {
|
117 | debug(isArray, data[k], val)
|
118 | data[k] = val
|
119 | } else {
|
120 | data[k] = val[0]
|
121 | }
|
122 |
|
123 | debug('k=%s val=%j', k, val, data[k])
|
124 | })
|
125 | }
|
126 |
|
127 | function validate (data, k, val, type, { typeDefs }) {
|
128 | const ArrayType = typeDefs.Array.type
|
129 |
|
130 | if (Array.isArray(type)) {
|
131 | for (let i = 0, l = type.length; i < l; i++) {
|
132 | if (type[i] === ArrayType) {
|
133 | continue
|
134 | }
|
135 | if (validate(data, k, val, type[i], { typeDefs })) {
|
136 | return true
|
137 | }
|
138 | }
|
139 | delete data[k]
|
140 | return false
|
141 | }
|
142 |
|
143 |
|
144 | if (type === ArrayType) {
|
145 | return true
|
146 | }
|
147 |
|
148 |
|
149 |
|
150 |
|
151 |
|
152 |
|
153 |
|
154 |
|
155 | if (type !== type) {
|
156 | debug('Poison NaN', k, val, type)
|
157 | delete data[k]
|
158 | return false
|
159 | }
|
160 |
|
161 |
|
162 | if (val === type) {
|
163 | debug('Explicitly allowed %j', val)
|
164 | data[k] = val
|
165 | return true
|
166 | }
|
167 |
|
168 |
|
169 | var ok = false
|
170 | var types = Object.keys(typeDefs)
|
171 | for (let i = 0, l = types.length; i < l; i++) {
|
172 | debug('test type %j %j %j', k, val, types[i])
|
173 | var t = typeDefs[types[i]]
|
174 | if (t && (
|
175 | (type && type.name && t.type && t.type.name) ?
|
176 | (type.name === t.type.name) :
|
177 | (type === t.type)
|
178 | )) {
|
179 | var d = {}
|
180 | ok = t.validate(d, k, val) !== false
|
181 | val = d[k]
|
182 | if (ok) {
|
183 | data[k] = val
|
184 | break
|
185 | }
|
186 | }
|
187 | }
|
188 | debug('OK? %j (%j %j %j)', ok, k, val, types[types.length - 1])
|
189 |
|
190 | if (!ok) {
|
191 | delete data[k]
|
192 | }
|
193 | return ok
|
194 | }
|
195 |
|
196 | function parse (args, data, remain, { typeDefs, types, shorthands }) {
|
197 | const StringType = typeDefs.String.type
|
198 | const NumberType = typeDefs.String.type
|
199 | const ArrayType = typeDefs.Array.type
|
200 | const BooleanType = typeDefs.Boolean.type
|
201 |
|
202 | debug('parse', args, data, remain)
|
203 |
|
204 | var abbrevs = abbrev(Object.keys(types))
|
205 | var shortAbbr = abbrev(Object.keys(shorthands))
|
206 |
|
207 | for (var i = 0; i < args.length; i++) {
|
208 | var arg = args[i]
|
209 | debug('arg', arg)
|
210 |
|
211 | if (arg.match(/^-{2,}$/)) {
|
212 |
|
213 |
|
214 | remain.push.apply(remain, args.slice(i + 1))
|
215 | args[i] = '--'
|
216 | break
|
217 | }
|
218 | var hadEq = false
|
219 | if (arg.charAt(0) === '-' && arg.length > 1) {
|
220 | var at = arg.indexOf('=')
|
221 | if (at > -1) {
|
222 | hadEq = true
|
223 | var v = arg.slice(at + 1)
|
224 | arg = arg.slice(0, at)
|
225 | args.splice(i, 1, arg, v)
|
226 | }
|
227 |
|
228 |
|
229 |
|
230 | var shRes = resolveShort(arg, shortAbbr, abbrevs, { shorthands })
|
231 | debug('arg=%j shRes=%j', arg, shRes)
|
232 | if (shRes) {
|
233 | debug(arg, shRes)
|
234 | args.splice.apply(args, [i, 1].concat(shRes))
|
235 | if (arg !== shRes[0]) {
|
236 | i--
|
237 | continue
|
238 | }
|
239 | }
|
240 | arg = arg.replace(/^-+/, '')
|
241 | var no = null
|
242 | while (arg.toLowerCase().indexOf('no-') === 0) {
|
243 | no = !no
|
244 | arg = arg.slice(3)
|
245 | }
|
246 |
|
247 | if (abbrevs[arg]) {
|
248 | arg = abbrevs[arg]
|
249 | }
|
250 |
|
251 | var argType = types[arg]
|
252 | var isTypeArray = Array.isArray(argType)
|
253 | if (isTypeArray && argType.length === 1) {
|
254 | isTypeArray = false
|
255 | argType = argType[0]
|
256 | }
|
257 |
|
258 | var isArray = argType === ArrayType ||
|
259 | isTypeArray && argType.indexOf(ArrayType) !== -1
|
260 |
|
261 |
|
262 | if (
|
263 | !Object.prototype.hasOwnProperty.call(types, arg) &&
|
264 | Object.prototype.hasOwnProperty.call(data, arg)
|
265 | ) {
|
266 | if (!Array.isArray(data[arg])) {
|
267 | data[arg] = [data[arg]]
|
268 | }
|
269 | isArray = true
|
270 | }
|
271 |
|
272 | var val
|
273 | var la = args[i + 1]
|
274 |
|
275 | var isBool = typeof no === 'boolean' ||
|
276 | argType === BooleanType ||
|
277 | isTypeArray && argType.indexOf(BooleanType) !== -1 ||
|
278 | (typeof argType === 'undefined' && !hadEq) ||
|
279 | (la === 'false' &&
|
280 | (argType === null ||
|
281 | isTypeArray && ~argType.indexOf(null)))
|
282 |
|
283 | if (isBool) {
|
284 |
|
285 | val = !no
|
286 |
|
287 | if (la === 'true' || la === 'false') {
|
288 | val = JSON.parse(la)
|
289 | la = null
|
290 | if (no) {
|
291 | val = !val
|
292 | }
|
293 | i++
|
294 | }
|
295 |
|
296 |
|
297 | if (isTypeArray && la) {
|
298 | if (~argType.indexOf(la)) {
|
299 |
|
300 | val = la
|
301 | i++
|
302 | } else if (la === 'null' && ~argType.indexOf(null)) {
|
303 |
|
304 | val = null
|
305 | i++
|
306 | } else if (!la.match(/^-{2,}[^-]/) &&
|
307 | !isNaN(la) &&
|
308 | ~argType.indexOf(NumberType)) {
|
309 |
|
310 | val = +la
|
311 | i++
|
312 | } else if (!la.match(/^-[^-]/) && ~argType.indexOf(StringType)) {
|
313 |
|
314 | val = la
|
315 | i++
|
316 | }
|
317 | }
|
318 |
|
319 | if (isArray) {
|
320 | (data[arg] = data[arg] || []).push(val)
|
321 | } else {
|
322 | data[arg] = val
|
323 | }
|
324 |
|
325 | continue
|
326 | }
|
327 |
|
328 | if (argType === StringType) {
|
329 | if (la === undefined) {
|
330 | la = ''
|
331 | } else if (la.match(/^-{1,2}[^-]+/)) {
|
332 | la = ''
|
333 | i--
|
334 | }
|
335 | }
|
336 |
|
337 | if (la && la.match(/^-{2,}$/)) {
|
338 | la = undefined
|
339 | i--
|
340 | }
|
341 |
|
342 | val = la === undefined ? true : la
|
343 | if (isArray) {
|
344 | (data[arg] = data[arg] || []).push(val)
|
345 | } else {
|
346 | data[arg] = val
|
347 | }
|
348 |
|
349 | i++
|
350 | continue
|
351 | }
|
352 | remain.push(arg)
|
353 | }
|
354 | }
|
355 |
|
356 | function resolveShort (arg, shortAbbr, abbrevs, { shorthands }) {
|
357 |
|
358 |
|
359 |
|
360 |
|
361 | arg = arg.replace(/^-+/, '')
|
362 |
|
363 |
|
364 | if (abbrevs[arg] === arg) {
|
365 | return null
|
366 | }
|
367 |
|
368 |
|
369 | if (shorthands[arg]) {
|
370 |
|
371 | if (shorthands[arg] && !Array.isArray(shorthands[arg])) {
|
372 | shorthands[arg] = shorthands[arg].split(/\s+/)
|
373 | }
|
374 |
|
375 | return shorthands[arg]
|
376 | }
|
377 |
|
378 |
|
379 | var singles = shorthands.___singles
|
380 | if (!singles) {
|
381 | singles = Object.keys(shorthands).filter(function (s) {
|
382 | return s.length === 1
|
383 | }).reduce(function (l, r) {
|
384 | l[r] = true
|
385 | return l
|
386 | }, {})
|
387 | shorthands.___singles = singles
|
388 | debug('shorthand singles', singles)
|
389 | }
|
390 |
|
391 | var chrs = arg.split('').filter(function (c) {
|
392 | return singles[c]
|
393 | })
|
394 |
|
395 | if (chrs.join('') === arg) {
|
396 | return chrs.map(function (c) {
|
397 | return shorthands[c]
|
398 | }).reduce(function (l, r) {
|
399 | return l.concat(r)
|
400 | }, [])
|
401 | }
|
402 |
|
403 |
|
404 | if (abbrevs[arg] && !shorthands[arg]) {
|
405 | return null
|
406 | }
|
407 |
|
408 |
|
409 | if (shortAbbr[arg]) {
|
410 | arg = shortAbbr[arg]
|
411 | }
|
412 |
|
413 |
|
414 | if (shorthands[arg] && !Array.isArray(shorthands[arg])) {
|
415 | shorthands[arg] = shorthands[arg].split(/\s+/)
|
416 | }
|
417 |
|
418 | return shorthands[arg]
|
419 | }
|
420 |
|
421 | module.exports = {
|
422 | nopt,
|
423 | clean,
|
424 | parse,
|
425 | validate,
|
426 | resolveShort,
|
427 | typeDefs: defaultTypeDefs,
|
428 | }
|