module.py 7.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178
  1. from abc import ABC, abstractmethod
  2. from pathlib import Path
  3. from typing import Any, Dict, Optional, Tuple, List
  4. import logging
  5. from typer import Typer, Option, Argument
  6. from .library import LibraryManager
  7. from .prompt import PromptHandler
  8. from .template import Template
  9. from .variables import VariableGroup, VariableManager
  10. from .config import ConfigManager
  11. logger = logging.getLogger('boilerplates')
  12. class Module(ABC):
  13. """
  14. Base Module for all CLI Commands.
  15. This class now uses VariableManager for centralized variable management,
  16. providing better organization and more advanced variable operations.
  17. """
  18. def __init__(self, name: str, description: str, files: list[str], vars: list[VariableGroup] = None):
  19. self.name = name
  20. self.description = description
  21. self.files = files
  22. # Initialize ConfigManager and VariableManager with it
  23. self.config_manager = ConfigManager()
  24. self.variable_manager = VariableManager(vars if vars is not None else [], self.config_manager)
  25. self.app = Typer()
  26. self.libraries = LibraryManager() # Initialize library manager
  27. # Validate that required attributes are set
  28. if not self.name:
  29. raise ValueError("Module name must be set")
  30. if not self.description:
  31. raise ValueError("Module description must be set")
  32. if not isinstance(self.files, list) or len(self.files) == 0:
  33. raise ValueError("Module files must be a non-empty list")
  34. if not all(isinstance(var, VariableGroup) for var in (vars if vars is not None else [])):
  35. raise ValueError("Module vars must be a list of VariableGroup instances")
  36. @property
  37. def vars(self) -> List[VariableGroup]:
  38. """Backward compatibility property for accessing variable groups."""
  39. return self.variable_manager.variable_groups
  40. def get_variable_summary(self) -> Dict[str, Any]:
  41. """Get a summary of all variables managed by this module."""
  42. return self.variable_manager.get_summary()
  43. def add_variable_group(self, group: VariableGroup) -> None:
  44. """Add a new variable group to this module."""
  45. self.variable_manager.add_group(group)
  46. def has_variable(self, name: str) -> bool:
  47. """Check if this module has a variable with the given name."""
  48. return self.variable_manager.has_variable(name)
  49. def list(self):
  50. """List all templates in the module."""
  51. logger.debug(f"Listing templates for module: {self.name}")
  52. templates = self.libraries.find(self.name, self.files, sorted=True)
  53. logger.debug(f"Found {len(templates)} templates")
  54. for template in templates:
  55. print(f"{template.id} ({template.name}, {template.directory})")
  56. return templates
  57. def show(self, id: str = Argument(..., metavar="template", help="The template to show details for")):
  58. """Show details about a template"""
  59. logger.debug(f"Showing details for template: {id} in module: {self.name}")
  60. template = self.libraries.find_by_id(self.name, self.files, id)
  61. if template:
  62. logger.debug(f"Template found: {template.name}")
  63. print(f"ID: {template.id}")
  64. print(f"Name: {template.name}")
  65. print(f"Directory: {template.directory}")
  66. print(f"Content:\n{template.content}")
  67. else:
  68. logger.error(f"Template with ID '{id}' not found")
  69. print(f"Template with ID '{id}' not found.")
  70. def generate(self, id: str = Argument(..., metavar="template", help="The template to generate from"), out: Optional[Path] = Option(None, "--out", "-o", help="Output file to save the generated template")):
  71. """Generate a new template with complex variable prompting logic"""
  72. logger.info(f"Generating template '{id}' from module '{self.name}'")
  73. # Step 1: Find template by ID
  74. logger.debug(f"Step 1: Finding template by ID: {id}")
  75. template = self.libraries.find_by_id(self.name, self.files, id)
  76. if not template:
  77. logger.error(f"Template '{id}' not found")
  78. print(f"Template '{id}' not found.")
  79. return
  80. logger.debug(f"Template found: {template.name} with {len(template.vars)} variables")
  81. # Step 2: Validate if the variables in the template are valid ones
  82. logger.debug(f"Step 2: Validating template variables: {template.vars}")
  83. success, missing = self.variable_manager.validate_template_variables(template.vars)
  84. if not success:
  85. logger.error(f"Template '{id}' has invalid variables: {missing}")
  86. print(f"Template '{id}' has invalid variables: {missing}")
  87. return
  88. logger.debug("All template variables are valid")
  89. # Step 3: Disable variables not found in template
  90. logger.debug(f"Step 3: Disabling variables not used by template")
  91. self.variable_manager.disable_variables_not_in_template(template.vars)
  92. logger.debug("Unused variables disabled")
  93. # Step 4: Resolve variable defaults with priority (module -> template -> user config)
  94. logger.debug(f"Step 4: Resolving variable defaults with priority")
  95. resolved_defaults = self.variable_manager.resolve_variable_defaults(
  96. self.name,
  97. template.vars,
  98. template.var_defaults
  99. )
  100. logger.debug(f"Resolved defaults: {resolved_defaults}")
  101. # Step 5: Match template vars with vars of the module (only enabled ones)
  102. logger.debug(f"Step 5: Filtering variables for template")
  103. filtered_vars = self.variable_manager.filter_variables_for_template(template.vars)
  104. logger.debug(f"Filtered variables: {list(filtered_vars.keys())}")
  105. # Step 6: Execute complex group-based prompting logic
  106. logger.debug(f"Step 6: Starting complex prompting logic")
  107. try:
  108. prompt = PromptHandler(filtered_vars, resolved_defaults)
  109. final_variable_values = prompt()
  110. logger.debug(f"Prompting completed with values: {final_variable_values}")
  111. except KeyboardInterrupt:
  112. logger.info("Template generation cancelled by user")
  113. print("\n[red]Template generation cancelled.[/red]")
  114. return
  115. except Exception as e:
  116. logger.error(f"Error during prompting: {e}")
  117. print(f"Error during variable prompting: {e}")
  118. return
  119. # Step 7: Generate template with final variable values
  120. logger.debug(f"Step 7: Generating template with final values")
  121. try:
  122. generated_content = template.render(final_variable_values)
  123. logger.debug("Template rendered successfully")
  124. except Exception as e:
  125. logger.error(f"Error rendering template: {e}")
  126. print(f"Error rendering template: {e}")
  127. return
  128. # Step 8: Output the generated content
  129. logger.debug(f"Step 8: Outputting generated content")
  130. if out:
  131. try:
  132. out.parent.mkdir(parents=True, exist_ok=True)
  133. with open(out, 'w', encoding='utf-8') as f:
  134. f.write(generated_content)
  135. logger.info(f"Template generated and saved to {out}")
  136. print(f"✅ Template generated and saved to {out}")
  137. except Exception as e:
  138. logger.error(f"Error saving to file {out}: {e}")
  139. print(f"Error saving to file {out}: {e}")
  140. else:
  141. print("\n" + "="*60)
  142. print("📄 Generated Template Content:")
  143. print("="*60)
  144. print(generated_content)
  145. def register(self, app: Typer):
  146. self.app.command()(self.list)
  147. self.app.command()(self.show)
  148. self.app.command()(self.generate)
  149. app.add_typer(self.app, name=self.name, help=self.description, no_args_is_help=True)