glitchlings 0.2.5__cp312-cp312-win_amd64.whl → 0.9.3__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/__init__.py +36 -17
- glitchlings/__main__.py +0 -1
- glitchlings/_zoo_rust/__init__.py +12 -0
- glitchlings/_zoo_rust.cp312-win_amd64.pyd +0 -0
- glitchlings/assets/__init__.py +180 -0
- glitchlings/assets/apostrofae_pairs.json +32 -0
- glitchlings/assets/ekkokin_homophones.json +2014 -0
- glitchlings/assets/hokey_assets.json +193 -0
- glitchlings/assets/lexemes/academic.json +1049 -0
- glitchlings/assets/lexemes/colors.json +1333 -0
- glitchlings/assets/lexemes/corporate.json +716 -0
- glitchlings/assets/lexemes/cyberpunk.json +22 -0
- glitchlings/assets/lexemes/lovecraftian.json +23 -0
- glitchlings/assets/lexemes/synonyms.json +3354 -0
- glitchlings/assets/mim1c_homoglyphs.json.gz.b64 +1064 -0
- glitchlings/assets/pipeline_assets.json +29 -0
- glitchlings/attack/__init__.py +53 -0
- glitchlings/attack/compose.py +299 -0
- glitchlings/attack/core.py +465 -0
- glitchlings/attack/encode.py +114 -0
- glitchlings/attack/metrics.py +104 -0
- glitchlings/attack/metrics_dispatch.py +70 -0
- glitchlings/attack/tokenization.py +157 -0
- glitchlings/auggie.py +283 -0
- glitchlings/compat/__init__.py +9 -0
- glitchlings/compat/loaders.py +355 -0
- glitchlings/compat/types.py +41 -0
- glitchlings/conf/__init__.py +41 -0
- glitchlings/conf/loaders.py +331 -0
- glitchlings/conf/schema.py +156 -0
- glitchlings/conf/types.py +72 -0
- glitchlings/config.toml +2 -0
- glitchlings/constants.py +59 -0
- glitchlings/dev/__init__.py +3 -0
- glitchlings/dev/docs.py +45 -0
- glitchlings/dlc/__init__.py +17 -3
- glitchlings/dlc/_shared.py +296 -0
- glitchlings/dlc/gutenberg.py +400 -0
- glitchlings/dlc/huggingface.py +37 -65
- glitchlings/dlc/prime.py +55 -114
- glitchlings/dlc/pytorch.py +98 -0
- glitchlings/dlc/pytorch_lightning.py +173 -0
- glitchlings/internal/__init__.py +16 -0
- glitchlings/internal/rust.py +159 -0
- glitchlings/internal/rust_ffi.py +432 -0
- glitchlings/main.py +123 -32
- glitchlings/runtime_config.py +24 -0
- glitchlings/util/__init__.py +29 -176
- glitchlings/util/adapters.py +65 -0
- glitchlings/util/keyboards.py +311 -0
- glitchlings/util/transcripts.py +108 -0
- glitchlings/zoo/__init__.py +47 -24
- glitchlings/zoo/assets/__init__.py +29 -0
- glitchlings/zoo/core.py +301 -167
- glitchlings/zoo/core_execution.py +98 -0
- glitchlings/zoo/core_planning.py +451 -0
- glitchlings/zoo/corrupt_dispatch.py +295 -0
- glitchlings/zoo/ekkokin.py +118 -0
- glitchlings/zoo/hokey.py +137 -0
- glitchlings/zoo/jargoyle.py +179 -274
- glitchlings/zoo/mim1c.py +106 -68
- glitchlings/zoo/pedant/__init__.py +107 -0
- glitchlings/zoo/pedant/core.py +105 -0
- glitchlings/zoo/pedant/forms.py +74 -0
- glitchlings/zoo/pedant/stones.py +74 -0
- glitchlings/zoo/redactyl.py +44 -175
- glitchlings/zoo/rng.py +259 -0
- glitchlings/zoo/rushmore.py +359 -116
- glitchlings/zoo/scannequin.py +18 -125
- glitchlings/zoo/transforms.py +386 -0
- glitchlings/zoo/typogre.py +76 -162
- glitchlings/zoo/validation.py +477 -0
- glitchlings/zoo/zeedub.py +33 -86
- glitchlings-0.9.3.dist-info/METADATA +334 -0
- glitchlings-0.9.3.dist-info/RECORD +80 -0
- {glitchlings-0.2.5.dist-info → glitchlings-0.9.3.dist-info}/entry_points.txt +1 -0
- glitchlings/zoo/_ocr_confusions.py +0 -34
- glitchlings/zoo/_rate.py +0 -21
- glitchlings/zoo/reduple.py +0 -169
- glitchlings-0.2.5.dist-info/METADATA +0 -490
- glitchlings-0.2.5.dist-info/RECORD +0 -27
- /glitchlings/{zoo → assets}/ocr_confusions.tsv +0 -0
- {glitchlings-0.2.5.dist-info → glitchlings-0.9.3.dist-info}/WHEEL +0 -0
- {glitchlings-0.2.5.dist-info → glitchlings-0.9.3.dist-info}/licenses/LICENSE +0 -0
- {glitchlings-0.2.5.dist-info → glitchlings-0.9.3.dist-info}/top_level.txt +0 -0
glitchlings/main.py
CHANGED
|
@@ -4,15 +4,22 @@ from __future__ import annotations
|
|
|
4
4
|
|
|
5
5
|
import argparse
|
|
6
6
|
import difflib
|
|
7
|
-
|
|
7
|
+
import json
|
|
8
8
|
import sys
|
|
9
|
+
from collections.abc import Sequence
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
from typing import cast
|
|
12
|
+
|
|
13
|
+
import yaml
|
|
9
14
|
|
|
10
15
|
from . import SAMPLE_TEXT
|
|
16
|
+
from .attack import Attack
|
|
17
|
+
from .conf import DEFAULT_ATTACK_SEED, build_gaggle, load_attack_config
|
|
11
18
|
from .zoo import (
|
|
12
|
-
Glitchling,
|
|
13
|
-
Gaggle,
|
|
14
19
|
BUILTIN_GLITCHLINGS,
|
|
15
20
|
DEFAULT_GLITCHLING_NAMES,
|
|
21
|
+
Gaggle,
|
|
22
|
+
Glitchling,
|
|
16
23
|
parse_glitchling_spec,
|
|
17
24
|
summon,
|
|
18
25
|
)
|
|
@@ -20,24 +27,30 @@ from .zoo import (
|
|
|
20
27
|
MAX_NAME_WIDTH = max(len(glitchling.name) for glitchling in BUILTIN_GLITCHLINGS.values())
|
|
21
28
|
|
|
22
29
|
|
|
23
|
-
def build_parser(
|
|
30
|
+
def build_parser(
|
|
31
|
+
*,
|
|
32
|
+
exit_on_error: bool = True,
|
|
33
|
+
include_text: bool = True,
|
|
34
|
+
) -> argparse.ArgumentParser:
|
|
24
35
|
"""Create and configure the CLI argument parser.
|
|
25
36
|
|
|
26
37
|
Returns:
|
|
27
38
|
argparse.ArgumentParser: The configured argument parser instance.
|
|
28
|
-
"""
|
|
29
39
|
|
|
40
|
+
"""
|
|
30
41
|
parser = argparse.ArgumentParser(
|
|
31
42
|
description=(
|
|
32
43
|
"Summon glitchlings to corrupt text. Provide input text as an argument, "
|
|
33
44
|
"via --file, or pipe it on stdin."
|
|
34
|
-
)
|
|
35
|
-
|
|
36
|
-
parser.add_argument(
|
|
37
|
-
"text",
|
|
38
|
-
nargs="?",
|
|
39
|
-
help="Text to corrupt. If omitted, stdin is used or --sample provides fallback text.",
|
|
45
|
+
),
|
|
46
|
+
exit_on_error=exit_on_error,
|
|
40
47
|
)
|
|
48
|
+
if include_text:
|
|
49
|
+
parser.add_argument(
|
|
50
|
+
"text",
|
|
51
|
+
nargs="*",
|
|
52
|
+
help="Text to corrupt. If omitted, stdin is used or --sample provides fallback text.",
|
|
53
|
+
)
|
|
41
54
|
parser.add_argument(
|
|
42
55
|
"-g",
|
|
43
56
|
"--glitchling",
|
|
@@ -53,7 +66,7 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
53
66
|
"-s",
|
|
54
67
|
"--seed",
|
|
55
68
|
type=int,
|
|
56
|
-
default=
|
|
69
|
+
default=None,
|
|
57
70
|
help="Seed controlling deterministic corruption order (default: 151).",
|
|
58
71
|
)
|
|
59
72
|
parser.add_argument(
|
|
@@ -77,12 +90,30 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
77
90
|
action="store_true",
|
|
78
91
|
help="List available glitchlings and exit.",
|
|
79
92
|
)
|
|
93
|
+
parser.add_argument(
|
|
94
|
+
"-c",
|
|
95
|
+
"--config",
|
|
96
|
+
type=Path,
|
|
97
|
+
help="Load glitchlings from a YAML configuration file.",
|
|
98
|
+
)
|
|
99
|
+
parser.add_argument(
|
|
100
|
+
"--report",
|
|
101
|
+
"--attack",
|
|
102
|
+
dest="report_format",
|
|
103
|
+
nargs="?",
|
|
104
|
+
const="json",
|
|
105
|
+
choices=["json", "yaml", "yml"],
|
|
106
|
+
help=(
|
|
107
|
+
"Output a structured Attack report (default: json). Use --attack as an alias. "
|
|
108
|
+
"Includes tokens, token IDs, metrics, and counts."
|
|
109
|
+
),
|
|
110
|
+
)
|
|
111
|
+
|
|
80
112
|
return parser
|
|
81
113
|
|
|
82
114
|
|
|
83
115
|
def list_glitchlings() -> None:
|
|
84
116
|
"""Print information about the available built-in glitchlings."""
|
|
85
|
-
|
|
86
117
|
for key in DEFAULT_GLITCHLING_NAMES:
|
|
87
118
|
glitchling = BUILTIN_GLITCHLINGS[key]
|
|
88
119
|
display_name = glitchling.name
|
|
@@ -103,58 +134,83 @@ def read_text(args: argparse.Namespace, parser: argparse.ArgumentParser) -> str:
|
|
|
103
134
|
|
|
104
135
|
Raises:
|
|
105
136
|
SystemExit: Raised indirectly via ``parser.error`` on failure.
|
|
106
|
-
"""
|
|
107
137
|
|
|
108
|
-
|
|
138
|
+
"""
|
|
139
|
+
file_path = cast(Path | None, getattr(args, "file", None))
|
|
140
|
+
if file_path is not None:
|
|
109
141
|
try:
|
|
110
|
-
return
|
|
142
|
+
return file_path.read_text(encoding="utf-8")
|
|
111
143
|
except OSError as exc:
|
|
112
|
-
filename = getattr(exc, "filename", None) or
|
|
144
|
+
filename = getattr(exc, "filename", None) or file_path
|
|
113
145
|
reason = exc.strerror or str(exc)
|
|
114
146
|
parser.error(f"Failed to read file {filename}: {reason}")
|
|
115
147
|
|
|
116
|
-
|
|
117
|
-
|
|
148
|
+
text_argument = cast(str | list[str] | None, getattr(args, "text", None))
|
|
149
|
+
if isinstance(text_argument, list):
|
|
150
|
+
if text_argument:
|
|
151
|
+
return " ".join(text_argument)
|
|
152
|
+
text_argument = None
|
|
153
|
+
if isinstance(text_argument, str) and text_argument:
|
|
154
|
+
return text_argument
|
|
118
155
|
|
|
119
156
|
if not sys.stdin.isatty():
|
|
120
157
|
return sys.stdin.read()
|
|
121
158
|
|
|
122
|
-
if args
|
|
159
|
+
if bool(getattr(args, "sample", False)):
|
|
123
160
|
return SAMPLE_TEXT
|
|
124
161
|
|
|
125
162
|
parser.error(
|
|
126
|
-
"No input text provided. Supply text as an argument, use --file, pipe input, or
|
|
163
|
+
"No input text provided. Supply text as an argument, use --file, pipe input, or "
|
|
164
|
+
"pass --sample."
|
|
127
165
|
)
|
|
128
166
|
raise AssertionError("parser.error should exit")
|
|
129
167
|
|
|
130
168
|
|
|
131
169
|
def summon_glitchlings(
|
|
132
|
-
names: list[str] | None,
|
|
170
|
+
names: list[str] | None,
|
|
171
|
+
parser: argparse.ArgumentParser,
|
|
172
|
+
seed: int | None,
|
|
173
|
+
*,
|
|
174
|
+
config_path: Path | None = None,
|
|
133
175
|
) -> Gaggle:
|
|
134
176
|
"""Instantiate the requested glitchlings and bundle them in a ``Gaggle``."""
|
|
177
|
+
if config_path is not None:
|
|
178
|
+
if names:
|
|
179
|
+
parser.error("Cannot combine --config with --glitchling.")
|
|
180
|
+
raise AssertionError("parser.error should exit")
|
|
135
181
|
|
|
182
|
+
try:
|
|
183
|
+
config = load_attack_config(config_path)
|
|
184
|
+
except (TypeError, ValueError) as exc:
|
|
185
|
+
parser.error(str(exc))
|
|
186
|
+
raise AssertionError("parser.error should exit")
|
|
187
|
+
|
|
188
|
+
return build_gaggle(config, seed_override=seed)
|
|
189
|
+
|
|
190
|
+
normalized: Sequence[str | Glitchling]
|
|
136
191
|
if names:
|
|
137
|
-
|
|
192
|
+
parsed: list[str | Glitchling] = []
|
|
138
193
|
for specification in names:
|
|
139
194
|
try:
|
|
140
|
-
|
|
195
|
+
parsed.append(parse_glitchling_spec(specification))
|
|
141
196
|
except ValueError as exc:
|
|
142
197
|
parser.error(str(exc))
|
|
143
198
|
raise AssertionError("parser.error should exit")
|
|
199
|
+
normalized = parsed
|
|
144
200
|
else:
|
|
145
|
-
normalized = DEFAULT_GLITCHLING_NAMES
|
|
201
|
+
normalized = list(DEFAULT_GLITCHLING_NAMES)
|
|
202
|
+
|
|
203
|
+
effective_seed = seed if seed is not None else DEFAULT_ATTACK_SEED
|
|
146
204
|
|
|
147
205
|
try:
|
|
148
|
-
return summon(normalized, seed=
|
|
206
|
+
return summon(list(normalized), seed=effective_seed)
|
|
149
207
|
except ValueError as exc:
|
|
150
208
|
parser.error(str(exc))
|
|
151
209
|
raise AssertionError("parser.error should exit")
|
|
152
210
|
|
|
153
211
|
|
|
154
|
-
|
|
155
212
|
def show_diff(original: str, corrupted: str) -> None:
|
|
156
213
|
"""Display a unified diff between the original and corrupted text."""
|
|
157
|
-
|
|
158
214
|
diff_lines = list(
|
|
159
215
|
difflib.unified_diff(
|
|
160
216
|
original.splitlines(keepends=True),
|
|
@@ -180,16 +236,46 @@ def run_cli(args: argparse.Namespace, parser: argparse.ArgumentParser) -> int:
|
|
|
180
236
|
|
|
181
237
|
Returns:
|
|
182
238
|
int: Exit code for the process (``0`` on success).
|
|
183
|
-
"""
|
|
184
239
|
|
|
240
|
+
"""
|
|
185
241
|
if args.list:
|
|
186
242
|
list_glitchlings()
|
|
187
243
|
return 0
|
|
188
244
|
|
|
245
|
+
report_format = cast(str | None, getattr(args, "report_format", None))
|
|
246
|
+
if report_format and args.diff:
|
|
247
|
+
parser.error("--diff cannot be combined with --report/--attack output.")
|
|
248
|
+
raise AssertionError("parser.error should exit")
|
|
249
|
+
|
|
250
|
+
normalized_report_format = None
|
|
251
|
+
if report_format:
|
|
252
|
+
normalized_report_format = "yaml" if report_format == "yml" else report_format
|
|
253
|
+
|
|
189
254
|
text = read_text(args, parser)
|
|
190
|
-
gaggle = summon_glitchlings(
|
|
255
|
+
gaggle = summon_glitchlings(
|
|
256
|
+
args.glitchlings,
|
|
257
|
+
parser,
|
|
258
|
+
args.seed,
|
|
259
|
+
config_path=args.config,
|
|
260
|
+
)
|
|
261
|
+
|
|
262
|
+
if normalized_report_format:
|
|
263
|
+
attack_seed = args.seed if args.seed is not None else getattr(gaggle, "seed", None)
|
|
264
|
+
attack = Attack(gaggle, seed=attack_seed)
|
|
265
|
+
result = attack.run(text)
|
|
266
|
+
payload = result.to_report()
|
|
267
|
+
payload["summary"] = result.summary()
|
|
268
|
+
|
|
269
|
+
if normalized_report_format == "json":
|
|
270
|
+
print(json.dumps(payload, indent=2))
|
|
271
|
+
else:
|
|
272
|
+
print(yaml.safe_dump(payload, sort_keys=False))
|
|
273
|
+
return 0
|
|
191
274
|
|
|
192
|
-
corrupted = gaggle(text)
|
|
275
|
+
corrupted = gaggle.corrupt(text)
|
|
276
|
+
if not isinstance(corrupted, str):
|
|
277
|
+
message = "Gaggle returned non-string output for string input"
|
|
278
|
+
raise TypeError(message)
|
|
193
279
|
|
|
194
280
|
if args.diff:
|
|
195
281
|
show_diff(text, corrupted)
|
|
@@ -207,10 +293,15 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
207
293
|
|
|
208
294
|
Returns:
|
|
209
295
|
int: Exit code suitable for use with ``sys.exit``.
|
|
296
|
+
|
|
210
297
|
"""
|
|
298
|
+
if argv is None:
|
|
299
|
+
raw_args = sys.argv[1:]
|
|
300
|
+
else:
|
|
301
|
+
raw_args = list(argv)
|
|
211
302
|
|
|
212
303
|
parser = build_parser()
|
|
213
|
-
args = parser.parse_args(
|
|
304
|
+
args = parser.parse_args(raw_args)
|
|
214
305
|
return run_cli(args, parser)
|
|
215
306
|
|
|
216
307
|
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
"""Compatibility wrapper for runtime configuration helpers.
|
|
2
|
+
|
|
3
|
+
Prefer ``glitchlings.conf`` for imports.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
from .conf import (
|
|
9
|
+
CONFIG_ENV_VAR,
|
|
10
|
+
DEFAULT_CONFIG_PATH,
|
|
11
|
+
RuntimeConfig,
|
|
12
|
+
get_config,
|
|
13
|
+
reload_config,
|
|
14
|
+
reset_config,
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
__all__ = [
|
|
18
|
+
"CONFIG_ENV_VAR",
|
|
19
|
+
"DEFAULT_CONFIG_PATH",
|
|
20
|
+
"RuntimeConfig",
|
|
21
|
+
"get_config",
|
|
22
|
+
"reload_config",
|
|
23
|
+
"reset_config",
|
|
24
|
+
]
|
glitchlings/util/__init__.py
CHANGED
|
@@ -1,181 +1,34 @@
|
|
|
1
|
-
import
|
|
2
|
-
from
|
|
3
|
-
|
|
4
|
-
SAMPLE_TEXT = "One morning, when Gregor Samsa woke from troubled dreams, he found himself transformed in his bed into a horrible vermin. He lay on his armour-like back, and if he lifted his head a little he could see his brown belly, slightly domed and divided by arches into stiff sections. The bedding was hardly able to cover it and seemed ready to slide off any moment. His many legs, pitifully thin compared with the size of the rest of him, waved about helplessly as he looked."
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
def string_diffs(a: str, b: str) -> list[list[tuple[str, str, str]]]:
|
|
8
|
-
"""
|
|
9
|
-
Compare two strings using SequenceMatcher and return
|
|
10
|
-
grouped adjacent opcodes (excluding 'equal' tags).
|
|
11
|
-
|
|
12
|
-
Each element is a tuple: (tag, a_text, b_text).
|
|
13
|
-
"""
|
|
14
|
-
sm = difflib.SequenceMatcher(None, a, b)
|
|
15
|
-
ops: list[list[tuple[str, str, str]]] = []
|
|
16
|
-
buffer: list[tuple[str, str, str]] = []
|
|
17
|
-
|
|
18
|
-
for tag, i1, i2, j1, j2 in sm.get_opcodes():
|
|
19
|
-
if tag == "equal":
|
|
20
|
-
# flush any buffered operations before skipping
|
|
21
|
-
if buffer:
|
|
22
|
-
ops.append(buffer)
|
|
23
|
-
buffer = []
|
|
24
|
-
continue
|
|
25
|
-
|
|
26
|
-
# append operation to buffer
|
|
27
|
-
buffer.append((tag, a[i1:i2], b[j1:j2]))
|
|
28
|
-
|
|
29
|
-
# flush trailing buffer
|
|
30
|
-
if buffer:
|
|
31
|
-
ops.append(buffer)
|
|
32
|
-
|
|
33
|
-
return ops
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
KeyNeighborMap = dict[str, list[str]]
|
|
37
|
-
KeyboardLayouts = dict[str, KeyNeighborMap]
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
def _build_neighbor_map(rows: Iterable[str]) -> KeyNeighborMap:
|
|
41
|
-
"""Derive 8-neighbour adjacency lists from keyboard layout rows."""
|
|
42
|
-
|
|
43
|
-
grid: dict[tuple[int, int], str] = {}
|
|
44
|
-
for y, row in enumerate(rows):
|
|
45
|
-
for x, char in enumerate(row):
|
|
46
|
-
if char == " ":
|
|
47
|
-
continue
|
|
48
|
-
grid[(x, y)] = char.lower()
|
|
49
|
-
|
|
50
|
-
neighbors: KeyNeighborMap = {}
|
|
51
|
-
for (x, y), char in grid.items():
|
|
52
|
-
seen: list[str] = []
|
|
53
|
-
for dy in (-1, 0, 1):
|
|
54
|
-
for dx in (-1, 0, 1):
|
|
55
|
-
if dx == 0 and dy == 0:
|
|
56
|
-
continue
|
|
57
|
-
candidate = grid.get((x + dx, y + dy))
|
|
58
|
-
if candidate is None:
|
|
59
|
-
continue
|
|
60
|
-
seen.append(candidate)
|
|
61
|
-
# Preserve encounter order but drop duplicates for determinism
|
|
62
|
-
deduped = list(dict.fromkeys(seen))
|
|
63
|
-
neighbors[char] = deduped
|
|
64
|
-
|
|
65
|
-
return neighbors
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
_KEYNEIGHBORS: KeyboardLayouts = {
|
|
69
|
-
"CURATOR_QWERTY": {
|
|
70
|
-
"a": [*"qwsz"],
|
|
71
|
-
"b": [*"vghn "],
|
|
72
|
-
"c": [*"xdfv "],
|
|
73
|
-
"d": [*"serfcx"],
|
|
74
|
-
"e": [*"wsdrf34"],
|
|
75
|
-
"f": [*"drtgvc"],
|
|
76
|
-
"g": [*"ftyhbv"],
|
|
77
|
-
"h": [*"gyujnb"],
|
|
78
|
-
"i": [*"ujko89"],
|
|
79
|
-
"j": [*"huikmn"],
|
|
80
|
-
"k": [*"jilom,"],
|
|
81
|
-
"l": [*"kop;.,"],
|
|
82
|
-
"m": [*"njk, "],
|
|
83
|
-
"n": [*"bhjm "],
|
|
84
|
-
"o": [*"iklp90"],
|
|
85
|
-
"p": [*"o0-[;l"],
|
|
86
|
-
"q": [*"was 12"],
|
|
87
|
-
"r": [*"edft45"],
|
|
88
|
-
"s": [*"awedxz"],
|
|
89
|
-
"t": [*"r56ygf"],
|
|
90
|
-
"u": [*"y78ijh"],
|
|
91
|
-
"v": [*"cfgb "],
|
|
92
|
-
"w": [*"q23esa"],
|
|
93
|
-
"x": [*"zsdc "],
|
|
94
|
-
"y": [*"t67uhg"],
|
|
95
|
-
"z": [*"asx"],
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
def _register_layout(name: str, rows: Iterable[str]) -> None:
|
|
101
|
-
_KEYNEIGHBORS[name] = _build_neighbor_map(rows)
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
_register_layout(
|
|
105
|
-
"DVORAK",
|
|
106
|
-
(
|
|
107
|
-
"`1234567890[]\\",
|
|
108
|
-
" ',.pyfgcrl/=\\",
|
|
109
|
-
" aoeuidhtns-",
|
|
110
|
-
" ;qjkxbmwvz",
|
|
111
|
-
),
|
|
112
|
-
)
|
|
113
|
-
|
|
114
|
-
_register_layout(
|
|
115
|
-
"COLEMAK",
|
|
116
|
-
(
|
|
117
|
-
"`1234567890-=",
|
|
118
|
-
" qwfpgjluy;[]\\",
|
|
119
|
-
" arstdhneio'",
|
|
120
|
-
" zxcvbkm,./",
|
|
121
|
-
),
|
|
122
|
-
)
|
|
123
|
-
|
|
124
|
-
_register_layout(
|
|
125
|
-
"QWERTY",
|
|
126
|
-
(
|
|
127
|
-
"`1234567890-=",
|
|
128
|
-
" qwertyuiop[]\\",
|
|
129
|
-
" asdfghjkl;'",
|
|
130
|
-
" zxcvbnm,./",
|
|
131
|
-
),
|
|
1
|
+
from glitchlings.zoo.transforms import KeyNeighborMap
|
|
2
|
+
from glitchlings.zoo.transforms import (
|
|
3
|
+
compute_string_diffs as string_diffs,
|
|
132
4
|
)
|
|
133
5
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
),
|
|
6
|
+
from .keyboards import (
|
|
7
|
+
KEYNEIGHBORS,
|
|
8
|
+
SHIFT_MAPS,
|
|
9
|
+
KeyboardLayouts,
|
|
10
|
+
KeyNeighbors,
|
|
11
|
+
ShiftMap,
|
|
12
|
+
ShiftMaps,
|
|
142
13
|
)
|
|
143
14
|
|
|
144
|
-
|
|
145
|
-
"
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
15
|
+
__all__ = [
|
|
16
|
+
"SAMPLE_TEXT",
|
|
17
|
+
"string_diffs",
|
|
18
|
+
"KeyNeighborMap",
|
|
19
|
+
"KeyboardLayouts",
|
|
20
|
+
"ShiftMap",
|
|
21
|
+
"ShiftMaps",
|
|
22
|
+
"KeyNeighbors",
|
|
23
|
+
"KEYNEIGHBORS",
|
|
24
|
+
"SHIFT_MAPS",
|
|
25
|
+
]
|
|
26
|
+
|
|
27
|
+
SAMPLE_TEXT = (
|
|
28
|
+
"One morning, when Gregor Samsa woke from troubled dreams, he found himself "
|
|
29
|
+
"transformed in his bed into a horrible vermin. He lay on his armour-like back, and "
|
|
30
|
+
"if he lifted his head a little he could see his brown belly, slightly domed and "
|
|
31
|
+
"divided by arches into stiff sections. The bedding was hardly able to cover it and "
|
|
32
|
+
"seemed ready to slide off any moment. His many legs, pitifully thin compared with "
|
|
33
|
+
"the size of the rest of him, waved about helplessly as he looked."
|
|
152
34
|
)
|
|
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
|
-
|
|
174
|
-
|
|
175
|
-
class KeyNeighbors:
|
|
176
|
-
def __init__(self) -> None:
|
|
177
|
-
for layout_name, layout in _KEYNEIGHBORS.items():
|
|
178
|
-
setattr(self, layout_name, layout)
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
KEYNEIGHBORS: KeyNeighbors = KeyNeighbors()
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
"""Adapter helpers shared across Python and DLC integrations."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from collections.abc import Iterable
|
|
6
|
+
from typing import Any
|
|
7
|
+
|
|
8
|
+
from ..zoo import Gaggle, Glitchling, summon
|
|
9
|
+
from .transcripts import TranscriptTarget
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def coerce_gaggle(
|
|
13
|
+
glitchlings: Glitchling | Gaggle | str | Iterable[str | Glitchling],
|
|
14
|
+
*,
|
|
15
|
+
seed: int,
|
|
16
|
+
apply_seed_to_existing: bool = False,
|
|
17
|
+
transcript_target: TranscriptTarget | None = None,
|
|
18
|
+
) -> Gaggle:
|
|
19
|
+
"""Return a :class:`Gaggle` built from any supported glitchling specifier.
|
|
20
|
+
|
|
21
|
+
Args:
|
|
22
|
+
glitchlings: A single Glitchling, Gaggle, string specification, or iterable
|
|
23
|
+
of glitchlings/specs.
|
|
24
|
+
seed: Seed to use when constructing a new Gaggle from the input.
|
|
25
|
+
apply_seed_to_existing: When True, also apply the seed to an existing
|
|
26
|
+
Gaggle instance. When False (default), existing Gaggles keep their
|
|
27
|
+
current seed.
|
|
28
|
+
transcript_target: Which transcript turns to corrupt. When None (default),
|
|
29
|
+
uses the Gaggle default ("last"). Accepts:
|
|
30
|
+
- "last": corrupt only the last turn (default)
|
|
31
|
+
- "all": corrupt all turns
|
|
32
|
+
- "assistant": corrupt only assistant turns
|
|
33
|
+
- "user": corrupt only user turns
|
|
34
|
+
- int: corrupt a specific index (negative indexing supported)
|
|
35
|
+
- Sequence[int]: corrupt specific indices
|
|
36
|
+
"""
|
|
37
|
+
if isinstance(glitchlings, Gaggle):
|
|
38
|
+
if apply_seed_to_existing:
|
|
39
|
+
glitchlings.seed = seed
|
|
40
|
+
glitchlings.sort_glitchlings()
|
|
41
|
+
if transcript_target is not None:
|
|
42
|
+
glitchlings.transcript_target = transcript_target
|
|
43
|
+
return glitchlings
|
|
44
|
+
|
|
45
|
+
if isinstance(glitchlings, (Glitchling, str)):
|
|
46
|
+
resolved: Iterable[Any] = [glitchlings]
|
|
47
|
+
else:
|
|
48
|
+
resolved = glitchlings
|
|
49
|
+
|
|
50
|
+
# Validate entries before passing to summon to give better error messages
|
|
51
|
+
resolved_list = list(resolved)
|
|
52
|
+
for index, entry in enumerate(resolved_list):
|
|
53
|
+
if not isinstance(entry, (str, Glitchling)):
|
|
54
|
+
raise TypeError(
|
|
55
|
+
f"glitchlings sequence entries must be Glitchling instances "
|
|
56
|
+
f"or string specifications (index {index})"
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
gaggle = summon(resolved_list, seed=seed)
|
|
60
|
+
if transcript_target is not None:
|
|
61
|
+
gaggle.transcript_target = transcript_target
|
|
62
|
+
return gaggle
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
__all__ = ["coerce_gaggle"]
|