aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--data/schemas/keyboard.jsonschema33
-rw-r--r--lib/python/qmk/c_parse.py26
-rwxr-xr-xlib/python/qmk/cli/generate/api.py2
-rwxr-xr-xlib/python/qmk/cli/generate/rules_mk.py9
-rw-r--r--lib/python/qmk/info.py82
5 files changed, 117 insertions, 35 deletions
diff --git a/data/schemas/keyboard.jsonschema b/data/schemas/keyboard.jsonschema
index 75e792b64..9355ee49b 100644
--- a/data/schemas/keyboard.jsonschema
+++ b/data/schemas/keyboard.jsonschema
@@ -90,6 +90,9 @@
90 "type": "object", 90 "type": "object",
91 "additionalProperties": false, 91 "additionalProperties": false,
92 "properties": { 92 "properties": {
93 "filename": {
94 "type": "string"
95 },
93 "c_macro": { 96 "c_macro": {
94 "type": "boolean" 97 "type": "boolean"
95 }, 98 },
@@ -119,6 +122,18 @@
119 "type": "number", 122 "type": "number",
120 "min": 0.25 123 "min": 0.25
121 }, 124 },
125 "r": {
126 "type": "number",
127 "min": 0
128 },
129 "rx": {
130 "type": "number",
131 "min": 0
132 },
133 "ry": {
134 "type": "number",
135 "min": 0
136 },
122 "w": { 137 "w": {
123 "type": "number", 138 "type": "number",
124 "min": 0.25 139 "min": 0.25
@@ -199,6 +214,12 @@
199 "min": 0, 214 "min": 0,
200 "multipleOf": 1 215 "multipleOf": 1
201 }, 216 },
217 "max_brightness": {
218 "type": "number",
219 "min": 0,
220 "max": 255,
221 "multipleOf": 1
222 },
202 "pin": { 223 "pin": {
203 "type": "string", 224 "type": "string",
204 "pattern": "^[A-K]\\d{1,2}$" 225 "pattern": "^[A-K]\\d{1,2}$"
@@ -207,6 +228,18 @@
207 "type": "number", 228 "type": "number",
208 "min": 0, 229 "min": 0,
209 "multipleOf": 1 230 "multipleOf": 1
231 },
232 "sleep": {"type": "boolean"},
233 "split": {"type": "boolean"},
234 "split_count": {
235 "type": "array",
236 "minLength": 2,
237 "maxLength": 2,
238 "items": {
239 "type": "number",
240 "min": 0,
241 "multipleOf": 1
242 }
210 } 243 }
211 } 244 }
212 }, 245 },
diff --git a/lib/python/qmk/c_parse.py b/lib/python/qmk/c_parse.py
index e41e271a4..67e196f0e 100644
--- a/lib/python/qmk/c_parse.py
+++ b/lib/python/qmk/c_parse.py
@@ -1,12 +1,27 @@
1"""Functions for working with config.h files. 1"""Functions for working with config.h files.
2""" 2"""
3from pathlib import Path 3from pathlib import Path
4import re
4 5
5from milc import cli 6from milc import cli
6 7
7from qmk.comment_remover import comment_remover 8from qmk.comment_remover import comment_remover
8 9
9default_key_entry = {'x': -1, 'y': 0, 'w': 1} 10default_key_entry = {'x': -1, 'y': 0, 'w': 1}
11single_comment_regex = re.compile(r' */[/*].*$')
12multi_comment_regex = re.compile(r'/\*(.|\n)*\*/', re.MULTILINE)
13
14
15def strip_line_comment(string):
16 """Removes comments from a single line string.
17 """
18 return single_comment_regex.sub('', string)
19
20
21def strip_multiline_comment(string):
22 """Removes comments from a single line string.
23 """
24 return multi_comment_regex.sub('', string)
10 25
11 26
12def c_source_files(dir_names): 27def c_source_files(dir_names):
@@ -53,7 +68,8 @@ def find_layouts(file):
53 parsed_layout = [_default_key(key) for key in layout.split(',')] 68 parsed_layout = [_default_key(key) for key in layout.split(',')]
54 69
55 for key in parsed_layout: 70 for key in parsed_layout:
56 key['matrix'] = matrix_locations.get(key['label']) 71 if key['label'] in matrix_locations:
72 key['matrix'] = matrix_locations[key['label']]
57 73
58 parsed_layouts[macro_name] = { 74 parsed_layouts[macro_name] = {
59 'key_count': len(parsed_layout), 75 'key_count': len(parsed_layout),
@@ -88,12 +104,10 @@ def parse_config_h_file(config_h_file, config_h=None):
88 if config_h_file.exists(): 104 if config_h_file.exists():
89 config_h_text = config_h_file.read_text() 105 config_h_text = config_h_file.read_text()
90 config_h_text = config_h_text.replace('\\\n', '') 106 config_h_text = config_h_text.replace('\\\n', '')
107 config_h_text = strip_multiline_comment(config_h_text)
91 108
92 for linenum, line in enumerate(config_h_text.split('\n')): 109 for linenum, line in enumerate(config_h_text.split('\n')):
93 line = line.strip() 110 line = strip_line_comment(line).strip()
94
95 if '//' in line:
96 line = line[:line.index('//')].strip()
97 111
98 if not line: 112 if not line:
99 continue 113 continue
@@ -156,6 +170,6 @@ def _parse_matrix_locations(matrix, file, macro_name):
156 row = row.replace('{', '').replace('}', '') 170 row = row.replace('{', '').replace('}', '')
157 for col_num, identifier in enumerate(row.split(',')): 171 for col_num, identifier in enumerate(row.split(',')):
158 if identifier != 'KC_NO': 172 if identifier != 'KC_NO':
159 matrix_locations[identifier] = (row_num, col_num) 173 matrix_locations[identifier] = [row_num, col_num]
160 174
161 return matrix_locations 175 return matrix_locations
diff --git a/lib/python/qmk/cli/generate/api.py b/lib/python/qmk/cli/generate/api.py
index 6d111f244..2fda38fc9 100755
--- a/lib/python/qmk/cli/generate/api.py
+++ b/lib/python/qmk/cli/generate/api.py
@@ -48,7 +48,7 @@ def generate_api(cli):
48 if 'vid' in usb and usb['vid'] not in usb_list['devices']: 48 if 'vid' in usb and usb['vid'] not in usb_list['devices']:
49 usb_list['devices'][usb['vid']] = {} 49 usb_list['devices'][usb['vid']] = {}
50 50
51 if 'pid' in usb and usb['pid'] not in usb_list['devices'][usb['vid']]: 51 if 'vid' in usb and usb['pid'] not in usb_list['devices'][usb['vid']]:
52 usb_list['devices'][usb['vid']][usb['pid']] = {} 52 usb_list['devices'][usb['vid']][usb['pid']] = {}
53 53
54 if 'vid' in usb and 'pid' in usb: 54 if 'vid' in usb and 'pid' in usb:
diff --git a/lib/python/qmk/cli/generate/rules_mk.py b/lib/python/qmk/cli/generate/rules_mk.py
index 72ed3c45f..570ef5a0d 100755
--- a/lib/python/qmk/cli/generate/rules_mk.py
+++ b/lib/python/qmk/cli/generate/rules_mk.py
@@ -41,9 +41,12 @@ def generate_rules_mk(cli):
41 # Find features that should be enabled 41 # Find features that should be enabled
42 if 'features' in kb_info_json: 42 if 'features' in kb_info_json:
43 for feature, enabled in kb_info_json['features'].items(): 43 for feature, enabled in kb_info_json['features'].items():
44 feature = feature.upper() 44 if feature == 'bootmagic_lite' and enabled:
45 enabled = 'yes' if enabled else 'no' 45 rules_mk_lines.append(f'BOOTMAGIC_ENABLE := lite')
46 rules_mk_lines.append(f'{feature}_ENABLE := {enabled}') 46 else:
47 feature = feature.upper()
48 enabled = 'yes' if enabled else 'no'
49 rules_mk_lines.append(f'{feature}_ENABLE := {enabled}')
47 50
48 # Set the LED driver 51 # Set the LED driver
49 if 'led_matrix' in kb_info_json and 'driver' in kb_info_json['led_matrix']: 52 if 'led_matrix' in kb_info_json and 'driver' in kb_info_json['led_matrix']:
diff --git a/lib/python/qmk/info.py b/lib/python/qmk/info.py
index 1cf12190d..39af88f79 100644
--- a/lib/python/qmk/info.py
+++ b/lib/python/qmk/info.py
@@ -26,13 +26,12 @@ led_matrix_properties = {
26} 26}
27 27
28rgblight_properties = { 28rgblight_properties = {
29 'led_count': 'RGBLED_NUM', 29 'led_count': ('RGBLED_NUM', int),
30 'pin': 'RGB_DI_PIN', 30 'pin': ('RGB_DI_PIN', str),
31 'split_count': 'RGBLED_SPLIT', 31 'max_brightness': ('RGBLIGHT_LIMIT_VAL', int),
32 'max_brightness': 'RGBLIGHT_LIMIT_VAL', 32 'hue_steps': ('RGBLIGHT_HUE_STEP', int),
33 'hue_steps': 'RGBLIGHT_HUE_STEP', 33 'saturation_steps': ('RGBLIGHT_SAT_STEP', int),
34 'saturation_steps': 'RGBLIGHT_SAT_STEP', 34 'brightness_steps': ('RGBLIGHT_VAL_STEP', int)
35 'brightness_steps': 'RGBLIGHT_VAL_STEP'
36} 35}
37 36
38rgblight_toggles = { 37rgblight_toggles = {
@@ -54,6 +53,8 @@ rgblight_animations = {
54 'twinkle': 'RGBLIGHT_EFFECT_TWINKLE' 53 'twinkle': 'RGBLIGHT_EFFECT_TWINKLE'
55} 54}
56 55
56usb_properties = {'vid': 'VENDOR_ID', 'pid': 'PRODUCT_ID', 'device_ver': 'DEVICE_VER'}
57
57true_values = ['1', 'on', 'yes'] 58true_values = ['1', 'on', 'yes']
58false_values = ['0', 'off', 'no'] 59false_values = ['0', 'off', 'no']
59 60
@@ -97,7 +98,8 @@ def info_json(keyboard):
97 keyboard_api_validate(info_data) 98 keyboard_api_validate(info_data)
98 99
99 except jsonschema.ValidationError as e: 100 except jsonschema.ValidationError as e:
100 cli.log.error('Invalid info.json data: %s', e.message) 101 json_path = '.'.join([str(p) for p in e.absolute_path])
102 cli.log.error('Invalid API data: %s: %s: %s', keyboard, json_path, e.message)
101 print(dir(e)) 103 print(dir(e))
102 exit() 104 exit()
103 105
@@ -198,6 +200,9 @@ def _extract_indicators(info_data, config_c):
198 if json_key in info_data.get('indicators', []) and config_key in config_c: 200 if json_key in info_data.get('indicators', []) and config_key in config_c:
199 _log_warning(info_data, f'Indicator {json_key} is specified in both info.json and config.h, the config.h value wins.') 201 _log_warning(info_data, f'Indicator {json_key} is specified in both info.json and config.h, the config.h value wins.')
200 202
203 if 'indicators' not in info_data:
204 info_data['indicators'] = {}
205
201 if config_key in config_c: 206 if config_key in config_c:
202 if 'indicators' not in info_data: 207 if 'indicators' not in info_data:
203 info_data['indicators'] = {} 208 info_data['indicators'] = {}
@@ -226,10 +231,23 @@ def _extract_community_layouts(info_data, rules):
226def _extract_features(info_data, rules): 231def _extract_features(info_data, rules):
227 """Find all the features enabled in rules.mk. 232 """Find all the features enabled in rules.mk.
228 """ 233 """
234 # Special handling for bootmagic which also supports a "lite" mode.
235 if rules.get('BOOTMAGIC_ENABLE') == 'lite':
236 rules['BOOTMAGIC_LITE_ENABLE'] = 'on'
237 del(rules['BOOTMAGIC_ENABLE'])
238 if rules.get('BOOTMAGIC_ENABLE') == 'full':
239 rules['BOOTMAGIC_ENABLE'] = 'on'
240
241 # Skip non-boolean features we haven't implemented special handling for
242 for feature in 'HAPTIC_ENABLE', 'QWIIC_ENABLE':
243 if rules.get(feature):
244 del(rules[feature])
245
246 # Process the rest of the rules as booleans
229 for key, value in rules.items(): 247 for key, value in rules.items():
230 if key.endswith('_ENABLE'): 248 if key.endswith('_ENABLE'):
231 key = '_'.join(key.split('_')[:-1]).lower() 249 key = '_'.join(key.split('_')[:-1]).lower()
232 value = True if value in true_values else False if value in false_values else value 250 value = True if value.lower() in true_values else False if value.lower() in false_values else value
233 251
234 if 'config_h_features' not in info_data: 252 if 'config_h_features' not in info_data:
235 info_data['config_h_features'] = {} 253 info_data['config_h_features'] = {}
@@ -280,12 +298,21 @@ def _extract_rgblight(info_data, config_c):
280 rgblight = info_data.get('rgblight', {}) 298 rgblight = info_data.get('rgblight', {})
281 animations = rgblight.get('animations', {}) 299 animations = rgblight.get('animations', {})
282 300
283 for json_key, config_key in rgblight_properties.items(): 301 if 'RGBLED_SPLIT' in config_c:
302 raw_split = config_c.get('RGBLED_SPLIT', '').replace('{', '').replace('}', '').strip()
303 rgblight['split_count'] = [int(i) for i in raw_split.split(',')]
304
305 for json_key, config_key_type in rgblight_properties.items():
306 config_key, config_type = config_key_type
307
284 if config_key in config_c: 308 if config_key in config_c:
285 if json_key in rgblight: 309 if json_key in rgblight:
286 _log_warning(info_data, 'RGB Light: %s is specified in both info.json and config.h, the config.h value wins.' % (json_key,)) 310 _log_warning(info_data, 'RGB Light: %s is specified in both info.json and config.h, the config.h value wins.' % (json_key,))
287 311
288 rgblight[json_key] = config_c[config_key] 312 try:
313 rgblight[json_key] = config_type(config_c[config_key])
314 except ValueError as e:
315 cli.log.error('%s: config.h: Could not convert "%s" to %s: %s', info_data['keyboard_folder'], config_c[config_key], config_type.__name__, e)
289 316
290 for json_key, config_key in rgblight_toggles.items(): 317 for json_key, config_key in rgblight_toggles.items():
291 if config_key in config_c: 318 if config_key in config_c:
@@ -332,11 +359,16 @@ def _extract_matrix_info(info_data, config_c):
332 359
333 info_data['matrix_pins'] = {} 360 info_data['matrix_pins'] = {}
334 361
362 # FIXME(skullydazed/anyone): Should really check every pin, not just the first
335 if row_pins: 363 if row_pins:
336 info_data['matrix_pins']['rows'] = row_pins.split(',') 364 row_pins = [pin.strip() for pin in row_pins.split(',') if pin]
365 if row_pins[0][0] in 'ABCDEFGHIJK' and row_pins[0][1].isdigit():
366 info_data['matrix_pins']['rows'] = row_pins
337 367
338 if col_pins: 368 if col_pins:
339 info_data['matrix_pins']['cols'] = col_pins.split(',') 369 col_pins = [pin.strip() for pin in col_pins.split(',') if pin]
370 if col_pins[0][0] in 'ABCDEFGHIJK' and col_pins[0][1].isdigit():
371 info_data['matrix_pins']['cols'] = col_pins
340 372
341 if direct_pins: 373 if direct_pins:
342 if 'matrix_pins' in info_data: 374 if 'matrix_pins' in info_data:
@@ -345,6 +377,9 @@ def _extract_matrix_info(info_data, config_c):
345 info_data['matrix_pins'] = {} 377 info_data['matrix_pins'] = {}
346 direct_pin_array = [] 378 direct_pin_array = []
347 379
380 while direct_pins[-1] != '}':
381 direct_pins = direct_pins[:-1]
382
348 for row in direct_pins.split('},{'): 383 for row in direct_pins.split('},{'):
349 if row.startswith('{'): 384 if row.startswith('{'):
350 row = row[1:] 385 row = row[1:]
@@ -368,8 +403,6 @@ def _extract_matrix_info(info_data, config_c):
368def _extract_usb_info(info_data, config_c): 403def _extract_usb_info(info_data, config_c):
369 """Populate the USB information. 404 """Populate the USB information.
370 """ 405 """
371 usb_properties = {'vid': 'VENDOR_ID', 'pid': 'PRODUCT_ID', 'device_ver': 'DEVICE_VER'}
372
373 if 'usb' not in info_data: 406 if 'usb' not in info_data:
374 info_data['usb'] = {} 407 info_data['usb'] = {}
375 408
@@ -378,10 +411,7 @@ def _extract_usb_info(info_data, config_c):
378 if info_name in info_data['usb']: 411 if info_name in info_data['usb']:
379 _log_warning(info_data, '%s in config.h is overwriting usb.%s in info.json' % (config_name, info_name)) 412 _log_warning(info_data, '%s in config.h is overwriting usb.%s in info.json' % (config_name, info_name))
380 413
381 info_data['usb'][info_name] = config_c[config_name] 414 info_data['usb'][info_name] = '0x' + config_c[config_name][2:].upper()
382
383 elif info_name not in info_data['usb']:
384 _log_error(info_data, '%s not specified in config.h, and %s not specified in info.json. One is required.' % (config_name, info_name))
385 415
386 return info_data 416 return info_data
387 417
@@ -519,8 +549,9 @@ def arm_processor_rules(info_data, rules):
519 info_data['processor'] = 'unknown' 549 info_data['processor'] = 'unknown'
520 550
521 if 'BOOTLOADER' in rules: 551 if 'BOOTLOADER' in rules:
522 if 'bootloader' in info_data: 552 # FIXME(skullydazed/anyone): need to remove the massive amounts of duplication first
523 _log_warning(info_data, 'Bootloader is specified in both info.json and rules.mk, the rules.mk value wins.') 553 # if 'bootloader' in info_data:
554 # _log_warning(info_data, 'Bootloader is specified in both info.json and rules.mk, the rules.mk value wins.')
524 555
525 info_data['bootloader'] = rules['BOOTLOADER'] 556 info_data['bootloader'] = rules['BOOTLOADER']
526 557
@@ -558,8 +589,9 @@ def avr_processor_rules(info_data, rules):
558 info_data['processor'] = 'unknown' 589 info_data['processor'] = 'unknown'
559 590
560 if 'BOOTLOADER' in rules: 591 if 'BOOTLOADER' in rules:
561 if 'bootloader' in info_data: 592 # FIXME(skullydazed/anyone): need to remove the massive amounts of duplication first
562 _log_warning(info_data, 'Bootloader is specified in both info.json and rules.mk, the rules.mk value wins.') 593 # if 'bootloader' in info_data:
594 # _log_warning(info_data, 'Bootloader is specified in both info.json and rules.mk, the rules.mk value wins.')
563 595
564 info_data['bootloader'] = rules['BOOTLOADER'] 596 info_data['bootloader'] = rules['BOOTLOADER']
565 else: 597 else:
@@ -593,8 +625,8 @@ def merge_info_jsons(keyboard, info_data):
593 keyboard_validate(new_info_data) 625 keyboard_validate(new_info_data)
594 626
595 except jsonschema.ValidationError as e: 627 except jsonschema.ValidationError as e:
596 cli.log.error('Invalid info.json data: %s', e.message) 628 json_path = '.'.join([str(p) for p in e.absolute_path])
597 cli.log.error('Not including file %s', info_file) 629 cli.log.error('Invalid info.json data: %s: %s: %s', info_file, json_path, e.message)
598 continue 630 continue
599 631
600 if not isinstance(new_info_data, dict): 632 if not isinstance(new_info_data, dict):