Repository: pre-commit/pre-commit-hooks
Pull request #514 (2020-08-27 00:30:47)

Add a way to do case-insensitive sorting via file-contents-sorter.
Does nothing fancy (no case folding).

But does what I need in my case.

Comments welcome of course.


Patch:
diff --git a/pre_commit_hooks/file_contents_sorter.py b/pre_commit_hooks/file_contents_sorter.py
index 76dc4fa..4c1c747 100644
--- a/pre_commit_hooks/file_contents_sorter.py
+++ b/pre_commit_hooks/file_contents_sorter.py
@@ -10,6 +10,8 @@ this hook on that file should reduce the instances of git merge
 conflicts and keep the file nicely ordered.
 """
 import argparse
+from typing import Any
+from typing import Callable
 from typing import IO
 from typing import Optional
 from typing import Sequence
@@ -18,9 +20,15 @@ PASS = 0
 FAIL = 1
 
 
-def sort_file_contents(f: IO[bytes]) -> int:
+def sort_file_contents(
+    f: IO[bytes],
+    key: Optional[Callable[[bytes], Any]],
+) -> int:
     before = list(f)
-    after = sorted(line.strip(b'\n\r') for line in before if line.strip())
+    after = sorted(
+        (line.strip(b'\n\r') for line in before if line.strip()),
+        key=key,
+    )
 
     before_string = b''.join(before)
     after_string = b'\n'.join(after) + b'\n'
@@ -37,13 +45,20 @@ def sort_file_contents(f: IO[bytes]) -> int:
 def main(argv: Optional[Sequence[str]] = None) -> int:
     parser = argparse.ArgumentParser()
     parser.add_argument('filenames', nargs='+', help='Files to sort')
+    parser.add_argument(
+        '--ignore-case',
+        action='store_const',
+        const=bytes.lower,
+        default=None,
+        help='fold lower case to upper case characters',
+    )
     args = parser.parse_args(argv)
 
     retv = PASS
 
     for arg in args.filenames:
         with open(arg, 'rb+') as file_obj:
-            ret_for_file = sort_file_contents(file_obj)
+            ret_for_file = sort_file_contents(file_obj, key=args.ignore_case)
 
             if ret_for_file:
                 print(f'Sorting {arg}')


Test patch:
diff --git a/tests/file_contents_sorter_test.py b/tests/file_contents_sorter_test.py
index c8afc2d..9ebb021 100644
--- a/tests/file_contents_sorter_test.py
+++ b/tests/file_contents_sorter_test.py
@@ -6,28 +6,52 @@ from pre_commit_hooks.file_contents_sorter import PASS
 
 
 @pytest.mark.parametrize(
-    ('input_s', 'expected_retval', 'output'),
+    ('input_s', 'argv', 'expected_retval', 'output'),
     (
-        (b'', FAIL, b'\n'),
-        (b'lonesome\n', PASS, b'lonesome\n'),
-        (b'missing_newline', FAIL, b'missing_newline\n'),
-        (b'newline\nmissing', FAIL, b'missing\nnewline\n'),
-        (b'missing\nnewline', FAIL, b'missing\nnewline\n'),
-        (b'alpha\nbeta\n', PASS, b'alpha\nbeta\n'),
-        (b'beta\nalpha\n', FAIL, b'alpha\nbeta\n'),
-        (b'C\nc\n', PASS, b'C\nc\n'),
-        (b'c\nC\n', FAIL, b'C\nc\n'),
-        (b'mag ical \n tre vor\n', FAIL, b' tre vor\nmag ical \n'),
-        (b'@\n-\n_\n#\n', FAIL, b'#\n-\n@\n_\n'),
-        (b'extra\n\n\nwhitespace\n', FAIL, b'extra\nwhitespace\n'),
-        (b'whitespace\n\n\nextra\n', FAIL, b'extra\nwhitespace\n'),
+        (b'', [], FAIL, b'\n'),
+        (b'lonesome\n', [], PASS, b'lonesome\n'),
+        (b'missing_newline', [], FAIL, b'missing_newline\n'),
+        (b'newline\nmissing', [], FAIL, b'missing\nnewline\n'),
+        (b'missing\nnewline', [], FAIL, b'missing\nnewline\n'),
+        (b'alpha\nbeta\n', [], PASS, b'alpha\nbeta\n'),
+        (b'beta\nalpha\n', [], FAIL, b'alpha\nbeta\n'),
+        (b'C\nc\n', [], PASS, b'C\nc\n'),
+        (b'c\nC\n', [], FAIL, b'C\nc\n'),
+        (b'mag ical \n tre vor\n', [], FAIL, b' tre vor\nmag ical \n'),
+        (b'@\n-\n_\n#\n', [], FAIL, b'#\n-\n@\n_\n'),
+        (b'extra\n\n\nwhitespace\n', [], FAIL, b'extra\nwhitespace\n'),
+        (b'whitespace\n\n\nextra\n', [], FAIL, b'extra\nwhitespace\n'),
+        (
+            b'fee\nFie\nFoe\nfum\n',
+            [],
+            FAIL,
+            b'Fie\nFoe\nfee\nfum\n',
+        ),
+        (
+            b'Fie\nFoe\nfee\nfum\n',
+            [],
+            PASS,
+            b'Fie\nFoe\nfee\nfum\n',
+        ),
+        (
+            b'fee\nFie\nFoe\nfum\n',
+            ['--ignore-case'],
+            PASS,
+            b'fee\nFie\nFoe\nfum\n',
+        ),
+        (
+            b'Fie\nFoe\nfee\nfum\n',
+            ['--ignore-case'],
+            FAIL,
+            b'fee\nFie\nFoe\nfum\n',
+        ),
     ),
 )
-def test_integration(input_s, expected_retval, output, tmpdir):
+def test_integration(input_s, argv, expected_retval, output, tmpdir):
     path = tmpdir.join('file.txt')
     path.write_binary(input_s)
 
-    output_retval = main([str(path)])
+    output_retval = main([str(path)] + argv)
 
     assert path.read_binary() == output
     assert output_retval == expected_retval