Configuration system for CLI (#6708)
* Rework how bin/qmk handles subcommands * qmk config wip * Code to show all configs * Fully working `qmk config` command * Mark some CLI arguments so they don't pollute the config file * Fleshed out config support, nicer subcommand support * sync with installable cli * pyformat * Add a test for subcommand_modules * Documentation for the `qmk config` command * split config_token on space so qmk config is more predictable * Rework how subcommands are imported * Document `arg_only` * Document deleting from CLI * Document how multiple operations work * Add cli config to the doc index * Add tests for the cli commands * Make running the tests more reliable * Be more selective about building all default keymaps * Update new-keymap to fit the new subcommand style * Add documentation about writing CLI scripts * Document new-keyboard * Update docs/cli_configuration.md Co-Authored-By: noroadsleft <18669334+noroadsleft@users.noreply.github.com> * Update docs/cli_development.md Co-Authored-By: noroadsleft <18669334+noroadsleft@users.noreply.github.com> * Update docs/cli_development.md Co-Authored-By: noroadsleft <18669334+noroadsleft@users.noreply.github.com> * Update docs/cli_development.md Co-Authored-By: noroadsleft <18669334+noroadsleft@users.noreply.github.com> * Address yan's comments. * Apply suggestions from code review suggestions from @noahfrederick Co-Authored-By: Noah Frederick <code@noahfrederick.com> * Apply suggestions from code review Co-Authored-By: Noah Frederick <code@noahfrederick.com> * Remove pip3 from the test runnerbetter_chibios_wait 0.7.26
parent
2f49cae9bc
commit
d569f08771
@ -1 +0,0 @@ |
|||||||
qmk |
|
@ -1 +0,0 @@ |
|||||||
qmk |
|
@ -1 +0,0 @@ |
|||||||
qmk |
|
@ -1 +0,0 @@ |
|||||||
qmk |
|
@ -0,0 +1,121 @@ |
|||||||
|
# QMK CLI Configuration |
||||||
|
|
||||||
|
This document explains how `qmk config` works. |
||||||
|
|
||||||
|
# Introduction |
||||||
|
|
||||||
|
Configuration for QMK CLI is a key/value system. Each key consists of a subcommand and an argument name separated by a period. This allows for a straightforward and direct translation between config keys and the arguments they set. |
||||||
|
|
||||||
|
## Simple Example |
||||||
|
|
||||||
|
As an example let's look at the command `qmk compile --keyboard clueboard/66/rev4 --keymap default`. |
||||||
|
|
||||||
|
There are two command line arguments that could be read from configuration instead: |
||||||
|
|
||||||
|
* `compile.keyboard` |
||||||
|
* `compile.keymap` |
||||||
|
|
||||||
|
Let's set these now: |
||||||
|
|
||||||
|
``` |
||||||
|
$ qmk config compile.keyboard=clueboard/66/rev4 compile.keymap=default |
||||||
|
compile.keyboard: None -> clueboard/66/rev4 |
||||||
|
compile.keymap: None -> default |
||||||
|
Ψ Wrote configuration to '/Users/example/Library/Application Support/qmk/qmk.ini' |
||||||
|
``` |
||||||
|
|
||||||
|
Now I can run `qmk compile` without specifying my keyboard and keymap each time. |
||||||
|
|
||||||
|
## Setting User Defaults |
||||||
|
|
||||||
|
Sometimes you want to share a setting between multiple commands. For example, multiple commands take the argument `--keyboard`. Rather than setting this value for every command you can set a user value which will be used by any command that takes that argument. |
||||||
|
|
||||||
|
Example: |
||||||
|
|
||||||
|
``` |
||||||
|
$ qmk config user.keyboard=clueboard/66/rev4 user.keymap=default |
||||||
|
user.keyboard: None -> clueboard/66/rev4 |
||||||
|
user.keymap: None -> default |
||||||
|
Ψ Wrote configuration to '/Users/example/Library/Application Support/qmk/qmk.ini' |
||||||
|
``` |
||||||
|
|
||||||
|
# CLI Documentation (`qmk config`) |
||||||
|
|
||||||
|
The `qmk config` command is used to interact with the underlying configuration. When run with no argument it shows the current configuration. When arguments are supplied they are assumed to be configuration tokens, which are strings containing no spaces with the following form: |
||||||
|
|
||||||
|
<subcommand|general|default>[.<key>][=<value>] |
||||||
|
|
||||||
|
## Setting Configuration Values |
||||||
|
|
||||||
|
You can set configuration values by putting an equal sign (=) into your config key. The key must always be the full `<section>.<key>` form. |
||||||
|
|
||||||
|
Example: |
||||||
|
|
||||||
|
``` |
||||||
|
$ qmk config default.keymap=default |
||||||
|
default.keymap: None -> default |
||||||
|
Ψ Wrote configuration to '/Users/example/Library/Application Support/qmk/qmk.ini' |
||||||
|
``` |
||||||
|
|
||||||
|
## Reading Configuration Values |
||||||
|
|
||||||
|
You can read configuration values for the entire configuration, a single key, or for an entire section. You can also specify multiple keys to display more than one value. |
||||||
|
|
||||||
|
### Entire Configuration Example |
||||||
|
|
||||||
|
qmk config |
||||||
|
|
||||||
|
### Whole Section Example |
||||||
|
|
||||||
|
qmk config compile |
||||||
|
|
||||||
|
### Single Key Example |
||||||
|
|
||||||
|
qmk config compile.keyboard |
||||||
|
|
||||||
|
### Multiple Keys Example |
||||||
|
|
||||||
|
qmk config user compile.keyboard compile.keymap |
||||||
|
|
||||||
|
## Deleting Configuration Values |
||||||
|
|
||||||
|
You can delete a configuration value by setting it to the special string `None`. |
||||||
|
|
||||||
|
Example: |
||||||
|
|
||||||
|
``` |
||||||
|
$ qmk config default.keymap=None |
||||||
|
default.keymap: default -> None |
||||||
|
Ψ Wrote configuration to '/Users/example/Library/Application Support/qmk/qmk.ini' |
||||||
|
``` |
||||||
|
|
||||||
|
## Multiple Operations |
||||||
|
|
||||||
|
You can combine multiple read and write operations into a single command. They will be executed and displayed in order: |
||||||
|
|
||||||
|
``` |
||||||
|
$ qmk config compile default.keymap=default compile.keymap=None |
||||||
|
compile.keymap=skully |
||||||
|
compile.keyboard=clueboard/66_hotswap/gen1 |
||||||
|
default.keymap: None -> default |
||||||
|
compile.keymap: skully -> None |
||||||
|
Ψ Wrote configuration to '/Users/example/Library/Application Support/qmk/qmk.ini' |
||||||
|
``` |
||||||
|
|
||||||
|
# User Configuration Options |
||||||
|
|
||||||
|
| Key | Default Value | Description | |
||||||
|
|-----|---------------|-------------| |
||||||
|
| user.keyboard | None | The keyboard path (Example: `clueboard/66/rev4`) | |
||||||
|
| user.keymap | None | The keymap name (Example: `default`) | |
||||||
|
| user.name | None | The user's github username. | |
||||||
|
|
||||||
|
# All Configuration Options |
||||||
|
|
||||||
|
| Key | Default Value | Description | |
||||||
|
|-----|---------------|-------------| |
||||||
|
| compile.keyboard | None | The keyboard path (Example: `clueboard/66/rev4`) | |
||||||
|
| compile.keymap | None | The keymap name (Example: `default`) | |
||||||
|
| hello.name | None | The name to greet when run. | |
||||||
|
| new_keyboard.keyboard | None | The keyboard path (Example: `clueboard/66/rev4`) | |
||||||
|
| new_keyboard.keymap | None | The keymap name (Example: `default`) | |
@ -0,0 +1,175 @@ |
|||||||
|
# QMK CLI Development |
||||||
|
|
||||||
|
This document has useful information for developers wishing to write new `qmk` subcommands. |
||||||
|
|
||||||
|
# Overview |
||||||
|
|
||||||
|
The QMK CLI operates using the subcommand pattern made famous by git. The main `qmk` script is simply there to setup the environment and pick the correct entrypoint to run. Each subcommand is a self-contained module with an entrypoint (decorated by `@cli.subcommand()`) that performs some action and returns a shell returncode, or None. |
||||||
|
|
||||||
|
# Subcommands |
||||||
|
|
||||||
|
[MILC](https://github.com/clueboard/milc) is the CLI framework `qmk` uses to handle argument parsing, configuration, logging, and many other features. It lets you focus on writing your tool without wasting your time writing glue code. |
||||||
|
|
||||||
|
Subcommands in the local CLI are always found in `qmk_firmware/lib/python/qmk/cli`. |
||||||
|
|
||||||
|
Let's start by looking at an example subcommand. This is `lib/python/qmk/cli/hello.py`: |
||||||
|
|
||||||
|
```python |
||||||
|
"""QMK Python Hello World |
||||||
|
|
||||||
|
This is an example QMK CLI script. |
||||||
|
""" |
||||||
|
from milc import cli |
||||||
|
|
||||||
|
|
||||||
|
@cli.argument('-n', '--name', default='World', help='Name to greet.') |
||||||
|
@cli.subcommand('QMK Hello World.') |
||||||
|
def hello(cli): |
||||||
|
"""Log a friendly greeting. |
||||||
|
""" |
||||||
|
cli.log.info('Hello, %s!', cli.config.hello.name) |
||||||
|
``` |
||||||
|
|
||||||
|
First we import the `cli` object from `milc`. This is how we interact with the user and control the script's behavior. We use `@cli.argument()` to define a command line flag, `--name`. This also creates a configuration variable named `hello.name` (and the corresponding `user.name`) which the user can set so they don't have to specify the argument. The `cli.subcommand()` decorator designates this function as a subcommand. The name of the subcommand will be taken from the name of the function. |
||||||
|
|
||||||
|
Once inside our function we find a typical "Hello, World!" program. We use `cli.log` to access the underlying [Logger Object](https://docs.python.org/3.5/library/logging.html#logger-objects), whose behavior is user controllable. We also access the value for name supplied by the user as `cli.config.hello.name`. The value for `cli.config.hello.name` will be determined by looking at the `--name` argument supplied by the user, if not provided it will use the value in the `qmk.ini` config file, and if neither of those is provided it will fall back to the default supplied in the `cli.argument()` decorator. |
||||||
|
|
||||||
|
# User Interaction |
||||||
|
|
||||||
|
MILC and the QMK CLI have several nice tools for interacting with the user. Using these standard tools will allow you to colorize your text for easier interactions, and allow the user to control when and how that information is displayed and stored. |
||||||
|
|
||||||
|
## Printing Text |
||||||
|
|
||||||
|
There are two main methods for outputting text in a subcommand- `cli.log` and `cli.echo()`. They operate in similar ways but you should prefer to use `cli.log.info()` for most general purpose printing. |
||||||
|
|
||||||
|
You can use special tokens to colorize your text, to make it easier to understand the output of your program. See [Colorizing Text](#colorizing-text) below. |
||||||
|
|
||||||
|
Both of these methods support built-in string formatting using python's [printf style string format operations](https://docs.python.org/3.5/library/stdtypes.html#old-string-formatting). You can use tokens such as `%s` and `%d` within your text strings then pass the values as arguments. See our Hello, World program above for an example. |
||||||
|
|
||||||
|
You should never use the format operator (`%`) directly, always pass values as arguments. |
||||||
|
|
||||||
|
### Logging (`cli.log`) |
||||||
|
|
||||||
|
The `cli.log` object gives you access to a [Logger Object](https://docs.python.org/3.5/library/logging.html#logger-objects). We have configured our log output to show the user a nice emoji for each log level (or the log level name if their terminal does not support unicode.) This way the user can tell at a glance which messages are most important when something goes wrong. |
||||||
|
|
||||||
|
The default log level is `INFO`. If the user runs `qmk -v <subcommand>` the default log level will be set to `DEBUG`. |
||||||
|
|
||||||
|
| Function | Emoji | |
||||||
|
|----------|-------| |
||||||
|
| cli.log.critical | `{bg_red}{fg_white}¬_¬{style_reset_all}` | |
||||||
|
| cli.log.error | `{fg_red}☒{style_reset_all}` | |
||||||
|
| cli.log.warning | `{fg_yellow}⚠{style_reset_all}` | |
||||||
|
| cli.log.info | `{fg_blue}Ψ{style_reset_all}` | |
||||||
|
| cli.log.debug | `{fg_cyan}☐{style_reset_all}` | |
||||||
|
| cli.log.notset | `{style_reset_all}¯\\_(o_o)_/¯` | |
||||||
|
|
||||||
|
### Printing (`cli.echo`) |
||||||
|
|
||||||
|
Sometimes you simply need to print text outside of the log system. This is appropriate if you are outputting fixed data or writing out something that should never be logged. Most of the time you should prefer `cli.log.info()` over `cli.echo`. |
||||||
|
|
||||||
|
### Colorizing Text |
||||||
|
|
||||||
|
You can colorize the output of your text by including color tokens within text. Use color to highlight, not to convey information. Remember that the user can disable color, and your subcommand should still be usable if they do. |
||||||
|
|
||||||
|
You should generally avoid setting the background color, unless it's integral to what you are doing. Remember that users have a lot of preferences when it comes to their terminal color, so you should pick colors that work well against both black and white backgrounds. |
||||||
|
|
||||||
|
Colors prefixed with 'fg' will affect the foreground (text) color. Colors prefixed with 'bg' will affect the background color. |
||||||
|
|
||||||
|
| Color | Background | Extended Background | Foreground | Extended Foreground| |
||||||
|
|-------|------------|---------------------|------------|--------------------| |
||||||
|
| Black | {bg_black} | {bg_lightblack_ex} | {fg_black} | {fg_lightblack_ex} | |
||||||
|
| Blue | {bg_blue} | {bg_lightblue_ex} | {fg_blue} | {fg_lightblue_ex} | |
||||||
|
| Cyan | {bg_cyan} | {bg_lightcyan_ex} | {fg_cyan} | {fg_lightcyan_ex} | |
||||||
|
| Green | {bg_green} | {bg_lightgreen_ex} | {fg_green} | {fg_lightgreen_ex} | |
||||||
|
| Magenta | {bg_magenta} | {bg_lightmagenta_ex} | {fg_magenta} | {fg_lightmagenta_ex} | |
||||||
|
| Red | {bg_red} | {bg_lightred_ex} | {fg_red} | {fg_lightred_ex} | |
||||||
|
| White | {bg_white} | {bg_lightwhite_ex} | {fg_white} | {fg_lightwhite_ex} | |
||||||
|
| Yellow | {bg_yellow} | {bg_lightyellow_ex} | {fg_yellow} | {fg_lightyellow_ex} | |
||||||
|
|
||||||
|
There are also control sequences that can be used to change the behavior of |
||||||
|
ANSI output: |
||||||
|
|
||||||
|
| Control Sequences | Description | |
||||||
|
|-------------------|-------------| |
||||||
|
| {style_bright} | Make the text brighter | |
||||||
|
| {style_dim} | Make the text dimmer | |
||||||
|
| {style_normal} | Make the text normal (neither `{style_bright}` nor `{style_dim}`) | |
||||||
|
| {style_reset_all} | Reset all text attributes to default. (This is automatically added to the end of every string.) | |
||||||
|
| {bg_reset} | Reset the background color to the user's default | |
||||||
|
| {fg_reset} | Reset the foreground color to the user's default | |
||||||
|
|
||||||
|
# Arguments and Configuration |
||||||
|
|
||||||
|
QMK handles the details of argument parsing and configuration for you. When you add a new argument it is automatically incorporated into the config tree based on your subcommand's name and the long name of the argument. You can access this configuration in `cli.config`, using either attribute-style access (`cli.config.<subcommand>.<argument>`) or dictionary-style access (`cli.config['<subcommand>']['<argument>']`). |
||||||
|
|
||||||
|
Under the hood QMK uses [ConfigParser](https://docs.python.org/3/library/configparser.html) to store configurations. This gives us an easy and straightforward way to represent the configuration in a human-editable way. We have wrapped access to this configuration to provide some nicities that ConfigParser does not normally have. |
||||||
|
|
||||||
|
## Reading Configuration Values |
||||||
|
|
||||||
|
You can interact with `cli.config` in all the ways you'd normally expect. For example the `qmk compile` command gets the keyboard name from `cli.config.compile.keyboard`. It does not need to know whether that value came from the command line, an environment variable, or the configuration file. |
||||||
|
|
||||||
|
Iteration is also supported: |
||||||
|
|
||||||
|
``` |
||||||
|
for section in cli.config: |
||||||
|
for key in cli.config[section]: |
||||||
|
cli.log.info('%s.%s: %s', section, key, cli.config[section][key]) |
||||||
|
``` |
||||||
|
|
||||||
|
## Setting Configuration Values |
||||||
|
|
||||||
|
You can set configuration values in the usual ways. |
||||||
|
|
||||||
|
Dictionary style: |
||||||
|
|
||||||
|
``` |
||||||
|
cli.config['<section>']['<key>'] = <value> |
||||||
|
``` |
||||||
|
|
||||||
|
Attribute style: |
||||||
|
|
||||||
|
``` |
||||||
|
cli.config.<section>.<key> = <value> |
||||||
|
``` |
||||||
|
|
||||||
|
## Deleting Configuration Values |
||||||
|
|
||||||
|
You can delete configuration values in the usual ways. |
||||||
|
|
||||||
|
Dictionary style: |
||||||
|
|
||||||
|
``` |
||||||
|
del(cli.config['<section>']['<key>']) |
||||||
|
``` |
||||||
|
|
||||||
|
Attribute style: |
||||||
|
|
||||||
|
``` |
||||||
|
del(cli.config.<section>.<key>) |
||||||
|
``` |
||||||
|
|
||||||
|
## Writing The Configuration File |
||||||
|
|
||||||
|
The configuration is not written out when it is changed. Most commands do not need to do this. We prefer to have the user change their configuration deliberitely using `qmk config`. |
||||||
|
|
||||||
|
You can use `cli.save_config()` to write out the configuration. |
||||||
|
|
||||||
|
## Excluding Arguments From Configuration |
||||||
|
|
||||||
|
Some arguments should not be propagated to the configuration file. These can be excluded by adding `arg_only=True` when creating the argument. |
||||||
|
|
||||||
|
Example: |
||||||
|
|
||||||
|
``` |
||||||
|
@cli.argument('-o', '--output', arg_only=True, help='File to write to') |
||||||
|
@cli.argument('filename', arg_only=True, help='Configurator JSON file') |
||||||
|
@cli.subcommand('Create a keymap.c from a QMK Configurator export.') |
||||||
|
def json_keymap(cli): |
||||||
|
pass |
||||||
|
``` |
||||||
|
|
||||||
|
You will only be able to access these arguments using `cli.args`. For example: |
||||||
|
|
||||||
|
``` |
||||||
|
cli.log.info('Reading from %s and writing to %s', cli.args.filename, cli.args.output) |
||||||
|
``` |
@ -1,45 +0,0 @@ |
|||||||
# Python Development in QMK |
|
||||||
|
|
||||||
This document gives an overview of how QMK has structured its python code. You should read this before working on any of the python code. |
|
||||||
|
|
||||||
## Script directories |
|
||||||
|
|
||||||
There are two places scripts live in QMK: `qmk_firmware/bin` and `qmk_firmware/util`. You should use `bin` for any python scripts that utilize the `qmk` wrapper. Scripts that are standalone and not run very often live in `util`. |
|
||||||
|
|
||||||
We discourage putting anything into `bin` that does not utilize the `qmk` wrapper. If you think you have a good reason for doing so please talk to us about your use case. |
|
||||||
|
|
||||||
## Python Modules |
|
||||||
|
|
||||||
Most of the QMK python modules can be found in `qmk_firmware/lib/python`. This is the path that we append to `sys.path`. |
|
||||||
|
|
||||||
We have a module hierarchy under that path: |
|
||||||
|
|
||||||
* `qmk_firmware/lib/python` |
|
||||||
* `milc.py` - The CLI library we use. Will be pulled out into its own module in the future. |
|
||||||
* `qmk` - Code associated with QMK |
|
||||||
* `cli` - Modules that will be imported for CLI commands. |
|
||||||
* `errors.py` - Errors that can be raised within QMK apps |
|
||||||
* `keymap.py` - Functions for working with keymaps |
|
||||||
|
|
||||||
## CLI Scripts |
|
||||||
|
|
||||||
We have a CLI wrapper that you should utilize for any user facing scripts. We think it's pretty easy to use and it gives you a lot of nice things for free. |
|
||||||
|
|
||||||
To use the wrapper simply place a module into `qmk_firmware/lib/python/qmk/cli`, and create a symlink to `bin/qmk` named after your module. Dashes in command names will be converted into dots so you can use hierarchy to manage commands. |
|
||||||
|
|
||||||
When `qmk` is run it checks to see how it was invoked. If it was invoked as `qmk` the module name is take from `sys.argv[1]`. If it was invoked as `qmk-<module-name>` then everything after the first dash is taken as the module name. Dashes and underscores are converted to dots, and then `qmk.cli` is prepended before the module is imported. |
|
||||||
|
|
||||||
The module uses `@cli.entrypoint()` and `@cli.argument()` decorators to define an entrypoint, which is where execution starts. |
|
||||||
|
|
||||||
## Example CLI Script |
|
||||||
|
|
||||||
We have provided a QMK Hello World script you can use as an example. To run it simply run `qmk hello` or `qmk-hello`. The source code is listed below. |
|
||||||
|
|
||||||
``` |
|
||||||
from milc import cli |
|
||||||
|
|
||||||
@cli.argument('-n', '--name', default='World', help='Name to greet.') |
|
||||||
@cli.entrypoint('QMK Python Hello World.') |
|
||||||
def main(cli): |
|
||||||
cli.echo('Hello, %s!', cli.config.general.name) |
|
||||||
``` |
|
@ -0,0 +1,13 @@ |
|||||||
|
"""QMK CLI Subcommands |
||||||
|
|
||||||
|
We list each subcommand here explicitly because all the reliable ways of searching for modules are slow and delay startup. |
||||||
|
""" |
||||||
|
from . import cformat |
||||||
|
from . import compile |
||||||
|
from . import config |
||||||
|
from . import doctor |
||||||
|
from . import hello |
||||||
|
from . import json |
||||||
|
from . import new |
||||||
|
from . import pyformat |
||||||
|
from . import pytest |
@ -0,0 +1,96 @@ |
|||||||
|
"""Read and write configuration settings |
||||||
|
""" |
||||||
|
import os |
||||||
|
import subprocess |
||||||
|
|
||||||
|
from milc import cli |
||||||
|
|
||||||
|
|
||||||
|
def print_config(section, key): |
||||||
|
"""Print a single config setting to stdout. |
||||||
|
""" |
||||||
|
cli.echo('%s.%s{fg_cyan}={fg_reset}%s', section, key, cli.config[section][key]) |
||||||
|
|
||||||
|
|
||||||
|
@cli.argument('-ro', '--read-only', action='store_true', help='Operate in read-only mode.') |
||||||
|
@cli.argument('configs', nargs='*', arg_only=True, help='Configuration options to read or write.') |
||||||
|
@cli.subcommand("Read and write configuration settings.") |
||||||
|
def config(cli): |
||||||
|
"""Read and write config settings. |
||||||
|
|
||||||
|
This script iterates over the config_tokens supplied as argument. Each config_token has the following form: |
||||||
|
|
||||||
|
section[.key][=value] |
||||||
|
|
||||||
|
If only a section (EG 'compile') is supplied all keys for that section will be displayed. |
||||||
|
|
||||||
|
If section.key is supplied the value for that single key will be displayed. |
||||||
|
|
||||||
|
If section.key=value is supplied the value for that single key will be set. |
||||||
|
|
||||||
|
If section.key=None is supplied the key will be deleted. |
||||||
|
|
||||||
|
No validation is done to ensure that the supplied section.key is actually used by qmk scripts. |
||||||
|
""" |
||||||
|
if not cli.args.configs: |
||||||
|
# Walk the config tree |
||||||
|
for section in cli.config: |
||||||
|
for key in cli.config[section]: |
||||||
|
print_config(section, key) |
||||||
|
|
||||||
|
return True |
||||||
|
|
||||||
|
# Process config_tokens |
||||||
|
save_config = False |
||||||
|
|
||||||
|
for argument in cli.args.configs: |
||||||
|
# Split on space in case they quoted multiple config tokens |
||||||
|
for config_token in argument.split(' '): |
||||||
|
# Extract the section, config_key, and value to write from the supplied config_token. |
||||||
|
if '=' in config_token: |
||||||
|
key, value = config_token.split('=') |
||||||
|
else: |
||||||
|
key = config_token |
||||||
|
value = None |
||||||
|
|
||||||
|
if '.' in key: |
||||||
|
section, config_key = key.split('.', 1) |
||||||
|
else: |
||||||
|
section = key |
||||||
|
config_key = None |
||||||
|
|
||||||
|
# Validation |
||||||
|
if config_key and '.' in config_key: |
||||||
|
cli.log.error('Config keys may not have more than one period! "%s" is not valid.', key) |
||||||
|
return False |
||||||
|
|
||||||
|
# Do what the user wants |
||||||
|
if section and config_key and value: |
||||||
|
# Write a config key |
||||||
|
log_string = '%s.%s{fg_cyan}:{fg_reset} %s {fg_cyan}->{fg_reset} %s' |
||||||
|
if cli.args.read_only: |
||||||
|
log_string += ' {fg_red}(change not written)' |
||||||
|
|
||||||
|
cli.echo(log_string, section, config_key, cli.config[section][config_key], value) |
||||||
|
|
||||||
|
if not cli.args.read_only: |
||||||
|
if value == 'None': |
||||||
|
del cli.config[section][config_key] |
||||||
|
else: |
||||||
|
cli.config[section][config_key] = value |
||||||
|
save_config = True |
||||||
|
|
||||||
|
elif section and config_key: |
||||||
|
# Display a single key |
||||||
|
print_config(section, config_key) |
||||||
|
|
||||||
|
elif section: |
||||||
|
# Display an entire section |
||||||
|
for key in cli.config[section]: |
||||||
|
print_config(section, key) |
||||||
|
|
||||||
|
# Ending actions |
||||||
|
if save_config: |
||||||
|
cli.save_config() |
||||||
|
|
||||||
|
return True |
@ -0,0 +1,5 @@ |
|||||||
|
"""QMK CLI JSON Subcommands |
||||||
|
|
||||||
|
We list each subcommand here explicitly because all the reliable ways of searching for modules are slow and delay startup. |
||||||
|
""" |
||||||
|
from . import keymap |
@ -0,0 +1 @@ |
|||||||
|
from . import keymap |
@ -0,0 +1,39 @@ |
|||||||
|
import subprocess |
||||||
|
|
||||||
|
|
||||||
|
def check_subcommand(command, *args): |
||||||
|
cmd = ['bin/qmk', command] + list(args) |
||||||
|
return subprocess.run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, universal_newlines=True) |
||||||
|
|
||||||
|
|
||||||
|
def test_cformat(): |
||||||
|
assert check_subcommand('cformat', 'tmk_core/common/backlight.c').returncode == 0 |
||||||
|
|
||||||
|
|
||||||
|
def test_compile(): |
||||||
|
assert check_subcommand('compile', '-kb', 'handwired/onekey/pytest', '-km', 'default').returncode == 0 |
||||||
|
|
||||||
|
|
||||||
|
def test_config(): |
||||||
|
result = check_subcommand('config') |
||||||
|
assert result.returncode == 0 |
||||||
|
assert 'general.color' in result.stdout |
||||||
|
|
||||||
|
|
||||||
|
def test_doctor(): |
||||||
|
result = check_subcommand('doctor') |
||||||
|
assert result.returncode == 0 |
||||||
|
assert 'QMK Doctor is checking your environment.' in result.stderr |
||||||
|
assert 'QMK is ready to go' in result.stderr |
||||||
|
|
||||||
|
|
||||||
|
def test_hello(): |
||||||
|
result = check_subcommand('hello') |
||||||
|
assert result.returncode == 0 |
||||||
|
assert 'Hello,' in result.stderr |
||||||
|
|
||||||
|
|
||||||
|
def test_pyformat(): |
||||||
|
result = check_subcommand('pyformat') |
||||||
|
assert result.returncode == 0 |
||||||
|
assert 'Successfully formatted the python code' in result.stderr |
Loading…
Reference in new issue