from __future__ import annotations import argparse import os from collections.abc import Sequence def _fix_file( filename: str, is_markdown: bool, chars: bytes | None, ) -> bool: with open(filename, mode='rb') as file_processed: lines = file_processed.readlines() newlines = [_process_line(line, is_markdown, chars) for line in lines] if newlines != lines: with open(filename, mode='wb') as file_processed: for line in newlines: file_processed.write(line) return True else: return False def _process_line( line: bytes, is_markdown: bool, chars: bytes | None, ) -> bytes: if line[-2:] == b'\r\n': eol = b'\r\n' line = line[:-2] elif line[-1:] == b'\n': eol = b'\n' line = line[:-1] else: eol = b'' # preserve trailing two-space for non-blank lines in markdown files if is_markdown and (not line.isspace()) and line.endswith(b' '): return line[:-2].rstrip(chars) + b' ' + eol return line.rstrip(chars) + eol def main(argv: Sequence[str] | None = None) -> int: parser = argparse.ArgumentParser() parser.add_argument( '--no-markdown-linebreak-ext', action='store_true', help=argparse.SUPPRESS, ) parser.add_argument( '--markdown-linebreak-ext', action='append', default=[], metavar='*|EXT[,EXT,...]', help=( 'Markdown extensions (or *) to not strip linebreak spaces. ' 'default: %(default)s' ), ) parser.add_argument( '--chars', help=( 'The set of characters to strip from the end of lines. ' 'Defaults to all whitespace characters.' ), ) parser.add_argument('filenames', nargs='*', help='Filenames to fix') args = parser.parse_args(argv) if args.no_markdown_linebreak_ext: print('--no-markdown-linebreak-ext now does nothing!') md_args = args.markdown_linebreak_ext if '' in md_args: parser.error('--markdown-linebreak-ext requires a non-empty argument') all_markdown = '*' in md_args # normalize extensions; split at ',', lowercase, and force 1 leading '.' md_exts = [ '.' + x.lower().lstrip('.') for x in ','.join(md_args).split(',') ] # reject probable "eaten" filename as extension: skip leading '.' with [1:] for ext in md_exts: if any(c in ext[1:] for c in r'./\:'): parser.error( f'bad --markdown-linebreak-ext extension ' f'{ext!r} (has . / \\ :)\n' f" (probably filename; use '--markdown-linebreak-ext=EXT')", ) chars = None if args.chars is None else args.chars.encode() return_code = 0 for filename in args.filenames: _, extension = os.path.splitext(filename.lower()) md = all_markdown or extension in md_exts if _fix_file(filename, md, chars): print(f'Fixing {filename}') return_code = 1 return return_code if __name__ == '__main__': raise SystemExit(main())