File 4674-Report-arguments-in-the-order-given.patch of Package erlang

From c16a0ad7d7de3867149f2d1ac76e0ee4e3bc6caf Mon Sep 17 00:00:00 2001
From: Roger Lipscombe <roger@differentpla.net>
Date: Mon, 11 Dec 2023 14:31:21 +0000
Subject: [PATCH] Report arguments in the order given

If you specify positional arguments at various levels of sub-command,
e.g. globally, then after a command, etc., the usage info listed them in
the wrong order.

This changes fixes that, updates the documentation to be explicit, and
adds a related test to check that the arguments are parsed in the
expected order.

Fixes #7934
---
 lib/stdlib/doc/src/argparse.xml    |  8 +--
 lib/stdlib/src/argparse.erl        |  4 +-
 lib/stdlib/test/argparse_SUITE.erl | 85 +++++++++++++++++++++++-------
 3 files changed, 71 insertions(+), 26 deletions(-)

diff --git a/lib/stdlib/doc/src/argparse.xml b/lib/stdlib/doc/src/argparse.xml
index 20e1f3a721..136e174a8b 100644
--- a/lib/stdlib/doc/src/argparse.xml
+++ b/lib/stdlib/doc/src/argparse.xml
@@ -692,9 +692,10 @@ argparse:run(["2", "-p", "3"], Cmd, #{}).
       <desc>
         <p>Generates help/usage information text for the command
           supplied, or any nested command when <c>command</c>
-          option is specified. Does not provide localisaton.
-          Expects <c>progname</c> to be set, otherwise defaults to
-          return value of <c>init:get_argument(progname)</c>.</p>
+          option is specified. Arguments are displayed in the same order as
+          specified in <c>Command</c>.  Does not provide localisation.  Expects
+          <c>progname</c> to be set, otherwise defaults to return value of
+          <c>init:get_argument(progname)</c>.</p>
       </desc>
     </func>
 
@@ -736,4 +737,3 @@ argparse:run(["2", "-p", "3"], Cmd, #{}).
   </funcs>
 
 </erlref>
-
diff --git a/lib/stdlib/src/argparse.erl b/lib/stdlib/src/argparse.erl
index 69b734a126..8240f784dd 100644
--- a/lib/stdlib/src/argparse.erl
+++ b/lib/stdlib/src/argparse.erl
@@ -1114,11 +1114,11 @@ format_help({ProgName, Root}, Format) ->
 
 %% collects options on the Path, and returns found Command
 collect_options(CmdName, Command, [], Args) ->
-    {CmdName, Command, maps:get(arguments, Command, []) ++ Args};
+    {CmdName, Command, Args ++ maps:get(arguments, Command, [])};
 collect_options(CmdName, Command, [Cmd|Tail], Args) ->
     Sub = maps:get(commands, Command),
     SubCmd = maps:get(Cmd, Sub),
-    collect_options(CmdName ++ " " ++ Cmd, SubCmd, Tail, maps:get(arguments, Command, []) ++ Args).
+    collect_options(CmdName ++ " " ++ Cmd, SubCmd, Tail, Args ++ maps:get(arguments, Command, [])).
 
 %% conditionally adds text and empty lines
 maybe_add(_ToAdd, [], _Element, Template) ->
diff --git a/lib/stdlib/test/argparse_SUITE.erl b/lib/stdlib/test/argparse_SUITE.erl
index a63b8867d2..21a0b455de 100644
--- a/lib/stdlib/test/argparse_SUITE.erl
+++ b/lib/stdlib/test/argparse_SUITE.erl
@@ -45,6 +45,7 @@
     usage/0, usage/1,
     usage_required_args/0, usage_required_args/1,
     usage_template/0, usage_template/1,
+    usage_args_ordering/0, usage_args_ordering/1,
     parser_error_usage/0, parser_error_usage/1,
     command_usage/0, command_usage/1,
     usage_width/0, usage_width/1,
@@ -52,7 +53,8 @@
     validator_exception/0, validator_exception/1,
     validator_exception_format/0, validator_exception_format/1,
 
-    run_handle/0, run_handle/1
+    run_handle/0, run_handle/1,
+    run_args_ordering/0, run_args_ordering/1
 ]).
 
 -include_lib("stdlib/include/assert.hrl").
@@ -70,14 +72,14 @@ groups() ->
             very_short, multi_short, proxy_arguments
         ]},
         {usage, [parallel], [
-            usage, usage_required_args, usage_template,
+            usage, usage_required_args, usage_template, usage_args_ordering,
             parser_error_usage, command_usage, usage_width
         ]},
         {validator, [parallel], [
             validator_exception, validator_exception_format
         ]},
         {run, [parallel], [
-            run_handle
+            run_handle, run_args_ordering
         ]}
     ].
 
@@ -743,10 +745,12 @@ usage() ->
 
 usage(Config) when is_list(Config) ->
     Cmd = ubiq_cmd(),
