Skip to content

Hotstring options

This module contains the declared option model, inheritance sentinel, semantic enums, and fully resolved option model.

The declared send mode uses SendMode (INPUT, PLAY, and EVENT). Fully resolved options use the separate ResolvedSendMode enum so Input's Play and Event fallback behaviors remain explicit without complicating the field type.

options

Parse, normalize, and resolve AutoHotkey v2 hotstring option strings.

HotstringOptions is the single source of truth for interpreting per-hotstring option strings in this project. Omitted inheritable options are represented by the enum member INHERIT of InheritedState. ResolvedHotstringOptions represents the corresponding fully resolved semantic state.

InheritedState

Bases: Enum

Represent an option value inherited from the applicable defaults.

Attributes:

Name Type Description
INHERIT

The option is not explicitly set and should inherit from the applicable default.

Source code in hotstring\core\options.py
27
28
29
30
31
32
33
34
35
36
class InheritedState(Enum):
    """Represent an option value inherited from the applicable defaults.

    Attributes:
        INHERIT:
            The option is not explicitly set and should inherit from the
            applicable default.
    """

    INHERIT = auto()

SettingState

Bases: Enum

Represent the explicit state of a two-state option.

Attributes:

Name Type Description
ENABLED

The option behavior is explicitly enabled.

DISABLED

The option behavior is explicitly disabled.

Source code in hotstring\core\options.py
39
40
41
42
43
44
45
46
47
48
49
50
class SettingState(Enum):
    """Represent the explicit state of a two-state option.

    Attributes:
        ENABLED:
            The option behavior is explicitly enabled.
        DISABLED:
            The option behavior is explicitly disabled.
    """

    ENABLED = auto()
    DISABLED = auto()

CaseMode

Bases: Enum

Represent AutoHotkey hotstring case-matching behavior.

Attributes:

Name Type Description
SENSITIVE

Match the trigger case-sensitively.

INSENSITIVE_CONFORMING

Match case-insensitively and allow replacement case conformation.

INSENSITIVE_FIXED

Match case-insensitively without replacement case conformation.

Source code in hotstring\core\options.py
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
class CaseMode(Enum):
    """Represent AutoHotkey hotstring case-matching behavior.

    Attributes:
        SENSITIVE:
            Match the trigger case-sensitively.
        INSENSITIVE_CONFORMING:
            Match case-insensitively and allow replacement case conformation.
        INSENSITIVE_FIXED:
            Match case-insensitively without replacement case conformation.
    """

    SENSITIVE = auto()
    INSENSITIVE_CONFORMING = auto()
    INSENSITIVE_FIXED = auto()

ReplacementMode

Bases: Enum

Represent AutoHotkey replacement-text interpretation behavior.

Attributes:

Name Type Description
NORMAL

Use normal replacement processing.

RAW

Use raw replacement processing.

TEXT

Use text-mode replacement processing.

Source code in hotstring\core\options.py
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
class ReplacementMode(Enum):
    """Represent AutoHotkey replacement-text interpretation behavior.

    Attributes:
        NORMAL:
            Use normal replacement processing.
        RAW:
            Use raw replacement processing.
        TEXT:
            Use text-mode replacement processing.
    """

    NORMAL = auto()
    RAW = auto()
    TEXT = auto()

SendMode

Bases: Enum

Represent the explicitly selected hotstring send mode.

Attributes:

Name Type Description
INPUT

Explicitly select SendInput.

PLAY

Explicitly select SendPlay.

EVENT

Explicitly select SendEvent.

Source code in hotstring\core\options.py
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
class SendMode(Enum):
    """Represent the explicitly selected hotstring send mode.

    Attributes:
        INPUT:
            Explicitly select SendInput.
        PLAY:
            Explicitly select SendPlay.
        EVENT:
            Explicitly select SendEvent.
    """

    INPUT = auto()
    PLAY = auto()
    EVENT = auto()

ResolvedSendMode

Bases: Enum

Represent the fully resolved hotstring send behavior.

Attributes:

Name Type Description
INPUT_WITH_PLAY_FALLBACK

Explicit SI: use SendInput with SendPlay fallback.

INPUT_WITH_EVENT_FALLBACK

Built-in default: use SendInput with SendEvent fallback.

PLAY

Use SendPlay.

EVENT

Use SendEvent.

