variables.py 3.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120
  1. from typing import Any, Dict, List, Optional, Set
  2. from dataclasses import dataclass, field
  3. @dataclass
  4. class TemplateVariable:
  5. """Variable detected from template analysis.
  6. Represents a variable found in a template with all its properties:
  7. - Simple variables: service_name, container_name
  8. - Dotted variables: traefik.host, network.name, service_port.http
  9. - Enabler variables: Variables used in {% if var %} conditions
  10. """
  11. name: str
  12. default: Any = None
  13. type: str = "string" # string, integer, float, boolean (inferred from default or usage)
  14. # Variable characteristics
  15. is_enabler: bool = False # Used in {% if %} conditions
  16. # Grouping info (extracted from dotted notation)
  17. group: Optional[str] = None # e.g., 'traefik' for 'traefik.host'
  18. # Metadata for enhanced UX
  19. description: Optional[str] = None # Override for variable description
  20. hint: Optional[str] = None # Helpful hint shown during input
  21. tip: Optional[str] = None # Additional tip or best practice
  22. icon: Optional[str] = None # Icon for this specific variable
  23. validation: Optional[str] = None # Regex pattern for validation
  24. @property
  25. def display_name(self) -> str:
  26. """Get display name for prompts."""
  27. if self.group:
  28. # Remove group prefix for display
  29. return self.name.replace(f"{self.group}.", "").replace(".", " ")
  30. return self.name.replace(".", " ")
  31. @property
  32. def is_required(self) -> bool:
  33. """Check if variable is required (no default value)."""
  34. return self.default is None
  35. def analyze_template_variables(
  36. vars_used: Set[str],
  37. var_defaults: Dict[str, Any],
  38. template_content: str
  39. ) -> Dict[str, TemplateVariable]:
  40. """Analyze template variables and create TemplateVariable objects.
  41. Args:
  42. vars_used: Set of all variable names used in template
  43. var_defaults: Dict of variable defaults from template
  44. template_content: The raw template content for additional analysis
  45. Returns:
  46. Dict mapping variable name to TemplateVariable object
  47. """
  48. variables = {}
  49. # Detect enabler variables (used in {% if %} conditions)
  50. enablers = _detect_enablers(template_content)
  51. for var_name in vars_used:
  52. var = TemplateVariable(
  53. name=var_name,
  54. default=var_defaults.get(var_name)
  55. )
  56. # Detect if it's an enabler
  57. var.is_enabler = var_name in enablers
  58. # Infer type from default value
  59. if var.default is not None:
  60. if isinstance(var.default, bool):
  61. var.type = "boolean"
  62. elif isinstance(var.default, int):
  63. var.type = "integer"
  64. elif isinstance(var.default, float):
  65. var.type = "float"
  66. else:
  67. var.type = "string"
  68. # If it's an enabler without a default, assume boolean
  69. if var.is_enabler and var.default is None:
  70. var.type = "boolean"
  71. var.default = False # Default enablers to False
  72. # Detect group from dotted notation
  73. if '.' in var_name:
  74. var.group = var_name.split('.')[0]
  75. variables[var_name] = var
  76. return variables
  77. def _detect_enablers(template_content: str) -> Set[str]:
  78. """Detect variables used as enablers in {% if %} conditions.
  79. Args:
  80. template_content: The raw template content
  81. Returns:
  82. Set of variable names that are used as enablers
  83. """
  84. import re
  85. enablers = set()
  86. # Find variables used in {% if var %} patterns
  87. # This catches: {% if var %}, {% if not var %}, {% if var and ... %}
  88. if_pattern = re.compile(r'{%\s*if\s+(not\s+)?(\w+)(?:\s|%)', re.MULTILINE)
  89. for match in if_pattern.finditer(template_content):
  90. var_name = match.group(2)
  91. # Skip Jinja2 keywords
  92. if var_name not in ['true', 'false', 'none', 'True', 'False', 'None']:
  93. enablers.add(var_name)
  94. return enablers