fusabi_tui/bindings/
mod.rs1use anyhow::Result;
41use fusabi::Engine;
42use fusabi::Value;
43use fusabi_vm::VmError;
44use std::cell::RefCell;
45use std::rc::Rc;
46
47use crate::formatting::{format_bytes, format_latency, format_number};
48use std::time::Duration;
49
50pub mod specs;
52
53#[derive(Clone)]
55pub struct FusabiTuiModule;
56
57impl FusabiTuiModule {
58 pub fn new() -> Self {
60 Self
61 }
62
63 pub fn register(&self, engine: &mut Engine) -> Result<()> {
81 engine.register_raw("tui_format_number", |_vm, args| {
87 if args.len() != 1 {
88 return Err(VmError::Runtime(
89 "tui_format_number expects 1 argument: number (int)".into(),
90 ));
91 }
92 let num = args[0].as_int().ok_or(VmError::TypeMismatch {
93 expected: "int",
94 got: args[0].type_name(),
95 })? as u64;
96
97 let formatted = format_number(num);
98 Ok(Value::Str(formatted))
99 });
100
101 engine.register_raw("tui_format_bytes", |_vm, args| {
103 if args.len() != 1 {
104 return Err(VmError::Runtime(
105 "tui_format_bytes expects 1 argument: bytes (int)".into(),
106 ));
107 }
108 let bytes = args[0].as_int().ok_or(VmError::TypeMismatch {
109 expected: "int",
110 got: args[0].type_name(),
111 })? as u64;
112
113 let formatted = format_bytes(bytes);
114 Ok(Value::Str(formatted))
115 });
116
117 engine.register_raw("tui_format_latency", |_vm, args| {
119 if args.len() != 1 {
120 return Err(VmError::Runtime(
121 "tui_format_latency expects 1 argument: microseconds (int)".into(),
122 ));
123 }
124 let us = args[0].as_int().ok_or(VmError::TypeMismatch {
125 expected: "int",
126 got: args[0].type_name(),
127 })? as u64;
128
129 let formatted = format_latency(us);
130 Ok(Value::Str(formatted))
131 });
132
133 engine.register_raw("tui_format_duration", |_vm, args| {
135 if args.len() != 1 {
136 return Err(VmError::Runtime(
137 "tui_format_duration expects 1 argument: seconds (int)".into(),
138 ));
139 }
140 let secs = args[0].as_int().ok_or(VmError::TypeMismatch {
141 expected: "int",
142 got: args[0].type_name(),
143 })? as u64;
144
145 let formatted = crate::formatting::format_duration(Duration::from_secs(secs));
146 Ok(Value::Str(formatted))
147 });
148
149 engine.register_raw("tui_table_spec_new", |_vm, args| {
152 if !args.is_empty() {
153 return Err(VmError::Runtime(
154 "tui_table_spec_new expects no arguments".into(),
155 ));
156 }
157
158 let mut fields = std::collections::HashMap::new();
159 fields.insert(
160 "columns".to_string(),
161 Value::Array(Rc::new(RefCell::new(vec![]))),
162 );
163 fields.insert(
164 "rows".to_string(),
165 Value::Array(Rc::new(RefCell::new(vec![]))),
166 );
167 fields.insert("title".to_string(), Value::Unit);
168 fields.insert("borders".to_string(), Value::Bool(true));
169
170 Ok(Value::Record(Rc::new(RefCell::new(fields))))
171 });
172
173 engine.register_raw("tui_table_add_column", |_vm, args| {
176 if args.len() != 3 {
177 return Err(VmError::Runtime(
178 "tui_table_add_column expects 3 arguments: spec (record), header (string), width (int)".into(),
179 ));
180 }
181
182 let spec = args[0].as_record().ok_or(VmError::TypeMismatch {
183 expected: "record",
184 got: args[0].type_name(),
185 })?;
186
187 let header = args[1].as_str().ok_or(VmError::TypeMismatch {
188 expected: "string",
189 got: args[1].type_name(),
190 })?;
191
192 let width = args[2].as_int().ok_or(VmError::TypeMismatch {
193 expected: "int",
194 got: args[2].type_name(),
195 })?;
196
197 let mut col_fields = std::collections::HashMap::new();
199 col_fields.insert("header".to_string(), Value::Str(header.to_string()));
200 col_fields.insert("width".to_string(), Value::Int(width));
201 let col = Value::Record(Rc::new(RefCell::new(col_fields)));
202
203 let spec_mut = spec.borrow_mut();
205 if let Some(columns_val) = spec_mut.get("columns") {
206 if let Some(columns_arr) = columns_val.as_array() {
207 columns_arr.borrow_mut().push(col);
208 }
209 }
210
211 drop(spec_mut);
212 Ok(Value::Record(spec.clone()))
213 });
214
215 engine.register_raw("tui_table_add_row", |_vm, args| {
218 if args.len() != 2 {
219 return Err(VmError::Runtime(
220 "tui_table_add_row expects 2 arguments: spec (record), cells (array)".into(),
221 ));
222 }
223
224 let spec = args[0].as_record().ok_or(VmError::TypeMismatch {
225 expected: "record",
226 got: args[0].type_name(),
227 })?;
228
229 let cells = args[1].as_array().ok_or(VmError::TypeMismatch {
230 expected: "array",
231 got: args[1].type_name(),
232 })?;
233
234 let spec_mut = spec.borrow_mut();
236 if let Some(rows_val) = spec_mut.get("rows") {
237 if let Some(rows_arr) = rows_val.as_array() {
238 rows_arr.borrow_mut().push(Value::Array(cells.clone()));
239 }
240 }
241
242 drop(spec_mut);
243 Ok(Value::Record(spec.clone()))
244 });
245
246 engine.register_raw("tui_table_set_title", |_vm, args| {
248 if args.len() != 2 {
249 return Err(VmError::Runtime(
250 "tui_table_set_title expects 2 arguments: spec (record), title (string)".into(),
251 ));
252 }
253
254 let spec = args[0].as_record().ok_or(VmError::TypeMismatch {
255 expected: "record",
256 got: args[0].type_name(),
257 })?;
258
259 let title = args[1].as_str().ok_or(VmError::TypeMismatch {
260 expected: "string",
261 got: args[1].type_name(),
262 })?;
263
264 spec.borrow_mut()
265 .insert("title".to_string(), Value::Str(title.to_string()));
266 Ok(Value::Record(spec.clone()))
267 });
268
269 Ok(())
270 }
271}
272
273impl Default for FusabiTuiModule {
274 fn default() -> Self {
275 Self::new()
276 }
277}