Source code in hotstring\core\options.py
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
class ResolvedSendMode(Enum):
    """Represent the fully resolved hotstring send behavior.

    Attributes:
        INPUT_WITH_PLAY_FALLBACK:
            Explicit `SI`: use SendInput with SendPlay fallback.
        INPUT_WITH_EVENT_FALLBACK:
            Built-in default: use SendInput with SendEvent fallback.
        PLAY:
            Use SendPlay.
        EVENT:
            Use SendEvent.
    """

    INPUT_WITH_PLAY_FALLBACK = auto()
    INPUT_WITH_EVENT_FALLBACK = auto()
    PLAY = auto()
    EVENT = auto()

HotstringOptions dataclass

Represent a validated per-hotstring AutoHotkey option declaration.

The original option string is retained in options. Every omitted option is represented by the singleton InheritedState.INHERIT, preserving the distinction between an inherited value and an explicitly selected value.

Attributes:

Name Type Description
options str

Original validated option string with surrounding horizontal whitespace removed.

ending_character_optional SettingState | InheritedState

Whether the hotstring may activate without an ending character, or InheritedState.INHERIT if not explicitly set.

trigger_inside_word SettingState | InheritedState

Explicit inside-word matching state, or InheritedState.INHERIT.

automatic_backspacing SettingState | InheritedState

Explicit automatic-backspacing state, or InheritedState.INHERIT.

case_mode CaseMode | InheritedState

Explicit case-matching mode, or InheritedState.INHERIT.

key_delay int | InheritedState

Explicit key delay, or InheritedState.INHERIT.

omit_ending_character SettingState | InheritedState

Explicit ending-character omission state, or InheritedState.INHERIT.

priority int | InheritedState

Explicit priority, or InheritedState.INHERIT.

replacement_mode ReplacementMode | InheritedState

Explicit replacement-processing mode, or InheritedState.INHERIT.

suspend_exempt SettingState | InheritedState

Explicit suspension-exemption state, or InheritedState.INHERIT.

send_mode SendMode | InheritedState

Explicit send mode, or InheritedState.INHERIT.

execute SettingState | InheritedState

Explicit execute state, or InheritedState.INHERIT.

reset_recognizer SettingState | InheritedState

Explicit recognizer-reset state, or InheritedState.INHERIT.

