Coverage Report

Created: 2026-03-14 06:47

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/rust/registry/src/index.crates.io-1949cf8c6b5b557f/tabled-0.20.0/src/tables/iter.rs
Line
Count
Source
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
29
use core::iter::FromIterator;
30
use std::{fmt, io};
31
32
use 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
46
use 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)]
88
pub struct IterTable<I> {
89
    records: I,
90
    cfg: CompactConfig,
91
    table: Settings,
92
}
93
94
#[derive(Debug, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)]
95
struct Settings {
96
    sniff: usize,
97
    count_columns: Option<usize>,
98
    count_rows: Option<usize>,
99
    width: Option<usize>,
100
    height: Option<usize>,
101
}
102
103
impl<I> IterTable<I> {
104
    /// Creates a new [`IterTable`] structure.
105
0
    pub fn new(iter: I) -> Self
106
0
    where
107
0
        I: IntoRecords,
108
    {
109
0
        Self {
110
0
            records: iter,
111
0
            cfg: create_config(),
112
0
            table: Settings {
113
0
                sniff: 1000,
114
0
                count_columns: None,
115
0
                count_rows: None,
116
0
                height: None,
117
0
                width: None,
118
0
            },
119
0
        }
120
0
    }
121
122
    /// With is a generic function which applies options to the [`IterTable`].
123
0
    pub fn with<O>(&mut self, option: O) -> &mut Self
124
0
    where
125
0
        O: TableOption<I, CompactConfig, ZeroDimension>,
126
    {
127
0
        let mut dims = ZeroDimension::new();
128
0
        option.change(&mut self.records, &mut self.cfg, &mut dims);
129
130
0
        self
131
0
    }
132
133
    /// Limit a number of columns.
134
0
    pub fn columns(&mut self, count_columns: usize) -> &mut Self {
135
0
        self.table.count_columns = Some(count_columns);
136
0
        self
137
0
    }
138
139
    /// Limit a number of rows.
140
0
    pub fn rows(&mut self, count_rows: usize) -> &mut Self {
141
0
        self.table.count_rows = Some(count_rows);
142
0
        self
143
0
    }
144
145
    /// Limit an amount of rows will be read for dimension estimations.
146
    ///
147
    /// By default it's 1000.
148
0
    pub fn sniff(&mut self, count: usize) -> &mut Self {
149
0
        self.table.sniff = count;
150
0
        self
151
0
    }
152
153
    /// Set a height for each row.
154
0
    pub fn height(&mut self, size: usize) -> &mut Self {
155
0
        self.table.height = Some(size);
156
0
        self
157
0
    }
158
159
    /// Set a width for each column.
160
0
    pub fn width(&mut self, size: usize) -> &mut Self {
161
0
        self.table.width = Some(size);
162
0
        self
163
0
    }
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
0
    pub fn to_string(self) -> String
170
0
    where
171
0
        I: IntoRecords,
172
0
        I::Cell: AsRef<str>,
173
    {
174
0
        let mut buf = String::new();
175
0
        self.fmt(&mut buf)
176
0
            .expect("according to a doc is safe to fmt() a string");
177
178
0
        buf
179
0
    }
180
181
    /// Format table into [`io::Write`]r.
182
0
    pub fn build<W>(self, writer: W) -> io::Result<()>
183
0
    where
184
0
        W: io::Write,
185
0
        I: IntoRecords,
186
0
        I::Cell: AsRef<str>,
187
    {
188
0
        let writer = UTF8Writer::new(writer);
189
0
        self.fmt(writer).map_err(io::Error::other)
190
0
    }
191
192
    /// Format table into [fmt::Write]er.
193
0
    pub fn fmt<W>(self, writer: W) -> fmt::Result
194
0
    where
195
0
        W: fmt::Write,
196
0
        I: IntoRecords,
197
0
        I::Cell: AsRef<str>,
198
    {
199
0
        build_grid(writer, self.records, self.cfg, self.table)
200
0
    }
201
}
202
203
impl<T> FromIterator<T> for IterTable<Vec<Vec<T::Item>>>
204
where
205
    T: IntoIterator,
