From 7b018f097db2d219908f74eb7a406ae5f7f93f46 Mon Sep 17 00:00:00 2001 From: Nick Brassel Date: Wed, 22 Dec 2021 05:44:47 +1100 Subject: Use the PR title rather than parsing the commit message. (#15537) --- lib/python/qmk/cli/generate/develop_pr_list.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'lib/python/qmk/cli') diff --git a/lib/python/qmk/cli/generate/develop_pr_list.py b/lib/python/qmk/cli/generate/develop_pr_list.py index 07e46752a6..fab0262773 100755 --- a/lib/python/qmk/cli/generate/develop_pr_list.py +++ b/lib/python/qmk/cli/generate/develop_pr_list.py @@ -97,7 +97,7 @@ def generate_develop_pr_list(cli): match = git_expr.search(line) if match: pr_info = _get_pr_info(cache, gh, match.group("pr")) - commit_info = {'hash': match.group("hash"), 'title': match.group("title"), 'pr_num': int(match.group("pr")), 'pr_labels': [label.name for label in pr_info.labels.items]} + commit_info = {'hash': match.group("hash"), 'title': pr_info['title'], 'pr_num': int(match.group("pr")), 'pr_labels': [label.name for label in pr_info.labels.items]} _categorise_commit(commit_info) def _dump_commit_list(name, collection): -- cgit v1.2.3 From 1a8a842cfb3e87a82afb57ba29ca59c5fa6fe97b Mon Sep 17 00:00:00 2001 From: Joel Challis Date: Wed, 29 Dec 2021 21:35:35 +0000 Subject: Fix compilation-database command under MSYS (#15652) * Fix compilation-database command under MSYS * Add comment --- lib/python/qmk/cli/generate/compilation_database.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'lib/python/qmk/cli') diff --git a/lib/python/qmk/cli/generate/compilation_database.py b/lib/python/qmk/cli/generate/compilation_database.py index 602635270c..7ac0f740fe 100755 --- a/lib/python/qmk/cli/generate/compilation_database.py +++ b/lib/python/qmk/cli/generate/compilation_database.py @@ -26,7 +26,8 @@ def system_libs(binary: str) -> List[Path]: # Actually query xxxxxx-gcc to find its include paths. if binary.endswith("gcc") or binary.endswith("g++"): - result = cli.run([binary, '-E', '-Wp,-v', '-'], capture_output=True, check=True, input='\n') + # (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(" "): -- cgit v1.2.3 From aea71554234cce71f9f9d4087253e53d2729f9a7 Mon Sep 17 00:00:00 2001 From: Joel Challis Date: Mon, 3 Jan 2022 21:54:46 +0000 Subject: Fix "No C files in filelist: None" (#15560) * Fix "No C files in filelist: None" * Align other commands * force absolute paths --- lib/python/qmk/cli/format/c.py | 22 ++++++++++++---------- lib/python/qmk/cli/format/python.py | 3 ++- 2 files changed, 14 insertions(+), 11 deletions(-) (limited to 'lib/python/qmk/cli') diff --git a/lib/python/qmk/cli/format/c.py b/lib/python/qmk/cli/format/c.py index 568684ed56..fe2f97da94 100644 --- a/lib/python/qmk/cli/format/c.py +++ b/lib/python/qmk/cli/format/c.py @@ -1,6 +1,5 @@ """Format C code according to QMK's style. """ -from os import path from shutil import which from subprocess import CalledProcessError, DEVNULL, Popen, PIPE @@ -15,6 +14,12 @@ core_dirs = ('drivers', 'quantum', 'tests', 'tmk_core', 'platforms') ignored = ('tmk_core/protocol/usb_hid', 'platforms/chibios/boards') +def is_relative_to(file, other): + """Provide similar behavior to PurePath.is_relative_to in Python > 3.9 + """ + return str(normpath(file).resolve()).startswith(str(normpath(other).resolve())) + + def find_clang_format(): """Returns the path to clang-format. """ @@ -68,18 +73,19 @@ def cformat_run(files): def filter_files(files, core_only=False): """Yield only files to be formatted and skip the rest """ + files = list(map(normpath, filter(None, files))) if core_only: # Filter non-core files for index, file in enumerate(files): # The following statement checks each file to see if the file path is # - in the core directories # - not in the ignored directories - if not any(str(file).startswith(i) for i in core_dirs) or any(str(file).startswith(i) for i in ignored): - files[index] = None + if not any(is_relative_to(file, i) for i in core_dirs) or any(is_relative_to(file, i) for i in ignored): + del files[index] cli.log.debug("Skipping non-core file %s, as '--core-only' is used.", file) for file in files: - if file and file.name.split('.')[-1] in c_file_suffixes: + if file.suffix[1:] in c_file_suffixes: yield file else: cli.log.debug('Skipping file %s', file) @@ -118,12 +124,8 @@ def format_c(cli): print(git_diff.stderr) return git_diff.returncode - files = [] - - for file in git_diff.stdout.strip().split('\n'): - if not any([file.startswith(ignore) for ignore in ignored]): - if path.exists(file) and file.split('.')[-1] in c_file_suffixes: - files.append(file) + changed_files = git_diff.stdout.strip().split('\n') + files = list(filter_files(changed_files, True)) # Sanity check if not files: diff --git a/lib/python/qmk/cli/format/python.py b/lib/python/qmk/cli/format/python.py index 47b5c45fd5..008622cac1 100755 --- a/lib/python/qmk/cli/format/python.py +++ b/lib/python/qmk/cli/format/python.py @@ -25,8 +25,9 @@ def yapf_run(files): def filter_files(files): """Yield only files to be formatted and skip the rest """ + files = list(map(normpath, filter(None, files))) for file in files: - if file and normpath(file).name.split('.')[-1] in py_file_suffixes: + if file.suffix[1:] in py_file_suffixes: yield file else: cli.log.debug('Skipping file %s', file) -- cgit v1.2.3 From 550c9a315f146bc30fc828726d977f3f41477d52 Mon Sep 17 00:00:00 2001 From: Joel Challis Date: Mon, 3 Jan 2022 22:42:15 +0000 Subject: Refix "No C files in filelist: None" (#15728) --- lib/python/qmk/cli/format/c.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) (limited to 'lib/python/qmk/cli') diff --git a/lib/python/qmk/cli/format/c.py b/lib/python/qmk/cli/format/c.py index fe2f97da94..8eb7fa1ed0 100644 --- a/lib/python/qmk/cli/format/c.py +++ b/lib/python/qmk/cli/format/c.py @@ -74,17 +74,16 @@ def filter_files(files, core_only=False): """Yield only files to be formatted and skip the rest """ files = list(map(normpath, filter(None, files))) - if core_only: - # Filter non-core files - for index, file in enumerate(files): + + for file in files: + if core_only: # The following statement checks each file to see if the file path is # - in the core directories # - not in the ignored directories if not any(is_relative_to(file, i) for i in core_dirs) or any(is_relative_to(file, i) for i in ignored): - del files[index] cli.log.debug("Skipping non-core file %s, as '--core-only' is used.", file) + continue - for file in files: if file.suffix[1:] in c_file_suffixes: yield file else: -- cgit v1.2.3 From c72ed7c02473dec4da6cb263c1e0fb2ca4856b94 Mon Sep 17 00:00:00 2001 From: Ryan Date: Mon, 17 Jan 2022 08:44:34 +1100 Subject: CLI: Parse USB device version BCD (#14580) * CLI: Parse USB device version BCD * Apply suggestions --- lib/python/qmk/cli/generate/config_h.py | 6 ++++++ 1 file changed, 6 insertions(+) (limited to 'lib/python/qmk/cli') diff --git a/lib/python/qmk/cli/generate/config_h.py b/lib/python/qmk/cli/generate/config_h.py index f16dca1de8..6b1012fae7 100755 --- a/lib/python/qmk/cli/generate/config_h.py +++ b/lib/python/qmk/cli/generate/config_h.py @@ -108,6 +108,12 @@ 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 == 'bcd_version': + (major, minor, revision) = config_value.split('.') + config_h_lines.append('') + config_h_lines.append(f'#ifndef {config_key}') + config_h_lines.append(f'# define {config_key} 0x{major.zfill(2)}{minor}{revision}') + config_h_lines.append(f'#endif // {config_key}') else: config_h_lines.append('') config_h_lines.append(f'#ifndef {config_key}') -- cgit v1.2.3 From 6e2b03cf6901a6bbd146c074e8e9d9160358c6d9 Mon Sep 17 00:00:00 2001 From: Nick Brassel Date: Wed, 2 Feb 2022 15:30:22 +1100 Subject: Fixup multibuild filegen (#16166) * Add env variable support to multibuild. * Generate version.h in build-specific location. --- lib/python/qmk/cli/multibuild.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'lib/python/qmk/cli') diff --git a/lib/python/qmk/cli/multibuild.py b/lib/python/qmk/cli/multibuild.py index 85ed0fa1e9..ad059edff3 100755 --- a/lib/python/qmk/cli/multibuild.py +++ b/lib/python/qmk/cli/multibuild.py @@ -32,6 +32,7 @@ def _is_split(keyboard_name): @cli.argument('-c', '--clean', arg_only=True, action='store_true', help="Remove object files before compiling.") @cli.argument('-f', '--filter', arg_only=True, action='append', default=[], help="Filter the list of keyboards based on the supplied value in rules.mk. Supported format is 'SPLIT_KEYBOARD=yes'. May be passed multiple times.") @cli.argument('-km', '--keymap', type=str, default='default', help="The keymap name to build. Default is 'default'.") +@cli.argument('-e', '--env', arg_only=True, action='append', default=[], help="Set a variable to be passed to make. May be passed multiple times.") @cli.subcommand('Compile QMK Firmware for all keyboards.', hidden=False if cli.config.user.developer else True) def multibuild(cli): """Compile QMK Firmware against all keyboards. @@ -68,7 +69,7 @@ def multibuild(cli): all: {keyboard_safe}_binary {keyboard_safe}_binary: @rm -f "{QMK_FIRMWARE}/.build/failed.log.{keyboard_safe}" || true - +@$(MAKE) -C "{QMK_FIRMWARE}" -f "{QMK_FIRMWARE}/build_keyboard.mk" KEYBOARD="{keyboard_name}" KEYMAP="{cli.args.keymap}" REQUIRE_PLATFORM_KEY= COLOR=true SILENT=false \\ + +@$(MAKE) -C "{QMK_FIRMWARE}" -f "{QMK_FIRMWARE}/build_keyboard.mk" KEYBOARD="{keyboard_name}" KEYMAP="{cli.args.keymap}" REQUIRE_PLATFORM_KEY= COLOR=true SILENT=false {' '.join(cli.args.env)} \\ >>"{QMK_FIRMWARE}/.build/build.log.{os.getpid()}.{keyboard_safe}" 2>&1 \\ || cp "{QMK_FIRMWARE}/.build/build.log.{os.getpid()}.{keyboard_safe}" "{QMK_FIRMWARE}/.build/failed.log.{os.getpid()}.{keyboard_safe}" @{{ grep '\[ERRORS\]' "{QMK_FIRMWARE}/.build/build.log.{os.getpid()}.{keyboard_safe}" >/dev/null 2>&1 && printf "Build %-64s \e[1;31m[ERRORS]\e[0m\\n" "{keyboard_name}:{cli.args.keymap}" ; }} \\ -- cgit v1.2.3 From c9f88d7c67e00b3689fd4afd7630bc7fcd5b7ed4 Mon Sep 17 00:00:00 2001 From: Ryan Date: Wed, 2 Feb 2022 15:31:42 +1100 Subject: `qmk doctor`: display qmk_firmware version tag (#16155) --- lib/python/qmk/cli/doctor/main.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) (limited to 'lib/python/qmk/cli') diff --git a/lib/python/qmk/cli/doctor/main.py b/lib/python/qmk/cli/doctor/main.py index ed20f46d3f..2e5e221e8f 100755 --- a/lib/python/qmk/cli/doctor/main.py +++ b/lib/python/qmk/cli/doctor/main.py @@ -11,7 +11,7 @@ from milc.questions import yesno from qmk import submodules from qmk.constants import QMK_FIRMWARE, QMK_FIRMWARE_UPSTREAM from .check import CheckStatus, check_binaries, check_binary_versions, check_submodules -from qmk.commands import git_check_repo, git_get_branch, git_is_dirty, git_get_remotes, git_check_deviation, in_virtualenv +from qmk.commands import git_check_repo, git_get_branch, git_get_tag, git_is_dirty, git_get_remotes, git_check_deviation, in_virtualenv def os_tests(): @@ -47,6 +47,11 @@ def git_tests(): git_branch = git_get_branch() if git_branch: cli.log.info('Git branch: %s', git_branch) + + repo_version = git_get_tag() + if repo_version: + cli.log.info('Repo version: %s', repo_version) + git_dirty = git_is_dirty() if git_dirty: cli.log.warning('{fg_yellow}Git has unstashed/uncommitted changes.') -- cgit v1.2.3 From db43e450771f4eefe27821a88cc86df46c1006c1 Mon Sep 17 00:00:00 2001 From: Nick Brassel Date: Fri, 4 Feb 2022 07:36:02 +1100 Subject: Ensure `version.h` is recreated each build. (#16188) --- lib/python/qmk/cli/generate/version_h.py | 3 +++ 1 file changed, 3 insertions(+) (limited to 'lib/python/qmk/cli') diff --git a/lib/python/qmk/cli/generate/version_h.py b/lib/python/qmk/cli/generate/version_h.py index b8e52588c4..69341e36f0 100644 --- a/lib/python/qmk/cli/generate/version_h.py +++ b/lib/python/qmk/cli/generate/version_h.py @@ -20,6 +20,9 @@ def generate_version_h(cli): version_h = create_version_h(cli.args.skip_git, cli.args.skip_all) if cli.args.output: + 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(version_h) if not cli.args.quiet: -- cgit v1.2.3 From 8fd8b2dc92a01b86a8e9ff3c37baeba760312140 Mon Sep 17 00:00:00 2001 From: Nick Brassel Date: Sat, 5 Feb 2022 07:36:57 +1100 Subject: Skip categorisation of PR if it's only for code formatting. (#16215) --- lib/python/qmk/cli/generate/develop_pr_list.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) (limited to 'lib/python/qmk/cli') diff --git a/lib/python/qmk/cli/generate/develop_pr_list.py b/lib/python/qmk/cli/generate/develop_pr_list.py index fab0262773..09236a7c42 100755 --- a/lib/python/qmk/cli/generate/develop_pr_list.py +++ b/lib/python/qmk/cli/generate/develop_pr_list.py @@ -12,6 +12,14 @@ fix_expr = re.compile(r'fix', flags=re.IGNORECASE) clean1_expr = re.compile(r'\[(develop|keyboard|keymap|core|cli|bug|docs|feature)\]', flags=re.IGNORECASE) clean2_expr = re.compile(r'^(develop|keyboard|keymap|core|cli|bug|docs|feature):', flags=re.IGNORECASE) +ignored_titles = ["Format code according to conventions"] + + +def _is_ignored(title): + for ignore in ignored_titles: + if ignore in title: + return + def _get_pr_info(cache, gh, pr_num): pull = cache.get(f'pull:{pr_num}') @@ -81,7 +89,9 @@ def generate_develop_pr_list(cli): else: normal_collection.append(info) - if "dependencies" in commit_info['pr_labels']: + if _is_ignored(commit_info['title']): + return + elif "dependencies" in commit_info['pr_labels']: fix_or_normal(commit_info, pr_list_bugs, pr_list_dependencies) elif "core" in commit_info['pr_labels']: fix_or_normal(commit_info, pr_list_bugs, pr_list_core) -- cgit v1.2.3 From 2e279f1b889a59156f30524cc83358f64ee53287 Mon Sep 17 00:00:00 2001 From: Joel Challis Date: Tue, 8 Feb 2022 19:03:30 +0000 Subject: Initial pass at data driven new-keyboard subcommand (#12795) * Initial pass at a data driven keyboard subcommand * format * lint * Handle bootloader now its mandatory --- lib/python/qmk/cli/new/keyboard.py | 293 ++++++++++++++++++++++++++----------- 1 file changed, 205 insertions(+), 88 deletions(-) (limited to 'lib/python/qmk/cli') diff --git a/lib/python/qmk/cli/new/keyboard.py b/lib/python/qmk/cli/new/keyboard.py index 4093b8c90d..59e781a932 100644 --- a/lib/python/qmk/cli/new/keyboard.py +++ b/lib/python/qmk/cli/new/keyboard.py @@ -1,15 +1,82 @@ """This script automates the creation of new keyboard directories using a starter template. """ +import re +import json +import shutil from datetime import date from pathlib import Path -import re +from dotty_dict import dotty -from qmk.commands import git_get_username -import qmk.path from milc import cli from milc.questions import choice, question -KEYBOARD_TYPES = ['avr', 'ps2avrgb'] +from qmk.commands import git_get_username +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 + +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) +available_layouts = sorted([x.name for x in COMMUNITY.iterdir() if x.is_dir()]) + + +def mcu_type(mcu): + """Callable for argparse validation. + """ + if mcu not in mcu_types: + raise ValueError + return mcu + + +def layout_type(layout): + """Callable for argparse validation. + """ + if layout not in available_layouts: + raise ValueError + return layout def keyboard_name(name): @@ -27,113 +94,163 @@ def validate_keyboard_name(name): return bool(regex.match(name)) -@cli.argument('-kb', '--keyboard', help='Specify the name for the new keyboard directory', arg_only=True, type=keyboard_name) -@cli.argument('-t', '--type', help='Specify the keyboard type', arg_only=True, choices=KEYBOARD_TYPES) -@cli.argument('-u', '--username', help='Specify your username (default from Git config)', arg_only=True) -@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): - """Creates a new keyboard. +def select_default_bootloader(mcu): + """Provide sane defaults for bootloader """ - cli.log.info('{style_bright}Generating a new QMK keyboard directory{style_normal}') - cli.echo('') + return MCU2BOOTLOADER.get(mcu, "custom") + + +def replace_placeholders(src, dest, tokens): + """Replaces the given placeholders in each template file. + """ + content = src.read_text() + for key, value in tokens.items(): + content = content.replace(f'%{key}%', value) - # Get keyboard name - new_keyboard_name = None - while not new_keyboard_name: - new_keyboard_name = cli.args.keyboard if cli.args.keyboard else question('Keyboard Name:') - if not validate_keyboard_name(new_keyboard_name): - cli.log.error('Keyboard names must contain only {fg_cyan}lowercase a-z{fg_reset}, {fg_cyan}0-9{fg_reset}, and {fg_cyan}_{fg_reset}! Please choose a different name.') + dest.write_text(content) - # Exit if passed by arg - if cli.args.keyboard: - return False - new_keyboard_name = None - continue +def augment_community_info(src, dest): + """Splice in any additional data into info.json + """ + info = json.loads(src.read_text()) + template = json.loads(dest.read_text()) - keyboard_path = qmk.path.keyboard(new_keyboard_name) - if keyboard_path.exists(): - cli.log.error(f'Keyboard {{fg_cyan}}{new_keyboard_name}{{fg_reset}} already exists! Please choose a different name.') + # merge community with template + deep_update(info, template) - # Exit if passed by arg - if cli.args.keyboard: - return False + # avoid assumptions on macro name by using the first available + first_layout = next(iter(info["layouts"].values()))["layout"] - new_keyboard_name = None + # guess at width and height now its optional + width, height = (0, 0) + for item in first_layout: + width = max(width, int(item["x"]) + 1) + height = max(height, int(item["y"]) + 1) - # Get keyboard type - keyboard_type = cli.args.type if cli.args.type else choice('Keyboard Type:', KEYBOARD_TYPES, default=0) + info["matrix_pins"] = { + "cols": ["C2"] * width, + "rows": ["D1"] * height, + } - # Get username - user_name = None - while not user_name: - user_name = question('Your GitHub User Name:', default=find_user_name()) + # assume a 1:1 mapping on matrix to electrical + for item in first_layout: + item["matrix"] = [int(item["y"]), int(item["x"])] - if not user_name: - cli.log.error('You didn\'t provide a username, and we couldn\'t find one set in your QMK or Git configs. Please try again.') + # finally write out the updated info.json + dest.write_text(json.dumps(info, cls=InfoJSONEncoder)) - # Exit if passed by arg - if cli.args.username: - return False - real_name = None - while not real_name: - real_name = question('Your real name:', default=user_name) +def prompt_keyboard(): + prompt = """{fg_yellow}Name Your Keyboard Project{style_reset_all} - keyboard_basename = keyboard_path.name - replacements = { - "YEAR": str(date.today().year), - "KEYBOARD": keyboard_basename, - "USER_NAME": user_name, - "YOUR_NAME": real_name, - } +For more infomation, see: +https://docs.qmk.fm/#/hardware_keyboard_guidelines?id=naming-your-keyboardproject - template_dir = Path('data/templates') - template_tree(template_dir / 'base', keyboard_path, replacements) - template_tree(template_dir / keyboard_type, keyboard_path, replacements) +keyboard Name? """ + + return question(prompt, validate=lambda x: not keyboard(x).exists()) - cli.echo('') - cli.log.info(f'{{fg_green}}Created a new keyboard called {{fg_cyan}}{new_keyboard_name}{{fg_green}}.{{fg_reset}}') - cli.log.info(f'To start working on things, `cd` into {{fg_cyan}}{keyboard_path}{{fg_reset}},') - cli.log.info('or open the directory in your preferred text editor.') +def prompt_user(): + prompt = """{fg_yellow}Attribution{style_reset_all} -def find_user_name(): - if cli.args.username: - return cli.args.username - elif cli.config.user.name: - return cli.config.user.name - else: - return git_get_username() +Used for maintainer, copyright, etc +Your GitHub Username? """ + return question(prompt, default=git_get_username()) -def template_tree(src: Path, dst: Path, replacements: dict): - """Recursively copy template and replace placeholders - Args: - src (Path) - The source folder to copy from - dst (Path) - The destination folder to copy to - replacements (dict) - a dictionary with "key":"value" pairs to replace. +def prompt_name(def_name): + prompt = """{fg_yellow}More Attribution{style_reset_all} - Raises: - FileExistsError - When trying to overwrite existing files +Used for maintainer, copyright, etc + +Your Real Name? """ + return question(prompt, default=def_name) + + +def prompt_layout(): + 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? """ + # avoid overwhelming user - remove some? + filtered_layouts = [x for x in available_layouts if not any(xs in x for xs in ['_split', '_blocker', '_tsangan', '_f13'])] + filtered_layouts.append("none of the above") + + return choice(prompt, filtered_layouts, default=len(filtered_layouts) - 1) + + +def prompt_mcu(): + prompt = """{fg_yellow}What Powers Your Project{style_reset_all} + +For more infomation, see: +https://docs.qmk.fm/#/compatible_microcontrollers + +MCU? """ + # remove any options strictly used for compatibility + filtered_mcu = [x for x in mcu_types if not any(xs in x for xs in ['cortex', 'unknown'])] + + return choice(prompt, filtered_mcu, default=filtered_mcu.index("atmega32u4")) + + +@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('-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): + """Creates a new keyboard. """ + cli.log.info('{style_bright}Generating a new QMK keyboard directory{style_normal}') + 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) + 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) - dst.mkdir(parents=True, exist_ok=True) + if not validate_keyboard_name(kb_name): + cli.log.error('Keyboard names must contain only {fg_cyan}lowercase a-z{fg_reset}, {fg_cyan}0-9{fg_reset}, and {fg_cyan}_{fg_reset}! Please choose a different name.') + return 1 - for child in src.iterdir(): - if child.is_dir(): - template_tree(child, dst / child.name, replacements=replacements) + if keyboard(kb_name).exists(): + cli.log.error(f'Keyboard {{fg_cyan}}{kb_name}{{fg_reset}} already exists! Please choose a different name.') + return 1 - if child.is_file(): - file_name = dst / (child.name % replacements) + tokens = {'YEAR': str(date.today().year), 'KEYBOARD': kb_name, 'USER_NAME': user_name, 'REAL_NAME': real_name, 'LAYOUT': default_layout, 'MCU': mcu, 'BOOTLOADER': bootloader} - with file_name.open(mode='x') as dst_f: - with child.open() as src_f: - template = src_f.read() - dst_f.write(template % replacements) + if cli.config.general.verbose: + cli.log.info("Creating keyboard with:") + for key, value in tokens.items(): + cli.echo(f" {key.ljust(10)}: {value}") + + # TODO: detach community layout and rename to just "LAYOUT" + if default_layout == 'none of the above': + default_layout = "ortho_4x4" + + # begin with making the deepest folder in the tree + keymaps_path = keyboard(kb_name) / 'keymaps/' + keymaps_path.mkdir(parents=True) + + # copy in keymap.c or keymap.json + community_keymap = Path(COMMUNITY / f'{default_layout}/default_{default_layout}/') + shutil.copytree(community_keymap, keymaps_path / 'default') + + # process template files + for file in list(TEMPLATE.iterdir()): + replace_placeholders(file, keyboard(kb_name) / file.name, tokens) + + # merge in infos + community_info = Path(COMMUNITY / f'{default_layout}/info.json') + augment_community_info(community_info, keyboard(kb_name) / community_info.name) + + cli.log.info(f'{{fg_green}}Created a new keyboard called {{fg_cyan}}{kb_name}{{fg_green}}.{{fg_reset}}') + cli.log.info(f'To start working on things, `cd` into {{fg_cyan}}keyboards/{kb_name}{{fg_reset}},') + cli.log.info('or open the directory in your preferred text editor.') + cli.log.info(f"And build with {{fg_yellow}}qmk compile -kb {kb_name} -km default{{fg_reset}}.") -- cgit v1.2.3 From f7e7671f691cfd42f322198f04690727f6493d73 Mon Sep 17 00:00:00 2001 From: Joel Challis Date: Thu, 10 Feb 2022 17:45:51 +0000 Subject: Migrate more makefile utilities to builddefs sub-directory (#16002) --- lib/python/qmk/cli/multibuild.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'lib/python/qmk/cli') diff --git a/lib/python/qmk/cli/multibuild.py b/lib/python/qmk/cli/multibuild.py index ad059edff3..dff8c88422 100755 --- a/lib/python/qmk/cli/multibuild.py +++ b/lib/python/qmk/cli/multibuild.py @@ -69,7 +69,7 @@ def multibuild(cli): all: {keyboard_safe}_binary {keyboard_safe}_binary: @rm -f "{QMK_FIRMWARE}/.build/failed.log.{keyboard_safe}" || true - +@$(MAKE) -C "{QMK_FIRMWARE}" -f "{QMK_FIRMWARE}/build_keyboard.mk" KEYBOARD="{keyboard_name}" KEYMAP="{cli.args.keymap}" REQUIRE_PLATFORM_KEY= COLOR=true SILENT=false {' '.join(cli.args.env)} \\ + +@$(MAKE) -C "{QMK_FIRMWARE}" -f "{QMK_FIRMWARE}/builddefs/build_keyboard.mk" KEYBOARD="{keyboard_name}" KEYMAP="{cli.args.keymap}" REQUIRE_PLATFORM_KEY= COLOR=true SILENT=false {' '.join(cli.args.env)} \\ >>"{QMK_FIRMWARE}/.build/build.log.{os.getpid()}.{keyboard_safe}" 2>&1 \\ || cp "{QMK_FIRMWARE}/.build/build.log.{os.getpid()}.{keyboard_safe}" "{QMK_FIRMWARE}/.build/failed.log.{os.getpid()}.{keyboard_safe}" @{{ grep '\[ERRORS\]' "{QMK_FIRMWARE}/.build/build.log.{os.getpid()}.{keyboard_safe}" >/dev/null 2>&1 && printf "Build %-64s \e[1;31m[ERRORS]\e[0m\\n" "{keyboard_name}:{cli.args.keymap}" ; }} \\ -- cgit v1.2.3 From 23c238a1807c3e8c3bdd4eb432506a1469640df5 Mon Sep 17 00:00:00 2001 From: Erovia Date: Mon, 14 Feb 2022 11:02:35 +0000 Subject: CLI: Minor additions #12795 (#16276) --- lib/python/qmk/cli/new/keyboard.py | 89 ++++++++++++++++---------------------- 1 file changed, 37 insertions(+), 52 deletions(-) (limited to 'lib/python/qmk/cli') diff --git a/lib/python/qmk/cli/new/keyboard.py b/lib/python/qmk/cli/new/keyboard.py index 59e781a932..8596994d38 100644 --- a/lib/python/qmk/cli/new/keyboard.py +++ b/lib/python/qmk/cli/new/keyboard.py @@ -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} +def prompt_user(): + 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) -- cgit v1.2.3 From b0621223bc53f634a28243b874379e8e157878fd Mon Sep 17 00:00:00 2001 From: Joel Challis Date: Tue, 15 Feb 2022 01:42:58 +0000 Subject: Various fixes for new-keyboard (#16358) --- lib/python/qmk/cli/new/keyboard.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) (limited to 'lib/python/qmk/cli') diff --git a/lib/python/qmk/cli/new/keyboard.py b/lib/python/qmk/cli/new/keyboard.py index 8596994d38..6fa9ad5b2c 100644 --- a/lib/python/qmk/cli/new/keyboard.py +++ b/lib/python/qmk/cli/new/keyboard.py @@ -208,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:") -- cgit v1.2.3 From f30f963a0b6ccaa151fe83dd8302fa1f6829086e Mon Sep 17 00:00:00 2001 From: Ryan Date: Tue, 22 Feb 2022 02:47:44 +1100 Subject: Internal docs generation updates (#16411) --- lib/python/qmk/cli/generate/docs.py | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) (limited to 'lib/python/qmk/cli') diff --git a/lib/python/qmk/cli/generate/docs.py b/lib/python/qmk/cli/generate/docs.py index 749336fea5..74112d834d 100644 --- a/lib/python/qmk/cli/generate/docs.py +++ b/lib/python/qmk/cli/generate/docs.py @@ -7,7 +7,9 @@ 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' @cli.subcommand('Build QMK documentation.', hidden=False if cli.config.user.developer else True) @@ -18,10 +20,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 = { @@ -34,6 +38,6 @@ def generate_docs(cli): # 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', BUILD_DOCS_PATH / 'internals_%s.md', DOXYGEN_PATH / 'xml'], **args) - cli.log.info('Successfully generated internal docs to %s.', BUILD_PATH) + cli.log.info('Successfully generated internal docs to %s.', BUILD_DOCS_PATH) -- cgit v1.2.3 From 8aec20c0da9481c5294b216346231a98570d1626 Mon Sep 17 00:00:00 2001 From: QMK Bot Date: Tue, 22 Feb 2022 10:29:47 -0800 Subject: Format code according to conventions (#16435) --- lib/python/qmk/cli/pytest.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'lib/python/qmk/cli') diff --git a/lib/python/qmk/cli/pytest.py b/lib/python/qmk/cli/pytest.py index 2e4a0a9f00..b7b17f0e9d 100644 --- a/lib/python/qmk/cli/pytest.py +++ b/lib/python/qmk/cli/pytest.py @@ -12,7 +12,8 @@ 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 -- cgit v1.2.3 From cf31355f08dca311a013168eb3eb995e2fc6a3d1 Mon Sep 17 00:00:00 2001 From: Joel Challis Date: Wed, 23 Feb 2022 17:33:08 +0000 Subject: Changelog 2022q1 (#16380) * Initial changelog pass * update generate-develop-pr-list content * Fix bad word-ness * Fix generate-develop-pr-list ignores * Update docs/ChangeLog/20220226.md Co-authored-by: Sergey Vlasov Co-authored-by: Sergey Vlasov --- lib/python/qmk/cli/generate/develop_pr_list.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'lib/python/qmk/cli') diff --git a/lib/python/qmk/cli/generate/develop_pr_list.py b/lib/python/qmk/cli/generate/develop_pr_list.py index 09236a7c42..549db5b185 100755 --- a/lib/python/qmk/cli/generate/develop_pr_list.py +++ b/lib/python/qmk/cli/generate/develop_pr_list.py @@ -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): -- cgit v1.2.3 From 779c7debcfff1a4a3ad578a0c12bdd50cba11039 Mon Sep 17 00:00:00 2001 From: Joel Challis Date: Sun, 27 Feb 2022 12:39:24 +0000 Subject: Fix issues with data driven split keyboards (#16457) --- lib/python/qmk/cli/generate/config_h.py | 36 ++++++++++++++++++--------------- lib/python/qmk/cli/generate/layouts.py | 16 ++++++--------- lib/python/qmk/cli/generate/rules_mk.py | 2 +- 3 files changed, 27 insertions(+), 27 deletions(-) (limited to 'lib/python/qmk/cli') diff --git a/lib/python/qmk/cli/generate/config_h.py b/lib/python/qmk/cli/generate/config_h.py index 6b1012fae7..24bbbdf517 100755 --- a/lib/python/qmk/cli/generate/config_h.py +++ b/lib/python/qmk/cli/generate/config_h.py @@ -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. """ @@ -183,6 +185,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'])) diff --git a/lib/python/qmk/cli/generate/layouts.py b/lib/python/qmk/cli/generate/layouts.py index e44266e1c8..a21311bd49 100755 --- a/lib/python/qmk/cli/generate/layouts.py +++ b/lib/python/qmk/cli/generate/layouts.py @@ -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']: diff --git a/lib/python/qmk/cli/generate/rules_mk.py b/lib/python/qmk/cli/generate/rules_mk.py index 5d8d7cc8a7..ce824f6378 100755 --- a/lib/python/qmk/cli/generate/rules_mk.py +++ b/lib/python/qmk/cli/generate/rules_mk.py @@ -29,7 +29,7 @@ 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()]) -- cgit v1.2.3 From fbfd5312b995a32af690c183cad0dc988f695e89 Mon Sep 17 00:00:00 2001 From: Erovia Date: Mon, 28 Feb 2022 20:02:39 +0000 Subject: CLI: Validate JSON keymap input (#16261) * Fix schema validator It should use the passed schema. * Add required attributes to keymap schema * Rework subcommands to validate the JSON keymaps The 'compile', 'flash' and 'json2c' subcommands were reworked to add JSON keymap validation so error is reported for non-JSON and non-compliant-JSON inputs. * Fix required fields in keymap schema * Add tests * Fix compiling keymaps directly from keymap directory * Schema should not require version for now. --- lib/python/qmk/cli/json2c.py | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) (limited to 'lib/python/qmk/cli') diff --git a/lib/python/qmk/cli/json2c.py b/lib/python/qmk/cli/json2c.py index ae8248e6b7..2873a9bfd3 100755 --- a/lib/python/qmk/cli/json2c.py +++ b/lib/python/qmk/cli/json2c.py @@ -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 == '-': -- cgit v1.2.3 From b75f6691a15a78b9c200a88e28792974ca2f9461 Mon Sep 17 00:00:00 2001 From: Erovia Date: Thu, 10 Mar 2022 21:33:41 +0000 Subject: CLI: Fix 'cd' subcommand on Windows (#16610) The 'cd' subcommand was failing as the current shell's Windows path was mangled while milc processed it. Using 'subprocess' directly avoids this issue and an extra layer of subshell. --- lib/python/qmk/cli/cd.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'lib/python/qmk/cli') diff --git a/lib/python/qmk/cli/cd.py b/lib/python/qmk/cli/cd.py index c62c3f56c6..ef03011f1f 100755 --- a/lib/python/qmk/cli/cd.py +++ b/lib/python/qmk/cli/cd.py @@ -2,6 +2,7 @@ """ import sys import os +import subprocess from milc import cli @@ -41,6 +42,6 @@ def cd(cli): # Set the prompt for the new shell qmk_env['MSYS2_PS1'] = qmk_env['PS1'] # Start the new subshell - cli.run([os.environ.get('SHELL', '/usr/bin/bash')], env=qmk_env) + subprocess.run([os.environ.get('SHELL', '/usr/bin/bash')], env=qmk_env) else: cli.log.info("Already within qmk_firmware directory.") -- cgit v1.2.3 From e5823b56501598c39d3f57719cf32f344212ede6 Mon Sep 17 00:00:00 2001 From: Joel Challis Date: Fri, 18 Mar 2022 01:09:29 +0000 Subject: [CLI] Add common util for dumping generated content (#16674) --- lib/python/qmk/cli/generate/config_h.py | 18 +++---------- lib/python/qmk/cli/generate/dfu_header.py | 18 +++---------- lib/python/qmk/cli/generate/keyboard_h.py | 18 +++---------- lib/python/qmk/cli/generate/layouts.py | 19 +++----------- lib/python/qmk/cli/generate/rules_mk.py | 14 +++-------- lib/python/qmk/cli/generate/version_h.py | 42 +++++++++++++++++++++++-------- 6 files changed, 51 insertions(+), 78 deletions(-) (limited to 'lib/python/qmk/cli') diff --git a/lib/python/qmk/cli/generate/config_h.py b/lib/python/qmk/cli/generate/config_h.py index 24bbbdf517..fdc76b23d4 100755 --- a/lib/python/qmk/cli/generate/config_h.py +++ b/lib/python/qmk/cli/generate/config_h.py @@ -9,7 +9,9 @@ from qmk.info import info_json from qmk.json_schema import json_load, validate from qmk.keyboard import keyboard_completer, keyboard_folder from qmk.keymap import locate_keymap +from qmk.commands import dump_lines from qmk.path import normpath +from qmk.constants import GPL2_HEADER_C_LIKE, GENERATED_HEADER_C_LIKE def direct_pins(direct_pins, postfix): @@ -181,7 +183,7 @@ def generate_config_h(cli): kb_info_json = dotty(info_json(cli.args.keyboard)) # Build the info_config.h file. - config_h_lines = ['/* This file was generated by `qmk generate-config-h`. Do not edit or copy.', ' */', '', '#pragma once'] + config_h_lines = [GPL2_HEADER_C_LIKE, GENERATED_HEADER_C_LIKE, '#pragma once'] generate_config_items(kb_info_json, config_h_lines) @@ -194,16 +196,4 @@ def generate_config_h(cli): generate_split_config(kb_info_json, config_h_lines) # Show the results - config_h = '\n'.join(config_h_lines) - - if cli.args.output: - 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) - - if not cli.args.quiet: - cli.log.info('Wrote info_config.h to %s.', cli.args.output) - - else: - print(config_h) + dump_lines(cli.args.output, config_h_lines, cli.args.quiet) diff --git a/lib/python/qmk/cli/generate/dfu_header.py b/lib/python/qmk/cli/generate/dfu_header.py index 7fb585fc7d..e873117387 100644 --- a/lib/python/qmk/cli/generate/dfu_header.py +++ b/lib/python/qmk/cli/generate/dfu_header.py @@ -7,6 +7,8 @@ from qmk.decorators import automagic_keyboard from qmk.info import info_json from qmk.path import is_keyboard, normpath from qmk.keyboard import keyboard_completer +from qmk.commands import dump_lines +from qmk.constants import GPL2_HEADER_C_LIKE, GENERATED_HEADER_C_LIKE @cli.argument('-o', '--output', arg_only=True, type=normpath, help='File to write to') @@ -30,7 +32,7 @@ def generate_dfu_header(cli): # Build the Keyboard.h file. kb_info_json = dotty(info_json(cli.config.generate_dfu_header.keyboard)) - keyboard_h_lines = ['/* This file was generated by `qmk generate-dfu-header`. Do not edit or copy.', ' */', '', '#pragma once'] + keyboard_h_lines = [GPL2_HEADER_C_LIKE, GENERATED_HEADER_C_LIKE, '#pragma once'] keyboard_h_lines.append(f'#define MANUFACTURER {kb_info_json["manufacturer"]}') keyboard_h_lines.append(f'#define PRODUCT {kb_info_json["keyboard_name"]} Bootloader') @@ -45,16 +47,4 @@ def generate_dfu_header(cli): keyboard_h_lines.append(f'#define QMK_SPEAKER {kb_info_json["qmk_lufa_bootloader.speaker"]}') # Show the results - keyboard_h = '\n'.join(keyboard_h_lines) - - if cli.args.output: - 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(keyboard_h) - - if not cli.args.quiet: - cli.log.info('Wrote Keyboard.h to %s.', cli.args.output) - - else: - print(keyboard_h) + dump_lines(cli.args.output, keyboard_h_lines, cli.args.quiet) diff --git a/lib/python/qmk/cli/generate/keyboard_h.py b/lib/python/qmk/cli/generate/keyboard_h.py index f05178cede..2058865cbf 100755 --- a/lib/python/qmk/cli/generate/keyboard_h.py +++ b/lib/python/qmk/cli/generate/keyboard_h.py @@ -3,8 +3,10 @@ from milc import cli from qmk.info import info_json +from qmk.commands import dump_lines from qmk.keyboard import keyboard_completer, keyboard_folder from qmk.path import normpath +from qmk.constants import GPL2_HEADER_C_LIKE, GENERATED_HEADER_C_LIKE def would_populate_layout_h(keyboard): @@ -36,22 +38,10 @@ def generate_keyboard_h(cli): has_layout_h = would_populate_layout_h(cli.args.keyboard) # Build the layouts.h file. - keyboard_h_lines = ['/* This file was generated by `qmk generate-keyboard-h`. Do not edit or copy.', ' */', '', '#pragma once', '#include "quantum.h"'] + keyboard_h_lines = [GPL2_HEADER_C_LIKE, GENERATED_HEADER_C_LIKE, '#pragma once', '#include "quantum.h"'] if not has_layout_h: keyboard_h_lines.append('#pragma error(".h is only optional for data driven keyboards - kb.h == bad times")') # Show the results - keyboard_h = '\n'.join(keyboard_h_lines) + '\n' - - if cli.args.output: - 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(keyboard_h) - - if not cli.args.quiet: - cli.log.info('Wrote keyboard_h to %s.', cli.args.output) - - else: - print(keyboard_h) + dump_lines(cli.args.output, keyboard_h_lines, cli.args.quiet) diff --git a/lib/python/qmk/cli/generate/layouts.py b/lib/python/qmk/cli/generate/layouts.py index a21311bd49..193633baf6 100755 --- a/lib/python/qmk/cli/generate/layouts.py +++ b/lib/python/qmk/cli/generate/layouts.py @@ -2,11 +2,12 @@ """ from milc import cli -from qmk.constants import COL_LETTERS, ROW_LETTERS +from qmk.constants import COL_LETTERS, ROW_LETTERS, GPL2_HEADER_C_LIKE, GENERATED_HEADER_C_LIKE from qmk.decorators import automagic_keyboard, automagic_keymap from qmk.info import info_json from qmk.keyboard import keyboard_completer, keyboard_folder from qmk.path import is_keyboard, normpath +from qmk.commands import dump_lines usb_properties = { 'vid': 'VENDOR_ID', @@ -38,7 +39,7 @@ def generate_layouts(cli): kb_info_json = info_json(cli.config.generate_layouts.keyboard) # Build the layouts.h file. - layouts_h_lines = ['/* This file was generated by `qmk generate-layouts`. Do not edit or copy.', ' */', '', '#pragma once'] + layouts_h_lines = [GPL2_HEADER_C_LIKE, GENERATED_HEADER_C_LIKE, '#pragma once'] if 'matrix_size' not in kb_info_json: cli.log.error('%s: Invalid matrix config.', cli.config.generate_layouts.keyboard) @@ -86,16 +87,4 @@ def generate_layouts(cli): layouts_h_lines.append('#endif') # Show the results - layouts_h = '\n'.join(layouts_h_lines) + '\n' - - if cli.args.output: - 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(layouts_h) - - if not cli.args.quiet: - cli.log.info('Wrote info_config.h to %s.', cli.args.output) - - else: - print(layouts_h) + dump_lines(cli.args.output, layouts_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 ce824f6378..29a1130f99 100755 --- a/lib/python/qmk/cli/generate/rules_mk.py +++ b/lib/python/qmk/cli/generate/rules_mk.py @@ -9,7 +9,9 @@ from qmk.info import info_json from qmk.json_schema import json_load, validate from qmk.keyboard import keyboard_completer, keyboard_folder from qmk.keymap import locate_keymap +from qmk.commands import dump_lines from qmk.path import normpath +from qmk.constants import GPL2_HEADER_SH_LIKE, GENERATED_HEADER_SH_LIKE def process_mapping_rule(kb_info_json, rules_key, info_dict): @@ -55,7 +57,7 @@ def generate_rules_mk(cli): kb_info_json = dotty(info_json(cli.args.keyboard)) info_rules_map = json_load(Path('data/mappings/info_rules.json')) - rules_mk_lines = ['# This file was generated by `qmk generate-rules-mk`. Do not edit or copy.', ''] + rules_mk_lines = [GPL2_HEADER_SH_LIKE, GENERATED_HEADER_SH_LIKE] # Iterate through the info_rules map to generate basic rules for rules_key, info_dict in info_rules_map.items(): @@ -83,14 +85,9 @@ def generate_rules_mk(cli): rules_mk_lines.append('CUSTOM_MATRIX ?= yes') # Show the results - rules_mk = '\n'.join(rules_mk_lines) + '\n' + dump_lines(cli.args.output, rules_mk_lines) if cli.args.output: - 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(rules_mk) - if cli.args.quiet: if cli.args.escape: print(cli.args.output.as_posix().replace(' ', '\\ ')) @@ -98,6 +95,3 @@ def generate_rules_mk(cli): print(cli.args.output) else: cli.log.info('Wrote rules.mk to %s.', cli.args.output) - - else: - print(rules_mk) diff --git a/lib/python/qmk/cli/generate/version_h.py b/lib/python/qmk/cli/generate/version_h.py index 69341e36f0..be9646748e 100644 --- a/lib/python/qmk/cli/generate/version_h.py +++ b/lib/python/qmk/cli/generate/version_h.py @@ -1,9 +1,15 @@ """Used by the make system to generate version.h for use in code. """ +from time import strftime + from milc import cli -from qmk.commands import create_version_h from qmk.path import normpath +from qmk.commands import dump_lines +from qmk.commands import get_git_version +from qmk.constants import GPL2_HEADER_C_LIKE, GENERATED_HEADER_C_LIKE + +TIME_FMT = '%Y-%m-%d-%H:%M:%S' @cli.argument('-o', '--output', arg_only=True, type=normpath, help='File to write to') @@ -17,15 +23,29 @@ def generate_version_h(cli): if cli.args.skip_all: cli.args.skip_git = True - version_h = create_version_h(cli.args.skip_git, cli.args.skip_all) - - if cli.args.output: - 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(version_h) + if cli.args.skip_all: + current_time = "1970-01-01-00:00:00" + else: + current_time = strftime(TIME_FMT) - if not cli.args.quiet: - cli.log.info('Wrote version.h to %s.', cli.args.output) + if cli.args.skip_git: + git_version = "NA" + chibios_version = "NA" + chibios_contrib_version = "NA" else: - print(version_h) + git_version = get_git_version(current_time) + chibios_version = get_git_version(current_time, "chibios", "os") + chibios_contrib_version = get_git_version(current_time, "chibios-contrib", "os") + + # Build the version.h file. + version_h_lines = [GPL2_HEADER_C_LIKE, GENERATED_HEADER_C_LIKE, '#pragma once'] + + version_h_lines.append(f""" +#define QMK_VERSION "{git_version}" +#define QMK_BUILDDATE "{current_time}" +#define CHIBIOS_VERSION "{chibios_version}" +#define CHIBIOS_CONTRIB_VERSION "{chibios_contrib_version}" +""") + + # Show the results + dump_lines(cli.args.output, version_h_lines, cli.args.quiet) -- cgit v1.2.3 From ed773ab73cab83b842dc62ff94ffb337ec66a5f3 Mon Sep 17 00:00:00 2001 From: Joel Challis Date: Fri, 18 Mar 2022 16:02:24 +0000 Subject: Relocate CLI git interactions (#16682) --- lib/python/qmk/cli/doctor/check.py | 12 ------------ lib/python/qmk/cli/doctor/main.py | 3 ++- lib/python/qmk/cli/generate/version_h.py | 8 ++++---- lib/python/qmk/cli/new/keyboard.py | 2 +- 4 files changed, 7 insertions(+), 18 deletions(-) (limited to 'lib/python/qmk/cli') diff --git a/lib/python/qmk/cli/doctor/check.py b/lib/python/qmk/cli/doctor/check.py index 2d691b64b0..8a0422ba72 100644 --- a/lib/python/qmk/cli/doctor/check.py +++ b/lib/python/qmk/cli/doctor/check.py @@ -7,7 +7,6 @@ from subprocess import DEVNULL from milc import cli from qmk import submodules -from qmk.constants import QMK_FIRMWARE class CheckStatus(Enum): @@ -150,14 +149,3 @@ def is_executable(command): cli.log.error("{fg_red}Can't run `%s %s`", command, version_arg) return False - - -def check_git_repo(): - """Checks that the .git directory exists inside QMK_HOME. - - This is a decent enough indicator that the qmk_firmware directory is a - proper Git repository, rather than a .zip download from GitHub. - """ - dot_git = QMK_FIRMWARE / '.git' - - return CheckStatus.OK if dot_git.exists() else CheckStatus.WARNING diff --git a/lib/python/qmk/cli/doctor/main.py b/lib/python/qmk/cli/doctor/main.py index 2e5e221e8f..2898a9894c 100755 --- a/lib/python/qmk/cli/doctor/main.py +++ b/lib/python/qmk/cli/doctor/main.py @@ -11,7 +11,8 @@ from milc.questions import yesno from qmk import submodules from qmk.constants import QMK_FIRMWARE, QMK_FIRMWARE_UPSTREAM from .check import CheckStatus, check_binaries, check_binary_versions, check_submodules -from qmk.commands import git_check_repo, git_get_branch, git_get_tag, git_is_dirty, git_get_remotes, git_check_deviation, in_virtualenv +from qmk.git import git_check_repo, git_get_branch, git_get_tag, git_is_dirty, git_get_remotes, git_check_deviation +from qmk.commands import in_virtualenv def os_tests(): diff --git a/lib/python/qmk/cli/generate/version_h.py b/lib/python/qmk/cli/generate/version_h.py index be9646748e..a75702c529 100644 --- a/lib/python/qmk/cli/generate/version_h.py +++ b/lib/python/qmk/cli/generate/version_h.py @@ -6,7 +6,7 @@ from milc import cli from qmk.path import normpath from qmk.commands import dump_lines -from qmk.commands import get_git_version +from qmk.git import git_get_version from qmk.constants import GPL2_HEADER_C_LIKE, GENERATED_HEADER_C_LIKE TIME_FMT = '%Y-%m-%d-%H:%M:%S' @@ -33,9 +33,9 @@ def generate_version_h(cli): chibios_version = "NA" chibios_contrib_version = "NA" else: - git_version = get_git_version(current_time) - chibios_version = get_git_version(current_time, "chibios", "os") - chibios_contrib_version = get_git_version(current_time, "chibios-contrib", "os") + git_version = git_get_version() or current_time + chibios_version = git_get_version("chibios", "os") or current_time + chibios_contrib_version = git_get_version("chibios-contrib", "os") or current_time # Build the version.h file. version_h_lines = [GPL2_HEADER_C_LIKE, GENERATED_HEADER_C_LIKE, '#pragma once'] diff --git a/lib/python/qmk/cli/new/keyboard.py b/lib/python/qmk/cli/new/keyboard.py index 6fa9ad5b2c..1cdfe53206 100644 --- a/lib/python/qmk/cli/new/keyboard.py +++ b/lib/python/qmk/cli/new/keyboard.py @@ -10,7 +10,7 @@ from dotty_dict import dotty from milc import cli from milc.questions import choice, question -from qmk.commands import git_get_username +from qmk.git import git_get_username from qmk.json_schema import load_jsonschema from qmk.path import keyboard from qmk.json_encoders import InfoJSONEncoder -- cgit v1.2.3 From efc9c525b19b33c6e09057218ea64f07f45f9555 Mon Sep 17 00:00:00 2001 From: Erovia Date: Thu, 24 Mar 2022 20:13:40 +0000 Subject: CLI: Add 'via2json' subcommand (#16468) --- lib/python/qmk/cli/__init__.py | 1 + lib/python/qmk/cli/via2json.py | 145 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 146 insertions(+) create mode 100755 lib/python/qmk/cli/via2json.py (limited to 'lib/python/qmk/cli') diff --git a/lib/python/qmk/cli/__init__.py b/lib/python/qmk/cli/__init__.py index c51eece955..5f65e677e5 100644 --- a/lib/python/qmk/cli/__init__.py +++ b/lib/python/qmk/cli/__init__.py @@ -69,6 +69,7 @@ subcommands = [ 'qmk.cli.new.keymap', 'qmk.cli.pyformat', 'qmk.cli.pytest', + 'qmk.cli.via2json', ] diff --git a/lib/python/qmk/cli/via2json.py b/lib/python/qmk/cli/via2json.py new file mode 100755 index 0000000000..6edc9dfbe5 --- /dev/null +++ b/lib/python/qmk/cli/via2json.py @@ -0,0 +1,145 @@ +"""Generate a keymap.c from a configurator export. +""" +import json +import re + +from milc import cli + +import qmk.keyboard +import qmk.path +from qmk.info import info_json +from qmk.json_encoders import KeymapJSONEncoder +from qmk.commands import parse_configurator_json, dump_lines +from qmk.keymap import generate_json, list_keymaps, locate_keymap, parse_keymap_c + + +def _find_via_layout_macro(keyboard): + keymap_layout = None + if 'via' in list_keymaps(keyboard): + keymap_path = locate_keymap(keyboard, 'via') + if keymap_path.suffix == '.json': + keymap_layout = parse_configurator_json(keymap_path)['layout'] + else: + keymap_layout = parse_keymap_c(keymap_path)['layers'][0]['layout'] + return keymap_layout + + +def _convert_macros(via_macros): + via_macros = list(filter(lambda f: bool(f), via_macros)) + if len(via_macros) == 0: + return list() + split_regex = re.compile(r'(}\,)|(\,{)') + macros = list() + for via_macro in via_macros: + # Split VIA macro to its elements + macro = split_regex.split(via_macro) + # Remove junk elements (None, '},' and ',{') + macro = list(filter(lambda f: False if f in (None, '},', ',{') else True, macro)) + macro_data = list() + for m in macro: + if '{' in m or '}' in m: + # Found keycode(s) + keycodes = m.split(',') + # Remove whitespaces and curly braces from around keycodes + keycodes = list(map(lambda s: s.strip(' {}'), keycodes)) + # Remove the KC prefix + keycodes = list(map(lambda s: s.replace('KC_', ''), keycodes)) + macro_data.append({"action": "tap", "keycodes": keycodes}) + else: + # Found text + macro_data.append(m) + macros.append(macro_data) + + return macros + + +def _fix_macro_keys(keymap_data): + macro_no = re.compile(r'MACRO0?([0-9]{1,2})') + for i in range(0, len(keymap_data)): + for j in range(0, len(keymap_data[i])): + kc = keymap_data[i][j] + m = macro_no.match(kc) + if m: + keymap_data[i][j] = f'MACRO_{m.group(1)}' + return keymap_data + + +def _via_to_keymap(via_backup, keyboard_data, keymap_layout): + # Check if passed LAYOUT is correct + layout_data = keyboard_data['layouts'].get(keymap_layout) + if not layout_data: + cli.log.error(f'LAYOUT macro {keymap_layout} is not a valid one for keyboard {cli.args.keyboard}!') + exit(1) + + layout_data = layout_data['layout'] + sorting_hat = list() + for index, data in enumerate(layout_data): + sorting_hat.append([index, data['matrix']]) + + sorting_hat.sort(key=lambda k: (k[1][0], k[1][1])) + + pos = 0 + for row_num in range(0, keyboard_data['matrix_size']['rows']): + for col_num in range(0, keyboard_data['matrix_size']['cols']): + if pos >= len(sorting_hat) or sorting_hat[pos][1][0] != row_num or sorting_hat[pos][1][1] != col_num: + sorting_hat.insert(pos, [None, [row_num, col_num]]) + else: + sorting_hat.append([None, [row_num, col_num]]) + pos += 1 + + keymap_data = list() + for layer in via_backup['layers']: + pos = 0 + layer_data = list() + for key in layer: + if sorting_hat[pos][0] is not None: + layer_data.append([sorting_hat[pos][0], key]) + pos += 1 + layer_data.sort() + layer_data = [kc[1] for kc in layer_data] + keymap_data.append(layer_data) + + return keymap_data + + +@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('filename', type=qmk.path.FileType('r'), arg_only=True, help='VIA Backup JSON file') +@cli.argument('-kb', '--keyboard', type=qmk.keyboard.keyboard_folder, completer=qmk.keyboard.keyboard_completer, arg_only=True, required=True, help='The keyboard\'s name') +@cli.argument('-km', '--keymap', arg_only=True, default='via2json', help='The keymap\'s name') +@cli.argument('-l', '--layout', arg_only=True, help='The keymap\'s layout') +@cli.subcommand('Convert a VIA backup json to keymap.json format.') +def via2json(cli): + """Convert a VIA backup json to keymap.json format. + + This command uses the `qmk.keymap` module to generate a keymap.json from a VIA backup json. The generated keymap is written to stdout, or to a file if -o is provided. + """ + # Find appropriate layout macro + keymap_layout = cli.args.layout if cli.args.layout else _find_via_layout_macro(cli.args.keyboard) + if not keymap_layout: + cli.log.error(f"Couldn't find LAYOUT macro for keyboard {cli.args.keyboard}. Please specify it with the '-l' argument.") + exit(1) + + # Load the VIA backup json + with cli.args.filename.open('r') as fd: + via_backup = json.load(fd) + + # Generate keyboard metadata + keyboard_data = info_json(cli.args.keyboard) + + # Get keycode array + keymap_data = _via_to_keymap(via_backup, keyboard_data, keymap_layout) + + # Convert macros + macro_data = list() + if via_backup.get('macros'): + macro_data = _convert_macros(via_backup['macros']) + + # Replace VIA macro keys with JSON keymap ones + keymap_data = _fix_macro_keys(keymap_data) + + # Generate the keymap.json + keymap_json = generate_json(cli.args.keymap, cli.args.keyboard, keymap_layout, keymap_data, macro_data) + + keymap_lines = [json.dumps(keymap_json, cls=KeymapJSONEncoder)] + dump_lines(cli.args.output, keymap_lines, cli.args.quiet) -- cgit v1.2.3