parsePlanContent static method
Parses <task> XML blocks from PLAN.md content into normalized issues.
Visible for testing.
Implementation
static List<Issue> parsePlanContent(
String content, {
Map<String, String> statesById = const <String, String>{},
}) {
final taskRegex = RegExp(r'<task[^>]*>.*?</task>', dotAll: true);
final matches = taskRegex.allMatches(content);
final issues = <Issue>[];
for (final match in matches) {
final block = match.group(0)!;
final XmlElement element;
try {
element = XmlDocument.parse(block).rootElement;
} catch (_) {
continue;
}
final id = element.getAttribute('id') ?? '';
if (id.isEmpty) continue;
final type = element.getAttribute('type') ?? 'implement';
final nameElements = element.findElements('n');
final title = nameElements.isNotEmpty
? nameElements.first.innerText.trim()
: 'Task $id';
final files = element
.findElements('files')
.expand((f) => f.findElements('file'))
.map((f) => f.innerText.trim())
.toList();
final objective = _firstChildText(element, 'objective');
final verification = _firstChildText(element, 'verification');
final acceptance = _firstChildText(element, 'acceptance');
final descriptionBuffer = StringBuffer();
if (objective != null) {
descriptionBuffer
..writeln('## Objective')
..writeln(objective);
}
if (verification != null) {
descriptionBuffer
..writeln()
..writeln('## Verification')
..writeln(verification);
}
if (acceptance != null) {
descriptionBuffer
..writeln()
..writeln('## Acceptance')
..writeln(acceptance);
}
if (files.isNotEmpty) {
descriptionBuffer
..writeln()
..writeln('## Files')
..writeln(files.map((f) => '- $f').join('\n'));
}
final state = statesById[id] ?? 'Todo';
issues.add(
Issue(
id: id,
identifier: 'PLAN-$id',
title: title,
description: descriptionBuffer.isEmpty
? null
: descriptionBuffer.toString().trimRight(),
priority: _safeParseInt(id),
state: state,
labels: <String>[type.toLowerCase()],
),
);
}
return issues;
}