glitchlings 0.2.1__cp312-cp312-win_amd64.whl → 0.2.2__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.
- glitchlings/_zoo_rust.cp312-win_amd64.pyd +0 -0
- glitchlings/main.py +17 -39
- glitchlings/util/__init__.py +30 -0
- glitchlings/zoo/__init__.py +96 -19
- glitchlings/zoo/_ocr_confusions.py +34 -0
- glitchlings/zoo/jargoyle.py +53 -11
- glitchlings/zoo/ocr_confusions.tsv +30 -0
- glitchlings/zoo/redactyl.py +3 -1
- glitchlings/zoo/scannequin.py +4 -29
- glitchlings/zoo/typogre.py +12 -4
- {glitchlings-0.2.1.dist-info → glitchlings-0.2.2.dist-info}/METADATA +12 -18
- glitchlings-0.2.2.dist-info/RECORD +25 -0
- glitchlings/_typogre_rust.cp312-win_amd64.pyd +0 -0
- glitchlings-0.2.1.dist-info/RECORD +0 -24
- {glitchlings-0.2.1.dist-info → glitchlings-0.2.2.dist-info}/WHEEL +0 -0
- {glitchlings-0.2.1.dist-info → glitchlings-0.2.2.dist-info}/entry_points.txt +0 -0
- {glitchlings-0.2.1.dist-info → glitchlings-0.2.2.dist-info}/licenses/LICENSE +0 -0
- {glitchlings-0.2.1.dist-info → glitchlings-0.2.2.dist-info}/top_level.txt +0 -0
Binary file
|
glitchlings/main.py
CHANGED
@@ -11,31 +11,12 @@ from . import SAMPLE_TEXT
|
|
11
11
|
from .zoo import (
|
12
12
|
Glitchling,
|
13
13
|
Gaggle,
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
reduple,
|
18
|
-
rushmore,
|
19
|
-
redactyl,
|
20
|
-
scannequin,
|
14
|
+
BUILTIN_GLITCHLINGS,
|
15
|
+
DEFAULT_GLITCHLING_NAMES,
|
16
|
+
parse_glitchling_spec,
|
21
17
|
summon,
|
22
18
|
)
|
23
19
|
|
24
|
-
|
25
|
-
BUILTIN_GLITCHLINGS: dict[str, Glitchling] = {
|
26
|
-
g.name.lower(): g
|
27
|
-
for g in [
|
28
|
-
typogre,
|
29
|
-
mim1c,
|
30
|
-
jargoyle,
|
31
|
-
reduple,
|
32
|
-
rushmore,
|
33
|
-
redactyl,
|
34
|
-
scannequin,
|
35
|
-
]
|
36
|
-
}
|
37
|
-
|
38
|
-
DEFAULT_GLITCHLING_NAMES: list[str] = list(BUILTIN_GLITCHLINGS.keys())
|
39
20
|
MAX_NAME_WIDTH = max(len(glitchling.name) for glitchling in BUILTIN_GLITCHLINGS.values())
|
40
21
|
|
41
22
|
|
@@ -62,8 +43,11 @@ def build_parser() -> argparse.ArgumentParser:
|
|
62
43
|
"--glitchling",
|
63
44
|
dest="glitchlings",
|
64
45
|
action="append",
|
65
|
-
metavar="
|
66
|
-
help=
|
46
|
+
metavar="SPEC",
|
47
|
+
help=(
|
48
|
+
"Glitchling to apply, optionally with parameters like "
|
49
|
+
"Typogre(max_change_rate=0.05). Repeat for multiples; defaults to all built-ins."
|
50
|
+
),
|
67
51
|
)
|
68
52
|
parser.add_argument(
|
69
53
|
"-s",
|
@@ -147,23 +131,16 @@ def read_text(args: argparse.Namespace, parser: argparse.ArgumentParser) -> str:
|
|
147
131
|
def summon_glitchlings(
|
148
132
|
names: list[str] | None, parser: argparse.ArgumentParser, seed: int
|
149
133
|
) -> Gaggle:
|
150
|
-
"""Instantiate the requested glitchlings and bundle them in a ``Gaggle``.
|
151
|
-
|
152
|
-
Args:
|
153
|
-
names: Optional list of glitchling names provided by the user.
|
154
|
-
parser: The argument parser used for emitting user-facing errors.
|
155
|
-
seed: Master seed controlling deterministic corruption order.
|
156
|
-
|
157
|
-
Returns:
|
158
|
-
Gaggle: A ready-to-use collection of glitchlings.
|
159
|
-
|
160
|
-
Raises:
|
161
|
-
SystemExit: Raised indirectly via ``parser.error`` when a provided glitchling
|
162
|
-
name is invalid.
|
163
|
-
"""
|
134
|
+
"""Instantiate the requested glitchlings and bundle them in a ``Gaggle``."""
|
164
135
|
|
165
136
|
if names:
|
166
|
-
normalized
|
137
|
+
normalized: list[str | Glitchling] = []
|
138
|
+
for specification in names:
|
139
|
+
try:
|
140
|
+
normalized.append(parse_glitchling_spec(specification))
|
141
|
+
except ValueError as exc:
|
142
|
+
parser.error(str(exc))
|
143
|
+
raise AssertionError("parser.error should exit")
|
167
144
|
else:
|
168
145
|
normalized = DEFAULT_GLITCHLING_NAMES
|
169
146
|
|
@@ -174,6 +151,7 @@ def summon_glitchlings(
|
|
174
151
|
raise AssertionError("parser.error should exit")
|
175
152
|
|
176
153
|
|
154
|
+
|
177
155
|
def show_diff(original: str, corrupted: str) -> None:
|
178
156
|
"""Display a unified diff between the original and corrupted text."""
|
179
157
|
|
glitchlings/util/__init__.py
CHANGED
@@ -141,6 +141,36 @@ _register_layout(
|
|
141
141
|
),
|
142
142
|
)
|
143
143
|
|
144
|
+
_register_layout(
|
145
|
+
"QWERTZ",
|
146
|
+
(
|
147
|
+
"^1234567890ß´",
|
148
|
+
" qwertzuiopü+",
|
149
|
+
" asdfghjklöä#",
|
150
|
+
" yxcvbnm,.-",
|
151
|
+
),
|
152
|
+
)
|
153
|
+
|
154
|
+
_register_layout(
|
155
|
+
"SPANISH_QWERTY",
|
156
|
+
(
|
157
|
+
"º1234567890'¡",
|
158
|
+
" qwertyuiop´+",
|
159
|
+
" asdfghjklñ´",
|
160
|
+
" <zxcvbnm,.-",
|
161
|
+
),
|
162
|
+
)
|
163
|
+
|
164
|
+
_register_layout(
|
165
|
+
"SWEDISH_QWERTY",
|
166
|
+
(
|
167
|
+
"§1234567890+´",
|
168
|
+
" qwertyuiopå¨",
|
169
|
+
" asdfghjklöä'",
|
170
|
+
" <zxcvbnm,.-",
|
171
|
+
),
|
172
|
+
)
|
173
|
+
|
144
174
|
|
145
175
|
class KeyNeighbors:
|
146
176
|
def __init__(self) -> None:
|
glitchlings/zoo/__init__.py
CHANGED
@@ -1,6 +1,11 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
|
3
|
+
import ast
|
4
|
+
from typing import Any
|
5
|
+
|
1
6
|
from .typogre import Typogre, typogre
|
2
7
|
from .mim1c import Mim1c, mim1c
|
3
|
-
from .jargoyle import Jargoyle, jargoyle
|
8
|
+
from .jargoyle import Jargoyle, jargoyle, dependencies_available as _jargoyle_available
|
4
9
|
from .reduple import Reduple, reduple
|
5
10
|
from .rushmore import Rushmore, rushmore
|
6
11
|
from .redactyl import Redactyl, redactyl
|
@@ -25,33 +30,105 @@ __all__ = [
|
|
25
30
|
"Glitchling",
|
26
31
|
"Gaggle",
|
27
32
|
"summon",
|
33
|
+
"BUILTIN_GLITCHLINGS",
|
34
|
+
"DEFAULT_GLITCHLING_NAMES",
|
35
|
+
"parse_glitchling_spec",
|
28
36
|
]
|
29
37
|
|
38
|
+
_HAS_JARGOYLE = _jargoyle_available()
|
39
|
+
|
40
|
+
_BUILTIN_GLITCHLING_LIST: list[Glitchling] = [typogre, mim1c]
|
41
|
+
if _HAS_JARGOYLE:
|
42
|
+
_BUILTIN_GLITCHLING_LIST.append(jargoyle)
|
43
|
+
_BUILTIN_GLITCHLING_LIST.extend([reduple, rushmore, redactyl, scannequin])
|
44
|
+
|
45
|
+
BUILTIN_GLITCHLINGS: dict[str, Glitchling] = {
|
46
|
+
glitchling.name.lower(): glitchling for glitchling in _BUILTIN_GLITCHLING_LIST
|
47
|
+
}
|
48
|
+
|
49
|
+
_BUILTIN_GLITCHLING_TYPES: dict[str, type[Glitchling]] = {
|
50
|
+
typogre.name.lower(): Typogre,
|
51
|
+
mim1c.name.lower(): Mim1c,
|
52
|
+
reduple.name.lower(): Reduple,
|
53
|
+
rushmore.name.lower(): Rushmore,
|
54
|
+
redactyl.name.lower(): Redactyl,
|
55
|
+
scannequin.name.lower(): Scannequin,
|
56
|
+
}
|
57
|
+
if _HAS_JARGOYLE:
|
58
|
+
_BUILTIN_GLITCHLING_TYPES[jargoyle.name.lower()] = Jargoyle
|
59
|
+
|
60
|
+
DEFAULT_GLITCHLING_NAMES: list[str] = list(BUILTIN_GLITCHLINGS.keys())
|
61
|
+
|
62
|
+
|
63
|
+
def parse_glitchling_spec(specification: str) -> Glitchling:
|
64
|
+
"""Return a glitchling instance configured according to ``specification``."""
|
65
|
+
|
66
|
+
text = specification.strip()
|
67
|
+
if not text:
|
68
|
+
raise ValueError("Glitchling specification cannot be empty.")
|
69
|
+
|
70
|
+
if "(" not in text:
|
71
|
+
glitchling = BUILTIN_GLITCHLINGS.get(text.lower())
|
72
|
+
if glitchling is None:
|
73
|
+
raise ValueError(f"Glitchling '{text}' not found.")
|
74
|
+
return glitchling
|
75
|
+
|
76
|
+
if not text.endswith(")"):
|
77
|
+
raise ValueError(f"Invalid parameter syntax for glitchling '{text}'.")
|
78
|
+
|
79
|
+
name_part, arg_source = text[:-1].split("(", 1)
|
80
|
+
name = name_part.strip()
|
81
|
+
if not name:
|
82
|
+
raise ValueError(f"Invalid glitchling specification '{text}'.")
|
83
|
+
|
84
|
+
lower_name = name.lower()
|
85
|
+
glitchling_type = _BUILTIN_GLITCHLING_TYPES.get(lower_name)
|
86
|
+
if glitchling_type is None:
|
87
|
+
raise ValueError(f"Glitchling '{name}' not found.")
|
88
|
+
|
89
|
+
try:
|
90
|
+
call_expr = ast.parse(f"_({arg_source})", mode="eval").body
|
91
|
+
except SyntaxError as exc:
|
92
|
+
raise ValueError(
|
93
|
+
f"Invalid parameter syntax for glitchling '{name}': {exc.msg}"
|
94
|
+
) from exc
|
95
|
+
|
96
|
+
if not isinstance(call_expr, ast.Call) or call_expr.args:
|
97
|
+
raise ValueError(
|
98
|
+
f"Glitchling '{name}' parameters must be provided as keyword arguments."
|
99
|
+
)
|
100
|
+
|
101
|
+
kwargs: dict[str, Any] = {}
|
102
|
+
for keyword in call_expr.keywords:
|
103
|
+
if keyword.arg is None:
|
104
|
+
raise ValueError(
|
105
|
+
f"Glitchling '{name}' does not support unpacking arbitrary keyword arguments."
|
106
|
+
)
|
107
|
+
try:
|
108
|
+
kwargs[keyword.arg] = ast.literal_eval(keyword.value)
|
109
|
+
except (ValueError, SyntaxError) as exc:
|
110
|
+
raise ValueError(
|
111
|
+
f"Failed to parse value for parameter '{keyword.arg}' on glitchling '{name}': {exc}"
|
112
|
+
) from exc
|
113
|
+
|
114
|
+
try:
|
115
|
+
return glitchling_type(**kwargs)
|
116
|
+
except TypeError as exc:
|
117
|
+
raise ValueError(f"Failed to instantiate glitchling '{name}': {exc}") from exc
|
118
|
+
|
30
119
|
|
31
120
|
def summon(glitchlings: list[str | Glitchling], seed: int = 151) -> Gaggle:
|
32
121
|
"""Summon glitchlings by name (using defaults) or instance (to change parameters)."""
|
33
|
-
|
34
|
-
|
35
|
-
for g in [
|
36
|
-
typogre,
|
37
|
-
mim1c,
|
38
|
-
jargoyle,
|
39
|
-
reduple,
|
40
|
-
rushmore,
|
41
|
-
redactyl,
|
42
|
-
scannequin,
|
43
|
-
]
|
44
|
-
}
|
45
|
-
summoned = []
|
122
|
+
|
123
|
+
summoned: list[Glitchling] = []
|
46
124
|
for entry in glitchlings:
|
47
125
|
if isinstance(entry, Glitchling):
|
48
126
|
summoned.append(entry)
|
49
127
|
continue
|
50
128
|
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
raise ValueError(f"Glitchling '{entry}' not found.")
|
129
|
+
try:
|
130
|
+
summoned.append(parse_glitchling_spec(entry))
|
131
|
+
except ValueError as exc:
|
132
|
+
raise ValueError(str(exc)) from exc
|
56
133
|
|
57
134
|
return Gaggle(summoned, seed=seed)
|
@@ -0,0 +1,34 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
|
3
|
+
from importlib import resources
|
4
|
+
|
5
|
+
_CONFUSION_TABLE: list[tuple[str, list[str]]] | None = None
|
6
|
+
|
7
|
+
|
8
|
+
def load_confusion_table() -> list[tuple[str, list[str]]]:
|
9
|
+
"""Load the OCR confusion table shared by Python and Rust implementations."""
|
10
|
+
global _CONFUSION_TABLE
|
11
|
+
if _CONFUSION_TABLE is not None:
|
12
|
+
return _CONFUSION_TABLE
|
13
|
+
|
14
|
+
data = resources.files(__package__) / "ocr_confusions.tsv"
|
15
|
+
text = data.read_text(encoding="utf-8")
|
16
|
+
indexed_entries: list[tuple[int, tuple[str, list[str]]]] = []
|
17
|
+
for line_number, line in enumerate(text.splitlines()):
|
18
|
+
stripped = line.strip()
|
19
|
+
if not stripped or stripped.startswith("#"):
|
20
|
+
continue
|
21
|
+
parts = stripped.split()
|
22
|
+
if len(parts) < 2:
|
23
|
+
continue
|
24
|
+
source, *replacements = parts
|
25
|
+
indexed_entries.append((line_number, (source, replacements)))
|
26
|
+
|
27
|
+
# Sort longer patterns first to avoid overlapping matches, mirroring the
|
28
|
+
# behaviour of the Rust `confusion_table` helper.
|
29
|
+
indexed_entries.sort(
|
30
|
+
key=lambda item: (-len(item[1][0]), item[0])
|
31
|
+
)
|
32
|
+
entries = [entry for _, entry in indexed_entries]
|
33
|
+
_CONFUSION_TABLE = entries
|
34
|
+
return entries
|
glitchlings/zoo/jargoyle.py
CHANGED
@@ -2,27 +2,67 @@ import random
|
|
2
2
|
import re
|
3
3
|
from collections.abc import Iterable
|
4
4
|
from dataclasses import dataclass
|
5
|
-
from typing import Any, Literal, cast
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
5
|
+
from typing import TYPE_CHECKING, Any, Literal, cast
|
6
|
+
|
7
|
+
try: # pragma: no cover - exercised in environments with NLTK installed
|
8
|
+
import nltk # type: ignore[import]
|
9
|
+
except ModuleNotFoundError as exc: # pragma: no cover - triggered when NLTK missing
|
10
|
+
nltk = None # type: ignore[assignment]
|
11
|
+
find = None # type: ignore[assignment]
|
12
|
+
_NLTK_IMPORT_ERROR = exc
|
13
|
+
else: # pragma: no cover - executed when NLTK is available
|
14
|
+
from nltk.corpus.reader import WordNetCorpusReader as _WordNetCorpusReader # type: ignore[import]
|
15
|
+
from nltk.data import find as _nltk_find # type: ignore[import]
|
16
|
+
|
17
|
+
find = _nltk_find
|
18
|
+
_NLTK_IMPORT_ERROR = None
|
19
|
+
|
20
|
+
if TYPE_CHECKING: # pragma: no cover - typing aid only
|
21
|
+
from nltk.corpus.reader import WordNetCorpusReader # type: ignore[import]
|
22
|
+
else: # Use ``Any`` at runtime to avoid hard dependency when NLTK missing
|
23
|
+
WordNetCorpusReader = Any
|
24
|
+
|
25
|
+
if nltk is not None: # pragma: no cover - guarded by import success
|
26
|
+
try:
|
27
|
+
from nltk.corpus import wordnet as _WORDNET_MODULE # type: ignore[import]
|
28
|
+
except ModuleNotFoundError: # pragma: no cover - only hit on namespace packages
|
29
|
+
_WORDNET_MODULE = None
|
30
|
+
else:
|
31
|
+
WordNetCorpusReader = _WordNetCorpusReader # type: ignore[assignment]
|
32
|
+
else:
|
33
|
+
_WORDNET_MODULE = None
|
10
34
|
|
11
35
|
from .core import AttackWave, Glitchling
|
12
36
|
|
13
|
-
try: # pragma: no cover - exercised when the namespace package is present
|
14
|
-
from nltk.corpus import wordnet as _WORDNET_MODULE
|
15
|
-
except ModuleNotFoundError: # pragma: no cover - triggered on modern NLTK installs
|
16
|
-
_WORDNET_MODULE = None
|
17
|
-
|
18
37
|
_WORDNET_HANDLE: WordNetCorpusReader | Any | None = _WORDNET_MODULE
|
19
38
|
|
20
39
|
_wordnet_ready = False
|
21
40
|
|
22
41
|
|
42
|
+
def _require_nltk() -> None:
|
43
|
+
"""Ensure the NLTK dependency is present before continuing."""
|
44
|
+
|
45
|
+
if nltk is None or find is None:
|
46
|
+
message = (
|
47
|
+
"The NLTK package is required for the jargoyle glitchling; install "
|
48
|
+
"the 'wordnet' extra via `pip install glitchlings[wordnet]`."
|
49
|
+
)
|
50
|
+
if '_NLTK_IMPORT_ERROR' in globals() and _NLTK_IMPORT_ERROR is not None:
|
51
|
+
raise RuntimeError(message) from _NLTK_IMPORT_ERROR
|
52
|
+
raise RuntimeError(message)
|
53
|
+
|
54
|
+
|
55
|
+
def dependencies_available() -> bool:
|
56
|
+
"""Return ``True`` when the runtime NLTK dependency is present."""
|
57
|
+
|
58
|
+
return nltk is not None and find is not None
|
59
|
+
|
60
|
+
|
23
61
|
def _load_wordnet_reader() -> WordNetCorpusReader:
|
24
62
|
"""Return a WordNet corpus reader from the downloaded corpus files."""
|
25
63
|
|
64
|
+
_require_nltk()
|
65
|
+
|
26
66
|
try:
|
27
67
|
root = find("corpora/wordnet")
|
28
68
|
except LookupError:
|
@@ -59,6 +99,8 @@ def ensure_wordnet() -> None:
|
|
59
99
|
if _wordnet_ready:
|
60
100
|
return
|
61
101
|
|
102
|
+
_require_nltk()
|
103
|
+
|
62
104
|
resource = _wordnet()
|
63
105
|
|
64
106
|
try:
|
@@ -272,4 +314,4 @@ class Jargoyle(Glitchling):
|
|
272
314
|
jargoyle = Jargoyle()
|
273
315
|
|
274
316
|
|
275
|
-
__all__ = ["Jargoyle", "ensure_wordnet", "jargoyle"]
|
317
|
+
__all__ = ["Jargoyle", "dependencies_available", "ensure_wordnet", "jargoyle"]
|
@@ -0,0 +1,30 @@
|
|
1
|
+
# Source Replacements (space-separated)
|
2
|
+
li h
|
3
|
+
h li
|
4
|
+
rn m
|
5
|
+
m rn
|
6
|
+
cl d
|
7
|
+
d cl
|
8
|
+
I l
|
9
|
+
l I 1
|
10
|
+
1 l I
|
11
|
+
0 O
|
12
|
+
O 0
|
13
|
+
B 8
|
14
|
+
8 B
|
15
|
+
S 5
|
16
|
+
5 S
|
17
|
+
Z 2
|
18
|
+
2 Z
|
19
|
+
G 6
|
20
|
+
6 G
|
21
|
+
“ "
|
22
|
+
” "
|
23
|
+
‘ '
|
24
|
+
’ '
|
25
|
+
— -
|
26
|
+
– -
|
27
|
+
vv w
|
28
|
+
w vv
|
29
|
+
ri n
|
30
|
+
n ri
|
glitchlings/zoo/redactyl.py
CHANGED
@@ -82,7 +82,9 @@ def redact_words(
|
|
82
82
|
if rng is None:
|
83
83
|
rng = random.Random(seed)
|
84
84
|
|
85
|
-
|
85
|
+
use_rust = _redact_words_rust is not None and isinstance(merge_adjacent, bool)
|
86
|
+
|
87
|
+
if use_rust:
|
86
88
|
return _redact_words_rust(
|
87
89
|
text,
|
88
90
|
replacement_char,
|
glitchlings/zoo/scannequin.py
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
import re
|
2
2
|
import random
|
3
3
|
|
4
|
+
from ._ocr_confusions import load_confusion_table
|
4
5
|
from .core import Glitchling, AttackWave, AttackOrder
|
5
6
|
|
6
7
|
try:
|
@@ -33,35 +34,9 @@ def _python_ocr_artifacts(
|
|
33
34
|
if not text:
|
34
35
|
return text
|
35
36
|
|
36
|
-
#
|
37
|
-
#
|
38
|
-
confusion_table
|
39
|
-
("li", ["h"]),
|
40
|
-
("h", ["li"]),
|
41
|
-
("rn", ["m"]),
|
42
|
-
("m", ["rn"]),
|
43
|
-
("cl", ["d"]),
|
44
|
-
("d", ["cl"]),
|
45
|
-
("I", ["l"]),
|
46
|
-
("l", ["I", "1"]),
|
47
|
-
("1", ["l", "I"]),
|
48
|
-
("0", ["O"]),
|
49
|
-
("O", ["0"]),
|
50
|
-
("B", ["8"]),
|
51
|
-
("8", ["B"]),
|
52
|
-
("S", ["5"]),
|
53
|
-
("5", ["S"]),
|
54
|
-
("Z", ["2"]),
|
55
|
-
("2", ["Z"]),
|
56
|
-
("G", ["6"]),
|
57
|
-
("6", ["G"]),
|
58
|
-
("“", ['"']),
|
59
|
-
("”", ['"']),
|
60
|
-
("‘", ["'"]),
|
61
|
-
("’", ["'"]),
|
62
|
-
("—", ["-"]), # em dash -> hyphen
|
63
|
-
("–", ["-"]), # en dash -> hyphen
|
64
|
-
]
|
37
|
+
# Keep the confusion definitions in a shared data file so both the Python
|
38
|
+
# and Rust implementations stay in sync.
|
39
|
+
confusion_table = load_confusion_table()
|
65
40
|
|
66
41
|
# Build candidate matches as (start, end, choices)
|
67
42
|
candidates: list[tuple[int, int, list[str]]] = []
|
glitchlings/zoo/typogre.py
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
from __future__ import annotations
|
2
2
|
|
3
|
+
import math
|
3
4
|
import random
|
4
5
|
from typing import Optional
|
5
6
|
|
@@ -7,7 +8,7 @@ from .core import Glitchling, AttackWave, AttackOrder
|
|
7
8
|
from ..util import KEYNEIGHBORS
|
8
9
|
|
9
10
|
try:
|
10
|
-
from glitchlings.
|
11
|
+
from glitchlings._zoo_rust import fatfinger as _fatfinger_rust
|
11
12
|
except ImportError: # pragma: no cover - compiled extension not present
|
12
13
|
_fatfinger_rust = None
|
13
14
|
|
@@ -91,8 +92,11 @@ def _fatfinger_python(
|
|
91
92
|
layout: dict[str, list[str]],
|
92
93
|
rng: random.Random,
|
93
94
|
) -> str:
|
95
|
+
rate = max(0.0, max_change_rate)
|
94
96
|
s = text
|
95
|
-
max_changes =
|
97
|
+
max_changes = math.ceil(len(s) * rate)
|
98
|
+
if max_changes == 0:
|
99
|
+
return s
|
96
100
|
|
97
101
|
positional_actions = ("char_swap", "missing_char", "extra_char", "nearby_char")
|
98
102
|
global_actions = ("skipped_space", "random_space", "unichar", "repeated_char")
|
@@ -148,12 +152,16 @@ def fatfinger(
|
|
148
152
|
if not text:
|
149
153
|
return ""
|
150
154
|
|
155
|
+
rate = max(0.0, max_change_rate)
|
156
|
+
if rate == 0.0:
|
157
|
+
return text
|
158
|
+
|
151
159
|
layout = getattr(KEYNEIGHBORS, keyboard)
|
152
160
|
|
153
161
|
if _fatfinger_rust is not None:
|
154
|
-
return _fatfinger_rust(text, max_change_rate=
|
162
|
+
return _fatfinger_rust(text, max_change_rate=rate, layout=layout, rng=rng)
|
155
163
|
|
156
|
-
return _fatfinger_python(text, max_change_rate=
|
164
|
+
return _fatfinger_python(text, max_change_rate=rate, layout=layout, rng=rng)
|
157
165
|
|
158
166
|
|
159
167
|
class Typogre(Glitchling):
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: glitchlings
|
3
|
-
Version: 0.2.
|
3
|
+
Version: 0.2.2
|
4
4
|
Summary: Monsters for your language games.
|
5
5
|
Author: osoleve
|
6
6
|
License: Apache License
|
@@ -232,11 +232,14 @@ Provides-Extra: hf
|
|
232
232
|
Requires-Dist: datasets>=4.0.0; extra == "hf"
|
233
233
|
Provides-Extra: wordnet
|
234
234
|
Requires-Dist: nltk>=3.9.1; extra == "wordnet"
|
235
|
+
Requires-Dist: numpy<=2.0,>=1.24; extra == "wordnet"
|
235
236
|
Provides-Extra: prime
|
236
237
|
Requires-Dist: verifiers>=0.1.3.post0; extra == "prime"
|
237
238
|
Provides-Extra: dev
|
238
239
|
Requires-Dist: pytest>=8.0.0; extra == "dev"
|
239
240
|
Requires-Dist: hypothesis>=6.140.0; extra == "dev"
|
241
|
+
Requires-Dist: nltk>=3.9.1; extra == "dev"
|
242
|
+
Requires-Dist: numpy<=2.0,>=1.24; extra == "dev"
|
240
243
|
Dynamic: license-file
|
241
244
|
|
242
245
|
#
|
@@ -294,22 +297,10 @@ print(gaggle(SAMPLE_TEXT))
|
|
294
297
|
|
295
298
|
## Usage
|
296
299
|
|
297
|
-
|
298
|
-
|
299
|
-
-
|
300
|
-
|
301
|
-
|
302
|
-
### Rust pipeline acceleration (opt-in)
|
303
|
-
|
304
|
-
The refactored Rust pipeline can execute multiple glitchlings without
|
305
|
-
bouncing back through Python, but it is gated behind a feature flag so
|
306
|
-
teams can roll it out gradually. After compiling the Rust extension
|
307
|
-
(`python -m cibuildwheel --output-dir dist`) set
|
308
|
-
`GLITCHLINGS_RUST_PIPELINE=1` (or `true`, `yes`, `on`) before importing
|
309
|
-
`glitchlings`. When the flag is set and the extension is available,
|
310
|
-
`Gaggle` automatically batches compatible glitchlings into the Rust
|
311
|
-
pipeline; otherwise it transparently falls back to the legacy Python
|
312
|
-
loop.
|
300
|
+
Need detailed usage patterns, dataset workflows, or tips for enabling the
|
301
|
+
Rust accelerator? Consult the [Glitchlings Usage Guide](docs/index.md)
|
302
|
+
for end-to-end instructions spanning the Python API, CLI, Hugging Face
|
303
|
+
integrations, and the feature-flagged Rust pipeline.
|
313
304
|
|
314
305
|
### Prime Intellect environments
|
315
306
|
|
@@ -384,11 +375,14 @@ glitchlings --list
|
|
384
375
|
# Run Typogre against the contents of a file and inspect the diff.
|
385
376
|
glitchlings -g typogre --file documents/report.txt --diff
|
386
377
|
|
378
|
+
# Configure glitchlings inline by passing keyword arguments.
|
379
|
+
glitchlings -g "Typogre(max_change_rate=0.05)" "Ghouls just wanna have fun"
|
380
|
+
|
387
381
|
# Pipe text straight into the CLI for an on-the-fly corruption.
|
388
382
|
echo "Beware LLM-written flavor-text" | glitchlings -g mim1c
|
389
383
|
```
|
390
384
|
|
391
|
-
Use `--help` for a complete breakdown of available options.
|
385
|
+
Use `--help` for a complete breakdown of available options, including support for parameterised glitchlings via `-g "Name(arg=value, ...)"` to mirror the Python API.
|
392
386
|
|
393
387
|
## Development
|
394
388
|
|
@@ -0,0 +1,25 @@
|
|
1
|
+
glitchlings/__init__.py,sha256=w8heFqUejrXM_9NNlM9CQnIGkmGUyBV29acg3WsocXA,622
|
2
|
+
glitchlings/__main__.py,sha256=pqNe1C9hMf8pap4oh6x6yo2h4Nsa2RFSaMWHfGtNXj0,130
|
3
|
+
glitchlings/_zoo_rust.cp312-win_amd64.pyd,sha256=Eh4tD2b4ym3zX0KWxVWCFRpmPsZFnyeOiFWr_qQGg5A,1989632
|
4
|
+
glitchlings/main.py,sha256=krujz3GBrdP6FU3O6Z9f3rvc444rT79Hm69zAPG3b-U,6160
|
5
|
+
glitchlings/dlc/__init__.py,sha256=IHD-GGhVFb7SVzErvf2YCJkOR4wGo0nFHXkn_daMvS8,146
|
6
|
+
glitchlings/dlc/huggingface.py,sha256=PIesnDIEvyJxj1IuLw2P9nVPTr4Nv81XM7w2axfyhkA,3029
|
7
|
+
glitchlings/dlc/prime.py,sha256=oKVAVWSD-aa-LqDsctSLXzq0JW2RaIc1l2859ogr4lY,8107
|
8
|
+
glitchlings/util/__init__.py,sha256=GoyQuHTfGRkHzuZwJji6QWSiGd_LHa9QiyjjEpBFW7E,4679
|
9
|
+
glitchlings/zoo/__init__.py,sha256=kYKKlNvEwKtrD26E1hfde33rkN83CMf_h5AQFGjQyBQ,4312
|
10
|
+
glitchlings/zoo/_ocr_confusions.py,sha256=W59Aa5MBDwRF65f8GV-6XwGAmlR5Uk7pa5qvHvhIYdY,1252
|
11
|
+
glitchlings/zoo/core.py,sha256=aGGc0M97QeKM5rsQjTZs3fhIVac0g8A72mW4u72YnD0,14373
|
12
|
+
glitchlings/zoo/jargoyle.py,sha256=TBzt9CFL5GBP_DjqKqUY54DFsX2VAU4LnBNMDIg7P-Y,10444
|
13
|
+
glitchlings/zoo/mim1c.py,sha256=YHFELu3fpY_9VxRavYfCoAWZYp-HZBXdiLk4DTKdqcY,2979
|
14
|
+
glitchlings/zoo/ocr_confusions.tsv,sha256=S-IJEYCIXYKT1Uu7Id8Lnvg5pw528yNigTtWUdnMv9k,213
|
15
|
+
glitchlings/zoo/redactyl.py,sha256=VV2mPE2WQ41Sl874TjaHu9ShhYlFNLI7embQqKM5_ZE,3738
|
16
|
+
glitchlings/zoo/reduple.py,sha256=WuMpmuZrf5x7JneiRjDF2Y0beEAn7j1DPCV2BuuTuRY,2873
|
17
|
+
glitchlings/zoo/rushmore.py,sha256=dAiv53B_6Zg-zNG5aW8YobJevyBV586HtJVlZqgcGR8,2790
|
18
|
+
glitchlings/zoo/scannequin.py,sha256=BLJ8VFNTrXxv6mKjTMPUHOqziXO-NLpKNQNPbxG7jLI,4178
|
19
|
+
glitchlings/zoo/typogre.py,sha256=CISk0aqI8y5SdZXibqhfP0cu5MZ7TkiOQ7kftqW9RtI,5680
|
20
|
+
glitchlings-0.2.2.dist-info/licenses/LICENSE,sha256=EFEP1evBfHaxsMTBjxm0sZVRp2wct8QLvHE1saII5FI,11538
|
21
|
+
glitchlings-0.2.2.dist-info/METADATA,sha256=mRSQQoNoQAPmmVzfUn6ZZLHL1I6n5wxr45o3DyWsSMw,27811
|
22
|
+
glitchlings-0.2.2.dist-info/WHEEL,sha256=8UP9x9puWI0P1V_d7K2oMTBqfeLNm21CTzZ_Ptr0NXU,101
|
23
|
+
glitchlings-0.2.2.dist-info/entry_points.txt,sha256=kGOwuAsjFDLtztLisaXtOouq9wFVMOJg5FzaAkg-Hto,54
|
24
|
+
glitchlings-0.2.2.dist-info/top_level.txt,sha256=VHFNBrLjtDwPCYXbGKi6o17Eueedi81eNbR3hBOoST0,12
|
25
|
+
glitchlings-0.2.2.dist-info/RECORD,,
|
Binary file
|
@@ -1,24 +0,0 @@
|
|
1
|
-
glitchlings/__init__.py,sha256=w8heFqUejrXM_9NNlM9CQnIGkmGUyBV29acg3WsocXA,622
|
2
|
-
glitchlings/__main__.py,sha256=pqNe1C9hMf8pap4oh6x6yo2h4Nsa2RFSaMWHfGtNXj0,130
|
3
|
-
glitchlings/_typogre_rust.cp312-win_amd64.pyd,sha256=k3PiIXoMQdZX8hMDkooQI4Rg2MH8YuHuq2rK7r5-8o4,265728
|
4
|
-
glitchlings/_zoo_rust.cp312-win_amd64.pyd,sha256=SXku4dvAYyulH-ALhuxJ_A0UareKExQn6gQ75l8fBlk,1943040
|
5
|
-
glitchlings/main.py,sha256=LIFZjSRlE4HiozHUhlIlEIelM6oCJii3GIsuTHW51DI,6547
|
6
|
-
glitchlings/dlc/__init__.py,sha256=IHD-GGhVFb7SVzErvf2YCJkOR4wGo0nFHXkn_daMvS8,146
|
7
|
-
glitchlings/dlc/huggingface.py,sha256=PIesnDIEvyJxj1IuLw2P9nVPTr4Nv81XM7w2axfyhkA,3029
|
8
|
-
glitchlings/dlc/prime.py,sha256=oKVAVWSD-aa-LqDsctSLXzq0JW2RaIc1l2859ogr4lY,8107
|
9
|
-
glitchlings/util/__init__.py,sha256=uGdfyq-RgEKu1nqrNbf-FoQRqnadjKME2aXbnK8hhuI,4169
|
10
|
-
glitchlings/zoo/__init__.py,sha256=_tE2DXkTmQwsojvCeY1ddAE7QkOvZSMTV6c8JzP_CEw,1401
|
11
|
-
glitchlings/zoo/core.py,sha256=aGGc0M97QeKM5rsQjTZs3fhIVac0g8A72mW4u72YnD0,14373
|
12
|
-
glitchlings/zoo/jargoyle.py,sha256=BCaJ5gzxCun3-K1Kh3-xweLc2mzcEWgRKnLNln3bbaA,8747
|
13
|
-
glitchlings/zoo/mim1c.py,sha256=YHFELu3fpY_9VxRavYfCoAWZYp-HZBXdiLk4DTKdqcY,2979
|
14
|
-
glitchlings/zoo/redactyl.py,sha256=vD7XixZEMmu_xXVNzf3AY7OvdRV1iE_sEqqDrAU4mPM,3674
|
15
|
-
glitchlings/zoo/reduple.py,sha256=WuMpmuZrf5x7JneiRjDF2Y0beEAn7j1DPCV2BuuTuRY,2873
|
16
|
-
glitchlings/zoo/rushmore.py,sha256=dAiv53B_6Zg-zNG5aW8YobJevyBV586HtJVlZqgcGR8,2790
|
17
|
-
glitchlings/zoo/scannequin.py,sha256=gwrbHpcCTp-SHXuQyQoqvw4pYzEogCjFdWPTGlmwURA,4796
|
18
|
-
glitchlings/zoo/typogre.py,sha256=gr-c7qy1OpvyaAJgomwdfkGFACJiXc1DAiCtQTxrhVI,5542
|
19
|
-
glitchlings-0.2.1.dist-info/licenses/LICENSE,sha256=EFEP1evBfHaxsMTBjxm0sZVRp2wct8QLvHE1saII5FI,11538
|
20
|
-
glitchlings-0.2.1.dist-info/METADATA,sha256=6ekbj9GPLZgsJxKO-ZyOgyuGinxEsJlKxD-vdePvNe8,28243
|
21
|
-
glitchlings-0.2.1.dist-info/WHEEL,sha256=8UP9x9puWI0P1V_d7K2oMTBqfeLNm21CTzZ_Ptr0NXU,101
|
22
|
-
glitchlings-0.2.1.dist-info/entry_points.txt,sha256=kGOwuAsjFDLtztLisaXtOouq9wFVMOJg5FzaAkg-Hto,54
|
23
|
-
glitchlings-0.2.1.dist-info/top_level.txt,sha256=VHFNBrLjtDwPCYXbGKi6o17Eueedi81eNbR3hBOoST0,12
|
24
|
-
glitchlings-0.2.1.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|