Merge remote-tracking branch 'upstream/develop' into xap
This commit is contained in:
commit
575d8c19fc
1132 changed files with 38265 additions and 8171 deletions
|
@ -1,7 +1,7 @@
|
|||
"""This script automates the generation of the QMK API data.
|
||||
"""
|
||||
from pathlib import Path
|
||||
from shutil import copyfile
|
||||
import shutil
|
||||
import json
|
||||
|
||||
from milc import cli
|
||||
|
@ -12,28 +12,42 @@ from qmk.json_encoders import InfoJSONEncoder
|
|||
from qmk.json_schema import json_load
|
||||
from qmk.keyboard import find_readme, list_keyboards
|
||||
|
||||
TEMPLATE_PATH = Path('data/templates/api/')
|
||||
BUILD_API_PATH = Path('.build/api_data/')
|
||||
|
||||
|
||||
@cli.argument('-n', '--dry-run', arg_only=True, action='store_true', help="Don't write the data to disk.")
|
||||
@cli.argument('-f', '--filter', arg_only=True, action='append', default=[], help="Filter the list of keyboards based on partial name matches the supplied value. May be passed multiple times.")
|
||||
@cli.subcommand('Creates a new keymap for the keyboard of your choosing', hidden=False if cli.config.user.developer else True)
|
||||
def generate_api(cli):
|
||||
"""Generates the QMK API data.
|
||||
"""
|
||||
api_data_dir = Path('api_data')
|
||||
v1_dir = api_data_dir / 'v1'
|
||||
if BUILD_API_PATH.exists():
|
||||
shutil.rmtree(BUILD_API_PATH)
|
||||
|
||||
shutil.copytree(TEMPLATE_PATH, BUILD_API_PATH)
|
||||
|
||||
v1_dir = BUILD_API_PATH / 'v1'
|
||||
keyboard_all_file = v1_dir / 'keyboards.json' # A massive JSON containing everything
|
||||
keyboard_list_file = v1_dir / 'keyboard_list.json' # A simple list of keyboard targets
|
||||
keyboard_aliases_file = v1_dir / 'keyboard_aliases.json' # A list of historical keyboard names and their new name
|
||||
keyboard_metadata_file = v1_dir / 'keyboard_metadata.json' # All the data configurator/via needs for initialization
|
||||
usb_file = v1_dir / 'usb.json' # A mapping of USB VID/PID -> keyboard target
|
||||
|
||||
if not api_data_dir.exists():
|
||||
api_data_dir.mkdir()
|
||||
# Filter down when required
|
||||
keyboard_list = list_keyboards()
|
||||
if cli.args.filter:
|
||||
kb_list = []
|
||||
for keyboard_name in keyboard_list:
|
||||
if any(i in keyboard_name for i in cli.args.filter):
|
||||
kb_list.append(keyboard_name)
|
||||
keyboard_list = kb_list
|
||||
|
||||
kb_all = {}
|
||||
usb_list = {}
|
||||
|
||||
# Generate and write keyboard specific JSON files
|
||||
for keyboard_name in list_keyboards():
|
||||
for keyboard_name in keyboard_list:
|
||||
kb_all[keyboard_name] = info_json(keyboard_name)
|
||||
keyboard_dir = v1_dir / 'keyboards' / keyboard_name
|
||||
keyboard_info = keyboard_dir / 'info.json'
|
||||
|
@ -47,7 +61,7 @@ def generate_api(cli):
|
|||
cli.log.debug('Wrote file %s', keyboard_info)
|
||||
|
||||
if keyboard_readme_src:
|
||||
copyfile(keyboard_readme_src, keyboard_readme)
|
||||
shutil.copyfile(keyboard_readme_src, keyboard_readme)
|
||||
cli.log.debug('Copied %s -> %s', keyboard_readme_src, keyboard_readme)
|
||||
|
||||
if 'usb' in kb_all[keyboard_name]:
|
||||
|
|
|
@ -21,18 +21,7 @@ def direct_pins(direct_pins, postfix):
|
|||
cols = ','.join(map(str, [col or 'NO_PIN' for col in row]))
|
||||
rows.append('{' + cols + '}')
|
||||
|
||||
col_count = len(direct_pins[0])
|
||||
row_count = len(direct_pins)
|
||||
|
||||
return f"""
|
||||
#ifndef MATRIX_COLS{postfix}
|
||||
# define MATRIX_COLS{postfix} {col_count}
|
||||
#endif // MATRIX_COLS{postfix}
|
||||
|
||||
#ifndef MATRIX_ROWS{postfix}
|
||||
# define MATRIX_ROWS{postfix} {row_count}
|
||||
#endif // MATRIX_ROWS{postfix}
|
||||
|
||||
#ifndef DIRECT_PINS{postfix}
|
||||
# define DIRECT_PINS{postfix} {{ {", ".join(rows)} }}
|
||||
#endif // DIRECT_PINS{postfix}
|
||||
|
@ -42,14 +31,9 @@ def direct_pins(direct_pins, postfix):
|
|||
def pin_array(define, pins, postfix):
|
||||
"""Return the config.h lines that set a pin array.
|
||||
"""
|
||||
pin_num = len(pins)
|
||||
pin_array = ', '.join(map(str, [pin or 'NO_PIN' for pin in pins]))
|
||||
|
||||
return f"""
|
||||
#ifndef {define}S{postfix}
|
||||
# define {define}S{postfix} {pin_num}
|
||||
#endif // {define}S{postfix}
|
||||
|
||||
#ifndef {define}_PINS{postfix}
|
||||
# define {define}_PINS{postfix} {{ {pin_array} }}
|
||||
#endif // {define}_PINS{postfix}
|
||||
|
@ -73,6 +57,24 @@ def matrix_pins(matrix_pins, postfix=''):
|
|||
return '\n'.join(pins)
|
||||
|
||||
|
||||
def generate_matrix_size(kb_info_json, config_h_lines):
|
||||
"""Add the matrix size to the config.h.
|
||||
"""
|
||||
if 'matrix_pins' in kb_info_json:
|
||||
col_count = kb_info_json['matrix_size']['cols']
|
||||
row_count = kb_info_json['matrix_size']['rows']
|
||||
|
||||
config_h_lines.append(f"""
|
||||
#ifndef MATRIX_COLS
|
||||
# define MATRIX_COLS {col_count}
|
||||
#endif // MATRIX_COLS
|
||||
|
||||
#ifndef MATRIX_ROWS
|
||||
# define MATRIX_ROWS {row_count}
|
||||
#endif // MATRIX_ROWS
|
||||
""")
|
||||
|
||||
|
||||
def generate_config_items(kb_info_json, config_h_lines):
|
||||
"""Iterate through the info_config map to generate basic config values.
|
||||
"""
|
||||
|
@ -80,7 +82,7 @@ def generate_config_items(kb_info_json, config_h_lines):
|
|||
|
||||
for config_key, info_dict in info_config_map.items():
|
||||
info_key = info_dict['info_key']
|
||||
key_type = info_dict.get('value_type', 'str')
|
||||
key_type = info_dict.get('value_type', 'raw')
|
||||
to_config = info_dict.get('to_config', True)
|
||||
|
||||
if not to_config:
|
||||
|
@ -108,6 +110,11 @@ def generate_config_items(kb_info_json, config_h_lines):
|
|||
config_h_lines.append(f'#ifndef {key}')
|
||||
config_h_lines.append(f'# define {key} {value}')
|
||||
config_h_lines.append(f'#endif // {key}')
|
||||
elif key_type == 'str':
|
||||
config_h_lines.append('')
|
||||
config_h_lines.append(f'#ifndef {config_key}')
|
||||
config_h_lines.append(f'# define {config_key} "{config_value}"')
|
||||
config_h_lines.append(f'#endif // {config_key}')
|
||||
elif key_type == 'bcd_version':
|
||||
(major, minor, revision) = config_value.split('.')
|
||||
config_h_lines.append('')
|
||||
|
@ -183,6 +190,8 @@ def generate_config_h(cli):
|
|||
|
||||
generate_config_items(kb_info_json, config_h_lines)
|
||||
|
||||
generate_matrix_size(kb_info_json, config_h_lines)
|
||||
|
||||
if 'matrix_pins' in kb_info_json:
|
||||
config_h_lines.append(matrix_pins(kb_info_json['matrix_pins']))
|
||||
|
||||
|
@ -196,7 +205,7 @@ def generate_config_h(cli):
|
|||
cli.args.output.parent.mkdir(parents=True, exist_ok=True)
|
||||
if cli.args.output.exists():
|
||||
cli.args.output.replace(cli.args.output.parent / (cli.args.output.name + '.bak'))
|
||||
cli.args.output.write_text(config_h)
|
||||
cli.args.output.write_text(config_h, encoding='utf-8')
|
||||
|
||||
if not cli.args.quiet:
|
||||
cli.log.info('Wrote info_config.h to %s.', cli.args.output)
|
||||
|
|
|
@ -18,7 +18,8 @@ ignored_titles = ["Format code according to conventions"]
|
|||
def _is_ignored(title):
|
||||
for ignore in ignored_titles:
|
||||
if ignore in title:
|
||||
return
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def _get_pr_info(cache, gh, pr_num):
|
||||
|
|
|
@ -7,7 +7,10 @@ from subprocess import DEVNULL
|
|||
from milc import cli
|
||||
|
||||
DOCS_PATH = Path('docs/')
|
||||
BUILD_PATH = Path('.build/docs/')
|
||||
BUILD_PATH = Path('.build/')
|
||||
BUILD_DOCS_PATH = BUILD_PATH / 'docs'
|
||||
DOXYGEN_PATH = BUILD_PATH / 'doxygen'
|
||||
MOXYGEN_PATH = BUILD_DOCS_PATH / 'internals'
|
||||
|
||||
|
||||
@cli.subcommand('Build QMK documentation.', hidden=False if cli.config.user.developer else True)
|
||||
|
@ -18,10 +21,12 @@ def generate_docs(cli):
|
|||
* [ ] Add a real build step... something static docs
|
||||
"""
|
||||
|
||||
if BUILD_PATH.exists():
|
||||
shutil.rmtree(BUILD_PATH)
|
||||
if BUILD_DOCS_PATH.exists():
|
||||
shutil.rmtree(BUILD_DOCS_PATH)
|
||||
if DOXYGEN_PATH.exists():
|
||||
shutil.rmtree(DOXYGEN_PATH)
|
||||
|
||||
shutil.copytree(DOCS_PATH, BUILD_PATH)
|
||||
shutil.copytree(DOCS_PATH, BUILD_DOCS_PATH)
|
||||
|
||||
# When not verbose we want to hide all output
|
||||
args = {
|
||||
|
@ -30,10 +35,10 @@ def generate_docs(cli):
|
|||
'stdin': DEVNULL,
|
||||
}
|
||||
|
||||
cli.log.info('Generating internal docs...')
|
||||
cli.log.info('Generating docs...')
|
||||
|
||||
# Generate internal docs
|
||||
cli.run(['doxygen', 'Doxyfile'], **args)
|
||||
cli.run(['moxygen', '-q', '-a', '-g', '-o', BUILD_PATH / 'internals_%s.md', 'doxygen/xml'], **args)
|
||||
cli.run(['moxygen', '-q', '-g', '-o', MOXYGEN_PATH / '%s.md', DOXYGEN_PATH / 'xml'], **args)
|
||||
|
||||
cli.log.info('Successfully generated internal docs to %s.', BUILD_PATH)
|
||||
cli.log.info('Successfully generated docs to %s.', BUILD_DOCS_PATH)
|
||||
|
|
|
@ -40,16 +40,12 @@ def generate_layouts(cli):
|
|||
# Build the layouts.h file.
|
||||
layouts_h_lines = ['/* This file was generated by `qmk generate-layouts`. Do not edit or copy.', ' */', '', '#pragma once']
|
||||
|
||||
if 'matrix_pins' in kb_info_json:
|
||||
if 'direct' in kb_info_json['matrix_pins']:
|
||||
col_num = len(kb_info_json['matrix_pins']['direct'][0])
|
||||
row_num = len(kb_info_json['matrix_pins']['direct'])
|
||||
elif 'cols' in kb_info_json['matrix_pins'] and 'rows' in kb_info_json['matrix_pins']:
|
||||
col_num = len(kb_info_json['matrix_pins']['cols'])
|
||||
row_num = len(kb_info_json['matrix_pins']['rows'])
|
||||
else:
|
||||
cli.log.error('%s: Invalid matrix config.', cli.config.generate_layouts.keyboard)
|
||||
return False
|
||||
if 'matrix_size' not in kb_info_json:
|
||||
cli.log.error('%s: Invalid matrix config.', cli.config.generate_layouts.keyboard)
|
||||
return False
|
||||
|
||||
col_num = kb_info_json['matrix_size']['cols']
|
||||
row_num = kb_info_json['matrix_size']['rows']
|
||||
|
||||
for layout_name in kb_info_json['layouts']:
|
||||
if kb_info_json['layouts'][layout_name]['c_macro']:
|
||||
|
|
|
@ -19,7 +19,7 @@ def process_mapping_rule(kb_info_json, rules_key, info_dict):
|
|||
return None
|
||||
|
||||
info_key = info_dict['info_key']
|
||||
key_type = info_dict.get('value_type', 'str')
|
||||
key_type = info_dict.get('value_type', 'raw')
|
||||
|
||||
try:
|
||||
rules_value = kb_info_json[info_key]
|
||||
|
@ -29,9 +29,11 @@ def process_mapping_rule(kb_info_json, rules_key, info_dict):
|
|||
if key_type in ['array', 'list']:
|
||||
return f'{rules_key} ?= {" ".join(rules_value)}'
|
||||
elif key_type == 'bool':
|
||||
return f'{rules_key} ?= {"on" if rules_value else "off"}'
|
||||
return f'{rules_key} ?= {"yes" if rules_value else "no"}'
|
||||
elif key_type == 'mapping':
|
||||
return '\n'.join([f'{key} ?= {value}' for key, value in rules_value.items()])
|
||||
elif key_type == 'str':
|
||||
return f'{rules_key} ?= "{rules_value}"'
|
||||
|
||||
return f'{rules_key} ?= {rules_value}'
|
||||
|
||||
|
|
|
@ -1,12 +1,11 @@
|
|||
"""Generate a keymap.c from a configurator export.
|
||||
"""
|
||||
import json
|
||||
|
||||
from argcomplete.completers import FilesCompleter
|
||||
from milc import cli
|
||||
|
||||
import qmk.keymap
|
||||
import qmk.path
|
||||
from qmk.commands import parse_configurator_json
|
||||
|
||||
|
||||
@cli.argument('-o', '--output', arg_only=True, type=qmk.path.normpath, help='File to write to')
|
||||
|
@ -19,14 +18,8 @@ def json2c(cli):
|
|||
This command uses the `qmk.keymap` module to generate a keymap.c from a configurator export. The generated keymap is written to stdout, or to a file if -o is provided.
|
||||
"""
|
||||
|
||||
try:
|
||||
# Parse the configurator from json file (or stdin)
|
||||
user_keymap = json.load(cli.args.filename)
|
||||
|
||||
except json.decoder.JSONDecodeError as ex:
|
||||
cli.log.error('The JSON input does not appear to be valid.')
|
||||
cli.log.error(ex)
|
||||
return False
|
||||
# Parse the configurator from json file (or stdin)
|
||||
user_keymap = parse_configurator_json(cli.args.filename)
|
||||
|
||||
# Environment processing
|
||||
if cli.args.output and cli.args.output.name == '-':
|
||||
|
|
|
@ -15,48 +15,11 @@ from qmk.json_schema import load_jsonschema
|
|||
from qmk.path import keyboard
|
||||
from qmk.json_encoders import InfoJSONEncoder
|
||||
from qmk.json_schema import deep_update
|
||||
from qmk.constants import MCU2BOOTLOADER
|
||||
|
||||
COMMUNITY = Path('layouts/default/')
|
||||
TEMPLATE = Path('data/templates/keyboard/')
|
||||
|
||||
MCU2BOOTLOADER = {
|
||||
"MKL26Z64": "halfkay",
|
||||
"MK20DX128": "halfkay",
|
||||
"MK20DX256": "halfkay",
|
||||
"MK66FX1M0": "halfkay",
|
||||
"STM32F042": "stm32-dfu",
|
||||
"STM32F072": "stm32-dfu",
|
||||
"STM32F103": "stm32duino",
|
||||
"STM32F303": "stm32-dfu",
|
||||
"STM32F401": "stm32-dfu",
|
||||
"STM32F405": "stm32-dfu",
|
||||
"STM32F407": "stm32-dfu",
|
||||
"STM32F411": "stm32-dfu",
|
||||
"STM32F446": "stm32-dfu",
|
||||
"STM32G431": "stm32-dfu",
|
||||
"STM32G474": "stm32-dfu",
|
||||
"STM32L412": "stm32-dfu",
|
||||
"STM32L422": "stm32-dfu",
|
||||
"STM32L432": "stm32-dfu",
|
||||
"STM32L433": "stm32-dfu",
|
||||
"STM32L442": "stm32-dfu",
|
||||
"STM32L443": "stm32-dfu",
|
||||
"GD32VF103": "gd32v-dfu",
|
||||
"WB32F3G71": "wb32-dfu",
|
||||
"atmega16u2": "atmel-dfu",
|
||||
"atmega32u2": "atmel-dfu",
|
||||
"atmega16u4": "atmel-dfu",
|
||||
"atmega32u4": "atmel-dfu",
|
||||
"at90usb162": "atmel-dfu",
|
||||
"at90usb646": "atmel-dfu",
|
||||
"at90usb647": "atmel-dfu",
|
||||
"at90usb1286": "atmel-dfu",
|
||||
"at90usb1287": "atmel-dfu",
|
||||
"atmega32a": "bootloadhid",
|
||||
"atmega328p": "usbasploader",
|
||||
"atmega328": "usbasploader",
|
||||
}
|
||||
|
||||
# defaults
|
||||
schema = dotty(load_jsonschema('keyboard'))
|
||||
mcu_types = sorted(schema["properties.processor.enum"], key=str.casefold)
|
||||
|
@ -141,20 +104,42 @@ def augment_community_info(src, dest):
|
|||
dest.write_text(json.dumps(info, cls=InfoJSONEncoder))
|
||||
|
||||
|
||||
def _question(*args, **kwargs):
|
||||
"""Ugly workaround until 'milc' learns to display a repromt msg
|
||||
"""
|
||||
# TODO: Remove this once milc.questions.question handles reprompt messages
|
||||
|
||||
reprompt = kwargs["reprompt"]
|
||||
del kwargs["reprompt"]
|
||||
validate = kwargs["validate"]
|
||||
del kwargs["validate"]
|
||||
|
||||
prompt = args[0]
|
||||
ret = None
|
||||
while not ret:
|
||||
ret = question(prompt, **kwargs)
|
||||
if not validate(ret):
|
||||
ret = None
|
||||
prompt = reprompt
|
||||
|
||||
return ret
|
||||
|
||||
|
||||
def prompt_keyboard():
|
||||
prompt = """{fg_yellow}Name Your Keyboard Project{style_reset_all}
|
||||
|
||||
For more infomation, see:
|
||||
https://docs.qmk.fm/#/hardware_keyboard_guidelines?id=naming-your-keyboardproject
|
||||
|
||||
keyboard Name? """
|
||||
Keyboard Name? """
|
||||
|
||||
return question(prompt, validate=lambda x: not keyboard(x).exists())
|
||||
errmsg = 'Keyboard already exists! Please choose a different name:'
|
||||
|
||||
return _question(prompt, reprompt=errmsg, validate=lambda x: not keyboard(x).exists())
|
||||
|
||||
|
||||
def prompt_user():
|
||||
prompt = """{fg_yellow}Attribution{style_reset_all}
|
||||
|
||||
prompt = """
|
||||
{fg_yellow}Attribution{style_reset_all}
|
||||
Used for maintainer, copyright, etc
|
||||
|
||||
Your GitHub Username? """
|
||||
|
@ -162,8 +147,8 @@ Your GitHub Username? """
|
|||
|
||||
|
||||
def prompt_name(def_name):
|
||||
prompt = """{fg_yellow}More Attribution{style_reset_all}
|
||||
|
||||
prompt = """
|
||||
{fg_yellow}More Attribution{style_reset_all}
|
||||
Used for maintainer, copyright, etc
|
||||
|
||||
Your Real Name? """
|
||||
|
@ -171,8 +156,8 @@ Your Real Name? """
|
|||
|
||||
|
||||
def prompt_layout():
|
||||
prompt = """{fg_yellow}Pick Base Layout{style_reset_all}
|
||||
|
||||
prompt = """
|
||||
{fg_yellow}Pick Base Layout{style_reset_all}
|
||||
As a starting point, one of the common layouts can be used to bootstrap the process
|
||||
|
||||
Default Layout? """
|
||||
|
@ -184,8 +169,8 @@ Default Layout? """
|
|||
|
||||
|
||||
def prompt_mcu():
|
||||
prompt = """{fg_yellow}What Powers Your Project{style_reset_all}
|
||||
|
||||
prompt = """
|
||||
{fg_yellow}What Powers Your Project{style_reset_all}
|
||||
For more infomation, see:
|
||||
https://docs.qmk.fm/#/compatible_microcontrollers
|
||||
|
||||
|
@ -199,7 +184,7 @@ MCU? """
|
|||
@cli.argument('-kb', '--keyboard', help='Specify the name for the new keyboard directory', arg_only=True, type=keyboard_name)
|
||||
@cli.argument('-l', '--layout', help='Community layout to bootstrap with', arg_only=True, type=layout_type)
|
||||
@cli.argument('-t', '--type', help='Specify the keyboard MCU type', arg_only=True, type=mcu_type)
|
||||
@cli.argument('-u', '--username', help='Specify your username (default from Git config)', arg_only=True)
|
||||
@cli.argument('-u', '--username', help='Specify your username (default from Git config)', dest='name')
|
||||
@cli.argument('-n', '--realname', help='Specify your real name if you want to use that. Defaults to username', arg_only=True)
|
||||
@cli.subcommand('Creates a new keyboard directory')
|
||||
def new_keyboard(cli):
|
||||
|
@ -209,8 +194,8 @@ def new_keyboard(cli):
|
|||
cli.echo('')
|
||||
|
||||
kb_name = cli.args.keyboard if cli.args.keyboard else prompt_keyboard()
|
||||
user_name = cli.args.username if cli.args.username else prompt_user()
|
||||
real_name = cli.args.realname or cli.args.username if cli.args.realname or cli.args.username else prompt_name(user_name)
|
||||
user_name = cli.config.new_keyboard.name if cli.config.new_keyboard.name else prompt_user()
|
||||
real_name = cli.args.realname or cli.config.new_keyboard.name if cli.args.realname or cli.config.new_keyboard.name else prompt_name(user_name)
|
||||
default_layout = cli.args.layout if cli.args.layout else prompt_layout()
|
||||
mcu = cli.args.type if cli.args.type else prompt_mcu()
|
||||
bootloader = select_default_bootloader(mcu)
|
||||
|
@ -223,7 +208,15 @@ def new_keyboard(cli):
|
|||
cli.log.error(f'Keyboard {{fg_cyan}}{kb_name}{{fg_reset}} already exists! Please choose a different name.')
|
||||
return 1
|
||||
|
||||
tokens = {'YEAR': str(date.today().year), 'KEYBOARD': kb_name, 'USER_NAME': user_name, 'REAL_NAME': real_name, 'LAYOUT': default_layout, 'MCU': mcu, 'BOOTLOADER': bootloader}
|
||||
tokens = { # Comment here is to force multiline formatting
|
||||
'YEAR': str(date.today().year),
|
||||
'KEYBOARD': kb_name,
|
||||
'USER_NAME': user_name,
|
||||
'REAL_NAME': real_name,
|
||||
'LAYOUT': default_layout,
|
||||
'MCU': mcu,
|
||||
'BOOTLOADER': bootloader
|
||||
}
|
||||
|
||||
if cli.config.general.verbose:
|
||||
cli.log.info("Creating keyboard with:")
|
||||
|
|
|
@ -12,7 +12,7 @@ from milc import cli
|
|||
def pytest(cli):
|
||||
"""Run several linting/testing commands.
|
||||
"""
|
||||
nose2 = cli.run(['nose2', '-v', '-t' 'lib/python', *cli.args.test], capture_output=False, stdin=DEVNULL)
|
||||
nose2 = cli.run(['nose2', '-v', '-t', 'lib/python', *cli.args.test], capture_output=False, stdin=DEVNULL)
|
||||
flake8 = cli.run(['flake8', 'lib/python'], capture_output=False, stdin=DEVNULL)
|
||||
|
||||
return flake8.returncode | nose2.returncode
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
"""Helper functions for commands.
|
||||
"""
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
import shutil
|
||||
|
@ -9,10 +8,11 @@ from subprocess import DEVNULL
|
|||
from time import strftime
|
||||
|
||||
from milc import cli
|
||||
import jsonschema
|
||||
|
||||
import qmk.keymap
|
||||
from qmk.constants import QMK_FIRMWARE, KEYBOARD_OUTPUT_PREFIX
|
||||
from qmk.json_schema import json_load
|
||||
from qmk.json_schema import json_load, validate
|
||||
|
||||
time_fmt = '%Y-%m-%d-%H:%M:%S'
|
||||
|
||||
|
@ -188,6 +188,10 @@ def compile_configurator_json(user_keymap, bootloader=None, parallel=1, **env_va
|
|||
|
||||
A command to run to compile and flash the C file.
|
||||
"""
|
||||
# In case the user passes a keymap.json from a keymap directory directly to the CLI.
|
||||
# e.g.: qmk compile - < keyboards/clueboard/california/keymaps/default/keymap.json
|
||||
user_keymap["keymap"] = user_keymap.get("keymap", "default_json")
|
||||
|
||||
# Write the keymap.c file
|
||||
keyboard_filesafe = user_keymap['keyboard'].replace('/', '_')
|
||||
target = f'{keyboard_filesafe}_{user_keymap["keymap"]}'
|
||||
|
@ -251,8 +255,15 @@ def compile_configurator_json(user_keymap, bootloader=None, parallel=1, **env_va
|
|||
def parse_configurator_json(configurator_file):
|
||||
"""Open and parse a configurator json export
|
||||
"""
|
||||
# FIXME(skullydazed/anyone): Add validation here
|
||||
user_keymap = json.load(configurator_file)
|
||||
user_keymap = json_load(configurator_file)
|
||||
# Validate against the jsonschema
|
||||
try:
|
||||
validate(user_keymap, 'qmk.keymap.v1')
|
||||
|
||||
except jsonschema.ValidationError as e:
|
||||
cli.log.error(f'Invalid JSON keymap: {configurator_file} : {e.message}')
|
||||
exit(1)
|
||||
|
||||
orig_keyboard = user_keymap['keyboard']
|
||||
aliases = json_load(Path('data/mappings/keyboard_aliases.json'))
|
||||
|
||||
|
|
|
@ -18,6 +18,45 @@ CHIBIOS_PROCESSORS = 'cortex-m0', 'cortex-m0plus', 'cortex-m3', 'cortex-m4', 'MK
|
|||
LUFA_PROCESSORS = 'at90usb162', 'atmega16u2', 'atmega32u2', 'atmega16u4', 'atmega32u4', 'at90usb646', 'at90usb647', 'at90usb1286', 'at90usb1287', None
|
||||
VUSB_PROCESSORS = 'atmega32a', 'atmega328p', 'atmega328', 'attiny85'
|
||||
|
||||
# Bootloaders of the supported processors
|
||||
MCU2BOOTLOADER = {
|
||||
"MKL26Z64": "halfkay",
|
||||
"MK20DX128": "halfkay",
|
||||
"MK20DX256": "halfkay",
|
||||
"MK66FX1M0": "halfkay",
|
||||
"STM32F042": "stm32-dfu",
|
||||
"STM32F072": "stm32-dfu",
|
||||
"STM32F103": "stm32duino",
|
||||
"STM32F303": "stm32-dfu",
|
||||
"STM32F401": "stm32-dfu",
|
||||
"STM32F405": "stm32-dfu",
|
||||
"STM32F407": "stm32-dfu",
|
||||
"STM32F411": "stm32-dfu",
|
||||
"STM32F446": "stm32-dfu",
|
||||
"STM32G431": "stm32-dfu",
|
||||
"STM32G474": "stm32-dfu",
|
||||
"STM32L412": "stm32-dfu",
|
||||
"STM32L422": "stm32-dfu",
|
||||
"STM32L432": "stm32-dfu",
|
||||
"STM32L433": "stm32-dfu",
|
||||
"STM32L442": "stm32-dfu",
|
||||
"STM32L443": "stm32-dfu",
|
||||
"GD32VF103": "gd32v-dfu",
|
||||
"WB32F3G71": "wb32-dfu",
|
||||
"atmega16u2": "atmel-dfu",
|
||||
"atmega32u2": "atmel-dfu",
|
||||
"atmega16u4": "atmel-dfu",
|
||||
"atmega32u4": "atmel-dfu",
|
||||
"at90usb162": "atmel-dfu",
|
||||
"at90usb646": "atmel-dfu",
|
||||
"at90usb647": "atmel-dfu",
|
||||
"at90usb1286": "atmel-dfu",
|
||||
"at90usb1287": "atmel-dfu",
|
||||
"atmega32a": "bootloadhid",
|
||||
"atmega328p": "usbasploader",
|
||||
"atmega328": "usbasploader",
|
||||
}
|
||||
|
||||
# Common format strings
|
||||
DATE_FORMAT = '%Y-%m-%d'
|
||||
DATETIME_FORMAT = '%Y-%m-%d %H:%M:%S %Z'
|
||||
|
|
|
@ -411,7 +411,7 @@ def _extract_config_h(info_data):
|
|||
|
||||
for config_key, info_dict in info_config_map.items():
|
||||
info_key = info_dict['info_key']
|
||||
key_type = info_dict.get('value_type', 'str')
|
||||
key_type = info_dict.get('value_type', 'raw')
|
||||
|
||||
try:
|
||||
if config_key in config_c and info_dict.get('to_json', True):
|
||||
|
@ -443,6 +443,9 @@ def _extract_config_h(info_data):
|
|||
elif key_type == 'int':
|
||||
dotty_info[info_key] = int(config_c[config_key])
|
||||
|
||||
elif key_type == 'str':
|
||||
dotty_info[info_key] = config_c[config_key].strip('"')
|
||||
|
||||
elif key_type == 'bcd_version':
|
||||
major = int(config_c[config_key][2:4])
|
||||
minor = int(config_c[config_key][4])
|
||||
|
@ -491,7 +494,7 @@ def _extract_rules_mk(info_data):
|
|||
|
||||
for rules_key, info_dict in info_rules_map.items():
|
||||
info_key = info_dict['info_key']
|
||||
key_type = info_dict.get('value_type', 'str')
|
||||
key_type = info_dict.get('value_type', 'raw')
|
||||
|
||||
try:
|
||||
if rules_key in rules and info_dict.get('to_json', True):
|
||||
|
@ -523,6 +526,9 @@ def _extract_rules_mk(info_data):
|
|||
elif key_type == 'int':
|
||||
dotty_info[info_key] = int(rules[rules_key])
|
||||
|
||||
elif key_type == 'str':
|
||||
dotty_info[info_key] = rules[rules_key].strip('"')
|
||||
|
||||
else:
|
||||
dotty_info[info_key] = rules[rules_key]
|
||||
|
||||
|
@ -550,6 +556,11 @@ def _matrix_size(info_data):
|
|||
info_data['matrix_size']['cols'] = len(info_data['matrix_pins']['cols'])
|
||||
info_data['matrix_size']['rows'] = len(info_data['matrix_pins']['rows'])
|
||||
|
||||
# Assumption of split common
|
||||
if 'split' in info_data:
|
||||
if info_data['split'].get('enabled', False):
|
||||
info_data['matrix_size']['rows'] *= 2
|
||||
|
||||
return info_data
|
||||
|
||||
|
||||
|
|
|
@ -16,7 +16,11 @@ def json_load(json_file):
|
|||
Note: file must be a Path object.
|
||||
"""
|
||||
try:
|
||||
return hjson.load(json_file.open(encoding='utf-8'))
|
||||
# Get the IO Stream for Path objects
|
||||
# Not necessary if the data is provided via stdin
|
||||
if isinstance(json_file, Path):
|
||||
json_file = json_file.open(encoding='utf-8')
|
||||
return hjson.load(json_file)
|
||||
|
||||
except (json.decoder.JSONDecodeError, hjson.HjsonDecodeError) as e:
|
||||
cli.log.error('Invalid JSON encountered attempting to load {fg_cyan}%s{fg_reset}:\n\t{fg_red}%s', json_file, e)
|
||||
|
@ -62,7 +66,7 @@ def create_validator(schema):
|
|||
"""Creates a validator for the given schema id.
|
||||
"""
|
||||
schema_store = compile_schema_store()
|
||||
resolver = jsonschema.RefResolver.from_schema(schema_store['qmk.keyboard.v1'], store=schema_store)
|
||||
resolver = jsonschema.RefResolver.from_schema(schema_store[schema], store=schema_store)
|
||||
|
||||
return jsonschema.Draft7Validator(schema_store[schema], resolver=resolver).validate
|
||||
|
||||
|
|
|
@ -162,13 +162,12 @@ def render_layout(layout_data, render_ascii, key_labels=None):
|
|||
"""
|
||||
textpad = [array('u', ' ' * 200) for x in range(100)]
|
||||
style = 'ascii' if render_ascii else 'unicode'
|
||||
box_chars = BOX_DRAWING_CHARACTERS[style]
|
||||
|
||||
for key in layout_data:
|
||||
x = ceil(key.get('x', 0) * 4)
|
||||
y = ceil(key.get('y', 0) * 3)
|
||||
w = ceil(key.get('w', 1) * 4)
|
||||
h = ceil(key.get('h', 1) * 3)
|
||||
x = key.get('x', 0)
|
||||
y = key.get('y', 0)
|
||||
w = key.get('w', 1)
|
||||
h = key.get('h', 1)
|
||||
|
||||
if key_labels:
|
||||
label = key_labels.pop(0)
|
||||
|
@ -177,26 +176,12 @@ def render_layout(layout_data, render_ascii, key_labels=None):
|
|||
else:
|
||||
label = key.get('label', '')
|
||||
|
||||
label_len = w - 2
|
||||
label_leftover = label_len - len(label)
|
||||
|
||||
if len(label) > label_len:
|
||||
label = label[:label_len]
|
||||
|
||||
label_blank = ' ' * label_len
|
||||
label_border = box_chars['h'] * label_len
|
||||
label_middle = label + ' '*label_leftover # noqa: yapf insists there be no whitespace around *
|
||||
|
||||
top_line = array('u', box_chars['tl'] + label_border + box_chars['tr'])
|
||||
lab_line = array('u', box_chars['v'] + label_middle + box_chars['v'])
|
||||
mid_line = array('u', box_chars['v'] + label_blank + box_chars['v'])
|
||||
bot_line = array('u', box_chars['bl'] + label_border + box_chars['br'])
|
||||
|
||||
textpad[y][x:x + w] = top_line
|
||||
textpad[y + 1][x:x + w] = lab_line
|
||||
for i in range(h - 3):
|
||||
textpad[y + i + 2][x:x + w] = mid_line
|
||||
textpad[y + h - 1][x:x + w] = bot_line
|
||||
if x >= 0.25 and w == 1.25 and h == 2:
|
||||
render_key_isoenter(textpad, x, y, w, h, label, style)
|
||||
elif w == 2.25 and h == 2:
|
||||
render_key_baenter(textpad, x, y, w, h, label, style)
|
||||
else:
|
||||
render_key_rect(textpad, x, y, w, h, label, style)
|
||||
|
||||
lines = []
|
||||
for line in textpad:
|
||||
|
@ -216,3 +201,96 @@ def render_layouts(info_json, render_ascii):
|
|||
layouts[layout] = render_layout(layout_data, render_ascii)
|
||||
|
||||
return layouts
|
||||
|
||||
|
||||
def render_key_rect(textpad, x, y, w, h, label, style):
|
||||
box_chars = BOX_DRAWING_CHARACTERS[style]
|
||||
x = ceil(x * 4)
|
||||
y = ceil(y * 3)
|
||||
w = ceil(w * 4)
|
||||
h = ceil(h * 3)
|
||||
|
||||
label_len = w - 2
|
||||
label_leftover = label_len - len(label)
|
||||
|
||||
if len(label) > label_len:
|
||||
label = label[:label_len]
|
||||
|
||||
label_blank = ' ' * label_len
|
||||
label_border = box_chars['h'] * label_len
|
||||
label_middle = label + ' '*label_leftover # noqa: yapf insists there be no whitespace around *
|
||||
|
||||
top_line = array('u', box_chars['tl'] + label_border + box_chars['tr'])
|
||||
lab_line = array('u', box_chars['v'] + label_middle + box_chars['v'])
|
||||
mid_line = array('u', box_chars['v'] + label_blank + box_chars['v'])
|
||||
bot_line = array('u', box_chars['bl'] + label_border + box_chars['br'])
|
||||
|
||||
textpad[y][x:x + w] = top_line
|
||||
textpad[y + 1][x:x + w] = lab_line
|
||||
for i in range(h - 3):
|
||||
textpad[y + i + 2][x:x + w] = mid_line
|
||||
textpad[y + h - 1][x:x + w] = bot_line
|
||||
|
||||
|
||||
def render_key_isoenter(textpad, x, y, w, h, label, style):
|
||||
box_chars = BOX_DRAWING_CHARACTERS[style]
|
||||
x = ceil(x * 4)
|
||||
y = ceil(y * 3)
|
||||
w = ceil(w * 4)
|
||||
h = ceil(h * 3)
|
||||
|
||||
label_len = w - 1
|
||||
label_leftover = label_len - len(label)
|
||||
|
||||
if len(label) > label_len:
|
||||
label = label[:label_len]
|
||||
|
||||
label_blank = ' ' * (label_len-1) # noqa: yapf insists there be no whitespace around - and *
|
||||
label_border_top = box_chars['h'] * label_len
|
||||
label_border_bottom = box_chars['h'] * (label_len-1) # noqa
|
||||
label_middle = label + ' '*label_leftover # noqa
|
||||
|
||||
top_line = array('u', box_chars['tl'] + label_border_top + box_chars['tr'])
|
||||
lab_line = array('u', box_chars['v'] + label_middle + box_chars['v'])
|
||||
crn_line = array('u', box_chars['bl'] + box_chars['tr'] + label_blank + box_chars['v'])
|
||||
mid_line = array('u', box_chars['v'] + label_blank + box_chars['v'])
|
||||
bot_line = array('u', box_chars['bl'] + label_border_bottom + box_chars['br'])
|
||||
|
||||
textpad[y][x - 1:x + w] = top_line
|
||||
textpad[y + 1][x - 1:x + w] = lab_line
|
||||
textpad[y + 2][x - 1:x + w] = crn_line
|
||||
textpad[y + 3][x:x + w] = mid_line
|
||||
textpad[y + 4][x:x + w] = mid_line
|
||||
textpad[y + 5][x:x + w] = bot_line
|
||||
|
||||
|
||||
def render_key_baenter(textpad, x, y, w, h, label, style):
|
||||
box_chars = BOX_DRAWING_CHARACTERS[style]
|
||||
x = ceil(x * 4)
|
||||
y = ceil(y * 3)
|
||||
w = ceil(w * 4)
|
||||
h = ceil(h * 3)
|
||||
|
||||
label_len = w - 2
|
||||
label_leftover = label_len - len(label)
|
||||
|
||||
if len(label) > label_len:
|
||||
label = label[:label_len]
|
||||
|
||||
label_blank = ' ' * (label_len-3) # noqa: yapf insists there be no whitespace around - and *
|
||||
label_border_top = box_chars['h'] * (label_len-3) # noqa
|
||||
label_border_bottom = box_chars['h'] * label_len
|
||||
label_middle = label + ' '*label_leftover # noqa
|
||||
|
||||
top_line = array('u', box_chars['tl'] + label_border_top + box_chars['tr'])
|
||||
mid_line = array('u', box_chars['v'] + label_blank + box_chars['v'])
|
||||
crn_line = array('u', box_chars['tl'] + box_chars['h'] + box_chars['h'] + box_chars['br'] + label_blank + box_chars['v'])
|
||||
lab_line = array('u', box_chars['v'] + label_middle + box_chars['v'])
|
||||
bot_line = array('u', box_chars['bl'] + label_border_bottom + box_chars['br'])
|
||||
|
||||
textpad[y][x + 3:x + w] = top_line
|
||||
textpad[y + 1][x + 3:x + w] = mid_line
|
||||
textpad[y + 2][x + 3:x + w] = mid_line
|
||||
textpad[y + 3][x:x + w] = crn_line
|
||||
textpad[y + 4][x:x + w] = lab_line
|
||||
textpad[y + 5][x:x + w] = bot_line
|
||||
|
|
|
@ -70,9 +70,13 @@ def normpath(path):
|
|||
|
||||
|
||||
class FileType(argparse.FileType):
|
||||
def __init__(self, encoding='UTF-8'):
|
||||
# Use UTF8 by default for stdin
|
||||
return super().__init__(encoding=encoding)
|
||||
|
||||
def __call__(self, string):
|
||||
"""normalize and check exists
|
||||
otherwise magic strings like '-' for stdin resolve to bad paths
|
||||
"""
|
||||
norm = normpath(string)
|
||||
return super().__call__(norm if norm.exists() else string)
|
||||
return norm if norm.exists() else super().__call__(string)
|
||||
|
|
|
@ -156,6 +156,18 @@ def test_json2c_stdin():
|
|||
assert result.stdout == '#include QMK_KEYBOARD_H\nconst uint16_t PROGMEM keymaps[][MATRIX_ROWS][MATRIX_COLS] = {\t[0] = LAYOUT_ortho_1x1(KC_A)};\n\n'
|
||||
|
||||
|
||||
def test_json2c_wrong_json():
|
||||
result = check_subcommand('json2c', 'keyboards/handwired/pytest/info.json')
|
||||
check_returncode(result, [1])
|
||||
assert 'Invalid JSON keymap' in result.stdout
|
||||
|
||||
|
||||
def test_json2c_no_json():
|
||||
result = check_subcommand('json2c', 'keyboards/handwired/pytest/pytest.h')
|
||||
check_returncode(result, [1])
|
||||
assert 'Invalid JSON encountered' in result.stdout
|
||||
|
||||
|
||||
def test_info():
|
||||
result = check_subcommand('info', '-kb', 'handwired/pytest/basic')
|
||||
check_returncode(result)
|
||||
|
@ -232,7 +244,7 @@ def test_clean():
|
|||
|
||||
|
||||
def test_generate_api():
|
||||
result = check_subcommand('generate-api', '--dry-run')
|
||||
result = check_subcommand('generate-api', '--dry-run', '--filter', 'handwired/pytest')
|
||||
check_returncode(result)
|
||||
|
||||
|
||||
|
@ -247,7 +259,7 @@ def test_generate_config_h():
|
|||
result = check_subcommand('generate-config-h', '-kb', 'handwired/pytest/basic')
|
||||
check_returncode(result)
|
||||
assert '# define DEVICE_VER 0x0001' in result.stdout
|
||||
assert '# define DESCRIPTION handwired/pytest/basic' in result.stdout
|
||||
assert '# define DESCRIPTION "handwired/pytest/basic"' in result.stdout
|
||||
assert '# define DIODE_DIRECTION COL2ROW' in result.stdout
|
||||
assert '# define MANUFACTURER none' in result.stdout
|
||||
assert '# define PRODUCT pytest' in result.stdout
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue