Skip to content

Commit 53b37b4

Browse files
committed
feat: Add extra key mapping section
Using ASCII art to declare the layout is limited to the keys declared in the geometries. Added an extra section `mapping` to the TOML layout configuration file, that enable mappings key explicitly with a dictionary.
1 parent ca590d0 commit 53b37b4

File tree

2 files changed

+58
-1
lines changed

2 files changed

+58
-1
lines changed

kalamine/layout.py

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
import sys
33
from dataclasses import dataclass
44
from pathlib import Path
5-
from typing import Dict, List, Optional, Set, Type, TypeVar
5+
from typing import Dict, List, Optional, Set, Type, TypeVar, Union
66

77
import click
88
import tomli
@@ -199,8 +199,47 @@ def __init__(
199199
self.layers[Layer.ALTGR]["spce"] = spc["altgr"]
200200
self.layers[Layer.ALTGR_SHIFT]["spce"] = spc["altgr_shift"]
201201

202+
# Extra mapping
203+
if mapping := layout_data.get("mapping"):
204+
self._parse_extra_mapping(mapping)
205+
202206
self._parse_dead_keys(spc)
203207

208+
@staticmethod
209+
def _parse_key_ref(raw: str) -> Optional[str]:
210+
"""Parse a key reference (e.g. to clone)"""
211+
if raw.startswith("(") and raw.endswith(")"):
212+
if (clone := raw[1:-1]) and clone in KEYS:
213+
return clone
214+
return None
215+
216+
def _parse_extra_mapping(self, mapping: Dict[str, Union[str, Dict[str, str]]]):
217+
"""Parse a layout dict"""
218+
layer: Optional[Layer]
219+
for raw_key, levels in mapping.items():
220+
# TODO: parse key in various ways (XKB, Linux keycode)
221+
if raw_key not in KEYS:
222+
raise ValueError(f"Unknown key: “{raw_key}”")
223+
key = raw_key
224+
# Check for key clone
225+
if isinstance(levels, str):
226+
# Check for clone
227+
if clone := self._parse_key_ref(levels):
228+
for layer, keys in self.layers.items():
229+
if value := keys.get(clone):
230+
self.layers[layer][key] = value
231+
continue
232+
raise ValueError(f"Unsupported key mapping: {raw_key}: {levels}")
233+
for raw_layer, raw_value in levels.items():
234+
if (layer := Layer.parse(raw_layer)) is None:
235+
raise ValueError(f"Cannot parse layer: “{raw_layer}”")
236+
if clone := self._parse_key_ref(raw_value):
237+
if (value := self.layers[layer].get(clone)) is None:
238+
continue
239+
else:
240+
value = raw_value
241+
self.layers[layer][key] = value
242+
204243
def _parse_dead_keys(self, spc: Dict[str, str]) -> None:
205244
"""Build a deadkey dict."""
206245

kalamine/utils.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,24 @@ class Layer(IntEnum):
4747
ALTGR = 4
4848
ALTGR_SHIFT = 5
4949

50+
@classmethod
51+
def parse(cls, raw: str) -> Optional["Layer"]:
52+
rawʹ = raw.casefold()
53+
if rawʹ == "1dk":
54+
return cls(cls.ODK)
55+
elif rawʹ == "1dk_shift":
56+
return cls(cls.ODK_SHIFT)
57+
else:
58+
for layer in cls:
59+
if rawʹ == layer.name.casefold():
60+
return layer
61+
try:
62+
if int(raw, base=10) == layer.value:
63+
return layer
64+
except ValueError:
65+
pass
66+
return None
67+
5068
def next(self) -> "Layer":
5169
"""The next layer in the layer ordering."""
5270
return Layer(int(self) + 1)

0 commit comments

Comments
 (0)