Skip to content

Commit 221dfc4

Browse files
author
Danny McClanahan
committed
refactor option parsing
clean up parsing code and in the process fix oustanding bug where coffeescript modified arguments meant for an executable script
1 parent 6c48af3 commit 221dfc4

File tree

1 file changed

+39
-50
lines changed

1 file changed

+39
-50
lines changed

src/optparse.coffee

Lines changed: 39 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
{repeat, isCoffee} = require './helpers'
1+
{repeat} = require './helpers'
22

33
# A simple **OptionParser** class to parse option flags from the command-line.
44
# Use it like so:
@@ -25,45 +25,26 @@ 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-
# Pass all arguments to script unchanged if first argument is the script to
29-
# be run; assume no options are for the coffeescript compiler. This allows
30-
# the use of '#!/usr/bin/env coffee' to run executable scripts on Linux
31-
# systems, which do not parse the '--' argument in the first line correctly.
32-
if (args.indexOf '--' is -1) and
33-
(args.length > 0) and
34-
(isCoffee args[0])
35-
return arguments: args
36-
options = arguments: []
37-
skippingArgument = no
38-
originalArgs = args
39-
args = normalizeArguments args
40-
for arg, i in args
41-
if skippingArgument
42-
skippingArgument = no
43-
continue
44-
if arg is '--'
45-
pos = originalArgs.indexOf '--'
46-
options.arguments = options.arguments.concat originalArgs[(pos + 1)..]
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 '--'
4745
break
48-
isOption = !!(arg.match(LONG_FLAG) or arg.match(SHORT_FLAG))
49-
# the CS option parser is a little odd; options after the first
50-
# non-option argument are treated as non-option arguments themselves
51-
seenNonOptionArg = options.arguments.length > 0
52-
unless seenNonOptionArg
53-
matchedRule = no
54-
for rule in @rules
55-
if rule.shortFlag is arg or rule.longFlag is arg
56-
value = true
57-
if rule.hasArgument
58-
skippingArgument = yes
59-
value = args[i + 1]
60-
options[rule.name] = if rule.isList then (options[rule.name] or []).concat value else value
61-
matchedRule = yes
62-
break
63-
throw new Error "unrecognized option: #{arg}" if isOption and not matchedRule
64-
if seenNonOptionArg or not isOption
65-
options.arguments.push arg
66-
options
46+
state.options.arguments = state.argsLeft[..]
47+
state.options
6748

6849
# Return the help text for this **OptionParser**, listing and describing all
6950
# of the valid options, for `--help` and such.
@@ -107,14 +88,22 @@ buildRule = (shortFlag, longFlag, description, options = {}) ->
10788
isList: !!(match and match[2])
10889
}
10990

110-
# Normalize arguments by expanding merged flags into multiple flags. This allows
111-
# you to have `-wl` be the same as `--watch --lint`.
112-
normalizeArguments = (args) ->
113-
args = args[..]
114-
result = []
115-
for arg in args
116-
if match = arg.match MULTI_FLAG
117-
result.push '-' + l for l in match[1].split ''
118-
else
119-
result.push arg
120-
result
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}"

0 commit comments

Comments
 (0)