@@ -15,8 +15,8 @@ exports.OptionParser = class OptionParser
15
15
# [short-flag, long-flag, description]
16
16
#
17
17
# 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
20
20
21
21
# Parse the list of arguments, populating an `options` object with all of the
22
22
# specified options, and return it. Options after the first non-option
@@ -25,26 +25,42 @@ exports.OptionParser = class OptionParser
25
25
# parsers that allow you to attach callback actions for every flag. Instead,
26
26
# you're responsible for interpreting the options object.
27
27
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
+ ++ i if cmdLineArg is ' --'
40
+ options .arguments = args[i.. ]
45
41
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
48
64
49
65
# Return the help text for this **OptionParser**, listing and describing all
50
66
# of the valid options, for `--help` and such.
@@ -61,23 +77,37 @@ exports.OptionParser = class OptionParser
61
77
# Helpers
62
78
# -------
63
79
64
- # Regex matchers for option flags.
80
+ # Regex matchers for option flags on the command line and their rules .
65
81
LONG_FLAG = / ^ (--\w [\w \- ] * )/
66
82
SHORT_FLAG = / ^ (-\w )$ /
67
83
MULTI_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.
68
86
OPTIONAL = / \[ (\w + (\* ? ))\] /
69
87
70
88
# Build and return the list of option rules. If the optional *short-flag* is
71
89
# 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
74
92
tuple .unshift null if tuple .length < 3
75
93
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}
76
105
77
106
# Build a rule from a `-o` short flag, a `--output [DIR]` long flag, and the
78
107
# description of what the option does.
79
- buildRule = (shortFlag , longFlag , description , options = {} ) ->
108
+ buildRule = (shortFlag , longFlag , description ) ->
80
109
match = longFlag .match (OPTIONAL)
110
+ shortFlag = shortFlag ? .match (SHORT_FLAG)[1 ]
81
111
longFlag = longFlag .match (LONG_FLAG)[1 ]
82
112
{
83
113
name : longFlag .substr 2
@@ -88,22 +118,10 @@ buildRule = (shortFlag, longFlag, description, options = {}) ->
88
118
isList : !! (match and match[2 ])
89
119
}
90
120
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]
108
123
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