Skip to content

Commit 3918fa8

Browse files
Options accepting a file should accept lists of files
The rationale is that these options readily accept multiple files from the command line, because they can be specified multiple times. However, duplicate option keys are invalid in an INI config file. Our alternative here is to accept multiple comma-separated values for each option key, in addition to mulitple occurrences of the same option key.
1 parent 742729a commit 3918fa8

File tree

3 files changed

+56
-30
lines changed

3 files changed

+56
-30
lines changed

codespell_lib/_codespell.py

Lines changed: 34 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -390,10 +390,9 @@ def parse_options(
390390
"-D",
391391
"--dictionary",
392392
action="append",
393-
help="custom dictionary file that contains spelling "
394-
"corrections. If this flag is not specified or "
395-
'equals "-" then the default dictionary is used. '
396-
"This option can be specified multiple times.",
393+
help="comma-separated list of custom dictionary files that "
394+
"contain spelling corrections. If this flag is not specified "
395+
'or equals "-" then the default dictionary is used.',
397396
)
398397
builtin_opts = "\n- ".join(
399398
[""] + [f"{d[0]!r} {d[1]}" for d in _builtin_dictionaries]
@@ -423,26 +422,26 @@ def parse_options(
423422
"-I",
424423
"--ignore-words",
425424
action="append",
426-
metavar="FILE",
427-
help="file that contains words that will be ignored "
428-
"by codespell. File must contain 1 word per line."
429-
" Words are case sensitive based on how they are "
430-
"written in the dictionary file",
425+
metavar="FILES",
426+
help="comma-separated list of files that contain "
427+
"words to be ignored by codespell. Files must contain "
428+
"1 word per line. Words are case sensitive based on "
429+
"how they are written in the dictionary file.",
431430
)
432431
parser.add_argument(
433432
"-L",
434433
"--ignore-words-list",
435434
action="append",
436435
metavar="WORDS",
437-
help="comma separated list of words to be ignored "
436+
help="comma-separated list of words to be ignored "
438437
"by codespell. Words are case sensitive based on "
439-
"how they are written in the dictionary file",
438+
"how they are written in the dictionary file.",
440439
)
441440
parser.add_argument(
442441
"--uri-ignore-words-list",
443442
action="append",
444443
metavar="WORDS",
445-
help="comma separated list of words to be ignored "
444+
help="comma-separated list of words to be ignored "
446445
"by codespell in URIs and emails only. Words are "
447446
"case sensitive based on how they are written in "
448447
'the dictionary file. If set to "*", all '
@@ -494,11 +493,13 @@ def parse_options(
494493
parser.add_argument(
495494
"-x",
496495
"--exclude-file",
496+
action="append",
497497
type=str,
498-
metavar="FILE",
499-
help="ignore whole lines that match those "
500-
"in the file FILE. The lines in FILE "
501-
"should match the to-be-excluded lines exactly",
498+
metavar="FILES",
499+
help="ignore whole lines that match those in "
500+
"the comma-separated list of files EXCLUDE. "
501+
"The lines in these files should match the "
502+
"to-be-excluded lines exactly",
502503
)
503504

504505
parser.add_argument(
@@ -1166,20 +1167,22 @@ def main(*args: str) -> int:
11661167
else:
11671168
ignore_word_regex = None
11681169

1169-
ignore_words_files = options.ignore_words or []
11701170
ignore_words, ignore_words_cased = parse_ignore_words_option(
11711171
options.ignore_words_list
11721172
)
1173-
1174-
for ignore_words_file in ignore_words_files:
1175-
if not os.path.isfile(ignore_words_file):
1176-
print(
1177-
f"ERROR: cannot find ignore-words file: {ignore_words_file}",
1178-
file=sys.stderr,
1179-
)
1180-
parser.print_help()
1181-
return EX_USAGE
1182-
build_ignore_words(ignore_words_file, ignore_words, ignore_words_cased)
1173+
if options.ignore_words:
1174+
ignore_words_files = flatten_clean_comma_separated_arguments(
1175+
options.ignore_words
1176+
)
1177+
for ignore_words_file in ignore_words_files:
1178+
if not os.path.isfile(ignore_words_file):
1179+
print(
1180+
f"ERROR: cannot find ignore-words file: {ignore_words_file}",
1181+
file=sys.stderr,
1182+
)
1183+
parser.print_help()
1184+
return EX_USAGE
1185+
build_ignore_words(ignore_words_file, ignore_words, ignore_words_cased)
11831186

11841187
uri_regex = options.uri_regex or uri_regex_def
11851188
try:
@@ -1196,7 +1199,7 @@ def main(*args: str) -> int:
11961199
itertools.chain(*parse_ignore_words_option(options.uri_ignore_words_list))
11971200
)
11981201

1199-
dictionaries = options.dictionary or ["-"]
1202+
dictionaries = flatten_clean_comma_separated_arguments(options.dictionary or ["-"])
12001203

12011204
use_dictionaries = []
12021205
for dictionary in dictionaries:
@@ -1258,7 +1261,9 @@ def main(*args: str) -> int:
12581261

12591262
exclude_lines: Set[str] = set()
12601263
if options.exclude_file:
1261-
build_exclude_hashes(options.exclude_file, exclude_lines)
1264+
exclude_files = flatten_clean_comma_separated_arguments(options.exclude_file)
1265+
for exclude_file in exclude_files:
1266+
build_exclude_hashes(exclude_file, exclude_lines)
12621267

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

codespell_lib/tests/test_basic.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -342,6 +342,20 @@ def test_ignore_dictionary(
342342
fname = tmp_path / "ignore.txt"
343343
fname.write_text("abandonned\nabilty\r\nackward")
344344
assert cs.main("-I", fname, bad_name) == 1
345+
# missing file in ignore list
346+
fname_missing = tmp_path / "missing.txt"
347+
result = cs.main("-I", fname_missing, bad_name, std=True)
348+
assert isinstance(result, tuple)
349+
code, _, stderr = result
350+
assert code == EX_USAGE
351+
assert "ERROR:" in stderr
352+
# comma-separated list of files
353+
fname_dummy1 = tmp_path / "dummy1.txt"
354+
fname_dummy1.touch()
355+
fname_dummy2 = tmp_path / "dummy2.txt"
356+
fname_dummy2.touch()
357+
assert cs.main("-I", fname_dummy1, "-I", fname, "-I", fname_dummy2, bad_name) == 1
358+
assert cs.main("-I", f"'{fname_dummy1},{fname},{fname_dummy2}'", bad_name) == 1
345359

346360

347361
def test_ignore_words_with_cases(
@@ -495,6 +509,13 @@ def test_exclude_file(
495509
)
496510
assert cs.main(bad_name) == 18
497511
assert cs.main("-x", fname, bad_name) == 1
512+
# comma-separated list of files
513+
fname_dummy1 = tmp_path / "dummy1.txt"
514+
fname_dummy1.touch()
515+
fname_dummy2 = tmp_path / "dummy2.txt"
516+
fname_dummy2.touch()
517+
assert cs.main("-x", fname_dummy1, "-x", fname, "-x", fname_dummy2, bad_name) == 1
518+
assert cs.main("-x", f"'{fname_dummy1},{fname},{fname_dummy2}'", bad_name) == 1
498519

499520

500521
def test_encoding(

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,6 @@ max-complexity = 45
169169
[tool.ruff.lint.pylint]
170170
allow-magic-value-types = ["bytes", "int", "str",]
171171
max-args = 13
172-
max-branches = 49
172+
max-branches = 51
173173
max-returns = 11
174174
max-statements = 119

0 commit comments

Comments
 (0)