| from __future__ import annotations | |
| import argparse | |
| import os | |
| from collections.abc import Sequence | |
| from typing import IO | |
| def fix_file(file_obj: IO[bytes]) -> int: | |
| # Test for newline at end of file | |
| # Empty files will throw IOError here | |
| try: | |
| file_obj.seek(-1, os.SEEK_END) | |
| except OSError: | |
| return 0 | |
| last_character = file_obj.read(1) | |
| # last_character will be '' for an empty file | |
| if last_character not in {b'\n', b'\r'} and last_character != b'': | |
| # Needs this seek for windows, otherwise IOError | |
| file_obj.seek(0, os.SEEK_END) | |
| file_obj.write(b'\n') | |
| return 1 | |
| while last_character in {b'\n', b'\r'}: | |
| # Deal with the beginning of the file | |
| if file_obj.tell() == 1: | |
| # If we've reached the beginning of the file and it is all | |
| # linebreaks then we can make this file empty | |
| file_obj.seek(0) | |
| file_obj.truncate() | |
| return 1 | |
| # Go back two bytes and read a character | |
| file_obj.seek(-2, os.SEEK_CUR) | |
| last_character = file_obj.read(1) | |
| # Our current position is at the end of the file just before any amount of | |
| # newlines. If we find extraneous newlines, then backtrack and trim them. | |
| position = file_obj.tell() | |
| remaining = file_obj.read() | |
| for sequence in (b'\n', b'\r\n', b'\r'): | |
| if remaining == sequence: | |
| return 0 | |
| elif remaining.startswith(sequence): | |
| file_obj.seek(position + len(sequence)) | |
| file_obj.truncate() | |
| return 1 | |
| return 0 | |
| def main(argv: Sequence[str] | None = None) -> int: | |
| parser = argparse.ArgumentParser() | |
| parser.add_argument('filenames', nargs='*', help='Filenames to fix') | |
| args = parser.parse_args(argv) | |
| retv = 0 | |
| for filename in args.filenames: | |
| # Read as binary so we can read byte-by-byte | |
| with open(filename, 'rb+') as file_obj: | |
| ret_for_file = fix_file(file_obj) | |
| if ret_for_file: | |
| print(f'Fixing {filename}') | |
| retv |= ret_for_file | |
| return retv | |
| if __name__ == '__main__': | |
| raise SystemExit(main()) |