From d569f0877155efc752994f8a21f5cf001f9d6ae6 Mon Sep 17 00:00:00 2001 From: skullydazed Date: Sun, 22 Sep 2019 13:25:33 -0700 Subject: 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 * Apply suggestions from code review Co-Authored-By: Noah Frederick * Remove pip3 from the test runner --- lib/python/qmk/tests/test_cli_commands.py | 39 +++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 lib/python/qmk/tests/test_cli_commands.py (limited to 'lib/python/qmk/tests/test_cli_commands.py') 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 0000000000..2fc6e0f723 --- /dev/null +++ b/lib/python/qmk/tests/test_cli_commands.py @@ -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 -- cgit v1.2.3 From f04e58dad6f56cdbd5d369c9e00405dcdb47c8ea Mon Sep 17 00:00:00 2001 From: Dan McClain Date: Mon, 7 Oct 2019 14:32:30 -0400 Subject: [CLI] Add `qmk list_keyboards` (#6927) `list_keyboards` replicates the `make list-keyboards` by globbing for all paths that include `rules.mk` and then removing the paths that include `keymaps`. This basis of this cli command could be reused in the future as a util, but is not done so here since this would be the only place that would use it currently Resolves #6911 --- docs/cli.md | 10 ++++++++++ lib/python/qmk/cli/__init__.py | 1 + lib/python/qmk/cli/list/__init__.py | 1 + lib/python/qmk/cli/list/keyboards.py | 26 ++++++++++++++++++++++++++ lib/python/qmk/tests/test_cli_commands.py | 8 ++++++++ 5 files changed, 46 insertions(+) create mode 100644 lib/python/qmk/cli/list/__init__.py create mode 100644 lib/python/qmk/cli/list/keyboards.py (limited to 'lib/python/qmk/tests/test_cli_commands.py') diff --git a/docs/cli.md b/docs/cli.md index fe86cac1fb..6ffe033653 100644 --- a/docs/cli.md +++ b/docs/cli.md @@ -115,6 +115,16 @@ This command examines your environment and alerts you to potential build or flas qmk doctor ``` +## `qmk list_keyboards` + +This command lists all the keyboards currently defined in `qmk_firmware` + +**Usage**: + +``` +qmk list_keyboards +``` + ## `qmk new-keymap` This command creates a new keymap based on a keyboard's existing default keymap. diff --git a/lib/python/qmk/cli/__init__.py b/lib/python/qmk/cli/__init__.py index fb4e0ecb46..e982a75fc8 100644 --- a/lib/python/qmk/cli/__init__.py +++ b/lib/python/qmk/cli/__init__.py @@ -8,6 +8,7 @@ from . import config from . import doctor from . import hello from . import json +from . import list from . import new from . import pyformat from . import pytest diff --git a/lib/python/qmk/cli/list/__init__.py b/lib/python/qmk/cli/list/__init__.py new file mode 100644 index 0000000000..c36ba69548 --- /dev/null +++ b/lib/python/qmk/cli/list/__init__.py @@ -0,0 +1 @@ +from . import keyboards diff --git a/lib/python/qmk/cli/list/keyboards.py b/lib/python/qmk/cli/list/keyboards.py new file mode 100644 index 0000000000..53a7af75c6 --- /dev/null +++ b/lib/python/qmk/cli/list/keyboards.py @@ -0,0 +1,26 @@ +"""List the keyboards currently defined within QMK +""" +import os +import re +import glob + +from milc import cli + +@cli.subcommand("List the keyboards currently defined within QMK") +def list_keyboards(cli): + """List the keyboards currently defined within QMK + """ + + base_path = os.path.join(os.getcwd(), "keyboards") + os.path.sep + kb_path_wildcard = os.path.join(base_path, "**", "rules.mk") + + # find everywhere we have rules.mk where keymaps isn't in the path + paths = [path for path in glob.iglob(kb_path_wildcard, recursive=True) if 'keymaps' not in path] + + # strip the keyboard directory path prefix and rules.mk suffix and alphabetize + find_name = lambda path: path.replace(base_path, "").replace(os.path.sep + "rules.mk", "") + names = sorted(map(find_name, paths)) + + for name in names: + # We echo instead of cli.log.info to allow easier piping of this output + cli.echo(name) diff --git a/lib/python/qmk/tests/test_cli_commands.py b/lib/python/qmk/tests/test_cli_commands.py index 2fc6e0f723..c9d632517b 100644 --- a/lib/python/qmk/tests/test_cli_commands.py +++ b/lib/python/qmk/tests/test_cli_commands.py @@ -37,3 +37,11 @@ def test_pyformat(): result = check_subcommand('pyformat') assert result.returncode == 0 assert 'Successfully formatted the python code' in result.stderr + + +def test_list_keyboards(): + result = check_subcommand('list_keyboards') + assert result.returncode == 0 + # check to see if a known keyboard is returned + # this will fail if handwired/onekey/pytest is removed + assert 'handwired/onekey/pytest' in result.stdout -- cgit v1.2.3 From a5a31a5fc0f14f4f66cf362ee85747be159e364d Mon Sep 17 00:00:00 2001 From: Erovia Date: Sun, 13 Oct 2019 20:23:11 +0200 Subject: MILC: Use dashes instead of underscores for subcommands The subcommand functions' name follows the Python convention of using snake case, but looks odd on the command line. Fix it by converting underscores to dashes, eg.: list_keyboards -> list-keyboards. --- docs/cli.md | 4 ++-- lib/python/milc.py | 7 ++++--- lib/python/qmk/tests/test_cli_commands.py | 2 +- 3 files changed, 7 insertions(+), 6 deletions(-) (limited to 'lib/python/qmk/tests/test_cli_commands.py') diff --git a/docs/cli.md b/docs/cli.md index d150ee917d..0d3703c8cf 100644 --- a/docs/cli.md +++ b/docs/cli.md @@ -125,14 +125,14 @@ This command examines your environment and alerts you to potential build or flas qmk doctor ``` -## `qmk list_keyboards` +## `qmk list-keyboards` This command lists all the keyboards currently defined in `qmk_firmware` **Usage**: ``` -qmk list_keyboards +qmk list-keyboards ``` ## `qmk new-keymap` diff --git a/lib/python/milc.py b/lib/python/milc.py index 1a29bb25c8..7b130bdea6 100644 --- a/lib/python/milc.py +++ b/lib/python/milc.py @@ -429,11 +429,12 @@ class MILC(object): self.arg_only.append(arg_name) del kwargs['arg_only'] + name = handler.__name__.replace("_", "-") if handler is self._entrypoint: self.add_argument(*args, **kwargs) - elif handler.__name__ in self.subcommands: - self.subcommands[handler.__name__].add_argument(*args, **kwargs) + elif name in self.subcommands: + self.subcommands[name].add_argument(*args, **kwargs) else: raise RuntimeError('Decorated function is not entrypoint or subcommand!') @@ -599,7 +600,7 @@ class MILC(object): self.add_subparsers() if not name: - name = handler.__name__ + name = handler.__name__.replace("_", "-") self.acquire_lock() kwargs['help'] = description diff --git a/lib/python/qmk/tests/test_cli_commands.py b/lib/python/qmk/tests/test_cli_commands.py index c9d632517b..9a9dc4b958 100644 --- a/lib/python/qmk/tests/test_cli_commands.py +++ b/lib/python/qmk/tests/test_cli_commands.py @@ -40,7 +40,7 @@ def test_pyformat(): def test_list_keyboards(): - result = check_subcommand('list_keyboards') + result = check_subcommand('list-keyboards') assert result.returncode == 0 # check to see if a known keyboard is returned # this will fail if handwired/onekey/pytest is removed -- cgit v1.2.3 From 79edb7c5942108774e667c172550a1519c5543ac Mon Sep 17 00:00:00 2001 From: skullY Date: Tue, 12 Nov 2019 17:27:08 -0800 Subject: Small CLI cleanups * yapf changes * Fix the cformat test * Make the normpath test work when run from / * `qmk config`: Mark `--read-only` as arg_only --- lib/python/qmk/cli/config.py | 2 +- lib/python/qmk/cli/list/keyboards.py | 1 + lib/python/qmk/errors.py | 1 - lib/python/qmk/tests/attrdict.py | 1 - lib/python/qmk/tests/test_cli_commands.py | 2 +- lib/python/qmk/tests/test_qmk_path.py | 2 +- 6 files changed, 4 insertions(+), 5 deletions(-) (limited to 'lib/python/qmk/tests/test_cli_commands.py') diff --git a/lib/python/qmk/cli/config.py b/lib/python/qmk/cli/config.py index d6c774e651..c4ee20cba5 100644 --- a/lib/python/qmk/cli/config.py +++ b/lib/python/qmk/cli/config.py @@ -12,7 +12,7 @@ def print_config(section, key): 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('-ro', '--read-only', arg_only=True, 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): diff --git a/lib/python/qmk/cli/list/keyboards.py b/lib/python/qmk/cli/list/keyboards.py index 53a7af75c6..2a29ccb146 100644 --- a/lib/python/qmk/cli/list/keyboards.py +++ b/lib/python/qmk/cli/list/keyboards.py @@ -6,6 +6,7 @@ import glob from milc import cli + @cli.subcommand("List the keyboards currently defined within QMK") def list_keyboards(cli): """List the keyboards currently defined within QMK diff --git a/lib/python/qmk/errors.py b/lib/python/qmk/errors.py index f9bf5b9af9..4a8a91556b 100644 --- a/lib/python/qmk/errors.py +++ b/lib/python/qmk/errors.py @@ -1,6 +1,5 @@ class NoSuchKeyboardError(Exception): """Raised when we can't find a keyboard/keymap directory. """ - def __init__(self, message): self.message = message diff --git a/lib/python/qmk/tests/attrdict.py b/lib/python/qmk/tests/attrdict.py index 391c75c4e1..a2584b9233 100644 --- a/lib/python/qmk/tests/attrdict.py +++ b/lib/python/qmk/tests/attrdict.py @@ -3,7 +3,6 @@ class AttrDict(dict): This should only be used to mock objects for unit testing. Please do not use this outside of qmk.tests. """ - def __init__(self, *args, **kwargs): super(AttrDict, self).__init__(*args, **kwargs) self.__dict__ = self diff --git a/lib/python/qmk/tests/test_cli_commands.py b/lib/python/qmk/tests/test_cli_commands.py index 9a9dc4b958..55b8d253f7 100644 --- a/lib/python/qmk/tests/test_cli_commands.py +++ b/lib/python/qmk/tests/test_cli_commands.py @@ -7,7 +7,7 @@ def check_subcommand(command, *args): def test_cformat(): - assert check_subcommand('cformat', 'tmk_core/common/backlight.c').returncode == 0 + assert check_subcommand('cformat', 'tmk_core/common/keyboard.c').returncode == 0 def test_compile(): diff --git a/lib/python/qmk/tests/test_qmk_path.py b/lib/python/qmk/tests/test_qmk_path.py index 94dbf3a6a6..d6961a0f65 100644 --- a/lib/python/qmk/tests/test_qmk_path.py +++ b/lib/python/qmk/tests/test_qmk_path.py @@ -10,4 +10,4 @@ def test_keymap_onekey_pytest(): def test_normpath(): path = qmk.path.normpath('lib/python') - assert path == os.environ['ORIG_CWD'] + '/lib/python' + assert path == os.path.join(os.environ['ORIG_CWD'], 'lib/python') -- cgit v1.2.3 From 7329c2d02d38f40a23d38f789de34057fd2acd42 Mon Sep 17 00:00:00 2001 From: Cody Bender <50554676+cfbender@users.noreply.github.com> Date: Tue, 12 Nov 2019 21:55:41 -0700 Subject: Add cli convert subcommand, from raw KLE to JSON (#6898) * Add initial pass at KLE convert * Add cli log on convert * Move kle2xy, add absolute filepath arg support * Add overwrite flag, and context sensitive conversion * Update docs/cli.md * Fix converter.py typo * Add convert unit test * Rename to kle2qmk * Rename subcommand * Rename subcommand to kle2json * Change tests to cover rename * Rename in __init__.py * Update CLI docs with new subcommand name * Fix from suggestions in PR #6898 * Help with cases of case sensitivity * Update cli.md * Use angle brackets to indicate required option * Make the output text more accurate --- docs/cli.md | 22 +++++ lib/python/kle2xy.py | 155 ++++++++++++++++++++++++++++++ lib/python/qmk/cli/__init__.py | 1 + lib/python/qmk/cli/kle2json.py | 79 +++++++++++++++ lib/python/qmk/converter.py | 33 +++++++ lib/python/qmk/tests/kle.txt | 5 + lib/python/qmk/tests/test_cli_commands.py | 2 + requirements.txt | 1 + 8 files changed, 298 insertions(+) create mode 100644 lib/python/kle2xy.py create mode 100755 lib/python/qmk/cli/kle2json.py create mode 100644 lib/python/qmk/converter.py create mode 100644 lib/python/qmk/tests/kle.txt (limited to 'lib/python/qmk/tests/test_cli_commands.py') diff --git a/docs/cli.md b/docs/cli.md index e655b0ee89..fb7d17d2a1 100644 --- a/docs/cli.md +++ b/docs/cli.md @@ -135,6 +135,28 @@ Creates a keymap.c from a QMK Configurator export. qmk json-keymap [-o OUTPUT] filename ``` +## `qmk kle2json` + +This command allows you to convert from raw KLE data to QMK Configurator JSON. It accepts either an absolute file path, or a file name in the current directory. By default it will not overwrite `info.json` if it is already present. Use the `-f` or `--force` flag to overwrite. + +**Usage**: + +``` +qmk kle2json [-f] +``` + +**Examples**: + +``` +$ qmk kle2json kle.txt +☒ File info.json already exists, use -f or --force to overwrite. +``` + +``` +$ qmk kle2json -f kle.txt -f +Ψ Wrote out to info.json +``` + ## `qmk list-keyboards` This command lists all the keyboards currently defined in `qmk_firmware` diff --git a/lib/python/kle2xy.py b/lib/python/kle2xy.py new file mode 100644 index 0000000000..ea16a4b5ee --- /dev/null +++ b/lib/python/kle2xy.py @@ -0,0 +1,155 @@ +""" Original code from https://github.com/skullydazed/kle2xy +""" + +import hjson +from decimal import Decimal + +class KLE2xy(list): + """Abstract interface for interacting with a KLE layout. + """ + def __init__(self, layout=None, name='', invert_y=True): + super(KLE2xy, self).__init__() + + self.name = name + self.invert_y = invert_y + self.key_width = Decimal('19.05') + self.key_skel = { + 'decal': False, + 'border_color': 'none', + 'keycap_profile': '', + 'keycap_color': 'grey', + 'label_color': 'black', + 'label_size': 3, + 'label_style': 4, + 'width': Decimal('1'), 'height': Decimal('1'), + 'x': Decimal('0'), 'y': Decimal('0') + } + self.rows = Decimal(0) + self.columns = Decimal(0) + + if layout: + self.parse_layout(layout) + + @property + def width(self): + """Returns the width of the keyboard plate. + """ + return (Decimal(self.columns) * self.key_width) + self.key_width/2 + + @property + def height(self): + """Returns the height of the keyboard plate. + """ + return (self.rows * self.key_width) + self.key_width/2 + + @property + def size(self): + """Returns the size of the keyboard plate. + """ + return (self.width, self.height) + + def attrs(self, properties): + """Parse the keyboard properties dictionary. + """ + # FIXME: Store more than just the keyboard name. + if 'name' in properties: + self.name = properties['name'] + + def parse_layout(self, layout): + # Wrap this in a dictionary so hjson will parse KLE raw data + layout = '{"layout": [' + layout + ']}' + layout = hjson.loads(layout)['layout'] + + # Initialize our state machine + current_key = self.key_skel.copy() + current_row = Decimal(0) + current_col = Decimal(0) + current_x = 0 + current_y = self.key_width / 2 + + if isinstance(layout[0], dict): + self.attrs(layout[0]) + layout = layout[1:] + + for row_num, row in enumerate(layout): + self.append([]) + + # Process the current row + for key in row: + if isinstance(key, dict): + if 'w' in key and key['w'] != Decimal(1): + current_key['width'] = Decimal(key['w']) + if 'w2' in key and 'h2' in key and key['w2'] == 1.5 and key['h2'] == 1: + # FIXME: ISO Key uses these params: {x:0.25,w:1.25,h:2,w2:1.5,h2:1,x2:-0.25} + current_key['isoenter'] = True + if 'h' in key and key['h'] != Decimal(1): + current_key['height'] = Decimal(key['h']) + if 'a' in key: + current_key['label_style'] = self.key_skel['label_style'] = int(key['a']) + if current_key['label_style'] < 0: + current_key['label_style'] = 0 + elif current_key['label_style'] > 9: + current_key['label_style'] = 9 + if 'f' in key: + font_size = int(key['f']) + if font_size > 9: + font_size = 9 + elif font_size < 1: + font_size = 1 + current_key['label_size'] = self.key_skel['label_size'] = font_size + if 'p' in key: + current_key['keycap_profile'] = self.key_skel['keycap_profile'] = key['p'] + if 'c' in key: + current_key['keycap_color'] = self.key_skel['keycap_color'] = key['c'] + if 't' in key: + # FIXME: Need to do better validation, plus figure out how to support multiple colors + if '\n' in key['t']: + key['t'] = key['t'].split('\n')[0] + if key['t'] == "0": + key['t'] = "#000000" + current_key['label_color'] = self.key_skel['label_color'] = key['t'] + if 'x' in key: + current_col += Decimal(key['x']) + current_x += Decimal(key['x']) * self.key_width + if 'y' in key: + current_row += Decimal(key['y']) + current_y += Decimal(key['y']) * self.key_width + if 'd' in key: + current_key['decal'] = True + + else: + current_key['name'] = key + current_key['row'] = current_row + current_key['column'] = current_col + + # Determine the X center + x_center = (current_key['width'] * self.key_width) / 2 + current_x += x_center + current_key['x'] = current_x + current_x += x_center + + # Determine the Y center + y_center = (current_key['height'] * self.key_width) / 2 + y_offset = y_center - (self.key_width / 2) + current_key['y'] = (current_y + y_offset) + + # Tend to our row/col count + current_col += current_key['width'] + if current_col > self.columns: + self.columns = current_col + + # Invert the y-axis if neccesary + if self.invert_y: + current_key['y'] = -current_key['y'] + + # Store this key + self[-1].append(current_key) + current_key = self.key_skel.copy() + + # Move to the next row + current_x = 0 + current_y += self.key_width + current_col = Decimal(0) + current_row += Decimal(1) + if current_row > self.rows: + self.rows = Decimal(current_row) diff --git a/lib/python/qmk/cli/__init__.py b/lib/python/qmk/cli/__init__.py index e41cc3dcb2..1b83e78c70 100644 --- a/lib/python/qmk/cli/__init__.py +++ b/lib/python/qmk/cli/__init__.py @@ -10,6 +10,7 @@ from . import doctor from . import hello from . import json from . import list +from . import kle2json from . import new from . import pyformat from . import pytest diff --git a/lib/python/qmk/cli/kle2json.py b/lib/python/qmk/cli/kle2json.py new file mode 100755 index 0000000000..22eb515dff --- /dev/null +++ b/lib/python/qmk/cli/kle2json.py @@ -0,0 +1,79 @@ +"""Convert raw KLE to JSON + +""" +import json +import os +from pathlib import Path +from argparse import FileType +from decimal import Decimal +from collections import OrderedDict + +from milc import cli +from kle2xy import KLE2xy + +from qmk.converter import kle2qmk + + +class CustomJSONEncoder(json.JSONEncoder): + def default(self, obj): + try: + if isinstance(obj, Decimal): + if obj % 2 in (Decimal(0), Decimal(1)): + return int(obj) + return float(obj) + except TypeError: + pass + return JSONEncoder.default(self, obj) + + +@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') +def kle2json(cli): + """Convert a KLE layout to QMK's layout format. + """ # If filename is a path + if cli.args.filename.startswith("/") or cli.args.filename.startswith("./"): + file_path = Path(cli.args.filename) + # Otherwise assume it is a file name + else: + 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)) + 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)) + return False; + try: + # Convert KLE raw to x/y coordinates (using kle2xy package from skullydazed) + kle = KLE2xy(raw_code) + except Exception as e: + cli.log.error('Could not parse KLE raw data: %s', raw_code) + cli.log.exception(e) + # FIXME: This should be better + return cli.log.error('Could not parse KLE raw data.') + keyboard = OrderedDict( + keyboard_name=kle.name, + url='', + maintainer='qmk', + width=kle.columns, + height=kle.rows, + layouts={'LAYOUT': { + 'layout': 'LAYOUT_JSON_HERE' + }}, + ) + # Initialize keyboard with json encoded from ordered dict + keyboard = json.dumps(keyboard, indent=4, separators=( + ', ', ': '), sort_keys=False, cls=CustomJSONEncoder) + # Initialize layout with kle2qmk from converter module + layout = json.dumps(kle2qmk(kle), separators=( + ', ', ':'), cls=CustomJSONEncoder) + # 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.write(keyboard) + file.close() + cli.log.info('Wrote out {fg_cyan}%s/info.json', str(out_path)) diff --git a/lib/python/qmk/converter.py b/lib/python/qmk/converter.py new file mode 100644 index 0000000000..bbd3531317 --- /dev/null +++ b/lib/python/qmk/converter.py @@ -0,0 +1,33 @@ +"""Functions to convert to and from QMK formats +""" +from collections import OrderedDict + + +def kle2qmk(kle): + """Convert a KLE layout to QMK's layout format. + """ + layout = [] + + for row in kle: + for key in row: + if key['decal']: + continue + + qmk_key = OrderedDict( + label="", + x=key['column'], + y=key['row'], + ) + + if key['width'] != 1: + qmk_key['w'] = key['width'] + if key['height'] != 1: + qmk_key['h'] = key['height'] + if 'name' in key and key['name']: + qmk_key['label'] = key['name'].split('\n', 1)[0] + else: + del (qmk_key['label']) + + layout.append(qmk_key) + + return layout diff --git a/lib/python/qmk/tests/kle.txt b/lib/python/qmk/tests/kle.txt new file mode 100644 index 0000000000..862a899ab9 --- /dev/null +++ b/lib/python/qmk/tests/kle.txt @@ -0,0 +1,5 @@ +["¬\n`","!\n1","\"\n2","£\n3","$\n4","%\n5","^\n6","&\n7","*\n8","(\n9",")\n0","_\n-","+\n=",{w:2},"Backspace"], +[{w:1.5},"Tab","Q","W","E","R","T","Y","U","I","O","P","{\n[","}\n]",{x:0.25,w:1.25,h:2,w2:1.5,h2:1,x2:-0.25},"Enter"], +[{w:1.75},"Caps Lock","A","S","D","F","G","H","J","K","L",":\n;","@\n'","~\n#"], +[{w:1.25},"Shift","|\n\\","Z","X","C","V","B","N","M","<\n,",">\n.","?\n/",{w:2.75},"Shift"], +[{w:1.25},"Ctrl",{w:1.25},"Win",{w:1.25},"Alt",{a:7,w:6.25},"",{a:4,w:1.25},"AltGr",{w:1.25},"Win",{w:1.25},"Menu",{w:1.25},"Ctrl"] diff --git a/lib/python/qmk/tests/test_cli_commands.py b/lib/python/qmk/tests/test_cli_commands.py index 55b8d253f7..d91af992a8 100644 --- a/lib/python/qmk/tests/test_cli_commands.py +++ b/lib/python/qmk/tests/test_cli_commands.py @@ -19,6 +19,8 @@ def test_config(): assert result.returncode == 0 assert 'general.color' in result.stdout +def test_kle2json(): + assert check_subcommand('kle2json', 'kle.txt', '-f').returncode == 0 def test_doctor(): result = check_subcommand('doctor') diff --git a/requirements.txt b/requirements.txt index f6257e399e..aa6ee1ba32 100644 --- a/requirements.txt +++ b/requirements.txt @@ -3,3 +3,4 @@ appdirs argcomplete colorama +hjson -- cgit v1.2.3 From a4c2a9b083d82d9e7d7fe3a68c0d51ae2280495f Mon Sep 17 00:00:00 2001 From: QMK Bot Date: Wed, 13 Nov 2019 05:24:56 +0000 Subject: format code according to conventions [skip ci] --- lib/python/kle2xy.py | 17 ++++------------- lib/python/qmk/cli/kle2json.py | 10 ++++------ lib/python/qmk/tests/test_cli_commands.py | 2 ++ 3 files changed, 10 insertions(+), 19 deletions(-) (limited to 'lib/python/qmk/tests/test_cli_commands.py') diff --git a/lib/python/kle2xy.py b/lib/python/kle2xy.py index ea16a4b5ee..9291443190 100644 --- a/lib/python/kle2xy.py +++ b/lib/python/kle2xy.py @@ -4,6 +4,7 @@ import hjson from decimal import Decimal + class KLE2xy(list): """Abstract interface for interacting with a KLE layout. """ @@ -13,17 +14,7 @@ class KLE2xy(list): self.name = name self.invert_y = invert_y self.key_width = Decimal('19.05') - self.key_skel = { - 'decal': False, - 'border_color': 'none', - 'keycap_profile': '', - 'keycap_color': 'grey', - 'label_color': 'black', - 'label_size': 3, - 'label_style': 4, - 'width': Decimal('1'), 'height': Decimal('1'), - 'x': Decimal('0'), 'y': Decimal('0') - } + self.key_skel = {'decal': False, 'border_color': 'none', 'keycap_profile': '', 'keycap_color': 'grey', 'label_color': 'black', 'label_size': 3, 'label_style': 4, 'width': Decimal('1'), 'height': Decimal('1'), 'x': Decimal('0'), 'y': Decimal('0')} self.rows = Decimal(0) self.columns = Decimal(0) @@ -34,13 +25,13 @@ class KLE2xy(list): def width(self): """Returns the width of the keyboard plate. """ - return (Decimal(self.columns) * self.key_width) + self.key_width/2 + return (Decimal(self.columns) * self.key_width) + self.key_width / 2 @property def height(self): """Returns the height of the keyboard plate. """ - return (self.rows * self.key_width) + self.key_width/2 + return (self.rows * self.key_width) + self.key_width / 2 @property def size(self): diff --git a/lib/python/qmk/cli/kle2json.py b/lib/python/qmk/cli/kle2json.py index 22eb515dff..5a4e97e3ab 100755 --- a/lib/python/qmk/cli/kle2json.py +++ b/lib/python/qmk/cli/kle2json.py @@ -31,7 +31,7 @@ class CustomJSONEncoder(json.JSONEncoder): @cli.subcommand('Convert a KLE layout to a Configurator JSON') def kle2json(cli): """Convert a KLE layout to QMK's layout format. - """ # If filename is a path + """ # If filename is a path if cli.args.filename.startswith("/") or cli.args.filename.startswith("./"): file_path = Path(cli.args.filename) # Otherwise assume it is a file name @@ -45,7 +45,7 @@ def kle2json(cli): # 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)) - return False; + return False try: # Convert KLE raw to x/y coordinates (using kle2xy package from skullydazed) kle = KLE2xy(raw_code) @@ -65,11 +65,9 @@ def kle2json(cli): }}, ) # Initialize keyboard with json encoded from ordered dict - keyboard = json.dumps(keyboard, indent=4, separators=( - ', ', ': '), sort_keys=False, cls=CustomJSONEncoder) + keyboard = json.dumps(keyboard, indent=4, separators=(', ', ': '), sort_keys=False, cls=CustomJSONEncoder) # Initialize layout with kle2qmk from converter module - layout = json.dumps(kle2qmk(kle), separators=( - ', ', ':'), cls=CustomJSONEncoder) + layout = json.dumps(kle2qmk(kle), separators=(', ', ':'), cls=CustomJSONEncoder) # Replace layout in keyboard json keyboard = keyboard.replace('"LAYOUT_JSON_HERE"', layout) # Write our info.json diff --git a/lib/python/qmk/tests/test_cli_commands.py b/lib/python/qmk/tests/test_cli_commands.py index d91af992a8..85d4d91af1 100644 --- a/lib/python/qmk/tests/test_cli_commands.py +++ b/lib/python/qmk/tests/test_cli_commands.py @@ -19,9 +19,11 @@ def test_config(): assert result.returncode == 0 assert 'general.color' in result.stdout + def test_kle2json(): assert check_subcommand('kle2json', 'kle.txt', '-f').returncode == 0 + def test_doctor(): result = check_subcommand('doctor') assert result.returncode == 0 -- cgit v1.2.3 From 897888db419239f013561b155de5993b1966820e Mon Sep 17 00:00:00 2001 From: jorgemanzo Date: Fri, 4 Oct 2019 23:38:34 -0700 Subject: Add CLI command for flashing a keyboard A new CLI subcommand was added, flash, which behaves very similar to the already present compile CLI comamnd, but with the added ability to target a bootloader. The command is used like so: qmk flash [-h] [-b] [-kb KEYBOARD] [-km KEYMAP] [-bl BOOTLOADER] [filename]. A -kb and -km is expected, or a configurator export JSON filename. A bootloader can be specified using -bl , and if left unspecified, the target is assumed to be :flash. -bl can be used to list the available bootloaders. If -km is provided, but no -kb , then a message is printed suggesting the user to run qmk list_keyboards. --- docs/cli.md | 24 +++++++++ lib/python/qmk/cli/__init__.py | 1 + lib/python/qmk/cli/compile.py | 14 +++-- lib/python/qmk/cli/flash.py | 87 +++++++++++++++++++++++++++++++ lib/python/qmk/commands.py | 57 ++++++++++++++++++++ lib/python/qmk/tests/test_cli_commands.py | 3 ++ 6 files changed, 181 insertions(+), 5 deletions(-) create mode 100644 lib/python/qmk/cli/flash.py create mode 100644 lib/python/qmk/commands.py (limited to 'lib/python/qmk/tests/test_cli_commands.py') diff --git a/docs/cli.md b/docs/cli.md index fb7d17d2a1..1c09527221 100644 --- a/docs/cli.md +++ b/docs/cli.md @@ -95,6 +95,30 @@ qmk compile qmk compile -kb -km ``` +## `qmk flash` + +This command is similar to `qmk compile`, but can also target a bootloader. The bootloader is optional, and is set to `:flash` by default. +To specify a different bootloader, use `-bl `. Visit +for more details of the available bootloaders. + +**Usage for Configurator Exports**: + +``` +qmk flash -bl +``` + +**Usage for Keymaps**: + +``` +qmk flash -kb -km -bl +``` + +**Listing the Bootloaders** + +``` +qmk flash -b +``` + ## `qmk config` This command lets you configure the behavior of QMK. For the full `qmk config` documentation see [CLI Configuration](cli_configuration.md). diff --git a/lib/python/qmk/cli/__init__.py b/lib/python/qmk/cli/__init__.py index 1b83e78c70..72ee38f562 100644 --- a/lib/python/qmk/cli/__init__.py +++ b/lib/python/qmk/cli/__init__.py @@ -7,6 +7,7 @@ from . import compile from . import config from . import docs from . import doctor +from . import flash from . import hello from . import json from . import list diff --git a/lib/python/qmk/cli/compile.py b/lib/python/qmk/cli/compile.py index 6646891b30..c7093d4215 100755 --- a/lib/python/qmk/cli/compile.py +++ b/lib/python/qmk/cli/compile.py @@ -9,6 +9,9 @@ import subprocess from argparse import FileType from milc import cli +from qmk.commands import create_make_command +from qmk.commands import parse_configurator_json +from qmk.commands import compile_configurator_json import qmk.keymap import qmk.path @@ -30,20 +33,21 @@ def compile(cli): """ if cli.args.filename: # Parse the configurator json - user_keymap = json.load(cli.args.filename) + user_keymap = parse_configurator_json(cli.args.filename) # Generate the keymap keymap_path = qmk.path.keymap(user_keymap['keyboard']) cli.log.info('Creating {fg_cyan}%s{style_reset_all} keymap in {fg_cyan}%s', user_keymap['keymap'], keymap_path) - qmk.keymap.write(user_keymap['keyboard'], user_keymap['keymap'], user_keymap['layout'], user_keymap['layers']) + + # Compile the keymap + command = compile_configurator_json(cli.args.filename) + cli.log.info('Wrote keymap to {fg_cyan}%s/%s/keymap.c', keymap_path, user_keymap['keymap']) - # Compile the keymap - command = ['make', ':'.join((user_keymap['keyboard'], user_keymap['keymap']))] elif cli.config.compile.keyboard and cli.config.compile.keymap: # Generate the make command for a specific keyboard/keymap. - command = ['make', ':'.join((cli.config.compile.keyboard, cli.config.compile.keymap))] + command = create_make_command(cli.config.compile.keyboard, cli.config.compile.keymap) else: cli.log.error('You must supply a configurator export or both `--keyboard` and `--keymap`.') diff --git a/lib/python/qmk/cli/flash.py b/lib/python/qmk/cli/flash.py new file mode 100644 index 0000000000..8f7bb55a22 --- /dev/null +++ b/lib/python/qmk/cli/flash.py @@ -0,0 +1,87 @@ +"""Compile and flash QMK Firmware + +You can compile a keymap already in the repo or using a QMK Configurator export. +A bootloader must be specified. +""" +import os +import sys +import subprocess +from argparse import FileType + +from milc import cli +from qmk.commands import create_make_command +from qmk.commands import parse_configurator_json +from qmk.commands import compile_configurator_json + +import qmk.path + + +def print_bootloader_help(): + """Prints the available bootloaders listed in docs.qmk.fm. + """ + cli.log.info('Here are the available bootloaders:') + cli.echo('\tdfu') + cli.echo('\tdfu-ee') + cli.echo('\tdfu-split-left') + cli.echo('\tdfu-split-right') + cli.echo('\tavrdude') + cli.echo('\tBootloadHID') + cli.echo('\tdfu-util') + cli.echo('\tdfu-util-split-left') + cli.echo('\tdfu-util-split-right') + cli.echo('\tst-link-cli') + cli.echo('For more info, visit https://docs.qmk.fm/#/flashing') + +@cli.argument('-bl', '--bootloader', default='flash', help='The flash command, corresponding to qmk\'s make options of bootloaders.') +@cli.argument('filename', nargs='?', arg_only=True, help='The configurator export JSON to compile. Use this if you dont want to specify a keymap and keyboard.') +@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.') +@cli.argument('-kb', '--keyboard', help='The keyboard to build a firmware for. Use this if you dont have a configurator file. Ignored when a configurator file is supplied.') +@cli.argument('-b', '--bootloaders', action='store_true', help='List the available bootloaders.') +@cli.subcommand('QMK Flash.') +def flash(cli): + """Compile and or flash QMK Firmware or keyboard/layout + + If a Configurator JSON export is supplied this command will create a new keymap. Keymap and Keyboard arguments + will be ignored. + + If no file is supplied, keymap and keyboard are expected. + + If bootloader is omitted, the one according to the rules.mk will be used. + + """ + command = [] + if cli.args.bootloaders: + # Provide usage and list bootloaders + cli.echo('usage: qmk flash [-h] [-b] [-kb KEYBOARD] [-km KEYMAP] [-bl BOOTLOADER] [filename]') + print_bootloader_help() + return False + + elif cli.args.keymap and not cli.args.keyboard: + # If only a keymap was given but no keyboard, suggest listing keyboards + cli.echo('usage: qmk flash [-h] [-b] [-kb KEYBOARD] [-km KEYMAP] [-bl BOOTLOADER] [filename]') + cli.log.error('run \'qmk list_keyboards\' to find out the supported keyboards') + return False + + elif cli.args.filename: + # Get keymap path to log info + user_keymap = parse_configurator_json(cli.args.filename) + keymap_path = qmk.path.keymap(user_keymap['keyboard']) + + cli.log.info('Creating {fg_cyan}%s{style_reset_all} keymap in {fg_cyan}%s', user_keymap['keymap'], keymap_path) + + # Convert the JSON into a C file and write it to disk. + command = compile_configurator_json(cli.args.filename, cli.args.bootloader) + + cli.log.info('Wrote keymap to {fg_cyan}%s/%s/keymap.c', keymap_path, user_keymap['keymap']) + + elif cli.args.keyboard and cli.args.keymap: + # Generate the make command for a specific keyboard/keymap. + command = create_make_command(cli.config.flash.keyboard, cli.config.flash.keymap, cli.args.bootloader) + + else: + cli.echo('usage: qmk flash [-h] [-b] [-kb KEYBOARD] [-km KEYMAP] [-bl BOOTLOADER] [filename]') + cli.log.error('You must supply a configurator export or both `--keyboard` and `--keymap`. You can also specify a bootloader with --bootloader. Use --bootloaders to list the available bootloaders.') + return False + + cli.log.info('Flashing keymap with {fg_cyan}%s\n\n', ' '.join(command)) + subprocess.run(command) \ No newline at end of file diff --git a/lib/python/qmk/commands.py b/lib/python/qmk/commands.py new file mode 100644 index 0000000000..9fbf00f165 --- /dev/null +++ b/lib/python/qmk/commands.py @@ -0,0 +1,57 @@ +"""Functions that build make commands +""" +import json +import qmk.keymap + +def create_make_command(keyboard, keymap, target=None): + """Create a make compile command + + Args: + keyboard + The path of the keyboard, for example 'plank' + + keymap + The name of the keymap, for example 'algernon' + + target + Usually a bootloader. + + Returns: + A command that can be run to make the specified keyboard and keymap + """ + if target is None: + return ['make', ':'.join((keyboard, keymap))] + return ['make', ':'.join((keyboard, keymap, target))] + +def parse_configurator_json(configurator_filename): + """Open and parse a configurator json export + """ + file = open(configurator_filename) + user_keymap = json.load(file) + file.close() + return user_keymap + +def compile_configurator_json(configurator_filename, bootloader=None): + """Convert a configurator export JSON file into a C file + + Args: + configurator_filename + The configurator JSON export file + + bootloader + A bootloader to flash + + Returns: + A command to run to compile and flash the C file. + """ + # Parse the configurator json + user_keymap = parse_configurator_json(configurator_filename) + + # Write the keymap C file + qmk.keymap.write(user_keymap['keyboard'], user_keymap['keymap'], user_keymap['layout'], user_keymap['layers']) + + # Return a command that can be run to make the keymap and flash if given + if bootloader is None: + return create_make_command(user_keymap['keyboard'], user_keymap['keymap']) + return create_make_command(user_keymap['keyboard'], user_keymap['keymap'], bootloader) + diff --git a/lib/python/qmk/tests/test_cli_commands.py b/lib/python/qmk/tests/test_cli_commands.py index 85d4d91af1..dcab8bdae4 100644 --- a/lib/python/qmk/tests/test_cli_commands.py +++ b/lib/python/qmk/tests/test_cli_commands.py @@ -13,6 +13,9 @@ def test_cformat(): def test_compile(): assert check_subcommand('compile', '-kb', 'handwired/onekey/pytest', '-km', 'default').returncode == 0 +def test_flash(): + assert check_subcommand('flash', '-b').returncode == 1 + assert check_subcommand('flash').returncode == 1 def test_config(): result = check_subcommand('config') -- cgit v1.2.3 From 7891de7f6d64431cedbd580a3f7863533dc8be88 Mon Sep 17 00:00:00 2001 From: QMK Bot Date: Sat, 16 Nov 2019 07:10:19 +0000 Subject: format code according to conventions [skip ci] --- lib/python/qmk/cli/compile.py | 3 +-- lib/python/qmk/cli/flash.py | 3 ++- lib/python/qmk/commands.py | 4 +++- lib/python/qmk/tests/test_cli_commands.py | 2 ++ 4 files changed, 8 insertions(+), 4 deletions(-) (limited to 'lib/python/qmk/tests/test_cli_commands.py') diff --git a/lib/python/qmk/cli/compile.py b/lib/python/qmk/cli/compile.py index c7093d4215..5c29800096 100755 --- a/lib/python/qmk/cli/compile.py +++ b/lib/python/qmk/cli/compile.py @@ -38,13 +38,12 @@ def compile(cli): # Generate the keymap keymap_path = qmk.path.keymap(user_keymap['keyboard']) cli.log.info('Creating {fg_cyan}%s{style_reset_all} keymap in {fg_cyan}%s', user_keymap['keymap'], keymap_path) - + # Compile the keymap command = compile_configurator_json(cli.args.filename) cli.log.info('Wrote keymap to {fg_cyan}%s/%s/keymap.c', keymap_path, user_keymap['keymap']) - elif cli.config.compile.keyboard and cli.config.compile.keymap: # Generate the make command for a specific keyboard/keymap. command = create_make_command(cli.config.compile.keyboard, cli.config.compile.keymap) diff --git a/lib/python/qmk/cli/flash.py b/lib/python/qmk/cli/flash.py index 8f7bb55a22..37556aaafd 100644 --- a/lib/python/qmk/cli/flash.py +++ b/lib/python/qmk/cli/flash.py @@ -32,6 +32,7 @@ def print_bootloader_help(): cli.echo('\tst-link-cli') cli.echo('For more info, visit https://docs.qmk.fm/#/flashing') + @cli.argument('-bl', '--bootloader', default='flash', help='The flash command, corresponding to qmk\'s make options of bootloaders.') @cli.argument('filename', nargs='?', arg_only=True, help='The configurator export JSON to compile. Use this if you dont want to specify a keymap and keyboard.') @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.') @@ -84,4 +85,4 @@ def flash(cli): return False cli.log.info('Flashing keymap with {fg_cyan}%s\n\n', ' '.join(command)) - subprocess.run(command) \ No newline at end of file + subprocess.run(command) diff --git a/lib/python/qmk/commands.py b/lib/python/qmk/commands.py index 9fbf00f165..f83a89578e 100644 --- a/lib/python/qmk/commands.py +++ b/lib/python/qmk/commands.py @@ -3,6 +3,7 @@ import json import qmk.keymap + def create_make_command(keyboard, keymap, target=None): """Create a make compile command @@ -23,6 +24,7 @@ def create_make_command(keyboard, keymap, target=None): return ['make', ':'.join((keyboard, keymap))] return ['make', ':'.join((keyboard, keymap, target))] + def parse_configurator_json(configurator_filename): """Open and parse a configurator json export """ @@ -31,6 +33,7 @@ def parse_configurator_json(configurator_filename): file.close() return user_keymap + def compile_configurator_json(configurator_filename, bootloader=None): """Convert a configurator export JSON file into a C file @@ -54,4 +57,3 @@ def compile_configurator_json(configurator_filename, bootloader=None): if bootloader is None: return create_make_command(user_keymap['keyboard'], user_keymap['keymap']) return create_make_command(user_keymap['keyboard'], user_keymap['keymap'], bootloader) - diff --git a/lib/python/qmk/tests/test_cli_commands.py b/lib/python/qmk/tests/test_cli_commands.py index dcab8bdae4..3f75cef3e1 100644 --- a/lib/python/qmk/tests/test_cli_commands.py +++ b/lib/python/qmk/tests/test_cli_commands.py @@ -13,10 +13,12 @@ def test_cformat(): def test_compile(): assert check_subcommand('compile', '-kb', 'handwired/onekey/pytest', '-km', 'default').returncode == 0 + def test_flash(): assert check_subcommand('flash', '-b').returncode == 1 assert check_subcommand('flash').returncode == 1 + def test_config(): result = check_subcommand('config') assert result.returncode == 0 -- cgit v1.2.3