summaryrefslogtreecommitdiffstats
path: root/lib/python
diff options
context:
space:
mode:
Diffstat (limited to 'lib/python')
-rw-r--r--lib/python/milc.py88
-rw-r--r--lib/python/qmk/cli/__init__.py7
-rw-r--r--lib/python/qmk/cli/cformat.py7
-rw-r--r--lib/python/qmk/cli/docs.py2
-rwxr-xr-xlib/python/qmk/cli/doctor.py13
-rwxr-xr-xlib/python/qmk/cli/hello.py2
-rwxr-xr-xlib/python/qmk/cli/json2c.py8
-rwxr-xr-xlib/python/qmk/cli/kle2json.py10
-rwxr-xr-xlib/python/qmk/cli/new/keymap.py2
-rwxr-xr-xlib/python/qmk/cli/pyformat.py2
-rw-r--r--lib/python/qmk/cli/pytest.py2
-rw-r--r--lib/python/qmk/commands.py4
-rw-r--r--lib/python/qmk/keymap.py19
13 files changed, 97 insertions, 69 deletions
diff --git a/lib/python/milc.py b/lib/python/milc.py
index 83edfc7f51..eb18984eb3 100644
--- a/lib/python/milc.py
+++ b/lib/python/milc.py
@@ -242,15 +242,24 @@ class SubparserWrapper(object):
This also stores the default for the argument in `self.cli.default_arguments`.
"""
- if 'action' in kwargs and kwargs['action'] == 'store_boolean':
+ if kwargs.get('action') == 'store_boolean':
# Store boolean will call us again with the enable/disable flag arguments
return handle_store_boolean(self, *args, **kwargs)
self.cli.acquire_lock()
+ argument_name = self.cli.get_argument_name(*args, **kwargs)
+
self.subparser.add_argument(*args, **kwargs)
+
+ if kwargs.get('action') == 'store_false':
+ self.cli._config_store_false.append(argument_name)
+
+ if kwargs.get('action') == 'store_true':
+ self.cli._config_store_true.append(argument_name)
+
if self.submodule not in self.cli.default_arguments:
self.cli.default_arguments[self.submodule] = {}
- self.cli.default_arguments[self.submodule][self.cli.get_argument_name(*args, **kwargs)] = kwargs.get('default')
+ self.cli.default_arguments[self.submodule][argument_name] = kwargs.get('default')
self.cli.release_lock()
@@ -268,11 +277,13 @@ class MILC(object):
# Define some basic info
self.acquire_lock()
+ self._config_store_true = []
+ self._config_store_false = []
self._description = None
self._entrypoint = None
self._inside_context_manager = False
self.ansi = ansi_colors
- self.arg_only = []
+ self.arg_only = {}
self.config = self.config_source = None
self.config_file = None
self.default_arguments = {}
@@ -377,7 +388,7 @@ class MILC(object):
self.add_argument('--log-file', help='File to write log messages to')
self.add_argument('--color', action='store_boolean', default=True, help='color in output')
self.add_argument('--config-file', help='The location for the configuration file')
- self.arg_only.append('config_file')
+ self.arg_only['config_file'] = ['general']
def add_subparsers(self, title='Sub-commands', **kwargs):
if self._inside_context_manager:
@@ -427,17 +438,20 @@ class MILC(object):
raise RuntimeError('You must run this before the with statement!')
def argument_function(handler):
- if 'arg_only' in kwargs and kwargs['arg_only']:
+ subcommand_name = handler.__name__.replace("_", "-")
+
+ if kwargs.get('arg_only'):
arg_name = self.get_argument_name(*args, **kwargs)
- self.arg_only.append(arg_name)
+ if arg_name not in self.arg_only:
+ self.arg_only[arg_name] = []
+ self.arg_only[arg_name].append(subcommand_name)
del kwargs['arg_only']
- name = handler.__name__.replace("_", "-")
if handler is self._entrypoint:
self.add_argument(*args, **kwargs)
- elif name in self.subcommands:
- self.subcommands[name].add_argument(*args, **kwargs)
+ elif subcommand_name in self.subcommands:
+ self.subcommands[subcommand_name].add_argument(*args, **kwargs)
else:
raise RuntimeError('Decorated function is not entrypoint or subcommand!')
@@ -511,35 +525,37 @@ class MILC(object):
if argument in ('subparsers', 'entrypoint'):
continue
- if argument not in self.arg_only:
- # Find the argument's section
- # Underscores in command's names are converted to dashes during initialization.
- # TODO(Erovia) Find a better solution
- entrypoint_name = self._entrypoint.__name__.replace("_", "-")
- if entrypoint_name in self.default_arguments and argument in self.default_arguments[entrypoint_name]:
- argument_found = True
- section = self._entrypoint.__name__
- if argument in self.default_arguments['general']:
- argument_found = True
- section = 'general'
-
- if not argument_found:
- raise RuntimeError('Could not find argument in `self.default_arguments`. This should be impossible!')
- exit(1)
+ # Find the argument's section
+ # Underscores in command's names are converted to dashes during initialization.
+ # TODO(Erovia) Find a better solution
+ entrypoint_name = self._entrypoint.__name__.replace("_", "-")
+ if entrypoint_name in self.default_arguments and argument in self.default_arguments[entrypoint_name]:
+ argument_found = True
+ section = self._entrypoint.__name__
+ if argument in self.default_arguments['general']:
+ argument_found = True
+ section = 'general'
+
+ if not argument_found:
+ raise RuntimeError('Could not find argument in `self.default_arguments`. This should be impossible!')
+ exit(1)
+
+ if argument not in self.arg_only or section not in self.arg_only[argument]:
+ # Determine the arg value and source
+ arg_value = getattr(self.args, argument)
+ if argument in self._config_store_true and arg_value:
+ passed_on_cmdline = True
+ elif argument in self._config_store_false and not arg_value:
+ passed_on_cmdline = True
+ elif arg_value is not None:
+ passed_on_cmdline = True
+ else:
+ passed_on_cmdline = False
# Merge this argument into self.config
- if argument in self.default_arguments['general'] or argument in self.default_arguments[entrypoint_name]:
- arg_value = getattr(self.args, argument)
- if arg_value is not None:
- self.config[section][argument] = arg_value
- self.config_source[section][argument] = 'argument'
- else:
- if argument not in self.config[entrypoint_name]:
- # Check if the argument exist for this section
- arg = getattr(self.args, argument)
- if arg is not None:
- self.config[section][argument] = arg
- self.config_source[section][argument] = 'argument'
+ if passed_on_cmdline and (argument in self.default_arguments['general'] or argument in self.default_arguments[entrypoint_name] or argument not in self.config[entrypoint_name]):
+ self.config[section][argument] = arg_value
+ self.config_source[section][argument] = 'argument'
self.release_lock()
diff --git a/lib/python/qmk/cli/__init__.py b/lib/python/qmk/cli/__init__.py
index eb524217cd..394a1353bc 100644
--- a/lib/python/qmk/cli/__init__.py
+++ b/lib/python/qmk/cli/__init__.py
@@ -2,6 +2,8 @@
We list each subcommand here explicitly because all the reliable ways of searching for modules are slow and delay startup.
"""
+import sys
+
from milc import cli
from . import cformat
@@ -19,5 +21,6 @@ from . import new
from . import pyformat
from . import pytest
-if not hasattr(cli, 'config_source'):
- cli.log.warning("Your QMK CLI is out of date. Please upgrade with `pip3 install --upgrade qmk` or by using your package manager.")
+if sys.version_info[0] != 3 or sys.version_info[1] < 6:
+ cli.log.error('Your Python is too old! Please upgrade to Python 3.6 or later.')
+ exit(127)
diff --git a/lib/python/qmk/cli/cformat.py b/lib/python/qmk/cli/cformat.py
index 7e3a91dcf0..0cd8b6192a 100644
--- a/lib/python/qmk/cli/cformat.py
+++ b/lib/python/qmk/cli/cformat.py
@@ -22,9 +22,8 @@ def cformat_run(files, all_files):
cli.log.warn('No changes detected. Use "qmk cformat -a" to format all files')
return False
if files and all_files:
- cli.log.warning('Filenames passed with -a, only formatting: %s', ','.join(cli.args.files))
- # 3.6+: Can remove the str casting, python will cast implicitly
- subprocess.run(clang_format + [str(file) for file in files], check=True)
+ cli.log.warning('Filenames passed with -a, only formatting: %s', ','.join(files))
+ subprocess.run(clang_format + [file for file in files], check=True)
cli.log.info('Successfully formatted the C code.')
except subprocess.CalledProcessError:
@@ -35,7 +34,7 @@ def cformat_run(files, all_files):
@cli.argument('-a', '--all-files', arg_only=True, action='store_true', help='Format all core files.')
@cli.argument('-b', '--base-branch', default='origin/master', help='Branch to compare to diffs to.')
@cli.argument('files', nargs='*', arg_only=True, help='Filename(s) to format.')
-@cli.subcommand("Format C code according to QMK's style.")
+@cli.subcommand("Format C code according to QMK's style.", hidden=False if cli.config.user.developer else True)
def cformat(cli):
"""Format C code according to QMK's style.
"""
diff --git a/lib/python/qmk/cli/docs.py b/lib/python/qmk/cli/docs.py
index 163c8b8015..5816106504 100644
--- a/lib/python/qmk/cli/docs.py
+++ b/lib/python/qmk/cli/docs.py
@@ -7,7 +7,7 @@ from milc import cli
@cli.argument('-p', '--port', default=8936, type=int, help='Port number to use.')
-@cli.subcommand('Run a local webserver for QMK documentation.')
+@cli.subcommand('Run a local webserver for QMK documentation.', hidden=False if cli.config.user.developer else True)
def docs(cli):
"""Spin up a local HTTPServer instance for the QMK docs.
"""
diff --git a/lib/python/qmk/cli/doctor.py b/lib/python/qmk/cli/doctor.py
index 3e6f6fe54e..3c46248372 100755
--- a/lib/python/qmk/cli/doctor.py
+++ b/lib/python/qmk/cli/doctor.py
@@ -135,16 +135,15 @@ def check_udev_rules():
}
if udev_dir.exists():
- udev_rules = [str(rule_file) for rule_file in udev_dir.glob('*.rules')]
+ udev_rules = [rule_file for rule_file in udev_dir.glob('*.rules')]
current_rules = set()
# Collect all rules from the config files
for rule_file in udev_rules:
- with open(rule_file, "r") as fd:
- for line in fd.readlines():
- line = line.strip()
- if not line.startswith("#") and len(line):
- current_rules.add(line)
+ for line in rule_file.read_text().split('\n'):
+ line = line.strip()
+ if not line.startswith("#") and len(line):
+ current_rules.add(line)
# Check if the desired rules are among the currently present rules
for bootloader, rules in desired_rules.items():
@@ -191,7 +190,7 @@ def is_executable(command):
cli.log.debug('Found {fg_cyan}%s', command)
return True
- cli.log.error("{fg_red}Can't run `%s %s`", (command, version_arg))
+ cli.log.error("{fg_red}Can't run `%s %s`", command, version_arg)
return False
diff --git a/lib/python/qmk/cli/hello.py b/lib/python/qmk/cli/hello.py
index bee28c3013..5119188a07 100755
--- a/lib/python/qmk/cli/hello.py
+++ b/lib/python/qmk/cli/hello.py
@@ -6,7 +6,7 @@ from milc import cli
@cli.argument('-n', '--name', default='World', help='Name to greet.')
-@cli.subcommand('QMK Hello World.')
+@cli.subcommand('QMK Hello World.', hidden=False if cli.config.user.developer else True)
def hello(cli):
"""Log a friendly greeting.
"""
diff --git a/lib/python/qmk/cli/json2c.py b/lib/python/qmk/cli/json2c.py
index 46c4d04bb7..5218405070 100755
--- a/lib/python/qmk/cli/json2c.py
+++ b/lib/python/qmk/cli/json2c.py
@@ -10,29 +10,27 @@ import qmk.path
@cli.argument('-o', '--output', arg_only=True, type=qmk.path.normpath, help='File to write to')
@cli.argument('-q', '--quiet', arg_only=True, action='store_true', help="Quiet mode, only output error messages")
-@cli.argument('filename', arg_only=True, help='Configurator JSON file')
+@cli.argument('filename', type=qmk.path.normpath, arg_only=True, help='Configurator JSON file')
@cli.subcommand('Creates a keymap.c from a QMK Configurator export.')
def json2c(cli):
"""Generate a keymap.c from a configurator export.
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.
"""
- cli.args.filename = qmk.path.normpath(cli.args.filename)
-
# Error checking
if not cli.args.filename.exists():
cli.log.error('JSON file does not exist!')
cli.print_usage()
exit(1)
- if str(cli.args.filename) == '-':
+ if cli.args.filename.name == '-':
# TODO(skullydazed/anyone): Read file contents from STDIN
cli.log.error('Reading from STDIN is not (yet) supported.')
cli.print_usage()
exit(1)
# Environment processing
- if cli.args.output == ('-'):
+ if cli.args.output.name == ('-'):
cli.args.output = None
# Parse the configurator json
diff --git a/lib/python/qmk/cli/kle2json.py b/lib/python/qmk/cli/kle2json.py
index 00f63d3622..5268462f92 100755
--- a/lib/python/qmk/cli/kle2json.py
+++ b/lib/python/qmk/cli/kle2json.py
@@ -26,7 +26,7 @@ class CustomJSONEncoder(json.JSONEncoder):
@cli.argument('filename', help='The KLE raw txt to convert')
@cli.argument('-f', '--force', action='store_true', help='Flag to overwrite current info.json')
-@cli.subcommand('Convert a KLE layout to a Configurator JSON')
+@cli.subcommand('Convert a KLE layout to a Configurator JSON', hidden=False if cli.config.user.developer else True)
def kle2json(cli):
"""Convert a KLE layout to QMK's layout format.
""" # If filename is a path
@@ -37,12 +37,12 @@ def kle2json(cli):
file_path = Path(os.environ['ORIG_CWD'], cli.args.filename)
# Check for valid file_path for more graceful failure
if not file_path.exists():
- return cli.log.error('File {fg_cyan}%s{style_reset_all} was not found.', str(file_path))
+ return cli.log.error('File {fg_cyan}%s{style_reset_all} was not found.', file_path)
out_path = file_path.parent
raw_code = file_path.open().read()
# Check if info.json exists, allow overwrite with force
if Path(out_path, "info.json").exists() and not cli.args.force:
- cli.log.error('File {fg_cyan}%s/info.json{style_reset_all} already exists, use -f or --force to overwrite.', str(out_path))
+ cli.log.error('File {fg_cyan}%s/info.json{style_reset_all} already exists, use -f or --force to overwrite.', out_path)
return False
try:
# Convert KLE raw to x/y coordinates (using kle2xy package from skullydazed)
@@ -69,7 +69,7 @@ def kle2json(cli):
# Replace layout in keyboard json
keyboard = keyboard.replace('"LAYOUT_JSON_HERE"', layout)
# Write our info.json
- file = open(str(out_path) + "/info.json", "w")
+ file = open(out_path + "/info.json", "w")
file.write(keyboard)
file.close()
- cli.log.info('Wrote out {fg_cyan}%s/info.json', str(out_path))
+ cli.log.info('Wrote out {fg_cyan}%s/info.json', out_path)
diff --git a/lib/python/qmk/cli/new/keymap.py b/lib/python/qmk/cli/new/keymap.py
index 5ae2628565..474fe7974f 100755
--- a/lib/python/qmk/cli/new/keymap.py
+++ b/lib/python/qmk/cli/new/keymap.py
@@ -40,7 +40,7 @@ def new_keymap(cli):
exit(1)
# create user directory with default keymap files
- shutil.copytree(str(keymap_path_default), str(keymap_path_new), symlinks=True)
+ shutil.copytree(keymap_path_default, keymap_path_new, symlinks=True)
# end message to user
cli.log.info("%s keymap directory created in: %s", keymap, keymap_path_new)
diff --git a/lib/python/qmk/cli/pyformat.py b/lib/python/qmk/cli/pyformat.py
index a53ba40c0a..1464443804 100755
--- a/lib/python/qmk/cli/pyformat.py
+++ b/lib/python/qmk/cli/pyformat.py
@@ -5,7 +5,7 @@ from milc import cli
import subprocess
-@cli.subcommand("Format python code according to QMK's style.")
+@cli.subcommand("Format python code according to QMK's style.", hidden=False if cli.config.user.developer else True)
def pyformat(cli):
"""Format python code according to QMK's style.
"""
diff --git a/lib/python/qmk/cli/pytest.py b/lib/python/qmk/cli/pytest.py
index 09611d750f..5417a9cb34 100644
--- a/lib/python/qmk/cli/pytest.py
+++ b/lib/python/qmk/cli/pytest.py
@@ -7,7 +7,7 @@ import subprocess
from milc import cli
-@cli.subcommand('QMK Python Unit Tests')
+@cli.subcommand('QMK Python Unit Tests', hidden=False if cli.config.user.developer else True)
def pytest(cli):
"""Run several linting/testing commands.
"""
diff --git a/lib/python/qmk/commands.py b/lib/python/qmk/commands.py
index 3424cdf085..5d2a03c9a8 100644
--- a/lib/python/qmk/commands.py
+++ b/lib/python/qmk/commands.py
@@ -5,6 +5,7 @@ import os
import platform
import subprocess
import shlex
+import shutil
import qmk.keymap
@@ -28,11 +29,12 @@ def create_make_command(keyboard, keymap, target=None):
A command that can be run to make the specified keyboard and keymap
"""
make_args = [keyboard, keymap]
+ make_cmd = 'gmake' if shutil.which('gmake') else 'make'
if target:
make_args.append(target)
- return ['make', ':'.join(make_args)]
+ return [make_cmd, ':'.join(make_args)]
def compile_configurator_json(user_keymap, bootloader=None):
diff --git a/lib/python/qmk/keymap.py b/lib/python/qmk/keymap.py
index b91ba89bed..69cdc8d5b5 100644
--- a/lib/python/qmk/keymap.py
+++ b/lib/python/qmk/keymap.py
@@ -1,6 +1,5 @@
"""Functions that help you work with QMK keymaps.
"""
-import os
from pathlib import Path
import qmk.path
@@ -11,7 +10,7 @@ DEFAULT_KEYMAP_C = """#include QMK_KEYBOARD_H
/* THIS FILE WAS GENERATED!
*
- * This file was generated by qmk-compile-json. You may or may not want to
+ * This file was generated by qmk json2c. You may or may not want to
* edit it directly.
*/
@@ -39,6 +38,15 @@ def template(keyboard):
return DEFAULT_KEYMAP_C
+def _strip_any(keycode):
+ """Remove ANY() from a keycode.
+ """
+ if keycode.startswith('ANY(') and keycode.endswith(')'):
+ keycode = keycode[4:-1]
+
+ return keycode
+
+
def generate(keyboard, layout, layers):
"""Returns a keymap.c for the specified keyboard, layout, and layers.
@@ -53,9 +61,12 @@ def generate(keyboard, layout, layers):
An array of arrays describing the keymap. Each item in the inner array should be a string that is a valid QMK keycode.
"""
layer_txt = []
+
for layer_num, layer in enumerate(layers):
if layer_num != 0:
layer_txt[-1] = layer_txt[-1] + ','
+
+ layer = map(_strip_any, layer)
layer_keys = ', '.join(layer)
layer_txt.append('\t[%s] = %s(%s)' % (layer_num, layout, layer_keys))
@@ -115,7 +126,7 @@ def list_keymaps(keyboard_name):
while kb_path != keyboards_dir:
keymaps_dir = kb_path / "keymaps"
if keymaps_dir.exists():
- names = names.union([keymap for keymap in os.listdir(str(keymaps_dir)) if (keymaps_dir / keymap / "keymap.c").is_file()])
+ names = names.union([keymap for keymap in keymaps_dir.iterdir() if (keymaps_dir / keymap / "keymap.c").is_file()])
kb_path = kb_path.parent
# if community layouts are supported, get them
@@ -123,6 +134,6 @@ def list_keymaps(keyboard_name):
for layout in rules_mk["LAYOUTS"].split():
cl_path = Path.cwd() / "layouts" / "community" / layout
if cl_path.exists():
- names = names.union([keymap for keymap in os.listdir(str(cl_path)) if (cl_path / keymap / "keymap.c").is_file()])
+ names = names.union([keymap for keymap in cl_path.iterdir() if (cl_path / keymap / "keymap.c").is_file()])
return sorted(names)