1
// info about each config option.
3
var debug = process.env.DEBUG_NOPT || process.env.NOPT_DEBUG
4
? function () { console.error.apply(console, arguments) }
7
var url = require("url")
8
, path = require("path")
9
, Stream = require("stream").Stream
10
, abbrev = require("abbrev")
12
module.exports = exports = nopt
16
{ String : { type: String, validate: validateString }
17
, Boolean : { type: Boolean, validate: validateBoolean }
18
, url : { type: url, validate: validateUrl }
19
, Number : { type: Number, validate: validateNumber }
20
, path : { type: path, validate: validatePath }
21
, Stream : { type: Stream, validate: validateStream }
22
, Date : { type: Date, validate: validateDate }
25
function nopt (types, shorthands, args, slice) {
26
args = args || process.argv
28
shorthands = shorthands || {}
29
if (typeof slice !== "number") slice = 2
31
debug(types, shorthands, args, slice)
33
args = args.slice(slice)
38
, original = args.slice(0)
40
parse(args, data, remain, types, shorthands)
42
clean(data, types, exports.typeDefs)
43
data.argv = {remain:remain,cooked:cooked,original:original}
44
Object.defineProperty(data.argv, 'toString', { value: function () {
45
return this.original.map(JSON.stringify).join(" ")
46
}, enumerable: false })
50
function clean (data, types, typeDefs) {
51
typeDefs = typeDefs || exports.typeDefs
53
, typeDefault = [false, true, null, String, Number, Array]
55
Object.keys(data).forEach(function (k) {
56
if (k === "argv") return
58
, isArray = Array.isArray(val)
60
if (!isArray) val = [val]
61
if (!type) type = typeDefault
62
if (type === Array) type = typeDefault.concat(Array)
63
if (!Array.isArray(type)) type = [type]
67
val = val.map(function (val) {
68
// if it's an unknown value, then parse false/true/null/numbers/dates
69
if (typeof val === "string") {
70
debug("string %j", val)
72
if ((val === "null" && ~type.indexOf(null))
74
(~type.indexOf(true) || ~type.indexOf(Boolean)))
75
|| (val === "false" &&
76
(~type.indexOf(false) || ~type.indexOf(Boolean)))) {
78
debug("jsonable %j", val)
79
} else if (~type.indexOf(Number) && !isNaN(val)) {
80
debug("convert to number", val)
82
} else if (~type.indexOf(Date) && !isNaN(Date.parse(val))) {
83
debug("convert to date", val)
88
if (!types.hasOwnProperty(k)) {
92
// allow `--no-blah` to set 'blah' to null if null is allowed
93
if (val === false && ~type.indexOf(null) &&
94
!(~type.indexOf(false) || ~type.indexOf(Boolean))) {
100
debug("prevalidated val", d, val, types[k])
101
if (!validate(d, k, val, types[k], typeDefs)) {
102
if (exports.invalidHandler) {
103
exports.invalidHandler(k, val, types[k], data)
104
} else if (exports.invalidHandler !== false) {
105
debug("invalid: "+k+"="+val, types[k])
109
debug("validated val", d, val, types[k])
111
}).filter(function (val) { return val !== remove })
113
if (!val.length) delete data[k]
115
debug(isArray, data[k], val)
117
} else data[k] = val[0]
119
debug("k=%s val=%j", k, val, data[k])
123
function validateString (data, k, val) {
124
data[k] = String(val)
127
function validatePath (data, k, val) {
128
if (val === true) return false
129
data[k] = path.resolve(String(val))
133
function validateNumber (data, k, val) {
134
debug("validate Number %j %j %j", k, val, isNaN(val))
135
if (isNaN(val)) return false
139
function validateDate (data, k, val) {
140
debug("validate Date %j %j %j", k, val, Date.parse(val))
141
var s = Date.parse(val)
142
if (isNaN(s)) return false
143
data[k] = new Date(val)
146
function validateBoolean (data, k, val) {
147
if (val instanceof Boolean) val = val.valueOf()
148
else if (typeof val === "string") {
149
if (!isNaN(val)) val = !!(+val)
150
else if (val === "null" || val === "false") val = false
156
function validateUrl (data, k, val) {
157
val = url.parse(String(val))
158
if (!val.host) return false
162
function validateStream (data, k, val) {
163
if (!(val instanceof Stream)) return false
167
function validate (data, k, val, type, typeDefs) {
168
// arrays are lists of types.
169
if (Array.isArray(type)) {
170
for (var i = 0, l = type.length; i < l; i ++) {
171
if (type[i] === Array) continue
172
if (validate(data, k, val, type[i], typeDefs)) return true
178
// an array of anything?
179
if (type === Array) return true
181
// NaN is poisonous. Means that something is not allowed.
183
debug("Poison NaN", k, val, type)
188
// explicit list of values
190
debug("Explicitly allowed %j", val)
191
// if (isArray) (data[k] = data[k] || []).push(val)
192
// else data[k] = val
197
// now go through the list of typeDefs, validate against each one.
199
, types = Object.keys(typeDefs)
200
for (var i = 0, l = types.length; i < l; i ++) {
201
debug("test type %j %j %j", k, val, types[i])
202
var t = typeDefs[types[i]]
203
if (t && type === t.type) {
205
ok = false !== t.validate(d, k, val)
208
// if (isArray) (data[k] = data[k] || []).push(val)
209
// else data[k] = val
215
debug("OK? %j (%j %j %j)", ok, k, val, types[i])
217
if (!ok) delete data[k]
221
function parse (args, data, remain, types, shorthands) {
222
debug("parse", args, data, remain)
225
, abbrevs = abbrev(Object.keys(types))
226
, shortAbbr = abbrev(Object.keys(shorthands))
228
for (var i = 0; i < args.length; i ++) {
232
if (arg.match(/^-{2,}$/)) {
234
// the rest are args.
235
remain.push.apply(remain, args.slice(i + 1))
240
if (arg.charAt(0) === "-" && arg.length > 1) {
241
if (arg.indexOf("=") !== -1) {
243
var v = arg.split("=")
246
args.splice.apply(args, [i, 1].concat([arg, v]))
249
// see if it's a shorthand
250
// if so, splice and back up to re-parse it.
251
var shRes = resolveShort(arg, shorthands, shortAbbr, abbrevs)
252
debug("arg=%j shRes=%j", arg, shRes)
255
args.splice.apply(args, [i, 1].concat(shRes))
256
if (arg !== shRes[0]) {
261
arg = arg.replace(/^-+/, "")
263
while (arg.toLowerCase().indexOf("no-") === 0) {
268
if (abbrevs[arg]) arg = abbrevs[arg]
270
var isArray = types[arg] === Array ||
271
Array.isArray(types[arg]) && types[arg].indexOf(Array) !== -1
273
// allow unknown things to be arrays if specified multiple times.
274
if (!types.hasOwnProperty(arg) && data.hasOwnProperty(arg)) {
275
if (!Array.isArray(data[arg]))
276
data[arg] = [data[arg]]
283
var isBool = typeof no === 'boolean' ||
284
types[arg] === Boolean ||
285
Array.isArray(types[arg]) && types[arg].indexOf(Boolean) !== -1 ||
286
(typeof types[arg] === 'undefined' && !hadEq) ||
288
(types[arg] === null ||
289
Array.isArray(types[arg]) && ~types[arg].indexOf(null)))
292
// just set and move along
294
// however, also support --bool true or --bool false
295
if (la === "true" || la === "false") {
302
// also support "foo":[Boolean, "bar"] and "--foo bar"
303
if (Array.isArray(types[arg]) && la) {
304
if (~types[arg].indexOf(la)) {
308
} else if ( la === "null" && ~types[arg].indexOf(null) ) {
312
} else if ( !la.match(/^-{2,}[^-]/) &&
314
~types[arg].indexOf(Number) ) {
318
} else if ( !la.match(/^-[^-]/) && ~types[arg].indexOf(String) ) {
325
if (isArray) (data[arg] = data[arg] || []).push(val)
331
if (types[arg] === String && la === undefined)
334
if (la && la.match(/^-{2,}$/)) {
339
val = la === undefined ? true : la
340
if (isArray) (data[arg] = data[arg] || []).push(val)
350
function resolveShort (arg, shorthands, shortAbbr, abbrevs) {
351
// handle single-char shorthands glommed together, like
352
// npm ls -glp, but only if there is one dash, and only if
353
// all of the chars are single-char shorthands, and it's
354
// not a match to some other abbrev.
355
arg = arg.replace(/^-+/, '')
357
// if it's an exact known option, then don't go any further
358
if (abbrevs[arg] === arg)
361
// if it's an exact known shortopt, same deal
362
if (shorthands[arg]) {
363
// make it an array, if it's a list of words
364
if (shorthands[arg] && !Array.isArray(shorthands[arg]))
365
shorthands[arg] = shorthands[arg].split(/\s+/)
367
return shorthands[arg]
370
// first check to see if this arg is a set of single-char shorthands
371
var singles = shorthands.___singles
373
singles = Object.keys(shorthands).filter(function (s) {
374
return s.length === 1
375
}).reduce(function (l,r) {
379
shorthands.___singles = singles
380
debug('shorthand singles', singles)
383
var chrs = arg.split("").filter(function (c) {
387
if (chrs.join("") === arg) return chrs.map(function (c) {
389
}).reduce(function (l, r) {
394
// if it's an arg abbrev, and not a literal shorthand, then prefer the arg
395
if (abbrevs[arg] && !shorthands[arg])
398
// if it's an abbr for a shorthand, then use that
402
// make it an array, if it's a list of words
403
if (shorthands[arg] && !Array.isArray(shorthands[arg]))
404
shorthands[arg] = shorthands[arg].split(/\s+/)
406
return shorthands[arg]