batrachian-toad 0.5.22__py3-none-any.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.
- batrachian_toad-0.5.22.dist-info/METADATA +197 -0
- batrachian_toad-0.5.22.dist-info/RECORD +120 -0
- batrachian_toad-0.5.22.dist-info/WHEEL +4 -0
- batrachian_toad-0.5.22.dist-info/entry_points.txt +2 -0
- batrachian_toad-0.5.22.dist-info/licenses/LICENSE +661 -0
- toad/__init__.py +46 -0
- toad/__main__.py +4 -0
- toad/_loop.py +86 -0
- toad/about.py +90 -0
- toad/acp/agent.py +671 -0
- toad/acp/api.py +47 -0
- toad/acp/encode_tool_call_id.py +12 -0
- toad/acp/messages.py +138 -0
- toad/acp/prompt.py +54 -0
- toad/acp/protocol.py +426 -0
- toad/agent.py +62 -0
- toad/agent_schema.py +70 -0
- toad/agents.py +45 -0
- toad/ansi/__init__.py +1 -0
- toad/ansi/_ansi.py +1612 -0
- toad/ansi/_ansi_colors.py +264 -0
- toad/ansi/_control_codes.py +37 -0
- toad/ansi/_keys.py +251 -0
- toad/ansi/_sgr_styles.py +64 -0
- toad/ansi/_stream_parser.py +418 -0
- toad/answer.py +22 -0
- toad/app.py +557 -0
- toad/atomic.py +37 -0
- toad/cli.py +257 -0
- toad/code_analyze.py +28 -0
- toad/complete.py +34 -0
- toad/constants.py +58 -0
- toad/conversation_markdown.py +19 -0
- toad/danger.py +371 -0
- toad/data/agents/ampcode.com.toml +51 -0
- toad/data/agents/augmentcode.com.toml +40 -0
- toad/data/agents/claude.com.toml +41 -0
- toad/data/agents/docker.com.toml +59 -0
- toad/data/agents/geminicli.com.toml +28 -0
- toad/data/agents/goose.ai.toml +51 -0
- toad/data/agents/inference.huggingface.co.toml +33 -0
- toad/data/agents/kimi.com.toml +35 -0
- toad/data/agents/openai.com.toml +53 -0
- toad/data/agents/opencode.ai.toml +61 -0
- toad/data/agents/openhands.dev.toml +44 -0
- toad/data/agents/stakpak.dev.toml +61 -0
- toad/data/agents/vibe.mistral.ai.toml +27 -0
- toad/data/agents/vtcode.dev.toml +62 -0
- toad/data/images/frog.png +0 -0
- toad/data/sounds/turn-over.wav +0 -0
- toad/db.py +5 -0
- toad/dec.py +332 -0
- toad/directory.py +234 -0
- toad/directory_watcher.py +96 -0
- toad/fuzzy.py +140 -0
- toad/gist.py +2 -0
- toad/history.py +138 -0
- toad/jsonrpc.py +576 -0
- toad/menus.py +14 -0
- toad/messages.py +74 -0
- toad/option_content.py +51 -0
- toad/os.py +0 -0
- toad/path_complete.py +145 -0
- toad/path_filter.py +124 -0
- toad/paths.py +71 -0
- toad/pill.py +23 -0
- toad/prompt/extract.py +19 -0
- toad/prompt/resource.py +68 -0
- toad/protocol.py +28 -0
- toad/screens/action_modal.py +94 -0
- toad/screens/agent_modal.py +172 -0
- toad/screens/command_edit_modal.py +58 -0
- toad/screens/main.py +192 -0
- toad/screens/permissions.py +390 -0
- toad/screens/permissions.tcss +72 -0
- toad/screens/settings.py +254 -0
- toad/screens/settings.tcss +101 -0
- toad/screens/store.py +476 -0
- toad/screens/store.tcss +261 -0
- toad/settings.py +354 -0
- toad/settings_schema.py +318 -0
- toad/shell.py +263 -0
- toad/shell_read.py +42 -0
- toad/slash_command.py +34 -0
- toad/toad.tcss +752 -0
- toad/version.py +80 -0
- toad/visuals/columns.py +273 -0
- toad/widgets/agent_response.py +79 -0
- toad/widgets/agent_thought.py +41 -0
- toad/widgets/command_pane.py +224 -0
- toad/widgets/condensed_path.py +93 -0
- toad/widgets/conversation.py +1626 -0
- toad/widgets/danger_warning.py +65 -0
- toad/widgets/diff_view.py +709 -0
- toad/widgets/flash.py +81 -0
- toad/widgets/future_text.py +126 -0
- toad/widgets/grid_select.py +223 -0
- toad/widgets/highlighted_textarea.py +180 -0
- toad/widgets/mandelbrot.py +294 -0
- toad/widgets/markdown_note.py +13 -0
- toad/widgets/menu.py +147 -0
- toad/widgets/non_selectable_label.py +5 -0
- toad/widgets/note.py +18 -0
- toad/widgets/path_search.py +381 -0
- toad/widgets/plan.py +180 -0
- toad/widgets/project_directory_tree.py +74 -0
- toad/widgets/prompt.py +741 -0
- toad/widgets/question.py +337 -0
- toad/widgets/shell_result.py +35 -0
- toad/widgets/shell_terminal.py +18 -0
- toad/widgets/side_bar.py +74 -0
- toad/widgets/slash_complete.py +211 -0
- toad/widgets/strike_text.py +66 -0
- toad/widgets/terminal.py +526 -0
- toad/widgets/terminal_tool.py +338 -0
- toad/widgets/throbber.py +90 -0
- toad/widgets/tool_call.py +303 -0
- toad/widgets/user_input.py +23 -0
- toad/widgets/version.py +5 -0
- toad/widgets/welcome.py +31 -0
|
@@ -0,0 +1,418 @@
|
|
|
1
|
+
from functools import lru_cache
|
|
2
|
+
import io
|
|
3
|
+
import re2 as re
|
|
4
|
+
|
|
5
|
+
import rich.repr
|
|
6
|
+
|
|
7
|
+
from textual.cache import LRUCache
|
|
8
|
+
from typing import Callable, Generator, Iterable
|
|
9
|
+
|
|
10
|
+
type TokenMatch = tuple[str, str]
|
|
11
|
+
|
|
12
|
+
type ParseResult[ParseType] = Generator[StreamRead | ParseType, Token, None]
|
|
13
|
+
type PatternCheck = Generator[None, str, TokenMatch | bool | None]
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@rich.repr.auto
|
|
17
|
+
class Pattern[ValueType]:
|
|
18
|
+
__slots__ = ["_send", "value"]
|
|
19
|
+
|
|
20
|
+
def __init__(self) -> None:
|
|
21
|
+
self._send: Callable[[str], None] | None = None
|
|
22
|
+
self.value: ValueType | None = None
|
|
23
|
+
|
|
24
|
+
def feed(self, character: str) -> bool | TokenMatch | None:
|
|
25
|
+
if self._send is None:
|
|
26
|
+
generator = self.check()
|
|
27
|
+
self._send = generator.send
|
|
28
|
+
next(generator)
|
|
29
|
+
try:
|
|
30
|
+
self._send(character)
|
|
31
|
+
except StopIteration as stop_iteration:
|
|
32
|
+
return stop_iteration.value
|
|
33
|
+
else:
|
|
34
|
+
return None
|
|
35
|
+
|
|
36
|
+
def check(self) -> PatternCheck:
|
|
37
|
+
return False
|
|
38
|
+
yield
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class StreamRead[ResultType]:
|
|
42
|
+
pass
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
@rich.repr.auto
|
|
46
|
+
class Read[ResultType](StreamRead[ResultType]):
|
|
47
|
+
__slots__ = ["remaining"]
|
|
48
|
+
|
|
49
|
+
def __init__(self, count: int) -> None:
|
|
50
|
+
self.remaining = count
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
@rich.repr.auto
|
|
54
|
+
class ReadUntil[ResultType](StreamRead[ResultType]):
|
|
55
|
+
__slots__ = ["characters", "_regex"]
|
|
56
|
+
|
|
57
|
+
def __init__(self, *characters: str) -> None:
|
|
58
|
+
self.characters = characters
|
|
59
|
+
self._regex = re.compile(
|
|
60
|
+
"|".join(re.escape(character) for character in characters)
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
def __rich_repr__(self) -> rich.repr.Result:
|
|
64
|
+
yield from self.characters
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
@rich.repr.auto
|
|
68
|
+
class ReadRegex[ResultType](StreamRead[ResultType]):
|
|
69
|
+
__slots__ = ["regex", "max_length", "_buffer"]
|
|
70
|
+
|
|
71
|
+
def __init__(self, regex: str, max_length: int | None = None) -> None:
|
|
72
|
+
self.regex = regex
|
|
73
|
+
self.max_length = max_length
|
|
74
|
+
self._buffer = io.StringIO()
|
|
75
|
+
|
|
76
|
+
def __rich_repr__(self) -> rich.repr.Result:
|
|
77
|
+
yield self.regex
|
|
78
|
+
|
|
79
|
+
@property
|
|
80
|
+
def buffer_size(self) -> int:
|
|
81
|
+
return self._buffer.tell()
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
@rich.repr.auto
|
|
85
|
+
class ReadPatterns[ResultType](StreamRead[ResultType]):
|
|
86
|
+
__slots__ = ["patterns", "_text"]
|
|
87
|
+
|
|
88
|
+
def __init__(self, start: str = "", **patterns: Pattern) -> None:
|
|
89
|
+
self.patterns = patterns
|
|
90
|
+
self._text = io.StringIO()
|
|
91
|
+
self._text.write(start)
|
|
92
|
+
|
|
93
|
+
@property
|
|
94
|
+
def unconsumed_text(self) -> str:
|
|
95
|
+
return self._text.getvalue()
|
|
96
|
+
|
|
97
|
+
def __rich_repr__(self) -> rich.repr.Result:
|
|
98
|
+
for key, value in self.patterns.items():
|
|
99
|
+
yield key, value
|
|
100
|
+
|
|
101
|
+
@property
|
|
102
|
+
def is_exhausted(self) -> bool:
|
|
103
|
+
return not self.patterns
|
|
104
|
+
|
|
105
|
+
def feed(self, text: str) -> tuple[int, TokenMatch | None]:
|
|
106
|
+
consumed = 0
|
|
107
|
+
new_patterns = patterns = self.patterns
|
|
108
|
+
for character in text:
|
|
109
|
+
consumed += 1
|
|
110
|
+
for name, sequence_validator in patterns.items():
|
|
111
|
+
if (value := sequence_validator.feed(character)) is False:
|
|
112
|
+
new_patterns = patterns.copy()
|
|
113
|
+
new_patterns.pop(name)
|
|
114
|
+
elif value:
|
|
115
|
+
return consumed, (name, value)
|
|
116
|
+
patterns = self._patterns = new_patterns
|
|
117
|
+
self._text.write(text[:consumed])
|
|
118
|
+
return consumed, None
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
@rich.repr.auto
|
|
122
|
+
class ReadPattern[ResultType](StreamRead[ResultType]):
|
|
123
|
+
"""Special case for a single pattern."""
|
|
124
|
+
|
|
125
|
+
__slots__ = ["name", "pattern", "_text", "_exhaused"]
|
|
126
|
+
|
|
127
|
+
def __init__(self, start: str, name: str, pattern: Pattern) -> None:
|
|
128
|
+
self.name = name
|
|
129
|
+
self.pattern: Pattern = pattern
|
|
130
|
+
self._text = io.StringIO()
|
|
131
|
+
self._text.write(start)
|
|
132
|
+
self._exhaused = False
|
|
133
|
+
|
|
134
|
+
@property
|
|
135
|
+
def unconsumed_text(self) -> str:
|
|
136
|
+
return self._text.getvalue()
|
|
137
|
+
|
|
138
|
+
def __rich_repr__(self) -> rich.repr.Result:
|
|
139
|
+
yield self.name
|
|
140
|
+
yield self.pattern
|
|
141
|
+
|
|
142
|
+
@property
|
|
143
|
+
def is_exhausted(self) -> bool:
|
|
144
|
+
return self._exhaused
|
|
145
|
+
|
|
146
|
+
def feed(self, text: str) -> tuple[int, TokenMatch | None]:
|
|
147
|
+
consumed = 0
|
|
148
|
+
feed = self.pattern.feed
|
|
149
|
+
for character in text:
|
|
150
|
+
consumed += 1
|
|
151
|
+
if (value := feed(character)) is False:
|
|
152
|
+
self._exhaused = True
|
|
153
|
+
break
|
|
154
|
+
elif value:
|
|
155
|
+
self._exhaused = True
|
|
156
|
+
return consumed, ("pattern", value)
|
|
157
|
+
self._text.write(text[:consumed])
|
|
158
|
+
return consumed, None
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
@rich.repr.auto
|
|
162
|
+
class Token:
|
|
163
|
+
"""A token containing text."""
|
|
164
|
+
|
|
165
|
+
__slots__ = "text"
|
|
166
|
+
|
|
167
|
+
def __init__(self, text: str = "") -> None:
|
|
168
|
+
self.text = text
|
|
169
|
+
|
|
170
|
+
def __rich_repr__(self) -> rich.repr.Result:
|
|
171
|
+
yield self.text
|
|
172
|
+
|
|
173
|
+
def __str__(self) -> str:
|
|
174
|
+
return self.text
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
class SeparatorToken(Token):
|
|
178
|
+
pass
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
class MatchToken(Token):
|
|
182
|
+
__slots__ = ["match"]
|
|
183
|
+
|
|
184
|
+
def __init__(self, text: str, match: re.Match) -> None:
|
|
185
|
+
self.match = match
|
|
186
|
+
super().__init__(text)
|
|
187
|
+
|
|
188
|
+
def __rich_repr__(self) -> rich.repr.Result:
|
|
189
|
+
yield self.match
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
class EOFToken(Token):
|
|
193
|
+
pass
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
class PatternToken(Token):
|
|
197
|
+
__slots__ = ["name", "value"]
|
|
198
|
+
|
|
199
|
+
def __init__(self, name: str, value: TokenMatch) -> None:
|
|
200
|
+
self.name = name
|
|
201
|
+
self.value = value
|
|
202
|
+
super().__init__("")
|
|
203
|
+
|
|
204
|
+
def __rich_repr__(self) -> rich.repr.Result:
|
|
205
|
+
yield self.name
|
|
206
|
+
yield None, self.value
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
class StreamParser[ParseType]:
|
|
210
|
+
"""Parses a stream of text into tokens."""
|
|
211
|
+
|
|
212
|
+
def __init__(self):
|
|
213
|
+
self._gen = self.parse()
|
|
214
|
+
self._reading: StreamRead | ParseType = next(self._gen)
|
|
215
|
+
self._cache = LRUCache(1024 * 4)
|
|
216
|
+
|
|
217
|
+
def read(self, count: int) -> Read:
|
|
218
|
+
"""Read a specific number of bytes.
|
|
219
|
+
|
|
220
|
+
Args:
|
|
221
|
+
count: Number of bytes to read.
|
|
222
|
+
"""
|
|
223
|
+
return Read(count)
|
|
224
|
+
|
|
225
|
+
@lru_cache(1024)
|
|
226
|
+
def read_until(self, *characters: str) -> ReadUntil:
|
|
227
|
+
"""Read until the given characters.
|
|
228
|
+
|
|
229
|
+
Args:
|
|
230
|
+
characters: Set of characters to stop read.
|
|
231
|
+
|
|
232
|
+
"""
|
|
233
|
+
return ReadUntil(*characters)
|
|
234
|
+
|
|
235
|
+
def read_regex(self, regex: str) -> ReadRegex:
|
|
236
|
+
"""Search for the matching regex.
|
|
237
|
+
|
|
238
|
+
Args:
|
|
239
|
+
regex: Regular expression.
|
|
240
|
+
"""
|
|
241
|
+
return ReadRegex(regex)
|
|
242
|
+
|
|
243
|
+
def read_patterns(self, start: str = "", **patterns) -> ReadPattern | ReadPatterns:
|
|
244
|
+
"""Read until a pattern matches, or the patterns have been exhausted.
|
|
245
|
+
|
|
246
|
+
Args:
|
|
247
|
+
start: Initial part of the string.
|
|
248
|
+
**patterns: One or more patterns.
|
|
249
|
+
"""
|
|
250
|
+
if len(patterns) == 1:
|
|
251
|
+
name, pattern = patterns.popitem()
|
|
252
|
+
return ReadPattern(start, name, pattern)
|
|
253
|
+
return ReadPatterns(start, **patterns)
|
|
254
|
+
|
|
255
|
+
def feed(self, text: str) -> Iterable[Token | ParseType]:
|
|
256
|
+
sequences = text.splitlines(keepends=True)
|
|
257
|
+
# TODO: Cache
|
|
258
|
+
for sequence in sequences:
|
|
259
|
+
yield from self._feed(sequence)
|
|
260
|
+
|
|
261
|
+
def _feed(self, text: str) -> Iterable[Token | ParseType]:
|
|
262
|
+
"""Feed text in to parser.
|
|
263
|
+
|
|
264
|
+
Args:
|
|
265
|
+
text: Text from stream.
|
|
266
|
+
|
|
267
|
+
Returns:
|
|
268
|
+
A generator of tokens or the parse type.
|
|
269
|
+
|
|
270
|
+
"""
|
|
271
|
+
if not text or self._gen is None:
|
|
272
|
+
yield EOFToken()
|
|
273
|
+
return
|
|
274
|
+
|
|
275
|
+
def send(token: Token) -> Iterable[Token]:
|
|
276
|
+
try:
|
|
277
|
+
while True:
|
|
278
|
+
new_token = self._gen.send(token)
|
|
279
|
+
if isinstance(new_token, StreamRead):
|
|
280
|
+
self._reading = new_token
|
|
281
|
+
break
|
|
282
|
+
else:
|
|
283
|
+
token = new_token
|
|
284
|
+
yield token
|
|
285
|
+
|
|
286
|
+
except StopIteration:
|
|
287
|
+
self._gen.close()
|
|
288
|
+
self._gen = None
|
|
289
|
+
|
|
290
|
+
while text:
|
|
291
|
+
if isinstance(self._reading, (ReadPattern, ReadPatterns)):
|
|
292
|
+
consumed, pattern_match = self._reading.feed(text)
|
|
293
|
+
|
|
294
|
+
if pattern_match is not None:
|
|
295
|
+
name, value = pattern_match
|
|
296
|
+
yield from send(PatternToken(name, value))
|
|
297
|
+
text = text[consumed:]
|
|
298
|
+
else:
|
|
299
|
+
if self._reading.is_exhausted:
|
|
300
|
+
unconsumed_text = self._reading.unconsumed_text
|
|
301
|
+
yield from send(Token(unconsumed_text))
|
|
302
|
+
text = text[consumed:]
|
|
303
|
+
else:
|
|
304
|
+
text = ""
|
|
305
|
+
|
|
306
|
+
elif isinstance(self._reading, Read):
|
|
307
|
+
if self._reading.remaining:
|
|
308
|
+
read_text = text[: self._reading.remaining]
|
|
309
|
+
read_text_length = len(read_text)
|
|
310
|
+
self._reading.remaining -= read_text_length
|
|
311
|
+
text = text[read_text_length:]
|
|
312
|
+
yield from send(Token(read_text))
|
|
313
|
+
else:
|
|
314
|
+
yield from send(Token(""))
|
|
315
|
+
|
|
316
|
+
elif isinstance(self._reading, ReadUntil):
|
|
317
|
+
if (match := self._reading._regex.search(text)) is not None:
|
|
318
|
+
start, end = match.span(0)
|
|
319
|
+
read_text = text[:start]
|
|
320
|
+
|
|
321
|
+
if read_text:
|
|
322
|
+
yield from send(Token(read_text))
|
|
323
|
+
text = text[start:]
|
|
324
|
+
else:
|
|
325
|
+
yield from send(SeparatorToken(text[start:end]))
|
|
326
|
+
text = text[end:]
|
|
327
|
+
else:
|
|
328
|
+
yield from send(Token(text))
|
|
329
|
+
text = ""
|
|
330
|
+
|
|
331
|
+
elif isinstance(self._reading, ReadRegex):
|
|
332
|
+
self._reading._buffer.write(text)
|
|
333
|
+
match_text = self._reading._buffer.getvalue()
|
|
334
|
+
if (
|
|
335
|
+
match := re.search(self._reading.regex, match_text, re.VERBOSE)
|
|
336
|
+
) is not None:
|
|
337
|
+
token_text = match_text[: match.start(0)]
|
|
338
|
+
if token_text:
|
|
339
|
+
yield from send(Token(token_text))
|
|
340
|
+
end = match.end(0)
|
|
341
|
+
yield from send(MatchToken(match.group(0), match))
|
|
342
|
+
text = text[end:]
|
|
343
|
+
else:
|
|
344
|
+
yield from send(Token(match_text))
|
|
345
|
+
text = ""
|
|
346
|
+
|
|
347
|
+
def parse(self) -> ParseResult[ParseType]:
|
|
348
|
+
yield from ()
|
|
349
|
+
|
|
350
|
+
|
|
351
|
+
if __name__ == "__main__":
|
|
352
|
+
# from rich import print
|
|
353
|
+
|
|
354
|
+
import string
|
|
355
|
+
|
|
356
|
+
class KeyValue(Pattern):
|
|
357
|
+
def check(self) -> PatternCheck:
|
|
358
|
+
"""Parses text in the form key:'value'
|
|
359
|
+
|
|
360
|
+
e.g
|
|
361
|
+
|
|
362
|
+
"""
|
|
363
|
+
key: str = ""
|
|
364
|
+
value: str = ""
|
|
365
|
+
is_letter = string.ascii_lowercase.__contains__
|
|
366
|
+
if not is_letter(character := (yield)):
|
|
367
|
+
return False
|
|
368
|
+
key += character
|
|
369
|
+
while is_letter(character := (yield)):
|
|
370
|
+
key += character
|
|
371
|
+
if character != ":":
|
|
372
|
+
return False
|
|
373
|
+
if (yield) != "'":
|
|
374
|
+
return False
|
|
375
|
+
while is_letter(character := (yield)):
|
|
376
|
+
value += character
|
|
377
|
+
if character != "'":
|
|
378
|
+
return False
|
|
379
|
+
self.value = (key, value)
|
|
380
|
+
return True
|
|
381
|
+
|
|
382
|
+
class TestParser(StreamParser):
|
|
383
|
+
def parse(self) -> ParseResult:
|
|
384
|
+
token = yield self.read_patterns(key_value=KeyValue())
|
|
385
|
+
print("!", repr(token))
|
|
386
|
+
yield token
|
|
387
|
+
while token := (yield self.read(1)):
|
|
388
|
+
print(repr(token))
|
|
389
|
+
# while True:
|
|
390
|
+
# token = yield self.read_until(":")
|
|
391
|
+
# if not token:
|
|
392
|
+
# break
|
|
393
|
+
# yield token
|
|
394
|
+
# if isinstance(token, SeparatorToken):
|
|
395
|
+
# break
|
|
396
|
+
# key += token.text
|
|
397
|
+
|
|
398
|
+
# while token := (yield self.read_regex(r"\'.*?\'")):
|
|
399
|
+
# yield token
|
|
400
|
+
# if isinstance(token, MatchToken):
|
|
401
|
+
# break
|
|
402
|
+
|
|
403
|
+
# string = yield self.read_regex("'.*?'")
|
|
404
|
+
# print("VALUE=", string)
|
|
405
|
+
|
|
406
|
+
# yield (yield self.read(3))
|
|
407
|
+
# while (text := (yield self.read_until("'"))) != "'":
|
|
408
|
+
# yield text
|
|
409
|
+
# yield text
|
|
410
|
+
# while (text := (yield self.read_until("'"))) != "'":
|
|
411
|
+
# yield text
|
|
412
|
+
# yield text
|
|
413
|
+
|
|
414
|
+
parser = TestParser()
|
|
415
|
+
|
|
416
|
+
for chunk in ["foo", ":", "'bar", "asd", "asdasd", "';"]:
|
|
417
|
+
for token in parser.feed(chunk):
|
|
418
|
+
print(repr(token))
|
toad/answer.py
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
from typing import Literal, NamedTuple
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
"""
|
|
5
|
+
allow_once - Allow this operation only this time
|
|
6
|
+
allow_always - Allow this operation and remember the choice
|
|
7
|
+
reject_once - Reject this operation only this time
|
|
8
|
+
reject_always - Reject this operation and remember the choice
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class Answer(NamedTuple):
|
|
13
|
+
"""An answer to a question posed by the agent."""
|
|
14
|
+
|
|
15
|
+
text: str
|
|
16
|
+
"""The textual response."""
|
|
17
|
+
id: str
|
|
18
|
+
"""The id of the response."""
|
|
19
|
+
kind: (
|
|
20
|
+
Literal["allow_once", "allow_always", "reject_once", "reject_always"] | None
|
|
21
|
+
) = None
|
|
22
|
+
"""Enumeration to potentially influence UI"""
|