From 9b09e7c6d71b3f8eab71cdc9d1c30a6873d08dc9 Mon Sep 17 00:00:00 2001 From: Ryan Date: Wed, 1 Mar 2023 12:51:18 +1100 Subject: Check all rows have the correct number of columns when parsing `g_led_config` (#19954) --- lib/python/qmk/c_parse.py | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) (limited to 'lib/python/qmk/c_parse.py') diff --git a/lib/python/qmk/c_parse.py b/lib/python/qmk/c_parse.py index 3d73e66091..560e5d3264 100644 --- a/lib/python/qmk/c_parse.py +++ b/lib/python/qmk/c_parse.py @@ -211,10 +211,13 @@ def _coerce_led_token(_type, value): return value_map[value] -def _validate_led_config(matrix, matrix_rows, matrix_indexes, position, position_raw, flags): +def _validate_led_config(matrix, matrix_rows, matrix_cols, matrix_indexes, position, position_raw, flags): # TODO: Improve crude parsing/validation if len(matrix) != matrix_rows and len(matrix) != (matrix_rows / 2): raise ValueError("Unable to parse g_led_config matrix data") + for index, row in enumerate(matrix): + if len(row) != matrix_cols: + raise ValueError(f"Number of columns in row {index} ({len(row)}) does not match matrix ({matrix_cols})") if len(position) != len(flags): raise ValueError(f"Number of g_led_config physical positions ({len(position)}) does not match number of flags ({len(flags)})") if len(matrix_indexes) and (max(matrix_indexes) >= len(flags)): @@ -228,13 +231,16 @@ def _validate_led_config(matrix, matrix_rows, matrix_indexes, position, position def _parse_led_config(file, matrix_cols, matrix_rows): """Return any 'raw' led/rgb matrix config """ - matrix_raw = [] + matrix = [] position_raw = [] flags = [] found_led_config = False bracket_count = 0 section = 0 + current_row_index = 0 + current_row = [] + for _type, value in lex(_preprocess_c_file(file), CLexer()): # Assume g_led_config..stuff..; if value == 'g_led_config': @@ -248,12 +254,16 @@ def _parse_led_config(file, matrix_cols, matrix_rows): if bracket_count == 2: section += 1 elif value == '}': + if section == 1 and bracket_count == 3: + matrix.append(current_row) + current_row = [] + current_row_index += 1 bracket_count -= 1 else: # Assume any non whitespace value here is important enough to stash if _type in [Token.Literal.Number.Integer, Token.Literal.Number.Float, Token.Literal.Number.Hex, Token.Name]: if section == 1 and bracket_count == 3: - matrix_raw.append(_coerce_led_token(_type, value)) + current_row.append(_coerce_led_token(_type, value)) if section == 2 and bracket_count == 3: position_raw.append(_coerce_led_token(_type, value)) if section == 3 and bracket_count == 2: @@ -263,16 +273,15 @@ def _parse_led_config(file, matrix_cols, matrix_rows): return None # Slightly better intrim format - matrix = list(_get_chunks(matrix_raw, matrix_cols)) position = list(_get_chunks(position_raw, 2)) - matrix_indexes = list(filter(lambda x: x is not None, matrix_raw)) + matrix_indexes = list(filter(lambda x: x is not None, sum(matrix, []))) # If we have not found anything - bail with no error if not section: return None # Throw any validation errors - _validate_led_config(matrix, matrix_rows, matrix_indexes, position, position_raw, flags) + _validate_led_config(matrix, matrix_rows, matrix_cols, matrix_indexes, position, position_raw, flags) return (matrix, position, flags) -- cgit v1.2.3 From 91e0457b0ee8f1b7160d8f7591e0f60195db5455 Mon Sep 17 00:00:00 2001 From: Ryan Date: Mon, 13 Mar 2023 23:20:54 +1100 Subject: JSON encoder: improve sorting of layout dict keys (#19974) --- lib/python/qmk/c_parse.py | 2 +- lib/python/qmk/json_encoders.py | 70 +++++++++++++++++++++---------- lib/python/qmk/tests/test_cli_commands.py | 4 +- 3 files changed, 52 insertions(+), 24 deletions(-) (limited to 'lib/python/qmk/c_parse.py') 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(): -- cgit v1.2.3