1 # A simple **OptionParser** class to parse option flags from the command-line.
4 # parser = new OptionParser switches, helpBanner
5 # options = parser.parse process.argv
7 # The first non-option is considered to be the start of the file (and file
8 # option) list, and all subsequent arguments are left unparsed.
9 exports.OptionParser = class OptionParser
11 # Initialize with a list of valid options, in the form:
13 # [short-flag, long-flag, description]
15 # Along with an an optional banner for the usage help.
16 constructor: (rules, banner) ->
18 @rules = buildRules rules
20 # Parse the list of arguments, populating an `options` object with all of the
21 # specified options, and returning it. `options.arguments` will be an array
22 # containing the remaning non-option arguments. This is a simpler API than
23 # many option parsers that allow you to attach callback actions for every
24 # flag. Instead, you're responsible for interpreting the options object.
26 options = arguments: []
27 args = normalizeArguments args
29 isOption = !!(arg.match(LONG_FLAG) or arg.match(SHORT_FLAG))
32 if rule.shortFlag is arg or rule.longFlag is arg
33 value = if rule.hasArgument then args[i += 1] else true
34 options[rule.name] = if rule.isList then (options[rule.name] or []).concat value else value
37 throw new Error "unrecognized option: #{arg}" if isOption and not matchedRule
39 options.arguments = args.slice i
43 # Return the help text for this **OptionParser**, listing and describing all
44 # of the valid options, for `--help` and such.
46 lines = ['Available options:']
47 lines.unshift "#{@banner}\n" if @banner
49 spaces = 15 - rule.longFlag.length
50 spaces = if spaces > 0 then Array(spaces + 1).join(' ') else ''
51 letPart = if rule.shortFlag then rule.shortFlag + ', ' else ' '
52 lines.push ' ' + letPart + rule.longFlag + spaces + rule.description
53 "\n#{ lines.join('\n') }\n"
58 # Regex matchers for option flags.
59 LONG_FLAG = /^(--\w[\w\-]+)/
61 MULTI_FLAG = /^-(\w{2,})/
62 OPTIONAL = /\[(\w+(\*?))\]/
64 # Build and return the list of option rules. If the optional *short-flag* is
65 # unspecified, leave it out by padding with `null`.
66 buildRules = (rules) ->
68 tuple.unshift null if tuple.length < 3
71 # Build a rule from a `-o` short flag, a `--output [DIR]` long flag, and the
72 # description of what the option does.
73 buildRule = (shortFlag, longFlag, description, options) ->
74 match = longFlag.match(OPTIONAL)
75 longFlag = longFlag.match(LONG_FLAG)[1]
78 name: longFlag.substr 2
81 description: description
82 hasArgument: !!(match and match[1])
83 isList: !!(match and match[2])
86 # Normalize arguments by expanding merged flags into multiple flags. This allows
87 # you to have `-wl` be the same as `--watch --lint`.
88 normalizeArguments = (args) ->
92 if match = arg.match MULTI_FLAG
93 result.push '-' + l for l in match[1].split ''