diff --git a/codespell_lib/_codespell.py b/codespell_lib/_codespell.py index b5e458fffb..b98cdd81f2 100644 --- a/codespell_lib/_codespell.py +++ b/codespell_lib/_codespell.py @@ -118,6 +118,7 @@ EX_OK = 0 EX_USAGE = 64 EX_DATAERR = 65 +EX_CONFIG = 78 # OPTIONS: # @@ -586,7 +587,7 @@ def parse_options( used_cfg_files.append(cfg_file) # Use config files - config.read(cfg_files) + config.read(used_cfg_files) if config.has_section("codespell"): # Build a "fake" argv list using option name and value. cfg_args = [] @@ -1021,7 +1022,14 @@ def _script_main() -> int: def main(*args: str) -> int: """Contains flow control""" - options, parser, used_cfg_files = parse_options(args) + try: + options, parser, used_cfg_files = parse_options(args) + except configparser.Error as e: + print( + f"ERROR: ill-formed config file: {e.message}", + file=sys.stderr, + ) + return EX_CONFIG # Report used config files if not options.quiet_level & QuietLevels.CONFIG_FILES: diff --git a/codespell_lib/tests/test_basic.py b/codespell_lib/tests/test_basic.py index 18aeca2b51..8a20f0a7e1 100644 --- a/codespell_lib/tests/test_basic.py +++ b/codespell_lib/tests/test_basic.py @@ -13,7 +13,13 @@ import pytest import codespell_lib as cs_ -from codespell_lib._codespell import EX_DATAERR, EX_OK, EX_USAGE, uri_regex_def +from codespell_lib._codespell import ( + EX_CONFIG, + EX_DATAERR, + EX_OK, + EX_USAGE, + uri_regex_def, +) def test_constants() -> None: @@ -21,6 +27,7 @@ def test_constants() -> None: assert EX_OK == 0 assert EX_USAGE == 64 assert EX_DATAERR == 65 + assert EX_CONFIG == 78 class MainWrapper: @@ -42,7 +49,7 @@ def main( assert frame is not None capsys = frame.f_locals["capsys"] stdout, stderr = capsys.readouterr() - assert code in (EX_OK, EX_USAGE, EX_DATAERR) + assert code in (EX_OK, EX_USAGE, EX_DATAERR, EX_CONFIG) if code == EX_DATAERR: # have some misspellings code = int(stderr.split("\n")[-2]) elif code == EX_OK and count: @@ -1028,7 +1035,7 @@ def test_uri_regex_def() -> None: assert not uri_regex.findall(boilerplate % uri), uri -def test_quiet_option_32( +def test_quiet_level_32( tmp_path: Path, tmpdir: pytest.TempPathFactory, capsys: pytest.CaptureFixture[str], @@ -1057,6 +1064,27 @@ def test_quiet_option_32( assert "setup.cfg" in stdout +def test_ill_formed_ini_config_file( + tmp_path: Path, + tmpdir: pytest.TempPathFactory, + capsys: pytest.CaptureFixture[str], +) -> None: + d = tmp_path / "files" + d.mkdir() + conf = str(tmp_path / "setup.cfg") + with open(conf, "w") as f: + # It should contain but lacks a section. + f.write("foobar =\n") + args = ("--config", conf) + + # Should not raise a configparser.Error exception. + result = cs.main(str(d), *args, std=True) + assert isinstance(result, tuple) + code, _, stderr = result + assert code == 78 + assert "ill-formed config file" in stderr + + @pytest.mark.parametrize("kind", ("toml", "cfg")) def test_config_toml( tmp_path: Path, diff --git a/pyproject.toml b/pyproject.toml index 0d437acac7..3574d5e25c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -143,6 +143,6 @@ max-complexity = 45 [tool.ruff.pylint] allow-magic-value-types = ["bytes", "int", "str",] max-args = 12 -max-branches = 48 -max-returns = 10 +max-branches = 49 +max-returns = 11 max-statements = 111