Anthony Sottile | 8f61529 | 2022-01-15 19:24:05 -0500 | [diff] [blame] | 1 | from __future__ import annotations |
| 2 | |
Anthony Sottile | acd9eaf | 2014-03-14 15:18:29 -0700 | [diff] [blame] | 3 | import argparse |
Alexander Dupuy | a6023ac | 2015-05-10 10:00:54 +0200 | [diff] [blame] | 4 | import os |
Anthony Sottile | 030bfac | 2019-01-31 19:19:10 -0800 | [diff] [blame] | 5 | from typing import Sequence |
Anthony Sottile | b08f834 | 2015-01-04 16:05:20 -0800 | [diff] [blame] | 6 | |
Anthony Sottile | 4574154 | 2014-03-22 18:21:54 -0700 | [diff] [blame] | 7 | |
Anthony Sottile | f5c42a0 | 2020-02-05 11:10:42 -0800 | [diff] [blame] | 8 | def _fix_file( |
| 9 | filename: str, |
| 10 | is_markdown: bool, |
Anthony Sottile | 8f61529 | 2022-01-15 19:24:05 -0500 | [diff] [blame] | 11 | chars: bytes | None, |
Anthony Sottile | f5c42a0 | 2020-02-05 11:10:42 -0800 | [diff] [blame] | 12 | ) -> bool: |
Lucas Cimon | cb23c48 | 2016-08-31 02:02:33 +0100 | [diff] [blame] | 13 | with open(filename, mode='rb') as file_processed: |
| 14 | lines = file_processed.readlines() |
iconmaster5326 | a33a8f0 | 2019-10-25 12:20:04 -0400 | [diff] [blame] | 15 | newlines = [_process_line(line, is_markdown, chars) for line in lines] |
Anthony Sottile | 8114733 | 2017-02-07 09:36:39 -0800 | [diff] [blame] | 16 | 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 Cimon | cb23c48 | 2016-08-31 02:02:33 +0100 | [diff] [blame] | 23 | |
| 24 | |
Anthony Sottile | f5c42a0 | 2020-02-05 11:10:42 -0800 | [diff] [blame] | 25 | def _process_line( |
| 26 | line: bytes, |
| 27 | is_markdown: bool, |
Anthony Sottile | 8f61529 | 2022-01-15 19:24:05 -0500 | [diff] [blame] | 28 | chars: bytes | None, |
Anthony Sottile | f5c42a0 | 2020-02-05 11:10:42 -0800 | [diff] [blame] | 29 | ) -> bytes: |
Anthony Sottile | 38e02ff | 2018-02-28 08:43:07 -0800 | [diff] [blame] | 30 | if line[-2:] == b'\r\n': |
| 31 | eol = b'\r\n' |
iconmaster5326 | a2f836a | 2019-10-25 11:34:26 -0400 | [diff] [blame] | 32 | line = line[:-2] |
Anthony Sottile | 38e02ff | 2018-02-28 08:43:07 -0800 | [diff] [blame] | 33 | elif line[-1:] == b'\n': |
| 34 | eol = b'\n' |
iconmaster5326 | a2f836a | 2019-10-25 11:34:26 -0400 | [diff] [blame] | 35 | line = line[:-1] |
Anthony Sottile | 38e02ff | 2018-02-28 08:43:07 -0800 | [diff] [blame] | 36 | else: |
| 37 | eol = b'' |
Lucas Cimon | cb23c48 | 2016-08-31 02:02:33 +0100 | [diff] [blame] | 38 | # preserve trailing two-space for non-blank lines in markdown files |
iconmaster5326 | a2f836a | 2019-10-25 11:34:26 -0400 | [diff] [blame] | 39 | if is_markdown and (not line.isspace()) and line.endswith(b' '): |
iconmaster5326 | a33a8f0 | 2019-10-25 12:20:04 -0400 | [diff] [blame] | 40 | return line[:-2].rstrip(chars) + b' ' + eol |
| 41 | return line.rstrip(chars) + eol |
Anthony Sottile | e3312c7 | 2014-12-23 12:04:19 -0800 | [diff] [blame] | 42 | |
| 43 | |
Anthony Sottile | 8f61529 | 2022-01-15 19:24:05 -0500 | [diff] [blame] | 44 | def main(argv: Sequence[str] | None = None) -> int: |
Anthony Sottile | acd9eaf | 2014-03-14 15:18:29 -0700 | [diff] [blame] | 45 | parser = argparse.ArgumentParser() |
Alexander Dupuy | a6023ac | 2015-05-10 10:00:54 +0200 | [diff] [blame] | 46 | parser.add_argument( |
| 47 | '--no-markdown-linebreak-ext', |
Anthony Sottile | 99453a5 | 2018-10-12 18:10:02 -0700 | [diff] [blame] | 48 | action='store_true', |
| 49 | help=argparse.SUPPRESS, |
Alexander Dupuy | a6023ac | 2015-05-10 10:00:54 +0200 | [diff] [blame] | 50 | ) |
| 51 | parser.add_argument( |
| 52 | '--markdown-linebreak-ext', |
| 53 | action='append', |
Anthony Sottile | 99453a5 | 2018-10-12 18:10:02 -0700 | [diff] [blame] | 54 | default=[], |
Alexander Dupuy | a6023ac | 2015-05-10 10:00:54 +0200 | [diff] [blame] | 55 | metavar='*|EXT[,EXT,...]', |
Anthony Sottile | 99453a5 | 2018-10-12 18:10:02 -0700 | [diff] [blame] | 56 | help=( |
| 57 | 'Markdown extensions (or *) to not strip linebreak spaces. ' |
| 58 | 'default: %(default)s' |
| 59 | ), |
Alexander Dupuy | a6023ac | 2015-05-10 10:00:54 +0200 | [diff] [blame] | 60 | ) |
iconmaster5326 | 886dfc4 | 2019-10-25 11:12:49 -0400 | [diff] [blame] | 61 | parser.add_argument( |
| 62 | '--chars', |
iconmaster5326 | a33a8f0 | 2019-10-25 12:20:04 -0400 | [diff] [blame] | 63 | help=( |
| 64 | 'The set of characters to strip from the end of lines. ' |
| 65 | 'Defaults to all whitespace characters.' |
| 66 | ), |
iconmaster5326 | 886dfc4 | 2019-10-25 11:12:49 -0400 | [diff] [blame] | 67 | ) |
Anthony Sottile | acd9eaf | 2014-03-14 15:18:29 -0700 | [diff] [blame] | 68 | parser.add_argument('filenames', nargs='*', help='Filenames to fix') |
| 69 | args = parser.parse_args(argv) |
| 70 | |
Anthony Sottile | 99453a5 | 2018-10-12 18:10:02 -0700 | [diff] [blame] | 71 | if args.no_markdown_linebreak_ext: |
| 72 | print('--no-markdown-linebreak-ext now does nothing!') |
| 73 | |
Alexander Dupuy | a6023ac | 2015-05-10 10:00:54 +0200 | [diff] [blame] | 74 | 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 Sottile | 4575652 | 2019-02-11 19:56:15 -0800 | [diff] [blame] | 78 | # normalize extensions; split at ',', lowercase, and force 1 leading '.' |
Anthony Sottile | 8114733 | 2017-02-07 09:36:39 -0800 | [diff] [blame] | 79 | md_exts = [ |
| 80 | '.' + x.lower().lstrip('.') for x in ','.join(md_args).split(',') |
| 81 | ] |
Alexander Dupuy | a6023ac | 2015-05-10 10:00:54 +0200 | [diff] [blame] | 82 | |
Anthony Sottile | 4575652 | 2019-02-11 19:56:15 -0800 | [diff] [blame] | 83 | # reject probable "eaten" filename as extension: skip leading '.' with [1:] |
Alexander Dupuy | a6023ac | 2015-05-10 10:00:54 +0200 | [diff] [blame] | 84 | for ext in md_exts: |
| 85 | if any(c in ext[1:] for c in r'./\:'): |
| 86 | parser.error( |
Anthony Sottile | f5c42a0 | 2020-02-05 11:10:42 -0800 | [diff] [blame] | 87 | f'bad --markdown-linebreak-ext extension ' |
| 88 | f'{ext!r} (has . / \\ :)\n' |
| 89 | f" (probably filename; use '--markdown-linebreak-ext=EXT')", |
Alexander Dupuy | a6023ac | 2015-05-10 10:00:54 +0200 | [diff] [blame] | 90 | ) |
Anthony Sottile | f5c42a0 | 2020-02-05 11:10:42 -0800 | [diff] [blame] | 91 | chars = None if args.chars is None else args.chars.encode() |
Lucas Cimon | bc5e7f2 | 2016-08-18 16:39:06 +0200 | [diff] [blame] | 92 | return_code = 0 |
Anthony Sottile | 9958d1e | 2017-10-09 10:39:58 -0700 | [diff] [blame] | 93 | for filename in args.filenames: |
| 94 | _, extension = os.path.splitext(filename.lower()) |
Anthony Sottile | 8114733 | 2017-02-07 09:36:39 -0800 | [diff] [blame] | 95 | md = all_markdown or extension in md_exts |
iconmaster5326 | a33a8f0 | 2019-10-25 12:20:04 -0400 | [diff] [blame] | 96 | if _fix_file(filename, md, chars): |
Anthony Sottile | f5c42a0 | 2020-02-05 11:10:42 -0800 | [diff] [blame] | 97 | print(f'Fixing {filename}') |
Anthony Sottile | 8114733 | 2017-02-07 09:36:39 -0800 | [diff] [blame] | 98 | return_code = 1 |
Lucas Cimon | bc5e7f2 | 2016-08-18 16:39:06 +0200 | [diff] [blame] | 99 | return return_code |
Anthony Sottile | acd9eaf | 2014-03-14 15:18:29 -0700 | [diff] [blame] | 100 | |
| 101 | |
Anthony Sottile | acd9eaf | 2014-03-14 15:18:29 -0700 | [diff] [blame] | 102 | if __name__ == '__main__': |
Anthony Sottile | 39ab2ed | 2021-10-23 13:23:50 -0400 | [diff] [blame] | 103 | raise SystemExit(main()) |