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
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
"""Pure metric dispatch functions.
|
|
2
|
+
|
|
3
|
+
This module contains pure functions for dispatching metric computations.
|
|
4
|
+
It does not import Rust FFI or perform any IO - it operates on already-
|
|
5
|
+
resolved metric functions.
|
|
6
|
+
|
|
7
|
+
Pure guarantees:
|
|
8
|
+
- No import side effects beyond stdlib
|
|
9
|
+
- No Rust FFI loading
|
|
10
|
+
- Deterministic dispatch logic
|
|
11
|
+
|
|
12
|
+
The impure Rust metric loading lives in metrics.py.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
from typing import Sequence, TypeGuard
|
|
18
|
+
|
|
19
|
+
TokenSequence = Sequence[str]
|
|
20
|
+
TokenBatch = Sequence[TokenSequence]
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def is_batch(tokens: TokenSequence | TokenBatch) -> TypeGuard[TokenBatch]:
|
|
24
|
+
"""Determine if tokens represent a batch of sequences.
|
|
25
|
+
|
|
26
|
+
An empty list is treated as an empty batch (returning True) so that
|
|
27
|
+
``metric([], [])`` returns ``[]`` rather than ``0.0``. This matches
|
|
28
|
+
the behavior of :meth:`Attack.run` when processing empty transcripts.
|
|
29
|
+
|
|
30
|
+
Args:
|
|
31
|
+
tokens: Either a sequence of token strings or a batch of such sequences.
|
|
32
|
+
|
|
33
|
+
Returns:
|
|
34
|
+
True if tokens is a batch (list of lists), False if a single sequence.
|
|
35
|
+
"""
|
|
36
|
+
if not tokens:
|
|
37
|
+
return True # Empty list is an empty batch
|
|
38
|
+
|
|
39
|
+
first = tokens[0]
|
|
40
|
+
return isinstance(first, Sequence) and not isinstance(first, (str, bytes))
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def validate_batch_consistency(
|
|
44
|
+
original: TokenSequence | TokenBatch,
|
|
45
|
+
corrupted: TokenSequence | TokenBatch,
|
|
46
|
+
metric_name: str,
|
|
47
|
+
) -> None:
|
|
48
|
+
"""Validate that both inputs are consistently batched or single.
|
|
49
|
+
|
|
50
|
+
Args:
|
|
51
|
+
original: Original token sequence or batch.
|
|
52
|
+
corrupted: Corrupted token sequence or batch.
|
|
53
|
+
metric_name: Name of the metric (for error messages).
|
|
54
|
+
|
|
55
|
+
Raises:
|
|
56
|
+
TypeError: If one input is batched and the other isn't.
|
|
57
|
+
"""
|
|
58
|
+
original_is_batch = is_batch(original)
|
|
59
|
+
corrupted_is_batch = is_batch(corrupted)
|
|
60
|
+
|
|
61
|
+
if original_is_batch != corrupted_is_batch:
|
|
62
|
+
raise TypeError(f"{metric_name} expects either both batch inputs or both single sequences")
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
__all__ = [
|
|
66
|
+
"TokenBatch",
|
|
67
|
+
"TokenSequence",
|
|
68
|
+
"is_batch",
|
|
69
|
+
"validate_batch_consistency",
|
|
70
|
+
]
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import importlib.util
|
|
4
|
+
import zlib
|
|
5
|
+
from typing import Any, Protocol, Sequence
|
|
6
|
+
|
|
7
|
+
DEFAULT_TIKTOKEN_ENCODINGS = ("o200k_base", "cl100k_base")
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class Tokenizer(Protocol):
|
|
11
|
+
def encode(self, text: str) -> tuple[list[str], list[int]]: ...
|
|
12
|
+
|
|
13
|
+
def decode(self, tokens: Sequence[str]) -> str: ...
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class WhitespaceTokenizer:
|
|
17
|
+
def encode(self, text: str) -> tuple[list[str], list[int]]:
|
|
18
|
+
tokens = text.split()
|
|
19
|
+
# Synthetic IDs based on adler32 hash for stability
|
|
20
|
+
ids = [zlib.adler32(t.encode("utf-8")) & 0xFFFFFFFF for t in tokens]
|
|
21
|
+
return tokens, ids
|
|
22
|
+
|
|
23
|
+
def decode(self, tokens: Sequence[str]) -> str:
|
|
24
|
+
return " ".join(tokens)
|
|
25
|
+
|
|
26
|
+
def encode_batch(self, texts: Sequence[str]) -> list[tuple[list[str], list[int]]]:
|
|
27
|
+
return [self.encode(text) for text in texts]
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class TiktokenTokenizer:
|
|
31
|
+
def __init__(self, model_name: str):
|
|
32
|
+
import tiktoken
|
|
33
|
+
|
|
34
|
+
self.name = model_name
|
|
35
|
+
try:
|
|
36
|
+
self.enc = tiktoken.get_encoding(model_name)
|
|
37
|
+
except ValueError:
|
|
38
|
+
self.enc = tiktoken.encoding_for_model(model_name)
|
|
39
|
+
|
|
40
|
+
def encode(self, text: str) -> tuple[list[str], list[int]]:
|
|
41
|
+
ids = self.enc.encode(text)
|
|
42
|
+
tokens = [
|
|
43
|
+
self.enc.decode_single_token_bytes(i).decode("utf-8", errors="replace") for i in ids
|
|
44
|
+
]
|
|
45
|
+
return tokens, ids
|
|
46
|
+
|
|
47
|
+
def decode(self, tokens: Sequence[str], sep: str = "") -> str:
|
|
48
|
+
return sep.join(tokens)
|
|
49
|
+
|
|
50
|
+
def encode_batch(self, texts: Sequence[str]) -> list[tuple[list[str], list[int]]]:
|
|
51
|
+
id_batches = [list(batch) for batch in self.enc.encode_batch(list(texts))]
|
|
52
|
+
token_batches: list[list[str]] = []
|
|
53
|
+
for ids in id_batches:
|
|
54
|
+
token_batches.append(
|
|
55
|
+
[
|
|
56
|
+
self.enc.decode_single_token_bytes(i).decode("utf-8", errors="replace")
|
|
57
|
+
for i in ids
|
|
58
|
+
]
|
|
59
|
+
)
|
|
60
|
+
return list(zip(token_batches, id_batches))
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
class HuggingFaceTokenizerWrapper:
|
|
64
|
+
def __init__(self, tokenizer_obj: Any):
|
|
65
|
+
self.tokenizer = tokenizer_obj
|
|
66
|
+
|
|
67
|
+
def encode(self, text: str) -> tuple[list[str], list[int]]:
|
|
68
|
+
# tokenizers.Tokenizer.encode returns an Encoding object
|
|
69
|
+
encoding = self.tokenizer.encode(text)
|
|
70
|
+
return encoding.tokens, encoding.ids
|
|
71
|
+
|
|
72
|
+
def decode(self, tokens: Sequence[str]) -> str:
|
|
73
|
+
# Use the tokenizer's decode method to properly handle model-specific
|
|
74
|
+
# artifacts (e.g., "##" for WordPiece, "Ġ" for BPE).
|
|
75
|
+
# Convert tokens to IDs first, then decode.
|
|
76
|
+
try:
|
|
77
|
+
token_ids = [self.tokenizer.token_to_id(token) for token in tokens]
|
|
78
|
+
# Filter out None values (tokens not in vocabulary)
|
|
79
|
+
valid_ids = [tid for tid in token_ids if tid is not None]
|
|
80
|
+
if valid_ids:
|
|
81
|
+
result: str = self.tokenizer.decode(valid_ids)
|
|
82
|
+
return result
|
|
83
|
+
except (AttributeError, TypeError):
|
|
84
|
+
pass
|
|
85
|
+
# Fallback: simple join without any replacements
|
|
86
|
+
return "".join(tokens)
|
|
87
|
+
|
|
88
|
+
def encode_batch(self, texts: Sequence[str]) -> list[tuple[list[str], list[int]]]:
|
|
89
|
+
encodings = self.tokenizer.encode_batch(list(texts))
|
|
90
|
+
return [(encoding.tokens, encoding.ids) for encoding in encodings]
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def resolve_tokenizer(tokenizer: str | Tokenizer | None) -> Tokenizer:
|
|
94
|
+
if tokenizer is None:
|
|
95
|
+
return _default_tokenizer()
|
|
96
|
+
|
|
97
|
+
if isinstance(tokenizer, str):
|
|
98
|
+
if importlib.util.find_spec("tiktoken"):
|
|
99
|
+
import tiktoken
|
|
100
|
+
|
|
101
|
+
try:
|
|
102
|
+
# Check if valid tiktoken encoding/model
|
|
103
|
+
try:
|
|
104
|
+
tiktoken.get_encoding(tokenizer)
|
|
105
|
+
return TiktokenTokenizer(tokenizer)
|
|
106
|
+
except ValueError:
|
|
107
|
+
try:
|
|
108
|
+
tiktoken.encoding_for_model(tokenizer)
|
|
109
|
+
return TiktokenTokenizer(tokenizer)
|
|
110
|
+
except ValueError:
|
|
111
|
+
pass
|
|
112
|
+
except ImportError:
|
|
113
|
+
pass
|
|
114
|
+
|
|
115
|
+
if importlib.util.find_spec("tokenizers"):
|
|
116
|
+
from tokenizers import Tokenizer
|
|
117
|
+
|
|
118
|
+
try:
|
|
119
|
+
return HuggingFaceTokenizerWrapper(Tokenizer.from_pretrained(tokenizer))
|
|
120
|
+
except Exception:
|
|
121
|
+
pass
|
|
122
|
+
|
|
123
|
+
raise ValueError(f"Could not resolve tokenizer: {tokenizer}")
|
|
124
|
+
|
|
125
|
+
# Check if it is a HuggingFace tokenizer object
|
|
126
|
+
if importlib.util.find_spec("tokenizers"):
|
|
127
|
+
from tokenizers import Tokenizer as HFTokenizer
|
|
128
|
+
|
|
129
|
+
if isinstance(tokenizer, HFTokenizer):
|
|
130
|
+
return HuggingFaceTokenizerWrapper(tokenizer)
|
|
131
|
+
|
|
132
|
+
return tokenizer
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def _default_tokenizer() -> Tokenizer:
|
|
136
|
+
"""Select a modern, lightweight tokenizer with graceful fallbacks."""
|
|
137
|
+
if importlib.util.find_spec("tiktoken"):
|
|
138
|
+
import tiktoken
|
|
139
|
+
|
|
140
|
+
for encoding in DEFAULT_TIKTOKEN_ENCODINGS:
|
|
141
|
+
try:
|
|
142
|
+
tiktoken.get_encoding(encoding)
|
|
143
|
+
return TiktokenTokenizer(encoding)
|
|
144
|
+
except ValueError:
|
|
145
|
+
continue
|
|
146
|
+
|
|
147
|
+
return WhitespaceTokenizer()
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
__all__ = [
|
|
151
|
+
"DEFAULT_TIKTOKEN_ENCODINGS",
|
|
152
|
+
"HuggingFaceTokenizerWrapper",
|
|
153
|
+
"TiktokenTokenizer",
|
|
154
|
+
"Tokenizer",
|
|
155
|
+
"WhitespaceTokenizer",
|
|
156
|
+
"resolve_tokenizer",
|
|
157
|
+
]
|
glitchlings/auggie.py
ADDED
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
"""Laboratory assistant for composing gaggles with behaviour-focused helpers."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from collections.abc import Iterable, Sequence
|
|
6
|
+
from typing import Collection, Literal
|
|
7
|
+
|
|
8
|
+
from .zoo.core import Gaggle, Glitchling
|
|
9
|
+
from .zoo.ekkokin import Ekkokin
|
|
10
|
+
from .zoo.hokey import Hokey
|
|
11
|
+
from .zoo.jargoyle import (
|
|
12
|
+
DEFAULT_LEXEMES,
|
|
13
|
+
DEFAULT_MODE,
|
|
14
|
+
Jargoyle,
|
|
15
|
+
JargoyleMode,
|
|
16
|
+
)
|
|
17
|
+
from .zoo.mim1c import Mim1c
|
|
18
|
+
from .zoo.pedant import Pedant
|
|
19
|
+
from .zoo.pedant.stones import PedantStone
|
|
20
|
+
from .zoo.redactyl import FULL_BLOCK, Redactyl
|
|
21
|
+
from .zoo.rushmore import Rushmore, RushmoreMode
|
|
22
|
+
from .zoo.scannequin import Scannequin
|
|
23
|
+
from .zoo.typogre import Typogre
|
|
24
|
+
from .zoo.zeedub import Zeedub
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class Auggie(Gaggle):
|
|
28
|
+
"""Assistant that incrementally assembles glitchlings into a gaggle."""
|
|
29
|
+
|
|
30
|
+
def __init__(
|
|
31
|
+
self,
|
|
32
|
+
glitchlings: Iterable[Glitchling] | None = None,
|
|
33
|
+
*,
|
|
34
|
+
seed: int = 151,
|
|
35
|
+
) -> None:
|
|
36
|
+
self._blueprint: list[Glitchling] = []
|
|
37
|
+
initial = list(glitchlings or [])
|
|
38
|
+
super().__init__(initial, seed=seed)
|
|
39
|
+
if initial:
|
|
40
|
+
self._blueprint = [glitchling.clone() for glitchling in initial]
|
|
41
|
+
self._rebuild_plan()
|
|
42
|
+
else:
|
|
43
|
+
self._blueprint = []
|
|
44
|
+
|
|
45
|
+
def _rebuild_plan(self) -> None:
|
|
46
|
+
self._clones_by_index = []
|
|
47
|
+
for index, glitchling in enumerate(self._blueprint):
|
|
48
|
+
clone = glitchling.clone()
|
|
49
|
+
setattr(clone, "_gaggle_index", index)
|
|
50
|
+
self._clones_by_index.append(clone)
|
|
51
|
+
self.sort_glitchlings()
|
|
52
|
+
|
|
53
|
+
def _enqueue(self, glitchling: Glitchling) -> "Auggie":
|
|
54
|
+
self._blueprint.append(glitchling)
|
|
55
|
+
self._rebuild_plan()
|
|
56
|
+
return self
|
|
57
|
+
|
|
58
|
+
def clone(self, seed: int | None = None) -> "Auggie":
|
|
59
|
+
clone_seed = seed if seed is not None else self.seed
|
|
60
|
+
resolved_seed = 151 if clone_seed is None else int(clone_seed)
|
|
61
|
+
blueprint = [glitch.clone() for glitch in self._blueprint]
|
|
62
|
+
return Auggie(blueprint, seed=resolved_seed)
|
|
63
|
+
|
|
64
|
+
def typo(
|
|
65
|
+
self,
|
|
66
|
+
*,
|
|
67
|
+
rate: float | None = None,
|
|
68
|
+
keyboard: str = "CURATOR_QWERTY",
|
|
69
|
+
seed: int | None = None,
|
|
70
|
+
) -> "Auggie":
|
|
71
|
+
"""Add :class:`Typogre` using behaviour-driven nomenclature."""
|
|
72
|
+
|
|
73
|
+
return self._enqueue(Typogre(rate=rate, keyboard=keyboard, seed=seed))
|
|
74
|
+
|
|
75
|
+
def confusable(
|
|
76
|
+
self,
|
|
77
|
+
*,
|
|
78
|
+
rate: float | None = None,
|
|
79
|
+
classes: list[str] | Literal["all"] | None = None,
|
|
80
|
+
banned_characters: Collection[str] | None = None,
|
|
81
|
+
seed: int | None = None,
|
|
82
|
+
) -> "Auggie":
|
|
83
|
+
"""Add :class:`Mim1c` for homoglyph substitutions."""
|
|
84
|
+
|
|
85
|
+
return self._enqueue(
|
|
86
|
+
Mim1c(
|
|
87
|
+
rate=rate,
|
|
88
|
+
classes=classes,
|
|
89
|
+
banned_characters=banned_characters,
|
|
90
|
+
seed=seed,
|
|
91
|
+
)
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
def curly_quotes(self, *, seed: int | None = None) -> "Auggie":
|
|
95
|
+
"""Add :class:`Pedant` evolved with Curlite to smarten punctuation."""
|
|
96
|
+
|
|
97
|
+
return self._enqueue(Pedant(stone=PedantStone.CURLITE, seed=seed))
|
|
98
|
+
|
|
99
|
+
def stretch(
|
|
100
|
+
self,
|
|
101
|
+
*,
|
|
102
|
+
rate: float = 0.3,
|
|
103
|
+
extension_min: int = 2,
|
|
104
|
+
extension_max: int = 5,
|
|
105
|
+
word_length_threshold: int = 6,
|
|
106
|
+
base_p: float = 0.45,
|
|
107
|
+
seed: int | None = None,
|
|
108
|
+
) -> "Auggie":
|
|
109
|
+
"""Add :class:`Hokey` for elongated, expressive words."""
|
|
110
|
+
|
|
111
|
+
return self._enqueue(
|
|
112
|
+
Hokey(
|
|
113
|
+
rate=rate,
|
|
114
|
+
extension_min=extension_min,
|
|
115
|
+
extension_max=extension_max,
|
|
116
|
+
word_length_threshold=word_length_threshold,
|
|
117
|
+
base_p=base_p,
|
|
118
|
+
seed=seed,
|
|
119
|
+
)
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
def homophone(
|
|
123
|
+
self,
|
|
124
|
+
*,
|
|
125
|
+
rate: float | None = None,
|
|
126
|
+
seed: int | None = None,
|
|
127
|
+
) -> "Auggie":
|
|
128
|
+
"""Add :class:`Ekkokin` to swap words for homophones."""
|
|
129
|
+
|
|
130
|
+
return self._enqueue(Ekkokin(rate=rate, seed=seed))
|
|
131
|
+
|
|
132
|
+
def pedantry(
|
|
133
|
+
self,
|
|
134
|
+
*,
|
|
135
|
+
stone: PedantStone | str = PedantStone.COEURITE,
|
|
136
|
+
seed: int | None = None,
|
|
137
|
+
) -> "Auggie":
|
|
138
|
+
"""Add :class:`Pedant` to evolve text via a chosen stone."""
|
|
139
|
+
|
|
140
|
+
return self._enqueue(Pedant(stone=stone, seed=seed))
|
|
141
|
+
|
|
142
|
+
def remix(
|
|
143
|
+
self,
|
|
144
|
+
*,
|
|
145
|
+
modes: RushmoreMode | str | Iterable[RushmoreMode | str] | None = None,
|
|
146
|
+
rate: float | None = None,
|
|
147
|
+
delete_rate: float | None = None,
|
|
148
|
+
duplicate_rate: float | None = None,
|
|
149
|
+
swap_rate: float | None = None,
|
|
150
|
+
seed: int | None = None,
|
|
151
|
+
unweighted: bool = False,
|
|
152
|
+
delete_unweighted: bool | None = None,
|
|
153
|
+
duplicate_unweighted: bool | None = None,
|
|
154
|
+
) -> "Auggie":
|
|
155
|
+
"""Add :class:`Rushmore` for deletion, duplication, and swap attacks."""
|
|
156
|
+
|
|
157
|
+
return self._enqueue(
|
|
158
|
+
Rushmore(
|
|
159
|
+
modes=modes,
|
|
160
|
+
rate=rate,
|
|
161
|
+
delete_rate=delete_rate,
|
|
162
|
+
duplicate_rate=duplicate_rate,
|
|
163
|
+
swap_rate=swap_rate,
|
|
164
|
+
seed=seed,
|
|
165
|
+
unweighted=unweighted,
|
|
166
|
+
delete_unweighted=delete_unweighted,
|
|
167
|
+
duplicate_unweighted=duplicate_unweighted,
|
|
168
|
+
)
|
|
169
|
+
)
|
|
170
|
+
|
|
171
|
+
def redact(
|
|
172
|
+
self,
|
|
173
|
+
*,
|
|
174
|
+
replacement_char: str = FULL_BLOCK,
|
|
175
|
+
rate: float | None = None,
|
|
176
|
+
merge_adjacent: bool = False,
|
|
177
|
+
seed: int | None = 151,
|
|
178
|
+
unweighted: bool = False,
|
|
179
|
+
) -> "Auggie":
|
|
180
|
+
"""Add :class:`Redactyl` to blackout words."""
|
|
181
|
+
|
|
182
|
+
return self._enqueue(
|
|
183
|
+
Redactyl(
|
|
184
|
+
replacement_char=replacement_char,
|
|
185
|
+
rate=rate,
|
|
186
|
+
merge_adjacent=merge_adjacent,
|
|
187
|
+
seed=seed if seed is not None else 151,
|
|
188
|
+
unweighted=unweighted,
|
|
189
|
+
)
|
|
190
|
+
)
|
|
191
|
+
|
|
192
|
+
def recolor(self, *, mode: JargoyleMode = "literal", seed: int | None = None) -> "Auggie":
|
|
193
|
+
"""Add :class:`Jargoyle` with ``lexemes="colors"`` to remap colour terms.
|
|
194
|
+
|
|
195
|
+
Args:
|
|
196
|
+
mode: "literal" for deterministic first-entry swaps,
|
|
197
|
+
"drift" for random selection from palette.
|
|
198
|
+
seed: Seed for deterministic randomness.
|
|
199
|
+
|
|
200
|
+
Returns:
|
|
201
|
+
Self for method chaining.
|
|
202
|
+
"""
|
|
203
|
+
return self._enqueue(Jargoyle(lexemes="colors", mode=mode, rate=1.0, seed=seed))
|
|
204
|
+
|
|
205
|
+
def drift(
|
|
206
|
+
self,
|
|
207
|
+
*,
|
|
208
|
+
lexemes: str = DEFAULT_LEXEMES,
|
|
209
|
+
mode: JargoyleMode = DEFAULT_MODE,
|
|
210
|
+
rate: float | None = None,
|
|
211
|
+
seed: int | None = None,
|
|
212
|
+
) -> "Auggie":
|
|
213
|
+
"""Add :class:`Jargoyle` for dictionary-based word drift.
|
|
214
|
+
|
|
215
|
+
Swaps words with alternatives from the specified lexeme dictionary.
|
|
216
|
+
|
|
217
|
+
Args:
|
|
218
|
+
lexemes: Dictionary to use. One of:
|
|
219
|
+
"colors" (color term swapping),
|
|
220
|
+
"synonyms" (general synonyms),
|
|
221
|
+
"corporate" (business jargon),
|
|
222
|
+
"academic" (scholarly terms).
|
|
223
|
+
mode: "literal" for deterministic first-entry swaps,
|
|
224
|
+
"drift" for random selection.
|
|
225
|
+
rate: Probability of transforming each matching word.
|
|
226
|
+
seed: Seed for deterministic randomness.
|
|
227
|
+
|
|
228
|
+
Returns:
|
|
229
|
+
Self for method chaining.
|
|
230
|
+
"""
|
|
231
|
+
return self._enqueue(Jargoyle(lexemes=lexemes, mode=mode, rate=rate, seed=seed))
|
|
232
|
+
|
|
233
|
+
def ocr(
|
|
234
|
+
self,
|
|
235
|
+
*,
|
|
236
|
+
rate: float | None = None,
|
|
237
|
+
seed: int | None = None,
|
|
238
|
+
) -> "Auggie":
|
|
239
|
+
"""Add :class:`Scannequin` to simulate OCR artefacts."""
|
|
240
|
+
|
|
241
|
+
return self._enqueue(Scannequin(rate=rate, seed=seed))
|
|
242
|
+
|
|
243
|
+
def zero_width(
|
|
244
|
+
self,
|
|
245
|
+
*,
|
|
246
|
+
rate: float | None = None,
|
|
247
|
+
seed: int | None = None,
|
|
248
|
+
characters: Sequence[str] | None = None,
|
|
249
|
+
) -> "Auggie":
|
|
250
|
+
"""Add :class:`Zeedub` to hide zero-width glyphs inside text."""
|
|
251
|
+
|
|
252
|
+
return self._enqueue(Zeedub(rate=rate, seed=seed, characters=characters))
|
|
253
|
+
|
|
254
|
+
def synonym(
|
|
255
|
+
self,
|
|
256
|
+
*,
|
|
257
|
+
rate: float | None = None,
|
|
258
|
+
seed: int | None = None,
|
|
259
|
+
lexemes: str = "synonyms",
|
|
260
|
+
mode: JargoyleMode = "drift",
|
|
261
|
+
) -> "Auggie":
|
|
262
|
+
"""Add :class:`Jargoyle` for synonym substitutions.
|
|
263
|
+
|
|
264
|
+
Args:
|
|
265
|
+
rate: Probability of transforming each matching word.
|
|
266
|
+
seed: Seed for deterministic randomness.
|
|
267
|
+
lexemes: Dictionary to use (default "synonyms").
|
|
268
|
+
mode: "literal" or "drift" (default "drift").
|
|
269
|
+
|
|
270
|
+
Returns:
|
|
271
|
+
Self for method chaining.
|
|
272
|
+
"""
|
|
273
|
+
return self._enqueue(
|
|
274
|
+
Jargoyle(
|
|
275
|
+
rate=rate,
|
|
276
|
+
seed=seed,
|
|
277
|
+
lexemes=lexemes,
|
|
278
|
+
mode=mode,
|
|
279
|
+
)
|
|
280
|
+
)
|
|
281
|
+
|
|
282
|
+
|
|
283
|
+
__all__ = ["Auggie"]
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
"""Compatibility helpers centralising optional dependency imports and extras.
|
|
2
|
+
|
|
3
|
+
For 1.0, this package no longer re-exports loader utilities or type sentinels.
|
|
4
|
+
Import directly from ``glitchlings.compat.loaders`` or ``glitchlings.compat.types``.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
__all__: list[str] = []
|