use alloc::{
borrow::{Cow, ToOwned},
format,
string::{String, ToString},
vec::Vec,
};
use crate::{
prop::IcalPropKind,
tree::{
cst::{IcalCst, IcalItem},
leaf::IcalLeaf,
line::IcalLine,
merge::{IcalComponentPath, IcalComponentStep, IcalPropPath, diff::identity_in},
},
};
pub(super) struct Node<'c, 'a> {
pub(super) path: IcalComponentPath<'a>,
pub(super) cst: &'c IcalCst<'a>,
}
impl<'a> IcalCst<'a> {
pub(super) fn nodes(&self) -> Vec<Node<'_, 'a>> {
let mut out = Vec::new();
self.walk(IcalComponentPath::default(), &mut out);
out
}
fn walk<'c>(&'c self, path: IcalComponentPath<'a>, out: &mut Vec<Node<'c, 'a>>) {
out.push(Node {
path: path.clone(),
cst: self,
});
let mut seen: Vec<(String, usize)> = Vec::new();
for child in self.children() {
let name = child.upper_name();
let ordinal = match seen.iter_mut().find(|(held, _)| *held == name) {
Some((_, count)) => {
*count += 1;
*count
}
None => {
seen.push((name.clone(), 0));
0
}
};
let mut nested = path.clone();
nested.0.push(IcalComponentStep {
key: Cow::Owned(child.identity(ordinal)),
name: Cow::Owned(name),
});
child.walk(nested, out);
}
}
pub(super) fn children(&self) -> impl Iterator<Item = &IcalCst<'a>> {
self.items.iter().filter_map(|item| match item {
IcalItem::Component(child) => Some(&**child),
_ => None,
})
}
pub(super) fn upper_name(&self) -> String {
self.begin
.as_ref()
.map(|begin| begin.raw_value_str().to_ascii_uppercase())
.unwrap_or_default()
}
pub(super) fn identity(&self, ordinal: usize) -> String {
let Some(uid) = self.first_raw(IcalPropKind::Uid) else {
return ordinal.to_string();
};
match self.first_raw(IcalPropKind::RecurrenceId) {
Some(id) => format!("{uid}/{id}"),
None => uid,
}
}
fn first_raw(&self, kind: IcalPropKind) -> Option<String> {
self.prop_lines()
.find(|line| line.name.get().eq_ignore_ascii_case(&kind))
.map(|line| line.raw_value_str().into_owned())
}
pub(super) fn prop_lines(&self) -> impl Iterator<Item = &IcalLine<'a>> {
self.items
.iter()
.filter_map(|item| match item {
IcalItem::Prop(line) => Some(line),
_ => None,
})
.filter(|line| !line.is_structural())
}
pub(super) fn at(&self, path: &IcalComponentPath<'a>) -> Option<&IcalCst<'a>> {
let mut held = self;
for step in &path.0 {
let mut ordinal = 0;
held = held.children().find(|child| {
if child.upper_name() != step.name {
return false;
}
let matched = child.identity(ordinal) == step.key;
ordinal += 1;
matched
})?;
}
Some(held)
}
pub(super) fn at_mut(&mut self, path: &IcalComponentPath<'a>) -> Option<&mut IcalCst<'a>> {
let mut held = self;
for step in &path.0 {
let mut ordinal = 0;
held = held.items.iter_mut().find_map(|item| {
let IcalItem::Component(child) = item else {
return None;
};
if child.upper_name() != step.name {
return None;
}
let matched = child.identity(ordinal) == step.key;
ordinal += 1;
matched.then_some(&mut **child)
})?;
}
Some(held)
}
pub(super) fn component_position(&self, at: &IcalComponentPath<'_>) -> Option<usize> {
let step = at.0.last()?;
let mut ordinal = 0;
self.items.iter().position(|item| {
let IcalItem::Component(child) = item else {
return false;
};
if child.upper_name() != step.name {
return false;
}
let held = child.identity(ordinal);
ordinal += 1;
held == step.key
})
}
pub(super) fn line_ordinal(
&self,
at: &IcalPropPath<'_>,
position: Option<usize>,
) -> Option<usize> {
let Some(identity) = &at.identity else {
return position;
};
self.prop_lines()
.filter(|line| line.name.get().eq_ignore_ascii_case(&at.name))
.position(|line| line.value_key() == **identity)
}
pub(super) fn line_at(
&self,
at: &IcalPropPath<'_>,
position: Option<usize>,
) -> Option<&IcalLine<'a>> {
let ordinal = self.line_ordinal(at, position)?;
self.prop_lines()
.filter(|line| line.name.get().eq_ignore_ascii_case(&at.name))
.nth(ordinal)
}
pub(super) fn nth_line_mut(&mut self, name: &str, at: usize) -> Option<&mut IcalLine<'a>> {
self.items
.iter_mut()
.filter_map(|item| match item {
IcalItem::Prop(line) => Some(line),
_ => None,
})
.filter(|line| !line.is_structural() && line.name.get().eq_ignore_ascii_case(name))
.nth(at)
}
pub(super) fn line_position(&self, name: &str, at: usize) -> Option<usize> {
let mut ordinal = 0;
self.items.iter().position(|item| {
let IcalItem::Prop(line) = item else {
return false;
};
if line.is_structural() || !line.name.get().eq_ignore_ascii_case(name) {
return false;
}
let held = ordinal;
ordinal += 1;
held == at
})
}
}
impl<'a> IcalLine<'a> {
pub(super) fn is_structural(&self) -> bool {
let name = self.name.get();
name.eq_ignore_ascii_case("BEGIN") || name.eq_ignore_ascii_case("END")
}
pub(super) fn value_text(&self) -> String {
let mut out = Vec::new();
self.value.write_bytes(&mut out);
String::from_utf8_lossy(&out).into_owned()
}
pub(super) fn value_key(&self) -> String {
self.value_text().to_lowercase()
}
pub(super) fn terminated(&self) -> IcalLine<'a> {
let mut held = self.clone();
if held.eol.get().is_empty() {
held.eol = IcalLeaf(Cow::Borrowed("\r\n"));
}
held
}
}
impl<'a> IcalComponentPath<'a> {
pub(super) fn parent(&self) -> IcalComponentPath<'a> {
let mut parent = self.clone();
parent.0.pop();
parent
}
pub(super) fn ancestors(&self) -> impl Iterator<Item = IcalComponentPath<'a>> + '_ {
(1..self.0.len()).map(|depth| IcalComponentPath(self.0[..depth].to_vec()))
}
}
impl<'a> IcalPropPath<'a> {
pub(super) fn of(
component: &IcalComponentPath<'a>,
lines: &[&IcalLine<'a>],
at: usize,
) -> Self {
let name = lines[at].name.get();
let index = lines[..at]
.iter()
.filter(|held| held.name.get().eq_ignore_ascii_case(name))
.count();
IcalPropPath {
component: component.clone(),
name: Cow::Owned(name.to_owned()),
index,
identity: identity_in(lines, at),
}
}
}