diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-06-07 05:48:48 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-06-07 05:48:48 +0000 |
commit | ef24de24a82fe681581cc130f342363c47c0969a (patch) | |
tree | 0d494f7e1a38b95c92426f58fe6eaa877303a86c /vendor/papergrid | |
parent | Releasing progress-linux version 1.74.1+dfsg1-1~progress7.99u1. (diff) | |
download | rustc-ef24de24a82fe681581cc130f342363c47c0969a.tar.xz rustc-ef24de24a82fe681581cc130f342363c47c0969a.zip |
Merging upstream version 1.75.0+dfsg1.
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'vendor/papergrid')
60 files changed, 11547 insertions, 0 deletions
diff --git a/vendor/papergrid/.cargo-checksum.json b/vendor/papergrid/.cargo-checksum.json new file mode 100644 index 000000000..b2a89dc61 --- /dev/null +++ b/vendor/papergrid/.cargo-checksum.json @@ -0,0 +1 @@ +{"files":{"Cargo.lock":"7e044f069f7fb00baab23079cb80e6c3718b79158fe0be6946f36d4199b4988e","Cargo.toml":"0381d261cebb9e568d34d11f91770a1424edb81c08a7f9e624b25b9e4e67bece","LICENSE-MIT":"49ad3fe9bc9d4f0798a481e1c73ac833c52b4525c72fb2c4b0cd1a510e88a177","README.md":"22e04f2d214da24344d16821627e51005979b94b6a76138f1c7d7f3b74ed821d","examples/color_map.rs":"309aea6737d4862ed235875a23a5ca8b24dc8b4621bcdf43f4f319fd91863e49","examples/colored_border.rs":"957ed3c9eb566a7c89ebcbd791f0286ea00714d91101df32484f03c7944eda01","examples/common_grid.rs":"e857595bda7c82258e74bb5b521d930ee40684263ea5b9dabf748669d21d2c92","examples/common_grid_no_std.rs":"5f4e1a97a95000ee4f4920643a35b153fe7638c4f5880fe16520aa8ab0a01c3c","examples/hello_world.rs":"d37088e1d5e2e9da9868ff80057c584d3ec3bbe8b2c6373d662140312be760a2","examples/papergrid_color.rs":"cd13b49fb627b1334cdf342eacad83992b1413bcf9e599fbe886a5856a0a3e5a","examples/span_usage.rs":"65dca2d8358b7519797c9dac8c31a02118b767d12c406e5b07d878f61d8136b6","src/color/ansi_color.rs":"f195d2204db6d3ed7f8c104d255ed595d4657113735a0a76b3ee9d552fb082b4","src/color/mod.rs":"aff92604ad47fe324a3b24eeb43f10cd88d050f1dce0b24c5f6bd313e85ad82c","src/color/static_color.rs":"41e71eb445e7896cebba2fbd3b929ffaf163841d1e026bfde0b84d3757fd492f","src/colors.rs":"d5128327f62afba64d1c3595c0e60444c5b9a1874a2d25e8fdd92488db1b6610","src/config/alignment.rs":"63140c1b37bdefe899a72d888376e4d09dc72fc0395fe5a8169075e6523944fd","src/config/border.rs":"2c3c54ac3ea30e1fedfc309cdc2ce65b25c6c5fef5a9bc4cb96a2c72d3dd348c","src/config/borders.rs":"eef502563ef8327d54281ffcf53985c2eaa49564efd40482262ed61c6fe5dc69","src/config/compact/mod.rs":"624b1e70fb55e120341e264090f0b96dd0d67af1e58ed4fc466e5a2b59c28cbf","src/config/entity.rs":"e7105b872e8bc2ece3b33512fddb8a49b2a4a2747cb4dc11832e5c8a52cf4ce9","src/config/indent.rs":"36eabb607418c6425789218a2a308adcd1de858d387b0054d7c1d4fb60c75fef","src/config/line.rs":"894b7a7a8d524998316bf3633595c2b4c6562e3720b1637a59148db95f99680c","src/config/mod.rs":"a35bb8f0e88c7f03528861a771ebc33a164186d8f764a4d3ec201023b93abc55","src/config/position.rs":"da7985c9ea27755a34c56992698140d96fef213dacba0d6c01cbbd7a888b2c79","src/config/sides.rs":"74dc20170d3c74dd3383a836eedc765328bb12f788f58e3a369129f58cdd1f77","src/config/spanned/borders_config.rs":"d90db37855ee352b1ed85d7f7086df6417f73fa45c825b7bdfcbd77284add13b","src/config/spanned/entity_map.rs":"171a1b30599e3a580158bb36ba24a2f5fd1cc9ca9ecca32cea15ff913b4d2611","src/config/spanned/formatting.rs":"092dcd385e7bba689f6a4643a1e97bf5d134633b3f48196f87c40315acdd7323","src/config/spanned/mod.rs":"8a4e09f7022cdb51b7a7b793959ca27702f1212c4914c531bb895987dcd90e3b","src/config/spanned/offset.rs":"1f48f5bbab07899da98c5792eca17ba2c4c5b0da0dfd3b99a23b7a99542a0598","src/dimension/compact.rs":"d4fee4d0e71bee70978f2c5ba28dfda26900995fc578a005977a733096254a14","src/dimension/mod.rs":"9d5d4bd81ff6b8da3dc5964b05dc67212770f90452f52e25874af906772176f9","src/dimension/spanned.rs":"c84633a9072a2a4cbb40425c7915f035605a42d06ca128397175eca51dfaa585","src/dimension/spanned_vec_records.rs":"a79ee463bc2a7b1f7cddf5609bfebf29679817904fa9c180fe84d47d1a877e35","src/grid/compact.rs":"176da4fe8fd06b350724223646809e375d8629c4a0b3ff663d210c8c73d86894","src/grid/iterable.rs":"6bb9df7c44de8224c533aeab18d256fb771a4181ba3cefc0c0fa3f676ab670b6","src/grid/mod.rs":"6d4b85cd4aced51ac15adabfc1c45caa1dfdc950ee67f5d927551105fd4a0eba","src/grid/peekable.rs":"83507fe45d99b73bc30615db977612c6651ae7b284359efcae56f2e90fbfbc51","src/lib.rs":"a5facbf0df5444df8ab0d0de7c08e1176e2bfa18b0069b17549d5ebf8ca2ad05","src/records/exact_records.rs":"e2f6f613cdd08e31f2ee6b5c0f6e0a7242e0c33d246d542cc5fbac6b92f5fcbf","src/records/into_records.rs":"7591512f44fe0ad015ae180b8bb603b9ed85d42b5b94255ba45ba6ecb922b4c1","src/records/iter_records.rs":"9ed52c17468a0d271115a96dd26c38a55f23ecc634834ddd75fdd4caf245b2b0","src/records/mod.rs":"dd58fd34e0ceadb6e7bdf4cfb5339842d07bd494a3ff91081e5ace71715e9782","src/records/peekable_records.rs":"fe0d96e1e11c520ae2e4755918f5dfc085b472eeaa83255a17a50c9c44d6d689","src/records/vec_records/cell.rs":"dc6ea0dc21ad4f54077ab3e749e920c0894a9ae183131bbd4b9fd5ae3cd385a1","src/records/vec_records/cell_info.rs":"29657b8aea4edbcd1f6cc722ad18e3ab421c7c828ac11748831bdb53755dc134","src/records/vec_records/mod.rs":"2d5c5f68be016b0a3ac8840fb6234060658e5a1479829a56c7a60bb310cac831","src/util/mod.rs":"5351c378b23d6ea21076352092d45abe641c293e0855c1d34ae2844fd2bb286e","src/util/string.rs":"fa22ae561397c8c9abd0a1e49861899ace684cd40c627b3fbd2a7db1216a489f","tests/grid/column_span.rs":"45f41564cf011b80ce0a5ce2baa4626366909350c32d0203f6e08e6ac00d2b92","tests/grid/format_configuration.rs":"e0a9c8269aabec443cd610b7b1b5d82bec9806d12cac4b35a62f040f2a40e6a1","tests/grid/mod.rs":"683b002b5a09ddc801fb7452d7cafb129546630966f12de79124ded401e60be4","tests/grid/render.rs":"58dbae9c694b127633a672b09e3c01f119235c729bfc3307995605a43f9ef500","tests/grid/row_span.rs":"df2ae66621fee380eb94f4560a626ecab4c7c13d66712566c22019b47e94468b","tests/grid/settings.rs":"37a08acb7d3ddf89bed6821d8e2a6fe42e1cd34ee25f5cdeefad927558cf879c","tests/grid/styling.rs":"9c5b0a399367c1660a5d0828bcaa732d8caa4761b775681f4d617fe5c961c599","tests/main.rs":"d54c32cbb3e1c476fda247a63bac07f99caa31f11a344563c33e8e9a52c4f17c","tests/util/grid_builder.rs":"418ab36bd19fd840e641c5f73434fa9a41b704b09c10b6add97fd47103cc3a54","tests/util/mod.rs":"bb73da6bdc2f6dbe1de249aaeeeabb3463a36a8fb5fb9b6faa13b5840cebdbe2"},"package":"a2ccbe15f2b6db62f9a9871642746427e297b0ceb85f9a7f1ee5ff47d184d0c8"}
\ No newline at end of file diff --git a/vendor/papergrid/Cargo.lock b/vendor/papergrid/Cargo.lock new file mode 100644 index 000000000..427b79bbe --- /dev/null +++ b/vendor/papergrid/Cargo.lock @@ -0,0 +1,137 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "ansi-str" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cf4578926a981ab0ca955dc023541d19de37112bc24c1a197bd806d3d86ad1d" +dependencies = [ + "ansitok", +] + +[[package]] +name = "ansitok" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "220044e6a1bb31ddee4e3db724d29767f352de47445a6cd75e1a173142136c83" +dependencies = [ + "nom", + "vte", +] + +[[package]] +name = "arrayvec" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b" + +[[package]] +name = "bytecount" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c676a478f63e9fa2dd5368a42f28bba0d6c560b775f38583c8bbaa7fcd67c9c" + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "memchr" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" + +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + +[[package]] +name = "owo-colors" +version = "3.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1b04fb49957986fdce4d6ee7a65027d55d4b6d2265e5848bbb507b58ccfdb6f" + +[[package]] +name = "papergrid" +version = "0.10.0" +dependencies = [ + "ansi-str", + "ansitok", + "bytecount", + "fnv", + "owo-colors", + "unicode-width", +] + +[[package]] +name = "proc-macro2" +version = "1.0.56" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b63bdb0cd06f1f4dedf69b254734f9b45af66e4a031e42a7480257d9898b435" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4424af4bf778aae2051a77b60283332f386554255d722233d09fbfc7e30da2fc" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "unicode-ident" +version = "1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5464a87b239f13a63a501f2701565754bae92d243d4bb7eb12f6d57d2269bf4" + +[[package]] +name = "unicode-width" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b" + +[[package]] +name = "utf8parse" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" + +[[package]] +name = "vte" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6cbce692ab4ca2f1f3047fcf732430249c0e971bfdd2b234cf2c47ad93af5983" +dependencies = [ + "arrayvec", + "utf8parse", + "vte_generate_state_changes", +] + +[[package]] +name = "vte_generate_state_changes" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d257817081c7dffcdbab24b9e62d2def62e2ff7d00b1c20062551e6cccc145ff" +dependencies = [ + "proc-macro2", + "quote", +] diff --git a/vendor/papergrid/Cargo.toml b/vendor/papergrid/Cargo.toml new file mode 100644 index 000000000..2b5cf8130 --- /dev/null +++ b/vendor/papergrid/Cargo.toml @@ -0,0 +1,85 @@ +# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO +# +# When uploading crates to the registry Cargo will automatically +# "normalize" Cargo.toml files for maximal compatibility +# with all versions of Cargo and also rewrite `path` dependencies +# to registry (e.g., crates.io) dependencies. +# +# If you are reading this file be aware that the original Cargo.toml +# will likely look very different (and much more reasonable). +# See Cargo.toml.orig for the original contents. + +[package] +edition = "2018" +name = "papergrid" +version = "0.10.0" +authors = ["Maxim Zhiburt <zhiburt@gmail.com>"] +description = "Papergrid is a core library to print a table" +readme = "README.md" +license = "MIT" +repository = "https://github.com/zhiburt/tabled" + +[[example]] +name = "papergrid_color" +required-features = [ + "std", + "color", +] + +[[example]] +name = "color_map" +path = "examples/color_map.rs" +required-features = ["std"] + +[[example]] +name = "colored_border" +path = "examples/colored_border.rs" +required-features = ["std"] + +[[example]] +name = "common_grid" +path = "examples/common_grid.rs" +required-features = ["std"] + +[[example]] +name = "span_usage" +path = "examples/span_usage.rs" +required-features = ["std"] + +[[example]] +name = "common_grid_no_std" +path = "examples/common_grid_no_std.rs" +required-features = [] + +[[example]] +name = "hello_world" +path = "examples/hello_world.rs" +required-features = ["std"] + +[dependencies.ansi-str] +version = "0.8" +optional = true + +[dependencies.ansitok] +version = "0.2" +optional = true + +[dependencies.bytecount] +version = "0.6" + +[dependencies.fnv] +version = "1.0" + +[dependencies.unicode-width] +version = "0.1" + +[dev-dependencies.owo-colors] +version = "3.4.0" + +[features] +color = [ + "ansi-str", + "ansitok", +] +default = ["std"] +std = [] diff --git a/vendor/papergrid/LICENSE-MIT b/vendor/papergrid/LICENSE-MIT new file mode 100644 index 000000000..6077b9d68 --- /dev/null +++ b/vendor/papergrid/LICENSE-MIT @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2021 Maxim Zhiburt + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/vendor/papergrid/README.md b/vendor/papergrid/README.md new file mode 100644 index 000000000..c5ad00f7b --- /dev/null +++ b/vendor/papergrid/README.md @@ -0,0 +1,84 @@ +# papergrid + +This is library for pretty tables. + +It has relatively low level API. +If you're interested in a more friendly one take a look at [`tabled`](https://github.com/zhiburt/tabled). + +## Usage + +```rust +use papergrid::{ + colors::NoColors, + config::{ + spanned::SpannedConfig, AlignmentHorizontal, AlignmentVertical, Borders, Entity, Indent, + Sides, + }, + dimension::{spanned::SpannedGridDimension, Estimate}, + grid::peekable::PeekableGrid, + records::vec_records::{CellInfo, VecRecords}, +}; + +fn main() { + let mut cfg = SpannedConfig::default(); + cfg.set_borders(Borders { + top: Some('-'), + top_left: Some('+'), + top_right: Some('+'), + top_intersection: Some('+'), + bottom: Some('-'), + bottom_left: Some('+'), + bottom_right: Some('+'), + bottom_intersection: Some('+'), + horizontal: Some('-'), + left_intersection: Some('+'), + right_intersection: Some('+'), + vertical: Some('|'), + left: Some('|'), + right: Some('|'), + intersection: Some('+'), + }); + cfg.set_column_span((1, 1), 3); + cfg.set_row_span((0, 0), 2); + cfg.set_alignment_horizontal((1, 0).into(), AlignmentHorizontal::Center); + cfg.set_alignment_vertical(Entity::Global, AlignmentVertical::Center); + cfg.set_padding( + (0, 0).into(), + Sides::new( + Indent::spaced(4), + Indent::spaced(4), + Indent::spaced(1), + Indent::spaced(1), + ), + ); + + let data = [ + ["Papergrid", "is a library", "for print tables", "!"], + ["", "Just like this", "", ""], + ]; + + let data = data + .iter() + .map(|row| row.iter().map(CellInfo::new).collect()) + .collect(); + + let records = VecRecords::new(data); + + let mut dims = SpannedGridDimension::default(); + dims.estimate(&records, &cfg); + + let grid = PeekableGrid::new(&records, &cfg, &dims, NoColors).to_string(); + + println!("{grid}"); +} +``` + +Running the example you must see. + +```text ++-----------------+------------+----------------+-+ +| |is a library|for print tables|!| ++ Papergrid +------------+----------------+-+ +| |Just like this | ++-----------------+------------+----------------+-+ +```
\ No newline at end of file diff --git a/vendor/papergrid/examples/color_map.rs b/vendor/papergrid/examples/color_map.rs new file mode 100644 index 000000000..e9c238ea4 --- /dev/null +++ b/vendor/papergrid/examples/color_map.rs @@ -0,0 +1,105 @@ +//! This example demonstrates using a [`HashMap`] of colors to simplify styling +//! sections of a [`Grid`] without embedding ANSI escape characters into cell values. +//! +//! * 🚩 This example requires the `color` feature. +//! +//! * Check out [`owo_colors`] for additional styling options available through their API. + +use std::{ + collections::HashMap, + fmt::{Display, Formatter}, + io::Write, +}; + +use owo_colors::{ + colors::{Black, Blue, Red}, + Style as OStyle, +}; + +use papergrid::{ + color::Color, + config::spanned::SpannedConfig, + config::{Borders, Position}, + dimension::spanned::SpannedGridDimension, + dimension::Estimate, + grid::iterable::Grid, + records::IterRecords, +}; + +fn main() { + let records = vec![vec!["Hello", "World"], vec!["Hi", "World"]]; + let records = IterRecords::new(&records, 2, None); + + let cfg = generate_table_config(); + + let mut dimension = SpannedGridDimension::default(); + dimension.estimate(records, &cfg); + + let colors = generate_colors(); + + let grid = Grid::new(records, &dimension, &cfg, &colors); + + grid.build(UTF8Stdout(std::io::stdout())).unwrap(); + println!(); +} + +fn generate_colors() -> HashMap<Position, Style> { + HashMap::from([ + ((0, 0), Style(OStyle::default().bg::<Red>().fg::<Black>())), + ((1, 1), Style(OStyle::default().bg::<Blue>())), + ]) +} + +fn generate_table_config() -> SpannedConfig { + let mut cfg = SpannedConfig::default(); + cfg.set_borders(Borders { + top: Some('-'), + bottom: Some('-'), + left: Some('|'), + right: Some('|'), + vertical: Some('|'), + horizontal: Some('-'), + ..Default::default() + }); + cfg.set_borders_missing('+'); + + cfg +} + +#[derive(Debug, Clone, Default)] +struct Style(OStyle); + +impl Color for Style { + fn fmt_prefix<W: std::fmt::Write>(&self, f: &mut W) -> std::fmt::Result { + let buf = OStylePrefix(&self.0).to_string(); + f.write_str(&buf) + } +} + +struct OStylePrefix<'a>(&'a OStyle); + +impl Display for OStylePrefix<'_> { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + self.0.fmt_prefix(f) + } +} + +struct UTF8Stdout(std::io::Stdout); + +impl std::fmt::Write for UTF8Stdout { + fn write_str(&mut self, s: &str) -> std::fmt::Result { + let mut buf = s.as_bytes(); + loop { + let n = self.0.write(buf).map_err(|_| std::fmt::Error::default())?; + if n == buf.len() { + break; + } + + buf = &buf[n..]; + } + + self.0.flush().map_err(|_| std::fmt::Error::default())?; + + Ok(()) + } +} diff --git a/vendor/papergrid/examples/colored_border.rs b/vendor/papergrid/examples/colored_border.rs new file mode 100644 index 000000000..ec7ff2848 --- /dev/null +++ b/vendor/papergrid/examples/colored_border.rs @@ -0,0 +1,74 @@ +//! This example demonstrates using colors to stylize [`Grid`] borders. +//! Borders can be set globally with [`SpannedConfig::set_border_color_global()`] +//! or individually with [`SpannedConfig::set_border_color()`]. +//! +//! * 🚩 This example requires the `color` feature. +//! +//! * [`CompactConfig`] also supports colorization when the `color` feature is enabled. + +use papergrid::{ + color::AnsiColor, + colors::NoColors, + config::spanned::SpannedConfig, + config::{AlignmentHorizontal, AlignmentVertical, Borders, Entity::Global, Indent, Sides}, + dimension::spanned::SpannedGridDimension, + dimension::Estimate, + grid::iterable::Grid, + records::IterRecords, +}; + +fn main() { + let cfg = generate_table_config(); + + let data = vec![ + vec!["Papergrid", "is a library", "for printing tables", "!"], + vec!["", "Just like this", "", ""], + ]; + let records = IterRecords::new(data, 4, Some(2)); + + let mut dim = SpannedGridDimension::default(); + dim.estimate(&records, &cfg); + + let grid = Grid::new(records, &dim, &cfg, NoColors).to_string(); + + println!("{grid}"); +} + +fn generate_table_config() -> SpannedConfig { + let style = Borders { + top: Some('-'), + top_left: Some('+'), + top_right: Some('+'), + top_intersection: Some('+'), + bottom: Some('-'), + bottom_left: Some('+'), + bottom_right: Some('+'), + bottom_intersection: Some('+'), + horizontal: Some('-'), + left_intersection: Some('+'), + right_intersection: Some('+'), + vertical: Some('|'), + left: Some('|'), + right: Some('|'), + intersection: Some('+'), + }; + + let mut cfg = SpannedConfig::default(); + cfg.set_borders(style); + cfg.set_column_span((1, 1), 3); + cfg.set_row_span((0, 0), 2); + cfg.set_alignment_horizontal((1, 0).into(), AlignmentHorizontal::Center); + cfg.set_alignment_vertical(Global, AlignmentVertical::Center); + cfg.set_padding( + (0, 0).into(), + Sides::new( + Indent::spaced(4), + Indent::spaced(4), + Indent::spaced(1), + Indent::spaced(1), + ), + ); + cfg.set_border_color_global(AnsiColor::new("\u{1b}[42m".into(), "\u{1b}[0m".into())); + + cfg +} diff --git a/vendor/papergrid/examples/common_grid.rs b/vendor/papergrid/examples/common_grid.rs new file mode 100644 index 000000000..a0fc8d4c7 --- /dev/null +++ b/vendor/papergrid/examples/common_grid.rs @@ -0,0 +1,67 @@ +//! This example demonstrates the flexibility of [`papergrid`] with manual configurations +//! of [`Borders`], [`CompactConfig`], and column counts with [`IterRecords`]. +//! +//! * For an alternative to [`CompactGrid`] and [`CompactGridDimension`] with +//! flexible row height, variable intra-column spans, and multiline cell support +//! see [`Grid`] and [`SpannedGridDimension`]. + +use papergrid::{ + config::compact::CompactConfig, + config::{AlignmentHorizontal, Borders, Indent, Sides}, + dimension::compact::CompactGridDimension, + dimension::Estimate, + grid::compact::CompactGrid, + records::IterRecords, +}; + +fn main() { + let cfg = generate_table_config(); + + let data = [ + ["Papergrid", "is a library", "for printing tables", "!"], + [ + "Just like this", + "NOTICE", + "that multiline is not supported", + "H\ne\nl\nl\no", + ], + ]; + let records = IterRecords::new(data, 4, None); + + let mut dim = CompactGridDimension::default(); + dim.estimate(records, &cfg); + + let grid = CompactGrid::new(records, &dim, &cfg); + + println!("{grid}"); +} + +fn generate_table_config() -> CompactConfig { + const STYLE: Borders<char> = Borders { + top: Some('-'), + top_left: Some('+'), + top_right: Some('+'), + top_intersection: Some('+'), + bottom: Some('-'), + bottom_left: Some('+'), + bottom_right: Some('+'), + bottom_intersection: Some('+'), + horizontal: Some('-'), + left_intersection: Some('+'), + right_intersection: Some('+'), + vertical: Some('|'), + left: Some('|'), + right: Some('|'), + intersection: Some('+'), + }; + + CompactConfig::default() + .set_borders(STYLE) + .set_alignment_horizontal(AlignmentHorizontal::Center) + .set_padding(Sides::new( + Indent::spaced(1), + Indent::spaced(1), + Indent::spaced(0), + Indent::spaced(0), + )) +} diff --git a/vendor/papergrid/examples/common_grid_no_std.rs b/vendor/papergrid/examples/common_grid_no_std.rs new file mode 100644 index 000000000..ebb77bcbb --- /dev/null +++ b/vendor/papergrid/examples/common_grid_no_std.rs @@ -0,0 +1,73 @@ +//! This example demonstrates using [`papergrid`] without [The Rust Standard Library](std). +//! +//! * Note the missing, pre-built [`Dimension`] implementations requiring manual design. + +use papergrid::{ + config::compact::CompactConfig, + config::{AlignmentHorizontal, Borders, Indent, Sides}, + dimension::Dimension, + grid::compact::CompactGrid, + records::IterRecords, +}; + +fn main() { + let data = [ + ["Papergrid", "is a library", "for printing tables", "!"], + [ + "Just like this", + "NOTICE", + "that multiline is not supported", + "H\ne\nl\nl\no", + ], + ]; + + let records = IterRecords::new(data, 4, None); + let dim = ConstDims(&[20, 15, 40, 3], 4); + let cfg = generate_table_config(); + + let grid = CompactGrid::new(records, &dim, &cfg); + + println!("{grid}"); +} + +fn generate_table_config() -> CompactConfig { + const STYLE: Borders<char> = Borders { + top: Some('-'), + top_left: Some('+'), + top_right: Some('+'), + top_intersection: Some('+'), + bottom: Some('-'), + bottom_left: Some('+'), + bottom_right: Some('+'), + bottom_intersection: Some('+'), + horizontal: Some('-'), + left_intersection: Some('+'), + right_intersection: Some('+'), + vertical: Some('|'), + left: Some('|'), + right: Some('|'), + intersection: Some('+'), + }; + + CompactConfig::default() + .set_borders(STYLE) + .set_alignment_horizontal(AlignmentHorizontal::Center) + .set_padding(Sides::new( + Indent::spaced(1), + Indent::spaced(1), + Indent::spaced(3), + Indent::spaced(0), + )) +} + +struct ConstDims<'a>(&'a [usize], usize); + +impl Dimension for ConstDims<'_> { + fn get_width(&self, column: usize) -> usize { + self.0[column] + } + + fn get_height(&self, _: usize) -> usize { + self.1 + } +} diff --git a/vendor/papergrid/examples/hello_world.rs b/vendor/papergrid/examples/hello_world.rs new file mode 100644 index 000000000..90be1fa17 --- /dev/null +++ b/vendor/papergrid/examples/hello_world.rs @@ -0,0 +1,78 @@ +//! This example demonstrates the flexibility of [`papergrid`] with manual configurations +//! of [`Borders`], [`SpannedConfig`], and column counts with [`IterRecords`]. +//! +//! * For an alternative to [`Grid`] and [`SpannedGridDimension`] with +//! uniform row height and intra-column spans see [`CompactGrid`] and [`CompactGridDimension`]. +//! * Note that [`Grid`] supports multiline cells whereas [`CompactGrid`] does not. +//! * Note that [`Dimension`] implementations rely on [`Dimension::estimate()`] +//! to correctly format outputs, and typically trigger index-out-of-bounds errors otherwise. + +use papergrid::{ + colors::NoColors, + config::spanned::SpannedConfig, + config::{AlignmentHorizontal, AlignmentVertical, Borders, Entity::Global, Indent, Sides}, + dimension::spanned::SpannedGridDimension, + dimension::Estimate, + grid::iterable::Grid, + records::IterRecords, +}; + +fn main() { + let cfg = generate_table_config(); + + let data = [ + ["Papergrid", "is a library", "for printing tables", "!"], + [ + "", + "Just like\n\nthis", + "", + "", + ], + ]; + let records = IterRecords::new(data, 4, None); + + let mut dim = SpannedGridDimension::default(); + dim.estimate(records, &cfg); + + let grid = Grid::new(records, &dim, &cfg, NoColors).to_string(); + + println!("{grid}"); +} + +fn generate_table_config() -> SpannedConfig { + const STYLE: Borders<char> = Borders { + top: Some('-'), + top_left: Some('+'), + top_right: Some('+'), + top_intersection: Some('+'), + bottom: Some('-'), + bottom_left: Some('+'), + bottom_right: Some('+'), + bottom_intersection: Some('+'), + horizontal: Some('-'), + left_intersection: Some('+'), + right_intersection: Some('+'), + vertical: Some('|'), + left: Some('|'), + right: Some('|'), + intersection: Some('+'), + }; + + let mut cfg = SpannedConfig::default(); + cfg.set_borders(STYLE); + cfg.set_column_span((1, 1), 3); + cfg.set_row_span((0, 0), 2); + cfg.set_alignment_horizontal((1, 0).into(), AlignmentHorizontal::Center); + cfg.set_alignment_vertical(Global, AlignmentVertical::Center); + cfg.set_padding( + (0, 0).into(), + Sides::new( + Indent::spaced(4), + Indent::spaced(4), + Indent::spaced(1), + Indent::spaced(1), + ), + ); + + cfg +} diff --git a/vendor/papergrid/examples/papergrid_color.rs b/vendor/papergrid/examples/papergrid_color.rs new file mode 100644 index 000000000..f634c0d6c --- /dev/null +++ b/vendor/papergrid/examples/papergrid_color.rs @@ -0,0 +1,69 @@ +//! This example demonstrates using colors to stylize [`Grid`] cells. +//! +//! * 🚩 This example requires the `color` feature. +//! +//! * Note that this example uses inline ANSI escape characters to style +//! grid cells. `Grid::new(_, _, _, NoColors)` indicates that a color +//! map is not provided. NOT that colors are ignored in the output. + +use std::io::Write; + +use papergrid::{ + colors::NoColors, config::spanned::SpannedConfig, config::Borders, + dimension::spanned::SpannedGridDimension, dimension::Estimate, grid::iterable::Grid, + records::IterRecords, +}; + +fn main() { + let data = vec![ + vec!["\u{1b}[42mHello\u{1b}[0m", "\u{1b}[43mWorld\u{1b}[0m"], + vec!["\u{1b}[44mHi\u{1b}[0m", "\u{1b}[45mWorld\u{1b}[0m"], + ]; + let records = IterRecords::new(data, 2, None); + + let cfg = generate_table_config(); + + let mut dimension = SpannedGridDimension::default(); + dimension.estimate(&records, &cfg); + + let grid = Grid::new(&records, &dimension, &cfg, NoColors); + + grid.build(UTF8Stdout(std::io::stdout())).unwrap(); + println!(); +} + +fn generate_table_config() -> SpannedConfig { + let mut cfg = SpannedConfig::default(); + cfg.set_borders(Borders { + top: Some('-'), + bottom: Some('-'), + left: Some('|'), + right: Some('|'), + vertical: Some('|'), + horizontal: Some('-'), + ..Default::default() + }); + cfg.set_borders_missing('+'); + + cfg +} + +struct UTF8Stdout(std::io::Stdout); + +impl std::fmt::Write for UTF8Stdout { + fn write_str(&mut self, s: &str) -> std::fmt::Result { + let mut buf = s.as_bytes(); + loop { + let n = self.0.write(buf).map_err(|_| std::fmt::Error::default())?; + if n == buf.len() { + break; + } + + buf = &buf[n..]; + } + + self.0.flush().map_err(|_| std::fmt::Error::default())?; + + Ok(()) + } +} diff --git a/vendor/papergrid/examples/span_usage.rs b/vendor/papergrid/examples/span_usage.rs new file mode 100644 index 000000000..d5f963767 --- /dev/null +++ b/vendor/papergrid/examples/span_usage.rs @@ -0,0 +1,63 @@ +use papergrid::{ + colors::NoColors, + config::{ + spanned::SpannedConfig, AlignmentHorizontal, AlignmentVertical, Borders, Entity, Indent, + Sides, + }, + dimension::{spanned::SpannedGridDimension, Estimate}, + grid::peekable::PeekableGrid, + records::vec_records::{CellInfo, VecRecords}, +}; + +fn main() { + let mut cfg = SpannedConfig::default(); + cfg.set_borders(Borders { + top: Some('-'), + top_left: Some('+'), + top_right: Some('+'), + top_intersection: Some('+'), + bottom: Some('-'), + bottom_left: Some('+'), + bottom_right: Some('+'), + bottom_intersection: Some('+'), + horizontal: Some('-'), + left_intersection: Some('+'), + right_intersection: Some('+'), + vertical: Some('|'), + left: Some('|'), + right: Some('|'), + intersection: Some('+'), + }); + cfg.set_column_span((1, 1), 3); + cfg.set_row_span((0, 0), 2); + cfg.set_alignment_horizontal((1, 0).into(), AlignmentHorizontal::Center); + cfg.set_alignment_vertical(Entity::Global, AlignmentVertical::Center); + cfg.set_padding( + (0, 0).into(), + Sides::new( + Indent::spaced(4), + Indent::spaced(4), + Indent::spaced(1), + Indent::spaced(1), + ), + ); + + let data = [ + ["Papergrid", "is a library", "for print tables", "!"], + ["", "Just like this", "", ""], + ]; + + let data = data + .iter() + .map(|row| row.iter().map(CellInfo::new).collect()) + .collect(); + + let records = VecRecords::new(data); + + let mut dims = SpannedGridDimension::default(); + dims.estimate(&records, &cfg); + + let grid = PeekableGrid::new(&records, &cfg, &dims, NoColors).to_string(); + + println!("{grid}"); +} diff --git a/vendor/papergrid/src/color/ansi_color.rs b/vendor/papergrid/src/color/ansi_color.rs new file mode 100644 index 000000000..6fcf4f832 --- /dev/null +++ b/vendor/papergrid/src/color/ansi_color.rs @@ -0,0 +1,86 @@ +use std::{ + borrow::Cow, + fmt::{self, Write}, +}; + +use super::{Color, StaticColor}; + +/// The structure represents a ANSI color by suffix and prefix. +#[derive(Debug, Default, Clone, PartialEq, Eq, PartialOrd, Ord)] +pub struct AnsiColor<'a> { + prefix: Cow<'a, str>, + suffix: Cow<'a, str>, +} + +impl<'a> AnsiColor<'a> { + /// Constructs a new instance with suffix and prefix. + /// + /// They are not checked so you should make sure you provide correct ANSI. + /// Otherwise you may want to use [`TryFrom`]. + /// + /// [`TryFrom`]: std::convert::TryFrom + pub const fn new(prefix: Cow<'a, str>, suffix: Cow<'a, str>) -> Self { + Self { prefix, suffix } + } +} + +impl AnsiColor<'_> { + /// Gets a reference to a prefix. + pub fn get_prefix(&self) -> &str { + &self.prefix + } + + /// Gets a reference to a suffix. + pub fn get_suffix(&self) -> &str { + &self.suffix + } +} + +impl Color for AnsiColor<'_> { + fn fmt_prefix<W: Write>(&self, f: &mut W) -> fmt::Result { + f.write_str(&self.prefix) + } + + fn fmt_suffix<W: Write>(&self, f: &mut W) -> fmt::Result { + f.write_str(&self.suffix) + } +} + +#[cfg(feature = "color")] +impl std::convert::TryFrom<&str> for AnsiColor<'static> { + type Error = (); + + fn try_from(value: &str) -> Result<Self, Self::Error> { + parse_ansi_color(value).ok_or(()) + } +} + +#[cfg(feature = "color")] +impl std::convert::TryFrom<String> for AnsiColor<'static> { + type Error = (); + + fn try_from(value: String) -> Result<Self, Self::Error> { + Self::try_from(value.as_str()) + } +} + +#[cfg(feature = "color")] +fn parse_ansi_color(s: &str) -> Option<AnsiColor<'static>> { + let mut blocks = ansi_str::get_blocks(s); + let block = blocks.next()?; + let style = block.style(); + + let start = style.start().to_string(); + let end = style.end().to_string(); + + Some(AnsiColor::new(start.into(), end.into())) +} + +impl From<StaticColor> for AnsiColor<'static> { + fn from(value: StaticColor) -> Self { + Self::new( + Cow::Borrowed(value.get_prefix()), + Cow::Borrowed(value.get_suffix()), + ) + } +} diff --git a/vendor/papergrid/src/color/mod.rs b/vendor/papergrid/src/color/mod.rs new file mode 100644 index 000000000..e4618b750 --- /dev/null +++ b/vendor/papergrid/src/color/mod.rs @@ -0,0 +1,51 @@ +//! A module which contains [`Color`] trait and its implementation [`AnsiColor`]. + +#[cfg(feature = "std")] +mod ansi_color; +mod static_color; + +#[cfg(feature = "std")] +pub use ansi_color::AnsiColor; + +pub use static_color::StaticColor; + +use core::fmt::{self, Write}; + +#[allow(unreachable_pub)] +/// A trait which prints an ANSI prefix and suffix. +pub trait Color { + /// Print ANSI prefix. + fn fmt_prefix<W: Write>(&self, f: &mut W) -> fmt::Result; + + /// Print ANSI suffix. + fn fmt_suffix<W: Write>(&self, f: &mut W) -> fmt::Result { + f.write_str("\u{1b}[0m") + } + + /// Print colored text. + /// + /// It may not handle `\n` (new lines). + fn colorize<W: Write>(&self, f: &mut W, text: &str) -> fmt::Result { + self.fmt_prefix(f)?; + f.write_str(text)?; + self.fmt_suffix(f)?; + Ok(()) + } +} + +impl<C> Color for &C +where + C: Color, +{ + fn fmt_prefix<W: Write>(&self, f: &mut W) -> fmt::Result { + C::fmt_prefix(self, f) + } + + fn fmt_suffix<W: Write>(&self, f: &mut W) -> fmt::Result { + C::fmt_suffix(self, f) + } + + fn colorize<W: Write>(&self, f: &mut W, text: &str) -> fmt::Result { + C::colorize(self, f, text) + } +} diff --git a/vendor/papergrid/src/color/static_color.rs b/vendor/papergrid/src/color/static_color.rs new file mode 100644 index 000000000..3f9a38cfd --- /dev/null +++ b/vendor/papergrid/src/color/static_color.rs @@ -0,0 +1,49 @@ +use core::fmt::{self, Write}; + +use super::Color; + +/// The structure represents a ANSI color by suffix and prefix. +#[derive(Debug, Default, Clone, Copy, Eq, PartialEq, PartialOrd, Ord)] +pub struct StaticColor { + prefix: &'static str, + suffix: &'static str, +} + +impl StaticColor { + /// Constructs a new instance with suffix and prefix. + /// + /// They are not checked so you should make sure you provide correct ANSI. + /// Otherwise you may want to use [`TryFrom`]. + /// + /// [`TryFrom`]: std::convert::TryFrom + pub const fn new(prefix: &'static str, suffix: &'static str) -> Self { + Self { prefix, suffix } + } + + /// Verifies if anything was actually set. + pub const fn is_empty(&self) -> bool { + self.prefix.is_empty() && self.suffix.is_empty() + } +} + +impl StaticColor { + /// Gets a reference to a prefix. + pub fn get_prefix(&self) -> &'static str { + self.prefix + } + + /// Gets a reference to a suffix. + pub fn get_suffix(&self) -> &'static str { + self.suffix + } +} + +impl Color for StaticColor { + fn fmt_prefix<W: Write>(&self, f: &mut W) -> fmt::Result { + f.write_str(self.prefix) + } + + fn fmt_suffix<W: Write>(&self, f: &mut W) -> fmt::Result { + f.write_str(self.suffix) + } +} diff --git a/vendor/papergrid/src/colors.rs b/vendor/papergrid/src/colors.rs new file mode 100644 index 000000000..f671ec005 --- /dev/null +++ b/vendor/papergrid/src/colors.rs @@ -0,0 +1,89 @@ +//! A module which contains [Colors] trait and its blanket implementations. + +use crate::{color::Color, config::Position}; + +/// A trait which represents map of colors. +pub trait Colors { + /// Color implementation. + type Color: Color; + + /// Returns a color for a given position. + fn get_color(&self, pos: (usize, usize)) -> Option<&Self::Color>; +} + +impl<C> Colors for &'_ C +where + C: Colors, +{ + type Color = C::Color; + + fn get_color(&self, pos: Position) -> Option<&Self::Color> { + C::get_color(self, pos) + } +} + +#[cfg(feature = "std")] +impl<C> Colors for std::collections::HashMap<Position, C> +where + C: Color, +{ + type Color = C; + + fn get_color(&self, pos: Position) -> Option<&Self::Color> { + self.get(&pos) + } +} + +#[cfg(feature = "std")] +impl<C> Colors for std::collections::BTreeMap<Position, C> +where + C: Color, +{ + type Color = C; + + fn get_color(&self, pos: Position) -> Option<&Self::Color> { + self.get(&pos) + } +} + +#[cfg(feature = "std")] +impl<C> Colors for crate::config::spanned::EntityMap<Option<C>> +where + C: Color, +{ + type Color = C; + + fn get_color(&self, pos: Position) -> Option<&Self::Color> { + self.get(pos.into()).as_ref() + } +} + +/// The structure represents empty [`Colors`] map. +#[derive(Debug, Default, Clone)] +pub struct NoColors; + +impl Colors for NoColors { + type Color = EmptyColor; + + fn get_color(&self, _: Position) -> Option<&Self::Color> { + None + } +} + +/// A color which is actually has not value. +#[derive(Debug)] +pub struct EmptyColor; + +impl Color for EmptyColor { + fn fmt_prefix<W: core::fmt::Write>(&self, _: &mut W) -> core::fmt::Result { + Ok(()) + } + + fn colorize<W: core::fmt::Write>(&self, _: &mut W, _: &str) -> core::fmt::Result { + Ok(()) + } + + fn fmt_suffix<W: core::fmt::Write>(&self, _: &mut W) -> core::fmt::Result { + Ok(()) + } +} diff --git a/vendor/papergrid/src/config/alignment.rs b/vendor/papergrid/src/config/alignment.rs new file mode 100644 index 000000000..2b3b5e9db --- /dev/null +++ b/vendor/papergrid/src/config/alignment.rs @@ -0,0 +1,21 @@ +/// [`AlignmentHorizontal`] represents an horizontal alignment of a cell content. +#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord)] +pub enum AlignmentHorizontal { + /// Align to the center. + Center, + /// Align on the left. + Left, + /// Align on the right. + Right, +} + +/// [`AlignmentVertical`] represents an vertical alignment of a cell content. +#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord)] +pub enum AlignmentVertical { + /// Align to the center. + Center, + /// Align to the top. + Top, + /// Align to the bottom. + Bottom, +} diff --git a/vendor/papergrid/src/config/border.rs b/vendor/papergrid/src/config/border.rs new file mode 100644 index 000000000..e803900f7 --- /dev/null +++ b/vendor/papergrid/src/config/border.rs @@ -0,0 +1,142 @@ +/// Border is a representation of a cells's borders (left, right, top, bottom, and the corners) +/// +/// +/// ```text +/// top border +/// | +/// V +/// corner top left ------> +_______+ <---- corner top left +/// | | +/// left border ----------> | cell | <---- right border +/// | | +/// corner bottom right --> +_______+ <---- corner bottom right +/// ^ +/// | +/// bottom border +/// ``` +#[derive(Debug, Clone, Copy, Default, Eq, PartialEq, PartialOrd, Ord)] +pub struct Border<T> { + /// A character for a top. + pub top: Option<T>, + /// A character for a bottom. + pub bottom: Option<T>, + /// A character for a left. + pub left: Option<T>, + /// A character for a right. + pub right: Option<T>, + /// A character for a left top corner. + pub left_top_corner: Option<T>, + /// A character for a left bottom corner. + pub left_bottom_corner: Option<T>, + /// A character for a right top corner. + pub right_top_corner: Option<T>, + /// A character for a right bottom corner. + pub right_bottom_corner: Option<T>, +} + +impl<T> Border<T> { + /// This function constructs a cell borders with all sides set. + #[allow(clippy::too_many_arguments)] + pub const fn full( + top: T, + bottom: T, + left: T, + right: T, + top_left: T, + top_right: T, + bottom_left: T, + bottom_right: T, + ) -> Self { + Self { + top: Some(top), + bottom: Some(bottom), + right: Some(right), + right_top_corner: Some(top_right), + right_bottom_corner: Some(bottom_right), + left: Some(left), + left_bottom_corner: Some(bottom_left), + left_top_corner: Some(top_left), + } + } + + /// Checks whether any side is set. + pub const fn is_empty(&self) -> bool { + self.top.is_none() + && self.left_top_corner.is_none() + && self.right_top_corner.is_none() + && self.bottom.is_none() + && self.left_bottom_corner.is_none() + && self.left_top_corner.is_none() + && self.left.is_none() + && self.right.is_none() + } + + /// Verifies whether anything is set on the top. + pub const fn has_top(&self) -> bool { + self.top.is_some() || self.left_top_corner.is_some() || self.right_top_corner.is_some() + } + + /// Verifies whether anything is set on the bottom. + pub const fn has_bottom(&self) -> bool { + self.bottom.is_some() + || self.left_bottom_corner.is_some() + || self.right_bottom_corner.is_some() + } + + /// Verifies whether anything is set on the left. + pub const fn has_left(&self) -> bool { + self.left.is_some() || self.left_top_corner.is_some() || self.left_bottom_corner.is_some() + } + + /// Verifies whether anything is set on the right. + pub const fn has_right(&self) -> bool { + self.right.is_some() + || self.right_top_corner.is_some() + || self.right_bottom_corner.is_some() + } +} + +impl<T: Copy> Border<T> { + /// This function constructs a cell borders with all sides's char set to a given character. + /// + /// It behaves like [`Border::full`] with the same character set to each side. + pub fn filled(c: T) -> Self { + Self::full(c, c, c, c, c, c, c, c) + } +} + +impl<T: Copy> Border<&T> { + /// This function constructs a cell borders with all sides's char set to a given character. + /// + /// It behaves like [`Border::full`] with the same character set to each side. + pub fn copied(&self) -> Border<T> { + Border { + top: self.top.copied(), + bottom: self.bottom.copied(), + left: self.left.copied(), + right: self.right.copied(), + left_bottom_corner: self.left_bottom_corner.copied(), + left_top_corner: self.left_top_corner.copied(), + right_bottom_corner: self.right_bottom_corner.copied(), + right_top_corner: self.right_top_corner.copied(), + } + } +} + +impl<T: Clone> Border<&T> { + /// This function constructs a cell borders with all sides's char set to a given character. + /// + /// It behaves like [`Border::full`] with the same character set to each side. + pub fn cloned(&self) -> Border<T> { + Border { + top: self.top.cloned(), + bottom: self.bottom.cloned(), + left: self.left.cloned(), + right: self.right.cloned(), + left_bottom_corner: self.left_bottom_corner.cloned(), + left_top_corner: self.left_top_corner.cloned(), + right_bottom_corner: self.right_bottom_corner.cloned(), + right_top_corner: self.right_top_corner.cloned(), + } + } +} diff --git a/vendor/papergrid/src/config/borders.rs b/vendor/papergrid/src/config/borders.rs new file mode 100644 index 000000000..d242e1f63 --- /dev/null +++ b/vendor/papergrid/src/config/borders.rs @@ -0,0 +1,152 @@ +/// Borders represents a Table frame with horizontal and vertical split lines. +#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +pub struct Borders<T> { + /// A top horizontal on the frame. + pub top: Option<T>, + /// A top left on the frame. + pub top_left: Option<T>, + /// A top right on the frame. + pub top_right: Option<T>, + /// A top horizontal intersection on the frame. + pub top_intersection: Option<T>, + + /// A bottom horizontal on the frame. + pub bottom: Option<T>, + /// A bottom left on the frame. + pub bottom_left: Option<T>, + /// A bottom right on the frame. + pub bottom_right: Option<T>, + /// A bottom horizontal intersection on the frame. + pub bottom_intersection: Option<T>, + + /// A horizontal split. + pub horizontal: Option<T>, + /// A vertical split. + pub vertical: Option<T>, + /// A top left character on the frame. + pub intersection: Option<T>, + + /// A vertical split on the left frame line. + pub left: Option<T>, + /// A horizontal split on the left frame line. + pub left_intersection: Option<T>, + + /// A vertical split on the right frame line. + pub right: Option<T>, + /// A horizontal split on the right frame line. + pub right_intersection: Option<T>, +} + +impl<T> Borders<T> { + /// Returns empty borders. + pub const fn empty() -> Self { + Self { + top: None, + top_left: None, + top_right: None, + top_intersection: None, + bottom: None, + bottom_left: None, + bottom_right: None, + bottom_intersection: None, + horizontal: None, + left: None, + right: None, + vertical: None, + left_intersection: None, + right_intersection: None, + intersection: None, + } + } + + /// Returns Borders filled in with a supplied value. + pub const fn filled(val: T) -> Self + where + T: Copy, + { + Self { + top: Some(val), + top_left: Some(val), + top_right: Some(val), + top_intersection: Some(val), + bottom: Some(val), + bottom_left: Some(val), + bottom_right: Some(val), + bottom_intersection: Some(val), + horizontal: Some(val), + left: Some(val), + right: Some(val), + vertical: Some(val), + left_intersection: Some(val), + right_intersection: Some(val), + intersection: Some(val), + } + } + + /// A verification whether any border was set. + pub const fn is_empty(&self) -> bool { + !(self.top.is_some() + || self.top_left.is_some() + || self.top_right.is_some() + || self.top_intersection.is_some() + || self.bottom.is_some() + || self.bottom_left.is_some() + || self.bottom_right.is_some() + || self.bottom_intersection.is_some() + || self.horizontal.is_some() + || self.left.is_some() + || self.right.is_some() + || self.vertical.is_some() + || self.left_intersection.is_some() + || self.right_intersection.is_some() + || self.intersection.is_some()) + } + + /// Verifies if borders has left line set on the frame. + pub const fn has_left(&self) -> bool { + self.left.is_some() + || self.left_intersection.is_some() + || self.top_left.is_some() + || self.bottom_left.is_some() + } + + /// Verifies if borders has right line set on the frame. + pub const fn has_right(&self) -> bool { + self.right.is_some() + || self.right_intersection.is_some() + || self.top_right.is_some() + || self.bottom_right.is_some() + } + + /// Verifies if borders has top line set on the frame. + pub const fn has_top(&self) -> bool { + self.top.is_some() + || self.top_intersection.is_some() + || self.top_left.is_some() + || self.top_right.is_some() + } + + /// Verifies if borders has bottom line set on the frame. + pub const fn has_bottom(&self) -> bool { + self.bottom.is_some() + || self.bottom_intersection.is_some() + || self.bottom_left.is_some() + || self.bottom_right.is_some() + } + + /// Verifies if borders has horizontal lines set. + pub const fn has_horizontal(&self) -> bool { + self.horizontal.is_some() + || self.left_intersection.is_some() + || self.right_intersection.is_some() + || self.intersection.is_some() + } + + /// Verifies if borders has vertical lines set. + pub const fn has_vertical(&self) -> bool { + self.intersection.is_some() + || self.vertical.is_some() + || self.top_intersection.is_some() + || self.bottom_intersection.is_some() + } +} diff --git a/vendor/papergrid/src/config/compact/mod.rs b/vendor/papergrid/src/config/compact/mod.rs new file mode 100644 index 000000000..099fa44d9 --- /dev/null +++ b/vendor/papergrid/src/config/compact/mod.rs @@ -0,0 +1,141 @@ +//! A module which contains configuration of a [`CompactGrid`] which is responsible for grid configuration. +//! +//! [`CompactGrid`]: crate::grid::compact::CompactGrid + +use crate::color::StaticColor; + +use crate::config::{AlignmentHorizontal, Borders, Indent, Line, Sides}; + +/// This structure represents a settings of a grid. +/// +/// grid: crate::Grid. +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +pub struct CompactConfig { + borders: Borders<char>, + horizontal_line1: Option<Line<char>>, + border_colors: Borders<StaticColor>, + margin: Sides<Indent>, + margin_color: Sides<StaticColor>, + padding: Sides<Indent>, + padding_color: Sides<StaticColor>, + halignment: AlignmentHorizontal, +} + +impl Default for CompactConfig { + fn default() -> Self { + Self::empty() + } +} + +impl CompactConfig { + /// Returns an standard config. + pub const fn empty() -> Self { + Self { + halignment: AlignmentHorizontal::Left, + horizontal_line1: None, + borders: Borders::empty(), + border_colors: Borders::empty(), + margin: Sides::filled(Indent::zero()), + margin_color: Sides::filled(StaticColor::new("", "")), + padding: Sides::new( + Indent::spaced(1), + Indent::spaced(1), + Indent::zero(), + Indent::zero(), + ), + padding_color: Sides::filled(StaticColor::new("", "")), + } + } + + /// Set grid margin. + pub const fn set_margin(mut self, margin: Sides<Indent>) -> Self { + self.margin = margin; + self + } + + /// Returns a grid margin. + pub const fn get_margin(&self) -> &Sides<Indent> { + &self.margin + } + + /// Set the [`Borders`] value as correct one. + pub const fn set_borders(mut self, borders: Borders<char>) -> Self { + self.borders = borders; + self + } + + /// Set the first horizontal line. + /// + /// It ignores the [`Borders`] horizontal value if set for 1st row. + pub const fn set_first_horizontal_line(mut self, line: Line<char>) -> Self { + self.horizontal_line1 = Some(line); + self + } + + /// Set the first horizontal line. + /// + /// It ignores the [`Borders`] horizontal value if set for 1st row. + pub const fn get_first_horizontal_line(&self) -> Option<Line<char>> { + self.horizontal_line1 + } + + /// Returns a current [`Borders`] structure. + pub const fn get_borders(&self) -> &Borders<char> { + &self.borders + } + + /// Returns a current [`Borders`] structure. + pub const fn get_borders_color(&self) -> &Borders<StaticColor> { + &self.border_colors + } + + /// Set a padding to a given cells. + pub const fn set_padding(mut self, padding: Sides<Indent>) -> Self { + self.padding = padding; + self + } + + /// Get a padding for a given. + pub const fn get_padding(&self) -> &Sides<Indent> { + &self.padding + } + + /// Set a horizontal alignment. + pub const fn set_alignment_horizontal(mut self, alignment: AlignmentHorizontal) -> Self { + self.halignment = alignment; + self + } + + /// Get a alignment horizontal. + pub const fn get_alignment_horizontal(&self) -> AlignmentHorizontal { + self.halignment + } + + /// Sets colors of border carcass on the grid. + pub const fn set_borders_color(mut self, borders: Borders<StaticColor>) -> Self { + self.border_colors = borders; + self + } + + /// Set colors for a margin. + pub const fn set_margin_color(mut self, color: Sides<StaticColor>) -> Self { + self.margin_color = color; + self + } + + /// Returns a margin color. + pub const fn get_margin_color(&self) -> Sides<StaticColor> { + self.margin_color + } + + /// Set a padding to a given cells. + pub const fn set_padding_color(mut self, color: Sides<StaticColor>) -> Self { + self.padding_color = color; + self + } + + /// Set a padding to a given cells. + pub const fn get_padding_color(&self) -> Sides<StaticColor> { + self.padding_color + } +} diff --git a/vendor/papergrid/src/config/entity.rs b/vendor/papergrid/src/config/entity.rs new file mode 100644 index 000000000..0a2d3ba9b --- /dev/null +++ b/vendor/papergrid/src/config/entity.rs @@ -0,0 +1,120 @@ +use super::Position; + +/// Entity a structure which represent a set of cells. +/// +/// For example such table: +/// +/// ```text +/// ┌───┬───┐ +/// │ 0 │ 1 │ +/// ├───┼───┤ +/// │ 1 │ 2 │ +/// └───┴───┘ +/// ``` +/// +/// - has 4 cells. +/// Which indexes are (0, 0), (0, 1), (1, 0), (1, 1). +/// +/// - has 2 rows. +/// Which indexes are 0, 1. +/// +/// - has 2 column. +/// Which indexes are 0, 1. +/// +/// In [`Entity`] terms, all cells on the grid we call `Global`. +#[derive(PartialEq, Eq, Debug, Hash, Clone, Copy)] +pub enum Entity { + /// All cells on the grid. + Global, + /// All cells in a column on the grid. + Column(usize), + /// All cells in a row on the grid. + Row(usize), + /// A particular cell (row, column) on the grid. + Cell(usize, usize), +} + +impl Entity { + /// Iterate over cells which are covered via the [`Entity`]. + pub fn iter(&self, count_rows: usize, count_cols: usize) -> EntityIterator { + EntityIterator { + entity: *self, + count_rows, + count_cols, + i: 0, + j: 0, + } + } +} + +impl From<Position> for Entity { + fn from((row, col): Position) -> Self { + Self::Cell(row, col) + } +} + +/// An iterator over cells. +/// +/// Produced from [`Entity::iter`]. +#[derive(Debug, Clone)] +pub struct EntityIterator { + entity: Entity, + count_rows: usize, + count_cols: usize, + i: usize, + j: usize, +} + +impl Iterator for EntityIterator { + type Item = Position; + + fn next(&mut self) -> Option<Self::Item> { + if self.count_rows == 0 || self.count_cols == 0 { + return None; + } + + match self.entity { + Entity::Cell(row, col) => { + self.count_cols = 0; + self.count_rows = 0; + + Some((row, col)) + } + Entity::Column(col) => { + if self.i >= self.count_rows { + return None; + } + + let i = self.i; + self.i += 1; + + Some((i, col)) + } + Entity::Row(row) => { + if self.j >= self.count_cols { + return None; + } + + let j = self.j; + self.j += 1; + + Some((row, j)) + } + Entity::Global => { + if self.j >= self.count_cols { + self.j = 0; + self.i += 1; + + if self.i >= self.count_rows { + return None; + } + } + + let j = self.j; + self.j += 1; + + Some((self.i, j)) + } + } + } +} diff --git a/vendor/papergrid/src/config/indent.rs b/vendor/papergrid/src/config/indent.rs new file mode 100644 index 000000000..4d987724c --- /dev/null +++ b/vendor/papergrid/src/config/indent.rs @@ -0,0 +1,31 @@ +/// Indent represent a filled space. +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +pub struct Indent { + /// A fill character. + pub fill: char, + /// A number of repeats of a fill character. + pub size: usize, +} + +impl Indent { + /// Creates a new Indent structure. + pub const fn new(size: usize, fill: char) -> Self { + Self { fill, size } + } + + /// Creates a new Indent structure with space (`' '`) as a fill character. + pub const fn spaced(size: usize) -> Self { + Self { size, fill: ' ' } + } + + /// Creates a new Indent structure with space (`' '`) as a fill character. + pub const fn zero() -> Self { + Self::new(0, ' ') + } +} + +impl Default for Indent { + fn default() -> Self { + Self { size: 0, fill: ' ' } + } +} diff --git a/vendor/papergrid/src/config/line.rs b/vendor/papergrid/src/config/line.rs new file mode 100644 index 000000000..c3089e1c4 --- /dev/null +++ b/vendor/papergrid/src/config/line.rs @@ -0,0 +1,42 @@ +/// A line data structure. +#[derive(Debug, Clone, Copy, Eq, PartialEq, PartialOrd, Ord)] +pub struct Line<T> { + /// A horizontal/vertical character. + pub main: T, + /// A horizontal/vertical intersection. + pub intersection: Option<T>, + /// A horizontal left / vertical top intersection. + pub connect1: Option<T>, + /// A horizontal right / vertical bottom intersection. + pub connect2: Option<T>, +} + +impl<T> Line<T> { + /// Creates a new line. + pub const fn new( + main: T, + intersection: Option<T>, + connect1: Option<T>, + connect2: Option<T>, + ) -> Self { + Self { + main, + intersection, + connect1, + connect2, + } + } + + /// Creates a new line. + pub const fn filled(val: T) -> Self + where + T: Copy, + { + Self { + main: val, + intersection: Some(val), + connect1: Some(val), + connect2: Some(val), + } + } +} diff --git a/vendor/papergrid/src/config/mod.rs b/vendor/papergrid/src/config/mod.rs new file mode 100644 index 000000000..bba0b458e --- /dev/null +++ b/vendor/papergrid/src/config/mod.rs @@ -0,0 +1,23 @@ +//! A module which contains a general settings which might be used in other grid implementations. + +mod alignment; +mod border; +mod borders; +mod entity; +mod indent; +mod line; +mod position; +mod sides; + +pub mod compact; +#[cfg(feature = "std")] +pub mod spanned; + +pub use alignment::{AlignmentHorizontal, AlignmentVertical}; +pub use border::Border; +pub use borders::Borders; +pub use entity::{Entity, EntityIterator}; +pub use indent::Indent; +pub use line::Line; +pub use position::Position; +pub use sides::Sides; diff --git a/vendor/papergrid/src/config/position.rs b/vendor/papergrid/src/config/position.rs new file mode 100644 index 000000000..3996453cc --- /dev/null +++ b/vendor/papergrid/src/config/position.rs @@ -0,0 +1,13 @@ +/// Position is a (row, col) position on a Grid. +/// +/// For example such table has 4 cells. +/// Which indexes are (0, 0), (0, 1), (1, 0), (1, 1). +/// +/// ```text +/// ┌───┬───┐ +/// │ 0 │ 1 │ +/// ├───┼───┤ +/// │ 1 │ 2 │ +/// └───┴───┘ +/// ``` +pub type Position = (usize, usize); diff --git a/vendor/papergrid/src/config/sides.rs b/vendor/papergrid/src/config/sides.rs new file mode 100644 index 000000000..7aa0fb498 --- /dev/null +++ b/vendor/papergrid/src/config/sides.rs @@ -0,0 +1,37 @@ +/// A structure which represents 4 box sides. +#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +pub struct Sides<T> { + /// Top side. + pub top: T, + /// Bottom side. + pub bottom: T, + /// Left side. + pub left: T, + /// Right side. + pub right: T, +} + +impl<T> Sides<T> { + /// Creates a new object. + pub const fn new(left: T, right: T, top: T, bottom: T) -> Self { + Self { + top, + bottom, + left, + right, + } + } + + /// Creates a new object. + pub const fn filled(value: T) -> Self + where + T: Copy, + { + Self { + top: value, + bottom: value, + left: value, + right: value, + } + } +} diff --git a/vendor/papergrid/src/config/spanned/borders_config.rs b/vendor/papergrid/src/config/spanned/borders_config.rs new file mode 100644 index 000000000..fe7729806 --- /dev/null +++ b/vendor/papergrid/src/config/spanned/borders_config.rs @@ -0,0 +1,486 @@ +use std::collections::{HashMap, HashSet}; + +use crate::config::{Border, Borders, Position}; + +#[derive(Debug, Default, Clone, PartialEq, Eq)] +pub(crate) struct BordersConfig<T> { + global: Option<T>, + borders: Borders<T>, + cells: BordersMap<T>, + horizontals: HashMap<usize, HorizontalLine<T>>, + verticals: HashMap<usize, VerticalLine<T>>, + layout: BordersLayout, +} + +impl<T: std::fmt::Debug> BordersConfig<T> { + pub(crate) fn insert_border(&mut self, pos: Position, border: Border<T>) { + if let Some(c) = border.top { + self.cells.horizontal.insert(pos, c); + self.layout.horizontals.insert(pos.0); + } + + if let Some(c) = border.bottom { + self.cells.horizontal.insert((pos.0 + 1, pos.1), c); + self.layout.horizontals.insert(pos.0 + 1); + } + + if let Some(c) = border.left { + self.cells.vertical.insert(pos, c); + self.layout.verticals.insert(pos.1); + } + + if let Some(c) = border.right { + self.cells.vertical.insert((pos.0, pos.1 + 1), c); + self.layout.verticals.insert(pos.1 + 1); + } + + if let Some(c) = border.left_top_corner { + self.cells.intersection.insert((pos.0, pos.1), c); + self.layout.horizontals.insert(pos.0); + self.layout.verticals.insert(pos.1); + } + + if let Some(c) = border.right_top_corner { + self.cells.intersection.insert((pos.0, pos.1 + 1), c); + self.layout.horizontals.insert(pos.0); + self.layout.verticals.insert(pos.1 + 1); + } + + if let Some(c) = border.left_bottom_corner { + self.cells.intersection.insert((pos.0 + 1, pos.1), c); + self.layout.horizontals.insert(pos.0 + 1); + self.layout.verticals.insert(pos.1); + } + + if let Some(c) = border.right_bottom_corner { + self.cells.intersection.insert((pos.0 + 1, pos.1 + 1), c); + self.layout.horizontals.insert(pos.0 + 1); + self.layout.verticals.insert(pos.1 + 1); + } + } + + pub(crate) fn remove_border(&mut self, pos: Position, shape: (usize, usize)) { + let (count_rows, count_cols) = shape; + + self.cells.horizontal.remove(&pos); + self.cells.horizontal.remove(&(pos.0 + 1, pos.1)); + self.cells.vertical.remove(&pos); + self.cells.vertical.remove(&(pos.0, pos.1 + 1)); + self.cells.intersection.remove(&pos); + self.cells.intersection.remove(&(pos.0 + 1, pos.1)); + self.cells.intersection.remove(&(pos.0, pos.1 + 1)); + self.cells.intersection.remove(&(pos.0 + 1, pos.1 + 1)); + + // clean up the layout. + + if !self.check_is_horizontal_set(pos.0, count_rows) { + self.layout.horizontals.remove(&pos.0); + } + + if !self.check_is_horizontal_set(pos.0 + 1, count_rows) { + self.layout.horizontals.remove(&(pos.0 + 1)); + } + + if !self.check_is_vertical_set(pos.1, count_cols) { + self.layout.verticals.remove(&pos.1); + } + + if !self.check_is_vertical_set(pos.1 + 1, count_cols) { + self.layout.verticals.remove(&(pos.1 + 1)); + } + } + + pub(crate) fn get_border(&self, pos: Position, shape: (usize, usize)) -> Border<&T> { + Border { + top: self.get_horizontal(pos, shape.0), + bottom: self.get_horizontal((pos.0 + 1, pos.1), shape.0), + left: self.get_vertical(pos, shape.1), + left_top_corner: self.get_intersection(pos, shape), + left_bottom_corner: self.get_intersection((pos.0 + 1, pos.1), shape), + right: self.get_vertical((pos.0, pos.1 + 1), shape.1), + right_top_corner: self.get_intersection((pos.0, pos.1 + 1), shape), + right_bottom_corner: self.get_intersection((pos.0 + 1, pos.1 + 1), shape), + } + } + + pub(crate) fn insert_horizontal_line(&mut self, row: usize, line: HorizontalLine<T>) { + if line.left.is_some() { + self.layout.left = true; + } + + // todo: when we delete lines these are still left set; so has_horizontal/vertical return true in some cases; + // it shall be fixed, but maybe we can improve the logic as it got a bit complicated. + if line.right.is_some() { + self.layout.right = true; + } + + if line.intersection.is_some() { + self.layout.inner_verticals = true; + } + + self.horizontals.insert(row, line); + self.layout.horizontals.insert(row); + } + + pub(crate) fn get_horizontal_line(&self, row: usize) -> Option<&HorizontalLine<T>> { + self.horizontals.get(&row) + } + + pub(crate) fn remove_horizontal_line(&mut self, row: usize, count_rows: usize) { + self.horizontals.remove(&row); + self.layout.horizontals.remove(&row); + + if self.has_horizontal(row, count_rows) { + self.layout.horizontals.insert(row); + } + } + + pub(crate) fn insert_vertical_line(&mut self, row: usize, line: VerticalLine<T>) { + if line.top.is_some() { + self.layout.top = true; + } + + if line.bottom.is_some() { + self.layout.bottom = true; + } + + self.verticals.insert(row, line); + self.layout.verticals.insert(row); + } + + pub(crate) fn get_vertical_line(&self, row: usize) -> Option<&VerticalLine<T>> { + self.verticals.get(&row) + } + + pub(crate) fn remove_vertical_line(&mut self, col: usize, count_columns: usize) { + self.verticals.remove(&col); + self.layout.verticals.remove(&col); + + if self.has_vertical(col, count_columns) { + self.layout.verticals.insert(col); + } + } + + pub(crate) fn set_borders(&mut self, borders: Borders<T>) { + self.borders = borders; + } + + pub(crate) fn get_borders(&self) -> &Borders<T> { + &self.borders + } + + pub(crate) fn get_global(&self) -> Option<&T> { + self.global.as_ref() + } + + pub(crate) fn set_global(&mut self, value: T) { + self.global = Some(value); + } + + pub(crate) fn get_vertical(&self, pos: Position, count_cols: usize) -> Option<&T> { + self.cells + .vertical + .get(&pos) + .or_else(|| self.verticals.get(&pos.1).and_then(|l| l.main.as_ref())) + .or({ + if pos.1 == count_cols { + self.borders.right.as_ref() + } else if pos.1 == 0 { + self.borders.left.as_ref() + } else { + self.borders.vertical.as_ref() + } + }) + .or(self.global.as_ref()) + } + + pub(crate) fn get_horizontal(&self, pos: Position, count_rows: usize) -> Option<&T> { + self.cells + .horizontal + .get(&pos) + .or_else(|| self.horizontals.get(&pos.0).and_then(|l| l.main.as_ref())) + .or({ + if pos.0 == 0 { + self.borders.top.as_ref() + } else if pos.0 == count_rows { + self.borders.bottom.as_ref() + } else { + self.borders.horizontal.as_ref() + } + }) + .or(self.global.as_ref()) + } + + pub(crate) fn get_intersection( + &self, + pos: Position, + (count_rows, count_cols): (usize, usize), + ) -> Option<&T> { + let use_top = pos.0 == 0; + let use_bottom = pos.0 == count_rows; + let use_left = pos.1 == 0; + let use_right = pos.1 == count_cols; + + if let Some(c) = self.cells.intersection.get(&pos) { + return Some(c); + } + + let hl_c = self.horizontals.get(&pos.0).and_then(|l| { + if use_left && l.left.is_some() { + l.left.as_ref() + } else if use_right && l.right.is_some() { + l.right.as_ref() + } else if !use_right && !use_left && l.intersection.is_some() { + l.intersection.as_ref() + } else { + None + } + }); + + if let Some(c) = hl_c { + return Some(c); + } + + let vl_c = self.verticals.get(&pos.1).and_then(|l| { + if use_top && l.top.is_some() { + l.top.as_ref() + } else if use_bottom && l.bottom.is_some() { + l.bottom.as_ref() + } else if !use_top && !use_bottom && l.intersection.is_some() { + l.intersection.as_ref() + } else { + None + } + }); + + if let Some(c) = vl_c { + return Some(c); + } + + let borders_c = { + if use_top && use_left { + self.borders.top_left.as_ref() + } else if use_top && use_right { + self.borders.top_right.as_ref() + } else if use_bottom && use_left { + self.borders.bottom_left.as_ref() + } else if use_bottom && use_right { + self.borders.bottom_right.as_ref() + } else if use_top { + self.borders.top_intersection.as_ref() + } else if use_bottom { + self.borders.bottom_intersection.as_ref() + } else if use_left { + self.borders.left_intersection.as_ref() + } else if use_right { + self.borders.right_intersection.as_ref() + } else { + self.borders.intersection.as_ref() + } + }; + + if let Some(c) = borders_c { + return Some(c); + } + + self.global.as_ref() + } + + pub(crate) fn has_horizontal(&self, row: usize, count_rows: usize) -> bool { + self.global.is_some() + || (row == 0 && self.borders.has_top()) + || (row == count_rows && self.borders.has_bottom()) + || (row > 0 && row < count_rows && self.borders.has_horizontal()) + || self.is_horizontal_set(row, count_rows) + } + + pub(crate) fn has_vertical(&self, col: usize, count_cols: usize) -> bool { + self.global.is_some() + || (col == 0 && self.borders.has_left()) + || (col == count_cols && self.borders.has_right()) + || (col > 0 && col < count_cols && self.borders.has_vertical()) + || self.is_vertical_set(col, count_cols) + } + + fn is_horizontal_set(&self, row: usize, count_rows: usize) -> bool { + (row == 0 && self.layout.top) + || (row == count_rows && self.layout.bottom) + || (row > 0 && row < count_rows && self.layout.inner_horizontals) + || self.layout.horizontals.contains(&row) + } + + fn is_vertical_set(&self, col: usize, count_cols: usize) -> bool { + (col == 0 && self.layout.left) + || (col == count_cols && self.layout.right) + || (col > 0 && col < count_cols && self.layout.inner_verticals) + || self.layout.verticals.contains(&col) + } + + fn check_is_horizontal_set(&self, row: usize, count_rows: usize) -> bool { + (row == 0 && self.layout.top) + || (row == count_rows && self.layout.bottom) + || (row > 0 && row < count_rows && self.layout.inner_horizontals) + || self.cells.horizontal.keys().any(|&p| p.0 == row) + || self.cells.intersection.keys().any(|&p| p.0 == row) + } + + fn check_is_vertical_set(&self, col: usize, count_cols: usize) -> bool { + (col == 0 && self.layout.left) + || (col == count_cols && self.layout.right) + || (col > 0 && col < count_cols && self.layout.inner_verticals) + || self.cells.vertical.keys().any(|&p| p.1 == col) + || self.cells.intersection.keys().any(|&p| p.1 == col) + } +} + +#[derive(Debug, Clone, Default, PartialEq, Eq)] +pub(crate) struct BordersMap<T> { + vertical: HashMap<Position, T>, + horizontal: HashMap<Position, T>, + intersection: HashMap<Position, T>, +} + +#[derive(Debug, Clone, Default, PartialEq, Eq)] +pub(crate) struct BordersLayout { + left: bool, + right: bool, + top: bool, + bottom: bool, + inner_verticals: bool, + inner_horizontals: bool, + horizontals: HashSet<usize>, + verticals: HashSet<usize>, +} + +/// A structure for a custom horizontal line. +#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord)] +pub struct HorizontalLine<T> { + /// Line character. + pub main: Option<T>, + /// Line intersection character. + pub intersection: Option<T>, + /// Left intersection character. + pub left: Option<T>, + /// Right intersection character. + pub right: Option<T>, +} + +impl<T> HorizontalLine<T> { + /// Verifies if the line has any setting set. + pub const fn is_empty(&self) -> bool { + self.main.is_none() + && self.intersection.is_none() + && self.left.is_none() + && self.right.is_none() + } +} + +/// A structure for a vertical line. +#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord)] +pub struct VerticalLine<T> { + /// Line character. + pub main: Option<T>, + /// Line intersection character. + pub intersection: Option<T>, + /// Left intersection character. + pub top: Option<T>, + /// Right intersection character. + pub bottom: Option<T>, +} + +impl<T> VerticalLine<T> { + /// Verifies if the line has any setting set. + pub const fn is_empty(&self) -> bool { + self.main.is_none() + && self.intersection.is_none() + && self.top.is_none() + && self.bottom.is_none() + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_insert_border() { + let mut borders = BordersConfig::<char>::default(); + borders.insert_border((0, 0), Border::filled('x')); + + assert_eq!(borders.get_border((0, 0), (10, 10)), Border::filled(&'x')); + assert_eq!(borders.get_border((0, 0), (0, 0)), Border::filled(&'x')); + + assert!(borders.is_horizontal_set(0, 10)); + assert!(borders.is_horizontal_set(1, 10)); + assert!(!borders.is_horizontal_set(2, 10)); + assert!(borders.is_vertical_set(0, 10)); + assert!(borders.is_vertical_set(1, 10)); + assert!(!borders.is_vertical_set(2, 10)); + + assert!(borders.is_horizontal_set(0, 0)); + assert!(borders.is_horizontal_set(1, 0)); + assert!(!borders.is_horizontal_set(2, 0)); + assert!(borders.is_vertical_set(0, 0)); + assert!(borders.is_vertical_set(1, 0)); + assert!(!borders.is_vertical_set(2, 0)); + } + + #[test] + fn test_insert_border_override() { + let mut borders = BordersConfig::<char>::default(); + borders.insert_border((0, 0), Border::filled('x')); + borders.insert_border((1, 0), Border::filled('y')); + borders.insert_border((0, 1), Border::filled('w')); + borders.insert_border((1, 1), Border::filled('q')); + + assert_eq!( + borders.get_border((0, 0), (10, 10)).copied(), + Border::full('x', 'y', 'x', 'w', 'x', 'w', 'y', 'q') + ); + assert_eq!( + borders.get_border((0, 1), (10, 10)).copied(), + Border::full('w', 'q', 'w', 'w', 'w', 'w', 'q', 'q') + ); + assert_eq!( + borders.get_border((1, 0), (10, 10)).copied(), + Border::full('y', 'y', 'y', 'q', 'y', 'q', 'y', 'q') + ); + assert_eq!( + borders.get_border((1, 1), (10, 10)).copied(), + Border::filled('q') + ); + + assert!(borders.is_horizontal_set(0, 10)); + assert!(borders.is_horizontal_set(1, 10)); + assert!(borders.is_horizontal_set(2, 10)); + assert!(!borders.is_horizontal_set(3, 10)); + assert!(borders.is_vertical_set(0, 10)); + assert!(borders.is_vertical_set(1, 10)); + assert!(borders.is_vertical_set(2, 10)); + assert!(!borders.is_vertical_set(3, 10)); + } + + #[test] + fn test_set_global() { + let mut borders = BordersConfig::<char>::default(); + borders.insert_border((0, 0), Border::filled('x')); + borders.set_global('l'); + + assert_eq!(borders.get_border((0, 0), (10, 10)), Border::filled(&'x')); + assert_eq!(borders.get_border((2, 0), (10, 10)), Border::filled(&'l')); + + assert!(borders.is_horizontal_set(0, 10)); + assert!(borders.is_horizontal_set(1, 10)); + assert!(!borders.is_horizontal_set(2, 10)); + assert!(borders.is_vertical_set(0, 10)); + assert!(borders.is_vertical_set(1, 10)); + assert!(!borders.is_vertical_set(2, 10)); + + assert!(borders.is_horizontal_set(0, 0)); + assert!(borders.is_horizontal_set(1, 0)); + assert!(!borders.is_horizontal_set(2, 0)); + assert!(borders.is_vertical_set(0, 0)); + assert!(borders.is_vertical_set(1, 0)); + assert!(!borders.is_vertical_set(2, 0)); + } +} diff --git a/vendor/papergrid/src/config/spanned/entity_map.rs b/vendor/papergrid/src/config/spanned/entity_map.rs new file mode 100644 index 000000000..472ea5fb2 --- /dev/null +++ b/vendor/papergrid/src/config/spanned/entity_map.rs @@ -0,0 +1,108 @@ +use fnv::FnvHashMap; + +use crate::config::{Entity, Position}; + +/// A structure to keep information for [`Entity`] as a key. +#[derive(Debug, Default, Clone, PartialEq, Eq)] +pub struct EntityMap<T> { + // we have a global type to allocate in on stack. + // because most of the time no changes are made to the [`EntityMap`]. + global: T, + columns: FnvHashMap<usize, T>, + rows: FnvHashMap<usize, T>, + cells: FnvHashMap<Position, T>, +} + +impl<T> EntityMap<T> { + /// Creates an empty [`EntityMap`]. + pub fn new(global: T) -> Self { + Self { + global, + rows: FnvHashMap::default(), + columns: FnvHashMap::default(), + cells: FnvHashMap::default(), + } + } + + /// Get a value for an [`Entity`]. + pub fn get(&self, entity: Entity) -> &T { + if self.rows.is_empty() && self.columns.is_empty() && self.cells.is_empty() { + return &self.global; + } + + match entity { + Entity::Column(col) => self.columns.get(&col).unwrap_or(&self.global), + Entity::Row(row) => self.rows.get(&row).unwrap_or(&self.global), + Entity::Cell(row, col) => { + // todo: optimize; + // + // Cause we can change rows/columns/cells separately we need to check them separately. + // But we often doing this checks in `Grid::fmt` and I believe if we could optimize it it could be beneficial. + // + // Haven't found a solution for that yet. + // + // I was wondering if there is a hash function like. + // Apparently it doesn't make sense cause we will reset columns/rows on cell insert which is not what we want. + // + // ``` + // hash(column, row) == hash(column) == hash(row) + // ``` + // + // ref: https://opendsa-server.cs.vt.edu/ODSA/Books/Everything/html/Sparse.html + // ref: https://users.rust-lang.org/t/make-hash-return-same-value-whather-the-order-of-element-of-a-tuple/69932/13 + + self.cells + .get(&(row, col)) + .or_else(|| self.columns.get(&col)) + .or_else(|| self.rows.get(&row)) + .unwrap_or(&self.global) + } + Entity::Global => &self.global, + } + } + + /// Removes a value for an [`Entity`]. + pub fn remove(&mut self, entity: Entity) { + match entity { + Entity::Global => { + self.cells.clear(); + self.rows.clear(); + self.columns.clear(); + } + Entity::Column(col) => self.cells.retain(|&(_, c), _| c != col), + Entity::Row(row) => self.cells.retain(|&(r, _), _| r != row), + Entity::Cell(row, col) => { + self.cells.remove(&(row, col)); + } + } + } +} + +impl<T: Clone> EntityMap<T> { + /// Set a value for an [`Entity`]. + pub fn insert(&mut self, entity: Entity, value: T) { + match entity { + Entity::Column(col) => { + for &row in self.rows.keys() { + self.cells.insert((row, col), value.clone()); + } + + self.columns.insert(col, value); + } + Entity::Row(row) => { + for &col in self.columns.keys() { + self.cells.insert((row, col), value.clone()); + } + + self.rows.insert(row, value); + } + Entity::Cell(row, col) => { + self.cells.insert((row, col), value); + } + Entity::Global => { + self.remove(Entity::Global); + self.global = value + } + } + } +} diff --git a/vendor/papergrid/src/config/spanned/formatting.rs b/vendor/papergrid/src/config/spanned/formatting.rs new file mode 100644 index 000000000..0865f6c5b --- /dev/null +++ b/vendor/papergrid/src/config/spanned/formatting.rs @@ -0,0 +1,21 @@ +/// Formatting represent a logic of formatting of a cell. +#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +pub struct Formatting { + /// An setting to allow horizontal trim. + pub horizontal_trim: bool, + /// An setting to allow vertical trim. + pub vertical_trim: bool, + /// An setting to allow alignment per line. + pub allow_lines_alignment: bool, +} + +impl Formatting { + /// Creates a new [`Formatting`] structure. + pub fn new(horizontal_trim: bool, vertical_trim: bool, allow_lines_alignment: bool) -> Self { + Self { + horizontal_trim, + vertical_trim, + allow_lines_alignment, + } + } +} diff --git a/vendor/papergrid/src/config/spanned/mod.rs b/vendor/papergrid/src/config/spanned/mod.rs new file mode 100644 index 000000000..a7f71e11c --- /dev/null +++ b/vendor/papergrid/src/config/spanned/mod.rs @@ -0,0 +1,887 @@ +//! A module which contains configuration options for a [`Grid`]. +//! +//! [`Grid`]: crate::grid::iterable::Grid + +mod borders_config; +mod entity_map; +mod formatting; +mod offset; + +use std::collections::HashMap; + +use crate::color::{AnsiColor, StaticColor}; +use crate::config::compact::CompactConfig; +use crate::config::{ + AlignmentHorizontal, AlignmentVertical, Border, Borders, Entity, Indent, Position, Sides, +}; +use borders_config::BordersConfig; + +pub use self::{entity_map::EntityMap, formatting::Formatting, offset::Offset}; + +/// This structure represents a settings of a grid. +/// +/// grid: crate::Grid. +#[derive(Debug, PartialEq, Eq, Clone)] +pub struct SpannedConfig { + margin: Sides<ColoredMarginIndent>, + padding: EntityMap<Sides<ColoredIndent>>, + alignment_h: EntityMap<AlignmentHorizontal>, + alignment_v: EntityMap<AlignmentVertical>, + formatting: EntityMap<Formatting>, + span_columns: HashMap<Position, usize>, + span_rows: HashMap<Position, usize>, + borders: BordersConfig<char>, + borders_colors: BordersConfig<AnsiColor<'static>>, + borders_missing_char: char, + horizontal_chars: HashMap<Position, HashMap<Offset, char>>, + horizontal_colors: HashMap<Position, HashMap<Offset, AnsiColor<'static>>>, + vertical_chars: HashMap<Position, HashMap<Offset, char>>, + vertical_colors: HashMap<Position, HashMap<Offset, AnsiColor<'static>>>, + justification: EntityMap<char>, + justification_color: EntityMap<Option<AnsiColor<'static>>>, +} + +impl Default for SpannedConfig { + fn default() -> Self { + Self { + margin: Sides::default(), + padding: EntityMap::default(), + formatting: EntityMap::default(), + alignment_h: EntityMap::new(AlignmentHorizontal::Left), + alignment_v: EntityMap::new(AlignmentVertical::Top), + span_columns: HashMap::default(), + span_rows: HashMap::default(), + borders: BordersConfig::default(), + borders_colors: BordersConfig::default(), + borders_missing_char: ' ', + horizontal_chars: HashMap::default(), + horizontal_colors: HashMap::default(), + vertical_chars: HashMap::default(), + vertical_colors: HashMap::default(), + justification: EntityMap::new(' '), + justification_color: EntityMap::default(), + } + } +} + +impl SpannedConfig { + /// Set a margin of a grid. + pub fn set_margin(&mut self, margin: Sides<Indent>) { + self.margin.left.indent = margin.left; + self.margin.right.indent = margin.right; + self.margin.top.indent = margin.top; + self.margin.bottom.indent = margin.bottom; + } + + /// Set a color of margin of a grid. + pub fn set_margin_color(&mut self, margin: Sides<Option<AnsiColor<'static>>>) { + self.margin.left.color = margin.left; + self.margin.right.color = margin.right; + self.margin.top.color = margin.top; + self.margin.bottom.color = margin.bottom; + } + + /// Set an offset of margin of a grid. + pub fn set_margin_offset(&mut self, margin: Sides<Offset>) { + self.margin.left.offset = margin.left; + self.margin.right.offset = margin.right; + self.margin.top.offset = margin.top; + self.margin.bottom.offset = margin.bottom; + } + + /// Returns a margin value currently set. + pub fn get_margin(&self) -> Sides<Indent> { + Sides::new( + self.margin.left.indent, + self.margin.right.indent, + self.margin.top.indent, + self.margin.bottom.indent, + ) + } + + /// Returns a margin color value currently set. + pub fn get_margin_color(&self) -> Sides<Option<AnsiColor<'static>>> { + Sides::new( + self.margin.left.color.clone(), + self.margin.right.color.clone(), + self.margin.top.color.clone(), + self.margin.bottom.color.clone(), + ) + } + + /// Returns a margin offset value currently set. + pub fn get_margin_offset(&self) -> Sides<Offset> { + Sides::new( + self.margin.left.offset, + self.margin.right.offset, + self.margin.top.offset, + self.margin.bottom.offset, + ) + } + + /// Clears all theme changes. + /// And sets it to default. + pub fn clear_theme(&mut self) { + self.borders = BordersConfig::default(); + self.horizontal_chars.clear(); + self.vertical_chars.clear(); + self.horizontal_colors.clear(); + self.vertical_colors.clear(); + } + + /// Set the [`Borders`] value as correct one. + pub fn set_borders(&mut self, borders: Borders<char>) { + self.borders.set_borders(borders); + } + + /// Gets a global border value if set. + pub fn get_global_border(&self) -> Option<&char> { + self.borders.get_global() + } + + /// Set the all [`Borders`] values to a char. + pub fn set_global_border(&mut self, c: char) { + self.borders.set_global(c); + } + + /// Returns a current [`Borders`] structure. + pub fn get_borders(&self) -> &Borders<char> { + self.borders.get_borders() + } + + /// Set the border line by row index. + /// + /// Row `0` means the top row. + /// Row `grid.count_rows()` means the bottom row. + pub fn insert_horizontal_line(&mut self, line: usize, val: HorizontalLine) { + self.borders.insert_horizontal_line(line, val); + } + + /// Sets off the border line by row index if any were set + /// + /// Row `0` means the top row. + /// Row `grid.count_rows()` means the bottom row. + pub fn remove_horizontal_line(&mut self, line: usize, count_rows: usize) { + self.borders.remove_horizontal_line(line, count_rows); + } + + /// Gets a overridden vertical line. + /// + /// Row `0` means the left row. + /// Row `grid.count_columns()` means the right most row. + pub fn get_vertical_line(&self, line: usize) -> Option<&VerticalLine> { + self.borders.get_vertical_line(line) + } + + /// Set the border line by column index. + /// + /// Row `0` means the left row. + /// Row `grid.count_columns()` means the right most row. + pub fn insert_vertical_line(&mut self, line: usize, val: VerticalLine) { + self.borders.insert_vertical_line(line, val); + } + + /// Sets off the border line by column index if any were set + /// + /// Row `0` means the left row. + /// Row `grid.count_columns()` means the right most row. + pub fn remove_vertical_line(&mut self, line: usize, count_columns: usize) { + self.borders.remove_vertical_line(line, count_columns); + } + + /// Gets a overridden line. + /// + /// Row `0` means the top row. + /// Row `grid.count_rows()` means the bottom row. + pub fn get_horizontal_line(&self, line: usize) -> Option<&HorizontalLine> { + self.borders.get_horizontal_line(line) + } + + /// Override a character on a horizontal line. + /// + /// If borders are not set the char won't be used. + /// + /// It takes not cell position but line as row and column of a cell; + /// So its range is line <= count_rows && col < count_columns. + pub fn set_horizontal_char(&mut self, pos: Position, c: char, offset: Offset) { + let chars = self + .horizontal_chars + .entry(pos) + .or_insert_with(|| HashMap::with_capacity(1)); + + chars.insert(offset, c); + } + + /// Get a list of overridden chars in a horizontal border. + /// + /// It takes not cell position but line as row and column of a cell; + /// So its range is line <= count_rows && col < count_columns. + pub fn lookup_horizontal_char(&self, pos: Position, offset: usize, end: usize) -> Option<char> { + self.horizontal_chars + .get(&pos) + .and_then(|chars| { + chars.get(&Offset::Begin(offset)).or_else(|| { + if end > offset { + if end == 0 { + chars.get(&Offset::End(0)) + } else { + chars.get(&Offset::End(end - offset - 1)) + } + } else { + None + } + }) + }) + .copied() + } + + /// Checks if there any char in a horizontal border being overridden. + /// + /// It takes not cell position but line as row and column of a cell; + /// So its range is line <= count_rows && col < count_columns. + pub fn is_overridden_horizontal(&self, pos: Position) -> bool { + self.horizontal_chars.get(&pos).is_some() + } + + /// Removes a list of overridden chars in a horizontal border. + /// + /// It takes not cell position but line as row and column of a cell; + /// So its range is line <= count_rows && col < count_columns. + pub fn remove_overridden_horizontal(&mut self, pos: Position) { + self.horizontal_chars.remove(&pos); + } + + /// Override a vertical split line. + /// + /// If borders are not set the char won't be used. + /// + /// It takes not cell position but cell row and column of a line; + /// So its range is row < count_rows && col <= count_columns. + pub fn set_vertical_char(&mut self, pos: Position, c: char, offset: Offset) { + let chars = self + .vertical_chars + .entry(pos) + .or_insert_with(|| HashMap::with_capacity(1)); + + chars.insert(offset, c); + } + + /// Get a list of overridden chars in a horizontal border. + /// + /// It takes not cell position but cell row and column of a line; + /// So its range is row < count_rows && col <= count_columns. + pub fn lookup_vertical_char(&self, pos: Position, offset: usize, end: usize) -> Option<char> { + self.vertical_chars + .get(&pos) + .and_then(|chars| { + chars.get(&Offset::Begin(offset)).or_else(|| { + if end > offset { + if end == 0 { + chars.get(&Offset::End(0)) + } else { + chars.get(&Offset::End(end - offset - 1)) + } + } else { + None + } + }) + }) + .copied() + } + + /// Checks if there any char in a horizontal border being overridden. + /// + /// It takes not cell position but cell row and column of a line; + /// So its range is row < count_rows && col <= count_columns. + pub fn is_overridden_vertical(&self, pos: Position) -> bool { + self.vertical_chars.get(&pos).is_some() + } + + /// Removes a list of overridden chars in a horizontal border. + /// + /// It takes not cell position but cell row and column of a line; + /// So its range is row < count_rows && col <= count_columns. + pub fn remove_overridden_vertical(&mut self, pos: Position) { + self.vertical_chars.remove(&pos); + } + + /// Override a character color on a horizontal line. + pub fn set_horizontal_color(&mut self, pos: Position, c: AnsiColor<'static>, offset: Offset) { + let chars = self + .horizontal_colors + .entry(pos) + .or_insert_with(|| HashMap::with_capacity(1)); + + chars.insert(offset, c); + } + + /// Get a overridden color in a horizontal border. + pub fn lookup_horizontal_color( + &self, + pos: Position, + offset: usize, + end: usize, + ) -> Option<&AnsiColor<'static>> { + self.horizontal_colors.get(&pos).and_then(|chars| { + chars.get(&Offset::Begin(offset)).or_else(|| { + if end > offset { + if end == 0 { + chars.get(&Offset::End(0)) + } else { + chars.get(&Offset::End(end - offset - 1)) + } + } else { + None + } + }) + }) + } + + /// Override a character color on a vertical line. + pub fn set_vertical_color(&mut self, pos: Position, c: AnsiColor<'static>, offset: Offset) { + let chars = self + .vertical_colors + .entry(pos) + .or_insert_with(|| HashMap::with_capacity(1)); + + chars.insert(offset, c); + } + + /// Get a overridden color in a vertical border. + pub fn lookup_vertical_color( + &self, + pos: Position, + offset: usize, + end: usize, + ) -> Option<&AnsiColor<'static>> { + self.vertical_colors.get(&pos).and_then(|chars| { + chars.get(&Offset::Begin(offset)).or_else(|| { + if end > offset { + if end == 0 { + chars.get(&Offset::End(0)) + } else { + chars.get(&Offset::End(end - offset - 1)) + } + } else { + None + } + }) + }) + } + + /// Set a padding to a given cells. + pub fn set_padding(&mut self, entity: Entity, padding: Sides<Indent>) { + let mut pad = self.padding.get(entity).clone(); + pad.left.indent = padding.left; + pad.right.indent = padding.right; + pad.top.indent = padding.top; + pad.bottom.indent = padding.bottom; + + self.padding.insert(entity, pad); + } + + /// Set a padding to a given cells. + pub fn set_padding_color( + &mut self, + entity: Entity, + padding: Sides<Option<AnsiColor<'static>>>, + ) { + let mut pad = self.padding.get(entity).clone(); + pad.left.color = padding.left; + pad.right.color = padding.right; + pad.top.color = padding.top; + pad.bottom.color = padding.bottom; + + self.padding.insert(entity, pad); + } + + /// Get a padding for a given [Entity]. + pub fn get_padding(&self, entity: Entity) -> Sides<Indent> { + let pad = self.padding.get(entity); + Sides::new( + pad.left.indent, + pad.right.indent, + pad.top.indent, + pad.bottom.indent, + ) + } + + /// Get a padding color for a given [Entity]. + pub fn get_padding_color(&self, entity: Entity) -> Sides<Option<AnsiColor<'static>>> { + let pad = self.padding.get(entity); + Sides::new( + pad.left.color.clone(), + pad.right.color.clone(), + pad.top.color.clone(), + pad.bottom.color.clone(), + ) + } + + /// Set a formatting to a given cells. + pub fn set_formatting(&mut self, entity: Entity, formatting: Formatting) { + self.formatting.insert(entity, formatting); + } + + /// Get a formatting settings for a given [Entity]. + pub fn get_formatting(&self, entity: Entity) -> &Formatting { + self.formatting.get(entity) + } + + /// Set a vertical alignment to a given cells. + pub fn set_alignment_vertical(&mut self, entity: Entity, alignment: AlignmentVertical) { + self.alignment_v.insert(entity, alignment); + } + + /// Get a vertical alignment for a given [Entity]. + pub fn get_alignment_vertical(&self, entity: Entity) -> &AlignmentVertical { + self.alignment_v.get(entity) + } + + /// Set a horizontal alignment to a given cells. + pub fn set_alignment_horizontal(&mut self, entity: Entity, alignment: AlignmentHorizontal) { + self.alignment_h.insert(entity, alignment); + } + + /// Get a horizontal alignment for a given [Entity]. + pub fn get_alignment_horizontal(&self, entity: Entity) -> &AlignmentHorizontal { + self.alignment_h.get(entity) + } + + /// Set border set a border value to all cells in [`Entity`]. + pub fn set_border(&mut self, pos: Position, border: Border<char>) { + self.borders.insert_border(pos, border); + } + + /// Returns a border of a cell. + pub fn get_border(&self, pos: Position, shape: (usize, usize)) -> Border<char> { + self.borders.get_border(pos, shape).copied() + } + + /// Returns a border color of a cell. + pub fn get_border_color( + &self, + pos: Position, + shape: (usize, usize), + ) -> Border<&AnsiColor<'static>> { + self.borders_colors.get_border(pos, shape) + } + + /// Set a character which will be used in case any misconfiguration of borders. + /// + /// It will be usde for example when you set a left char for border frame and top but didn't set a top left corner. + pub fn set_borders_missing(&mut self, c: char) { + self.borders_missing_char = c; + } + + /// Get a character which will be used in case any misconfiguration of borders. + pub fn get_borders_missing(&self) -> char { + self.borders_missing_char + } + + /// Gets a color of all borders on the grid. + pub fn get_border_color_global(&self) -> Option<&AnsiColor<'static>> { + self.borders_colors.get_global() + } + + /// Sets a color of all borders on the grid. + pub fn set_border_color_global(&mut self, clr: AnsiColor<'static>) { + self.borders_colors = BordersConfig::default(); + self.borders_colors.set_global(clr); + } + + /// Gets colors of a borders carcass on the grid. + pub fn get_color_borders(&self) -> &Borders<AnsiColor<'static>> { + self.borders_colors.get_borders() + } + + /// Sets colors of border carcass on the grid. + pub fn set_borders_color(&mut self, clrs: Borders<AnsiColor<'static>>) { + self.borders_colors.set_borders(clrs); + } + + /// Sets a color of border of a cell on the grid. + pub fn set_border_color(&mut self, pos: Position, border: Border<AnsiColor<'static>>) { + self.borders_colors.insert_border(pos, border) + } + + /// Sets off all borders possible on the [`Entity`]. + /// + /// It doesn't changes globally set borders through [`SpannedConfig::set_borders`]. + // + // todo: would be great to remove a shape + pub fn remove_border(&mut self, pos: Position, shape: (usize, usize)) { + self.borders.remove_border(pos, shape); + } + + /// Gets a color of border of a cell on the grid. + // + // todo: would be great to remove a shape + pub fn remove_border_color(&mut self, pos: Position, shape: (usize, usize)) { + self.borders_colors.remove_border(pos, shape); + } + + /// Get a justification which will be used while expanding cells width/height. + pub fn get_justification(&self, entity: Entity) -> char { + *self.justification.get(entity) + } + + /// Get a justification color which will be used while expanding cells width/height. + /// + /// `None` means no color. + pub fn get_justification_color(&self, entity: Entity) -> Option<&AnsiColor<'static>> { + self.justification_color.get(entity).as_ref() + } + + /// Set a justification which will be used while expanding cells width/height. + pub fn set_justification(&mut self, entity: Entity, c: char) { + self.justification.insert(entity, c); + } + + /// Set a justification color which will be used while expanding cells width/height. + /// + /// `None` removes it. + pub fn set_justification_color(&mut self, entity: Entity, color: Option<AnsiColor<'static>>) { + self.justification_color.insert(entity, color); + } + + /// Get a span value of the cell, if any is set. + pub fn get_column_spans(&self) -> HashMap<Position, usize> { + self.span_columns.clone() + } + + /// Get a span value of the cell, if any is set. + pub fn get_row_spans(&self) -> HashMap<Position, usize> { + self.span_rows.clone() + } + + /// Get a span value of the cell, if any is set. + pub fn get_column_span(&self, pos: Position) -> Option<usize> { + self.span_columns.get(&pos).copied() + } + + /// Get a span value of the cell, if any is set. + pub fn get_row_span(&self, pos: Position) -> Option<usize> { + self.span_rows.get(&pos).copied() + } + + /// Removes column spans. + pub fn remove_column_spans(&mut self) { + self.span_columns.clear() + } + + /// Removes row spans. + pub fn remove_row_spans(&mut self) { + self.span_rows.clear() + } + + /// Set a column span to a given cells. + /// + /// BEWARE + /// + /// IT'S CALLER RESPONSIBILITY TO MAKE SURE + /// THAT THERE NO INTERSECTIONS IN PLACE AND THE SPAN VALUE IS CORRECT + pub fn set_column_span(&mut self, pos: Position, span: usize) { + set_cell_column_span(self, pos, span); + } + + /// Verifies if there's any spans set. + pub fn has_column_spans(&self) -> bool { + !self.span_columns.is_empty() + } + + /// Set a column span to a given cells. + /// + /// BEWARE + /// + /// IT'S CALLER RESPONSIBILITY TO MAKE SURE + /// THAT THERE NO INTERSECTIONS IN PLACE AND THE SPAN VALUE IS CORRECT + pub fn set_row_span(&mut self, pos: Position, span: usize) { + set_cell_row_span(self, pos, span); + } + + /// Verifies if there's any spans set. + pub fn has_row_spans(&self) -> bool { + !self.span_rows.is_empty() + } + + /// Gets an intersection character which would be rendered on the grid. + /// + /// grid: crate::Grid + pub fn get_intersection(&self, pos: Position, shape: (usize, usize)) -> Option<char> { + let c = self.borders.get_intersection(pos, shape); + if let Some(c) = c { + return Some(*c); + } + + if self.has_horizontal(pos.0, shape.0) && self.has_vertical(pos.1, shape.1) { + return Some(self.get_borders_missing()); + } + + None + } + + /// Gets a horizontal character which would be rendered on the grid. + /// + /// grid: crate::Grid + pub fn get_horizontal(&self, pos: Position, count_rows: usize) -> Option<char> { + let c = self.borders.get_horizontal(pos, count_rows); + if let Some(c) = c { + return Some(*c); + } + + if self.has_horizontal(pos.0, count_rows) { + return Some(self.get_borders_missing()); + } + + None + } + + /// Gets a vertical character which would be rendered on the grid. + /// + /// grid: crate::Grid + pub fn get_vertical(&self, pos: Position, count_columns: usize) -> Option<char> { + if let Some(c) = self.borders.get_vertical(pos, count_columns) { + return Some(*c); + } + + if self.has_vertical(pos.1, count_columns) { + return Some(self.get_borders_missing()); + } + + None + } + + /// Gets a color of a cell horizontal. + pub fn get_horizontal_color( + &self, + pos: Position, + count_rows: usize, + ) -> Option<&AnsiColor<'static>> { + self.borders_colors.get_horizontal(pos, count_rows) + } + + /// Gets a color of a cell vertical. + pub fn get_vertical_color( + &self, + pos: Position, + count_columns: usize, + ) -> Option<&AnsiColor<'static>> { + self.borders_colors.get_vertical(pos, count_columns) + } + + /// Gets a color of a cell vertical. + pub fn get_intersection_color( + &self, + pos: Position, + shape: (usize, usize), + ) -> Option<&AnsiColor<'static>> { + self.borders_colors.get_intersection(pos, shape) + } + + /// Checks if grid would have a horizontal border with the current configuration. + /// + /// grid: crate::Grid + pub fn has_horizontal(&self, row: usize, count_rows: usize) -> bool { + self.borders.has_horizontal(row, count_rows) + } + + /// Checks if grid would have a vertical border with the current configuration. + /// + /// grid: crate::Grid + pub fn has_vertical(&self, col: usize, count_columns: usize) -> bool { + self.borders.has_vertical(col, count_columns) + } + + /// Calculates an amount of horizontal lines would present on the grid. + /// + /// grid: crate::Grid + pub fn count_horizontal(&self, count_rows: usize) -> usize { + (0..=count_rows) + .filter(|&row| self.has_horizontal(row, count_rows)) + .count() + } + + /// Calculates an amount of vertical lines would present on the grid. + /// + /// grid: crate::Grid + pub fn count_vertical(&self, count_columns: usize) -> usize { + (0..=count_columns) + .filter(|&col| self.has_vertical(col, count_columns)) + .count() + } + + /// The function returns whether the cells will be rendered or it will be hidden because of a span. + pub fn is_cell_visible(&self, pos: Position) -> bool { + !(self.is_cell_covered_by_column_span(pos) + || self.is_cell_covered_by_row_span(pos) + || self.is_cell_covered_by_both_spans(pos)) + } + + /// The function checks if a cell is hidden because of a row span. + pub fn is_cell_covered_by_row_span(&self, pos: Position) -> bool { + is_cell_covered_by_row_span(self, pos) + } + + /// The function checks if a cell is hidden because of a column span. + pub fn is_cell_covered_by_column_span(&self, pos: Position) -> bool { + is_cell_covered_by_column_span(self, pos) + } + + /// The function checks if a cell is hidden indirectly because of a row and column span combination. + pub fn is_cell_covered_by_both_spans(&self, pos: Position) -> bool { + is_cell_covered_by_both_spans(self, pos) + } +} + +impl From<CompactConfig> for SpannedConfig { + fn from(compact: CompactConfig) -> Self { + use Entity::Global; + + let mut cfg = Self::default(); + + cfg.set_padding(Global, *compact.get_padding()); + cfg.set_padding_color(Global, to_ansi_color(compact.get_padding_color())); + cfg.set_margin(*compact.get_margin()); + cfg.set_margin_color(to_ansi_color(compact.get_margin_color())); + cfg.set_alignment_horizontal(Global, compact.get_alignment_horizontal()); + cfg.set_borders(*compact.get_borders()); + cfg.set_borders_color(borders_static_color_to_ansi_color( + *compact.get_borders_color(), + )); + + if let Some(line) = compact.get_first_horizontal_line() { + cfg.insert_horizontal_line( + 1, + HorizontalLine { + intersection: line.intersection, + left: line.connect1, + right: line.connect2, + main: Some(line.main), + }, + ); + } + + cfg + } +} + +fn to_ansi_color(b: Sides<StaticColor>) -> Sides<Option<AnsiColor<'static>>> { + Sides::new( + Some(b.left.into()), + Some(b.right.into()), + Some(b.top.into()), + Some(b.bottom.into()), + ) +} + +fn borders_static_color_to_ansi_color(b: Borders<StaticColor>) -> Borders<AnsiColor<'static>> { + Borders { + left: b.left.map(|c| c.into()), + right: b.right.map(|c| c.into()), + top: b.top.map(|c| c.into()), + bottom: b.bottom.map(|c| c.into()), + bottom_intersection: b.bottom_intersection.map(|c| c.into()), + bottom_left: b.bottom_left.map(|c| c.into()), + bottom_right: b.bottom_right.map(|c| c.into()), + horizontal: b.horizontal.map(|c| c.into()), + intersection: b.intersection.map(|c| c.into()), + left_intersection: b.left_intersection.map(|c| c.into()), + right_intersection: b.right_intersection.map(|c| c.into()), + top_intersection: b.top_intersection.map(|c| c.into()), + top_left: b.top_left.map(|c| c.into()), + top_right: b.top_right.map(|c| c.into()), + vertical: b.vertical.map(|c| c.into()), + } +} + +fn set_cell_row_span(cfg: &mut SpannedConfig, pos: Position, span: usize) { + // such spans aren't supported + if span == 0 { + return; + } + + // It's a default span so we can do nothing. + // but we check if it's an override of a span. + if span == 1 { + cfg.span_rows.remove(&pos); + return; + } + + cfg.span_rows.insert(pos, span); +} + +fn set_cell_column_span(cfg: &mut SpannedConfig, pos: Position, span: usize) { + // such spans aren't supported + if span == 0 { + return; + } + + // It's a default span so we can do nothing. + // but we check if it's an override of a span. + if span == 1 { + cfg.span_columns.remove(&pos); + return; + } + + cfg.span_columns.insert(pos, span); +} + +fn is_cell_covered_by_column_span(cfg: &SpannedConfig, pos: Position) -> bool { + cfg.span_columns + .iter() + .any(|(&(row, col), span)| pos.1 > col && pos.1 < col + span && row == pos.0) +} + +fn is_cell_covered_by_row_span(cfg: &SpannedConfig, pos: Position) -> bool { + cfg.span_rows + .iter() + .any(|(&(row, col), span)| pos.0 > row && pos.0 < row + span && col == pos.1) +} + +fn is_cell_covered_by_both_spans(cfg: &SpannedConfig, pos: Position) -> bool { + if !cfg.has_column_spans() || !cfg.has_row_spans() { + return false; + } + + cfg.span_rows.iter().any(|(p1, row_span)| { + cfg.span_columns + .iter() + .filter(|(p2, _)| &p1 == p2) + .any(|(_, col_span)| { + pos.0 > p1.0 && pos.0 < p1.0 + row_span && pos.1 > p1.1 && pos.1 < p1.1 + col_span + }) + }) +} + +#[derive(Default, Debug, Clone, PartialEq, Eq)] +struct ColoredIndent { + indent: Indent, + color: Option<AnsiColor<'static>>, +} + +/// A colorefull margin indent. +#[derive(Debug, Clone, PartialEq, Eq)] +struct ColoredMarginIndent { + /// An indent value. + indent: Indent, + /// An offset value. + offset: Offset, + /// An color value. + color: Option<AnsiColor<'static>>, +} + +impl Default for ColoredMarginIndent { + fn default() -> Self { + Self { + indent: Indent::default(), + offset: Offset::Begin(0), + color: None, + } + } +} + +/// HorizontalLine represents a horizontal border line. +pub type HorizontalLine = borders_config::HorizontalLine<char>; + +/// HorizontalLine represents a vertical border line. +pub type VerticalLine = borders_config::VerticalLine<char>; diff --git a/vendor/papergrid/src/config/spanned/offset.rs b/vendor/papergrid/src/config/spanned/offset.rs new file mode 100644 index 000000000..03055ca34 --- /dev/null +++ b/vendor/papergrid/src/config/spanned/offset.rs @@ -0,0 +1,8 @@ +/// The structure represents an offset in a text. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)] +pub enum Offset { + /// An offset from the start. + Begin(usize), + /// An offset from the end. + End(usize), +} diff --git a/vendor/papergrid/src/dimension/compact.rs b/vendor/papergrid/src/dimension/compact.rs new file mode 100644 index 000000000..e53429ad9 --- /dev/null +++ b/vendor/papergrid/src/dimension/compact.rs @@ -0,0 +1,121 @@ +//! The module contains a [`CompactGridDimension`] for [`CompactGrid`] height/width estimation. +//! +//! [`CompactGrid`]: crate::grid::compact::CompactGrid + +use core::cmp::max; + +use crate::{ + dimension::{Dimension, Estimate}, + records::Records, + util::string::{count_lines, string_width_multiline}, +}; + +use crate::config::compact::CompactConfig; + +/// A [`Dimension`] implementation which calculates exact column/row width/height. +/// +/// [`Grid`]: crate::grid::iterable::Grid +#[derive(Debug, Default, Clone, PartialEq, Eq)] +pub struct CompactGridDimension { + height: usize, + width: Vec<usize>, +} + +impl CompactGridDimension { + /// Calculates height of rows. + pub fn height<R: Records>(records: R, cfg: &CompactConfig) -> Vec<usize> { + build_height(records, cfg) + } + + /// Calculates width of columns. + pub fn width<R: Records>(records: R, cfg: &CompactConfig) -> Vec<usize> { + build_width(records, cfg) + } + + /// Calculates dimensions of columns. + pub fn dimension<R: Records>(records: R, cfg: &CompactConfig) -> (Vec<usize>, Vec<usize>) { + build_dims(records, cfg) + } +} + +impl Dimension for CompactGridDimension { + fn get_width(&self, column: usize) -> usize { + self.width[column] + } + + fn get_height(&self, _: usize) -> usize { + self.height + } +} + +impl<R> Estimate<R, CompactConfig> for CompactGridDimension +where + R: Records, +{ + fn estimate(&mut self, records: R, cfg: &CompactConfig) { + self.width = build_width(records, cfg); + let pad = cfg.get_padding(); + self.height = 1 + pad.top.size + pad.bottom.size; + } +} + +fn build_dims<R: Records>(records: R, cfg: &CompactConfig) -> (Vec<usize>, Vec<usize>) { + let mut heights = vec![]; + let mut widths = vec![0; records.count_columns()]; + + for columns in records.iter_rows() { + let mut row_height = 0; + for (col, cell) in columns.into_iter().enumerate() { + let height = get_cell_height(cell.as_ref(), cfg); + let width = get_cell_width(cell.as_ref(), cfg); + row_height = max(row_height, height); + widths[col] = max(widths[col], width) + } + + heights.push(row_height); + } + + (widths, heights) +} + +fn build_height<R: Records>(records: R, cfg: &CompactConfig) -> Vec<usize> { + let mut heights = vec![]; + + for columns in records.iter_rows() { + let mut row_height = 0; + for cell in columns.into_iter() { + let height = get_cell_height(cell.as_ref(), cfg); + row_height = max(row_height, height); + } + + heights.push(row_height); + } + + heights +} + +fn build_width<R: Records>(records: R, cfg: &CompactConfig) -> Vec<usize> { + let mut widths = vec![0; records.count_columns()]; + for columns in records.iter_rows() { + for (col, cell) in columns.into_iter().enumerate() { + let width = get_cell_width(cell.as_ref(), cfg); + widths[col] = max(widths[col], width); + } + } + + widths +} + +fn get_cell_height(cell: &str, cfg: &CompactConfig) -> usize { + let count_lines = max(1, count_lines(cell)); + let pad = cfg.get_padding(); + + count_lines + pad.top.size + pad.bottom.size +} + +fn get_cell_width(text: &str, cfg: &CompactConfig) -> usize { + let width = string_width_multiline(text); + let pad = cfg.get_padding(); + + width + pad.left.size + pad.right.size +} diff --git a/vendor/papergrid/src/dimension/mod.rs b/vendor/papergrid/src/dimension/mod.rs new file mode 100644 index 000000000..40be17b48 --- /dev/null +++ b/vendor/papergrid/src/dimension/mod.rs @@ -0,0 +1,44 @@ +//! The module contains an [`Dimension`] trait and its implementations. + +#[cfg(feature = "std")] +pub mod compact; +#[cfg(feature = "std")] +pub mod spanned; +#[cfg(feature = "std")] +pub mod spanned_vec_records; + +/// Dimension of a [`Grid`] +/// +/// It's a friend trait of [`Estimate`]. +/// +/// [`Grid`]: crate::grid::iterable::Grid +pub trait Dimension { + /// Get a column width by index. + fn get_width(&self, column: usize) -> usize; + + /// Get a row height by index. + fn get_height(&self, row: usize) -> usize; +} + +impl<T> Dimension for &T +where + T: Dimension, +{ + fn get_height(&self, row: usize) -> usize { + T::get_height(self, row) + } + + fn get_width(&self, column: usize) -> usize { + T::get_width(self, column) + } +} + +/// Dimension estimation of a [`Grid`] +/// +/// It's a friend trait of [`Dimension`]. +/// +/// [`Grid`]: crate::grid::iterable::Grid +pub trait Estimate<R, C> { + /// Estimates a metric. + fn estimate(&mut self, records: R, config: &C); +} diff --git a/vendor/papergrid/src/dimension/spanned.rs b/vendor/papergrid/src/dimension/spanned.rs new file mode 100644 index 000000000..6597ba176 --- /dev/null +++ b/vendor/papergrid/src/dimension/spanned.rs @@ -0,0 +1,329 @@ +//! The module contains a [`SpannedGridDimension`] for [`Grid`] height/width estimation. +//! +//! [`Grid`]: crate::grid::iterable::Grid + +use std::{ + cmp::{max, Ordering}, + collections::HashMap, +}; + +use crate::{ + config::Position, + dimension::{Dimension, Estimate}, + records::Records, + util::string::{count_lines, string_dimension, string_width_multiline}, +}; + +use crate::config::spanned::SpannedConfig; + +/// A [`Dimension`] implementation which calculates exact column/row width/height. +/// +/// [`Grid`]: crate::grid::iterable::Grid +#[derive(Debug, Default, Clone, PartialEq, Eq)] +pub struct SpannedGridDimension { + height: Vec<usize>, + width: Vec<usize>, +} + +impl SpannedGridDimension { + /// Calculates height of rows. + pub fn height<R: Records>(records: R, cfg: &SpannedConfig) -> Vec<usize> { + build_height(records, cfg) + } + + /// Calculates width of columns. + pub fn width<R: Records>(records: R, cfg: &SpannedConfig) -> Vec<usize> { + build_width(records, cfg) + } + + /// Return width and height lists. + pub fn get_values(self) -> (Vec<usize>, Vec<usize>) { + (self.width, self.height) + } +} + +impl Dimension for SpannedGridDimension { + fn get_width(&self, column: usize) -> usize { + self.width[column] + } + + fn get_height(&self, row: usize) -> usize { + self.height[row] + } +} + +impl<R> Estimate<R, SpannedConfig> for SpannedGridDimension +where + R: Records, +{ + fn estimate(&mut self, records: R, cfg: &SpannedConfig) { + let (width, height) = build_dimensions(records, cfg); + self.width = width; + self.height = height; + } +} + +fn build_dimensions<R: Records>(records: R, cfg: &SpannedConfig) -> (Vec<usize>, Vec<usize>) { + let count_columns = records.count_columns(); + + let mut widths = vec![0; count_columns]; + let mut heights = vec![]; + + let mut vspans = HashMap::new(); + let mut hspans = HashMap::new(); + + for (row, columns) in records.iter_rows().into_iter().enumerate() { + let mut row_height = 0; + for (col, cell) in columns.into_iter().enumerate() { + let pos = (row, col); + if !cfg.is_cell_visible(pos) { + continue; + } + + let text = cell.as_ref(); + let (height, width) = string_dimension(text); + let pad = cfg.get_padding(pos.into()); + let width = width + pad.left.size + pad.right.size; + let height = height + pad.top.size + pad.bottom.size; + + match cfg.get_column_span(pos) { + Some(n) if n > 1 => { + vspans.insert(pos, (n, width)); + } + _ => widths[col] = max(widths[col], width), + } + + match cfg.get_row_span(pos) { + Some(n) if n > 1 => { + hspans.insert(pos, (n, height)); + } + _ => row_height = max(row_height, height), + } + } + + heights.push(row_height); + } + + let count_rows = heights.len(); + + adjust_vspans(cfg, count_columns, &vspans, &mut widths); + adjust_hspans(cfg, count_rows, &hspans, &mut heights); + + (widths, heights) +} + +fn adjust_hspans( + cfg: &SpannedConfig, + len: usize, + spans: &HashMap<Position, (usize, usize)>, + heights: &mut [usize], +) { + if spans.is_empty() { + return; + } + + let mut spans_ordered = spans + .iter() + .map(|(k, v)| ((k.0, k.1), *v)) + .collect::<Vec<_>>(); + spans_ordered.sort_unstable_by(|(arow, acol), (brow, bcol)| match arow.cmp(brow) { + Ordering::Equal => acol.cmp(bcol), + ord => ord, + }); + + for ((row, _), (span, height)) in spans_ordered { + adjust_row_range(cfg, height, len, row, row + span, heights); + } +} + +fn adjust_row_range( + cfg: &SpannedConfig, + max_span_height: usize, + len: usize, + start: usize, + end: usize, + heights: &mut [usize], +) { + let range_height = range_height(cfg, len, start, end, heights); + if range_height >= max_span_height { + return; + } + + inc_range(heights, max_span_height - range_height, start, end); +} + +fn range_height( + cfg: &SpannedConfig, + len: usize, + start: usize, + end: usize, + heights: &[usize], +) -> usize { + let count_borders = count_horizontal_borders(cfg, len, start, end); + let range_height = heights[start..end].iter().sum::<usize>(); + count_borders + range_height +} + +fn count_horizontal_borders(cfg: &SpannedConfig, len: usize, start: usize, end: usize) -> usize { + (start..end) + .skip(1) + .filter(|&i| cfg.has_horizontal(i, len)) + .count() +} + +fn get_cell_height(cell: &str, cfg: &SpannedConfig, pos: Position) -> usize { + let count_lines = max(1, count_lines(cell)); + let padding = cfg.get_padding(pos.into()); + count_lines + padding.top.size + padding.bottom.size +} + +fn inc_range(list: &mut [usize], size: usize, start: usize, end: usize) { + if list.is_empty() { + return; + } + + let span = end - start; + let one = size / span; + let rest = size - span * one; + + let mut i = start; + while i < end { + if i == start { + list[i] += one + rest; + } else { + list[i] += one; + } + + i += 1; + } +} + +fn adjust_vspans( + cfg: &SpannedConfig, + len: usize, + spans: &HashMap<Position, (usize, usize)>, + widths: &mut [usize], +) { + if spans.is_empty() { + return; + } + + // The overall width distribution will be different depend on the order. + // + // We sort spans in order to prioritize the smaller spans first. + let mut spans_ordered = spans + .iter() + .map(|(k, v)| ((k.0, k.1), *v)) + .collect::<Vec<_>>(); + spans_ordered.sort_unstable_by(|a, b| match a.1 .0.cmp(&b.1 .0) { + Ordering::Equal => a.0.cmp(&b.0), + o => o, + }); + + for ((_, col), (span, width)) in spans_ordered { + adjust_column_range(cfg, width, len, col, col + span, widths); + } +} + +fn adjust_column_range( + cfg: &SpannedConfig, + max_span_width: usize, + len: usize, + start: usize, + end: usize, + widths: &mut [usize], +) { + let range_width = range_width(cfg, len, start, end, widths); + if range_width >= max_span_width { + return; + } + + inc_range(widths, max_span_width - range_width, start, end); +} + +fn get_cell_width(text: &str, cfg: &SpannedConfig, pos: Position) -> usize { + let padding = get_cell_padding(cfg, pos); + let width = string_width_multiline(text); + width + padding +} + +fn get_cell_padding(cfg: &SpannedConfig, pos: Position) -> usize { + let padding = cfg.get_padding(pos.into()); + padding.left.size + padding.right.size +} + +fn range_width( + cfg: &SpannedConfig, + len: usize, + start: usize, + end: usize, + widths: &[usize], +) -> usize { + let count_borders = count_vertical_borders(cfg, len, start, end); + let range_width = widths[start..end].iter().sum::<usize>(); + count_borders + range_width +} + +fn count_vertical_borders(cfg: &SpannedConfig, len: usize, start: usize, end: usize) -> usize { + (start..end) + .skip(1) + .filter(|&i| cfg.has_vertical(i, len)) + .count() +} + +fn build_height<R: Records>(records: R, cfg: &SpannedConfig) -> Vec<usize> { + let mut heights = vec![]; + let mut hspans = HashMap::new(); + + for (row, columns) in records.iter_rows().into_iter().enumerate() { + let mut row_height = 0; + for (col, cell) in columns.into_iter().enumerate() { + let pos = (row, col); + if !cfg.is_cell_visible(pos) { + continue; + } + + let height = get_cell_height(cell.as_ref(), cfg, pos); + match cfg.get_row_span(pos) { + Some(n) if n > 1 => { + hspans.insert(pos, (n, height)); + } + _ => row_height = max(row_height, height), + } + } + + heights.push(row_height); + } + + adjust_hspans(cfg, heights.len(), &hspans, &mut heights); + + heights +} + +fn build_width<R: Records>(records: R, cfg: &SpannedConfig) -> Vec<usize> { + let count_columns = records.count_columns(); + + let mut widths = vec![0; count_columns]; + let mut vspans = HashMap::new(); + + for (row, columns) in records.iter_rows().into_iter().enumerate() { + for (col, cell) in columns.into_iter().enumerate() { + let pos = (row, col); + if !cfg.is_cell_visible(pos) { + continue; + } + + let width = get_cell_width(cell.as_ref(), cfg, pos); + match cfg.get_column_span(pos) { + Some(n) if n > 1 => { + vspans.insert(pos, (n, width)); + } + _ => widths[col] = max(widths[col], width), + } + } + } + + adjust_vspans(cfg, count_columns, &vspans, &mut widths); + + widths +} diff --git a/vendor/papergrid/src/dimension/spanned_vec_records.rs b/vendor/papergrid/src/dimension/spanned_vec_records.rs new file mode 100644 index 000000000..75e85a850 --- /dev/null +++ b/vendor/papergrid/src/dimension/spanned_vec_records.rs @@ -0,0 +1,332 @@ +//! The module contains a [`SpannedVecRecordsDimension`] for [`Grid`] height/width estimation. +//! +//! [`Grid`]: crate::grid::iterable::Grid + +use std::{ + cmp::{max, Ordering}, + collections::HashMap, +}; + +use crate::{ + config::Position, + dimension::{Dimension, Estimate}, + records::{ + vec_records::{Cell, VecRecords}, + Records, + }, +}; + +use crate::config::spanned::SpannedConfig; + +/// A [`Dimension`] implementation which calculates exact column/row width/height for [`VecRecords`]. +/// +/// It is a specialization of [`SpannedGridDimension`] for [`VecRecords`]. +/// +/// [`SpannedGridDimension`]: crate::dimension::spanned::SpannedGridDimension +#[derive(Debug, Default, Clone, PartialEq, Eq)] +pub struct SpannedVecRecordsDimension { + height: Vec<usize>, + width: Vec<usize>, +} + +impl SpannedVecRecordsDimension { + /// Calculates height of rows. + pub fn height<T: Cell + AsRef<str>>( + records: &VecRecords<T>, + cfg: &SpannedConfig, + ) -> Vec<usize> { + build_height(records, cfg) + } + + /// Calculates width of columns. + pub fn width<T: Cell + AsRef<str>>(records: &VecRecords<T>, cfg: &SpannedConfig) -> Vec<usize> { + build_width(records, cfg) + } + + /// Return width and height lists. + pub fn get_values(self) -> (Vec<usize>, Vec<usize>) { + (self.width, self.height) + } +} + +impl Dimension for SpannedVecRecordsDimension { + fn get_width(&self, column: usize) -> usize { + self.width[column] + } + + fn get_height(&self, row: usize) -> usize { + self.height[row] + } +} + +impl<T> Estimate<&VecRecords<T>, SpannedConfig> for SpannedVecRecordsDimension +where + T: Cell + AsRef<str>, +{ + fn estimate(&mut self, records: &VecRecords<T>, cfg: &SpannedConfig) { + let (width, height) = build_dimensions(records, cfg); + self.width = width; + self.height = height; + } +} + +fn build_dimensions<T: Cell + AsRef<str>>( + records: &VecRecords<T>, + cfg: &SpannedConfig, +) -> (Vec<usize>, Vec<usize>) { + let count_columns = records.count_columns(); + + let mut widths = vec![0; count_columns]; + let mut heights = vec![]; + + let mut vspans = HashMap::new(); + let mut hspans = HashMap::new(); + + for (row, columns) in records.iter_rows().enumerate() { + let mut row_height = 0; + for (col, cell) in columns.iter().enumerate() { + let pos = (row, col); + if !cfg.is_cell_visible(pos) { + continue; + } + + let width = cell.width(); + let height = cell.count_lines(); + let pad = cfg.get_padding(pos.into()); + let width = width + pad.left.size + pad.right.size; + let height = height + pad.top.size + pad.bottom.size; + + match cfg.get_column_span(pos) { + Some(n) if n > 1 => { + vspans.insert(pos, (n, width)); + } + _ => widths[col] = max(widths[col], width), + } + + match cfg.get_row_span(pos) { + Some(n) if n > 1 => { + hspans.insert(pos, (n, height)); + } + _ => row_height = max(row_height, height), + } + } + + heights.push(row_height); + } + + let count_rows = heights.len(); + + adjust_vspans(cfg, count_columns, &vspans, &mut widths); + adjust_hspans(cfg, count_rows, &hspans, &mut heights); + + (widths, heights) +} + +fn adjust_hspans( + cfg: &SpannedConfig, + len: usize, + spans: &HashMap<Position, (usize, usize)>, + heights: &mut [usize], +) { + if spans.is_empty() { + return; + } + + let mut spans_ordered = spans + .iter() + .map(|(k, v)| ((k.0, k.1), *v)) + .collect::<Vec<_>>(); + spans_ordered.sort_unstable_by(|(arow, acol), (brow, bcol)| match arow.cmp(brow) { + Ordering::Equal => acol.cmp(bcol), + ord => ord, + }); + + for ((row, _), (span, height)) in spans_ordered { + adjust_row_range(cfg, height, len, row, row + span, heights); + } +} + +fn adjust_row_range( + cfg: &SpannedConfig, + max_span_height: usize, + len: usize, + start: usize, + end: usize, + heights: &mut [usize], +) { + let range_height = range_height(cfg, len, start, end, heights); + if range_height >= max_span_height { + return; + } + + inc_range(heights, max_span_height - range_height, start, end); +} + +fn range_height( + cfg: &SpannedConfig, + len: usize, + start: usize, + end: usize, + heights: &[usize], +) -> usize { + let count_borders = count_horizontal_borders(cfg, len, start, end); + let range_height = heights[start..end].iter().sum::<usize>(); + count_borders + range_height +} + +fn count_horizontal_borders(cfg: &SpannedConfig, len: usize, start: usize, end: usize) -> usize { + (start..end) + .skip(1) + .filter(|&i| cfg.has_horizontal(i, len)) + .count() +} + +fn inc_range(list: &mut [usize], size: usize, start: usize, end: usize) { + if list.is_empty() { + return; + } + + let span = end - start; + let one = size / span; + let rest = size - span * one; + + let mut i = start; + while i < end { + if i == start { + list[i] += one + rest; + } else { + list[i] += one; + } + + i += 1; + } +} + +fn adjust_vspans( + cfg: &SpannedConfig, + len: usize, + spans: &HashMap<Position, (usize, usize)>, + widths: &mut [usize], +) { + if spans.is_empty() { + return; + } + + // The overall width distribution will be different depend on the order. + // + // We sort spans in order to prioritize the smaller spans first. + let mut spans_ordered = spans + .iter() + .map(|(k, v)| ((k.0, k.1), *v)) + .collect::<Vec<_>>(); + spans_ordered.sort_unstable_by(|a, b| match a.1 .0.cmp(&b.1 .0) { + Ordering::Equal => a.0.cmp(&b.0), + o => o, + }); + + for ((_, col), (span, width)) in spans_ordered { + adjust_column_range(cfg, width, len, col, col + span, widths); + } +} + +fn adjust_column_range( + cfg: &SpannedConfig, + max_span_width: usize, + len: usize, + start: usize, + end: usize, + widths: &mut [usize], +) { + let range_width = range_width(cfg, len, start, end, widths); + if range_width >= max_span_width { + return; + } + + inc_range(widths, max_span_width - range_width, start, end); +} + +fn get_cell_padding_horizontal(cfg: &SpannedConfig, pos: Position) -> usize { + let padding = cfg.get_padding(pos.into()); + padding.left.size + padding.right.size +} + +fn get_cell_vertical_padding(cfg: &SpannedConfig, pos: Position) -> usize { + let padding = cfg.get_padding(pos.into()); + padding.top.size + padding.bottom.size +} + +fn range_width( + cfg: &SpannedConfig, + len: usize, + start: usize, + end: usize, + widths: &[usize], +) -> usize { + let count_borders = count_vertical_borders(cfg, len, start, end); + let range_width = widths[start..end].iter().sum::<usize>(); + count_borders + range_width +} + +fn count_vertical_borders(cfg: &SpannedConfig, len: usize, start: usize, end: usize) -> usize { + (start..end) + .skip(1) + .filter(|&i| cfg.has_vertical(i, len)) + .count() +} + +fn build_height<T: Cell + AsRef<str>>(records: &VecRecords<T>, cfg: &SpannedConfig) -> Vec<usize> { + let mut heights = vec![]; + let mut hspans = HashMap::new(); + + for (row, columns) in records.iter_rows().enumerate() { + let mut row_height = 0; + for (col, cell) in columns.iter().enumerate() { + let pos = (row, col); + if !cfg.is_cell_visible(pos) { + continue; + } + + let height = cell.count_lines() + get_cell_vertical_padding(cfg, pos); + match cfg.get_row_span(pos) { + Some(n) if n > 1 => { + hspans.insert(pos, (n, height)); + } + _ => row_height = max(row_height, height), + } + } + + heights.push(row_height); + } + + adjust_hspans(cfg, heights.len(), &hspans, &mut heights); + + heights +} + +fn build_width<T: Cell + AsRef<str>>(records: &VecRecords<T>, cfg: &SpannedConfig) -> Vec<usize> { + let count_columns = records.count_columns(); + + let mut widths = vec![0; count_columns]; + let mut vspans = HashMap::new(); + + for (row, columns) in records.iter_rows().enumerate() { + for (col, cell) in columns.iter().enumerate() { + let pos = (row, col); + if !cfg.is_cell_visible(pos) { + continue; + } + + let width = cell.width() + get_cell_padding_horizontal(cfg, (row, col)); + match cfg.get_column_span(pos) { + Some(n) if n > 1 => { + vspans.insert(pos, (n, width)); + } + _ => widths[col] = max(widths[col], width), + } + } + } + + adjust_vspans(cfg, count_columns, &vspans, &mut widths); + + widths +} diff --git a/vendor/papergrid/src/grid/compact.rs b/vendor/papergrid/src/grid/compact.rs new file mode 100644 index 000000000..f9536bd80 --- /dev/null +++ b/vendor/papergrid/src/grid/compact.rs @@ -0,0 +1,659 @@ +//! The module contains a [`CompactGrid`] structure, +//! which is a relatively strict grid. + +use core::{ + borrow::Borrow, + fmt::{self, Display, Write}, +}; + +use crate::{ + color::{Color, StaticColor}, + colors::{Colors, NoColors}, + config::{AlignmentHorizontal, Borders, Indent, Line, Sides}, + dimension::Dimension, + records::Records, + util::string::string_width, +}; + +use crate::config::compact::CompactConfig; + +/// Grid provides a set of methods for building a text-based table. +#[derive(Debug, Clone)] +pub struct CompactGrid<R, D, G, C> { + records: R, + config: G, + dimension: D, + colors: C, +} + +impl<R, D, G> CompactGrid<R, D, G, NoColors> { + /// The new method creates a grid instance with default styles. + pub fn new(records: R, dimension: D, config: G) -> Self { + CompactGrid { + records, + config, + dimension, + colors: NoColors::default(), + } + } +} + +impl<R, D, G, C> CompactGrid<R, D, G, C> { + /// Sets colors map. + pub fn with_colors<Colors>(self, colors: Colors) -> CompactGrid<R, D, G, Colors> { + CompactGrid { + records: self.records, + config: self.config, + dimension: self.dimension, + colors, + } + } + + /// Builds a table. + pub fn build<F>(self, mut f: F) -> fmt::Result + where + R: Records, + D: Dimension, + C: Colors, + G: Borrow<CompactConfig>, + F: Write, + { + if self.records.count_columns() == 0 { + return Ok(()); + } + + let config = self.config.borrow(); + print_grid(&mut f, self.records, config, &self.dimension, &self.colors) + } + + /// Builds a table into string. + /// + /// Notice that it consumes self. + #[cfg(feature = "std")] + #[allow(clippy::inherent_to_string)] + pub fn to_string(self) -> String + where + R: Records, + D: Dimension, + G: Borrow<CompactConfig>, + C: Colors, + { + let mut buf = String::new(); + self.build(&mut buf).expect("It's guaranteed to never happen otherwise it's considered an stdlib error or impl error"); + buf + } +} + +impl<R, D, G, C> Display for CompactGrid<R, D, G, C> +where + for<'a> &'a R: Records, + D: Dimension, + G: Borrow<CompactConfig>, + C: Colors, +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let records = &self.records; + let config = self.config.borrow(); + + print_grid(f, records, config, &self.dimension, &self.colors) + } +} + +fn print_grid<F: Write, R: Records, D: Dimension, C: Colors>( + f: &mut F, + records: R, + cfg: &CompactConfig, + dims: &D, + colors: &C, +) -> fmt::Result { + let count_columns = records.count_columns(); + let count_rows = records.hint_count_rows(); + + if count_columns == 0 || matches!(count_rows, Some(0)) { + return Ok(()); + } + + let mut records = records.iter_rows().into_iter(); + let mut next_columns = records.next(); + + if next_columns.is_none() { + return Ok(()); + } + + let wtotal = total_width(cfg, dims, count_columns); + + let borders = cfg.get_borders(); + let bcolors = cfg.get_borders_color(); + + let h_chars = create_horizontal(borders); + let h_colors = create_horizontal_colors(bcolors); + + let has_horizontal = borders.has_horizontal(); + let has_horizontal_colors = bcolors.has_horizontal(); + let has_horizontal_second = cfg.get_first_horizontal_line().is_some(); + + let vert = ( + borders.left.map(|c| (c, bcolors.left)), + borders.vertical.map(|c| (c, bcolors.vertical)), + borders.right.map(|c| (c, bcolors.right)), + ); + + let margin = cfg.get_margin(); + let margin_color = cfg.get_margin_color(); + let pad = create_padding(cfg); + let align = cfg.get_alignment_horizontal(); + let mar = ( + (margin.left, margin_color.left), + (margin.right, margin_color.right), + ); + + let widths = (0..count_columns).map(|col| dims.get_width(col)); + + let mut new_line = false; + + if margin.top.size > 0 { + let wtotal = wtotal + margin.left.size + margin.right.size; + print_indent_lines(f, wtotal, margin.top, margin_color.top)?; + new_line = true; + } + + if borders.has_top() { + if new_line { + f.write_char('\n')? + } + + print_indent2(f, margin.left, margin_color.left)?; + + let chars = create_horizontal_top(borders); + if bcolors.has_top() { + let chars_color = create_horizontal_top_colors(bcolors); + print_split_line_colored(f, chars, chars_color, dims, count_columns)?; + } else { + print_split_line(f, chars, dims, count_columns)?; + } + + print_indent2(f, margin.right, margin_color.right)?; + + new_line = true; + } + + let mut row = 0; + while let Some(columns) = next_columns { + let columns = columns.into_iter(); + next_columns = records.next(); + + if row > 0 && has_horizontal { + if new_line { + f.write_char('\n')?; + } + + print_indent2(f, margin.left, margin_color.left)?; + + if has_horizontal_colors { + print_split_line_colored(f, h_chars, h_colors, dims, count_columns)?; + } else { + print_split_line(f, h_chars, dims, count_columns)?; + } + + print_indent2(f, margin.right, margin_color.right)?; + } else if row == 1 && has_horizontal_second { + if new_line { + f.write_char('\n')?; + } + + print_indent2(f, margin.left, margin_color.left)?; + + let h_chars = cfg.get_first_horizontal_line().expect("must be here"); + + if has_horizontal_colors { + print_split_line_colored(f, h_chars, h_colors, dims, count_columns)?; + } else { + print_split_line(f, h_chars, dims, count_columns)?; + } + + print_indent2(f, margin.right, margin_color.right)?; + } + + if new_line { + f.write_char('\n')?; + } + + let columns = columns + .enumerate() + .map(|(col, text)| (text, colors.get_color((row, col)))); + + let widths = widths.clone(); + print_grid_row(f, columns, widths, mar, pad, vert, align)?; + + new_line = true; + row += 1; + } + + if borders.has_bottom() { + f.write_char('\n')?; + + print_indent2(f, margin.left, margin_color.left)?; + + let chars = create_horizontal_bottom(borders); + if bcolors.has_bottom() { + let chars_color = create_horizontal_bottom_colors(bcolors); + print_split_line_colored(f, chars, chars_color, dims, count_columns)?; + } else { + print_split_line(f, chars, dims, count_columns)?; + } + + print_indent2(f, margin.right, margin_color.right)?; + } + + if cfg.get_margin().bottom.size > 0 { + f.write_char('\n')?; + + let wtotal = wtotal + margin.left.size + margin.right.size; + print_indent_lines(f, wtotal, margin.bottom, margin_color.bottom)?; + } + + Ok(()) +} + +type ColoredIndent = (Indent, StaticColor); + +#[allow(clippy::too_many_arguments)] +fn print_grid_row<F, I, T, C, D>( + f: &mut F, + columns: I, + widths: D, + mar: (ColoredIndent, ColoredIndent), + pad: Sides<ColoredIndent>, + vert: (BorderChar, BorderChar, BorderChar), + align: AlignmentHorizontal, +) -> fmt::Result +where + F: Write, + I: Iterator<Item = (T, Option<C>)>, + T: AsRef<str>, + C: Color, + D: Iterator<Item = usize> + Clone, +{ + if pad.top.0.size > 0 { + for _ in 0..pad.top.0.size { + print_indent2(f, mar.0 .0, mar.0 .1)?; + print_columns_empty_colored(f, widths.clone(), vert, pad.top.1)?; + print_indent2(f, mar.1 .0, mar.1 .1)?; + + f.write_char('\n')?; + } + } + + let mut widths1 = widths.clone(); + let columns = columns.map(move |(text, color)| { + let width = widths1.next().expect("must be here"); + (text, color, width) + }); + + print_indent2(f, mar.0 .0, mar.0 .1)?; + print_row_columns(f, columns, vert, pad, align)?; + print_indent2(f, mar.1 .0, mar.1 .1)?; + + for _ in 0..pad.bottom.0.size { + f.write_char('\n')?; + + print_indent2(f, mar.0 .0, mar.0 .1)?; + print_columns_empty_colored(f, widths.clone(), vert, pad.bottom.1)?; + print_indent2(f, mar.1 .0, mar.1 .1)?; + } + + Ok(()) +} + +fn create_padding(cfg: &CompactConfig) -> Sides<ColoredIndent> { + let pad = cfg.get_padding(); + let pad_colors = cfg.get_padding_color(); + Sides::new( + (pad.left, pad_colors.left), + (pad.right, pad_colors.right), + (pad.top, pad_colors.top), + (pad.bottom, pad_colors.bottom), + ) +} + +fn create_horizontal(b: &Borders<char>) -> Line<char> { + Line::new(b.horizontal.unwrap_or(' '), b.intersection, b.left, b.right) +} + +fn create_horizontal_top(b: &Borders<char>) -> Line<char> { + Line::new( + b.top.unwrap_or(' '), + b.top_intersection, + b.top_left, + b.top_right, + ) +} + +fn create_horizontal_bottom(b: &Borders<char>) -> Line<char> { + Line::new( + b.bottom.unwrap_or(' '), + b.bottom_intersection, + b.bottom_left, + b.bottom_right, + ) +} + +fn create_horizontal_colors( + b: &Borders<StaticColor>, +) -> (StaticColor, StaticColor, StaticColor, StaticColor) { + ( + b.horizontal.unwrap_or(StaticColor::default()), + b.left.unwrap_or(StaticColor::default()), + b.intersection.unwrap_or(StaticColor::default()), + b.right.unwrap_or(StaticColor::default()), + ) +} + +fn create_horizontal_top_colors( + b: &Borders<StaticColor>, +) -> (StaticColor, StaticColor, StaticColor, StaticColor) { + ( + b.top.unwrap_or(StaticColor::default()), + b.top_left.unwrap_or(StaticColor::default()), + b.top_intersection.unwrap_or(StaticColor::default()), + b.top_right.unwrap_or(StaticColor::default()), + ) +} + +fn create_horizontal_bottom_colors( + b: &Borders<StaticColor>, +) -> (StaticColor, StaticColor, StaticColor, StaticColor) { + ( + b.bottom.unwrap_or(StaticColor::default()), + b.bottom_left.unwrap_or(StaticColor::default()), + b.bottom_intersection.unwrap_or(StaticColor::default()), + b.bottom_right.unwrap_or(StaticColor::default()), + ) +} + +fn total_width<D: Dimension>(cfg: &CompactConfig, dims: &D, count_columns: usize) -> usize { + let content_width = total_columns_width(count_columns, dims); + let count_verticals = count_verticals(cfg, count_columns); + + content_width + count_verticals +} + +fn total_columns_width<D: Dimension>(count_columns: usize, dims: &D) -> usize { + (0..count_columns).map(|i| dims.get_width(i)).sum::<usize>() +} + +fn count_verticals(cfg: &CompactConfig, count_columns: usize) -> usize { + assert!(count_columns > 0); + + let count_verticals = count_columns - 1; + let borders = cfg.get_borders(); + borders.has_vertical() as usize * count_verticals + + borders.has_left() as usize + + borders.has_right() as usize +} + +type BorderChar = Option<(char, Option<StaticColor>)>; + +fn print_row_columns<F, I, T, C>( + f: &mut F, + mut columns: I, + borders: (BorderChar, BorderChar, BorderChar), + pad: Sides<ColoredIndent>, + align: AlignmentHorizontal, +) -> Result<(), fmt::Error> +where + F: Write, + I: Iterator<Item = (T, Option<C>, usize)>, + T: AsRef<str>, + C: Color, +{ + if let Some((c, color)) = borders.0 { + print_char(f, c, color)?; + } + + if let Some((text, color, width)) = columns.next() { + let text = text.as_ref(); + let text = text.lines().next().unwrap_or(""); + print_cell(f, text, width, color, (pad.left, pad.right), align)?; + } + + for (text, color, width) in columns { + if let Some((c, color)) = borders.1 { + print_char(f, c, color)?; + } + + let text = text.as_ref(); + let text = text.lines().next().unwrap_or(""); + print_cell(f, text, width, color, (pad.left, pad.right), align)?; + } + + if let Some((c, color)) = borders.2 { + print_char(f, c, color)?; + } + + Ok(()) +} + +fn print_columns_empty_colored<F: Write, I: Iterator<Item = usize>>( + f: &mut F, + mut columns: I, + borders: (BorderChar, BorderChar, BorderChar), + color: StaticColor, +) -> Result<(), fmt::Error> { + if let Some((c, color)) = borders.0 { + print_char(f, c, color)?; + } + + if let Some(width) = columns.next() { + color.fmt_prefix(f)?; + repeat_char(f, ' ', width)?; + color.fmt_suffix(f)?; + } + + for width in columns { + if let Some((c, color)) = borders.1 { + print_char(f, c, color)?; + } + + color.fmt_prefix(f)?; + repeat_char(f, ' ', width)?; + color.fmt_suffix(f)?; + } + + if let Some((c, color)) = borders.2 { + print_char(f, c, color)?; + } + + Ok(()) +} + +fn print_cell<F: Write, C: Color>( + f: &mut F, + text: &str, + width: usize, + color: Option<C>, + (pad_l, pad_r): (ColoredIndent, ColoredIndent), + align: AlignmentHorizontal, +) -> fmt::Result { + let available = width - pad_l.0.size - pad_r.0.size; + + let text_width = string_width(text); + let (left, right) = if available < text_width { + (0, 0) + } else { + calculate_indent(align, text_width, available) + }; + + print_indent(f, pad_l.0.fill, pad_l.0.size, pad_l.1)?; + + repeat_char(f, ' ', left)?; + print_text(f, text, color)?; + repeat_char(f, ' ', right)?; + + print_indent(f, pad_r.0.fill, pad_r.0.size, pad_r.1)?; + + Ok(()) +} + +fn print_split_line_colored<F: Write>( + f: &mut F, + chars: Line<char>, + colors: (StaticColor, StaticColor, StaticColor, StaticColor), + dimension: impl Dimension, + count_columns: usize, +) -> fmt::Result { + let mut used_color = StaticColor::default(); + + if let Some(c) = chars.connect1 { + colors.1.fmt_prefix(f)?; + f.write_char(c)?; + used_color = colors.1; + } + + let width = dimension.get_width(0); + if width > 0 { + prepare_coloring(f, &colors.0, &mut used_color)?; + repeat_char(f, chars.main, width)?; + } + + for col in 1..count_columns { + if let Some(c) = &chars.intersection { + prepare_coloring(f, &colors.2, &mut used_color)?; + f.write_char(*c)?; + } + + let width = dimension.get_width(col); + if width > 0 { + prepare_coloring(f, &colors.0, &mut used_color)?; + repeat_char(f, chars.main, width)?; + } + } + + if let Some(c) = &chars.connect2 { + prepare_coloring(f, &colors.3, &mut used_color)?; + f.write_char(*c)?; + } + + used_color.fmt_suffix(f)?; + + Ok(()) +} + +fn print_split_line<F: Write>( + f: &mut F, + chars: Line<char>, + dimension: impl Dimension, + count_columns: usize, +) -> fmt::Result { + if let Some(c) = chars.connect1 { + f.write_char(c)?; + } + + let width = dimension.get_width(0); + if width > 0 { + repeat_char(f, chars.main, width)?; + } + + for col in 1..count_columns { + if let Some(c) = chars.intersection { + f.write_char(c)?; + } + + let width = dimension.get_width(col); + if width > 0 { + repeat_char(f, chars.main, width)?; + } + } + + if let Some(c) = chars.connect2 { + f.write_char(c)?; + } + + Ok(()) +} + +fn print_text<F: Write>(f: &mut F, text: &str, clr: Option<impl Color>) -> fmt::Result { + match clr { + Some(color) => { + color.fmt_prefix(f)?; + f.write_str(text)?; + color.fmt_suffix(f) + } + None => f.write_str(text), + } +} + +fn prepare_coloring<F: Write>(f: &mut F, clr: &StaticColor, used: &mut StaticColor) -> fmt::Result { + if *used != *clr { + used.fmt_suffix(f)?; + clr.fmt_prefix(f)?; + *used = *clr; + } + + Ok(()) +} + +fn calculate_indent( + alignment: AlignmentHorizontal, + text_width: usize, + available: usize, +) -> (usize, usize) { + let diff = available - text_width; + match alignment { + AlignmentHorizontal::Left => (0, diff), + AlignmentHorizontal::Right => (diff, 0), + AlignmentHorizontal::Center => { + let left = diff / 2; + let rest = diff - left; + (left, rest) + } + } +} + +fn repeat_char<F: Write>(f: &mut F, c: char, n: usize) -> fmt::Result { + for _ in 0..n { + f.write_char(c)?; + } + + Ok(()) +} + +fn print_char<F: Write>(f: &mut F, c: char, color: Option<StaticColor>) -> fmt::Result { + match color { + Some(color) => { + color.fmt_prefix(f)?; + f.write_char(c)?; + color.fmt_suffix(f) + } + None => f.write_char(c), + } +} + +fn print_indent_lines<F: Write>( + f: &mut F, + width: usize, + indent: Indent, + color: StaticColor, +) -> fmt::Result { + print_indent(f, indent.fill, width, color)?; + f.write_char('\n')?; + + for _ in 1..indent.size { + f.write_char('\n')?; + print_indent(f, indent.fill, width, color)?; + } + + Ok(()) +} + +fn print_indent<F: Write>(f: &mut F, c: char, n: usize, color: StaticColor) -> fmt::Result { + color.fmt_prefix(f)?; + repeat_char(f, c, n)?; + color.fmt_suffix(f)?; + + Ok(()) +} + +fn print_indent2<F: Write>(f: &mut F, indent: Indent, color: StaticColor) -> fmt::Result { + print_indent(f, indent.fill, indent.size, color) +} diff --git a/vendor/papergrid/src/grid/iterable.rs b/vendor/papergrid/src/grid/iterable.rs new file mode 100644 index 000000000..c02998b0b --- /dev/null +++ b/vendor/papergrid/src/grid/iterable.rs @@ -0,0 +1,1358 @@ +//! The module contains a [`Grid`] structure. + +use std::{ + borrow::{Borrow, Cow}, + cmp, + collections::BTreeMap, + fmt::{self, Write}, +}; + +use crate::{ + color::{AnsiColor, Color}, + colors::Colors, + config::{AlignmentHorizontal, AlignmentVertical, Indent, Position, Sides}, + dimension::Dimension, + records::Records, + util::string::{count_lines, get_lines, string_width, string_width_multiline, Lines}, +}; + +use crate::config::spanned::{Formatting, Offset, SpannedConfig}; + +/// Grid provides a set of methods for building a text-based table. +#[derive(Debug, Clone)] +pub struct Grid<R, D, G, C> { + records: R, + config: G, + dimension: D, + colors: C, +} + +impl<R, D, G, C> Grid<R, D, G, C> { + /// The new method creates a grid instance with default styles. + pub fn new(records: R, dimension: D, config: G, colors: C) -> Self { + Grid { + records, + config, + dimension, + colors, + } + } +} + +impl<R, D, G, C> Grid<R, D, G, C> { + /// Builds a table. + pub fn build<F>(self, mut f: F) -> fmt::Result + where + R: Records, + D: Dimension, + C: Colors, + G: Borrow<SpannedConfig>, + F: Write, + { + if self.records.count_columns() == 0 || self.records.hint_count_rows() == Some(0) { + return Ok(()); + } + + let config = self.config.borrow(); + print_grid(&mut f, self.records, config, &self.dimension, &self.colors) + } + + /// Builds a table into string. + /// + /// Notice that it consumes self. + #[allow(clippy::inherent_to_string)] + pub fn to_string(self) -> String + where + R: Records, + D: Dimension, + G: Borrow<SpannedConfig>, + C: Colors, + { + let mut buf = String::new(); + self.build(&mut buf).expect("It's guaranteed to never happen otherwise it's considered an stdlib error or impl error"); + buf + } +} + +fn print_grid<F: Write, R: Records, D: Dimension, C: Colors>( + f: &mut F, + records: R, + cfg: &SpannedConfig, + dimension: &D, + colors: &C, +) -> fmt::Result { + // spanned version is a bit more complex and 'supposedly' slower, + // because spans are considered to be not a general case we are having 2 versions + let grid_has_spans = cfg.has_column_spans() || cfg.has_row_spans(); + if grid_has_spans { + print_grid_spanned(f, records, cfg, dimension, colors) + } else { + print_grid_general(f, records, cfg, dimension, colors) + } +} + +fn print_grid_general<F: Write, R: Records, D: Dimension, C: Colors>( + f: &mut F, + records: R, + cfg: &SpannedConfig, + dims: &D, + colors: &C, +) -> fmt::Result { + let count_columns = records.count_columns(); + + let mut totalw = None; + let totalh = records + .hint_count_rows() + .map(|count_rows| total_height(cfg, dims, count_rows)); + + let mut records_iter = records.iter_rows().into_iter(); + let mut next_columns = records_iter.next(); + + if next_columns.is_none() { + return Ok(()); + } + + if cfg.get_margin().top.size > 0 { + totalw = Some(output_width(cfg, dims, count_columns)); + + print_margin_top(f, cfg, totalw.unwrap())?; + f.write_char('\n')?; + } + + let mut row = 0; + let mut line = 0; + let mut is_prev_row_skipped = false; + let mut buf = None; + while let Some(columns) = next_columns { + let columns = columns.into_iter(); + next_columns = records_iter.next(); + let is_last_row = next_columns.is_none(); + + let height = dims.get_height(row); + let count_rows = convert_count_rows(row, is_last_row); + let has_horizontal = cfg.has_horizontal(row, count_rows); + let shape = (count_rows, count_columns); + + if row > 0 && !is_prev_row_skipped && (has_horizontal || height > 0) { + f.write_char('\n')?; + } + + if has_horizontal { + print_horizontal_line(f, cfg, line, totalh, dims, row, shape)?; + + line += 1; + + if height > 0 { + f.write_char('\n')?; + } + } + + if height == 1 { + print_single_line_columns(f, columns, cfg, colors, dims, row, line, totalh, shape)? + } else if height > 0 { + if buf.is_none() { + buf = Some(Vec::with_capacity(count_columns)); + } + + let buf = buf.as_mut().unwrap(); + print_multiline_columns( + f, columns, cfg, colors, dims, height, row, line, totalh, shape, buf, + )?; + + buf.clear(); + } + + if height == 0 && !has_horizontal { + is_prev_row_skipped = true; + } else { + is_prev_row_skipped = false; + } + + line += height; + row += 1; + } + + if cfg.has_horizontal(row, row) { + f.write_char('\n')?; + let shape = (row, count_columns); + print_horizontal_line(f, cfg, line, totalh, dims, row, shape)?; + } + + { + let margin = cfg.get_margin(); + if margin.bottom.size > 0 { + let totalw = totalw.unwrap_or_else(|| output_width(cfg, dims, count_columns)); + + f.write_char('\n')?; + print_margin_bottom(f, cfg, totalw)?; + } + } + + Ok(()) +} + +fn output_width<D: Dimension>(cfg: &SpannedConfig, d: D, count_columns: usize) -> usize { + let margin = cfg.get_margin(); + total_width(cfg, &d, count_columns) + margin.left.size + margin.right.size +} + +#[allow(clippy::too_many_arguments)] +fn print_horizontal_line<F: Write, D: Dimension>( + f: &mut F, + cfg: &SpannedConfig, + line: usize, + totalh: Option<usize>, + dimension: &D, + row: usize, + shape: (usize, usize), +) -> fmt::Result { + print_margin_left(f, cfg, line, totalh)?; + print_split_line(f, cfg, dimension, row, shape)?; + print_margin_right(f, cfg, line, totalh)?; + Ok(()) +} + +#[allow(clippy::too_many_arguments)] +fn print_multiline_columns<'a, F, I, D, C>( + f: &mut F, + columns: I, + cfg: &'a SpannedConfig, + colors: &'a C, + dimension: &D, + height: usize, + row: usize, + line: usize, + totalh: Option<usize>, + shape: (usize, usize), + buf: &mut Vec<Cell<I::Item, &'a C::Color>>, +) -> fmt::Result +where + F: Write, + I: Iterator, + I::Item: AsRef<str>, + D: Dimension, + C: Colors, +{ + collect_columns(buf, columns, cfg, colors, dimension, height, row); + print_columns_lines(f, buf, height, cfg, line, row, totalh, shape)?; + Ok(()) +} + +#[allow(clippy::too_many_arguments)] +fn print_single_line_columns<F, I, D, C>( + f: &mut F, + columns: I, + cfg: &SpannedConfig, + colors: &C, + dims: &D, + row: usize, + line: usize, + totalh: Option<usize>, + shape: (usize, usize), +) -> fmt::Result +where + F: Write, + I: Iterator, + I::Item: AsRef<str>, + D: Dimension, + C: Colors, +{ + print_margin_left(f, cfg, line, totalh)?; + + for (col, cell) in columns.enumerate() { + let pos = (row, col); + let width = dims.get_width(col); + let color = colors.get_color(pos); + print_vertical_char(f, cfg, pos, 0, 1, shape.1)?; + print_single_line_column(f, cell.as_ref(), cfg, width, color, pos)?; + } + + print_vertical_char(f, cfg, (row, shape.1), 0, 1, shape.1)?; + + print_margin_right(f, cfg, line, totalh)?; + + Ok(()) +} + +fn print_single_line_column<F: Write, C: Color>( + f: &mut F, + text: &str, + cfg: &SpannedConfig, + width: usize, + color: Option<&C>, + pos: Position, +) -> fmt::Result { + let pos = pos.into(); + let pad = cfg.get_padding(pos); + let pad_color = cfg.get_padding_color(pos); + let fmt = cfg.get_formatting(pos); + let space = cfg.get_justification(pos); + let space_color = cfg.get_justification_color(pos); + + let (text, text_width) = if fmt.horizontal_trim && !text.is_empty() { + let text = string_trim(text); + let width = string_width(&text); + + (text, width) + } else { + let text = Cow::Borrowed(text); + let width = string_width_multiline(&text); + + (text, width) + }; + + let alignment = *cfg.get_alignment_horizontal(pos); + let available_width = width - pad.left.size - pad.right.size; + let (left, right) = calculate_indent(alignment, text_width, available_width); + + print_padding(f, &pad.left, pad_color.left.as_ref())?; + + print_indent(f, space, left, space_color)?; + print_text(f, &text, color)?; + print_indent(f, space, right, space_color)?; + + print_padding(f, &pad.right, pad_color.right.as_ref())?; + + Ok(()) +} + +#[allow(clippy::too_many_arguments)] +fn print_columns_lines<T, F: Write, C: Color>( + f: &mut F, + buf: &mut [Cell<T, C>], + height: usize, + cfg: &SpannedConfig, + line: usize, + row: usize, + totalh: Option<usize>, + shape: (usize, usize), +) -> fmt::Result { + for i in 0..height { + let exact_line = line + i; + + print_margin_left(f, cfg, exact_line, totalh)?; + + for (col, cell) in buf.iter_mut().enumerate() { + print_vertical_char(f, cfg, (row, col), i, height, shape.1)?; + cell.display(f)?; + } + + print_vertical_char(f, cfg, (row, shape.1), i, height, shape.1)?; + + print_margin_right(f, cfg, exact_line, totalh)?; + + if i + 1 != height { + f.write_char('\n')?; + } + } + + Ok(()) +} + +fn collect_columns<'a, I, D, C>( + buf: &mut Vec<Cell<I::Item, &'a C::Color>>, + iter: I, + cfg: &SpannedConfig, + colors: &'a C, + dimension: &D, + height: usize, + row: usize, +) where + I: Iterator, + I::Item: AsRef<str>, + C: Colors, + D: Dimension, +{ + let iter = iter.enumerate().map(|(col, cell)| { + let pos = (row, col); + let width = dimension.get_width(col); + let color = colors.get_color(pos); + Cell::new(cell, width, height, cfg, color, pos) + }); + + buf.extend(iter); +} + +fn print_split_line<F: Write, D: Dimension>( + f: &mut F, + cfg: &SpannedConfig, + dimension: &D, + row: usize, + shape: (usize, usize), +) -> fmt::Result { + let mut used_color = None; + print_vertical_intersection(f, cfg, (row, 0), shape, &mut used_color)?; + + for col in 0..shape.1 { + let width = dimension.get_width(col); + + // general case + if width > 0 { + let pos = (row, col); + let main = cfg.get_horizontal(pos, shape.0); + match main { + Some(c) => { + let clr = cfg.get_horizontal_color(pos, shape.0); + prepare_coloring(f, clr, &mut used_color)?; + print_horizontal_border(f, cfg, pos, width, c, &used_color)?; + } + None => repeat_char(f, ' ', width)?, + } + } + + print_vertical_intersection(f, cfg, (row, col + 1), shape, &mut used_color)?; + } + + if let Some(clr) = used_color.take() { + clr.fmt_suffix(f)?; + } + + Ok(()) +} + +fn print_grid_spanned<F: Write, R: Records, D: Dimension, C: Colors>( + f: &mut F, + records: R, + cfg: &SpannedConfig, + dims: &D, + colors: &C, +) -> fmt::Result { + let count_columns = records.count_columns(); + + let total_width = total_width(cfg, dims, count_columns); + let margin = cfg.get_margin(); + let total_width_with_margin = total_width + margin.left.size + margin.right.size; + + let totalh = records + .hint_count_rows() + .map(|rows| total_height(cfg, dims, rows)); + + if margin.top.size > 0 { + print_margin_top(f, cfg, total_width_with_margin)?; + f.write_char('\n')?; + } + + let mut buf = BTreeMap::new(); + + let mut records_iter = records.iter_rows().into_iter(); + let mut next_columns = records_iter.next(); + + let mut need_new_line = false; + let mut line = 0; + let mut row = 0; + while let Some(columns) = next_columns { + let columns = columns.into_iter(); + next_columns = records_iter.next(); + let is_last_row = next_columns.is_none(); + + let height = dims.get_height(row); + let count_rows = convert_count_rows(row, is_last_row); + let shape = (count_rows, count_columns); + + let has_horizontal = cfg.has_horizontal(row, count_rows); + if need_new_line && (has_horizontal || height > 0) { + f.write_char('\n')?; + need_new_line = false; + } + + if has_horizontal { + print_margin_left(f, cfg, line, totalh)?; + print_split_line_spanned(f, &mut buf, cfg, dims, row, shape)?; + print_margin_right(f, cfg, line, totalh)?; + + line += 1; + + if height > 0 { + f.write_char('\n')?; + } + } + + print_spanned_columns( + f, &mut buf, columns, cfg, colors, dims, height, row, line, totalh, shape, + )?; + + if has_horizontal || height > 0 { + need_new_line = true; + } + + line += height; + row += 1; + } + + if row > 0 { + if cfg.has_horizontal(row, row) { + f.write_char('\n')?; + let shape = (row, count_columns); + print_horizontal_line(f, cfg, line, totalh, dims, row, shape)?; + } + + if margin.bottom.size > 0 { + f.write_char('\n')?; + print_margin_bottom(f, cfg, total_width_with_margin)?; + } + } + + Ok(()) +} + +fn print_split_line_spanned<S, F: Write, D: Dimension, C: Color>( + f: &mut F, + buf: &mut BTreeMap<usize, (Cell<S, C>, usize, usize)>, + cfg: &SpannedConfig, + dimension: &D, + row: usize, + shape: (usize, usize), +) -> fmt::Result { + let mut used_color = None; + print_vertical_intersection(f, cfg, (row, 0), shape, &mut used_color)?; + + for col in 0..shape.1 { + let pos = (row, col); + if cfg.is_cell_covered_by_both_spans(pos) { + continue; + } + + let width = dimension.get_width(col); + let mut col = col; + if cfg.is_cell_covered_by_row_span(pos) { + // means it's part of other a spanned cell + // so. we just need to use line from other cell. + + let (cell, _, _) = buf.get_mut(&col).unwrap(); + cell.display(f)?; + + // We need to use a correct right split char. + let original_row = closest_visible_row(cfg, pos).unwrap(); + if let Some(span) = cfg.get_column_span((original_row, col)) { + col += span - 1; + } + } else if width > 0 { + // general case + let main = cfg.get_horizontal(pos, shape.0); + match main { + Some(c) => { + let clr = cfg.get_horizontal_color(pos, shape.0); + prepare_coloring(f, clr, &mut used_color)?; + print_horizontal_border(f, cfg, pos, width, c, &used_color)?; + } + None => repeat_char(f, ' ', width)?, + } + } + + print_vertical_intersection(f, cfg, (row, col + 1), shape, &mut used_color)?; + } + + if let Some(clr) = used_color.take() { + clr.fmt_suffix(f)?; + } + + Ok(()) +} + +fn print_vertical_intersection<'a, F: fmt::Write>( + f: &mut F, + cfg: &'a SpannedConfig, + pos: Position, + shape: (usize, usize), + used_color: &mut Option<&'a AnsiColor<'static>>, +) -> fmt::Result { + match cfg.get_intersection(pos, shape) { + Some(c) => { + let clr = cfg.get_intersection_color(pos, shape); + prepare_coloring(f, clr, used_color)?; + f.write_char(c) + } + None => Ok(()), + } +} + +#[allow(clippy::too_many_arguments, clippy::type_complexity)] +fn print_spanned_columns<'a, F, I, D, C>( + f: &mut F, + buf: &mut BTreeMap<usize, (Cell<I::Item, &'a C::Color>, usize, usize)>, + iter: I, + cfg: &SpannedConfig, + colors: &'a C, + dimension: &D, + this_height: usize, + row: usize, + line: usize, + totalh: Option<usize>, + shape: (usize, usize), +) -> fmt::Result +where + F: Write, + I: Iterator, + I::Item: AsRef<str>, + D: Dimension, + C: Colors, +{ + if this_height == 0 { + // it's possible that we dont show row but it contains an actual cell which will be + // rendered after all cause it's a rowspanned + + let mut skip = 0; + for (col, cell) in iter.enumerate() { + if skip > 0 { + skip -= 1; + continue; + } + + if let Some((_, _, colspan)) = buf.get(&col) { + skip = *colspan - 1; + continue; + } + + let pos = (row, col); + let rowspan = cfg.get_row_span(pos).unwrap_or(1); + if rowspan < 2 { + continue; + } + + let height = if rowspan > 1 { + range_height(cfg, dimension, row, row + rowspan, shape.0) + } else { + this_height + }; + + let colspan = cfg.get_column_span(pos).unwrap_or(1); + skip = colspan - 1; + let width = if colspan > 1 { + range_width(cfg, dimension, col, col + colspan, shape.1) + } else { + dimension.get_width(col) + }; + + let color = colors.get_color(pos); + let cell = Cell::new(cell, width, height, cfg, color, pos); + + buf.insert(col, (cell, rowspan, colspan)); + } + + buf.retain(|_, (_, rowspan, _)| { + *rowspan -= 1; + *rowspan != 0 + }); + + return Ok(()); + } + + let mut skip = 0; + for (col, cell) in iter.enumerate() { + if skip > 0 { + skip -= 1; + continue; + } + + if let Some((_, _, colspan)) = buf.get(&col) { + skip = *colspan - 1; + continue; + } + + let pos = (row, col); + let colspan = cfg.get_column_span(pos).unwrap_or(1); + skip = colspan - 1; + + let width = if colspan > 1 { + range_width(cfg, dimension, col, col + colspan, shape.1) + } else { + dimension.get_width(col) + }; + + let rowspan = cfg.get_row_span(pos).unwrap_or(1); + let height = if rowspan > 1 { + range_height(cfg, dimension, row, row + rowspan, shape.0) + } else { + this_height + }; + + let color = colors.get_color(pos); + let cell = Cell::new(cell, width, height, cfg, color, pos); + + buf.insert(col, (cell, rowspan, colspan)); + } + + for i in 0..this_height { + let exact_line = line + i; + let cell_line = i; + + print_margin_left(f, cfg, exact_line, totalh)?; + + for (&col, (cell, _, _)) in buf.iter_mut() { + print_vertical_char(f, cfg, (row, col), cell_line, this_height, shape.1)?; + cell.display(f)?; + } + + print_vertical_char(f, cfg, (row, shape.1), cell_line, this_height, shape.1)?; + + print_margin_right(f, cfg, exact_line, totalh)?; + + if i + 1 != this_height { + f.write_char('\n')?; + } + } + + buf.retain(|_, (_, rowspan, _)| { + *rowspan -= 1; + *rowspan != 0 + }); + + Ok(()) +} + +fn print_horizontal_border<F: Write>( + f: &mut F, + cfg: &SpannedConfig, + pos: Position, + width: usize, + c: char, + used_color: &Option<&AnsiColor<'static>>, +) -> fmt::Result { + if !cfg.is_overridden_horizontal(pos) { + return repeat_char(f, c, width); + } + + for i in 0..width { + let c = cfg.lookup_horizontal_char(pos, i, width).unwrap_or(c); + match cfg.lookup_horizontal_color(pos, i, width) { + Some(color) => match used_color { + Some(clr) => { + clr.fmt_suffix(f)?; + color.fmt_prefix(f)?; + f.write_char(c)?; + color.fmt_suffix(f)?; + clr.fmt_prefix(f)?; + } + None => { + color.fmt_prefix(f)?; + f.write_char(c)?; + color.fmt_suffix(f)?; + } + }, + _ => f.write_char(c)?, + } + } + + Ok(()) +} + +struct Cell<T, C> { + lines: LinesIter<T>, + width: usize, + indent_top: usize, + indent_left: Option<usize>, + alignh: AlignmentHorizontal, + fmt: Formatting, + pad: Sides<Indent>, + pad_color: Sides<Option<AnsiColor<'static>>>, + color: Option<C>, + justification: (char, Option<AnsiColor<'static>>), +} + +impl<T, C> Cell<T, C> +where + T: AsRef<str>, +{ + fn new( + text: T, + width: usize, + height: usize, + cfg: &SpannedConfig, + color: Option<C>, + pos: Position, + ) -> Cell<T, C> { + let fmt = *cfg.get_formatting(pos.into()); + let pad = cfg.get_padding(pos.into()); + let pad_color = cfg.get_padding_color(pos.into()).clone(); + let alignh = *cfg.get_alignment_horizontal(pos.into()); + let alignv = *cfg.get_alignment_vertical(pos.into()); + let justification = ( + cfg.get_justification(pos.into()), + cfg.get_justification_color(pos.into()).cloned(), + ); + + let (count_lines, skip) = if fmt.vertical_trim { + let (len, top, _) = count_empty_lines(text.as_ref()); + (len, top) + } else { + (count_lines(text.as_ref()), 0) + }; + + let indent_top = top_indent(&pad, alignv, count_lines, height); + + let mut indent_left = None; + if !fmt.allow_lines_alignment { + let text_width = get_text_width(text.as_ref(), fmt.horizontal_trim); + let available = width - pad.left.size - pad.right.size; + indent_left = Some(calculate_indent(alignh, text_width, available).0); + } + + let mut lines = LinesIter::new(text); + for _ in 0..skip { + let _ = lines.lines.next(); + } + + Self { + lines, + indent_left, + indent_top, + width, + alignh, + fmt, + pad, + pad_color, + color, + justification, + } + } +} + +impl<T, C> Cell<T, C> +where + C: Color, +{ + fn display<F: Write>(&mut self, f: &mut F) -> fmt::Result { + if self.indent_top > 0 { + self.indent_top -= 1; + print_padding_n(f, &self.pad.top, self.pad_color.top.as_ref(), self.width)?; + return Ok(()); + } + + let line = match self.lines.lines.next() { + Some(line) => line, + None => { + let color = self.pad_color.bottom.as_ref(); + print_padding_n(f, &self.pad.bottom, color, self.width)?; + return Ok(()); + } + }; + + let line = if self.fmt.horizontal_trim && !line.is_empty() { + string_trim(&line) + } else { + line + }; + + let line_width = string_width(&line); + let available_width = self.width - self.pad.left.size - self.pad.right.size; + + let (left, right) = if self.fmt.allow_lines_alignment { + calculate_indent(self.alignh, line_width, available_width) + } else { + let left = self.indent_left.expect("must be here"); + (left, available_width - line_width - left) + }; + + let (justification, justification_color) = + (self.justification.0, self.justification.1.as_ref()); + + print_padding(f, &self.pad.left, self.pad_color.left.as_ref())?; + + print_indent(f, justification, left, justification_color)?; + print_text(f, &line, self.color.as_ref())?; + print_indent(f, justification, right, justification_color)?; + + print_padding(f, &self.pad.right, self.pad_color.right.as_ref())?; + + Ok(()) + } +} + +struct LinesIter<C> { + _cell: C, + /// SAFETY: IT'S NOT SAFE TO KEEP THE 'static REFERENCES AROUND AS THEY ARE NOT 'static in reality AND WILL BE DROPPED + _text: &'static str, + /// SAFETY: IT'S NOT SAFE TO KEEP THE 'static REFERENCES AROUND AS THEY ARE NOT 'static in reality AND WILL BE DROPPED + lines: Lines<'static>, +} + +impl<C> LinesIter<C> { + fn new(cell: C) -> Self + where + C: AsRef<str>, + { + // We want to not allocate a String/Vec. + // It's currently not possible due to a lifetime issues. (It's known as self-referential struct) + // + // Here we change the lifetime of text. + // + // # Safety + // + // It must be safe because the referenced string and the references are dropped at the same time. + // And the referenced String is guaranteed to not be changed. + let text = cell.as_ref(); + let text = unsafe { + std::str::from_utf8_unchecked(std::slice::from_raw_parts(text.as_ptr(), text.len())) + }; + + let lines = get_lines(text); + + Self { + _cell: cell, + _text: text, + lines, + } + } +} + +fn print_text<F: Write>(f: &mut F, text: &str, clr: Option<impl Color>) -> fmt::Result { + match clr { + Some(color) => { + color.fmt_prefix(f)?; + f.write_str(text)?; + color.fmt_suffix(f) + } + None => f.write_str(text), + } +} + +fn prepare_coloring<'a, 'b, F: Write>( + f: &mut F, + clr: Option<&'a AnsiColor<'b>>, + used_color: &mut Option<&'a AnsiColor<'b>>, +) -> fmt::Result { + match clr { + Some(clr) => match used_color.as_mut() { + Some(used_clr) => { + if **used_clr != *clr { + used_clr.fmt_suffix(f)?; + clr.fmt_prefix(f)?; + *used_clr = clr; + } + } + None => { + clr.fmt_prefix(f)?; + *used_color = Some(clr); + } + }, + None => { + if let Some(clr) = used_color.take() { + clr.fmt_suffix(f)? + } + } + } + + Ok(()) +} + +fn top_indent( + padding: &Sides<Indent>, + alignment: AlignmentVertical, + cell_height: usize, + available: usize, +) -> usize { + let height = available - padding.top.size; + let indent = indent_from_top(alignment, height, cell_height); + + indent + padding.top.size +} + +fn indent_from_top(alignment: AlignmentVertical, available: usize, real: usize) -> usize { + match alignment { + AlignmentVertical::Top => 0, + AlignmentVertical::Bottom => available - real, + AlignmentVertical::Center => (available - real) / 2, + } +} + +fn calculate_indent( + alignment: AlignmentHorizontal, + text_width: usize, + available: usize, +) -> (usize, usize) { + let diff = available - text_width; + match alignment { + AlignmentHorizontal::Left => (0, diff), + AlignmentHorizontal::Right => (diff, 0), + AlignmentHorizontal::Center => { + let left = diff / 2; + let rest = diff - left; + (left, rest) + } + } +} + +fn repeat_char<F: Write>(f: &mut F, c: char, n: usize) -> fmt::Result { + for _ in 0..n { + f.write_char(c)?; + } + + Ok(()) +} + +fn print_vertical_char<F: Write>( + f: &mut F, + cfg: &SpannedConfig, + pos: Position, + line: usize, + count_lines: usize, + count_columns: usize, +) -> fmt::Result { + let symbol = match cfg.get_vertical(pos, count_columns) { + Some(c) => c, + None => return Ok(()), + }; + + let symbol = cfg + .is_overridden_vertical(pos) + .then(|| cfg.lookup_vertical_char(pos, line, count_lines)) + .flatten() + .unwrap_or(symbol); + + match cfg.get_vertical_color(pos, count_columns) { + Some(clr) => { + clr.fmt_prefix(f)?; + f.write_char(symbol)?; + clr.fmt_suffix(f)?; + } + None => f.write_char(symbol)?, + } + + Ok(()) +} + +fn print_margin_top<F: Write>(f: &mut F, cfg: &SpannedConfig, width: usize) -> fmt::Result { + let indent = cfg.get_margin().top; + let offset = cfg.get_margin_offset().top; + let color = cfg.get_margin_color(); + let color = color.top.as_ref(); + print_indent_lines(f, &indent, &offset, color, width) +} + +fn print_margin_bottom<F: Write>(f: &mut F, cfg: &SpannedConfig, width: usize) -> fmt::Result { + let indent = cfg.get_margin().bottom; + let offset = cfg.get_margin_offset().bottom; + let color = cfg.get_margin_color(); + let color = color.bottom.as_ref(); + print_indent_lines(f, &indent, &offset, color, width) +} + +fn print_margin_left<F: Write>( + f: &mut F, + cfg: &SpannedConfig, + line: usize, + height: Option<usize>, +) -> fmt::Result { + let indent = cfg.get_margin().left; + let offset = cfg.get_margin_offset().left; + let color = cfg.get_margin_color(); + let color = color.left.as_ref(); + print_margin_vertical(f, indent, offset, color, line, height) +} + +fn print_margin_right<F: Write>( + f: &mut F, + cfg: &SpannedConfig, + line: usize, + height: Option<usize>, +) -> fmt::Result { + let indent = cfg.get_margin().right; + let offset = cfg.get_margin_offset().right; + let color = cfg.get_margin_color(); + let color = color.right.as_ref(); + print_margin_vertical(f, indent, offset, color, line, height) +} + +fn print_margin_vertical<F: Write>( + f: &mut F, + indent: Indent, + offset: Offset, + color: Option<&AnsiColor<'_>>, + line: usize, + height: Option<usize>, +) -> fmt::Result { + if indent.size == 0 { + return Ok(()); + } + + match offset { + Offset::Begin(mut offset) => { + if let Some(max) = height { + offset = cmp::min(offset, max); + } + + if line >= offset { + print_indent(f, indent.fill, indent.size, color)?; + } else { + repeat_char(f, ' ', indent.size)?; + } + } + Offset::End(mut offset) => { + if let Some(max) = height { + offset = cmp::min(offset, max); + let pos = max - offset; + + if line >= pos { + repeat_char(f, ' ', indent.size)?; + } else { + print_indent(f, indent.fill, indent.size, color)?; + } + } else { + print_indent(f, indent.fill, indent.size, color)?; + } + } + } + + Ok(()) +} + +fn print_indent_lines<F: Write>( + f: &mut F, + indent: &Indent, + offset: &Offset, + color: Option<&AnsiColor<'_>>, + width: usize, +) -> fmt::Result { + if indent.size == 0 { + return Ok(()); + } + + let (start_offset, end_offset) = match offset { + Offset::Begin(start) => (*start, 0), + Offset::End(end) => (0, *end), + }; + + let start_offset = std::cmp::min(start_offset, width); + let end_offset = std::cmp::min(end_offset, width); + let indent_size = width - start_offset - end_offset; + + for i in 0..indent.size { + if start_offset > 0 { + repeat_char(f, ' ', start_offset)?; + } + + if indent_size > 0 { + print_indent(f, indent.fill, indent_size, color)?; + } + + if end_offset > 0 { + repeat_char(f, ' ', end_offset)?; + } + + if i + 1 != indent.size { + f.write_char('\n')?; + } + } + + Ok(()) +} + +fn print_padding<F: Write>(f: &mut F, pad: &Indent, color: Option<&AnsiColor<'_>>) -> fmt::Result { + print_indent(f, pad.fill, pad.size, color) +} + +fn print_padding_n<F: Write>( + f: &mut F, + pad: &Indent, + color: Option<&AnsiColor<'_>>, + n: usize, +) -> fmt::Result { + print_indent(f, pad.fill, n, color) +} + +fn print_indent<F: Write>( + f: &mut F, + c: char, + n: usize, + color: Option<&AnsiColor<'_>>, +) -> fmt::Result { + if n == 0 { + return Ok(()); + } + + match color { + Some(color) => { + color.fmt_prefix(f)?; + repeat_char(f, c, n)?; + color.fmt_suffix(f) + } + None => repeat_char(f, c, n), + } +} + +fn range_width( + cfg: &SpannedConfig, + d: impl Dimension, + start: usize, + end: usize, + max: usize, +) -> usize { + let count_borders = count_verticals_in_range(cfg, start, end, max); + let range_width = (start..end).map(|col| d.get_width(col)).sum::<usize>(); + + count_borders + range_width +} + +fn range_height( + cfg: &SpannedConfig, + d: impl Dimension, + from: usize, + end: usize, + max: usize, +) -> usize { + let count_borders = count_horizontals_in_range(cfg, from, end, max); + let range_width = (from..end).map(|col| d.get_height(col)).sum::<usize>(); + + count_borders + range_width +} + +fn count_horizontals_in_range(cfg: &SpannedConfig, from: usize, end: usize, max: usize) -> usize { + (from + 1..end) + .map(|i| cfg.has_horizontal(i, max) as usize) + .sum() +} + +fn count_verticals_in_range(cfg: &SpannedConfig, start: usize, end: usize, max: usize) -> usize { + (start..end) + .skip(1) + .map(|i| cfg.has_vertical(i, max) as usize) + .sum() +} + +fn closest_visible_row(cfg: &SpannedConfig, mut pos: Position) -> Option<usize> { + loop { + if cfg.is_cell_visible(pos) { + return Some(pos.0); + } + + if pos.0 == 0 { + return None; + } + + pos.0 -= 1; + } +} + +fn convert_count_rows(row: usize, is_last: bool) -> usize { + if is_last { + row + 1 + } else { + row + 2 + } +} + +/// Trims a string. +fn string_trim(text: &str) -> Cow<'_, str> { + #[cfg(feature = "color")] + { + ansi_str::AnsiStr::ansi_trim(text) + } + + #[cfg(not(feature = "color"))] + { + text.trim().into() + } +} + +fn total_width<D: Dimension>(cfg: &SpannedConfig, dimension: &D, count_columns: usize) -> usize { + (0..count_columns) + .map(|i| dimension.get_width(i)) + .sum::<usize>() + + cfg.count_vertical(count_columns) +} + +fn total_height<D: Dimension>(cfg: &SpannedConfig, dimension: &D, count_rows: usize) -> usize { + (0..count_rows) + .map(|i| dimension.get_height(i)) + .sum::<usize>() + + cfg.count_horizontal(count_rows) +} + +fn count_empty_lines(cell: &str) -> (usize, usize, usize) { + let mut len = 0; + let mut top = 0; + let mut bottom = 0; + let mut top_check = true; + + for line in get_lines(cell) { + let is_empty = line.trim().is_empty(); + if top_check { + if is_empty { + top += 1; + } else { + len = 1; + top_check = false; + } + + continue; + } + + if is_empty { + bottom += 1; + } else { + len += bottom + 1; + bottom = 0; + } + } + + (len, top, bottom) +} + +fn get_text_width(text: &str, trim: bool) -> usize { + if trim { + get_lines(text) + .map(|line| string_width(line.trim())) + .max() + .unwrap_or(0) + } else { + string_width_multiline(text) + } +} + +#[cfg(test)] +mod tests { + // use crate::util::string_width; + + use super::*; + + // #[test] + // fn horizontal_alignment_test() { + // use std::fmt; + + // struct F<'a>(&'a str, AlignmentHorizontal, usize); + + // impl fmt::Display for F<'_> { + // fn fmt(&self, f: &mut impl fmt::Write) -> fmt::Result { + // let (left, right) = calculate_indent(self.1, string_width(self.0), self.2); + // print_text_formatted(f, &self.0, 4, Option::<&AnsiColor<'_>>::None) + // } + // } + + // assert_eq!(F("AAA", AlignmentHorizontal::Right, 4).to_string(), " AAA"); + // assert_eq!(F("AAA", AlignmentHorizontal::Left, 4).to_string(), "AAA "); + // assert_eq!(F("AAA", AlignmentHorizontal::Center, 4).to_string(), "AAA "); + // assert_eq!(F("🎩", AlignmentHorizontal::Center, 4).to_string(), " 🎩 "); + // assert_eq!(F("🎩", AlignmentHorizontal::Center, 3).to_string(), "🎩 "); + + // #[cfg(feature = "color")] + // { + // use owo_colors::OwoColorize; + // let text = "Colored Text".red().to_string(); + // assert_eq!( + // F(&text, AlignmentHorizontal::Center, 15).to_string(), + // format!(" {} ", text) + // ); + // } + // } + + #[test] + fn vertical_alignment_test() { + use AlignmentVertical::*; + + assert_eq!(indent_from_top(Bottom, 1, 1), 0); + assert_eq!(indent_from_top(Top, 1, 1), 0); + assert_eq!(indent_from_top(Center, 1, 1), 0); + assert_eq!(indent_from_top(Bottom, 3, 1), 2); + assert_eq!(indent_from_top(Top, 3, 1), 0); + assert_eq!(indent_from_top(Center, 3, 1), 1); + assert_eq!(indent_from_top(Center, 4, 1), 1); + } + + #[test] + fn count_empty_lines_test() { + assert_eq!(count_empty_lines("\n\nsome text\n\n\n"), (1, 2, 3)); + assert_eq!(count_empty_lines("\n\nsome\ntext\n\n\n"), (2, 2, 3)); + assert_eq!(count_empty_lines("\n\nsome\nsome\ntext\n\n\n"), (3, 2, 3)); + assert_eq!(count_empty_lines("\n\n\n\n"), (0, 5, 0)); + } +} diff --git a/vendor/papergrid/src/grid/mod.rs b/vendor/papergrid/src/grid/mod.rs new file mode 100644 index 000000000..ba3db33bd --- /dev/null +++ b/vendor/papergrid/src/grid/mod.rs @@ -0,0 +1,9 @@ +//! Module contains a list of backends for pretty print tables. + +pub mod compact; + +#[cfg(feature = "std")] +pub mod iterable; + +#[cfg(feature = "std")] +pub mod peekable; diff --git a/vendor/papergrid/src/grid/peekable.rs b/vendor/papergrid/src/grid/peekable.rs new file mode 100644 index 000000000..ee7877293 --- /dev/null +++ b/vendor/papergrid/src/grid/peekable.rs @@ -0,0 +1,976 @@ +//! The module contains a [`PeekableGrid`] structure. + +use core::borrow::Borrow; +use std::{ + borrow::Cow, + cmp, + fmt::{self, Write}, +}; + +use crate::{ + color::{AnsiColor, Color}, + colors::Colors, + config::spanned::{Formatting, Offset, SpannedConfig}, + config::{AlignmentHorizontal, AlignmentVertical, Indent, Position, Sides}, + dimension::Dimension, + records::{ExactRecords, PeekableRecords, Records}, + util::string::string_width, +}; + +/// Grid provides a set of methods for building a text-based table. +#[derive(Debug, Clone)] +pub struct PeekableGrid<R, G, D, C> { + records: R, + config: G, + dimension: D, + colors: C, +} + +impl<R, G, D, C> PeekableGrid<R, G, D, C> { + /// The new method creates a grid instance with default styles. + pub fn new(records: R, config: G, dimension: D, colors: C) -> Self { + PeekableGrid { + records, + config, + dimension, + colors, + } + } +} + +impl<R, G, D, C> PeekableGrid<R, G, D, C> { + /// Builds a table. + pub fn build<F>(self, mut f: F) -> fmt::Result + where + R: Records + PeekableRecords + ExactRecords, + D: Dimension, + C: Colors, + G: Borrow<SpannedConfig>, + F: Write, + { + if self.records.count_columns() == 0 || self.records.hint_count_rows() == Some(0) { + return Ok(()); + } + + let config = self.config.borrow(); + print_grid(&mut f, self.records, config, &self.dimension, &self.colors) + } + + /// Builds a table into string. + /// + /// Notice that it consumes self. + #[allow(clippy::inherent_to_string)] + pub fn to_string(self) -> String + where + R: Records + PeekableRecords + ExactRecords, + D: Dimension, + G: Borrow<SpannedConfig>, + C: Colors, + { + let mut buf = String::new(); + self.build(&mut buf).expect("It's guaranteed to never happen otherwise it's considered an stdlib error or impl error"); + buf + } +} + +fn print_grid<F: Write, R: Records + PeekableRecords + ExactRecords, D: Dimension, C: Colors>( + f: &mut F, + records: R, + cfg: &SpannedConfig, + dimension: &D, + colors: &C, +) -> fmt::Result { + if cfg.has_column_spans() || cfg.has_row_spans() { + build_grid_spanned(f, &records, cfg, dimension, colors) + } else { + build_grid(f, &records, cfg, dimension, colors) + } +} + +fn build_grid<F: Write, R: Records + PeekableRecords + ExactRecords, D: Dimension, C: Colors>( + f: &mut F, + records: &R, + cfg: &SpannedConfig, + dimension: &D, + colors: &C, +) -> fmt::Result { + let shape = (records.count_rows(), records.count_columns()); + + let total_width = total_width(cfg, dimension, shape.1); + let total_width_with_margin = + total_width + cfg.get_margin().left.size + cfg.get_margin().right.size; + + let total_height = total_height(cfg, dimension, shape.0); + + if cfg.get_margin().top.size > 0 { + print_margin_top(f, cfg, total_width_with_margin)?; + f.write_char('\n')?; + } + + let mut table_line = 0; + let mut prev_empty_horizontal = false; + for row in 0..shape.0 { + let height = dimension.get_height(row); + + if cfg.has_horizontal(row, shape.0) { + if prev_empty_horizontal { + f.write_char('\n')?; + } + + print_margin_left(f, cfg, table_line, total_height)?; + print_split_line(f, cfg, dimension, row, shape)?; + print_margin_right(f, cfg, table_line, total_height)?; + + if height > 0 { + f.write_char('\n')?; + prev_empty_horizontal = false; + } else { + prev_empty_horizontal = true; + } + + table_line += 1; + } else if height > 0 && prev_empty_horizontal { + f.write_char('\n')?; + prev_empty_horizontal = false; + } + + for i in 0..height { + print_margin_left(f, cfg, table_line, total_height)?; + + for col in 0..records.count_columns() { + print_vertical_char(f, cfg, (row, col), i, height, shape.1)?; + + let width = dimension.get_width(col); + print_cell_line(f, records, cfg, colors, width, height, (row, col), i)?; + + let is_last_column = col + 1 == records.count_columns(); + if is_last_column { + print_vertical_char(f, cfg, (row, col + 1), i, height, shape.1)?; + } + } + + print_margin_right(f, cfg, table_line, total_height)?; + + let is_last_line = i + 1 == height; + let is_last_row = row + 1 == records.count_rows(); + if !(is_last_line && is_last_row) { + f.write_char('\n')?; + } + + table_line += 1; + } + } + + if cfg.has_horizontal(shape.0, shape.0) { + f.write_char('\n')?; + print_margin_left(f, cfg, table_line, total_height)?; + print_split_line(f, cfg, dimension, records.count_rows(), shape)?; + print_margin_right(f, cfg, table_line, total_height)?; + } + + if cfg.get_margin().bottom.size > 0 { + f.write_char('\n')?; + print_margin_bottom(f, cfg, total_width_with_margin)?; + } + + Ok(()) +} + +fn print_split_line<F: Write, D: Dimension>( + f: &mut F, + cfg: &SpannedConfig, + dimension: &D, + row: usize, + shape: (usize, usize), +) -> fmt::Result { + let mut used_color = None; + print_vertical_intersection(f, cfg, (row, 0), shape, &mut used_color)?; + + for col in 0..shape.1 { + let width = dimension.get_width(col); + + // general case + if width > 0 { + let pos = (row, col); + let main = cfg.get_horizontal(pos, shape.0); + match main { + Some(c) => { + let clr = cfg.get_horizontal_color(pos, shape.0); + prepare_coloring(f, clr, &mut used_color)?; + print_horizontal_border(f, cfg, pos, width, c, &used_color)?; + } + None => repeat_char(f, ' ', width)?, + } + } + + print_vertical_intersection(f, cfg, (row, col + 1), shape, &mut used_color)?; + } + + if let Some(clr) = used_color.take() { + clr.fmt_suffix(f)?; + } + + Ok(()) +} + +fn print_vertical_intersection<'a, F: fmt::Write>( + f: &mut F, + cfg: &'a SpannedConfig, + pos: Position, + shape: (usize, usize), + used_color: &mut Option<&'a AnsiColor<'static>>, +) -> fmt::Result { + match cfg.get_intersection(pos, shape) { + Some(c) => { + let clr = cfg.get_intersection_color(pos, shape); + prepare_coloring(f, clr, used_color)?; + f.write_char(c) + } + None => Ok(()), + } +} + +fn prepare_coloring<'a, 'b, F: Write>( + f: &mut F, + clr: Option<&'a AnsiColor<'b>>, + used_color: &mut Option<&'a AnsiColor<'b>>, +) -> fmt::Result { + match clr { + Some(clr) => match used_color.as_mut() { + Some(used_clr) => { + if **used_clr != *clr { + used_clr.fmt_suffix(f)?; + clr.fmt_prefix(f)?; + *used_clr = clr; + } + } + None => { + clr.fmt_prefix(f)?; + *used_color = Some(clr); + } + }, + None => { + if let Some(clr) = used_color.take() { + clr.fmt_suffix(f)? + } + } + } + + Ok(()) +} + +fn print_vertical_char<F: Write>( + f: &mut F, + cfg: &SpannedConfig, + pos: Position, + line: usize, + count_lines: usize, + count_columns: usize, +) -> fmt::Result { + let symbol = match cfg.get_vertical(pos, count_columns) { + Some(c) => c, + None => return Ok(()), + }; + + let symbol = cfg + .is_overridden_vertical(pos) + .then(|| cfg.lookup_vertical_char(pos, line, count_lines)) + .flatten() + .unwrap_or(symbol); + + match cfg.get_vertical_color(pos, count_columns) { + Some(clr) => { + clr.fmt_prefix(f)?; + f.write_char(symbol)?; + clr.fmt_suffix(f)?; + } + None => f.write_char(symbol)?, + } + + Ok(()) +} + +fn build_grid_spanned< + F: Write, + R: Records + PeekableRecords + ExactRecords, + D: Dimension, + C: Colors, +>( + f: &mut F, + records: &R, + cfg: &SpannedConfig, + dims: &D, + colors: &C, +) -> fmt::Result { + let shape = (records.count_rows(), records.count_columns()); + + let total_width = total_width(cfg, dims, shape.1); + let total_width_with_margin = + total_width + cfg.get_margin().left.size + cfg.get_margin().right.size; + + let total_height = total_height(cfg, dims, shape.0); + + if cfg.get_margin().top.size > 0 { + print_margin_top(f, cfg, total_width_with_margin)?; + f.write_char('\n')?; + } + + let mut table_line = 0; + let mut prev_empty_horizontal = false; + for row in 0..records.count_rows() { + let count_lines = dims.get_height(row); + + if cfg.has_horizontal(row, shape.0) { + if prev_empty_horizontal { + f.write_char('\n')?; + } + + print_margin_left(f, cfg, table_line, total_height)?; + print_split_line_spanned(f, records, cfg, dims, colors, row, shape)?; + print_margin_right(f, cfg, table_line, total_height)?; + + if count_lines > 0 { + f.write_char('\n')?; + prev_empty_horizontal = false; + } else { + prev_empty_horizontal = true; + } + + table_line += 1; + } else if count_lines > 0 && prev_empty_horizontal { + f.write_char('\n')?; + prev_empty_horizontal = false; + } + + for i in 0..count_lines { + print_margin_left(f, cfg, table_line, total_height)?; + + for col in 0..records.count_columns() { + if cfg.is_cell_covered_by_both_spans((row, col)) { + continue; + } + + if cfg.is_cell_covered_by_column_span((row, col)) { + let is_last_column = col + 1 == records.count_columns(); + if is_last_column { + print_vertical_char(f, cfg, (row, col + 1), i, count_lines, shape.1)?; + } + + continue; + } + + print_vertical_char(f, cfg, (row, col), i, count_lines, shape.1)?; + + if cfg.is_cell_covered_by_row_span((row, col)) { + // means it's part of other a spanned cell + // so. we just need to use line from other cell. + let original_row = closest_visible_row(cfg, (row, col)).unwrap(); + + // considering that the content will be printed instead horizontal lines so we can skip some lines. + let mut skip_lines = (original_row..row) + .map(|i| dims.get_height(i)) + .sum::<usize>(); + + skip_lines += (original_row + 1..=row) + .map(|row| cfg.has_horizontal(row, shape.0) as usize) + .sum::<usize>(); + + let line = i + skip_lines; + let pos = (original_row, col); + + let width = get_cell_width(cfg, dims, pos, shape.1); + let height = get_cell_height(cfg, dims, pos, shape.0); + + print_cell_line(f, records, cfg, colors, width, height, pos, line)?; + } else { + let width = get_cell_width(cfg, dims, (row, col), shape.1); + let height = get_cell_height(cfg, dims, (row, col), shape.0); + print_cell_line(f, records, cfg, colors, width, height, (row, col), i)?; + } + + let is_last_column = col + 1 == records.count_columns(); + if is_last_column { + print_vertical_char(f, cfg, (row, col + 1), i, count_lines, shape.1)?; + } + } + + print_margin_right(f, cfg, table_line, total_height)?; + + let is_last_line = i + 1 == count_lines; + let is_last_row = row + 1 == records.count_rows(); + if !(is_last_line && is_last_row) { + f.write_char('\n')?; + } + + table_line += 1; + } + } + + if cfg.has_horizontal(shape.0, shape.0) { + f.write_char('\n')?; + print_margin_left(f, cfg, table_line, total_height)?; + print_split_line(f, cfg, dims, records.count_rows(), shape)?; + print_margin_right(f, cfg, table_line, total_height)?; + } + + if cfg.get_margin().bottom.size > 0 { + f.write_char('\n')?; + print_margin_bottom(f, cfg, total_width_with_margin)?; + } + + Ok(()) +} + +fn print_split_line_spanned< + F: Write, + R: Records + ExactRecords + PeekableRecords, + D: Dimension, + C: Colors, +>( + f: &mut F, + records: &R, + cfg: &SpannedConfig, + dims: &D, + colors: &C, + row: usize, + shape: (usize, usize), +) -> fmt::Result { + let mut used_color = None; + print_vertical_intersection(f, cfg, (row, 0), shape, &mut used_color)?; + + for col in 0..shape.1 { + let pos = (row, col); + if cfg.is_cell_covered_by_both_spans(pos) { + continue; + } + + if cfg.is_cell_covered_by_row_span(pos) { + // means it's part of other a spanned cell + // so. we just need to use line from other cell. + + let original_row = closest_visible_row(cfg, (row, col)).unwrap(); + + // considering that the content will be printed instead horizontal lines so we can skip some lines. + let mut skip_lines = (original_row..row) + .map(|i| dims.get_height(i)) + .sum::<usize>(); + + // skip horizontal lines + if row > 0 { + skip_lines += (original_row..row - 1) + .map(|row| cfg.has_horizontal(row + 1, shape.0) as usize) + .sum::<usize>(); + } + + let pos = (original_row, col); + let height = get_cell_height(cfg, dims, pos, shape.0); + let width = get_cell_width(cfg, dims, pos, shape.1); + let line = skip_lines; + + print_cell_line(f, records, cfg, colors, width, height, pos, line)?; + + // We need to use a correct right split char. + let mut col = col; + if let Some(span) = cfg.get_column_span(pos) { + col += span - 1; + } + + print_vertical_intersection(f, cfg, (row, col + 1), shape, &mut used_color)?; + + continue; + } + + let width = dims.get_width(col); + if width > 0 { + // general case + let main = cfg.get_horizontal(pos, shape.0); + match main { + Some(c) => { + let clr = cfg.get_horizontal_color(pos, shape.0); + prepare_coloring(f, clr, &mut used_color)?; + print_horizontal_border(f, cfg, pos, width, c, &used_color)?; + } + None => repeat_char(f, ' ', width)?, + } + } + + print_vertical_intersection(f, cfg, (row, col + 1), shape, &mut used_color)?; + } + + if let Some(clr) = used_color { + clr.fmt_suffix(f)?; + } + + Ok(()) +} + +fn print_horizontal_border<F: Write>( + f: &mut F, + cfg: &SpannedConfig, + pos: Position, + width: usize, + c: char, + used_color: &Option<&AnsiColor<'static>>, +) -> fmt::Result { + if !cfg.is_overridden_horizontal(pos) { + return repeat_char(f, c, width); + } + + for i in 0..width { + let c = cfg.lookup_horizontal_char(pos, i, width).unwrap_or(c); + match cfg.lookup_horizontal_color(pos, i, width) { + Some(color) => match used_color { + Some(clr) => { + clr.fmt_suffix(f)?; + color.fmt_prefix(f)?; + f.write_char(c)?; + color.fmt_suffix(f)?; + clr.fmt_prefix(f)?; + } + None => { + color.fmt_prefix(f)?; + f.write_char(c)?; + color.fmt_suffix(f)?; + } + }, + _ => f.write_char(c)?, + } + } + + Ok(()) +} + +#[allow(clippy::too_many_arguments)] +fn print_cell_line<F: Write, R: Records + PeekableRecords + ExactRecords, C: Colors>( + f: &mut F, + records: &R, + cfg: &SpannedConfig, + colors: &C, + width: usize, + height: usize, + pos: Position, + line: usize, +) -> fmt::Result { + let entity = pos.into(); + + let mut cell_height = records.count_lines(pos); + let formatting = *cfg.get_formatting(entity); + if formatting.vertical_trim { + cell_height -= + count_empty_lines_at_start(records, pos) + count_empty_lines_at_end(records, pos); + } + + if cell_height > height { + // it may happen if the height estimation decide so + cell_height = height; + } + + let pad = cfg.get_padding(entity); + let pad_color = cfg.get_padding_color(entity); + let alignment = cfg.get_alignment_vertical(entity); + let indent = top_indent(&pad, *alignment, cell_height, height); + if indent > line { + return print_indent(f, pad.top.fill, width, pad_color.top.as_ref()); + } + + let mut index = line - indent; + let cell_has_this_line = cell_height > index; + if !cell_has_this_line { + // happens when other cells have bigger height + return print_indent(f, pad.bottom.fill, width, pad_color.bottom.as_ref()); + } + + if formatting.vertical_trim { + let empty_lines = count_empty_lines_at_start(records, pos); + index += empty_lines; + + if index > records.count_lines(pos) { + return print_indent(f, pad.top.fill, width, pad_color.top.as_ref()); + } + } + + print_indent(f, pad.left.fill, pad.left.size, pad_color.left.as_ref())?; + + let width = width - pad.left.size - pad.right.size; + let alignment = *cfg.get_alignment_horizontal(entity); + let justification = ( + cfg.get_justification(entity), + cfg.get_justification_color(entity), + ); + let color = colors.get_color(pos); + print_line( + f, + records, + pos, + index, + alignment, + formatting, + color, + justification, + width, + )?; + + print_indent(f, pad.right.fill, pad.right.size, pad_color.right.as_ref())?; + + Ok(()) +} + +#[allow(clippy::too_many_arguments)] +fn print_line<F: Write, R: Records + PeekableRecords, C: Color>( + f: &mut F, + records: &R, + pos: Position, + index: usize, + alignment: AlignmentHorizontal, + formatting: Formatting, + color: Option<C>, + justification: (char, Option<&AnsiColor<'_>>), + available: usize, +) -> fmt::Result { + let line = records.get_line(pos, index); + let (line, line_width) = if formatting.horizontal_trim { + let line = string_trim(line); + let width = string_width(&line); + (line, width) + } else { + let width = records.get_line_width(pos, index); + (Cow::Borrowed(line), width) + }; + + if formatting.allow_lines_alignment { + let (left, right) = calculate_indent(alignment, line_width, available); + return print_text_with_pad(f, &line, color, justification, left, right); + } + + let cell_width = if formatting.horizontal_trim { + (0..records.count_lines(pos)) + .map(|i| records.get_line(pos, i)) + .map(|line| string_width(line.trim())) + .max() + .unwrap_or_default() + } else { + records.get_width(pos) + }; + + let (left, right) = calculate_indent(alignment, cell_width, available); + print_text_with_pad(f, &line, color, justification, left, right)?; + + // todo: remove me + let rest_width = cell_width - line_width; + repeat_char(f, ' ', rest_width)?; + + Ok(()) +} + +fn print_text_with_pad<F: Write, C: Color>( + f: &mut F, + text: &str, + color: Option<C>, + justification: (char, Option<&AnsiColor<'_>>), + left: usize, + right: usize, +) -> fmt::Result { + print_indent(f, justification.0, left, justification.1)?; + print_text(f, text, color)?; + print_indent(f, justification.0, right, justification.1)?; + Ok(()) +} + +fn print_text<F: Write, C: Color>(f: &mut F, text: &str, clr: Option<C>) -> fmt::Result { + match clr { + Some(color) => { + color.fmt_prefix(f)?; + f.write_str(text)?; + color.fmt_suffix(f) + } + None => f.write_str(text), + } +} + +fn top_indent( + pad: &Sides<Indent>, + alignment: AlignmentVertical, + cell_height: usize, + available: usize, +) -> usize { + let height = available - pad.top.size; + let indent = indent_from_top(alignment, height, cell_height); + + indent + pad.top.size +} + +fn indent_from_top(alignment: AlignmentVertical, available: usize, real: usize) -> usize { + match alignment { + AlignmentVertical::Top => 0, + AlignmentVertical::Bottom => available - real, + AlignmentVertical::Center => (available - real) / 2, + } +} + +fn calculate_indent( + alignment: AlignmentHorizontal, + text_width: usize, + available: usize, +) -> (usize, usize) { + let diff = available - text_width; + match alignment { + AlignmentHorizontal::Left => (0, diff), + AlignmentHorizontal::Right => (diff, 0), + AlignmentHorizontal::Center => { + let left = diff / 2; + let rest = diff - left; + (left, rest) + } + } +} + +fn repeat_char<F: Write>(f: &mut F, c: char, n: usize) -> fmt::Result { + for _ in 0..n { + f.write_char(c)?; + } + + Ok(()) +} + +fn count_empty_lines_at_end<R>(records: &R, pos: Position) -> usize +where + R: Records + PeekableRecords, +{ + (0..records.count_lines(pos)) + .map(|i| records.get_line(pos, i)) + .rev() + .take_while(|l| l.trim().is_empty()) + .count() +} + +fn count_empty_lines_at_start<R>(records: &R, pos: Position) -> usize +where + R: Records + PeekableRecords, +{ + (0..records.count_lines(pos)) + .map(|i| records.get_line(pos, i)) + .take_while(|s| s.trim().is_empty()) + .count() +} + +fn total_width<D: Dimension>(cfg: &SpannedConfig, dimension: &D, count_columns: usize) -> usize { + (0..count_columns) + .map(|i| dimension.get_width(i)) + .sum::<usize>() + + cfg.count_vertical(count_columns) +} + +fn total_height<D: Dimension>(cfg: &SpannedConfig, dimension: &D, count_rows: usize) -> usize { + (0..count_rows) + .map(|i| dimension.get_height(i)) + .sum::<usize>() + + cfg.count_horizontal(count_rows) +} + +fn print_margin_top<F: Write>(f: &mut F, cfg: &SpannedConfig, width: usize) -> fmt::Result { + let indent = cfg.get_margin().top; + let offset = cfg.get_margin_offset().top; + let color = cfg.get_margin_color(); + let color = color.top.as_ref(); + print_indent_lines(f, &indent, &offset, color, width) +} + +fn print_margin_bottom<F: Write>(f: &mut F, cfg: &SpannedConfig, width: usize) -> fmt::Result { + let indent = cfg.get_margin().bottom; + let offset = cfg.get_margin_offset().bottom; + let color = cfg.get_margin_color(); + let color = color.bottom.as_ref(); + print_indent_lines(f, &indent, &offset, color, width) +} + +fn print_margin_left<F: Write>( + f: &mut F, + cfg: &SpannedConfig, + line: usize, + height: usize, +) -> fmt::Result { + let indent = cfg.get_margin().left; + let offset = cfg.get_margin_offset().left; + let color = cfg.get_margin_color(); + let color = color.left.as_ref(); + print_margin_vertical(f, indent, offset, color, line, height) +} + +fn print_margin_right<F: Write>( + f: &mut F, + cfg: &SpannedConfig, + line: usize, + height: usize, +) -> fmt::Result { + let indent = cfg.get_margin().right; + let offset = cfg.get_margin_offset().right; + let color = cfg.get_margin_color(); + let color = color.right.as_ref(); + print_margin_vertical(f, indent, offset, color, line, height) +} + +fn print_margin_vertical<F: Write>( + f: &mut F, + indent: Indent, + offset: Offset, + color: Option<&AnsiColor<'_>>, + line: usize, + height: usize, +) -> fmt::Result { + if indent.size == 0 { + return Ok(()); + } + + match offset { + Offset::Begin(offset) => { + let offset = cmp::min(offset, height); + if line >= offset { + print_indent(f, indent.fill, indent.size, color)?; + } else { + repeat_char(f, ' ', indent.size)?; + } + } + Offset::End(offset) => { + let offset = cmp::min(offset, height); + let pos = height - offset; + + if line >= pos { + repeat_char(f, ' ', indent.size)?; + } else { + print_indent(f, indent.fill, indent.size, color)?; + } + } + } + + Ok(()) +} + +fn print_indent_lines<F: Write>( + f: &mut F, + indent: &Indent, + offset: &Offset, + color: Option<&AnsiColor<'_>>, + width: usize, +) -> fmt::Result { + if indent.size == 0 { + return Ok(()); + } + + let (start_offset, end_offset) = match offset { + Offset::Begin(start) => (*start, 0), + Offset::End(end) => (0, *end), + }; + + let start_offset = std::cmp::min(start_offset, width); + let end_offset = std::cmp::min(end_offset, width); + let indent_size = width - start_offset - end_offset; + + for i in 0..indent.size { + if start_offset > 0 { + repeat_char(f, ' ', start_offset)?; + } + + if indent_size > 0 { + print_indent(f, indent.fill, indent_size, color)?; + } + + if end_offset > 0 { + repeat_char(f, ' ', end_offset)?; + } + + if i + 1 != indent.size { + f.write_char('\n')?; + } + } + + Ok(()) +} + +fn print_indent<F: Write, C: Color>(f: &mut F, c: char, n: usize, color: Option<C>) -> fmt::Result { + if n == 0 { + return Ok(()); + } + + match color { + Some(color) => { + color.fmt_prefix(f)?; + repeat_char(f, c, n)?; + color.fmt_suffix(f) + } + None => repeat_char(f, c, n), + } +} + +fn get_cell_width<D: Dimension>(cfg: &SpannedConfig, dims: &D, pos: Position, max: usize) -> usize { + match cfg.get_column_span(pos) { + Some(span) => { + let start = pos.1; + let end = pos.1 + span; + range_width(dims, start, end) + count_verticals_range(cfg, start, end, max) + } + None => dims.get_width(pos.1), + } +} + +fn range_width<D: Dimension>(dims: &D, start: usize, end: usize) -> usize { + (start..end).map(|col| dims.get_width(col)).sum::<usize>() +} + +fn count_verticals_range(cfg: &SpannedConfig, start: usize, end: usize, max: usize) -> usize { + (start + 1..end) + .map(|i| cfg.has_vertical(i, max) as usize) + .sum() +} + +fn get_cell_height<D: Dimension>( + cfg: &SpannedConfig, + dims: &D, + pos: Position, + max: usize, +) -> usize { + match cfg.get_row_span(pos) { + Some(span) => { + let start = pos.0; + let end = pos.0 + span; + range_height(dims, start, end) + count_horizontals_range(cfg, start, end, max) + } + None => dims.get_height(pos.0), + } +} + +fn range_height<D: Dimension>(dims: &D, start: usize, end: usize) -> usize { + (start..end).map(|col| dims.get_height(col)).sum::<usize>() +} + +fn count_horizontals_range(cfg: &SpannedConfig, start: usize, end: usize, max: usize) -> usize { + (start + 1..end) + .map(|i| cfg.has_horizontal(i, max) as usize) + .sum() +} + +fn closest_visible_row(cfg: &SpannedConfig, mut pos: Position) -> Option<usize> { + loop { + if cfg.is_cell_visible(pos) { + return Some(pos.0); + } + + if pos.0 == 0 { + return None; + } + + pos.0 -= 1; + } +} + +/// Trims a string. +fn string_trim(text: &str) -> Cow<'_, str> { + #[cfg(feature = "color")] + { + ansi_str::AnsiStr::ansi_trim(text) + } + + #[cfg(not(feature = "color"))] + { + text.trim().into() + } +} diff --git a/vendor/papergrid/src/lib.rs b/vendor/papergrid/src/lib.rs new file mode 100644 index 000000000..8da4f14ba --- /dev/null +++ b/vendor/papergrid/src/lib.rs @@ -0,0 +1,87 @@ +#![cfg_attr(not(any(feature = "std", test)), no_std)] +#![warn( + rust_2018_idioms, + rust_2018_compatibility, + rust_2021_compatibility, + missing_debug_implementations, + unreachable_pub, + missing_docs +)] +#![allow(clippy::uninlined_format_args)] +#![deny(unused_must_use)] +#![doc( + html_logo_url = "https://raw.githubusercontent.com/zhiburt/tabled/86ac146e532ce9f7626608d7fd05072123603a2e/assets/tabled-gear.svg" +)] + +//! Papergrid is a library for generating text-based tables. +//! +//! It has relatively low level API. +//! If you're interested in a more friendly one take a look at [`tabled`](https://github.com/zhiburt/tabled). +//! +//! # Example +//! +#![cfg_attr(feature = "std", doc = "```")] +#![cfg_attr(not(feature = "std"), doc = "```ignore")] +//! use papergrid::{ +//! records::IterRecords, +//! dimension::{Estimate}, +//! config::Borders, +//! colors::NoColors, +//! grid::iterable::Grid, +//! config::spanned::SpannedConfig, +//! dimension::spanned::SpannedGridDimension, +//! }; +//! +//! // Creating a borders structure of a grid. +//! let borders = Borders { +//! top: Some('-'), +//! top_left: Some('+'), +//! top_right: Some('+'), +//! top_intersection: Some('+'), +//! bottom: Some('-'), +//! bottom_left: Some('+'), +//! bottom_right: Some('+'), +//! bottom_intersection: Some('+'), +//! horizontal: Some('-'), +//! vertical: Some('|'), +//! left: Some('|'), +//! right: Some('|'), +//! intersection: Some('+'), +//! left_intersection: Some('+'), +//! right_intersection: Some('+'), +//! }; +//! +//! // Creating a grid config. +//! let mut cfg = SpannedConfig::default(); +//! cfg.set_borders(borders); +//! +//! // Creating an actual data for grid. +//! let records = vec![vec!["Hello", "World"], vec!["Hi", "World"]]; +//! let records = IterRecords::new(records, 2, None); +//! +//! // Estimate grid dimension. +//! let mut dimension = SpannedGridDimension::default(); +//! dimension.estimate(&records, &cfg); +//! +//! // Creating a grid. +//! let grid = Grid::new(&records, &dimension, &cfg, NoColors).to_string(); +//! +//! assert_eq!( +//! grid, +//! concat!( +//! "+-----+-----+\n", +//! "|Hello|World|\n", +//! "+-----+-----+\n", +//! "|Hi |World|\n", +//! "+-----+-----+", +//! ), +//! ); +//! ``` + +pub mod color; +pub mod colors; +pub mod config; +pub mod dimension; +pub mod grid; +pub mod records; +pub mod util; diff --git a/vendor/papergrid/src/records/exact_records.rs b/vendor/papergrid/src/records/exact_records.rs new file mode 100644 index 000000000..9a564bc82 --- /dev/null +++ b/vendor/papergrid/src/records/exact_records.rs @@ -0,0 +1,31 @@ +/// [`Records`] extension which guarantees the amount of rows. +/// +/// [`Records`]: crate::records::Records +pub trait ExactRecords { + /// Returns an exact amount of rows in records. + /// + /// It must be guaranteed that an iterator will yield this amount. + fn count_rows(&self) -> usize; +} + +impl<T> ExactRecords for &T +where + T: ExactRecords, +{ + fn count_rows(&self) -> usize { + T::count_rows(self) + } +} + +#[cfg(feature = "std")] +impl<T> ExactRecords for Vec<T> { + fn count_rows(&self) -> usize { + self.len() + } +} + +impl<T> ExactRecords for [T] { + fn count_rows(&self) -> usize { + self.len() + } +} diff --git a/vendor/papergrid/src/records/into_records.rs b/vendor/papergrid/src/records/into_records.rs new file mode 100644 index 000000000..c1de73d9c --- /dev/null +++ b/vendor/papergrid/src/records/into_records.rs @@ -0,0 +1,33 @@ +/// The representation of data, rows and columns of a [`Grid`]. +/// +/// [`Grid`]: crate::grid::iterable::Grid +pub trait IntoRecords { + /// A string representation of a [`Grid`] cell. + /// + /// [`Grid`]: crate::grid::iterable::Grid + type Cell: AsRef<str>; + + /// Cell iterator inside a row. + type IterColumns: IntoIterator<Item = Self::Cell>; + + /// Rows iterator. + type IterRows: IntoIterator<Item = Self::IterColumns>; + + /// Returns an iterator over rows. + fn iter_rows(self) -> Self::IterRows; +} + +impl<T> IntoRecords for T +where + T: IntoIterator, + <T as IntoIterator>::Item: IntoIterator, + <<T as IntoIterator>::Item as IntoIterator>::Item: AsRef<str>, +{ + type Cell = <<T as IntoIterator>::Item as IntoIterator>::Item; + type IterColumns = <T as IntoIterator>::Item; + type IterRows = <T as IntoIterator>::IntoIter; + + fn iter_rows(self) -> Self::IterRows { + self.into_iter() + } +} diff --git a/vendor/papergrid/src/records/iter_records.rs b/vendor/papergrid/src/records/iter_records.rs new file mode 100644 index 000000000..3d4f50754 --- /dev/null +++ b/vendor/papergrid/src/records/iter_records.rs @@ -0,0 +1,87 @@ +use super::{IntoRecords, Records}; + +/// A [Records] implementation for any [IntoIterator]. +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +pub struct IterRecords<I> { + iter: I, + count_columns: usize, + count_rows: Option<usize>, +} + +impl<I> IterRecords<I> { + /// Returns a new [IterRecords] object. + pub const fn new(iter: I, count_columns: usize, count_rows: Option<usize>) -> Self { + Self { + iter, + count_columns, + count_rows, + } + } +} + +impl<I> IntoRecords for IterRecords<I> +where + I: IntoRecords, +{ + type Cell = I::Cell; + type IterColumns = I::IterColumns; + type IterRows = I::IterRows; + + fn iter_rows(self) -> Self::IterRows { + self.iter.iter_rows() + } +} + +// why this does not work? + +// impl<'a, I> IntoRecords for &'a IterRecords<I> +// where +// &'a I: IntoRecords, +// { +// type Cell = <&'a I as IntoRecords>::Cell; +// type IterColumns = <&'a I as IntoRecords>::IterColumns; +// type IterRows = <&'a I as IntoRecords>::IterRows; + +// fn iter_rows(self) -> Self::IterRows { +// // (&self.iter).iter_rows() +// todo!() +// } +// } + +impl<I> Records for IterRecords<I> +where + I: IntoRecords, +{ + type Iter = I; + + fn iter_rows(self) -> <Self::Iter as IntoRecords>::IterRows { + self.iter.iter_rows() + } + + fn count_columns(&self) -> usize { + self.count_columns + } + + fn hint_count_rows(&self) -> Option<usize> { + self.count_rows + } +} + +impl<'a, I> Records for &'a IterRecords<I> +where + &'a I: IntoRecords, +{ + type Iter = &'a I; + + fn iter_rows(self) -> <Self::Iter as IntoRecords>::IterRows { + (&self.iter).iter_rows() + } + + fn count_columns(&self) -> usize { + self.count_columns + } + + fn hint_count_rows(&self) -> Option<usize> { + self.count_rows + } +} diff --git a/vendor/papergrid/src/records/mod.rs b/vendor/papergrid/src/records/mod.rs new file mode 100644 index 000000000..149b6f55f --- /dev/null +++ b/vendor/papergrid/src/records/mod.rs @@ -0,0 +1,31 @@ +//! The module contains a [Records] abstraction of a [`Grid`] trait and its implementers. +//! +//! [`Grid`]: crate::grid::iterable::Grid + +mod exact_records; +mod into_records; +mod iter_records; +mod peekable_records; + +pub use exact_records::ExactRecords; +pub use into_records::IntoRecords; +pub use iter_records::IterRecords; +pub use peekable_records::PeekableRecords; + +#[cfg(feature = "std")] +pub mod vec_records; + +/// Records represents table data. +pub trait Records { + /// Iterator which goes over rows. + type Iter: IntoRecords; + + /// Returns a iterator over rows. + fn iter_rows(self) -> <Self::Iter as IntoRecords>::IterRows; + + /// Returns count of columns in the records. + fn count_columns(&self) -> usize; + + /// Hint amount of rows in the records. + fn hint_count_rows(&self) -> Option<usize>; +} diff --git a/vendor/papergrid/src/records/peekable_records.rs b/vendor/papergrid/src/records/peekable_records.rs new file mode 100644 index 000000000..528ba5f0c --- /dev/null +++ b/vendor/papergrid/src/records/peekable_records.rs @@ -0,0 +1,52 @@ +use crate::config::Position; + +/// The representation of data, rows and columns of a grid. +pub trait PeekableRecords { + /// Returns a text of a cell by an index. + fn get_text(&self, pos: Position) -> &str; + + /// Returns a line of a text of a cell by an index. + fn get_line(&self, pos: Position, line: usize) -> &str { + self.get_text(pos).lines().nth(line).unwrap() + } + + /// Returns an amount of lines of a text of a cell by an index. + fn count_lines(&self, pos: Position) -> usize { + self.get_text(pos).lines().count() + } + + /// Returns a width of a text of a cell by an index. + fn get_width(&self, pos: Position) -> usize { + crate::util::string::string_width_multiline(self.get_text(pos)) + } + + /// Returns a width of line of a text of a cell by an index. + fn get_line_width(&self, pos: Position, line: usize) -> usize { + crate::util::string::string_width(self.get_line(pos, line)) + } +} + +impl<R> PeekableRecords for &R +where + R: PeekableRecords, +{ + fn get_text(&self, pos: Position) -> &str { + R::get_text(self, pos) + } + + fn get_line(&self, pos: Position, line: usize) -> &str { + R::get_line(self, pos, line) + } + + fn count_lines(&self, pos: Position) -> usize { + R::count_lines(self, pos) + } + + fn get_width(&self, pos: Position) -> usize { + R::get_width(self, pos) + } + + fn get_line_width(&self, pos: Position, line: usize) -> usize { + R::get_line_width(self, pos, line) + } +} diff --git a/vendor/papergrid/src/records/vec_records/cell.rs b/vendor/papergrid/src/records/vec_records/cell.rs new file mode 100644 index 000000000..38a36b6e3 --- /dev/null +++ b/vendor/papergrid/src/records/vec_records/cell.rs @@ -0,0 +1,19 @@ +/// Cell implementation which can be used with [`VecRecords`]. +/// +/// [`VecRecords`]: crate::records::vec_records::VecRecords +pub trait Cell { + /// Gets a text. + fn text(&self) -> &str; + + /// Gets a line by index. + fn line(&self, line: usize) -> &str; + + /// Returns a number of lines cell has. + fn count_lines(&self) -> usize; + + /// Returns a width of cell. + fn width(&self) -> usize; + + /// Returns a width of cell line. + fn line_width(&self, line: usize) -> usize; +} diff --git a/vendor/papergrid/src/records/vec_records/cell_info.rs b/vendor/papergrid/src/records/vec_records/cell_info.rs new file mode 100644 index 000000000..d9b0ed71e --- /dev/null +++ b/vendor/papergrid/src/records/vec_records/cell_info.rs @@ -0,0 +1,170 @@ +use std::{borrow::Cow, cmp::max}; + +use crate::{ + records::vec_records::Cell, + util::string::{self, count_lines, get_lines, string_width}, +}; + +/// The struct is a [Cell] implementation which keeps width information pre allocated. +#[derive(Debug, Default, PartialEq, Eq, PartialOrd, Ord)] +pub struct CellInfo<S> { + text: S, + width: usize, + lines: Vec<StrWithWidth<'static>>, +} + +impl<S> CellInfo<S> { + /// Creates a new instance of the structure. + pub fn new(text: S) -> Self + where + S: AsRef<str>, + { + create_cell_info(text) + } + + /// Creates a new instance of the structure with a single line. + pub fn exact(text: S, width: usize, lines: Vec<StrWithWidth<'static>>) -> Self { + Self { text, width, lines } + } + + /// Return a original text value. + pub fn into_inner(self) -> S { + self.text + } +} + +impl<S> AsRef<str> for CellInfo<S> +where + S: AsRef<str>, +{ + fn as_ref(&self) -> &str { + self.text() + } +} + +impl<S> Cell for CellInfo<S> +where + S: AsRef<str>, +{ + fn text(&self) -> &str { + self.text.as_ref() + } + + fn line(&self, i: usize) -> &str { + if i == 0 && self.lines.is_empty() { + return self.text.as_ref(); + } + + &self.lines[i].text + } + + fn count_lines(&self) -> usize { + std::cmp::max(1, self.lines.len()) + } + + fn width(&self) -> usize { + self.width + } + + fn line_width(&self, i: usize) -> usize { + if i == 0 && self.lines.is_empty() { + return self.width; + } + + self.lines[i].width + } +} + +impl<S> Clone for CellInfo<S> +where + S: Clone + AsRef<str>, +{ + fn clone(&self) -> Self { + let mut cell = Self { + text: self.text.clone(), + width: self.width, + lines: vec![StrWithWidth::default(); self.lines.len()], + }; + + for (i, line) in self.lines.iter().enumerate() { + // We need to redirect pointers to the original string. + // + // # Safety + // + // It must be safe because the referenced string and the references are dropped at the same time. + // And the referenced String is guaranteed to not be changed. + let text = unsafe { + let text_ptr = self.text.as_ref().as_ptr(); + let line_ptr = line.text.as_ptr(); + let text_shift = line_ptr as isize - text_ptr as isize; + + let new_text_shifted_ptr = cell.text.as_ref().as_ptr().offset(text_shift); + + std::str::from_utf8_unchecked(std::slice::from_raw_parts( + new_text_shifted_ptr, + line.text.len(), + )) + }; + + cell.lines[i].width = line.width; + cell.lines[i].text = Cow::Borrowed(text); + } + + cell + } +} + +/// StrWithWidth is a structure is responsible for a string and it's width. +#[derive(Debug, Default, PartialEq, Eq, PartialOrd, Ord, Clone)] +pub struct StrWithWidth<'a> { + text: Cow<'a, str>, + width: usize, +} + +impl<'a> StrWithWidth<'a> { + /// Creates a new object. + pub fn new(text: Cow<'a, str>, width: usize) -> Self { + Self { text, width } + } +} + +fn create_cell_info<S: AsRef<str>>(text: S) -> CellInfo<S> { + let mut info = CellInfo { + text, + lines: vec![], + width: 0, + }; + + // Here we do a small optimization. + // We check if there's only 1 line in which case we don't allocate lines Vec + let count_lines = count_lines(info.text.as_ref()); + if count_lines < 2 { + info.width = string::string_width_multiline(info.text.as_ref()); + return info; + } + + // In case `Cow::Borrowed` we want to not allocate a String. + // It's currerently not possible due to a lifetime issues. (It's known as self-referential struct) + // + // Here we change the lifetime of text. + // + // # Safety + // + // It must be safe because the referenced string and the references are dropped at the same time. + // And the referenced String is guaranteed to not be changed. + let text = unsafe { + std::str::from_utf8_unchecked(std::slice::from_raw_parts( + info.text.as_ref().as_ptr(), + info.text.as_ref().len(), + )) + }; + + info.lines = vec![StrWithWidth::new(Cow::Borrowed(""), 0); count_lines]; + for (line, i) in get_lines(text).zip(info.lines.iter_mut()) { + i.width = string_width(&line); + i.text = line; + info.width = max(info.width, i.width); + } + + info +} diff --git a/vendor/papergrid/src/records/vec_records/mod.rs b/vendor/papergrid/src/records/vec_records/mod.rs new file mode 100644 index 000000000..279eb3a7b --- /dev/null +++ b/vendor/papergrid/src/records/vec_records/mod.rs @@ -0,0 +1,124 @@ +//! Module contains [`VecRecords`]. + +mod cell; +mod cell_info; + +use crate::{ + config::Position, + records::{ExactRecords, IntoRecords, Records}, +}; +use std::ops::{Deref, DerefMut}; + +use super::PeekableRecords; + +pub use cell::Cell; +pub use cell_info::{CellInfo, StrWithWidth}; + +/// A [Records] implementation based on allocated buffers. +#[derive(Debug, Clone, Default, PartialEq, Eq, PartialOrd, Ord)] +pub struct VecRecords<T> { + data: Vec<Vec<T>>, + shape: (usize, usize), +} + +impl<T> VecRecords<T> { + /// Creates new [`VecRecords`] structure. + /// + /// It assumes that data vector has all rows has the same length(). + pub fn new(data: Vec<Vec<T>>) -> Self { + let count_columns = data.get(0).map_or(0, |row| row.len()); + let count_rows = data.len(); + let shape = (count_rows, count_columns); + + Self { data, shape } + } +} + +impl<T> Records for VecRecords<T> +where + T: AsRef<str>, +{ + type Iter = Vec<Vec<T>>; + + fn iter_rows(self) -> <Self::Iter as IntoRecords>::IterRows { + self.data.iter_rows() + } + + fn count_columns(&self) -> usize { + self.shape.1 + } + + fn hint_count_rows(&self) -> Option<usize> { + Some(self.shape.0) + } +} + +impl<'a, T> Records for &'a VecRecords<T> +where + T: AsRef<str>, +{ + type Iter = &'a [Vec<T>]; + + fn iter_rows(self) -> <Self::Iter as IntoRecords>::IterRows { + (&self.data).iter_rows() + } + + fn count_columns(&self) -> usize { + self.shape.1 + } + + fn hint_count_rows(&self) -> Option<usize> { + Some(self.shape.0) + } +} + +impl<T> ExactRecords for VecRecords<T> { + fn count_rows(&self) -> usize { + self.shape.0 + } +} + +impl<T> PeekableRecords for VecRecords<T> +where + T: Cell, +{ + fn get_text(&self, (row, col): Position) -> &str { + self[row][col].text() + } + + fn count_lines(&self, (row, col): Position) -> usize { + self[row][col].count_lines() + } + + fn get_line(&self, (row, col): Position, line: usize) -> &str { + self[row][col].line(line) + } + + fn get_line_width(&self, (row, col): Position, line: usize) -> usize { + self[row][col].line_width(line) + } + + fn get_width(&self, (row, col): Position) -> usize { + self[row][col].width() + } +} + +impl<T> Deref for VecRecords<T> { + type Target = Vec<Vec<T>>; + + fn deref(&self) -> &Self::Target { + &self.data + } +} + +impl<T> DerefMut for VecRecords<T> { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.data + } +} + +impl<T> From<VecRecords<T>> for Vec<Vec<T>> { + fn from(records: VecRecords<T>) -> Self { + records.data + } +} diff --git a/vendor/papergrid/src/util/mod.rs b/vendor/papergrid/src/util/mod.rs new file mode 100644 index 000000000..8480ff9be --- /dev/null +++ b/vendor/papergrid/src/util/mod.rs @@ -0,0 +1,3 @@ +//! A module contains utility functions which grid relay on. + +pub mod string; diff --git a/vendor/papergrid/src/util/string.rs b/vendor/papergrid/src/util/string.rs new file mode 100644 index 000000000..0017e6a91 --- /dev/null +++ b/vendor/papergrid/src/util/string.rs @@ -0,0 +1,270 @@ +//! This module contains a different functions which are used by the [`Grid`]. +//! +//! You should use it if you want to comply with how [`Grid`]. +//! +//! [`Grid`]: crate::grid::iterable::Grid + +/// Returns string width and count lines of a string. It's a combination of [`string_width_multiline`] and [`count_lines`]. +#[cfg(feature = "std")] +pub fn string_dimension(text: &str) -> (usize, usize) { + #[cfg(not(feature = "color"))] + { + let (lines, acc, max) = text.chars().fold((1, 0, 0), |(lines, acc, max), c| { + if c == '\n' { + (lines + 1, 0, acc.max(max)) + } else { + let w = unicode_width::UnicodeWidthChar::width(c).unwrap_or(0); + (lines, acc + w, max) + } + }); + + (lines, acc.max(max)) + } + + #[cfg(feature = "color")] + { + get_lines(text) + .map(|line| string_width(&line)) + .fold((0, 0), |(i, acc), width| (i + 1, acc.max(width))) + } +} + +/// Returns a string width. +pub fn string_width(text: &str) -> usize { + #[cfg(not(feature = "color"))] + { + unicode_width::UnicodeWidthStr::width(text) + } + + #[cfg(feature = "color")] + { + // we need to strip ansi because of terminal links + // and they're can't be stripped by ansi_str. + + ansitok::parse_ansi(text) + .filter(|e| e.kind() == ansitok::ElementKind::Text) + .map(|e| &text[e.start()..e.end()]) + .map(unicode_width::UnicodeWidthStr::width) + .sum() + } +} + +/// Returns a max string width of a line. +pub fn string_width_multiline(text: &str) -> usize { + #[cfg(not(feature = "color"))] + { + text.lines() + .map(unicode_width::UnicodeWidthStr::width) + .max() + .unwrap_or(0) + } + + #[cfg(feature = "color")] + { + text.lines().map(string_width).max().unwrap_or(0) + } +} + +/// Calculates a number of lines. +pub fn count_lines(s: &str) -> usize { + if s.is_empty() { + return 1; + } + + bytecount::count(s.as_bytes(), b'\n') + 1 +} + +/// Returns a list of tabs (`\t`) in a string.. +pub fn count_tabs(s: &str) -> usize { + bytecount::count(s.as_bytes(), b'\t') +} + +/// Splits the string by lines. +#[cfg(feature = "std")] +pub fn get_lines(text: &str) -> Lines<'_> { + #[cfg(not(feature = "color"))] + { + // we call `split()` but not `lines()` in order to match colored implementation + // specifically how we treat a trailing '\n' character. + Lines { + inner: text.split('\n'), + } + } + + #[cfg(feature = "color")] + { + Lines { + inner: ansi_str::AnsiStr::ansi_split(text, "\n"), + } + } +} + +/// Iterator over lines. +/// +/// In comparison to `std::str::Lines`, it treats trailing '\n' as a new line. +#[allow(missing_debug_implementations)] +#[cfg(feature = "std")] +pub struct Lines<'a> { + #[cfg(not(feature = "color"))] + inner: std::str::Split<'a, char>, + #[cfg(feature = "color")] + inner: ansi_str::AnsiSplit<'a>, +} +#[cfg(feature = "std")] +impl<'a> Iterator for Lines<'a> { + type Item = std::borrow::Cow<'a, str>; + + fn next(&mut self) -> Option<Self::Item> { + #[cfg(not(feature = "color"))] + { + self.inner.next().map(std::borrow::Cow::Borrowed) + } + + #[cfg(feature = "color")] + { + self.inner.next() + } + } +} + +#[cfg(feature = "std")] +/// Replaces tabs in a string with a given width of spaces. +pub fn replace_tab(text: &str, n: usize) -> std::borrow::Cow<'_, str> { + if !text.contains('\t') { + return std::borrow::Cow::Borrowed(text); + } + + // it's a general case which probably must be faster? + let replaced = if n == 4 { + text.replace('\t', " ") + } else { + let mut text = text.to_owned(); + replace_tab_range(&mut text, n); + text + }; + + std::borrow::Cow::Owned(replaced) +} + +#[cfg(feature = "std")] +fn replace_tab_range(cell: &mut String, n: usize) -> &str { + let mut skip = 0; + while let &Some(pos) = &cell[skip..].find('\t') { + let pos = skip + pos; + + let is_escaped = pos > 0 && cell.get(pos - 1..pos) == Some("\\"); + if is_escaped { + skip = pos + 1; + } else if n == 0 { + cell.remove(pos); + skip = pos; + } else { + // I'am not sure which version is faster a loop of 'replace' + // or allacation of a string for replacement; + cell.replace_range(pos..=pos, &" ".repeat(n)); + skip = pos + 1; + } + + if cell.is_empty() || skip >= cell.len() { + break; + } + } + + cell +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn string_width_emojie_test() { + // ...emojis such as “joy”, which normally take up two columns when printed in a terminal + // https://github.com/mgeisler/textwrap/pull/276 + assert_eq!(string_width("🎩"), 2); + assert_eq!(string_width("Rust 💕"), 7); + assert_eq!(string_width_multiline("Go 👍\nC 😎"), 5); + } + + #[cfg(feature = "color")] + #[test] + fn colored_string_width_test() { + use owo_colors::OwoColorize; + assert_eq!(string_width(&"hello world".red().to_string()), 11); + assert_eq!( + string_width_multiline(&"hello\nworld".blue().to_string()), + 5 + ); + assert_eq!(string_width("\u{1b}[34m0\u{1b}[0m"), 1); + assert_eq!(string_width(&"0".red().to_string()), 1); + } + + #[test] + fn count_lines_test() { + assert_eq!( + count_lines("\u{1b}[37mnow is the time for all good men\n\u{1b}[0m"), + 2 + ); + assert_eq!(count_lines("now is the time for all good men\n"), 2); + } + + #[cfg(feature = "color")] + #[test] + fn string_width_multinline_for_link() { + assert_eq!( + string_width_multiline( + "\u{1b}]8;;file:///home/nushell/asd.zip\u{1b}\\asd.zip\u{1b}]8;;\u{1b}\\" + ), + 7 + ); + } + + #[cfg(feature = "color")] + #[test] + fn string_width_for_link() { + assert_eq!( + string_width("\u{1b}]8;;file:///home/nushell/asd.zip\u{1b}\\asd.zip\u{1b}]8;;\u{1b}\\"), + 7 + ); + } + + #[cfg(feature = "std")] + #[test] + fn string_dimension_test() { + assert_eq!( + string_dimension("\u{1b}[37mnow is the time for all good men\n\u{1b}[0m"), + { + #[cfg(feature = "color")] + { + (2, 32) + } + #[cfg(not(feature = "color"))] + { + (2, 36) + } + } + ); + assert_eq!( + string_dimension("now is the time for all good men\n"), + (2, 32) + ); + assert_eq!(string_dimension("asd"), (1, 3)); + assert_eq!(string_dimension(""), (1, 0)); + } + + #[cfg(feature = "std")] + #[test] + fn replace_tab_test() { + assert_eq!(replace_tab("123\t\tabc\t", 3), "123 abc "); + + assert_eq!(replace_tab("\t", 0), ""); + assert_eq!(replace_tab("\t", 3), " "); + assert_eq!(replace_tab("123\tabc", 3), "123 abc"); + assert_eq!(replace_tab("123\tabc\tzxc", 0), "123abczxc"); + + assert_eq!(replace_tab("\\t", 0), "\\t"); + assert_eq!(replace_tab("\\t", 4), "\\t"); + assert_eq!(replace_tab("123\\tabc", 0), "123\\tabc"); + assert_eq!(replace_tab("123\\tabc", 4), "123\\tabc"); + } +} diff --git a/vendor/papergrid/tests/grid/column_span.rs b/vendor/papergrid/tests/grid/column_span.rs new file mode 100644 index 000000000..3ea8f8546 --- /dev/null +++ b/vendor/papergrid/tests/grid/column_span.rs @@ -0,0 +1,431 @@ +#![cfg(feature = "std")] + +use papergrid::config::{AlignmentHorizontal, Borders, Entity, Indent, Sides}; + +use crate::util::grid; +use testing_table::test_table; + +test_table!( + row_span, + grid(2, 2) + .config(|cfg|{ + cfg.set_column_span((0, 0), 2); + cfg.set_alignment_horizontal(Entity::Cell(0, 0), AlignmentHorizontal::Center); + }) + .build(), + "+---+---+" + "| 0-0 |" + "+---+---+" + "|1-0|1-1|" + "+---+---+" +); + +test_table!( + miltiline_span, + grid(2, 2) + .change_cell((0, 0), "0-0\n0-1") + .config(|cfg|{ + cfg.set_column_span((0, 0), 2); + cfg.set_alignment_horizontal(Entity::Cell(0, 0), AlignmentHorizontal::Center); + }) + .build(), + "+---+---+" + "| 0-0 |" + "| 0-1 |" + "+---+---+" + "|1-0|1-1|" + "+---+---+" +); + +test_table!( + row_span_multilane, + grid(4, 3) + .data([ + ["first line", "", "e.g."], + ["0", "1", "2"], + ["0", "1", "2"], + ["full last line", "", ""], + ]) + .config(|cfg|{ + cfg.set_column_span((0, 0), 2); + cfg.set_column_span((3, 0), 3); + }) + .build(), + "+-----+----+----+" + "|first line|e.g.|" + "+-----+----+----+" + "|0 |1 |2 |" + "+-----+----+----+" + "|0 |1 |2 |" + "+-----+----+----+" + "|full last line |" + "+-----+----+----+" +); + +test_table!( + row_span_with_horizontal_ident, + grid(3, 2) + .config(|cfg| { + cfg.set_column_span((0, 0), 2); + cfg.set_padding( + Entity::Cell(1, 0), + Sides::new( + Indent::spaced(4), + Indent::spaced(4), + Indent::zero(), + Indent::zero(), + ), + ); + }) + .build(), + "+-----------+---+" + "|0-0 |" + "+-----------+---+" + "| 1-0 |1-1|" + "+-----------+---+" + "|2-0 |2-1|" + "+-----------+---+" +); + +test_table!( + _row_span_3x3_with_horizontal_ident, + grid(3, 3) + .config(|cfg| { + cfg.set_column_span((0, 0), 3); + cfg.set_column_span((1, 0), 2); + cfg.set_column_span((2, 0), 2); + }) + .build(), + "+-+-+---+" + "|0-0 |" + "+-+-+---+" + "|1-0|1-2|" + "+-+-+---+" + "|2-0|2-2|" + "+-+-+---+" +); + +test_table!( + _3x3_with_2_colided_row_span_0, + grid(3, 3) + .change_cell((0, 0), "0-0xxxxxxx") + .config(|cfg| { + cfg.set_column_span((0, 0), 2); + cfg.set_column_span((1, 1), 2); + }) + .build(), + "+-----+----+---+" + "|0-0xxxxxxx|0-2|" + "+-----+----+---+" + "|1-0 |1-1 |" + "+-----+----+---+" + "|2-0 |2-1 |2-2|" + "+-----+----+---+" +); + +test_table!( + _3x3_with_2_colided_row_span_1, + grid(3, 3) + .change_cell((1, 1), "1-1xxxxxxx") + .config(|cfg| { + cfg.set_column_span((0, 0), 2); + cfg.set_column_span((1, 1), 2); + }) + .build(), + "+---+-----+----+" + "|0-0 |0-2 |" + "+---+-----+----+" + "|1-0|1-1xxxxxxx|" + "+---+-----+----+" + "|2-0|2-1 |2-2 |" + "+---+-----+----+" +); + +test_table!( + _3x3_with_2_colided_row_span_2, + grid(3, 3) + .change_cell((1, 1), "1-1xxxxxxx") + .change_cell((2, 0), "2-0xxxxxxxxxxxxx") + .config(|cfg| { + cfg.set_column_span((0, 0), 2); + cfg.set_column_span((1, 1), 2); + }) + .build(), + "+----------------+-----+----+" + "|0-0 |0-2 |" + "+----------------+-----+----+" + "|1-0 |1-1xxxxxxx|" + "+----------------+-----+----+" + "|2-0xxxxxxxxxxxxx|2-1 |2-2 |" + "+----------------+-----+----+" +); + +test_table!( + _3x3_with_2_colided_row_span_3, + grid(3, 3) + .change_cell((2, 1), "2-1xxxxxxxxxxxxx") + .config(|cfg| { + cfg.set_column_span((0, 0), 2); + cfg.set_column_span((1, 1), 2); + }) + .build(), + "+---+----------------+---+" + "|0-0 |0-2|" + "+---+----------------+---+" + "|1-0|1-1 |" + "+---+----------------+---+" + "|2-0|2-1xxxxxxxxxxxxx|2-2|" + "+---+----------------+---+" +); + +test_table!( + _3x3_with_2_colided_row_span_4, + grid(3, 3) + .change_cell((0, 2), "0-2xxxxxxxxxxxxx") + .config(|cfg| { + cfg.set_column_span((0, 0), 2); + cfg.set_column_span((1, 1), 2); + }) + .build(), + "+---+---+----------------+" + "|0-0 |0-2xxxxxxxxxxxxx|" + "+---+---+----------------+" + "|1-0|1-1 |" + "+---+---+----------------+" + "|2-0|2-1|2-2 |" + "+---+---+----------------+" +); + +test_table!( + spaned_column_in_first_cell_3x3, + grid(3, 3) + .change_cell((0, 0), "0-0xxxxxxx") + .config(|cfg| cfg.set_column_span((0, 0), 2)) + .build(), + "+-----+----+---+" + "|0-0xxxxxxx|0-2|" + "+-----+----+---+" + "|1-0 |1-1 |1-2|" + "+-----+----+---+" + "|2-0 |2-1 |2-2|" + "+-----+----+---+" +); + +test_table!( + row_span_with_different_length, + grid(3, 2) + .data([["first row", ""], ["0", "1"], ["a longer second row", ""]]) + .config(|cfg| { + cfg.set_column_span((0, 0), 2); + cfg.set_column_span((2, 0), 2); + }) + .build(), + "+---------+---------+" + "|first row |" + "+---------+---------+" + "|0 |1 |" + "+---------+---------+" + "|a longer second row|" + "+---------+---------+" +); + +test_table!( + row_span_with_odd_length, + grid(2, 2) + .data([["3 ", ""], ["2", "4"]]) + .config(|cfg| cfg.set_column_span((0, 0), 2)) + .build(), + "+--+-+" + "|3 |" + "+--+-+" + "|2 |4|" + "+--+-+" +); + +test_table!( + only_row_spaned, + grid(3, 2) + .config(|cfg| { + cfg.set_column_span((0, 0), 2); + cfg.set_column_span((1, 0), 2); + cfg.set_column_span((2, 0), 2); + }) + .build(), + "+-+-+" + "|0-0|" + "+-+-+" + "|1-0|" + "+-+-+" + "|2-0|" + "+-+-+" +); + +test_table!( + grid_2x2_span_test, + grid(2, 2) + .data([["123", ""], ["asd", "asd"]]) + .config(|cfg| cfg.set_column_span((0, 0), 2)) + .build(), + "+---+---+" + "|123 |" + "+---+---+" + "|asd|asd|" + "+---+---+" +); + +test_table!( + grid_2x2_span_2_test_0, + grid(2, 2) + .data([["1234", ""], ["asdw", ""]]) + .config(|cfg| { + cfg.set_column_span((0, 0), 2); + cfg.set_column_span((1, 0), 2); + }) + .build(), + "+--+-+" + "|1234|" + "+--+-+" + "|asdw|" + "+--+-+" +); + +test_table!( + grid_2x2_span_2_test_1, + grid(2, 2) + .data([["1", ""], ["a", ""]]) + .config(|cfg| { + cfg.set_column_span((0, 0), 2); + cfg.set_column_span((1, 0), 2); + }) + .build(), + "+++" + "|1|" + "+++" + "|a|" + "+++" +); + +test_table!( + row_span_with_no_split_style, + grid(2, 2) + .config(|cfg| { + cfg.set_borders(Borders::default()); + cfg.set_column_span((0, 0), 2); + cfg.set_alignment_horizontal(Entity::Cell(0, 0), AlignmentHorizontal::Center); + }) + .build(), + " 0-0 " + "1-01-1" +); + +test_table!( + _2x3_zero_span_between_cells_0, + grid(2, 3) + .config(|cfg| cfg.set_column_span((0, 0), 2)) + .build(), + "+---+---+---+" + "|0-0 |0-2|" + "+---+---+---+" + "|1-0|1-1|1-2|" + "+---+---+---+" +); + +test_table!( + _2x3_zero_span_between_cells_1, + grid(2, 3) + .config(|cfg| { + cfg.set_column_span((0, 0), 2); + cfg.set_column_span((1, 0), 2); + }) + .build(), + "+-+-+---+" + "|0-0|0-2|" + "+-+-+---+" + "|1-0|1-2|" + "+-+-+---+" +); + +test_table!( + _2x3_zero_span_at_the_end_0, + grid(2, 3) + .config(|cfg| { + cfg.set_column_span((0, 0), 3); + }) + .build(), + "+---+---+---+" + "|0-0 |" + "+---+---+---+" + "|1-0|1-1|1-2|" + "+---+---+---+" +); + +test_table!( + _2x3_zero_span_at_the_end_1, + grid(2, 3) + .config(|cfg| { + cfg.set_column_span((0, 0), 3); + cfg.set_column_span((1, 0), 3); + }) + .build(), + "+-+++" + "|0-0|" + "+-+++" + "|1-0|" + "+-+++" +); + +test_table!( + zero_span_grid, + grid(2, 2) + .data([["123", ""], ["asd", "asd"]]) + .config(|cfg| { + cfg.set_column_span((0, 0), 2); + cfg.set_column_span((1, 0), 2); + }) + .build(), + "+-+-+" + "|123|" + "+-+-+" + "|asd|" + "+-+-+" +); + +test_table!( + zero_span_grid_1, + grid(2, 2) + .data([["123", ""], ["asd", "asd"]]) + .config(|cfg| { + cfg.set_row_span((0, 0), 2); + cfg.set_row_span((0, 1), 2); + }) + .build(), + "+---++" + "+123++" + "+---++" +); + +test_table!( + zero_span_grid_2, + grid(2, 2) + .data([["123", "axc"], ["asd", "asd"]]) + .config(|cfg| { + cfg.set_row_span((0, 0), 2); + cfg.set_row_span((0, 1), 2); + }) + .build(), + "+---+---+" + "+123+axc+" + "+---+---+" +); + +test_table!( + zero_span_is_not_handled, + grid(2, 2) + .config(|cfg| { cfg.set_column_span((0, 1), 0); }) + .build(), + "+---+---+" + "|0-0|0-1|" + "+---+---+" + "|1-0|1-1|" + "+---+---+" +); diff --git a/vendor/papergrid/tests/grid/format_configuration.rs b/vendor/papergrid/tests/grid/format_configuration.rs new file mode 100644 index 000000000..1abfaa34a --- /dev/null +++ b/vendor/papergrid/tests/grid/format_configuration.rs @@ -0,0 +1,953 @@ +#![cfg(feature = "std")] + +use papergrid::config::{spanned::Formatting, AlignmentHorizontal, AlignmentVertical, Entity}; + +use crate::util::grid; +use testing_table::static_table; + +#[test] +fn formatting_test() { + let tests = [ + ( + AlignmentHorizontal::Left, + AlignmentVertical::Top, + Formatting::new(false, false, true), + static_table!( + "+-------------+----------+" + "|A long string| |" + "| | |" + "| | |" + "| |A |" + "| | string|" + "| |with |" + "| | new |" + "| |line |" + "| | |" + "| | |" + "| | |" + "+-------------+----------+" + "|1-0 |1-1 |" + "+-------------+----------+" + "|A one more |... |" + "| string | |" + "|with | |" + "| new | |" + "|line | |" + "+-------------+----------+" + ), + ), + ( + AlignmentHorizontal::Left, + AlignmentVertical::Top, + Formatting::new(true, false, true), + static_table!( + "+-------------+----------+" + "|A long string| |" + "| | |" + "| | |" + "| |A |" + "| |string |" + "| |with |" + "| |new |" + "| |line |" + "| | |" + "| | |" + "| | |" + "+-------------+----------+" + "|1-0 |1-1 |" + "+-------------+----------+" + "|A one more |... |" + "|string | |" + "|with | |" + "|new | |" + "|line | |" + "+-------------+----------+" + ), + ), + ( + AlignmentHorizontal::Left, + AlignmentVertical::Top, + Formatting::new(true, true, true), + static_table!( + "+-------------+----------+" + "|A long string|A |" + "| |string |" + "| |with |" + "| |new |" + "| |line |" + "| | |" + "| | |" + "| | |" + "| | |" + "| | |" + "| | |" + "+-------------+----------+" + "|1-0 |1-1 |" + "+-------------+----------+" + "|A one more |... |" + "|string | |" + "|with | |" + "|new | |" + "|line | |" + "+-------------+----------+" + ), + ), + // + ( + AlignmentHorizontal::Center, + AlignmentVertical::Top, + Formatting::new(false, false, true), + static_table!( + "+-------------+----------+" + "|A long string| |" + "| | |" + "| | |" + "| | A |" + "| | string|" + "| | with |" + "| | new |" + "| | line |" + "| | |" + "| | |" + "| | |" + "+-------------+----------+" + "| 1-0 | 1-1 |" + "+-------------+----------+" + "| A one more | ... |" + "| string | |" + "| with | |" + "| new | |" + "| line | |" + "+-------------+----------+" + ), + ), + ( + AlignmentHorizontal::Center, + AlignmentVertical::Top, + Formatting::new(true, false, true), + static_table!( + "+-------------+----------+" + "|A long string| |" + "| | |" + "| | |" + "| | A |" + "| | string |" + "| | with |" + "| | new |" + "| | line |" + "| | |" + "| | |" + "| | |" + "+-------------+----------+" + "| 1-0 | 1-1 |" + "+-------------+----------+" + "| A one more | ... |" + "| string | |" + "| with | |" + "| new | |" + "| line | |" + "+-------------+----------+" + ), + ), + ( + AlignmentHorizontal::Center, + AlignmentVertical::Top, + Formatting::new(true, true, true), + static_table!( + "+-------------+----------+" + "|A long string| A |" + "| | string |" + "| | with |" + "| | new |" + "| | line |" + "| | |" + "| | |" + "| | |" + "| | |" + "| | |" + "| | |" + "+-------------+----------+" + "| 1-0 | 1-1 |" + "+-------------+----------+" + "| A one more | ... |" + "| string | |" + "| with | |" + "| new | |" + "| line | |" + "+-------------+----------+" + ), + ), + // + ( + AlignmentHorizontal::Right, + AlignmentVertical::Top, + Formatting::new(false, false, true), + static_table!( + "+-------------+----------+" + "|A long string| |" + "| | |" + "| | |" + "| | A|" + "| | string|" + "| | with|" + "| | new|" + "| | line|" + "| | |" + "| | |" + "| | |" + "+-------------+----------+" + "| 1-0| 1-1|" + "+-------------+----------+" + "| A one more| ...|" + "| string| |" + "| with| |" + "| new| |" + "| line| |" + "+-------------+----------+" + ), + ), + ( + AlignmentHorizontal::Right, + AlignmentVertical::Top, + Formatting::new(true, false, true), + static_table!( + "+-------------+----------+" + "|A long string| |" + "| | |" + "| | |" + "| | A|" + "| | string|" + "| | with|" + "| | new|" + "| | line|" + "| | |" + "| | |" + "| | |" + "+-------------+----------+" + "| 1-0| 1-1|" + "+-------------+----------+" + "| A one more| ...|" + "| string| |" + "| with| |" + "| new| |" + "| line| |" + "+-------------+----------+" + ), + ), + ( + AlignmentHorizontal::Right, + AlignmentVertical::Top, + Formatting::new(true, true, true), + static_table!( + "+-------------+----------+" + "|A long string| A|" + "| | string|" + "| | with|" + "| | new|" + "| | line|" + "| | |" + "| | |" + "| | |" + "| | |" + "| | |" + "| | |" + "+-------------+----------+" + "| 1-0| 1-1|" + "+-------------+----------+" + "| A one more| ...|" + "| string| |" + "| with| |" + "| new| |" + "| line| |" + "+-------------+----------+" + ), + ), + // asd + ( + AlignmentHorizontal::Left, + AlignmentVertical::Center, + Formatting::new(false, false, true), + static_table!( + "+-------------+----------+" + "| | |" + "| | |" + "| | |" + "| |A |" + "| | string|" + "|A long string|with |" + "| | new |" + "| |line |" + "| | |" + "| | |" + "| | |" + "+-------------+----------+" + "|1-0 |1-1 |" + "+-------------+----------+" + "|A one more | |" + "| string | |" + "|with |... |" + "| new | |" + "|line | |" + "+-------------+----------+" + ), + ), + ( + AlignmentHorizontal::Left, + AlignmentVertical::Center, + Formatting::new(true, false, true), + static_table!( + "+-------------+----------+" + "| | |" + "| | |" + "| | |" + "| |A |" + "| |string |" + "|A long string|with |" + "| |new |" + "| |line |" + "| | |" + "| | |" + "| | |" + "+-------------+----------+" + "|1-0 |1-1 |" + "+-------------+----------+" + "|A one more | |" + "|string | |" + "|with |... |" + "|new | |" + "|line | |" + "+-------------+----------+" + ), + ), + ( + AlignmentHorizontal::Left, + AlignmentVertical::Center, + Formatting::new(true, true, true), + static_table!( + "+-------------+----------+" + "| | |" + "| | |" + "| | |" + "| |A |" + "| |string |" + "|A long string|with |" + "| |new |" + "| |line |" + "| | |" + "| | |" + "| | |" + "+-------------+----------+" + "|1-0 |1-1 |" + "+-------------+----------+" + "|A one more | |" + "|string | |" + "|with |... |" + "|new | |" + "|line | |" + "+-------------+----------+" + ), + ), + // + ( + AlignmentHorizontal::Center, + AlignmentVertical::Center, + Formatting::new(false, false, true), + static_table!( + "+-------------+----------+" + "| | |" + "| | |" + "| | |" + "| | A |" + "| | string|" + "|A long string| with |" + "| | new |" + "| | line |" + "| | |" + "| | |" + "| | |" + "+-------------+----------+" + "| 1-0 | 1-1 |" + "+-------------+----------+" + "| A one more | |" + "| string | |" + "| with | ... |" + "| new | |" + "| line | |" + "+-------------+----------+" + ), + ), + ( + AlignmentHorizontal::Center, + AlignmentVertical::Center, + Formatting::new(true, false, true), + static_table!( + "+-------------+----------+" + "| | |" + "| | |" + "| | |" + "| | A |" + "| | string |" + "|A long string| with |" + "| | new |" + "| | line |" + "| | |" + "| | |" + "| | |" + "+-------------+----------+" + "| 1-0 | 1-1 |" + "+-------------+----------+" + "| A one more | |" + "| string | |" + "| with | ... |" + "| new | |" + "| line | |" + "+-------------+----------+" + ), + ), + ( + AlignmentHorizontal::Center, + AlignmentVertical::Center, + Formatting::new(true, true, true), + static_table!( + "+-------------+----------+" + "| | |" + "| | |" + "| | |" + "| | A |" + "| | string |" + "|A long string| with |" + "| | new |" + "| | line |" + "| | |" + "| | |" + "| | |" + "+-------------+----------+" + "| 1-0 | 1-1 |" + "+-------------+----------+" + "| A one more | |" + "| string | |" + "| with | ... |" + "| new | |" + "| line | |" + "+-------------+----------+" + ), + ), + // + ( + AlignmentHorizontal::Right, + AlignmentVertical::Center, + Formatting::new(false, false, true), + static_table!( + "+-------------+----------+" + "| | |" + "| | |" + "| | |" + "| | A|" + "| | string|" + "|A long string| with|" + "| | new|" + "| | line|" + "| | |" + "| | |" + "| | |" + "+-------------+----------+" + "| 1-0| 1-1|" + "+-------------+----------+" + "| A one more| |" + "| string| |" + "| with| ...|" + "| new| |" + "| line| |" + "+-------------+----------+" + ), + ), + ( + AlignmentHorizontal::Right, + AlignmentVertical::Center, + Formatting::new(true, false, true), + static_table!( + "+-------------+----------+" + "| | |" + "| | |" + "| | |" + "| | A|" + "| | string|" + "|A long string| with|" + "| | new|" + "| | line|" + "| | |" + "| | |" + "| | |" + "+-------------+----------+" + "| 1-0| 1-1|" + "+-------------+----------+" + "| A one more| |" + "| string| |" + "| with| ...|" + "| new| |" + "| line| |" + "+-------------+----------+" + ), + ), + ( + AlignmentHorizontal::Right, + AlignmentVertical::Center, + Formatting::new(true, true, true), + static_table!( + "+-------------+----------+" + "| | |" + "| | |" + "| | |" + "| | A|" + "| | string|" + "|A long string| with|" + "| | new|" + "| | line|" + "| | |" + "| | |" + "| | |" + "+-------------+----------+" + "| 1-0| 1-1|" + "+-------------+----------+" + "| A one more| |" + "| string| |" + "| with| ...|" + "| new| |" + "| line| |" + "+-------------+----------+" + ), + ), + // + // asd + ( + AlignmentHorizontal::Left, + AlignmentVertical::Bottom, + Formatting::new(false, false, true), + static_table!( + "+-------------+----------+" + "| | |" + "| | |" + "| | |" + "| |A |" + "| | string|" + "| |with |" + "| | new |" + "| |line |" + "| | |" + "| | |" + "|A long string| |" + "+-------------+----------+" + "|1-0 |1-1 |" + "+-------------+----------+" + "|A one more | |" + "| string | |" + "|with | |" + "| new | |" + "|line |... |" + "+-------------+----------+" + ), + ), + ( + AlignmentHorizontal::Left, + AlignmentVertical::Bottom, + Formatting::new(true, false, true), + static_table!( + "+-------------+----------+" + "| | |" + "| | |" + "| | |" + "| |A |" + "| |string |" + "| |with |" + "| |new |" + "| |line |" + "| | |" + "| | |" + "|A long string| |" + "+-------------+----------+" + "|1-0 |1-1 |" + "+-------------+----------+" + "|A one more | |" + "|string | |" + "|with | |" + "|new | |" + "|line |... |" + "+-------------+----------+" + ), + ), + ( + AlignmentHorizontal::Left, + AlignmentVertical::Bottom, + Formatting::new(true, true, true), + static_table!( + "+-------------+----------+" + "| | |" + "| | |" + "| | |" + "| | |" + "| | |" + "| | |" + "| |A |" + "| |string |" + "| |with |" + "| |new |" + "|A long string|line |" + "+-------------+----------+" + "|1-0 |1-1 |" + "+-------------+----------+" + "|A one more | |" + "|string | |" + "|with | |" + "|new | |" + "|line |... |" + "+-------------+----------+" + ), + ), + // + ( + AlignmentHorizontal::Center, + AlignmentVertical::Bottom, + Formatting::new(false, false, true), + static_table!( + "+-------------+----------+" + "| | |" + "| | |" + "| | |" + "| | A |" + "| | string|" + "| | with |" + "| | new |" + "| | line |" + "| | |" + "| | |" + "|A long string| |" + "+-------------+----------+" + "| 1-0 | 1-1 |" + "+-------------+----------+" + "| A one more | |" + "| string | |" + "| with | |" + "| new | |" + "| line | ... |" + "+-------------+----------+" + ), + ), + ( + AlignmentHorizontal::Center, + AlignmentVertical::Bottom, + Formatting::new(true, false, true), + static_table!( + "+-------------+----------+" + "| | |" + "| | |" + "| | |" + "| | A |" + "| | string |" + "| | with |" + "| | new |" + "| | line |" + "| | |" + "| | |" + "|A long string| |" + "+-------------+----------+" + "| 1-0 | 1-1 |" + "+-------------+----------+" + "| A one more | |" + "| string | |" + "| with | |" + "| new | |" + "| line | ... |" + "+-------------+----------+" + ), + ), + ( + AlignmentHorizontal::Center, + AlignmentVertical::Bottom, + Formatting::new(true, true, true), + static_table!( + "+-------------+----------+" + "| | |" + "| | |" + "| | |" + "| | |" + "| | |" + "| | |" + "| | A |" + "| | string |" + "| | with |" + "| | new |" + "|A long string| line |" + "+-------------+----------+" + "| 1-0 | 1-1 |" + "+-------------+----------+" + "| A one more | |" + "| string | |" + "| with | |" + "| new | |" + "| line | ... |" + "+-------------+----------+" + ), + ), + // + ( + AlignmentHorizontal::Right, + AlignmentVertical::Bottom, + Formatting::new(false, false, true), + static_table!( + "+-------------+----------+" + "| | |" + "| | |" + "| | |" + "| | A|" + "| | string|" + "| | with|" + "| | new|" + "| | line|" + "| | |" + "| | |" + "|A long string| |" + "+-------------+----------+" + "| 1-0| 1-1|" + "+-------------+----------+" + "| A one more| |" + "| string| |" + "| with| |" + "| new| |" + "| line| ...|" + "+-------------+----------+" + ), + ), + ( + AlignmentHorizontal::Right, + AlignmentVertical::Bottom, + Formatting::new(true, false, true), + static_table!( + "+-------------+----------+" + "| | |" + "| | |" + "| | |" + "| | A|" + "| | string|" + "| | with|" + "| | new|" + "| | line|" + "| | |" + "| | |" + "|A long string| |" + "+-------------+----------+" + "| 1-0| 1-1|" + "+-------------+----------+" + "| A one more| |" + "| string| |" + "| with| |" + "| new| |" + "| line| ...|" + "+-------------+----------+" + ), + ), + ( + AlignmentHorizontal::Right, + AlignmentVertical::Bottom, + Formatting::new(true, true, true), + static_table!( + "+-------------+----------+" + "| | |" + "| | |" + "| | |" + "| | |" + "| | |" + "| | |" + "| | A|" + "| | string|" + "| | with|" + "| | new|" + "|A long string| line|" + "+-------------+----------+" + "| 1-0| 1-1|" + "+-------------+----------+" + "| A one more| |" + "| string| |" + "| with| |" + "| new| |" + "| line| ...|" + "+-------------+----------+" + ), + ), + ]; + + let grid = grid(3, 2) + .change_cell((0, 0), "A long string") + .change_cell((0, 1), "\n\n\nA\n string\nwith\n new\nline\n\n\n") + .change_cell((2, 0), "A one more\n string\nwith\n new\nline") + .change_cell((2, 1), "..."); + + for (i, test) in tests.iter().enumerate() { + let table = grid + .clone() + .config(|cfg| { + cfg.set_alignment_horizontal(Entity::Global, test.0); + cfg.set_alignment_vertical(Entity::Global, test.1); + cfg.set_formatting(Entity::Global, test.2); + }) + .clone() + .build(); + + let expected = test.3; + assert_eq!(table, expected, "test case #{i:?} failed"); + } +} + +#[test] +fn formatting_empty_test() { + for (rows, cols) in [(0, 0), (0, 4), (4, 0)] { + let formatting = Formatting::new(true, true, true); + assert_eq!( + grid(rows, cols) + .config(|cfg| cfg.set_formatting(Entity::Global, formatting)) + .build(), + "" + ); + } +} + +#[test] +fn formatting_1x1_test() { + let json = r#" +{ + "id": "0001", + "batters": { + "batter": [ + { "id": "1002", "type": "Chocolate" }, + ] + }, + "topping": [ + { "id": "5003", "type": "Chocolate" }, + { "id": "5004", "type": "Maple" } + ] +}"#; + + let grid = grid(1, 1).data([[json]]); + + assert_eq!( + grid.clone() + .config( + |cfg| cfg.set_alignment_horizontal(Entity::Cell(0, 0), AlignmentHorizontal::Left) + ) + .build(), + static_table!( + r#"+--------------------------------------------------+"# + r#"| |"# + r#"|{ |"# + r#"| "id": "0001", |"# + r#"| "batters": { |"# + r#"| "batter": [ |"# + r#"| { "id": "1002", "type": "Chocolate" },|"# + r#"| ] |"# + r#"| }, |"# + r#"| "topping": [ |"# + r#"| { "id": "5003", "type": "Chocolate" }, |"# + r#"| { "id": "5004", "type": "Maple" } |"# + r#"| ] |"# + r#"|} |"# + r#"+--------------------------------------------------+"# + ), + ); + + assert_eq!( + grid.clone() + .config(|cfg| cfg.set_formatting(Entity::Global, Formatting::new(false, false, true))) + .build(), + static_table!( + r#"+--------------------------------------------------+"# + r#"| |"# + r#"|{ |"# + r#"| "id": "0001", |"# + r#"| "batters": { |"# + r#"| "batter": [ |"# + r#"| { "id": "1002", "type": "Chocolate" },|"# + r#"| ] |"# + r#"| }, |"# + r#"| "topping": [ |"# + r#"| { "id": "5003", "type": "Chocolate" }, |"# + r#"| { "id": "5004", "type": "Maple" } |"# + r#"| ] |"# + r#"|} |"# + r#"+--------------------------------------------------+"# + ), + ); + + assert_eq!( + grid.clone() + .config(|cfg| cfg.set_formatting(Entity::Global, Formatting::new(true, false, true))) + .build(), + static_table!( + r#"+--------------------------------------------------+"# + r#"| |"# + r#"|{ |"# + r#"|"id": "0001", |"# + r#"|"batters": { |"# + r#"|"batter": [ |"# + r#"|{ "id": "1002", "type": "Chocolate" }, |"# + r#"|] |"# + r#"|}, |"# + r#"|"topping": [ |"# + r#"|{ "id": "5003", "type": "Chocolate" }, |"# + r#"|{ "id": "5004", "type": "Maple" } |"# + r#"|] |"# + r#"|} |"# + r#"+--------------------------------------------------+"# + ), + ); + + assert_eq!( + grid.config(|cfg| cfg.set_formatting(Entity::Global, Formatting::new(true, true, true))) + .build(), + static_table!( + r#"+--------------------------------------------------+"# + r#"|{ |"# + r#"|"id": "0001", |"# + r#"|"batters": { |"# + r#"|"batter": [ |"# + r#"|{ "id": "1002", "type": "Chocolate" }, |"# + r#"|] |"# + r#"|}, |"# + r#"|"topping": [ |"# + r#"|{ "id": "5003", "type": "Chocolate" }, |"# + r#"|{ "id": "5004", "type": "Maple" } |"# + r#"|] |"# + r#"|} |"# + r#"| |"# + r#"+--------------------------------------------------+"# + ), + ); +} + +#[test] +fn tabs_arent_handled() { + let json = "{ +\t\t \"id\": \"1\", +\t\t \"name\": \"Hello World\", +\t\t \"list\": [ +\t\t\t\t [1, 2, 3], +\t\t\t\t [4, 5, 6], +\t\t ] +}"; + + let grid = grid(1, 1).data([[json]]); + + assert_eq!( + grid.build(), + static_table!( + "+-----------------------+" + "|{ |" + "|\t\t \"id\": \"1\", |" + "|\t\t \"name\": \"Hello World\",|" + "|\t\t \"list\": [ |" + "|\t\t\t\t [1, 2, 3], |" + "|\t\t\t\t [4, 5, 6], |" + "|\t\t ] |" + "|} |" + "+-----------------------+" + ), + ); +} diff --git a/vendor/papergrid/tests/grid/mod.rs b/vendor/papergrid/tests/grid/mod.rs new file mode 100644 index 000000000..e639d6728 --- /dev/null +++ b/vendor/papergrid/tests/grid/mod.rs @@ -0,0 +1,6 @@ +mod column_span; +mod format_configuration; +mod render; +mod row_span; +mod settings; +mod styling; diff --git a/vendor/papergrid/tests/grid/render.rs b/vendor/papergrid/tests/grid/render.rs new file mode 100644 index 000000000..6c140c9ad --- /dev/null +++ b/vendor/papergrid/tests/grid/render.rs @@ -0,0 +1,251 @@ +// Copyright (c) 2021 Maxim Zhiburt +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. + +#![cfg(feature = "std")] + +use std::vec; + +use papergrid::{ + colors::NoColors, + config::spanned::SpannedConfig, + config::{AlignmentHorizontal, AlignmentVertical, Borders, Entity}, + grid::iterable::Grid, + records::IterRecords, +}; + +use crate::util::{grid, ConstantDimension, DEFAULT_BORDERS}; +use testing_table::test_table; + +test_table!(render_0x0, grid(0, 0).build(), ""); + +test_table!( + render_1x1, + grid(1, 1).change_cell((0, 0), "one line").build(), + "+--------+" + "|one line|" + "+--------+" +); + +test_table!( + render_1x1_empty, + grid(1, 1).change_cell((0, 0), "").build(), + "++" + "||" + "++" +); + +test_table!( + render_1x1_empty_with_height_0, + { + let data = vec![vec![""]]; + let data = IterRecords::new(data, 1, Some(1)); + + let dims = ConstantDimension(vec![0], vec![0]); + + let mut cfg = SpannedConfig::default(); + cfg.set_borders(DEFAULT_BORDERS); + + let grid = Grid::new(&data, &dims, &cfg, NoColors); + grid.to_string() + }, + "++" + "++" +); + +test_table!( + render_1x1_empty_with_height_with_width, + { + let data = vec![vec![String::from("")]]; + let data = IterRecords::new(&data, 1, Some(1)); + + let dims = ConstantDimension(vec![10], vec![0]); + let mut cfg = SpannedConfig::default(); + cfg.set_borders(Borders { + top_left: Some('┌'), + top_right: Some('┐'), + bottom_left: Some('└'), + bottom_right: Some('┘'), + top: Some('─'), + bottom: Some('─'), + ..Default::default() + }); + + let grid = Grid::new(data, &dims, &cfg, NoColors); + grid.to_string() + }, + "┌──────────┐" + "└──────────┘" +); + +test_table!( + render_2x2, + grid(2, 2).build(), + "+---+---+" + "|0-0|0-1|" + "+---+---+" + "|1-0|1-1|" + "+---+---+" +); + +test_table!( + render_3x2, + grid(3, 2).build(), + "+---+---+" + "|0-0|0-1|" + "+---+---+" + "|1-0|1-1|" + "+---+---+" + "|2-0|2-1|" + "+---+---+" +); + +test_table!( + render_1x2, + grid(1, 2).data([["hello", "world"]]).build(), + "+-----+-----+" + "|hello|world|" + "+-----+-----+" +); + +test_table!( + render_multilane, + grid(2, 2) + .data([ + ["left\ncell", "right one"], + ["the second column got the beginning here", "and here\nwe\nsee\na\nlong\nstring"], + ]) + .build(), + "+----------------------------------------+---------+" + "|left |right one|" + "|cell | |" + "+----------------------------------------+---------+" + "|the second column got the beginning here|and here |" + "| |we |" + "| |see |" + "| |a |" + "| |long |" + "| |string |" + "+----------------------------------------+---------+" +); + +test_table!( + render_multilane_alignment, + grid(2, 2) + .config(|cfg|{ + cfg.set_alignment_horizontal(Entity::Cell(0, 0), AlignmentHorizontal::Center); + cfg.set_alignment_horizontal(Entity::Cell(1, 1), AlignmentHorizontal::Right); + }) + .data([ + ["left\ncell", "right one"], + ["the second column got the beginning here", "and here\nwe\nsee\na\nlong\nstring"], + ]) + .build(), + "+----------------------------------------+---------+" + "| left |right one|" + "| cell | |" + "+----------------------------------------+---------+" + "|the second column got the beginning here| and here|" + "| | we |" + "| | see |" + "| | a |" + "| | long |" + "| | string |" + "+----------------------------------------+---------+" +); + +test_table!( + render_multilane_vertical_alignment, + grid(2, 2) + .data([ + ["left\ncell", "right one"], + ["the second column got the beginning here", "and here\nwe\nsee\na\nlong\nstring"], + ]) + .config(|cfg|{ + cfg.set_alignment_horizontal(Entity::Cell(0, 0), AlignmentHorizontal::Center); + cfg.set_alignment_vertical(Entity::Cell(1, 0), AlignmentVertical::Center); + cfg.set_alignment_horizontal(Entity::Cell(1, 1), AlignmentHorizontal::Right); + }) + .build(), + "+----------------------------------------+---------+" + "| left |right one|" + "| cell | |" + "+----------------------------------------+---------+" + "| | and here|" + "| | we |" + "|the second column got the beginning here| see |" + "| | a |" + "| | long |" + "| | string |" + "+----------------------------------------+---------+" +); + +test_table!( + render_empty_cell, + grid(2, 2).change_cell((0, 1), "").build(), + "+---+---+" + "|0-0| |" + "+---+---+" + "|1-0|1-1|" + "+---+---+" +); + +test_table!( + hieroglyph_multiline_handling, + grid(1, 2).data([["哈哈", "哈\n哈"]]).build(), + "+----+--+" + "|哈哈|哈|" + "| |哈|" + "+----+--+" +); + +test_table!( + hieroglyph_handling_2, + grid(2, 1).data([["জী._ডি._ব্লক_সল্টলেক_দূর্গা_পুজো_২০১৮.jpg"], ["Hello"]]).build(), + "+------------------------------------+" + "|জী._ডি._ব্লক_সল্টলেক_দূর্গা_পুজো_২০১৮.jpg|" + "+------------------------------------+" + "|Hello |" + "+------------------------------------+" +); + +test_table!( + doesnt_render_return_carige_0, + grid(2, 2).change_cell((0, 1), "123\r\r\r567").build(), + "+---+------+" + "|0-0|123\r\r\r567|" + "+---+------+" + "|1-0|1-1 |" + "+---+------+" +); + +test_table!( + doesnt_render_return_carige_1, + grid(2, 2).change_cell((1, 1), "12345678").change_cell((0, 1), "123\r\r\r567").build(), + "+---+--------+" + "|0-0|123\r\r\r567 |" + "+---+--------+" + "|1-0|12345678|" + "+---+--------+" +); + +// #[test] +// #[ignore = "I am not sure what is the right behaiviour here"] +// fn hieroglyph_handling() { +// let grid = util::grid_from([["哈哈", "哈"]]); + +// assert_eq!( +// grid, +// "+----+--+\n\ +// |哈哈 |哈 |\n\ +// +----+--+", +// ) +// } diff --git a/vendor/papergrid/tests/grid/row_span.rs b/vendor/papergrid/tests/grid/row_span.rs new file mode 100644 index 000000000..054ee2af3 --- /dev/null +++ b/vendor/papergrid/tests/grid/row_span.rs @@ -0,0 +1,1062 @@ +#![cfg(feature = "std")] + +use papergrid::config::{ + AlignmentHorizontal, AlignmentVertical, Borders, + Entity::{self, *}, + Indent, Sides, +}; + +use crate::util::grid; +use testing_table::test_table; + +test_table!( + _2x2_vertical_alignment_center, + grid(2, 2) + .config(|cfg|{ + cfg.set_row_span((0, 0), 2); + cfg.set_alignment_vertical(Cell(0, 0), AlignmentVertical::Center); + }) + .build(), + "+---+---+" + "| |0-1|" + "+0-0+---+" + "| |1-1|" + "+---+---+" +); + +test_table!( + _2x2_vertical_alignment_bottom, + grid(2, 2) + .config(|cfg|{ + cfg.set_row_span((0, 0), 2); + cfg.set_alignment_vertical(Cell(0, 0), AlignmentVertical::Bottom); + }) + .build(), + "+---+---+" + "| |0-1|" + "+ +---+" + "|0-0|1-1|" + "+---+---+" +); + +test_table!( + _2x2_multiline, + grid(2, 2) + .change_cell((0, 0), "0-0\n0-1xxx") + .config(|cfg|{ + cfg.set_row_span((0, 0), 2); + cfg.set_alignment_vertical(Cell(0, 0), AlignmentVertical::Center); + }) + .build(), + "+------+---+" + "|0-0 |0-1|" + "+0-1xxx+---+" + "| |1-1|" + "+------+---+" +); + +test_table!( + _2x2_multiline_vertical_alignment_bottom, + grid(2, 2) + .change_cell((0, 0), "0-0\n0-1xxx") + .config(|cfg|{ + cfg.set_row_span((0, 0), 2); + cfg.set_alignment_vertical(Cell(0, 0), AlignmentVertical::Bottom); + }) + .build(), + "+------+---+" + "| |0-1|" + "+0-0 +---+" + "|0-1xxx|1-1|" + "+------+---+" +); + +test_table!( + _4x3_multiline_0, + grid(4, 3) + .data([ + ["first line", "0-1", "full last line"], + ["", "1", ""], + ["0", "1", ""], + ["3-0", "3-1", ""], + ]) + .config(|cfg|{ + cfg.set_row_span((0, 0), 2); + cfg.set_row_span((0, 2), 4); + }) + .build(), + "+----------+---+--------------+" + "|first line|0-1|full last line|" + "+ +---+ +" + "| |1 | |" + "+----------+---+ +" + "|0 |1 | |" + "+----------+---+ +" + "|3-0 |3-1| |" + "+----------+---+--------------+" +); + +test_table!( + _3x2_with_horizontal_ident_on_spanned_cell, + grid(3, 2) + .config(|cfg|{ + cfg.set_row_span((0, 0), 2); + cfg.set_padding(Cell(1, 0), Sides::new(Indent::spaced(4), Indent::spaced(4), Indent::default(), Indent::default())); + }) + .build(), + "+---+---+" + "|0-0|0-1|" + "+ +---+" + "| |1-1|" + "+---+---+" + "|2-0|2-1|" + "+---+---+" +); + +test_table!( + _3x2_with_horizontal_ident, + grid(3, 2) + .config(|cfg|{ + cfg.set_row_span((0, 0), 2); + cfg.set_padding(Cell(0, 0), Sides::new(Indent::spaced(4), Indent::spaced(4), Indent::default(), Indent::default())); + }) + .build(), + "+-----------+---+" + "| 0-0 |0-1|" + "+ +---+" + "| |1-1|" + "+-----------+---+" + "|2-0 |2-1|" + "+-----------+---+" +); + +test_table!( + _3x2_with_vertical_ident, + grid(3, 2) + .config(|cfg|{ + cfg.set_row_span((0, 0), 2); + cfg.set_padding(Cell(0, 0), Sides::new(Indent::default(), Indent::default(), Indent::spaced(4), Indent::spaced(4))); + }) + .build(), + "+---+---+" + "| |0-1|" + "| | |" + "| | |" + "| | |" + "+0-0+---+" + "| |1-1|" + "| | |" + "| | |" + "| | |" + "+---+---+" + "|2-0|2-1|" + "+---+---+" +); + +test_table!( + _3x3_render_0, + grid(3, 3) + .config(|cfg|{ + cfg.set_row_span((0, 0), 3); + cfg.set_row_span((0, 1), 2); + cfg.set_row_span((0, 2), 2); + }) + .build(), + "+---+---+---+" + "+0-0+0-1+0-2+" + "+ +---+---+" + "| |2-1|2-2|" + "+---+---+---+" +); + +test_table!( + _3x3_render_1, + grid(3, 3) + .change_cell((0, 1), "t\ne\nx\nt") + .config(|cfg|{ + cfg.set_row_span((0, 0), 3); + cfg.set_row_span((0, 1), 2); + cfg.set_row_span((0, 2), 2); + }) + .build(), + "+---+---+---+" + "|0-0|t |0-2|" + "| |e | |" + "+ +x + +" + "| |t | |" + "+ +---+---+" + "| |2-1|2-2|" + "+---+---+---+" +); + +test_table!( + _3x3_coliison_0, + grid(3, 3) + .change_cell((0, 0), "0-0xxxxxxx") + .config(|cfg|{ + cfg.set_row_span((0, 0), 2); + cfg.set_row_span((1, 1), 2); + }) + .build(), + "+----------+---+---+" + "|0-0xxxxxxx|0-1|0-2|" + "+ +---+---+" + "| |1-1|1-2|" + "+----------+ +---+" + "|2-0 | |2-2|" + "+----------+---+---+" +); + +test_table!( + _3x3_coliison_1, + grid(3, 3) + .change_cell((1, 1), "1-1xxxxxxx") + .config(|cfg|{ + cfg.set_row_span((0, 0), 2); + cfg.set_row_span((1, 1), 2); + }) + .build(), + "+---+----------+---+" + "|0-0|0-1 |0-2|" + "+ +----------+---+" + "| |1-1xxxxxxx|1-2|" + "+---+ +---+" + "|2-0| |2-2|" + "+---+----------+---+" +); + +test_table!( + _3x3_coliison_2, + grid(3, 3) + .change_cell((1, 1), "1-1\nx\nx\nxxxxx") + .change_cell((0, 2), "2-0x\nxxx\nxx\nxxxxxxx") + .config(|cfg|{ + cfg.set_row_span((0, 0), 2); + cfg.set_row_span((1, 1), 2); + }) + .build(), + "+---+-----+-------+" + "|0-0|0-1 |2-0x |" + "| | |xxx |" + "| | |xx |" + "| | |xxxxxxx|" + "+ +-----+-------+" + "| |1-1 |1-2 |" + "| |x | |" + "+---+x +-------+" + "|2-0|xxxxx|2-2 |" + "+---+-----+-------+" +); + +test_table!( + _3x3_coliison_3, + grid(3, 3) + .change_cell((1, 2), "2-1\nxx\nxx\nxx\nxxxxxx\nx") + .config(|cfg|{ + cfg.set_row_span((0, 0), 2); + cfg.set_row_span((1, 1), 2); + }) + .build(), + "+---+---+------+" + "|0-0|0-1|0-2 |" + "+ +---+------+" + "| |1-1|2-1 |" + "| | |xx |" + "| | |xx |" + "| | |xx |" + "| | |xxxxxx|" + "| | |x |" + "+---+ +------+" + "|2-0| |2-2 |" + "+---+---+------+" +); + +test_table!( + _3x3_coliison_4, + grid(3, 3) + .change_cell((2, 1), "0-2\nx\nx\nx\nx\nxxxxxxx\nx\nx") + .config(|cfg|{ + cfg.set_row_span((0, 0), 2); + cfg.set_row_span((1, 2), 2); + }) + .build(), + "+---+-------+---+" + "|0-0|0-1 |0-2|" + "+ +-------+---+" + "| |1-1 |1-2|" + "+---+-------+ +" + "|2-0|0-2 | |" + "| |x | |" + "| |x | |" + "| |x | |" + "| |x | |" + "| |xxxxxxx| |" + "| |x | |" + "| |x | |" + "+---+-------+---+" +); + +test_table!( + _3x3_first_row, + grid(3, 3) + .change_cell((0, 0), "0-0\nxx\nx\nx\nx\nx\nx") + .config(|cfg|{ cfg.set_row_span((0, 0), 2); }) + .build(), + "+---+---+---+" + "|0-0|0-1|0-2|" + "|xx | | |" + "|x | | |" + "+x +---+---+" + "|x |1-1|1-2|" + "|x | | |" + "|x | | |" + "+---+---+---+" + "|2-0|2-1|2-2|" + "+---+---+---+" +); + +test_table!( + _2x3_with_different_length, + grid(2, 3) + .change_cell((0, 0), "f\nir\nst\n ro\nw") + .change_cell((0, 2), "a\n \nlonger\n \nsecond\n \nrow") + .change_cell((1, 0), "0") + .change_cell((1, 1), "1") + .config(|cfg|{ + cfg.set_row_span((0, 0), 2); + cfg.set_row_span((0, 2), 2); + }) + .build(), + "+---+---+------+" + "|f |0-1|a |" + "|ir | | |" + "|st | |longer|" + "+ ro+---+ +" + "|w |1 |second|" + "| | | |" + "| | |row |" + "+---+---+------+" +); + +test_table!( + _2x2_with_odd_length, + grid(2, 2) + .change_cell((0, 0), "3\n \n \n ") + .change_cell((0, 1), "2") + .change_cell((1, 1), "4") + .config(|cfg|{ + cfg.set_row_span((0, 0), 2); + }) + .build(), + "+-+-+" + "|3|2|" + "| | |" + "+ +-+" + "| |4|" + "+-+-+" +); + +test_table!( + _2x3_only_col_spaned, + grid(2, 3) + .config(|cfg|{ + cfg.set_row_span((0, 0), 2); + cfg.set_row_span((0, 1), 2); + cfg.set_row_span((0, 2), 2); + }) + .build(), + "+---+---+---+" + "+0-0+0-1+0-2+" + "+---+---+---+" +); + +test_table!( + _2x2_render_0, + grid(2, 2) + .change_cell((0, 0), "1\n\n\n\n\n\n\n23") + .config(|cfg|{ + cfg.set_row_span((0, 0), 2); + }) + .build(), + "+--+---+" + "|1 |0-1|" + "| | |" + "| | |" + "| | |" + "+ +---+" + "| |1-1|" + "| | |" + "|23| |" + "+--+---+" +); + +test_table!( + _2x2_render_1, + grid(2, 2) + .data([["12\n3\n4", "a\ns\ndw"], ["asd", "asd"]]) + .config(|cfg|{ + cfg.set_row_span((0, 0), 2); + cfg.set_row_span((0, 1), 2); + }) + .build(), + "+--+--+" + "|12|a |" + "+3 +s +" + "|4 |dw|" + "+--+--+" +); + +test_table!( + _2x2_render_2, + grid(2, 2) + .data([["1", "a"], ["asd", "asd"]]) + .config(|cfg|{ + cfg.set_row_span((0, 0), 2); + cfg.set_row_span((0, 1), 2); + }) + .build(), + "+-+-+" + "+1+a+" + "+-+-+" +); + +test_table!( + _2x2_render_3, + grid(2, 2) + .data([["1as\nd\n", "a"], ["as\ndasdds\na", "asd"]]) + .config(|cfg|{ + cfg.set_row_span((0, 0), 2); + cfg.set_row_span((0, 1), 2); + }) + .build(), + "+---+-+" + "|1as|a|" + "+d + +" + "| | |" + "+---+-+" +); + +test_table!( + _2x2_render_4, + grid(2, 2) + .data([["1as\nd\n", "a"], ["as\ndasdds\na", "asd"]]) + .config(|cfg|{ + cfg.set_row_span((0, 0), 2); + cfg.set_row_span((0, 1), 2); + cfg.set_alignment_vertical(Entity::Global, AlignmentVertical::Center) + }) + .build(), + "+---+-+" + "|1as| |" + "+d +a+" + "| | |" + "+---+-+" +); + +test_table!( + _2x2_render_5, + grid(2, 2) + .data([["1a\ns\nd\n", "a"], ["as\ndasdds\na", "asd"]]) + .config(|cfg|{ + cfg.set_row_span((0, 0), 2); + cfg.set_row_span((0, 1), 2); + cfg.set_alignment_vertical(Entity::Global, AlignmentVertical::Center) + }) + .build(), + "+--+-+" + "|1a| |" + "|s |a|" + "+d + +" + "| | |" + "+--+-+" +); + +test_table!( + _2x2_render_6, + grid(2, 2) + .data([["1a\ns\nd", "a"], ["as\ndasdds\na", "asd"]]) + .config(|cfg|{ + cfg.set_row_span((0, 0), 2); + cfg.set_row_span((0, 1), 2); + cfg.set_alignment_vertical(Entity::Global, AlignmentVertical::Bottom) + }) + .build(), + "+--+-+" + "|1a| |" + "+s + +" + "|d |a|" + "+--+-+" +); + +test_table!( + _2x2_with_no_split_style, + grid(2, 2) + .change_cell((0, 0), "1\n2\n3") + .config(|cfg|{ + cfg.set_borders(Borders::default()); + cfg.set_row_span((0, 0), 2); + cfg.set_alignment_vertical(Cell(0, 0), AlignmentVertical::Center); + }) + .build(), + "10-1" + "2 " + "31-1" +); + +test_table!( + _3x2_with_zero_row_span_0, + grid(3, 2) + .config(|cfg|{ + cfg.set_row_span((0, 0), 2); + }) + .build(), + "+---+---+" + "|0-0|0-1|" + "+ +---+" + "| |1-1|" + "+---+---+" + "|2-0|2-1|" + "+---+---+" +); + +test_table!( + _3x2_with_zero_row_span_1, + grid(3, 2) + .config(|cfg|{ + cfg.set_row_span((0, 0), 2); + cfg.set_row_span((0, 1), 2); + }) + .build(), + "+---+---+" + "+0-0+0-1+" + "+---+---+" + "|2-0|2-1|" + "+---+---+" +); + +test_table!( + _3x2_with_zero_row_span_2, + grid(3, 2) + .config(|cfg|{ + cfg.set_row_span((0, 1), 3); + }) + .build(), + "+---+---+" + "|0-0|0-1|" + "+---+ +" + "|1-0| |" + "+---+ +" + "|2-0| |" + "+---+---+" +); + +test_table!( + _3x2_with_zero_row_span_3, + grid(3, 2) + .config(|cfg|{ + cfg.set_row_span((0, 1), 3); + cfg.set_row_span((0, 0), 3); + }) + .build(), + "+---+---+" + "+0-0+0-1+" + "+ + +" + "+---+---+" +); + +test_table!( + _2x2_with_zero_row_span_4, + grid(2, 2) + .config(|cfg|{ + cfg.set_row_span((0, 0), 2); + cfg.set_row_span((0, 1), 2); + }) + .build(), + "+---+---+" + "+0-0+0-1+" + "+---+---+" +); + +test_table!( + _4x4_with_row_span_and_col_span_0, + grid(4, 4) + .change_cell((1, 1), "123\n345\n555\n333") + .config(|cfg|{ + cfg.set_row_span((1, 1), 2); + cfg.set_column_span((1, 1), 2); + cfg.set_alignment_horizontal(Cell(1, 1), AlignmentHorizontal::Center); + cfg.set_alignment_vertical(Cell(1, 1), AlignmentVertical::Center); + }) + .build(), + "+---+---+---+---+" + "|0-0|0-1|0-2|0-3|" + "+---+---+---+---+" + "|1-0| 123 |1-3|" + "| | 345 | |" + "+---+ 555 +---+" + "|2-0| 333 |2-3|" + "+---+---+---+---+" + "|3-0|3-1|3-2|3-3|" + "+---+---+---+---+" +); + +test_table!( + _4x4_with_row_span_and_col_span_1, + grid(4, 4) + .change_cell((0, 0), "123\n345\n555\n333") + .config(|cfg|{ + cfg.set_row_span((0, 0), 2); + cfg.set_column_span((0, 0), 2); + cfg.set_alignment_horizontal(Cell(0, 0), AlignmentHorizontal::Center); + cfg.set_alignment_vertical(Cell(0, 0), AlignmentVertical::Center); + }) + .build(), + "+---+---+---+---+" + "| 123 |0-2|0-3|" + "| 345 | | |" + "+ 555 +---+---+" + "| 333 |1-2|1-3|" + "+---+---+---+---+" + "|2-0|2-1|2-2|2-3|" + "+---+---+---+---+" + "|3-0|3-1|3-2|3-3|" + "+---+---+---+---+" +); + +test_table!( + _4x4_with_row_span_and_col_span_2, + grid(4, 4) + .change_cell((2, 0), "123\n345\n555\n333") + .config(|cfg|{ + cfg.set_row_span((2, 0), 2); + cfg.set_column_span((2, 0), 2); + cfg.set_alignment_horizontal(Cell(2, 0), AlignmentHorizontal::Center); + cfg.set_alignment_vertical(Cell(2, 0), AlignmentVertical::Center); + }) + .build(), + "+---+---+---+---+" + "|0-0|0-1|0-2|0-3|" + "+---+---+---+---+" + "|1-0|1-1|1-2|1-3|" + "+---+---+---+---+" + "| 123 |2-2|2-3|" + "| 345 | | |" + "+ 555 +---+---+" + "| 333 |3-2|3-3|" + "+---+---+---+---+" +); + +test_table!( + _4x4_with_row_span_and_col_span_3, + grid(4, 4) + .change_cell((2, 2), "123\n345\n555\n333") + .config(|cfg|{ + cfg.set_row_span((2, 2), 2); + cfg.set_column_span((2, 2), 2); + cfg.set_alignment_horizontal(Cell(2, 2), AlignmentHorizontal::Center); + cfg.set_alignment_vertical(Cell(2, 2), AlignmentVertical::Center); + }) + .build(), + "+---+---+---+---+" + "|0-0|0-1|0-2|0-3|" + "+---+---+---+---+" + "|1-0|1-1|1-2|1-3|" + "+---+---+---+---+" + "|2-0|2-1| 123 |" + "| | | 345 |" + "+---+---+ 555 +" + "|3-0|3-1| 333 |" + "+---+---+---+---+" +); + +test_table!( + _4x4_with_row_span_and_col_span_4, + grid(4, 4) + .change_cell((0, 2), "123\n345\n555\n333") + .config(|cfg|{ + cfg.set_row_span((0, 2), 2); + cfg.set_column_span((0, 2), 2); + cfg.set_alignment_horizontal(Cell(0, 2), AlignmentHorizontal::Center); + cfg.set_alignment_vertical(Cell(0, 2), AlignmentVertical::Center); + }) + .build(), + "+---+---+---+---+" + "|0-0|0-1| 123 |" + "| | | 345 |" + "+---+---+ 555 +" + "|1-0|1-1| 333 |" + "+---+---+---+---+" + "|2-0|2-1|2-2|2-3|" + "+---+---+---+---+" + "|3-0|3-1|3-2|3-3|" + "+---+---+---+---+" +); + +test_table!( + _4x4_with_row_span_and_col_span_5, + grid(4, 4) + .change_cell((0, 1), "123\n345\n555\n333") + .config(|cfg|{ + cfg.set_row_span((0, 1), 2); + cfg.set_column_span((0, 1), 2); + cfg.set_alignment_horizontal(Cell(0, 1), AlignmentHorizontal::Center); + cfg.set_alignment_vertical(Cell(0, 1), AlignmentVertical::Center); + }) + .build(), + "+---+---+---+---+" + "|0-0| 123 |0-3|" + "| | 345 | |" + "+---+ 555 +---+" + "|1-0| 333 |1-3|" + "+---+---+---+---+" + "|2-0|2-1|2-2|2-3|" + "+---+---+---+---+" + "|3-0|3-1|3-2|3-3|" + "+---+---+---+---+" +); + +test_table!( + _4x4_with_row_span_and_col_span_6, + grid(4, 4) + .change_cell((1, 1), "123\n345\n555\n333") + .config(|cfg|{ + cfg.set_row_span((1, 1), 3); + cfg.set_column_span((1, 1), 3); + cfg.set_alignment_horizontal(Cell(1, 1), AlignmentHorizontal::Center); + cfg.set_alignment_vertical(Cell(1, 1), AlignmentVertical::Center); + }) + .build(), + "+---+---+---+---+" + "|0-0|0-1|0-2|0-3|" + "+---+---+---+---+" + "|1-0| 123 |" + "+---+ 345 +" + "|2-0| 555 |" + "+---+ 333 +" + "|3-0| |" + "+---+---+---+---+" +); + +test_table!( + _4x4_with_row_span_and_col_span_7, + grid(4, 4) + .change_cell((0, 0), "123\n345\n555\n333") + .config(|cfg|{ + cfg.set_row_span((0, 0), 3); + cfg.set_column_span((0, 0), 3); + cfg.set_alignment_horizontal(Cell(0, 0), AlignmentHorizontal::Center); + cfg.set_alignment_vertical(Cell(0, 0), AlignmentVertical::Center); + }) + .build(), + "+---+---+---+---+" + "| 123 |0-3|" + "+ 345 +---+" + "| 555 |1-3|" + "+ 333 +---+" + "| |2-3|" + "+---+---+---+---+" + "|3-0|3-1|3-2|3-3|" + "+---+---+---+---+" +); + +test_table!( + _4x4_with_row_span_and_col_span_8, + grid(4, 4) + .change_cell((0, 1), "123\n345\n555\n333") + .config(|cfg|{ + cfg.set_row_span((0, 1), 3); + cfg.set_column_span((0, 1), 3); + cfg.set_alignment_horizontal(Cell(0, 1), AlignmentHorizontal::Center); + cfg.set_alignment_vertical(Cell(0, 1), AlignmentVertical::Center); + }) + .build(), + "+---+---+---+---+" + "|0-0| 123 |" + "+---+ 345 +" + "|1-0| 555 |" + "+---+ 333 +" + "|2-0| |" + "+---+---+---+---+" + "|3-0|3-1|3-2|3-3|" + "+---+---+---+---+" +); + +test_table!( + _4x4_with_row_span_and_col_span_9, + grid(4, 4) + .change_cell((1, 0), "123\n345\n555\n333") + .config(|cfg|{ + cfg.set_row_span((1, 0), 3); + cfg.set_column_span((1, 0), 3); + cfg.set_alignment_horizontal(Cell(1, 0), AlignmentHorizontal::Center); + cfg.set_alignment_vertical(Cell(1, 0), AlignmentVertical::Center); + }) + .build(), + "+---+---+---+---+" + "|0-0|0-1|0-2|0-3|" + "+---+---+---+---+" + "| 123 |1-3|" + "+ 345 +---+" + "| 555 |2-3|" + "+ 333 +---+" + "| |3-3|" + "+---+---+---+---+" +); + +test_table!( + _4x4_with_row_span_and_col_span_10, + grid(4, 4) + .change_cell((0, 0), "hello\nworld\n!\n!\n!\n!") + .config(|cfg|{ + cfg.set_column_span((1, 1), 2); + cfg.set_column_span((3, 0), 3); + cfg.set_row_span((0, 0), 2); + cfg.set_row_span((0, 3), 3); + }) + .build(), + "+-----+---+---+---+" + "|hello|0-1|0-2|0-3|" + "|world| | | |" + "|! | | | |" + "+! +---+---+ +" + "|! |1-1 | |" + "|! | | |" + "+-----+---+---+ +" + "|2-0 |2-1|2-2| |" + "+-----+---+---+---+" + "|3-0 |3-3|" + "+-----+---+---+---+" +); + +test_table!( + _4x4_with_row_span_and_col_span_11, + grid(4, 4) + .change_cell((0, 2), "q\nw\ne\nr\nt") + .change_cell((0, 3), "q1\nw1\ne1\nr1\nt1") + .config(|cfg|{ + cfg.set_column_span((0, 0), 2); + cfg.set_row_span((0, 2), 2); + cfg.set_row_span((0, 3), 3); + }) + .build(), + "+---+---+---+---+" + "|0-0 |q |q1 |" + "| |w |w1 |" + "+---+---+e +e1 +" + "|1-0|1-1|r |r1 |" + "| | |t |t1 |" + "+---+---+---+ +" + "|2-0|2-1|2-2| |" + "+---+---+---+---+" + "|3-0|3-1|3-2|3-3|" + "+---+---+---+---+" +); + +test_table!( + _3x5_with_row_span_and_col_span_12, + grid(3, 5) + .change_cell((0, 3), "q\nw\ne\nr\nt") + .change_cell((0, 4), "q1\nw1\ne1\nr1\nt1") + .config(|cfg|{ + cfg.set_column_span((0, 0), 2); + cfg.set_row_span((0, 3), 2); + cfg.set_row_span((0, 4), 3); + }) + .build(), + "+---+---+---+---+--+" + "|0-0 |0-2|q |q1|" + "| | |w |w1|" + "+---+---+---+e +e1+" + "|1-0|1-1|1-2|r |r1|" + "| | | |t |t1|" + "+---+---+---+---+ +" + "|2-0|2-1|2-2|2-3| |" + "+---+---+---+---+--+" +); + +test_table!( + _3x5_with_row_span_and_col_span_13, + grid(3, 5) + .change_cell((0, 3), "q\nw\ne\nr\nt\n") + .change_cell((0, 4), "q1\nw1\ne1\nr1\nt1\n") + .config(|cfg|{ + cfg.set_column_span((0, 0), 2); + cfg.set_row_span((0, 3), 2); + cfg.set_row_span((0, 4), 3); + }) + .build(), + "+---+---+---+---+--+" + "|0-0 |0-2|q |q1|" + "| | |w |w1|" + "| | |e |e1|" + "+---+---+---+r +r1+" + "|1-0|1-1|1-2|t |t1|" + "| | | | | |" + "+---+---+---+---+ +" + "|2-0|2-1|2-2|2-3| |" + "+---+---+---+---+--+" +); + +test_table!( + _3x5_with_row_span_and_col_span_14, + grid(3, 5) + .change_cell((0, 3), "q\nw\ne\nr\nt\n") + .change_cell((0, 4), "q1\nw1\ne1\nr1\nt1\n") + .config(|cfg|{ + cfg.set_column_span((0, 0), 2); + cfg.set_row_span((0, 3), 2); + cfg.set_row_span((0, 4), 3); + cfg.set_padding( + Cell(0, 0), + Sides::new(Indent::new(2, ' '), Indent::new(2, ' '), Indent::new(2, ' '), Indent::new(2, ' ')) + ); + cfg.set_padding( + Cell(0, 3), + Sides::new(Indent::new(2, ' '), Indent::new(2, ' '), Indent::new(2, ' '), Indent::new(2, ' ')) + ); + cfg.set_padding( + Cell(1, 2), + Sides::new(Indent::new(2, ' '), Indent::new(2, ' '), Indent::new(4, ' '), Indent::new(2, ' ')) + ); + }) + .build(), + "+---+---+-------+-----+--+" + "| |0-2 | |q1|" + "| | | |w1|" + "| 0-0 | | q |e1|" + "| | | w |r1|" + "| | | e |t1|" + "+---+---+-------+ r + +" + "|1-0|1-1| | t | |" + "| | | | | |" + "| | | | | |" + "| | | | | |" + "| | | 1-2 | | |" + "| | | | | |" + "| | | | | |" + "+---+---+-------+-----+ +" + "|2-0|2-1|2-2 |2-3 | |" + "+---+---+-------+-----+--+" +); + +// is this correct? +test_table!( + _3x4_with_row_span_and_col_span_13, + grid(3, 5) + .config(|cfg|{ + cfg.set_column_span((0, 0), 2); + cfg.set_column_span((1, 0), 2); + cfg.set_row_span((1, 0), 2); + }) + .build(), + "+-+-+---+---+---+" + "|0-0|0-2|0-3|0-4|" + "+-+-+---+---+---+" + "|1-0|1-2|1-3|1-4|" + "+ +---+---+---+" + "| |2-2|2-3|2-4|" + "+-+-+---+---+---+" +); + +test_table!( + _5x2_render_0, + grid(5, 2) + .change_cell((1, 1), "1\n2\n3\n4") + .config(|cfg|{ + cfg.set_row_span((1, 1), 4); + }) + .build(), + "+---+---+" + "|0-0|0-1|" + "+---+---+" + "|1-0|1 |" + "+---+2 +" + "|2-0|3 |" + "+---+4 +" + "|3-0| |" + "+---+ +" + "|4-0| |" + "+---+---+" +); + +test_table!( + _3x4_column_span_0, + grid(3, 4) + .config(|cfg|{ + cfg.set_column_span((0, 0), 2); + cfg.set_column_span((1, 0), 2); + cfg.set_column_span((2, 0), 2); + }) + .build(), + "+-+-+---+---+" + "|0-0|0-2|0-3|" + "+-+-+---+---+" + "|1-0|1-2|1-3|" + "+-+-+---+---+" + "|2-0|2-2|2-3|" + "+-+-+---+---+" +); + +test_table!( + _3x4_column_span_1, + grid(3, 4) + .config(|cfg|{ + cfg.set_column_span((0, 0), 3); + cfg.set_column_span((1, 0), 3); + cfg.set_column_span((2, 0), 3); + }) + .build(), + "+-+++---+" + "|0-0|0-3|" + "+-+++---+" + "|1-0|1-3|" + "+-+++---+" + "|2-0|2-3|" + "+-+++---+" +); + +test_table!( + _3x4_column_span_2, + grid(3, 4) + .change_cell((0, 0), "") + .change_cell((1, 0), "") + .change_cell((2, 0), "") + .config(|cfg|{ + cfg.set_column_span((0, 0), 3); + cfg.set_column_span((1, 0), 3); + cfg.set_column_span((2, 0), 3); + }) + .build(), + "++++---+" + "| |0-3|" + "++++---+" + "| |1-3|" + "++++---+" + "| |2-3|" + "++++---+" +); + +// #[test] +// #[ignore = "todo; create some logic of combining spans? or somehow resolving to not get the following"] +// fn render_grid_with_row_3() { +// let mut grid = util::new_grid::<3, 5>(); + +// grid.set(Entity::Cell(0, 0), Settings::new().span(2)); +// grid.set(Entity::Cell(0, 1), Settings::new().span(2)); +// grid.set(Entity::Cell(0, 2), Settings::new().span(2)); + +// assert_eq!( +// grid.to_string(), +// concat!( +// "+---+---+---+---+--+\n", +// "|0-0 |0-2|q |q1|\n", +// "| | |w |w1|\n", +// "+---+---+---+e +e1+\n", +// "|1-0|1-1|1-2|r |r1|\n", +// "| | | |t |t1|\n", +// "+---+---+---+---+ +\n", +// "|2-0|2-1|2-2|2-3| |\n", +// "+---+---+---+---+--+\n", +// ) +// ); +// } diff --git a/vendor/papergrid/tests/grid/settings.rs b/vendor/papergrid/tests/grid/settings.rs new file mode 100644 index 000000000..7e0446aab --- /dev/null +++ b/vendor/papergrid/tests/grid/settings.rs @@ -0,0 +1,265 @@ +#![cfg(feature = "std")] + +use papergrid::color::AnsiColor; +use papergrid::config::{AlignmentHorizontal, Border, Borders, Entity, Indent, Sides}; + +use crate::util::grid; +use testing_table::test_table; + +test_table!( + override_by_global_alignment_0, + grid(2, 2) + .data([["xxxxx", "xx"], ["y", "yyyyyyyyyy"]]) + .config(|cfg| cfg.set_alignment_horizontal(Entity::Cell(0, 1), AlignmentHorizontal::Right)) + .build(), + "+-----+----------+" + "|xxxxx| xx|" + "+-----+----------+" + "|y |yyyyyyyyyy|" + "+-----+----------+" +); + +test_table!( + override_by_global_alignment_1, + grid(2, 2) + .data([["xxxxx", "xx"], ["y", "yyyyyyyyyy"]]) + .config(|cfg| cfg.set_alignment_horizontal(Entity::Global, AlignmentHorizontal::Center)) + .build(), + "+-----+----------+" + "|xxxxx| xx |" + "+-----+----------+" + "| y |yyyyyyyyyy|" + "+-----+----------+" +); + +test_table!( + remove_border_test, + grid(2, 2) + .config(|cfg| { + cfg.set_borders(Borders::default()); + cfg.set_border( + (0, 0), + Border { + top: Some('x'), + bottom: Some('o'), + left: Some('q'), + ..Default::default() + }, + ); + + cfg.remove_border((0, 0), (2, 2)); + }) + .build(), + "0-00-1\n1-01-1" +); + +test_table!( + entity_row_overrides_column_intersection_0, + grid(2, 2) + .config(|cfg| { + cfg.set_borders(Borders::default()); + cfg.set_padding( + Entity::Column(0), + Sides { + bottom: Indent::new(3, '$'), + ..Default::default() + }, + ); + }) + .build(), + "0-00-1" + "$$$ " + "$$$ " + "$$$ " + "1-01-1" + "$$$ " + "$$$ " + "$$$ " +); + +test_table!( + entity_row_overrides_column_intersection_1, + grid(2, 2) + .config(|cfg| { + cfg.set_borders(Borders::default()); + cfg.set_padding( + Entity::Column(0), + Sides { + bottom: Indent::new(3, '$'), + ..Default::default() + }, + ); + cfg.set_padding( + Entity::Row(1), + Sides { + bottom: Indent::new(2, '#'), + ..Default::default() + }, + ); + }) + .build(), + "0-00-1" + "$$$ " + "$$$ " + "$$$ " + "1-01-1" + "######" + "######" +); + +test_table!( + entity_column_overrides_row_intersection_0, + grid(2, 2) + .config(|cfg| { + cfg.set_borders(Borders::default()); + cfg.set_padding( + Entity::Row(0), + Sides { + bottom: Indent::new(3, '$'), + ..Default::default() + }, + ); + }) + .build(), + "0-00-1\n$$$$$$\n$$$$$$\n$$$$$$\n1-01-1" +); + +test_table!( + entity_column_overrides_row_intersection_1, + grid(2, 2) + .config(|cfg| { + cfg.set_borders(Borders::default()); + cfg.set_padding( + Entity::Row(0), + Sides::new( + Indent::default(), + Indent::default(), + Indent::default(), + Indent::new(3, '$'), + ), + ); + cfg.set_padding( + Entity::Column(1), + Sides::new( + Indent::default(), + Indent::default(), + Indent::default(), + Indent::new(2, '#'), + ), + ); + }) + .build(), + "0-00-1\n$$$###\n$$$###\n$$$###\n1-01-1\n ###\n ###" +); + +test_table!( + test_justification_char_left_alignment, + grid(2, 2) + .data([["Hello", "World"], ["", "Hello Hello Hello Hello Hello"]]) + .config(|cfg| cfg.set_justification(Entity::Global, '$')) + .build(), + "+-----+-----------------------------+" + "|Hello|World$$$$$$$$$$$$$$$$$$$$$$$$|" + "+-----+-----------------------------+" + "|$$$$$|Hello Hello Hello Hello Hello|" + "+-----+-----------------------------+" +); + +test_table!( + test_justification_char_right_alignment, + grid(2, 2) + .data([["Hello", "World"], ["", "Hello Hello Hello Hello Hello"]]) + .config(|cfg| { + cfg.set_justification(Entity::Global, '$'); + cfg.set_alignment_horizontal(Entity::Global, AlignmentHorizontal::Right); + }) + .build(), + "+-----+-----------------------------+" + "|Hello|$$$$$$$$$$$$$$$$$$$$$$$$World|" + "+-----+-----------------------------+" + "|$$$$$|Hello Hello Hello Hello Hello|" + "+-----+-----------------------------+" +); + +test_table!( + test_justification_char_center_alignment, + grid(2, 2) + .data([["Hello", "World"], ["", "Hello Hello Hello Hello Hello"]]) + .config(|cfg| { + cfg.set_justification(Entity::Global, '$'); + cfg.set_alignment_horizontal(Entity::Global, AlignmentHorizontal::Center); + }) + .build(), + "+-----+-----------------------------+" + "|Hello|$$$$$$$$$$$$World$$$$$$$$$$$$|" + "+-----+-----------------------------+" + "|$$$$$|Hello Hello Hello Hello Hello|" + "+-----+-----------------------------+" +); + +test_table!( + test_justification_color_left_alignment, + grid(2, 2) + .data([["Hello", "World"], ["", "Hello Hello Hello Hello Hello"]]) + .config(|cfg| { + cfg.set_justification(Entity::Global, '$'); + cfg.set_justification_color(Entity::Global, Some(AnsiColor::new("\u{1b}[34m".into(), "\u{1b}[39m".into()))); + }) + .build(), + "+-----+-----------------------------+" + "|Hello|World\u{1b}[34m$$$$$$$$$$$$$$$$$$$$$$$$\u{1b}[39m|" + "+-----+-----------------------------+" + "|\u{1b}[34m$$$$$\u{1b}[39m|Hello Hello Hello Hello Hello|" + "+-----+-----------------------------+" +); + +test_table!( + test_justification_color_right_alignment, + grid(2, 2) + .data([["Hello", "World"], ["", "Hello Hello Hello Hello Hello"]]) + .config(|cfg| { + cfg.set_justification(Entity::Global, '$'); + cfg.set_justification_color(Entity::Global, Some(AnsiColor::new("\u{1b}[34m".into(), "\u{1b}[39m".into()))); + cfg.set_alignment_horizontal(Entity::Global, AlignmentHorizontal::Right); + }) + .build(), + "+-----+-----------------------------+" + "|Hello|\u{1b}[34m$$$$$$$$$$$$$$$$$$$$$$$$\u{1b}[39mWorld|" + "+-----+-----------------------------+" + "|\u{1b}[34m$$$$$\u{1b}[39m|Hello Hello Hello Hello Hello|" + "+-----+-----------------------------+" +); + +test_table!( + test_justification_color_center_alignment, + grid(2, 2) + .data([["Hello", "World"], ["", "Hello Hello Hello Hello Hello"]]) + .config(|cfg| { + cfg.set_justification(Entity::Global, '$'); + cfg.set_justification_color(Entity::Global, Some(AnsiColor::new("\u{1b}[34m".into(), "\u{1b}[39m".into()))); + cfg.set_alignment_horizontal(Entity::Global, AlignmentHorizontal::Center); + }) + .build(), + "+-----+-----------------------------+" + "|Hello|\u{1b}[34m$$$$$$$$$$$$\u{1b}[39mWorld\u{1b}[34m$$$$$$$$$$$$\u{1b}[39m|" + "+-----+-----------------------------+" + "|\u{1b}[34m$$\u{1b}[39m\u{1b}[34m$$$\u{1b}[39m|Hello Hello Hello Hello Hello|" + "+-----+-----------------------------+" +); + +test_table!( + test_justification_color_center_alignment_entity, + grid(2, 2) + .data([["Hello", "World"], ["", "Hello Hello Hello Hello Hello"]]) + .config(|cfg| { + cfg.set_justification(Entity::Cell(0, 0), '$'); + cfg.set_justification_color(Entity::Column(1), Some(AnsiColor::new("\u{1b}[34m".into(), "\u{1b}[39m".into()))); + cfg.set_alignment_horizontal(Entity::Row(2), AlignmentHorizontal::Center); + }) + .build(), + "+-----+-----------------------------+" + "|Hello|World\u{1b}[34m \u{1b}[39m|" + "+-----+-----------------------------+" + "| |Hello Hello Hello Hello Hello|" + "+-----+-----------------------------+" +); diff --git a/vendor/papergrid/tests/grid/styling.rs b/vendor/papergrid/tests/grid/styling.rs new file mode 100644 index 000000000..b1886a8de --- /dev/null +++ b/vendor/papergrid/tests/grid/styling.rs @@ -0,0 +1,334 @@ +#![cfg(feature = "std")] + +use papergrid::config::{AlignmentHorizontal, Border, Borders, Entity, Indent, Sides}; + +use crate::util::grid; +use testing_table::test_table; + +#[cfg(feature = "color")] +use ::{owo_colors::OwoColorize, papergrid::color::AnsiColor, std::convert::TryFrom}; + +test_table!( + grid_2x2_custom_frame_test, + grid(2, 2) + .config(|cfg| (0..2).for_each(|r| (0..2).for_each(|c| cfg.set_border((r, c), Border::full('*', '*', '|', '|', '#', '#', '#', '#'))))) + .build(), + "#***#***#" + "|0-0|0-1|" + "#***#***#" + "|1-0|1-1|" + "#***#***#" +); + +test_table!( + grid_2x2_custom_column_test_0, + grid(2, 2) + .config(|cfg| (0..2).for_each(|r| cfg.set_border((r, 1), Border::full('*', '*', '|', '|', '#', '#', '#', '#')))) + .build(), + "+---#***#" + "|0-0|0-1|" + "+---#***#" + "|1-0|1-1|" + "+---#***#" +); + +test_table!( + grid_2x2_custom_column_test_1, + grid(2, 2) + .config(|cfg| (0..2).for_each(|r| cfg.set_border((r, 0), Border::full('*', '*', '|', '|', '#', '#', '#', '#')))) + .build(), + "#***#---+" + "|0-0|0-1|" + "#***#---+" + "|1-0|1-1|" + "#***#---+" +); + +test_table!( + grid_2x2_custom_row_test_0, + grid(2, 2) + .config(|cfg| (0..2).for_each(|c| cfg.set_border((0, c), Border::full('*', '*', '|', '|', '#', '#', '#', '#')))) + .build(), + "#***#***#" + "|0-0|0-1|" + "#***#***#" + "|1-0|1-1|" + "+---+---+" +); + +test_table!( + grid_2x2_custom_row_test_1, + grid(2, 2) + .config(|cfg| (0..2).for_each(|c| cfg.set_border((1, c), Border::full('*', '*', '|', '|', '#', '#', '#', '#')))) + .build(), + "+---+---+" + "|0-0|0-1|" + "#***#***#" + "|1-0|1-1|" + "#***#***#" +); + +test_table!( + grid_2x2_change_cell_border_test_0, + grid(2, 2) + .config(|cfg| (0..2).for_each(|_| cfg.set_border((0, 1), Border::full('*', '^', '@', '#', '~', '!', '%', '&')))) + .build(), + "+---~***!" + "|0-0@0-1#" + "+---%^^^&" + "|1-0|1-1|" + "+---+---+" +); + +test_table!( + grid_2x2_alignment_test_0, + grid(2, 2) + .change_cell((0, 0), "asd ") + .change_cell((0, 1), "asd ") + .config(|cfg| { + cfg.set_alignment_horizontal(Entity::Column(0), AlignmentHorizontal::Left); + cfg.set_alignment_horizontal(Entity::Column(1), AlignmentHorizontal::Right); + }) + .build(), + "+-------+-------+" + "|asd |asd |" + "+-------+-------+" + "|1-0 | 1-1|" + "+-------+-------+" +); + +test_table!( + grid_2x2_alignment_test_1, + grid(2, 2) + .data([["asd ", "asd "], ["asd ", "asd "]]) + .config(|cfg| { + cfg.set_alignment_horizontal(Entity::Column(0), AlignmentHorizontal::Left); + cfg.set_alignment_horizontal(Entity::Column(1), AlignmentHorizontal::Right); + }) + .build(), + "+-------+-------+" + "|asd |asd |" + "+-------+-------+" + "|asd |asd |" + "+-------+-------+" +); + +test_table!( + grid_2x2_indent_test, + grid(2, 2) + .config(|cfg| { + cfg.set_padding( + Entity::Global, + Sides::new( + Indent::spaced(1), + Indent::spaced(1), + Indent::spaced(1), + Indent::spaced(1), + ), + ); + cfg.set_padding(Entity::Column(0), Sides::new( + Indent::default(), + Indent::default(), + Indent::default(), + Indent::default(), + )); + }) + .build(), + "+---+-----+" + "|0-0| |" + "| | 0-1 |" + "| | |" + "+---+-----+" + "|1-0| |" + "| | 1-1 |" + "| | |" + "+---+-----+" +); + +test_table!( + grid_2x2_vertical_resize_test, + grid(2, 2).change_cell((1, 1), "asd ").build(), + "+---+--------+" + "|0-0|0-1 |" + "+---+--------+" + "|1-0|asd |" + "+---+--------+" +); + +test_table!( + grid_2x2_without_frame_test_0, + grid(2, 2) + .config(|cfg| { + cfg.set_borders(Borders { + vertical: Some(' '), + ..Default::default() + }); + }) + .build(), + "0-0 0-1" + "1-0 1-1" +); + +test_table!( + grid_2x2_without_frame_test_1, + grid(2, 2) + .config(|cfg| { + cfg.set_borders(Borders { + vertical: Some(' '), + horizontal: Some(' '), + intersection: Some(' '), + ..Default::default() + }); + }) + .build(), + "0-0 0-1" + " " + "1-0 1-1" +); + +test_table!( + grid_2x2_custom_border_test, + grid(2, 2) + .config(|cfg| { + cfg.set_border( + (0, 0), + Border { + bottom: Some('-'), + top: Some('*'), + left: Some('$'), + left_top_corner: Some(' '), + left_bottom_corner: Some('+'), + ..Default::default() + }, + ); + cfg.set_border( + (0, 1), + Border::full('*', '-', '@', '%', ' ', ' ', '+', '+'), + ); + cfg.set_border( + (1, 0), + Border { + bottom: Some('*'), + left: Some('#'), + left_top_corner: Some('+'), + left_bottom_corner: Some('\u{0020}'), + ..Default::default() + }, + ); + cfg.set_border( + (1, 1), + Border { + bottom: Some('*'), + left: Some('^'), + left_top_corner: Some('+'), + right_top_corner: Some('+'), + right: Some('!'), + left_bottom_corner: Some(' '), + right_bottom_corner: Some(' '), + ..Default::default() + }, + ); + }) + .build(), + " *** *** " + "$0-0@0-1%" + "+---+---+" + "#1-0^1-1!" + "\u{0020}*** *** " +); + +test_table!( + when_border_is_not_complete_default_char_is_used_test, + grid(2, 2) + .config(|cfg| { + cfg.set_borders(Borders { + vertical: Some(' '), + ..Default::default() + }); + cfg.set_border( + (1, 1), + Border { + top: Some('*'), + ..Default::default() + }, + ); + }) + .build(), + "0-0 0-1" + " ***" + "1-0 1-1" +); + +test_table!( + when_1_vertical_is_set_second_must_use_default_test, + grid(2, 2) + .config(|cfg| { + cfg.set_borders(Borders::default()); + cfg.set_border( + (1, 0), + Border { + right: Some('*'), + ..Default::default() + }, + ); + }) + .build(), + "0-0 0-1" + "1-0*1-1" +); + +#[cfg(feature = "color")] +test_table!( + grid_2x2_ansi_border_test, + grid(2, 2) + .config(|cfg| { + (0..2).for_each(|r| (0..2).for_each(|c| { + let top = AnsiColor::try_from(" ".green().on_red().to_string()).unwrap(); + let bottom = AnsiColor::try_from(" ".on_green().blue().to_string()).unwrap(); + let left = AnsiColor::try_from(" ".on_red().white().to_string()).unwrap(); + let right = AnsiColor::try_from(" ".on_red().green().to_string()).unwrap(); + let tl = AnsiColor::try_from(" ".magenta().to_string()).unwrap(); + let tr = AnsiColor::try_from(" ".on_blue().to_string()).unwrap(); + let bl = AnsiColor::try_from(" ".yellow().to_string()).unwrap(); + let br = AnsiColor::try_from(" ".on_yellow().to_string()).unwrap(); + + cfg.set_border((r, c), Border::full('*', '#', '~', '!', '@', '$', '%', '^')); + cfg.set_border_color((r, c), Border::full(top, bottom, left, right, tl, tr, bl, br)); + })) + }) + .build(), + "\u{1b}[35m@\u{1b}[39m\u{1b}[32m\u{1b}[41m***\u{1b}[39m\u{1b}[49m\u{1b}[35m@\u{1b}[39m\u{1b}[32m\u{1b}[41m***\u{1b}[39m\u{1b}[49m\u{1b}[44m$\u{1b}[49m" + "\u{1b}[37m\u{1b}[41m~\u{1b}[39m\u{1b}[49m0-0\u{1b}[37m\u{1b}[41m~\u{1b}[39m\u{1b}[49m0-1\u{1b}[32m\u{1b}[41m!\u{1b}[39m\u{1b}[49m" + "\u{1b}[35m@\u{1b}[39m\u{1b}[32m\u{1b}[41m***\u{1b}[39m\u{1b}[49m\u{1b}[35m@\u{1b}[39m\u{1b}[32m\u{1b}[41m***\u{1b}[39m\u{1b}[49m\u{1b}[44m$\u{1b}[49m" + "\u{1b}[37m\u{1b}[41m~\u{1b}[39m\u{1b}[49m1-0\u{1b}[37m\u{1b}[41m~\u{1b}[39m\u{1b}[49m1-1\u{1b}[32m\u{1b}[41m!\u{1b}[39m\u{1b}[49m" + "\u{1b}[33m%\u{1b}[39m\u{1b}[34m\u{1b}[42m###\u{1b}[39m\u{1b}[49m\u{1b}[33m%\u{1b}[39m\u{1b}[34m\u{1b}[42m###\u{1b}[39m\u{1b}[49m\u{1b}[43m^\u{1b}[49m" +); + +#[cfg(feature = "color")] +test_table!( + grid_2x2_ansi_global_set_test, + grid(2, 2) + .config(|cfg| { + let color = " ".on_blue().red().bold().to_string(); + cfg.set_border_color_global(AnsiColor::try_from(color).unwrap()); + }) + .build(), + "\u{1b}[1m\u{1b}[31m\u{1b}[44m+---+---+\u{1b}[22m\u{1b}[39m\u{1b}[49m" + "\u{1b}[1m\u{1b}[31m\u{1b}[44m|\u{1b}[22m\u{1b}[39m\u{1b}[49m0-0\u{1b}[1m\u{1b}[31m\u{1b}[44m|\u{1b}[22m\u{1b}[39m\u{1b}[49m0-1\u{1b}[1m\u{1b}[31m\u{1b}[44m|\u{1b}[22m\u{1b}[39m\u{1b}[49m" + "\u{1b}[1m\u{1b}[31m\u{1b}[44m+---+---+\u{1b}[22m\u{1b}[39m\u{1b}[49m" + "\u{1b}[1m\u{1b}[31m\u{1b}[44m|\u{1b}[22m\u{1b}[39m\u{1b}[49m1-0\u{1b}[1m\u{1b}[31m\u{1b}[44m|\u{1b}[22m\u{1b}[39m\u{1b}[49m1-1\u{1b}[1m\u{1b}[31m\u{1b}[44m|\u{1b}[22m\u{1b}[39m\u{1b}[49m" + "\u{1b}[1m\u{1b}[31m\u{1b}[44m+---+---+\u{1b}[22m\u{1b}[39m\u{1b}[49m" +); + +#[cfg(feature = "color")] +#[test] +fn grid_2x2_ansi_border_none_if_string_is_not_1_char_test() { + assert!(AnsiColor::try_from("12").is_ok()); + assert!(AnsiColor::try_from("123").is_ok()); + assert!(AnsiColor::try_from("").is_err()); + + assert!(AnsiColor::try_from("1").is_ok()); + assert!(AnsiColor::try_from("1".on_red().to_string()).is_ok()); + assert!(AnsiColor::try_from("1".on_red().blue().to_string()).is_ok()); + assert!(AnsiColor::try_from("1".truecolor(0, 1, 3).on_truecolor(1, 2, 3).to_string()).is_ok()); +} diff --git a/vendor/papergrid/tests/main.rs b/vendor/papergrid/tests/main.rs new file mode 100644 index 000000000..e34e9e61c --- /dev/null +++ b/vendor/papergrid/tests/main.rs @@ -0,0 +1,3 @@ +mod grid; + +mod util; diff --git a/vendor/papergrid/tests/util/grid_builder.rs b/vendor/papergrid/tests/util/grid_builder.rs new file mode 100644 index 000000000..c2a82b87a --- /dev/null +++ b/vendor/papergrid/tests/util/grid_builder.rs @@ -0,0 +1,139 @@ +#![cfg(feature = "std")] +#![allow(dead_code, unused_macros, unused_imports)] + +use std::collections::HashMap; + +use papergrid::{ + colors::NoColors, + config::spanned::SpannedConfig, + config::{Borders, Position}, + dimension::spanned::SpannedGridDimension, + dimension::{Dimension, Estimate}, + grid::iterable::Grid, + records::{IterRecords, Records}, +}; + +pub fn grid(rows: usize, cols: usize) -> GridBuilder { + GridBuilder::new(rows, cols) +} + +#[derive(Debug, Default, Clone)] +pub struct GridBuilder { + size: (usize, usize), + cfg: SpannedConfig, + data: HashMap<Position, String>, +} + +impl GridBuilder { + pub fn new(rows: usize, cols: usize) -> Self { + let mut cfg = SpannedConfig::default(); + cfg.set_borders(DEFAULT_BORDERS); + + Self { + size: (rows, cols), + cfg, + ..Default::default() + } + } + + pub fn config(mut self, mut f: impl FnMut(&mut SpannedConfig)) -> Self { + f(&mut self.cfg); + self + } + + pub fn data( + mut self, + data: impl IntoIterator<Item = impl IntoIterator<Item = impl Into<String>>>, + ) -> Self { + for (i, rows) in data.into_iter().enumerate() { + for (j, text) in rows.into_iter().enumerate() { + let text = text.into(); + self.data.insert((i, j), text); + } + } + + self + } + + pub fn change_cell(mut self, pos: Position, text: impl Into<String>) -> Self { + self.data.insert(pos, text.into()); + self + } + + pub fn build(self) -> String { + let mut data = records(self.size.0, self.size.1); + for ((row, col), text) in self.data { + data[row][col] = text; + } + + let grid = build_grid(data, self.cfg, self.size); + grid.to_string() + } +} + +fn build_grid( + data: Vec<Vec<String>>, + cfg: SpannedConfig, + shape: (usize, usize), +) -> Grid<IterRecords<Vec<Vec<String>>>, SpannedGridDimension, SpannedConfig, NoColors> { + let records = IterRecords::new(data, shape.1, Some(shape.0)); + + let mut dims = SpannedGridDimension::default(); + dims.estimate(&records, &cfg); + + Grid::new(records, dims, cfg, NoColors) +} + +fn records(rows: usize, cols: usize) -> Vec<Vec<String>> { + let mut records = vec![vec![String::new(); cols]; rows]; + (0..rows).for_each(|row| { + (0..cols).for_each(|col| { + let text = format!("{row}-{col}"); + records[row][col] = text; + }); + }); + + records +} + +pub const DEFAULT_BORDERS: Borders<char> = Borders { + top: Some('-'), + top_left: Some('+'), + top_right: Some('+'), + top_intersection: Some('+'), + + bottom: Some('-'), + bottom_left: Some('+'), + bottom_right: Some('+'), + bottom_intersection: Some('+'), + + horizontal: Some('-'), + left_intersection: Some('+'), + right_intersection: Some('+'), + + left: Some('|'), + right: Some('|'), + vertical: Some('|'), + + intersection: Some('+'), +}; + +/// A [`Estimate`]or of a width for a [`Grid`]. +/// +/// [`Grid`]: crate::grid::iterable::Grid +#[derive(Debug, Default, Clone, PartialEq, Eq)] +pub struct ConstantDimension(pub Vec<usize>, pub Vec<usize>); + +impl Dimension for ConstantDimension { + fn get_width(&self, column: usize) -> usize { + self.0[column] + } + + fn get_height(&self, row: usize) -> usize { + self.1[row] + } +} + +impl<R> Estimate<R, SpannedConfig> for ConstantDimension { + fn estimate(&mut self, _: R, _: &SpannedConfig) {} +} diff --git a/vendor/papergrid/tests/util/mod.rs b/vendor/papergrid/tests/util/mod.rs new file mode 100644 index 000000000..26c629e14 --- /dev/null +++ b/vendor/papergrid/tests/util/mod.rs @@ -0,0 +1,4 @@ +mod grid_builder; + +#[cfg(feature = "std")] +pub use grid_builder::*; |