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);
}