Data Layer

Data structures and configuration models.

Configuration Models

Configuration models for Svalboard Keymap Image Maker tool.

This module defines the Pydantic configuration models used to customize the appearance and behavior of generated keymap images. The configuration is hierarchical, with the root SkimConfig containing nested models for keyboard settings, keycode mappings, and output styling.

Configuration can be loaded from YAML files or constructed programmatically. All models use Pydantic’s BaseModel for validation and serialization.

Example

Loading configuration from a YAML file:

import yaml
from skim.data.config import SkimConfig

with open("skim-config.yaml") as f:
    data = yaml.safe_load(f)
config = SkimConfig(**data)

Creating configuration programmatically:

from skim.data.config import SkimConfig, LayerColor, Palette

config = SkimConfig()
new_layers = config.output.style.palette.layers + (LayerColor(base_color="#FF0000"),)
new_palette = config.output.style.palette.model_copy(update={"layers": new_layers})
new_style = config.output.style.model_copy(update={"palette": new_palette})
new_output = config.output.model_copy(update={"style": new_style})
config = config.model_copy(update={"output": new_output})
skim.data.config.SplitSidePositionStr

Annotated type alias for SplitSidePosition that accepts string values and converts them to enum members.

class skim.data.config.KeyboardLayer(**data)[source]

Bases: BaseModel

Configuration for a single keyboard layer.

Defines the metadata associated with a layer in the keymap, including its internal name. This is used to customize how layers are named in generated images.

Parameters:
  • index (int)

  • id (str | None)

  • name (str)

  • variant (str | None)

id

Optional unique identifier for the layer. Used for internal reference when processing a keymap from c2json that uses C define-macros instead of layer numbers in with the layer switch functions. It may be None if not specified.

name

Full descriptive name of the layer (e.g., “Base Layer”, “Symbols”, “Navigation”). Used as the “image title” on the generated images.

variant

Optional secondary label shown below the layer name (e.g., “COLEMAK”). Used to display additional layer metadata in the overview image. Defaults to None if not specified.

Example

>>> layer = KeyboardLayer(name="Base Layer")
>>> layer.name
'Base Layer'
>>> layer.variant is None
True
model_config: ClassVar[ConfigDict] = {'frozen': True}

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

index: int
id: str | None
name: str
variant: str | None
class skim.data.config.KeyboardFeatures(**data)[source]

Bases: BaseModel

Configuration for optional keyboard hardware features.

Controls which optional hardware features are enabled when generating keymap images. These settings affect which keys are rendered.

Parameters:

double_south (bool)

double_south

Whether to render the MH (double-south) keys on finger clusters. When False, these positions are hidden. Defaults to False as not all Svalboard configurations have these keys.

Example

>>> features = KeyboardFeatures(double_south=True)
>>> features.double_south
True

Note

Currently the Svalboard keyboard only have a single feature modifier that can impact a keymap image, but this configuration object is here to future-proof this tool on eventual changes.

model_config: ClassVar[ConfigDict] = {'frozen': True}

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

double_south: bool
class skim.data.config.Keyboard(**data)[source]

Bases: BaseModel

Keyboard-specific configuration settings.

Contains settings that describe the physical keyboard configuration and layer definitions. This is used to customize the rendering based on the specific keyboard setup.

Parameters:
features

Hardware feature flags controlling the keymap rendering. Defaults to a KeyboardFeatures instance with all features disabled.

layers

Tuple of layer configurations defining the metadata for each layer in the keymap. The order corresponds to layer indices (0, 1, 2, etc.).

Example

>>> keyboard = Keyboard(
...     features=KeyboardFeatures(double_south=True),
...     layers=(
...         KeyboardLayer(name="Base"),
...         KeyboardLayer(name="Symbols"),
...     ),
... )
>>> len(keyboard.layers)
2
model_config: ClassVar[ConfigDict] = {'frozen': True}

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

features: KeyboardFeatures
layers: Annotated[tuple[KeyboardLayer, ...]]
model_post_init(context)[source]

Initialize the layer ID lookup map after model construction.

Builds an internal mapping from layer identifiers to their QMK firmware indices for efficient layer lookup. If a layer has an explicit id, that ID is used as the key. Otherwise, the string representation of the layer’s position index is used.

This method is called automatically by Pydantic after the model is constructed.

Parameters:

context (object) – Pydantic validation context (unused but required by the Pydantic post-init signature).

Return type:

None

Example

>>> keyboard = Keyboard(
...     layers=[
...         KeyboardLayer(index=0, id="base", name="Base"),
...         KeyboardLayer(index=15, name="Symbols"),
...     ]
... )
>>> keyboard.layer_index("base")
0
>>> keyboard.layer_index("1")  # Second layer has no id, uses QMK index
15
layer_index(key)[source]

Look up a layer’s QMK firmware index by its identifier.

Returns the QMK firmware index of the layer matching the given key. The key can be either a layer’s explicit id, the string representation of its position index (for layers without an id), or an integer index which is converted to string for lookup.

Parameters:

key (str | None) – The layer identifier to look up. This can be a layer’s id attribute, a string index like "0", "1", or an integer index like 0, 1. If None, returns None.

Return type:

int | None

Returns:

The QMK firmware index of the matching layer, or None if no layer matches the given key or if key is None.

Example

>>> keyboard = Keyboard(
...     layers=[
...         KeyboardLayer(index=0, id="nav", name="Navigation"),
...         KeyboardLayer(index=15, name="Symbols"),
...     ]
... )
>>> keyboard.layer_index("nav")
0
>>> keyboard.layer_index("1")
15
>>> keyboard.layer_index("unknown") is None
True
qmk_index_to_position(qmk_idx)[source]

Look up a layer’s position by its QMK firmware index.

Returns the zero-based position of the layer in the layers tuple that has the given QMK firmware index. This is useful when layer indices in the firmware are non-sequential (e.g., 0, 1, 2, 15).

Parameters:

qmk_idx (int) – The QMK firmware layer index to look up.

Return type:

int | None

Returns:

The position of the matching layer in the layers tuple, or None if no layer has the given QMK index.

Example

>>> keyboard = Keyboard(
...     layers=[
...         KeyboardLayer(index=0, name="Base"),
...         KeyboardLayer(index=15, name="Mouse"),
...     ]
... )
>>> keyboard.qmk_index_to_position(15)
1
>>> keyboard.qmk_index_to_position(5) is None
True
layer_qmk_index(position)[source]

Get the QMK firmware index for a layer at a given position.

Returns the QMK firmware layer index for the layer at the given position in the layers tuple.

Parameters:

position (int) – The zero-based position of the layer in the layers tuple.

Return type:

int

Returns:

