@@ -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,42 @@ 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
41- # 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 ' --'
28+
29+ options = arguments : []
30+ for cmdLineArg, i in args
31+ # The CS option parser is a little odd; options after the first
32+ # non-option argument are treated as non-option arguments themselves.
33+ # Executable scripts do not need to have a `--` at the end of the
34+ # shebang ("#!") line, and if they do, they won't work on Linux.
35+ multiFlags = trySplitMultiFlag (cmdLineArg)
36+ curFlags = multiFlags or trySingleFlag (cmdLineArg)
37+ # If the current argument could not be parsed as one or more arguments.
38+ unless curFlags?
39+ ++ j if cmdLineArg is ' --'
40+ options .arguments = [curFlags[j.. ]... , args[++ i.. ]... ]
4541 break
46- state .options .arguments = state .argsLeft [.. ]
47- state .options
42+
43+ # Normalize arguments by expanding merged flags into multiple
44+ # flags. This allows you to have `-wl` be the same as `--watch --lint`.
45+ for argFlag, j in curFlags
46+ rule = @rules .flagDict [argFlag]
47+ unless rule?
48+ context = if multiFlags? then " (in multi-flag '#{ cmdLineArg} ')" else ' '
49+ throw new Error " unrecognized option: #{ argFlag}#{ context} "
50+
51+ {hasArgument , isList , name } = rule
52+
53+ options[name] = switch hasArgument
54+ when no then true
55+ else
56+ next = curFlags[++ j] or args[++ i]
57+ unless next?
58+ throw new Error " value required for '#{ argFlag} ':
59+ was the last argument provided"
60+ switch isList
61+ when no then next
62+ else (options[name] or []).concat next
63+ options
4864
4965 # Return the help text for this **OptionParser**, listing and describing all
5066 # of the valid options, for `--help` and such.
@@ -61,23 +77,37 @@ exports.OptionParser = class OptionParser
6177# Helpers
6278# -------
6379
64- # Regex matchers for option flags.
80+ # Regex matchers for option flags on the command line and their rules .
6581LONG_FLAG = / ^ (--\w [\w \- ] * )/
6682SHORT_FLAG = / ^ (-\w )$ /
6783MULTI_FLAG = / ^ -(\w {2,} )/
84+ # Matches the long flag part of a rule for an option with an argument. Not
85+ # applied to anything in process.argv.
6886OPTIONAL = / \[ (\w + (\* ? ))\] /
6987
7088# Build and return the list of option rules. If the optional *short-flag* is
7189# unspecified, leave it out by padding with `null`.
72- buildRules = (rules ) ->
73- for tuple in rules
90+ buildRules = (ruleDecls ) ->
91+ ruleList = for tuple in ruleDecls
7492 tuple .unshift null if tuple .length < 3
7593 buildRule tuple...
94+ flagDict = {}
95+ for rule in ruleList
96+ # shortFlag is null if not provided in the rule.
97+ for flag in [rule .shortFlag , rule .longFlag ] when flag?
98+ prevRule = flagDict[flag]
99+ if prevRule?
100+ throw new Error " flag #{ flag} for switch #{ rule .name }
101+ was already declared for switch #{ prevRule .name } "
102+ flagDict[flag] = rule
103+
104+ {ruleList, flagDict}
76105
77106# Build a rule from a `-o` short flag, a `--output [DIR]` long flag, and the
78107# description of what the option does.
79- buildRule = (shortFlag , longFlag , description , options = {} ) ->
108+ buildRule = (shortFlag , longFlag , description ) ->
80109 match = longFlag .match (OPTIONAL)
110+ shortFlag = shortFlag ? .match (SHORT_FLAG)[1 ]
81111 longFlag = longFlag .match (LONG_FLAG)[1 ]
82112 {
83113 name : longFlag .substr 2
@@ -88,22 +118,10 @@ buildRule = (shortFlag, longFlag, description, options = {}) ->
88118 isList : !! (match and match[2 ])
89119 }
90120
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
121+ trySingleFlag = (arg ) ->
122+ ([LONG_FLAG, SHORT_FLAG].some (pat) -> arg .match (pat)? ) and [arg]
108123
109- throw new Error " unrecognized option: #{ arg} "
124+ trySplitMultiFlag = (arg ) ->
125+ arg .match (MULTI_FLAG)? [1 ]
126+ .split (' ' )
127+ .map (flagName) -> " -#{ flagName} "
0 commit comments