| #!/usr/bin/env python3 | |
| import io | |
| import json | |
| import re | |
| import sys | |
| from collections import OrderedDict | |
| def extract_numbers(s): | |
| """ | |
| >>> extract_numbers("CLK_HROW_WR10END2_3") | |
| ('CLK_HROW_WR', 10, 'END', 2, '_', 3) | |
| >>> extract_numbers("VBRK_WR1END2") | |
| ('VBRK_WR', 1, 'END', 2) | |
| """ | |
| bits = [] | |
| for m in re.finditer("([^0-9]*)([0-9]*)", s): | |
| if m.group(1): | |
| bits.append(m.group(1)) | |
| if m.group(2): | |
| bits.append(int(m.group(2))) | |
| return tuple(bits) | |
| def sort(data): | |
| """Sort data types via "natural" numbers. | |
| Supports all the basic Python data types. | |
| >>> o = sort({ | |
| ... 't1': {'c','b'}, # Set | |
| ... 't2': ('a2', 'a10', 'e'), # Tuple | |
| ... 't3': [5, 3, 2], # List | |
| ... 't4': { # Dictionary | |
| ... 'a4': ('b2', 'b3'), | |
| ... 'a2': ['c1', 'c2', 'c0', 'c10'], | |
| ... }, | |
| ... 't5': ['a1b5', 'a2b1', 'a1b1'], | |
| ... }) | |
| >>> for t in o: | |
| ... print(t+':', o[t]) | |
| t1: ('b', 'c') | |
| t2: ('a2', 'a10', 'e') | |
| t3: (5, 3, 2) | |
| t4: OrderedDict([('a2', ('c1', 'c2', 'c0', 'c10')), ('a4', ('b2', 'b3'))]) | |
| t5: ('a1b5', 'a2b1', 'a1b1') | |
| Don't mangle "pairs" | |
| >>> sort([('b', 'c'), ('2', '1')]) | |
| (('b', 'c'), ('2', '1')) | |
| """ | |
| def key(o): | |
| if o is None: | |
| return None | |
| elif isinstance(o, str): | |
| return extract_numbers(o) | |
| elif isinstance(o, int): | |
| return o | |
| elif isinstance(o, (list, tuple)): | |
| return tuple(key(i) for i in o) | |
| elif isinstance(o, dict): | |
| return tuple((key(k), key(v)) for k, v in o.items()) | |
| elif isinstance(o, set): | |
| return tuple(key(k) for k in o) | |
| raise ValueError(repr(o)) | |
| def rsorter(o): | |
| if isinstance(o, dict): | |
| nitems = [] | |
| for k, v in o.items(): | |
| nitems.append((key(k), k, rsorter(v))) | |
| nitems.sort(key=lambda n: n[0]) | |
| new_dict = OrderedDict() | |
| for _, k, v in nitems: | |
| new_dict[k] = v | |
| return new_dict | |
| elif isinstance(o, set): | |
| return tuple(sorted((rsorter(v) for v in o), key=key)) | |
| elif isinstance(o, (tuple, list)): | |
| return tuple(rsorter(v) for v in o) | |
| else: | |
| return o | |
| return rsorter(data) | |
| def pprint(f, data): | |
| detach = False | |
| if not isinstance(f, io.TextIOBase): | |
| detach = True | |
| f = io.TextIOWrapper(f) | |
| data = sort(data) | |
| json.dump(data, f, indent=4) | |
| f.write('\n') | |
| f.flush() | |
| if detach: | |
| f.detach() | |
| if __name__ == "__main__": | |
| if len(sys.argv) == 1: | |
| import doctest | |
| doctest.testmod() | |
| else: | |
| assert len(sys.argv) == 2 | |
| d = json.load(open(sys.argv[1])) | |
| pprint(sys.stdout, d) |