You can not select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
					214 lines
				
				7.4 KiB
			
		
		
			
		
	
	
					214 lines
				
				7.4 KiB
			| 
								 
											3 years ago
										 
									 | 
							
								If you want to write an option parser, and have it be good, there are
							 | 
						||
| 
								 | 
							
								two ways to do it.  The Right Way, and the Wrong Way.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								The Wrong Way is to sit down and write an option parser.  We've all done
							 | 
						||
| 
								 | 
							
								that.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								The Right Way is to write some complex configurable program with so many
							 | 
						||
| 
								 | 
							
								options that you hit the limit of your frustration just trying to
							 | 
						||
| 
								 | 
							
								manage them all, and defer it with duct-tape solutions until you see
							 | 
						||
| 
								 | 
							
								exactly to the core of the problem, and finally snap and write an
							 | 
						||
| 
								 | 
							
								awesome option parser.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								If you want to write an option parser, don't write an option parser.
							 | 
						||
| 
								 | 
							
								Write a package manager, or a source control system, or a service
							 | 
						||
| 
								 | 
							
								restarter, or an operating system.  You probably won't end up with a
							 | 
						||
| 
								 | 
							
								good one of those, but if you don't give up, and you are relentless and
							 | 
						||
| 
								 | 
							
								diligent enough in your procrastination, you may just end up with a very
							 | 
						||
| 
								 | 
							
								nice option parser.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								## USAGE
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								```javascript
							 | 
						||
| 
								 | 
							
								// my-program.js
							 | 
						||
| 
								 | 
							
								var nopt = require("nopt")
							 | 
						||
| 
								 | 
							
								  , Stream = require("stream").Stream
							 | 
						||
| 
								 | 
							
								  , path = require("path")
							 | 
						||
| 
								 | 
							
								  , knownOpts = { "foo" : [String, null]
							 | 
						||
| 
								 | 
							
								                , "bar" : [Stream, Number]
							 | 
						||
| 
								 | 
							
								                , "baz" : path
							 | 
						||
| 
								 | 
							
								                , "bloo" : [ "big", "medium", "small" ]
							 | 
						||
| 
								 | 
							
								                , "flag" : Boolean
							 | 
						||
| 
								 | 
							
								                , "pick" : Boolean
							 | 
						||
| 
								 | 
							
								                , "many1" : [String, Array]
							 | 
						||
| 
								 | 
							
								                , "many2" : [path, Array]
							 | 
						||
| 
								 | 
							
								                }
							 | 
						||
| 
								 | 
							
								  , shortHands = { "foofoo" : ["--foo", "Mr. Foo"]
							 | 
						||
| 
								 | 
							
								                 , "b7" : ["--bar", "7"]
							 | 
						||
| 
								 | 
							
								                 , "m" : ["--bloo", "medium"]
							 | 
						||
| 
								 | 
							
								                 , "p" : ["--pick"]
							 | 
						||
| 
								 | 
							
								                 , "f" : ["--flag"]
							 | 
						||
| 
								 | 
							
								                 }
							 | 
						||
| 
								 | 
							
								             // everything is optional.
							 | 
						||
| 
								 | 
							
								             // knownOpts and shorthands default to {}
							 | 
						||
| 
								 | 
							
								             // arg list defaults to process.argv
							 | 
						||
| 
								 | 
							
								             // slice defaults to 2
							 | 
						||
| 
								 | 
							
								  , parsed = nopt(knownOpts, shortHands, process.argv, 2)
							 | 
						||
| 
								 | 
							
								console.log(parsed)
							 | 
						||
| 
								 | 
							
								```
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								This would give you support for any of the following:
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								```console
							 | 
						||
| 
								 | 
							
								$ node my-program.js --foo "blerp" --no-flag
							 | 
						||
| 
								 | 
							
								{ "foo" : "blerp", "flag" : false }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								$ node my-program.js ---bar 7 --foo "Mr. Hand" --flag
							 | 
						||
| 
								 | 
							
								{ bar: 7, foo: "Mr. Hand", flag: true }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								$ node my-program.js --foo "blerp" -f -----p
							 | 
						||
| 
								 | 
							
								{ foo: "blerp", flag: true, pick: true }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								$ node my-program.js -fp --foofoo
							 | 
						||
| 
								 | 
							
								{ foo: "Mr. Foo", flag: true, pick: true }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								$ node my-program.js --foofoo -- -fp  # -- stops the flag parsing.
							 | 
						||
| 
								 | 
							
								{ foo: "Mr. Foo", argv: { remain: ["-fp"] } }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								$ node my-program.js --blatzk -fp # unknown opts are ok.
							 | 
						||
| 
								 | 
							
								{ blatzk: true, flag: true, pick: true }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								$ node my-program.js --blatzk=1000 -fp # but you need to use = if they have a value
							 | 
						||
| 
								 | 
							
								{ blatzk: 1000, flag: true, pick: true }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								$ node my-program.js --no-blatzk -fp # unless they start with "no-"
							 | 
						||
| 
								 | 
							
								{ blatzk: false, flag: true, pick: true }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								$ node my-program.js --baz b/a/z # known paths are resolved.
							 | 
						||
| 
								 | 
							
								{ baz: "/Users/isaacs/b/a/z" }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								# if Array is one of the types, then it can take many
							 | 
						||
| 
								 | 
							
								# values, and will always be an array.  The other types provided
							 | 
						||
| 
								 | 
							
								# specify what types are allowed in the list.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								$ node my-program.js --many1 5 --many1 null --many1 foo
							 | 
						||
| 
								 | 
							
								{ many1: ["5", "null", "foo"] }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								$ node my-program.js --many2 foo --many2 bar
							 | 
						||
| 
								 | 
							
								{ many2: ["/path/to/foo", "path/to/bar"] }
							 | 
						||
| 
								 | 
							
								```
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								Read the tests at the bottom of `lib/nopt.js` for more examples of
							 | 
						||
