/rust/registry/src/index.crates.io-1949cf8c6b5b557f/tabled-0.22.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, SkipRows}, |
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 | | skip_rows: 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 | 0 | pub const fn new(iter: I) -> Self |
100 | 0 | where |
101 | 0 | I: IntoRecords, |
102 | | { |
103 | 0 | Self { |
104 | 0 | records: iter, |
105 | 0 | cfg: create_config(), |
106 | 0 | count_columns: 0, |
107 | 0 | count_rows: None, |
108 | 0 | skip_rows: 0, |
109 | 0 | dims: ConstDimension::new(ConstSize::Value(2), ConstSize::Value(1)), |
110 | 0 | } |
111 | 0 | } |
112 | | } |
113 | | |
114 | | impl<I, const ROWS: usize, const COLS: usize> CompactTable<I, ConstDimension<COLS, ROWS>> { |
115 | | /// Set a height for each row. |
116 | 0 | pub fn height<S: Into<ConstSize<COUNT_ROWS>>, const COUNT_ROWS: usize>( |
117 | 0 | self, |
118 | 0 | size: S, |
119 | 0 | ) -> CompactTable<I, ConstDimension<COLS, COUNT_ROWS>> { |
120 | 0 | let (width, _) = self.dims.into(); |
121 | 0 | CompactTable { |
122 | 0 | dims: ConstDimension::new(width, size.into()), |
123 | 0 | records: self.records, |
124 | 0 | cfg: self.cfg, |
125 | 0 | count_columns: self.count_columns, |
126 | 0 | count_rows: self.count_rows, |
127 | 0 | skip_rows: self.skip_rows, |
128 | 0 | } |
129 | 0 | } |
130 | | |
131 | | /// Set a width for each column. |
132 | 0 | pub fn width<S: Into<ConstSize<COUNT_COLUMNS>>, const COUNT_COLUMNS: usize>( |
133 | 0 | self, |
134 | 0 | size: S, |
135 | 0 | ) -> CompactTable<I, ConstDimension<COUNT_COLUMNS, ROWS>> { |
136 | 0 | let (_, height) = self.dims.into(); |
137 | 0 | CompactTable { |
138 | 0 | dims: ConstDimension::new(size.into(), height), |
139 | 0 | records: self.records, |
140 | 0 | cfg: self.cfg, |
141 | 0 | count_columns: self.count_columns, |
142 | 0 | count_rows: self.count_rows, |
143 | 0 | skip_rows: self.skip_rows, |
144 | 0 | } |
145 | 0 | } |
146 | | } |
147 | | |
148 | | impl<I, D> CompactTable<I, D> { |
149 | | /// Creates a new [`CompactTable`] structure with a known dimension. |
150 | | /// |
151 | | /// Notice that the function wont call [`Estimate`]. |
152 | | /// |
153 | | /// [`Estimate`]: crate::grid::dimension::Estimate |
154 | 0 | pub fn with_dimension(iter: I, dimension: D) -> Self |
155 | 0 | where |
156 | 0 | I: IntoRecords, |
157 | | { |
158 | 0 | Self { |
159 | 0 | records: iter, |
160 | 0 | dims: dimension, |
161 | 0 | cfg: create_config(), |
162 | 0 | count_columns: 0, |
163 | 0 | count_rows: None, |
164 | 0 | skip_rows: 0, |
165 | 0 | } |
166 | 0 | } |
167 | | |
168 | | /// With is a generic function which applies options to the [`CompactTable`]. |
169 | 0 | pub fn with<O>(mut self, option: O) -> Self |
170 | 0 | where |
171 | 0 | for<'a> O: TableOption<IterRecords<&'a I>, CompactConfig, D>, |
172 | | { |
173 | 0 | let mut records = IterRecords::new(&self.records, self.count_columns, self.count_rows); |
174 | 0 | option.change(&mut records, &mut self.cfg, &mut self.dims); |
175 | | |
176 | 0 | self |
177 | 0 | } |
178 | | |
179 | | /// Limit a number of rows. |
180 | 0 | pub const fn rows(mut self, count_rows: usize) -> Self { |
181 | 0 | self.count_rows = Some(count_rows); |
182 | 0 | self |
183 | 0 | } |
184 | | |
185 | | /// Limit a number of columns. |
186 | 0 | pub const fn columns(mut self, count: usize) -> Self { |
187 | 0 | self.count_columns = count; |
188 | 0 | self |
189 | 0 | } |
190 | | |
191 | | /// Skip a number of rows from the top. |
192 | | /// |
193 | | /// It's applied before [`CompactTable::rows`], so the row limit counts the |
194 | | /// remaining rows. |
195 | | /// |
196 | | #[cfg_attr(feature = "std", doc = "```")] |
197 | | #[cfg_attr(not(feature = "std"), doc = "```ignore")] |
198 | | /// use tabled::{settings::Style, tables::CompactTable}; |
199 | | /// |
200 | | /// let data = [ |
201 | | /// ["FreeBSD", "1993"], |
202 | | /// ["OpenBSD", "1995"], |
203 | | /// ["HardenedBSD", "2014"], |
204 | | /// ]; |
205 | | /// |
206 | | /// let table = CompactTable::from(data) |
207 | | /// .skip(1) |
208 | | /// .with(Style::psql()) |
209 | | /// .to_string(); |
210 | | /// |
211 | | /// assert_eq!( |
212 | | /// table, |
213 | | /// concat!( |
214 | | /// " OpenBSD | 1995 \n", |
215 | | /// " HardenedBSD | 2014 ", |
216 | | /// ) |
217 | | /// ); |
218 | | /// ``` |
219 | 0 | pub const fn skip(mut self, count: usize) -> Self { |
220 | 0 | self.skip_rows = count; |
221 | 0 | self |
222 | 0 | } |
223 | | |
224 | | /// Returns a table config. |
225 | 0 | pub fn get_config(&self) -> &CompactConfig { |
226 | 0 | &self.cfg |
227 | 0 | } |
228 | | |
229 | | /// Returns a table config. |
230 | 0 | pub fn get_config_mut(&mut self) -> &mut CompactConfig { |
231 | 0 | &mut self.cfg |
232 | 0 | } |
233 | | |
234 | | /// Format table into [fmt::Write]er. |
235 | 0 | pub fn fmt<W>(self, writer: W) -> fmt::Result |
236 | 0 | where |
237 | 0 | I: IntoRecords, |
238 | 0 | I::Cell: AsRef<str>, |
239 | 0 | D: Dimension, |
240 | 0 | W: fmt::Write, |
241 | | { |
242 | 0 | build_grid( |
243 | 0 | writer, |
244 | 0 | self.records, |
245 | 0 | self.dims, |
246 | 0 | self.cfg, |
247 | 0 | self.count_columns, |
248 | 0 | self.count_rows, |
249 | 0 | self.skip_rows, |
250 | | ) |
251 | 0 | } |
252 | | |
253 | | /// Format table into a writer. |
254 | | #[cfg(feature = "std")] |
255 | | #[cfg_attr(docsrs, doc(cfg(feature = "std")))] |
256 | 0 | pub fn build<W>(self, writer: W) -> std::io::Result<()> |
257 | 0 | where |
258 | 0 | I: IntoRecords, |
259 | 0 | I::Cell: AsRef<str>, |
260 | 0 | D: Dimension, |
261 | 0 | W: std::io::Write, |
262 | | { |
263 | 0 | let writer = crate::util::utf8_writer::UTF8Writer::new(writer); |
264 | 0 | self.fmt(writer).map_err(std::io::Error::other) |
265 | 0 | } |
266 | | |
267 | | /// Build a string. |
268 | | /// |
269 | | /// We can't implement [`std::string::ToString`] cause it does takes `&self` reference. |
270 | | #[allow(clippy::inherent_to_string)] |
271 | | #[cfg(feature = "std")] |
272 | | #[cfg_attr(docsrs, doc(cfg(feature = "std")))] |
273 | 0 | pub fn to_string(self) -> String |
274 | 0 | where |
275 | 0 | I: IntoRecords, |
276 | 0 | I::Cell: AsRef<str>, |
277 | 0 | D: Dimension, |
278 | | { |
279 | 0 | let mut buf = String::new(); |
280 | 0 | self.fmt(&mut buf) |
281 | 0 | .expect("it's expected to be ok according to doc"); |
282 | | |
283 | 0 | buf |
284 | 0 | } |
285 | | } |
286 | | |
287 | | impl<T, const ROWS: usize, const COLS: usize> From<[[T; COLS]; ROWS]> |
288 | | for CompactTable<[[T; COLS]; ROWS], ConstDimension<COLS, ROWS>> |
289 | | where |
290 | | T: AsRef<str>, |
291 | | { |
292 | 0 | fn from(mat: [[T; COLS]; ROWS]) -> Self { |
293 | 0 | let mut width = [0; COLS]; |
294 | 0 | for row in mat.iter() { |
295 | 0 | for (col, text) in row.iter().enumerate() { |
296 | 0 | let text = text.as_ref(); |
297 | 0 | let text_width = get_line_width(text); |
298 | 0 | width[col] = max(width[col], text_width); |
299 | 0 | } |
300 | | } |
301 | | |
302 | | // add padding |
303 | 0 | for w in &mut width { |
304 | 0 | *w += 2; |
305 | 0 | } |
306 | | |
307 | 0 | let dims = ConstDimension::new(ConstSize::List(width), ConstSize::Value(1)); |
308 | 0 | Self::with_dimension(mat, dims).columns(COLS).rows(ROWS) |
309 | 0 | } |
310 | | } |
311 | | |
312 | 0 | fn build_grid<W, I, D>( |
313 | 0 | writer: W, |
314 | 0 | records: I, |
315 | 0 | dims: D, |
316 | 0 | config: CompactConfig, |
317 | 0 | cols: usize, |
318 | 0 | rows: Option<usize>, |
319 | 0 | skip: usize, |
320 | 0 | ) -> fmt::Result |
321 | 0 | where |
322 | 0 | W: fmt::Write, |
323 | 0 | I: IntoRecords, |
324 | 0 | I::Cell: AsRef<str>, |
325 | 0 | D: Dimension, |
326 | | { |
327 | 0 | let records = SkipRows::new(records, skip); |
328 | | |
329 | 0 | match rows { |
330 | 0 | Some(limit) => { |
331 | 0 | let records = LimitRows::new(records, limit); |
332 | 0 | let records = LimitColumns::new(records, cols); |
333 | 0 | let records = IterRecords::new(records, cols, rows); |
334 | 0 | CompactGrid::new(records, config, dims, NoColors).build(writer) |
335 | | } |
336 | | None => { |
337 | 0 | let records = LimitColumns::new(records, cols); |
338 | 0 | let records = IterRecords::new(records, cols, rows); |
339 | 0 | CompactGrid::new(records, config, dims, NoColors).build(writer) |
340 | | } |
341 | | } |
342 | 0 | } |
343 | | |
344 | 0 | const fn create_config() -> CompactConfig { |
345 | 0 | CompactConfig::new() |
346 | 0 | .set_padding(Sides::new( |
347 | 0 | Indent::spaced(1), |
348 | 0 | Indent::spaced(1), |
349 | 0 | Indent::zero(), |
350 | 0 | Indent::zero(), |
351 | | )) |
352 | 0 | .set_alignment_horizontal(AlignmentHorizontal::Left) |
353 | 0 | .set_borders(Style::ascii().get_borders()) |
354 | 0 | } |
355 | | |
356 | | impl<R, D> TableOption<R, CompactConfig, D> for CompactConfig { |
357 | 0 | fn change(self, _: &mut R, cfg: &mut CompactConfig, _: &mut D) { |
358 | 0 | *cfg = self; |
359 | 0 | } |
360 | | } |