Expand multi-letter aliases in OptionParser (#5025)

Before this commit, -abc was processed by OptionParser as an alias.
With this commit, we deprecate this behaviour: if -abc still matches
an alias, we warn, otherwise, we split it into -a -b -c.
This commit is contained in:
Andrea Leopardi
2016-07-20 10:39:52 +02:00
committed by GitHub
parent 1f256671d6
commit 80aec928fc
2 changed files with 57 additions and 20 deletions
+46 -17
View File
@@ -327,23 +327,34 @@ defmodule OptionParser do
{:error, argv}
end
defp next(["-" <> option | rest] = argv, aliases, switches, strict) do
# Handles --foo or --foo=bar
defp next(["--" <> option | rest], _aliases, switches, strict) do
{option, value} = split_option(option)
original = "-" <> option
tagged = tag_option(option, switches, aliases)
tagged = tag_option(option, switches)
do_next(tagged, value, "--" <> option, rest, switches, strict)
end
# Handles -a, -abc, -abc=something
defp next(["-" <> option = original | rest] = argv, aliases, switches, strict) do
{option, value} = split_option(option)
original_option = "-" <> option
cond do
negative_number?(original) ->
{:error, argv}
strict and not option_defined?(tagged, switches) ->
{:undefined, original, value, rest}
true ->
{option, kinds, value} = normalize_option(tagged, value, switches)
{value, kinds, rest} = normalize_value(value, kinds, rest, strict)
case validate_option(value, kinds) do
{:ok, new_value} -> {:ok, option, new_value, rest}
:invalid -> {:invalid, original, value, rest}
String.contains?(option, ["-", "_"]) ->
{:undefined, original_option, value, rest}
String.length(option) > 1 ->
if (opt = get_option(option)) && (alias = aliases[opt]) do
IO.warn "multi-letter aliases are deprecated, got: #{inspect(opt)}"
do_next({:default, alias}, value, original_option, rest, switches, strict)
else
next(expand_multiletter_alias(option, value) ++ rest, aliases, switches, strict)
end
true ->
# We have a regular one-letter alias here
tagged = tag_single_letter_alias(option, aliases)
do_next(tagged, value, original_option, rest, switches, strict)
end
end
@@ -351,6 +362,19 @@ defmodule OptionParser do
{:error, argv}
end
defp do_next(tagged, value, original, rest, switches, strict) do
if strict and not option_defined?(tagged, switches) do
{:undefined, original, value, rest}
else
{option, kinds, value} = normalize_option(tagged, value, switches)
{value, kinds, rest} = normalize_value(value, kinds, rest, strict)
case validate_option(value, kinds) do
{:ok, new_value} -> {:ok, option, new_value, rest}
:invalid -> {:invalid, original, value, rest}
end
end
end
@doc """
Receives a key-value enumerable and converts it to argv.
@@ -506,18 +530,18 @@ defmodule OptionParser do
end
end
defp tag_option("-no-" <> option, switches, _aliases) do
defp tag_option("no-" <> option = original, switches) do
cond do
(negated = get_option(option)) && :boolean in List.wrap(switches[negated]) ->
{:negated, negated}
option = get_option("no-" <> option) ->
option = get_option(original) ->
{:default, option}
true ->
:unknown
end
end
defp tag_option("-" <> option, _switches, _aliases) do
defp tag_option(option, _switches) do
if option = get_option(option) do
{:default, option}
else
@@ -525,15 +549,20 @@ defmodule OptionParser do
end
end
defp tag_option(option, _switches, aliases) when is_binary(option) do
opt = get_option(option)
if alias = aliases[opt] do
defp tag_single_letter_alias(alias, aliases) when is_binary(alias) do
if alias = aliases[String.to_atom(alias)] do
{:default, alias}
else
:unknown
end
end
defp expand_multiletter_alias(letters, value) when is_binary(letters) do
expanded = letters |> String.codepoints() |> Enum.map(&("-" <> &1))
last = List.last(expanded) <> if(value, do: "=" <> value, else: "")
Enum.drop(expanded, -1) ++ [last]
end
defp option_defined?(:unknown, _switches) do
false
end
+11 -3
View File
@@ -58,9 +58,17 @@ defmodule OptionParserTest do
== {[s: "from_docs/"], [], []}
end
test "does not parse - as a switch" do
assert OptionParser.parse(["-source=from_docs/"], aliases: [s: :source])
== {[], [], [{"-source", "from_docs/"}]}
test "parses -ab as -a -b" do
aliases = [a: :first, b: :second]
assert OptionParser.parse(["-ab"], aliases: aliases)
== {[first: true, second: true], [], []}
assert OptionParser.parse(["-ab=1"], aliases: aliases, switches: [second: :integer])
== {[first: true, second: 1], [], []}
assert OptionParser.parse(["-ab", "1"], aliases: aliases, switches: [second: :integer])
== {[first: true, second: 1], [], []}
end
test "parses configured booleans" do