summaryrefslogtreecommitdiffstats
path: root/pre_commit_hooks/pretty_format_json.py
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2022-01-30 11:02:58 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2022-01-30 11:02:58 +0000
commit307d578d739eb254ef3000fdde94271af9b8923e (patch)
treecf256ef8bea7b1cad51d3a662dd4d6885156e98b /pre_commit_hooks/pretty_format_json.py
parentInitial commit. (diff)
downloadpre-commit-hooks-307d578d739eb254ef3000fdde94271af9b8923e.tar.xz
pre-commit-hooks-307d578d739eb254ef3000fdde94271af9b8923e.zip
Adding upstream version 4.1.0.upstream/4.1.0
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'pre_commit_hooks/pretty_format_json.py')
-rw-r--r--pre_commit_hooks/pretty_format_json.py135
1 files changed, 135 insertions, 0 deletions
diff --git a/pre_commit_hooks/pretty_format_json.py b/pre_commit_hooks/pretty_format_json.py
new file mode 100644
index 0000000..33ad5a1
--- /dev/null
+++ b/pre_commit_hooks/pretty_format_json.py
@@ -0,0 +1,135 @@
+import argparse
+import json
+import sys
+from difflib import unified_diff
+from typing import List
+from typing import Mapping
+from typing import Optional
+from typing import Sequence
+from typing import Tuple
+from typing import Union
+
+
+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) -> Union[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: Optional[Sequence[str]] = 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())