Skip to content

Commit baaf924

Browse files
author
Danny McClanahan
committed
address comments
1 parent 221dfc4 commit baaf924

File tree

1 file changed

+60
-44
lines changed

1 file changed

+60
-44
lines changed

src/optparse.coffee

Lines changed: 60 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -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.
6578
LONG_FLAG = /^(--\w[\w\-]*)/
6679
SHORT_FLAG = /^(-\w)$/
6780
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.
6883
OPTIONAL = /\[(\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

Comments
 (0)