__main__.py 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159
  1. #!/usr/bin/env python3
  2. """
  3. Main entry point for the Boilerplates CLI application.
  4. This file serves as the primary executable when running the CLI.
  5. """
  6. import importlib
  7. import logging
  8. import pkgutil
  9. import sys
  10. from pathlib import Path
  11. from typing import Optional
  12. from typer import Typer, Context, Option
  13. from rich.console import Console
  14. import cli.modules
  15. from cli.core.registry import registry
  16. # Using standard Python exceptions instead of custom ones
  17. app = Typer(no_args_is_help=True)
  18. console = Console()
  19. def setup_logging(log_level: str = "WARNING"):
  20. """Configure the logging system with the specified log level.
  21. Args:
  22. log_level: The logging level (DEBUG, INFO, WARNING, ERROR, CRITICAL)
  23. Raises:
  24. ValueError: If the log level is invalid
  25. RuntimeError: If logging configuration fails
  26. """
  27. numeric_level = getattr(logging, log_level.upper(), None)
  28. if not isinstance(numeric_level, int):
  29. raise ValueError(
  30. f"Invalid log level '{log_level}'. Valid levels: DEBUG, INFO, WARNING, ERROR, CRITICAL"
  31. )
  32. try:
  33. logging.basicConfig(
  34. level=numeric_level,
  35. format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
  36. datefmt='%Y-%m-%d %H:%M:%S'
  37. )
  38. logger = logging.getLogger('__name__')
  39. logger.setLevel(numeric_level)
  40. except Exception as e:
  41. raise RuntimeError(f"Failed to configure logging: {e}")
  42. @app.callback()
  43. def main(
  44. ctx: Context,
  45. log_level: Optional[str] = Option(
  46. "WARNING",
  47. "--log-level",
  48. help="Set the logging level (DEBUG, INFO, WARNING, ERROR, CRITICAL)"
  49. )
  50. ):
  51. """Main CLI application for managing boilerplates."""
  52. # Configure logging based on the provided log level
  53. setup_logging(log_level)
  54. # Store log level in context for potential use by other commands
  55. ctx.ensure_object(dict)
  56. ctx.obj['log_level'] = log_level
  57. def init_app():
  58. """Initialize the application by discovering and registering modules.
  59. Raises:
  60. ImportError: If critical module import operations fail
  61. RuntimeError: If application initialization fails
  62. """
  63. logger = logging.getLogger('boilerplates')
  64. failed_imports = []
  65. failed_registrations = []
  66. try:
  67. # Auto-discover and import all modules
  68. modules_path = Path(cli.modules.__file__).parent
  69. logger.debug(f"Discovering modules in {modules_path}")
  70. for finder, name, ispkg in pkgutil.iter_modules([str(modules_path)]):
  71. if not ispkg and not name.startswith('_') and name != 'base':
  72. try:
  73. logger.debug(f"Importing module: {name}")
  74. importlib.import_module(f"cli.modules.{name}")
  75. except ImportError as e:
  76. error_info = f"Import failed for '{name}': {str(e)}"
  77. failed_imports.append(error_info)
  78. logger.warning(error_info)
  79. except Exception as e:
  80. error_info = f"Unexpected error importing '{name}': {str(e)}"
  81. failed_imports.append(error_info)
  82. logger.error(error_info)
  83. # Register modules with app
  84. modules = registry.create_instances()
  85. logger.debug(f"Registering {len(modules)} discovered modules")
  86. for module in modules:
  87. try:
  88. logger.debug(f"Registering module: {module.__class__.__name__}")
  89. module.register_cli(app)
  90. except Exception as e:
  91. error_info = f"Registration failed for '{module.__class__.__name__}': {str(e)}"
  92. failed_registrations.append(error_info)
  93. # Log warning but don't raise exception for individual module failures
  94. logger.warning(error_info)
  95. console.print(f"[yellow]Warning:[/yellow] {error_info}")
  96. # If we have no modules registered at all, that's a critical error
  97. if not modules and not failed_imports:
  98. raise RuntimeError("No modules found to register")
  99. # Log summary
  100. successful_modules = len(modules) - len(failed_registrations)
  101. logger.info(f"Application initialized: {successful_modules} modules registered successfully")
  102. if failed_imports:
  103. logger.info(f"Module import failures: {len(failed_imports)}")
  104. if failed_registrations:
  105. logger.info(f"Module registration failures: {len(failed_registrations)}")
  106. except Exception as e:
  107. error_details = []
  108. if failed_imports:
  109. error_details.extend(["Import failures:"] + [f" - {err}" for err in failed_imports])
  110. if failed_registrations:
  111. error_details.extend(["Registration failures:"] + [f" - {err}" for err in failed_registrations])
  112. details = "\n".join(error_details) if error_details else str(e)
  113. raise RuntimeError(f"Application initialization failed: {details}")
  114. def run():
  115. """Run the CLI application."""
  116. try:
  117. init_app()
  118. app()
  119. except (ValueError, RuntimeError) as e:
  120. # Handle configuration and initialization errors cleanly
  121. console.print(f"[bold red]Error:[/bold red] {e}")
  122. sys.exit(1)
  123. except ImportError as e:
  124. # Handle module import errors with detailed info
  125. console.print(f"[bold red]Module Import Error:[/bold red] {e}")
  126. sys.exit(1)
  127. except KeyboardInterrupt:
  128. # Handle Ctrl+C gracefully
  129. console.print("\n[yellow]Operation cancelled by user[/yellow]")
  130. sys.exit(130)
  131. except Exception as e:
  132. # Handle unexpected errors - show simplified message
  133. console.print(f"[bold red]Unexpected error:[/bold red] {e}")
  134. console.print("[dim]Use --log-level DEBUG for more details[/dim]")
  135. sys.exit(1)
  136. if __name__ == "__main__":
  137. run()