glitchlings 0.4.3__cp312-cp312-win_amd64.whl → 0.4.5__cp312-cp312-win_amd64.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of glitchlings might be problematic. Click here for more details.
- glitchlings/__init__.py +4 -0
- glitchlings/_zoo_rust.cp312-win_amd64.pyd +0 -0
- glitchlings/compat.py +2 -4
- glitchlings/config.py +2 -4
- glitchlings/data/__init__.py +1 -0
- glitchlings/data/hokey_assets.json +193 -0
- glitchlings/dlc/_shared.py +86 -1
- glitchlings/dlc/huggingface.py +6 -6
- glitchlings/dlc/prime.py +1 -1
- glitchlings/dlc/pytorch.py +9 -59
- glitchlings/dlc/pytorch_lightning.py +10 -34
- glitchlings/lexicon/__init__.py +5 -1
- glitchlings/lexicon/_cache.py +3 -5
- glitchlings/lexicon/vector.py +6 -5
- glitchlings/lexicon/wordnet.py +4 -8
- glitchlings/util/hokey_generator.py +144 -0
- glitchlings/util/stretch_locator.py +140 -0
- glitchlings/util/stretchability.py +375 -0
- glitchlings/zoo/__init__.py +5 -1
- glitchlings/zoo/_rate.py +114 -1
- glitchlings/zoo/_rust_extensions.py +143 -0
- glitchlings/zoo/adjax.py +3 -4
- glitchlings/zoo/apostrofae.py +3 -4
- glitchlings/zoo/core.py +21 -9
- glitchlings/zoo/hokey.py +173 -0
- glitchlings/zoo/jargoyle.py +6 -2
- glitchlings/zoo/redactyl.py +4 -5
- glitchlings/zoo/reduple.py +3 -4
- glitchlings/zoo/rushmore.py +3 -4
- glitchlings/zoo/scannequin.py +3 -4
- glitchlings/zoo/typogre.py +3 -4
- glitchlings/zoo/zeedub.py +3 -4
- {glitchlings-0.4.3.dist-info → glitchlings-0.4.5.dist-info}/METADATA +32 -8
- glitchlings-0.4.5.dist-info/RECORD +53 -0
- glitchlings-0.4.3.dist-info/RECORD +0 -46
- {glitchlings-0.4.3.dist-info → glitchlings-0.4.5.dist-info}/WHEEL +0 -0
- {glitchlings-0.4.3.dist-info → glitchlings-0.4.5.dist-info}/entry_points.txt +0 -0
- {glitchlings-0.4.3.dist-info → glitchlings-0.4.5.dist-info}/licenses/LICENSE +0 -0
- {glitchlings-0.4.3.dist-info → glitchlings-0.4.5.dist-info}/top_level.txt +0 -0
glitchlings/zoo/__init__.py
CHANGED
|
@@ -14,6 +14,7 @@ from .core import (
|
|
|
14
14
|
plan_glitchling_specs,
|
|
15
15
|
plan_glitchlings,
|
|
16
16
|
)
|
|
17
|
+
from .hokey import Hokey, hokey
|
|
17
18
|
from .jargoyle import Jargoyle, jargoyle
|
|
18
19
|
from .jargoyle import dependencies_available as _jargoyle_available
|
|
19
20
|
from .mim1c import Mim1c, mim1c
|
|
@@ -33,6 +34,8 @@ __all__ = [
|
|
|
33
34
|
"jargoyle",
|
|
34
35
|
"Apostrofae",
|
|
35
36
|
"apostrofae",
|
|
37
|
+
"Hokey",
|
|
38
|
+
"hokey",
|
|
36
39
|
"Adjax",
|
|
37
40
|
"adjax",
|
|
38
41
|
"Reduple",
|
|
@@ -61,7 +64,7 @@ __all__ = [
|
|
|
61
64
|
|
|
62
65
|
_HAS_JARGOYLE = _jargoyle_available()
|
|
63
66
|
|
|
64
|
-
_BUILTIN_GLITCHLING_LIST: list[Glitchling] = [typogre, apostrofae, mim1c]
|
|
67
|
+
_BUILTIN_GLITCHLING_LIST: list[Glitchling] = [typogre, apostrofae, hokey, mim1c]
|
|
65
68
|
if _HAS_JARGOYLE:
|
|
66
69
|
_BUILTIN_GLITCHLING_LIST.append(jargoyle)
|
|
67
70
|
_BUILTIN_GLITCHLING_LIST.extend([adjax, reduple, rushmore, redactyl, scannequin, zeedub])
|
|
@@ -73,6 +76,7 @@ BUILTIN_GLITCHLINGS: dict[str, Glitchling] = {
|
|
|
73
76
|
_BUILTIN_GLITCHLING_TYPES: dict[str, type[Glitchling]] = {
|
|
74
77
|
typogre.name.lower(): Typogre,
|
|
75
78
|
apostrofae.name.lower(): Apostrofae,
|
|
79
|
+
hokey.name.lower(): Hokey,
|
|
76
80
|
mim1c.name.lower(): Mim1c,
|
|
77
81
|
adjax.name.lower(): Adjax,
|
|
78
82
|
reduple.name.lower(): Reduple,
|
glitchlings/zoo/_rate.py
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
|
+
"""Utilities for handling legacy parameter names across glitchling classes."""
|
|
2
|
+
|
|
1
3
|
from __future__ import annotations
|
|
2
4
|
|
|
5
|
+
import warnings
|
|
6
|
+
|
|
3
7
|
|
|
4
8
|
def resolve_rate(
|
|
5
9
|
*,
|
|
@@ -8,11 +12,120 @@ def resolve_rate(
|
|
|
8
12
|
default: float,
|
|
9
13
|
legacy_name: str,
|
|
10
14
|
) -> float:
|
|
11
|
-
"""Return the effective rate while enforcing mutual exclusivity.
|
|
15
|
+
"""Return the effective rate while enforcing mutual exclusivity.
|
|
16
|
+
|
|
17
|
+
This function centralizes the handling of legacy parameter names, allowing
|
|
18
|
+
glitchlings to maintain backwards compatibility while encouraging migration
|
|
19
|
+
to the standardized 'rate' parameter.
|
|
20
|
+
|
|
21
|
+
Parameters
|
|
22
|
+
----------
|
|
23
|
+
rate : float | None
|
|
24
|
+
The preferred parameter value.
|
|
25
|
+
legacy_value : float | None
|
|
26
|
+
The deprecated legacy parameter value.
|
|
27
|
+
default : float
|
|
28
|
+
Default value if neither parameter is specified.
|
|
29
|
+
legacy_name : str
|
|
30
|
+
Name of the legacy parameter for error/warning messages.
|
|
31
|
+
|
|
32
|
+
Returns
|
|
33
|
+
-------
|
|
34
|
+
float
|
|
35
|
+
The resolved rate value.
|
|
36
|
+
|
|
37
|
+
Raises
|
|
38
|
+
------
|
|
39
|
+
ValueError
|
|
40
|
+
If both rate and legacy_value are specified simultaneously.
|
|
41
|
+
|
|
42
|
+
Warnings
|
|
43
|
+
--------
|
|
44
|
+
DeprecationWarning
|
|
45
|
+
If the legacy parameter is used, a deprecation warning is issued.
|
|
46
|
+
|
|
47
|
+
Examples
|
|
48
|
+
--------
|
|
49
|
+
>>> resolve_rate(rate=0.5, legacy_value=None, default=0.1, legacy_name="old_rate")
|
|
50
|
+
0.5
|
|
51
|
+
>>> resolve_rate(rate=None, legacy_value=0.3, default=0.1, legacy_name="old_rate")
|
|
52
|
+
0.3 # Issues deprecation warning
|
|
53
|
+
>>> resolve_rate(rate=None, legacy_value=None, default=0.1, legacy_name="old_rate")
|
|
54
|
+
0.1
|
|
55
|
+
|
|
56
|
+
"""
|
|
12
57
|
if rate is not None and legacy_value is not None:
|
|
13
58
|
raise ValueError(f"Specify either 'rate' or '{legacy_name}', not both.")
|
|
59
|
+
|
|
14
60
|
if rate is not None:
|
|
15
61
|
return rate
|
|
62
|
+
|
|
63
|
+
if legacy_value is not None:
|
|
64
|
+
warnings.warn(
|
|
65
|
+
f"The '{legacy_name}' parameter is deprecated and will be removed in version 0.6.0. "
|
|
66
|
+
f"Use 'rate' instead.",
|
|
67
|
+
DeprecationWarning,
|
|
68
|
+
stacklevel=3,
|
|
69
|
+
)
|
|
70
|
+
return legacy_value
|
|
71
|
+
|
|
72
|
+
return default
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def resolve_legacy_param(
|
|
76
|
+
*,
|
|
77
|
+
preferred_value: object,
|
|
78
|
+
legacy_value: object,
|
|
79
|
+
default: object,
|
|
80
|
+
preferred_name: str,
|
|
81
|
+
legacy_name: str,
|
|
82
|
+
) -> object:
|
|
83
|
+
"""Resolve a parameter that has both preferred and legacy names.
|
|
84
|
+
|
|
85
|
+
This is a generalized version of resolve_rate() that works with any type.
|
|
86
|
+
|
|
87
|
+
Parameters
|
|
88
|
+
----------
|
|
89
|
+
preferred_value : object
|
|
90
|
+
The value from the preferred parameter name.
|
|
91
|
+
legacy_value : object
|
|
92
|
+
The value from the legacy parameter name.
|
|
93
|
+
default : object
|
|
94
|
+
Default value if neither parameter is specified.
|
|
95
|
+
preferred_name : str
|
|
96
|
+
Name of the preferred parameter.
|
|
97
|
+
legacy_name : str
|
|
98
|
+
Name of the legacy parameter for warning messages.
|
|
99
|
+
|
|
100
|
+
Returns
|
|
101
|
+
-------
|
|
102
|
+
object
|
|
103
|
+
The resolved parameter value.
|
|
104
|
+
|
|
105
|
+
Raises
|
|
106
|
+
------
|
|
107
|
+
ValueError
|
|
108
|
+
If both preferred and legacy values are specified simultaneously.
|
|
109
|
+
|
|
110
|
+
Warnings
|
|
111
|
+
--------
|
|
112
|
+
DeprecationWarning
|
|
113
|
+
If the legacy parameter is used.
|
|
114
|
+
|
|
115
|
+
"""
|
|
116
|
+
if preferred_value is not None and legacy_value is not None:
|
|
117
|
+
raise ValueError(f"Specify either '{preferred_name}' or '{legacy_name}', not both.")
|
|
118
|
+
|
|
119
|
+
if preferred_value is not None:
|
|
120
|
+
return preferred_value
|
|
121
|
+
|
|
16
122
|
if legacy_value is not None:
|
|
123
|
+
warnings.warn(
|
|
124
|
+
f"The '{legacy_name}' parameter is deprecated and will be removed in version 0.6.0. "
|
|
125
|
+
f"Use '{preferred_name}' instead.",
|
|
126
|
+
DeprecationWarning,
|
|
127
|
+
stacklevel=3,
|
|
128
|
+
)
|
|
17
129
|
return legacy_value
|
|
130
|
+
|
|
18
131
|
return default
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
"""Centralized loading and fallback management for optional Rust extensions.
|
|
2
|
+
|
|
3
|
+
This module provides a single source of truth for importing Rust-accelerated
|
|
4
|
+
operations, eliminating duplicated try/except blocks across the codebase.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import logging
|
|
10
|
+
from typing import Any, Callable
|
|
11
|
+
|
|
12
|
+
log = logging.getLogger(__name__)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
# Cache of loaded Rust operations to avoid repeated import attempts
|
|
16
|
+
_rust_operation_cache: dict[str, Callable[..., Any] | None] = {}
|
|
17
|
+
_rust_module_available: bool | None = None
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def is_rust_module_available() -> bool:
|
|
21
|
+
"""Check if the Rust extension module can be imported.
|
|
22
|
+
|
|
23
|
+
Returns
|
|
24
|
+
-------
|
|
25
|
+
bool
|
|
26
|
+
True if glitchlings._zoo_rust can be imported successfully.
|
|
27
|
+
|
|
28
|
+
Notes
|
|
29
|
+
-----
|
|
30
|
+
The result is cached after the first check to avoid repeated import attempts.
|
|
31
|
+
"""
|
|
32
|
+
global _rust_module_available
|
|
33
|
+
|
|
34
|
+
if _rust_module_available is not None:
|
|
35
|
+
return _rust_module_available
|
|
36
|
+
|
|
37
|
+
try:
|
|
38
|
+
import glitchlings._zoo_rust # noqa: F401
|
|
39
|
+
|
|
40
|
+
_rust_module_available = True
|
|
41
|
+
log.debug("Rust extension module successfully loaded")
|
|
42
|
+
except (ImportError, ModuleNotFoundError):
|
|
43
|
+
_rust_module_available = False
|
|
44
|
+
log.debug("Rust extension module not available; using Python fallbacks")
|
|
45
|
+
|
|
46
|
+
return _rust_module_available
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def get_rust_operation(operation_name: str) -> Callable[..., Any] | None:
|
|
50
|
+
"""Load a specific Rust operation by name with caching.
|
|
51
|
+
|
|
52
|
+
Parameters
|
|
53
|
+
----------
|
|
54
|
+
operation_name : str
|
|
55
|
+
The name of the operation to import from glitchlings._zoo_rust.
|
|
56
|
+
|
|
57
|
+
Returns
|
|
58
|
+
-------
|
|
59
|
+
Callable | None
|
|
60
|
+
The Rust operation callable if available, None otherwise.
|
|
61
|
+
|
|
62
|
+
Examples
|
|
63
|
+
--------
|
|
64
|
+
>>> fatfinger = get_rust_operation("fatfinger")
|
|
65
|
+
>>> if fatfinger is not None:
|
|
66
|
+
... result = fatfinger(text, ...)
|
|
67
|
+
... else:
|
|
68
|
+
... result = python_fallback(text, ...)
|
|
69
|
+
|
|
70
|
+
Notes
|
|
71
|
+
-----
|
|
72
|
+
- Results are cached to avoid repeated imports
|
|
73
|
+
- Returns None if the Rust module is unavailable or the operation doesn't exist
|
|
74
|
+
- All import errors are logged at debug level
|
|
75
|
+
"""
|
|
76
|
+
# Check cache first
|
|
77
|
+
if operation_name in _rust_operation_cache:
|
|
78
|
+
return _rust_operation_cache[operation_name]
|
|
79
|
+
|
|
80
|
+
# If the module isn't available, don't try to import individual operations
|
|
81
|
+
if not is_rust_module_available():
|
|
82
|
+
_rust_operation_cache[operation_name] = None
|
|
83
|
+
return None
|
|
84
|
+
|
|
85
|
+
try:
|
|
86
|
+
from glitchlings import _zoo_rust
|
|
87
|
+
|
|
88
|
+
operation = getattr(_zoo_rust, operation_name, None)
|
|
89
|
+
_rust_operation_cache[operation_name] = operation
|
|
90
|
+
|
|
91
|
+
if operation is None:
|
|
92
|
+
log.debug(f"Rust operation '{operation_name}' not found in extension module")
|
|
93
|
+
else:
|
|
94
|
+
log.debug(f"Rust operation '{operation_name}' loaded successfully")
|
|
95
|
+
|
|
96
|
+
return operation
|
|
97
|
+
|
|
98
|
+
except (ImportError, ModuleNotFoundError, AttributeError) as exc:
|
|
99
|
+
log.debug(f"Failed to load Rust operation '{operation_name}': {exc}")
|
|
100
|
+
_rust_operation_cache[operation_name] = None
|
|
101
|
+
return None
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def clear_cache() -> None:
|
|
105
|
+
"""Clear the operation cache, forcing re-import on next access.
|
|
106
|
+
|
|
107
|
+
This is primarily useful for testing scenarios where the Rust module
|
|
108
|
+
availability might change during runtime.
|
|
109
|
+
"""
|
|
110
|
+
global _rust_module_available, _rust_operation_cache
|
|
111
|
+
|
|
112
|
+
_rust_module_available = None
|
|
113
|
+
_rust_operation_cache.clear()
|
|
114
|
+
log.debug("Rust extension cache cleared")
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def preload_operations(*operation_names: str) -> dict[str, Callable[..., Any] | None]:
|
|
118
|
+
"""Eagerly load multiple Rust operations at once.
|
|
119
|
+
|
|
120
|
+
Parameters
|
|
121
|
+
----------
|
|
122
|
+
*operation_names : str
|
|
123
|
+
Names of operations to preload.
|
|
124
|
+
|
|
125
|
+
Returns
|
|
126
|
+
-------
|
|
127
|
+
dict[str, Callable | None]
|
|
128
|
+
Mapping of operation names to their callables (or None if unavailable).
|
|
129
|
+
|
|
130
|
+
Examples
|
|
131
|
+
--------
|
|
132
|
+
>>> ops = preload_operations("fatfinger", "reduplicate_words", "delete_random_words")
|
|
133
|
+
>>> fatfinger = ops["fatfinger"]
|
|
134
|
+
"""
|
|
135
|
+
return {name: get_rust_operation(name) for name in operation_names}
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
__all__ = [
|
|
139
|
+
"is_rust_module_available",
|
|
140
|
+
"get_rust_operation",
|
|
141
|
+
"clear_cache",
|
|
142
|
+
"preload_operations",
|
|
143
|
+
]
|
glitchlings/zoo/adjax.py
CHANGED
|
@@ -4,13 +4,12 @@ import random
|
|
|
4
4
|
from typing import Any, cast
|
|
5
5
|
|
|
6
6
|
from ._rate import resolve_rate
|
|
7
|
+
from ._rust_extensions import get_rust_operation
|
|
7
8
|
from ._text_utils import split_preserving_whitespace, split_token_edges
|
|
8
9
|
from .core import AttackWave, Glitchling
|
|
9
10
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
except ImportError: # pragma: no cover - optional acceleration
|
|
13
|
-
_swap_adjacent_words_rust = None
|
|
11
|
+
# Load Rust-accelerated operation if available
|
|
12
|
+
_swap_adjacent_words_rust = get_rust_operation("swap_adjacent_words")
|
|
14
13
|
|
|
15
14
|
|
|
16
15
|
def _python_swap_adjacent_words(
|
glitchlings/zoo/apostrofae.py
CHANGED
|
@@ -8,12 +8,11 @@ from functools import cache
|
|
|
8
8
|
from importlib import resources
|
|
9
9
|
from typing import Any, Sequence, cast
|
|
10
10
|
|
|
11
|
+
from ._rust_extensions import get_rust_operation
|
|
11
12
|
from .core import AttackOrder, AttackWave, Gaggle, Glitchling
|
|
12
13
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
except ImportError: # pragma: no cover - compiled extension not present
|
|
16
|
-
_apostrofae_rust = None
|
|
14
|
+
# Load Rust-accelerated operation if available
|
|
15
|
+
_apostrofae_rust = get_rust_operation("apostrofae")
|
|
17
16
|
|
|
18
17
|
|
|
19
18
|
@cache
|
glitchlings/zoo/core.py
CHANGED
|
@@ -10,15 +10,13 @@ from hashlib import blake2s
|
|
|
10
10
|
from typing import TYPE_CHECKING, Any, Callable, Protocol, TypedDict, TypeGuard, Union, cast
|
|
11
11
|
|
|
12
12
|
from ..compat import get_datasets_dataset, require_datasets
|
|
13
|
+
from ._rust_extensions import get_rust_operation
|
|
13
14
|
|
|
14
15
|
_DatasetsDataset = get_datasets_dataset()
|
|
15
16
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
except ImportError: # pragma: no cover - compiled extension not present
|
|
20
|
-
_compose_glitchlings_rust = None
|
|
21
|
-
_plan_glitchlings_rust = None
|
|
17
|
+
# Load Rust-accelerated orchestration operations if available
|
|
18
|
+
_compose_glitchlings_rust = get_rust_operation("compose_glitchlings")
|
|
19
|
+
_plan_glitchlings_rust = get_rust_operation("plan_glitchlings")
|
|
22
20
|
|
|
23
21
|
|
|
24
22
|
log = logging.getLogger(__name__)
|
|
@@ -135,7 +133,12 @@ def _plan_glitchlings_with_rust(
|
|
|
135
133
|
|
|
136
134
|
try:
|
|
137
135
|
plan = _plan_glitchlings_rust(specs, int(master_seed))
|
|
138
|
-
except
|
|
136
|
+
except (
|
|
137
|
+
TypeError,
|
|
138
|
+
ValueError,
|
|
139
|
+
RuntimeError,
|
|
140
|
+
AttributeError,
|
|
141
|
+
): # pragma: no cover - defer to Python fallback on failure
|
|
139
142
|
log.debug("Rust orchestration planning failed; falling back to Python plan", exc_info=True)
|
|
140
143
|
return None
|
|
141
144
|
|
|
@@ -537,10 +540,19 @@ class Gaggle(Glitchling):
|
|
|
537
540
|
"""Apply each glitchling to string input sequentially."""
|
|
538
541
|
master_seed = self.seed
|
|
539
542
|
descriptors = self._pipeline_descriptors()
|
|
540
|
-
if
|
|
543
|
+
if (
|
|
544
|
+
master_seed is not None
|
|
545
|
+
and descriptors is not None
|
|
546
|
+
and _compose_glitchlings_rust is not None
|
|
547
|
+
):
|
|
541
548
|
try:
|
|
542
549
|
return cast(str, _compose_glitchlings_rust(text, descriptors, master_seed))
|
|
543
|
-
except
|
|
550
|
+
except (
|
|
551
|
+
TypeError,
|
|
552
|
+
ValueError,
|
|
553
|
+
RuntimeError,
|
|
554
|
+
AttributeError,
|
|
555
|
+
): # pragma: no cover - fall back to Python execution
|
|
544
556
|
log.debug("Rust pipeline failed; falling back", exc_info=True)
|
|
545
557
|
|
|
546
558
|
corrupted = text
|
glitchlings/zoo/hokey.py
ADDED
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
"""Hokey glitchling that performs expressive lengthening."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import random
|
|
6
|
+
from typing import Any, cast
|
|
7
|
+
|
|
8
|
+
from ..util.hokey_generator import HokeyConfig, HokeyGenerator, StretchEvent
|
|
9
|
+
from ..util.stretchability import StretchabilityAnalyzer
|
|
10
|
+
from ._rust_extensions import get_rust_operation
|
|
11
|
+
from .core import AttackOrder, AttackWave, Gaggle
|
|
12
|
+
from .core import Glitchling as GlitchlingBase
|
|
13
|
+
|
|
14
|
+
_hokey_rust = get_rust_operation("hokey")
|
|
15
|
+
_ANALYZER = StretchabilityAnalyzer()
|
|
16
|
+
_GENERATOR = HokeyGenerator(analyzer=_ANALYZER)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def _python_extend_vowels(
|
|
20
|
+
text: str,
|
|
21
|
+
*,
|
|
22
|
+
rate: float,
|
|
23
|
+
extension_min: int,
|
|
24
|
+
extension_max: int,
|
|
25
|
+
word_length_threshold: int,
|
|
26
|
+
base_p: float,
|
|
27
|
+
rng: random.Random,
|
|
28
|
+
return_trace: bool = False,
|
|
29
|
+
) -> str | tuple[str, list[StretchEvent]]:
|
|
30
|
+
config = HokeyConfig(
|
|
31
|
+
rate=rate,
|
|
32
|
+
extension_min=extension_min,
|
|
33
|
+
extension_max=extension_max,
|
|
34
|
+
word_length_threshold=word_length_threshold,
|
|
35
|
+
base_p=base_p,
|
|
36
|
+
)
|
|
37
|
+
result, events = _GENERATOR.generate(text, rng=rng, config=config)
|
|
38
|
+
return (result, events) if return_trace else result
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def extend_vowels(
|
|
42
|
+
text: str,
|
|
43
|
+
rate: float = 0.3,
|
|
44
|
+
extension_min: int = 2,
|
|
45
|
+
extension_max: int = 5,
|
|
46
|
+
word_length_threshold: int = 6,
|
|
47
|
+
seed: int | None = None,
|
|
48
|
+
rng: random.Random | None = None,
|
|
49
|
+
*,
|
|
50
|
+
return_trace: bool = False,
|
|
51
|
+
base_p: float | None = None,
|
|
52
|
+
) -> str | tuple[str, list[StretchEvent]]:
|
|
53
|
+
"""Extend expressive segments of words for emphasis.
|
|
54
|
+
|
|
55
|
+
Parameters
|
|
56
|
+
----------
|
|
57
|
+
text : str
|
|
58
|
+
Input text to transform.
|
|
59
|
+
rate : float, optional
|
|
60
|
+
Global selection rate for candidate words.
|
|
61
|
+
extension_min : int, optional
|
|
62
|
+
Minimum number of extra repetitions for the stretch unit.
|
|
63
|
+
extension_max : int, optional
|
|
64
|
+
Maximum number of extra repetitions for the stretch unit.
|
|
65
|
+
word_length_threshold : int, optional
|
|
66
|
+
Preferred maximum alphabetic length; longer words are de-emphasised but not
|
|
67
|
+
excluded.
|
|
68
|
+
seed : int, optional
|
|
69
|
+
Deterministic seed when ``rng`` is not supplied.
|
|
70
|
+
rng : random.Random, optional
|
|
71
|
+
Random number generator to drive sampling.
|
|
72
|
+
return_trace : bool, optional
|
|
73
|
+
When ``True`` also return the stretch events for introspection.
|
|
74
|
+
base_p : float, optional
|
|
75
|
+
Base probability for the negative-binomial sampler (heavier tails for smaller
|
|
76
|
+
values). Defaults to ``0.45``.
|
|
77
|
+
"""
|
|
78
|
+
if not text:
|
|
79
|
+
empty_trace: list[StretchEvent] = []
|
|
80
|
+
return (text, empty_trace) if return_trace else text
|
|
81
|
+
|
|
82
|
+
if rng is None:
|
|
83
|
+
rng = random.Random(seed)
|
|
84
|
+
base_probability = base_p if base_p is not None else 0.45
|
|
85
|
+
|
|
86
|
+
if return_trace or _hokey_rust is None:
|
|
87
|
+
return _python_extend_vowels(
|
|
88
|
+
text,
|
|
89
|
+
rate=rate,
|
|
90
|
+
extension_min=extension_min,
|
|
91
|
+
extension_max=extension_max,
|
|
92
|
+
word_length_threshold=word_length_threshold,
|
|
93
|
+
base_p=base_probability,
|
|
94
|
+
rng=rng,
|
|
95
|
+
return_trace=return_trace,
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
return cast(
|
|
99
|
+
str,
|
|
100
|
+
_hokey_rust(
|
|
101
|
+
text,
|
|
102
|
+
rate,
|
|
103
|
+
extension_min,
|
|
104
|
+
extension_max,
|
|
105
|
+
word_length_threshold,
|
|
106
|
+
base_probability,
|
|
107
|
+
rng,
|
|
108
|
+
),
|
|
109
|
+
)
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
class Hokey(GlitchlingBase):
|
|
113
|
+
"""Glitchling that stretches words using linguistic heuristics."""
|
|
114
|
+
|
|
115
|
+
seed: int | None
|
|
116
|
+
|
|
117
|
+
def __init__(
|
|
118
|
+
self,
|
|
119
|
+
*,
|
|
120
|
+
rate: float = 0.3,
|
|
121
|
+
extension_min: int = 2,
|
|
122
|
+
extension_max: int = 5,
|
|
123
|
+
word_length_threshold: int = 6,
|
|
124
|
+
base_p: float = 0.45,
|
|
125
|
+
seed: int | None = None,
|
|
126
|
+
) -> None:
|
|
127
|
+
self._master_seed: int | None = seed
|
|
128
|
+
|
|
129
|
+
def _corruption_wrapper(text: str, **kwargs: Any) -> str:
|
|
130
|
+
result = extend_vowels(text, **kwargs)
|
|
131
|
+
return result if isinstance(result, str) else result[0]
|
|
132
|
+
|
|
133
|
+
super().__init__(
|
|
134
|
+
name="Hokey",
|
|
135
|
+
corruption_function=_corruption_wrapper,
|
|
136
|
+
scope=AttackWave.CHARACTER,
|
|
137
|
+
order=AttackOrder.FIRST,
|
|
138
|
+
seed=seed,
|
|
139
|
+
rate=rate,
|
|
140
|
+
extension_min=extension_min,
|
|
141
|
+
extension_max=extension_max,
|
|
142
|
+
word_length_threshold=word_length_threshold,
|
|
143
|
+
base_p=base_p,
|
|
144
|
+
)
|
|
145
|
+
|
|
146
|
+
def pipeline_operation(self) -> dict[str, Any] | None:
|
|
147
|
+
return {
|
|
148
|
+
"type": "hokey",
|
|
149
|
+
"rate": self.kwargs.get("rate", 0.3),
|
|
150
|
+
"extension_min": self.kwargs.get("extension_min", 2),
|
|
151
|
+
"extension_max": self.kwargs.get("extension_max", 5),
|
|
152
|
+
"word_length_threshold": self.kwargs.get("word_length_threshold", 6),
|
|
153
|
+
"base_p": self.kwargs.get("base_p", 0.45),
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
def reset_rng(self, seed: int | None = None) -> None:
|
|
157
|
+
if seed is not None:
|
|
158
|
+
self._master_seed = seed
|
|
159
|
+
super().reset_rng(seed)
|
|
160
|
+
if self.seed is None:
|
|
161
|
+
return
|
|
162
|
+
derived = Gaggle.derive_seed(int(seed), self.name, 0)
|
|
163
|
+
self.seed = int(derived)
|
|
164
|
+
self.rng = random.Random(self.seed)
|
|
165
|
+
self.kwargs["seed"] = self.seed
|
|
166
|
+
else:
|
|
167
|
+
super().reset_rng(None)
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
hokey = Hokey()
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
__all__ = ["Hokey", "hokey", "extend_vowels"]
|
glitchlings/zoo/jargoyle.py
CHANGED
|
@@ -14,7 +14,11 @@ _wordnet_module: ModuleType | None
|
|
|
14
14
|
|
|
15
15
|
try: # pragma: no cover - optional WordNet dependency
|
|
16
16
|
import glitchlings.lexicon.wordnet as _wordnet_module
|
|
17
|
-
except
|
|
17
|
+
except (
|
|
18
|
+
ImportError,
|
|
19
|
+
ModuleNotFoundError,
|
|
20
|
+
AttributeError,
|
|
21
|
+
): # pragma: no cover - triggered when nltk unavailable
|
|
18
22
|
_wordnet_module = None
|
|
19
23
|
|
|
20
24
|
_wordnet_runtime: ModuleType | None = _wordnet_module
|
|
@@ -49,7 +53,7 @@ def dependencies_available() -> bool:
|
|
|
49
53
|
try:
|
|
50
54
|
# Fall back to the configured default lexicon (typically the bundled vector cache).
|
|
51
55
|
get_default_lexicon(seed=None)
|
|
52
|
-
except
|
|
56
|
+
except (RuntimeError, ImportError, ModuleNotFoundError, AttributeError):
|
|
53
57
|
return False
|
|
54
58
|
return True
|
|
55
59
|
|
glitchlings/zoo/redactyl.py
CHANGED
|
@@ -3,6 +3,7 @@ import re
|
|
|
3
3
|
from typing import Any, cast
|
|
4
4
|
|
|
5
5
|
from ._rate import resolve_rate
|
|
6
|
+
from ._rust_extensions import get_rust_operation
|
|
6
7
|
from ._sampling import weighted_sample_without_replacement
|
|
7
8
|
from ._text_utils import (
|
|
8
9
|
WordToken,
|
|
@@ -13,11 +14,8 @@ from .core import AttackWave, Glitchling
|
|
|
13
14
|
|
|
14
15
|
FULL_BLOCK = "█"
|
|
15
16
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
from glitchlings._zoo_rust import redact_words as _redact_words_rust
|
|
19
|
-
except ImportError: # pragma: no cover - compiled extension not present
|
|
20
|
-
_redact_words_rust = None
|
|
17
|
+
# Load Rust-accelerated operation if available
|
|
18
|
+
_redact_words_rust = get_rust_operation("redact_words")
|
|
21
19
|
|
|
22
20
|
|
|
23
21
|
def _python_redact_words(
|
|
@@ -119,6 +117,7 @@ def redact_words(
|
|
|
119
117
|
use_rust = _redact_words_rust is not None and isinstance(merge_adjacent, bool)
|
|
120
118
|
|
|
121
119
|
if use_rust:
|
|
120
|
+
assert _redact_words_rust is not None # Type narrowing for mypy
|
|
122
121
|
return cast(
|
|
123
122
|
str,
|
|
124
123
|
_redact_words_rust(
|
glitchlings/zoo/reduple.py
CHANGED
|
@@ -2,13 +2,12 @@ import random
|
|
|
2
2
|
from typing import Any, cast
|
|
3
3
|
|
|
4
4
|
from ._rate import resolve_rate
|
|
5
|
+
from ._rust_extensions import get_rust_operation
|
|
5
6
|
from ._text_utils import WordToken, collect_word_tokens, split_preserving_whitespace
|
|
6
7
|
from .core import AttackWave, Glitchling
|
|
7
8
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
except ImportError: # pragma: no cover - compiled extension not present
|
|
11
|
-
_reduplicate_words_rust = None
|
|
9
|
+
# Load Rust-accelerated operation if available
|
|
10
|
+
_reduplicate_words_rust = get_rust_operation("reduplicate_words")
|
|
12
11
|
|
|
13
12
|
|
|
14
13
|
def _python_reduplicate_words(
|
glitchlings/zoo/rushmore.py
CHANGED
|
@@ -4,13 +4,12 @@ import re
|
|
|
4
4
|
from typing import Any, cast
|
|
5
5
|
|
|
6
6
|
from ._rate import resolve_rate
|
|
7
|
+
from ._rust_extensions import get_rust_operation
|
|
7
8
|
from ._text_utils import WordToken, collect_word_tokens, split_preserving_whitespace
|
|
8
9
|
from .core import AttackWave, Glitchling
|
|
9
10
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
except ImportError: # pragma: no cover - compiled extension not present
|
|
13
|
-
_delete_random_words_rust = None
|
|
11
|
+
# Load Rust-accelerated operation if available
|
|
12
|
+
_delete_random_words_rust = get_rust_operation("delete_random_words")
|
|
14
13
|
|
|
15
14
|
|
|
16
15
|
def _python_delete_random_words(
|
glitchlings/zoo/scannequin.py
CHANGED
|
@@ -4,12 +4,11 @@ from typing import Any, cast
|
|
|
4
4
|
|
|
5
5
|
from ._ocr_confusions import load_confusion_table
|
|
6
6
|
from ._rate import resolve_rate
|
|
7
|
+
from ._rust_extensions import get_rust_operation
|
|
7
8
|
from .core import AttackOrder, AttackWave, Glitchling
|
|
8
9
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
except ImportError: # pragma: no cover - compiled extension not present
|
|
12
|
-
_ocr_artifacts_rust = None
|
|
10
|
+
# Load Rust-accelerated operation if available
|
|
11
|
+
_ocr_artifacts_rust = get_rust_operation("ocr_artifacts")
|
|
13
12
|
|
|
14
13
|
|
|
15
14
|
def _python_ocr_artifacts(
|
glitchlings/zoo/typogre.py
CHANGED
|
@@ -6,12 +6,11 @@ from typing import Any, Optional, cast
|
|
|
6
6
|
|
|
7
7
|
from ..util import KEYNEIGHBORS
|
|
8
8
|
from ._rate import resolve_rate
|
|
9
|
+
from ._rust_extensions import get_rust_operation
|
|
9
10
|
from .core import AttackOrder, AttackWave, Glitchling
|
|
10
11
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
except ImportError: # pragma: no cover - compiled extension not present
|
|
14
|
-
_fatfinger_rust = None
|
|
12
|
+
# Load Rust-accelerated operation if available
|
|
13
|
+
_fatfinger_rust = get_rust_operation("fatfinger")
|
|
15
14
|
|
|
16
15
|
|
|
17
16
|
def _python_unichar(text: str, rng: random.Random) -> str:
|