Revert "Export"
[platform/framework/web/web-ui-fw.git] / build-tools / lib / jslint / nopt.js
1 // info about each config option.
2
3 var debug = process.env.DEBUG_NOPT || process.env.NOPT_DEBUG
4   ? function () { console.error.apply(console, arguments) }
5   : function () {}
6
7 var url = require("url")
8   , path = require("path")
9   , Stream = require("stream").Stream
10   , abbrev = require("./abbrev")
11
12 module.exports = exports = nopt
13 exports.clean = clean
14
15 exports.typeDefs =
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    }
23   }
24
25 function nopt (types, shorthands, args, slice) {
26   args = args || process.argv
27   types = types || {}
28   shorthands = shorthands || {}
29   if (typeof slice !== "number") slice = 2
30
31   debug(types, shorthands, args, slice)
32
33   args = args.slice(slice)
34   var data = {}
35     , key
36     , remain = []
37     , cooked = args
38     , original = args.slice(0)
39
40   parse(args, data, remain, types, shorthands)
41   // now data is full
42   clean(data, types, exports.typeDefs)
43   data.argv = {remain:remain,cooked:cooked,original:original}
44   data.argv.toString = function () {
45     return this.original.map(JSON.stringify).join(" ")
46   }
47   return data
48 }
49
50 function clean (data, types, typeDefs) {
51   typeDefs = typeDefs || exports.typeDefs
52   var remove = {}
53     , typeDefault = [false, true, null, String, Number]
54
55   Object.keys(data).forEach(function (k) {
56     if (k === "argv") return
57     var val = data[k]
58       , isArray = Array.isArray(val)
59       , type = types[k]
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]
64
65     debug("val=%j", val)
66     debug("types=", 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)
71         val = val.trim()
72         if ((val === "null" && ~type.indexOf(null))
73             || (val === "true" &&
74                (~type.indexOf(true) || ~type.indexOf(Boolean)))
75             || (val === "false" &&
76                (~type.indexOf(false) || ~type.indexOf(Boolean)))) {
77           val = JSON.parse(val)
78           debug("jsonable %j", val)
79         } else if (~type.indexOf(Number) && !isNaN(val)) {
80           debug("convert to number", val)
81           val = +val
82         } else if (~type.indexOf(Date) && !isNaN(Date.parse(val))) {
83           debug("convert to date", val)
84           val = new Date(val)
85         }
86       }
87
88       if (!types.hasOwnProperty(k)) {
89         return val
90       }
91
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))) {
95         val = null
96       }
97
98       var d = {}
99       d[k] = val
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])
106         }
107         return remove
108       }
109       debug("validated val", d, val, types[k])
110       return d[k]
111     }).filter(function (val) { return val !== remove })
112
113     if (!val.length) delete data[k]
114     else if (isArray) {
115       debug(isArray, data[k], val)
116       data[k] = val
117     } else data[k] = val[0]
118
119     debug("k=%s val=%j", k, val, data[k])
120   })
121 }
122
123 function validateString (data, k, val) {
124   data[k] = String(val)
125 }
126
127 function validatePath (data, k, val) {
128   data[k] = path.resolve(String(val))
129   return true
130 }
131
132 function validateNumber (data, k, val) {
133   debug("validate Number %j %j %j", k, val, isNaN(val))
134   if (isNaN(val)) return false
135   data[k] = +val
136 }
137
138 function validateDate (data, k, val) {
139   debug("validate Date %j %j %j", k, val, Date.parse(val))
140   var s = Date.parse(val)
141   if (isNaN(s)) return false
142   data[k] = new Date(val)
143 }
144
145 function validateBoolean (data, k, val) {
146   if (val instanceof Boolean) val = val.valueOf()
147   else if (typeof val === "string") {
148     if (!isNaN(val)) val = !!(+val)
149     else if (val === "null" || val === "false") val = false
150     else val = true
151   } else val = !!val
152   data[k] = val
153 }
154
155 function validateUrl (data, k, val) {
156   val = url.parse(String(val))
157   if (!val.host) return false
158   data[k] = val.href
159 }
160
161 function validateStream (data, k, val) {
162   if (!(val instanceof Stream)) return false
163   data[k] = val
164 }
165
166 function validate (data, k, val, type, typeDefs) {
167   // arrays are lists of types.
168   if (Array.isArray(type)) {
169     for (var i = 0, l = type.length; i < l; i ++) {
170       if (type[i] === Array) continue
171       if (validate(data, k, val, type[i], typeDefs)) return true
172     }
173     delete data[k]
174     return false
175   }
176
177   // an array of anything?
178   if (type === Array) return true
179
180   // NaN is poisonous.  Means that something is not allowed.
181   if (type !== type) {
182     debug("Poison NaN", k, val, type)
183     delete data[k]
184     return false
185   }
186
187   // explicit list of values
188   if (val === type) {
189     debug("Explicitly allowed %j", val)
190     // if (isArray) (data[k] = data[k] || []).push(val)
191     // else data[k] = val
192     data[k] = val
193     return true
194   }
195
196   // now go through the list of typeDefs, validate against each one.
197   var ok = false
198     , types = Object.keys(typeDefs)
199   for (var i = 0, l = types.length; i < l; i ++) {
200     debug("test type %j %j %j", k, val, types[i])
201     var t = typeDefs[types[i]]
202     if (t && type === t.type) {
203       var d = {}
204       ok = false !== t.validate(d, k, val)
205       val = d[k]
206       if (ok) {
207         // if (isArray) (data[k] = data[k] || []).push(val)
208         // else data[k] = val
209         data[k] = val
210         break
211       }
212     }
213   }
214   debug("OK? %j (%j %j %j)", ok, k, val, types[i])
215
216   if (!ok) delete data[k]
217   return ok
218 }
219
220 function parse (args, data, remain, types, shorthands) {
221   debug("parse", args, data, remain)
222
223   var key = null
224     , abbrevs = abbrev(Object.keys(types))
225     , shortAbbr = abbrev(Object.keys(shorthands))
226
227   for (var i = 0; i < args.length; i ++) {
228     var arg = args[i]
229     debug("arg", arg)
230
231     if (arg.match(/^-{2,}$/)) {
232       // done with keys.
233       // the rest are args.
234       remain.push.apply(remain, args.slice(i + 1))
235       args[i] = "--"
236       break
237     }
238     if (arg.charAt(0) === "-") {
239       if (arg.indexOf("=") !== -1) {
240         var v = arg.split("=")
241         arg = v.shift()
242         v = v.join("=")
243         args.splice.apply(args, [i, 1].concat([arg, v]))
244       }
245       // see if it's a shorthand
246       // if so, splice and back up to re-parse it.
247       var shRes = resolveShort(arg, shorthands, shortAbbr, abbrevs)
248       debug("arg=%j shRes=%j", arg, shRes)
249       if (shRes) {
250         debug(arg, shRes)
251         args.splice.apply(args, [i, 1].concat(shRes))
252         if (arg !== shRes[0]) {
253           i --
254           continue
255         }
256       }
257       arg = arg.replace(/^-+/, "")
258       var no = false
259       while (arg.toLowerCase().indexOf("no-") === 0) {
260         no = !no
261         arg = arg.substr(3)
262       }
263
264       if (abbrevs[arg]) arg = abbrevs[arg]
265
266       var isArray = types[arg] === Array ||
267         Array.isArray(types[arg]) && types[arg].indexOf(Array) !== -1
268
269       var val
270         , la = args[i + 1]
271
272       var isBool = no ||
273         types[arg] === Boolean ||
274         Array.isArray(types[arg]) && types[arg].indexOf(Boolean) !== -1 ||
275         (la === "false" &&
276          (types[arg] === null ||
277           Array.isArray(types[arg]) && ~types[arg].indexOf(null)))
278
279       if (isBool) {
280         // just set and move along
281         val = !no
282         // however, also support --bool true or --bool false
283         if (la === "true" || la === "false") {
284           val = JSON.parse(la)
285           la = null
286           if (no) val = !val
287           i ++
288         }
289
290         // also support "foo":[Boolean, "bar"] and "--foo bar"
291         if (Array.isArray(types[arg]) && la) {
292           if (~types[arg].indexOf(la)) {
293             // an explicit type
294             val = la
295             i ++
296           } else if ( la === "null" && ~types[arg].indexOf(null) ) {
297             // null allowed
298             val = null
299             i ++
300           } else if ( !la.match(/^-{2,}[^-]/) &&
301                       !isNaN(la) &&
302                       ~types[arg].indexOf(Number) ) {
303             // number
304             val = +la
305             i ++
306           } else if ( !la.match(/^-[^-]/) && ~types[arg].indexOf(String) ) {
307             // string
308             val = la
309             i ++
310           }
311         }
312
313         if (isArray) (data[arg] = data[arg] || []).push(val)
314         else data[arg] = val
315
316         continue
317       }
318
319       if (la && la.match(/^-{2,}$/)) {
320         la = undefined
321         i --
322       }
323
324       val = la === undefined ? true : la
325       if (isArray) (data[arg] = data[arg] || []).push(val)
326       else data[arg] = val
327
328       i ++
329       continue
330     }
331     remain.push(arg)
332   }
333 }
334
335 function resolveShort (arg, shorthands, shortAbbr, abbrevs) {
336   // handle single-char shorthands glommed together, like
337   // npm ls -glp, but only if there is one dash, and only if
338   // all of the chars are single-char shorthands, and it's
339   // not a match to some other abbrev.
340   arg = arg.replace(/^-+/, '')
341   if (abbrevs[arg] && !shorthands[arg]) {
342     return null
343   }
344   if (shortAbbr[arg]) {
345     arg = shortAbbr[arg]
346   } else {
347     var singles = shorthands.___singles
348     if (!singles) {
349       singles = Object.keys(shorthands).filter(function (s) {
350         return s.length === 1
351       }).reduce(function (l,r) { l[r] = true ; return l }, {})
352       shorthands.___singles = singles
353     }
354     var chrs = arg.split("").filter(function (c) {
355       return singles[c]
356     })
357     if (chrs.join("") === arg) return chrs.map(function (c) {
358       return shorthands[c]
359     }).reduce(function (l, r) {
360       return l.concat(r)
361     }, [])
362   }
363
364   if (shorthands[arg] && !Array.isArray(shorthands[arg])) {
365     shorthands[arg] = shorthands[arg].split(/\s+/)
366   }
367   return shorthands[arg]
368 }
369
370 if (module === require.main) {
371 var assert = require("assert")
372   , util = require("util")
373
374   , shorthands =
375     { s : ["--loglevel", "silent"]
376     , d : ["--loglevel", "info"]
377     , dd : ["--loglevel", "verbose"]
378     , ddd : ["--loglevel", "silly"]
379     , noreg : ["--no-registry"]
380     , reg : ["--registry"]
381     , "no-reg" : ["--no-registry"]
382     , silent : ["--loglevel", "silent"]
383     , verbose : ["--loglevel", "verbose"]
384     , h : ["--usage"]
385     , H : ["--usage"]
386     , "?" : ["--usage"]
387     , help : ["--usage"]
388     , v : ["--version"]
389     , f : ["--force"]
390     , desc : ["--description"]
391     , "no-desc" : ["--no-description"]
392     , "local" : ["--no-global"]
393     , l : ["--long"]
394     , p : ["--parseable"]
395     , porcelain : ["--parseable"]
396     , g : ["--global"]
397     }
398
399   , types =
400     { aoa: Array
401     , nullstream: [null, Stream]
402     , date: Date
403     , str: String
404     , browser : String
405     , cache : path
406     , color : ["always", Boolean]
407     , depth : Number
408     , description : Boolean
409     , dev : Boolean
410     , editor : path
411     , force : Boolean
412     , global : Boolean
413     , globalconfig : path
414     , group : [String, Number]
415     , gzipbin : String
416     , logfd : [Number, Stream]
417     , loglevel : ["silent","win","error","warn","info","verbose","silly"]
418     , long : Boolean
419     , "node-version" : [false, String]
420     , npaturl : url
421     , npat : Boolean
422     , "onload-script" : [false, String]
423     , outfd : [Number, Stream]
424     , parseable : Boolean
425     , pre: Boolean
426     , prefix: path
427     , proxy : url
428     , "rebuild-bundle" : Boolean
429     , registry : url
430     , searchopts : String
431     , searchexclude: [null, String]
432     , shell : path
433     , t: [Array, String]
434     , tag : String
435     , tar : String
436     , tmp : path
437     , "unsafe-perm" : Boolean
438     , usage : Boolean
439     , user : String
440     , username : String
441     , userconfig : path
442     , version : Boolean
443     , viewer: path
444     , _exit : Boolean
445     }
446
447 ; [["-v", {version:true}, []]
448   ,["---v", {version:true}, []]
449   ,["ls -s --no-reg connect -d",
450     {loglevel:"info",registry:null},["ls","connect"]]
451   ,["ls ---s foo",{loglevel:"silent"},["ls","foo"]]
452   ,["ls --registry blargle", {}, ["ls"]]
453   ,["--no-registry", {registry:null}, []]
454   ,["--no-color true", {color:false}, []]
455   ,["--no-color false", {color:true}, []]
456   ,["--no-color", {color:false}, []]
457   ,["--color false", {color:false}, []]
458   ,["--color --logfd 7", {logfd:7,color:true}, []]
459   ,["--color=true", {color:true}, []]
460   ,["--logfd=10", {logfd:10}, []]
461   ,["--tmp=/tmp -tar=gtar",{tmp:"/tmp",tar:"gtar"},[]]
462   ,["--tmp=tmp -tar=gtar",
463     {tmp:path.resolve(process.cwd(), "tmp"),tar:"gtar"},[]]
464   ,["--logfd x", {}, []]
465   ,["a -true -- -no-false", {true:true},["a","-no-false"]]
466   ,["a -no-false", {false:false},["a"]]
467   ,["a -no-no-true", {true:true}, ["a"]]
468   ,["a -no-no-no-false", {false:false}, ["a"]]
469   ,["---NO-no-No-no-no-no-nO-no-no"+
470     "-No-no-no-no-no-no-no-no-no"+
471     "-no-no-no-no-NO-NO-no-no-no-no-no-no"+
472     "-no-body-can-do-the-boogaloo-like-I-do"
473    ,{"body-can-do-the-boogaloo-like-I-do":false}, []]
474   ,["we are -no-strangers-to-love "+
475     "--you-know the-rules --and so-do-i "+
476     "---im-thinking-of=a-full-commitment "+
477     "--no-you-would-get-this-from-any-other-guy "+
478     "--no-gonna-give-you-up "+
479     "-no-gonna-let-you-down=true "+
480     "--no-no-gonna-run-around false "+
481     "--desert-you=false "+
482     "--make-you-cry false "+
483     "--no-tell-a-lie "+
484     "--no-no-and-hurt-you false"
485    ,{"strangers-to-love":false
486     ,"you-know":"the-rules"
487     ,"and":"so-do-i"
488     ,"you-would-get-this-from-any-other-guy":false
489     ,"gonna-give-you-up":false
490     ,"gonna-let-you-down":false
491     ,"gonna-run-around":false
492     ,"desert-you":false
493     ,"make-you-cry":false
494     ,"tell-a-lie":false
495     ,"and-hurt-you":false
496     },["we", "are"]]
497   ,["-t one -t two -t three"
498    ,{t: ["one", "two", "three"]}
499    ,[]]
500   ,["-t one -t null -t three four five null"
501    ,{t: ["one", "null", "three"]}
502    ,["four", "five", "null"]]
503   ,["-t foo"
504    ,{t:["foo"]}
505    ,[]]
506   ,["--no-t"
507    ,{t:["false"]}
508    ,[]]
509   ,["-no-no-t"
510    ,{t:["true"]}
511    ,[]]
512   ,["-aoa one -aoa null -aoa 100"
513    ,{aoa:["one", null, 100]}
514    ,[]]
515   ,["-str 100"
516    ,{str:"100"}
517    ,[]]
518   ,["--color always"
519    ,{color:"always"}
520    ,[]]
521   ,["--no-nullstream"
522    ,{nullstream:null}
523    ,[]]
524   ,["--nullstream false"
525    ,{nullstream:null}
526    ,[]]
527   ,["--notadate 2011-01-25"
528    ,{notadate: "2011-01-25"}
529    ,[]]
530   ,["--date 2011-01-25"
531    ,{date: new Date("2011-01-25")}
532    ,[]]
533   ].forEach(function (test) {
534     var argv = test[0].split(/\s+/)
535       , opts = test[1]
536       , rem = test[2]
537       , actual = nopt(types, shorthands, argv, 0)
538       , parsed = actual.argv
539     delete actual.argv
540     console.log(util.inspect(actual, false, 2, true), parsed.remain)
541     for (var i in opts) {
542       var e = JSON.stringify(opts[i])
543         , a = JSON.stringify(actual[i] === undefined ? null : actual[i])
544       if (e && typeof e === "object") {
545         assert.deepEqual(e, a)
546       } else {
547         assert.equal(e, a)
548       }
549     }
550     assert.deepEqual(rem, parsed.remain)
551   })
552 }