zmk 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- zmk/__init__.py +0 -0
- zmk/_version.py +16 -0
- zmk/backports.py +10 -0
- zmk/build.py +174 -0
- zmk/commands/__init__.py +28 -0
- zmk/commands/cd.py +56 -0
- zmk/commands/code.py +197 -0
- zmk/commands/config.py +83 -0
- zmk/commands/download.py +26 -0
- zmk/commands/init.py +134 -0
- zmk/commands/keyboard/__init__.py +21 -0
- zmk/commands/keyboard/add.py +164 -0
- zmk/commands/keyboard/list.py +201 -0
- zmk/commands/keyboard/new.py +356 -0
- zmk/commands/keyboard/remove.py +28 -0
- zmk/commands/module/__init__.py +19 -0
- zmk/commands/module/add.py +162 -0
- zmk/commands/module/list.py +36 -0
- zmk/commands/module/remove.py +113 -0
- zmk/commands/west.py +45 -0
- zmk/config.py +123 -0
- zmk/exceptions.py +52 -0
- zmk/hardware.py +234 -0
- zmk/main.py +57 -0
- zmk/menu.py +482 -0
- zmk/prompt.py +17 -0
- zmk/repo.py +238 -0
- zmk/styles.py +21 -0
- zmk/templates/__init__.py +82 -0
- zmk/templates/board/nrf52840/Kconfig +22 -0
- zmk/templates/board/nrf52840/Kconfig.defconfig +22 -0
- zmk/templates/board/nrf52840/board.cmake +5 -0
- zmk/templates/board/nrf52840/board.yaml +18 -0
- zmk/templates/board/nrf52840/board_defconfig +34 -0
- zmk/templates/board/nrf52840/common_inner.dtsi +29 -0
- zmk/templates/board/nrf52840/common_outer.dtsi +59 -0
- zmk/templates/board/nrf52840/pinctrl.dtsi +26 -0
- zmk/templates/board/nrf52840/split/${id}.conf +1 -0
- zmk/templates/board/nrf52840/split/${id}.dtsi +26 -0
- zmk/templates/board/nrf52840/split/${id}.keymap +1 -0
- zmk/templates/board/nrf52840/split/${id}.yaml +1 -0
- zmk/templates/board/nrf52840/split/${id}.zmk.yml +8 -0
- zmk/templates/board/nrf52840/split/${id}_left-pinctrl.dtsi +1 -0
- zmk/templates/board/nrf52840/split/${id}_left.dts +1 -0
- zmk/templates/board/nrf52840/split/${id}_left_defconfig +1 -0
- zmk/templates/board/nrf52840/split/${id}_right-pinctrl.dtsi +1 -0
- zmk/templates/board/nrf52840/split/${id}_right.dts +1 -0
- zmk/templates/board/nrf52840/split/${id}_right_defconfig +1 -0
- zmk/templates/board/nrf52840/split/Kconfig +3 -0
- zmk/templates/board/nrf52840/split/Kconfig.board +7 -0
- zmk/templates/board/nrf52840/split/Kconfig.defconfig +16 -0
- zmk/templates/board/nrf52840/split/board.cmake +1 -0
- zmk/templates/board/nrf52840/unibody/${id}-pinctrl.dtsi +1 -0
- zmk/templates/board/nrf52840/unibody/${id}.conf +1 -0
- zmk/templates/board/nrf52840/unibody/${id}.dts +24 -0
- zmk/templates/board/nrf52840/unibody/${id}.keymap +1 -0
- zmk/templates/board/nrf52840/unibody/${id}.yaml +1 -0
- zmk/templates/board/nrf52840/unibody/${id}.zmk.yml +4 -0
- zmk/templates/board/nrf52840/unibody/${id}_defconfig +1 -0
- zmk/templates/board/nrf52840/unibody/Kconfig +3 -0
- zmk/templates/board/nrf52840/unibody/Kconfig.board +3 -0
- zmk/templates/board/nrf52840/unibody/Kconfig.defconfig +9 -0
- zmk/templates/board/nrf52840/unibody/board.cmake +1 -0
- zmk/templates/board/other/Kconfig +10 -0
- zmk/templates/board/other/Kconfig.defconfig +22 -0
- zmk/templates/board/other/board.cmake +6 -0
- zmk/templates/board/other/board.yaml +19 -0
- zmk/templates/board/other/board_defconfig +15 -0
- zmk/templates/board/other/pinctrl.dtsi +8 -0
- zmk/templates/board/other/split/${id}.conf +1 -0
- zmk/templates/board/other/split/${id}.dtsi +5 -0
- zmk/templates/board/other/split/${id}.keymap +1 -0
- zmk/templates/board/other/split/${id}.yaml +1 -0
- zmk/templates/board/other/split/${id}.zmk.yml +8 -0
- zmk/templates/board/other/split/${id}_left-pinctrl.dtsi +1 -0
- zmk/templates/board/other/split/${id}_left.dts +1 -0
- zmk/templates/board/other/split/${id}_left_defconfig +1 -0
- zmk/templates/board/other/split/${id}_right-pinctrl.dtsi +1 -0
- zmk/templates/board/other/split/${id}_right.dts +1 -0
- zmk/templates/board/other/split/${id}_right_defconfig +1 -0
- zmk/templates/board/other/split/Kconfig +3 -0
- zmk/templates/board/other/split/Kconfig.board +13 -0
- zmk/templates/board/other/split/Kconfig.defconfig +16 -0
- zmk/templates/board/other/split/board.cmake +1 -0
- zmk/templates/board/other/unibody/${id}-pinctrl.dtsi +1 -0
- zmk/templates/board/other/unibody/${id}.conf +1 -0
- zmk/templates/board/other/unibody/${id}.dts +5 -0
- zmk/templates/board/other/unibody/${id}.keymap +1 -0
- zmk/templates/board/other/unibody/${id}.yaml +1 -0
- zmk/templates/board/other/unibody/${id}.zmk.yml +4 -0
- zmk/templates/board/other/unibody/${id}_defconfig +1 -0
- zmk/templates/board/other/unibody/Kconfig +3 -0
- zmk/templates/board/other/unibody/Kconfig.board +8 -0
- zmk/templates/board/other/unibody/Kconfig.defconfig +9 -0
- zmk/templates/board/other/unibody/board.cmake +1 -0
- zmk/templates/common/board.dts +32 -0
- zmk/templates/common/board_left.dts +7 -0
- zmk/templates/common/board_right.dts +7 -0
- zmk/templates/common/board_split.dtsi +7 -0
- zmk/templates/common/default.conf +12 -0
- zmk/templates/common/hardware.zmk.yml +19 -0
- zmk/templates/common/keymap.dtsi +23 -0
- zmk/templates/common/keymap_split.dtsi +9 -0
- zmk/templates/common/kscan.dtsi +24 -0
- zmk/templates/common/kscan_split_common.dtsi +20 -0
- zmk/templates/common/kscan_split_left.dtsi +9 -0
- zmk/templates/common/kscan_split_right.dtsi +14 -0
- zmk/templates/common/matrix_transform.dtsi +12 -0
- zmk/templates/common/matrix_transform_split.dtsi +12 -0
- zmk/templates/common/shield.overlay +24 -0
- zmk/templates/common/shield_left.overlay +6 -0
- zmk/templates/common/shield_right.overlay +6 -0
- zmk/templates/shield/split/${id}.conf +1 -0
- zmk/templates/shield/split/${id}.dtsi +7 -0
- zmk/templates/shield/split/${id}.keymap +1 -0
- zmk/templates/shield/split/${id}.zmk.yml +5 -0
- zmk/templates/shield/split/${id}_left.overlay +1 -0
- zmk/templates/shield/split/${id}_right.overlay +1 -0
- zmk/templates/shield/split/Kconfig.defconfig +16 -0
- zmk/templates/shield/split/Kconfig.shield +5 -0
- zmk/templates/shield/unibody/${id}.conf +1 -0
- zmk/templates/shield/unibody/${id}.keymap +1 -0
- zmk/templates/shield/unibody/${id}.overlay +4 -0
- zmk/templates/shield/unibody/${id}.zmk.yml +5 -0
- zmk/templates/shield/unibody/Kconfig.defconfig +6 -0
- zmk/templates/shield/unibody/Kconfig.shield +2 -0
- zmk/terminal.py +179 -0
- zmk/util.py +53 -0
- zmk/yaml.py +64 -0
- zmk-0.1.0.dist-info/LICENSE +21 -0
- zmk-0.1.0.dist-info/METADATA +290 -0
- zmk-0.1.0.dist-info/RECORD +135 -0
- zmk-0.1.0.dist-info/WHEEL +5 -0
- zmk-0.1.0.dist-info/entry_points.txt +2 -0
- zmk-0.1.0.dist-info/top_level.txt +1 -0
zmk/__init__.py
ADDED
|
File without changes
|
zmk/_version.py
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# file generated by setuptools_scm
|
|
2
|
+
# don't change, don't track in version control
|
|
3
|
+
TYPE_CHECKING = False
|
|
4
|
+
if TYPE_CHECKING:
|
|
5
|
+
from typing import Tuple, Union
|
|
6
|
+
VERSION_TUPLE = Tuple[Union[int, str], ...]
|
|
7
|
+
else:
|
|
8
|
+
VERSION_TUPLE = object
|
|
9
|
+
|
|
10
|
+
version: str
|
|
11
|
+
__version__: str
|
|
12
|
+
__version_tuple__: VERSION_TUPLE
|
|
13
|
+
version_tuple: VERSION_TUPLE
|
|
14
|
+
|
|
15
|
+
__version__ = version = '0.1.0'
|
|
16
|
+
__version_tuple__ = version_tuple = (0, 1, 0)
|
zmk/backports.py
ADDED
zmk/build.py
ADDED
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Build matrix processing.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import collections.abc
|
|
6
|
+
from dataclasses import asdict, dataclass, field
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
from typing import Any, Iterable, Optional
|
|
9
|
+
|
|
10
|
+
import dacite
|
|
11
|
+
|
|
12
|
+
from .repo import Repo
|
|
13
|
+
from .yaml import YAML
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@dataclass
|
|
17
|
+
class BuildItem:
|
|
18
|
+
"""An item in the build matrix"""
|
|
19
|
+
|
|
20
|
+
board: str
|
|
21
|
+
shield: Optional[str] = None
|
|
22
|
+
snippet: Optional[str] = None
|
|
23
|
+
cmake_args: Optional[str] = None
|
|
24
|
+
artifact_name: Optional[str] = None
|
|
25
|
+
|
|
26
|
+
def __rich__(self):
|
|
27
|
+
parts = []
|
|
28
|
+
parts.append(self.board)
|
|
29
|
+
|
|
30
|
+
if self.shield:
|
|
31
|
+
parts.append(self.shield)
|
|
32
|
+
|
|
33
|
+
if self.snippet:
|
|
34
|
+
parts.append(f"[dim]snippet: {self.snippet}[/dim]")
|
|
35
|
+
|
|
36
|
+
if self.artifact_name:
|
|
37
|
+
parts.append(f"[dim]artifact-name: {self.artifact_name}[/dim]")
|
|
38
|
+
|
|
39
|
+
if self.cmake_args:
|
|
40
|
+
parts.append(f"[dim]cmake-args: {self.cmake_args}[/dim]")
|
|
41
|
+
|
|
42
|
+
return "[dim], [/dim]".join(parts)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
@dataclass
|
|
46
|
+
class _BuildMatrixWrapper:
|
|
47
|
+
include: list[BuildItem] = field(default_factory=list)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
class BuildMatrix:
|
|
51
|
+
"""Interface to read and edit ZMK's build.yaml file"""
|
|
52
|
+
|
|
53
|
+
_path: Path
|
|
54
|
+
_yaml: YAML
|
|
55
|
+
_data: Any
|
|
56
|
+
|
|
57
|
+
@classmethod
|
|
58
|
+
def from_repo(cls, repo: Repo):
|
|
59
|
+
"""Get the build matrix for a repo"""
|
|
60
|
+
return cls(repo.build_matrix_path)
|
|
61
|
+
|
|
62
|
+
def __init__(self, path: Path) -> None:
|
|
63
|
+
self._path = path
|
|
64
|
+
self._yaml = YAML(typ="rt")
|
|
65
|
+
self._yaml.indent(mapping=2, sequence=4, offset=2)
|
|
66
|
+
try:
|
|
67
|
+
self._data = self._yaml.load(self._path)
|
|
68
|
+
except FileNotFoundError:
|
|
69
|
+
self._data = None
|
|
70
|
+
|
|
71
|
+
def write(self):
|
|
72
|
+
"""Updated the YAML file, creating it if necessary"""
|
|
73
|
+
self._yaml.dump(self._data, self._path)
|
|
74
|
+
|
|
75
|
+
@property
|
|
76
|
+
def path(self):
|
|
77
|
+
"""Path to the matrix's YAML file"""
|
|
78
|
+
return self._path
|
|
79
|
+
|
|
80
|
+
@property
|
|
81
|
+
def include(self) -> list[BuildItem]:
|
|
82
|
+
"""List of build items in the matrix"""
|
|
83
|
+
normalized = _keys_to_python(self._data)
|
|
84
|
+
if not normalized:
|
|
85
|
+
return []
|
|
86
|
+
|
|
87
|
+
wrapper = dacite.from_dict(_BuildMatrixWrapper, normalized)
|
|
88
|
+
return wrapper.include
|
|
89
|
+
|
|
90
|
+
def has_item(self, item: BuildItem):
|
|
91
|
+
"""Get whether the matrix has a build item"""
|
|
92
|
+
return item in self.include
|
|
93
|
+
|
|
94
|
+
def append(self, items: BuildItem | Iterable[BuildItem]) -> list[BuildItem]:
|
|
95
|
+
"""
|
|
96
|
+
Add build items to the matrix.
|
|
97
|
+
|
|
98
|
+
:return: the items that were added.
|
|
99
|
+
"""
|
|
100
|
+
items = [items] if isinstance(items, BuildItem) else items
|
|
101
|
+
|
|
102
|
+
include = self.include
|
|
103
|
+
items = [i for i in items if i not in include]
|
|
104
|
+
|
|
105
|
+
if not items:
|
|
106
|
+
return []
|
|
107
|
+
|
|
108
|
+
if not self._data:
|
|
109
|
+
self._data = self._yaml.map()
|
|
110
|
+
|
|
111
|
+
if "include" not in self._data:
|
|
112
|
+
self._data["include"] = self._yaml.seq()
|
|
113
|
+
|
|
114
|
+
self._data["include"].extend([_to_yaml(i) for i in items])
|
|
115
|
+
return items
|
|
116
|
+
|
|
117
|
+
def remove(self, items: BuildItem | Iterable[BuildItem]) -> list[BuildItem]:
|
|
118
|
+
"""
|
|
119
|
+
Remove build items from the matrix.
|
|
120
|
+
|
|
121
|
+
:return: the items that were removed.
|
|
122
|
+
"""
|
|
123
|
+
if not self._data or "include" not in self._data:
|
|
124
|
+
return False
|
|
125
|
+
|
|
126
|
+
removed = []
|
|
127
|
+
items = [items] if isinstance(items, BuildItem) else items
|
|
128
|
+
|
|
129
|
+
# TODO: there's probably a more efficient way to do this, but this is easy
|
|
130
|
+
for item in items:
|
|
131
|
+
try:
|
|
132
|
+
index = self.include.index(item)
|
|
133
|
+
del self._data["include"][index]
|
|
134
|
+
removed.append(item)
|
|
135
|
+
except ValueError:
|
|
136
|
+
pass
|
|
137
|
+
|
|
138
|
+
return removed
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
def _keys_to_python(data: Any):
|
|
142
|
+
"""
|
|
143
|
+
Fix any keys with hyphens to underscores so that dacite.from_dict() will
|
|
144
|
+
work correctly.
|
|
145
|
+
"""
|
|
146
|
+
|
|
147
|
+
def fix_key(key: str):
|
|
148
|
+
return key.replace("-", "_")
|
|
149
|
+
|
|
150
|
+
match data:
|
|
151
|
+
case str():
|
|
152
|
+
return data
|
|
153
|
+
|
|
154
|
+
case collections.abc.Sequence():
|
|
155
|
+
return [_keys_to_python(i) for i in data]
|
|
156
|
+
|
|
157
|
+
case collections.abc.Mapping():
|
|
158
|
+
return {fix_key(k): _keys_to_python(v) for k, v in data.items()}
|
|
159
|
+
|
|
160
|
+
case _:
|
|
161
|
+
return data
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
def _to_yaml(item: BuildItem):
|
|
165
|
+
"""
|
|
166
|
+
Convert a BuildItem to a dict with keys changed back from underscores to hyphens.
|
|
167
|
+
"""
|
|
168
|
+
|
|
169
|
+
def fix_key(key: str):
|
|
170
|
+
return key.replace("_", "-")
|
|
171
|
+
|
|
172
|
+
data = asdict(item)
|
|
173
|
+
|
|
174
|
+
return {fix_key(k): v for k, v in data.items() if v is not None}
|
zmk/commands/__init__.py
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
"""
|
|
2
|
+
ZMK CLI subcommands.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import typer
|
|
6
|
+
|
|
7
|
+
from . import cd, code, config, download, init, keyboard, module, west
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def register(app: typer.Typer):
|
|
11
|
+
"""Register all commands with the app"""
|
|
12
|
+
app.command()(cd.cd)
|
|
13
|
+
app.command()(code.code)
|
|
14
|
+
app.command()(config.config)
|
|
15
|
+
app.command()(download.download)
|
|
16
|
+
app.command(name="dl")(download.download)
|
|
17
|
+
app.command()(init.init)
|
|
18
|
+
app.command()(west.update)
|
|
19
|
+
app.command(
|
|
20
|
+
add_help_option=False,
|
|
21
|
+
context_settings={
|
|
22
|
+
"allow_extra_args": True,
|
|
23
|
+
"ignore_unknown_options": True,
|
|
24
|
+
},
|
|
25
|
+
)(west.west)
|
|
26
|
+
|
|
27
|
+
app.add_typer(keyboard.app)
|
|
28
|
+
app.add_typer(module.app)
|
zmk/commands/cd.py
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
"""
|
|
2
|
+
"zmk cd" command.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import os
|
|
6
|
+
import subprocess
|
|
7
|
+
import sys
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
|
|
10
|
+
import shellingham
|
|
11
|
+
import typer
|
|
12
|
+
|
|
13
|
+
from ..config import Config
|
|
14
|
+
from ..exceptions import FatalError, FatalHomeMissing, FatalHomeNotSet
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def cd(ctx: typer.Context):
|
|
18
|
+
"""Go to the ZMK config repo."""
|
|
19
|
+
if not sys.stdout.isatty():
|
|
20
|
+
raise FatalError(
|
|
21
|
+
"This command can only be used from an interactive shell. "
|
|
22
|
+
'Use "cd $(zmk config user.home)" instead.'
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
cfg = ctx.find_object(Config)
|
|
26
|
+
home = cfg.home_path
|
|
27
|
+
|
|
28
|
+
if home is None:
|
|
29
|
+
raise FatalHomeNotSet()
|
|
30
|
+
|
|
31
|
+
if not home.is_dir():
|
|
32
|
+
raise FatalHomeMissing(home)
|
|
33
|
+
|
|
34
|
+
if home == Path(os.getcwd()):
|
|
35
|
+
# Already in the home directory. Nothing to do.
|
|
36
|
+
return
|
|
37
|
+
|
|
38
|
+
os.chdir(home)
|
|
39
|
+
|
|
40
|
+
try:
|
|
41
|
+
_, shell = shellingham.detect_shell()
|
|
42
|
+
except shellingham.ShellDetectionFailure:
|
|
43
|
+
shell = _default_shell()
|
|
44
|
+
|
|
45
|
+
if os.name == "nt":
|
|
46
|
+
subprocess.run([shell], env=os.environ.copy(), check=False)
|
|
47
|
+
else:
|
|
48
|
+
os.execl(shell, shell)
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def _default_shell():
|
|
52
|
+
if os.name == "posix":
|
|
53
|
+
return os.environ["SHELL"]
|
|
54
|
+
if os.name == "nt":
|
|
55
|
+
return os.environ["COMSPEC"]
|
|
56
|
+
raise NotImplementedError(f"Don't know how to get a shell for OS {os.name!r}")
|
zmk/commands/code.py
ADDED
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
"""
|
|
2
|
+
"zmk code" command.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import platform
|
|
6
|
+
import shlex
|
|
7
|
+
import shutil
|
|
8
|
+
import subprocess
|
|
9
|
+
from configparser import NoOptionError
|
|
10
|
+
from dataclasses import dataclass, field
|
|
11
|
+
from enum import Flag, auto
|
|
12
|
+
from typing import Annotated, Callable, Optional
|
|
13
|
+
|
|
14
|
+
import rich
|
|
15
|
+
import typer
|
|
16
|
+
from rich.markdown import Markdown
|
|
17
|
+
|
|
18
|
+
from ..config import Config, Settings
|
|
19
|
+
from ..exceptions import FatalError
|
|
20
|
+
from ..menu import show_menu
|
|
21
|
+
from ..repo import Repo
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def code(
|
|
25
|
+
ctx: typer.Context,
|
|
26
|
+
keyboard: Annotated[
|
|
27
|
+
Optional[str],
|
|
28
|
+
typer.Argument(
|
|
29
|
+
help="Name of the keyboard to edit. If omitted, opens the repo directory.",
|
|
30
|
+
),
|
|
31
|
+
] = None,
|
|
32
|
+
open_conf: Annotated[
|
|
33
|
+
bool,
|
|
34
|
+
typer.Option("--conf", "-c", help="Open the .conf file instead of the keymap."),
|
|
35
|
+
] = False,
|
|
36
|
+
open_build_matrix: Annotated[
|
|
37
|
+
bool,
|
|
38
|
+
typer.Option(
|
|
39
|
+
"--build", "-b", help="Open the build matrix instead of a keymap."
|
|
40
|
+
),
|
|
41
|
+
] = False,
|
|
42
|
+
):
|
|
43
|
+
"""Open the repo or a .keymap or .conf file in a text editor."""
|
|
44
|
+
|
|
45
|
+
cfg = ctx.find_object(Config)
|
|
46
|
+
repo = cfg.get_repo()
|
|
47
|
+
|
|
48
|
+
if open_build_matrix:
|
|
49
|
+
path = repo.build_matrix_path
|
|
50
|
+
else:
|
|
51
|
+
path = _get_file(repo, keyboard, open_conf)
|
|
52
|
+
|
|
53
|
+
editor = _get_editor(cfg, path.is_dir())
|
|
54
|
+
|
|
55
|
+
cmd = shlex.split(editor) + [path]
|
|
56
|
+
subprocess.call(cmd, shell=True)
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def _get_file(repo: Repo, keyboard: str, open_conf: bool):
|
|
60
|
+
if not keyboard:
|
|
61
|
+
return repo.path
|
|
62
|
+
|
|
63
|
+
ext = ".conf" if open_conf else ".keymap"
|
|
64
|
+
return repo.config_path / f"{keyboard}{ext}"
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def _get_editor(cfg: Config, is_directory: bool):
|
|
68
|
+
if is_directory:
|
|
69
|
+
try:
|
|
70
|
+
return cfg.get(Settings.CORE_EXPLORER)
|
|
71
|
+
except NoOptionError:
|
|
72
|
+
pass
|
|
73
|
+
try:
|
|
74
|
+
return cfg.get(Settings.CORE_EDITOR)
|
|
75
|
+
except NoOptionError:
|
|
76
|
+
pass
|
|
77
|
+
|
|
78
|
+
# No editor found. Prompt the user to select one and try again.
|
|
79
|
+
_select_editor(cfg)
|
|
80
|
+
return _get_editor(cfg, is_directory)
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
class Support(Flag):
|
|
84
|
+
"""Types of files an editor supports"""
|
|
85
|
+
|
|
86
|
+
FILE = auto()
|
|
87
|
+
DIR = auto()
|
|
88
|
+
|
|
89
|
+
ALL = FILE | DIR
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
@dataclass
|
|
93
|
+
class Editor:
|
|
94
|
+
"""File editing tool"""
|
|
95
|
+
|
|
96
|
+
name: str
|
|
97
|
+
cmd: str
|
|
98
|
+
"Executable name or command line to execute this tool"
|
|
99
|
+
support: Support = Support.FILE
|
|
100
|
+
"Types of files this tool supports editing"
|
|
101
|
+
test: Callable[[], bool] = None
|
|
102
|
+
"""
|
|
103
|
+
Function that returns true if the tool is installed.
|
|
104
|
+
Defaults to `which {self.cmd}`.
|
|
105
|
+
"""
|
|
106
|
+
paths: list[str] = field(default_factory=list)
|
|
107
|
+
"Extra paths to search for the executable if `test` is not set"
|
|
108
|
+
|
|
109
|
+
def __rich__(self):
|
|
110
|
+
return self.name
|
|
111
|
+
|
|
112
|
+
def get_command(self):
|
|
113
|
+
"""Get the command to execute the tool, or None if it is not installed"""
|
|
114
|
+
if self.test and self.test():
|
|
115
|
+
return self.cmd
|
|
116
|
+
|
|
117
|
+
if shutil.which(self.cmd):
|
|
118
|
+
return self.cmd
|
|
119
|
+
|
|
120
|
+
for path in self.paths:
|
|
121
|
+
if cmd := shutil.which(self.cmd, path=path):
|
|
122
|
+
return shlex.quote(cmd)
|
|
123
|
+
|
|
124
|
+
return None
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def _mac():
|
|
128
|
+
return platform.system() == "Darwin"
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
_NPP_PATHS = ["C:\\Program Files\\Notepad++", "C:\\Program Files (x86)\\Notepad++"]
|
|
132
|
+
_SUBL_PATHS = [
|
|
133
|
+
"C:\\Program Files\\sublime text 3",
|
|
134
|
+
"C:\\Program Files (x86)\\sublime text 3",
|
|
135
|
+
]
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
_EDITORS: list[Editor] = [
|
|
139
|
+
# Graphical text editors
|
|
140
|
+
Editor("Visual Studio Code", cmd="code", support=Support.ALL),
|
|
141
|
+
Editor("Sublime Text", cmd="subl", support=Support.ALL, paths=_SUBL_PATHS),
|
|
142
|
+
Editor("Emacs", cmd="emacs", support=Support.ALL),
|
|
143
|
+
Editor("Gedit", cmd="gedit"),
|
|
144
|
+
Editor("Notepad", cmd="notepad"),
|
|
145
|
+
Editor("Notepad++", cmd="notepad++", paths=_NPP_PATHS),
|
|
146
|
+
Editor("TextEdit", cmd="open -a TextEdit", test=_mac),
|
|
147
|
+
Editor("TextMate", cmd="mate", support=Support.ALL),
|
|
148
|
+
# Terminal text editors
|
|
149
|
+
Editor("Nano", cmd="nano"),
|
|
150
|
+
Editor("Neovim", cmd="nvim", support=Support.ALL),
|
|
151
|
+
Editor("Vim", cmd="vim", support=Support.ALL),
|
|
152
|
+
# File explorers
|
|
153
|
+
Editor("File Explorer", cmd="explorer", support=Support.DIR),
|
|
154
|
+
Editor("Nautilus", cmd="nautilus", support=Support.DIR),
|
|
155
|
+
Editor("Finder", cmd="open", support=Support.DIR, test=_mac),
|
|
156
|
+
]
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
def _select_editor(cfg: Config):
|
|
160
|
+
available = [e for e in _EDITORS if e.get_command()]
|
|
161
|
+
file_editors = [e for e in available if e.support & Support.FILE]
|
|
162
|
+
dir_editors = [e for e in available if e.support & Support.DIR]
|
|
163
|
+
|
|
164
|
+
if not file_editors:
|
|
165
|
+
raise FatalError(
|
|
166
|
+
"Could not find a known text editor.\n"
|
|
167
|
+
"Run \"zmk config core.editor='<command>'\" "
|
|
168
|
+
'replacing "<command>" with the path to a text editor.'
|
|
169
|
+
)
|
|
170
|
+
|
|
171
|
+
editor = show_menu("Select a text editor:", file_editors, filter_func=_filter)
|
|
172
|
+
cfg.set(Settings.CORE_EDITOR, editor.get_command())
|
|
173
|
+
|
|
174
|
+
explorer = None
|
|
175
|
+
if editor.support & Support.DIR:
|
|
176
|
+
cfg.remove(Settings.CORE_EXPLORER)
|
|
177
|
+
elif dir_editors:
|
|
178
|
+
explorer = show_menu(
|
|
179
|
+
"This text editor only supports opening files.\n"
|
|
180
|
+
"Select another tool for opening directories:",
|
|
181
|
+
dir_editors,
|
|
182
|
+
filter_func=_filter,
|
|
183
|
+
)
|
|
184
|
+
cfg.set(Settings.CORE_EXPLORER, explorer.get_command())
|
|
185
|
+
|
|
186
|
+
cfg.write()
|
|
187
|
+
|
|
188
|
+
rich.print("Editor saved:")
|
|
189
|
+
rich.print(Markdown(f'`zmk config core.editor="{editor.get_command()}"`'))
|
|
190
|
+
if explorer:
|
|
191
|
+
rich.print(Markdown(f'`zmk config core.explorer="{explorer.get_command()}"`'))
|
|
192
|
+
|
|
193
|
+
rich.print()
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
def _filter(editor: Editor, text: str):
|
|
197
|
+
return text.casefold() in editor.name.casefold()
|
zmk/commands/config.py
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
"""
|
|
2
|
+
"zmk config" command.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from typing import Annotated, Optional
|
|
6
|
+
|
|
7
|
+
import typer
|
|
8
|
+
from rich.console import Console
|
|
9
|
+
|
|
10
|
+
from .. import styles
|
|
11
|
+
from ..config import Config
|
|
12
|
+
|
|
13
|
+
console = Console(
|
|
14
|
+
highlighter=styles.KeyValueHighlighter(), theme=styles.KEY_VALUE_THEME
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def _path_callback(ctx: typer.Context, value: bool):
|
|
19
|
+
if value:
|
|
20
|
+
cfg = ctx.find_object(Config)
|
|
21
|
+
print(cfg.path)
|
|
22
|
+
raise typer.Exit()
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def config(
|
|
26
|
+
ctx: typer.Context,
|
|
27
|
+
name: Annotated[
|
|
28
|
+
Optional[str],
|
|
29
|
+
typer.Argument(
|
|
30
|
+
help="Setting name. Prints all setting values if omitted.",
|
|
31
|
+
),
|
|
32
|
+
] = None,
|
|
33
|
+
value: Annotated[
|
|
34
|
+
Optional[str],
|
|
35
|
+
typer.Argument(help="New setting value. Prints the current value if omitted."),
|
|
36
|
+
] = None,
|
|
37
|
+
unset: Annotated[
|
|
38
|
+
bool,
|
|
39
|
+
typer.Option("--unset", "-u", help="Remove the setting with the given name."),
|
|
40
|
+
] = False,
|
|
41
|
+
_: Annotated[
|
|
42
|
+
Optional[bool],
|
|
43
|
+
typer.Option(
|
|
44
|
+
"--path",
|
|
45
|
+
"-p",
|
|
46
|
+
help="Print the path to the ZMK CLI configuration file and exit.",
|
|
47
|
+
is_eager=True,
|
|
48
|
+
callback=_path_callback,
|
|
49
|
+
),
|
|
50
|
+
] = False,
|
|
51
|
+
):
|
|
52
|
+
"""Get and set ZMK CLI settings."""
|
|
53
|
+
|
|
54
|
+
cfg = ctx.find_object(Config)
|
|
55
|
+
|
|
56
|
+
if name is None:
|
|
57
|
+
_list_settings(cfg)
|
|
58
|
+
elif unset:
|
|
59
|
+
_unset_setting(cfg, name)
|
|
60
|
+
elif value:
|
|
61
|
+
_set_setting(cfg, name, value)
|
|
62
|
+
else:
|
|
63
|
+
_get_setting(cfg, name)
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def _list_settings(cfg: Config):
|
|
67
|
+
for name, value in sorted(cfg.items()):
|
|
68
|
+
console.print(f"{name}={value}")
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def _unset_setting(cfg: Config, name: str):
|
|
72
|
+
cfg.remove(name)
|
|
73
|
+
cfg.write()
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def _set_setting(cfg: Config, name: str, value: str):
|
|
77
|
+
cfg.set(name, value)
|
|
78
|
+
cfg.write()
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def _get_setting(cfg: Config, name: str):
|
|
82
|
+
if value := cfg.get(name, fallback=None):
|
|
83
|
+
console.print(value)
|
zmk/commands/download.py
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
"""
|
|
2
|
+
"zmk download" command.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import typer
|
|
6
|
+
|
|
7
|
+
from ..config import Config
|
|
8
|
+
from ..repo import Repo
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def download(ctx: typer.Context):
|
|
12
|
+
"""Open the web page to download firmware from GitHub."""
|
|
13
|
+
|
|
14
|
+
cfg = ctx.find_object(Config)
|
|
15
|
+
repo = cfg.get_repo()
|
|
16
|
+
|
|
17
|
+
actions_url = _get_actions_url(repo)
|
|
18
|
+
|
|
19
|
+
typer.launch(actions_url)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def _get_actions_url(repo: Repo):
|
|
23
|
+
remote = repo.git("remote", capture_output=True).strip()
|
|
24
|
+
remote_url = repo.git("remote", "get-url", remote, capture_output=True).strip()
|
|
25
|
+
|
|
26
|
+
return f"{remote_url}/actions/workflows/build.yml"
|