Browse Source

get qmk generate-api into a good state

pull/11473/head
Zach White 3 years ago
committed by Zach White
parent
commit
b2c26f7cdd
5 changed files with 117 additions and 35 deletions
  1. +33
    -0
      data/schemas/keyboard.jsonschema
  2. +20
    -6
      lib/python/qmk/c_parse.py
  3. +1
    -1
      lib/python/qmk/cli/generate/api.py
  4. +6
    -3
      lib/python/qmk/cli/generate/rules_mk.py
  5. +57
    -25
      lib/python/qmk/info.py

+ 33
- 0
data/schemas/keyboard.jsonschema View File

@ -90,6 +90,9 @@
"type": "object", "type": "object",
"additionalProperties": false, "additionalProperties": false,
"properties": { "properties": {
"filename": {
"type": "string"
},
"c_macro": { "c_macro": {
"type": "boolean" "type": "boolean"
}, },
@ -119,6 +122,18 @@
"type": "number", "type": "number",
"min": 0.25 "min": 0.25
}, },
"r": {
"type": "number",
"min": 0
},
"rx": {
"type": "number",
"min": 0
},
"ry": {
"type": "number",
"min": 0
},
"w": { "w": {
"type": "number", "type": "number",
"min": 0.25 "min": 0.25
@ -199,6 +214,12 @@
"min": 0, "min": 0,
"multipleOf": 1 "multipleOf": 1
}, },
"max_brightness": {
"type": "number",
"min": 0,
"max": 255,
"multipleOf": 1
},
"pin": { "pin": {
"type": "string", "type": "string",
"pattern": "^[A-K]\\d{1,2}$" "pattern": "^[A-K]\\d{1,2}$"
@ -207,6 +228,18 @@
"type": "number", "type": "number",
"min": 0, "min": 0,
"multipleOf": 1 "multipleOf": 1
},
"sleep": {"type": "boolean"},
"split": {"type": "boolean"},
"split_count": {
"type": "array",
"minLength": 2,
"maxLength": 2,
"items": {
"type": "number",
"min": 0,
"multipleOf": 1
}
} }
} }
}, },


+ 20
- 6
lib/python/qmk/c_parse.py View File

