/rust/registry/src/index.crates.io-1949cf8c6b5b557f/tabled-0.20.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}, |
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 | | } |
95 | | |
96 | | impl<I> CompactTable<I, ConstDimension<0, 0>> { |
97 | | /// Creates a new [`CompactTable`] structure with a width dimension for all columns. |
98 | 0 | pub const fn new(iter: I) -> Self |
99 | 0 | where |
100 | 0 | I: IntoRecords, |
101 | | { |
102 | 0 | Self { |
103 | 0 | records: iter, |
104 | 0 | cfg: create_config(), |
105 | 0 | count_columns: 0, |
106 | 0 | count_rows: None, |
107 | 0 | dims: ConstDimension::new(ConstSize::Value(2), ConstSize::Value(1)), |
108 | 0 | } |
109 | 0 | } |
110 | | } |
111 | | |
112 | | impl<I, const ROWS: usize, const COLS: usize> CompactTable<I, ConstDimension<COLS, ROWS>> { |
113 | | /// Set a height for each row. |
114 | 0 | pub fn height<S: Into<ConstSize<COUNT_ROWS>>, const COUNT_ROWS: usize>( |
115 | 0 | self, |
116 | 0 | size: S, |
117 | 0 | ) -> CompactTable<I, ConstDimension<COLS, COUNT_ROWS>> { |
118 | 0 | let (width, _) = self.dims.into(); |
119 | 0 | CompactTable { |
120 | 0 | dims: ConstDimension::new(width, size.into()), |
121 | 0 | records: self.records, |
122 | 0 | cfg: self.cfg, |
123 | 0 | count_columns: self.count_columns, |
124 | 0 | count_rows: self.count_rows, |
125 | 0 | } |
126 | 0 | } |
127 | | |
128 | | /// Set a width for each column. |
129 | 0 | pub fn width<S: Into<ConstSize<COUNT_COLUMNS>>, const COUNT_COLUMNS: usize>( |
130 | 0 | self, |
131 | 0 | size: S, |
132 | 0 | ) -> CompactTable<I, ConstDimension<COUNT_COLUMNS, ROWS>> { |
133 | 0 | let (_, height) = self.dims.into(); |
134 | 0 | CompactTable { |
135 | 0 | dims: ConstDimension::new(size.into(), height), |
136 | 0 | records: self.records, |
137 | 0 | cfg: self.cfg, |
138 | 0 | count_columns: self.count_columns, |
139 | 0 | count_rows: self.count_rows, |
140 | 0 | } |
141 | 0 | } |
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 | 0 | pub fn with_dimension(iter: I, dimension: D) -> Self |
151 | 0 | where |
152 | 0 | I: IntoRecords, |
153 | | { |
154 | 0 | Self { |
155 | 0 | records: iter, |
156 | 0 | dims: dimension, |
157 | 0 | cfg: create_config(), |
158 | 0 | count_columns: 0, |
159 | 0 | count_rows: None, |
160 | 0 | } |
161 | 0 | } |
162 | | |
163 | | /// With is a generic function which applies options to the [`CompactTable`]. |
164 | 0 | pub fn with<O>(mut self, option: O) -> Self |
165 | 0 | where |
166 | 0 | for<'a> O: TableOption<IterRecords<&'a I>, CompactConfig, D>, |
167 | | { |
168 | 0 | let mut records = IterRecords::new(&self.records, self.count_columns, self.count_rows); |
169 | 0 | option.change(&mut records, &mut self.cfg, &mut self.dims); |
170 | | |
171 | 0 | self |
172 | 0 | } |
173 | | |
174 | | /// Limit a number of rows. |
175 | 0 | pub const fn rows(mut self, count_rows: usize) -> Self { |
176 | 0 | self.count_rows = Some(count_rows); |
177 | 0 | self |
178 | 0 | } |
179 | | |
180 | | /// Limit a number of columns. |
181 | 0 | pub const fn columns(mut self, count: usize) -> Self { |
182 | 0 | self.count_columns = count; |
183 | 0 | self |
184 | 0 | } |
185 | | |
186 | | /// Returns a table config. |
187 | 0 | pub fn get_config(&self) -> &CompactConfig { |
188 | 0 | &self.cfg |
189 | 0 | } |
190 | | |
191 | | /// Returns a table config. |
192 | 0 | pub fn get_config_mut(&mut self) -> &mut CompactConfig { |
193 | 0 | &mut self.cfg |
194 | 0 | } |
195 | | |
196 | | /// Format table into [fmt::Write]er. |
197 | 0 | pub fn fmt<W>(self, writer: W) -> fmt::Result |
198 | 0 | where |
199 | 0 | I: IntoRecords, |
200 | 0 | I::Cell: AsRef<str>, |
201 | 0 | D: Dimension, |
202 | 0 | W: fmt::Write, |
203 | | { |
204 | 0 | build_grid( |
205 | 0 | writer, |
206 | 0 | self.records, |
207 | 0 | self.dims, |
208 | 0 | self.cfg, |
209 | 0 | self.count_columns, |
210 | 0 | self.count_rows, |
211 | | ) |
212 | 0 | } |
213 | | |
214 | | /// Format table into a writer. |
215 | | #[cfg(feature = "std")] |
216 | | #[cfg_attr(docsrs, doc(cfg(feature = "std")))] |
217 | 0 | pub fn build<W>(self, writer: W) -> std::io::Result<()> |
218 | 0 | where |
219 | 0 | I: IntoRecords, |
220 | 0 | I::Cell: AsRef<str>, |
221 | 0 | D: Dimension, |
222 | 0 | W: std::io::Write, |
223 | | { |
224 | 0 | let writer = crate::util::utf8_writer::UTF8Writer::new(writer); |
225 | 0 | self.fmt(writer).map_err(std::io::Error::other) |
226 | 0 | } |
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 | 0 | pub fn to_string(self) -> String |
235 | 0 | where |
236 | 0 | I: IntoRecords, |
237 | 0 | I::Cell: AsRef<str>, |
238 | 0 | D: Dimension, |
239 | | { |
240 | 0 | let mut buf = String::new(); |
241 | 0 | self.fmt(&mut buf) |
242 | 0 | .expect("it's expected to be ok according to doc"); |
243 | | |
244 | 0 | buf |
245 | 0 | } |
246 | | } |
247 | | |
248 | | impl<T, const ROWS: usize, const COLS: usize> From<[[T; COLS]; ROWS]> |
249 | | for CompactTable<[[T; COLS]; ROWS], ConstDimension<COLS, ROWS>> |
250 | | where |
251 | | T: AsRef<str>, |
252 | | { |
253 | 0 | fn from(mat: [[T; COLS]; ROWS]) -> Self { |
254 | 0 | let mut width = [0; COLS]; |
255 | 0 | for row in mat.iter() { |
256 | 0 | for (col, text) in row.iter().enumerate() { |
257 | 0 | let text = text.as_ref(); |
258 | 0 | let text_width = get_line_width(text); |
259 | 0 | width[col] = max(width[col], text_width); |
260 | 0 | } |
261 | | } |
262 | | |
263 | | // add padding |
264 | 0 | for w in &mut width { |
265 | 0 | *w += 2; |
266 | 0 | } |
267 | | |
268 | 0 | let dims = ConstDimension::new(ConstSize::List(width), ConstSize::Value(1)); |
269 | 0 | Self::with_dimension(mat, dims).columns(COLS).rows(ROWS) |
270 | 0 | } |
271 | | } |
272 | | |
273 | 0 | fn build_grid<W, I, D>( |
274 | 0 | writer: W, |
275 | 0 | records: I, |
276 | 0 | dims: D, |
277 | 0 | config: CompactConfig, |
278 | 0 | cols: usize, |
279 | 0 | rows: Option<usize>, |
280 | 0 | ) -> fmt::Result |
281 | 0 | where |
282 | 0 | W: fmt::Write, |
283 | 0 | I: IntoRecords, |
284 | 0 | I::Cell: AsRef<str>, |
285 | 0 | D: Dimension, |
286 | | { |
287 | 0 | match rows { |
288 | 0 | Some(limit) => { |
289 | 0 | let records = LimitRows::new(records, limit); |
290 | 0 | let records = LimitColumns::new(records, cols); |
291 | 0 | let records = IterRecords::new(records, cols, rows); |
292 | 0 | CompactGrid::new(records, config, dims, NoColors).build(writer) |
293 | | } |
294 | | None => { |
295 | 0 | let records = LimitColumns::new(records, cols); |
296 | 0 | let records = IterRecords::new(records, cols, rows); |
297 | 0 | CompactGrid::new(records, config, dims, NoColors).build(writer) |
298 | | } |
299 | | } |
300 | 0 | } |
301 | | |
302 | 0 | const fn create_config() -> CompactConfig { |
303 | 0 | CompactConfig::new() |
304 | 0 | .set_padding(Sides::new( |
305 | 0 | Indent::spaced(1), |
306 | 0 | Indent::spaced(1), |
307 | 0 | Indent::zero(), |
308 | 0 | Indent::zero(), |
309 | | )) |
310 | 0 | .set_alignment_horizontal(AlignmentHorizontal::Left) |
311 | 0 | .set_borders(Style::ascii().get_borders()) |
312 | 0 | } |
313 | | |
314 | | impl<R, D> TableOption<R, CompactConfig, D> for CompactConfig { |
315 | 0 | fn change(self, _: &mut R, cfg: &mut CompactConfig, _: &mut D) { |
316 | 0 | *cfg = self; |
317 | 0 | } |
318 | | } |