|
|
@@ -1,480 +1,250 @@
|
|
|
-from typing import Dict, Any, List, Optional
|
|
|
-import logging
|
|
|
+"""Simplified prompt handler for dotted notation variables."""
|
|
|
+from typing import Dict, Any, List, Tuple, Optional
|
|
|
+from collections import OrderedDict
|
|
|
from rich.console import Console
|
|
|
from rich.prompt import Prompt, Confirm, IntPrompt, FloatPrompt
|
|
|
-from rich.table import Table
|
|
|
-from rich import box
|
|
|
+import logging
|
|
|
|
|
|
logger = logging.getLogger('boilerplates')
|
|
|
+console = Console()
|
|
|
|
|
|
-class PromptHandler:
|
|
|
- """Prompt handler with Rich UI for variable configuration."""
|
|
|
|
|
|
- def __init__(self, variable_groups: Dict[str, Any], resolved_defaults: Dict[str, Any] = None):
|
|
|
- """Initialize the prompt handler.
|
|
|
+class SimplifiedPromptHandler:
|
|
|
+ """Clean prompt handler for dotted notation variables."""
|
|
|
+
|
|
|
+ def __init__(self, variables: Dict[str, Any], defaults: Dict[str, Any], dict_keys: Dict[str, List[str]] = None):
|
|
|
+ """Initialize with template variables and defaults.
|
|
|
|
|
|
Args:
|
|
|
- variable_groups: Dictionary of variable groups from VariableManager
|
|
|
- resolved_defaults: Pre-resolved default values with priority handling
|
|
|
+ variables: Dict of variable name to Variable object
|
|
|
+ defaults: Dict of variable name to default value
|
|
|
+ dict_keys: Dict variables and their keys used in template
|
|
|
"""
|
|
|
- self.variable_groups = variable_groups
|
|
|
- self.resolved_defaults = resolved_defaults or {}
|
|
|
- self.console = Console()
|
|
|
- self.final_values = {}
|
|
|
-
|
|
|
- # Map prompt types to their handlers
|
|
|
- self.prompt_handlers = {
|
|
|
- 'boolean': self._prompt_boolean,
|
|
|
- 'integer': self._prompt_integer,
|
|
|
- 'float': self._prompt_float,
|
|
|
- 'choice': self._prompt_choice,
|
|
|
- 'list': self._prompt_list,
|
|
|
- 'string': self._prompt_string
|
|
|
- }
|
|
|
+ self.variables = variables
|
|
|
+ self.defaults = defaults
|
|
|
+ self.dict_keys = dict_keys or {}
|
|
|
+ self.values = {}
|
|
|
|
|
|
def __call__(self) -> Dict[str, Any]:
|
|
|
- """Execute the prompting logic and return final variable values."""
|
|
|
- logger.debug(f"Starting prompt handler with {len(self.variable_groups)} variable groups")
|
|
|
-
|
|
|
- # Process groups in order (general first if exists)
|
|
|
- for group_name, group_data in self._get_ordered_groups():
|
|
|
- self._process_variable_group(group_name, group_data)
|
|
|
+ """Execute the prompting flow."""
|
|
|
+ # Group variables by prefix (preserves registration order)
|
|
|
+ groups, standalone = self._group_variables()
|
|
|
|
|
|
- self._show_summary()
|
|
|
- return self.final_values
|
|
|
-
|
|
|
- def _get_ordered_groups(self) -> List[tuple]:
|
|
|
- """Get groups in processing order (general first)."""
|
|
|
- ordered = []
|
|
|
- if 'general' in self.variable_groups:
|
|
|
- ordered.append(('general', self.variable_groups['general']))
|
|
|
+ # Process standalone variables first as "General" group
|
|
|
+ if standalone:
|
|
|
+ self._process_variable_set("General", standalone, is_group=False)
|
|
|
|
|
|
- for name, data in self.variable_groups.items():
|
|
|
- if name != 'general':
|
|
|
- ordered.append((name, data))
|
|
|
+ # Process each group in the order they were registered
|
|
|
+ for group_name, group_vars in groups.items():
|
|
|
+ self._process_variable_set(group_name.title(), group_vars, is_group=True)
|
|
|
|
|
|
- return ordered
|
|
|
+ return self.values
|
|
|
|
|
|
- def _process_variable_group(self, group_name: str, group_data: Dict[str, Any]):
|
|
|
- """Process a single variable group."""
|
|
|
- variables = group_data.get('vars', {})
|
|
|
- if not variables:
|
|
|
- return
|
|
|
-
|
|
|
- # Flatten multivalue variables to check which ones are truly required
|
|
|
- required_items, optional_items = self._categorize_variables(variables)
|
|
|
-
|
|
|
- if not (required_items or optional_items):
|
|
|
- return
|
|
|
+ def _group_variables(self) -> Tuple[Dict[str, List[str]], List[str]]:
|
|
|
+ """Group variables by their prefix, preserving registration order.
|
|
|
|
|
|
- # Apply defaults for all optional items
|
|
|
- for var_name, key, default_value in optional_items:
|
|
|
- if key is not None:
|
|
|
- # Multivalue with key
|
|
|
- if var_name not in self.final_values:
|
|
|
- self.final_values[var_name] = {}
|
|
|
- self.final_values[var_name][key] = default_value
|
|
|
+ Returns:
|
|
|
+ (groups, standalone) where groups is {prefix: [var_names]}
|
|
|
+ """
|
|
|
+ groups = OrderedDict()
|
|
|
+ standalone = []
|
|
|
+
|
|
|
+ # Process variables in registration order
|
|
|
+ for var_name in self.variables.keys():
|
|
|
+ if '.' in var_name:
|
|
|
+ # This is a child variable (e.g., 'traefik.host')
|
|
|
+ prefix = var_name.split('.')[0]
|
|
|
+
|
|
|
+ # Create group if needed (preserves first encounter order)
|
|
|
+ if prefix not in groups:
|
|
|
+ groups[prefix] = []
|
|
|
+
|
|
|
+ # Add to group
|
|
|
+ groups[prefix].append(var_name)
|
|
|
else:
|
|
|
- # Simple variable
|
|
|
- self.final_values[var_name] = default_value
|
|
|
+ # Standalone variable (no dots)
|
|
|
+ standalone.append(var_name)
|
|
|
|
|
|
- # Check for enabler variable
|
|
|
- enabler_var_name = group_data.get('enabler', '')
|
|
|
- has_enabler = enabler_var_name and enabler_var_name in variables
|
|
|
+ return groups, standalone
|
|
|
+
|
|
|
+ def _process_variable_set(self, display_name: str, var_names: List[str],
|
|
|
+ is_group: bool = False):
|
|
|
+ """Unified method to process any set of variables.
|
|
|
|
|
|
- # Determine if group should be processed
|
|
|
- if has_enabler:
|
|
|
- # Handle enabler group
|
|
|
- enabled = self._prompt_enabler(group_name, enabler_var_name)
|
|
|
- self.final_values[enabler_var_name] = enabled
|
|
|
+ Args:
|
|
|
+ display_name: Name to show in the header
|
|
|
+ var_names: List of variable names to process
|
|
|
+ is_group: Whether this is a group (vs standalone)
|
|
|
+ """
|
|
|
+ # Deduplicate variables
|
|
|
+ var_names = list(dict.fromkeys(var_names)) # Preserves order while removing duplicates
|
|
|
+
|
|
|
+ # Check if this group has an enabler (standalone variable with same name as group)
|
|
|
+ group_name = display_name.lower()
|
|
|
+ enabler = None
|
|
|
+ if is_group and group_name in self.variables:
|
|
|
+ enabler = group_name
|
|
|
+ # Ask about enabler first
|
|
|
+ console.print(f"\n[bold cyan]{display_name} Configuration[/bold cyan]")
|
|
|
+ var = self.variables[enabler]
|
|
|
+ enabled = Confirm.ask(
|
|
|
+ f"Enable {enabler}?",
|
|
|
+ default=bool(self.defaults.get(enabler, False))
|
|
|
+ )
|
|
|
+ self.values[enabler] = enabled
|
|
|
+
|
|
|
if not enabled:
|
|
|
+ # Skip all group variables
|
|
|
return
|
|
|
- elif not required_items:
|
|
|
- # No required items, ask if user wants to configure optional ones
|
|
|
- if not self._should_process_optional_group(group_name):
|
|
|
- return
|
|
|
-
|
|
|
- # Show group header
|
|
|
- self._show_group_header(group_name, group_data)
|
|
|
-
|
|
|
- # Process required items first
|
|
|
- if required_items:
|
|
|
- for var_name, key, _ in required_items:
|
|
|
- if var_name == enabler_var_name:
|
|
|
- continue # Already handled
|
|
|
-
|
|
|
- if key is not None:
|
|
|
- # Multivalue required key
|
|
|
- value = self._prompt_for_multivalue_key(
|
|
|
- var_name, key, variables[var_name], required=True
|
|
|
- )
|
|
|
- if var_name not in self.final_values:
|
|
|
- self.final_values[var_name] = {}
|
|
|
- self.final_values[var_name][key] = value
|
|
|
- else:
|
|
|
- # Simple required variable
|
|
|
- self.final_values[var_name] = self._prompt_for_variable(
|
|
|
- var_name, variables[var_name], required=True
|
|
|
- )
|
|
|
-
|
|
|
- # Process optional items if user wants to change them
|
|
|
- # Filter out already-prompted items from required_items
|
|
|
- already_prompted = set((var_name, key) for var_name, key, _ in required_items)
|
|
|
- optional_to_prompt = [
|
|
|
- (var_name, key, default) for var_name, key, default in optional_items
|
|
|
- if var_name != enabler_var_name and (var_name, key) not in already_prompted
|
|
|
- ]
|
|
|
|
|
|
- if optional_to_prompt:
|
|
|
- self._handle_optional_items(group_name, optional_to_prompt, variables)
|
|
|
-
|
|
|
- def _handle_optional_items(self, group_name: str, optional_items: list, variables: Dict[str, Any]):
|
|
|
- """Handle optional items (variables or multivalue keys with defaults)."""
|
|
|
- # Group items by variable for preview
|
|
|
- vars_to_show = {}
|
|
|
- for var_name, key, default in optional_items:
|
|
|
- if var_name not in vars_to_show:
|
|
|
- vars_to_show[var_name] = []
|
|
|
- vars_to_show[var_name].append((key, default))
|
|
|
-
|
|
|
- # Show preview
|
|
|
- self._show_preview(list(vars_to_show.keys()))
|
|
|
+ # Split into required and optional
|
|
|
+ required = []
|
|
|
+ optional = []
|
|
|
+ for var_name in var_names:
|
|
|
+ if var_name in self.defaults:
|
|
|
+ optional.append(var_name)
|
|
|
+ else:
|
|
|
+ required.append(var_name)
|
|
|
|
|
|
- # Ask if user wants to customize
|
|
|
- try:
|
|
|
- want_to_customize = Confirm.ask(f"Do you want to change {group_name} values?", default=False)
|
|
|
- except (EOFError, KeyboardInterrupt):
|
|
|
- logger.debug(f"User interrupted customization for {group_name}, using defaults")
|
|
|
- return
|
|
|
+ # Apply defaults
|
|
|
+ for var_name in optional:
|
|
|
+ self.values[var_name] = self.defaults[var_name]
|
|
|
|
|
|
- if want_to_customize:
|
|
|
- for var_name, key, default in optional_items:
|
|
|
- var_data = variables[var_name]
|
|
|
-
|
|
|
- if key is not None:
|
|
|
- # Multivalue item
|
|
|
- value = self._prompt_for_multivalue_key(
|
|
|
- var_name, key, var_data, required=False
|
|
|
- )
|
|
|
- if isinstance(key, int):
|
|
|
- # Handle list index
|
|
|
- if var_name not in self.final_values:
|
|
|
- self.final_values[var_name] = []
|
|
|
- while len(self.final_values[var_name]) <= key:
|
|
|
- self.final_values[var_name].append(None)
|
|
|
- self.final_values[var_name][key] = value
|
|
|
- else:
|
|
|
- # Handle dict key
|
|
|
- if var_name not in self.final_values:
|
|
|
- self.final_values[var_name] = {}
|
|
|
- self.final_values[var_name][key] = value
|
|
|
+ # Process required variables
|
|
|
+ if required:
|
|
|
+ if not enabler: # Don't repeat header if we already showed it for enabler
|
|
|
+ console.print(f"\n[bold cyan]{display_name} - Required Configuration[/bold cyan]")
|
|
|
+ for var_name in required:
|
|
|
+ var = self.variables[var_name]
|
|
|
+ if var_name in self.dict_keys:
|
|
|
+ console.print(f"\n[cyan]{var.description or var_name}[/cyan]")
|
|
|
+ self.values[var_name] = self._prompt_dict_variable(var_name, var)
|
|
|
else:
|
|
|
- # Simple variable
|
|
|
- current_value = self.final_values.get(var_name)
|
|
|
- self.final_values[var_name] = self._prompt_for_variable(
|
|
|
- var_name, var_data, required=False, current_value=current_value
|
|
|
- )
|
|
|
-
|
|
|
- def _categorize_variables(self, variables: Dict[str, Any]) -> tuple:
|
|
|
- """Categorize variables into required and optional items.
|
|
|
-
|
|
|
- Returns:
|
|
|
- (required_items, optional_items) where each item is (var_name, key_or_index, default_value)
|
|
|
- For simple variables, key_or_index is None.
|
|
|
- """
|
|
|
- required_items = []
|
|
|
- optional_items = []
|
|
|
-
|
|
|
- for var_name, var_data in variables.items():
|
|
|
- patterns = var_data.get('usage_patterns', {})
|
|
|
+ display = var_name.replace('.', ' ') if is_group else var_name
|
|
|
+ self.values[var_name] = self._prompt_variable(display, var, required=True)
|
|
|
+
|
|
|
+ # Process optional variables
|
|
|
+ if optional:
|
|
|
+ console.print(f"\n[bold cyan]{display_name} - Optional Configuration[/bold cyan]")
|
|
|
+ # Filter out enabler variables from display
|
|
|
+ display_optional = [v for v in optional if v != enabler]
|
|
|
+ if display_optional:
|
|
|
+ self._show_variables(display_optional)
|
|
|
|
|
|
- if patterns and patterns.get('keys'):
|
|
|
- # Multivalue with specific keys
|
|
|
- for key in patterns['keys']:
|
|
|
- # Check if this specific key has a default
|
|
|
- default = None
|
|
|
- if var_name in self.resolved_defaults and isinstance(self.resolved_defaults[var_name], dict):
|
|
|
- default = self.resolved_defaults[var_name].get(key)
|
|
|
-
|
|
|
- if default is None:
|
|
|
- required_items.append((var_name, key, None))
|
|
|
+ if Confirm.ask("Do you want to change any values?", default=False):
|
|
|
+ for var_name in optional:
|
|
|
+ # Skip the enabler variable as it was already handled
|
|
|
+ if var_name == enabler:
|
|
|
+ continue
|
|
|
+ var = self.variables[var_name]
|
|
|
+ if var_name in self.dict_keys:
|
|
|
+ console.print(f"\n[cyan]{var.description or var_name}[/cyan]")
|
|
|
+ self.values[var_name] = self._prompt_dict_variable(
|
|
|
+ var_name, var, current_values=self.values.get(var_name, {})
|
|
|
+ )
|
|
|
else:
|
|
|
- optional_items.append((var_name, key, default))
|
|
|
+ display = var_name.replace('.', ' ') if is_group else var_name
|
|
|
+ self.values[var_name] = self._prompt_variable(
|
|
|
+ display, var, current_value=self.values[var_name]
|
|
|
+ )
|
|
|
+
|
|
|
+
|
|
|
+ def _prompt_dict_variable(self, var_name: str, var: Any, current_values: Dict[str, Any] = None) -> Dict[str, Any]:
|
|
|
+ """Prompt for a dict variable with dynamic keys."""
|
|
|
+ result = {}
|
|
|
+ keys = self.dict_keys.get(var_name, [])
|
|
|
+ current_values = current_values or {}
|
|
|
+
|
|
|
+ for key in keys:
|
|
|
+ # Use current value if available, otherwise check for default
|
|
|
+ current_value = current_values.get(key)
|
|
|
+ if current_value is None:
|
|
|
+ if var_name in self.defaults and isinstance(self.defaults[var_name], dict):
|
|
|
+ current_value = self.defaults[var_name].get(key)
|
|
|
|
|
|
- elif patterns and patterns.get('indices'):
|
|
|
- # Multivalue with specific indices
|
|
|
- for idx in patterns['indices']:
|
|
|
- # Check if this specific index has a default
|
|
|
- default = None
|
|
|
- if var_name in self.resolved_defaults and isinstance(self.resolved_defaults[var_name], list):
|
|
|
- if idx < len(self.resolved_defaults[var_name]):
|
|
|
- default = self.resolved_defaults[var_name][idx]
|
|
|
-
|
|
|
- if default is None:
|
|
|
- required_items.append((var_name, idx, None))
|
|
|
- else:
|
|
|
- optional_items.append((var_name, idx, default))
|
|
|
+ prompt_msg = f"Enter {var_name}['{key}']"
|
|
|
+ if current_value is not None:
|
|
|
+ prompt_msg += f" [dim]({current_value})[/dim]"
|
|
|
+ else:
|
|
|
+ prompt_msg += " [red](Required)[/red]"
|
|
|
|
|
|
+ # Infer type from current value or default
|
|
|
+ if isinstance(current_value, int):
|
|
|
+ while True:
|
|
|
+ try:
|
|
|
+ result[key] = IntPrompt.ask(prompt_msg, default=current_value)
|
|
|
+ break
|
|
|
+ except ValueError:
|
|
|
+ console.print("[red]Please enter a valid integer[/red]")
|
|
|
+ elif isinstance(current_value, bool):
|
|
|
+ result[key] = Confirm.ask(prompt_msg, default=current_value)
|
|
|
else:
|
|
|
- # Simple variable
|
|
|
- default = self.resolved_defaults.get(var_name)
|
|
|
- if default is None:
|
|
|
- required_items.append((var_name, None, None))
|
|
|
+ value = Prompt.ask(prompt_msg, default=str(current_value) if current_value else None)
|
|
|
+ result[key] = value if value else current_value
|
|
|
+
|
|
|
+ return result
|
|
|
+
|
|
|
+ def _show_variables(self, var_names: List[str]):
|
|
|
+ """Display current variable values."""
|
|
|
+ for var_name in var_names:
|
|
|
+ value = self.values.get(var_name, self.defaults.get(var_name))
|
|
|
+ if value is not None:
|
|
|
+ display_name = var_name.replace('.', ' ')
|
|
|
+ # Special formatting for dict values - show each key separately
|
|
|
+ if isinstance(value, dict):
|
|
|
+ for key, val in value.items():
|
|
|
+ console.print(f" {display_name}['{key}']: [dim]{val}[/dim]")
|
|
|
else:
|
|
|
- optional_items.append((var_name, None, default))
|
|
|
-
|
|
|
- return required_items, optional_items
|
|
|
-
|
|
|
- def _prompt_for_multivalue_key(self, var_name: str, key, var_data: Dict[str, Any], required: bool = False) -> Any:
|
|
|
- """Prompt for a specific key/index of a multivalue variable."""
|
|
|
- var_type = var_data.get('type', 'string')
|
|
|
-
|
|
|
- # Build prompt message
|
|
|
- if isinstance(key, int):
|
|
|
- prompt_msg = f"{var_name}[{key}]"
|
|
|
- else:
|
|
|
- prompt_msg = f"{var_name}['{key}']"
|
|
|
-
|
|
|
- if var_data.get('description'):
|
|
|
- prompt_msg = f"Enter {prompt_msg} ({var_data['description']})"
|
|
|
- else:
|
|
|
- prompt_msg = f"Enter {prompt_msg}"
|
|
|
-
|
|
|
- if required:
|
|
|
- prompt_msg += " [red](Required)[/red]"
|
|
|
-
|
|
|
- # Get current value if exists
|
|
|
- current_value = None
|
|
|
- if var_name in self.final_values:
|
|
|
- if isinstance(self.final_values[var_name], dict) and key in self.final_values[var_name]:
|
|
|
- current_value = self.final_values[var_name][key]
|
|
|
- elif isinstance(self.final_values[var_name], list) and isinstance(key, int) and key < len(self.final_values[var_name]):
|
|
|
- current_value = self.final_values[var_name][key]
|
|
|
-
|
|
|
- # Prompt based on type
|
|
|
- handler = self.prompt_handlers.get(var_type, self.prompt_handlers['string'])
|
|
|
- if var_type == 'string':
|
|
|
- return handler(prompt_msg, current_value, required)
|
|
|
- else:
|
|
|
- return handler(prompt_msg, current_value)
|
|
|
-
|
|
|
- def _should_process_optional_group(self, group_name: str) -> bool:
|
|
|
- """Ask if user wants to configure optional settings for a group."""
|
|
|
- try:
|
|
|
- return Confirm.ask(f"Do you want to configure {group_name} settings?", default=False)
|
|
|
- except (EOFError, KeyboardInterrupt):
|
|
|
- return False
|
|
|
-
|
|
|
-
|
|
|
- def _prompt_enabler(self, group_name: str, enabler_var_name: str) -> bool:
|
|
|
- """Prompt for a group enabler variable."""
|
|
|
- current_value = self.final_values.get(enabler_var_name, False)
|
|
|
- try:
|
|
|
- return Confirm.ask(
|
|
|
- f"Do you want to enable [bold]{group_name}[/bold]?",
|
|
|
- default=bool(current_value)
|
|
|
- )
|
|
|
- except (EOFError, KeyboardInterrupt):
|
|
|
- logger.debug(f"User interrupted enabler prompt for {group_name}")
|
|
|
- return False
|
|
|
-
|
|
|
- def _show_group_header(self, group_name: str, group_data: Dict[str, Any]):
|
|
|
- """Display group header."""
|
|
|
- icon = group_data.get('icon', '')
|
|
|
- display_name = group_data.get('display_name', group_name.title())
|
|
|
- icon_display = f"{icon} " if icon else ""
|
|
|
- self.console.print(f"\n{icon_display}[bold magenta]{display_name} Variables[/bold magenta]")
|
|
|
-
|
|
|
-
|
|
|
- def _show_preview(self, variables: List[str]):
|
|
|
- """Show preview of configured values."""
|
|
|
- if not variables:
|
|
|
- return
|
|
|
-
|
|
|
- previews = []
|
|
|
- for var_name in variables:
|
|
|
- value = self.final_values.get(var_name)
|
|
|
- if value is None:
|
|
|
- display_value = "not set"
|
|
|
- elif isinstance(value, dict):
|
|
|
- # Show dict values compactly
|
|
|
- items = [f"{k}={v}" for k, v in value.items()]
|
|
|
- display_value = "{" + ", ".join(items[:2]) + ("..." if len(items) > 2 else "") + "}"
|
|
|
- elif isinstance(value, list):
|
|
|
- # Show list values compactly
|
|
|
- display_value = "[" + ", ".join(str(v) for v in value[:2]) + (", ..." if len(value) > 2 else "") + "]"
|
|
|
- else:
|
|
|
- display_value = str(value)[:22] + "..." if len(str(value)) > 25 else str(value)
|
|
|
- previews.append(f"{var_name}={display_value}")
|
|
|
-
|
|
|
- self.console.print(f"[dim white]({', '.join(previews)})[/dim white]")
|
|
|
-
|
|
|
-
|
|
|
- def _prompt_for_variable(self, var_name: str, var_data: Dict[str, Any], required: bool = False, current_value: Any = None) -> Any:
|
|
|
- """Prompt user for a single variable.
|
|
|
-
|
|
|
- Note: Multivalue variables with patterns are handled separately via _prompt_for_multivalue_key.
|
|
|
- This method only handles simple variables or multivalue without specific patterns.
|
|
|
- """
|
|
|
- var_type = var_data.get('type', 'string')
|
|
|
+ console.print(f" {display_name}: [dim]{value}[/dim]")
|
|
|
+
|
|
|
+ def _prompt_variable(
|
|
|
+ self,
|
|
|
+ name: str,
|
|
|
+ var: Any,
|
|
|
+ required: bool = False,
|
|
|
+ current_value: Any = None
|
|
|
+ ) -> Any:
|
|
|
+ """Prompt for a single variable value."""
|
|
|
+ var_type = var.type if hasattr(var, 'type') else 'string'
|
|
|
+ description = var.description if hasattr(var, 'description') else ''
|
|
|
|
|
|
# Build prompt message
|
|
|
- prompt_message = self._build_prompt_message(var_name, var_data, required, current_value)
|
|
|
-
|
|
|
- # Get handler and execute prompt
|
|
|
- handler = self.prompt_handlers.get(var_type, self.prompt_handlers['string'])
|
|
|
-
|
|
|
- try:
|
|
|
- # Special handling for choice type (needs options)
|
|
|
- if var_type == 'choice':
|
|
|
- return handler(prompt_message, current_value, var_data.get('options', []))
|
|
|
- # Special handling for string type (needs required flag)
|
|
|
- elif var_type == 'string':
|
|
|
- return handler(prompt_message, current_value, required)
|
|
|
- else:
|
|
|
- return handler(prompt_message, current_value)
|
|
|
- except KeyboardInterrupt:
|
|
|
- raise
|
|
|
- except Exception as e:
|
|
|
- logger.error(f"Error prompting for {var_name}: {e}")
|
|
|
- self.console.print(f"[red]Error getting input for {var_name}[/red]")
|
|
|
- # Fallback to string prompt
|
|
|
- return self._prompt_string(prompt_message, current_value, required)
|
|
|
-
|
|
|
- def _build_prompt_message(self, var_name: str, var_data: Dict[str, Any], required: bool, current_value: Any) -> str:
|
|
|
- """Build the prompt message for a variable."""
|
|
|
- parts = ["Enter", f"[bold]{var_name}[/bold]"]
|
|
|
-
|
|
|
- if description := var_data.get('description'):
|
|
|
+ parts = [f"Enter {name}"]
|
|
|
+ if description:
|
|
|
parts.append(f"({description})")
|
|
|
-
|
|
|
if current_value is not None:
|
|
|
parts.append(f"[dim]({current_value})[/dim]")
|
|
|
elif required:
|
|
|
parts.append("[red](Required)[/red]")
|
|
|
|
|
|
- return " ".join(parts)
|
|
|
-
|
|
|
- def _prompt_string(self, prompt_message: str, current_value: Any = None, required: bool = False) -> str:
|
|
|
- """Prompt for string input."""
|
|
|
- default = str(current_value) if current_value is not None else None
|
|
|
-
|
|
|
- while True:
|
|
|
- try:
|
|
|
- value = Prompt.ask(prompt_message, default=default) or ""
|
|
|
-
|
|
|
- if required and not value.strip():
|
|
|
- self.console.print("[red]This field is required[/red]")
|
|
|
- continue
|
|
|
-
|
|
|
- return value.strip()
|
|
|
- except (EOFError, KeyboardInterrupt):
|
|
|
- raise KeyboardInterrupt("Operation cancelled by user")
|
|
|
-
|
|
|
- def _prompt_boolean(self, prompt_message: str, current_value: Any = None) -> bool:
|
|
|
- """Prompt for boolean input."""
|
|
|
- default = bool(current_value) if current_value is not None else None
|
|
|
- try:
|
|
|
- return Confirm.ask(prompt_message, default=default)
|
|
|
- except (EOFError, KeyboardInterrupt):
|
|
|
- raise KeyboardInterrupt("Operation cancelled by user")
|
|
|
-
|
|
|
- def _prompt_integer(self, prompt_message: str, current_value: Any = None) -> int:
|
|
|
- """Prompt for integer input."""
|
|
|
- default = int(current_value) if current_value is not None else None
|
|
|
-
|
|
|
- while True:
|
|
|
- try:
|
|
|
- return IntPrompt.ask(prompt_message, default=default)
|
|
|
- except ValueError:
|
|
|
- self.console.print("[red]Please enter a valid integer[/red]")
|
|
|
- except (EOFError, KeyboardInterrupt):
|
|
|
- raise KeyboardInterrupt("Operation cancelled by user")
|
|
|
-
|
|
|
- def _prompt_float(self, prompt_message: str, current_value: Any = None) -> float:
|
|
|
- """Prompt for float input."""
|
|
|
- default = float(current_value) if current_value is not None else None
|
|
|
-
|
|
|
- while True:
|
|
|
- try:
|
|
|
- return FloatPrompt.ask(prompt_message, default=default)
|
|
|
- except ValueError:
|
|
|
- self.console.print("[red]Please enter a valid number[/red]")
|
|
|
- except (EOFError, KeyboardInterrupt):
|
|
|
- raise KeyboardInterrupt("Operation cancelled by user")
|
|
|
-
|
|
|
- def _prompt_choice(self, prompt_message: str, current_value: Any = None, options: List[Any] = None) -> Any:
|
|
|
- """Prompt for choice from options."""
|
|
|
- if not options:
|
|
|
- return self._prompt_string(prompt_message, current_value)
|
|
|
+ prompt_msg = " ".join(parts)
|
|
|
|
|
|
- # Show options
|
|
|
- self.console.print("\n[dim]Available options:[/dim]")
|
|
|
- for i, option in enumerate(options, 1):
|
|
|
- marker = "→" if option == current_value else " "
|
|
|
- self.console.print(f" {marker} {i}. {option}")
|
|
|
+ # Handle different types
|
|
|
+ if var_type == 'boolean':
|
|
|
+ default = bool(current_value) if current_value is not None else None
|
|
|
+ return Confirm.ask(prompt_msg, default=default)
|
|
|
|
|
|
- while True:
|
|
|
- try:
|
|
|
- choice = Prompt.ask(f"{prompt_message} (1-{len(options)})")
|
|
|
-
|
|
|
- # Try numeric selection
|
|
|
+ elif var_type == 'integer':
|
|
|
+ default = int(current_value) if current_value is not None else None
|
|
|
+ while True:
|
|
|
try:
|
|
|
- idx = int(choice) - 1
|
|
|
- if 0 <= idx < len(options):
|
|
|
- return options[idx]
|
|
|
+ return IntPrompt.ask(prompt_msg, default=default)
|
|
|
except ValueError:
|
|
|
- # Try string match
|
|
|
- matches = [opt for opt in options if str(opt).lower() == choice.lower()]
|
|
|
- if matches:
|
|
|
- return matches[0]
|
|
|
-
|
|
|
- self.console.print(f"[red]Invalid choice. Enter 1-{len(options)} or option name[/red]")
|
|
|
- except (EOFError, KeyboardInterrupt):
|
|
|
- raise KeyboardInterrupt("Operation cancelled by user")
|
|
|
-
|
|
|
- def _prompt_list(self, prompt_message: str, current_value: Any = None) -> List[str]:
|
|
|
- """Prompt for list input (comma-separated)."""
|
|
|
- default = ", ".join(str(item) for item in current_value) if isinstance(current_value, list) else str(current_value or "")
|
|
|
-
|
|
|
- self.console.print("[dim]Enter values separated by commas[/dim]")
|
|
|
+ console.print("[red]Please enter a valid integer[/red]")
|
|
|
|
|
|
- try:
|
|
|
- value = Prompt.ask(prompt_message, default=default)
|
|
|
- return [item.strip() for item in value.split(',') if item.strip()] if value.strip() else []
|
|
|
- except (EOFError, KeyboardInterrupt):
|
|
|
- raise KeyboardInterrupt("Operation cancelled by user")
|
|
|
-
|
|
|
- def _show_summary(self):
|
|
|
- """Display summary of configured variables."""
|
|
|
- if not self.final_values:
|
|
|
- return
|
|
|
-
|
|
|
- # Compact summary for few variables, table for many
|
|
|
- if len(self.final_values) <= 5:
|
|
|
- summaries = []
|
|
|
- for name, value in self.final_values.items():
|
|
|
- display_value = self._truncate_value(value, 20)
|
|
|
- summaries.append(f"[cyan]{name}[/cyan]=[green]{display_value}[/green]")
|
|
|
- self.console.print(f"\n[dim]Using:[/dim] {', '.join(summaries)}")
|
|
|
- else:
|
|
|
- table = Table(box=box.SIMPLE)
|
|
|
- table.add_column("Variable", style="cyan")
|
|
|
- table.add_column("Value", style="green")
|
|
|
-
|
|
|
- for name, value in self.final_values.items():
|
|
|
- display_value = self._truncate_value(value, 50)
|
|
|
- table.add_row(name, display_value)
|
|
|
-
|
|
|
- self.console.print(table)
|
|
|
-
|
|
|
- self.console.print()
|
|
|
+ elif var_type == 'float':
|
|
|
+ default = float(current_value) if current_value is not None else None
|
|
|
+ while True:
|
|
|
+ try:
|
|
|
+ return FloatPrompt.ask(prompt_msg, default=default)
|
|
|
+ except ValueError:
|
|
|
+ console.print("[red]Please enter a valid number[/red]")
|
|
|
|
|
|
- # Confirm generation
|
|
|
- if not Confirm.ask("Proceed with generation?", default=True):
|
|
|
- raise KeyboardInterrupt("Generation cancelled by user")
|
|
|
-
|
|
|
- def _truncate_value(self, value: Any, max_length: int) -> str:
|
|
|
- """Truncate value for display."""
|
|
|
- display_value = ", ".join(str(item) for item in value) if isinstance(value, list) else str(value)
|
|
|
- return display_value[:max_length-3] + "..." if len(display_value) > max_length else display_value
|
|
|
+ else: # string
|
|
|
+ default = str(current_value) if current_value is not None else None
|
|
|
+ while True:
|
|
|
+ value = Prompt.ask(prompt_msg, default=default) or ""
|
|
|
+ if required and not value.strip():
|
|
|
+ console.print("[red]This field is required[/red]")
|
|
|
+ continue
|
|
|
+ return value.strip()
|