Coverage for src/interactive_handler.py: 0%
39 statements
« prev ^ index » next coverage.py v7.8.0, created at 2025-06-05 22:56 -0700
« prev ^ index » next coverage.py v7.8.0, created at 2025-06-05 22:56 -0700
1"""
2Interactive prompt utilities for Chuck command handlers.
4This module provides utilities for command handlers to interact with users,
5collect input, and manage interactive workflows.
6"""
8from typing import List, Optional
9from getpass import getpass
10from rich.console import Console
12from src.ui.theme import MESSAGE_STANDARD
15class InteractivePrompt:
16 """
17 Utility for handling interactive user input within command handlers.
19 This allows command handlers to prompt for and process user input
20 before returning their final CommandResult.
21 """
23 def __init__(self, console: Optional[Console] = None):
24 """Initialize with an optional console instance."""
25 self.console = console or Console()
26 self.context = {} # Store context data across interactions
28 def prompt(
29 self,
30 message: str,
31 valid_responses: Optional[List[str]] = None,
32 case_sensitive: bool = False,
33 default: Optional[str] = None,
34 hidden: bool = False,
35 ) -> str:
36 """
37 Display a prompt and get validated user input.
39 Args:
40 message: The message to display to the user
41 valid_responses: Optional list of valid responses
42 case_sensitive: Whether validation should be case-sensitive
43 default: Default value if user enters nothing
44 hidden: If True, hide user input (useful for secrets)
46 Returns:
47 The user's response as a string
48 """
49 # Display prompt
50 self.console.print(f"\n[{MESSAGE_STANDARD}]{message}[/{MESSAGE_STANDARD}]")
52 if valid_responses:
53 options = "/".join(valid_responses)
54 self.console.print(f"[dim]Options: {options}[/dim]")
56 if default:
57 self.console.print(f"[dim]Default: {default}[/dim]")
59 # Display the prompt and get user input directly
60 # This works with the TUI interface
61 self.console.print(f"[{MESSAGE_STANDARD}]> [/{MESSAGE_STANDARD}]", end="")
62 if hidden:
63 response = getpass("")
64 else:
65 response = input()
67 response = response.strip()
69 # Handle empty response with default
70 if not response and default:
71 return default
73 # Validate response if needed
74 if valid_responses:
75 valid_check = response
76 if not case_sensitive:
77 valid_check = response.lower()
78 valid_options = [opt.lower() for opt in valid_responses]
79 else:
80 valid_options = valid_responses
82 if valid_check in valid_options:
83 return response
84 else:
85 self.console.print(
86 f"[yellow]Invalid response. Please enter one of: {'/'.join(valid_responses)}[/yellow]"
87 )
88 # Recursive call to prompt again
89 return self.prompt(
90 message,
91 valid_responses,
92 case_sensitive,
93 default,
94 hidden=hidden,
95 )
96 else:
97 # No validation needed
98 return response
100 def prompt_yes_no(self, message: str, default: Optional[str] = None) -> bool:
101 """
102 Convenience method for yes/no prompts.
104 Args:
105 message: The question to ask
106 default: Default value ("yes", "no", or None)
108 Returns:
109 True for yes, False for no
110 """
111 valid_default = None
112 if default:
113 valid_default = (
114 default.lower() if default.lower() in ["yes", "no", "y", "n"] else None
115 )
117 response = self.prompt(
118 message,
119 valid_responses=["yes", "no", "y", "n"],
120 case_sensitive=False,
121 default=valid_default,
122 ).lower()
124 return response in ["yes", "y"]