tabled/tables/
iter.rs

1//! This module contains a [`IterTable`] table.
2//!
3//! In contrast to [`Table`] [`IterTable`] does no allocations but it consumes an iterator.
4//! It's useful when you don't want to re/allocate a buffer for your data.
5//!
6//! # Example
7//!
8//! ```
9//! use tabled::{grid::records::IterRecords, tables::IterTable};
10//!
11//! let iterator = vec![vec!["First", "row"], vec!["Second", "row"]];
12//! let records = IterRecords::new(iterator, 2, Some(2));
13//! let table = IterTable::new(records);
14//!
15//! let s = table.to_string();
16//!
17//! assert_eq!(
18//!     s,
19//!     "+--------+-----+\n\
20//!      | First  | row |\n\
21//!      +--------+-----+\n\
22//!      | Second | row |\n\
23//!      +--------+-----+",
24//! );
25//! ```
26//!
27//! [`Table`]: crate::Table
28
29use core::iter::FromIterator;
30use std::{fmt, io};
31
32use crate::{
33    grid::{
34        colors::NoColors,
35        config::{AlignmentHorizontal, CompactConfig, Indent, Sides, SpannedConfig},
36        dimension::{CompactGridDimension, DimensionValue, StaticDimension, ZeroDimension},
37        records::{
38            into_records::{BufRecords, LimitColumns, LimitRows, TruncateContent},
39            IntoRecords, IterRecords,
40        },
41        IterGrid,
42    },
43    settings::{Style, TableOption},
44};
45
46use crate::util::utf8_writer::UTF8Writer;
47
48/// A table which consumes an [`IntoRecords`] iterator.
49///
50/// To be able to build table we need a dimensions.
51/// If no width and count_columns is set, [`IterTable`] will sniff the records, by
52/// keeping a number of rows buffered (You can set the number via [`IterTable::sniff`]).
53///
54/// In contrast to [`Table`] [`IterTable`] does no allocations but it consumes an iterator.
55/// It's useful when you don't want to re/allocate a buffer for your data.
56///
57/// # Example
58///
59/// ```
60/// use tabled::{grid::records::IterRecords, tables::IterTable};
61/// use tabled::assert::assert_table;
62///
63/// let data = vec![
64///     vec!["First", "row"],
65///     vec!["Second", "row"],
66///     vec!["Third", "big \n multiline row"],
67/// ];
68///
69/// let records = IterRecords::new(data, 2, Some(2));
70/// let mut table = IterTable::new(records);
71/// table.sniff(1);
72///
73/// // notice because of sniff 1 we have all rows after the first one being truncated
74/// assert_table!(
75///     table.to_string(),
76///     "+-------+-----+"
77///     "| First | row |"
78///     "+-------+-----+"
79///     "| Secon | row |"
80///     "+-------+-----+"
81///     "| Third | big |"
82///     "+-------+-----+"
83/// );
84/// ```
85///
86/// [`Table`]: crate::Table
87#[derive(Debug, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)]
88pub struct IterTable<I> {
89    records: I,
90    cfg: CompactConfig,
91    table: Settings,
92}
93
94#[derive(Debug, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)]
95struct Settings {
96    sniff: usize,
97    count_columns: Option<usize>,
98    count_rows: Option<usize>,
99    width: Option<usize>,
100    height: Option<usize>,
101}
102
103impl<I> IterTable<I> {
104    /// Creates a new [`IterTable`] structure.
105    pub fn new(iter: I) -> Self
106    where
107        I: IntoRecords,
108    {
109        Self {
110            records: iter,
111            cfg: create_config(),
112            table: Settings {
113                sniff: 1000,
114                count_columns: None,
115                count_rows: None,
116                height: None,
117                width: None,
118            },
119        }
120    }
121
122    /// With is a generic function which applies options to the [`IterTable`].
123    pub fn with<O>(&mut self, option: O) -> &mut Self
124    where
125        O: TableOption<I, CompactConfig, ZeroDimension>,
126    {
127        let mut dims = ZeroDimension::new();
128        option.change(&mut self.records, &mut self.cfg, &mut dims);
129
130        self
131    }
132
133    /// Limit a number of columns.
134    pub fn columns(&mut self, count_columns: usize) -> &mut Self {
135        self.table.count_columns = Some(count_columns);
136        self
137    }
138
139    /// Limit a number of rows.
140    pub fn rows(&mut self, count_rows: usize) -> &mut Self {
141        self.table.count_rows = Some(count_rows);
142        self
143    }
144
145    /// Limit an amount of rows will be read for dimension estimations.
146    ///
147    /// By default it's 1000.
148    pub fn sniff(&mut self, count: usize) -> &mut Self {
149        self.table.sniff = count;
150        self
151    }
152
153    /// Set a height for each row.
154    pub fn height(&mut self, size: usize) -> &mut Self {
155        self.table.height = Some(size);
156        self
157    }
158
159    /// Set a width for each column.
160    pub fn width(&mut self, size: usize) -> &mut Self {
161        self.table.width = Some(size);
162        self
163    }
164
165    /// Build a string.
166    ///
167    /// We can't implement [`std::string::ToString`] cause it does takes `&self` reference.
168    #[allow(clippy::inherent_to_string)]
169    pub fn to_string(self) -> String
170    where
171        I: IntoRecords,
172        I::Cell: AsRef<str>,
173    {
174        let mut buf = String::new();
175        self.fmt(&mut buf)
176            .expect("according to a doc is safe to fmt() a string");
177
178        buf
179    }
180
181    /// Format table into [`io::Write`]r.
182    pub fn build<W>(self, writer: W) -> io::Result<()>
183    where
184        W: io::Write,
185        I: IntoRecords,
186        I::Cell: AsRef<str>,
187    {
188        let writer = UTF8Writer::new(writer);
189        self.fmt(writer).map_err(io::Error::other)
190    }
191
192    /// Format table into [fmt::Write]er.
193    pub fn fmt<W>(self, writer: W) -> fmt::Result
194    where
195        W: fmt::Write,
196        I: IntoRecords,
197        I::Cell: AsRef<str>,
198    {
199        build_grid(writer, self.records, self.cfg, self.table)
200    }
201}
202
203impl<T> FromIterator<T> for IterTable<Vec<Vec<T::Item>>>
204where
205    T: IntoIterator,
206    T::Item: AsRef<str>,
207{
208    fn from_iter<I: IntoIterator<Item = T>>(iter: I) -> Self {
209        let data = iter
210            .into_iter()
211            .map(|row| row.into_iter().collect())
212            .collect();
213
214        Self::new(data)
215    }
216}
217
218fn build_grid<W, I>(f: W, iter: I, cfg: CompactConfig, opts: Settings) -> fmt::Result
219where
220    W: fmt::Write,
221    I: IntoRecords,
222    I::Cell: AsRef<str>,
223{
224    let width_config = opts.width.is_some() && opts.count_columns.is_some();
225    if width_config {
226        build_table_with_static_dims(f, iter, cfg, opts)
227    } else if opts.width.is_some() {
228        build_table_sniffing_with_width(f, iter, cfg, opts)
229    } else {
230        build_table_sniffing(f, iter, cfg, opts)
231    }
232}
233
234fn build_table_with_static_dims<W, I>(
235    f: W,
236    iter: I,
237    cfg: CompactConfig,
238    opts: Settings,
239) -> fmt::Result
240where
241    W: fmt::Write,
242    I: IntoRecords,
243    I::Cell: AsRef<str>,
244{
245    let count_columns = opts.count_columns.unwrap();
246    let width = opts.width.unwrap();
247    let height = opts.height.unwrap_or(1);
248    let pad = cfg.get_padding();
249    let contentw = DimensionValue::Exact(width);
250    let width = DimensionValue::Exact(width + pad.left.size + pad.right.size);
251    let height = DimensionValue::Exact(height + pad.top.size + pad.bottom.size);
252    let dims = StaticDimension::new(width, height);
253    let cfg = SpannedConfig::from(cfg);
254
255    match opts.count_rows {
256        Some(limit) => {
257            let records = LimitRows::new(iter, limit);
258            let records = build_records(records, contentw, count_columns, Some(limit));
259            IterGrid::new(records, cfg, dims, NoColors).build(f)
260        }
261        None => {
262            let records = build_records(iter, contentw, count_columns, None);
263            IterGrid::new(records, cfg, dims, NoColors).build(f)
264        }
265    }
266}
267
268fn build_table_sniffing<W, I>(f: W, iter: I, cfg: CompactConfig, opts: Settings) -> fmt::Result
269where
270    W: fmt::Write,
271    I: IntoRecords,
272    I::Cell: AsRef<str>,
273{
274    let records = BufRecords::new(iter, opts.sniff);
275
276    let count_columns = get_count_columns(&opts, records.as_slice());
277
278    let (mut width, height) = {
279        let records = LimitColumns::new(records.as_slice(), count_columns);
280        let records = IterRecords::new(records, count_columns, None);
281        CompactGridDimension::dimension(records, &cfg)
282    };
283
284    let padding = cfg.get_padding();
285    let pad = padding.left.size + padding.right.size;
286    let padv = padding.top.size + padding.bottom.size;
287
288    if opts.sniff == 0 {
289        width = std::iter::repeat_n(pad, count_columns).collect::<Vec<_>>();
290    }
291
292    let content_width = DimensionValue::List(width.iter().map(|i| i.saturating_sub(pad)).collect());
293    let dims_width = DimensionValue::List(width);
294
295    let height_exact = opts.height.unwrap_or(1) + padv;
296    let mut dims_height = DimensionValue::Partial(height, height_exact);
297
298    if opts.height.is_some() {
299        dims_height = DimensionValue::Exact(height_exact);
300    }
301
302    let dims = StaticDimension::new(dims_width, dims_height);
303    let cfg = SpannedConfig::from(cfg);
304
305    match opts.count_rows {
306        Some(limit) => {
307            let records = LimitRows::new(records, limit);
308            let records = build_records(records, content_width, count_columns, Some(limit));
309            IterGrid::new(records, cfg, dims, NoColors).build(f)
310        }
311        None => {
312            let records = build_records(records, content_width, count_columns, None);
313            IterGrid::new(records, cfg, dims, NoColors).build(f)
314        }
315    }
316}
317
318fn build_table_sniffing_with_width<W, I>(
319    f: W,
320    iter: I,
321    cfg: CompactConfig,
322    opts: Settings,
323) -> fmt::Result
324where
325    W: fmt::Write,
326    I: IntoRecords,
327    I::Cell: AsRef<str>,
328{
329    let records = BufRecords::new(iter, opts.sniff);
330
331    let count_columns = get_count_columns(&opts, records.as_slice());
332
333    let width = opts.width.unwrap();
334    let contentw = DimensionValue::Exact(width);
335
336    let padding = cfg.get_padding();
337    let pad = padding.left.size + padding.right.size;
338    let padv = padding.top.size + padding.bottom.size;
339
340    let height = opts.height.unwrap_or(1) + padv;
341    let dimsh = DimensionValue::Exact(height);
342    let dimsw = DimensionValue::Exact(width + pad);
343    let dims = StaticDimension::new(dimsw, dimsh);
344
345    let cfg = SpannedConfig::from(cfg);
346
347    match opts.count_rows {
348        Some(limit) => {
349            let records = LimitRows::new(records, limit);
350            let records = build_records(records, contentw, count_columns, Some(limit));
351            IterGrid::new(records, cfg, dims, NoColors).build(f)
352        }
353        None => {
354            let records = build_records(records, contentw, count_columns, None);
355            IterGrid::new(records, cfg, dims, NoColors).build(f)
356        }
357    }
358}
359
360fn get_count_columns<T>(opts: &Settings, buf: &[Vec<T>]) -> usize {
361    match opts.count_columns {
362        Some(size) => size,
363        None => buf.iter().map(|row| row.len()).max().unwrap_or(0),
364    }
365}
366
367const fn create_config() -> CompactConfig {
368    CompactConfig::new()
369        .set_padding(Sides::new(
370            Indent::spaced(1),
371            Indent::spaced(1),
372            Indent::zero(),
373            Indent::zero(),
374        ))
375        .set_alignment_horizontal(AlignmentHorizontal::Left)
376        .set_borders(Style::ascii().get_borders())
377}
378
379fn build_records<I>(
380    records: I,
381    width: DimensionValue,
382    count_columns: usize,
383    count_rows: Option<usize>,
384) -> IterRecords<LimitColumns<TruncateContent<I, StaticDimension>>>
385where
386    I: IntoRecords,
387{
388    let dims = StaticDimension::new(width, DimensionValue::Exact(0));
389    let records = TruncateContent::new(records, dims);
390    let records = LimitColumns::new(records, count_columns);
391    IterRecords::new(records, count_columns, count_rows)
392}