command.py 7.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178
  1. """
  2. Base classes and utilities for CLI modules and commands.
  3. Provides common functionality and patterns for all modules.
  4. """
  5. import logging
  6. from abc import ABC
  7. from typing import Optional, Set
  8. import typer
  9. from rich.console import Console
  10. from .config import ConfigManager
  11. class BaseModule(ABC):
  12. """Abstract base class for all CLI modules with shared commands."""
  13. def __init__(self, name: str, icon: str = "", description: str = ""):
  14. self.name = name
  15. self.icon = icon
  16. self.description = description
  17. self.console = Console()
  18. self.logger = logging.getLogger(f"boilerplates.module.{name}")
  19. def get_valid_variables(self) -> Set[str]:
  20. """
  21. Get the set of valid variable names for this module.
  22. Subclasses can override this to provide module-specific validation.
  23. """
  24. return set()
  25. def get_app(self) -> typer.Typer:
  26. """
  27. Create and return the Typer app with shared commands.
  28. Subclasses can override this to add module-specific commands.
  29. """
  30. app = typer.Typer(
  31. name=self.name,
  32. help=f"{self.icon} {self.description}",
  33. rich_markup_mode="rich"
  34. )
  35. # Add shared config commands
  36. self._add_config_commands(app)
  37. # Add module-specific commands
  38. self._add_module_commands(app)
  39. return app
  40. def _add_config_commands(self, app: typer.Typer) -> None:
  41. """
  42. Add shared configuration commands to the app.
  43. These commands are available for all modules.
  44. """
  45. config_app = typer.Typer(name="config", help="Manage module configuration")
  46. app.add_typer(config_app, name="config")
  47. @config_app.command("set", help="Set a configuration value")
  48. def set_config(
  49. key: str = typer.Argument(..., help="Configuration key"),
  50. value: str = typer.Argument(..., help="Configuration value")
  51. ):
  52. """Set a configuration value for this module."""
  53. # Validate that the key is a valid variable for this module
  54. valid_vars = self.get_valid_variables()
  55. if valid_vars and key not in valid_vars:
  56. self.console.print(f"[red]✗[/red] Invalid config key '{key}'. Valid keys are: {', '.join(sorted(valid_vars))}")
  57. raise typer.Exit(code=1)
  58. config_manager = ConfigManager(self.name)
  59. try:
  60. # Try to parse as JSON for complex values
  61. import json
  62. try:
  63. parsed_value = json.loads(value)
  64. except json.JSONDecodeError:
  65. parsed_value = value
  66. config_manager.set(key, parsed_value)
  67. self.console.print(f"[green]✓[/green] Set {self.name} config '{key}' = {parsed_value}")
  68. except Exception as e:
  69. self.console.print(f"[red]✗[/red] Failed to set config: {e}")
  70. @config_app.command("get", help="Get a configuration value")
  71. def get_config(
  72. key: str = typer.Argument(..., help="Configuration key"),
  73. default: Optional[str] = typer.Option(None, "--default", "-d", help="Default value if key not found")
  74. ):
  75. """Get a configuration value for this module."""
  76. config_manager = ConfigManager(self.name)
  77. value = config_manager.get(key, default)
  78. if value is None:
  79. self.console.print(f"[yellow]⚠[/yellow] Config key '{key}' not found")
  80. return
  81. import json
  82. if isinstance(value, (dict, list)):
  83. self.console.print(json.dumps(value, indent=2))
  84. else:
  85. self.console.print(f"{key}: {value}")
  86. @config_app.command("list", help="List all configuration values")
  87. def list_config():
  88. """List all configuration values for this module."""
  89. config_manager = ConfigManager(self.name)
  90. config = config_manager.list_all()
  91. if not config:
  92. self.console.print(f"[yellow]No configuration found for {self.name}[/yellow]")
  93. return
  94. from rich.table import Table
  95. table = Table(title=f"⚙️ {self.name.title()} Configuration", title_style="bold blue")
  96. table.add_column("Key", style="cyan", no_wrap=True)
  97. table.add_column("Value", style="green")
  98. import json
  99. for key, value in config.items():
  100. if isinstance(value, (dict, list)):
  101. value_str = json.dumps(value, indent=2)
  102. else:
  103. value_str = str(value)
  104. table.add_row(key, value_str)
  105. self.console.print(table)
  106. @config_app.command("delete", help="Delete a configuration value")
  107. def delete_config(key: str = typer.Argument(..., help="Configuration key")):
  108. """Delete a configuration value for this module."""
  109. config_manager = ConfigManager(self.name)
  110. if config_manager.delete(key):
  111. self.console.print(f"[green]✓[/green] Deleted config key '{key}'")
  112. else:
  113. self.console.print(f"[yellow]⚠[/yellow] Config key '{key}' not found")
  114. @config_app.command("variables", help="List valid configuration variables for this module")
  115. def list_variables():
  116. """List all valid configuration variables for this module."""
  117. valid_vars = self.get_valid_variables()
  118. if not valid_vars:
  119. self.console.print(f"[yellow]No variables defined for {self.name} module yet.[/yellow]")
  120. return
  121. from rich.table import Table
  122. table = Table(title=f"🔧 Valid {self.name.title()} Variables", title_style="bold blue")
  123. table.add_column("Variable Name", style="cyan", no_wrap=True)
  124. table.add_column("Set", style="magenta")
  125. table.add_column("Type", style="green")
  126. table.add_column("Description", style="dim")
  127. # Get detailed variable information
  128. if hasattr(self, '_get_variable_details'):
  129. var_details = self._get_variable_details()
  130. for var_name in sorted(valid_vars):
  131. if var_name in var_details:
  132. detail = var_details[var_name]
  133. table.add_row(
  134. var_name,
  135. detail.get('set', 'unknown'),
  136. detail.get('type', 'str'),
  137. detail.get('display_name', '')
  138. )
  139. else:
  140. table.add_row(var_name, 'unknown', 'str', '')
  141. else:
  142. for var_name in sorted(valid_vars):
  143. table.add_row(var_name, 'unknown', 'str', '')
  144. self.console.print(table)
  145. def _add_module_commands(self, app: typer.Typer) -> None:
  146. """
  147. Override this method in subclasses to add module-specific commands.
  148. This is called after the shared commands are added.
  149. """
  150. pass