|
2 | 2 |
|
3 | 3 | import json |
4 | 4 | import logging |
5 | | -from itertools import chain, islice |
| 5 | +from itertools import batched, chain |
6 | 6 | from pathlib import Path |
7 | 7 | from typing import Iterable, Sequence |
8 | 8 |
|
@@ -127,7 +127,6 @@ def _str_to_key( # pylint: disable=too-many-return-statements,too-many-locals,t |
127 | 127 | def recurse(new_binding): |
128 | 128 | return self._str_to_key(new_binding, current_layer, key_positions) |
129 | 129 |
|
130 | | - |
131 | 130 | if isinstance(binding, str): |
132 | 131 | if binding.startswith("@") and (mapped := self.aliases.get(binding.lstrip("@"), False)): |
133 | 132 | return recurse(mapped) |
@@ -177,34 +176,54 @@ def _get_layers(self, nodes: list[pp.ParseResults]) -> dict[str, list[LayoutKey] |
177 | 176 | assert self.defsrc_indices is not None |
178 | 177 | assert self.defsrc_to_pos is not None |
179 | 178 |
|
| 179 | + layer_names = [ |
| 180 | + node[1] if node[0] == "deflayer" else node[1][0] for node in nodes if node[0] in ("deflayer", "deflayermap") |
| 181 | + ] |
180 | 182 | layer_nodes = {node[1]: node[2:] for node in nodes if node[0] == "deflayer"} |
181 | | - self.update_layer_names(list(layer_nodes)) |
| 183 | + layermap_nodes = {node[1][0]: node[2:] for node in nodes if node[0] == "deflayermap"} |
182 | 184 |
|
183 | | - layers: dict[str, list[LayoutKey]] = {} |
184 | | - for layer_ind, (layer_name, layer) in enumerate(layer_nodes.items()): |
185 | | - layers[layer_name] = [LayoutKey() for _ in range(len(self.defsrc_to_pos))] |
186 | | - for key_pos, layer_key in zip(self.defsrc_indices, layer): |
| 185 | + self.update_layer_names(layer_names) |
| 186 | + |
| 187 | + def create_from_deflayer(ind: int, name: str, keys: list[pp.ParseResults]) -> list[LayoutKey]: |
| 188 | + assert self.defsrc_indices is not None |
| 189 | + assert self.defsrc_to_pos is not None |
| 190 | + layer = [LayoutKey() for _ in range(len(self.defsrc_to_pos))] |
| 191 | + for key_pos, key in zip(self.defsrc_indices, keys): |
187 | 192 | try: |
188 | | - layers[layer_name][key_pos] = self._str_to_key(layer_key, layer_ind, [key_pos]) |
| 193 | + layer[key_pos] = self._str_to_key(key, ind, [key_pos]) |
189 | 194 | except Exception as err: |
190 | 195 | raise ParseError( |
191 | | - f'Could not parse keycode "{layer_key}" in layer "{layer_name}" with exception "{err}"' |
| 196 | + f'Could not parse keycode "{key}" in layer "{name}" with exception "{err}"' |
192 | 197 | ) from err |
193 | | - return layers |
| 198 | + return layer |
194 | 199 |
|
195 | | - @staticmethod |
196 | | - def _get_raw_combo_nodes(nodes: list[pp.ParseResults]) -> list[tuple[(str | pp.ParseResults), ...]]: |
197 | | - try: |
198 | | - chords_node = next(node[1:] for node in nodes if node[0] in ("defchordsv2", "defchordsv2-experimental")) |
199 | | - except StopIteration: |
200 | | - return [] |
201 | | - |
202 | | - def batched(iterable, n): |
203 | | - it = iter(iterable) |
204 | | - while batch := tuple(islice(it, n)): |
205 | | - yield batch |
| 200 | + def create_from_deflayermap(ind: int, name: str, mappings: list[pp.ParseResults]) -> list[LayoutKey]: |
| 201 | + assert self.defsrc_to_pos is not None |
| 202 | + default_action = LayoutKey() |
| 203 | + layer: list[LayoutKey | None] = [None for _ in range(len(self.defsrc_to_pos))] |
| 204 | + for input_elt, action_elt in batched(mappings, 2): |
| 205 | + try: |
| 206 | + match input_elt: |
| 207 | + case "_" | "__" | "___": |
| 208 | + logger.warning('"_", "__" and "___" in deflayermap are not distinguished at the moment') |
| 209 | + default_action = self._str_to_key(action_elt, ind, []) |
| 210 | + case _: |
| 211 | + assert isinstance(input_elt, str) |
| 212 | + key_pos = self.defsrc_to_pos[self._canonicalize_defsrc(input_elt)] |
| 213 | + layer[key_pos] = self._str_to_key(action_elt, ind, [key_pos]) |
| 214 | + except Exception as err: |
| 215 | + raise ParseError( |
| 216 | + f'Could not parse action element "{action_elt}" in layermap "{name}" with exception "{err}"' |
| 217 | + ) from err |
| 218 | + return [default_action.copy() if key is None else key for key in layer] |
206 | 219 |
|
207 | | - return list(batched(chords_node, 5)) |
| 220 | + layers: dict[str, list[LayoutKey]] = {} |
| 221 | + for layer_ind, layer_name in enumerate(layer_names): |
| 222 | + if layer_keys := layer_nodes.get(layer_name): # deflayer node |
| 223 | + layers[layer_name] = create_from_deflayer(layer_ind, layer_name, layer_keys) |
| 224 | + else: # deflayermap node |
| 225 | + layers[layer_name] = create_from_deflayermap(layer_ind, layer_name, layermap_nodes[layer_name]) |
| 226 | + return layers |
208 | 227 |
|
209 | 228 | def _get_combos(self, raw_combo_nodes: list[tuple[(str | pp.ParseResults), ...]]) -> list[ComboSpec]: |
210 | 229 | assert self.layer_names is not None |
@@ -236,16 +255,22 @@ def _parse(self, in_str: str, file_name: str | None = None) -> tuple[dict, Keyma |
236 | 255 | """ |
237 | 256 | nodes = self._parse_cfg(in_str, Path(file_name) if file_name else None) |
238 | 257 |
|
239 | | - if any(node[1:] for node in nodes if node[0] == "deflayermap"): |
240 | | - logger.warning("deflayermap is not currently supported") |
241 | | - |
242 | | - if any(node[1:] for node in nodes if node[0] == "deflocalkeys"): |
| 258 | + if any(node for node in nodes if node[0] == "deflocalkeys"): |
243 | 259 | logger.warning("deflocalkeys is not currently supported") |
244 | 260 |
|
245 | 261 | defsrc = next(node[1:] for node in nodes if node[0] == "defsrc") |
246 | | - raw_combo_nodes = self._get_raw_combo_nodes(nodes) |
| 262 | + raw_combo_nodes = list( |
| 263 | + chain.from_iterable( |
| 264 | + batched(node[1:], 5) for node in nodes if node[0] in ("defchordsv2", "defchordsv2-experimental") |
| 265 | + ) |
| 266 | + ) |
| 267 | + deflayermap_srcs = chain.from_iterable(node[2::2] for node in nodes if node[0] == "deflayermap") |
247 | 268 |
|
248 | | - self._find_physical_layout(defsrc, set(pos for combo_def in raw_combo_nodes for pos in combo_def[0])) |
| 269 | + self._find_physical_layout( |
| 270 | + defsrc, |
| 271 | + {pos for combo_def in raw_combo_nodes for pos in combo_def[0]} |
| 272 | + | {pos for pos in deflayermap_srcs if pos not in ("_", "__", "___")}, |
| 273 | + ) |
249 | 274 | assert self.physical_layout is not None |
250 | 275 |
|
251 | 276 | self._get_aliases_vars(nodes) |
|
0 commit comments