1use std::{
55 cmp::max,
56 fmt::{self, Write as FmtWrite},
57 io::{self, Write},
58 marker::PhantomData,
59 mem
60};
61
62use unicode_truncate::UnicodeTruncateStr;
63use unicode_width::UnicodeWidthStr;
64
65#[cfg(test)]
66mod tests;
67
68pub struct Stream<T, Out: Write> {
70 columns: Vec<Column<T>>,
72 max_width: usize,
73 grow: Option<bool>,
74 output: Out,
75 borders: bool,
76 padding: bool,
77 title: Option<String>,
78
79 #[allow(dead_code)] wrap: bool,
81
82 sizes_calculated: bool,
83 width: usize, buffer: Vec<T>,
85
86 str_buf: String,
88
89 _pd: PhantomData<T>,
90}
91
92
93impl <T, Out: Write> Stream<T, Out> {
94 pub fn new(output: Out, columns: Vec<Column<T>>) -> Self {
96 let mut term_width = crossterm::terminal::size().map(|(w,_)| w as usize);
97 if cfg!(windows) {
98 term_width = term_width.map(|w| w - 1);
103 }
104
105 Self{
106 columns,
107 max_width: 0,
108 width: 0, grow: None,
110 output,
111 wrap: false,
112 borders: false,
113 padding: true,
114 title: None,
115
116 sizes_calculated: false,
117 buffer: vec![],
118
119 str_buf: String::new(),
120
121 _pd: Default::default(),
122 }.max_width(
123 term_width.unwrap_or(80)
124 )
125 }
126
127 pub fn borders(mut self, borders: bool) -> Self {
129 self.borders = borders;
130 let width = self.max_width;
131 self.max_width(width)
132 }
133
134 pub fn max_width(mut self, max_width: usize) -> Self {
139 let num_cols = self.columns.len();
140 let padding = if self.padding { 1 } else { 0 };
141 let dividers = (num_cols - 1) * (1 + 2*padding);
142 let border = if self.borders { 1 } else { 0 };
143 let borders = border * (border + padding) * 2;
144
145 let col_widths = self.columns.iter().map(|c| c.min_width).sum::<usize>();
146 let min_width = col_widths + borders + dividers;
147 self.max_width = max(max_width, min_width);
148
149 let title_width = self.title.as_ref().map(|t| t.width()).unwrap_or(0) + borders;
151 self.max_width = max(self.max_width, title_width);
152
153 self
154 }
155
156 pub fn padding(mut self, padding: bool) -> Self {
158 self.padding = padding;
159 let width = self.max_width;
160 self.max_width(width)
161 }
162
163 pub fn grow(mut self, grow: bool) -> Self {
171 self.grow = Some(grow);
172 self
173 }
174
175 pub fn title(mut self, title: &str) -> Self {
177 self.title = Some(title.to_string());
178 let width = self.max_width;
179 self.max_width(width)
180 }
181
182 pub fn row(&mut self, data: T) -> io::Result<()> {
186
187 if self.sizes_calculated {
188 return self.print_row(data);
189 }
190
191 self.buffer.push(data);
192 if self.buffer.len() > 100 {
193 self.grow = self.grow.or(Some(true));
195 self.write_buffer()?;
196 }
197
198 Ok(())
199 }
200
201 fn write_buffer(&mut self) -> io::Result<()> {
202 self.calc_sizes()?;
203
204 self.print_headers()?;
205
206 let buffer = mem::replace(&mut self.buffer, vec![]);
207 for row in buffer {
208 self.print_row(row)?;
209 }
210
211 Ok(())
212 }
213
214 fn print_headers(&mut self) -> io::Result<()> {
215 self.hr()?;
216
217 let border_width = if self.borders { 1 } else { 0 } + if self.padding { 1 } else { 0 };
218 let title_width = self.width - (border_width * 2);
219
220 if let Some(title) = &self.title {
221 let title = title.clone();
222 self.border_left()?;
223 Alignment::Center.write(&mut self.output, title_width, &title)?;
224 self.border_right()?;
225 self.hr()?;
226 }
227
228 let has_headers = self.columns.iter().any(|c| c.header.is_some());
229 if has_headers {
230 let divider = if self.padding { " | " } else { "|" };
231 self.border_left()?;
232 for (i, col) in self.columns.iter().enumerate() {
233 if i > 0 {
234 write!(&mut self.output, "{}", divider)?;
235 }
236 let name = col.header.as_ref().map(|h| h.as_str()).unwrap_or("");
237 Alignment::Center.write(&mut self.output, col.width, name)?;
238 }
239 self.border_right()?;
240 self.hr()?;
241 }
242
243 Ok(())
244 }
245
246 fn hr(&mut self) -> io::Result<()> {
247 writeln!(&mut self.output, "{1:-<0$}", self.width, "")
248 }
249
250 fn border_left(&mut self) -> io::Result<()> {
251 if self.borders {
252 let border = if self.padding { "| " } else { "|" };
253 write!(&mut self.output, "{}", border)?;
254 }
255 Ok(())
256 }
257 fn border_right(&mut self) -> io::Result<()> {
258 if self.borders {
259 let border = if self.padding { " |" } else { "|" };
260 writeln!(&mut self.output, "{}", border)
261 } else {
262 writeln!(&mut self.output, "")
263 }
264 }
265
266 fn print_row(&mut self, row: T) -> io::Result<()> {
267
268 let buf = &mut self.str_buf;
269 let out = &mut self.output;
270
271 if self.borders {
272 write!(out, "|")?;
273 if self.padding {
274 write!(out, " ")?;
275 }
276 }
277
278 for (i, col) in self.columns.iter().enumerate() {
279 if i > 0 {
280 if self.padding {
281 write!(out, " | ")?;
282 } else {
283 write!(out, "|")?;
284 }
285 }
286
287 buf.clear();
288 write!(
289 buf,
290 "{}",
291 Displayer{ row: &row, writer: col.writer.as_ref() }
292 ).to_io()?;
293
294 col.alignment.write(out, col.width, buf.as_str())?;
295 }
296
297 if self.borders {
298 if self.padding {
299 write!(out, " ")?;
300 }
301 write!(out, "|")?;
302 }
303
304 writeln!(out, "")?;
305
306 Ok(())
307 }
308
309 fn calc_sizes(&mut self) -> io::Result<()> {
310 if self.sizes_calculated { return Ok(()); }
311 self.sizes_calculated = true; for row in &self.buffer {
315 for col in self.columns.iter_mut() {
316 self.str_buf.clear();
317 write!(
318 &mut self.str_buf,
319 "{}",
320 Displayer{ row, writer: col.writer.as_ref() }
321 ).to_io()?;
322 let width = self.str_buf.width();
323 col.max_width = max(col.max_width, width);
324 col.width_sum += width;
325 }
326 }
327
328 let num_cols = self.columns.len();
329 let padding = if self.padding { 1 } else { 0 };
330 let dividers = (num_cols - 1) * (1 + 2*padding);
331 let border = if self.borders { 1 } else { 0 };
332 let borders = border * (border + padding) * 2;
333 let available_width = self.max_width - borders - dividers;
334
335
336 let col_width = |c: &Column<T>| {
339 let mut width = max(
340 c.max_width,
341 c.header.as_ref().map(|h| h.len()).unwrap_or(0)
342 );
343 width = max(width, c.min_width);
344 width
345 };
346
347 let all_max: usize = self.columns.iter().map(col_width).sum();
348 if all_max < available_width {
349 let extra_width = if self.grow.unwrap_or(false) {
353 self.width = self.max_width;
354 available_width - all_max
355 } else {
356 self.width = all_max + dividers + borders;
357 0
358 };
359
360 let extra_per_col = extra_width / num_cols;
361 let mut extra_last_col = extra_width % num_cols;
362 for col in self.columns.iter_mut().rev() {
363 col.width = col_width(col) + extra_per_col + extra_last_col;
364 extra_last_col = 0;
365 }
366
367 return Ok(());
368 }
369
370 for big_cols in 1..=self.columns.len() {
378 if self.penalize_big_cols(big_cols) {
382 self.width = self.max_width;
383 return Ok(())
384 }
385 }
386
387 panic!("Couldn't display {} columns worth of data in {} columns of text", self.columns.len(), self.max_width);
389 }
390
391 fn penalize_big_cols(&mut self, num_big_cols: usize) -> bool {
394 let num_cols = self.columns.len();
395 let padding = if self.padding { 1 } else { 0 };
396 let dividers = (num_cols - 1) * (1 + 2*padding);
397 let border = if self.borders { 1 } else { 0 };
398 let borders = border * (border + padding) * 2;
399 let available_width = self.max_width - borders - dividers;
400
401 let mut col_refs: Vec<_> = self.columns.iter_mut().collect();
402 col_refs.sort_by_key(|c| c.width_sum); let (small_cols, big_cols) = col_refs.split_at_mut(num_cols - num_big_cols);
404
405 let needed_width: usize =
406 small_cols.iter().map(|c| max(c.min_width, c.max_width)).sum::<usize>()
407 + big_cols.iter().map(|c| c.min_width).sum::<usize>();
408
409 if needed_width > available_width {
410 return false
411 }
412
413 let mut remaining_width = available_width;
415 for col in small_cols.iter_mut() {
416 col.width = max(col.min_width, col.max_width);
417 remaining_width -= col.width;
418 }
419
420 let mut big_cols_left = num_big_cols;
425 for col in big_cols.iter_mut() {
426 let cols_per_big_col = remaining_width / big_cols_left;
427 if cols_per_big_col < col.min_width {
428 col.width = col.min_width;
429 remaining_width -= col.width;
430 big_cols_left -= 1;
431 }
432 }
433
434 if big_cols_left > 0 {
436 let cols_per_big_col = remaining_width / big_cols_left;
437 for col in big_cols.iter_mut() {
438 if col.width > 0 { continue; } col.width = cols_per_big_col;
440 }
441
442 remaining_width -= big_cols_left * cols_per_big_col;
443
444 if remaining_width > 0 {
446 for col in big_cols.iter_mut().rev().take(1) {
447 col.width += remaining_width;
448 }
449 }
450 }
451
452 true
453 }
454
455 pub fn finish(mut self) -> io::Result<()> {
459 if !self.buffer.is_empty() {
460 self.write_buffer()?;
461 }
462 self.hr()?;
463
464
465 Ok(())
466 }
467
468 pub fn footer(mut self, footer: &str) -> io::Result<()> {
470 if !self.buffer.is_empty() {
471 self.write_buffer()?;
472 }
473
474 let border_width = if self.borders { 1 } else { 0 } + if self.padding { 1 } else { 0 };
475 let foot_width = self.width - (border_width * 2);
476
477 self.hr()?;
478 self.border_left()?;
479 Alignment::Center.write(&mut self.output, foot_width, &footer)?;
480 self.border_right()?;
481 self.hr()?;
482
483 Ok(())
484 }
485}
486
487pub struct Column<T> {
489 header: Option<String>,
490 writer: Box<dyn Fn(&mut fmt::Formatter, &T) -> fmt::Result>,
491
492 alignment: Alignment,
493
494 min_width: usize,
496
497 width: usize,
499
500 max_width: usize, width_sum: usize, _pd: PhantomData<T>,
506}
507
508impl <T> Column<T> {
509
510 pub fn new<F>(func: F) -> Self
512 where F: (Fn(&mut fmt::Formatter, &T) -> fmt::Result) + 'static
513 {
514 Self {
515 header: None,
516 writer: Box::new(func),
517 alignment: Alignment::Left,
518
519 min_width: 1,
522 width: 0,
523 max_width: 0,
524 width_sum: 0,
525
526
527 _pd: Default::default(),
528 }
529 }
530
531 pub fn header(mut self, name: &str) -> Self {
535 self.header = Some(name.to_string());
536 self.min_width = max(self.min_width, name.len());
537 self
538 }
539
540 pub fn min_width(mut self, min_width: usize) -> Self {
545 self.min_width = min_width;
546 self
547 }
548
549 pub fn left(mut self) -> Self {
551 self.alignment = Alignment::Left;
552 self
553 }
554
555 pub fn right(mut self) -> Self {
557 self.alignment = Alignment::Right;
558 self
559 }
560
561 pub fn center(mut self) -> Self {
563 self.alignment = Alignment::Center;
564 self
565 }
566}
567
568enum Alignment {
569 Left,
570 Center,
571 Right,
572}
573
574impl Alignment {
575 fn write<W: io::Write>(&self, out: &mut W, col_width: usize, value: &str) -> io::Result<()> {
579 let (value, width) = value.unicode_truncate(col_width);
580 let (lpad, rpad) = match self {
581 Alignment::Left => (0, col_width - width),
582 Alignment::Right => (col_width - width, 0),
583 Alignment::Center => {
584 let padding = col_width - width;
585 let half = padding / 2;
586 let remainder = padding % 2;
587 (half, half + remainder)
588 }
589 };
590 write!(out, "{0:1$}{3}{0:2$}", "", lpad, rpad, value)
593 }
594}
595
596trait ToIOResult {
597 fn to_io(self) -> io::Result<()>;
598}
599
600impl ToIOResult for fmt::Result {
601 fn to_io(self) -> io::Result<()> {
602 self.map_err(|e| io::Error::new(io::ErrorKind::Other, e))
603 }
604}
605
606struct Displayer<'a, T> {
607 row: &'a T,
608 writer: &'a dyn Fn(&mut fmt::Formatter, &T) -> fmt::Result,
609}
610
611impl <'a, T> fmt::Display for Displayer<'a, T> {
612 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
613 (self.writer)(f, self.row)
614 }
615}
616
617#[macro_export]
622macro_rules! col {
623 ($t:ty : .$field:ident) => {
624 $crate::Column::new(|f, row: &$t| write!(f, "{}", row.$field))
625 };
626 ($t:ty : $s:literal, $(.$field:ident),*) => {
627 $crate::Column::new(|f, row: &$t| write!(f, $s, $(row.$field)*,))
628 };
629}