summaryrefslogtreecommitdiff
path: root/lib/python/qmk/json_encoders.py
diff options
context:
space:
mode:
Diffstat (limited to 'lib/python/qmk/json_encoders.py')
-rwxr-xr-xlib/python/qmk/json_encoders.py192
1 files changed, 192 insertions, 0 deletions
diff --git a/lib/python/qmk/json_encoders.py b/lib/python/qmk/json_encoders.py
new file mode 100755
index 0000000000..9f3da022b4
--- /dev/null
+++ b/lib/python/qmk/json_encoders.py
@@ -0,0 +1,192 @@
+"""Class that pretty-prints QMK info.json files.
+"""
+import json
+from decimal import Decimal
+
+newline = '\n'
+
+
+class QMKJSONEncoder(json.JSONEncoder):
+ """Base class for all QMK JSON encoders.
+ """
+ container_types = (list, tuple, dict)
+ indentation_char = " "
+
+ def __init__(self, *args, **kwargs):
+ super().__init__(*args, **kwargs)
+ self.indentation_level = 0
+
+ if not self.indent:
+ self.indent = 4
+
+ def encode_decimal(self, obj):
+ """Encode a decimal object.
+ """
+ if obj == int(obj): # I can't believe Decimal objects don't have .is_integer()
+ return int(obj)
+
+ return float(obj)
+
+ def encode_list(self, obj):
+ """Encode a list-like object.
+ """
+ if self.primitives_only(obj):
+ return "[" + ", ".join(self.encode(element) for element in obj) + "]"
+
+ else:
+ self.indentation_level += 1
+ output = [self.indent_str + self.encode(element) for element in obj]
+ self.indentation_level -= 1
+
+ return "[\n" + ",\n".join(output) + "\n" + self.indent_str + "]"
+
+ def encode(self, obj):
+ """Encode keymap.json objects for QMK.
+ """
+ if isinstance(obj, Decimal):
+ return self.encode_decimal(obj)
+
+ elif isinstance(obj, (list, tuple)):
+ return self.encode_list(obj)
+
+ elif isinstance(obj, dict):
+ return self.encode_dict(obj)
+
+ else:
+ return super().encode(obj)
+
+ def primitives_only(self, obj):
+ """Returns true if the object doesn't have any container type objects (list, tuple, dict).
+ """
+ if isinstance(obj, dict):
+ obj = obj.values()
+
+ return not any(isinstance(element, self.container_types) for element in obj)
+
+ @property
+ def indent_str(self):
+ return self.indentation_char * (self.indentation_level * self.indent)
+
+
+class InfoJSONEncoder(QMKJSONEncoder):
+ """Custom encoder to make info.json's a little nicer to work with.
+ """
+ def encode_dict(self, obj):
+ """Encode info.json dictionaries.
+ """
+ if obj:
+ if self.indentation_level == 4:
+ # These are part of a layout, put them on a single line.
+ return "{ " + ", ".join(f"{self.encode(key)}: {self.encode(element)}" for key, element in sorted(obj.items())) + " }"
+
+ else:
+ self.indentation_level += 1
+ output = [self.indent_str + f"{json.dumps(key)}: {self.encode(value)}" for key, value in sorted(obj.items(), key=self.sort_dict)]
+ self.indentation_level -= 1
+ return "{\n" + ",\n".join(output) + "\n" + self.indent_str + "}"
+ else:
+ return "{}"
+
+ def sort_dict(self, key):
+ """Forces layout to the back of the sort order.
+ """
+ key = key[0]
+
+ if self.indentation_level == 1:
+ if key == 'manufacturer':
+ return '10keyboard_name'
+
+ elif key == 'keyboard_name':
+ return '11keyboard_name'
+
+ elif key == 'maintainer':
+ return '12maintainer'
+
+ elif key in ('height', 'width'):
+ return '40' + str(key)
+
+ elif key == 'community_layouts':
+ return '97community_layouts'
+
+ elif key == 'layout_aliases':
+ return '98layout_aliases'
+
+ elif key == 'layouts':
+ return '99layouts'
+
+ else:
+ return '50' + str(key)
+
+ return key
+
+
+class KeymapJSONEncoder(QMKJSONEncoder):
+ """Custom encoder to make keymap.json's a little nicer to work with.
+ """
+ def encode_dict(self, obj):
+ """Encode dictionary objects for keymap.json.
+ """
+ if obj:
+ self.indentation_level += 1
+ output_lines = [f"{self.indent_str}{json.dumps(key)}: {self.encode(value)}" for key, value in sorted(obj.items(), key=self.sort_dict)]
+ output = ',\n'.join(output_lines)
+ self.indentation_level -= 1
+
+ return f"{{\n{output}\n{self.indent_str}}}"
+
+ else:
+ return "{}"
+
+ def encode_list(self, obj):
+ """Encode a list-like object.
+ """
+ if self.indentation_level == 2:
+ indent_level = self.indentation_level + 1
+ # We have a list of keycodes
+ layer = [[]]
+
+ for key in obj:
+ if key == 'JSON_NEWLINE':
+ layer.append([])
+ else:
+ layer[-1].append(f'"{key}"')
+
+ layer = [f"{self.indent_str*indent_level}{', '.join(row)}" for row in layer]
+
+ return f"{self.indent_str}[\n{newline.join(layer)}\n{self.indent_str*self.indentation_level}]"
+
+ elif self.primitives_only(obj):
+ return "[" + ", ".join(self.encode(element) for element in obj) + "]"
+
+ else:
+ self.indentation_level += 1
+ output = [self.indent_str + self.encode(element) for element in obj]
+ self.indentation_level -= 1
+
+ return "[\n" + ",\n".join(output) + "\n" + self.indent_str + "]"
+
+ def sort_dict(self, key):
+ """Sorts the hashes in a nice way.
+ """
+ key = key[0]
+
+ if self.indentation_level == 1:
+ if key == 'version':
+ return '00version'
+
+ elif key == 'author':
+ return '01author'
+
+ elif key == 'notes':
+ return '02notes'
+
+ elif key == 'layers':
+ return '98layers'
+
+ elif key == 'documentation':
+ return '99documentation'
+
+ else:
+ return '50' + str(key)
+
+ return key