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

1""" 

2Interactive prompt utilities for Chuck command handlers. 

3 

4This module provides utilities for command handlers to interact with users, 

5collect input, and manage interactive workflows. 

6""" 

7 

8from typing import List, Optional 

9from getpass import getpass 

10from rich.console import Console 

11 

12from src.ui.theme import MESSAGE_STANDARD 

13 

14 

15class InteractivePrompt: 

16 """ 

17 Utility for handling interactive user input within command handlers. 

18 

19 This allows command handlers to prompt for and process user input 

20 before returning their final CommandResult. 

21 """ 

22 

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 

27 

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. 

38 

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) 

45 

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}]") 

51 

52 if valid_responses: 

53 options = "/".join(valid_responses) 

54 self.console.print(f"[dim]Options: {options}[/dim]") 

55 

56 if default: 

57 self.console.print(f"[dim]Default: {default}[/dim]") 

58 

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() 

66 

67 response = response.strip() 

68 

69 # Handle empty response with default 

70 if not response and default: 

71 return default 

72 

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 

81 

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 

99 

100 def prompt_yes_no(self, message: str, default: Optional[str] = None) -> bool: 

101 """ 

102 Convenience method for yes/no prompts. 

103 

104 Args: 

105 message: The question to ask 

106 default: Default value ("yes", "no", or None) 

107 

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 ) 

116 

117 response = self.prompt( 

118 message, 

119 valid_responses=["yes", "no", "y", "n"], 

120 case_sensitive=False, 

121 default=valid_default, 

122 ).lower() 

123 

124 return response in ["yes", "y"]