diff --git a/README.rst b/README.rst index fdd16cc5e5..a9562e8f2b 100644 --- a/README.rst +++ b/README.rst @@ -229,6 +229,13 @@ instead of these invalid entries: .. _tomli: https://pypi.org/project/tomli/ +Reading arguments from file +--------------------------- + +Additional arguments can be read from a file with ``@PATH``. Arguments are +extracted using ``shlex.split()``. + + pre-commit hook --------------- diff --git a/codespell_lib/_codespell.py b/codespell_lib/_codespell.py index 94ab65d068..288697936c 100644 --- a/codespell_lib/_codespell.py +++ b/codespell_lib/_codespell.py @@ -23,6 +23,7 @@ import itertools import os import re +import shlex import sys import textwrap from collections.abc import Iterable, Sequence @@ -388,7 +389,18 @@ def _supports_ansi_colors() -> bool: def parse_options( args: Sequence[str], ) -> tuple[argparse.Namespace, argparse.ArgumentParser, list[str]]: - parser = argparse.ArgumentParser(formatter_class=NewlineHelpFormatter) + # Split lines read from `@PATH` using shlex.split(), otherwise default + # behaviour is to have one arg per line. See: + # https://docs.python.org/3/library/argparse.html#argparse.ArgumentParser.convert_arg_line_to_args + class ArgumentParser(argparse.ArgumentParser): + def convert_arg_line_to_args(self, arg_line: str) -> list[str]: + return shlex.split(arg_line) + + parser = ArgumentParser( + formatter_class=NewlineHelpFormatter, + fromfile_prefix_chars="@", + epilog="Use @PATH to read additional arguments from file PATH.", + ) parser.set_defaults(colors=_supports_ansi_colors()) parser.add_argument("--version", action="version", version=VERSION) diff --git a/codespell_lib/tests/test_basic.py b/codespell_lib/tests/test_basic.py index d8f97ea3f1..06e09737e3 100644 --- a/codespell_lib/tests/test_basic.py +++ b/codespell_lib/tests/test_basic.py @@ -1458,3 +1458,26 @@ def test_stdin(tmp_path: Path, capsys: pytest.CaptureFixture[str]) -> None: code, stdout, _ = result assert stdout == "1: Thsi ==> This\n" assert code == 1 + + +def test_args_from_file( + tmp_path: Path, + capsys: pytest.CaptureFixture[str], +) -> None: + fname1 = tmp_path / "tmp1" + fname2 = tmp_path / "tmp2" + fname3 = tmp_path / "tmp3" + fname_list = tmp_path / "tmp_list" + fname_list.write_text(f"{fname1} {fname2}\n{fname3}") + fname1.write_text("abandonned\ncode") + fname2.write_text("exmaple\n") + fname3.write_text("abilty\n") + print(f"@{fname_list}") + result = cs.main(f"@{fname_list}", std=True) + assert isinstance(result, tuple) + code, stdout, stderr = result + print(f"{code=} {stdout=} {stderr=}") + assert "tmp1:1: abandonned ==> abandoned\n" in stdout, f"{stdout=}" + assert "tmp2:1: exmaple ==> example\n" in stdout, f"{stdout=}" + assert "tmp3:1: abilty ==> ability\n" in stdout, f"{stdout=}" + assert code == 3, f"{code=}"