1pub mod data;
53pub mod group;
54pub mod plain;
55pub mod rules;
56pub mod sort;
57pub mod stream;
58pub mod transform;
59pub mod virtualized;
60
61use std::fmt;
62use std::sync::Arc;
63
64use rich::r#box::Box as BoxSet;
65use rich::{ColumnOptions, Console, Justify, Style, Table, Text};
66
67pub use data::TableData;
68pub use group::{Aggregate, Group, GroupBy};
69pub use plain::{PlainRows, PlainTree};
70pub use rules::{ColumnRef, Comparison, ResolvedRules, RuleError, StyleRule, StyleRules, Target};
71pub use sort::{Compare, Order, SortKey};
72pub use stream::{RenderStats, StreamingTable, Window};
73pub use virtualized::{FnCells, FnRows, Page, VirtualRows, VirtualTable};
74
75pub const STYLES: &[(&str, &str)] = &[
80 ("table.group", "bold"),
81 ("table.aggregate", "italic"),
82 ("table.sort_indicator", "cyan"),
83 ("table.more", "dim"),
84 ("table.position", "dim"),
85 ("table.null", "dim italic"),
86 ("table.row_number", "dim"),
87];
88
89pub(crate) fn style(console: &Console, key: &str) -> Style {
91 let fallback = STYLES
92 .iter()
93 .find(|(name, _)| *name == key)
94 .map_or("", |(_, spec)| *spec);
95 crate::event::theme_style(console, key, fallback)
96}
97
98#[derive(Clone, Debug, Default)]
103pub enum Value {
104 #[default]
106 Null,
107 Int(i64),
109 Float(f64),
111 Str(String),
113 Text(Text),
115}
116
117impl Value {
118 pub fn is_empty(&self) -> bool {
120 match self {
121 Value::Null => true,
122 Value::Str(s) => s.is_empty(),
123 Value::Text(t) => t.plain().is_empty(),
124 Value::Int(_) | Value::Float(_) => false,
125 }
126 }
127
128 pub fn as_f64(&self) -> Option<f64> {
130 match self {
131 Value::Int(n) => Some(*n as f64),
132 Value::Float(f) => Some(*f),
133 _ => None,
134 }
135 }
136
137 pub fn plain(&self) -> String {
139 match self {
140 Value::Null => String::new(),
141 Value::Int(n) => n.to_string(),
142 Value::Float(f) => f.to_string(),
143 Value::Str(s) => s.clone(),
144 Value::Text(t) => t.plain().to_string(),
145 }
146 }
147
148 pub fn to_text(&self) -> Text {
151 match self {
152 Value::Text(t) => t.clone(),
153 other => Text::new(other.plain()),
154 }
155 }
156}
157
158impl PartialEq for Value {
163 fn eq(&self, other: &Self) -> bool {
164 match (self, other) {
165 (Value::Null, Value::Null) => true,
166 (Value::Int(a), Value::Int(b)) => a == b,
167 (Value::Float(a), Value::Float(b)) => a.to_bits() == b.to_bits(),
168 (Value::Str(a), Value::Str(b)) => a == b,
169 _ => false,
170 }
171 }
172}
173
174impl From<&str> for Value {
175 fn from(s: &str) -> Self {
176 Value::Str(s.to_string())
177 }
178}
179
180impl From<String> for Value {
181 fn from(s: String) -> Self {
182 Value::Str(s)
183 }
184}
185
186impl From<Text> for Value {
187 fn from(t: Text) -> Self {
188 Value::Text(t)
189 }
190}
191
192impl From<f64> for Value {
193 fn from(f: f64) -> Self {
194 Value::Float(f)
195 }
196}
197
198macro_rules! int_value {
199 ($($t:ty),*) => {$(
200 impl From<$t> for Value {
201 fn from(n: $t) -> Self {
202 i64::try_from(n).map_or(Value::Float(n as f64), Value::Int)
203 }
204 }
205 )*};
206}
207int_value!(i8, i16, i32, i64, u8, u16, u32, u64, usize, isize);
208
209impl<T: Into<Value>> From<Option<T>> for Value {
210 fn from(v: Option<T>) -> Self {
211 v.map_or(Value::Null, Into::into)
212 }
213}
214
215pub type Formatter = Arc<dyn Fn(&Value) -> Text + Send + Sync>;
217
218#[derive(Clone)]
234pub struct Column {
235 header: String,
236 options: ColumnOptions,
237 formatter: Option<Formatter>,
238}
239
240impl fmt::Debug for Column {
241 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
242 f.debug_struct("Column")
243 .field("header", &self.header)
244 .field("options", &self.options)
245 .field("formatter", &self.formatter.is_some())
246 .finish()
247 }
248}
249
250impl Column {
251 pub fn new(header: impl Into<String>) -> Self {
253 Column {
254 header: header.into(),
255 options: ColumnOptions::default(),
256 formatter: None,
257 }
258 }
259
260 #[must_use]
262 pub fn justify(mut self, justify: Justify) -> Self {
263 self.options.justify = justify;
264 self
265 }
266
267 #[must_use]
269 pub fn options(mut self, options: ColumnOptions) -> Self {
270 self.options = options;
271 self
272 }
273
274 #[must_use]
277 pub fn format(mut self, formatter: impl Fn(&Value) -> Text + Send + Sync + 'static) -> Self {
278 self.formatter = Some(Arc::new(formatter));
279 self
280 }
281
282 pub fn header(&self) -> &str {
284 &self.header
285 }
286
287 pub fn column_options(&self) -> &ColumnOptions {
289 &self.options
290 }
291
292 pub fn cell(&self, value: &Value) -> Text {
294 match &self.formatter {
295 Some(format) => format(value),
296 None => value.to_text(),
297 }
298 }
299}
300
301pub(crate) fn headers(console: &Console, columns: &[Column], keys: &[SortKey]) -> Vec<Text> {
304 let ascii = console.ascii_only();
305 columns
306 .iter()
307 .enumerate()
308 .map(|(index, column)| {
309 let mut text = Text::new(column.header.clone());
310 if let Some(priority) = keys.iter().position(|k| k.column == index) {
311 let mut mark = sort::indicator(keys[priority].order, ascii).to_string();
312 if keys.len() > 1 {
313 mark.push_str(&(priority + 1).to_string());
314 }
315 text.append(" ", None);
316 text.append(&mark, Some(style(console, "table.sort_indicator").into()));
317 }
318 text
319 })
320 .collect()
321}
322
323#[derive(Clone, Debug)]
325pub(crate) struct Frame {
326 pub title: Option<String>,
327 pub caption: Option<String>,
328 pub box_set: Option<BoxSet>,
330 pub show_edge: bool,
331 pub expand: bool,
332 pub border_style: Style,
333}
334
335impl Default for Frame {
336 fn default() -> Self {
337 Frame {
338 title: None,
339 caption: None,
340 box_set: Some(rich::r#box::HEAVY_HEAD),
341 show_edge: true,
342 expand: false,
343 border_style: Style::new(),
344 }
345 }
346}
347
348impl Frame {
349 pub fn table(
351 &self,
352 columns: &[Column],
353 headers: &[Text],
354 show_header: bool,
355 annotations: bool,
356 ) -> Table {
357 let mut table = Table::new()
358 .show_header(show_header)
359 .show_edge(self.show_edge)
360 .expand(self.expand)
361 .border_style(self.border_style.clone());
362 table = match self.box_set {
363 Some(box_set) => table.box_set(box_set),
364 None => table.without_box(),
365 };
366 if annotations {
367 if let Some(title) = &self.title {
368 table = table.title(title.clone());
369 }
370 if let Some(caption) = &self.caption {
371 table = table.caption(caption.clone());
372 }
373 }
374 for (column, header) in columns.iter().zip(headers) {
375 table.add_column_with(header.clone(), column.options.clone());
376 }
377 table
378 }
379
380 pub fn edge_lines(&self) -> usize {
382 usize::from(self.box_set.is_some() && self.show_edge)
383 }
384}
385
386macro_rules! frame_builders {
388 ([$($g:ident)?] $ty:ty) => {
389 impl$(<$g>)? $ty {
390 pub fn title(mut self, title: impl Into<String>) -> Self {
393 self.frame.title = Some(title.into());
394 self
395 }
396 pub fn caption(mut self, caption: impl Into<String>) -> Self {
398 self.frame.caption = Some(caption.into());
399 self
400 }
401 pub fn box_set(mut self, box_set: rich::r#box::Box) -> Self {
403 self.frame.box_set = Some(box_set);
404 self
405 }
406 pub fn without_box(mut self) -> Self {
408 self.frame.box_set = None;
409 self
410 }
411 pub fn show_edge(mut self, show: bool) -> Self {
413 self.frame.show_edge = show;
414 self
415 }
416 pub fn expand(mut self, expand: bool) -> Self {
418 self.frame.expand = expand;
419 self
420 }
421 pub fn border_style(mut self, style: rich::Style) -> Self {
423 self.frame.border_style = style;
424 self
425 }
426 }
427 };
428}
429pub(crate) use frame_builders;