qmk_firmware

QMK firmware for my keyboards (Corne, Sweep Ferris) and trackball (Ploopy Adept)
Log | Files | Refs | Submodules | LICENSE

json_encoders.py (7836B)


      1 """Class that pretty-prints QMK info.json files.
      2 """
      3 import json
      4 from decimal import Decimal
      5 
      6 newline = '\n'
      7 
      8 
      9 class QMKJSONEncoder(json.JSONEncoder):
     10     """Base class for all QMK JSON encoders.
     11     """
     12     container_types = (list, tuple, dict)
     13     indentation_char = " "
     14 
     15     def __init__(self, *args, **kwargs):
     16         super().__init__(*args, **kwargs)
     17         self.indentation_level = 0
     18 
     19         if not self.indent:
     20             self.indent = 4
     21 
     22     def encode_decimal(self, obj):
     23         """Encode a decimal object.
     24         """
     25         if obj == int(obj):  # I can't believe Decimal objects don't have .is_integer()
     26             return int(obj)
     27 
     28         return float(obj)
     29 
     30     def encode_dict(self, obj, path):
     31         """Encode a dict-like object.
     32         """
     33         if obj:
     34             self.indentation_level += 1
     35 
     36             items = sorted(obj.items(), key=self.sort_dict) if self.sort_keys else obj.items()
     37             output = [self.indent_str + f"{json.dumps(key)}: {self.encode(value, path + [key])}" for key, value in items]
     38 
     39             self.indentation_level -= 1
     40 
     41             return "{\n" + ",\n".join(output) + "\n" + self.indent_str + "}"
     42         else:
     43             return "{}"
     44 
     45     def encode_dict_single_line(self, obj, path):
     46         """Encode a dict-like object onto a single line.
     47         """
     48         return "{" + ", ".join(f"{json.dumps(key)}: {self.encode(value, path + [key])}" for key, value in sorted(obj.items(), key=self.sort_layout)) + "}"
     49 
     50     def encode_list(self, obj, path):
     51         """Encode a list-like object.
     52         """
     53         if self.primitives_only(obj):
     54             return "[" + ", ".join(self.encode(value, path + [index]) for index, value in enumerate(obj)) + "]"
     55 
     56         else:
     57             self.indentation_level += 1
     58 
     59             if path[-1] in ('layout', 'rotary'):
     60                 # These are part of a LED layout or encoder config, put them on a single line
     61                 output = [self.indent_str + self.encode_dict_single_line(value, path + [index]) for index, value in enumerate(obj)]
     62             else:
     63                 output = [self.indent_str + self.encode(value, path + [index]) for index, value in enumerate(obj)]
     64 
     65             self.indentation_level -= 1
     66 
     67             return "[\n" + ",\n".join(output) + "\n" + self.indent_str + "]"
     68 
     69     def encode(self, obj, path=[]):
     70         """Encode JSON objects for QMK.
     71         """
     72         if isinstance(obj, Decimal):
     73             return self.encode_decimal(obj)
     74 
     75         elif isinstance(obj, (list, tuple)):
     76             return self.encode_list(obj, path)
     77 
     78         elif isinstance(obj, dict):
     79             return self.encode_dict(obj, path)
     80 
     81         else:
     82             return super().encode(obj)
     83 
     84     def primitives_only(self, obj):
     85         """Returns true if the object doesn't have any container type objects (list, tuple, dict).
     86         """
     87         if isinstance(obj, dict):
     88             obj = obj.values()
     89 
     90         return not any(isinstance(element, self.container_types) for element in obj)
     91 
     92     @property
     93     def indent_str(self):
     94         return self.indentation_char * (self.indentation_level * self.indent)
     95 
     96 
     97 class InfoJSONEncoder(QMKJSONEncoder):
     98     """Custom encoder to make info.json's a little nicer to work with.
     99     """
    100     def sort_layout(self, item):
    101         """Sorts the hashes in a nice way.
    102         """
    103         key = item[0]
    104 
    105         if key == 'label':
    106             return '00label'
    107 
    108         elif key == 'matrix':
    109             return '01matrix'
    110 
    111         elif key == 'x':
    112             return '02x'
    113 
    114         elif key == 'y':
    115             return '03y'
    116 
    117         elif key == 'w':
    118             return '04w'
    119 
    120         elif key == 'h':
    121             return '05h'
    122 
    123         elif key == 'flags':
    124             return '06flags'
    125 
    126         return key
    127 
    128     def sort_dict(self, item):
    129         """Forces layout to the back of the sort order.
    130         """
    131         key = item[0]
    132 
    133         if self.indentation_level == 1:
    134             if key == 'manufacturer':
    135                 return '10manufacturer'
    136 
    137             elif key == 'keyboard_name':
    138                 return '11keyboard_name'
    139 
    140             elif key == 'maintainer':
    141                 return '12maintainer'
    142 
    143             elif key == 'community_layouts':
    144                 return '97community_layouts'
    145 
    146             elif key == 'layout_aliases':
    147                 return '98layout_aliases'
    148 
    149             elif key == 'layouts':
    150                 return '99layouts'
    151 
    152             else:
    153                 return '50' + str(key)
    154 
    155         return key
    156 
    157 
    158 class KeymapJSONEncoder(QMKJSONEncoder):
    159     """Custom encoder to make keymap.json's a little nicer to work with.
    160     """
    161     def encode_list(self, obj, path):
    162         """Encode a list-like object.
    163         """
    164         if self.indentation_level == 2:
    165             indent_level = self.indentation_level + 1
    166             # We have a list of keycodes
    167             layer = [[]]
    168 
    169             for key in obj:
    170                 if key == 'JSON_NEWLINE':
    171                     layer.append([])
    172                 else:
    173                     if isinstance(key, dict):
    174                         # We have a macro
    175 
    176                         # TODO: Add proper support for nicely formatting keymap.json macros
    177                         layer[-1].append(f'{self.encode(key)}')
    178                     else:
    179                         layer[-1].append(f'"{key}"')
    180 
    181             layer = [f"{self.indent_str * indent_level}{', '.join(row)}" for row in layer]
    182 
    183             return f"{self.indent_str}[\n{newline.join(layer)}\n{self.indent_str * self.indentation_level}]"
    184 
    185         elif self.primitives_only(obj):
    186             return "[" + ", ".join(self.encode(element) for element in obj) + "]"
    187 
    188         else:
    189             self.indentation_level += 1
    190             output = [self.indent_str + self.encode(element) for element in obj]
    191             self.indentation_level -= 1
    192 
    193             return "[\n" + ",\n".join(output) + "\n" + self.indent_str + "]"
    194 
    195     def sort_dict(self, item):
    196         """Sorts the hashes in a nice way.
    197         """
    198         key = item[0]
    199 
    200         if self.indentation_level == 1:
    201             if key == 'version':
    202                 return '00version'
    203 
    204             elif key == 'author':
    205                 return '01author'
    206 
    207             elif key == 'notes':
    208                 return '02notes'
    209 
    210             elif key == 'layers':
    211                 return '98layers'
    212 
    213             elif key == 'documentation':
    214                 return '99documentation'
    215 
    216             else:
    217                 return '50' + str(key)
    218 
    219         return key
    220 
    221 
    222 class UserspaceJSONEncoder(QMKJSONEncoder):
    223     """Custom encoder to make userspace qmk.json's a little nicer to work with.
    224     """
    225     def sort_dict(self, item):
    226         """Sorts the hashes in a nice way.
    227         """
    228         key = item[0]
    229 
    230         if self.indentation_level == 1:
    231             if key == 'userspace_version':
    232                 return '00userspace_version'
    233 
    234             if key == 'build_targets':
    235                 return '01build_targets'
    236 
    237         return key
    238 
    239 
    240 class CommunityModuleJSONEncoder(QMKJSONEncoder):
    241     """Custom encoder to make qmk_module.json's a little nicer to work with.
    242     """
    243     def sort_dict(self, item):
    244         """Sorts the hashes in a nice way.
    245         """
    246         key = item[0]
    247 
    248         if self.indentation_level == 1:
    249             if key == 'module_name':
    250                 return '00module_name'
    251             if key == 'maintainer':
    252                 return '01maintainer'
    253             if key == 'license':
    254                 return '02license'
    255             if key == 'url':
    256                 return '03url'
    257             if key == 'features':
    258                 return '04features'
    259             if key == 'keycodes':
    260                 return '05keycodes'
    261         elif self.indentation_level == 3:  # keycodes
    262             if key == 'key':
    263                 return '00key'
    264             if key == 'aliases':
    265                 return '01aliases'
    266 
    267         return key