Source code in hotstring\core\options.py
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
@dataclass(slots=True, frozen=True)
class HotstringOptions:
    """Represent a validated per-hotstring AutoHotkey option declaration.

    The original option string is retained in `options`. Every omitted option
    is represented by the singleton `InheritedState.INHERIT`, preserving the
    distinction between an inherited value and an explicitly selected value.

    Attributes:
        options:
            Original validated option string with surrounding horizontal
            whitespace removed.
        ending_character_optional:
            Whether the hotstring may activate without an ending character,
            or `InheritedState.INHERIT` if not explicitly set.
        trigger_inside_word:
            Explicit inside-word matching state, or `InheritedState.INHERIT`.
        automatic_backspacing:
            Explicit automatic-backspacing state, or `InheritedState.INHERIT`.
        case_mode:
            Explicit case-matching mode, or `InheritedState.INHERIT`.
        key_delay:
            Explicit key delay, or `InheritedState.INHERIT`.
        omit_ending_character:
            Explicit ending-character omission state, or `InheritedState.INHERIT`.
        priority:
            Explicit priority, or `InheritedState.INHERIT`.
        replacement_mode:
            Explicit replacement-processing mode, or `InheritedState.INHERIT`.
        suspend_exempt:
            Explicit suspension-exemption state, or `InheritedState.INHERIT`.
        send_mode:
            Explicit send mode, or `InheritedState.INHERIT`.
        execute:
            Explicit execute state, or `InheritedState.INHERIT`.
        reset_recognizer:
            Explicit recognizer-reset state, or `InheritedState.INHERIT`.
    """

    options: str = field(init=True, compare=False)

    ending_character_optional: SettingState | InheritedState = field(
        init=False,
        default=InheritedState.INHERIT,
        metadata={
            "value_to_str": {SettingState.ENABLED: "*", SettingState.DISABLED: "*0"},
            "str_to_value": {"*": SettingState.ENABLED, "*0": SettingState.DISABLED},
        },
    )
    trigger_inside_word: SettingState | InheritedState = field(
        init=False,
        default=InheritedState.INHERIT,
        metadata={
            "value_to_str": {SettingState.ENABLED: "?", SettingState.DISABLED: "?0"},
            "str_to_value": {"?": SettingState.ENABLED, "?0": SettingState.DISABLED},
        },
    )
    automatic_backspacing: SettingState | InheritedState = field(
        init=False,
        default=InheritedState.INHERIT,
        metadata={
            "value_to_str": {SettingState.ENABLED: "B", SettingState.DISABLED: "B0"},
            "str_to_value": {"B": SettingState.ENABLED, "B0": SettingState.DISABLED},
        },
    )
    case_mode: CaseMode | InheritedState = field(
        init=False,
        default=InheritedState.INHERIT,
        metadata={
            "value_to_str": {
                CaseMode.SENSITIVE: "C",
                CaseMode.INSENSITIVE_CONFORMING: "C0",
                CaseMode.INSENSITIVE_FIXED: "C1",
            },
            "str_to_value": {
                "C": CaseMode.SENSITIVE,
                "C0": CaseMode.INSENSITIVE_CONFORMING,
                "C1": CaseMode.INSENSITIVE_FIXED,
            },
        },
    )
    key_delay: int | InheritedState = field(
        init=False,
        default=InheritedState.INHERIT,
        metadata={"value_to_str": {}, "str_to_value": {}},
    )
    omit_ending_character: SettingState | InheritedState = field(
        init=False,
        default=InheritedState.INHERIT,
        metadata={
            "value_to_str": {SettingState.ENABLED: "O", SettingState.DISABLED: "O0"},
            "str_to_value": {"O": SettingState.ENABLED, "O0": SettingState.DISABLED},
        },
    )
    priority: int | InheritedState = field(
        init=False,
        default=InheritedState.INHERIT,
        metadata={"value_to_str": {}, "str_to_value": {}},
    )
    replacement_mode: ReplacementMode | InheritedState = field(
        init=False,
        default=InheritedState.INHERIT,
        metadata={
            "value_to_str": {
                ReplacementMode.NORMAL: "R0",
                ReplacementMode.RAW: "R",
                ReplacementMode.TEXT: "T",
            },
            "str_to_value": {
                "R0": ReplacementMode.NORMAL,
                "T0": ReplacementMode.NORMAL,
                "R": ReplacementMode.RAW,
                "T": ReplacementMode.TEXT,
            },
        },
    )
    suspend_exempt: SettingState | InheritedState = field(
        init=False,
        default=InheritedState.INHERIT,
        metadata={
            "value_to_str": {SettingState.ENABLED: "S", SettingState.DISABLED: "S0"},
            "str_to_value": {"S": SettingState.ENABLED, "S0": SettingState.DISABLED},
        },
    )
    send_mode: SendMode | InheritedState = field(
        init=False,
        default=InheritedState.INHERIT,
        metadata={
            "value_to_str": {
                SendMode.INPUT: "SI",
                SendMode.PLAY: "SP",
                SendMode.EVENT: "SE",
            },
            "str_to_value": {
                "SI": SendMode.INPUT,
                "SP": SendMode.PLAY,
                "SE": SendMode.EVENT,
            },
        },
    )
    execute: SettingState | InheritedState = field(
        init=False,
        default=InheritedState.INHERIT,
        metadata={
            "value_to_str": {SettingState.ENABLED: "X", SettingState.DISABLED: "X0"},
            "str_to_value": {"X": SettingState.ENABLED, "X0": SettingState.DISABLED},
        },
    )
    reset_recognizer: SettingState | InheritedState = field(
        init=False,
        default=InheritedState.INHERIT,
        metadata={
            "value_to_str": {SettingState.ENABLED: "Z", SettingState.DISABLED: "Z0"},
            "str_to_value": {"Z": SettingState.ENABLED, "Z0": SettingState.DISABLED},
        },
    )

    def __post_init__(self) -> None:
        """Validate and parse the supplied option string.

        Raises:
            TypeError:
                If `options` is not a string.
            ValueError:
                If the string contains an unsupported option or an invalid
                numeric value.
        """
        if not isinstance(self.options, str):
            raise TypeError(
                f"Hotstring options must be a string, not {type(self.options).__name__}"
            )

        object.__setattr__(self, "options", self.options.strip(" \t"))

        field_name_to_info = {field_info.name: field_info for field_info in fields(self)}
        options = self.options
        position = 0

        while position < len(options):
            match = _OPTION_PATTERN.match(options, position)
            if match is None:
                raise ValueError(
                    f"Invalid hotstring option at position {position}: {options[position:]!r}"
                )

            option = match.group(1).upper()

            if option.startswith("K"):
                key_delay = int(option[1:])
                if not -1 <= key_delay <= _INT32_MAX:
                    raise ValueError(
                        f"Hotstring key delay must be between -1 and {_INT32_MAX}: {option!r}"
                    )
                object.__setattr__(self, "key_delay", key_delay)

            elif option.startswith("P"):
                priority = int(option[1:])
                if not _INT32_MIN <= priority <= _INT32_MAX:
                    raise ValueError(
                        f"Hotstring priority is outside the signed 32-bit range: {option!r}"
                    )
                object.__setattr__(self, "priority", priority)

            else:
                field_name = _NON_NUMERIC_OPTION_FIELD_NAME.get(option)
                if field_name is None:
                    raise AssertionError(f"Unhandled hotstring option: {option!r}")

                field_info = field_name_to_info[field_name]
                str_to_value = field_info.metadata.get("str_to_value", {})
                value = str_to_value.get(option)
                if value is None:
                    raise RuntimeError(
                        f"Failed to map hotstring option {option!r} to a value for "
                        f"field {field_name!r}; this should never happen."
                    )
                object.__setattr__(self, field_name, value)

            position = match.end()

    def declaration(self) -> str:
        """Return a canonical option string for the parsed semantic state.

        Returns:
            Canonical option text in dataclass field order. Inherited options
            are omitted.
        """
        options_str = ""

        for field_info in fields(self):
            name = field_info.name
            if name == "options":
                continue

            value = getattr(self, name)
            if value is InheritedState.INHERIT:
                continue

            if name == "key_delay":
                option_str = f"K{value}"
            elif name == "priority":
                option_str = f"P{value}"
            else:
                value_to_str = field_info.metadata.get("value_to_str", {})
                option_str = value_to_str.get(value)
                if option_str is None:
                    raise RuntimeError(
                        f"Failed to map value {value!r} of field {name!r} to a string; "
                        "this should never happen."
                    )

            options_str += option_str

        return options_str

