mod compare;
mod diff;
mod judge;
mod node;
mod op;
mod replay;
use alloc::{borrow::Cow, vec::Vec};
use crate::{
param::IcalParam,
tree::{
cst::IcalCst,
merge::{
diff::Diff,
op::{Op, Slot},
replay::{Restored, Shift},
},
},
value::IcalValue,
};
pub struct IcalMerge<'m, 'a> {
pub base: &'m IcalCst<'a>,
pub left: &'m IcalCst<'a>,
pub right: &'m IcalCst<'a>,
}
impl<'a> IcalMerge<'_, 'a> {
pub fn merge(self) -> IcalMergeReport<'a> {
let version = self.base.version();
let base = self.base.nodes();
let left = self.left.nodes();
let right = self.right.nodes();
let left_ops = Diff {
base: &base,
side: &left,
version,
}
.run();
let right_ops = Diff {
base: &base,
side: &right,
version,
}
.run();
let mut merged = self.left.clone();
let mut conflicts = Vec::new();
let mut applicable = Vec::new();
for op in &right_ops {
let verdict = self.judge(op, &left_ops, &right_ops);
if verdict.applies {
applicable.push(op);
}
if let Some(left) = verdict.reason {
conflicts.push(IcalMergeConflict {
left,
right: op.action.clone(),
});
}
}
applicable.sort_by_key(|op| op.replay_order());
let shift = Shift::of(&left_ops);
let mut restored = Restored::default();
for op in applicable {
self.apply(&mut merged, op, &shift, &mut restored);
}
IcalMergeReport {
merged,
left: left_ops.into_iter().map(|op| op.action).collect(),
right: right_ops.into_iter().map(|op| op.action).collect(),
conflicts,
}
}
}
#[derive(Clone, Debug)]
pub struct IcalMergeReport<'a> {
pub merged: IcalCst<'a>,
pub left: Vec<IcalMergeAction<'a>>,
pub right: Vec<IcalMergeAction<'a>>,
pub conflicts: Vec<IcalMergeConflict<'a>>,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct IcalMergeConflict<'a> {
pub left: IcalMergeReason<'a>,
pub right: IcalMergeAction<'a>,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum IcalMergeReason<'a> {
Divergent(IcalMergeAction<'a>),
Recurrence(IcalMergeAction<'a>),
}
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct IcalComponentPath<'a>(pub Vec<IcalComponentStep<'a>>);
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct IcalComponentStep<'a> {
pub name: Cow<'a, str>,
pub key: Cow<'a, str>,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct IcalPropPath<'a> {
pub component: IcalComponentPath<'a>,
pub name: Cow<'a, str>,
pub index: usize,
pub identity: Option<Cow<'a, str>>,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum IcalMergeAction<'a> {
ComponentAdded {
at: IcalComponentPath<'a>,
},
ComponentRemoved {
at: IcalComponentPath<'a>,
},
PropAdded {
at: IcalPropPath<'a>,
value: IcalValue<'a>,
},
PropRemoved {
at: IcalPropPath<'a>,
value: IcalValue<'a>,
},
ValueChanged {
at: IcalPropPath<'a>,
old: IcalValue<'a>,
new: IcalValue<'a>,
},
ValueItemAdded {
at: IcalPropPath<'a>,
item: Cow<'a, str>,
},
ValueItemRemoved {
at: IcalPropPath<'a>,
item: Cow<'a, str>,
},
ParamAdded {
at: IcalPropPath<'a>,
param: IcalParam<'a>,
},
ParamRemoved {
at: IcalPropPath<'a>,
param: IcalParam<'a>,
},
ParamChanged {
at: IcalPropPath<'a>,
old: IcalParam<'a>,
new: IcalParam<'a>,
},
}
impl<'a> IcalMergeAction<'a> {
fn is_removal(&self) -> bool {
matches!(
self,
Self::ComponentRemoved { .. }
| Self::PropRemoved { .. }
| Self::ValueItemRemoved { .. }
| Self::ParamRemoved { .. }
)
}
fn prop_path(&self) -> Option<&IcalPropPath<'a>> {
match self {
Self::ComponentAdded { .. } | Self::ComponentRemoved { .. } => None,
Self::PropAdded { at, .. }
| Self::PropRemoved { at, .. }
| Self::ValueChanged { at, .. }
| Self::ValueItemAdded { at, .. }
| Self::ValueItemRemoved { at, .. }
| Self::ParamAdded { at, .. }
| Self::ParamRemoved { at, .. }
| Self::ParamChanged { at, .. } => Some(at),
}
}
}