diff --git a/.github/workflows/update_challenge_progress.yml b/.github/workflows/update_challenge_progress.yml new file mode 100644 index 00000000..f0db25ea --- /dev/null +++ b/.github/workflows/update_challenge_progress.yml @@ -0,0 +1,59 @@ +name: Update Challenge Progress + +on: + pull_request: + types: [opened, edited, reopened] + +jobs: + update_progress: + runs-on: ubuntu-latest + steps: + - name: Checkout Repository + uses: actions/checkout@v3 + with: + ref: ${{ github.head_ref }} + fetch-depth: 0 + + - name: Set up Python + uses: actions/setup-python@v4 + with: + python-version: '3.x' + + - name: Run extract_pr_data.py + working-directory: _MonthlyChallenges + run: python extract_pr_data.py + + - name: Run update_scoreboard.py + working-directory: _MonthlyChallenges + run: python update_scoreboard.py + + - name: Run update_dashboard.py + working-directory: _MonthlyChallenges + run: python update_dashboard.py + + - name: Commit updated files + working-directory: _MonthlyChallenges + run: | + git config --global user.name "${{ secrets.GIT_USER_NAME }}" + git config --global user.email "${{ secrets.GIT_USER_EMAIL }}" + git add scoreboard.json DASHBOARD.md HISTORY.md + git commit -m "Update challenge progress dashboard" || echo "No changes to commit" + git push origin ${{ github.head_ref }} + + - name: Post PR Comment with progress + if: github.event_name == 'pull_request' + uses: actions/github-script@v6 + with: + script: | + const fs = require('fs'); + // DASHBOARD.md 파일에서 내용 읽어오기 (working-directory에 따라 경로 조정) + const dashboard = fs.readFileSync('_MonthlyChallenges/DASHBOARD.md', 'utf8'); + // PR 이벤트에서 PR 번호 가져오기 + const prNumber = context.payload.pull_request.number; + // GitHub REST API를 통해 코멘트 생성 + github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: prNumber, + body: dashboard + }); diff --git a/_MonthlyChallenges/DASHBOARD.md b/_MonthlyChallenges/DASHBOARD.md new file mode 100644 index 00000000..e69de29b diff --git a/_MonthlyChallenges/HISTORY.md b/_MonthlyChallenges/HISTORY.md new file mode 100644 index 00000000..e69de29b diff --git a/_MonthlyChallenges/extract_pr_data.py b/_MonthlyChallenges/extract_pr_data.py new file mode 100644 index 00000000..30392876 --- /dev/null +++ b/_MonthlyChallenges/extract_pr_data.py @@ -0,0 +1,68 @@ +import os +import json +import re + + +def main(): + # 1. GitHub 이벤트 페이로드 로드 + event_path = os.getenv('GITHUB_EVENT_PATH') + print("event_path", event_path) + if not event_path: + print("GITHUB_EVENT_PATH 환경변수가 설정되어 있지 않습니다.") + exit(1) + + with open(event_path, 'r', encoding='utf-8') as f: + event_data = json.load(f) + print(f"event_data: {event_data}") + + pr_body = event_data['pull_request']['body'] + pr_number = event_data['pull_request']['number'] + username = event_data['pull_request']['user']['login'] + print(f"pr_body: {pr_body}, pr_number: {pr_number}") + + if pr_number is None: + print("PR 번호를 찾을 수 없습니다.") + exit(1) + + # 2. PR 본문을 줄 단위로 분할 + lines = pr_body.splitlines() + print(f"lines: {lines}") + + # 3. "푼 문제" 섹션 탐지 및 문제 항목 파싱 + in_problem_section = False + problem_entries = [] + + # 정규표현식 패턴: + # - [백준 #2169. 로봇 조종하기](https://www.acmicpc.net/problem/2169): DP / 골드2 + pattern = r'- \[(?P[^\]]+?) #(?P\d+)\.\s+(?P.*?)\]\((?P<link>.*?)\):\s*(?P<algorithm>[^/]+)\s*/\s*(?P<difficulty>.+)' + + for line in lines: + # "푼 문제" 키워드를 찾으면 해당 섹션 시작으로 설정 + if "푼 문제" in line: + in_problem_section = True + continue + + if in_problem_section: + # bullet list 항목만 처리 + if line.startswith('- '): + match = re.match(pattern, line) + print(f"match: {match}") + if match: + entry = match.groupdict() + print(f"entry: {entry}") + # 문제 번호를 정수로 변환 + entry['problem_id'] = int(entry.pop("id")) + entry['pr_number'] = pr_number + entry['username'] = username + # 공백 제거 + entry['algorithm'] = entry['algorithm'].strip() + entry['difficulty'] = entry['difficulty'].strip() + problem_entries.append(entry) + print(f"problem_entries: {problem_entries}") + + # 4. 추출된 데이터를 pr_data.json 파일에 저장 + with open("pr_data.json","w", encoding='utf-8') as f: + json.dump(problem_entries, f, ensure_ascii=False, indent=2) + +if __name__ == '__main__': + main() diff --git a/_MonthlyChallenges/update_dashboard.py b/_MonthlyChallenges/update_dashboard.py new file mode 100644 index 00000000..ba2b2dab --- /dev/null +++ b/_MonthlyChallenges/update_dashboard.py @@ -0,0 +1,100 @@ +import os +import json +from datetime import datetime + +SCOREBOARD_FILE = "scoreboard.json" +DASHBOARD_FILE = "DASHBOARD.md" +HISTORY_FILE = "HISTORY.md" + +CHALLENGE_TYPES = { + "그래프": 5, + "DP": 5 +} + +def generate_dashboard(scoreboard): + # scoreboard 구조 예시 (새로운 형식): + # { + # "month": "2023-04", + # "users": { + # "user1": { + # "그래프": [101, 102], + # "DP": [2169, 1520], + # "achieved": { + # "그래프": true, + # "DP": false + # } + # }, + # "user2": { ... } + # } + # } + month = scoreboard.get("month", "Unknown") + users = scoreboard.get("users", {}) + + md = f"# {month} 챌린지 진행 상황\n\n" + md += "| 사용자 | 챌린지 유형 | 문제 수 | 달성 여부 |\n" + md += "| ------ | ----------- | ------- | --------- |\n" + + # 각 사용자별 진행 상황 표 작성 + for user, data in users.items(): + for ctype in CHALLENGE_TYPES.keys(): + count = len(data.get(ctype, [])) + achieved = data.get("achieved", {}).get(ctype, False) + achieved_str = "✅" if achieved else "❌" + md += f"| {user} | {ctype} | {count} | {achieved_str} |\n" + + return md + +def archive_current_month(scoreboard): + # HISTORY_FILE에 현재 달 기록을 추가 (append 방식) + dashboard_md = generate_dashboard(scoreboard) + with open(HISTORY_FILE, "a", encoding="utf-8") as f: + f.write(dashboard_md) + f.write("\n\n") # 구분을 위한 빈 줄 추가 + print("HISTORY.md 업데이트 완료!") + +def update_dashboard(): + # 1. scoreboard.json 로드 + if not os.path.exists(SCOREBOARD_FILE): + print(f"{SCOREBOARD_FILE} 파일이 없습니다.") + return + + with open(SCOREBOARD_FILE, "r", encoding="utf-8") as f: + scoreboard = json.load(f) + + # 2. 기존 파일 구조가 새 형식("month", "users")이 아니라면 변환 + if "month" not in scoreboard or "users" not in scoreboard: + # 기존 구조는 사용자 이름이 최상위 키인 형태 + scoreboard = { + "month": datetime.now().strftime("%Y-%m"), + "users": scoreboard + } + # 새 형식으로 저장 + with open(SCOREBOARD_FILE, "w", encoding="utf-8") as f: + json.dump(scoreboard, f, ensure_ascii=False, indent=2) + print("기존 scoreboard 형식을 새 구조로 변환하였습니다.") + + # 3. 현재 달 확인 및 월 초기화 처리 + current_month = datetime.now().strftime("%Y-%m") + stored_month = scoreboard.get("month", current_month) + print(f"현재 달: {current_month}, 저장된 달: {stored_month}") + + if stored_month != current_month: + print(f"새로운 달({current_month})로 넘어감 - 이전 달({stored_month}) 기록을 히스토리에 저장합니다.") + archive_current_month(scoreboard) + # scoreboard 초기화: users는 빈 dict, month는 현재 달로 갱신 + scoreboard = { + "month": current_month, + "users": {} + } + with open(SCOREBOARD_FILE, "w", encoding="utf-8") as f: + json.dump(scoreboard, f, ensure_ascii=False, indent=2) + + # 4. DASHBOARD.md 파일 생성 및 업데이트 + md_content = generate_dashboard(scoreboard) + with open(DASHBOARD_FILE, "w", encoding="utf-8") as f: + f.write(md_content) + + print("DASHBOARD.md 업데이트 완료!") + +if __name__ == '__main__': + update_dashboard() diff --git a/_MonthlyChallenges/update_scoreboard.py b/_MonthlyChallenges/update_scoreboard.py new file mode 100644 index 00000000..49bfe119 --- /dev/null +++ b/_MonthlyChallenges/update_scoreboard.py @@ -0,0 +1,94 @@ +import os +import json +from datetime import datetime + +SCOREBOARD_FILE = "scoreboard.json" +PR_DATA_FILE = "pr_data.json" + +# 챌린지 설정 (매달 투표 결과로 정해진 유형과 목표 문제 수) +CHALLENGE_TYPES = { + "그래프": 5, + "DP": 5 +} + + +def initialize_user(): + # 사용자별 초기 스코어보드 구조 + return { + **{ctype: [] for ctype in CHALLENGE_TYPES.keys()}, + "achieved": {ctype: False for ctype in CHALLENGE_TYPES.keys()} + } + + +def main(): + # 1. 기존 스코어보드 로드 (없으면 빈 dict로 초기화) + if os.path.exists(SCOREBOARD_FILE): + with open(SCOREBOARD_FILE, 'r', encoding='utf-8') as f: + try: + scoreboard = json.load(f) + except json.JSONDecodeError: + scoreboard = {} + else: + scoreboard = {} + + print(f"scorebard: {scoreboard}") + + # 2. 새 구조("month", "users")가 없다면 변환 + if "users" not in scoreboard or "month" not in scoreboard: + scoreboard = { + "month": datetime.now().strftime("%Y-%m"), + "users": scoreboard # 기존 scoreboard의 내용(사용자 데이터)이 있다면 여기에 넣음 + } + + users = scoreboard["users"] + + # 3. pr_data.json 파일 로드 + if not os.path.exists(PR_DATA_FILE): + print(f"{PR_DATA_FILE} 파일이 존재하지 않습니다.") + exit(1) + + with open(PR_DATA_FILE, 'r', encoding='utf-8') as f: + pr_data = json.load(f) + + print(f"pr_data: {pr_data}") + + # 4. pr_data의 각 항목을 순회하며 사용자별 스코어보드 업데이트 + for entry in pr_data: + username = entry["username"] + algorithm = entry["algorithm"] + problem_id = entry["problem_id"] + + if not username or not algorithm or problem_id is None: + continue + + print(f"username: {username}, algorithm: {algorithm}, problem_id: {problem_id}") + + # 챌린지 유형에 포함되어 있는지 확인 (예: "그래프", "DP") + if algorithm not in CHALLENGE_TYPES: + continue # 챌린지 대상이 아니면 무시 + + # 사용자의 기록이 없으면 초기화 + if username not in users: + scoreboard[username] = initialize_user() + + # 해당 유형 문제 번호를 중복 없이 추가 + if problem_id not in users[username].get(algorithm, []): + users[username][algorithm].append(problem_id) + + print(f"users: {users}") + + # 5. 각 사용자별로 달성 여부 업데이트 + for username, data in users.items(): + for ctype, goal in CHALLENGE_TYPES.items(): + count = len(data.get(ctype, [])) + # 목표 수 이상이면 달성 처리 + data["achieved"][ctype] = (count >= goal) + + # 5. 스코어보드 저장 + with open(SCOREBOARD_FILE, 'w', encoding='utf-8') as f: + json.dump(scoreboard, f, ensure_ascii=False, indent=2) + + print("scoreboard.json 업데이트 완료!") + +if __name__ == '__main__': + main()