Skip to content

Commit

Permalink
Handle multiline options
Browse files Browse the repository at this point in the history
Clean up options expecting lists before using them, as they may contain
newlines.

Examples:
* Enclosing command-line arguments in quotes may introduce newlines in
  option values:
  	$ codespell -S "A, B,
  	>               C, D, E"
* INI files may contain multiline values:
  	[codespell]
  	skip = A, B,
  	       C, D, E,

In all the above cases, the option parsing mechanism keeps the newlines
(and spaces). We need to clean up, by splitting on commas and stripping
each resulting item from newlines (and spaces).
  • Loading branch information
DimitriPapadopoulos committed Apr 10, 2024
1 parent 69c0f00 commit 9c29387
Show file tree
Hide file tree
Showing 2 changed files with 45 additions and 31 deletions.
36 changes: 24 additions & 12 deletions codespell_lib/_codespell.py
Original file line number Diff line number Diff line change
Expand Up @@ -160,19 +160,15 @@ class QuietLevels:


class GlobMatch:
def __init__(self, pattern: Optional[str]) -> None:
self.pattern_list: Optional[List[str]]
if pattern:
# Pattern might be a list of comma-delimited strings
self.pattern_list = ",".join(pattern).split(",")
else:
self.pattern_list = None
def __init__(self, pattern: Optional[List[str]]) -> None:
self.pattern_list: Optional[List[str]] = pattern

def match(self, filename: str) -> bool:
if self.pattern_list is None:
return False

return any(fnmatch.fnmatch(filename, p) for p in self.pattern_list)
return (
any(fnmatch.fnmatch(filename, p) for p in self.pattern_list)
if self.pattern_list
else False
)


class Misspelling:
Expand Down Expand Up @@ -1109,6 +1105,22 @@ def parse_file(
return bad_count


def flatten_clean_comma_separated_arguments(
arguments: Optional[List[str]],
) -> Optional[List[str]]:
"""
>>> flatten_clean_comma_separated_arguments(["a, b ,\n c, d,", "e"])
['a', 'b', 'c', 'd', 'e']
>>> flatten_clean_comma_separated_arguments([])
>>> flatten_clean_comma_separated_arguments(None)
"""
return (
[item.strip() for argument in arguments for item in argument.split(",") if item]
if arguments
else None
)


def _script_main() -> int:
"""Wrap to main() for setuptools."""
return main(*sys.argv[1:])
Expand Down Expand Up @@ -1256,7 +1268,7 @@ def main(*args: str) -> int:

file_opener = FileOpener(options.hard_encoding_detection, options.quiet_level)

glob_match = GlobMatch(options.skip)
glob_match = GlobMatch(flatten_clean_comma_separated_arguments(options.skip))
try:
glob_match.match("/random/path") # does not need a real path
except re.error:
Expand Down
40 changes: 21 additions & 19 deletions codespell_lib/tests/test_basic.py
Original file line number Diff line number Diff line change
Expand Up @@ -573,6 +573,8 @@ def test_ignore(
(subdir / "bad.txt").write_text("abandonned")
assert cs.main(tmp_path) == 2
assert cs.main("--skip=bad*", tmp_path) == 0
assert cs.main("--skip=whatever.txt,bad*,whatelse.txt", tmp_path) == 0
assert cs.main("--skip=whatever.txt,\n bad* ,", tmp_path) == 0
assert cs.main("--skip=*ignoredir*", tmp_path) == 1
assert cs.main("--skip=ignoredir", tmp_path) == 1
assert cs.main("--skip=*ignoredir/bad*", tmp_path) == 1
Expand Down Expand Up @@ -1213,7 +1215,7 @@ def test_ill_formed_ini_config_file(
assert "ill-formed config file" in stderr


@pytest.mark.parametrize("kind", ["cfg", "toml", "toml_list"])
@pytest.mark.parametrize("kind", ["cfg", "cfg_multiline", "toml", "toml_list"])
def test_config_toml(
tmp_path: Path,
capsys: pytest.CaptureFixture[str],
Expand All @@ -1235,44 +1237,44 @@ def test_config_toml(
assert "bad.txt" in stdout
assert "abandonned.txt" in stdout

if kind == "cfg":
if kind.startswith("cfg"):
conffile = tmp_path / "setup.cfg"
args = ("--config", conffile)
conffile.write_text(
"""\
if kind == "cfg":
text = """\
[codespell]
skip = bad.txt, whatever.txt
count =
"""
)
elif kind == "toml":
assert kind == "toml"
else:
assert kind == "cfg_multiline"
text = """\
[codespell]
skip = bad.txt, whatever.txt
count =
"""
conffile.write_text(text)
else:
if sys.version_info < (3, 11):
pytest.importorskip("tomli")
tomlfile = tmp_path / "pyproject.toml"
args = ("--toml", tomlfile)
tomlfile.write_text(
"""\
if kind == "toml":
text = """\
[tool.codespell]
skip = 'bad.txt,whatever.txt'
check-filenames = false
count = true
"""
)
else:
assert kind == "toml_list"
if sys.version_info < (3, 11):
pytest.importorskip("tomli")
tomlfile = tmp_path / "pyproject.toml"
args = ("--toml", tomlfile)
tomlfile.write_text(
"""\
else:
assert kind == "toml_list"
text = """\
[tool.codespell]
skip = ['bad.txt', 'whatever.txt']
check-filenames = false
count = true
"""
)
tomlfile.write_text(text)

# Should pass when skipping bad.txt or abandonned.txt
result = cs.main(d, *args, std=True)
Expand Down

0 comments on commit 9c29387

Please sign in to comment.