""" Template analyzer for dependency extraction. Implements FR-2.2: TemplateAnalysis Analyzes templates to extract and catalog content macros. """ from typing import List, Set from markitect.prompts.templates.models import PromptTemplate, MacroKind from markitect.prompts.templates.parser import MacroParser class TemplateAnalysisResult: """ Result of template analysis. Provides summary information about template dependencies. Attributes: template: Analyzed template total_macros: Total number of macros found required_count: Number of required macros optional_count: Number of optional macros generate_count: Number of generate macros unique_targets: Set of unique target names has_circular_potential: Whether template references itself """ def __init__(self, template: PromptTemplate): self.template = template self.total_macros = len(template.macros) self.required_count = len(template.get_required_dependencies()) self.optional_count = len(template.get_optional_dependencies()) self.generate_count = len(template.get_generators()) self.unique_targets = self._get_unique_targets() self.has_circular_potential = template.name in self.unique_targets def _get_unique_targets(self) -> Set[str]: """Get set of unique target names from all macros.""" return {macro.target for macro in self.template.macros} def to_dict(self) -> dict: """Convert result to dictionary.""" return { "template_id": self.template.id, "template_name": self.template.name, "total_macros": self.total_macros, "required_count": self.required_count, "optional_count": self.optional_count, "generate_count": self.generate_count, "unique_targets": list(self.unique_targets), "has_circular_potential": self.has_circular_potential, } class TemplateAnalyzer: """ Analyzer for extracting dependencies from templates. Implements FR-2.2: TemplateAnalysis Uses MacroParser to detect and extract ContentMacros, then populates the template's macro list and provides analysis results. """ def __init__(self): self.parser = MacroParser() def analyze(self, template: PromptTemplate, content: str) -> TemplateAnalysisResult: """ Analyze template content and extract macros. Updates the template's macros list and marks it as analyzed. Args: template: Template to analyze content: Template content to parse Returns: Analysis result with summary information Raises: MacroParsingError: If macro syntax is invalid """ # Parse macros from content macros = self.parser.parse(content) # Update template template.macros = macros template.analyzed = True # Create and return analysis result return TemplateAnalysisResult(template) def quick_check(self, content: str) -> dict: """ Quick check of template content without full analysis. Useful for validation before template creation. Args: content: Template content Returns: Dictionary with macro counts and basic info """ has_macros = self.parser.has_macros(content) if not has_macros: return { "has_macros": False, "required": 0, "optional": 0, "generate": 0, "total": 0, } counts = self.parser.count_macros(content) return { "has_macros": True, "required": counts['required'], "optional": counts['optional'], "generate": counts['generate'], "total": sum(counts.values()), } def find_dependencies(self, template: PromptTemplate) -> dict: """ Extract dependency information from analyzed template. Args: template: Analyzed template Returns: Dictionary with categorized dependencies Raises: ValueError: If template has not been analyzed """ if not template.analyzed: raise ValueError( f"Template '{template.name}' has not been analyzed. " "Call analyze() first." ) return { "required": template.get_required_dependencies(), "optional": template.get_optional_dependencies(), "generators": template.get_generators(), "all_targets": list({m.target for m in template.macros}), } def validate_references( self, template: PromptTemplate, available_artifacts: Set[str], ) -> dict: """ Validate that template references can be resolved. Args: template: Template to validate available_artifacts: Set of available artifact names Returns: Dictionary with validation results: - missing_required: List of missing required artifacts - missing_optional: List of missing optional artifacts - missing_generators: List of missing generator templates - is_valid: True if all required deps are available Raises: ValueError: If template has not been analyzed """ if not template.analyzed: raise ValueError( f"Template '{template.name}' has not been analyzed. " "Call analyze() first." ) required = set(template.get_required_dependencies()) optional = set(template.get_optional_dependencies()) generators = set(template.get_generators()) missing_required = required - available_artifacts missing_optional = optional - available_artifacts missing_generators = generators - available_artifacts return { "missing_required": list(missing_required), "missing_optional": list(missing_optional), "missing_generators": list(missing_generators), "is_valid": len(missing_required) == 0, }