""" Gitea-specific configuration management. """ import os import subprocess from pathlib import Path from dataclasses import dataclass from typing import Optional from urllib.parse import urlparse from .exceptions import GiteaConfigError def load_dotenv_file(env_file: Path) -> None: """Load environment variables from a .env file.""" if not env_file.exists(): return with open(env_file, 'r') as f: for line in f: line = line.strip() if line and not line.startswith('#') and '=' in line: key, value = line.split('=', 1) os.environ.setdefault(key.strip(), value.strip()) @dataclass class GiteaConfig: """Configuration for Gitea API access.""" # Repository settings (required) gitea_url: str = "" repo_owner: str = "" repo_name: str = "" # Authentication (optional for read operations) auth_token: Optional[str] = None @property def base_api_url(self) -> str: """Get the base API URL for this repository.""" return f"{self.gitea_url}/api/v1" @property def repo_api_url(self) -> str: """Get the repository API URL.""" return f"{self.base_api_url}/repos/{self.repo_owner}/{self.repo_name}" @property def issues_api_url(self) -> str: """Get the issues API URL.""" return f"{self.repo_api_url}/issues" @property def milestones_api_url(self) -> str: """Get the milestones API URL.""" return f"{self.repo_api_url}/milestones" @property def labels_api_url(self) -> str: """Get the labels API URL.""" return f"{self.repo_api_url}/labels" @classmethod def from_environment(cls, env_prefix: str = "GITEA") -> "GiteaConfig": """Create config from environment variables. Args: env_prefix: Environment variable prefix (default: GITEA) Looks for {prefix}_URL, {prefix}_REPO_OWNER, etc. """ # Auto-load .env.gitea file if it exists env_file = Path(".env.gitea") load_dotenv_file(env_file) config = cls() # Load from environment config.gitea_url = os.getenv(f"{env_prefix}_URL", "") config.repo_owner = os.getenv(f"{env_prefix}_REPO_OWNER", "") config.repo_name = os.getenv(f"{env_prefix}_REPO_NAME", "") config.auth_token = os.getenv(f"{env_prefix}_API_TOKEN") return config @classmethod def from_git_repository(cls) -> "GiteaConfig": """Create config by auto-detecting from current git repository. Only requires GITEA_API_TOKEN environment variable. All other settings are detected from git remote origin. """ try: # Get git remote origin URL result = subprocess.run( ['git', 'remote', 'get-url', 'origin'], capture_output=True, text=True, check=True ) origin_url = result.stdout.strip() # Parse different URL formats if origin_url.startswith('http://') or origin_url.startswith('https://'): # HTTP(S) format: https://gitea.example.com/owner/repo.git parsed = urlparse(origin_url) gitea_url = f"{parsed.scheme}://{parsed.netloc}" path_parts = parsed.path.strip('/').split('/') if len(path_parts) >= 2: repo_owner = path_parts[0] repo_name = path_parts[1].replace('.git', '') else: raise GiteaConfigError(f"Cannot parse repository path from URL: {origin_url}") elif '@' in origin_url: # SSH format: git@gitea.example.com:owner/repo.git if ':' in origin_url: host_part, path_part = origin_url.split(':', 1) host = host_part.split('@')[-1] gitea_url = f"https://{host}" # Assume HTTPS for API path_parts = path_part.strip('/').split('/') if len(path_parts) >= 2: repo_owner = path_parts[0] repo_name = path_parts[1].replace('.git', '') else: raise GiteaConfigError(f"Cannot parse repository path from SSH URL: {origin_url}") else: raise GiteaConfigError(f"Cannot parse SSH URL format: {origin_url}") else: raise GiteaConfigError(f"Unsupported git remote URL format: {origin_url}") # Get auth token from environment auth_token = os.getenv('GITEA_API_TOKEN') return cls( gitea_url=gitea_url, repo_owner=repo_owner, repo_name=repo_name, auth_token=auth_token ) except subprocess.CalledProcessError as e: raise GiteaConfigError(f"Failed to get git remote origin: {e}") except Exception as e: raise GiteaConfigError(f"Failed to auto-detect git repository config: {e}") @classmethod def from_tddai_config(cls, tddai_config) -> "GiteaConfig": """Create GiteaConfig from legacy TddaiConfig for backwards compatibility.""" return cls( gitea_url=tddai_config.gitea_url, repo_owner=tddai_config.repo_owner, repo_name=tddai_config.repo_name, auth_token=os.getenv('GITEA_API_TOKEN') ) def validate(self) -> None: """Validate configuration settings.""" if not self.gitea_url: raise GiteaConfigError("gitea_url cannot be empty") if not self.repo_owner: raise GiteaConfigError("repo_owner cannot be empty") if not self.repo_name: raise GiteaConfigError("repo_name cannot be empty") # Validate URL format if not (self.gitea_url.startswith('http://') or self.gitea_url.startswith('https://')): raise GiteaConfigError("gitea_url must start with http:// or https://") def requires_auth(self, operation: str = "read") -> bool: """Check if operation requires authentication.""" write_operations = {"create", "update", "delete", "write"} return operation in write_operations and not self.auth_token