__post_init__

__post_init__() -> None

Validate and parse the supplied option string.

Raises:

Type Description
TypeError

If options is not a string.

ValueError

If the string contains an unsupported option or an invalid numeric value.

Source code in hotstring\core\options.py
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
def __post_init__(self) -> None:
    """Validate and parse the supplied option string.

    Raises:
        TypeError:
            If `options` is not a string.
        ValueError:
            If the string contains an unsupported option or an invalid
            numeric value.
    """
    if not isinstance(self.options, str):
        raise TypeError(
            f"Hotstring options must be a string, not {type(self.options).__name__}"
        )

    object.__setattr__(self, "options", self.options.strip(" \t"))

    field_name_to_info = {field_info.name: field_info for field_info in fields(self)}
    options = self.options
    position = 0

    while position < len(options):
        match = _OPTION_PATTERN.match(options, position)
        if match is None:
            raise ValueError(
                f"Invalid hotstring option at position {position}: {options[position:]!r}"
            )

        option = match.group(1).upper()

        if option.startswith("K"):
            key_delay = int(option[1:])
            if not -1 <= key_delay <= _INT32_MAX:
                raise ValueError(
                    f"Hotstring key delay must be between -1 and {_INT32_MAX}: {option!r}"
                )
            object.__setattr__(self, "key_delay", key_delay)

        elif option.startswith("P"):
            priority = int(option[1:])
            if not _INT32_MIN <= priority <= _INT32_MAX:
                raise ValueError(
                    f"Hotstring priority is outside the signed 32-bit range: {option!r}"
                )
            object.__setattr__(self, "priority", priority)

        else:
            field_name = _NON_NUMERIC_OPTION_FIELD_NAME.get(option)
            if field_name is None:
                raise AssertionError(f"Unhandled hotstring option: {option!r}")

            field_info = field_name_to_info[field_name]
            str_to_value = field_info.metadata.get("str_to_value", {})
            value = str_to_value.get(option)
            if value is None:
                raise RuntimeError(
                    f"Failed to map hotstring option {option!r} to a value for "
                    f"field {field_name!r}; this should never happen."
                )
            object.__setattr__(self, field_name, value)

        position = match.end()

declaration

