File 6141-argparse-Accept-the-progname-in-the-command-path.patch of Package erlang
From 04d4c9268fbe1c5eb30d70b3ce3dc43d6db563e7 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jean-S=C3=A9bastien=20P=C3=A9dron?=
<jean-sebastien.pedron@dumbbell.fr>
Date: Mon, 9 Dec 2024 12:15:09 +0100
Subject: [PATCH] argparse: Accept the progname in the command path
[Why]
The documentation of `cmd_path()` states that it always starts with the
progname. Indeed, `argparse:parse/{1,2}` returns a command path with the
progname at the beginning.
However, if `argparse:help/2` is called with `#{command => CmdPath}` in
the parser options with `CmdPath` starting with the progname, it crashes
with a `badkey` exception because it expects the command path to only
contain commands and sub-commands.
[How]
The patch drops the first element of the command path if it matches the
progname.
This adds support for the correct values of `CmdPath` while retaining
backward compatibility with callers that dropped the progname themselves
to work around the issue.
---
lib/stdlib/src/argparse.erl | 16 +++++++++++++++-
lib/stdlib/test/argparse_SUITE.erl | 9 ++++++---
2 files changed, 21 insertions(+), 4 deletions(-)
diff --git a/lib/stdlib/src/argparse.erl b/lib/stdlib/src/argparse.erl
index 3536bec696..c39302ef03 100644
--- a/lib/stdlib/src/argparse.erl
+++ b/lib/stdlib/src/argparse.erl
@@ -1592,7 +1592,21 @@ is_valid_command_help(_) ->
format_help({ProgName, Root}, Format) ->
Prefix = hd(maps:get(prefixes, Format, [$-])),
- Nested = maps:get(command, Format, []),
+ Nested0 = maps:get(command, Format, [ProgName]),
+ %% The command path should always start with the progname, that's why it is
+ %% dropped here to keep the command and sub-commands only.
+ %%
+ %% However, earlier versions of this function did not drop that progname.
+ %% The function thus used to crash with a badkey excception if the caller
+ %% passed the `CmdPath' returned by `parse/2' to this function's `command'.
+ %% Therefore, to keep backward compatibility, if the command path does not
+ %% start with the progname, it uses the entire list untouched.
+ Nested = case Nested0 of
+ [ProgName | Tail] ->
+ Tail;
+ _ ->
+ Nested0
+ end,
%% descent into commands collecting all options on the way
{_CmdName, Cmd, AllArgs} = collect_options(ProgName, Root, Nested, []),
%% split arguments into Flags, Options, Positional, and create help lines
diff --git a/lib/stdlib/test/argparse_SUITE.erl b/lib/stdlib/test/argparse_SUITE.erl
index f0cd7c39f7..3c67a6d0c8 100644
--- a/lib/stdlib/test/argparse_SUITE.erl
+++ b/lib/stdlib/test/argparse_SUITE.erl
@@ -782,6 +782,9 @@ usage(Config) when is_list(Config) ->
" --unsafe unsafe atom (atom)\n"
" --safe safe atom (existing atom)\n"
" -foobar foobaring option\n",
+ ?assertEqual(Usage, unicode:characters_to_list(argparse:help(Cmd,
+ #{progname => "erl", command => ["erl", "start"]}))),
+ %% Same assertion for the backward-compatible way of calling `argparse:help/2'.
?assertEqual(Usage, unicode:characters_to_list(argparse:help(Cmd,
#{progname => "erl", command => ["start"]}))),
FullCmd = "Usage:\n erl"
@@ -812,7 +815,7 @@ usage(Config) when is_list(Config) ->
" --float floating-point long form argument (float), default: 3.14\n"
" ---extra extra option very deep\n",
?assertEqual(CrawlerStatus, unicode:characters_to_list(argparse:help(Cmd,
- #{progname => "erl", command => ["status", "crawler"]}))),
+ #{progname => "erl", command => ["erl", "status", "crawler"]}))),
ok.
usage_help_binary() ->
@@ -821,7 +824,7 @@ usage_required_args() ->
usage_required_args(Config) when is_list(Config) ->
Cmd = #{commands => #{"test" => #{arguments => [#{name => required, required => true, long => "-req"}]}}},
Expected = "Usage:\n " ++ prog() ++ " test --req <required>\n\nOptional arguments:\n --req required\n",
- ?assertEqual(Expected, unicode:characters_to_list(argparse:help(Cmd, #{command => ["test"]}))).
+ ?assertEqual(Expected, unicode:characters_to_list(argparse:help(Cmd, #{command => ["erl", "test"]}))).
usage_template() ->
[{doc, "Tests templates in help/usage"}].
@@ -888,7 +891,7 @@ usage_args_ordering(Config) when is_list(Config) ->
" second second\n"
" third third\n"
" fourth fourth\n",
- unicode:characters_to_list(argparse:help(Cmd, #{command => ["cmd"]}))),
+ unicode:characters_to_list(argparse:help(Cmd, #{command => ["erl", "cmd"]}))),
ok.
parser_error_usage() ->
--
2.43.0