use crate::bindings::{Bindings, Citation};
use crate::codec::schema::{Item, Scalar, Shape, field};
use crate::codec::{Files, LABELS, Mode, Rec, SpecPath, Val, read_file, write_content};
use crate::identity::Key;
use crate::prose::label_spans;
use super::Relabel;
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct Rewrites {
pub files: Files,
pub rewritten: Vec<Relabel>,
}
struct Element {
key: Key,
mentions: Vec<Key>,
}
struct Withdrawals<'a> {
bindings: &'a Bindings,
}
#[must_use]
pub fn rewrite(files: &Files, bindings: &Bindings) -> Rewrites {
let mut rewrites = Rewrites::default();
if !bindings.has_withdrawn() {
return rewrites;
}
let withdrawals = Withdrawals { bindings };
for (path, bytes) in files {
if *path == SpecPath::new(LABELS) {
continue;
}
let cites = std::str::from_utf8(bytes).is_ok_and(|text| {
label_spans(text)
.iter()
.any(|span| bindings.is_withdrawn(span.label))
});
let Some((role, mut content)) = cites
.then(|| read_file(path, bytes, Mode::Strict).ok())
.flatten()
else {
continue;
};
let before = rewrites.rewritten.len();
for record in content.records_mut() {
withdrawals.record(record, None, &mut rewrites.rewritten);
}
if rewrites.rewritten.len() > before {
rewrites
.files
.insert(path.clone(), write_content(path, role, content));
}
}
rewrites
}
impl Withdrawals<'_> {
fn record(&self, record: &mut Rec, around: Option<&Element>, out: &mut Vec<Relabel>) {
let kind = record.kind;
let own = kind.field(field::MENTIONS).and_then(|_| element(record));
let governing = own.as_ref().or(around);
for (name, value) in &mut record.fields {
let (_, each) = kind.field(name).expect("a field the schema defines");
self.value(value, each.shape, governing, out);
}
}
fn value(
&self,
value: &mut Val,
shape: Shape,
governing: Option<&Element>,
out: &mut Vec<Relabel>,
) {
let text = Item::Scalar(Scalar::Text);
match (shape, value) {
(Shape::One(item), Val::Scalar { text: prose, .. }) if item == text => {
self.text(prose, governing, out);
}
(Shape::List(item), Val::List(items)) if item == text => {
let prose = items.iter_mut().filter_map(|item| match item {
Val::Scalar { text, .. } => Some(text),
_ => None,
});
prose.for_each(|text| self.text(text, governing, out));
}
(_, value) => value
.records_mut()
.for_each(|child| self.record(child, governing, out)),
}
}
fn text(&self, text: &mut String, element: Option<&Element>, out: &mut Vec<Relabel>) {
let Some(element) = element else {
return;
};
let mut found = Vec::new();
for span in label_spans(text).into_iter().rev() {
let mentioned = |key| element.mentions.contains(&key);
let citation = if span.qualified {
Citation::NotWithdrawn
} else {
self.bindings.citation(span.label, mentioned)
};
if let Citation::Rewritten(live) = citation {
text.replace_range(span.range, &live.to_string());
found.push(Relabel {
element: element.key,
withdrawn: span.label,
label: live,
});
}
}
out.extend(found.into_iter().rev());
}
}
fn element(record: &Rec) -> Option<Element> {
let key = Key::parse(record.text(field::KEY)?).ok()?;
let mentions = record
.list(field::MENTIONS)
.iter()
.filter_map(|mention| Key::parse(mention.text()?).ok())
.collect();
Some(Element { key, mentions })
}