tree-mumu 0.1.0-rc.2

Creates Linux `tree`-style renderings of MuMu values
Documentation
// src/share/walk.rs
//
// Convert any MuMu Value into a simple logical tree (Node) suitable for
// Linux `tree`-style rendering.

use std::collections::HashSet;
use std::sync::Arc;

use indexmap::IndexMap;
use mumu::parser::types::Value;

use super::label::leaf_preview;

#[derive(Debug, Clone)]
pub struct Node {
    /// Displayed entry name (e.g., "user" or "[0]" or root label).
    pub name: String,
    /// If Some(..) this node is a leaf and the text is printed after name (e.g., `name: text`).
    pub leaf: Option<String>,
    /// Children when this is a container node.
    pub children: Vec<Node>,
}

impl Node {
    pub fn leaf(name: String, text: String) -> Self {
        Self { name, leaf: Some(text), children: Vec::new() }
    }
    pub fn container(name: String, children: Vec<Node>) -> Self {
        Self { name, leaf: None, children }
    }
}

#[derive(Debug, Clone)]
pub struct WalkOpts {
    pub quote_strings: bool,
    pub show_types: bool,
    pub index_labels: bool,
    pub max_depth: Option<usize>, // None = unbounded
}

/// Build a Node tree from a Value.
/// `root_label` will be used as the top-level node name.
pub fn build_tree(root_label: &str, v: &Value, opts: &WalkOpts) -> Node {
    let mut seen: HashSet<usize> = HashSet::new();
    let children = descend(v, opts, 0, &mut seen);
    Node::container(root_label.to_string(), children)
}

fn descend(v: &Value, opts: &WalkOpts, depth: usize, seen: &mut HashSet<usize>) -> Vec<Node> {
    if let Some(limit) = opts.max_depth {
        if depth >= limit {
            return vec![Node::leaf("".to_string(), String::new())];
        }
    }

    match v {
        Value::KeyedArray(map) => keyed_children(map, opts, depth, seen),

        Value::MixedArray(items) => list_children(items, opts, depth, seen),

        Value::IntArray(xs) => {
            let items = xs.iter().map(|&n| Value::Int(n)).collect::<Vec<_>>();
            list_children(&items, opts, depth, seen)
        }
        Value::FloatArray(xs) => {
            let items = xs.iter().map(|&n| Value::Float(n)).collect::<Vec<_>>();
            list_children(&items, opts, depth, seen)
        }
        Value::BoolArray(xs) => {
            let items = xs.iter().map(|&b| Value::Bool(b)).collect::<Vec<_>>();
            list_children(&items, opts, depth, seen)
        }
        Value::StrArray(xs) => {
            let items = xs.iter().map(|s| Value::SingleString(s.clone())).collect::<Vec<_>>();
            list_children(&items, opts, depth, seen)
        }

        Value::Int2DArray(rows) => {
            let mut out = Vec::new();
            for (ri, row) in rows.iter().enumerate() {
                let label = if opts.index_labels { format!("[{}]", ri) } else { String::new() };
                let row_items = row.iter().map(|&n| Value::Int(n)).collect::<Vec<_>>();
                let kids = list_children(&row_items, opts, depth + 1, seen);
                out.push(Node::container(label, kids));
            }
            out
        }
        Value::Float2DArray(rows) => {
            let mut out = Vec::new();
            for (ri, row) in rows.iter().enumerate() {
                let label = if opts.index_labels { format!("[{}]", ri) } else { String::new() };
                let row_items = row.iter().map(|&n| Value::Float(n)).collect::<Vec<_>>();
                let kids = list_children(&row_items, opts, depth + 1, seen);
                out.push(Node::container(label, kids));
            }
            out
        }

        Value::Ref(cell) => {
            let raw: usize = Arc::as_ptr(cell) as usize;
            if !seen.insert(raw) {
                return vec![Node::leaf(String::new(), "".to_string())];
            }
            let inner = cell.lock().unwrap().clone();
            let mut nodes = descend(&inner, opts, depth, seen);
            seen.remove(&raw);
            if nodes.is_empty() {
                nodes.push(Node::leaf(String::new(), "".to_string()));
            }
            nodes
        }

        // Leaf-like
        other => {
            let text = leaf_preview(other, opts.quote_strings, opts.show_types);
            vec![Node::leaf(String::new(), text)]
        }
    }
}

