use crate::execution_mode::ExecutionMode;
use crate::intensity::Intensity;
use crate::measure::Measure;
use crate::node::{Node, NodePayload};
pub trait Visit {
fn visit_tree(&mut self, root: &Node) {
self.visit_node(root, &[]);
}
fn visit_node(&mut self, node: &Node, ancestors: &[&Node]) {
walk_node(self, node, ancestors);
}
fn visit_leaf(&mut self, _measures: &[Measure], _intensity: Option<&Intensity>, _ancestors: &[&Node]) {}
fn visit_exercise(&mut self, _name: Option<&str>, _measures: &[Measure], _intensity: Option<&Intensity>, _ancestors: &[&Node]) {}
fn visit_block(&mut self, _mode: &ExecutionMode, _ancestors: &[&Node]) {}
}
pub fn walk_node<V: Visit + ?Sized>(v: &mut V, node: &Node, ancestors: &[&Node]) {
match &node.payload {
NodePayload::Leaf { measures, intensity } => {
v.visit_leaf(measures, intensity.as_ref(), ancestors);
}
NodePayload::Exercise { measures, intensity, .. } => {
v.visit_exercise(node.name.as_deref(), measures, intensity.as_ref(), ancestors);
}
NodePayload::Block { execution_mode, .. } => {
v.visit_block(execution_mode, ancestors);
}
NodePayload::Temporal { .. } | NodePayload::Custom { .. } => {}
}
let mut child_ancestors = ancestors.to_vec();
child_ancestors.push(node);
for child in &node.children {
v.visit_node(child, &child_ancestors);
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::execution_mode::ExecutionMode;
use crate::measure::{Measure, WeightUnit};
use crate::node::*;
use std::collections::BTreeMap;
struct TreeStats {
sets: usize,
blocks: usize,
exercises: Vec<String>,
}
impl Visit for TreeStats {
fn visit_leaf(&mut self, _measures: &[Measure], _intensity: Option<&Intensity>, _ancestors: &[&Node]) {
self.sets += 1;
}
fn visit_block(&mut self, _mode: &ExecutionMode, _ancestors: &[&Node]) {
self.blocks += 1;
}
fn visit_exercise(&mut self, name: Option<&str>, _measures: &[Measure], _intensity: Option<&Intensity>, _ancestors: &[&Node]) {
if let Some(n) = name {
self.exercises.push(n.to_string());
}
}
}
fn make_superset_session() -> Node {
let set_a1 = Node {
id: NodeId::from_string("sa1"), kind: NodeKind::Set, name: None, children: vec![],
payload: NodePayload::Leaf { measures: vec![Measure::Weight { amount: 30.0, unit: WeightUnit::Kg }, Measure::Reps(10)], intensity: None },
metadata: BTreeMap::new(),
};
let set_a2 = Node {
id: NodeId::from_string("sa2"), kind: NodeKind::Set, name: None, children: vec![],
payload: NodePayload::Leaf { measures: vec![Measure::Weight { amount: 30.0, unit: WeightUnit::Kg }, Measure::Reps(10)], intensity: None },
metadata: BTreeMap::new(),
};
let ex_a = Node {
id: NodeId::from_string("ea"), kind: NodeKind::Exercise, name: Some("DB Row".into()),
children: vec![set_a1, set_a2],
payload: NodePayload::Exercise { measures: vec![], intensity: None, rest_seconds: None },
metadata: BTreeMap::new(),
};
let set_b1 = Node {
id: NodeId::from_string("sb1"), kind: NodeKind::Set, name: None, children: vec![],
payload: NodePayload::Leaf { measures: vec![Measure::Weight { amount: 25.0, unit: WeightUnit::Kg }, Measure::Reps(12)], intensity: None },
metadata: BTreeMap::new(),
};
let ex_b = Node {
id: NodeId::from_string("eb"), kind: NodeKind::Exercise, name: Some("Lateral Raise".into()),
children: vec![set_b1],
payload: NodePayload::Exercise { measures: vec![], intensity: None, rest_seconds: None },
metadata: BTreeMap::new(),
};
let block = Node {
id: NodeId::from_string("blk"), kind: NodeKind::Block, name: Some("Superset A".into()),
children: vec![ex_a, ex_b],
payload: NodePayload::Block { execution_mode: ExecutionMode::Parallel, rest_seconds: Some(90.0) },
metadata: BTreeMap::new(),
};
Node {
id: NodeId::from_string("sess"), kind: NodeKind::Session, name: Some("Upper".into()),
children: vec![block],
payload: NodePayload::Temporal { rest_seconds: None },
metadata: BTreeMap::new(),
}
}
#[test]
fn visit_counts_sets_and_blocks() {
let session = make_superset_session();
let mut stats = TreeStats { sets: 0, blocks: 0, exercises: vec![] };
stats.visit_tree(&session);
assert_eq!(stats.sets, 3);
assert_eq!(stats.blocks, 1);
assert_eq!(stats.exercises, vec!["DB Row", "Lateral Raise"]);
}
struct SetWithExercise {
results: Vec<(String, String)>, }
impl Visit for SetWithExercise {
fn visit_leaf(&mut self, _measures: &[Measure], _: Option<&Intensity>, ancestors: &[&Node]) {
let exercise_name = ancestors.iter().rev()
.find(|n| matches!(n.kind, NodeKind::Exercise))
.and_then(|n| n.name.as_deref())
.unwrap_or("unknown");
let set_id = ancestors.last()
.map(|_| "set")
.unwrap_or("orphan");
self.results.push((exercise_name.to_string(), set_id.to_string()));
}
}
#[test]
fn visit_with_path_context() {
let session = make_superset_session();
let mut finder = SetWithExercise { results: vec![] };
finder.visit_tree(&session);
assert_eq!(finder.results.len(), 3);
assert_eq!(finder.results[0].0, "DB Row");
assert_eq!(finder.results[1].0, "DB Row");
assert_eq!(finder.results[2].0, "Lateral Raise");
}
}