Coverage Report

Created: 2026-09-14 07:01

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/rust/registry/src/index.crates.io-1949cf8c6b5b557f/tabled-0.22.0/src/tables/compact.rs
Line
Count
Source
1
//! This module contains a [`CompactTable`] table.
2
3
use core::{cmp::max, fmt};
4
5
use crate::{
6
    grid::{
7
        colors::NoColors,
8
        config::{AlignmentHorizontal, CompactConfig, Indent, Sides},
9
        dimension::{ConstDimension, ConstSize, Dimension},
10
        records::{
11
            into_records::{LimitColumns, LimitRows, SkipRows},
12
            IntoRecords, IterRecords,
13
        },
14
        util::string::get_line_width,
15
        CompactGrid,
16
    },
17
    settings::{style::Style, TableOption},
18
};
19
20
/// A table which consumes an [`IntoRecords`] iterator.
21
/// It assumes that the content has only single line.
22
///
23
/// In contrast to [`Table`] [`CompactTable`] does no allocations but it consumes an iterator.
24
/// It's useful when you don't want to re/allocate a buffer for your data.
25
///
26
/// # Example
27
///
28
/// It works smoothly with arrays.
29
///
30
#[cfg_attr(feature = "std", doc = "```")]
31
#[cfg_attr(not(feature = "std"), doc = "```ignore")]
32
/// use tabled::{settings::Style, tables::CompactTable};
33
///
34
/// let data = [
35
///     ["FreeBSD", "1993", "William and Lynne Jolitz", "?"],
36
///     ["OpenBSD", "1995", "Theo de Raadt", ""],
37
///     ["HardenedBSD", "2014", "Oliver Pinter and Shawn Webb", ""],
38
/// ];
39
///
40
/// let table = CompactTable::from(data)
41
///     .with(Style::psql())
42
///     .to_string();
43
///
44
/// assert_eq!(
45
///     table,
46
///     concat!(
47
///         " FreeBSD     | 1993 | William and Lynne Jolitz     | ? \n",
48
///         " OpenBSD     | 1995 | Theo de Raadt                |   \n",
49
///         " HardenedBSD | 2014 | Oliver Pinter and Shawn Webb |   ",
50
///     )
51
/// );
52
/// ```
53
///
54
/// But it's default creation requires to be given an estimated cell width, and the amount of columns.
55
///
56
#[cfg_attr(feature = "std", doc = "```")]
57
#[cfg_attr(not(feature = "std"), doc = "```ignore")]
58
/// use tabled::{settings::Style, tables::CompactTable};
59
///
60
/// let data = [
61
///     ["FreeBSD", "1993", "William and Lynne Jolitz", "?"],
62
///     ["OpenBSD", "1995", "Theo de Raadt", ""],
63
///     ["HardenedBSD", "2014", "Oliver Pinter and Shawn Webb", ""],
64
/// ];
65
///
66
/// // See what will happen if the given width is too narrow
67
///
68
/// let table = CompactTable::new(&data)
69
///     .columns(4)
70
///     .width(5)
71
///     .with(Style::ascii())
72
///     .to_string();
73
///
74
/// assert_eq!(
75
///     table,
76
///     "+-----+-----+-----+-----+\n\
77
///      | FreeBSD | 1993 | William and Lynne Jolitz | ?   |\n\
78
///      |-----+-----+-----+-----|\n\
79
///      | OpenBSD | 1995 | Theo de Raadt |     |\n\
80
///      |-----+-----+-----+-----|\n\
81
///      | HardenedBSD | 2014 | Oliver Pinter and Shawn Webb |     |\n\
82
///      +-----+-----+-----+-----+"
83
/// );
84
/// ```
85
///
86
/// [`Table`]: crate::Table
87
#[derive(Debug, Clone)]
88
pub struct CompactTable<I, D> {
89
    records: I,
90
    cfg: CompactConfig,
91
    dims: D,
92
    count_columns: usize,
93
    count_rows: Option<usize>,
94
    skip_rows: usize,
95
}
96
97
impl<I> CompactTable<I, ConstDimension<0, 0>> {
98
    /// Creates a new [`CompactTable`] structure with a width dimension for all columns.
99
0
    pub const fn new(iter: I) -> Self
100
0
    where
101
0
        I: IntoRecords,
102
    {
103
0
        Self {
104
0
            records: iter,
105
0
            cfg: create_config(),
106
0
            count_columns: 0,
107
0
            count_rows: None,
108
0
            skip_rows: 0,
109
0
            dims: ConstDimension::new(ConstSize::Value(2), ConstSize::Value(1)),
110
0
        }
111
0
    }
112
}
113
114
impl<I, const ROWS: usize, const COLS: usize> CompactTable<I, ConstDimension<COLS, ROWS>> {
115
    /// Set a height for each row.
116
0
    pub fn height<S: Into<ConstSize<COUNT_ROWS>>, const COUNT_ROWS: usize>(
117
0
        self,
118
0
        size: S,
119
0
    ) -> CompactTable<I, ConstDimension<COLS, COUNT_ROWS>> {
120
0
        let (width, _) = self.dims.into();
121
0
        CompactTable {
122
0
            dims: ConstDimension::new(width, size.into()),
123
0
            records: self.records,
124
0
            cfg: self.cfg,
125
0
            count_columns: self.count_columns,
126
0
            count_rows: self.count_rows,
127
0
            skip_rows: self.skip_rows,
128
0
        }
129
0
    }
130
131
    /// Set a width for each column.
132
0
    pub fn width<S: Into<ConstSize<COUNT_COLUMNS>>, const COUNT_COLUMNS: usize>(
133
0
        self,
134
0
        size: S,
135
0
    ) -> CompactTable<I, ConstDimension<COUNT_COLUMNS, ROWS>> {
136
0
        let (_, height) = self.dims.into();
137
0
        CompactTable {
138
0
            dims: ConstDimension::new(size.into(), height),
139
0
            records: self.records,
140
0
            cfg: self.cfg,
141
0
            count_columns: self.count_columns,
142
0
            count_rows: self.count_rows,
143
0
            skip_rows: self.skip_rows,
144
0
        }
145
0
    }
146
}
147
148
impl<I, D> CompactTable<I, D> {
149
    /// Creates a new [`CompactTable`] structure with a known dimension.
150
    ///
151
    /// Notice that the function wont call [`Estimate`].
152
    ///
153
    /// [`Estimate`]: crate::grid::dimension::Estimate
154
0
    pub fn with_dimension(iter: I, dimension: D) -> Self
155
0
    where
156
0
        I: IntoRecords,
157
    {
158
0
        Self {
159
0
            records: iter,
160
0
            dims: dimension,
161
0
            cfg: create_config(),
162
0
            count_columns: 0,
163
0
            count_rows: None,
164
0
            skip_rows: 0,
165
0
        }
166
0
    }
167
168
    /// With is a generic function which applies options to the [`CompactTable`].
169
0
    pub fn with<O>(mut self, option: O) -> Self
170
0
    where
171
0
        for<'a> O: TableOption<IterRecords<&'a I>, CompactConfig, D>,
172
    {
173
0
        let mut records = IterRecords::new(&self.records, self.count_columns, self.count_rows);
174
0
        option.change(&mut records, &mut self.cfg, &mut self.dims);
175
176
0
        self
177
0
    }
178
179
    /// Limit a number of rows.
180
0
    pub const fn rows(mut self, count_rows: usize) -> Self {
181
0
        self.count_rows = Some(count_rows);
182
0
        self
183
0
    }
184
185
    /// Limit a number of columns.
186
0
    pub const fn columns(mut self, count: usize) -> Self {
187
0
        self.count_columns = count;
188
0
        self
189
0
    }
190
191
    /// Skip a number of rows from the top.
192
    ///
193
    /// It's applied before [`CompactTable::rows`], so the row limit counts the
194
    /// remaining rows.
195
    ///
196
    #[cfg_attr(feature = "std", doc = "```")]
197
    #[cfg_attr(not(feature = "std"), doc = "```ignore")]
198
    /// use tabled::{settings::Style, tables::CompactTable};
199
    ///
200
    /// let data = [
201
    ///     ["FreeBSD", "1993"],
202
    ///     ["OpenBSD", "1995"],
203
    ///     ["HardenedBSD", "2014"],
204
    /// ];
205
    ///
206
    /// let table = CompactTable::from(data)
207
    ///     .skip(1)
208
    ///     .with(Style::psql())
209
    ///     .to_string();
210
    ///
211
    /// assert_eq!(
212
    ///     table,
213
    ///     concat!(
214
    ///         " OpenBSD     | 1995 \n",
215
    ///         " HardenedBSD | 2014 ",
216
    ///     )
217
    /// );
218
    /// ```
219
0
    pub const fn skip(mut self, count: usize) -> Self {
220
0
        self.skip_rows = count;
221
0
        self
222
0
    }
223
224
    /// Returns a table config.
225
0
    pub fn get_config(&self) -> &CompactConfig {
226
0
        &self.cfg
227
0
    }
228
229
    /// Returns a table config.
230
0
    pub fn get_config_mut(&mut self) -> &mut CompactConfig {
231
0
        &mut self.cfg
232
0
    }
233
234
    /// Format table into [fmt::Write]er.
235
0
    pub fn fmt<W>(self, writer: W) -> fmt::Result
236
0
    where
237
0
        I: IntoRecords,
238
0
        I::Cell: AsRef<str>,
239
0
        D: Dimension,
240
0
        W: fmt::Write,
241
    {
242
0
        build_grid(
243
0
            writer,
244
0
            self.records,
245
0
            self.dims,
246
0
            self.cfg,
247
0
            self.count_columns,
248
0
            self.count_rows,
249
0
            self.skip_rows,
250
        )
251
0
    }
252
253
    /// Format table into a writer.
254
    #[cfg(feature = "std")]
255
    #[cfg_attr(docsrs, doc(cfg(feature = "std")))]
256
0
    pub fn build<W>(self, writer: W) -> std::io::Result<()>
257
0
    where
258
0
        I: IntoRecords,
259
0
        I::Cell: AsRef<str>,
260
0
        D: Dimension,
261
0
        W: std::io::Write,
262
    {
263
0
        let writer = crate::util::utf8_writer::UTF8Writer::new(writer);
264
0
        self.fmt(writer).map_err(std::io::Error::other)
265
0
    }
266
267
    /// Build a string.
268
    ///
269
    /// We can't implement [`std::string::ToString`] cause it does takes `&self` reference.
270
    #[allow(clippy::inherent_to_string)]
271
    #[cfg(feature = "std")]
272
    #[cfg_attr(docsrs, doc(cfg(feature = "std")))]
273
0
    pub fn to_string(self) -> String
274
0
    where
275
0
        I: IntoRecords,
276
0
        I::Cell: AsRef<str>,
277
0
        D: Dimension,
278
    {
279
0
        let mut buf = String::new();
280
0
        self.fmt(&mut buf)
281
0
            .expect("it's expected to be ok according to doc");
282
283
0
        buf
284
0
    }
285
}
286
287
impl<T, const ROWS: usize, const COLS: usize> From<[[T; COLS]; ROWS]>
288
    for CompactTable<[[T; COLS]; ROWS], ConstDimension<COLS, ROWS>>
