capability-core-string-skeleton 0.1.0

A Rust crate providing a framework to generate and manipulate skill tree skeletons using string-based node representations. It aggregates dispatch and leaf holder nodes into scalable domain models.
Documentation
// ---------------- [ File: capability-core-string-skeleton/src/top_down.rs ]
crate::ix!();

/// A single node value covering every category, used for top‑down traversal.
#[derive(Debug, Clone)]
pub enum CoreSkeletalNode {
    Dispatch(CoreSkeletalDispatchNode),
    Aggregate(CoreSkeletalAggregateNode),
    LeafHolder(CoreSkeletalLeafHolderNode),
}

impl CoreSkeletalNode {
    /// Borrow the node’s canonical name.
    pub fn name(&self) -> &String {
        match self {
            CoreSkeletalNode::Dispatch(n)   => n.name(),
            CoreSkeletalNode::Aggregate(n)  => n.name(),
            CoreSkeletalNode::LeafHolder(n) => n.name(),
        }
    }
}

/// Extra, non‑mutating helper APIs for `CoreStringSkeleton`.
impl CoreStringSkeleton {
    /// Return **all** nodes in a single `Vec`, ordered so that every parent
    /// appears before any of its descendants, regardless of node category.
    #[instrument(level = "trace", skip_all)]
    pub fn top_down_nodes(&self) -> Vec<CoreSkeletalNode> {
        use std::collections::{HashMap, HashSet, VecDeque};

        // --- adjacency + child set ------------------------------------------------------------
        let mut child_set = HashSet::<String>::new();
        let mut adj: HashMap<String, Vec<String>> = HashMap::new();

        for n in self.dispatch_nodes() {
            let entry = adj.entry(n.name().clone()).or_default();
            for ch in n.children() {
                child_set.insert(ch.name().clone());
                entry.push(ch.name().clone());
            }
        }
        for n in self.aggregate_nodes() {
            let entry = adj.entry(n.name().clone()).or_default();
            for ch in n.children() {
                child_set.insert(ch.name().clone());
                entry.push(ch.name().clone());
            }
        }

        // --- all unique names -----------------------------------------------------------------
        let mut all_names = HashSet::<String>::new();
        for n in self.dispatch_nodes()    { all_names.insert(n.name().clone()); }
        for n in self.aggregate_nodes()   { all_names.insert(n.name().clone()); }
        for n in self.leaf_holder_nodes() { all_names.insert(n.name().clone()); }

        // --- roots ----------------------------------------------------------------------------
        let roots: Vec<String> = all_names.iter()
                                          .filter(|n| !child_set.contains(*n))
                                          .cloned()
                                          .collect();
        trace!(?roots, "identified root nodes for `top_down_nodes`");

        // --- breadth‑first depth assignment ---------------------------------------------------
        let mut depth: HashMap<String, usize> = HashMap::new();
        let mut q: VecDeque<(String, usize)> = roots.into_iter().map(|r| (r, 0)).collect();

        while let Some((name, d)) = q.pop_front() {
            if depth.contains_key(&name) { continue; }
            depth.insert(name.clone(), d);
            for ch in adj.get(&name).unwrap_or(&Vec::new()) {
                q.push_back((ch.clone(), d + 1));
            }
        }
        let fallback = depth.values().copied().max().unwrap_or(0) + 1;
        for n in &all_names { depth.entry(n.clone()).or_insert(fallback); }

        // --- build + sort combined list -------------------------------------------------------
        let mut combined: Vec<CoreSkeletalNode> = Vec::with_capacity(all_names.len());
        combined.extend(self.dispatch_nodes().iter().cloned().map(CoreSkeletalNode::Dispatch));
        combined.extend(self.aggregate_nodes().iter().cloned().map(CoreSkeletalNode::Aggregate));
        combined.extend(self.leaf_holder_nodes().iter().cloned().map(CoreSkeletalNode::LeafHolder));

        combined.sort_by(|a, b| {
            depth[a.name()]
                .cmp(&depth[b.name()])
                .then(a.name().cmp(b.name()))
        });

        combined
    }
}