Skip to content

Commit 988f2af

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

File tree

1 file changed

+62
-43
lines changed

1 file changed

+62
-43
lines changed

src/optparse.coffee

Lines changed: 62 additions & 43 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,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+
multi = trySplitMultiFlag(cmdLineArg)
36+
toProcess = multi or trySingleFlag(cmdLineArg)
37+
# If the current argument could not be parsed as one or more arguments.
38+
unless toProcess?
39+
++i if cmdLineArg is '--'
40+
options.arguments = 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 toProcess
46+
rule = @rules.flagDict[argFlag]
47+
unless rule?
48+
context = if multi? 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 = toProcess[++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.
6581
LONG_FLAG = /^(--\w[\w\-]*)/
6682
SHORT_FLAG = /^(-\w)$/
6783
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.
6886
OPTIONAL = /\[(\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,11 @@ 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+
if ([LONG_FLAG, SHORT_FLAG].some (pat) -> arg.match(pat)?) then [arg]
123+
else null
108124

109-
throw new Error "unrecognized option: #{arg}"
125+
trySplitMultiFlag = (arg) ->
126+
arg.match(MULTI_FLAG)?[1]
127+
.split('')
128+
.map (flagName) -> "-#{flagName}"

0 commit comments

Comments
 (0)