mirror of
https://github.com/pre-commit/pre-commit-hooks.git
synced 2025-08-14 09:27:21 +08:00

This adds custom sorting to preferencially add a list of top keys at the start of any json hash in the json document
192 lines
6.3 KiB
Python
192 lines
6.3 KiB
Python
from __future__ import print_function
|
|
|
|
import argparse
|
|
import sys
|
|
from collections import OrderedDict
|
|
|
|
import simplejson
|
|
|
|
class SortableOrderedDict(OrderedDict):
|
|
"""Performs an in-place sort of the keys if you want."""
|
|
def sort(*args, **kwds):
|
|
self = args[0]
|
|
args = args[1:]
|
|
if 'key' not in kwds:
|
|
kwds['key'] = lambda x: x[0]
|
|
if len(args):
|
|
raise TypeError('expected no positional arguments got {0}'.format(len(args)))
|
|
sorted_od = sorted([x for x in self.items()], **kwds)
|
|
self.clear()
|
|
self.update(sorted_od)
|
|
|
|
class TrackedSod(SortableOrderedDict):
|
|
"""Tracks instances of the SortableOrderedDict."""
|
|
_instances = []
|
|
def __init__(self, *args, **kwds):
|
|
super(TrackedSod, self).__init__(*args, **kwds)
|
|
self.__track(self)
|
|
|
|
@classmethod
|
|
def __track(cls, obj):
|
|
cls._instances.append(obj)
|
|
|
|
|
|
def _get_pretty_format(contents, indent, sort_keys=True, top_keys=[]):
|
|
class KeyToCmp(object):
|
|
def __init__(self, obj, *args):
|
|
self.obj = obj[0]
|
|
def __lt__(self, other):
|
|
if self.obj in top_keys and other.obj in top_keys:
|
|
return top_keys.index(self.obj) < top_keys.index(other.obj)
|
|
elif self.obj in top_keys and other.obj not in top_keys:
|
|
return True
|
|
elif self.obj not in top_keys and other.obj in top_keys:
|
|
return False
|
|
else:
|
|
return self.obj < other.obj
|
|
def __gt__(self, other):
|
|
if self.obj in top_keys and other.obj in top_keys:
|
|
return top_keys.index(self.obj) > top_keys.index(other.obj)
|
|
elif self.obj in top_keys and other.obj not in top_keys:
|
|
return False
|
|
elif self.obj not in top_keys and other.obj in top_keys:
|
|
return True
|
|
else:
|
|
return self.obj > other.obj
|
|
def __eq__(self, other):
|
|
if self.obj in top_keys and other.obj in top_keys:
|
|
return top_keys.index(self.obj) == top_keys.index(other.obj)
|
|
elif self.obj in top_keys and other.obj not in top_keys:
|
|
return False
|
|
elif self.obj not in top_keys and other.obj in top_keys:
|
|
return False
|
|
else:
|
|
return self.obj == other.obj
|
|
def __le__(self, other):
|
|
if self.obj in top_keys and other.obj in top_keys:
|
|
return top_keys.index(self.obj) <= top_keys.index(other.obj)
|
|
elif self.obj in top_keys and other.obj not in top_keys:
|
|
return True
|
|
elif self.obj not in top_keys and other.obj in top_keys:
|
|
return False
|
|
else:
|
|
return self.obj <= other.obj
|
|
def __ge__(self, other):
|
|
if self.obj in top_keys and other.obj in top_keys:
|
|
return top_keys.index(self.obj) >= top_keys.index(other.obj)
|
|
elif self.obj in top_keys and other.obj not in top_keys:
|
|
return False
|
|
elif self.obj not in top_keys and other.obj in top_keys:
|
|
return True
|
|
else:
|
|
return self.obj >= other.obj
|
|
def __ne__(self, other):
|
|
if self.obj in top_keys and other.obj in top_keys:
|
|
return top_keys.index(self.obj) != top_keys.index(other.obj)
|
|
elif self.obj in top_keys and other.obj not in top_keys:
|
|
return False
|
|
elif self.obj not in top_keys and other.obj in top_keys:
|
|
return False
|
|
else:
|
|
return self.obj != other.obj
|
|
py_obj = simplejson.loads(contents, object_pairs_hook=TrackedSod)
|
|
if sort_keys:
|
|
for tsod in TrackedSod._instances:
|
|
tsod.sort(key=KeyToCmp)
|
|
# dumps don't end with a newline
|
|
return simplejson.dumps(py_obj, indent=indent) + "\n"
|
|
|
|
def _autofix(filename, new_contents):
|
|
print("Fixing file {0}".format(filename))
|
|
with open(filename, 'w') as f:
|
|
f.write(new_contents)
|
|
|
|
|
|
def parse_indent(s):
|
|
# type: (str) -> str
|
|
try:
|
|
int_indentation_spec = int(s)
|
|
except ValueError:
|
|
if not s.strip():
|
|
return s
|
|
else:
|
|
raise ValueError(
|
|
'Non-whitespace JSON indentation delimiter supplied. ',
|
|
)
|
|
else:
|
|
if int_indentation_spec >= 0:
|
|
return int_indentation_spec * ' '
|
|
else:
|
|
raise ValueError(
|
|
'Negative integer supplied to construct JSON indentation delimiter. ',
|
|
)
|
|
|
|
def parse_topkeys(s):
|
|
# type: (str) -> array
|
|
return s.split(',')
|
|
|
|
def pretty_format_json(argv=None):
|
|
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_indent,
|
|
default=' ',
|
|
help='String used as delimiter for one indentation level',
|
|
)
|
|
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) as f:
|
|
contents = f.read()
|
|
|
|
try:
|
|
pretty_contents = _get_pretty_format(
|
|
contents, args.indent, sort_keys=not args.no_sort_keys,
|
|
top_keys=args.top_keys
|
|
)
|
|
|
|
if contents != pretty_contents:
|
|
print("File {0} is not pretty-formatted".format(json_file))
|
|
|
|
if args.autofix:
|
|
_autofix(json_file, pretty_contents)
|
|
|
|
status = 1
|
|
|
|
except simplejson.JSONDecodeError:
|
|
print(
|
|
"Input File {0} is not a valid JSON, consider using check-json"
|
|
.format(json_file)
|
|
)
|
|
return 1
|
|
|
|
return status
|
|
|
|
|
|
if __name__ == '__main__':
|
|
sys.exit(pretty_format_json())
|