Files

249 lines
8.3 KiB
Python

#!/usr/bin/env python3
"""
Script to create GitHub issues for LeagueLedger project.
"""
import requests
import os
import json
import sys
import re
from typing import Dict, List, Any
from dotenv import load_dotenv
from pathlib import Path
# Load environment variables from .env file
env_path = Path(__file__).parent / '.env'
load_dotenv(dotenv_path=env_path)
# GitHub repository information
GITHUB_API_URL = "https://api.github.com"
REPO_OWNER = os.getenv("REPO_OWNER")
REPO_NAME = os.getenv("REPO_NAME")
# GitHub authentication - use a personal access token
GITHUB_TOKEN = os.getenv("GITHUB_TOKEN")
def validate_env():
"""Validate that all required environment variables are set."""
missing_vars = []
if not GITHUB_TOKEN:
missing_vars.append("GITHUB_TOKEN")
if not REPO_OWNER:
missing_vars.append("REPO_OWNER")
if not REPO_NAME:
missing_vars.append("REPO_NAME")
if missing_vars:
print("Error: The following environment variables are missing in your .env file:")
for var in missing_vars:
print(f"- {var}")
print("\nPlease update your .env file in the scripts directory.")
return False
return True
def get_all_issues():
"""Get all open issues from the repository."""
url = f"{GITHUB_API_URL}/repos/{REPO_OWNER}/{REPO_NAME}/issues"
params = {
"state": "open",
"per_page": 100
}
headers = {
"Authorization": f"token {GITHUB_TOKEN}",
"Accept": "application/vnd.github.v3+json"
}
response = requests.get(url, params=params, headers=headers)
if response.status_code == 200:
return response.json()
else:
print(f"Failed to fetch issues: {response.status_code}")
print(response.text)
return []
def close_issue(issue_number):
"""Close an issue by its number."""
url = f"{GITHUB_API_URL}/repos/{REPO_OWNER}/{REPO_NAME}/issues/{issue_number}"
headers = {
"Authorization": f"token {GITHUB_TOKEN}",
"Accept": "application/vnd.github.v3+json"
}
data = {
"state": "closed"
}
response = requests.patch(url, json=data, headers=headers)
if response.status_code == 200:
print(f"Successfully closed issue #{issue_number}")
return True
else:
print(f"Failed to close issue #{issue_number}: {response.status_code}")
print(response.text)
return False
def delete_all_issues():
"""Close all open issues in the repository."""
issues = get_all_issues()
if not issues:
print("No open issues found.")
return
print(f"Found {len(issues)} open issues. Closing all...")
for issue in issues:
close_issue(issue["number"])
print("Finished closing issues.")
def create_github_issue(issue_data: Dict[str, Any]) -> Dict[str, Any]:
"""Create a GitHub issue using the GitHub API."""
url = f"{GITHUB_API_URL}/repos/{REPO_OWNER}/{REPO_NAME}/issues"
headers = {
"Authorization": f"token {GITHUB_TOKEN}",
"Accept": "application/vnd.github.v3+json"
}
response = requests.post(url, json=issue_data, headers=headers)
if response.status_code == 201:
print(f"Successfully created issue: {issue_data['title']}")
return response.json()
else:
print(f"Failed to create issue: {response.status_code}")
print(response.text)
return {}
def parse_todo_file(file_path):
"""Parse the TODO.md file and extract the issues."""
try:
with open(file_path, 'r', encoding='utf-8') as f:
content = f.read()
except Exception as e:
print(f"Error reading TODO.md file: {e}")
return []
# Split the content by sections (## headings)
sections = re.split(r'^## ', content, flags=re.MULTILINE)
# The first section is the title and intro, skip it
sections = sections[1:]
all_issues = []
for section in sections:
# Get the section title from the first line
title_line = section.split('\n', 1)[0].strip()
section_content = section.split('\n', 1)[1].strip() if len(section.split('\n', 1)) > 1 else ""
# Create a main issue for the section
section_issue = {
"title": title_line,
"body": f"## {title_line}\n\nThis issue tracks the progress of all {title_line.lower()} tasks.\n\n### Tasks\n{section_content}",
"labels": ["epic", title_line.lower().replace(' & ', '-').replace(' ', '-')]
}
all_issues.append(section_issue)
# Parse individual tasks in the section
tasks = parse_section_tasks(section_content, title_line)
all_issues.extend(tasks)
return all_issues
def parse_section_tasks(section_content, section_title):
"""Parse individual tasks from a section."""
issues = []
# Extract tasks at the top level (directly under the section)
tasks = re.findall(r'^- \[ \] (.+)$', section_content, re.MULTILINE)
for task in tasks:
# Skip tasks that are groups (they have subtasks and usually end with a colon)
if task.strip().endswith(':') or task.strip().startswith('**'):
continue
issue = {
"title": task.strip(),
"body": f"## Description\nImplement the following task from {section_title}:\n\n{task.strip()}\n\n## Related\nPart of the {section_title} epic.",
"labels": ["task", section_title.lower().replace(' & ', '-').replace(' ', '-')]
}
issues.append(issue)
# Extract grouped tasks (subtasks under headings like "User Management")
groups = re.findall(r'- \[ \] \*\*(.*?)\*\*\s*\n((?: - \[ \].*\n)*)', section_content, re.MULTILINE)
for group_name, group_tasks_text in groups:
# Create a group issue
group_issue = {
"title": f"{group_name}",
"body": f"## Description\nImplement {group_name} features for {section_title}.\n\n## Tasks\n{group_tasks_text}\n\n## Related\nPart of the {section_title} epic.",
"labels": ["group", section_title.lower().replace(' & ', '-').replace(' ', '-'), group_name.lower().replace(' ', '-')]
}
issues.append(group_issue)
# Add individual subtasks
subtasks = re.findall(r' - \[ \] (.*)', group_tasks_text)
for subtask in subtasks:
subtask_issue = {
"title": f"{group_name}: {subtask.strip()}",
"body": f"## Description\nImplement the following {group_name} feature:\n\n{subtask.strip()}\n\n## Related\nPart of the {group_name} group in the {section_title} epic.",
"labels": ["subtask", section_title.lower().replace(' & ', '-').replace(' ', '-'), group_name.lower().replace(' ', '-')]
}
issues.append(subtask_issue)
return issues
def main():
"""Main function to create all the issues."""
print("LeagueLedger GitHub Issue Creator")
print("=================================")
# Validate environment variables
if not validate_env():
sys.exit(1)
print(f"Repository: {REPO_OWNER}/{REPO_NAME}")
# Delete existing issues
delete_option = input("Do you want to close all existing open issues? (y/n): ").lower()
if delete_option == 'y':
delete_all_issues()
# Parse TODO.md and create issues
todo_path = Path(__file__).parent.parent / 'TODO.md'
if not todo_path.exists():
print(f"Error: TODO.md file not found at {todo_path}")
sys.exit(1)
print(f"Parsing TODO.md at {todo_path}...")
issues = parse_todo_file(todo_path)
print(f"Found {len(issues)} issues to create.")
create_option = input(f"Do you want to create {len(issues)} GitHub issues? (y/n): ").lower()
if create_option != 'y':
print("Operation cancelled.")
sys.exit(0)
# Create issues
created_issues = []
for issue in issues:
result = create_github_issue(issue)
if result:
created_issues.append(result)
# Show results
if created_issues:
print("\nSuccessfully created issues:")
for issue in created_issues:
print(f"- #{issue['number']} {issue['title']}: {issue['html_url']}")
print("\nCompleted creating GitHub issues.")
if __name__ == "__main__":
main()