]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
gh-118131: Command-line interface for the `random` module (#118132)
authorHugo van Kemenade <1324225+hugovk@users.noreply.github.com>
Sun, 5 May 2024 06:30:03 +0000 (08:30 +0200)
committerGitHub <noreply@github.com>
Sun, 5 May 2024 06:30:03 +0000 (06:30 +0000)
Doc/library/cmdline.rst
Doc/library/random.rst
Doc/whatsnew/3.13.rst
Lib/random.py
Lib/test/test_random.py
Misc/NEWS.d/next/Library/2024-04-21-18-55-42.gh-issue-118131.eAT0is.rst [new file with mode: 0644]

index b2379befeffcbab2b09d50cc307ca28305fb1c3f..5174515ffc23edd8f309b8e63de53dffca36fd4f 100644 (file)
@@ -36,6 +36,7 @@ The following modules have a command-line interface.
 * :mod:`pyclbr`
 * :mod:`pydoc`
 * :mod:`quopri`
+* :ref:`random <random-cli>`
 * :mod:`runpy`
 * :ref:`site <site-commandline>`
 * :ref:`sqlite3 <sqlite3-cli>`
index 61798263d61195c912143a8a1fb4a07379832dda..4584bbc40aa07bc9860574474275df170c905f79 100644 (file)
@@ -706,3 +706,83 @@ positive unnormalized float and is equal to ``math.ulp(0.0)``.)
    <https://allendowney.com/research/rand/downey07randfloat.pdf>`_ a
    paper by Allen B. Downey describing ways to generate more
    fine-grained floats than normally generated by :func:`.random`.
+
+.. _random-cli:
+
+Command-line usage
+------------------
+
+.. versionadded:: 3.13
+
+The :mod:`!random` module can be executed from the command line.
+
+.. code-block:: sh
+
+   python -m random [-h] [-c CHOICE [CHOICE ...] | -i N | -f N] [input ...]
+
+The following options are accepted:
+
+.. program:: random
+
+.. option:: -h, --help
+
+   Show the help message and exit.
+
+.. option:: -c CHOICE [CHOICE ...]
+            --choice CHOICE [CHOICE ...]
+
+   Print a random choice, using :meth:`choice`.
+
+.. option:: -i <N>
+            --integer <N>
+
+   Print a random integer between 1 and N inclusive, using :meth:`randint`.
+
+.. option:: -f <N>
+            --float <N>
+
+   Print a random floating point number between 1 and N inclusive,
+   using :meth:`uniform`.
+
+If no options are given, the output depends on the input:
+
+* String or multiple: same as :option:`--choice`.
+* Integer: same as :option:`--integer`.
+* Float: same as :option:`--float`.
+
+.. _random-cli-example:
+
+Command-line example
+--------------------
+
+Here are some examples of the :mod:`!random` command-line interface:
+
+.. code-block:: console
+
+   $ # Choose one at random
+   $ python -m random egg bacon sausage spam "Lobster Thermidor aux crevettes with a Mornay sauce"
+   Lobster Thermidor aux crevettes with a Mornay sauce
+
+   $ # Random integer
+   $ python -m random 6
+   6
+
+   $ # Random floating-point number
+   $ python -m random 1.8
+   1.7080016272295635
+
+   $ # With explicit arguments
+   $ python  -m random --choice egg bacon sausage spam "Lobster Thermidor aux crevettes with a Mornay sauce"
+   egg
+
+   $ python -m random --integer 6
+   3
+
+   $ python -m random --float 1.8
+   1.5666339105010318
+
+   $ python -m random --integer 6
+   5
+
+   $ python -m random --float 6
+   3.1942323316565915
index a5f5ba41ebf66eb7f2c4a8a31f34813e000c6084..27a1aeaab018dbbddb70d90f806a0cd312c52cbf 100644 (file)
@@ -722,6 +722,12 @@ queue
   termination.
   (Contributed by Laurie Opperman and Yves Duprat in :gh:`104750`.)
 
+random
+------
+
+* Add a :ref:`command-line interface <random-cli>`.
+  (Contributed by Hugo van Kemenade in :gh:`54321`.)
+
 re
 --
 * Rename :exc:`!re.error` to :exc:`re.PatternError` for improved clarity.
index 875beb2f8cf41c7e818c5d1d50c48067f4ee034f..bcc11c7cd3c208382cbca56908b766d10084a401 100644 (file)
@@ -996,5 +996,75 @@ if hasattr(_os, "fork"):
     _os.register_at_fork(after_in_child=_inst.seed)
 
 
