""" Issue creation for Gitea API. """ import json import os import subprocess from subprocess import PIPE from typing import Dict, Any, Optional, List from .config import get_config from .exceptions import IssueError class IssueCreator: """Creates new issues via Gitea API.""" def __init__(self, config=None, auth_token=None): self.config = config or get_config() self.auth_token = auth_token or os.getenv('GITEA_API_TOKEN') def create_issue(self, title: str, body: str, **kwargs) -> Dict[str, Any]: """Create a new issue via POST operation. Args: title: Issue title (required) body: Issue description/body (required) **kwargs: Optional fields (assignees, milestone, labels, etc.) Returns: Dict containing created issue data including issue number Raises: IssueError: If creation fails """ if not self.auth_token: raise IssueError("Authentication token required for issue creation") if not title.strip(): raise IssueError("Issue title cannot be empty") # Prepare issue data issue_data = { 'title': title.strip(), 'body': body.strip() if body else '' } # Add optional fields if 'assignees' in kwargs and kwargs['assignees']: issue_data['assignees'] = kwargs['assignees'] if 'milestone' in kwargs and kwargs['milestone']: issue_data['milestone'] = kwargs['milestone'] if 'labels' in kwargs and kwargs['labels']: issue_data['labels'] = kwargs['labels'] url = self.config.issues_api_url try: # Prepare curl command with authentication curl_cmd = [ 'curl', '-s', '-X', 'POST', '-H', 'Content-Type: application/json', '-H', f'Authorization: token {self.auth_token}', '-d', json.dumps(issue_data), url ] result = subprocess.run( curl_cmd, stdout=PIPE, stderr=PIPE, universal_newlines=True, check=True ) if result.returncode != 0: raise IssueError(f"Failed to create issue: {result.stderr}") response_data = json.loads(result.stdout) # Check for API error responses if 'message' in response_data and 'number' not in response_data: raise IssueError(f"Failed to create issue: {response_data['message']}") return response_data except subprocess.CalledProcessError as e: raise IssueError(f"Failed to create issue: {e}") except json.JSONDecodeError as e: raise IssueError(f"Failed to parse response data: {e}") def create_enhancement_issue(self, title: str, use_case: str, technical_requirements: str = "", acceptance_criteria: List[str] = None, dependencies: List[str] = None, priority: str = "Medium") -> Dict[str, Any]: """Create an enhancement issue with structured format. Args: title: Issue title use_case: UseCase description technical_requirements: Technical implementation details acceptance_criteria: List of acceptance criteria dependencies: List of dependency descriptions priority: Priority level (High, Medium, Low) Returns: Dict containing created issue data """ # Build structured body body_parts = [f"UseCase: {use_case}"] if technical_requirements: body_parts.extend([ "", "Technical Requirements:", technical_requirements ]) if acceptance_criteria: body_parts.extend([ "", "Acceptance Criteria:" ]) for criterion in acceptance_criteria: body_parts.append(f"- [ ] {criterion}") if dependencies: body_parts.extend([ "", "Dependencies:" ]) for dep in dependencies: body_parts.append(f"- {dep}") body = "\n".join(body_parts) # Create with enhancement label return self.create_issue( title=title, body=body, labels=[priority.lower(), "enhancement"] ) def create_bug_issue(self, title: str, description: str, steps_to_reproduce: List[str] = None, expected_behavior: str = "", actual_behavior: str = "", environment: str = "") -> Dict[str, Any]: """Create a bug issue with structured format. Args: title: Bug title description: Bug description steps_to_reproduce: List of reproduction steps expected_behavior: What should happen actual_behavior: What actually happens environment: Environment details Returns: Dict containing created issue data """ body_parts = [description] if steps_to_reproduce: body_parts.extend([ "", "Steps to Reproduce:" ]) for i, step in enumerate(steps_to_reproduce, 1): body_parts.append(f"{i}. {step}") if expected_behavior: body_parts.extend([ "", f"Expected Behavior: {expected_behavior}" ]) if actual_behavior: body_parts.extend([ "", f"Actual Behavior: {actual_behavior}" ]) if environment: body_parts.extend([ "", f"Environment: {environment}" ]) body = "\n".join(body_parts) # Create with bug label return self.create_issue( title=title, body=body, labels=["bug"] ) def create_from_template(self, template_file: str, **template_vars) -> Dict[str, Any]: """Create issue from a template file. Args: template_file: Path to template file **template_vars: Variables to substitute in template Returns: Dict containing created issue data """ try: with open(template_file, 'r') as f: template_content = f.read() # Simple template variable substitution for key, value in template_vars.items(): template_content = template_content.replace(f"{{{key}}}", str(value)) # Extract title (first line) and body (rest) lines = template_content.strip().split('\n') if not lines or (len(lines) == 1 and not lines[0].strip()): raise IssueError("Template file is empty") title = lines[0].replace('Title: ', '').strip() body = '\n'.join(lines[1:]).strip() return self.create_issue(title=title, body=body) except FileNotFoundError: raise IssueError(f"Template file not found: {template_file}") except Exception as e: raise IssueError(f"Failed to process template: {e}")