| 
								 | 
							
								what this puppy can do.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								## Types
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								The following types are supported, and defined on `nopt.typeDefs`
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								* String: A normal string.  No parsing is done.
							 | 
						||
| 
								 | 
							
								* path: A file system path.  Gets resolved against cwd if not absolute.
							 | 
						||
| 
								 | 
							
								* url: A url.  If it doesn't parse, it isn't accepted.
							 | 
						||
| 
								 | 
							
								* Number: Must be numeric.
							 | 
						||
| 
								 | 
							
								* Date: Must parse as a date. If it does, and `Date` is one of the options,
							 | 
						||
| 
								 | 
							
								  then it will return a Date object, not a string.
							 | 
						||
| 
								 | 
							
								* Boolean: Must be either `true` or `false`.  If an option is a boolean,
							 | 
						||
| 
								 | 
							
								  then it does not need a value, and its presence will imply `true` as
							 | 
						||
| 
								 | 
							
								  the value.  To negate boolean flags, do `--no-whatever` or `--whatever
							 | 
						||
| 
								 | 
							
								  false`
							 | 
						||
| 
								 | 
							
								* NaN: Means that the option is strictly not allowed.  Any value will
							 | 
						||
| 
								 | 
							
								  fail.
							 | 
						||
| 
								 | 
							
								* Stream: An object matching the "Stream" class in node.  Valuable
							 | 
						||
| 
								 | 
							
								  for use when validating programmatically.  (npm uses this to let you
							 | 
						||
| 
								 | 
							
								  supply any WriteStream on the `outfd` and `logfd` config options.)
							 | 
						||
| 
								 | 
							
								* Array: If `Array` is specified as one of the types, then the value
							 | 
						||
| 
								 | 
							
								  will be parsed as a list of options.  This means that multiple values
							 | 
						||
| 
								 | 
							
								  can be specified, and that the value will always be an array.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								If a type is an array of values not on this list, then those are
							 | 
						||
| 
								 | 
							
								considered valid values.  For instance, in the example above, the
							 | 
						||
| 
								 | 
							
								`--bloo` option can only be one of `"big"`, `"medium"`, or `"small"`,
							 | 
						||
| 
								 | 
							
								and any other value will be rejected.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								When parsing unknown fields, `"true"`, `"false"`, and `"null"` will be
							 | 
						||
| 
								 | 
							
								interpreted as their JavaScript equivalents.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								You can also mix types and values, or multiple types, in a list.  For
							 | 
						||
| 
								 | 
							
								instance `{ blah: [Number, null] }` would allow a value to be set to
							 | 
						||
| 
								 | 
							
								either a Number or null.  When types are ordered, this implies a
							 | 
						||
| 
								 | 
							
								preference, and the first type that can be used to properly interpret
							 | 
						||