declaration() -> str

Return a canonical option string for the parsed semantic state.

Returns:

Type Description
str

Canonical option text in dataclass field order. Inherited options

str

are omitted.

Source code in hotstring\core\options.py
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
def declaration(self) -> str:
    """Return a canonical option string for the parsed semantic state.

    Returns:
        Canonical option text in dataclass field order. Inherited options
        are omitted.
    """
    options_str = ""

    for field_info in fields(self):
        name = field_info.name
        if name == "options":
            continue

        value = getattr(self, name)
        if value is InheritedState.INHERIT:
            continue

        if name == "key_delay":
            option_str = f"K{value}"
        elif name == "priority":
            option_str = f"P{value}"
        else:
            value_to_str = field_info.metadata.get("value_to_str", {})
            option_str = value_to_str.get(value)
            if option_str is None:
                raise RuntimeError(
                    f"Failed to map value {value!r} of field {name!r} to a string; "
                    "this should never happen."
                )

        options_str += option_str

    return options_str

ResolvedHotstringOptions dataclass

Represent a fully resolved AutoHotkey hotstring option state.

Unlike HotstringOptions, every field contains a concrete semantic value. InheritedState is therefore absent from every field annotation.

Attributes:

Name Type Description
ending_character_optional SettingState

Whether the hotstring may activate without an ending character.

trigger_inside_word SettingState

Whether the trigger may begin after an alphanumeric character.

automatic_backspacing SettingState

Whether AutoHotkey automatically erases the typed trigger.

case_mode CaseMode

Effective case-matching mode.

key_delay int

Effective hotstring key delay.

omit_ending_character SettingState

Whether an ending character is omitted from replacement output.

priority int

Effective hotstring thread priority.

replacement_mode ReplacementMode

Effective replacement-text processing mode.

suspend_exempt SettingState

Whether the hotstring is exempt from suspension.

send_mode ResolvedSendMode

Effective replacement send mode.

execute SettingState

Whether inline content is executed rather than used as literal replacement text.

reset_recognizer SettingState

Whether the recognizer resets after activation.

