1use crate::db::dataset::{Column, Dataset, Record};
2use crate::db::format::Format;
3use crate::error::MyResult;
4use crate::util::painter::{Painter, Style};
5use std::io::{Read, Write};
6use std::iter::zip;
7
8#[derive(Clone, Copy)]
9pub enum TableHeader {
10 Include,
11 Exclude,
12 Simple,
13}
14
15pub struct TableLayout<'a, R: Read> {
16 dataset: Dataset<'a, R>,
17 widths: Vec<Format>,
18 header: TableHeader,
19 group: Option<usize>,
20}
21
22impl<'a, R: Read> TableLayout<'a, R> {
23 pub fn from_dataset(
24 dataset: Dataset<'a, R>,
25 header: TableHeader,
26 group: Option<&str>,
27 ) -> Self {
28 match header {
29 TableHeader::Include => {
30 let widths = dataset.get_widths();
31 let widths = zip(&dataset.columns, &widths)
32 .map(|((_, format, _), width)| Format::max(format, width))
33 .collect::<Vec<_>>();
34 let group = find_column(&dataset.columns, group);
35 Self { dataset, widths, header, group }
36 }
37 TableHeader::Exclude => {
38 let widths = dataset.get_widths();
39 let group = find_column(&dataset.columns, group);
40 Self { dataset, widths, header, group }
41 }
42 TableHeader::Simple => {
43 let widths = dataset.columns.iter().map(|_| Format::Text(0)).collect();
44 let group = None;
45 Self { dataset, widths, header, group }
46 }
47 }
48 }
49
50 pub fn print_dataset<W: Write>(
51 &self,
52 writer: &mut W,
53 painter: Painter,
54 ) -> MyResult<()> {
55 if let TableHeader::Include = self.header {
56 self.print_header(writer, painter, Some(Style::Column), &self.dataset.columns)?;
57 self.print_separator(writer, painter)?;
58 }
59 let mut prev = None;
60 self.dataset.get_measured(4096, |record, mut sep| {
61 if let Some(group) = self.group {
62 let curr = record.get(group).map(|(x, _)| x.as_str()).unwrap_or_default();
63 if prev.as_ref().is_some_and(|x| x != curr) {
64 prev.replace(curr.to_string());
65 sep = true;
66 } else if prev.is_none() {
67 prev.replace(curr.to_string());
68 }
69 }
70 if sep {
71 self.print_separator(writer, painter)?;
72 }
73 self.print_record(writer, painter, None, &record)?;
74 Ok(())
75 })
76 }
77
78 fn print_header<W: Write>(
79 &self,
80 writer: &mut W,
81 painter: Painter,
82 style: Option<Style>,
83 header: &Vec<Column>,
84 ) -> MyResult<()> {
85 for (index, ((name, format, _), width)) in zip(header, &self.widths).enumerate() {
86 let inner = index + 1 < header.len();
87 format_value(writer, painter, style, name, format, width, inner)?;
88 }
89 writeln!(writer)?;
90 Ok(())
91 }
92
93 fn print_separator<W: Write>(
94 &self,
95 writer: &mut W,
96 painter: Painter,
97 ) -> MyResult<()> {
98 for (index, ((_, format, _), width)) in zip(&self.dataset.columns, &self.widths).enumerate() {
99 let inner = index + 1 < self.widths.len();
100 if inner {
101 painter.print_hyphens(writer, Style::Column, width.total(), true)?;
102 } else {
103 painter.print_hyphens(writer, Style::Column, format.total(), false)?;
104 }
105 }
106 writeln!(writer)?;
107 Ok(())
108 }
109
110 fn print_record<W: Write>(
111 &self,
112 writer: &mut W,
113 painter: Painter,
114 style: Option<Style>,
115 record: &Record,
116 ) -> MyResult<()> {
117 for (index, ((value, format), width)) in zip(record, &self.widths).enumerate() {
118 let inner = index + 1 < record.len();
119 if value.is_empty() {
120 let format = format.unit();
121 format_value(writer, painter, Some(Style::Column), "-", &format, width, inner)?;
122 } else {
123 format_value(writer, painter, style, value, format, width, inner)?;
124 }
125 }
126 writeln!(writer)?;
127 Ok(())
128 }
129}
130
131fn find_column(columns: &Vec<Column>, group: Option<&str>) -> Option<usize> {
132 if let Some(group) = group {
133 columns.iter().position(|(x, _, _)| x.eq_ignore_ascii_case(group))
134 } else {
135 None
136 }
137}
138
139fn format_value<W: Write>(
140 writer: &mut W,
141 painter: Painter,
142 style: Option<Style>,
143 value: &str,
144 format: &Format,
145 width: &Format,
146 inner: bool,
147) -> MyResult<()> {
148 match format {
149 Format::Text(length) => {
150 let style = style.unwrap_or(Style::Text);
151 if inner {
152 let right = (width.total() + 2).checked_sub(*length).unwrap_or_default();
153 painter.print_value(writer, style, value, 0, right)?;
154 } else {
155 painter.print_value(writer, style, value, 0, 0)?;
156 }
157 }
158 Format::Number(left, right) => {
159 let style = style.unwrap_or(Style::Number);
160 let left = width.left().checked_sub(*left).unwrap_or_default();
161 if inner {
162 let right = (width.right() + 2).checked_sub(*right).unwrap_or_default();
163 painter.print_value(writer, style, value, left, right)?;
164 } else {
165 painter.print_value(writer, style, value, left, 0)?;
166 }
167 }
168 }
169 Ok(())
170}