Repository: codespell-project/codespell

Issue:
codespell fails with uncaught exception on empty files
```sh
$ mkdir -p foo
$ touch foo/bar.sql
$ cd foo
$ 
$ codespell
Traceback (most recent call last):
  File "/home/username/.local/bin/codespell", line 11, in <module>
    load_entry_point('codespell', 'console_scripts', 'codespell')()
  File "/my/path/codespell/codespell_lib/_codespell.py", line 754, in _script_main
    return main(*sys.argv[1:])
  File "/my/path/codespell/codespell_lib/_codespell.py", line 905, in main
    bad_count += parse_file(
  File "/my/path/codespell/codespell_lib/_codespell.py", line 642, in parse_file
    lines, encoding = file_opener.open(filename)
  File "/my/path/codespell/codespell_lib/_codespell.py", line 174, in open
    return self.open_with_internal(filename)
  File "/my/path/codespell/codespell_lib/_codespell.py", line 223, in open_with_internal
    raise Exception('Unknown encoding: %s' % filename)
Exception: Unknown encoding
$ 
```


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
@@ -200,30 +200,27 @@ def open_with_chardet(self, filename):
         return lines, encoding
 
     def open_with_internal(self, filename):
-        curr = 0
-        while True:
-            try:
-                f = codecs.open(filename, 'r', encoding=encodings[curr])
-            except UnicodeDecodeError:
-                if not self.quiet_level & QuietLevels.ENCODING:
-                    print("WARNING: Decoding file using encoding=%s failed: %s"
-                          % (encodings[curr], filename,), file=sys.stderr)
-                    try:
-                        print("WARNING: Trying next encoding %s"
-                              % encodings[curr + 1], file=sys.stderr)
-                    except IndexError:
-                        pass
-
-                curr += 1
-            else:
-                lines = f.readlines()
-                f.close()
-                break
-        if not lines:
+        encoding = None
+        first_try = True
+        for encoding in encodings:
+            if first_try:
+                first_try = False
+            elif not self.quiet_level & QuietLevels.ENCODING:
+                print("WARNING: Trying next encoding %s"
+                      % encoding, file=sys.stderr)
+            with codecs.open(filename, 'r', encoding=encoding) as f:
+                try:
+                    lines = f.readlines()
+                except UnicodeDecodeError:
+                    if not self.quiet_level & QuietLevels.ENCODING:
+                        print("WARNING: Decoding file using encoding=%s "
+                              "failed: %s" % (encoding, filename,),
+                              file=sys.stderr)
+                else:
+                    break
+        else:
             raise Exception('Unknown encoding')
 
-        encoding = encodings[curr]
-
         return lines, encoding
 
 # -.-:-.-:-.-:-.:-.-:-.-:-.-:-.-:-.:-.-:-.-:-.-:-.-:-.:-.-:-


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
@@ -272,6 +272,19 @@ def test_encoding(tmpdir, capsys):
     with open(f.name, 'ab') as f:
         f.write(u'naieve\n'.encode('utf-8'))
     assert cs.main(f.name) == 1
+    # Encoding detection (only try ISO 8859-1 because UTF-8 is the default)
+    with open(f.name, 'wb') as f:
+        f.write(b'Speling error, non-ASCII: h\xe9t\xe9rog\xe9n\xe9it\xe9\n')
+    # check warnings about wrong encoding are enabled with "-q 0"
+    code, stdout, stderr = cs.main('-q', '0', f.name, std=True, count=True)
+    assert code == 1
+    assert 'Speling' in stdout
+    assert 'iso-8859-1' in stderr
+    # check warnings about wrong encoding are disabled with "-q 1"
+    code, stdout, stderr = cs.main('-q', '1', f.name, std=True, count=True)
+    assert code == 1
+    assert 'Speling' in stdout
+    assert 'iso-8859-1' not in stderr
     # Binary file warning
     with open(f.name, 'wb') as f:
         f.write(b'\x00\x00naiive\x00\x00')