glitchlings 0.4.0__tar.gz → 0.4.2__tar.gz
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-0.4.0/src/glitchlings.egg-info → glitchlings-0.4.2}/PKG-INFO +68 -4
- {glitchlings-0.4.0 → glitchlings-0.4.2}/README.md +57 -3
- {glitchlings-0.4.0 → glitchlings-0.4.2}/pyproject.toml +74 -1
- {glitchlings-0.4.0 → glitchlings-0.4.2}/rust/zoo/src/glitch_ops.rs +7 -2
- {glitchlings-0.4.0 → glitchlings-0.4.2}/rust/zoo/src/lib.rs +66 -0
- {glitchlings-0.4.0 → glitchlings-0.4.2}/rust/zoo/src/pipeline.rs +105 -1
- {glitchlings-0.4.0 → glitchlings-0.4.2}/rust/zoo/src/rng.rs +19 -0
- {glitchlings-0.4.0 → glitchlings-0.4.2}/rust/zoo/src/text_buffer.rs +39 -0
- {glitchlings-0.4.0 → glitchlings-0.4.2}/src/glitchlings/__init__.py +26 -17
- {glitchlings-0.4.0 → glitchlings-0.4.2}/src/glitchlings/__main__.py +0 -1
- glitchlings-0.4.2/src/glitchlings/compat.py +215 -0
- {glitchlings-0.4.0 → glitchlings-0.4.2}/src/glitchlings/config.py +136 -19
- glitchlings-0.4.2/src/glitchlings/dlc/_shared.py +68 -0
- {glitchlings-0.4.0 → glitchlings-0.4.2}/src/glitchlings/dlc/huggingface.py +26 -41
- {glitchlings-0.4.0 → glitchlings-0.4.2}/src/glitchlings/dlc/prime.py +64 -101
- {glitchlings-0.4.0 → glitchlings-0.4.2}/src/glitchlings/lexicon/__init__.py +26 -19
- glitchlings-0.4.2/src/glitchlings/lexicon/_cache.py +104 -0
- {glitchlings-0.4.0 → glitchlings-0.4.2}/src/glitchlings/lexicon/graph.py +18 -39
- {glitchlings-0.4.0 → glitchlings-0.4.2}/src/glitchlings/lexicon/metrics.py +1 -8
- {glitchlings-0.4.0 → glitchlings-0.4.2}/src/glitchlings/lexicon/vector.py +29 -67
- {glitchlings-0.4.0 → glitchlings-0.4.2}/src/glitchlings/lexicon/wordnet.py +39 -30
- {glitchlings-0.4.0 → glitchlings-0.4.2}/src/glitchlings/main.py +9 -13
- {glitchlings-0.4.0 → glitchlings-0.4.2}/src/glitchlings/util/__init__.py +18 -4
- glitchlings-0.4.2/src/glitchlings/util/adapters.py +27 -0
- {glitchlings-0.4.0 → glitchlings-0.4.2}/src/glitchlings/zoo/__init__.py +21 -14
- {glitchlings-0.4.0 → glitchlings-0.4.2}/src/glitchlings/zoo/_ocr_confusions.py +1 -3
- {glitchlings-0.4.0 → glitchlings-0.4.2}/src/glitchlings/zoo/_rate.py +1 -4
- {glitchlings-0.4.0 → glitchlings-0.4.2}/src/glitchlings/zoo/_sampling.py +0 -1
- {glitchlings-0.4.0 → glitchlings-0.4.2}/src/glitchlings/zoo/_text_utils.py +1 -5
- {glitchlings-0.4.0 → glitchlings-0.4.2}/src/glitchlings/zoo/adjax.py +0 -2
- {glitchlings-0.4.0 → glitchlings-0.4.2}/src/glitchlings/zoo/core.py +185 -56
- {glitchlings-0.4.0 → glitchlings-0.4.2}/src/glitchlings/zoo/jargoyle.py +9 -14
- {glitchlings-0.4.0 → glitchlings-0.4.2}/src/glitchlings/zoo/mim1c.py +11 -10
- {glitchlings-0.4.0 → glitchlings-0.4.2}/src/glitchlings/zoo/redactyl.py +5 -8
- {glitchlings-0.4.0 → glitchlings-0.4.2}/src/glitchlings/zoo/reduple.py +3 -1
- {glitchlings-0.4.0 → glitchlings-0.4.2}/src/glitchlings/zoo/rushmore.py +2 -8
- {glitchlings-0.4.0 → glitchlings-0.4.2}/src/glitchlings/zoo/scannequin.py +5 -4
- {glitchlings-0.4.0 → glitchlings-0.4.2}/src/glitchlings/zoo/typogre.py +3 -7
- {glitchlings-0.4.0 → glitchlings-0.4.2}/src/glitchlings/zoo/zeedub.py +2 -2
- {glitchlings-0.4.0 → glitchlings-0.4.2/src/glitchlings.egg-info}/PKG-INFO +68 -4
- {glitchlings-0.4.0 → glitchlings-0.4.2}/src/glitchlings.egg-info/SOURCES.txt +5 -21
- {glitchlings-0.4.0 → glitchlings-0.4.2}/src/glitchlings.egg-info/requires.txt +10 -0
- glitchlings-0.4.0/tests/test_benchmarks.py +0 -88
- glitchlings-0.4.0/tests/test_cli.py +0 -306
- glitchlings-0.4.0/tests/test_config.py +0 -59
- glitchlings-0.4.0/tests/test_dataset_corruption.py +0 -128
- glitchlings-0.4.0/tests/test_gaggle.py +0 -46
- glitchlings-0.4.0/tests/test_glitchling_core.py +0 -68
- glitchlings-0.4.0/tests/test_glitchlings_determinism.py +0 -103
- glitchlings-0.4.0/tests/test_graph_lexicon.py +0 -70
- glitchlings-0.4.0/tests/test_huggingface_dlc.py +0 -58
- glitchlings-0.4.0/tests/test_jargoyle.py +0 -209
- glitchlings-0.4.0/tests/test_keyboard_layouts.py +0 -42
- glitchlings-0.4.0/tests/test_lexicon_config.py +0 -56
- glitchlings-0.4.0/tests/test_lexicon_metrics.py +0 -120
- glitchlings-0.4.0/tests/test_parameter_effects.py +0 -281
- glitchlings-0.4.0/tests/test_prime_echo_chamber.py +0 -308
- glitchlings-0.4.0/tests/test_property_based.py +0 -150
- glitchlings-0.4.0/tests/test_rust_backed_glitchlings.py +0 -679
- glitchlings-0.4.0/tests/test_text_utils.py +0 -37
- glitchlings-0.4.0/tests/test_util.py +0 -35
- glitchlings-0.4.0/tests/test_vector_lexicon.py +0 -193
- {glitchlings-0.4.0 → glitchlings-0.4.2}/LICENSE +0 -0
- {glitchlings-0.4.0 → glitchlings-0.4.2}/MANIFEST.in +0 -0
- {glitchlings-0.4.0 → glitchlings-0.4.2}/rust/Cargo.lock +0 -0
- {glitchlings-0.4.0 → glitchlings-0.4.2}/rust/Cargo.toml +0 -0
- {glitchlings-0.4.0 → glitchlings-0.4.2}/rust/zoo/Cargo.toml +0 -0
- {glitchlings-0.4.0 → glitchlings-0.4.2}/rust/zoo/assets/ocr_confusions.tsv +0 -0
- {glitchlings-0.4.0 → glitchlings-0.4.2}/rust/zoo/build.rs +0 -0
- {glitchlings-0.4.0 → glitchlings-0.4.2}/rust/zoo/src/resources.rs +0 -0
- {glitchlings-0.4.0 → glitchlings-0.4.2}/rust/zoo/src/typogre.rs +0 -0
- {glitchlings-0.4.0 → glitchlings-0.4.2}/rust/zoo/src/zeedub.rs +0 -0
- {glitchlings-0.4.0 → glitchlings-0.4.2}/setup.cfg +0 -0
- {glitchlings-0.4.0 → glitchlings-0.4.2}/src/glitchlings/config.toml +0 -0
- {glitchlings-0.4.0 → glitchlings-0.4.2}/src/glitchlings/dlc/__init__.py +0 -0
- {glitchlings-0.4.0 → glitchlings-0.4.2}/src/glitchlings/lexicon/data/default_vector_cache.json +0 -0
- {glitchlings-0.4.0 → glitchlings-0.4.2}/src/glitchlings/zoo/ocr_confusions.tsv +0 -0
- {glitchlings-0.4.0 → glitchlings-0.4.2}/src/glitchlings.egg-info/dependency_links.txt +0 -0
- {glitchlings-0.4.0 → glitchlings-0.4.2}/src/glitchlings.egg-info/entry_points.txt +0 -0
- {glitchlings-0.4.0 → glitchlings-0.4.2}/src/glitchlings.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: glitchlings
|
|
3
|
-
Version: 0.4.
|
|
3
|
+
Version: 0.4.2
|
|
4
4
|
Summary: Monsters for your language games.
|
|
5
5
|
Author: osoleve
|
|
6
6
|
License: Apache License
|
|
@@ -239,6 +239,16 @@ Provides-Extra: dev
|
|
|
239
239
|
Requires-Dist: pytest>=8.0.0; extra == "dev"
|
|
240
240
|
Requires-Dist: hypothesis>=6.140.0; extra == "dev"
|
|
241
241
|
Requires-Dist: numpy<=2.0,>=1.24; extra == "dev"
|
|
242
|
+
Requires-Dist: mkdocs>=1.6.0; extra == "dev"
|
|
243
|
+
Requires-Dist: mkdocstrings[python]>=0.24.0; extra == "dev"
|
|
244
|
+
Requires-Dist: mkdocs-material>=9.5.0; extra == "dev"
|
|
245
|
+
Requires-Dist: mkdocstrings-python>=1.10.0; extra == "dev"
|
|
246
|
+
Requires-Dist: interrogate>=1.5.0; extra == "dev"
|
|
247
|
+
Requires-Dist: black>=24.4.0; extra == "dev"
|
|
248
|
+
Requires-Dist: isort>=5.13.0; extra == "dev"
|
|
249
|
+
Requires-Dist: ruff>=0.6.0; extra == "dev"
|
|
250
|
+
Requires-Dist: mypy>=1.8.0; extra == "dev"
|
|
251
|
+
Requires-Dist: pre-commit>=3.8.0; extra == "dev"
|
|
242
252
|
Dynamic: license-file
|
|
243
253
|
|
|
244
254
|
#
|
|
@@ -338,10 +348,66 @@ They're horrible little gremlins, but they're not _unreasonable_.
|
|
|
338
348
|
|
|
339
349
|
Keyboard warriors can challenge them directly via the `glitchlings` command:
|
|
340
350
|
|
|
351
|
+
<!-- BEGIN: CLI_USAGE -->
|
|
341
352
|
```bash
|
|
342
353
|
# Discover which glitchlings are currently on the loose.
|
|
343
354
|
glitchlings --list
|
|
355
|
+
```
|
|
356
|
+
|
|
357
|
+
```text
|
|
358
|
+
Typogre — scope: Character, order: early
|
|
359
|
+
Mim1c — scope: Character, order: last
|
|
360
|
+
Jargoyle — scope: Word, order: normal
|
|
361
|
+
Adjax — scope: Word, order: normal
|
|
362
|
+
Reduple — scope: Word, order: normal
|
|
363
|
+
Rushmore — scope: Word, order: normal
|
|
364
|
+
Redactyl — scope: Word, order: normal
|
|
365
|
+
Scannequin — scope: Character, order: late
|
|
366
|
+
Zeedub — scope: Character, order: last
|
|
367
|
+
```
|
|
368
|
+
|
|
369
|
+
```bash
|
|
370
|
+
# Review the full CLI contract.
|
|
371
|
+
glitchlings --help
|
|
372
|
+
```
|
|
344
373
|
|
|
374
|
+
```text
|
|
375
|
+
usage: glitchlings [-h] [-g SPEC] [-s SEED] [-f FILE] [--sample] [--diff]
|
|
376
|
+
[--list] [-c CONFIG]
|
|
377
|
+
[text]
|
|
378
|
+
|
|
379
|
+
Summon glitchlings to corrupt text. Provide input text as an argument, via
|
|
380
|
+
--file, or pipe it on stdin.
|
|
381
|
+
|
|
382
|
+
positional arguments:
|
|
383
|
+
text Text to corrupt. If omitted, stdin is used or --sample
|
|
384
|
+
provides fallback text.
|
|
385
|
+
|
|
386
|
+
options:
|
|
387
|
+
-h, --help show this help message and exit
|
|
388
|
+
-g SPEC, --glitchling SPEC
|
|
389
|
+
Glitchling to apply, optionally with parameters like
|
|
390
|
+
Typogre(rate=0.05). Repeat for multiples; defaults to
|
|
391
|
+
all built-ins.
|
|
392
|
+
-s SEED, --seed SEED Seed controlling deterministic corruption order
|
|
393
|
+
(default: 151).
|
|
394
|
+
-f FILE, --file FILE Read input text from a file instead of the command
|
|
395
|
+
line argument.
|
|
396
|
+
--sample Use the included SAMPLE_TEXT when no other input is
|
|
397
|
+
provided.
|
|
398
|
+
--diff Show a unified diff between the original and corrupted
|
|
399
|
+
text.
|
|
400
|
+
--list List available glitchlings and exit.
|
|
401
|
+
-c CONFIG, --config CONFIG
|
|
402
|
+
Load glitchlings from a YAML configuration file.
|
|
403
|
+
```
|
|
404
|
+
<!-- END: CLI_USAGE -->
|
|
405
|
+
|
|
406
|
+
Run `python docs/build_cli_reference.py` whenever you tweak the CLI so the README stays in sync with the actual output. The script executes the commands above and replaces the block between the markers automatically.
|
|
407
|
+
|
|
408
|
+
Prefer inline tweaks? You can still configure glitchlings directly in the shell:
|
|
409
|
+
|
|
410
|
+
```bash
|
|
345
411
|
# Run Typogre against the contents of a file and inspect the diff.
|
|
346
412
|
glitchlings -g typogre --file documents/report.txt --diff
|
|
347
413
|
|
|
@@ -355,8 +421,6 @@ echo "Beware LLM-written flavor-text" | glitchlings -g mim1c
|
|
|
355
421
|
glitchlings --config experiments/chaos.yaml "Let slips the glitchlings of war"
|
|
356
422
|
```
|
|
357
423
|
|
|
358
|
-
Use `--help` for a complete breakdown of available options, including support for parameterised glitchlings via `-g "Name(arg=value, ...)"` to mirror the Python API.
|
|
359
|
-
|
|
360
424
|
Attack configurations live in plain YAML files so you can version-control experiments without touching code:
|
|
361
425
|
|
|
362
426
|
```yaml
|
|
@@ -420,7 +484,7 @@ _How can a computer need reading glasses?_
|
|
|
420
484
|
|
|
421
485
|
### Zeedub
|
|
422
486
|
|
|
423
|
-
|
|
487
|
+
_Watch your step around here._
|
|
424
488
|
|
|
425
489
|
> _**Invisible Ink.**_ Zeedub slips zero-width codepoints between non-space character pairs, forcing models to reason about text whose visible form masks hidden glyphs.
|
|
426
490
|
>
|
|
@@ -95,10 +95,66 @@ They're horrible little gremlins, but they're not _unreasonable_.
|
|
|
95
95
|
|
|
96
96
|
Keyboard warriors can challenge them directly via the `glitchlings` command:
|
|
97
97
|
|
|
98
|
+
<!-- BEGIN: CLI_USAGE -->
|
|
98
99
|
```bash
|
|
99
100
|
# Discover which glitchlings are currently on the loose.
|
|
100
101
|
glitchlings --list
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
```text
|
|
105
|
+
Typogre — scope: Character, order: early
|
|
106
|
+
Mim1c — scope: Character, order: last
|
|
107
|
+
Jargoyle — scope: Word, order: normal
|
|
108
|
+
Adjax — scope: Word, order: normal
|
|
109
|
+
Reduple — scope: Word, order: normal
|
|
110
|
+
Rushmore — scope: Word, order: normal
|
|
111
|
+
Redactyl — scope: Word, order: normal
|
|
112
|
+
Scannequin — scope: Character, order: late
|
|
113
|
+
Zeedub — scope: Character, order: last
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
```bash
|
|
117
|
+
# Review the full CLI contract.
|
|
118
|
+
glitchlings --help
|
|
119
|
+
```
|
|
101
120
|
|
|
121
|
+
```text
|
|
122
|
+
usage: glitchlings [-h] [-g SPEC] [-s SEED] [-f FILE] [--sample] [--diff]
|
|
123
|
+
[--list] [-c CONFIG]
|
|
124
|
+
[text]
|
|
125
|
+
|
|
126
|
+
Summon glitchlings to corrupt text. Provide input text as an argument, via
|
|
127
|
+
--file, or pipe it on stdin.
|
|
128
|
+
|
|
129
|
+
positional arguments:
|
|
130
|
+
text Text to corrupt. If omitted, stdin is used or --sample
|
|
131
|
+
provides fallback text.
|
|
132
|
+
|
|
133
|
+
options:
|
|
134
|
+
-h, --help show this help message and exit
|
|
135
|
+
-g SPEC, --glitchling SPEC
|
|
136
|
+
Glitchling to apply, optionally with parameters like
|
|
137
|
+
Typogre(rate=0.05). Repeat for multiples; defaults to
|
|
138
|
+
all built-ins.
|
|
139
|
+
-s SEED, --seed SEED Seed controlling deterministic corruption order
|
|
140
|
+
(default: 151).
|
|
141
|
+
-f FILE, --file FILE Read input text from a file instead of the command
|
|
142
|
+
line argument.
|
|
143
|
+
--sample Use the included SAMPLE_TEXT when no other input is
|
|
144
|
+
provided.
|
|
145
|
+
--diff Show a unified diff between the original and corrupted
|
|
146
|
+
text.
|
|
147
|
+
--list List available glitchlings and exit.
|
|
148
|
+
-c CONFIG, --config CONFIG
|
|
149
|
+
Load glitchlings from a YAML configuration file.
|
|
150
|
+
```
|
|
151
|
+
<!-- END: CLI_USAGE -->
|
|
152
|
+
|
|
153
|
+
Run `python docs/build_cli_reference.py` whenever you tweak the CLI so the README stays in sync with the actual output. The script executes the commands above and replaces the block between the markers automatically.
|
|
154
|
+
|
|
155
|
+
Prefer inline tweaks? You can still configure glitchlings directly in the shell:
|
|
156
|
+
|
|
157
|
+
```bash
|
|
102
158
|
# Run Typogre against the contents of a file and inspect the diff.
|
|
103
159
|
glitchlings -g typogre --file documents/report.txt --diff
|
|
104
160
|
|
|
@@ -112,8 +168,6 @@ echo "Beware LLM-written flavor-text" | glitchlings -g mim1c
|
|
|
112
168
|
glitchlings --config experiments/chaos.yaml "Let slips the glitchlings of war"
|
|
113
169
|
```
|
|
114
170
|
|
|
115
|
-
Use `--help` for a complete breakdown of available options, including support for parameterised glitchlings via `-g "Name(arg=value, ...)"` to mirror the Python API.
|
|
116
|
-
|
|
117
171
|
Attack configurations live in plain YAML files so you can version-control experiments without touching code:
|
|
118
172
|
|
|
119
173
|
```yaml
|
|
@@ -177,7 +231,7 @@ _How can a computer need reading glasses?_
|
|
|
177
231
|
|
|
178
232
|
### Zeedub
|
|
179
233
|
|
|
180
|
-
|
|
234
|
+
_Watch your step around here._
|
|
181
235
|
|
|
182
236
|
> _**Invisible Ink.**_ Zeedub slips zero-width codepoints between non-space character pairs, forcing models to reason about text whose visible form masks hidden glyphs.
|
|
183
237
|
>
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "glitchlings"
|
|
3
|
-
version = "0.4.
|
|
3
|
+
version = "0.4.2"
|
|
4
4
|
description = "Monsters for your language games."
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
requires-python = ">=3.10"
|
|
@@ -56,6 +56,16 @@ dev = [
|
|
|
56
56
|
"pytest>=8.0.0",
|
|
57
57
|
"hypothesis>=6.140.0",
|
|
58
58
|
"numpy>=1.24,<=2.0",
|
|
59
|
+
"mkdocs>=1.6.0",
|
|
60
|
+
"mkdocstrings[python]>=0.24.0",
|
|
61
|
+
"mkdocs-material>=9.5.0",
|
|
62
|
+
"mkdocstrings-python>=1.10.0",
|
|
63
|
+
"interrogate>=1.5.0",
|
|
64
|
+
"black>=24.4.0",
|
|
65
|
+
"isort>=5.13.0",
|
|
66
|
+
"ruff>=0.6.0",
|
|
67
|
+
"mypy>=1.8.0",
|
|
68
|
+
"pre-commit>=3.8.0",
|
|
59
69
|
]
|
|
60
70
|
|
|
61
71
|
[build-system]
|
|
@@ -85,3 +95,66 @@ debug = false
|
|
|
85
95
|
pythonpath = [
|
|
86
96
|
"src",
|
|
87
97
|
]
|
|
98
|
+
|
|
99
|
+
[tool.interrogate]
|
|
100
|
+
config = true
|
|
101
|
+
fail-under = 80
|
|
102
|
+
ignore-init-module = true
|
|
103
|
+
ignore-module = true
|
|
104
|
+
ignore-nested-functions = true
|
|
105
|
+
ignore-private = true
|
|
106
|
+
ignore-semiprivate = true
|
|
107
|
+
ignore-magic = true
|
|
108
|
+
ignore-property-decorators = false
|
|
109
|
+
color = true
|
|
110
|
+
quiet = false
|
|
111
|
+
exclude = [
|
|
112
|
+
"tests",
|
|
113
|
+
"docs",
|
|
114
|
+
"rust",
|
|
115
|
+
"benchmarks",
|
|
116
|
+
]
|
|
117
|
+
|
|
118
|
+
[tool.black]
|
|
119
|
+
line-length = 100
|
|
120
|
+
target-version = ["py310"]
|
|
121
|
+
|
|
122
|
+
[tool.isort]
|
|
123
|
+
profile = "black"
|
|
124
|
+
line_length = 100
|
|
125
|
+
|
|
126
|
+
[tool.ruff]
|
|
127
|
+
target-version = "py310"
|
|
128
|
+
line-length = 100
|
|
129
|
+
|
|
130
|
+
[tool.ruff.lint]
|
|
131
|
+
select = ["E", "F", "I"]
|
|
132
|
+
|
|
133
|
+
[tool.mypy]
|
|
134
|
+
python_version = "3.10"
|
|
135
|
+
follow_imports = "skip"
|
|
136
|
+
ignore_missing_imports = true
|
|
137
|
+
enable_error_code = ["ignore-without-code"]
|
|
138
|
+
|
|
139
|
+
[[tool.mypy.overrides]]
|
|
140
|
+
module = [
|
|
141
|
+
"glitchlings.util.adapters",
|
|
142
|
+
"glitchlings.dlc._shared",
|
|
143
|
+
"glitchlings.dlc.huggingface",
|
|
144
|
+
"glitchlings.dlc.prime",
|
|
145
|
+
]
|
|
146
|
+
strict = true
|
|
147
|
+
|
|
148
|
+
[[tool.mypy.overrides]]
|
|
149
|
+
module = [
|
|
150
|
+
"glitchlings.compat",
|
|
151
|
+
"glitchlings.config",
|
|
152
|
+
"glitchlings.lexicon",
|
|
153
|
+
"glitchlings.lexicon.*",
|
|
154
|
+
"glitchlings.zoo",
|
|
155
|
+
"glitchlings.zoo.*",
|
|
156
|
+
"glitchlings.main",
|
|
157
|
+
"glitchlings.__main__",
|
|
158
|
+
"glitchlings.__init__",
|
|
159
|
+
]
|
|
160
|
+
ignore_errors = true
|
|
@@ -398,6 +398,7 @@ impl GlitchOp for SwapAdjacentWordsOp {
|
|
|
398
398
|
}
|
|
399
399
|
|
|
400
400
|
let mut index = 0usize;
|
|
401
|
+
let mut replacements: SmallVec<[(usize, String); 8]> = SmallVec::new();
|
|
401
402
|
while index + 1 < total_words {
|
|
402
403
|
let left_segment = match buffer.word_segment(index) {
|
|
403
404
|
Some(segment) => segment,
|
|
@@ -423,13 +424,17 @@ impl GlitchOp for SwapAdjacentWordsOp {
|
|
|
423
424
|
if should_swap {
|
|
424
425
|
let left_replacement = format!("{left_prefix}{right_core}{left_suffix}");
|
|
425
426
|
let right_replacement = format!("{right_prefix}{left_core}{right_suffix}");
|
|
426
|
-
|
|
427
|
-
|
|
427
|
+
replacements.push((index, left_replacement));
|
|
428
|
+
replacements.push((index + 1, right_replacement));
|
|
428
429
|
}
|
|
429
430
|
|
|
430
431
|
index += 2;
|
|
431
432
|
}
|
|
432
433
|
|
|
434
|
+
if !replacements.is_empty() {
|
|
435
|
+
buffer.replace_words_bulk(replacements.into_iter())?;
|
|
436
|
+
}
|
|
437
|
+
|
|
433
438
|
Ok(())
|
|
434
439
|
}
|
|
435
440
|
}
|
|
@@ -122,6 +122,47 @@ fn cached_layout_vec(layout_dict: &PyDict) -> PyResult<Arc<Vec<(String, Vec<Stri
|
|
|
122
122
|
Ok(entry.clone())
|
|
123
123
|
}
|
|
124
124
|
|
|
125
|
+
#[derive(Debug)]
|
|
126
|
+
struct PyGagglePlanInput {
|
|
127
|
+
name: String,
|
|
128
|
+
scope: i32,
|
|
129
|
+
order: i32,
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
impl<'py> FromPyObject<'py> for PyGagglePlanInput {
|
|
133
|
+
fn extract(obj: &'py PyAny) -> PyResult<Self> {
|
|
134
|
+
if let Ok(dict) = obj.downcast::<PyDict>() {
|
|
135
|
+
let name: String = dict
|
|
136
|
+
.get_item("name")?
|
|
137
|
+
.ok_or_else(|| PyValueError::new_err("plan input missing 'name' field"))?
|
|
138
|
+
.extract()?;
|
|
139
|
+
let scope: i32 = dict
|
|
140
|
+
.get_item("scope")?
|
|
141
|
+
.ok_or_else(|| PyValueError::new_err("plan input missing 'scope' field"))?
|
|
142
|
+
.extract()?;
|
|
143
|
+
let order: i32 = dict
|
|
144
|
+
.get_item("order")?
|
|
145
|
+
.ok_or_else(|| PyValueError::new_err("plan input missing 'order' field"))?
|
|
146
|
+
.extract()?;
|
|
147
|
+
return Ok(Self { name, scope, order });
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
let name = obj
|
|
151
|
+
.getattr("name")
|
|
152
|
+
.map_err(|_| PyValueError::new_err("plan input missing attribute 'name'"))?
|
|
153
|
+
.extract()?;
|
|
154
|
+
let scope = obj
|
|
155
|
+
.getattr("scope")
|
|
156
|
+
.map_err(|_| PyValueError::new_err("plan input missing attribute 'scope'"))?
|
|
157
|
+
.extract()?;
|
|
158
|
+
let order = obj
|
|
159
|
+
.getattr("order")
|
|
160
|
+
.map_err(|_| PyValueError::new_err("plan input missing attribute 'order'"))?
|
|
161
|
+
.extract()?;
|
|
162
|
+
Ok(Self { name, scope, order })
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
125
166
|
#[derive(Debug)]
|
|
126
167
|
enum PyGlitchOperation {
|
|
127
168
|
Reduplicate {
|
|
@@ -346,6 +387,30 @@ fn redact_words(
|
|
|
346
387
|
apply_operation(text, op, rng).map_err(glitch_ops::GlitchOpError::into_pyerr)
|
|
347
388
|
}
|
|
348
389
|
|
|
390
|
+
#[pyfunction]
|
|
391
|
+
fn plan_glitchlings(
|
|
392
|
+
glitchlings: Vec<PyGagglePlanInput>,
|
|
393
|
+
master_seed: i128,
|
|
394
|
+
) -> PyResult<Vec<(usize, u64)>> {
|
|
395
|
+
let plan = pipeline::plan_gaggle(
|
|
396
|
+
glitchlings
|
|
397
|
+
.into_iter()
|
|
398
|
+
.enumerate()
|
|
399
|
+
.map(|(index, input)| pipeline::GagglePlanInput {
|
|
400
|
+
index,
|
|
401
|
+
name: input.name,
|
|
402
|
+
scope: input.scope,
|
|
403
|
+
order: input.order,
|
|
404
|
+
})
|
|
405
|
+
.collect(),
|
|
406
|
+
master_seed,
|
|
407
|
+
);
|
|
408
|
+
Ok(plan
|
|
409
|
+
.into_iter()
|
|
410
|
+
.map(|entry| (entry.index, entry.seed))
|
|
411
|
+
.collect())
|
|
412
|
+
}
|
|
413
|
+
|
|
349
414
|
#[pyfunction]
|
|
350
415
|
fn compose_glitchlings(
|
|
351
416
|
text: &str,
|
|
@@ -418,6 +483,7 @@ fn _zoo_rust(_py: Python, m: &Bound<'_, PyModule>) -> PyResult<()> {
|
|
|
418
483
|
m.add_function(wrap_pyfunction!(swap_adjacent_words, m)?)?;
|
|
419
484
|
m.add_function(wrap_pyfunction!(ocr_artifacts, m)?)?;
|
|
420
485
|
m.add_function(wrap_pyfunction!(redact_words, m)?)?;
|
|
486
|
+
m.add_function(wrap_pyfunction!(plan_glitchlings, m)?)?;
|
|
421
487
|
m.add_function(wrap_pyfunction!(compose_glitchlings, m)?)?;
|
|
422
488
|
m.add_function(wrap_pyfunction!(typogre::fatfinger, m)?)?;
|
|
423
489
|
m.add_function(wrap_pyfunction!(zeedub::inject_zero_widths, m)?)?;
|
|
@@ -68,6 +68,57 @@ impl Pipeline {
|
|
|
68
68
|
}
|
|
69
69
|
}
|
|
70
70
|
|
|
71
|
+
#[derive(Debug, Clone, PartialEq, Eq)]
|
|
72
|
+
pub struct GagglePlanEntry {
|
|
73
|
+
pub index: usize,
|
|
74
|
+
pub seed: u64,
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
#[derive(Debug, Clone)]
|
|
78
|
+
pub struct GagglePlanInput {
|
|
79
|
+
pub index: usize,
|
|
80
|
+
pub name: String,
|
|
81
|
+
pub scope: i32,
|
|
82
|
+
pub order: i32,
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
struct PlannedGlitchling {
|
|
86
|
+
index: usize,
|
|
87
|
+
name: String,
|
|
88
|
+
scope: i32,
|
|
89
|
+
order: i32,
|
|
90
|
+
seed: u64,
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
pub fn plan_gaggle(inputs: Vec<GagglePlanInput>, master_seed: i128) -> Vec<GagglePlanEntry> {
|
|
94
|
+
let mut planned: Vec<PlannedGlitchling> = inputs
|
|
95
|
+
.into_iter()
|
|
96
|
+
.map(|input| PlannedGlitchling {
|
|
97
|
+
seed: derive_seed(master_seed, &input.name, input.index as i128),
|
|
98
|
+
index: input.index,
|
|
99
|
+
name: input.name,
|
|
100
|
+
scope: input.scope,
|
|
101
|
+
order: input.order,
|
|
102
|
+
})
|
|
103
|
+
.collect();
|
|
104
|
+
|
|
105
|
+
planned.sort_by(|left, right| {
|
|
106
|
+
left.scope
|
|
107
|
+
.cmp(&right.scope)
|
|
108
|
+
.then(left.order.cmp(&right.order))
|
|
109
|
+
.then(left.name.cmp(&right.name))
|
|
110
|
+
.then(left.index.cmp(&right.index))
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
planned
|
|
114
|
+
.into_iter()
|
|
115
|
+
.map(|item| GagglePlanEntry {
|
|
116
|
+
index: item.index,
|
|
117
|
+
seed: item.seed,
|
|
118
|
+
})
|
|
119
|
+
.collect()
|
|
120
|
+
}
|
|
121
|
+
|
|
71
122
|
pub fn derive_seed(master_seed: i128, glitchling_name: &str, index: i128) -> u64 {
|
|
72
123
|
let mut hasher = Blake2s::<U8>::new();
|
|
73
124
|
Digest::update(&mut hasher, int_to_bytes(master_seed));
|
|
@@ -109,7 +160,9 @@ fn int_to_bytes(value: i128) -> Vec<u8> {
|
|
|
109
160
|
|
|
110
161
|
#[cfg(test)]
|
|
111
162
|
mod tests {
|
|
112
|
-
use super::{
|
|
163
|
+
use super::{
|
|
164
|
+
derive_seed, plan_gaggle, GagglePlanEntry, GagglePlanInput, GlitchDescriptor, Pipeline,
|
|
165
|
+
};
|
|
113
166
|
use crate::glitch_ops::{
|
|
114
167
|
DeleteRandomWordsOp, GlitchOperation, OcrArtifactsOp, RedactWordsOp, ReduplicateWordsOp,
|
|
115
168
|
SwapAdjacentWordsOp,
|
|
@@ -222,4 +275,55 @@ mod tests {
|
|
|
222
275
|
.expect("pipeline succeeds");
|
|
223
276
|
assert_eq!(output, "this Echo please line");
|
|
224
277
|
}
|
|
278
|
+
|
|
279
|
+
#[test]
|
|
280
|
+
fn plan_gaggle_orders_by_scope_order_and_name() {
|
|
281
|
+
let master_seed = 5151i128;
|
|
282
|
+
let inputs = vec![
|
|
283
|
+
GagglePlanInput {
|
|
284
|
+
index: 0,
|
|
285
|
+
name: "Typogre".to_string(),
|
|
286
|
+
scope: 5,
|
|
287
|
+
order: 3,
|
|
288
|
+
},
|
|
289
|
+
GagglePlanInput {
|
|
290
|
+
index: 1,
|
|
291
|
+
name: "Reduple".to_string(),
|
|
292
|
+
scope: 4,
|
|
293
|
+
order: 3,
|
|
294
|
+
},
|
|
295
|
+
GagglePlanInput {
|
|
296
|
+
index: 2,
|
|
297
|
+
name: "Rushmore".to_string(),
|
|
298
|
+
scope: 4,
|
|
299
|
+
order: 2,
|
|
300
|
+
},
|
|
301
|
+
GagglePlanInput {
|
|
302
|
+
index: 3,
|
|
303
|
+
name: "Mim1c".to_string(),
|
|
304
|
+
scope: 5,
|
|
305
|
+
order: 2,
|
|
306
|
+
},
|
|
307
|
+
];
|
|
308
|
+
let plan = plan_gaggle(inputs, master_seed);
|
|
309
|
+
let expected = vec![
|
|
310
|
+
GagglePlanEntry {
|
|
311
|
+
index: 2,
|
|
312
|
+
seed: derive_seed(master_seed, "Rushmore", 2),
|
|
313
|
+
},
|
|
314
|
+
GagglePlanEntry {
|
|
315
|
+
index: 1,
|
|
316
|
+
seed: derive_seed(master_seed, "Reduple", 1),
|
|
317
|
+
},
|
|
318
|
+
GagglePlanEntry {
|
|
319
|
+
index: 3,
|
|
320
|
+
seed: derive_seed(master_seed, "Mim1c", 3),
|
|
321
|
+
},
|
|
322
|
+
GagglePlanEntry {
|
|
323
|
+
index: 0,
|
|
324
|
+
seed: derive_seed(master_seed, "Typogre", 0),
|
|
325
|
+
},
|
|
326
|
+
];
|
|
327
|
+
assert_eq!(plan, expected);
|
|
328
|
+
}
|
|
225
329
|
}
|
|
@@ -323,6 +323,25 @@ mod tests {
|
|
|
323
323
|
}
|
|
324
324
|
}
|
|
325
325
|
|
|
326
|
+
#[test]
|
|
327
|
+
fn random_matches_python_for_additional_seed() {
|
|
328
|
+
let mut rng = PyRng::new(3815924951222172525);
|
|
329
|
+
let expected = [
|
|
330
|
+
0.18518006574496737,
|
|
331
|
+
0.5841689581060610,
|
|
332
|
+
0.3699113163178772,
|
|
333
|
+
0.7394349068470196,
|
|
334
|
+
0.6855497906317899,
|
|
335
|
+
];
|
|
336
|
+
for value in expected {
|
|
337
|
+
let actual = rng.random();
|
|
338
|
+
assert!(
|
|
339
|
+
(actual - value).abs() < 1e-15,
|
|
340
|
+
"expected {value}, got {actual}"
|
|
341
|
+
);
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
|
|
326
345
|
#[test]
|
|
327
346
|
fn randrange_supports_default_arguments() {
|
|
328
347
|
let mut rng = PyRng::new(151);
|
|
@@ -171,6 +171,32 @@ impl TextBuffer {
|
|
|
171
171
|
Ok(())
|
|
172
172
|
}
|
|
173
173
|
|
|
174
|
+
/// Replace multiple words in a single pass, avoiding repeated reindexing.
|
|
175
|
+
pub fn replace_words_bulk<I>(&mut self, replacements: I) -> Result<(), TextBufferError>
|
|
176
|
+
where
|
|
177
|
+
I: IntoIterator<Item = (usize, String)>,
|
|
178
|
+
{
|
|
179
|
+
let mut applied_any = false;
|
|
180
|
+
for (word_index, replacement) in replacements {
|
|
181
|
+
let segment_index = self
|
|
182
|
+
.word_segment_indices
|
|
183
|
+
.get(word_index)
|
|
184
|
+
.copied()
|
|
185
|
+
.ok_or(TextBufferError::InvalidWordIndex { index: word_index })?;
|
|
186
|
+
let segment = self
|
|
187
|
+
.segments
|
|
188
|
+
.get_mut(segment_index)
|
|
189
|
+
.ok_or(TextBufferError::InvalidWordIndex { index: word_index })?;
|
|
190
|
+
segment.set_text(replacement, SegmentKind::Word);
|
|
191
|
+
applied_any = true;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
if applied_any {
|
|
195
|
+
self.reindex();
|
|
196
|
+
}
|
|
197
|
+
Ok(())
|
|
198
|
+
}
|
|
199
|
+
|
|
174
200
|
/// Deletes the word at the requested index.
|
|
175
201
|
pub fn delete_word(&mut self, word_index: usize) -> Result<(), TextBufferError> {
|
|
176
202
|
let segment_index = self
|
|
@@ -402,6 +428,19 @@ mod tests {
|
|
|
402
428
|
assert_eq!(buffer.spans().len(), 5);
|
|
403
429
|
}
|
|
404
430
|
|
|
431
|
+
#[test]
|
|
432
|
+
fn bulk_replace_words_updates_multiple_entries() {
|
|
433
|
+
let mut buffer = TextBuffer::from_str("alpha beta gamma delta");
|
|
434
|
+
buffer
|
|
435
|
+
.replace_words_bulk(vec![(0, "delta".to_string()), (3, "alpha".to_string())])
|
|
436
|
+
.expect("bulk replace succeeds");
|
|
437
|
+
assert_eq!(buffer.to_string(), "delta beta gamma alpha");
|
|
438
|
+
let spans = buffer.spans();
|
|
439
|
+
assert_eq!(spans[0].char_range, 0..5);
|
|
440
|
+
assert_eq!(spans.len(), 7);
|
|
441
|
+
assert_eq!(spans.last().unwrap().char_range, 17..22);
|
|
442
|
+
}
|
|
443
|
+
|
|
405
444
|
#[test]
|
|
406
445
|
fn replace_char_range_handles_multisegment_updates() {
|
|
407
446
|
let mut buffer = TextBuffer::from_str("Hello world");
|
|
@@ -1,29 +1,33 @@
|
|
|
1
|
+
from .config import AttackConfig, build_gaggle, load_attack_config
|
|
2
|
+
from .util import SAMPLE_TEXT
|
|
1
3
|
from .zoo import (
|
|
2
|
-
Typogre,
|
|
3
|
-
typogre,
|
|
4
|
-
Mim1c,
|
|
5
|
-
mim1c,
|
|
6
|
-
Jargoyle,
|
|
7
|
-
jargoyle,
|
|
8
4
|
Adjax,
|
|
9
|
-
|
|
5
|
+
Gaggle,
|
|
6
|
+
Glitchling,
|
|
7
|
+
Jargoyle,
|
|
8
|
+
Mim1c,
|
|
10
9
|
Redactyl,
|
|
11
|
-
redactyl,
|
|
12
10
|
Reduple,
|
|
13
|
-
reduple,
|
|
14
11
|
Rushmore,
|
|
15
|
-
rushmore,
|
|
16
12
|
Scannequin,
|
|
17
|
-
|
|
13
|
+
Typogre,
|
|
18
14
|
Zeedub,
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
15
|
+
adjax,
|
|
16
|
+
is_rust_pipeline_enabled,
|
|
17
|
+
is_rust_pipeline_supported,
|
|
18
|
+
jargoyle,
|
|
19
|
+
mim1c,
|
|
20
|
+
pipeline_feature_flag_enabled,
|
|
21
|
+
plan_glitchling_specs,
|
|
22
|
+
plan_glitchlings,
|
|
23
|
+
redactyl,
|
|
24
|
+
reduple,
|
|
25
|
+
rushmore,
|
|
26
|
+
scannequin,
|
|
22
27
|
summon,
|
|
28
|
+
typogre,
|
|
29
|
+
zeedub,
|
|
23
30
|
)
|
|
24
|
-
from .config import AttackConfig, build_gaggle, load_attack_config
|
|
25
|
-
from .util import SAMPLE_TEXT
|
|
26
|
-
|
|
27
31
|
|
|
28
32
|
__all__ = [
|
|
29
33
|
"Typogre",
|
|
@@ -47,6 +51,11 @@ __all__ = [
|
|
|
47
51
|
"summon",
|
|
48
52
|
"Glitchling",
|
|
49
53
|
"Gaggle",
|
|
54
|
+
"plan_glitchlings",
|
|
55
|
+
"plan_glitchling_specs",
|
|
56
|
+
"is_rust_pipeline_enabled",
|
|
57
|
+
"is_rust_pipeline_supported",
|
|
58
|
+
"pipeline_feature_flag_enabled",
|
|
50
59
|
"SAMPLE_TEXT",
|
|
51
60
|
"AttackConfig",
|
|
52
61
|
"build_gaggle",
|