| 
								 | 
							
								the value will be used.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								To define a new type, add it to `nopt.typeDefs`.  Each item in that
							 | 
						||
| 
								 | 
							
								hash is an object with a `type` member and a `validate` method.  The
							 | 
						||
| 
								 | 
							
								`type` member is an object that matches what goes in the type list.  The
							 | 
						||
| 
								 | 
							
								`validate` method is a function that gets called with `validate(data,
							 | 
						||
| 
								 | 
							
								key, val)`.  Validate methods should assign `data[key]` to the valid
							 | 
						||
| 
								 | 
							
								value of `val` if it can be handled properly, or return boolean
							 | 
						||
| 
								 | 
							
								`false` if it cannot.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								You can also call `nopt.clean(data, types, typeDefs)` to clean up a
							 | 
						||
| 
								 | 
							
								config object and remove its invalid properties.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								## Error Handling
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								By default, nopt outputs a warning to standard error when invalid values for
							 | 
						||
| 
								 | 
							
								known options are found.  You can change this behavior by assigning a method
							 | 
						||
| 
								 | 
							
								to `nopt.invalidHandler`.  This method will be called with
							 | 
						||
| 
								 | 
							
								the offending `nopt.invalidHandler(key, val, types)`.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								If no `nopt.invalidHandler` is assigned, then it will console.error
							 | 
						||
| 
								 | 
							
								its whining.  If it is assigned to boolean `false` then the warning is
							 | 
						||
| 
								 | 
							
								suppressed.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								## Abbreviations
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								Yes, they are supported.  If you define options like this:
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								```javascript
							 | 
						||
| 
								 | 
							
								{ "foolhardyelephants" : Boolean
							 | 
						||
| 
								 | 
							
								, "pileofmonkeys" : Boolean }
							 | 
						||
| 
								 | 
							
								```
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								Then this will work:
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								```bash
							 | 
						||
| 
								 | 
							
								node program.js --foolhar --pil
							 | 
						||
| 
								 | 
							
								node program.js --no-f --pileofmon
							 | 
						||
| 
								 | 
							
								# etc.
							 | 
						||
| 
								 | 
							
								```
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								## Shorthands
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								Shorthands are a hash of shorter option names to a snippet of args that
							 | 
						||
| 
								 | 
							
								they expand to.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								If multiple one-character shorthands are all combined, and the
							 | 
						||
| 
								 | 
							
								combination does not unambiguously match any other option or shorthand,
							 | 
						||
| 
								 | 
							
								then they will be broken up into their constituent parts.  For example:
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								```json
							 | 
						||
| 
								 | 
							
								{ "s" : ["--loglevel", "silent"]
							 | 
						||
| 
								 | 
							
								, "g" : "--global"
							 | 
						||
| 
								 | 
							
								, "f" : "--force"
							 | 
						||
| 
								 | 
							
								, "p" : "--parseable"
							 | 
						||
| 
								 | 
							
								, "l" : "--long"
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								```
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								```bash
							 | 
						||
| 
								 | 
							
								npm ls -sgflp
							 | 
						||
| 
								 | 
							
								# just like doing this:
							 | 
						||
| 
								 | 
							
								npm ls --loglevel silent --global --force --long --parseable
							 | 
						||
| 
								 | 
							
								```
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								## The Rest of the args
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								The config object returned by nopt is given a special member called
							 | 
						||
| 
								 | 
							
								`argv`, which is an object with the following fields:
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								* `remain`: The remaining args after all the parsing has occurred.
							 | 
						||
| 
								 | 
							
								* `original`: The args as they originally appeared.
							 | 
						||
| 
								 | 
							
								* `cooked`: The args after flags and shorthands are expanded.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								## Slicing
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								Node programs are called with more or less the exact argv as it appears
							 | 
						||
| 
								 | 
							
								in C land, after the v8 and node-specific options have been plucked off.
							 | 
						||
| 
								 | 
							
								As such, `argv[0]` is always `node` and `argv[1]` is always the
							 | 
						||
| 
								 | 
							
								JavaScript program being run.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								That's usually not very useful to you.  So they're sliced off by
							 | 
						||
| 
								 | 
							
								default.  If you want them, then you can pass in `0` as the last
							 | 
						||
| 
								 | 
							
								argument, or any other number that you'd like to slice off the start of
							 | 
						||
| 
								 | 
							
								the list.
							 |