From 27d8ea5dfd280822e14305d24d95815be176bc8f Mon Sep 17 00:00:00 2001 From: Antti Kaihola <13725+akaihola@users.noreply.github.com> Date: Mon, 29 Jul 2024 20:03:17 +0300 Subject: [PATCH 1/4] Stop with exit code 3 on command line parsing fail --- CHANGES.rst | 2 ++ src/darkgraylib/command_line.py | 28 ++++++++++++++++++---- src/darkgraylib/tests/test_command_line.py | 12 ++++++++++ 3 files changed, 38 insertions(+), 4 deletions(-) diff --git a/CHANGES.rst b/CHANGES.rst index 4bb2cadf..fd5c157b 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -5,6 +5,8 @@ These features will be included in the next release: Added ----- +- Return exit code 3 if command line parsing fails. We want to reserve 2 for file not + found errors which will make GitHub actions easier. Fixed ----- diff --git a/src/darkgraylib/command_line.py b/src/darkgraylib/command_line.py index 4ee1b879..03e30518 100644 --- a/src/darkgraylib/command_line.py +++ b/src/darkgraylib/command_line.py @@ -3,7 +3,7 @@ from __future__ import annotations import sys -from argparse import SUPPRESS, ArgumentParser, Namespace +from argparse import SUPPRESS, ArgumentError, ArgumentParser, Namespace from functools import partial from typing import Any, Callable, Protocol, TypeVar @@ -25,6 +25,11 @@ ) from darkgraylib.version import __version__ +EXIT_CODE_FILE_NOT_FOUND = 2 +EXIT_CODE_CMDLINE_ERROR = 3 +EXIT_CODE_DEPENDENCY = 4 +EXIT_CODE_UNKNOWN = 123 + def make_argument_parser( require_src: bool, @@ -119,6 +124,21 @@ def __call__(self, require_src: bool) -> ArgumentParser: ... +def parse_args(parser: ArgumentParser, argv: list[str]) -> Namespace: + """Parse command line arguments, exit with exit code 3 on error. + + :param parser: The argument parser object + :param argv: Command line to parse + :return: The parsed command line arguments + + """ + try: + return parser.parse_args(argv) + except SystemExit as exc_info: + exit_code, = exc_info.args + sys.exit(EXIT_CODE_CMDLINE_ERROR if exit_code == 2 else exit_code) + + def parse_command_line( argument_parser_factory: ArgumentParserFactory, argv: list[str] | None, @@ -154,7 +174,7 @@ def parse_command_line( # 1. Parse the paths of files/directories to process into `args.src`, and the config # file path into `args.config`. parser_for_srcs = argument_parser_factory(require_src=False) - args = parser_for_srcs.parse_args(argv) + args = parse_args(parser_for_srcs, argv) # 2. Locate `pyproject.toml` based on the `-c`/`--config` command line option, or # if it's not provided, based on the paths to process, or in the current @@ -172,14 +192,14 @@ def parse_command_line( # based on the configuration file and the command line options for all options # except `src` (the list of files to process). parser_for_srcs.set_defaults(**config) - args = parser_for_srcs.parse_args(argv) + args = parse_args(parser_for_srcs, argv) # 5. Make sure an error for missing file/directory paths is thrown if we're not # running in stdin mode and no file/directory is configured in `pyproject.toml`. if args.stdin_filename is None and not config.get("src"): parser = argument_parser_factory(require_src=True) parser.set_defaults(**config) - args = parser.parse_args(argv) + args = parse_args(parser, argv) # Make sure there aren't invalid option combinations after merging configuration and # command line options. diff --git a/src/darkgraylib/tests/test_command_line.py b/src/darkgraylib/tests/test_command_line.py index 9991f71a..99e1637e 100644 --- a/src/darkgraylib/tests/test_command_line.py +++ b/src/darkgraylib/tests/test_command_line.py @@ -359,3 +359,15 @@ def test_parse_command_line_load_config_hook_called(tmp_path, monkeypatch): ) hook_mock.assert_called_once_with({"revision": "main"}) + + +def test_parse_command_line_invalid_arguments(capsys): + """Test that parse_command_line exits with code 3 for invalid arguments.""" + with pytest.raises(SystemExit) as excinfo: + parse_command_line( + make_test_argument_parser, ["--invalid-option"], "darkgraylib", BaseConfig + ) + + assert excinfo.value.code == 3 + captured = capsys.readouterr() + assert "error: unrecognized arguments: --invalid-option" in captured.err From 3e4e34217d903b73e680d0e7bcf701399d89251c Mon Sep 17 00:00:00 2001 From: Antti Kaihola <13725+akaihola@users.noreply.github.com> Date: Wed, 31 Jul 2024 18:41:22 +0300 Subject: [PATCH 2/4] Improve change log entry --- CHANGES.rst | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGES.rst b/CHANGES.rst index fd5c157b..09f0a3d2 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -6,7 +6,8 @@ These features will be included in the next release: Added ----- - Return exit code 3 if command line parsing fails. We want to reserve 2 for file not - found errors which will make GitHub actions easier. + found errors which will make GitHub actions easier. Also define constants for various + exit codes to be used in Darkgraylib and applications which use it. Fixed ----- From d69e28ca675628b4ce2508de9bb705411d5eb3b0 Mon Sep 17 00:00:00 2001 From: Antti Kaihola <13725+akaihola@users.noreply.github.com> Date: Wed, 31 Jul 2024 18:41:30 +0300 Subject: [PATCH 3/4] Drop unused import --- src/darkgraylib/command_line.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/darkgraylib/command_line.py b/src/darkgraylib/command_line.py index 03e30518..f5de764c 100644 --- a/src/darkgraylib/command_line.py +++ b/src/darkgraylib/command_line.py @@ -3,7 +3,7 @@ from __future__ import annotations import sys -from argparse import SUPPRESS, ArgumentError, ArgumentParser, Namespace +from argparse import SUPPRESS, ArgumentParser, Namespace from functools import partial from typing import Any, Callable, Protocol, TypeVar From 33f44dc45cdd48d63d78777bde01bef948c8625d Mon Sep 17 00:00:00 2001 From: Antti Kaihola <13725+akaihola@users.noreply.github.com> Date: Wed, 31 Jul 2024 18:41:39 +0300 Subject: [PATCH 4/4] Better handling of `SystemExit` arguments --- src/darkgraylib/command_line.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/darkgraylib/command_line.py b/src/darkgraylib/command_line.py index f5de764c..f3236584 100644 --- a/src/darkgraylib/command_line.py +++ b/src/darkgraylib/command_line.py @@ -135,8 +135,11 @@ def parse_args(parser: ArgumentParser, argv: list[str]) -> Namespace: try: return parser.parse_args(argv) except SystemExit as exc_info: - exit_code, = exc_info.args - sys.exit(EXIT_CODE_CMDLINE_ERROR if exit_code == 2 else exit_code) + if exc_info.args == (2,): + # Change all exceptions from argparse to exit code 3 (cmdline error) + sys.exit(EXIT_CODE_CMDLINE_ERROR) + # For other exit codes, exit with the original code + raise def parse_command_line(