Source code in hotstring\core\options.py
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
@dataclass(slots=True, frozen=True, kw_only=True)
class ResolvedHotstringOptions:
    """Represent a fully resolved AutoHotkey hotstring option state.

    Unlike [`HotstringOptions`][hotstring.core.options.HotstringOptions], every
    field contains a concrete semantic value. `InheritedState` is therefore
    absent from every field annotation.

    Attributes:
        ending_character_optional:
            Whether the hotstring may activate without an ending character.
        trigger_inside_word:
            Whether the trigger may begin after an alphanumeric character.
        automatic_backspacing:
            Whether AutoHotkey automatically erases the typed trigger.
        case_mode:
            Effective case-matching mode.
        key_delay:
            Effective hotstring key delay.
        omit_ending_character:
            Whether an ending character is omitted from replacement output.
        priority:
            Effective hotstring thread priority.
        replacement_mode:
            Effective replacement-text processing mode.
        suspend_exempt:
            Whether the hotstring is exempt from suspension.
        send_mode:
            Effective replacement send mode.
        execute:
            Whether inline content is executed rather than used as literal
            replacement text.
        reset_recognizer:
            Whether the recognizer resets after activation.
    """

    ending_character_optional: SettingState
    trigger_inside_word: SettingState
    automatic_backspacing: SettingState
    case_mode: CaseMode
    key_delay: int
    omit_ending_character: SettingState
    priority: int
    replacement_mode: ReplacementMode
    suspend_exempt: SettingState
    send_mode: ResolvedSendMode
    execute: SettingState
    reset_recognizer: SettingState

    def __post_init__(self) -> None:
        """Validate the resolved option state.

        Raises:
            TypeError:
                If any field has an invalid type.
            ValueError:
                If any numeric field is outside its accepted range.
        """
        binary_field_names = (
            "ending_character_optional",
            "trigger_inside_word",
            "automatic_backspacing",
            "omit_ending_character",
            "suspend_exempt",
            "execute",
            "reset_recognizer",
        )

        if not isinstance(self.case_mode, CaseMode):
            raise TypeError(
                f"case_mode must be a CaseMode member, got {type(self.case_mode).__name__}"
            )
        if isinstance(self.key_delay, bool) or not isinstance(self.key_delay, int):
            raise TypeError(f"key_delay must be an int, got {type(self.key_delay).__name__}")
        if not -1 <= self.key_delay <= _INT32_MAX:
            raise ValueError(f"key_delay must be between -1 and {_INT32_MAX}, got {self.key_delay}")
        if isinstance(self.priority, bool) or not isinstance(self.priority, int):
            raise TypeError(f"priority must be an int, got {type(self.priority).__name__}")
        if not _INT32_MIN <= self.priority <= _INT32_MAX:
            raise ValueError(
                f"priority must not be outside the signed 32-bit range, got {self.priority}"
            )
        if not isinstance(self.replacement_mode, ReplacementMode):
            raise TypeError(
                "replacement_mode must be a ReplacementMode member, got "
                f"{type(self.replacement_mode).__name__}"
            )
        if not isinstance(self.send_mode, ResolvedSendMode):
            raise TypeError(
                f"send_mode must be a ResolvedSendMode member, got {type(self.send_mode).__name__}"
            )

        for field_name in binary_field_names:
            value = getattr(self, field_name)
            if not isinstance(value, SettingState):
                raise TypeError(
                    f"{field_name} must be a SettingState member, got {type(value).__name__}"
                )

    @classmethod
    def from_options(
        cls,
        options: HotstringOptions,
        *,
        defaults: ResolvedHotstringOptions,
    ) -> Self:
        """Resolve one parsed declaration against concrete applicable defaults.

        Each explicitly set value in `options` overrides the corresponding
        value in `defaults`; each `InheritedState.INHERIT` value leaves the
        applicable default unchanged.

        Explicit `SI` is resolved to SendInput with SendPlay fallback, while
        an inherited built-in default can remain SendInput with SendEvent
        fallback. This preserves AutoHotkey's distinction between those cases.

        Args:
            options:
                Parsed per-hotstring declaration to resolve.
            defaults:
                Fully resolved defaults applicable to the declaration.

        Returns:
            New fully resolved option state.

        Raises:
            TypeError:
                If either argument has an invalid type.
            RuntimeError:
                If the parsed and resolved option models stop exposing the
                same option field names.
        """
        if not isinstance(options, HotstringOptions):
            raise TypeError(
                f"options must be a HotstringOptions instance, got {type(options).__name__}"
            )
        if not isinstance(defaults, cls):
            raise TypeError(
                f"defaults must be a {cls.__name__} instance, got {type(defaults).__name__}"
            )

        parsed_field_names = {
            field_info.name for field_info in fields(options) if field_info.name != "options"
        }
        resolved_field_names = {field_info.name for field_info in fields(cls)}
        if parsed_field_names != resolved_field_names:
            raise RuntimeError(
                "HotstringOptions and ResolvedHotstringOptions option fields are inconsistent."
            )

        overrides: dict[str, object] = {}
        for name in resolved_field_names:
            value = getattr(options, name)
            if value is InheritedState.INHERIT:
                continue

            if name == "send_mode":
                if value is SendMode.INPUT:
                    overrides[name] = ResolvedSendMode.INPUT_WITH_PLAY_FALLBACK
                elif value is SendMode.PLAY:
                    overrides[name] = ResolvedSendMode.PLAY
                elif value is SendMode.EVENT:
                    overrides[name] = ResolvedSendMode.EVENT
                else:
                    raise RuntimeError(f"Unhandled explicit send mode: {value!r}")
            else:
                overrides[name] = value

        return cast(Self, replace(defaults, **overrides))

__post_init__

__post_init__() -> None

Validate the resolved option state.

Raises:

Type Description
TypeError

If any field has an invalid type.

ValueError

If any numeric field is outside its accepted range.

