Skip to content

Commit

Permalink
JSON encoder: improve sorting of layout dict keys (#19974)
Browse files Browse the repository at this point in the history
  • Loading branch information
fauxpark authored Mar 13, 2023
1 parent cf5e9d1 commit 91e0457
Show file tree
Hide file tree
Showing 3 changed files with 52 additions and 24 deletions.
2 changes: 1 addition & 1 deletion lib/python/qmk/c_parse.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@

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')
Expand Down
70 changes: 49 additions & 21 deletions lib/python/qmk/json_encoders.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,30 +27,39 @@ def encode_decimal(self, obj):

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):
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]

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)
Expand All @@ -71,22 +80,43 @@ def indent_str(self):
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.
"""
Expand Down Expand Up @@ -120,21 +150,19 @@ def sort_dict(self, key):
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:
Expand Down
4 changes: 2 additions & 2 deletions lib/python/qmk/tests/test_cli_commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -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():
Expand All @@ -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():
Expand Down

0 comments on commit 91e0457

Please sign in to comment.