| 1 | //! This module contains a [`CompactTable`] table. |
| 2 | //! |
| 3 | //! In contrast to [`Table`] [`CompactTable`] 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 | //! It works smoothly with arrays. |
| 9 | //! |
| 10 | #![cfg_attr (feature = "std" , doc = "```" )] |
| 11 | #![cfg_attr (not(feature = "std" ), doc = "```ignore" )] |
| 12 | //!use tabled::{settings::Style, tables::CompactTable}; |
| 13 | //! |
| 14 | //! let data = [ |
| 15 | //! ["FreeBSD" , "1993" , "William and Lynne Jolitz" , "?" ], |
| 16 | //! ["OpenBSD" , "1995" , "Theo de Raadt" , "" ], |
| 17 | //! ["HardenedBSD" , "2014" , "Oliver Pinter and Shawn Webb" , "" ], |
| 18 | //! ]; |
| 19 | //! |
| 20 | //! let table = CompactTable::from(data) |
| 21 | //! .with(Style::psql()) |
| 22 | //! .to_string(); |
| 23 | //! |
| 24 | //! assert_eq!( |
| 25 | //! table, |
| 26 | //! concat!( |
| 27 | //! " FreeBSD | 1993 | William and Lynne Jolitz | ? \n" , |
| 28 | //! " OpenBSD | 1995 | Theo de Raadt | \n" , |
| 29 | //! " HardenedBSD | 2014 | Oliver Pinter and Shawn Webb | " , |
| 30 | //! ) |
| 31 | //! ); |
| 32 | //! ``` |
| 33 | //! |
| 34 | //! But it's default creation requires to be given an estimated cell width, and the amount of columns. |
| 35 | //! |
| 36 | #![cfg_attr (feature = "std" , doc = "```" )] |
| 37 | #![cfg_attr (not(feature = "std" ), doc = "```ignore" )] |
| 38 | //!use tabled::{settings::Style, tables::CompactTable}; |
| 39 | //! |
| 40 | //! let data = [ |
| 41 | //! ["FreeBSD" , "1993" , "William and Lynne Jolitz" , "?" ], |
| 42 | //! ["OpenBSD" , "1995" , "Theo de Raadt" , "" ], |
| 43 | //! ["HardenedBSD" , "2014" , "Oliver Pinter and Shawn Webb" , "" ], |
| 44 | //! ]; |
| 45 | //! |
| 46 | //! // See what will happen if the given width is too narrow |
| 47 | //! |
| 48 | //! let table = CompactTable::new(&data) |
| 49 | //! .columns(4) |
| 50 | //! .width(5) |
| 51 | //! .with(Style::ascii()) |
| 52 | //! .to_string(); |
| 53 | //! |
| 54 | //! assert_eq!( |
| 55 | //! table, |
| 56 | //! "+-----+-----+-----+-----+ \n\ |
| 57 | //! | FreeBSD | 1993 | William and Lynne Jolitz | ? | \n\ |
| 58 | //! |-----+-----+-----+-----| \n\ |
| 59 | //! | OpenBSD | 1995 | Theo de Raadt | | \n\ |
| 60 | //! |-----+-----+-----+-----| \n\ |
| 61 | //! | HardenedBSD | 2014 | Oliver Pinter and Shawn Webb | | \n\ |
| 62 | //! +-----+-----+-----+-----+" |
| 63 | //! ); |
| 64 | //! ``` |
| 65 | //! |
| 66 | //! [`Table`]: crate::Table |
| 67 | |
| 68 | use core::cmp::max; |
| 69 | use core::fmt; |
| 70 | |
| 71 | use crate::{ |
| 72 | grid::{ |
| 73 | config::{AlignmentHorizontal, CompactConfig, Indent, Sides}, |
| 74 | dimension::{ConstDimension, ConstSize, Dimension}, |
| 75 | records::{ |
| 76 | into_records::{LimitColumns, LimitRows}, |
| 77 | IntoRecords, IterRecords, |
| 78 | }, |
| 79 | util::string::string_width, |
| 80 | CompactGrid, |
| 81 | }, |
| 82 | settings::{style::Style, TableOption}, |
| 83 | }; |
| 84 | |
| 85 | /// A table which consumes an [`IntoRecords`] iterator. |
| 86 | /// It assumes that the content has only single line. |
| 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 | } |
| 95 | |
| 96 | impl<I> CompactTable<I, ConstDimension<0, 0>> { |
| 97 | /// Creates a new [`CompactTable`] structure with a width dimension for all columns. |
| 98 | pub const fn new(iter: I) -> Self |
| 99 | where |
| 100 | I: IntoRecords, |
| 101 | { |
| 102 | Self { |
| 103 | records: iter, |
| 104 | cfg: create_config(), |
| 105 | count_columns: 0, |
| 106 | count_rows: None, |
| 107 | dims: ConstDimension::new(width:ConstSize::Value(2), height:ConstSize::Value(1)), |
| 108 | } |
| 109 | } |
| 110 | } |
| 111 | |
| 112 | impl<I, const ROWS: usize, const COLS: usize> CompactTable<I, ConstDimension<COLS, ROWS>> { |
| 113 | /// Set a height for each row. |
| 114 | pub fn height<S: Into<ConstSize<COUNT_ROWS>>, const COUNT_ROWS: usize>( |
| 115 | self, |
| 116 | size: S, |
| 117 | ) -> CompactTable<I, ConstDimension<COLS, COUNT_ROWS>> { |
| 118 | let (width, _) = self.dims.into(); |
| 119 | CompactTable { |
| 120 | dims: ConstDimension::new(width, size.into()), |
| 121 | records: self.records, |
| 122 | cfg: self.cfg, |
| 123 | count_columns: self.count_columns, |
| 124 | count_rows: self.count_rows, |
| 125 | } |
| 126 | } |
| 127 | |
| 128 | /// Set a width for each column. |
| 129 | pub fn width<S: Into<ConstSize<COUNT_COLUMNS>>, const COUNT_COLUMNS: usize>( |
| 130 | self, |
| 131 | size: S, |
| 132 | ) -> CompactTable<I, ConstDimension<COUNT_COLUMNS, ROWS>> { |
| 133 | let (_, height) = self.dims.into(); |
| 134 | CompactTable { |
| 135 | dims: ConstDimension::new(size.into(), height), |
| 136 | records: self.records, |
| 137 | cfg: self.cfg, |
| 138 | count_columns: self.count_columns, |
| 139 | count_rows: self.count_rows, |
| 140 | } |
| 141 | } |
| 142 | } |
| 143 | |
| 144 | impl<I, D> CompactTable<I, D> { |
| 145 | /// Creates a new [`CompactTable`] structure with a known dimension. |
| 146 | /// |
| 147 | /// Notice that the function wont call [`Estimate`]. |
| 148 | /// |
| 149 | /// [`Estimate`]: crate::grid::dimension::Estimate |
| 150 | pub fn with_dimension(iter: I, dimension: D) -> Self |
| 151 | where |
| 152 | I: IntoRecords, |
| 153 | { |
| 154 | Self { |
| 155 | records: iter, |
| 156 | dims: dimension, |
| 157 | cfg: create_config(), |
| 158 | count_columns: 0, |
| 159 | count_rows: None, |
| 160 | } |
| 161 | } |
| 162 | |
| 163 | /// With is a generic function which applies options to the [`CompactTable`]. |
| 164 | pub fn with<O>(mut self, option: O) -> Self |
| 165 | where |
| 166 | for<'a> O: TableOption<IterRecords<&'a I>, CompactConfig, D>, |
| 167 | { |
| 168 | let mut records = IterRecords::new(&self.records, self.count_columns, self.count_rows); |
| 169 | option.change(&mut records, &mut self.cfg, &mut self.dims); |
| 170 | |
| 171 | self |
| 172 | } |
| 173 | |
| 174 | /// Limit a number of rows. |
| 175 | pub const fn rows(mut self, count_rows: usize) -> Self { |
| 176 | self.count_rows = Some(count_rows); |
| 177 | self |
| 178 | } |
| 179 | |
| 180 | /// Limit a number of columns. |
| 181 | pub const fn columns(mut self, count: usize) -> Self { |
| 182 | self.count_columns = count; |
| 183 | self |
| 184 | } |
| 185 | |
| 186 | /// Returns a table config. |
| 187 | pub fn get_config(&self) -> &CompactConfig { |
| 188 | &self.cfg |
| 189 | } |
| 190 | |
| 191 | /// Returns a table config. |
| 192 | pub fn get_config_mut(&mut self) -> &mut CompactConfig { |
| 193 | &mut self.cfg |
| 194 | } |
| 195 | |
| 196 | /// Format table into [fmt::Write]er. |
| 197 | pub fn fmt<W>(self, writer: W) -> fmt::Result |
| 198 | where |
| 199 | I: IntoRecords, |
| 200 | I::Cell: AsRef<str>, |
| 201 | D: Dimension, |
| 202 | W: fmt::Write, |
| 203 | { |
| 204 | build_grid( |
| 205 | writer, |
| 206 | self.records, |
| 207 | self.dims, |
| 208 | self.cfg, |
| 209 | self.count_columns, |
| 210 | self.count_rows, |
| 211 | ) |
| 212 | } |
| 213 | |
| 214 | /// Format table into a writer. |
| 215 | #[cfg (feature = "std" )] |
| 216 | #[cfg_attr (docsrs, doc(cfg(feature = "std" )))] |
| 217 | pub fn build<W>(self, writer: W) -> std::io::Result<()> |
| 218 | where |
| 219 | I: IntoRecords, |
| 220 | I::Cell: AsRef<str>, |
| 221 | D: Dimension, |
| 222 | W: std::io::Write, |
| 223 | { |
| 224 | let writer = super::util::utf8_writer::UTF8Writer::new(writer); |
| 225 | self.fmt(writer) |
| 226 | .map_err(|err| std::io::Error::new(std::io::ErrorKind::Other, err)) |
| 227 | } |
| 228 | |
| 229 | /// Build a string. |
| 230 | /// |
| 231 | /// We can't implement [`std::string::ToString`] cause it does takes `&self` reference. |
| 232 | #[allow (clippy::inherent_to_string)] |
| 233 | #[cfg (feature = "std" )] |
| 234 | #[cfg_attr (docsrs, doc(cfg(feature = "std" )))] |
| 235 | pub fn to_string(self) -> String |
| 236 | where |
| 237 | I: IntoRecords, |
| 238 | I::Cell: AsRef<str>, |
| 239 | D: Dimension, |
| 240 | { |
| 241 | let mut buf = String::new(); |
| 242 | self.fmt(&mut buf) |
| 243 | .expect("it's expected to be ok according to doc" ); |
| 244 | |
| 245 | buf |
| 246 | } |
| 247 | } |
| 248 | |
| 249 | impl<T, const ROWS: usize, const COLS: usize> From<[[T; COLS]; ROWS]> |
| 250 | for CompactTable<[[T; COLS]; ROWS], ConstDimension<COLS, ROWS>> |
| 251 | where |
| 252 | T: AsRef<str>, |
| 253 | { |
| 254 | fn from(mat: [[T; COLS]; ROWS]) -> Self { |
| 255 | let mut width: [usize; COLS] = [0; COLS]; |
| 256 | for row: &[T; COLS] in mat.iter() { |
| 257 | for (col: usize, text: &T) in row.iter().enumerate() { |
| 258 | let text: &str = text.as_ref(); |
| 259 | let text_width: usize = string_width(text); |
| 260 | width[col] = max(v1:width[col], v2:text_width); |
| 261 | } |
| 262 | } |
| 263 | |
| 264 | // add padding |
| 265 | for w: &mut usize in &mut width { |
| 266 | *w += 2; |
| 267 | } |
| 268 | |
| 269 | let dims: ConstDimension = ConstDimension::new(width:ConstSize::List(width), height:ConstSize::Value(1)); |
| 270 | Self::with_dimension(mat, dims).columns(COLS).rows(ROWS) |
| 271 | } |
| 272 | } |
| 273 | |
| 274 | fn build_grid<W, I, D>( |
| 275 | writer: W, |
| 276 | records: I, |
| 277 | dims: D, |
| 278 | config: CompactConfig, |
| 279 | cols: usize, |
| 280 | rows: Option<usize>, |
| 281 | ) -> Result<(), fmt::Error> |
| 282 | where |
| 283 | W: fmt::Write, |
| 284 | I: IntoRecords, |
| 285 | I::Cell: AsRef<str>, |
| 286 | D: Dimension, |
| 287 | { |
| 288 | match rows { |
| 289 | Some(limit: usize) => { |
| 290 | let records: LimitRows = LimitRows::new(records, limit); |
| 291 | let records: LimitColumns> = LimitColumns::new(records, limit:cols); |
| 292 | let records: IterRecords> = IterRecords::new(iter:records, count_columns:cols, rows); |
| 293 | CompactGrid::new(records, dimension:dims, config).build(writer) |
| 294 | } |
| 295 | None => { |
| 296 | let records: LimitColumns = LimitColumns::new(records, limit:cols); |
| 297 | let records: IterRecords> = IterRecords::new(iter:records, count_columns:cols, rows); |
| 298 | CompactGrid::new(records, dimension:dims, config).build(writer) |
| 299 | } |
| 300 | } |
| 301 | } |
| 302 | |
| 303 | const fn create_config() -> CompactConfig { |
| 304 | CompactConfigCompactConfig::new() |
| 305 | .set_padding(Sides::new( |
| 306 | Indent::spaced(1), |
| 307 | Indent::spaced(1), |
| 308 | Indent::zero(), |
| 309 | Indent::zero(), |
| 310 | )) |
| 311 | .set_alignment_horizontal(alignment:AlignmentHorizontal::Left) |
| 312 | .set_borders(Style::ascii().get_borders()) |
| 313 | } |
| 314 | |
| 315 | impl<R, D> TableOption<R, CompactConfig, D> for CompactConfig { |
| 316 | fn change(self, _: &mut R, cfg: &mut CompactConfig, _: &mut D) { |
| 317 | *cfg = self; |
| 318 | } |
| 319 | } |
| 320 | |