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