use crate::files::Edit;
use crate::token::TokenKind;
#[derive(Clone, Copy, Debug)]
pub struct Queued<'a> {
pub name: &'a str,
pub text: &'a [u8],
pub edits: &'a [Edit],
}
#[derive(Clone, Copy, Debug)]
pub struct Change<'a> {
pub input: usize,
pub name: &'a str,
pub text: &'a [u8],
pub edit: &'a Edit,
pub template: &'a [TokenKind],
pub later: usize,
pub index: usize,
pub total: usize,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum Answer {
Accept,
Skip,
Replace(Vec<u8>),
AcceptTemplate,
SkipTemplate,
AcceptAll,
Quit,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct Skipped {
pub input: usize,
pub start: usize,
pub end: usize,
}
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct Reviewed {
pub kept: Vec<Vec<Edit>>,
pub skipped: Vec<Skipped>,
}
pub fn review<E>(
queue: &[Queued<'_>],
mut ask: impl FnMut(&Change<'_>) -> Result<Answer, E>,
) -> Result<Option<Reviewed>, E> {
let total: usize = queue.iter().map(|q| q.edits.len()).sum();
let shapes: Vec<Vec<Vec<TokenKind>>> = queue
.iter()
.map(|q| q.edits.iter().map(|e| crate::templates::silhouette(q.text, e.start..e.end)).collect())
.collect();
let mut kept: Vec<Vec<Edit>> = queue.iter().map(|_| Vec::new()).collect();
let mut answered: Vec<(&[TokenKind], bool)> = Vec::new();
let mut skipped = Vec::new();
let mut all = false;
let mut index = 0usize;
for (k, q) in queue.iter().enumerate() {
for (e, edit) in q.edits.iter().enumerate() {
index += 1;
if all {
kept[k].push(edit.clone());
continue;
}
let shape = shapes[k][e].as_slice();
if let Some(&(_, accepted)) = answered.iter().find(|(s, _)| *s == shape) {
if accepted {
kept[k].push(edit.clone());
} else {
skipped.push(Skipped { input: k, start: edit.start, end: edit.end });
}
continue;
}
let later = shapes
.iter()
.enumerate()
.flat_map(|(j, per_input)| per_input.iter().enumerate().map(move |(i, s)| (j, i, s)))
.filter(|&(j, i, s)| (j > k || (j == k && i > e)) && s.as_slice() == shape)
.count();
let change =
Change { input: k, name: q.name, text: q.text, edit, template: shape, later, index, total };
match ask(&change)? {
Answer::Accept => kept[k].push(edit.clone()),
Answer::Skip => {}
Answer::Replace(replacement) => {
kept[k].push(Edit { start: edit.start, end: edit.end, replacement });
}
Answer::AcceptTemplate => {
kept[k].push(edit.clone());
answered.push((shape, true));
}
Answer::SkipTemplate => {
answered.push((shape, false));
skipped.push(Skipped { input: k, start: edit.start, end: edit.end });
}
Answer::AcceptAll => {
kept[k].push(edit.clone());
all = true;
}
Answer::Quit => return Ok(None),
}
}
}
Ok(Some(Reviewed { kept, skipped }))
}
#[cfg(test)]
mod tests {
use super::*;
fn edit(text: &[u8], from: &str, to: &str) -> Edit {
let start = text.windows(from.len()).position(|w| w == from.as_bytes()).expect("the text holds it");
Edit { start, end: start + from.len(), replacement: to.as_bytes().to_vec() }
}
fn queued<'a>(a: &'a [u8], b: &'a [u8], ea: &'a [Edit], eb: &'a [Edit]) -> Vec<Queued<'a>> {
vec![Queued { name: "a", text: a, edits: ea }, Queued { name: "b", text: b, edits: eb }]
}
#[test]
fn each_answer_keeps_what_it_says() {
let a = b"x 1 y 2 word";
let b = b"z 3 more";
let ea = [edit(a, "1", "one"), edit(a, "2", "two"), edit(a, "word", "WORD")];
let eb = [edit(b, "3", "three"), edit(b, "more", "MORE")];
let q = queued(a, b, &ea, &eb);
let mut asked = Vec::new();
let answers = [Answer::Accept, Answer::Replace(b"deux".to_vec()), Answer::Skip, Answer::Skip, Answer::Accept];
let mut next = answers.iter();
let reviewed = review(&q, |c| -> Result<Answer, ()> {
asked.push((c.name.to_string(), c.index, c.total, c.later));
Ok(next.next().expect("an answer for each change").clone())
})
.expect("no error")
.expect("not quit");
assert_eq!(
asked,
[
("a".to_string(), 1, 5, 2),
("a".to_string(), 2, 5, 1),
("a".to_string(), 3, 5, 1),
("b".to_string(), 4, 5, 0),
("b".to_string(), 5, 5, 0),
]
);
assert_eq!(reviewed.kept[0], [ea[0].clone(), edit(a, "2", "deux")]);
assert_eq!(reviewed.kept[1], [eb[1].clone()]);
assert!(reviewed.skipped.is_empty());
}
#[test]
fn a_template_answer_decides_the_later_changes_of_its_template() {
let a = b"x 1 y 2 word";
let b = b"z 3 more";
let ea = [edit(a, "1", "one"), edit(a, "2", "two"), edit(a, "word", "WORD")];
let eb = [edit(b, "3", "three"), edit(b, "more", "MORE")];
let q = queued(a, b, &ea, &eb);
let mut asked = 0;
let reviewed = review(&q, |c| -> Result<Answer, ()> {
asked += 1;
Ok(if c.edit.replacement == b"one" { Answer::SkipTemplate } else { Answer::AcceptTemplate })
})
.expect("no error")
.expect("not quit");
assert_eq!(asked, 2);
assert_eq!(reviewed.kept, [vec![ea[2].clone()], vec![eb[1].clone()]]);
assert_eq!(
reviewed.skipped,
[
Skipped { input: 0, start: ea[0].start, end: ea[0].end },
Skipped { input: 0, start: ea[1].start, end: ea[1].end },
Skipped { input: 1, start: eb[0].start, end: eb[0].end },
]
);
}
#[test]
fn all_keeps_the_rest_and_quit_keeps_nothing() {
let a = b"x 1 y 2 word";
let b = b"z 3 more";
let ea = [edit(a, "1", "one"), edit(a, "2", "two"), edit(a, "word", "WORD")];
let eb = [edit(b, "3", "three"), edit(b, "more", "MORE")];
let q = queued(a, b, &ea, &eb);
let mut asked = 0;
let reviewed = review(&q, |_| -> Result<Answer, ()> {
asked += 1;
Ok(if asked == 1 { Answer::Skip } else { Answer::AcceptAll })
})
.expect("no error")
.expect("not quit");
assert_eq!(asked, 2);
assert_eq!(reviewed.kept, [vec![ea[1].clone(), ea[2].clone()], eb.to_vec()]);
let quit = review(&q, |c| -> Result<Answer, ()> { Ok(if c.index == 3 { Answer::Quit } else { Answer::Accept }) });
assert_eq!(quit, Ok(None));
let failed = review(&q, |_| -> Result<Answer, &str> { Err("no answer") });
assert_eq!(failed, Err("no answer"));
}
}