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