use std::sync::LazyLock;
use regex::Regex;
use argonauts_core::names::MessageId;
use argonauts_core::steps::task::TaskReport;
static REPLY: LazyLock<Regex> = LazyLock::new(|| {
Regex::new(
r"(?i)^\s*[-*]?\s*`?(answer|accept|approve|reject|object)\s+(m-\d{3,})`?\s*[:,-]?\s*(.*)$",
)
.expect("a valid pattern")
});
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Reply {
Answer {
to: MessageId,
text: String,
},
Vote {
on: MessageId,
accept: bool,
reason: String,
},
}
pub fn in_report(report: &TaskReport) -> Vec<Reply> {
let lines = report
.summary
.lines()
.chain(report.concerns.iter().flat_map(|concern| concern.lines()));
let mut replies: Vec<Reply> = Vec::new();
for line in lines {
let Some(reply) = parse(line) else {
continue;
};
if !replies.iter().any(|known| about(known) == about(&reply)) {
replies.push(reply);
}
}
replies
}
fn parse(line: &str) -> Option<Reply> {
let found = REPLY.captures(line)?;
let verb = found.get(1)?.as_str().to_ascii_lowercase();
let id = MessageId::parse(found.get(2)?.as_str())?;
let rest = found
.get(3)
.map(|rest| rest.as_str().trim().trim_end_matches('`').trim().to_owned())
.unwrap_or_default();
Some(match verb.as_str() {
"answer" => Reply::Answer { to: id, text: rest },
"accept" | "approve" => Reply::Vote {
on: id,
accept: true,
reason: rest,
},
_ => Reply::Vote {
on: id,
accept: false,
reason: rest,
},
})
}
fn about(reply: &Reply) -> &MessageId {
match reply {
Reply::Answer { to, .. } => to,
Reply::Vote { on, .. } => on,
}
}
#[cfg(test)]
mod tests {
use argonauts_core::steps::task::ReportStatus;
use super::*;
#[test]
fn replies_are_read_from_summary_and_concerns() {
let report = TaskReport {
status: ReportStatus::Done,
summary: "Built the store.\nanswer m-004: Seconds, as an integer.".to_owned(),
files_changed: Vec::new(),
tests_run: Vec::new(),
concerns: vec![
"- Reject m-007: the interface is frozen".to_owned(),
"accept m-007: changed my mind".to_owned(),
"`approve m-009`".to_owned(),
"The answer m-004 is above".to_owned(),
],
questions: Vec::new(),
change_requests: Vec::new(),
memory_proposals: Vec::new(),
answer: String::new(),
};
assert_eq!(
in_report(&report),
[
Reply::Answer {
to: MessageId::numbered(4),
text: "Seconds, as an integer.".to_owned()
},
Reply::Vote {
on: MessageId::numbered(7),
accept: false,
reason: "the interface is frozen".to_owned()
},
Reply::Vote {
on: MessageId::numbered(9),
accept: true,
reason: String::new()
},
]
);
}
}