206
    T::Item: AsRef<str>,
207
{
208
0
    fn from_iter<I: IntoIterator<Item = T>>(iter: I) -> Self {
209
0
        let data = iter
210
0
            .into_iter()
211
0
            .map(|row| row.into_iter().collect())
212
0
            .collect();
213
214
0
        Self::new(data)
215
0
    }
216
}
217
218
0
fn build_grid<W, I>(f: W, iter: I, cfg: CompactConfig, opts: Settings) -> fmt::Result
219
0
where
220
0
    W: fmt::Write,
221
0
    I: IntoRecords,
222
0
    I::Cell: AsRef<str>,
223
{
224
0
    let width_config = opts.width.is_some() && opts.count_columns.is_some();
225
0
    if width_config {
226
0
        build_table_with_static_dims(f, iter, cfg, opts)
227
0
    } else if opts.width.is_some() {
228
0
        build_table_sniffing_with_width(f, iter, cfg, opts)
229
    } else {
230
0
        build_table_sniffing(f, iter, cfg, opts)
231
    }
232
0
}
233
234
0
fn build_table_with_static_dims<W, I>(
235
0
    f: W,
236
0
    iter: I,
237
0
    cfg: CompactConfig,
238
0
    opts: Settings,
239
0
) -> fmt::Result
240
0
where
241
0
    W: fmt::Write,
242
0
    I: IntoRecords,
243
0
    I::Cell: AsRef<str>,
244
{
245
0
    let count_columns = opts.count_columns.unwrap();
246
0
    let width = opts.width.unwrap();
247
0
    let height = opts.height.unwrap_or(1);
248
0
    let pad = cfg.get_padding();
249
0
    let contentw = DimensionValue::Exact(width);
250
0
    let width = DimensionValue::Exact(width + pad.left.size + pad.right.size);
251
0
    let height = DimensionValue::Exact(height + pad.top.size + pad.bottom.size);
252
0
    let dims = StaticDimension::new(width, height);
253
0
    let cfg = SpannedConfig::from(cfg);
254
255
0
    match opts.count_rows {
256
0
        Some(limit) => {
257
0
            let records = LimitRows::new(iter, limit);
258
0
            let records = build_records(records, contentw, count_columns, Some(limit));
259
0
            IterGrid::new(records, cfg, dims, NoColors).build(f)
260
        }
261
        None => {
262
0
            let records = build_records(iter, contentw, count_columns, None);
263
0
            IterGrid::new(records, cfg, dims, NoColors).build(f)
264
        }
265
    }
266
0
}
267
268
0
fn build_table_sniffing<W, I>(f: W, iter: I, cfg: CompactConfig, opts: Settings) -> fmt::Result
269
0
where
270
0
    W: fmt::Write,
271
0
    I: IntoRecords,
272
0
    I::Cell: AsRef<str>,
