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        &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}