Skip to content

Commit d1ccca9

Browse files
committed
feat: Support board revisions in keyboard add/list commands
"zmk keyboard add" nows understand board revisions. If you attempt to add a board which has multiple revisions (and you don't provide a revision with a parameter like "-c nice_nano@2"), it will now prompt you to select a revision. When copying .keymap and .conf files to your config repo, if the keyboard is a board with a revision, it will now check for versioned files before checking for a common file with no revision. "zmk keyboard list" now supports a --revisions flag which prints each board revision as a separate item.
1 parent bfa4008 commit d1ccca9

File tree

6 files changed

+309
-31
lines changed

6 files changed

+309
-31
lines changed

zmk/commands/config.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,7 @@
1010
from .. import styles
1111
from ..config import Config, get_config
1212

13-
console = Console(
14-
highlighter=styles.KeyValueHighlighter(), theme=styles.KEY_VALUE_THEME
15-
)
13+
console = Console(highlighter=styles.KeyValueHighlighter(), theme=styles.THEME)
1614

1715

1816
def _path_callback(ctx: typer.Context, value: bool):

zmk/commands/keyboard/add.py

Lines changed: 71 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,16 @@
1515
from ...exceptions import FatalError
1616
from ...hardware import (
1717
Board,
18+
Hardware,
1819
Keyboard,
1920
Shield,
21+
append_revision,
2022
get_hardware,
2123
is_compatible,
24+
normalize_revision,
2225
show_hardware_menu,
26+
show_revision_menu,
27+
split_revision,
2328
)
2429
from ...repo import Repo
2530
from ...util import spinner
@@ -59,12 +64,21 @@ def keyboard_add(
5964

6065
keyboard = None
6166
controller = None
67+
revision = None
6268

6369
if keyboard_id:
70+
keyboard_id, keyboard_revision = split_revision(keyboard_id)
71+
6472
keyboard = hardware.find_keyboard(keyboard_id)
6573
if keyboard is None:
6674
raise KeyboardNotFound(keyboard_id)
6775

76+
# If the keyboard ID contained a revision, use that.
77+
# Make sure it is valid before continuing to any other prompts.
78+
if keyboard_revision:
79+
revision = keyboard_revision
80+
_check_revision(keyboard, revision)
81+
6882
if controller_id:
6983
if not isinstance(keyboard, Shield):
7084
raise FatalError(
@@ -77,12 +91,20 @@ def keyboard_add(
7791
raise ControllerNotFound(controller_id)
7892

7993
elif controller_id:
94+
controller_id, controller_revision = split_revision(controller_id)
95+
8096
# User specified a controller but not a keyboard. Filter the keyboard
8197
# list to just those compatible with the controller.
8298
controller = hardware.find_controller(controller_id)
8399
if controller is None:
84100
raise ControllerNotFound(controller_id)
85101

102+
# If the controller ID contained a revision, use that.
103+
# Make sure it is valid before continuing to any other prompts.
104+
if controller_revision:
105+
revision = controller_revision
106+
_check_revision(controller, revision)
107+
86108
hardware.keyboards = [
87109
kb
88110
for kb in hardware.keyboards
@@ -108,16 +130,28 @@ def keyboard_add(
108130
f'Keyboard "{keyboard.id}" is not compatible with controller "{controller.id}"'
109131
)
110132

133+
# Check if the controller needs a revision.
134+
revision = _get_revision(controller, revision)
135+
else:
136+
# If the keyboard isn't a shield, it may need a revision.
137+
revision = _get_revision(keyboard, revision)
138+
111139
name = keyboard.id
112140
if controller:
113141
name += ", " + controller.id
114142

115-
if _add_keyboard(repo, keyboard, controller):
143+
if revision:
144+
revision = normalize_revision(revision)
145+
name = append_revision(name, revision)
146+
147+
if _add_keyboard(repo, keyboard, controller, revision):
116148
console.print(f'Added "{name}".')
117149
else:
118150
console.print(f'"{name}" is already in the build matrix.')
119151

120-
console.print(f'Run "zmk code {keyboard.id}" to edit the keymap.')
152+
keymap_name = keyboard.get_keymap_path(revision).with_suffix("").name
153+
154+
console.print(f'Run "zmk code {keymap_name}" to edit the keymap.')
121155

122156

123157
class KeyboardNotFound(FatalError):
@@ -140,23 +174,26 @@ def _copy_keyboard_file(repo: Repo, path: Path):
140174
shutil.copy2(path, dest_path)
141175

142176

143-
def _get_build_items(keyboard: Keyboard, controller: Board | None):
177+
def _get_build_items(
178+
keyboard: Keyboard, controller: Board | None, revision: str | None
179+
):
144180
boards = []
145181
shields = []
146182

147183
match keyboard:
148184
case Shield(id=shield_id, siblings=siblings):
149185
if controller is None:
150-
raise ValueError("controller may not be None if keyboard is a shield")
186+
raise FatalError("controller may not be None if keyboard is a shield")
151187

152188
shields = siblings or [shield_id]
153-
boards = [controller.id]
189+
boards = [append_revision(controller.id, revision)]
154190

155191
case Board(id=board_id, siblings=siblings):
156192
boards = siblings or [board_id]
193+
boards = [append_revision(board, revision) for board in boards]
157194

158195
case _:
159-
raise ValueError("Unexpected keyboard/controller combination")
196+
raise FatalError("Unexpected keyboard/controller combination")
160197

161198
if shields:
162199
return [
@@ -166,11 +203,35 @@ def _get_build_items(keyboard: Keyboard, controller: Board | None):
166203
return [BuildItem(board=b) for b in boards]
167204

168205

169-
def _add_keyboard(repo: Repo, keyboard: Keyboard, controller: Board | None):
170-
_copy_keyboard_file(repo, keyboard.keymap_path)
171-
_copy_keyboard_file(repo, keyboard.config_path)
206+
def _get_revision(board: Hardware, revision: str | None):
207+
# If no revision was specified and the board uses revisions, prompt to
208+
# select a revision.
209+
return revision if revision else show_revision_menu(board)
210+
211+
212+
def _check_revision(board: Hardware, revision: str):
213+
if board.has_revision(revision):
214+
# Revision is OK
215+
return
216+
217+
supported_revisions = board.get_revisions()
218+
219+
if not supported_revisions:
220+
raise FatalError(f"{board.id} does not have any revisions.")
221+
222+
raise FatalError(
223+
f'{board.id} does not support revision "@{revision}". Use one of:\n'
224+
+ "\n".join(f" @{normalize_revision(rev)}" for rev in supported_revisions)
225+
)
226+
227+
228+
def _add_keyboard(
229+
repo: Repo, keyboard: Keyboard, controller: Board | None, revision: str | None
230+
):
231+
_copy_keyboard_file(repo, keyboard.get_keymap_path(revision))
232+
_copy_keyboard_file(repo, keyboard.get_config_path(revision))
172233

173-
items = _get_build_items(keyboard, controller)
234+
items = _get_build_items(keyboard, controller, revision)
174235

175236
matrix = BuildMatrix.from_repo(repo)
176237
added = matrix.append(items)

zmk/commands/keyboard/list.py

Lines changed: 40 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,17 +5,27 @@
55
from collections.abc import Iterable
66
from typing import Annotated
77

8-
import rich
98
import typer
109
from rich import box
1110
from rich.columns import Columns
11+
from rich.console import Console
1212
from rich.table import Table
1313

14+
from zmk import styles
15+
1416
from ...backports import StrEnum
1517
from ...build import BuildItem, BuildMatrix
1618
from ...config import get_config
1719
from ...exceptions import FatalError
18-
from ...hardware import Board, Hardware, Shield, get_hardware, is_compatible
20+
from ...hardware import (
21+
Board,
22+
Hardware,
23+
Shield,
24+
append_revision,
25+
get_hardware,
26+
is_compatible,
27+
normalize_revision,
28+
)
1929
from ...util import spinner
2030

2131
# TODO: allow output as unformatted list
@@ -36,7 +46,12 @@ def _list_build_matrix(ctx: typer.Context, value: bool):
3646
if not value:
3747
return
3848

39-
console = rich.get_console()
49+
console = Console(
50+
highlighter=styles.chain_highlighters(
51+
[styles.BoardIdHighlighter(), styles.CommandLineHighlighter()]
52+
),
53+
theme=styles.THEME,
54+
)
4055

4156
cfg = get_config(ctx)
4257
repo = cfg.get_repo()
@@ -48,7 +63,12 @@ def _list_build_matrix(ctx: typer.Context, value: bool):
4863
has_cmake_args = any(item.cmake_args for item in include)
4964
has_artifact_name = any(item.artifact_name for item in include)
5065

51-
table = Table(box=box.SQUARE, border_style="dim blue", header_style="bright_cyan")
66+
table = Table(
67+
box=box.SQUARE,
68+
border_style="dim blue",
69+
header_style="bright_cyan",
70+
highlight=True,
71+
)
5272
table.add_column("Board")
5373
table.add_column("Shield")
5474
if has_snippet:
@@ -129,10 +149,16 @@ def keyboard_list(
129149
"--standalone", help="List only keyboards with onboard controllers."
130150
),
131151
] = False,
152+
revisions: Annotated[
153+
bool,
154+
typer.Option(
155+
"--revisions", "--rev", "-r", help="Display revisions for each board."
156+
),
157+
] = False,
132158
) -> None:
133159
"""List supported keyboards or keyboards in the build matrix."""
134160

135-
console = rich.get_console()
161+
console = Console(highlighter=styles.BoardIdHighlighter(), theme=styles.THEME)
136162

137163
cfg = get_config(ctx)
138164
repo = cfg.get_repo()
@@ -187,7 +213,15 @@ def keyboard_list(
187213
list_type = ListType.KEYBOARD
188214

189215
def print_items(header: str, items: Iterable[Hardware]):
190-
names = [item.id for item in items]
216+
if revisions:
217+
names = [
218+
append_revision(item.id, normalize_revision(rev))
219+
for item in items
220+
for rev in (item.get_revisions() or [None])
221+
]
222+
else:
223+
names = [item.id for item in items]
224+
191225
if not names:
192226
return
193227

0 commit comments

Comments
 (0)