aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rwxr-xr-xbin/qmk4
-rw-r--r--docs/cli_development.md32
-rw-r--r--lib/python/kle2xy.py2
-rwxr-xr-xlib/python/qmk/cli/compile.py3
-rw-r--r--lib/python/qmk/cli/config.py94
-rwxr-xr-xlib/python/qmk/cli/doctor.py2
-rw-r--r--lib/python/qmk/cli/flash.py10
-rwxr-xr-xlib/python/qmk/cli/json/keymap.py1
-rwxr-xr-xlib/python/qmk/cli/kle2json.py4
-rw-r--r--lib/python/qmk/cli/list/keyboards.py26
-rw-r--r--lib/python/qmk/cli/pytest.py16
-rw-r--r--lib/python/qmk/keymap.py4
-rw-r--r--lib/python/qmk/path.py1
-rw-r--r--requirements.txt2
-rw-r--r--setup.cfg9
15 files changed, 125 insertions, 85 deletions
diff --git a/bin/qmk b/bin/qmk
index 5da8673ba..4d5b3d884 100755
--- a/bin/qmk
+++ b/bin/qmk
@@ -41,7 +41,7 @@ else:
41 os.environ['QMK_VERSION'] = 'nogit-' + strftime('%Y-%m-%d-%H:%M:%S') + '-dirty' 41 os.environ['QMK_VERSION'] = 'nogit-' + strftime('%Y-%m-%d-%H:%M:%S') + '-dirty'
42 42
43# Setup the CLI 43# Setup the CLI
44import milc 44import milc # noqa
45 45
46milc.EMOJI_LOGLEVELS['INFO'] = '{fg_blue}Ψ{style_reset_all}' 46milc.EMOJI_LOGLEVELS['INFO'] = '{fg_blue}Ψ{style_reset_all}'
47 47
@@ -61,7 +61,7 @@ def main():
61 os.chdir(qmk_dir) 61 os.chdir(qmk_dir)
62 62
63 # Import the subcommands 63 # Import the subcommands
64 import qmk.cli 64 import qmk.cli # noqa
65 65
66 # Execute 66 # Execute
67 return_code = milc.cli() 67 return_code = milc.cli()
diff --git a/docs/cli_development.md b/docs/cli_development.md
index f5c7ad139..cc8c59d06 100644
--- a/docs/cli_development.md
+++ b/docs/cli_development.md
@@ -173,3 +173,35 @@ You will only be able to access these arguments using `cli.args`. For example:
173``` 173```
174cli.log.info('Reading from %s and writing to %s', cli.args.filename, cli.args.output) 174cli.log.info('Reading from %s and writing to %s', cli.args.filename, cli.args.output)
175``` 175```
176
177# Testing, and Linting, and Formatting (oh my!)
178
179We use nose2, flake8, and yapf to test, lint, and format code. You can use the `pytest` and `pyformat` subcommands to run these tests:
180
181### Testing and Linting
182
183 qmk pytest
184
185### Formatting
186
187 qmk pyformat
188
189## Formatting Details
190
191We use [yapf](https://github.com/google/yapf) to automatically format code. Our configuration is in the `[yapf]` section of `setup.cfg`.
192
193?> Tip- Many editors can use yapf as a plugin to automatically format code as you type.
194
195## Testing Details
196
197Our tests can be found in `lib/python/qmk/tests/`. You will find both unit and integration tests in this directory. We hope you will write both unit and integration tests for your code, but if you do not please favor integration tests.
198
199If your PR does not include a comprehensive set of tests please add comments like this to your code so that other people know where they can help:
200
201 # TODO(unassigned/<yourGithubUsername>): Write <unit|integration> tests
202
203We use [nose2](https://nose2.readthedocs.io/en/latest/getting_started.html) to run our tests. You can refer to the nose2 documentation for more details on what you can do in your test functions.
204
205## Linting Details
206
207We use flake8 to lint our code. Your code should pass flake8 before you open a PR. This will be checked when you run `qmk pytest` and by CI when you submit a PR.
diff --git a/lib/python/kle2xy.py b/lib/python/kle2xy.py
index 929144319..bff1d025b 100644
--- a/lib/python/kle2xy.py
+++ b/lib/python/kle2xy.py
@@ -46,7 +46,7 @@ class KLE2xy(list):
46 if 'name' in properties: 46 if 'name' in properties:
47 self.name = properties['name'] 47 self.name = properties['name']
48 48
49 def parse_layout(self, layout): 49 def parse_layout(self, layout): # noqa FIXME(skullydazed): flake8 says this has a complexity of 25, it should be refactored.
50 # Wrap this in a dictionary so hjson will parse KLE raw data 50 # Wrap this in a dictionary so hjson will parse KLE raw data
51 layout = '{"layout": [' + layout + ']}' 51 layout = '{"layout": [' + layout + ']}'
52 layout = hjson.loads(layout)['layout'] 52 layout = hjson.loads(layout)['layout']
diff --git a/lib/python/qmk/cli/compile.py b/lib/python/qmk/cli/compile.py
index 5c2980009..234ffb12c 100755
--- a/lib/python/qmk/cli/compile.py
+++ b/lib/python/qmk/cli/compile.py
@@ -2,9 +2,6 @@
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"""
5import json
6import os
7import sys
8import subprocess 5import subprocess
9from argparse import FileType 6from argparse import FileType
10 7
diff --git a/lib/python/qmk/cli/config.py b/lib/python/qmk/cli/config.py
index c4ee20cba..e17d8bb9b 100644
--- a/lib/python/qmk/cli/config.py
+++ b/lib/python/qmk/cli/config.py
@@ -1,8 +1,5 @@
1"""Read and write configuration settings 1"""Read and write configuration settings
2""" 2"""
3import os
4import subprocess
5
6from milc import cli 3from milc import cli
7 4
8 5
@@ -12,6 +9,54 @@ def print_config(section, key):
12 cli.echo('%s.%s{fg_cyan}={fg_reset}%s', section, key, cli.config[section][key]) 9 cli.echo('%s.%s{fg_cyan}={fg_reset}%s', section, key, cli.config[section][key])
13 10
14 11
12def show_config():
13 """Print the current configuration to stdout.
14 """
15 for section in cli.config:
16 for key in cli.config[section]:
17 print_config(section, key)
18
19
20def parse_config_token(config_token):
21 """Split a user-supplied configuration-token into its components.
22 """
23 section = option = value = None
24
25 if '=' in config_token and '.' not in config_token:
26 cli.log.error('Invalid configuration token, the key must be of the form <section>.<option>: %s', config_token)
27 return section, option, value
28
29 # Separate the key (<section>.<option>) from the value
30 if '=' in config_token:
31 key, value = config_token.split('=')
32 else:
33 key = config_token
34
35 # Extract the section and option from the key
36 if '.' in key:
37 section, option = key.split('.', 1)
38 else:
39 section = key
40
41 return section, option, value
42
43
44def set_config(section, option, value):
45 """Set a config key in the running config.
46 """
47 log_string = '%s.%s{fg_cyan}:{fg_reset} %s {fg_cyan}->{fg_reset} %s'
48 if cli.args.read_only:
49 log_string += ' {fg_red}(change not written)'
50
51 cli.echo(log_string, section, option, cli.config[section][option], value)
52
53 if not cli.args.read_only:
54 if value == 'None':
55 del cli.config[section][option]
56 else:
57 cli.config[section][option] = value
58
59
15@cli.argument('-ro', '--read-only', arg_only=True, action='store_true', help='Operate in read-only mode.') 60@cli.argument('-ro', '--read-only', arg_only=True, action='store_true', help='Operate in read-only mode.')
16@cli.argument('configs', nargs='*', arg_only=True, help='Configuration options to read or write.') 61@cli.argument('configs', nargs='*', arg_only=True, help='Configuration options to read or write.')
17@cli.subcommand("Read and write configuration settings.") 62@cli.subcommand("Read and write configuration settings.")
@@ -33,12 +78,7 @@ def config(cli):
33 No validation is done to ensure that the supplied section.key is actually used by qmk scripts. 78 No validation is done to ensure that the supplied section.key is actually used by qmk scripts.
34 """ 79 """
35 if not cli.args.configs: 80 if not cli.args.configs:
36 # Walk the config tree 81 return show_config()
37 for section in cli.config:
38 for key in cli.config[section]:
39 print_config(section, key)
40
41 return True
42 82
43 # Process config_tokens 83 # Process config_tokens
44 save_config = False 84 save_config = False
@@ -46,43 +86,23 @@ def config(cli):
46 for argument in cli.args.configs: 86 for argument in cli.args.configs:
47 # Split on space in case they quoted multiple config tokens 87 # Split on space in case they quoted multiple config tokens
48 for config_token in argument.split(' '): 88 for config_token in argument.split(' '):
49 # Extract the section, config_key, and value to write from the supplied config_token. 89 section, option, value = parse_config_token(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 90
62 # Validation 91 # Validation
63 if config_key and '.' in config_key: 92 if option and '.' in option:
64 cli.log.error('Config keys may not have more than one period! "%s" is not valid.', key) 93 cli.log.error('Config keys may not have more than one period! "%s" is not valid.', config_token)
65 return False 94 return False
66 95
67 # Do what the user wants 96 # Do what the user wants
68 if section and config_key and value: 97 if section and option and value:
69 # Write a config key 98 # Write a configuration option
70 log_string = '%s.%s{fg_cyan}:{fg_reset} %s {fg_cyan}->{fg_reset} %s' 99 set_config(section, option, value)
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: 100 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 101 save_config = True
82 102
83 elif section and config_key: 103 elif section and option:
84 # Display a single key 104 # Display a single key
85 print_config(section, config_key) 105 print_config(section, option)
86 106
87 elif section: 107 elif section:
88 # Display an entire section 108 # Display an entire section
diff --git a/lib/python/qmk/cli/doctor.py b/lib/python/qmk/cli/doctor.py
index c2723bfcb..1010eafb3 100755
--- a/lib/python/qmk/cli/doctor.py
+++ b/lib/python/qmk/cli/doctor.py
@@ -2,11 +2,9 @@
2 2
3Check up for QMK environment. 3Check up for QMK environment.
4""" 4"""
5import os
6import platform 5import platform
7import shutil 6import shutil
8import subprocess 7import subprocess
9from glob import glob
10 8
11from milc import cli 9from milc import cli
12 10
diff --git a/lib/python/qmk/cli/flash.py b/lib/python/qmk/cli/flash.py
index 37556aaaf..031cb9496 100644
--- a/lib/python/qmk/cli/flash.py
+++ b/lib/python/qmk/cli/flash.py
@@ -3,17 +3,11 @@
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.
4A bootloader must be specified. 4A bootloader must be specified.
5""" 5"""
6import os
7import sys
8import subprocess 6import subprocess
9from argparse import FileType
10
11from milc import cli
12from qmk.commands import create_make_command
13from qmk.commands import parse_configurator_json
14from qmk.commands import compile_configurator_json
15 7
16import qmk.path 8import qmk.path
9from milc import cli
10from qmk.commands import compile_configurator_json, create_make_command, parse_configurator_json
17 11
18 12
19def print_bootloader_help(): 13def print_bootloader_help():
diff --git a/lib/python/qmk/cli/json/keymap.py b/lib/python/qmk/cli/json/keymap.py
index 7b7553104..a030ab53d 100755
--- a/lib/python/qmk/cli/json/keymap.py
+++ b/lib/python/qmk/cli/json/keymap.py
@@ -2,7 +2,6 @@
2""" 2"""
3import json 3import json
4import os 4import os
5import sys
6 5
7from milc import cli 6from milc import cli
8 7
diff --git a/lib/python/qmk/cli/kle2json.py b/lib/python/qmk/cli/kle2json.py
index 5a4e97e3a..00f63d362 100755
--- a/lib/python/qmk/cli/kle2json.py
+++ b/lib/python/qmk/cli/kle2json.py
@@ -1,10 +1,8 @@
1"""Convert raw KLE to JSON 1"""Convert raw KLE to JSON
2
3""" 2"""
4import json 3import json
5import os 4import os
6from pathlib import Path 5from pathlib import Path
7from argparse import FileType
8from decimal import Decimal 6from decimal import Decimal
9from collections import OrderedDict 7from collections import OrderedDict
10 8
@@ -23,7 +21,7 @@ class CustomJSONEncoder(json.JSONEncoder):
23 return float(obj) 21 return float(obj)
24 except TypeError: 22 except TypeError:
25 pass 23 pass
26 return JSONEncoder.default(self, obj) 24 return json.JSONEncoder.default(self, obj)
27 25
28 26
29@cli.argument('filename', help='The KLE raw txt to convert') 27@cli.argument('filename', help='The KLE raw txt to convert')
diff --git a/lib/python/qmk/cli/list/keyboards.py b/lib/python/qmk/cli/list/keyboards.py
index 2a29ccb14..76e7760e8 100644
--- a/lib/python/qmk/cli/list/keyboards.py
+++ b/lib/python/qmk/cli/list/keyboards.py
@@ -1,27 +1,27 @@
1"""List the keyboards currently defined within QMK 1"""List the keyboards currently defined within QMK
2""" 2"""
3import os 3import os
4import re
5import glob 4import glob
6 5
7from milc import cli 6from milc import cli
8 7
8BASE_PATH = os.path.join(os.getcwd(), "keyboards") + os.path.sep
9KB_WILDCARD = os.path.join(BASE_PATH, "**", "rules.mk")
10
11
12def find_name(path):
13 """Determine the keyboard name by stripping off the base_path and rules.mk.
14 """
15 return path.replace(BASE_PATH, "").replace(os.path.sep + "rules.mk", "")
16
9 17
10@cli.subcommand("List the keyboards currently defined within QMK") 18@cli.subcommand("List the keyboards currently defined within QMK")
11def list_keyboards(cli): 19def list_keyboards(cli):
12 """List the keyboards currently defined within QMK 20 """List the keyboards currently defined within QMK
13 """ 21 """
14
15 base_path = os.path.join(os.getcwd(), "keyboards") + os.path.sep
16 kb_path_wildcard = os.path.join(base_path, "**", "rules.mk")
17
18 # find everywhere we have rules.mk where keymaps isn't in the path 22 # find everywhere we have rules.mk where keymaps isn't in the path
19 paths = [path for path in glob.iglob(kb_path_wildcard, recursive=True) if 'keymaps' not in path] 23 paths = [path for path in glob.iglob(KB_WILDCARD, recursive=True) if 'keymaps' not in path]
20
21 # strip the keyboard directory path prefix and rules.mk suffix and alphabetize
22 find_name = lambda path: path.replace(base_path, "").replace(os.path.sep + "rules.mk", "")
23 names = sorted(map(find_name, paths))
24 24
25 for name in names: 25 # Extract the keyboard name from the path and print it
26 # We echo instead of cli.log.info to allow easier piping of this output 26 for keyboard_name in sorted(map(find_name, paths)):
27 cli.echo(name) 27 print(keyboard_name)
diff --git a/lib/python/qmk/cli/pytest.py b/lib/python/qmk/cli/pytest.py
index 14613e1d9..09611d750 100644
--- a/lib/python/qmk/cli/pytest.py
+++ b/lib/python/qmk/cli/pytest.py
@@ -2,19 +2,15 @@
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 5import subprocess
6
6from milc import cli 7from milc import cli
7 8
8 9
9@cli.subcommand('QMK Python Unit Tests') 10@cli.subcommand('QMK Python Unit Tests')
10def pytest(cli): 11def pytest(cli):
11 """Use nose2 to run unittests 12 """Run several linting/testing commands.
12 """ 13 """
13 try: 14 flake8 = subprocess.run(['flake8', 'lib/python', 'bin/qmk'])
14 import nose2 15 nose2 = subprocess.run(['nose2', '-v'])
15 16 return flake8.returncode | nose2.returncode
16 except ImportError:
17 cli.log.error('Could not import nose2! Please install it with {fg_cyan}pip3 install nose2')
18 return False
19
20 nose2.discover(argv=['nose2', '-v'])
diff --git a/lib/python/qmk/keymap.py b/lib/python/qmk/keymap.py
index 396b53a6f..f4a2893f3 100644
--- a/lib/python/qmk/keymap.py
+++ b/lib/python/qmk/keymap.py
@@ -1,12 +1,8 @@
1"""Functions that help you work with QMK keymaps. 1"""Functions that help you work with QMK keymaps.
2""" 2"""
3import json
4import logging
5import os 3import os
6from traceback import format_exc
7 4
8import qmk.path 5import qmk.path
9from qmk.errors import NoSuchKeyboardError
10 6
11# The `keymap.c` template to use when a keyboard doesn't have its own 7# The `keymap.c` template to use when a keyboard doesn't have its own
12DEFAULT_KEYMAP_C = """#include QMK_KEYBOARD_H 8DEFAULT_KEYMAP_C = """#include QMK_KEYBOARD_H
diff --git a/lib/python/qmk/path.py b/lib/python/qmk/path.py
index 2149625cc..cf087265f 100644
--- a/lib/python/qmk/path.py
+++ b/lib/python/qmk/path.py
@@ -2,7 +2,6 @@
2""" 2"""
3import logging 3import logging
4import os 4import os
5from pkgutil import walk_packages
6 5
7from qmk.errors import NoSuchKeyboardError 6from qmk.errors import NoSuchKeyboardError
8 7
diff --git a/requirements.txt b/requirements.txt
index aa6ee1ba3..033b688fc 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -4,3 +4,5 @@ appdirs
4argcomplete 4argcomplete
5colorama 5colorama
6hjson 6hjson
7nose2
8flake8
diff --git a/setup.cfg b/setup.cfg
index 528512ac6..9e178ad65 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -1,4 +1,13 @@
1# Python settings for QMK 1# Python settings for QMK
2[flake8]
3ignore =
4 # QMK is ok with long lines.
5 E501
6per_file_ignores =
7 **/__init__.py:F401
8
9# Let's slowly crank this down
10max_complexity=16
2 11
3[yapf] 12[yapf]
4# Align closing bracket with visual indentation. 13# Align closing bracket with visual indentation.