repl-toolkit 1.2.0__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.
- repl_toolkit/__init__.py +70 -0
- repl_toolkit/actions/__init__.py +24 -0
- repl_toolkit/actions/action.py +223 -0
- repl_toolkit/actions/registry.py +564 -0
- repl_toolkit/async_repl.py +374 -0
- repl_toolkit/completion/__init__.py +15 -0
- repl_toolkit/completion/prefix.py +109 -0
- repl_toolkit/completion/shell_expansion.py +453 -0
- repl_toolkit/formatting.py +152 -0
- repl_toolkit/headless_repl.py +251 -0
- repl_toolkit/ptypes.py +122 -0
- repl_toolkit/tests/__init__.py +5 -0
- repl_toolkit/tests/conftest.py +79 -0
- repl_toolkit/tests/test_actions.py +578 -0
- repl_toolkit/tests/test_async_repl.py +381 -0
- repl_toolkit/tests/test_completion.py +656 -0
- repl_toolkit/tests/test_formatting.py +232 -0
- repl_toolkit/tests/test_headless.py +677 -0
- repl_toolkit/tests/test_types.py +174 -0
- repl_toolkit-1.2.0.dist-info/METADATA +761 -0
- repl_toolkit-1.2.0.dist-info/RECORD +24 -0
- repl_toolkit-1.2.0.dist-info/WHEEL +5 -0
- repl_toolkit-1.2.0.dist-info/licenses/LICENSE +21 -0
- repl_toolkit-1.2.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,453 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Environment variable and shell command expansion completer.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import os
|
|
6
|
+
import re
|
|
7
|
+
import subprocess
|
|
8
|
+
from typing import Iterable, List, Optional
|
|
9
|
+
|
|
10
|
+
from prompt_toolkit.completion import Completer, Completion
|
|
11
|
+
from prompt_toolkit.document import Document
|
|
12
|
+
from prompt_toolkit.formatted_text import FormattedText
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class ShellExpansionCompleter(Completer):
|
|
16
|
+
"""
|
|
17
|
+
Completer that expands environment variables and executes shell commands.
|
|
18
|
+
|
|
19
|
+
Supports two patterns:
|
|
20
|
+
- ${VAR_NAME}: Expands to environment variable value
|
|
21
|
+
- $(command): Executes shell command and shows output
|
|
22
|
+
|
|
23
|
+
For multi-line command output, offers:
|
|
24
|
+
- ALL: Complete with all lines (always includes full output)
|
|
25
|
+
- Individual lines: Select specific line (always includes full line content)
|
|
26
|
+
|
|
27
|
+
Display limits affect only the completion menu appearance, not the inserted content.
|
|
28
|
+
|
|
29
|
+
This class is designed to be extensible. Override these public methods to customize:
|
|
30
|
+
- execute_command(): Custom command execution logic
|
|
31
|
+
- process_command_output(): Transform command output before display
|
|
32
|
+
- filter_lines(): Custom line filtering
|
|
33
|
+
- format_command_completion(): Customize command completion display
|
|
34
|
+
- format_variable_completion(): Customize variable completion display
|
|
35
|
+
- truncate_display(): Custom truncation logic
|
|
36
|
+
|
|
37
|
+
Security: Commands only execute when user presses Tab (not automatically).
|
|
38
|
+
|
|
39
|
+
Args:
|
|
40
|
+
timeout: Command execution timeout in seconds (default: 2.0)
|
|
41
|
+
multiline_all: Include "ALL" option for multi-line output (default: True)
|
|
42
|
+
max_lines: Maximum lines to show in completion menu (default: 50)
|
|
43
|
+
ALL option always includes full output regardless of this limit
|
|
44
|
+
max_display_length: Maximum line length in completion menu (default: 80)
|
|
45
|
+
Actual completion text is never truncated
|
|
46
|
+
|
|
47
|
+
Example:
|
|
48
|
+
>>> completer = ShellExpansionCompleter()
|
|
49
|
+
>>> # User types: Hello ${USER}
|
|
50
|
+
>>> # Press Tab: Shows username completion
|
|
51
|
+
>>> # User types: Files: $(ls)
|
|
52
|
+
>>> # Press Tab: Shows file list completion options
|
|
53
|
+
|
|
54
|
+
>>> # Extend with custom behavior
|
|
55
|
+
>>> class CachedShellExpansion(ShellExpansionCompleter):
|
|
56
|
+
... def execute_command(self, command):
|
|
57
|
+
... # Add caching logic
|
|
58
|
+
... return super().execute_command(command)
|
|
59
|
+
"""
|
|
60
|
+
|
|
61
|
+
# Pattern to match ${VAR_NAME}
|
|
62
|
+
VAR_PATTERN = re.compile(r"\$\{([A-Za-z_][A-Za-z0-9_]*)\}")
|
|
63
|
+
|
|
64
|
+
# Pattern to match $(command)
|
|
65
|
+
CMD_PATTERN = re.compile(r"\$\(([^)]+)\)")
|
|
66
|
+
|
|
67
|
+
def __init__(
|
|
68
|
+
self,
|
|
69
|
+
timeout: float = 2.0,
|
|
70
|
+
multiline_all: bool = True,
|
|
71
|
+
max_lines: int = 50,
|
|
72
|
+
max_display_length: int = 80,
|
|
73
|
+
):
|
|
74
|
+
"""Initialize the completer."""
|
|
75
|
+
self.timeout = timeout
|
|
76
|
+
self.multiline_all = multiline_all
|
|
77
|
+
self.max_lines = max_lines
|
|
78
|
+
self.max_display_length = max_display_length
|
|
79
|
+
|
|
80
|
+
def get_completions(self, document: Document, complete_event) -> Iterable[Completion]:
|
|
81
|
+
"""
|
|
82
|
+
Get completions for environment variables and commands.
|
|
83
|
+
|
|
84
|
+
Args:
|
|
85
|
+
document: Current document
|
|
86
|
+
complete_event: Completion event
|
|
87
|
+
|
|
88
|
+
Yields:
|
|
89
|
+
Completion objects for patterns at cursor position
|
|
90
|
+
"""
|
|
91
|
+
text = document.text
|
|
92
|
+
cursor_pos = document.cursor_position
|
|
93
|
+
|
|
94
|
+
# Try command pattern first
|
|
95
|
+
for match in self.CMD_PATTERN.finditer(text):
|
|
96
|
+
if match.start() <= cursor_pos <= match.end():
|
|
97
|
+
# Cursor is within this command pattern
|
|
98
|
+
command = match.group(1).strip()
|
|
99
|
+
if command:
|
|
100
|
+
yield from self.complete_command(command, match, cursor_pos)
|
|
101
|
+
return
|
|
102
|
+
|
|
103
|
+
# Try environment variable pattern
|
|
104
|
+
for match in self.VAR_PATTERN.finditer(text):
|
|
105
|
+
if match.start() <= cursor_pos <= match.end():
|
|
106
|
+
# Cursor is within this variable pattern
|
|
107
|
+
var_name = match.group(1)
|
|
108
|
+
if var_name in os.environ:
|
|
109
|
+
value = os.environ[var_name]
|
|
110
|
+
start_pos = match.start() - cursor_pos
|
|
111
|
+
|
|
112
|
+
# Use public method for formatting
|
|
113
|
+
yield self.format_variable_completion(
|
|
114
|
+
var_name, value, start_pos, match.group(0)
|
|
115
|
+
)
|
|
116
|
+
return
|
|
117
|
+
|
|
118
|
+
def truncate_display(self, text: str) -> str:
|
|
119
|
+
"""
|
|
120
|
+
Truncate text for display purposes only.
|
|
121
|
+
|
|
122
|
+
Override this method to customize truncation behavior.
|
|
123
|
+
|
|
124
|
+
Args:
|
|
125
|
+
text: Text to potentially truncate
|
|
126
|
+
|
|
127
|
+
Returns:
|
|
128
|
+
Truncated text with ellipsis if needed, or original text
|
|
129
|
+
"""
|
|
130
|
+
if len(text) > self.max_display_length:
|
|
131
|
+
return text[: self.max_display_length - 3] + "..."
|
|
132
|
+
return text
|
|
133
|
+
|
|
134
|
+
def execute_command(self, command: str) -> subprocess.CompletedProcess:
|
|
135
|
+
"""
|
|
136
|
+
Execute shell command.
|
|
137
|
+
|
|
138
|
+
Override this method to customize command execution (e.g., add caching,
|
|
139
|
+
security filtering, or use a different execution mechanism).
|
|
140
|
+
|
|
141
|
+
Args:
|
|
142
|
+
command: Shell command to execute
|
|
143
|
+
|
|
144
|
+
Returns:
|
|
145
|
+
CompletedProcess with stdout, stderr, and returncode
|
|
146
|
+
|
|
147
|
+
Raises:
|
|
148
|
+
subprocess.TimeoutExpired: If command exceeds timeout
|
|
149
|
+
subprocess.SubprocessError: For other execution errors
|
|
150
|
+
"""
|
|
151
|
+
return subprocess.run(
|
|
152
|
+
command, shell=True, capture_output=True, text=True, timeout=self.timeout
|
|
153
|
+
)
|
|
154
|
+
|
|
155
|
+
def process_command_output(self, output: str, command: str) -> str:
|
|
156
|
+
"""
|
|
157
|
+
Process command output before creating completions.
|
|
158
|
+
|
|
159
|
+
Override this method to transform, filter, or modify command output.
|
|
160
|
+
|
|
161
|
+
Args:
|
|
162
|
+
output: Raw command output
|
|
163
|
+
command: The command that was executed
|
|
164
|
+
|
|
165
|
+
Returns:
|
|
166
|
+
Processed output string
|
|
167
|
+
"""
|
|
168
|
+
return output.strip()
|
|
169
|
+
|
|
170
|
+
def filter_lines(self, lines: List[str]) -> List[str]:
|
|
171
|
+
"""
|
|
172
|
+
Filter lines from command output.
|
|
173
|
+
|
|
174
|
+
Override this method to implement custom line filtering logic.
|
|
175
|
+
Default implementation removes empty lines.
|
|
176
|
+
|
|
177
|
+
Args:
|
|
178
|
+
lines: List of output lines
|
|
179
|
+
|
|
180
|
+
Returns:
|
|
181
|
+
Filtered list of lines
|
|
182
|
+
"""
|
|
183
|
+
return [line for line in lines if line.strip()]
|
|
184
|
+
|
|
185
|
+
def format_variable_completion(
|
|
186
|
+
self, var_name: str, value: str, start_pos: int, pattern_text: str
|
|
187
|
+
) -> Completion:
|
|
188
|
+
"""
|
|
189
|
+
Format environment variable completion.
|
|
190
|
+
|
|
191
|
+
Override this method to customize how variable completions are displayed.
|
|
192
|
+
|
|
193
|
+
Args:
|
|
194
|
+
var_name: Variable name (without ${})
|
|
195
|
+
value: Variable value
|
|
196
|
+
start_pos: Start position for replacement
|
|
197
|
+
pattern_text: Original pattern text like ${VAR}
|
|
198
|
+
|
|
199
|
+
Returns:
|
|
200
|
+
Completion object
|
|
201
|
+
"""
|
|
202
|
+
display_value = self.truncate_display(value)
|
|
203
|
+
|
|
204
|
+
return Completion(
|
|
205
|
+
text=value,
|
|
206
|
+
start_position=start_pos,
|
|
207
|
+
display=FormattedText(
|
|
208
|
+
[
|
|
209
|
+
("class:completion.var", "${"),
|
|
210
|
+
("class:completion.var.name", var_name),
|
|
211
|
+
("class:completion.var", "}"),
|
|
212
|
+
("class:completion.arrow", " → "),
|
|
213
|
+
("class:completion.value", display_value),
|
|
214
|
+
]
|
|
215
|
+
),
|
|
216
|
+
display_meta=FormattedText([("class:completion.meta", "Environment variable")]),
|
|
217
|
+
)
|
|
218
|
+
|
|
219
|
+
def format_command_completion(
|
|
220
|
+
self, command_output: str, pattern_text: str, start_pos: int, label: Optional[str] = None
|
|
221
|
+
) -> Completion:
|
|
222
|
+
"""
|
|
223
|
+
Format single-line command completion.
|
|
224
|
+
|
|
225
|
+
Override this method to customize how command completions are displayed.
|
|
226
|
+
|
|
227
|
+
Args:
|
|
228
|
+
command_output: Command output text
|
|
229
|
+
pattern_text: Original pattern text like $(command)
|
|
230
|
+
start_pos: Start position for replacement
|
|
231
|
+
label: Optional label for the completion
|
|
232
|
+
|
|
233
|
+
Returns:
|
|
234
|
+
Completion object
|
|
235
|
+
"""
|
|
236
|
+
display_text = self.truncate_display(command_output)
|
|
237
|
+
|
|
238
|
+
return Completion(
|
|
239
|
+
text=command_output,
|
|
240
|
+
start_position=start_pos,
|
|
241
|
+
display=FormattedText(
|
|
242
|
+
[
|
|
243
|
+
("class:completion.cmd", pattern_text),
|
|
244
|
+
("class:completion.arrow", " → "),
|
|
245
|
+
("class:completion.value", display_text),
|
|
246
|
+
]
|
|
247
|
+
),
|
|
248
|
+
display_meta=FormattedText([("class:completion.meta", "Shell command")]),
|
|
249
|
+
)
|
|
250
|
+
|
|
251
|
+
def format_multiline_completion(
|
|
252
|
+
self, line_text: str, line_number: int, pattern_text: str, start_pos: int
|
|
253
|
+
) -> Completion:
|
|
254
|
+
"""
|
|
255
|
+
Format individual line completion for multi-line output.
|
|
256
|
+
|
|
257
|
+
Override this method to customize multi-line completion display.
|
|
258
|
+
|
|
259
|
+
Args:
|
|
260
|
+
line_text: Text of the line
|
|
261
|
+
line_number: Line number (1-based)
|
|
262
|
+
pattern_text: Original pattern text like $(command)
|
|
263
|
+
start_pos: Start position for replacement
|
|
264
|
+
|
|
265
|
+
Returns:
|
|
266
|
+
Completion object
|
|
267
|
+
"""
|
|
268
|
+
display_line = self.truncate_display(line_text)
|
|
269
|
+
|
|
270
|
+
return Completion(
|
|
271
|
+
text=line_text,
|
|
272
|
+
start_position=start_pos,
|
|
273
|
+
display=FormattedText(
|
|
274
|
+
[
|
|
275
|
+
("class:completion.cmd", pattern_text),
|
|
276
|
+
("class:completion.arrow", " → "),
|
|
277
|
+
("class:completion.line", f"Line {line_number}: "),
|
|
278
|
+
("class:completion.value", display_line),
|
|
279
|
+
]
|
|
280
|
+
),
|
|
281
|
+
display_meta=FormattedText([("class:completion.meta", "Shell command")]),
|
|
282
|
+
)
|
|
283
|
+
|
|
284
|
+
def format_all_lines_completion(
|
|
285
|
+
self, full_output: str, line_count: int, pattern_text: str, start_pos: int
|
|
286
|
+
) -> Completion:
|
|
287
|
+
"""
|
|
288
|
+
Format "ALL" completion for multi-line output.
|
|
289
|
+
|
|
290
|
+
Override this method to customize the ALL option display.
|
|
291
|
+
|
|
292
|
+
Args:
|
|
293
|
+
full_output: Complete command output with newlines
|
|
294
|
+
line_count: Number of non-empty lines
|
|
295
|
+
pattern_text: Original pattern text like $(command)
|
|
296
|
+
start_pos: Start position for replacement
|
|
297
|
+
|
|
298
|
+
Returns:
|
|
299
|
+
Completion object
|
|
300
|
+
"""
|
|
301
|
+
return Completion(
|
|
302
|
+
text=full_output,
|
|
303
|
+
start_position=start_pos,
|
|
304
|
+
display=FormattedText(
|
|
305
|
+
[
|
|
306
|
+
("class:completion.cmd", pattern_text),
|
|
307
|
+
("class:completion.arrow", " → "),
|
|
308
|
+
("class:completion.multiline", f"ALL ({line_count} lines)"),
|
|
309
|
+
]
|
|
310
|
+
),
|
|
311
|
+
display_meta=FormattedText([("class:completion.meta", "Shell command")]),
|
|
312
|
+
)
|
|
313
|
+
|
|
314
|
+
def format_error_completion(
|
|
315
|
+
self, error_message: str, pattern_text: str, start_pos: int
|
|
316
|
+
) -> Completion:
|
|
317
|
+
"""
|
|
318
|
+
Format error completion.
|
|
319
|
+
|
|
320
|
+
Override this method to customize error display.
|
|
321
|
+
|
|
322
|
+
Args:
|
|
323
|
+
error_message: Error message to display
|
|
324
|
+
pattern_text: Original pattern text like $(command)
|
|
325
|
+
start_pos: Start position for replacement
|
|
326
|
+
|
|
327
|
+
Returns:
|
|
328
|
+
Completion object with empty text
|
|
329
|
+
"""
|
|
330
|
+
error_display = self.truncate_display(error_message)
|
|
331
|
+
|
|
332
|
+
return Completion(
|
|
333
|
+
text="",
|
|
334
|
+
start_position=start_pos,
|
|
335
|
+
display=FormattedText(
|
|
336
|
+
[
|
|
337
|
+
("class:completion.cmd", pattern_text),
|
|
338
|
+
("class:completion.arrow", " → "),
|
|
339
|
+
("class:completion.error", f"Error: {error_display}"),
|
|
340
|
+
]
|
|
341
|
+
),
|
|
342
|
+
display_meta=FormattedText([("class:completion.meta", "Shell command")]),
|
|
343
|
+
)
|
|
344
|
+
|
|
345
|
+
def complete_command(self, command: str, match, cursor_pos: int) -> Iterable[Completion]:
|
|
346
|
+
"""
|
|
347
|
+
Execute command and yield completion(s).
|
|
348
|
+
|
|
349
|
+
This is the main public method for command completion. Override to change
|
|
350
|
+
the overall completion flow.
|
|
351
|
+
|
|
352
|
+
Args:
|
|
353
|
+
command: Shell command to execute
|
|
354
|
+
match: Regex match object
|
|
355
|
+
cursor_pos: Cursor position
|
|
356
|
+
|
|
357
|
+
Yields:
|
|
358
|
+
Completion objects with command output
|
|
359
|
+
"""
|
|
360
|
+
start_pos = match.start() - cursor_pos
|
|
361
|
+
pattern_text = match.group(0)
|
|
362
|
+
|
|
363
|
+
try:
|
|
364
|
+
result = self.execute_command(command)
|
|
365
|
+
|
|
366
|
+
if result.returncode == 0:
|
|
367
|
+
output = self.process_command_output(result.stdout, command)
|
|
368
|
+
|
|
369
|
+
if not output:
|
|
370
|
+
# Command succeeded but no output
|
|
371
|
+
yield Completion(
|
|
372
|
+
text="",
|
|
373
|
+
start_position=start_pos,
|
|
374
|
+
display=FormattedText(
|
|
375
|
+
[
|
|
376
|
+
("class:completion.cmd", pattern_text),
|
|
377
|
+
("class:completion.arrow", " → "),
|
|
378
|
+
("class:completion.info", "(no output)"),
|
|
379
|
+
]
|
|
380
|
+
),
|
|
381
|
+
display_meta=FormattedText([("class:completion.meta", "Shell command")]),
|
|
382
|
+
)
|
|
383
|
+
else:
|
|
384
|
+
# Check if multi-line output
|
|
385
|
+
lines = output.split("\n")
|
|
386
|
+
non_empty_lines = self.filter_lines(lines)
|
|
387
|
+
|
|
388
|
+
if len(non_empty_lines) > 1:
|
|
389
|
+
# Multi-line output
|
|
390
|
+
yield from self.complete_multiline(
|
|
391
|
+
output, non_empty_lines, pattern_text, start_pos
|
|
392
|
+
)
|
|
393
|
+
else:
|
|
394
|
+
# Single line output
|
|
395
|
+
yield self.format_command_completion(output, pattern_text, start_pos)
|
|
396
|
+
else:
|
|
397
|
+
# Command failed
|
|
398
|
+
error_msg = result.stderr.strip() or f"Exit code {result.returncode}"
|
|
399
|
+
yield self.format_error_completion(error_msg, pattern_text, start_pos)
|
|
400
|
+
|
|
401
|
+
except subprocess.TimeoutExpired:
|
|
402
|
+
yield self.format_error_completion(
|
|
403
|
+
f"Timeout ({self.timeout}s)", pattern_text, start_pos
|
|
404
|
+
)
|
|
405
|
+
except FileNotFoundError:
|
|
406
|
+
yield self.format_error_completion("Command not found", pattern_text, start_pos)
|
|
407
|
+
|
|
408
|
+
def complete_multiline(
|
|
409
|
+
self, full_output: str, lines: list, pattern_text: str, start_pos: int
|
|
410
|
+
) -> Iterable[Completion]:
|
|
411
|
+
"""
|
|
412
|
+
Yield completions for multi-line command output.
|
|
413
|
+
|
|
414
|
+
Override this method to change multi-line completion behavior.
|
|
415
|
+
|
|
416
|
+
Args:
|
|
417
|
+
full_output: Complete command output (with newlines)
|
|
418
|
+
lines: List of non-empty lines
|
|
419
|
+
pattern_text: Original pattern text like $(command)
|
|
420
|
+
start_pos: Start position for replacement
|
|
421
|
+
|
|
422
|
+
Yields:
|
|
423
|
+
Completion for ALL and individual lines (up to max_lines limit)
|
|
424
|
+
"""
|
|
425
|
+
total_lines = len(lines)
|
|
426
|
+
|
|
427
|
+
# First option: ALL (complete with all lines - never truncated)
|
|
428
|
+
if self.multiline_all:
|
|
429
|
+
yield self.format_all_lines_completion(
|
|
430
|
+
full_output, total_lines, pattern_text, start_pos
|
|
431
|
+
)
|
|
432
|
+
|
|
433
|
+
# Individual line options (limited to max_lines)
|
|
434
|
+
lines_to_show = lines[: self.max_lines]
|
|
435
|
+
remaining = total_lines - len(lines_to_show)
|
|
436
|
+
|
|
437
|
+
for i, line in enumerate(lines_to_show, 1):
|
|
438
|
+
yield self.format_multiline_completion(line, i, pattern_text, start_pos)
|
|
439
|
+
|
|
440
|
+
# If there are more lines, show an indicator
|
|
441
|
+
if remaining > 0:
|
|
442
|
+
yield Completion(
|
|
443
|
+
text="", # No completion action
|
|
444
|
+
start_position=start_pos,
|
|
445
|
+
display=FormattedText(
|
|
446
|
+
[
|
|
447
|
+
("class:completion.cmd", pattern_text),
|
|
448
|
+
("class:completion.arrow", " → "),
|
|
449
|
+
("class:completion.info", f"({remaining} more lines... use ALL)"),
|
|
450
|
+
]
|
|
451
|
+
),
|
|
452
|
+
display_meta=FormattedText([("class:completion.meta", "Shell command")]),
|
|
453
|
+
)
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Formatted text utilities for repl_toolkit.
|
|
3
|
+
|
|
4
|
+
This module provides utilities for working with formatted text in prompt_toolkit,
|
|
5
|
+
including auto-detection of format types and smart printing.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import re
|
|
9
|
+
from typing import Callable
|
|
10
|
+
|
|
11
|
+
from prompt_toolkit import print_formatted_text
|
|
12
|
+
from prompt_toolkit.formatted_text import ANSI, HTML
|
|
13
|
+
|
|
14
|
+
# Pre-compile regex patterns for performance
|
|
15
|
+
_ANSI_PATTERN = re.compile(r"\x1b\[[0-9;]*m")
|
|
16
|
+
_HTML_PATTERN = re.compile(r"</?[a-zA-Z][a-zA-Z0-9]*\s*/?>")
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def detect_format_type(text: str) -> str:
|
|
20
|
+
"""
|
|
21
|
+
Detect the format type of a text string.
|
|
22
|
+
|
|
23
|
+
Detects three format types:
|
|
24
|
+
- 'ansi': Text contains ANSI escape codes (e.g., \\x1b[1m)
|
|
25
|
+
- 'html': Text contains HTML-like tags (e.g., <b>, <darkcyan>)
|
|
26
|
+
- 'plain': Plain text with no special formatting
|
|
27
|
+
|
|
28
|
+
Args:
|
|
29
|
+
text: Text string to analyze
|
|
30
|
+
|
|
31
|
+
Returns:
|
|
32
|
+
Format type: 'ansi', 'html', or 'plain'
|
|
33
|
+
|
|
34
|
+
Examples:
|
|
35
|
+
>>> detect_format_type("<b>Bold</b>")
|
|
36
|
+
'html'
|
|
37
|
+
>>> detect_format_type("\\x1b[1mBold\\x1b[0m")
|
|
38
|
+
'ansi'
|
|
39
|
+
>>> detect_format_type("Plain text")
|
|
40
|
+
'plain'
|
|
41
|
+
>>> detect_format_type("a < b and c > d")
|
|
42
|
+
'plain'
|
|
43
|
+
"""
|
|
44
|
+
# Check for ANSI escape codes (most specific)
|
|
45
|
+
if _ANSI_PATTERN.search(text):
|
|
46
|
+
return "ansi"
|
|
47
|
+
|
|
48
|
+
# Check for HTML tags
|
|
49
|
+
# Valid HTML tag names: start with letter, contain letters/numbers
|
|
50
|
+
if _HTML_PATTERN.search(text):
|
|
51
|
+
return "html"
|
|
52
|
+
|
|
53
|
+
# Plain text
|
|
54
|
+
return "plain"
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def auto_format(text: str):
|
|
58
|
+
"""
|
|
59
|
+
Auto-detect format type and return appropriate formatted text object.
|
|
60
|
+
|
|
61
|
+
This function analyzes the input text and wraps it in the appropriate
|
|
62
|
+
prompt_toolkit formatted text type (HTML, ANSI, or plain string).
|
|
63
|
+
|
|
64
|
+
Args:
|
|
65
|
+
text: Text string to format
|
|
66
|
+
|
|
67
|
+
Returns:
|
|
68
|
+
Formatted text object (HTML, ANSI, or str)
|
|
69
|
+
|
|
70
|
+
Examples:
|
|
71
|
+
>>> auto_format("<b>Bold</b>")
|
|
72
|
+
HTML('<b>Bold</b>')
|
|
73
|
+
>>> auto_format("\\x1b[1mBold\\x1b[0m")
|
|
74
|
+
ANSI('\\x1b[1mBold\\x1b[0m')
|
|
75
|
+
>>> auto_format("Plain text")
|
|
76
|
+
'Plain text'
|
|
77
|
+
"""
|
|
78
|
+
format_type = detect_format_type(text)
|
|
79
|
+
|
|
80
|
+
if format_type == "ansi":
|
|
81
|
+
return ANSI(text)
|
|
82
|
+
elif format_type == "html":
|
|
83
|
+
return HTML(text)
|
|
84
|
+
else:
|
|
85
|
+
return text
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def print_auto_formatted(text: str, **kwargs) -> None:
|
|
89
|
+
"""
|
|
90
|
+
Print text with auto-detected formatting.
|
|
91
|
+
|
|
92
|
+
This function automatically detects the format type (HTML, ANSI, or plain)
|
|
93
|
+
and prints the text with appropriate formatting applied.
|
|
94
|
+
|
|
95
|
+
Args:
|
|
96
|
+
text: Text to print (may contain HTML tags, ANSI codes, or be plain)
|
|
97
|
+
**kwargs: Additional arguments passed to print_formatted_text
|
|
98
|
+
(e.g., end, flush, style, output)
|
|
99
|
+
|
|
100
|
+
Examples:
|
|
101
|
+
>>> print_auto_formatted("<b>Bold</b> text")
|
|
102
|
+
Bold text
|
|
103
|
+
>>> print_auto_formatted("\\x1b[1mBold\\x1b[0m text")
|
|
104
|
+
Bold text
|
|
105
|
+
>>> print_auto_formatted("Plain text")
|
|
106
|
+
Plain text
|
|
107
|
+
"""
|
|
108
|
+
formatted = auto_format(text)
|
|
109
|
+
print_formatted_text(formatted, **kwargs)
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def create_auto_printer() -> Callable:
|
|
113
|
+
"""
|
|
114
|
+
Create a printer function with auto-format detection.
|
|
115
|
+
|
|
116
|
+
Returns a callable that can be used as a drop-in replacement for print(),
|
|
117
|
+
with automatic detection and application of formatting (HTML or ANSI).
|
|
118
|
+
|
|
119
|
+
This is particularly useful for injecting into callback handlers or other
|
|
120
|
+
components that accept a custom printer function.
|
|
121
|
+
|
|
122
|
+
Returns:
|
|
123
|
+
Callable printer function with signature: printer(text, **kwargs)
|
|
124
|
+
|
|
125
|
+
Examples:
|
|
126
|
+
>>> printer = create_auto_printer()
|
|
127
|
+
>>> printer("<b>Bold</b>", end="", flush=True)
|
|
128
|
+
Bold
|
|
129
|
+
>>> printer(" text\\n")
|
|
130
|
+
text
|
|
131
|
+
|
|
132
|
+
Usage with callback handlers:
|
|
133
|
+
>>> from some_library import CallbackHandler
|
|
134
|
+
>>> handler = CallbackHandler(
|
|
135
|
+
... response_prefix="<b>Bot:</b> ",
|
|
136
|
+
... printer=create_auto_printer()
|
|
137
|
+
... )
|
|
138
|
+
"""
|
|
139
|
+
|
|
140
|
+
def printer(text: str, **kwargs):
|
|
141
|
+
"""Auto-format and print text."""
|
|
142
|
+
print_auto_formatted(text, **kwargs)
|
|
143
|
+
|
|
144
|
+
return printer
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
__all__ = [
|
|
148
|
+
"detect_format_type",
|
|
149
|
+
"auto_format",
|
|
150
|
+
"print_auto_formatted",
|
|
151
|
+
"create_auto_printer",
|
|
152
|
+
]
|