diff --git a/cli/core/prompt.py b/cli/core/prompt.py index a9666107..ab3075f9 100644 --- a/cli/core/prompt.py +++ b/cli/core/prompt.py @@ -118,11 +118,7 @@ class SimplifiedPromptHandler: console.print(f"\n[bold cyan]{display_name} - Required Configuration[/bold cyan]") for var_name in required: var = self.variables[var_name] - if var.is_dict: - console.print(f"\n[cyan]{var.name}[/cyan]") - self.values[var_name] = self._prompt_dict_variable(var) - else: - self.values[var_name] = self._prompt_variable(var, required=True) + self.values[var_name] = self._prompt_variable(var, required=True) # Process optional variables if optional: @@ -135,49 +131,9 @@ class SimplifiedPromptHandler: if Confirm.ask("Do you want to change any values?", default=False): for var_name in optional: var = self.variables[var_name] - if var.is_dict: - console.print(f"\n[cyan]{var.name}[/cyan]") - self.values[var_name] = self._prompt_dict_variable( - var, current_values=self.values.get(var_name, {}) - ) - else: - self.values[var_name] = self._prompt_variable( - var, current_value=self.values[var_name] - ) - - - def _prompt_dict_variable(self, var: TemplateVariable, current_values: Dict[str, Any] = None) -> Dict[str, Any]: - """Prompt for a dict variable with dynamic keys.""" - result = {} - current_values = current_values or {} - - for key in var.dict_keys: - # Use current value if available, otherwise check for default - current_value = current_values.get(key) - if current_value is None and isinstance(var.default, dict): - current_value = var.default.get(key) - - 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: - value = Prompt.ask(prompt_msg, default=str(current_value) if current_value else None) - result[key] = value if value else current_value - - return result + self.values[var_name] = self._prompt_variable( + var, current_value=self.values[var_name] + ) def _show_variables(self, var_names: List[str]): """Display current variable values.""" @@ -185,12 +141,7 @@ class SimplifiedPromptHandler: var = self.variables[var_name] value = self.values.get(var_name, var.default) if value is not None: - # Special formatting for dict values - show each key separately - if isinstance(value, dict): - for key, val in value.items(): - console.print(f" {var.display_name}['{key}']: [dim]{val}[/dim]") - else: - console.print(f" {var.display_name}: [dim]{value}[/dim]") + console.print(f" {var.display_name}: [dim]{value}[/dim]") def _prompt_variable( self, diff --git a/cli/core/template.py b/cli/core/template.py index efdeb465..4f5dbe35 100644 --- a/cli/core/template.py +++ b/cli/core/template.py @@ -36,7 +36,6 @@ class Template: # Template variable analysis results vars: Set[str] = field(default_factory=set, init=False) var_defaults: Dict[str, Any] = field(default_factory=dict, init=False) - var_dict_keys: Dict[str, List[str]] = field(default_factory=dict, init=False) # Track dict access patterns variables: Dict[str, TemplateVariable] = field(default_factory=dict, init=False) # Analyzed variables def __post_init__(self): @@ -52,10 +51,10 @@ class Template: self.size = self.file_path.stat().st_size if self.file_path.exists() else 0 # Parse template variables - self.vars, self.var_defaults, self.var_dict_keys = self._parse_template_variables(self.content) + self.vars, self.var_defaults = self._parse_template_variables(self.content) # Analyze variables to create TemplateVariable objects self.variables = analyze_template_variables( - self.vars, self.var_defaults, self.var_dict_keys, self.content + self.vars, self.var_defaults, self.content ) @staticmethod @@ -96,17 +95,15 @@ class Template: post = frontmatter.load(f) return post.metadata, post.content - def _parse_template_variables(self, template_content: str) -> Tuple[Set[str], Dict[str, Any], Dict[str, List[str]]]: + def _parse_template_variables(self, template_content: str) -> Tuple[Set[str], Dict[str, Any]]: """Parse Jinja2 template to extract variables and their defaults. - Handles multiple patterns: + Handles: - Simple variables: service_name - - Dotted notation: traefik.host - - Dict notation: service_port['http'] - - Nested patterns: nginx_dashboard.port['dashboard'] + - Dotted notation: traefik.host, service_port.http Returns: - Tuple of (all_variable_names, variable_defaults, dict_access_patterns) + Tuple of (all_variable_names, variable_defaults) """ try: env = self._create_jinja_env() @@ -115,42 +112,7 @@ class Template: # Start with variables found by Jinja2's meta utility all_variables = meta.find_undeclared_variables(ast) - # Track dict access patterns - dict_keys = {} # var_name -> list of keys accessed - - # Handle all Getitem nodes (dict access) - for node in ast.find_all(nodes.Getitem): - if isinstance(node.arg, nodes.Const) and isinstance(node.arg.value, str): - key = node.arg.value - current = node.node - - # Handle nested patterns like nginx_dashboard.port['dashboard'] - if isinstance(current, nodes.Getattr): - # Build the full dotted name - parts = [current.attr] - base = current.node - while isinstance(base, nodes.Getattr): - parts.insert(0, base.attr) - base = base.node - if isinstance(base, nodes.Name): - parts.insert(0, base.name) - var_name = '.'.join(parts) - # This is a dotted variable with dict access - all_variables.add(var_name) - if var_name not in dict_keys: - dict_keys[var_name] = [] - if key not in dict_keys[var_name]: - dict_keys[var_name].append(key) - - # Handle simple dict access like service_port['http'] - elif isinstance(current, nodes.Name): - var_name = current.name - if var_name not in dict_keys: - dict_keys[var_name] = [] - if key not in dict_keys[var_name]: - dict_keys[var_name].append(key) - - # Handle dotted notation variables (like traefik.host, swarm.replicas) + # Handle dotted notation variables (like traefik.host, service_port.http) for node in ast.find_all(nodes.Getattr): current = node.node # Build the full dotted name @@ -171,37 +133,6 @@ class Template: if isinstance(node.node, nodes.Name): defaults[node.node.name] = node.args[0].value - # Handle dict access defaults - elif isinstance(node.node, nodes.Getitem): - if isinstance(node.node.arg, nodes.Const): - key = node.node.arg.value - - # Handle nested pattern defaults: {{ nginx_dashboard.port['dashboard'] | default(8081) }} - if isinstance(node.node.node, nodes.Getattr): - # Build the full dotted name - parts = [] - current = node.node.node - while isinstance(current, nodes.Getattr): - parts.insert(0, current.attr) - current = current.node - if isinstance(current, nodes.Name): - parts.insert(0, current.name) - var_name = '.'.join(parts) - if var_name not in defaults: - defaults[var_name] = {} - if not isinstance(defaults[var_name], dict): - defaults[var_name] = {} - defaults[var_name][key] = node.args[0].value - - # Handle simple dict defaults: {{ var['key'] | default(value) }} - elif isinstance(node.node.node, nodes.Name): - var_name = node.node.node.name - if var_name not in defaults: - defaults[var_name] = {} - if not isinstance(defaults[var_name], dict): - defaults[var_name] = {} - defaults[var_name][key] = node.args[0].value - # Handle dotted variable defaults: {{ traefik.host | default('example.com') }} elif isinstance(node.node, nodes.Getattr): # Build the full dotted name @@ -215,10 +146,10 @@ class Template: var_name = '.'.join(parts) defaults[var_name] = node.args[0].value - return all_variables, defaults, dict_keys + return all_variables, defaults except Exception as e: logging.getLogger('boilerplates').debug(f"Error parsing template variables: {e}") - return set(), {}, {} + return set(), {} def validate(self) -> List[str]: """Validate template integrity. diff --git a/cli/core/variables.py b/cli/core/variables.py index ab16de0e..1e970c95 100644 --- a/cli/core/variables.py +++ b/cli/core/variables.py @@ -8,8 +8,7 @@ class TemplateVariable: Represents a variable found in a template with all its properties: - Simple variables: service_name, container_name - - Dotted variables: traefik.host, network.name - - Dict variables: service_port['http'], nginx_dashboard.port['dashboard'] + - Dotted variables: traefik.host, network.name, service_port.http - Enabler variables: Variables used in {% if var %} conditions """ name: str @@ -18,8 +17,6 @@ class TemplateVariable: # Variable characteristics is_enabler: bool = False # Used in {% if %} conditions - is_dict: bool = False # Has dict access patterns like var['key'] - dict_keys: List[str] = field(default_factory=list) # Keys accessed if is_dict # Grouping info (extracted from dotted notation) group: Optional[str] = None # e.g., 'traefik' for 'traefik.host' @@ -41,7 +38,6 @@ class TemplateVariable: def analyze_template_variables( vars_used: Set[str], var_defaults: Dict[str, Any], - var_dict_keys: Dict[str, List[str]], template_content: str ) -> Dict[str, TemplateVariable]: """Analyze template variables and create TemplateVariable objects. @@ -49,7 +45,6 @@ def analyze_template_variables( Args: vars_used: Set of all variable names used in template var_defaults: Dict of variable defaults from template - var_dict_keys: Dict of variables with dict access patterns template_content: The raw template content for additional analysis Returns: @@ -69,22 +64,16 @@ def analyze_template_variables( # Detect if it's an enabler var.is_enabler = var_name in enablers - # Detect if it's a dict variable - if var_name in var_dict_keys: - var.is_dict = True - var.dict_keys = var_dict_keys[var_name] - # Dict variables might have dict defaults - if isinstance(var.default, dict): - var.type = "dict" - # Infer type from default value - if var.default is not None and var.type == "string": + if var.default is not None: if isinstance(var.default, bool): var.type = "boolean" elif isinstance(var.default, int): var.type = "integer" elif isinstance(var.default, float): var.type = "float" + else: + var.type = "string" # If it's an enabler without a default, assume boolean if var.is_enabler and var.default is None: diff --git a/library/compose/nginx/compose.yaml b/library/compose/nginx/compose.yaml index b3a944be..ebd40015 100644 --- a/library/compose/nginx/compose.yaml +++ b/library/compose/nginx/compose.yaml @@ -29,12 +29,12 @@ services: - traefik.http.routers.{{ container_name }}.service={{ container_name }} {% endif %} {% endif %} - {% if not traefik and service_port %} + {% if not traefik %} ports: - - "{{ service_port['http'] | default(8080) }}:80" - - "{{ service_port['https'] | default(8443) }}:443" + - "{{ service_port_http | default(8080) }}:80" + - "{{ service_port_https | default(8443) }}:443" {% if nginx_dashboard %} - - "{{ nginx_dashboard.port['dashboard'] | default(8081) }}:8080" + - "{{ nginx_dashboard_port_dashboard | default(8081) }}:8080" {% endif %} {% endif %} # volumes: