| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216 |
- """Global configuration management for the boilerplate CLI."""
- from dataclasses import dataclass, field
- from pathlib import Path
- from typing import Any, Dict, List, Optional
- import logging
- import yaml
- from .exceptions import ConfigurationError
- logger = logging.getLogger('boilerplates')
- @dataclass
- class LibraryConfig:
- """Configuration for a single library."""
- name: str
- type: str # 'local' or 'git'
- path: Optional[str] = None # For local libraries
- repo: Optional[str] = None # For git libraries
- branch: str = "main" # For git libraries
- priority: int = 0 # Higher priority = checked first
- @dataclass
- class Config:
- """Global configuration management."""
-
- # Paths
- config_dir: Path = field(default_factory=lambda: Path.home() / ".boilerplates")
- cache_dir: Path = field(default_factory=lambda: Path.home() / ".boilerplates" / "cache")
-
- # Libraries
- libraries: List[LibraryConfig] = field(default_factory=list)
-
- # Application settings
- log_level: str = "INFO"
- default_editor: str = "vim"
- auto_update_remotes: bool = False
- template_validation: bool = True
-
- # UI settings
- use_rich_output: bool = True
- confirm_generation: bool = True
- show_summary: bool = True
-
- def __post_init__(self):
- """Ensure directories exist."""
- self.config_dir.mkdir(parents=True, exist_ok=True)
- self.cache_dir.mkdir(parents=True, exist_ok=True)
-
- @classmethod
- def load(cls, config_path=None):
- """Load configuration from file or use defaults.
-
- Args:
- config_path: Optional path to config file. If not provided,
- uses ~/.boilerplates/config.yaml
-
- Returns:
- Config instance with loaded or default values
- """
- if config_path is None:
- config_path = Path.home() / ".boilerplates" / "config.yaml"
-
- if config_path.exists():
- try:
- with open(config_path, 'r') as f:
- data = yaml.safe_load(f) or {}
-
- # Parse libraries if present
- libraries = []
- for lib_data in data.get('libraries', []):
- try:
- libraries.append(LibraryConfig(**lib_data))
- except TypeError as e:
- logger.warning(f"Invalid library configuration: {lib_data}, error: {e}")
-
- # Remove libraries from data to avoid duplicate in Config init
- if 'libraries' in data:
- del data['libraries']
-
- # Convert path strings to Path objects
- if 'config_dir' in data:
- data['config_dir'] = Path(data['config_dir'])
- if 'cache_dir' in data:
- data['cache_dir'] = Path(data['cache_dir'])
-
- config = cls(**data, libraries=libraries)
- logger.debug(f"Loaded configuration from {config_path}")
- return config
-
- except yaml.YAMLError as e:
- raise ConfigurationError("config.yaml", f"Invalid YAML format: {e}")
- except Exception as e:
- logger.warning(f"Failed to load config from {config_path}: {e}, using defaults")
- return cls()
- else:
- logger.debug(f"No config file found at {config_path}, using defaults")
- return cls()
-
- def save(self, config_path=None):
- """Save configuration to file.
-
- Args:
- config_path: Optional path to save config. If not provided,
- uses ~/.boilerplates/config.yaml
- """
- if config_path is None:
- config_path = self.config_dir / "config.yaml"
-
- data = {
- 'config_dir': str(self.config_dir),
- 'cache_dir': str(self.cache_dir),
- 'log_level': self.log_level,
- 'default_editor': self.default_editor,
- 'auto_update_remotes': self.auto_update_remotes,
- 'template_validation': self.template_validation,
- 'use_rich_output': self.use_rich_output,
- 'confirm_generation': self.confirm_generation,
- 'show_summary': self.show_summary,
- 'libraries': [
- {
- 'name': lib.name,
- 'type': lib.type,
- 'path': lib.path,
- 'repo': lib.repo,
- 'branch': lib.branch,
- 'priority': lib.priority
- }
- for lib in self.libraries
- ]
- }
-
- # Remove None values from library configs
- for lib in data['libraries']:
- lib = {k: v for k, v in lib.items() if v is not None}
-
- try:
- config_path.parent.mkdir(parents=True, exist_ok=True)
- with open(config_path, 'w') as f:
- yaml.safe_dump(data, f, default_flow_style=False, sort_keys=False)
- logger.debug(f"Saved configuration to {config_path}")
- except Exception as e:
- raise ConfigurationError("config.yaml", f"Failed to save: {e}")
-
- def add_library(self, library):
- """Add a library configuration.
-
- Args:
- library: LibraryConfig instance to add
- """
- # Check for duplicate names
- existing_names = {lib.name for lib in self.libraries}
- if library.name in existing_names:
- raise ConfigurationError(
- f"library:{library.name}",
- f"Library with name '{library.name}' already exists"
- )
-
- self.libraries.append(library)
- # Sort by priority (highest first)
- self.libraries.sort(key=lambda l: l.priority, reverse=True)
-
- def remove_library(self, name):
- """Remove a library configuration by name.
-
- Args:
- name: Name of the library to remove
-
- Returns:
- True if library was removed, False if not found
- """
- original_count = len(self.libraries)
- self.libraries = [lib for lib in self.libraries if lib.name != name]
- return len(self.libraries) < original_count
-
- def get_library(self, name):
- """Get a library configuration by name.
-
- Args:
- name: Name of the library
-
- Returns:
- LibraryConfig if found, None otherwise
- """
- for lib in self.libraries:
- if lib.name == name:
- return lib
- return None
- # Global configuration instance
- _config = None
- def get_config():
- """Get the global configuration instance.
-
- Returns:
- The global Config instance, loading it if necessary
- """
- global _config
- if _config is None:
- _config = Config.load()
- return _config
- def set_config(config):
- """Set the global configuration instance.
-
- Args:
- config: Config instance to use globally
- """
- global _config
- _config = config
|