Repository: codespell-project/codespell

Issue:
bad character range in a file skip glob
> Also, as result `codespell --skip "example file with actual [weird signs in it and b-a]"` goes down with
> 
> ```
> Traceback (most recent call last):
>   File "/home/mateusz/.local/bin/codespell", line 8, in <module>
>     sys.exit(_script_main())
>   File "/home/mateusz/.local/lib/python3.8/site-packages/codespell_lib/_codespell.py", line 748, in _script_main
>     return main(*sys.argv[1:])
>   File "/home/mateusz/.local/lib/python3.8/site-packages/codespell_lib/_codespell.py", line 876, in main
>     if glob_match.match(root):  # skip (absolute) directories
>   File "/home/mateusz/.local/lib/python3.8/site-packages/codespell_lib/_codespell.py", line 108, in match
>     if fnmatch.fnmatch(filename, p):
>   File "/usr/lib/python3.8/fnmatch.py", line 36, in fnmatch
>     return fnmatchcase(name, pat)
>   File "/usr/lib/python3.8/fnmatch.py", line 70, in fnmatchcase
>     match = _compile_pattern(pat)
>   File "/usr/lib/python3.8/fnmatch.py", line 46, in _compile_pattern
>     return re.compile(res).match
>   File "/usr/lib/python3.8/re.py", line 252, in compile
>     return _compile(pattern, flags)
>   File "/usr/lib/python3.8/re.py", line 304, in _compile
>     p = sre_compile.compile(pattern, flags)
>   File "/usr/lib/python3.8/sre_compile.py", line 764, in compile
>     p = sre_parse.parse(p, flags)
>   File "/usr/lib/python3.8/sre_parse.py", line 948, in parse
>     p = _parse_sub(source, state, flags & SRE_FLAG_VERBOSE, 0)
>   File "/usr/lib/python3.8/sre_parse.py", line 443, in _parse_sub
>     itemsappend(_parse(source, state, verbose, nested + 1,
>   File "/usr/lib/python3.8/sre_parse.py", line 834, in _parse
>     p = _parse_sub(source, state, sub_verbose, nested + 1)
>   File "/usr/lib/python3.8/sre_parse.py", line 443, in _parse_sub
>     itemsappend(_parse(source, state, verbose, nested + 1,
>   File "/usr/lib/python3.8/sre_parse.py", line 598, in _parse
>     raise source.error(msg, len(this) + 1 + len(that))
> re.error: bad character range b-a at position 56
> ```

_Originally posted by @matkoniecz in https://github.com/codespell-project/codespell/issues/2142#issue-1050634961

While that glob obviously won't work, we probably shouldn't crash quite so unhelpfully and instead catch the error...


Discussion:

Resolving patch:
diff --git a/codespell_lib/_codespell.py b/codespell_lib/_codespell.py
--- a/codespell_lib/_codespell.py
+++ b/codespell_lib/_codespell.py
@@ -898,7 +898,15 @@ def main(*args):
 
     file_opener = FileOpener(options.hard_encoding_detection,
                              options.quiet_level)
+
     glob_match = GlobMatch(options.skip)
+    try:
+        glob_match.match("/random/path")  # does not need a real path
+    except re.error:
+        print("ERROR: --skip/-S has been fed an invalid glob, "
+              "try escaping special characters",
+              file=sys.stderr)
+        return EX_USAGE
 
     bad_count = 0
     for filename in options.files:


Test patch:
diff --git a/codespell_lib/tests/test_basic.py b/codespell_lib/tests/test_basic.py
--- a/codespell_lib/tests/test_basic.py
+++ b/codespell_lib/tests/test_basic.py
@@ -117,10 +117,30 @@ def test_basic(tmpdir, capsys):
     assert cs.main(d) == 0
 
     # empty directory
-    os.mkdir(op.join(d, 'test'))
+    os.mkdir(op.join(d, 'empty'))
     assert cs.main(d) == 0
 
 
+def test_bad_glob(tmpdir, capsys):
+    # disregard invalid globs, properly handle escaped globs
+    g = op.join(tmpdir, 'glob')
+    os.mkdir(g)
+    fname = op.join(g, '[b-a].txt')
+    with open(fname, 'a') as f:
+        f.write('abandonned\n')
+    assert cs.main(g) == 1
+    # bad glob is invalid
+    code, _, stderr = cs.main('--skip', '[b-a].txt',
+                              g, std=True)
+    if sys.hexversion < 0x030A05F0:  # Python < 3.10.5 raises re.error
+        assert code == EX_USAGE, 'invalid glob'
+        assert 'invalid glob' in stderr
+    else:  # Python >= 3.10.5 does not match
+        assert code == 1
+    # properly escaped glob is valid, and matches glob-like file name
+    assert cs.main('--skip', '[[]b-a[]].txt', g) == 0
+
+
 @pytest.mark.skipif(
     not sys.platform == 'linux', reason='Only supported on Linux')
 def test_permission_error(tmp_path, capsys):