fn parse_sprint_section(
lines: &[&str],
start_idx: usize,
captures: ®ex::Captures,
parsers: &Parsers,
) -> Result<(Sprint, String, usize)> {
let version = captures
.get(1)
.expect("internal error")
.as_str()
.to_string();
let title = captures
.get(2)
.expect("internal error")
.as_str()
.to_string();
let mut sprint = create_initial_sprint(&version, &title);
let lines_consumed = parse_sprint_content(lines, start_idx, &mut sprint, parsers)?;
Ok((sprint, version, lines_consumed))
}
fn create_initial_sprint(version: &str, title: &str) -> Sprint {
Sprint {
version: version.to_string(),
title: title.to_string(),
start_date: Utc::now(),
end_date: Utc::now() + chrono::Duration::days(14),
priority: Priority::P0,
tasks: Vec::new(),
definition_of_done: Vec::new(),
quality_gates: Vec::new(),
}
}
fn parse_sprint_content(
lines: &[&str],
start_idx: usize,
sprint: &mut Sprint,
parsers: &Parsers,
) -> Result<usize> {
let mut i = start_idx + 1;
while i < lines.len() {
let line = lines[i];
if is_next_section_start(line) {
break;
}
i += process_sprint_line(lines, i, sprint, parsers)?;
}
Ok(i - start_idx)
}
fn process_sprint_line(
lines: &[&str],
current_idx: usize,
sprint: &mut Sprint,
parsers: &Parsers,
) -> Result<usize> {
let line = lines[current_idx];
if line.contains("**Duration**:") {
process_duration_line(line, sprint);
Ok(1)
} else if line.contains("**Priority**:") {
process_priority_line(line, sprint);
Ok(1)
} else if line.contains("| ID | Description |") {
let (tasks, advance) = parse_tasks_table(&lines[current_idx..], &parsers.task_regex)?;
sprint.tasks = tasks;
Ok(advance)
} else if line.contains("### Definition of Done") {
let (items, advance) =
parse_definition_of_done(&lines[current_idx..], &parsers.done_regex)?;
sprint.definition_of_done = items;
Ok(advance)
} else {
Ok(1)
}
}
fn is_next_section_start(line: &str) -> bool {
line.starts_with("## ") && line.contains("Sprint:")
}
fn process_duration_line(line: &str, sprint: &mut Sprint) {
if let Some(duration) = parse_duration(line) {
sprint.start_date = duration.0;
sprint.end_date = duration.1;
}
}
fn process_priority_line(line: &str, sprint: &mut Sprint) {
if let Some(priority) = parse_priority(line) {
sprint.priority = priority;
}
}
fn parse_tasks_table(lines: &[&str], task_regex: &Regex) -> Result<(Vec<Task>, usize)> {
let mut tasks = Vec::new();
let mut i = 2;
while i < lines.len() && lines[i].starts_with('|') {
if let Some(captures) = task_regex.captures(lines[i]) {
tasks.push(create_task_from_captures(&captures));
}
i += 1;
}
Ok((tasks, i))
}
fn parse_definition_of_done(lines: &[&str], done_regex: &Regex) -> Result<(Vec<String>, usize)> {
let mut items = Vec::new();
let mut i = 1;
while i < lines.len() && lines[i].starts_with("- [") {
if let Some(captures) = done_regex.captures(lines[i]) {
items.push(
captures
.get(2)
.expect("internal error")
.as_str()
.to_string(),
);
}
i += 1;
}
Ok((items, i))
}
fn parse_backlog_section(
lines: &[&str],
start_idx: usize,
task_regex: &Regex,
) -> Result<(Vec<Task>, usize)> {
let mut i = start_idx + 1;
while i < lines.len() && !lines[i].starts_with('|') {
i += 1;
}
if i + 2 >= lines.len() {
return Ok((Vec::new(), i - start_idx));
}
i += 2; let mut tasks = Vec::new();
while i < lines.len() && lines[i].starts_with('|') {
if let Some(captures) = task_regex.captures(lines[i]) {
tasks.push(create_task_from_captures(&captures));
}
i += 1;
}
Ok((tasks, i - start_idx))
}
fn create_task_from_captures(captures: ®ex::Captures) -> Task {
Task {
id: captures
.get(1)
.expect("internal error")
.as_str()
.to_string(),
description: captures
.get(2)
.expect("internal error")
.as_str()
.trim()
.to_string(),
status: parse_task_status(captures.get(3).expect("internal error").as_str()),
complexity: Complexity::from_str(captures.get(4).expect("internal error").as_str().trim())
.unwrap_or(Complexity::Medium),
priority: Priority::from_str(captures.get(5).expect("internal error").as_str().trim())
.unwrap_or(Priority::P1),
assignee: None,
started_at: None,
completed_at: None,
}
}