aboutsummaryrefslogtreecommitdiff
path: root/lib/python/qmk
diff options
context:
space:
mode:
Diffstat (limited to 'lib/python/qmk')
-rw-r--r--lib/python/qmk/cli/__init__.py13
-rw-r--r--lib/python/qmk/cli/cformat.py6
-rwxr-xr-xlib/python/qmk/cli/compile.py10
-rw-r--r--lib/python/qmk/cli/config.py96
-rwxr-xr-xlib/python/qmk/cli/doctor.py5
-rwxr-xr-xlib/python/qmk/cli/hello.py6
-rw-r--r--lib/python/qmk/cli/json/__init__.py5
-rwxr-xr-xlib/python/qmk/cli/json/keymap.py20
-rw-r--r--lib/python/qmk/cli/new/__init__.py1
-rwxr-xr-xlib/python/qmk/cli/new/keymap.py17
-rwxr-xr-xlib/python/qmk/cli/pyformat.py5
-rw-r--r--lib/python/qmk/cli/pytest.py (renamed from lib/python/qmk/cli/nose2.py)8
-rw-r--r--lib/python/qmk/path.py1
-rw-r--r--lib/python/qmk/tests/test_cli_commands.py39
14 files changed, 195 insertions, 37 deletions
diff --git a/lib/python/qmk/cli/__init__.py b/lib/python/qmk/cli/__init__.py
index e69de29bb..fb4e0ecb4 100644
--- a/lib/python/qmk/cli/__init__.py
+++ b/lib/python/qmk/cli/__init__.py
@@ -0,0 +1,13 @@
1"""QMK CLI Subcommands
2
3We list each subcommand here explicitly because all the reliable ways of searching for modules are slow and delay startup.
4"""
5from . import cformat
6from . import compile
7from . import config
8from . import doctor
9from . import hello
10from . import json
11from . import new
12from . import pyformat
13from . import pytest
diff --git a/lib/python/qmk/cli/cformat.py b/lib/python/qmk/cli/cformat.py
index 91e650368..d2382bdbd 100644
--- a/lib/python/qmk/cli/cformat.py
+++ b/lib/python/qmk/cli/cformat.py
@@ -6,9 +6,9 @@ import subprocess
6from milc import cli 6from milc import cli
7 7
8 8
9@cli.argument('files', nargs='*', help='Filename(s) to format.') 9@cli.argument('files', nargs='*', arg_only=True, help='Filename(s) to format.')
10@cli.entrypoint("Format C code according to QMK's style.") 10@cli.subcommand("Format C code according to QMK's style.")
11def main(cli): 11def cformat(cli):
12 """Format C code according to QMK's style. 12 """Format C code according to QMK's style.
13 """ 13 """
14 clang_format = ['clang-format', '-i'] 14 clang_format = ['clang-format', '-i']
diff --git a/lib/python/qmk/cli/compile.py b/lib/python/qmk/cli/compile.py
index 7e14ad8fb..6646891b3 100755
--- a/lib/python/qmk/cli/compile.py
+++ b/lib/python/qmk/cli/compile.py
@@ -14,11 +14,11 @@ import qmk.keymap
14import qmk.path 14import qmk.path
15 15
16 16
17@cli.argument('filename', nargs='?', type=FileType('r'), help='The configurator export to compile') 17@cli.argument('filename', nargs='?', arg_only=True, type=FileType('r'), help='The configurator export to compile')
18@cli.argument('-kb', '--keyboard', help='The keyboard to build a firmware for. Ignored when a configurator export is supplied.') 18@cli.argument('-kb', '--keyboard', help='The keyboard to build a firmware for. Ignored when a configurator export is supplied.')
19@cli.argument('-km', '--keymap', help='The keymap to build a firmware for. Ignored when a configurator export is supplied.') 19@cli.argument('-km', '--keymap', help='The keymap to build a firmware for. Ignored when a configurator export is supplied.')
20@cli.entrypoint('Compile a QMK Firmware.') 20@cli.subcommand('Compile a QMK Firmware.')
21def main(cli): 21def compile(cli):
22 """Compile a QMK Firmware. 22 """Compile a QMK Firmware.
23 23
24 If a Configurator export is supplied this command will create a new keymap, overwriting an existing keymap if one exists. 24 If a Configurator export is supplied this command will create a new keymap, overwriting an existing keymap if one exists.
@@ -41,9 +41,9 @@ def main(cli):
41 # Compile the keymap 41 # Compile the keymap
42 command = ['make', ':'.join((user_keymap['keyboard'], user_keymap['keymap']))] 42 command = ['make', ':'.join((user_keymap['keyboard'], user_keymap['keymap']))]
43 43
44 elif cli.config.general.keyboard and cli.config.general.keymap: 44 elif cli.config.compile.keyboard and cli.config.compile.keymap:
45 # Generate the make command for a specific keyboard/keymap. 45 # Generate the make command for a specific keyboard/keymap.
46 command = ['make', ':'.join((cli.config.general.keyboard, cli.config.general.keymap))] 46 command = ['make', ':'.join((cli.config.compile.keyboard, cli.config.compile.keymap))]
47 47
48 else: 48 else:
49 cli.log.error('You must supply a configurator export or both `--keyboard` and `--keymap`.') 49 cli.log.error('You must supply a configurator export or both `--keyboard` and `--keymap`.')
diff --git a/lib/python/qmk/cli/config.py b/lib/python/qmk/cli/config.py
new file mode 100644
index 000000000..d6c774e65
--- /dev/null
+++ b/lib/python/qmk/cli/config.py
@@ -0,0 +1,96 @@
1"""Read and write configuration settings
2"""
3import os
4import subprocess
5
6from milc import cli
7
8
9def print_config(section, key):
10 """Print a single config setting to stdout.
11 """
12 cli.echo('%s.%s{fg_cyan}={fg_reset}%s', section, key, cli.config[section][key])
13
14
15@cli.argument('-ro', '--read-only', action='store_true', help='Operate in read-only mode.')
16@cli.argument('configs', nargs='*', arg_only=True, help='Configuration options to read or write.')
17@cli.subcommand("Read and write configuration settings.")
18def config(cli):
19 """Read and write config settings.
20
21 This script iterates over the config_tokens supplied as argument. Each config_token has the following form:
22
23 section[.key][=value]
24
25 If only a section (EG 'compile') is supplied all keys for that section will be displayed.
26
27 If section.key is supplied the value for that single key will be displayed.
28
29 If section.key=value is supplied the value for that single key will be set.
30
31 If section.key=None is supplied the key will be deleted.
32
33 No validation is done to ensure that the supplied section.key is actually used by qmk scripts.
34 """
35 if not cli.args.configs:
36 # Walk the config tree
37 for section in cli.config:
38 for key in cli.config[section]:
39 print_config(section, key)
40
41 return True
42
43 # Process config_tokens
44 save_config = False
45
46 for argument in cli.args.configs:
47 # Split on space in case they quoted multiple config tokens
48 for config_token in argument.split(' '):
49 # Extract the section, config_key, and value to write from the supplied config_token.
50 if '=' in config_token:
51 key, value = config_token.split('=')
52 else:
53 key = config_token
54 value = None
55
56 if '.' in key:
57 section, config_key = key.split('.', 1)
58 else:
59 section = key
60 config_key = None
61
62 # Validation
63 if config_key and '.' in config_key:
64 cli.log.error('Config keys may not have more than one period! "%s" is not valid.', key)
65 return False
66
67 # Do what the user wants
68 if section and config_key and value:
69 # Write a config key
70 log_string = '%s.%s{fg_cyan}:{fg_reset} %s {fg_cyan}->{fg_reset} %s'
71 if cli.args.read_only:
72 log_string += ' {fg_red}(change not written)'
73
74 cli.echo(log_string, section, config_key, cli.config[section][config_key], value)
75
76 if not cli.args.read_only:
77 if value == 'None':
78 del cli.config[section][config_key]
79 else:
80 cli.config[section][config_key] = value
81 save_config = True
82
83 elif section and config_key:
84 # Display a single key
85 print_config(section, config_key)
86
87 elif section:
88 # Display an entire section
89 for key in cli.config[section]:
90 print_config(section, key)
91
92 # Ending actions
93 if save_config:
94 cli.save_config()
95
96 return True
diff --git a/lib/python/qmk/cli/doctor.py b/lib/python/qmk/cli/doctor.py
index 5a713b20f..3474422a8 100755
--- a/lib/python/qmk/cli/doctor.py
+++ b/lib/python/qmk/cli/doctor.py
@@ -11,8 +11,8 @@ from glob import glob
11from milc import cli 11from milc import cli
12 12
13 13
14@cli.entrypoint('Basic QMK environment checks') 14@cli.subcommand('Basic QMK environment checks')
15def main(cli): 15def doctor(cli):
16 """Basic QMK environment checks. 16 """Basic QMK environment checks.
17 17
18 This is currently very simple, it just checks that all the expected binaries are on your system. 18 This is currently very simple, it just checks that all the expected binaries are on your system.
@@ -36,6 +36,7 @@ def main(cli):
36 else: 36 else:
37 try: 37 try:
38 subprocess.run([binary, '--version'], stdout=subprocess.PIPE, stderr=subprocess.PIPE, timeout=5, check=True) 38 subprocess.run([binary, '--version'], stdout=subprocess.PIPE, stderr=subprocess.PIPE, timeout=5, check=True)
39 cli.log.info('Found {fg_cyan}%s', binary)
39 except subprocess.CalledProcessError: 40 except subprocess.CalledProcessError:
40 cli.log.error("{fg_red}Can't run `%s --version`", binary) 41 cli.log.error("{fg_red}Can't run `%s --version`", binary)
41 ok = False 42 ok = False
diff --git a/lib/python/qmk/cli/hello.py b/lib/python/qmk/cli/hello.py
index bc0cb6de1..bee28c301 100755
--- a/lib/python/qmk/cli/hello.py
+++ b/lib/python/qmk/cli/hello.py
@@ -6,8 +6,8 @@ from milc import cli
6 6
7 7
8@cli.argument('-n', '--name', default='World', help='Name to greet.') 8@cli.argument('-n', '--name', default='World', help='Name to greet.')
9@cli.entrypoint('QMK Hello World.') 9@cli.subcommand('QMK Hello World.')
10def main(cli): 10def hello(cli):
11 """Log a friendly greeting. 11 """Log a friendly greeting.
12 """ 12 """
13 cli.log.info('Hello, %s!', cli.config.general.name) 13 cli.log.info('Hello, %s!', cli.config.hello.name)
diff --git a/lib/python/qmk/cli/json/__init__.py b/lib/python/qmk/cli/json/__init__.py
index e69de29bb..f4ebfc45b 100644
--- a/lib/python/qmk/cli/json/__init__.py
+++ b/lib/python/qmk/cli/json/__init__.py
@@ -0,0 +1,5 @@
1"""QMK CLI JSON Subcommands
2
3We list each subcommand here explicitly because all the reliable ways of searching for modules are slow and delay startup.
4"""
5from . import keymap
diff --git a/lib/python/qmk/cli/json/keymap.py b/lib/python/qmk/cli/json/keymap.py
index e2d0b5809..a65acd619 100755
--- a/lib/python/qmk/cli/json/keymap.py
+++ b/lib/python/qmk/cli/json/keymap.py
@@ -9,10 +9,10 @@ from milc import cli
9import qmk.keymap 9import qmk.keymap
10 10
11 11
12@cli.argument('-o', '--output', help='File to write to') 12@cli.argument('-o', '--output', arg_only=True, help='File to write to')
13@cli.argument('filename', help='Configurator JSON file') 13@cli.argument('filename', arg_only=True, help='Configurator JSON file')
14@cli.entrypoint('Create a keymap.c from a QMK Configurator export.') 14@cli.subcommand('Create a keymap.c from a QMK Configurator export.')
15def main(cli): 15def json_keymap(cli):
16 """Generate a keymap.c from a configurator export. 16 """Generate a keymap.c from a configurator export.
17 17
18 This command uses the `qmk.keymap` module to generate a keymap.c from a configurator export. The generated keymap is written to stdout, or to a file if -o is provided. 18 This command uses the `qmk.keymap` module to generate a keymap.c from a configurator export. The generated keymap is written to stdout, or to a file if -o is provided.
@@ -28,8 +28,8 @@ def main(cli):
28 exit(1) 28 exit(1)
29 29
30 # Environment processing 30 # Environment processing
31 if cli.config.general.output == ('-'): 31 if cli.args.output == ('-'):
32 cli.config.general.output = None 32 cli.args.output = None
33 33
34 # Parse the configurator json 34 # Parse the configurator json
35 with open(qmk.path.normpath(cli.args.filename), 'r') as fd: 35 with open(qmk.path.normpath(cli.args.filename), 'r') as fd:
@@ -38,17 +38,17 @@ def main(cli):
38 # Generate the keymap 38 # Generate the keymap
39 keymap_c = qmk.keymap.generate(user_keymap['keyboard'], user_keymap['layout'], user_keymap['layers']) 39 keymap_c = qmk.keymap.generate(user_keymap['keyboard'], user_keymap['layout'], user_keymap['layers'])
40 40
41 if cli.config.general.output: 41 if cli.args.output:
42 output_dir = os.path.dirname(cli.config.general.output) 42 output_dir = os.path.dirname(cli.args.output)
43 43
44 if not os.path.exists(output_dir): 44 if not os.path.exists(output_dir):
45 os.makedirs(output_dir) 45 os.makedirs(output_dir)
46 46
47 output_file = qmk.path.normpath(cli.config.general.output) 47 output_file = qmk.path.normpath(cli.args.output)
48 with open(output_file, 'w') as keymap_fd: 48 with open(output_file, 'w') as keymap_fd:
49 keymap_fd.write(keymap_c) 49 keymap_fd.write(keymap_c)
50 50
51 cli.log.info('Wrote keymap to %s.', cli.config.general.output) 51 cli.log.info('Wrote keymap to %s.', cli.args.output)
52 52
53 else: 53 else:
54 print(keymap_c) 54 print(keymap_c)
diff --git a/lib/python/qmk/cli/new/__init__.py b/lib/python/qmk/cli/new/__init__.py
index e69de29bb..c6a26939b 100644
--- a/lib/python/qmk/cli/new/__init__.py
+++ b/lib/python/qmk/cli/new/__init__.py
@@ -0,0 +1 @@
from . import keymap
diff --git a/lib/python/qmk/cli/new/keymap.py b/lib/python/qmk/cli/new/keymap.py
index b378e5ab4..5efb81c93 100755
--- a/lib/python/qmk/cli/new/keymap.py
+++ b/lib/python/qmk/cli/new/keymap.py
@@ -6,15 +6,15 @@ import shutil
6from milc import cli 6from milc import cli
7 7
8 8
9@cli.argument('-k', '--keyboard', help='Specify keyboard name. Example: 1upkeyboards/1up60hse') 9@cli.argument('-kb', '--keyboard', help='Specify keyboard name. Example: 1upkeyboards/1up60hse')
10@cli.argument('-u', '--username', help='Specify any name for the new keymap directory') 10@cli.argument('-km', '--keymap', help='Specify the name for the new keymap directory')
11@cli.entrypoint('Creates a new keymap for the keyboard of your choosing') 11@cli.subcommand('Creates a new keymap for the keyboard of your choosing')
12def main(cli): 12def new_keymap(cli):
13 """Creates a new keymap for the keyboard of your choosing. 13 """Creates a new keymap for the keyboard of your choosing.
14 """ 14 """
15 # ask for user input if keyboard or username was not provided in the command line 15 # ask for user input if keyboard or username was not provided in the command line
16 keyboard = cli.config.general.keyboard if cli.config.general.keyboard else input("Keyboard Name: ") 16 keyboard = cli.config.new_keymap.keyboard if cli.config.new_keymap.keyboard else input("Keyboard Name: ")
17 username = cli.config.general.username if cli.config.general.username else input("Username: ") 17 keymap = cli.config.new_keymap.keymap if cli.config.new_keymap.keymap else input("Keymap Name: ")
18 18
19 # generate keymap paths 19 # generate keymap paths
20 kb_path = os.path.join(os.getcwd(), "keyboards", keyboard) 20 kb_path = os.path.join(os.getcwd(), "keyboards", keyboard)
@@ -36,6 +36,5 @@ def main(cli):
36 shutil.copytree(keymap_path_default, keymap_path, symlinks=True) 36 shutil.copytree(keymap_path_default, keymap_path, symlinks=True)
37 37
38 # end message to user 38 # end message to user
39 cli.log.info("%s keymap directory created in: %s\n" + 39 cli.log.info("%s keymap directory created in: %s", username, keymap_path)
40 "Compile a firmware file with your new keymap by typing: \n" + 40 cli.log.info("Compile a firmware with your new keymap by typing: \n" + "qmk compile -kb %s -km %s", keyboard, username)
41 "qmk compile -kb %s -km %s", username, keymap_path, keyboard, username)
diff --git a/lib/python/qmk/cli/pyformat.py b/lib/python/qmk/cli/pyformat.py
index b1f8c02b2..a53ba40c0 100755
--- a/lib/python/qmk/cli/pyformat.py
+++ b/lib/python/qmk/cli/pyformat.py
@@ -5,12 +5,13 @@ from milc import cli
5import subprocess 5import subprocess
6 6
7 7
8@cli.entrypoint("Format python code according to QMK's style.") 8@cli.subcommand("Format python code according to QMK's style.")
9def main(cli): 9def pyformat(cli):
10 """Format python code according to QMK's style. 10 """Format python code according to QMK's style.
11 """ 11 """
12 try: 12 try:
13 subprocess.run(['yapf', '-vv', '-ri', 'bin/qmk', 'lib/python'], check=True) 13 subprocess.run(['yapf', '-vv', '-ri', 'bin/qmk', 'lib/python'], check=True)
14 cli.log.info('Successfully formatted the python code in `bin/qmk` and `lib/python`.') 14 cli.log.info('Successfully formatted the python code in `bin/qmk` and `lib/python`.')
15
15 except subprocess.CalledProcessError: 16 except subprocess.CalledProcessError:
16 cli.log.error('Error formatting python code!') 17 cli.log.error('Error formatting python code!')
diff --git a/lib/python/qmk/cli/nose2.py b/lib/python/qmk/cli/pytest.py
index c6c9c67b3..14613e1d9 100644
--- a/lib/python/qmk/cli/nose2.py
+++ b/lib/python/qmk/cli/pytest.py
@@ -2,17 +2,19 @@
2 2
3QMK script to run unit and integration tests against our python code. 3QMK script to run unit and integration tests against our python code.
4""" 4"""
5import sys
5from milc import cli 6from milc import cli
6 7
7 8
8@cli.entrypoint('QMK Python Unit Tests') 9@cli.subcommand('QMK Python Unit Tests')
9def main(cli): 10def pytest(cli):
10 """Use nose2 to run unittests 11 """Use nose2 to run unittests
11 """ 12 """
12 try: 13 try:
13 import nose2 14 import nose2
15
14 except ImportError: 16 except ImportError:
15 cli.log.error('Could not import nose2! Please install it with {fg_cyan}pip3 install nose2') 17 cli.log.error('Could not import nose2! Please install it with {fg_cyan}pip3 install nose2')
16 return False 18 return False
17 19
18 nose2.discover() 20 nose2.discover(argv=['nose2', '-v'])
diff --git a/lib/python/qmk/path.py b/lib/python/qmk/path.py
index cf087265f..2149625cc 100644
--- a/lib/python/qmk/path.py
+++ b/lib/python/qmk/path.py
@@ -2,6 +2,7 @@
2""" 2"""
3import logging 3import logging
4import os 4import os
5from pkgutil import walk_packages
5 6
6from qmk.errors import NoSuchKeyboardError 7from qmk.errors import NoSuchKeyboardError
7 8
diff --git a/lib/python/qmk/tests/test_cli_commands.py b/lib/python/qmk/tests/test_cli_commands.py
new file mode 100644
index 000000000..2fc6e0f72
--- /dev/null
+++ b/lib/python/qmk/tests/test_cli_commands.py
@@ -0,0 +1,39 @@
1import subprocess
2
3
4def check_subcommand(command, *args):
5 cmd = ['bin/qmk', command] + list(args)
6 return subprocess.run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, universal_newlines=True)
7
8
9def test_cformat():
10 assert check_subcommand('cformat', 'tmk_core/common/backlight.c').returncode == 0
11
12
13def test_compile():
14 assert check_subcommand('compile', '-kb', 'handwired/onekey/pytest', '-km', 'default').returncode == 0
15
16
17def test_config():
18 result = check_subcommand('config')
19 assert result.returncode == 0
20 assert 'general.color' in result.stdout
21
22
23def test_doctor():
24 result = check_subcommand('doctor')
25 assert result.returncode == 0
26 assert 'QMK Doctor is checking your environment.' in result.stderr
27 assert 'QMK is ready to go' in result.stderr
28
29
30def test_hello():
31 result = check_subcommand('hello')
32 assert result.returncode == 0
33 assert 'Hello,' in result.stderr
34
35
36def test_pyformat():
37 result = check_subcommand('pyformat')
38 assert result.returncode == 0
39 assert 'Successfully formatted the python code' in result.stderr