// WHY: sections are identified by emoji header; order defines generated layout const SECTIONS = [ { key: 'completed', header: /^## ✅/, emoji: '✅', hasNote: false }, { key: 'todo', header: /^## 🚀/, emoji: '⏳', hasNote: true }, { key: 'inProgress', header: /^## 🔄/, emoji: '🔄', hasNote: true }, { key: 'backlog', header: /^## 📋/, emoji: '📋', hasNote: false } ]; const SECTION_LABEL = { completed: 'Befejezett', todo: 'TODO', inProgress: 'In Progress', backlog: 'Backlog' }; function parseTodoFile(content) { const sections = { completed: { header: null, rows: [] }, todo: { header: null, rows: [] }, inProgress: { header: null, rows: [] }, backlog: { header: null, rows: [] } }; const rows = []; let current = null; for (const line of content.split('\n')) { const sec = SECTIONS.find(s => s.header.test(line)); if (sec) { current = sec.key; if (!sections[current].header) sections[current].header = line; continue; } if (!current || !line.trim().startsWith('|')) continue; const cols = line.split('|').map(c => c.trim()).filter(c => c !== ''); if (cols.length < 3) continue; if (cols[0] === 'Plane' || /^[-]+$/.test(cols[0])) continue; const row = { id: cols[0], name: cols[1], status: cols[2], note: cols[3] || '' }; sections[current].rows.push(row); rows.push({ section: current, ...row }); } return { sections, rows }; } function seqNum(id) { const match = id.match(/(\d+)$/); return match ? parseInt(match[1], 10) : 0; } function buildTable(sec, rows) { const header = sec.hasNote ? '| Plane | Feladat | Státusz | Megjegyzés |' : '| Plane | Feladat | Státusz |'; const separator = sec.hasNote ? '|-------|---------|---------|-----------|' : '|-------|---------|---------|'; const body = rows .slice() .sort((a, b) => seqNum(a.id) - seqNum(b.id)) .map(row => { if (sec.hasNote) return `| ${row.id} | ${row.name} | ${sec.emoji} | ${row.note || ''} |`; return `| ${row.id} | ${row.name} | ${sec.emoji} |`; }); return [header, separator, ...body].join('\n'); } // WHY: only the section region is regenerated, prefix/suffix (overview, commands) are preserved. // Prefix/suffix are trimmed and rejoined with normalized separators so that a previous run // that dropped the blank line after `---` cannot corrupt the section headers (`---## ✅`). function regenerate(content, finalSections) { const lines = content.split('\n'); const firstIdx = lines.findIndex(l => SECTIONS[0].header.test(l)); const backlogIdx = lines.findIndex(l => SECTIONS[3].header.test(l)); if (firstIdx === -1 || backlogIdx === -1) throw new Error('TODO.md section headers not found'); let endIdx = lines.length; for (let i = backlogIdx + 1; i < lines.length; i++) { if (lines[i].trim() === '---') { endIdx = i; break; } } const prefix = lines.slice(0, firstIdx).join('\n').trimEnd(); const suffix = lines.slice(endIdx + 1).join('\n').trimStart(); const headerOf = key => { const sec = SECTIONS.find(s => s.key === key); const idx = lines.findIndex(l => sec.header.test(l)); return idx >= 0 ? lines[idx].replace(/^## /, '') : key; }; const blocks = SECTIONS.map(sec => { const rows = finalSections[sec.key] || []; return `## ${headerOf(sec.key)}\n\n${buildTable(sec, rows)}`; }); const middle = blocks.join('\n\n---\n\n'); const result = `${prefix}\n\n${middle}\n\n---\n\n${suffix}`; return result.endsWith('\n') ? result : `${result}\n`; } module.exports = { SECTIONS, SECTION_LABEL, parseTodoFile, buildTable, regenerate, seqNum };