1 | //! This module contains a main table representation [`Table`]. |
2 | |
3 | use core::ops::DerefMut; |
4 | use std::{borrow::Cow, fmt, iter::FromIterator}; |
5 | |
6 | use crate::{ |
7 | builder::Builder, |
8 | grid::{ |
9 | colors::NoColors, |
10 | config::{ |
11 | AlignmentHorizontal, ColorMap, ColoredConfig, CompactConfig, Entity, Formatting, |
12 | Indent, Sides, SpannedConfig, |
13 | }, |
14 | dimension::{CompleteDimensionVecRecords, Dimension, Estimate, PeekableDimension}, |
15 | records::{ |
16 | vec_records::{CellInfo, VecRecords}, |
17 | ExactRecords, Records, |
18 | }, |
19 | PeekableGrid, |
20 | }, |
21 | settings::{Style, TableOption}, |
22 | Tabled, |
23 | }; |
24 | |
25 | /// The structure provides an interface for building a table for types that implements [`Tabled`]. |
26 | /// |
27 | /// To build a string representation of a table you must use a [`std::fmt::Display`]. |
28 | /// Or simply call `.to_string()` method. |
29 | /// |
30 | /// The default table [`Style`] is [`Style::ascii`], |
31 | /// with a 1 left and right [`Padding`]. |
32 | /// |
33 | /// ## Example |
34 | /// |
35 | /// ### Basic usage |
36 | /// |
37 | /// ```rust,no_run |
38 | /// use tabled::Table; |
39 | /// |
40 | /// let table = Table::new(&["Year" , "2021" ]); |
41 | /// ``` |
42 | /// |
43 | /// ### With settings |
44 | /// |
45 | /// ```rust,no_run |
46 | /// use tabled::{Table, settings::{Style, Alignment}}; |
47 | /// |
48 | /// let data = vec!["Hello" , "2021" ]; |
49 | /// let mut table = Table::new(&data); |
50 | /// table.with(Style::psql()).with(Alignment::left()); |
51 | /// |
52 | /// println!("{}" , table); |
53 | /// ``` |
54 | /// |
55 | /// [`Padding`]: crate::settings::Padding |
56 | /// [`Style`]: crate::settings::Style |
57 | /// [`Style::ascii`]: crate::settings::Style::ascii |
58 | #[derive (Debug, Clone, PartialEq, Eq)] |
59 | pub struct Table { |
60 | records: VecRecords<CellInfo<String>>, |
61 | config: ColoredConfig, |
62 | dimension: CompleteDimensionVecRecords<'static>, |
63 | } |
64 | |
65 | impl Table { |
66 | /// New creates a Table instance. |
67 | /// |
68 | /// If you use a reference iterator you'd better use [`FromIterator`] instead. |
69 | /// As it has a different lifetime constraints and make less copies therefore. |
70 | pub fn new<I, T>(iter: I) -> Self |
71 | where |
72 | I: IntoIterator<Item = T>, |
73 | T: Tabled, |
74 | { |
75 | let mut header = Vec::with_capacity(T::LENGTH); |
76 | for text in T::headers() { |
77 | let text = text.into_owned(); |
78 | let cell = CellInfo::new(text); |
79 | header.push(cell); |
80 | } |
81 | |
82 | let mut records = vec![header]; |
83 | for row in iter.into_iter() { |
84 | let mut list = Vec::with_capacity(T::LENGTH); |
85 | for text in row.fields().into_iter() { |
86 | let text = text.into_owned(); |
87 | let cell = CellInfo::new(text); |
88 | |
89 | list.push(cell); |
90 | } |
91 | |
92 | records.push(list); |
93 | } |
94 | |
95 | let records = VecRecords::new(records); |
96 | |
97 | Self { |
98 | records, |
99 | config: ColoredConfig::new(configure_grid()), |
100 | dimension: CompleteDimensionVecRecords::default(), |
101 | } |
102 | } |
103 | |
104 | /// Creates a builder from a data set given. |
105 | /// |
106 | /// # Example |
107 | /// |
108 | /// |
109 | #[cfg_attr (feature = "derive" , doc = "```" )] |
110 | #[cfg_attr (not(feature = "derive" ), doc = "```ignore" )] |
111 | /// use tabled::{ |
112 | /// Table, Tabled, |
113 | /// settings::{object::Segment, Modify, Alignment} |
114 | /// }; |
115 | /// |
116 | /// #[derive(Tabled)] |
117 | /// struct User { |
118 | /// name: &'static str, |
119 | /// #[tabled(inline("device::" ))] |
120 | /// device: Device, |
121 | /// } |
122 | /// |
123 | /// #[derive(Tabled)] |
124 | /// enum Device { |
125 | /// PC, |
126 | /// Mobile |
127 | /// } |
128 | /// |
129 | /// let data = vec![ |
130 | /// User { name: "Vlad" , device: Device::Mobile }, |
131 | /// User { name: "Dimitry" , device: Device::PC }, |
132 | /// User { name: "John" , device: Device::PC }, |
133 | /// ]; |
134 | /// |
135 | /// let mut table = Table::builder(data) |
136 | /// .index() |
137 | /// .column(0) |
138 | /// .transpose() |
139 | /// .build() |
140 | /// .with(Modify::new(Segment::new(1.., 1..)).with(Alignment::center())) |
141 | /// .to_string(); |
142 | /// |
143 | /// assert_eq!( |
144 | /// table, |
145 | /// "+----------------+------+---------+------+ \n\ |
146 | /// | name | Vlad | Dimitry | John | \n\ |
147 | /// +----------------+------+---------+------+ \n\ |
148 | /// | device::PC | | + | + | \n\ |
149 | /// +----------------+------+---------+------+ \n\ |
150 | /// | device::Mobile | + | | | \n\ |
151 | /// +----------------+------+---------+------+" |
152 | /// ) |
153 | /// ``` |
154 | pub fn builder<I, T>(iter: I) -> Builder |
155 | where |
156 | T: Tabled, |
157 | I: IntoIterator<Item = T>, |
158 | { |
159 | let mut records = Vec::new(); |
160 | for row in iter { |
161 | let mut list = Vec::with_capacity(T::LENGTH); |
162 | for text in row.fields().into_iter() { |
163 | list.push(text.into_owned()); |
164 | } |
165 | |
166 | records.push(list); |
167 | } |
168 | |
169 | let mut b = Builder::from(records); |
170 | let _ = b.set_header(T::headers()).hint_column_size(T::LENGTH); |
171 | |
172 | b |
173 | } |
174 | |
175 | /// With is a generic function which applies options to the [`Table`]. |
176 | /// |
177 | /// It applies settings immediately. |
178 | pub fn with<O>(&mut self, option: O) -> &mut Self |
179 | where |
180 | O: TableOption< |
181 | VecRecords<CellInfo<String>>, |
182 | CompleteDimensionVecRecords<'static>, |
183 | ColoredConfig, |
184 | >, |
185 | { |
186 | self.dimension.clear_width(); |
187 | self.dimension.clear_height(); |
188 | |
189 | option.change(&mut self.records, &mut self.config, &mut self.dimension); |
190 | |
191 | self |
192 | } |
193 | |
194 | /// Returns a table shape (count rows, count columns). |
195 | pub fn shape(&self) -> (usize, usize) { |
196 | (self.count_rows(), self.count_columns()) |
197 | } |
198 | |
199 | /// Returns an amount of rows in the table. |
200 | pub fn count_rows(&self) -> usize { |
201 | self.records.count_rows() |
202 | } |
203 | |
204 | /// Returns an amount of columns in the table. |
205 | pub fn count_columns(&self) -> usize { |
206 | self.records.count_columns() |
207 | } |
208 | |
209 | /// Returns a table shape (count rows, count columns). |
210 | pub fn is_empty(&self) -> bool { |
211 | let (count_rows, count_cols) = self.shape(); |
212 | count_rows == 0 || count_cols == 0 |
213 | } |
214 | |
215 | /// Returns total widths of a table, including margin and horizontal lines. |
216 | pub fn total_height(&self) -> usize { |
217 | let mut dims = CompleteDimensionVecRecords::from_origin(&self.dimension); |
218 | dims.estimate(&self.records, self.config.as_ref()); |
219 | |
220 | let total = (0..self.count_rows()) |
221 | .map(|row| dims.get_height(row)) |
222 | .sum::<usize>(); |
223 | let counth = self.config.count_horizontal(self.count_rows()); |
224 | |
225 | let margin = self.config.get_margin(); |
226 | |
227 | total + counth + margin.top.size + margin.bottom.size |
228 | } |
229 | |
230 | /// Returns total widths of a table, including margin and vertical lines. |
231 | pub fn total_width(&self) -> usize { |
232 | let mut dims = CompleteDimensionVecRecords::from_origin(&self.dimension); |
233 | dims.estimate(&self.records, self.config.as_ref()); |
234 | |
235 | let total = (0..self.count_columns()) |
236 | .map(|col| dims.get_width(col)) |
237 | .sum::<usize>(); |
238 | let countv = self.config.count_vertical(self.count_columns()); |
239 | |
240 | let margin = self.config.get_margin(); |
241 | |
242 | total + countv + margin.left.size + margin.right.size |
243 | } |
244 | |
245 | /// Returns a table config. |
246 | pub fn get_config(&self) -> &ColoredConfig { |
247 | &self.config |
248 | } |
249 | |
250 | /// Returns a table config. |
251 | pub fn get_config_mut(&mut self) -> &mut ColoredConfig { |
252 | &mut self.config |
253 | } |
254 | |
255 | /// Returns a used records. |
256 | pub fn get_records(&self) -> &VecRecords<CellInfo<String>> { |
257 | &self.records |
258 | } |
259 | |
260 | /// Returns a used records. |
261 | pub fn get_records_mut(&mut self) -> &mut VecRecords<CellInfo<String>> { |
262 | &mut self.records |
263 | } |
264 | } |
265 | |
266 | impl Default for Table { |
267 | fn default() -> Self { |
268 | Self { |
269 | records: VecRecords::default(), |
270 | config: ColoredConfig::new(config:configure_grid()), |
271 | dimension: CompleteDimensionVecRecords::default(), |
272 | } |
273 | } |
274 | } |
275 | |
276 | impl fmt::Display for Table { |
277 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
278 | if self.is_empty() { |
279 | return Ok(()); |
280 | } |
281 | |
282 | let config: Cow<'_, SpannedConfig> = use_format_configuration(f, self); |
283 | let colors: &ColorMap = self.config.get_colors(); |
284 | |
285 | if !self.dimension.is_empty() { |
286 | let mut dims: CompleteDimensionVecRecords<'_> = self.dimension.clone(); |
287 | dims.estimate(&self.records, config.as_ref()); |
288 | |
289 | print_grid(f, &self.records, &config, &dims, colors) |
290 | } else { |
291 | let mut dims: PeekableDimension = PeekableDimension::default(); |
292 | dims.estimate(&self.records, &config); |
293 | |
294 | print_grid(f, &self.records, &config, &dims, colors) |
295 | } |
296 | } |
297 | } |
298 | |
299 | impl<T, V> FromIterator<T> for Table |
300 | where |
301 | T: IntoIterator<Item = V>, |
302 | V: Into<String>, |
303 | { |
304 | fn from_iter<I: IntoIterator<Item = T>>(iter: I) -> Self { |
305 | Builder::from_iter(iter.into_iter().map(|i| i.into_iter().map(|s| s.into()))).build() |
306 | } |
307 | } |
308 | |
309 | impl From<Builder> for Table { |
310 | fn from(builder: Builder) -> Self { |
311 | let data: Vec<Vec<CellInfo<String>>> = builder.into(); |
312 | let records: VecRecords> = VecRecords::new(data); |
313 | |
314 | Self { |
315 | records, |
316 | config: ColoredConfig::new(config:configure_grid()), |
317 | dimension: CompleteDimensionVecRecords::default(), |
318 | } |
319 | } |
320 | } |
321 | |
322 | impl From<Table> for Builder { |
323 | fn from(val: Table) -> Self { |
324 | let count_columns: usize = val.count_columns(); |
325 | let data: Vec<Vec<CellInfo<String>>> = val.records.into(); |
326 | let mut builder: Builder = Builder::from(data); |
327 | let _ = builder.hint_column_size(count_columns); |
328 | builder |
329 | } |
330 | } |
331 | |
332 | impl<R, D> TableOption<R, D, ColoredConfig> for CompactConfig { |
333 | fn change(self, _: &mut R, cfg: &mut ColoredConfig, _: &mut D) { |
334 | *cfg.deref_mut() = self.into(); |
335 | } |
336 | } |
337 | |
338 | impl<R, D> TableOption<R, D, ColoredConfig> for ColoredConfig { |
339 | fn change(self, _: &mut R, cfg: &mut ColoredConfig, _: &mut D) { |
340 | *cfg = self; |
341 | } |
342 | } |
343 | |
344 | impl<R, D> TableOption<R, D, ColoredConfig> for SpannedConfig { |
345 | fn change(self, _: &mut R, cfg: &mut ColoredConfig, _: &mut D) { |
346 | *cfg.deref_mut() = self; |
347 | } |
348 | } |
349 | |
350 | impl<R, D> TableOption<R, D, ColoredConfig> for &SpannedConfig { |
351 | fn change(self, _: &mut R, cfg: &mut ColoredConfig, _: &mut D) { |
352 | *cfg.deref_mut() = self.clone(); |
353 | } |
354 | } |
355 | |
356 | fn convert_fmt_alignment(alignment: fmt::Alignment) -> AlignmentHorizontal { |
357 | match alignment { |
358 | fmt::Alignment::Left => AlignmentHorizontal::Left, |
359 | fmt::Alignment::Right => AlignmentHorizontal::Right, |
360 | fmt::Alignment::Center => AlignmentHorizontal::Center, |
361 | } |
362 | } |
363 | |
364 | fn table_padding(alignment: fmt::Alignment, available: usize) -> (usize, usize) { |
365 | match alignment { |
366 | fmt::Alignment::Left => (available, 0), |
367 | fmt::Alignment::Right => (0, available), |
368 | fmt::Alignment::Center => { |
369 | let left: usize = available / 2; |
370 | let right: usize = available - left; |
371 | (left, right) |
372 | } |
373 | } |
374 | } |
375 | |
376 | fn configure_grid() -> SpannedConfig { |
377 | let mut cfg: SpannedConfig = SpannedConfig::default(); |
378 | cfg.set_padding( |
379 | Entity::Global, |
380 | padding:Sides::new( |
381 | left:Indent::spaced(1), |
382 | right:Indent::spaced(1), |
383 | top:Indent::default(), |
384 | bottom:Indent::default(), |
385 | ), |
386 | ); |
387 | cfg.set_alignment_horizontal(Entity::Global, alignment:AlignmentHorizontal::Left); |
388 | cfg.set_formatting(Entity::Global, Formatting::new(horizontal_trim:false, vertical_trim:false, allow_lines_alignment:false)); |
389 | cfg.set_borders(*Style::ascii().get_borders()); |
390 | |
391 | cfg |
392 | } |
393 | |
394 | fn use_format_configuration<'a>( |
395 | f: &mut fmt::Formatter<'_>, |
396 | table: &'a Table, |
397 | ) -> Cow<'a, SpannedConfig> { |
398 | if f.align().is_some() || f.width().is_some() { |
399 | let mut cfg: SpannedConfig = table.config.as_ref().clone(); |
400 | |
401 | set_align_table(f, &mut cfg); |
402 | set_width_table(f, &mut cfg, table); |
403 | |
404 | Cow::Owned(cfg) |
405 | } else { |
406 | Cow::Borrowed(table.config.as_ref()) |
407 | } |
408 | } |
409 | |
410 | fn set_align_table(f: &fmt::Formatter<'_>, cfg: &mut SpannedConfig) { |
411 | if let Some(alignment: Alignment) = f.align() { |
412 | let alignment: AlignmentHorizontal = convert_fmt_alignment(alignment); |
413 | cfg.set_alignment_horizontal(Entity::Global, alignment); |
414 | } |
415 | } |
416 | |
417 | fn set_width_table(f: &fmt::Formatter<'_>, cfg: &mut SpannedConfig, table: &Table) { |
418 | if let Some(width) = f.width() { |
419 | let total_width = table.total_width(); |
420 | if total_width >= width { |
421 | return; |
422 | } |
423 | |
424 | let mut fill = f.fill(); |
425 | if fill == char::default() { |
426 | fill = ' ' ; |
427 | } |
428 | |
429 | let available = width - total_width; |
430 | let alignment = f.align().unwrap_or(fmt::Alignment::Left); |
431 | let (left, right) = table_padding(alignment, available); |
432 | |
433 | let mut margin = cfg.get_margin(); |
434 | margin.left.size += left; |
435 | margin.right.size += right; |
436 | |
437 | if (margin.left.size > 0 && margin.left.fill == char::default()) || fill != char::default() |
438 | { |
439 | margin.left.fill = fill; |
440 | } |
441 | |
442 | if (margin.right.size > 0 && margin.right.fill == char::default()) |
443 | || fill != char::default() |
444 | { |
445 | margin.right.fill = fill; |
446 | } |
447 | |
448 | cfg.set_margin(margin); |
449 | } |
450 | } |
451 | |
452 | fn print_grid<F: fmt::Write, D: Dimension>( |
453 | f: &mut F, |
454 | records: &VecRecords<CellInfo<String>>, |
455 | cfg: &SpannedConfig, |
456 | dims: D, |
457 | colors: &ColorMap, |
458 | ) -> fmt::Result { |
459 | if !colors.is_empty() { |
460 | PeekableGrid::new(records, config:cfg, &dims, colors).build(f) |
461 | } else { |
462 | PeekableGrid::new(records, config:cfg, &dims, colors:NoColors).build(f) |
463 | } |
464 | } |
465 | |