| 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::{object::Object, CellOption, 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 builder = Builder::with_capacity(0, T::LENGTH); |
| 160 | builder.push_record(T::headers()); |
| 161 | |
| 162 | for row in iter { |
| 163 | builder.push_record(row.fields().into_iter()); |
| 164 | } |
| 165 | |
| 166 | builder |
| 167 | } |
| 168 | |
| 169 | /// It's a generic function which applies options to the [`Table`]. |
| 170 | /// |
| 171 | /// It applies settings immediately. |
| 172 | pub fn with<O>(&mut self, option: O) -> &mut Self |
| 173 | where |
| 174 | for<'a> O: TableOption< |
| 175 | VecRecords<CellInfo<String>>, |
| 176 | ColoredConfig, |
| 177 | CompleteDimensionVecRecords<'a>, |
| 178 | >, |
| 179 | { |
| 180 | let reastimation_hint = option.hint_change(); |
| 181 | let mut dims = self.dimension.from_origin(); |
| 182 | |
| 183 | option.change(&mut self.records, &mut self.config, &mut dims); |
| 184 | |
| 185 | let (widths, heights) = dims.into_inner(); |
| 186 | dimension_reastimate(&mut self.dimension, widths, heights, reastimation_hint); |
| 187 | |
| 188 | self |
| 189 | } |
| 190 | |
| 191 | /// It's a generic function which applies options to a particalar cells on the [`Table`]. |
| 192 | /// Target cells using [`Object`]s such as [`Cell`], [`Rows`], [`Location`] and more. |
| 193 | /// |
| 194 | /// It applies settings immediately. |
| 195 | /// |
| 196 | /// [`Cell`]: crate::settings::object::Cell |
| 197 | /// [`Rows`]: crate::settings::object::Rows |
| 198 | /// [`Location`]: crate::settings::location::Locator |
| 199 | pub fn modify<T, O>(&mut self, target: T, option: O) -> &mut Self |
| 200 | where |
| 201 | T: Object<VecRecords<CellInfo<String>>>, |
| 202 | O: CellOption<VecRecords<CellInfo<String>>, ColoredConfig> + Clone, |
| 203 | { |
| 204 | for entity in target.cells(&self.records) { |
| 205 | let opt = option.clone(); |
| 206 | opt.change(&mut self.records, &mut self.config, entity); |
| 207 | } |
| 208 | |
| 209 | let reastimation_hint = option.hint_change(); |
| 210 | dimension_reastimate_likely(&mut self.dimension, reastimation_hint); |
| 211 | |
| 212 | self |
| 213 | } |
| 214 | |
| 215 | /// Returns a table shape (count rows, count columns). |
| 216 | pub fn shape(&self) -> (usize, usize) { |
| 217 | (self.count_rows(), self.count_columns()) |
| 218 | } |
| 219 | |
| 220 | /// Returns an amount of rows in the table. |
| 221 | pub fn count_rows(&self) -> usize { |
| 222 | self.records.count_rows() |
| 223 | } |
| 224 | |
| 225 | /// Returns an amount of columns in the table. |
| 226 | pub fn count_columns(&self) -> usize { |
| 227 | self.records.count_columns() |
| 228 | } |
| 229 | |
| 230 | /// Returns a table shape (count rows, count columns). |
| 231 | pub fn is_empty(&self) -> bool { |
| 232 | let (count_rows, count_cols) = self.shape(); |
| 233 | count_rows == 0 || count_cols == 0 |
| 234 | } |
| 235 | |
| 236 | /// Returns total widths of a table, including margin and horizontal lines. |
| 237 | pub fn total_height(&self) -> usize { |
| 238 | let mut dims = CompleteDimensionVecRecords::from_origin(&self.dimension); |
| 239 | dims.estimate(&self.records, self.config.as_ref()); |
| 240 | |
| 241 | let total = (0..self.count_rows()) |
| 242 | .map(|row| dims.get_height(row)) |
| 243 | .sum::<usize>(); |
| 244 | let counth = self.config.count_horizontal(self.count_rows()); |
| 245 | |
| 246 | let margin = self.config.get_margin(); |
| 247 | |
| 248 | total + counth + margin.top.size + margin.bottom.size |
| 249 | } |
| 250 | |
| 251 | /// Returns total widths of a table, including margin and vertical lines. |
| 252 | pub fn total_width(&self) -> usize { |
| 253 | let mut dims = CompleteDimensionVecRecords::from_origin(&self.dimension); |
| 254 | dims.estimate(&self.records, self.config.as_ref()); |
| 255 | |
| 256 | let total = (0..self.count_columns()) |
| 257 | .map(|col| dims.get_width(col)) |
| 258 | .sum::<usize>(); |
| 259 | let countv = self.config.count_vertical(self.count_columns()); |
| 260 | |
| 261 | let margin = self.config.get_margin(); |
| 262 | |
| 263 | total + countv + margin.left.size + margin.right.size |
| 264 | } |
| 265 | |
| 266 | /// Returns a table config. |
| 267 | pub fn get_config(&self) -> &ColoredConfig { |
| 268 | &self.config |
| 269 | } |
| 270 | |
| 271 | /// Returns a table config. |
| 272 | pub fn get_config_mut(&mut self) -> &mut ColoredConfig { |
| 273 | &mut self.config |
| 274 | } |
| 275 | |
| 276 | /// Returns a used records. |
| 277 | pub fn get_records(&self) -> &VecRecords<CellInfo<String>> { |
| 278 | &self.records |
| 279 | } |
| 280 | |
| 281 | /// Returns a used records. |
| 282 | pub fn get_records_mut(&mut self) -> &mut VecRecords<CellInfo<String>> { |
| 283 | &mut self.records |
| 284 | } |
| 285 | } |
| 286 | |
| 287 | impl Default for Table { |
| 288 | fn default() -> Self { |
| 289 | Self { |
| 290 | records: VecRecords::default(), |
| 291 | config: ColoredConfig::new(config:configure_grid()), |
| 292 | dimension: CompleteDimensionVecRecords::default(), |
| 293 | } |
| 294 | } |
| 295 | } |
| 296 | |
| 297 | impl fmt::Display for Table { |
| 298 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
| 299 | if self.is_empty() { |
| 300 | return Ok(()); |
| 301 | } |
| 302 | |
| 303 | let config: Cow<'_, SpannedConfig> = use_format_configuration(f, self); |
| 304 | let colors: &ColorMap = self.config.get_colors(); |
| 305 | |
| 306 | if !self.dimension.is_empty() { |
| 307 | let mut dims: CompleteDimensionVecRecords<'static> = self.dimension.clone(); |
| 308 | dims.estimate(&self.records, config.as_ref()); |
| 309 | |
| 310 | print_grid(f, &self.records, &config, &dims, colors) |
| 311 | } else { |
| 312 | let mut dims: PeekableDimension = PeekableDimension::default(); |
| 313 | dims.estimate(&self.records, &config); |
| 314 | |
| 315 | print_grid(f, &self.records, &config, &dims, colors) |
| 316 | } |
| 317 | } |
| 318 | } |
| 319 | |
| 320 | impl<T> FromIterator<T> for Table |
| 321 | where |
| 322 | T: IntoIterator, |
| 323 | T::Item: Into<String>, |
| 324 | { |
| 325 | fn from_iter<I: IntoIterator<Item = T>>(iter: I) -> Self { |
| 326 | Builder::from_iter(iter.into_iter().map(|i| i.into_iter().map(|s| s.into()))).build() |
| 327 | } |
| 328 | } |
| 329 | |
| 330 | impl From<Builder> for Table { |
| 331 | fn from(builder: Builder) -> Self { |
| 332 | let data: Vec>> = builder.into(); |
| 333 | let records: VecRecords> = VecRecords::new(data); |
| 334 | |
| 335 | Self { |
| 336 | records, |
| 337 | config: ColoredConfig::new(config:configure_grid()), |
| 338 | dimension: CompleteDimensionVecRecords::default(), |
| 339 | } |
| 340 | } |
| 341 | } |
| 342 | |
| 343 | impl From<Table> for Builder { |
| 344 | fn from(val: Table) -> Self { |
| 345 | let data: Vec>> = val.records.into(); |
| 346 | Builder::from_vec(data) |
| 347 | } |
| 348 | } |
| 349 | |
| 350 | impl<R, D> TableOption<R, ColoredConfig, D> for CompactConfig { |
| 351 | fn change(self, _: &mut R, cfg: &mut ColoredConfig, _: &mut D) { |
| 352 | *cfg.deref_mut() = self.into(); |
| 353 | } |
| 354 | } |
| 355 | |
| 356 | impl<R, D> TableOption<R, ColoredConfig, D> for ColoredConfig { |
| 357 | fn change(self, _: &mut R, cfg: &mut ColoredConfig, _: &mut D) { |
| 358 | *cfg = self; |
| 359 | } |
| 360 | } |
| 361 | |
| 362 | impl<R, D> TableOption<R, ColoredConfig, D> for SpannedConfig { |
| 363 | fn change(self, _: &mut R, cfg: &mut ColoredConfig, _: &mut D) { |
| 364 | *cfg.deref_mut() = self; |
| 365 | } |
| 366 | } |
| 367 | |
| 368 | impl<R, D> TableOption<R, ColoredConfig, D> for &SpannedConfig { |
| 369 | fn change(self, _: &mut R, cfg: &mut ColoredConfig, _: &mut D) { |
| 370 | *cfg.deref_mut() = self.clone(); |
| 371 | } |
| 372 | } |
| 373 | |
| 374 | fn convert_fmt_alignment(alignment: fmt::Alignment) -> AlignmentHorizontal { |
| 375 | match alignment { |
| 376 | fmt::Alignment::Left => AlignmentHorizontal::Left, |
| 377 | fmt::Alignment::Right => AlignmentHorizontal::Right, |
| 378 | fmt::Alignment::Center => AlignmentHorizontal::Center, |
| 379 | } |
| 380 | } |
| 381 | |
| 382 | fn table_padding(alignment: fmt::Alignment, available: usize) -> (usize, usize) { |
| 383 | match alignment { |
| 384 | fmt::Alignment::Left => (available, 0), |
| 385 | fmt::Alignment::Right => (0, available), |
| 386 | fmt::Alignment::Center => { |
| 387 | let left: usize = available / 2; |
| 388 | let right: usize = available - left; |
| 389 | (left, right) |
| 390 | } |
| 391 | } |
| 392 | } |
| 393 | |
| 394 | fn configure_grid() -> SpannedConfig { |
| 395 | let mut cfg: SpannedConfig = SpannedConfig::default(); |
| 396 | cfg.set_padding( |
| 397 | Entity::Global, |
| 398 | padding:Sides::new( |
| 399 | left:Indent::spaced(1), |
| 400 | right:Indent::spaced(1), |
| 401 | top:Indent::default(), |
| 402 | bottom:Indent::default(), |
| 403 | ), |
| 404 | ); |
| 405 | cfg.set_alignment_horizontal(Entity::Global, alignment:AlignmentHorizontal::Left); |
| 406 | cfg.set_formatting(Entity::Global, Formatting::new(horizontal_trim:false, vertical_trim:false, lines_alignment:false)); |
| 407 | cfg.set_borders(Style::ascii().get_borders()); |
| 408 | |
| 409 | cfg |
| 410 | } |
| 411 | |
| 412 | fn use_format_configuration<'a>( |
| 413 | f: &mut fmt::Formatter<'_>, |
| 414 | table: &'a Table, |
| 415 | ) -> Cow<'a, SpannedConfig> { |
| 416 | if f.align().is_some() || f.width().is_some() { |
| 417 | let mut cfg: SpannedConfig = table.config.as_ref().clone(); |
| 418 | |
| 419 | set_align_table(f, &mut cfg); |
| 420 | set_width_table(f, &mut cfg, table); |
| 421 | |
| 422 | Cow::Owned(cfg) |
| 423 | } else { |
| 424 | Cow::Borrowed(table.config.as_ref()) |
| 425 | } |
| 426 | } |
| 427 | |
| 428 | fn set_align_table(f: &fmt::Formatter<'_>, cfg: &mut SpannedConfig) { |
| 429 | if let Some(alignment: Alignment) = f.align() { |
| 430 | let alignment: AlignmentHorizontal = convert_fmt_alignment(alignment); |
| 431 | cfg.set_alignment_horizontal(Entity::Global, alignment); |
| 432 | } |
| 433 | } |
| 434 | |
| 435 | fn set_width_table(f: &fmt::Formatter<'_>, cfg: &mut SpannedConfig, table: &Table) { |
| 436 | if let Some(width) = f.width() { |
| 437 | let total_width = table.total_width(); |
| 438 | if total_width >= width { |
| 439 | return; |
| 440 | } |
| 441 | |
| 442 | let mut fill = f.fill(); |
| 443 | if fill == char::default() { |
| 444 | fill = ' ' ; |
| 445 | } |
| 446 | |
| 447 | let available = width - total_width; |
| 448 | let alignment = f.align().unwrap_or(fmt::Alignment::Left); |
| 449 | let (left, right) = table_padding(alignment, available); |
| 450 | |
| 451 | let mut margin = cfg.get_margin(); |
| 452 | margin.left.size += left; |
| 453 | margin.right.size += right; |
| 454 | |
| 455 | if (margin.left.size > 0 && margin.left.fill == char::default()) || fill != char::default() |
| 456 | { |
| 457 | margin.left.fill = fill; |
| 458 | } |
| 459 | |
| 460 | if (margin.right.size > 0 && margin.right.fill == char::default()) |
| 461 | || fill != char::default() |
| 462 | { |
| 463 | margin.right.fill = fill; |
| 464 | } |
| 465 | |
| 466 | cfg.set_margin(margin); |
| 467 | } |
| 468 | } |
| 469 | |
| 470 | fn print_grid<F: fmt::Write, D: Dimension>( |
| 471 | f: &mut F, |
| 472 | records: &VecRecords<CellInfo<String>>, |
| 473 | cfg: &SpannedConfig, |
| 474 | dims: D, |
| 475 | colors: &ColorMap, |
| 476 | ) -> fmt::Result { |
| 477 | if !colors.is_empty() { |
| 478 | PeekableGrid::new(records, config:cfg, &dims, colors).build(f) |
| 479 | } else { |
| 480 | PeekableGrid::new(records, config:cfg, &dims, colors:NoColors).build(f) |
| 481 | } |
| 482 | } |
| 483 | |
| 484 | fn dimension_reastimate( |
| 485 | dims: &mut CompleteDimensionVecRecords<'_>, |
| 486 | widths: Option<Vec<usize>>, |
| 487 | heights: Option<Vec<usize>>, |
| 488 | hint: Option<Entity>, |
| 489 | ) { |
| 490 | let hint: Entity = match hint { |
| 491 | Some(hint: Entity) => hint, |
| 492 | None => return, |
| 493 | }; |
| 494 | |
| 495 | match hint { |
| 496 | Entity::Global | Entity::Cell(_, _) => { |
| 497 | dims_set_widths(dims, list:widths); |
| 498 | dims_set_heights(dims, list:heights); |
| 499 | } |
| 500 | Entity::Column(_) => { |
| 501 | dims_set_widths(dims, list:widths); |
| 502 | } |
| 503 | Entity::Row(_) => { |
| 504 | dims_set_heights(dims, list:heights); |
| 505 | } |
| 506 | } |
| 507 | } |
| 508 | |
| 509 | fn dims_set_widths(dims: &mut CompleteDimensionVecRecords<'_>, list: Option<Vec<usize>>) { |
| 510 | match list { |
| 511 | Some(list: Vec) => match dims.get_widths() { |
| 512 | Some(widths: &[usize]) => { |
| 513 | if widths == list { |
| 514 | dims.clear_width(); |
| 515 | } else { |
| 516 | dims.set_widths(columns:list); |
| 517 | } |
| 518 | } |
| 519 | None => dims.set_widths(columns:list), |
| 520 | }, |
| 521 | None => { |
| 522 | dims.clear_width(); |
| 523 | } |
| 524 | } |
| 525 | } |
| 526 | |
| 527 | fn dims_set_heights(dims: &mut CompleteDimensionVecRecords<'_>, list: Option<Vec<usize>>) { |
| 528 | match list { |
| 529 | Some(list: Vec) => match dims.get_heights() { |
| 530 | Some(heights: &[usize]) => { |
| 531 | if heights == list { |
| 532 | dims.clear_height(); |
| 533 | } else { |
| 534 | dims.set_heights(rows:list); |
| 535 | } |
| 536 | } |
| 537 | None => dims.set_heights(rows:list), |
| 538 | }, |
| 539 | None => { |
| 540 | dims.clear_height(); |
| 541 | } |
| 542 | } |
| 543 | } |
| 544 | |
| 545 | fn dimension_reastimate_likely(dims: &mut CompleteDimensionVecRecords<'_>, hint: Option<Entity>) { |
| 546 | let hint: Entity = match hint { |
| 547 | Some(hint: Entity) => hint, |
| 548 | None => return, |
| 549 | }; |
| 550 | |
| 551 | match hint { |
| 552 | Entity::Global | Entity::Cell(_, _) => { |
| 553 | dims.clear_width(); |
| 554 | dims.clear_height() |
| 555 | } |
| 556 | Entity::Column(_) => { |
| 557 | dims.clear_width(); |
| 558 | } |
| 559 | Entity::Row(_) => dims.clear_height(), |
| 560 | } |
| 561 | } |
| 562 | |