-    Usage = "Usage:\n  erl start {crawler|doze} [-lrfv] [-s <shard>...] [-z <z>] [-m <more>] [-b <bin>]\n"
-        "      [-g <g>] [-t <t>] ---maybe-req -y <y> --yyy <y> [-u <u>] [-c <choice>]\n"
-        "      [-q <fc>] [-w <ac>] [--unsafe <au>] [--safe <as>] [-foobar <long>] [--force]\n"
-        "      [-i <interval>] [--req <weird>] [--float <float>] <server> [<optpos>]\n\n"
+    Usage = "Usage:\n"
+        "  erl start {crawler|doze} [-rfvl] [--force] [-i <interval>] [--req <weird>]\n"
+        "      [--float <float>] [-s <shard>...] [-z <z>] [-m <more>] [-b <bin>] [-g <g>]\n"
+        "      [-t <t>] ---maybe-req -y <y> --yyy <y> [-u <u>] [-c <choice>] [-q <fc>]\n"
+        "      [-w <ac>] [--unsafe <au>] [--safe <as>] [-foobar <long>] <server> [<optpos>]\n"
+        "\n"
         "Subcommands:\n"
         "  crawler      controls crawler behaviour\n"
         "  doze         dozes a bit\n\n"
@@ -754,6 +758,12 @@ usage(Config) when is_list(Config) ->
         "  server       server to start\n"
         "  optpos       optional positional (int)\n\n"
         "Optional arguments:\n"
+        "  -r           recursive\n"
+        "  -f, --force  force\n"
+        "  -v           verbosity level\n"
+        "  -i           interval set (int >= 1)\n"
+        "  --req        required optional, right?\n"
+        "  --float      floating-point long form argument (float), default: 3.14\n"
         "  -s           initial shards (int)\n"
         "  -z           between (1 <= int <= 10)\n"
         "  -l           maybe lower (int <= 10)\n"
@@ -769,13 +779,7 @@ usage(Config) when is_list(Config) ->
         "  -w           atom choice (choice: one, two)\n"
         "  --unsafe     unsafe atom (atom)\n"
         "  --safe       safe atom (existing atom)\n"
-        "  -foobar      foobaring option\n"
-        "  -r           recursive\n"
-        "  -f, --force  force\n"
-        "  -v           verbosity level\n"
-        "  -i           interval set (int >= 1)\n"
-        "  --req        required optional, right?\n"
-        "  --float      floating-point long form argument (float), default: 3.14\n",
+        "  -foobar      foobaring option\n",
     ?assertEqual(Usage, unicode:characters_to_list(argparse:help(Cmd,
         #{progname => "erl", command => ["start"]}))),
     FullCmd = "Usage:\n  erl"
@@ -793,13 +797,16 @@ usage(Config) when is_list(Config) ->
         "  --float     floating-point long form argument (float), default: 3.14\n",
     ?assertEqual(FullCmd, unicode:characters_to_list(argparse:help(Cmd,
         #{progname => erl}))),
-    CrawlerStatus = "Usage:\n  erl status crawler [-rfv] [---extra <extra>] [--force] [-i <interval>]\n"
-        "      [--req <weird>] [--float <float>]\n\nOptional arguments:\n"
-        "  ---extra    extra option very deep\n  -r          recursive\n"
-        "  -f, --force force\n  -v          verbosity level\n"
+    CrawlerStatus = "Usage:\n  erl status crawler [-rfv] [--force] [-i <interval>] [--req <weird>]\n"
+        "      [--float <float>] [---extra <extra>]\n\n"
+        "Optional arguments:\n"
+        "  -r          recursive\n"
+        "  -f, --force force\n"
+        "  -v          verbosity level\n"
         "  -i          interval set (int >= 1)\n"
         "  --req       required optional, right?\n"
-        "  --float     floating-point long form argument (float), default: 3.14\n",
+        "  --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"]}))),
     ok.
@@ -856,6 +863,30 @@ usage_template(Config) when is_list(Config) ->
         unicode:characters_to_list(argparse:help(CmdISO, #{}))),
     ok.
 
+usage_args_ordering() ->
+    [{doc, "Tests the ordering of arguments in usage text"}].
+
+usage_args_ordering(Config) when is_list(Config) ->
+    Cmd = #{arguments => [
+        #{name => first},
+        #{name => second}
+        ],
+        commands => #{
+            "cmd" => #{arguments => [
+                #{name => third},
+                #{name => fourth}
+            ]}}
+    },
+    ?assertEqual("Usage:\n  " ++ prog() ++ " cmd <first> <second> <third> <fourth>\n"
+        "\n"
+        "Arguments:\n"
+        "  first  first\n"
+        "  second second\n"
+        "  third  third\n"
+        "  fourth fourth\n",
+        unicode:characters_to_list(argparse:help(Cmd, #{command => ["cmd"]}))),
+    ok.
+
 parser_error_usage() ->
     [{doc, "Tests that parser errors have corresponding usage text"}].
 
@@ -1060,4 +1091,18 @@ run_handle(Config) when is_list(Config) ->
         argparse:run(["map", "arg"], #{commands => #{"map" => #{
             handler => {maps, to_list},
             arguments => [#{name => arg}]}}},
-            #{})).
\ No newline at end of file
+            #{})).
+
+run_args_ordering() ->
+    [{doc, "Test that positional arguments are parsed in the correct order"}].
+
+run_args_ordering(Config) when is_list(Config) ->
+    ?assertEqual([{first,"1"},{second,"2"},{third,"3"},{fourth,"4"}],
+        argparse:run(["cmd", "1", "2", "3", "4"],
+            #{arguments => [#{name => first}, #{name => second}],
+            commands => #{
+                "cmd" => #{
+                    handler => {maps, to_list},
+                    arguments => [#{name => third}, #{name => fourth}]}
+            }},
+            #{progname => example})).
-- 
2.35.3

openSUSE Build Service is sponsored by