summary refs log tree commit diff
path: root/lib
diff options
context:
space:
mode:
authorRyan <fauxpark@gmail.com>2023-03-13 23:20:54 +1100
committerGitHub <noreply@github.com>2023-03-13 23:20:54 +1100
commit91e0457b0ee8f1b7160d8f7591e0f60195db5455 (patch)
tree67732e1f2a3f8f16bd101518a76f66d72ea0fb64 /lib
parentcf5e9d172d38e9a9b65cb568631259bf1e2bfec6 (diff)
JSON encoder: improve sorting of layout dict keys (#19974)
Diffstat (limited to 'lib')
-rw-r--r--lib/python/qmk/c_parse.py2
-rwxr-xr-xlib/python/qmk/json_encoders.py70
-rw-r--r--lib/python/qmk/tests/test_cli_commands.py4
3 files changed, 52 insertions, 24 deletions
diff --git a/lib/python/qmk/c_parse.py b/lib/python/qmk/c_parse.py
index 501c41972f..663688e240 100644
--- a/lib/python/qmk/c_parse.py
+++ b/lib/python/qmk/c_parse.py
@@ -11,7 +11,7 @@ from milc import cli
 
 from qmk.comment_remover import comment_remover
 
-default_key_entry = {'x': -1, 'y': 0, 'w': 1}
+default_key_entry = {'x': -1, 'y': 0}
 single_comment_regex = re.compile(r'\s+/[/*].*$')
 multi_comment_regex = re.compile(r'/\*(.|\n)*?\*/', re.MULTILINE)
 layout_macro_define_regex = re.compile(r'^#\s*define')
diff --git a/lib/python/qmk/json_encoders.py b/lib/python/qmk/json_encoders.py
index f968b3dbb2..e61c63aff3 100755
--- a/lib/python/qmk/json_encoders.py
+++ b/lib/python/qmk/json_encoders.py
@@ -27,7 +27,10 @@ class QMKJSONEncoder(json.JSONEncoder):
 
         return float(obj)
 
-    def encode_list(self, obj):
+    def encode_dict_single_line(self, obj):
+        return "{" + ", ".join(f"{self.encode(key)}: {self.encode(element)}" for key, element in sorted(obj.items(), key=self.sort_layout)) + "}"
+
+    def encode_list(self, obj, key=None):
         """Encode a list-like object.
         """
         if self.primitives_only(obj):
@@ -35,22 +38,28 @@ class QMKJSONEncoder(json.JSONEncoder):
 
         else:
             self.indentation_level += 1
-            output = [self.indent_str + self.encode(element) for element in obj]
+
+            if key in ('layout', 'rotary'):
+                # These are part of a layout or led/encoder config, put them on a single line.
+                output = [self.indent_str + self.encode_dict_single_line(element) for element in obj]
+            else:
+                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):
+    def encode(self, obj, key=None):
         """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)
+            return self.encode_list(obj, key)
 
         elif isinstance(obj, dict):
-            return self.encode_dict(obj)
+            return self.encode_dict(obj, key)
 
         else:
             return super().encode(obj)
@@ -71,22 +80,43 @@ class QMKJSONEncoder(json.JSONEncoder):
 class InfoJSONEncoder(QMKJSONEncoder):
     """Custom encoder to make info.json's a little nicer to work with.
     """
-    def encode_dict(self, obj):
+    def encode_dict(self, obj, key):
         """Encode info.json dictionaries.
         """
         if obj:
-            if set(("x", "y")).issubset(obj.keys()):
-                # These are part of a layout/led_config, 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 + "}"
+            self.indentation_level += 1
+            output = [self.indent_str + f"{json.dumps(k)}: {self.encode(v, k)}" for k, v 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_layout(self, key):
+        key = key[0]
+
+        if key == 'label':
+            return '00label'
+
+        elif key == 'matrix':
+            return '01matrix'
+
+        elif key == 'x':
+            return '02x'
+
+        elif key == 'y':
+            return '03y'
+
+        elif key == 'w':
+            return '04w'
+
+        elif key == 'h':
+            return '05h'
+
+        elif key == 'flags':
+            return '06flags'
+
+        return key
+
     def sort_dict(self, key):
         """Forces layout to the back of the sort order.
         """
@@ -120,21 +150,19 @@ class InfoJSONEncoder(QMKJSONEncoder):
 class KeymapJSONEncoder(QMKJSONEncoder):
     """Custom encoder to make keymap.json's a little nicer to work with.
     """
-    def encode_dict(self, obj):
+    def encode_dict(self, obj, key):
         """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)
+            output = [self.indent_str + f"{json.dumps(k)}: {self.encode(v, k)}" for k, v in sorted(obj.items(), key=self.sort_dict)]
             self.indentation_level -= 1
-
-            return f"{{\n{output}\n{self.indent_str}}}"
+            return "{\n" + ",\n".join(output) + "\n" + self.indent_str + "}"
 
         else:
             return "{}"
 
-    def encode_list(self, obj):
+    def encode_list(self, obj, k=None):
         """Encode a list-like object.
         """
         if self.indentation_level == 2:
diff --git a/lib/python/qmk/tests/test_cli_commands.py b/lib/python/qmk/tests/test_cli_commands.py
index e598b281a6..28838ec368 100644
--- a/lib/python/qmk/tests/test_cli_commands.py
+++ b/lib/python/qmk/tests/test_cli_commands.py
@@ -291,7 +291,7 @@ def test_generate_version_h():
 def test_format_json_keyboard():
     result = check_subcommand('format-json', '--format', 'keyboard', 'lib/python/qmk/tests/minimal_info.json')
     check_returncode(result)
-    assert result.stdout == '{\n    "keyboard_name": "tester",\n    "maintainer": "qmk",\n    "layouts": {\n        "LAYOUT": {\n            "layout": [\n                { "label": "KC_A", "matrix": [0, 0], "x": 0, "y": 0 }\n            ]\n        }\n    }\n}\n'
+    assert result.stdout == '{\n    "keyboard_name": "tester",\n    "maintainer": "qmk",\n    "layouts": {\n        "LAYOUT": {\n            "layout": [\n                {"label": "KC_A", "matrix": [0, 0], "x": 0, "y": 0}\n            ]\n        }\n    }\n}\n'
 
 
 def test_format_json_keymap():
@@ -303,7 +303,7 @@ def test_format_json_keymap():
 def test_format_json_keyboard_auto():
     result = check_subcommand('format-json', '--format', 'auto', 'lib/python/qmk/tests/minimal_info.json')
     check_returncode(result)
-    assert result.stdout == '{\n    "keyboard_name": "tester",\n    "maintainer": "qmk",\n    "layouts": {\n        "LAYOUT": {\n            "layout": [\n                { "label": "KC_A", "matrix": [0, 0], "x": 0, "y": 0 }\n            ]\n        }\n    }\n}\n'
+    assert result.stdout == '{\n    "keyboard_name": "tester",\n    "maintainer": "qmk",\n    "layouts": {\n        "LAYOUT": {\n            "layout": [\n                {"label": "KC_A", "matrix": [0, 0], "x": 0, "y": 0}\n            ]\n        }\n    }\n}\n'
 
 
 def test_format_json_keymap_auto():