mirror of
https://github.com/ChristianLempa/boilerplates.git
synced 2026-09-27 09:26:22 -04:00
refactor: Simplify to dotted notation only, remove dict variable handling
- Removed all dict variable handling code - Simplified TemplateVariable dataclass (no more is_dict, dict_keys) - Simplified template parser to only handle dotted notation - Removed _prompt_dict_variable from prompt handler - Updated nginx template to use underscores instead of dots/dicts This greatly simplifies the system but requires templates to use underscores (service_port_http) instead of dots or dict access to avoid Jinja2's attribute resolution confusion.
This commit is contained in:
+5
-54
@@ -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,
|
||||
|
||||
+9
-78
@@ -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.
|
||||
|
||||
+4
-15
@@ -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:
|
||||
|
||||
@@ -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:
|
||||
|
||||
Reference in New Issue
Block a user