module.py 8.4 KB

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