mirror of
https://github.com/nushell/nushell
synced 2025-01-12 21:29:07 +00:00
Give tabled a try (#5969)
* Drop in replacement from nu-table to tabled. Must act the same way as original nu-table. Signed-off-by: Maxim Zhiburt <zhiburt@gmail.com> Fix some issues Signed-off-by: Maxim Zhiburt <zhiburt@gmail.com> * Bump ansi-str version Signed-off-by: Maxim Zhiburt <zhiburt@gmail.com> * Update to latest Signed-off-by: Maxim Zhiburt <zhiburt@gmail.com> * Fix footer issue Signed-off-by: Maxim Zhiburt <zhiburt@gmail.com> * Fix header alignment Signed-off-by: Maxim Zhiburt <zhiburt@gmail.com> * Fix header style Signed-off-by: Maxim Zhiburt <zhiburt@gmail.com> * Use latest tabled/ansi-str * Refactorings Signed-off-by: Maxim Zhiburt <zhiburt@gmail.com> * Fix clippy warnings Signed-off-by: Maxim Zhiburt <zhiburt@gmail.com>
This commit is contained in:
parent
e77219a59f
commit
d1687df067
9 changed files with 667 additions and 1053 deletions
149
Cargo.lock
generated
149
Cargo.lock
generated
|
@ -35,7 +35,7 @@ checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"getrandom 0.2.6",
|
"getrandom 0.2.6",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"version_check 0.9.4",
|
"version_check",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -69,22 +69,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "77e9c9abb82613923ec78d7a461595d52491ba7240f3c64c0bbe0e6d98e0fce0"
|
checksum = "77e9c9abb82613923ec78d7a461595d52491ba7240f3c64c0bbe0e6d98e0fce0"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ansi-parser"
|
name = "ansi-str"
|
||||||
version = "0.8.0"
|
version = "0.2.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "git+https://github.com/zhiburt/ansi-str?rev=655cd8125a032286082794690c2cc6dc835345b4#655cd8125a032286082794690c2cc6dc835345b4"
|
||||||
checksum = "bcb2392079bf27198570d6af79ecbd9ec7d8f16d3ec6b60933922fdb66287127"
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"heapless 0.5.6",
|
"ansi_rs",
|
||||||
"nom 4.2.3",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ansi-str"
|
name = "ansi_rs"
|
||||||
version = "0.2.0"
|
version = "0.1.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "git+https://gitlab.com/zhiburt/ansi_rs#f45b8378e5ac7bafc0ca2d2748505fe92e2af6d6"
|
||||||
checksum = "e04d04a41a1463228d6eed971b9cdadd86b5577c69c09fd2379c57fe5e159071"
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"ansi-parser",
|
"nom 7.1.1",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -164,18 +161,6 @@ dependencies = [
|
||||||
"strength_reduce",
|
"strength_reduce",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "as-slice"
|
|
||||||
version = "0.1.5"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "45403b49e3954a4b8428a0ac21a4b7afadccf92bfd96273f1a58cd4812496ae0"
|
|
||||||
dependencies = [
|
|
||||||
"generic-array 0.12.4",
|
|
||||||
"generic-array 0.13.3",
|
|
||||||
"generic-array 0.14.5",
|
|
||||||
"stable_deref_trait",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "assert_cmd"
|
name = "assert_cmd"
|
||||||
version = "2.0.4"
|
version = "2.0.4"
|
||||||
|
@ -317,7 +302,7 @@ version = "0.9.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4"
|
checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"generic-array 0.14.5",
|
"generic-array",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -326,7 +311,7 @@ version = "0.10.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "0bf7fe51849ea569fd452f37822f606a5cabb684dc918707a0193fd4664ff324"
|
checksum = "0bf7fe51849ea569fd452f37822f606a5cabb684dc918707a0193fd4664ff324"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"generic-array 0.14.5",
|
"generic-array",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -386,6 +371,12 @@ dependencies = [
|
||||||
"utf8-width",
|
"utf8-width",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "bytecount"
|
||||||
|
version = "0.6.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2c676a478f63e9fa2dd5368a42f28bba0d6c560b775f38583c8bbaa7fcd67c9c"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bytemuck"
|
name = "bytemuck"
|
||||||
version = "1.9.1"
|
version = "1.9.1"
|
||||||
|
@ -724,7 +715,7 @@ version = "0.1.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "57952ca27b5e3606ff4dd79b0020231aaf9d6aa76dc05fd30137538c50bd3ce8"
|
checksum = "57952ca27b5e3606ff4dd79b0020231aaf9d6aa76dc05fd30137538c50bd3ce8"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"generic-array 0.14.5",
|
"generic-array",
|
||||||
"typenum",
|
"typenum",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -856,7 +847,7 @@ version = "0.9.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066"
|
checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"generic-array 0.14.5",
|
"generic-array",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -1329,24 +1320,6 @@ dependencies = [
|
||||||
"byteorder",
|
"byteorder",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "generic-array"
|
|
||||||
version = "0.12.4"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "ffdf9f34f1447443d37393cc6c2b8313aebddcd96906caf34e54c68d8e57d7bd"
|
|
||||||
dependencies = [
|
|
||||||
"typenum",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "generic-array"
|
|
||||||
version = "0.13.3"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "f797e67af32588215eaaab8327027ee8e71b9dd0b2b26996aedf20c030fce309"
|
|
||||||
dependencies = [
|
|
||||||
"typenum",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "generic-array"
|
name = "generic-array"
|
||||||
version = "0.14.5"
|
version = "0.14.5"
|
||||||
|
@ -1354,7 +1327,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "fd48d33ec7f05fbfa152300fdad764757cbded343c1aa1cff2fbaf4134851803"
|
checksum = "fd48d33ec7f05fbfa152300fdad764757cbded343c1aa1cff2fbaf4134851803"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"typenum",
|
"typenum",
|
||||||
"version_check 0.9.4",
|
"version_check",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -1483,15 +1456,6 @@ dependencies = [
|
||||||
"regex",
|
"regex",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "hash32"
|
|
||||||
version = "0.1.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "d4041af86e63ac4298ce40e5cca669066e75b6f1aa3390fe2561ffa5e1d9f4cc"
|
|
||||||
dependencies = [
|
|
||||||
"byteorder",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "hash32"
|
name = "hash32"
|
||||||
version = "0.2.1"
|
version = "0.2.1"
|
||||||
|
@ -1535,18 +1499,6 @@ dependencies = [
|
||||||
"hashbrown 0.11.2",
|
"hashbrown 0.11.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "heapless"
|
|
||||||
version = "0.5.6"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "74911a68a1658cfcfb61bc0ccfbd536e3b6e906f8c2f7883ee50157e3e2184f1"
|
|
||||||
dependencies = [
|
|
||||||
"as-slice",
|
|
||||||
"generic-array 0.13.3",
|
|
||||||
"hash32 0.1.1",
|
|
||||||
"stable_deref_trait",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "heapless"
|
name = "heapless"
|
||||||
version = "0.7.13"
|
version = "0.7.13"
|
||||||
|
@ -1554,7 +1506,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "8a08e755adbc0ad283725b29f4a4883deee15336f372d5f61fae59efec40f983"
|
checksum = "8a08e755adbc0ad283725b29f4a4883deee15336f372d5f61fae59efec40f983"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"atomic-polyfill",
|
"atomic-polyfill",
|
||||||
"hash32 0.2.1",
|
"hash32",
|
||||||
"rustc_version 0.4.0",
|
"rustc_version 0.4.0",
|
||||||
"spin",
|
"spin",
|
||||||
"stable_deref_trait",
|
"stable_deref_trait",
|
||||||
|
@ -2412,16 +2364,6 @@ version = "1.2.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a5b8c256fd9471521bcb84c3cdba98921497f1a331cbc15b8030fc63b82050ce"
|
checksum = "a5b8c256fd9471521bcb84c3cdba98921497f1a331cbc15b8030fc63b82050ce"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "nom"
|
|
||||||
version = "4.2.3"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "2ad2a91a8e869eeb30b9cb3119ae87773a8f4ae617f41b1eb9c154b2905f7bd6"
|
|
||||||
dependencies = [
|
|
||||||
"memchr",
|
|
||||||
"version_check 0.1.5",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "nom"
|
name = "nom"
|
||||||
version = "7.1.1"
|
version = "7.1.1"
|
||||||
|
@ -2722,7 +2664,7 @@ dependencies = [
|
||||||
name = "nu-pretty-hex"
|
name = "nu-pretty-hex"
|
||||||
version = "0.65.1"
|
version = "0.65.1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"heapless 0.7.13",
|
"heapless",
|
||||||
"nu-ansi-term",
|
"nu-ansi-term",
|
||||||
"rand 0.8.5",
|
"rand 0.8.5",
|
||||||
]
|
]
|
||||||
|
@ -2771,6 +2713,7 @@ dependencies = [
|
||||||
"nu-protocol",
|
"nu-protocol",
|
||||||
"regex",
|
"regex",
|
||||||
"strip-ansi-escapes",
|
"strip-ansi-escapes",
|
||||||
|
"tabled",
|
||||||
"unicode-width",
|
"unicode-width",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -3092,6 +3035,17 @@ version = "3.4.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "decf7381921fea4dcb2549c5667eda59b3ec297ab7e2b5fc33eac69d2e7da87b"
|
checksum = "decf7381921fea4dcb2549c5667eda59b3ec297ab7e2b5fc33eac69d2e7da87b"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "papergrid"
|
||||||
|
version = "0.4.0"
|
||||||
|
source = "git+https://github.com/zhiburt/tabled?rev=e3cbdea5b81edda8b3cc7b9f64f98fecae7db423#e3cbdea5b81edda8b3cc7b9f64f98fecae7db423"
|
||||||
|
dependencies = [
|
||||||
|
"ansi-str",
|
||||||
|
"bytecount",
|
||||||
|
"strip-ansi-escapes",
|
||||||
|
"unicode-width",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "parking_lot"
|
name = "parking_lot"
|
||||||
version = "0.11.2"
|
version = "0.11.2"
|
||||||
|
@ -3544,7 +3498,7 @@ dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn",
|
"syn",
|
||||||
"version_check 0.9.4",
|
"version_check",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -3555,7 +3509,7 @@ checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"version_check 0.9.4",
|
"version_check",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -4658,6 +4612,29 @@ dependencies = [
|
||||||
"winapi 0.3.9",
|
"winapi 0.3.9",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tabled"
|
||||||
|
version = "0.7.0"
|
||||||
|
source = "git+https://github.com/zhiburt/tabled?rev=e3cbdea5b81edda8b3cc7b9f64f98fecae7db423#e3cbdea5b81edda8b3cc7b9f64f98fecae7db423"
|
||||||
|
dependencies = [
|
||||||
|
"ansi-str",
|
||||||
|
"papergrid",
|
||||||
|
"tabled_derive",
|
||||||
|
"unicode-width",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tabled_derive"
|
||||||
|
version = "0.3.0"
|
||||||
|
source = "git+https://github.com/zhiburt/tabled?rev=e3cbdea5b81edda8b3cc7b9f64f98fecae7db423#e3cbdea5b81edda8b3cc7b9f64f98fecae7db423"
|
||||||
|
dependencies = [
|
||||||
|
"heck 0.4.0",
|
||||||
|
"proc-macro-error",
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tempdir"
|
name = "tempdir"
|
||||||
version = "0.3.7"
|
version = "0.3.7"
|
||||||
|
@ -4952,7 +4929,7 @@ version = "0.9.7"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "09b01702b0fd0b3fadcf98e098780badda8742d4f4a7676615cad90e8ac73622"
|
checksum = "09b01702b0fd0b3fadcf98e098780badda8742d4f4a7676615cad90e8ac73622"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"version_check 0.9.4",
|
"version_check",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -5080,12 +5057,6 @@ version = "1.8.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "5fc1631c774f0f9570797191e01247cbefde789eebfbf128074cb934115a6133"
|
checksum = "5fc1631c774f0f9570797191e01247cbefde789eebfbf128074cb934115a6133"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "version_check"
|
|
||||||
version = "0.1.5"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "914b1a6776c4c929a602fafd8bc742e06365d4bcbe48c30f9cca5824f70dc9dd"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "version_check"
|
name = "version_check"
|
||||||
version = "0.9.4"
|
version = "0.9.4"
|
||||||
|
|
|
@ -173,7 +173,8 @@ impl Command for Table {
|
||||||
theme: load_theme_from_config(config),
|
theme: load_theme_from_config(config),
|
||||||
};
|
};
|
||||||
|
|
||||||
let result = nu_table::draw_table(&table, term_width, &color_hm, config);
|
let result = nu_table::draw_table(&table, term_width, &color_hm, config)
|
||||||
|
.unwrap_or_else(|| format!("Couldn't fit table into {} columns!", term_width));
|
||||||
|
|
||||||
Ok(Value::String {
|
Ok(Value::String {
|
||||||
val: result,
|
val: result,
|
||||||
|
@ -556,7 +557,8 @@ impl Iterator for PagingTableCreator {
|
||||||
|
|
||||||
match table {
|
match table {
|
||||||
Ok(Some(table)) => {
|
Ok(Some(table)) => {
|
||||||
let result = nu_table::draw_table(&table, term_width, &color_hm, &self.config);
|
let result = nu_table::draw_table(&table, term_width, &color_hm, &self.config)
|
||||||
|
.unwrap_or_else(|| format!("Couldn't fit table into {} columns!", term_width));
|
||||||
|
|
||||||
Some(Ok(result.as_bytes().to_vec()))
|
Some(Ok(result.as_bytes().to_vec()))
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,5 +17,8 @@ nu-protocol = { path = "../nu-protocol", version = "0.65.1" }
|
||||||
regex = "1.4"
|
regex = "1.4"
|
||||||
unicode-width = "0.1.8"
|
unicode-width = "0.1.8"
|
||||||
strip-ansi-escapes = "0.1.1"
|
strip-ansi-escapes = "0.1.1"
|
||||||
ansi-str = "0.2.0"
|
ansi-str = { git = "https://github.com/zhiburt/ansi-str", rev = "655cd8125a032286082794690c2cc6dc835345b4" }
|
||||||
atty = "0.2.14"
|
atty = "0.2.14"
|
||||||
|
tabled = { git = "https://github.com/zhiburt/tabled", rev = "e3cbdea5b81edda8b3cc7b9f64f98fecae7db423", features = [
|
||||||
|
"color",
|
||||||
|
] }
|
||||||
|
|
|
@ -5,5 +5,4 @@ mod wrap;
|
||||||
|
|
||||||
pub use table::{draw_table, Table};
|
pub use table::{draw_table, Table};
|
||||||
pub use table_theme::TableTheme;
|
pub use table_theme::TableTheme;
|
||||||
pub use textstyle::{StyledString, TextStyle};
|
pub use textstyle::{Alignment, StyledString, TextStyle};
|
||||||
pub use wrap::Alignment;
|
|
||||||
|
|
|
@ -29,7 +29,8 @@ fn main() {
|
||||||
// get the default config
|
// get the default config
|
||||||
let config = Config::default();
|
let config = Config::default();
|
||||||
// Capture the table as a string
|
// Capture the table as a string
|
||||||
let output_table = draw_table(&table, width, &color_hm, &config);
|
let output_table = draw_table(&table, width, &color_hm, &config)
|
||||||
|
.unwrap_or_else(|| format!("Couldn't fit table into {} columns!", width));
|
||||||
// Draw the table
|
// Draw the table
|
||||||
println!("{}", output_table)
|
println!("{}", output_table)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,16 +1,16 @@
|
||||||
use crate::table_theme::TableTheme;
|
use crate::table_theme::TableTheme;
|
||||||
use crate::wrap::{column_width, split_sublines, wrap, Alignment, Subline, WrappedCell};
|
use crate::StyledString;
|
||||||
use crate::{StyledString, TextStyle};
|
use nu_ansi_term::Style;
|
||||||
use nu_ansi_term::{AnsiString, AnsiStrings, Style};
|
|
||||||
use nu_protocol::{Config, FooterMode};
|
use nu_protocol::{Config, FooterMode};
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::fmt::Write;
|
use tabled::{
|
||||||
|
builder::Builder,
|
||||||
enum SeparatorPosition {
|
formatting_settings::AlignmentStrategy,
|
||||||
Top,
|
object::{Cell, Columns, Rows},
|
||||||
Middle,
|
papergrid,
|
||||||
Bottom,
|
style::BorderColor,
|
||||||
}
|
Alignment, Modify, TableOption,
|
||||||
|
};
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Table {
|
pub struct Table {
|
||||||
|
@ -33,669 +33,176 @@ impl Table {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct ProcessedTable {
|
|
||||||
pub headers: Vec<ProcessedCell>,
|
|
||||||
pub data: Vec<Vec<ProcessedCell>>,
|
|
||||||
pub theme: TableTheme,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct ProcessedCell {
|
|
||||||
pub contents: Vec<Vec<Subline>>,
|
|
||||||
pub style: TextStyle,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct WrappedTable {
|
|
||||||
pub column_widths: Vec<usize>,
|
|
||||||
pub headers: Vec<WrappedCell>,
|
|
||||||
pub data: Vec<Vec<WrappedCell>>,
|
|
||||||
pub theme: TableTheme,
|
|
||||||
pub footer: Vec<WrappedCell>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl WrappedTable {
|
|
||||||
fn print_separator(
|
|
||||||
&self,
|
|
||||||
separator_position: SeparatorPosition,
|
|
||||||
color_hm: &HashMap<String, Style>,
|
|
||||||
) -> String {
|
|
||||||
let column_count = self.column_widths.len();
|
|
||||||
let mut output: Vec<AnsiString> = Vec::new();
|
|
||||||
let sep_color = color_hm
|
|
||||||
.get("separator")
|
|
||||||
.unwrap_or(&Style::default())
|
|
||||||
.to_owned();
|
|
||||||
|
|
||||||
match separator_position {
|
|
||||||
SeparatorPosition::Top => {
|
|
||||||
for column in self.column_widths.iter().enumerate() {
|
|
||||||
if column.0 == 0 && self.theme.print_left_border {
|
|
||||||
output.push(sep_color.paint(self.theme.top_left.to_string()));
|
|
||||||
}
|
|
||||||
|
|
||||||
for _ in 0..*column.1 {
|
|
||||||
output.push(sep_color.paint(self.theme.top_horizontal.to_string()));
|
|
||||||
}
|
|
||||||
|
|
||||||
output.push(sep_color.paint(self.theme.top_horizontal.to_string()));
|
|
||||||
output.push(sep_color.paint(self.theme.top_horizontal.to_string()));
|
|
||||||
if column.0 == column_count - 1 {
|
|
||||||
if self.theme.print_right_border {
|
|
||||||
output.push(sep_color.paint(self.theme.top_right.to_string()));
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
output.push(sep_color.paint(self.theme.top_center.to_string()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
output.push(AnsiString::from("\n".to_string()));
|
|
||||||
}
|
|
||||||
SeparatorPosition::Middle => {
|
|
||||||
for column in self.column_widths.iter().enumerate() {
|
|
||||||
if column.0 == 0 && self.theme.print_left_border {
|
|
||||||
output.push(sep_color.paint(self.theme.middle_left.to_string()));
|
|
||||||
}
|
|
||||||
|
|
||||||
for _ in 0..*column.1 {
|
|
||||||
output.push(sep_color.paint(self.theme.middle_horizontal.to_string()));
|
|
||||||
}
|
|
||||||
|
|
||||||
output.push(sep_color.paint(self.theme.middle_horizontal.to_string()));
|
|
||||||
output.push(sep_color.paint(self.theme.middle_horizontal.to_string()));
|
|
||||||
|
|
||||||
if column.0 == column_count - 1 {
|
|
||||||
if self.theme.print_right_border {
|
|
||||||
output.push(sep_color.paint(self.theme.middle_right.to_string()));
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
output.push(sep_color.paint(self.theme.center.to_string()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
output.push(AnsiString::from("\n".to_string()));
|
|
||||||
}
|
|
||||||
SeparatorPosition::Bottom => {
|
|
||||||
for column in self.column_widths.iter().enumerate() {
|
|
||||||
if column.0 == 0 && self.theme.print_left_border {
|
|
||||||
output.push(sep_color.paint(self.theme.bottom_left.to_string()));
|
|
||||||
}
|
|
||||||
for _ in 0..*column.1 {
|
|
||||||
output.push(sep_color.paint(self.theme.bottom_horizontal.to_string()));
|
|
||||||
}
|
|
||||||
output.push(sep_color.paint(self.theme.bottom_horizontal.to_string()));
|
|
||||||
output.push(sep_color.paint(self.theme.bottom_horizontal.to_string()));
|
|
||||||
|
|
||||||
if column.0 == column_count - 1 {
|
|
||||||
if self.theme.print_right_border {
|
|
||||||
output.push(sep_color.paint(self.theme.bottom_right.to_string()));
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
output.push(sep_color.paint(self.theme.bottom_center.to_string()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
AnsiStrings(&output[..]).to_string()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn print_cell_contents(
|
|
||||||
&self,
|
|
||||||
cells: &[WrappedCell],
|
|
||||||
color_hm: &HashMap<String, Style>,
|
|
||||||
) -> String {
|
|
||||||
let sep_color = color_hm
|
|
||||||
.get("separator")
|
|
||||||
.unwrap_or(&Style::default())
|
|
||||||
.to_owned();
|
|
||||||
|
|
||||||
let mut total_output = String::new();
|
|
||||||
|
|
||||||
for current_line in 0.. {
|
|
||||||
let mut lines_printed = 0;
|
|
||||||
|
|
||||||
let mut output = String::new();
|
|
||||||
if self.theme.print_left_border {
|
|
||||||
output.push_str(
|
|
||||||
&sep_color
|
|
||||||
.paint(&self.theme.left_vertical.to_string())
|
|
||||||
.to_string(),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
for column in cells.iter().enumerate() {
|
|
||||||
if let Some(line) = (column.1).lines.get(current_line) {
|
|
||||||
let remainder = self.column_widths[column.0] - line.width;
|
|
||||||
output.push(' ');
|
|
||||||
|
|
||||||
match column.1.style.alignment {
|
|
||||||
Alignment::Left => {
|
|
||||||
if let Some(color) = column.1.style.color_style {
|
|
||||||
output.push_str(&color.paint(&line.line).to_string());
|
|
||||||
} else {
|
|
||||||
output.push_str(&line.line);
|
|
||||||
}
|
|
||||||
for _ in 0..remainder {
|
|
||||||
output.push(' ');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Alignment::Center => {
|
|
||||||
for _ in 0..remainder / 2 {
|
|
||||||
output.push(' ');
|
|
||||||
}
|
|
||||||
if let Some(color) = column.1.style.color_style {
|
|
||||||
output.push_str(&color.paint(&line.line).to_string());
|
|
||||||
} else {
|
|
||||||
output.push_str(&line.line);
|
|
||||||
}
|
|
||||||
for _ in 0..(remainder / 2 + remainder % 2) {
|
|
||||||
output.push(' ');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Alignment::Right => {
|
|
||||||
for _ in 0..remainder {
|
|
||||||
output.push(' ');
|
|
||||||
}
|
|
||||||
if let Some(color) = column.1.style.color_style {
|
|
||||||
output.push_str(&color.paint(&line.line).to_string());
|
|
||||||
} else {
|
|
||||||
output.push_str(&line.line);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
output.push(' ');
|
|
||||||
lines_printed += 1;
|
|
||||||
} else {
|
|
||||||
for _ in 0..self.column_widths[column.0] + 2 {
|
|
||||||
output.push(' ');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if column.0 < cells.len() - 1 {
|
|
||||||
output.push_str(
|
|
||||||
&sep_color
|
|
||||||
.paint(&self.theme.center_vertical.to_string())
|
|
||||||
.to_string(),
|
|
||||||
);
|
|
||||||
} else if self.theme.print_right_border {
|
|
||||||
output.push_str(
|
|
||||||
&sep_color
|
|
||||||
.paint(&self.theme.right_vertical.to_string())
|
|
||||||
.to_string(),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if lines_printed == 0 {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
writeln!(&mut total_output, "{}", output).expect("writing should be done to buffer");
|
|
||||||
}
|
|
||||||
total_output
|
|
||||||
}
|
|
||||||
|
|
||||||
fn print_table(&self, color_hm: &HashMap<String, Style>, config: &Config) -> String {
|
|
||||||
let mut output = String::new();
|
|
||||||
|
|
||||||
// TODO: This may be unnecessary after JTs changes. Let's remove it and see.
|
|
||||||
// #[cfg(windows)]
|
|
||||||
// {
|
|
||||||
// let _ = nu_ansi_term::enable_ansi_support();
|
|
||||||
// }
|
|
||||||
|
|
||||||
if self.data.is_empty() {
|
|
||||||
return output;
|
|
||||||
}
|
|
||||||
|
|
||||||
// The top border
|
|
||||||
if self.theme.print_top_border {
|
|
||||||
output.push_str(&self.print_separator(SeparatorPosition::Top, color_hm));
|
|
||||||
}
|
|
||||||
|
|
||||||
// The header
|
|
||||||
let skip_headers = (self.headers.len() == 2 && self.headers[1].max_width == 0)
|
|
||||||
|| (self.headers.len() == 1 && self.headers[0].max_width == 0);
|
|
||||||
|
|
||||||
if !self.headers.is_empty() && !skip_headers {
|
|
||||||
output.push_str(&self.print_cell_contents(&self.headers, color_hm));
|
|
||||||
}
|
|
||||||
|
|
||||||
// The middle section
|
|
||||||
let mut first_row = true;
|
|
||||||
for row in &self.data {
|
|
||||||
if !first_row {
|
|
||||||
if self.theme.separate_rows {
|
|
||||||
output.push_str(&self.print_separator(SeparatorPosition::Middle, color_hm));
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
first_row = false;
|
|
||||||
|
|
||||||
if self.theme.separate_header && !self.headers.is_empty() && !skip_headers {
|
|
||||||
output.push_str(&self.print_separator(SeparatorPosition::Middle, color_hm));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
output.push_str(&self.print_cell_contents(row, color_hm));
|
|
||||||
}
|
|
||||||
|
|
||||||
match config.footer_mode {
|
|
||||||
FooterMode::Always => {
|
|
||||||
if self.theme.separate_header && !self.headers.is_empty() && !skip_headers {
|
|
||||||
output.push_str(&self.print_separator(SeparatorPosition::Middle, color_hm));
|
|
||||||
}
|
|
||||||
|
|
||||||
if !self.headers.is_empty() && !skip_headers {
|
|
||||||
output.push_str(&self.print_cell_contents(&self.footer, color_hm));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
FooterMode::RowCount(r) => {
|
|
||||||
if self.data.len() as u64 > r {
|
|
||||||
if self.theme.separate_header && !self.headers.is_empty() && !skip_headers {
|
|
||||||
output.push_str(&self.print_separator(SeparatorPosition::Middle, color_hm));
|
|
||||||
}
|
|
||||||
|
|
||||||
if !self.headers.is_empty() && !skip_headers {
|
|
||||||
output.push_str(&self.print_cell_contents(&self.footer, color_hm));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => {} // Never and Auto aka auto get eaten and nothing happens
|
|
||||||
}
|
|
||||||
|
|
||||||
// The table finish
|
|
||||||
if self.theme.print_bottom_border {
|
|
||||||
output.push_str(&self.print_separator(SeparatorPosition::Bottom, color_hm));
|
|
||||||
}
|
|
||||||
|
|
||||||
// the atty is for when people do ls from vim, there should be no coloring there
|
|
||||||
if !config.use_ansi_coloring || !atty::is(atty::Stream::Stdout) {
|
|
||||||
// Draw the table without ansi colors
|
|
||||||
if let Ok(bytes) = strip_ansi_escapes::strip(&output) {
|
|
||||||
String::from_utf8_lossy(&bytes).to_string()
|
|
||||||
} else {
|
|
||||||
output
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Draw the table with ansi colors
|
|
||||||
output
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn process_table(table: &Table) -> ProcessedTable {
|
|
||||||
let mut processed_data = vec![];
|
|
||||||
for row in &table.data {
|
|
||||||
let mut out_row = vec![];
|
|
||||||
for column in row {
|
|
||||||
let cleaned = clean(&column.contents);
|
|
||||||
out_row.push(ProcessedCell {
|
|
||||||
contents: split_sublines(&cleaned),
|
|
||||||
style: column.style,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
processed_data.push(out_row);
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut processed_headers = vec![];
|
|
||||||
for header in &table.headers {
|
|
||||||
let cleaned = clean(&header.contents);
|
|
||||||
processed_headers.push(ProcessedCell {
|
|
||||||
contents: split_sublines(&cleaned),
|
|
||||||
style: header.style,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
ProcessedTable {
|
|
||||||
headers: processed_headers,
|
|
||||||
data: processed_data,
|
|
||||||
theme: table.theme.clone(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn clean(input: &str) -> String {
|
|
||||||
let input = input.replace('\r', "");
|
|
||||||
|
|
||||||
input.replace('\t', " ")
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_max_column_widths(processed_table: &ProcessedTable) -> Vec<usize> {
|
|
||||||
use std::cmp::max;
|
|
||||||
|
|
||||||
let mut max_num_columns = 0;
|
|
||||||
|
|
||||||
max_num_columns = max(max_num_columns, processed_table.headers.len());
|
|
||||||
|
|
||||||
for row in &processed_table.data {
|
|
||||||
max_num_columns = max(max_num_columns, row.len());
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut output = vec![0; max_num_columns];
|
|
||||||
|
|
||||||
for column in processed_table.headers.iter().enumerate() {
|
|
||||||
output[column.0] = max(output[column.0], column_width(&column.1.contents));
|
|
||||||
}
|
|
||||||
|
|
||||||
for row in &processed_table.data {
|
|
||||||
for column in row.iter().enumerate() {
|
|
||||||
output[column.0] = max(output[column.0], column_width(&column.1.contents));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
output
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn maybe_truncate_columns(termwidth: usize, processed_table: &mut ProcessedTable) {
|
|
||||||
// Make sure we have enough space for the columns we have
|
|
||||||
let max_num_of_columns = termwidth / 10;
|
|
||||||
|
|
||||||
// If we have too many columns, truncate the table
|
|
||||||
if max_num_of_columns < processed_table.headers.len() {
|
|
||||||
processed_table.headers.truncate(max_num_of_columns);
|
|
||||||
|
|
||||||
for entry in processed_table.data.iter_mut() {
|
|
||||||
entry.truncate(max_num_of_columns);
|
|
||||||
}
|
|
||||||
|
|
||||||
processed_table.headers.push(ProcessedCell {
|
|
||||||
contents: vec![vec![Subline {
|
|
||||||
subline: "...".to_string(),
|
|
||||||
width: 3,
|
|
||||||
}]],
|
|
||||||
style: TextStyle::basic_center(),
|
|
||||||
});
|
|
||||||
|
|
||||||
for entry in processed_table.data.iter_mut() {
|
|
||||||
entry.push(ProcessedCell {
|
|
||||||
contents: vec![vec![Subline {
|
|
||||||
subline: "...".to_string(),
|
|
||||||
width: 3,
|
|
||||||
}]],
|
|
||||||
style: TextStyle::basic_center(),
|
|
||||||
}); // ellipsis is centred
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn draw_table(
|
pub fn draw_table(
|
||||||
table: &Table,
|
table: &Table,
|
||||||
termwidth: usize,
|
termwidth: usize,
|
||||||
color_hm: &HashMap<String, Style>,
|
color_hm: &HashMap<String, Style>,
|
||||||
config: &Config,
|
config: &Config,
|
||||||
) -> String {
|
) -> Option<String> {
|
||||||
// Remove the edges, if used
|
// Remove the edges, if used
|
||||||
let edges_width = if table.theme.print_left_border && table.theme.print_right_border {
|
let (headers, data) = crate::wrap::wrap(&table.headers, &table.data, termwidth, &table.theme)?;
|
||||||
3
|
let headers = if headers.is_empty() {
|
||||||
} else if table.theme.print_left_border || table.theme.print_right_border {
|
None
|
||||||
1
|
|
||||||
} else {
|
} else {
|
||||||
0
|
Some(headers)
|
||||||
};
|
};
|
||||||
|
|
||||||
if termwidth < edges_width {
|
let alignments = build_alignment_map(&table.data);
|
||||||
return format!("Couldn't fit table into {} columns!", termwidth);
|
|
||||||
}
|
|
||||||
|
|
||||||
let raw_termwidth = termwidth;
|
let theme = &table.theme;
|
||||||
let termwidth = termwidth - edges_width;
|
|
||||||
|
|
||||||
let mut processed_table = process_table(table);
|
let with_header = headers.is_some();
|
||||||
|
let with_footer = with_header && need_footer(config, data.len() as u64);
|
||||||
|
|
||||||
let max_per_column = get_max_column_widths(&processed_table);
|
let table = build_table(data, headers, Some(alignments), config, with_footer);
|
||||||
|
let table = load_theme(table, color_hm, theme, with_footer, with_header);
|
||||||
|
|
||||||
maybe_truncate_columns(termwidth, &mut processed_table);
|
print_table(table, termwidth)
|
||||||
|
|
||||||
let headers_len = processed_table.headers.len();
|
|
||||||
|
|
||||||
// fix the length of the table if there are no headers:
|
|
||||||
let headers_len = if headers_len == 0 {
|
|
||||||
if !table.data.is_empty() && !table.data[0].is_empty() {
|
|
||||||
table.data[0].len()
|
|
||||||
} else {
|
|
||||||
return String::new();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
headers_len
|
|
||||||
};
|
|
||||||
|
|
||||||
// Measure how big our columns need to be (accounting for separators also)
|
|
||||||
let max_naive_column_width = (termwidth - 3 * (headers_len - 1)) / headers_len;
|
|
||||||
|
|
||||||
let column_space = ColumnSpace::measure(&max_per_column, max_naive_column_width, headers_len);
|
|
||||||
|
|
||||||
// This gives us the max column width
|
|
||||||
let max_column_width = column_space.max_width(termwidth);
|
|
||||||
|
|
||||||
// This width isn't quite right, as we're rounding off some of our space
|
|
||||||
let column_space = match max_column_width {
|
|
||||||
None => return format!("Couldn't fit table into {} columns!", raw_termwidth),
|
|
||||||
Some(max_column_width) => column_space.fix_almost_column_width(
|
|
||||||
&max_per_column,
|
|
||||||
max_naive_column_width,
|
|
||||||
max_column_width,
|
|
||||||
headers_len,
|
|
||||||
),
|
|
||||||
};
|
|
||||||
|
|
||||||
// This should give us the final max column width
|
|
||||||
let max_column_width = column_space.max_width(termwidth);
|
|
||||||
let re_leading =
|
|
||||||
regex::Regex::new(r"(?P<beginsp>^\s+)").expect("error with leading space regex");
|
|
||||||
let re_trailing =
|
|
||||||
regex::Regex::new(r"(?P<endsp>\s+$)").expect("error with trailing space regex");
|
|
||||||
|
|
||||||
let wrapped_table = match max_column_width {
|
|
||||||
None => return format!("Couldn't fit table into {} columns!", raw_termwidth),
|
|
||||||
Some(max_column_width) => wrap_cells(
|
|
||||||
processed_table,
|
|
||||||
max_column_width,
|
|
||||||
color_hm,
|
|
||||||
&re_leading,
|
|
||||||
&re_trailing,
|
|
||||||
),
|
|
||||||
};
|
|
||||||
|
|
||||||
wrapped_table.print_table(color_hm, config)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn wrap_cells(
|
fn print_table(table: tabled::Table, term_width: usize) -> Option<String> {
|
||||||
processed_table: ProcessedTable,
|
let s = table.to_string();
|
||||||
max_column_width: usize,
|
|
||||||
|
let width = s.lines().next().map(papergrid::string_width).unwrap_or(0);
|
||||||
|
if width > term_width {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
Some(s)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn build_alignment_map(data: &[Vec<StyledString>]) -> Vec<Vec<Alignment>> {
|
||||||
|
let mut v = vec![Vec::new(); data.len()];
|
||||||
|
for (i, row) in data.iter().enumerate() {
|
||||||
|
let mut row_alignments = Vec::with_capacity(row.len());
|
||||||
|
for col in row {
|
||||||
|
row_alignments.push(Alignment::Horizontal(col.style.alignment));
|
||||||
|
}
|
||||||
|
|
||||||
|
v[i] = row_alignments;
|
||||||
|
}
|
||||||
|
|
||||||
|
v
|
||||||
|
}
|
||||||
|
|
||||||
|
fn build_table(
|
||||||
|
data: Vec<Vec<String>>,
|
||||||
|
headers: Option<Vec<String>>,
|
||||||
|
alignment_map: Option<Vec<Vec<Alignment>>>,
|
||||||
|
config: &Config,
|
||||||
|
need_footer: bool,
|
||||||
|
) -> tabled::Table {
|
||||||
|
let header_present = headers.is_some();
|
||||||
|
let mut builder = Builder::from(data);
|
||||||
|
|
||||||
|
if let Some(headers) = headers {
|
||||||
|
builder = builder.set_columns(headers.clone());
|
||||||
|
|
||||||
|
if need_footer {
|
||||||
|
builder = builder.add_record(headers);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut table = builder.build();
|
||||||
|
|
||||||
|
table = table.with(
|
||||||
|
Modify::new(Rows::new(1..))
|
||||||
|
.with(Alignment::left())
|
||||||
|
.with(AlignmentStrategy::PerLine),
|
||||||
|
);
|
||||||
|
|
||||||
|
if !config.disable_table_indexes {
|
||||||
|
table = table.with(Modify::new(Columns::first()).with(Alignment::right()));
|
||||||
|
}
|
||||||
|
|
||||||
|
if header_present {
|
||||||
|
table = table.with(Modify::new(Rows::first()).with(Alignment::center()));
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(alignments) = alignment_map {
|
||||||
|
table = apply_alignments(table, alignments, header_present);
|
||||||
|
}
|
||||||
|
|
||||||
|
table
|
||||||
|
}
|
||||||
|
|
||||||
|
fn apply_alignments(
|
||||||
|
mut table: tabled::Table,
|
||||||
|
alignment: Vec<Vec<Alignment>>,
|
||||||
|
header_present: bool,
|
||||||
|
) -> tabled::Table {
|
||||||
|
let offset = if header_present { 1 } else { 0 };
|
||||||
|
for (row, alignments) in alignment.into_iter().enumerate() {
|
||||||
|
for (col, alignment) in alignments.into_iter().enumerate() {
|
||||||
|
table = table.with(Modify::new(Cell(row + offset, col)).with(alignment));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
table
|
||||||
|
}
|
||||||
|
|
||||||
|
fn load_theme(
|
||||||
|
mut table: tabled::Table,
|
||||||
color_hm: &HashMap<String, Style>,
|
color_hm: &HashMap<String, Style>,
|
||||||
re_leading: ®ex::Regex,
|
theme: &TableTheme,
|
||||||
re_trailing: ®ex::Regex,
|
with_footer: bool,
|
||||||
) -> WrappedTable {
|
with_header: bool,
|
||||||
let mut column_widths = vec![
|
) -> tabled::Table {
|
||||||
0;
|
table = table.with(theme.theme.clone());
|
||||||
std::cmp::max(
|
|
||||||
processed_table.headers.len(),
|
|
||||||
if !processed_table.data.is_empty() {
|
|
||||||
processed_table.data[0].len()
|
|
||||||
} else {
|
|
||||||
0
|
|
||||||
}
|
|
||||||
)
|
|
||||||
];
|
|
||||||
let mut output_headers = vec![];
|
|
||||||
for header in processed_table.headers.into_iter().enumerate() {
|
|
||||||
let mut wrapped = WrappedCell {
|
|
||||||
lines: vec![],
|
|
||||||
max_width: 0,
|
|
||||||
style: header.1.style,
|
|
||||||
};
|
|
||||||
|
|
||||||
for contents in header.1.contents.into_iter() {
|
if let Some(color) = color_hm.get("separator") {
|
||||||
let (mut lines, inner_max_width) = wrap(
|
let color = color.paint(" ").to_string();
|
||||||
max_column_width,
|
if let Ok(color) = BorderColor::try_from(color) {
|
||||||
contents.into_iter(),
|
table = table.with(color);
|
||||||
color_hm,
|
}
|
||||||
re_leading,
|
}
|
||||||
re_trailing,
|
|
||||||
|
if with_footer {
|
||||||
|
table = table.with(FooterStyle).with(
|
||||||
|
Modify::new(Rows::last())
|
||||||
|
.with(Alignment::center())
|
||||||
|
.with(AlignmentStrategy::PerCell),
|
||||||
);
|
);
|
||||||
wrapped.lines.append(&mut lines);
|
|
||||||
if inner_max_width > wrapped.max_width {
|
|
||||||
wrapped.max_width = inner_max_width;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if column_widths[header.0] < wrapped.max_width {
|
|
||||||
column_widths[header.0] = wrapped.max_width;
|
|
||||||
}
|
|
||||||
output_headers.push(wrapped);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut output_data = vec![];
|
if !with_header {
|
||||||
for row in processed_table.data.into_iter() {
|
table = table.with(RemoveHeaderLine);
|
||||||
let mut output_row = vec![];
|
|
||||||
for column in row.into_iter().enumerate() {
|
|
||||||
let mut wrapped = WrappedCell {
|
|
||||||
lines: vec![],
|
|
||||||
max_width: 0,
|
|
||||||
style: column.1.style,
|
|
||||||
};
|
|
||||||
for contents in column.1.contents.into_iter() {
|
|
||||||
let (mut lines, inner_max_width) = wrap(
|
|
||||||
max_column_width,
|
|
||||||
contents.into_iter(),
|
|
||||||
color_hm,
|
|
||||||
re_leading,
|
|
||||||
re_trailing,
|
|
||||||
);
|
|
||||||
wrapped.lines.append(&mut lines);
|
|
||||||
if inner_max_width > wrapped.max_width {
|
|
||||||
wrapped.max_width = inner_max_width;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if column_widths[column.0] < wrapped.max_width {
|
|
||||||
column_widths[column.0] = wrapped.max_width;
|
|
||||||
}
|
|
||||||
output_row.push(wrapped);
|
|
||||||
}
|
|
||||||
output_data.push(output_row);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut footer = vec![
|
table
|
||||||
WrappedCell {
|
}
|
||||||
lines: vec![],
|
|
||||||
max_width: 0,
|
|
||||||
style: TextStyle {
|
|
||||||
..Default::default()
|
|
||||||
},
|
|
||||||
};
|
|
||||||
output_headers.len()
|
|
||||||
];
|
|
||||||
footer.clone_from_slice(&output_headers[..]);
|
|
||||||
|
|
||||||
WrappedTable {
|
fn need_footer(config: &Config, count_records: u64) -> bool {
|
||||||
column_widths,
|
matches!(config.footer_mode, FooterMode::RowCount(limit) if count_records > limit)
|
||||||
headers: output_headers,
|
|| matches!(config.footer_mode, FooterMode::Always)
|
||||||
data: output_data,
|
}
|
||||||
theme: processed_table.theme,
|
|
||||||
footer,
|
struct FooterStyle;
|
||||||
|
|
||||||
|
impl TableOption for FooterStyle {
|
||||||
|
fn change(&mut self, grid: &mut papergrid::Grid) {
|
||||||
|
if grid.count_columns() == 0 || grid.count_rows() == 0 {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut line = papergrid::Line::default();
|
||||||
|
|
||||||
|
let border = grid.get_border((0, 0));
|
||||||
|
line.left = border.left_bottom_corner;
|
||||||
|
line.intersection = border.right_bottom_corner;
|
||||||
|
line.horizontal = border.bottom;
|
||||||
|
|
||||||
|
let border = grid.get_border((0, grid.count_columns() - 1));
|
||||||
|
line.right = border.right_bottom_corner;
|
||||||
|
|
||||||
|
grid.set_split_line(grid.count_rows() - 1, line);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct ColumnSpace {
|
struct RemoveHeaderLine;
|
||||||
num_overages: usize,
|
|
||||||
underage_sum: usize,
|
|
||||||
overage_separator_sum: usize,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ColumnSpace {
|
impl TableOption for RemoveHeaderLine {
|
||||||
/// Measure how much space we have once we subtract off the columns who are small enough
|
fn change(&mut self, grid: &mut papergrid::Grid) {
|
||||||
fn measure(
|
grid.set_split_line(1, papergrid::Line::default());
|
||||||
max_per_column: &[usize],
|
|
||||||
max_naive_column_width: usize,
|
|
||||||
headers_len: usize,
|
|
||||||
) -> ColumnSpace {
|
|
||||||
let mut num_overages = 0;
|
|
||||||
let mut underage_sum = 0;
|
|
||||||
let mut overage_separator_sum = 0;
|
|
||||||
let iter = max_per_column.iter().enumerate().take(headers_len);
|
|
||||||
|
|
||||||
for (i, &column_max) in iter {
|
|
||||||
if column_max > max_naive_column_width {
|
|
||||||
num_overages += 1;
|
|
||||||
if i != (headers_len - 1) {
|
|
||||||
overage_separator_sum += 3;
|
|
||||||
}
|
|
||||||
if i == 0 {
|
|
||||||
overage_separator_sum += 1;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
underage_sum += column_max;
|
|
||||||
// if column isn't last, add 3 for its separator
|
|
||||||
if i != (headers_len - 1) {
|
|
||||||
underage_sum += 3;
|
|
||||||
}
|
|
||||||
if i == 0 {
|
|
||||||
underage_sum += 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ColumnSpace {
|
|
||||||
num_overages,
|
|
||||||
underage_sum,
|
|
||||||
overage_separator_sum,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn fix_almost_column_width(
|
|
||||||
self,
|
|
||||||
max_per_column: &[usize],
|
|
||||||
max_naive_column_width: usize,
|
|
||||||
max_column_width: usize,
|
|
||||||
headers_len: usize,
|
|
||||||
) -> ColumnSpace {
|
|
||||||
let mut num_overages = 0;
|
|
||||||
let mut overage_separator_sum = 0;
|
|
||||||
let mut underage_sum = self.underage_sum;
|
|
||||||
let iter = max_per_column.iter().enumerate().take(headers_len);
|
|
||||||
|
|
||||||
for (i, &column_max) in iter {
|
|
||||||
if column_max > max_naive_column_width {
|
|
||||||
if column_max <= max_column_width {
|
|
||||||
underage_sum += column_max;
|
|
||||||
// if column isn't last, add 3 for its separator
|
|
||||||
if i != (headers_len - 1) {
|
|
||||||
underage_sum += 3;
|
|
||||||
}
|
|
||||||
if i == 0 {
|
|
||||||
underage_sum += 1;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Column is still too large, so let's count it
|
|
||||||
num_overages += 1;
|
|
||||||
if i != (headers_len - 1) {
|
|
||||||
overage_separator_sum += 3;
|
|
||||||
}
|
|
||||||
if i == 0 {
|
|
||||||
overage_separator_sum += 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ColumnSpace {
|
|
||||||
num_overages,
|
|
||||||
underage_sum,
|
|
||||||
overage_separator_sum,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn max_width(&self, termwidth: usize) -> Option<usize> {
|
|
||||||
let ColumnSpace {
|
|
||||||
num_overages,
|
|
||||||
underage_sum,
|
|
||||||
overage_separator_sum,
|
|
||||||
} = self;
|
|
||||||
|
|
||||||
if *num_overages > 0 {
|
|
||||||
termwidth
|
|
||||||
.checked_sub(1)?
|
|
||||||
.checked_sub(*underage_sum)?
|
|
||||||
.checked_sub(*overage_separator_sum)?
|
|
||||||
.checked_div(*num_overages)
|
|
||||||
} else {
|
|
||||||
Some(99999)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,329 +1,130 @@
|
||||||
|
use tabled::{style::StyleConfig, Style};
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct TableTheme {
|
pub struct TableTheme {
|
||||||
pub top_left: char,
|
pub(crate) theme: StyleConfig,
|
||||||
pub middle_left: char,
|
pub(crate) is_left_set: bool,
|
||||||
pub bottom_left: char,
|
pub(crate) is_right_set: bool,
|
||||||
pub top_center: char,
|
|
||||||
pub center: char,
|
|
||||||
pub bottom_center: char,
|
|
||||||
pub top_right: char,
|
|
||||||
pub middle_right: char,
|
|
||||||
pub bottom_right: char,
|
|
||||||
pub top_horizontal: char,
|
|
||||||
pub middle_horizontal: char,
|
|
||||||
pub bottom_horizontal: char,
|
|
||||||
pub left_vertical: char,
|
|
||||||
pub center_vertical: char,
|
|
||||||
pub right_vertical: char,
|
|
||||||
|
|
||||||
pub separate_header: bool,
|
|
||||||
pub separate_rows: bool,
|
|
||||||
|
|
||||||
pub print_left_border: bool,
|
|
||||||
pub print_right_border: bool,
|
|
||||||
pub print_top_border: bool,
|
|
||||||
pub print_bottom_border: bool,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TableTheme {
|
impl TableTheme {
|
||||||
#[allow(unused)]
|
|
||||||
pub fn basic() -> TableTheme {
|
pub fn basic() -> TableTheme {
|
||||||
TableTheme {
|
Self {
|
||||||
top_left: '+',
|
theme: Style::ascii().into(),
|
||||||
middle_left: '+',
|
is_left_set: true,
|
||||||
bottom_left: '+',
|
is_right_set: true,
|
||||||
top_center: '+',
|
|
||||||
center: '+',
|
|
||||||
bottom_center: '+',
|
|
||||||
top_right: '+',
|
|
||||||
middle_right: '+',
|
|
||||||
bottom_right: '+',
|
|
||||||
top_horizontal: '-',
|
|
||||||
middle_horizontal: '-',
|
|
||||||
bottom_horizontal: '-',
|
|
||||||
left_vertical: '|',
|
|
||||||
center_vertical: '|',
|
|
||||||
right_vertical: '|',
|
|
||||||
|
|
||||||
separate_header: true,
|
|
||||||
separate_rows: true,
|
|
||||||
|
|
||||||
print_left_border: true,
|
|
||||||
print_right_border: true,
|
|
||||||
print_top_border: true,
|
|
||||||
print_bottom_border: true,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(unused)]
|
|
||||||
pub fn thin() -> TableTheme {
|
pub fn thin() -> TableTheme {
|
||||||
TableTheme {
|
Self {
|
||||||
top_left: '┌',
|
theme: Style::modern().into(),
|
||||||
middle_left: '├',
|
is_left_set: true,
|
||||||
bottom_left: '└',
|
is_right_set: true,
|
||||||
top_center: '┬',
|
|
||||||
center: '┼',
|
|
||||||
bottom_center: '┴',
|
|
||||||
top_right: '┐',
|
|
||||||
middle_right: '┤',
|
|
||||||
bottom_right: '┘',
|
|
||||||
|
|
||||||
top_horizontal: '─',
|
|
||||||
middle_horizontal: '─',
|
|
||||||
bottom_horizontal: '─',
|
|
||||||
|
|
||||||
left_vertical: '│',
|
|
||||||
center_vertical: '│',
|
|
||||||
right_vertical: '│',
|
|
||||||
|
|
||||||
separate_header: true,
|
|
||||||
separate_rows: true,
|
|
||||||
|
|
||||||
print_left_border: true,
|
|
||||||
print_right_border: true,
|
|
||||||
print_top_border: true,
|
|
||||||
print_bottom_border: true,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(unused)]
|
|
||||||
pub fn light() -> TableTheme {
|
pub fn light() -> TableTheme {
|
||||||
TableTheme {
|
Self {
|
||||||
top_left: ' ',
|
theme: Style::blank().header('─').into(),
|
||||||
middle_left: '─',
|
is_left_set: false,
|
||||||
bottom_left: ' ',
|
is_right_set: false,
|
||||||
top_center: ' ',
|
|
||||||
center: '─',
|
|
||||||
bottom_center: ' ',
|
|
||||||
top_right: ' ',
|
|
||||||
middle_right: '─',
|
|
||||||
bottom_right: ' ',
|
|
||||||
|
|
||||||
top_horizontal: ' ',
|
|
||||||
middle_horizontal: '─',
|
|
||||||
bottom_horizontal: ' ',
|
|
||||||
|
|
||||||
left_vertical: ' ',
|
|
||||||
center_vertical: ' ',
|
|
||||||
right_vertical: ' ',
|
|
||||||
|
|
||||||
separate_header: true,
|
|
||||||
separate_rows: false,
|
|
||||||
|
|
||||||
print_left_border: true,
|
|
||||||
print_right_border: true,
|
|
||||||
print_top_border: false,
|
|
||||||
print_bottom_border: true,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(unused)]
|
|
||||||
pub fn compact() -> TableTheme {
|
pub fn compact() -> TableTheme {
|
||||||
TableTheme {
|
Self {
|
||||||
top_left: '─',
|
theme: Style::modern()
|
||||||
middle_left: '─',
|
.left_off()
|
||||||
bottom_left: '─',
|
.right_off()
|
||||||
top_center: '┬',
|
.horizontal_off()
|
||||||
center: '┼',
|
.into(),
|
||||||
bottom_center: '┴',
|
is_left_set: false,
|
||||||
top_right: '─',
|
is_right_set: false,
|
||||||
middle_right: '─',
|
|
||||||
bottom_right: '─',
|
|
||||||
top_horizontal: '─',
|
|
||||||
middle_horizontal: '─',
|
|
||||||
bottom_horizontal: '─',
|
|
||||||
|
|
||||||
left_vertical: ' ',
|
|
||||||
center_vertical: '│',
|
|
||||||
right_vertical: ' ',
|
|
||||||
|
|
||||||
separate_header: true,
|
|
||||||
separate_rows: false,
|
|
||||||
|
|
||||||
print_left_border: false,
|
|
||||||
print_right_border: false,
|
|
||||||
print_top_border: true,
|
|
||||||
print_bottom_border: true,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(unused)]
|
|
||||||
pub fn with_love() -> TableTheme {
|
pub fn with_love() -> TableTheme {
|
||||||
TableTheme {
|
Self {
|
||||||
top_left: '❤',
|
theme: Style::psql()
|
||||||
middle_left: '❤',
|
.header('❤')
|
||||||
bottom_left: '❤',
|
.top('❤')
|
||||||
top_center: '❤',
|
.bottom('❤')
|
||||||
center: '❤',
|
.vertical('❤')
|
||||||
bottom_center: '❤',
|
.into(),
|
||||||
top_right: '❤',
|
is_left_set: false,
|
||||||
middle_right: '❤',
|
is_right_set: false,
|
||||||
bottom_right: '❤',
|
|
||||||
top_horizontal: '❤',
|
|
||||||
middle_horizontal: '❤',
|
|
||||||
bottom_horizontal: '❤',
|
|
||||||
|
|
||||||
left_vertical: ' ',
|
|
||||||
center_vertical: '❤',
|
|
||||||
right_vertical: ' ',
|
|
||||||
|
|
||||||
separate_header: true,
|
|
||||||
separate_rows: false,
|
|
||||||
|
|
||||||
print_left_border: false,
|
|
||||||
print_right_border: false,
|
|
||||||
print_top_border: true,
|
|
||||||
print_bottom_border: true,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(unused)]
|
|
||||||
pub fn compact_double() -> TableTheme {
|
pub fn compact_double() -> TableTheme {
|
||||||
TableTheme {
|
Self {
|
||||||
top_left: '═',
|
theme: Style::psql()
|
||||||
middle_left: '═',
|
.header('═')
|
||||||
bottom_left: '═',
|
.top('═')
|
||||||
top_center: '╦',
|
.bottom('═')
|
||||||
center: '╬',
|
.vertical('║')
|
||||||
bottom_center: '╩',
|
.top_intersection('╦')
|
||||||
top_right: '═',
|
.bottom_intersection('╩')
|
||||||
middle_right: '═',
|
.header_intersection('╬')
|
||||||
bottom_right: '═',
|
.into(),
|
||||||
top_horizontal: '═',
|
is_left_set: false,
|
||||||
middle_horizontal: '═',
|
is_right_set: false,
|
||||||
bottom_horizontal: '═',
|
|
||||||
|
|
||||||
left_vertical: ' ',
|
|
||||||
center_vertical: '║',
|
|
||||||
right_vertical: ' ',
|
|
||||||
|
|
||||||
separate_header: true,
|
|
||||||
separate_rows: false,
|
|
||||||
|
|
||||||
print_left_border: false,
|
|
||||||
print_right_border: false,
|
|
||||||
print_top_border: true,
|
|
||||||
print_bottom_border: true,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(unused)]
|
|
||||||
pub fn rounded() -> TableTheme {
|
pub fn rounded() -> TableTheme {
|
||||||
TableTheme {
|
Self {
|
||||||
top_left: '╭',
|
theme: Style::rounded().into(),
|
||||||
middle_left: '├',
|
is_left_set: true,
|
||||||
bottom_left: '╰',
|
is_right_set: true,
|
||||||
top_center: '┬',
|
|
||||||
center: '┼',
|
|
||||||
bottom_center: '┴',
|
|
||||||
top_right: '╮',
|
|
||||||
middle_right: '┤',
|
|
||||||
bottom_right: '╯',
|
|
||||||
top_horizontal: '─',
|
|
||||||
middle_horizontal: '─',
|
|
||||||
bottom_horizontal: '─',
|
|
||||||
|
|
||||||
left_vertical: '│',
|
|
||||||
center_vertical: '│',
|
|
||||||
right_vertical: '│',
|
|
||||||
|
|
||||||
separate_header: true,
|
|
||||||
separate_rows: false,
|
|
||||||
|
|
||||||
print_left_border: true,
|
|
||||||
print_right_border: true,
|
|
||||||
print_top_border: true,
|
|
||||||
print_bottom_border: true,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(unused)]
|
|
||||||
pub fn reinforced() -> TableTheme {
|
pub fn reinforced() -> TableTheme {
|
||||||
TableTheme {
|
Self {
|
||||||
top_left: '┏',
|
theme: Style::modern()
|
||||||
middle_left: '├',
|
.top_left_corner('┏')
|
||||||
bottom_left: '┗',
|
.top_right_corner('┓')
|
||||||
top_center: '┬',
|
.bottom_left_corner('┗')
|
||||||
center: '┼',
|
.bottom_right_corner('┛')
|
||||||
bottom_center: '┴',
|
.horizontal_off()
|
||||||
top_right: '┓',
|
.into(),
|
||||||
middle_right: '┤',
|
is_left_set: true,
|
||||||
bottom_right: '┛',
|
is_right_set: true,
|
||||||
top_horizontal: '─',
|
|
||||||
middle_horizontal: '─',
|
|
||||||
bottom_horizontal: '─',
|
|
||||||
|
|
||||||
left_vertical: '│',
|
|
||||||
center_vertical: '│',
|
|
||||||
right_vertical: '│',
|
|
||||||
|
|
||||||
separate_header: true,
|
|
||||||
separate_rows: false,
|
|
||||||
|
|
||||||
print_left_border: true,
|
|
||||||
print_right_border: true,
|
|
||||||
print_top_border: true,
|
|
||||||
print_bottom_border: true,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(unused)]
|
|
||||||
pub fn heavy() -> TableTheme {
|
pub fn heavy() -> TableTheme {
|
||||||
TableTheme {
|
Self {
|
||||||
top_left: '┏',
|
theme: Style::modern()
|
||||||
middle_left: '┣',
|
.header('━')
|
||||||
bottom_left: '┗',
|
.top('━')
|
||||||
top_center: '┳',
|
.bottom('━')
|
||||||
center: '╋',
|
.vertical('┃')
|
||||||
bottom_center: '┻',
|
.left('┃')
|
||||||
top_right: '┓',
|
.right('┃')
|
||||||
middle_right: '┫',
|
.left_intersection('┣')
|
||||||
bottom_right: '┛',
|
.right_intersection('┫')
|
||||||
top_horizontal: '━',
|
.bottom_intersection('┻')
|
||||||
middle_horizontal: '━',
|
.top_intersection('┳')
|
||||||
bottom_horizontal: '━',
|
.top_left_corner('┏')
|
||||||
|
.top_right_corner('┓')
|
||||||
left_vertical: '┃',
|
.bottom_left_corner('┗')
|
||||||
center_vertical: '┃',
|
.bottom_right_corner('┛')
|
||||||
right_vertical: '┃',
|
.header_intersection('╋')
|
||||||
|
.horizontal_off()
|
||||||
separate_header: true,
|
.into(),
|
||||||
separate_rows: false,
|
is_left_set: true,
|
||||||
|
is_right_set: true,
|
||||||
print_left_border: true,
|
|
||||||
print_right_border: true,
|
|
||||||
print_top_border: true,
|
|
||||||
print_bottom_border: true,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#[allow(unused)]
|
|
||||||
pub fn none() -> TableTheme {
|
pub fn none() -> TableTheme {
|
||||||
TableTheme {
|
Self {
|
||||||
top_left: ' ',
|
theme: Style::blank().into(),
|
||||||
middle_left: ' ',
|
is_left_set: false,
|
||||||
bottom_left: ' ',
|
is_right_set: false,
|
||||||
top_center: ' ',
|
|
||||||
center: ' ',
|
|
||||||
bottom_center: ' ',
|
|
||||||
top_right: ' ',
|
|
||||||
middle_right: ' ',
|
|
||||||
bottom_right: ' ',
|
|
||||||
|
|
||||||
top_horizontal: ' ',
|
|
||||||
middle_horizontal: ' ',
|
|
||||||
bottom_horizontal: ' ',
|
|
||||||
|
|
||||||
left_vertical: ' ',
|
|
||||||
center_vertical: ' ',
|
|
||||||
right_vertical: ' ',
|
|
||||||
|
|
||||||
separate_header: false,
|
|
||||||
separate_rows: false,
|
|
||||||
|
|
||||||
print_left_border: false,
|
|
||||||
print_right_border: false,
|
|
||||||
print_top_border: false,
|
|
||||||
print_bottom_border: false,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
use crate::wrap::Alignment;
|
|
||||||
use nu_ansi_term::{Color, Style};
|
use nu_ansi_term::{Color, Style};
|
||||||
|
|
||||||
|
pub type Alignment = tabled::AlignmentHorizontal;
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy)]
|
#[derive(Debug, Clone, Copy)]
|
||||||
pub struct TextStyle {
|
pub struct TextStyle {
|
||||||
pub alignment: Alignment,
|
pub alignment: Alignment,
|
||||||
|
@ -239,7 +240,7 @@ impl Default for TextStyle {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone, Default)]
|
||||||
pub struct StyledString {
|
pub struct StyledString {
|
||||||
pub contents: String,
|
pub contents: String,
|
||||||
pub style: TextStyle,
|
pub style: TextStyle,
|
||||||
|
|
|
@ -1,18 +1,12 @@
|
||||||
use crate::textstyle::TextStyle;
|
use crate::textstyle::TextStyle;
|
||||||
|
use crate::{StyledString, TableTheme};
|
||||||
use ansi_str::AnsiStr;
|
use ansi_str::AnsiStr;
|
||||||
use nu_ansi_term::Style;
|
use nu_ansi_term::Style;
|
||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::{fmt::Display, iter::Iterator};
|
use std::iter::Iterator;
|
||||||
use unicode_width::{UnicodeWidthChar, UnicodeWidthStr};
|
use unicode_width::{UnicodeWidthChar, UnicodeWidthStr};
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy)]
|
|
||||||
pub enum Alignment {
|
|
||||||
Left,
|
|
||||||
Center,
|
|
||||||
Right,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Subline {
|
pub struct Subline {
|
||||||
pub subline: String,
|
pub subline: String,
|
||||||
|
@ -39,21 +33,6 @@ pub struct WrappedCell {
|
||||||
pub style: TextStyle,
|
pub style: TextStyle,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Display for Line {
|
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
||||||
let mut first = true;
|
|
||||||
for subline in &self.sublines {
|
|
||||||
if !first {
|
|
||||||
write!(f, " ")?;
|
|
||||||
} else {
|
|
||||||
first = false;
|
|
||||||
}
|
|
||||||
write!(f, "{}", subline.subline)?;
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Removes ANSI escape codes and some ASCII control characters
|
/// Removes ANSI escape codes and some ASCII control characters
|
||||||
///
|
///
|
||||||
/// Keeps `\n` removes `\r`, `\t` etc.
|
/// Keeps `\n` removes `\r`, `\t` etc.
|
||||||
|
@ -181,7 +160,7 @@ fn split_word(cell_width: usize, word: &str) -> Vec<Subline> {
|
||||||
output
|
output
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn wrap(
|
pub fn wrap_content(
|
||||||
cell_width: usize,
|
cell_width: usize,
|
||||||
mut input: impl Iterator<Item = Subline>,
|
mut input: impl Iterator<Item = Subline>,
|
||||||
color_hm: &HashMap<String, Style>,
|
color_hm: &HashMap<String, Style>,
|
||||||
|
@ -318,3 +297,353 @@ pub fn wrap(
|
||||||
|
|
||||||
(output, current_max)
|
(output, current_max)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn wrap(
|
||||||
|
headers: &[StyledString],
|
||||||
|
data: &[Vec<StyledString>],
|
||||||
|
termwidth: usize,
|
||||||
|
theme: &TableTheme,
|
||||||
|
) -> Option<(Vec<String>, Vec<Vec<String>>)> {
|
||||||
|
// Remove the edges, if used
|
||||||
|
let edges_width = if theme.is_left_set && theme.is_right_set {
|
||||||
|
3
|
||||||
|
} else if theme.is_left_set || theme.is_right_set {
|
||||||
|
1
|
||||||
|
} else {
|
||||||
|
0
|
||||||
|
};
|
||||||
|
|
||||||
|
if termwidth < edges_width {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
let termwidth = termwidth - edges_width;
|
||||||
|
|
||||||
|
let (mut headers_splited, mut data_splited) = split_lines(headers, data);
|
||||||
|
|
||||||
|
let max_per_column = get_max_column_widths(&headers_splited, &data_splited);
|
||||||
|
|
||||||
|
maybe_truncate_columns(termwidth, &mut headers_splited, &mut data_splited);
|
||||||
|
|
||||||
|
let mut headers_len = headers_splited.len();
|
||||||
|
if headers_len == 0 {
|
||||||
|
if !data.is_empty() && !data[0].is_empty() {
|
||||||
|
headers_len = data_splited[0].len();
|
||||||
|
} else {
|
||||||
|
return Some((Vec::new(), Vec::new()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Measure how big our columns need to be (accounting for separators also)
|
||||||
|
let max_naive_column_width = (termwidth - 3 * (headers_len - 1)) / headers_len;
|
||||||
|
|
||||||
|
let column_space = ColumnSpace::measure(&max_per_column, max_naive_column_width, headers_len);
|
||||||
|
|
||||||
|
// This gives us the max column width
|
||||||
|
let max_column_width = column_space.max_width(termwidth)?;
|
||||||
|
|
||||||
|
// This width isn't quite right, as we're rounding off some of our space
|
||||||
|
let column_space = column_space.fix_almost_column_width(
|
||||||
|
&max_per_column,
|
||||||
|
max_naive_column_width,
|
||||||
|
max_column_width,
|
||||||
|
headers_len,
|
||||||
|
);
|
||||||
|
|
||||||
|
// This should give us the final max column width
|
||||||
|
let max_column_width = column_space.max_width(termwidth)?;
|
||||||
|
|
||||||
|
let re_leading =
|
||||||
|
regex::Regex::new(r"(?P<beginsp>^\s+)").expect("error with leading space regex");
|
||||||
|
let re_trailing =
|
||||||
|
regex::Regex::new(r"(?P<endsp>\s+$)").expect("error with trailing space regex");
|
||||||
|
|
||||||
|
let result = wrap_cells(
|
||||||
|
headers_splited,
|
||||||
|
data_splited,
|
||||||
|
max_column_width,
|
||||||
|
&re_leading,
|
||||||
|
&re_trailing,
|
||||||
|
);
|
||||||
|
|
||||||
|
Some(result)
|
||||||
|
}
|
||||||
|
|
||||||
|
struct ContentLines {
|
||||||
|
pub lines: Vec<Vec<Subline>>,
|
||||||
|
pub style: TextStyle,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn split_lines(
|
||||||
|
headers: &[StyledString],
|
||||||
|
data: &[Vec<StyledString>],
|
||||||
|
) -> (Vec<ContentLines>, Vec<Vec<ContentLines>>) {
|
||||||
|
let mut splited_headers = Vec::with_capacity(headers.len());
|
||||||
|
for column in headers {
|
||||||
|
let content = clean(&column.contents);
|
||||||
|
let lines = split_sublines(&content);
|
||||||
|
splited_headers.push(ContentLines {
|
||||||
|
lines,
|
||||||
|
style: column.style,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut splited_data = Vec::with_capacity(data.len());
|
||||||
|
for row in data {
|
||||||
|
let mut splited_row = Vec::with_capacity(row.len());
|
||||||
|
for column in row {
|
||||||
|
let content = clean(&column.contents);
|
||||||
|
let lines = split_sublines(&content);
|
||||||
|
splited_row.push(ContentLines {
|
||||||
|
lines,
|
||||||
|
style: column.style,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
splited_data.push(splited_row);
|
||||||
|
}
|
||||||
|
|
||||||
|
(splited_headers, splited_data)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_max_column_widths(headers: &[ContentLines], data: &[Vec<ContentLines>]) -> Vec<usize> {
|
||||||
|
use std::cmp::max;
|
||||||
|
|
||||||
|
let mut max_num_columns = 0;
|
||||||
|
|
||||||
|
max_num_columns = max(max_num_columns, headers.len());
|
||||||
|
|
||||||
|
for row in data {
|
||||||
|
max_num_columns = max(max_num_columns, row.len());
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut output = vec![0; max_num_columns];
|
||||||
|
|
||||||
|
for (col, content) in headers.iter().enumerate() {
|
||||||
|
output[col] = max(output[col], column_width(&content.lines));
|
||||||
|
}
|
||||||
|
|
||||||
|
for row in data {
|
||||||
|
for (col, content) in row.iter().enumerate() {
|
||||||
|
output[col] = max(output[col], column_width(&content.lines));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
output
|
||||||
|
}
|
||||||
|
|
||||||
|
fn wrap_cells(
|
||||||
|
headers_splited: Vec<ContentLines>,
|
||||||
|
data_splited: Vec<Vec<ContentLines>>,
|
||||||
|
max_column_width: usize,
|
||||||
|
re_leading: ®ex::Regex,
|
||||||
|
re_trailing: ®ex::Regex,
|
||||||
|
) -> (Vec<String>, Vec<Vec<String>>) {
|
||||||
|
let mut header = vec![String::new(); headers_splited.len()];
|
||||||
|
for (col, splited) in headers_splited.into_iter().enumerate() {
|
||||||
|
let mut wrapped = vec![];
|
||||||
|
for contents in splited.lines {
|
||||||
|
let (mut lines, _) = wrap_content(
|
||||||
|
max_column_width,
|
||||||
|
contents.into_iter(),
|
||||||
|
&HashMap::new(),
|
||||||
|
re_leading,
|
||||||
|
re_trailing,
|
||||||
|
);
|
||||||
|
wrapped.append(&mut lines);
|
||||||
|
}
|
||||||
|
|
||||||
|
let content = wrapped
|
||||||
|
.into_iter()
|
||||||
|
.map(|l| l.line)
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
.join("\n");
|
||||||
|
let content = splited
|
||||||
|
.style
|
||||||
|
.color_style
|
||||||
|
.map(|color| color.paint(&content).to_string())
|
||||||
|
.unwrap_or(content);
|
||||||
|
|
||||||
|
header[col] = content;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut data = vec![Vec::new(); data_splited.len()];
|
||||||
|
for (row, splited) in data_splited.into_iter().enumerate() {
|
||||||
|
for splited in splited.into_iter() {
|
||||||
|
let mut wrapped = vec![];
|
||||||
|
for contents in splited.lines {
|
||||||
|
let (mut lines, _) = wrap_content(
|
||||||
|
max_column_width,
|
||||||
|
contents.into_iter(),
|
||||||
|
&HashMap::new(),
|
||||||
|
re_leading,
|
||||||
|
re_trailing,
|
||||||
|
);
|
||||||
|
wrapped.append(&mut lines);
|
||||||
|
}
|
||||||
|
|
||||||
|
let content = wrapped
|
||||||
|
.into_iter()
|
||||||
|
.map(|l| l.line)
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
.join("\n");
|
||||||
|
let content = splited
|
||||||
|
.style
|
||||||
|
.color_style
|
||||||
|
.map(|color| color.paint(&content).to_string())
|
||||||
|
.unwrap_or(content);
|
||||||
|
|
||||||
|
data[row].push(content);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
(header, data)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn maybe_truncate_columns(
|
||||||
|
termwidth: usize,
|
||||||
|
headers: &mut Vec<ContentLines>,
|
||||||
|
data: &mut [Vec<ContentLines>],
|
||||||
|
) {
|
||||||
|
// Make sure we have enough space for the columns we have
|
||||||
|
let max_num_of_columns = termwidth / 10;
|
||||||
|
|
||||||
|
// If we have too many columns, truncate the table
|
||||||
|
if max_num_of_columns < headers.len() {
|
||||||
|
headers.truncate(max_num_of_columns);
|
||||||
|
headers.push(ContentLines {
|
||||||
|
lines: vec![vec![Subline {
|
||||||
|
subline: String::from("..."),
|
||||||
|
width: 3,
|
||||||
|
}]],
|
||||||
|
style: TextStyle::basic_center(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if max_num_of_columns < headers.len() {
|
||||||
|
for entry in data.iter_mut() {
|
||||||
|
entry.truncate(max_num_of_columns);
|
||||||
|
entry.push(ContentLines {
|
||||||
|
lines: vec![vec![Subline {
|
||||||
|
subline: String::from("..."),
|
||||||
|
width: 3,
|
||||||
|
}]],
|
||||||
|
style: TextStyle::basic_center(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct ColumnSpace {
|
||||||
|
num_overages: usize,
|
||||||
|
underage_sum: usize,
|
||||||
|
overage_separator_sum: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ColumnSpace {
|
||||||
|
/// Measure how much space we have once we subtract off the columns who are small enough
|
||||||
|
fn measure(
|
||||||
|
max_per_column: &[usize],
|
||||||
|
max_naive_column_width: usize,
|
||||||
|
headers_len: usize,
|
||||||
|
) -> ColumnSpace {
|
||||||
|
let mut num_overages = 0;
|
||||||
|
let mut underage_sum = 0;
|
||||||
|
let mut overage_separator_sum = 0;
|
||||||
|
let iter = max_per_column.iter().enumerate().take(headers_len);
|
||||||
|
|
||||||
|
for (i, &column_max) in iter {
|
||||||
|
if column_max > max_naive_column_width {
|
||||||
|
num_overages += 1;
|
||||||
|
if i != (headers_len - 1) {
|
||||||
|
overage_separator_sum += 3;
|
||||||
|
}
|
||||||
|
if i == 0 {
|
||||||
|
overage_separator_sum += 1;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
underage_sum += column_max;
|
||||||
|
// if column isn't last, add 3 for its separator
|
||||||
|
if i != (headers_len - 1) {
|
||||||
|
underage_sum += 3;
|
||||||
|
}
|
||||||
|
if i == 0 {
|
||||||
|
underage_sum += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ColumnSpace {
|
||||||
|
num_overages,
|
||||||
|
underage_sum,
|
||||||
|
overage_separator_sum,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn fix_almost_column_width(
|
||||||
|
self,
|
||||||
|
max_per_column: &[usize],
|
||||||
|
max_naive_column_width: usize,
|
||||||
|
max_column_width: usize,
|
||||||
|
headers_len: usize,
|
||||||
|
) -> ColumnSpace {
|
||||||
|
let mut num_overages = 0;
|
||||||
|
let mut overage_separator_sum = 0;
|
||||||
|
let mut underage_sum = self.underage_sum;
|
||||||
|
let iter = max_per_column.iter().enumerate().take(headers_len);
|
||||||
|
|
||||||
|
for (i, &column_max) in iter {
|
||||||
|
if column_max > max_naive_column_width {
|
||||||
|
if column_max <= max_column_width {
|
||||||
|
underage_sum += column_max;
|
||||||
|
// if column isn't last, add 3 for its separator
|
||||||
|
if i != (headers_len - 1) {
|
||||||
|
underage_sum += 3;
|
||||||
|
}
|
||||||
|
if i == 0 {
|
||||||
|
underage_sum += 1;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Column is still too large, so let's count it
|
||||||
|
num_overages += 1;
|
||||||
|
if i != (headers_len - 1) {
|
||||||
|
overage_separator_sum += 3;
|
||||||
|
}
|
||||||
|
if i == 0 {
|
||||||
|
overage_separator_sum += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ColumnSpace {
|
||||||
|
num_overages,
|
||||||
|
underage_sum,
|
||||||
|
overage_separator_sum,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn max_width(&self, termwidth: usize) -> Option<usize> {
|
||||||
|
let ColumnSpace {
|
||||||
|
num_overages,
|
||||||
|
underage_sum,
|
||||||
|
overage_separator_sum,
|
||||||
|
} = self;
|
||||||
|
|
||||||
|
if *num_overages > 0 {
|
||||||
|
termwidth
|
||||||
|
.checked_sub(1)?
|
||||||
|
.checked_sub(*underage_sum)?
|
||||||
|
.checked_sub(*overage_separator_sum)?
|
||||||
|
.checked_div(*num_overages)
|
||||||
|
} else {
|
||||||
|
Some(99999)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn clean(input: &str) -> String {
|
||||||
|
let input = input.replace('\r', "");
|
||||||
|
|
||||||
|
input.replace('\t', " ")
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue