@@ -15,8 +15,8 @@ exports.OptionParser = class OptionParser
1515 # [short-flag, long-flag, description]
1616 #
1717 # Along with an an optional banner for the usage help.
18- constructor : (rules , @banner ) ->
19- @rules = buildRules rules
18+ constructor : (ruleDecls , @banner ) ->
19+ @rules = buildRules ruleDecls
2020
2121 # Parse the list of arguments, populating an `options` object with all of the
2222 # specified options, and return it. Options after the first non-option
@@ -25,26 +25,39 @@ exports.OptionParser = class OptionParser
2525 # parsers that allow you to attach callback actions for every flag. Instead,
2626 # you're responsible for interpreting the options object.
2727 parse : (args ) ->
28- state =
29- argsLeft : args[.. ]
30- options : {}
31- while (arg = state .argsLeft .shift ())?
32- if (arg .match (LONG_FLAG) ? arg .match (SHORT_FLAG))?
33- tryMatchOptionalArgument (arg, state, @rules )
34- else if (multiMatch = arg .match (MULTI_FLAG))?
35- # Normalize arguments by expanding merged flags into multiple
36- # flags. This allows you to have `-wl` be the same as `--watch --lint`.
37- normalized = " -#{ multiArg} " for multiArg in multiMatch[1 ].split ' '
38- state .argsLeft .unshift (normalized... )
39- else
40- # the CS option parser is a little odd; options after the first
28+
29+ options = {}
30+ for cmdArg, i in args
31+ # Normalize arguments by expanding merged flags into multiple
32+ # flags. This allows you to have `-wl` be the same as `--watch --lint`.
33+ multi = splitMultiArg cmdArg
34+ subArgs = multi or [cmdArg]
35+ for sub, j in subArgs
36+ # The CS option parser is a little odd; options after the first
4137 # non-option argument are treated as non-option arguments themselves.
42- # executable scripts do not need to have a `--` at the end of the
43- # shebang ("#!") line, and if they do, they won't work on Linux
44- state .argsLeft .unshift (arg) unless arg is ' --'
45- break
46- state .options .arguments = state .argsLeft [.. ]
47- state .options
38+ # Executable scripts do not need to have a `--` at the end of the
39+ # shebang ("#!") line, and if they do, they won't work on Linux.
40+ unless looksLikeOption sub
41+ ++ j if sub is ` -- `
42+ options .arguments = [subArgs[j.. ]... , args[++ i.. ]... ]
43+ return options
44+
45+ rule = @rules .flagDict [sub]
46+ unless rule?
47+ context = if multi? then " (in multi-flag '#{ cmdArg} ')" else ' '
48+ throw new Error " unrecognized option: #{ sub}#{ context} "
49+
50+ {hasArgument , isList , name } = rule
51+
52+ value = if hasArgument
53+ subArgs[++ j] or args[++ i] or
54+ throw new Error " value required for '#{ sub} ':
55+ was the last argument provided"
56+ else true
57+
58+ value = (options[name] or []).concat value if isList
59+ options[name] = value
60+ options
4861
4962 # Return the help text for this **OptionParser**, listing and describing all
5063 # of the valid options, for `--help` and such.
@@ -61,23 +74,38 @@ exports.OptionParser = class OptionParser
6174# Helpers
6275# -------
6376
64- # Regex matchers for option flags.
77+ # Regex matchers for option flags on the command line and their rules .
6578LONG_FLAG = / ^ (--\w [\w \- ] * )/
6679SHORT_FLAG = / ^ (-\w )$ /
6780MULTI_FLAG = / ^ -(\w {2,} )/
81+ # Matches the long flag part of a rule for an option with an argument. Not
82+ # applied to anything in process.argv.
6883OPTIONAL = / \[ (\w + (\* ? ))\] /
6984
7085# Build and return the list of option rules. If the optional *short-flag* is
7186# unspecified, leave it out by padding with `null`.
72- buildRules = (rules ) ->
73- for tuple in rules
87+ buildRules = (ruleDecls ) ->
88+ ruleList = for tuple in ruleDecls
7489 tuple .unshift null if tuple .length < 3
7590 buildRule tuple...
91+ switches = {}
92+ argFlags = {}
93+ for rule in ruleList
94+ flagDict = if rule .hasArgument then argFlags else switches
95+ for flag in [rule .shortFlag , rule .longFlag ]
96+ prevRule = flagDict[flag]
97+ if prevRule?
98+ throw new Error " flag #{ flag} for switch #{ rule .name }
99+ was already declared for switch #{ prevRule .name } "
100+ flagDict[flag] = rule
101+
102+ {ruleList, switches, argFlags}
76103
77104# Build a rule from a `-o` short flag, a `--output [DIR]` long flag, and the
78105# description of what the option does.
79- buildRule = (shortFlag , longFlag , description , options = {} ) ->
106+ buildRule = (shortFlag , longFlag , description ) ->
80107 match = longFlag .match (OPTIONAL)
108+ shortFlag = shortFlag .match (SHORT_FLAG)[1 ]
81109 longFlag = longFlag .match (LONG_FLAG)[1 ]
82110 {
83111 name : longFlag .substr 2
@@ -88,22 +116,10 @@ buildRule = (shortFlag, longFlag, description, options = {}) ->
88116 isList : !! (match and match[2 ])
89117 }
90118
91- addArgument = (rule , options , value ) ->
92- options[rule .name ] = if rule .isList
93- (options[rule .name ] ? []).concat value
94- else value
95-
96- tryMatchOptionalArgument = (arg , state , rules ) ->
97- for rule in rules
98- if arg in [rule .shortFlag , rule .longFlag ]
99- if rule .hasArgument
100- value = state .argsLeft .shift ()
101- if not value?
102- throw new Error " #{ arg} requires a value, but was the last argument"
103- else
104- value = true
105-
106- addArgument (rule, state .options , value)
107- return
108-
109- throw new Error " unrecognized option: #{ arg} "
119+ looksLikeOption = (maybeOption ) ->
120+ [LONG_FLAG, SHORT_FLAG].some (pat) -> maybeOption .match (pat)?
121+
122+ splitMultiArg = (maybeMulti ) ->
123+ maybeMulti .match (MULTI_FLAG)? [1 ]
124+ .split (' ' )
125+ .map (arg) -> " -#{ arg} "
0 commit comments