fn keyed_children(map: &IndexMap<String, Value>, opts: &WalkOpts, depth: usize, seen: &mut HashSet<usize>) -> Vec<Node> {
    let mut out = Vec::new();
    for (k, val) in map.iter() {
        match val {
            Value::KeyedArray(_)
            | Value::MixedArray(_)
            | Value::IntArray(_) | Value::FloatArray(_) | Value::BoolArray(_) | Value::StrArray(_)
            | Value::Int2DArray(_) | Value::Float2DArray(_) => {
                let kids = descend(val, opts, depth + 1, seen);
                out.push(Node::container(k.clone(), kids));
            }
            Value::Ref(cell) => {
                let raw: usize = Arc::as_ptr(cell) as usize;
                if !seen.insert(raw) {
                    out.push(Node::leaf(k.clone(), "".to_string()));
                    continue;
                }
                let inner = cell.lock().unwrap().clone();
                let kids = match &inner {
                    Value::KeyedArray(_)
                    | Value::MixedArray(_)
                    | Value::IntArray(_) | Value::FloatArray(_) | Value::BoolArray(_) | Value::StrArray(_)
                    | Value::Int2DArray(_) | Value::Float2DArray(_) => descend(&inner, opts, depth + 1, seen),
                    _ => {
                        vec![Node::leaf(String::new(), leaf_preview(&inner, opts.quote_strings, opts.show_types))]
                    }
                };
                seen.remove(&raw);
                if kids.len() == 1 && kids[0].children.is_empty() && kids[0].name.is_empty() && kids[0].leaf.is_some() {
                    out.push(Node::leaf(k.clone(), kids[0].leaf.clone().unwrap()));
                } else {
                    out.push(Node::container(k.clone(), kids));
                }
            }
            // scalar
            _ => {
                out.push(Node::leaf(k.clone(), leaf_preview(val, opts.quote_strings, opts.show_types)));
            }
        }
    }
    out
}

fn list_children(items: &[Value], opts: &WalkOpts, depth: usize, seen: &mut HashSet<usize>) -> Vec<Node> {
    let mut out = Vec::new();
    for (i, val) in items.iter().enumerate() {
        let name = if opts.index_labels { format!("[{}]", i) } else { String::new() };
        match val {
            Value::KeyedArray(_)
            | Value::MixedArray(_)
            | Value::IntArray(_) | Value::FloatArray(_) | Value::BoolArray(_) | Value::StrArray(_)
            | Value::Int2DArray(_) | Value::Float2DArray(_) => {
                let kids = descend(val, opts, depth + 1, seen);
                out.push(Node::container(name, kids));
            }
            Value::Ref(cell) => {
                let raw: usize = Arc::as_ptr(cell) as usize;
                if !seen.insert(raw) {
                    out.push(Node::leaf(name, "".to_string()));
                    continue;
                }
                let inner = cell.lock().unwrap().clone();
                let kids = match &inner {
                    Value::KeyedArray(_)
                    | Value::MixedArray(_)
                    | Value::IntArray(_) | Value::FloatArray(_) | Value::BoolArray(_) | Value::StrArray(_)
                    | Value::Int2DArray(_) | Value::Float2DArray(_) => descend(&inner, opts, depth + 1, seen),
                    _ => vec![Node::leaf(String::new(), leaf_preview(&inner, opts.quote_strings, opts.show_types))],
                };
                seen.remove(&raw);
                if kids.len() == 1 && kids[0].children.is_empty() && kids[0].name.is_empty() && kids[0].leaf.is_some() {
                    out.push(Node::leaf(name, kids[0].leaf.clone().unwrap()));
                } else {
                    out.push(Node::container(name, kids));
                }
            }
            _ => {
                out.push(Node::leaf(name, leaf_preview(val, opts.quote_strings, opts.show_types)));
            }
        }
    }
    out
}