import { KanbanConfig, InclusionMode, KanbanSortType } from "./types"; const INCLUSION_MODES: InclusionMode[] = ["gtd-only", "all", "none"]; const SORT_TYPES: KanbanSortType[] = ["due-date", "title", "modified-date"]; const CARD_DETAILS = ["hover", "always", "none"]; /** * Normalise a parsed gtd-kanban YAML object into a KanbanConfig with * SPEC defaults. Invalid values produce warnings, not failures. */ export default function parseKanbanConfig(raw: any): KanbanConfig { const warnings: string[] = []; const input = raw && typeof raw === "object" ? raw : {}; const knownKeys = [ "title", "scope", "todos", "sort-type", "sort", "in-progress-tag", "card-detail", "done-window", ]; for (const key of Object.keys(input)) { if (!knownKeys.includes(key)) warnings.push(`Unknown option "${key}"`); } const title = input.title !== undefined && input.title !== null ? String(input.title) : null; // scope (shared semantics with gtd-calendar) let scopeDepth = 0; if (input.scope !== undefined) { const value = input.scope; if (value === "this-folder") { scopeDepth = 0; } else if (value === "children") { scopeDepth = Infinity; } else if (Number.isInteger(Number(value)) && Number(value) >= 0) { scopeDepth = Number(value); } else { warnings.push(`Invalid scope "${value}" (using "this-folder")`); } } // todos inclusion let todos: InclusionMode = "gtd-only"; if (input.todos !== undefined) { const candidate = String(input.todos).toLowerCase() as InclusionMode; if (INCLUSION_MODES.includes(candidate)) { todos = candidate; } else { warnings.push(`Invalid todos "${input.todos}" (using "gtd-only")`); } } // sort-type let sortType: KanbanSortType = "due-date"; if (input["sort-type"] !== undefined) { let candidate = String(input["sort-type"]).toLowerCase(); if (candidate === "modified_date") candidate = "modified-date"; // alias if (SORT_TYPES.includes(candidate as KanbanSortType)) { sortType = candidate as KanbanSortType; } else { warnings.push( `Invalid sort-type "${input["sort-type"]}" (using "due-date")` ); } } // sort direction let sort: KanbanConfig["sort"] = "asc"; if (input.sort !== undefined) { const candidate = String(input.sort).toLowerCase(); if (candidate === "asc" || candidate === "desc") { sort = candidate; } else { warnings.push(`Invalid sort "${input.sort}" (using "asc")`); } } // in-progress tag (configurable; lowercased to match tag comparison) let inProgressTag = "in-progress"; if ( input["in-progress-tag"] !== undefined && input["in-progress-tag"] !== null && String(input["in-progress-tag"]).trim() !== "" ) { inProgressTag = String(input["in-progress-tag"]).trim().toLowerCase(); } // card-detail let cardDetail: KanbanConfig["cardDetail"] = "hover"; if (input["card-detail"] !== undefined) { const candidate = String(input["card-detail"]).toLowerCase(); if (CARD_DETAILS.includes(candidate)) { cardDetail = candidate as KanbanConfig["cardDetail"]; } else { warnings.push( `Invalid card-detail "${input["card-detail"]}" (using "hover")` ); } } // done-window (days; "all" => Infinity) let doneWindow = 7; if (input["done-window"] !== undefined) { const value = input["done-window"]; if (String(value).toLowerCase() === "all") { doneWindow = Infinity; } else if (Number.isInteger(Number(value)) && Number(value) >= 0) { doneWindow = Number(value); } else { warnings.push(`Invalid done-window "${value}" (using 7)`); } } return { title, scopeDepth, todos, sortType, sort, inProgressTag, cardDetail, doneWindow, warnings, }; }