Skip to main content

vantage_cli_util/
table_display.rs

1use comfy_table::{Attribute, Cell, Color, Table as ComfyTable};
2use indexmap::IndexMap;
3use vantage_dataset::prelude::ReadableValueSet;
4use vantage_table::prelude::ColumnLike;
5use vantage_table::table::Table;
6use vantage_table::traits::table_source::TableSource;
7use vantage_types::{Entity, Record, TerminalRender};
8
9/// Map a color hint string to a comfy-table Color.
10fn hint_to_color(hint: &str) -> Option<Color> {
11    match hint {
12        "green" => Some(Color::Green),
13        "red" => Some(Color::Red),
14        "blue" => Some(Color::Blue),
15        "yellow" => Some(Color::Yellow),
16        "cyan" => Some(Color::Cyan),
17        "magenta" => Some(Color::Magenta),
18        _ => None,
19    }
20}
21
22/// Create a styled Cell from a TerminalRender value.
23fn render_cell<V: TerminalRender>(value: &V) -> Cell {
24    let text = value.render();
25    let mut cell = Cell::new(&text);
26
27    if let Some(hint) = value.color_hint() {
28        if hint == "dim" {
29            cell = cell.add_attribute(Attribute::Dim);
30        } else if let Some(color) = hint_to_color(hint) {
31            cell = cell.fg(color);
32        }
33    }
34
35    cell
36}
37
38/// Fetch records from a table and print them as a formatted ASCII table.
39///
40/// Reads the id field from the table's column flags to avoid duplicating
41/// the id column in the output.
42pub async fn print_table<T, E>(table: &Table<T, E>) -> vantage_core::Result<()>
43where
44    T: TableSource,
45    T::Value: TerminalRender,
46    T::Id: std::fmt::Display,
47    E: Entity<T::Value>,
48{
49    let id_field = table.id_field().map(|col| col.name().to_string());
50    let records = table.list_values().await?;
51    render_records(&records, id_field.as_deref());
52    Ok(())
53}
54
55/// Render records as a formatted ASCII table.
56///
57/// `id_field` names the column used as the record key. That column is
58/// skipped in the data columns to avoid duplication.
59pub fn render_records<Id: std::fmt::Display, V: TerminalRender>(
60    records: &IndexMap<Id, Record<V>>,
61    id_field: Option<&str>,
62) {
63    if records.is_empty() {
64        println!("No records found.");
65        return;
66    }
67
68    let first_record = records.values().next().unwrap();
69    let columns: Vec<&String> = first_record
70        .keys()
71        .filter(|k| k.as_str() != "id" && Some(k.as_str()) != id_field)
72        .collect();
73
74    let mut table = ComfyTable::new();
75
76    let mut header = vec![Cell::new("id").add_attribute(Attribute::Bold)];
77    header.extend(
78        columns
79            .iter()
80            .map(|c| Cell::new(c).add_attribute(Attribute::Bold)),
81    );
82    table.set_header(header);
83
84    for (id, record) in records {
85        let mut row = vec![Cell::new(id)];
86        for col in &columns {
87            if let Some(value) = record.get(col.as_str()) {
88                row.push(render_cell(value));
89            } else {
90                row.push(Cell::new(""));
91            }
92        }
93        table.add_row(row);
94    }
95
96    println!("{table}");
97    println!("Found {} records", records.len());
98}