blob: 392e226122de3c63fac10cef852fd41157291cec [file] [log] [blame]
"""
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())