249 lines
8.3 KiB
Python
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() |