The QMK firmware layer index.

Example

>>> keyboard = Keyboard(
...     layers=[
...         KeyboardLayer(index=0, name="Base"),
...         KeyboardLayer(index=15, name="Mouse"),
...     ]
... )
>>> keyboard.layer_qmk_index(1)
15
class skim.data.config.Keycode(**data)[source]

Bases: BaseModel

A keycode-to-label mapping override.

Defines a custom mapping from a QMK keycode string to a display label. This is used to customize how specific keycodes are rendered, either by preprocessing them before the standard mapping or by overriding the default label entirely.

Parameters:
keycode

The QMK keycode string to match (e.g., “KC_A”, “KC_ESC”). Must match exactly, including case.

target

The replacement label or transformed keycode. For pre-processing, this is typically another keycode. For overrides, this is the display label.

Example

>>> # Override KC_SPC to display as "Space"
>>> override = Keycode(keycode="KC_SPC", target="Space")
>>> override.keycode
'KC_SPC'
model_config: ClassVar[ConfigDict] = {'frozen': True}

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

keycode: str
target: str
class skim.data.config.Keycodes(**data)[source]

Bases: BaseModel

Configuration for keycode transformation and display.

Contains tuples of keycode mappings that customize how QMK keycodes are transformed and displayed. The pre-processing transformation happens without any processing by the tool. This mapping should not use the alias and other replacements features from key resolution. Then, the standard label mapping happens with the overrides defined in this configuration having higher priority than the default transformations.

Parameters:
  • pre_process (Annotated[tuple[Keycode, ...], BeforeValidator(func=~skim.data.config._coerce_to_tuple, json_schema_input_type=PydanticUndefined)])

  • overrides (Annotated[tuple[Keycode, ...], BeforeValidator(func=~skim.data.config._coerce_to_tuple, json_schema_input_type=PydanticUndefined)])

pre_process

Keycode transformations applied before standard mapping. Useful for normalizing keycodes or representing custom keycodes that act like othes QMK keycodes including functions. Defaults to an empty tuple.

overrides

Keycode-to-label mappings that override the standard mapping results. Applied after all other transformations. Defaults to an empty tuple.

Example

>>> keycodes = Keycodes(
...     pre_process=(Keycode(keycode="LCTL_T(KC_A)", target="MT(MOD_LCTL,KC_A)"),),
...     overrides=(Keycode(keycode="KC_SPC", target="Space"),),
... )
>>> len(keycodes.overrides)
1
model_config: ClassVar[ConfigDict] = {'frozen': True}

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

pre_process: Annotated[tuple[Keycode, ...]]
overrides: Annotated[tuple[Keycode, ...]]
class skim.data.config.Spacing(**data)[source]

Bases: BaseModel

Spacing configuration for layout margins and inset (padding and in-between elements spacing).

Controls the whitespace around and within the generated keymap images. Spacing is specified in SVG units (typically pixels at default scale).

Parameters:
margin

Outer margin around the entire keyboard layout. Space between the keyboard and the image edge. Defaults to 0.

inset

Inner padding within the keyboard border. Space between the border and the key clusters. Defaults to 20.

Example

>>> spacing = Spacing(margin=10, inset=25)
>>> spacing.margin
10
model_config: ClassVar[ConfigDict] = {'frozen': True}

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

margin: float
inset: float
class skim.data.config.Layout(**data)[source]

Bases: BaseModel

Layout dimensions and spacing configuration.

Controls the overall dimensions and spacing of generated keymap images. The height is calculated automatically based on the width to maintain the correct aspect ratio for the Svalboard layout.

Parameters:
width

Total width of the generated image in SVG units (typically pixels at default scale). Defaults to 1600.

spacing

Spacing configuration for margins and padding. Defaults to a Spacing instance with default values.

Example

>>> layout = Layout(width=1200, spacing=Spacing(margin=20))
>>> layout.width
1200
model_config: ClassVar[ConfigDict] = {'frozen': True}

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

width: float
spacing: Spacing
class skim.data.config.Border(**data)[source]

Bases: BaseModel

Border styling configuration.

Controls the appearance of borders drawn around the keyboard layout and optionally around individual key groups.

Parameters:
width

Line width of the border in SVG units. Defaults to 2.

radius

Corner radius for rounded borders in SVG units. Set to 0 for square corners. Defaults to 10.

Example

>>> border = Border(width=3, radius=15)
>>> border.radius
15.0
model_config: ClassVar[ConfigDict] = {'frozen': True}

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

width: float
radius: float
class skim.data.config.LayerColor(**data)[source]

Bases: BaseModel

Color configuration for a keyboard layer.

Defines the color scheme used for keys on a specific layer. Supports both single-color and gradient modes. In gradient mode, different keys within a cluster can have different shades for visual depth.

The gradient tuple contains 6 colors corresponding to the 6 positions in a cluster (e.g., center, north, east, south, west, double-south for finger clusters).

Parameters:
base_color

