1mod datatype;
26use calm_io::stdout;
27use calm_io::stdoutln;
28use owo_colors::OwoColorize;
29
30pub struct TableConfig {
31 std_color: [u8; 3],
32 neg_num_color: [u8; 3],
33 na_color: [u8; 3],
34 meta_color: [u8; 3],
35 header_color: [u8; 3],
36 title_option: String,
37 footer_option: String,
38 display_meta: bool,
39 extend_option: bool,
40 line_counter: bool,
41 debug_mode: bool,
42 is_tty: bool,
43 is_force_color: bool,
44 term_tuple: (u16, u16),
45 sigfig: i64,
46 lower_column_width: usize,
47 upper_column_width: usize,
48 row_display_option: usize,
49}
50
51const NORD_META_COLOR: [u8; 3] = [143, 188, 187];
52const NORD_HEADER_COLOR: [u8; 3] = [94, 129, 172];
53const NORD_STD_COLOR: [u8; 3] = [216, 222, 233];
54const NORD_NA_COLOR: [u8; 3] = [191, 97, 106];
55const NORD_NEG_NUM_COLOR: [u8; 3] = [208, 135, 112];
56
57impl Default for TableConfig {
58 fn default() -> Self {
59 Self {
60 std_color: NORD_STD_COLOR,
61 neg_num_color: NORD_NEG_NUM_COLOR,
62 na_color: NORD_NA_COLOR,
63 meta_color: NORD_META_COLOR,
64 header_color: NORD_HEADER_COLOR,
65 title_option: Default::default(),
66 footer_option: Default::default(),
67 display_meta: false,
68 extend_option: true,
69 line_counter: false,
70 debug_mode: false,
71 is_tty: atty::is(atty::Stream::Stdout),
72 is_force_color: false,
73 term_tuple: crossterm::terminal::size().unwrap(),
74 sigfig: 3,
75 lower_column_width: 2,
76 upper_column_width: 50,
77 row_display_option: 25,
78 }
79 }
80}
81
82pub fn display_table(rdr: &[Vec<String>], config: TableConfig) {
83 let cols: usize = rdr[0].len();
90 let rows_in_file: usize = rdr.len();
91 let rows: usize = if config.extend_option {
92 rdr.len().min(rows_in_file + 1)
93 } else {
94 rdr.len().min(config.row_display_option + 1)
95 };
96
97 let rows_remaining: usize = rows_in_file - rows;
98 let ellipsis = '\u{2026}'.to_string();
99 let row_remaining_text: String = format!("{ellipsis} with {rows_remaining} more rows");
100
101 let mut v: Vec<Vec<&str>> = Vec::new(); for col in 0..cols {
104 let column = rdr
105 .iter()
106 .take(rows)
107 .map(|row| row.get(col).unwrap().as_str())
108 .collect();
109 v.push(column)
110 }
111
112 if config.debug_mode {
113 println!("{:?}", "v");
114 println!("{v:?}");
115 }
116
117 if config.debug_mode {
118 let mut vec_datatypes = Vec::with_capacity(cols);
120 for column in &v {
121 vec_datatypes.push(datatype::get_col_data_type(column))
122 }
123 println!("{:?}", "vec_datatypes");
124 println!("{vec_datatypes:?}");
125 }
126
127 let vf: Vec<Vec<String>> = v
129 .iter()
130 .map(|col| {
131 datatype::format_strings(
132 col,
133 config.lower_column_width,
134 config.upper_column_width,
135 config.sigfig,
136 )
137 })
138 .collect();
139
140 if config.debug_mode {
141 println!("{:?}", "Transposed Vector of Elements");
142 println!("{v:?}");
143 println!("{:?}", "Formatted: Vector of Elements");
144 println!("{vf:?}");
145 }
146
147 let mut vp = Vec::new();
149 for r in 0..rows {
150 let row = vf.iter().map(|col| col[r].to_string()).collect();
151 vp.push(row);
152 }
153
154 let num_cols_to_print = if config.extend_option {
155 cols
156 } else {
157 get_num_cols_to_print(cols, vp.clone(), config.term_tuple)
158 };
159
160 if config.display_meta {
162 let meta_text = "tv dim:";
163 let div = "x";
164 let _ = match stdout!("{: <6}", "") {
165 Ok(_) => Ok(()),
166 Err(e) => match e.kind() {
167 std::io::ErrorKind::BrokenPipe => Ok(()),
168 _ => Err(e),
169 },
170 };
171 if config.is_tty || config.is_force_color {
172 let _ = match stdoutln!(
173 "{} {} {} {}",
174 meta_text.truecolor(
175 config.meta_color[0],
176 config.meta_color[1],
177 config.meta_color[2]
178 ),
179 (rows_in_file - 1).truecolor(
180 config.meta_color[0],
181 config.meta_color[1],
182 config.meta_color[2]
183 ),
184 div.truecolor(
185 config.meta_color[0],
186 config.meta_color[1],
187 config.meta_color[2]
188 ),
189 (cols).truecolor(
190 config.meta_color[0],
191 config.meta_color[1],
192 config.meta_color[2]
193 ),
194 ) {
195 Ok(_) => Ok(()),
196 Err(e) => match e.kind() {
197 std::io::ErrorKind::BrokenPipe => Ok(()),
198 _ => Err(e),
199 },
200 };
201 } else {
202 let _ = match stdoutln!("{} {} {} {}", meta_text, rows_in_file - 1, div, cols) {
203 Ok(_) => Ok(()),
204 Err(e) => match e.kind() {
205 std::io::ErrorKind::BrokenPipe => Ok(()),
206 _ => Err(e),
207 },
208 };
209 }
210 }
211 if !datatype::is_na(&config.title_option) {
213 let _ = match stdout!("{: <6}", "") {
214 Ok(_) => Ok(()),
215 Err(e) => match e.kind() {
216 std::io::ErrorKind::BrokenPipe => Ok(()),
217 _ => Err(e),
218 },
219 };
220 if config.is_tty || config.is_force_color {
221 let _ = match stdoutln!(
222 "{}",
223 config
224 .title_option
225 .truecolor(
226 config.meta_color[0],
227 config.meta_color[1],
228 config.meta_color[2]
229 )
230 .underline()
231 .bold()
232 ) {
233 Ok(_) => Ok(()),
234 Err(e) => match e.kind() {
235 std::io::ErrorKind::BrokenPipe => Ok(()),
236 _ => Err(e),
237 },
238 };
239 } else {
240 let _ = match stdoutln!("{}", config.title_option) {
241 Ok(_) => Ok(()),
242 Err(e) => match e.kind() {
243 std::io::ErrorKind::BrokenPipe => Ok(()),
244 _ => Err(e),
245 },
246 };
247 }
248 }
249
250 if config.line_counter {
252 let _ = match stdout!("{: <6}", "") {
253 Ok(_) => Ok(()),
254 Err(e) => match e.kind() {
255 std::io::ErrorKind::BrokenPipe => Ok(()),
256 _ => Err(e),
257 },
258 };
259 }
260 for col in 0..num_cols_to_print {
262 let text = vp[0].get(col).unwrap().to_string();
263 if config.is_tty || config.is_force_color {
264 let _ = match stdout!(
265 "{}",
266 text
267 .truecolor(
268 config.header_color[0],
269 config.header_color[1],
270 config.header_color[2]
271 )
272 .bold()
273 ) {
274 Ok(_) => Ok(()),
275 Err(e) => match e.kind() {
276 std::io::ErrorKind::BrokenPipe => Ok(()),
277 _ => Err(e),
278 },
279 };
280 } else {
281 let _ = match stdout!("{}", text) {
282 Ok(_) => Ok(()),
283 Err(e) => match e.kind() {
284 std::io::ErrorKind::BrokenPipe => Ok(()),
285 _ => Err(e),
286 },
287 };
288 }
289 }
290 let _ = match stdoutln!() {
301 Ok(_) => Ok(()),
302 Err(e) => match e.kind() {
303 std::io::ErrorKind::BrokenPipe => Ok(()),
304 _ => Err(e),
305 },
306 };
307 vp.iter()
308 .enumerate()
309 .take(rows)
310 .skip(1)
311 .for_each(|(i, row)| {
312 if config.line_counter {
313 if config.is_tty || config.is_force_color {
314 let _ = match stdout!(
315 "{: <6}",
316 i.truecolor(
317 config.meta_color[0],
318 config.meta_color[1],
319 config.meta_color[2]
320 )
321 ) {
322 Ok(_) => Ok(()),
323 Err(e) => match e.kind() {
324 std::io::ErrorKind::BrokenPipe => Ok(()),
325 _ => Err(e),
326 },
327 };
328 } else {
329 let _ = match stdout!("{: <6}", i) {
330 Ok(_) => Ok(()),
331 Err(e) => match e.kind() {
332 std::io::ErrorKind::BrokenPipe => Ok(()),
333 _ => Err(e),
334 },
335 };
336 }
337 }
338 row.iter().take(num_cols_to_print).for_each(|col| {
339 if config.is_tty || config.is_force_color {
340 let _ = match stdout!(
341 "{}",
342 if datatype::is_na_string_padded(col) {
343 col.truecolor(config.na_color[0], config.na_color[1], config.na_color[2])
344 } else if datatype::is_number(col) && datatype::is_negative_number(col) {
345 col.truecolor(
346 config.neg_num_color[0],
347 config.neg_num_color[1],
348 config.neg_num_color[2],
349 )
350 } else {
351 col.truecolor(
352 config.std_color[0],
353 config.std_color[1],
354 config.std_color[2],
355 )
356 }
357 ) {
358 Ok(_) => Ok(()),
359 Err(e) => match e.kind() {
360 std::io::ErrorKind::BrokenPipe => Ok(()),
361 _ => Err(e),
362 },
363 };
364 } else {
365 let _ = match stdout!("{}", col) {
366 Ok(_) => Ok(()),
367 Err(e) => match e.kind() {
368 std::io::ErrorKind::BrokenPipe => Ok(()),
369 _ => Err(e),
370 },
371 };
372 }
373 });
374 let _ = match stdoutln!() {
375 Ok(_) => Ok(()),
376 Err(e) => match e.kind() {
377 std::io::ErrorKind::BrokenPipe => Ok(()),
378 _ => Err(e),
379 },
380 };
381 });
382
383 if rows_remaining > 0 {
385 let _ = match stdout!("{: <6}", "") {
386 Ok(_) => Ok(()),
387 Err(e) => match e.kind() {
388 std::io::ErrorKind::BrokenPipe => Ok(()),
389 _ => Err(e),
390 },
391 };
392 if config.is_tty || config.is_force_color {
393 let _ = match stdout!(
394 "{}",
395 row_remaining_text.truecolor(
396 config.meta_color[0],
397 config.meta_color[1],
398 config.meta_color[2]
399 )
400 ) {
401 Ok(_) => Ok(()),
402 Err(e) => match e.kind() {
403 std::io::ErrorKind::BrokenPipe => Ok(()),
404 _ => Err(e),
405 },
406 };
407 } else {
408 let _ = match stdout!("{}", row_remaining_text) {
409 Ok(_) => Ok(()),
410 Err(e) => match e.kind() {
411 std::io::ErrorKind::BrokenPipe => Ok(()),
412 _ => Err(e),
413 },
414 };
415 }
416 let extra_cols_to_mention = num_cols_to_print;
417 let remainder_cols = cols - extra_cols_to_mention;
418 if extra_cols_to_mention < cols {
419 let meta_text_and = "and";
420 let meta_text_var = "more variables";
421 let meta_text_comma = ",";
422 let meta_text_colon = ":";
423 if config.is_tty || config.is_force_color {
424 let _ = match stdout!(
425 " {} {} {}{}",
426 meta_text_and.truecolor(
427 config.meta_color[0],
428 config.meta_color[1],
429 config.meta_color[2]
430 ),
431 remainder_cols.truecolor(
432 config.meta_color[0],
433 config.meta_color[1],
434 config.meta_color[2]
435 ),
436 meta_text_var.truecolor(
437 config.meta_color[0],
438 config.meta_color[1],
439 config.meta_color[2]
440 ),
441 meta_text_colon.truecolor(
442 config.meta_color[0],
443 config.meta_color[1],
444 config.meta_color[2]
445 )
446 ) {
447 Ok(_) => Ok(()),
448 Err(e) => match e.kind() {
449 std::io::ErrorKind::BrokenPipe => Ok(()),
450 _ => Err(e),
451 },
452 };
453 } else {
454 let _ = match stdout!(
455 " {} {} {}{}",
456 meta_text_and,
457 remainder_cols,
458 meta_text_var,
459 meta_text_colon
460 ) {
461 Ok(_) => Ok(()),
462 Err(e) => match e.kind() {
463 std::io::ErrorKind::BrokenPipe => Ok(()),
464 _ => Err(e),
465 },
466 };
467 }
468 for col in extra_cols_to_mention..cols {
469 let text = rdr[0].get(col).unwrap();
470 if config.is_tty || config.is_force_color {
471 let _ = match stdout!(
472 " {}",
473 text.truecolor(
474 config.meta_color[0],
475 config.meta_color[1],
476 config.meta_color[2]
477 )
478 ) {
479 Ok(_) => Ok(()),
480 Err(e) => match e.kind() {
481 std::io::ErrorKind::BrokenPipe => Ok(()),
482 _ => Err(e),
483 },
484 };
485 } else {
486 let _ = match stdout!(" {}", text) {
487 Ok(_) => Ok(()),
488 Err(e) => match e.kind() {
489 std::io::ErrorKind::BrokenPipe => Ok(()),
490 _ => Err(e),
491 },
492 };
493 }
494
495 if col + 1 < cols {
497 if config.is_tty || config.is_force_color {
498 let _ = match stdout!(
499 "{}",
500 meta_text_comma.truecolor(
501 config.meta_color[0],
502 config.meta_color[1],
503 config.meta_color[2]
504 )
505 ) {
506 Ok(_) => Ok(()),
507 Err(e) => match e.kind() {
508 std::io::ErrorKind::BrokenPipe => Ok(()),
509 _ => Err(e),
510 },
511 };
512 } else {
513 let _ = match stdout!("{}", meta_text_comma) {
514 Ok(_) => Ok(()),
515 Err(e) => match e.kind() {
516 std::io::ErrorKind::BrokenPipe => Ok(()),
517 _ => Err(e),
518 },
519 };
520 }
521 }
522 } }
524 }
525
526 if !datatype::is_na(&config.footer_option) {
528 let _ = match stdout!("{: <6}", "") {
529 Ok(_) => Ok(()),
530 Err(e) => match e.kind() {
531 std::io::ErrorKind::BrokenPipe => Ok(()),
532 _ => Err(e),
533 },
534 };
535 if config.is_tty || config.is_force_color {
536 let _ = match stdoutln!(
537 "{}",
538 config.footer_option.truecolor(
539 config.meta_color[0],
540 config.meta_color[1],
541 config.meta_color[2]
542 )
543 ) {
544 Ok(_) => Ok(()),
545 Err(e) => match e.kind() {
546 std::io::ErrorKind::BrokenPipe => Ok(()),
547 _ => Err(e),
548 },
549 };
550 } else {
551 let _ = match stdoutln!("{}", config.footer_option) {
552 Ok(_) => Ok(()),
553 Err(e) => match e.kind() {
554 std::io::ErrorKind::BrokenPipe => Ok(()),
555 _ => Err(e),
556 },
557 };
558 }
559 }
560}
561
562fn get_num_cols_to_print(cols: usize, vp: Vec<Vec<String>>, term_tuple: (u16, u16)) -> usize {
564 let mut last = 0;
565 let mut j = format!("{: <6}", "");
566 for col in 0..cols {
567 let text = vp[0].get(col).unwrap().to_string();
568 j.push_str(&text);
569 let total_width = j.chars().count();
570 let term_width = term_tuple.0 as usize;
571 if total_width > term_width {
572 break;
573 }
574 last = col + 1;
575 }
576 last
577}