Skip to content

Commit

Permalink
get qmk generate-api into a good state
Browse files Browse the repository at this point in the history
  • Loading branch information
skullydazed committed Dec 2, 2020
1 parent 0e3b195 commit 0bf774b
Show file tree
Hide file tree
Showing 5 changed files with 117 additions and 35 deletions.
33 changes: 33 additions & 0 deletions data/schemas/keyboard.jsonschema
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,9 @@
"type": "object",
"additionalProperties": false,
"properties": {
"filename": {
"type": "string"
},
"c_macro": {
"type": "boolean"
},
Expand Down Expand Up @@ -119,6 +122,18 @@
"type": "number",
"min": 0.25
},
"r": {
"type": "number",
"min": 0
},
"rx": {
"type": "number",
"min": 0
},
"ry": {
"type": "number",
"min": 0
},
"w": {
"type": "number",
"min": 0.25
Expand Down Expand Up @@ -199,6 +214,12 @@
"min": 0,
"multipleOf": 1
},
"max_brightness": {
"type": "number",
"min": 0,
"max": 255,
"multipleOf": 1
},
"pin": {
"type": "string",
"pattern": "^[A-K]\\d{1,2}$"
Expand All @@ -207,6 +228,18 @@
"type": "number",
"min": 0,
"multipleOf": 1
},
"sleep": {"type": "boolean"},
"split": {"type": "boolean"},
"split_count": {
"type": "array",
"minLength": 2,
"maxLength": 2,
"items": {
"type": "number",
"min": 0,
"multipleOf": 1
}
}
}
},
Expand Down
26 changes: 20 additions & 6 deletions lib/python/qmk/c_parse.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,27 @@
"""Functions for working with config.h files.
"""
from pathlib import Path
import re

from milc import cli

from qmk.comment_remover import comment_remover

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):
Expand Down Expand Up @@ -53,7 +68,8 @@ def find_layouts(file):
parsed_layout = [_default_key(key) for key in layout.split(',')]

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] = {
'key_count': len(parsed_layout),
Expand Down Expand Up @@ -88,12 +104,10 @@ def parse_config_h_file(config_h_file, config_h=None):
if config_h_file.exists():
config_h_text = config_h_file.read_text()
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')):
line = line.strip()

if '//' in line:
line = line[:line.index('//')].strip()
line = strip_line_comment(line).strip()

if not line:
continue
Expand Down Expand Up @@ -156,6 +170,6 @@ def _parse_matrix_locations(matrix, file, macro_name):
row = row.replace('{', '').replace('}', '')
for col_num, identifier in enumerate(row.split(',')):
if identifier != 'KC_NO':
matrix_locations[identifier] = (row_num, col_num)
matrix_locations[identifier] = [row_num, col_num]

return matrix_locations
2 changes: 1 addition & 1 deletion lib/python/qmk/cli/generate/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ def generate_api(cli):
if 'vid' in usb and usb['vid'] not in usb_list['devices']:
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']] = {}

if 'vid' in usb and 'pid' in usb:
Expand Down
9 changes: 6 additions & 3 deletions lib/python/qmk/cli/generate/rules_mk.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,9 +41,12 @@ def generate_rules_mk(cli):
# Find features that should be enabled
if 'features' in kb_info_json:
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
if 'led_matrix' in kb_info_json and 'driver' in kb_info_json['led_matrix']:
Expand Down
82 changes: 57 additions & 25 deletions lib/python/qmk/info.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,13 +26,12 @@
}

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 = {
Expand All @@ -54,6 +53,8 @@
'twinkle': 'RGBLIGHT_EFFECT_TWINKLE'
}

usb_properties = {'vid': 'VENDOR_ID', 'pid': 'PRODUCT_ID', 'device_ver': 'DEVICE_VER'}

true_values = ['1', 'on', 'yes']
false_values = ['0', 'off', 'no']

Expand Down Expand Up @@ -97,7 +98,8 @@ def info_json(keyboard):
keyboard_api_validate(info_data)

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))
exit()

Expand Down Expand Up @@ -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:
_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:
info_data['indicators'][json_key] = config_c.get(config_key)

Expand All @@ -223,10 +228,23 @@ def _extract_community_layouts(info_data, rules):
def _extract_features(info_data, rules):
"""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():
if key.endswith('_ENABLE'):
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:
info_data['config_h_features'] = {}
Expand Down Expand Up @@ -277,12 +295,21 @@ def _extract_rgblight(info_data, config_c):
rgblight = info_data.get('rgblight', {})
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 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,))

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():
if config_key in config_c:
Expand Down Expand Up @@ -329,11 +356,16 @@ def _extract_matrix_info(info_data, config_c):

info_data['matrix_pins'] = {}

# FIXME(skullydazed/anyone): Should really check every pin, not just the first
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:
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 'matrix_pins' in info_data:
Expand All @@ -342,6 +374,9 @@ def _extract_matrix_info(info_data, config_c):
info_data['matrix_pins'] = {}
direct_pin_array = []

while direct_pins[-1] != '}':
direct_pins = direct_pins[:-1]

for row in direct_pins.split('},{'):
if row.startswith('{'):
row = row[1:]
Expand All @@ -365,8 +400,6 @@ def _extract_matrix_info(info_data, config_c):
def _extract_usb_info(info_data, config_c):
"""Populate the USB information.
"""
usb_properties = {'vid': 'VENDOR_ID', 'pid': 'PRODUCT_ID', 'device_ver': 'DEVICE_VER'}

if 'usb' not in info_data:
info_data['usb'] = {}

Expand All @@ -375,10 +408,7 @@ def _extract_usb_info(info_data, config_c):
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))

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

Expand Down Expand Up @@ -516,8 +546,9 @@ def arm_processor_rules(info_data, rules):
info_data['processor'] = 'unknown'

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']

Expand Down Expand Up @@ -555,8 +586,9 @@ def avr_processor_rules(info_data, rules):
info_data['processor'] = 'unknown'

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']
else:
Expand Down Expand Up @@ -590,8 +622,8 @@ def merge_info_jsons(keyboard, info_data):
keyboard_validate(new_info_data)

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

if not isinstance(new_info_data, dict):
Expand Down

0 comments on commit 0bf774b

Please sign in to comment.