summaryrefslogtreecommitdiffstats
path: root/pre_commit_hooks/file_contents_sorter.py
blob: 392e226122de3c63fac10cef852fd41157291cec (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
"""
A very simple pre-commit hook that, when passed one or more filenames
as arguments, will sort the lines in those files.

An example use case for this: you have a deploy-allowlist.txt file
in a repo that contains a list of filenames that is used to specify
files to be included in a docker container. This file has one filename
per line. Various users are adding/removing lines from this file; using
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 Iterable
from typing import Optional
from typing import Sequence

PASS = 0
FAIL = 1


def sort_file_contents(
    f: IO[bytes],
    key: Optional[Callable[[bytes], Any]],
    *,
    unique: bool = False,
) -> int:
    before = list(f)
    lines: Iterable[bytes] = (
        line.rstrip(b'\n\r') for line in before if line.strip()
    )
    if unique:
        lines = set(lines)
    after = sorted(lines, key=key)

    before_string = b''.join(before)
    after_string = b'\n'.join(after) + b'\n'

    if before_string == after_string:
        return PASS
    else:
        f.seek(0)
        f.write(after_string)
        f.truncate()
        return FAIL


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',
    )
    parser.add_argument(
        '--unique',
        action='store_true',
        help='ensure each line is unique',
    )
    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, key=args.ignore_case, unique=args.unique,
            )

            if ret_for_file:
                print(f'Sorting {arg}')

            retv |= ret_for_file

    return retv


if __name__ == '__main__':
    raise SystemExit(main())