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