Browse Source

Add support for tab completion (#12411)

* Add support for tab completion

* make flake8 happy

* Add documentation
pull/12595/head 0.12.35
Zach White 3 years ago
committed by GitHub
parent
commit
588bcdc8ca
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
23 changed files with 169 additions and 86 deletions
  1. +1
    -0
      docs/_summary.md
  2. +27
    -0
      docs/cli_tab_complete.md
  3. +12
    -2
      lib/python/qmk/cli/__init__.py
  4. +4
    -3
      lib/python/qmk/cli/c2json.py
  5. +2
    -1
      lib/python/qmk/cli/cformat.py
  6. +6
    -4
      lib/python/qmk/cli/compile.py
  7. +4
    -3
      lib/python/qmk/cli/flash.py
  8. +2
    -2
      lib/python/qmk/cli/generate/config_h.py
  9. +2
    -1
      lib/python/qmk/cli/generate/dfu_header.py
  10. +2
    -2
      lib/python/qmk/cli/generate/info_json.py
  11. +2
    -2
      lib/python/qmk/cli/generate/layouts.py
  12. +2
    -2
      lib/python/qmk/cli/generate/rules_mk.py
  13. +2
    -2
      lib/python/qmk/cli/info.py
  14. +2
    -1
      lib/python/qmk/cli/json2c.py
  15. +2
    -1
      lib/python/qmk/cli/kle2json.py
  16. +2
    -1
      lib/python/qmk/cli/lint.py
  17. +2
    -2
      lib/python/qmk/cli/list/keymaps.py
  18. +2
    -2
      lib/python/qmk/cli/new/keymap.py
  19. +11
    -49
      lib/python/qmk/decorators.py
  20. +25
    -1
      lib/python/qmk/keyboard.py
  21. +52
    -4
      lib/python/qmk/keymap.py
  22. +1
    -1
      requirements.txt
  23. +2
    -0
      util/qmk_tab_complete.sh

+ 1
- 0
docs/_summary.md View File

@ -29,6 +29,7 @@
* [Overview](cli.md) * [Overview](cli.md)
* [Configuration](cli_configuration.md) * [Configuration](cli_configuration.md)
* [Commands](cli_commands.md) * [Commands](cli_commands.md)
* [Tab Completion](cli_tab_complete.md)
* Using QMK * Using QMK
* Guides * Guides


+ 27
- 0
docs/cli_tab_complete.md View File

@ -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

+ 12
- 2
lib/python/qmk/cli/__init__.py View File

@ -4,7 +4,7 @@ We list each subcommand here explicitly because all the reliable ways of searchi
""" """
import sys import sys
from milc import cli
from milc import cli, __VERSION__
from . import c2json from . import c2json
from . import cformat from . import cformat
@ -47,5 +47,15 @@ from . import pytest
# void: 3.9 # void: 3.9
if sys.version_info[0] != 3 or sys.version_info[1] < 7: 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) exit(127)

+ 4
- 3
lib/python/qmk/cli/c2json.py View File

