Skip to main content

zql_cli/db/layout/
table.rs

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        &mut 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        if let Some(group) = self.group {
60            let mut prev = None;
61            self.dataset.group_records(group);
62            self.dataset.get_measured(4096, |record, sep| {
63                if test_column(&mut prev, &record, group) || sep {
64                    self.print_separator(writer, painter)?;
65                }
66                self.print_record(writer, painter, None, &record)?;
67                Ok(())
68            })
69        } else {
70            self.dataset.get_measured(4096, |record, sep| {
71                if sep {
72                    self.print_separator(writer, painter)?;
73                }
74                self.print_record(writer, painter, None, &record)?;
75                Ok(())
76            })
77        }
78    }
79
80    fn print_header<W: Write>(
81        &self,
82        writer: &mut W,
83        painter: Painter,
84        style: Option<Style>,
85        header: &Vec<Column>,
86    ) -> MyResult<()> {
87        for (index, ((name, format, _), width)) in zip(header, &self.widths).enumerate() {
88            let inner = index + 1 < header.len();
89            format_value(writer, painter, style, name, format, width, inner)?;
90        }
91        writeln!(writer)?;
92        Ok(())
93    }
94
95    fn print_separator<W: Write>(
96        &self,
97        writer: &mut W,
98        painter: Painter,
99    ) -> MyResult<()> {
100        for (index, ((_, format, _), width)) in zip(&self.dataset.columns, &self.widths).enumerate() {
101            let inner = index + 1 < self.widths.len();
102            if inner {
103                painter.print_hyphens(writer, Style::Column, width.total(), true)?;
104            } else {
105                painter.print_hyphens(writer, Style::Column, format.total(), false)?;
106            }
107        }
108        writeln!(writer)?;
109        Ok(())
110    }
111
112    fn print_record<W: Write>(
113        &self,
114        writer: &mut W,
115        painter: Painter,
116        style: Option<Style>,
117        record: &Record,
118    ) -> MyResult<()> {
119        for (index, ((value, format), width)) in zip(record, &self.widths).enumerate() {
120            let inner = index + 1 < record.len();
121            if value.is_empty() {
122                let format = format.unit();
123                format_value(writer, painter, Some(Style::Column), "-", &format, width, inner)?;
124            } else {
125                format_value(writer, painter, style, value, format, width, inner)?;
126            }
127        }
128        writeln!(writer)?;
129        Ok(())
130    }
131}
132
133fn find_column(columns: &Vec<Column>, group: Option<&str>) -> Option<usize> {
134    if let Some(group) = group {
135        columns.iter().position(|(x, _, _)| x.eq_ignore_ascii_case(group))
136    } else {
137        None
138    }
139}
140
141fn test_column(prev: &mut Option<String>, record: &Record, group: usize) -> bool {
142    let curr = record.get(group).map(|(x, _)| x.as_str()).unwrap_or_default();
143    if prev.as_ref().is_some_and(|x| x != curr) {
144        prev.replace(curr.to_string());
145        true
146    } else if prev.is_none() {
147        prev.replace(curr.to_string());
148        false
149    } else {
150        false
151    }
152}
153
154fn format_value<W: Write>(
155    writer: &mut W,
156    painter: Painter,
157    style: Option<Style>,
158    value: &str,
159    format: &Format,
160    width: &Format,
161    inner: bool,
162) -> MyResult<()> {
163    match format {
164        Format::Text(length) => {
165            let style = style.unwrap_or(Style::Text);
166            if inner {
167                let right = (width.total() + 2).checked_sub(*length).unwrap_or_default();
168                painter.print_value(writer, style, value, 0, right)?;
169            } else {
170                painter.print_value(writer, style, value, 0, 0)?;
171            }
172        }
173        Format::Number(left, right) => {
174            let style = style.unwrap_or(Style::Number);
175            let left = width.left().checked_sub(*left).unwrap_or_default();
176            if inner {
177                let right = (width.right() + 2).checked_sub(*right).unwrap_or_default();
178                painter.print_value(writer, style, value, left, right)?;
179            } else {
180                painter.print_value(writer, style, value, left, 0)?;
181            }
182        }
183    }
184    Ok(())
185}