Anthony Sottile | 8f61529 | 2022-01-15 19:24:05 -0500 | [diff] [blame] | 1 | from __future__ import annotations |
| 2 | |
Léo Cavaillé | 55bf22d | 2015-06-10 17:08:48 -0400 | [diff] [blame] | 3 | import argparse |
Calum Lind | 00974ef | 2017-12-10 08:57:34 +0000 | [diff] [blame] | 4 | import json |
jack1142 | 1de4fe6 | 2021-03-14 18:21:48 +0100 | [diff] [blame] | 5 | import sys |
Joey Pinhas | ec6c39e | 2019-09-24 15:42:24 -0400 | [diff] [blame] | 6 | from difflib import unified_diff |
Anthony Sottile | 030bfac | 2019-01-31 19:19:10 -0800 | [diff] [blame] | 7 | from typing import Mapping |
Anthony Sottile | 030bfac | 2019-01-31 19:19:10 -0800 | [diff] [blame] | 8 | from typing import Sequence |
mattclegg | 700b18e | 2016-04-14 09:30:42 +0100 | [diff] [blame] | 9 | |
dmlb2000 | 845a3d5 | 2016-11-03 09:41:23 -0700 | [diff] [blame] | 10 | |
Anthony Sottile | 4575652 | 2019-02-11 19:56:15 -0800 | [diff] [blame] | 11 | def _get_pretty_format( |
Anthony Sottile | f5c42a0 | 2020-02-05 11:10:42 -0800 | [diff] [blame] | 12 | contents: str, |
| 13 | indent: str, |
| 14 | ensure_ascii: bool = True, |
| 15 | sort_keys: bool = True, |
| 16 | top_keys: Sequence[str] = (), |
| 17 | ) -> str: |
Anthony Sottile | 8f61529 | 2022-01-15 19:24:05 -0500 | [diff] [blame] | 18 | def pairs_first(pairs: Sequence[tuple[str, str]]) -> Mapping[str, str]: |
dmlb2000 | d06a515 | 2016-11-03 15:47:21 -0700 | [diff] [blame] | 19 | before = [pair for pair in pairs if pair[0] in top_keys] |
| 20 | before = sorted(before, key=lambda x: top_keys.index(x[0])) |
| 21 | after = [pair for pair in pairs if pair[0] not in top_keys] |
| 22 | if sort_keys: |
Anthony Sottile | f5c42a0 | 2020-02-05 11:10:42 -0800 | [diff] [blame] | 23 | after.sort() |
| 24 | return dict(before + after) |
Calum Lind | 00974ef | 2017-12-10 08:57:34 +0000 | [diff] [blame] | 25 | json_pretty = json.dumps( |
| 26 | json.loads(contents, object_pairs_hook=pairs_first), |
John Hu | 543c5c7 | 2017-03-16 11:27:34 +0800 | [diff] [blame] | 27 | indent=indent, |
Anthony Sottile | b95dcad | 2017-03-20 08:24:58 -0700 | [diff] [blame] | 28 | ensure_ascii=ensure_ascii, |
Calum Lind | 00974ef | 2017-12-10 08:57:34 +0000 | [diff] [blame] | 29 | ) |
Anthony Sottile | f5c42a0 | 2020-02-05 11:10:42 -0800 | [diff] [blame] | 30 | return f'{json_pretty}\n' |
Léo Cavaillé | 55bf22d | 2015-06-10 17:08:48 -0400 | [diff] [blame] | 31 | |
dmlb2000 | c7ab197 | 2016-11-03 15:49:04 -0700 | [diff] [blame] | 32 | |
Anthony Sottile | f5c42a0 | 2020-02-05 11:10:42 -0800 | [diff] [blame] | 33 | def _autofix(filename: str, new_contents: str) -> None: |
| 34 | print(f'Fixing file {filename}') |
| 35 | with open(filename, 'w', encoding='UTF-8') as f: |
Léo Cavaillé | 55bf22d | 2015-06-10 17:08:48 -0400 | [diff] [blame] | 36 | f.write(new_contents) |
| 37 | |
| 38 | |
Anthony Sottile | 8f61529 | 2022-01-15 19:24:05 -0500 | [diff] [blame] | 39 | def parse_num_to_int(s: str) -> int | str: |
Calum Lind | 5b6ddaf | 2017-12-10 09:34:36 +0000 | [diff] [blame] | 40 | """Convert string numbers to int, leaving strings as is.""" |
Sander Maijers | a562886 | 2016-06-10 20:16:00 +0200 | [diff] [blame] | 41 | try: |
Calum Lind | 5b6ddaf | 2017-12-10 09:34:36 +0000 | [diff] [blame] | 42 | return int(s) |
Sander Maijers | abaf0d1 | 2016-06-13 11:34:55 +0200 | [diff] [blame] | 43 | except ValueError: |
Calum Lind | 5b6ddaf | 2017-12-10 09:34:36 +0000 | [diff] [blame] | 44 | return s |
Sander Maijers | a562886 | 2016-06-10 20:16:00 +0200 | [diff] [blame] | 45 | |
dmlb2000 | 84b1fb6 | 2016-11-03 15:54:48 -0700 | [diff] [blame] | 46 | |
Anthony Sottile | 8f61529 | 2022-01-15 19:24:05 -0500 | [diff] [blame] | 47 | def parse_topkeys(s: str) -> list[str]: |
dmlb2000 | 845a3d5 | 2016-11-03 09:41:23 -0700 | [diff] [blame] | 48 | return s.split(',') |
Sander Maijers | a562886 | 2016-06-10 20:16:00 +0200 | [diff] [blame] | 49 | |
dmlb2000 | 84b1fb6 | 2016-11-03 15:54:48 -0700 | [diff] [blame] | 50 | |
Anthony Sottile | f5c42a0 | 2020-02-05 11:10:42 -0800 | [diff] [blame] | 51 | def get_diff(source: str, target: str, file: str) -> str: |
Joey Pinhas | 0ff23d4 | 2019-09-13 14:30:52 -0400 | [diff] [blame] | 52 | source_lines = source.splitlines(True) |
| 53 | target_lines = target.splitlines(True) |
Joey Pinhas | ec6c39e | 2019-09-24 15:42:24 -0400 | [diff] [blame] | 54 | diff = unified_diff(source_lines, target_lines, fromfile=file, tofile=file) |
| 55 | return ''.join(diff) |
Joey Pinhas | 780f202 | 2019-08-16 12:38:41 -0400 | [diff] [blame] | 56 | |
| 57 | |
Anthony Sottile | 8f61529 | 2022-01-15 19:24:05 -0500 | [diff] [blame] | 58 | def main(argv: Sequence[str] | None = None) -> int: |
Léo Cavaillé | 55bf22d | 2015-06-10 17:08:48 -0400 | [diff] [blame] | 59 | parser = argparse.ArgumentParser() |
| 60 | parser.add_argument( |
| 61 | '--autofix', |
| 62 | action='store_true', |
| 63 | dest='autofix', |
Anthony Sottile | 17478a0 | 2016-04-14 08:25:52 -0700 | [diff] [blame] | 64 | help='Automatically fixes encountered not-pretty-formatted files', |
Léo Cavaillé | 55bf22d | 2015-06-10 17:08:48 -0400 | [diff] [blame] | 65 | ) |
| 66 | parser.add_argument( |
| 67 | '--indent', |
Calum Lind | 5b6ddaf | 2017-12-10 09:34:36 +0000 | [diff] [blame] | 68 | type=parse_num_to_int, |
| 69 | default='2', |
| 70 | help=( |
| 71 | 'The number of indent spaces or a string to be used as delimiter' |
| 72 | ' for indentation level e.g. 4 or "\t" (Default: 2)' |
| 73 | ), |
Léo Cavaillé | 55bf22d | 2015-06-10 17:08:48 -0400 | [diff] [blame] | 74 | ) |
Sébastien Larivière | f769c20 | 2016-03-12 17:04:33 -0500 | [diff] [blame] | 75 | parser.add_argument( |
John Hu | 543c5c7 | 2017-03-16 11:27:34 +0800 | [diff] [blame] | 76 | '--no-ensure-ascii', |
| 77 | action='store_true', |
| 78 | dest='no_ensure_ascii', |
| 79 | default=False, |
Anthony Sottile | 4575652 | 2019-02-11 19:56:15 -0800 | [diff] [blame] | 80 | help=( |
| 81 | 'Do NOT convert non-ASCII characters to Unicode escape sequences ' |
| 82 | '(\\uXXXX)' |
| 83 | ), |
John Hu | 543c5c7 | 2017-03-16 11:27:34 +0800 | [diff] [blame] | 84 | ) |
| 85 | parser.add_argument( |
Sébastien Larivière | f769c20 | 2016-03-12 17:04:33 -0500 | [diff] [blame] | 86 | '--no-sort-keys', |
| 87 | action='store_true', |
| 88 | dest='no_sort_keys', |
| 89 | default=False, |
Anthony Sottile | 17478a0 | 2016-04-14 08:25:52 -0700 | [diff] [blame] | 90 | help='Keep JSON nodes in the same order', |
Sébastien Larivière | f769c20 | 2016-03-12 17:04:33 -0500 | [diff] [blame] | 91 | ) |
dmlb2000 | 845a3d5 | 2016-11-03 09:41:23 -0700 | [diff] [blame] | 92 | parser.add_argument( |
| 93 | '--top-keys', |
| 94 | type=parse_topkeys, |
| 95 | dest='top_keys', |
| 96 | default=[], |
| 97 | help='Ordered list of keys to keep at the top of JSON hashes', |
| 98 | ) |
Léo Cavaillé | 55bf22d | 2015-06-10 17:08:48 -0400 | [diff] [blame] | 99 | parser.add_argument('filenames', nargs='*', help='Filenames to fix') |
| 100 | args = parser.parse_args(argv) |
| 101 | |
| 102 | status = 0 |
| 103 | |
| 104 | for json_file in args.filenames: |
Anthony Sottile | f5c42a0 | 2020-02-05 11:10:42 -0800 | [diff] [blame] | 105 | with open(json_file, encoding='UTF-8') as f: |
Léo Cavaillé | 55bf22d | 2015-06-10 17:08:48 -0400 | [diff] [blame] | 106 | contents = f.read() |
Léo Cavaillé | 55bf22d | 2015-06-10 17:08:48 -0400 | [diff] [blame] | 107 | |
Anthony Sottile | 17478a0 | 2016-04-14 08:25:52 -0700 | [diff] [blame] | 108 | try: |
| 109 | pretty_contents = _get_pretty_format( |
John Hu | 543c5c7 | 2017-03-16 11:27:34 +0800 | [diff] [blame] | 110 | contents, args.indent, ensure_ascii=not args.no_ensure_ascii, |
Anthony Sottile | b95dcad | 2017-03-20 08:24:58 -0700 | [diff] [blame] | 111 | sort_keys=not args.no_sort_keys, top_keys=args.top_keys, |
Anthony Sottile | 17478a0 | 2016-04-14 08:25:52 -0700 | [diff] [blame] | 112 | ) |
Calum Lind | 00974ef | 2017-12-10 08:57:34 +0000 | [diff] [blame] | 113 | except ValueError: |
Léo Cavaillé | 55bf22d | 2015-06-10 17:08:48 -0400 | [diff] [blame] | 114 | print( |
Anthony Sottile | f5c42a0 | 2020-02-05 11:10:42 -0800 | [diff] [blame] | 115 | f'Input File {json_file} is not a valid JSON, consider using ' |
| 116 | f'check-json', |
Léo Cavaillé | 55bf22d | 2015-06-10 17:08:48 -0400 | [diff] [blame] | 117 | ) |
| 118 | return 1 |
| 119 | |
jack1142 | 1de4fe6 | 2021-03-14 18:21:48 +0100 | [diff] [blame] | 120 | if contents != pretty_contents: |
| 121 | if args.autofix: |
| 122 | _autofix(json_file, pretty_contents) |
| 123 | else: |
| 124 | diff_output = get_diff(contents, pretty_contents, json_file) |
| 125 | sys.stdout.buffer.write(diff_output.encode()) |
| 126 | |
| 127 | status = 1 |
| 128 | |
Léo Cavaillé | 55bf22d | 2015-06-10 17:08:48 -0400 | [diff] [blame] | 129 | return status |
| 130 | |
| 131 | |
| 132 | if __name__ == '__main__': |
Anthony Sottile | 39ab2ed | 2021-10-23 13:23:50 -0400 | [diff] [blame] | 133 | raise SystemExit(main()) |