diff options
Diffstat (limited to 'lib/python')
-rw-r--r-- | lib/python/qmk/cli/__init__.py | 2 | ||||
-rwxr-xr-x | lib/python/qmk/cli/generate/api.py | 9 | ||||
-rw-r--r-- | lib/python/qmk/cli/generate/keycodes.py | 54 | ||||
-rw-r--r-- | lib/python/qmk/cli/generate/keycodes_tests.py | 39 | ||||
-rw-r--r-- | lib/python/qmk/cli/migrate.py | 81 | ||||
-rwxr-xr-x | lib/python/qmk/cli/new/keymap.py | 56 | ||||
-rw-r--r-- | lib/python/qmk/keycodes.py | 73 | ||||
-rw-r--r-- | lib/python/qmk/submodules.py | 2 |
8 files changed, 279 insertions, 37 deletions
diff --git a/lib/python/qmk/cli/__init__.py b/lib/python/qmk/cli/__init__.py index 9190af4e50..a6d53c1cb6 100644 --- a/lib/python/qmk/cli/__init__.py +++ b/lib/python/qmk/cli/__init__.py @@ -57,6 +57,7 @@ subcommands = [ 'qmk.cli.generate.keyboard_c', 'qmk.cli.generate.keyboard_h', 'qmk.cli.generate.keycodes', + 'qmk.cli.generate.keycodes_tests', 'qmk.cli.generate.rgb_breathe_table', 'qmk.cli.generate.rules_mk', 'qmk.cli.generate.version_h', @@ -71,6 +72,7 @@ subcommands = [ 'qmk.cli.list.keymaps', 'qmk.cli.list.layouts', 'qmk.cli.kle2json', + 'qmk.cli.migrate', 'qmk.cli.multibuild', 'qmk.cli.new.keyboard', 'qmk.cli.new.keymap', diff --git a/lib/python/qmk/cli/generate/api.py b/lib/python/qmk/cli/generate/api.py index 8650a36b84..dd4830f543 100755 --- a/lib/python/qmk/cli/generate/api.py +++ b/lib/python/qmk/cli/generate/api.py @@ -11,7 +11,7 @@ from qmk.info import info_json from qmk.json_encoders import InfoJSONEncoder from qmk.json_schema import json_load from qmk.keyboard import find_readme, list_keyboards -from qmk.keycodes import load_spec, list_versions +from qmk.keycodes import load_spec, list_versions, list_languages DATA_PATH = Path('data') TEMPLATE_PATH = DATA_PATH / 'templates/api/' @@ -44,6 +44,13 @@ def _resolve_keycode_specs(output_folder): output_file = output_folder / f'constants/keycodes_{version}.json' output_file.write_text(json.dumps(overall, indent=4), encoding='utf-8') + for lang in list_languages(): + for version in list_versions(lang): + overall = load_spec(version, lang) + + output_file = output_folder / f'constants/keycodes_{lang}_{version}.json' + output_file.write_text(json.dumps(overall, indent=4), encoding='utf-8') + # Purge files consumed by 'load_spec' shutil.rmtree(output_folder / 'constants/keycodes/') diff --git a/lib/python/qmk/cli/generate/keycodes.py b/lib/python/qmk/cli/generate/keycodes.py index 29b7db3c80..cf80689708 100644 --- a/lib/python/qmk/cli/generate/keycodes.py +++ b/lib/python/qmk/cli/generate/keycodes.py @@ -8,6 +8,24 @@ from qmk.path import normpath from qmk.keycodes import load_spec +def _render_key(key): + width = 7 + if 'S(' in key: + width += len('S()') + if 'A(' in key: + width += len('A()') + if 'RCTL(' in key: + width += len('RCTL()') + if 'ALGR(' in key: + width += len('ALGR()') + return key.ljust(width) + + +def _render_label(label): + label = label.replace("\\", "(backslash)") + return label + + def _generate_ranges(lines, keycodes): lines.append('') lines.append('enum qk_keycode_ranges {') @@ -67,6 +85,22 @@ def _generate_helpers(lines, keycodes): lines.append(f'#define IS_{ group.upper() }_KEYCODE(code) ((code) >= {lo} && (code) <= {hi})') +def _generate_aliases(lines, keycodes): + lines.append('') + lines.append('// Aliases') + for key, value in keycodes["aliases"].items(): + define = _render_key(value.get("key")) + val = _render_key(key) + label = _render_label(value.get("label")) + + lines.append(f'#define {define} {val} // {label}') + + lines.append('') + for key, value in keycodes["aliases"].items(): + for alias in value.get("aliases", []): + lines.append(f'#define {alias} {value.get("key")}') + + @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") @@ -86,3 +120,23 @@ def generate_keycodes(cli): # Show the results dump_lines(cli.args.output, keycodes_h_lines, cli.args.quiet) + + +@cli.argument('-v', '--version', arg_only=True, required=True, help='Version of keycodes to generate.') +@cli.argument('-l', '--lang', arg_only=True, required=True, help='Language 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") +@cli.subcommand('Used by the make system to generate keymap_{lang}.h from keycodes_{lang}_{version}.json', hidden=True) +def generate_keycode_extras(cli): + """Generates the header file. + """ + + # Build the header file. + keycodes_h_lines = [GPL2_HEADER_C_LIKE, GENERATED_HEADER_C_LIKE, '#pragma once', '#include "keymap.h"', '// clang-format off'] + + keycodes = load_spec(cli.args.version, cli.args.lang) + + _generate_aliases(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/keycodes_tests.py b/lib/python/qmk/cli/generate/keycodes_tests.py new file mode 100644 index 0000000000..453b4693a7 --- /dev/null +++ b/lib/python/qmk/cli/generate/keycodes_tests.py @@ -0,0 +1,39 @@ +"""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('};') + + +@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") +@cli.subcommand('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/migrate.py b/lib/python/qmk/cli/migrate.py new file mode 100644 index 0000000000..4164f9c8ad --- /dev/null +++ b/lib/python/qmk/cli/migrate.py @@ -0,0 +1,81 @@ +"""Migrate keyboard configuration to "Data Driven" +""" +import json +from pathlib import Path +from dotty_dict import dotty + +from milc import cli + +from qmk.keyboard import keyboard_completer, keyboard_folder, resolve_keyboard +from qmk.info import info_json, find_info_json +from qmk.json_encoders import InfoJSONEncoder +from qmk.json_schema import json_load + + +def _candidate_files(keyboard): + kb_dir = Path(resolve_keyboard(keyboard)) + + cur_dir = Path('keyboards') + files = [] + for dir in kb_dir.parts: + cur_dir = cur_dir / dir + files.append(cur_dir / 'config.h') + files.append(cur_dir / 'rules.mk') + + return [file for file in files if file.exists()] + + +@cli.argument('-f', '--filter', arg_only=True, action='append', default=[], help="Filter the performed migrations based on the supplied value. Supported format is 'KEY' located from 'data/mappings'. May be passed multiple times.") +@cli.argument('-kb', '--keyboard', arg_only=True, type=keyboard_folder, completer=keyboard_completer, required=True, help='The keyboard\'s name') +@cli.subcommand('Migrate keyboard config to "Data Driven".', hidden=True) +def migrate(cli): + """Migrate keyboard configuration to "Data Driven" + """ + # Merge mappings as we do not care to where "KEY" is found just that its removed + info_config_map = json_load(Path('data/mappings/info_config.hjson')) + info_rules_map = json_load(Path('data/mappings/info_rules.hjson')) + info_map = {**info_config_map, **info_rules_map} + + # Parse target info.json which will receive updates + target_info = Path(find_info_json(cli.args.keyboard)[0]) + info_data = dotty(json_load(target_info)) + + # Already parsed used for updates + kb_info_json = dotty(info_json(cli.args.keyboard)) + + # List of candidate files + files = _candidate_files(cli.args.keyboard) + + # Filter down keys if requested + keys = info_map.keys() + if cli.args.filter: + keys = list(set(keys) & set(cli.args.filter)) + + cli.log.info(f'{{fg_green}}Migrating keyboard {{fg_cyan}}{cli.args.keyboard}{{fg_green}}.{{fg_reset}}') + + # Start migration + for file in files: + cli.log.info(f' Migrating file {file}') + file_contents = file.read_text(encoding='utf-8').split('\n') + for key in keys: + for num, line in enumerate(file_contents): + if line.startswith(f'{key} =') or line.startswith(f'#define {key} '): + cli.log.info(f' Migrating {key}...') + + while line.rstrip().endswith('\\'): + file_contents.pop(num) + line = file_contents[num] + file_contents.pop(num) + + update_key = info_map[key]["info_key"] + if update_key in kb_info_json: + info_data[update_key] = kb_info_json[update_key] + + file.write_text('\n'.join(file_contents), encoding='utf-8') + + # Finally write out updated info.json + cli.log.info(f' Updating {target_info}') + target_info.write_text(json.dumps(info_data.to_dict(), cls=InfoJSONEncoder)) + + cli.log.info(f'{{fg_green}}Migration of keyboard {{fg_cyan}}{cli.args.keyboard}{{fg_green}} complete!{{fg_reset}}') + cli.log.info(f"Verify build with {{fg_yellow}}qmk compile -kb {cli.args.keyboard} -km default{{fg_reset}}.") diff --git a/lib/python/qmk/cli/new/keymap.py b/lib/python/qmk/cli/new/keymap.py index 60cb743cb6..e7823bc46d 100755 --- a/lib/python/qmk/cli/new/keymap.py +++ b/lib/python/qmk/cli/new/keymap.py @@ -1,12 +1,32 @@ """This script automates the copying of the default keymap into your own keymap. """ import shutil -from pathlib import Path -import qmk.path +from milc import cli +from milc.questions import question + +from qmk.path import is_keyboard, keymap +from qmk.git import git_get_username from qmk.decorators import automagic_keyboard, automagic_keymap from qmk.keyboard import keyboard_completer, keyboard_folder -from milc import cli + + +def prompt_keyboard(): + prompt = """{fg_yellow}Select Keyboard{style_reset_all} +If you`re unsure you can view a full list of supported keyboards with {fg_yellow}qmk list-keyboards{style_reset_all}. + +Keyboard Name? """ + + return question(prompt) + + +def prompt_user(): + prompt = """ +{fg_yellow}Name Your Keymap{style_reset_all} +Used for maintainer, copyright, etc + +Your GitHub Username? """ + return question(prompt, default=git_get_username()) @cli.argument('-kb', '--keyboard', type=keyboard_folder, completer=keyboard_completer, help='Specify keyboard name. Example: 1upkeyboards/1up60hse') @@ -17,32 +37,34 @@ from milc import cli def new_keymap(cli): """Creates a new keymap for the keyboard of your choosing. """ - # ask for user input if keyboard or keymap was not provided in the command line - keyboard = cli.config.new_keymap.keyboard if cli.config.new_keymap.keyboard else input("Keyboard Name: ") - keymap = cli.config.new_keymap.keymap if cli.config.new_keymap.keymap else input("Keymap Name: ") + cli.log.info('{style_bright}Generating a new keymap{style_normal}') + cli.echo('') - # generate keymap paths - kb_path = Path('keyboards') / keyboard - keymap_path = qmk.path.keymap(keyboard) - keymap_path_default = keymap_path / 'default' - keymap_path_new = keymap_path / keymap + # 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() # check directories - if not kb_path.exists(): - cli.log.error('Keyboard %s does not exist!', kb_path) + if not is_keyboard(kb_name): + cli.log.error(f'Keyboard {{fg_cyan}}{kb_name}{{fg_reset}} does not exist! Please choose a valid name.') return False + # generate keymap paths + km_path = keymap(kb_name) + keymap_path_default = km_path / 'default' + keymap_path_new = km_path / user_name + if not keymap_path_default.exists(): - cli.log.error('Keyboard default %s does not exist!', keymap_path_default) + cli.log.error(f'Default keymap {{fg_cyan}}{keymap_path_default}{{fg_reset}} does not exist!') return False if keymap_path_new.exists(): - cli.log.error('Keymap %s already exists!', keymap_path_new) + cli.log.error(f'Keymap {{fg_cyan}}{user_name}{{fg_reset}} already exists! Please choose a different name.') return False # create user directory with default keymap files shutil.copytree(keymap_path_default, keymap_path_new, symlinks=True) # end message to user - cli.log.info("%s keymap directory created in: %s", keymap, keymap_path_new) - cli.log.info("Compile a firmware with your new keymap by typing: \n\n\tqmk compile -kb %s -km %s\n", keyboard, keymap) + 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}}.") diff --git a/lib/python/qmk/keycodes.py b/lib/python/qmk/keycodes.py index cf1ee0767a..600163bab9 100644 --- a/lib/python/qmk/keycodes.py +++ b/lib/python/qmk/keycodes.py @@ -2,7 +2,42 @@ from pathlib import Path from qmk.json_schema import deep_update, json_load, validate -CONSTANTS_PATH = Path('data/constants/keycodes/') +CONSTANTS_PATH = Path('data/constants/') +KEYCODES_PATH = CONSTANTS_PATH / 'keycodes' +EXTRAS_PATH = KEYCODES_PATH / 'extras' + + +def _find_versions(path, prefix): + ret = [] + for file in path.glob(f'{prefix}_[0-9].[0-9].[0-9].hjson'): + ret.append(file.stem.split('_')[-1]) + + ret.sort(reverse=True) + return ret + + +def _load_fragments(path, prefix, version): + file = path / f'{prefix}_{version}.hjson' + if not file.exists(): + raise ValueError(f'Requested keycode spec ({prefix}:{version}) is invalid!') + + # Load base + spec = json_load(file) + + # Merge in fragments + fragments = path.glob(f'{prefix}_{version}_*.hjson') + for file in fragments: + deep_update(spec, json_load(file)) + + return spec + + +def _search_path(lang=None): + return EXTRAS_PATH if lang else KEYCODES_PATH + + +def _search_prefix(lang=None): + return f'keycodes_{lang}' if lang else 'keycodes' def _validate(spec): @@ -19,26 +54,20 @@ def _validate(spec): raise ValueError(f'Keycode spec contains duplicate keycodes! ({",".join(duplicates)})') -def load_spec(version): +def load_spec(version, lang=None): """Build keycode data from the requested spec file """ if version == 'latest': - version = list_versions()[0] + version = list_versions(lang)[0] - file = CONSTANTS_PATH / f'keycodes_{version}.hjson' - if not file.exists(): - raise ValueError(f'Requested keycode spec ({version}) is invalid!') + path = _search_path(lang) + prefix = _search_prefix(lang) # Load base - spec = json_load(file) - - # Merge in fragments - fragments = CONSTANTS_PATH.glob(f'keycodes_{version}_*.hjson') - for file in fragments: - deep_update(spec, json_load(file)) + spec = _load_fragments(path, prefix, version) # Sort? - spec['keycodes'] = dict(sorted(spec['keycodes'].items())) + spec['keycodes'] = dict(sorted(spec.get('keycodes', {}).items())) # Validate? _validate(spec) @@ -46,12 +75,20 @@ def load_spec(version): return spec -def list_versions(): +def list_versions(lang=None): """Return available versions - sorted newest first """ - ret = [] - for file in CONSTANTS_PATH.glob('keycodes_[0-9].[0-9].[0-9].hjson'): - ret.append(file.stem.split('_')[1]) + path = _search_path(lang) + prefix = _search_prefix(lang) + + return _find_versions(path, prefix) + + +def list_languages(): + """Return available languages + """ + ret = set() + for file in EXTRAS_PATH.glob('keycodes_*_[0-9].[0-9].[0-9].hjson'): + ret.add(file.stem.split('_')[1]) - ret.sort(reverse=True) return ret diff --git a/lib/python/qmk/submodules.py b/lib/python/qmk/submodules.py index 52efa602a0..d37ca5e644 100644 --- a/lib/python/qmk/submodules.py +++ b/lib/python/qmk/submodules.py @@ -40,7 +40,7 @@ def status(): else: raise ValueError('Unknown `git submodule status` sha-1 prefix character: "%s"' % status) - submodule_logs = cli.run(['git', 'submodule', '-q', 'foreach', 'git --no-pager log --pretty=format:"$sm_path%x01%h%x01%ad%x01%s%x0A" --date=iso -n1']) + submodule_logs = cli.run(['git', 'submodule', '-q', 'foreach', 'git --no-pager log --no-show-signature --pretty=format:"$sm_path%x01%h%x01%ad%x01%s%x0A" --date=iso -n1']) for log_line in submodule_logs.stdout.split('\n'): if not log_line: continue |