/rust/registry/src/index.crates.io-1949cf8c6b5b557f/tabled-0.20.0/src/tables/iter.rs
Line | Count | Source |
1 | | //! This module contains a [`IterTable`] table. |
2 | | //! |
3 | | //! In contrast to [`Table`] [`IterTable`] 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 | | //! ``` |
9 | | //! use tabled::{grid::records::IterRecords, tables::IterTable}; |
10 | | //! |
11 | | //! let iterator = vec![vec!["First", "row"], vec!["Second", "row"]]; |
12 | | //! let records = IterRecords::new(iterator, 2, Some(2)); |
13 | | //! let table = IterTable::new(records); |
14 | | //! |
15 | | //! let s = table.to_string(); |
16 | | //! |
17 | | //! assert_eq!( |
18 | | //! s, |
19 | | //! "+--------+-----+\n\ |
20 | | //! | First | row |\n\ |
21 | | //! +--------+-----+\n\ |
22 | | //! | Second | row |\n\ |
23 | | //! +--------+-----+", |
24 | | //! ); |
25 | | //! ``` |
26 | | //! |
27 | | //! [`Table`]: crate::Table |
28 | | |
29 | | use core::iter::FromIterator; |
30 | | use std::{fmt, io}; |
31 | | |
32 | | use crate::{ |
33 | | grid::{ |
34 | | colors::NoColors, |
35 | | config::{AlignmentHorizontal, CompactConfig, Indent, Sides, SpannedConfig}, |
36 | | dimension::{CompactGridDimension, DimensionValue, StaticDimension, ZeroDimension}, |
37 | | records::{ |
38 | | into_records::{BufRecords, LimitColumns, LimitRows, TruncateContent}, |
39 | | IntoRecords, IterRecords, |
40 | | }, |
41 | | IterGrid, |
42 | | }, |
43 | | settings::{Style, TableOption}, |
44 | | }; |
45 | | |
46 | | use crate::util::utf8_writer::UTF8Writer; |
47 | | |
48 | | /// A table which consumes an [`IntoRecords`] iterator. |
49 | | /// |
50 | | /// To be able to build table we need a dimensions. |
51 | | /// If no width and count_columns is set, [`IterTable`] will sniff the records, by |
52 | | /// keeping a number of rows buffered (You can set the number via [`IterTable::sniff`]). |
53 | | /// |
54 | | /// In contrast to [`Table`] [`IterTable`] does no allocations but it consumes an iterator. |
55 | | /// It's useful when you don't want to re/allocate a buffer for your data. |
56 | | /// |
57 | | /// # Example |
58 | | /// |
59 | | /// ``` |
60 | | /// use tabled::{grid::records::IterRecords, tables::IterTable}; |
61 | | /// use tabled::assert::assert_table; |
62 | | /// |
63 | | /// let data = vec![ |
64 | | /// vec!["First", "row"], |
65 | | /// vec!["Second", "row"], |
66 | | /// vec!["Third", "big \n multiline row"], |
67 | | /// ]; |
68 | | /// |
69 | | /// let records = IterRecords::new(data, 2, Some(2)); |
70 | | /// let mut table = IterTable::new(records); |
71 | | /// table.sniff(1); |
72 | | /// |
73 | | /// // notice because of sniff 1 we have all rows after the first one being truncated |
74 | | /// assert_table!( |
75 | | /// table.to_string(), |
76 | | /// "+-------+-----+" |
77 | | /// "| First | row |" |
78 | | /// "+-------+-----+" |
79 | | /// "| Secon | row |" |
80 | | /// "+-------+-----+" |
81 | | /// "| Third | big |" |
82 | | /// "+-------+-----+" |
83 | | /// ); |
84 | | /// ``` |
85 | | /// |
86 | | /// [`Table`]: crate::Table |
87 | | #[derive(Debug, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)] |
88 | | pub struct IterTable<I> { |
89 | | records: I, |
90 | | cfg: CompactConfig, |
91 | | table: Settings, |
92 | | } |
93 | | |
94 | | #[derive(Debug, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)] |
95 | | struct Settings { |
96 | | sniff: usize, |
97 | | count_columns: Option<usize>, |
98 | | count_rows: Option<usize>, |
99 | | width: Option<usize>, |
100 | | height: Option<usize>, |
101 | | } |
102 | | |
103 | | impl<I> IterTable<I> { |
104 | | /// Creates a new [`IterTable`] structure. |
105 | 0 | pub fn new(iter: I) -> Self |
106 | 0 | where |
107 | 0 | I: IntoRecords, |
108 | | { |
109 | 0 | Self { |
110 | 0 | records: iter, |
111 | 0 | cfg: create_config(), |
112 | 0 | table: Settings { |
113 | 0 | sniff: 1000, |
114 | 0 | count_columns: None, |
115 | 0 | count_rows: None, |
116 | 0 | height: None, |
117 | 0 | width: None, |
118 | 0 | }, |
119 | 0 | } |
120 | 0 | } |
121 | | |
122 | | /// With is a generic function which applies options to the [`IterTable`]. |
123 | 0 | pub fn with<O>(&mut self, option: O) -> &mut Self |
124 | 0 | where |
125 | 0 | O: TableOption<I, CompactConfig, ZeroDimension>, |
126 | | { |
127 | 0 | let mut dims = ZeroDimension::new(); |
128 | 0 | option.change(&mut self.records, &mut self.cfg, &mut dims); |
129 | | |
130 | 0 | self |
131 | 0 | } |
132 | | |
133 | | /// Limit a number of columns. |
134 | 0 | pub fn columns(&mut self, count_columns: usize) -> &mut Self { |
135 | 0 | self.table.count_columns = Some(count_columns); |
136 | 0 | self |
137 | 0 | } |
138 | | |
139 | | /// Limit a number of rows. |
140 | 0 | pub fn rows(&mut self, count_rows: usize) -> &mut Self { |
141 | 0 | self.table.count_rows = Some(count_rows); |
142 | 0 | self |
143 | 0 | } |
144 | | |
145 | | /// Limit an amount of rows will be read for dimension estimations. |
146 | | /// |
147 | | /// By default it's 1000. |
148 | 0 | pub fn sniff(&mut self, count: usize) -> &mut Self { |
149 | 0 | self.table.sniff = count; |
150 | 0 | self |
151 | 0 | } |
152 | | |
153 | | /// Set a height for each row. |
154 | 0 | pub fn height(&mut self, size: usize) -> &mut Self { |
155 | 0 | self.table.height = Some(size); |
156 | 0 | self |
157 | 0 | } |
158 | | |
159 | | /// Set a width for each column. |
160 | 0 | pub fn width(&mut self, size: usize) -> &mut Self { |
161 | 0 | self.table.width = Some(size); |
162 | 0 | self |
163 | 0 | } |
164 | | |
165 | | /// Build a string. |
166 | | /// |
167 | | /// We can't implement [`std::string::ToString`] cause it does takes `&self` reference. |
168 | | #[allow(clippy::inherent_to_string)] |
169 | 0 | pub fn to_string(self) -> String |
170 | 0 | where |
171 | 0 | I: IntoRecords, |
172 | 0 | I::Cell: AsRef<str>, |
173 | | { |
174 | 0 | let mut buf = String::new(); |
175 | 0 | self.fmt(&mut buf) |
176 | 0 | .expect("according to a doc is safe to fmt() a string"); |
177 | | |
178 | 0 | buf |
179 | 0 | } |
180 | | |
181 | | /// Format table into [`io::Write`]r. |
182 | 0 | pub fn build<W>(self, writer: W) -> io::Result<()> |
183 | 0 | where |
184 | 0 | W: io::Write, |
185 | 0 | I: IntoRecords, |
186 | 0 | I::Cell: AsRef<str>, |
187 | | { |
188 | 0 | let writer = UTF8Writer::new(writer); |
189 | 0 | self.fmt(writer).map_err(io::Error::other) |
190 | 0 | } |
191 | | |
192 | | /// Format table into [fmt::Write]er. |
193 | 0 | pub fn fmt<W>(self, writer: W) -> fmt::Result |
194 | 0 | where |
195 | 0 | W: fmt::Write, |
196 | 0 | I: IntoRecords, |
197 | 0 | I::Cell: AsRef<str>, |
198 | | { |
199 | 0 | build_grid(writer, self.records, self.cfg, self.table) |
200 | 0 | } |
201 | | } |
202 | | |
203 | | impl<T> FromIterator<T> for IterTable<Vec<Vec<T::Item>>> |
204 | | where |
205 | | T: IntoIterator, |
206 | | T::Item: AsRef<str>, |
207 | | { |
208 | 0 | fn from_iter<I: IntoIterator<Item = T>>(iter: I) -> Self { |
209 | 0 | let data = iter |
210 | 0 | .into_iter() |
211 | 0 | .map(|row| row.into_iter().collect()) |
212 | 0 | .collect(); |
213 | | |
214 | 0 | Self::new(data) |
215 | 0 | } |
216 | | } |
217 | | |
218 | 0 | fn build_grid<W, I>(f: W, iter: I, cfg: CompactConfig, opts: Settings) -> fmt::Result |
219 | 0 | where |
220 | 0 | W: fmt::Write, |
221 | 0 | I: IntoRecords, |
222 | 0 | I::Cell: AsRef<str>, |
223 | | { |
224 | 0 | let width_config = opts.width.is_some() && opts.count_columns.is_some(); |
225 | 0 | if width_config { |
226 | 0 | build_table_with_static_dims(f, iter, cfg, opts) |
227 | 0 | } else if opts.width.is_some() { |
228 | 0 | build_table_sniffing_with_width(f, iter, cfg, opts) |
229 | | } else { |
230 | 0 | build_table_sniffing(f, iter, cfg, opts) |
231 | | } |
232 | 0 | } |
233 | | |
234 | 0 | fn build_table_with_static_dims<W, I>( |
235 | 0 | f: W, |
236 | 0 | iter: I, |
237 | 0 | cfg: CompactConfig, |
238 | 0 | opts: Settings, |
239 | 0 | ) -> fmt::Result |
240 | 0 | where |
241 | 0 | W: fmt::Write, |
242 | 0 | I: IntoRecords, |
243 | 0 | I::Cell: AsRef<str>, |
244 | | { |
245 | 0 | let count_columns = opts.count_columns.unwrap(); |
246 | 0 | let width = opts.width.unwrap(); |
247 | 0 | let height = opts.height.unwrap_or(1); |
248 | 0 | let pad = cfg.get_padding(); |
249 | 0 | let contentw = DimensionValue::Exact(width); |
250 | 0 | let width = DimensionValue::Exact(width + pad.left.size + pad.right.size); |
251 | 0 | let height = DimensionValue::Exact(height + pad.top.size + pad.bottom.size); |
252 | 0 | let dims = StaticDimension::new(width, height); |
253 | 0 | let cfg = SpannedConfig::from(cfg); |
254 | | |
255 | 0 | match opts.count_rows { |
256 | 0 | Some(limit) => { |
257 | 0 | let records = LimitRows::new(iter, limit); |
258 | 0 | let records = build_records(records, contentw, count_columns, Some(limit)); |
259 | 0 | IterGrid::new(records, cfg, dims, NoColors).build(f) |
260 | | } |
261 | | None => { |
262 | 0 | let records = build_records(iter, contentw, count_columns, None); |
263 | 0 | IterGrid::new(records, cfg, dims, NoColors).build(f) |
264 | | } |
265 | | } |
266 | 0 | } |
267 | | |
268 | 0 | fn build_table_sniffing<W, I>(f: W, iter: I, cfg: CompactConfig, opts: Settings) -> fmt::Result |
269 | 0 | where |
270 | 0 | W: fmt::Write, |
271 | 0 | I: IntoRecords, |
272 | 0 | I::Cell: AsRef<str>, |
273 | | { |
274 | 0 | let records = BufRecords::new(iter, opts.sniff); |
275 | | |
276 | 0 | let count_columns = get_count_columns(&opts, records.as_slice()); |
277 | | |
278 | 0 | let (mut width, height) = { |
279 | 0 | let records = LimitColumns::new(records.as_slice(), count_columns); |
280 | 0 | let records = IterRecords::new(records, count_columns, None); |
281 | 0 | CompactGridDimension::dimension(records, &cfg) |
282 | 0 | }; |
283 | | |
284 | 0 | let padding = cfg.get_padding(); |
285 | 0 | let pad = padding.left.size + padding.right.size; |
286 | 0 | let padv = padding.top.size + padding.bottom.size; |
287 | | |
288 | 0 | if opts.sniff == 0 { |
289 | 0 | width = std::iter::repeat_n(pad, count_columns).collect::<Vec<_>>(); |
290 | 0 | } |
291 | | |
292 | 0 | let content_width = DimensionValue::List(width.iter().map(|i| i.saturating_sub(pad)).collect()); |
293 | 0 | let dims_width = DimensionValue::List(width); |
294 | | |
295 | 0 | let height_exact = opts.height.unwrap_or(1) + padv; |
296 | 0 | let mut dims_height = DimensionValue::Partial(height, height_exact); |
297 | | |
298 | 0 | if opts.height.is_some() { |
299 | 0 | dims_height = DimensionValue::Exact(height_exact); |
300 | 0 | } |
301 | | |
302 | 0 | let dims = StaticDimension::new(dims_width, dims_height); |
303 | 0 | let cfg = SpannedConfig::from(cfg); |
304 | | |
305 | 0 | match opts.count_rows { |
306 | 0 | Some(limit) => { |
307 | 0 | let records = LimitRows::new(records, limit); |
308 | 0 | let records = build_records(records, content_width, count_columns, Some(limit)); |
309 | 0 | IterGrid::new(records, cfg, dims, NoColors).build(f) |
310 | | } |
311 | | None => { |
312 | 0 | let records = build_records(records, content_width, count_columns, None); |
313 | 0 | IterGrid::new(records, cfg, dims, NoColors).build(f) |
314 | | } |
315 | | } |
316 | 0 | } |
317 | | |
318 | 0 | fn build_table_sniffing_with_width<W, I>( |
319 | 0 | f: W, |
320 | 0 | iter: I, |
321 | 0 | cfg: CompactConfig, |
322 | 0 | opts: Settings, |
323 | 0 | ) -> fmt::Result |
324 | 0 | where |
325 | 0 | W: fmt::Write, |
326 | 0 | I: IntoRecords, |
327 | 0 | I::Cell: AsRef<str>, |
328 | | { |
329 | 0 | let records = BufRecords::new(iter, opts.sniff); |
330 | | |
331 | 0 | let count_columns = get_count_columns(&opts, records.as_slice()); |
332 | | |
333 | 0 | let width = opts.width.unwrap(); |
334 | 0 | let contentw = DimensionValue::Exact(width); |
335 | | |
336 | 0 | let padding = cfg.get_padding(); |
337 | 0 | let pad = padding.left.size + padding.right.size; |
338 | 0 | let padv = padding.top.size + padding.bottom.size; |
339 | | |
340 | 0 | let height = opts.height.unwrap_or(1) + padv; |
341 | 0 | let dimsh = DimensionValue::Exact(height); |
342 | 0 | let dimsw = DimensionValue::Exact(width + pad); |
343 | 0 | let dims = StaticDimension::new(dimsw, dimsh); |
344 | | |
345 | 0 | let cfg = SpannedConfig::from(cfg); |
346 | | |
347 | 0 | match opts.count_rows { |
348 | 0 | Some(limit) => { |
349 | 0 | let records = LimitRows::new(records, limit); |
350 | 0 | let records = build_records(records, contentw, count_columns, Some(limit)); |
351 | 0 | IterGrid::new(records, cfg, dims, NoColors).build(f) |
352 | | } |
353 | | None => { |
354 | 0 | let records = build_records(records, contentw, count_columns, None); |
355 | 0 | IterGrid::new(records, cfg, dims, NoColors).build(f) |
356 | | } |
357 | | } |
358 | 0 | } |
359 | | |
360 | 0 | fn get_count_columns<T>(opts: &Settings, buf: &[Vec<T>]) -> usize { |
361 | 0 | match opts.count_columns { |
362 | 0 | Some(size) => size, |
363 | 0 | None => buf.iter().map(|row| row.len()).max().unwrap_or(0), |
364 | | } |
365 | 0 | } |
366 | | |
367 | 0 | const fn create_config() -> CompactConfig { |
368 | 0 | CompactConfig::new() |
369 | 0 | .set_padding(Sides::new( |
370 | 0 | Indent::spaced(1), |
371 | 0 | Indent::spaced(1), |
372 | 0 | Indent::zero(), |
373 | 0 | Indent::zero(), |
374 | | )) |
375 | 0 | .set_alignment_horizontal(AlignmentHorizontal::Left) |
376 | 0 | .set_borders(Style::ascii().get_borders()) |
377 | 0 | } |
378 | | |
379 | 0 | fn build_records<I>( |
380 | 0 | records: I, |
381 | 0 | width: DimensionValue, |
382 | 0 | count_columns: usize, |
383 | 0 | count_rows: Option<usize>, |
384 | 0 | ) -> IterRecords<LimitColumns<TruncateContent<I, StaticDimension>>> |
385 | 0 | where |
386 | 0 | I: IntoRecords, |
387 | | { |
388 | 0 | let dims = StaticDimension::new(width, DimensionValue::Exact(0)); |
389 | 0 | let records = TruncateContent::new(records, dims); |
390 | 0 | let records = LimitColumns::new(records, count_columns); |
391 | 0 | IterRecords::new(records, count_columns, count_rows) |
392 | 0 | } |