Skip to content

Commit 962bc8d

Browse files
Use the schema to eliminate custom code (qmk#11108)
* use the schema to eliminate custom code * Update docs/reference_info_json.md Co-authored-by: Ryan <[email protected]> * make flake8 happy * bugfix * do not overwrite make vars from json Co-authored-by: Ryan <[email protected]>
1 parent c550047 commit 962bc8d

File tree

8 files changed

+75
-54
lines changed

8 files changed

+75
-54
lines changed

data/schemas/keyboard.jsonschema

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525
},
2626
"processor": {
2727
"type": "string",
28-
"enum": ["MK20DX128", "MK20DX256", "MKL26Z64", "STM32F042", "STM32F072", "STM32F103", "STM32F303", "STM32F401", "STM32F411", "at90usb1286", "at90usb646", "atmega16u2", "atmega328p", "atmega32a", "atmega32u2", "atmega32u4", "attiny85", "cortex-m4"]
28+
"enum": ["MK20DX128", "MK20DX256", "MKL26Z64", "STM32F042", "STM32F072", "STM32F103", "STM32F303", "STM32F401", "STM32F411", "at90usb1286", "at90usb646", "atmega16u2", "atmega328p", "atmega32a", "atmega32u2", "atmega32u4", "attiny85", "cortex-m4", "unknown"]
2929
},
3030
"bootloader": {
3131
"type": "string",

docs/reference_info_json.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,7 @@ Example:
106106
["A7", "B1"],
107107
[null, "B2"]
108108
]
109+
}
109110
}
110111
```
111112

lib/python/qmk/cli/generate/info_json.py

Lines changed: 32 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,41 @@
44
"""
55
import json
66

7+
from jsonschema import Draft7Validator, validators
78
from milc import cli
89

9-
from qmk.info_json_encoder import InfoJSONEncoder
1010
from qmk.decorators import automagic_keyboard, automagic_keymap
11-
from qmk.info import info_json
11+
from qmk.info import info_json, _jsonschema
12+
from qmk.info_json_encoder import InfoJSONEncoder
1213
from qmk.path import is_keyboard
1314

1415

16+
def pruning_validator(validator_class):
17+
"""Extends Draft7Validator to remove properties that aren't specified in the schema.
18+
"""
19+
validate_properties = validator_class.VALIDATORS["properties"]
20+
21+
def remove_additional_properties(validator, properties, instance, schema):
22+
for prop in list(instance.keys()):
23+
if prop not in properties:
24+
del instance[prop]
25+
26+
for error in validate_properties(validator, properties, instance, schema):
27+
yield error
28+
29+
return validators.extend(validator_class, {"properties": remove_additional_properties})
30+
31+
32+
def strip_info_json(kb_info_json):
33+
"""Remove the API-only properties from the info.json.
34+
"""
35+
pruning_draft_7_validator = pruning_validator(Draft7Validator)
36+
schema = _jsonschema('keyboard')
37+
validator = pruning_draft_7_validator(schema).validate
38+
39+
return validator(kb_info_json)
40+
41+
1542
@cli.argument('-kb', '--keyboard', help='Keyboard to show info for.')
1643
@cli.argument('-km', '--keymap', help='Show the layers for a JSON keymap too.')
1744
@cli.subcommand('Generate an info.json file for a keyboard.', hidden=False if cli.config.user.developer else True)
@@ -22,7 +49,7 @@ def generate_info_json(cli):
2249
"""
2350
# Determine our keyboard(s)
2451
if not cli.config.generate_info_json.keyboard:
25-
cli.log.error('Missing paramater: --keyboard')
52+
cli.log.error('Missing parameter: --keyboard')
2653
cli.subcommands['info'].print_help()
2754
return False
2855

@@ -32,18 +59,7 @@ def generate_info_json(cli):
3259

3360
# Build the info.json file
3461
kb_info_json = info_json(cli.config.generate_info_json.keyboard)
35-
pared_down_json = {}
36-
37-
for key in ('manufacturer', 'maintainer', 'usb', 'keyboard_name', 'width', 'height', 'debounce', 'diode_direction', 'features', 'community_layouts', 'layout_aliases', 'matrix_pins', 'rgblight', 'url'):
38-
if key in kb_info_json:
39-
pared_down_json[key] = kb_info_json[key]
40-
41-
pared_down_json['layouts'] = {}
42-
if 'layouts' in kb_info_json:
43-
for layout_name, layout in kb_info_json['layouts'].items():
44-
pared_down_json['layouts'][layout_name] = {}
45-
pared_down_json['layouts'][layout_name]['key_count'] = layout.get('key_count', len(layout['layout']))
46-
pared_down_json['layouts'][layout_name]['layout'] = layout['layout']
62+
strip_info_json(kb_info_json)
4763

4864
# Display the results
49-
print(json.dumps(pared_down_json, indent=2, cls=InfoJSONEncoder))
65+
print(json.dumps(kb_info_json, indent=2, cls=InfoJSONEncoder))

lib/python/qmk/cli/generate/layouts.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,10 @@ def generate_layouts(cli):
5454
if kb_info_json['layouts'][layout_name]['c_macro']:
5555
continue
5656

57+
if 'matrix' not in kb_info_json['layouts'][layout_name]['layout'][0]:
58+
cli.log.debug('%s/%s: No matrix data!', cli.config.generate_layouts.keyboard, layout_name)
59+
continue
60+
5761
layout_keys = []
5862
layout_matrix = [['KC_NO' for i in range(col_num)] for i in range(row_num)]
5963

lib/python/qmk/cli/generate/rules_mk.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -37,26 +37,26 @@ def generate_rules_mk(cli):
3737

3838
# Bring in settings
3939
for info_key, rule_key in info_to_rules.items():
40-
rules_mk_lines.append(f'{rule_key} := {kb_info_json[info_key]}')
40+
rules_mk_lines.append(f'{rule_key} ?= {kb_info_json[info_key]}')
4141

4242
# Find features that should be enabled
4343
if 'features' in kb_info_json:
4444
for feature, enabled in kb_info_json['features'].items():
4545
if feature == 'bootmagic_lite' and enabled:
46-
rules_mk_lines.append('BOOTMAGIC_ENABLE := lite')
46+
rules_mk_lines.append('BOOTMAGIC_ENABLE ?= lite')
4747
else:
4848
feature = feature.upper()
4949
enabled = 'yes' if enabled else 'no'
50-
rules_mk_lines.append(f'{feature}_ENABLE := {enabled}')
50+
rules_mk_lines.append(f'{feature}_ENABLE ?= {enabled}')
5151

5252
# Set the LED driver
5353
if 'led_matrix' in kb_info_json and 'driver' in kb_info_json['led_matrix']:
5454
driver = kb_info_json['led_matrix']['driver']
55-
rules_mk_lines.append(f'LED_MATRIX_DRIVER = {driver}')
55+
rules_mk_lines.append(f'LED_MATRIX_DRIVER ?= {driver}')
5656

5757
# Add community layouts
5858
if 'community_layouts' in kb_info_json:
59-
rules_mk_lines.append(f'LAYOUTS = {" ".join(kb_info_json["community_layouts"])}')
59+
rules_mk_lines.append(f'LAYOUTS ?= {" ".join(kb_info_json["community_layouts"])}')
6060

6161
# Show the results
6262
rules_mk = '\n'.join(rules_mk_lines) + '\n'

lib/python/qmk/constants.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,5 +26,5 @@
2626
LED_INDICATORS = {
2727
'caps_lock': 'LED_CAPS_LOCK_PIN',
2828
'num_lock': 'LED_NUM_LOCK_PIN',
29-
'scrol_lock': 'LED_SCROLL_LOCK_PIN'
29+
'scrol_lock': 'LED_SCROLL_LOCK_PIN',
3030
}

lib/python/qmk/info.py

Lines changed: 29 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
"""Functions that help us generate and use info.json files.
22
"""
33
import json
4+
from collections.abc import Mapping
45
from glob import glob
56
from pathlib import Path
67

@@ -140,6 +141,8 @@ def _json_load(json_file):
140141

141142
def _jsonschema(schema_name):
142143
"""Read a jsonschema file from disk.
144+
145+
FIXME(skullydazed/anyone): Refactor to make this a public function.
143146
"""
144147
schema_path = Path(f'data/schemas/{schema_name}.jsonschema')
145148

@@ -638,49 +641,44 @@ def unknown_processor_rules(info_data, rules):
638641
return info_data
639642

640643

644+
def deep_update(origdict, newdict):
645+
"""Update a dictionary in place, recursing to do a deep copy.
646+
"""
647+
for key, value in newdict.items():
648+
if isinstance(value, Mapping):
649+
origdict[key] = deep_update(origdict.get(key, {}), value)
650+
651+
else:
652+
origdict[key] = value
653+
654+
return origdict
655+
656+
641657
def merge_info_jsons(keyboard, info_data):
642658
"""Return a merged copy of all the info.json files for a keyboard.
643659
"""
644660
for info_file in find_info_json(keyboard):
645661
# Load and validate the JSON data
662+
new_info_data = _json_load(info_file)
663+
664+
if not isinstance(new_info_data, dict):
665+
_log_error(info_data, "Invalid file %s, root object should be a dictionary." % (str(info_file),))
666+
continue
667+
646668
try:
647-
new_info_data = _json_load(info_file)
648669
keyboard_validate(new_info_data)
649-
650670
except jsonschema.ValidationError as e:
651671
json_path = '.'.join([str(p) for p in e.absolute_path])
652-
cli.log.error('Invalid info.json data: %s: %s: %s', info_file, json_path, e.message)
672+
cli.log.error('Not including data from file: %s', info_file)
673+
cli.log.error('\t%s: %s', json_path, e.message)
653674
continue
654675

655-
if not isinstance(new_info_data, dict):
656-
_log_error(info_data, "Invalid file %s, root object should be a dictionary." % (str(info_file),))
657-
continue
658-
659-
# Copy whitelisted keys into `info_data`
660-
for key in ('debounce', 'diode_direction', 'indicators', 'keyboard_name', 'manufacturer', 'identifier', 'url', 'maintainer', 'processor', 'bootloader', 'width', 'height'):
661-
if key in new_info_data:
662-
info_data[key] = new_info_data[key]
663-
664-
# Deep merge certain keys
665-
# FIXME(skullydazed/anyone): this should be generalized more so that we can inteligently merge more than one level deep. It would be nice if we could filter on valid keys too. That may have to wait for a future where we use openapi or something.
666-
for key in ('features', 'layout_aliases', 'led_matrix', 'matrix_pins', 'rgblight', 'usb'):
667-
if key in new_info_data:
668-
if key not in info_data:
669-
info_data[key] = {}
670-
671-
info_data[key].update(new_info_data[key])
672-
673-
# Merge the layouts
674-
if 'community_layouts' in new_info_data:
675-
if 'community_layouts' in info_data:
676-
for layout in new_info_data['community_layouts']:
677-
if layout not in info_data['community_layouts']:
678-
info_data['community_layouts'].append(layout)
679-
else:
680-
info_data['community_layouts'] = new_info_data['community_layouts']
676+
# Mark the layouts as coming from json
677+
for layout in new_info_data.get('layouts', {}).values():
678+
layout['c_macro'] = False
681679

682-
if 'layouts' in new_info_data:
683-
_merge_layouts(info_data, new_info_data)
680+
# Update info_data with the new data
681+
deep_update(info_data, new_info_data)
684682

685683
return info_data
686684

setup.cfg

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
ignore =
44
# QMK is ok with long lines.
55
E501
6+
# Conflicts with our yapf config
7+
E231
68
per_file_ignores =
79
**/__init__.py:F401
810

0 commit comments

Comments
 (0)