From ceb85610c77b7487b0b7d742415301922c6b13b6 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Tue, 21 May 2024 22:47:18 +0200 Subject: Adding upstream version 4.5.0+dfsg. Signed-off-by: Daniel Baumann --- pre_commit_hooks/pretty_format_json.py | 133 +++++++++++++++++++++++++++++++++ 1 file changed, 133 insertions(+) create mode 100644 pre_commit_hooks/pretty_format_json.py (limited to 'pre_commit_hooks/pretty_format_json.py') diff --git a/pre_commit_hooks/pretty_format_json.py b/pre_commit_hooks/pretty_format_json.py new file mode 100644 index 0000000..627a11c --- /dev/null +++ b/pre_commit_hooks/pretty_format_json.py @@ -0,0 +1,133 @@ +from __future__ import annotations + +import argparse +import json +import sys +from difflib import unified_diff +from typing import Mapping +from typing import Sequence + + +def _get_pretty_format( + contents: str, + indent: str, + ensure_ascii: bool = True, + sort_keys: bool = True, + top_keys: Sequence[str] = (), +) -> str: + def pairs_first(pairs: Sequence[tuple[str, str]]) -> Mapping[str, str]: + 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: + after.sort() + return dict(before + after) + json_pretty = json.dumps( + json.loads(contents, object_pairs_hook=pairs_first), + indent=indent, + ensure_ascii=ensure_ascii, + ) + return f'{json_pretty}\n' + + +def _autofix(filename: str, new_contents: str) -> None: + print(f'Fixing file {filename}') + with open(filename, 'w', encoding='UTF-8') as f: + f.write(new_contents) + + +def parse_num_to_int(s: str) -> int | str: + """Convert string numbers to int, leaving strings as is.""" + try: + return int(s) + except ValueError: + return s + + +def parse_topkeys(s: str) -> list[str]: + return s.split(',') + + +def get_diff(source: str, target: str, file: str) -> str: + source_lines = source.splitlines(True) + target_lines = target.splitlines(True) + diff = unified_diff(source_lines, target_lines, fromfile=file, tofile=file) + return ''.join(diff) + + +def main(argv: Sequence[str] | None = None) -> int: + parser = argparse.ArgumentParser() + parser.add_argument( + '--autofix', + action='store_true', + dest='autofix', + help='Automatically fixes encountered not-pretty-formatted files', + ) + parser.add_argument( + '--indent', + 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)' + ), + ) + parser.add_argument( + '--no-ensure-ascii', + action='store_true', + dest='no_ensure_ascii', + default=False, + help=( + 'Do NOT convert non-ASCII characters to Unicode escape sequences ' + '(\\uXXXX)' + ), + ) + parser.add_argument( + '--no-sort-keys', + action='store_true', + dest='no_sort_keys', + default=False, + help='Keep JSON nodes in the same order', + ) + 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', + ) + parser.add_argument('filenames', nargs='*', help='Filenames to fix') + args = parser.parse_args(argv) + + status = 0 + + for json_file in args.filenames: + with open(json_file, encoding='UTF-8') as f: + contents = f.read() + + try: + pretty_contents = _get_pretty_format( + contents, args.indent, ensure_ascii=not args.no_ensure_ascii, + sort_keys=not args.no_sort_keys, top_keys=args.top_keys, + ) + except ValueError: + print( + f'Input File {json_file} is not a valid JSON, consider using ' + f'check-json', + ) + return 1 + + 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 + + return status + + +if __name__ == '__main__': + raise SystemExit(main()) -- cgit v1.2.3