blob: a30dce9276d1e04ae372d7f74cbdfe3efe4dd640 [file] [log] [blame]
Anthony Sottile8f615292022-01-15 19:24:05 -05001from __future__ import annotations
2
Anthony Sottile57f15332014-03-22 21:40:57 -07003import argparse
4import os
Anthony Sottile030bfac2019-01-31 19:19:10 -08005from typing import IO
Anthony Sottile030bfac2019-01-31 19:19:10 -08006from typing import Sequence
Anthony Sottile57f15332014-03-22 21:40:57 -07007
Anthony Sottile57f15332014-03-22 21:40:57 -07008
Anthony Sottilef5c42a02020-02-05 11:10:42 -08009def fix_file(file_obj: IO[bytes]) -> int:
Anthony Sottile57f15332014-03-22 21:40:57 -070010 # Test for newline at end of file
11 # Empty files will throw IOError here
12 try:
13 file_obj.seek(-1, os.SEEK_END)
Anthony Sottilef5c42a02020-02-05 11:10:42 -080014 except OSError:
Anthony Sottile57f15332014-03-22 21:40:57 -070015 return 0
16 last_character = file_obj.read(1)
17 # last_character will be '' for an empty file
mtkennerlye694a6c2018-10-13 19:44:02 -040018 if last_character not in {b'\n', b'\r'} and last_character != b'':
Anthony Sottiledd9a07d2015-01-19 16:43:10 -080019 # Needs this seek for windows, otherwise IOError
20 file_obj.seek(0, os.SEEK_END)
Anthony Sottileb80ca9e2014-04-13 22:09:14 -070021 file_obj.write(b'\n')
Anthony Sottile57f15332014-03-22 21:40:57 -070022 return 1
23
mtkennerlye694a6c2018-10-13 19:44:02 -040024 while last_character in {b'\n', b'\r'}:
Anthony Sottile57f15332014-03-22 21:40:57 -070025 # Deal with the beginning of the file
26 if file_obj.tell() == 1:
27 # If we've reached the beginning of the file and it is all
28 # linebreaks then we can make this file empty
29 file_obj.seek(0)
30 file_obj.truncate()
31 return 1
32
33 # Go back two bytes and read a character
34 file_obj.seek(-2, os.SEEK_CUR)
35 last_character = file_obj.read(1)
36
37 # Our current position is at the end of the file just before any amount of
mtkennerly2ab58322018-10-13 19:00:22 -040038 # newlines. If we find extraneous newlines, then backtrack and trim them.
39 position = file_obj.tell()
40 remaining = file_obj.read()
mtkennerlye694a6c2018-10-13 19:44:02 -040041 for sequence in (b'\n', b'\r\n', b'\r'):
42 if remaining == sequence:
43 return 0
44 elif remaining.startswith(sequence):
mtkennerly2ab58322018-10-13 19:00:22 -040045 file_obj.seek(position + len(sequence))
46 file_obj.truncate()
47 return 1
Anthony Sottile57f15332014-03-22 21:40:57 -070048
49 return 0
50
51
Anthony Sottile8f615292022-01-15 19:24:05 -050052def main(argv: Sequence[str] | None = None) -> int:
Anthony Sottile57f15332014-03-22 21:40:57 -070053 parser = argparse.ArgumentParser()
54 parser.add_argument('filenames', nargs='*', help='Filenames to fix')
55 args = parser.parse_args(argv)
56
57 retv = 0
58
59 for filename in args.filenames:
60 # Read as binary so we can read byte-by-byte
61 with open(filename, 'rb+') as file_obj:
62 ret_for_file = fix_file(file_obj)
63 if ret_for_file:
Anthony Sottilef5c42a02020-02-05 11:10:42 -080064 print(f'Fixing {filename}')
Anthony Sottile57f15332014-03-22 21:40:57 -070065 retv |= ret_for_file
66
67 return retv
68
69
70if __name__ == '__main__':
Anthony Sottile39ab2ed2021-10-23 13:23:50 -040071 raise SystemExit(main())