aboutsummaryrefslogtreecommitdiff
path: root/lib/python/qmk
diff options
context:
space:
mode:
authorZach White <skullydazed@gmail.com>2021-04-14 19:00:22 -0700
committerGitHub <noreply@github.com>2021-04-14 19:00:22 -0700
commit588bcdc8ca212b195a428fc43766a59a9252c08d (patch)
tree4867ef610b2178d51002063bd4913e806f771543 /lib/python/qmk
parentb33e6793de6c5f5124ee88fb3eb62d8f54f74940 (diff)
downloadqmk_firmware-588bcdc8ca212b195a428fc43766a59a9252c08d.tar.gz
qmk_firmware-588bcdc8ca212b195a428fc43766a59a9252c08d.zip
Add support for tab completion (#12411)
* Add support for tab completion * make flake8 happy * Add documentation
Diffstat (limited to 'lib/python/qmk')
-rw-r--r--lib/python/qmk/cli/__init__.py14
-rw-r--r--lib/python/qmk/cli/c2json.py7
-rw-r--r--lib/python/qmk/cli/cformat.py3
-rwxr-xr-xlib/python/qmk/cli/compile.py10
-rw-r--r--lib/python/qmk/cli/flash.py7
-rwxr-xr-xlib/python/qmk/cli/generate/config_h.py4
-rw-r--r--lib/python/qmk/cli/generate/dfu_header.py3
-rwxr-xr-xlib/python/qmk/cli/generate/info_json.py4
-rwxr-xr-xlib/python/qmk/cli/generate/layouts.py4
-rwxr-xr-xlib/python/qmk/cli/generate/rules_mk.py4
-rwxr-xr-xlib/python/qmk/cli/info.py4
-rwxr-xr-xlib/python/qmk/cli/json2c.py3
-rwxr-xr-xlib/python/qmk/cli/kle2json.py3
-rw-r--r--lib/python/qmk/cli/lint.py3
-rw-r--r--lib/python/qmk/cli/list/keymaps.py4
-rwxr-xr-xlib/python/qmk/cli/new/keymap.py4
-rw-r--r--lib/python/qmk/decorators.py60
-rw-r--r--lib/python/qmk/keyboard.py26
-rw-r--r--lib/python/qmk/keymap.py56
19 files changed, 138 insertions, 85 deletions
diff --git a/lib/python/qmk/cli/__init__.py b/lib/python/qmk/cli/__init__.py
index 1349e68a9..f7df90811 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
4""" 4"""
5import sys 5import sys
6 6
7from milc import cli 7from milc import cli, __VERSION__
8 8
9from . import c2json 9from . import c2json
10from . import cformat 10from . import cformat
@@ -47,5 +47,15 @@ from . import pytest
47# void: 3.9 47# void: 3.9
48 48
49if sys.version_info[0] != 3 or sys.version_info[1] < 7: 49if sys.version_info[0] != 3 or sys.version_info[1] < 7:
50 cli.log.error('Your Python is too old! Please upgrade to Python 3.7 or later.') 50 print('Error: Your Python is too old! Please upgrade to Python 3.7 or later.')
51 exit(127)
52
53milc_version = __VERSION__.split('.')
54
55if int(milc_version[0]) < 2 and int(milc_version[1]) < 3:
56 from pathlib import Path
57
58 requirements = Path('requirements.txt').resolve()
59
60 print(f'Your MILC library is too old! Please upgrade: python3 -m pip install -U -r {str(requirements)}')
51 exit(127) 61 exit(127)
diff --git a/lib/python/qmk/cli/c2json.py b/lib/python/qmk/cli/c2json.py
index 1fa833b64..e66b0a1b5 100644
--- a/lib/python/qmk/cli/c2json.py
+++ b/lib/python/qmk/cli/c2json.py
@@ -2,20 +2,21 @@
2""" 2"""
3import json 3import json
4 4
5from argcomplete.completers import FilesCompleter
5from milc import cli 6from milc import cli
6 7
7import qmk.keymap 8import qmk.keymap
8import qmk.path 9import qmk.path
9from qmk.json_encoders import InfoJSONEncoder 10from qmk.json_encoders import InfoJSONEncoder
10from qmk.keyboard import keyboard_folder 11from qmk.keyboard import keyboard_completer, keyboard_folder
11 12
12 13
13@cli.argument('--no-cpp', arg_only=True, action='store_false', help='Do not use \'cpp\' on keymap.c') 14@cli.argument('--no-cpp', arg_only=True, action='store_false', help='Do not use \'cpp\' on keymap.c')
14@cli.argument('-o', '--output', arg_only=True, type=qmk.path.normpath, help='File to write to') 15@cli.argument('-o', '--output', arg_only=True, type=qmk.path.normpath, help='File to write to')
15@cli.argument('-q', '--quiet', arg_only=True, action='store_true', help="Quiet mode, only output error messages") 16@cli.argument('-q', '--quiet', arg_only=True, action='store_true', help="Quiet mode, only output error messages")
16@cli.argument('-kb', '--keyboard', arg_only=True, type=keyboard_folder, required=True, help='The keyboard\'s name') 17@cli.argument('-kb', '--keyboard', arg_only=True, type=keyboard_folder, completer=keyboard_completer, required=True, help='The keyboard\'s name')
17@cli.argument('-km', '--keymap', arg_only=True, required=True, help='The keymap\'s name') 18@cli.argument('-km', '--keymap', arg_only=True, required=True, help='The keymap\'s name')
18@cli.argument('filename', arg_only=True, help='keymap.c file') 19@cli.argument('filename', arg_only=True, completer=FilesCompleter('.c'), help='keymap.c file')
19@cli.subcommand('Creates a keymap.json from a keymap.c file.') 20@cli.subcommand('Creates a keymap.json from a keymap.c file.')
20def c2json(cli): 21def c2json(cli):
21 """Generate a keymap.json from a keymap.c file. 22 """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 c7e93b2ab..d0d3b3b0a 100644
--- a/lib/python/qmk/cli/cformat.py
+++ b/lib/python/qmk/cli/cformat.py
@@ -3,6 +3,7 @@
3import subprocess 3import subprocess
4from shutil import which 4from shutil import which
5 5
6from argcomplete.completers import FilesCompleter
6from milc import cli 7from milc import cli
7 8
8from qmk.path import normpath 9from qmk.path import normpath
@@ -33,7 +34,7 @@ def cformat_run(files, all_files):
33 34
34@cli.argument('-a', '--all-files', arg_only=True, action='store_true', help='Format all core files.') 35@cli.argument('-a', '--all-files', arg_only=True, action='store_true', help='Format all core files.')
35@cli.argument('-b', '--base-branch', default='origin/master', help='Branch to compare to diffs to.') 36@cli.argument('-b', '--base-branch', default='origin/master', help='Branch to compare to diffs to.')
36@cli.argument('files', nargs='*', arg_only=True, help='Filename(s) to format.') 37@cli.argument('files', nargs='*', arg_only=True, completer=FilesCompleter('.c'), help='Filename(s) to format.')
37@cli.subcommand("Format C code according to QMK's style.", hidden=False if cli.config.user.developer else True) 38@cli.subcommand("Format C code according to QMK's style.", hidden=False if cli.config.user.developer else True)
38def cformat(cli): 39def cformat(cli):
39 """Format C code according to QMK's style. 40 """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 5793e9892..23ca4e00a 100755
--- a/lib/python/qmk/cli/compile.py
+++ b/lib/python/qmk/cli/compile.py
@@ -2,17 +2,19 @@
2 2
3You can compile a keymap already in the repo or using a QMK Configurator export. 3You can compile a keymap already in the repo or using a QMK Configurator export.
4""" 4"""
5from argcomplete.completers import FilesCompleter
5from milc import cli 6from milc import cli
6 7
7import qmk.path 8import qmk.path
8from qmk.decorators import automagic_keyboard, automagic_keymap 9from qmk.decorators import automagic_keyboard, automagic_keymap
9from qmk.commands import compile_configurator_json, create_make_command, parse_configurator_json 10from qmk.commands import compile_configurator_json, create_make_command, parse_configurator_json
10from qmk.keyboard import keyboard_folder 11from qmk.keyboard import keyboard_completer, keyboard_folder
12from qmk.keymap import keymap_completer
11 13
12 14
13@cli.argument('filename', nargs='?', arg_only=True, type=qmk.path.FileType('r'), help='The configurator export to compile') 15@cli.argument('filename', nargs='?', arg_only=True, type=qmk.path.FileType('r'), completer=FilesCompleter('.json'), help='The configurator export to compile')
14@cli.argument('-kb', '--keyboard', type=keyboard_folder, help='The keyboard to build a firmware for. Ignored when a configurator export is supplied.') 16@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.')
15@cli.argument('-km', '--keymap', help='The keymap to build a firmware for. Ignored when a configurator export is supplied.') 17@cli.argument('-km', '--keymap', completer=keymap_completer, help='The keymap to build a firmware for. Ignored when a configurator export is supplied.')
16@cli.argument('-n', '--dry-run', arg_only=True, action='store_true', help="Don't actually build, just show the make command to be run.") 18@cli.argument('-n', '--dry-run', arg_only=True, action='store_true', help="Don't actually build, just show the make command to be run.")
17@cli.argument('-j', '--parallel', type=int, default=1, help="Set the number of parallel make jobs to run.") 19@cli.argument('-j', '--parallel', type=int, default=1, help="Set the number of parallel make jobs to run.")
18@cli.argument('-e', '--env', arg_only=True, action='append', default=[], help="Set a variable to be passed to make. May be passed multiple times.") 20@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 c9273c3f9..1b6784061 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.
4A bootloader must be specified. 4A bootloader must be specified.
5""" 5"""
6 6
7from argcomplete.completers import FilesCompleter
7from milc import cli 8from milc import cli
8 9
9import qmk.path 10import qmk.path
10from qmk.decorators import automagic_keyboard, automagic_keymap 11from qmk.decorators import automagic_keyboard, automagic_keymap
11from qmk.commands import compile_configurator_json, create_make_command, parse_configurator_json 12from qmk.commands import compile_configurator_json, create_make_command, parse_configurator_json
12from qmk.keyboard import keyboard_folder 13from qmk.keyboard import keyboard_completer, keyboard_folder
13 14
14 15
15def print_bootloader_help(): 16def print_bootloader_help():
@@ -30,11 +31,11 @@ def print_bootloader_help():
30 cli.echo('For more info, visit https://docs.qmk.fm/#/flashing') 31 cli.echo('For more info, visit https://docs.qmk.fm/#/flashing')
31 32
32 33
33@cli.argument('filename', nargs='?', arg_only=True, type=qmk.path.FileType('r'), help='The configurator export JSON to compile.') 34@cli.argument('filename', nargs='?', arg_only=True, type=qmk.path.FileType('r'), completer=FilesCompleter('.json'), help='The configurator export JSON to compile.')
34@cli.argument('-b', '--bootloaders', action='store_true', help='List the available bootloaders.') 35@cli.argument('-b', '--bootloaders', action='store_true', help='List the available bootloaders.')
35@cli.argument('-bl', '--bootloader', default='flash', help='The flash command, corresponding to qmk\'s make options of bootloaders.') 36@cli.argument('-bl', '--bootloader', default='flash', help='The flash command, corresponding to qmk\'s make options of bootloaders.')
36@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.') 37@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.')
37@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.') 38@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.')
38@cli.argument('-n', '--dry-run', arg_only=True, action='store_true', help="Don't actually build, just show the make command to be run.") 39@cli.argument('-n', '--dry-run', arg_only=True, action='store_true', help="Don't actually build, just show the make command to be run.")
39@cli.argument('-j', '--parallel', type=int, default=1, help="Set the number of parallel make jobs to run.") 40@cli.argument('-j', '--parallel', type=int, default=1, help="Set the number of parallel make jobs to run.")
40@cli.argument('-e', '--env', arg_only=True, action='append', default=[], help="Set a variable to be passed to make. May be passed multiple times.") 41@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 ccea6d7a0..54cd5b96a 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
8from qmk.decorators import automagic_keyboard, automagic_keymap 8from qmk.decorators import automagic_keyboard, automagic_keymap
9from qmk.info import info_json 9from qmk.info import info_json
10from qmk.json_schema import json_load 10from qmk.json_schema import json_load
11from qmk.keyboard import keyboard_folder 11from qmk.keyboard import keyboard_completer, keyboard_folder
12from qmk.path import is_keyboard, normpath 12from qmk.path import is_keyboard, normpath
13 13
14 14
@@ -75,7 +75,7 @@ def matrix_pins(matrix_pins):
75 75
76@cli.argument('-o', '--output', arg_only=True, type=normpath, help='File to write to') 76@cli.argument('-o', '--output', arg_only=True, type=normpath, help='File to write to')
77@cli.argument('-q', '--quiet', arg_only=True, action='store_true', help="Quiet mode, only output error messages") 77@cli.argument('-q', '--quiet', arg_only=True, action='store_true', help="Quiet mode, only output error messages")
78@cli.argument('-kb', '--keyboard', type=keyboard_folder, help='Keyboard to generate config.h for.') 78@cli.argument('-kb', '--keyboard', type=keyboard_folder, completer=keyboard_completer, help='Keyboard to generate config.h for.')
79@cli.subcommand('Used by the make system to generate info_config.h from info.json', hidden=True) 79@cli.subcommand('Used by the make system to generate info_config.h from info.json', hidden=True)
80@automagic_keyboard 80@automagic_keyboard
81@automagic_keymap 81@automagic_keymap
diff --git a/lib/python/qmk/cli/generate/dfu_header.py b/lib/python/qmk/cli/generate/dfu_header.py
index 6f958b3a3..211ed9991 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
6from qmk.decorators import automagic_keyboard 6from qmk.decorators import automagic_keyboard
7from qmk.info import info_json 7from qmk.info import info_json
8from qmk.path import is_keyboard, normpath 8from qmk.path import is_keyboard, normpath
9from qmk.keyboard import keyboard_completer
9 10
10 11
11@cli.argument('-o', '--output', arg_only=True, type=normpath, help='File to write to') 12@cli.argument('-o', '--output', arg_only=True, type=normpath, help='File to write to')
12@cli.argument('-q', '--quiet', arg_only=True, action='store_true', help="Quiet mode, only output error messages") 13@cli.argument('-q', '--quiet', arg_only=True, action='store_true', help="Quiet mode, only output error messages")
13@cli.argument('-kb', '--keyboard', help='Keyboard to generate LUFA Keyboard.h for.') 14@cli.argument('-kb', '--keyboard', completer=keyboard_completer, help='Keyboard to generate LUFA Keyboard.h for.')
14@cli.subcommand('Used by the make system to generate LUFA Keyboard.h from info.json', hidden=True) 15@cli.subcommand('Used by the make system to generate LUFA Keyboard.h from info.json', hidden=True)
15@automagic_keyboard 16@automagic_keyboard
16def generate_dfu_header(cli): 17def 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 1af7f0439..8931b68b6 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
11from qmk.info import info_json 11from qmk.info import info_json
12from qmk.json_encoders import InfoJSONEncoder 12from qmk.json_encoders import InfoJSONEncoder
13from qmk.json_schema import load_jsonschema 13from qmk.json_schema import load_jsonschema
14from qmk.keyboard import keyboard_folder 14from qmk.keyboard import keyboard_completer, keyboard_folder
15from qmk.path import is_keyboard 15from qmk.path import is_keyboard
16 16
17 17
@@ -41,7 +41,7 @@ def strip_info_json(kb_info_json):
41 return validator(kb_info_json) 41 return validator(kb_info_json)
42 42
43 43
44@cli.argument('-kb', '--keyboard', type=keyboard_folder, help='Keyboard to show info for.') 44@cli.argument('-kb', '--keyboard', type=keyboard_folder, completer=keyboard_completer, help='Keyboard to show info for.')
45@cli.argument('-km', '--keymap', help='Show the layers for a JSON keymap too.') 45@cli.argument('-km', '--keymap', help='Show the layers for a JSON keymap too.')
46@cli.subcommand('Generate an info.json file for a keyboard.', hidden=False if cli.config.user.developer else True) 46@cli.subcommand('Generate an info.json file for a keyboard.', hidden=False if cli.config.user.developer else True)
47@automagic_keyboard 47@automagic_keyboard
diff --git a/lib/python/qmk/cli/generate/layouts.py b/lib/python/qmk/cli/generate/layouts.py
index 7b4394291..ad6946d6c 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
5from qmk.constants import COL_LETTERS, ROW_LETTERS 5from qmk.constants import COL_LETTERS, ROW_LETTERS
6from qmk.decorators import automagic_keyboard, automagic_keymap 6from qmk.decorators import automagic_keyboard, automagic_keymap
7from qmk.info import info_json 7from qmk.info import info_json
8from qmk.keyboard import keyboard_folder 8from qmk.keyboard import keyboard_completer, keyboard_folder
9from qmk.path import is_keyboard, normpath 9from qmk.path import is_keyboard, normpath
10 10
11usb_properties = { 11usb_properties = {
@@ -17,7 +17,7 @@ usb_properties = {
17 17
18@cli.argument('-o', '--output', arg_only=True, type=normpath, help='File to write to') 18@cli.argument('-o', '--output', arg_only=True, type=normpath, help='File to write to')
19@cli.argument('-q', '--quiet', arg_only=True, action='store_true', help="Quiet mode, only output error messages") 19@cli.argument('-q', '--quiet', arg_only=True, action='store_true', help="Quiet mode, only output error messages")
20@cli.argument('-kb', '--keyboard', type=keyboard_folder, help='Keyboard to generate config.h for.') 20@cli.argument('-kb', '--keyboard', type=keyboard_folder, completer=keyboard_completer, help='Keyboard to generate config.h for.')
21@cli.subcommand('Used by the make system to generate layouts.h from info.json', hidden=True) 21@cli.subcommand('Used by the make system to generate layouts.h from info.json', hidden=True)
22@automagic_keyboard 22@automagic_keyboard
23@automagic_keymap 23@automagic_keymap
diff --git a/lib/python/qmk/cli/generate/rules_mk.py b/lib/python/qmk/cli/generate/rules_mk.py
index 91759d26c..41c94e16b 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
8from qmk.decorators import automagic_keyboard, automagic_keymap 8from qmk.decorators import automagic_keyboard, automagic_keymap
9from qmk.info import info_json 9from qmk.info import info_json
10from qmk.json_schema import json_load 10from qmk.json_schema import json_load
11from qmk.keyboard import keyboard_folder 11from qmk.keyboard import keyboard_completer, keyboard_folder
12from qmk.path import is_keyboard, normpath 12from qmk.path import is_keyboard, normpath
13 13
14 14
@@ -39,7 +39,7 @@ def process_mapping_rule(kb_info_json, rules_key, info_dict):
39@cli.argument('-o', '--output', arg_only=True, type=normpath, help='File to write to') 39@cli.argument('-o', '--output', arg_only=True, type=normpath, help='File to write to')
40@cli.argument('-q', '--quiet', arg_only=True, action='store_true', help="Quiet mode, only output error messages") 40@cli.argument('-q', '--quiet', arg_only=True, action='store_true', help="Quiet mode, only output error messages")
41@cli.argument('-e', '--escape', arg_only=True, action='store_true', help="Escape spaces in quiet mode") 41@cli.argument('-e', '--escape', arg_only=True, action='store_true', help="Escape spaces in quiet mode")
42@cli.argument('-kb', '--keyboard', type=keyboard_folder, help='Keyboard to generate config.h for.') 42@cli.argument('-kb', '--keyboard', type=keyboard_folder, completer=keyboard_completer, help='Keyboard to generate config.h for.')
43@cli.subcommand('Used by the make system to generate info_config.h from info.json', hidden=True) 43@cli.subcommand('Used by the make system to generate info_config.h from info.json', hidden=True)
44@automagic_keyboard 44@automagic_keyboard
45@automagic_keymap 45@automagic_keymap
diff --git a/lib/python/qmk/cli/info.py b/lib/python/qmk/cli/info.py
index aac507c1a..572b305ca 100755
--- a/lib/python/qmk/cli/info.py
+++ b/lib/python/qmk/cli/info.py
@@ -10,7 +10,7 @@ from milc import cli
10from qmk.json_encoders import InfoJSONEncoder 10from qmk.json_encoders import InfoJSONEncoder
11from qmk.constants import COL_LETTERS, ROW_LETTERS 11from qmk.constants import COL_LETTERS, ROW_LETTERS
12from qmk.decorators import automagic_keyboard, automagic_keymap 12from qmk.decorators import automagic_keyboard, automagic_keymap
13from qmk.keyboard import keyboard_folder, render_layouts, render_layout 13from qmk.keyboard import keyboard_completer, keyboard_folder, render_layouts, render_layout
14from qmk.keymap import locate_keymap 14from qmk.keymap import locate_keymap
15from qmk.info import info_json 15from qmk.info import info_json
16from qmk.path import is_keyboard 16from qmk.path import is_keyboard
@@ -124,7 +124,7 @@ def print_text_output(kb_info_json):
124 show_keymap(kb_info_json, False) 124 show_keymap(kb_info_json, False)
125 125
126 126
127@cli.argument('-kb', '--keyboard', type=keyboard_folder, help='Keyboard to show info for.') 127@cli.argument('-kb', '--keyboard', type=keyboard_folder, completer=keyboard_completer, help='Keyboard to show info for.')
128@cli.argument('-km', '--keymap', help='Show the layers for a JSON keymap too.') 128@cli.argument('-km', '--keymap', help='Show the layers for a JSON keymap too.')
129@cli.argument('-l', '--layouts', action='store_true', help='Render the layouts.') 129@cli.argument('-l', '--layouts', action='store_true', help='Render the layouts.')
130@cli.argument('-m', '--matrix', action='store_true', help='Render the layouts with matrix information.') 130@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 5a2fb96c7..a90578c02 100755
--- a/lib/python/qmk/cli/json2c.py
+++ b/lib/python/qmk/cli/json2c.py
@@ -2,6 +2,7 @@
2""" 2"""
3import json 3import json
4 4
5from argcomplete.completers import FilesCompleter
5from milc import cli 6from milc import cli
6 7
7import qmk.keymap 8import qmk.keymap
@@ -10,7 +11,7 @@ import qmk.path
10 11
11@cli.argument('-o', '--output', arg_only=True, type=qmk.path.normpath, help='File to write to') 12@cli.argument('-o', '--output', arg_only=True, type=qmk.path.normpath, help='File to write to')
12@cli.argument('-q', '--quiet', arg_only=True, action='store_true', help="Quiet mode, only output error messages") 13@cli.argument('-q', '--quiet', arg_only=True, action='store_true', help="Quiet mode, only output error messages")
13@cli.argument('filename', type=qmk.path.FileType('r'), arg_only=True, help='Configurator JSON file') 14@cli.argument('filename', type=qmk.path.FileType('r'), arg_only=True, completer=FilesCompleter('.json'), help='Configurator JSON file')
14@cli.subcommand('Creates a keymap.c from a QMK Configurator export.') 15@cli.subcommand('Creates a keymap.c from a QMK Configurator export.')
15def json2c(cli): 16def json2c(cli):
16 """Generate a keymap.c from a configurator export. 17 """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 91499c9af..acb75ef4f 100755
--- a/lib/python/qmk/cli/kle2json.py
+++ b/lib/python/qmk/cli/kle2json.py
@@ -4,6 +4,7 @@ import json
4import os 4import os
5from pathlib import Path 5from pathlib import Path
6 6
7from argcomplete.completers import FilesCompleter
7from milc import cli 8from milc import cli
8from kle2xy import KLE2xy 9from kle2xy import KLE2xy
9 10
@@ -11,7 +12,7 @@ from qmk.converter import kle2qmk
11from qmk.json_encoders import InfoJSONEncoder 12from qmk.json_encoders import InfoJSONEncoder
12 13
13 14
14@cli.argument('filename', help='The KLE raw txt to convert') 15@cli.argument('filename', completer=FilesCompleter('.json'), help='The KLE raw txt to convert')
15@cli.argument('-f', '--force', action='store_true', help='Flag to overwrite current info.json') 16@cli.argument('-f', '--force', action='store_true', help='Flag to overwrite current info.json')
16@cli.subcommand('Convert a KLE layout to a Configurator JSON', hidden=False if cli.config.user.developer else True) 17@cli.subcommand('Convert a KLE layout to a Configurator JSON', hidden=False if cli.config.user.developer else True)
17def kle2json(cli): 18def kle2json(cli):
diff --git a/lib/python/qmk/cli/lint.py b/lib/python/qmk/cli/lint.py
index 74467021e..a164dba63 100644
--- a/lib/python/qmk/cli/lint.py
+++ b/lib/python/qmk/cli/lint.py
@@ -4,12 +4,13 @@ from milc import cli
4 4
5from qmk.decorators import automagic_keyboard, automagic_keymap 5from qmk.decorators import automagic_keyboard, automagic_keymap
6from qmk.info import info_json 6from qmk.info import info_json
7from qmk.keyboard import keyboard_completer
7from qmk.keymap import locate_keymap 8from qmk.keymap import locate_keymap
8from qmk.path import is_keyboard, keyboard 9from qmk.path import is_keyboard, keyboard
9 10
10 11
11@cli.argument('--strict', action='store_true', help='Treat warnings as errors.') 12@cli.argument('--strict', action='store_true', help='Treat warnings as errors.')
12@cli.argument('-kb', '--keyboard', help='The keyboard to check.') 13@cli.argument('-kb', '--keyboard', completer=keyboard_completer, help='The keyboard to check.')
13@cli.argument('-km', '--keymap', help='The keymap to check.') 14@cli.argument('-km', '--keymap', help='The keymap to check.')
14@cli.subcommand('Check keyboard and keymap for common mistakes.') 15@cli.subcommand('Check keyboard and keymap for common mistakes.')
15@automagic_keyboard 16@automagic_keyboard
diff --git a/lib/python/qmk/cli/list/keymaps.py b/lib/python/qmk/cli/list/keymaps.py
index 7c0ad4399..d79ab75b5 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
4 4
5import qmk.keymap 5import qmk.keymap
6from qmk.decorators import automagic_keyboard 6from qmk.decorators import automagic_keyboard
7from qmk.keyboard import keyboard_folder 7from qmk.keyboard import keyboard_completer, keyboard_folder
8 8
9 9
10@cli.argument("-kb", "--keyboard", type=keyboard_folder, help="Specify keyboard name. Example: 1upkeyboards/1up60hse") 10@cli.argument("-kb", "--keyboard", type=keyboard_folder, completer=keyboard_completer, help="Specify keyboard name. Example: 1upkeyboards/1up60hse")
11@cli.subcommand("List the keymaps for a specific keyboard") 11@cli.subcommand("List the keymaps for a specific keyboard")
12@automagic_keyboard 12@automagic_keyboard
13def list_keymaps(cli): 13def list_keymaps(cli):
diff --git a/lib/python/qmk/cli/new/keymap.py b/lib/python/qmk/cli/new/keymap.py
index ea98a287c..60cb743cb 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
5 5
6import qmk.path 6import qmk.path
7from qmk.decorators import automagic_keyboard, automagic_keymap 7from qmk.decorators import automagic_keyboard, automagic_keymap
8from qmk.keyboard import keyboard_folder 8from qmk.keyboard import keyboard_completer, keyboard_folder
9from milc import cli 9from milc import cli
10 10
11 11
12@cli.argument('-kb', '--keyboard', type=keyboard_folder, help='Specify keyboard name. Example: 1upkeyboards/1up60hse') 12@cli.argument('-kb', '--keyboard', type=keyboard_folder, completer=keyboard_completer, help='Specify keyboard name. Example: 1upkeyboards/1up60hse')
13@cli.argument('-km', '--keymap', help='Specify the name for the new keymap directory') 13@cli.argument('-km', '--keymap', help='Specify the name for the new keymap directory')
14@cli.subcommand('Creates a new keymap for the keyboard of your choosing') 14@cli.subcommand('Creates a new keymap for the keyboard of your choosing')
15@automagic_keyboard 15@automagic_keyboard
diff --git a/lib/python/qmk/decorators.py b/lib/python/qmk/decorators.py
index 629402b09..8d43ae980 100644
--- a/lib/python/qmk/decorators.py
+++ b/lib/python/qmk/decorators.py
@@ -1,13 +1,12 @@
1"""Helpful decorators that subcommands can use. 1"""Helpful decorators that subcommands can use.
2""" 2"""
3import functools 3import functools
4from pathlib import Path
5from time import monotonic 4from time import monotonic
6 5
7from milc import cli 6from milc import cli
8 7
9from qmk.keymap import is_keymap_dir 8from qmk.keyboard import find_keyboard_from_dir
10from qmk.path import is_keyboard, under_qmk_firmware 9from qmk.keymap import find_keymap_from_dir
11 10
12 11
13def automagic_keyboard(func): 12def automagic_keyboard(func):
@@ -17,27 +16,13 @@ def automagic_keyboard(func):
17 """ 16 """
18 @functools.wraps(func) 17 @functools.wraps(func)
19 def wrapper(*args, **kwargs): 18 def wrapper(*args, **kwargs):
20 # Check to make sure their copy of MILC supports config_source
21 if not hasattr(cli, 'config_source'):
22 cli.log.error("This subcommand requires a newer version of the QMK CLI. Please upgrade using `pip3 install --upgrade qmk` or your package manager.")
23 exit(1)
24
25 # Ensure that `--keyboard` was not passed and CWD is under `qmk_firmware/keyboards` 19 # Ensure that `--keyboard` was not passed and CWD is under `qmk_firmware/keyboards`
26 if cli.config_source[cli._entrypoint.__name__]['keyboard'] != 'argument': 20 if cli.config_source[cli._entrypoint.__name__]['keyboard'] != 'argument':
27 relative_cwd = under_qmk_firmware() 21 keyboard = find_keyboard_from_dir()
28
29 if relative_cwd and len(relative_cwd.parts) > 1 and relative_cwd.parts[0] == 'keyboards':
30 # Attempt to extract the keyboard name from the current directory
31 current_path = Path('/'.join(relative_cwd.parts[1:]))
32
33 if 'keymaps' in current_path.parts:
34 # Strip current_path of anything after `keymaps`
35 keymap_index = len(current_path.parts) - current_path.parts.index('keymaps') - 1
36 current_path = current_path.parents[keymap_index]
37 22
38 if is_keyboard(current_path): 23 if keyboard:
39 cli.config[cli._entrypoint.__name__]['keyboard'] = str(current_path) 24 cli.config[cli._entrypoint.__name__]['keyboard'] = keyboard
40 cli.config_source[cli._entrypoint.__name__]['keyboard'] = 'keyboard_directory' 25 cli.config_source[cli._entrypoint.__name__]['keyboard'] = 'keyboard_directory'
41 26
42 return func(*args, **kwargs) 27 return func(*args, **kwargs)
43 28
@@ -51,36 +36,13 @@ def automagic_keymap(func):
51 """ 36 """
52 @functools.wraps(func) 37 @functools.wraps(func)
53 def wrapper(*args, **kwargs): 38 def wrapper(*args, **kwargs):
54 # Check to make sure their copy of MILC supports config_source
55 if not hasattr(cli, 'config_source'):
56 cli.log.error("This subcommand requires a newer version of the QMK CLI. Please upgrade using `pip3 install --upgrade qmk` or your package manager.")
57 exit(1)
58
59 # Ensure that `--keymap` was not passed and that we're under `qmk_firmware` 39 # Ensure that `--keymap` was not passed and that we're under `qmk_firmware`
60 if cli.config_source[cli._entrypoint.__name__]['keymap'] != 'argument': 40 if cli.config_source[cli._entrypoint.__name__]['keymap'] != 'argument':
61 relative_cwd = under_qmk_firmware() 41 keymap_name, keymap_type = find_keymap_from_dir()
62 42
63 if relative_cwd and len(relative_cwd.parts) > 1: 43 if keymap_name:
64 # If we're in `qmk_firmware/keyboards` and `keymaps` is in our path, try to find the keyboard name. 44 cli.config[cli._entrypoint.__name__]['keymap'] = keymap_name
65 if relative_cwd.parts[0] == 'keyboards' and 'keymaps' in relative_cwd.parts: 45 cli.config_source[cli._entrypoint.__name__]['keymap'] = keymap_type
66 current_path = Path('/'.join(relative_cwd.parts[1:])) # Strip 'keyboards' from the front
67
68 if 'keymaps' in current_path.parts and current_path.name != 'keymaps':
69 while current_path.parent.name != 'keymaps':
70 current_path = current_path.parent
71 cli.config[cli._entrypoint.__name__]['keymap'] = current_path.name
72 cli.config_source[cli._entrypoint.__name__]['keymap'] = 'keymap_directory'
73
74 # If we're in `qmk_firmware/layouts` guess the name from the community keymap they're in
75 elif relative_cwd.parts[0] == 'layouts' and is_keymap_dir(relative_cwd):
76 cli.config[cli._entrypoint.__name__]['keymap'] = relative_cwd.name
77 cli.config_source[cli._entrypoint.__name__]['keymap'] = 'layouts_directory'
78
79 # If we're in `qmk_firmware/users` guess the name from the userspace they're in
80 elif relative_cwd.parts[0] == 'users':
81 # Guess the keymap name based on which userspace they're in
82 cli.config[cli._entrypoint.__name__]['keymap'] = relative_cwd.parts[1]
83 cli.config_source[cli._entrypoint.__name__]['keymap'] = 'users_directory'
84 46
85 return func(*args, **kwargs) 47 return func(*args, **kwargs)
86 48
diff --git a/lib/python/qmk/keyboard.py b/lib/python/qmk/keyboard.py
index 89f9346c4..0168d17ef 100644
--- a/lib/python/qmk/keyboard.py
+++ b/lib/python/qmk/keyboard.py
@@ -9,7 +9,7 @@ from glob import glob
9from qmk.c_parse import parse_config_h_file 9from qmk.c_parse import parse_config_h_file
10from qmk.json_schema import json_load 10from qmk.json_schema import json_load
11from qmk.makefile import parse_rules_mk_file 11from qmk.makefile import parse_rules_mk_file
12from qmk.path import is_keyboard 12from qmk.path import is_keyboard, under_qmk_firmware
13 13
14BOX_DRAWING_CHARACTERS = { 14BOX_DRAWING_CHARACTERS = {
15 "unicode": { 15 "unicode": {
@@ -33,6 +33,24 @@ BOX_DRAWING_CHARACTERS = {
33base_path = os.path.join(os.getcwd(), "keyboards") + os.path.sep 33base_path = os.path.join(os.getcwd(), "keyboards") + os.path.sep
34 34
35 35
36def find_keyboard_from_dir():
37 """Returns a keyboard name based on the user's current directory.
38 """
39 relative_cwd = under_qmk_firmware()
40
41 if relative_cwd and len(relative_cwd.parts) > 1 and relative_cwd.parts[0] == 'keyboards':
42 # Attempt to extract the keyboard name from the current directory
43 current_path = Path('/'.join(relative_cwd.parts[1:]))
44
45 if 'keymaps' in current_path.parts:
46 # Strip current_path of anything after `keymaps`
47 keymap_index = len(current_path.parts) - current_path.parts.index('keymaps') - 1
48 current_path = current_path.parents[keymap_index]
49
50 if is_keyboard(current_path):
51 return str(current_path)
52
53
36def keyboard_folder(keyboard): 54def keyboard_folder(keyboard):
37 """Returns the actual keyboard folder. 55 """Returns the actual keyboard folder.
38 56
@@ -61,6 +79,12 @@ def _find_name(path):
61 return path.replace(base_path, "").replace(os.path.sep + "rules.mk", "") 79 return path.replace(base_path, "").replace(os.path.sep + "rules.mk", "")
62 80
63 81
82def keyboard_completer(prefix, action, parser, parsed_args):
83 """Returns a list of keyboards for tab completion.
84 """
85 return list_keyboards()
86
87
64def list_keyboards(): 88def list_keyboards():
65 """Returns a list of all keyboards. 89 """Returns a list of all keyboards.
66 """ 90 """
diff --git a/lib/python/qmk/keymap.py b/lib/python/qmk/keymap.py
index d8495c38b..4ad9ffb59 100644
--- a/lib/python/qmk/keymap.py
+++ b/lib/python/qmk/keymap.py
@@ -1,19 +1,19 @@
1"""Functions that help you work with QMK keymaps. 1"""Functions that help you work with QMK keymaps.
2""" 2"""
3from pathlib import Path
4import json 3import json
5import subprocess 4import subprocess
6import sys 5import sys
6from pathlib import Path
7 7
8import argcomplete
9from milc import cli
8from pygments.lexers.c_cpp import CLexer 10from pygments.lexers.c_cpp import CLexer
9from pygments.token import Token 11from pygments.token import Token
10from pygments import lex 12from pygments import lex
11 13
12from milc import cli
13
14from qmk.keyboard import rules_mk
15import qmk.path 14import qmk.path
16import qmk.commands 15import qmk.commands
16from qmk.keyboard import find_keyboard_from_dir, rules_mk
17 17
18# The `keymap.c` template to use when a keyboard doesn't have its own 18# The `keymap.c` template to use when a keyboard doesn't have its own
19DEFAULT_KEYMAP_C = """#include QMK_KEYBOARD_H 19DEFAULT_KEYMAP_C = """#include QMK_KEYBOARD_H
@@ -74,6 +74,54 @@ def _strip_any(keycode):
74 return keycode 74 return keycode
75 75
76 76
77def find_keymap_from_dir():
78 """Returns `(keymap_name, source)` for the directory we're currently in.
79
80 """
81 relative_cwd = qmk.path.under_qmk_firmware()
82
83 if relative_cwd and len(relative_cwd.parts) > 1:
84 # If we're in `qmk_firmware/keyboards` and `keymaps` is in our path, try to find the keyboard name.
85 if relative_cwd.parts[0] == 'keyboards' and 'keymaps' in relative_cwd.parts:
86 current_path = Path('/'.join(relative_cwd.parts[1:])) # Strip 'keyboards' from the front
87
88 if 'keymaps' in current_path.parts and current_path.name != 'keymaps':
89 while current_path.parent.name != 'keymaps':
90 current_path = current_path.parent
91
92 return current_path.name, 'keymap_directory'
93
94 # If we're in `qmk_firmware/layouts` guess the name from the community keymap they're in
95 elif relative_cwd.parts[0] == 'layouts' and is_keymap_dir(relative_cwd):
96 return relative_cwd.name, 'layouts_directory'
97
98 # If we're in `qmk_firmware/users` guess the name from the userspace they're in
99 elif relative_cwd.parts[0] == 'users':
100 # Guess the keymap name based on which userspace they're in
101 return relative_cwd.parts[1], 'users_directory'
102
103 return None, None
104
105
106def keymap_completer(prefix, action, parser, parsed_args):
107 """Returns a list of keymaps for tab completion.
108 """
109 try:
110 if parsed_args.keyboard:
111 return list_keymaps(parsed_args.keyboard)
112
113 keyboard = find_keyboard_from_dir()
114
115 if keyboard:
116 return list_keymaps(keyboard)
117
118 except Exception as e:
119 argcomplete.warn(f'Error: {e.__class__.__name__}: {str(e)}')
120 return []
121
122 return []
123
124
77def is_keymap_dir(keymap, c=True, json=True, additional_files=None): 125def is_keymap_dir(keymap, c=True, json=True, additional_files=None):
78 """Return True if Path object `keymap` has a keymap file inside. 126 """Return True if Path object `keymap` has a keymap file inside.
79 127