273
{
274
0
    let records = BufRecords::new(iter, opts.sniff);
275
276
0
    let count_columns = get_count_columns(&opts, records.as_slice());
277
278
0
    let (mut width, height) = {
279
0
        let records = LimitColumns::new(records.as_slice(), count_columns);
280
0
        let records = IterRecords::new(records, count_columns, None);
281
0
        CompactGridDimension::dimension(records, &cfg)
282
0
    };
283
284
0
    let padding = cfg.get_padding();
285
0
    let pad = padding.left.size + padding.right.size;
286
0
    let padv = padding.top.size + padding.bottom.size;
287
288
0
    if opts.sniff == 0 {
289
0
        width = std::iter::repeat_n(pad, count_columns).collect::<Vec<_>>();
290
0
    }
291
292
0
    let content_width = DimensionValue::List(width.iter().map(|i| i.saturating_sub(pad)).collect());
293
0
    let dims_width = DimensionValue::List(width);
294
295
0
    let height_exact = opts.height.unwrap_or(1) + padv;
296
0
    let mut dims_height = DimensionValue::Partial(height, height_exact);
297
298
0
    if opts.height.is_some() {
299
0
        dims_height = DimensionValue::Exact(height_exact);
300
0
    }
301
302
0
    let dims = StaticDimension::new(dims_width, dims_height);
303
0
    let cfg = SpannedConfig::from(cfg);
304
305
0
    match opts.count_rows {
306
0
        Some(limit) => {
307
0
            let records = LimitRows::new(records, limit);
308
0
            let records = build_records(records, content_width, count_columns, Some(limit));
309
0
            IterGrid::new(records, cfg, dims, NoColors).build(f)
310
        }
311
        None => {
312
0
            let records = build_records(records, content_width, count_columns, None);
313
0
            IterGrid::new(records, cfg, dims, NoColors).build(f)
314
        }
315
    }
316
0
}
317
318
0
fn build_table_sniffing_with_width<W, I>(
319
0
    f: W,
320
0
    iter: I,
321
0
    cfg: CompactConfig,
322
0
    opts: Settings,
323
0
) -> fmt::Result
324
0
where
325
0
    W: fmt::Write,
326
0
    I: IntoRecords,
327
0
    I::Cell: AsRef<str>,
328
{
329
0
    let records = BufRecords::new(iter, opts.sniff);
330
331
0
    let count_columns = get_count_columns(&opts, records.as_slice());
332
333
0
    let width = opts.width.unwrap();
334
0
    let contentw = DimensionValue::Exact(width);
335
336
0
    let padding = cfg.get_padding();
337
0
    let pad = padding.left.size + padding.right.size;
338
0
    let padv = padding.top.size + padding.bottom.size;
339
340
0
    let height = opts.height.unwrap_or(1) + padv;
341
0
    let dimsh = DimensionValue::Exact(height);
342
0
    let dimsw = DimensionValue::Exact(width + pad);
343
0
    let dims = StaticDimension::new(dimsw, dimsh);
344
345
0
    let cfg = SpannedConfig::from(cfg);
346
347
0
    match opts.count_rows {
348
0
        Some(limit) => {
349
0
            let records = LimitRows::new(records, limit);
350
0
            let records = build_records(records, contentw, count_columns, Some(limit));
351
0
            IterGrid::new(records, cfg, dims, NoColors).build(f)
352
        }
353
        None => {
354
0
            let records = build_records(records, contentw, count_columns, None);
355
0
            IterGrid::new(records, cfg, dims, NoColors).build(f)
356
        }
357
    }
358
0
}
359
360
0
fn get_count_columns<T>(opts: &Settings, buf: &[Vec<T>]) -> usize {
361
0
    match opts.count_columns {
362
0
        Some(size) => size,
363
0
        None => buf.iter().map(|row| row.len()).max().unwrap_or(0),
364
    }
365
0
}
366
367
0
const fn create_config() -> CompactConfig {
368
0
    CompactConfig::new()
369
0
        .set_padding(Sides::new(
370
0
            Indent::spaced(1),
371
0
            Indent::spaced(1),
372
0
            Indent::zero(),
373
0
            Indent::zero(),
374
        ))
375
0
        .set_alignment_horizontal(AlignmentHorizontal::Left)
376
0
        .set_borders(Style::ascii().get_borders())
377
0
}
378
379
0
fn build_records<I>(
380
0
    records: I,
381
0
    width: DimensionValue,
382
0
    count_columns: usize,
383
0
    count_rows: Option<usize>,
384
0
) -> IterRecords<LimitColumns<TruncateContent<I, StaticDimension>>>
385
0
where
386
0
    I: IntoRecords,
387
{
388
0
    let dims = StaticDimension::new(width, DimensionValue::Exact(0));
389
0
    let records = TruncateContent::new(records, dims);
390
0
    let records = LimitColumns::new(records, count_columns);
391
0
    IterRecords::new(records, count_columns, count_rows)
392
0
}