@@ -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,39 @@ 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
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
41
37
# 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
48
61
49
62
# Return the help text for this **OptionParser**, listing and describing all
50
63
# of the valid options, for `--help` and such.
@@ -61,23 +74,38 @@ exports.OptionParser = class OptionParser
61
74
# Helpers
62
75
# -------
63
76
64
- # Regex matchers for option flags.
77
+ # Regex matchers for option flags on the command line and their rules .
65
78
LONG_FLAG = / ^ (--\w [\w \- ] * )/
66
79
SHORT_FLAG = / ^ (-\w )$ /
67
80
MULTI_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.
68
83
OPTIONAL = / \[ (\w + (\* ? ))\] /
69
84
70
85
# Build and return the list of option rules. If the optional *short-flag* is
71
86
# 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
74
89
tuple .unshift null if tuple .length < 3
75
90
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}
76
103
77
104
# Build a rule from a `-o` short flag, a `--output [DIR]` long flag, and the
78
105
# description of what the option does.
79
- buildRule = (shortFlag , longFlag , description , options = {} ) ->
106
+ buildRule = (shortFlag , longFlag , description ) ->
80
107
match = longFlag .match (OPTIONAL)
108
+ shortFlag = shortFlag .match (SHORT_FLAG)[1 ]
81
109
longFlag = longFlag .match (LONG_FLAG)[1 ]
82
110
{
83
111
name : longFlag .substr 2
@@ -88,22 +116,10 @@ buildRule = (shortFlag, longFlag, description, options = {}) ->
88
116
isList : !! (match and match[2 ])
89
117
}
90
118
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