diff --git a/tests/test_config.py b/tests/test_config.py index f7d3e5cd..494433de 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -13,6 +13,7 @@ _parse_args, _parse_toml, make_config, + InputError, ) @@ -173,7 +174,7 @@ def test_invalid_config_options_output(): If the config file contains unknown options we want to abort. """ - with pytest.raises(SystemExit): + with pytest.raises(InputError): _check_input_config({"unknown_key_1": 1}) @@ -185,7 +186,7 @@ def test_incompatible_option_type(key, value): wrong_types = {int, str, list, bool} - {type(value)} for wrong_type in wrong_types: test_value = wrong_type() - with pytest.raises(SystemExit): + with pytest.raises(InputError): _check_input_config({key: test_value}) @@ -193,5 +194,5 @@ def test_missing_paths(): """ If the script is run without any paths, we want to abort. """ - with pytest.raises(SystemExit): + with pytest.raises(InputError): make_config([]) diff --git a/tests/test_script.py b/tests/test_script.py index 62c36e90..5723addb 100644 --- a/tests/test_script.py +++ b/tests/test_script.py @@ -91,3 +91,7 @@ def test_make_whitelist(): assert ( call_vulture(["vulture/", "--make-whitelist"]) == ExitCode.NoDeadCode ) + + +def test_version(): + assert call_vulture(["--version"]) == ExitCode.NoDeadCode diff --git a/vulture/config.py b/vulture/config.py index 3576af5c..4aa0d2d5 100644 --- a/vulture/config.py +++ b/vulture/config.py @@ -4,12 +4,10 @@ """ import argparse import pathlib -import sys import toml from .version import __version__ -from vulture.utils import ExitCode #: Possible configuration options and their respective defaults DEFAULTS = { @@ -24,19 +22,24 @@ } +class InputError(Exception): + def __init__(self, message): + self.message = message + + def _check_input_config(data): """ Checks the types of the values in *data* against the expected types of - config-values. If a value is of the wrong type it will raise a SystemExit. + config-values. If a value has the wrong type, raise an InputError. """ for key, value in data.items(): if key not in DEFAULTS: - sys.exit(f"Unknown configuration key: {key}") + raise InputError(f"Unknown configuration key: {key}") # The linter suggests to use "isinstance" here but this fails to # detect the difference between `int` and `bool`. if type(value) is not type(DEFAULTS[key]): # noqa: E721 expected_type = type(DEFAULTS[key]).__name__ - sys.exit(f"Data type for {key} must be {expected_type!r}") + raise InputError(f"Data type for {key} must be {expected_type!r}") def _check_output_config(config): @@ -44,10 +47,10 @@ def _check_output_config(config): Run sanity checks on the generated config after all parsing and preprocessing is done. - Exit the application if an error is encountered. + Raise InputError if an error is encountered. """ if not config["paths"]: - sys.exit("Please pass at least one file or directory") + raise InputError("Please pass at least one file or directory") def _parse_toml(infile): @@ -177,6 +180,10 @@ def make_config(argv=None, tomlfile=None): auto-detect an existing ``pyproject.toml`` file and exists solely for unit-testing. """ + + # Parse CLI first to skip sanity checks when --version or --help is given. + cli_config = _parse_args(argv) + # If we loaded data from a TOML file, we want to print this out on stdout # in verbose mode so we need to keep the value around. detected_toml_path = "" @@ -193,11 +200,6 @@ def make_config(argv=None, tomlfile=None): else: config = {} - try: - cli_config = _parse_args(argv) - except SystemExit as e: - raise SystemExit(ExitCode.InvalidCmdlineArguments) from e - # Overwrite TOML options with CLI options, if given. config.update(cli_config) diff --git a/vulture/core.py b/vulture/core.py index 04166358..4eeb8eee 100644 --- a/vulture/core.py +++ b/vulture/core.py @@ -9,7 +9,7 @@ from vulture import lines from vulture import noqa from vulture import utils -from vulture.config import make_config +from vulture.config import InputError, make_config from vulture.utils import ExitCode @@ -727,7 +727,12 @@ def generic_visit(self, node): def main(): - config = make_config() + try: + config = make_config() + except InputError as e: + print(e, file=sys.stderr) + sys.exit(ExitCode.InvalidCmdlineArguments) + vulture = Vulture( verbose=config["verbose"], ignore_names=config["ignore_names"],