kubectl_view_allocations/tree.rs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167
//! module to make tree-table or tree from a list,
//! by computing the string prefix that contains line of to link item of the list
//! like a tree (multi-root)
//!
//! ```rust
//! use kubectl_view_allocations::tree::provide_prefix;
//!
//! let items = vec![
//! "1/2",
//! "1/2/3",
//! "1/2/3/4",
//! "1/2/5",
//! "6",
//! "7",
//! "7/8",
//! "7/9",
//! ];
//!
//! let prefixes = provide_prefix(&items, |parent, item| {
//! let pi = item.split("/");
//! let pp = parent.split("/");
//! (pi.count() == pp.count() + 1) && item.starts_with(parent)
//! });
//!
//! let mut actual = String::new();
//! prefixes.iter().zip(items).for_each(|(p, i)|
//! actual.push_str(&format!("{} {}\n", p, i))
//! );
//!
//! let expected = r#" 1/2
//! ├─ 1/2/3
//! │ └─ 1/2/3/4
//! └─ 1/2/5
//! 6
//! 7
//! ├─ 7/8
//! └─ 7/9
//! "#;
//! //dbg!(&actual);
//! assert_eq!(actual, expected);
//! ```
//!
#[derive(Debug, Clone)]
struct TreeNode {
parent: Option<usize>,
level: Vec<bool>,
children: Vec<usize>,
}
fn level_to_string(level: &[bool]) -> String {
const EMPTY: &str = " ";
const EDGE: &str = " └─";
const PIPE: &str = " │ ";
const BRANCH: &str = " ├─";
let mut prefix = String::new();
if !level.is_empty() {
let last_col = level.len() - 1;
for (col, is_last_child) in level.iter().enumerate() {
let is_last_col = col == last_col;
let s = match (*is_last_child, is_last_col) {
(true, false) => EMPTY,
(true, true) => EDGE,
(false, false) => PIPE,
(false, true) => BRANCH,
};
prefix.push_str(s);
}
}
prefix
}
fn write_tree_level_of_children(nodes: &mut [TreeNode], idx: usize) {
if let Some(node) = nodes.get(idx) {
let treenode = node.clone();
let mut d = treenode.children.len();
for s in treenode.children.iter() {
if let Some(child) = nodes.get_mut(*s) {
let mut lnext = treenode.level.clone();
lnext.push(d == 1);
d -= 1;
child.level = lnext;
}
}
}
}
fn make_tree_by_reverse_depth_first<I, F>(items: &[I], is_parent_of: F) -> Vec<TreeNode>
where
F: Fn(&I, &I) -> bool,
{
let mut current: Option<usize> = None;
let mut nodes: Vec<TreeNode> = vec![];
for (i, item) in items.iter().enumerate() {
while current.is_some() && !is_parent_of(&items[current.unwrap()], item) {
current = nodes.get_mut(current.unwrap()).and_then(|n| n.parent);
}
let treenode = TreeNode {
parent: current,
level: vec![],
children: vec![],
};
if let Some(parent) = current {
if let Some(node) = nodes.get_mut(parent) {
node.children.push(i);
}
}
nodes.push(treenode);
current = Some(i);
}
nodes
}
/// Generate a list of prefix to display items as a tree (multi-root)
/// - the input should be sorted in the target display order
/// - the input order of ìtems is preserved into output
/// - the input order is used to ask for parent of item (parent should be before child)
/// - output as the same number of element than input `items`
/// - output can be zipped with input ìtems
/// - is_parent_of(maybe_parent, item)
pub fn provide_prefix<I, F>(items: &[I], is_parent_of: F) -> Vec<String>
where
F: Fn(&I, &I) -> bool,
{
let mut nodes: Vec<TreeNode> = make_tree_by_reverse_depth_first(items, is_parent_of);
//dbg!(&nodes);
for i in 0..nodes.len() {
write_tree_level_of_children(&mut nodes, i);
}
//dbg!(&nodes);
nodes.iter().map(|n| level_to_string(&n.level)).collect()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_() {
let items = vec!["1/2", "1/2/3", "1/2/3/4", "1/2/5", "6", "7", "7/8", "7/9"];
let prefixes = provide_prefix(&items, |parent, item| {
let pi = item.split('/');
let pp = parent.split('/');
(pi.count() == pp.count() + 1) && item.starts_with(parent)
});
let mut actual = String::new();
prefixes
.iter()
.zip(items)
.for_each(|(p, i)| actual.push_str(&format!("{} {}\n", p, i)));
let expected = r#" 1/2
├─ 1/2/3
│ └─ 1/2/3/4
└─ 1/2/5
6
7
├─ 7/8
└─ 7/9
"#;
//dbg!(&actual);
assert_eq!(actual, expected);
}
}