@ -1,12 +1,27 @@
"""Functions for working with config.h files. """Functions for working with config.h files.
""" """
from pathlib import Path from pathlib import Path
import re
from milc import cli from milc import cli
from qmk.comment_remover import comment_remover from qmk.comment_remover import comment_remover
default_key_entry = {'x': -1, 'y': 0, 'w': 1} default_key_entry = {'x': -1, 'y': 0, 'w': 1}
single_comment_regex = re.compile(r' */[/*].*$')
multi_comment_regex = re.compile(r'/\*(.|\n)*\*/', re.MULTILINE)
def strip_line_comment(string):
"""Removes comments from a single line string.
"""
return single_comment_regex.sub('', string)
def strip_multiline_comment(string):
"""Removes comments from a single line string.
"""
return multi_comment_regex.sub('', string)
def c_source_files(dir_names): def c_source_files(dir_names):
@ -53,7 +68,8 @@ def find_layouts(file):
parsed_layout = [_default_key(key) for key in layout.split(',')] parsed_layout = [_default_key(key) for key in layout.split(',')]
for key in parsed_layout: for key in parsed_layout:
key['matrix'] = matrix_locations.get(key['label'])
if key['label'] in matrix_locations:
key['matrix'] = matrix_locations[key['label']]
parsed_layouts[macro_name] = { parsed_layouts[macro_name] = {
'key_count': len(parsed_layout), 'key_count': len(parsed_layout),
@ -88,12 +104,10 @@ def parse_config_h_file(config_h_file, config_h=None):
if config_h_file.exists(): if config_h_file.exists():
config_h_text = config_h_file.read_text() config_h_text = config_h_file.read_text()
config_h_text = config_h_text.replace('\\\n', '') config_h_text = config_h_text.replace('\\\n', '')
config_h_text = strip_multiline_comment(config_h_text)
for linenum, line in enumerate(config_h_text.split('\n')): for linenum, line in enumerate(config_h_text.split('\n')):
line = line.strip()
if '//' in line:
line = line[:line.index('//')].strip()
line = strip_line_comment(line).strip()
if not line: if not line:
continue continue
@ -156,6 +170,6 @@ def _parse_matrix_locations(matrix, file, macro_name):
row = row.replace('{', '').replace('}', '') row = row.replace('{', '').replace('}', '')
for col_num, identifier in enumerate(row.split(',')): for col_num, identifier in enumerate(row.split(',')):
if identifier != 'KC_NO': if identifier != 'KC_NO':
matrix_locations[identifier] = (row_num, col_num)
matrix_locations[identifier] = [row_num, col_num]
return matrix_locations return matrix_locations

+ 1
- 1
lib/python/qmk/cli/generate/api.py View File

@ -48,7 +48,7 @@ def generate_api(cli):
if 'vid' in usb and usb['vid'] not in usb_list['devices']: if 'vid' in usb and usb['vid'] not in usb_list['devices']:
usb_list['devices'][usb['vid']] = {} usb_list['devices'][usb['vid']] = {}
if 'pid' in usb and usb['pid'] not in usb_list['devices'][usb['vid']]:
if 'vid' in usb and usb['pid'] not in usb_list['devices'][usb['vid']]:
usb_list['devices'][usb['vid']][usb['pid']] = {} usb_list['devices'][usb['vid']][usb['pid']] = {}
if 'vid' in usb and 'pid' in usb: if 'vid' in usb and 'pid' in usb:


+ 6
- 3
lib/python/qmk/cli/generate/rules_mk.py View File

@ -41,9 +41,12 @@ def generate_rules_mk(cli):
# Find features that should be enabled # Find features that should be enabled
if 'features' in kb_info_json: if 'features' in kb_info_json:
for feature, enabled in kb_info_json['features'].items(): for feature, enabled in kb_info_json['features'].items():
feature = feature.upper()
enabled = 'yes' if enabled else 'no'
rules_mk_lines.append(f'{feature}_ENABLE := {enabled}')
if feature == 'bootmagic_lite' and enabled:
rules_mk_lines.append(f'BOOTMAGIC_ENABLE := lite')
else:
feature = feature.upper()
enabled = 'yes' if enabled else 'no'
rules_mk_lines.append(f'{feature}_ENABLE := {enabled}')
# Set the LED driver # Set the LED driver
if 'led_matrix' in kb_info_json and 'driver' in kb_info_json['led_matrix']: if 'led_matrix' in kb_info_json and 'driver' in kb_info_json['led_matrix']:


+ 57
- 25
lib/python/qmk/info.py View File

@ -26,13 +26,12 @@ led_matrix_properties = {
} }
rgblight_properties = { rgblight_properties = {
'led_count': 'RGBLED_NUM',
'pin': 'RGB_DI_PIN',
'split_count': 'RGBLED_SPLIT',
'max_brightness': 'RGBLIGHT_LIMIT_VAL',
'hue_steps': 'RGBLIGHT_HUE_STEP',
'saturation_steps': 'RGBLIGHT_SAT_STEP',
'brightness_steps': 'RGBLIGHT_VAL_STEP'
'led_count': ('RGBLED_NUM', int),
'pin': ('RGB_DI_PIN', str),
'max_brightness': ('RGBLIGHT_LIMIT_VAL', int),
'hue_steps': ('RGBLIGHT_HUE_STEP', int),
'saturation_steps': ('RGBLIGHT_SAT_STEP', int),
'brightness_steps': ('RGBLIGHT_VAL_STEP', int)
} }
rgblight_toggles = { rgblight_toggles = {
@ -54,6 +53,8 @@ rgblight_animations = {
'twinkle': 'RGBLIGHT_EFFECT_TWINKLE' 'twinkle': 'RGBLIGHT_EFFECT_TWINKLE'
} }
usb_properties = {'vid': 'VENDOR_ID', 'pid': 'PRODUCT_ID', 'device_ver': 'DEVICE_VER'}
true_values = ['1', 'on', 'yes'] true_values = ['1', 'on', 'yes']
false_values = ['0', 'off', 'no'] false_values = ['0', 'off', 'no']
@ -97,7 +98,8 @@ def info_json(keyboard):
keyboard_api_validate(info_data) keyboard_api_validate(info_data)
except jsonschema.ValidationError as e: except jsonschema.ValidationError as e:
cli.log.error('Invalid info.json data: %s', e.message)
json_path = '.'.join([str(p) for p in e.absolute_path])
cli.log.error('Invalid API data: %s: %s: %s', keyboard, json_path, e.message)
print(dir(e)) print(dir(e))
exit() exit()
@ -198,6 +200,9 @@ def _extract_indicators(info_data, config_c):
if json_key in info_data.get('indicators', []) and config_key in config_c: if json_key in info_data.get('indicators', []) and config_key in config_c:
_log_warning(info_data, f'Indicator {json_key} is specified in both info.json and config.h, the config.h value wins.') _log_warning(info_data, f'Indicator {json_key} is specified in both info.json and config.h, the config.h value wins.')
if 'indicators' not in info_data:
info_data['indicators'] = {}
if config_key in config_c: if config_key in config_c:
if 'indicators' not in info_data: if 'indicators' not in info_data:
info_data['indicators'] = {} info_data['indicators'] = {}
@ -226,10 +231,23 @@ def _extract_community_layouts(info_data, rules):
def _extract_features(info_data, rules): def _extract_features(info_data, rules):
"""Find all the features enabled in rules.mk. """Find all the features enabled in rules.mk.
""" """
# Special handling for bootmagic which also supports a "lite" mode.
if rules.get('BOOTMAGIC_ENABLE') == 'lite':
rules['BOOTMAGIC_LITE_ENABLE'] = 'on'
del(rules['BOOTMAGIC_ENABLE'])
if rules.get('BOOTMAGIC_ENABLE') == 'full':
rules['BOOTMAGIC_ENABLE'] = 'on'
# Skip non-boolean features we haven't implemented special handling for
for feature in 'HAPTIC_ENABLE', 'QWIIC_ENABLE':
if rules.get(feature):
del(rules[feature])
# Process the rest of the rules as booleans
for key, value in rules.items(): for key, value in rules.items():
if key.endswith('_ENABLE'): if key.endswith('_ENABLE'):
key = '_'.join(key.split('_')[:-1]).lower() key = '_'.join(key.split('_')[:-1]).lower()
value = True if value in true_values else False if value in false_values else value
value = True if value.lower() in true_values else False if value.lower() in false_values else value
if 'config_h_features' not in info_data: if 'config_h_features' not in info_data:
info_data['config_h_features'] = {} info_data['config_h_features'] = {}
@ -280,12 +298,21 @@ def _extract_rgblight(info_data, config_c):
rgblight = info_data.get('rgblight', {}) rgblight = info_data.get('rgblight', {})
animations = rgblight.get('animations', {}) animations = rgblight.get('animations', {})
for json_key, config_key in rgblight_properties.items():
if 'RGBLED_SPLIT' in config_c:
raw_split = config_c.get('RGBLED_SPLIT', '').replace('{', '').replace('}', '').strip()
rgblight['split_count'] = [int(i) for i in raw_split.split(',')]
for json_key, config_key_type in rgblight_properties.items():
config_key, config_type = config_key_type
if config_key in config_c: if config_key in config_c:
if json_key in rgblight: if json_key in rgblight:
_log_warning(info_data, 'RGB Light: %s is specified in both info.json and config.h, the config.h value wins.' % (json_key,)) _log_warning(info_data, 'RGB Light: %s is specified in both info.json and config.h, the config.h value wins.' % (json_key,))
rgblight[json_key] = config_c[config_key]
try:
rgblight[json_key] = config_type(config_c[config_key])
except ValueError as e:
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)
for json_key, config_key in rgblight_toggles.items(): for json_key, config_key in rgblight_toggles.items():
if config_key in config_c: if config_key in config_c:
@ -332,11 +359,16 @@ def _extract_matrix_info(info_data, config_c):
info_data['matrix_pins'] = {} info_data['matrix_pins'] = {}
# FIXME(skullydazed/anyone): Should really check every pin, not just the first
if row_pins: if row_pins:
info_data['matrix_pins']['rows'] = row_pins.split(',')
row_pins = [pin.strip() for pin in row_pins.split(',') if pin]
if row_pins[0][0] in 'ABCDEFGHIJK' and row_pins[0][1].isdigit():
info_data['matrix_pins']['rows'] = row_pins
if col_pins: if col_pins:
info_data['matrix_pins']['cols'] = col_pins.split(',')
col_pins = [pin.strip() for pin in col_pins.split(',') if pin]
if col_pins[0][0] in 'ABCDEFGHIJK' and col_pins[0][1].isdigit():
info_data['matrix_pins']['cols'] = col_pins
if direct_pins: if direct_pins:
if 'matrix_pins' in info_data: if 'matrix_pins' in info_data:
@ -345,6 +377,9 @@ def _extract_matrix_info(info_data, config_c):
info_data['matrix_pins'] = {} info_data['matrix_pins'] = {}
direct_pin_array = [] direct_pin_array = []
while direct_pins[-1] != '}':
direct_pins = direct_pins[:-1]
for row in direct_pins.split('},{'): for row in direct_pins.split('},{'):
if row.startswith('{'): if row.startswith('{'):
row = row[1:] row = row[1:]
@ -368,8 +403,6 @@ def _extract_matrix_info(info_data, config_c):
def _extract_usb_info(info_data, config_c): def _extract_usb_info(info_data, config_c):
"""Populate the USB information. """Populate the USB information.
""" """
usb_properties = {'vid': 'VENDOR_ID', 'pid': 'PRODUCT_ID', 'device_ver': 'DEVICE_VER'}
if 'usb' not in info_data: if 'usb' not in info_data:
info_data['usb'] = {} info_data['usb'] = {}
@ -378,10 +411,7 @@ def _extract_usb_info(info_data, config_c):
if info_name in info_data['usb']: if info_name in info_data['usb']:
_log_warning(info_data, '%s in config.h is overwriting usb.%s in info.json' % (config_name, info_name)) _log_warning(info_data, '%s in config.h is overwriting usb.%s in info.json' % (config_name, info_name))
info_data['usb'][info_name] = config_c[config_name]
elif info_name not in info_data['usb']:
_log_error(info_data, '%s not specified in config.h, and %s not specified in info.json. One is required.' % (config_name, info_name))
info_data['usb'][info_name] = '0x' + config_c[config_name][2:].upper()
return info_data return info_data
@ -519,8 +549,9 @@ def arm_processor_rules(info_data, rules):
info_data['processor'] = 'unknown' info_data['processor'] = 'unknown'
if 'BOOTLOADER' in rules: if 'BOOTLOADER' in rules:
if 'bootloader' in info_data:
_log_warning(info_data, 'Bootloader is specified in both info.json and rules.mk, the rules.mk value wins.')
# FIXME(skullydazed/anyone): need to remove the massive amounts of duplication first
# if 'bootloader' in info_data:
# _log_warning(info_data, 'Bootloader is specified in both info.json and rules.mk, the rules.mk value wins.')
info_data['bootloader'] = rules['BOOTLOADER'] info_data['bootloader'] = rules['BOOTLOADER']
@ -558,8 +589,9 @@ def avr_processor_rules(info_data, rules):
info_data['processor'] = 'unknown' info_data['processor'] = 'unknown'
if 'BOOTLOADER' in rules: if 'BOOTLOADER' in rules:
if 'bootloader' in info_data:
_log_warning(info_data, 'Bootloader is specified in both info.json and rules.mk, the rules.mk value wins.')
# FIXME(skullydazed/anyone): need to remove the massive amounts of duplication first
# if 'bootloader' in info_data:
# _log_warning(info_data, 'Bootloader is specified in both info.json and rules.mk, the rules.mk value wins.')
info_data['bootloader'] = rules['BOOTLOADER'] info_data['bootloader'] = rules['BOOTLOADER']
else: else:
@ -593,8 +625,8 @@ def merge_info_jsons(keyboard, info_data):
keyboard_validate(new_info_data) keyboard_validate(new_info_data)
except jsonschema.ValidationError as e: except jsonschema.ValidationError as e:
cli.log.error('Invalid info.json data: %s', e.message)
cli.log.error('Not including file %s', info_file)
json_path = '.'.join([str(p) for p in e.absolute_path])
cli.log.error('Invalid info.json data: %s: %s: %s', info_file, json_path, e.message)
continue continue
if not isinstance(new_info_data, dict): if not isinstance(new_info_data, dict):


Loading…
Cancel
Save