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 | |
68 | use core::cmp::max; |
69 | use core::fmt; |
70 | |
71 | use 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)] |
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 | } |
95 | |
96 | impl<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 | |
112 | impl<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 | |
144 | impl<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 | |
249 | impl<T, const ROWS: usize, const COLS: usize> From<[[T; COLS]; ROWS]> |
250 | for CompactTable<[[T; COLS]; ROWS], ConstDimension<COLS, ROWS>> |
251 | where |
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 | |
274 | fn 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> |
282 | where |
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 | |
303 | const 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 | |
315 | impl<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 | |