diff --git a/lib/elixir/lib/option_parser.ex b/lib/elixir/lib/option_parser.ex index b0e934dfff..9473c420b0 100644 --- a/lib/elixir/lib/option_parser.ex +++ b/lib/elixir/lib/option_parser.ex @@ -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 diff --git a/lib/elixir/test/elixir/option_parser_test.exs b/lib/elixir/test/elixir/option_parser_test.exs index e88748aaad..3c12db8a46 100644 --- a/lib/elixir/test/elixir/option_parser_test.exs +++ b/lib/elixir/test/elixir/option_parser_test.exs @@ -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