289
where
290
    T: AsRef<str>,
291
{
292
0
    fn from(mat: [[T; COLS]; ROWS]) -> Self {
293
0
        let mut width = [0; COLS];
294
0
        for row in mat.iter() {
295
0
            for (col, text) in row.iter().enumerate() {
296
0
                let text = text.as_ref();
297
0
                let text_width = get_line_width(text);
298
0
                width[col] = max(width[col], text_width);
299
0
            }
300
        }
301
302
        // add padding
303
0
        for w in &mut width {
304
0
            *w += 2;
305
0
        }
306
307
0
        let dims = ConstDimension::new(ConstSize::List(width), ConstSize::Value(1));
308
0
        Self::with_dimension(mat, dims).columns(COLS).rows(ROWS)
309
0
    }
310
}
311
312
0
fn build_grid<W, I, D>(
313
0
    writer: W,
314
0
    records: I,
315
0
    dims: D,
316
0
    config: CompactConfig,
317
0
    cols: usize,
318
0
    rows: Option<usize>,
319
0
    skip: usize,
320
0
) -> fmt::Result
321
0
where
322
0
    W: fmt::Write,
323
0
    I: IntoRecords,
324
0
    I::Cell: AsRef<str>,
325
0
    D: Dimension,
326
{
327
0
    let records = SkipRows::new(records, skip);
328
329
0
    match rows {
330
0
        Some(limit) => {
331
0
            let records = LimitRows::new(records, limit);
332
0
            let records = LimitColumns::new(records, cols);
333
0
            let records = IterRecords::new(records, cols, rows);
334
0
            CompactGrid::new(records, config, dims, NoColors).build(writer)
335
        }
336
        None => {
337
0
            let records = LimitColumns::new(records, cols);
338
0
            let records = IterRecords::new(records, cols, rows);
339
0
            CompactGrid::new(records, config, dims, NoColors).build(writer)
340
        }
341
    }
342
0
}
343
344
0
const fn create_config() -> CompactConfig {
345
0
    CompactConfig::new()
346
0
        .set_padding(Sides::new(
347
0
            Indent::spaced(1),
348
0
            Indent::spaced(1),
349
0
            Indent::zero(),
350
0
            Indent::zero(),
351
        ))
352
0
        .set_alignment_horizontal(AlignmentHorizontal::Left)
353
0
        .set_borders(Style::ascii().get_borders())
354
0
}
355
356
impl<R, D> TableOption<R, CompactConfig, D> for CompactConfig {
357
0
    fn change(self, _: &mut R, cfg: &mut CompactConfig, _: &mut D) {
358
0
        *cfg = self;
359
0
    }
360
}