From 68bcddb029f0a04e2b3893eade753b5862617216 Mon Sep 17 00:00:00 2001 From: Dimitri Papadopoulos <3234522+DimitriPapadopoulos@users.noreply.github.com> Date: Fri, 3 Mar 2023 21:31:47 +0100 Subject: [PATCH] Options that expect a file should accept lists of files too 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. The alternative is to accept multiple values for each occurrence of an option key. --- codespell_lib/_codespell.py | 57 ++++++++++++++++++++++--------------- pyproject.toml | 4 +-- 2 files changed, 36 insertions(+), 25 deletions(-) diff --git a/codespell_lib/_codespell.py b/codespell_lib/_codespell.py index dadb575378b..f7f643a5070 100644 --- a/codespell_lib/_codespell.py +++ b/codespell_lib/_codespell.py @@ -339,10 +339,9 @@ def parse_options( "-D", "--dictionary", action="append", - help="custom dictionary file that contains spelling " - "corrections. If this flag is not specified or " - 'equals "-" then the default dictionary is used. ' - "This option can be specified multiple times.", + help="comma-separated list of custom dictionary files that " + "contain spelling corrections. If this flag is not specified " + 'or equals "-" then the default dictionary is used.', ) builtin_opts = "\n- ".join( [""] + [f"{d[0]!r} {d[1]}" for d in _builtin_dictionaries] @@ -372,26 +371,26 @@ def parse_options( "-I", "--ignore-words", action="append", - metavar="FILE", - help="file that contains words that will be ignored " - "by codespell. File must contain 1 word per line." - " Words are case sensitive based on how they are " - "written in the dictionary file", + metavar="FILES", + help="comma-separated list of files that contain " + "words to be ignored by codespell. Files must contain " + "1 word per line. Words are case sensitive based on " + "how they are written in the dictionary file.", ) parser.add_argument( "-L", "--ignore-words-list", action="append", metavar="WORDS", - help="comma separated list of words to be ignored " + help="comma-separated list of words to be ignored " "by codespell. Words are case sensitive based on " - "how they are written in the dictionary file", + "how they are written in the dictionary file.", ) parser.add_argument( "--uri-ignore-words-list", action="append", metavar="WORDS", - help="comma separated list of words to be ignored " + help="comma-separated list of words to be ignored " "by codespell in URIs and emails only. Words are " "case sensitive based on how they are written in " 'the dictionary file. If set to "*", all ' @@ -443,11 +442,13 @@ def parse_options( parser.add_argument( "-x", "--exclude-file", + action="append", type=str, - metavar="FILE", - help="ignore whole lines that match those " - "in the file FILE. The lines in FILE " - "should match the to-be-excluded lines exactly", + metavar="FILES", + help="ignore whole lines that match those in " + "the comma-separated list of files EXCLUDE. " + "The lines in these files should match the " + "to-be-excluded lines exactly", ) parser.add_argument( @@ -984,6 +985,18 @@ def parse_file( return bad_count +def flatten_comma_separated_arguments( + arguments: List[str], +) -> List[str]: + """ + >>> flatten_comma_separated_arguments(["a,b,c", "d", "e"]) + ['a', 'b', 'c', 'd', 'e'] + >>> flatten_comma_separated_arguments([]) + [] + """ + return [item for argument in arguments for item in argument.split(",")] + + def _script_main() -> int: """Wrap to main() for setuptools.""" return main(*sys.argv[1:]) @@ -1028,8 +1041,8 @@ def main(*args: str) -> int: else: ignore_word_regex = None - ignore_words_files = options.ignore_words or [] ignore_words = parse_ignore_words_option(options.ignore_words_list) + ignore_words_files = flatten_comma_separated_arguments(options.ignore_words or []) for ignore_words_file in ignore_words_files: if not os.path.isfile(ignore_words_file): print( @@ -1052,10 +1065,7 @@ def main(*args: str) -> int: return EX_USAGE uri_ignore_words = parse_ignore_words_option(options.uri_ignore_words_list) - if options.dictionary: - dictionaries = options.dictionary - else: - dictionaries = ["-"] + dictionaries = flatten_comma_separated_arguments(options.dictionary or ["-"]) use_dictionaries = [] for dictionary in dictionaries: if dictionary == "-": @@ -1118,8 +1128,9 @@ def main(*args: str) -> int: context = (context_before, context_after) exclude_lines: Set[str] = set() - if options.exclude_file: - build_exclude_hashes(options.exclude_file, exclude_lines) + exclude_files = flatten_comma_separated_arguments(options.exclude_file or []) + for exclude_file in exclude_files: + build_exclude_hashes(exclude_file, exclude_lines) file_opener = FileOpener(options.hard_encoding_detection, options.quiet_level) diff --git a/pyproject.toml b/pyproject.toml index c6b11fac8c0..13d73e593f8 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -141,6 +141,6 @@ max-complexity = 45 [tool.ruff.pylint] allow-magic-value-types = ["bytes", "int", "str",] max-args = 12 -max-branches = 48 +max-branches = 46 max-returns = 10 -max-statements = 111 +max-statements = 108