summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLeandro Pereira <leandro@leandro.io>2023-03-28 12:12:33 -0400
committerGitHub <noreply@github.com>2023-03-28 18:12:33 +0200
commitc760a5a4af71a46a1400b345c3e55a2c18625104 (patch)
tree9caaa7b7ba7d0147b59b170f36c77450d23e3ed4
parent0f812e00cbfbb6d86e065e4b494bbda6e5ed207c (diff)
downloadelixir-c760a5a4af71a46a1400b345c3e55a2c18625104.tar.gz
Add `:return_separator` to `OptionParser` (#12499)
-rw-r--r--lib/elixir/lib/option_parser.ex31
-rw-r--r--lib/elixir/test/elixir/option_parser_test.exs17
-rw-r--r--lib/mix/lib/mix/tasks/run.ex4
-rw-r--r--lib/mix/test/mix/tasks/run_test.exs57
4 files changed, 105 insertions, 4 deletions
diff --git a/lib/elixir/lib/option_parser.ex b/lib/elixir/lib/option_parser.ex
index 89a76f057..cce5cd63c 100644
--- a/lib/elixir/lib/option_parser.ex
+++ b/lib/elixir/lib/option_parser.ex
@@ -33,7 +33,8 @@ defmodule OptionParser do
switches: keyword,
strict: keyword,
aliases: keyword,
- allow_nonexistent_atoms: boolean
+ allow_nonexistent_atoms: boolean,
+ return_separator: boolean
]
defmodule ParseError do
@@ -83,6 +84,7 @@ defmodule OptionParser do
* `:switches` or `:strict` - see the "Switch definitions" section below
* `:allow_nonexistent_atoms` - see the "Parsing unknown switches" section below
* `:aliases` - see the "Aliases" section below
+ * `:return_separator` - see the "Return separator" section below
## Switch definitions
@@ -230,6 +232,21 @@ defmodule OptionParser do
...> )
{[unlock: "path/to/file", unlock: "path/to/another/file"], [], []}
+ ## Return separator
+
+ The separator `--` implies options should no longer be processed.
+ By default, the separator is not returned as parts of the arguments,
+ but that can be changed via the `:return_separator` option:
+
+ iex> OptionParser.parse(["--", "lib"], return_separator: true, strict: [])
+ {[], ["--", "lib"], []}
+
+ iex> OptionParser.parse(["--no-halt", "--", "lib"], return_separator: true, switches: [halt: :boolean])
+ {[halt: false], ["--", "lib"], []}
+
+ iex> OptionParser.parse(["script.exs", "--no-halt", "--", "foo"], return_separator: true, switches: [halt: :boolean])
+ {[{:halt, false}], ["script.exs", "--", "foo"], []}
+
"""
@spec parse(argv, options) :: {parsed, argv, errors}
def parse(argv, opts \\ []) when is_list(argv) and is_list(opts) do
@@ -363,8 +380,15 @@ defmodule OptionParser do
invalid = if config.strict?, do: [{option, nil} | invalid], else: invalid
do_parse(rest, config, opts, args, invalid, all?)
- {:error, ["--" | rest]} ->
- {Enum.reverse(opts), Enum.reverse(args, rest), Enum.reverse(invalid)}
+ {:error, ["--" | rest] = remaining_args} ->
+ args =
+ if config.return_separator? do
+ Enum.reverse(args, remaining_args)
+ else
+ Enum.reverse(args, rest)
+ end
+
+ {Enum.reverse(opts), args, Enum.reverse(invalid)}
{:error, [arg | rest] = remaining_args} ->
# there is no option
@@ -616,6 +640,7 @@ defmodule OptionParser do
%{
aliases: opts[:aliases] || [],
allow_nonexistent_atoms?: opts[:allow_nonexistent_atoms] || false,
+ return_separator?: opts[:return_separator] || false,
strict?: strict?,
switches: switches
}
diff --git a/lib/elixir/test/elixir/option_parser_test.exs b/lib/elixir/test/elixir/option_parser_test.exs
index 7f38e4264..c589a1165 100644
--- a/lib/elixir/test/elixir/option_parser_test.exs
+++ b/lib/elixir/test/elixir/option_parser_test.exs
@@ -148,6 +148,23 @@ defmodule OptionParserTest do
) == {[source: "foo"], ["bar", "--", "-x"], []}
end
+ test "return separators" do
+ assert OptionParser.parse_head(["--", "foo"],
+ switches: [],
+ return_separator: true
+ ) == {[], ["--", "foo"], []}
+
+ assert OptionParser.parse_head(["--no-halt", "--", "foo"],
+ switches: [halt: :boolean],
+ return_separator: true
+ ) == {[halt: false], ["--", "foo"], []}
+
+ assert OptionParser.parse_head(["foo.exs", "--no-halt", "--", "foo"],
+ switches: [halt: :boolean],
+ return_separator: true
+ ) == {[], ["foo.exs", "--no-halt", "--", "foo"], []}
+ end
+
test "parses - as argument" do
argv = ["--foo", "-", "-b", "-"]
opts = [strict: [foo: :boolean, boo: :string], aliases: [b: :boo]]
diff --git a/lib/mix/lib/mix/tasks/run.ex b/lib/mix/lib/mix/tasks/run.ex
index 3eb30e66d..3eab6eb42 100644
--- a/lib/mix/lib/mix/tasks/run.ex
+++ b/lib/mix/lib/mix/tasks/run.ex
@@ -78,7 +78,8 @@ defmodule Mix.Tasks.Run do
elixir_version_check: :boolean,
parallel_require: :keep,
preload_modules: :boolean
- ]
+ ],
+ return_separator: true
)
run(args, opts, head, &Code.eval_string/1, &Code.require_file/1)
@@ -112,6 +113,7 @@ defmodule Mix.Tasks.Run do
{file, argv} =
case {Keyword.has_key?(opts, :eval), head} do
+ {_, ["--" | rest]} -> {nil, rest}
{true, _} -> {nil, head}
{_, [head | tail]} -> {head, tail}
{_, []} -> {nil, []}
diff --git a/lib/mix/test/mix/tasks/run_test.exs b/lib/mix/test/mix/tasks/run_test.exs
index 9f25d6a1d..6845fdfef 100644
--- a/lib/mix/test/mix/tasks/run_test.exs
+++ b/lib/mix/test/mix/tasks/run_test.exs
@@ -81,6 +81,14 @@ defmodule Mix.Tasks.RunTest do
assert_received {:argv, ["foo", "-e", "bar"]}
unload_file.()
+ Mix.Tasks.Run.run([file, "foo", "--", "bar"])
+ assert_received {:argv, ["foo", "--", "bar"]}
+
+ unload_file.()
+ Mix.Tasks.Run.run([file, "--custom-opt", "foo", "--", "bar"])
+ assert_received {:argv, ["--custom-opt", "foo", "--", "bar"]}
+
+ unload_file.()
Mix.Tasks.Run.run(["-e", expr, file, "foo", "-x", "bar"])
assert_received {:argv, [^file, "foo", "-x", "bar"]}
@@ -93,4 +101,53 @@ defmodule Mix.Tasks.RunTest do
assert_received {:argv, [^file, "-x", "bar"]}
end)
end
+
+ defmodule AppArgvSample do
+ def project do
+ [app: :app_argv_sample, version: "0.1.0"]
+ end
+
+ def application do
+ send(self(), {:argv, System.argv()})
+ [extra_applications: [:logger]]
+ end
+ end
+
+ describe "rewrite System.argv without file arg" do
+ setup do
+ Mix.Project.pop()
+ Mix.Project.push(AppArgvSample)
+ :ok
+ end
+
+ test "no args" do
+ in_fixture("no_mixfile", fn ->
+ Mix.Tasks.Run.run(["--"])
+ assert_received {:argv, []}
+ end)
+ end
+
+ test "multiple args" do
+ in_fixture("no_mixfile", fn ->
+ Mix.Tasks.Run.run(["--", "foo", "bar"])
+ assert_received {:argv, ["foo", "bar"]}
+ end)
+ end
+
+ test "with opts" do
+ in_fixture("no_mixfile", fn ->
+ Mix.Tasks.Run.run(["--no-start", "--", "foo", "bar"])
+ assert_received {:argv, ["foo", "bar"]}
+ end)
+ end
+
+ test "with eval" do
+ in_fixture("no_mixfile", fn ->
+ expr = "send self(), {:test, :argv}"
+ Mix.Tasks.Run.run(["-e", expr, "--", "foo"])
+ assert_received {:argv, ["foo"]}
+ assert_received {:test, :argv}
+ end)
+ end
+ end
end