1pub mod data;
46pub mod group;
47pub mod sort;
48pub mod stream;
49pub mod transform;
50
51use std::fmt;
52use std::sync::Arc;
53
54use rich::r#box::Box as BoxSet;
55use rich::{ColumnOptions, Console, Justify, Style, Table, Text};
56
57pub use data::TableData;
58pub use group::{Aggregate, Group, GroupBy};
59pub use sort::{Compare, Order, SortKey};
60pub use stream::{RenderStats, StreamingTable, Window};
61
62pub const STYLES: &[(&str, &str)] = &[
67 ("table.group", "bold"),
68 ("table.aggregate", "italic"),
69 ("table.sort_indicator", "cyan"),
70 ("table.more", "dim"),
71];
72
73pub(crate) fn style(console: &Console, key: &str) -> Style {
75 let fallback = STYLES
76 .iter()
77 .find(|(name, _)| *name == key)
78 .map_or("", |(_, spec)| *spec);
79 crate::event::theme_style(console, key, fallback)
80}
81
82#[derive(Clone, Debug, Default)]
87pub enum Value {
88 #[default]
90 Null,
91 Int(i64),
93 Float(f64),
95 Str(String),
97 Text(Text),
99}
100
101impl Value {
102 pub fn is_empty(&self) -> bool {
104 match self {
105 Value::Null => true,
106 Value::Str(s) => s.is_empty(),
107 Value::Text(t) => t.plain().is_empty(),
108 Value::Int(_) | Value::Float(_) => false,
109 }
110 }
111
112 pub fn as_f64(&self) -> Option<f64> {
114 match self {
115 Value::Int(n) => Some(*n as f64),
116 Value::Float(f) => Some(*f),
117 _ => None,
118 }
119 }
120
121 pub fn plain(&self) -> String {
123 match self {
124 Value::Null => String::new(),
125 Value::Int(n) => n.to_string(),
126 Value::Float(f) => f.to_string(),
127 Value::Str(s) => s.clone(),
128 Value::Text(t) => t.plain().to_string(),
129 }
130 }
131
132 pub fn to_text(&self) -> Text {
135 match self {
136 Value::Text(t) => t.clone(),
137 other => Text::new(other.plain()),
138 }
139 }
140}
141
142impl PartialEq for Value {
147 fn eq(&self, other: &Self) -> bool {
148 match (self, other) {
149 (Value::Null, Value::Null) => true,
150 (Value::Int(a), Value::Int(b)) => a == b,
151 (Value::Float(a), Value::Float(b)) => a.to_bits() == b.to_bits(),
152 (Value::Str(a), Value::Str(b)) => a == b,
153 _ => false,
154 }
155 }
156}
157
158impl From<&str> for Value {
159 fn from(s: &str) -> Self {
160 Value::Str(s.to_string())
161 }
162}
163
164impl From<String> for Value {
165 fn from(s: String) -> Self {
166 Value::Str(s)
167 }
168}
169
170impl From<Text> for Value {
171 fn from(t: Text) -> Self {
172 Value::Text(t)
173 }
174}
175
176impl From<f64> for Value {
177 fn from(f: f64) -> Self {
178 Value::Float(f)
179 }
180}
181
182macro_rules! int_value {
183 ($($t:ty),*) => {$(
184 impl From<$t> for Value {
185 fn from(n: $t) -> Self {
186 i64::try_from(n).map_or(Value::Float(n as f64), Value::Int)
187 }
188 }
189 )*};
190}
191int_value!(i8, i16, i32, i64, u8, u16, u32, u64, usize, isize);
192
193impl<T: Into<Value>> From<Option<T>> for Value {
194 fn from(v: Option<T>) -> Self {
195 v.map_or(Value::Null, Into::into)
196 }
197}
198
199pub type Formatter = Arc<dyn Fn(&Value) -> Text + Send + Sync>;
201
202#[derive(Clone)]
218pub struct Column {
219 header: String,
220 options: ColumnOptions,
221 formatter: Option<Formatter>,
222}
223
224impl fmt::Debug for Column {
225 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
226 f.debug_struct("Column")
227 .field("header", &self.header)
228 .field("options", &self.options)
229 .field("formatter", &self.formatter.is_some())
230 .finish()
231 }
232}
233
234impl Column {
235 pub fn new(header: impl Into<String>) -> Self {
237 Column {
238 header: header.into(),
239 options: ColumnOptions::default(),
240 formatter: None,
241 }
242 }
243
244 pub fn justify(mut self, justify: Justify) -> Self {
246 self.options.justify = justify;
247 self
248 }
249
250 pub fn options(mut self, options: ColumnOptions) -> Self {
252 self.options = options;
253 self
254 }
255
256 pub fn format(mut self, formatter: impl Fn(&Value) -> Text + Send + Sync + 'static) -> Self {
259 self.formatter = Some(Arc::new(formatter));
260 self
261 }
262
263 pub fn header(&self) -> &str {
265 &self.header
266 }
267
268 pub fn column_options(&self) -> &ColumnOptions {
270 &self.options
271 }
272
273 pub fn cell(&self, value: &Value) -> Text {
275 match &self.formatter {
276 Some(format) => format(value),
277 None => value.to_text(),
278 }
279 }
280}
281
282pub(crate) fn headers(console: &Console, columns: &[Column], keys: &[SortKey]) -> Vec<Text> {
285 let ascii = console.ascii_only();
286 columns
287 .iter()
288 .enumerate()
289 .map(|(index, column)| {
290 let mut text = Text::new(column.header.clone());
291 if let Some(priority) = keys.iter().position(|k| k.column == index) {
292 let mut mark = sort::indicator(keys[priority].order, ascii).to_string();
293 if keys.len() > 1 {
294 mark.push_str(&(priority + 1).to_string());
295 }
296 text.append(" ", None);
297 text.append(&mark, Some(style(console, "table.sort_indicator").into()));
298 }
299 text
300 })
301 .collect()
302}
303
304#[derive(Clone, Debug)]
306pub(crate) struct Frame {
307 pub title: Option<String>,
308 pub caption: Option<String>,
309 pub box_set: Option<BoxSet>,
311 pub show_edge: bool,
312 pub expand: bool,
313 pub border_style: Style,
314}
315
316impl Default for Frame {
317 fn default() -> Self {
318 Frame {
319 title: None,
320 caption: None,
321 box_set: Some(rich::r#box::HEAVY_HEAD),
322 show_edge: true,
323 expand: false,
324 border_style: Style::new(),
325 }
326 }
327}
328
329impl Frame {
330 pub fn table(
332 &self,
333 columns: &[Column],
334 headers: &[Text],
335 show_header: bool,
336 annotations: bool,
337 ) -> Table {
338 let mut table = Table::new()
339 .show_header(show_header)
340 .show_edge(self.show_edge)
341 .expand(self.expand)
342 .border_style(self.border_style.clone());
343 table = match self.box_set {
344 Some(box_set) => table.box_set(box_set),
345 None => table.without_box(),
346 };
347 if annotations {
348 if let Some(title) = &self.title {
349 table = table.title(title.clone());
350 }
351 if let Some(caption) = &self.caption {
352 table = table.caption(caption.clone());
353 }
354 }
355 for (column, header) in columns.iter().zip(headers) {
356 table.add_column_with(header.clone(), column.options.clone());
357 }
358 table
359 }
360
361 pub fn edge_lines(&self) -> usize {
363 usize::from(self.box_set.is_some() && self.show_edge)
364 }
365}
366
367macro_rules! frame_builders {
369 ([$($g:ident)?] $ty:ty) => {
370 impl$(<$g>)? $ty {
371 pub fn title(mut self, title: impl Into<String>) -> Self {
374 self.frame.title = Some(title.into());
375 self
376 }
377 pub fn caption(mut self, caption: impl Into<String>) -> Self {
379 self.frame.caption = Some(caption.into());
380 self
381 }
382 pub fn box_set(mut self, box_set: rich::r#box::Box) -> Self {
384 self.frame.box_set = Some(box_set);
385 self
386 }
387 pub fn without_box(mut self) -> Self {
389 self.frame.box_set = None;
390 self
391 }
392 pub fn show_edge(mut self, show: bool) -> Self {
394 self.frame.show_edge = show;
395 self
396 }
397 pub fn expand(mut self, expand: bool) -> Self {
399 self.frame.expand = expand;
400 self
401 }
402 pub fn border_style(mut self, style: rich::Style) -> Self {
404 self.frame.border_style = style;
405 self
406 }
407 }
408 };
409}
410pub(crate) use frame_builders;