aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--data/schemas/keyboard.jsonschema2
-rw-r--r--docs/reference_info_json.md1
-rwxr-xr-xlib/python/qmk/cli/generate/info_json.py48
-rwxr-xr-xlib/python/qmk/cli/generate/layouts.py4
-rwxr-xr-xlib/python/qmk/cli/generate/rules_mk.py10
-rw-r--r--lib/python/qmk/constants.py2
-rw-r--r--lib/python/qmk/info.py60
-rw-r--r--setup.cfg2
8 files changed, 75 insertions, 54 deletions
diff --git a/data/schemas/keyboard.jsonschema b/data/schemas/keyboard.jsonschema
index e13771e92..f76c7fd18 100644
--- a/data/schemas/keyboard.jsonschema
+++ b/data/schemas/keyboard.jsonschema
@@ -25,7 +25,7 @@
25 }, 25 },
26 "processor": { 26 "processor": {
27 "type": "string", 27 "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"]
29 }, 29 },
30 "bootloader": { 30 "bootloader": {
31 "type": "string", 31 "type": "string",
diff --git a/docs/reference_info_json.md b/docs/reference_info_json.md
index 47506bc92..c9864ea2d 100644
--- a/docs/reference_info_json.md
+++ b/docs/reference_info_json.md
@@ -106,6 +106,7 @@ Example:
106 ["A7", "B1"], 106 ["A7", "B1"],
107 [null, "B2"] 107 [null, "B2"]
108 ] 108 ]
109 }
109} 110}
110``` 111```
111 112
diff --git a/lib/python/qmk/cli/generate/info_json.py b/lib/python/qmk/cli/generate/info_json.py
index fba4b1c01..f3fc54ddc 100755
--- a/lib/python/qmk/cli/generate/info_json.py
+++ b/lib/python/qmk/cli/generate/info_json.py
@@ -4,14 +4,41 @@ Compile an info.json for a particular keyboard and pretty-print it.
4""" 4"""
5import json 5import json
6 6
7from jsonschema import Draft7Validator, validators
7from milc import cli 8from milc import cli
8 9
9from qmk.info_json_encoder import InfoJSONEncoder
10from qmk.decorators import automagic_keyboard, automagic_keymap 10from qmk.decorators import automagic_keyboard, automagic_keymap
11from qmk.info import info_json 11from qmk.info import info_json, _jsonschema
12from qmk.info_json_encoder import InfoJSONEncoder
12from qmk.path import is_keyboard 13from qmk.path import is_keyboard
13 14
14 15
16def 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
32def 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
15@cli.argument('-kb', '--keyboard', help='Keyboard to show info for.') 42@cli.argument('-kb', '--keyboard', help='Keyboard to show info for.')
16@cli.argument('-km', '--keymap', help='Show the layers for a JSON keymap too.') 43@cli.argument('-km', '--keymap', help='Show the layers for a JSON keymap too.')
17@cli.subcommand('Generate an info.json file for a keyboard.', hidden=False if cli.config.user.developer else True) 44@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):
22 """ 49 """
23 # Determine our keyboard(s) 50 # Determine our keyboard(s)
24 if not cli.config.generate_info_json.keyboard: 51 if not cli.config.generate_info_json.keyboard:
25 cli.log.error('Missing paramater: --keyboard') 52 cli.log.error('Missing parameter: --keyboard')
26 cli.subcommands['info'].print_help() 53 cli.subcommands['info'].print_help()
27 return False 54 return False
28 55
@@ -32,18 +59,7 @@ def generate_info_json(cli):
32 59
33 # Build the info.json file 60 # Build the info.json file
34 kb_info_json = info_json(cli.config.generate_info_json.keyboard) 61 kb_info_json = info_json(cli.config.generate_info_json.keyboard)
35 pared_down_json = {} 62 strip_info_json(kb_info_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']
47 63
48 # Display the results 64 # 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))
diff --git a/lib/python/qmk/cli/generate/layouts.py b/lib/python/qmk/cli/generate/layouts.py
index 273870e15..b7baae065 100755
--- a/lib/python/qmk/cli/generate/layouts.py
+++ b/lib/python/qmk/cli/generate/layouts.py
@@ -54,6 +54,10 @@ def generate_layouts(cli):
54 if kb_info_json['layouts'][layout_name]['c_macro']: 54 if kb_info_json['layouts'][layout_name]['c_macro']:
55 continue 55 continue
56 56
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
57 layout_keys = [] 61 layout_keys = []
58 layout_matrix = [['KC_NO' for i in range(col_num)] for i in range(row_num)] 62 layout_matrix = [['KC_NO' for i in range(col_num)] for i in range(row_num)]
59 63
diff --git a/lib/python/qmk/cli/generate/rules_mk.py b/lib/python/qmk/cli/generate/rules_mk.py
index 2a7e91856..0fdccb404 100755
--- a/lib/python/qmk/cli/generate/rules_mk.py
+++ b/lib/python/qmk/cli/generate/rules_mk.py
@@ -37,26 +37,26 @@ def generate_rules_mk(cli):
37 37
38 # Bring in settings 38 # Bring in settings
39 for info_key, rule_key in info_to_rules.items(): 39 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]}')
41 41
42 # Find features that should be enabled 42 # Find features that should be enabled
43 if 'features' in kb_info_json: 43 if 'features' in kb_info_json:
44 for feature, enabled in kb_info_json['features'].items(): 44 for feature, enabled in kb_info_json['features'].items():
45 if feature == 'bootmagic_lite' and enabled: 45 if feature == 'bootmagic_lite' and enabled:
46 rules_mk_lines.append('BOOTMAGIC_ENABLE := lite') 46 rules_mk_lines.append('BOOTMAGIC_ENABLE ?= lite')
47 else: 47 else:
48 feature = feature.upper() 48 feature = feature.upper()
49 enabled = 'yes' if enabled else 'no' 49 enabled = 'yes' if enabled else 'no'
50 rules_mk_lines.append(f'{feature}_ENABLE := {enabled}') 50 rules_mk_lines.append(f'{feature}_ENABLE ?= {enabled}')
51 51
52 # Set the LED driver 52 # Set the LED driver
53 if 'led_matrix' in kb_info_json and 'driver' in kb_info_json['led_matrix']: 53 if 'led_matrix' in kb_info_json and 'driver' in kb_info_json['led_matrix']:
54 driver = kb_info_json['led_matrix']['driver'] 54 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}')
56 56
57 # Add community layouts 57 # Add community layouts
58 if 'community_layouts' in kb_info_json: 58 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"])}')
60 60
61 # Show the results 61 # Show the results
62 rules_mk = '\n'.join(rules_mk_lines) + '\n' 62 rules_mk = '\n'.join(rules_mk_lines) + '\n'
diff --git a/lib/python/qmk/constants.py b/lib/python/qmk/constants.py
index 675832c50..6a643070f 100644
--- a/lib/python/qmk/constants.py
+++ b/lib/python/qmk/constants.py
@@ -26,5 +26,5 @@ ROW_LETTERS = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnop'
26LED_INDICATORS = { 26LED_INDICATORS = {
27 'caps_lock': 'LED_CAPS_LOCK_PIN', 27 'caps_lock': 'LED_CAPS_LOCK_PIN',
28 'num_lock': 'LED_NUM_LOCK_PIN', 28 'num_lock': 'LED_NUM_LOCK_PIN',
29 'scrol_lock': 'LED_SCROLL_LOCK_PIN' 29 'scrol_lock': 'LED_SCROLL_LOCK_PIN',
30} 30}
diff --git a/lib/python/qmk/info.py b/lib/python/qmk/info.py
index 28c281a4b..0ea5136a9 100644
--- a/lib/python/qmk/info.py
+++ b/lib/python/qmk/info.py
@@ -1,6 +1,7 @@
1"""Functions that help us generate and use info.json files. 1"""Functions that help us generate and use info.json files.
2""" 2"""
3import json 3import json
4from collections.abc import Mapping
4from glob import glob 5from glob import glob
5from pathlib import Path 6from pathlib import Path
6 7
@@ -140,6 +141,8 @@ def _json_load(json_file):
140 141
141def _jsonschema(schema_name): 142def _jsonschema(schema_name):
142 """Read a jsonschema file from disk. 143 """Read a jsonschema file from disk.
144
145 FIXME(skullydazed/anyone): Refactor to make this a public function.
143 """ 146 """
144 schema_path = Path(f'data/schemas/{schema_name}.jsonschema') 147 schema_path = Path(f'data/schemas/{schema_name}.jsonschema')
145 148
@@ -638,49 +641,44 @@ def unknown_processor_rules(info_data, rules):
638 return info_data 641 return info_data
639 642
640 643
644def 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
641def merge_info_jsons(keyboard, info_data): 657def merge_info_jsons(keyboard, info_data):
642 """Return a merged copy of all the info.json files for a keyboard. 658 """Return a merged copy of all the info.json files for a keyboard.
643 """ 659 """
644 for info_file in find_info_json(keyboard): 660 for info_file in find_info_json(keyboard):
645 # Load and validate the JSON data 661 # 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
646 try: 668 try:
647 new_info_data = _json_load(info_file)
648 keyboard_validate(new_info_data) 669 keyboard_validate(new_info_data)
649
650 except jsonschema.ValidationError as e: 670 except jsonschema.ValidationError as e:
651 json_path = '.'.join([str(p) for p in e.absolute_path]) 671 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)
653 continue 674 continue
654 675
655 if not isinstance(new_info_data, dict): 676 # Mark the layouts as coming from json
656 _log_error(info_data, "Invalid file %s, root object should be a dictionary." % (str(info_file),)) 677 for layout in new_info_data.get('layouts', {}).values():
657 continue 678 layout['c_macro'] = False
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']
681 679
682 if 'layouts' in new_info_data: 680 # Update info_data with the new data
683 _merge_layouts(info_data, new_info_data) 681 deep_update(info_data, new_info_data)
684 682
685 return info_data 683 return info_data
686 684
diff --git a/setup.cfg b/setup.cfg
index 5ef2f9ba0..baa6a0396 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -3,6 +3,8 @@
3ignore = 3ignore =
4 # QMK is ok with long lines. 4 # QMK is ok with long lines.
5 E501 5 E501
6 # Conflicts with our yapf config
7 E231
6per_file_ignores = 8per_file_ignores =
7 **/__init__.py:F401 9 **/__init__.py:F401
8 10