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 | |
69 | use core::cmp::max; |
70 | use core::fmt; |
71 | |
72 | use 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)] |
89 | pub struct CompactTable<I, D> { |
90 | records: I, |
91 | cfg: CompactConfig, |
92 | dims: D, |
93 | count_columns: usize, |
94 | count_rows: Option<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 | 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 | |
113 | impl<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 | |
145 | impl<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 | |
245 | impl<T, const ROWS: usize, const COLS: usize> From<[[T; COLS]; ROWS]> |
246 | for CompactTable<[[T; COLS]; ROWS], ConstDimension<COLS, ROWS>> |
247 | where |
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 | |
270 | fn 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 | |
293 | const 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 | |
305 | impl<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 | |