ublx 0.1.5

TUI to index once, enrich with metadata, and browse a flat snapshot in a 3-pane layout with multiple modes.
//! XLSX-specific metadata: table built from a key (e.g. `sheet_stats`) and nested objects (sheet name -> { rows, columns, ... }).

use serde_json::{Map, Value};

use super::format;
use super::sections::{ContentsSection, Section};

const COL_NAME: &str = "Name";

/// If `obj` is a sheet_stats-style object (name -> { key: value, ... }), returns a Section. Title from `section_key` (e.g. `"sheet_stats"` -> `"Sheet Stats"`); column keys are `COL_NAME` plus the keys from the first nested object (e.g. `"rows"`, `"columns"`).
#[must_use]
pub fn sheet_stats_to_section(section_key: &str, obj: &Map<String, Value>) -> Section {
    let data_keys: Vec<String> = obj
        .values()
        .find_map(|v| v.as_object())
        .map(|o| o.keys().cloned().collect())
        .unwrap_or_default();
    let column_keys: Vec<String> = std::iter::once(COL_NAME.to_string())
        .chain(data_keys.clone())
        .collect();
    let columns: Vec<String> = column_keys.iter().map(|k| format::format_key(k)).collect();

    let mut entries: Vec<Value> = Vec::new();
    for (name, v) in obj {
        let Some(sub) = v.as_object() else {
            continue;
        };
        let mut row = serde_json::Map::new();
        row.insert(COL_NAME.to_string(), Value::String(name.clone()));
        for key in &data_keys {
            let val = sub.get(key).cloned().unwrap_or(Value::Null);
            row.insert(key.clone(), val);
        }
        entries.push(Value::Object(row));
    }
    Section::Contents(ContentsSection {
        title: format::format_key(section_key),
        columns,
        column_keys,
        entries,
        sub_title: false,
    })
}

/// Returns true if `obj` looks like `sheet_stats` (object with at least one value that has "rows" and "columns").
#[must_use]
pub fn is_sheet_stats(obj: &Map<String, Value>) -> bool {
    let mut it = obj.values();
    let Some(first) = it.next() else {
        return false;
    };
    let Some(sub) = first.as_object() else {
        return false;
    };
    sub.contains_key("rows") && sub.contains_key("columns")
}