use crate::muterprater::backend::types::WrapOutcomeWord;
use crate::muterprater::types::{BaselineAxis, SourceCoordinate};
const BASELINE_MARKER: &str = "Unmutated baseline";
const ROSTER_MARKER: &str = "Found";
const ROSTER_SUBJECT: &str = "mutant";
const BASELINE_QUALIFIED_WORD: &str = "ok";
const OUTCOME_WORDS: [(&str, WrapOutcomeWord); 5] = [
("caught", WrapOutcomeWord::Caught),
("missed", WrapOutcomeWord::Missed),
("unviable", WrapOutcomeWord::Unviable),
("timeout", WrapOutcomeWord::TimedOut),
("failed", WrapOutcomeWord::ToolFailed),
];
pub(super) enum LineReading<'line> {
Roster(u32),
Baseline(BaselineAxis),
Mutant {
word: WrapOutcomeWord,
coordinate: SourceCoordinate,
damage: String,
line: &'line str,
},
Unread,
}
pub(super) fn read_line(line: &str) -> LineReading<'_> {
let mut tokens = line.split_whitespace();
let Some(word) = tokens.next() else {
return LineReading::Unread;
};
let rest = tokens.collect::<Vec<&str>>();
if let Some(found) = roster_count(word, &rest) {
return LineReading::Roster(found);
}
if rest.join(" ").starts_with(BASELINE_MARKER) {
return LineReading::Baseline(baseline_axis(word));
}
mutant_reading(word, &rest, line)
}
fn roster_count(word: &str, rest: &[&str]) -> Option<u32> {
if word != ROSTER_MARKER {
return None;
}
let count = rest.first()?;
let subject = rest.get(1)?;
if !subject.starts_with(ROSTER_SUBJECT) {
return None;
}
count.parse::<u32>().ok()
}
fn baseline_axis(word: &str) -> BaselineAxis {
if word.eq_ignore_ascii_case(BASELINE_QUALIFIED_WORD) {
BaselineAxis::Qualified
} else {
BaselineAxis::Failed
}
}
fn mutant_reading<'line>(word: &str, rest: &[&str], line: &'line str) -> LineReading<'line> {
let Some(outcome) = outcome_word(word) else {
return LineReading::Unread;
};
let Some(coordinate_text) = rest.first() else {
return LineReading::Unread;
};
let Some(coordinate) = read_coordinate(coordinate_text) else {
return LineReading::Unread;
};
let damage = rest.get(1..).unwrap_or_default().join(" ");
if damage.is_empty() {
return LineReading::Unread;
}
LineReading::Mutant {
word: outcome,
coordinate,
damage,
line,
}
}
fn outcome_word(token: &str) -> Option<WrapOutcomeWord> {
OUTCOME_WORDS
.iter()
.find(|(spelling, _)| token.eq_ignore_ascii_case(spelling))
.map(|(_, word)| *word)
}
fn read_coordinate(token: &str) -> Option<SourceCoordinate> {
let body = token.strip_suffix(':').unwrap_or(token);
let mut fields = body.rsplitn(3, ':');
let column_text = fields.next()?;
let line_text = fields.next()?;
let file = fields.next()?;
let column = column_text.parse::<u32>().ok()?;
let line = line_text.parse::<u32>().ok()?;
SourceCoordinate::reported(file, line, column).ok()
}