use alloc::{
borrow::Cow,
string::{String, ToString},
vec::Vec,
};
use crate::{
param::IcalParam,
tree::{
codec::unescape::unescape_param,
line::IcalLine,
merge::{IcalMergeAction, Slot},
param::node::IcalParamNode,
value::node::IcalValueNode,
},
};
impl IcalValueNode<'_> {
pub(super) fn same_value_as(&self, other: &IcalValueNode<'_>) -> bool {
if self.escaper != other.escaper {
return self.raw_bytes() == other.raw_bytes();
}
let count = self.component_count().max(other.component_count());
(0..count).all(|i| {
component_eq(
&self.decode_component_list(i),
&other.decode_component_list(i),
)
})
}
pub(super) fn same_item_as(&self, other: &IcalValueNode<'_>, item: &str) -> bool {
let raw = |node: &IcalValueNode<'_>| -> Option<Vec<u8>> {
let at = node
.decode_list()
.iter()
.position(|held| held.as_ref() == item)?;
node.raw_list().into_iter().nth(at)
};
match (raw(self), raw(other)) {
(Some(ours), Some(theirs)) => ours == theirs,
_ => false,
}
}
pub(super) fn raw_bytes(&self) -> Vec<u8> {
let mut out = Vec::new();
self.write_bytes(&mut out);
out
}
}
impl IcalParamNode<'_> {
pub(super) fn same_param_as(&self, other: &IcalParamNode<'_>) -> bool {
if self.escaper != other.escaper {
return self.raw_bytes() == other.raw_bytes();
}
self.values.len() == other.values.len()
&& self.values.iter().zip(&other.values).all(|(ours, theirs)| {
unescape_param(ours.get(), self.escaper)
== unescape_param(theirs.get(), other.escaper)
})
}
pub(super) fn raw_bytes(&self) -> Vec<u8> {
let mut out = Vec::new();
self.write_bytes(&mut out);
out
}
fn sorted_values(&self) -> Vec<String> {
let mut values: Vec<String> = self
.values
.iter()
.map(|leaf| leaf.get().to_string())
.collect();
values.sort_unstable();
values
}
}
impl IcalParam<'_> {
pub(super) fn merge_name(&self) -> String {
match self {
IcalParam::Unknown { name, .. } => name.to_ascii_uppercase(),
known => known
.kind()
.map(|kind| kind.to_ascii_uppercase())
.unwrap_or_default(),
}
}
pub(super) fn is_unordered(&self) -> bool {
matches!(
self,
IcalParam::DelegatedFrom(_)
| IcalParam::DelegatedTo(_)
| IcalParam::Member(_)
| IcalParam::Feature(_)
)
}
pub(super) fn same_value_as(&self, other: &IcalParam<'_>) -> bool {
match (self, other) {
(IcalParam::DelegatedFrom(ours), IcalParam::DelegatedFrom(theirs))
| (IcalParam::DelegatedTo(ours), IcalParam::DelegatedTo(theirs))
| (IcalParam::Member(ours), IcalParam::Member(theirs))
| (IcalParam::Feature(ours), IcalParam::Feature(theirs)) => {
sorted(ours) == sorted(theirs)
}
(ours, theirs) => ours == theirs,
}
}
}
impl IcalLine<'_> {
pub(super) fn param_position(&self, name: &str, at: usize) -> Option<usize> {
self.params
.iter()
.enumerate()
.filter(|(_, held)| held.name.get().to_ascii_uppercase() == name)
.map(|(held, _)| held)
.nth(at)
}
pub(super) fn same_param_bytes_as(
&self,
our_slot: &Slot,
theirs: &IcalLine<'_>,
their_slot: &Slot,
param: &IcalParam<'_>,
) -> bool {
let (
Slot::Param {
name: our_name,
at: our_at,
},
Slot::Param {
name: their_name,
at: their_at,
},
) = (our_slot, their_slot)
else {
return false;
};
let (Some(ours), Some(theirs)) = (
self.param_position(our_name, *our_at)
.map(|held| &self.params[held]),
theirs
.param_position(their_name, *their_at)
.map(|held| &theirs.params[held]),
) else {
return false;
};
if !param.is_unordered() {
return ours.raw_bytes() == theirs.raw_bytes();
}
ours.name.get().eq_ignore_ascii_case(theirs.name.get())
&& ours.sorted_values() == theirs.sorted_values()
}
}
impl IcalMergeAction<'_> {
pub(super) fn same_change_as(&self, other: &IcalMergeAction<'_>) -> bool {
use IcalMergeAction::{ParamAdded, ParamChanged, ParamRemoved};
match (self, other) {
(
ParamAdded {
at: our_at,
param: ours,
},
ParamAdded {
at: their_at,
param: theirs,
},
)
| (
ParamRemoved {
at: our_at,
param: ours,
},
ParamRemoved {
at: their_at,
param: theirs,
},
) => our_at == their_at && ours.same_value_as(theirs),
(
ParamChanged {
at: our_at,
old: our_old,
new: our_new,
},
ParamChanged {
at: their_at,
old: their_old,
new: their_new,
},
) => {
our_at == their_at
&& our_old.same_value_as(their_old)
&& our_new.same_value_as(their_new)
}
(ours, theirs) => ours == theirs,
}
}
}
fn component_eq(old: &[Cow<'_, str>], new: &[Cow<'_, str>]) -> bool {
let eq = old.len() == new.len()
&& old
.iter()
.zip(new)
.all(|(old, new)| old.as_ref() == new.as_ref());
eq || (old.iter().all(|value| value.is_empty()) && new.iter().all(|value| value.is_empty()))
}
fn sorted<'v>(values: &'v [Cow<'_, str>]) -> Vec<&'v str> {
let mut items: Vec<&str> = values.iter().map(Cow::as_ref).collect();
items.sort_unstable();
items
}