Write a basic keymap sanity checker utility
This commit is contained in:
parent
3f83e6a85b
commit
94130740c4
@ -22,6 +22,23 @@ jobs:
|
||||
paths:
|
||||
- .venv
|
||||
|
||||
test:
|
||||
docker:
|
||||
- image: 'python:3.7'
|
||||
|
||||
environment:
|
||||
KMK_TEST: 1
|
||||
PIPENV_VENV_IN_PROJECT: 1
|
||||
|
||||
steps:
|
||||
- checkout
|
||||
- restore_cache:
|
||||
keys:
|
||||
- v1-kmk-venv-{{ checksum "Pipfile.lock" }}
|
||||
|
||||
- run: pip install pipenv==2018.7.1
|
||||
- run: make test
|
||||
|
||||
build_pyboard:
|
||||
docker:
|
||||
- image: 'python:3.7'
|
||||
@ -70,6 +87,14 @@ workflows:
|
||||
only: /.*/
|
||||
tags:
|
||||
only: /.*/
|
||||
- test:
|
||||
filters:
|
||||
branches:
|
||||
only: /.*/
|
||||
tags:
|
||||
only: /.*/
|
||||
requires:
|
||||
- lint
|
||||
- build_pyboard:
|
||||
filters:
|
||||
branches:
|
||||
@ -77,7 +102,7 @@ workflows:
|
||||
tags:
|
||||
only: /.*/
|
||||
requires:
|
||||
- lint
|
||||
- test
|
||||
- build_teensy_31:
|
||||
filters:
|
||||
branches:
|
||||
@ -85,4 +110,4 @@ workflows:
|
||||
tags:
|
||||
only: /.*/
|
||||
requires:
|
||||
- lint
|
||||
- test
|
||||
|
14
Makefile
14
Makefile
@ -20,6 +20,18 @@ lint: devdeps
|
||||
fix-isort: devdeps
|
||||
@find kmk/ user_keymaps/ -name "*.py" | xargs pipenv run isort
|
||||
|
||||
test: micropython-build-unix
|
||||
@echo "===> Testing keymap_sanity_check.py script"
|
||||
@echo " --> Known good layout should pass..."
|
||||
@MICROPYPATH=tests/test_data:./ ./bin/micropython.sh bin/keymap_sanity_check.py keymaps/known_good.py
|
||||
@echo " --> Layer with ghosted MO should fail..."
|
||||
@MICROPYPATH=tests/test_data:./ ./bin/micropython.sh bin/keymap_sanity_check.py keymaps/ghosted_layer_mo.py 2>/dev/null && exit 1 || exit 0
|
||||
@echo " --> Sharing a pin between rows/cols should fail..."
|
||||
@MICROPYPATH=tests/test_data:./ ./bin/micropython.sh bin/keymap_sanity_check.py keymaps/duplicated_pins_between_row_col.py 2>/dev/null && exit 1 || exit 0
|
||||
@echo " --> Sharing a pin between two rows should fail..."
|
||||
@MICROPYPATH=tests/test_data:./ ./bin/micropython.sh bin/keymap_sanity_check.py keymaps/duplicate_row_pins.py 2>/dev/null && exit 1 || exit 0
|
||||
@echo "===> The sanity checker is sane, unlike klardotsh"
|
||||
|
||||
.submodules: .gitmodules
|
||||
@echo "===> Pulling dependencies, this may take several minutes"
|
||||
@git submodule update --init --recursive
|
||||
@ -41,7 +53,7 @@ circuitpy-deps: .circuitpy-deps
|
||||
|
||||
micropython-deps: .micropython-deps
|
||||
|
||||
vendor/micropython/ports/unix/micropython: vendor/micropython/ports/unix/modules/.kmk_frozen
|
||||
vendor/micropython/ports/unix/micropython: micropython-deps vendor/micropython/ports/unix/modules/.kmk_frozen
|
||||
@make -j4 -C vendor/micropython/ports/unix
|
||||
|
||||
micropython-build-unix: vendor/micropython/ports/unix/micropython
|
||||
|
70
bin/keymap_sanity_check.py
Executable file
70
bin/keymap_sanity_check.py
Executable file
@ -0,0 +1,70 @@
|
||||
#!/usr/bin/env micropython
|
||||
|
||||
import sys
|
||||
|
||||
import uos
|
||||
|
||||
from kmk.common.keycodes import Keycodes
|
||||
|
||||
if len(sys.argv) < 2:
|
||||
print('Must provide a keymap to test as first argument', file=sys.stderr)
|
||||
sys.exit(200)
|
||||
|
||||
user_keymap_file = sys.argv[1]
|
||||
|
||||
if user_keymap_file.endswith('.py'):
|
||||
user_keymap_file = user_keymap_file[:-3]
|
||||
|
||||
# Before we can import the user's keymap, we need to wrangle sys.path to
|
||||
# add our stub modules. Before we can do THAT, we have to figure out where
|
||||
# we actually are, and that's not the most trivial thing in MicroPython!
|
||||
#
|
||||
# The hack here is to see if we can find ourselves in whatever uPy thinks
|
||||
# the current directory is. If we can, we need to head up a level. Obviously,
|
||||
# if the layout of the KMK repo ever changes, this script will need updated
|
||||
# or all hell will break loose.
|
||||
|
||||
# First, hack around https://github.com/micropython/micropython/issues/2322,
|
||||
# where frozen modules aren't available if MicroPython is running a script
|
||||
# rather than via REPL
|
||||
sys.path.insert(0, '')
|
||||
|
||||
if any(fname == 'keymap_sanity_check.py' for fname, _, _ in uos.ilistdir()):
|
||||
sys.path.extend(('../', '../upy-unix-stubs/'))
|
||||
else:
|
||||
sys.path.extend(('./', './upy-unix-stubs'))
|
||||
|
||||
user_keymap = __import__(user_keymap_file)
|
||||
|
||||
if hasattr(user_keymap, 'cols') or hasattr(user_keymap, 'rows'):
|
||||
assert hasattr(user_keymap, 'cols'), 'Handwired keyboards must have both rows and cols defined'
|
||||
assert hasattr(user_keymap, 'rows'), 'Handwired keyboards must have both rows and cols defined'
|
||||
|
||||
# Ensure that no pins are duplicated in a handwire config
|
||||
# This is the same check done in the MatrixScanners, relying
|
||||
# on the __repr__ of the objects to be unique (because generally,
|
||||
# Pin objects themselves are not hashable)
|
||||
assert len(user_keymap.cols) == len({p for p in user_keymap.cols}), \
|
||||
'Cannot use a single pin for multiple columns'
|
||||
assert len(user_keymap.rows) == len({p for p in user_keymap.rows}), \
|
||||
'Cannot use a single pin for multiple rows'
|
||||
|
||||
unique_pins = {repr(c) for c in user_keymap.cols} | {repr(r) for r in user_keymap.rows}
|
||||
assert len(unique_pins) == len(user_keymap.cols) + len(user_keymap.rows), \
|
||||
'Cannot use a pin as both a column and row'
|
||||
|
||||
assert hasattr(user_keymap, 'keymap'), 'Must define a keymap array'
|
||||
assert len(user_keymap.keymap), 'Keymap must contain at least one layer'
|
||||
|
||||
for lidx, layer in enumerate(user_keymap.keymap):
|
||||
assert len(layer), 'Layer {} must contain at least one row'.format(lidx)
|
||||
assert all(len(row) for row in layer), 'Layer {} must not contain empty rows'.format(lidx)
|
||||
assert all(len(row) == len(layer[0]) for row in user_keymap.keymap), \
|
||||
'All rows in layer {} must be of the same length'.format(lidx)
|
||||
|
||||
for ridx, row in enumerate(layer):
|
||||
for cidx, key in enumerate(row):
|
||||
if key.code == Keycodes.Layers._KC_MO:
|
||||
assert user_keymap.keymap[key.layer][ridx][cidx] == Keycodes.KMK.KC_TRNS, \
|
||||
('The physical key used for MO layer switching must be KC_TRNS on the '
|
||||
'target layer or you will get stuck on that layer.')
|
@ -1,11 +0,0 @@
|
||||
[[source]]
|
||||
url = "https://pypi.org/simple"
|
||||
verify_ssl = true
|
||||
name = "pypi"
|
||||
|
||||
[packages]
|
||||
|
||||
[dev-packages]
|
||||
|
||||
[requires]
|
||||
python_version = "3.7"
|
@ -4,6 +4,7 @@ max_line_length = 99
|
||||
ignore = X100
|
||||
per-file-ignores =
|
||||
user_keymaps/**/*.py: F401,E501
|
||||
tests/test_data/keymaps/**/*.py: F401,E501
|
||||
|
||||
[isort]
|
||||
known_third_party = analogio,bitbangio,bleio,board,busio,digitalio,framebuf,gamepad,gc,microcontroller,micropython,pulseio,pyb,pydux,uio,ubluepy,machine,pyb
|
||||
known_third_party = analogio,bitbangio,bleio,board,busio,digitalio,framebuf,gamepad,gc,microcontroller,micropython,pulseio,pyb,pydux,uio,ubluepy,machine,pyb,uos
|
||||
|
0
tests/test_data/__init__.py
Normal file
0
tests/test_data/__init__.py
Normal file
0
tests/test_data/keymaps/__init__.py
Normal file
0
tests/test_data/keymaps/__init__.py
Normal file
29
tests/test_data/keymaps/duplicate_row_pins.py
Normal file
29
tests/test_data/keymaps/duplicate_row_pins.py
Normal file
@ -0,0 +1,29 @@
|
||||
import machine
|
||||
|
||||
from kmk.common.consts import DiodeOrientation
|
||||
from kmk.common.keycodes import KC
|
||||
from kmk.entrypoints.handwire.pyboard import main
|
||||
|
||||
p = machine.Pin.board
|
||||
cols = (p.X10, p.X10, p.X12)
|
||||
rows = (p.X1, p.X2, p.X3)
|
||||
|
||||
diode_orientation = DiodeOrientation.COLUMNS
|
||||
|
||||
keymap = [
|
||||
[
|
||||
[KC.MO(1), KC.H, KC.RESET],
|
||||
[KC.MO(2), KC.I, KC.ENTER],
|
||||
[KC.LCTRL, KC.SPACE, KC.LSHIFT],
|
||||
],
|
||||
[
|
||||
[KC.TRNS, KC.B, KC.C],
|
||||
[KC.NO, KC.D, KC.E],
|
||||
[KC.F, KC.G, KC.H],
|
||||
],
|
||||
[
|
||||
[KC.X, KC.Y, KC.Z],
|
||||
[KC.TRNS, KC.N, KC.O],
|
||||
[KC.R, KC.P, KC.Q],
|
||||
],
|
||||
]
|
29
tests/test_data/keymaps/duplicated_pins_between_row_col.py
Normal file
29
tests/test_data/keymaps/duplicated_pins_between_row_col.py
Normal file
@ -0,0 +1,29 @@
|
||||
import machine
|
||||
|
||||
from kmk.common.consts import DiodeOrientation
|
||||
from kmk.common.keycodes import KC
|
||||
from kmk.entrypoints.handwire.pyboard import main
|
||||
|
||||
p = machine.Pin.board
|
||||
cols = (p.X10, p.X11, p.X12)
|
||||
rows = (p.X1, p.X11, p.X3)
|
||||
|
||||
diode_orientation = DiodeOrientation.COLUMNS
|
||||
|
||||
keymap = [
|
||||
[
|
||||
[KC.MO(1), KC.H, KC.RESET],
|
||||
[KC.MO(2), KC.I, KC.ENTER],
|
||||
[KC.LCTRL, KC.SPACE, KC.LSHIFT],
|
||||
],
|
||||
[
|
||||
[KC.TRNS, KC.B, KC.C],
|
||||
[KC.NO, KC.D, KC.E],
|
||||
[KC.F, KC.G, KC.H],
|
||||
],
|
||||
[
|
||||
[KC.X, KC.Y, KC.Z],
|
||||
[KC.TRNS, KC.N, KC.O],
|
||||
[KC.R, KC.P, KC.Q],
|
||||
],
|
||||
]
|
29
tests/test_data/keymaps/ghosted_layer_mo.py
Normal file
29
tests/test_data/keymaps/ghosted_layer_mo.py
Normal file
@ -0,0 +1,29 @@
|
||||
import machine
|
||||
|
||||
from kmk.common.consts import DiodeOrientation
|
||||
from kmk.common.keycodes import KC
|
||||
from kmk.entrypoints.handwire.pyboard import main
|
||||
|
||||
p = machine.Pin.board
|
||||
cols = (p.X10, p.X11, p.X12)
|
||||
rows = (p.X1, p.X2, p.X3)
|
||||
|
||||
diode_orientation = DiodeOrientation.COLUMNS
|
||||
|
||||
keymap = [
|
||||
[
|
||||
[KC.MO(1), KC.H, KC.RESET],
|
||||
[KC.MO(2), KC.I, KC.ENTER],
|
||||
[KC.LCTRL, KC.SPACE, KC.LSHIFT],
|
||||
],
|
||||
[
|
||||
[KC.A, KC.B, KC.C],
|
||||
[KC.NO, KC.D, KC.E],
|
||||
[KC.F, KC.G, KC.H],
|
||||
],
|
||||
[
|
||||
[KC.X, KC.Y, KC.Z],
|
||||
[KC.TRNS, KC.N, KC.O],
|
||||
[KC.R, KC.P, KC.Q],
|
||||
],
|
||||
]
|
29
tests/test_data/keymaps/known_good.py
Normal file
29
tests/test_data/keymaps/known_good.py
Normal file
@ -0,0 +1,29 @@
|
||||
import machine
|
||||
|
||||
from kmk.common.consts import DiodeOrientation
|
||||
from kmk.common.keycodes import KC
|
||||
from kmk.entrypoints.handwire.pyboard import main
|
||||
|
||||
p = machine.Pin.board
|
||||
cols = (p.X10, p.X11, p.X12)
|
||||
rows = (p.X1, p.X2, p.X3)
|
||||
|
||||
diode_orientation = DiodeOrientation.COLUMNS
|
||||
|
||||
keymap = [
|
||||
[
|
||||
[KC.MO(1), KC.H, KC.RESET],
|
||||
[KC.MO(2), KC.I, KC.ENTER],
|
||||
[KC.LCTRL, KC.SPACE, KC.LSHIFT],
|
||||
],
|
||||
[
|
||||
[KC.TRNS, KC.B, KC.C],
|
||||
[KC.NO, KC.D, KC.E],
|
||||
[KC.F, KC.G, KC.H],
|
||||
],
|
||||
[
|
||||
[KC.X, KC.Y, KC.Z],
|
||||
[KC.TRNS, KC.N, KC.O],
|
||||
[KC.R, KC.P, KC.Q],
|
||||
],
|
||||
]
|
18
upy-unix-stubs/machine/__init__.py
Normal file
18
upy-unix-stubs/machine/__init__.py
Normal file
@ -0,0 +1,18 @@
|
||||
class Anything:
|
||||
'''
|
||||
A stub class which will repr as a provided name
|
||||
'''
|
||||
def __init__(self, name):
|
||||
self.name = name
|
||||
|
||||
def __repr__(self):
|
||||
return 'Anything<{}>'.format(self.name)
|
||||
|
||||
|
||||
class Passthrough:
|
||||
def __getattr__(self, attr):
|
||||
return Anything(attr)
|
||||
|
||||
|
||||
class Pin:
|
||||
board = Passthrough()
|
0
upy-unix-stubs/pyb/USB_HID.py
Normal file
0
upy-unix-stubs/pyb/USB_HID.py
Normal file
0
upy-unix-stubs/pyb/__init__.py
Normal file
0
upy-unix-stubs/pyb/__init__.py
Normal file
0
upy-unix-stubs/pyb/delay.py
Normal file
0
upy-unix-stubs/pyb/delay.py
Normal file
0
user_keymaps/__init__.py
Normal file
0
user_keymaps/__init__.py
Normal file
0
user_keymaps/kdb424/__init__.py
Normal file
0
user_keymaps/kdb424/__init__.py
Normal file
0
user_keymaps/klardotsh/__init__.py
Normal file
0
user_keymaps/klardotsh/__init__.py
Normal file
Loading…
Reference in New Issue
Block a user