diff options
Diffstat (limited to 'lib/python/qmk/cli')
-rw-r--r-- | lib/python/qmk/cli/__init__.py | 3 | ||||
-rw-r--r-- | lib/python/qmk/cli/generate/community_modules.py | 75 | ||||
-rw-r--r--[-rwxr-xr-x] | lib/python/qmk/cli/generate/compilation_database.py | 174 | ||||
-rw-r--r-- | lib/python/qmk/cli/generate/keycodes.py | 16 | ||||
-rw-r--r-- | lib/python/qmk/cli/generate/keycodes_tests.py | 39 | ||||
-rwxr-xr-x | lib/python/qmk/cli/generate/rules_mk.py | 34 | ||||
-rw-r--r-- | lib/python/qmk/cli/lint.py | 44 | ||||
-rwxr-xr-x | lib/python/qmk/cli/new/keymap.py | 60 |
8 files changed, 201 insertions, 244 deletions
diff --git a/lib/python/qmk/cli/__init__.py b/lib/python/qmk/cli/__init__.py index 3f2ba9ce3c..26905ec134 100644 --- a/lib/python/qmk/cli/__init__.py +++ b/lib/python/qmk/cli/__init__.py @@ -58,7 +58,6 @@ subcommands = [ 'qmk.cli.generate.keyboard_c', 'qmk.cli.generate.keyboard_h', 'qmk.cli.generate.keycodes', - 'qmk.cli.generate.keycodes_tests', 'qmk.cli.generate.keymap_h', 'qmk.cli.generate.make_dependencies', 'qmk.cli.generate.rgb_breathe_table', @@ -215,7 +214,7 @@ if sys.version_info[0] != 3 or sys.version_info[1] < 9: milc_version = __VERSION__.split('.') -if int(milc_version[0]) < 2 and int(milc_version[1]) < 4: +if int(milc_version[0]) < 2 and int(milc_version[1]) < 9: requirements = Path('requirements.txt').resolve() _eprint(f'Your MILC library is too old! Please upgrade: python3 -m pip install -U -r {str(requirements)}') diff --git a/lib/python/qmk/cli/generate/community_modules.py b/lib/python/qmk/cli/generate/community_modules.py index 23678a2fb5..61e24077d3 100644 --- a/lib/python/qmk/cli/generate/community_modules.py +++ b/lib/python/qmk/cli/generate/community_modules.py @@ -8,7 +8,7 @@ import qmk.path from qmk.info import get_modules from qmk.keyboard import keyboard_completer, keyboard_folder from qmk.commands import dump_lines -from qmk.constants import GPL2_HEADER_C_LIKE, GENERATED_HEADER_C_LIKE +from qmk.constants import GPL2_HEADER_C_LIKE, GENERATED_HEADER_C_LIKE, GPL2_HEADER_SH_LIKE, GENERATED_HEADER_SH_LIKE from qmk.community_modules import module_api_list, load_module_jsons, find_module_path @@ -76,6 +76,8 @@ def _render_api_implementations(api, module): lines.append(f'__attribute__((weak)) {api.ret_type} {api.name}_{module_name}_user({api.args}) {{') if api.ret_type == 'bool': lines.append(' return true;') + elif api.ret_type in ['layer_state_t', 'report_mouse_t']: + lines.append(f' return {api.call_params};') else: pass lines.append('}') @@ -86,6 +88,8 @@ def _render_api_implementations(api, module): if api.ret_type == 'bool': lines.append(f' if(!{api.name}_{module_name}_user({api.call_params})) {{ return false; }}') lines.append(' return true;') + elif api.ret_type in ['layer_state_t', 'report_mouse_t']: + lines.append(f' return {api.name}_{module_name}_user({api.call_params});') else: lines.append(f' {api.name}_{module_name}_user({api.call_params});') lines.append('}') @@ -96,6 +100,8 @@ def _render_api_implementations(api, module): if api.ret_type == 'bool': lines.append(f' if(!{api.name}_{module_name}_kb({api.call_params})) {{ return false; }}') lines.append(' return true;') + elif api.ret_type in ['layer_state_t', 'report_mouse_t']: + lines.append(f' return {api.name}_{module_name}_kb({api.call_params});') else: lines.append(f' {api.name}_{module_name}_kb({api.call_params});') lines.append('}') @@ -113,14 +119,81 @@ def _render_core_implementation(api, modules): module_name = Path(module).name if api.ret_type == 'bool': lines.append(f' && {api.name}_{module_name}({api.call_params})') + elif api.ret_type in ['layer_state_t', 'report_mouse_t']: + lines.append(f' {api.call_params} = {api.name}_{module_name}({api.call_params});') else: lines.append(f' {api.name}_{module_name}({api.call_params});') if api.ret_type == 'bool': lines.append(' ;') + elif api.ret_type in ['layer_state_t', 'report_mouse_t']: + lines.append(f' return {api.call_params};') lines.append('}') return lines +def _generate_features_rules(features_dict): + lines = [] + for feature, enabled in features_dict.items(): + feature = feature.upper() + enabled = 'yes' if enabled else 'no' + lines.append(f'{feature}_ENABLE={enabled}') + return lines + + +def _generate_modules_rules(keyboard, filename): + lines = [] + modules = get_modules(keyboard, filename) + if len(modules) > 0: + lines.append('') + lines.append('OPT_DEFS += -DCOMMUNITY_MODULES_ENABLE=TRUE') + for module in modules: + module_path = qmk.path.unix_style_path(find_module_path(module)) + if not module_path: + raise FileNotFoundError(f"Module '{module}' not found.") + lines.append('') + lines.append(f'COMMUNITY_MODULES += {module_path.name}') # use module_path here instead of module as it may be a subdirectory + lines.append(f'OPT_DEFS += -DCOMMUNITY_MODULE_{module_path.name.upper()}_ENABLE=TRUE') + lines.append(f'COMMUNITY_MODULE_PATHS += {module_path}') + lines.append(f'VPATH += {module_path}') + lines.append(f'SRC += $(wildcard {module_path}/{module_path.name}.c)') + lines.append(f'MODULE_NAME_{module_path.name.upper()} := {module_path.name}') + lines.append(f'MODULE_PATH_{module_path.name.upper()} := {module_path}') + lines.append(f'-include {module_path}/rules.mk') + + module_jsons = load_module_jsons(modules) + for module_json in module_jsons: + if 'features' in module_json: + lines.append('') + lines.append(f'# Module: {module_json["module_name"]}') + lines.extend(_generate_features_rules(module_json['features'])) + return lines + + [email protected]('-o', '--output', arg_only=True, type=qmk.path.normpath, help='File to write to') [email protected]('-q', '--quiet', arg_only=True, action='store_true', help="Quiet mode, only output error messages") [email protected]('-e', '--escape', arg_only=True, action='store_true', help="Escape spaces in quiet mode") [email protected]('-kb', '--keyboard', arg_only=True, type=keyboard_folder, completer=keyboard_completer, help='Keyboard to generate rules.mk for.') [email protected]('filename', nargs='?', arg_only=True, type=qmk.path.FileType('r'), completer=FilesCompleter('.json'), help='A configurator export JSON to be compiled and flashed or a pre-compiled binary firmware file (bin/hex) to be flashed.') [email protected]('Creates a community_modules_rules_mk from a keymap.json file.') +def generate_community_modules_rules_mk(cli): + + rules_mk_lines = [GPL2_HEADER_SH_LIKE, GENERATED_HEADER_SH_LIKE] + + rules_mk_lines.extend(_generate_modules_rules(cli.args.keyboard, cli.args.filename)) + + # Show the results + dump_lines(cli.args.output, rules_mk_lines) + + if cli.args.output: + if cli.args.quiet: + if cli.args.escape: + print(cli.args.output.as_posix().replace(' ', '\\ ')) + else: + print(cli.args.output) + else: + cli.log.info('Wrote rules.mk to %s.', cli.args.output) + + @cli.argument('-o', '--output', arg_only=True, type=qmk.path.normpath, help='File to write to') @cli.argument('-q', '--quiet', arg_only=True, action='store_true', help="Quiet mode, only output error messages") @cli.argument('-kb', '--keyboard', arg_only=True, type=keyboard_folder, completer=keyboard_completer, help='Keyboard to generate community_modules.h for.') diff --git a/lib/python/qmk/cli/generate/compilation_database.py b/lib/python/qmk/cli/generate/compilation_database.py index b9c716bf0c..339b53c2c2 100755..100644 --- a/lib/python/qmk/cli/generate/compilation_database.py +++ b/lib/python/qmk/cli/generate/compilation_database.py @@ -1,169 +1,9 @@ -"""Creates a compilation database for the given keyboard build. -""" +from milc import cli -import json -import os -import re -import shlex -import shutil -from functools import lru_cache -from pathlib import Path -from typing import Dict, Iterator, List, Union -from milc import cli, MILC - -from qmk.commands import find_make -from qmk.constants import QMK_FIRMWARE -from qmk.decorators import automagic_keyboard, automagic_keymap -from qmk.keyboard import keyboard_completer, keyboard_folder -from qmk.keymap import keymap_completer -from qmk.build_targets import KeyboardKeymapBuildTarget - - -@lru_cache(maxsize=10) -def system_libs(binary: str) -> List[Path]: - """Find the system include directory that the given build tool uses. - """ - cli.log.debug("searching for system library directory for binary: %s", binary) - - # Actually query xxxxxx-gcc to find its include paths. - if binary.endswith("gcc") or binary.endswith("g++"): - # (TODO): Remove 'stdin' once 'input' no longer causes issues under MSYS - result = cli.run([binary, '-E', '-Wp,-v', '-'], capture_output=True, check=True, stdin=None, input='\n') - paths = [] - for line in result.stderr.splitlines(): - if line.startswith(" "): - paths.append(Path(line.strip()).resolve()) - return paths - - return list(Path(binary).resolve().parent.parent.glob("*/include")) if binary else [] - - -@lru_cache(maxsize=10) -def cpu_defines(binary: str, compiler_args: str) -> List[str]: - cli.log.debug("gathering definitions for compilation: %s %s", binary, compiler_args) - if binary.endswith("gcc") or binary.endswith("g++"): - invocation = [binary, '-dM', '-E'] - if binary.endswith("gcc"): - invocation.extend(['-x', 'c']) - elif binary.endswith("g++"): - invocation.extend(['-x', 'c++']) - compiler_args = shlex.split(compiler_args) - invocation.extend(compiler_args) - invocation.append('-') - result = cli.run(invocation, capture_output=True, check=True, stdin=None, input='\n') - define_args = [] - for line in result.stdout.splitlines(): - line_args = line.split(' ', 2) - if len(line_args) == 3 and line_args[0] == '#define': - define_args.append(f'-D{line_args[1]}={line_args[2]}') - elif len(line_args) == 2 and line_args[0] == '#define': - define_args.append(f'-D{line_args[1]}') - return list(sorted(set(define_args))) - return [] - - -file_re = re.compile(r'printf "Compiling: ([^"]+)') -cmd_re = re.compile(r'LOG=\$\((.+?)&&') - - -def parse_make_n(f: Iterator[str]) -> List[Dict[str, str]]: - """parse the output of `make -n <target>` - - This function makes many assumptions about the format of your build log. - This happens to work right now for qmk. - """ - - state = 'start' - this_file = None - records = [] - for line in f: - if state == 'start': - m = file_re.search(line) - if m: - this_file = m.group(1) - state = 'cmd' - - if state == 'cmd': - assert this_file - m = cmd_re.search(line) - if m: - # we have a hit! - this_cmd = m.group(1) - args = shlex.split(this_cmd) - binary = shutil.which(args[0]) - compiler_args = set(filter(lambda x: x.startswith('-m') or x.startswith('-f'), args)) - for s in system_libs(binary): - args += ['-isystem', '%s' % s] - args.extend(cpu_defines(binary, ' '.join(shlex.quote(s) for s in compiler_args))) - new_cmd = ' '.join(shlex.quote(s) for s in args) - records.append({"directory": str(QMK_FIRMWARE.resolve()), "command": new_cmd, "file": this_file}) - state = 'start' - - return records - - -def write_compilation_database(keyboard: str = None, keymap: str = None, output_path: Path = QMK_FIRMWARE / 'compile_commands.json', skip_clean: bool = False, command: List[str] = None, **env_vars) -> bool: - # Generate the make command for a specific keyboard/keymap. - if not command: - from qmk.build_targets import KeyboardKeymapBuildTarget # Lazy load due to circular references - target = KeyboardKeymapBuildTarget(keyboard, keymap) - command = target.compile_command(dry_run=True, **env_vars) - - if not command: - cli.log.error('You must supply both `--keyboard` and `--keymap`, or be in a directory for a keyboard or keymap.') - cli.echo('usage: qmk generate-compilation-database [-kb KEYBOARD] [-km KEYMAP]') - return False - - # remove any environment variable overrides which could trip us up - env = os.environ.copy() - env.pop("MAKEFLAGS", None) - - # re-use same executable as the main make invocation (might be gmake) - if not skip_clean: - clean_command = [find_make(), "clean"] - cli.log.info('Making clean with {fg_cyan}%s', ' '.join(clean_command)) - cli.run(clean_command, capture_output=False, check=True, env=env) - - cli.log.info('Gathering build instructions from {fg_cyan}%s', ' '.join(command)) - - result = cli.run(command, capture_output=True, check=True, env=env) - db = parse_make_n(result.stdout.splitlines()) - if not db: - cli.log.error("Failed to parse output from make output:\n%s", result.stdout) - return False - - cli.log.info("Found %s compile commands", len(db)) - - cli.log.info(f"Writing build database to {output_path}") - output_path.write_text(json.dumps(db, indent=4)) - - return True - - [email protected]('-kb', '--keyboard', type=keyboard_folder, completer=keyboard_completer, help='The keyboard\'s name') [email protected]('-km', '--keymap', completer=keymap_completer, help='The keymap\'s name') [email protected]('Create a compilation database.') -@automagic_keyboard -@automagic_keymap -def generate_compilation_database(cli: MILC) -> Union[bool, int]: - """Creates a compilation database for the given keyboard build. - - Does a make clean, then a make -n for this target and uses the dry-run output to create - a compilation database (compile_commands.json). This file can help some IDEs and - IDE-like editors work better. For more information about this: - - https://clang.llvm.org/docs/JSONCompilationDatabase.html - """ - # check both config domains: the magic decorator fills in `generate_compilation_database` but the user is - # more likely to have set `compile` in their config file. - current_keyboard = cli.config.generate_compilation_database.keyboard or cli.config.user.keyboard - current_keymap = cli.config.generate_compilation_database.keymap or cli.config.user.keymap - - if not current_keyboard: - cli.log.error('Could not determine keyboard!') - elif not current_keymap: - cli.log.error('Could not determine keymap!') - - target = KeyboardKeymapBuildTarget(current_keyboard, current_keymap) - return target.generate_compilation_database() [email protected]('-kb', '--keyboard', help='[unused] The keyboard\'s name') [email protected]('-km', '--keymap', help='[unused] The keymap\'s name') [email protected]('[deprecated] Create a compilation database.') +def generate_compilation_database(cli): + cli.log.error('This command is deprecated and has effectively been removed. Please use the `--compiledb` flag with `qmk compile` instead.') + return False diff --git a/lib/python/qmk/cli/generate/keycodes.py b/lib/python/qmk/cli/generate/keycodes.py index 719fced5d5..d686935fa8 100644 --- a/lib/python/qmk/cli/generate/keycodes.py +++ b/lib/python/qmk/cli/generate/keycodes.py @@ -125,6 +125,20 @@ def _generate_aliases(lines, keycodes): lines.append(f'#define {alias} {value.get("key")}') +def _generate_version(lines, keycodes, prefix=''): + version = keycodes['version'] + major, minor, patch = map(int, version.split('.')) + + bcd = f'0x{major:02d}{minor:02d}{patch:04d}' + + lines.append('') + lines.append(f'#define QMK_{prefix}KEYCODES_VERSION "{version}"') + lines.append(f'#define QMK_{prefix}KEYCODES_VERSION_BCD {bcd}') + lines.append(f'#define QMK_{prefix}KEYCODES_VERSION_MAJOR {major}') + lines.append(f'#define QMK_{prefix}KEYCODES_VERSION_MINOR {minor}') + lines.append(f'#define QMK_{prefix}KEYCODES_VERSION_PATCH {patch}') + + @cli.argument('-v', '--version', arg_only=True, required=True, help='Version of keycodes to generate.') @cli.argument('-o', '--output', arg_only=True, type=normpath, help='File to write to') @cli.argument('-q', '--quiet', arg_only=True, action='store_true', help="Quiet mode, only output error messages") @@ -138,6 +152,7 @@ def generate_keycodes(cli): keycodes = load_spec(cli.args.version) + _generate_version(keycodes_h_lines, keycodes) _generate_ranges(keycodes_h_lines, keycodes) _generate_defines(keycodes_h_lines, keycodes) _generate_helpers(keycodes_h_lines, keycodes) @@ -160,6 +175,7 @@ def generate_keycode_extras(cli): keycodes = load_spec(cli.args.version, cli.args.lang) + _generate_version(keycodes_h_lines, keycodes, f'{cli.args.lang.upper()}_') _generate_aliases(keycodes_h_lines, keycodes) # Show the results diff --git a/lib/python/qmk/cli/generate/keycodes_tests.py b/lib/python/qmk/cli/generate/keycodes_tests.py deleted file mode 100644 index 453b4693a7..0000000000 --- a/lib/python/qmk/cli/generate/keycodes_tests.py +++ /dev/null @@ -1,39 +0,0 @@ -"""Used by the make system to generate a keycode lookup table from keycodes_{version}.json -""" -from milc import cli - -from qmk.constants import GPL2_HEADER_C_LIKE, GENERATED_HEADER_C_LIKE -from qmk.commands import dump_lines -from qmk.path import normpath -from qmk.keycodes import load_spec - - -def _generate_defines(lines, keycodes): - lines.append('') - lines.append('std::map<uint16_t, std::string> KEYCODE_ID_TABLE = {') - for key, value in keycodes["keycodes"].items(): - lines.append(f' {{{value.get("key")}, "{value.get("key")}"}},') - lines.append('};') - - [email protected]('-v', '--version', arg_only=True, required=True, help='Version of keycodes to generate.') [email protected]('-o', '--output', arg_only=True, type=normpath, help='File to write to') [email protected]('-q', '--quiet', arg_only=True, action='store_true', help="Quiet mode, only output error messages") [email protected]('Used by the make system to generate a keycode lookup table from keycodes_{version}.json', hidden=True) -def generate_keycodes_tests(cli): - """Generates a keycode to identifier lookup table for unit test output. - """ - - # Build the keycodes.h file. - keycodes_h_lines = [GPL2_HEADER_C_LIKE, GENERATED_HEADER_C_LIKE, '// clang-format off'] - keycodes_h_lines.append('extern "C" {\n#include <keycode.h>\n}') - keycodes_h_lines.append('#include <map>') - keycodes_h_lines.append('#include <string>') - keycodes_h_lines.append('#include <cstdint>') - - keycodes = load_spec(cli.args.version) - - _generate_defines(keycodes_h_lines, keycodes) - - # Show the results - dump_lines(cli.args.output, keycodes_h_lines, cli.args.quiet) diff --git a/lib/python/qmk/cli/generate/rules_mk.py b/lib/python/qmk/cli/generate/rules_mk.py index ef4101d77f..358a22fd1d 100755 --- a/lib/python/qmk/cli/generate/rules_mk.py +++ b/lib/python/qmk/cli/generate/rules_mk.py @@ -6,13 +6,12 @@ from dotty_dict import dotty from argcomplete.completers import FilesCompleter from milc import cli -from qmk.info import info_json, get_modules +from qmk.info import info_json from qmk.json_schema import json_load from qmk.keyboard import keyboard_completer, keyboard_folder from qmk.commands import dump_lines, parse_configurator_json -from qmk.path import normpath, unix_style_path, FileType +from qmk.path import normpath, FileType from qmk.constants import GPL2_HEADER_SH_LIKE, GENERATED_HEADER_SH_LIKE -from qmk.community_modules import find_module_path, load_module_jsons def generate_rule(rules_key, rules_value): @@ -56,33 +55,6 @@ def generate_features_rules(features_dict): return lines -def generate_modules_rules(keyboard, filename): - lines = [] - modules = get_modules(keyboard, filename) - if len(modules) > 0: - lines.append('') - lines.append('OPT_DEFS += -DCOMMUNITY_MODULES_ENABLE=TRUE') - for module in modules: - module_path = unix_style_path(find_module_path(module)) - if not module_path: - raise FileNotFoundError(f"Module '{module}' not found.") - lines.append('') - lines.append(f'COMMUNITY_MODULES += {module_path.name}') # use module_path here instead of module as it may be a subdirectory - lines.append(f'OPT_DEFS += -DCOMMUNITY_MODULE_{module_path.name.upper()}_ENABLE=TRUE') - lines.append(f'COMMUNITY_MODULE_PATHS += {module_path}') - lines.append(f'VPATH += {module_path}') - lines.append(f'SRC += $(wildcard {module_path}/{module_path.name}.c)') - lines.append(f'-include {module_path}/rules.mk') - - module_jsons = load_module_jsons(modules) - for module_json in module_jsons: - if 'features' in module_json: - lines.append('') - lines.append(f'# Module: {module_json["module_name"]}') - lines.extend(generate_features_rules(module_json['features'])) - return lines - - @cli.argument('filename', nargs='?', arg_only=True, type=FileType('r'), completer=FilesCompleter('.json'), help='A configurator export JSON to be compiled and flashed or a pre-compiled binary firmware file (bin/hex) to be flashed.') @cli.argument('-o', '--output', arg_only=True, type=normpath, help='File to write to') @cli.argument('-q', '--quiet', arg_only=True, action='store_true', help="Quiet mode, only output error messages") @@ -133,8 +105,6 @@ def generate_rules_mk(cli): if converter: rules_mk_lines.append(generate_rule('CONVERT_TO', converter)) - rules_mk_lines.extend(generate_modules_rules(cli.args.keyboard, cli.args.filename)) - # Show the results dump_lines(cli.args.output, rules_mk_lines) diff --git a/lib/python/qmk/cli/lint.py b/lib/python/qmk/cli/lint.py index bcf905f579..484ddb5bd9 100644 --- a/lib/python/qmk/cli/lint.py +++ b/lib/python/qmk/cli/lint.py @@ -1,5 +1,6 @@ """Command to look over a keyboard/keymap and check for common mistakes. """ +from dotty_dict import dotty from pathlib import Path from milc import cli @@ -11,6 +12,7 @@ from qmk.keymap import locate_keymap, list_keymaps from qmk.path import keyboard from qmk.git import git_get_ignored_files from qmk.c_parse import c_source_files, preprocess_c_file +from qmk.json_schema import json_load CHIBIOS_CONF_CHECKS = ['chconf.h', 'halconf.h', 'mcuconf.h', 'board.h'] INVALID_KB_FEATURES = set(['encoder_map', 'dip_switch_map', 'combo', 'tap_dance', 'via']) @@ -26,7 +28,7 @@ def _list_defaultish_keymaps(kb): defaultish.extend(INVALID_KM_NAMES) keymaps = set() - for x in list_keymaps(kb): + for x in list_keymaps(kb, include_userspace=False): if x in defaultish or x.startswith('default'): keymaps.add(x) @@ -171,6 +173,14 @@ def _handle_invalid_features(kb, info): return ok +def _handle_invalid_config(kb, info): + """Check for invalid keyboard level config + """ + if info.get('url') == "": + cli.log.warning(f'{kb}: Invalid keyboard level config detected - Optional field "url" should not be empty.') + return True + + def _chibios_conf_includenext_check(target): """Check the ChibiOS conf.h for the correct inclusion of the next conf.h """ @@ -206,6 +216,32 @@ def _rules_mk_assignment_only(rules_mk): return errors +def _handle_duplicating_code_defaults(kb, info): + def _collect_dotted_output(kb_info_json, prefix=''): + """Print the info.json in a plain text format with dot-joined keys. + """ + for key in sorted(kb_info_json): + new_prefix = f'{prefix}.{key}' if prefix else key + + if isinstance(kb_info_json[key], dict): + yield from _collect_dotted_output(kb_info_json[key], new_prefix) + elif isinstance(kb_info_json[key], list): + # TODO: handle non primitives? + yield (new_prefix, kb_info_json[key]) + else: + yield (new_prefix, kb_info_json[key]) + + defaults_map = json_load(Path('data/mappings/info_defaults.hjson')) + dotty_info = dotty(info) + + for key, v_default in _collect_dotted_output(defaults_map): + v_info = dotty_info.get(key) + if v_default == v_info: + cli.log.warning(f'{kb}: Option "{key}" duplicates default value of "{v_default}"') + + return True + + def keymap_check(kb, km): """Perform the keymap level checks. """ @@ -255,6 +291,12 @@ def keyboard_check(kb): # noqa C901 if not _handle_invalid_features(kb, kb_info): ok = False + if not _handle_invalid_config(kb, kb_info): + ok = False + + if not _handle_duplicating_code_defaults(kb, kb_info): + ok = False + invalid_files = git_get_ignored_files(f'keyboards/{kb}/') for file in invalid_files: if 'keymap' in file: diff --git a/lib/python/qmk/cli/new/keymap.py b/lib/python/qmk/cli/new/keymap.py index 8b7160f5f2..20a0c0198c 100755 --- a/lib/python/qmk/cli/new/keymap.py +++ b/lib/python/qmk/cli/new/keymap.py @@ -1,10 +1,12 @@ """This script automates the copying of the default keymap into your own keymap. """ import re +import json import shutil +from pathlib import Path from milc import cli -from milc.questions import question +from milc.questions import question, choice from qmk.constants import HAS_QMK_USERSPACE, QMK_USERSPACE from qmk.path import is_keyboard, keymaps, keymap @@ -12,6 +14,34 @@ from qmk.git import git_get_username from qmk.decorators import automagic_keyboard, automagic_keymap from qmk.keyboard import keyboard_completer, keyboard_folder from qmk.userspace import UserspaceDefs +from qmk.json_schema import json_load +from qmk.json_encoders import KeymapJSONEncoder +from qmk.info import info_json + + +def _list_available_converters(kb_name): + """Search for converters that can be applied to a given keyboard + """ + if not is_keyboard(kb_name): + return None + + info = info_json(kb_name) + pin_compatible = info.get('pin_compatible') + if not pin_compatible: + return None + + return sorted(folder.name.split('_to_')[-1] for folder in Path('platforms').glob(f'*/converters/{pin_compatible}_to_*')) + + +def _set_converter(file, converter): + """add/overwrite any existing converter specified in keymap.json + """ + json_data = json_load(file) if file.exists() else {} + + json_data['converter'] = converter + + output = json.dumps(json_data, cls=KeymapJSONEncoder, sort_keys=True) + file.write_text(output + '\n', encoding='utf-8') def validate_keymap_name(name): @@ -37,8 +67,28 @@ Keymap name? """ return question(prompt, default=git_get_username()) +def prompt_converter(kb_name): + prompt = """ +{fg_yellow}Configure Development Board{style_reset_all} +For more information, see: +https://docs.qmk.fm/feature_converters + +Use converter? """ + + converters = _list_available_converters(kb_name) + if not converters: + return None + + choices = ['No (default)', *converters] + + answer = choice(prompt, options=choices, default=0) + return None if choices.index(answer) == 0 else answer + + @cli.argument('-kb', '--keyboard', type=keyboard_folder, completer=keyboard_completer, help='Specify keyboard name. Example: 1upkeyboards/1up60hse') @cli.argument('-km', '--keymap', help='Specify the name for the new keymap directory') [email protected]('--converter', help='Specify the name of a converter to configure') [email protected]('--skip-converter', arg_only=True, action='store_true', help='Skip converter') @cli.subcommand('Creates a new keymap for the keyboard of your choosing') @automagic_keyboard @automagic_keymap @@ -51,9 +101,12 @@ def new_keymap(cli): # ask for user input if keyboard or keymap was not provided in the command line kb_name = cli.config.new_keymap.keyboard if cli.config.new_keymap.keyboard else prompt_keyboard() user_name = cli.config.new_keymap.keymap if cli.config.new_keymap.keymap else prompt_user() + converter = cli.config.new_keymap.converter if cli.args.skip_converter or cli.config.new_keymap.converter else prompt_converter(kb_name) # check directories - if not is_keyboard(kb_name): + try: + kb_name = keyboard_folder(kb_name) + except ValueError: cli.log.error(f'Keyboard {{fg_cyan}}{kb_name}{{fg_reset}} does not exist! Please choose a valid name.') return False @@ -77,6 +130,9 @@ def new_keymap(cli): # create user directory with default keymap files shutil.copytree(keymap_path_default, keymap_path_new, symlinks=True) + if converter: + _set_converter(keymap_path_new / 'keymap.json', converter) + # end message to user cli.log.info(f'{{fg_green}}Created a new keymap called {{fg_cyan}}{user_name}{{fg_green}} in: {{fg_cyan}}{keymap_path_new}.{{fg_reset}}') cli.log.info(f"Compile a firmware with your new keymap by typing: {{fg_yellow}}qmk compile -kb {kb_name} -km {user_name}{{fg_reset}}.") |