1use core::iter::FromIterator;
30use std::{fmt, io};
31
32use 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
46use crate::util::utf8_writer::UTF8Writer;
47
48#[derive(Debug, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)]
88pub struct IterTable<I> {
89 records: I,
90 cfg: CompactConfig,
91 table: Settings,
92}
93
94#[derive(Debug, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)]
95struct Settings {
96 sniff: usize,
97 count_columns: Option<usize>,
98 count_rows: Option<usize>,
99 width: Option<usize>,
100 height: Option<usize>,
101}
102
103impl<I> IterTable<I> {
104 pub fn new(iter: I) -> Self
106 where
107 I: IntoRecords,
108 {
109 Self {
110 records: iter,
111 cfg: create_config(),
112 table: Settings {
113 sniff: 1000,
114 count_columns: None,
115 count_rows: None,
116 height: None,
117 width: None,
118 },
119 }
120 }
121
122 pub fn with<O>(&mut self, option: O) -> &mut Self
124 where
125 O: TableOption<I, CompactConfig, ZeroDimension>,
126 {
127 let mut dims = ZeroDimension::new();
128 option.change(&mut self.records, &mut self.cfg, &mut dims);
129
130 self
131 }
132
133 pub fn columns(&mut self, count_columns: usize) -> &mut Self {
135 self.table.count_columns = Some(count_columns);
136 self
137 }
138
139 pub fn rows(&mut self, count_rows: usize) -> &mut Self {
141 self.table.count_rows = Some(count_rows);
142 self
143 }
144
145 pub fn sniff(&mut self, count: usize) -> &mut Self {
149 self.table.sniff = count;
150 self
151 }
152
153 pub fn height(&mut self, size: usize) -> &mut Self {
155 self.table.height = Some(size);
156 self
157 }
158
159 pub fn width(&mut self, size: usize) -> &mut Self {
161 self.table.width = Some(size);
162 self
163 }
164
165 #[allow(clippy::inherent_to_string)]
169 pub fn to_string(self) -> String
170 where
171 I: IntoRecords,
172 I::Cell: AsRef<str>,
173 {
174 let mut buf = String::new();
175 self.fmt(&mut buf)
176 .expect("according to a doc is safe to fmt() a string");
177
178 buf
179 }
180
181 pub fn build<W>(self, writer: W) -> io::Result<()>
183 where
184 W: io::Write,
185 I: IntoRecords,
186 I::Cell: AsRef<str>,
187 {
188 let writer = UTF8Writer::new(writer);
189 self.fmt(writer).map_err(io::Error::other)
190 }
191
192 pub fn fmt<W>(self, writer: W) -> fmt::Result
194 where
195 W: fmt::Write,
196 I: IntoRecords,
197 I::Cell: AsRef<str>,
198 {
199 build_grid(writer, self.records, self.cfg, self.table)
200 }
201}
202
203impl<T> FromIterator<T> for IterTable<Vec<Vec<T::Item>>>
204where
205 T: IntoIterator,
206 T::Item: AsRef<str>,
207{
208 fn from_iter<I: IntoIterator<Item = T>>(iter: I) -> Self {
209 let data = iter
210 .into_iter()
211 .map(|row| row.into_iter().collect())
212 .collect();
213
214 Self::new(data)
215 }
216}
217
218fn build_grid<W, I>(f: W, iter: I, cfg: CompactConfig, opts: Settings) -> fmt::Result
219where
220 W: fmt::Write,
221 I: IntoRecords,
222 I::Cell: AsRef<str>,
223{
224 let width_config = opts.width.is_some() && opts.count_columns.is_some();
225 if width_config {
226 build_table_with_static_dims(f, iter, cfg, opts)
227 } else if opts.width.is_some() {
228 build_table_sniffing_with_width(f, iter, cfg, opts)
229 } else {
230 build_table_sniffing(f, iter, cfg, opts)
231 }
232}
233
234fn build_table_with_static_dims<W, I>(
235 f: W,
236 iter: I,
237 cfg: CompactConfig,
238 opts: Settings,
239) -> fmt::Result
240where
241 W: fmt::Write,
242 I: IntoRecords,
243 I::Cell: AsRef<str>,
244{
245 let count_columns = opts.count_columns.unwrap();
246 let width = opts.width.unwrap();
247 let height = opts.height.unwrap_or(1);
248 let pad = cfg.get_padding();
249 let contentw = DimensionValue::Exact(width);
250 let width = DimensionValue::Exact(width + pad.left.size + pad.right.size);
251 let height = DimensionValue::Exact(height + pad.top.size + pad.bottom.size);
252 let dims = StaticDimension::new(width, height);
253 let cfg = SpannedConfig::from(cfg);
254
255 match opts.count_rows {
256 Some(limit) => {
257 let records = LimitRows::new(iter, limit);
258 let records = build_records(records, contentw, count_columns, Some(limit));
259 IterGrid::new(records, cfg, dims, NoColors).build(f)
260 }
261 None => {
262 let records = build_records(iter, contentw, count_columns, None);
263 IterGrid::new(records, cfg, dims, NoColors).build(f)
264 }
265 }
266}
267
268fn build_table_sniffing<W, I>(f: W, iter: I, cfg: CompactConfig, opts: Settings) -> fmt::Result
269where
270 W: fmt::Write,
271 I: IntoRecords,
272 I::Cell: AsRef<str>,
273{
274 let records = BufRecords::new(iter, opts.sniff);
275
276 let count_columns = get_count_columns(&opts, records.as_slice());
277
278 let (mut width, height) = {
279 let records = LimitColumns::new(records.as_slice(), count_columns);
280 let records = IterRecords::new(records, count_columns, None);
281 CompactGridDimension::dimension(records, &cfg)
282 };
283
284 let padding = cfg.get_padding();
285 let pad = padding.left.size + padding.right.size;
286 let padv = padding.top.size + padding.bottom.size;
287
288 if opts.sniff == 0 {
289 width = std::iter::repeat_n(pad, count_columns).collect::<Vec<_>>();
290 }
291
292 let content_width = DimensionValue::List(width.iter().map(|i| i.saturating_sub(pad)).collect());
293 let dims_width = DimensionValue::List(width);
294
295 let height_exact = opts.height.unwrap_or(1) + padv;
296 let mut dims_height = DimensionValue::Partial(height, height_exact);
297
298 if opts.height.is_some() {
299 dims_height = DimensionValue::Exact(height_exact);
300 }
301
302 let dims = StaticDimension::new(dims_width, dims_height);
303 let cfg = SpannedConfig::from(cfg);
304
305 match opts.count_rows {
306 Some(limit) => {
307 let records = LimitRows::new(records, limit);
308 let records = build_records(records, content_width, count_columns, Some(limit));
309 IterGrid::new(records, cfg, dims, NoColors).build(f)
310 }
311 None => {
312 let records = build_records(records, content_width, count_columns, None);
313 IterGrid::new(records, cfg, dims, NoColors).build(f)
314 }
315 }
316}
317
318fn build_table_sniffing_with_width<W, I>(
319 f: W,
320 iter: I,
321 cfg: CompactConfig,
322 opts: Settings,
323) -> fmt::Result
324where
325 W: fmt::Write,
326 I: IntoRecords,
327 I::Cell: AsRef<str>,
328{
329 let records = BufRecords::new(iter, opts.sniff);
330
331 let count_columns = get_count_columns(&opts, records.as_slice());
332
333 let width = opts.width.unwrap();
334 let contentw = DimensionValue::Exact(width);
335
336 let padding = cfg.get_padding();
337 let pad = padding.left.size + padding.right.size;
338 let padv = padding.top.size + padding.bottom.size;
339
340 let height = opts.height.unwrap_or(1) + padv;
341 let dimsh = DimensionValue::Exact(height);
342 let dimsw = DimensionValue::Exact(width + pad);
343 let dims = StaticDimension::new(dimsw, dimsh);
344
345 let cfg = SpannedConfig::from(cfg);
346
347 match opts.count_rows {
348 Some(limit) => {
349 let records = LimitRows::new(records, limit);
350 let records = build_records(records, contentw, count_columns, Some(limit));
351 IterGrid::new(records, cfg, dims, NoColors).build(f)
352 }
353 None => {
354 let records = build_records(records, contentw, count_columns, None);
355 IterGrid::new(records, cfg, dims, NoColors).build(f)
356 }
357 }
358}
359
360fn get_count_columns<T>(opts: &Settings, buf: &[Vec<T>]) -> usize {
361 match opts.count_columns {
362 Some(size) => size,
363 None => buf.iter().map(|row| row.len()).max().unwrap_or(0),
364 }
365}
366
367const fn create_config() -> CompactConfig {
368 CompactConfig::new()
369 .set_padding(Sides::new(
370 Indent::spaced(1),
371 Indent::spaced(1),
372 Indent::zero(),
373 Indent::zero(),
374 ))
375 .set_alignment_horizontal(AlignmentHorizontal::Left)
376 .set_borders(Style::ascii().get_borders())
377}
378
379fn build_records<I>(
380 records: I,
381 width: DimensionValue,
382 count_columns: usize,
383 count_rows: Option<usize>,
384) -> IterRecords<LimitColumns<TruncateContent<I, StaticDimension>>>
385where
386 I: IntoRecords,
387{
388 let dims = StaticDimension::new(width, DimensionValue::Exact(0));
389 let records = TruncateContent::new(records, dims);
390 let records = LimitColumns::new(records, count_columns);
391 IterRecords::new(records, count_columns, count_rows)
392}