Source code in hotstring\core\options.py
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
def __post_init__(self) -> None:
    """Validate the resolved option state.

    Raises:
        TypeError:
            If any field has an invalid type.
        ValueError:
            If any numeric field is outside its accepted range.
    """
    binary_field_names = (
        "ending_character_optional",
        "trigger_inside_word",
        "automatic_backspacing",
        "omit_ending_character",
        "suspend_exempt",
        "execute",
        "reset_recognizer",
    )

    if not isinstance(self.case_mode, CaseMode):
        raise TypeError(
            f"case_mode must be a CaseMode member, got {type(self.case_mode).__name__}"
        )
    if isinstance(self.key_delay, bool) or not isinstance(self.key_delay, int):
        raise TypeError(f"key_delay must be an int, got {type(self.key_delay).__name__}")
    if not -1 <= self.key_delay <= _INT32_MAX:
        raise ValueError(f"key_delay must be between -1 and {_INT32_MAX}, got {self.key_delay}")
    if isinstance(self.priority, bool) or not isinstance(self.priority, int):
        raise TypeError(f"priority must be an int, got {type(self.priority).__name__}")
    if not _INT32_MIN <= self.priority <= _INT32_MAX:
        raise ValueError(
            f"priority must not be outside the signed 32-bit range, got {self.priority}"
        )
    if not isinstance(self.replacement_mode, ReplacementMode):
        raise TypeError(
            "replacement_mode must be a ReplacementMode member, got "
            f"{type(self.replacement_mode).__name__}"
        )
    if not isinstance(self.send_mode, ResolvedSendMode):
        raise TypeError(
            f"send_mode must be a ResolvedSendMode member, got {type(self.send_mode).__name__}"
        )

    for field_name in binary_field_names:
        value = getattr(self, field_name)
        if not isinstance(value, SettingState):
            raise TypeError(
                f"{field_name} must be a SettingState member, got {type(value).__name__}"
            )

from_options classmethod

from_options(
    options: HotstringOptions,
    *,
    defaults: ResolvedHotstringOptions,
) -> Self

Resolve one parsed declaration against concrete applicable defaults.

Each explicitly set value in options overrides the corresponding value in defaults; each InheritedState.INHERIT value leaves the applicable default unchanged.

Explicit SI is resolved to SendInput with SendPlay fallback, while an inherited built-in default can remain SendInput with SendEvent fallback. This preserves AutoHotkey's distinction between those cases.

Parameters:

Name Type Description Default
options HotstringOptions

Parsed per-hotstring declaration to resolve.

required
defaults ResolvedHotstringOptions

Fully resolved defaults applicable to the declaration.

required

Returns:

Type Description
Self

New fully resolved option state.

Raises:

Type Description
TypeError

If either argument has an invalid type.

RuntimeError

If the parsed and resolved option models stop exposing the same option field names.

Source code in hotstring\core\options.py
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
@classmethod
def from_options(
    cls,
    options: HotstringOptions,
    *,
    defaults: ResolvedHotstringOptions,
) -> Self:
    """Resolve one parsed declaration against concrete applicable defaults.

    Each explicitly set value in `options` overrides the corresponding
    value in `defaults`; each `InheritedState.INHERIT` value leaves the
    applicable default unchanged.

    Explicit `SI` is resolved to SendInput with SendPlay fallback, while
    an inherited built-in default can remain SendInput with SendEvent
    fallback. This preserves AutoHotkey's distinction between those cases.

    Args:
        options:
            Parsed per-hotstring declaration to resolve.
        defaults:
            Fully resolved defaults applicable to the declaration.

    Returns:
        New fully resolved option state.

    Raises:
        TypeError:
            If either argument has an invalid type.
        RuntimeError:
            If the parsed and resolved option models stop exposing the
            same option field names.
    """
    if not isinstance(options, HotstringOptions):
        raise TypeError(
            f"options must be a HotstringOptions instance, got {type(options).__name__}"
        )
    if not isinstance(defaults, cls):
        raise TypeError(
            f"defaults must be a {cls.__name__} instance, got {type(defaults).__name__}"
        )

    parsed_field_names = {
        field_info.name for field_info in fields(options) if field_info.name != "options"
    }
    resolved_field_names = {field_info.name for field_info in fields(cls)}
    if parsed_field_names != resolved_field_names:
        raise RuntimeError(
            "HotstringOptions and ResolvedHotstringOptions option fields are inconsistent."
        )

    overrides: dict[str, object] = {}
    for name in resolved_field_names:
        value = getattr(options, name)
        if value is InheritedState.INHERIT:
            continue

        if name == "send_mode":
            if value is SendMode.INPUT:
                overrides[name] = ResolvedSendMode.INPUT_WITH_PLAY_FALLBACK
            elif value is SendMode.PLAY:
                overrides[name] = ResolvedSendMode.PLAY
            elif value is SendMode.EVENT:
                overrides[name] = ResolvedSendMode.EVENT
            else:
                raise RuntimeError(f"Unhandled explicit send mode: {value!r}")
        else:
            overrides[name] = value

    return cast(Self, replace(defaults, **overrides))