The primary color for this layer as a CSS color string (e.g., “#FF0000”, “red”, “rgb(255,0,0)”). Used when gradient is None or as a fallback.

color_index

Index into the gradient for the primary key color. Only used when gradient is set. Defaults to 2.

gradient

Optional tuple of 6 CSS color strings for position-based coloring within clusters. When None, the tool will generate the gradient tuple based on the base_color and the index position it should be in.

Example

>>> # Single color mode
>>> layer = LayerColor(base_color="#FF0000")
>>> layer[0]
'#FF0000'
>>> # Gradient mode
>>> layer = LayerColor(
...     base_color="#FF0000",
...     gradient=("#FF0000", "#CC0000", "#990000", "#660000", "#330000", "#000000"),
... )
>>> layer[1]
'#CC0000'
model_config: ClassVar[ConfigDict] = {'frozen': True}

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

base_color: str
color_index: int
gradient: tuple[str, str, str, str, str, str] | None
__getitem__(index)[source]

Get the color for a specific cluster position.

Parameters:

index (int) – Position index from 0-5 corresponding to cluster key positions.

Return type:

str

Returns:

The CSS color string for the specified position. Returns base_color if gradient is not set.

Raises:

IndexError – If index is outside the valid range (0-5).

Example

>>> layer = LayerColor(base_color="#FFF")
>>> layer[0]
'#FFF'
__str__()[source]

Return a string representation of the color configuration.

Return type:

str

Returns:

A JSON-like array string of the colors. For single-color mode, returns a single-element array. For gradient mode, returns all 6 colors.

Example

>>> str(LayerColor(base_color="#FFF"))
'["#FFF"]'
property dark_accent_color: str

Get the darker accent color for this layer.

Returns the second color in the gradient (index 1) if a gradient is defined, otherwise returns the base_color. This is typically used for key borders, shadows, or accent elements.

Returns:

A CSS color string for the accent color.

Example

>>> layer = LayerColor(
...     base_color="#FF0000",
...     gradient=("#FF0000", "#AA0000", "#880000", "#660000", "#440000", "#220000"),
... )
>>> layer.dark_accent_color
'#AA0000'
class skim.data.config.Palette(**data)[source]

Bases: BaseModel

Color palette configuration for the entire keyboard.

Defines the color scheme used throughout the generated keymap images, including background colors, text colors, and per-layer key colors.

Parameters:
  • overrides (dict[str, str])

  • neutral_color (str)

  • text_color (str)

  • key_label_color (str)

  • background_color (str)

  • border_color (str)

  • layers (Annotated[tuple[LayerColor, ...], BeforeValidator(func=~skim.data.config._coerce_to_tuple, json_schema_input_type=PydanticUndefined)])

overrides

Dictionary mapping color names to colors values. Used to change the color defined by W3C on the 147 supported named colors on SVG files. Defaults to an empty dictionary so the standard definitions should be used.

neutral_color

Color for keys that don’t have layer-specific coloring (e.g., some thumb cluster keys). Defaults to “#6F768B” (gray).

text_color

Default text color for non key labels. Defaults to “black”.

key_label_color

Text color for key labels. Defaults to “white” for contrast against typically dark key backgrounds.

background_color

Background color for the entire image. Defaults to “white”.

border_color

Color for keyboard and cluster borders. Defaults to “black”.

layers

Tuple of LayerColor configurations, one per layer. Layer indices correspond to positions in this tuple. Defaults to an empty tuple.

Example

>>> palette = Palette(
...     background_color="#F0F0F0",
...     layers=(
...         LayerColor(base_color="#3366CC"),
...         LayerColor(base_color="#CC6633"),
...     ),
... )
>>> palette.background_color
'#F0F0F0'
model_config: ClassVar[ConfigDict] = {'frozen': True}

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

overrides: dict[str, str]
neutral_color: str
text_color: str
key_label_color: str
background_color: str
border_color: str
layers: Annotated[tuple[LayerColor, ...]]
class skim.data.config.SplitSidePosition(value)[source]

Bases: str, Enum

Position options for hold-tap key symbol placement.

Controls where the “hold” portion of a hold-tap key is displayed relative to the “tap” portion. This affects keys like LT(1, KC_A) where tapping produces “A” but holding activates layer 1.

QMK_DEFINED

Use the position defined in QMK firmware settings. This respects the argument order of the macro-functions defined by QMK, which is always the hold part as the first argument, and the tap part as the second.

INWARD

Place the hold symbol toward the center of the keyboard cluster (right on left side, left on right side).

OUTWARD

Place the hold symbol toward the outside of the keyboard cluster (left on left side, right on right side). This is the default.

QMK_DEFINED = 'qmk'
INWARD = 'inward'
OUTWARD = 'outward'
__format__(format_spec)

Returns format using actual value type unless __str__ has been overridden.

skim.data.config.SplitSidePositionStr

Annotated type for SplitSidePosition that accepts string inputs.

This type alias allows configuration files to specify hold symbol positions as plain strings (e.g., “inward”, “outward”) which are automatically converted to SplitSidePosition enum members during validation.

Example

In a YAML configuration file:

style:
  hold_symbol_position: outward

When parsed, “outward” is converted to SplitSidePosition.OUTWARD.

alias of Annotated[SplitSidePosition, BeforeValidator(func=~skim.data.config., json_schema_input_type=PydanticUndefined)]

class skim.data.config.Style(**data)[source]

Bases: BaseModel

Visual styling configuration for keymap images.

Controls the overall visual appearance of generated images, including colors, borders, and key labeling options.

Parameters:
  • use_layer_colors_on_keys (bool)

  • hold_symbol_position (Annotated[SplitSidePosition, BeforeValidator(func=~skim.data.config.<lambda>, json_schema_input_type=PydanticUndefined)])

  • border (Border | None)

  • palette (Palette)

  • use_system_fonts (bool)

  • show_layer_indicators (bool)

  • show_layer_connectors (bool)

  • show_transparent_fallthrough (bool)

use_layer_colors_on_keys

Whether to color keys backgrounds based on the layer it activates. When True, keys use colors from the palette’s layer list. When False, all keys use their standard colors. Defaults to True.

hold_symbol_position

Where to place the “hold” portion of hold-tap keys relative to the “tap” portion. See SplitSidePosition for options. Defaults to OUTWARD.

border

Border styling configuration, or None to disable borders. Defaults to a Border instance with default values.

palette

Color palette configuration for the entire keyboard. Defaults to a Palette instance with default values.

show_transparent_fallthrough

When True (default), transparent keycodes (KC_TRNS / _______) on layers above 0 render the same label as layer 0 in a faded “ghost” color. Set False to leave transparent keys blank.

Example

>>> style = Style(
...     use_layer_colors_on_keys=True,
...     hold_symbol_position=SplitSidePosition.INWARD,
... )
>>> style.hold_symbol_position
<SplitSidePosition.INWARD: 'inward'>
model_config: ClassVar[ConfigDict] = {'frozen': True}

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

use_layer_colors_on_keys: bool
hold_symbol_position: Annotated[SplitSidePosition]
border: Border | None
palette: Palette
use_system_fonts: bool
show_layer_indicators: bool
show_layer_connectors: bool
show_transparent_fallthrough: bool
class skim.data.config.Output(**data)[source]

Bases: BaseModel

Output configuration for generated images.

Groups together layout dimensions and visual styling settings that control the final appearance of generated keymap images.

Parameters:
layout

Layout dimensions and spacing configuration. Defaults to a Layout instance with default values.

style

Visual styling configuration. Defaults to a Style instance with default values.

keymap_title

Optional title for the overview keymap image. When set, overrides the auto-generated title. Defaults to None.

copyright

Optional copyright notice displayed in the overview image. Defaults to None.

Example

>>> output = Output(
...     layout=Layout(width=1000),
...     style=Style(use_layer_colors_on_keys=False),
... )
>>> output.layout.width
1000
model_config: ClassVar[ConfigDict] = {'frozen': True}

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

layout: Layout
style: Style
keymap_title: str | None
copyright: str | None
class skim.data.config.SkimConfig(**data)[source]

Bases: BaseModel

Root configuration model for skim keymap image generation.

This is the top-level configuration class that contains all settings for generating Svalboard keymap images. It can be loaded from YAML files or constructed programmatically.

The configuration is organized into three main sections: - keyboard: Hardware and layer settings - keycodes: Keycode transformation and display rules - output: Layout dimensions and visual styling

Parameters:
keyboard

Keyboard-specific configuration including hardware features and layer definitions. Defaults to a Keyboard instance with default values.

keycodes

Keycode transformation rules including pre-processing and overrides. Defaults to a Keycodes instance with empty rule tuples.

output

Output configuration including layout dimensions and visual styling. Defaults to an Output instance with default values.

Example

Creating a basic configuration:

config = SkimConfig()
new_layout = config.output.layout.model_copy(update={"width": 1200})
new_output = config.output.model_copy(update={"layout": new_layout})
config = config.model_copy(update={"output": new_output})

Loading from a dictionary (e.g., parsed YAML):

data = {
    "keyboard": {
        "features": {"double_south": True},
        "layers": [{"label": "1", "name": "Base"}],
    },
    "output": {"layout": {"width": 1000}},
}
config = SkimConfig(**data)
model_config: ClassVar[ConfigDict] = {'frozen': True}

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

keyboard: Keyboard
keycodes: Keycodes
output: Output

Keyboard Structures

Keyboard cluster data structures for Svalboard key layouts.

This module provides generic container classes for representing key clusters on the Svalboard keyboard. The Svalboard has a unique 3D layout with two types of clusters:

  • Finger clusters: 5-directional keys (center, north, east, south, west) plus an optional double-south key. Each hand has 4 finger clusters (pinky, ring, middle, index).

  • Thumb clusters: 6 keys (down, pad, up, nail, knuckle, double-down). Each hand has 1 thumb cluster.

Both cluster types are generic containers that can hold any type of value, making them suitable for storing keycodes, labels, colors, or any other per-key data.

Example

Creating clusters with a default value and overrides:

from skim.data.keyboard import FingerCluster, ThumbCluster

# All keys set to empty string, except south_key
finger = FingerCluster("", south_key="A")

# Clusters are iterable and unpackable
center, north, east, south, west, dsouth = finger

Zipping multiple clusters together:

from skim.data.keyboard import FingerCluster, zip_clusters

keycodes = FingerCluster("KC_NO", center_key="KC_A")
labels = FingerCluster("", center_key="A")

# Create a cluster where each position contains both values
combined = zip_clusters(FingerCluster, "KeyData", codes=keycodes, labels=labels)
# combined.center_key.codes == "KC_A"
# combined.center_key.labels == "A"
class skim.data.keyboard.ClusterT

TypeVar bound to cluster types for writing generic cluster functions.

This TypeVar is constrained to FingerCluster or ThumbCluster (or any subclass of _ClusterBase), enabling type-safe generic functions that work with any cluster type.

Example

Writing a generic function that works with any cluster:

from skim.data.keyboard import ClusterT, FingerCluster, ThumbCluster

def count_non_empty(cluster: ClusterT) -> int:
    return sum(1 for value in cluster if value)

finger = FingerCluster("", center_key="A")
thumb = ThumbCluster("X")

count_non_empty(finger)  # Works with FingerCluster
count_non_empty(thumb)   # Works with ThumbCluster

alias of TypeVar(‘ClusterT’, bound=_ClusterBase[Any])

class skim.data.keyboard.FingerCluster(*, center_key: T, north_key: T, east_key: T, south_key: T, west_key: T, double_south_key: T)[source]
class skim.data.keyboard.FingerCluster(default: T, *, center_key: T = ..., north_key: T = ..., east_key: T = ..., south_key: T = ..., west_key: T = ..., double_south_key: T = ...)

Bases: _ClusterBase[T]

A container for values associated with a Svalboard finger cluster.

Each finger on the Svalboard has a 5-directional key cluster arranged in a cross pattern, plus an optional double-south key for chording. The physical layout corresponds to pushing the finger in different directions:

  • center_key: The home/rest position (pressing straight down)

  • north_key: Pushing the finger forward (away from palm)

  • east_key: Pushing the finger toward the thumb

  • south_key: Pulling the finger backward (toward palm)

  • west_key: Pushing the finger away from the thumb

  • double_south_key: A secondary south key on certain Svalboard boards

This class is generic and can store any type of value at each position, making it suitable for keycodes, labels, styling information, or any other per-key data.

The class supports two initialization modes: 1. Explicit: All six fields must be provided as keyword arguments 2. Default with overrides: A default value fills all positions, with

optional keyword arguments to override specific positions

Parameters:
  • default (T | _Unset)

  • kwargs (Any)

center_key

Value for the center (home) position.

north_key

Value for the north (forward) position.

east_key

Value for the east (thumb-ward) position.

south_key

Value for the south (backward) position.

west_key

Value for the west (away from thumb) position.

double_south_key

Value for the double-south position.

Example

Creating with all explicit values:

cluster = FingerCluster(
    center_key="A",
    north_key="B",
    east_key="C",
    south_key="D",
    west_key="E",
    double_south_key="F",
)

Creating with a default and overrides:

cluster = FingerCluster("", south_key="Space")
# center="", north="", east="", south="Space", west="", dsouth=""

Unpacking values:

center, north, east, south, west, dsouth = cluster
center_key: TypeVar(T)
north_key: TypeVar(T)
east_key: TypeVar(T)
south_key: TypeVar(T)
west_key: TypeVar(T)
double_south_key: TypeVar(T)
__init__(default=<UNSET>, **kwargs)[source]

Initialize the finger cluster.

Parameters:
  • default (Union[TypeVar(T), _Unset]) – Optional default value for all positions. If provided, any position not explicitly set via kwargs will use this value. If not provided, all positions must be set via kwargs.

  • **kwargs (Any) – Key position values. Valid keys are: center_key, north_key, east_key, south_key, west_key, double_south_key.

Raises:

TypeError – If default is not provided and any required key position is missing from kwargs.

Return type:

None

classmethod from_sequence(values)[source]

Create a FingerCluster from a sequence of 6 values.

See _ClusterBase.from_sequence() for full documentation.

Return type:

FingerCluster[TypeVar(T)]

Parameters:

values (Sequence[T])

map(fn)[source]

Create a new FingerCluster by applying a function to each value.

See _ClusterBase.map() for full documentation.

Return type:

FingerCluster[TypeVar(U)]

Parameters:

fn (Callable[[T], U])

class skim.data.keyboard.ThumbCluster(*, down_key: T, pad_key: T, up_key: T, nail_key: T, knuckle_key: T, double_down_key: T)[source]
class skim.data.keyboard.ThumbCluster(default: T, *, down_key: T = ..., pad_key: T = ..., up_key: T = ..., nail_key: T = ..., knuckle_key: T = ..., double_down_key: T = ...)

Bases: _ClusterBase[T]

A container for values associated with a Svalboard thumb cluster.

Each thumb on the Svalboard has a cluster of keys operated by different parts of the thumb and in different directions. The physical layout corresponds to:

  • down_key: Pressing the thumb straight down

  • pad_key: Using the thumb pad (fleshy part)

  • up_key: Pressing upward with the thumb

  • nail_key: Using the thumbnail area

  • knuckle_key: Using the thumb knuckle

  • double_down_key: A secondary down position activated by exercing

    extra force to the down key

This class is generic and can store any type of value at each position, making it suitable for keycodes, labels, styling information, or any other per-key data.

The class supports two initialization modes: 1. Explicit: All six fields must be provided as keyword arguments 2. Default with overrides: A default value fills all positions, with

optional keyword arguments to override specific positions

Parameters:
  • default (T | _Unset)

  • kwargs (Any)

down_key

Value for the down (pressing) position.

pad_key

Value for the thumb pad position.

up_key

Value for the up position.

nail_key

Value for the thumbnail position.

knuckle_key

Value for the thumb knuckle position.

double_down_key

Value for the double-down position.

Example

Creating with all explicit values:

cluster = ThumbCluster(
    down_key="Space",
    pad_key="Enter",
    up_key="Tab",
    nail_key="Esc",
    knuckle_key="Ctrl",
    double_down_key="",
)

Creating with a default and overrides:

cluster = ThumbCluster("", down_key="Space")
# down="Space", pad="", up="", nail="", knuckle="", ddown=""

Unpacking values:

down, pad, up, nail, knuckle, ddown = cluster
down_key: TypeVar(T)
pad_key: TypeVar(T)
up_key: TypeVar(T)
nail_key: TypeVar(T)
knuckle_key: TypeVar(T)
double_down_key: TypeVar(T)
__init__(default=<UNSET>, **kwargs)[source]

Initialize the thumb cluster.

Parameters:
  • default (Union[TypeVar(T), _Unset]) – Optional default value for all positions. If provided, any position not explicitly set via kwargs will use this value. If not provided, all positions must be set via kwargs.

  • **kwargs (Any) – Key position values. Valid keys are: down_key, pad_key, up_key, nail_key, knuckle_key, double_down_key.

Raises:

TypeError – If default is not provided and any required key position is missing from kwargs.

Return type:

None

classmethod from_sequence(values)[source]

Create a ThumbCluster from a sequence of 6 values.

See _ClusterBase.from_sequence() for full documentation.

Return type:

ThumbCluster[TypeVar(T)]

Parameters:

values (Sequence[T])

map(fn)[source]

Create a new ThumbCluster by applying a function to each value.

See _ClusterBase.map() for full documentation.

Return type:

ThumbCluster[TypeVar(U)]

Parameters:

fn (Callable[[T], U])

class skim.data.keyboard.SplitSide(index, middle, ring, pinky, thumb)[source]

Bases: Generic[T]

A container representing one side (left or right) of a Svalboard keyboard.

Each side of the Svalboard consists of four finger clusters (index, middle, ring, pinky) and one thumb cluster. This class provides a generic container that can hold any type of per-key data for an entire side of the keyboard.

The class is generic over type T, which represents the type of value stored at each key position, allowing it to be used for keycodes, labels, colors, or any other per-key data.

Parameters:
index

The index finger cluster (6 keys).

middle

The middle finger cluster (6 keys).

ring

The ring finger cluster (6 keys).

pinky

The pinky finger cluster (6 keys).

thumb

The thumb cluster (6 keys).

Example

Creating a side with string values:

from skim.data.keyboard import SplitSide, FingerCluster, ThumbCluster

side = SplitSide(
    index=FingerCluster(""),
    middle=FingerCluster(""),
    ring=FingerCluster(""),
    pinky=FingerCluster(""),
    thumb=ThumbCluster(""),
)

Accessing keys by linear index:

key = side[0]  # First key of index finger (center)
key = side[24]  # First key of thumb (down)

Iterating over all clusters:

for cluster in side:
    print(cluster)
index: FingerCluster[TypeVar(T)]
middle: FingerCluster[TypeVar(T)]
ring: FingerCluster[TypeVar(T)]
pinky: FingerCluster[TypeVar(T)]
thumb: ThumbCluster[TypeVar(T)]
__iter__()[source]

Iterate over all clusters in the side.

Yields clusters in order: index, middle, ring, pinky, thumb.

Yields:

Each cluster on this side of the keyboard, starting with finger clusters and ending with the thumb cluster.

Return type:

Iterator[Union[FingerCluster[TypeVar(T)], ThumbCluster[TypeVar(T)]]]

__getitem__(idx)[source]

Access a key value by linear index.

Provides flat indexing across all 30 keys on this side. Keys 0-23 are the finger clusters (6 keys each × 4 fingers), and keys 24-29 are the thumb cluster.

Parameters:

idx (int) – Linear index from 0-29. Indices 0-5 are index finger, 6-11 are middle finger, 12-17 are ring finger, 18-23 are pinky finger, and 24-29 are thumb.

Return type:

TypeVar(T)

Returns:

The value stored at the specified key position.

Raises:

IndexError – If idx is outside the valid range (0-29).

Example

Accessing individual keys:

side[0]  # Index finger center key
side[6]  # Middle finger center key
side[24]  # Thumb down key
property fingers: tuple[FingerCluster[T], ...]

Return all finger clusters as a tuple.

Returns:

(index, middle, ring, pinky).

Return type:

A tuple of the four finger clusters in order

__init__(index, middle, ring, pinky, thumb)
Parameters:
Return type:

None

class skim.data.keyboard.SvalboardLayout(left, right)[source]

Bases: Generic[T]

A container representing the complete Svalboard keyboard layout.

The Svalboard is a split ergonomic keyboard with a unique 3D key arrangement. This class provides a container for storing per-key data across the entire keyboard, with support for both hierarchical access (side → cluster → key) and flat linear indexing.

The keyboard has 60 total keys: - Left side: 24 finger keys (4 clusters × 6 keys) + 6 thumb keys = 30 keys - Right side: 24 finger keys (4 clusters × 6 keys) + 6 thumb keys = 30 keys

The class is generic over type T, which represents the type of value stored at each key position.

Parameters:
left

The left side of the keyboard (30 keys).

right

The right side of the keyboard (30 keys).

Example

Creating a layout with string values:

from skim.data.keyboard import (
    SvalboardLayout,
    SplitSide,
    FingerCluster,
    ThumbCluster,
)


def make_side():
    return SplitSide(
        index=FingerCluster(""),
        middle=FingerCluster(""),
        ring=FingerCluster(""),
        pinky=FingerCluster(""),
        thumb=ThumbCluster(""),
    )


layout = SvalboardLayout(left=make_side(), right=make_side())

Accessing keys by linear index:

key = layout[0]  # Right index finger center
key = layout[24]  # Left index finger center
key = layout[48]  # Right thumb down
key = layout[54]  # Left thumb down

Iterating over all keys:

for key in layout:
    print(key)  # Prints all 60 keys
__init__(left, right)
Parameters:
Return type:

None

left: SplitSide[TypeVar(T)]
right: SplitSide[TypeVar(T)]
__iter__()[source]

Iterate over all key values in the layout.

Yields keys in the standard Svalboard order: 1. Right hand finger clusters (index → pinky, 24 keys) 2. Left hand finger clusters (index → pinky, 24 keys) 3. Right thumb cluster (6 keys) 4. Left thumb cluster (6 keys)

This order matches the typical QMK keymap array layout for the Svalboard.

Yields:

Each key value in the layout, totaling 60 values.

Return type:

Iterator[TypeVar(T)]

__getitem__(idx)[source]

Access a key value by linear index.

Provides flat indexing across all 60 keys in the layout using the standard Svalboard key ordering: - 0-23: Right hand finger keys - 24-47: Left hand finger keys - 48-53: Right thumb keys - 54-59: Left thumb keys

Parameters:

idx (int) – Linear index from 0-59.

Return type:

TypeVar(T)

Returns:

The value stored at the specified key position.

Raises:

IndexError – If idx is outside the valid range (0-59).

Example

Accessing individual keys:

layout[0]  # Right index finger center
layout[24]  # Left index finger center
layout[48]  # Right thumb down
layout[54]  # Left thumb down
classmethod from_sequence(values)[source]

Create a SvalboardLayout from a flat sequence of 60 values.

Constructs a complete keyboard layout from a linear sequence of values, mapping each index to its corresponding key position using the standard Svalboard ordering (same as __getitem__ and __iter__).

The mapping is: - 0-23: Right hand finger keys (index, middle, ring, pinky clusters) - 24-47: Left hand finger keys (index, middle, ring, pinky clusters) - 48-53: Right thumb keys - 54-59: Left thumb keys

Within each finger cluster, the 6 keys are ordered: center, north, east, south, west, double_south.

Within each thumb cluster, the 6 keys are ordered: down, pad, up, nail, knuckle, double_down.

Parameters:

values (Sequence[TypeVar(T)]) – A sequence of exactly 60 values to populate the layout. Can be a list, tuple, or any object supporting len() and index access.

Return type:

SvalboardLayout[TypeVar(T)]

Returns:

A new SvalboardLayout with values distributed across all key positions.

Raises:

ValueError – If the sequence does not contain exactly 60 values.

Example

Creating a layout from a list:

keys = ["KC_A"] * 60  # 60 identical values
layout = SvalboardLayout.from_sequence(keys)

# Or with distinct values
keys = [f"KEY_{i}" for i in range(60)]
layout = SvalboardLayout.from_sequence(keys)
layout[0]  # "KEY_0" (right index center)
layout[59]  # "KEY_59" (left thumb double_down)
classmethod from_zipped(*, bundle='KeyValues', **layouts)[source]

Create a new layout by zipping values from multiple source layouts.

This class method combines multiple SvalboardLayout instances into a single layout where each key position contains a bundle of values from all source layouts. The bundle can be either a dynamically created frozen dataclass or a user-provided dataclass type.

This is useful when you need to associate multiple pieces of data with each key position, such as pairing keycodes with their display labels, or combining styling information from multiple sources.

Parameters:
  • bundle (str | type) – Either a string name for a dynamically created bundle dataclass, or an existing dataclass type to use. Defaults to “KeyValues”.

  • **layouts (SvalboardLayout[Any]) – Keyword arguments mapping names to source layouts. Each name becomes an attribute on the bundle objects.

Return type:

SvalboardLayout[Any]

Returns:

A new SvalboardLayout where each key position contains a frozen dataclass instance with attributes from each source layout.

Raises:
  • ValueError – If no layouts are provided.

  • TypeError – If a provided bundle class is missing required attributes.

Example

Using a dynamic bundle class:

codes = SvalboardLayout.from_sequence(["KC_A"] * 60)
labels = SvalboardLayout.from_sequence(["A"] * 60)

combined = SvalboardLayout.from_zipped(
    bundle="KeyData",
    code=codes,
    label=labels,
)

combined[0].code  # "KC_A"
combined[0].label  # "A"

Using a custom dataclass:

@dataclass(frozen=True)
class KeyData:
    code: str
    label: str


combined = SvalboardLayout.from_zipped(bundle=KeyData, code=codes, label=labels)
map(fn)[source]

Create a new layout by applying a function to each key value.

This method transforms each of the 60 key values in the layout using the provided function, returning a new SvalboardLayout with the transformed values.

Parameters:

fn (Callable[[TypeVar(T)], TypeVar(U)]) – A callable that takes a value of type T and returns a value of type U. Applied to each key position in the layout.

Return type:

SvalboardLayout[TypeVar(U)]

Returns:

A new SvalboardLayout with transformed values.

Example

Transforming layout values:

codes = SvalboardLayout.from_sequence(["KC_A"] * 60)
labels = codes.map(keycode_to_label)

# Chain multiple transformations
result = layout.map(fn1).map(fn2)

# Use with lambdas
upper = labels.map(str.upper)
class skim.data.keyboard.SvalboardKeymap(layers)[source]

Bases: Generic[T]

A complete Svalboard keymap containing multiple layers.

A keymap represents the full key configuration for a Svalboard keyboard, organized into multiple layers. Each layer is a complete SvalboardLayout containing all 60 keys. Users typically switch between layers to access different key bindings (e.g., base layer, symbols layer, navigation layer).

The class is generic over type T, which represents the type of value stored at each key position, allowing it to be used for keycodes, labels, colors, or any other per-key data.

Parameters:

layers (dict[int, SvalboardLayout[T]])

layers

A dict mapping layer indices to SvalboardLayout objects. Keys are QMK layer indices (which may be non-sequential, e.g. 0, 1, 2, 15). Layer 0 is typically the base/default layer.

Example

Creating a keymap with multiple layers:

from skim.data.keyboard import SvalboardKeymap, SvalboardLayout

# Create layouts for each layer
base_layer = SvalboardLayout.from_sequence(["KC_A"] * 60)
symbol_layer = SvalboardLayout.from_sequence(["KC_1"] * 60)
nav_layer = SvalboardLayout.from_sequence(["KC_LEFT"] * 60)

keymap = SvalboardKeymap(layers={0: base_layer, 1: symbol_layer, 2: nav_layer})

Accessing layers:

keymap.layers[0]  # Base layer
keymap.layers[1][0]  # First key of symbol layer
len(keymap.layers)  # Number of layers
__init__(layers)
Parameters:

layers (dict[int, SvalboardLayout[T]])

Return type:

None

layers: dict[int, SvalboardLayout[TypeVar(T)]]
skim.data.keyboard.zip_clusters(cluster_type, bundle='KeyValues', /, **clusters)[source]

Zip multiple clusters into a single cluster of bundled values.

This function combines multiple clusters of the same category (all FingerCluster or all ThumbCluster) into a new cluster where each position contains a bundle object holding values from all source clusters.

This is useful when you need to associate multiple pieces of data with each key position, such as pairing keycodes with their display labels, or combining styling information from multiple sources.

Parameters:
  • cluster_type (type[TypeVar(ClusterT, bound= _ClusterBase[Any])]) – The type of cluster to create (FingerCluster or ThumbCluster). This also determines the expected field structure for all source clusters.

  • bundle (str | type) – Either a string name for a dynamically created bundle dataclass, or an existing dataclass type to use. Defaults to “KeyValues”.

  • **clusters (_ClusterBase[Any]) – Keyword arguments mapping names to source clusters. Each name becomes an attribute on the bundle objects. All clusters must have the same field structure as cluster_type.

Return type:

TypeVar(ClusterT, bound= _ClusterBase[Any])

Returns:

A new cluster of the specified type where each field contains a frozen dataclass instance with attributes from each source cluster.

Raises:
  • ValueError – If no clusters are provided.

  • TypeError – If any source cluster has fields that don’t match the target cluster type, or if a provided bundle class is missing required attributes.

Example

Using a dynamic bundle class:

from skim.data.keyboard import FingerCluster, zip_clusters

keycodes = FingerCluster("KC_NO", center_key="KC_A", south_key="KC_SPC")
labels = FingerCluster("", center_key="A", south_key="Space")
colors = FingerCluster("#888", center_key="#F00", south_key="#0F0")

combined = zip_clusters(
    FingerCluster, "KeyData", code=keycodes, label=labels, color=colors
)

# Access bundled values
combined.center_key.code  # "KC_A"
combined.center_key.label  # "A"
combined.center_key.color  # "#F00"

Using a custom dataclass:

@dataclass(frozen=True)
class KeyData:
    code: str
    label: str
    color: str


combined = zip_clusters(
    FingerCluster, KeyData, code=keycodes, label=labels, color=colors
)
skim.data.keyboard.zip_layouts(bundle='KeyValues', /, **layouts)[source]

Zip multiple layouts into a single layout of bundled values.

This function combines multiple SvalboardLayout instances into a new layout where each key position contains a bundle object holding values from all source layouts.

This is useful when you need to associate multiple pieces of data with each key position, such as pairing keycodes with their display labels, or combining styling information from multiple sources.

Parameters:
  • bundle (str | type) – Either a string name for a dynamically created bundle dataclass, or an existing dataclass type to use. Defaults to “KeyValues”.

  • **layouts (SvalboardLayout[Any]) – Keyword arguments mapping names to source layouts. Each name becomes an attribute on the bundle objects.

Return type:

SvalboardLayout[Any]

Returns:

A new SvalboardLayout where each key position contains a frozen dataclass instance with attributes from each source layout.

Raises:
  • ValueError – If no layouts are provided.

  • TypeError – If a provided bundle class is missing required attributes.

Example

Using a dynamic bundle class:

from skim.data.keyboard import SvalboardLayout, zip_layouts

codes = SvalboardLayout.from_sequence(["KC_A"] * 60)
labels = SvalboardLayout.from_sequence(["A"] * 60)
colors = SvalboardLayout.from_sequence(["#F00"] * 60)

combined = zip_layouts("KeyData", code=codes, label=labels, color=colors)

# Access bundled values
combined[0].code  # "KC_A"
combined[0].label  # "A"
combined[0].color  # "#F00"

Using a custom dataclass:

@dataclass(frozen=True)
class KeyData:
    code: str
    label: str
    color: str


combined = zip_layouts(KeyData, code=codes, label=labels, color=colors)

CLI Data Transfer Objects

Data transfer objects for CLI argument handling.

This module defines frozen dataclasses used to pass parsed CLI arguments between the command-line interface and the application layer. These DTOs provide type-safe, immutable containers for input/output file specifications and layer selection options.

Example

>>> from skim.data import InputFiles, OutputFiles, KeymapGeneratorTargets
>>> from pathlib import Path
>>> inputs = InputFiles(config=Path("config.yaml"), keymap=Path("keymap.kbi"))
>>> outputs = OutputFiles(output_dir=Path("./images"), output_format="png")
>>> targets = KeymapGeneratorTargets.from_args(("1", "3-5", "overview"))
class skim.data.cli.RenderEngine(value)[source]

Bases: Enum

Available render engines for non-vector image generation.

CHROMIUM

Use Playwright with Chromium browser for rendering.

CAIRO

Use Cairo graphics library for rendering.

CHROMIUM = 'chromium'
CAIRO = 'cairo'
class skim.data.cli.OutputFiles(output_dir=<factory>, output_format='svg', force_overwrite=False, use_system_fonts=False, render_engine=None)[source]

Bases: object

Configuration for output file generation.

Specifies where and how to write generated keymap images. This is a frozen dataclass, meaning instances are immutable after creation.

Parameters:
output_dir

Directory path where generated images will be written. The directory will be created if it doesn’t exist. Defaults to the current working directory.

output_format

Image format for output files. Supported values are “svg”, “png”, “jpeg”, “webp”, and “avif”. Defaults to “svg”.

force_overwrite

Whether to overwrite existing files without prompting for confirmation. Defaults to False.

use_system_fonts

Whether to use system fonts instead of embedding fonts in SVG. Defaults to False.

render_engine

Which render engine to use for non-vector formats. Options are CHROMIUM (Playwright) or CAIRO. If None, uses the first available engine. Defaults to None.

Example

>>> output = OutputFiles(
...     output_dir=Path("./images"),
...     output_format="png",
...     force_overwrite=True,
... )
>>> output.output_format
'png'
output_dir: Path
output_format: str
force_overwrite: bool
use_system_fonts: bool
render_engine: RenderEngine | None
__init__(output_dir=<factory>, output_format='svg', force_overwrite=False, use_system_fonts=False, render_engine=None)
Parameters:
Return type:

None

class skim.data.cli.InputFiles(config=None, keymap=None, force_stdin_keymap=False)[source]

Bases: object

Configuration for input file sources.

Specifies the source files for keymap data and optional configuration. This is a frozen dataclass, meaning instances are immutable after creation.

Parameters:
  • config (Path | None)

  • keymap (Path | None)

  • force_stdin_keymap (bool)

config

Optional path to a YAML configuration file. When provided, settings from this file override the default configuration. Defaults to None (use default configuration).

keymap

Optional path to the keymap file (.kbi, .vil, or .json). When None, keymap data is read from stdin. Defaults to None.

force_stdin_keymap

Whether to read keymap data from stdin instead of a file. When True, the keymap is ignored and stdin is used. Defaults to False.

Example

>>> # Read keymap from file with custom config
>>> inputs = InputFiles(
...     config=Path("my-config.yaml"),
...     keymap=Path("my-keymap.kbi"),
... )
>>> # Read keymap from stdin
>>> inputs = InputFiles(force_stdin_keymap=True)
config: Path | None
keymap: Path | None
force_stdin_keymap: bool
__init__(config=None, keymap=None, force_stdin_keymap=False)
Parameters:
  • config (Path | None)

  • keymap (Path | None)

  • force_stdin_keymap (bool)

Return type:

None

class skim.data.cli.KeymapGeneratorTargets(all_layers=False, overview=False, selected_layers=<factory>)[source]

Bases: object

Specification of which layers and views to generate.

Defines the target outputs for keymap generation, including which individual layers to render and whether to generate an overview image. This is a frozen dataclass, meaning instances are immutable after creation.

Parameters:
all_layers

Whether to generate images for all layers in the keymap. When True, selected_layers is ignored. Defaults to False.

overview

Whether to generate an overview image showing all layers in a grid layout. Defaults to False.

selected_layers

List of specific layer indices to generate. Only used when all_layers is False. Layer indices are 1-based in CLI input but stored as 0-based internally. Defaults to an empty list.

Example

>>> # Generate specific layers and overview
>>> targets = KeymapGeneratorTargets(
...     overview=True,
...     selected_layers=[0, 2, 4],
... )
>>> targets.overview
True
>>> # Generate all layers
>>> targets = KeymapGeneratorTargets(all_layers=True, overview=True)
all_layers: bool
overview: bool
selected_layers: list[int]
classmethod from_args(layer, logger=<built-in function print>)[source]

Parse CLI layer arguments into a KeymapGeneratorTargets instance.

Interprets various layer selection formats from command-line arguments and constructs the appropriate targets configuration. Supports ranges, comma-separated values, and special keywords.

Parameters:
  • layer (tuple[str, ...]) –

    Tuple of layer specification strings from the CLI. Each string can be:

    • A single number: “1”, “3” (generates that layer)

    • A range: “1-3” (generates layers 1, 2, and 3)

    • Comma-separated: “1,3,5” (generates layers 1, 3, and 5)

    • ”overview”: Generates only the overview image

    • ”all-layers”: Generates all individual layers

    • ”all”: Generates all layers plus overview (default behavior)

  • logger (Callable[[str], None]) – Callable for warning messages about invalid input. Defaults to print. Called with a string message when invalid layer specifications are encountered.

Return type:

KeymapGeneratorTargets

Returns:

A KeymapGeneratorTargets instance configured according to the parsed arguments.

Example

>>> # No arguments = all layers + overview
>>> targets = KeymapGeneratorTargets.from_args(())
>>> targets.all_layers
True
>>> targets.overview
True
>>> # Specific layers
>>> targets = KeymapGeneratorTargets.from_args(("1", "3-5"))
>>> targets.selected_layers
[1, 3, 4, 5]
>>> # Keywords
>>> targets = KeymapGeneratorTargets.from_args(("overview",))
>>> targets.overview
True
>>> targets.all_layers
False
__init__(all_layers=False, overview=False, selected_layers=<factory>)
Parameters:
Return type:

None

Trie

Trie (prefix tree) data structure for efficient prefix matching.

This module provides a Trie implementation optimized for checking whether a string starts with any of a predefined set of prefixes. It is used internally by the keycode label adapter to efficiently match QMK macro function names like “LT”, “MO”, “TG”, etc.

Example

>>> from skim.data.trie import Trie
>>> trie = Trie(["LT", "MO", "TG", "OSL"])
>>> trie.get_matching_prefix("LT0")
'LT'
>>> trie.get_matching_prefix("MO(1)")
'MO'
>>> trie.get_matching_prefix("KC_A") is None
True
class skim.data.trie.Trie(words)[source]

Bases: object

A trie (prefix tree) for efficient prefix matching.

This data structure allows O(m) lookup time to check if a string starts with any word from a predefined set, where m is the length of the search string (or more precisely, the length of the matching prefix).

The trie is built once during initialization and then supports fast prefix queries. It is particularly useful for parsing QMK macro functions where we need to identify function names at the start of strings like “LT(1, KC_A)” or “MO(2)”.

Parameters:

words (Iterable[str])

root

The root node of the trie, represented as a nested dictionary. Each key is a character leading to a child node (another dict), except for None which marks the end of a word and stores the complete word string.

Example

>>> trie = Trie(["cat", "car", "card"])
>>> trie.get_matching_prefix("catalog")
'cat'
>>> trie.get_matching_prefix("car")
'car'
>>> trie.get_matching_prefix("dog") is None
True
__init__(words)[source]

Initialize the trie with a collection of words.

Builds the trie structure by inserting all provided words. Each word creates a path through the trie, with the complete word stored at the terminal node.

Parameters:

words (Iterable[str]) – An iterable of strings to index in the trie. Can be a list, tuple, set, generator, or any other iterable of strings.

Return type:

None

Example

>>> trie = Trie(["LT", "MO", "TG"])
>>> trie.get_matching_prefix("LT0")
'LT'
>>> # Can also use generators
>>> trie = Trie(w.upper() for w in ["lt", "mo", "tg"])
>>> trie.get_matching_prefix("MO(1)")
'MO'
root: dict
get_matching_prefix(search_string)[source]

Find the longest indexed word that is a prefix of the search string.

Traverses the trie following characters from the search string. If a complete indexed word is found (marked by a None key), that word is returned. The search continues to find the longest possible match.

Parameters:

search_string (str) – The string to check for a matching prefix.

Return type:

str | None

Returns:

The matching prefix word if found, or None if the search string doesn’t start with any indexed word.

Example

>>> trie = Trie(["LT", "LM", "OSL"])
>>> trie.get_matching_prefix("LT(1, KC_A)")
'LT'
>>> trie.get_matching_prefix("OSL(2)")
'OSL'
>>> trie.get_matching_prefix("KC_SPACE") is None
True
>>> trie.get_matching_prefix("LT")
'LT'