+# ------------------------------------------------------
+# -------------- command-line interface ----------------
+
+
+def _parse_args(arg_list: list[str] | None):
+    import argparse
+    parser = argparse.ArgumentParser(
+        formatter_class=argparse.RawTextHelpFormatter)
+    group = parser.add_mutually_exclusive_group()
+    group.add_argument(
+        "-c", "--choice", nargs="+",
+        help="print a random choice")
+    group.add_argument(
+        "-i", "--integer", type=int, metavar="N",
+        help="print a random integer between 1 and N inclusive")
+    group.add_argument(
+        "-f", "--float", type=float, metavar="N",
+        help="print a random floating point number between 1 and N inclusive")
+    group.add_argument(
+        "--test", type=int, const=10_000, nargs="?",
+        help=argparse.SUPPRESS)
+    parser.add_argument("input", nargs="*",
+                        help="""\
+if no options given, output depends on the input
+    string or multiple: same as --choice
+    integer: same as --integer
+    float: same as --float""")
+    args = parser.parse_args(arg_list)
+    return args, parser.format_help()
+
+
+def main(arg_list: list[str] | None = None) -> int | str:
+    args, help_text = _parse_args(arg_list)
+
+    # Explicit arguments
+    if args.choice:
+        return choice(args.choice)
+
+    if args.integer is not None:
+        return randint(1, args.integer)
+
+    if args.float is not None:
+        return uniform(1, args.float)
+
+    if args.test:
+        _test(args.test)
+        return ""
+
+    # No explicit argument, select based on input
+    if len(args.input) == 1:
+        val = args.input[0]
+        try:
+            # Is it an integer?
+            val = int(val)
+            return randint(1, val)
+        except ValueError:
+            try:
+                # Is it a float?
+                val = float(val)
+                return uniform(1, val)
+            except ValueError:
+                # Split in case of space-separated string: "a b c"
+                return choice(val.split())
+
+    if len(args.input) >= 2:
+        return choice(args.input)
+
+    return help_text
+
+
 if __name__ == '__main__':
-    _test()
+    print(main())
index b1e4ef4197d130896028364d4aafe64901e4b675..9a44ab1768656aadcd2f8e104f84f2636c4bc918 100644 (file)
@@ -4,6 +4,7 @@ import random
 import os
 import time
 import pickle
+import shlex
 import warnings
 import test.support
 
@@ -1397,5 +1398,47 @@ class TestModule(unittest.TestCase):
             support.wait_process(pid, exitcode=0)
 
 
+class CommandLineTest(unittest.TestCase):
+    def test_parse_args(self):
+        args, help_text = random._parse_args(shlex.split("--choice a b c"))
+        self.assertEqual(args.choice, ["a", "b", "c"])
+        self.assertTrue(help_text.startswith("usage: "))
+
+        args, help_text = random._parse_args(shlex.split("--integer 5"))
+        self.assertEqual(args.integer, 5)
+        self.assertTrue(help_text.startswith("usage: "))
+
+        args, help_text = random._parse_args(shlex.split("--float 2.5"))
+        self.assertEqual(args.float, 2.5)
+        self.assertTrue(help_text.startswith("usage: "))
+
+        args, help_text = random._parse_args(shlex.split("a b c"))
+        self.assertEqual(args.input, ["a", "b", "c"])
+        self.assertTrue(help_text.startswith("usage: "))
+
+        args, help_text = random._parse_args(shlex.split("5"))
+        self.assertEqual(args.input, ["5"])
+        self.assertTrue(help_text.startswith("usage: "))
+
+        args, help_text = random._parse_args(shlex.split("2.5"))
+        self.assertEqual(args.input, ["2.5"])
+        self.assertTrue(help_text.startswith("usage: "))
+
+    def test_main(self):
+        for command, expected in [
+            ("--choice a b c", "b"),
+            ('"a b c"', "b"),
+            ("a b c", "b"),
+            ("--choice 'a a' 'b b' 'c c'", "b b"),
+            ("'a a' 'b b' 'c c'", "b b"),
+            ("--integer 5", 4),
+            ("5", 4),
+            ("--float 2.5", 2.266632777287572),
+            ("2.5", 2.266632777287572),
+        ]:
+            random.seed(0)
+            self.assertEqual(random.main(shlex.split(command)), expected)
+
+
 if __name__ == "__main__":
     unittest.main()
diff --git a/Misc/NEWS.d/next/Library/2024-04-21-18-55-42.gh-issue-118131.eAT0is.rst b/Misc/NEWS.d/next/Library/2024-04-21-18-55-42.gh-issue-118131.eAT0is.rst
new file mode 100644 (file)
index 0000000..83ed66c
--- /dev/null
@@ -0,0 +1,2 @@
+Add command-line interface for the :mod:`random` module. Patch by Hugo van
+Kemenade.