From 588bcdc8ca212b195a428fc43766a59a9252c08d Mon Sep 17 00:00:00 2001 From: Zach White Date: Wed, 14 Apr 2021 19:00:22 -0700 Subject: [PATCH] Add support for tab completion (#12411) * Add support for tab completion * make flake8 happy * Add documentation --- docs/_summary.md | 1 + docs/cli_tab_complete.md | 27 ++++++++++ lib/python/qmk/cli/__init__.py | 14 +++++- lib/python/qmk/cli/c2json.py | 7 +-- lib/python/qmk/cli/cformat.py | 3 +- lib/python/qmk/cli/compile.py | 10 ++-- lib/python/qmk/cli/flash.py | 7 +-- lib/python/qmk/cli/generate/config_h.py | 4 +- lib/python/qmk/cli/generate/dfu_header.py | 3 +- lib/python/qmk/cli/generate/info_json.py | 4 +- lib/python/qmk/cli/generate/layouts.py | 4 +- lib/python/qmk/cli/generate/rules_mk.py | 4 +- lib/python/qmk/cli/info.py | 4 +- lib/python/qmk/cli/json2c.py | 3 +- lib/python/qmk/cli/kle2json.py | 3 +- lib/python/qmk/cli/lint.py | 3 +- lib/python/qmk/cli/list/keymaps.py | 4 +- lib/python/qmk/cli/new/keymap.py | 4 +- lib/python/qmk/decorators.py | 60 +++++------------------ lib/python/qmk/keyboard.py | 26 +++++++++- lib/python/qmk/keymap.py | 56 +++++++++++++++++++-- requirements.txt | 2 +- util/qmk_tab_complete.sh | 2 + 23 files changed, 169 insertions(+), 86 deletions(-) create mode 100644 docs/cli_tab_complete.md create mode 100644 util/qmk_tab_complete.sh diff --git a/docs/_summary.md b/docs/_summary.md index 83799acdb8b86ad5738d15613521d92740de60a0..825514e6b5d30c00cc2ae22def2d086356af271c 100644 --- a/docs/_summary.md +++ b/docs/_summary.md @@ -29,6 +29,7 @@ * [Overview](cli.md) * [Configuration](cli_configuration.md) * [Commands](cli_commands.md) + * [Tab Completion](cli_tab_complete.md) * Using QMK * Guides diff --git a/docs/cli_tab_complete.md b/docs/cli_tab_complete.md new file mode 100644 index 0000000000000000000000000000000000000000..2217d4fd3bc6772e48161bcd73096748dd19a03a --- /dev/null +++ b/docs/cli_tab_complete.md @@ -0,0 +1,27 @@ +# Tab Completion for QMK + +If you are using Bash 4.2 or later, Zsh, or FiSH you can enable Tab Completion for the QMK CLI. This will let you tab complete the names of flags, keyboards, files, and other `qmk` options. + +## Setup + +There are several ways you can setup tab completion. + +### For Your User Only + +Add this to the end of your `.profile` or `.bashrc`: + + source ~/qmk_firmware/util/qmk_tab_complete.sh + +If you put `qmk_firmware` into another location you will need to adjust this path. + +### System Wide Symlink + +If you want the tab completion available to all users of the system you can add a symlink to the `qmk_tab_complete.sh` script: + + `ln -s ~/qmk_firmware/util/qmk_tab_complete.sh /etc/profile.d/qmk_tab_complete.sh` + +### System Wide Copy + +In some cases a symlink may not work. Instead you can copy the file directly into place. Be aware that updates to the tab complete script may happen from time to time, you will want to recopy the file periodically. + + cp util/qmk_tab_complete.sh /etc/profile.d diff --git a/lib/python/qmk/cli/__init__.py b/lib/python/qmk/cli/__init__.py index 1349e68a9bfe7a8f0e0a8a05be4a55b9f41b230b..f7df9081198446def9bef76be7e8d343e8c06c99 100644 --- a/lib/python/qmk/cli/__init__.py +++ b/lib/python/qmk/cli/__init__.py @@ -4,7 +4,7 @@ We list each subcommand here explicitly because all the reliable ways of searchi """ import sys -from milc import cli +from milc import cli, __VERSION__ from . import c2json from . import cformat @@ -47,5 +47,15 @@ from . import pytest # void: 3.9 if sys.version_info[0] != 3 or sys.version_info[1] < 7: - cli.log.error('Your Python is too old! Please upgrade to Python 3.7 or later.') + print('Error: Your Python is too old! Please upgrade to Python 3.7 or later.') + exit(127) + +milc_version = __VERSION__.split('.') + +if int(milc_version[0]) < 2 and int(milc_version[1]) < 3: + from pathlib import Path + + requirements = Path('requirements.txt').resolve() + + print(f'Your MILC library is too old! Please upgrade: python3 -m pip install -U -r {str(requirements)}') exit(127) diff --git a/lib/python/qmk/cli/c2json.py b/lib/python/qmk/cli/c2json.py index 1fa833b647a2c721ea92dcc30fe2298cb374c08f..e66b0a1b5899c9a446650c3b4c20515e8a0996ce 100644 --- a/lib/python/qmk/cli/c2json.py +++ b/lib/python/qmk/cli/c2json.py @@ -2,20 +2,21 @@ """ import json +from argcomplete.completers import FilesCompleter from milc import cli import qmk.keymap import qmk.path from qmk.json_encoders import InfoJSONEncoder -from qmk.keyboard import keyboard_folder +from qmk.keyboard import keyboard_completer, keyboard_folder @cli.argument('--no-cpp', arg_only=True, action='store_false', help='Do not use \'cpp\' on keymap.c') @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, required=True, help='The keyboard\'s name') +@cli.argument('-kb', '--keyboard', arg_only=True, type=keyboard_folder, completer=keyboard_completer, required=True, help='The keyboard\'s name') @cli.argument('-km', '--keymap', arg_only=True, required=True, help='The keymap\'s name') -@cli.argument('filename', arg_only=True, help='keymap.c file') +@cli.argument('filename', arg_only=True, completer=FilesCompleter('.c'), help='keymap.c file') @cli.subcommand('Creates a keymap.json from a keymap.c file.') def c2json(cli): """Generate a keymap.json from a keymap.c file. diff --git a/lib/python/qmk/cli/cformat.py b/lib/python/qmk/cli/cformat.py index c7e93b2ab63fa6002bf784573110aebe9b2f19ff..d0d3b3b0a3cd4caff8e5cf4d345e93458469cfaa 100644 --- a/lib/python/qmk/cli/cformat.py +++ b/lib/python/qmk/cli/cformat.py @@ -3,6 +3,7 @@ import subprocess from shutil import which +from argcomplete.completers import FilesCompleter from milc import cli from qmk.path import normpath @@ -33,7 +34,7 @@ def cformat_run(files, all_files): @cli.argument('-a', '--all-files', arg_only=True, action='store_true', help='Format all core files.') @cli.argument('-b', '--base-branch', default='origin/master', help='Branch to compare to diffs to.') -@cli.argument('files', nargs='*', arg_only=True, help='Filename(s) to format.') +@cli.argument('files', nargs='*', arg_only=True, completer=FilesCompleter('.c'), help='Filename(s) to format.') @cli.subcommand("Format C code according to QMK's style.", hidden=False if cli.config.user.developer else True) def cformat(cli): """Format C code according to QMK's style. diff --git a/lib/python/qmk/cli/compile.py b/lib/python/qmk/cli/compile.py index 5793e989283303e8844ca5c5214b15a3ac2961fa..23ca4e00a6581c7d8070f2318babdb7f3e207247 100755 --- a/lib/python/qmk/cli/compile.py +++ b/lib/python/qmk/cli/compile.py @@ -2,17 +2,19 @@ You can compile a keymap already in the repo or using a QMK Configurator export. """ +from argcomplete.completers import FilesCompleter from milc import cli import qmk.path from qmk.decorators import automagic_keyboard, automagic_keymap from qmk.commands import compile_configurator_json, create_make_command, parse_configurator_json -from qmk.keyboard import keyboard_folder +from qmk.keyboard import keyboard_completer, keyboard_folder +from qmk.keymap import keymap_completer -@cli.argument('filename', nargs='?', arg_only=True, type=qmk.path.FileType('r'), help='The configurator export to compile') -@cli.argument('-kb', '--keyboard', type=keyboard_folder, help='The keyboard to build a firmware for. Ignored when a configurator export is supplied.') -@cli.argument('-km', '--keymap', help='The keymap to build a firmware for. Ignored when a configurator export is supplied.') +@cli.argument('filename', nargs='?', arg_only=True, type=qmk.path.FileType('r'), completer=FilesCompleter('.json'), help='The configurator export to compile') +@cli.argument('-kb', '--keyboard', type=keyboard_folder, completer=keyboard_completer, help='The keyboard to build a firmware for. Ignored when a configurator export is supplied.') +@cli.argument('-km', '--keymap', completer=keymap_completer, help='The keymap to build a firmware for. Ignored when a configurator export is supplied.') @cli.argument('-n', '--dry-run', arg_only=True, action='store_true', help="Don't actually build, just show the make command to be run.") @cli.argument('-j', '--parallel', type=int, default=1, help="Set the number of parallel make jobs to run.") @cli.argument('-e', '--env', arg_only=True, action='append', default=[], help="Set a variable to be passed to make. May be passed multiple times.") diff --git a/lib/python/qmk/cli/flash.py b/lib/python/qmk/cli/flash.py index c9273c3f9888f957847efb9d77b5f9b1f8d7bd7b..1b678406169a2c20e41a5d247b1295d44033c02c 100644 --- a/lib/python/qmk/cli/flash.py +++ b/lib/python/qmk/cli/flash.py @@ -4,12 +4,13 @@ You can compile a keymap already in the repo or using a QMK Configurator export. A bootloader must be specified. """ +from argcomplete.completers import FilesCompleter from milc import cli import qmk.path from qmk.decorators import automagic_keyboard, automagic_keymap from qmk.commands import compile_configurator_json, create_make_command, parse_configurator_json -from qmk.keyboard import keyboard_folder +from qmk.keyboard import keyboard_completer, keyboard_folder def print_bootloader_help(): @@ -30,11 +31,11 @@ def print_bootloader_help(): cli.echo('For more info, visit https://docs.qmk.fm/#/flashing') -@cli.argument('filename', nargs='?', arg_only=True, type=qmk.path.FileType('r'), help='The configurator export JSON to compile.') +@cli.argument('filename', nargs='?', arg_only=True, type=qmk.path.FileType('r'), completer=FilesCompleter('.json'), help='The configurator export JSON to compile.') @cli.argument('-b', '--bootloaders', action='store_true', help='List the available bootloaders.') @cli.argument('-bl', '--bootloader', default='flash', help='The flash command, corresponding to qmk\'s make options of bootloaders.') @cli.argument('-km', '--keymap', help='The keymap to build a firmware for. Use this if you dont have a configurator file. Ignored when a configurator file is supplied.') -@cli.argument('-kb', '--keyboard', type=keyboard_folder, help='The keyboard to build a firmware for. Use this if you dont have a configurator file. Ignored when a configurator file is supplied.') +@cli.argument('-kb', '--keyboard', type=keyboard_folder, completer=keyboard_completer, help='The keyboard to build a firmware for. Use this if you dont have a configurator file. Ignored when a configurator file is supplied.') @cli.argument('-n', '--dry-run', arg_only=True, action='store_true', help="Don't actually build, just show the make command to be run.") @cli.argument('-j', '--parallel', type=int, default=1, help="Set the number of parallel make jobs to run.") @cli.argument('-e', '--env', arg_only=True, action='append', default=[], help="Set a variable to be passed to make. May be passed multiple times.") diff --git a/lib/python/qmk/cli/generate/config_h.py b/lib/python/qmk/cli/generate/config_h.py index ccea6d7a05da98ee9d0bdad584913cc8d9b02482..54cd5b96a87f4054d299df1dec523e0ce1b2ca77 100755 --- a/lib/python/qmk/cli/generate/config_h.py +++ b/lib/python/qmk/cli/generate/config_h.py @@ -8,7 +8,7 @@ from milc import cli from qmk.decorators import automagic_keyboard, automagic_keymap from qmk.info import info_json from qmk.json_schema import json_load -from qmk.keyboard import keyboard_folder +from qmk.keyboard import keyboard_completer, keyboard_folder from qmk.path import is_keyboard, normpath @@ -75,7 +75,7 @@ def matrix_pins(matrix_pins): @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.argument('-kb', '--keyboard', type=keyboard_folder, help='Keyboard to generate config.h for.') +@cli.argument('-kb', '--keyboard', type=keyboard_folder, completer=keyboard_completer, help='Keyboard to generate config.h for.') @cli.subcommand('Used by the make system to generate info_config.h from info.json', hidden=True) @automagic_keyboard @automagic_keymap diff --git a/lib/python/qmk/cli/generate/dfu_header.py b/lib/python/qmk/cli/generate/dfu_header.py index 6f958b3a3df6323d903edf5a651dfa7912e0a0c8..211ed9991a182df32cdeaaf34e8b51b711a01a91 100644 --- a/lib/python/qmk/cli/generate/dfu_header.py +++ b/lib/python/qmk/cli/generate/dfu_header.py @@ -6,11 +6,12 @@ from milc import cli 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 @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.argument('-kb', '--keyboard', help='Keyboard to generate LUFA Keyboard.h for.') +@cli.argument('-kb', '--keyboard', completer=keyboard_completer, help='Keyboard to generate LUFA Keyboard.h for.') @cli.subcommand('Used by the make system to generate LUFA Keyboard.h from info.json', hidden=True) @automagic_keyboard def generate_dfu_header(cli): diff --git a/lib/python/qmk/cli/generate/info_json.py b/lib/python/qmk/cli/generate/info_json.py index 1af7f04392eda6472b2920322e55983723b0f5ff..8931b68b6ff4fca2f7233f0de3c7f122a77fe34e 100755 --- a/lib/python/qmk/cli/generate/info_json.py +++ b/lib/python/qmk/cli/generate/info_json.py @@ -11,7 +11,7 @@ from qmk.decorators import automagic_keyboard, automagic_keymap from qmk.info import info_json from qmk.json_encoders import InfoJSONEncoder from qmk.json_schema import load_jsonschema -from qmk.keyboard import keyboard_folder +from qmk.keyboard import keyboard_completer, keyboard_folder from qmk.path import is_keyboard @@ -41,7 +41,7 @@ def strip_info_json(kb_info_json): return validator(kb_info_json) -@cli.argument('-kb', '--keyboard', type=keyboard_folder, help='Keyboard to show info for.') +@cli.argument('-kb', '--keyboard', type=keyboard_folder, completer=keyboard_completer, help='Keyboard to show info for.') @cli.argument('-km', '--keymap', help='Show the layers for a JSON keymap too.') @cli.subcommand('Generate an info.json file for a keyboard.', hidden=False if cli.config.user.developer else True) @automagic_keyboard diff --git a/lib/python/qmk/cli/generate/layouts.py b/lib/python/qmk/cli/generate/layouts.py index 7b4394291f6887d9935c5cb1be611ef4faea8d81..ad6946d6cf198f8bd401c8d15f037d41b62a1d61 100755 --- a/lib/python/qmk/cli/generate/layouts.py +++ b/lib/python/qmk/cli/generate/layouts.py @@ -5,7 +5,7 @@ from milc import cli from qmk.constants import COL_LETTERS, ROW_LETTERS from qmk.decorators import automagic_keyboard, automagic_keymap from qmk.info import info_json -from qmk.keyboard import keyboard_folder +from qmk.keyboard import keyboard_completer, keyboard_folder from qmk.path import is_keyboard, normpath usb_properties = { @@ -17,7 +17,7 @@ usb_properties = { @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.argument('-kb', '--keyboard', type=keyboard_folder, help='Keyboard to generate config.h for.') +@cli.argument('-kb', '--keyboard', type=keyboard_folder, completer=keyboard_completer, help='Keyboard to generate config.h for.') @cli.subcommand('Used by the make system to generate layouts.h from info.json', hidden=True) @automagic_keyboard @automagic_keymap diff --git a/lib/python/qmk/cli/generate/rules_mk.py b/lib/python/qmk/cli/generate/rules_mk.py index 91759d26c64f2d3a0f744461d8be93c4177f84ba..41c94e16b554d584a6e66ec3f4ca8f9e6e18aec5 100755 --- a/lib/python/qmk/cli/generate/rules_mk.py +++ b/lib/python/qmk/cli/generate/rules_mk.py @@ -8,7 +8,7 @@ from milc import cli from qmk.decorators import automagic_keyboard, automagic_keymap from qmk.info import info_json from qmk.json_schema import json_load -from qmk.keyboard import keyboard_folder +from qmk.keyboard import keyboard_completer, keyboard_folder from qmk.path import is_keyboard, normpath @@ -39,7 +39,7 @@ def process_mapping_rule(kb_info_json, rules_key, info_dict): @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.argument('-e', '--escape', arg_only=True, action='store_true', help="Escape spaces in quiet mode") -@cli.argument('-kb', '--keyboard', type=keyboard_folder, help='Keyboard to generate config.h for.') +@cli.argument('-kb', '--keyboard', type=keyboard_folder, completer=keyboard_completer, help='Keyboard to generate config.h for.') @cli.subcommand('Used by the make system to generate info_config.h from info.json', hidden=True) @automagic_keyboard @automagic_keymap diff --git a/lib/python/qmk/cli/info.py b/lib/python/qmk/cli/info.py index aac507c1a5d98b44e8adffe3235f62f163cbaa0e..572b305cacb2192e0e393a25742dedee7a209a5b 100755 --- a/lib/python/qmk/cli/info.py +++ b/lib/python/qmk/cli/info.py @@ -10,7 +10,7 @@ from milc import cli from qmk.json_encoders import InfoJSONEncoder from qmk.constants import COL_LETTERS, ROW_LETTERS from qmk.decorators import automagic_keyboard, automagic_keymap -from qmk.keyboard import keyboard_folder, render_layouts, render_layout +from qmk.keyboard import keyboard_completer, keyboard_folder, render_layouts, render_layout from qmk.keymap import locate_keymap from qmk.info import info_json from qmk.path import is_keyboard @@ -124,7 +124,7 @@ def print_text_output(kb_info_json): show_keymap(kb_info_json, False) -@cli.argument('-kb', '--keyboard', type=keyboard_folder, help='Keyboard to show info for.') +@cli.argument('-kb', '--keyboard', type=keyboard_folder, completer=keyboard_completer, help='Keyboard to show info for.') @cli.argument('-km', '--keymap', help='Show the layers for a JSON keymap too.') @cli.argument('-l', '--layouts', action='store_true', help='Render the layouts.') @cli.argument('-m', '--matrix', action='store_true', help='Render the layouts with matrix information.') diff --git a/lib/python/qmk/cli/json2c.py b/lib/python/qmk/cli/json2c.py index 5a2fb96c7886aa7cab510f97f014c4edfc9e3246..a90578c021d791eb77c8f310b4ca78c8c1e130bb 100755 --- a/lib/python/qmk/cli/json2c.py +++ b/lib/python/qmk/cli/json2c.py @@ -2,6 +2,7 @@ """ import json +from argcomplete.completers import FilesCompleter from milc import cli import qmk.keymap @@ -10,7 +11,7 @@ import qmk.path @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='Configurator JSON file') +@cli.argument('filename', type=qmk.path.FileType('r'), arg_only=True, completer=FilesCompleter('.json'), help='Configurator JSON file') @cli.subcommand('Creates a keymap.c from a QMK Configurator export.') def json2c(cli): """Generate a keymap.c from a configurator export. diff --git a/lib/python/qmk/cli/kle2json.py b/lib/python/qmk/cli/kle2json.py index 91499c9af32cdfa61a8c518f746c37b7f7bb5f09..acb75ef4fdb0f06a4ea06c81543816d39312506a 100755 --- a/lib/python/qmk/cli/kle2json.py +++ b/lib/python/qmk/cli/kle2json.py @@ -4,6 +4,7 @@ import json import os from pathlib import Path +from argcomplete.completers import FilesCompleter from milc import cli from kle2xy import KLE2xy @@ -11,7 +12,7 @@ from qmk.converter import kle2qmk from qmk.json_encoders import InfoJSONEncoder -@cli.argument('filename', help='The KLE raw txt to convert') +@cli.argument('filename', completer=FilesCompleter('.json'), help='The KLE raw txt to convert') @cli.argument('-f', '--force', action='store_true', help='Flag to overwrite current info.json') @cli.subcommand('Convert a KLE layout to a Configurator JSON', hidden=False if cli.config.user.developer else True) def kle2json(cli): diff --git a/lib/python/qmk/cli/lint.py b/lib/python/qmk/cli/lint.py index 74467021e07555e8806ce8d00dde29e1097ee06c..a164dba63244d8df8ad53f99d273b2efecd4a4b8 100644 --- a/lib/python/qmk/cli/lint.py +++ b/lib/python/qmk/cli/lint.py @@ -4,12 +4,13 @@ from milc import cli from qmk.decorators import automagic_keyboard, automagic_keymap from qmk.info import info_json +from qmk.keyboard import keyboard_completer from qmk.keymap import locate_keymap from qmk.path import is_keyboard, keyboard @cli.argument('--strict', action='store_true', help='Treat warnings as errors.') -@cli.argument('-kb', '--keyboard', help='The keyboard to check.') +@cli.argument('-kb', '--keyboard', completer=keyboard_completer, help='The keyboard to check.') @cli.argument('-km', '--keymap', help='The keymap to check.') @cli.subcommand('Check keyboard and keymap for common mistakes.') @automagic_keyboard diff --git a/lib/python/qmk/cli/list/keymaps.py b/lib/python/qmk/cli/list/keymaps.py index 7c0ad43997de7b3c0d2a60427622ed40212503db..d79ab75b58226464ffb515ecf72ad013db19d8f8 100644 --- a/lib/python/qmk/cli/list/keymaps.py +++ b/lib/python/qmk/cli/list/keymaps.py @@ -4,10 +4,10 @@ from milc import cli import qmk.keymap from qmk.decorators import automagic_keyboard -from qmk.keyboard import keyboard_folder +from qmk.keyboard import keyboard_completer, keyboard_folder -@cli.argument("-kb", "--keyboard", type=keyboard_folder, help="Specify keyboard name. Example: 1upkeyboards/1up60hse") +@cli.argument("-kb", "--keyboard", type=keyboard_folder, completer=keyboard_completer, help="Specify keyboard name. Example: 1upkeyboards/1up60hse") @cli.subcommand("List the keymaps for a specific keyboard") @automagic_keyboard def list_keymaps(cli): diff --git a/lib/python/qmk/cli/new/keymap.py b/lib/python/qmk/cli/new/keymap.py index ea98a287c165f80c6de0b16dfa681015dda3d6da..60cb743cb66bd37735ee123e6cdce267975a7e08 100755 --- a/lib/python/qmk/cli/new/keymap.py +++ b/lib/python/qmk/cli/new/keymap.py @@ -5,11 +5,11 @@ from pathlib import Path import qmk.path from qmk.decorators import automagic_keyboard, automagic_keymap -from qmk.keyboard import keyboard_folder +from qmk.keyboard import keyboard_completer, keyboard_folder from milc import cli -@cli.argument('-kb', '--keyboard', type=keyboard_folder, help='Specify keyboard name. Example: 1upkeyboards/1up60hse') +@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') @cli.subcommand('Creates a new keymap for the keyboard of your choosing') @automagic_keyboard diff --git a/lib/python/qmk/decorators.py b/lib/python/qmk/decorators.py index 629402b0957c9c49e9bc7e49c2b00bff7ecb45fd..8d43ae980f58443fd07cda74e387da3b7f20d35a 100644 --- a/lib/python/qmk/decorators.py +++ b/lib/python/qmk/decorators.py @@ -1,13 +1,12 @@ """Helpful decorators that subcommands can use. """ import functools -from pathlib import Path from time import monotonic from milc import cli -from qmk.keymap import is_keymap_dir -from qmk.path import is_keyboard, under_qmk_firmware +from qmk.keyboard import find_keyboard_from_dir +from qmk.keymap import find_keymap_from_dir def automagic_keyboard(func): @@ -17,27 +16,13 @@ def automagic_keyboard(func): """ @functools.wraps(func) def wrapper(*args, **kwargs): - # Check to make sure their copy of MILC supports config_source - if not hasattr(cli, 'config_source'): - cli.log.error("This subcommand requires a newer version of the QMK CLI. Please upgrade using `pip3 install --upgrade qmk` or your package manager.") - exit(1) - # Ensure that `--keyboard` was not passed and CWD is under `qmk_firmware/keyboards` if cli.config_source[cli._entrypoint.__name__]['keyboard'] != 'argument': - relative_cwd = under_qmk_firmware() - - if relative_cwd and len(relative_cwd.parts) > 1 and relative_cwd.parts[0] == 'keyboards': - # Attempt to extract the keyboard name from the current directory - current_path = Path('/'.join(relative_cwd.parts[1:])) - - if 'keymaps' in current_path.parts: - # Strip current_path of anything after `keymaps` - keymap_index = len(current_path.parts) - current_path.parts.index('keymaps') - 1 - current_path = current_path.parents[keymap_index] + keyboard = find_keyboard_from_dir() - if is_keyboard(current_path): - cli.config[cli._entrypoint.__name__]['keyboard'] = str(current_path) - cli.config_source[cli._entrypoint.__name__]['keyboard'] = 'keyboard_directory' + if keyboard: + cli.config[cli._entrypoint.__name__]['keyboard'] = keyboard + cli.config_source[cli._entrypoint.__name__]['keyboard'] = 'keyboard_directory' return func(*args, **kwargs) @@ -51,36 +36,13 @@ def automagic_keymap(func): """ @functools.wraps(func) def wrapper(*args, **kwargs): - # Check to make sure their copy of MILC supports config_source - if not hasattr(cli, 'config_source'): - cli.log.error("This subcommand requires a newer version of the QMK CLI. Please upgrade using `pip3 install --upgrade qmk` or your package manager.") - exit(1) - # Ensure that `--keymap` was not passed and that we're under `qmk_firmware` if cli.config_source[cli._entrypoint.__name__]['keymap'] != 'argument': - relative_cwd = under_qmk_firmware() - - if relative_cwd and len(relative_cwd.parts) > 1: - # If we're in `qmk_firmware/keyboards` and `keymaps` is in our path, try to find the keyboard name. - if relative_cwd.parts[0] == 'keyboards' and 'keymaps' in relative_cwd.parts: - current_path = Path('/'.join(relative_cwd.parts[1:])) # Strip 'keyboards' from the front - - if 'keymaps' in current_path.parts and current_path.name != 'keymaps': - while current_path.parent.name != 'keymaps': - current_path = current_path.parent - cli.config[cli._entrypoint.__name__]['keymap'] = current_path.name - cli.config_source[cli._entrypoint.__name__]['keymap'] = 'keymap_directory' - - # If we're in `qmk_firmware/layouts` guess the name from the community keymap they're in - elif relative_cwd.parts[0] == 'layouts' and is_keymap_dir(relative_cwd): - cli.config[cli._entrypoint.__name__]['keymap'] = relative_cwd.name - cli.config_source[cli._entrypoint.__name__]['keymap'] = 'layouts_directory' - - # If we're in `qmk_firmware/users` guess the name from the userspace they're in - elif relative_cwd.parts[0] == 'users': - # Guess the keymap name based on which userspace they're in - cli.config[cli._entrypoint.__name__]['keymap'] = relative_cwd.parts[1] - cli.config_source[cli._entrypoint.__name__]['keymap'] = 'users_directory' + keymap_name, keymap_type = find_keymap_from_dir() + + if keymap_name: + cli.config[cli._entrypoint.__name__]['keymap'] = keymap_name + cli.config_source[cli._entrypoint.__name__]['keymap'] = keymap_type return func(*args, **kwargs) diff --git a/lib/python/qmk/keyboard.py b/lib/python/qmk/keyboard.py index 89f9346c40664461630b0ae7b82787dce2884ee0..0168d17ef394a790c85b23e1dfd4c33df09618b5 100644 --- a/lib/python/qmk/keyboard.py +++ b/lib/python/qmk/keyboard.py @@ -9,7 +9,7 @@ from glob import glob from qmk.c_parse import parse_config_h_file from qmk.json_schema import json_load from qmk.makefile import parse_rules_mk_file -from qmk.path import is_keyboard +from qmk.path import is_keyboard, under_qmk_firmware BOX_DRAWING_CHARACTERS = { "unicode": { @@ -33,6 +33,24 @@ BOX_DRAWING_CHARACTERS = { base_path = os.path.join(os.getcwd(), "keyboards") + os.path.sep +def find_keyboard_from_dir(): + """Returns a keyboard name based on the user's current directory. + """ + relative_cwd = under_qmk_firmware() + + if relative_cwd and len(relative_cwd.parts) > 1 and relative_cwd.parts[0] == 'keyboards': + # Attempt to extract the keyboard name from the current directory + current_path = Path('/'.join(relative_cwd.parts[1:])) + + if 'keymaps' in current_path.parts: + # Strip current_path of anything after `keymaps` + keymap_index = len(current_path.parts) - current_path.parts.index('keymaps') - 1 + current_path = current_path.parents[keymap_index] + + if is_keyboard(current_path): + return str(current_path) + + def keyboard_folder(keyboard): """Returns the actual keyboard folder. @@ -61,6 +79,12 @@ def _find_name(path): return path.replace(base_path, "").replace(os.path.sep + "rules.mk", "") +def keyboard_completer(prefix, action, parser, parsed_args): + """Returns a list of keyboards for tab completion. + """ + return list_keyboards() + + def list_keyboards(): """Returns a list of all keyboards. """ diff --git a/lib/python/qmk/keymap.py b/lib/python/qmk/keymap.py index d8495c38bcf051b6f7a925cbbd17be975eab9569..4ad9ffb5913257f0a4413b4e26a9e85db32e7d09 100644 --- a/lib/python/qmk/keymap.py +++ b/lib/python/qmk/keymap.py @@ -1,19 +1,19 @@ """Functions that help you work with QMK keymaps. """ -from pathlib import Path import json import subprocess import sys +from pathlib import Path +import argcomplete +from milc import cli from pygments.lexers.c_cpp import CLexer from pygments.token import Token from pygments import lex -from milc import cli - -from qmk.keyboard import rules_mk import qmk.path import qmk.commands +from qmk.keyboard import find_keyboard_from_dir, rules_mk # The `keymap.c` template to use when a keyboard doesn't have its own DEFAULT_KEYMAP_C = """#include QMK_KEYBOARD_H @@ -74,6 +74,54 @@ def _strip_any(keycode): return keycode +def find_keymap_from_dir(): + """Returns `(keymap_name, source)` for the directory we're currently in. + + """ + relative_cwd = qmk.path.under_qmk_firmware() + + if relative_cwd and len(relative_cwd.parts) > 1: + # If we're in `qmk_firmware/keyboards` and `keymaps` is in our path, try to find the keyboard name. + if relative_cwd.parts[0] == 'keyboards' and 'keymaps' in relative_cwd.parts: + current_path = Path('/'.join(relative_cwd.parts[1:])) # Strip 'keyboards' from the front + + if 'keymaps' in current_path.parts and current_path.name != 'keymaps': + while current_path.parent.name != 'keymaps': + current_path = current_path.parent + + return current_path.name, 'keymap_directory' + + # If we're in `qmk_firmware/layouts` guess the name from the community keymap they're in + elif relative_cwd.parts[0] == 'layouts' and is_keymap_dir(relative_cwd): + return relative_cwd.name, 'layouts_directory' + + # If we're in `qmk_firmware/users` guess the name from the userspace they're in + elif relative_cwd.parts[0] == 'users': + # Guess the keymap name based on which userspace they're in + return relative_cwd.parts[1], 'users_directory' + + return None, None + + +def keymap_completer(prefix, action, parser, parsed_args): + """Returns a list of keymaps for tab completion. + """ + try: + if parsed_args.keyboard: + return list_keymaps(parsed_args.keyboard) + + keyboard = find_keyboard_from_dir() + + if keyboard: + return list_keymaps(keyboard) + + except Exception as e: + argcomplete.warn(f'Error: {e.__class__.__name__}: {str(e)}') + return [] + + return [] + + def is_keymap_dir(keymap, c=True, json=True, additional_files=None): """Return True if Path object `keymap` has a keymap file inside. diff --git a/requirements.txt b/requirements.txt index f74cb73cb727bb86deb2ec2ce7afc728be7da048..8553e2c3f02d6de5fee019974d605f96a3cc2f2c 100644 --- a/requirements.txt +++ b/requirements.txt @@ -5,5 +5,5 @@ colorama dotty-dict hjson jsonschema>=3 -milc>=1.1.0 +milc>=1.3.0 pygments diff --git a/util/qmk_tab_complete.sh b/util/qmk_tab_complete.sh new file mode 100644 index 0000000000000000000000000000000000000000..ebcb5536acf06b7ccf3ca0b6f331822f57a8f76c --- /dev/null +++ b/util/qmk_tab_complete.sh @@ -0,0 +1,2 @@ +# Register qmk with tab completion +eval "$(register-python-argcomplete --no-defaults qmk)"