@ -2,20 +2,21 @@
""" """
import json import json
from argcomplete.completers import FilesCompleter
from milc import cli from milc import cli
import qmk.keymap import qmk.keymap
import qmk.path import qmk.path
from qmk.json_encoders import InfoJSONEncoder 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('--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('-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('-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('-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.') @cli.subcommand('Creates a keymap.json from a keymap.c file.')
def c2json(cli): def c2json(cli):
"""Generate a keymap.json from a keymap.c file. """Generate a keymap.json from a keymap.c file.


+ 2
- 1
lib/python/qmk/cli/cformat.py View File

@ -3,6 +3,7 @@
import subprocess import subprocess
from shutil import which from shutil import which
from argcomplete.completers import FilesCompleter
from milc import cli from milc import cli
from qmk.path import normpath 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('-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('-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) @cli.subcommand("Format C code according to QMK's style.", hidden=False if cli.config.user.developer else True)
def cformat(cli): def cformat(cli):
"""Format C code according to QMK's style. """Format C code according to QMK's style.


+ 6
- 4
lib/python/qmk/cli/compile.py View File

@ -2,17 +2,19 @@
You can compile a keymap already in the repo or using a QMK Configurator export. You can compile a keymap already in the repo or using a QMK Configurator export.
""" """
from argcomplete.completers import FilesCompleter
from milc import cli from milc import cli
import qmk.path import qmk.path
from qmk.decorators import automagic_keyboard, automagic_keymap from qmk.decorators import automagic_keyboard, automagic_keymap
from qmk.commands import compile_configurator_json, create_make_command, parse_configurator_json 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('-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('-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.") @cli.argument('-e', '--env', arg_only=True, action='append', default=[], help="Set a variable to be passed to make. May be passed multiple times.")


+ 4
- 3
lib/python/qmk/cli/flash.py View File

@ -4,12 +4,13 @@ You can compile a keymap already in the repo or using a QMK Configurator export.
A bootloader must be specified. A bootloader must be specified.
""" """
from argcomplete.completers import FilesCompleter
from milc import cli from milc import cli
import qmk.path import qmk.path
from qmk.decorators import automagic_keyboard, automagic_keymap from qmk.decorators import automagic_keyboard, automagic_keymap
from qmk.commands import compile_configurator_json, create_make_command, parse_configurator_json 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(): def print_bootloader_help():
@ -30,11 +31,11 @@ def print_bootloader_help():
cli.echo('For more info, visit https://docs.qmk.fm/#/flashing') 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('-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('-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('-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('-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('-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.") @cli.argument('-e', '--env', arg_only=True, action='append', default=[], help="Set a variable to be passed to make. May be passed multiple times.")


+ 2
- 2
lib/python/qmk/cli/generate/config_h.py View File

@ -8,7 +8,7 @@ from milc import cli
from qmk.decorators import automagic_keyboard, automagic_keymap from qmk.decorators import automagic_keyboard, automagic_keymap
from qmk.info import info_json from qmk.info import info_json
from qmk.json_schema import json_load 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 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('-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('-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) @cli.subcommand('Used by the make system to generate info_config.h from info.json', hidden=True)
@automagic_keyboard @automagic_keyboard
@automagic_keymap @automagic_keymap


+ 2
- 1
lib/python/qmk/cli/generate/dfu_header.py View File

@ -6,11 +6,12 @@ from milc import cli
from qmk.decorators import automagic_keyboard from qmk.decorators import automagic_keyboard
from qmk.info import info_json from qmk.info import info_json
from qmk.path import is_keyboard, normpath 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('-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('-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) @cli.subcommand('Used by the make system to generate LUFA Keyboard.h from info.json', hidden=True)
@automagic_keyboard @automagic_keyboard
def generate_dfu_header(cli): def generate_dfu_header(cli):


+ 2
- 2
lib/python/qmk/cli/generate/info_json.py View File

@ -11,7 +11,7 @@ from qmk.decorators import automagic_keyboard, automagic_keymap
from qmk.info import info_json from qmk.info import info_json
from qmk.json_encoders import InfoJSONEncoder from qmk.json_encoders import InfoJSONEncoder
from qmk.json_schema import load_jsonschema 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 from qmk.path import is_keyboard
@ -41,7 +41,7 @@ def strip_info_json(kb_info_json):
return validator(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.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) @cli.subcommand('Generate an info.json file for a keyboard.', hidden=False if cli.config.user.developer else True)
@automagic_keyboard @automagic_keyboard


+ 2
- 2
lib/python/qmk/cli/generate/layouts.py View File

@ -5,7 +5,7 @@ from milc import cli
from qmk.constants import COL_LETTERS, ROW_LETTERS from qmk.constants import COL_LETTERS, ROW_LETTERS
from qmk.decorators import automagic_keyboard, automagic_keymap from qmk.decorators import automagic_keyboard, automagic_keymap
from qmk.info import info_json 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 from qmk.path import is_keyboard, normpath
usb_properties = { usb_properties = {
@ -17,7 +17,7 @@ usb_properties = {
@cli.argument('-o', '--output', arg_only=True, type=normpath, help='File to write to') @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('-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) @cli.subcommand('Used by the make system to generate layouts.h from info.json', hidden=True)
@automagic_keyboard @automagic_keyboard
@automagic_keymap @automagic_keymap


+ 2
- 2
lib/python/qmk/cli/generate/rules_mk.py View File

@ -8,7 +8,7 @@ from milc import cli
from qmk.decorators import automagic_keyboard, automagic_keymap from qmk.decorators import automagic_keyboard, automagic_keymap
from qmk.info import info_json from qmk.info import info_json
from qmk.json_schema import json_load 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 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('-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('-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('-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) @cli.subcommand('Used by the make system to generate info_config.h from info.json', hidden=True)
@automagic_keyboard @automagic_keyboard
@automagic_keymap @automagic_keymap


+ 2
- 2
lib/python/qmk/cli/info.py View File

@ -10,7 +10,7 @@ from milc import cli
from qmk.json_encoders import InfoJSONEncoder from qmk.json_encoders import InfoJSONEncoder
from qmk.constants import COL_LETTERS, ROW_LETTERS from qmk.constants import COL_LETTERS, ROW_LETTERS
from qmk.decorators import automagic_keyboard, automagic_keymap 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.keymap import locate_keymap
from qmk.info import info_json from qmk.info import info_json
from qmk.path import is_keyboard from qmk.path import is_keyboard
@ -124,7 +124,7 @@ def print_text_output(kb_info_json):
show_keymap(kb_info_json, False) 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('-km', '--keymap', help='Show the layers for a JSON keymap too.')
@cli.argument('-l', '--layouts', action='store_true', help='Render the layouts.') @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.') @cli.argument('-m', '--matrix', action='store_true', help='Render the layouts with matrix information.')


+ 2
- 1
lib/python/qmk/cli/json2c.py View File

@ -2,6 +2,7 @@
""" """
import json import json
from argcomplete.completers import FilesCompleter
from milc import cli from milc import cli
import qmk.keymap 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('-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('-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.') @cli.subcommand('Creates a keymap.c from a QMK Configurator export.')
def json2c(cli): def json2c(cli):
"""Generate a keymap.c from a configurator export. """Generate a keymap.c from a configurator export.


+ 2
- 1
lib/python/qmk/cli/kle2json.py View File

@ -4,6 +4,7 @@ import json
import os import os
from pathlib import Path from pathlib import Path
from argcomplete.completers import FilesCompleter
from milc import cli from milc import cli
from kle2xy import KLE2xy from kle2xy import KLE2xy
@ -11,7 +12,7 @@ from qmk.converter import kle2qmk
from qmk.json_encoders import InfoJSONEncoder 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.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) @cli.subcommand('Convert a KLE layout to a Configurator JSON', hidden=False if cli.config.user.developer else True)
def kle2json(cli): def kle2json(cli):


+ 2
- 1
lib/python/qmk/cli/lint.py View File

@ -4,12 +4,13 @@ from milc import cli
from qmk.decorators import automagic_keyboard, automagic_keymap from qmk.decorators import automagic_keyboard, automagic_keymap
from qmk.info import info_json from qmk.info import info_json
from qmk.keyboard import keyboard_completer
from qmk.keymap import locate_keymap from qmk.keymap import locate_keymap
from qmk.path import is_keyboard, keyboard from qmk.path import is_keyboard, keyboard
@cli.argument('--strict', action='store_true', help='Treat warnings as errors.') @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.argument('-km', '--keymap', help='The keymap to check.')
@cli.subcommand('Check keyboard and keymap for common mistakes.') @cli.subcommand('Check keyboard and keymap for common mistakes.')
@automagic_keyboard @automagic_keyboard


+ 2
- 2
lib/python/qmk/cli/list/keymaps.py View File

@ -4,10 +4,10 @@ from milc import cli
import qmk.keymap import qmk.keymap
from qmk.decorators import automagic_keyboard 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") @cli.subcommand("List the keymaps for a specific keyboard")
@automagic_keyboard @automagic_keyboard
def list_keymaps(cli): def list_keymaps(cli):


+ 2
- 2
lib/python/qmk/cli/new/keymap.py View File

@ -5,11 +5,11 @@ from pathlib import Path
import qmk.path import qmk.path
from qmk.decorators import automagic_keyboard, automagic_keymap 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 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.argument('-km', '--keymap', help='Specify the name for the new keymap directory')
@cli.subcommand('Creates a new keymap for the keyboard of your choosing') @cli.subcommand('Creates a new keymap for the keyboard of your choosing')
@automagic_keyboard @automagic_keyboard


+ 11
- 49
lib/python/qmk/decorators.py View File

@ -1,13 +1,12 @@
"""Helpful decorators that subcommands can use. """Helpful decorators that subcommands can use.
""" """
import functools import functools
from pathlib import Path
from time import monotonic from time import monotonic
from milc import cli 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): def automagic_keyboard(func):
@ -17,27 +16,13 @@ def automagic_keyboard(func):
""" """
@functools.wraps(func) @functools.wraps(func)
def wrapper(*args, **kwargs): 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` # Ensure that `--keyboard` was not passed and CWD is under `qmk_firmware/keyboards`
if cli.config_source[cli._entrypoint.__name__]['keyboard'] != 'argument': 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) return func(*args, **kwargs)
@ -51,36 +36,13 @@ def automagic_keymap(func):
""" """
@functools.wraps(func) @functools.wraps(func)
def wrapper(*args, **kwargs): 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` # Ensure that `--keymap` was not passed and that we're under `qmk_firmware`
if cli.config_source[cli._entrypoint.__name__]['keymap'] != 'argument': 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) return func(*args, **kwargs)


+ 25
- 1
lib/python/qmk/keyboard.py View File

@ -9,7 +9,7 @@ from glob import glob
from qmk.c_parse import parse_config_h_file from qmk.c_parse import parse_config_h_file
from qmk.json_schema import json_load from qmk.json_schema import json_load
from qmk.makefile import parse_rules_mk_file 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 = { BOX_DRAWING_CHARACTERS = {
"unicode": { "unicode": {
@ -33,6 +33,24 @@ BOX_DRAWING_CHARACTERS = {
base_path = os.path.join(os.getcwd(), "keyboards") + os.path.sep 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): def keyboard_folder(keyboard):
"""Returns the actual keyboard folder. """Returns the actual keyboard folder.
@ -61,6 +79,12 @@ def _find_name(path):
return path.replace(base_path, "").replace(os.path.sep + "rules.mk", "") 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(): def list_keyboards():
"""Returns a list of all keyboards. """Returns a list of all keyboards.
""" """


+ 52
- 4
lib/python/qmk/keymap.py View File

@ -1,19 +1,19 @@
"""Functions that help you work with QMK keymaps. """Functions that help you work with QMK keymaps.
""" """
from pathlib import Path
import json import json
import subprocess import subprocess
import sys import sys
from pathlib import Path
import argcomplete
from milc import cli
from pygments.lexers.c_cpp import CLexer from pygments.lexers.c_cpp import CLexer
from pygments.token import Token from pygments.token import Token
from pygments import lex from pygments import lex
from milc import cli
from qmk.keyboard import rules_mk
import qmk.path import qmk.path
import qmk.commands 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 # The `keymap.c` template to use when a keyboard doesn't have its own
DEFAULT_KEYMAP_C = """#include QMK_KEYBOARD_H DEFAULT_KEYMAP_C = """#include QMK_KEYBOARD_H
@ -74,6 +74,54 @@ def _strip_any(keycode):
return 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): def is_keymap_dir(keymap, c=True, json=True, additional_files=None):
"""Return True if Path object `keymap` has a keymap file inside. """Return True if Path object `keymap` has a keymap file inside.


+ 1
- 1
requirements.txt View File

@ -5,5 +5,5 @@ colorama
dotty-dict dotty-dict
hjson hjson
jsonschema>=3 jsonschema>=3
milc>=1.1.0
milc>=1.3.0
pygments pygments

+ 2
- 0
util/qmk_tab_complete.sh View File

@ -0,0 +1,2 @@
# Register qmk with tab completion
eval "$(register-python-argcomplete --no-defaults qmk)"

Loading…
Cancel
Save