summaryrefslogtreecommitdiffstats
path: root/vendor/papergrid
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-06-07 05:48:48 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-06-07 05:48:48 +0000
commitef24de24a82fe681581cc130f342363c47c0969a (patch)
tree0d494f7e1a38b95c92426f58fe6eaa877303a86c /vendor/papergrid
parentReleasing progress-linux version 1.74.1+dfsg1-1~progress7.99u1. (diff)
downloadrustc-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')
-rw-r--r--vendor/papergrid/.cargo-checksum.json1
-rw-r--r--vendor/papergrid/Cargo.lock137
-rw-r--r--vendor/papergrid/Cargo.toml85
-rw-r--r--vendor/papergrid/LICENSE-MIT21
-rw-r--r--vendor/papergrid/README.md84
-rw-r--r--vendor/papergrid/examples/color_map.rs105
-rw-r--r--vendor/papergrid/examples/colored_border.rs74
-rw-r--r--vendor/papergrid/examples/common_grid.rs67
-rw-r--r--vendor/papergrid/examples/common_grid_no_std.rs73
-rw-r--r--vendor/papergrid/examples/hello_world.rs78
-rw-r--r--vendor/papergrid/examples/papergrid_color.rs69
-rw-r--r--vendor/papergrid/examples/span_usage.rs63
-rw-r--r--vendor/papergrid/src/color/ansi_color.rs86
-rw-r--r--vendor/papergrid/src/color/mod.rs51
-rw-r--r--vendor/papergrid/src/color/static_color.rs49
-rw-r--r--vendor/papergrid/src/colors.rs89
-rw-r--r--vendor/papergrid/src/config/alignment.rs21
-rw-r--r--vendor/papergrid/src/config/border.rs142
-rw-r--r--vendor/papergrid/src/config/borders.rs152
-rw-r--r--vendor/papergrid/src/config/compact/mod.rs141
-rw-r--r--vendor/papergrid/src/config/entity.rs120
-rw-r--r--vendor/papergrid/src/config/indent.rs31
-rw-r--r--vendor/papergrid/src/config/line.rs42
-rw-r--r--vendor/papergrid/src/config/mod.rs23
-rw-r--r--vendor/papergrid/src/config/position.rs13
-rw-r--r--vendor/papergrid/src/config/sides.rs37
-rw-r--r--vendor/papergrid/src/config/spanned/borders_config.rs486
-rw-r--r--vendor/papergrid/src/config/spanned/entity_map.rs108
-rw-r--r--vendor/papergrid/src/config/spanned/formatting.rs21
-rw-r--r--vendor/papergrid/src/config/spanned/mod.rs887
-rw-r--r--vendor/papergrid/src/config/spanned/offset.rs8
-rw-r--r--vendor/papergrid/src/dimension/compact.rs121
-rw-r--r--vendor/papergrid/src/dimension/mod.rs44
-rw-r--r--vendor/papergrid/src/dimension/spanned.rs329
-rw-r--r--vendor/papergrid/src/dimension/spanned_vec_records.rs332
-rw-r--r--vendor/papergrid/src/grid/compact.rs659
-rw-r--r--vendor/papergrid/src/grid/iterable.rs1358
-rw-r--r--vendor/papergrid/src/grid/mod.rs9
-rw-r--r--vendor/papergrid/src/grid/peekable.rs976
-rw-r--r--vendor/papergrid/src/lib.rs87
-rw-r--r--vendor/papergrid/src/records/exact_records.rs31
-rw-r--r--vendor/papergrid/src/records/into_records.rs33
-rw-r--r--vendor/papergrid/src/records/iter_records.rs87
-rw-r--r--vendor/papergrid/src/records/mod.rs31
-rw-r--r--vendor/papergrid/src/records/peekable_records.rs52
-rw-r--r--vendor/papergrid/src/records/vec_records/cell.rs19
-rw-r--r--vendor/papergrid/src/records/vec_records/cell_info.rs170
-rw-r--r--vendor/papergrid/src/records/vec_records/mod.rs124
-rw-r--r--vendor/papergrid/src/util/mod.rs3
-rw-r--r--vendor/papergrid/src/util/string.rs270
-rw-r--r--vendor/papergrid/tests/grid/column_span.rs431
-rw-r--r--vendor/papergrid/tests/grid/format_configuration.rs953
-rw-r--r--vendor/papergrid/tests/grid/mod.rs6
-rw-r--r--vendor/papergrid/tests/grid/render.rs251
-rw-r--r--vendor/papergrid/tests/grid/row_span.rs1062
-rw-r--r--vendor/papergrid/tests/grid/settings.rs265
-rw-r--r--vendor/papergrid/tests/grid/styling.rs334
-rw-r--r--vendor/papergrid/tests/main.rs3
-rw-r--r--vendor/papergrid/tests/util/grid_builder.rs139
-rw-r--r--vendor/papergrid/tests/util/mod.rs4
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::*;