Skip to main content

shape_runtime/
content_dispatch.rs

1//! Content trait dispatch — typed-handle subset.
2//!
3//! Phase 2b minimization: the broad ValueWord-typed `render_as_content` /
4//! `render_as_content_for` dispatch chain (~600 LoC) was deleted along
5//! with the polymorphic-the deleted tag_bits module it depended on (tag_bits,
6//! ValueBits, ValueWordDisplay). The kind-threaded replacement lands
7//! when shape-vm's content rendering is rebuilt on top of `slot_to_wire`
8//! / `slot_extract_content` (the wire_conversion entry points).
9//!
10//! What remains: typed-handle helpers used by `wire_conversion` and the
11//! REPL output path — `datatable_to_content_node`, `arrow_type_label`,
12//! `arrow_cell_display`, plus the user-resolver hooks (kept as
13//! placeholders since they're write-only at the moment — the readers
14//! were in the deleted dispatch chain).
15
16use crate::content_renderer::RendererCapabilities;
17use shape_value::DataTable;
18use shape_value::content::{BorderStyle, ContentNode, ContentTable};
19
20/// Well-known adapter names for ContentFor<Adapter> dispatch.
21pub mod adapters {
22    pub const TERMINAL: &str = "Terminal";
23    pub const HTML: &str = "Html";
24    pub const MARKDOWN: &str = "Markdown";
25    pub const JSON: &str = "Json";
26    pub const PLAIN: &str = "Plain";
27}
28
29pub fn capabilities_for_adapter(adapter: &str) -> RendererCapabilities {
30    match adapter {
31        adapters::TERMINAL => RendererCapabilities::terminal(),
32        adapters::HTML => RendererCapabilities::html(),
33        adapters::MARKDOWN => RendererCapabilities::markdown(),
34        adapters::JSON => RendererCapabilities::json(),
35        _ => RendererCapabilities::plain(),
36    }
37}
38
39/// Render a `DataTable` as a structured `ContentNode::Table`. Used by
40/// `wire_conversion::slot_extract_content` and the REPL output path.
41pub fn datatable_to_content_node(dt: &DataTable, max_rows: Option<usize>) -> ContentNode {
42    use arrow_array::Array;
43
44    let headers = dt.column_names();
45    let total = dt.row_count();
46    let limit = max_rows.unwrap_or(total).min(total);
47
48    let schema = dt.inner().schema();
49    let column_types: Vec<String> = schema
50        .fields()
51        .iter()
52        .map(|f| arrow_type_label(f.data_type()))
53        .collect();
54
55    let batch = dt.inner();
56    let mut rows = Vec::with_capacity(limit);
57    for row_idx in 0..limit {
58        let mut cells = Vec::with_capacity(headers.len());
59        for col_idx in 0..headers.len() {
60            let col = batch.column(col_idx);
61            let text = if col.is_null(row_idx) {
62                "null".to_string()
63            } else {
64                arrow_cell_display(col.as_ref(), row_idx)
65            };
66            cells.push(ContentNode::plain(text));
67        }
68        rows.push(cells);
69    }
70
71    ContentNode::Table(ContentTable {
72        headers,
73        rows,
74        border: BorderStyle::default(),
75        max_rows: None,
76        column_types: Some(column_types),
77        total_rows: if total > limit { Some(total) } else { None },
78        sortable: true,
79    })
80}
81
82fn arrow_type_label(dt: &arrow_schema::DataType) -> String {
83    use arrow_schema::DataType;
84    match dt {
85        DataType::Int8 => "i8".to_string(),
86        DataType::Int16 => "i16".to_string(),
87        DataType::Int32 => "i32".to_string(),
88        DataType::Int64 => "int".to_string(),
89        DataType::UInt8 => "u8".to_string(),
90        DataType::UInt16 => "u16".to_string(),
91        DataType::UInt32 => "u32".to_string(),
92        DataType::UInt64 => "u64".to_string(),
93        DataType::Float32 => "f32".to_string(),
94        DataType::Float64 => "number".to_string(),
95        DataType::Boolean => "bool".to_string(),
96        DataType::Utf8 | DataType::LargeUtf8 => "string".to_string(),
97        DataType::Date32 | DataType::Date64 => "date".to_string(),
98        DataType::Timestamp(_, _) => "timestamp".to_string(),
99        other => format!("{:?}", other),
100    }
101}
102
103fn arrow_cell_display(array: &dyn arrow_array::Array, index: usize) -> String {
104    use arrow_array::cast::AsArray;
105    use arrow_schema::DataType;
106
107    match array.data_type() {
108        DataType::Int8 => array.as_primitive::<arrow_array::types::Int8Type>().value(index).to_string(),
109        DataType::Int16 => array.as_primitive::<arrow_array::types::Int16Type>().value(index).to_string(),
110        DataType::Int32 => array.as_primitive::<arrow_array::types::Int32Type>().value(index).to_string(),
111        DataType::Int64 => array.as_primitive::<arrow_array::types::Int64Type>().value(index).to_string(),
112        DataType::UInt8 => array.as_primitive::<arrow_array::types::UInt8Type>().value(index).to_string(),
113        DataType::UInt16 => array.as_primitive::<arrow_array::types::UInt16Type>().value(index).to_string(),
114        DataType::UInt32 => array.as_primitive::<arrow_array::types::UInt32Type>().value(index).to_string(),
115        DataType::UInt64 => array.as_primitive::<arrow_array::types::UInt64Type>().value(index).to_string(),
116        DataType::Float32 => array.as_primitive::<arrow_array::types::Float32Type>().value(index).to_string(),
117        DataType::Float64 => array.as_primitive::<arrow_array::types::Float64Type>().value(index).to_string(),
118        DataType::Boolean => array.as_boolean().value(index).to_string(),
119        DataType::Utf8 => array.as_string::<i32>().value(index).to_string(),
120        DataType::LargeUtf8 => array.as_string::<i64>().value(index).to_string(),
121        _ => format!("{:?}", array.slice(index, 1)),
122    }
123}