xcad 9 mesi fa
parent
commit
9a5e808618
4 ha cambiato i file con 68 aggiunte e 88 eliminazioni
  1. 27 30
      cli/__main__.py
  2. 3 0
      cli/core/library.py
  3. 36 55
      cli/core/module.py
  4. 2 3
      cli/core/template.py

+ 27 - 30
cli/__main__.py

@@ -4,69 +4,66 @@ Main entry point for the Boilerplates CLI application.
 This file serves as the primary executable when running the CLI.
 """
 import importlib
-import logging
 import pkgutil
 import sys
 from pathlib import Path
-from typer import Typer, Option, Context
+from typer import Typer, Context
+from rich.console import Console
 import cli.modules
 from cli.core.registry import registry
+from cli.core.exceptions import BoilerplateError
 
 app = Typer(no_args_is_help=True)
-
-# Set up logging
-logging.basicConfig(
-    level=logging.CRITICAL,
-    format='[%(levelname)s] %(message)s',
-    stream=sys.stdout
-)
-logger = logging.getLogger('boilerplates')
+console = Console()
 
 @app.callback()
-def main(
-    ctx: Context,
-    debug: bool = Option(False, "--debug", help="Enable debug logging")
-):
+def main(ctx: Context):
   """Main CLI application for managing boilerplates."""
-  # Enable debug logging if requested
-  if debug:
-    logging.getLogger('boilerplates').setLevel(logging.DEBUG)
-    logger.debug("Debug logging enabled")
-  
-  logger.debug("Starting boilerplates CLI application")
+  pass
 
 def init_app():
   """Initialize the application by discovering and registering modules."""
   try:
     # Auto-discover and import all modules
     modules_path = Path(cli.modules.__file__).parent
-    logger.debug(f"Discovering modules in: {modules_path}")
     
     for finder, name, ispkg in pkgutil.iter_modules([str(modules_path)]):
       if not ispkg and not name.startswith('_') and name != 'base':
         try:
-          logger.debug(f"Importing module: {name}")
           importlib.import_module(f"cli.modules.{name}")
         except ImportError as e:
-          logger.warning(f"Could not import {name}: {e}")
+          # Silently skip modules that can't be imported
+          pass
     
     # Register modules with app
-    logger.debug(f"Registering {len(registry.create_instances())} modules")
     for module in registry.create_instances():
       try:
-        logger.debug(f"Registering module: {module.__class__.__name__}")
         module.register_cli(app)
       except Exception as e:
-        logger.error(f"Error registering {module.__class__.__name__}: {e}")
+        console.print(f"[yellow]Warning:[/yellow] Error registering {module.__class__.__name__}: {e}")
     
   except Exception as e:
-    logger.error(f"Application initialization error: {e}")
-    exit(1)
+    console.print(f"[bold red]Application initialization error:[/bold red] {e}")
+    sys.exit(1)
 
 def run():
   """Run the CLI application."""
-  init_app()
-  app()
+  try:
+    init_app()
+    app()
+  except BoilerplateError as e:
+    # Handle our custom exceptions cleanly without stack trace
+    console.print(f"[bold red]Error:[/bold red] {e}")
+    sys.exit(1)
+  except KeyboardInterrupt:
+    # Handle Ctrl+C gracefully
+    console.print("\n[yellow]Operation cancelled by user[/yellow]")
+    sys.exit(130)
+  except Exception as e:
+    # Handle unexpected errors - show simplified message
+    console.print(f"[bold red]Unexpected error:[/bold red] {e}")
+    console.print("[dim]Run with --debug for more details[/dim]")
+    sys.exit(1)
 
 if __name__ == "__main__":
   run()

+ 3 - 0
cli/core/library.py

@@ -52,6 +52,9 @@ class Library:
         if file_path.is_file():
           # Create Template object using the new class method
           template = Template.from_file(file_path)
+          # Set module context if not already specified in frontmatter
+          if not template.module:
+            template.module = module_name
           templates.append(template)
 
     if sorted:

+ 36 - 55
cli/core/module.py

@@ -58,12 +58,8 @@ class Module(ABC):
 
   def show(self, id: str = Argument(..., help="Template ID")):
     """Show template details."""
-    logger.debug(f"Showing template: {id}")
-    
     template = self._get_template(id)
-    if not template:
-      return
-    
+
     # Header
     version = f" v{template.version}" if template.version else ""
     console.print(f"[bold magenta]{template.name} ({template.id}{version})[/bold magenta]")
@@ -86,18 +82,15 @@ class Module(ABC):
     
     # Content
     if template.content:
-      console.print(f"\n{template.content}")
-  
-  def _get_template(self, template_id: str, raise_on_missing: bool = False):
+      print(f"\n{template.content}")
+
+  def _get_template(self, template_id: str):
     """Get template by ID with unified error handling."""
     template = self.libraries.find_by_id(self.name, self.files, template_id)
     
     if not template:
-      logger.error(f"Template '{template_id}' not found")
-      if raise_on_missing:
-        raise TemplateNotFoundError(template_id, self.name)
-      console.print(f"[red]Template '{template_id}' not found[/red]")
-    
+      raise TemplateNotFoundError(template_id, self.name)
+
     return template
 
   def generate(
@@ -106,17 +99,19 @@ class Module(ABC):
     out: Optional[Path] = Option(None, "--out", "-o")
   ):
     """Generate from template."""
-    logger.debug(f"Generating template: {id}")
-    
-    template = self._get_template(id, raise_on_missing=True)
+    template = self._get_template(id)
     
     # Validate template (will raise TemplateValidationError if validation fails)
     self._validate_template(template, id)
     
-    print("TEST")
+    print("TEST SUCCESSFUL")
   
   def _validate_template(self, template, template_id: str) -> None:
     """Validate template and raise error if validation fails."""
+    from .exceptions import TemplateValidationError
+    
+    validation_errors = []
+    
     # Update template variables with module variable registry
     module_variable_registry = getattr(self, 'variables', None)
     if module_variable_registry:
@@ -125,47 +120,33 @@ class Module(ABC):
       # Validate parent-child relationships in the registry
       registry_errors = module_variable_registry.validate_parent_child_relationships()
       if registry_errors:
-        logger.error(f"Variable registry validation errors: {registry_errors}")
+        validation_errors.extend(registry_errors)
+      
+      # Validate that all template variables are either registered or in template_vars
+      unregistered_vars = []
+      for var_name in template.vars:
+        # Check if variable is registered in module
+        if not module_variable_registry.get_variable(var_name):
+          # Check if it's defined in template's frontmatter variable_metadata (template_vars)
+          if var_name not in template.variable_metadata:
+            unregistered_vars.append(var_name)
+      
+      if unregistered_vars:
+        validation_errors.append(
+          f"Unregistered variables found: {', '.join(unregistered_vars)}. "
+          f"Variables must be either registered in the module or defined in template frontmatter 'variables' section."
+        )
     
-    # Validate template with module variables
-    warnings = template.validate(module_variable_registry)
+    # Validate template syntax and structure
+    template_warnings = template.validate(module_variable_registry)
+    
+    # If there are validation errors, fail with TemplateValidationError
+    if validation_errors:
+      raise TemplateValidationError(template_id, validation_errors)
     
     # If there are non-critical warnings, log them but don't fail
-    if warnings:
-      logger.warning(f"Template '{template_id}' has validation warnings: {warnings}")
-  
-  def _apply_metadata_to_variables(self, variables: Dict[str, Any], template_metadata: Dict[str, Any]):
-    """Apply metadata from module and template to variables."""
-    # First apply module metadata
-    module_var_metadata = self.metadata.get('variables', {})
-    for var_name, var in variables.items():
-      if var_name in module_var_metadata:
-        meta = module_var_metadata[var_name]
-        if 'hint' in meta and not var.hint:
-          var.hint = meta['hint']
-        if 'description' in meta and not var.description:
-          var.description = meta['description']
-        if 'tip' in meta and not var.tip:
-          var.tip = meta['tip']
-        if 'validation' in meta and not var.validation:
-          var.validation = meta['validation']
-        if 'icon' in meta and not var.icon:
-          var.icon = meta['icon']
-    
-    # Then apply template metadata (overrides module metadata)
-    for var_name, var in variables.items():
-      if var_name in template_metadata:
-        meta = template_metadata[var_name]
-        if 'hint' in meta:
-          var.hint = meta['hint']
-        if 'description' in meta:
-          var.description = meta['description']
-        if 'tip' in meta:
-          var.tip = meta['tip']
-        if 'validation' in meta:
-          var.validation = meta['validation']
-        if 'icon' in meta:
-          var.icon = meta['icon']
+    if template_warnings:
+      logger.warning(f"Template '{template_id}' has validation warnings: {template_warnings}")
   
   def register_cli(self, app: Typer):
     """Register module commands with the main app."""

+ 2 - 3
cli/core/template.py

@@ -1,5 +1,5 @@
 from pathlib import Path
-from typing import Any, Dict, List, Set, Tuple
+from typing import Any, Dict, List, Set, Tuple, Optional
 from dataclasses import dataclass, field
 import logging
 import re
@@ -37,7 +37,6 @@ class Template:
   # Template variable analysis results
   vars: Set[str] = field(default_factory=set, init=False)
   var_defaults: Dict[str, Any] = field(default_factory=dict, init=False)
-  
   def __post_init__(self):
     """Initialize computed properties after dataclass initialization."""
     # Set default name if not provided
@@ -180,7 +179,7 @@ class Template:
     # The template parser will have found all variables used
     
     # Check for missing required frontmatter fields
-    if not self.name or self.name == self.file_path.parent.name:
+    if not self.name:
       errors.append("Missing 'name' in frontmatter")
     
     if not self.description or self.description == 'No description available':