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,70 @@
1
+ """
2
+ REPL Toolkit - A Python toolkit for building interactive REPL and headless interfaces.
3
+
4
+ This package provides tools for creating interactive command-line interfaces
5
+ with support for both commands and keyboard shortcuts, featuring late backend
6
+ binding for resource context scenarios.
7
+
8
+ Key Features:
9
+ - Action system with commands and keyboard shortcuts
10
+ - Late backend binding for resource contexts
11
+ - Protocol-based architecture for type safety
12
+ - Comprehensive test coverage
13
+ - Async-native design
14
+ - Auto-formatting utilities for HTML and ANSI text
15
+ - Custom output handling with configurable printer
16
+ - Shell expansion and prefix-based completion
17
+
18
+ Example:
19
+ >>> import asyncio
20
+ >>> from repl_toolkit import run_async_repl, ActionRegistry
21
+ >>>
22
+ >>> class MyBackend:
23
+ ... async def handle_input(self, user_input: str) -> bool:
24
+ ... print(f"You said: {user_input}")
25
+ ... return True
26
+ >>>
27
+ >>> async def main():
28
+ ... backend = MyBackend()
29
+ ... await run_async_repl(backend=backend)
30
+ >>>
31
+ >>> # asyncio.run(main())
32
+ """
33
+
34
+ __version__ = "1.2.0"
35
+ __author__ = "REPL Toolkit Contributors"
36
+ __license__ = "MIT"
37
+
38
+ from .actions import Action, ActionContext, ActionError, ActionRegistry
39
+
40
+ # Core exports
41
+ from .async_repl import AsyncREPL, run_async_repl
42
+ from .completion import PrefixCompleter, ShellExpansionCompleter
43
+ from .formatting import auto_format, create_auto_printer, detect_format_type, print_auto_formatted
44
+ from .headless_repl import HeadlessREPL, run_headless_mode
45
+ from .ptypes import ActionHandler, AsyncBackend, Completer
46
+
47
+ __all__ = [
48
+ # Core classes
49
+ "AsyncREPL",
50
+ "HeadlessREPL",
51
+ "ActionRegistry",
52
+ "Action",
53
+ "ActionContext",
54
+ "ActionError",
55
+ # Convenience functions
56
+ "run_async_repl",
57
+ "run_headless_mode",
58
+ # Protocols/Types
59
+ "AsyncBackend",
60
+ "ActionHandler",
61
+ "Completer",
62
+ # Formatting utilities
63
+ "detect_format_type",
64
+ "auto_format",
65
+ "print_auto_formatted",
66
+ "create_auto_printer",
67
+ # Completion utilities
68
+ "ShellExpansionCompleter",
69
+ "PrefixCompleter",
70
+ ]
@@ -0,0 +1,24 @@
1
+ """
2
+ Action system for repl_toolkit.
3
+
4
+ This module provides the action architecture that combines command
5
+ and keyboard shortcut handling into a single, extensible framework.
6
+
7
+ The action system allows developers to define actions that can be triggered
8
+ by either typed commands (e.g., /help) or keyboard shortcuts (e.g., F1),
9
+ providing a consistent and discoverable interface for users.
10
+ """
11
+
12
+ from .action import Action, ActionContext, ActionError, ActionExecutionError, ActionValidationError
13
+ from .registry import ActionRegistry
14
+
15
+ __all__ = [
16
+ # Core action types
17
+ "Action",
18
+ "ActionContext",
19
+ "ActionError",
20
+ "ActionValidationError",
21
+ "ActionExecutionError",
22
+ # Registry
23
+ "ActionRegistry",
24
+ ]
@@ -0,0 +1,223 @@
1
+ """
2
+ Core action definition and context for the action system.
3
+
4
+ This module defines the Action dataclass and ActionContext that form the
5
+ foundation of the command and keyboard shortcut system.
6
+ """
7
+
8
+ from dataclasses import dataclass, field
9
+ from typing import TYPE_CHECKING, Any, Callable, List, Optional, Union
10
+
11
+ from loguru import logger
12
+
13
+ if TYPE_CHECKING:
14
+ from .registry import ActionRegistry
15
+
16
+
17
+ @dataclass
18
+ class Action:
19
+ """
20
+ Action definition that can be triggered by commands or keyboard shortcuts.
21
+
22
+ Actions provide a way to define functionality that can be accessed
23
+ through multiple interaction methods (commands, shortcuts) while maintaining
24
+ consistent behavior and documentation.
25
+
26
+ Example:
27
+ # Action with both command and shortcut
28
+ help_action = Action(
29
+ name="show_help",
30
+ description="Show help information",
31
+ category="General",
32
+ handler=show_help_function,
33
+ command="/help",
34
+ command_usage="/help [command] - Show help for all or specific command",
35
+ keys="F1",
36
+ keys_description="Show help"
37
+ )
38
+
39
+ # Command-only action
40
+ history_action = Action(
41
+ name="show_history",
42
+ description="Display conversation history",
43
+ category="Information",
44
+ handler=show_history_function,
45
+ command="/history",
46
+ command_usage="/history - Display message history"
47
+ )
48
+
49
+ # Shortcut-only action
50
+ save_action = Action(
51
+ name="quick_save",
52
+ description="Quick save current state",
53
+ category="File",
54
+ handler=quick_save_function,
55
+ keys="ctrl-s",
56
+ keys_description="Quick save"
57
+ )
58
+
59
+ # Main-loop action (handled externally)
60
+ exit_action = Action(
61
+ name="exit_repl",
62
+ description="Exit the application",
63
+ category="Control",
64
+ handler=None, # Handled by main loop
65
+ command="/exit",
66
+ command_usage="/exit - Exit the application"
67
+ )
68
+ """
69
+
70
+ # Core action definition
71
+ name: str # Unique action identifier
72
+ description: str # Human-readable description
73
+ category: str # Category for grouping/organization
74
+ handler: Optional[Union[Callable, str]] # Handler function, import path, or None for main-loop
75
+
76
+ # Command binding (optional)
77
+ command: Optional[str] = None # Command string (e.g., "/help")
78
+ command_args_description: Optional[str] = None # Description of command arguments
79
+ command_usage: Optional[str] = None # Full usage description
80
+
81
+ # Keyboard shortcut binding (optional)
82
+ keys: Optional[Union[str, List[str]]] = None # Key combination(s)
83
+ keys_description: Optional[str] = None # Shortcut description for help
84
+
85
+ # Metadata and control
86
+ enabled: bool = True # Whether action is currently enabled
87
+ context: Optional[str] = None # Context where action is available
88
+ requires_backend: bool = False # Whether action needs backend access
89
+ hidden: bool = False # Hide from help listings
90
+
91
+ def __post_init__(self):
92
+ """Validate action definition after initialization."""
93
+ logger.trace("Action.__post_init__() entry")
94
+
95
+ if not self.name:
96
+ raise ValueError("Action name cannot be empty")
97
+
98
+ if not self.description:
99
+ raise ValueError("Action description cannot be empty")
100
+
101
+ if not self.category:
102
+ raise ValueError("Action category cannot be empty")
103
+
104
+ # Handler can be None for main-loop actions like exit/quit
105
+ # but if provided, it cannot be empty string
106
+ if self.handler == "":
107
+ raise ValueError(
108
+ "Action handler cannot be empty string (use None for main-loop actions)"
109
+ )
110
+
111
+ if not self.command and not self.keys:
112
+ raise ValueError("Action must have either command or keys binding")
113
+
114
+ # Validate command format
115
+ if self.command and not self.command.startswith("/"):
116
+ raise ValueError(f"Command '{self.command}' must start with '/'")
117
+
118
+ # Ensure command usage is provided for commands
119
+ if self.command and not self.command_usage:
120
+ logger.warning(f"Action '{self.name}' has command but no usage description")
121
+
122
+ # Ensure keys description is provided for shortcuts
123
+ if self.keys and not self.keys_description:
124
+ logger.warning(f"Action '{self.name}' has keys but no keys description")
125
+
126
+ logger.trace("Action.__post_init__() exit")
127
+
128
+ @property
129
+ def has_command(self) -> bool:
130
+ """Check if action has a command binding."""
131
+ logger.trace("Action.has_command() entry/exit")
132
+ return self.command is not None
133
+
134
+ @property
135
+ def has_shortcut(self) -> bool:
136
+ """Check if action has a keyboard shortcut binding."""
137
+ logger.trace("Action.has_shortcut() entry/exit")
138
+ return self.keys is not None
139
+
140
+ @property
141
+ def is_main_loop_action(self) -> bool:
142
+ """Check if action is handled by the main loop (handler is None)."""
143
+ logger.trace("Action.is_main_loop_action() entry/exit")
144
+ return self.handler is None
145
+
146
+ def get_keys_list(self) -> List[str]:
147
+ """Get keys as a list, handling both string and list formats."""
148
+ logger.trace("Action.get_keys_list() entry")
149
+
150
+ if not self.keys:
151
+ logger.trace("Action.get_keys_list() exit - no keys")
152
+ return []
153
+
154
+ result = [self.keys] if isinstance(self.keys, str) else self.keys
155
+ logger.trace("Action.get_keys_list() exit")
156
+ return result
157
+
158
+
159
+ @dataclass
160
+ class ActionContext:
161
+ """
162
+ Context information passed to action handlers.
163
+
164
+ ActionContext provides handlers with access to the registry, backend,
165
+ and context-specific information needed to execute actions properly.
166
+
167
+ The context varies depending on how the action was triggered:
168
+ - Command: args contains parsed command arguments
169
+ - Keyboard: event contains the key press event
170
+ - Programmatic: context can be customized
171
+ """
172
+
173
+ registry: "ActionRegistry" # Reference to action registry
174
+ backend: Optional[Any] = None # Backend instance (if available)
175
+ event: Optional[Any] = None # KeyPress event for shortcuts
176
+ args: List[str] = field(default_factory=list) # Command arguments
177
+ triggered_by: str = "unknown" # How action was triggered
178
+ user_input: Optional[str] = None # Original user input
179
+ headless_mode: bool = False # Whether in headless mode
180
+ buffer: Optional[Any] = None # Reference to input buffer (if applicable)
181
+ printer: Callable[[str], None] = print # Output function for action messages
182
+
183
+ def __post_init__(self):
184
+ """Set triggered_by based on available context."""
185
+ logger.trace("ActionContext.__post_init__() entry")
186
+
187
+ if self.triggered_by == "unknown":
188
+ if self.event is not None:
189
+ self.triggered_by = "shortcut"
190
+ elif self.args or self.user_input:
191
+ self.triggered_by = "command"
192
+ else:
193
+ self.triggered_by = "programmatic"
194
+
195
+ logger.trace("ActionContext.__post_init__() exit")
196
+
197
+
198
+ class ActionError(Exception):
199
+ """Base exception for action-related errors."""
200
+
201
+ def __init__(self, message: str, action_name: Optional[str] = None):
202
+ """
203
+ Initialize action error.
204
+
205
+ Args:
206
+ message: Error description
207
+ action_name: Name of action that caused error (optional)
208
+ """
209
+ logger.trace("ActionError.__init__() entry/exit")
210
+ super().__init__(message)
211
+ self.action_name = action_name
212
+
213
+
214
+ class ActionValidationError(ActionError):
215
+ """Exception raised when action validation fails."""
216
+
217
+ pass
218
+
219
+
220
+ class ActionExecutionError(ActionError):
221
+ """Exception raised when action execution fails."""
222
+
223
+ pass