blob: 84f50671f2361198effb90fe576405cac7f27e2e [file] [log] [blame]
Anthony Sottile8f615292022-01-15 19:24:05 -05001from __future__ import annotations
2
Anthony Sottileacd9eaf2014-03-14 15:18:29 -07003import argparse
Alexander Dupuya6023ac2015-05-10 10:00:54 +02004import os
Anthony Sottile030bfac2019-01-31 19:19:10 -08005from typing import Sequence
Anthony Sottileb08f8342015-01-04 16:05:20 -08006
Anthony Sottile45741542014-03-22 18:21:54 -07007
Anthony Sottilef5c42a02020-02-05 11:10:42 -08008def _fix_file(
9 filename: str,
10 is_markdown: bool,
Anthony Sottile8f615292022-01-15 19:24:05 -050011 chars: bytes | None,
Anthony Sottilef5c42a02020-02-05 11:10:42 -080012) -> bool:
Lucas Cimoncb23c482016-08-31 02:02:33 +010013 with open(filename, mode='rb') as file_processed:
14 lines = file_processed.readlines()
iconmaster5326a33a8f02019-10-25 12:20:04 -040015 newlines = [_process_line(line, is_markdown, chars) for line in lines]
Anthony Sottile81147332017-02-07 09:36:39 -080016 if newlines != lines:
17 with open(filename, mode='wb') as file_processed:
18 for line in newlines:
19 file_processed.write(line)
20 return True
21 else:
22 return False
Lucas Cimoncb23c482016-08-31 02:02:33 +010023
24
Anthony Sottilef5c42a02020-02-05 11:10:42 -080025def _process_line(
26 line: bytes,
27 is_markdown: bool,
Anthony Sottile8f615292022-01-15 19:24:05 -050028 chars: bytes | None,
Anthony Sottilef5c42a02020-02-05 11:10:42 -080029) -> bytes:
Anthony Sottile38e02ff2018-02-28 08:43:07 -080030 if line[-2:] == b'\r\n':
31 eol = b'\r\n'
iconmaster5326a2f836a2019-10-25 11:34:26 -040032 line = line[:-2]
Anthony Sottile38e02ff2018-02-28 08:43:07 -080033 elif line[-1:] == b'\n':
34 eol = b'\n'
iconmaster5326a2f836a2019-10-25 11:34:26 -040035 line = line[:-1]
Anthony Sottile38e02ff2018-02-28 08:43:07 -080036 else:
37 eol = b''
Lucas Cimoncb23c482016-08-31 02:02:33 +010038 # preserve trailing two-space for non-blank lines in markdown files
iconmaster5326a2f836a2019-10-25 11:34:26 -040039 if is_markdown and (not line.isspace()) and line.endswith(b' '):
iconmaster5326a33a8f02019-10-25 12:20:04 -040040 return line[:-2].rstrip(chars) + b' ' + eol
41 return line.rstrip(chars) + eol
Anthony Sottilee3312c72014-12-23 12:04:19 -080042
43
Anthony Sottile8f615292022-01-15 19:24:05 -050044def main(argv: Sequence[str] | None = None) -> int:
Anthony Sottileacd9eaf2014-03-14 15:18:29 -070045 parser = argparse.ArgumentParser()
Alexander Dupuya6023ac2015-05-10 10:00:54 +020046 parser.add_argument(
47 '--no-markdown-linebreak-ext',
Anthony Sottile99453a52018-10-12 18:10:02 -070048 action='store_true',
49 help=argparse.SUPPRESS,
Alexander Dupuya6023ac2015-05-10 10:00:54 +020050 )
51 parser.add_argument(
52 '--markdown-linebreak-ext',
53 action='append',
Anthony Sottile99453a52018-10-12 18:10:02 -070054 default=[],
Alexander Dupuya6023ac2015-05-10 10:00:54 +020055 metavar='*|EXT[,EXT,...]',
Anthony Sottile99453a52018-10-12 18:10:02 -070056 help=(
57 'Markdown extensions (or *) to not strip linebreak spaces. '
58 'default: %(default)s'
59 ),
Alexander Dupuya6023ac2015-05-10 10:00:54 +020060 )
iconmaster5326886dfc42019-10-25 11:12:49 -040061 parser.add_argument(
62 '--chars',
iconmaster5326a33a8f02019-10-25 12:20:04 -040063 help=(
64 'The set of characters to strip from the end of lines. '
65 'Defaults to all whitespace characters.'
66 ),
iconmaster5326886dfc42019-10-25 11:12:49 -040067 )
Anthony Sottileacd9eaf2014-03-14 15:18:29 -070068 parser.add_argument('filenames', nargs='*', help='Filenames to fix')
69 args = parser.parse_args(argv)
70
Anthony Sottile99453a52018-10-12 18:10:02 -070071 if args.no_markdown_linebreak_ext:
72 print('--no-markdown-linebreak-ext now does nothing!')
73
Alexander Dupuya6023ac2015-05-10 10:00:54 +020074 md_args = args.markdown_linebreak_ext
75 if '' in md_args:
76 parser.error('--markdown-linebreak-ext requires a non-empty argument')
77 all_markdown = '*' in md_args
Anthony Sottile45756522019-02-11 19:56:15 -080078 # normalize extensions; split at ',', lowercase, and force 1 leading '.'
Anthony Sottile81147332017-02-07 09:36:39 -080079 md_exts = [
80 '.' + x.lower().lstrip('.') for x in ','.join(md_args).split(',')
81 ]
Alexander Dupuya6023ac2015-05-10 10:00:54 +020082
Anthony Sottile45756522019-02-11 19:56:15 -080083 # reject probable "eaten" filename as extension: skip leading '.' with [1:]
Alexander Dupuya6023ac2015-05-10 10:00:54 +020084 for ext in md_exts:
85 if any(c in ext[1:] for c in r'./\:'):
86 parser.error(
Anthony Sottilef5c42a02020-02-05 11:10:42 -080087 f'bad --markdown-linebreak-ext extension '
88 f'{ext!r} (has . / \\ :)\n'
89 f" (probably filename; use '--markdown-linebreak-ext=EXT')",
Alexander Dupuya6023ac2015-05-10 10:00:54 +020090 )
Anthony Sottilef5c42a02020-02-05 11:10:42 -080091 chars = None if args.chars is None else args.chars.encode()
Lucas Cimonbc5e7f22016-08-18 16:39:06 +020092 return_code = 0
Anthony Sottile9958d1e2017-10-09 10:39:58 -070093 for filename in args.filenames:
94 _, extension = os.path.splitext(filename.lower())
Anthony Sottile81147332017-02-07 09:36:39 -080095 md = all_markdown or extension in md_exts
iconmaster5326a33a8f02019-10-25 12:20:04 -040096 if _fix_file(filename, md, chars):
Anthony Sottilef5c42a02020-02-05 11:10:42 -080097 print(f'Fixing {filename}')
Anthony Sottile81147332017-02-07 09:36:39 -080098 return_code = 1
Lucas Cimonbc5e7f22016-08-18 16:39:06 +020099 return return_code
Anthony Sottileacd9eaf2014-03-14 15:18:29 -0700100
101
Anthony Sottileacd9eaf2014-03-14 15:18:29 -0700102if __name__ == '__main__':
Anthony Sottile39ab2ed2021-10-23 13:23:50 -0400103 raise SystemExit(main())