parse method
Parses Kanban diagram from cleaned lines Returns tuple of (MermaidDiagramData, KanbanChartData) or null if invalid
Implementation
(MermaidDiagramData, KanbanChartData)? parse(List<String> lines) {
if (lines.isEmpty) return null;
String? title;
String? ticketBaseUrl;
final columns = <KanbanColumn>[];
KanbanColumn? currentColumn;
final currentTasks = <KanbanTask>[];
var i = 0;
// Step 1: Check for YAML frontmatter config
if (lines[i].trim() == '---') {
final configEnd = _findConfigEnd(lines, i + 1);
if (configEnd != -1) {
ticketBaseUrl = _parseConfig(lines.sublist(i + 1, configEnd));
i = configEnd + 1;
}
}
// Step 2: Parse kanban content
while (i < lines.length) {
final line = lines[i];
final trimmedLine = line.trim();
i++;
if (trimmedLine.isEmpty) continue;
// Skip 'kanban' keyword
if (trimmedLine.toLowerCase() == 'kanban') continue;
// Parse title
if (trimmedLine.toLowerCase().startsWith('title ')) {
title = trimmedLine.substring(6).trim();
continue;
}
// Check if line is indented (potential task)
final isIndented = line.startsWith(' ') || line.startsWith('\t');
// Parse column: columnId[Column Title] or columnId[Column Title] wip:N
// Columns should NOT be deeply indented (max 2 spaces for formatting)
final columnMatch =
RegExp(r'^(\w+)\[([^\]]+)\](?:\s+wip:(\d+))?$').firstMatch(trimmedLine);
if (columnMatch != null && !line.startsWith(' ')) {
// Save previous column
if (currentColumn != null) {
columns.add(currentColumn.copyWith(tasks: List.from(currentTasks)));
currentTasks.clear();
}
final columnId = columnMatch.group(1)!;
final columnTitle = columnMatch.group(2)!;
final wipLimit =
columnMatch.group(3) != null ? int.tryParse(columnMatch.group(3)!) : null;
currentColumn = KanbanColumn(
id: columnId,
title: columnTitle,
tasks: const [],
wipLimit: wipLimit,
);
continue;
}
// Parse task (must be deeply indented - 4+ spaces or tabs)
if (isIndented && line.startsWith(' ')) {
if (currentColumn == null) continue; // Task without column
final task = _parseTask(trimmedLine);
if (task != null) {
currentTasks.add(task);
}
}
}
// Save last column
if (currentColumn != null) {
columns.add(currentColumn.copyWith(tasks: List.from(currentTasks)));
}
if (columns.isEmpty) return null;
final kanbanData = KanbanChartData(
columns: columns,
title: title,
ticketBaseUrl: ticketBaseUrl,
);
final diagramData = MermaidDiagramData(
type: DiagramType.kanban,
nodes: const [],
edges: const [],
title: title,
);
return (diagramData, kanbanData);
}