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.
@@ -0,0 +1,761 @@
1
+ Metadata-Version: 2.4
2
+ Name: repl-toolkit
3
+ Version: 1.2.0
4
+ Summary: A Python toolkit for building interactive REPL and headless interfaces with action support
5
+ Author-email: REPL Toolkit Contributors <martin.j.bartlett@gmail.com>
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/bassmanitram/repl-toolkit
8
+ Project-URL: Documentation, https://repl-toolkit.readthedocs.io/
9
+ Project-URL: Repository, https://github.com/bassmanitram/repl-toolkit.git
10
+ Project-URL: Bug Tracker, https://github.com/bassmanitram/repl-toolkit/issues
11
+ Keywords: repl,cli,interactive,chat,toolkit,actions,keyboard,shortcuts
12
+ Classifier: Development Status :: 5 - Production/Stable
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: License :: OSI Approved :: MIT License
15
+ Classifier: Operating System :: OS Independent
16
+ Classifier: Programming Language :: Python :: 3
17
+ Classifier: Programming Language :: Python :: 3.8
18
+ Classifier: Programming Language :: Python :: 3.9
19
+ Classifier: Programming Language :: Python :: 3.10
20
+ Classifier: Programming Language :: Python :: 3.11
21
+ Classifier: Programming Language :: Python :: 3.12
22
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
23
+ Classifier: Topic :: Software Development :: User Interfaces
24
+ Classifier: Topic :: System :: Shells
25
+ Requires-Python: >=3.8
26
+ Description-Content-Type: text/markdown
27
+ License-File: LICENSE
28
+ Requires-Dist: prompt-toolkit>=3.0.0
29
+ Requires-Dist: loguru>=0.5.0
30
+ Provides-Extra: test
31
+ Requires-Dist: pytest>=6.0; extra == "test"
32
+ Requires-Dist: pytest-asyncio>=0.18.0; extra == "test"
33
+ Requires-Dist: pytest-cov>=3.0.0; extra == "test"
34
+ Provides-Extra: dev
35
+ Requires-Dist: black>=22.0.0; extra == "dev"
36
+ Requires-Dist: isort>=5.0.0; extra == "dev"
37
+ Requires-Dist: flake8>=4.0.0; extra == "dev"
38
+ Requires-Dist: mypy>=0.991; extra == "dev"
39
+ Requires-Dist: build>=0.8.0; extra == "dev"
40
+ Requires-Dist: twine>=4.0.0; extra == "dev"
41
+ Requires-Dist: pre-commit>=3.0.0; extra == "dev"
42
+ Requires-Dist: bandit>=1.7.0; extra == "dev"
43
+ Requires-Dist: safety>=2.0.0; extra == "dev"
44
+ Dynamic: license-file
45
+
46
+ # REPL Toolkit
47
+
48
+ [![PyPI version](https://badge.fury.io/py/repl-toolkit.svg)](https://badge.fury.io/py/repl-toolkit)
49
+
50
+ A Python toolkit for building interactive REPL and headless interfaces with support for both commands and keyboard shortcuts, featuring late backend binding for resource context scenarios.
51
+
52
+ ## Key Features
53
+
54
+ ### Action System
55
+ - **Single Definition**: One action, multiple triggers (command + shortcut)
56
+ - **Flexible Binding**: Command-only, shortcut-only, or both
57
+ - **Context Aware**: Actions know how they were triggered
58
+ - **Dynamic Registration**: Add actions at runtime
59
+ - **Category Organization**: Organize actions for better help systems
60
+
61
+ ### Developer Experience
62
+ - **Protocol-Based**: Type-safe interfaces with runtime checking
63
+ - **Easy Extension**: Simple inheritance and registration patterns
64
+ - **Rich Help System**: Automatic help generation with usage examples
65
+ - **Error Handling**: Comprehensive error handling and user feedback
66
+ - **Async Native**: Built for modern async Python applications
67
+ - **Late Backend Binding**: Initialize REPL before backend is available
68
+
69
+ ### Production Ready
70
+ - **Comprehensive Tests**: Full test coverage with pytest
71
+ - **Documentation**: Complete API documentation and examples
72
+ - **Performance**: Efficient action lookup and execution
73
+ - **Logging**: Structured logging with loguru integration
74
+ - **Headless Support**: Non-interactive mode for automation and testing
75
+
76
+ ## Installation
77
+
78
+ ```bash
79
+ pip install repl-toolkit
80
+ ```
81
+
82
+ **Dependencies:**
83
+ - Python 3.8+
84
+ - prompt-toolkit >= 3.0.0
85
+ - loguru >= 0.5.0
86
+
87
+ ## Quick Start
88
+
89
+ ### Basic Usage
90
+
91
+ ```python
92
+ import asyncio
93
+ from repl_toolkit import run_async_repl, ActionRegistry, Action
94
+
95
+ # Your backend that processes user input
96
+ class MyBackend:
97
+ async def handle_input(self, user_input: str) -> bool:
98
+ print(f"You said: {user_input}")
99
+ return True
100
+
101
+ # Create action registry with custom actions
102
+ class MyActions(ActionRegistry):
103
+ def __init__(self):
104
+ super().__init__()
105
+
106
+ # Add action with both command and shortcut
107
+ self.register_action(
108
+ name="save_data",
109
+ description="Save current data",
110
+ category="File",
111
+ handler=self._save_data,
112
+ command="/save",
113
+ command_usage="/save [filename] - Save data to file",
114
+ keys="ctrl-s",
115
+ keys_description="Quick save"
116
+ )
117
+
118
+ def _save_data(self, context):
119
+ # Access backend through context
120
+ backend = context.backend
121
+ filename = context.args[0] if context.args else "data.txt"
122
+ print(f"Saving to {filename}")
123
+ if context.triggered_by == "shortcut":
124
+ print(" (Triggered by Ctrl+S)")
125
+
126
+ # Run the REPL with late backend binding
127
+ async def main():
128
+ actions = MyActions()
129
+ backend = MyBackend()
130
+
131
+ await run_async_repl(
132
+ backend=backend,
133
+ action_registry=actions,
134
+ prompt_string="My App: "
135
+ )
136
+
137
+ if __name__ == "__main__":
138
+ asyncio.run(main())
139
+ ```
140
+
141
+ ### Resource Context Pattern
142
+
143
+ The late backend binding pattern is useful when your backend requires resources that are only available within a specific context:
144
+
145
+ ```python
146
+ import asyncio
147
+ from repl_toolkit import AsyncREPL, ActionRegistry
148
+
149
+ class DatabaseBackend:
150
+ def __init__(self, db_connection):
151
+ self.db = db_connection
152
+
153
+ async def handle_input(self, user_input: str) -> bool:
154
+ # Use database connection
155
+ result = await self.db.query(user_input)
156
+ print(f"Query result: {result}")
157
+ return True
158
+
159
+ async def main():
160
+ # Create REPL without backend (backend not available yet)
161
+ actions = ActionRegistry()
162
+ repl = AsyncREPL(action_registry=actions)
163
+
164
+ # Backend only available within resource context
165
+ async with get_database_connection() as db:
166
+ backend = DatabaseBackend(db)
167
+ # Now run REPL with backend
168
+ await repl.run(backend, "Database connected!")
169
+
170
+ asyncio.run(main())
171
+ ```
172
+
173
+ Users can now:
174
+ - Type `/save myfile.txt` OR press `Ctrl+S`
175
+ - Type `/help` OR press `F1` for help
176
+ - All actions work seamlessly both ways
177
+
178
+ ## Core Concepts
179
+
180
+ ### Actions
181
+
182
+ Actions are the heart of the extension system. Each action can be triggered by:
183
+ - **Commands**: Typed commands like `/help` or `/save filename`
184
+ - **Keyboard Shortcuts**: Key combinations like `F1` or `Ctrl+S`
185
+ - **Programmatic**: Direct execution in code
186
+
187
+ ```python
188
+ from repl_toolkit import Action
189
+
190
+ # Both command and shortcut
191
+ action = Action(
192
+ name="my_action",
193
+ description="Does something useful",
194
+ category="Utilities",
195
+ handler=my_handler_function,
196
+ command="/myaction",
197
+ command_usage="/myaction [args] - Does something useful",
198
+ keys="F5",
199
+ keys_description="Quick action trigger"
200
+ )
201
+
202
+ # Command-only action
203
+ cmd_action = Action(
204
+ name="command_only",
205
+ description="Command-only functionality",
206
+ category="Commands",
207
+ handler=cmd_handler,
208
+ command="/cmdonly"
209
+ )
210
+
211
+ # Shortcut-only action
212
+ key_action = Action(
213
+ name="shortcut_only",
214
+ description="Keyboard shortcut",
215
+ category="Shortcuts",
216
+ handler=key_handler,
217
+ keys="ctrl-k",
218
+ keys_description="Special shortcut"
219
+ )
220
+ ```
221
+
222
+ ### Action Registry
223
+
224
+ The `ActionRegistry` manages all actions and provides the interface between the REPL and your application logic:
225
+
226
+ ```python
227
+ from repl_toolkit import ActionRegistry
228
+
229
+ class MyRegistry(ActionRegistry):
230
+ def __init__(self):
231
+ super().__init__()
232
+ self._register_my_actions()
233
+
234
+ def _register_my_actions(self):
235
+ # Command + shortcut
236
+ self.register_action(
237
+ name="action_name",
238
+ description="What it does",
239
+ category="Category",
240
+ handler=self._handler_method,
241
+ command="/cmd",
242
+ keys="F2"
243
+ )
244
+
245
+ def _handler_method(self, context):
246
+ # Access backend through context
247
+ backend = context.backend
248
+ if backend:
249
+ # Use backend
250
+ pass
251
+ ```
252
+
253
+ ### Action Context
254
+
255
+ Action handlers receive rich context about how they were invoked:
256
+
257
+ ```python
258
+ def my_handler(context: ActionContext):
259
+ # Access the registry and backend
260
+ registry = context.registry
261
+ backend = context.backend # Available after run() is called
262
+
263
+ # Different context based on trigger method
264
+ if context.triggered_by == "command":
265
+ args = context.args # Command arguments
266
+ print(f"Command args: {args}")
267
+
268
+ elif context.triggered_by == "shortcut":
269
+ event = context.event # Keyboard event
270
+ print("Triggered by keyboard shortcut")
271
+
272
+ # Original user input (for commands)
273
+ if context.user_input:
274
+ print(f"Full input: {context.user_input}")
275
+ ```
276
+
277
+ ## Built-in Actions
278
+
279
+ Every registry comes with built-in actions:
280
+
281
+ | Action | Command | Shortcut | Description |
282
+ |--------|---------|----------|-------------|
283
+ | **Help** | `/help [action]` | `F1` | Show help for all actions or specific action |
284
+ | **Shortcuts** | `/shortcuts` | - | List all keyboard shortcuts |
285
+ | **Shell** | `/shell [cmd]` | - | Drop to interactive shell or run command |
286
+ | **Exit** | `/exit` | - | Exit the application |
287
+ | **Quit** | `/quit` | - | Quit the application |
288
+
289
+ ## Keyboard Shortcuts
290
+
291
+ The system supports rich keyboard shortcut definitions:
292
+
293
+ ```python
294
+ # Function keys
295
+ keys="F1" # F1
296
+ keys="F12" # F12
297
+
298
+ # Modifier combinations
299
+ keys="ctrl-s" # Ctrl+S
300
+ keys="alt-h" # Alt+H
301
+ keys="shift-tab" # Shift+Tab
302
+
303
+ # Complex combinations
304
+ keys="ctrl-alt-d" # Ctrl+Alt+D
305
+
306
+ # Multiple shortcuts for same action
307
+ keys=["F5", "ctrl-r"] # Either F5 OR Ctrl+R
308
+ ```
309
+
310
+ ## Headless Mode
311
+
312
+ For automation, testing, and batch processing:
313
+
314
+ ```python
315
+ import asyncio
316
+ from repl_toolkit import run_headless_mode
317
+
318
+ class BatchBackend:
319
+ async def handle_input(self, user_input: str) -> bool:
320
+ # Process input without user interaction
321
+ result = await process_batch_input(user_input)
322
+ return result
323
+
324
+ async def main():
325
+ backend = BatchBackend()
326
+
327
+ # Process initial message, then read from stdin
328
+ success = await run_headless_mode(
329
+ backend=backend,
330
+ initial_message="Starting batch processing"
331
+ )
332
+
333
+ return 0 if success else 1
334
+
335
+ # Usage:
336
+ # echo -e "Line 1\nLine 2\n/send\nLine 3" | python script.py
337
+ ```
338
+
339
+ ### Headless Features
340
+
341
+ - **stdin Processing**: Reads input line by line from stdin
342
+ - **Buffer Accumulation**: Content lines accumulate until `/send` command
343
+ - **Multiple Send Cycles**: Support for multiple `/send` operations
344
+ - **Command Processing**: Full action system support in headless mode
345
+ - **EOF Handling**: Automatically sends remaining buffer on EOF
346
+
347
+ ## Architecture
348
+
349
+ ### Late Backend Binding
350
+
351
+ The architecture supports late backend binding, allowing you to initialize the REPL before the backend is available:
352
+
353
+ ```
354
+ ┌─────────────────┐ ┌──────────────────┐ ┌─────────────────┐
355
+ │ AsyncREPL │───▶│ ActionRegistry │ │ Your Backend │
356
+ │ (Interface) │ │ (Action System) │ │ (Available Later)│
357
+ └─────────────────┘ └──────────────────┘ └─────────────────┘
358
+ │ │ │
359
+ ▼ ▼ ▼
360
+ ┌─────────────────┐ ┌──────────────────┐ ┌─────────────────┐
361
+ │ prompt_toolkit │ │ Actions │ │ Resource Context│
362
+ │ (Terminal) │ │ (Commands+Keys) │ │ (DB, API, etc.)│
363
+ └─────────────────┘ └──────────────────┘ └─────────────────┘
364
+ ```
365
+
366
+ ### Protocol-Based Design
367
+
368
+ The toolkit uses Python protocols for type safety and flexibility:
369
+
370
+ ```python
371
+ from repl_toolkit.ptypes import AsyncBackend, ActionHandler
372
+
373
+ # Your backend must implement AsyncBackend
374
+ class MyBackend(AsyncBackend):
375
+ async def handle_input(self, user_input: str) -> bool:
376
+ # Process input, return success/failure
377
+ return True
378
+
379
+ # Action registries implement ActionHandler
380
+ class MyActions(ActionHandler):
381
+ def execute_action(self, action_name: str, context: ActionContext):
382
+ # Execute action by name
383
+ pass
384
+
385
+ def handle_command(self, command_string: str):
386
+ # Handle command input
387
+ pass
388
+
389
+ def validate_action(self, action_name: str) -> bool:
390
+ # Check if action exists
391
+ return action_name in self.actions
392
+
393
+ def list_actions(self) -> List[str]:
394
+ # Return available actions
395
+ return list(self.actions.keys())
396
+ ```
397
+
398
+ ## Completion Utilities
399
+
400
+ REPL Toolkit includes powerful completion utilities for building sophisticated command-line interfaces with autocompletion support.
401
+
402
+ For detailed documentation on completion features including:
403
+ - **PrefixCompleter**: Slash commands, at-mentions, hashtags with intelligent prefix detection
404
+ - **ShellExpansionCompleter**: Environment variable and shell command expansion
405
+ - Customization patterns and extensibility
406
+
407
+ See [repl_toolkit/completion/README.md](repl_toolkit/completion/README.md) for complete documentation and examples.
408
+
409
+ ## Examples
410
+
411
+ ### Basic Example
412
+
413
+ ```python
414
+ # examples/basic_usage.py - Complete working example
415
+ import asyncio
416
+ from repl_toolkit import run_async_repl, ActionRegistry, Action
417
+
418
+ class EchoBackend:
419
+ async def handle_input(self, input: str) -> bool:
420
+ print(f"Echo: {input}")
421
+ return True
422
+
423
+ async def main():
424
+ backend = EchoBackend()
425
+ await run_async_repl(backend=backend)
426
+
427
+ asyncio.run(main())
428
+ ```
429
+
430
+ ### Advanced Example
431
+
432
+ ```python
433
+ # examples/advanced_usage.py - Full-featured example
434
+ import asyncio
435
+ from repl_toolkit import AsyncREPL, ActionRegistry, Action, ActionContext
436
+
437
+ class AdvancedBackend:
438
+ def __init__(self):
439
+ self.data = []
440
+
441
+ async def handle_input(self, input: str) -> bool:
442
+ self.data.append(input)
443
+ print(f"Stored: {input} (Total: {len(self.data)})")
444
+ return True
445
+
446
+ class AdvancedActions(ActionRegistry):
447
+ def __init__(self):
448
+ super().__init__()
449
+
450
+ # Statistics with both command and shortcut
451
+ self.register_action(
452
+ name="show_stats",
453
+ description="Show data statistics",
454
+ category="Info",
455
+ handler=self._show_stats,
456
+ command="/stats",
457
+ keys="F3"
458
+ )
459
+
460
+ def _show_stats(self, context):
461
+ backend = context.backend
462
+ count = len(backend.data) if backend else 0
463
+ print(f"Statistics: {count} items stored")
464
+
465
+ async def main():
466
+ actions = AdvancedActions()
467
+ backend = AdvancedBackend()
468
+
469
+ repl = AsyncREPL(action_registry=actions, prompt_string="Advanced: ")
470
+ await repl.run(backend)
471
+
472
+ asyncio.run(main())
473
+ ```
474
+
475
+ ## Development
476
+
477
+ ### Setup Development Environment
478
+
479
+ ```bash
480
+ git clone https://github.com/bassmanitram/repl-toolkit.git
481
+ cd repl-toolkit
482
+ pip install -e ".[dev,test]"
483
+ ```
484
+
485
+ ### Run Tests
486
+
487
+ ```bash
488
+ pytest
489
+ ```
490
+
491
+ ### Run Tests with Coverage
492
+
493
+ ```bash
494
+ pytest --cov=repl_toolkit --cov-report=html
495
+ ```
496
+
497
+ ### Code Formatting
498
+
499
+ ```bash
500
+ black repl_toolkit/
501
+ isort repl_toolkit/
502
+ ```
503
+
504
+ ### Type Checking
505
+
506
+ ```bash
507
+ mypy repl_toolkit/
508
+ ```
509
+
510
+ ## Testing
511
+
512
+ Run the comprehensive test suite:
513
+
514
+ ```bash
515
+ # Install test dependencies
516
+ pip install pytest pytest-asyncio
517
+
518
+ # Run all tests
519
+ pytest
520
+
521
+ # Run with coverage
522
+ pytest --cov=repl_toolkit --cov-report=html
523
+
524
+ # Run specific test categories
525
+ pytest repl_toolkit/tests/test_actions.py # Action system tests
526
+ pytest repl_toolkit/tests/test_async_repl.py # REPL interface tests
527
+ pytest repl_toolkit/tests/test_headless.py # Headless mode tests
528
+ ```
529
+
530
+ ### Writing Tests
531
+
532
+ ```python
533
+ import pytest
534
+ from repl_toolkit import ActionRegistry, Action, ActionContext
535
+
536
+ def test_my_action():
537
+ # Test action execution
538
+ registry = ActionRegistry()
539
+
540
+ executed = []
541
+ def test_handler(context):
542
+ executed.append(context.triggered_by)
543
+
544
+ action = Action(
545
+ name="test",
546
+ description="Test action",
547
+ category="Test",
548
+ handler=test_handler,
549
+ command="/test"
550
+ )
551
+
552
+ registry.register_action(action)
553
+
554
+ context = ActionContext(registry=registry)
555
+ registry.execute_action("test", context)
556
+
557
+ assert executed == ["programmatic"]
558
+ ```
559
+
560
+ ## API Reference
561
+
562
+ ### Core Classes
563
+
564
+ #### `AsyncREPL`
565
+ ```python
566
+ class AsyncREPL:
567
+ def __init__(
568
+ self,
569
+ action_registry: Optional[ActionHandler] = None,
570
+ completer: Optional[Completer] = None,
571
+ prompt_string: Optional[str] = None,
572
+ history_path: Optional[Path] = None
573
+ )
574
+
575
+ async def run(self, backend: AsyncBackend, initial_message: Optional[str] = None)
576
+ ```
577
+
578
+ #### `ActionRegistry`
579
+ ```python
580
+ class ActionRegistry(ActionHandler):
581
+ def register_action(self, action: Action) -> None
582
+ def register_action(self, name, description, category, handler, command=None, keys=None, **kwargs) -> None
583
+
584
+ def execute_action(self, action_name: str, context: ActionContext) -> None
585
+ def handle_command(self, command_string: str, **kwargs) -> None
586
+ def handle_shortcut(self, key_combo: str, event: Any) -> None
587
+
588
+ def validate_action(self, action_name: str) -> bool
589
+ def list_actions(self) -> List[str]
590
+ def get_actions_by_category(self) -> Dict[str, List[Action]]
591
+ ```
592
+
593
+ ### Convenience Functions
594
+
595
+ #### `run_async_repl()`
596
+ ```python
597
+ async def run_async_repl(
598
+ backend: AsyncBackend,
599
+ action_registry: Optional[ActionHandler] = None,
600
+ completer: Optional[Completer] = None,
601
+ initial_message: Optional[str] = None,
602
+ prompt_string: Optional[str] = None,
603
+ history_path: Optional[Path] = None,
604
+ )
605
+ ```
606
+
607
+ #### `run_headless_mode()`
608
+ ```python
609
+ async def run_headless_mode(
610
+ backend: AsyncBackend,
611
+ action_registry: Optional[ActionHandler] = None,
612
+ initial_message: Optional[str] = None,
613
+ ) -> bool
614
+ ```
615
+
616
+ ### Protocols
617
+
618
+ #### `AsyncBackend`
619
+ ```python
620
+ class AsyncBackend(Protocol):
621
+ async def handle_input(self, user_input: str) -> bool: ...
622
+ ```
623
+
624
+ #### `ActionHandler`
625
+ ```python
626
+ class ActionHandler(Protocol):
627
+ def execute_action(self, action_name: str, context: ActionContext) -> None: ...
628
+ def handle_command(self, command_string: str, **kwargs) -> None: ...
629
+ def validate_action(self, action_name: str) -> bool: ...
630
+ def list_actions(self) -> List[str]: ...
631
+ ```
632
+
633
+ ## License
634
+
635
+ MIT License. See LICENSE file for details.
636
+
637
+ ## Contributing
638
+
639
+ Contributions are welcome! Please read [CONTRIBUTING.md](CONTRIBUTING.md) for guidelines and submit pull requests to the [main repository](https://github.com/bassmanitram/repl-toolkit).
640
+
641
+ ## Links
642
+
643
+ - **GitHub Repository**: https://github.com/bassmanitram/repl-toolkit
644
+ - **PyPI Package**: https://pypi.org/project/repl-toolkit/
645
+ - **Documentation**: https://repl-toolkit.readthedocs.io/
646
+ - **Issue Tracker**: https://github.com/bassmanitram/repl-toolkit/issues
647
+
648
+ ## Changelog
649
+
650
+ See [CHANGELOG.md](CHANGELOG.md) for version history and changes.
651
+
652
+ ## Acknowledgments
653
+
654
+ - Built on [prompt-toolkit](https://github.com/prompt-toolkit/python-prompt-toolkit) for terminal handling
655
+ - Logging by [loguru](https://github.com/Delgan/loguru) for structured logs
656
+ - Inspired by modern CLI tools and REPL interfaces
657
+
658
+ ## Formatting Utilities
659
+
660
+ REPL Toolkit includes utilities for automatically detecting and applying formatted text (HTML or ANSI) without needing to explicitly wrap text in format types.
661
+
662
+ ### Auto-Format Detection
663
+
664
+ The formatting utilities can automatically detect whether text contains HTML tags, ANSI escape codes, or is plain text:
665
+
666
+ ```python
667
+ from repl_toolkit import detect_format_type, auto_format, print_auto_formatted
668
+
669
+ # Detect format type
670
+ detect_format_type("<b>Bold</b>") # Returns: 'html'
671
+ detect_format_type("\x1b[1mBold\x1b[0m") # Returns: 'ansi'
672
+ detect_format_type("Plain text") # Returns: 'plain'
673
+
674
+ # Auto-format and print
675
+ print_auto_formatted("<b>Bold HTML</b>") # Automatically applies HTML formatting
676
+ print_auto_formatted("\x1b[1mBold ANSI\x1b[0m") # Automatically applies ANSI formatting
677
+ print_auto_formatted("Plain text") # Prints as-is
678
+ ```
679
+
680
+ ### Creating Auto-Printers
681
+
682
+ The `create_auto_printer()` function creates a printer that can be used as a drop-in replacement for `print()` with automatic format detection:
683
+
684
+ ```python
685
+ from repl_toolkit import create_auto_printer
686
+
687
+ # Create a printer
688
+ printer = create_auto_printer()
689
+
690
+ # Use it like print()
691
+ printer("<b>Bold text</b>") # HTML formatting applied
692
+ printer("\x1b[1mANSI bold\x1b[0m") # ANSI formatting applied
693
+ printer("Plain text") # No formatting
694
+
695
+ # Works with all print() parameters
696
+ printer("<b>Prefix:</b> ", end="", flush=True)
697
+ printer("Hello world\n")
698
+ ```
699
+
700
+ ### Integration with Callback Handlers
701
+
702
+ The auto-printer is particularly useful for integrating with callback handlers from other libraries:
703
+
704
+ ```python
705
+ from repl_toolkit import create_auto_printer
706
+ from some_library import CallbackHandler
707
+
708
+ # Create handler with auto-formatting printer
709
+ handler = CallbackHandler(
710
+ response_prefix="<b><darkcyan>🤖 Assistant:</darkcyan></b> ",
711
+ printer=create_auto_printer() # Automatically formats HTML tags
712
+ )
713
+
714
+ # The response_prefix will be properly formatted without needing
715
+ # to explicitly wrap it in HTML() or ANSI()
716
+ ```
717
+
718
+ ### Format Detection Rules
719
+
720
+ The auto-detection uses the following rules:
721
+
722
+ 1. **ANSI Detection**: Looks for ANSI escape codes (`\x1b[...m`)
723
+ - Pattern: `\x1b\[[0-9;]*m`
724
+ - Examples: `\x1b[1m`, `\x1b[31;1m`
725
+
726
+ 2. **HTML Detection**: Looks for HTML-like tags
727
+ - Pattern: `</?[a-zA-Z][a-zA-Z0-9]*\s*/?>`
728
+ - Examples: `<b>`, `</b>`, `<darkcyan>`, `<tag/>`
729
+ - Avoids false positives: `a < b`, `<123>`, `<_tag>`
730
+
731
+ 3. **Plain Text**: Everything else
732
+
733
+ ### API Reference
734
+
735
+ #### `detect_format_type(text: str) -> str`
736
+ Detect the format type of a text string.
737
+
738
+ **Returns**: `'ansi'`, `'html'`, or `'plain'`
739
+
740
+ #### `auto_format(text: str)`
741
+ Auto-detect format type and return appropriate formatted text object.
742
+
743
+ **Returns**: `HTML`, `ANSI`, or `str` object
744
+
745
+ #### `print_auto_formatted(text: str, **kwargs) -> None`
746
+ Print text with auto-detected formatting.
747
+
748
+ **Parameters**: Same as `print_formatted_text()` from prompt_toolkit
749
+
750
+ #### `create_auto_printer() -> Callable`
751
+ Create a printer function with auto-format detection.
752
+
753
+ **Returns**: Callable with signature `printer(text: str, **kwargs)`
754
+
755
+ ### Example
756
+
757
+ See `examples/formatting_demo.py` for a complete demonstration of the formatting utilities.
758
+
759
+ ```bash
760
+ python examples/formatting_demo.py
761
+ ```