|
|
@@ -1,255 +0,0 @@
|
|
|
-"""Simplified prompt handler for template 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
|
|
|
-import logging
|
|
|
-from .variables import Variable
|
|
|
-
|
|
|
-logger = logging.getLogger('boilerplates')
|
|
|
-console = Console()
|
|
|
-
|
|
|
-
|
|
|
-class SimplifiedPromptHandler:
|
|
|
- """Prompt handler for template-detected variables."""
|
|
|
-
|
|
|
- def __init__(self, variables: Dict[str, Variable]):
|
|
|
- """Initialize with template variables.
|
|
|
-
|
|
|
- Args:
|
|
|
- variables: Dict of variable name to Variable object
|
|
|
- """
|
|
|
- self.variables = variables
|
|
|
- self.values = {}
|
|
|
- self.category_metadata = {} # Will be set by Module if available
|
|
|
-
|
|
|
- def __call__(self) -> Dict[str, Any]:
|
|
|
- """Execute the prompting flow."""
|
|
|
- # Group variables by prefix (preserves registration order)
|
|
|
- groups, standalone = self._group_variables()
|
|
|
-
|
|
|
- # Process standalone variables first as "General" group
|
|
|
- if standalone:
|
|
|
- self._process_variable_set("General", standalone, is_group=False)
|
|
|
-
|
|
|
- # 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 self.values
|
|
|
-
|
|
|
- def _group_variables(self) -> Tuple[Dict[str, List[str]], List[str]]:
|
|
|
- """Group variables by their prefix or enabler status.
|
|
|
-
|
|
|
- Returns:
|
|
|
- (groups, standalone) where groups is {prefix: [var_names]}
|
|
|
- """
|
|
|
- groups = OrderedDict()
|
|
|
- standalone = []
|
|
|
-
|
|
|
- # First pass: identify all groups
|
|
|
- for var_name, var in self.variables.items():
|
|
|
- if var.group:
|
|
|
- # This variable belongs to a group
|
|
|
- if var.group not in groups:
|
|
|
- groups[var.group] = []
|
|
|
- groups[var.group].append(var_name)
|
|
|
- else:
|
|
|
- # Check if this is an enabler for other variables
|
|
|
- # An enabler is a variable that other variables use as their group
|
|
|
- is_group_enabler = any(v.group == var_name for v in self.variables.values())
|
|
|
- if is_group_enabler:
|
|
|
- if var_name not in groups:
|
|
|
- groups[var_name] = []
|
|
|
- # The enabler itself is not added to the group list
|
|
|
- else:
|
|
|
- # Truly standalone variable
|
|
|
- standalone.append(var_name)
|
|
|
-
|
|
|
- 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.
|
|
|
-
|
|
|
- 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
|
|
|
-
|
|
|
- # Get icon and description for this category
|
|
|
- icon = self._get_category_icon(display_name)
|
|
|
- description = self._get_category_description(display_name)
|
|
|
-
|
|
|
- # Check if this group has an enabler
|
|
|
- group_name = display_name.lower()
|
|
|
- enabler = None
|
|
|
- if is_group and group_name in self.variables:
|
|
|
- enabler_var = self.variables[group_name]
|
|
|
- if enabler_var.is_enabler:
|
|
|
- enabler = group_name
|
|
|
- # Show section header with icon and description
|
|
|
- header = f"\n{icon}[bold cyan]{display_name}[/bold cyan]"
|
|
|
- if description:
|
|
|
- header += f" [dim]- {description}[/dim]"
|
|
|
- console.print(header)
|
|
|
- console.print() # Add newline after header
|
|
|
- enabled = Confirm.ask(
|
|
|
- f"Enable {enabler}?",
|
|
|
- default=bool(enabler_var.default)
|
|
|
- )
|
|
|
- self.values[enabler] = enabled
|
|
|
-
|
|
|
- if not enabled:
|
|
|
- # Skip all group variables
|
|
|
- return
|
|
|
-
|
|
|
- # Split into required and optional
|
|
|
- required = []
|
|
|
- optional = []
|
|
|
- for var_name in var_names:
|
|
|
- var = self.variables[var_name]
|
|
|
- if var.is_required:
|
|
|
- required.append(var_name)
|
|
|
- else:
|
|
|
- optional.append(var_name)
|
|
|
-
|
|
|
- # Apply defaults
|
|
|
- for var_name in optional:
|
|
|
- self.values[var_name] = self.variables[var_name].default
|
|
|
-
|
|
|
- # Process required variables
|
|
|
- if required:
|
|
|
- if not enabler: # Show header only if we haven't shown it for enabler
|
|
|
- header = f"\n{icon}[bold cyan]{display_name}[/bold cyan]"
|
|
|
- if description:
|
|
|
- header += f" [dim]- {description}[/dim]"
|
|
|
- console.print(header)
|
|
|
- console.print() # Add newline after header
|
|
|
- for var_name in required:
|
|
|
- var = self.variables[var_name]
|
|
|
- self.values[var_name] = self._prompt_variable(var, required=True)
|
|
|
-
|
|
|
- # Process optional variables
|
|
|
- if optional:
|
|
|
- # Filter out enabler variables from display
|
|
|
- display_optional = [v for v in optional if v != enabler]
|
|
|
- if display_optional:
|
|
|
- # Show section header if not already shown
|
|
|
- if not required and not enabler:
|
|
|
- header = f"\n{icon}[bold cyan]{display_name}[/bold cyan]"
|
|
|
- if description:
|
|
|
- header += f" [dim]- {description}[/dim]"
|
|
|
- console.print(header)
|
|
|
- console.print() # Add newline after header
|
|
|
-
|
|
|
- # Show current values with label
|
|
|
- console.print("\n[white]Default values [/white]", end="")
|
|
|
- self._show_variables_inline(display_optional)
|
|
|
-
|
|
|
- if Confirm.ask("Do you want to change any values?", default=False):
|
|
|
- console.print() # Add newline after prompt
|
|
|
- for var_name in optional:
|
|
|
- # Skip the enabler variable as it was already handled
|
|
|
- if var_name == enabler:
|
|
|
- continue
|
|
|
- var = self.variables[var_name]
|
|
|
- self.values[var_name] = self._prompt_variable(
|
|
|
- var, current_value=self.values[var_name]
|
|
|
- )
|
|
|
-
|
|
|
- def _show_variables_inline(self, var_names: List[str]):
|
|
|
- """Display variables inline without header."""
|
|
|
- items = []
|
|
|
- for var_name in var_names:
|
|
|
- var = self.variables[var_name]
|
|
|
- value = self.values.get(var_name, var.default)
|
|
|
- if value is not None:
|
|
|
- # Format value based on type
|
|
|
- if isinstance(value, bool):
|
|
|
- formatted_value = str(value).lower()
|
|
|
- elif isinstance(value, str) and ' ' in value:
|
|
|
- formatted_value = f"'{value}'"
|
|
|
- else:
|
|
|
- formatted_value = str(value)
|
|
|
- items.append(f"{var.display_name}: [cyan]({formatted_value})[/cyan]")
|
|
|
-
|
|
|
- if items:
|
|
|
- console.print(f"[dim white]{', '.join(items)}[/dim white]")
|
|
|
-
|
|
|
- def _get_category_icon(self, category: str) -> str:
|
|
|
- """Get icon for a category."""
|
|
|
- # Only use icons from metadata
|
|
|
- if self.category_metadata and category.lower() in self.category_metadata:
|
|
|
- cat_meta = self.category_metadata[category.lower()]
|
|
|
- if 'icon' in cat_meta:
|
|
|
- return cat_meta['icon'] + ' '
|
|
|
- return '' # No icon if not defined in metadata
|
|
|
-
|
|
|
- def _get_category_description(self, category: str) -> str:
|
|
|
- """Get description for a category."""
|
|
|
- if self.category_metadata and category.lower() in self.category_metadata:
|
|
|
- cat_meta = self.category_metadata[category.lower()]
|
|
|
- if 'description' in cat_meta:
|
|
|
- return cat_meta['description']
|
|
|
- return '' # No description if not defined in metadata
|
|
|
-
|
|
|
- def _prompt_variable(
|
|
|
- self,
|
|
|
- var: Variable,
|
|
|
- required: bool = False,
|
|
|
- current_value: Any = None
|
|
|
- ) -> Any:
|
|
|
- """Prompt for a single variable value."""
|
|
|
- # Build prompt message with description if available
|
|
|
- display_text = var.description if var.description else var.display_name
|
|
|
-
|
|
|
- # Add hint if available
|
|
|
- hint_text = ""
|
|
|
- if var.hint:
|
|
|
- hint_text = f" [dim]({var.hint})[/dim]"
|
|
|
-
|
|
|
- # Build the full prompt
|
|
|
- if current_value is not None:
|
|
|
- prompt_msg = f"Enter {display_text}{hint_text} [dim]({current_value})[/dim]"
|
|
|
- elif required:
|
|
|
- prompt_msg = f"Enter {display_text}{hint_text} [red](Required)[/red]"
|
|
|
- else:
|
|
|
- prompt_msg = f"Enter {display_text}{hint_text}"
|
|
|
-
|
|
|
- # Show tip if available
|
|
|
- if var.tip:
|
|
|
- console.print(f"[dim cyan]💡 {var.tip}[/dim cyan]")
|
|
|
-
|
|
|
- # 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)
|
|
|
-
|
|
|
- elif var.type == 'integer':
|
|
|
- default = int(current_value) if current_value is not None else None
|
|
|
- while True:
|
|
|
- try:
|
|
|
- return IntPrompt.ask(prompt_msg, default=default)
|
|
|
- except ValueError:
|
|
|
- console.print("[red]Please enter a valid integer[/red]")
|
|
|
-
|
|
|
- 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]")
|
|
|
-
|
|
|
- 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()
|