* Parse the Phases markdown table into structured objects. * * Expects a table with columns (in any order): * | # | Status | Type | Phase | Done When | * * Returns an array of phase objects. Rows that don't parse cleanly are skipped. * * @param {string} content - Full campaign file content
(content)
| 43 | * @returns {Array<{number: number, status: string, type: string, name: string, doneWhen: string}>} |
| 44 | */ |
| 45 | function parsePhaseTable(content) { |
| 46 | const section = parseSection(content, 'Phases'); |
| 47 | if (!section) return []; |
| 48 | |
| 49 | const rows = section.split(/\r?\n/).filter(line => line.trim().startsWith('|')); |
| 50 | if (rows.length < 2) return []; |
| 51 | |
| 52 | // First row is the header — extract column positions |
| 53 | const headerCells = rows[0].split('|').map(c => c.trim().toLowerCase()); |
| 54 | const col = { |
| 55 | number: headerCells.indexOf('#'), |
| 56 | status: headerCells.indexOf('status'), |
| 57 | type: headerCells.indexOf('type'), |
| 58 | name: headerCells.findIndex(h => h === 'phase' || h === 'name'), |
| 59 | doneWhen: headerCells.findIndex(h => h.includes('done') || h.includes('when')), |
| 60 | }; |
| 61 | |
| 62 | // Second row is the separator — skip it |
| 63 | const dataRows = rows.slice(2); |
| 64 | const phases = []; |
| 65 | |
| 66 | for (const row of dataRows) { |
| 67 | const cells = row.split('|').map(c => c.trim()); |
| 68 | // cells[0] is empty (before first |), col indices from indexOf already |
| 69 | // account for this empty slot so use them directly — no +1 adjustment. |
| 70 | const get = (idx) => (idx >= 0 && idx < cells.length ? cells[idx] : ''); |
| 71 | const num = parseInt(get(col.number), 10); |
| 72 | if (Number.isNaN(num)) continue; |
| 73 | phases.push({ |
| 74 | number: num, |
| 75 | status: get(col.status) || 'pending', |
| 76 | type: get(col.type) || '', |
| 77 | name: get(col.name) || '', |
| 78 | doneWhen: get(col.doneWhen) || '', |
| 79 | }); |
| 80 | } |
| 81 | |
| 82 | return phases; |
| 83 | } |
| 84 | |
| 85 | function parseCampaignContent(content, options = {}) { |
| 86 | const slug = options.slug || null; |
no test coverage detected