2022-01-16 08:24:05 +08:00
|
|
|
from __future__ import annotations
|
|
|
|
|
2015-06-11 05:08:48 +08:00
|
|
|
import argparse
|
2017-12-10 16:57:34 +08:00
|
|
|
import json
|
2021-03-15 01:21:48 +08:00
|
|
|
import sys
|
2024-10-12 07:30:07 +08:00
|
|
|
from collections.abc import Mapping
|
|
|
|
from collections.abc import Sequence
|
2019-09-25 03:42:24 +08:00
|
|
|
from difflib import unified_diff
|
2016-04-14 16:30:42 +08:00
|
|
|
|
2016-11-04 00:41:23 +08:00
|
|
|
|
2019-02-12 11:56:15 +08:00
|
|
|
def _get_pretty_format(
|
2020-02-06 03:10:42 +08:00
|
|
|
contents: str,
|
|
|
|
indent: str,
|
|
|
|
ensure_ascii: bool = True,
|
|
|
|
sort_keys: bool = True,
|
|
|
|
top_keys: Sequence[str] = (),
|
|
|
|
) -> str:
|
2022-01-16 08:24:05 +08:00
|
|
|
def pairs_first(pairs: Sequence[tuple[str, str]]) -> Mapping[str, str]:
|
2016-11-04 06:47:21 +08:00
|
|
|
before = [pair for pair in pairs if pair[0] in top_keys]
|
|
|
|
before = sorted(before, key=lambda x: top_keys.index(x[0]))
|
|
|
|
after = [pair for pair in pairs if pair[0] not in top_keys]
|
|
|
|
if sort_keys:
|
2020-02-06 03:10:42 +08:00
|
|
|
after.sort()
|
|
|
|
return dict(before + after)
|
2017-12-10 16:57:34 +08:00
|
|
|
json_pretty = json.dumps(
|
|
|
|
json.loads(contents, object_pairs_hook=pairs_first),
|
2017-03-16 11:27:34 +08:00
|
|
|
indent=indent,
|
2017-03-20 23:24:58 +08:00
|
|
|
ensure_ascii=ensure_ascii,
|
2017-12-10 16:57:34 +08:00
|
|
|
)
|
2020-02-06 03:10:42 +08:00
|
|
|
return f'{json_pretty}\n'
|
2015-06-11 05:08:48 +08:00
|
|
|
|
2016-11-04 06:49:04 +08:00
|
|
|
|
2020-02-06 03:10:42 +08:00
|
|
|
def _autofix(filename: str, new_contents: str) -> None:
|
|
|
|
print(f'Fixing file {filename}')
|
|
|
|
with open(filename, 'w', encoding='UTF-8') as f:
|
2015-06-11 05:08:48 +08:00
|
|
|
f.write(new_contents)
|
|
|
|
|
|
|
|
|
2022-01-16 08:24:05 +08:00
|
|
|
def parse_num_to_int(s: str) -> int | str:
|
2017-12-10 17:34:36 +08:00
|
|
|
"""Convert string numbers to int, leaving strings as is."""
|
2016-06-11 02:16:00 +08:00
|
|
|
try:
|
2017-12-10 17:34:36 +08:00
|
|
|
return int(s)
|
2016-06-11 02:16:00 +08:00
|
|
|
except ValueError:
|
2017-12-10 17:34:36 +08:00
|
|
|
return s
|
2016-06-11 02:16:00 +08:00
|
|
|
|
2016-11-04 06:54:48 +08:00
|
|
|
|
2022-01-16 08:24:05 +08:00
|
|
|
def parse_topkeys(s: str) -> list[str]:
|
2016-11-04 00:41:23 +08:00
|
|
|
return s.split(',')
|
2016-06-11 02:16:00 +08:00
|
|
|
|
2016-11-04 06:54:48 +08:00
|
|
|
|
2020-02-06 03:10:42 +08:00
|
|
|
def get_diff(source: str, target: str, file: str) -> str:
|
2019-09-14 02:30:52 +08:00
|
|
|
source_lines = source.splitlines(True)
|
|
|
|
target_lines = target.splitlines(True)
|
2019-09-25 03:42:24 +08:00
|
|
|
diff = unified_diff(source_lines, target_lines, fromfile=file, tofile=file)
|
|
|
|
return ''.join(diff)
|
2019-08-17 00:38:41 +08:00
|
|
|
|
|
|
|
|
2022-01-16 08:24:05 +08:00
|
|
|
def main(argv: Sequence[str] | None = None) -> int:
|
2015-06-11 05:08:48 +08:00
|
|
|
parser = argparse.ArgumentParser()
|
|
|
|
parser.add_argument(
|
|
|
|
'--autofix',
|
|
|
|
action='store_true',
|
|
|
|
dest='autofix',
|
2016-04-14 23:25:52 +08:00
|
|
|
help='Automatically fixes encountered not-pretty-formatted files',
|
2015-06-11 05:08:48 +08:00
|
|
|
)
|
|
|
|
parser.add_argument(
|
|
|
|
'--indent',
|
2017-12-10 17:34:36 +08:00
|
|
|
type=parse_num_to_int,
|
|
|
|
default='2',
|
|
|
|
help=(
|
|
|
|
'The number of indent spaces or a string to be used as delimiter'
|
|
|
|
' for indentation level e.g. 4 or "\t" (Default: 2)'
|
|
|
|
),
|
2015-06-11 05:08:48 +08:00
|
|
|
)
|
2017-03-16 11:27:34 +08:00
|
|
|
parser.add_argument(
|
|
|
|
'--no-ensure-ascii',
|
|
|
|
action='store_true',
|
|
|
|
dest='no_ensure_ascii',
|
|
|
|
default=False,
|
2019-02-12 11:56:15 +08:00
|
|
|
help=(
|
|
|
|
'Do NOT convert non-ASCII characters to Unicode escape sequences '
|
|
|
|
'(\\uXXXX)'
|
|
|
|
),
|
2017-03-16 11:27:34 +08:00
|
|
|
)
|
2016-03-13 06:04:33 +08:00
|
|
|
parser.add_argument(
|
|
|
|
'--no-sort-keys',
|
|
|
|
action='store_true',
|
|
|
|
dest='no_sort_keys',
|
|
|
|
default=False,
|
2016-04-14 23:25:52 +08:00
|
|
|
help='Keep JSON nodes in the same order',
|
2016-03-13 06:04:33 +08:00
|
|
|
)
|
2016-11-04 00:41:23 +08:00
|
|
|
parser.add_argument(
|
|
|
|
'--top-keys',
|
|
|
|
type=parse_topkeys,
|
|
|
|
dest='top_keys',
|
|
|
|
default=[],
|
|
|
|
help='Ordered list of keys to keep at the top of JSON hashes',
|
|
|
|
)
|
2015-06-11 05:08:48 +08:00
|
|
|
parser.add_argument('filenames', nargs='*', help='Filenames to fix')
|
|
|
|
args = parser.parse_args(argv)
|
|
|
|
|
|
|
|
status = 0
|
|
|
|
|
|
|
|
for json_file in args.filenames:
|
2020-02-06 03:10:42 +08:00
|
|
|
with open(json_file, encoding='UTF-8') as f:
|
2015-06-11 05:08:48 +08:00
|
|
|
contents = f.read()
|
|
|
|
|
2016-04-14 23:25:52 +08:00
|
|
|
try:
|
|
|
|
pretty_contents = _get_pretty_format(
|
2017-03-16 11:27:34 +08:00
|
|
|
contents, args.indent, ensure_ascii=not args.no_ensure_ascii,
|
2017-03-20 23:24:58 +08:00
|
|
|
sort_keys=not args.no_sort_keys, top_keys=args.top_keys,
|
2016-04-14 23:25:52 +08:00
|
|
|
)
|
2017-12-10 16:57:34 +08:00
|
|
|
except ValueError:
|
2015-06-11 05:08:48 +08:00
|
|
|
print(
|
2020-02-06 03:10:42 +08:00
|
|
|
f'Input File {json_file} is not a valid JSON, consider using '
|
|
|
|
f'check-json',
|
2015-06-11 05:08:48 +08:00
|
|
|
)
|
2021-03-15 01:21:48 +08:00
|
|
|
status = 1
|
2024-04-13 04:22:10 +08:00
|
|
|
else:
|
|
|
|
if contents != pretty_contents:
|
|
|
|
if args.autofix:
|
|
|
|
_autofix(json_file, pretty_contents)
|
|
|
|
else:
|
|
|
|
diff_output = get_diff(
|
|
|
|
contents,
|
|
|
|
pretty_contents,
|
|
|
|
json_file,
|
|
|
|
)
|
|
|
|
sys.stdout.buffer.write(diff_output.encode())
|
|
|
|
|
|
|
|
status = 1
|
2021-03-15 01:21:48 +08:00
|
|
|
|
2015-06-11 05:08:48 +08:00
|
|
|
return status
|
|
|
|
|
|
|
|
|
|
|
|
if __name__ == '__main__':
|
2021-10-24 01:23:50 +08:00
|
|
|
raise SystemExit(main())
|