Skip to content

Commit e7f3db9

Browse files
author
José Valim
committed
Merge pull request #2397 from alco/option-parser-fix
Do not allow underscores in option names. Closes #2391
2 parents 4682511 + e942c07 commit e7f3db9

File tree

2 files changed

+74
-23
lines changed

2 files changed

+74
-23
lines changed

lib/elixir/lib/option_parser.ex

+42-23
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,9 @@ defmodule OptionParser do
3232
3333
Note Elixir also converts the switches to underscore atoms, as
3434
`--source-path` becomes `:source_path`, to better suit Elixir
35-
conventions.
35+
conventions. This means that option names on the command line cannot contain
36+
underscores; such options will be reported as `:undefined` (in strict mode)
37+
or `:invalid` (in basic mode).
3638
3739
## Switches
3840
@@ -224,7 +226,7 @@ defmodule OptionParser do
224226
else
225227
{opt_name, kinds, value} = normalize_option(tagged, value, switches)
226228
{value, kinds, rest} = normalize_value(value, kinds, rest, strict)
227-
case validate_option(opt_name, value, kinds) do
229+
case validate_option(value, kinds) do
228230
{:ok, new_value} -> {:ok, opt_name, new_value, rest}
229231
:invalid -> {:invalid, opt_name_bin, value, rest}
230232
end
@@ -252,31 +254,31 @@ defmodule OptionParser do
252254
{aliases, switches, strict, all}
253255
end
254256

255-
defp validate_option(option, value, kinds) do
256-
{invalid_opt, value} = cond do
257+
defp validate_option(value, kinds) do
258+
{is_invalid, value} = cond do
257259
:invalid in kinds ->
258-
{option, value}
260+
{true, value}
259261
:boolean in kinds ->
260262
case value do
261263
t when t in [true, "true"] -> {nil, true}
262264
f when f in [false, "false"] -> {nil, false}
263-
_ -> {option, value}
265+
_ -> {true, value}
264266
end
265267
:integer in kinds ->
266268
case Integer.parse(value) do
267269
{value, ""} -> {nil, value}
268-
_ -> {option, value}
270+
_ -> {true, value}
269271
end
270272
:float in kinds ->
271273
case Float.parse(value) do
272274
{value, ""} -> {nil, value}
273-
_ -> {option, value}
275+
_ -> {true, value}
274276
end
275277
true ->
276278
{nil, value}
277279
end
278280

279-
if invalid_opt do
281+
if is_invalid do
280282
:invalid
281283
else
282284
{:ok, value}
@@ -301,11 +303,11 @@ defmodule OptionParser do
301303
if alias = aliases[opt] do
302304
{:default, alias}
303305
else
304-
{:unknown, opt}
306+
:unknown
305307
end
306308
end
307309

308-
defp option_defined?({:unknown, _option}, _switches) do
310+
defp option_defined?(:unknown, _switches) do
309311
false
310312
end
311313

@@ -317,8 +319,8 @@ defmodule OptionParser do
317319
Keyword.has_key?(switches, option)
318320
end
319321

320-
defp normalize_option({:unknown, option}, value, _switches) do
321-
{option, [:invalid], value}
322+
defp normalize_option(:unknown, value, _switches) do
323+
{nil, [:invalid], value}
322324
end
323325

324326
defp normalize_option({:negated, option}, nil, switches) do
@@ -374,29 +376,46 @@ defmodule OptionParser do
374376
end
375377
end
376378

377-
defp to_underscore(option) do
378-
for <<c <- option>>, into: "", do: << if(c == ?-, do: ?_, else: c) >>
379-
end
379+
defp to_underscore(option), do: to_underscore(option, <<>>)
380+
381+
defp to_underscore("_" <> _rest, _acc), do: nil
382+
383+
defp to_underscore("-" <> rest, acc),
384+
do: to_underscore(rest, acc <> "_")
385+
386+
defp to_underscore(<<c>> <> rest, acc),
387+
do: to_underscore(rest, <<acc::binary, c>>)
388+
389+
defp to_underscore(<<>>, acc), do: acc
380390

381391
defp get_option(option) do
382-
option |> to_underscore |> String.to_atom
392+
if str = to_underscore(option) do
393+
String.to_atom(str)
394+
end
383395
end
384396

385397
defp reverse_negated(negated) do
386398
String.to_atom("no_" <> Atom.to_string(negated))
387399
end
388400

389401
defp get_negated("no-" <> rest = option, value, switches) do
390-
negated = get_option(rest)
391-
option = if Keyword.has_key?(switches, negated) and value == nil do
392-
negated
402+
if negated = get_option(rest) do
403+
option = if Keyword.has_key?(switches, negated) and value == nil do
404+
negated
405+
else
406+
get_option(option)
407+
end
408+
{:negated, option}
393409
else
394-
get_option(option)
410+
:unknown
395411
end
396-
{:negated, option}
397412
end
398413

399414
defp get_negated(rest, _value, _switches) do
400-
{:default, get_option(rest)}
415+
if option = get_option(rest) do
416+
{:default, option}
417+
else
418+
:unknown
419+
end
401420
end
402421
end

lib/elixir/test/elixir/option_parser_test.exs

+32
Original file line numberDiff line numberDiff line change
@@ -209,6 +209,38 @@ defmodule OptionParserTest do
209209
== {[foo: true, boo: "-"], ["-"], []}
210210
end
211211

212+
test "multi-word option" do
213+
config = [switches: [hello_world: :boolean]]
214+
assert OptionParser.next(["--hello-world"], config)
215+
== {:ok, :hello_world, true, []}
216+
assert OptionParser.next(["--no-hello-world"], config)
217+
== {:ok, :hello_world, false, []}
218+
219+
assert OptionParser.next(["--hello-world"], [])
220+
== {:ok, :hello_world, true, []}
221+
assert OptionParser.next(["--no-hello-world"], [])
222+
== {:ok, :no_hello_world, true, []}
223+
assert OptionParser.next(["--hello_world"], [])
224+
== {:invalid, "--hello_world", nil, []}
225+
assert OptionParser.next(["--no-hello_world"], [])
226+
== {:invalid, "--no-hello_world", nil, []}
227+
228+
assert OptionParser.next(["--no-hello-world"], strict: [])
229+
== {:undefined, "--no-hello-world", nil, []}
230+
assert OptionParser.next(["--no-hello_world"], strict: [])
231+
== {:undefined, "--no-hello_world", nil, []}
232+
233+
config = [strict: [hello_world: :boolean]]
234+
assert OptionParser.next(["--hello-world"], config)
235+
== {:ok, :hello_world, true, []}
236+
assert OptionParser.next(["--no-hello-world"], config)
237+
== {:ok, :hello_world, false, []}
238+
assert OptionParser.next(["--hello_world"], config)
239+
== {:undefined, "--hello_world", nil, []}
240+
assert OptionParser.next(["--no-hello_world"], config)
241+
== {:undefined, "--no-hello_world", nil, []}
242+
end
243+
212244
test "next strict: good options" do
213245
config = [strict: [str: :string, int: :integer, bool: :boolean]]
214246
assert OptionParser.next(["--str", "hello", "..."], config)

0 commit comments

Comments
 (0)