|
| 1 | +name: Create Weekly Directory |
| 2 | + |
| 3 | +on: |
| 4 | + workflow_dispatch: |
| 5 | + inputs: |
| 6 | + issue_number: |
| 7 | + description: '이슈 번호 (예: 1, 2, 3)' |
| 8 | + required: true |
| 9 | + type: string |
| 10 | + |
| 11 | +jobs: |
| 12 | + create-directory: |
| 13 | + runs-on: ubuntu-latest |
| 14 | + permissions: |
| 15 | + contents: write |
| 16 | + issues: read |
| 17 | + |
| 18 | + steps: |
| 19 | + - name: Checkout repository |
| 20 | + uses: actions/checkout@v4 |
| 21 | + |
| 22 | + - name: Get issue info and create directory |
| 23 | + id: create |
| 24 | + uses: actions/github-script@v7 |
| 25 | + with: |
| 26 | + script: | |
| 27 | + const issueNumber = parseInt('${{ inputs.issue_number }}'); |
| 28 | +
|
| 29 | + // 이슈 정보 가져오기 |
| 30 | + const { data: issue } = await github.rest.issues.get({ |
| 31 | + owner: context.repo.owner, |
| 32 | + repo: context.repo.repo, |
| 33 | + issue_number: issueNumber |
| 34 | + }); |
| 35 | +
|
| 36 | + console.log(`📋 Issue title: ${issue.title}`); |
| 37 | + console.log(`📝 Issue body:\n${issue.body}`); |
| 38 | +
|
| 39 | + // 제목에서 주차 정보 파싱: [Week01] 2026.01.01 ~ 2026.01.07 주차 문제 |
| 40 | + const titleMatch = issue.title.match(/\[Week(\d+)\]\s*(\d{4}\.\d{2}\.\d{2})\s*~\s*(\d{4}\.\d{2}\.\d{2})/); |
| 41 | + if (!titleMatch) { |
| 42 | + core.setFailed('이슈 제목 형식이 올바르지 않습니다. [WeekXX] YYYY.MM.DD ~ YYYY.MM.DD 형식이어야 합니다.'); |
| 43 | + return; |
| 44 | + } |
| 45 | +
|
| 46 | + const weekNumber = titleMatch[1]; |
| 47 | + const startDate = titleMatch[2]; |
| 48 | + const endDate = titleMatch[3]; |
| 49 | +
|
| 50 | + console.log(`📅 Week: ${weekNumber}, ${startDate} ~ ${endDate}`); |
| 51 | +
|
| 52 | + // 이슈 본문에서 문제 정보 파싱 |
| 53 | + const body = issue.body || ''; |
| 54 | + const problems = []; |
| 55 | +
|
| 56 | + // 문제 섹션 파싱 (#### 문제 1, #### 문제 2, ...) |
| 57 | + const problemRegex = /####\s*문제\s*(\d+)[^\n]*\n([\s\S]*?)(?=####\s*문제|\n---|\n###|$)/g; |
| 58 | + let match; |
| 59 | +
|
| 60 | + while ((match = problemRegex.exec(body)) !== null) { |
| 61 | + const problemContent = match[2]; |
| 62 | +
|
| 63 | + const platform = problemContent.match(/\*\*플랫폼\*\*:\s*([^\n*]+)/)?.[1]?.trim() || ''; |
| 64 | + const problemNum = problemContent.match(/\*\*문제 번호\*\*:\s*([^\n*]+)/)?.[1]?.trim() || ''; |
| 65 | + const problemName = problemContent.match(/\*\*문제 이름\*\*:\s*([^\n*]+)/)?.[1]?.trim() || ''; |
| 66 | + const difficulty = problemContent.match(/\*\*난이도\*\*:\s*([^\n*]+)/)?.[1]?.trim() || ''; |
| 67 | + const link = problemContent.match(/\*\*링크\*\*:\s*([^\n*]+)/)?.[1]?.trim() || ''; |
| 68 | +
|
| 69 | + if (platform && problemNum && problemName) { |
| 70 | + problems.push({ |
| 71 | + platform, |
| 72 | + problemNum, |
| 73 | + problemName, |
| 74 | + difficulty, |
| 75 | + link |
| 76 | + }); |
| 77 | + } |
| 78 | + } |
| 79 | +
|
| 80 | + console.log(`🔍 Found ${problems.length} problems`); |
| 81 | + problems.forEach((p, i) => console.log(` ${i+1}. [${p.platform}] ${p.problemNum} - ${p.problemName}`)); |
| 82 | +
|
| 83 | + // 플랫폼 약어 변환 |
| 84 | + const getPlatformPrefix = (platform) => { |
| 85 | + const p = platform.toLowerCase(); |
| 86 | + if (p.includes('백준') || p.includes('boj') || p.includes('acmicpc')) return 'BOJ'; |
| 87 | + if (p.includes('프로그래머스') || p.includes('programmers')) return 'PGS'; |
| 88 | + if (p.includes('리트코드') || p.includes('leetcode')) return 'LTC'; |
| 89 | + return 'ETC'; |
| 90 | + }; |
| 91 | +
|
| 92 | + // README.md 내용 생성 |
| 93 | + let readmeContent = `# Week ${weekNumber} (${startDate} ~ ${endDate})\n\n`; |
| 94 | + readmeContent += `## 📝 이번 주 문제\n\n`; |
| 95 | +
|
| 96 | + const folderStructure = []; |
| 97 | +
|
| 98 | + problems.forEach((problem, index) => { |
| 99 | + const prefix = getPlatformPrefix(problem.platform); |
| 100 | + const folderName = `${prefix}_${problem.problemNum}_${problem.problemName.replace(/\s+/g, '')}`; |
| 101 | + folderStructure.push(folderName); |
| 102 | +
|
| 103 | + readmeContent += `### 문제 ${index + 1}: ${problem.problemName}\n`; |
| 104 | + readmeContent += `- **플랫폼**: ${problem.platform}\n`; |
| 105 | + readmeContent += `- **문제 번호**: ${problem.problemNum}\n`; |
| 106 | + readmeContent += `- **난이도**: ${problem.difficulty}\n`; |
| 107 | + readmeContent += `- **링크**: ${problem.link.startsWith('http') ? `[문제 링크](${problem.link})` : problem.link}\n`; |
| 108 | + readmeContent += `\n`; |
| 109 | + }); |
| 110 | +
|
| 111 | + readmeContent += `## 💡 폴더 구조\n\n`; |
| 112 | + readmeContent += '```\n'; |
| 113 | + readmeContent += `week${weekNumber}/\n`; |
| 114 | + folderStructure.forEach((folder, i) => { |
| 115 | + const prefix = i === folderStructure.length - 1 ? '└──' : '├──'; |
| 116 | + readmeContent += `${prefix} ${folder}/\n`; |
| 117 | + }); |
| 118 | + readmeContent += '```\n\n'; |
| 119 | +
|
| 120 | + readmeContent += `## ✅ 진행 현황\n\n`; |
| 121 | + readmeContent += `- [ ] @sukangpunch\n`; |
| 122 | + readmeContent += `- [ ] @Hexeong\n`; |
| 123 | + readmeContent += `- [ ] @whqtker\n`; |
| 124 | + readmeContent += `- [ ] @JAEHEE25\n`; |
| 125 | + readmeContent += `- [ ] @Gyuhyeok99\n`; |
| 126 | +
|
| 127 | + // 출력 설정 |
| 128 | + const fs = require('fs'); |
| 129 | + const path = `weekly/week${weekNumber}`; |
| 130 | +
|
| 131 | + core.setOutput('week_number', weekNumber); |
| 132 | + core.setOutput('directory_path', path); |
| 133 | + core.setOutput('readme_content', readmeContent); |
| 134 | +
|
| 135 | + // 파일 저장 (Node.js fs 사용) |
| 136 | + if (!fs.existsSync(path)) { |
| 137 | + fs.mkdirSync(path, { recursive: true }); |
| 138 | + } |
| 139 | + fs.writeFileSync(`${path}/README.md`, readmeContent); |
| 140 | +
|
| 141 | + console.log(`\n✅ Created ${path}/README.md`); |
| 142 | +
|
| 143 | + - name: Commit and push |
| 144 | + run: | |
| 145 | + git config --local user.email "action@github.com" |
| 146 | + git config --local user.name "GitHub Action" |
| 147 | + git add weekly/ |
| 148 | + git diff —staged —quiet || git commit -m "[Week${{ steps.create.outputs.week_number }}] 주차 디렉토리 및 README 생성 |
| 149 | +
|
| 150 | + 🤖 Generated with GitHub Actions" |
| 151 | + git push |
0 commit comments