tabled-0.18.0/.cargo_vcs_info.json0000644000000001440000000000100123540ustar { "git": { "sha1": "573f2752d0eaf997fb795e34e8f5a21aeae9e8e0" }, "path_in_vcs": "tabled" }tabled-0.18.0/Cargo.lock0000644000000134560000000000100103410ustar # This file is automatically @generated by Cargo. # It is not intended for manual editing. version = 4 [[package]] name = "ansi-str" version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "060de1453b69f46304b28274f382132f4e72c55637cf362920926a70d090890d" dependencies = [ "ansitok 0.3.0", ] [[package]] name = "ansitok" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "220044e6a1bb31ddee4e3db724d29767f352de47445a6cd75e1a173142136c83" dependencies = [ "nom", "vte 0.10.1", ] [[package]] name = "ansitok" version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c0a8acea8c2f1c60f0a92a8cd26bf96ca97db56f10bbcab238bbe0cceba659ee" dependencies = [ "nom", "vte 0.14.1", ] [[package]] name = "arrayvec" version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b" [[package]] name = "arrayvec" version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" [[package]] name = "bytecount" version = "0.6.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5ce89b21cab1437276d2650d57e971f9d548a2d9037cc231abdc0562b97498ce" [[package]] name = "fnv" version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" [[package]] name = "heck" version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" [[package]] name = "memchr" version = "2.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" [[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 = "papergrid" version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b915f831b85d984193fdc3d3611505871dc139b2534530fa01c1a6a6707b6723" dependencies = [ "ansi-str", "ansitok 0.3.0", "bytecount", "fnv", "unicode-width", ] [[package]] name = "proc-macro-error-attr2" version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "96de42df36bb9bba5542fe9f1a054b8cc87e172759a1868aa05c1f3acc89dfc5" dependencies = [ "proc-macro2", "quote", ] [[package]] name = "proc-macro-error2" version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "11ec05c52be0a07b08061f7dd003e7d7092e0472bc731b4af7bb1ef876109802" dependencies = [ "proc-macro-error-attr2", "proc-macro2", "quote", "syn", ] [[package]] name = "proc-macro2" version = "1.0.93" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "60946a68e5f9d28b0dc1c21bb8a97ee7d018a8b322fa57838ba31cc878e22d99" dependencies = [ "unicode-ident", ] [[package]] name = "quote" version = "1.0.38" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0e4dccaaaf89514f546c693ddc140f729f958c247918a13380cccc6078391acc" dependencies = [ "proc-macro2", ] [[package]] name = "syn" version = "2.0.96" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d5d0adab1ae378d7f53bdebc67a39f1f151407ef230f0ce2883572f5d8985c80" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] [[package]] name = "tabled" version = "0.18.0" dependencies = [ "ansi-str", "ansitok 0.3.0", "papergrid", "tabled_derive", "testing_table", ] [[package]] name = "tabled_derive" version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "52d9946811baad81710ec921809e2af67ad77719418673b2a3794932d57b7538" dependencies = [ "heck", "proc-macro-error2", "proc-macro2", "quote", "syn", ] [[package]] name = "testing_table" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9491a98ee2ccc17bd4178e29d9dace769bff6cdb5e876b57ab9ef4ef38edd9e" dependencies = [ "ansitok 0.2.0", "unicode-width", ] [[package]] name = "unicode-ident" version = "1.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a210d160f08b701c8721ba1c726c11662f877ea6b7094007e1ca9a1041945034" [[package]] name = "unicode-width" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fc81956842c57dac11422a97c3b8195a1ff727f06e85c84ed2e8aa277c9a0fd" [[package]] name = "utf8parse" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "vte" version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6cbce692ab4ca2f1f3047fcf732430249c0e971bfdd2b234cf2c47ad93af5983" dependencies = [ "arrayvec 0.5.2", "utf8parse", "vte_generate_state_changes", ] [[package]] name = "vte" version = "0.14.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "231fdcd7ef3037e8330d8e17e61011a2c244126acc0a982f4040ac3f9f0bc077" dependencies = [ "arrayvec 0.7.6", "memchr", ] [[package]] name = "vte_generate_state_changes" version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2e369bee1b05d510a7b4ed645f5faa90619e05437111783ea5848f28d97d3c2e" dependencies = [ "proc-macro2", "quote", ] tabled-0.18.0/Cargo.toml0000644000000204010000000000100103500ustar # 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 = "tabled" version = "0.18.0" authors = ["Maxim Zhiburt "] build = false autolib = false autobins = false autoexamples = false autotests = false autobenches = false description = "An easy to use library for pretty print tables of Rust `struct`s and `enum`s." homepage = "https://github.com/zhiburt/tabled" documentation = "https://docs.rs/tabled" readme = "README.md" keywords = [ "table", "print", "pretty-table", "format", "terminal", ] categories = [ "text-processing", "visualization", ] license = "MIT" repository = "https://github.com/zhiburt/tabled" [package.metadata.docs.rs] all-features = true rustc-args = [ "--cfg", "docsrs", ] rustdoc-args = [ "--cfg", "docsrs", ] targets = ["x86_64-unknown-linux-gnu"] [lib] name = "tabled" path = "src/lib.rs" [[example]] name = "alphabet" path = "examples/alphabet.rs" required-features = ["std"] [[example]] name = "border_text" path = "examples/border_text.rs" required-features = ["std"] [[example]] name = "builder" path = "examples/builder.rs" required-features = ["std"] [[example]] name = "builder_index" path = "examples/builder_index.rs" required-features = ["derive"] [[example]] name = "chess" path = "examples/chess.rs" required-features = ["std"] [[example]] name = "col_row_macros" path = "examples/col_row_macros.rs" required-features = [ "macros", "derive", ] [[example]] name = "color" path = "examples/color.rs" required-features = [ "ansi", "derive", ] [[example]] name = "colored_borders" path = "examples/colored_borders.rs" required-features = ["derive"] [[example]] name = "colored_padding" path = "examples/colored_padding.rs" required-features = [ "ansi", "derive", ] [[example]] name = "colorization" path = "examples/colorization.rs" required-features = [ "std", "derive", ] [[example]] name = "column_names" path = "examples/column_names.rs" required-features = [ "std", "derive", ] [[example]] name = "compact_table" path = "examples/compact_table.rs" required-features = [] [[example]] name = "compact_table_2" path = "examples/compact_table_2.rs" required-features = [] [[example]] name = "compact_table_3" path = "examples/compact_table_3.rs" [[example]] name = "concat" path = "examples/concat.rs" required-features = ["derive"] [[example]] name = "custom_style" path = "examples/custom_style.rs" required-features = ["derive"] [[example]] name = "derive_crate_override" path = "examples/derive/crate_override.rs" required-features = ["derive"] [[example]] name = "derive_display_type" path = "examples/derive/display_type.rs" required-features = ["derive"] [[example]] name = "derive_display_with" path = "examples/derive/display_with.rs" required-features = ["derive"] [[example]] name = "derive_format" path = "examples/derive/format.rs" required-features = ["derive"] [[example]] name = "derive_format_enum" path = "examples/derive/format_enum.rs" required-features = ["derive"] [[example]] name = "derive_inline" path = "examples/derive/inline.rs" required-features = ["derive"] [[example]] name = "derive_inline_enum" path = "examples/derive/inline_enum.rs" required-features = ["derive"] [[example]] name = "derive_order" path = "examples/derive/order.rs" required-features = ["derive"] [[example]] name = "derive_rename" path = "examples/derive/rename.rs" required-features = ["derive"] [[example]] name = "disable" path = "examples/disable.rs" required-features = ["derive"] [[example]] name = "extended_display" path = "examples/extended_display.rs" required-features = ["derive"] [[example]] name = "extract" path = "examples/extract.rs" required-features = ["derive"] [[example]] name = "format" path = "examples/format.rs" required-features = ["derive"] [[example]] name = "formatting_settings" path = "examples/formatting_settings.rs" required-features = ["std"] [[example]] name = "grid_colors" path = "examples/grid_colors.rs" required-features = ["derive"] [[example]] name = "height" path = "examples/height.rs" required-features = ["std"] [[example]] name = "highlight" path = "examples/highlight.rs" required-features = ["std"] [[example]] name = "highlight_color" path = "examples/highlight_color.rs" required-features = ["std"] [[example]] name = "hyperlink" path = "examples/hyperlink.rs" required-features = [ "derive", "ansi", ] [[example]] name = "interactive" path = "examples/interactive.rs" required-features = ["derive"] [[example]] name = "iter_table" path = "examples/iter_table.rs" required-features = ["std"] [[example]] name = "margin" path = "examples/margin.rs" required-features = ["std"] [[example]] name = "matrix" path = "examples/matrix.rs" required-features = ["std"] [[example]] name = "merge_duplicates_horizontal" path = "examples/merge_duplicates_horizontal.rs" required-features = ["derive"] [[example]] name = "merge_duplicates_vertical" path = "examples/merge_duplicates_vertical.rs" required-features = ["derive"] [[example]] name = "nested_table" path = "examples/nested_table.rs" required-features = ["std"] [[example]] name = "nested_table_2" path = "examples/nested_table_2.rs" required-features = ["derive"] [[example]] name = "nested_table_3" path = "examples/nested_table_3.rs" required-features = ["derive"] [[example]] name = "option" path = "examples/option.rs" required-features = ["derive"] [[example]] name = "panel" path = "examples/panel.rs" required-features = ["derive"] [[example]] name = "pool_table" path = "examples/pool_table.rs" required-features = ["std"] [[example]] name = "pool_table2" path = "examples/pool_table2.rs" required-features = ["std"] [[example]] name = "rename_all" path = "examples/derive/rename_all.rs" required-features = ["derive"] [[example]] name = "reverse" path = "examples/reverse.rs" required-features = ["std"] [[example]] name = "rotate" path = "examples/rotate.rs" required-features = ["derive"] [[example]] name = "settings_list" path = "examples/settings_list.rs" required-features = ["derive"] [[example]] name = "shadow" path = "examples/shadow.rs" required-features = ["macros"] [[example]] name = "skip" path = "examples/derive/skip.rs" required-features = ["derive"] [[example]] name = "span" path = "examples/span.rs" required-features = ["std"] [[example]] name = "span_column" path = "examples/span_column.rs" required-features = ["std"] [[example]] name = "span_row" path = "examples/span_row.rs" required-features = ["std"] [[example]] name = "split" path = "examples/split.rs" required-features = [ "std", "macros", ] [[example]] name = "style_modern_rounded" path = "examples/style_modern_rounded.rs" required-features = ["std"] [[example]] name = "table" path = "examples/table.rs" required-features = ["derive"] [[example]] name = "table_width" path = "examples/table_width.rs" required-features = ["std"] [[example]] name = "table_width_2" path = "examples/table_width_2.rs" required-features = ["std"] [[example]] name = "target_content" path = "examples/target_content.rs" required-features = [ "std", "derive", ] [[example]] name = "theme" path = "examples/theme.rs" required-features = [ "derive", "std", ] [[test]] name = "main" path = "tests/main.rs" [dependencies.ansi-str] version = "0.9" optional = true [dependencies.ansitok] version = "0.3" optional = true [dependencies.papergrid] version = "0.14" default-features = false [dependencies.tabled_derive] version = "0.10" optional = true [dev-dependencies.testing_table] version = "0.2" features = ["ansi"] [features] ansi = [ "papergrid/ansi", "ansi-str", "ansitok", "std", ] default = [ "derive", "macros", ] derive = [ "tabled_derive", "std", ] macros = ["std"] std = ["papergrid/std"] [badges.coveralls] branch = "master" repository = "https://github.com/zhiburt/tabled" service = "github" [badges.maintenance] status = "actively-developed" tabled-0.18.0/Cargo.toml.orig000064400000000000000000000166771046102023000140550ustar 00000000000000[package] name = "tabled" version = "0.18.0" authors = ["Maxim Zhiburt "] edition = "2018" description = "An easy to use library for pretty print tables of Rust `struct`s and `enum`s." repository = "https://github.com/zhiburt/tabled" homepage = "https://github.com/zhiburt/tabled" documentation = "https://docs.rs/tabled" license = "MIT" keywords = ["table", "print", "pretty-table", "format", "terminal"] categories = ["text-processing", "visualization"] readme = "README.md" [badges] coveralls = { repository = "https://github.com/zhiburt/tabled", branch = "master", service = "github" } maintenance = { status = "actively-developed" } [[example]] name = "color" required-features = ["ansi", "derive"] [[example]] name = "colored_borders" required-features = ["derive"] [[example]] name = "colored_padding" path = "examples/colored_padding.rs" required-features = ["ansi", "derive"] [[example]] name = "disable" required-features = ["derive"] [[example]] name = "rename_all" path = "examples/derive/rename_all.rs" required-features = ["derive"] [[example]] name = "derive_rename" path = "examples/derive/rename.rs" required-features = ["derive"] [[example]] name = "derive_order" path = "examples/derive/order.rs" required-features = ["derive"] [[example]] name = "skip" path = "examples/derive/skip.rs" required-features = ["derive"] [[example]] name = "derive_inline" path = "examples/derive/inline.rs" required-features = ["derive"] [[example]] name = "derive_inline_enum" path = "examples/derive/inline_enum.rs" required-features = ["derive"] [[example]] name = "derive_display_with" path = "examples/derive/display_with.rs" required-features = ["derive"] [[example]] name = "derive_display_type" path = "examples/derive/display_type.rs" required-features = ["derive"] [[example]] name = "derive_crate_override" path = "examples/derive/crate_override.rs" required-features = ["derive"] [[example]] name = "derive_format" path = "examples/derive/format.rs" required-features = ["derive"] [[example]] name = "derive_format_enum" path = "examples/derive/format_enum.rs" required-features = ["derive"] [[example]] name = "table" path = "examples/table.rs" required-features = ["derive"] [[example]] name = "builder_index" path = "examples/builder_index.rs" required-features = ["derive"] [[example]] name = "concat" path = "examples/concat.rs" required-features = ["derive"] [[example]] name = "custom_style" path = "examples/custom_style.rs" required-features = ["derive"] [[example]] name = "extended_display" path = "examples/extended_display.rs" required-features = ["derive"] [[example]] name = "extract" path = "examples/extract.rs" required-features = ["derive"] [[example]] name = "format" path = "examples/format.rs" required-features = ["derive"] [[example]] name = "panel" path = "examples/panel.rs" required-features = ["derive"] [[example]] name = "rotate" path = "examples/rotate.rs" required-features = ["derive"] [[example]] name = "shadow" path = "examples/shadow.rs" required-features = ["macros"] [[example]] name = "nested_table_2" path = "examples/nested_table_2.rs" required-features = ["derive"] [[example]] name = "nested_table_3" path = "examples/nested_table_3.rs" required-features = ["derive"] [[example]] name = "col_row_macros" path = "examples/col_row_macros.rs" required-features = ["macros", "derive"] [[example]] name = "merge_duplicates_horizontal" path = "examples/merge_duplicates_horizontal.rs" required-features = ["derive"] [[example]] name = "merge_duplicates_vertical" path = "examples/merge_duplicates_vertical.rs" required-features = ["derive"] [[example]] name = "hyperlink" path = "examples/hyperlink.rs" required-features = ["derive", "ansi"] [[example]] name = "highlight" path = "examples/highlight.rs" required-features = ["std"] [[example]] name = "highlight_color" path = "examples/highlight_color.rs" required-features = ["std"] [[example]] name = "border_text" path = "examples/border_text.rs" required-features = ["std"] [[example]] name = "span" path = "examples/span.rs" required-features = ["std"] [[example]] name = "span_column" path = "examples/span_column.rs" required-features = ["std"] [[example]] name = "span_row" path = "examples/span_row.rs" required-features = ["std"] [[example]] name = "nested_table" path = "examples/nested_table.rs" required-features = ["std"] [[example]] name = "builder" path = "examples/builder.rs" required-features = ["std"] [[example]] name = "table_width" path = "examples/table_width.rs" required-features = ["std"] [[example]] name = "table_width_2" path = "examples/table_width_2.rs" required-features = ["std"] [[example]] name = "height" path = "examples/height.rs" required-features = ["std"] [[example]] name = "margin" path = "examples/margin.rs" required-features = ["std"] [[example]] name = "iter_table" path = "examples/iter_table.rs" required-features = ["std"] [[example]] name = "matrix" path = "examples/matrix.rs" required-features = ["std"] [[example]] name = "formatting_settings" path = "examples/formatting_settings.rs" required-features = ["std"] [[example]] name = "settings_list" path = "examples/settings_list.rs" required-features = ["derive"] [[example]] name = "grid_colors" path = "examples/grid_colors.rs" required-features = ["derive"] [[example]] name = "compact_table" path = "examples/compact_table.rs" required-features = [] [[example]] name = "compact_table_2" path = "examples/compact_table_2.rs" required-features = [] [[example]] name = "alphabet" path = "examples/alphabet.rs" required-features = ["std"] [[example]] name = "split" path = "examples/split.rs" required-features = ["std", "macros"] [[example]] name = "pool_table" path = "examples/pool_table.rs" required-features = ["std"] [[example]] name = "pool_table2" path = "examples/pool_table2.rs" required-features = ["std"] [[example]] name = "column_names" path = "examples/column_names.rs" required-features = ["std", "derive"] [[example]] name = "colorization" path = "examples/colorization.rs" required-features = ["std", "derive"] [[example]] name = "chess" path = "examples/chess.rs" required-features = ["std"] [[example]] name = "target_content" path = "examples/target_content.rs" required-features = ["std", "derive"] [[example]] name = "style_modern_rounded" path = "examples/style_modern_rounded.rs" required-features = ["std"] [[example]] name = "reverse" path = "examples/reverse.rs" required-features = ["std"] [[example]] name = "theme" path = "examples/theme.rs" required-features = ["derive", "std"] [[example]] name = "interactive" path = "examples/interactive.rs" required-features = ["derive"] [[example]] name = "option" path = "examples/option.rs" required-features = ["derive"] [features] default = ["derive", "macros"] std = ["papergrid/std"] derive = ["tabled_derive", "std"] ansi = ["papergrid/ansi", "ansi-str", "ansitok", "std"] macros = ["std"] [dependencies] papergrid = { version = "0.14", default-features = false } tabled_derive = { version = "0.10", optional = true } ansi-str = { version = "0.9", optional = true } ansitok = { version = "0.3", optional = true } [dev-dependencies] testing_table = { version = "0.2", features = ["ansi"] } # To run it locally (probably need to `add #![feature(doc_cfg)]` to the crate attributes to enable. # # RUSTDOCFLAGS="--cfg docsrs" cargo +nightly -Zunstable-options doc --no-deps --all-features --open [package.metadata.docs.rs] all-features = true targets = ["x86_64-unknown-linux-gnu"] rustdoc-args = ["--cfg", "docsrs"] rustc-args = ["--cfg", "docsrs"] tabled-0.18.0/LICENSE-MIT000064400000000000000000000020561046102023000126040ustar 00000000000000MIT 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. tabled-0.18.0/README.md000064400000000000000000000066351046102023000124360ustar 00000000000000[github](https://github.com/zhiburt/tabled/) [crates.io](https://crates.io/crates/tabled) [docs.rs](https://docs.rs/tabled) [build status](https://github.com/zhiburt/tabled/actions) [coverage](https://coveralls.io/github/zhiburt/tabled) [dependency status](https://deps.rs/repo/github/zhiburt/tabled) # tabled An easy to use library for pretty printing tables of Rust `struct`s and `enum`s. There are more examples and you can find in this [`README`](https://github.com/zhiburt/tabled/blob/master/README.md). ## Usage To print a list of structs or enums as a table your types should implement the the `Tabled` trait or derive it with a `#[derive(Tabled)]` macro. Most of the default types implement the trait out of the box. Most of a table configuration can be found in [`tabled::settings`](https://docs.rs/tabled/latest/tabled/settings/index.html) module. ```rust use tabled::{Table, Tabled}; use testing_table::assert_table; #[derive(Tabled)] struct Language<'a> { name: &'a str, designed_by: &'a str, invented_year: usize, } let languages = vec![ Language { name: "C", designed_by: "Dennis Ritchie", invented_year: 1972 }, Language { name: "Go", designed_by: "Rob Pike", invented_year: 2009 }, Language { name: "Rust", designed_by: "Graydon Hoare", invented_year: 2010 }, Language { name: "Hare", designed_by: "Drew DeVault", invented_year: 2022 }, ]; let table = Table::new(languages); assert_table!( table, "+------+----------------+---------------+" "| name | designed_by | invented_year |" "+------+----------------+---------------+" "| C | Dennis Ritchie | 1972 |" "+------+----------------+---------------+" "| Go | Rob Pike | 2009 |" "+------+----------------+---------------+" "| Rust | Graydon Hoare | 2010 |" "+------+----------------+---------------+" "| Hare | Drew DeVault | 2022 |" "+------+----------------+---------------+" ); ``` The same example but we are building a table step by step. ```rust use tabled::{builder::Builder, settings::Style}; use testing_table::assert_table; let mut builder = Builder::new(); builder.push_record(["C", "Dennis Ritchie", "1972"]); builder.push_record(["Go", "Rob Pike", "2009"]); builder.push_record(["Rust", "Graydon Hoare", "2010"]); builder.push_record(["Hare", "Drew DeVault", "2022"]); let mut table = builder.build(); table.with(Style::ascii_rounded()); assert_table!( table, ".------------------------------." "| C | Dennis Ritchie | 1972 |" "| Go | Rob Pike | 2009 |" "| Rust | Graydon Hoare | 2010 |" "| Hare | Drew DeVault | 2022 |" "'------------------------------'" ); ``` tabled-0.18.0/examples/README.md000064400000000000000000001426061046102023000142530ustar 00000000000000This file contains an overview of examples. - `derive` folder contains a list of examples which uses different `#[derive(Tabled)]` attributes. - `show` folder contains a program which uses different styles and prints the resulting table. - `terminal_size` folder contains a program which spreads the table to the max terminal width and max terminal height. You can use which dimension to use via args `--width`, `--height` by default 2 are used. Bellow there's a list of results of running some examples. ## table ``` | name | based_on | is_active | is_cool | |---------|----------|-----------|---------| | Manjaro | Arch | true | true | | Arch | | true | true | | Debian | | true | true | ``` ## border_text ``` Numbers ─┬────┬────┬────┐ │ 0 │ 1 │ 2 │ 3 │ 4 │ More numbers ─┼────┼────┤ │ 5 │ 6 │ 7 │ 8 │ 9 │ │ 10 │ 11 │ 12 │ 13 │ 14 │ end. ────┴────┴────┴────┘ ``` ## builder_index ``` ┌───────────┬─────────┬──────┬────────┐ │ │ Manjaro │ Arch │ Debian │ ├───────────┼─────────┼──────┼────────┤ │ based_on │ Arch │ None │ None │ ├───────────┼─────────┼──────┼────────┤ │ is_active │ true │ true │ true │ ├───────────┼─────────┼──────┼────────┤ │ is_cool │ true │ true │ true │ └───────────┴─────────┴──────┴────────┘ ``` ## builder ``` | https://en.wikipedia.org/wiki/Ocean | |---------------+---------------------| | The terms "the ocean" or "the sea" | | used without specification refer to | | the interconnected body of salt wa | | ter covering the majority of the Ea | | rth's surface | | =================================== | | # | Ocean | | 0 | Atlantic | | 1 | Pacific | | 2 | Indian | | 3 | Southern | | 4 | Arctic | ``` ## chess Preview ## col_row_macros ``` +-------------------------------------------+---------------------------------------------+ | .---------------------------------------. | ┌────────────────────┬─────┬──────────────┐ | | | name | age | is_validated | | │ name │ age │ is_validated │ | | | Jon Doe | 255 | false | | ├────────────────────┼─────┼──────────────┤ | | | Mark Nelson | 13 | true | | │ Jack Black │ 51 │ false │ | | | Terminal Monitor | 0 | false | | ├────────────────────┼─────┼──────────────┤ | | | Adam Blend | 17 | true | | │ Michelle Goldstein │ 44 │ true │ | | '---------------------------------------' | └────────────────────┴─────┴──────────────┘ | +-------------------------------------------+---------------------------------------------+ +-------------------------------------------+ | .---------------------------------------. | | | name | age | is_validated | | | | Jon Doe | 255 | false | | | | Mark Nelson | 13 | true | | | | Terminal Monitor | 0 | false | | | | Adam Blend | 17 | true | | | '---------------------------------------' | +-------------------------------------------+ | .---------------------------------------. | | | name | age | is_validated | | | | Jon Doe | 255 | false | | | | Mark Nelson | 13 | true | | | | Terminal Monitor | 0 | false | | | | Adam Blend | 17 | true | | | '---------------------------------------' | +-------------------------------------------+ | .---------------------------------------. | | | name | age | is_validated | | | | Jon Doe | 255 | false | | | | Mark Nelson | 13 | true | | | | Terminal Monitor | 0 | false | | | | Adam Blend | 17 | true | | | '---------------------------------------' | +-------------------------------------------+ +-------------------------------------------------------------------------------+ | +-------+-----+--------------+ ┌────────────────────┬─────┬──────────────┐ | | | name | age | is_validated | │ name │ age │ is_validated │ | | +-------+-----+--------------+ ├────────────────────┼─────┼──────────────┤ | | | Sam | 31 | true | │ Jack Black │ 51 │ false │ | | +-------+-----+--------------+ ├────────────────────┼─────┼──────────────┤ | | | Sarah | 26 | true | │ Michelle Goldstein │ 44 │ true │ | | +-------+-----+--------------+ └────────────────────┴─────┴──────────────┘ | +-------------------------------------------------------------------------------+ | .---------------------------------------. | | | name | age | is_validated | | | | Jon Doe | 255 | false | | | | Mark Nelson | 13 | true | | | | Terminal Monitor | 0 | false | | | | Adam Blend | 17 | true | | | '---------------------------------------' | +-------------------------------------------------------------------------------+ ``` ## color Preview ## colored_borders Preview ## colored_padding Preview ## colorization Preview ## column_names Preview ## concat ``` temperature_c wind_ms latitude longitude 16 3000 111.111 333.333 -20 300 5.111 7282.1 40 100 0 0 0 0 ``` ## custom_style ``` ┌────────────────────┬─────────────────────────────────┐ │ name │ first_release developer │ ├────────────────────┼─────────────────────────────────┤ │ Sublime Text 3 │ 2008 Sublime HQ │ │ Visual Studio Code │ 2015 Microsoft │ │ Notepad++ │ 2003 Don Ho │ │ GNU Emacs │ 1984 Richard Stallman │ │ Neovim │ 2015 Vim community │ └────────────────────┴─────────────────────────────────┘ ``` ## disable ``` ########### # name # based_on | is_cool | ###########----------|---------| # Debian # | true | ########### # Arch # | true | ########### # Manjaro # Arch | true | ########### ``` ## expanded_display ``` -[ RECORD 0 ]------ name | Manjaro based_on | Arch is_active | true is_cool | true -[ RECORD 1 ]------ name | Arch based_on | is_active | true is_cool | true -[ RECORD 2 ]------ name | Debian based_on | is_active | true is_cool | true ``` ## extract ``` ┌───────────────┬───────────────────────────┬──────────────────┬────────────────────┐ │ artist │ name │ released │ level_of_greatness │ ├───────────────┼───────────────────────────┼──────────────────┼────────────────────┤ │ Pink Floyd │ The Dark Side of the Moon │ 01 March 1973 │ Unparalleled │ ├───────────────┼───────────────────────────┼──────────────────┼────────────────────┤ │ Fleetwood Mac │ Rumours │ 04 February 1977 │ Outstanding │ ├───────────────┼───────────────────────────┼──────────────────┼────────────────────┤ │ Led Zeppelin │ Led Zeppelin IV │ 08 November 1971 │ Supreme │ └───────────────┴───────────────────────────┴──────────────────┴────────────────────┘ ┼───────────────────────────┼──────────────────┼──────────────┤ │ The Dark Side of the Moon │ 01 March 1973 │ Unparalleled │ ┼───────────────────────────┼──────────────────┼──────────────┤ │ Rumours │ 04 February 1977 │ Outstanding │ ┼───────────────────────────┼──────────────────┼──────────────┤ ┌───────────────────────────┬──────────────────┬───────────────┐ │ The Dark Side of the Moon │ 01 March 1973 │ Unparalleled │ ├───────────────────────────┼──────────────────┼───────────────┤ │ Rumours │ 04 February 1977 │ +Outstanding+ │ └───────────────────────────┴──────────────────┴───────────────┘ ``` ## format ``` 0 | 1 | 2 ---------------------------------------------+--------------------------------+------------------------- 8ae4e8957caeaa467acbce963701e227af00a1c7... | bypass open-source transmitter | index neural panel 48c76de71bd685486d97dc8f4f05aa6fcc0c3f86... | program online alarm | copy bluetooth card 6ffc2a2796229fc7bf59471ad907f58b897005d0... | CSV | reboot mobile capacitor ``` ## formatting_settings ``` ╭───────────────────╮ │ &str │ ├───────────────────┤ │ │ │ [ │ │ "foo", │ │ { │ │ "bar": 1, │ │ "baz": [ │ │ 2, │ │ 3 │ │ ] │ │ } │ │ ] │ ╰───────────────────╯ ╭───────────────────╮ │ &str │ ├───────────────────┤ │ │ │ [ │ │ "foo", │ │ { │ │ "bar": 1, │ │ "baz": [ │ │ 2, │ │ 3 │ │ ] │ │ } │ │ ] │ ╰───────────────────╯ ╭───────────────────╮ │ &str │ ├───────────────────┤ │ [ │ │ "foo", │ │ { │ │ "bar": 1, │ │ "baz": [ │ │ 2, │ │ 3 │ │ ] │ │ } │ │ ] │ │ │ ╰───────────────────╯ ``` ## highlight ``` ************* * 0 │ 1 │ 2 * *****───***** │ A * B * C │ ├───*───*───┤ │ D * E * F │ ├───*───*───┤ │ G * H * I │ └───*****───┘ ``` ## margin ``` vvvvvvvvvvvvvvvvvv vvvvvvvvvvvvvvvvvv <<<<=== === ===>>> <<<< 0 1 2 >>> <<<<=== === ===>>> <<<< A B C >>> <<<< D E F >>> <<<< G H I >>> <<<<=== === ===>>> ^^^^^^^^^^^^^^^^^^ ``` ## nested_table ``` +-----------------------------------------------+ | +---------------------+ | | | Animal | | | +---------------------+ | | | +-----------------+ | | | | | +age: Int | | | | | | +gender: String | | | | | +-----------------+ | | | | +-----------------+ | | | | | +isMammal() | | | | | | +mate() | | | | | +-----------------+ | | | +---------------------+ | | ▲ | | | | | | | | +-----------------------------------+ | | | Duck | | | +-----------------------------------+ | | | +-------------------------------+ | | | | | +beakColor: String = "yellow" | | | | | +-------------------------------+ | | | | +-------------------------------+ | | | | | +swim() | | | | | | +quack() | | | | | +-------------------------------+ | | | +-----------------------------------+ | +-----------------------------------------------+ ``` ## nested_table_2 ``` ┌───────┬─────────────────────────────────────────────────┬──────────────────────────────────────────────┐ │ name │ main_os │ switch_os │ ├───────┼─────────────────────────────────────────────────┼──────────────────────────────────────────────┤ │ Azure │ ╔═════════╦═════════════╦═══════════╦═════════╗ │ ╔═════════╦══════════╦═══════════╦═════════╗ │ │ │ ║ name ║ based_on ║ is_active ║ is_cool ║ │ ║ name ║ based_on ║ is_active ║ is_cool ║ │ │ │ ╠═════════╬═════════════╬═══════════╬═════════╣ │ ╠═════════╬══════════╬═══════════╬═════════╣ │ │ │ ║ Windows ║ Independent ║ true ║ true ║ │ ║ Manjaro ║ Arch ║ true ║ true ║ │ │ │ ╚═════════╩═════════════╩═══════════╩═════════╝ │ ╚═════════╩══════════╩═══════════╩═════════╝ │ ├───────┼─────────────────────────────────────────────────┼──────────────────────────────────────────────┤ │ AWS │ ╔════════╦═════════════╦═══════════╦═════════╗ │ ╔══════╦═════════════╦═══════════╦═════════╗ │ │ │ ║ name ║ based_on ║ is_active ║ is_cool ║ │ ║ name ║ based_on ║ is_active ║ is_cool ║ │ │ │ ╠════════╬═════════════╬═══════════╬═════════╣ │ ╠══════╬═════════════╬═══════════╬═════════╣ │ │ │ ║ Debian ║ Independent ║ true ║ true ║ │ ║ Arch ║ Independent ║ true ║ true ║ │ │ │ ╚════════╩═════════════╩═══════════╩═════════╝ │ ╚══════╩═════════════╩═══════════╩═════════╝ │ ├───────┼─────────────────────────────────────────────────┼──────────────────────────────────────────────┤ │ GCP │ ╔════════╦═════════════╦═══════════╦═════════╗ │ ╔══════╦═════════════╦═══════════╦═════════╗ │ │ │ ║ name ║ based_on ║ is_active ║ is_cool ║ │ ║ name ║ based_on ║ is_active ║ is_cool ║ │ │ │ ╠════════╬═════════════╬═══════════╬═════════╣ │ ╠══════╬═════════════╬═══════════╬═════════╣ │ │ │ ║ Debian ║ Independent ║ true ║ true ║ │ ║ Arch ║ Independent ║ true ║ true ║ │ │ │ ╚════════╩═════════════╩═══════════╩═════════╝ │ ╚══════╩═════════════╩═══════════╩═════════╝ │ └───────┴─────────────────────────────────────────────────┴──────────────────────────────────────────────┘ ``` ## nested_table_3 ``` ************************************************* * Thank You * ************************************************* | +------------+------------------------------+ | | | Contributors | | | +------------+------------------------------+ | | | author | profile | | | +------------+------------------------------+ | | | kozmod | https:/github.com/kozmod | | | +------------+------------------------------+ | | | IsaacCloos | https:/github.com/IsaacCloos | | | +------------+------------------------------+ | | +-----------+-----------------------------+ | | | Issuers | | | +-----------+-----------------------------+ | | | author | profile | | | +-----------+-----------------------------+ | | | aharpervc | https:/github.com/aharpervc | | | +-----------+-----------------------------+ | +-----------------------------------------------+ ``` ## panel ``` ┌───┬────────────────────────────────────────────────────────────────────┬───┐ │ S │ Tabled Releases │ S │ │ o │ │ o │ │ m │ │ m │ │ e │ │ e │ │ ├─────────┬────────────────┬───────────┬─────────────────────────────┤ │ │ t │ version │ published_date │ is_active │ major_feature │ t │ │ e │ │ │ │ │ e │ │ x ├─────────┼────────────────┼───────────┼─────────────────────────────┤ x │ │ t │ 0.2.1 │ 2021-06-23 │ true │ #[header(inline)] attribute │ t │ │ │ │ │ │ │ │ │ g ├─────────┼────────────────┼───────────┼─────────────────────────────┤ g │ │ o │ 0.2.0 │ 2021-06-19 │ false │ API changes │ o │ │ e │ │ │ │ │ e │ │ s ├─────────┼────────────────┼───────────┼─────────────────────────────┤ s │ │ │ 0.1.4 │ 2021-06-07 │ false │ display_with attribute │ │ │ h │ │ │ │ │ h │ │ e ├─────────┴────────────────┴───────────┴─────────────────────────────┤ e │ │ r │ N - 3 │ r │ │ e │ │ e │ └───┴────────────────────────────────────────────────────────────────────┴───┘ ``` ## print_matrix ``` ┌────┬────┬────┬────┬────┬────┬────┬────┬────┬─────┐ │ 0 │ 1 │ 2 │ 3 │ 4 │ 5 │ 6 │ 7 │ 8 │ 9 │ ├────┼────┼────┼────┼────┼────┼────┼────┼────┼─────┤ │ 1 │ 2 │ 3 │ 4 │ 5 │ 6 │ 7 │ 8 │ 9 │ 10 │ ├────┼────┼────┼────┼────┼────┼────┼────┼────┼─────┤ │ 2 │ 4 │ 6 │ 8 │ 10 │ 12 │ 14 │ 16 │ 18 │ 20 │ ├────┼────┼────┼────┼────┼────┼────┼────┼────┼─────┤ │ 3 │ 6 │ 9 │ 12 │ 15 │ 18 │ 21 │ 24 │ 27 │ 30 │ ├────┼────┼────┼────┼────┼────┼────┼────┼────┼─────┤ │ 4 │ 8 │ 12 │ 16 │ 20 │ 24 │ 28 │ 32 │ 36 │ 40 │ ├────┼────┼────┼────┼────┼────┼────┼────┼────┼─────┤ │ 5 │ 10 │ 15 │ 20 │ 25 │ 30 │ 35 │ 40 │ 45 │ 50 │ ├────┼────┼────┼────┼────┼────┼────┼────┼────┼─────┤ │ 6 │ 12 │ 18 │ 24 │ 30 │ 36 │ 42 │ 48 │ 54 │ 60 │ ├────┼────┼────┼────┼────┼────┼────┼────┼────┼─────┤ │ 7 │ 14 │ 21 │ 28 │ 35 │ 42 │ 49 │ 56 │ 63 │ 70 │ ├────┼────┼────┼────┼────┼────┼────┼────┼────┼─────┤ │ 8 │ 16 │ 24 │ 32 │ 40 │ 48 │ 56 │ 64 │ 72 │ 80 │ ├────┼────┼────┼────┼────┼────┼────┼────┼────┼─────┤ │ 9 │ 18 │ 27 │ 36 │ 45 │ 54 │ 63 │ 72 │ 81 │ 90 │ ├────┼────┼────┼────┼────┼────┼────┼────┼────┼─────┤ │ 10 │ 20 │ 30 │ 40 │ 50 │ 60 │ 70 │ 80 │ 90 │ 100 │ └────┴────┴────┴────┴────┴────┴────┴────┴────┴─────┘ ``` ## rotate ``` +--------------+------------------------+---------------------------+--------------------------+ | link | https://getfedora.org/ | https://www.opensuse.org/ | https://endeavouros.com/ | +--------------+------------------------+---------------------------+--------------------------+ | distribution | Fedora | OpenSUSE | Endeavouros | +--------------+------------------------+---------------------------+--------------------------+ | id | 0 | 2 | 3 | +--------------+------------------------+---------------------------+--------------------------+ ``` ## shadow ``` ┌──┬┐ ╔══╦╗ ╓──┬╖ ╒═╤╕ │ ││ ║ ║║ ║ │║ │ ││ ├──┼┤ ╠══╬╣ ╟──┼╢ ╞═╪╡ └──┴┘ ╚══╩╝ ╙──┴╜ ╘═╧╛ ┌──────────────────┐ │ ╔═══╗ Some text │▒▒ │ ╚═╦═╝ In the box│▒▒ ╞═╤══╩══╤══════════╡▒▒ │ ├──┬──┤ │▒▒ │ └──┴──┘ │▒▒ └──────────────────┘▒▒ ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒ ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒ ``` ## span ``` ┌───────────────────────────────────────────────────────────────────────────────┐ │ span all 5 columns │ ├───────────────────────────────────────────────────────────────┬───────────────┤ │ span 4 columns │ just 1 column │ ├───────────────────────────────┬───────────────┬───────────────┼───────────────┤ │ span 2 columns │ just 1 column │ │ │ ├───────────────┬───────────────┴───────────────┤ just 1 column │ │ │ just 1 column │ span 2 columns │ span │ just 1 column │ │ │ span │ 3 │ span │ ├───────────────┤ 2 │ columns │ 4 │ │ just 1 column │ columns │ │ columns │ ├───────────────┼───────────────┬───────────────┼───────────────┤ │ │ just 1 column │ just 1 column │ just 1 column │ just 1 column │ │ └───────────────┴───────────────┴───────────────┴───────────────┴───────────────┘ ``` ## table_width ``` | 0 | 1 | |------------------|-----------| | Hello World!!! | 3.3.22.2 | | Guten Morgen | 1.1.1.1 | | Добры вечар | 127.0.0.1 | | Bonjour le monde | | | Ciao mondo | | | 0 | 1 | |------------|-----| | Hello W... | ... | | Guten M... | ... | | Добры в... | ... | | Bonjour... | | | Ciao mondo | | | 0 | 1 | |-------|-----| | Hello | ... | | W... | | | Guten | ... | | M... | | | Добры | ... | | в... | | | Bonjo | | | ur... | | | Ciao | | | mondo | | | 0 | 1 | |---------------|------------| | Hello | ... | | W... | | | Guten | ... | | M... | | | Добры | ... | | в... | | | Bonjo | | | ur... | | | Ciao | | | mondo | | ``` ## table_width_2 ``` .----------------------------------------. | usize | &str | | 0 | # Changelog | | 1 | All notable changes to this | | | projectwill be documented in | | | thisfile. | | 2 | The format is based on [Keep a | | | Changelog](https://keepachange | | | log.com/en/1.0.0/), | | 3 | and this project adheres to | | | [SemanticVersioning](https://s | | | emver.org/spec/v2.0.0.html). | | 4 | ## Unreleased | '-------+--------------------------------' ``` ## alphabet ``` +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | a | b | c | d | e | f | g | h | i | j | k | l | m | n | o | p | q | r | s | t | u | v | w | x | y | z | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ ``` ## compact_table ``` | Debian | | true | |-------|-----|-----| | Arch | | true | | Manjaro | Arch | true | ``` ## compact_table_2 ``` Debian | 1.1.1.1 | true ---------+-----------+------ Arch | 127.1.1.1 | true Manjaro | Arch | true ``` ## extended_display ``` -[ RECORD 0 ]------ name | Manjaro based_on | Arch is_active | true is_cool | true -[ RECORD 1 ]------ name | Arch based_on | is_active | true is_cool | true -[ RECORD 2 ]------ name | Debian based_on | is_active | true is_cool | true ``` ## height ``` Table | &str | i32 | |-------------|-----| | Multi | 123 | | line | | | string | | | Single line | 234 | Table increase height to 10 | &str | i32 | | | | | | | |-------------|-----| | Multi | 123 | | line | | | string | | | | | | Single line | 234 | | | | Table decrease height to 4 | &str | i32 | |-------------|-----| | Multi | 123 | | Single line | 234 | Table decrease height to 0 |--|--| ``` ## iter_table ``` .----------------------------------------------------------------------------------------. | 0 | ok | //! The example can be run by this command | | 1 | ok | //! `cargo run --example iter_table` | | 2 | ok | | | 3 | ok | use std::io::BufRead; | | 4 | ok | | | 5 | ok | use tabled::{settings::Style, tables::IterTable}; | | 6 | ok | | | 7 | ok | fn main() { | | 8 | ok | let path = file!(); | | 9 | ok | let file = std::fs::File::open(path).unwrap(); | | 10 | ok | let reader = std::io::BufReader::new(file); | | 11 | ok | let iterator = reader.lines().enumerate().map(|(i, line)| match line { | | 12 | ok | Ok(line) => [i.to_string(), String::from("ok"), line], | | 13 | ok | Err(err) => [i.to_string(), String::from("error"), err.to_string()], | | 14 | ok | }); | | 15 | ok | | | 16 | ok | let table = IterTable::new(iterator).with(Style::ascii_rounded()); | | 17 | ok | | | 18 | ok | table.build(std::io::stdout()).unwrap(); | | 19 | ok | println!() | | 20 | ok | } | '----------------------------------------------------------------------------------------' ``` ## margin ``` vvvvvvvvvvvvvvvvvv vvvvvvvvvvvvvvvvvv <<<<=== === ===>>> <<<< 0 1 2 >>> <<<<=== === ===>>> <<<< A B C >>> <<<< D E F >>> <<<< G H I >>> <<<<=== === ===>>> ^^^^^^^^^^^^^^^^^^ ``` ## matrix ``` ┌────┬────┬────┬────┬────┬────┬────┬────┬────┬─────┐ │ 0 │ 1 │ 2 │ 3 │ 4 │ 5 │ 6 │ 7 │ 8 │ 9 │ ├────┼────┼────┼────┼────┼────┼────┼────┼────┼─────┤ │ 1 │ 2 │ 3 │ 4 │ 5 │ 6 │ 7 │ 8 │ 9 │ 10 │ ├────┼────┼────┼────┼────┼────┼────┼────┼────┼─────┤ │ 2 │ 4 │ 6 │ 8 │ 10 │ 12 │ 14 │ 16 │ 18 │ 20 │ ├────┼────┼────┼────┼────┼────┼────┼────┼────┼─────┤ │ 3 │ 6 │ 9 │ 12 │ 15 │ 18 │ 21 │ 24 │ 27 │ 30 │ ├────┼────┼────┼────┼────┼────┼────┼────┼────┼─────┤ │ 4 │ 8 │ 12 │ 16 │ 20 │ 24 │ 28 │ 32 │ 36 │ 40 │ ├────┼────┼────┼────┼────┼────┼────┼────┼────┼─────┤ │ 5 │ 10 │ 15 │ 20 │ 25 │ 30 │ 35 │ 40 │ 45 │ 50 │ ├────┼────┼────┼────┼────┼────┼────┼────┼────┼─────┤ │ 6 │ 12 │ 18 │ 24 │ 30 │ 36 │ 42 │ 48 │ 54 │ 60 │ ├────┼────┼────┼────┼────┼────┼────┼────┼────┼─────┤ │ 7 │ 14 │ 21 │ 28 │ 35 │ 42 │ 49 │ 56 │ 63 │ 70 │ ├────┼────┼────┼────┼────┼────┼────┼────┼────┼─────┤ │ 8 │ 16 │ 24 │ 32 │ 40 │ 48 │ 56 │ 64 │ 72 │ 80 │ ├────┼────┼────┼────┼────┼────┼────┼────┼────┼─────┤ │ 9 │ 18 │ 27 │ 36 │ 45 │ 54 │ 63 │ 72 │ 81 │ 90 │ ├────┼────┼────┼────┼────┼────┼────┼────┼────┼─────┤ │ 10 │ 20 │ 30 │ 40 │ 50 │ 60 │ 70 │ 80 │ 90 │ 100 │ └────┴────┴────┴────┴────┴────┴────┴────┴────┴─────┘ ``` ## merge_duplicates ``` ┌────────────┬─────────┬────────┐ │ db │ table │ total │ ├────────────┼─────────┼────────┤ │ database_1 │ table_1 │ 10712 │ │ ├─────────┼────────┤ │ │ table_2 │ 57 │ │ ├─────────┤ │ │ │ table_3 │ │ ├────────────┼─────────┼────────┤ │ database_2 │ table_1 │ 72 │ │ ├─────────┼────────┤ │ │ table_2 │ 75 │ ├────────────┼─────────┼────────┤ │ database_3 │ table_1 │ 20 │ │ ├─────────┼────────┤ │ │ table_2 │ 21339 │ │ ├─────────┼────────┤ │ │ table_3 │ 141723 │ └────────────┴─────────┴────────┘ ``` ## merge_duplicates_2 ``` ╭───────────┬───────────────────────────────────────────────────────────────────────────╮ │ │ 0 1 2 3 4 5 6 7 │ ├───────────┼───────────────────────────────────────────────────────────────────────────┤ │ db │ database_1 database_2 database_3 │ │ origin_db │ database_1 database_3 │ │ table │ table_1 table_2 table_3 table_1 table_2 table_1 table_2 table_3 │ │ total │ 10712 57 72 75 20 21339 141723 │ ╰───────────┴───────────────────────────────────────────────────────────────────────────╯ ``` ## settings_list ``` +----------------------+-----------------+--------------------+ | name | first_release | developer | +----------------------+-----------------+--------------------+ | Sublime Text 3 | 2008 | Sublime HQ | +----------------------+-----------------+--------------------+ | Visual Studio Code | 2015 | Microsoft | +----------------------+-----------------+--------------------+ | Notepad++ | 2003 | Don Ho | +----------------------+-----------------+--------------------+ | GNU Emacs | 1984 | Richard Stallman | +----------------------+-----------------+--------------------+ | Neovim | 2015 | Vim community | +----------------------+-----------------+--------------------+ ``` ## split ``` ┌───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┐ │ a │ b │ c │ d │ e │ f │ g │ h │ i │ j │ k │ l │ m │ n │ o │ p │ q │ r │ s │ t │ u │ v │ w │ x │ y │ z │ └───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┘ ┌───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┐ ┌───┬───┐ ┌───┬───┐ ┌───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┐ │ a │ b │ c │ d │ e │ f │ g │ h │ i │ j │ k │ l │ │ a │ b │ │ a │ b │ │ a │ y │ b │ z │ c │ d │ e │ f │ g │ h │ i │ j │ k │ l │ ├───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┤ ├───┼───┤ ├───┼───┤ ├───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┤ │ m │ n │ o │ p │ q │ r │ s │ t │ u │ v │ w │ x │ │ c │ d │ │ m │ n │ │ m │ │ n │ │ o │ p │ q │ r │ s │ t │ u │ v │ w │ x │ ├───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┤ ├───┼───┤ ├───┼───┤ └───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┘ │ y │ z │ │ │ │ │ │ │ │ │ │ │ │ e │ f │ │ y │ z │ ┌───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┐ └───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┘ ├───┼───┤ ├───┼───┤ │ a │ b │ c │ d │ e │ f │ g │ h │ i │ j │ k │ l │ y │ z │ │ g │ h │ │ c │ d │ ├───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┤ ├───┼───┤ ├───┼───┤ │ m │ n │ o │ p │ q │ r │ s │ t │ u │ v │ w │ x │ │ │ │ i │ j │ │ o │ p │ └───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┘ ├───┼───┤ ├───┼───┤ │ k │ l │ │ e │ f │ ├───┼───┤ ├───┼───┤ │ m │ n │ │ q │ r │ ├───┼───┤ ├───┼───┤ │ o │ p │ │ g │ h │ ├───┼───┤ ├───┼───┤ │ q │ r │ │ s │ t │ ├───┼───┤ ├───┼───┤ │ s │ t │ │ i │ j │ ├───┼───┤ ├───┼───┤ │ u │ v │ │ u │ v │ ├───┼───┤ ├───┼───┤ │ w │ x │ │ k │ l │ ├───┼───┤ ├───┼───┤ │ y │ z │ │ w │ x │ └───┴───┘ └───┴───┘ ```tabled-0.18.0/examples/alphabet.rs000064400000000000000000000006351046102023000151150ustar 00000000000000//! This example demonstrates instantiating a [`Table`] from an [`IntoIterator`] compliant object. //! //! * Note how [`Range`] [expression syntax](https://doc.rust-lang.org/reference/expressions/range-expr.html) //! is used to idiomatically represent the English alphabet. use std::iter::FromIterator; use tabled::Table; fn main() { let table = Table::from_iter(['a'..='z']); println!("{table}"); } tabled-0.18.0/examples/border_text.rs000064400000000000000000000030421046102023000156510ustar 00000000000000//! This example demonstrates inserting text into the borders //! of a [`Table`] with [`BorderText`]; a powerful labeling tool. //! //! * [`BorderText`] currently supports: //! * Horizontal border placement //! * Placement starting column offset //! * Text colorization //! //! * Note how the flexibility of [`Style`] is utilized //! to remove horizontal borders from the table entirely, //! and then granularly reinserts one for a highly customized //! visualization. //! //! * Note how the [`Rows`] utility object is used to idiomatically //! reference the first and last rows of a [`Table`] without writing //! the necessary logic by hand. //! //! * 🚀 Combining several easy-to-use tools, //! to create unique data representations is what makes [`tabled`] great! use tabled::{ settings::{ object::Rows, style::{Border, HorizontalLine, LineText, Style}, Theme, }, Table, }; fn main() { let data = [[5, 6, 7, 8, 9], [10, 11, 12, 13, 14]]; let hline = HorizontalLine::inherit(Style::modern()).left(Border::inherit(Style::modern()).get_left()); let mut theme = Theme::from_style(Style::modern()); theme.remove_horizontal_lines(); theme.insert_horizontal_line(1, hline); let table = Table::new(data) .with(theme) .with(LineText::new("Numbers", Rows::first()).offset(1)) .with(LineText::new("More numbers", Rows::single(1)).offset(1)) .with(LineText::new("end", Rows::last() + 1).offset(1)) .to_string(); println!("{table}"); } tabled-0.18.0/examples/builder.rs000064400000000000000000000020711046102023000147570ustar 00000000000000//! This example demonstrates an alternative method for creating a [`Table`]. //! [`Builder`] is an efficient implementation of the [builder design pattern](https://en.wikipedia.org/wiki/Builder_pattern). //! //! > The intent of the Builder design pattern is to separate the construction of a complex object from its representation. //! > -- Wikipedia //! //! * Note how [Builder] can be used to define a table's shape manually //! and can be populated through iteration if it is mutable. This flexibility //! is useful when you don't have direct control over the datasets you intend to [table](tabled). use tabled::{builder::Builder, settings::Style}; fn main() { let oceans = "Atlantic, Pacific, Indian, Southern, Arctic"; let mut builder = Builder::default(); builder.push_record(["#", "Ocean"]); for (i, ocean) in oceans.split(", ").enumerate() { builder.push_record([i.to_string(), ocean.to_string()]); } let mut table = builder.build(); table.with(Style::markdown().remove_horizontals()); println!("{table}"); } tabled-0.18.0/examples/builder_index.rs000064400000000000000000000026151046102023000161520ustar 00000000000000//! This example demonstrates evolving the standard [`Builder`] to an [`IndexBuilder`], //! and then manipulating the constructing table with a newly prepended index column. //! //! * An [`IndexBuilder`] is capable of several useful manipulations, including: //! * Giving the new index column a name //! * Transposing the index column around a table //! * Choosing a location for the new index column besides 0; the default //! //! * Note that like with any builder pattern the [`IndexBuilder::build()`] function //! is necessary to produce a displayable [`Table`]. use tabled::{settings::Style, Table, Tabled}; #[derive(Tabled)] struct Distribution { name: String, based_on: String, is_active: bool, is_cool: bool, } impl Distribution { fn new(name: &str, based: &str, is_active: bool, is_cool: bool) -> Self { Self { name: name.to_string(), based_on: based.to_string(), is_active, is_cool, } } } fn main() { let data = vec![ Distribution::new("Manjaro", "Arch", true, true), Distribution::new("Arch", "None", true, true), Distribution::new("Debian", "None", true, true), ]; let mut table = Table::builder(data) .index() .column(0) .transpose() .name(None) .build(); table.with(Style::modern_rounded()); println!("{table}"); } tabled-0.18.0/examples/chess.rs000064400000000000000000000021661046102023000144430ustar 00000000000000//! This example demonstrates using the [`Color`] [setting](tabled::settings) to //! stylize text, backgrounds, and borders. //! //! * 🚩 This example requires the `color` feature. //! //! * Note how [`Format::content()`] is used to break out [`CellOption`] //! specifications. This is helpful for organizing extensive [`Table`] configurations. use std::iter::FromIterator; use tabled::{ builder::Builder, settings::{style::Style, themes::Colorization, Color}, }; fn main() { let board = [ ["♜", "♞", "♝", "♛", "♚", "♝", "♞", "♜"], ["♟", "♟", "♟", "♟", "♟", "♟", "♟", "♟"], ["", "", "", "", "", "", "", ""], ["", "", "", "", "", "", "", ""], ["", "", "", "", "", "", "", ""], ["", "", "", "", "", "", "", ""], ["♙", "♙", "♙", "♙", "♙", "♙", "♙", "♙"], ["♖", "♘", "♗", "♕", "♔", "♗", "♘", "♖"], ]; let mut table = Builder::from_iter(board).build(); table .with(Style::empty()) .with(Colorization::chess(Color::BG_WHITE, Color::BG_BLACK)); println!("{table}"); } tabled-0.18.0/examples/col_row_macros.rs000064400000000000000000000034421046102023000163440ustar 00000000000000//! This example demonstrates using the [`col!`] and [`row!`] macros to easily //! organize multiple tables together into a single, new [`Table`] display. //! //! * 🚩 This example requires the `macros` feature. //! //! * Note how both macros can be used in combination to layer //! several table arrangements together. //! //! * Note how [`col!`] and [`row!`] support idiomatic argument duplication //! with the familiar `[T; N]` syntax. use tabled::{ col, row, settings::{Alignment, Style}, Table, Tabled, }; #[derive(Tabled)] struct Person { name: String, age: u8, is_validated: bool, } impl Person { fn new(name: &str, age: u8, is_validated: bool) -> Self { Self { name: name.into(), age, is_validated, } } } fn main() { let validated = [ Person::new("Sam", 31, true), Person::new("Sarah", 26, true), Person::new("Sam", 31, true), ]; let not_validated = [ Person::new("Jack Black", 51, false), Person::new("Michelle Goldstein", 44, false), ]; let unsure = [ Person::new("Jon Doe", 255, false), Person::new("Mark Nelson", 13, true), Person::new("Terminal Monitor", 0, false), Person::new("Adam Blend", 17, true), ]; let validated = Table::new(validated).with(Style::ascii()).to_string(); let not_validated = Table::new(not_validated).with(Style::modern()).to_string(); let unsure = Table::new(unsure).with(Style::ascii_rounded()).to_string(); let output = col![ row![validated, not_validated] .with(Style::empty()) .with(Alignment::center_vertical()), row![unsure; 3].with(Style::empty()) ] .with(Alignment::center()) .to_string(); println!("{output}"); } tabled-0.18.0/examples/color.rs000064400000000000000000000030101046102023000144410ustar 00000000000000//! This example demonstrates using the [`Color`] [setting](tabled::settings) to //! stylize text, backgrounds, and borders. //! //! * 🚩 This example requires the `color` feature. //! //! * Note how [`Format::content()`] is used to break out [`CellOption`] //! specifications. This is helpful for organizing extensive [`Table`] configurations. use tabled::{ settings::{ object::{Columns, Rows}, style::{BorderColor, Style}, Color, }, Table, Tabled, }; #[derive(Tabled)] struct Bsd { distribution: String, first_release: usize, is_active: bool, } impl Bsd { fn new(dist: &str, first_release: usize, is_active: bool) -> Self { Self { distribution: dist.to_string(), first_release, is_active, } } } fn main() { let data = vec![ Bsd::new("BSD", 1978, false), Bsd::new("SunOS", 1982, false), Bsd::new("NetBSD", 1993, true), Bsd::new("FreeBSD", 1993, true), Bsd::new("OpenBSD", 1995, true), ]; let border = BorderColor::new() .bottom(Color::FG_RED) .corner_bottom_left(Color::FG_MAGENTA) .corner_bottom_right(Color::FG_MAGENTA); let mut table = Table::new(data); table .with(Style::psql()) .modify(Rows::first(), border) .modify(Columns::single(0), Color::FG_RED | Color::BG_BRIGHT_WHITE) .modify(Columns::single(1), Color::FG_GREEN) .modify(Columns::single(2), Color::FG_BLUE); println!("{table}"); } tabled-0.18.0/examples/colored_borders.rs000064400000000000000000000041051046102023000165000ustar 00000000000000//! This example demonstrates using the [`RawStyle`] [setting](tabled::settings) to //! to granularly specify border colors. //! //! * 🚩 This example requires the `color` feature. //! //! * Note how [`Color`] contains several helpful, const values covering //! a basic selection of foreground and background colors. [`Color`] also //! supports custom colors with [`Color::new()`]. use tabled::{ settings::{style::Style, themes::Theme, Color}, Table, Tabled, }; #[derive(Tabled)] struct CodeEditor { name: String, first_release: String, developer: String, } impl CodeEditor { fn new(name: &str, first_release: &str, developer: &str) -> Self { Self { name: name.to_string(), first_release: first_release.to_string(), developer: developer.to_string(), } } } fn main() { let data = [ CodeEditor::new("Sublime Text 3", "2008", "Sublime HQ"), CodeEditor::new("Visual Studio Code", "2015", "Microsoft"), CodeEditor::new("Notepad++", "2003", "Don Ho"), CodeEditor::new("GNU Emacs", "1984", "Richard Stallman"), CodeEditor::new("Neovim", "2015", "Vim community"), ]; let mut style = Theme::from(Style::extended()); style.set_colors_top(Color::FG_RED); style.set_colors_bottom(Color::FG_CYAN); style.set_colors_left(Color::FG_BLUE); style.set_colors_right(Color::FG_GREEN); style.set_colors_corner_top_left(Color::FG_BLUE); style.set_colors_corner_top_right(Color::FG_RED); style.set_colors_corner_bottom_left(Color::FG_CYAN); style.set_colors_corner_bottom_right(Color::FG_GREEN); style.set_colors_intersection_bottom(Color::FG_CYAN); style.set_colors_intersection_top(Color::FG_RED); style.set_colors_intersection_right(Color::FG_GREEN); style.set_colors_intersection_left(Color::FG_BLUE); style.set_colors_intersection(Color::FG_MAGENTA); style.set_colors_horizontal(Color::FG_MAGENTA); style.set_colors_vertical(Color::FG_MAGENTA); let table = Table::new(data).with(style).to_string(); println!("{table}"); } tabled-0.18.0/examples/colored_padding.rs000064400000000000000000000062761046102023000164610ustar 00000000000000//! This example demonstrates using the [`Padding::colorize()`] function in several ways //! to give a [`Table`] display a vibrant aesthetic. //! //! * 🚩 This example requires the `color` feature. //! //! * Note how the [`Color`] [setting](tabled::settings) is used to simplify creating //! reusable themes for text, backgrounds, padded whitespace, and borders. //! //! * Note how a unique color can be set for each direction. use tabled::{ settings::{ object::{Columns, Object, Rows, Segment}, style::BorderColor, Alignment, Color, Format, Margin, MarginColor, Modify, Padding, PaddingColor, Style, }, Table, Tabled, }; #[derive(Tabled)] #[tabled(rename_all = "PascalCase")] struct Fundamental { quantity: String, value: String, unit: String, symbol: char, } impl Fundamental { fn new(quantity: &str, symbol: char, value: &str, unit: &str) -> Self { Self { symbol, quantity: quantity.to_string(), value: value.to_string(), unit: unit.to_string(), } } } fn main() { // data source: https://www.britannica.com/science/physical-constant #[rustfmt::skip] let data = [ Fundamental::new("constant of gravitation", 'G', "6.67384 × 10⁻¹¹", "cubic metre per second squared per kilogram"), Fundamental::new("speed of light (in a vacuum)", 'c', "2.99792458 × 10⁻⁸", "metres per second"), Fundamental::new("Planck's constant", 'h', "6.626070040 × 10⁻³⁴", "joule second"), Fundamental::new("Boltzmann constant", 'k', "1.38064852 × 10⁻²³", "joule per kelvin"), Fundamental::new("Faraday constant", 'F', "9.648533289 × 10⁴", "coulombs per mole"), ]; let pane_color = Color::rgb_bg(220, 220, 220); let border_color = Color::rgb_bg(200, 200, 220); let data_color = Color::rgb_bg(200, 200, 220); let header_settings = Modify::new(Rows::first()) .with(Padding::new(1, 1, 2, 2)) .with(PaddingColor::new( Color::BG_GREEN, Color::BG_YELLOW, Color::BG_MAGENTA, Color::BG_CYAN, )) .with(Alignment::center()) .with(Padding::expand(true)) .with(Format::content(|s| { (Color::FG_WHITE | Color::BG_BLACK).colorize(s) })); let data_settings = Modify::new(Rows::first().inverse()) .with(Alignment::center()) .with(Padding::expand(true)) .with(PaddingColor::filled(data_color.clone())); let symbol_settings = Modify::new(Columns::single(1).not(Rows::first())) .with(Format::content(|s| Color::BOLD.colorize(s))); let unit_settings = Modify::new(Columns::single(3).not(Rows::first())) .with(Format::content(|s| Color::UNDERLINE.colorize(s))); let table = Table::new(data) .with(Style::rounded()) .with(Margin::new(1, 2, 1, 1)) .with(MarginColor::filled(pane_color)) .with(BorderColor::filled(border_color)) .with(Modify::new(Segment::all()).with(data_color)) .with(header_settings) .with(data_settings) .with(symbol_settings) .with(unit_settings) .to_string(); println!("\n\n{table}\n\n"); } tabled-0.18.0/examples/colorization.rs000064400000000000000000000041361046102023000160510ustar 00000000000000//! This example demonstrates using the [`Color`] [setting](tabled::settings) to //! stylize text, backgrounds, and borders. //! //! * 🚩 This example requires the `color` feature. //! //! * Note how [`Format::content()`] is used to break out [`CellOption`] //! specifications. This is helpful for organizing extensive [`Table`] configurations. use std::iter::FromIterator; use tabled::{ settings::{object::Rows, style::Style, themes::Colorization, Color, Concat}, Table, Tabled, }; #[derive(Tabled)] #[tabled(rename_all = "UPPERCASE")] struct Employee { id: usize, #[tabled(rename = "FIRST NAME")] first_name: String, #[tabled(rename = "LAST NAME")] last_name: String, salary: usize, comment: String, } impl Employee { fn new(id: usize, first_name: &str, last_name: &str, salary: usize, comment: &str) -> Self { Self { id, salary, first_name: first_name.to_string(), last_name: last_name.to_string(), comment: comment.to_string(), } } } fn main() { let data = vec![ Employee::new(1, "Arya", "Stark", 3000, ""), Employee::new(20, "Jon", "Snow", 2000, "You know nothing, Jon Snow!"), Employee::new(300, "Tyrion", "Lannister", 5000, ""), ]; let total = data.iter().map(|e| e.salary).sum::(); let total_row = Table::from_iter([vec![ String::default(), String::default(), String::from("TOTAL"), total.to_string(), ]]); let clr_data_primary = Color::BG_WHITE | Color::FG_BLACK; let clr_data_second = Color::BG_BRIGHT_WHITE | Color::FG_BLACK; let clr_head = Color::BOLD | Color::BG_CYAN | Color::FG_BLACK; let clr_footer = Color::BOLD | Color::BG_BLUE | Color::FG_BLACK; let mut table = Table::new(data); table .with(Concat::vertical(total_row)) .with(Style::empty()) .with(Colorization::rows([clr_data_primary, clr_data_second])) .with(Colorization::exact([clr_head], Rows::first())) .with(Colorization::exact([clr_footer], Rows::last())); println!("{table}"); } tabled-0.18.0/examples/column_names.rs000064400000000000000000000024501046102023000160120ustar 00000000000000//! This example demonstrates how to set column names on a top horizontal line. //! //! It sets a `clickhouse` like table style (first seen on). use tabled::{ settings::{style::Style, themes::ColumnNames, Alignment, Color}, Table, Tabled, }; #[derive(Debug, Tabled)] struct Function { declaration: String, name: String, return_type: String, } impl Function { fn new(decl: &str, name: &str, ret_type: &str) -> Self { Self { declaration: decl.to_string(), name: name.to_string(), return_type: ret_type.to_string(), } } } fn main() { #[rustfmt::skip] let data = vec![ Function::new("struct stack *stack_create(int)", "stack_create", "struct stack *"), Function::new("void stack_destroy(struct stack *)", "stack_destroy", "void"), Function::new("int stack_put(struct stack *, vm_offset_t)", "stack_put", "int"), Function::new("void stack_copy(const struct stack *, struct stack *)", "stack_copy", "void"), ]; let mut table = Table::new(data); table.with(Style::modern().remove_horizontal()); table.with( ColumnNames::default() .color(Color::BOLD | Color::BG_BLUE | Color::FG_WHITE) .alignment(Alignment::center()), ); println!("{table}"); } tabled-0.18.0/examples/compact_table.rs000064400000000000000000000012611046102023000161260ustar 00000000000000//! This example demonstrates creating a `new()` [`CompactTable`] with //! manual specifications for column count, column widths, and border styling. //! //! * [`CompactTable`] is a [`Table`] alternative that trades off reduced //! flexibility for improved performance. #![allow(unused_variables)] use tabled::{settings::style::Style, tables::CompactTable}; fn main() { let data = [ ["Debian", "", "true"], ["Arch", "", "true"], ["Manjaro", "Arch", "true"], ]; let table = CompactTable::new(data) .columns(3) .width([7, 5, 5]) .with(Style::markdown()); #[cfg(feature = "std")] println!("{}", table.to_string()); } tabled-0.18.0/examples/compact_table_2.rs000064400000000000000000000012341046102023000163470ustar 00000000000000//! This example demonstrates creating a [`CompactTable`] `from()` a //! multidimensional array. //! //! * Note how [`CompactTable::from()`] inherits the lengths of the nested arrays //! as typed definitions through [const generics](https://practice.rs/generics-traits/const-generics.html). #![allow(unused_variables)] use tabled::{settings::style::Style, tables::CompactTable}; fn main() { let data = [ ["Debian", "1.1.1.1", "true"], ["Arch", "127.1.1.1", "true"], ["Manjaro", "Arch", "true"], ]; let table = CompactTable::from(data).with(Style::psql()); #[cfg(feature = "std")] println!("{}", table.to_string()); } tabled-0.18.0/examples/compact_table_3.rs000064400000000000000000000010561046102023000163520ustar 00000000000000//! This example demonstrates how [`CompactTable`] is limited to single //! line rows. //! //! * Note how the multiline data is accepted, but then truncated in the display. #![allow(unused_variables)] use tabled::{settings::style::Style, tables::CompactTable}; fn main() { let data = [ ["De\nbi\nan", "1.1.1.1", "true"], ["Arch", "127.1.1.1", "true"], ["Manjaro", "A\nr\nc\nh", "true"], ]; let table = CompactTable::from(data).with(Style::psql()); #[cfg(feature = "std")] println!("{}", table.to_string()); } tabled-0.18.0/examples/concat.rs000064400000000000000000000026111046102023000146000ustar 00000000000000//! This example demonstrates using the [`Concat`] [`TableOption`] to concatenate //! [`tables`](Table) together. //! //! * [`Concat`] supports appending tables vertically and horizontally. //! //! * Note how the base tables style settings take take precedence over the appended table. //! If the two tables are of unequal shape, additional blank cells are added as needed. use tabled::{ settings::{Alignment, Concat, Style}, Table, Tabled, }; #[derive(Debug, Tabled)] struct Weather { temperature_c: f64, wind_ms: f64, } #[derive(Debug, Tabled)] struct Location( #[tabled(rename = "latitude")] f64, #[tabled(rename = "longitude")] f64, ); fn main() { let weather_data = [ Weather { temperature_c: 1.0, wind_ms: 3.0, }, Weather { temperature_c: -20.0, wind_ms: 30.0, }, Weather { temperature_c: 40.0, wind_ms: 100.0, }, ]; let location_data = [ Location(111.111, 333.333), Location(5.111, 7282.1), Location(0.0, 0.0), Location(0.0, 0.0), ]; let location_table = Table::new(location_data); let mut weather_table = Table::new(weather_data); weather_table .with(Concat::horizontal(location_table)) .with(Style::empty()) .with(Alignment::right()); println!("{weather_table}"); } tabled-0.18.0/examples/custom_style.rs000064400000000000000000000030041046102023000160600ustar 00000000000000//! This example demonstrates customizing one of the [`tabled`] default [styles](Style) //! to create a unique [`Table`] display. //! //! * Note that all predesigned styles can be configured completely. //! Styles can also be created from scratch! //! //! * Note that adding and removing borders with a [`Style`] theme doesn't affect the //! number of functional columns and rows. use tabled::{ settings::{ style::{HorizontalLine, Style, VerticalLine}, Alignment, }, Table, Tabled, }; #[derive(Tabled)] struct CodeEditor { name: String, developer: String, first_release: usize, } impl CodeEditor { fn new(name: &str, first_release: usize, developer: &str) -> Self { Self { first_release, name: name.to_string(), developer: developer.to_string(), } } } fn main() { let data = [ CodeEditor::new("Sublime Text 3", 2008, "Sublime HQ"), CodeEditor::new("Visual Studio Code", 2015, "Microsoft"), CodeEditor::new("Notepad++", 2003, "Don Ho"), CodeEditor::new("GNU Emacs", 1984, "Richard Stallman"), CodeEditor::new("Neovim", 2015, "Vim community"), ]; let theme = Style::modern() .horizontals([(1, HorizontalLine::inherit(Style::modern()))]) .verticals([(1, VerticalLine::inherit(Style::modern()))]) .remove_horizontal() .remove_vertical(); let mut table = Table::new(data); table.with((theme, Alignment::center())); println!("{table}"); } tabled-0.18.0/examples/derive/crate_override.rs000064400000000000000000000021401046102023000176010ustar 00000000000000//! This example demonstrates using the [attribute macro](https://doc.rust-lang.org/reference/procedural-macros.html#attribute-macros) //! [`skip`] to omit specific fields from becoming columns in a [`Table`] display. //! //! Note how [`skip`] annoys [clippy](https://doc.rust-lang.org/clippy/) with `dead_code` //! warnings. This can be addressed with compiler overrides like `#[allow(dead_code)]`. pub mod unknown_crate { pub use ::tabled::{Table, Tabled}; } // make sure we are not using default 'tabled::*' path #[allow(non_camel_case_types, dead_code)] type tabled = usize; #[derive(unknown_crate::Tabled)] #[tabled(crate = "unknown_crate")] struct Country { name: String, city: String, } impl Country { fn new(name: &str, city: &str) -> Self { Self { name: name.to_string(), city: city.to_string(), } } } fn main() { let data = [ Country::new("Afghanistan", "Kabul"), Country::new("Angola", "Luanda"), Country::new("Canada", "Ottawa"), ]; let table = unknown_crate::Table::new(data); println!("{table}"); } tabled-0.18.0/examples/derive/display_type.rs000064400000000000000000000017241046102023000173210ustar 00000000000000use tabled::Tabled; #[derive(Tabled)] #[tabled(display(Option, "display_option", "UNKNOWN"))] pub struct Country { name: String, capital: Option, currency: Option, } fn display_option(opt: &Option, default: &str) -> String where T: ToString, { match opt { Some(val) => val.to_string().to_uppercase(), None => default.to_string(), } } fn main() { let data = vec![ Country { name: String::from("France"), capital: Some(String::from("Paris")), currency: Some(String::from("EUR")), }, Country { name: String::from("Germany"), capital: Some(String::from("Berlin")), currency: None, }, Country { name: String::from("Unknown"), capital: None, currency: Some(String::from("01")), }, ]; let table = tabled::Table::new(data); println!("{}", table); } tabled-0.18.0/examples/derive/display_with.rs000064400000000000000000000035501046102023000173120ustar 00000000000000//! This example demonstrates using the [attribute macro](https://doc.rust-lang.org/reference/procedural-macros.html#attribute-macros) //! [`display_with`] to seamlessly augment field representations in a [`Table`] display. //! //! * [`display_with`] functions act as transformers during [`Table`] instantiation. //! //! * Note how [`display_with`] works with [std] and custom functions alike. //! //! * [`display_with`] attributes can be constructed in two ways (shown below). //! //! * Attribute arguments can be directly overridden with static values, effectively ignoring the //! augmented fields natural value entirely. Even an entire object can be passed as context with `self`. use tabled::{Table, Tabled}; #[derive(Tabled)] struct Country { name: String, #[tabled(display = "str::to_uppercase")] capital: String, #[tabled(display("perimeter", self, false))] area_km2: f32, #[tabled(display("tabled::derive::display::option", "unknown"))] currency: Option, } fn perimeter<'a>(area: &f32, country: &Country, _milies: bool) -> String { let is_big = *area > 1_000_000.0f32; let big_sign = if is_big { "B" } else { "" }; format!("{} {}", country.area_km2, big_sign) } fn main() { let data = [ Country { name: String::from("Afghanistan"), capital: String::from("Kabul"), area_km2: 652867.0, currency: Some(String::from("Afghan afghani (AFN)")), }, Country { name: String::from("Angola"), capital: String::from("Luanda"), area_km2: 1246700.0, currency: None, }, Country { name: String::from("Canada"), capital: String::from("Ottawa"), area_km2: 9984670.0, currency: None, }, ]; let table = Table::new(data); println!("{table}"); } tabled-0.18.0/examples/derive/format.rs000064400000000000000000000022571046102023000161050ustar 00000000000000//! This example demonstrates using the [attribute macro](https://doc.rust-lang.org/reference/procedural-macros.html#attribute-macros) //! [`format`] to beatifuly castomize the resulting values, be used for table contraction. use tabled::{settings::Style, Table, Tabled}; #[derive(Tabled)] struct Phone { #[tabled(format = "code {}")] code: String, #[tabled(rename = "")] #[tabled(format("{}/{}", self.alias.join(","), self.number))] number: String, #[tabled(skip)] alias: Vec, } impl Phone { fn new(code: &str, number: &str, alias: &[&str]) -> Self { let alias = alias.iter().map(ToString::to_string).collect(); Self { code: code.to_string(), number: number.to_string(), alias, } } } fn main() { let data = [ Phone::new("AFN", "11111111", &["Mate"]), Phone::new("CAD", "22222222", &["Sara", "Football", "meetup"]), Phone::new("RUS", "33333333", &["Cris", "meetup"]), Phone::new("BLR", "44444444", &["Ham", "meetup"]), ]; let mut table = Table::new(data); table.with(Style::modern_rounded().remove_horizontal()); println!("{table}"); } tabled-0.18.0/examples/derive/format_enum.rs000064400000000000000000000013211046102023000171200ustar 00000000000000//! This example demonstrates using the [attribute macro](https://doc.rust-lang.org/reference/procedural-macros.html#attribute-macros) //! [`format`] to beatifuly castomize the resulting values, be used for table contraction. use tabled::{Table, Tabled}; #[derive(Tabled)] enum Vehicle { #[tabled(inline)] Car(#[tabled(format = "car->{}", rename = "cars")] String), #[tabled(inline)] Boat(#[tabled(format = "boat->{}", rename = "boats")] String), } fn main() { let data = [ Vehicle::Car("bmw".into()), Vehicle::Car("audi".into()), Vehicle::Car("volkswagen".into()), Vehicle::Boat("ford".into()), ]; let table = Table::new(data); println!("{table}"); } tabled-0.18.0/examples/derive/inline.rs000064400000000000000000000024471046102023000160740ustar 00000000000000//! This example demonstrates using the [attribute macro](https://doc.rust-lang.org/reference/procedural-macros.html#attribute-macros) //! [`inline`] to expand struct fields to individual columns in a [`Table`] display. //! //! * Note that without inlining a struct or enum field, those objects //! must implement the [`Display`] trait as they will be represented in //! a single column with the value of their [`ToString`] output. use tabled::{Table, Tabled}; #[derive(Tabled)] struct Country { name: String, #[tabled(inline)] currency: Currency, area_km2: f32, } #[derive(Tabled)] struct Currency { currency: String, currency_short: String, } impl Country { fn new(name: &str, currency: &str, currency_short: &str, area_km2: f32) -> Self { Self { name: name.to_string(), area_km2, currency: Currency { currency: currency.to_string(), currency_short: currency_short.to_string(), }, } } } fn main() { let data = [ Country::new("Afghanistan", "Afghani", "AFN", 652867.0), Country::new("Angola", "Kwanza", "AOA", 1246700.0), Country::new("Canada", "Canadian Dollar", "CAD", 9984670.0), ]; let table = Table::new(data); println!("{table}"); } tabled-0.18.0/examples/derive/inline_enum.rs000064400000000000000000000025231046102023000171130ustar 00000000000000//! This example demonstrates using the [attribute macro](https://doc.rust-lang.org/reference/procedural-macros.html#attribute-macros) //! [`inline`] to expand enum fields to individual columns in a [`Table`] display. //! //! * Note how the optional [`inline`] argument is used to apply prefixes //! to decomposed column headers. This is helpful for organizing tables //! with repetitive fields that would normally result in confusing headers. //! //! * Note that without inlining a struct or enum field, those objects //! must implement the [`Display`] trait as they will be represented in //! a single column with the value of their [`ToString`] output. use tabled::{Table, Tabled}; #[derive(Tabled)] enum Contact { #[tabled(inline)] Telegram { #[tabled(inline("tg::"))] num: Number, }, #[tabled(inline)] Local(#[tabled(inline("local::"))] Number), } #[derive(Tabled)] struct Number { number: String, code: usize, } impl Number { fn new(number: &str, code: usize) -> Self { Self { number: number.to_string(), code, } } } fn main() { let data = [ Contact::Local(Number::new("654321", 123)), Contact::Telegram { num: Number::new("123456", 123), }, ]; let table = Table::new(data); println!("{table}"); } tabled-0.18.0/examples/derive/order.rs000064400000000000000000000017051046102023000157250ustar 00000000000000//! This example demonstrates using the [attribute macro](https://doc.rust-lang.org/reference/procedural-macros.html#attribute-macros) //! [`order`] to relocate fields to specified indexes in a [`Table`] display. //! //! * By default, [`Table`] columns are shown in the same ordered they are //! defined in the deriving struct/enum definition. use tabled::{Table, Tabled}; #[derive(Tabled)] struct Country { name: String, capital: String, #[tabled(order = 0)] area_km2: f32, } impl Country { fn new(name: &str, city: &str, area_km2: f32) -> Self { Self { name: name.to_string(), capital: city.to_string(), area_km2, } } } fn main() { let data = [ Country::new("Afghanistan", "Kabul", 652867.0), Country::new("Angola", "Luanda", 1246700.0), Country::new("Canada", "Ottawa", 9984670.0), ]; let table = Table::new(data); println!("{table}"); } tabled-0.18.0/examples/derive/rename.rs000064400000000000000000000015001046102023000160520ustar 00000000000000//! This example demonstrates using the [attribute macro](https://doc.rust-lang.org/reference/procedural-macros.html#attribute-macros) //! [`rename`] to alias specific fields in a [`Table`] display. use tabled::{Table, Tabled}; #[derive(Tabled)] struct Country { name: String, capital: String, #[tabled(rename = "area")] area_km2: f32, } impl Country { fn new(name: &str, capital: &str, area: f32) -> Self { Self { name: name.to_string(), capital: capital.to_string(), area_km2: area, } } } fn main() { let data = [ Country::new("Afghanistan", "Kabul", 652867.0), Country::new("Angola", "Luanda", 1246700.0), Country::new("Canada", "Ottawa", 9984670.0), ]; let table = Table::new(data); println!("{table}"); } tabled-0.18.0/examples/derive/rename_all.rs000064400000000000000000000021541046102023000167100ustar 00000000000000//! This example demonstrates using the [attribute macro](https://doc.rust-lang.org/reference/procedural-macros.html#attribute-macros) //! [`rename_all`] to apply table-wide header formatting in a [`Table`] display. //! //! * Supported formatting rules include: //! * 'camelCase' //! * 'kabab-case' //! * 'PascalCase' //! * 'SCREAMING_SNAKE_CASE' //! * 'snake_case' //! * 'lowercase' //! * 'UPPERCASE' //! * 'verbatim' use tabled::{Table, Tabled}; #[derive(Tabled)] #[tabled(rename_all = "camelCase")] struct Country { name: String, #[tabled(rename_all = "kebab-case")] capital_city: String, area_km2: f32, } impl Country { fn new(name: &str, city: &str, area_km2: f32) -> Self { Self { name: name.to_string(), capital_city: city.to_string(), area_km2, } } } fn main() { let data = [ Country::new("Afghanistan", "Kabul", 652867.0), Country::new("Angola", "Luanda", 1246700.0), Country::new("Canada", "Ottawa", 9984670.0), ]; let table = Table::new(data); println!("{table}"); } tabled-0.18.0/examples/derive/skip.rs000064400000000000000000000020501046102023000155520ustar 00000000000000//! This example demonstrates using the [attribute macro](https://doc.rust-lang.org/reference/procedural-macros.html#attribute-macros) //! [`skip`] to omit specific fields from becoming columns in a [`Table`] display. //! //! Note how [`skip`] annoys [clippy](https://doc.rust-lang.org/clippy/) with `dead_code` //! warnings. This can be addressed with compiler overrides like `#[allow(dead_code)]`. #![allow(dead_code)] use tabled::{Table, Tabled}; #[derive(Tabled)] struct Country { name: String, #[tabled(skip)] capital: String, #[tabled(skip)] area_km2: f32, } impl Country { fn new(name: &str, capital: &str, area: f32) -> Self { Self { name: name.to_string(), capital: capital.to_string(), area_km2: area, } } } fn main() { let data = [ Country::new("Afghanistan", "Kabul", 652867.0), Country::new("Angola", "Luanda", 1246700.0), Country::new("Canada", "Ottawa", 9984670.0), ]; let table = Table::new(data); println!("{table}"); } tabled-0.18.0/examples/disable.rs000064400000000000000000000021031046102023000147300ustar 00000000000000//! This example demonstrates using the [`Remove`] to remove specific //! cell data from a [`Table`] display. //! //! * ⚠️ Using [`Remove`] in combination with other [`Style`] customizations may yield unexpected results. //! It is safest to use [`Remove`] last in a chain of alterations. use tabled::{ settings::{location::ByColumnName, Remove}, Table, Tabled, }; #[derive(Tabled)] struct Distribution { name: String, based_on: String, is_active: bool, is_cool: bool, } impl Distribution { fn new(name: &str, based_on: &str, is_active: bool, is_cool: bool) -> Self { Self { name: name.to_string(), based_on: based_on.to_string(), is_active, is_cool, } } } fn main() { let data = [ Distribution::new("Debian", "", true, true), Distribution::new("Arch", "", true, true), Distribution::new("Manjaro", "Arch", true, true), ]; let mut table = Table::new(data); table.with(Remove::column(ByColumnName::new("is_active"))); println!("{table}"); } tabled-0.18.0/examples/extended_display.rs000064400000000000000000000015251046102023000166610ustar 00000000000000//! This example demonstrates using [ExtendedTable], a [Table] alternative with //! limited flexibility but a greater emphasis on large data displays. use tabled::{tables::ExtendedTable, Tabled}; #[derive(Tabled)] struct Distribution { name: String, based_on: String, is_active: bool, is_cool: bool, } impl Distribution { fn new(name: &str, based_on: &str, is_active: bool, is_cool: bool) -> Self { Self { name: name.to_string(), based_on: based_on.to_string(), is_active, is_cool, } } } fn main() { let data = vec![ Distribution::new("Manjaro", "Arch", true, true), Distribution::new("Arch", "", true, true), Distribution::new("Debian", "", true, true), ]; let table = ExtendedTable::new(data); println!("{table}"); } tabled-0.18.0/examples/extract.rs000064400000000000000000000034041046102023000150040ustar 00000000000000//! This example demonstrates using the [`Extract`] [`TableOption`] to //! produce a subsection of a [`Table`]. //! //! * [`Extract`] can return a new [`Table`] with three functions: //! * `rows()` | yields subset of the initial rows //! * `columns()` | yields subset of the initial columns //! * `segment()` | yields subsection of the initial table //! //! * Note how [`Extract`] methods accepts [`RangeBounds`] arguments, //! making subset specifications concise. use tabled::{ settings::{object::Rows, Alignment, Extract, Style}, Table, Tabled, }; #[derive(Tabled)] struct Album { artist: String, name: String, released: String, #[tabled(format = "{:?}")] greatness: Greatness, } impl Album { fn new(artist: &str, name: &str, released: &str, greatness: Greatness) -> Self { Self { name: name.to_string(), artist: artist.to_string(), released: released.to_string(), greatness, } } } #[derive(Debug)] enum Greatness { Supreme, Outstanding, Unparalleled, } fn main() { use Greatness::*; #[rustfmt::skip] let data = [ Album::new("Pink Floyd", "The Dark Side of the Moon", "01 March 1973", Unparalleled), Album::new("Fleetwood Mac", "Rumours", "04 February 1977", Outstanding), Album::new("Led Zeppelin", "Led Zeppelin IV", "08 November 1971", Supreme), ]; println!("Full table"); let mut table = Table::new(data); table .with(Style::modern()) .modify(Rows::first(), Alignment::center()) .modify(Rows::new(1..), Alignment::left()); println!("{table}"); println!("Segment row: (1..=2) column: (1..)"); table.with(Extract::segment(1..=2, 1..)); println!("{table}"); } tabled-0.18.0/examples/format.rs000064400000000000000000000026261046102023000146270ustar 00000000000000//! This example demonstrates using the [`Format`] [`CellOption`] factory to alter //! the cells of a [`Table`]. //! //! * Note how [`Format::content()`] gives access to the respective cell content for replacement. //! And [`Format::positioned()`] additionally provides the index coordinates of that cell. //! //! * Note how the [std] [`format!`] macro is used to update the values of the affected cells. use tabled::{ settings::{ object::{Columns, Object, Rows}, Format, Style, }, Table, Tabled, }; #[derive(Tabled)] struct Commit { id: &'static str, header: &'static str, message: &'static str, } fn main() { let data = [ Commit { header: "bypass open-source transmitter", message: "index neural panel", id: "8ae4e8957caeaa467acbce963701e227af00a1c7", }, Commit { header: "program online alarm", message: "copy bluetooth card", id: "48c76de71bd685486d97dc8f4f05aa6fcc0c3f86", }, Commit { header: "CSV", message: "reboot mobile capacitor", id: "6ffc2a2796229fc7bf59471ad907f58b897005d0", }, ]; let mut table = Table::new(data); table.with(Style::psql()); table.modify( Columns::first().not(Rows::first()), Format::content(|s| s.chars().take(5).collect()), ); println!("{table}"); } tabled-0.18.0/examples/formatting_settings.rs000064400000000000000000000020441046102023000174230ustar 00000000000000//! This example demonstrates using the [`Alignment`], [`AlignmentStrategy`], and [`TrimStrategy`] [`CellOptions`] //! to align the content of a [`Table`] in several nuanced ways. //! //! * Note how [`AlignmentStrategy`] and [`TrimStrategy`] provide useful tools for managing multiline cells and //! cell values that are bloated with whitespace. use tabled::{ settings::{ formatting::{AlignmentStrategy, TrimStrategy}, Alignment, Style, }, Table, }; fn main() { let some_json = r#" [ "foo", { "bar": 1, "baz": [ 2, 3 ] } ]"#; let mut table = Table::new([some_json]); table.with(Style::rounded()).with(Alignment::center()); println!("A default Alignment settings\n{table}"); table.with(AlignmentStrategy::PerLine); println!("Per line Alignment strategy\n{table}"); table .with(AlignmentStrategy::PerCell) .with(TrimStrategy::Both); println!("A default Alignment; allowing vertical and horizontal trim\n{table}"); } tabled-0.18.0/examples/grid_colors.rs000064400000000000000000000021621046102023000156400ustar 00000000000000//! This example demonstrates using [`Color`] as a [`CellOption`] modifier to stylize //! the cells of a [`Table`]. //! //! * Note how the [`Color`] [setting](tabled::settings) is used to simplify creating //! reusable themes for backgrounds. use tabled::{ settings::{Color, Style}, Table, Tabled, }; #[derive(Tabled)] struct Bsd { distribution: String, year_of_first_release: usize, is_active: bool, } impl Bsd { fn new(distribution: &str, year_of_first_release: usize, is_active: bool) -> Self { Self { distribution: distribution.to_string(), year_of_first_release, is_active, } } } fn main() { let data = vec![ Bsd::new("BSD", 1978, false), Bsd::new("SunOS", 1982, false), Bsd::new("NetBSD", 1993, true), Bsd::new("FreeBSD", 1993, true), Bsd::new("OpenBSD", 1995, true), ]; let mut table = Table::new(data); table .with(Style::psql()) .modify((0, 0), Color::BG_BLUE) .modify((1, 1), Color::BG_GREEN) .modify((2, 2), Color::BG_RED); println!("{table}"); } tabled-0.18.0/examples/height.rs000064400000000000000000000030421046102023000146000ustar 00000000000000//! This example demonstrates using the [`Height`] [`TableOption`] for adjusting //! the height of a [`Table`]. //! //! * [`Height`] supports three key features: //! * [`CellHeightIncrease`] spreads new whitespace between the [`Table`] //! rows up to the specified line count. //! * [`CellHeightLimit`] removes lines from the [`Table`] rows fairly, until //! it has no choice but to remove single-line-rows entirely, bottom up. //! * [`HeightList`] accepts an array of height specifications that are applied //! to the rows with the same index. This is helpful for granularly specifying individual //! row heights irrespective of [`Padding`] or [`Margin`]. use tabled::{ settings::{peaker::Priority, Height, Style}, Table, }; fn main() { let data = vec![("Multi\nline\nstring", 123), ("Single line", 234)]; let mut table = Table::builder(data).build(); table.with(Style::markdown()); println!("Table\n"); println!("{table}"); println!(); let table_ = table.clone().with(Height::increase(10)).to_string(); println!("Table increase height to 10\n"); println!("{table_}"); println!(); let table_ = table .clone() .with(Height::limit(4).priority(Priority::max(true))) .to_string(); println!("Table decrease height to 4\n"); println!("{table_}"); let table_ = table .clone() .with(Height::limit(0).priority(Priority::max(true))) .to_string(); println!("Table decrease height to 0\n"); println!("{table_}"); } tabled-0.18.0/examples/highlight.rs000064400000000000000000000014121046102023000152760ustar 00000000000000//! This example demonstrates using the [`Highlight`] [`TableOption`] to //! decorate sections of a [`Table`] with a unique [`Border`]. //! //! * Note how [`Highlight`] arguments can be chained together to //! create cross-sections and non-symmetrical shapes. use tabled::{ settings::{ object::{Columns, Object, Rows}, style::Style, Highlight, }, Table, }; fn main() { let data = vec![["A", "B", "C"], ["D", "E", "F"], ["G", "H", "I"]]; let target = Columns::first() .not(Rows::last()) .and(Rows::last() - 1) .and(Rows::last().intersect(Columns::last())); let mut table = Table::new(data); table.with(Style::modern()); table.with(Highlight::outline(target, '*')); println!("{table}"); } tabled-0.18.0/examples/highlight_color.rs000064400000000000000000000013351046102023000165000ustar 00000000000000//! This example demonstrates using [`Highlight`] in combination with [`BorderColor`] to //! frame sections of a [`Table`] with a unique background [`Color`]. //! //! * Note how [`Highlight::colored()`] is used to accept the necessary input instead of [`Highlight::new()`]. use tabled::{ settings::{ object::{Columns, Object, Rows}, Color, Highlight, Style, }, Table, }; fn main() { let data = vec![["A", "B", "C"], ["D", "E", "F"], ["G", "H", "I"]]; let target = Rows::first().and(Columns::single(1)); let color = Color::BG_BRIGHT_BLACK; let mut table = Table::new(data); table.with(Style::modern()); table.with(Highlight::colored(target, color)); println!("{table}"); } tabled-0.18.0/examples/hyperlink.rs000064400000000000000000000051771046102023000153500ustar 00000000000000//! This example demonstrates how hyperlinks can be embedded into a [`Table`] display. //! //! While not a [`tabled`] specific implementation, it is helpful to know that //! most users expect certain elements of interactivity based on the purpose of your display. //! //! * 🚩 This example requires the `color` feature. //! //! * ⚠️ Terminal interfaces may differ in how they parse links or make them interactive. //! [`tabled`] doesn't have the final say on whether a link is clickable or not. use tabled::{ settings::{object::Segment, Alignment, Style, Width}, Table, Tabled, }; fn main() { let multicolored_debian = "\x1b[30mDebian\x1b[0m\ \x1b[31m Debian\x1b[0m\ \x1b[32m Debian\x1b[0m\ \x1b[33m Debian\x1b[0m\ \x1b[34m Debian\x1b[0m\ \x1b[35m Debian\x1b[0m\ \x1b[36m Debian\x1b[0m\ \x1b[37m Debian\x1b[0m\ \x1b[40m Debian\x1b[0m\ \x1b[41m Debian\x1b[0m\ \x1b[42m Debian\x1b[0m\ \x1b[43m Debian\x1b[0m\ \x1b[44m Debian\x1b[0m"; let debian_repeat = "DebianDebianDebianDebianDebianDebianDebianDebianDebianDebianDebianDebianDebianDebian" .to_string(); let debian_colored_link = format_osc8_hyperlink("https://www.debian.org/", multicolored_debian); let debian_link = format_osc8_hyperlink("https://www.debian.org/", "Debian"); let wiki_link = format_osc8_hyperlink("https://www.wikipedia.org/", "Debian"); let data = [ Distribution::new("Debian".into(), false), Distribution::new(debian_link.clone(), true), Distribution::new(format!("{debian_link} a link followed by text"), true), Distribution::new( format!("{debian_link} links with intervening text {wiki_link}"), true, ), Distribution::new(format!("a link surrounded {debian_link} by text"), true), Distribution::new(debian_colored_link, true), Distribution::new(debian_repeat, false), ]; let mut table = Table::new(&data); table .with(Style::ascii_rounded()) .with(Alignment::left()) .modify(Segment::all(), Width::wrap(16).keep_words(true)); println!("{table}"); let mut table = Table::new(&data); table .with(Style::ascii_rounded()) .with(Alignment::left()) .modify(Segment::all(), Width::wrap(16)); println!("{table}"); } #[derive(Tabled)] struct Distribution { name: String, is_hyperlink: bool, } impl Distribution { fn new(name: String, is_hyperlink: bool) -> Self { Self { name, is_hyperlink } } } fn format_osc8_hyperlink(url: &str, text: &str) -> String { format!("\x1b]8;;{url}\x1b\\{text}\x1b]8;;\x1b\\",) } tabled-0.18.0/examples/interactive.rs000064400000000000000000000070051046102023000156500ustar 00000000000000use std::{thread::sleep, time::Duration}; use tabled::{ settings::{ object::{ObjectIterator, Rows}, style::BorderColor, themes::Colorization, Color, Style, }, Table, }; use tabled_derive::Tabled; #[derive(Tabled, Clone, Debug)] struct Item { name: String, #[tabled(format("{}", self.category.join(",")))] category: Vec, value: f64, } impl Item { fn new(name: &str, category: &[&str], value: f64) -> Self { Self { name: name.to_owned(), category: category.iter().map(ToString::to_string).collect(), value, } } } fn main() { let mut p = Pager::default(); p.append(|t| { t.with(Style::blank()); }); p.append(|t| { t.with(Colorization::rows([ Color::rgb_bg(0, 0, 0) | Color::rgb_fg(255, 255, 255), Color::rgb_bg(255, 255, 255) | Color::rgb_fg(0, 0, 0), ])); }); p.append(|t| { t.with(Colorization::exact([Color::UNDERLINE], Rows::first())); }); p.append(|t| { t.modify( Rows::new(1..).step_by(2), BorderColor::new().left(Color::rgb_bg(255, 255, 255)), ) .modify( Rows::new(2..).step_by(2), BorderColor::new().left(Color::rgb_bg(0, 0, 0)), ); }); p.append(|t| { t.with(Colorization::exact( [ Color::rgb_bg(0, 0, 0) | Color::rgb_fg(255, 255, 255), Color::rgb_bg(255, 255, 255) | Color::rgb_fg(0, 0, 0), ], Rows::new(1..), )) .modify( Rows::new(1..).step_by(2), BorderColor::new().left(Color::rgb_bg(0, 0, 0)), ) .modify( Rows::new(2..).step_by(2), BorderColor::new().left(Color::rgb_bg(255, 255, 255)), ); }); p.append(|t| { t.with(Colorization::exact( [ Color::rgb_bg(128, 128, 255) | Color::rgb_fg(0, 0, 0), Color::rgb_bg(200, 100, 150) | Color::rgb_fg(0, 0, 0), ], Rows::new(1..), )) .modify( Rows::new(1..).step_by(2), BorderColor::new().left(Color::rgb_bg(128, 128, 255)), ) .modify( Rows::new(2..).step_by(2), BorderColor::new().left(Color::rgb_bg(200, 100, 150)), ); }); let data = [ Item::new("Light Bulb", &["Household"], 3.67), Item::new("Toothbrush", &["Household", "Bathroom"], 3.67), Item::new("Tire", &["Vehicle"], 299.0), ]; let table = Table::new(data); p.render(table); } type Step = Box u64>; #[derive(Default)] struct Pager { pages: Vec, } impl Pager { fn append(&mut self, f: F) where F: Fn(&mut Table) + 'static, { self.append_timed(f, 400); } fn append_timed(&mut self, f: F, time_ms: u64) where F: Fn(&mut Table) + 'static, { self.pages.push(step(f, time_ms)); } fn render(&self, mut table: Table) { run_steps(&mut table, &self.pages) } } fn step(f: F, delay_ms: u64) -> Step where F: Fn(&mut Table) + 'static, { Box::new(move |t| { (f)(t); delay_ms }) } fn run_steps(table: &mut Table, steps: &[Step]) { const CLEAR: &str = "\u{1b}[2J"; let mut t: u64; for step in steps { println!("{}", CLEAR); t = (step)(table); println!("{}", table); sleep(Duration::from_millis(t)); } } tabled-0.18.0/examples/iter_table.rs000064400000000000000000000020641046102023000154450ustar 00000000000000//! This example demonstrates using [`IterTable`], an [allocation](https://doc.rust-lang.org/nomicon/vec/vec-alloc.html) //! free [`Table`] alternative that translates an iterator into a display. //! //! * Note how [`IterTable`] supports the familiar `.with()` syntax for applying display //! modifications. //! //! * [`IterTable`] supports manual configuration of: //! * Record sniffing (default 1000 rows) //! * Row cutoff //! * Row height //! * Column cutoff //! * Column width use std::{ fs::File, io::{stdout, BufRead, BufReader}, }; use tabled::{settings::Style, tables::IterTable}; fn main() { let path = file!(); let file = File::open(path).unwrap(); let reader = BufReader::new(file); let iterator = reader.lines().enumerate().map(|(i, line)| match line { Ok(line) => [i.to_string(), "ok".into(), line], Err(err) => [i.to_string(), "error".into(), err.to_string()], }); let table = IterTable::new(iterator).with(Style::ascii_rounded()); table.build(stdout()).unwrap(); println!() } tabled-0.18.0/examples/margin.rs000064400000000000000000000010741046102023000146100ustar 00000000000000//! This example demonstrates using the [`Margin`] [`TableOption`] to buffer space //! around a [`Table`] display. //! //! * Note how the [`Margin::fill()`] function allows for overriding the default whitespace //! with any [`char`]. use tabled::{ settings::{Margin, Style}, Table, }; fn main() { let data = vec![["A", "B", "C"], ["D", "E", "F"], ["G", "H", "I"]]; let table = Table::new(data) .with(Style::re_structured_text()) .with(Margin::new(4, 3, 2, 1).fill('<', '>', 'v', '^')) .to_string(); println!("{table}"); } tabled-0.18.0/examples/matrix.rs000064400000000000000000000014061046102023000146360ustar 00000000000000//! This example demonstrates how [`tabled`] is an excellent tool for creating //! dataset visualizations. //! //! * 🚀 When native display solutions, such as the [`Debug`] trait and [pretty printing](https://doc.rust-lang.org/std/fmt/#sign0) //! options, aren't enough, [`tabled`] is a great choice for improving the quality of your displays. use tabled::{settings::Style, Table}; fn matrix() -> [[usize; N]; N] { let mut matrix = [[0; N]; N]; #[allow(clippy::needless_range_loop)] for i in 0..N { for j in 0..N { matrix[i][j] = (i + 1) * (j + 1); } } matrix } fn main() { let data = matrix::<10>(); let table = Table::new(data).with(Style::modern()).to_string(); println!("{table}"); } tabled-0.18.0/examples/merge_duplicates_horizontal.rs000064400000000000000000000036421046102023000211230ustar 00000000000000//! This example demonstrates using the [`Merge`] [`TableOption`] to clarify //! redundancies in a [`Table`] display. //! //! * Note how a custom theme is applied to give the [`Merged`](Merge) cells //! a unique look. //! //! * Merge supports both [`Merge::vertical()`] and [`Merge::horizontal()`]. use tabled::{ settings::{ style::{HorizontalLine, Style, VerticalLine}, Border, Merge, }, Table, Tabled, }; fn main() { let data = [ Database::new("database_1", "database_1", "table_1", 10712), Database::new("database_1", "database_1", "table_2", 57), Database::new("database_1", "database_1", "table_3", 57), Database::new("database_2", "", "table_1", 72), Database::new("database_2", "", "table_2", 75), Database::new("database_3", "database_3", "table_1", 20), Database::new("database_3", "", "table_2", 21339), Database::new("database_3", "", "table_3", 141723), ]; let mut table = Table::builder(data).index().transpose().build(); config_theme(&mut table); table.with(Merge::horizontal()); println!("{table}"); } #[derive(Tabled)] struct Database { #[tabled(rename = "db")] db_name: String, origin_db: String, #[tabled(rename = "table")] table_name: String, total: usize, } impl Database { fn new(db_name: &str, origin_db: &str, table_name: &str, total: usize) -> Self { Self { db_name: db_name.to_string(), origin_db: origin_db.to_string(), table_name: table_name.to_string(), total, } } } fn config_theme(table: &mut Table) { let style = Style::modern() .frame(Border::inherit(Style::rounded())) .horizontals([(1, HorizontalLine::inherit(Style::modern()))]) .verticals([(1, VerticalLine::inherit(Style::modern()))]) .remove_horizontal() .remove_vertical(); table.with(style); } tabled-0.18.0/examples/merge_duplicates_vertical.rs000064400000000000000000000032141046102023000205360ustar 00000000000000//! This example demonstrates using the [`Merge`] [`TableOption`] to clarify //! redundancies in a [`Table`] display. //! //! * Note how repetitive entries must be consecutive, in their specified direction, //! to be merged together. //! //! * Note how [`BorderSpanCorrection`] is used to resolve display issues incurred //! from [`Span`] decisions made through duplicate detection. //! //! * Merge supports both [`Merge::vertical()`] and [`Merge::horizontal()`]. use tabled::{ settings::{ style::{BorderSpanCorrection, Style}, Merge, }, Table, Tabled, }; fn main() { let data = [ DatabaseTable::new("database_1", "table_1", 10712), DatabaseTable::new("database_1", "table_2", 57), DatabaseTable::new("database_1", "table_3", 57), DatabaseTable::new("database_2", "table_1", 72), DatabaseTable::new("database_2", "table_2", 75), DatabaseTable::new("database_3", "table_1", 20), DatabaseTable::new("database_3", "table_2", 21339), DatabaseTable::new("database_3", "table_3", 141723), ]; let table = Table::new(data) .with(Merge::vertical()) .with(Style::modern()) .with(BorderSpanCorrection) .to_string(); println!("{table}"); } #[derive(Tabled)] struct DatabaseTable { #[tabled(rename = "db")] db_name: String, #[tabled(rename = "table")] table_name: String, total: usize, } impl DatabaseTable { fn new(db_name: &str, table_name: &str, total: usize) -> Self { Self { db_name: db_name.to_string(), table_name: table_name.to_string(), total, } } } tabled-0.18.0/examples/nested_table.rs000064400000000000000000000067101046102023000157660ustar 00000000000000//! This example demonstrates how [`Tables`](Table) can be comprised of other tables. //! //! * This first nested [`Table`] example showcases the [`Builder`] approach. //! //! * Note how a great deal of manual customizations have been applied to create a //! highly unique display. //! //! * 🎉 Inspired by https://github.com/p-ranav/tabulate#nested-tables/ use std::iter::FromIterator; use tabled::{ builder::Builder, settings::{ object::{Rows, Segment}, style::{HorizontalLine, Style}, Alignment, Modify, Padding, Width, }, Table, }; fn main() { let animal = create_class( "Animal", &[("age", "Int", ""), ("gender", "String", "")], &["isMammal", "mate"], ); let duck = create_class( "Duck", &[("beakColor", "String", "yellow")], &["swim", "quack"], ); let data = [ [animal.to_string()], [String::from("▲")], [String::from("|")], [String::from("|")], [duck.to_string()], ]; let mut table = Builder::from_iter(data).build(); table .with(Style::ascii().remove_horizontal()) .with(Padding::new(5, 5, 0, 0)) .with(Alignment::center()); println!("{table}"); } fn create_class(name: &str, fields: &[(&str, &str, &str)], methods: &[&str]) -> Table { let fields = fields .iter() .map(|(field, t, d)| [format_field(field, t, d)]); let mut table_fields = Builder::from_iter(fields).build(); table_fields.with(Style::ascii().remove_horizontal().remove_vertical()); let methods = methods.iter().map(|method| [format_method(method)]); let mut table_methods = Builder::from_iter(methods).build(); table_methods.with(Style::ascii().remove_horizontal().remove_vertical()); let (table_fields, table_methods) = make_equal_width(table_fields, table_methods); let data = [ [name.to_string()], [table_fields.to_string()], [table_methods.to_string()], ]; let mut table = Builder::from_iter(data).build(); let style = Style::ascii() .horizontals([(1, HorizontalLine::inherit(Style::ascii()))]) .remove_horizontal() .remove_vertical(); table .with(style) .with(Modify::new(Segment::all()).with(Alignment::left())) .with(Modify::new(Rows::first()).with(Alignment::center())); table } fn format_field(field: &&str, field_type: &&str, default_value: &&str) -> String { if default_value.is_empty() { format!("+{field}: {field_type}") } else { format!("+{field}: {field_type} = {default_value:?}") } } fn format_method(method: &str) -> String { format!("+{method}()") } fn make_equal_width(mut table1: Table, mut table2: Table) -> (Table, Table) { // We want to make a fields table and methods table to have the same width. // To not set it to constant, we check a width of each of them and correct the other. // // it's safe to do .len() because we use ascii theme and no colors. let table1_width = table1.to_string().lines().next().unwrap().len(); let table2_width = table2.to_string().lines().next().unwrap().len(); match table1_width.cmp(&table2_width) { std::cmp::Ordering::Less => { table1.with(Width::increase(table2_width)); } std::cmp::Ordering::Greater => { table2.with(Width::increase(table1_width)); } std::cmp::Ordering::Equal => (), } (table1, table2) } tabled-0.18.0/examples/nested_table_2.rs000064400000000000000000000032251046102023000162050ustar 00000000000000//! This example demonstrates a minimalist implementation of [`Tabling`](Table) records //! with struct fields. //! //! * This second nested [`Table`] example showcases the [`derive`] approach. //! //! * Note how the [`display_with`] attribute macro applies the custom `display_distribution` //! filter function, which, in this case, applies styles to the final display. use tabled::{settings::Style, Table, Tabled}; fn main() { #[rustfmt::skip] let data = [ Vendor::new("Azure", Dist::new("Windows", None), Dist::new("Manjaro", Some("Arch"))), Vendor::new("AWS", Dist::new("Debian", None), Dist::new("Arch", None)), Vendor::new("GCP", Dist::new("Debian", None), Dist::new("Arch", None)), ]; let mut table = Table::new(data); table.with(Style::modern()); println!("{table}"); } #[derive(Tabled)] struct Vendor { name: String, #[tabled(display = "display_distribution")] main_os: Dist, #[tabled(display = "display_distribution")] switch_os: Dist, } impl Vendor { fn new(name: &str, main_os: Dist, switch_os: Dist) -> Self { Self { name: name.to_string(), main_os, switch_os, } } } fn display_distribution(d: &Dist) -> String { Table::new([d]).with(Style::extended()).to_string() } #[derive(Tabled)] struct Dist { name: String, #[tabled(format("{}", self.based_on.as_deref().unwrap_or_else(|| "Independent")))] based_on: Option, } impl Dist { fn new(name: &str, based_on: Option<&str>) -> Self { Self { name: name.to_string(), based_on: based_on.map(|s| s.to_string()), } } } tabled-0.18.0/examples/nested_table_3.rs000064400000000000000000000032361046102023000162100ustar 00000000000000//! This example demonstrates creating a nested [`Table`] by instantiating a new //! [`Table`] from a collection of other [`Tables`](Table). //! //! * This third nested [`Table`] example showcases the [`Table::new()`] approach. use tabled::{ settings::{ object::Rows, style::{BorderSpanCorrection, Style}, Alignment, Extract, Highlight, Padding, Panel, }, Table, Tabled, }; #[derive(Tabled)] struct Contribution { author: &'static str, profile: &'static str, } impl Contribution { fn new(author: &'static str, profile: &'static str) -> Self { Self { author, profile } } } fn main() { let committers = [ Contribution::new("kozmod", "https:/github.com/kozmod"), Contribution::new("IsaacCloos", "https:/github.com/IsaacCloos"), ]; let issuers = [Contribution::new( "aharpervc", "https:/github.com/aharpervc", )]; let committers_table = Table::new(committers) .with(Panel::header("Contributors")) .with(Alignment::center()) .with(BorderSpanCorrection) .to_string(); let issues_table = Table::new(issuers) .with(Panel::header("Issuers")) .with(Alignment::center()) .with(BorderSpanCorrection) .to_string(); let mut welcome_table = Table::new([(committers_table, issues_table)]); welcome_table .with(Extract::rows(1..)) .with(Panel::header("Thank You")) .with(Style::ascii().remove_horizontal()) .modify(Rows::new(1..), Padding::new(1, 1, 1, 0)) .with(Alignment::center()) .with(Highlight::outline(Rows::first(), '*')); println!("{welcome_table}"); } tabled-0.18.0/examples/option.rs000064400000000000000000000024301046102023000146400ustar 00000000000000//! This example demonstrates instantiating a [`Table`] from an [`IntoIterator`] compliant object. //! //! * Note how [`Range`] [expression syntax](https://doc.rust-lang.org/reference/expressions/range-expr.html) //! is used to idiomatically represent the English alphabet. use tabled::{Table, Tabled}; #[derive(Tabled)] struct Repository { name: String, owner: String, #[tabled(inline)] head: Option, #[tabled(inline)] updated_at: Option, } #[derive(Tabled)] struct Commit { at: u64, hash: String, } fn main() { let repos = vec![ Repository { name: String::from("django-sheets"), owner: String::from("georgewhewell"), head: None, updated_at: Some(0), }, Repository { name: String::from("undervolt"), owner: String::from("georgewhewell"), head: None, updated_at: None, }, Repository { name: String::from("msp-elixir"), owner: String::from("georgewhewell"), head: Some(Commit { at: 0, hash: String::from("4342f01"), }), updated_at: None, }, ]; let table = Table::new(repos); println!("{table}"); } tabled-0.18.0/examples/panel.rs000064400000000000000000000033171046102023000144340ustar 00000000000000//! This example demonstrates using the [`Panel`] [`TableOption`] to inject //! table-length columns and rows into a [`Table`]. //! //! * [`Panel`] supports four injection options: //! * Horizontal | manual index selection //! * Vertical | manual index selection //! * Header | before first row //! * Footer | after last row use tabled::{ settings::{ object::Segment, style::{BorderSpanCorrection, Style}, Alignment, Modify, Panel, Width, }, Table, Tabled, }; #[derive(Tabled)] struct Release { version: &'static str, published_date: &'static str, is_active: bool, major_feature: &'static str, } impl Release { const fn new(v: &'static str, p: &'static str, active: bool, feature: &'static str) -> Self { Self { version: v, published_date: p, is_active: active, major_feature: feature, } } } const DATA: [Release; 3] = [ Release::new("0.2.1", "2021-06-23", true, "#[header(inline)] attribute"), Release::new("0.2.0", "2021-06-19", false, "API changes"), Release::new("0.1.4", "2021-06-07", false, "display_with attribute"), ]; fn main() { let mut table = Table::new(DATA); table .with(Panel::header("Tabled Releases")) .with(Panel::footer(format!("N - {}", DATA.len()))) .with(Panel::vertical(0, "Some text goes here")) .with(Panel::vertical(5, "Some text goes here")) .with(Modify::new((0, 0)).with(Width::wrap(1))) .with(Modify::new((0, 5)).with(Width::wrap(1))) .with(Modify::new(Segment::all()).with(Alignment::center())) .with(Style::modern()) .with(BorderSpanCorrection); println!("{table}"); } tabled-0.18.0/examples/pool_table.rs000064400000000000000000000014451046102023000154550ustar 00000000000000//! This example demonstrates a [`PoolTable`] usage. use tabled::{ settings::{style::Style, Alignment}, tables::{PoolTable, TableValue}, }; fn main() { let data = vec![ vec!["Hello World", "Hello World", "Hello World"], vec!["Hello", "", "Hello"], vec!["W", "o", "r", "l", "d"], ]; let data = TableValue::Column( data.into_iter() .map(|row| { TableValue::Row( row.into_iter() .map(|text| TableValue::Cell(text.to_owned())) .collect(), ) }) .collect(), ); let table = PoolTable::from(data) .with(Style::modern()) .with(Alignment::center()) .to_string(); println!("{table}"); } tabled-0.18.0/examples/pool_table2.rs000064400000000000000000000007721046102023000155410ustar 00000000000000//! This example demonstrates a [`PoolTable`] usage. use tabled::{ settings::{Alignment, Style}, tables::PoolTable, }; fn main() { let characters = [ "Naruto Uzumaki", "Kakashi Hatake", "Minato Namikaze", "Jiraiya", "Orochimaru", "Itachi Uchiha", ]; let data = characters.chunks(2); let table = PoolTable::new(data) .with(Style::dots()) .with(Alignment::center()) .to_string(); println!("{table}"); } tabled-0.18.0/examples/reverse.rs000064400000000000000000000016211046102023000150040ustar 00000000000000use std::iter::FromIterator; use tabled::{ settings::{Padding, Reverse, Shadow, Style}, Table, }; fn main() { let data = [ ["string", "support", "wood", "station", "should", "height"], ["shaking", "sweet", "answer", "could", "over", "rough"], ["equator", "save", "pig", "camera", "alone", "office"], ["eight", "act", "image", "attached", "gone", "zero"], ["applied", "sense", "use", "shoe", "born", "care"], ["easier", "shout", "noun", "applied", "rear", "crowd"], ["coal", "flag", "current", "nearby", "expect", "heading"], ]; let mut table = Table::from_iter(data); table.with(Style::rounded().remove_horizontals().remove_vertical()); table.with(Padding::new(2, 2, 0, 0)); table.with(Shadow::new(3).set_offset(6)); table.with(Reverse::rows(1, 0)); table.with(Reverse::columns(0, 0)); println!("{table}"); } tabled-0.18.0/examples/rotate.rs000064400000000000000000000016411046102023000146310ustar 00000000000000//! This example demonstrates using the [`Rotate`] [`TableOption`] to rotate the cells //! of a [`Table`]. //! //! * [`Rotate`] supports four motions: //! * `Left` | 90 degree shift //! * `Right` | 90 degree shift //! * `Top` & `Bottom` | Reverse row order use tabled::{settings::Rotate, Table, Tabled}; #[derive(Tabled)] struct Linux { id: u8, distribution: &'static str, link: &'static str, } impl Linux { fn new(id: u8, distribution: &'static str, link: &'static str) -> Self { Self { id, distribution, link, } } } fn main() { let data = vec![ Linux::new(0, "Fedora", "https://getfedora.org/"), Linux::new(2, "OpenSUSE", "https://www.opensuse.org/"), Linux::new(3, "Endeavouros", "https://endeavouros.com/"), ]; let table = Table::new(data).with(Rotate::Left).to_string(); println!("{table}"); } tabled-0.18.0/examples/settings_list.rs000064400000000000000000000031131046102023000162220ustar 00000000000000//! This example demonstrates using the [`Settings`] [`TableOption`] to array //! [`Table`] configurations in a separate step from instantiation. //! //! * Note how this methodology can lead to huge performance gains //! with compile-time constants. use tabled::{ settings::{ object::{FirstRow, Rows}, style::{On, Style}, Alignment, Modify, ModifyList, Padding, Settings, }, Table, Tabled, }; #[derive(Tabled)] struct CodeEditor { name: &'static str, first_release: &'static str, developer: &'static str, } impl CodeEditor { fn new(name: &'static str, first_release: &'static str, developer: &'static str) -> Self { Self { name, first_release, developer, } } } // unfortunately we can't leave it as a blank type, so we need to provide it. type TableTheme = Settings< Settings>, Padding>, ModifyList, >; const THEME: TableTheme = Settings::empty() .with(Style::ascii()) .with(Padding::new(1, 3, 0, 0)) .with(Modify::list(Rows::first(), Alignment::center())); fn main() { let data = [ CodeEditor::new("Sublime Text 3", "2008", "Sublime HQ"), CodeEditor::new("Visual Studio Code", "2015", "Microsoft"), CodeEditor::new("Notepad++", "2003", "Don Ho"), CodeEditor::new("GNU Emacs", "1984", "Richard Stallman"), CodeEditor::new("Neovim", "2015", "Vim community"), ]; let mut table = Table::new(data); table.with(THEME); println!("{table}"); } tabled-0.18.0/examples/shadow.rs000064400000000000000000000110101046102023000146070ustar 00000000000000//! This example can be run with the following command: //! //! `cargo run --example shadow -- "Some text" "In the box"` //! //! This example demonstrates using the [`Shadow`] [`TableOption`] to create //! a striking frame around a [`Table`] display. //! //! * [`Shadow`] supports several configurations: //! * Thickness //! * Offset //! * Direction //! * Color //! * Fill character //! //! * 🎉 Inspired by use std::iter::FromIterator; use tabled::{ builder::Builder, grid::{config::Borders, util::string}, row, settings::{ style::{LineChar, Style}, Height, Padding, Shadow, Width, }, Table, }; fn main() { let message = read_message(); print_table(message); } fn print_table(message: String) { let main_table = create_main_table(&message); let main_table_width = main_table.total_width(); let small_table_row = create_small_table_list(main_table_width); println!("{small_table_row}"); println!("{main_table}"); } fn read_message() -> String { let args = std::env::args().collect::>(); if args.len() < 2 { eprintln!("Expected to get at least 1 argument to be printed"); std::process::exit(-1); } let mut buf = String::new(); for (i, text) in args.iter().skip(1).enumerate() { if i > 0 { buf.push('\n'); } buf.push_str(text); } buf } fn create_small_table_list(width_available: usize) -> String { let style1 = Style::modern(); let style2 = Style::extended(); let style3 = Style::modern() .left('║') .right('║') .intersection_left('╟') .intersection_right('╢') .corner_top_right('╖') .corner_top_left('╓') .corner_bottom_right('╜') .corner_bottom_left('╙'); let style4 = Style::modern() .top('═') .bottom('═') .corner_top_right('╕') .corner_top_left('╒') .corner_bottom_right('╛') .corner_bottom_left('╘') .horizontal('═') .intersection_left('╞') .intersection_right('╡') .intersection_top('╤') .intersection_bottom('╧') .intersection('╪'); let mut tables = [ create_small_table(Borders::from(style1)), create_small_table(Borders::from(style2)), create_small_table(Borders::from(style3)), create_small_table(Borders::from(style4)), ]; const TOTAL_TABLE_WIDTH: usize = 19; if width_available > TOTAL_TABLE_WIDTH { let mut rest = width_available - TOTAL_TABLE_WIDTH; while rest > 0 { for table in &mut tables { let current_width = table.total_width(); table.with(Width::increase(current_width + 1)); rest -= 1; if rest == 0 { break; } } } } let small_table_row = row![tables[0], tables[1], tables[2], tables[3]] .with(Style::blank()) .with(Padding::zero()) .to_string(); small_table_row } fn create_small_table(style: Borders) -> Table { let mut table = Builder::from_iter(vec![vec![" ", ""], vec![" ", ""]]).build(); table .with(style) .with(Padding::zero()) .with(Height::list([1, 0])); table } // todo: very likely can be simplified fn create_main_table(message: &str) -> Table { let (count_lines, message_width) = string::get_text_dimension(message); let count_additional_separators = if count_lines > 2 { count_lines - 2 } else { 0 }; let left_table_space = (0..count_additional_separators) .map(|_| " ║ \n") .collect::(); let left_table = format!( " ╔═══╗ \n ╚═╦═╝ \n{}═╤══╩══╤\n ├──┬──┤\n └──┴──┘", left_table_space ); let mut message = message.to_owned(); if count_lines < 2 { let mut i = count_lines; while i < 2 { message.push('\n'); i += 1; } } let count_lines = count_lines.max(2); let message = format!("{}\n{}", message, "═".repeat(message_width)); let mut table = row![left_table, message]; table .with(Padding::zero()) .with(Style::modern().remove_vertical()) .modify((0, 0), LineChar::vertical('╞', count_lines)) .modify((0, 2), LineChar::vertical('╡', count_lines)) .with(Shadow::new(2)); table } tabled-0.18.0/examples/span.rs000064400000000000000000000026641046102023000143020ustar 00000000000000//! This example demonstrates using the [`Span`] [`CellOption`] to //! extend [Cells](Cell) over a specified number of columns/rows. //! //! * Note how [`Span`] is available for [`Cell`] modifications //! after the [`Modify`] [`TableOption`] is applied. //! //! * ⚠️ `with()` is a reused pattern within [`tabled`] for both [`Table`] //! and [`Cell`] modifications. It can be easy for beginners to mistakenly //! try to pass [`settings`] intended for one to the other. use tabled::{ settings::{ object::Cell, style::{BorderSpanCorrection, Style}, Alignment, Modify, Span, }, Table, }; fn main() { let data = [["just 1 column"; 5]; 5]; let h_span = |r, c, span| Modify::new(Cell::new(r, c)).with(Span::row(span)); let v_span = |r, c, span| Modify::new(Cell::new(r, c)).with(Span::column(span)); let table = Table::new(data) .with(h_span(0, 0, 5).with("span all 5 columns")) .with(h_span(1, 0, 4).with("span 4 columns")) .with(h_span(2, 0, 2).with("span 2 columns")) .with(v_span(2, 4, 4).with("just 1 column\nspan\n4\ncolumns")) .with(v_span(3, 1, 2).with("span 2 columns\nspan\n2\ncolumns")) .with(v_span(2, 3, 3).with("just 1 column\nspan\n3\ncolumns")) .with(h_span(3, 1, 2)) .with(Style::modern()) .with(BorderSpanCorrection) .with(Alignment::center_vertical()) .to_string(); println!("{table}"); } tabled-0.18.0/examples/span_column.rs000064400000000000000000000027321046102023000156530ustar 00000000000000//! [COMMENTED] A list of examples for a span use. use tabled::{ settings::{style::BorderSpanCorrection, Alignment, Span, Style}, Table, }; fn main() { let data = [ (1, 2, 3), (4, 5, 6), (7, 8, 9), (10, 11, 12), (13, 14, 15), (16, 17, 18), (19, 20, 21), ]; let mut table = Table::new(data); table.with(Style::modern_rounded()); // Here we show how original table looks. println!("{table}"); // Then there's a list of Span appliences and it's affects. // Spread cell by 1 column to the right. table.modify((0, 1), Span::column(2)); // Spread cell all the way to the right, // Which essentially covers all row. table.modify((1, 0), Span::column(isize::MAX)); // Spread cell by 1 column to the left. table.modify((2, 1), Span::column(-1)); // Spread cell all the way to the left. table.modify((3, 2), Span::column(isize::MIN)); // Spread cell to cover the whole row. table.modify((4, 0), Span::column(0)); // Spread cell to cover the whole row. table.modify((5, 1), Span::column(0)); // Spread cell to cover the whole row. table.modify((6, 2), Span::column(0)); // Set a default span for a cell, // Essentially removing a span setting for it. table.modify((4, 0), Span::column(1)); // Correct the style to look good table.with(BorderSpanCorrection); table.with(Alignment::center()); println!("{table}"); } tabled-0.18.0/examples/span_row.rs000064400000000000000000000027271046102023000151710ustar 00000000000000//! [COMMENTED] A list of examples for a span use. use tabled::{ settings::{style::BorderSpanCorrection, Alignment, Span, Style}, Table, }; fn main() { let data = [ (1, 2, 3, 5, (6, 7, 8)), (9, 10, 11, 12, (13, 14, 15)), (16, 17, 18, 19, (20, 21, 22)), (23, 24, 25, 26, (27, 28, 29)), ]; let mut table = Table::new(data); table.with(Style::modern_rounded()); // Here we show how original table looks. println!("{table}"); // Then there's a list of Span appliences and it's affects. // Spread cell by 1 row to the right. table.modify((0, 0), Span::row(2)); // Spread cell all the way to the right, // Which essentially covers all columns. table.modify((0, 1), Span::row(isize::MAX)); // Spread cell by 1 row to the top. table.modify((1, 2), Span::row(-1)); // Spread cell all the way to the top. table.modify((2, 3), Span::row(isize::MIN)); // Spread cell to cover the whole column. table.modify((0, 4), Span::row(0)); // Spread cell to cover the whole column. table.modify((1, 5), Span::row(0)); // Spread cell to cover the whole column. table.modify((2, 6), Span::row(0)); // Set a default span for a cell, // Essentially removing a span setting for it. table.modify((0, 4), Span::row(1)); // Correct the style to look good table.with(BorderSpanCorrection); table.with(Alignment::center_vertical()); println!("{table}"); } tabled-0.18.0/examples/split.rs000064400000000000000000000023601046102023000144650ustar 00000000000000//! This example demonstrates using the [`Split`] [`TableOption`] to //! transform a [`Table`] display in multiple ways. //! //! * Several configurations are available to customize a [`Split`] instruction: //! * [`Index`](usize) //! * [`Behavior`] //! * [`Direction`] //! * [`Display`] use std::iter::FromIterator; use tabled::{ col, row, settings::{split::Split, style::Style, Padding}, Table, }; fn main() { let mut table = Table::from_iter(['a'..='z']); table.with(Style::modern()); let table_1 = table.clone().with(Split::column(12)).clone(); let table_2 = table_1.clone().with(Split::column(2).zip()).to_string(); let table_3 = table_1.clone().with(Split::column(2).concat()).to_string(); let table_4 = table_1.clone().with(Split::row(2).zip()).to_string(); let table_5 = table_1.clone().with(Split::row(2).concat()).to_string(); let mut table = col![ table, row![ table_1, table_2, table_3, col![table_4, table_5] .with(Style::blank()) .with(Padding::zero()) ] .with(Style::blank()) .with(Padding::zero()), ]; table.with(Style::blank()); println!("{table}"); } tabled-0.18.0/examples/style_modern_rounded.rs000064400000000000000000000015031046102023000175540ustar 00000000000000use tabled::{ grid::config::Borders, settings::{ style::{HorizontalLine, On, Style}, Border, }, Table, }; const STYLE_1: Style = Style::modern().frame(Border::inherit(Style::rounded())); const STYLE_2: Style = Style::rounded() .line_horizontal(HorizontalLine::inherit(Style::modern())) .remove_horizontals(); fn main() { assert_eq!(Borders::from(STYLE_1), Borders::from(STYLE_2)); let data = vec![("Hello", "world", "!"); 3]; let mut table1 = Table::new(&data); table1.with(STYLE_2); let mut table2 = Table::new(&data); table2.with(STYLE_1); let output1 = table1.to_string(); let output2 = table2.to_string(); let output = Table::new([(output1, output2)]); println!("{}", output); } tabled-0.18.0/examples/table.rs000064400000000000000000000035551046102023000144300ustar 00000000000000//! This example demonstrates the fundamental qualities of the [crate](https://crates.io/crates/tabled) [`tabled`]. //! //! * [`tabled`] is powered by convenient [procedural macros](https://doc.rust-lang.org/reference/procedural-macros.html#procedural-macros) //! like [`Tabled`]; a deriveable trait that allows your custom //! structs and enums to be represented with [`tabled`]'s powerful suite of features. //! //! * [`Table`] is the root object to which all of [`tabled`]'s implementation //! tools guide you, and from which all of its customization options derive value. //! The READMEs, examples, and docs found in this project will show you many dozens //! of ways you can build tables, and show you how to use the available tools //! to build the data representations that best fit your needs. //! //! * [`Table::with()`] plays a central role in giving the user control over their //! displays. A majority of [`Table`] customizations are implemented through this highly //! dynamic function. A few [`TableOptions`](TableOption) include: //! * [`Style`] //! * [`Modify`] //! * [`Alignment`] //! * [`Padding`] use tabled::{ settings::{object::Rows, Alignment, Style}, Table, Tabled, }; #[derive(Debug, Tabled)] struct Distribution { name: String, based_on: String, is_active: bool, } impl Distribution { fn new(name: &str, base: &str, is_active: bool) -> Self { Self { based_on: base.to_owned(), name: name.to_owned(), is_active, } } } fn main() { let data = [ Distribution::new("Debian", "", true), Distribution::new("Arch", "", true), Distribution::new("Manjaro", "Arch", true), ]; let mut table = Table::new(data); table .with(Style::markdown()) .modify(Rows::first(), Alignment::center()); println!("{table}"); } tabled-0.18.0/examples/table_width.rs000064400000000000000000000023311046102023000156160ustar 00000000000000//! This example demonstrates using the [`Width`] [`TableOption`] to expand and //! contract a [`Table`] display. //! //! * Note how table-wide size adjustments are applied proportionally to all columns. //! //! * Note how [fluent](https://en.wikipedia.org/wiki/Fluent_interface) functions //! are available to make subtle customizations to [`Width`] primary features like //! [`Width::truncate()`], [`Width::increase()`], and [`Width::wrap()`]. use tabled::{ settings::{measurement::Percent, object::Segment, Alignment, Modify, Style, Width}, Table, }; fn main() { let data = [ ["Hello World!!!", "3.3.22.2"], ["Guten Morgen", "1.1.1.1"], ["Добры вечар", "127.0.0.1"], ["Bonjour le monde", ""], ["Ciao mondo", ""], ]; let mut table = Table::builder(data).build(); table.with(Style::markdown()).with(Alignment::left()); println!("Original table\n{table}\n"); table.with(Width::truncate(20).suffix("...")); println!("Truncated table\n{table}\n"); table.with(Modify::new(Segment::all()).with(Width::wrap(5))); println!("Wrapped table\n{table}\n"); table.with(Width::increase(Percent(200))); println!("Widen table\n{table}"); } tabled-0.18.0/examples/table_width_2.rs000064400000000000000000000012261046102023000160410ustar 00000000000000//! This example demonstrates using [`Wrap::keep_words()`] to preserve //! word shape while truncating a table to the specified size. Without //! this setting enabled, a word could possibly be split into pieces, //! greatly reducing the legibility of the display. use tabled::{ settings::{object::Segment, Style, Width}, Table, }; fn main() { let readme_text = include_str!("../../CHANGELOG.md"); let lines = readme_text.lines().filter(|s| !s.is_empty()).enumerate(); let mut table = Table::new(lines); table.with(Style::ascii_rounded()); table.modify(Segment::all(), Width::wrap(30).keep_words(true)); println!("{table}"); } tabled-0.18.0/examples/target_content.rs000064400000000000000000000022231046102023000163500ustar 00000000000000//! This example demonstrates [`Locator`] usage to colorize certain cells. use tabled::{ settings::{location::Locator, style::Style, Color}, Table, Tabled, }; #[derive(Tabled)] struct Job { title: String, #[tabled(display = "JobStatus::as_string")] status: JobStatus, } impl Job { fn new(title: &str, status: JobStatus) -> Self { Self { title: title.to_string(), status, } } } enum JobStatus { Open, Closed, } impl JobStatus { fn as_string(&self) -> &'static str { match self { JobStatus::Open => "open", JobStatus::Closed => "closed", } } } fn main() { let data = vec![ Job::new("C Developer", JobStatus::Open), Job::new("Rust Developer", JobStatus::Closed), Job::new("Kernel Developer", JobStatus::Open), ]; let mut table = Table::new(data); table .with(Style::empty()) .modify(Locator::content("open"), Color::BG_WHITE | Color::FG_BLACK) .modify( Locator::content("closed"), Color::BG_GREEN | Color::FG_BLACK, ); println!("{table}"); } tabled-0.18.0/examples/theme.rs000064400000000000000000000040411046102023000144320ustar 00000000000000use tabled::{ settings::{ themes::{Layout, Theme}, Alignment, Reverse, Style, }, Table, Tabled, }; #[derive(Tabled)] struct Concept { name: String, desc: String, } impl Concept { fn new(name: &str, desc: &str) -> Self { Self { name: name.to_string(), desc: desc.to_string(), } } } fn main() { let data = vec![ Concept::new("vnode", "A structure representing a filesystem entity like a file, directory, device node, etc at VFS abstraction level"), Concept::new("Physical block number", "In the context of FreeBSD filesystems layer we use this term when referring to fixed-size 512-byte blocks"), Concept::new("Logical block number", "Typical filesystems have a notion of filesystem blocks which may be constituted of multiple physical (512-byte) or media blocks"), Concept::new("Device vnode", "A filesystem is backed by some media that actually contains the data"), Concept::new("Buffer cache", "Buffer cache is a layer between filesystems and I/O code that performs actual media access via peripheral drivers"), Concept::new("struct bufobj", "struct bufobj represents a set of buffers belonging to the same abstract object"), Concept::new("struct buf", "struct buf represents a single buffer. In addition to holding identity of the buffer"), Concept::new("bread(9)", "Filesystem use bread (breadn, breada, etc) functions to access underlying data through a buffer cache layer"), Concept::new("VOP_BMAP", "VOP_BMAP translates a given logical block number within a given vnode to a physical (512-byte) block number within underlying media"), Concept::new("VOP_STRATEGY", "VOP_STRATEGY method fulfills an I/O request described by given struct buf object"), ]; let mut table = Table::new(data); table.with(Theme::from_style(Style::ascii())); table.with(Reverse::rows(1, 0)); table.with(Reverse::columns(0, 0)); table.with(Layout::new(Alignment::top(), true)); println!("{table}"); } tabled-0.18.0/src/builder/index_builder.rs000064400000000000000000000213611046102023000165500ustar 00000000000000use crate::{grid::records::vec_records::Text, Table}; use super::Builder; /// [`IndexBuilder`] helps to add an index to the table. /// /// Index is a column on the left of the table. /// /// It also can be used to transpose the table. /// /// Creates a new [`IndexBuilder`] instance. /// /// It creates a default index a range from 0 to N. (N - count rows) /// It also sets a default columns to the range 0 .. N (N - count columns). ///nfo<'a> /// # Example /// /// ``` /// use tabled::builder::Builder; /// /// let mut builder = Builder::default(); /// builder.push_record(["i", "col-1", "col-2"]); /// builder.push_record(["0", "value-1", "value-2"]); /// /// let table = builder.index().build().to_string(); /// /// assert_eq!( /// table, /// "+---+---+---------+---------+\n\ /// | | i | col-1 | col-2 |\n\ /// +---+---+---------+---------+\n\ /// | 0 | 0 | value-1 | value-2 |\n\ /// +---+---+---------+---------+" /// ) /// ``` /// /// # Example /// /// ``` /// use tabled::builder::Builder; /// /// let table = Builder::default() /// .index() /// .build(); /// ``` #[derive(Debug, Clone)] pub struct IndexBuilder { /// Index is an index data. /// It's always set. index: Vec>, /// Name of an index name: Option>, /// A flag which checks if we need to actually use index. /// /// It might happen when it's only necessary to [`Self::transpose`] table. print_index: bool, /// A flag which checks if table was transposed. transposed: bool, /// Data originated in [`Builder`]. data: Vec>>, /// A size of columns count_columns: usize, } impl IndexBuilder { /// No flag makes builder to not use an index. /// /// It may be useful when only [`Self::transpose`] need to be used. /// /// ``` /// use tabled::builder::Builder; /// /// let mut builder = Builder::default(); /// builder.push_record(["i", "col-1", "col-2"]); /// builder.push_record(["0", "value-1", "value-2"]); /// builder.push_record(["2", "value-3", "value-4"]); /// /// let table = builder.index().hide().build().to_string(); /// /// assert_eq!( /// table, /// "+---+---------+---------+\n\ /// | i | col-1 | col-2 |\n\ /// +---+---------+---------+\n\ /// | 0 | value-1 | value-2 |\n\ /// +---+---------+---------+\n\ /// | 2 | value-3 | value-4 |\n\ /// +---+---------+---------+" /// ) /// ``` pub fn hide(mut self) -> Self { self.print_index = false; self } /// Set an index name. /// /// When [`None`] the name won't be used. /// /// # Example /// /// ``` /// use tabled::builder::Builder; /// /// let mut builder = Builder::default(); /// builder.push_record(["i", "column1", "column2"]); /// builder.push_record(["0", "value1", "value2"]); /// /// let table = builder.index() /// .column(1) /// .name(Some(String::from("index"))) /// .build(); /// /// assert_eq!( /// table.to_string(), /// "+--------+---+---------+\n\ /// | | i | column2 |\n\ /// +--------+---+---------+\n\ /// | index | | |\n\ /// +--------+---+---------+\n\ /// | value1 | 0 | value2 |\n\ /// +--------+---+---------+" /// ) /// ``` pub fn name(mut self, name: Option) -> Self { self.name = name.map(Text::new); self } /// Sets a index to the chosen column. /// /// Also sets a name of the index to the column name. /// /// # Example /// /// ``` /// use tabled::builder::Builder; /// /// let mut builder = Builder::default(); /// builder.push_record(["i", "column1", "column2"]); /// builder.push_record(["0", "value1", "value2"]); /// /// let table = builder.index().column(1).build(); /// /// assert_eq!( /// table.to_string(), /// "+---------+---+---------+\n\ /// | | i | column2 |\n\ /// +---------+---+---------+\n\ /// | column1 | | |\n\ /// +---------+---+---------+\n\ /// | value1 | 0 | value2 |\n\ /// +---------+---+---------+" /// ) /// ``` pub fn column(mut self, column: usize) -> Self { if column >= self.count_columns { return self; } self.index = get_column(&mut self.data, column); let name = self.index.remove(0); self.name = Some(name); self } /// Transpose index and columns. /// /// # Example /// /// ``` /// use tabled::builder::Builder; /// /// let mut builder = Builder::default(); /// builder.push_record(["i", "column-1", "column-2", "column-3"]); /// builder.push_record(["0", "value-1", "value-2", "value-3"]); /// builder.push_record(["1", "value-4", "value-5", "value-6"]); /// builder.push_record(["2", "value-7", "value-8", "value-9"]); /// /// let table = builder.index().column(1).transpose().build(); /// /// assert_eq!( /// table.to_string(), /// "+----------+---------+---------+---------+\n\ /// | column-1 | value-1 | value-4 | value-7 |\n\ /// +----------+---------+---------+---------+\n\ /// | i | 0 | 1 | 2 |\n\ /// +----------+---------+---------+---------+\n\ /// | column-2 | value-2 | value-5 | value-8 |\n\ /// +----------+---------+---------+---------+\n\ /// | column-3 | value-3 | value-6 | value-9 |\n\ /// +----------+---------+---------+---------+" /// ) /// ``` pub fn transpose(mut self) -> Self { if self.data.is_empty() { return self; } let mut columns = self.data.remove(0); std::mem::swap(&mut self.index, &mut columns); let count_columns = columns.len(); rotate_vector(&mut self.data, self.index.len()); self.data.insert(0, columns); self.transposed = !self.transposed; self.count_columns = count_columns; self } /// Builds a table. pub fn build(self) -> Table { let builder: Builder = self.into(); builder.build() } } impl From for IndexBuilder { fn from(builder: Builder) -> Self { let count_columns = builder.count_columns(); let data: Vec> = builder.into(); let mut index = Vec::new(); if !data.is_empty() { // we exclude first row which contains a header let count_rows = data.len() - 1; index = build_range_index(count_rows); } Self { index, data, count_columns, name: None, print_index: true, transposed: false, } } } impl From for Builder { fn from(b: IndexBuilder) -> Self { build_index(b) } } fn build_index(mut b: IndexBuilder) -> Builder { // we can skip the conversion if this builder has neither data rows nor header row if b.index.is_empty() && b.count_columns == 0 { return Builder::default(); } // add index column if b.print_index { b.index.insert(0, Text::default()); insert_column(&mut b.data, b.index, 0); } if let Some(name) = b.name { if b.transposed && b.print_index { b.data[0][0] = name; } else { let count_columns = b.data[0].len(); let mut name_row = vec![Text::default(); count_columns]; name_row[0] = name; b.data.insert(1, name_row); } } Builder::from_vec(b.data) } fn build_range_index(n: usize) -> Vec> { (0..n).map(|i| i.to_string()).map(Text::new).collect() } // note: Pretty heavy operation. fn rotate_vector(rows: &mut Vec>, count_columns: usize) where T: Default + Clone, { let count_rows = rows.len(); let mut columns = vec![vec![T::default(); count_rows]; count_columns]; for col in 0..count_columns { for (row, data) in rows.iter_mut().enumerate() { let value = data.pop().expect("expected to be controlled"); let col = count_columns - col - 1; columns[col][row] = value; } } *rows = columns; } fn insert_column(v: &mut [Vec], mut column: Vec, col: usize) { for row in v.iter_mut() { let value = column.remove(col); row.insert(col, value); } } fn get_column(v: &mut [Vec], col: usize) -> Vec where T: Default, { let mut column = Vec::with_capacity(v.len()); for row in v.iter_mut() { let value = row.remove(col); column.push(value); } column } tabled-0.18.0/src/builder/mod.rs000064400000000000000000000103341046102023000145100ustar 00000000000000//! Builder module provides a [`Builder`] type which helps building //! a [`Table`] dynamically. //! //! It also contains [`IndexBuilder`] which can help to build a table with index. //! //! # Examples //! //! Here's an example of [`IndexBuilder`] usage //! #![cfg_attr(feature = "derive", doc = "```")] #![cfg_attr(not(feature = "derive"), doc = "```ignore")] //! use tabled::{Table, Tabled, settings::Style}; //! //! #[derive(Tabled)] //! struct Mission { //! name: &'static str, //! #[tabled(inline)] //! status: Status, //! } //! //! #[derive(Tabled)] //! enum Status { //! Complete, //! Started, //! Ready, //! Unknown, //! } //! //! let data = [ //! Mission { name: "Algebra", status: Status::Unknown }, //! Mission { name: "Apolo", status: Status::Complete }, //! ]; //! //! let mut builder = Table::builder(&data) //! .index() //! .column(0) //! .name(None) //! .transpose(); //! //! let mut table = builder.build(); //! table.with(Style::modern()); //! //! println!("{}", table); //! //! assert_eq!( //! table.to_string(), //! concat!( //! "┌──────────┬─────────┬───────┐\n", //! "│ │ Algebra │ Apolo │\n", //! "├──────────┼─────────┼───────┤\n", //! "│ Complete │ │ + │\n", //! "├──────────┼─────────┼───────┤\n", //! "│ Started │ │ │\n", //! "├──────────┼─────────┼───────┤\n", //! "│ Ready │ │ │\n", //! "├──────────┼─────────┼───────┤\n", //! "│ Unknown │ + │ │\n", //! "└──────────┴─────────┴───────┘", //! ), //! ) //! ``` //! //! Example when we don't want to show empty data of enum where not all variants are used. //! #![cfg_attr(feature = "derive", doc = "```")] #![cfg_attr(not(feature = "derive"), doc = "```ignore")] //! use tabled::{Table, Tabled, settings::Style}; //! //! #[derive(Tabled)] //! enum Status { //! #[tabled(inline)] //! Complete { //! started_timestamp: usize, //! finihsed_timestamp: usize, //! }, //! #[tabled(inline)] //! Started { //! timestamp: usize, //! }, //! Ready, //! Unknown, //! } //! //! let data = [ //! Status::Unknown, //! Status::Complete { started_timestamp: 123, finihsed_timestamp: 234 }, //! ]; //! //! let table = Table::new(data) //! .with(Style::modern()) //! .to_string(); //! //! println!("{}", table); //! //! assert_eq!( //! table, //! concat!( //! "┌───────────────────┬────────────────────┬───────────┬───────┬─────────┐\n", //! "│ started_timestamp │ finihsed_timestamp │ timestamp │ Ready │ Unknown │\n", //! "├───────────────────┼────────────────────┼───────────┼───────┼─────────┤\n", //! "│ │ │ │ │ + │\n", //! "├───────────────────┼────────────────────┼───────────┼───────┼─────────┤\n", //! "│ 123 │ 234 │ │ │ │\n", //! "└───────────────────┴────────────────────┴───────────┴───────┴─────────┘", //! ), //! ) //! ``` //! //! [`Table`]: crate::Table mod index_builder; mod table_builder; pub use index_builder::IndexBuilder; pub use table_builder::Builder; tabled-0.18.0/src/builder/table_builder.rs000064400000000000000000000353011046102023000165270ustar 00000000000000use std::iter::FromIterator; use crate::{grid::records::vec_records::Text, Table}; use super::IndexBuilder; /// Builder creates a [`Table`] from dynamic data set. /// /// It useful when the amount of columns or rows is not known statically. /// /// ```rust /// use tabled::builder::Builder; /// /// let mut builder = Builder::default(); /// builder.push_record(["index", "measure", "value"]); /// builder.push_record(["0", "weight", "0.443"]); /// /// let table = builder.build(); /// /// println!("{}", table); /// ``` /// /// It may be useful to use [`FromIterator`] for building. /// /// ```rust /// use tabled::builder::Builder; /// use std::iter::FromIterator; /// /// let data = vec![ /// ["column1", "column2"], /// ["data1", "data2"], /// ["data3", "data4"], /// ]; /// /// let table = Builder::from_iter(data).build(); /// /// println!("{}", table); /// ``` #[derive(Debug, Default, Clone)] pub struct Builder { /// A list of rows. data: Vec>>, /// A number of columns. count_columns: usize, /// A content of cells which are created in case rows has different length. empty_text: Text, } impl Builder { /// Creates a [`Builder`] instance. /// /// ``` /// use tabled::builder::Builder; /// /// let builder = Builder::new(); /// ``` pub fn new() -> Self { Self::default() } /// Creates a [`Builder`] instance with a given row capacity. /// /// ``` /// use tabled::builder::Builder; /// /// let mut builder = Builder::with_capacity(2, 3); /// builder.push_record((0..3).map(|i| i.to_string())); /// builder.push_record(["i", "surname", "lastname"]); /// ``` pub fn with_capacity(count_records: usize, count_columns: usize) -> Self { let mut builder = Self::new(); builder.data = Vec::with_capacity(count_records); builder.count_columns = count_columns; builder } /// Creates a [`Builder`] instance. /// /// # Safety /// /// It's marked unsafe to emphasize that you shall make sure that all rows bound to have the same length. /// /// ``` /// use tabled::builder::Builder; /// /// let data = vec![]; /// let builder = Builder::from_vec(data); /// ``` pub fn from_vec(data: Vec>>) -> Self { let count_columns = if data.is_empty() { 0 } else { data[0].len() }; Self { data, count_columns, empty_text: Text::default(), } } /// Sets a content of cells which are created in case rows has different length. /// /// /// ```rust /// use tabled::builder::Builder; /// /// let mut builder = Builder::default(); /// builder.set_empty("undefined"); /// builder.push_record((0..3).map(|i| i.to_string())); /// builder.push_record(["i"]); /// ``` pub fn set_empty(&mut self, text: T) where T: Into, { self.empty_text = Text::new(text.into()); } /// Build creates a [`Table`] instance. /// /// ```rust /// use tabled::builder::Builder; /// /// let mut builder = Builder::default(); /// builder.push_record(["i", "column1", "column2"]); /// builder.push_record(["0", "value1", "value2"]); /// ``` pub fn build(self) -> Table { Table::from(self) } /// Add an index to the [`Table`]. /// /// Default index is a range 0-N where N is amount of records. /// /// # Example /// /// ``` /// use tabled::Table; /// /// let table = Table::builder(&["Hello", "World", "!"]).index().build(); /// /// assert_eq!( /// table.to_string(), /// "+---+-------+\n\ /// | | &str |\n\ /// +---+-------+\n\ /// | 0 | Hello |\n\ /// +---+-------+\n\ /// | 1 | World |\n\ /// +---+-------+\n\ /// | 2 | ! |\n\ /// +---+-------+" /// ) /// ``` pub fn index(self) -> IndexBuilder { IndexBuilder::from(self) } /// Adds a row to a [`Table`]. /// /// ``` /// use tabled::builder::Builder; /// /// let mut builder = Builder::default(); /// builder.push_record((0..3).map(|i| i.to_string())); /// builder.push_record(["i", "surname", "lastname"]); /// ``` pub fn push_record(&mut self, record: R) where R: IntoIterator, R::Item: Into, { let list = create_row(record, self.count_columns, &self.empty_text); let list_length = list.len(); if !is_size_eq(self.count_columns, list_length) { let size = list_length - self.count_columns; resize_rows(&mut self.data, size, &self.empty_text) } self.count_columns = list_length; self.data.push(list); } /// Insert a row into a specific position. /// /// # Panics /// /// Panics if `index > count_rows`. pub fn insert_record(&mut self, index: usize, record: R) where R: IntoIterator, R::Item: Into, { let list = create_row(record, self.count_columns, &self.empty_text); let list_length = list.len(); if !is_size_eq(self.count_columns, list_length) { let size = list_length - self.count_columns; resize_rows(&mut self.data, size, &self.empty_text) } self.count_columns = list_length; self.data.insert(index, list); } /// Clean removes empty columns and rows. /// /// # Example /// /// ``` /// use tabled::Table; /// /// let mut builder = Table::builder(&["Hello", "World", ""]); /// builder.clean(); /// /// let table = builder.build(); /// /// assert_eq!( /// table.to_string(), /// "+-------+\n\ /// | &str |\n\ /// +-------+\n\ /// | Hello |\n\ /// +-------+\n\ /// | World |\n\ /// +-------+" /// ) /// ``` pub fn clean(&mut self) { self.count_columns -= remove_empty_columns(&mut self.data, self.count_columns); remove_empty_rows(&mut self.data, self.count_columns); } /// Removes a row with a specific position. /// /// Index expected to be in range. /// `Builder::count_records() < x >= 0` /// /// # Panics /// /// Panics if `row_index > count_rows`. pub fn remove_record(&mut self, index: usize) { let _ = self.data.remove(index); } /// Removes a column with a specific position. /// /// Index expected to be in range. /// `Builder::count_columns() < x >= 0` /// /// # Panics /// /// Panics if `index > count_columns`. pub fn remove_column(&mut self, index: usize) { for row in &mut self.data { let _ = row.remove(index); } self.count_columns -= 1; } /// Push a column. pub fn push_column(&mut self, column: I) where I: IntoIterator, I::Item: Into, { let mut iter = column.into_iter(); for row in self.data.iter_mut() { let text = iter .next() .map(Into::into) .map(Text::new) .unwrap_or(self.empty_text.clone()); row.push(text); } for text in iter { let text = Text::new(text.into()); let mut row = Vec::with_capacity(self.count_columns + 1); for _ in 0..self.count_columns { row.push(self.empty_text.clone()); } row.push(text); self.data.push(row); } self.count_columns += 1; } /// Insert a column with a specific position. /// /// In case a column is bigger then the total amount of rows it will be truncated. /// /// # Panics /// /// Panics if `index > count_columns`. pub fn insert_column(&mut self, index: usize, column: I) where I: IntoIterator, I::Item: Into, { let mut iter = column.into_iter(); for row in self.data.iter_mut() { let text = iter .next() .map(Into::into) .map(Text::new) .unwrap_or(self.empty_text.clone()); row.insert(index, text); } for text in iter { let text = Text::new(text.into()); let mut row = Vec::with_capacity(self.count_columns + 1); for _ in 0..index { row.push(self.empty_text.clone()); } row.push(text); for _ in index..self.count_columns { row.push(self.empty_text.clone()); } } self.count_columns += 1; } /// Remove all records. pub fn clear(&mut self) { self.data.clear(); self.count_columns = 0; } /// Returns an amount of columns which would be present in a built table. pub fn count_columns(&self) -> usize { self.count_columns } /// Returns an amount of rows which would be present in a built table. /// /// Notice that it does not include header if present; /// It returns only amount of records. pub fn count_records(&self) -> usize { self.data.len() } } impl From for Vec> { fn from(builder: Builder) -> Self { builder .data .into_iter() .map(|row| row.into_iter().map(Text::into_inner).collect()) .collect() } } impl From for Vec>> { fn from(builder: Builder) -> Self { builder.data } } impl From> for Builder where K: ToString, V: ToString, { fn from(m: std::collections::HashMap) -> Self { let mut b = Self::with_capacity(m.len(), 2); for (k, v) in m { b.push_record([k.to_string(), v.to_string()]); } b } } impl From> for Builder where K: ToString, V: ToString, { fn from(m: std::collections::BTreeMap) -> Self { let mut b = Self::with_capacity(m.len(), 2); for (k, v) in m { b.push_record([k.to_string(), v.to_string()]); } b } } impl From> for Builder where V: ToString, { fn from(m: std::collections::HashSet) -> Self { let mut b = Self::with_capacity(m.len(), 1); for v in m { b.push_record([v.to_string()]); } b } } impl From> for Builder where V: ToString, { fn from(m: std::collections::BTreeSet) -> Self { let mut b = Self::with_capacity(m.len(), 1); for v in m { b.push_record([v.to_string()]); } b } } impl FromIterator for Builder where R: IntoIterator, R::Item: Into, { fn from_iter>(iter: T) -> Self { let mut builder = Self::new(); for row in iter { builder.push_record(row); } builder } } impl Extend for Builder where D: Into, { fn extend>(&mut self, iter: T) { self.push_record(iter); } } impl From>> for Builder { fn from(data: Vec>) -> Self { let mut data = data .into_iter() .map(|row| row.into_iter().map(Text::new).collect()) .collect(); let count_columns = equalize_row_length(&mut data); Self { data, count_columns, empty_text: Text::default(), } } } impl From>>> for Builder { fn from(mut data: Vec>>) -> Self { let count_columns = equalize_row_length(&mut data); Self { data, count_columns, empty_text: Text::default(), } } } fn create_row(row: R, size: usize, default: &Text) -> Vec> where R: IntoIterator, R::Item: Into, { let mut list = Vec::with_capacity(size); for text in row { let text = text.into(); let text = Text::new(text); list.push(text); } if list.len() < size { for _ in 0..size - list.len() { let text = default.clone(); list.push(text); } } list } fn remove_empty_columns(data: &mut [Vec>], count_columns: usize) -> usize { let mut deleted = 0; for col in 0..count_columns { let col = col - deleted; let mut is_empty_column = true; for row in data.iter() { let text = &row[col]; if !text.as_ref().is_empty() { is_empty_column = false; break; } } if is_empty_column { for row in data.iter_mut() { let _ = row.remove(col); } deleted += 1; } } deleted } fn remove_empty_rows(data: &mut Vec>>, count_columns: usize) { let mut deleted = 0; for row in 0..data.len() { let row = row - deleted; let mut is_empty_row = true; for col in 0..count_columns { let cell = &data[row][col]; if !cell.as_ref().is_empty() { is_empty_row = false; break; } } if is_empty_row { let _ = data.remove(row); deleted += 1; } } } fn resize_rows(data: &mut Vec>>, size: usize, empty_text: &Text) { for row in data { append_vec(row, empty_text.clone(), size); } } fn append_vec(v: &mut Vec, value: T, n: usize) where T: Clone, { for _ in 0..n { v.push(value.clone()); } } fn is_size_eq(expected: usize, new: usize) -> bool { use std::cmp::Ordering; match new.cmp(&expected) { Ordering::Less => { unreachable!("must be impossible due to the assumptions/checks we do"); } Ordering::Greater => false, Ordering::Equal => true, } } fn equalize_row_length(data: &mut Vec>>) -> usize { if data.is_empty() { return 0; } let first_row_length = data[0].len(); let init = (first_row_length, true); let (count_columns, is_consistent) = data.iter().fold(init, |mut acc, cur| { let length = cur.len(); acc.1 = acc.1 && acc.0 == length; acc.0 = std::cmp::max(acc.0, length); acc }); if !is_consistent { let empty_text = Text::default(); for row in data { let size = count_columns - row.len(); append_vec(row, empty_text.clone(), size); } } count_columns } tabled-0.18.0/src/derive/display/mod.rs000064400000000000000000000111741046102023000160100ustar 00000000000000//! Module contains a list of helpers for work with display. use core::fmt::Debug; /// A function which is usefull in conjuntion with /// `#[tabled(display)]` and `#[tabled(display)]`. /// /// It can be used with any [`Option`] type. /// You must provide a second argument which represents a value be printed in case of [`None`]. /// /// # Example /// /// ``` /// use tabled::Tabled; /// use tabled::derive::display; /// use testing_table::assert_table; /// /// #[derive(Tabled)] /// #[tabled(display(Option, "display::option", "Unknown"))] /// pub struct ZKP<'a> { /// application: &'a str, /// state: Option<&'a str> /// } /// /// let data = vec![ /// ZKP { application: "Decentralized Identity", state: Some("Proved") }, /// ZKP { application: "Voting Systems", state: Some("Investigation") }, /// ZKP { application: "Privacy-Preserving Transactions", state: None }, /// ]; /// /// let table = tabled::Table::new(data); /// /// assert_table!( /// table, /// "+---------------------------------+---------------+" /// "| application | state |" /// "+---------------------------------+---------------+" /// "| Decentralized Identity | Proved |" /// "+---------------------------------+---------------+" /// "| Voting Systems | Investigation |" /// "+---------------------------------+---------------+" /// "| Privacy-Preserving Transactions | Unknown |" /// "+---------------------------------+---------------+" /// ); /// ``` pub fn option(value: &Option, default: &str) -> String where T: ToString, { match value { Some(val) => val.to_string(), None => default.to_string(), } } /// A function which is usefull in conjuntion with /// `#[tabled(display)]` and `#[tabled(display)]`. /// /// It can be used with any type which implements a [`Debug`]. /// So rather then [`std::fmt::Display`] usage we will be using a debug implementation. /// /// ``` /// use tabled::Tabled; /// use tabled::derive::display; /// use testing_table::assert_table; /// /// #[derive(Tabled)] /// #[tabled(display(Option, "display::debug"))] /// pub struct ZKP<'a> { /// application: &'a str, /// state: Option<&'a str> /// } /// /// let data = vec![ /// ZKP { application: "Decentralized Identity", state: Some("Proved") }, /// ZKP { application: "Voting Systems", state: Some("Investigation") }, /// ZKP { application: "Privacy-Preserving Transactions", state: None }, /// ]; /// /// let table = tabled::Table::new(data); /// /// assert_table!( /// table, /// r#"+---------------------------------+-----------------------+"# /// r#"| application | state |"# /// r#"+---------------------------------+-----------------------+"# /// r#"| Decentralized Identity | Some("Proved") |"# /// r#"+---------------------------------+-----------------------+"# /// r#"| Voting Systems | Some("Investigation") |"# /// r#"+---------------------------------+-----------------------+"# /// r#"| Privacy-Preserving Transactions | None |"# /// r#"+---------------------------------+-----------------------+"# /// ); /// ``` pub fn debug(value: &T) -> String where T: Debug, { format!("{:?}", value) } /// A function which is usefull in conjuntion with /// `#[tabled(display)]` and `#[tabled(display)]`. /// /// It just returns an empty string. /// /// ``` /// use tabled::Tabled; /// use tabled::derive::display; /// use testing_table::assert_table; /// /// #[derive(Tabled)] /// pub struct ZKP<'a> { /// application: &'a str, /// #[tabled(display = "display::empty")] /// state: Option<&'a str> /// } /// /// let data = vec![ /// ZKP { application: "Decentralized Identity", state: Some("Proved") }, /// ZKP { application: "Voting Systems", state: Some("Investigation") }, /// ZKP { application: "Privacy-Preserving Transactions", state: None }, /// ]; /// /// let table = tabled::Table::new(data); /// /// assert_table!( /// table, /// r#"+---------------------------------+-------+"# /// r#"| application | state |"# /// r#"+---------------------------------+-------+"# /// r#"| Decentralized Identity | |"# /// r#"+---------------------------------+-------+"# /// r#"| Voting Systems | |"# /// r#"+---------------------------------+-------+"# /// r#"| Privacy-Preserving Transactions | |"# /// r#"+---------------------------------+-------+"# /// ); /// ``` pub fn empty(_value: &T) -> String { String::new() } tabled-0.18.0/src/derive/mod.rs000064400000000000000000000121331046102023000143370ustar 00000000000000//! Module contains a list of helpers for work with derive. pub mod display; /// A derive macro to implement a [`Tabled`] trait. /// /// The macro available only when `derive` feature in turned on (and it is by default). /// /// ``` /// use tabled::Tabled; /// /// #[derive(Tabled)] /// struct SomeType { /// field0: &'static str, /// field1: String, /// field2: usize, /// } /// ``` /// /// To be able to use the derive each field must implement `std::fmt::Display`.\ /// The following example will cause an error because of that. /// /// ```,compile_fail /// use tabled::Tabled; /// /// #[derive(Tabled)] /// struct SomeType { /// field1: Vec, /// } /// ``` /// /// Bellow you'll find available options for it. /// /// ## Rename a column name /// /// You can use a `#[tabled(rename = "")]` attribute to override a column name. /// /// ``` /// use tabled::Tabled; /// /// #[derive(Tabled)] /// struct Person { /// #[tabled(rename = "Name")] /// first_name: String, /// #[tabled(rename = "Surname")] /// last_name: String, /// } /// ``` /// /// ## Hide a column /// /// You can mark fields as hidden in which case they fill be ignored and not be present on a sheet. /// /// ``` /// use tabled::Tabled; /// /// #[derive(Tabled)] /// struct Person { /// id: u8, /// #[tabled(skip)] /// number: String, /// name: String, /// } /// ``` /// /// ## Set column order /// /// You can change the order in which they will be displayed in table. /// /// ``` /// use tabled::Tabled; /// /// #[derive(Tabled)] /// struct Person { /// id: u8, /// #[tabled(order = 0)] /// number: String, /// #[tabled(order = 1)] /// name: String, /// } /// ``` /// /// ## Format fields /// /// Using `#[derive(Tabled)]` is possible only when all fields implement a `Display` trait.\ /// However, this may be not convinient for example when a field uses the `Option` type.\ /// There's 2 common ways how to solve this: /// /// - Implement `Tabled` trait manually for a type. /// - Wrap `Option` to something like `DisplayedOption(Option)` and implement a Display trait for it. /// /// But, it's not quite convient either. /// /// So alternatively, we provide the next solutions. /// /// - Use the `#[tabled(display = "func")]` - attribute to set a display function. /// - Use the `#[tabled(format = "{}")]` - attribute to format field. /// /// ### `#[tabled(display)]` /// /// A poverfull helper, the set function must have a first argument as a reference to a field.\ /// It supports custom arguments as well (including `self`). /// /// You can set it right on the whole type,\ /// In which case all fields which are matching a set type will be using the given function. /// /// We also provide a set of commonly used function for your types.\ /// You can find them in [`tabled::derive::display`]. /// /// ``` /// use tabled::Tabled; /// use tabled::derive::display; /// /// #[derive(Tabled)] /// #[tabled(display(i64, "display_i64"))] /// pub struct Record { /// pub id: i64, /// #[tabled(display("display::option", "unvalidated"))] /// pub valid: Option, /// #[tabled(display("display_private", self))] /// pub private: (), /// } /// /// fn display_private(_: &(), rec: &Record) -> String { /// todo!() /// } /// /// fn display_i64(val: &i64) -> String { /// todo!() /// } /// ``` /// /// ### `#[tabled(format)]` /// /// An analogue to [`format!`], which can be used right on the field.\ /// /// ``` /// use tabled::Tabled; /// /// #[derive(Tabled)] /// struct Record { /// #[tabled(skip)] /// id: u8, /// #[tabled(format("{}.{}-{}", self.id, self.name, 123))] /// name: String, /// } /// ``` /// /// ## Format headers /// /// Beside `#[tabled(rename = "")]` you can change a format of a column name using\ /// `#[tabled(rename_all = "UPPERCASE")]`. /// /// ``` /// use tabled::Tabled; /// /// #[derive(Tabled)] /// #[tabled(rename_all = "CamelCase")] /// struct Person { /// id: u8, /// number: String, /// name: String, /// #[tabled(rename_all = "snake_case")] /// middle_name: String, /// } /// ``` /// /// ## Embeding a field /// /// You can inline a field or a variant if it implements `Tabled` trait\ /// using `#[tabled(inline)]`. /// You can also set a prefix for inlined elements by given it as a argument\ /// `#[tabled(inline("::::"))]`. /// /// ``` /// use tabled::Tabled; /// /// #[derive(Tabled)] /// struct Person { /// id: u8, /// name: String, /// #[tabled(inline)] /// ed: Education, /// } /// /// #[derive(Tabled)] /// struct Education { /// uni: String, /// graduated: bool, /// } /// ``` /// /// And it works for enums as well. /// /// ``` /// use tabled::Tabled; /// /// #[derive(Tabled)] /// enum Vehicle { /// #[tabled(inline("Auto::"))] /// Auto { /// model: String, /// engine: String, /// }, /// #[tabled(inline)] /// Bikecycle( /// String, /// #[tabled(inline)] Bike, /// ), /// } /// /// #[derive(Tabled)] /// struct Bike { /// brand: &'static str, /// price: f32, /// } /// ``` /// /// [`tabled::derive::display`]: crate::tabled::derive::display pub use tabled_derive::Tabled; tabled-0.18.0/src/grid/config/colored_config.rs000064400000000000000000000055571046102023000174640ustar 00000000000000use std::ops::{Deref, DerefMut}; use papergrid::config::Position; use crate::grid::{ ansi::ANSIBuf, config::{Entity, EntityMap, SpannedConfig}, }; /// A spanned configuration plus colors for cells. #[derive(Debug, Default, PartialEq, Eq, Clone)] pub struct ColoredConfig { config: SpannedConfig, colors: ColorMap, } impl ColoredConfig { /// Create a new colored config. pub fn new(config: SpannedConfig) -> Self { Self { config, colors: ColorMap::default(), } } /// Set a color for a given cell. /// /// The outcome is the same as if you'd use [`Format`] and added a color but it'd work only with `color` feature on. /// While this method works in all contexts. /// /// [`Format`]: crate::settings::Format pub fn set_color(&mut self, pos: Entity, color: ANSIBuf) -> &mut Self { match self.colors.0.as_mut() { Some(map) => map.insert(pos, color), None => { let mut colors = EntityMap::default(); colors.insert(pos, color); self.colors = ColorMap(Some(colors)); } } self } /// Set a list of colors. pub fn set_colors(&mut self, colors: EntityMap) -> &mut Self { self.colors = ColorMap(Some(colors)); self } /// Remove a color for a given cell. pub fn remove_color(&mut self, pos: Entity) -> &mut Self { if let Some(colors) = self.colors.0.as_mut() { colors.remove(pos); } self } /// Returns a list of colors. pub fn get_colors(&self) -> &ColorMap { &self.colors } /// Returns an inner config. pub fn into_inner(self) -> SpannedConfig { self.config } } impl Deref for ColoredConfig { type Target = SpannedConfig; fn deref(&self) -> &Self::Target { &self.config } } impl DerefMut for ColoredConfig { fn deref_mut(&mut self) -> &mut Self::Target { &mut self.config } } impl From for ColoredConfig { fn from(value: SpannedConfig) -> Self { Self::new(value) } } impl AsRef for ColoredConfig { fn as_ref(&self) -> &SpannedConfig { &self.config } } /// A colors structure for [`ColoredConfig`]. #[derive(Debug, Default, PartialEq, Eq, Clone)] pub struct ColorMap(Option>); impl ColorMap { /// Checks if any colors is set on. pub fn is_empty(&self) -> bool { self.0.is_none() } } impl crate::grid::colors::Colors for ColorMap { type Color = ANSIBuf; fn get_color(&self, pos: Position) -> Option<&Self::Color> { self.0.as_ref().map(|map| map.get(pos)) } fn is_empty(&self) -> bool { self.0 .as_ref() .map(|cfg| cfg.is_empty() && cfg.as_ref().is_empty()) .unwrap_or(true) } } tabled-0.18.0/src/grid/config/compact_config.rs000064400000000000000000000000631046102023000174460ustar 00000000000000pub use papergrid::config::compact::CompactConfig; tabled-0.18.0/src/grid/config/compact_multiline_config.rs000064400000000000000000000117731046102023000215420ustar 00000000000000use crate::grid::{ ansi::ANSIStr, config::{ AlignmentHorizontal, AlignmentVertical, Borders, CompactConfig, Formatting, Indent, Sides, }, }; /// A [`CompactConfig`] based configuration plus vertical alignment and formatting options. #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] pub struct CompactMultilineConfig { config: CompactConfig, alignment_vertical: AlignmentVertical, formatting: Formatting, } impl CompactMultilineConfig { /// Create a new [`CompactMultilineConfig`]. pub const fn new() -> Self { Self { config: CompactConfig::new(), alignment_vertical: AlignmentVertical::Top, formatting: Formatting::new(false, false, false), } } /// Create a new [`CompactMultilineConfig`]. pub const fn from_compact(config: CompactConfig) -> Self { Self { config, alignment_vertical: AlignmentVertical::Top, formatting: Formatting::new(false, false, false), } } /// Set a horizontal alignment. pub fn set_alignment_vertical(&mut self, alignment: AlignmentVertical) { self.alignment_vertical = alignment } /// Get a alignment horizontal. pub const fn get_alignment_vertical(&self) -> AlignmentVertical { self.alignment_vertical } /// Set grid margin. pub fn set_margin(&mut self, margin: Sides) { self.config = self.config.set_margin(margin); } /// Returns a grid margin. pub const fn get_margin(&self) -> &Sides { self.config.get_margin() } /// Set the [`Borders`] value as correct one. pub fn set_borders(&mut self, borders: Borders) { self.config = self.config.set_borders(borders); } /// Returns a current [`Borders`] structure. pub const fn get_borders(&self) -> &Borders { self.config.get_borders() } /// Returns a current [`Borders`] structure. pub const fn get_borders_color(&self) -> &Borders> { self.config.get_borders_color() } /// Set a padding to a given cells. pub fn set_padding(&mut self, padding: Sides) { self.config = self.config.set_padding(padding) } /// Get a padding for a given. pub const fn get_padding(&self) -> &Sides { self.config.get_padding() } /// Set a horizontal alignment. pub fn set_alignment_horizontal(&mut self, alignment: AlignmentHorizontal) { self.config = self.config.set_alignment_horizontal(alignment) } /// Get a alignment horizontal. pub const fn get_alignment_horizontal(&self) -> AlignmentHorizontal { self.config.get_alignment_horizontal() } /// Sets colors of border carcass on the grid. pub fn set_borders_color(&mut self, borders: Borders>) { self.config = self.config.set_borders_color(borders) } /// Set colors for a margin. pub fn set_margin_color(&mut self, color: Sides>) { self.config = self.config.set_margin_color(color) } /// Returns a margin color. pub const fn get_margin_color(&self) -> &Sides> { self.config.get_margin_color() } /// Set a padding color to all cells. pub fn set_padding_color(&mut self, color: Sides>) { self.config = self.config.set_padding_color(color) } /// get a padding color. pub const fn get_padding_color(&self) -> &Sides> { self.config.get_padding_color() } /// Set formatting. pub fn set_formatting(&mut self, formatting: Formatting) { self.formatting = formatting } /// Get formatting. pub const fn get_formatting(&self) -> Formatting { self.formatting } } impl Default for CompactMultilineConfig { fn default() -> Self { Self { config: Default::default(), alignment_vertical: AlignmentVertical::Top, formatting: Formatting::default(), } } } impl From for CompactConfig { fn from(cfg: CompactMultilineConfig) -> Self { cfg.config } } impl From for CompactMultilineConfig { fn from(config: CompactConfig) -> Self { Self { config, alignment_vertical: AlignmentVertical::Top, formatting: Formatting::default(), } } } #[cfg(feature = "std")] impl From for crate::grid::config::SpannedConfig { fn from(compact: CompactMultilineConfig) -> Self { use crate::grid::config::Entity::*; use crate::grid::config::SpannedConfig; let mut cfg = SpannedConfig::from(compact.config); cfg.set_alignment_vertical(Global, compact.alignment_vertical); cfg.set_line_alignment(Global, compact.formatting.allow_lines_alignment); cfg.set_trim_horizontal(Global, compact.formatting.horizontal_trim); cfg.set_trim_vertical(Global, compact.formatting.vertical_trim); cfg } } tabled-0.18.0/src/grid/config/mod.rs000064400000000000000000000013521046102023000152540ustar 00000000000000//! Module contains a list of configs for varios tables/grids. #[cfg(feature = "std")] mod colored_config; #[cfg(feature = "std")] mod spanned_config; mod compact_config; mod compact_multiline_config; pub use papergrid::config::{ AlignmentHorizontal, AlignmentVertical, Border, Borders, Entity, EntityIterator, Formatting, HorizontalLine, Indent, Position, Sides, VerticalLine, }; #[cfg(feature = "std")] #[cfg_attr(docsrs, doc(cfg(feature = "std")))] pub use spanned_config::{EntityMap, Offset, SpannedConfig}; #[cfg(feature = "std")] #[cfg_attr(docsrs, doc(cfg(feature = "std")))] pub use colored_config::{ColorMap, ColoredConfig}; pub use compact_config::CompactConfig; pub use compact_multiline_config::CompactMultilineConfig; tabled-0.18.0/src/grid/config/spanned_config.rs000064400000000000000000000001101046102023000174410ustar 00000000000000pub use papergrid::config::spanned::{EntityMap, Offset, SpannedConfig}; tabled-0.18.0/src/grid/dimension/complete_dimension.rs000064400000000000000000000072411046102023000210750ustar 00000000000000use std::borrow::Cow; use crate::grid::{ config::{ColoredConfig, SpannedConfig}, dimension::{Dimension, Estimate, SpannedGridDimension}, records::{IntoRecords, Records}, }; /// CompleteDimension is a [`Dimension`] implementation for a [`Table`] /// /// [`Table`]: crate::Table #[derive(Debug, Default, PartialEq, Eq, PartialOrd, Ord, Clone)] pub struct CompleteDimension<'a> { width: Option>, height: Option>, } impl CompleteDimension<'_> { /// Checks whether is the dimensions is set. pub fn is_complete(&self) -> bool { self.width.is_some() && self.height.is_some() } /// Checks whether is nothing was set. pub fn is_empty(&self) -> bool { self.width.is_none() && self.height.is_none() } /// Set column widths. /// /// In general the method is only considered to be useful to a [`TableOption`]. /// /// BE CAREFUL WITH THIS METHOD as it supposed that the content is not bigger than the provided widths. /// /// [`TableOption`]: crate::settings::TableOption pub fn set_widths(&mut self, columns: Vec) -> bool { self.width = Some(Cow::Owned(columns)); true } /// Set rows heights. /// /// In general the method is only considered to be useful to a [`TableOption`]. /// /// BE CAREFUL WITH THIS METHOD as it supposed that the content is not bigger than the provided heights. /// /// [`TableOption`]: crate::settings::TableOption pub fn set_heights(&mut self, rows: Vec) -> bool { self.height = Some(Cow::Owned(rows)); true } /// Force width estimation. pub fn clear_width(&mut self) { self.width = None; } /// Force height estimation. pub fn clear_height(&mut self) { self.height = None; } /// Copies a reference from self. pub fn from_origin(&self) -> CompleteDimension<'_> { let width = self.width.as_deref().map(Cow::Borrowed); let height = self.height.as_deref().map(Cow::Borrowed); CompleteDimension { width, height } } } impl Dimension for CompleteDimension<'_> { fn get_width(&self, column: usize) -> usize { let width = self .width .as_ref() .expect("It must always be Some at this point"); width[column] } fn get_height(&self, row: usize) -> usize { let height = self .height .as_ref() .expect("It must always be Some at this point"); height[row] } } impl Estimate for CompleteDimension<'_> where R: Records, ::Cell: AsRef, { fn estimate(&mut self, records: R, cfg: &SpannedConfig) { match (self.width.is_some(), self.height.is_some()) { (true, true) => {} (true, false) => { self.height = Some(Cow::Owned(SpannedGridDimension::height(records, cfg))); } (false, true) => { self.width = Some(Cow::Owned(SpannedGridDimension::width(records, cfg))); } (false, false) => { let mut dims = SpannedGridDimension::default(); dims.estimate(records, cfg); let (width, height) = dims.get_values(); self.width = Some(Cow::Owned(width)); self.height = Some(Cow::Owned(height)); } } } } impl Estimate for CompleteDimension<'_> where R: Records, ::Cell: AsRef, { fn estimate(&mut self, records: R, cfg: &ColoredConfig) { Estimate::estimate(self, records, cfg.as_ref()) } } tabled-0.18.0/src/grid/dimension/complete_dimension_vec_records.rs000064400000000000000000000112751046102023000234550ustar 00000000000000use std::borrow::Cow; use crate::grid::{ config::{ColoredConfig, SpannedConfig}, dimension::{Dimension, Estimate, SpannedVecRecordsDimension}, records::vec_records::{Cell, VecRecords}, }; /// CompleteDimension is a [`Dimension`] implementation for a [`Table`] /// /// [`Table`]: crate::Table #[derive(Debug, Default, PartialEq, Eq, PartialOrd, Ord, Clone)] pub struct CompleteDimensionVecRecords<'a> { width: Option>, height: Option>, } impl CompleteDimensionVecRecords<'_> { /// Checks whether is the dimensions is set. pub fn is_complete(&self) -> bool { self.width.is_some() && self.height.is_some() } /// Checks whether is nothing was set. pub fn is_empty(&self) -> bool { self.width.is_none() && self.height.is_none() } /// Set column widths. /// /// In general the method is only considered to be useful to a [`TableOption`]. /// /// BE CAREFUL WITH THIS METHOD as it supposed that the content is not bigger than the provided widths. /// /// [`TableOption`]: crate::settings::TableOption pub fn set_widths(&mut self, columns: Vec) { self.width = Some(Cow::Owned(columns)); } /// Get column widths. /// /// In general the method is only considered to be useful to a [`TableOption`]. /// /// BE CAREFUL WITH THIS METHOD as it supposed that the content is not bigger than the provided widths. /// /// [`TableOption`]: crate::settings::TableOption pub fn get_widths(&self) -> Option<&'_ [usize]> { self.width.as_deref() } /// Set rows heights. /// /// In general the method is only considered to be useful to a [`TableOption`]. /// /// BE CAREFUL WITH THIS METHOD as it supposed that the content is not bigger than the provided heights. /// /// [`TableOption`]: crate::settings::TableOption pub fn set_heights(&mut self, rows: Vec) { self.height = Some(Cow::Owned(rows)); } /// Get row heights. /// /// In general the method is only considered to be useful to a [`TableOption`]. /// /// BE CAREFUL WITH THIS METHOD as it supposed that the content is not bigger than the provided widths. /// /// [`TableOption`]: crate::settings::TableOption pub fn get_heights(&self) -> Option<&'_ [usize]> { self.height.as_deref() } /// Force width estimation. pub fn clear_width(&mut self) { self.width = None; } /// Force height estimation. pub fn clear_height(&mut self) { self.height = None; } /// Copies a reference from self. pub fn from_origin(&self) -> CompleteDimensionVecRecords<'_> { let width = self.width.as_deref().map(Cow::Borrowed); let height = self.height.as_deref().map(Cow::Borrowed); CompleteDimensionVecRecords { width, height } } /// Copies a reference from self. pub fn into_inner(self) -> (Option>, Option>) { let width = self.width.map(|list| list.into_owned()); let height = self.height.map(|list| list.into_owned()); (width, height) } } impl Dimension for CompleteDimensionVecRecords<'_> { fn get_width(&self, column: usize) -> usize { let width = self .width .as_ref() .expect("It must always be Some at this point"); width[column] } fn get_height(&self, row: usize) -> usize { let height = self .height .as_ref() .expect("It must always be Some at this point"); height[row] } } impl + Cell> Estimate<&VecRecords, SpannedConfig> for CompleteDimensionVecRecords<'_> { fn estimate(&mut self, records: &VecRecords, cfg: &SpannedConfig) { match (self.width.is_some(), self.height.is_some()) { (true, true) => {} (true, false) => { self.height = Some(Cow::Owned(SpannedVecRecordsDimension::height(records, cfg))); } (false, true) => { self.width = Some(Cow::Owned(SpannedVecRecordsDimension::width(records, cfg))); } (false, false) => { let mut dims = SpannedVecRecordsDimension::default(); dims.estimate(records, cfg); let (width, height) = dims.get_values(); self.width = Some(Cow::Owned(width)); self.height = Some(Cow::Owned(height)); } } } } impl + Cell> Estimate<&VecRecords, ColoredConfig> for CompleteDimensionVecRecords<'_> { fn estimate(&mut self, records: &VecRecords, cfg: &ColoredConfig) { self.estimate(records, cfg.as_ref()) } } tabled-0.18.0/src/grid/dimension/const_dimension.rs000064400000000000000000000040311046102023000204050ustar 00000000000000//! Module contains a dimension estimator for [`CompactTable`] //! //! [`CompactTable`]: crate::tables::CompactTable use crate::grid::dimension::{Dimension, Estimate}; /// A constant size dimension or a value dimension. #[derive(Debug, Clone, Copy)] pub struct ConstDimension { height: ConstSize, width: ConstSize, } impl ConstDimension { /// Returns a new dimension object with a given estimates. pub const fn new(width: ConstSize, height: ConstSize) -> Self { Self { width, height } } } impl Dimension for ConstDimension { fn get_width(&self, column: usize) -> usize { match self.width { ConstSize::List(list) => list[column], ConstSize::Value(val) => val, } } fn get_height(&self, row: usize) -> usize { match self.height { ConstSize::List(list) => list[row], ConstSize::Value(val) => val, } } } impl From> for (ConstSize, ConstSize) { fn from(value: ConstDimension) -> Self { (value.width, value.height) } } impl Estimate for ConstDimension { fn estimate(&mut self, _: R, _: &D) {} } /// Const size represents either a const array values or a single value which responsible for the whole list. #[derive(Debug, Clone, Copy)] pub enum ConstSize { /// A constant array of estimates. List([usize; N]), /// A value which act as a single estimate for all entries. Value(usize), } impl From for ConstSize<0> { fn from(value: usize) -> Self { ConstSize::Value(value) } } impl From<[usize; N]> for ConstSize { fn from(value: [usize; N]) -> Self { ConstSize::List(value) } } tabled-0.18.0/src/grid/dimension/mod.rs000064400000000000000000000020131046102023000157670ustar 00000000000000//! Module contains a list of implementations of [`Estimate`] and [`Dimension`]. mod const_dimension; mod pool_table_dimension; #[cfg(feature = "std")] mod complete_dimension; #[cfg(feature = "std")] mod complete_dimension_vec_records; #[cfg(feature = "std")] mod peekable_dimension; #[cfg(feature = "std")] mod static_dimension; #[cfg(feature = "std")] #[cfg_attr(docsrs, doc(cfg(feature = "std")))] pub use self::{ complete_dimension::CompleteDimension, complete_dimension_vec_records::CompleteDimensionVecRecords, peekable_dimension::PeekableDimension, static_dimension::{DimensionValue, StaticDimension}, }; pub use const_dimension::{ConstDimension, ConstSize}; pub use papergrid::dimension::{Dimension, Estimate}; pub use pool_table_dimension::{DimensionPriority, PoolTableDimension}; #[cfg(feature = "std")] #[cfg_attr(docsrs, doc(cfg(feature = "std")))] pub use papergrid::dimension::{ compact::CompactGridDimension, spanned::SpannedGridDimension, spanned_vec_records::SpannedVecRecordsDimension, }; tabled-0.18.0/src/grid/dimension/peekable_dimension.rs000064400000000000000000000217151046102023000210370ustar 00000000000000use crate::grid::{ config::SpannedConfig, dimension::{Dimension, Estimate}, records::vec_records::{Text, VecRecords}, records::Records, }; /// PeekableDimension is a [`Dimension`] implementation for a [`Table`] /// /// [`Table`]: crate::Table #[derive(Debug, Default, Clone)] pub struct PeekableDimension { width: Vec, height: Vec, } impl PeekableDimension { /// Calculates height of rows. pub fn height>(records: &VecRecords>, cfg: &SpannedConfig) -> Vec { estimation::build_height(records, cfg) } /// Calculates width of columns. pub fn width>(records: &VecRecords>, cfg: &SpannedConfig) -> Vec { estimation::build_width(records, cfg) } /// Return width and height lists. pub fn get_values(self) -> (Vec, Vec) { (self.width, self.height) } } impl Dimension for PeekableDimension { fn get_width(&self, column: usize) -> usize { self.width[column] } fn get_height(&self, row: usize) -> usize { self.height[row] } } impl Estimate<&VecRecords>, SpannedConfig> for PeekableDimension where T: AsRef, { fn estimate(&mut self, records: &VecRecords>, cfg: &SpannedConfig) { let (width, height) = estimation::build_dimensions(records, cfg); self.width = width; self.height = height; } } mod estimation { use core::cmp::{max, Ordering}; use std::collections::HashMap; use crate::grid::{config::Position, records::vec_records::Cell}; use super::*; pub(super) fn build_dimensions>( records: &VecRecords>, cfg: &SpannedConfig, ) -> (Vec, Vec) { 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).into(); if !cfg.is_cell_visible(pos) { continue; } let height = cell.count_lines(); let width = cell.width(); let pad = cfg.get_padding(pos); 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 => { let _ = vspans.insert(pos, (n, width)); } _ => widths[col] = max(widths[col], width), } match cfg.get_row_span(pos) { Some(n) if n > 1 => { let _ = 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, heights: &mut [usize], ) { if spans.is_empty() { return; } let mut spans_ordered = spans.iter().map(|(k, v)| (k, *v)).collect::>(); spans_ordered.sort_unstable_by(|(arow, acol), (brow, bcol)| match arow.cmp(brow) { Ordering::Equal => acol.cmp(bcol), ord => ord, }); for (p, (span, height)) in spans_ordered { adjust_row_range(cfg, height, len, p.row(), p.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::(); 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, 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, *v)).collect::>(); 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 (p, (span, width)) in spans_ordered { adjust_column_range(cfg, width, len, p.col(), p.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 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::(); 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() } pub(super) fn build_height>( records: &VecRecords>, cfg: &SpannedConfig, ) -> Vec { 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).into(); if !cfg.is_cell_visible(pos) { continue; } let height = cell.count_lines(); match cfg.get_row_span(pos) { Some(n) if n > 1 => { let _ = hspans.insert(pos, (n, height)); } _ => row_height = max(row_height, height), } } heights.push(row_height); } adjust_hspans(cfg, heights.len(), &hspans, &mut heights); heights } pub(super) fn build_width>( records: &VecRecords>, cfg: &SpannedConfig, ) -> Vec { 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).into(); if !cfg.is_cell_visible(pos) { continue; } let width = cell.width(); match cfg.get_column_span(pos) { Some(n) if n > 1 => { let _ = vspans.insert(pos, (n, width)); } _ => widths[col] = max(widths[col], width), } } } adjust_vspans(cfg, count_columns, &vspans, &mut widths); widths } } tabled-0.18.0/src/grid/dimension/pool_table_dimension.rs000064400000000000000000000020611046102023000214000ustar 00000000000000/// PoolTableDimension is a dimension resolve strategy for [`PoolTable`] /// /// [`PoolTable`]: crate::tables::PoolTable #[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy)] pub struct PoolTableDimension { width: DimensionPriority, height: DimensionPriority, } impl PoolTableDimension { /// Creates a new object. pub fn new(width: DimensionPriority, height: DimensionPriority) -> Self { Self { width, height } } /// Return a width priority. pub fn width(&self) -> DimensionPriority { self.width } /// Return a height priority. pub fn height(&self) -> DimensionPriority { self.height } } /// A control of width/height logic for situations where we must increase some cell to align columns/row. #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] pub enum DimensionPriority { /// Increase first cell width/height in a row/column. First, /// Increase last cell width/height in a row/column. Last, /// Increase cells width/height 1 by 1 in a row/column. List, } tabled-0.18.0/src/grid/dimension/static_dimension.rs000064400000000000000000000030201046102023000205430ustar 00000000000000use crate::grid::dimension::{Dimension, Estimate}; /// A constant dimension. #[derive(Debug, Clone)] pub struct StaticDimension { width: DimensionValue, height: DimensionValue, } impl StaticDimension { /// Creates a constant dimension. pub fn new(width: DimensionValue, height: DimensionValue) -> Self { Self { width, height } } } impl From for (DimensionValue, DimensionValue) { fn from(value: StaticDimension) -> Self { (value.width, value.height) } } impl Dimension for StaticDimension { fn get_width(&self, column: usize) -> usize { self.width.get(column) } fn get_height(&self, row: usize) -> usize { self.height.get(row) } } impl Estimate for StaticDimension { fn estimate(&mut self, _: R, _: &C) {} } /// A dimension value. #[derive(Debug, Clone)] pub enum DimensionValue { /// Const width value. Exact(usize), /// A list of width values for columns. List(Vec), /// A list of width values for columns and a value for the rest. Partial(Vec, usize), } impl DimensionValue { /// Get a width by column. pub fn get(&self, col: usize) -> usize { match self { DimensionValue::Exact(val) => *val, DimensionValue::List(cols) => cols[col], DimensionValue::Partial(cols, val) => { if cols.len() > col { cols[col] } else { *val } } } } } tabled-0.18.0/src/grid/mod.rs000064400000000000000000000012211046102023000140020ustar 00000000000000//! Module is responsible for tables underlyign grid. //! //! It might be used when implementing your own [`TableOption`] and [`CellOption`]. //! //! [`TableOption`]: crate::settings::TableOption //! [`CellOption`]: crate::settings::CellOption pub mod config; pub mod dimension; pub mod records; pub use papergrid::ansi; pub use papergrid::colors; pub use papergrid::util; pub use papergrid::grid::compact::CompactGrid; #[cfg(feature = "std")] #[cfg_attr(docsrs, doc(cfg(feature = "std")))] pub use papergrid::grid::iterable::Grid; #[cfg(feature = "std")] #[cfg_attr(docsrs, doc(cfg(feature = "std")))] pub use papergrid::grid::peekable::PeekableGrid; tabled-0.18.0/src/grid/records/empty_records.rs000064400000000000000000000016461046102023000175560ustar 00000000000000//! An empty [`Records`] implementation. use core::iter::{repeat, Repeat, Take}; use super::Records; /// Empty representation of [`Records`]. #[derive(Debug, Default, Clone)] pub struct EmptyRecords { rows: usize, cols: usize, } impl EmptyRecords { /// Constructs an empty representation of [`Records`] with a given shape. pub fn new(rows: usize, cols: usize) -> Self { Self { rows, cols } } } impl From<(usize, usize)> for EmptyRecords { fn from((count_rows, count_columns): (usize, usize)) -> Self { Self::new(count_rows, count_columns) } } impl Records for EmptyRecords { type Iter = Take>>>; fn iter_rows(self) -> Self::Iter { repeat(repeat("").take(self.cols)).take(self.rows) } fn count_columns(&self) -> usize { self.cols } fn hint_count_rows(&self) -> Option { Some(self.rows) } } tabled-0.18.0/src/grid/records/into_records/buf_records.rs000064400000000000000000000050611046102023000216610ustar 00000000000000//! A module contains [`BufRecords`] iterator. use crate::grid::records::IntoRecords; /// BufRecords inspects [`IntoRecords`] iterator and keeps read data buffered. /// So it can be checking before hand. #[derive(Debug)] pub struct BufRecords { iter: I, buf: Vec>, } impl BufRecords<(), ()> { /// Creates new [`BufRecords`] structure, filling the buffer. pub fn new( records: I, sniff: usize, ) -> BufRecords<::IntoIter, I::Cell> where I: IntoRecords, { let mut buf = vec![]; let mut iter = records.iter_rows().into_iter(); for data in iter.by_ref().take(sniff) { let data = data.into_iter().collect::>(); buf.push(data) } BufRecords { iter, buf } } } impl BufRecords { /// Returns a slice of a keeping buffer. pub fn as_slice(&self) -> &[Vec] { &self.buf } } impl IntoRecords for BufRecords::Item> where I: Iterator, I::Item: IntoIterator, { type Cell = ::Item; type IterColumns = BufIterator<::IntoIter, Self::Cell>; type IterRows = BufRecordsIter; fn iter_rows(self) -> Self::IterRows { BufRecordsIter { iter: self.iter, buf: self.buf.into_iter(), } } } /// A row iterator for [`BufRecords`] #[derive(Debug)] pub struct BufRecordsIter { iter: I, buf: std::vec::IntoIter>, } impl Iterator for BufRecordsIter::Item> where I: Iterator, I::Item: IntoIterator, { type Item = BufIterator<::IntoIter, ::Item>; fn next(&mut self) -> Option { match self.buf.next() { Some(i) => Some(BufIterator::Buffered(i.into_iter())), None => self .iter .next() .map(|i| BufIterator::Iterator(i.into_iter())), } } } /// An iterator over some iterator or allocated buffer. #[derive(Debug)] pub enum BufIterator { /// Allocated iterator. Buffered(std::vec::IntoIter), /// Given iterator. Iterator(I), } impl Iterator for BufIterator where I: Iterator, { type Item = I::Item; fn next(&mut self) -> Option { match self { BufIterator::Buffered(iter) => iter.next(), BufIterator::Iterator(iter) => iter.next(), } } } tabled-0.18.0/src/grid/records/into_records/either_string.rs000064400000000000000000000010011046102023000222200ustar 00000000000000//! A module with a utility enum [`EitherString`]. /// Either allocated string or some type which can be used as a string. #[derive(Debug)] pub enum EitherString { /// Allocated string. Owned(String), /// Something which can be used as a string. Some(T), } impl AsRef for EitherString where T: AsRef, { fn as_ref(&self) -> &str { match self { EitherString::Owned(s) => s.as_ref(), EitherString::Some(s) => s.as_ref(), } } } tabled-0.18.0/src/grid/records/into_records/limit_column_records.rs000064400000000000000000000034111046102023000235750ustar 00000000000000//! The module contains [`LimitColumns`] records iterator. use crate::grid::records::IntoRecords; /// An iterator which limits amount of columns. #[derive(Debug)] pub struct LimitColumns { records: I, limit: usize, } impl LimitColumns<()> { /// Creates new [`LimitColumns`]. pub fn new(records: I, limit: usize) -> LimitColumns { LimitColumns { records, limit } } } impl IntoRecords for LimitColumns where I: IntoRecords, { type Cell = I::Cell; type IterColumns = LimitColumnsColumnsIter<::IntoIter>; type IterRows = LimitColumnsIter<::IntoIter>; fn iter_rows(self) -> Self::IterRows { LimitColumnsIter { iter: self.records.iter_rows().into_iter(), limit: self.limit, } } } /// An iterator over rows for [`LimitColumns`]. #[derive(Debug)] pub struct LimitColumnsIter { iter: I, limit: usize, } impl Iterator for LimitColumnsIter where I: Iterator, I::Item: IntoIterator, { type Item = LimitColumnsColumnsIter<::IntoIter>; fn next(&mut self) -> Option { let iter = self.iter.next()?; let iter = LimitColumnsColumnsIter { iter: iter.into_iter(), limit: self.limit, }; Some(iter) } } /// An iterator over columns for [`LimitColumns`]. #[derive(Debug)] pub struct LimitColumnsColumnsIter { iter: I, limit: usize, } impl Iterator for LimitColumnsColumnsIter where I: Iterator, { type Item = I::Item; fn next(&mut self) -> Option { if self.limit == 0 { return None; } self.limit -= 1; self.iter.next() } } tabled-0.18.0/src/grid/records/into_records/limit_row_records.rs000064400000000000000000000023021046102023000231050ustar 00000000000000//! The module contains [`LimitRows`] records iterator. use crate::grid::records::IntoRecords; /// [`LimitRows`] is an records iterator which limits amount of rows. #[derive(Debug)] pub struct LimitRows { records: I, limit: usize, } impl LimitRows<()> { /// Creates new [`LimitRows`] iterator. pub fn new(records: I, limit: usize) -> LimitRows { LimitRows { records, limit } } } impl IntoRecords for LimitRows where I: IntoRecords, { type Cell = I::Cell; type IterColumns = I::IterColumns; type IterRows = LimitRowsIter<::IntoIter>; fn iter_rows(self) -> Self::IterRows { LimitRowsIter { iter: self.records.iter_rows().into_iter(), limit: self.limit, } } } /// A rows iterator for [`LimitRows`] #[derive(Debug)] pub struct LimitRowsIter { iter: I, limit: usize, } impl Iterator for LimitRowsIter where I: Iterator, I::Item: IntoIterator, { type Item = I::Item; fn next(&mut self) -> Option { if self.limit == 0 { return None; } self.limit -= 1; self.iter.next() } } tabled-0.18.0/src/grid/records/into_records/mod.rs000064400000000000000000000013751046102023000201470ustar 00000000000000//! The module contains a list of helpers for [`IntoRecords`] //! //! [`IntoRecords`]: crate::grid::records::IntoRecords pub mod limit_column_records; pub mod limit_row_records; #[cfg(feature = "std")] #[cfg_attr(docsrs, doc(cfg(feature = "std")))] pub mod buf_records; #[cfg(feature = "std")] #[cfg_attr(docsrs, doc(cfg(feature = "std")))] pub mod either_string; #[cfg(feature = "std")] #[cfg_attr(docsrs, doc(cfg(feature = "std")))] pub mod truncate_records; #[cfg(feature = "std")] #[cfg_attr(docsrs, doc(cfg(feature = "std")))] pub use buf_records::BufRecords; #[cfg(feature = "std")] #[cfg_attr(docsrs, doc(cfg(feature = "std")))] pub use truncate_records::TruncateContent; pub use limit_column_records::LimitColumns; pub use limit_row_records::LimitRows; tabled-0.18.0/src/grid/records/into_records/truncate_records.rs000064400000000000000000000052011046102023000227260ustar 00000000000000//! The module contains [`TruncateContent`] records iterator. use crate::{ grid::dimension::Dimension, grid::records::into_records::either_string::EitherString, grid::records::IntoRecords, grid::util::string::get_text_width, settings::width::Truncate, }; /// A records iterator which truncates all cells to a given width. #[derive(Debug)] pub struct TruncateContent { records: I, dimension: D, } impl TruncateContent<(), ()> { /// Creates new [`TruncateContent`] object. pub fn new(records: I, dimension: D) -> TruncateContent { TruncateContent { records, dimension } } } impl IntoRecords for TruncateContent where I: IntoRecords, I::Cell: AsRef, D: Clone + Dimension, { type Cell = EitherString; type IterColumns = TruncateContentColumnsIter<::IntoIter, D>; type IterRows = TruncateContentIter<::IntoIter, D>; fn iter_rows(self) -> Self::IterRows { TruncateContentIter { iter: self.records.iter_rows().into_iter(), dimension: self.dimension.clone(), } } } /// A row iterator for [`TruncateContent`]. #[derive(Debug)] pub struct TruncateContentIter { iter: I, dimension: D, } impl Iterator for TruncateContentIter where I: Iterator, I::Item: IntoIterator, D: Clone, { type Item = TruncateContentColumnsIter<::IntoIter, D>; fn next(&mut self) -> Option { let iter = self.iter.next()?; let iter = TruncateContentColumnsIter { iter: iter.into_iter(), iter_column: 0, dimension: self.dimension.clone(), }; Some(iter) } } /// A column iterator for [`TruncateContent`]. #[derive(Debug)] pub struct TruncateContentColumnsIter { iter: I, dimension: D, iter_column: usize, } impl Iterator for TruncateContentColumnsIter where I: Iterator, I::Item: AsRef, D: Dimension, { type Item = EitherString; fn next(&mut self) -> Option { let text = self.iter.next()?; let text_ref = text.as_ref(); let width = self.dimension.get_width(self.iter_column); self.iter_column += 1; let text_width = get_text_width(text_ref); let is_small = text_width <= width; if is_small { Some(EitherString::Some(text)) } else { let text = Truncate::truncate(text_ref, width); let text = text.into_owned(); Some(EitherString::Owned(text)) } } } tabled-0.18.0/src/grid/records/mod.rs000064400000000000000000000011301046102023000154420ustar 00000000000000//! The module contains [`Records`], [`ExactRecords`], [`RecordsMut`], [`Resizable`] traits //! and its implementations. //! //! Also it provides a list of helpers for a user built [`Records`] via [`into_records`]. mod empty_records; mod records_mut; mod resizable; pub mod into_records; pub use empty_records::EmptyRecords; pub use papergrid::records::{ExactRecords, IntoRecords, IterRecords, PeekableRecords, Records}; pub use records_mut::RecordsMut; pub use resizable::Resizable; #[cfg(feature = "std")] #[cfg_attr(docsrs, doc(cfg(feature = "std")))] pub use papergrid::records::vec_records; tabled-0.18.0/src/grid/records/records_mut.rs000064400000000000000000000016521046102023000172220ustar 00000000000000use crate::grid::config::Position; #[cfg(feature = "std")] use crate::grid::records::vec_records::{Text, VecRecords}; /// A [`Records`] representation which can modify cell by (row, column) index. /// /// [`Records`]: crate::grid::records::Records pub trait RecordsMut { /// Sets a text to a given cell by index. fn set(&mut self, pos: Position, text: Text); } impl RecordsMut for &'_ mut T where T: RecordsMut, { fn set(&mut self, pos: Position, text: Text) { T::set(self, pos, text) } } #[cfg(feature = "std")] impl RecordsMut for VecRecords> { fn set(&mut self, pos: Position, text: String) { self[pos.row()][pos.col()] = Text::new(text); } } #[cfg(feature = "std")] impl RecordsMut<&str> for VecRecords> { fn set(&mut self, p: Position, text: &str) { self[p.row()][p.col()] = Text::new(text.to_string()); } } tabled-0.18.0/src/grid/records/resizable.rs000064400000000000000000000135701046102023000166560ustar 00000000000000use crate::grid::config::Position; #[cfg(feature = "std")] use crate::grid::records::vec_records::VecRecords; /// A records representation which can be modified by moving rows/columns around. pub trait Resizable { /// Swap cells with one another. fn swap(&mut self, lhs: Position, rhs: Position); /// Swap rows with one another. fn swap_row(&mut self, lhs: usize, rhs: usize); /// Swap columns with one another. fn swap_column(&mut self, lhs: usize, rhs: usize); /// Adds a new row to a data set. fn push_row(&mut self); /// Adds a new column to a data set. fn push_column(&mut self); /// Removes a row from a data set by index. fn remove_row(&mut self, row: usize); /// Removes a column from a data set by index. fn remove_column(&mut self, column: usize); /// Inserts a row at index. fn insert_row(&mut self, row: usize); /// Inserts column at index. fn insert_column(&mut self, column: usize); } impl Resizable for &'_ mut T where T: Resizable, { fn swap(&mut self, lhs: Position, rhs: Position) { T::swap(self, lhs, rhs) } fn swap_row(&mut self, lhs: usize, rhs: usize) { T::swap_row(self, lhs, rhs) } fn swap_column(&mut self, lhs: usize, rhs: usize) { T::swap_column(self, lhs, rhs) } fn push_row(&mut self) { T::push_row(self) } fn push_column(&mut self) { T::push_column(self) } fn remove_row(&mut self, row: usize) { T::remove_row(self, row) } fn remove_column(&mut self, column: usize) { T::remove_column(self, column) } fn insert_row(&mut self, row: usize) { T::insert_row(self, row) } fn insert_column(&mut self, column: usize) { T::insert_column(self, column) } } #[cfg(feature = "std")] impl Resizable for Vec> where T: Default + Clone, { fn swap(&mut self, lhs: Position, rhs: Position) { if lhs == rhs { return; } let t = std::mem::take(&mut self[lhs.row()][lhs.col()]); let t = std::mem::replace(&mut self[rhs.row()][rhs.col()], t); let _ = std::mem::replace(&mut self[lhs.row()][lhs.col()], t); } fn swap_row(&mut self, lhs: usize, rhs: usize) { let t = std::mem::take(&mut self[lhs]); let t = std::mem::replace(&mut self[rhs], t); let _ = std::mem::replace(&mut self[lhs], t); } fn swap_column(&mut self, lhs: usize, rhs: usize) { for row in self.iter_mut() { row.swap(lhs, rhs); } } fn push_row(&mut self) { let count_columns = self.first().map(|l| l.len()).unwrap_or(0); self.push(vec![T::default(); count_columns]); } fn push_column(&mut self) { for row in self.iter_mut() { row.push(T::default()); } } fn remove_row(&mut self, row: usize) { let _ = self.remove(row); } fn remove_column(&mut self, column: usize) { for row in self.iter_mut() { let _ = row.remove(column); } } fn insert_row(&mut self, row: usize) { let count_columns = self.first().map(|l| l.len()).unwrap_or(0); self.insert(row, vec![T::default(); count_columns]); } fn insert_column(&mut self, column: usize) { for row in self { row.insert(column, T::default()); } } } #[cfg(feature = "std")] impl Resizable for VecRecords where T: Default + Clone, { fn swap(&mut self, lhs: Position, rhs: Position) { if lhs == rhs { return; } let t = std::mem::take(&mut self[lhs.row()][lhs.col()]); let t = std::mem::replace(&mut self[rhs.row()][rhs.col()], t); let _ = std::mem::replace(&mut self[lhs.row()][lhs.col()], t); } fn swap_row(&mut self, lhs: usize, rhs: usize) { let t = std::mem::take(&mut self[lhs]); let t = std::mem::replace(&mut self[rhs], t); let _ = std::mem::replace(&mut self[lhs], t); } fn swap_column(&mut self, lhs: usize, rhs: usize) { for row in self.iter_mut() { row.swap(lhs, rhs); } } fn push_row(&mut self) { let records = std::mem::replace(self, VecRecords::new(vec![])); let mut data: Vec> = records.into(); let count_columns = data.first().map(|l| l.len()).unwrap_or(0); data.push(vec![T::default(); count_columns]); *self = VecRecords::new(data); } fn push_column(&mut self) { let records = std::mem::replace(self, VecRecords::new(vec![])); let mut data: Vec> = records.into(); for row in &mut data { row.push(T::default()); } *self = VecRecords::new(data); } fn remove_row(&mut self, row: usize) { let records = std::mem::replace(self, VecRecords::new(vec![])); let mut data: Vec> = records.into(); let _ = data.remove(row); *self = VecRecords::new(data); } fn remove_column(&mut self, column: usize) { let records = std::mem::replace(self, VecRecords::new(vec![])); let mut data: Vec> = records.into(); for row in &mut data { let _ = row.remove(column); } *self = VecRecords::new(data); } fn insert_row(&mut self, row: usize) { let records = std::mem::replace(self, VecRecords::new(vec![])); let mut data: Vec> = records.into(); let count_columns = data.first().map(|l| l.len()).unwrap_or(0); data.insert(row, vec![T::default(); count_columns]); *self = VecRecords::new(data); } fn insert_column(&mut self, column: usize) { let records = std::mem::replace(self, VecRecords::new(vec![])); let mut data: Vec> = records.into(); for row in &mut data { row.insert(column, T::default()); } *self = VecRecords::new(data); } } tabled-0.18.0/src/iter/layout_iterator.rs000064400000000000000000000036061046102023000165000ustar 00000000000000#[cfg(feature = "std")] use crate::{Table, Tabled}; /// [`LayoutIterator`] is a convient abstraction to iterate over rows/columns. #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)] pub struct LayoutIterator { from: usize, to: usize, batch: usize, i: usize, } impl LayoutIterator { /// Creates a custom [`LayoutIterator`] instance. pub fn new(from: usize, to: usize, batch: usize) -> Self { Self { from, to, batch, i: 0, } } /// Creates a record iterator for KV table created by [`Table::kv`]. /// So it basically skips all rows until next record starts. #[cfg(feature = "std")] pub fn kv_batches(t: &Table) -> Self where T: Tabled, { Self::new(0, t.count_rows(), T::LENGTH) } } impl Iterator for LayoutIterator { type Item = usize; fn next(&mut self) -> Option { if self.batch == 0 { return None; } if self.from >= self.to { return None; } let value = self.i * self.batch; self.from += self.batch; self.i += 1; Some(value) } } #[cfg(test)] mod tests { use crate::iter::LayoutIterator; #[test] fn test_layout_iterator() { assert_eq!( LayoutIterator::new(0, 5, 1).collect::>(), vec![0, 1, 2, 3, 4] ); assert_eq!( LayoutIterator::new(0, 5, 2).collect::>(), vec![0, 2, 4] ); assert_eq!( LayoutIterator::new(0, 6, 2).collect::>(), vec![0, 2, 4] ); assert_eq!(LayoutIterator::new(0, 0, 2).collect::>(), vec![]); assert_eq!(LayoutIterator::new(0, 5, 0).collect::>(), vec![]); assert_eq!(LayoutIterator::new(0, 0, 0).collect::>(), vec![]); } } tabled-0.18.0/src/iter/mod.rs000064400000000000000000000001461046102023000140250ustar 00000000000000//! A module for iterator structures. mod layout_iterator; pub use layout_iterator::LayoutIterator; tabled-0.18.0/src/lib.rs000064400000000000000000000224231046102023000130530ustar 00000000000000//! An easy to use library for pretty print tables of Rust `struct`s and `enum`s. //! //! There's two approaches to construct a table. //! //! 1. When the type of data is known. //! 2. When it's unknown. //! //! Here you can work with both.\ //! For first approach you shall find [`derive::Tabled`] macros being very helpfull.\ //! For a later one you shall take a look at [`Builder`]. //! //! There are a number of [`settings`] you can use\ //! to change table appearance, layout and data itself. //! //! Beside a default [`Table`] type there are more,\ //! more specific table which works best when there are some constraints. //! #![cfg_attr(all(feature = "derive", feature = "std"), doc = "```")] #![cfg_attr(not(all(feature = "derive", feature = "std")), doc = "```ignore")] //! use tabled::{Tabled, Table}; //! use tabled::settings::{Style, Alignment, object::Columns}; //! use testing_table::assert_table; //! //! #[derive(Tabled)] //! struct Language { //! name: &'static str, //! designed_by: &'static str, //! invented_year: usize, //! } //! //! let languages = vec![ //! Language{ name: "C", designed_by: "Dennis Ritchie", invented_year: 1972 }, //! Language{ name: "Rust", designed_by: "Graydon Hoare", invented_year: 2010 }, //! Language{ name: "Go", designed_by: "Rob Pike", invented_year: 2009 }, //! ]; //! //! let mut table = Table::new(languages); //! table.with(Style::modern()); //! table.modify(Columns::first(), Alignment::right()); //! //! assert_table!( //! table, //! "┌──────┬────────────────┬───────────────┐" //! "│ name │ designed_by │ invented_year │" //! "├──────┼────────────────┼───────────────┤" //! "│ C │ Dennis Ritchie │ 1972 │" //! "├──────┼────────────────┼───────────────┤" //! "│ Rust │ Graydon Hoare │ 2010 │" //! "├──────┼────────────────┼───────────────┤" //! "│ Go │ Rob Pike │ 2009 │" //! "└──────┴────────────────┴───────────────┘" //! ); //! ``` //! //! ## Building table step by step //! //! When you data scheme is not known at compile time.\ //! You most likely will not able to relay on [`Table`].\ //! One option would be is to use [`Builder`]. //! #![cfg_attr(feature = "std", doc = "```")] #![cfg_attr(not(feature = "std"), doc = "```ignore")] //! use std::iter::once; //! use tabled::{builder::Builder, settings::Style}; //! use testing_table::assert_table; //! //! const X: usize = 3; //! const Y: usize = 5; //! //! let mut builder = Builder::default(); //! //! for i in 0..X { //! let row = (0..Y).map(|j| (i * j).to_string()); //! builder.push_record(row); //! } //! //! builder.insert_record(0, (0..Y).map(|i| i.to_string())); //! builder.insert_column(0, once(String::new()).chain((0..X).map(|i| i.to_string()))); //! //! let mut table = builder.build(); //! table.with(Style::rounded()); //! //! assert_table!( //! table, //! "╭───┬───┬───┬───┬───┬───╮" //! "│ │ 0 │ 1 │ 2 │ 3 │ 4 │" //! "├───┼───┼───┼───┼───┼───┤" //! "│ 0 │ 0 │ 0 │ 0 │ 0 │ 0 │" //! "│ 1 │ 0 │ 1 │ 2 │ 3 │ 4 │" //! "│ 2 │ 0 │ 2 │ 4 │ 6 │ 8 │" //! "╰───┴───┴───┴───┴───┴───╯" //! ); //! ``` //! //! ## Settings //! //! You can find lots of settings in [`tabled::settings`]. //! //! ## Hints //! //! [`Table`] can be build from vast majority of Rust's standard types.\ //! This allows you to run the following code. //! #![cfg_attr(feature = "std", doc = "```")] #![cfg_attr(not(feature = "std"), doc = "```ignore")] //! use tabled::Table; //! use testing_table::assert_table; //! //! let table = Table::new(&[1, 2, 3]); //! //! assert_table!( //! table, //! "+-----+" //! "| i32 |" //! "+-----+" //! "| 1 |" //! "+-----+" //! "| 2 |" //! "+-----+" //! "| 3 |" //! "+-----+" //! ); //! ``` //! //! You can compine types, and settings together using a tupples.\ //! And achive magical results. //! #![cfg_attr(feature = "std", doc = "```")] #![cfg_attr(not(feature = "std"), doc = "```ignore")] //! use tabled::Table; //! use tabled::settings::{style::{Style, HorizontalLine}, Alignment, Padding}; //! use testing_table::assert_table; //! //! let data = &[(1, 2, "Hello"), (1, 3, "World")]; //! //! let mut table = Table::new(data); //! table.with( //! Style::modern() //! .remove_horizontal() //! .horizontals([(1, HorizontalLine::inherit(Style::modern()))]) //! ); //! table.with((Alignment::right(), Padding::new(2, 0, 2, 1))); //! //! assert_table!( //! table, //! "┌─────┬─────┬───────┐" //! "│ │ │ │" //! "│ │ │ │" //! "│ i32│ i32│ &str│" //! "│ │ │ │" //! "├─────┼─────┼───────┤" //! "│ │ │ │" //! "│ │ │ │" //! "│ 1│ 2│ Hello│" //! "│ │ │ │" //! "│ │ │ │" //! "│ │ │ │" //! "│ 1│ 3│ World│" //! "│ │ │ │" //! "└─────┴─────┴───────┘" //! ); //! ``` //! //! Be ware you don't obligated to `collect` your data before building. //! #![cfg_attr(all(feature = "derive", feature = "std"), doc = "```")] #![cfg_attr(not(all(feature = "derive", feature = "std")), doc = "```ignore")] //! use tabled::{Tabled, Table}; //! use testing_table::assert_table; //! //! let data = (0..3).map(|i| [i, i * 2, i * 3]); //! //! let mut table = Table::new(data); //! //! assert_table!( //! table, //! "+---+---+---+" //! "| 0 | 1 | 2 |" //! "+---+---+---+" //! "| 0 | 0 | 0 |" //! "+---+---+---+" //! "| 1 | 2 | 3 |" //! "+---+---+---+" //! "| 2 | 4 | 6 |" //! "+---+---+---+" //! ); //! ``` //! //! Build table using [`row!`] and [`col!`] macros. //! #![cfg_attr(all(feature = "macros", feature = "std"), doc = "```")] #![cfg_attr(not(all(feature = "macros", feature = "std")), doc = "```ignore")] //! use tabled::{row, col}; //! use testing_table::assert_table; //! //! let table = row![ //! col!["Hello", "World", "!"], //! col!["Hello"; 3], //! col!["World"; 3], //! ]; //! //! assert_table!( //! table, //! "+-----------+-----------+-----------+" //! "| +-------+ | +-------+ | +-------+ |" //! "| | Hello | | | Hello | | | World | |" //! "| +-------+ | +-------+ | +-------+ |" //! "| | World | | | Hello | | | World | |" //! "| +-------+ | +-------+ | +-------+ |" //! "| | ! | | | Hello | | | World | |" //! "| +-------+ | +-------+ | +-------+ |" //! "+-----------+-----------+-----------+" //! ); //! ``` //! //! # `no_std` //! //! Only [`CompactTable`] can be used in `no_std` context. //! //! # Features //! //! - `std` - Used by default. If not its considered `no_std` with a limited set of functionality. //! - `derive` - Used by default. A support for `Tabled` derive macro. //! - `ansi` - A support for ANSI sequences. //! - `macros` - A support for `row!`, `col!` macro. //! //! ## More information //! //! You can find more examples of settings and attributes in //! [README.md](https://github.com/zhiburt/tabled/blob/master/README.md) //! //! [`Builder`]: crate::builder::Builder //! [`IterTable`]: crate::tables::IterTable //! [`CompactTable`]: crate::tables::CompactTable //! [`fmt::Write`]: core::fmt::Write //! [`row!`]: crate::row //! [`col!`]: crate::col //! [`tabled::settings`]: crate::settings #![cfg_attr(not(any(feature = "std", test)), no_std)] #![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] #![doc( html_logo_url = "https://raw.githubusercontent.com/zhiburt/tabled/86ac146e532ce9f7626608d7fd05072123603a2e/assets/tabled-gear.svg" )] #![deny(unused_must_use)] #![warn( missing_docs, rust_2018_idioms, rust_2018_compatibility, missing_debug_implementations, unreachable_pub, future_incompatible, single_use_lifetimes, trivial_casts, trivial_numeric_casts, unused_extern_crates, unused_import_braces, unused_qualifications, unused_results, unused_variables, variant_size_differences )] #![allow(clippy::uninlined_format_args)] #[cfg(feature = "macros")] mod macros; #[cfg(feature = "std")] mod tabled; mod util; #[cfg(feature = "std")] #[cfg_attr(docsrs, doc(cfg(feature = "std")))] pub mod builder; #[cfg(feature = "derive")] #[cfg_attr(docsrs, doc(cfg(feature = "derive")))] pub mod derive; pub mod grid; pub mod iter; pub mod settings; pub mod tables; #[cfg(feature = "std")] #[cfg_attr(docsrs, doc(cfg(feature = "std")))] pub use crate::{tabled::Tabled, tables::Table}; #[cfg(feature = "derive")] #[cfg_attr(docsrs, doc(cfg(feature = "derive")))] pub use derive::Tabled; tabled-0.18.0/src/macros/col.rs000064400000000000000000000024261046102023000143470ustar 00000000000000/// Creates a [`Table`] with [`Display`] arguments nested within. /// /// The macros allows several tables to be displayed vertically. /// /// Companion to [`row!`]. /// /// # Examples /// ```rust,no_run /// # use tabled::{row, col, Table}; /// # let (table1, table2, table3) = (Table::new(&[String::new()]), Table::new(&[String::new()]), Table::new(&[String::new()])); /// let new_table = col![table1, table2]; /// let new_table_of_clones = col![table1; 3]; /// let columns_and_rows = col![ /// table1, /// row![table2, table3] /// ]; /// ``` /// /// [`row!`]: crate::row /// [`Table`]: crate::Table /// [`Display`]: std::fmt::Display #[macro_export] #[cfg_attr(docsrs, doc(cfg(feature = "macros")))] macro_rules! col { // Vertical ( $($table:expr), * $(,)? ) => {{ let mut builder = $crate::builder::Builder::default(); $( builder.push_record([$table.to_string()]); )* builder.build() }}; // Duplicate single item ( $table:expr; $N:expr) => {{ let mut builder = $crate::builder::Builder::default(); let n = $N; if n > 0 { let t = $table.to_string(); for _ in 0..$N { builder.push_record([t.clone()]); } } builder.build() }}; } tabled-0.18.0/src/macros/mod.rs000064400000000000000000000002001046102023000143350ustar 00000000000000//! This module contains macro functions for dynamic [`Table`] construction. //! //! [`Table`]: crate::Table mod col; mod row; tabled-0.18.0/src/macros/row.rs000064400000000000000000000023261046102023000144000ustar 00000000000000/// Creates a [`Table`] with [`Display`] arguments nested within. /// /// The macros allows several tables to be displayed horizontally. /// /// Companion to [`col!`]. /// /// # Examples /// ```rust,no_run /// # use tabled::{row, col, Table}; /// # let (table1, table2, table3) = (Table::new(&[String::new()]), Table::new(&[String::new()]), Table::new(&[String::new()])); /// let new_table = row![table1, table2]; /// let new_table_of_clones = row![table1; 3]; /// let rows_and_columns = row![ /// table1, /// col![table2, table3] /// ]; /// ``` /// /// [`col!`]: crate::col /// [`Table`]: crate::Table /// [`Display`]: std::fmt::Display #[macro_export] #[cfg_attr(docsrs, doc(cfg(feature = "macros")))] macro_rules! row { // Horizontal Display ( $($table:expr), * $(,)? ) => {{ let mut builder = $crate::builder::Builder::default(); let record = [ $($table.to_string(),)* ]; builder.push_record(record); builder.build() }}; // Duplicate single item ( $table:expr; $N:expr) => {{ let mut builder = $crate::builder::Builder::default(); let duplicates = vec![$table.to_string(); $N]; builder.push_record(duplicates); builder.build() }}; } tabled-0.18.0/src/settings/alignment/mod.rs000064400000000000000000000177611046102023000167130ustar 00000000000000//! This module contains an [`Alignment`] setting for cells on the [`Table`]. //! //! # Example //! #![cfg_attr(feature = "std", doc = "```")] #![cfg_attr(not(feature = "std"), doc = "```ignore")] //! # use tabled::{Table, settings::{Alignment, Modify, object::Rows}}; //! # let data: Vec<&'static str> = Vec::new(); //! let mut table = Table::new(&data); //! table.with(Modify::new(Rows::single(0)).with(Alignment::center())); //! ``` //! //! [`Table`]: crate::Table use crate::{ grid::config::{ AlignmentHorizontal, AlignmentVertical, CompactConfig, CompactMultilineConfig, Entity, }, settings::TableOption, }; use AlignmentInner::*; #[cfg(feature = "std")] use crate::grid::config::ColoredConfig; /// Alignment represent a horizontal and vertical alignment setting for any cell on a [`Table`]. /// /// An alignment strategy can be set by [`AlignmentStrategy`]. /// /// # Example /// #[cfg_attr(feature = "std", doc = "```")] #[cfg_attr(not(feature = "std"), doc = "```ignore")] /// use tabled::{ /// Table, /// settings::{ /// formatting::AlignmentStrategy, /// object::Segment, Alignment, Modify, Style, /// } /// }; /// /// let data = [ /// ["1", "2", "3"], /// ["Some\nMulti\nLine\nText", "and a line", "here"], /// ["4", "5", "6"], /// ]; /// /// let mut table = Table::new(&data); /// table /// .with(Style::modern()) /// .with( /// Modify::new(Segment::all()) /// .with(Alignment::right()) /// .with(Alignment::center()) /// .with(AlignmentStrategy::PerCell) /// ); /// /// assert_eq!( /// table.to_string(), /// concat!( /// "┌───────┬────────────┬──────┐\n", /// "│ 0 │ 1 │ 2 │\n", /// "├───────┼────────────┼──────┤\n", /// "│ 1 │ 2 │ 3 │\n", /// "├───────┼────────────┼──────┤\n", /// "│ Some │ and a line │ here │\n", /// "│ Multi │ │ │\n", /// "│ Line │ │ │\n", /// "│ Text │ │ │\n", /// "├───────┼────────────┼──────┤\n", /// "│ 4 │ 5 │ 6 │\n", /// "└───────┴────────────┴──────┘", /// ), /// ) /// ``` /// /// [`Table`]: crate::Table /// [`AlignmentStrategy`]: crate::settings::formatting::AlignmentStrategy #[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord)] pub struct Alignment { inner: AlignmentInner, } #[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord)] enum AlignmentInner { /// A horizontal alignment. Horizontal(AlignmentHorizontal), /// A vertical alignment. Vertical(AlignmentVertical), } impl Alignment { /// Left constructs a horizontal alignment to [`AlignmentHorizontal::Left`] pub const fn left() -> Self { Self::horizontal(AlignmentHorizontal::Left) } /// Right constructs a horizontal alignment to [`AlignmentHorizontal::Right`] /// /// ## Notice /// /// When you use [`MinWidth`] the alignment might not work as you expected. /// You could try to apply [`TrimStrategy`] which may help. /// /// [`MinWidth`]: crate::settings::width::MinWidth /// [`TrimStrategy`]: crate::settings::formatting::TrimStrategy pub const fn right() -> Self { Self::horizontal(AlignmentHorizontal::Right) } /// Center constructs a horizontal alignment to [`AlignmentHorizontal::Center`] /// /// ## Notice /// /// When you use [`MinWidth`] the alignment might not work as you expected. /// You could try to apply [`TrimStrategy`] which may help. /// /// [`MinWidth`]: crate::settings::width::MinWidth /// [`TrimStrategy`]: crate::settings::formatting::TrimStrategy pub const fn center() -> Self { Self::horizontal(AlignmentHorizontal::Center) } /// Top constructs a vertical alignment to [`AlignmentVertical::Top`] pub const fn top() -> Self { Self::vertical(AlignmentVertical::Top) } /// Bottom constructs a vertical alignment to [`AlignmentVertical::Bottom`] pub const fn bottom() -> Self { Self::vertical(AlignmentVertical::Bottom) } /// `Center_vertical` constructs a vertical alignment to [`AlignmentVertical::Center`] pub const fn center_vertical() -> Self { Self::vertical(AlignmentVertical::Center) } /// Convert alignment to horizontal. pub const fn as_horizontal(self) -> Option { match self.inner { Horizontal(alignment) => Some(alignment), Vertical(_) => None, } } /// Convert alignment to vertical. pub const fn as_vertical(self) -> Option { match self.inner { Horizontal(_) => None, Vertical(alignment) => Some(alignment), } } /// Returns an alignment with the given horizontal alignment. const fn horizontal(alignment: AlignmentHorizontal) -> Self { Self::new(Horizontal(alignment)) } /// Returns an alignment with the given vertical alignment. const fn vertical(alignment: AlignmentVertical) -> Self { Self::new(Vertical(alignment)) } const fn new(inner: AlignmentInner) -> Self { Self { inner } } } #[cfg(feature = "std")] impl crate::settings::CellOption for Alignment { fn change(self, _: &mut R, cfg: &mut ColoredConfig, entity: Entity) { match self.inner { Horizontal(a) => cfg.set_alignment_horizontal(entity, a), Vertical(a) => cfg.set_alignment_vertical(entity, a), } } } #[cfg(feature = "std")] impl TableOption for Alignment { fn change(self, _: &mut R, cfg: &mut ColoredConfig, _: &mut D) { match self.inner { Horizontal(a) => cfg.set_alignment_horizontal(Entity::Global, a), Vertical(a) => cfg.set_alignment_vertical(Entity::Global, a), } } fn hint_change(&self) -> Option { None } } impl TableOption for Alignment { fn change(self, _: &mut R, cfg: &mut CompactConfig, _: &mut D) { if let Horizontal(a) = self.inner { *cfg = cfg.set_alignment_horizontal(a); } } fn hint_change(&self) -> Option { None } } impl TableOption for Alignment { fn change(self, _: &mut R, cfg: &mut CompactMultilineConfig, _: &mut D) { match self.inner { Horizontal(a) => cfg.set_alignment_horizontal(a), Vertical(a) => cfg.set_alignment_vertical(a), } } fn hint_change(&self) -> Option { None } } impl From for Alignment { fn from(value: AlignmentHorizontal) -> Self { match value { AlignmentHorizontal::Center => Self::center(), AlignmentHorizontal::Left => Self::left(), AlignmentHorizontal::Right => Self::right(), } } } impl From for Alignment { fn from(value: AlignmentVertical) -> Self { match value { AlignmentVertical::Center => Self::center_vertical(), AlignmentVertical::Top => Self::top(), AlignmentVertical::Bottom => Self::bottom(), } } } impl From for Option { fn from(value: Alignment) -> Self { match value.inner { Horizontal(alignment) => Some(alignment), Vertical(_) => None, } } } impl From for Option { fn from(value: Alignment) -> Self { match value.inner { Vertical(alignment) => Some(alignment), Horizontal(_) => None, } } } tabled-0.18.0/src/settings/cell_option.rs000064400000000000000000000071161046102023000164560ustar 00000000000000use crate::grid::{ config::Entity, records::{ExactRecords, Records, RecordsMut}, }; /// A trait for configuring a single cell. /// /// A cell is represented by row and column indexes. /// /// A cell can be targeted by [`Cell`]. /// /// [`Cell`]: crate::settings::object::Cell pub trait CellOption { /// Modification function of a certail part of a grid targeted by [`Entity`]. fn change(self, records: &mut R, cfg: &mut C, entity: Entity); /// A hint whether an [`TableOption`] is going to change table layout. /// /// Return [`None`] if no changes are being done. /// Otherwise return: /// /// - [Entity::Global] - a grand layout changed. /// - [Entity::Row] - a certain row was changed. /// - [Entity::Column] - a certain column was changed. /// - [Entity::Cell] - a certain cell was changed. /// /// By default it's considered to be a grand change. /// /// This methods primarily is used as an optimization, /// to not make unnecessary calculations if they're not needed, after using the [`TableOption`]. /// /// [`TableOption`]: crate::settings::TableOption fn hint_change(&self) -> Option { Some(Entity::Global) } } #[cfg(feature = "std")] impl CellOption for String where R: Records + ExactRecords + RecordsMut, { fn change(self, records: &mut R, cfg: &mut C, entity: Entity) { (&self).change(records, cfg, entity); } } #[cfg(feature = "std")] impl CellOption for &String where R: Records + ExactRecords + RecordsMut, { fn change(self, records: &mut R, _: &mut C, entity: Entity) { let count_rows = records.count_rows(); let count_cols = records.count_columns(); for pos in entity.iter(count_rows, count_cols) { records.set(pos, self.clone()); } } } impl<'a, R, C> CellOption for &'a str where R: Records + ExactRecords + RecordsMut<&'a str>, { fn change(self, records: &mut R, _: &mut C, entity: Entity) { let count_rows = records.count_rows(); let count_cols = records.count_columns(); for pos in entity.iter(count_rows, count_cols) { records.set(pos, self); } } } #[cfg(feature = "std")] macro_rules! tuple_trait_impl { ( $($name:ident)+ ) => { impl),+> CellOption for ($($name,)+) { fn change(self, records: &mut R, cfg: &mut C, entity: Entity) { #![allow(non_snake_case)] let ($($name,)+) = self; $( $name::change($name, records, cfg, entity); )+ } fn hint_change(&self) -> Option { #![allow(non_snake_case)] let ($($name,)+) = &self; let list = [ $( $name::hint_change($name), )+ ]; crate::settings::table_option::hint_change_list(&list) } } }; } #[cfg(feature = "std")] tuple_trait_impl!(T0 T1); #[cfg(feature = "std")] tuple_trait_impl!(T0 T1 T2); #[cfg(feature = "std")] tuple_trait_impl!(T0 T1 T2 T3); #[cfg(feature = "std")] tuple_trait_impl!(T0 T1 T2 T3 T4); #[cfg(feature = "std")] tuple_trait_impl!(T0 T1 T2 T3 T4 T5); #[cfg(feature = "std")] tuple_trait_impl!(T0 T1 T2 T3 T4 T5 T6); #[cfg(feature = "std")] tuple_trait_impl!(T0 T1 T2 T3 T4 T5 T6 T7); #[cfg(feature = "std")] tuple_trait_impl!(T0 T1 T2 T3 T4 T5 T6 T7 T8); #[cfg(feature = "std")] tuple_trait_impl!(T0 T1 T2 T3 T4 T5 T6 T7 T8 T9); tabled-0.18.0/src/settings/color/mod.rs000064400000000000000000000412031046102023000160370ustar 00000000000000//! This module contains a configuration of a [`Border`] or a [`Table`] to set its borders color via [`Color`]. //! //! [`Border`]: crate::settings::Border //! [`Table`]: crate::Table use std::{fmt, ops::BitOr}; use crate::{ grid::{ ansi::{ANSIBuf, ANSIFmt, ANSIStr as StaticColor}, config::{ColoredConfig, Entity}, }, settings::{CellOption, TableOption}, }; /// Color represents a color which can be set to things like [`Border`], [`Padding`] and [`Margin`]. /// /// # Example /// /// ``` /// use tabled::{settings::Color, Table}; /// /// let data = [ /// (0u8, "Hello"), /// (1u8, "World"), /// ]; /// /// let table = Table::new(data) /// .with(Color::BG_BLUE) /// .to_string(); /// /// println!("{}", table); /// ``` /// /// [`Padding`]: crate::settings::Padding /// [`Margin`]: crate::settings::Margin /// [`Border`]: crate::settings::Border #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct Color { inner: ColorInner, } #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] enum ColorInner { Static(StaticColor<'static>), Buf(ANSIBuf), } #[rustfmt::skip] impl Color { /// A color representation. /// /// Notice that the colors are constants so you can't combine them. pub const FG_BLACK: Self = Self::new_static("\u{1b}[30m", "\u{1b}[39m"); /// A color representation. /// /// Notice that the colors are constants so you can't combine them. pub const FG_BLUE: Self = Self::new_static("\u{1b}[34m", "\u{1b}[39m"); /// A color representation. /// /// Notice that the colors are constants so you can't combine them. pub const FG_BRIGHT_BLACK: Self = Self::new_static("\u{1b}[90m", "\u{1b}[39m"); /// A color representation. /// /// Notice that the colors are constants so you can't combine them. pub const FG_BRIGHT_BLUE: Self = Self::new_static("\u{1b}[94m", "\u{1b}[39m"); /// A color representation. /// /// Notice that the colors are constants so you can't combine them. pub const FG_BRIGHT_CYAN: Self = Self::new_static("\u{1b}[96m", "\u{1b}[39m"); /// A color representation. /// /// Notice that the colors are constants so you can't combine them. pub const FG_BRIGHT_GREEN: Self = Self::new_static("\u{1b}[92m", "\u{1b}[39m"); /// A color representation. /// /// Notice that the colors are constants so you can't combine them. pub const FG_BRIGHT_MAGENTA: Self = Self::new_static("\u{1b}[95m", "\u{1b}[39m"); /// A color representation. /// /// Notice that the colors are constants so you can't combine them. pub const FG_BRIGHT_RED: Self = Self::new_static("\u{1b}[91m", "\u{1b}[39m"); /// A color representation. /// /// Notice that the colors are constants so you can't combine them. pub const FG_BRIGHT_WHITE: Self = Self::new_static("\u{1b}[97m", "\u{1b}[39m"); /// A color representation. /// /// Notice that the colors are constants so you can't combine them. pub const FG_BRIGHT_YELLOW: Self = Self::new_static("\u{1b}[93m", "\u{1b}[39m"); /// A color representation. /// /// Notice that the colors are constants so you can't combine them. pub const FG_CYAN: Self = Self::new_static("\u{1b}[36m", "\u{1b}[39m"); /// A color representation. /// /// Notice that the colors are constants so you can't combine them. pub const FG_GREEN: Self = Self::new_static("\u{1b}[32m", "\u{1b}[39m"); /// A color representation. /// /// Notice that the colors are constants so you can't combine them. pub const FG_MAGENTA: Self = Self::new_static("\u{1b}[35m", "\u{1b}[39m"); /// A color representation. /// /// Notice that the colors are constants so you can't combine them. pub const FG_RED: Self = Self::new_static("\u{1b}[31m", "\u{1b}[39m"); /// A color representation. /// /// Notice that the colors are constants so you can't combine them. pub const FG_WHITE: Self = Self::new_static("\u{1b}[37m", "\u{1b}[39m"); /// A color representation. /// /// Notice that the colors are constants so you can't combine them. pub const FG_YELLOW: Self = Self::new_static("\u{1b}[33m", "\u{1b}[39m"); /// A color representation. /// /// Notice that the colors are constants so you can't combine them. pub const BG_BLACK: Self = Self::new_static("\u{1b}[40m", "\u{1b}[49m"); /// A color representation. /// /// Notice that the colors are constants so you can't combine them. pub const BG_BLUE: Self = Self::new_static("\u{1b}[44m", "\u{1b}[49m"); /// A color representation. /// /// Notice that the colors are constants so you can't combine them. pub const BG_BRIGHT_BLACK: Self = Self::new_static("\u{1b}[100m", "\u{1b}[49m"); /// A color representation. /// /// Notice that the colors are constants so you can't combine them. pub const BG_BRIGHT_BLUE: Self = Self::new_static("\u{1b}[104m", "\u{1b}[49m"); /// A color representation. /// /// Notice that the colors are constants so you can't combine them. pub const BG_BRIGHT_CYAN: Self = Self::new_static("\u{1b}[106m", "\u{1b}[49m"); /// A color representation. /// /// Notice that the colors are constants so you can't combine them. pub const BG_BRIGHT_GREEN: Self = Self::new_static("\u{1b}[102m", "\u{1b}[49m"); /// A color representation. /// /// Notice that the colors are constants so you can't combine them. pub const BG_BRIGHT_MAGENTA: Self = Self::new_static("\u{1b}[105m", "\u{1b}[49m"); /// A color representation. /// /// Notice that the colors are constants so you can't combine them. pub const BG_BRIGHT_RED: Self = Self::new_static("\u{1b}[101m", "\u{1b}[49m"); /// A color representation. /// /// Notice that the colors are constants so you can't combine them. pub const BG_BRIGHT_WHITE: Self = Self::new_static("\u{1b}[107m", "\u{1b}[49m"); /// A color representation. /// /// Notice that the colors are constants so you can't combine them. pub const BG_BRIGHT_YELLOW: Self = Self::new_static("\u{1b}[103m", "\u{1b}[49m"); /// A color representation. /// /// Notice that the colors are constants so you can't combine them. pub const BG_CYAN: Self = Self::new_static("\u{1b}[46m", "\u{1b}[49m"); /// A color representation. /// /// Notice that the colors are constants so you can't combine them. pub const BG_GREEN: Self = Self::new_static("\u{1b}[42m", "\u{1b}[49m"); /// A color representation. /// /// Notice that the colors are constants so you can't combine them. pub const BG_MAGENTA: Self = Self::new_static("\u{1b}[45m", "\u{1b}[49m"); /// A color representation. /// /// Notice that the colors are constants so you can't combine them. pub const BG_RED: Self = Self::new_static("\u{1b}[41m", "\u{1b}[49m"); /// A color representation. /// /// Notice that the colors are constants so you can't combine them. pub const BG_WHITE: Self = Self::new_static("\u{1b}[47m", "\u{1b}[49m"); /// A color representation. /// /// Notice that the colors are constants so you can't combine them. pub const BG_YELLOW: Self = Self::new_static("\u{1b}[43m", "\u{1b}[49m"); /// A color representation. /// /// Notice that the colors are constants so you can't combine them. pub const BOLD: Self = Self::new_static("\u{1b}[1m", "\u{1b}[22m"); /// A color representation. /// /// Notice that the colors are constants so you can't combine them. pub const UNDERLINE: Self = Self::new_static("\u{1b}[4m", "\u{1b}[24m"); } impl Color { /// Creates a new [`Color`]` instance, with ANSI prefix and ANSI suffix. /// You can use [`TryFrom`] to construct it from [`String`]. pub fn new(prefix: P, suffix: S) -> Self where P: Into, S: Into, { let color = ANSIBuf::new(prefix, suffix); let inner = ColorInner::Buf(color); Self { inner } } /// Creates a new empty [`Color`]`. pub const fn empty() -> Self { Self::new_static("", "") } /// Return a prefix. pub fn get_prefix(&self) -> &str { match &self.inner { ColorInner::Static(color) => color.get_prefix(), ColorInner::Buf(color) => color.get_prefix(), } } /// Return a suffix. pub fn get_suffix(&self) -> &str { match &self.inner { ColorInner::Static(color) => color.get_suffix(), ColorInner::Buf(color) => color.get_suffix(), } } /// Tries to get a static value of the color. pub fn as_ansi_str(&self) -> Option> { match self.inner { ColorInner::Static(value) => Some(value), ColorInner::Buf(_) => None, } } /// Parses the string, /// /// # Panics /// /// PANICS if the input string incorrectly built. /// Use [`std::convert::TryFrom`] instead if you are not sure about the input. #[cfg(feature = "ansi")] pub fn parse(text: S) -> Self where S: AsRef, { std::convert::TryFrom::try_from(text.as_ref()).unwrap() } /// Create a 24 bit foreground color with RGB pub fn rgb_fg(r: u8, g: u8, b: u8) -> Self { Self { inner: ColorInner::Buf(ANSIBuf::new( format!("\u{1b}[38;2;{};{};{}m", r, g, b), "\u{1b}[39m", )), } } /// Create a 24 bit background color with RGB. /// /// The terminal need to support the escape sequence pub fn rgb_bg(r: u8, g: u8, b: u8) -> Self { Self { inner: ColorInner::Buf(ANSIBuf::new( format!("\u{1b}[48;2;{};{};{}m", r, g, b), "\u{1b}[49m", )), } } /// Colorize a string. pub fn colorize(&self, text: S) -> String where S: AsRef, { let mut buf = String::new(); for (i, line) in text.as_ref().lines().enumerate() { if i > 0 { buf.push('\n'); } buf.push_str(self.get_prefix()); buf.push_str(line); buf.push_str(self.get_suffix()); } buf } const fn new_static(prefix: &'static str, suffix: &'static str) -> Self { let color = StaticColor::new(prefix, suffix); let inner = ColorInner::Static(color); Self { inner } } } impl Default for Color { fn default() -> Self { Self { inner: ColorInner::Static(StaticColor::default()), } } } impl From for ANSIBuf { fn from(color: Color) -> Self { match color.inner { ColorInner::Static(color) => ANSIBuf::from(color), ColorInner::Buf(color) => color, } } } impl From for Color { fn from(color: ANSIBuf) -> Self { Self { inner: ColorInner::Buf(color), } } } impl From> for Color { fn from(color: StaticColor<'static>) -> Self { Self { inner: ColorInner::Static(color), } } } impl BitOr for Color { type Output = Color; fn bitor(self, rhs: Self) -> Self::Output { let l_prefix = self.get_prefix(); let l_suffix = self.get_suffix(); let r_prefix = rhs.get_prefix(); let r_suffix = rhs.get_suffix(); let mut prefix = l_prefix.to_string(); if l_prefix != r_prefix { prefix.push_str(r_prefix); } let mut suffix = l_suffix.to_string(); if l_suffix != r_suffix { suffix.push_str(r_suffix); } Self::new(prefix, suffix) } } #[cfg(feature = "ansi")] impl std::convert::TryFrom<&str> for Color { type Error = (); fn try_from(value: &str) -> Result { let buf = ANSIBuf::try_from(value)?; Ok(Color { inner: ColorInner::Buf(buf), }) } } #[cfg(feature = "ansi")] impl std::convert::TryFrom for Color { type Error = (); fn try_from(value: String) -> Result { let buf = ANSIBuf::try_from(value)?; Ok(Color { inner: ColorInner::Buf(buf), }) } } impl TableOption for Color { fn change(self, _: &mut R, cfg: &mut ColoredConfig, _: &mut D) { let color = self.into(); let _ = cfg.set_color(Entity::Global, color); } fn hint_change(&self) -> Option { None } } impl CellOption for Color { fn change(self, _: &mut R, cfg: &mut ColoredConfig, entity: Entity) { let color = self.into(); let _ = cfg.set_color(entity, color); } fn hint_change(&self) -> Option { None } } impl CellOption for &Color { fn change(self, _: &mut R, cfg: &mut ColoredConfig, entity: Entity) { let color = self.clone().into(); let _ = cfg.set_color(entity, color); } fn hint_change(&self) -> Option { None } } impl ANSIFmt for Color { fn fmt_ansi_prefix(&self, f: &mut W) -> fmt::Result { match &self.inner { ColorInner::Static(color) => color.fmt_ansi_prefix(f), ColorInner::Buf(color) => color.fmt_ansi_prefix(f), } } fn fmt_ansi_suffix(&self, f: &mut W) -> fmt::Result { match &self.inner { ColorInner::Static(color) => color.fmt_ansi_suffix(f), ColorInner::Buf(color) => color.fmt_ansi_suffix(f), } } } #[cfg(test)] mod tests { use super::*; #[cfg(feature = "ansi")] use std::convert::TryFrom; #[test] fn test_xor_operation() { assert_eq!( Color::FG_BLACK | Color::FG_BLUE, Color::new("\u{1b}[30m\u{1b}[34m", "\u{1b}[39m") ); assert_eq!( Color::FG_BRIGHT_GREEN | Color::BG_BLUE, Color::new("\u{1b}[92m\u{1b}[44m", "\u{1b}[39m\u{1b}[49m") ); assert_eq!( Color::new("...", "!!!") | Color::new("@@@", "###"), Color::new("...@@@", "!!!###") ); assert_eq!( Color::new("...", "!!!") | Color::new("@@@", "###") | Color::new("$$$", "%%%"), Color::new("...@@@$$$", "!!!###%%%") ); } #[cfg(feature = "ansi")] #[test] fn test_try_from() { assert_eq!(Color::try_from(""), Err(())); assert_eq!( Color::try_from("\u{1b}[31m\u{1b}[42m\u{1b}[39m\u{1b}[49m"), Err(()) ); assert_eq!(Color::try_from("."), Ok(Color::new("", ""))); assert_eq!(Color::try_from("...."), Ok(Color::new("", ""))); assert_eq!( Color::try_from(String::from("\u{1b}[31m\u{1b}[42m.\u{1b}[39m\u{1b}[49m")), Ok(Color::new("\u{1b}[31m\u{1b}[42m", "\u{1b}[39m\u{1b}[49m")) ); assert_eq!( Color::try_from(String::from("\u{1b}[31m\u{1b}[42m...\u{1b}[39m\u{1b}[49m")), Ok(Color::new("\u{1b}[31m\u{1b}[42m", "\u{1b}[39m\u{1b}[49m")) ); assert_eq!( Color::try_from(String::from( "\u{1b}[31m\u{1b}[42m.\n.\n.\u{1b}[39m\u{1b}[49m" )), Ok(Color::new("\u{1b}[31m\u{1b}[42m", "\u{1b}[39m\u{1b}[49m")) ); assert_eq!( Color::try_from(String::from( "\u{1b}[31m\u{1b}[42m.\n.\n.\n\u{1b}[39m\u{1b}[49m" )), Ok(Color::new("\u{1b}[31m\u{1b}[42m", "\u{1b}[39m\u{1b}[49m")) ); assert_eq!( Color::try_from(String::from("\u{1b}[31m\u{1b}[42m\n\u{1b}[39m\u{1b}[49m")), Ok(Color::new("\u{1b}[31m\u{1b}[42m", "\u{1b}[39m\u{1b}[49m")) ); } #[test] fn test_rgb_color() { assert_eq!( Color::rgb_bg(255, 255, 255), Color::new("\u{1b}[48;2;255;255;255m", "\u{1b}[49m") ); assert_eq!( Color::rgb_bg(0, 255, 128), Color::new("\u{1b}[48;2;0;255;128m", "\u{1b}[49m") ); assert_eq!( Color::rgb_fg(0, 255, 128), Color::new("\u{1b}[38;2;0;255;128m", "\u{1b}[39m") ); assert_eq!( Color::rgb_fg(255, 255, 255), Color::new("\u{1b}[38;2;255;255;255m", "\u{1b}[39m") ); assert_eq!( Color::rgb_bg(255, 255, 255) | Color::rgb_fg(0, 0, 0), Color::new( "\u{1b}[48;2;255;255;255m\u{1b}[38;2;0;0;0m", "\u{1b}[49m\u{1b}[39m" ) ) } } tabled-0.18.0/src/settings/concat/mod.rs000064400000000000000000000131401046102023000161670ustar 00000000000000//! This module contains a [`Concat`] primitive which can be in order to combine 2 [`Table`]s into 1. //! //! # Example //! //! ``` //! use tabled::{Table, settings::Concat}; //! let table1 = Table::new([0, 1, 2, 3]); //! let table2 = Table::new(["A", "B", "C", "D"]); //! //! let mut table3 = table1; //! table3.with(Concat::horizontal(table2)); //! ``` use std::borrow::Cow; use papergrid::config::pos; use crate::{ grid::records::{ExactRecords, PeekableRecords, Records, RecordsMut, Resizable}, settings::TableOption, Table, }; /// [`Concat`] concatenates tables along a particular axis [Horizontal | Vertical]. /// It doesn't do any key or column comparisons like SQL's join does. /// /// When the tables has different sizes, empty cells will be created by default. /// /// [`Concat`] in horizontal mode has similar behaviour to tuples `(a, b)`. /// But it behaves on tables rather than on an actual data. /// /// [`Concat`] DOES NOT handle style merge and other configuration of 2nd table, /// it just uses 1st one as a bases. /// /// # Example /// /// #[cfg_attr(feature = "derive", doc = "```")] #[cfg_attr(not(feature = "derive"), doc = "```ignore")] /// use tabled::{Table, Tabled, settings::{Style, Concat}}; /// /// #[derive(Tabled)] /// struct Message { /// id: &'static str, /// text: &'static str, /// } /// /// #[derive(Tabled)] /// struct Department(#[tabled(rename = "department")] &'static str); /// /// let messages = [ /// Message { id: "0", text: "Hello World" }, /// Message { id: "1", text: "Do do do something", }, /// ]; /// /// let departments = [ /// Department("Admins"), /// Department("DevOps"), /// Department("R&D"), /// ]; /// /// let mut table = Table::new(messages); /// table /// .with(Concat::horizontal(Table::new(departments))) /// .with(Style::extended()); /// /// assert_eq!( /// table.to_string(), /// concat!( /// "╔════╦════════════════════╦════════════╗\n", /// "║ id ║ text ║ department ║\n", /// "╠════╬════════════════════╬════════════╣\n", /// "║ 0 ║ Hello World ║ Admins ║\n", /// "╠════╬════════════════════╬════════════╣\n", /// "║ 1 ║ Do do do something ║ DevOps ║\n", /// "╠════╬════════════════════╬════════════╣\n", /// "║ ║ ║ R&D ║\n", /// "╚════╩════════════════════╩════════════╝", /// ) /// ) /// ``` #[derive(Debug)] pub struct Concat { table: Table, mode: ConcatMode, default_cell: Cow<'static, str>, } #[derive(Debug)] enum ConcatMode { Vertical, Horizontal, } impl Concat { fn new(table: Table, mode: ConcatMode) -> Self { Self { table, mode, default_cell: Cow::Borrowed(""), } } /// Concatenate 2 tables horizontally (along axis=0) pub fn vertical(table: Table) -> Self { Self::new(table, ConcatMode::Vertical) } /// Concatenate 2 tables vertically (along axis=1) pub fn horizontal(table: Table) -> Self { Self::new(table, ConcatMode::Horizontal) } /// Sets a cell's content for cases where 2 tables has different sizes. pub fn default_cell(mut self, cell: impl Into>) -> Self { self.default_cell = cell.into(); self } } impl TableOption for Concat where R: Records + ExactRecords + Resizable + PeekableRecords + RecordsMut, { fn change(mut self, records: &mut R, _: &mut C, _: &mut D) { let count_rows = records.count_rows(); let count_cols = records.count_columns(); let rhs = &mut self.table; match self.mode { ConcatMode::Horizontal => { for _ in 0..rhs.count_columns() { records.push_column(); } for row in count_rows..rhs.count_rows() { records.push_row(); for col in 0..records.count_columns() { records.set(pos(row, col), self.default_cell.to_string()); } } for row in 0..rhs.shape().0 { for col in 0..rhs.shape().1 { let text = rhs.get_records().get_text(pos(row, col)).to_string(); let col = col + count_cols; records.set((row, col).into(), text); } } } ConcatMode::Vertical => { for _ in 0..rhs.count_rows() { records.push_row(); } for col in count_cols..rhs.shape().1 { records.push_column(); for row in 0..records.count_rows() { records.set(pos(row, col), self.default_cell.to_string()); } } for row in 0..rhs.shape().0 { for col in 0..rhs.shape().1 { let text = rhs.get_records().get_text(pos(row, col)).to_string(); let row = row + count_rows; records.set(pos(row, col), text); } } } } } } tabled-0.18.0/src/settings/disable/mod.rs000064400000000000000000000124511046102023000163270ustar 00000000000000//! This module contains a [`Remove`] structure which helps to //! remove an etheir column or row from a [`Table`]. //! //! # Example //! //! ```rust,no_run //! # use tabled::{Table, settings::{Remove, object::Rows}}; //! # let data: Vec<&'static str> = Vec::new(); //! let table = Table::new(&data).with(Remove::row(Rows::first())); //! ``` //! //! [`Table`]: crate::Table use std::marker::PhantomData; use crate::{ grid::records::{ExactRecords, Records, Resizable}, settings::{location::Location, TableOption}, }; /// Remove removes particular rows/columns from a [`Table`]. /// /// It tries to keeps track of style changes which may occur. /// But it's not guaranteed will be the way you would expect it to be. /// /// Generally you should avoid use of [`Remove`] because it's a slow function and modifies the underlying records. /// Providing correct data right away is better. /// /// # Example /// /// ``` /// use tabled::{Table, settings::{Remove, object::Rows}}; /// /// let data = vec!["Hello", "World", "!!!"]; /// /// let table = Table::new(data).with(Remove::row(Rows::new(1..2))).to_string(); /// /// assert_eq!( /// table, /// "+-------+\n\ /// | &str |\n\ /// +-------+\n\ /// | World |\n\ /// +-------+\n\ /// | !!! |\n\ /// +-------+" /// ); /// /// ``` /// [`Table`]: crate::Table #[derive(Debug)] pub struct Remove { locator: L, target: PhantomData, } impl Remove { /// Remove columns. /// /// Available locators are: /// /// - [`Columns`] /// - [`Column`] /// - [`FirstColumn`] /// - [`LastColumn`] /// - [`ByColumnName`] /// /// ```rust /// use tabled::{builder::Builder, settings::{Remove, location::ByColumnName, object::Columns}}; /// /// let mut builder = Builder::default(); /// builder.push_record(["col1", "col2", "col3"]); /// builder.push_record(["Hello", "World", "1"]); /// /// let table = builder.build() /// .with(Remove::column(ByColumnName::new("col3"))) /// .to_string(); /// /// assert_eq!( /// table, /// "+-------+-------+\n\ /// | col1 | col2 |\n\ /// +-------+-------+\n\ /// | Hello | World |\n\ /// +-------+-------+" /// ); /// ``` /// /// [`Columns`]: crate::settings::object::Columns /// [`Column`]: crate::settings::object::Column /// [`FirstColumn`]: crate::settings::object::FirstColumn /// [`LastColumn`]: crate::settings::object::LastColumn /// [`ByColumnName`]: crate::settings::location::ByColumnName pub fn column(locator: L) -> Self { Self { locator, target: PhantomData, } } } impl Remove { /// Remove rows. /// /// Available locators are: /// /// - [`Rows`] /// - [`Row`] /// - [`FirstRow`] /// - [`LastRow`] /// /// ```rust /// use tabled::{settings::{Remove, object::Rows}, builder::Builder}; /// /// let mut builder = Builder::default(); /// builder.push_record(["col1", "col2", "col3"]); /// builder.push_record(["Hello", "World", "1"]); /// /// let table = builder.build() /// .with(Remove::row(Rows::first())) /// .to_string(); /// /// assert_eq!( /// table, /// "+-------+-------+---+\n\ /// | Hello | World | 1 |\n\ /// +-------+-------+---+" /// ); /// ``` /// /// [`Rows`]: crate::settings::object::Rows /// [`Row`]: crate::settings::object::Row /// [`FirstRow`]: crate::settings::object::FirstRow /// [`LastRow`]: crate::settings::object::LastRow pub fn row(locator: L) -> Self { Self { locator, target: PhantomData, } } } /// A marker struct for [`Remove`]. #[derive(Debug)] pub struct TargetRow; /// A marker struct for [`Remove`]. #[derive(Debug)] pub struct TargetColumn; impl TableOption for Remove where L: Location, R: Records + Resizable, { fn change(mut self, records: &mut R, _: &mut C, _: &mut D) { let columns = self.locator.locate(records).into_iter().collect::>(); let mut shift = 0; for col in columns.into_iter() { if col - shift > records.count_columns() { continue; } records.remove_column(col - shift); shift += 1; } // fixme: I am pretty sure that we violate span constrains by removing rows/cols // Because span may be bigger then the max number of rows/cols } } impl TableOption for Remove where L: Location, R: ExactRecords + Resizable, { fn change(mut self, records: &mut R, _: &mut C, _: &mut D) { let rows = self.locator.locate(records).into_iter().collect::>(); let mut shift = 0; for row in rows.into_iter() { if row - shift > records.count_rows() { continue; } records.remove_row(row - shift); shift += 1; } // fixme: I am pretty sure that we violate span constrains by removing rows/cols // Because span may be bigger then the max number of rows/cols } } tabled-0.18.0/src/settings/duplicate/mod.rs000064400000000000000000000077031046102023000167020ustar 00000000000000//! This module contains an [`Dup`] setting the [`Table`]. //! //! # Example //! //! ``` //! # use tabled::{Table, settings::{Dup, object::{Columns, Rows}}}; //! # let data: Vec<&'static str> = Vec::new(); //! let mut table = Table::new(&data); //! table.with(Dup::new(Rows::first(), Columns::first())); //! ``` //! //! [`Table`]: crate::Table use crate::{ grid::config::Position, grid::records::{ExactRecords, PeekableRecords, Records, RecordsMut}, settings::{object::Object, TableOption}, }; /// [`Dup`] duplicates a given set of cells into another set of ones [`Table`]. /// /// # Example /// /// ``` /// use tabled::{Table, settings::{object::Rows, Dup}}; /// /// let data = [ /// ["1", "2", "3"], /// ["Some\nMulti\nLine\nText", "and a line", "here"], /// ["4", "5", "6"], /// ]; /// /// let mut table = Table::new(&data); /// table.with(Dup::new(Rows::single(1), Rows::single(2))); /// /// assert_eq!( /// table.to_string(), /// "+-------+------------+------+\n\ /// | 0 | 1 | 2 |\n\ /// +-------+------------+------+\n\ /// | Some | and a line | here |\n\ /// | Multi | | |\n\ /// | Line | | |\n\ /// | Text | | |\n\ /// +-------+------------+------+\n\ /// | Some | and a line | here |\n\ /// | Multi | | |\n\ /// | Line | | |\n\ /// | Text | | |\n\ /// +-------+------------+------+\n\ /// | 4 | 5 | 6 |\n\ /// +-------+------------+------+", /// ) /// ``` /// /// [`Table`]: crate::Table #[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord)] pub struct Dup { src: Src, dst: Dst, } impl Dup { /// New creates a new [`Dup`] modifier. /// /// # Example /// /// ``` /// # use tabled::{Table, settings::{Dup, object::{Columns, Rows}}}; /// # let data: Vec<&'static str> = Vec::new(); /// let mut table = Table::new(&data); /// table.with(Dup::new(Rows::first(), Columns::last())); /// ``` pub fn new(dst: Dst, src: Src) -> Self { Self { src, dst } } } impl TableOption for Dup where Dst: Object, Src: Object, R: Records + ExactRecords + PeekableRecords + RecordsMut, { fn change(self, records: &mut R, _: &mut C, _: &mut D) { let input = collect_input(records, self.src); set_cells(records, &input, self.dst); } } fn collect_input(records: &mut R, src: O) -> Vec where O: Object, R: Records + ExactRecords + PeekableRecords + RecordsMut, { let count_rows = records.count_rows(); let count_columns = records.count_columns(); let mut input = Vec::new(); for entity in src.cells(records) { for pos in entity.iter(count_rows, count_columns) { if !is_valid_cell(pos, count_rows, count_columns) { continue; } let text = records.get_text(pos).to_owned(); input.push(text); } } input } fn set_cells(records: &mut R, src: &[String], dst: O) where O: Object, R: Records + ExactRecords + PeekableRecords + RecordsMut, { if src.is_empty() { return; } let count_rows = records.count_rows(); let count_columns = records.count_columns(); for entity in dst.cells(records) { let mut source = src.iter().cycle(); for pos in entity.iter(count_rows, count_columns) { if !is_valid_cell(pos, count_rows, count_columns) { continue; } let text = source.next().unwrap().clone(); records.set(pos, text); } } } fn is_valid_cell(pos: Position, count_rows: usize, count_columns: usize) -> bool { if pos.row() > count_rows { return false; } if pos.col() > count_columns { return false; } true } tabled-0.18.0/src/settings/extract/mod.rs000064400000000000000000000173111046102023000163760ustar 00000000000000//! This module contains an [`Extract`] structure which is used to //! obtain an ordinary segment from the [`Table`]. //! //! There's a similar structure [`Highlight`] which does a highlighting a of segments. //! //! [`Table`]: crate::Table //! [`Highlight`]: crate::settings::highlight::Highlight use core::cmp::min; use core::ops::{Bound, RangeBounds, RangeFull}; use crate::{ grid::records::{ExactRecords, Records, Resizable}, settings::TableOption, }; /// Returns a new [`Table`] that reflects a segment of the referenced [`Table`] /// /// # Example /// #[cfg_attr(feature = "std", doc = "```")] #[cfg_attr(not(feature = "std"), doc = "```ignore")] /// use tabled::{Table, settings::{Format, object::Rows, Modify, Extract}}; /// /// let data = vec![ /// (0, "Grodno", true), /// (1, "Minsk", true), /// (2, "Hamburg", false), /// (3, "Brest", true), /// ]; /// /// let table = Table::new(&data) /// .with(Modify::new(Rows::new(1..)).with(Format::content(|s| format!(": {} :", s)))) /// .with(Extract::segment(1..=2, 1..)) /// .to_string(); /// /// assert_eq!(table, "+------------+----------+\n\ /// | : Grodno : | : true : |\n\ /// +------------+----------+\n\ /// | : Minsk : | : true : |\n\ /// +------------+----------+"); /// ``` /// /// [`Table`]: crate::Table #[derive(Debug)] pub struct Extract { rows: R, columns: C, } impl Extract where R: RangeBounds, C: RangeBounds, { /// Returns a new [`Table`] that reflects a segment of the referenced [`Table`] /// /// ```rust,no_run /// # use tabled::settings::Extract; /// let rows = 1..3; /// let columns = 1..; /// Extract::segment(rows, columns); /// ``` /// /// # Range /// /// A [`RangeBounds`] argument can be less than or equal to the shape of a [`Table`] /// /// If a [`RangeBounds`] argument is malformed or too large the thread will panic /// /// ```text /// // Empty Full Out of bounds /// Extract::segment(0..0, 0..0) Extract::segment(.., ..) Extract::segment(0..1, ..4) /// []. . . [O O O [O O O X] //ERROR /// . . . O O O . . . /// . . . O O O] . . . /// ``` /// /// [`Table`]: crate::Table pub fn segment(rows: R, columns: C) -> Self { Extract { rows, columns } } } impl Extract where R: RangeBounds, { /// Returns a new [`Table`] that reflects a segment of the referenced [`Table`] /// /// The segment is defined by [`RangeBounds`] for Rows /// /// ```rust,no_run /// # use tabled::settings::Extract; /// Extract::rows(1..3); /// ``` /// /// # Range /// /// A [`RangeBounds`] argument can be less than or equal to the shape of a [`Table`] /// /// If a [`RangeBounds`] argument is malformed or too large the thread will panic /// /// ```text /// // Empty Full Out of bounds /// Extract::rows(0..0) Extract::rows(..) Extract::rows(0..4) /// []. . . [O O O [O O O /// . . . O O O O O O /// . . . O O O] O O O /// X X X] // ERROR /// ``` /// /// [`Table`]: crate::Table pub fn rows(rows: R) -> Self { Extract { rows, columns: .. } } } impl Extract where C: RangeBounds, { /// Returns a new [`Table`] that reflects a segment of the referenced [`Table`] /// /// The segment is defined by [`RangeBounds`] for columns. /// /// ```rust,no_run /// # use tabled::settings::Extract; /// Extract::columns(1..3); /// ``` /// /// # Range /// /// A [`RangeBounds`] argument can be less than or equal to the shape of a [`Table`] /// /// If a [`RangeBounds`] argument is malformed or too large the thread will panic /// /// ```text /// // Empty Full Out of bounds /// Extract::columns(0..0) Extract::columns(..) Extract::columns(0..4) /// []. . . [O O O [O O O X /// . . . O O O O O O X /// . . . O O O] O O O X] // ERROR /// ``` /// /// [`Table`]: crate::Table pub fn columns(columns: C) -> Self { Extract { rows: .., columns } } } impl TableOption for Extract where R: RangeBounds + Clone, C: RangeBounds + Clone, RR: Records + ExactRecords + Resizable, { fn change(self, records: &mut RR, _: &mut Cfg, _: &mut D) { let count_rows = records.count_rows(); let count_columns = records.count_columns(); let mut rows = bounds_to_usize(self.rows.start_bound(), self.rows.end_bound(), count_rows); let mut cols = bounds_to_usize( self.columns.start_bound(), self.columns.end_bound(), count_columns, ); // Cleanup table in case if boundaries are exceeded. // // todo: can be optimized by adding a clear() method to Resizable rows.0 = min(rows.0, count_rows); cols.0 = min(cols.0, count_columns); extract(records, (count_rows, count_columns), rows, cols); } } /// Returns a new [`Grid`] that reflects a segment of the referenced [`Grid`]. /// /// # Example /// /// ```text /// grid /// +---+---+---+ /// |0-0|0-1|0-2| /// +---+---+---+ /// |1-0|1-1|1-2| /// +---+---+---+ /// |2-0|2-1|2-2| /// +---+---+---+ /// /// let rows = ..; /// let columns = ..1; /// grid.extract(rows, columns) /// /// grid /// +---+ /// |0-0| /// +---+ /// |1-0| /// +---+ /// |2-0| /// +---+ /// ``` fn extract( records: &mut R, (count_rows, count_cols): (usize, usize), (start_row, end_row): (usize, usize), (start_col, end_col): (usize, usize), ) where R: Resizable, { for (i, row) in (0..start_row).enumerate() { let row = row - i; records.remove_row(row); } let count_rows = count_rows - start_row; let end_row = end_row - start_row; for (i, row) in (end_row..count_rows).enumerate() { let row = row - i; records.remove_row(row); } for (i, col) in (0..start_col).enumerate() { let col = col - i; records.remove_column(col); } let count_cols = count_cols - start_col; let end_col = end_col - start_col; for (i, col) in (end_col..count_cols).enumerate() { let col = col - i; records.remove_column(col); } } fn bounds_to_usize( left: Bound<&usize>, right: Bound<&usize>, count_elements: usize, ) -> (usize, usize) { match (left, right) { (Bound::Included(x), Bound::Included(y)) => (*x, y + 1), (Bound::Included(x), Bound::Excluded(y)) => (*x, *y), (Bound::Included(x), Bound::Unbounded) => (*x, count_elements), (Bound::Unbounded, Bound::Unbounded) => (0, count_elements), (Bound::Unbounded, Bound::Included(y)) => (0, y + 1), (Bound::Unbounded, Bound::Excluded(y)) => (0, *y), (Bound::Excluded(_), Bound::Unbounded) | (Bound::Excluded(_), Bound::Included(_)) | (Bound::Excluded(_), Bound::Excluded(_)) => { unreachable!("A start bound can't be excluded") } } } tabled-0.18.0/src/settings/format/format_config.rs000064400000000000000000000005231046102023000202470ustar 00000000000000use crate::settings::TableOption; /// This is a struct wrapper for a lambda which changes config. #[derive(Debug)] pub struct FormatConfig(pub(crate) F); impl TableOption for FormatConfig where F: FnMut(&mut C), { fn change(mut self, _: &mut R, cfg: &mut C, _: &mut D) { (self.0)(cfg); } } tabled-0.18.0/src/settings/format/format_content.rs000064400000000000000000000045351046102023000204630ustar 00000000000000use crate::{ grid::config::Entity, grid::records::{ExactRecords, PeekableRecords, Records, RecordsMut}, settings::{CellOption, TableOption}, }; /// A lambda which formats cell content. #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] pub struct FormatContent { f: F, multiline: bool, } impl FormatContent { pub(crate) fn new(f: F) -> Self { Self { f, multiline: false, } } } impl FormatContent { /// Multiline a helper function for changing multiline content of cell. /// Using this formatting applied for all rows not to a string as a whole. /// /// ```rust,no_run /// use tabled::{Table, settings::{Format, object::Segment, Modify}}; /// /// let data: Vec<&'static str> = Vec::new(); /// let table = Table::new(&data) /// .with(Modify::new(Segment::all()).with(Format::content(|s| s.to_string()).multiline())) /// .to_string(); /// ``` pub fn multiline(mut self) -> Self { self.multiline = true; self } } impl TableOption for FormatContent where F: FnMut(&str) -> String + Clone, R: Records + ExactRecords + PeekableRecords + RecordsMut, { fn change(self, records: &mut R, cfg: &mut C, _: &mut D) { CellOption::change(self, records, cfg, Entity::Global); } } impl CellOption for FormatContent where F: FnMut(&str) -> String + Clone, R: Records + ExactRecords + PeekableRecords + RecordsMut, { fn change(mut self, records: &mut R, _: &mut C, entity: Entity) { let count_rows = records.count_rows(); let count_cols = records.count_columns(); for pos in entity.iter(count_rows, count_cols) { if !pos.is_covered((count_rows, count_cols).into()) { continue; } let content = records.get_text(pos); let content = if self.multiline { multiline(self.f.clone())(content) } else { (self.f)(content) }; records.set(pos, content); } } } fn multiline String>(mut f: F) -> impl FnMut(&str) -> String { move |s: &str| { let mut v = Vec::new(); for line in s.lines() { v.push(f(line)); } v.join("\n") } } tabled-0.18.0/src/settings/format/format_positioned.rs000064400000000000000000000031631046102023000211620ustar 00000000000000use papergrid::config::Position; use crate::{ grid::config::Entity, grid::records::{ExactRecords, PeekableRecords, Records, RecordsMut}, settings::{CellOption, TableOption}, }; /// [`FormatContentPositioned`] is like a [`FormatContent`] an abstraction over a function you can use against a cell. /// /// It different from [`FormatContent`] that it provides a row and column index. /// /// [`FormatContent`]: crate::settings::format::FormatContent #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] pub struct FormatContentPositioned(F); impl FormatContentPositioned { pub(crate) fn new(f: F) -> Self { Self(f) } } impl TableOption for FormatContentPositioned where F: FnMut(&str, Position) -> String, R: Records + ExactRecords + PeekableRecords + RecordsMut, { fn change(self, records: &mut R, cfg: &mut C, _: &mut D) { CellOption::change(self, records, cfg, Entity::Global); } } impl CellOption for FormatContentPositioned where F: FnMut(&str, Position) -> String, R: Records + ExactRecords + PeekableRecords + RecordsMut, { fn change(mut self, records: &mut R, _: &mut C, entity: Entity) { let count_rows = records.count_rows(); let count_cols = records.count_columns(); for pos in entity.iter(count_rows, count_cols) { if !pos.is_covered((count_rows, count_cols).into()) { continue; } let content = records.get_text(pos); let content = (self.0)(content, pos); records.set(pos, content); } } } tabled-0.18.0/src/settings/format/mod.rs000064400000000000000000000106201046102023000162100ustar 00000000000000//! This module contains a list of primitives to help to modify a [`Table`]. //! //! [`Table`]: crate::Table mod format_config; mod format_content; mod format_positioned; pub use format_config::FormatConfig; pub use format_content::FormatContent; pub use format_positioned::FormatContentPositioned; use papergrid::config::Position; /// A formatting function of particular cells on a [`Table`]. /// /// [`Table`]: crate::Table #[derive(Debug)] pub struct Format; impl Format { /// This function creates a new [`Format`] instance, so /// it can be used as a grid setting. /// /// # Example /// /// ``` /// use tabled::{Table, settings::{Format, object::Rows, Modify}}; /// /// let data = vec![ /// (0, "Grodno", true), /// (1, "Minsk", true), /// (2, "Hamburg", false), /// (3, "Brest", true), /// ]; /// /// let table = Table::new(&data) /// .with(Modify::new(Rows::new(1..)).with(Format::content(|s| format!(": {} :", s)))) /// .to_string(); /// /// assert_eq!( /// table, /// "+-------+-------------+-----------+\n\ /// | i32 | &str | bool |\n\ /// +-------+-------------+-----------+\n\ /// | : 0 : | : Grodno : | : true : |\n\ /// +-------+-------------+-----------+\n\ /// | : 1 : | : Minsk : | : true : |\n\ /// +-------+-------------+-----------+\n\ /// | : 2 : | : Hamburg : | : false : |\n\ /// +-------+-------------+-----------+\n\ /// | : 3 : | : Brest : | : true : |\n\ /// +-------+-------------+-----------+" /// ); /// ``` pub fn content(f: F) -> FormatContent where F: FnMut(&str) -> String, { FormatContent::new(f) } /// This function creates a new [`FormatContentPositioned`], so /// it can be used as a grid setting. /// /// It's different from [`Format::content`] as it also provides a row and column index. /// /// # Example /// /// ``` /// use tabled::{Table, settings::{Format, object::Rows, Modify}}; /// /// let data = vec![ /// (0, "Grodno", true), /// (1, "Minsk", true), /// (2, "Hamburg", false), /// (3, "Brest", true), /// ]; /// /// let table = Table::new(&data) /// .modify(Rows::single(0), Format::positioned(|_, p| p.col().to_string())) /// .to_string(); /// /// assert_eq!( /// table, /// "+---+---------+-------+\n\ /// | 0 | 1 | 2 |\n\ /// +---+---------+-------+\n\ /// | 0 | Grodno | true |\n\ /// +---+---------+-------+\n\ /// | 1 | Minsk | true |\n\ /// +---+---------+-------+\n\ /// | 2 | Hamburg | false |\n\ /// +---+---------+-------+\n\ /// | 3 | Brest | true |\n\ /// +---+---------+-------+" /// ); /// ``` pub fn positioned(f: F) -> FormatContentPositioned where F: FnMut(&str, Position) -> String, { FormatContentPositioned::new(f) } /// This function creates [`FormatConfig`] function to modify a table config. /// /// # Example /// /// ``` /// use tabled::{ /// Table, /// settings::{Format, object::Rows, Modify}, /// grid::config::ColoredConfig, /// }; /// /// let data = vec![ /// (0, "Grodno", true), /// (1, "Minsk", true), /// (2, "Hamburg", false), /// (3, "Brest", true), /// ]; /// /// let table = Table::new(&data) /// .with(Format::config(|cfg: &mut ColoredConfig| cfg.set_justification((0,1).into(), '.'))) /// .to_string(); /// /// assert_eq!( /// table, /// "+-----+---------+-------+\n\ /// | i32 | &str... | bool |\n\ /// +-----+---------+-------+\n\ /// | 0 | Grodno | true |\n\ /// +-----+---------+-------+\n\ /// | 1 | Minsk | true |\n\ /// +-----+---------+-------+\n\ /// | 2 | Hamburg | false |\n\ /// +-----+---------+-------+\n\ /// | 3 | Brest | true |\n\ /// +-----+---------+-------+" /// ); /// ``` pub fn config(f: F) -> FormatConfig { FormatConfig(f) } } // todo: Add a lambda with all arguments tabled-0.18.0/src/settings/formatting/alignment_strategy.rs000064400000000000000000000223171046102023000222210ustar 00000000000000use crate::{ grid::config::{ColoredConfig, CompactMultilineConfig, Entity}, settings::{CellOption, TableOption}, }; /// `AlignmentStrategy` is a responsible for a flow how we apply an alignment. /// It mostly matters for multiline strings. /// /// # Examples /// /// ``` /// use tabled::{ /// Table, /// settings::{ /// Style, Modify, Alignment, object::Segment, /// formatting::{AlignmentStrategy, TrimStrategy} /// } /// }; /// /// // sample_from: https://opensource.adobe.com/Spry/samples/data_region/JSONDataSetSample.html /// let json = r#" /// { /// "id": "0001", /// "type": "donut", /// "name": "Cake", /// "ppu": 0.55, /// "batters": { /// "batter": [ /// { "id": "1001", "type": "Regular" }, /// { "id": "1002", "type": "Chocolate" }, /// ] /// }, /// "topping": [ /// { "id": "5001", "type": "None" }, /// { "id": "5006", "type": "Chocolate with Sprinkles" }, /// { "id": "5003", "type": "Chocolate" }, /// { "id": "5004", "type": "Maple" } /// ] /// }"#; /// /// let mut table = Table::new(&[json]); /// table /// .with(Style::modern()) /// .with(Modify::new(Segment::all()).with(Alignment::right())) /// .with(Modify::new(Segment::all()).with(TrimStrategy::None)); /// /// println!("{}", table); /// /// assert_eq!( /// format!("\n{}", table), /// r#" /// ┌───────────────────────────────────────────────────────────────┐ /// │ &str │ /// ├───────────────────────────────────────────────────────────────┤ /// │ │ /// │ { │ /// │ "id": "0001", │ /// │ "type": "donut", │ /// │ "name": "Cake", │ /// │ "ppu": 0.55, │ /// │ "batters": { │ /// │ "batter": [ │ /// │ { "id": "1001", "type": "Regular" }, │ /// │ { "id": "1002", "type": "Chocolate" }, │ /// │ ] │ /// │ }, │ /// │ "topping": [ │ /// │ { "id": "5001", "type": "None" }, │ /// │ { "id": "5006", "type": "Chocolate with Sprinkles" }, │ /// │ { "id": "5003", "type": "Chocolate" }, │ /// │ { "id": "5004", "type": "Maple" } │ /// │ ] │ /// │ } │ /// └───────────────────────────────────────────────────────────────┘"#); /// /// table /// .with(Modify::new(Segment::all()).with(AlignmentStrategy::PerCell)) /// .with(Modify::new(Segment::all()).with(TrimStrategy::Horizontal)); /// /// assert_eq!( /// format!("\n{}", table), /// r#" /// ┌───────────────────────────────────────────────────────────────┐ /// │ &str │ /// ├───────────────────────────────────────────────────────────────┤ /// │ │ /// │ { │ /// │ "id": "0001", │ /// │ "type": "donut", │ /// │ "name": "Cake", │ /// │ "ppu": 0.55, │ /// │ "batters": { │ /// │ "batter": [ │ /// │ { "id": "1001", "type": "Regular" }, │ /// │ { "id": "1002", "type": "Chocolate" }, │ /// │ ] │ /// │ }, │ /// │ "topping": [ │ /// │ { "id": "5001", "type": "None" }, │ /// │ { "id": "5006", "type": "Chocolate with Sprinkles" }, │ /// │ { "id": "5003", "type": "Chocolate" }, │ /// │ { "id": "5004", "type": "Maple" } │ /// │ ] │ /// │ } │ /// └───────────────────────────────────────────────────────────────┘"#); /// /// table.with(Modify::new(Segment::all()).with(AlignmentStrategy::PerLine)); /// /// assert_eq!( /// format!("\n{}", table), /// r#" /// ┌───────────────────────────────────────────────────────────────┐ /// │ &str │ /// ├───────────────────────────────────────────────────────────────┤ /// │ │ /// │ { │ /// │ "id": "0001", │ /// │ "type": "donut", │ /// │ "name": "Cake", │ /// │ "ppu": 0.55, │ /// │ "batters": { │ /// │ "batter": [ │ /// │ { "id": "1001", "type": "Regular" }, │ /// │ { "id": "1002", "type": "Chocolate" }, │ /// │ ] │ /// │ }, │ /// │ "topping": [ │ /// │ { "id": "5001", "type": "None" }, │ /// │ { "id": "5006", "type": "Chocolate with Sprinkles" }, │ /// │ { "id": "5003", "type": "Chocolate" }, │ /// │ { "id": "5004", "type": "Maple" } │ /// │ ] │ /// │ } │ /// └───────────────────────────────────────────────────────────────┘"#); /// ``` #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] pub enum AlignmentStrategy { /// Apply alignment for cell content as a whole. PerCell, /// Apply alignment for each line of a cell content as a whole. PerLine, } impl CellOption for AlignmentStrategy { fn change(self, _: &mut R, cfg: &mut ColoredConfig, entity: Entity) { let on = match &self { AlignmentStrategy::PerCell => false, AlignmentStrategy::PerLine => true, }; cfg.set_line_alignment(entity, on); } } impl TableOption for AlignmentStrategy { fn change(self, records: &mut R, cfg: &mut ColoredConfig, _: &mut D) { >::change(self, records, cfg, Entity::Global) } fn hint_change(&self) -> Option { None } } impl TableOption for AlignmentStrategy { fn change(self, _: &mut R, cfg: &mut CompactMultilineConfig, _: &mut D) { let mut f = cfg.get_formatting(); match &self { AlignmentStrategy::PerCell => f.allow_lines_alignment = false, AlignmentStrategy::PerLine => f.allow_lines_alignment = true, } cfg.set_formatting(f); } fn hint_change(&self) -> Option { None } } tabled-0.18.0/src/settings/formatting/charset.rs000064400000000000000000000075361046102023000177600ustar 00000000000000use std::borrow::Cow; use papergrid::config::pos; use crate::{ grid::config::Entity, grid::records::{ExactRecords, PeekableRecords, Records, RecordsMut}, settings::{CellOption, TableOption}, }; /// A structure to handle special chars. #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] pub struct Charset; impl Charset { /// Returns [`CleanCharset`] which removes all `\t` and `\r` occurrences. /// /// Notice that tab is just removed rather then being replaced with spaces. /// You might be better call [`TabSize`] first if you not expect such behavior. /// /// # Example /// /// ``` /// use tabled::{Table, settings::formatting::Charset}; /// /// let text = "Some\ttext\t\twith \\tabs"; /// /// let mut table = Table::new([text]); /// table.with(Charset::clean()); /// /// assert_eq!( /// table.to_string(), /// "+--------------------+\n\ /// | &str |\n\ /// +--------------------+\n\ /// | Sometextwith \\tabs |\n\ /// +--------------------+" /// ) /// ``` /// /// [`TabSize`]: crate::settings::formatting::TabSize pub fn clean() -> CleanCharset { CleanCharset } } /// [`CleanCharset`] removes all `\t` and `\r` occurrences. /// /// # Example /// /// ``` /// use std::iter::FromIterator; /// use tabled::{ /// Table, builder::Builder, /// settings::formatting::Charset, /// }; /// /// let text = "Some text which was created on windows \r\n yes they use this \\r\\n"; /// /// let mut builder = Builder::from(Table::from_iter([[text]])); /// builder.insert_record(0, ["win. text"]); /// /// let mut table = builder.build(); /// table.with(Charset::clean()); /// /// assert_eq!( /// table.to_string(), /// "+-----------------------------------------+\n\ /// | win. text |\n\ /// +-----------------------------------------+\n\ /// | Some text which was created on windows |\n\ /// | yes they use this \\r\\n |\n\ /// +-----------------------------------------+" /// ) /// ``` #[derive(Debug, Default, Clone)] pub struct CleanCharset; impl CleanCharset { /// Removes all symbols which may break the layout such as `\t`, `\r` and more. /// /// Notice that tab is just removed rather then being replaced with spaces. /// /// # Example /// /// ``` /// use tabled::settings::formatting::CleanCharset; /// /// assert_eq!( /// CleanCharset::clean("Some\ttext\t\twith \\tabs\r\nSome"), /// "Sometextwith \\tabs\nSome" /// ) /// ``` pub fn clean(s: &str) -> Cow<'_, str> { Cow::Owned(clean_charset(s)) } } impl TableOption for CleanCharset where R: Records + ExactRecords + RecordsMut + PeekableRecords, { fn change(self, records: &mut R, _: &mut C, _: &mut D) { for row in 0..records.count_rows() { for col in 0..records.count_columns() { let pos = pos(row, col); let text = records.get_text(pos); let text = clean_charset(text); records.set(pos, text); } } } } impl CellOption for CleanCharset where R: Records + ExactRecords + PeekableRecords + RecordsMut, { fn change(self, records: &mut R, _: &mut C, entity: Entity) { let count_rows = records.count_rows(); let count_cols = records.count_columns(); for pos in entity.iter(count_rows, count_cols) { let text = records.get_text(pos); let text = clean_charset(text); records.set(pos, text); } } } fn clean_charset(text: &str) -> String { // It's enough for covering '\t' and '\r' // as well as a list of other unwanted escapes. text.replace(|c| c != '\n' && c < ' ', "") } tabled-0.18.0/src/settings/formatting/justification.rs000064400000000000000000000064321046102023000211740ustar 00000000000000use crate::{ grid::{ ansi::ANSIBuf, config::{ColoredConfig, Entity}, }, settings::{CellOption, Color, TableOption}, }; /// Set a justification character and a color. /// /// Default value is `' '` (``) with no color. /// /// # Examples /// /// Setting a justification character. /// /// ``` /// use tabled::{ /// Table, /// settings::formatting::Justification, /// }; /// /// let mut table = Table::new(&[("Hello", ""), ("", "World")]); /// table.with(Justification::new('#')); /// /// assert_eq!( /// table.to_string(), /// "+-------+-------+\n\ /// | &str# | &str# |\n\ /// +-------+-------+\n\ /// | Hello | ##### |\n\ /// +-------+-------+\n\ /// | ##### | World |\n\ /// +-------+-------+" /// ); /// ``` /// /// Setting a justification color. /// /// ``` /// use tabled::{ /// Table, /// settings::{formatting::Justification, Color}, /// }; /// /// let mut table = Table::new(&[("Hello", ""), ("", "World")]); /// table.with(Justification::default().color(Color::BG_BRIGHT_RED)); /// /// assert_eq!( /// table.to_string(), /// "+-------+-------+\n\ /// | &str\u{1b}[101m \u{1b}[49m | &str\u{1b}[101m \u{1b}[49m |\n\ /// +-------+-------+\n\ /// | Hello | \u{1b}[101m \u{1b}[49m |\n\ /// +-------+-------+\n\ /// | \u{1b}[101m \u{1b}[49m | World |\n\ /// +-------+-------+" /// ); /// ``` /// /// Use different justification for different columns. /// /// ``` /// use tabled::{ /// Table, /// settings::{Modify, object::Columns, formatting::Justification}, /// }; /// /// let mut table = Table::new(&[("Hello", ""), ("", "World")]); /// table.with(Modify::new(Columns::single(0)).with(Justification::new('#'))); /// table.with(Modify::new(Columns::single(1)).with(Justification::new('@'))); /// /// assert_eq!( /// table.to_string(), /// "+-------+-------+\n\ /// | &str# | &str@ |\n\ /// +-------+-------+\n\ /// | Hello | @@@@@ |\n\ /// +-------+-------+\n\ /// | ##### | World |\n\ /// +-------+-------+" /// ); /// ``` /// #[derive(Debug, Default, Clone)] pub struct Justification { c: Option, color: Option, } impl Justification { /// Creates new [`Justification`] object. pub fn new(c: char) -> Self { Self { c: Some(c), color: None, } } /// Sets a color for a justification. pub fn color(self, color: Color) -> Self { Self { c: self.c, color: Some(color.into()), } } } impl TableOption for Justification { fn change(self, _: &mut R, cfg: &mut ColoredConfig, _: &mut D) { let c = self.c.unwrap_or(' '); let color = self.color; cfg.set_justification(Entity::Global, c); cfg.set_justification_color(Entity::Global, color); } fn hint_change(&self) -> Option { None } } impl CellOption for Justification { fn change(self, _: &mut R, cfg: &mut ColoredConfig, entity: Entity) { let c = self.c.unwrap_or(' '); let color = self.color; cfg.set_justification(entity, c); cfg.set_justification_color(entity, color); } fn hint_change(&self) -> Option { None } } tabled-0.18.0/src/settings/formatting/mod.rs000064400000000000000000000012301046102023000170670ustar 00000000000000//! This module contains settings for render strategy of papergrid. //! //! - [`TrimStrategy`] and [`AlignmentStrategy`] allows to set [`Alignment`] settings. //! - [`TabSize`] sets a default tab size. //! - [`Charset`] responsible for special char treatment. //! - [`Justification`] responsible for justification space of content. //! //! [`Alignment`]: crate::settings::Alignment mod alignment_strategy; mod charset; mod justification; mod tab_size; mod trim_strategy; pub use alignment_strategy::AlignmentStrategy; pub use charset::{Charset, CleanCharset}; pub use justification::Justification; pub use tab_size::TabSize; pub use trim_strategy::TrimStrategy; tabled-0.18.0/src/settings/formatting/tab_size.rs000064400000000000000000000031421046102023000201140ustar 00000000000000use papergrid::config::pos; use crate::{ grid::records::{ExactRecords, PeekableRecords, Records, RecordsMut}, settings::TableOption, }; /// Set a tab size. /// /// The size is used in order to calculate width correctly. /// /// Default value is 4 (basically 1 '\t' equals 4 spaces). /// /// IMPORTANT: The tab character might be not present in output, /// it might be replaced by spaces. /// /// # Example /// /// ``` /// use tabled::{Table, settings::formatting::TabSize}; /// /// let text = "Some\ttext\t\twith \\tabs"; /// /// let mut table = Table::new([text]); /// table.with(TabSize::new(4)); /// /// assert_eq!( /// table.to_string(), /// "+--------------------------------+\n\ /// | &str |\n\ /// +--------------------------------+\n\ /// | Some text with \\tabs |\n\ /// +--------------------------------+" /// ) /// ``` #[derive(Debug, Default, Clone)] pub struct TabSize(usize); impl TabSize { /// Creates new [`TabSize`] object. pub fn new(size: usize) -> Self { Self(size) } } impl TableOption for TabSize where R: Records + ExactRecords + RecordsMut + PeekableRecords, { fn change(self, records: &mut R, _: &mut C, _: &mut D) { let tab_size = self.0; for row in 0..records.count_rows() { for col in 0..records.count_columns() { let pos = pos(row, col); let text = records.get_text(pos); let text = text.replace('\t', &" ".repeat(tab_size)); records.set(pos, text); } } } } tabled-0.18.0/src/settings/formatting/trim_strategy.rs000064400000000000000000000070151046102023000212140ustar 00000000000000use crate::{ grid::config::ColoredConfig, grid::config::Entity, settings::{CellOption, TableOption}, }; /// `TrimStrategy` determines if it's allowed to use empty space while doing [`Alignment`]. /// /// # Examples /// /// ``` /// use tabled::{ /// Table, /// settings::{ /// Style, Modify, Alignment, object::Segment, /// formatting::{TrimStrategy, AlignmentStrategy} /// } /// }; /// /// let mut table = Table::new(&[" Hello World"]); /// table /// .with(Style::modern()) /// .with( /// Modify::new(Segment::all()) /// .with(Alignment::left()) /// .with(TrimStrategy::Horizontal) /// ); /// /// // Note that nothing was changed exactly. /// /// assert_eq!( /// table.to_string(), /// "┌────────────────┐\n\ /// │ &str │\n\ /// ├────────────────┤\n\ /// │ Hello World │\n\ /// └────────────────┘" /// ); /// /// // To trim lines you would need also set [`AlignmentStrategy`]. /// table.with(Modify::new(Segment::all()).with(AlignmentStrategy::PerLine)); /// /// assert_eq!( /// table.to_string(), /// "┌────────────────┐\n\ /// │ &str │\n\ /// ├────────────────┤\n\ /// │ Hello World │\n\ /// └────────────────┘" /// ); /// /// let mut table = Table::new(&[" \n\n\n Hello World"]); /// table /// .with(Style::modern()) /// .with( /// Modify::new(Segment::all()) /// .with(Alignment::center()) /// .with(Alignment::top()) /// .with(TrimStrategy::Vertical) /// ); /// /// assert_eq!( /// table.to_string(), /// "┌─────────────────┐\n\ /// │ &str │\n\ /// ├─────────────────┤\n\ /// │ Hello World │\n\ /// │ │\n\ /// │ │\n\ /// │ │\n\ /// └─────────────────┘" /// ); /// ``` /// /// [`Alignment`]: crate::settings::Alignment #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] pub enum TrimStrategy { /// Allow vertical trim. Vertical, /// Allow horizontal trim. Horizontal, /// Allow horizontal and vertical trim. Both, /// Doesn't allow any trim. None, } impl CellOption for TrimStrategy { fn change(self, _: &mut R, cfg: &mut ColoredConfig, entity: Entity) { match self { TrimStrategy::Vertical => { cfg.set_trim_vertical(entity, true); } TrimStrategy::Horizontal => { cfg.set_trim_horizontal(entity, true); } TrimStrategy::Both => { cfg.set_trim_horizontal(entity, true); cfg.set_trim_vertical(entity, true); } TrimStrategy::None => { cfg.set_trim_horizontal(entity, false); cfg.set_trim_vertical(entity, false); } } } } impl TableOption for TrimStrategy { fn change(self, records: &mut R, cfg: &mut ColoredConfig, _: &mut D) { >::change(self, records, cfg, Entity::Global) } fn hint_change(&self) -> Option { None } } tabled-0.18.0/src/settings/height/cell_height_increase.rs000064400000000000000000000057621046102023000215440ustar 00000000000000use crate::{ grid::config::ColoredConfig, grid::config::Entity, grid::dimension::CompleteDimensionVecRecords, grid::records::{ExactRecords, IntoRecords, PeekableRecords, Records, RecordsMut}, grid::util::string::count_lines, settings::{measurement::Measurement, peaker::Peaker, CellOption, Height, TableOption}, }; use super::TableHeightIncrease; /// A modification for cell/table to increase its height. /// /// If used for a [`Table`] [`PriorityNone`] is used. /// /// [`PriorityNone`]: crate::settings::peaker::PriorityNone /// [`Table`]: crate::Table #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] pub struct CellHeightIncrease { height: W, } impl CellHeightIncrease { /// Creates a new object of the structure. pub fn new(height: W) -> Self where W: Measurement, { Self { height } } /// The priority makes sense only for table, so the function /// converts it to [`TableHeightIncrease`] with a given priority. pub fn priority

(self, priority: P) -> TableHeightIncrease where P: Peaker, W: Measurement, { TableHeightIncrease::new(self.height).priority(priority) } } impl CellOption for CellHeightIncrease where W: Measurement, R: Records + ExactRecords + PeekableRecords + RecordsMut, for<'a> &'a R: Records, for<'a> <<&'a R as Records>::Iter as IntoRecords>::Cell: AsRef, { fn change(self, records: &mut R, cfg: &mut ColoredConfig, entity: Entity) { let height = self.height.measure(&*records, cfg); let count_rows = records.count_rows(); let count_columns = records.count_columns(); for pos in entity.iter(count_rows, count_columns) { if !pos.is_covered((count_rows, count_columns).into()) { continue; } let text = records.get_text(pos); let cell_height = count_lines(text); if cell_height >= height { continue; } let content = add_lines(text, height - cell_height); records.set(pos, content); } } } impl TableOption> for CellHeightIncrease where W: Measurement, R: Records + ExactRecords + PeekableRecords, for<'a> &'a R: Records, for<'a> <<&'a R as Records>::Iter as IntoRecords>::Cell: AsRef, { fn change( self, records: &mut R, cfg: &mut ColoredConfig, dims: &mut CompleteDimensionVecRecords<'_>, ) { let height = self.height.measure(&*records, cfg); TableHeightIncrease::new(height).change(records, cfg, dims) } fn hint_change(&self) -> Option { Some(Entity::Row(0)) } } fn add_lines(s: &str, n: usize) -> String { let mut text = String::with_capacity(s.len() + n); text.push_str(s); text.extend(std::iter::repeat('\n').take(n)); text } tabled-0.18.0/src/settings/height/cell_height_limit.rs000064400000000000000000000056271046102023000210710ustar 00000000000000use crate::{ grid::{ config::{ColoredConfig, Entity}, dimension::CompleteDimensionVecRecords, records::{ExactRecords, IntoRecords, PeekableRecords, Records, RecordsMut}, util::string::{count_lines, get_lines}, }, settings::{measurement::Measurement, peaker::Peaker, CellOption, Height, TableOption}, }; use super::table_height_limit::TableHeightLimit; /// A modification for cell/table to increase its height. /// /// If used for a [`Table`] [`PriorityNone`] is used. /// /// [`PriorityNone`]: crate::settings::peaker::PriorityNone /// [`Table`]: crate::Table #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] pub struct CellHeightLimit { height: W, } impl CellHeightLimit { /// Constructs a new object. pub fn new(height: W) -> Self where W: Measurement, { Self { height } } /// Set's a priority by which the limit logic will be applied. pub fn priority

(self, priority: P) -> TableHeightLimit where P: Peaker, W: Measurement, { TableHeightLimit::new(self.height).priority(priority) } } impl CellOption for CellHeightLimit where W: Measurement, R: Records + ExactRecords + PeekableRecords + RecordsMut, for<'a> &'a R: Records, for<'a> <<&'a R as Records>::Iter as IntoRecords>::Cell: AsRef, { fn change(self, records: &mut R, cfg: &mut ColoredConfig, entity: Entity) { let height = self.height.measure(&*records, cfg); let count_rows = records.count_rows(); let count_columns = records.count_columns(); for pos in entity.iter(count_rows, count_columns) { if !pos.is_covered((count_rows, count_columns).into()) { continue; } let text = records.get_text(pos); let count_lines = count_lines(text); if count_lines <= height { continue; } let content = limit_lines(text, height); records.set(pos, content); } } } impl TableOption> for CellHeightLimit where W: Measurement, R: Records + ExactRecords + PeekableRecords + RecordsMut, for<'a> &'a R: Records, for<'a> <<&'a R as Records>::Iter as IntoRecords>::Cell: AsRef, { fn change( self, records: &mut R, cfg: &mut ColoredConfig, dims: &mut CompleteDimensionVecRecords<'_>, ) { let height = self.height.measure(&*records, cfg); TableHeightLimit::new(height).change(records, cfg, dims) } } fn limit_lines(s: &str, n: usize) -> String { let mut text = String::new(); for (i, line) in get_lines(s).take(n).enumerate() { if i > 0 { text.push('\n'); } text.push_str(&line); } text } tabled-0.18.0/src/settings/height/height_list.rs000064400000000000000000000020631046102023000177160ustar 00000000000000use std::iter::FromIterator; use crate::{ grid::dimension::CompleteDimensionVecRecords, grid::records::{ExactRecords, Records}, settings::TableOption, }; /// A structure used to set [`Table`] height via a list of rows heights. /// /// [`Table`]: crate::Table #[derive(Debug)] pub struct HeightList { list: Vec, } impl HeightList { /// Creates a new object. pub fn new(list: Vec) -> Self { Self { list } } } impl From> for HeightList { fn from(list: Vec) -> Self { Self::new(list) } } impl FromIterator for HeightList { fn from_iter>(iter: T) -> Self { Self::new(iter.into_iter().collect()) } } impl TableOption> for HeightList where R: ExactRecords + Records, { fn change(self, records: &mut R, _: &mut C, dims: &mut CompleteDimensionVecRecords<'_>) { if self.list.len() < records.count_rows() { return; } dims.set_heights(self.list); } } tabled-0.18.0/src/settings/height/mod.rs000064400000000000000000000166461046102023000162060ustar 00000000000000//! The module contains [`Height`] structure which is responsible for a table and cell height. mod cell_height_increase; mod cell_height_limit; mod height_list; mod table_height_increase; mod table_height_limit; mod util; use crate::settings::measurement::Measurement; pub use cell_height_increase::CellHeightIncrease; pub use cell_height_limit::CellHeightLimit; pub use height_list::HeightList; pub use table_height_increase::TableHeightIncrease; pub use table_height_limit::TableHeightLimit; /// Height is a abstract factory for height settings. /// /// # Example /// /// ``` /// use tabled::{Table, settings::{Height, Settings}}; /// /// let data = vec![ /// ("Some data", "here", "and here"), /// ("Some data on a next", "line", "right here"), /// ]; /// /// let table = Table::new(data) /// .with(Settings::new(Height::limit(10), Height::increase(10))) /// .to_string(); /// /// assert_eq!( /// table, /// "+---------------------+------+------------+\n\ /// | &str | &str | &str |\n\ /// | | | |\n\ /// +---------------------+------+------------+\n\ /// | Some data | here | and here |\n\ /// | | | |\n\ /// +---------------------+------+------------+\n\ /// | Some data on a next | line | right here |\n\ /// | | | |\n\ /// +---------------------+------+------------+", /// ) /// ``` #[derive(Debug)] pub struct Height; impl Height { /// Create [`CellHeightIncrease`] to set a table/cell height. /// /// # Example /// /// ## Cell height /// /// ``` /// use tabled::{Table, settings::{Height, Modify, object::Columns}}; /// /// let data = vec![ /// ("Some data", "here", "and here"), /// ("Some data on a next", "line", "right here"), /// ]; /// /// let table = Table::new(data) /// .with(Modify::new(Columns::first()).with(Height::increase(5))) /// .to_string(); /// /// assert_eq!( /// table, /// "+---------------------+------+------------+\n\ /// | &str | &str | &str |\n\ /// | | | |\n\ /// | | | |\n\ /// | | | |\n\ /// | | | |\n\ /// +---------------------+------+------------+\n\ /// | Some data | here | and here |\n\ /// | | | |\n\ /// | | | |\n\ /// | | | |\n\ /// | | | |\n\ /// +---------------------+------+------------+\n\ /// | Some data on a next | line | right here |\n\ /// | | | |\n\ /// | | | |\n\ /// | | | |\n\ /// | | | |\n\ /// +---------------------+------+------------+" /// ) /// ``` /// /// ## Table height /// /// ``` /// use tabled::{Table, settings::Height}; /// /// let data = vec![ /// ("Some data", "here", "and here"), /// ("Some data on a next", "line", "right here"), /// ]; /// /// let table = Table::new(data) /// .with(Height::increase(10)) /// .to_string(); /// /// assert_eq!( /// table, /// "+---------------------+------+------------+\n\ /// | &str | &str | &str |\n\ /// | | | |\n\ /// +---------------------+------+------------+\n\ /// | Some data | here | and here |\n\ /// | | | |\n\ /// +---------------------+------+------------+\n\ /// | Some data on a next | line | right here |\n\ /// | | | |\n\ /// +---------------------+------+------------+", /// ) /// ``` pub fn increase>(height: W) -> CellHeightIncrease { CellHeightIncrease::new(height) } /// Create [`CellHeightLimit`] to set a table/cell height. /// /// # Example /// /// ## Cell height /// /// ``` /// use tabled::{Table, settings::{Height, Modify, object::Columns}}; /// /// let data = vec![ /// ("Some\ndata", "here", "and here"), /// ("Some\ndata on a next", "line", "right here"), /// ]; /// /// let table = Table::new(data) /// .with(Modify::new(Columns::first()).with(Height::limit(1))) /// .to_string(); /// /// assert_eq!( /// table, /// "+------+------+------------+\n\ /// | &str | &str | &str |\n\ /// +------+------+------------+\n\ /// | Some | here | and here |\n\ /// +------+------+------------+\n\ /// | Some | line | right here |\n\ /// +------+------+------------+" /// ) /// ``` /// /// ## Table height /// /// ``` /// use tabled::{Table, settings::Height}; /// /// let data = vec![ /// ("Some\ndata", "here", "and here"), /// ("Some\ndata on a next", "line", "right here"), /// ]; /// /// let table = Table::new(&data) /// .with(Height::limit(6)) /// .to_string(); /// /// assert_eq!( /// table, /// "+------+------+------------+\n\ /// +------+------+------------+\n\ /// | Some | here | and here |\n\ /// +------+------+------------+\n\ /// | Some | line | right here |\n\ /// +------+------+------------+", /// ); /// /// let table = Table::new(&data) /// .with(Height::limit(1)) /// .to_string(); /// /// assert_eq!( /// table, /// "+--+--+--+\n\ /// +--+--+--+\n\ /// +--+--+--+\n\ /// +--+--+--+", /// ); /// ``` pub fn limit>(height: W) -> CellHeightLimit { CellHeightLimit::new(height) } /// Create [`HeightList`] to set a table height to a constant list of row heights. /// /// Notice if you provide a list with `.len()` less than `Table::count_rows` then it will have no affect. /// /// # Example /// /// ``` /// use tabled::{Table, settings::{Height, Modify, object::Columns}}; /// /// let data = vec![ /// ("Some\ndata", "here", "and here"), /// ("Some\ndata on a next", "line", "right here"), /// ]; /// /// let table = Table::new(data) /// .with(Height::list([1, 0, 2])) /// .to_string(); /// /// assert_eq!( /// table, /// "+----------------+------+------------+\n\ /// | &str | &str | &str |\n\ /// +----------------+------+------------+\n\ /// +----------------+------+------------+\n\ /// | Some | line | right here |\n\ /// | data on a next | | |\n\ /// +----------------+------+------------+", /// ) /// ``` pub fn list>(rows: I) -> HeightList { HeightList::new(rows.into_iter().collect()) } } tabled-0.18.0/src/settings/height/table_height_increase.rs000064400000000000000000000044201046102023000217020ustar 00000000000000use crate::{ grid::{ config::{ColoredConfig, Entity}, dimension::CompleteDimensionVecRecords, records::{ExactRecords, IntoRecords, PeekableRecords, Records}, }, settings::{ measurement::Measurement, peaker::{Peaker, PriorityNone}, Height, TableOption, }, }; use super::util::get_table_height; /// A modification of a table to increase the table height. #[derive(Debug, Clone)] pub struct TableHeightIncrease { height: W, priority: P, } impl TableHeightIncrease { /// Creates a new object. pub fn new(height: W) -> Self where W: Measurement, { Self { height, priority: PriorityNone::default(), } } /// Sets a different priority logic. pub fn priority

(self, priority: P) -> TableHeightIncrease where P: Peaker, { TableHeightIncrease { priority, height: self.height, } } } impl TableOption> for TableHeightIncrease where W: Measurement, P: Peaker + Clone, R: Records + ExactRecords + PeekableRecords, for<'a> &'a R: Records, for<'a> <<&'a R as Records>::Iter as IntoRecords>::Cell: AsRef, { fn change( self, records: &mut R, cfg: &mut ColoredConfig, dims: &mut CompleteDimensionVecRecords<'_>, ) { if records.count_rows() == 0 || records.count_columns() == 0 { return; } let height = self.height.measure(&*records, cfg); let (total, mut heights) = get_table_height(&*records, cfg); if total >= height { return; } get_increase_list(&mut heights, height, total, self.priority); dims.set_heights(heights); } fn hint_change(&self) -> Option { Some(Entity::Row(0)) } } fn get_increase_list

(list: &mut [usize], total: usize, mut current: usize, mut peaker: P) where P: Peaker, { while current != total { let col = match peaker.peak(&[], list) { Some(col) => col, None => break, }; list[col] += 1; current += 1; } } tabled-0.18.0/src/settings/height/table_height_limit.rs000064400000000000000000000057551046102023000212430ustar 00000000000000use crate::{ grid::{ config::ColoredConfig, dimension::CompleteDimensionVecRecords, records::{ExactRecords, IntoRecords, PeekableRecords, Records, RecordsMut}, util::string::{count_lines, get_lines}, }, settings::{ measurement::Measurement, peaker::{Peaker, PriorityNone}, Height, TableOption, }, }; use super::util::get_table_height; /// A modification of a table to decrease the table height. #[derive(Debug)] pub struct TableHeightLimit { height: W, priority: P, } impl TableHeightLimit { /// Creates a new object. pub fn new(height: W) -> Self where W: Measurement, { Self { height, priority: PriorityNone::default(), } } /// Sets a different priority logic. pub fn priority

(self, priority: P) -> TableHeightLimit where P: Peaker, { TableHeightLimit { priority, height: self.height, } } } impl TableOption> for TableHeightLimit where W: Measurement, P: Peaker + Clone, R: Records + ExactRecords + PeekableRecords + RecordsMut, for<'a> &'a R: Records, for<'a> <<&'a R as Records>::Iter as IntoRecords>::Cell: AsRef, { fn change( self, records: &mut R, cfg: &mut ColoredConfig, dims: &mut CompleteDimensionVecRecords<'_>, ) { let count_rows = records.count_rows(); let count_cols = records.count_columns(); if count_rows == 0 || count_cols == 0 { return; } let height = self.height.measure(&*records, cfg); let (total, mut heights) = get_table_height(&*records, cfg); if total <= height { return; } decrease_list(&mut heights, total, height, self.priority); for (row, &height) in heights.iter().enumerate() { for col in 0..count_cols { let text = records.get_text((row, col).into()); let count_lines = count_lines(text); if count_lines <= height { continue; } let text = limit_lines(text, height); records.set((row, col).into(), text); } } dims.set_heights(heights); } } fn decrease_list

(list: &mut [usize], total: usize, mut value: usize, mut peaker: P) where P: Peaker, { while value != total { let p = peaker.peak(&[], list); let row = match p { Some(row) => row, None => break, }; list[row] -= 1; value += 1; } } fn limit_lines(s: &str, n: usize) -> String { let mut text = String::new(); for (i, line) in get_lines(s).take(n).enumerate() { if i > 0 { text.push('\n'); } text.push_str(&line); } text } tabled-0.18.0/src/settings/height/util.rs000064400000000000000000000012321046102023000163650ustar 00000000000000use crate::grid::{ config::SpannedConfig, dimension::SpannedGridDimension, records::{ExactRecords, IntoRecords, Records}, }; pub(crate) fn get_table_height(records: R, cfg: &SpannedConfig) -> (usize, Vec) where R: Records + ExactRecords, ::Cell: AsRef, { let count_horizontals = cfg.count_horizontal(records.count_rows()); let margin = cfg.get_margin(); let margin_size = margin.top.size + margin.bottom.size; let list = SpannedGridDimension::height(records, cfg); let total = list.iter().sum::(); let total = total + count_horizontals + margin_size; (total, list) } tabled-0.18.0/src/settings/highlight/mod.rs000064400000000000000000000326621046102023000167010ustar 00000000000000//! This module contains a [`Highlight`] primitive, which helps //! changing a [`Border`] style of any segment on a [`Table`]. //! //! [`Table`]: crate::Table use std::collections::HashSet; use crate::{ grid::{ ansi::ANSIBuf, config::{Border, ColoredConfig, Entity, Position, SpannedConfig}, records::{ExactRecords, Records}, }, settings::{ object::Object, style::{Border as ConstBorder, BorderColor}, Color, TableOption, }, }; /// Highlight modifies a table style by changing a border of a target [`Table`] segment. /// /// It basically highlights outer border of a given segment. /// /// # Example /// /// ``` /// use tabled::{ /// Table, /// settings::{ /// Highlight, Border, Style, /// object::{Segment, Object} /// } /// }; /// /// let data = [ /// ("ELF", "Extensible Linking Format", true), /// ("DWARF", "", true), /// ("PE", "Portable Executable", false), /// ]; /// /// let table = Table::new(data.iter().enumerate()) /// .with(Style::markdown()) /// .with(Highlight::outline(Segment::all().not((0,0).and((1, 0)).and((0, 1)).and((0, 3))), '*')) /// .to_string(); /// /// assert_eq!( /// table, /// concat!( /// " ***************************** \n", /// "| usize | &str * &str * bool |\n", /// "|-------*********---------------------------*********\n", /// "| 0 * ELF | Extensible Linking Format | true *\n", /// "********* *\n", /// "* 1 | DWARF | | true *\n", /// "* *\n", /// "* 2 | PE | Portable Executable | false *\n", /// "*****************************************************", /// ), /// ); /// ``` /// /// [`Table`]: crate::Table #[derive(Debug)] pub struct Highlight { target: O, border: Option>, color: Option>, } impl Highlight { /// Build a new instance of [`Highlight`] with '*' char being used as changer. /// /// BE AWARE: if target exceeds boundaries it may panic. pub const fn new(target: O) -> Self { Self::_new(target, None, None) } /// Build a new instance of [`Highlight`], /// highlighting by a character. /// /// BE AWARE: if target exceeds boundaries it may panic. pub const fn outline(target: O, c: char) -> Self { Self::_new(target, Some(Border::filled(c)), None) } /// Build a new instance of [`Highlight`], /// highlighting by a color and a given character for a border. /// /// BE AWARE: if target exceeds boundaries it may panic. pub fn colored(target: O, color: Color) -> Self { let color = Border::filled(&color).cloned().convert(); Self::_new(target, None, Some(color)) } /// Set a border for a [`Highlight`]. pub fn border(self, border: ConstBorder) -> Self { let border = border.into_inner(); Self { target: self.target, border: Some(border), color: self.color, } } /// Set a border color for a [`Highlight`]. pub fn color(self, border: BorderColor) -> Self { let border = border.into_inner(); let border = border.convert(); Self { target: self.target, border: self.border, color: Some(border), } } /// Build a new instance of [`Highlight`] /// /// BE AWARE: if target exceeds boundaries it may panic. const fn _new(target: O, border: Option>, color: Option>) -> Self { Self { target, border, color, } } } impl TableOption for Highlight where O: Object, R: Records + ExactRecords, { fn change(self, records: &mut R, cfg: &mut ColoredConfig, _: &mut D) { let count_rows = records.count_rows(); let count_cols = records.count_columns(); let cells = self.target.cells(records); let segments = split_segments(cells, count_rows, count_cols); match (self.border, self.color) { (None, Some(color)) => { for sector in segments { set_border_color(cfg, §or, &color); } } (Some(border), None) => { for sector in segments { set_border(cfg, §or, border); } } (Some(border), Some(color)) => { for sector in segments { set_border(cfg, §or, border); set_border_color(cfg, §or, &color); } } (None, None) => { // noop } } } fn hint_change(&self) -> Option { None } } fn set_border_color(cfg: &mut SpannedConfig, sector: &HashSet, border: &Border) { if sector.is_empty() { return; } let color = border.clone(); for &p in sector { let border = build_cell_border(sector, p, &color); cfg.set_border_color(p, border); } } fn split_segments( cells: impl Iterator, count_rows: usize, count_cols: usize, ) -> Vec> { let mut segments: Vec> = Vec::new(); for entity in cells { for cell in entity.iter(count_rows, count_cols) { let found_segment = segments .iter_mut() .find(|s| s.iter().any(|&c| is_cell_connected(cell, c))); match found_segment { Some(segment) => { let _ = segment.insert(cell); } None => { let mut segment = HashSet::new(); let _ = segment.insert(cell); segments.push(segment); } } } } let mut squashed_segments: Vec> = Vec::new(); while !segments.is_empty() { let mut segment = segments.remove(0); let mut i = 0; while i < segments.len() { if is_segment_connected(&segment, &segments[i]) { segment.extend(&segments[i]); let _ = segments.remove(i); } else { i += 1; } } squashed_segments.push(segment); } squashed_segments } fn is_cell_connected(p1: Position, p2: Position) -> bool { if p1.col() == p2.col() { let up = p1.row() == p2.row() + 1; let down = p2.row() > 0 && p1.row() == p2.row() - 1; if up || down { return true; } } if p1.row() == p2.row() { let left = p2.col() > 0 && p1.col() == p2.col() - 1; let right = p1.col() == p2.col() + 1; if left || right { return true; } } false } fn is_segment_connected(segment1: &HashSet, segment2: &HashSet) -> bool { for &cell1 in segment1.iter() { for &cell2 in segment2.iter() { if is_cell_connected(cell1, cell2) { return true; } } } false } fn set_border(cfg: &mut SpannedConfig, sector: &HashSet, border: Border) { if sector.is_empty() { return; } for &pos in sector { let border = build_cell_border(sector, pos, &border); cfg.set_border(pos, border); } } fn build_cell_border(sector: &HashSet, p: Position, border: &Border) -> Border where T: Default + Clone, { let has_top_neighbor = has_top_neighbor(sector, p); let has_bottom_neighbor = has_bottom_neighbor(sector, p); let has_left_neighbor = has_left_neighbor(sector, p); let has_right_neighbor = has_right_neighbor(sector, p); let has_left_top_neighbor = has_left_top_neighbor(sector, p); let has_right_top_neighbor = has_right_top_neighbor(sector, p); let has_left_bottom_neighbor = has_left_bottom_neighbor(sector, p); let has_right_bottom_neighbor = has_right_bottom_neighbor(sector, p); let mut b = Border::default(); if let Some(c) = border.top.clone() { if !has_top_neighbor { b.top = Some(c.clone()); if has_right_neighbor && !has_right_top_neighbor { b.right_top_corner = Some(c); } } } if let Some(c) = border.bottom.clone() { if !has_bottom_neighbor { b.bottom = Some(c.clone()); if has_right_neighbor && !has_right_bottom_neighbor { b.right_bottom_corner = Some(c); } } } if let Some(c) = border.left.clone() { if !has_left_neighbor { b.left = Some(c.clone()); if has_bottom_neighbor && !has_left_bottom_neighbor { b.left_bottom_corner = Some(c); } } } if let Some(c) = border.right.clone() { if !has_right_neighbor { b.right = Some(c.clone()); if has_bottom_neighbor && !has_right_bottom_neighbor { b.right_bottom_corner = Some(c); } } } if let Some(c) = border.left_top_corner.clone() { if !has_left_neighbor && !has_top_neighbor { b.left_top_corner = Some(c); } } if let Some(c) = border.left_bottom_corner.clone() { if !has_left_neighbor && !has_bottom_neighbor { b.left_bottom_corner = Some(c); } } if let Some(c) = border.right_top_corner.clone() { if !has_right_neighbor && !has_top_neighbor { b.right_top_corner = Some(c); } } if let Some(c) = border.right_bottom_corner.clone() { if !has_right_neighbor && !has_bottom_neighbor { b.right_bottom_corner = Some(c); } } { if !has_bottom_neighbor { if !has_left_neighbor && has_left_top_neighbor { if let Some(c) = border.right_top_corner.clone() { b.left_top_corner = Some(c); } } if has_left_neighbor && has_left_bottom_neighbor { if let Some(c) = border.left_top_corner.clone() { b.left_bottom_corner = Some(c); } } if !has_right_neighbor && has_right_top_neighbor { if let Some(c) = border.left_top_corner.clone() { b.right_top_corner = Some(c); } } if has_right_neighbor && has_right_bottom_neighbor { if let Some(c) = border.right_top_corner.clone() { b.right_bottom_corner = Some(c); } } } if !has_top_neighbor { if !has_left_neighbor && has_left_bottom_neighbor { if let Some(c) = border.right_bottom_corner.clone() { b.left_bottom_corner = Some(c); } } if has_left_neighbor && has_left_top_neighbor { if let Some(c) = border.left_bottom_corner.clone() { b.left_top_corner = Some(c); } } if !has_right_neighbor && has_right_bottom_neighbor { if let Some(c) = border.left_bottom_corner.clone() { b.right_bottom_corner = Some(c); } } if has_right_neighbor && has_right_top_neighbor { if let Some(c) = border.right_bottom_corner.clone() { b.right_top_corner = Some(c); } } } } b } fn has_top_neighbor(sector: &HashSet, p: Position) -> bool { p.row() > 0 && sector.contains(&(p - (1, 0))) } fn has_bottom_neighbor(sector: &HashSet, p: Position) -> bool { sector.contains(&(p + (1, 0))) } fn has_left_neighbor(sector: &HashSet, p: Position) -> bool { p.col() > 0 && sector.contains(&(p - (0, 1))) } fn has_right_neighbor(sector: &HashSet, p: Position) -> bool { sector.contains(&(p + (0, 1))) } fn has_left_top_neighbor(sector: &HashSet, p: Position) -> bool { p.row() > 0 && p.col() > 0 && sector.contains(&(p - (1, 1))) } fn has_right_top_neighbor(sector: &HashSet, p: Position) -> bool { p.row() > 0 && sector.contains(&(p - (1, 0) + (0, 1))) } fn has_left_bottom_neighbor(sector: &HashSet, p: Position) -> bool { p.col() > 0 && sector.contains(&(p + (1, 0) - (0, 1))) } fn has_right_bottom_neighbor(sector: &HashSet, p: Position) -> bool { sector.contains(&(p + (1, 1))) } #[cfg(test)] mod tests { use papergrid::config::pos; use super::*; #[test] fn test_is_connected() { assert!(is_cell_connected(pos(0, 0), pos(0, 1))); assert!(is_cell_connected(pos(0, 0), pos(1, 0))); assert!(!is_cell_connected(pos(0, 0), pos(1, 1))); assert!(is_cell_connected(pos(0, 1), pos(0, 0))); assert!(is_cell_connected(pos(1, 0), pos(0, 0))); assert!(!is_cell_connected(pos(1, 1), pos(0, 0))); assert!(is_cell_connected(pos(1, 1), pos(0, 1))); assert!(is_cell_connected(pos(1, 1), pos(1, 0))); assert!(is_cell_connected(pos(1, 1), pos(2, 1))); assert!(is_cell_connected(pos(1, 1), pos(1, 2))); assert!(!is_cell_connected(pos(1, 1), pos(1, 1))); } } tabled-0.18.0/src/settings/location/by_column_name.rs000064400000000000000000000031321046102023000207400ustar 00000000000000use crate::{ grid::config::Entity, grid::records::{ExactRecords, PeekableRecords, Records}, settings::location::Location, settings::object::Object, }; /// The structure is an implementation of [`Location`] to search for a column by it's name. /// A name is considered be a value in a first row. /// /// So even if in reality there's no header, the first row will be considered to be one. #[derive(Debug, Clone, Copy, Default, Hash, PartialEq, Eq, PartialOrd, Ord)] pub struct ByColumnName(S); impl ByColumnName { /// Constructs a new object of the structure. pub fn new(text: S) -> Self where S: AsRef, { Self(text) } } impl Location for ByColumnName where S: AsRef, R: Records + ExactRecords + PeekableRecords, { type Coordinate = usize; type IntoIter = Vec; fn locate(&mut self, records: &R) -> Self::IntoIter { // todo: can be optimized by creating Iterator (0..records.count_columns()) .filter(|col| records.get_text((0, *col).into()) == self.0.as_ref()) .collect::>() } } impl Object for ByColumnName where S: AsRef, R: Records + PeekableRecords + ExactRecords, { type Iter = std::vec::IntoIter; fn cells(&self, records: &R) -> Self::Iter { // todo: can be optimized by creating Iterator (0..records.count_columns()) .filter(|col| records.get_text((0, *col).into()) == self.0.as_ref()) .map(Entity::Column) .collect::>() .into_iter() } } tabled-0.18.0/src/settings/location/by_condition.rs000064400000000000000000000036101046102023000204320ustar 00000000000000use crate::{ grid::config::Entity, grid::{ config::Position, records::{ExactRecords, PeekableRecords, Records}, }, settings::location::Location, settings::object::Object, }; /// The structure is an implementation of [`Location`] to search for cells with a specified condition. #[derive(Debug, Clone, Copy, Default, Hash, PartialEq, Eq, PartialOrd, Ord)] pub struct ByCondition(F); impl ByCondition { /// Constructs a new object of the structure. pub fn new(search: F) -> Self where F: Fn(&str) -> bool, { Self(search) } } impl Location for ByCondition where F: Fn(&str) -> bool, R: Records + ExactRecords + PeekableRecords, { type Coordinate = Position; type IntoIter = Vec; fn locate(&mut self, records: &R) -> Self::IntoIter { // todo: can be optimized by creating Iterator let cond = &self.0; let mut out = vec![]; for row in 0..records.count_rows() { for col in 0..records.count_columns() { let text = records.get_text((row, col).into()); if cond(text) { out.push((row, col).into()); } } } out } } impl Object for ByCondition where F: Fn(&str) -> bool, R: Records + ExactRecords + PeekableRecords, { type Iter = std::vec::IntoIter; fn cells(&self, records: &R) -> Self::Iter { // todo: can be optimized by creating Iterator let cond = &self.0; let mut out = vec![]; for row in 0..records.count_rows() { for col in 0..records.count_columns() { let text = records.get_text((row, col).into()); if cond(text) { out.push(Entity::Cell(row, col)); } } } out.into_iter() } } tabled-0.18.0/src/settings/location/by_content.rs000064400000000000000000000035721046102023000201250ustar 00000000000000use crate::{ grid::config::Entity, grid::{ config::Position, records::{ExactRecords, PeekableRecords, Records}, }, settings::location::Location, settings::object::Object, }; /// The structure is an implementation of [`Location`] to search for cells with a given content. #[derive(Debug, Clone, Copy, Default, Hash, PartialEq, Eq, PartialOrd, Ord)] pub struct ByContent(S); impl ByContent { /// Constructs a new object of the structure. pub fn new(text: S) -> Self where S: AsRef, { Self(text) } } impl Location for ByContent where S: AsRef, R: Records + ExactRecords + PeekableRecords, { type Coordinate = Position; type IntoIter = Vec; fn locate(&mut self, records: &R) -> Self::IntoIter { // todo: can be optimized by creating Iterator let text = self.0.as_ref(); let mut out = vec![]; for row in 0..records.count_rows() { for col in 0..records.count_columns() { let cell = records.get_text((row, col).into()); if cell.eq(text) { out.push((row, col).into()); } } } out } } impl Object for ByContent where S: AsRef, R: Records + PeekableRecords + ExactRecords, { type Iter = std::vec::IntoIter; fn cells(&self, records: &R) -> Self::Iter { // todo: can be optimized by creating Iterator let text = self.0.as_ref(); let mut out = vec![]; for row in 0..records.count_rows() { for col in 0..records.count_columns() { let cell = records.get_text((row, col).into()); if cell.eq(text) { out.push(Entity::Cell(row, col)); } } } out.into_iter() } } tabled-0.18.0/src/settings/location/locator.rs000064400000000000000000000017071046102023000174220ustar 00000000000000use super::{ByColumnName, ByCondition, ByContent}; /// An abstract factory for locations, to be used to find different things on the table. #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Default, Hash)] pub struct Locator; impl Locator { /// Constructs a new location searcher for a cells with a given content. pub fn content(text: S) -> ByContent where S: AsRef, { ByContent::new(text) } /// Constructs a new location searcher for a column by its header. pub fn column(text: S) -> ByColumnName where S: AsRef, { ByColumnName::new(text) } /// Constructs a new location searcher with a specified condition closure. /// /// Return `true` if it shall be included in output. /// Otherwise return `false`. pub fn by(condition: F) -> ByCondition where F: Fn(&str) -> bool, { ByCondition::new(condition) } } tabled-0.18.0/src/settings/location/mod.rs000064400000000000000000000277341046102023000165460ustar 00000000000000//! The module contains a [`Location`] trait and implementations for it. //! //! # Example //! #![cfg_attr(feature = "derive", doc = "```")] #![cfg_attr(not(feature = "derive"), doc = "```ignore")] //! use tabled::{ //! settings::{ //! location::Locator, //! object::{Columns, Object}, //! Alignment, Modify, Padding, //! }, //! Table, Tabled, //! }; //! //! #[derive(Tabled)] //! struct Reading { //! link: &'static str, //! comment: &'static str, //! } //! //! let data = [ //! Reading { link: "https://www.gnu.org/software/grub/manual/multiboot/multiboot.html", comment: "todo" }, //! Reading { link: "https://wiki.debian.org/initramfs", comment: "todo" }, //! Reading { link: "http://jdebp.uk/FGA/efi-boot-process.html", comment: "todo,2" }, //! Reading { link: "https://wiki.debian.org/UEFI", comment: "todo,2" }, //! ]; //! //! let mut table = Table::new(data); //! table.with(Padding::zero()); //! table.with(Modify::new(Locator::column("link")).with(Alignment::right())); //! table.with(Modify::new(Locator::content("todo")).with("todo,1")); //! table.with( //! Modify::new(Columns::single(1).intersect(Locator::by(|text| text.contains("todo")))) //! .with(Padding::new(4, 0, 0, 0)), //! ); //! //! let output = table.to_string(); //! //! assert_eq!( //! output, //! concat!( //! "+-----------------------------------------------------------------+----------+\n", //! "| link|comment |\n", //! "+-----------------------------------------------------------------+----------+\n", //! "|https://www.gnu.org/software/grub/manual/multiboot/multiboot.html| todo,1|\n", //! "+-----------------------------------------------------------------+----------+\n", //! "| https://wiki.debian.org/initramfs| todo,1|\n", //! "+-----------------------------------------------------------------+----------+\n", //! "| http://jdebp.uk/FGA/efi-boot-process.html| todo,2|\n", //! "+-----------------------------------------------------------------+----------+\n", //! "| https://wiki.debian.org/UEFI| todo,2|\n", //! "+-----------------------------------------------------------------+----------+", //! ), //! ); //! ``` // todo: Add .modify method for Table mod by_column_name; mod by_condition; mod by_content; mod locator; pub use by_column_name::ByColumnName; pub use by_condition::ByCondition; pub use by_content::ByContent; pub use locator::Locator; use core::ops::Bound; use std::{ iter::{self, Once}, ops::{Range, RangeBounds}, }; use crate::{ grid::records::{ExactRecords, Records}, settings::object::{Column, Columns, FirstColumn, FirstRow, LastColumn, LastRow, Row, Rows}, }; /// Location is an interface which searches for a particular thing in the [`Records`], /// and returns coordinate of the foundings if any. pub trait Location { /// A coordinate of the finding. type Coordinate; /// An iterator of the coordinates. /// If it's empty it's considered that nothing is found. type IntoIter: IntoIterator; /// Search for the thing in [`Records`], returning a list of coordinates. fn locate(&mut self, records: &Records) -> Self::IntoIter; } impl Location for Columns where B: RangeBounds, R: Records, { type Coordinate = usize; type IntoIter = Range; fn locate(&mut self, records: &R) -> Self::IntoIter { let range = self.get_range(); let max = records.count_columns(); let (from, to) = bounds_to_usize(range.start_bound(), range.end_bound(), max); from..to } } impl Location for Column { type Coordinate = usize; type IntoIter = Once; fn locate(&mut self, _: &R) -> Self::IntoIter { iter::once((*self).into()) } } impl Location for FirstColumn { type Coordinate = usize; type IntoIter = Once; fn locate(&mut self, _: &R) -> Self::IntoIter { iter::once(0) } } impl Location for LastColumn where R: Records, { type Coordinate = usize; type IntoIter = Once; fn locate(&mut self, records: &R) -> Self::IntoIter { if records.count_columns() > 0 { iter::once(records.count_columns() - 1) } else { iter::once(0) } } } impl Location for Rows where R: ExactRecords, B: RangeBounds, { type Coordinate = usize; type IntoIter = Range; fn locate(&mut self, records: &R) -> Self::IntoIter { let (from, to) = bounds_to_usize( self.get_range().start_bound(), self.get_range().end_bound(), records.count_rows(), ); from..to } } impl Location for Row { type Coordinate = usize; type IntoIter = Once; fn locate(&mut self, _: &R) -> Self::IntoIter { iter::once((*self).into()) } } impl Location for FirstRow { type Coordinate = usize; type IntoIter = Once; fn locate(&mut self, _: &R) -> Self::IntoIter { iter::once(0) } } impl Location for LastRow where R: ExactRecords, { type Coordinate = usize; type IntoIter = Once; fn locate(&mut self, records: &R) -> Self::IntoIter { if records.count_rows() > 0 { iter::once(records.count_rows() - 1) } else { iter::once(0) } } } fn bounds_to_usize( left: Bound<&usize>, right: Bound<&usize>, count_elements: usize, ) -> (usize, usize) { match (left, right) { (Bound::Included(x), Bound::Included(y)) => (*x, y + 1), (Bound::Included(x), Bound::Excluded(y)) => (*x, *y), (Bound::Included(x), Bound::Unbounded) => (*x, count_elements), (Bound::Unbounded, Bound::Unbounded) => (0, count_elements), (Bound::Unbounded, Bound::Included(y)) => (0, y + 1), (Bound::Unbounded, Bound::Excluded(y)) => (0, *y), (Bound::Excluded(_), Bound::Unbounded) | (Bound::Excluded(_), Bound::Included(_)) | (Bound::Excluded(_), Bound::Excluded(_)) => { unreachable!("A start bound can't be excluded") } } } #[cfg(test)] mod tests { use crate::{ grid::config::Entity, grid::records::vec_records::Text, grid::records::vec_records::VecRecords, settings::location::{ByColumnName, ByCondition, ByContent}, settings::object::Object, }; use Entity::*; #[test] fn object_by_column_name_test() { let data = [ vec![vec![1, 2, 3], vec![1, 2, 3], vec![1, 2, 3]], vec![vec![1, 2, 3], vec![1, 1, 3], vec![1, 2, 1]], vec![vec![1, 1, 3], vec![1, 1, 3], vec![1, 1, 1]], vec![vec![1, 1, 1], vec![1, 1, 3], vec![1, 1, 1]], vec![vec![0, 1, 1], vec![1, 1, 3], vec![1, 1, 1]], vec![vec![0, 0, 0], vec![1, 1, 3], vec![1, 1, 1]], ]; assert_eq!(cells(by_colname("1"), &data[0]), [Column(0)]); assert_eq!(cells(by_colname("1"), &data[1]), [Column(0)]); assert_eq!(cells(by_colname("1"), &data[2]), [Column(0), Column(1)]); assert_eq!( cells(by_colname("1"), &data[3]), [Column(0), Column(1), Column(2)] ); assert_eq!(cells(by_colname("1"), &data[4]), [Column(1), Column(2)]); assert_eq!(cells(by_colname("1"), &data[5]), []); } #[test] fn object_by_content_test() { let data = [ vec![vec![1, 2, 3], vec![1, 2, 3], vec![1, 2, 3]], vec![vec![1, 2, 3], vec![1, 1, 3], vec![1, 2, 1]], vec![vec![1, 1, 3], vec![1, 1, 3], vec![1, 1, 1]], vec![vec![1, 1, 1], vec![1, 1, 3], vec![1, 1, 1]], vec![vec![0, 1, 1], vec![1, 1, 3], vec![1, 1, 1]], vec![vec![0, 0, 0], vec![1, 1, 3], vec![1, 1, 1]], ]; assert_eq!(cells(by_content("1"), &[]), []); assert_eq!(cells(by_content("1"), &[vec![], vec![], vec![]]), []); assert_eq!( cells(by_content("1"), &data[0]), [Cell(0, 0), Cell(1, 0), Cell(2, 0)] ); assert_eq!( cells(by_content("1"), &data[1]), [Cell(0, 0), Cell(1, 0), Cell(1, 1), Cell(2, 0), Cell(2, 2)] ); assert_eq!( cells(by_content("1"), &data[2]), [ Cell(0, 0), Cell(0, 1), Cell(1, 0), Cell(1, 1), Cell(2, 0), Cell(2, 1), Cell(2, 2) ] ); assert_eq!( cells(by_content("1"), &data[3]), [ Cell(0, 0), Cell(0, 1), Cell(0, 2), Cell(1, 0), Cell(1, 1), Cell(2, 0), Cell(2, 1), Cell(2, 2) ] ); assert_eq!( cells(by_content("1"), &data[4]), [ Cell(0, 1), Cell(0, 2), Cell(1, 0), Cell(1, 1), Cell(2, 0), Cell(2, 1), Cell(2, 2) ] ); assert_eq!( cells(by_content("1"), &data[5]), [Cell(1, 0), Cell(1, 1), Cell(2, 0), Cell(2, 1), Cell(2, 2)] ); } #[test] fn object_by_condition_test() { let data = [ vec![vec![1, 2, 3], vec![1, 2, 3], vec![1, 2, 3]], vec![vec![1, 2, 3], vec![1, 1, 3], vec![1, 2, 1]], vec![vec![1, 1, 3], vec![1, 1, 3], vec![1, 1, 1]], vec![vec![1, 1, 1], vec![1, 1, 3], vec![1, 1, 1]], vec![vec![0, 1, 1], vec![1, 1, 3], vec![1, 1, 1]], vec![vec![0, 0, 0], vec![1, 1, 3], vec![1, 1, 1]], ]; assert_eq!(cells(by_cond("1"), &[]), []); assert_eq!(cells(by_cond("1"), &[vec![], vec![], vec![]]), []); assert_eq!( cells(by_cond("1"), &data[0]), [Cell(0, 0), Cell(1, 0), Cell(2, 0)] ); assert_eq!( cells(by_cond("1"), &data[1]), [Cell(0, 0), Cell(1, 0), Cell(1, 1), Cell(2, 0), Cell(2, 2)] ); assert_eq!( cells(by_cond("1"), &data[2]), [ Cell(0, 0), Cell(0, 1), Cell(1, 0), Cell(1, 1), Cell(2, 0), Cell(2, 1), Cell(2, 2) ] ); assert_eq!( cells(by_cond("1"), &data[3]), [ Cell(0, 0), Cell(0, 1), Cell(0, 2), Cell(1, 0), Cell(1, 1), Cell(2, 0), Cell(2, 1), Cell(2, 2) ] ); assert_eq!( cells(by_cond("1"), &data[4]), [ Cell(0, 1), Cell(0, 2), Cell(1, 0), Cell(1, 1), Cell(2, 0), Cell(2, 1), Cell(2, 2) ] ); assert_eq!( cells(by_cond("1"), &data[5]), [Cell(1, 0), Cell(1, 1), Cell(2, 0), Cell(2, 1), Cell(2, 2)] ); } fn by_colname(text: &str) -> ByColumnName<&str> { ByColumnName::new(text) } fn by_content(text: &str) -> ByContent<&str> { ByContent::new(text) } fn by_cond(text: &'static str) -> ByCondition bool> { ByCondition::new(move |content| content == text) } fn cells(o: O, data: &[Vec]) -> Vec where O: Object>>, { let data = data .iter() .map(|row| row.iter().map(|n| n.to_string()).map(Text::new).collect()) .collect(); let records = VecRecords::new(data); o.cells(&records).collect::>() } } tabled-0.18.0/src/settings/margin/mod.rs000064400000000000000000000061261046102023000162030ustar 00000000000000//! This module contains a Margin settings of a [`Table`]. //! //! # Example //! #![cfg_attr(feature = "std", doc = "```")] #![cfg_attr(not(feature = "std"), doc = "```ignore")] //! # use tabled::{settings::Margin, Table}; //! # let data: Vec<&'static str> = Vec::new(); //! let table = Table::new(&data) //! .with(Margin::new(1, 1, 1, 1).fill('>', '<', 'V', '^')); //! ``` //! //! [`Table`]: crate::Table use crate::{ grid::{ config::{CompactConfig, CompactMultilineConfig}, config::{Indent, Sides}, }, settings::TableOption, }; #[cfg(feature = "std")] use crate::grid::config::ColoredConfig; /// Margin is responsible for a left/right/top/bottom outer indent of a grid. /// /// # Example /// #[cfg_attr(feature = "std", doc = "```")] #[cfg_attr(not(feature = "std"), doc = "```ignore")] /// use tabled::{settings::{Margin, Style}, Table}; /// /// let data = vec!["Hello", "World", "!"]; /// /// let mut table = Table::new(data); /// table /// .with(Style::markdown()) /// .with(Margin::new(3, 3, 1, 0)); /// /// assert_eq!( /// table.to_string(), /// concat!( /// " \n", /// " | &str | \n", /// " |-------| \n", /// " | Hello | \n", /// " | World | \n", /// " | ! | ", /// ) /// ); /// ``` #[derive(Debug, Clone)] pub struct Margin { indent: Sides, } impl Margin { /// Construct's an Margin object. /// /// It uses space(' ') as a default fill character. /// To set a custom character you can use [`Margin::fill`] function. pub const fn new(left: usize, right: usize, top: usize, bottom: usize) -> Self { Self { indent: Sides::new( Indent::spaced(left), Indent::spaced(right), Indent::spaced(top), Indent::spaced(bottom), ), } } /// The function, sets a characters for the margin on an each side. pub const fn fill(mut self, left: char, right: char, top: char, bottom: char) -> Self { self.indent.left.fill = left; self.indent.right.fill = right; self.indent.top.fill = top; self.indent.bottom.fill = bottom; self } } impl From for Sides { fn from(value: Margin) -> Self { value.indent } } impl From> for Margin { fn from(indent: Sides) -> Self { Self { indent } } } #[cfg(feature = "std")] impl TableOption for Margin { fn change(self, _: &mut R, cfg: &mut ColoredConfig, _: &mut D) { let indent = self.indent; let margin = Sides::new(indent.left, indent.right, indent.top, indent.bottom); cfg.set_margin(margin); } } impl TableOption for Margin { fn change(self, _: &mut R, cfg: &mut CompactConfig, _: &mut D) { *cfg = cfg.set_margin(self.indent); } } impl TableOption for Margin { fn change(self, _: &mut R, cfg: &mut CompactMultilineConfig, _: &mut D) { cfg.set_margin(self.indent); } } tabled-0.18.0/src/settings/margin_color/mod.rs000064400000000000000000000062021046102023000173740ustar 00000000000000//! This module contains a Margin settings of a [`Table`]. //! //! [`Table`]: crate::Table use crate::{ grid::{ ansi::ANSIStr, config::Sides, config::{CompactConfig, CompactMultilineConfig}, }, settings::TableOption, }; #[cfg(feature = "std")] use crate::grid::{ansi::ANSIBuf, config::ColoredConfig}; /// MarginColor is responsible for a left/right/top/bottom outer color of a grid. /// /// # Example /// #[cfg_attr(feature = "ansi", doc = "```")] #[cfg_attr(not(feature = "ansi"), doc = "```ignore")] /// use tabled::{ /// settings::{Margin, MarginColor, Style, Color}, /// Table, /// }; /// /// let data = vec!["Hello", "World", "!"]; /// /// let mut table = Table::new(data); /// table /// .with(Style::markdown()) /// .with(Margin::new(3, 3, 1, 0)) /// .with(MarginColor::filled(Color::BG_RED)); /// /// assert_eq!( /// table.to_string(), /// concat!( /// "\u{1b}[41m \u{1b}[49m\n", /// "\u{1b}[41m \u{1b}[49m| &str |\u{1b}[41m \u{1b}[49m\n", /// "\u{1b}[41m \u{1b}[49m|-------|\u{1b}[41m \u{1b}[49m\n", /// "\u{1b}[41m \u{1b}[49m| Hello |\u{1b}[41m \u{1b}[49m\n", /// "\u{1b}[41m \u{1b}[49m| World |\u{1b}[41m \u{1b}[49m\n", /// "\u{1b}[41m \u{1b}[49m| ! |\u{1b}[41m \u{1b}[49m", /// ) /// ); /// ``` #[derive(Debug, Clone)] pub struct MarginColor { colors: Sides, } impl MarginColor { /// Construct's an Margin object. pub const fn new(left: C, right: C, top: C, bottom: C) -> Self { Self { colors: Sides::new(left, right, top, bottom), } } /// The function, sets a color for the margin on an each side. pub fn filled(color: C) -> Self where C: Clone, { Self::new(color.clone(), color.clone(), color.clone(), color) } } impl MarginColor> { /// Construct's an Padding object with no color. pub const fn empty() -> Self { Self::new( ANSIStr::empty(), ANSIStr::empty(), ANSIStr::empty(), ANSIStr::empty(), ) } } #[cfg(feature = "std")] impl TableOption for MarginColor where C: Into + Clone, { fn change(self, _: &mut R, cfg: &mut ColoredConfig, _: &mut D) { let c = self.colors.clone(); let margin = Sides::new( Some(c.left.into()), Some(c.right.into()), Some(c.top.into()), Some(c.bottom.into()), ); cfg.set_margin_color(margin); } } impl TableOption for MarginColor where C: Into> + Clone, { fn change(self, _: &mut R, cfg: &mut CompactConfig, _: &mut D) { let colors = self.colors.convert_into(); *cfg = cfg.set_margin_color(colors); } } impl TableOption for MarginColor where C: Into> + Clone, { fn change(self, _: &mut R, cfg: &mut CompactMultilineConfig, _: &mut D) { let colors = self.colors.convert_into(); cfg.set_margin_color(colors); } } tabled-0.18.0/src/settings/measurement/mod.rs000064400000000000000000000070521046102023000172520ustar 00000000000000//! The module contains [`Measurement`] trait and its implementations to be used in [`Height`] and [`Width`].; use crate::{ grid::config::SpannedConfig, grid::dimension::SpannedGridDimension, grid::records::{ExactRecords, IntoRecords, PeekableRecords, Records}, grid::util::string::{self, get_text_width}, settings::{Height, Width}, }; // todo: Change the trait to not bind to exact Records /// A width value which can be obtained on behalf of [`Table`]. /// /// [`Table`]: crate::Table pub trait Measurement { /// Returns a measurement value. fn measure(&self, records: R, cfg: &SpannedConfig) -> usize where R: Records + ExactRecords + PeekableRecords, ::Cell: AsRef; } impl Measurement for usize { fn measure(&self, _: R, _: &SpannedConfig) -> usize { *self } } /// Max width value. #[derive(Debug)] pub struct Max; impl Measurement for Max { fn measure( &self, records: R, _: &SpannedConfig, ) -> usize { grid_widths(&records) .map(|r| r.max().unwrap_or(0)) .max() .unwrap_or(0) } } impl Measurement for Max { fn measure( &self, records: R, _: &SpannedConfig, ) -> usize { records_heights(&records) .map(|r| r.max().unwrap_or(0)) .max() .unwrap_or(0) } } /// Min width value. #[derive(Debug)] pub struct Min; impl Measurement for Min { fn measure( &self, records: R, _: &SpannedConfig, ) -> usize { grid_widths(&records) .map(|r| r.min().unwrap_or(0)) .max() .unwrap_or(0) } } impl Measurement for Min { fn measure( &self, records: R, _: &SpannedConfig, ) -> usize { records_heights(&records) .map(|r| r.max().unwrap_or(0)) .min() .unwrap_or(0) } } /// Percent from a total table width. #[derive(Debug)] pub struct Percent(pub usize); impl Measurement for Percent { fn measure(&self, records: R, cfg: &SpannedConfig) -> usize where R: Records, ::Cell: AsRef, { let total = SpannedGridDimension::width_total(records, cfg); (total * self.0) / 100 } } impl Measurement for Percent { fn measure(&self, records: R, cfg: &SpannedConfig) -> usize where R: Records + ExactRecords, ::Cell: AsRef, { let total = SpannedGridDimension::height_total(records, cfg); (total * self.0) / 100 } } fn grid_widths(records: &R) -> impl Iterator + '_> + '_ where R: Records + ExactRecords + PeekableRecords, { let (count_rows, count_cols) = (records.count_rows(), records.count_columns()); (0..count_rows).map(move |row| { (0..count_cols).map(move |col| get_text_width(records.get_text((row, col).into()))) }) } fn records_heights(records: &R) -> impl Iterator + '_> + '_ where R: Records + ExactRecords + PeekableRecords, { (0..records.count_rows()).map(move |row| { (0..records.count_columns()) .map(move |col| string::count_lines(records.get_text((row, col).into()))) }) } tabled-0.18.0/src/settings/merge/mod.rs000064400000000000000000000145271046102023000160310ustar 00000000000000//! The module contains a set of methods to merge cells together via [`Span`]s. //! //! [`Span`]: crate::settings::span::Span use crate::{ grid::config::ColoredConfig, grid::records::{ExactRecords, PeekableRecords, Records}, settings::TableOption, }; /// Merge to combine duplicates together, using [`Span`]. /// /// [`Span`]: crate::settings::span::Span #[derive(Debug)] pub struct Merge; impl Merge { /// Vertical merge. pub fn vertical() -> MergeDuplicatesVertical { MergeDuplicatesVertical } /// Horizontal merge. pub fn horizontal() -> MergeDuplicatesHorizontal { MergeDuplicatesHorizontal } } /// A modificator for [`Table`] which looks up for duplicates in columns and /// in case of duplicate merges the cells together using [`Span`]. /// /// [`Table`]: crate::Table /// [`Span`]: crate::settings::span::Span #[derive(Debug)] pub struct MergeDuplicatesVertical; impl TableOption for MergeDuplicatesVertical where R: Records + PeekableRecords + ExactRecords, { #[allow(clippy::assigning_clones)] // NOTE: Temporarily disabled due to a issue with `assigning_clones` not respecting MSRV in clippy 1.78.0. // See https://github.com/rust-lang/rust-clippy/issues/12502 fn change(self, records: &mut R, cfg: &mut ColoredConfig, _: &mut D) { let count_rows = records.count_rows(); let count_cols = records.count_columns(); if count_rows == 0 || count_cols == 0 { return; } for column in 0..count_cols { let mut repeat_length = 0; let mut repeat_value = String::new(); let mut repeat_is_set = false; let mut last_is_row_span = false; for row in (0..count_rows).rev() { if last_is_row_span { last_is_row_span = false; continue; } // we need to mitigate messing existing spans let is_cell_visible = cfg.is_cell_visible((row, column).into()); let is_row_span_cell = cfg.get_column_span((row, column).into()).is_some(); if !repeat_is_set { if !is_cell_visible { continue; } if is_row_span_cell { continue; } repeat_length = 1; repeat_value = records.get_text((row, column).into()).to_owned(); repeat_is_set = true; continue; } if is_row_span_cell { repeat_is_set = false; last_is_row_span = true; continue; } if !is_cell_visible { repeat_is_set = false; continue; } let text = records.get_text((row, column).into()); let is_duplicate = text == repeat_value; if is_duplicate { repeat_length += 1; continue; } if repeat_length > 1 { cfg.set_row_span((row + 1, column).into(), repeat_length); } repeat_length = 1; repeat_value = records.get_text((row, column).into()).to_owned(); } if repeat_length > 1 { cfg.set_row_span((0, column).into(), repeat_length); } } } } /// A modificator for [`Table`] which looks up for duplicates in rows and /// in case of duplicate merges the cells together using [`Span`]. /// /// [`Table`]: crate::Table /// [`Span`]: crate::settings::span::Span #[derive(Debug)] pub struct MergeDuplicatesHorizontal; impl TableOption for MergeDuplicatesHorizontal where R: Records + PeekableRecords + ExactRecords, { #[allow(clippy::assigning_clones)] // NOTE: Temporarily disabled due to a issue with `assigning_clones` not respecting MSRV in clippy 1.78.0. // See https://github.com/rust-lang/rust-clippy/issues/12502 fn change(self, records: &mut R, cfg: &mut ColoredConfig, _: &mut D) { let count_rows = records.count_rows(); let count_cols = records.count_columns(); if count_rows == 0 || count_cols == 0 { return; } for row in 0..count_rows { let mut repeat_length = 0; let mut repeat_value = String::new(); let mut repeat_is_set = false; let mut last_is_col_span = false; for column in (0..count_cols).rev() { if last_is_col_span { last_is_col_span = false; continue; } // we need to mitigate messing existing spans let is_cell_visible = cfg.is_cell_visible((row, column).into()); let is_col_span_cell = cfg.get_row_span((row, column).into()).is_some(); if !repeat_is_set { if !is_cell_visible { continue; } if is_col_span_cell { continue; } repeat_length = 1; repeat_value = records.get_text((row, column).into()).to_owned(); repeat_is_set = true; continue; } if is_col_span_cell { repeat_is_set = false; last_is_col_span = true; continue; } if !is_cell_visible { repeat_is_set = false; continue; } let text = records.get_text((row, column).into()); let is_duplicate = text == repeat_value; if is_duplicate { repeat_length += 1; continue; } if repeat_length > 1 { cfg.set_column_span((row, column + 1).into(), repeat_length); } repeat_length = 1; repeat_value = records.get_text((row, column).into()).to_owned(); } if repeat_length > 1 { cfg.set_column_span((row, 0).into(), repeat_length); } } } } tabled-0.18.0/src/settings/mod.rs000064400000000000000000000126321046102023000147250ustar 00000000000000//! Module contains various table configuration settings. //! //! There 2 types of settings; //! //! - [`CellOption`] which can modify only a cell. //! - [`TableOption`] which can modify table as a whole. //! //! [`CellOption`] works on behave of [`Modify`] which is actually a [`TableOption`]. //! //! Notice that it's possible to combine settings together by the help of [`Settings`]. //! #![cfg_attr(feature = "std", doc = "```")] #![cfg_attr(not(feature = "std"), doc = "```ignore")] //! use tabled::{Table, settings::{Settings, Style, Padding}}; //! //! let table_config = Settings::default() //! .with(Padding::new(2, 2, 1, 1)) //! .with(Style::rounded()); //! //! let data = [[2023;9]; 3]; //! //! let table = Table::new(data).with(table_config).to_string(); //! //! assert_eq!( //! table, //! "╭────────┬────────┬────────┬────────┬────────┬────────┬────────┬────────┬────────╮\n\ //! │ │ │ │ │ │ │ │ │ │\n\ //! │ 0 │ 1 │ 2 │ 3 │ 4 │ 5 │ 6 │ 7 │ 8 │\n\ //! │ │ │ │ │ │ │ │ │ │\n\ //! ├────────┼────────┼────────┼────────┼────────┼────────┼────────┼────────┼────────┤\n\ //! │ │ │ │ │ │ │ │ │ │\n\ //! │ 2023 │ 2023 │ 2023 │ 2023 │ 2023 │ 2023 │ 2023 │ 2023 │ 2023 │\n\ //! │ │ │ │ │ │ │ │ │ │\n\ //! │ │ │ │ │ │ │ │ │ │\n\ //! │ 2023 │ 2023 │ 2023 │ 2023 │ 2023 │ 2023 │ 2023 │ 2023 │ 2023 │\n\ //! │ │ │ │ │ │ │ │ │ │\n\ //! │ │ │ │ │ │ │ │ │ │\n\ //! │ 2023 │ 2023 │ 2023 │ 2023 │ 2023 │ 2023 │ 2023 │ 2023 │ 2023 │\n\ //! │ │ │ │ │ │ │ │ │ │\n\ //! ╰────────┴────────┴────────┴────────┴────────┴────────┴────────┴────────┴────────╯" //! ) //! ``` mod cell_option; mod settings_list; mod table_option; mod alignment; mod extract; mod margin; mod margin_color; mod padding; mod padding_color; mod reverse; mod rotate; #[cfg(feature = "std")] mod modify; #[cfg(feature = "std")] mod color; #[cfg(feature = "std")] mod concat; #[cfg(feature = "std")] mod duplicate; pub mod style; #[cfg(feature = "std")] #[cfg_attr(docsrs, doc(cfg(feature = "std")))] pub mod object; #[cfg(feature = "std")] #[cfg_attr(docsrs, doc(cfg(feature = "std")))] pub mod disable; #[cfg(feature = "std")] #[cfg_attr(docsrs, doc(cfg(feature = "std")))] pub mod format; #[cfg(feature = "std")] #[cfg_attr(docsrs, doc(cfg(feature = "std")))] pub mod formatting; #[cfg(feature = "std")] #[cfg_attr(docsrs, doc(cfg(feature = "std")))] pub mod height; #[cfg(feature = "std")] #[cfg_attr(docsrs, doc(cfg(feature = "std")))] pub mod highlight; #[cfg(feature = "std")] #[cfg_attr(docsrs, doc(cfg(feature = "std")))] pub mod location; #[cfg(feature = "std")] #[cfg_attr(docsrs, doc(cfg(feature = "std")))] pub mod measurement; #[cfg(feature = "std")] #[cfg_attr(docsrs, doc(cfg(feature = "std")))] pub mod merge; #[cfg(feature = "std")] #[cfg_attr(docsrs, doc(cfg(feature = "std")))] pub mod padding_expand; #[cfg(feature = "std")] #[cfg_attr(docsrs, doc(cfg(feature = "std")))] pub mod panel; #[cfg(feature = "std")] #[cfg_attr(docsrs, doc(cfg(feature = "std")))] pub mod peaker; #[cfg(feature = "std")] #[cfg_attr(docsrs, doc(cfg(feature = "std")))] mod shadow; #[cfg(feature = "std")] #[cfg_attr(docsrs, doc(cfg(feature = "std")))] pub mod span; #[cfg(feature = "std")] #[cfg_attr(docsrs, doc(cfg(feature = "std")))] pub mod split; #[cfg(feature = "std")] #[cfg_attr(docsrs, doc(cfg(feature = "std")))] pub mod themes; #[cfg(feature = "std")] #[cfg_attr(docsrs, doc(cfg(feature = "std")))] pub mod width; pub use cell_option::CellOption; pub use settings_list::{EmptySettings, Settings}; pub use table_option::TableOption; pub use self::{ alignment::Alignment, extract::Extract, margin::Margin, margin_color::MarginColor, padding::Padding, padding_color::PaddingColor, reverse::Reverse, rotate::Rotate, style::Border, style::Style, }; #[cfg(feature = "std")] #[cfg_attr(docsrs, doc(cfg(feature = "std")))] pub use self::{ color::Color, concat::Concat, disable::Remove, duplicate::Dup, format::Format, height::Height, highlight::Highlight, merge::Merge, modify::{Modify, ModifyList}, panel::Panel, shadow::Shadow, span::Span, themes::Theme, width::Width, }; tabled-0.18.0/src/settings/modify.rs000064400000000000000000000071111046102023000154310ustar 00000000000000use crate::{ grid::{ config::Entity, records::{ExactRecords, Records}, }, settings::{object::Object, CellOption, Settings, TableOption}, }; /// Modify structure provide an abstraction, to be able to apply /// a set of [`CellOption`]s to the same object. /// /// Be aware that the settings are applied all to a cell at a time. /// So sometimes you may need to make a several calls of [`Modify`] in order to achieve the desired affect. #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] pub struct Modify { obj: O, } impl Modify { /// Creates a new [`Modify`] without any options. pub const fn new(obj: O) -> Self { Self { obj } } /// A function which combines together [`Modify::new`] and [`Modify::with`] calls. /// /// ``` /// use tabled::{Table, settings::{Modify, Padding, object::Rows}}; /// /// let table = Table::new(&["Year", "2021"]) /// .with(Modify::list(Rows::first(), Padding::new(1, 1, 1, 1))) /// .to_string(); /// /// assert_eq!( /// table, /// "+------+\n\ /// | |\n\ /// | &str |\n\ /// | |\n\ /// +------+\n\ /// | Year |\n\ /// +------+\n\ /// | 2021 |\n\ /// +------+" /// ) /// ``` pub const fn list(obj: O, next: M) -> ModifyList { ModifyList { obj, modifiers: next, } } /// It's a generic function which stores a [`CellOption`]. /// /// IMPORTANT: /// The function *doesn't* changes a [`Table`]. /// [`Table`] will be changed only after passing [`Modify`] object to [`Table::with`]. /// /// ``` /// use tabled::{Table, settings::{Modify, Padding, object::Rows}}; /// /// let table = Table::new(&["Year", "2021"]) /// .with(Modify::new(Rows::first()).with(Padding::new(1, 1, 1, 1))) /// .to_string(); /// /// assert_eq!( /// table, /// "+------+\n\ /// | |\n\ /// | &str |\n\ /// | |\n\ /// +------+\n\ /// | Year |\n\ /// +------+\n\ /// | 2021 |\n\ /// +------+" /// ) /// ``` /// /// [`Table`]: crate::Table /// [`Table::with`]: crate::Table::with pub fn with(self, next: M) -> ModifyList { ModifyList { obj: self.obj, modifiers: next, } } } /// This is a container of [`CellOption`]s which are applied to a set [`Object`]. #[derive(Debug)] pub struct ModifyList { obj: O, modifiers: S, } impl ModifyList { /// With a generic function which stores a [`CellOption`]. /// /// IMPORTANT: /// The function *doesn't* changes a [`Table`]. /// [`Table`] will be changed only after passing [`Modify`] object to [`Table::with`]. /// /// [`Table`]: crate::Table /// [`Table::with`]: crate::Table::with pub fn with(self, next: M2) -> ModifyList> { ModifyList { obj: self.obj, modifiers: Settings::new(self.modifiers, next), } } } impl TableOption for ModifyList where O: Object, M: CellOption + Clone, R: Records + ExactRecords, { fn change(self, records: &mut R, cfg: &mut C, _: &mut D) { for entity in self.obj.cells(records) { self.modifiers.clone().change(records, cfg, entity); } } fn hint_change(&self) -> Option { self.modifiers.hint_change() } } tabled-0.18.0/src/settings/object/cell.rs000064400000000000000000000035621046102023000163350ustar 00000000000000use crate::{ grid::config::{Entity, Position}, settings::object::Object, }; /// Cell denotes a particular cell on a [`Table`]. /// /// For example such table has 4 cells. /// Which indexes are (0, 0), (0, 1), (1, 0), (1, 1). /// /// ```text /// ┌───┬───┐ /// │ 0 │ 1 │ /// ├───┼───┤ /// │ 1 │ 2 │ /// └───┴───┘ /// ``` /// /// [`Table`]: crate::Table #[derive(Debug, Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord)] pub struct Cell(usize, usize); impl Cell { /// Create new cell structure. pub fn new(row: usize, col: usize) -> Self { Self(row, col) } } impl From for Cell { fn from(pos: Position) -> Self { Self(pos.row(), pos.col()) } } impl From<(usize, usize)> for Cell { fn from(pos: (usize, usize)) -> Self { Self(pos.0, pos.1) } } impl From for Position { fn from(Cell(row, col): Cell) -> Self { Position::new(row, col) } } impl Object for Cell { type Iter = EntityOnce; fn cells(&self, _: &I) -> Self::Iter { EntityOnce::new(Some(Entity::Cell(self.0, self.1))) } } impl Object for Position { type Iter = EntityOnce; fn cells(&self, _: &I) -> Self::Iter { EntityOnce::new(Some(Entity::Cell(self.row(), self.col()))) } } impl Object for (usize, usize) { type Iter = EntityOnce; fn cells(&self, _: &I) -> Self::Iter { EntityOnce::new(Some(Entity::Cell(self.0, self.1))) } } /// An [`Iterator`] which returns an entity once. #[derive(Debug)] pub struct EntityOnce { entity: Option, } impl EntityOnce { pub(crate) const fn new(entity: Option) -> Self { Self { entity } } } impl Iterator for EntityOnce { type Item = Entity; fn next(&mut self) -> Option { self.entity.take() } } tabled-0.18.0/src/settings/object/columns.rs000064400000000000000000000110631046102023000170710ustar 00000000000000use std::ops::{Add, RangeBounds, Sub}; use crate::{ grid::config::Entity, grid::records::{ExactRecords, Records}, settings::object::{cell::EntityOnce, Object}, }; use super::util::bounds_to_usize; /// Column denotes a set of cells on given columns on a [`Table`]. /// /// [`Table`]: crate::Table #[derive(Debug)] pub struct Columns { range: R, } impl Columns { /// Returns a new instance of [`Columns`] for a range of columns. /// /// If the boundaries are exceeded it may panic. pub fn new(range: R) -> Self where R: RangeBounds, { Self { range } } pub(crate) fn get_range(&self) -> &R { &self.range } } impl Columns<()> { /// Returns a new instance of [`Columns`] for a single column. /// /// If the boundaries are exceeded it may panic. pub fn single(index: usize) -> Column { Column(index) } /// Returns a new instance of [`Columns`] for a first column. /// /// If the boundaries are exceeded the object will produce no cells. pub fn first() -> FirstColumn { FirstColumn } /// Returns a new instance of [`Columns`] for a last column. /// /// If the boundaries are exceeded the object will produce no cells. pub fn last() -> LastColumn { LastColumn } } impl Object for Columns where R: RangeBounds, I: Records, { type Iter = ColumnsIter; fn cells(&self, records: &I) -> Self::Iter { let max = records.count_columns(); let start = self.range.start_bound(); let end = self.range.end_bound(); let (x, y) = bounds_to_usize(start, end, max); ColumnsIter::new(x, y) } } /// `FirstColumn` represents the first column on a grid. #[derive(Debug)] pub struct FirstColumn; impl Object for FirstColumn where I: Records + ExactRecords, { type Iter = EntityOnce; fn cells(&self, records: &I) -> Self::Iter { if records.count_rows() == 0 || records.count_columns() == 0 { return EntityOnce::new(None); } EntityOnce::new(Some(Entity::Column(0))) } } impl Add for FirstColumn { type Output = Column; fn add(self, rhs: usize) -> Self::Output { Column(rhs) } } /// `LastColumn` represents the last column on a grid. #[derive(Debug)] pub struct LastColumn; impl Object for LastColumn where I: Records + ExactRecords, { type Iter = EntityOnce; fn cells(&self, records: &I) -> Self::Iter { if records.count_rows() == 0 || records.count_columns() == 0 { return EntityOnce::new(None); } let col = records.count_columns().saturating_sub(1); EntityOnce::new(Some(Entity::Column(col))) } } impl Sub for LastColumn { type Output = LastColumnOffset; fn sub(self, rhs: usize) -> Self::Output { LastColumnOffset { offset: rhs } } } /// Column represents a single column on a grid. #[derive(Debug, Clone, Copy)] pub struct Column(usize); impl Object for Column { type Iter = EntityOnce; fn cells(&self, _: &I) -> Self::Iter { EntityOnce::new(Some(Entity::Column(self.0))) } } impl From for Column { fn from(i: usize) -> Self { Self(i) } } impl From for usize { fn from(val: Column) -> Self { val.0 } } /// `LastColumnOffset` represents a single column on a grid indexed via offset from the last column. #[derive(Debug)] pub struct LastColumnOffset { offset: usize, } impl Object for LastColumnOffset where I: Records + ExactRecords, { type Iter = EntityOnce; fn cells(&self, records: &I) -> Self::Iter { if records.count_rows() == 0 || records.count_columns() == 0 { return EntityOnce::new(None); } let col = records.count_columns().saturating_sub(1); if self.offset > col { return EntityOnce::new(None); } let col = col - self.offset; EntityOnce::new(Some(Entity::Column(col))) } } /// An [`Iterator`] which goes goes over columns of a [`Table`]. /// /// [`Table`]: crate::Table #[derive(Debug)] pub struct ColumnsIter { start: usize, end: usize, } impl ColumnsIter { const fn new(start: usize, end: usize) -> Self { Self { start, end } } } impl Iterator for ColumnsIter { type Item = Entity; fn next(&mut self) -> Option { if self.start >= self.end { return None; } let col = self.start; self.start += 1; Some(Entity::Column(col)) } } tabled-0.18.0/src/settings/object/frame.rs000064400000000000000000000026611046102023000165070ustar 00000000000000use crate::{ grid::config::Entity, grid::records::{ExactRecords, Records}, settings::object::Object, }; /// Frame includes cells which are on the edges of each side. /// Therefore it's [`Object`] implementation returns a subset of cells which are present in frame. #[derive(Debug)] pub struct Frame; impl Object for Frame where I: Records + ExactRecords, { type Iter = FrameIter; fn cells(&self, records: &I) -> Self::Iter { FrameIter::new(records.count_rows(), records.count_columns()) } } /// An [`Iterator`] which goes goes over all cell on a frame of a [`Table`]. /// /// [`Table`]: crate::Table #[derive(Debug)] pub struct FrameIter { rows: usize, cols: usize, row: usize, col: usize, } impl FrameIter { const fn new(count_rows: usize, count_columns: usize) -> Self { Self { rows: count_rows, cols: count_columns, row: 0, col: 0, } } } impl Iterator for FrameIter { type Item = Entity; fn next(&mut self) -> Option { if self.cols == 0 || self.rows == 0 { return None; } if self.row == self.rows { return None; } let row = self.row; let col = self.col; self.col += 1; if self.col == self.cols { self.row += 1; self.col = 0; } Some(Entity::Cell(row, col)) } } tabled-0.18.0/src/settings/object/iterator.rs000064400000000000000000000157061046102023000172520ustar 00000000000000//! This module contains an [`ObjectIterator`]. use core::marker::PhantomData; use crate::grid::config::Entity; use crate::settings::object::Object; /// A utility trait helps to modify an [`Object`], /// by various functions. pub trait ObjectIterator: Object { /// Skip N entities. fn skip(self, n: usize) -> SkipObject where Self: Sized, { SkipObject::new(self, n) } /// Make a step for an iteration of entities. fn step_by(self, n: usize) -> StepByObject where Self: Sized, { StepByObject::new(self, n) } /// Use a filter while iterating over entities. fn filter(self, predicate: F) -> FilterObject where Self: Sized, F: Fn(Entity) -> bool, { FilterObject::new(self, predicate) } } impl ObjectIterator for T where T: Object {} /// Skip object for any [`Object`]. #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Default, Hash)] pub struct SkipObject { obj: O, n: usize, _records: PhantomData, } impl SkipObject { fn new(obj: O, n: usize) -> Self { Self { obj, n, _records: PhantomData, } } } impl Object for SkipObject where O: Object, { type Iter = SkipObjectIter; fn cells(&self, records: &R) -> Self::Iter { SkipObjectIter::new(self.obj.cells(records), self.n) } } /// Skip object iterator for any [`Object`]. #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Default, Hash)] pub struct SkipObjectIter { iter: I, n: usize, } impl SkipObjectIter { fn new(iter: I, n: usize) -> Self { Self { iter, n } } } impl Iterator for SkipObjectIter where I: Iterator, { type Item = I::Item; fn next(&mut self) -> Option { while self.n > 0 { self.n -= 1; let _ = self.iter.next()?; } self.iter.next() } } /// Step object for any [`Object`]. #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Default, Hash)] pub struct StepByObject { obj: O, n: usize, _records: PhantomData, } impl StepByObject { fn new(obj: O, n: usize) -> Self { Self { obj, n, _records: PhantomData, } } } impl Object for StepByObject where O: Object, { type Iter = StepByObjectIter; fn cells(&self, records: &R) -> Self::Iter { StepByObjectIter::new(self.obj.cells(records), self.n) } } /// Step object iterator for any [`Object`]. #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Default, Hash)] pub struct StepByObjectIter { iter: I, step: usize, end: bool, } impl StepByObjectIter { fn new(iter: I, step: usize) -> Self { let end = step == 0; Self { iter, step, end } } } impl Iterator for StepByObjectIter where I: Iterator, { type Item = I::Item; fn next(&mut self) -> Option { if self.end { return None; } let item = self.iter.next(); let _ = item.as_ref()?; for _ in 0..self.step - 1 { let next = self.iter.next(); if next.is_none() { self.end = true; break; } } item } } /// Filter object for any [`Object`]. #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Default, Hash)] pub struct FilterObject { obj: O, f: F, _records: PhantomData, } impl FilterObject { fn new(obj: O, f: F) -> Self { Self { obj, f, _records: PhantomData, } } } impl Object for FilterObject where O: Object, F: Fn(Entity) -> bool + Clone, { type Iter = FilterObjectIter; fn cells(&self, records: &R) -> Self::Iter { FilterObjectIter::new(self.obj.cells(records), self.f.clone()) } } /// Filter object iterator for any [`Object`]. #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Default, Hash)] pub struct FilterObjectIter { iter: I, f: F, } impl FilterObjectIter { fn new(iter: I, f: F) -> Self { Self { iter, f } } } impl Iterator for FilterObjectIter where I: Iterator, F: Fn(Entity) -> bool, { type Item = I::Item; fn next(&mut self) -> Option { loop { match self.iter.next() { Some(item) => { if (self.f)(item) { return Some(item); } } None => return None, } } } } #[cfg(test)] mod tests { use crate::{ grid::records::vec_records::VecRecords, settings::object::{Columns, Rows}, }; use super::*; #[test] fn test_skip_iterator() { use Entity::*; assert_eq!( cells(Rows::new(1..5).skip(1), 10, 10), [Row(2), Row(3), Row(4)] ); assert_eq!( cells(Columns::new(5..).skip(1), 10, 10), [Column(6), Column(7), Column(8), Column(9)] ); assert_eq!(cells((1, 5).skip(1), 10, 10), []); assert_eq!(cells((1, 5).skip(0), 10, 10), [Cell(1, 5)]); assert_eq!( cells(Rows::new(1..5).skip(0), 10, 10), [Row(1), Row(2), Row(3), Row(4)] ); } #[test] fn test_step_by_iterator() { use Entity::*; assert_eq!(cells(Rows::new(1..5).step_by(0), 10, 10), []); assert_eq!( cells(Rows::new(1..5).step_by(1), 10, 10), [Row(1), Row(2), Row(3), Row(4)] ); assert_eq!(cells(Rows::new(1..5).step_by(2), 10, 10), [Row(1), Row(3)]); assert_eq!( cells(Columns::new(5..).step_by(1), 10, 10), [Column(5), Column(6), Column(7), Column(8), Column(9)] ); assert_eq!(cells((1, 5).step_by(2), 10, 10), [Cell(1, 5)]); assert_eq!(cells((1, 5).step_by(1), 10, 10), [Cell(1, 5)]); assert_eq!(cells(Rows::new(1..5).step_by(100), 10, 10), [Row(1)]); } #[test] fn test_filter_iterator() { use Entity::*; assert_eq!( cells(Rows::new(1..5).filter(|i| matches!(i, Row(3))), 10, 10), [Row(3)] ); assert_eq!(cells(Rows::new(1..5).filter(|_| false), 10, 10), []); assert_eq!( cells(Rows::new(1..5).filter(|_| true), 10, 10), [Row(1), Row(2), Row(3), Row(4)] ); } fn cells(o: O, count_rows: usize, count_cols: usize) -> Vec where O: Object>, { let data = vec![vec![String::default(); count_cols]; count_rows]; let records = VecRecords::new(data); o.cells(&records).collect::>() } } tabled-0.18.0/src/settings/object/mod.rs000064400000000000000000000552271046102023000162020ustar 00000000000000//! This module contains a list of primitives that implement a [`Object`] trait.\ //! They help to locate a necessary segment on a [`Table`]. //! //! # Example //! //! ``` //! use tabled::{ //! settings::{ //! object::{Columns, Object, Rows}, //! Alignment, //! }, //! Table, //! }; //! use testing_table::assert_table; //! //! let data = [ //! [1, 2, 3, 4, 5], //! [10, 20, 30, 40, 50], //! [100, 200, 300, 400, 500], //! ]; //! //! let mut table = Table::new(data); //! table.modify(Rows::first().not(Columns::first()), Alignment::right()); //! //! assert_table!( //! table, //! "+-----+-----+-----+-----+-----+" //! "| 0 | 1 | 2 | 3 | 4 |" //! "+-----+-----+-----+-----+-----+" //! "| 1 | 2 | 3 | 4 | 5 |" //! "+-----+-----+-----+-----+-----+" //! "| 10 | 20 | 30 | 40 | 50 |" //! "+-----+-----+-----+-----+-----+" //! "| 100 | 200 | 300 | 400 | 500 |" //! "+-----+-----+-----+-----+-----+" //! ); //! ``` //! //! [`Table`]: crate::Table mod cell; mod columns; mod frame; mod iterator; mod rows; mod segment; pub(crate) mod util; use std::{collections::HashSet, marker::PhantomData}; use self::segment::SectorCellsIter; use crate::{ grid::config::{Entity, EntityIterator}, grid::records::{ExactRecords, Records}, }; pub use cell::{Cell, EntityOnce}; pub use columns::{Column, Columns, ColumnsIter, FirstColumn, LastColumn, LastColumnOffset}; pub use frame::{Frame, FrameIter}; pub use iterator::{ FilterObject, FilterObjectIter, ObjectIterator, SkipObject, SkipObjectIter, StepByObject, StepByObjectIter, }; use papergrid::config::Position; pub use rows::{FirstRow, LastRow, LastRowOffset, Row, Rows, RowsIter}; pub use segment::{SectorIter, Segment, SegmentAll}; /// Object helps to locate a necessary part of a [`Table`]. /// /// [`Table`]: crate::Table pub trait Object { /// An [`Iterator`] which returns a list of cells. type Iter: Iterator; /// Cells returns a set of coordinates of cells. fn cells(&self, records: &R) -> Self::Iter; /// Combines cells. /// It doesn't repeat cells. fn and(self, rhs: O) -> UnionCombination where Self: Sized, { UnionCombination::new(self, rhs) } /// Excludes rhs cells from this cells. fn not(self, rhs: O) -> DiffCombination where Self: Sized, { DiffCombination::new(self, rhs) } /// Returns cells which are present in both [`Object`]s only. fn intersect(self, rhs: O) -> IntersectionCombination where Self: Sized, { IntersectionCombination::new(self, rhs) } /// Returns cells which are not present in target [`Object`]. fn inverse(self) -> InversionCombination where Self: Sized, { InversionCombination::new(self) } } /// Combination struct used for chaining [`Object`]'s. /// /// Combines 2 sets of cells into one. /// /// Duplicates are removed from the output set. #[derive(Debug)] pub struct UnionCombination { lhs: L, rhs: R, _records: PhantomData, } impl UnionCombination { fn new(lhs: L, rhs: R) -> Self { Self { lhs, rhs, _records: PhantomData, } } } impl Object for UnionCombination where L: Object, R: Object, I: Records + ExactRecords, { type Iter = UnionIter; fn cells(&self, records: &I) -> Self::Iter { let lhs = self.lhs.cells(records); let rhs = self.rhs.cells(records); UnionIter::new(lhs, rhs, records.count_rows(), records.count_columns()) } } /// Difference struct used for chaining [`Object`]'s. /// /// Returns cells from 1st set with removed ones from the 2nd set. #[derive(Debug)] pub struct DiffCombination { lhs: L, rhs: R, _records: PhantomData, } impl DiffCombination { fn new(lhs: L, rhs: R) -> Self { Self { lhs, rhs, _records: PhantomData, } } } impl Object for DiffCombination where L: Object, R: Object, I: Records + ExactRecords, { type Iter = DiffIter; fn cells(&self, records: &I) -> Self::Iter { let lhs = self.lhs.cells(records); let rhs = self.rhs.cells(records); DiffIter::new(lhs, rhs, records.count_rows(), records.count_columns()) } } /// Intersection struct used for chaining [`Object`]'s. /// /// Returns cells which are present in 2 sets. /// But not in one of them #[derive(Debug)] pub struct IntersectionCombination { lhs: L, rhs: R, _records: PhantomData, } impl IntersectionCombination { fn new(lhs: L, rhs: R) -> Self { Self { lhs, rhs, _records: PhantomData, } } } impl Object for IntersectionCombination where L: Object, R: Object, I: Records + ExactRecords, { type Iter = IntersectIter; fn cells(&self, records: &I) -> Self::Iter { let lhs = self.lhs.cells(records); let rhs = self.rhs.cells(records); IntersectIter::new(lhs, rhs, records.count_rows(), records.count_columns()) } } /// Inversion struct used for chaining [`Object`]'s. /// /// Returns cells which are present in 2 sets. /// But not in one of them #[derive(Debug)] pub struct InversionCombination { obj: O, _records: PhantomData, } impl InversionCombination { fn new(obj: O) -> Self { Self { obj, _records: PhantomData, } } } impl Object for InversionCombination where O: Object, I: Records + ExactRecords, { type Iter = InversionIter; fn cells(&self, records: &I) -> Self::Iter { let obj = self.obj.cells(records); InversionIter::new(obj, records.count_rows(), records.count_columns()) } } /// An [`Iterator`] which goes over a combination [`Object::Iter`]. #[derive(Debug)] pub struct UnionIter { lhs: Option, rhs: R, seen: HashSet, current: Option, count_rows: usize, count_cols: usize, } impl UnionIter where L: Iterator, R: Iterator, { fn new(lhs: L, rhs: R, count_rows: usize, count_cols: usize) -> Self { let size = match lhs.size_hint() { (s1, Some(s2)) if s1 == s2 => s1, _ => 0, }; Self { lhs: Some(lhs), rhs, seen: HashSet::with_capacity(size), current: None, count_rows, count_cols, } } } impl Iterator for UnionIter where L: Iterator, R: Iterator, { type Item = Entity; fn next(&mut self) -> Option { if let Some(iter) = self.current.as_mut() { for p in iter.by_ref() { if self.lhs.is_none() && self.seen.contains(&p) { continue; } let _ = self.seen.insert(p); return Some(p.into()); } } if let Some(lhs) = self.lhs.as_mut() { for entity in lhs.by_ref() { let mut iter = entity.iter(self.count_rows, self.count_cols); if let Some(p) = iter.by_ref().next() { let _ = self.seen.insert(p); self.current = Some(iter); return Some(p.into()); } } self.lhs = None; } for entity in self.rhs.by_ref() { let mut iter = entity.iter(self.count_rows, self.count_cols); for p in iter.by_ref() { if !self.seen.contains(&p) { let _ = self.seen.insert(p); self.current = Some(iter); return Some(p.into()); } } } None } } /// An [`Iterator`] which goes over only cells which are present in first [`Object::Iter`] but not second. #[derive(Debug)] pub struct DiffIter { lhs: L, seen: HashSet, count_rows: usize, count_cols: usize, current: Option, } impl DiffIter where L: Iterator, { fn new(lhs: L, rhs: R, count_rows: usize, count_cols: usize) -> Self where R: Iterator, { let size = match rhs.size_hint() { (s1, Some(s2)) if s1 == s2 => s1, _ => 0, }; let mut seen = HashSet::with_capacity(size); for entity in rhs { seen.extend(entity.iter(count_rows, count_cols)); } Self { lhs, seen, count_rows, count_cols, current: None, } } } impl Iterator for DiffIter where L: Iterator, { type Item = Entity; fn next(&mut self) -> Option { if let Some(iter) = self.current.as_mut() { for p in iter.by_ref() { if !self.seen.contains(&p) { return Some(p.into()); } } } for entity in self.lhs.by_ref() { let mut iter = entity.iter(self.count_rows, self.count_cols); for p in iter.by_ref() { if !self.seen.contains(&p) { self.current = Some(iter); return Some(p.into()); } } } None } } /// An [`Iterator`] which goes goes over cells which are present in both [`Object::Iter`]ators. #[derive(Debug)] pub struct IntersectIter { lhs: L, seen: HashSet, count_rows: usize, count_cols: usize, current: Option, } impl IntersectIter where L: Iterator, { fn new(lhs: L, rhs: R, count_rows: usize, count_cols: usize) -> Self where R: Iterator, { let size = match rhs.size_hint() { (s1, Some(s2)) if s1 == s2 => s1, _ => 0, }; let mut seen = HashSet::with_capacity(size); for entity in rhs { seen.extend(entity.iter(count_rows, count_cols)); } Self { lhs, seen, count_rows, count_cols, current: None, } } } impl Iterator for IntersectIter where L: Iterator, { type Item = Entity; fn next(&mut self) -> Option { if let Some(iter) = self.current.as_mut() { for p in iter.by_ref() { if self.seen.contains(&p) { return Some(p.into()); } } } for entity in self.lhs.by_ref() { let mut iter = entity.iter(self.count_rows, self.count_cols); for p in iter.by_ref() { if self.seen.contains(&p) { self.current = Some(iter); return Some(p.into()); } } } None } } /// An [`Iterator`] which goes goes over cells which are not present an [`Object::Iter`]ator. #[derive(Debug)] pub struct InversionIter { all: SectorCellsIter, seen: HashSet, } impl InversionIter { fn new(obj: O, count_rows: usize, count_columns: usize) -> Self where O: Iterator, { let size = match obj.size_hint() { (s1, Some(s2)) if s1 == s2 => s1, _ => 0, }; let mut seen = HashSet::with_capacity(size); for entity in obj { seen.extend(entity.iter(count_rows, count_columns)); } let all = SectorCellsIter::new(0, count_rows, 0, count_columns); Self { all, seen } } } impl Iterator for InversionIter { type Item = Entity; fn next(&mut self) -> Option { for p in self.all.by_ref() { if !self.seen.contains(&p) { return Some(p.into()); } } None } } #[cfg(test)] mod tests { use crate::grid::records::vec_records::VecRecords; use super::*; #[test] fn cell_test() { assert_eq!(vec_cells((0, 0), 2, 3), [Entity::Cell(0, 0)]); assert_eq!(vec_cells((1, 1), 2, 3), [Entity::Cell(1, 1)]); assert_eq!(vec_cells((1, 1), 0, 0), [Entity::Cell(1, 1)]); assert_eq!(vec_cells((1, 100), 2, 3), [Entity::Cell(1, 100)]); assert_eq!(vec_cells((100, 1), 2, 3), [Entity::Cell(100, 1)]); } #[test] fn columns_test() { assert_eq!( vec_cells(Columns::new(..), 2, 3), [Entity::Column(0), Entity::Column(1), Entity::Column(2)] ); assert_eq!( vec_cells(Columns::new(1..), 2, 3), [Entity::Column(1), Entity::Column(2)] ); assert_eq!(vec_cells(Columns::new(2..), 2, 3), [Entity::Column(2)]); assert_eq!(vec_cells(Columns::new(3..), 2, 3), []); assert_eq!(vec_cells(Columns::new(3..), 0, 0), []); assert_eq!(vec_cells(Columns::new(0..1), 2, 3), [Entity::Column(0)]); assert_eq!(vec_cells(Columns::new(1..2), 2, 3), [Entity::Column(1)]); assert_eq!(vec_cells(Columns::new(2..3), 2, 3), [Entity::Column(2)]); assert_eq!(vec_cells(Columns::new(..), 0, 0), []); assert_eq!(vec_cells(Columns::new(..), 2, 0), []); assert_eq!(vec_cells(Columns::new(..), 0, 3), []); } #[test] fn first_column_test() { assert_eq!(vec_cells(Columns::first(), 5, 2), [Entity::Column(0)]); assert_eq!(vec_cells(Columns::first(), 0, 0), []); assert_eq!(vec_cells(Columns::first(), 10, 0), []); assert_eq!(vec_cells(Columns::first(), 0, 10), []); } #[test] fn last_column_test() { assert_eq!(vec_cells(Columns::last(), 5, 2), [Entity::Column(1)]); assert_eq!(vec_cells(Columns::last(), 5, 29), [Entity::Column(28)]); assert_eq!(vec_cells(Columns::last(), 0, 0), []); assert_eq!(vec_cells(Columns::last(), 10, 0), []); assert_eq!(vec_cells(Columns::last(), 0, 10), []); } #[test] fn last_column_sub_test() { assert_eq!(vec_cells(Columns::last(), 5, 2), [Entity::Column(1)]); assert_eq!(vec_cells(Columns::last() - 0, 5, 2), [Entity::Column(1)]); assert_eq!(vec_cells(Columns::last() - 1, 5, 2), [Entity::Column(0)]); assert_eq!(vec_cells(Columns::last() - 2, 5, 2), []); assert_eq!(vec_cells(Columns::last() - 100, 5, 2), []); } #[test] fn first_column_add_test() { assert_eq!(vec_cells(Columns::first(), 5, 2), [Entity::Column(0)]); assert_eq!(vec_cells(Columns::first() + 0, 5, 2), [Entity::Column(0)]); assert_eq!(vec_cells(Columns::first() + 1, 5, 2), [Entity::Column(1)]); assert_eq!(vec_cells(Columns::first() + 2, 5, 2), [Entity::Column(2)]); assert_eq!( vec_cells(Columns::first() + 100, 5, 2), [Entity::Column(100)] ); } #[test] fn rows_test() { assert_eq!( vec_cells(Rows::new(..), 2, 3), [Entity::Row(0), Entity::Row(1)] ); assert_eq!(vec_cells(Rows::new(1..), 2, 3), [Entity::Row(1)]); assert_eq!(vec_cells(Rows::new(2..), 2, 3), []); assert_eq!(vec_cells(Rows::new(2..), 0, 0), []); assert_eq!(vec_cells(Rows::new(0..1), 2, 3), [Entity::Row(0)],); assert_eq!(vec_cells(Rows::new(1..2), 2, 3), [Entity::Row(1)],); assert_eq!(vec_cells(Rows::new(..), 0, 0), []); assert_eq!(vec_cells(Rows::new(..), 0, 3), []); assert_eq!( vec_cells(Rows::new(..), 2, 0), [Entity::Row(0), Entity::Row(1)] ); } #[test] fn last_row_test() { assert_eq!(vec_cells(Rows::last(), 5, 2), [Entity::Row(4)]); assert_eq!(vec_cells(Rows::last(), 100, 2), [Entity::Row(99)]); assert_eq!(vec_cells(Rows::last(), 0, 0), []); assert_eq!(vec_cells(Rows::last(), 5, 0), []); assert_eq!(vec_cells(Rows::last(), 0, 2), []); } #[test] fn first_row_test() { assert_eq!(vec_cells(Rows::first(), 5, 2), [Entity::Row(0)]); assert_eq!(vec_cells(Rows::first(), 100, 2), [Entity::Row(0)]); assert_eq!(vec_cells(Rows::first(), 0, 0), []); assert_eq!(vec_cells(Rows::first(), 5, 0), []); assert_eq!(vec_cells(Rows::first(), 0, 2), []); } #[test] fn last_row_sub_test() { assert_eq!(vec_cells(Rows::last(), 5, 2), [Entity::Row(4)]); assert_eq!(vec_cells(Rows::last() - 0, 5, 2), [Entity::Row(4)]); assert_eq!(vec_cells(Rows::last() - 1, 5, 2), [Entity::Row(3)]); assert_eq!(vec_cells(Rows::last() - 2, 5, 2), [Entity::Row(2)]); assert_eq!(vec_cells(Rows::last() - 3, 5, 2), [Entity::Row(1)]); assert_eq!(vec_cells(Rows::last() - 4, 5, 2), [Entity::Row(0)]); assert_eq!(vec_cells(Rows::last() - 5, 5, 2), []); assert_eq!(vec_cells(Rows::last() - 100, 5, 2), []); assert_eq!(vec_cells(Rows::last() - 1, 0, 0), []); assert_eq!(vec_cells(Rows::last() - 1, 5, 0), []); assert_eq!(vec_cells(Rows::last() - 1, 0, 2), []); } #[test] fn first_row_add_test() { assert_eq!(vec_cells(Rows::first(), 5, 2), [Entity::Row(0)]); assert_eq!(vec_cells(Rows::first() + 0, 5, 2), [Entity::Row(0)]); assert_eq!(vec_cells(Rows::first() + 1, 5, 2), [Entity::Row(1)]); assert_eq!(vec_cells(Rows::first() + 2, 5, 2), [Entity::Row(2)]); assert_eq!(vec_cells(Rows::first() + 3, 5, 2), [Entity::Row(3)]); assert_eq!(vec_cells(Rows::first() + 4, 5, 2), [Entity::Row(4)]); assert_eq!(vec_cells(Rows::first() + 5, 5, 2), [Entity::Row(5)]); assert_eq!(vec_cells(Rows::first() + 100, 5, 2), [Entity::Row(100)]); assert_eq!(vec_cells(Rows::first() + 1, 0, 0), [Entity::Row(1)]); assert_eq!(vec_cells(Rows::first() + 1, 5, 0), [Entity::Row(1)]); assert_eq!(vec_cells(Rows::first() + 1, 0, 2), [Entity::Row(1)]); } #[test] fn frame_test() { assert_eq!( vec_cells(Frame, 2, 3), [ Entity::Cell(0, 0), Entity::Cell(0, 1), Entity::Cell(0, 2), Entity::Cell(1, 0), Entity::Cell(1, 1), Entity::Cell(1, 2) ] ); assert_eq!(vec_cells(Frame, 0, 0), []); assert_eq!(vec_cells(Frame, 2, 0), []); assert_eq!(vec_cells(Frame, 0, 2), []); } #[test] fn segment_test() { assert_eq!( vec_cells(Segment::new(.., ..), 2, 3), [ Entity::Cell(0, 0), Entity::Cell(0, 1), Entity::Cell(0, 2), Entity::Cell(1, 0), Entity::Cell(1, 1), Entity::Cell(1, 2) ] ); assert_eq!( vec_cells(Segment::new(1.., ..), 2, 3), [Entity::Cell(1, 0), Entity::Cell(1, 1), Entity::Cell(1, 2)] ); assert_eq!(vec_cells(Segment::new(2.., ..), 2, 3), []); assert_eq!( vec_cells(Segment::new(.., 1..), 2, 3), [ Entity::Cell(0, 1), Entity::Cell(0, 2), Entity::Cell(1, 1), Entity::Cell(1, 2) ] ); assert_eq!( vec_cells(Segment::new(.., 2..), 2, 3), [Entity::Cell(0, 2), Entity::Cell(1, 2)] ); assert_eq!(vec_cells(Segment::new(.., 3..), 2, 3), []); assert_eq!( vec_cells(Segment::new(1.., 1..), 2, 3), [Entity::Cell(1, 1), Entity::Cell(1, 2)] ); assert_eq!( vec_cells(Segment::new(1..2, 1..2), 2, 3), [Entity::Cell(1, 1)] ); assert_eq!(vec_cells(Segment::new(5.., 5..), 2, 3), []); } #[test] fn object_and_test() { assert_eq!( vec_cells(Cell::new(0, 0).and(Cell::new(0, 0)), 2, 3), [Entity::Cell(0, 0)] ); assert_eq!( vec_cells(Cell::new(0, 0).and(Cell::new(1, 2)), 2, 3), [Entity::Cell(0, 0), Entity::Cell(1, 2)] ); assert_eq!(vec_cells(Cell::new(0, 0).and(Cell::new(1, 2)), 0, 0), []); } #[test] fn object_not_test() { assert_eq!(vec_cells(Rows::first().not(Cell::new(0, 0)), 0, 0), []); assert_eq!(vec_cells(Cell::new(0, 0).not(Cell::new(0, 0)), 2, 3), []); assert_eq!( vec_cells(Rows::first().not(Cell::new(0, 0)), 2, 3), [Entity::Cell(0, 1), Entity::Cell(0, 2)] ); assert_eq!( vec_cells(Columns::single(1).not(Rows::single(1)), 3, 3), [Entity::Cell(0, 1), Entity::Cell(2, 1)] ); assert_eq!( vec_cells(Rows::single(1).not(Columns::single(1)), 3, 3), [Entity::Cell(1, 0), Entity::Cell(1, 2)] ); } #[test] fn object_intersect_test() { assert_eq!( vec_cells(Rows::first().intersect(Cell::new(0, 0)), 0, 0), [] ); assert_eq!( vec_cells(Segment::all().intersect(Rows::single(1)), 2, 3), [Entity::Cell(1, 0), Entity::Cell(1, 1), Entity::Cell(1, 2)] ); assert_eq!( vec_cells(Cell::new(0, 0).intersect(Cell::new(0, 0)), 2, 3), [Entity::Cell(0, 0)] ); assert_eq!( vec_cells(Rows::first().intersect(Cell::new(0, 0)), 2, 3), [Entity::Cell(0, 0)] ); // maybe we somehow shall not limit the rows/columns by the max count? assert_eq!( vec_cells(Rows::single(1).intersect(Columns::single(1)), 2, 1), [] ); } #[test] fn object_inverse_test() { assert_eq!(vec_cells(Segment::all().inverse(), 2, 3), []); assert_eq!( vec_cells(Cell::new(0, 0).inverse(), 2, 3), [ Entity::Cell(0, 1), Entity::Cell(0, 2), Entity::Cell(1, 0), Entity::Cell(1, 1), Entity::Cell(1, 2) ] ); assert_eq!( vec_cells(Rows::first().inverse(), 2, 3), [Entity::Cell(1, 0), Entity::Cell(1, 1), Entity::Cell(1, 2)] ); assert_eq!(vec_cells(Rows::first().inverse(), 0, 0), []); } fn vec_cells>>( o: O, count_rows: usize, count_cols: usize, ) -> Vec { let data = vec![vec![String::default(); count_cols]; count_rows]; let records = VecRecords::new(data); o.cells(&records).collect::>() } } tabled-0.18.0/src/settings/object/rows.rs000064400000000000000000000121061046102023000164020ustar 00000000000000use std::ops::{Add, RangeBounds, Sub}; use crate::{ grid::config::Entity, grid::records::{ExactRecords, Records}, settings::object::{cell::EntityOnce, Object}, }; use super::util::bounds_to_usize; /// Row denotes a set of cells on given rows on a [`Table`]. /// /// [`Table`]: crate::Table #[derive(Debug)] pub struct Rows { range: R, } impl Rows { /// Returns a new instance of [`Rows`] for a range of rows. /// /// If the boundaries are exceeded it may panic. pub fn new(range: R) -> Self where R: RangeBounds, { Self { range } } pub(crate) const fn get_range(&self) -> &R { &self.range } } impl Rows<()> { /// Returns a new instance of [`Rows`] with a single row. /// /// If the boundaries are exceeded it may panic. pub const fn single(index: usize) -> Row { Row { index } } /// Returns a first row [`Object`]. /// /// If the table has 0 rows returns an empty set of cells. pub const fn first() -> FirstRow { FirstRow } /// Returns a last row [`Object`]. /// /// If the table has 0 rows returns an empty set of cells. pub const fn last() -> LastRow { LastRow } } impl Object for Rows where R: RangeBounds, I: ExactRecords, { type Iter = RowsIter; fn cells(&self, records: &I) -> Self::Iter { let start = self.range.start_bound(); let end = self.range.end_bound(); let max = records.count_rows(); let (x, y) = bounds_to_usize(start, end, max); RowsIter::new(x, y) } } /// A row which is located by an offset from the first row. #[derive(Debug, Clone, Copy)] pub struct Row { index: usize, } impl Object for Row { type Iter = EntityOnce; fn cells(&self, _: &I) -> Self::Iter { EntityOnce::new(Some(Entity::Row(self.index))) } } impl From for usize { fn from(val: Row) -> Self { val.index } } impl From for Row { fn from(index: usize) -> Row { Row { index } } } /// This structure represents the first row of a [`Table`]. /// It's often contains headers data. /// /// [`Table`]: crate::Table #[derive(Debug)] pub struct FirstRow; impl Object for FirstRow where I: Records + ExactRecords, { type Iter = EntityOnce; fn cells(&self, records: &I) -> Self::Iter { if records.count_columns() == 0 || records.count_rows() == 0 { return EntityOnce::new(None); } EntityOnce::new(Some(Entity::Row(0))) } } impl Add for FirstRow { type Output = Row; fn add(self, rhs: usize) -> Self::Output { Row { index: rhs } } } /// This structure represents the last row of a [`Table`]. /// /// [`Table`]: crate::Table #[derive(Debug)] pub struct LastRow; impl Object for LastRow where I: Records + ExactRecords, { type Iter = EntityOnce; fn cells(&self, records: &I) -> Self::Iter { let count_rows = records.count_rows(); if records.count_columns() == 0 || count_rows == 0 { return EntityOnce::new(None); } let row = count_rows - 1; EntityOnce::new(Some(Entity::Row(row))) } } impl Sub for LastRow { type Output = LastRowOffset; fn sub(self, rhs: usize) -> Self::Output { LastRowOffset::sub(rhs) } } impl Add for LastRow { type Output = LastRowOffset; fn add(self, rhs: usize) -> Self::Output { LastRowOffset::add(rhs) } } /// A row which is located by an offset from the last row. #[derive(Debug)] pub struct LastRowOffset { offset: usize, sign: bool, } impl LastRowOffset { fn sub(offset: usize) -> Self { Self { offset, sign: false, } } fn add(offset: usize) -> Self { Self { offset, sign: true } } } impl Object for LastRowOffset where I: Records + ExactRecords, { type Iter = EntityOnce; fn cells(&self, records: &I) -> Self::Iter { let count_rows = records.count_rows(); if records.count_columns() == 0 || count_rows == 0 { return EntityOnce::new(None); } let last_row = count_rows - 1; if self.sign { let row = last_row + self.offset; EntityOnce::new(Some(Entity::Row(row))) } else { if self.offset > last_row { return EntityOnce::new(None); } let row = last_row - self.offset; EntityOnce::new(Some(Entity::Row(row))) } } } /// An [`Iterator`] which goes goes over all rows of a [`Table`]. /// /// [`Table`]: crate::Table #[derive(Debug)] pub struct RowsIter { start: usize, end: usize, } impl RowsIter { const fn new(start: usize, end: usize) -> Self { Self { start, end } } } impl Iterator for RowsIter { type Item = Entity; fn next(&mut self) -> Option { if self.start >= self.end { return None; } let col = self.start; self.start += 1; Some(Entity::Row(col)) } } tabled-0.18.0/src/settings/object/segment.rs000064400000000000000000000065311046102023000170570ustar 00000000000000use std::ops::{RangeBounds, RangeFull}; use papergrid::config::Position; use crate::{ grid::config::Entity, grid::records::{ExactRecords, Records}, settings::object::{cell::EntityOnce, Object}, }; use super::util::bounds_to_usize; /// This structure represents a sub table of [`Table`]. /// /// [`Table`]: crate::Table #[derive(Debug)] pub struct Segment { columns: C, rows: R, } impl Segment { /// Returns a table segment on which are present all cells. pub fn all() -> SegmentAll { SegmentAll } } impl Segment where C: RangeBounds, R: RangeBounds, { /// This function builds a [`Segment`]. pub fn new(rows: R, columns: C) -> Self { Self { columns, rows } } } impl Object for Segment where C: RangeBounds, R: RangeBounds, I: Records + ExactRecords, { type Iter = SectorIter; fn cells(&self, records: &I) -> Self::Iter { let start = self.rows.start_bound(); let end = self.rows.end_bound(); let max = records.count_rows(); let (rows_start, rows_end) = bounds_to_usize(start, end, max); let start = self.columns.start_bound(); let end = self.columns.end_bound(); let max = records.count_columns(); let (cols_start, cols_end) = bounds_to_usize(start, end, max); SectorIter::new(rows_start, rows_end, cols_start, cols_end) } } /// This is a segment which contains all cells on the table. /// /// Can be created from [`Segment::all`]. #[derive(Debug)] pub struct SegmentAll; impl Object for SegmentAll { type Iter = EntityOnce; fn cells(&self, _: &I) -> Self::Iter { EntityOnce::new(Some(Entity::Global)) } } /// An [`Iterator`] which goes goes over all cell in a sector in a [`Table`]. /// /// [`Table`]: crate::Table #[derive(Debug)] pub struct SectorIter { iter: SectorCellsIter, } impl SectorIter { const fn new(rows_start: usize, rows_end: usize, cols_start: usize, cols_end: usize) -> Self { Self { iter: SectorCellsIter::new(rows_start, rows_end, cols_start, cols_end), } } } impl Iterator for SectorIter { type Item = Entity; fn next(&mut self) -> Option { self.iter.next().map(Into::into) } } #[derive(Debug)] pub(crate) struct SectorCellsIter { rows_end: usize, cols_start: usize, cols_end: usize, row: usize, col: usize, } impl SectorCellsIter { /// Create an iterator from 1st row to last from 1st col to last. pub(crate) const fn new( rows_start: usize, rows_end: usize, cols_start: usize, cols_end: usize, ) -> Self { Self { rows_end, cols_start, cols_end, row: rows_start, col: cols_start, } } } impl Iterator for SectorCellsIter { type Item = Position; fn next(&mut self) -> Option { if self.row >= self.rows_end { return None; } if self.col >= self.cols_end { return None; } let row = self.row; let col = self.col; self.col += 1; if self.col == self.cols_end { self.row += 1; self.col = self.cols_start; } Some((row, col).into()) } } tabled-0.18.0/src/settings/object/util.rs000064400000000000000000000015131046102023000163650ustar 00000000000000use std::ops::Bound; /// Converts a range bound to its indexes. pub(super) fn bounds_to_usize( left: Bound<&usize>, right: Bound<&usize>, count_elements: usize, ) -> (usize, usize) { match (left, right) { (Bound::Included(x), Bound::Included(y)) => (*x, y + 1), (Bound::Included(x), Bound::Excluded(y)) => (*x, *y), (Bound::Included(x), Bound::Unbounded) => (*x, count_elements), (Bound::Unbounded, Bound::Unbounded) => (0, count_elements), (Bound::Unbounded, Bound::Included(y)) => (0, y + 1), (Bound::Unbounded, Bound::Excluded(y)) => (0, *y), (Bound::Excluded(_), Bound::Unbounded) | (Bound::Excluded(_), Bound::Included(_)) | (Bound::Excluded(_), Bound::Excluded(_)) => { unreachable!("A start bound can't be excluded") } } } tabled-0.18.0/src/settings/padding/mod.rs000064400000000000000000000103511046102023000163270ustar 00000000000000//! This module contains a [`Padding`] setting of a cell on a [`Table`]. //! //! # Example //! //! #![cfg_attr(feature = "std", doc = "```")] #![cfg_attr(not(feature = "std"), doc = "```ignore")] //! # use tabled::{settings::{Style, Padding, object::Rows, Modify}, Table}; //! # let data: Vec<&'static str> = Vec::new(); //! let table = Table::new(&data) //! .with(Modify::new(Rows::single(0)).with(Padding::new(0, 0, 1, 1).fill('>', '<', '^', 'V'))); //! ``` //! //! [`Table`]: crate::Table use crate::{ grid::{ config::{CompactConfig, CompactMultilineConfig}, config::{Indent, Sides}, }, settings::TableOption, }; #[cfg(feature = "std")] use crate::{ grid::{config::ColoredConfig, config::Entity}, settings::padding_expand::PaddingExpand, settings::CellOption, }; /// Padding is responsible for a left/right/top/bottom inner indent of a particular cell. /// /// # Example /// #[cfg_attr(feature = "std", doc = "```")] #[cfg_attr(not(feature = "std"), doc = "```ignore")] /// use tabled::{ /// Table, /// settings::{Padding, Style, Modify, object::Cell}, /// }; /// /// let table = Table::new("2022".chars()) /// .with(Style::modern()) /// .with(Modify::new((2, 0)).with(Padding::new(1, 1, 2, 2))) /// .to_string(); /// /// assert_eq!( /// table, /// concat!( /// "┌──────┐\n", /// "│ char │\n", /// "├──────┤\n", /// "│ 2 │\n", /// "├──────┤\n", /// "│ │\n", /// "│ │\n", /// "│ 0 │\n", /// "│ │\n", /// "│ │\n", /// "├──────┤\n", /// "│ 2 │\n", /// "├──────┤\n", /// "│ 2 │\n", /// "└──────┘", /// ), /// ); /// ``` #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] pub struct Padding { indent: Sides, } impl Padding { /// Construct's an Padding object. /// /// It uses space(' ') as a default fill character. /// To set a custom character you can use [`Padding::fill`] function. pub const fn new(left: usize, right: usize, top: usize, bottom: usize) -> Self { Self { indent: Sides::new( Indent::spaced(left), Indent::spaced(right), Indent::spaced(top), Indent::spaced(bottom), ), } } /// Construct's an Padding object with all sides set to 0. /// /// It uses space(' ') as a default fill character. /// To set a custom character you can use [`Padding::fill`] function. pub const fn zero() -> Self { Self::new(0, 0, 0, 0) } /// The function, sets a characters for the padding on an each side. pub const fn fill(mut self, left: char, right: char, top: char, bottom: char) -> Self { self.indent.left.fill = left; self.indent.right.fill = right; self.indent.top.fill = top; self.indent.bottom.fill = bottom; self } #[cfg(feature = "std")] /// Construct's an PaddingExpand object. pub const fn expand(horizontal: bool) -> PaddingExpand { if horizontal { PaddingExpand::Horizontal } else { PaddingExpand::Vertical } } } #[cfg(feature = "std")] impl CellOption for Padding { fn change(self, _: &mut R, cfg: &mut ColoredConfig, entity: Entity) { let indent = self.indent; let pad = Sides::new(indent.left, indent.right, indent.top, indent.bottom); cfg.set_padding(entity, pad); } } #[cfg(feature = "std")] impl TableOption for Padding { fn change(self, records: &mut R, cfg: &mut ColoredConfig, _: &mut D) { >::change(self, records, cfg, Entity::Global) } } impl TableOption for Padding { fn change(self, _: &mut R, cfg: &mut CompactConfig, _: &mut D) { *cfg = cfg.set_padding(self.indent); } } impl TableOption for Padding { fn change(self, _: &mut R, cfg: &mut CompactMultilineConfig, _: &mut D) { cfg.set_padding(self.indent); } } tabled-0.18.0/src/settings/padding_color/mod.rs000064400000000000000000000106311046102023000175260ustar 00000000000000//! This module contains a [`PaddingColor`] setting of a cell on a [`Table`]. //! //! # Example //! #![cfg_attr(feature = "std", doc = "```")] #![cfg_attr(not(feature = "std"), doc = "```ignore")] //! # use tabled::{settings::{Style, Padding, object::Rows, Modify}, Table}; //! # let data: Vec<&'static str> = Vec::new(); //! let table = Table::new(&data) //! .with(Modify::new(Rows::single(0)) //! .with(Padding::new(0, 0, 1, 1).fill('>', '<', '^', 'V')) //! ); //! ``` //! //! [`Table`]: crate::Table use crate::{ grid::{ ansi::ANSIStr, config::Sides, config::{CompactConfig, CompactMultilineConfig}, }, settings::TableOption, }; #[cfg(feature = "std")] use crate::grid::{ansi::ANSIBuf, config::ColoredConfig, config::Entity}; #[cfg(feature = "std")] use crate::settings::CellOption; /// PaddingColor is responsible for a left/right/top/bottom inner color of a particular cell. /// /// # Example /// #[cfg_attr(feature = "ansi", doc = "```")] #[cfg_attr(not(feature = "ansi"), doc = "```ignore")] /// use tabled::{ /// Table, /// settings::{Padding, PaddingColor, Color, Style}, /// }; /// /// let table = Table::new("2024".chars()) /// .with(Style::modern()) /// .modify((2, 0), Padding::new(2, 4, 0, 0)) /// .modify((2, 0), PaddingColor::filled(Color::FG_RED)) /// .to_string(); /// /// assert_eq!( /// table, /// concat!( /// "┌───────┐\n", /// "│ char │\n", /// "├───────┤\n", /// "│ 2 │\n", /// "├───────┤\n", /// "│\u{1b}[31m \u{1b}[39m0\u{1b}[31m \u{1b}[39m│\n", /// "├───────┤\n", /// "│ 2 │\n", /// "├───────┤\n", /// "│ 4 │\n", /// "└───────┘", /// ), /// ); /// ``` #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] pub struct PaddingColor { colors: Sides, } impl PaddingColor { /// Construct's an Padding object. pub const fn new(left: C, right: C, top: C, bottom: C) -> Self { Self { colors: Sides::new(left, right, top, bottom), } } /// The function, sets a color for all sides. pub fn filled(color: C) -> Self where C: Clone, { Self::new(color.clone(), color.clone(), color.clone(), color) } } impl PaddingColor> { /// Construct's an Padding object with no color. pub const fn empty() -> Self { Self::new( ANSIStr::empty(), ANSIStr::empty(), ANSIStr::empty(), ANSIStr::empty(), ) } } impl From> for Sides { fn from(value: PaddingColor) -> Self { value.colors } } impl From> for PaddingColor { fn from(colors: Sides) -> Self { Self { colors } } } #[cfg(feature = "std")] impl CellOption for PaddingColor where C: Into + Clone, { fn change(self, _: &mut R, cfg: &mut ColoredConfig, entity: Entity) { let colors = self.colors.clone(); let pad = Sides::new( Some(colors.left.into()), Some(colors.right.into()), Some(colors.top.into()), Some(colors.bottom.into()), ); cfg.set_padding_color(entity, pad); } } #[cfg(feature = "std")] impl TableOption for PaddingColor where C: Into + Clone, { fn change(self, records: &mut R, cfg: &mut ColoredConfig, _: &mut D) { >::change(self, records, cfg, Entity::Global) } } impl TableOption for PaddingColor where C: Into> + Clone, { fn change(self, _: &mut R, cfg: &mut CompactConfig, _: &mut D) { let c = self.colors.clone(); let colors = Sides::new(c.left.into(), c.right.into(), c.top.into(), c.bottom.into()); *cfg = cfg.set_padding_color(colors); } } impl TableOption for PaddingColor where C: Into> + Clone, { fn change(self, _: &mut R, cfg: &mut CompactMultilineConfig, _: &mut D) { let c = self.colors.clone(); let colors = Sides::new(c.left.into(), c.right.into(), c.top.into(), c.bottom.into()); cfg.set_padding_color(colors); } } tabled-0.18.0/src/settings/padding_expand/mod.rs000064400000000000000000000146601046102023000176750ustar 00000000000000//! This module contains a [`PaddingExpand`] setting to expand cells padding to its limit a cell. use papergrid::{ config::{AlignmentHorizontal, AlignmentVertical}, dimension::spanned::SpannedGridDimension, records::{ExactRecords, IntoRecords, PeekableRecords, Records}, }; #[cfg(feature = "std")] use crate::{ grid::{config::ColoredConfig, config::Entity}, settings::CellOption, }; use super::TableOption; /// PaddingExpand is able to expand padding to its limit a cell. /// Maybe usefull in cases were its colorization is supposed to be used next. /// /// # Example /// #[cfg_attr(feature = "ansi", doc = "```")] #[cfg_attr(not(feature = "ansi"), doc = "```ignore")] /// use std::iter::FromIterator; /// /// use tabled::{ /// settings::{Padding, Style, Color, PaddingColor}, /// Table, /// }; /// /// let char_split = |s: &str| s.chars().map(|c| c.to_string()).collect::>(); /// let data = vec![ /// char_split("2021"), /// char_split("2022"), /// char_split("2023"), /// char_split("2024"), /// ]; /// /// let table = Table::from_iter(&data) /// .with(Style::ascii()) /// .with(PaddingColor::filled(Color::BG_BLUE)) /// .modify((2, 1), Padding::new(2, 2, 3, 3)) /// .with(Padding::expand(true)) /// .with(Padding::expand(false)) /// .to_string(); /// /// assert_eq!( /// table, /// concat!( /// "+---+-----+---+---+\n", /// "|2\u{1b}[44m \u{1b}[49m|0\u{1b}[44m \u{1b}[49m|2\u{1b}[44m \u{1b}[49m|1\u{1b}[44m \u{1b}[49m|\n", /// "+---+-----+---+---+\n", /// "|2\u{1b}[44m \u{1b}[49m|0\u{1b}[44m \u{1b}[49m|2\u{1b}[44m \u{1b}[49m|2\u{1b}[44m \u{1b}[49m|\n", /// "+---+-----+---+---+\n", /// "|2\u{1b}[44m \u{1b}[49m|0\u{1b}[44m \u{1b}[49m|2\u{1b}[44m \u{1b}[49m|3\u{1b}[44m \u{1b}[49m|\n", /// "|\u{1b}[44m \u{1b}[49m|\u{1b}[44m \u{1b}[49m|\u{1b}[44m \u{1b}[49m|\u{1b}[44m \u{1b}[49m|\n", /// "|\u{1b}[44m \u{1b}[49m|\u{1b}[44m \u{1b}[49m|\u{1b}[44m \u{1b}[49m|\u{1b}[44m \u{1b}[49m|\n", /// "|\u{1b}[44m \u{1b}[49m|\u{1b}[44m \u{1b}[49m|\u{1b}[44m \u{1b}[49m|\u{1b}[44m \u{1b}[49m|\n", /// "|\u{1b}[44m \u{1b}[49m|\u{1b}[44m \u{1b}[49m|\u{1b}[44m \u{1b}[49m|\u{1b}[44m \u{1b}[49m|\n", /// "|\u{1b}[44m \u{1b}[49m|\u{1b}[44m \u{1b}[49m|\u{1b}[44m \u{1b}[49m|\u{1b}[44m \u{1b}[49m|\n", /// "|\u{1b}[44m \u{1b}[49m|\u{1b}[44m \u{1b}[49m|\u{1b}[44m \u{1b}[49m|\u{1b}[44m \u{1b}[49m|\n", /// "+---+-----+---+---+\n", /// "|2\u{1b}[44m \u{1b}[49m|0\u{1b}[44m \u{1b}[49m|2\u{1b}[44m \u{1b}[49m|4\u{1b}[44m \u{1b}[49m|\n", /// "+---+-----+---+---+", /// ), /// ); /// ``` #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] pub enum PaddingExpand { /// Horizontal expansion of padding (LEFT and RIGHT) Horizontal, /// Vertical expansion of padding (TOP and BOTTOM) Vertical, } impl TableOption for PaddingExpand where R: Records + ExactRecords + PeekableRecords, for<'a> &'a R: Records, for<'a> <<&'a R as Records>::Iter as IntoRecords>::Cell: AsRef, { fn change(self, records: &mut R, cfg: &mut ColoredConfig, _: &mut D) { >::change(self, records, cfg, Entity::Global) } } impl CellOption for PaddingExpand where R: Records + ExactRecords + PeekableRecords, for<'a> &'a R: Records, for<'a> <<&'a R as Records>::Iter as IntoRecords>::Cell: AsRef, { fn change(self, records: &mut R, cfg: &mut ColoredConfig, entity: Entity) { match self { PaddingExpand::Vertical => expand_vertical(records, cfg, entity), PaddingExpand::Horizontal => expand_horizontal(records, cfg, entity), } } } fn expand_horizontal(records: &mut R, cfg: &mut ColoredConfig, entity: Entity) where R: Records + ExactRecords + PeekableRecords, for<'a> &'a R: Records, for<'a> <<&'a R as Records>::Iter as IntoRecords>::Cell: AsRef, { let widths = SpannedGridDimension::width(&*records, cfg); let count_rows = records.count_rows(); let count_cols = records.count_columns(); for pos in entity.iter(count_rows, count_cols) { let col = pos.col(); let column_width = widths[col]; let width = records.get_width(pos); if width < column_width { let alignment = *cfg.get_alignment_horizontal(pos); let available_width = column_width - width; let (left, right) = split_horizontal_space(alignment, available_width); let mut pad = cfg.get_padding(pos); pad.left.size = left; pad.right.size = right; cfg.set_padding(Entity::from(pos), pad); } } } fn expand_vertical(records: &mut R, cfg: &mut ColoredConfig, entity: Entity) where R: Records + ExactRecords + PeekableRecords, for<'a> &'a R: Records, for<'a> <<&'a R as Records>::Iter as IntoRecords>::Cell: AsRef, { let heights = SpannedGridDimension::height(&*records, cfg); let count_rows = records.count_rows(); let count_cols = records.count_columns(); for pos in entity.iter(count_rows, count_cols) { let row = pos.row(); let row_height = heights[row]; let cell_height = records.count_lines(pos); if cell_height < row_height { let alignment = *cfg.get_alignment_vertical(pos); let available_width = row_height - cell_height; let (top, bottom) = split_vertical_space(alignment, available_width); let mut pad = cfg.get_padding(pos); pad.top.size = top; pad.bottom.size = bottom; cfg.set_padding(Entity::from(pos), pad); } } } fn split_horizontal_space(al: AlignmentHorizontal, space: usize) -> (usize, usize) { match al { AlignmentHorizontal::Center => { let left = space / 2; let right = space - left; (left, right) } AlignmentHorizontal::Left => (0, space), AlignmentHorizontal::Right => (space, 0), } } fn split_vertical_space(al: AlignmentVertical, space: usize) -> (usize, usize) { match al { AlignmentVertical::Center => { let left = space / 2; let right = space - left; (left, right) } AlignmentVertical::Top => (0, space), AlignmentVertical::Bottom => (space, 0), } } tabled-0.18.0/src/settings/panel/footer.rs000064400000000000000000000013711046102023000165410ustar 00000000000000use crate::{ grid::config::ColoredConfig, grid::records::{ExactRecords, Records, RecordsMut, Resizable}, settings::TableOption, }; use super::Panel; /// Footer renders a [`Panel`] at the bottom. /// See [`Panel`]. #[derive(Debug)] pub struct Footer(S); impl Footer { /// Creates a new object. pub fn new(text: S) -> Self where S: AsRef, { Self(text) } } impl TableOption for Footer where S: AsRef, R: Records + ExactRecords + Resizable + RecordsMut, { fn change(self, records: &mut R, cfg: &mut ColoredConfig, dimension: &mut D) { Panel::horizontal(records.count_rows(), self.0.as_ref()).change(records, cfg, dimension); } } tabled-0.18.0/src/settings/panel/header.rs000064400000000000000000000013431046102023000164720ustar 00000000000000use crate::{ grid::config::ColoredConfig, grid::records::{ExactRecords, Records, RecordsMut, Resizable}, settings::TableOption, }; use super::Panel; /// Header inserts a [`Panel`] at the top. /// See [`Panel`]. #[derive(Debug)] pub struct Header(S); impl Header { /// Creates a new object. pub fn new(text: S) -> Self where S: AsRef, { Self(text) } } impl TableOption for Header where S: AsRef, R: Records + ExactRecords + Resizable + RecordsMut, { fn change(self, records: &mut R, cfg: &mut ColoredConfig, dimension: &mut D) { Panel::horizontal(0, self.0.as_ref()).change(records, cfg, dimension); } } tabled-0.18.0/src/settings/panel/horizontal_panel.rs000064400000000000000000000040451046102023000206140ustar 00000000000000use crate::{ grid::config::{ColoredConfig, SpannedConfig}, grid::records::{ExactRecords, Records, RecordsMut, Resizable}, settings::TableOption, }; /// A horizontal/column span from 0 to a count rows. #[derive(Debug)] pub struct HorizontalPanel { text: S, row: usize, } impl HorizontalPanel { /// Creates a new horizontal panel. pub fn new(row: usize, text: S) -> Self { Self { row, text } } } impl TableOption for HorizontalPanel where S: AsRef, R: Records + ExactRecords + Resizable + RecordsMut, { fn change(self, records: &mut R, cfg: &mut ColoredConfig, _: &mut D) { let count_rows = records.count_rows(); let count_cols = records.count_columns(); if self.row > count_rows { return; } let is_intersect_vertical_span = (0..records.count_columns()) .any(|col| cfg.is_cell_covered_by_row_span((self.row, col).into())); if is_intersect_vertical_span { return; } move_rows_aside(records, self.row); move_row_spans(cfg, self.row); let text = self.text.as_ref().to_owned(); records.set((self.row, 0).into(), text); cfg.set_column_span((self.row, 0).into(), count_cols); } } fn move_rows_aside(records: &mut R, row: usize) { records.push_row(); let count_rows = records.count_rows(); let shift_count = count_rows - row; for i in 1..shift_count { let row = count_rows - i; records.swap_row(row, row - 1); } } fn move_row_spans(cfg: &mut SpannedConfig, target_row: usize) { for (p, span) in cfg.get_column_spans() { if p.row() < target_row { continue; } cfg.set_column_span(p, 1); cfg.set_column_span(p + (1, 0), span); } for (p, span) in cfg.get_row_spans() { if p.row() < target_row { continue; } cfg.set_row_span(p, 1); cfg.set_row_span(p + (1, 0), span); } } tabled-0.18.0/src/settings/panel/mod.rs000064400000000000000000000064651046102023000160330ustar 00000000000000//! This module contains primitives to create a spread row. //! Ultimately it is a cell with a span set to a number of columns on the [`Table`]. //! //! You can use a [`Span`] to set a custom span. //! //! # Example //! //! ``` //! use tabled::{Table, settings::Panel}; //! //! let data = [[1, 2, 3], [4, 5, 6]]; //! //! let table = Table::new(data) //! .with(Panel::vertical(1, "S\np\nl\ni\nt")) //! .with(Panel::header("Numbers")) //! .to_string(); //! //! println!("{}", table); //! //! assert_eq!( //! table, //! concat!( //! "+---+---+---+---+\n", //! "| Numbers |\n", //! "+---+---+---+---+\n", //! "| 0 | S | 1 | 2 |\n", //! "+---+ p +---+---+\n", //! "| 1 | l | 2 | 3 |\n", //! "+---+ i +---+---+\n", //! "| 4 | t | 5 | 6 |\n", //! "+---+---+---+---+", //! ) //! ) //! ``` //! //! [`Table`]: crate::Table //! [`Span`]: crate::settings::span::Span mod footer; mod header; mod horizontal_panel; mod vertical_panel; pub use footer::Footer; pub use header::Header; pub use horizontal_panel::HorizontalPanel; pub use vertical_panel::VerticalPanel; /// Panel allows to add a Row which has 1 continues Cell to a [`Table`]. /// /// See `examples/panel.rs`. /// /// [`Table`]: crate::Table #[derive(Debug)] pub struct Panel; impl Panel { /// Creates an empty vertical row at given index. /// /// ``` /// use tabled::{settings::Panel, Table}; /// /// let data = [[1, 2, 3], [4, 5, 6]]; /// /// let table = Table::new(data) /// .with(Panel::vertical(1, "Tabled Releases")) /// .to_string(); /// /// println!("{}", table); /// /// assert_eq!( /// table, /// concat!( /// "+---+-----------------+---+---+\n", /// "| 0 | Tabled Releases | 1 | 2 |\n", /// "+---+ +---+---+\n", /// "| 1 | | 2 | 3 |\n", /// "+---+ +---+---+\n", /// "| 4 | | 5 | 6 |\n", /// "+---+-----------------+---+---+", /// ) /// ) /// ``` pub fn vertical>(column: usize, text: S) -> VerticalPanel { VerticalPanel::new(column, text) } /// Creates an empty horizontal row at given index. /// /// ``` /// use tabled::{Table, settings::Panel}; /// /// let data = [[1, 2, 3], [4, 5, 6]]; /// /// let table = Table::new(data) /// .with(Panel::vertical(1, "")) /// .to_string(); /// /// println!("{}", table); /// /// assert_eq!( /// table, /// concat!( /// "+---+--+---+---+\n", /// "| 0 | | 1 | 2 |\n", /// "+---+ +---+---+\n", /// "| 1 | | 2 | 3 |\n", /// "+---+ +---+---+\n", /// "| 4 | | 5 | 6 |\n", /// "+---+--+---+---+", /// ) /// ) /// ``` pub fn horizontal>(row: usize, text: S) -> HorizontalPanel { HorizontalPanel::new(row, text) } /// Creates an horizontal row at first row. pub fn header>(text: S) -> Header { Header::new(text) } /// Creates an horizontal row at last row. pub fn footer>(text: S) -> Footer { Footer::new(text) } } tabled-0.18.0/src/settings/panel/vertical_panel.rs000064400000000000000000000077721046102023000202460ustar 00000000000000use crate::{ grid::config::{ColoredConfig, SpannedConfig}, grid::records::{ExactRecords, Records, RecordsMut, Resizable}, settings::TableOption, }; /// A vertical/row span from 0 to a count columns. #[derive(Debug)] pub struct VerticalPanel { text: S, col: usize, } impl VerticalPanel { /// Creates a new vertical panel. pub fn new(col: usize, text: S) -> Self where S: AsRef, { Self { text, col } } /// Split the set text to a certain width, so it fits within it. pub fn width(self, width: usize) -> VerticalPanel where S: AsRef, { let mut text = String::new(); if width > 0 { text = split_string_by_width(self.text.as_ref(), width); } VerticalPanel { text, col: self.col, } } } impl TableOption for VerticalPanel where S: AsRef, R: Records + ExactRecords + Resizable + RecordsMut, { fn change(self, records: &mut R, cfg: &mut ColoredConfig, _: &mut D) { let count_rows = records.count_rows(); let count_cols = records.count_columns(); if self.col > count_cols { return; } let is_intersect_horizontal_span = (0..=records.count_rows()) .any(|row| cfg.is_cell_covered_by_column_span((row, self.col).into())); if is_intersect_horizontal_span { return; } move_columns_aside(records, self.col); move_column_spans(cfg, self.col); let text = self.text.as_ref().to_owned(); records.set((0, self.col).into(), text); cfg.set_row_span((0, self.col).into(), count_rows); } } fn move_columns_aside(records: &mut R, column: usize) { records.push_column(); let count_columns = records.count_columns(); let shift_count = count_columns - column; for i in 1..shift_count { let col = count_columns - i; records.swap_column(col, col - 1); } } fn move_column_spans(cfg: &mut SpannedConfig, target_column: usize) { for (p, span) in cfg.get_column_spans() { if p.col() < target_column { continue; } cfg.set_column_span(p, 1); cfg.set_column_span(p + (0, 1), span); } for (p, span) in cfg.get_row_spans() { if p.col() < target_column { continue; } cfg.set_row_span(p, 1); cfg.set_row_span(p + (0, 1), span); } } fn split_string_by_width(str: &str, width: usize) -> String { if width == 0 { return String::new(); } let (lhs, rhs) = crate::util::string::split_str(str, width); if rhs.is_empty() { return lhs.into_owned(); } let mut buf = lhs.into_owned(); let mut next = rhs.into_owned(); while !next.is_empty() { let (lhs, rhs) = crate::util::string::split_str(&next, width); buf.push('\n'); buf.push_str(&lhs); next = rhs.into_owned(); } buf } #[cfg(test)] mod tests { use super::*; #[test] fn test_split_string_by_width() { assert_eq!(split_string_by_width("123456789", 3), "123\n456\n789"); assert_eq!(split_string_by_width("123456789", 2), "12\n34\n56\n78\n9"); assert_eq!( split_string_by_width("123456789", 1), "1\n2\n3\n4\n5\n6\n7\n8\n9" ); assert_eq!(split_string_by_width("123456789", 0), ""); assert_eq!( split_string_by_width("\u{1b}[31;100m😳😳🏳️\u{1b}[39m\u{1b}[49m😳🏳️", 3), { #[cfg(feature = "ansi")] { "\u{1b}[31m\u{1b}[100m😳\u{1b}[39m\u{1b}[49m�\n\u{1b}[31m\u{1b}[100m🏳\u{fe0f}\u{1b}[39m\u{1b}[49m😳\n🏳\u{fe0f}" } #[cfg(not(feature = "ansi"))] { "\u{1b}[31\n;10\n0m�\n😳🏳\n\u{fe0f}\u{1b}[39\nm\u{1b}[4\n9m�\n🏳\u{fe0f}" } } ); } } tabled-0.18.0/src/settings/peaker/left.rs000064400000000000000000000015511046102023000163450ustar 00000000000000use super::Peaker; /// A Peaker which goes over column 1 by 1, but truncates as much as possible left side. #[derive(Debug, Default, Clone, PartialEq, Eq, PartialOrd, Ord, Copy, Hash)] pub struct PriorityLeft { i: usize, } impl PriorityLeft { /// Creates a new priority which does not target anything. pub const fn new() -> Self { Self { i: 0 } } } impl Peaker for PriorityLeft { fn peak(&mut self, min: &[usize], widths: &[usize]) -> Option { let col = self.i; if widths[col] > min[col] { return Some(col); } if col + 1 == widths.len() { return None; } let mut col = col + 1; while widths[col] == min[col] { if col + 1 == widths.len() { return None; } col += 1; } Some(col) } } tabled-0.18.0/src/settings/peaker/max.rs000064400000000000000000000027341046102023000162040ustar 00000000000000use super::Peaker; /// A Peaker which goes over the biggest column first. #[derive(Debug, Default, Clone, PartialEq, Eq, PartialOrd, Ord, Copy, Hash)] pub struct PriorityMax { side: bool, } impl PriorityMax { /// Creates a [`PriorityMax`] object with a side set to right or left, /// It's crusial in cases where both columns has equal widths and we need to peak a left or right. /// /// Passing true means a right side. /// Passing false means a left side. pub fn new(priorities_right: bool) -> Self { Self { side: priorities_right, } } /// Creates a [`PriorityMax`] object with left side prioritized, /// See [`PriorityMax::new`]. pub fn left() -> Self { Self::new(false) } /// Creates a [`PriorityMax`] object with right side prioritized, /// See [`PriorityMax::new`]. pub fn right() -> Self { Self::new(true) } } impl Peaker for PriorityMax { fn peak(&mut self, mins: &[usize], widths: &[usize]) -> Option { if self.side { (0..widths.len()) .filter(|&i| mins.is_empty() || widths[i] > mins[i]) .max_by_key(|&i| widths[i]) .filter(|&col| widths[col] != 0) } else { (0..widths.len()) .rev() .filter(|&i| mins.is_empty() || widths[i] > mins[i]) .max_by_key(|&i| widths[i]) .filter(|&col| widths[col] != 0) } } } tabled-0.18.0/src/settings/peaker/min.rs000064400000000000000000000027421046102023000162010ustar 00000000000000use super::Peaker; /// A Peaker which goes over the smallest column first. #[derive(Debug, Default, Clone, PartialEq, Eq, PartialOrd, Ord, Copy, Hash)] pub struct PriorityMin { side: bool, } impl PriorityMin { /// Creates a [`PriorityMin`] object with a side set to right or left, /// It's crusial in cases where both columns has equal widths and we need to peak a left or right. /// /// Passing true means a right side. /// Passing false means a left side. pub fn new(priorities_right: bool) -> Self { Self { side: priorities_right, } } /// Creates a [`PriorityMin`] object with left side prioritized, /// See [`PriorityMin::new`]. pub fn left() -> Self { Self::new(false) } /// Creates a [`PriorityMin`] object with right side prioritized, /// See [`PriorityMin::new`]. pub fn right() -> Self { Self::new(true) } } impl Peaker for PriorityMin { fn peak(&mut self, mins: &[usize], widths: &[usize]) -> Option { match self.side { true => (0..widths.len()) .filter(|&i| mins.is_empty() || widths[i] > mins[i]) .min_by_key(|&i| widths[i]) .filter(|&col| widths[col] != 0), false => (0..widths.len()) .rev() .filter(|&i| mins.is_empty() || widths[i] > mins[i]) .min_by_key(|&i| widths[i]) .filter(|&col| widths[col] != 0), } } } tabled-0.18.0/src/settings/peaker/mod.rs000064400000000000000000000212011046102023000161640ustar 00000000000000//! The module contains [`Priority`] and [`Peaker`] trait, //! its implementations to be used in [`Height`] and [`Width`]. //! //! [`Width`]: crate::settings::width::Width //! [`Height`]: crate::settings::height::Height mod left; mod max; mod min; mod none; mod right; pub use left::PriorityLeft; pub use max::PriorityMax; pub use min::PriorityMin; pub use none::PriorityNone; pub use right::PriorityRight; /// A strategy of width function. /// It determines the order how a function is applied. /// /// For example which column we shall peak to truncate when doing width alogorithms. pub trait Peaker { /// This function returns an index which will be changed. /// Or `None` if no changes are necessary. /// /// When [`None`] returned the alogorithm must be stopped. fn peak(&mut self, mins: &[usize], values: &[usize]) -> Option; } /// An abstract factory to construct different [`Peaker`] methods. /// /// ``` /// # use tabled::{Table, settings::{Style, peaker::Priority, Width}}; /// # use testing_table::assert_table; /// # /// let data = [ /// ("1", "Hello", 100), /// ("2", "World", 1000), /// ]; /// /// let mut table = Table::new(data); /// table.with(Style::modern()); /// table.with(Width::wrap(15).priority(Priority::max(false))); /// /// let output = table.to_string(); /// /// assert_table!( /// output, /// "┌───┬────┬────┐" /// "│ & │ &s │ i3 │" /// "│ s │ tr │ 2 │" /// "│ t │ │ │" /// "│ r │ │ │" /// "├───┼────┼────┤" /// "│ 1 │ He │ 10 │" /// "│ │ ll │ 0 │" /// "│ │ o │ │" /// "├───┼────┼────┤" /// "│ 2 │ Wo │ 10 │" /// "│ │ rl │ 00 │" /// "│ │ d │ │" /// "└───┴────┴────┘" /// ); /// ``` #[derive(Debug, Clone, Copy, Default, Hash, PartialEq, Eq, PartialOrd, Ord)] pub struct Priority; impl Priority { /// Returns a [`Peaker`] which goes over list one by one, /// in order from left to right with no prioritization, /// just peaking each value after another. /// /// ``` /// # use tabled::{Table, settings::{Style, peaker::Priority, Width}}; /// # use testing_table::assert_table; /// # /// let data = [ /// ("1", "Hello", 100), /// ("2", "World", 1000), /// ]; /// /// let mut table = Table::new(data); /// table.with(Style::modern()); /// table.with(Width::wrap(15).priority(Priority::none())); /// /// let output = table.to_string(); /// /// assert_table!( /// output, /// "┌───┬────┬────┐" /// "│ & │ &s │ i3 │" /// "│ s │ tr │ 2 │" /// "│ t │ │ │" /// "│ r │ │ │" /// "├───┼────┼────┤" /// "│ 1 │ He │ 10 │" /// "│ │ ll │ 0 │" /// "│ │ o │ │" /// "├───┼────┼────┤" /// "│ 2 │ Wo │ 10 │" /// "│ │ rl │ 00 │" /// "│ │ d │ │" /// "└───┴────┴────┘" /// ); /// ``` pub fn none() -> PriorityNone { PriorityNone::new() } /// Returns a [`Peaker`] which goes over list peacking a minimum value, /// and prioritizing a chosen side when equal values are met. /// /// ``` /// # use tabled::{Table, settings::{Style, peaker::Priority, Width}}; /// # use testing_table::assert_table; /// # /// let data = [ /// ("1", "Hello", 100), /// ("2", "World", 1000), /// ]; /// /// let mut table = Table::new(data); /// table.with(Style::modern()); /// table.with(Width::wrap(15).priority(Priority::min(true))); /// /// let output = table.to_string(); /// /// assert_table!( /// output, /// "┌──┬───────┬──┐" /// "│ │ &str │ │" /// "├──┼───────┼──┤" /// "│ │ Hello │ │" /// "├──┼───────┼──┤" /// "│ │ World │ │" /// "└──┴───────┴──┘" /// ); /// ``` pub fn min(right: bool) -> PriorityMin { PriorityMin::new(right) } /// Returns a [`Peaker`] which goes over list peacking a maximum value, /// and prioritizing a chosen side when equal values are met. /// /// ``` /// # use tabled::{Table, settings::{Style, peaker::Priority, Width}}; /// # use testing_table::assert_table; /// # /// let data = [ /// ("1", "Hello", 100), /// ("2", "World", 1000), /// ]; /// /// let mut table = Table::new(data); /// table.with(Style::modern()); /// table.with(Width::wrap(15).priority(Priority::max(true))); /// /// let output = table.to_string(); /// /// assert_table!( /// output, /// "┌────┬────┬───┐" /// "│ &s │ &s │ i │" /// "│ tr │ tr │ 3 │" /// "│ │ │ 2 │" /// "├────┼────┼───┤" /// "│ 1 │ He │ 1 │" /// "│ │ ll │ 0 │" /// "│ │ o │ 0 │" /// "├────┼────┼───┤" /// "│ 2 │ Wo │ 1 │" /// "│ │ rl │ 0 │" /// "│ │ d │ 0 │" /// "│ │ │ 0 │" /// "└────┴────┴───┘" /// ); /// ``` pub fn max(right: bool) -> PriorityMax { PriorityMax::new(right) } /// Returns a [`Peaker`] which goes over list peacking a left most value as far as possible. /// /// ``` /// # use tabled::{Table, settings::{Style, peaker::Priority, Width}}; /// # use testing_table::assert_table; /// # /// let data = [ /// ("1", "Hello", 100), /// ("2", "World", 1000), /// ]; /// /// let mut table = Table::new(data); /// table.with(Style::modern()); /// table.with(Width::wrap(15).priority(Priority::left())); /// /// let output = table.to_string(); /// /// assert_table!( /// output, /// "┌──┬───┬──────┐" /// "│ │ & │ i32 │" /// "│ │ s │ │" /// "│ │ t │ │" /// "│ │ r │ │" /// "├──┼───┼──────┤" /// "│ │ H │ 100 │" /// "│ │ e │ │" /// "│ │ l │ │" /// "│ │ l │ │" /// "│ │ o │ │" /// "├──┼───┼──────┤" /// "│ │ W │ 1000 │" /// "│ │ o │ │" /// "│ │ r │ │" /// "│ │ l │ │" /// "│ │ d │ │" /// "└──┴───┴──────┘" /// ); /// ``` pub fn left() -> PriorityLeft { PriorityLeft::new() } /// Returns a [`Peaker`] which goes over list peacking a right most value as far as possible. /// /// ``` /// # use tabled::{Table, settings::{Style, peaker::Priority, Width}}; /// # use testing_table::assert_table; /// # /// let data = [ /// ("1", "Hello", 100), /// ("2", "World", 1000), /// ]; /// /// let mut table = Table::new(data); /// table.with(Style::modern()); /// table.with(Width::wrap(15).priority(Priority::right())); /// /// let output = table.to_string(); /// /// assert_table!( /// output, /// "┌──────┬───┬──┐" /// "│ &str │ & │ │" /// "│ │ s │ │" /// "│ │ t │ │" /// "│ │ r │ │" /// "├──────┼───┼──┤" /// "│ 1 │ H │ │" /// "│ │ e │ │" /// "│ │ l │ │" /// "│ │ l │ │" /// "│ │ o │ │" /// "├──────┼───┼──┤" /// "│ 2 │ W │ │" /// "│ │ o │ │" /// "│ │ r │ │" /// "│ │ l │ │" /// "│ │ d │ │" /// "└──────┴───┴──┘" /// ); /// ``` pub fn right() -> PriorityRight { PriorityRight::new() } } tabled-0.18.0/src/settings/peaker/none.rs000064400000000000000000000015771046102023000163620ustar 00000000000000use super::Peaker; /// A Peaker which goes over column 1 by 1. #[derive(Debug, Default, Clone, PartialEq, Eq, PartialOrd, Ord, Copy, Hash)] pub struct PriorityNone { i: usize, } impl PriorityNone { /// Creates a new priority which does not target anything. pub const fn new() -> Self { Self { i: 0 } } } impl Peaker for PriorityNone { fn peak(&mut self, _: &[usize], widths: &[usize]) -> Option { let mut i = self.i; let mut count_empty = 0; while widths[i] == 0 { i += 1; if i >= widths.len() { i = 0; } count_empty += 1; if count_empty == widths.len() { return None; } } let col = i; i += 1; if i >= widths.len() { i = 0; } self.i = i; Some(col) } } tabled-0.18.0/src/settings/peaker/right.rs000064400000000000000000000020261046102023000165260ustar 00000000000000use super::Peaker; /// A Peaker which goes over column 1 by 1, from right side, but truncates as much as possible right side. #[derive(Debug, Default, Clone, PartialEq, Eq, PartialOrd, Ord, Copy, Hash)] pub struct PriorityRight { i: Option, } impl PriorityRight { /// Creates a new priority which does not target anything. pub const fn new() -> Self { Self { i: None } } } impl Peaker for PriorityRight { fn peak(&mut self, min: &[usize], widths: &[usize]) -> Option { if widths.is_empty() { return None; } if self.i.is_none() { self.i = Some(widths.len() - 1); } let col = self.i.expect("checked"); if widths[col] > min[col] { return Some(col); } if col == 0 { return None; } let mut col = col - 1; while widths[col] == min[col] { if col == 0 { return None; } col -= 1; } Some(col) } } tabled-0.18.0/src/settings/reverse/mod.rs000064400000000000000000000060321046102023000163750ustar 00000000000000use crate::{ grid::records::{ExactRecords, Records, Resizable}, settings::TableOption, }; /// Reverse data on the table. #[derive(Debug, Clone, Copy, Default, Hash, PartialEq, Eq, PartialOrd, Ord)] pub struct Reverse { columns: bool, skip: usize, skip_from_end: usize, } impl Reverse { /// Reverse columns. pub const fn columns(start: usize, end: usize) -> Self { Self::new(true, start, end) } /// Reverse rows. pub const fn rows(start: usize, end: usize) -> Self { Self::new(false, start, end) } const fn new(columns: bool, skip: usize, skip_from_end: usize) -> Self { Self { columns, skip, skip_from_end, } } } impl TableOption for Reverse where R: Resizable + Records + ExactRecords, { fn change(self, records: &mut R, _: &mut C, _: &mut D) { let count_rows = records.count_rows(); let count_columns = records.count_columns(); let skip = self.skip_from_end + self.skip; if self.columns { if count_columns <= skip { return; } let start = self.skip; let end = count_columns - self.skip_from_end; reverse_columns(records, start, end); } else { if count_rows <= skip { return; } let start = self.skip; let end = count_rows - self.skip_from_end; reverse_rows(records, start, end); } } } fn reverse_rows(data: &mut R, start: usize, end: usize) where R: Resizable + ExactRecords, { let count_rows = end - start; if count_rows < 2 { return; } for (i, row) in (start..end / 2).enumerate() { data.swap_row(row, end - i - 1); } } fn reverse_columns(data: &mut R, start: usize, end: usize) where R: Resizable + Records, { let count_columns = end - start; if count_columns < 2 { return; } for (i, col) in (start..end / 2).enumerate() { data.swap_column(col, end - i - 1); } } #[cfg(test)] #[cfg(feature = "std")] mod tests { use crate::grid::records::{vec_records::VecRecords, Records}; use super::{reverse_columns, reverse_rows}; #[test] fn test_reverse_rows() { assert_eq!( rev_rows(vec![vec![0, 1, 2], vec![3, 4, 5], vec![6, 7, 8]]), vec![vec![6, 7, 8], vec![3, 4, 5], vec![0, 1, 2]] ) } #[test] fn test_reverse_columns() { assert_eq!( rev_cols(vec![vec![0, 1, 2], vec![3, 4, 5], vec![6, 7, 8]]), vec![vec![2, 1, 0], vec![5, 4, 3], vec![8, 7, 6]] ) } fn rev_rows(mut data: Vec>) -> Vec> { let end = data.len(); reverse_rows(&mut data, 0, end); data } fn rev_cols(data: Vec>) -> Vec> { let mut records = VecRecords::new(data); let end = records.count_columns(); reverse_columns(&mut records, 0, end); records.into() } } tabled-0.18.0/src/settings/rotate/mod.rs000064400000000000000000000101011046102023000162100ustar 00000000000000//! This module contains a [`Rotate`] primitive which can be used in order to rotate [`Table`]. //! //! It's also possible to transpose the table at the point of construction. //! See [`Builder::index`]. //! //! # Example //! #![cfg_attr(feature = "std", doc = "```")] #![cfg_attr(not(feature = "std"), doc = "```ignore")] //! use tabled::{Table, settings::Rotate}; //! //! let data = [[1, 2, 3], [4, 5, 6]]; //! //! let table = Table::new(data).with(Rotate::Left).to_string(); //! //! assert_eq!( //! table, //! concat!( //! "+---+---+---+\n", //! "| 2 | 3 | 6 |\n", //! "+---+---+---+\n", //! "| 1 | 2 | 5 |\n", //! "+---+---+---+\n", //! "| 0 | 1 | 4 |\n", //! "+---+---+---+", //! ) //! ); //! ``` //! //! [`Table`]: crate::Table //! [`Builder::index`]: crate::builder::Builder::index // use core::cmp::max; use core::cmp::max; use crate::{ grid::records::{ExactRecords, Records, Resizable}, settings::TableOption, }; /// Rotate can be used to rotate a table by 90 degrees. #[derive(Debug)] pub enum Rotate { /// Rotate [`Table`] to the left. /// /// [`Table`]: crate::Table Left, /// Rotate [`Table`] to the right. /// /// [`Table`]: crate::Table Right, /// Rotate [`Table`] to the top. /// /// So the top becomes the bottom. /// /// [`Table`]: crate::Table Top, /// Rotate [`Table`] to the bottom. /// /// So the top becomes the bottom. /// /// [`Table`]: crate::Table Bottom, } impl TableOption for Rotate where R: Records + ExactRecords + Resizable, { fn change(self, records: &mut R, _: &mut C, _: &mut D) { match self { Self::Left => rotate_left(records), Self::Right => rotate_right(records), Self::Bottom | Self::Top => rotate_horizontal(records), } } } fn rotate_horizontal(records: &mut R) where R: Records + ExactRecords + Resizable, { let count_rows = records.count_rows(); let count_cols = records.count_columns(); for row in 0..count_rows / 2 { for col in 0..count_cols { let last_row = count_rows - row - 1; records.swap((last_row, col).into(), (row, col).into()); } } } fn rotate_left(records: &mut R) where R: Records + ExactRecords + Resizable, { let count_rows = records.count_rows(); let count_cols = records.count_columns(); let size = max(count_rows, count_cols); { for _ in count_rows..size { records.push_row(); } for _ in count_cols..size { records.push_column(); } } for col in 0..size { for row in col..size { records.swap((col, row).into(), (row, col).into()); } } for row in 0..count_cols / 2 { records.swap_row(row, count_cols - row - 1); } { for (shift, row) in (count_rows..size).enumerate() { let row = row - shift; records.remove_column(row); } for (shift, col) in (count_cols..size).enumerate() { let col = col - shift; records.remove_row(col); } } } fn rotate_right(records: &mut R) where R: Records + ExactRecords + Resizable, { let count_rows = records.count_rows(); let count_cols = records.count_columns(); let size = max(count_rows, count_cols); { for _ in count_rows..size { records.push_row(); } for _ in count_cols..size { records.push_column(); } } for col in 0..size { for row in col..size { records.swap((col, row).into(), (row, col).into()); } } for col in 0..count_rows / 2 { records.swap_column(col, count_rows - col - 1); } { for (shift, row) in (count_rows..size).enumerate() { let row = row - shift; records.remove_column(row); } for (shift, col) in (count_cols..size).enumerate() { let col = col - shift; records.remove_row(col); } } } tabled-0.18.0/src/settings/settings_list.rs000064400000000000000000000057271046102023000170500ustar 00000000000000use crate::{grid::config::Entity, settings::TableOption}; #[cfg(feature = "std")] use crate::settings::CellOption; /// Settings is a combinator of [`TableOption`]s. #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] pub struct Settings(A, B); impl Default for Settings { fn default() -> Self { Self(EmptySettings, EmptySettings) } } impl Settings<(), ()> { /// Creates an empty list. pub const fn empty() -> Settings { Settings(EmptySettings, EmptySettings) } } impl Settings { /// Creates a new combinator. pub const fn new(settings1: A, settings2: B) -> Settings { Settings(settings1, settings2) } /// Add an option to a combinator. pub const fn with(self, settings: C) -> Settings { Settings(self, settings) } } #[cfg(feature = "std")] impl CellOption for Settings where A: CellOption, B: CellOption, { fn change(self, records: &mut R, cfg: &mut C, entity: Entity) { self.0.change(records, cfg, entity); self.1.change(records, cfg, entity); } fn hint_change(&self) -> Option { match (self.0.hint_change(), self.1.hint_change()) { (None, None) => None, (Some(a), Some(b)) => Some(combine_entity(a, b)), (None, value) => value, (value, None) => value, } } } impl TableOption for Settings where A: TableOption, B: TableOption, { fn change(self, records: &mut R, cfg: &mut C, dims: &mut D) { self.0.change(records, cfg, dims); self.1.change(records, cfg, dims); } fn hint_change(&self) -> Option { match (self.0.hint_change(), self.1.hint_change()) { (None, None) => None, (Some(a), Some(b)) => Some(combine_entity(a, b)), (None, value) => value, (value, None) => value, } } } /// A marker structure to be able to create an empty [`Settings`]. #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] pub struct EmptySettings; #[cfg(feature = "std")] impl CellOption for EmptySettings { fn change(self, _: &mut R, _: &mut C, _: Entity) {} } impl TableOption for EmptySettings { fn change(self, _: &mut R, _: &mut C, _: &mut D) {} } pub(crate) fn combine_entity(x1: Entity, x2: Entity) -> Entity { use Entity::*; match (x1, x2) { (Column(a), Column(b)) if a == b => Column(a), (Column(a), Cell(_, b)) if a == b => Column(a), (Row(a), Row(b)) if a == b => Row(a), (Row(a), Cell(b, _)) if a == b => Row(a), (Cell(_, a), Column(b)) if a == b => Column(a), (Cell(a, _), Row(b)) if a == b => Row(a), (Cell(a, b), Cell(a1, b1)) if a == a1 && b == b1 => Cell(a, b), _ => Global, } } tabled-0.18.0/src/settings/shadow/mod.rs000064400000000000000000000115271046102023000162140ustar 00000000000000//! This module contains a [`Shadow`] option for a [`Table`]. //! //! # Example //! //! ``` //! use tabled::{Table, settings::{Shadow, Style}}; //! //! let data = vec!["Hello", "World", "!"]; //! //! let table = Table::new(data) //! .with(Style::markdown()) //! .with(Shadow::new(1)) //! .to_string(); //! //! assert_eq!( //! table, //! concat!( //! "| &str | \n", //! "|-------|▒\n", //! "| Hello |▒\n", //! "| World |▒\n", //! "| ! |▒\n", //! " ▒▒▒▒▒▒▒▒▒", //! ) //! ); //! ``` //! //! [`Table`]: crate::Table use crate::{ grid::ansi::ANSIBuf, grid::config::{ColoredConfig, Indent, Offset, Sides}, settings::{color::Color, TableOption}, }; /// The structure represents a shadow of a table. /// /// NOTICE: It uses [`Margin`] therefore it often can't be combined. /// /// [`Margin`]: crate::settings::Margin #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] pub struct Shadow { c: char, size: usize, size_offset: usize, direction: Sides, color: Option, } impl Shadow { /// A default fill character to be used. pub const DEFAULT_FILL: char = '▒'; /// Construct's an [`Shadow`] object with default fill [`Shadow::DEFAULT_FILL`]. /// /// It uses space(' ') as a default fill character. /// To set a custom character you can use [`Self::set_fill`] function. pub fn new(size: usize) -> Self { Self { c: Self::DEFAULT_FILL, size, size_offset: 1, direction: Sides::new(false, true, false, true), color: None, } } /// The function, sets a characters for the [`Shadow`] to be used. pub fn set_fill(mut self, c: char) -> Self { self.c = c; self } /// Set an offset value (default is '1'). pub fn set_offset(mut self, size: usize) -> Self { self.size_offset = size; self } /// Switch shadow to top. pub fn set_top(mut self) -> Self { self.direction.top = true; self.direction.bottom = false; self } /// Switch shadow to bottom. pub fn set_bottom(mut self) -> Self { self.direction.bottom = true; self.direction.top = false; self } /// Switch shadow to left. pub fn set_left(mut self) -> Self { self.direction.left = true; self.direction.right = false; self } /// Switch shadow to right. pub fn set_right(mut self) -> Self { self.direction.right = true; self.direction.left = false; self } /// Sets a color for a shadow. pub fn set_color(mut self, color: Color) -> Self { self.color = Some(color); self } } impl TableOption for Shadow { fn change(self, _: &mut R, cfg: &mut ColoredConfig, _: &mut D) { set_margin(cfg, self.size, self.c, &self.direction); set_margin_offset(cfg, self.size_offset, &self.direction); if let Some(color) = &self.color { set_margin_color(cfg, color.clone().into(), &self.direction); } } } fn set_margin(cfg: &mut ColoredConfig, size: usize, c: char, direction: &Sides) { let mut margin: Sides = Sides::default(); if direction.top { margin.top.size = size; margin.top.fill = c; } if direction.bottom { margin.bottom.size = size; margin.bottom.fill = c; } if direction.left { margin.left.size = size; margin.left.fill = c; } if direction.right { margin.right.size = size; margin.right.fill = c; } cfg.set_margin(margin); } fn set_margin_offset(cfg: &mut ColoredConfig, size: usize, direction: &Sides) { let mut margin = Sides::filled(Offset::Begin(0)); if direction.right && direction.bottom { margin.bottom = Offset::Begin(size); margin.right = Offset::Begin(size); } if direction.right && direction.top { margin.top = Offset::Begin(size); margin.right = Offset::End(size); } if direction.left && direction.bottom { margin.bottom = Offset::End(size); margin.left = Offset::Begin(size); } if direction.left && direction.top { margin.top = Offset::End(size); margin.left = Offset::End(size); } cfg.set_margin_offset(margin); } fn set_margin_color(cfg: &mut ColoredConfig, color: ANSIBuf, direction: &Sides) { let mut margin: Sides> = Sides::default(); if direction.right { margin.right = Some(color.clone()); } if direction.top { margin.top = Some(color.clone()); } if direction.left { margin.left = Some(color.clone()); } if direction.bottom { margin.bottom = Some(color.clone()); } cfg.set_margin_color(margin); } tabled-0.18.0/src/settings/span/column.rs000064400000000000000000000105631046102023000164050ustar 00000000000000use std::cmp::{self, Ordering}; use crate::{ grid::{ config::{ColoredConfig, Entity, Position, SpannedConfig}, records::{ExactRecords, PeekableRecords, Records, RecordsMut}, }, settings::CellOption, }; /// Columns (horizontal) span. #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] pub struct ColumnSpan { size: isize, } impl ColumnSpan { /// Creates a new column (horizontal) span. pub fn new(size: isize) -> Self { Self { size } } /// Creates a new column (horizontal) span with a maximux value possible. pub fn max() -> Self { Self::new(isize::MAX) } /// Creates a new column (horizontal) span with a min value possible. pub fn min() -> Self { Self::new(isize::MIN) } /// Creates a new column (horizontal) to spread all the columns. pub fn spread() -> Self { Self::new(0) } } impl CellOption for ColumnSpan where R: Records + ExactRecords + PeekableRecords + RecordsMut, { fn change(self, records: &mut R, cfg: &mut ColoredConfig, entity: Entity) { let count_rows = records.count_rows(); let count_cols = records.count_columns(); let shape = (count_rows, count_cols).into(); for pos in entity.iter(count_rows, count_cols) { set_col_span(records, cfg, self.size, pos, shape); } remove_false_spans(cfg); } } fn set_col_span( recs: &mut R, cfg: &mut SpannedConfig, span: isize, pos: Position, shape: Position, ) where R: Records + ExactRecords + PeekableRecords + RecordsMut, { if !pos.is_covered(shape) { return; } match span.cmp(&0) { Ordering::Less => { // * got correct value [span, col] // * clean the route from col to pos.col() // * set the (pos.row(), col) to content from pos // * set span let span = span.unsigned_abs(); let (col, span) = if span > pos.col() { (0, pos.col()) } else { (pos.col() - span, span) }; let content = recs.get_text(pos).to_string(); for i in col..span + 1 { recs.set(Position::new(pos.row(), i), String::new()); } recs.set(Position::new(pos.row(), col), content); cfg.set_column_span(Position::new(pos.row(), col), span + 1); } Ordering::Equal => { let content = recs.get_text(pos).to_string(); let span = recs.count_columns(); for i in 0..recs.count_columns() { recs.set(Position::new(pos.row(), i), String::new()); } recs.set(Position::new(pos.row(), 0), content); cfg.set_column_span(Position::new(pos.row(), 0), span); } Ordering::Greater => { let span = cmp::min(span as usize, shape.col() - pos.col()); if span_has_intersections(cfg, pos, span) { return; } set_span_column(cfg, pos, span); } } } fn set_span_column(cfg: &mut SpannedConfig, p: Position, span: usize) { if span == 0 { if p.col() == 0 { return; } if let Some(nearcol) = closest_visible(cfg, p - (0, 1)) { let span = p.col() + 1 - nearcol; cfg.set_column_span((p.row(), nearcol).into(), span); } } cfg.set_column_span(p, span); } fn closest_visible(cfg: &SpannedConfig, mut pos: Position) -> Option { loop { if cfg.is_cell_visible(pos) { return Some(pos.col()); } if pos.col() == 0 { return None; } pos -= (0, 1); } } fn span_has_intersections(cfg: &SpannedConfig, p: Position, span: usize) -> bool { for col in p.col()..p.col() + span { if !cfg.is_cell_visible((p.row(), col).into()) { return true; } } false } fn remove_false_spans(cfg: &mut SpannedConfig) { for (pos, _) in cfg.get_column_spans() { if cfg.is_cell_visible(pos) { continue; } cfg.set_row_span(pos, 1); cfg.set_column_span(pos, 1); } for (pos, _) in cfg.get_row_spans() { if cfg.is_cell_visible(pos) { continue; } cfg.set_row_span(pos, 1); cfg.set_column_span(pos, 1); } } tabled-0.18.0/src/settings/span/mod.rs000064400000000000000000000066031046102023000156670ustar 00000000000000//! This module contains a [`Span`] settings, it helps to //! make a cell take more space then it generally takes. //! //! # Example //! //! ```rust,no_run //! # use tabled::{Table, settings::{Style, Span, Modify, object::Columns}}; //! # let data: Vec<&'static str> = Vec::new(); //! let table = Table::new(&data) //! .with(Modify::new(Columns::single(0)).with(Span::column(2))); //! ``` mod column; mod row; pub use column::ColumnSpan; pub use row::RowSpan; /// Span represent a horizontal/column span setting for any cell on a [`Table`]. /// /// ``` /// use tabled::{settings::{Span, Modify}, Table}; /// /// let data = [[1, 2, 3], [4, 5, 6]]; /// /// let table = Table::new(data) /// .modify((0, 0), Span::row(2)) /// .modify((0, 1), Span::column(2)) /// .modify((2, 0), Span::column(1000)) /// .to_string(); /// /// assert_eq!( /// table, /// concat!( /// "+---+---+---+\n", /// "| 0 | 1 |\n", /// "+ +---+---+\n", /// "| | 2 | 3 |\n", /// "+---+---+---+\n", /// "| 4 |\n", /// "+---+---+---+", /// ) /// ) /// ``` /// /// [`Table`]: crate::Table #[derive(Debug)] pub struct Span; impl Span { /// New constructs a horizontal/column [`Span`]. /// /// Value can be: /// * == 0 - which means spread the cell on the whole line /// * == 1 - which is a default span so can be used for removal of spans /// * > 1 - which means to spread a cell by given number of columns right /// * < 0 - which means to spread a cell by given number of columns left /// /// # Example /// /// ``` /// use tabled::{settings::{Span, Modify}, Table}; /// /// let data = [[1, 2, 3], [4, 5, 6]]; /// /// let table = Table::new(data) /// .modify((0, 0), Span::column(100)) /// .modify((1, 1), Span::column(2)) /// .modify((2, 1), Span::column(-1)) /// .to_string(); /// /// assert_eq!( /// table, /// concat!( /// "+---++---+\n", /// "| 0 |\n", /// "+---++---+\n", /// "| 1 | 2 |\n", /// "+---++---+\n", /// "| 5 | 6 |\n", /// "+---++---+", /// ) /// ) /// ``` pub fn column(size: isize) -> ColumnSpan { ColumnSpan::new(size) } /// New constructs a vertical/row [`Span`]. /// /// Value can be: /// * == 0 - which means spread the cell on the whole line /// * == 1 - which is a default span so can be used for removal of spans /// * > 1 - which means to spread a cell by given number of rows bottom /// * < 0 - which means to spread a cell by given number of rows top /// /// # Example /// /// ``` /// use tabled::{settings::{Span, Modify}, Table}; /// /// let data = [[1, 2, 3], [4, 5, 6]]; /// /// let table = Table::new(data) /// .modify((0, 0), Span::row(100)) /// .modify((1, 1), Span::row(2)) /// .modify((2, 2), Span::row(-1)) /// .to_string(); /// /// assert_eq!( /// table, /// concat!( /// "+---+---+---+\n", /// "| 0 | 1 | 2 |\n", /// "+ +---+---+\n", /// "+ + 2 + 6 +\n", /// "+---+---+---+", /// ) /// ) /// ``` pub fn row(size: isize) -> RowSpan { RowSpan::new(size) } } tabled-0.18.0/src/settings/span/row.rs000064400000000000000000000105461046102023000157200ustar 00000000000000use std::cmp::{self, Ordering}; use crate::{ grid::{ config::{ColoredConfig, Entity, Position, SpannedConfig}, records::{ExactRecords, PeekableRecords, Records, RecordsMut}, }, settings::CellOption, }; /// Row (vertical) span. #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] pub struct RowSpan { size: isize, } impl RowSpan { /// Creates a new row (vertical) span. pub const fn new(size: isize) -> Self { Self { size } } /// Creates a new row (vertical) span with a maximux value possible. pub const fn max() -> Self { Self::new(isize::MAX) } /// Creates a new row (vertical) span with a min value possible. pub fn min() -> Self { Self::new(isize::MIN) } /// Creates a new row (vertical) to spread on all rows. pub fn spread() -> Self { Self::new(0) } } impl CellOption for RowSpan where R: Records + ExactRecords + PeekableRecords + RecordsMut, { fn change(self, records: &mut R, cfg: &mut ColoredConfig, entity: Entity) { let count_rows = records.count_rows(); let count_cols = records.count_columns(); let shape = (count_rows, count_cols).into(); for pos in entity.iter(count_rows, count_cols) { set_span(records, cfg, self.size, pos, shape); } remove_false_spans(cfg); } } fn set_span(recs: &mut R, cfg: &mut SpannedConfig, span: isize, pos: Position, shape: Position) where R: Records + ExactRecords + PeekableRecords + RecordsMut, { if !pos.is_covered(shape) { return; } match span.cmp(&0) { Ordering::Less => { // * got correct value [span, row] // * clean the route from row to pos.row() // * set the (row, pos.col()) to content from pos // * set span let span = span.unsigned_abs(); let (row, span) = if span > pos.row() { (0, pos.row()) } else { (pos.row() - span, span) }; let content = recs.get_text(pos).to_string(); for i in row..span + 1 { recs.set(Position::new(i, pos.col()), String::new()); } recs.set(Position::new(row, pos.col()), content); cfg.set_row_span(Position::new(row, pos.col()), span + 1); } Ordering::Equal => { let content = recs.get_text(pos).to_string(); let span = recs.count_rows(); for i in 0..recs.count_rows() { recs.set(Position::new(i, pos.col()), String::new()); } recs.set(Position::new(0, pos.col()), content); cfg.set_row_span(Position::new(0, pos.col()), span); } Ordering::Greater => { let span = cmp::min(span as usize, shape.row() - pos.row()); if span_has_intersections(cfg, pos, span) { return; } set_span_row(cfg, pos, span); } } } fn set_span_row(cfg: &mut SpannedConfig, p: Position, span: usize) { if span == 0 { if p.col() == 0 { return; } if let Some(nearcol) = closest_visible(cfg, p - (0, 1)) { let span = p.col() + 1 - nearcol; cfg.set_row_span((p.row(), nearcol).into(), span); } } cfg.set_row_span(p, span); } fn closest_visible(cfg: &SpannedConfig, mut pos: Position) -> Option { loop { if cfg.is_cell_visible(pos) { return Some(pos.row()); } if pos.row() == 0 { // can happen if we have a above horizontal spanned cell return None; } pos -= (1, 0); } } fn span_has_intersections(cfg: &SpannedConfig, p: Position, span: usize) -> bool { for row in p.row()..p.row() + span { if !cfg.is_cell_visible((row, p.col()).into()) { return true; } } false } fn remove_false_spans(cfg: &mut SpannedConfig) { for (pos, _) in cfg.get_column_spans() { if cfg.is_cell_visible(pos) { continue; } cfg.set_row_span(pos, 1); cfg.set_column_span(pos, 1); } for (pos, _) in cfg.get_row_spans() { if cfg.is_cell_visible(pos) { continue; } cfg.set_row_span(pos, 1); cfg.set_column_span(pos, 1); } } tabled-0.18.0/src/settings/split/mod.rs000064400000000000000000000557561046102023000160760ustar 00000000000000//! This module contains a [`Split`] setting which is used to //! format the cells of a [`Table`] by a provided index, direction, behavior, and display preference. //! //! [`Table`]: crate::Table use core::ops::Range; use crate::grid::{ config::Position, records::{ExactRecords, PeekableRecords, Records, Resizable}, }; use super::TableOption; #[derive(Debug, Clone, Copy)] enum Direction { Column, Row, } #[derive(Debug, Clone, Copy)] enum Behavior { Concat, Zip, } #[derive(Debug, Clone, Copy, PartialEq)] enum Display { Clean, Retain, } /// Returns a new [`Table`] formatted with several optional parameters. /// /// The required index parameter determines how many columns/rows a table will be redistributed into. /// /// - index /// - direction /// - behavior /// - display /// /// #### Directions /// /// Direction functions are the entry point for the `Split` setting. /// /// There are two directions available: `column` and `row`. /// /// ```rust /// use std::iter::FromIterator; /// use tabled::{Table, settings::split::Split}; /// /// let mut table = Table::from_iter(['a'..='z']); /// table.with(Split::column(12)); /// table.with(Split::row(2)); /// ``` /// /// ```text /// ┌───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┐ /// │ a │ b │ c │ d │ e │ f │ g │ h │ i │ j │ k │ l │ m │ n │ o │ p │ q │ r │ s │ t │ u │ v │ w │ x │ y │ z │ /// └───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┘ /// ┌───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┐ /// │ a │ b │ c │ d │ e │ f │ g │ h │ i │ j │ k │ l │ /// ├───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┤ /// │ m │ n │ o │ p │ q │ r │ s │ t │ u │ v │ w │ x │ /// ├───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┤ /// │ y │ z │ │ │ │ │ │ │ │ │ │ │<- y and z act as anchors to new empty cells /// └───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┘ to conform to the new shape /// ┌───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┐ /// │ a │ y │ b │ z │ c │ d │ e │ f │ g │ h │ i │ j │ k │ l │ /// ├───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┤<- Display::Clean removes empty cells that would be anchors otherwise /// │ m │ │ n │ │ o │ p │ q │ r │ s │ t │ u │ v │ w │ x │ /// └───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┘ /// ^anchors^ /// ``` /// /// /// #### Behaviors /// /// Behaviors determine how cells attempt to conform to the new tables shape. /// /// There are two behaviors available: `zip` and `concat`. /// /// `zip` is the default behavior. /// /// ```rust /// use std::iter::FromIterator; /// use tabled::{Table, settings::split::Split}; /// /// let mut table = Table::from_iter(['a'..='z']); /// table.with(Split::column(2).concat()); /// table.with(Split::column(2).zip()); /// ``` /// /// ```text /// +---+---+ /// | a | b | /// +---+---+ /// +---+---+---+---+---+ | f | g | /// | a | b | c | d | e | Split::column(2).concat() +---+---+ /// +---+---+---+---+---+ => | c | d | /// | f | g | h | i | j | +---+---+ /// +---+---+---+---+---+ | h | i | /// +---+---+ /// | e | | /// +---+---+ /// | j | | /// +---+---+ /// /// sect 3 +---+---+ /// sect 1 sect 2 (anchors) | a | b | /// / \ / \ / \ +---+---+ /// +---+---+---+---+---+ | c | d | /// | a | b | c | d | e | Split::column(2).zip() +---+---+ /// +---+---+---+---+---+ => | e | | /// | f | g | h | i | j | +---+---+ /// +---+---+---+---+---+ | f | g | /// +---+---+ /// | h | i | /// +---+---+ /// | j | | /// +---+---+ /// ``` /// /// #### Displays /// /// Display functions give the user the choice to `retain` or `clean` empty sections in a `Split` table result. /// /// - `retain` does not filter any existing or newly added cells when conforming to a new shape. /// /// - `clean` filters out empty columns/rows from the output and prevents empty cells from acting as anchors to newly inserted cells. /// /// `clean` is the default `Display`. /// /// ```rust /// use std::iter::FromIterator; /// use tabled::{ /// settings::{split::Split, style::Style}, /// Table, /// }; /// let mut table = Table::from_iter(['a'..='z']); /// table.with(Split::column(25)).with(Style::modern()); /// table.clone().with(Split::column(1).concat().retain()); /// table.clone().with(Split::column(1).concat()); // .clean() is not necessary as it is the default display property /// ``` /// /// ```text /// ┌───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┐ /// │ a │ b │ c │ d │ e │ f │ g │ h │ i │ j │ k │ l │ m │ n │ o │ p │ q │ r │ s │ t │ u │ v │ w │ x │ y │ /// ├───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┤ /// │ z │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │<- lots of extra cells generated /// └───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┘ /// ┌───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┐ /// │ a │ b │ c │ d │ e │ f │ g │ h │ i │ j │ k │ l │ m │ n │ o │ p │ q │ r │ s │ t │ u │ v │ w │ x │ y │ z │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ /// └───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┘ /// ┌───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┐ ^ cells retained during concatenation /// │ a │ b │ c │ d │ e │ f │ g │ h │ i │ j │ k │ l │ m │ n │ o │ p │ q │ r │ s │ t │ u │ v │ w │ x │ y │ z │ /// └───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┘<- cells removed during concatenation /// ``` /// /// /// # Example /// /// ```rust /// use std::iter::FromIterator; /// use tabled::{ /// settings::split::Split, /// Table, /// }; /// /// let mut table = Table::from_iter(['a'..='z']); /// let table = table.with(Split::column(4)).to_string(); /// /// assert_eq!(table, "+---+---+---+---+\n\ /// | a | b | c | d |\n\ /// +---+---+---+---+\n\ /// | e | f | g | h |\n\ /// +---+---+---+---+\n\ /// | i | j | k | l |\n\ /// +---+---+---+---+\n\ /// | m | n | o | p |\n\ /// +---+---+---+---+\n\ /// | q | r | s | t |\n\ /// +---+---+---+---+\n\ /// | u | v | w | x |\n\ /// +---+---+---+---+\n\ /// | y | z | | |\n\ /// +---+---+---+---+") /// ``` /// /// [`Table`]: crate::Table #[derive(Debug, Clone, Copy)] pub struct Split { direction: Direction, behavior: Behavior, display: Display, index: usize, } impl Split { /// Returns a new [`Table`] split on the column at the provided index. /// /// The column found at that index becomes the new right-most column in the returned table. /// Columns found beyond the index are redistributed into the table based on other defined /// parameters. /// /// ```rust,no_run /// # use tabled::settings::split::Split; /// Split::column(4); /// ``` /// /// [`Table`]: crate::Table pub fn column(index: usize) -> Self { Split { direction: Direction::Column, behavior: Behavior::Zip, display: Display::Clean, index, } } /// Returns a new [`Table`] split on the row at the provided index. /// /// The row found at that index becomes the new bottom row in the returned table. /// Rows found beyond the index are redistributed into the table based on other defined /// parameters. /// /// ```rust,no_run /// # use tabled::settings::split::Split; /// Split::row(4); /// ``` /// /// [`Table`]: crate::Table pub fn row(index: usize) -> Self { Split { direction: Direction::Row, behavior: Behavior::Zip, display: Display::Clean, index, } } /// Returns a split [`Table`] with the redistributed cells pushed to the back of the new shape. /// /// ```text /// +---+---+ /// | a | b | /// +---+---+ /// +---+---+---+---+---+ | f | g | /// | a | b | c | d | e | Split::column(2).concat() +---+---+ /// +---+---+---+---+---+ => | c | d | /// | f | g | h | i | j | +---+---+ /// +---+---+---+---+---+ | h | i | /// +---+---+ /// | e | | /// +---+---+ /// | j | | /// +---+---+ /// ``` /// /// [`Table`]: crate::Table pub fn concat(self) -> Self { Self { behavior: Behavior::Concat, ..self } } /// Returns a split [`Table`] with the redistributed cells inserted behind /// the first correlating column/row one after another. /// /// ```text /// +---+---+ /// | a | b | /// +---+---+ /// +---+---+---+---+---+ | c | d | /// | a | b | c | d | e | Split::column(2).zip() +---+---+ /// +---+---+---+---+---+ => | e | | /// | f | g | h | i | j | +---+---+ /// +---+---+---+---+---+ | f | g | /// +---+---+ /// | h | i | /// +---+---+ /// | j | | /// +---+---+ /// ``` /// /// [`Table`]: crate::Table pub fn zip(self) -> Self { Self { behavior: Behavior::Zip, ..self } } /// Returns a split [`Table`] with the empty columns/rows filtered out. /// /// ```text /// /// /// +---+---+---+ /// +---+---+---+---+---+ | a | b | c | /// | a | b | c | d | e | Split::column(3).clean() +---+---+---+ /// +---+---+---+---+---+ => | d | e | | /// | f | g | h | | | +---+---+---+ /// +---+---+---+---+---+ | f | g | h | /// ^ ^ +---+---+---+ /// these cells are filtered /// from the resulting table /// ``` /// /// ## Notes /// /// This is apart of the default configuration for Split. /// /// See [`retain`] for an alternative display option. /// /// [`Table`]: crate::Table /// [`retain`]: crate::settings::split::Split::retain pub fn clean(self) -> Self { Self { display: Display::Clean, ..self } } /// Returns a split [`Table`] with all cells retained. /// /// ```text /// +---+---+---+ /// | a | b | c | /// +---+---+---+ /// +---+---+---+---+---+ | d | e | | /// | a | b | c | d | e | Split::column(3).retain() +---+---+---+ /// +---+---+---+---+---+ => | f | g | h | /// | f | g | h | | | +---+---+---+ /// +---+---+---+---+---+ |-----------> | | | | /// ^ ^ | +---+---+---+ /// |___|_____cells are kept! /// ``` /// /// ## Notes /// /// See [`clean`] for an alternative display option. /// /// [`Table`]: crate::Table /// [`clean`]: crate::settings::split::Split::clean pub fn retain(self) -> Self { Self { display: Display::Retain, ..self } } } impl TableOption for Split where R: Records + ExactRecords + Resizable + PeekableRecords, { fn change(self, records: &mut R, _: &mut C, _: &mut D) { // variables let Split { direction, behavior, display, index: section_length, } = self; let mut filtered_sections = 0; // early return check if records.count_columns() == 0 || records.count_rows() == 0 || section_length == 0 { return; } // computed variables let (primary_length, secondary_length) = compute_length_arrangement(records, direction); let sections_per_direction = ceil_div(primary_length, section_length); let (outer_range, inner_range) = compute_range_order(secondary_length, sections_per_direction, behavior); // work for outer_index in outer_range { let from_secondary_index = outer_index * sections_per_direction - filtered_sections; for inner_index in inner_range.clone() { let (section_index, from_secondary_index, to_secondary_index) = compute_range_variables( outer_index, inner_index, secondary_length, from_secondary_index, sections_per_direction, filtered_sections, behavior, ); match (direction, behavior) { (Direction::Column, Behavior::Concat) => records.push_row(), (Direction::Column, Behavior::Zip) => records.insert_row(to_secondary_index), (Direction::Row, Behavior::Concat) => records.push_column(), (Direction::Row, Behavior::Zip) => records.insert_column(to_secondary_index), } let section_is_empty = copy_section( records, section_length, section_index, primary_length, from_secondary_index, to_secondary_index, direction, ); if section_is_empty && display == Display::Clean { delete(records, to_secondary_index, direction); filtered_sections += 1; } } } cleanup(records, section_length, primary_length, direction); } } /// Determine which direction should be considered the primary, and which the secondary based on direction fn compute_length_arrangement(records: &mut R, direction: Direction) -> (usize, usize) where R: Records + ExactRecords, { match direction { Direction::Column => (records.count_columns(), records.count_rows()), Direction::Row => (records.count_rows(), records.count_columns()), } } /// reduce the table size to the length of the index in the specified direction fn cleanup(records: &mut R, section_length: usize, primary_length: usize, direction: Direction) where R: Resizable, { for segment in (section_length..primary_length).rev() { match direction { Direction::Column => records.remove_column(segment), Direction::Row => records.remove_row(segment), } } } /// Delete target index row or column fn delete(records: &mut R, target_index: usize, direction: Direction) where R: Resizable, { match direction { Direction::Column => records.remove_row(target_index), Direction::Row => records.remove_column(target_index), } } /// copy cells to new location /// /// returns if the copied section was entirely blank fn copy_section( records: &mut R, section_length: usize, section_index: usize, primary_length: usize, from_secondary_index: usize, to_secondary_index: usize, direction: Direction, ) -> bool where R: ExactRecords + Resizable + PeekableRecords, { let mut section_is_empty = true; for to_primary_index in 0..section_length { let from_primary_index = to_primary_index + section_index * section_length; if from_primary_index < primary_length { let from_position = format_position(direction, from_primary_index, from_secondary_index); if !records.get_text(from_position).is_empty() { section_is_empty = false; } records.swap( from_position, format_position(direction, to_primary_index, to_secondary_index), ); } } section_is_empty } /// determine section over direction or vice versa based on behavior fn compute_range_order( direction_length: usize, sections_per_direction: usize, behavior: Behavior, ) -> (Range, Range) { match behavior { Behavior::Concat => (1..sections_per_direction, 0..direction_length), Behavior::Zip => (0..direction_length, 1..sections_per_direction), } } /// helper function for shimming both behaviors to work within a single nested loop fn compute_range_variables( outer_index: usize, inner_index: usize, direction_length: usize, from_secondary_index: usize, sections_per_direction: usize, filtered_sections: usize, behavior: Behavior, ) -> (usize, usize, usize) { match behavior { Behavior::Concat => ( outer_index, inner_index, inner_index + outer_index * direction_length - filtered_sections, ), Behavior::Zip => ( inner_index, from_secondary_index, outer_index * sections_per_direction + inner_index - filtered_sections, ), } } /// utility for arguments of a position easily fn format_position(direction: Direction, primary_index: usize, secondary_index: usize) -> Position { match direction { Direction::Column => (secondary_index, primary_index).into(), Direction::Row => (primary_index, secondary_index).into(), } } /// ceil division utility because the std lib ceil_div isn't stable yet fn ceil_div(x: usize, y: usize) -> usize { debug_assert!(x != 0); 1 + ((x - 1) / y) } tabled-0.18.0/src/settings/style/border.rs000064400000000000000000000244561046102023000165720ustar 00000000000000use core::marker::PhantomData; use crate::{ grid::config::Border as GridBorder, settings::style::{On, Style}, }; #[cfg(feature = "std")] use crate::{ grid::config::{ColoredConfig, Entity}, grid::records::{ExactRecords, Records}, settings::{CellOption, TableOption}, }; /// Border represents a border of a Cell. /// /// ```text /// top border /// | /// V /// corner top left ------> +_______+ <---- corner top left /// | | /// left border ----------> | cell | <---- right border /// | | /// corner bottom right --> +_______+ <---- corner bottom right /// ^ /// | /// bottom border /// ``` /// /// ```rust,no_run /// # use tabled::{Table, settings::{style::{Style, Border}, object::Rows}}; /// # let data: Vec<&'static str> = Vec::new(); /// let table = Table::new(&data) /// .with(Style::ascii()) /// .modify(Rows::single(0), Border::new().top('x')); /// ``` #[derive(Debug, Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] pub struct Border { inner: GridBorder, _top: PhantomData, _bottom: PhantomData, _left: PhantomData, _right: PhantomData, } impl Border { pub(crate) const fn from_border(inner: GridBorder) -> Border { Border { inner, _top: PhantomData, _bottom: PhantomData, _left: PhantomData, _right: PhantomData, } } } impl Border<(), (), (), ()> { /// Creates an empty border. pub const fn new() -> Self { Self::from_border(GridBorder::empty()) } } impl Border { /// This function constructs a cell borders with all sides set. #[allow(clippy::too_many_arguments)] pub const fn full( top: char, bottom: char, left: char, right: char, top_left: char, top_right: char, bottom_left: char, bottom_right: char, ) -> Self { Border::from_border(GridBorder::full( top, bottom, left, right, top_left, top_right, bottom_left, bottom_right, )) } /// 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 const fn filled(c: char) -> Self { Self::full(c, c, c, c, c, c, c, c) } /// Using this function you deconstruct the existing borders. pub const fn empty() -> EmptyBorder { EmptyBorder } } impl Border { /// Fetches outer border from a style. pub const fn inherit( style: Style, ) -> Self { let borders = style.get_borders(); let line = GridBorder::new( borders.top, borders.bottom, borders.left, borders.right, borders.top_left, borders.bottom_left, borders.top_right, borders.bottom_right, ); Self::from_border(line) } } impl Border { /// Set a top border character. pub const fn top(mut self, c: char) -> Border { self.inner.top = Some(c); Border::from_border(self.inner) } /// Set a bottom border character. pub const fn bottom(mut self, c: char) -> Border { self.inner.bottom = Some(c); Border::from_border(self.inner) } /// Set a left border character. pub const fn left(mut self, c: char) -> Border { self.inner.left = Some(c); Border::from_border(self.inner) } /// Set a right border character. pub const fn right(mut self, c: char) -> Border { self.inner.right = Some(c); Border::from_border(self.inner) } /// Converts a border into a general data structure. pub const fn into_inner(self) -> GridBorder { self.inner } } impl Border { /// Get a right character. pub const fn get_right(&self) -> char { get_char(self.inner.right) } } impl Border { /// Get a left character. pub const fn get_left(&self) -> char { get_char(self.inner.left) } } impl Border { /// Get a top character. pub const fn get_top(&self) -> char { get_char(self.inner.top) } } impl Border { /// Get a bottom character. pub const fn get_bottom(&self) -> char { get_char(self.inner.bottom) } } impl Border { /// Set a top left intersection character. pub const fn corner_top_left(mut self, c: char) -> Self { self.inner.left_top_corner = Some(c); self } /// Get a top left intersection character. pub const fn get_corner_top_left(&self) -> char { get_char(self.inner.left_top_corner) } } impl Border { /// Set a top right intersection character. pub const fn corner_top_right(mut self, c: char) -> Self { self.inner.right_top_corner = Some(c); self } /// Get a top right intersection character. pub const fn get_corner_top_right(&self) -> char { get_char(self.inner.right_top_corner) } } impl Border { /// Set a bottom left intersection character. pub const fn corner_bottom_left(mut self, c: char) -> Self { self.inner.left_bottom_corner = Some(c); self } /// Get a bottom left intersection character. pub const fn get_corner_bottom_left(&self) -> char { get_char(self.inner.left_bottom_corner) } } impl Border { /// Set a bottom right intersection character. pub const fn corner_bottom_right(mut self, c: char) -> Self { self.inner.right_bottom_corner = Some(c); self } /// Get a bottom left intersection character. pub const fn get_corner_bottom_right(&self) -> char { get_char(self.inner.right_bottom_corner) } } impl From> for GridBorder { fn from(value: Border) -> Self { value.inner } } #[cfg(feature = "std")] impl CellOption for Border where Data: Records + ExactRecords, { fn change(self, records: &mut Data, cfg: &mut ColoredConfig, entity: Entity) { CellOption::change(self.inner, records, cfg, entity) } } #[cfg(feature = "std")] impl TableOption for Border where Data: Records + ExactRecords, { fn change(self, records: &mut Data, cfg: &mut ColoredConfig, dims: &mut D) { let border = self.into_inner(); TableOption::change(border, records, cfg, dims); } } #[cfg(feature = "std")] impl CellOption for GridBorder where R: Records + ExactRecords, { fn change(self, records: &mut R, cfg: &mut ColoredConfig, entity: Entity) { let shape = (records.count_rows(), records.count_columns()); for pos in entity.iter(shape.0, shape.1) { cfg.set_border(pos, self); } } } #[cfg(feature = "std")] impl TableOption for GridBorder where R: Records + ExactRecords, { fn change(self, records: &mut R, cfg: &mut ColoredConfig, _: &mut D) { let count_rows = records.count_rows(); let count_columns = records.count_columns(); let shape = (count_rows, count_columns); if count_rows == 0 || count_columns == 0 { return; } let border = self; for col in 0..count_columns { let pos = (0, col).into(); let mut b = cfg.get_border(pos, shape); b.top = border.top; b.right_top_corner = border.top; cfg.set_border(pos, b); let pos = (count_rows - 1, col).into(); let mut b = cfg.get_border(pos, shape); b.bottom = border.bottom; b.right_bottom_corner = border.bottom; cfg.set_border(pos, b); } for row in 0..count_rows { let pos = (row, 0).into(); let mut b = cfg.get_border(pos, shape); b.left = border.left; b.left_bottom_corner = border.left; cfg.set_border(pos, b); let pos = (row, count_columns - 1).into(); let mut b = cfg.get_border(pos, shape); b.right = border.right; b.right_bottom_corner = border.right; cfg.set_border(pos, b); } let pos = (0, 0).into(); let mut b = cfg.get_border(pos, shape); b.left_top_corner = border.left_top_corner; cfg.remove_border(pos, shape); cfg.set_border(pos, b); let pos = (0, count_columns - 1).into(); let mut b = cfg.get_border(pos, shape); b.right_top_corner = border.right_top_corner; cfg.remove_border(pos, shape); cfg.set_border(pos, b); let pos = (count_rows - 1, 0).into(); let mut b = cfg.get_border(pos, shape); b.left_bottom_corner = border.left_bottom_corner; cfg.remove_border(pos, shape); cfg.set_border(pos, b); let pos = (count_rows - 1, count_columns - 1).into(); let mut b = cfg.get_border(pos, shape); b.right_bottom_corner = border.right_bottom_corner; cfg.remove_border(pos, shape); cfg.set_border(pos, b); } } #[derive(Debug, Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] pub struct EmptyBorder; #[cfg(feature = "std")] impl CellOption for EmptyBorder where R: Records + ExactRecords, { fn change(self, records: &mut R, cfg: &mut ColoredConfig, entity: Entity) { let shape = (records.count_rows(), records.count_columns()); for pos in entity.iter(shape.0, shape.1) { cfg.remove_border(pos, shape); } } } const fn get_char(c: Option) -> char { match c { Some(c) => c, None => unreachable!(), } } tabled-0.18.0/src/settings/style/border_color.rs000064400000000000000000000165551046102023000177710ustar 00000000000000//! This module contains a configuration of a Border to set its color via [`BorderColor`]. use crate::{ grid::{ config::{Border as GridBorder, ColoredConfig, Entity}, records::{ExactRecords, Records}, }, settings::{CellOption, Color, TableOption}, }; /// Border represents a border color of a Cell. /// /// ```text /// top border /// | /// V /// corner top left ------> +_______+ <---- corner top left /// | | /// left border ----------> | cell | <---- right border /// | | /// corner bottom right --> +_______+ <---- corner bottom right /// ^ /// | /// bottom border /// ``` /// /// # Example /// /// ```rust,no_run /// # use tabled::{Table, settings::{style::{Style, BorderColor}, object::Rows, Color}}; /// # let data: Vec<&'static str> = Vec::new(); /// let table = Table::new(&data) /// .with(Style::ascii()) /// .modify(Rows::single(0), BorderColor::new().top(Color::FG_RED)); /// ``` #[derive(Debug, Default, Clone, PartialEq, Eq, PartialOrd, Ord)] pub struct BorderColor { inner: GridBorder, } impl BorderColor { pub(crate) const fn from_border(inner: GridBorder) -> Self { BorderColor { inner } } /// Creates an empty border. pub const fn new() -> Self { Self::from_border(GridBorder::empty()) } /// This function constructs a cell borders with all sides set. #[allow(clippy::too_many_arguments)] pub const fn full( top: Color, bottom: Color, left: Color, right: Color, top_left: Color, top_right: Color, bottom_left: Color, bottom_right: Color, ) -> Self { Self::from_border(GridBorder::full( top, bottom, left, right, top_left, top_right, bottom_left, bottom_right, )) } /// This function constructs a cell borders with all sides's char set to a given color. /// It behaves like [`BorderColor::full`] with the same color set to each side. pub fn filled(c: Color) -> Self { Self::full( c.clone(), c.clone(), c.clone(), c.clone(), c.clone(), c.clone(), c.clone(), c, ) } /// Set a top border color. pub fn top(mut self, c: Color) -> Self { self.inner.top = Some(c); BorderColor::from_border(self.inner) } /// Set a bottom border color. pub fn bottom(mut self, c: Color) -> Self { self.inner.bottom = Some(c); BorderColor::from_border(self.inner) } /// Set a left border color. pub fn left(mut self, c: Color) -> Self { self.inner.left = Some(c); BorderColor::from_border(self.inner) } /// Set a right border color. pub fn right(mut self, c: Color) -> Self { self.inner.right = Some(c); BorderColor::from_border(self.inner) } /// Converts a border into a general data structure. pub fn into_inner(self) -> GridBorder { self.inner } /// Set a top left intersection color. pub fn corner_top_left(mut self, c: Color) -> Self { self.inner.left_top_corner = Some(c); self } /// Set a top right intersection color. pub fn corner_top_right(mut self, c: Color) -> Self { self.inner.right_top_corner = Some(c); self } /// Set a bottom left intersection color. pub fn corner_bottom_left(mut self, c: Color) -> Self { self.inner.left_bottom_corner = Some(c); self } /// Set a bottom right intersection color. pub fn corner_bottom_right(mut self, c: Color) -> Self { self.inner.right_bottom_corner = Some(c); self } } impl From for GridBorder { fn from(value: BorderColor) -> Self { value.inner } } impl CellOption for BorderColor where Data: Records + ExactRecords, { fn change(self, records: &mut Data, cfg: &mut ColoredConfig, entity: Entity) { let border = self.inner.clone().convert(); if matches!(entity, Entity::Global) && border.is_same() && !border.is_empty() { let color = border.top.expect("ok"); cfg.set_border_color_default(color); return; } let count_rows = records.count_rows(); let count_columns = records.count_columns(); for pos in entity.iter(count_rows, count_columns) { cfg.set_border_color(pos, border.clone()); } } } impl TableOption for BorderColor where Data: Records + ExactRecords, { fn change(self, records: &mut Data, cfg: &mut ColoredConfig, _: &mut D) { let count_rows = records.count_rows(); let count_columns = records.count_columns(); let shape = (count_rows, count_columns); if count_rows == 0 || count_columns == 0 { return; } let color = self.inner.clone().convert(); for col in 0..count_columns { let pos = (0, col).into(); let mut b = GridBorder::cloned(&cfg.get_border_color(pos, shape)); b.top = color.top.clone(); b.right_top_corner = color.top.clone(); cfg.set_border_color(pos, b); let pos = (count_rows - 1, col).into(); let mut b = GridBorder::cloned(&cfg.get_border_color(pos, shape)); b.bottom = color.bottom.clone(); b.right_bottom_corner = color.bottom.clone(); cfg.set_border_color(pos, b); } for row in 0..count_rows { let pos = (row, 0).into(); let mut b = GridBorder::cloned(&cfg.get_border_color(pos, shape)); b.left = color.left.clone(); b.left_bottom_corner = color.left.clone(); cfg.set_border_color(pos, b); let pos = (row, count_columns - 1).into(); let mut b = GridBorder::cloned(&cfg.get_border_color(pos, shape)); b.right = color.right.clone(); b.right_bottom_corner = color.right.clone(); cfg.set_border_color(pos, b); } let pos = (0, 0).into(); let mut b = GridBorder::cloned(&cfg.get_border_color(pos, shape)); b.left_top_corner = color.left_top_corner.clone(); cfg.remove_border_color(pos, shape); cfg.set_border_color(pos, b); let pos = (0, count_columns - 1).into(); let mut b = GridBorder::cloned(&cfg.get_border_color(pos, shape)); b.right_top_corner = color.right_top_corner.clone(); cfg.remove_border_color(pos, shape); cfg.set_border_color(pos, b); let pos = (count_rows - 1, 0).into(); let mut b = GridBorder::cloned(&cfg.get_border_color(pos, shape)); b.left_bottom_corner = color.left_bottom_corner.clone(); cfg.remove_border_color(pos, shape); cfg.set_border_color(pos, b); let pos = (count_rows - 1, count_columns - 1).into(); let mut b = GridBorder::cloned(&cfg.get_border_color(pos, shape)); b.right_bottom_corner = color.right_bottom_corner.clone(); cfg.remove_border_color(pos, shape); cfg.set_border_color(pos, b); } } tabled-0.18.0/src/settings/style/builder.rs000064400000000000000000001615671046102023000167500ustar 00000000000000//! This module contains a compile time style builder [`Style`]. use core::marker::PhantomData; use crate::{ grid::config::{Border as GridBorder, Borders, CompactConfig, CompactMultilineConfig}, settings::{ style::{HorizontalLine, VerticalLine}, Border, TableOption, }, }; #[cfg(feature = "std")] use crate::grid::config::ColoredConfig; /// Style is represents a theme of a [`Table`]. /// /// ```text /// corner top left top intersection corner top right /// . | . /// . V . /// ╭───┬───┬───┬───┬───┬───┬────┬────┬────╮ /// │ i │ 0 │ 1 │ 2 │ 3 │ 4 │ 5 │ 6 │ 7 │ /// ├───┼───┼───┼───┼───┼───┼────┼────┼────┤ <- this horizontal line is custom 'horizontals' /// │ 0 │ 0 │ 0 │ 0 │ 0 │ 0 │ 0 │ 0 │ 0 │ other lines horizontal lines are not set they called 'horizontal' /// │ 1 │ 0 │ 1 │ 2 │ 3 │ 4 │ 5 │ 6 │ 7 │ /// │ 2 │ 0 │ 2 │ 4 │ 6 │ 8 │ 10 │ 12 │ 14 │ /// ╰───┴───┴───┴───┴───┴───┴────┴────┴────╯ /// . ^ ^ . /// . | | . /// corner bottom left | bottom intersection corner bottom right /// | /// | /// all this vertical lines are called 'vertical' /// ``` /// /// /// ```text /// ┌───┬───┬───┬───┬───┐ /// │ 0 │ 1 │ 2 │ 3 │ 4 │ /// intersection left ->├───X───X───X───X───┤ <- all this horizontal lines are called 'horizontal' /// │ 1 │ 2 │ 3 │ 4 │ 5 │ /// ├───X───X───X───X───┤ <- intersection right /// │ 2 │ 3 │ 4 │ 5 │ 6 │ /// └───┴───┴───┴───┴───┘ /// /// All 'X' positions are called 'intersection'. /// It's a place where 'vertical' and 'horizontal' lines intersect. /// ``` /// /// It tries to limit an controlling a valid state of it. /// For example, it won't allow to call method [`Style::corner_top_left`] unless [`Style::left`] and [`Style::top`] is set. /// /// You can turn [`Style`] into [`Theme`] to have a precise control using [`Into`] implementation. /// /// # Example /// #[cfg_attr(feature = "std", doc = "```")] #[cfg_attr(not(feature = "std"), doc = "```ignore")] /// use tabled::{Table, settings::style::Style}; /// /// let data = vec!["Hello", "2021"]; /// let mut table = Table::new(&data); /// /// let style = Style::ascii().bottom('*').intersection(' '); /// table.with(style); /// /// println!("{}", table); /// ``` /// /// [`Table`]: crate::Table /// [`Theme`]: crate::settings::themes::Theme /// [`Style::corner_top_left`]: Style::corner_top_left /// [`Style::left`]: Style.left /// [`Style::top`]: Style.function.top #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct Style { borders: Borders, horizontals: HArray, verticals: VArray, _top: PhantomData, _bottom: PhantomData, _left: PhantomData, _right: PhantomData, _horizontal: PhantomData, _vertical: PhantomData, } type HLine = crate::grid::config::HorizontalLine; type VLine = crate::grid::config::VerticalLine; type HArray = [(usize, HLine); N]; type VArray = [(usize, VLine); N]; /// A marker struct which is used in [`Style`]. #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Copy, Default, Hash)] pub struct On; impl Style<(), (), (), (), (), (), 0, 0> { /// This style is a style with no styling options on, /// /// ```text /// id distribution link /// 0 Fedora https://getfedora.org/ /// 2 OpenSUSE https://www.opensuse.org/ /// 3 Endeavouros https://endeavouros.com/ /// ``` /// /// Note: The cells in the example have 1-left and 1-right indent. /// /// This style can be used as a base style to build a custom one. /// /// ```rust,no_run /// # use tabled::settings::style::Style; /// let style = Style::empty() /// .top('*') /// .bottom('*') /// .vertical('#') /// .intersection_top('*'); /// ``` pub const fn empty() -> Style<(), (), (), (), (), (), 0, 0> { Style::new(Borders::empty(), [], []) } /// This style is analog of `empty` but with a vertical space(' ') line. /// /// ```text /// id distribution link /// 0 Fedora https://getfedora.org/ /// 2 OpenSUSE https://www.opensuse.org/ /// 3 Endeavouros https://endeavouros.com/ /// ``` pub const fn blank() -> Style<(), (), (), (), (), On, 0, 0> { Style::new( create_borders( HLine::empty(), HLine::empty(), HLine::empty(), None, None, Some(' '), ), [], [], ) } /// This is a style which relays only on ASCII charset. /// /// It has horizontal and vertical lines. /// /// ```text /// +----+--------------+---------------------------+ /// | id | distribution | link | /// +----+--------------+---------------------------+ /// | 0 | Fedora | https://getfedora.org/ | /// +----+--------------+---------------------------+ /// | 2 | OpenSUSE | https://www.opensuse.org/ | /// +----+--------------+---------------------------+ /// | 3 | Endeavouros | https://endeavouros.com/ | /// +----+--------------+---------------------------+ /// ``` pub const fn ascii() -> Style { Style::new( create_borders( HLine::full('-', '+', '+', '+'), HLine::full('-', '+', '+', '+'), HLine::full('-', '+', '+', '+'), Some('|'), Some('|'), Some('|'), ), [], [], ) } /// `psql` style looks like a table style `PostgreSQL` uses. /// /// It has only 1 horizontal line which splits header. /// And no left and right vertical lines. /// /// ```text /// id | distribution | link /// ----+--------------+--------------------------- /// 0 | Fedora | https://getfedora.org/ /// 2 | OpenSUSE | https://www.opensuse.org/ /// 3 | Endeavouros | https://endeavouros.com/ /// ``` pub const fn psql() -> Style<(), (), (), (), (), On, 1, 0> { Style::new( create_borders( HLine::empty(), HLine::empty(), HLine::empty(), None, None, Some('|'), ), [(1, HLine::new(Some('-'), Some('+'), None, None))], [], ) } /// `markdown` style mimics a `Markdown` table style. /// /// ```text /// | id | distribution | link | /// |----|--------------|---------------------------| /// | 0 | Fedora | https://getfedora.org/ | /// | 2 | OpenSUSE | https://www.opensuse.org/ | /// | 3 | Endeavouros | https://endeavouros.com/ | /// ``` pub const fn markdown() -> Style<(), (), On, On, (), On, 1, 0> { Style::new( create_borders( HLine::empty(), HLine::empty(), HLine::empty(), Some('|'), Some('|'), Some('|'), ), [(1, HLine::full('-', '|', '|', '|'))], [], ) } /// This style is analog of [`Style::ascii`] which uses UTF-8 charset. /// /// It has vertical and horizontal split lines. /// /// ```text /// ┌────┬──────────────┬───────────────────────────┐ /// │ id │ distribution │ link │ /// ├────┼──────────────┼───────────────────────────┤ /// │ 0 │ Fedora │ https://getfedora.org/ │ /// ├────┼──────────────┼───────────────────────────┤ /// │ 2 │ OpenSUSE │ https://www.opensuse.org/ │ /// ├────┼──────────────┼───────────────────────────┤ /// │ 3 │ Endeavouros │ https://endeavouros.com/ │ /// └────┴──────────────┴───────────────────────────┘ /// ``` pub const fn modern() -> Style { Style::new( create_borders( HLine::full('─', '┬', '┌', '┐'), HLine::full('─', '┴', '└', '┘'), HLine::full('─', '┼', '├', '┤'), Some('│'), Some('│'), Some('│'), ), [], [], ) } /// This style looks like a [`Style::modern`] but without horozizontal lines except a header. /// /// Beware: It uses UTF-8 characters. /// /// ```text /// ┌────┬──────────────┬───────────────────────────┐ /// │ id │ distribution │ link │ /// ├────┼──────────────┼───────────────────────────┤ /// │ 0 │ Fedora │ https://getfedora.org/ │ /// │ 2 │ OpenSUSE │ https://www.opensuse.org/ │ /// │ 3 │ Endeavouros │ https://endeavouros.com/ │ /// └────┴──────────────┴───────────────────────────┘ /// ``` pub const fn sharp() -> Style { Style::new( create_borders( HLine::full('─', '┬', '┌', '┐'), HLine::full('─', '┴', '└', '┘'), HLine::empty(), Some('│'), Some('│'), Some('│'), ), [(1, HLine::full('─', '┼', '├', '┤'))], [], ) } /// This style looks like a [`Style::sharp`] but with rounded corners. /// /// Beware: It uses UTF-8 characters. /// /// ```text /// ╭────┬──────────────┬───────────────────────────╮ /// │ id │ distribution │ link │ /// ├────┼──────────────┼───────────────────────────┤ /// │ 0 │ Fedora │ https://getfedora.org/ │ /// │ 2 │ OpenSUSE │ https://www.opensuse.org/ │ /// │ 3 │ Endeavouros │ https://endeavouros.com/ │ /// ╰────┴──────────────┴───────────────────────────╯ /// ``` pub const fn rounded() -> Style { Style::new( create_borders( HLine::full('─', '┬', '╭', '╮'), HLine::full('─', '┴', '╰', '╯'), HLine::empty(), Some('│'), Some('│'), Some('│'), ), [(1, HLine::full('─', '┼', '├', '┤'))], [], ) } /// This style looks like a [`Style::rounded`] but with horizontals lines. /// /// Beware: It uses UTF-8 characters. /// /// ```text /// ╭────┬──────────────┬───────────────────────────╮ /// │ id │ distribution │ link │ /// ├────┼──────────────┼───────────────────────────┤ /// │ 0 │ Fedora │ https://getfedora.org/ │ /// ├────┼──────────────┼───────────────────────────┤ /// │ 2 │ OpenSUSE │ https://www.opensuse.org/ │ /// ├────┼──────────────┼───────────────────────────┤ /// │ 3 │ Endeavouros │ https://endeavouros.com/ │ /// ╰────┴──────────────┴───────────────────────────╯ /// ``` pub const fn modern_rounded() -> Style { Style::new( create_borders( HLine::full('─', '┬', '╭', '╮'), HLine::full('─', '┴', '╰', '╯'), HLine::full('─', '┼', '├', '┤'), Some('│'), Some('│'), Some('│'), ), [], [], ) } /// This style uses a chars which resembles '2 lines'. /// /// Beware: It uses UTF8 characters. /// /// ```text /// ╔════╦══════════════╦═══════════════════════════╗ /// ║ id ║ distribution ║ link ║ /// ╠════╬══════════════╬═══════════════════════════╣ /// ║ 0 ║ Fedora ║ https://getfedora.org/ ║ /// ╠════╬══════════════╬═══════════════════════════╣ /// ║ 2 ║ OpenSUSE ║ https://www.opensuse.org/ ║ /// ╠════╬══════════════╬═══════════════════════════╣ /// ║ 3 ║ Endeavouros ║ https://endeavouros.com/ ║ /// ╚════╩══════════════╩═══════════════════════════╝ /// ``` pub const fn extended() -> Style { Style::new( create_borders( HLine::full('═', '╦', '╔', '╗'), HLine::full('═', '╩', '╚', '╝'), HLine::full('═', '╬', '╠', '╣'), Some('║'), Some('║'), Some('║'), ), [], [], ) } /// This is a style uses only '.' and ':' chars. /// It has a vertical and horizontal split lines. /// /// ```text /// ................................................. /// : id : distribution : link : /// :....:..............:...........................: /// : 0 : Fedora : https://getfedora.org/ : /// :....:..............:...........................: /// : 2 : OpenSUSE : https://www.opensuse.org/ : /// :....:..............:...........................: /// : 3 : Endeavouros : https://endeavouros.com/ : /// :....:..............:...........................: /// ``` pub const fn dots() -> Style { Style::new( create_borders( HLine::full('.', '.', '.', '.'), HLine::full('.', ':', ':', ':'), HLine::full('.', ':', ':', ':'), Some(':'), Some(':'), Some(':'), ), [], [], ) } /// This style is one of table views in `ReStructuredText`. /// /// ```text /// ==== ============== =========================== /// id distribution link /// ==== ============== =========================== /// 0 Fedora https://getfedora.org/ /// 2 OpenSUSE https://www.opensuse.org/ /// 3 Endeavouros https://endeavouros.com/ /// ==== ============== =========================== /// ``` pub const fn re_structured_text() -> Style { Style::new( create_borders( HLine::new(Some('='), Some(' '), None, None), HLine::new(Some('='), Some(' '), None, None), HLine::empty(), None, None, Some(' '), ), [(1, HLine::new(Some('='), Some(' '), None, None))], [], ) } /// This is a theme analog of [`Style::rounded`], but in using ascii charset and /// with no horizontal lines. /// /// ```text /// .-----------------------------------------------. /// | id | distribution | link | /// | 0 | Fedora | https://getfedora.org/ | /// | 2 | OpenSUSE | https://www.opensuse.org/ | /// | 3 | Endeavouros | https://endeavouros.com/ | /// '-----------------------------------------------' /// ``` pub const fn ascii_rounded() -> Style { Style::new( create_borders( HLine::full('-', '-', '.', '.'), HLine::full('-', '-', '\'', '\''), HLine::empty(), Some('|'), Some('|'), Some('|'), ), [], [], ) } } impl Style { pub(crate) const fn new( borders: Borders, horizontals: HArray, verticals: VArray, ) -> Self { Self { borders, horizontals, verticals, _top: PhantomData, _bottom: PhantomData, _left: PhantomData, _right: PhantomData, _horizontal: PhantomData, _vertical: PhantomData, } } pub(crate) const fn get_borders(&self) -> Borders { self.borders } #[cfg(feature = "std")] pub(crate) const fn get_horizontals(&self) -> [(usize, HLine); HSIZE] { self.horizontals } } impl Style { /// Set border horizontal lines. /// /// # Example /// #[cfg_attr(feature = "derive", doc = "```")] #[cfg_attr(not(feature = "derive"), doc = "```ignore")] /// use tabled::{settings::style::{Style, HorizontalLine}, Table}; /// /// let data = (0..3).map(|i| ("Hello", i)); /// let mut table = Table::new(data); /// /// let style = Style::rounded().horizontals([ /// (1, HorizontalLine::filled('#')), /// (2, HorizontalLine::filled('&')), /// (3, HorizontalLine::filled('@')), /// ]); /// /// table.with(style); /// /// assert_eq!( /// table.to_string(), /// concat!( /// "╭───────┬─────╮\n", /// "│ &str │ i32 │\n", /// "###############\n", /// "│ Hello │ 0 │\n", /// "&&&&&&&&&&&&&&&\n", /// "│ Hello │ 1 │\n", /// "@@@@@@@@@@@@@@@\n", /// "│ Hello │ 2 │\n", /// "╰───────┴─────╯", /// ) /// ) /// ``` pub const fn horizontals( self, list: [(usize, HorizontalLine); SIZE], ) -> Style { let list = harr_convert(list); Style::new(self.borders, list, self.verticals) } /// Set border vertical lines. /// /// # Example /// #[cfg_attr(feature = "derive", doc = "```")] #[cfg_attr(not(feature = "derive"), doc = "```ignore")] /// use tabled::{settings::style::{Style, VerticalLine}, Table}; /// /// let data = (0..3).map(|i| ("Hello", "World", i)); /// let mut table = Table::new(data); /// /// let style = Style::rounded().verticals([ /// (1, VerticalLine::new('#').top(':').bottom('.')), /// (2, VerticalLine::new('&').top(':').bottom('.')), /// ]); /// let table = table.with(style).to_string(); /// /// assert_eq!( /// table, /// concat!( /// "╭───────:───────:─────╮\n", /// "│ &str # &str & i32 │\n", /// "├───────┼───────┼─────┤\n", /// "│ Hello # World & 0 │\n", /// "│ Hello # World & 1 │\n", /// "│ Hello # World & 2 │\n", /// "╰───────.───────.─────╯", /// ) /// ) /// ``` pub const fn verticals( self, list: [(usize, VerticalLine); SIZE], ) -> Style { let list = varr_convert(list); Style::new(self.borders, self.horizontals, list) } /// Removes all horizontal lines set by [`Style::horizontals`] pub const fn remove_horizontals(self) -> Style { Style::new(self.borders, [], self.verticals) } /// Removes all verticals lines set by [`Style::verticals`] pub const fn remove_verticals(self) -> Style { Style::new(self.borders, self.horizontals, []) } /// Sets a top border. /// /// Any corners and intersections which were set will be overridden. pub const fn top(mut self, c: char) -> Style where T: Copy, B: Copy, H: Copy, { self.borders.top = Some(c); if self.borders.has_left() { self.borders.top_left = Some(c); } if self.borders.has_right() { self.borders.top_right = Some(c); } if self.borders.has_vertical() { self.borders.top_intersection = Some(c); } let verticals = varr_set(self.verticals, VLine::new(None, None, Some(c), None)); Style::new(self.borders, self.horizontals, verticals) } /// Sets a bottom border. /// /// Any corners and intersections which were set will be overridden. pub const fn bottom(mut self, c: char) -> Style where T: Copy, B: Copy, H: Copy, { self.borders.bottom = Some(c); if self.borders.has_left() { self.borders.bottom_left = Some(c); } if self.borders.has_right() { self.borders.bottom_right = Some(c); } if self.borders.has_vertical() { self.borders.bottom_intersection = Some(c); } let verticals = varr_set(self.verticals, VLine::new(None, None, None, Some(c))); Style::new(self.borders, self.horizontals, verticals) } /// Sets a left border. /// /// Any corners and intersections which were set will be overridden. pub const fn left(mut self, c: char) -> Style where L: Copy, R: Copy, V: Copy, { self.borders.left = Some(c); if self.borders.has_top() { self.borders.top_left = Some(c); } if self.borders.has_bottom() { self.borders.bottom_left = Some(c); } if self.borders.has_horizontal() { self.borders.left_intersection = Some(c); } let horizontals = harr_set(self.horizontals, HLine::new(None, None, Some(c), None)); Style::new(self.borders, horizontals, self.verticals) } /// Sets a right border. /// /// Any corners and intersections which were set will be overridden. pub const fn right(mut self, c: char) -> Style where L: Copy, R: Copy, V: Copy, { self.borders.right = Some(c); if self.borders.has_top() { self.borders.top_right = Some(c); } if self.borders.has_bottom() { self.borders.bottom_right = Some(c); } if self.borders.has_horizontal() { self.borders.right_intersection = Some(c); } let horizontals = harr_set(self.horizontals, HLine::new(None, None, None, Some(c))); Style::new(self.borders, horizontals, self.verticals) } /// Sets a horizontal split line. /// /// Any corners and intersections which were set will be overridden. pub const fn horizontal(mut self, c: char) -> Style where T: Copy, B: Copy, H: Copy, { self.borders.horizontal = Some(c); if self.borders.has_vertical() { self.borders.intersection = Some(c); } if self.borders.has_left() { self.borders.left_intersection = Some(c); } if self.borders.has_right() { self.borders.right_intersection = Some(c); } let verticals = varr_set(self.verticals, VLine::new(None, Some(c), None, None)); Style::new(self.borders, self.horizontals, verticals) } /// Sets a vertical split line. /// /// Any corners and intersections which were set will be overridden. pub const fn vertical(mut self, c: char) -> Style where L: Copy, R: Copy, V: Copy, { self.borders.vertical = Some(c); if self.borders.has_horizontal() { self.borders.intersection = Some(c); } if self.borders.has_top() { self.borders.top_intersection = Some(c); } if self.borders.has_bottom() { self.borders.bottom_intersection = Some(c); } let horizontals = harr_set(self.horizontals, HLine::new(None, Some(c), None, None)); Style::new(self.borders, horizontals, self.verticals) } /// Set a vertical line. /// An equivalent of calling vertical+top_intersection+bottom_intersection+intersion. /// /// Notice, that it will clear everything that is outdated, meaning /// If your style has a top border line and but the given vertical line has not got it then it will be removed. pub const fn line_vertical( mut self, line: VerticalLine, ) -> Style where L: Copy, R: Copy, Top: Copy, Bottom: Copy, Intersection: Copy, { let line = line.into_inner(); self.borders.vertical = line.main; self.borders.intersection = line.intersection; self.borders.top_intersection = line.top; self.borders.bottom_intersection = line.bottom; if line.intersection.is_none() { self.borders.horizontal = None; self.borders.left_intersection = None; self.borders.right_intersection = None; self.borders.intersection = None; } else { if self.borders.has_left() && self.borders.left_intersection.is_none() { self.borders.left_intersection = Some(' '); } if self.borders.has_right() && self.borders.right_intersection.is_none() { self.borders.right_intersection = Some(' '); } if self.borders.horizontal.is_none() { self.borders.horizontal = Some(' '); } } if line.top.is_none() { self.borders.top = None; self.borders.top_left = None; self.borders.top_right = None; self.borders.top_intersection = None; } if line.bottom.is_none() { self.borders.bottom = None; self.borders.bottom_left = None; self.borders.bottom_right = None; self.borders.bottom_intersection = None; } let horizontals = harr_set( self.horizontals, HLine::new(None, line.intersection, None, None), ); let verticals = varr_set( self.verticals, VLine::new(None, line.intersection, line.top, line.bottom), ); Style::new(self.borders, horizontals, verticals) } /// Set a horizontal line. /// An equivalent of calling horizontal+left_intersection+right_intersection+intersion. /// /// Notice, that it will clear everything that is outdated, meaning /// If your style has a left border line and but the given vertical line has not got it then it will be removed. pub const fn line_horizontal( mut self, line: HorizontalLine, ) -> Style where L: Copy, R: Copy, Left: Copy, Right: Copy, Intersection: Copy, { let line = line.into_inner(); self.borders.horizontal = line.main; self.borders.intersection = line.intersection; self.borders.left_intersection = line.left; self.borders.right_intersection = line.right; if line.intersection.is_none() { self.borders.vertical = None; self.borders.top_intersection = None; self.borders.bottom_intersection = None; self.borders.intersection = None; } else { if self.borders.has_top() && self.borders.top_intersection.is_none() { self.borders.top_intersection = Some(' '); } if self.borders.has_bottom() && self.borders.bottom_intersection.is_none() { self.borders.bottom_intersection = Some(' '); } if self.borders.vertical.is_none() { self.borders.vertical = Some(' '); } } if line.left.is_none() { self.borders.left = None; self.borders.top_left = None; self.borders.bottom_left = None; self.borders.left_intersection = None; } if line.right.is_none() { self.borders.right = None; self.borders.top_right = None; self.borders.bottom_right = None; self.borders.right_intersection = None; } let horizontals = harr_set( self.horizontals, HLine::new(None, line.intersection, line.left, line.right), ); let verticals = varr_set( self.verticals, VLine::new(None, line.intersection, None, None), ); Style::new(self.borders, horizontals, verticals) } /// Set a horizontal line. /// An equivalent of calling top+cornet_top_right+cornet_top_left+top_intersection. /// /// Notice, that it will clear everything that is outdated, meaning /// If your style has a left border line and but the given vertical line has not got it then it will be removed. pub const fn line_top( mut self, line: HorizontalLine, ) -> Style where L: Copy, R: Copy, Left: Copy, Right: Copy, Intersection: Copy, { let line = line.into_inner(); self.borders.top = line.main; self.borders.top_intersection = line.intersection; self.borders.top_left = line.left; self.borders.top_right = line.right; if line.intersection.is_none() { self.borders.vertical = None; self.borders.top_intersection = None; self.borders.bottom_intersection = None; self.borders.intersection = None; } else { if self.borders.has_top() && self.borders.top_intersection.is_none() { self.borders.top_intersection = Some(' '); } if self.borders.has_bottom() && self.borders.bottom_intersection.is_none() { self.borders.bottom_intersection = Some(' '); } if self.borders.vertical.is_none() { self.borders.vertical = Some(' '); } } if line.left.is_none() { self.borders.left = None; self.borders.top_left = None; self.borders.bottom_left = None; self.borders.left_intersection = None; } if line.right.is_none() { self.borders.right = None; self.borders.top_right = None; self.borders.bottom_right = None; self.borders.right_intersection = None; } let horizontals = harr_set( self.horizontals, HLine::new(None, line.intersection, line.left, line.right), ); let verticals = varr_set( self.verticals, VLine::new(None, line.intersection, None, None), ); Style::new(self.borders, horizontals, verticals) } /// Set a horizontal line. /// An equivalent of calling bottom+cornet_bottom_right+cornet_bottom_left+bottom_intersection. /// /// Notice, that it will clear everything that is outdated, meaning /// If your style has a left border line and but the given vertical line has not got it then it will be removed. pub const fn line_bottom( mut self, line: HorizontalLine, ) -> Style where L: Copy, R: Copy, Left: Copy, Right: Copy, Intersection: Copy, { let line = line.into_inner(); self.borders.bottom = line.main; self.borders.bottom_intersection = line.intersection; self.borders.bottom_left = line.left; self.borders.bottom_right = line.right; if line.intersection.is_none() { self.borders.vertical = None; self.borders.top_intersection = None; self.borders.bottom_intersection = None; self.borders.intersection = None; } else { if self.borders.has_top() && self.borders.top_intersection.is_none() { self.borders.top_intersection = Some(' '); } if self.borders.has_bottom() && self.borders.bottom_intersection.is_none() { self.borders.bottom_intersection = Some(' '); } if self.borders.vertical.is_none() { self.borders.vertical = Some(' '); } } if line.left.is_none() { self.borders.left = None; self.borders.top_left = None; self.borders.bottom_left = None; self.borders.left_intersection = None; } if line.right.is_none() { self.borders.right = None; self.borders.top_right = None; self.borders.bottom_right = None; self.borders.right_intersection = None; } let horizontals = harr_set( self.horizontals, HLine::new(None, line.intersection, line.left, line.right), ); let verticals = varr_set( self.verticals, VLine::new(None, line.intersection, None, None), ); Style::new(self.borders, horizontals, verticals) } /// Set a vertical line. /// An equivalent of calling left+corner_top_left+corner_bottom_left+left_intersection. /// /// Notice, that it will clear everything that is outdated, meaning /// If your style has a top border line and but the given vertical line has not got it then it will be removed. pub const fn line_left( mut self, line: VerticalLine, ) -> Style where L: Copy, R: Copy, Top: Copy, Bottom: Copy, Intersection: Copy, { let line = line.into_inner(); self.borders.left = line.main; self.borders.left_intersection = line.intersection; self.borders.top_left = line.top; self.borders.bottom_left = line.bottom; if line.intersection.is_none() { self.borders.horizontal = None; self.borders.left_intersection = None; self.borders.right_intersection = None; self.borders.intersection = None; } else { if self.borders.has_left() && self.borders.left_intersection.is_none() { self.borders.left_intersection = Some(' '); } if self.borders.has_right() && self.borders.right_intersection.is_none() { self.borders.right_intersection = Some(' '); } if self.borders.horizontal.is_none() { self.borders.horizontal = Some(' '); } } if line.top.is_none() { self.borders.top = None; self.borders.top_left = None; self.borders.top_right = None; self.borders.top_intersection = None; } if line.bottom.is_none() { self.borders.bottom = None; self.borders.bottom_left = None; self.borders.bottom_right = None; self.borders.bottom_intersection = None; } let horizontals = harr_set( self.horizontals, HLine::new(None, line.intersection, None, None), ); let verticals = varr_set( self.verticals, VLine::new(None, line.intersection, line.top, line.bottom), ); Style::new(self.borders, horizontals, verticals) } /// Set a vertical line. /// An equivalent of calling right+corner_top_right+corner_bottom_right+right_intersection. /// /// Notice, that it will clear everything that is outdated, meaning /// If your style has a top border line and but the given vertical line has not got it then it will be removed. pub const fn line_right( mut self, line: VerticalLine, ) -> Style where L: Copy, R: Copy, Top: Copy, Bottom: Copy, Intersection: Copy, { let line = line.into_inner(); self.borders.right = line.main; self.borders.right_intersection = line.intersection; self.borders.top_right = line.top; self.borders.bottom_right = line.bottom; if line.intersection.is_none() { self.borders.horizontal = None; self.borders.left_intersection = None; self.borders.right_intersection = None; self.borders.intersection = None; } else { if self.borders.has_left() && self.borders.left_intersection.is_none() { self.borders.left_intersection = Some(' '); } if self.borders.has_right() && self.borders.right_intersection.is_none() { self.borders.right_intersection = Some(' '); } if self.borders.horizontal.is_none() { self.borders.horizontal = Some(' '); } } if line.top.is_none() { self.borders.top = None; self.borders.top_left = None; self.borders.top_right = None; self.borders.top_intersection = None; } if line.bottom.is_none() { self.borders.bottom = None; self.borders.bottom_left = None; self.borders.bottom_right = None; self.borders.bottom_intersection = None; } let horizontals = harr_set( self.horizontals, HLine::new(None, line.intersection, None, None), ); let verticals = varr_set( self.verticals, VLine::new(None, line.intersection, line.top, line.bottom), ); Style::new(self.borders, horizontals, verticals) } /// Set a frame for a style. /// /// It makes assumptions that a full frame will be set, but it may not be. /// /// # Example /// /// ``` /// use tabled::{Table, settings::style::{Style, Border}}; /// /// let data = [["10:52:19", "Hello"], ["10:52:20", "World"]]; /// let table = Table::new(data) /// .with(Style::ascii().frame(Border::inherit(Style::modern()))) /// .to_string(); /// /// assert_eq!( /// table, /// concat!( /// "┌──────────+───────┐\n", /// "│ 0 | 1 │\n", /// "+----------+-------+\n", /// "│ 10:52:19 | Hello │\n", /// "+----------+-------+\n", /// "│ 10:52:20 | World │\n", /// "└──────────+───────┘", /// ) /// ); /// ``` pub const fn frame( mut self, border: Border, ) -> Style where T: Copy, B: Copy, L: Copy, R: Copy, H: Copy, V: Copy, Left: Copy, Right: Copy, Top: Copy, Bottom: Copy, { let border = border.into_inner(); let border = correct_border(border); let horizontals = harr_set( self.horizontals, HLine::new(None, None, border.left, border.right), ); let verticals = varr_set( self.verticals, VLine::new(None, None, border.top, border.bottom), ); self.borders.top = border.top; self.borders.bottom = border.bottom; self.borders.left = border.left; self.borders.top_left = border.left_top_corner; self.borders.bottom_left = border.left_bottom_corner; self.borders.right = border.right; self.borders.top_right = border.right_top_corner; self.borders.bottom_right = border.right_bottom_corner; Style::new(self.borders, horizontals, verticals) } } impl Style { /// Sets a top left corner. pub const fn corner_top_left(mut self, c: char) -> Self { self.borders.top_left = Some(c); Style::new(self.borders, self.horizontals, self.verticals) } } impl Style { /// Sets a top right corner. pub const fn corner_top_right(mut self, c: char) -> Self { self.borders.top_right = Some(c); Style::new(self.borders, self.horizontals, self.verticals) } } impl Style { /// Sets a bottom right corner. pub const fn corner_bottom_right(mut self, c: char) -> Self { self.borders.bottom_right = Some(c); Style::new(self.borders, self.horizontals, self.verticals) } } impl Style { /// Sets a bottom left corner. pub const fn corner_bottom_left(mut self, c: char) -> Self { self.borders.bottom_left = Some(c); Style::new(self.borders, self.horizontals, self.verticals) } } impl Style { /// Sets a left intersection char. pub const fn intersection_left(mut self, c: char) -> Self { self.borders.left_intersection = Some(c); Style::new(self.borders, self.horizontals, self.verticals) } } impl Style { /// Sets a right intersection char. pub const fn intersection_right(mut self, c: char) -> Self { self.borders.right_intersection = Some(c); Style::new(self.borders, self.horizontals, self.verticals) } } impl Style { /// Sets a top intersection char. pub const fn intersection_top(mut self, c: char) -> Self { self.borders.top_intersection = Some(c); Style::new(self.borders, self.horizontals, self.verticals) } } impl Style { /// Sets a bottom intersection char. pub const fn intersection_bottom(mut self, c: char) -> Self { self.borders.bottom_intersection = Some(c); Style::new(self.borders, self.horizontals, self.verticals) } } impl Style { /// Sets an inner intersection char. /// A char between horizontal and vertical split lines. pub const fn intersection(mut self, c: char) -> Self where T: Copy, B: Copy, R: Copy, L: Copy, { self.borders.intersection = Some(c); let horizontals = harr_set(self.horizontals, HLine::new(None, Some(c), None, None)); let verticals = varr_set(self.verticals, VLine::new(None, Some(c), None, None)); Style::new(self.borders, horizontals, verticals) } } impl Style { /// Removes top border. pub const fn remove_top(mut self) -> Style<(), B, L, R, H, V, HN, VN> where B: Copy, H: Copy, { self.borders.top = None; self.borders.top_intersection = None; self.borders.top_left = None; self.borders.top_right = None; let verticals = varr_unset(self.verticals, VLine::new(None, None, Some(' '), None)); Style::new(self.borders, self.horizontals, verticals) } } impl Style { /// Removes bottom border. pub const fn remove_bottom(mut self) -> Style where T: Copy, H: Copy, { self.borders.bottom = None; self.borders.bottom_intersection = None; self.borders.bottom_left = None; self.borders.bottom_right = None; let verticals = varr_unset(self.verticals, VLine::new(None, None, None, Some(' '))); Style::new(self.borders, self.horizontals, verticals) } } impl Style { /// Removes left border. pub const fn remove_left(mut self) -> Style where R: Copy, V: Copy, { self.borders.left = None; self.borders.left_intersection = None; self.borders.top_left = None; self.borders.bottom_left = None; let horizontals = harr_unset(self.horizontals, HLine::new(None, None, Some(' '), None)); Style::new(self.borders, horizontals, self.verticals) } } impl Style { /// Removes right border. pub const fn remove_right(mut self) -> Style where L: Copy, V: Copy, { self.borders.right = None; self.borders.right_intersection = None; self.borders.top_right = None; self.borders.bottom_right = None; let horizontals = harr_unset(self.horizontals, HLine::new(None, None, None, Some(' '))); Style::new(self.borders, horizontals, self.verticals) } } impl Style { /// Removes horizontal split lines. /// /// Not including custom split lines. pub const fn remove_horizontal(mut self) -> Style where T: Copy, B: Copy, V: Copy, { self.borders.horizontal = None; self.borders.left_intersection = None; self.borders.right_intersection = None; self.borders.intersection = None; // let lines = linearr_unset(lines, Line::new(None, Some(' '), None, None)); let verticals = self.verticals; Style::new(self.borders, self.horizontals, verticals) } } impl Style { /// Removes vertical split lines. pub const fn remove_vertical(mut self) -> Style where R: Copy, L: Copy, { self.borders.vertical = None; self.borders.top_intersection = None; self.borders.bottom_intersection = None; self.borders.intersection = None; // let lines = linearr_unset(lines, Line::new(None, Some(' '), None, None)); let horizontals = self.horizontals; Style::new(self.borders, horizontals, self.verticals) } } impl Style { /// Removes frame. pub const fn remove_frame(self) -> Style<(), (), (), (), H, V, HN, VN> where V: Copy, H: Copy, { self.remove_bottom() .remove_top() .remove_left() .remove_right() } } #[cfg(feature = "std")] impl TableOption for Style { fn change(self, _: &mut Data, cfg: &mut ColoredConfig, _: &mut Dims) { cfg_clear_borders(cfg); cfg_set_custom_lines(cfg, &self.horizontals, &self.verticals); cfg.set_borders(self.borders); } } impl TableOption for Style { fn change(self, _: &mut Data, cfg: &mut CompactConfig, _: &mut Dims) { *cfg = cfg.set_borders(self.borders); } } impl TableOption for Style { fn change(self, _: &mut Data, cfg: &mut CompactMultilineConfig, _: &mut Dims) { cfg.set_borders(self.borders); } } impl From> for Borders { fn from(value: Style) -> Self { value.borders } } const fn correct_border(mut border: GridBorder) -> GridBorder { if border.has_top() && border.top.is_none() { border.top = Some(' '); } if border.has_bottom() && border.bottom.is_none() { border.bottom = Some(' '); } if border.has_left() && border.left.is_none() { border.left = Some(' '); } if border.has_right() && border.right.is_none() { border.right = Some(' '); } if border.has_top() && border.has_left() && border.left_top_corner.is_none() { border.left_top_corner = Some(' '); } if border.has_top() && border.has_right() && border.right_top_corner.is_none() { border.right_top_corner = Some(' '); } if border.has_bottom() && border.has_left() && border.left_top_corner.is_none() { border.left_bottom_corner = Some(' '); } if border.has_bottom() && border.has_right() && border.right_bottom_corner.is_none() { border.right_bottom_corner = Some(' '); } border } const fn varr_convert( lines: [(usize, VerticalLine); N], ) -> VArray { let mut buf = [(0, VLine::empty()); N]; let mut i = 0; while i < N { let (index, line) = &lines[i]; let index = *index; let line = line.into_inner(); buf[i].0 = index; buf[i].1 = line; i += 1; } buf } const fn harr_convert( lines: [(usize, HorizontalLine); N], ) -> HArray { let mut buf = [(0, HLine::empty()); N]; let mut i = 0; while i < N { let (index, line) = &lines[i]; let index = *index; let line = line.into_inner(); buf[i].0 = index; buf[i].1 = line; i += 1; } buf } const fn harr_set(lines: HArray, set: HLine) -> HArray { let mut buf = [(0, HLine::empty()); N]; let mut i = 0; while i < N { let (index, mut line) = lines[i]; if set.left.is_some() { line.left = set.left; } if set.right.is_some() { line.right = set.right; } if set.intersection.is_some() { line.intersection = set.intersection; } if set.main.is_some() { line.main = set.main; } buf[i].0 = index; buf[i].1 = line; i += 1; } buf } const fn harr_unset(lines: HArray, set: HLine) -> HArray { let mut buf = [(0, HLine::empty()); N]; let mut i = 0; while i < N { let (index, mut line) = lines[i]; if set.left.is_some() { line.left = None; } if set.right.is_some() { line.right = None; } if set.intersection.is_some() { line.intersection = None; } if set.main.is_some() { line.main = None; } buf[i].0 = index; buf[i].1 = line; i += 1; } buf } const fn varr_set(lines: VArray, set: VLine) -> VArray { let mut buf = [(0, VLine::empty()); N]; let mut i = 0; while i < N { let (index, mut line) = lines[i]; if set.top.is_some() { line.top = set.top; } if set.bottom.is_some() { line.bottom = set.bottom; } if set.intersection.is_some() { line.intersection = set.intersection; } if set.main.is_some() { line.main = set.main; } buf[i].0 = index; buf[i].1 = line; i += 1; } buf } const fn varr_unset(lines: VArray, set: VLine) -> VArray { let mut buf = [(0, VLine::empty()); N]; let mut i = 0; while i < N { let (index, mut line) = lines[i]; if set.top.is_some() { line.top = None; } if set.bottom.is_some() { line.bottom = None; } if set.intersection.is_some() { line.intersection = None; } if set.main.is_some() { line.main = None; } buf[i].0 = index; buf[i].1 = line; i += 1; } buf } const fn create_borders( top: HLine, bottom: HLine, horizontal: HLine, left: Option, right: Option, vertical: Option, ) -> Borders { Borders { top: top.main, top_left: top.left, top_right: top.right, top_intersection: top.intersection, bottom: bottom.main, bottom_left: bottom.left, bottom_right: bottom.right, bottom_intersection: bottom.intersection, left_intersection: horizontal.left, right_intersection: horizontal.right, horizontal: horizontal.main, intersection: horizontal.intersection, left, right, vertical, } } #[cfg(feature = "std")] fn cfg_set_custom_lines( cfg: &mut ColoredConfig, hlines: &[(usize, HLine)], vlines: &[(usize, VLine)], ) { for &(row, line) in hlines { cfg.insert_horizontal_line(row, line); } for &(col, line) in vlines { cfg.insert_vertical_line(col, line); } } #[cfg(feature = "std")] fn cfg_clear_borders(cfg: &mut ColoredConfig) { cfg.remove_borders(); cfg.remove_vertical_chars(); cfg.remove_horizontal_chars(); cfg.remove_borders_colors(); cfg.remove_color_line_horizontal(); cfg.remove_color_line_vertical(); } tabled-0.18.0/src/settings/style/horizontal_line.rs000064400000000000000000000133061046102023000205050ustar 00000000000000use core::marker::PhantomData; use crate::grid::config::HorizontalLine as Line; use crate::grid::config::VerticalLine; use crate::settings::style::On; use crate::settings::Style; /// A horizontal split line which can be used to set a border. #[derive(Debug, Clone, Copy, Default, Hash, PartialEq, Eq, PartialOrd, Ord)] pub struct HorizontalLine { line: Line, _left: PhantomData, _right: PhantomData, _intersection: PhantomData, } impl HorizontalLine<(), (), ()> { /// Creates a new horizontal split line. pub const fn new(main: char) -> Self { Self::update(Line::new(Some(main), None, None, None)) } } impl HorizontalLine { /// Creates a stub horizontal line. pub const fn empty() -> Self { Self::update(Line::new(None, None, None, None)) } /// Fetches vertical line from a style. pub const fn inherit( style: Style, ) -> Self { let borders = style.get_borders(); let line = Line::new( borders.horizontal, borders.intersection, borders.left_intersection, borders.right_intersection, ); Self::update(line) } /// Fetches left vertical line from a style. pub const fn inherit_top( style: Style, ) -> Self { let borders = style.get_borders(); let line = Line::new( borders.top, borders.top_intersection, borders.top_left, borders.top_right, ); Self::update(line) } /// Fetches right vertical line from a style. pub const fn inherit_bottom( style: Style, ) -> Self { let borders = style.get_borders(); let line = Line::new( borders.bottom, borders.bottom_intersection, borders.bottom_left, borders.bottom_right, ); Self::update(line) } } impl HorizontalLine { /// Creates a new horizontal split line. pub const fn full(main: char, intersection: char, left: char, right: char) -> Self { let line = Line::new(Some(main), Some(intersection), Some(left), Some(right)); Self::update(line) } /// Creates a new horizontal split line. pub const fn filled(main: char) -> Self { Self::full(main, main, main, main) } } impl HorizontalLine { pub(crate) const fn update(line: Line) -> HorizontalLine { Self { line, _left: PhantomData, _right: PhantomData, _intersection: PhantomData, } } /// Set a horizontal character. pub const fn horizontal(mut self, c: char) -> HorizontalLine { self.line.main = Some(c); HorizontalLine::update(self.line) } /// Set a vertical intersection character. pub const fn intersection(mut self, c: char) -> HorizontalLine { self.line.intersection = Some(c); HorizontalLine::update(self.line) } /// Set a left character. pub const fn left(mut self, c: char) -> HorizontalLine { self.line.left = Some(c); HorizontalLine::update(self.line) } /// Set a right character. pub const fn right(mut self, c: char) -> HorizontalLine { self.line.right = Some(c); HorizontalLine::update(self.line) } } impl HorizontalLine { /// Get a horizontal character. pub const fn get_horizontal(&self) -> char { opt_get(self.line.main) } /// Get a general structure of line. pub const fn into_inner(&self) -> Line { self.line } } impl HorizontalLine { /// Set a vertical intersection character. pub const fn get_intersection(&self) -> char { opt_get(self.line.intersection) } /// Remove a vertical intersection character. pub const fn remove_intersection(mut self) -> HorizontalLine { self.line.intersection = None; HorizontalLine::update(self.line) } } impl HorizontalLine { /// Get a left character. pub const fn get_left(&self) -> char { opt_get(self.line.left) } /// Remove a horizontal left character. pub const fn remove_left(mut self) -> HorizontalLine<(), R, I> { self.line.left = None; HorizontalLine::update(self.line) } } impl HorizontalLine { /// Get a right character. pub const fn get_right(&self) -> char { opt_get(self.line.right) } /// Remove a horizontal right character. pub const fn remove_right(mut self) -> HorizontalLine { self.line.right = None; HorizontalLine::update(self.line) } } impl From> for Line { fn from(value: HorizontalLine) -> Self { value.line } } impl From> for HorizontalLine { fn from(value: Line) -> Self { let mut line = Self::empty(); line.line = value; line } } impl From> for VerticalLine { fn from(value: HorizontalLine) -> Self { VerticalLine::new( value.line.main, value.line.intersection, value.line.left, value.line.right, ) } } const fn opt_get(opt: Option) -> char { match opt { Some(value) => value, None => unreachable!(), } } tabled-0.18.0/src/settings/style/line_char.rs000064400000000000000000000051051046102023000172270ustar 00000000000000use crate::{ grid::config::{ColoredConfig, Entity, Position, SpannedConfig}, grid::records::{ExactRecords, Records}, settings::CellOption, }; use super::Offset; /// [`LineChar`] sets a char to a specific location on a horizontal line. /// /// # Example /// /// ```rust /// use tabled::{Table, settings::{style::{Style, LineChar, Offset}, Modify, object::{Object, Rows, Columns}}}; /// /// let mut table = Table::new(["Hello World"]); /// table /// .with(Style::markdown()) /// .with(Modify::new(Rows::single(1)) /// .with(LineChar::horizontal(':', Offset::Begin(0))) /// .with(LineChar::horizontal(':', Offset::End(0))) /// ) /// .with(Modify::new((1, 0).and((1, 1))).with(LineChar::vertical('#', Offset::Begin(0)))); /// /// assert_eq!( /// table.to_string(), /// concat!( /// "| &str |\n", /// "|:-----------:|\n", /// "# Hello World #", /// ), /// ); /// ``` #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] pub struct LineChar { c: char, offset: Offset, horizontal: bool, } impl LineChar { /// Creates a [`LineChar`] which overrides horizontal line. pub fn horizontal(c: char, offset: impl Into) -> Self { let offset = offset.into(); let horizontal = true; Self { c, offset, horizontal, } } /// Creates a [`LineChar`] which overrides vertical line. pub fn vertical(c: char, offset: impl Into) -> Self { let offset = offset.into(); let horizontal = false; Self { c, offset, horizontal, } } } impl CellOption for LineChar where R: Records + ExactRecords, { fn change(self, records: &mut R, cfg: &mut ColoredConfig, entity: Entity) { let cells = entity.iter(records.count_rows(), records.count_columns()); match self.horizontal { true => add_char_horizontal(cfg, self.c, self.offset, cells), false => add_char_vertical(cfg, self.c, self.offset, cells), } } } fn add_char_vertical>( cfg: &mut SpannedConfig, c: char, offset: Offset, cells: I, ) { let offset = offset.into(); for pos in cells { cfg.set_vertical_char(pos, c, offset); } } fn add_char_horizontal>( cfg: &mut SpannedConfig, c: char, offset: Offset, cells: I, ) { let offset = offset.into(); for pos in cells { cfg.set_horizontal_char(pos, c, offset); } } tabled-0.18.0/src/settings/style/line_text.rs000064400000000000000000000422121046102023000172760ustar 00000000000000use papergrid::config::{AlignmentHorizontal, AlignmentVertical}; use crate::{ grid::{ ansi::ANSIBuf, config::{self, ColoredConfig, Entity, SpannedConfig}, dimension::{Dimension, Estimate}, records::{ExactRecords, Records}, util::string::get_text_width, }, settings::{ object::{ Column, FirstColumn, FirstRow, LastColumn, LastColumnOffset, LastRow, LastRowOffset, Object, Row, }, Alignment, Color, TableOption, }, }; use super::Offset; /// [`LineText`] writes a custom text on a border. /// /// # Example /// /// ```rust /// use tabled::{Table, settings::style::LineText, settings::object::Rows}; /// /// let mut table = Table::new(["Hello World"]); /// table.with(LineText::new("+-.table", Rows::first())); /// /// assert_eq!( /// table.to_string(), /// "+-.table------+\n\ /// | &str |\n\ /// +-------------+\n\ /// | Hello World |\n\ /// +-------------+" /// ); /// ``` #[derive(Debug)] pub struct LineText { // todo: change to T and specify to be As text: String, offset: Offset, color: Option, alignment: Option, line: Line, } impl LineText { /// Creates a [`LineText`] instance. /// /// Line can be a column or a row. /// Lines are numbered from 0 to the `count_rows`/`count_columns` included: /// (`line >= 0 && line <= count_rows`) /// (`line >= 0 && line <= count_columns`). /// /// ``` /// use tabled::{Table, settings::style::LineText, settings::object::Columns}; /// /// let mut table = Table::new(["Hello World"]); /// table.with(LineText::new("TABLE", Columns::single(0))); /// table.with(LineText::new("TABLE", Columns::single(1))); /// /// assert_eq!( /// table.to_string(), /// "T-------------T\n\ /// A &str A\n\ /// B-------------B\n\ /// L Hello World L\n\ /// E-------------E" /// ); /// ``` pub fn new(text: S, line: Line) -> Self where S: Into, { LineText { line, text: text.into(), offset: Offset::Begin(0), color: None, alignment: None, } } /// Set an offset from which the text will be started. /// /// ``` /// use tabled::Table; /// use tabled::settings::{Alignment, style::LineText, object::Rows}; /// /// let mut table = Table::new(["Hello World"]); /// table.with(LineText::new("TABLE", Rows::first()).align(Alignment::center())); /// /// assert_eq!( /// table.to_string(), /// "+----TABLE----+\n\ /// | &str |\n\ /// +-------------+\n\ /// | Hello World |\n\ /// +-------------+" /// ); /// ``` pub fn align(mut self, alignment: Alignment) -> Self { self.alignment = Some(alignment); self } /// Set an offset from which the text will be started. /// /// ``` /// use tabled::{Table, settings::style::LineText, settings::object::Rows}; /// /// let mut table = Table::new(["Hello World"]); /// table.with(LineText::new("TABLE", Rows::first()).offset(3)); /// /// assert_eq!( /// table.to_string(), /// "+--TABLE------+\n\ /// | &str |\n\ /// +-------------+\n\ /// | Hello World |\n\ /// +-------------+" /// ); /// ``` pub fn offset(mut self, offset: impl Into) -> Self { self.offset = offset.into(); self } /// Set a color of the text. /// /// ``` /// use tabled::Table; /// use tabled::settings::{object::Rows, Color, style::LineText}; /// /// let mut table = Table::new(["Hello World"]); /// table.with(LineText::new("TABLE", Rows::first()).color(Color::FG_BLUE)); /// /// assert_eq!( /// table.to_string(), /// "\u{1b}[34mT\u{1b}[39m\u{1b}[34mA\u{1b}[39m\u{1b}[34mB\u{1b}[39m\u{1b}[34mL\u{1b}[39m\u{1b}[34mE\u{1b}[39m---------+\n\ /// | &str |\n\ /// +-------------+\n\ /// | Hello World |\n\ /// +-------------+" /// ); /// ``` pub fn color(mut self, color: Color) -> Self { self.color = Some(color.into()); self } } impl TableOption for LineText where R: Records + ExactRecords, for<'a> &'a R: Records, for<'a> D: Estimate<&'a R, ColoredConfig>, D: Dimension, { fn change(self, records: &mut R, cfg: &mut ColoredConfig, dims: &mut D) { let line = self.line.into(); change_horizontal_chars(records, dims, cfg, create_line(self, line)) } } impl TableOption for LineText where R: Records + ExactRecords, for<'a> &'a R: Records, for<'a> D: Estimate<&'a R, ColoredConfig>, D: Dimension, { fn change(self, records: &mut R, cfg: &mut ColoredConfig, dims: &mut D) { change_horizontal_chars(records, dims, cfg, create_line(self, 0)) } } impl TableOption for LineText where R: Records + ExactRecords, for<'a> &'a R: Records, for<'a> D: Estimate<&'a R, ColoredConfig>, D: Dimension, { fn change(self, records: &mut R, cfg: &mut ColoredConfig, dims: &mut D) { let line = self.line.cells(records).next(); if let Some(Entity::Row(line)) = line { change_horizontal_chars(records, dims, cfg, create_line(self, line)) } } } impl TableOption for LineText where R: Records + ExactRecords, for<'a> &'a R: Records, for<'a> D: Estimate<&'a R, ColoredConfig>, D: Dimension, { fn change(self, records: &mut R, cfg: &mut ColoredConfig, dims: &mut D) { let line = self.line.cells(records).next(); if let Some(Entity::Row(line)) = line { change_horizontal_chars(records, dims, cfg, create_line(self, line)) } } } impl TableOption for LineText where R: Records + ExactRecords, for<'a> &'a R: Records, for<'a> D: Estimate<&'a R, ColoredConfig>, D: Dimension, { fn change(self, records: &mut R, cfg: &mut ColoredConfig, dims: &mut D) { let line = self.line.into(); change_vertical_chars(records, dims, cfg, create_line(self, line)) } } impl TableOption for LineText where R: Records + ExactRecords, for<'a> &'a R: Records, for<'a> D: Estimate<&'a R, ColoredConfig>, D: Dimension, { fn change(self, records: &mut R, cfg: &mut ColoredConfig, dims: &mut D) { change_vertical_chars(records, dims, cfg, create_line(self, 0)) } } impl TableOption for LineText where R: Records + ExactRecords, for<'a> &'a R: Records, for<'a> D: Estimate<&'a R, ColoredConfig>, D: Dimension, { fn change(self, records: &mut R, cfg: &mut ColoredConfig, dims: &mut D) { let line = records.count_rows(); change_vertical_chars(records, dims, cfg, create_line(self, line)) } } impl TableOption for LineText where R: Records + ExactRecords, for<'a> &'a R: Records, for<'a> D: Estimate<&'a R, ColoredConfig>, D: Dimension, { fn change(self, records: &mut R, cfg: &mut ColoredConfig, dims: &mut D) { let line = self.line.cells(records).next(); if let Some(Entity::Column(line)) = line { change_vertical_chars(records, dims, cfg, create_line(self, line)) } } } fn set_horizontal_chars( cfg: &mut SpannedConfig, dims: &D, line: LineText, shape: (usize, usize), ) where D: Dimension, { let alignment = line.alignment.and_then(|a| a.as_horizontal()); let offset = line.offset; let text = &line.text; let color = &line.color; let line = line.line; let (_, count_columns) = shape; let total_width = total_width(cfg, dims, count_columns); let offset = match alignment { Some(alignment) => { let off = get_horizontal_alignment_offset(text, alignment, total_width); offset_sum(off, offset) } None => offset, }; let pos = get_start_pos(offset, total_width); let pos = match pos { Some(pos) => pos, None => return, }; let mut chars = text.chars(); let mut i = cfg.has_vertical(0, count_columns) as usize; if i == 1 && pos == 0 { let c = match chars.next() { Some(c) => c, None => return, }; let mut b = cfg.get_border((line, 0).into(), shape); b.left_top_corner = b.left_top_corner.map(|_| c); cfg.set_border((line, 0).into(), b); if let Some(color) = color.as_ref() { let mut b = cfg.get_border_color((line, 0).into(), shape).cloned(); b.left_top_corner = Some(color.clone()); cfg.set_border_color((line, 0).into(), b); } } for col in 0..count_columns { let w = dims.get_width(col); if i + w > pos { for off in 0..w { if i + off < pos { continue; } let c = match chars.next() { Some(c) => c, None => return, }; cfg.set_horizontal_char((line, col).into(), c, config::Offset::Begin(off)); if let Some(color) = color.as_ref() { cfg.set_horizontal_color( (line, col).into(), color.clone(), config::Offset::Begin(off), ); } } } i += w; if cfg.has_vertical(col + 1, count_columns) { i += 1; if i > pos { let c = match chars.next() { Some(c) => c, None => return, }; let mut b = cfg.get_border((line, col).into(), shape); b.right_top_corner = b.right_top_corner.map(|_| c); cfg.set_border((line, col).into(), b); if let Some(color) = color.as_ref() { let mut b = cfg.get_border_color((line, col).into(), shape).cloned(); b.right_top_corner = Some(color.clone()); cfg.set_border_color((line, col).into(), b); } } } } } fn set_vertical_chars( cfg: &mut SpannedConfig, dims: &D, line: LineText, shape: (usize, usize), ) where D: Dimension, { let alignment = line.alignment.and_then(|a| a.as_vertical()); let offset = line.offset; let text = &line.text; let color = &line.color; let line = line.line; let (count_rows, _) = shape; let total_width = total_height(cfg, dims, count_rows); let offset = match alignment { Some(alignment) => { let off = get_vertical_alignment_offset(text, alignment, total_width); offset_sum(off, offset) } None => offset, }; let pos = get_start_pos(offset, total_width); let pos = match pos { Some(pos) => pos, None => return, }; let mut chars = text.chars(); let mut i = cfg.has_horizontal(0, count_rows) as usize; if i == 1 && pos == 0 { let c = match chars.next() { Some(c) => c, None => return, }; let mut b = cfg.get_border((0, line).into(), shape); b.left_top_corner = b.left_top_corner.map(|_| c); cfg.set_border((0, line).into(), b); if let Some(color) = color.as_ref() { let mut b = cfg.get_border_color((0, line).into(), shape).cloned(); b.left_top_corner = Some(color.clone()); cfg.set_border_color((0, line).into(), b); } } for row in 0..count_rows { let row_height = dims.get_height(row); if i + row_height > pos { for off in 0..row_height { if i + off < pos { continue; } let c = match chars.next() { Some(c) => c, None => return, }; cfg.set_vertical_char((row, line).into(), c, config::Offset::Begin(off)); // todo: is this correct? I think it shall be off + i if let Some(color) = color.as_ref() { cfg.set_vertical_color( (row, line).into(), color.clone(), config::Offset::Begin(off), ); } } } i += row_height; if cfg.has_horizontal(row + 1, count_rows) { i += 1; if i > pos { let c = match chars.next() { Some(c) => c, None => return, }; let mut b = cfg.get_border((row, line).into(), shape); b.left_bottom_corner = b.left_bottom_corner.map(|_| c); cfg.set_border((row, line).into(), b); if let Some(color) = color.as_ref() { let mut b = cfg.get_border_color((row, line).into(), shape).cloned(); b.left_bottom_corner = Some(color.clone()); cfg.set_border_color((row, line).into(), b); } } } } } fn get_start_pos(offset: Offset, total: usize) -> Option { match offset { Offset::Begin(i) => { if i > total { None } else { Some(i) } } Offset::End(i) => { if i > total { None } else { Some(total - i) } } } } fn get_horizontal_alignment_offset( text: &str, alignment: AlignmentHorizontal, total: usize, ) -> Offset { match alignment { AlignmentHorizontal::Center => { let width = get_text_width(text); let mut off = 0; if total > width { let center = total / 2; let text_center = width / 2; off = center.saturating_sub(text_center); } Offset::Begin(off) } AlignmentHorizontal::Left => Offset::Begin(0), AlignmentHorizontal::Right => { let width = get_text_width(text); Offset::End(width) } } } fn get_vertical_alignment_offset(text: &str, alignment: AlignmentVertical, total: usize) -> Offset { match alignment { AlignmentVertical::Center => { let width = get_text_width(text); let mut off = 0; if total > width { let center = total / 2; let text_center = width / 2; off = center.saturating_sub(text_center); } Offset::Begin(off) } AlignmentVertical::Top => Offset::Begin(0), AlignmentVertical::Bottom => Offset::End(0), } } fn offset_sum(orig: Offset, and: Offset) -> Offset { match (orig, and) { (Offset::Begin(a), Offset::Begin(b)) => Offset::Begin(a + b), (Offset::Begin(a), Offset::End(b)) => Offset::Begin(a.saturating_sub(b)), (Offset::End(a), Offset::Begin(b)) => Offset::End(a + b), (Offset::End(a), Offset::End(b)) => Offset::End(a.saturating_sub(b)), } } // todo: Can be move all the estimation function to util or somewhere cause I am sure it's not first place it's defined/used. fn total_width(cfg: &SpannedConfig, dims: &D, count_columns: usize) -> usize where D: Dimension, { let mut total = cfg.has_vertical(count_columns, count_columns) as usize; for col in 0..count_columns { total += dims.get_width(col); total += cfg.has_vertical(col, count_columns) as usize; } total } fn total_height(cfg: &SpannedConfig, dims: &D, count_rows: usize) -> usize where D: Dimension, { let mut total = cfg.has_horizontal(count_rows, count_rows) as usize; for row in 0..count_rows { total += dims.get_height(row); total += cfg.has_horizontal(row, count_rows) as usize; } total } fn change_horizontal_chars( records: &mut R, dims: &mut D, cfg: &mut ColoredConfig, line: LineText, ) where R: Records + ExactRecords, for<'a> D: Estimate<&'a R, ColoredConfig>, D: Dimension, { dims.estimate(records, cfg); let shape = (records.count_rows(), records.count_columns()); set_horizontal_chars(cfg, dims, line, shape); } fn change_vertical_chars( records: &mut R, dims: &mut D, cfg: &mut ColoredConfig, line: LineText, ) where R: Records + ExactRecords, for<'a> D: Estimate<&'a R, ColoredConfig>, D: Dimension, { dims.estimate(records, cfg); let shape = (records.count_rows(), records.count_columns()); set_vertical_chars(cfg, dims, line, shape); } fn create_line(orig: LineText, line: usize) -> LineText { LineText { text: orig.text, offset: orig.offset, color: orig.color, alignment: orig.alignment, line, } } tabled-0.18.0/src/settings/style/mod.rs000064400000000000000000000077621046102023000160750ustar 00000000000000//! This module contains a list of primitives which can be applied to change [`Table`] style. //! //! ## [`Style`] //! //! It is responsible for a table border style. //! An individual cell border can be set by [`Border`]. //! //! ### Example //! #![cfg_attr(feature = "std", doc = "```")] #![cfg_attr(not(feature = "std"), doc = "```ignore")] //! use tabled::{Table, settings::Style}; //! //! let data = vec!["Hello", "2022"]; //! let mut table = Table::new(&data); //! table.with(Style::psql()); //! //! assert_eq!( //! table.to_string(), //! concat!( //! " &str \n", //! "-------\n", //! " Hello \n", //! " 2022 ", //! ) //! ) //! ``` //! //! ## [`LineText`] //! //! It's used to override a border with a custom text. //! //! ### Example //! #![cfg_attr(feature = "std", doc = "```")] #![cfg_attr(not(feature = "std"), doc = "```ignore")] //! use tabled::{ //! Table, settings::{style::{LineText, Style}, object::Rows}, //! }; //! //! let data = vec!["Hello", "2022"]; //! let table = Table::new(&data) //! .with(Style::psql()) //! .with(LineText::new("Santa", Rows::single(1))) //! .to_string(); //! //! assert_eq!( //! table, //! concat!( //! " &str \n", //! "Santa--\n", //! " Hello \n", //! " 2022 ", //! ) //! ) //! ``` //! //! ## [`Border`] //! //! [`Border`] can be used to modify cell's borders. //! //! It's possible to set a collored border when `color` feature is on. //! //! ### Example //! #![cfg_attr(feature = "std", doc = "```")] #![cfg_attr(not(feature = "std"), doc = "```ignore")] //! use tabled::{Table, settings::{Modify, Style, style::Border}}; //! //! let data = vec!["Hello", "2022"]; //! let table = Table::new(&data) //! .with(Style::psql()) //! .modify((0, 0), Border::inherit(Style::modern())) //! .to_string(); //! //! assert_eq!( //! table, //! concat!( //! "┌───────┐\n", //! "│ &str │\n", //! "└───────┘\n", //! " Hello \n", //! " 2022 ", //! ) //! ) //! ``` //! //! ## [`Theme`] //! //! A different representation of [`Theme`]. //! With no checks in place. //! //! It also contains a list of types to support colors. //! //! [`Table`]: crate::Table //! [`BorderText`]: crate::settings::style::BorderText //! [`Theme`]: crate::settings::themes::Theme mod border; mod builder; mod horizontal_line; mod offset; mod vertical_line; #[cfg(feature = "std")] mod border_color; #[cfg(feature = "std")] mod line_char; #[cfg(feature = "std")] mod line_text; #[cfg(feature = "std")] mod span_border_correction; #[cfg(feature = "std")] #[cfg_attr(docsrs, doc(cfg(feature = "std")))] pub use self::{ border_color::BorderColor, line_char::LineChar, line_text::LineText, span_border_correction::BorderSpanCorrection, }; pub use self::{ border::Border, builder::{On, Style}, horizontal_line::HorizontalLine, offset::Offset, vertical_line::VerticalLine, }; use crate::grid::config::{Borders, CompactConfig, CompactMultilineConfig}; use crate::settings::TableOption; #[cfg(feature = "std")] use crate::grid::config::ColoredConfig; #[cfg(feature = "std")] impl TableOption for Borders { fn change(self, _: &mut R, cfg: &mut ColoredConfig, _: &mut D) { cfg_clear_borders(cfg); cfg.set_borders(self); } } impl TableOption for Borders { fn change(self, _: &mut R, cfg: &mut CompactConfig, _: &mut D) { *cfg = cfg.set_borders(self); } } impl TableOption for Borders { fn change(self, _: &mut R, cfg: &mut CompactMultilineConfig, _: &mut D) { cfg.set_borders(self); } } #[cfg(feature = "std")] fn cfg_clear_borders(cfg: &mut ColoredConfig) { cfg.remove_borders(); cfg.remove_borders_colors(); cfg.remove_vertical_chars(); cfg.remove_horizontal_chars(); cfg.remove_color_line_horizontal(); cfg.remove_color_line_vertical(); } tabled-0.18.0/src/settings/style/offset.rs000064400000000000000000000024521046102023000165730ustar 00000000000000#[cfg(feature = "std")] use crate::grid::config; /// 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. // todo: rename to start? Begin(usize), /// An offset from the end. End(usize), } #[cfg(feature = "std")] impl From for config::Offset { fn from(o: Offset) -> Self { match o { Offset::Begin(i) => config::Offset::Begin(i), Offset::End(i) => config::Offset::End(i), } } } // todo: Add an example of usage impl From for Offset { fn from(value: isize) -> Self { if value > 0 { Offset::Begin(value as usize) } else { Offset::End((-value) as usize) } } } impl From for Offset { fn from(value: i32) -> Self { if value > 0 { Offset::Begin(value as usize) } else { Offset::End((-value) as usize) } } } impl From for Offset { fn from(value: i64) -> Self { if value > 0 { Offset::Begin(value as usize) } else { Offset::End((-value) as usize) } } } impl From for Offset { fn from(value: usize) -> Self { Offset::Begin(value) } } tabled-0.18.0/src/settings/style/span_border_correction.rs000064400000000000000000000163541046102023000220400ustar 00000000000000//! This module contains [`BorderSpanCorrection`] structure, which can be useful when [`Span`] is used, and //! you want to fix the intersections symbols which are left intact by default. //! //! [`Span`]: crate::settings::span::Span use crate::{ grid::{ config::{ColoredConfig, Position, SpannedConfig}, records::{ExactRecords, Records}, }, settings::TableOption, }; /// A correctness function of style for [`Table`] which has [`Span`]s. /// /// Try to fix the style when table contains spans. /// /// By default [`Style`] doesn't implies any logic to better render split lines when /// [`Span`] is used. /// /// So this function can be used to set the split lines in regard of spans used. /// /// # Example /// /// ``` /// use tabled::{ /// Table, /// settings::{ /// Modify, style::{Style, BorderSpanCorrection}, /// Format, Span, object::Cell /// } /// }; /// /// let data = vec![ /// ("09", "June", "2022"), /// ("10", "July", "2022"), /// ]; /// /// let mut table = Table::new(data); /// table.with(Modify::new((0, 0)).with("date").with(Span::column(3))); /// /// assert_eq!( /// table.to_string(), /// concat!( /// "+----+------+------+\n", /// "| date |\n", /// "+----+------+------+\n", /// "| 09 | June | 2022 |\n", /// "+----+------+------+\n", /// "| 10 | July | 2022 |\n", /// "+----+------+------+", /// ) /// ); /// /// table.with(BorderSpanCorrection); /// /// assert_eq!( /// table.to_string(), /// concat!( /// "+------------------+\n", /// "| date |\n", /// "+----+------+------+\n", /// "| 09 | June | 2022 |\n", /// "+----+------+------+\n", /// "| 10 | July | 2022 |\n", /// "+----+------+------+", /// ) /// ); /// ``` /// See [`BorderSpanCorrection`]. /// /// [`Table`]: crate::Table /// [`Span`]: crate::settings::span::Span /// [`Style`]: crate::settings::Style /// [`Style::correct_spans`]: crate::settings::style::BorderSpanCorrection #[derive(Debug)] pub struct BorderSpanCorrection; impl TableOption for BorderSpanCorrection where R: Records + ExactRecords, { fn change(self, records: &mut R, cfg: &mut ColoredConfig, _: &mut D) { let shape = (records.count_rows(), records.count_columns()); correct_span_styles(cfg, shape); } } fn correct_span_styles(cfg: &mut SpannedConfig, shape: (usize, usize)) { for (p, span) in cfg.get_column_spans() { for col in p.col()..p.col() + span { if col == 0 { continue; } let is_first = col == p.col(); let has_up = p.row() > 0 && has_left(cfg, (p.row() - 1, col).into(), shape); let has_down = p.row() + 1 < shape.0 && has_left(cfg, (p.row() + 1, col).into(), shape); let borders = cfg.get_borders(); let mut border = cfg.get_border((p.row(), col).into(), shape); let has_top_border = border.left_top_corner.is_some() && border.top.is_some(); if has_top_border { if has_up && is_first { border.left_top_corner = borders.intersection; } else if has_up { border.left_top_corner = borders.bottom_intersection; } else if is_first { border.left_top_corner = borders.top_intersection; } else { border.left_top_corner = border.top; } } let has_bottom_border = border.left_bottom_corner.is_some() && border.bottom.is_some(); if has_bottom_border { if has_down && is_first { border.left_bottom_corner = borders.intersection; } else if has_down { border.left_bottom_corner = borders.top_intersection; } else if is_first { border.left_bottom_corner = borders.bottom_intersection; } else { border.left_bottom_corner = border.bottom; } } cfg.set_border((p.row(), col).into(), border); } } for (p, span) in cfg.get_row_spans() { let (r, col) = p.into(); for row in r + 1..r + span { let mut border = cfg.get_border((row, col).into(), shape); let borders = cfg.get_borders(); let has_left_border = border.left_top_corner.is_some(); if has_left_border { let has_left = col > 0 && has_top(cfg, (row, col - 1).into(), shape); if has_left { border.left_top_corner = borders.right_intersection; } else { border.left_top_corner = borders.vertical; } } let has_right_border = border.right_top_corner.is_some(); if has_right_border { let has_right = col + 1 < shape.1 && has_top(cfg, (row, col + 1).into(), shape); if has_right { border.right_top_corner = borders.left_intersection; } else { border.right_top_corner = borders.vertical; } } cfg.set_border((row, col).into(), border); } } let cells = iter_totally_spanned_cells(cfg, shape).collect::>(); for p in cells { let (row, col) = p.into(); if row == 0 { continue; } let mut border = cfg.get_border((row, col).into(), shape); let borders = cfg.get_borders(); let has_right = col + 1 < shape.1 && has_top(cfg, (row, col + 1).into(), shape); let has_up = has_left(cfg, (row - 1, col).into(), shape); if has_up && !has_right { border.right_top_corner = borders.right_intersection; } if !has_up && has_right { border.right_top_corner = borders.left_intersection; } let has_down = row + 1 < shape.0 && has_left(cfg, (row + 1, col).into(), shape); if has_down { border.left_bottom_corner = borders.top_intersection; } cfg.set_border((row, col).into(), border); } } fn has_left(cfg: &SpannedConfig, pos: Position, shape: (usize, usize)) -> bool { if cfg.is_cell_covered_by_both_spans(pos) || cfg.is_cell_covered_by_column_span(pos) { return false; } let border = cfg.get_border(pos, shape); border.left.is_some() || border.left_top_corner.is_some() || border.left_bottom_corner.is_some() } fn has_top(cfg: &SpannedConfig, pos: Position, shape: (usize, usize)) -> bool { if cfg.is_cell_covered_by_both_spans(pos) || cfg.is_cell_covered_by_row_span(pos) { return false; } let border = cfg.get_border(pos, shape); border.top.is_some() || border.left_top_corner.is_some() || border.right_top_corner.is_some() } fn iter_totally_spanned_cells( cfg: &SpannedConfig, shape: (usize, usize), ) -> impl Iterator + '_ { // todo: can be optimized let (count_rows, count_cols) = shape; (0..count_rows).flat_map(move |row| { (0..count_cols) .map(move |col| (row, col).into()) .filter(move |p| cfg.is_cell_covered_by_both_spans(*p)) }) } tabled-0.18.0/src/settings/style/vertical_line.rs000064400000000000000000000134161046102023000201270ustar 00000000000000use core::marker::PhantomData; use crate::grid::config::HorizontalLine; use crate::grid::config::VerticalLine as Line; use crate::settings::style::On; use crate::settings::Style; /// A vertical split line which can be used to set a border. #[derive(Debug, Clone, Copy, Default, Hash, PartialEq, Eq, PartialOrd, Ord)] pub struct VerticalLine { line: Line, _top: PhantomData, _bottom: PhantomData, _intersection: PhantomData, } impl VerticalLine<(), (), ()> { /// Creates a new vertical split line. pub const fn new(main: char) -> Self { Self::update(Line::new(Some(main), None, None, None)) } } impl VerticalLine { /// Creates a stub horizontal line. pub const fn empty() -> Self { Self::update(Line::empty()) } /// Fetches vertical line from a style. pub const fn inherit( style: Style, ) -> Self { let borders = style.get_borders(); let line = Line::new( borders.vertical, borders.intersection, borders.top_intersection, borders.bottom_intersection, ); Self::update(line) } /// Fetches left vertical line from a style. pub const fn inherit_left( style: Style, ) -> Self { let borders = style.get_borders(); let line = Line::new( borders.left, borders.left_intersection, borders.top_left, borders.bottom_left, ); Self::update(line) } /// Fetches right vertical line from a style. pub const fn inherit_right( style: Style, ) -> Self { let borders = style.get_borders(); let line = Line::new( borders.right, borders.right_intersection, borders.top_right, borders.bottom_right, ); Self::update(line) } } impl VerticalLine { /// Creates a new vertical split line. pub const fn full(main: char, intersection: char, top: char, bottom: char) -> Self { Self::update(Line::new( Some(main), Some(intersection), Some(top), Some(bottom), )) } /// Creates a new vertical split line. pub const fn filled(main: char) -> Self { Self::full(main, main, main, main) } } impl VerticalLine { /// Set a vertical character. pub const fn vertical(mut self, c: char) -> VerticalLine { self.line.main = Some(c); VerticalLine::update(self.line) } /// Set a vertical intersection character. pub const fn intersection(mut self, c: char) -> VerticalLine { self.line.intersection = Some(c); VerticalLine::update(self.line) } /// Set a top character. pub const fn top(mut self, c: char) -> VerticalLine { self.line.top = Some(c); VerticalLine::update(self.line) } /// Set a bottom character. pub const fn bottom(mut self, c: char) -> VerticalLine { self.line.bottom = Some(c); VerticalLine::update(self.line) } } impl VerticalLine { pub(crate) const fn update(line: Line) -> VerticalLine { Self { line, _top: PhantomData, _bottom: PhantomData, _intersection: PhantomData, } } /// Get a vertical character. pub const fn get_vertical(&self) -> char { match self.line.main { Some(c) => c, None => unreachable!(), } } /// Get a general structure of line. pub const fn into_inner(&self) -> Line { self.line } } impl VerticalLine { /// Set a horizontal intersection character. pub const fn get_intersection(&self) -> char { match self.line.intersection { Some(c) => c, None => unreachable!(), } } /// Remove a horizontal intersection character. pub const fn remove_intersection(mut self) -> VerticalLine { self.line.intersection = None; VerticalLine::update(self.line) } } impl VerticalLine { /// Get a top character. pub const fn get_top(&self) -> char { opt_get(self.line.top) } /// Remove a vertical top character. pub const fn remove_top(mut self) -> VerticalLine<(), B, I> { self.line.top = None; VerticalLine::update(self.line) } } impl VerticalLine { /// Get a bottom character. pub const fn get_bottom(&self) -> char { opt_get(self.line.bottom) } /// Remove a vertical bottom character. pub const fn remove_bottom(mut self) -> VerticalLine { self.line.bottom = None; VerticalLine::update(self.line) } } impl From> for Line { fn from(value: VerticalLine) -> Self { value.line } } impl From> for HorizontalLine { fn from(value: VerticalLine) -> Self { HorizontalLine::new( value.line.main, value.line.intersection, value.line.top, value.line.bottom, ) } } impl From> for VerticalLine { fn from(value: Line) -> Self { let mut line = Self::empty(); line.line = value; line } } const fn opt_get(opt: Option) -> char { match opt { Some(value) => value, None => unreachable!(), } } tabled-0.18.0/src/settings/table_option.rs000064400000000000000000000110171046102023000166210ustar 00000000000000use crate::grid::config::Entity; /// A trait which is responsible for configuration of a [`Table`]. /// /// [`Table`]: crate::Table pub trait TableOption { /// The function modificaties of records and a grid configuration. fn change(self, records: &mut R, cfg: &mut C, dimension: &mut D); /// A hint whether an [`TableOption`] is going to change table layout. /// /// Return [`None`] if no changes are being done. /// Otherwise return: /// /// - [Entity::Global] - a grand layout changed. (a change which MIGHT require height/width update) /// - [Entity::Row] - a certain row was changed. (a change which MIGHT require height update) /// - [Entity::Column] - a certain column was changed. (a change which MIGHT require width update) /// - [Entity::Cell] - a certain cell was changed. (a local change, no width/height update) /// /// By default it's considered to be a grand change. /// /// This methods primarily is used as an optimization, /// to not make unnecessary calculations if they're not needed, after using the [`TableOption`]. fn hint_change(&self) -> Option { Some(Entity::Global) } } // todo: probably we could add one more hint but it likely require Vec, // so, as I am not sure about exact interface it's better be commented. // /// A hint which layout part a [`TableOption`] is going to change. // /// // /// Return [`None`] if no part are being changed. // /// Otherwise return: // /// // /// - [Entity::Global] - a total layout affection. // /// - [Entity::Row] - a certain row affection. // /// - [Entity::Column] - a certain column affection. // /// - [Entity::Cell] - a certain cell affection. // /// // /// By default it's considered to be a grand change. // /// // /// This methods primarily is used as an optimization, // /// to not make unnecessary calculations if they're not needed, after using the [`TableOption`]. // fn hint_target(&self, records: &R) -> Option> { // let _ = records; // Some(vec![Entity::Global]) // } impl TableOption for &[T] where for<'a> &'a T: TableOption, { fn change(self, records: &mut R, cfg: &mut C, dimension: &mut D) { for opt in self { opt.change(records, cfg, dimension) } } } #[cfg(feature = "std")] impl TableOption for Vec where T: TableOption, { fn change(self, records: &mut R, cfg: &mut C, dimension: &mut D) { for opt in self { opt.change(records, cfg, dimension) } } } #[cfg(feature = "std")] macro_rules! tuple_trait_impl { ( $($name:ident)+ ) => { impl),+> TableOption for ($($name,)+) { fn change(self, records: &mut R, cfg: &mut C, dimension: &mut D) { #![allow(non_snake_case)] let ($($name,)+) = self; $( $name::change($name, records, cfg, dimension); )+ } fn hint_change(&self) -> Option { #![allow(non_snake_case)] let ($($name,)+) = &self; let list = [ $( $name::hint_change($name), )+ ]; hint_change_list(&list) } } }; } #[cfg(feature = "std")] tuple_trait_impl!(T0 T1); #[cfg(feature = "std")] tuple_trait_impl!(T0 T1 T2); #[cfg(feature = "std")] tuple_trait_impl!(T0 T1 T2 T3); #[cfg(feature = "std")] tuple_trait_impl!(T0 T1 T2 T3 T4); #[cfg(feature = "std")] tuple_trait_impl!(T0 T1 T2 T3 T4 T5); #[cfg(feature = "std")] tuple_trait_impl!(T0 T1 T2 T3 T4 T5 T6); #[cfg(feature = "std")] tuple_trait_impl!(T0 T1 T2 T3 T4 T5 T6 T7); #[cfg(feature = "std")] tuple_trait_impl!(T0 T1 T2 T3 T4 T5 T6 T7 T8); #[cfg(feature = "std")] tuple_trait_impl!(T0 T1 T2 T3 T4 T5 T6 T7 T8 T9); #[cfg(feature = "std")] pub(crate) fn hint_change_list(list: &[Option]) -> Option { let mut entries = vec![]; for e in list.iter().flatten() { entries.push(*e); } if entries.is_empty() { return None; } Some(combine_entity_list(&entries)) } #[cfg(feature = "std")] pub(crate) fn combine_entity_list(list: &[Entity]) -> Entity { if list.is_empty() { // must never happen return Entity::Global; } let mut entity = list[0]; for e in &list[1..] { entity = crate::settings::settings_list::combine_entity(entity, *e); } entity } tabled-0.18.0/src/settings/themes/colorization.rs000064400000000000000000000260251046102023000201500ustar 00000000000000use crate::{ grid::{ ansi::ANSIBuf, config::{ColoredConfig, Entity, Sides}, records::{ExactRecords, Records}, }, settings::{object::Object, Color, TableOption}, }; /// [`Colorization`] sets a color for the whole table data (so it's not include the borders). /// /// You can colorize borders in a different round using [`BorderColor`] or [`Theme`] /// /// # Examples /// /// ``` /// use std::iter::FromIterator; /// /// use tabled::builder::Builder; /// use tabled::settings::{style::BorderColor, themes::Colorization, Color, Style}; /// /// let data = [["Hello", "World"], ["Hi", "World"], ["Halo", "World"]]; /// /// let color1 = Color::FG_BLACK | Color::BG_WHITE; /// let color2 = Color::BG_BLACK | Color::FG_WHITE; /// let color3 = Color::FG_RED | Color::BG_RED; /// /// let mut table = Builder::from_iter(data).build(); /// table /// .with(Colorization::chess(color1, color2)) /// .with(Style::modern()) /// .with(BorderColor::filled(color3)); /// /// println!("{table}"); /// ``` /// /// [`Theme`]: crate::settings::themes::Theme /// [`BorderColor`]: crate::settings::style::BorderColor #[derive(Debug, Clone, PartialEq, Eq)] pub struct Colorization { pattern: ColorizationPattern, colors: Vec, } #[derive(Debug, Clone, PartialEq, Eq)] enum ColorizationPattern { Column, Row, ByRow, ByColumn, Chess, } impl Colorization { /// Creates a [`Colorization`] with a chess pattern. /// /// ``` /// use std::iter::FromIterator; /// /// use tabled::builder::Builder; /// use tabled::settings::{themes::Colorization, Color, Style}; /// /// let data = [["Hello", "World"], ["Hi", "World"], ["Halo", "World"]]; /// /// let color1 = Color::FG_BLACK | Color::BG_WHITE; /// let color2 = Color::BG_BLACK | Color::FG_WHITE; /// /// let mut table = Builder::from_iter(data).build(); /// table /// .with(Colorization::chess(color1, color2)) /// .with(Style::empty()); /// /// println!("{table}"); /// ``` pub fn chess(white: Color, black: Color) -> Self { Self::new(vec![white, black], ColorizationPattern::Chess) } /// Creates a [`Colorization`] with a target [`Object`]. /// /// ``` /// use std::iter::FromIterator; /// /// use tabled::builder::Builder; /// use tabled::settings::object::Rows; /// use tabled::settings::{themes::Colorization, Color, Style}; /// /// let data = [["Hello", "World"], ["Hi", "World"], ["Halo", "World"]]; /// /// let color1 = Color::FG_BLACK | Color::BG_WHITE; /// let color2 = Color::BG_BLACK | Color::FG_WHITE; /// /// let mut table = Builder::from_iter(data).build(); /// table /// .with(Colorization::exact([color1, color2], Rows::first())) /// .with(Style::empty()); /// /// println!("{table}"); /// ``` pub fn exact(colors: I, target: O) -> ExactColorization where I: IntoIterator, I::Item: Into, { let colors = colors.into_iter().map(Into::into).collect(); ExactColorization::new(colors, target) } /// Creates a [`Colorization`] with a pattern which changes row by row. /// /// ``` /// use std::iter::FromIterator; /// /// use tabled::builder::Builder; /// use tabled::settings::object::Rows; /// use tabled::settings::{themes::Colorization, Color, Style}; /// /// let data = [["Hello", "World"], ["Hi", "World"], ["Halo", "World"]]; /// /// let color1 = Color::FG_BLACK | Color::BG_WHITE; /// let color2 = Color::BG_BLACK | Color::FG_WHITE; /// /// let mut table = Builder::from_iter(data).build(); /// table /// .with(Colorization::rows([color1, color2])) /// .with(Style::empty()); /// /// println!("{table}"); /// ``` pub fn rows(colors: I) -> Self where I: IntoIterator, I::Item: Into, { Self::new(colors, ColorizationPattern::Row) } /// Creates a [`Colorization`] with a pattern which changes column by column. /// /// ``` /// use std::iter::FromIterator; /// /// use tabled::builder::Builder; /// use tabled::settings::object::Rows; /// use tabled::settings::{themes::Colorization, Color, Style}; /// /// let data = [["Hello", "World"], ["Hi", "World"], ["Halo", "World"]]; /// /// let color1 = Color::FG_BLACK | Color::BG_WHITE; /// let color2 = Color::BG_BLACK | Color::FG_WHITE; /// /// let mut table = Builder::from_iter(data).build(); /// table /// .with(Colorization::columns([color1, color2])) /// .with(Style::empty()); /// /// println!("{table}"); /// ``` pub fn columns(colors: I) -> Self where I: IntoIterator, I::Item: Into, { Self::new(colors, ColorizationPattern::Column) } /// Creates a [`Colorization`] with a pattern which peaks cells one by one iterating over rows. /// /// ``` /// use std::iter::FromIterator; /// /// use tabled::builder::Builder; /// use tabled::settings::object::Rows; /// use tabled::settings::{themes::Colorization, Color, Style}; /// /// let data = [["Hello", "World"], ["Hi", "World"], ["Halo", "World"]]; /// /// let color1 = Color::FG_BLACK | Color::BG_WHITE; /// let color2 = Color::BG_BLACK | Color::FG_WHITE; /// /// let mut table = Builder::from_iter(data).build(); /// table /// .with(Colorization::by_row([color1, color2])) /// .with(Style::empty()); /// /// println!("{table}"); /// ``` pub fn by_row(colors: I) -> Self where I: IntoIterator, I::Item: Into, { Self::new(colors, ColorizationPattern::ByRow) } /// Creates a [`Colorization`] with a pattern which peaks cells one by one iterating over columns. /// /// ``` /// use std::iter::FromIterator; /// /// use tabled::builder::Builder; /// use tabled::settings::object::Rows; /// use tabled::settings::{themes::Colorization, Color, Style}; /// /// let data = [["Hello", "World"], ["Hi", "World"], ["Halo", "World"]]; /// /// let color1 = Color::FG_BLACK | Color::BG_WHITE; /// let color2 = Color::BG_BLACK | Color::FG_WHITE; /// /// let mut table = Builder::from_iter(data).build(); /// table /// .with(Colorization::by_column([color1, color2])) /// .with(Style::empty()); /// /// println!("{table}"); /// ``` pub fn by_column(colors: I) -> Self where I: IntoIterator, I::Item: Into, { Self::new(colors, ColorizationPattern::ByColumn) } fn new(colors: I, pattern: ColorizationPattern) -> Self where I: IntoIterator, I::Item: Into, { let colors = colors.into_iter().map(Into::into).collect(); Self { colors, pattern } } } impl TableOption for Colorization where R: Records + ExactRecords, { fn change(self, records: &mut R, cfg: &mut ColoredConfig, _: &mut D) { if self.colors.is_empty() { return; } let count_columns = records.count_columns(); let count_rows = records.count_rows(); match self.pattern { ColorizationPattern::Column => colorize_columns(&self.colors, count_columns, cfg), ColorizationPattern::Row => colorize_rows(&self.colors, count_rows, cfg), ColorizationPattern::ByRow => { colorize_by_row(&self.colors, count_rows, count_columns, cfg) } ColorizationPattern::ByColumn => { colorize_by_column(&self.colors, count_rows, count_columns, cfg) } ColorizationPattern::Chess => { colorize_diogonals(&self.colors, count_rows, count_columns, cfg) } } } } fn colorize_columns(colors: &[Color], count_columns: usize, cfg: &mut ColoredConfig) { for (col, color) in (0..count_columns).zip(colors.iter().cycle()) { colorize_entity(color, Entity::Column(col), cfg); } } fn colorize_rows(colors: &[Color], count_rows: usize, cfg: &mut ColoredConfig) { for (row, color) in (0..count_rows).zip(colors.iter().cycle()) { colorize_entity(color, Entity::Row(row), cfg); } } fn colorize_by_row( colors: &[Color], count_rows: usize, count_columns: usize, cfg: &mut ColoredConfig, ) { let mut color_peek = colors.iter().cycle(); for row in 0..count_rows { for col in 0..count_columns { let color = color_peek.next().unwrap(); colorize_entity(color, Entity::Cell(row, col), cfg); } } } fn colorize_by_column( colors: &[Color], count_rows: usize, count_columns: usize, cfg: &mut ColoredConfig, ) { let mut color_peek = colors.iter().cycle(); for col in 0..count_columns { for row in 0..count_rows { let color = color_peek.next().unwrap(); colorize_entity(color, Entity::Cell(row, col), cfg); } } } fn colorize_diogonals( colors: &[Color], count_rows: usize, count_columns: usize, cfg: &mut ColoredConfig, ) { let mut color_peek = colors.iter().cycle(); for mut row in 0..count_rows { let color = color_peek.next().unwrap(); for col in 0..count_columns { colorize_entity(color, Entity::Cell(row, col), cfg); row += 1; if row == count_rows { break; } } } let _ = color_peek.next().unwrap(); for mut col in 1..count_columns { let color = color_peek.next().unwrap(); for row in 0..count_rows { colorize_entity(color, Entity::Cell(row, col), cfg); col += 1; if col == count_columns { break; } } } } fn colorize_entity(color: &Color, pos: Entity, cfg: &mut ColoredConfig) { let ansi_color = ANSIBuf::from(color.clone()); let _ = cfg.set_color(pos, ansi_color.clone()); cfg.set_justification_color(pos, Some(ansi_color.clone())); cfg.set_padding_color( pos, Sides::new( Some(ansi_color.clone()), Some(ansi_color.clone()), Some(ansi_color.clone()), Some(ansi_color), ), ); } /// A colorization of a target [`Object`]. /// /// Can be created by [`Colorization::exact`]. #[derive(Debug, Clone)] pub struct ExactColorization { colors: Vec, target: O, } impl ExactColorization { fn new(colors: Vec, target: O) -> Self { Self { colors, target } } } impl TableOption for ExactColorization where O: Object, { fn change(self, records: &mut R, cfg: &mut ColoredConfig, _: &mut D) { if self.colors.is_empty() { return; } let mut color_peek = self.colors.iter().cycle(); for pos in self.target.cells(records) { let color = color_peek.next().unwrap(); colorize_entity(color, pos, cfg); } } } tabled-0.18.0/src/settings/themes/column_names.rs000064400000000000000000000327001046102023000201110ustar 00000000000000use std::cmp; use crate::{ grid::{ config::{AlignmentHorizontal, AlignmentVertical, ColoredConfig, Position}, dimension::{CompleteDimensionVecRecords, Dimension, Estimate}, records::{ vec_records::{Text, VecRecords}, ExactRecords, PeekableRecords, Records, Resizable, }, util::string::{get_char_width, get_line_width}, }, settings::{ object::{Column, Row}, style::{LineText, Offset}, Alignment, Color, TableOption, }, }; /// [`ColumnNames`] sets strings on horizontal lines for the columns. /// /// Notice that using a [`Default`] would reuse a names from the first row. /// /// # Examples /// /// ``` /// use std::iter::FromIterator; /// use tabled::{ /// Table, /// settings::{themes::ColumnNames, Alignment}, /// }; /// /// let data = vec![ /// vec!["Hello", "World"], /// vec!["Hello", "World"], /// ]; /// /// let mut table = Table::from_iter(data); /// table.with( /// ColumnNames::new(["head1", "head2"]) /// .line(2) /// .alignment(Alignment::right()) /// ); /// /// assert_eq!( /// table.to_string(), /// "+-------+-------+\n\ /// | Hello | World |\n\ /// +-------+-------+\n\ /// | Hello | World |\n\ /// +--head1+--head2+" /// ); /// ``` /// /// [`Default`] usage. /// /// ``` /// use std::iter::FromIterator; /// use tabled::{Table, settings::themes::ColumnNames}; /// /// let data = vec![ /// vec!["Hello", "World"], /// vec!["Hello", "World"], /// ]; /// /// let mut table = Table::from_iter(data); /// table.with(ColumnNames::default()); /// /// assert_eq!( /// table.to_string(), /// "+Hello--+World--+\n\ /// | Hello | World |\n\ /// +-------+-------+" /// ); /// ``` #[derive(Debug, Clone)] pub struct ColumnNames { names: Option>, colors: Option>, alignments: ListValue, line: usize, } impl Default for ColumnNames { fn default() -> Self { Self { names: Default::default(), colors: Default::default(), line: Default::default(), alignments: ListValue::Static(Alignment::left()), } } } impl ColumnNames { /// Creates a [`ColumnNames`] with a given names. /// /// Using a [`Default`] would reuse a names from the first row. /// /// # Example /// /// ``` /// use std::iter::FromIterator; /// use tabled::{Table, settings::themes::ColumnNames}; /// /// let mut table = Table::from_iter(vec![vec!["Hello", "World"]]); /// table.with(ColumnNames::new(["head1", "head2"])); /// /// assert_eq!( /// table.to_string(), /// "+head1--+head2--+\n\ /// | Hello | World |\n\ /// +-------+-------+" /// ); /// ``` pub fn new(names: I) -> Self where I: IntoIterator, I::Item: Into, { let names = names.into_iter().map(Into::into).collect::>(); Self { names: Some(names), ..Default::default() } } /// Set color for the column names. /// /// By default there's no colors. /// /// # Example /// /// ``` /// use std::iter::FromIterator; /// use tabled::Table; /// use tabled::settings::{Color, themes::ColumnNames}; /// /// let mut table = Table::from_iter(vec![vec!["Hello", "World"]]); /// table.with(ColumnNames::new(["head1", "head2"]).color(vec![Color::FG_RED])); /// /// assert_eq!( /// table.to_string(), /// "+\u{1b}[31mh\u{1b}[39m\u{1b}[31me\u{1b}[39m\u{1b}[31ma\u{1b}[39m\u{1b}[31md\u{1b}[39m\u{1b}[31m1\u{1b}[39m--+head2--+\n\ /// | Hello | World |\n\ /// +-------+-------+" /// ); /// ``` pub fn color(self, color: T) -> Self where T: Into>, { Self { names: self.names, line: self.line, alignments: self.alignments, colors: Some(color.into()), } } /// Set a horizontal line the names will be applied to. /// /// The default value is 0 (the top horizontal line). /// /// # Example /// /// ``` /// use std::iter::FromIterator; /// use tabled::{Table, settings::themes::ColumnNames}; /// /// let mut table = Table::from_iter(vec![vec!["Hello", "World"]]); /// table.with(ColumnNames::new(["head1", "head2"]).line(1)); /// /// assert_eq!( /// table.to_string(), /// "+-------+-------+\n\ /// | Hello | World |\n\ /// +head1--+head2--+" /// ); /// ``` pub fn line(self, i: usize) -> Self { Self { names: self.names, line: i, alignments: self.alignments, colors: self.colors, } } /// Set an alignment for the names. /// /// By default it's left aligned. /// /// # Example /// /// ``` /// use std::iter::FromIterator; /// use tabled::{ /// Table, /// settings::{themes::ColumnNames, Alignment}, /// }; /// /// let mut table = Table::from_iter(vec![vec!["Hello", "World"]]); /// table.with(ColumnNames::new(["head1", "head2"]).alignment(Alignment::right())); /// /// assert_eq!( /// table.to_string(), /// "+--head1+--head2+\n\ /// | Hello | World |\n\ /// +-------+-------+" /// ); /// ``` pub fn alignment(self, alignment: T) -> Self where T: Into>, { Self { names: self.names, line: self.line, alignments: alignment.into(), colors: self.colors, } } } impl TableOption>, ColoredConfig, CompleteDimensionVecRecords<'_>> for ColumnNames { fn change( self, records: &mut VecRecords>, cfg: &mut ColoredConfig, dims: &mut CompleteDimensionVecRecords<'_>, ) { let count_rows = records.count_rows(); let count_columns = records.count_columns(); if count_columns == 0 || count_rows == 0 || self.line > count_rows { return; } let alignment_horizontal = convert_alignment_value(self.alignments.clone()); let alignment_vertical = convert_alignment_value(self.alignments.clone()); if let Some(alignment) = alignment_horizontal { let names = get_column_names(records, self.names); let names = vec_set_size(names, records.count_columns()); set_column_text(names, self.line, alignment, self.colors, records, dims, cfg); return; } if let Some(alignment) = alignment_vertical { let names = get_column_names(records, self.names); let names = vec_set_size(names, records.count_rows()); set_row_text(names, self.line, alignment, self.colors, records, dims, cfg); return; } let names = get_column_names(records, self.names); let names = vec_set_size(names, records.count_columns()); let alignment = ListValue::Static(AlignmentHorizontal::Left); set_column_text(names, self.line, alignment, self.colors, records, dims, cfg); } } fn set_column_text( names: Vec, target_line: usize, alignments: ListValue, colors: Option>, records: &mut VecRecords>, dims: &mut CompleteDimensionVecRecords<'_>, cfg: &mut ColoredConfig, ) { dims.estimate(&*records, cfg); let count_columns = names.len(); let widths = names .iter() .enumerate() .map(|(col, name)| (cmp::max(get_line_width(name), dims.get_width(col)))) .collect::>(); dims.set_widths(widths.clone()); let mut total_width = 0; for (column, (width, name)) in widths.into_iter().zip(names).enumerate() { let color = get_color(&colors, column); let alignment = alignments.get(column).unwrap_or(AlignmentHorizontal::Left); let left_vertical = get_vertical_width(cfg, (target_line, column).into(), count_columns); let grid_offset = total_width + left_vertical + get_horizontal_indent(&name, alignment, width); let line = Row::from(target_line); let linetext = create_line_text(&name, grid_offset, color, line); linetext.change(records, cfg, dims); total_width += width + left_vertical; } } fn set_row_text( names: Vec, target_line: usize, alignments: ListValue, colors: Option>, records: &mut VecRecords>, dims: &mut CompleteDimensionVecRecords<'_>, cfg: &mut ColoredConfig, ) { dims.estimate(&*records, cfg); let count_rows = names.len(); let heights = names .iter() .enumerate() .map(|(row, name)| (cmp::max(get_line_width(name), dims.get_height(row)))) .collect::>(); dims.set_heights(heights.clone()); let mut total_height = 0; for (row, (row_height, name)) in heights.into_iter().zip(names).enumerate() { let color = get_color(&colors, row); let alignment = alignments.get(row).unwrap_or(AlignmentVertical::Top); let top_horizontal = get_horizontal_width(cfg, (row, target_line).into(), count_rows); let cell_indent = get_vertical_indent(&name, alignment, row_height); let grid_offset = total_height + top_horizontal + cell_indent; let line = Column::from(target_line); let linetext = create_line_text(&name, grid_offset, color, line); linetext.change(records, cfg, dims); total_height += row_height + top_horizontal; } } fn get_column_names( records: &mut VecRecords>, opt: Option>, ) -> Vec { match opt { Some(names) => names .into_iter() .map(|name| name.lines().next().unwrap_or("").to_string()) .collect::>(), None => collect_head(records), } } fn vec_set_size(mut data: Vec, size: usize) -> Vec { match data.len().cmp(&size) { cmp::Ordering::Equal => {} cmp::Ordering::Less => { let additional_size = size - data.len(); data.extend(std::iter::repeat(String::new()).take(additional_size)); } cmp::Ordering::Greater => { data.truncate(size); } } data } fn collect_head(records: &mut VecRecords>) -> Vec { if records.count_rows() == 0 || records.count_columns() == 0 { return Vec::new(); } let names = (0..records.count_columns()) .map(|column| records.get_line((0, column).into(), 0)) .map(ToString::to_string) .collect(); records.remove_row(0); names } fn create_line_text(text: &str, offset: usize, color: Option<&Color>, line: T) -> LineText { let offset = Offset::Begin(offset); let mut btext = LineText::new(text, line).offset(offset); if let Some(color) = color { btext = btext.color(color.clone()); } btext } fn get_color(colors: &Option>, i: usize) -> Option<&Color> { match colors { Some(ListValue::List(list)) => list.get(i), Some(ListValue::Static(color)) => Some(color), None => None, } } fn get_horizontal_indent(text: &str, align: AlignmentHorizontal, available: usize) -> usize { match align { AlignmentHorizontal::Left => 0, AlignmentHorizontal::Right => available - get_line_width(text), AlignmentHorizontal::Center => (available - get_line_width(text)) / 2, } } fn get_vertical_indent(text: &str, align: AlignmentVertical, available: usize) -> usize { match align { AlignmentVertical::Top => 0, AlignmentVertical::Bottom => available - get_line_width(text), AlignmentVertical::Center => (available - get_line_width(text)) / 2, } } fn get_vertical_width(cfg: &mut ColoredConfig, pos: Position, count_columns: usize) -> usize { cfg.get_vertical(pos, count_columns) .map(get_char_width) .unwrap_or(0) } fn get_horizontal_width(cfg: &mut ColoredConfig, pos: Position, count_rows: usize) -> usize { cfg.get_horizontal(pos, count_rows) .map(get_char_width) .unwrap_or(0) } fn convert_alignment_value(value: ListValue) -> Option> where Option: From, { match value { ListValue::List(list) => { let new = list .iter() .flat_map(|value| Option::from(*value)) .collect::>(); if new.len() == list.len() { Some(ListValue::List(new)) } else { None } } ListValue::Static(value) => Option::from(value).map(ListValue::Static), } } #[derive(Debug, Clone)] pub enum ListValue { List(Vec), Static(T), } impl ListValue { fn get(&self, i: usize) -> Option where T: Copy, { match self { ListValue::List(list) => list.get(i).copied(), ListValue::Static(alignment) => Some(*alignment), } } } impl From for ListValue { fn from(value: T) -> Self { Self::Static(value) } } impl From> for ListValue { fn from(value: Vec) -> Self { Self::List(value) } } impl Default for ListValue where T: Default, { fn default() -> Self { Self::Static(T::default()) } } tabled-0.18.0/src/settings/themes/layout.rs000064400000000000000000000114261046102023000167500ustar 00000000000000//! Module contains [`Layout`] setting. use papergrid::records::{ExactRecords, PeekableRecords, Records}; use crate::{ grid::{ config::{AlignmentHorizontal, AlignmentVertical}, records::{RecordsMut, Resizable}, }, settings::{Alignment, Rotate, TableOption}, }; /// Layout can be used to move header to a specific corner. #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct Layout { orientation: HeadPosition, footer: bool, } #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] enum HeadPosition { Top, Bottom, Left, Right, } impl Layout { /// Construct a new layout setting. pub fn new(stick: Alignment, footer: bool) -> Self { let orientation = convert_orientation(stick); Self { footer, orientation, } } } impl TableOption for Layout where R: Records + Resizable + ExactRecords + PeekableRecords + RecordsMut, { fn change(self, records: &mut R, _: &mut C, _: &mut D) { move_head_if(records, self.orientation); if self.footer { copy_head(records, self.orientation); } } } const fn convert_orientation(position: Alignment) -> HeadPosition { match (position.as_horizontal(), position.as_vertical()) { (None, Some(AlignmentVertical::Top)) => HeadPosition::Top, (None, Some(AlignmentVertical::Bottom)) => HeadPosition::Bottom, (Some(AlignmentHorizontal::Left), None) => HeadPosition::Left, (Some(AlignmentHorizontal::Right), None) => HeadPosition::Right, (None, Some(AlignmentVertical::Center)) => HeadPosition::Top, (Some(AlignmentHorizontal::Center), None) => HeadPosition::Top, (None, None) | (Some(_), Some(_)) => HeadPosition::Top, } } fn copy_head(records: &mut R, orientation: HeadPosition) where R: Records + Resizable + ExactRecords + PeekableRecords + RecordsMut, { let head = collect_head_by(records, orientation); match orientation { HeadPosition::Top => cp_row(records, head, records.count_rows()), HeadPosition::Bottom => cp_row(records, head, 0), HeadPosition::Left => cp_column(records, head, records.count_columns()), HeadPosition::Right => cp_column(records, head, 0), } } fn collect_head_by(records: &mut R, orientation: HeadPosition) -> Vec where R: Records + PeekableRecords + ExactRecords, { match orientation { HeadPosition::Top => collect_head(records, 0), HeadPosition::Bottom => collect_head(records, records.count_rows() - 1), HeadPosition::Left => collect_head_vertical(records, 0), HeadPosition::Right => collect_head_vertical(records, records.count_columns() - 1), } } fn cp_row(records: &mut R, row: Vec, pos: usize) where R: Records + Resizable + ExactRecords + PeekableRecords + RecordsMut, { records.insert_row(pos); for (col, text) in row.into_iter().enumerate() { records.set((pos, col).into(), text); } } fn cp_column(records: &mut R, column: Vec, pos: usize) where R: Records + Resizable + ExactRecords + PeekableRecords + RecordsMut, { records.insert_column(pos); for (row, text) in column.into_iter().enumerate() { records.set((row, pos).into(), text); } } fn move_head_if(records: &mut R, orientation: HeadPosition) where R: Records + Resizable + ExactRecords + PeekableRecords + RecordsMut, { match orientation { HeadPosition::Top => {} HeadPosition::Bottom => { let head = collect_head(records, 0); push_row(records, head); records.remove_row(0); } HeadPosition::Left => { Rotate::Left.change(records, &mut (), &mut ()); Rotate::Bottom.change(records, &mut (), &mut ()); } HeadPosition::Right => { Rotate::Right.change(records, &mut (), &mut ()); } } } fn collect_head(records: &mut R, row: usize) -> Vec where R: Records + PeekableRecords, { (0..records.count_columns()) .map(|column| records.get_text((row, column).into())) .map(ToString::to_string) .collect() } fn collect_head_vertical(records: &mut R, column: usize) -> Vec where R: Records + PeekableRecords + ExactRecords, { (0..records.count_rows()) .map(|row| records.get_text((row, column).into())) .map(ToString::to_string) .collect() } fn push_row(records: &mut R, row: Vec) where R: Records + ExactRecords + Resizable + RecordsMut, { records.push_row(); let last_row = records.count_rows() - 1; for (col, text) in row.into_iter().enumerate() { records.set((last_row, col).into(), text); } } tabled-0.18.0/src/settings/themes/mod.rs000064400000000000000000000005741046102023000162140ustar 00000000000000//! The module contains a variety of configurations of table, which often //! changes not a single setting. //! As such they are making relatively big changes to the configuration. mod colorization; mod column_names; mod layout; mod theme; pub use colorization::{Colorization, ExactColorization}; pub use column_names::ColumnNames; pub use layout::Layout; pub use theme::Theme; tabled-0.18.0/src/settings/themes/theme.rs000064400000000000000000000624531046102023000165430ustar 00000000000000//! This module contains [`RawStyle`] structure, which is analogues to [`Style`] but not generic, //! so sometimes it can be used more conveniently. // todo: StyleFromTable() // table.with(&mut StyleFromTable); // vs // Theme::from(table.get_config()); // // not sure what the best interface is // IMHO 2 use std::collections::HashMap; use crate::{ grid::config::{ Border, Borders, ColoredConfig, CompactConfig, CompactMultilineConfig, HorizontalLine, VerticalLine, }, settings::{style::Style, Color, TableOption}, }; /// A raw style data, which can be produced safely from [`Style`]. /// /// It can be useful in order to not have a generics and be able to use it as a variable more conveniently. #[derive(Debug, Clone, PartialEq, Eq)] pub struct Theme { chars: Borders, colors: Borders, lines_horizontals: Option>>, lines_verticals: Option>>, lines_horizontal1: Option>, } impl Theme { /// Creates a new empty style. /// /// It's an analog of [`Style::empty`] pub const fn new() -> Self { Self::gen(Borders::empty(), Borders::empty(), None, None, None) } /// Build a theme out of a style builder. pub const fn from_style( style: Style, ) -> Self { let chars = style.get_borders(); let hlines = style.get_horizontals(); let hlines1 = hlines_find(hlines, 1); Self::gen(chars, Borders::empty(), None, None, hlines1) } /// Returns an outer border of the style. pub fn set_frame(&mut self, frame: Border) { self.chars.top = frame.top; self.chars.bottom = frame.bottom; self.chars.left = frame.left; self.chars.right = frame.right; self.chars.top_left = frame.left_top_corner; self.chars.top_right = frame.right_top_corner; self.chars.bottom_left = frame.left_bottom_corner; self.chars.bottom_right = frame.right_bottom_corner; } /// Returns an outer border of the style. pub fn set_frame_colors(&mut self, frame: Border) { self.colors.top = frame.top; self.colors.bottom = frame.bottom; self.colors.left = frame.left; self.colors.right = frame.right; self.colors.top_left = frame.left_top_corner; self.colors.top_right = frame.right_top_corner; self.colors.bottom_left = frame.left_bottom_corner; self.colors.bottom_right = frame.right_bottom_corner; } /// Set borders structure. pub fn set_borders(&mut self, borders: Borders) { self.chars = borders; } /// Set borders structure. pub fn set_colors(&mut self, borders: Borders) { self.colors = borders; } /// Get borders structure. pub const fn get_borders(&self) -> &Borders { &self.chars } /// Get borders color structure. pub const fn get_borders_colors(&self) -> &Borders { &self.colors } /// Get borders structure. pub fn get_borders_mut(&mut self) -> &mut Borders { &mut self.chars } /// Get borders color structure. pub fn get_colors_mut(&mut self) -> &mut Borders { &mut self.colors } /// Remove borders. pub fn remove_borders(&mut self) { self.set_borders(Borders::empty()); } /// Remove colors. pub fn remove_colors(&mut self) { self.set_colors(Borders::empty()); } /// Remove horizontal lines. pub fn remove_horizontal_lines(&mut self) { self.set_horizontal_lines(HashMap::new()); self.lines_horizontal1 = None; self.chars.horizontal = None; self.chars.left_intersection = None; self.chars.right_intersection = None; self.chars.intersection = None; } /// Remove vertical lines. pub fn remove_vertical_lines(&mut self) { self.set_vertical_lines(HashMap::new()); self.chars.vertical = None; self.chars.top_intersection = None; self.chars.bottom_intersection = None; self.chars.intersection = None; } /// Set an outer border. pub const fn get_frame(&self) -> Border { Border { top: self.chars.top, bottom: self.chars.bottom, left: self.chars.left, right: self.chars.right, left_top_corner: self.chars.top_left, right_top_corner: self.chars.top_right, left_bottom_corner: self.chars.bottom_left, right_bottom_corner: self.chars.bottom_right, } } /// Set an outer border. pub const fn get_frame_colors(&self) -> Border<&Color> { Border { top: self.colors.top.as_ref(), bottom: self.colors.bottom.as_ref(), left: self.colors.left.as_ref(), right: self.colors.right.as_ref(), left_top_corner: self.colors.top_left.as_ref(), right_top_corner: self.colors.top_right.as_ref(), left_bottom_corner: self.colors.bottom_left.as_ref(), right_bottom_corner: self.colors.bottom_right.as_ref(), } } /// Set horizontal border lines. /// /// # Example /// /// ``` /// use std::collections::HashMap; /// use tabled::{Table, settings::style::{Style, HorizontalLine}, settings::themes::Theme}; /// /// let mut style = Theme::from(Style::re_structured_text()); /// /// let mut lines = HashMap::new(); /// lines.insert(1, HorizontalLine::inherit(Style::extended()).into()); /// /// style.set_horizontal_lines(lines); /// /// let data = (0..3).map(|i| ("Hello", i)); /// let table = Table::new(data).with(style).to_string(); /// /// assert_eq!( /// table, /// concat!( /// " ======= ===== \n", /// " &str i32 \n", /// "╠═══════╬═════╣\n", /// " Hello 0 \n", /// " Hello 1 \n", /// " Hello 2 \n", /// " ======= ===== ", /// ), /// ) /// ``` pub fn set_horizontal_lines(&mut self, lines: HashMap>) { self.lines_horizontals = Some(lines); } /// Set vertical border lines. /// /// # Example /// /// ``` /// use std::collections::HashMap; /// use tabled::{ /// Table, /// settings::style::{Style, HorizontalLine}, /// settings::themes::Theme, /// }; /// /// /// let mut style = Theme::from_style(Style::re_structured_text()); /// /// let mut lines = HashMap::new(); /// lines.insert(1, HorizontalLine::inherit(Style::extended()).into()); /// /// style.set_vertical_lines(lines); /// /// let data = (0..3).map(|i| ("Hello", i)); /// let table = Table::new(data).with(style).to_string(); /// /// assert_eq!( /// table, /// concat!( /// "=======╠=====\n", /// " &str ═ i32 \n", /// "======= =====\n", /// " Hello ═ 0 \n", /// " Hello ═ 1 \n", /// " Hello ═ 2 \n", /// "=======╣=====", /// ), /// ) /// ``` pub fn set_vertical_lines(&mut self, lines: HashMap>) { self.lines_verticals = Some(lines); } /// Insert a vertical line into specific column location. pub fn insert_vertical_line(&mut self, line: usize, vertical: L) where L: Into>, { let vertical = vertical.into(); let verticals = match &mut self.lines_verticals { Some(verticals) => verticals, None => { self.lines_verticals = Some(HashMap::with_capacity(1)); self.lines_verticals.as_mut().expect("checked") } }; let _ = verticals.insert(line, vertical); } /// Insert a horizontal line to a specific row location. pub fn insert_horizontal_line(&mut self, line: usize, horizontal: L) where L: Into>, { let horizontal = horizontal.into(); let horizontals = match &mut self.lines_horizontals { Some(horizontals) => horizontals, None => { self.lines_horizontals = Some(HashMap::with_capacity(1)); self.lines_horizontals.as_mut().expect("checked") } }; let _ = horizontals.insert(line, horizontal); } /// Get a vertical line at the row if any set. pub fn get_vertical_line(&self, column: usize) -> Option<&VerticalLine> { self.lines_verticals.as_ref().and_then(|m| m.get(&column)) } /// Get a horizontal line at the row if any set. pub fn get_horizontal_line(&self, row: usize) -> Option<&HorizontalLine> { let line = self.lines_horizontals.as_ref().and_then(|m| m.get(&row)); if line.is_some() { return line; } if row == 1 && self.lines_horizontal1.is_some() { return self.lines_horizontal1.as_ref(); } None } /// Verifies if borders has left line set on the frame. pub const fn borders_has_left(&self) -> bool { self.chars.has_left() } /// Verifies if borders has right line set on the frame. pub const fn borders_has_right(&self) -> bool { self.chars.has_right() } /// Verifies if borders has top line set on the frame. pub const fn borders_has_top(&self) -> bool { self.chars.has_top() } /// Verifies if borders has bottom line set on the frame. pub const fn borders_has_bottom(&self) -> bool { self.chars.has_bottom() } /// Verifies if borders has horizontal lines set. pub const fn borders_has_horizontal(&self) -> bool { self.chars.has_horizontal() } /// Verifies if borders has vertical lines set. pub const fn borders_has_vertical(&self) -> bool { self.chars.has_vertical() } const fn gen( chars: Borders, colors: Borders, horizontals: Option>>, verticals: Option>>, horizontal1: Option>, ) -> Self { Self { chars, colors, lines_horizontals: horizontals, lines_verticals: verticals, lines_horizontal1: horizontal1, } } } impl From> for Theme { fn from(borders: Borders) -> Self { Self::gen( borders, Borders::empty(), Default::default(), Default::default(), None, ) } } impl Default for Theme { fn default() -> Self { Self::new() } } impl TableOption for Theme { fn change(self, _: &mut R, cfg: &mut ColoredConfig, _: &mut D) { cfg_clear_borders(cfg); cfg_set_custom_lines( cfg, self.lines_horizontals, self.lines_verticals, self.lines_horizontal1, ); cfg_set_borders(cfg, self.chars, self.colors); } } impl TableOption for Theme { fn change(self, _: &mut R, cfg: &mut CompactConfig, _: &mut D) { *cfg = cfg.set_borders(self.chars); } } impl TableOption for Theme { fn change(self, _: &mut R, cfg: &mut CompactMultilineConfig, _: &mut D) { cfg.set_borders(self.chars); } } impl From> for Theme { fn from(style: Style) -> Self { Self::from_style(style) } } impl From for Theme { fn from(cfg: ColoredConfig) -> Self { let borders = *cfg.get_borders(); let colors = cfg.get_color_borders().clone().convert_into(); let horizontals = cfg.get_horizontal_lines().into_iter().collect(); let verticals = cfg.get_vertical_lines().into_iter().collect(); Self::gen(borders, colors, Some(horizontals), Some(verticals), None) } } macro_rules! func_set_chars { ($name:ident, $arg:ident, $desc:expr) => { #[doc = concat!("Set a border character", " ", "", $desc, "", " ", ".")] pub fn $name(&mut self, c: char) { self.chars.$arg = Some(c); } }; } macro_rules! func_remove_chars { ($name:ident, $arg:ident, $desc:expr) => { #[doc = concat!("Remove a border character", " ", "", $desc, "", " ", ".")] pub fn $name(&mut self) { self.chars.$arg = None; } }; } macro_rules! func_get_chars { ($name:ident, $arg:ident, $desc:expr) => { #[doc = concat!("Get a border character", " ", "", $desc, "", " ", ".")] pub const fn $name(&self) -> Option { self.chars.$arg } }; } macro_rules! func_set_colors { ($name:ident, $arg:ident, $desc:expr) => { #[doc = concat!("Set a border color", " ", "", $desc, "", " ", ".")] pub fn $name(&mut self, color: Color) { self.colors.$arg = Some(color); } }; } macro_rules! func_remove_colors { ($name:ident, $arg:ident, $desc:expr) => { #[doc = concat!("Remove a border color", " ", "", $desc, "", " ", ".")] pub fn $name(&mut self) { self.colors.$arg = None; } }; } macro_rules! func_get_colors { ($name:ident, $arg:ident, $desc:expr) => { #[doc = concat!("Get a border color", " ", "", $desc, "", " ", ".")] pub fn $name(&self) -> Option<&Color> { self.colors.$arg.as_ref() } }; } #[rustfmt::skip] impl Theme { func_set_chars!(set_borders_top, top, "top"); func_set_chars!(set_borders_bottom, bottom, "bottom"); func_set_chars!(set_borders_left, left, "left"); func_set_chars!(set_borders_right, right, "right"); func_set_chars!(set_borders_corner_top_left, top_left, "top left corner"); func_set_chars!(set_borders_corner_top_right, top_right, "top right corner"); func_set_chars!(set_borders_corner_bottom_left, bottom_left, "bottom left corner"); func_set_chars!(set_borders_corner_bottom_right, bottom_right, "bottom right corner"); func_set_chars!(set_borders_intersection_top, top_intersection, "top intersection with a vertical line"); func_set_chars!(set_borders_intersection_bottom, bottom_intersection, "bottom intersection with a vertical line"); func_set_chars!(set_borders_intersection_left, left_intersection, "left intersection with a horizontal line"); func_set_chars!(set_borders_intersection_right, right_intersection, "right intersection with a horizontal line"); func_set_chars!(set_borders_intersection, intersection, "intersection of horizontal and vertical line"); func_set_chars!(set_borders_horizontal, horizontal, "horizontal"); func_set_chars!(set_borders_vertical, vertical, "vertical"); } #[rustfmt::skip] impl Theme { func_get_chars!(get_borders_top, top, "top"); func_get_chars!(get_borders_bottom, bottom, "bottom"); func_get_chars!(get_borders_left, left, "left"); func_get_chars!(get_borders_right, right, "right"); func_get_chars!(get_borders_corner_top_left, top_left, "top left corner"); func_get_chars!(get_borders_corner_top_right, top_right, "top right corner"); func_get_chars!(get_borders_corner_bottom_left, bottom_left, "bottom left corner"); func_get_chars!(get_borders_corner_bottom_right, bottom_right, "bottom right corner"); func_get_chars!(get_borders_intersection_top, top_intersection, "top intersection with a vertical line"); func_get_chars!(get_borders_intersection_bottom, bottom_intersection, "bottom intersection with a vertical line"); func_get_chars!(get_borders_intersection_left, left_intersection, "left intersection with a horizontal line"); func_get_chars!(get_borders_intersection_right, right_intersection, "right intersection with a horizontal line"); func_get_chars!(get_borders_intersection, intersection, "intersection of horizontal and vertical line"); func_get_chars!(get_borders_horizontal, horizontal, "horizontal"); func_get_chars!(get_borders_vertical, vertical, "vertical"); } #[rustfmt::skip] impl Theme { func_remove_chars!(remove_borders_top, top, "top"); func_remove_chars!(remove_borders_bottom, bottom, "bottom"); func_remove_chars!(remove_borders_left, left, "left"); func_remove_chars!(remove_borders_right, right, "right"); func_remove_chars!(remove_borders_corner_top_left, top_left, "top left corner"); func_remove_chars!(remove_borders_corner_top_right, top_right, "top right corner"); func_remove_chars!(remove_borders_corner_bottom_left, bottom_left, "bottom left corner"); func_remove_chars!(remove_borders_corner_bottom_right, bottom_right, "bottom right corner"); func_remove_chars!(remove_borders_intersection_top, top_intersection, "top intersection with a vertical line"); func_remove_chars!(remove_borders_intersection_bottom, bottom_intersection, "bottom intersection with a vertical line"); func_remove_chars!(remove_borders_intersection_left, left_intersection, "left intersection with a horizontal line"); func_remove_chars!(remove_borders_intersection_right, right_intersection, "right intersection with a horizontal line"); func_remove_chars!(remove_borders_intersection, intersection, "intersection of horizontal and vertical line"); func_remove_chars!(remove_borders_horizontal, horizontal, "horizontal"); func_remove_chars!(remove_borders_vertical, vertical, "vertical"); } #[rustfmt::skip] impl Theme { func_set_colors!(set_colors_top, top, "top"); func_set_colors!(set_colors_bottom, bottom, "bottom"); func_set_colors!(set_colors_left, left, "left"); func_set_colors!(set_colors_right, right, "right"); func_set_colors!(set_colors_corner_top_left, top_left, "top left corner"); func_set_colors!(set_colors_corner_top_right, top_right, "top right corner"); func_set_colors!(set_colors_corner_bottom_left, bottom_left, "bottom left corner"); func_set_colors!(set_colors_corner_bottom_right, bottom_right, "bottom right corner"); func_set_colors!(set_colors_intersection_top, top_intersection, "top intersection with a vertical line"); func_set_colors!(set_colors_intersection_bottom, bottom_intersection, "bottom intersection with a vertical line"); func_set_colors!(set_colors_intersection_left, left_intersection, "left intersection with a horizontal line"); func_set_colors!(set_colors_intersection_right, right_intersection, "right intersection with a horizontal line"); func_set_colors!(set_colors_intersection, intersection, "intersection of horizontal and vertical line"); func_set_colors!(set_colors_horizontal, horizontal, "horizontal"); func_set_colors!(set_colors_vertical, vertical, "vertical"); } #[rustfmt::skip] impl Theme { func_remove_colors!(remove_colors_top, top, "top"); func_remove_colors!(remove_colors_bottom, bottom, "bottom"); func_remove_colors!(remove_colors_left, left, "left"); func_remove_colors!(remove_colors_right, right, "right"); func_remove_colors!(remove_colors_corner_top_left, top_left, "top left corner"); func_remove_colors!(remove_colors_corner_top_right, top_right, "top right corner"); func_remove_colors!(remove_colors_corner_bottom_left, bottom_left, "bottom left corner"); func_remove_colors!(remove_colors_corner_bottom_right, bottom_right, "bottom right corner"); func_remove_colors!(remove_colors_intersection_top, top_intersection, "top intersection with a vertical line"); func_remove_colors!(remove_colors_intersection_bottom, bottom_intersection, "bottom intersection with a vertical line"); func_remove_colors!(remove_colors_intersection_left, left_intersection, "left intersection with a horizontal line"); func_remove_colors!(remove_colors_intersection_right, right_intersection, "right intersection with a horizontal line"); func_remove_colors!(remove_colors_intersection, intersection, "intersection of horizontal and vertical line"); func_remove_colors!(remove_colors_horizontal, horizontal, "horizontal"); func_remove_colors!(remove_colors_vertical, vertical, "vertical"); } #[rustfmt::skip] impl Theme { func_get_colors!(get_colors_top, top, "top"); func_get_colors!(get_colors_bottom, bottom, "bottom"); func_get_colors!(get_colors_left, left, "left"); func_get_colors!(get_colors_right, right, "right"); func_get_colors!(get_colors_corner_top_left, top_left, "top left corner"); func_get_colors!(get_colors_corner_top_right, top_right, "top right corner"); func_get_colors!(get_colors_corner_bottom_left, bottom_left, "bottom left corner"); func_get_colors!(get_colors_corner_bottom_right, bottom_right, "bottom right corner"); func_get_colors!(get_colors_intersection_top, top_intersection, "top intersection with a vertical line"); func_get_colors!(get_colors_intersection_bottom, bottom_intersection, "bottom intersection with a vertical line"); func_get_colors!(get_colors_intersection_left, left_intersection, "left intersection with a horizontal line"); func_get_colors!(get_colors_intersection_right, right_intersection, "right intersection with a horizontal line"); func_get_colors!(get_colors_intersection, intersection, "intersection of horizontal and vertical line"); func_get_colors!(get_colors_horizontal, horizontal, "horizontal"); func_get_colors!(get_colors_vertical, vertical, "vertical"); } fn cfg_clear_borders(cfg: &mut ColoredConfig) { cfg.remove_borders(); cfg.remove_borders_colors(); cfg.remove_vertical_chars(); cfg.remove_horizontal_chars(); cfg.remove_color_line_horizontal(); cfg.remove_color_line_vertical(); } fn cfg_set_borders(cfg: &mut ColoredConfig, borders: Borders, colors: Borders) { cfg.set_borders(borders); if !colors.is_empty() { cfg.set_borders_color(colors.convert_into()); } } fn cfg_set_custom_lines( cfg: &mut ColoredConfig, horizontals: Option>>, verticals: Option>>, horizontal1: Option>, ) { if let Some(line) = horizontal1 { cfg.insert_horizontal_line(1, line); } if let Some(horizontals) = horizontals { for (row, line) in horizontals { cfg.insert_horizontal_line(row, line); } } if let Some(verticals) = verticals { for (col, line) in verticals { cfg.insert_vertical_line(col, line); } } } const fn hlines_find( lines: [(usize, HorizontalLine); N], search: usize, ) -> Option> { let mut line = None; let mut i = 0; while i < lines.len() { let (num, hline) = lines[i]; if num == search { line = Some(hline); } i += 1; } line } tabled-0.18.0/src/settings/width/justify.rs000064400000000000000000000052561046102023000167660ustar 00000000000000//! This module contains [`Justify`] structure, used to set an exact width to each column. use crate::{ grid::config::ColoredConfig, grid::records::{ExactRecords, IntoRecords, PeekableRecords, Records, RecordsMut}, settings::{ measurement::{Max, Measurement, Min}, CellOption, TableOption, Width, }, }; /// Justify sets all columns widths to the set value. /// /// Be aware that it doesn't consider padding. /// So if you want to set a exact width you might need to use [`Padding`] to set it to 0. /// /// ## Examples /// /// ``` /// use tabled::{Table, settings::{Width, Style, object::Segment, Padding, Modify}}; /// /// let data = ["Hello", "World", "!"]; /// /// let table = Table::new(&data) /// .with(Style::markdown()) /// .with(Modify::new(Segment::all()).with(Padding::zero())) /// .with(Width::justify(3)); /// ``` /// /// [`Max`] usage to justify by a max column width. /// /// ``` /// use tabled::{Table, settings::{width::Justify, Style}}; /// /// let data = ["Hello", "World", "!"]; /// /// let table = Table::new(&data) /// .with(Style::markdown()) /// .with(Justify::max()); /// ``` /// /// [`Padding`]: crate::settings::Padding #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] pub struct Justify { width: W, } impl Justify where W: Measurement, { /// Creates a new [`Justify`] instance. /// /// Be aware that [`Padding`] is not considered when comparing the width. /// /// [`Padding`]: crate::settings::Padding pub fn new(width: W) -> Self { Self { width } } } impl Justify { /// Creates a new Justify instance with a Max width used as a value. pub fn max() -> Self { Self { width: Max } } } impl Justify { /// Creates a new Justify instance with a Min width used as a value. pub fn min() -> Self { Self { width: Min } } } impl TableOption for Justify where W: Measurement, R: Records + ExactRecords + PeekableRecords + RecordsMut, for<'a> &'a R: Records, for<'a> <<&'a R as Records>::Iter as IntoRecords>::Cell: AsRef, { fn change(self, records: &mut R, cfg: &mut ColoredConfig, _: &mut D) { let width = self.width.measure(&*records, cfg); let count_rows = records.count_rows(); let count_columns = records.count_columns(); for row in 0..count_rows { for col in 0..count_columns { let pos = (row, col).into(); CellOption::change(Width::increase(width), records, cfg, pos); CellOption::change(Width::truncate(width), records, cfg, pos); } } } } tabled-0.18.0/src/settings/width/min_width.rs000064400000000000000000000136471046102023000172560ustar 00000000000000//! This module contains [`MinWidth`] structure, used to increase width of a [`Table`]s or a cell on a [`Table`]. //! //! [`Table`]: crate::Table use crate::{ grid::config::{ColoredConfig, Entity}, grid::dimension::CompleteDimensionVecRecords, grid::records::{ExactRecords, IntoRecords, PeekableRecords, Records, RecordsMut}, grid::util::string::{get_lines, get_text_width}, settings::{ measurement::Measurement, peaker::{Peaker, PriorityNone}, CellOption, TableOption, Width, }, }; use super::util::get_table_widths_with_total; /// [`MinWidth`] changes a content in case if it's length is lower then the boundary. /// /// It can be applied to a whole table. /// /// It does nothing in case if the content's length is bigger then the boundary. /// /// Be aware that further changes of the table may cause the width being not set. /// For example applying [`Padding`] after applying [`MinWidth`] will make the former have no affect. /// (You should use [`Padding`] first). /// /// Be aware that it doesn't consider padding. /// So if you want to set a exact width you might need to use [`Padding`] to set it to 0. /// /// ## Examples /// /// Cell change /// /// ``` /// use tabled::{Table, settings::{object::Segment, Width, Style, Modify}}; /// /// let data = ["Hello", "World", "!"]; /// /// let table = Table::new(&data) /// .with(Style::markdown()) /// .with(Modify::new(Segment::all()).with(Width::increase(10))); /// ``` /// Table change /// /// ``` /// use tabled::{Table, settings::Width}; /// /// let table = Table::new(&["Hello World!"]).with(Width::increase(5)); /// ``` /// /// [`Padding`]: crate::settings::Padding #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] pub struct MinWidth { width: W, fill: char, priority: P, } impl MinWidth where W: Measurement, { /// Creates a new instance of [`MinWidth`]. pub const fn new(width: W) -> Self { Self { width, fill: ' ', priority: PriorityNone::new(), } } } impl MinWidth { /// Set's a fill character which will be used to fill the space /// when increasing the length of the string to the set boundary. /// /// Used only if changing cells. pub fn fill_with(mut self, c: char) -> Self { self.fill = c; self } /// Priority defines the logic by which a increase of width will be applied when is done for the whole table. /// /// - [`PriorityNone`] which inc the columns one after another. /// - [`PriorityMax`] inc the biggest columns first. /// - [`PriorityMin`] inc the lowest columns first. /// /// [`PriorityMax`]: crate::settings::peaker::PriorityMax /// [`PriorityMin`]: crate::settings::peaker::PriorityMin pub fn priority(self, peacker: PP) -> MinWidth { MinWidth { fill: self.fill, width: self.width, priority: peacker, } } } impl CellOption for MinWidth where W: Measurement, R: Records + ExactRecords + PeekableRecords + RecordsMut, for<'a> &'a R: Records, for<'a> <<&'a R as Records>::Iter as IntoRecords>::Cell: AsRef, { fn change(self, records: &mut R, cfg: &mut ColoredConfig, entity: Entity) { let width = self.width.measure(&*records, cfg); let count_rows = records.count_rows(); let count_columns = records.count_columns(); for pos in entity.iter(count_rows, count_columns) { if !pos.is_covered((count_rows, count_columns).into()) { continue; } let cell = records.get_text(pos); let cell_width = get_text_width(cell); if cell_width >= width { continue; } let content = increase_width(cell, width, self.fill); records.set(pos, content); } } fn hint_change(&self) -> Option { Some(Entity::Column(0)) } } impl TableOption> for MinWidth where W: Measurement, P: Peaker, R: Records + ExactRecords + PeekableRecords, for<'a> &'a R: Records, for<'a> <<&'a R as Records>::Iter as IntoRecords>::Cell: AsRef, { fn change( self, records: &mut R, cfg: &mut ColoredConfig, dims: &mut CompleteDimensionVecRecords<'_>, ) { if records.count_rows() == 0 || records.count_columns() == 0 { return; } let necessary_width = self.width.measure(&*records, cfg); let (widths, total_width) = get_table_widths_with_total(&*records, cfg); if total_width >= necessary_width { return; } let widths = get_increase_list(widths, necessary_width, total_width, self.priority); dims.set_widths(widths); } fn hint_change(&self) -> Option { Some(Entity::Column(0)) } } fn get_increase_list( mut widths: Vec, need: usize, mut current: usize, mut peaker: F, ) -> Vec where F: Peaker, { while need != current { let col = match peaker.peak(&[], &widths) { Some(col) => col, None => break, }; widths[col] += 1; current += 1; } widths } fn increase_width(s: &str, width: usize, fill_with: char) -> String { use crate::grid::util::string::get_line_width; use std::{borrow::Cow, iter::repeat}; get_lines(s) .map(|line| { let length = get_line_width(&line); if length < width { let mut line = line.into_owned(); let remain = width - length; line.extend(repeat(fill_with).take(remain)); Cow::Owned(line) } else { line } }) .collect::>() .join("\n") } tabled-0.18.0/src/settings/width/mod.rs000064400000000000000000000111641046102023000160430ustar 00000000000000//! This module contains object which can be used to limit a cell to a given width: //! //! - [`Truncate`] cuts a cell content to limit width. //! - [`Wrap`] split the content via new lines in order to fit max width. //! - [`Justify`] sets columns width to the same value. //! //! To set a a table width, a combination of [`Width::truncate`] or [`Width::wrap`] and [`Width::increase`] can be used. //! //! ## Example //! //! ``` //! use tabled::{Table, settings::Width}; //! //! let table = Table::new(&["Hello World!"]) //! .with(Width::wrap(7)) //! .with(Width::increase(7)) //! .to_string(); //! //! assert_eq!( //! table, //! concat!( //! "+-----+\n", //! "| &st |\n", //! "| r |\n", //! "+-----+\n", //! "| Hel |\n", //! "| lo |\n", //! "| Wor |\n", //! "| ld! |\n", //! "+-----+", //! ) //! ); //! ``` mod justify; mod min_width; mod truncate; mod util; mod width_list; mod wrap; use crate::settings::measurement::Measurement; pub use self::{ justify::Justify, min_width::MinWidth, truncate::{SuffixLimit, Truncate}, width_list::WidthList, wrap::Wrap, }; /// Width allows you to set a min and max width of an object on a [`Table`] /// using different strategies. /// /// It also allows you to set a min and max width for a whole table. /// /// You can apply a min and max strategy at the same time with the same value, /// the value will be a total table width. /// /// It is an abstract factory. /// /// Beware that borders are not removed when you set a size value to very small. /// For example if you set size to 0 the table still be rendered but with all content removed. /// /// Also be aware that it doesn't changes [`Padding`] settings nor it considers them. /// /// The function is color aware if a `color` feature is on. /// /// ## Examples /// /// ### Cell change /// /// ``` /// use tabled::{Table, settings::{object::Segment, Width, Style, Modify}}; /// /// let data = ["Hello", "World", "!"]; /// /// let table = Table::new(&data) /// .with(Style::markdown()) /// .with(Modify::new(Segment::all()).with(Width::truncate(3).suffix("..."))); /// ``` /// /// ### Table change /// /// ``` /// use tabled::{Table, settings::Width}; /// /// let table = Table::new(&["Hello World!"]).with(Width::wrap(5)); /// ``` /// /// ### Total width /// /// ``` /// use tabled::{Table, settings::Width}; /// /// let table = Table::new(&["Hello World!"]) /// .with(Width::wrap(5)) /// .with(Width::increase(5)); /// ``` /// /// [`Padding`]: crate::settings::Padding /// [`Table`]: crate::Table #[derive(Debug)] pub struct Width; impl Width { /// Returns a [`Wrap`] structure. pub fn wrap>(width: W) -> Wrap { Wrap::new(width) } /// Returns a [`Truncate`] structure. pub fn truncate>(width: W) -> Truncate<'static, W> { Truncate::new(width) } /// Returns a [`MinWidth`] structure. pub fn increase>(width: W) -> MinWidth { MinWidth::new(width) } /// Returns a [`Justify`] structure. pub fn justify>(width: W) -> Justify { Justify::new(width) } /// Create [`WidthList`] to set a table width to a constant list of column widths. /// /// Notice if you provide a list with `.len()` smaller than `Table::count_columns` then it will have no affect. /// /// Also notice that you must provide values bigger than or equal to a real content width, otherwise it may panic. /// /// # Example /// /// ``` /// use tabled::{Table, settings::Width}; /// /// let data = vec![ /// ("Some\ndata", "here", "and here"), /// ("Some\ndata on a next", "line", "right here"), /// ]; /// /// let table = Table::new(data) /// .with(Width::list([20, 10, 12])) /// .to_string(); /// /// assert_eq!( /// table, /// "+--------------------+----------+------------+\n\ /// | &str | &str | &str |\n\ /// +--------------------+----------+------------+\n\ /// | Some | here | and here |\n\ /// | data | | |\n\ /// +--------------------+----------+------------+\n\ /// | Some | line | right here |\n\ /// | data on a next | | |\n\ /// +--------------------+----------+------------+" /// ) /// ``` pub fn list>(rows: I) -> WidthList { WidthList::new(rows.into_iter().collect()) } } tabled-0.18.0/src/settings/width/truncate.rs000064400000000000000000000347221046102023000171160ustar 00000000000000//! This module contains [`Truncate`] structure, used to decrease width of a [`Table`]s or a cell on a [`Table`] by truncating the width. //! //! [`Table`]: crate::Table use std::{borrow::Cow, iter}; use crate::{ grid::{ config::{ColoredConfig, Entity, SpannedConfig}, dimension::CompleteDimensionVecRecords, records::{EmptyRecords, ExactRecords, IntoRecords, PeekableRecords, Records, RecordsMut}, util::string::{get_line_width, get_text_width}, }, settings::{ measurement::Measurement, peaker::{Peaker, PriorityNone}, CellOption, TableOption, Width, }, }; use super::util::{get_table_widths, get_table_widths_with_total}; use crate::util::string::cut_str; /// Truncate cut the string to a given width if its length exceeds it. /// Otherwise keeps the content of a cell untouched. /// /// The function is color aware if a `color` feature is on. /// /// Be aware that it doesn't consider padding. /// So if you want to set a exact width you might need to use [`Padding`] to set it to 0. /// /// ## Example /// /// ``` /// use tabled::{Table, settings::{object::Segment, Width, Modify}}; /// /// let table = Table::new(&["Hello World!"]) /// .with(Modify::new(Segment::all()).with(Width::truncate(3))); /// ``` /// /// [`Padding`]: crate::settings::Padding #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] pub struct Truncate<'a, W = usize, P = PriorityNone> { width: W, suffix: Option>, multiline: bool, priority: P, } #[cfg(feature = "ansi")] #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] struct TruncateSuffix<'a> { text: Cow<'a, str>, limit: SuffixLimit, try_color: bool, } #[cfg(not(feature = "ansi"))] #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] struct TruncateSuffix<'a> { text: Cow<'a, str>, limit: SuffixLimit, } impl Default for TruncateSuffix<'_> { fn default() -> Self { Self { text: Cow::default(), limit: SuffixLimit::Cut, #[cfg(feature = "ansi")] try_color: false, } } } /// A suffix limit settings. #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] pub enum SuffixLimit { /// Cut the suffix. Cut, /// Don't show the suffix. Ignore, /// Use a string with n chars instead. Replace(char), } impl Truncate<'static, W> where W: Measurement, { /// Creates a [`Truncate`] object pub const fn new(width: W) -> Truncate<'static, W> { Self { width, multiline: false, suffix: None, priority: PriorityNone::new(), } } } impl<'a, W, P> Truncate<'a, W, P> { /// Sets a suffix which will be appended to a resultant string. /// /// The suffix is used in 3 circumstances: /// 1. If original string is *bigger* than the suffix. /// We cut more of the original string and append the suffix. /// 2. If suffix is bigger than the original string. /// We cut the suffix to fit in the width by default. /// But you can peak the behaviour by using [`Truncate::suffix_limit`] pub fn suffix>>(self, suffix: S) -> Truncate<'a, W, P> { let mut suff = self.suffix.unwrap_or_default(); suff.text = suffix.into(); Truncate { width: self.width, multiline: self.multiline, priority: self.priority, suffix: Some(suff), } } /// Sets a suffix limit, which is used when the suffix is too big to be used. pub fn suffix_limit(self, limit: SuffixLimit) -> Truncate<'a, W, P> { let mut suff = self.suffix.unwrap_or_default(); suff.limit = limit; Truncate { width: self.width, multiline: self.multiline, priority: self.priority, suffix: Some(suff), } } /// Use trancate logic per line, not as a string as a whole. pub fn multiline(self, on: bool) -> Truncate<'a, W, P> { Truncate { width: self.width, multiline: on, suffix: self.suffix, priority: self.priority, } } #[cfg(feature = "ansi")] /// Sets a optional logic to try to colorize a suffix. pub fn suffix_try_color(self, color: bool) -> Truncate<'a, W, P> { let mut suff = self.suffix.unwrap_or_default(); suff.try_color = color; Truncate { width: self.width, multiline: self.multiline, priority: self.priority, suffix: Some(suff), } } } impl<'a, W, P> Truncate<'a, W, P> { /// Priority defines the logic by which a truncate will be applied when is done for the whole table. /// /// - [`PriorityNone`] which cuts the columns one after another. /// - [`PriorityMax`] cuts the biggest columns first. /// - [`PriorityMin`] cuts the lowest columns first. /// /// [`PriorityMax`]: crate::settings::peaker::PriorityMax /// [`PriorityMin`]: crate::settings::peaker::PriorityMin pub fn priority(self, priority: PP) -> Truncate<'a, W, PP> { Truncate { width: self.width, multiline: self.multiline, suffix: self.suffix, priority, } } } impl Truncate<'_, (), ()> { /// Truncate a given string pub fn truncate(text: &str, width: usize) -> Cow<'_, str> { truncate_text(text, width, "", false) } } impl CellOption for Truncate<'_, W, P> where W: Measurement, R: Records + ExactRecords + PeekableRecords + RecordsMut, for<'a> &'a R: Records, for<'a> <<&'a R as Records>::Iter as IntoRecords>::Cell: AsRef, { fn change(self, records: &mut R, cfg: &mut ColoredConfig, entity: Entity) { let available = self.width.measure(&*records, cfg); let mut width = available; let mut suffix = Cow::Borrowed(""); if let Some(x) = self.suffix.as_ref() { let (cut_suffix, rest_width) = make_suffix(x, width); suffix = cut_suffix; width = rest_width; }; let count_rows = records.count_rows(); let count_columns = records.count_columns(); let colorize = need_suffix_color_preservation(&self.suffix); for pos in entity.iter(count_rows, count_columns) { if !pos.is_covered((count_rows, count_columns).into()) { continue; } let text = records.get_text(pos); let cell_width = get_text_width(text); if available >= cell_width { continue; } let text = truncate_multiline(text, &suffix, width, available, colorize, self.multiline); records.set(pos, text.into_owned()); } } } fn truncate_multiline<'a>( text: &'a str, suffix: &'a str, width: usize, twidth: usize, suffix_color: bool, multiline: bool, ) -> Cow<'a, str> { if multiline { let mut buf = String::new(); for (i, line) in crate::grid::util::string::get_lines(text).enumerate() { if i != 0 { buf.push('\n'); } let line = make_text_truncated(&line, suffix, width, twidth, suffix_color); buf.push_str(&line); } Cow::Owned(buf) } else { make_text_truncated(text, suffix, width, twidth, suffix_color) } } fn make_text_truncated<'a>( text: &'a str, suffix: &'a str, width: usize, twidth: usize, suffix_color: bool, ) -> Cow<'a, str> { if width == 0 { if twidth == 0 { Cow::Borrowed("") } else { Cow::Borrowed(suffix) } } else { truncate_text(text, width, suffix, suffix_color) } } fn need_suffix_color_preservation(_suffix: &Option>) -> bool { #[cfg(not(feature = "ansi"))] { false } #[cfg(feature = "ansi")] { _suffix.as_ref().is_some_and(|s| s.try_color) } } fn make_suffix<'a>(suffix: &'a TruncateSuffix<'_>, width: usize) -> (Cow<'a, str>, usize) { let suffix_length = get_line_width(&suffix.text); if width > suffix_length { return (Cow::Borrowed(suffix.text.as_ref()), width - suffix_length); } match suffix.limit { SuffixLimit::Ignore => (Cow::Borrowed(""), width), SuffixLimit::Cut => { let suffix = cut_str(&suffix.text, width); (suffix, 0) } SuffixLimit::Replace(c) => { let suffix = Cow::Owned(iter::repeat(c).take(width).collect()); (suffix, 0) } } } impl TableOption> for Truncate<'_, W, P> where W: Measurement, P: Peaker, R: Records + ExactRecords + PeekableRecords + RecordsMut, for<'a> &'a R: Records, for<'a> <<&'a R as Records>::Iter as IntoRecords>::Cell: AsRef, { fn change( self, records: &mut R, cfg: &mut ColoredConfig, dims: &mut CompleteDimensionVecRecords<'_>, ) { if records.count_rows() == 0 || records.count_columns() == 0 { return; } let width = self.width.measure(&*records, cfg); let (widths, total) = get_table_widths_with_total(&*records, cfg); if total <= width { return; } let suffix = self.suffix.as_ref().map(|s| TruncateSuffix { text: Cow::Borrowed(&s.text), limit: s.limit, #[cfg(feature = "ansi")] try_color: s.try_color, }); let multiline = self.multiline; let widths = truncate_total_width( records, cfg, widths, total, width, self.priority, suffix, multiline, ); dims.set_widths(widths); } } #[allow(clippy::too_many_arguments)] fn truncate_total_width( records: &mut R, cfg: &mut ColoredConfig, mut widths: Vec, total: usize, width: usize, priority: P, suffix: Option>, multiline: bool, ) -> Vec where P: Peaker, R: Records + PeekableRecords + ExactRecords + RecordsMut, for<'a> &'a R: Records, for<'a> <<&'a R as Records>::Iter as IntoRecords>::Cell: AsRef, { let count_rows = records.count_rows(); let count_columns = records.count_columns(); let min_widths = get_table_widths(EmptyRecords::new(count_rows, count_columns), cfg); decrease_widths(&mut widths, &min_widths, total, width, priority); let points = get_decrease_cell_list(cfg, &widths, &min_widths, (count_rows, count_columns)); for ((row, col), width) in points { let mut truncate = Truncate::new(width); truncate.suffix.clone_from(&suffix); truncate.multiline = multiline; CellOption::change(truncate, records, cfg, (row, col).into()); } widths } fn truncate_text<'a>( text: &'a str, width: usize, suffix: &str, _suffix_color: bool, ) -> Cow<'a, str> { let content = cut_str(text, width); if suffix.is_empty() { return content; } #[cfg(feature = "ansi")] { if _suffix_color { if let Some(block) = ansi_str::get_blocks(text).last() { if block.has_ansi() { let style = block.style(); Cow::Owned(format!( "{}{}{}{}", content, style.start(), suffix, style.end() )) } else { let mut content = content.into_owned(); content.push_str(suffix); Cow::Owned(content) } } else { let mut content = content.into_owned(); content.push_str(suffix); Cow::Owned(content) } } else { let mut content = content.into_owned(); content.push_str(suffix); Cow::Owned(content) } } #[cfg(not(feature = "ansi"))] { let mut content = content.into_owned(); content.push_str(suffix); Cow::Owned(content) } } fn get_decrease_cell_list( cfg: &SpannedConfig, widths: &[usize], min_widths: &[usize], shape: (usize, usize), ) -> Vec<((usize, usize), usize)> { let mut points = Vec::new(); (0..shape.1).for_each(|col| { (0..shape.0) .filter(|&row| cfg.is_cell_visible((row, col).into())) .for_each(|row| { let (width, width_min) = match cfg.get_column_span((row, col).into()) { Some(span) => { let width = (col..col + span).map(|i| widths[i]).sum::(); let min_width = (col..col + span).map(|i| min_widths[i]).sum::(); let count_borders = count_borders(cfg, col, col + span, shape.1); (width + count_borders, min_width + count_borders) } None => (widths[col], min_widths[col]), }; if width >= width_min { let padding = cfg.get_padding((row, col).into()); let width = width.saturating_sub(padding.left.size + padding.right.size); points.push(((row, col), width)); } }); }); points } fn decrease_widths( widths: &mut [usize], min_widths: &[usize], total_width: usize, mut width: usize, mut peeaker: F, ) where F: Peaker, { let mut empty_list = 0; for col in 0..widths.len() { if widths[col] == 0 || widths[col] <= min_widths[col] { empty_list += 1; } } while width != total_width { if empty_list == widths.len() { break; } let col = match peeaker.peak(min_widths, widths) { Some(col) => col, None => break, }; if widths[col] == 0 || widths[col] <= min_widths[col] { continue; } widths[col] -= 1; if widths[col] == 0 || widths[col] <= min_widths[col] { empty_list += 1; } width += 1; } } fn count_borders(cfg: &SpannedConfig, start: usize, end: usize, count_columns: usize) -> usize { (start..end) .skip(1) .filter(|&i| cfg.has_vertical(i, count_columns)) .count() } tabled-0.18.0/src/settings/width/util.rs000064400000000000000000000016001046102023000162330ustar 00000000000000use crate::{ grid::config::SpannedConfig, grid::dimension::SpannedGridDimension, grid::records::{IntoRecords, Records}, }; pub(crate) fn get_table_widths(records: R, cfg: &SpannedConfig) -> Vec where R: Records, ::Cell: AsRef, { SpannedGridDimension::width(records, cfg) } pub(crate) fn get_table_widths_with_total(records: R, cfg: &SpannedConfig) -> (Vec, usize) where R: Records, ::Cell: AsRef, { let widths = SpannedGridDimension::width(records, cfg); let total_width = get_table_total_width(&widths, cfg); (widths, total_width) } fn get_table_total_width(list: &[usize], cfg: &SpannedConfig) -> usize { let margin = cfg.get_margin(); list.iter().sum::() + cfg.count_vertical(list.len()) + margin.left.size + margin.right.size } tabled-0.18.0/src/settings/width/width_list.rs000064400000000000000000000020241046102023000174310ustar 00000000000000use std::iter::FromIterator; use crate::{ grid::dimension::CompleteDimensionVecRecords, grid::records::Records, settings::TableOption, }; /// A structure used to set [`Table`] width via a list of columns widths. /// /// [`Table`]: crate::Table #[derive(Debug)] pub struct WidthList { list: Vec, } impl WidthList { /// Creates a new object. pub fn new(list: Vec) -> Self { Self { list } } } impl From> for WidthList { fn from(list: Vec) -> Self { Self::new(list) } } impl FromIterator for WidthList { fn from_iter>(iter: T) -> Self { Self::new(iter.into_iter().collect()) } } impl TableOption> for WidthList where R: Records, { fn change(self, records: &mut R, _: &mut C, dimension: &mut CompleteDimensionVecRecords<'_>) { if self.list.len() < records.count_columns() { return; } dimension.set_widths(self.list); } } tabled-0.18.0/src/settings/width/wrap.rs000064400000000000000000001405261046102023000162420ustar 00000000000000//! This module contains [`Wrap`] structure, used to decrease width of a [`Table`]s or a cell on a [`Table`] by wrapping it's content //! to a new line. //! //! [`Table`]: crate::Table use crate::{ grid::{ config::SpannedConfig, config::{ColoredConfig, Entity}, dimension::CompleteDimensionVecRecords, records::{EmptyRecords, ExactRecords, IntoRecords, PeekableRecords, Records, RecordsMut}, util::string::{get_char_width, get_text_width}, }, settings::{ measurement::Measurement, peaker::{Peaker, PriorityNone}, width::Width, CellOption, TableOption, }, }; #[cfg(not(feature = "ansi"))] use crate::grid::util::string::get_string_width; use super::util::{get_table_widths, get_table_widths_with_total}; use crate::util::string::split_at_width; /// Wrap wraps a string to a new line in case it exceeds the provided max boundary. /// Otherwise keeps the content of a cell untouched. /// /// The function is color aware if a `color` feature is on. /// /// Be aware that it doesn't consider padding. /// So if you want to set a exact width you might need to use [`Padding`] to set it to 0. /// /// ## Example /// /// ``` /// use tabled::{Table, settings::{object::Segment, width::Width, Modify}}; /// /// let table = Table::new(&["Hello World!"]) /// .with(Modify::new(Segment::all()).with(Width::wrap(3))); /// ``` /// /// [`Padding`]: crate::settings::Padding #[derive(Debug, Clone)] pub struct Wrap { width: W, keep_words: bool, priority: P, } impl Wrap { /// Creates a [`Wrap`] object pub fn new(width: W) -> Self where W: Measurement, { Wrap { width, keep_words: false, priority: PriorityNone::new(), } } } impl Wrap { /// Priority defines the logic by which a truncate will be applied when is done for the whole table. /// /// - [`PriorityNone`] which cuts the columns one after another. /// - [`PriorityMax`] cuts the biggest columns first. /// - [`PriorityMin`] cuts the lowest columns first. /// /// Be aware that it doesn't consider padding. /// So if you want to set a exact width you might need to use [`Padding`] to set it to 0. /// /// [`Padding`]: crate::settings::Padding /// [`PriorityMax`]: crate::settings::peaker::PriorityMax /// [`PriorityMin`]: crate::settings::peaker::PriorityMin pub fn priority(self, priority: PP) -> Wrap { Wrap { width: self.width, keep_words: self.keep_words, priority, } } /// Set the keep words option. /// /// If a wrapping point will be in a word, [`Wrap`] will /// preserve a word (if possible) and wrap the string before it. pub fn keep_words(mut self, on: bool) -> Self { self.keep_words = on; self } } impl Wrap<(), ()> { /// Wrap a given string pub fn wrap(text: &str, width: usize, keeping_words: bool) -> String { wrap_text(text, width, keeping_words) } } impl TableOption> for Wrap where W: Measurement, P: Peaker, R: Records + ExactRecords + PeekableRecords + RecordsMut, for<'a> &'a R: Records, for<'a> <<&'a R as Records>::Iter as IntoRecords>::Cell: AsRef, { fn change( self, records: &mut R, cfg: &mut ColoredConfig, dims: &mut CompleteDimensionVecRecords<'_>, ) { if records.count_rows() == 0 || records.count_columns() == 0 { return; } let width = self.width.measure(&*records, cfg); let (widths, total) = get_table_widths_with_total(&*records, cfg); if width >= total { return; } let priority = self.priority; let keep_words = self.keep_words; let widths = wrap_total_width(records, cfg, widths, total, width, keep_words, priority); dims.set_widths(widths); } } impl CellOption for Wrap where W: Measurement, R: Records + ExactRecords + PeekableRecords + RecordsMut, for<'a> &'a R: Records, for<'a> <<&'a R as Records>::Iter as IntoRecords>::Cell: AsRef, { fn change(self, records: &mut R, cfg: &mut ColoredConfig, entity: Entity) { let width = self.width.measure(&*records, cfg); let count_rows = records.count_rows(); let count_columns = records.count_columns(); for pos in entity.iter(count_rows, count_columns) { if !pos.is_covered((count_rows, count_columns).into()) { continue; } let text = records.get_text(pos); let cell_width = get_text_width(text); if cell_width <= width { continue; } let wrapped = wrap_text(text, width, self.keep_words); records.set(pos, wrapped); } } } fn wrap_total_width( records: &mut R, cfg: &mut ColoredConfig, mut widths: Vec, total_width: usize, width: usize, keep_words: bool, priority: P, ) -> Vec where R: Records + ExactRecords + PeekableRecords + RecordsMut, P: Peaker, for<'a> &'a R: Records, for<'a> <<&'a R as Records>::Iter as IntoRecords>::Cell: AsRef, { let shape = (records.count_rows(), records.count_columns()); let min_widths = get_table_widths(EmptyRecords::from(shape), cfg); decrease_widths(&mut widths, &min_widths, total_width, width, priority); let points = get_decrease_cell_list(cfg, &widths, &min_widths, shape); for ((row, col), width) in points { let mut wrap = Wrap::new(width); wrap.keep_words = keep_words; >::change(wrap, records, cfg, (row, col).into()); } widths } #[cfg(not(feature = "ansi"))] pub(crate) fn wrap_text(text: &str, width: usize, keep_words: bool) -> String { if width == 0 { return String::new(); } if keep_words { split_keeping_words(text, width, "\n") } else { chunks(text, width).join("\n") } } #[cfg(feature = "ansi")] pub(crate) fn wrap_text(text: &str, width: usize, keep_words: bool) -> String { use crate::util::string::strip_osc; if width == 0 { return String::new(); } let (text, url): (String, Option) = strip_osc(text); let (prefix, suffix) = build_link_prefix_suffix(url); if keep_words { split_keeping_words(&text, width, &prefix, &suffix) } else { chunks(&text, width, &prefix, &suffix).join("\n") } } #[cfg(feature = "ansi")] fn build_link_prefix_suffix(url: Option) -> (String, String) { match url { Some(url) => { // https://gist.github.com/egmontkob/eb114294efbcd5adb1944c9f3cb5feda let osc8 = "\x1b]8;;"; let st = "\x1b\\"; (format!("{osc8}{url}{st}"), format!("{osc8}{st}")) } None => ("".to_string(), "".to_string()), } } #[cfg(not(feature = "ansi"))] fn chunks(s: &str, width: usize) -> Vec { const REPLACEMENT: char = '\u{FFFD}'; if width == 0 { return Vec::new(); } let mut prev_newline = false; let mut buf = String::with_capacity(width); let mut list = Vec::new(); let mut i = 0; for c in s.chars() { if c == '\n' { if buf.is_empty() { list.push(String::new()); } else { list.push(buf); } buf = String::with_capacity(width); i = 0; prev_newline = true; continue; } let c_width = get_char_width(c); if i + c_width > width { let count_unknowns = width - i; buf.extend(std::iter::repeat(REPLACEMENT).take(count_unknowns)); i += count_unknowns; } else { buf.push(c); i += c_width; } if i == width { list.push(buf); buf = String::with_capacity(width); i = 0; prev_newline = false; } } if !buf.is_empty() || prev_newline { list.push(buf); } list } #[cfg(feature = "ansi")] fn chunks(s: &str, width: usize, prefix: &str, suffix: &str) -> Vec { use std::fmt::Write; if width == 0 { return Vec::new(); } let mut list = Vec::new(); let mut line = String::with_capacity(width); let mut line_width = 0; for b in ansi_str::get_blocks(s) { let text_style = b.style(); let mut text_slice = b.text(); if text_slice.is_empty() { continue; } let available_space = width - line_width; if available_space == 0 { list.push(line); line = String::with_capacity(width); line_width = 0; } line.push_str(prefix); let _ = write!(&mut line, "{}", text_style.start()); while !text_slice.is_empty() { let available_space = width - line_width; let part_width = get_text_width(text_slice); if part_width <= available_space { line.push_str(text_slice); line_width += part_width; if available_space == 0 { let _ = write!(&mut line, "{}", text_style.end()); line.push_str(suffix); list.push(line); line = String::with_capacity(width); line.push_str(prefix); line_width = 0; let _ = write!(&mut line, "{}", text_style.start()); } break; } let (lhs, rhs, (unknowns, split_char)) = split_string_at(text_slice, available_space); text_slice = &rhs[split_char..]; line.push_str(lhs); line_width += get_text_width(lhs); const REPLACEMENT: char = '\u{FFFD}'; line.extend(std::iter::repeat(REPLACEMENT).take(unknowns)); line_width += unknowns; if line_width == width { let _ = write!(&mut line, "{}", text_style.end()); line.push_str(suffix); list.push(line); line = String::with_capacity(width); line.push_str(prefix); line_width = 0; let _ = write!(&mut line, "{}", text_style.start()); } } if line_width > 0 { let _ = write!(&mut line, "{}", text_style.end()); } } if line_width > 0 { line.push_str(suffix); list.push(line); } list } #[cfg(not(feature = "ansi"))] fn split_keeping_words(s: &str, width: usize, sep: &str) -> String { const REPLACEMENT: char = '\u{FFFD}'; let mut lines = Vec::new(); let mut line = String::with_capacity(width); let mut line_width = 0; let mut is_first_word = true; for word in s.split(' ') { if !is_first_word { let line_has_space = line_width < width; if line_has_space { line.push(' '); line_width += 1; is_first_word = false; } } if is_first_word { is_first_word = false; } let word_width = get_string_width(word); let line_has_space = line_width + word_width <= width; if line_has_space { line.push_str(word); line_width += word_width; continue; } if word_width <= width { // the word can be fit to 'width' so we put it on new line line.extend(std::iter::repeat(' ').take(width - line_width)); lines.push(line); line = String::with_capacity(width); line_width = 0; line.push_str(word); line_width += word_width; is_first_word = false; } else { // the word is too long any way so we split it let mut word_part = word; while !word_part.is_empty() { let available_space = width - line_width; let (lhs, rhs, (unknowns, split_char)) = split_string_at(word_part, available_space); word_part = &rhs[split_char..]; line_width += get_string_width(lhs) + unknowns; is_first_word = false; line.push_str(lhs); line.extend(std::iter::repeat(REPLACEMENT).take(unknowns)); if line_width == width { lines.push(line); line = String::with_capacity(width); line_width = 0; is_first_word = true; } } } } if line_width > 0 { line.extend(std::iter::repeat(' ').take(width - line_width)); lines.push(line); } lines.join(sep) } #[cfg(feature = "ansi")] fn split_keeping_words(text: &str, width: usize, prefix: &str, suffix: &str) -> String { if text.is_empty() || width == 0 { return String::new(); } let stripped_text = ansi_str::AnsiStr::ansi_strip(text); let mut word_width = 0; let mut word_chars = 0; let mut blocks = parsing::Blocks::new(ansi_str::get_blocks(text)); let mut buf = parsing::MultilineBuffer::new(width); buf.set_prefix(prefix); buf.set_suffix(suffix); for c in stripped_text.chars() { match c { ' ' => { parsing::handle_word(&mut buf, &mut blocks, word_chars, word_width, 1); word_chars = 0; word_width = 0; } '\n' => { parsing::handle_word(&mut buf, &mut blocks, word_chars, word_width, 1); word_chars = 0; word_width = 0; } _ => { word_width += get_char_width(c); word_chars += 1; } } } if word_chars > 0 { parsing::handle_word(&mut buf, &mut blocks, word_chars, word_width, 0); buf.finish_line(&blocks); } buf.into_string() } #[cfg(feature = "ansi")] mod parsing { use super::get_char_width; use ansi_str::{AnsiBlock, AnsiBlockIter, Style}; use std::fmt::Write; pub(super) struct Blocks<'a> { iter: AnsiBlockIter<'a>, current: Option>, } impl<'a> Blocks<'a> { pub(super) fn new(iter: AnsiBlockIter<'a>) -> Self { Self { iter, current: None, } } pub(super) fn next_block(&mut self) -> Option> { self.current .take() .or_else(|| self.iter.next().map(RelativeBlock::new)) } } pub(super) struct RelativeBlock<'a> { block: AnsiBlock<'a>, pos: usize, } impl<'a> RelativeBlock<'a> { pub(super) fn new(block: AnsiBlock<'a>) -> Self { Self { block, pos: 0 } } pub(super) fn get_text(&self) -> &str { &self.block.text()[self.pos..] } pub(super) fn get_origin(&self) -> &str { self.block.text() } pub(super) fn get_style(&self) -> &Style { self.block.style() } } pub(super) struct MultilineBuffer<'a> { buf: String, width_last: usize, width: usize, prefix: &'a str, suffix: &'a str, } impl<'a> MultilineBuffer<'a> { pub(super) fn new(width: usize) -> Self { Self { buf: String::new(), width_last: 0, prefix: "", suffix: "", width, } } pub(super) fn into_string(self) -> String { self.buf } pub(super) fn set_suffix(&mut self, suffix: &'a str) { self.suffix = suffix; } pub(super) fn set_prefix(&mut self, prefix: &'a str) { self.prefix = prefix; } pub(super) fn max_width(&self) -> usize { self.width } pub(super) fn available_width(&self) -> usize { self.width - self.width_last } pub(super) fn fill(&mut self, c: char) -> usize { debug_assert_eq!(get_char_width(c), 1); let rest_width = self.available_width(); for _ in 0..rest_width { self.buf.push(c); } rest_width } pub(super) fn set_next_line(&mut self, blocks: &Blocks<'_>) { if let Some(block) = &blocks.current { let _ = self .buf .write_fmt(format_args!("{}", block.get_style().end())); } self.buf.push_str(self.suffix); let _ = self.fill(' '); self.buf.push('\n'); self.width_last = 0; self.buf.push_str(self.prefix); if let Some(block) = &blocks.current { let _ = self .buf .write_fmt(format_args!("{}", block.get_style().start())); } } pub(super) fn finish_line(&mut self, blocks: &Blocks<'_>) { if let Some(block) = &blocks.current { let _ = self .buf .write_fmt(format_args!("{}", block.get_style().end())); } self.buf.push_str(self.suffix); let _ = self.fill(' '); self.width_last = 0; } pub(super) fn read_chars(&mut self, block: &RelativeBlock<'_>, n: usize) -> (usize, usize) { let mut count_chars = 0; let mut count_bytes = 0; for c in block.get_text().chars() { if count_chars == n { break; } count_chars += 1; count_bytes += c.len_utf8(); let cwidth = get_char_width(c); let available_space = self.width - self.width_last; if available_space == 0 { let _ = self .buf .write_fmt(format_args!("{}", block.get_style().end())); self.buf.push_str(self.suffix); self.buf.push('\n'); self.buf.push_str(self.prefix); let _ = self .buf .write_fmt(format_args!("{}", block.get_style().start())); self.width_last = 0; } let is_enough_space = self.width_last + cwidth <= self.width; if !is_enough_space { // thereatically a cwidth can be 2 but buf_width is 1 // but it handled here too; const REPLACEMENT: char = '\u{FFFD}'; let _ = self.fill(REPLACEMENT); self.width_last = self.width; } else { self.buf.push(c); self.width_last += cwidth; } } (count_chars, count_bytes) } pub(super) fn read_chars_unchecked( &mut self, block: &RelativeBlock<'_>, n: usize, ) -> (usize, usize) { let mut count_chars = 0; let mut count_bytes = 0; for c in block.get_text().chars() { if count_chars == n { break; } count_chars += 1; count_bytes += c.len_utf8(); let cwidth = get_char_width(c); self.width_last += cwidth; self.buf.push(c); } debug_assert!(self.width_last <= self.width); (count_chars, count_bytes) } } pub(super) fn read_chars(buf: &mut MultilineBuffer<'_>, blocks: &mut Blocks<'_>, n: usize) { let mut n = n; while n > 0 { let is_new_block = blocks.current.is_none(); let mut block = blocks.next_block().expect("Must never happen"); if is_new_block { buf.buf.push_str(buf.prefix); let _ = buf .buf .write_fmt(format_args!("{}", block.get_style().start())); } let (read_count, read_bytes) = buf.read_chars(&block, n); if block.pos + read_bytes == block.get_origin().len() { let _ = buf .buf .write_fmt(format_args!("{}", block.get_style().end())); } else { block.pos += read_bytes; blocks.current = Some(block); } n -= read_count; } } pub(super) fn read_chars_unchecked( buf: &mut MultilineBuffer<'_>, blocks: &mut Blocks<'_>, n: usize, ) { let mut n = n; while n > 0 { let is_new_block = blocks.current.is_none(); let mut block = blocks.next_block().expect("Must never happen"); if is_new_block { buf.buf.push_str(buf.prefix); let _ = buf .buf .write_fmt(format_args!("{}", block.get_style().start())); } let (read_count, read_bytes) = buf.read_chars_unchecked(&block, n); if block.pos + read_bytes == block.get_origin().len() { let _ = buf .buf .write_fmt(format_args!("{}", block.get_style().end())); } else { block.pos += read_bytes; blocks.current = Some(block); } n -= read_count; } } pub(super) fn handle_word( buf: &mut MultilineBuffer<'_>, blocks: &mut Blocks<'_>, word_chars: usize, word_width: usize, additional_read: usize, ) { if word_chars > 0 { let has_line_space = word_width <= buf.available_width(); let is_word_too_big = word_width > buf.max_width(); if is_word_too_big { read_chars(buf, blocks, word_chars + additional_read); } else if has_line_space { read_chars_unchecked(buf, blocks, word_chars); if additional_read > 0 { read_chars(buf, blocks, additional_read); } } else { buf.set_next_line(&*blocks); read_chars_unchecked(buf, blocks, word_chars); if additional_read > 0 { read_chars(buf, blocks, additional_read); } } return; } let has_current_line_space = additional_read <= buf.available_width(); if has_current_line_space { read_chars_unchecked(buf, blocks, additional_read); } else { buf.set_next_line(&*blocks); read_chars_unchecked(buf, blocks, additional_read); } } } fn split_string_at(text: &str, at: usize) -> (&str, &str, (usize, usize)) { let (length, width, split_char_size) = split_at_width(text, at); let count_unknowns = if split_char_size > 0 { at - width } else { 0 }; let (lhs, rhs) = text.split_at(length); (lhs, rhs, (count_unknowns, split_char_size)) } fn decrease_widths( widths: &mut [usize], min_widths: &[usize], total_width: usize, mut width: usize, mut peeaker: F, ) where F: Peaker, { let mut empty_list = 0; for col in 0..widths.len() { if widths[col] == 0 || widths[col] <= min_widths[col] { empty_list += 1; } } while width != total_width { if empty_list == widths.len() { break; } let col = match peeaker.peak(min_widths, widths) { Some(col) => col, None => break, }; if widths[col] == 0 || widths[col] <= min_widths[col] { continue; } widths[col] -= 1; if widths[col] == 0 || widths[col] <= min_widths[col] { empty_list += 1; } width += 1; } } fn get_decrease_cell_list( cfg: &SpannedConfig, widths: &[usize], min_widths: &[usize], shape: (usize, usize), ) -> Vec<((usize, usize), usize)> { let mut points = Vec::new(); (0..shape.1).for_each(|col| { (0..shape.0) .filter(|&row| cfg.is_cell_visible((row, col).into())) .for_each(|row| { let (width, width_min) = match cfg.get_column_span((row, col).into()) { Some(span) => { let width = (col..col + span).map(|i| widths[i]).sum::(); let min_width = (col..col + span).map(|i| min_widths[i]).sum::(); let count_borders = count_borders(cfg, col, col + span, shape.1); (width + count_borders, min_width + count_borders) } None => (widths[col], min_widths[col]), }; if width >= width_min { let padding = cfg.get_padding((row, col).into()); let width = width.saturating_sub(padding.left.size + padding.right.size); points.push(((row, col), width)); } }); }); points } fn count_borders(cfg: &SpannedConfig, start: usize, end: usize, count_columns: usize) -> usize { (start..end) .skip(1) .filter(|&i| cfg.has_vertical(i, count_columns)) .count() } #[cfg(test)] mod tests { use super::*; #[test] fn split_test() { #[cfg(not(feature = "ansi"))] let split = |text, width| chunks(text, width).join("\n"); #[cfg(feature = "ansi")] let split = |text, width| chunks(text, width, "", "").join("\n"); assert_eq!(split("123456", 0), ""); assert_eq!(split("123456", 1), "1\n2\n3\n4\n5\n6"); assert_eq!(split("123456", 2), "12\n34\n56"); assert_eq!(split("12345", 2), "12\n34\n5"); assert_eq!(split("123456", 6), "123456"); assert_eq!(split("123456", 10), "123456"); assert_eq!(split("😳😳😳😳😳", 1), "�\n�\n�\n�\n�"); assert_eq!(split("😳😳😳😳😳", 2), "😳\n😳\n😳\n😳\n😳"); assert_eq!(split("😳😳😳😳😳", 3), "😳�\n😳�\n😳"); assert_eq!(split("😳😳😳😳😳", 6), "😳😳😳\n😳😳"); assert_eq!(split("😳😳😳😳😳", 20), "😳😳😳😳😳"); assert_eq!(split("😳123😳", 1), "�\n1\n2\n3\n�"); assert_eq!(split("😳12😳3", 1), "�\n1\n2\n�\n3"); } #[test] fn chunks_test() { #[allow(clippy::redundant_closure)] #[cfg(not(feature = "ansi"))] let chunks = |text, width| chunks(text, width); #[cfg(feature = "ansi")] let chunks = |text, width| chunks(text, width, "", ""); assert_eq!(chunks("123456", 0), [""; 0]); assert_eq!(chunks("123456", 1), ["1", "2", "3", "4", "5", "6"]); assert_eq!(chunks("123456", 2), ["12", "34", "56"]); assert_eq!(chunks("12345", 2), ["12", "34", "5"]); assert_eq!(chunks("😳😳😳😳😳", 1), ["�", "�", "�", "�", "�"]); assert_eq!(chunks("😳😳😳😳😳", 2), ["😳", "😳", "😳", "😳", "😳"]); assert_eq!(chunks("😳😳😳😳😳", 3), ["😳�", "😳�", "😳"]); } #[cfg(not(feature = "ansi"))] #[test] fn split_by_line_keeping_words_test() { let split_keeping_words = |text, width| split_keeping_words(text, width, "\n"); assert_eq!(split_keeping_words("123456", 1), "1\n2\n3\n4\n5\n6"); assert_eq!(split_keeping_words("123456", 2), "12\n34\n56"); assert_eq!(split_keeping_words("12345", 2), "12\n34\n5 "); assert_eq!(split_keeping_words("😳😳😳😳😳", 1), "�\n�\n�\n�\n�"); assert_eq!(split_keeping_words("111 234 1", 4), "111 \n234 \n1 "); } #[cfg(feature = "ansi")] #[test] fn split_by_line_keeping_words_test() { #[cfg(feature = "ansi")] let split_keeping_words = |text, width| split_keeping_words(text, width, "", ""); assert_eq!(split_keeping_words("123456", 1), "1\n2\n3\n4\n5\n6"); assert_eq!(split_keeping_words("123456", 2), "12\n34\n56"); assert_eq!(split_keeping_words("12345", 2), "12\n34\n5 "); assert_eq!(split_keeping_words("😳😳😳😳😳", 1), "�\n�\n�\n�\n�"); assert_eq!(split_keeping_words("111 234 1", 4), "111 \n234 \n1 "); } #[cfg(feature = "ansi")] #[test] fn split_by_line_keeping_words_color_test() { #[cfg(feature = "ansi")] let split_keeping_words = |text, width| split_keeping_words(text, width, "", ""); #[cfg(not(feature = "ansi"))] let split_keeping_words = |text, width| split_keeping_words(text, width, "\n"); let text = "\u{1b}[36mJapanese “vacancy” button\u{1b}[0m"; assert_eq!(split_keeping_words(text, 2), "\u{1b}[36mJa\u{1b}[39m\n\u{1b}[36mpa\u{1b}[39m\n\u{1b}[36mne\u{1b}[39m\n\u{1b}[36mse\u{1b}[39m\n\u{1b}[36m “\u{1b}[39m\n\u{1b}[36mva\u{1b}[39m\n\u{1b}[36mca\u{1b}[39m\n\u{1b}[36mnc\u{1b}[39m\n\u{1b}[36my”\u{1b}[39m\n\u{1b}[36m b\u{1b}[39m\n\u{1b}[36mut\u{1b}[39m\n\u{1b}[36mto\u{1b}[39m\n\u{1b}[36mn\u{1b}[39m "); assert_eq!(split_keeping_words(text, 1), "\u{1b}[36mJ\u{1b}[39m\n\u{1b}[36ma\u{1b}[39m\n\u{1b}[36mp\u{1b}[39m\n\u{1b}[36ma\u{1b}[39m\n\u{1b}[36mn\u{1b}[39m\n\u{1b}[36me\u{1b}[39m\n\u{1b}[36ms\u{1b}[39m\n\u{1b}[36me\u{1b}[39m\n\u{1b}[36m \u{1b}[39m\n\u{1b}[36m“\u{1b}[39m\n\u{1b}[36mv\u{1b}[39m\n\u{1b}[36ma\u{1b}[39m\n\u{1b}[36mc\u{1b}[39m\n\u{1b}[36ma\u{1b}[39m\n\u{1b}[36mn\u{1b}[39m\n\u{1b}[36mc\u{1b}[39m\n\u{1b}[36my\u{1b}[39m\n\u{1b}[36m”\u{1b}[39m\n\u{1b}[36m \u{1b}[39m\n\u{1b}[36mb\u{1b}[39m\n\u{1b}[36mu\u{1b}[39m\n\u{1b}[36mt\u{1b}[39m\n\u{1b}[36mt\u{1b}[39m\n\u{1b}[36mo\u{1b}[39m\n\u{1b}[36mn\u{1b}[39m"); } #[cfg(feature = "ansi")] #[test] fn split_by_line_keeping_words_color_2_test() { use ansi_str::AnsiStr; #[cfg(feature = "ansi")] let split_keeping_words = |text, width| split_keeping_words(text, width, "", ""); #[cfg(not(feature = "ansi"))] let split_keeping_words = |text, width| split_keeping_words(text, width, "\n"); let text = "\u{1b}[37mTigre Ecuador OMYA Andina 3824909999 Calcium carbonate Colombia\u{1b}[0m"; assert_eq!( split_keeping_words(text, 2) .ansi_split("\n") .collect::>(), [ "\u{1b}[37mTi\u{1b}[39m", "\u{1b}[37mgr\u{1b}[39m", "\u{1b}[37me \u{1b}[39m", "\u{1b}[37mEc\u{1b}[39m", "\u{1b}[37mua\u{1b}[39m", "\u{1b}[37mdo\u{1b}[39m", "\u{1b}[37mr \u{1b}[39m", "\u{1b}[37m \u{1b}[39m", "\u{1b}[37mOM\u{1b}[39m", "\u{1b}[37mYA\u{1b}[39m", "\u{1b}[37m A\u{1b}[39m", "\u{1b}[37mnd\u{1b}[39m", "\u{1b}[37min\u{1b}[39m", "\u{1b}[37ma \u{1b}[39m", "\u{1b}[37m \u{1b}[39m", "\u{1b}[37m \u{1b}[39m", "\u{1b}[37m38\u{1b}[39m", "\u{1b}[37m24\u{1b}[39m", "\u{1b}[37m90\u{1b}[39m", "\u{1b}[37m99\u{1b}[39m", "\u{1b}[37m99\u{1b}[39m", "\u{1b}[37m \u{1b}[39m", "\u{1b}[37m \u{1b}[39m", "\u{1b}[37m \u{1b}[39m", "\u{1b}[37mCa\u{1b}[39m", "\u{1b}[37mlc\u{1b}[39m", "\u{1b}[37miu\u{1b}[39m", "\u{1b}[37mm \u{1b}[39m", "\u{1b}[37mca\u{1b}[39m", "\u{1b}[37mrb\u{1b}[39m", "\u{1b}[37mon\u{1b}[39m", "\u{1b}[37mat\u{1b}[39m", "\u{1b}[37me \u{1b}[39m", "\u{1b}[37m \u{1b}[39m", "\u{1b}[37m \u{1b}[39m", "\u{1b}[37m \u{1b}[39m", "\u{1b}[37mCo\u{1b}[39m", "\u{1b}[37mlo\u{1b}[39m", "\u{1b}[37mmb\u{1b}[39m", "\u{1b}[37mia\u{1b}[39m" ] ); assert_eq!( split_keeping_words(text, 1) .ansi_split("\n") .collect::>(), [ "\u{1b}[37mT\u{1b}[39m", "\u{1b}[37mi\u{1b}[39m", "\u{1b}[37mg\u{1b}[39m", "\u{1b}[37mr\u{1b}[39m", "\u{1b}[37me\u{1b}[39m", "\u{1b}[37m \u{1b}[39m", "\u{1b}[37mE\u{1b}[39m", "\u{1b}[37mc\u{1b}[39m", "\u{1b}[37mu\u{1b}[39m", "\u{1b}[37ma\u{1b}[39m", "\u{1b}[37md\u{1b}[39m", "\u{1b}[37mo\u{1b}[39m", "\u{1b}[37mr\u{1b}[39m", "\u{1b}[37m \u{1b}[39m", "\u{1b}[37m \u{1b}[39m", "\u{1b}[37m \u{1b}[39m", "\u{1b}[37mO\u{1b}[39m", "\u{1b}[37mM\u{1b}[39m", "\u{1b}[37mY\u{1b}[39m", "\u{1b}[37mA\u{1b}[39m", "\u{1b}[37m \u{1b}[39m", "\u{1b}[37mA\u{1b}[39m", "\u{1b}[37mn\u{1b}[39m", "\u{1b}[37md\u{1b}[39m", "\u{1b}[37mi\u{1b}[39m", "\u{1b}[37mn\u{1b}[39m", "\u{1b}[37ma\u{1b}[39m", "\u{1b}[37m \u{1b}[39m", "\u{1b}[37m \u{1b}[39m", "\u{1b}[37m \u{1b}[39m", "\u{1b}[37m \u{1b}[39m", "\u{1b}[37m \u{1b}[39m", "\u{1b}[37m3\u{1b}[39m", "\u{1b}[37m8\u{1b}[39m", "\u{1b}[37m2\u{1b}[39m", "\u{1b}[37m4\u{1b}[39m", "\u{1b}[37m9\u{1b}[39m", "\u{1b}[37m0\u{1b}[39m", "\u{1b}[37m9\u{1b}[39m", "\u{1b}[37m9\u{1b}[39m", "\u{1b}[37m9\u{1b}[39m", "\u{1b}[37m9\u{1b}[39m", "\u{1b}[37m \u{1b}[39m", "\u{1b}[37m \u{1b}[39m", "\u{1b}[37m \u{1b}[39m", "\u{1b}[37m \u{1b}[39m", "\u{1b}[37m \u{1b}[39m", "\u{1b}[37m \u{1b}[39m", "\u{1b}[37mC\u{1b}[39m", "\u{1b}[37ma\u{1b}[39m", "\u{1b}[37ml\u{1b}[39m", "\u{1b}[37mc\u{1b}[39m", "\u{1b}[37mi\u{1b}[39m", "\u{1b}[37mu\u{1b}[39m", "\u{1b}[37mm\u{1b}[39m", "\u{1b}[37m \u{1b}[39m", "\u{1b}[37mc\u{1b}[39m", "\u{1b}[37ma\u{1b}[39m", "\u{1b}[37mr\u{1b}[39m", "\u{1b}[37mb\u{1b}[39m", "\u{1b}[37mo\u{1b}[39m", "\u{1b}[37mn\u{1b}[39m", "\u{1b}[37ma\u{1b}[39m", "\u{1b}[37mt\u{1b}[39m", "\u{1b}[37me\u{1b}[39m", "\u{1b}[37m \u{1b}[39m", "\u{1b}[37m \u{1b}[39m", "\u{1b}[37m \u{1b}[39m", "\u{1b}[37m \u{1b}[39m", "\u{1b}[37m \u{1b}[39m", "\u{1b}[37m \u{1b}[39m", "\u{1b}[37m \u{1b}[39m", "\u{1b}[37mC\u{1b}[39m", "\u{1b}[37mo\u{1b}[39m", "\u{1b}[37ml\u{1b}[39m", "\u{1b}[37mo\u{1b}[39m", "\u{1b}[37mm\u{1b}[39m", "\u{1b}[37mb\u{1b}[39m", "\u{1b}[37mi\u{1b}[39m", "\u{1b}[37ma\u{1b}[39m" ] ) } #[cfg(feature = "ansi")] #[test] fn split_by_line_keeping_words_color_3_test() { let split = |text, width| split_keeping_words(text, width, "", ""); assert_eq!( split( "\u{1b}[37m🚵🏻🚵🏻🚵🏻🚵🏻🚵🏻🚵🏻🚵🏻🚵🏻🚵🏻🚵🏻\u{1b}[0m", 3, ), "\u{1b}[37m🚵�\u{1b}[39m\n\u{1b}[37m🚵�\u{1b}[39m\n\u{1b}[37m🚵�\u{1b}[39m\n\u{1b}[37m🚵�\u{1b}[39m\n\u{1b}[37m🚵�\u{1b}[39m\n\u{1b}[37m🚵�\u{1b}[39m\n\u{1b}[37m🚵�\u{1b}[39m\n\u{1b}[37m🚵�\u{1b}[39m\n\u{1b}[37m🚵�\u{1b}[39m\n\u{1b}[37m🚵�\u{1b}[39m", ); assert_eq!( split("\u{1b}[37mthis is a long sentence\u{1b}[0m", 7), "\u{1b}[37mthis is\u{1b}[39m\n\u{1b}[37m a long\u{1b}[39m\n\u{1b}[37m senten\u{1b}[39m\n\u{1b}[37mce\u{1b}[39m " ); assert_eq!( split("\u{1b}[37mHello World\u{1b}[0m", 7), "\u{1b}[37mHello \u{1b}[39m \n\u{1b}[37mWorld\u{1b}[39m " ); assert_eq!( split("\u{1b}[37mHello Wo\u{1b}[37mrld\u{1b}[0m", 7), "\u{1b}[37mHello \u{1b}[39m \n\u{1b}[37mWo\u{1b}[39m\u{1b}[37mrld\u{1b}[39m " ); assert_eq!( split("\u{1b}[37mHello Wo\u{1b}[37mrld\u{1b}[0m", 8), "\u{1b}[37mHello \u{1b}[39m \n\u{1b}[37mWo\u{1b}[39m\u{1b}[37mrld\u{1b}[39m " ); } #[cfg(not(feature = "ansi"))] #[test] fn split_keeping_words_4_test() { let split_keeping_words = |text, width| split_keeping_words(text, width, "\n"); assert_eq!(split_keeping_words("12345678", 3,), "123\n456\n78 "); assert_eq!(split_keeping_words("12345678", 2,), "12\n34\n56\n78"); } #[cfg(feature = "ansi")] #[test] fn split_keeping_words_4_test() { let split_keeping_words = |text, width| split_keeping_words(text, width, "", ""); #[cfg(not(feature = "ansi"))] let split_keeping_words = |text, width| split_keeping_words(text, width, "\n"); assert_eq!(split_keeping_words("12345678", 3,), "123\n456\n78 "); assert_eq!(split_keeping_words("12345678", 2,), "12\n34\n56\n78"); } #[cfg(feature = "ansi")] #[test] fn chunks_test_with_prefix_and_suffix() { assert_eq!(chunks("123456", 0, "^", "$"), ["^$"; 0]); assert_eq!( chunks("123456", 1, "^", "$"), ["^1$", "^2$", "^3$", "^4$", "^5$", "^6$"] ); assert_eq!(chunks("123456", 2, "^", "$"), ["^12$", "^34$", "^56$"]); assert_eq!(chunks("12345", 2, "^", "$"), ["^12$", "^34$", "^5$"]); assert_eq!( chunks("😳😳😳😳😳", 1, "^", "$"), ["^�$", "^�$", "^�$", "^�$", "^�$"] ); assert_eq!( chunks("😳😳😳😳😳", 2, "^", "$"), ["^😳$", "^😳$", "^😳$", "^😳$", "^😳$"] ); assert_eq!( chunks("😳😳😳😳😳", 3, "^", "$"), ["^😳�$", "^😳�$", "^😳$"] ); } #[cfg(feature = "ansi")] #[test] fn split_by_line_keeping_words_test_with_prefix_and_suffix() { assert_eq!( split_keeping_words("123456", 1, "^", "$"), "^1$\n^2$\n^3$\n^4$\n^5$\n^6$" ); assert_eq!( split_keeping_words("123456", 2, "^", "$"), "^12$\n^34$\n^56$" ); assert_eq!( split_keeping_words("12345", 2, "^", "$"), "^12$\n^34$\n^5$ " ); assert_eq!( split_keeping_words("😳😳😳😳😳", 1, "^", "$"), "^�$\n^�$\n^�$\n^�$\n^�$" ); } #[cfg(feature = "ansi")] #[test] fn split_by_line_keeping_words_color_2_test_with_prefix_and_suffix() { use ansi_str::AnsiStr; let text = "\u{1b}[37mTigre Ecuador OMYA Andina 3824909999 Calcium carbonate Colombia\u{1b}[0m"; assert_eq!( split_keeping_words(text, 2, "^", "$") .ansi_split("\n") .collect::>(), [ "^\u{1b}[37mTi\u{1b}[39m$", "^\u{1b}[37mgr\u{1b}[39m$", "^\u{1b}[37me \u{1b}[39m$", "^\u{1b}[37mEc\u{1b}[39m$", "^\u{1b}[37mua\u{1b}[39m$", "^\u{1b}[37mdo\u{1b}[39m$", "^\u{1b}[37mr \u{1b}[39m$", "^\u{1b}[37m \u{1b}[39m$", "^\u{1b}[37mOM\u{1b}[39m$", "^\u{1b}[37mYA\u{1b}[39m$", "^\u{1b}[37m A\u{1b}[39m$", "^\u{1b}[37mnd\u{1b}[39m$", "^\u{1b}[37min\u{1b}[39m$", "^\u{1b}[37ma \u{1b}[39m$", "^\u{1b}[37m \u{1b}[39m$", "^\u{1b}[37m \u{1b}[39m$", "^\u{1b}[37m38\u{1b}[39m$", "^\u{1b}[37m24\u{1b}[39m$", "^\u{1b}[37m90\u{1b}[39m$", "^\u{1b}[37m99\u{1b}[39m$", "^\u{1b}[37m99\u{1b}[39m$", "^\u{1b}[37m \u{1b}[39m$", "^\u{1b}[37m \u{1b}[39m$", "^\u{1b}[37m \u{1b}[39m$", "^\u{1b}[37mCa\u{1b}[39m$", "^\u{1b}[37mlc\u{1b}[39m$", "^\u{1b}[37miu\u{1b}[39m$", "^\u{1b}[37mm \u{1b}[39m$", "^\u{1b}[37mca\u{1b}[39m$", "^\u{1b}[37mrb\u{1b}[39m$", "^\u{1b}[37mon\u{1b}[39m$", "^\u{1b}[37mat\u{1b}[39m$", "^\u{1b}[37me \u{1b}[39m$", "^\u{1b}[37m \u{1b}[39m$", "^\u{1b}[37m \u{1b}[39m$", "^\u{1b}[37m \u{1b}[39m$", "^\u{1b}[37mCo\u{1b}[39m$", "^\u{1b}[37mlo\u{1b}[39m$", "^\u{1b}[37mmb\u{1b}[39m$", "^\u{1b}[37mia\u{1b}[39m$" ] ); assert_eq!( split_keeping_words(text, 1, "^", "$") .ansi_split("\n") .collect::>(), [ "^\u{1b}[37mT\u{1b}[39m$", "^\u{1b}[37mi\u{1b}[39m$", "^\u{1b}[37mg\u{1b}[39m$", "^\u{1b}[37mr\u{1b}[39m$", "^\u{1b}[37me\u{1b}[39m$", "^\u{1b}[37m \u{1b}[39m$", "^\u{1b}[37mE\u{1b}[39m$", "^\u{1b}[37mc\u{1b}[39m$", "^\u{1b}[37mu\u{1b}[39m$", "^\u{1b}[37ma\u{1b}[39m$", "^\u{1b}[37md\u{1b}[39m$", "^\u{1b}[37mo\u{1b}[39m$", "^\u{1b}[37mr\u{1b}[39m$", "^\u{1b}[37m \u{1b}[39m$", "^\u{1b}[37m \u{1b}[39m$", "^\u{1b}[37m \u{1b}[39m$", "^\u{1b}[37mO\u{1b}[39m$", "^\u{1b}[37mM\u{1b}[39m$", "^\u{1b}[37mY\u{1b}[39m$", "^\u{1b}[37mA\u{1b}[39m$", "^\u{1b}[37m \u{1b}[39m$", "^\u{1b}[37mA\u{1b}[39m$", "^\u{1b}[37mn\u{1b}[39m$", "^\u{1b}[37md\u{1b}[39m$", "^\u{1b}[37mi\u{1b}[39m$", "^\u{1b}[37mn\u{1b}[39m$", "^\u{1b}[37ma\u{1b}[39m$", "^\u{1b}[37m \u{1b}[39m$", "^\u{1b}[37m \u{1b}[39m$", "^\u{1b}[37m \u{1b}[39m$", "^\u{1b}[37m \u{1b}[39m$", "^\u{1b}[37m \u{1b}[39m$", "^\u{1b}[37m3\u{1b}[39m$", "^\u{1b}[37m8\u{1b}[39m$", "^\u{1b}[37m2\u{1b}[39m$", "^\u{1b}[37m4\u{1b}[39m$", "^\u{1b}[37m9\u{1b}[39m$", "^\u{1b}[37m0\u{1b}[39m$", "^\u{1b}[37m9\u{1b}[39m$", "^\u{1b}[37m9\u{1b}[39m$", "^\u{1b}[37m9\u{1b}[39m$", "^\u{1b}[37m9\u{1b}[39m$", "^\u{1b}[37m \u{1b}[39m$", "^\u{1b}[37m \u{1b}[39m$", "^\u{1b}[37m \u{1b}[39m$", "^\u{1b}[37m \u{1b}[39m$", "^\u{1b}[37m \u{1b}[39m$", "^\u{1b}[37m \u{1b}[39m$", "^\u{1b}[37mC\u{1b}[39m$", "^\u{1b}[37ma\u{1b}[39m$", "^\u{1b}[37ml\u{1b}[39m$", "^\u{1b}[37mc\u{1b}[39m$", "^\u{1b}[37mi\u{1b}[39m$", "^\u{1b}[37mu\u{1b}[39m$", "^\u{1b}[37mm\u{1b}[39m$", "^\u{1b}[37m \u{1b}[39m$", "^\u{1b}[37mc\u{1b}[39m$", "^\u{1b}[37ma\u{1b}[39m$", "^\u{1b}[37mr\u{1b}[39m$", "^\u{1b}[37mb\u{1b}[39m$", "^\u{1b}[37mo\u{1b}[39m$", "^\u{1b}[37mn\u{1b}[39m$", "^\u{1b}[37ma\u{1b}[39m$", "^\u{1b}[37mt\u{1b}[39m$", "^\u{1b}[37me\u{1b}[39m$", "^\u{1b}[37m \u{1b}[39m$", "^\u{1b}[37m \u{1b}[39m$", "^\u{1b}[37m \u{1b}[39m$", "^\u{1b}[37m \u{1b}[39m$", "^\u{1b}[37m \u{1b}[39m$", "^\u{1b}[37m \u{1b}[39m$", "^\u{1b}[37m \u{1b}[39m$", "^\u{1b}[37mC\u{1b}[39m$", "^\u{1b}[37mo\u{1b}[39m$", "^\u{1b}[37ml\u{1b}[39m$", "^\u{1b}[37mo\u{1b}[39m$", "^\u{1b}[37mm\u{1b}[39m$", "^\u{1b}[37mb\u{1b}[39m$", "^\u{1b}[37mi\u{1b}[39m$", "^\u{1b}[37ma\u{1b}[39m$" ] ) } #[cfg(feature = "ansi")] #[test] fn chunks_wrap_2() { let text = "\u{1b}[30mDebian\u{1b}[0m\u{1b}[31mDebian\u{1b}[0m\u{1b}[32mDebian\u{1b}[0m\u{1b}[33mDebian\u{1b}[0m\u{1b}[34mDebian\u{1b}[0m\u{1b}[35mDebian\u{1b}[0m\u{1b}[36mDebian\u{1b}[0m\u{1b}[37mDebian\u{1b}[0m\u{1b}[40mDebian\u{1b}[0m\u{1b}[41mDebian\u{1b}[0m\u{1b}[42mDebian\u{1b}[0m\u{1b}[43mDebian\u{1b}[0m\u{1b}[44mDebian\u{1b}[0m"; assert_eq!( chunks(text, 30, "", ""), [ "\u{1b}[30mDebian\u{1b}[39m\u{1b}[31mDebian\u{1b}[39m\u{1b}[32mDebian\u{1b}[39m\u{1b}[33mDebian\u{1b}[39m\u{1b}[34mDebian\u{1b}[39m", "\u{1b}[35mDebian\u{1b}[39m\u{1b}[36mDebian\u{1b}[39m\u{1b}[37mDebian\u{1b}[39m\u{1b}[40mDebian\u{1b}[49m\u{1b}[41mDebian\u{1b}[49m", "\u{1b}[42mDebian\u{1b}[49m\u{1b}[43mDebian\u{1b}[49m\u{1b}[44mDebian\u{1b}[49m", ] ); } #[cfg(feature = "ansi")] #[test] fn chunks_wrap_3() { let text = "\u{1b}[37mCreate bytes from the \u{1b}[0m\u{1b}[7;34marg\u{1b}[0m\u{1b}[37muments.\u{1b}[0m"; assert_eq!( chunks(text, 22, "", ""), [ "\u{1b}[37mCreate bytes from the \u{1b}[39m", "\u{1b}[7m\u{1b}[34marg\u{1b}[27m\u{1b}[39m\u{1b}[37muments.\u{1b}[39m" ] ); } #[cfg(feature = "ansi")] #[test] fn chunks_wrap_3_keeping_words() { let text = "\u{1b}[37mCreate bytes from the \u{1b}[0m\u{1b}[7;34marg\u{1b}[0m\u{1b}[37muments.\u{1b}[0m"; assert_eq!( split_keeping_words(text, 22, "", ""), "\u{1b}[37mCreate bytes from the \u{1b}[39m\n\u{1b}[7m\u{1b}[34marg\u{1b}[27m\u{1b}[39m\u{1b}[37muments.\u{1b}[39m " ); } #[cfg(feature = "ansi")] #[test] fn chunks_wrap_4() { let text = "\u{1b}[37mReturns the floor of a number (l\u{1b}[0m\u{1b}[41;37marg\u{1b}[0m\u{1b}[37mest integer less than or equal to that number).\u{1b}[0m"; assert_eq!( chunks(text, 10, "", ""), [ "\u{1b}[37mReturns th\u{1b}[39m", "\u{1b}[37me floor of\u{1b}[39m", "\u{1b}[37m a number \u{1b}[39m", "\u{1b}[37m(l\u{1b}[39m\u{1b}[37m\u{1b}[41marg\u{1b}[39m\u{1b}[49m\u{1b}[37mest i\u{1b}[39m", "\u{1b}[37mnteger les\u{1b}[39m", "\u{1b}[37ms than or \u{1b}[39m", "\u{1b}[37mequal to t\u{1b}[39m", "\u{1b}[37mhat number\u{1b}[39m", "\u{1b}[37m).\u{1b}[39m", ] ); } #[cfg(feature = "ansi")] #[test] fn chunks_wrap_4_keeping_words() { let text = "\u{1b}[37mReturns the floor of a number (l\u{1b}[0m\u{1b}[41;37marg\u{1b}[0m\u{1b}[37mest integer less than or equal to that number).\u{1b}[0m"; assert_eq!( split_keeping_words(text, 10, "", ""), concat!( "\u{1b}[37mReturns \u{1b}[39m \n", "\u{1b}[37mthe floor \u{1b}[39m\n", "\u{1b}[37mof a \u{1b}[39m \n", "\u{1b}[37mnumber \u{1b}[39m \n", "\u{1b}[37m(l\u{1b}[39m\u{1b}[37m\u{1b}[41marg\u{1b}[39m\u{1b}[49m\u{1b}[37mest \u{1b}[39m \n", "\u{1b}[37minteger \u{1b}[39m \n", "\u{1b}[37mless than \u{1b}[39m\n", "\u{1b}[37mor equal \u{1b}[39m \n", "\u{1b}[37mto that \u{1b}[39m \n", "\u{1b}[37mnumber).\u{1b}[39m ", ) ); } } tabled-0.18.0/src/tabled.rs000064400000000000000000000073731046102023000135470ustar 00000000000000use std::borrow::Cow; /// Tabled a trait responsible for providing a header fields and a row fields. /// /// It's urgent that `header` len is equal to `fields` len. /// /// ```text /// Self::headers().len() == self.fields().len() /// ``` pub trait Tabled { /// A length of fields and headers, /// which must be the same. const LENGTH: usize; /// Fields method must return a list of cells. /// /// The cells will be placed in the same row, preserving the order. fn fields(&self) -> Vec>; /// Headers must return a list of column names. fn headers() -> Vec>; } impl Tabled for &T where T: Tabled, { const LENGTH: usize = T::LENGTH; fn fields(&self) -> Vec> { T::fields(self) } fn headers() -> Vec> { T::headers() } } impl Tabled for Box where T: Tabled, { const LENGTH: usize = T::LENGTH; fn fields(&self) -> Vec> { T::fields(self) } fn headers() -> Vec> { T::headers() } } impl Tabled for Option where T: Tabled, { const LENGTH: usize = T::LENGTH; fn fields(&self) -> Vec> { match self { Some(value) => Tabled::fields(value), None => vec![Cow::Borrowed(""); Self::LENGTH], } } fn headers() -> Vec> { T::headers() } } macro_rules! tuple_table { ( $($name:ident)+ ) => { impl<$($name: Tabled),+> Tabled for ($($name,)+){ const LENGTH: usize = $($name::LENGTH+)+ 0; fn fields(&self) -> Vec> { #![allow(non_snake_case)] let ($($name,)+) = self; let mut fields = Vec::with_capacity(Self::LENGTH); $(fields.append(&mut $name.fields());)+ fields } fn headers() -> Vec> { let mut fields = Vec::with_capacity(Self::LENGTH); $(fields.append(&mut $name::headers());)+ fields } } }; } tuple_table! { A } tuple_table! { A B } tuple_table! { A B C } tuple_table! { A B C D } tuple_table! { A B C D E } tuple_table! { A B C D E F } macro_rules! default_table { ( $t:ty ) => { impl Tabled for $t { const LENGTH: usize = 1; fn fields(&self) -> Vec> { vec![Cow::Owned(self.to_string())] } fn headers() -> Vec> { vec![Cow::Borrowed(stringify!($t))] } } }; ( $t:ty = borrowed ) => { impl Tabled for $t { const LENGTH: usize = 1; fn fields(&self) -> Vec> { vec![Cow::Borrowed(self)] } fn headers() -> Vec> { vec![Cow::Borrowed(stringify!($t))] } } }; } default_table!(&str = borrowed); default_table!(str = borrowed); default_table!(String); default_table!(char); default_table!(bool); default_table!(isize); default_table!(usize); default_table!(u8); default_table!(u16); default_table!(u32); default_table!(u64); default_table!(u128); default_table!(i8); default_table!(i16); default_table!(i32); default_table!(i64); default_table!(i128); default_table!(f32); default_table!(f64); impl Tabled for [T; N] where T: std::fmt::Display, { const LENGTH: usize = N; fn fields(&self) -> Vec> { self.iter() .map(ToString::to_string) .map(Cow::Owned) .collect() } fn headers() -> Vec> { (0..N).map(|i| Cow::Owned(format!("{i}"))).collect() } } tabled-0.18.0/src/tables/compact.rs000064400000000000000000000220701046102023000152030ustar 00000000000000//! This module contains a [`CompactTable`] table. use core::cmp::max; use core::fmt; use crate::{ grid::{ config::{AlignmentHorizontal, CompactConfig, Indent, Sides}, dimension::{ConstDimension, ConstSize, Dimension}, records::{ into_records::{LimitColumns, LimitRows}, IntoRecords, IterRecords, }, util::string::get_line_width, CompactGrid, }, settings::{style::Style, TableOption}, }; /// A table which consumes an [`IntoRecords`] iterator. /// It assumes that the content has only single line. /// /// In contrast to [`Table`] [`CompactTable`] does no allocations but it consumes an iterator. /// It's useful when you don't want to re/allocate a buffer for your data. /// /// # Example /// /// It works smoothly with arrays. /// #[cfg_attr(feature = "std", doc = "```")] #[cfg_attr(not(feature = "std"), doc = "```ignore")] /// use tabled::{settings::Style, tables::CompactTable}; /// /// let data = [ /// ["FreeBSD", "1993", "William and Lynne Jolitz", "?"], /// ["OpenBSD", "1995", "Theo de Raadt", ""], /// ["HardenedBSD", "2014", "Oliver Pinter and Shawn Webb", ""], /// ]; /// /// let table = CompactTable::from(data) /// .with(Style::psql()) /// .to_string(); /// /// assert_eq!( /// table, /// concat!( /// " FreeBSD | 1993 | William and Lynne Jolitz | ? \n", /// " OpenBSD | 1995 | Theo de Raadt | \n", /// " HardenedBSD | 2014 | Oliver Pinter and Shawn Webb | ", /// ) /// ); /// ``` /// /// But it's default creation requires to be given an estimated cell width, and the amount of columns. /// #[cfg_attr(feature = "std", doc = "```")] #[cfg_attr(not(feature = "std"), doc = "```ignore")] /// use tabled::{settings::Style, tables::CompactTable}; /// /// let data = [ /// ["FreeBSD", "1993", "William and Lynne Jolitz", "?"], /// ["OpenBSD", "1995", "Theo de Raadt", ""], /// ["HardenedBSD", "2014", "Oliver Pinter and Shawn Webb", ""], /// ]; /// /// // See what will happen if the given width is too narrow /// /// let table = CompactTable::new(&data) /// .columns(4) /// .width(5) /// .with(Style::ascii()) /// .to_string(); /// /// assert_eq!( /// table, /// "+-----+-----+-----+-----+\n\ /// | FreeBSD | 1993 | William and Lynne Jolitz | ? |\n\ /// |-----+-----+-----+-----|\n\ /// | OpenBSD | 1995 | Theo de Raadt | |\n\ /// |-----+-----+-----+-----|\n\ /// | HardenedBSD | 2014 | Oliver Pinter and Shawn Webb | |\n\ /// +-----+-----+-----+-----+" /// ); /// ``` /// /// [`Table`]: crate::Table #[derive(Debug, Clone)] pub struct CompactTable { records: I, cfg: CompactConfig, dims: D, count_columns: usize, count_rows: Option, } impl CompactTable> { /// Creates a new [`CompactTable`] structure with a width dimension for all columns. pub const fn new(iter: I) -> Self where I: IntoRecords, { Self { records: iter, cfg: create_config(), count_columns: 0, count_rows: None, dims: ConstDimension::new(ConstSize::Value(2), ConstSize::Value(1)), } } } impl CompactTable> { /// Set a height for each row. pub fn height>, const COUNT_ROWS: usize>( self, size: S, ) -> CompactTable> { let (width, _) = self.dims.into(); CompactTable { dims: ConstDimension::new(width, size.into()), records: self.records, cfg: self.cfg, count_columns: self.count_columns, count_rows: self.count_rows, } } /// Set a width for each column. pub fn width>, const COUNT_COLUMNS: usize>( self, size: S, ) -> CompactTable> { let (_, height) = self.dims.into(); CompactTable { dims: ConstDimension::new(size.into(), height), records: self.records, cfg: self.cfg, count_columns: self.count_columns, count_rows: self.count_rows, } } } impl CompactTable { /// Creates a new [`CompactTable`] structure with a known dimension. /// /// Notice that the function wont call [`Estimate`]. /// /// [`Estimate`]: crate::grid::dimension::Estimate pub fn with_dimension(iter: I, dimension: D) -> Self where I: IntoRecords, { Self { records: iter, dims: dimension, cfg: create_config(), count_columns: 0, count_rows: None, } } /// With is a generic function which applies options to the [`CompactTable`]. pub fn with(mut self, option: O) -> Self where for<'a> O: TableOption, CompactConfig, D>, { let mut records = IterRecords::new(&self.records, self.count_columns, self.count_rows); option.change(&mut records, &mut self.cfg, &mut self.dims); self } /// Limit a number of rows. pub const fn rows(mut self, count_rows: usize) -> Self { self.count_rows = Some(count_rows); self } /// Limit a number of columns. pub const fn columns(mut self, count: usize) -> Self { self.count_columns = count; self } /// Returns a table config. pub fn get_config(&self) -> &CompactConfig { &self.cfg } /// Returns a table config. pub fn get_config_mut(&mut self) -> &mut CompactConfig { &mut self.cfg } /// Format table into [fmt::Write]er. pub fn fmt(self, writer: W) -> fmt::Result where I: IntoRecords, I::Cell: AsRef, D: Dimension, W: fmt::Write, { build_grid( writer, self.records, self.dims, self.cfg, self.count_columns, self.count_rows, ) } /// Format table into a writer. #[cfg(feature = "std")] #[cfg_attr(docsrs, doc(cfg(feature = "std")))] pub fn build(self, writer: W) -> std::io::Result<()> where I: IntoRecords, I::Cell: AsRef, D: Dimension, W: std::io::Write, { let writer = crate::util::utf8_writer::UTF8Writer::new(writer); self.fmt(writer) .map_err(|err| std::io::Error::new(std::io::ErrorKind::Other, err)) } /// Build a string. /// /// We can't implement [`std::string::ToString`] cause it does takes `&self` reference. #[allow(clippy::inherent_to_string)] #[cfg(feature = "std")] #[cfg_attr(docsrs, doc(cfg(feature = "std")))] pub fn to_string(self) -> String where I: IntoRecords, I::Cell: AsRef, D: Dimension, { let mut buf = String::new(); self.fmt(&mut buf) .expect("it's expected to be ok according to doc"); buf } } impl From<[[T; COLS]; ROWS]> for CompactTable<[[T; COLS]; ROWS], ConstDimension> where T: AsRef, { fn from(mat: [[T; COLS]; ROWS]) -> Self { let mut width = [0; COLS]; for row in mat.iter() { for (col, text) in row.iter().enumerate() { let text = text.as_ref(); let text_width = get_line_width(text); width[col] = max(width[col], text_width); } } // add padding for w in &mut width { *w += 2; } let dims = ConstDimension::new(ConstSize::List(width), ConstSize::Value(1)); Self::with_dimension(mat, dims).columns(COLS).rows(ROWS) } } fn build_grid( writer: W, records: I, dims: D, config: CompactConfig, cols: usize, rows: Option, ) -> Result<(), fmt::Error> where W: fmt::Write, I: IntoRecords, I::Cell: AsRef, D: Dimension, { match rows { Some(limit) => { let records = LimitRows::new(records, limit); let records = LimitColumns::new(records, cols); let records = IterRecords::new(records, cols, rows); CompactGrid::new(records, dims, config).build(writer) } None => { let records = LimitColumns::new(records, cols); let records = IterRecords::new(records, cols, rows); CompactGrid::new(records, dims, config).build(writer) } } } const fn create_config() -> CompactConfig { CompactConfig::new() .set_padding(Sides::new( Indent::spaced(1), Indent::spaced(1), Indent::zero(), Indent::zero(), )) .set_alignment_horizontal(AlignmentHorizontal::Left) .set_borders(Style::ascii().get_borders()) } impl TableOption for CompactConfig { fn change(self, _: &mut R, cfg: &mut CompactConfig, _: &mut D) { *cfg = self; } } tabled-0.18.0/src/tables/extended.rs000064400000000000000000000213011046102023000153510ustar 00000000000000//! This module contains an [`ExtendedTable`] structure which is useful in cases where //! a structure has a lot of fields. use crate::grid::util::string::get_line_width; use crate::Tabled; use std::cell::RefCell; use std::fmt::{self, Debug, Display}; use std::rc::Rc; /// `ExtendedTable` display data in a 'expanded display mode' from postgresql. /// It may be useful for a large data sets with a lot of fields. /// /// See 'Examples' in . /// /// It escapes strings to resolve a multi-line ones. /// Because of that ANSI sequences will be not be rendered too so colores will not be showed. /// #[cfg_attr(feature = "derive", doc = "```")] #[cfg_attr(not(feature = "derive"), doc = "```ignore")] /// use tabled::{Tabled, tables::ExtendedTable}; /// /// #[derive(Tabled)] /// struct Language { /// name: &'static str, /// designed_by: &'static str, /// invented_year: usize, /// } /// /// let languages = vec![ /// Language{ name: "C", designed_by: "Dennis Ritchie", invented_year: 1972 }, /// Language{ name: "Rust", designed_by: "Graydon Hoare", invented_year: 2010 }, /// Language{ name: "Go", designed_by: "Rob Pike", invented_year: 2009 }, /// ]; /// /// let table = ExtendedTable::new(languages).to_string(); /// /// let expected = "-[ RECORD 0 ]-+---------------\n\ /// name | C\n\ /// designed_by | Dennis Ritchie\n\ /// invented_year | 1972\n\ /// -[ RECORD 1 ]-+---------------\n\ /// name | Rust\n\ /// designed_by | Graydon Hoare\n\ /// invented_year | 2010\n\ /// -[ RECORD 2 ]-+---------------\n\ /// name | Go\n\ /// designed_by | Rob Pike\n\ /// invented_year | 2009"; /// /// assert_eq!(table, expected); /// ``` #[derive(Clone)] pub struct ExtendedTable { fields: Vec, records: Vec>, template: Rc String>>, } impl ExtendedTable { /// Creates a new instance of `ExtendedTable` pub fn new(iter: impl IntoIterator) -> Self where T: Tabled, { let data = iter .into_iter() .map(|i| { i.fields() .into_iter() .map(|s| s.escape_debug().to_string()) .collect() }) .collect(); let header = T::headers() .into_iter() .map(|s| s.escape_debug().to_string()) .collect(); Self { records: data, fields: header, template: Rc::new(RefCell::new(record_template)), } } /// Truncates table to a set width value for a table. /// It returns a success inticator, where `false` means it's not possible to set the table width, /// because of the given arguments. /// /// It tries to not affect fields, but if there's no enough space all records will be deleted and fields will be cut. /// /// The minimum width is 14. pub fn truncate(&mut self, max: usize, suffix: &str) -> bool { // -[ RECORD 0 ]- let teplate_width = self.records.len().to_string().len() + 13; let min_width = teplate_width; if max < min_width { return false; } let suffix_width = get_line_width(suffix); if max < suffix_width { return false; } let max = max - suffix_width; let fields_max_width = self .fields .iter() .map(|s| get_line_width(s)) .max() .unwrap_or_default(); // 3 is a space for ' | ' let fields_affected = max < fields_max_width + 3; if fields_affected { if max < 3 { return false; } let max = max - 3; if max < suffix_width { return false; } let max = max - suffix_width; truncate_fields(&mut self.fields, max, suffix); truncate_records(&mut self.records, 0, suffix); } else { let max = max - fields_max_width - 3 - suffix_width; truncate_records(&mut self.records, max, suffix); } true } /// Sets the template for a record. pub fn template(mut self, template: F) -> Self where F: Fn(usize) -> String + 'static, { self.template = Rc::new(RefCell::new(template)); self } } impl From>> for ExtendedTable { fn from(mut data: Vec>) -> Self { if data.is_empty() { return Self { fields: vec![], records: vec![], template: Rc::new(RefCell::new(record_template)), }; } let fields = data.remove(0); Self { fields, records: data, template: Rc::new(RefCell::new(record_template)), } } } impl Display for ExtendedTable { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { if self.records.is_empty() { return Ok(()); } // It's possible that field|header can be a multiline string so // we escape it and trim \" chars. let fields = self.fields.iter().collect::>(); let max_field_width = fields .iter() .map(|s| get_line_width(s)) .max() .unwrap_or_default(); let max_values_length = self .records .iter() .map(|record| record.iter().map(|s| get_line_width(s)).max()) .max() .unwrap_or_default() .unwrap_or_default(); for (i, records) in self.records.iter().enumerate() { write_header_template(f, &self.template, i, max_field_width, max_values_length)?; for (value, field) in records.iter().zip(fields.iter()) { writeln!(f)?; write_record(f, field, value, max_field_width)?; } let is_last_record = i + 1 == self.records.len(); if !is_last_record { writeln!(f)?; } } Ok(()) } } impl Debug for ExtendedTable { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("ExtendedTable") .field("fields", &self.fields) .field("records", &self.records) .finish_non_exhaustive() } } fn truncate_records(records: &mut Vec>, max_width: usize, suffix: &str) { for fields in records { truncate_fields(fields, max_width, suffix); } } fn truncate_fields(records: &mut Vec, max_width: usize, suffix: &str) { for text in records { truncate(text, max_width, suffix); } } fn write_header_template( f: &mut fmt::Formatter<'_>, template: &Rc String>>, index: usize, max_field_width: usize, max_values_length: usize, ) -> fmt::Result { let record_template = template.borrow()(index); let mut template = format!("-{record_template}-"); let default_template_length = template.len(); // 3 - is responsible for ' | ' formatting let max_line_width = std::cmp::max( max_field_width + 3 + max_values_length, default_template_length, ); let rest_to_print = max_line_width - default_template_length; if rest_to_print > 0 { // + 1 is a space after field name and we get a next pos so its +2 if max_field_width + 2 > default_template_length { let part1 = (max_field_width + 1) - default_template_length; let part2 = rest_to_print - part1 - 1; template.extend( std::iter::repeat('-') .take(part1) .chain(std::iter::once('+')) .chain(std::iter::repeat('-').take(part2)), ); } else { template.extend(std::iter::repeat('-').take(rest_to_print)); } } write!(f, "{template}")?; Ok(()) } fn write_record( f: &mut fmt::Formatter<'_>, field: &str, value: &str, max_field_width: usize, ) -> fmt::Result { write!(f, "{field:max_field_width$} | {value}") } fn truncate(text: &mut String, max: usize, suffix: &str) { let original_len = text.len(); if max == 0 || text.is_empty() { *text = String::new(); } else { *text = crate::util::string::cut_str2(text, max).into_owned(); } let cut_was_done = text.len() < original_len; if !suffix.is_empty() && cut_was_done { text.push_str(suffix); } } fn record_template(index: usize) -> String { format!("[ RECORD {index} ]") } tabled-0.18.0/src/tables/iter.rs000064400000000000000000000264401046102023000145250ustar 00000000000000//! This module contains a [`IterTable`] table. //! //! In contrast to [`Table`] [`IterTable`] does no allocations but it consumes an iterator. //! It's useful when you don't want to re/allocate a buffer for your data. //! //! # Example //! //! ``` //! use tabled::{grid::records::IterRecords, tables::IterTable}; //! //! let iterator = vec![vec!["First", "row"], vec!["Second", "row"]]; //! let records = IterRecords::new(iterator, 2, Some(2)); //! let table = IterTable::new(records); //! //! let s = table.to_string(); //! //! assert_eq!( //! s, //! "+--------+-----+\n\ //! | First | row |\n\ //! +--------+-----+\n\ //! | Second | row |\n\ //! +--------+-----+", //! ); //! ``` //! //! [`Table`]: crate::Table use std::{fmt, io}; use crate::{ grid::{ colors::NoColors, config::{AlignmentHorizontal, CompactConfig, Indent, Sides, SpannedConfig}, dimension::{CompactGridDimension, Dimension, DimensionValue, StaticDimension}, records::{ into_records::{BufRecords, LimitColumns, LimitRows, TruncateContent}, IntoRecords, IterRecords, }, Grid, }, settings::{Style, TableOption}, }; use crate::util::utf8_writer::UTF8Writer; /// A table which consumes an [`IntoRecords`] iterator. /// /// To be able to build table we need a dimensions. /// If no width and count_columns is set, [`IterTable`] will sniff the records, by /// keeping a number of rows buffered (You can set the number via [`IterTable::sniff`]). /// /// In contrast to [`Table`] [`IterTable`] does no allocations but it consumes an iterator. /// It's useful when you don't want to re/allocate a buffer for your data. /// /// # Example /// /// ``` /// use tabled::{grid::records::IterRecords, tables::IterTable}; /// /// let data = vec![ /// vec!["First", "row"], /// vec!["Second", "row"], /// vec!["Third", "big row"], /// ]; /// /// let records = IterRecords::new(data, 2, Some(2)); /// let table = IterTable::new(records).sniff(1); /// /// // notice because of sniff 1 we have all rows after the first one being truncated /// assert_eq!( /// table.to_string(), /// "+-------+-----+\n\ /// | First | row |\n\ /// +-------+-----+\n\ /// | Secon | row |\n\ /// +-------+-----+\n\ /// | Third | big |\n\ /// +-------+-----+", /// ); /// ``` /// /// [`Table`]: crate::Table #[derive(Debug, Clone)] pub struct IterTable { records: I, cfg: CompactConfig, table: Settings, } #[derive(Debug, Clone)] struct Settings { sniff: usize, count_columns: Option, count_rows: Option, width: Option, height: Option, } impl IterTable { /// Creates a new [`IterTable`] structure. pub fn new(iter: I) -> Self where I: IntoRecords, { Self { records: iter, cfg: create_config(), table: Settings { sniff: 1000, count_columns: None, count_rows: None, height: None, width: None, }, } } /// With is a generic function which applies options to the [`IterTable`]. pub fn with(mut self, option: O) -> Self where for<'a> O: TableOption, CompactConfig, StaticDimension>, { let count_columns = self.table.count_columns.unwrap_or(0); let mut records = IterRecords::new(&self.records, count_columns, self.table.count_rows); let mut dims = StaticDimension::new(DimensionValue::Exact(0), DimensionValue::Exact(1)); option.change(&mut records, &mut self.cfg, &mut dims); self } /// Limit a number of columns. pub fn columns(mut self, count_columns: usize) -> Self { self.table.count_columns = Some(count_columns); self } /// Limit a number of rows. pub fn rows(mut self, count_rows: usize) -> Self { self.table.count_rows = Some(count_rows); self } /// Limit an amount of rows will be read for dimension estimations. /// /// By default it's 1000. pub fn sniff(mut self, count: usize) -> Self { self.table.sniff = count; self } /// Set a height for each row. pub fn height(mut self, size: usize) -> Self { self.table.height = Some(size); self } /// Set a width for each column. pub fn width(mut self, size: usize) -> Self { self.table.width = Some(size); self } /// Build a string. /// /// We can't implement [`std::string::ToString`] cause it does takes `&self` reference. #[allow(clippy::inherent_to_string)] pub fn to_string(self) -> String where I: IntoRecords, I::Cell: AsRef, { let mut buf = String::new(); self.fmt(&mut buf) .expect("according to a doc is safe to fmt() a string"); buf } /// Format table into [`io::Write`]r. pub fn build(self, writer: W) -> io::Result<()> where W: io::Write, I: IntoRecords, I::Cell: AsRef, { let writer = UTF8Writer::new(writer); self.fmt(writer) .map_err(|err| io::Error::new(io::ErrorKind::Other, err)) } /// Format table into [fmt::Write]er. pub fn fmt(self, writer: W) -> fmt::Result where W: fmt::Write, I: IntoRecords, I::Cell: AsRef, { build_grid(writer, self.records, self.cfg, self.table) } } fn build_grid(f: W, iter: I, cfg: CompactConfig, opts: Settings) -> fmt::Result where W: fmt::Write, I: IntoRecords, I::Cell: AsRef, { let width_config = opts.width.is_some() && opts.count_columns.is_some(); if width_config { build_table_with_static_dims(f, iter, cfg, opts) } else if opts.width.is_some() { build_table_sniffing_with_width(f, iter, cfg, opts) } else { build_table_sniffing(f, iter, cfg, opts) } } fn build_table_with_static_dims( f: W, iter: I, cfg: CompactConfig, opts: Settings, ) -> fmt::Result where W: fmt::Write, I: IntoRecords, I::Cell: AsRef, { let count_columns = opts.count_columns.unwrap(); let width = opts.width.unwrap(); let height = opts.height.unwrap_or(1); let contentw = WidthDimension::Exact(width); let pad = cfg.get_padding(); let w = DimensionValue::Exact(width + pad.left.size + pad.right.size); let h = DimensionValue::Exact(height + pad.top.size + pad.bottom.size); let dims = StaticDimension::new(w, h); let cfg = SpannedConfig::from(cfg); match opts.count_rows { Some(limit) => { let records = LimitRows::new(iter, limit); let records = build_records(records, contentw, count_columns, Some(limit)); Grid::new(records, dims, cfg, NoColors).build(f) } None => { let records = build_records(iter, contentw, count_columns, None); Grid::new(records, dims, cfg, NoColors).build(f) } } } fn build_table_sniffing(f: W, iter: I, cfg: CompactConfig, opts: Settings) -> fmt::Result where W: fmt::Write, I: IntoRecords, I::Cell: AsRef, { let records = BufRecords::new(iter, opts.sniff); let count_columns = get_count_columns(&opts, records.as_slice()); let (mut width, height) = { let records = LimitColumns::new(records.as_slice(), count_columns); let records = IterRecords::new(records, count_columns, None); CompactGridDimension::dimension(records, &cfg) }; let padding = cfg.get_padding(); let pad = padding.left.size + padding.right.size; let padv = padding.top.size + padding.bottom.size; if opts.sniff == 0 { width = std::iter::repeat(pad) .take(count_columns) .collect::>(); } let content_width = WidthDimension::List(width.iter().map(|i| i.saturating_sub(pad)).collect()); let dims_width = DimensionValue::List(width); let height_exact = opts.height.unwrap_or(1) + padv; let mut dims_height = DimensionValue::Partial(height, height_exact); if opts.height.is_some() { dims_height = DimensionValue::Exact(height_exact); } let dims = StaticDimension::new(dims_width, dims_height); let cfg = SpannedConfig::from(cfg); match opts.count_rows { Some(limit) => { let records = LimitRows::new(records, limit); let records = build_records(records, content_width, count_columns, Some(limit)); Grid::new(records, dims, cfg, NoColors).build(f) } None => { let records = build_records(records, content_width, count_columns, None); Grid::new(records, dims, cfg, NoColors).build(f) } } } fn build_table_sniffing_with_width( f: W, iter: I, cfg: CompactConfig, opts: Settings, ) -> fmt::Result where W: fmt::Write, I: IntoRecords, I::Cell: AsRef, { let records = BufRecords::new(iter, opts.sniff); let count_columns = get_count_columns(&opts, records.as_slice()); let width = opts.width.unwrap(); let contentw = WidthDimension::Exact(width); let padding = cfg.get_padding(); let pad = padding.left.size + padding.right.size; let padv = padding.top.size + padding.bottom.size; let height = opts.height.unwrap_or(1) + padv; let dimsh = DimensionValue::Exact(height); let dimsw = DimensionValue::Exact(width + pad); let dims = StaticDimension::new(dimsw, dimsh); let cfg = SpannedConfig::from(cfg); match opts.count_rows { Some(limit) => { let records = LimitRows::new(records, limit); let records = build_records(records, contentw, count_columns, Some(limit)); Grid::new(records, dims, cfg, NoColors).build(f) } None => { let records = build_records(records, contentw, count_columns, None); Grid::new(records, dims, cfg, NoColors).build(f) } } } fn get_count_columns(opts: &Settings, buf: &[Vec]) -> usize { match opts.count_columns { Some(size) => size, None => buf.iter().map(|row| row.len()).max().unwrap_or(0), } } const fn create_config() -> CompactConfig { CompactConfig::new() .set_padding(Sides::new( Indent::spaced(1), Indent::spaced(1), Indent::zero(), Indent::zero(), )) .set_alignment_horizontal(AlignmentHorizontal::Left) .set_borders(Style::ascii().get_borders()) } fn build_records( records: I, width: WidthDimension, count_columns: usize, count_rows: Option, ) -> IterRecords>> where I: IntoRecords, { let records = TruncateContent::new(records, width); let records = LimitColumns::new(records, count_columns); IterRecords::new(records, count_columns, count_rows) } /// A dimension value. #[derive(Debug, Clone)] enum WidthDimension { Exact(usize), List(Vec), } impl Dimension for WidthDimension { fn get_width(&self, column: usize) -> usize { match self { WidthDimension::Exact(value) => *value, WidthDimension::List(list) => list[column], } } fn get_height(&self, _row: usize) -> usize { unreachable!("A height method is not supposed to be called"); } } tabled-0.18.0/src/tables/mod.rs000064400000000000000000000050011046102023000143270ustar 00000000000000//! Module contains a list of table representatives. //! //! ## [`Table`] //! //! A default table implementation. //! //! At it's core it keeps data buffered. //! Be cautious about it. //! //! Peek it by default. //! //! ## [`IterTable`] //! //! Just like [`Table`] but it's API is a bit different to serve better in context //! where there is a memory limit. //! //! It's different in implementation algorithms. //! //! From performance point of view it's similar to [`Table`], may be a bit slower. //! Test it on your specific table representation. //! //! Peek it when you want to have a feature full table. //! But you have a memory conserns. //! //! ## [`PoolTable`] //! //! A table with a greater control of a layout. //! So you can build tables with a different layout/look easily. //! //! Peek it when you need it. //! //! ## [`CompactTable`] //! //! A table with a limited subset of settings but it works in a `no-std` context. //! And it consumes the least amount of memory/cpu. //! Cause it print records one by one. //! //! Peek it when your data contains a single line only, //! and you don't need lots a features. //! Or you're at `no-std` context. //! //! It's likely the fastest table in this limited context. //! //! ## [`ExtendedTable`] //! //! It's a table which is useful for showing large amount of data. //! Though it's performance is generic. //! //! Peek it when you need it. mod compact; #[cfg(feature = "std")] mod extended; #[cfg(feature = "std")] mod iter; #[cfg(feature = "std")] mod table; #[cfg(feature = "std")] mod table_pool; #[cfg(feature = "std")] #[cfg_attr(docsrs, doc(cfg(feature = "std")))] pub use table::Table; #[cfg(feature = "std")] #[cfg_attr(docsrs, doc(cfg(feature = "std")))] pub use iter::IterTable; #[cfg(feature = "std")] #[cfg_attr(docsrs, doc(cfg(feature = "std")))] pub use extended::ExtendedTable; #[cfg(feature = "std")] #[cfg_attr(docsrs, doc(cfg(feature = "std")))] pub use table_pool::{PoolTable, TableValue}; pub use compact::CompactTable; // todo: Create a PoolTable backend in papergrid with generics so it coulb be used differently // rather then with our own impl of dimension // // todo: Replace all usage of concrete configs to a AsRef generics, so some could be used interchangeably // // todo: Think about all the Config hierachly; we probably shall have something like a Decorator approach there. // config(borders) -> config(borders+colors) -> config(borders+colors+spans) // // Or maybe many interfacies e.g ColorConfig, BorderConfig, AlignmentConfig etc. tabled-0.18.0/src/tables/table.rs000064400000000000000000000622061046102023000146510ustar 00000000000000//! This module contains a main table representation [`Table`]. use core::ops::DerefMut; use std::{borrow::Cow, fmt, iter::FromIterator}; use crate::{ builder::Builder, grid::{ colors::NoColors, config::{ AlignmentHorizontal, ColorMap, ColoredConfig, CompactConfig, Entity, Indent, Sides, SpannedConfig, }, dimension::{CompleteDimensionVecRecords, Dimension, Estimate, PeekableDimension}, records::{ vec_records::{Text, VecRecords}, ExactRecords, Records, }, PeekableGrid, }, settings::{object::Object, CellOption, Style, TableOption}, Tabled, }; /// The structure provides an interface for building a table for types that implements [`Tabled`]. /// /// To build a string representation of a table you must use a [`std::fmt::Display`]. /// Or simply call `.to_string()` method. /// /// The default table [`Style`] is [`Style::ascii`], /// with a single left and right [`Padding`]. /// /// ## Example /// /// ### Basic usage /// /// ```rust,no_run /// use tabled::Table; /// /// let table = Table::new(&["Year", "2021"]); /// ``` /// /// ### With settings /// /// ```rust,no_run /// use tabled::{Table, settings::{Style, Alignment}}; /// /// let data = vec!["Hello", "2021"]; /// let mut table = Table::new(&data); /// table /// .with(Style::psql()) /// .with(Alignment::left()); /// ``` /// /// ### With a [`Tabled`] trait. /// /// ```rust,no_run /// use tabled::{Table, Tabled}; /// /// #[derive(Tabled)] /// struct Character { /// good: f32, /// bad: f32, /// encouraging: f32, /// destructive: f32, /// } /// /// #[derive(Tabled)] /// struct Person<'a>( /// #[tabled(rename = "name")] &'a str, /// #[tabled(inline)] Character, /// ); /// /// let data = vec![ /// Person("007", Character { good: 0.8, bad: 0.2, encouraging: 0.8, destructive: 0.1}), /// Person("001", Character { good: 0.2, bad: 0.5, encouraging: 0.2, destructive: 0.1}), /// Person("006", Character { good: 0.4, bad: 0.1, encouraging: 0.5, destructive: 0.8}), /// ]; /// /// let table = Table::new(&data); /// ``` /// /// [`Padding`]: crate::settings::Padding /// [`Style`]: crate::settings::Style /// [`Style::ascii`]: crate::settings::Style::ascii #[derive(Debug, Clone, PartialEq, Eq)] pub struct Table { records: VecRecords>, config: ColoredConfig, dimension: CompleteDimensionVecRecords<'static>, } impl Table { /// Creates a Table instance, from a list of [`Tabled`] values. /// /// If you use a reference iterator you'd better use [`FromIterator`] instead. /// As it has a different lifetime constraints and make less copies therefore. /// /// # Examples /// /// ``` /// use tabled::{Table, Tabled}; /// use testing_table::assert_table; /// /// #[derive(Tabled)] /// struct Relationship { /// love: bool /// } /// /// let list = vec![ /// Relationship { love: true }, /// Relationship { love: false }, /// ]; /// /// let table = Table::new(list); /// /// assert_table!( /// table, /// "+-------+" /// "| love |" /// "+-------+" /// "| true |" /// "+-------+" /// "| false |" /// "+-------+" /// ); /// ``` /// /// ## Notice that you can pass tuples. /// /// ``` /// use tabled::{Table, Tabled}; /// use testing_table::assert_table; /// /// #[derive(Tabled)] /// struct Relationship { /// love: bool /// } /// /// let list = vec![ /// ("Kate", Relationship { love: true }), /// ("", Relationship { love: false }), /// ]; /// /// let table = Table::new(list); /// /// assert_table!( /// table, /// "+------+-------+" /// "| &str | love |" /// "+------+-------+" /// "| Kate | true |" /// "+------+-------+" /// "| | false |" /// "+------+-------+" /// ); /// ``` /// /// ## Notice that you can pass const arrays as well. /// /// ``` /// use tabled::Table; /// use testing_table::assert_table; /// /// let list = vec![ /// ["Kate", "+", "+", "+", "-"], /// ["", "-", "-", "-", "-"], /// ]; /// /// let table = Table::new(list); /// /// assert_table!( /// table, /// "+------+---+---+---+---+" /// "| 0 | 1 | 2 | 3 | 4 |" /// "+------+---+---+---+---+" /// "| Kate | + | + | + | - |" /// "+------+---+---+---+---+" /// "| | - | - | - | - |" /// "+------+---+---+---+---+" /// ); /// ``` /// /// ## As a different way to create a [`Table`], you can use [`Table::from_iter`]. /// /// ``` /// use std::iter::FromIterator; /// use tabled::Table; /// use testing_table::assert_table; /// /// let list = vec![ /// vec!["Kate", "+", "+", "+", "-"], /// vec!["", "-", "-", "-", "-"], /// ]; /// /// let table = Table::from_iter(list); /// /// assert_table!( /// table, /// "+------+---+---+---+---+" /// "| Kate | + | + | + | - |" /// "+------+---+---+---+---+" /// "| | - | - | - | - |" /// "+------+---+---+---+---+" /// ); /// ``` pub fn new(iter: I) -> Self where I: IntoIterator, T: Tabled, { let mut header = Vec::with_capacity(T::LENGTH); for text in T::headers() { let text = text.into_owned(); let cell = Text::new(text); header.push(cell); } let mut records = vec![header]; for row in iter.into_iter() { let mut list = Vec::with_capacity(T::LENGTH); for text in row.fields().into_iter() { let text = text.into_owned(); let cell = Text::new(text); list.push(cell); } records.push(list); } let records = VecRecords::new(records); Self { records, config: ColoredConfig::new(configure_grid()), dimension: CompleteDimensionVecRecords::default(), } } /// Creates a Key-Value [`Table`] instance, from a list of [`Tabled`] values. /// /// # Examples /// /// ``` /// use tabled::{Table, Tabled}; /// use testing_table::assert_table; /// /// #[derive(Tabled)] /// #[tabled(rename_all = "PascalCase")] /// struct Swim { /// event: String, /// time: String, /// #[tabled(rename = "Pool Length")] /// pool: u8, /// } /// /// const POOL_25: u8 = 25; /// const POOL_50: u8 = 50; /// /// let list = vec![ /// Swim { event: String::from("Men 100 Freestyle"), time: String::from("47.77"), pool: POOL_25 }, /// Swim { event: String::from("Men 400 Freestyle"), time: String::from("03:59.16"), pool: POOL_25 }, /// Swim { event: String::from("Men 800 Freestyle"), time: String::from("08:06.70"), pool: POOL_25 }, /// Swim { event: String::from("Men 4x100 Medley Relay"), time: String::from("03:27.28"), pool: POOL_50 }, /// ]; /// /// let table = Table::kv(list); /// /// assert_table!( /// table, /// "+-------------+------------------------+" /// "| Event | Men 100 Freestyle |" /// "+-------------+------------------------+" /// "| Time | 47.77 |" /// "+-------------+------------------------+" /// "| Pool Length | 25 |" /// "+-------------+------------------------+" /// "| Event | Men 400 Freestyle |" /// "+-------------+------------------------+" /// "| Time | 03:59.16 |" /// "+-------------+------------------------+" /// "| Pool Length | 25 |" /// "+-------------+------------------------+" /// "| Event | Men 800 Freestyle |" /// "+-------------+------------------------+" /// "| Time | 08:06.70 |" /// "+-------------+------------------------+" /// "| Pool Length | 25 |" /// "+-------------+------------------------+" /// "| Event | Men 4x100 Medley Relay |" /// "+-------------+------------------------+" /// "| Time | 03:27.28 |" /// "+-------------+------------------------+" /// "| Pool Length | 50 |" /// "+-------------+------------------------+" /// ); /// ``` /// /// A more complex example with a subtle style. /// /// ``` /// use tabled::{Table, Tabled, settings::Style}; /// use tabled::settings::{style::HorizontalLine, Theme}; /// use testing_table::assert_table; /// /// #[derive(Tabled)] /// #[tabled(rename_all = "PascalCase")] /// struct Swim { /// event: String, /// time: String, /// #[tabled(rename = "Pool Length")] /// pool: u8, /// } /// /// const POOL_25: u8 = 25; /// const POOL_50: u8 = 50; /// /// let list = vec![ /// Swim { event: String::from("Men 100 Freestyle"), time: String::from("47.77"), pool: POOL_25 }, /// Swim { event: String::from("Men 400 Freestyle"), time: String::from("03:59.16"), pool: POOL_25 }, /// Swim { event: String::from("Men 800 Freestyle"), time: String::from("08:06.70"), pool: POOL_25 }, /// Swim { event: String::from("Men 4x100 Medley Relay"), time: String::from("03:27.28"), pool: POOL_50 }, /// ]; /// /// let mut table = Table::kv(list); /// /// let mut style = Theme::from_style(Style::rounded().remove_horizontals()); /// for entry in 1 .. table.count_rows() / Swim::LENGTH { /// style.insert_horizontal_line(entry * Swim::LENGTH, HorizontalLine::inherit(Style::modern())); /// } /// /// table.with(style); /// /// assert_table!( /// table, /// "╭─────────────┬────────────────────────╮" /// "│ Event │ Men 100 Freestyle │" /// "│ Time │ 47.77 │" /// "│ Pool Length │ 25 │" /// "├─────────────┼────────────────────────┤" /// "│ Event │ Men 400 Freestyle │" /// "│ Time │ 03:59.16 │" /// "│ Pool Length │ 25 │" /// "├─────────────┼────────────────────────┤" /// "│ Event │ Men 800 Freestyle │" /// "│ Time │ 08:06.70 │" /// "│ Pool Length │ 25 │" /// "├─────────────┼────────────────────────┤" /// "│ Event │ Men 4x100 Medley Relay │" /// "│ Time │ 03:27.28 │" /// "│ Pool Length │ 50 │" /// "╰─────────────┴────────────────────────╯" /// ); /// ``` pub fn kv(iter: I) -> Self where I: IntoIterator, T: Tabled, { let headers = T::headers(); let mut records = Vec::new(); for row in iter.into_iter() { for (text, name) in row.fields().into_iter().zip(headers.iter()) { let key = Text::new(name.clone().into_owned()); let value = Text::new(text.into_owned()); records.push(vec![key, value]); } } let records = VecRecords::new(records); Self { records, config: ColoredConfig::new(configure_grid()), dimension: CompleteDimensionVecRecords::default(), } } /// Creates a builder from a data set given. /// /// # Example /// #[cfg_attr(feature = "derive", doc = "```")] #[cfg_attr(not(feature = "derive"), doc = "```ignore")] /// use tabled::{ /// Table, Tabled, /// settings::{object::Segment, Modify, Alignment} /// }; /// /// #[derive(Tabled)] /// struct User { /// name: &'static str, /// #[tabled(inline("device::"))] /// device: Device, /// } /// /// #[derive(Tabled)] /// enum Device { /// PC, /// Mobile /// } /// /// let data = vec![ /// User { name: "Vlad", device: Device::Mobile }, /// User { name: "Dimitry", device: Device::PC }, /// User { name: "John", device: Device::PC }, /// ]; /// /// let mut table = Table::builder(data) /// .index() /// .column(0) /// .transpose() /// .build() /// .modify(Segment::new(1.., 1..), Alignment::center()) /// .to_string(); /// /// assert_eq!( /// table, /// "+----------------+------+---------+------+\n\ /// | name | Vlad | Dimitry | John |\n\ /// +----------------+------+---------+------+\n\ /// | device::PC | | + | + |\n\ /// +----------------+------+---------+------+\n\ /// | device::Mobile | + | | |\n\ /// +----------------+------+---------+------+" /// ) /// ``` pub fn builder(iter: I) -> Builder where T: Tabled, I: IntoIterator, { let mut builder = Builder::with_capacity(0, T::LENGTH); builder.push_record(T::headers()); for row in iter { builder.push_record(row.fields().into_iter()); } builder } /// It's a generic function which applies options to the [`Table`]. /// /// It applies settings immediately. pub fn with(&mut self, option: O) -> &mut Self where for<'a> O: TableOption>, ColoredConfig, CompleteDimensionVecRecords<'a>>, { let reastimation_hint = option.hint_change(); let mut dims = self.dimension.from_origin(); option.change(&mut self.records, &mut self.config, &mut dims); let (widths, heights) = dims.into_inner(); dimension_reastimate(&mut self.dimension, widths, heights, reastimation_hint); self } /// It's a generic function which applies options to particular cells on the [`Table`]. /// Target cells using [`Object`]s such as [`Cell`], [`Rows`], [`Location`] and more. /// /// It applies settings immediately. /// /// [`Cell`]: crate::settings::object::Cell /// [`Rows`]: crate::settings::object::Rows /// [`Location`]: crate::settings::location::Locator pub fn modify(&mut self, target: T, option: O) -> &mut Self where T: Object>>, O: CellOption>, ColoredConfig> + Clone, { for entity in target.cells(&self.records) { let opt = option.clone(); opt.change(&mut self.records, &mut self.config, entity); } let reastimation_hint = option.hint_change(); dimension_reastimate_likely(&mut self.dimension, reastimation_hint); self } /// Returns a table shape (count rows, count columns). pub fn shape(&self) -> (usize, usize) { (self.count_rows(), self.count_columns()) } /// Returns an amount of rows in the table. pub fn count_rows(&self) -> usize { self.records.count_rows() } /// Returns an amount of columns in the table. pub fn count_columns(&self) -> usize { self.records.count_columns() } /// Returns a table shape (count rows, count columns). pub fn is_empty(&self) -> bool { let (count_rows, count_cols) = self.shape(); count_rows == 0 || count_cols == 0 } /// Returns total widths of a table, including margin and horizontal lines. pub fn total_height(&self) -> usize { let mut dims = CompleteDimensionVecRecords::from_origin(&self.dimension); dims.estimate(&self.records, self.config.as_ref()); let total = (0..self.count_rows()) .map(|row| dims.get_height(row)) .sum::(); let counth = self.config.count_horizontal(self.count_rows()); let margin = self.config.get_margin(); total + counth + margin.top.size + margin.bottom.size } /// Returns total widths of a table, including margin and vertical lines. pub fn total_width(&self) -> usize { let mut dims = CompleteDimensionVecRecords::from_origin(&self.dimension); dims.estimate(&self.records, self.config.as_ref()); let total = (0..self.count_columns()) .map(|col| dims.get_width(col)) .sum::(); let countv = self.config.count_vertical(self.count_columns()); let margin = self.config.get_margin(); total + countv + margin.left.size + margin.right.size } /// Returns a table config. pub fn get_config(&self) -> &ColoredConfig { &self.config } /// Returns a table config. pub fn get_config_mut(&mut self) -> &mut ColoredConfig { &mut self.config } /// Returns a used records. pub fn get_records(&self) -> &VecRecords> { &self.records } /// Returns a used records. pub fn get_records_mut(&mut self) -> &mut VecRecords> { &mut self.records } } impl Default for Table { fn default() -> Self { Self { records: VecRecords::default(), config: ColoredConfig::new(configure_grid()), dimension: CompleteDimensionVecRecords::default(), } } } impl fmt::Display for Table { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { if self.is_empty() { return Ok(()); } let config = use_format_configuration(f, self); let colors = self.config.get_colors(); if !self.dimension.is_empty() { let mut dims = self.dimension.clone(); dims.estimate(&self.records, config.as_ref()); print_grid(f, &self.records, &config, &dims, colors) } else { let mut dims = PeekableDimension::default(); dims.estimate(&self.records, &config); print_grid(f, &self.records, &config, &dims, colors) } } } impl FromIterator for Table where T: IntoIterator, T::Item: Into, { fn from_iter>(iter: I) -> Self { Builder::from_iter(iter.into_iter().map(|i| i.into_iter().map(|s| s.into()))).build() } } impl From for Table { fn from(builder: Builder) -> Self { let data = builder.into(); let records = VecRecords::new(data); Self { records, config: ColoredConfig::new(configure_grid()), dimension: CompleteDimensionVecRecords::default(), } } } impl From for Builder { fn from(val: Table) -> Self { let data = val.records.into(); Builder::from_vec(data) } } impl TableOption for CompactConfig { fn change(self, _: &mut R, cfg: &mut ColoredConfig, _: &mut D) { *cfg.deref_mut() = self.into(); } } impl TableOption for ColoredConfig { fn change(self, _: &mut R, cfg: &mut ColoredConfig, _: &mut D) { *cfg = self; } } impl TableOption for SpannedConfig { fn change(self, _: &mut R, cfg: &mut ColoredConfig, _: &mut D) { *cfg.deref_mut() = self; } } impl TableOption for &SpannedConfig { fn change(self, _: &mut R, cfg: &mut ColoredConfig, _: &mut D) { *cfg.deref_mut() = self.clone(); } } fn convert_fmt_alignment(alignment: fmt::Alignment) -> AlignmentHorizontal { match alignment { fmt::Alignment::Left => AlignmentHorizontal::Left, fmt::Alignment::Right => AlignmentHorizontal::Right, fmt::Alignment::Center => AlignmentHorizontal::Center, } } fn table_padding(alignment: fmt::Alignment, available: usize) -> (usize, usize) { match alignment { fmt::Alignment::Left => (available, 0), fmt::Alignment::Right => (0, available), fmt::Alignment::Center => { let left = available / 2; let right = available - left; (left, right) } } } fn configure_grid() -> SpannedConfig { let mut cfg = SpannedConfig::default(); cfg.set_padding( Entity::Global, Sides::new( Indent::spaced(1), Indent::spaced(1), Indent::default(), Indent::default(), ), ); cfg.set_alignment_horizontal(Entity::Global, AlignmentHorizontal::Left); cfg.set_line_alignment(Entity::Global, false); cfg.set_trim_horizontal(Entity::Global, false); cfg.set_trim_vertical(Entity::Global, false); cfg.set_borders(Style::ascii().get_borders()); cfg } fn use_format_configuration<'a>( f: &mut fmt::Formatter<'_>, table: &'a Table, ) -> Cow<'a, SpannedConfig> { if f.align().is_some() || f.width().is_some() { let mut cfg = table.config.as_ref().clone(); set_align_table(f, &mut cfg); set_width_table(f, &mut cfg, table); Cow::Owned(cfg) } else { Cow::Borrowed(table.config.as_ref()) } } fn set_align_table(f: &fmt::Formatter<'_>, cfg: &mut SpannedConfig) { if let Some(alignment) = f.align() { let alignment = convert_fmt_alignment(alignment); cfg.set_alignment_horizontal(Entity::Global, alignment); } } fn set_width_table(f: &fmt::Formatter<'_>, cfg: &mut SpannedConfig, table: &Table) { if let Some(width) = f.width() { let total_width = table.total_width(); if total_width >= width { return; } let mut fill = f.fill(); if fill == char::default() { fill = ' '; } let available = width - total_width; let alignment = f.align().unwrap_or(fmt::Alignment::Left); let (left, right) = table_padding(alignment, available); let mut margin = cfg.get_margin(); margin.left.size += left; margin.right.size += right; if (margin.left.size > 0 && margin.left.fill == char::default()) || fill != char::default() { margin.left.fill = fill; } if (margin.right.size > 0 && margin.right.fill == char::default()) || fill != char::default() { margin.right.fill = fill; } cfg.set_margin(margin); } } fn print_grid( f: &mut F, records: &VecRecords>, cfg: &SpannedConfig, dims: D, colors: &ColorMap, ) -> fmt::Result { if !colors.is_empty() { PeekableGrid::new(records, cfg, &dims, colors).build(f) } else { PeekableGrid::new(records, cfg, &dims, NoColors).build(f) } } fn dimension_reastimate( dims: &mut CompleteDimensionVecRecords<'_>, widths: Option>, heights: Option>, hint: Option, ) { let hint = match hint { Some(hint) => hint, None => return, }; match hint { Entity::Global | Entity::Cell(_, _) => { dims_set_widths(dims, widths); dims_set_heights(dims, heights); } Entity::Column(_) => { dims_set_widths(dims, widths); } Entity::Row(_) => { dims_set_heights(dims, heights); } } } fn dims_set_widths(dims: &mut CompleteDimensionVecRecords<'_>, list: Option>) { match list { Some(list) => match dims.get_widths() { Some(widths) => { if widths == list { dims.clear_width(); } else { dims.set_widths(list); } } None => dims.set_widths(list), }, None => { dims.clear_width(); } } } fn dims_set_heights(dims: &mut CompleteDimensionVecRecords<'_>, list: Option>) { match list { Some(list) => match dims.get_heights() { Some(heights) => { if heights == list { dims.clear_height(); } else { dims.set_heights(list); } } None => dims.set_heights(list), }, None => { dims.clear_height(); } } } fn dimension_reastimate_likely(dims: &mut CompleteDimensionVecRecords<'_>, hint: Option) { let hint = match hint { Some(hint) => hint, None => return, }; match hint { Entity::Global | Entity::Cell(_, _) => { dims.clear_width(); dims.clear_height() } Entity::Column(_) => { dims.clear_width(); } Entity::Row(_) => dims.clear_height(), } } tabled-0.18.0/src/tables/table_pool.rs000064400000000000000000001167231046102023000157060ustar 00000000000000use core::fmt::{self, Display, Formatter}; use crate::{ grid::{ config::{AlignmentHorizontal, CompactMultilineConfig, Indent, Sides}, dimension::{DimensionPriority, PoolTableDimension}, records::EmptyRecords, records::IntoRecords, }, settings::{Style, TableOption}, }; /// [`PoolTable`] is a table which allows a greater set of possibilities for cell alignment. /// It's data is not aligned in any way by default. /// /// It works similar to the main [`Table`] by default. /// /// /// ``` /// use tabled::tables::PoolTable; /// /// let data = vec![ /// vec!["Hello", "World", "!"], /// vec!["Salve", "mondo", "!"], /// vec!["Hola", "mundo", "!"], /// ]; /// /// let table = PoolTable::new(data).to_string(); /// /// assert_eq!( /// table, /// "+-------+-------+---+\n\ /// | Hello | World | ! |\n\ /// +-------+-------+---+\n\ /// | Salve | mondo | ! |\n\ /// +-------+-------+---+\n\ /// | Hola | mundo | ! |\n\ /// +-------+-------+---+" /// ) /// ``` /// /// But it allows you to have a different number of columns inside the rows. /// /// ``` /// use tabled::tables::PoolTable; /// /// let data = vec![ /// vec!["Hello", "World", "!"], /// vec!["Salve, mondo!"], /// vec!["Hola", "mundo", "", "", "!"], /// ]; /// /// let table = PoolTable::new(data).to_string(); /// /// assert_eq!( /// table, /// "+---------+---------+----+\n\ /// | Hello | World | ! |\n\ /// +---------+---------+----+\n\ /// | Salve, mondo! |\n\ /// +------+-------+--+--+---+\n\ /// | Hola | mundo | | | ! |\n\ /// +------+-------+--+--+---+" /// ) /// ``` /// /// Notice that you also can build a custom table layout by using [`TableValue`]. /// /// ``` /// use tabled::tables::{PoolTable, TableValue}; /// /// let message = "Hello\nWorld"; /// /// let data = TableValue::Column(vec![ /// TableValue::Row(vec![ /// TableValue::Column(vec![ /// TableValue::Cell(String::from(message)), /// ]), /// TableValue::Column(vec![ /// TableValue::Cell(String::from(message)), /// TableValue::Row(vec![ /// TableValue::Cell(String::from(message)), /// TableValue::Cell(String::from(message)), /// TableValue::Cell(String::from(message)), /// ]) /// ]), /// ]), /// TableValue::Cell(String::from(message)), /// ]); /// /// let table = PoolTable::from(data).to_string(); /// /// assert_eq!( /// table, /// "+-------+-----------------------+\n\ /// | Hello | Hello |\n\ /// | World | World |\n\ /// | +-------+-------+-------+\n\ /// | | Hello | Hello | Hello |\n\ /// | | World | World | World |\n\ /// +-------+-------+-------+-------+\n\ /// | Hello |\n\ /// | World |\n\ /// +-------------------------------+" /// ) /// ``` /// /// [`Table`]: crate::Table #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] pub struct PoolTable { config: CompactMultilineConfig, dims: PoolTableDimension, value: TableValue, } impl PoolTable { /// Creates a [`PoolTable`] out from a record iterator. pub fn new(iter: I) -> Self where I: IntoRecords, I::Cell: AsRef, { let value = TableValue::Column( iter.iter_rows() .into_iter() .map(|row| { row.into_iter() .map(|cell| cell.as_ref().to_string()) .map(TableValue::Cell) .collect() }) .map(TableValue::Row) .collect(), ); Self { config: configure_grid(), dims: PoolTableDimension::new(DimensionPriority::List, DimensionPriority::List), value, } } /// A is a generic function which applies options to the [`PoolTable`] configuration. /// /// Notice that it has a limited support of options. /// /// ``` /// use tabled::tables::PoolTable; /// use tabled::settings::{Style, Padding}; /// /// let data = vec![ /// vec!["Hello", "World", "!"], /// vec!["Salve", "mondo", "!"], /// vec!["Hola", "mundo", "!"], /// ]; /// /// let table = PoolTable::new(data) /// .with(Style::extended()) /// .with(Padding::zero()) /// .to_string(); /// /// assert_eq!( /// table, /// "╔═════╦═════╦═╗\n\ /// ║Hello║World║!║\n\ /// ╠═════╬═════╬═╣\n\ /// ║Salve║mondo║!║\n\ /// ╠═════╬═════╬═╣\n\ /// ║Hola ║mundo║!║\n\ /// ╚═════╩═════╩═╝" /// ) /// ``` pub fn with(&mut self, option: O) -> &mut Self where O: TableOption, { let mut records = EmptyRecords::default(); option.change(&mut records, &mut self.config, &mut self.dims); self } } impl From for PoolTable { fn from(value: TableValue) -> Self { Self { config: configure_grid(), dims: PoolTableDimension::new(DimensionPriority::List, DimensionPriority::List), value, } } } impl Display for PoolTable { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { print::build_table(&self.value, &self.config, self.dims).fmt(f) } } /// [`TableValue`] a structure which is responsible for a [`PoolTable`] layout. #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] pub enum TableValue { /// A horizontal row. Row(Vec), /// A vertical column. Column(Vec), /// A single cell. Cell(String), } fn configure_grid() -> CompactMultilineConfig { let pad = Sides::new( Indent::spaced(1), Indent::spaced(1), Indent::zero(), Indent::zero(), ); let mut cfg = CompactMultilineConfig::new(); cfg.set_padding(pad); cfg.set_alignment_horizontal(AlignmentHorizontal::Left); cfg.set_borders(Style::ascii().get_borders()); cfg } impl TableOption for PoolTableDimension { fn change(self, _: &mut R, _: &mut C, dimension: &mut PoolTableDimension) { *dimension = self; } } impl TableOption for CompactMultilineConfig { fn change(self, _: &mut R, config: &mut CompactMultilineConfig, _: &mut D) { *config = self; } } mod print { use std::{cmp::max, collections::HashMap, iter::repeat}; use crate::{ builder::Builder, grid::{ ansi::ANSIStr, config::{ AlignmentHorizontal, AlignmentVertical, Border, Borders, CompactMultilineConfig, Indent, Sides, }, dimension::{DimensionPriority, PoolTableDimension}, util::string::{ count_lines, get_line_width, get_lines, get_text_dimension, get_text_width, }, }, settings::{Padding, Style}, }; use super::TableValue; #[derive(Debug, Default)] struct PrintContext { pos: usize, is_last_col: bool, is_last_row: bool, is_first_col: bool, is_first_row: bool, kv: bool, kv_is_first: bool, list: bool, list_is_first: bool, no_left: bool, no_right: bool, no_bottom: bool, lean_top: bool, top_intersection: bool, top_left: bool, intersections_horizontal: Vec, intersections_vertical: Vec, size: Dim, } struct CellData { content: String, intersections_horizontal: Vec, intersections_vertical: Vec, } impl CellData { fn new(content: String, i_horizontal: Vec, i_vertical: Vec) -> Self { Self { content, intersections_horizontal: i_horizontal, intersections_vertical: i_vertical, } } } pub(super) fn build_table( val: &TableValue, cfg: &CompactMultilineConfig, dims_priority: PoolTableDimension, ) -> String { let dims = collect_table_dimensions(val, cfg); let ctx = PrintContext { is_last_col: true, is_last_row: true, is_first_col: true, is_first_row: true, size: *dims.all.get(&0).unwrap(), ..Default::default() }; let data = _build_table(val, cfg, &dims, dims_priority, ctx); let mut table = data.content; let margin = cfg.get_margin(); let has_margin = margin.top.size > 0 || margin.bottom.size > 0 || margin.left.size > 0 || margin.right.size > 0; if has_margin { let color = convert_border_colors(*cfg.get_margin_color()); table = set_margin(&table, *margin, color); } table } fn _build_table( val: &TableValue, cfg: &CompactMultilineConfig, dims: &Dimensions, priority: PoolTableDimension, ctx: PrintContext, ) -> CellData { match val { TableValue::Cell(text) => generate_value_cell(text, cfg, ctx), TableValue::Row(list) => { if list.is_empty() { return generate_value_cell("", cfg, ctx); } generate_table_row(list, cfg, dims, priority, ctx) } TableValue::Column(list) => { if list.is_empty() { return generate_value_cell("", cfg, ctx); } generate_table_column(list, cfg, dims, priority, ctx) } } } fn generate_table_column( list: &[TableValue], cfg: &CompactMultilineConfig, dims: &Dimensions, priority: PoolTableDimension, ctx: PrintContext, ) -> CellData { let array_dims = dims.arrays.get(&ctx.pos).unwrap(); let height = dims.all.get(&ctx.pos).unwrap().height; let additional_height = ctx.size.height - height; let (chunk_height, mut rest_height) = split_value(additional_height, list.len()); let mut intersections_horizontal = ctx.intersections_horizontal; let mut intersections_vertical = ctx.intersections_vertical; let mut next_vsplit = false; let mut next_intersections_vertical = vec![]; let mut builder = Builder::new(); for (i, val) in list.iter().enumerate() { let val_pos = *array_dims.index.get(&i).unwrap(); let mut height = dims.all.get(&val_pos).unwrap().height; match priority.height() { DimensionPriority::First => { if i == 0 { height += additional_height; } } DimensionPriority::Last => { if i + 1 == list.len() { height += additional_height; } } DimensionPriority::List => { height += chunk_height; if rest_height > 0 { height += 1; rest_height -= 1; // must be safe } } } let size = Dim::new(ctx.size.width, height); let (split, intersections_vertical) = short_splits3(&mut intersections_vertical, size.height); let old_split = next_vsplit; next_vsplit = split; let is_prev_list_not_first = ctx.list && !ctx.list_is_first; let valctx = PrintContext { pos: val_pos, is_last_col: ctx.is_last_col, is_last_row: ctx.is_last_row && i + 1 == list.len(), is_first_col: ctx.is_first_col, is_first_row: ctx.is_first_row && i == 0, kv: ctx.kv, kv_is_first: ctx.kv_is_first, list: true, list_is_first: i == 0 && !is_prev_list_not_first, no_left: ctx.no_left, no_right: ctx.no_right, no_bottom: ctx.no_bottom && i + 1 == list.len(), lean_top: ctx.lean_top && i == 0, top_intersection: (ctx.top_intersection && i == 0) || old_split, top_left: ctx.top_left || i > 0, intersections_horizontal, intersections_vertical, size, }; let data = _build_table(val, cfg, dims, priority, valctx); intersections_horizontal = data.intersections_horizontal; next_intersections_vertical.extend(data.intersections_vertical); builder.push_record([data.content]); } let table = builder .build() .with(Style::empty()) .with(Padding::zero()) .to_string(); CellData::new(table, intersections_horizontal, next_intersections_vertical) } fn generate_table_row( list: &[TableValue], cfg: &CompactMultilineConfig, dims: &Dimensions, priority: PoolTableDimension, ctx: PrintContext, ) -> CellData { let array_dims = dims.arrays.get(&ctx.pos).unwrap(); let list_width = dims.all.get(&ctx.pos).unwrap().width; let additional_width = ctx.size.width - list_width; let (chunk_width, mut rest_width) = split_value(additional_width, list.len()); let mut intersections_horizontal = ctx.intersections_horizontal; let mut intersections_vertical = ctx.intersections_vertical; let mut new_intersections_horizontal = vec![]; let mut split_next = false; let mut buf = Vec::with_capacity(list.len()); for (i, val) in list.iter().enumerate() { let val_pos = *array_dims.index.get(&i).unwrap(); let mut width = dims.all.get(&val_pos).unwrap().width; match priority.width() { DimensionPriority::First => { if i == 0 { width += additional_width; } } DimensionPriority::Last => { if i + 1 == list.len() { width += additional_width; } } DimensionPriority::List => { width += chunk_width; if rest_width > 0 { width += 1; rest_width -= 1; // must be safe } } } let size = Dim::new(width, ctx.size.height); let (split, intersections_horizontal) = short_splits3(&mut intersections_horizontal, width); let old_split = split_next; split_next = split; let is_prev_list_not_first = ctx.list && !ctx.list_is_first; let valctx = PrintContext { pos: val_pos, is_first_col: ctx.is_first_col && i == 0, is_last_col: ctx.is_last_col && i + 1 == list.len(), is_last_row: ctx.is_last_row, is_first_row: ctx.is_first_row, kv: false, kv_is_first: false, list: false, list_is_first: !is_prev_list_not_first, no_left: false, no_right: !(ctx.is_last_col && i + 1 == list.len()), no_bottom: false, lean_top: !(ctx.is_first_col && i == 0), top_intersection: (ctx.top_intersection && i == 0) || old_split, top_left: ctx.top_left && i == 0, intersections_horizontal, intersections_vertical, size, }; let val = _build_table(val, cfg, dims, priority, valctx); intersections_vertical = val.intersections_vertical; new_intersections_horizontal.extend(val.intersections_horizontal.iter()); let value = val.content; buf.push(value); } let mut builder = Builder::with_capacity(1, buf.len()); builder.push_record(buf); let mut table = builder.build(); let _ = table.with(Style::empty()); let _ = table.with(Padding::zero()); let table = table.to_string(); CellData::new(table, new_intersections_horizontal, intersections_vertical) } fn generate_value_cell( text: &str, cfg: &CompactMultilineConfig, ctx: PrintContext, ) -> CellData { let width = ctx.size.width; let height = ctx.size.height; let table = generate_value_table(text, cfg, ctx); CellData::new(table, vec![width], vec![height]) } fn generate_value_table( text: &str, cfg: &CompactMultilineConfig, mut ctx: PrintContext, ) -> String { if ctx.size.width == 0 || ctx.size.height == 0 { return String::new(); } let halignment = cfg.get_alignment_horizontal(); let valignment = cfg.get_alignment_vertical(); let pad = cfg.get_padding(); let pad_color = convert_border_colors(*cfg.get_padding_color()); let lines_alignment = cfg.get_formatting().allow_lines_alignment; let mut borders = *cfg.get_borders(); let bottom_intersection = cfg.get_borders().bottom_intersection.unwrap_or(' '); let mut horizontal_splits = short_splits(&mut ctx.intersections_horizontal, ctx.size.width); squash_splits(&mut horizontal_splits); let right_intersection = borders.right_intersection.unwrap_or(' '); let mut vertical_splits = short_splits(&mut ctx.intersections_vertical, ctx.size.height); squash_splits(&mut vertical_splits); config_borders(&mut borders, &ctx); let border = create_border(borders); let borders_colors = *cfg.get_borders_color(); let border_color = create_border(borders_colors); let mut height = ctx.size.height; height -= pad.top.size + pad.bottom.size; let mut width = ctx.size.width; width -= pad.left.size + pad.right.size; let count_lines = count_lines(text); let (top, bottom) = indent_vertical(valignment, height, count_lines); let mut buf = String::new(); print_top_line( &mut buf, border, border_color, &horizontal_splits, bottom_intersection, ctx.size.width, ); let mut line_index = 0; let mut vertical_splits = &vertical_splits[..]; for _ in 0..top { let mut border = border; if vertical_splits.first() == Some(&line_index) { border.left = Some(right_intersection); vertical_splits = &vertical_splits[1..]; } print_line(&mut buf, border, border_color, None, ' ', ctx.size.width); line_index += 1; } for _ in 0..pad.top.size { let mut border = border; if vertical_splits.first() == Some(&line_index) { border.left = Some(right_intersection); vertical_splits = &vertical_splits[1..]; } print_line( &mut buf, border, border_color, pad_color.top, pad.top.fill, ctx.size.width, ); line_index += 1; } if lines_alignment { for line in get_lines(text) { let line_width = get_line_width(&line); let (left, right) = indent_horizontal(halignment, width, line_width); if border.has_left() { let mut c = border.left.unwrap_or(' '); if vertical_splits.first() == Some(&line_index) { c = right_intersection; vertical_splits = &vertical_splits[1..]; } print_char(&mut buf, c, border_color.left); } print_chars(&mut buf, pad.left.fill, pad_color.left, pad.left.size); buf.extend(repeat(' ').take(left)); buf.push_str(&line); buf.extend(repeat(' ').take(right)); print_chars(&mut buf, pad.right.fill, pad_color.right, pad.right.size); if border.has_right() { print_char(&mut buf, border.right.unwrap_or(' '), border_color.right); } buf.push('\n'); line_index += 1; } } else { let text_width = get_text_width(text); let (left, _) = indent_horizontal(halignment, width, text_width); for line in get_lines(text) { let line_width = get_line_width(&line); let right = width - line_width - left; if border.has_left() { let mut c = border.left.unwrap_or(' '); if vertical_splits.first() == Some(&line_index) { c = right_intersection; vertical_splits = &vertical_splits[1..]; } print_char(&mut buf, c, border_color.left); } print_chars(&mut buf, pad.left.fill, pad_color.left, pad.left.size); buf.extend(repeat(' ').take(left)); buf.push_str(&line); buf.extend(repeat(' ').take(right)); print_chars(&mut buf, pad.right.fill, pad_color.right, pad.right.size); if border.has_right() { print_char(&mut buf, border.right.unwrap_or(' '), border_color.right); } buf.push('\n'); line_index += 1; } } for _ in 0..pad.bottom.size { let mut border = border; if vertical_splits.first() == Some(&line_index) { border.left = Some(right_intersection); vertical_splits = &vertical_splits[1..]; } print_line( &mut buf, border, border_color, pad_color.bottom, pad.bottom.fill, ctx.size.width, ); line_index += 1; } for _ in 0..bottom { let mut border = border; if vertical_splits.first() == Some(&line_index) { border.left = Some(right_intersection); vertical_splits = &vertical_splits[1..]; } print_line(&mut buf, border, border_color, None, ' ', ctx.size.width); line_index += 1; } print_bottom_line(&mut buf, border, border_color, ctx.size.width); let _ = buf.remove(buf.len() - 1); buf } fn print_chars(buf: &mut String, c: char, color: Option>, width: usize) { match color { Some(color) => { buf.push_str(color.get_prefix()); buf.extend(repeat(c).take(width)); buf.push_str(color.get_suffix()); } None => buf.extend(repeat(c).take(width)), } } fn print_char(buf: &mut String, c: char, color: Option>) { match color { Some(color) => { buf.push_str(color.get_prefix()); buf.push(c); buf.push_str(color.get_suffix()); } None => buf.push(c), } } fn print_line( buf: &mut String, border: Border, border_color: Border>, color: Option>, c: char, width: usize, ) { if border.has_left() { let c = border.left.unwrap_or(' '); print_char(buf, c, border_color.left); } print_chars(buf, c, color, width); if border.has_right() { let c = border.right.unwrap_or(' '); print_char(buf, c, border_color.right); } buf.push('\n'); } fn print_top_line( buf: &mut String, border: Border, color: Border>, splits: &[usize], split_char: char, width: usize, ) { if !border.has_top() { return; } let mut used_color: Option> = None; if border.has_left() { if let Some(color) = color.left_top_corner { used_color = Some(color); buf.push_str(color.get_prefix()); } let c = border.left_top_corner.unwrap_or(' '); buf.push(c); } if let Some(color) = color.top { match used_color { Some(used) => { if used != color { buf.push_str(used.get_suffix()); buf.push_str(color.get_prefix()); } } None => { buf.push_str(color.get_prefix()); used_color = Some(color); } } } let c = border.top.unwrap_or(' '); if splits.is_empty() { buf.extend(repeat(c).take(width)); } else { let mut splits = splits; for i in 0..width { if splits.first() == Some(&i) { buf.push(split_char); splits = &splits[1..]; } else { buf.push(c); } } } if border.has_right() { if let Some(color) = color.right_top_corner { match used_color { Some(used) => { if used != color { buf.push_str(used.get_suffix()); buf.push_str(color.get_prefix()); } } None => { buf.push_str(color.get_prefix()); used_color = Some(color); } } } let c = border.right_top_corner.unwrap_or(' '); buf.push(c); } if let Some(used) = used_color { buf.push_str(used.get_suffix()); } buf.push('\n'); } fn print_bottom_line( buf: &mut String, border: Border, color: Border>, width: usize, ) { if !border.has_bottom() { return; } let mut used_color: Option> = None; if border.has_left() { if let Some(color) = color.left_bottom_corner { used_color = Some(color); buf.push_str(color.get_prefix()); } let c = border.left_bottom_corner.unwrap_or(' '); buf.push(c); } if let Some(color) = color.bottom { match used_color { Some(used) => { if used != color { buf.push_str(used.get_suffix()); buf.push_str(color.get_prefix()); } } None => { buf.push_str(color.get_prefix()); used_color = Some(color); } } } let c = border.bottom.unwrap_or(' '); buf.extend(repeat(c).take(width)); if border.has_right() { if let Some(color) = color.right_bottom_corner { match used_color { Some(used) => { if used != color { buf.push_str(used.get_suffix()); buf.push_str(color.get_prefix()); } } None => { buf.push_str(color.get_prefix()); used_color = Some(color); } } } let c = border.right_bottom_corner.unwrap_or(' '); buf.push(c); } if let Some(used) = used_color { buf.push_str(used.get_suffix()); } buf.push('\n'); } fn create_border(borders: Borders) -> Border { Border { top: borders.top, bottom: borders.bottom, left: borders.left, right: borders.right, left_top_corner: borders.top_left, left_bottom_corner: borders.bottom_left, right_top_corner: borders.top_right, right_bottom_corner: borders.bottom_right, } } fn config_borders(borders: &mut Borders, ctx: &PrintContext) { // set top_left { if ctx.kv && ctx.kv_is_first { borders.top_left = borders.top_intersection; } if ctx.kv && !ctx.kv_is_first { borders.top_left = borders.intersection; } if ctx.kv && ctx.list && !ctx.list_is_first { borders.top_left = borders.left_intersection; } if ctx.is_first_col && !ctx.is_first_row { borders.top_left = borders.left_intersection; } if ctx.lean_top { borders.top_left = borders.top_intersection; } if ctx.top_left { borders.top_left = borders.left_intersection; } if ctx.top_intersection { borders.top_left = borders.intersection; } } if ctx.is_last_col && !ctx.is_first_row { borders.top_right = borders.right_intersection; } if !ctx.is_first_col && ctx.is_last_row { borders.bottom_left = borders.bottom_intersection; } if !ctx.is_last_row || ctx.no_bottom { cfg_no_bottom_borders(borders); } if ctx.no_right { cfg_no_right_borders(borders); } } fn cfg_no_bottom_borders(borders: &mut Borders) { borders.bottom = None; borders.bottom_intersection = None; borders.bottom_left = None; borders.bottom_right = None; borders.horizontal = None; } fn cfg_no_right_borders(borders: &mut Borders) { borders.right = None; borders.right_intersection = None; borders.top_right = None; borders.bottom_right = None; borders.vertical = None; } #[derive(Debug, Default)] struct Dimensions { all: HashMap, arrays: HashMap, } #[derive(Debug, Default, Clone, Copy)] struct Dim { width: usize, height: usize, } impl Dim { fn new(width: usize, height: usize) -> Self { Self { width, height } } } #[derive(Debug, Default)] struct ArrayDimensions { max: Dim, index: HashMap, } fn collect_table_dimensions(val: &TableValue, cfg: &CompactMultilineConfig) -> Dimensions { let mut buf = Dimensions::default(); let (dim, _) = __collect_table_dims(&mut buf, val, cfg, 0); let _ = buf.all.insert(0, dim); buf } fn __collect_table_dims( buf: &mut Dimensions, val: &TableValue, cfg: &CompactMultilineConfig, pos: usize, ) -> (Dim, usize) { match val { TableValue::Cell(text) => (str_dimension(text, cfg), 0), TableValue::Row(list) => { if list.is_empty() { return (empty_dimension(cfg), 0); } let mut index = ArrayDimensions { max: Dim::default(), index: HashMap::with_capacity(list.len()), }; let mut total_width = 0; let mut count_elements = list.len(); let mut val_pos = pos + 1; for (i, value) in list.iter().enumerate() { let (dim, elements) = __collect_table_dims(buf, value, cfg, val_pos); count_elements += elements; total_width += dim.width; index.max.width = max(index.max.width, dim.width); index.max.height = max(index.max.height, dim.height); let _ = buf.all.insert(val_pos, dim); let _ = index.index.insert(i, val_pos); val_pos += 1 + elements; } let max_height = index.max.height; let _ = buf.arrays.insert(pos, index); let has_vertical = cfg.get_borders().has_left(); total_width += has_vertical as usize * (list.len() - 1); (Dim::new(total_width, max_height), count_elements) } TableValue::Column(list) => { if list.is_empty() { return (empty_dimension(cfg), 0); } let mut index = ArrayDimensions { max: Dim::default(), index: HashMap::with_capacity(list.len()), }; let mut total_height = 0; let mut count_elements = list.len(); let mut val_pos = pos + 1; for (i, value) in list.iter().enumerate() { let (dim, elements) = __collect_table_dims(buf, value, cfg, val_pos); count_elements += elements; total_height += dim.height; index.max.width = max(index.max.width, dim.width); index.max.height = max(index.max.height, dim.height); let _ = buf.all.insert(val_pos, dim); let _ = index.index.insert(i, val_pos); val_pos += 1 + elements; } let max_width = index.max.width; let _ = buf.arrays.insert(pos, index); let has_horizontal = cfg.get_borders().has_top(); total_height += has_horizontal as usize * (list.len() - 1); (Dim::new(max_width, total_height), count_elements) } } } fn empty_dimension(cfg: &CompactMultilineConfig) -> Dim { Dim::new(get_padding_horizontal(cfg), 1 + get_padding_vertical(cfg)) } fn str_dimension(text: &str, cfg: &CompactMultilineConfig) -> Dim { let (count_lines, width) = get_text_dimension(text); let w = width + get_padding_horizontal(cfg); let h = count_lines + get_padding_vertical(cfg); Dim::new(w, h) } fn get_padding_horizontal(cfg: &CompactMultilineConfig) -> usize { let pad = cfg.get_padding(); pad.left.size + pad.right.size } fn get_padding_vertical(cfg: &CompactMultilineConfig) -> usize { let pad = cfg.get_padding(); pad.top.size + pad.bottom.size } fn split_value(value: usize, by: usize) -> (usize, usize) { let val = value / by; let rest = value - val * by; (val, rest) } fn indent_vertical(al: AlignmentVertical, available: usize, real: usize) -> (usize, usize) { let top = indent_top(al, available, real); let bottom = available - real - top; (top, bottom) } fn indent_horizontal(al: AlignmentHorizontal, available: usize, real: usize) -> (usize, usize) { let top = indent_left(al, available, real); let right = available - real - top; (top, right) } fn indent_top(al: AlignmentVertical, available: usize, real: usize) -> usize { match al { AlignmentVertical::Top => 0, AlignmentVertical::Bottom => available - real, AlignmentVertical::Center => (available - real) / 2, } } fn indent_left(al: AlignmentHorizontal, available: usize, real: usize) -> usize { match al { AlignmentHorizontal::Left => 0, AlignmentHorizontal::Right => available - real, AlignmentHorizontal::Center => (available - real) / 2, } } fn short_splits(splits: &mut Vec, width: usize) -> Vec { if splits.is_empty() { return Vec::new(); } let mut out = Vec::new(); let mut pos = 0; for &split in splits.iter() { if pos + split >= width { break; } pos += split; out.push(pos); } let _ = splits.drain(..out.len()); if !splits.is_empty() && pos <= width { let rest = width - pos; splits[0] -= rest; } out } fn short_splits3(splits: &mut Vec, width: usize) -> (bool, Vec) { if splits.is_empty() { return (false, Vec::new()); } let mut out = Vec::new(); let mut pos = 0; for &split in splits.iter() { if pos + split >= width { break; } pos += split + 1; out.push(split); } let _ = splits.drain(..out.len()); if splits.is_empty() { return (false, out); } if pos <= width { splits[0] -= width - pos; if splits[0] > 0 { splits[0] -= 1; } else { let _ = splits.remove(0); return (true, out); } } (false, out) } fn squash_splits(splits: &mut [usize]) { splits.iter_mut().enumerate().for_each(|(i, s)| *s += i); } fn set_margin( table: &str, margin: Sides, color: Sides>>, ) -> String { if table.is_empty() { return String::new(); } let mut buf = String::new(); let width = get_text_width(table); let top_color = color.top; let bottom_color = color.bottom; let left_color = color.left; let right_color = color.right; for _ in 0..margin.top.size { print_chars(&mut buf, margin.left.fill, left_color, margin.left.size); print_chars(&mut buf, margin.top.fill, top_color, width); print_chars(&mut buf, margin.right.fill, right_color, margin.right.size); buf.push('\n'); } for line in get_lines(table) { print_chars(&mut buf, margin.left.fill, left_color, margin.left.size); buf.push_str(&line); print_chars(&mut buf, margin.right.fill, right_color, margin.right.size); buf.push('\n'); } for _ in 0..margin.bottom.size { print_chars(&mut buf, margin.left.fill, left_color, margin.left.size); print_chars(&mut buf, margin.bottom.fill, bottom_color, width); print_chars(&mut buf, margin.right.fill, right_color, margin.right.size); buf.push('\n'); } let _ = buf.remove(buf.len() - 1); buf } fn convert_border_colors( pad_color: Sides>, ) -> Sides>> { Sides::new( (!pad_color.left.is_empty()).then_some(pad_color.left), (!pad_color.right.is_empty()).then_some(pad_color.right), (!pad_color.top.is_empty()).then_some(pad_color.top), (!pad_color.bottom.is_empty()).then_some(pad_color.bottom), ) } } tabled-0.18.0/src/util/mod.rs000064400000000000000000000001441046102023000140350ustar 00000000000000#[cfg(feature = "std")] pub(crate) mod string; #[cfg(feature = "std")] pub(crate) mod utf8_writer; tabled-0.18.0/src/util/string.rs000064400000000000000000000252761046102023000146010ustar 00000000000000use std::borrow::Cow; use crate::grid::util::string::get_char_width; /// The function cuts the string to a specific width. /// Preserving colors with `ansi` feature on. pub(crate) fn split_str(s: &str, width: usize) -> (Cow<'_, str>, Cow<'_, str>) { #[cfg(feature = "ansi")] { const REPLACEMENT: char = '\u{FFFD}'; let stripped = ansi_str::AnsiStr::ansi_strip(s); let (length, cutwidth, csize) = split_at_width(&stripped, width); let (mut lhs, mut rhs) = ansi_str::AnsiStr::ansi_split_at(s, length); if csize > 0 { let mut buf = lhs.into_owned(); let count_unknowns = width - cutwidth; buf.extend(std::iter::repeat(REPLACEMENT).take(count_unknowns)); lhs = Cow::Owned(buf); rhs = Cow::Owned(ansi_str::AnsiStr::ansi_cut(rhs.as_ref(), csize..).into_owned()); } (lhs, rhs) } #[cfg(not(feature = "ansi"))] { const REPLACEMENT: char = '\u{FFFD}'; let (length, cutwidth, csize) = split_at_width(s, width); let (lhs, rhs) = s.split_at(length); if csize == 0 { return (Cow::Borrowed(lhs), Cow::Borrowed(rhs)); } let count_unknowns = width - cutwidth; let mut buf = lhs.to_owned(); buf.extend(std::iter::repeat(REPLACEMENT).take(count_unknowns)); (Cow::Owned(buf), Cow::Borrowed(&rhs[csize..])) } } /// The function cuts the string to a specific width. /// Preserving colors with `ansi` feature on. pub(crate) fn cut_str(s: &str, width: usize) -> Cow<'_, str> { #[cfg(feature = "ansi")] { const REPLACEMENT: char = '\u{FFFD}'; let stripped = ansi_str::AnsiStr::ansi_strip(s); let (length, cutwidth, csize) = split_at_width(&stripped, width); let mut buf = ansi_str::AnsiStr::ansi_cut(s, ..length); if csize != 0 { let mut b = buf.into_owned(); let count_unknowns = width - cutwidth; b.extend(std::iter::repeat(REPLACEMENT).take(count_unknowns)); buf = Cow::Owned(b); } buf } #[cfg(not(feature = "ansi"))] { cut_str2(s, width) } } /// The function cuts the string to a specific width. /// While not preserving ansi sequences. pub(crate) fn cut_str2(text: &str, width: usize) -> Cow<'_, str> { const REPLACEMENT: char = '\u{FFFD}'; let (length, cutwidth, csize) = split_at_width(text, width); if csize == 0 { let buf = &text[..length]; return Cow::Borrowed(buf); } let buf = &text[..length]; let mut buf = buf.to_owned(); let count_unknowns = width - cutwidth; buf.extend(std::iter::repeat(REPLACEMENT).take(count_unknowns)); Cow::Owned(buf) } /// The function splits a string in the position and /// returns a exact number of bytes before the position and in case of a split in an unicode grapheme /// a width of a character which was tried to be split in. pub(crate) fn split_at_width(s: &str, at_width: usize) -> (usize, usize, usize) { let mut length = 0; let mut width = 0; for c in s.chars() { if width == at_width { break; }; if c == '\n' { width = 0; length += 1; continue; } let c_width = get_char_width(c); let c_length = c.len_utf8(); // We cut the chars which takes more then 1 symbol to display, // in order to archive the necessary width. if width + c_width > at_width { return (length, width, c_length); } width += c_width; length += c_length; } (length, width, 0) } /// Strip OSC codes from `s`. If `s` is a single OSC8 hyperlink, with no other text, then return /// (s_with_all_hyperlinks_removed, Some(url)). If `s` does not meet this description, then return /// (s_with_all_hyperlinks_removed, None). Any ANSI color sequences in `s` will be retained. See /// /// /// The function is based on Dan Davison delta ansi library. #[cfg(feature = "ansi")] pub(crate) fn strip_osc(text: &str) -> (String, Option) { #[derive(Debug)] enum ExtractOsc8HyperlinkState { ExpectOsc8Url, ExpectFirstText, ExpectMoreTextOrTerminator, SeenOneHyperlink, WillNotReturnUrl, } use ExtractOsc8HyperlinkState::*; let mut url = None; let mut state = ExpectOsc8Url; let mut buf = String::with_capacity(text.len()); for el in ansitok::parse_ansi(text) { match el.kind() { ansitok::ElementKind::Osc => match state { ExpectOsc8Url => { url = Some(&text[el.start()..el.end()]); state = ExpectFirstText; } ExpectMoreTextOrTerminator => state = SeenOneHyperlink, _ => state = WillNotReturnUrl, }, ansitok::ElementKind::Sgr => buf.push_str(&text[el.start()..el.end()]), ansitok::ElementKind::Csi => buf.push_str(&text[el.start()..el.end()]), ansitok::ElementKind::Esc => {} ansitok::ElementKind::Text => { buf.push_str(&text[el.start()..el.end()]); match state { ExpectFirstText => state = ExpectMoreTextOrTerminator, ExpectMoreTextOrTerminator => {} _ => state = WillNotReturnUrl, } } } } match state { WillNotReturnUrl => (buf, None), _ => { let url = url.and_then(|s| { s.strip_prefix("\x1b]8;;") .and_then(|s| s.strip_suffix('\x1b')) }); if let Some(url) = url { (buf, Some(url.to_string())) } else { (buf, None) } } } } #[cfg(test)] mod tests { use super::*; use crate::grid::util::string::get_line_width; #[test] fn strip_test() { assert_eq!(cut_str("123456", 0), ""); assert_eq!(cut_str("123456", 3), "123"); assert_eq!(cut_str("123456", 10), "123456"); assert_eq!(cut_str("a week ago", 4), "a we"); assert_eq!(cut_str("😳😳😳😳😳", 0), ""); assert_eq!(cut_str("😳😳😳😳😳", 3), "😳�"); assert_eq!(cut_str("😳😳😳😳😳", 4), "😳😳"); assert_eq!(cut_str("😳😳😳😳😳", 20), "😳😳😳😳😳"); assert_eq!(cut_str("🏳️🏳️", 0), ""); assert_eq!(cut_str("🏳️🏳️", 1), "🏳"); assert_eq!(cut_str("🏳️🏳️", 2), "🏳\u{fe0f}🏳"); assert_eq!(get_line_width("🏳️🏳️"), get_line_width("🏳\u{fe0f}🏳\u{fe0f}")); assert_eq!(cut_str("🎓", 1), "�"); assert_eq!(cut_str("🎓", 2), "🎓"); assert_eq!(cut_str("🥿", 1), "�"); assert_eq!(cut_str("🥿", 2), "🥿"); assert_eq!(cut_str("🩰", 1), "�"); assert_eq!(cut_str("🩰", 2), "🩰"); assert_eq!(cut_str("👍🏿", 1), "�"); assert_eq!(cut_str("👍🏿", 2), "👍"); assert_eq!(cut_str("👍🏿", 3), "👍�"); assert_eq!(cut_str("👍🏿", 4), "👍🏿"); assert_eq!(cut_str("🇻🇬", 1), "🇻"); assert_eq!(cut_str("🇻🇬", 2), "🇻🇬"); assert_eq!(cut_str("🇻🇬", 3), "🇻🇬"); assert_eq!(cut_str("🇻🇬", 4), "🇻🇬"); } #[cfg(feature = "ansi")] #[test] fn strip_color_test() { let numbers = "\u{1b}[31;100m123456\u{1b}[39m\u{1b}[49m"; assert_eq!(cut_str(numbers, 0), "\u{1b}[31;100m\u{1b}[39m\u{1b}[49m"); assert_eq!(cut_str(numbers, 3), "\u{1b}[31;100m123\u{1b}[39m\u{1b}[49m"); assert_eq!( cut_str(numbers, 10), "\u{1b}[31;100m123456\u{1b}[39m\u{1b}[49m" ); let emojies = "\u{1b}[31;100m😳😳😳😳😳\u{1b}[39m\u{1b}[49m"; assert_eq!(cut_str(emojies, 0), "\u{1b}[31;100m\u{1b}[39m\u{1b}[49m"); assert_eq!(cut_str(emojies, 3), "\u{1b}[31;100m😳\u{1b}[39m\u{1b}[49m�"); assert_eq!( cut_str(emojies, 4), "\u{1b}[31;100m😳😳\u{1b}[39m\u{1b}[49m" ); assert_eq!( cut_str(emojies, 20), "\u{1b}[31;100m😳😳😳😳😳\u{1b}[39m\u{1b}[49m" ); let emojies = "\u{1b}[31;100m🏳️🏳️\u{1b}[39m\u{1b}[49m"; assert_eq!(cut_str(emojies, 0), "\u{1b}[31;100m\u{1b}[39m\u{1b}[49m"); assert_eq!(cut_str(emojies, 1), "\u{1b}[31;100m🏳\u{1b}[39m\u{1b}[49m"); assert_eq!( cut_str(emojies, 2), "\u{1b}[31;100m🏳\u{fe0f}🏳\u{1b}[39m\u{1b}[49m" ); assert_eq!( get_line_width(emojies), get_line_width("\u{1b}[31;100m🏳\u{fe0f}🏳\u{fe0f}\u{1b}[39m\u{1b}[49m") ); } #[test] #[cfg(feature = "ansi")] fn test_color_strip() { let s = "\u{1b}[5;33;48;2;12;200;100mCollored string\u{1b}[0m"; assert_eq!( cut_str(s, 1), "\u{1b}[5;33;48;2;12;200;100mC\u{1b}[25m\u{1b}[39m\u{1b}[49m" ) } #[test] #[cfg(feature = "ansi")] fn test_srip_osc() { assert_eq!( strip_osc("just a string here"), (String::from("just a string here"), None) ); assert_eq!( strip_osc("/etc/rc.conf"), (String::from("/etc/rc.conf"), None) ); assert_eq!( strip_osc( "https://gitlab.com/finestructure/swiftpackageindex-builder/-/pipelines/1054655982" ), (String::from("https://gitlab.com/finestructure/swiftpackageindex-builder/-/pipelines/1054655982"), None) ); assert_eq!( strip_osc(&build_link_prefix_suffix("just a string here")), (String::default(), Some(String::from("just a string here"))) ); assert_eq!( strip_osc(&build_link_prefix_suffix("/etc/rc.conf")), (String::default(), Some(String::from("/etc/rc.conf"))) ); assert_eq!( strip_osc( &build_link_prefix_suffix("https://gitlab.com/finestructure/swiftpackageindex-builder/-/pipelines/1054655982") ), (String::default(), Some(String::from("https://gitlab.com/finestructure/swiftpackageindex-builder/-/pipelines/1054655982"))) ); #[cfg(feature = "ansi")] fn build_link_prefix_suffix(url: &str) -> String { // https://gist.github.com/egmontkob/eb114294efbcd5adb1944c9f3cb5feda let osc8 = "\x1b]8;;"; let st = "\x1b\\"; format!("{osc8}{url}{st}") } } } tabled-0.18.0/src/util/utf8_writer.rs000064400000000000000000000010171046102023000155400ustar 00000000000000use std::fmt; use std::io; pub(crate) struct UTF8Writer(W); impl UTF8Writer { pub(crate) fn new(writer: W) -> Self { Self(writer) } } impl fmt::Write for UTF8Writer where W: io::Write, { fn write_str(&mut self, s: &str) -> fmt::Result { let mut buf = s.as_bytes(); loop { let n = self.0.write(buf).map_err(|_| fmt::Error)?; if n == buf.len() { break; } buf = &buf[n..]; } Ok(()) } } tabled-0.18.0/tests/core/builder_test.rs000064400000000000000000000502571046102023000163030ustar 00000000000000#![cfg(feature = "std")] use std::iter::FromIterator; use tabled::builder::Builder; use testing_table::test_table; test_table!( push_record, { let mut b = Builder::default(); b.push_record(["1", "2", "3"]); b.push_record(["a", "b", "c"]); b.push_record(["d", "e", "f"]); b.build() }, "+---+---+---+" "| 1 | 2 | 3 |" "+---+---+---+" "| a | b | c |" "+---+---+---+" "| d | e | f |" "+---+---+---+" ); test_table!( header_remove_0, { let mut b = Builder::default(); b.push_record(["1", "2", "3"]); b.push_record(["a", "b", "c"]); b.push_record(["d", "e", "f"]); b.remove_record(0); b.build() }, "+---+---+---+" "| a | b | c |" "+---+---+---+" "| d | e | f |" "+---+---+---+" ); test_table!( header_remove_1, { let mut b = Builder::default(); b.push_record(["a", "b", "c"]); b.push_record(["1", "2", "3", "4", "5"]); b.push_record(["d", "e", "f"]); b.remove_record(1); b.build() }, "+---+---+---+--+--+" "| a | b | c | | |" "+---+---+---+--+--+" "| d | e | f | | |" "+---+---+---+--+--+" ); test_table!( remove_row_0, { let mut b = Builder::default(); b.push_record(["1", "2", "3"]); b.push_record(["d", "e", "f"]); b.push_record(["a", "b", "c"]); b.remove_record(1); b.build() }, "+---+---+---+" "| 1 | 2 | 3 |" "+---+---+---+" "| a | b | c |" "+---+---+---+" ); test_table!( remove_column_0, { let mut b = Builder::default(); b.push_record(["1", "2", "3"]); b.push_record(["a", "b", "c"]); b.push_record(["d", "e", "f"]); b.remove_column(2); b.build() }, "+---+---+" "| 1 | 2 |" "+---+---+" "| a | b |" "+---+---+" "| d | e |" "+---+---+" ); test_table!( push_column_0, { let mut b = Builder::default(); b.push_record(["1", "2", "3"]); b.push_record(["a", "b", "c"]); b.push_record(["d", "e", "f"]); b.push_column(["4", "g", "h"]); b.build() }, "+---+---+---+---+" "| 1 | 2 | 3 | 4 |" "+---+---+---+---+" "| a | b | c | g |" "+---+---+---+---+" "| d | e | f | h |" "+---+---+---+---+" ); test_table!( push_column_1, { let mut b = Builder::default(); b.push_record(["a", "b", "c"]); b.push_record(["d", "e", "f"]); b.push_column(["g", "h"]); b.build() }, "+---+---+---+---+" "| a | b | c | g |" "+---+---+---+---+" "| d | e | f | h |" "+---+---+---+---+" ); test_table!( push_column_2, { let mut b = Builder::default(); b.push_column(["a", "d"]); b.push_column(["b", "e"]); b.push_column(["c", "f"]); b.push_column(["g", "h"]); b.build() }, "+---+---+---+---+" "| a | b | c | g |" "+---+---+---+---+" "| d | e | f | h |" "+---+---+---+---+" ); test_table!( push_column_3, { let mut b = Builder::default(); b.push_column(["a", "d"]); b.push_column(["b", "e"]); b.push_column(["c", "f", "f"]); b.push_column(["g", "h"]); let a: [&str; 0] = []; b.push_column(a); b.build() }, "+---+---+---+---+--+" "| a | b | c | g | |" "+---+---+---+---+--+" "| d | e | f | h | |" "+---+---+---+---+--+" "| | | f | | |" "+---+---+---+---+--+" ); test_table!( push_column_4, { let mut b = Builder::default(); b.push_column(["4", "g", "h"]); b.build() }, "+---+" "| 4 |" "+---+" "| g |" "+---+" "| h |" "+---+" ); test_table!( push_column_5, { let mut b = Builder::default(); b.push_column(["4", "g", "h"]); b.push_column(["5", "g", "h", "j"]); b.push_column(["6"]); b.build() }, "+---+---+---+" "| 4 | 5 | 6 |" "+---+---+---+" "| g | g | |" "+---+---+---+" "| h | h | |" "+---+---+---+" "| | j | |" "+---+---+---+" ); test_table!( insert_column_0, { let mut b = Builder::default(); b.push_record(["a", "b", "c"]); b.push_record(["d", "e", "f"]); b.insert_column(2, ["k", "n"]); b.build() }, "+---+---+---+---+" "| a | b | k | c |" "+---+---+---+---+" "| d | e | n | f |" "+---+---+---+---+" ); test_table!( insert_column_1, { let mut b = Builder::default(); b.push_record(["1", "2", "3"]); b.push_record(["a", "b", "c"]); b.push_record(["d", "e", "f"]); b.insert_column(3, ["4", "l", "d"]); b.build() }, "+---+---+---+---+" "| 1 | 2 | 3 | 4 |" "+---+---+---+---+" "| a | b | c | l |" "+---+---+---+---+" "| d | e | f | d |" "+---+---+---+---+" ); test_table!( reset_table_0, { let mut b = Builder::default(); b.push_record(["1", "2", "3"]); b.push_record(["a", "b", "c"]); b.push_record(["d", "e", "f"]); b.clear(); b.build() }, "" ); test_table!( from_iter, Builder::from_iter([["n", "name"], ["0", "Dmitriy"], ["1", "Vladislav"]]).build(), "+---+-----------+" "| n | name |" "+---+-----------+" "| 0 | Dmitriy |" "+---+-----------+" "| 1 | Vladislav |" "+---+-----------+" ); test_table!( used_with_different_number_of_columns_0, { let mut b = Builder::default(); b.push_record(["1", "2"]); b.push_record(["a", "b", "c"]); b.push_record(["d"]); b.build() }, "+---+---+---+" "| 1 | 2 | |" "+---+---+---+" "| a | b | c |" "+---+---+---+" "| d | | |" "+---+---+---+" ); test_table!( used_with_different_number_of_columns_1, { let mut b = Builder::default(); b.push_record(["1", "2", "3"]); b.push_record(["a", "b"]); b.push_record(["d"]); b.build() }, "+---+---+---+" "| 1 | 2 | 3 |" "+---+---+---+" "| a | b | |" "+---+---+---+" "| d | | |" "+---+---+---+" ); test_table!( used_with_different_number_of_columns_2, { let mut b = Builder::default(); b.push_record(["1"]); b.push_record(["a", "b"]); b.push_record(["d", "e", "f"]); b.build() }, "+---+---+---+" "| 1 | | |" "+---+---+---+" "| a | b | |" "+---+---+---+" "| d | e | f |" "+---+---+---+" ); test_table!( with_default_cell_0, { let mut b = Builder::default(); b.push_record(["1", "2"]); b.set_empty("NaN"); b.push_record(["a", "b", "c"]); b.push_record(["d"]); b.build() }, "+---+-----+-----+" "| 1 | 2 | NaN |" "+---+-----+-----+" "| a | b | c |" "+---+-----+-----+" "| d | NaN | NaN |" "+---+-----+-----+" ); test_table!( with_default_cell_1, { let mut b = Builder::default(); b.push_record(["1"]); b.set_empty("NaN"); b.push_record(["a", "b"]); b.push_record(["d", "e", "f"]); b.build() }, "+---+-----+-----+" "| 1 | NaN | NaN |" "+---+-----+-----+" "| a | b | NaN |" "+---+-----+-----+" "| d | e | f |" "+---+-----+-----+" ); test_table!( extend, { let mut b = Builder::default(); b.extend(["1", "2", "3"]); b.extend(["a", "b", "c"]); b.extend(["d", "e", "f"]); b.build() }, "+---+---+---+" "| 1 | 2 | 3 |" "+---+---+---+" "| a | b | c |" "+---+---+---+" "| d | e | f |" "+---+---+---+" ); test_table!( from_vector_0, Builder::from_iter(vec![ vec!["1".to_string(), "2".to_string(), "3".to_string()], vec!["a".to_string(), "b".to_string(), "c".to_string()], vec!["d".to_string(), "e".to_string(), "f".to_string()], ]) .build(), "+---+---+---+" "| 1 | 2 | 3 |" "+---+---+---+" "| a | b | c |" "+---+---+---+" "| d | e | f |" "+---+---+---+" ); test_table!( from_with_empty_lines_0, Builder::from_iter(vec![ vec!["1".to_string(), "2".to_string(), "3".to_string()], vec![], vec![], vec!["d".to_string(), "e".to_string(), "f".to_string()], ]) .build(), "+---+---+---+" "| 1 | 2 | 3 |" "+---+---+---+" "| | | |" "+---+---+---+" "| | | |" "+---+---+---+" "| d | e | f |" "+---+---+---+" ); test_table!( from_with_empty_lines_1, Builder::from_iter(vec![ vec!["1".to_string(), "2".to_string(), "3".to_string()], vec![], vec!["d".to_string(), "e".to_string(), "f".to_string()], ]) .build(), "+---+---+---+" "| 1 | 2 | 3 |" "+---+---+---+" "| | | |" "+---+---+---+" "| d | e | f |" "+---+---+---+" ); test_table!( from_with_empty_lines_2, Builder::from_iter(vec![ vec![], vec!["1".to_string(), "2".to_string(), "3".to_string()], vec!["d".to_string(), "e".to_string(), "f".to_string()], ]) .build(), "+---+---+---+" "| | | |" "+---+---+---+" "| 1 | 2 | 3 |" "+---+---+---+" "| d | e | f |" "+---+---+---+" ); test_table!( from_with_empty_lines_3, Builder::from_iter(vec![ vec!["1".to_string(), "2".to_string(), "3".to_string()], vec!["d".to_string(), "e".to_string(), "f".to_string()], vec![], ]) .build(), "+---+---+---+" "| 1 | 2 | 3 |" "+---+---+---+" "| d | e | f |" "+---+---+---+" "| | | |" "+---+---+---+" ); test_table!( clean_0, clean(Builder::from_iter([["1", "2", "3"], ["a", "b", "c"], ["d", "e", "f"]])), "+---+---+---+" "| 1 | 2 | 3 |" "+---+---+---+" "| a | b | c |" "+---+---+---+" "| d | e | f |" "+---+---+---+" ); test_table!( clean_1, clean(Builder::from_iter([["", "2", "3"], ["", "b", "c"], ["", "e", "f"]])), "+---+---+" "| 2 | 3 |" "+---+---+" "| b | c |" "+---+---+" "| e | f |" "+---+---+" ); test_table!( clean_2, clean(Builder::from_iter([["1", "", "3"], ["a", "", "c"], ["d", "", "f"]])), "+---+---+" "| 1 | 3 |" "+---+---+" "| a | c |" "+---+---+" "| d | f |" "+---+---+" ); test_table!( clean_3, clean(Builder::from_iter([["1", "2", ""], ["a", "b", ""], ["d", "e", ""]])), "+---+---+" "| 1 | 2 |" "+---+---+" "| a | b |" "+---+---+" "| d | e |" "+---+---+" ); test_table!( clean_4, clean(Builder::from_iter([["", "", "3"], ["", "", "c"], ["", "", "f"]])), "+---+" "| 3 |" "+---+" "| c |" "+---+" "| f |" "+---+" ); test_table!( clean_5, clean(Builder::from_iter([["1", "", ""], ["a", "", ""], ["d", "", ""]])), "+---+" "| 1 |" "+---+" "| a |" "+---+" "| d |" "+---+" ); test_table!( clean_6, clean(Builder::from_iter([["", "2", ""], ["", "b", ""], ["", "e", ""]])), "+---+" "| 2 |" "+---+" "| b |" "+---+" "| e |" "+---+" ); test_table!( clean_7, clean(Builder::from_iter([["", "", ""], ["a", "b", "c"], ["d", "e", "f"]])), "+---+---+---+" "| a | b | c |" "+---+---+---+" "| d | e | f |" "+---+---+---+" ); test_table!( clean_8, clean(Builder::from_iter([["1", "2", "3"], ["", "", ""], ["d", "e", "f"]])), "+---+---+---+" "| 1 | 2 | 3 |" "+---+---+---+" "| d | e | f |" "+---+---+---+" ); test_table!( clean_9, clean(Builder::from_iter([["1", "2", "3"], ["a", "b", "c"], ["", "", ""]])), "+---+---+---+" "| 1 | 2 | 3 |" "+---+---+---+" "| a | b | c |" "+---+---+---+" ); test_table!( clean_10, clean(Builder::from_iter([["", "", ""], ["", "", ""], ["d", "e", "f"]])), "+---+---+---+" "| d | e | f |" "+---+---+---+" ); test_table!( clean_11, clean(Builder::from_iter([["1", "2", "3"], ["", "", ""], ["", "", ""]])), "+---+---+---+" "| 1 | 2 | 3 |" "+---+---+---+" ); test_table!( clean_12, clean(Builder::from_iter([["", "", ""], ["a", "b", "c"], ["", "", ""]])), "+---+---+---+" "| a | b | c |" "+---+---+---+" ); test_table!( clean_13, clean(Builder::from_iter([["1", "", "3"], ["", "", ""], ["d", "", "f"]])), "+---+---+" "| 1 | 3 |" "+---+---+" "| d | f |" "+---+---+" ); test_table!( clean_with_columns_0, clean_with_head([["1", "2", "3"], ["a", "b", "c"], ["d", "e", "f"]], ["col1", "col2", "col3"]), "+------+------+------+" "| col1 | col2 | col3 |" "+------+------+------+" "| 1 | 2 | 3 |" "+------+------+------+" "| a | b | c |" "+------+------+------+" "| d | e | f |" "+------+------+------+" ); test_table!( clean_with_columns_1, clean_with_head([["", "2", "3"], ["", "b", "c"], ["", "e", "f"]], ["", "col2", "col3"]), "+------+------+" "| col2 | col3 |" "+------+------+" "| 2 | 3 |" "+------+------+" "| b | c |" "+------+------+" "| e | f |" "+------+------+" ); test_table!( clean_with_columns_2, clean_with_head([["1", "", "3"], ["a", "", "c"], ["d", "", "f"]], ["col1", "", "col3"]), "+------+------+" "| col1 | col3 |" "+------+------+" "| 1 | 3 |" "+------+------+" "| a | c |" "+------+------+" "| d | f |" "+------+------+" ); test_table!( clean_with_columns_3, clean_with_head([["1", "2", ""], ["a", "b", ""], ["d", "e", ""]], ["col1", "col2", ""]), "+------+------+" "| col1 | col2 |" "+------+------+" "| 1 | 2 |" "+------+------+" "| a | b |" "+------+------+" "| d | e |" "+------+------+" ); test_table!( clean_with_columns_4, clean_with_head([["", "", "3"], ["", "", "c"], ["", "", "f"]], ["", "", "col3"]), "+------+" "| col3 |" "+------+" "| 3 |" "+------+" "| c |" "+------+" "| f |" "+------+" ); test_table!( clean_with_columns_5, clean_with_head([["1", "", ""], ["a", "", ""], ["d", "", ""]], ["col1", "", ""]), "+------+" "| col1 |" "+------+" "| 1 |" "+------+" "| a |" "+------+" "| d |" "+------+" ); test_table!( clean_with_columns_6, clean_with_head([["", "2", ""], ["", "b", ""], ["", "e", ""]], ["", "col2", ""]), "+------+" "| col2 |" "+------+" "| 2 |" "+------+" "| b |" "+------+" "| e |" "+------+" ); test_table!( clean_with_columns_7, clean_with_head([["", "", ""], ["", "", ""], ["", "", ""]], ["", "", ""]), "" ); test_table!( clean_with_columns_8, clean_with_head([["", "", ""], ["a", "b", "c"], ["d", "e", "f"]], ["col1", "col2", "col3"]), "+------+------+------+" "| col1 | col2 | col3 |" "+------+------+------+" "| a | b | c |" "+------+------+------+" "| d | e | f |" "+------+------+------+" ); test_table!( clean_with_columns_9, clean_with_head([["1", "2", "3"], ["", "", ""], ["d", "e", "f"]], ["col1", "col2", "col3"]), "+------+------+------+" "| col1 | col2 | col3 |" "+------+------+------+" "| 1 | 2 | 3 |" "+------+------+------+" "| d | e | f |" "+------+------+------+" ); test_table!( clean_with_columns_10, clean_with_head([["1", "2", "3"], ["a", "b", "c"], ["", "", ""]], ["col1", "col2", "col3"]), "+------+------+------+" "| col1 | col2 | col3 |" "+------+------+------+" "| 1 | 2 | 3 |" "+------+------+------+" "| a | b | c |" "+------+------+------+" ); test_table!( clean_with_columns_11, clean_with_head([["", "", ""], ["", "", ""], ["d", "e", "f"]], ["col1", "col2", "col3"]), "+------+------+------+" "| col1 | col2 | col3 |" "+------+------+------+" "| d | e | f |" "+------+------+------+" ); test_table!( clean_with_columns_12, clean_with_head([["1", "2", "3"], ["", "", ""], ["", "", ""]], ["col1", "col2", "col3"]), "+------+------+------+" "| col1 | col2 | col3 |" "+------+------+------+" "| 1 | 2 | 3 |" "+------+------+------+" ); test_table!( clean_with_columns_13, clean_with_head([["", "", ""], ["a", "b", "c"], ["", "", ""]], ["col1", "col2", "col3"]), "+------+------+------+" "| col1 | col2 | col3 |" "+------+------+------+" "| a | b | c |" "+------+------+------+" ); test_table!( clean_with_columns_14, clean_with_head([["1", "", "3"], ["", "", ""], ["d", "", "f"]], ["col1", "", "col3"]), "+------+------+" "| col1 | col3 |" "+------+------+" "| 1 | 3 |" "+------+------+" "| d | f |" "+------+------+" ); test_table!(clean_empty_0, clean(Builder::from_iter([[""; 0]; 0])), ""); test_table!(clean_empty_1, clean(Builder::from_iter([[""; 0]; 10])), ""); test_table!( index, Builder::from_iter([["n", "name"], ["0", "Dmitriy"], ["1", "Vladislav"]]).index().build(), "+---+---+-----------+" "| | n | name |" "+---+---+-----------+" "| 0 | 0 | Dmitriy |" "+---+---+-----------+" "| 1 | 1 | Vladislav |" "+---+---+-----------+" ); test_table!( index_set_name, Builder::from_iter([["n", "name"], ["0", "Dmitriy"], ["1", "Vladislav"]]) .index() .name(Some("A index name".into())) .build(), "+--------------+---+-----------+" "| | n | name |" "+--------------+---+-----------+" "| A index name | | |" "+--------------+---+-----------+" "| 0 | 0 | Dmitriy |" "+--------------+---+-----------+" "| 1 | 1 | Vladislav |" "+--------------+---+-----------+" ); test_table!( index_enumeration, Builder::from_iter([["n", "name"], ["0", "Dmitriy"], ["1", "Vladislav"]]) .index() .hide() .build(), "+---+-----------+" "| n | name |" "+---+-----------+" "| 0 | Dmitriy |" "+---+-----------+" "| 1 | Vladislav |" "+---+-----------+" ); test_table!( set_index, Builder::from_iter([["n", "name"], ["0", "Dmitriy"], ["1", "Vladislav"]]) .index() .column(1) .build(), "+-----------+---+" "| | n |" "+-----------+---+" "| name | |" "+-----------+---+" "| Dmitriy | 0 |" "+-----------+---+" "| Vladislav | 1 |" "+-----------+---+" ); test_table!( set_index_and_set_index_name_0, Builder::from_iter([["n", "name"], ["0", "Dmitriy"], ["1", "Vladislav"]]) .index() .column(1) .name(Some("Hello".into())) .build(), "+-----------+---+" "| | n |" "+-----------+---+" "| Hello | |" "+-----------+---+" "| Dmitriy | 0 |" "+-----------+---+" "| Vladislav | 1 |" "+-----------+---+" ); test_table!( set_index_and_set_index_name_1, Builder::from_iter([["n", "name"], ["0", "Dmitriy"], ["1", "Vladislav"]]) .index() .column(1) .name(None) .build(), "+-----------+---+" "| | n |" "+-----------+---+" "| Dmitriy | 0 |" "+-----------+---+" "| Vladislav | 1 |" "+-----------+---+" ); test_table!( index_transpose, Builder::from_iter([["n", "name", "zz"], ["0", "Dmitriy", "123"], ["1", "Vladislav", "123"]]) .index() .transpose() .build(), "+------+---------+-----------+" "| | 0 | 1 |" "+------+---------+-----------+" "| n | 0 | 1 |" "+------+---------+-----------+" "| name | Dmitriy | Vladislav |" "+------+---------+-----------+" "| zz | 123 | 123 |" "+------+---------+-----------+" ); fn clean(mut b: Builder) -> String { b.clean(); b.build().to_string() } fn clean_with_head(data: D, rec: impl IntoIterator) -> String where D: IntoIterator, D::Item: IntoIterator, ::Item: Into, { let mut b = Builder::from_iter(data); b.insert_record(0, rec); b.clean(); b.build().to_string() } tabled-0.18.0/tests/core/compact_table.rs000064400000000000000000000055001046102023000164020ustar 00000000000000#![cfg(feature = "std")] use tabled::{ grid::{ config::CompactConfig, dimension::CompactGridDimension, dimension::Estimate, records::IterRecords, }, tables::CompactTable, }; use crate::matrix::Matrix; use testing_table::test_table; test_table!( compact_new, CompactTable::new(Matrix::new(3, 3).to_vec()).to_string(), "" ); test_table!( compact_with_dimension, { let data = Matrix::with_no_frame(3, 3).to_vec(); let mut dims = CompactGridDimension::default(); dims.estimate(IterRecords::new(&data, 3, None), &CompactConfig::default()); CompactTable::with_dimension(data, dims).columns(3).to_string() }, "+-----+-----+-----+" "| 0-0 | 0-1 | 0-2 |" "|-----+-----+-----|" "| 1-0 | 1-1 | 1-2 |" "|-----+-----+-----|" "| 2-0 | 2-1 | 2-2 |" "+-----+-----+-----+" ); test_table!( compact_width, CompactTable::new(Matrix::with_no_frame(3, 3).to_vec().to_vec()).columns(3).width(5).to_string(), "+-----+-----+-----+" "| 0-0 | 0-1 | 0-2 |" "|-----+-----+-----|" "| 1-0 | 1-1 | 1-2 |" "|-----+-----+-----|" "| 2-0 | 2-1 | 2-2 |" "+-----+-----+-----+" ); test_table!( compact_width_pad_not_included, CompactTable::new(Matrix::with_no_frame(3, 3).to_vec()).columns(3).width(3).to_string(), "+---+---+---+" "| 0-0 | 0-1 | 0-2 |" "|---+---+---|" "| 1-0 | 1-1 | 1-2 |" "|---+---+---|" "| 2-0 | 2-1 | 2-2 |" "+---+---+---+" ); test_table!( compact_width_bigger, CompactTable::new(Matrix::with_no_frame(3, 3).to_vec()).columns(3).width(10).to_string(), "+----------+----------+----------+" "| 0-0 | 0-1 | 0-2 |" "|----------+----------+----------|" "| 1-0 | 1-1 | 1-2 |" "|----------+----------+----------|" "| 2-0 | 2-1 | 2-2 |" "+----------+----------+----------+" ); test_table!( compact_columns, CompactTable::new(Matrix::with_no_frame(3, 3).to_vec()).columns(3).to_string(), "+--+--+--+" "| 0-0 | 0-1 | 0-2 |" "|--+--+--|" "| 1-0 | 1-1 | 1-2 |" "|--+--+--|" "| 2-0 | 2-1 | 2-2 |" "+--+--+--+" ); test_table!( compact_cols_zero, CompactTable::new(Matrix::with_no_frame(3, 3).to_vec()) .columns(0) .to_string(), "" ); test_table!( compact_cols_less, CompactTable::new(Matrix::with_no_frame(3, 3).to_vec()) .columns(1) .to_string(), "+--+" "| 0-0 |" "|--|" "| 1-0 |" "|--|" "| 2-0 |" "+--+" ); test_table!( compact_cols_more, CompactTable::new(Matrix::with_no_frame(3, 3).to_vec()) .columns(5) .to_string(), "+--+--+--+--+--+" "| 0-0 | 0-1 | 0-2 |" "|--+--+--+--+--|" "| 1-0 | 1-1 | 1-2 |" "|--+--+--+--+--|" "| 2-0 | 2-1 | 2-2 |" "+--+--+--+--+--+" ); tabled-0.18.0/tests/core/extended_table_test.rs000064400000000000000000000306461046102023000176240ustar 00000000000000#![cfg(feature = "std")] use tabled::{tables::ExtendedTable, Tabled}; use crate::matrix::Matrix; use testing_table::{static_table, test_table}; #[cfg(feature = "ansi")] use tabled::settings::Color; macro_rules! assert_expanded_display { ( $data:expr, $expected:expr ) => { let table = ExtendedTable::new($data).to_string(); assert_eq!(table, $expected); }; } macro_rules! build_tabled_type { ( $name:ident, $length:expr, $fields:expr, $headers:expr ) => { #[derive(Debug, Clone, Copy)] struct $name; impl Tabled for $name { const LENGTH: usize = $length; fn fields(&self) -> Vec> { $fields.iter().map(|s| s.to_string().into()).collect() } fn headers() -> Vec> { $headers.iter().map(|s| s.to_string().into()).collect() } } }; } test_table!( display, ExtendedTable::from(Matrix::vec(3, 3)), "-[ RECORD 0 ]-" "N | 0" "column 0 | 0-0" "column 1 | 0-1" "column 2 | 0-2" "-[ RECORD 1 ]-" "N | 1" "column 0 | 1-0" "column 1 | 1-1" "column 2 | 1-2" "-[ RECORD 2 ]-" "N | 2" "column 0 | 2-0" "column 1 | 2-1" "column 2 | 2-2" ); #[test] fn display_empty_records() { build_tabled_type!(TestType, 3, ["He", "123", "asd"], ["1", "2", "3"]); let data: Vec = vec![]; assert_expanded_display!(data, ""); } #[test] fn display_empty() { build_tabled_type!( TestType, 3, { let d: Vec = vec![]; d }, { let d: Vec = vec![]; d } ); let data: Vec = vec![]; assert_expanded_display!(data, ""); } #[test] fn display_empty_2() { build_tabled_type!(EmptyType, 0, [""; 0], [""; 0]); assert_expanded_display!(&[EmptyType], "-[ RECORD 0 ]-"); } #[test] fn display_dynamic_header_template() { { build_tabled_type!(TestType, 3, ["He", "123", "asd"], ["1", "2", "3"]); assert_expanded_display!( &[TestType], static_table!( "-[ RECORD 0 ]-" "1 | He" "2 | 123" "3 | asd" ) ); } { build_tabled_type!(TestType, 3, ["He", "123", "asd"], ["11", "2222222", "3"]); assert_expanded_display!( &[TestType], static_table!( "-[ RECORD 0 ]-" "11 | He" "2222222 | 123" "3 | asd" ) ); } { build_tabled_type!( TestType, 3, ["HeheHehe", "123", "asd"], ["11", "2222222", "3"] ); assert_expanded_display!( &[TestType], static_table!( "-[ RECORD 0 ]-----" "11 | HeheHehe" "2222222 | 123" "3 | asd" ) ); } { build_tabled_type!(TestType, 3, ["He", "123", "asd"], ["11111111111", "2", "3"]); assert_expanded_display!( &[TestType], static_table!( "-[ RECORD 0 ]----" "11111111111 | He" "2 | 123" "3 | asd" ) ); } { build_tabled_type!( TestType, 3, ["He", "123", "asd"], ["1111111111111", "2", "3"] ); assert_expanded_display!( &[TestType], static_table!( "-[ RECORD 0 ]-+----" "1111111111111 | He" "2 | 123" "3 | asd" ) ); } { build_tabled_type!( TestType, 3, ["He", "123", "asd"], ["11111111111111111111111111111", "2", "3"] ); assert_expanded_display!( &[TestType], static_table!( "-[ RECORD 0 ]-----------------+----" "11111111111111111111111111111 | He" "2 | 123" "3 | asd" ) ); } { build_tabled_type!(TestType, 3, ["22"], ["11111111111"]); assert_expanded_display!( std::iter::repeat(TestType).take(11), static_table!( "-[ RECORD 0 ]---" "11111111111 | 22" "-[ RECORD 1 ]---" "11111111111 | 22" "-[ RECORD 2 ]---" "11111111111 | 22" "-[ RECORD 3 ]---" "11111111111 | 22" "-[ RECORD 4 ]---" "11111111111 | 22" "-[ RECORD 5 ]---" "11111111111 | 22" "-[ RECORD 6 ]---" "11111111111 | 22" "-[ RECORD 7 ]---" "11111111111 | 22" "-[ RECORD 8 ]---" "11111111111 | 22" "-[ RECORD 9 ]---" "11111111111 | 22" "-[ RECORD 10 ]--" "11111111111 | 22" ) ); } } #[test] fn display_multiline_field() { build_tabled_type!(TestType, 3, ["1", "2", "3"], ["Hello\nWorld", "123", "asd"]); assert_expanded_display!( [TestType], static_table!( "-[ RECORD 0 ]---" "Hello\\nWorld | 1" "123 | 2" "asd | 3" ) ); } #[test] fn display_multiline_record_value() { let mut data = Matrix::list::<2, 3>(); data[0][0] = "Hello\nWorld".to_string(); data[0][1] = "123".to_string(); data[0][2] = "asd".to_string(); assert_expanded_display!( data, static_table!( "-[ RECORD 0 ]----------" "N | Hello\\nWorld" "column 0 | 123" "column 1 | asd" "column 2 | 0-2" "-[ RECORD 1 ]----------" "N | 1" "column 0 | 1-0" "column 1 | 1-1" "column 2 | 1-2" ) ); } test_table!( display_with_truncate, { let data = Matrix::new(3, 3).insert((1, 0).into(), "a long string").to_vec(); let mut table = ExtendedTable::from(data); table.truncate(14, ""); table.to_string() }, "-[ RECORD 0 ]-" "N | a l" "column 0 | 0-0" "column 1 | 0-1" "column 2 | 0-2" "-[ RECORD 1 ]-" "N | 1" "column 0 | 1-0" "column 1 | 1-1" "column 2 | 1-2" "-[ RECORD 2 ]-" "N | 2" "column 0 | 2-0" "column 1 | 2-1" "column 2 | 2-2" ); test_table!( truncate_with_suffix, { let data = Matrix::new(3, 3).insert((1, 0).into(), "a long string").to_vec(); let mut table = ExtendedTable::from(data); table.truncate(15, ".."); table.to_string() }, "-[ RECORD 0 ]-" "N | .." "column 0 | .." "column 1 | .." "column 2 | .." "-[ RECORD 1 ]-" "N | .." "column 0 | .." "column 1 | .." "column 2 | .." "-[ RECORD 2 ]-" "N | .." "column 0 | .." "column 1 | .." "column 2 | .." ); #[test] fn truncate_big_fields() { build_tabled_type!( TestType, 3, ["1", "2", "3"], ["A quite big field", "123", "asd"] ); let data: Vec = vec![TestType, TestType]; let mut table = ExtendedTable::new(&data); table.truncate(14, ".."); let table = table.to_string(); assert_eq!( table, static_table!( "-[ RECORD 0 ]-" "A quite.. | .." "123 | .." "asd | .." "-[ RECORD 1 ]-" "A quite.. | .." "123 | .." "asd | .." ) ); let mut table = ExtendedTable::new(&data); table.truncate(15, ".."); let table = table.to_string(); assert_eq!( table, static_table!( "-[ RECORD 0 ]--" "A quite .. | .." "123 | .." "asd | .." "-[ RECORD 1 ]--" "A quite .. | .." "123 | .." "asd | .." ) ); let mut table = ExtendedTable::new(&data); table.truncate(0, ".."); let table = table.to_string(); assert_eq!( table, static_table!( "-[ RECORD 0 ]-----+--" "A quite big field | 1" "123 | 2" "asd | 3" "-[ RECORD 1 ]-----+--" "A quite big field | 1" "123 | 2" "asd | 3" ) ); let mut table = ExtendedTable::new(&data); table.truncate(20, "......"); let table = table.to_string(); assert_eq!( table, static_table!( "-[ RECORD 0 ]-------" "A qui...... | ......" "123 | ......" "asd | ......" "-[ RECORD 1 ]-------" "A qui...... | ......" "123 | ......" "asd | ......" ) ); } test_table!( truncate_too_small, { let data = Matrix::new(3, 3).insert((1, 0).into(), "a long string").to_vec(); let mut table = ExtendedTable::from(data); let success = table.truncate(2, ""); assert!(!success); table }, "-[ RECORD 0 ]-----------" "N | a long string" "column 0 | 0-0" "column 1 | 0-1" "column 2 | 0-2" "-[ RECORD 1 ]-----------" "N | 1" "column 0 | 1-0" "column 1 | 1-1" "column 2 | 1-2" "-[ RECORD 2 ]-----------" "N | 2" "column 0 | 2-0" "column 1 | 2-1" "column 2 | 2-2" ); #[cfg(feature = "ansi")] #[test] fn display_colored() { let mut data = Matrix::list::<3, 3>(); data[0][2] = String::from("\u{1b}[31;44mhttps://getfedora.org/\u{1b}[0m"); data[1][2] = String::from("\u{1b}[32;40mhttps://www.opensuse.org/\u{1b}[0m"); data[2][2] = String::from("\u{1b}[4m\u{1b}[34mhttps://endeavouros.com/\u{1b}[39m\u{1b}[0m"); assert_expanded_display!( data, static_table!( "-[ RECORD 0 ]------------------------------------------------------------" "N | 0" "column 0 | 0-0" "column 1 | \\u{1b}[31;44mhttps://getfedora.org/\\u{1b}[0m" "column 2 | 0-2" "-[ RECORD 1 ]------------------------------------------------------------" "N | 1" "column 0 | 1-0" "column 1 | \\u{1b}[32;40mhttps://www.opensuse.org/\\u{1b}[0m" "column 2 | 1-2" "-[ RECORD 2 ]------------------------------------------------------------" "N | 2" "column 0 | 2-0" "column 1 | \\u{1b}[4m\\u{1b}[34mhttps://endeavouros.com/\\u{1b}[39m\\u{1b}[0m" "column 2 | 2-2" ) ); } #[cfg(feature = "ansi")] #[test] fn display_with_truncate_colored() { let mut data = Matrix::list::<2, 3>(); data[0][2] = Color::FG_RED.colorize("https://getfedora.org/"); data[1][1] = (Color::FG_WHITE | Color::BG_BLACK).colorize("https://endeavouros.com/"); data[1][2] = "https://www.opensuse.org/".to_string(); let mut table = ExtendedTable::new(&data); table.truncate(20, ""); let table = table.to_string(); assert_eq!( table, static_table!( "-[ RECORD 0 ]-------" "N | 0" "column 0 | 0-0" "column 1 | \\u{1b}[31" "column 2 | 0-2" "-[ RECORD 1 ]-------" "N | 1" "column 0 | \\u{1b}[37" "column 1 | https://w" "column 2 | 1-2" ) ); } #[test] fn record_template() { build_tabled_type!(TestType, 3, ["1", "2", "3"], ["a", "b", "c"]); let data: Vec = vec![TestType, TestType]; let description = "ROW"; let table = ExtendedTable::new(data) .template(move |index| format!("< {} {} >", description, index + 1)); let table = table.to_string(); assert_eq!( table, static_table!( "-< ROW 1 >-" "a | 1" "b | 2" "c | 3" "-< ROW 2 >-" "a | 1" "b | 2" "c | 3" ) ); } tabled-0.18.0/tests/core/index_test.rs000064400000000000000000000200531046102023000157530ustar 00000000000000#![cfg(feature = "std")] use std::iter::FromIterator; use crate::matrix::Matrix; use tabled::{builder::Builder, Table}; use testing_table::test_table; test_table!( builder_index, Table::builder(Matrix::list::<3, 2>()).index().build(), "+---+---+----------+----------+" "| | N | column 0 | column 1 |" "+---+---+----------+----------+" "| 0 | 0 | 0-0 | 0-1 |" "+---+---+----------+----------+" "| 1 | 1 | 1-0 | 1-1 |" "+---+---+----------+----------+" "| 2 | 2 | 2-0 | 2-1 |" "+---+---+----------+----------+" ); test_table!( builder_index_transpose, Table::builder(Matrix::list::<4, 2>()).index().transpose().build(), "+----------+-----+-----+-----+-----+" "| | 0 | 1 | 2 | 3 |" "+----------+-----+-----+-----+-----+" "| N | 0 | 1 | 2 | 3 |" "+----------+-----+-----+-----+-----+" "| column 0 | 0-0 | 1-0 | 2-0 | 3-0 |" "+----------+-----+-----+-----+-----+" "| column 1 | 0-1 | 1-1 | 2-1 | 3-1 |" "+----------+-----+-----+-----+-----+" ); test_table!( builder_index_0, Table::builder(Matrix::list::<4, 2>()).index().column(0).build(), "+---+----------+----------+" "| | column 0 | column 1 |" "+---+----------+----------+" "| N | | |" "+---+----------+----------+" "| 0 | 0-0 | 0-1 |" "+---+----------+----------+" "| 1 | 1-0 | 1-1 |" "+---+----------+----------+" "| 2 | 2-0 | 2-1 |" "+---+----------+----------+" "| 3 | 3-0 | 3-1 |" "+---+----------+----------+" ); test_table!( builder_index_0_no_name, Table::builder(Matrix::list::<4, 2>()).index().column(0).name(None).build(), "+---+----------+----------+" "| | column 0 | column 1 |" "+---+----------+----------+" "| 0 | 0-0 | 0-1 |" "+---+----------+----------+" "| 1 | 1-0 | 1-1 |" "+---+----------+----------+" "| 2 | 2-0 | 2-1 |" "+---+----------+----------+" "| 3 | 3-0 | 3-1 |" "+---+----------+----------+" ); test_table!( builder_index_0_name, Table::builder(Matrix::list::<4, 2>()).index().column(0).name(Some("Hello World".into())).build(), "+-------------+----------+----------+" "| | column 0 | column 1 |" "+-------------+----------+----------+" "| Hello World | | |" "+-------------+----------+----------+" "| 0 | 0-0 | 0-1 |" "+-------------+----------+----------+" "| 1 | 1-0 | 1-1 |" "+-------------+----------+----------+" "| 2 | 2-0 | 2-1 |" "+-------------+----------+----------+" "| 3 | 3-0 | 3-1 |" "+-------------+----------+----------+" ); test_table!( builder_index_0_name_transpose, Table::builder(Matrix::list::<4, 2>()).index().column(0).name(Some("Hello World".into())).transpose().build(), "+-------------+-----+-----+-----+-----+" "| Hello World | 0 | 1 | 2 | 3 |" "+-------------+-----+-----+-----+-----+" "| column 0 | 0-0 | 1-0 | 2-0 | 3-0 |" "+-------------+-----+-----+-----+-----+" "| column 1 | 0-1 | 1-1 | 2-1 | 3-1 |" "+-------------+-----+-----+-----+-----+" ); test_table!( builder_index_with_no_columns, Builder::from_iter([["1", "2", "3"], ["a", "b", "c"], ["d", "e", "f"]]).index().build(), "+---+---+---+---+" "| | 1 | 2 | 3 |" "+---+---+---+---+" "| 0 | a | b | c |" "+---+---+---+---+" "| 1 | d | e | f |" "+---+---+---+---+" ); test_table!( builder_index_with_no_columns_and_name, Builder::from_iter([["1", "2", "3"], ["a", "b", "c"], ["d", "e", "f"]]) .index() .name(Some(String::from("Hello World"))) .build(), "+-------------+---+---+---+" "| | 1 | 2 | 3 |" "+-------------+---+---+---+" "| Hello World | | | |" "+-------------+---+---+---+" "| 0 | a | b | c |" "+-------------+---+---+---+" "| 1 | d | e | f |" "+-------------+---+---+---+" ); test_table!( builder_index_with_no_columns_transpose, Builder::from_iter([["1", "2", "3"], ["a", "b", "c"], ["d", "e", "f"]]) .index() .transpose() .build(), "+---+---+---+" "| | 0 | 1 |" "+---+---+---+" "| 1 | a | d |" "+---+---+---+" "| 2 | b | e |" "+---+---+---+" "| 3 | c | f |" "+---+---+---+" ); test_table!(builder_index_empty, Builder::default().index().build(), ""); test_table!( builder_index_transpose_empty, Builder::default().index().transpose().build(), "" ); test_table!( builder_index_invalid_doesnt_panic, Builder::default().index().column(100).build(), "" ); test_table!( builder_index_name_doesnt_shown_when_empty, Builder::default() .index() .name(Some("Hello World".into())) .build(), "" ); test_table!( builder_index_with_header_but_no_data, { let mut b = Builder::default(); b.push_record(["one", "two", "three"]); b.index().build() }, "+--+-----+-----+-------+" "| | one | two | three |" "+--+-----+-----+-------+" ); #[test] fn builder_index_transpose_transpose() { let data = Matrix::list::<4, 2>(); let builder = Table::builder(data).index(); let orig_table = builder.clone().build().to_string(); let two_times_transposed_table = builder.transpose().transpose().build().to_string(); assert_eq!(orig_table, two_times_transposed_table,); } #[test] fn builder_index_no_name_transpose_transpose() { let data = Matrix::list::<4, 2>(); let builder = Table::builder(data).index().name(None); let orig_table = builder.clone().build().to_string(); let two_times_transposed_table = builder.transpose().transpose().build().to_string(); assert_eq!(orig_table, two_times_transposed_table,); } test_table!( builder_index_convert_back_to_builder, { let mut b = Builder::new(); b.push_record(["one", "two", "three"]); Builder::from(b.clone().index().hide()).build() }, "+-----+-----+-------+" "| one | two | three |" "+-----+-----+-------+" ); test_table!( index_column_3_times, Table::builder(Matrix::list::<3, 2>()).index().column(0).column(0).column(0).build(), "+----------+" "| |" "+----------+" "| column 1 |" "+----------+" "| 0-1 |" "+----------+" "| 1-1 |" "+----------+" "| 2-1 |" "+----------+" ); test_table!( index_transpose_with_no_name0, Builder::from_iter([["col1", "col2", "col3"], ["a", "b", "c"], ["d", "e", "f"]]) .index() .transpose() .name(Some(String::from("hello world"))) .transpose() .build(), "+-------------+------+------+------+" "| | col1 | col2 | col3 |" "+-------------+------+------+------+" "| hello world | | | |" "+-------------+------+------+------+" "| 0 | a | b | c |" "+-------------+------+------+------+" "| 1 | d | e | f |" "+-------------+------+------+------+" ); test_table!( index_transpose_with_no_name1, Builder::from_iter([["col1", "col2", "col3"], ["a", "b", "c"], ["d", "e", "f"]]) .index() .transpose() .name(Some(String::from("hello world"))) .build(), "+-------------+---+---+" "| hello world | 0 | 1 |" "+-------------+---+---+" "| col1 | a | d |" "+-------------+---+---+" "| col2 | b | e |" "+-------------+---+---+" "| col3 | c | f |" "+-------------+---+---+" ); test_table!( index_transpose_with_no_name2, Builder::from_iter([["col1", "col2", "col3"], ["a", "b", "c"], ["d", "e", "f"]]) .index() .transpose() .build(), "+------+---+---+" "| | 0 | 1 |" "+------+---+---+" "| col1 | a | d |" "+------+---+---+" "| col2 | b | e |" "+------+---+---+" "| col3 | c | f |" "+------+---+---+" ); tabled-0.18.0/tests/core/iter_table.rs000064400000000000000000000126471046102023000157310ustar 00000000000000#![cfg(feature = "std")] use tabled::tables::IterTable; use crate::matrix::Matrix; use testing_table::test_table; test_table!( iter_table, IterTable::new(Matrix::with_no_frame(3, 3).to_vec()), "+-----+-----+-----+" "| 0-0 | 0-1 | 0-2 |" "+-----+-----+-----+" "| 1-0 | 1-1 | 1-2 |" "+-----+-----+-----+" "| 2-0 | 2-1 | 2-2 |" "+-----+-----+-----+" ); test_table!( iter_table_cols, IterTable::new(Matrix::with_no_frame(3, 3).to_vec()).columns(3), "+-----+-----+-----+" "| 0-0 | 0-1 | 0-2 |" "+-----+-----+-----+" "| 1-0 | 1-1 | 1-2 |" "+-----+-----+-----+" "| 2-0 | 2-1 | 2-2 |" "+-----+-----+-----+" ); test_table!( iter_table_cols_less, IterTable::new(Matrix::with_no_frame(3, 3).to_vec()).columns(2), "+-----+-----+" "| 0-0 | 0-1 |" "+-----+-----+" "| 1-0 | 1-1 |" "+-----+-----+" "| 2-0 | 2-1 |" "+-----+-----+" ); test_table!( iter_table_cols_zero, IterTable::new(Matrix::with_no_frame(3, 3).to_vec()).columns(0), "" ); test_table!( iter_table_iterator, { let mut buf = String::new(); IterTable::new((0..3).map(|i: usize| (0..5).map(move |j: usize| format!("{i},{j}")))).fmt(&mut buf).unwrap(); buf }, "+-----+-----+-----+-----+-----+" "| 0,0 | 0,1 | 0,2 | 0,3 | 0,4 |" "+-----+-----+-----+-----+-----+" "| 1,0 | 1,1 | 1,2 | 1,3 | 1,4 |" "+-----+-----+-----+-----+-----+" "| 2,0 | 2,1 | 2,2 | 2,3 | 2,4 |" "+-----+-----+-----+-----+-----+" ); test_table!( iter_table_width, IterTable::new(Matrix::with_no_frame(3, 3).to_vec()).width(2), "+----+----+----+" "| 0- | 0- | 0- |" "+----+----+----+" "| 1- | 1- | 1- |" "+----+----+----+" "| 2- | 2- | 2- |" "+----+----+----+" ); test_table!( iter_table_height_does_not_work, IterTable::new(Matrix::with_no_frame(3, 3).to_vec()).height(5), "+-----+-----+-----+" "| 0-0 | 0-1 | 0-2 |" "| | | |" "| | | |" "| | | |" "| | | |" "+-----+-----+-----+" "| 1-0 | 1-1 | 1-2 |" "| | | |" "| | | |" "| | | |" "| | | |" "+-----+-----+-----+" "| 2-0 | 2-1 | 2-2 |" "| | | |" "| | | |" "| | | |" "| | | |" "+-----+-----+-----+" ); test_table!( iter_table_sniff_0, IterTable::new(Matrix::with_no_frame(3, 3).to_vec()).sniff(0), "" ); test_table!( iter_table_multiline, IterTable::new( vec![ vec!["0", "1", "2", "3"], vec!["0\n1\n2\n3\n4", "0\n1\n2\n\n\n3\n4", "0\n1\n2\n3\n4\n\n\n", "0\n1\n2\n\n\n3\n4\n"] ] ), "+---+---+---+---+" "| 0 | 1 | 2 | 3 |" "+---+---+---+---+" "| 0 | 0 | 0 | 0 |" "| 1 | 1 | 1 | 1 |" "| 2 | 2 | 2 | 2 |" "| 3 | | 3 | |" "| 4 | | 4 | |" "| | 3 | | 3 |" "| | 4 | | 4 |" "| | | | |" "+---+---+---+---+" ); test_table!( iter_table_multiline_sniff_1, IterTable::new( vec![ vec!["0", "1", "2", "3"], vec!["0\n1\n2\n3\n4", "0\n1\n2\n\n\n3\n4", "0\n1\n2\n3\n4\n\n\n", "0\n1\n2\n\n\n3\n4\n"] ] ) .sniff(1), "+---+---+---+---+\n| 0 | 1 | 2 | 3 |\n+---+---+---+---+\n| 0\n1\n2\n3\n4 | 0\n1\n2\n\n\n3\n4 | 0\n1\n2\n3\n4\n\n\n | 0\n1\n2\n\n\n3\n4\n |\n+---+---+---+---+" ); test_table!( iter_table_multiline_sniff_2, IterTable::new( vec![ vec!["0", "1", "2", "3"], vec!["0\n1\n2\n3\n4", "0\n1\n2\n\n\n3\n4", "0\n1\n2\n3\n4\n\n\n", "0\n1\n2\n\n\n3\n4\n"], vec!["0\n1\n2\n3\n4", "0\n1\n2\n\n\n3\n4", "0\n1\n2\n3\n4\n\n\n", "0\n1\n2\n\n\n3\n4\n"], ] ) .sniff(2), "+---+---+---+---+\n| 0 | 1 | 2 | 3 |\n+---+---+---+---+\n| 0 | 0 | 0 | 0 |\n| 1 | 1 | 1 | 1 |\n| 2 | 2 | 2 | 2 |\n| 3 | | 3 | |\n| 4 | | 4 | |\n| | 3 | | 3 |\n| | 4 | | 4 |\n| | | | |\n+---+---+---+---+\n| 0\n1\n2\n3\n4 | 0\n1\n2\n\n\n3\n4 | 0\n1\n2\n3\n4\n\n\n | 0\n1\n2\n\n\n3\n4\n |\n+---+---+---+---+" ); test_table!( iter_table_multiline_height_work, IterTable::new( vec![ vec!["0", "1", "2", "3"], vec!["0\n1\n2\n3\n4", "0\n1\n2\n\n\n3\n4", "0\n1\n2\n3\n4\n\n\n", "0\n1\n2\n\n\n3\n4\n"] ] ) .height(3) , "+---+---+---+---+" "| 0 | 1 | 2 | 3 |" "| | | | |" "| | | | |" "+---+---+---+---+" "| 0 | 0 | 0 | 0 |" "| 1 | 1 | 1 | 1 |" "| 2 | 2 | 2 | 2 |" "+---+---+---+---+" ); test_table!( iter_table_sniff_cut, IterTable::new( vec![ vec!["12", "12", "22", "32"], vec!["0", "0", "0", "0"], vec!["023", "123", "223", "323"], ] ) .sniff(2) , "+----+----+----+----+" "| 12 | 12 | 22 | 32 |" "+----+----+----+----+" "| 0 | 0 | 0 | 0 |" "+----+----+----+----+" "| 02 | 12 | 22 | 32 |" "+----+----+----+----+" ); test_table!( iter_table_sniff, IterTable::new( vec![ vec!["023", "123", "223", "323"], vec!["12", "12", "22", "32"], vec!["0", "0", "0", "0"], ] ) .sniff(2) , "+-----+-----+-----+-----+" "| 023 | 123 | 223 | 323 |" "+-----+-----+-----+-----+" "| 12 | 12 | 22 | 32 |" "+-----+-----+-----+-----+" "| 0 | 0 | 0 | 0 |" "+-----+-----+-----+-----+" ); tabled-0.18.0/tests/core/iter_test.rs000064400000000000000000000055731046102023000156210ustar 00000000000000#![cfg(all(feature = "std", feature = "derive"))] use tabled::{ iter::LayoutIterator, settings::{style::BorderSpanCorrection, Span, Style}, Table, Tabled, }; use testing_table::test_table; test_table!( push_record, { #[derive(Tabled)] struct Company<'a> { name: &'a str, street: &'a str, city: &'a str, zip_code: &'a str, } let companies = vec![ Company { name: "INTEL CORP", city: "SANTA CLARA", street: "2200 MISSION COLLEGE BLVD, RNB-4-151", zip_code: "95054" }, Company { name: "Apple Inc.", city: "CUPERTINO", street: "ONE APPLE PARK WAY", zip_code: "95014" }, ]; let mut table = Table::kv(&companies); for row in LayoutIterator::kv_batches::(&table) { table.modify((row, 1), Span::column(-1)); } table.with(Style::modern()); table.with(BorderSpanCorrection); table }, "┌─────────────────────────────────────────────────┐" "│ INTEL CORP │" "├──────────┬──────────────────────────────────────┤" "│ street │ 2200 MISSION COLLEGE BLVD, RNB-4-151 │" "├──────────┼──────────────────────────────────────┤" "│ city │ SANTA CLARA │" "├──────────┼──────────────────────────────────────┤" "│ zip_code │ 95054 │" "├──────────┴──────────────────────────────────────┤" "│ Apple Inc. │" "├──────────┬──────────────────────────────────────┤" "│ street │ ONE APPLE PARK WAY │" "├──────────┼──────────────────────────────────────┤" "│ city │ CUPERTINO │" "├──────────┼──────────────────────────────────────┤" "│ zip_code │ 95014 │" "└──────────┴──────────────────────────────────────┘" ); tabled-0.18.0/tests/core/kv_test.rs000064400000000000000000001116531046102023000152730ustar 00000000000000#![cfg(feature = "std")] use std::iter::FromIterator; use tabled::{ builder::Builder, settings::{ formatting::Charset, Height, Highlight, Modify, Padding, Settings, Shadow, Style, Width, }, Table, }; use crate::matrix::Matrix; use testing_table::test_table; mod default_types { use super::*; test_table!( table_str_vec, Table::kv(vec!["hello", "world"]), "+------+-------+" "| &str | hello |" "+------+-------+" "| &str | world |" "+------+-------+" ); test_table!( table_char_vec, Table::kv(vec!['a', 'b', 'c']), "+------+---+" "| char | a |" "+------+---+" "| char | b |" "+------+---+" "| char | c |" "+------+---+" ); test_table!( table_bool_vec, Table::kv(vec![true, false, true]), "+------+-------+" "| bool | true |" "+------+-------+" "| bool | false |" "+------+-------+" "| bool | true |" "+------+-------+" ); test_table!( table_usize_vec, Table::kv(vec![0usize, 1usize, 2usize]), "+-------+---+" "| usize | 0 |" "+-------+---+" "| usize | 1 |" "+-------+---+" "| usize | 2 |" "+-------+---+" ); test_table!( table_isize_vec, Table::kv(vec![0isize, 1isize, 2isize]), "+-------+---+" "| isize | 0 |" "+-------+---+" "| isize | 1 |" "+-------+---+" "| isize | 2 |" "+-------+---+" ); test_table!( table_u8_vec, Table::kv(vec![0u8, 1u8, 2u8]), "+----+---+" "| u8 | 0 |" "+----+---+" "| u8 | 1 |" "+----+---+" "| u8 | 2 |" "+----+---+" ); test_table!( table_u16_vec, Table::kv(vec![0u16, 1u16, 2u16]), "+-----+---+" "| u16 | 0 |" "+-----+---+" "| u16 | 1 |" "+-----+---+" "| u16 | 2 |" "+-----+---+" ); test_table!( table_u32_vec, Table::kv(vec![0u32, 1u32, 2u32]), "+-----+---+" "| u32 | 0 |" "+-----+---+" "| u32 | 1 |" "+-----+---+" "| u32 | 2 |" "+-----+---+" ); test_table!( table_u64_vec, Table::kv(vec![0u64, 1u64, 2u64]), "+-----+---+" "| u64 | 0 |" "+-----+---+" "| u64 | 1 |" "+-----+---+" "| u64 | 2 |" "+-----+---+" ); test_table!( table_u128_vec, Table::kv(vec![0u128, 1u128, 2u128]), "+------+---+" "| u128 | 0 |" "+------+---+" "| u128 | 1 |" "+------+---+" "| u128 | 2 |" "+------+---+" ); test_table!( table_i8_vec, Table::kv(vec![0i8, 1i8, 2i8]), "+----+---+" "| i8 | 0 |" "+----+---+" "| i8 | 1 |" "+----+---+" "| i8 | 2 |" "+----+---+" ); test_table!( table_i16_vec, Table::kv(vec![0i16, 1, 2]), "+-----+---+" "| i16 | 0 |" "+-----+---+" "| i16 | 1 |" "+-----+---+" "| i16 | 2 |" "+-----+---+" ); test_table!( table_i32_vec, Table::kv(vec![0i32, 1, 2]), "+-----+---+" "| i32 | 0 |" "+-----+---+" "| i32 | 1 |" "+-----+---+" "| i32 | 2 |" "+-----+---+" ); test_table!( table_i64_vec, Table::kv(vec![0i64, 1, 2]), "+-----+---+" "| i64 | 0 |" "+-----+---+" "| i64 | 1 |" "+-----+---+" "| i64 | 2 |" "+-----+---+" ); test_table!( table_i128_vec, Table::kv(vec![0i128, 1, 2]), "+------+---+" "| i128 | 0 |" "+------+---+" "| i128 | 1 |" "+------+---+" "| i128 | 2 |" "+------+---+" ); test_table!( table_array, Table::kv(vec![[0, 1, 2], [3, 4, 5], [6, 7, 8]]), "+---+---+" "| 0 | 0 |" "+---+---+" "| 1 | 1 |" "+---+---+" "| 2 | 2 |" "+---+---+" "| 0 | 3 |" "+---+---+" "| 1 | 4 |" "+---+---+" "| 2 | 5 |" "+---+---+" "| 0 | 6 |" "+---+---+" "| 1 | 7 |" "+---+---+" "| 2 | 8 |" "+---+---+" ); } test_table!( table_tuple, Table::kv(vec![("we are in", 2020)]), "+------+-----------+" "| &str | we are in |" "+------+-----------+" "| i32 | 2020 |" "+------+-----------+" ); test_table!( table_single_tuple, Table::kv(vec![(2020,)]), "+-----+------+" "| i32 | 2020 |" "+-----+------+" ); test_table!( table_tuple_vec, #[allow(unknown_lints)] #[allow(clippy::needless_borrow)] #[allow(clippy::needless_borrows_for_generic_args)] Table::kv(&[(0, "Monday"), (1, "Thursday")]), "+------+----------+" "| i32 | 0 |" "+------+----------+" "| &str | Monday |" "+------+----------+" "| i32 | 1 |" "+------+----------+" "| &str | Thursday |" "+------+----------+" ); test_table!( build_table_from_iterator, Matrix::new(3, 3).with(Style::psql()), " N | column 0 | column 1 | column 2 " "---+----------+----------+----------" " 0 | 0-0 | 0-1 | 0-2 " " 1 | 1-0 | 1-1 | 1-2 " " 2 | 2-0 | 2-1 | 2-2 " ); test_table!( multiline_table_test_0, Table::kv([["This is the 0.19 release of Nushell. If you'd like to read more about it, please check out: https://www.nushell.sh/blog/2020/09/01/nushell_0_19.html\r\n\r\nFor convenience, we are providing full builds for Windows, Linux, and macOS. These are the \"all extra features\" builds, so be sure you have the requirements to enable all capabilities: https://github.com/nushell/book/blob/master/en/installation.md#dependencies\r\n"]]) .with(Charset::clean()) .with(Style::modern()), r#"┌───┬──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐"# r#"│ 0 │ This is the 0.19 release of Nushell. If you'd like to read more about it, please check out: https://www.nushell.sh/blog/2020/09/01/nushell_0_19.html │"# r#"│ │ │"# r#"│ │ For convenience, we are providing full builds for Windows, Linux, and macOS. These are the "all extra features" builds, so be sure you have the requirements to enable all capabilities: https://github.com/nushell/book/blob/master/en/installation.md#dependencies │"# r#"│ │ │"# r#"└───┴──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘"# ); test_table!( multiline_table_test_1, Table::kv([["This is the 0.19 release of Nushell. If you'd like to read more about it, please check out: https://www.nushell.sh/blog/2020/09/01/nushell_0_19.html\r\n\r\nFor convenience, we are providing full builds for Windows, Linux, and macOS. These are the \"all extra features\" builds, so be sure you have the requirements to enable all capabilities: https://github.com/nushell/book/blob/master/en/installation.md#dependencies\r\n"]]) .with(Charset::clean()) .with(Style::modern()) .with(Width::wrap(100)), "┌──┬───────────────────────────────────────────────────────────────────────────────────────────────┐" "│ │ This is the 0.19 release of Nushell. If you'd like to read more about it, please check out: h │" "│ │ ttps://www.nushell.sh/blog/2020/09/01/nushell_0_19.html │" "│ │ │" "│ │ For convenience, we are providing full builds for Windows, Linux, and macOS. These are the \"a │" "│ │ ll extra features\" builds, so be sure you have the requirements to enable all capabilities: h │" "│ │ ttps://github.com/nushell/book/blob/master/en/installation.md#dependencies │" "│ │ │" "└──┴───────────────────────────────────────────────────────────────────────────────────────────────┘" ); test_table!( multiline_table_test_2, Table::kv([["This is the 0.19 release of Nushell. If you'd like to read more about it, please check out: https://www.nushell.sh/blog/2020/09/01/nushell_0_19.html\r\n\r\nFor convenience, we are providing full builds for Windows, Linux, and macOS. These are the \"all extra features\" builds, so be sure you have the requirements to enable all capabilities: https://github.com/nushell/book/blob/master/en/installation.md#dependencies\r\n"]]) .with(Modify::new((0, 1)).with(Charset::clean())) .with(Style::modern()) .with(Width::wrap(100)), r#"┌──┬───────────────────────────────────────────────────────────────────────────────────────────────┐"# r#"│ │ This is the 0.19 release of Nushell. If you'd like to read more about it, please check out: h │"# r#"│ │ ttps://www.nushell.sh/blog/2020/09/01/nushell_0_19.html │"# r#"│ │ │"# r#"│ │ For convenience, we are providing full builds for Windows, Linux, and macOS. These are the "a │"# r#"│ │ ll extra features" builds, so be sure you have the requirements to enable all capabilities: h │"# r#"│ │ ttps://github.com/nushell/book/blob/master/en/installation.md#dependencies │"# r#"│ │ │"# r#"└──┴───────────────────────────────────────────────────────────────────────────────────────────────┘"# ); #[cfg(feature = "derive")] mod derived { use super::*; use std::collections::{BTreeMap, BTreeSet}; use tabled::{settings::style::Style, Tabled}; #[derive(Tabled)] struct TestType { f1: u8, f2: &'static str, } test_table!( table_vector_structures, Table::kv([TestType { f1: 0, f2: "0" }, TestType { f1: 1, f2: "1" }]), "+----+---+" "| f1 | 0 |" "+----+---+" "| f2 | 0 |" "+----+---+" "| f1 | 1 |" "+----+---+" "| f2 | 1 |" "+----+---+" ); test_table!( table_empty_vector_structures, Table::kv({ let v: Vec = Vec::new(); v }), "" ); test_table!( table_option, Table::kv(Some(TestType { f1: 0, f2: "0" })), "+----+---+" "| f1 | 0 |" "+----+---+" "| f2 | 0 |" "+----+---+" ); test_table!(table_option_none, Table::kv(Option::::None), "",); test_table!( table_tuple_with_structure_vec, Table::kv([(0, TestType { f1: 0, f2: "0str" }), (1, TestType { f1: 1, f2: "1str" })]), "+-----+------+" "| i32 | 0 |" "+-----+------+" "| f1 | 0 |" "+-----+------+" "| f2 | 0str |" "+-----+------+" "| i32 | 1 |" "+-----+------+" "| f1 | 1 |" "+-----+------+" "| f2 | 1str |" "+-----+------+" ); test_table!( table_vector_structures_with_hidden_tabled, Table::kv({ #[derive(Tabled)] struct St { #[allow(dead_code)] #[tabled(skip)] f1: u8, f2: &'static str, } vec![St { f1: 0, f2: "0" }, St { f1: 1, f2: "1" }] }), "+----+---+" "| f2 | 0 |" "+----+---+" "| f2 | 1 |" "+----+---+" ); test_table!( table_enum, Table::kv({ #[derive(Tabled)] enum Letters { Vowels { character: char, lang: u8 }, Consonant(char), Digit, } vec![ Letters::Vowels { character: 'a', lang: 0, }, Letters::Consonant('w'), Letters::Vowels { character: 'b', lang: 1, }, Letters::Vowels { character: 'c', lang: 2, }, Letters::Digit, ] }), "+-----------+---+" "| Vowels | + |" "+-----------+---+" "| Consonant | |" "+-----------+---+" "| Digit | |" "+-----------+---+" "| Vowels | |" "+-----------+---+" "| Consonant | + |" "+-----------+---+" "| Digit | |" "+-----------+---+" "| Vowels | + |" "+-----------+---+" "| Consonant | |" "+-----------+---+" "| Digit | |" "+-----------+---+" "| Vowels | + |" "+-----------+---+" "| Consonant | |" "+-----------+---+" "| Digit | |" "+-----------+---+" "| Vowels | |" "+-----------+---+" "| Consonant | |" "+-----------+---+" "| Digit | + |" "+-----------+---+" ); test_table!( table_enum_with_hidden_variant, Table::kv({ #[allow(dead_code)] #[derive(Tabled)] enum Letters { Vowels { character: char, lang: u8, }, Consonant(char), #[tabled(skip)] Digit, } vec![ Letters::Vowels { character: 'a', lang: 0, }, Letters::Consonant('w'), Letters::Vowels { character: 'b', lang: 1, }, Letters::Vowels { character: 'c', lang: 2, }, Letters::Digit, ] }), "+-----------+---+" "| Vowels | + |" "+-----------+---+" "| Consonant | |" "+-----------+---+" "| Vowels | |" "+-----------+---+" "| Consonant | + |" "+-----------+---+" "| Vowels | + |" "+-----------+---+" "| Consonant | |" "+-----------+---+" "| Vowels | + |" "+-----------+---+" "| Consonant | |" "+-----------+---+" "| Vowels | |" "+-----------+---+" "| Consonant | |" "+-----------+---+" ); test_table!( table_btreemap, Table::kv({ #[derive(Tabled)] struct A { b: u8, c: &'static str, } let mut map = BTreeMap::new(); map.insert(0, A { b: 1, c: "s1" }); map.insert(1, A { b: 2, c: "s2" }); map.insert(3, A { b: 3, c: "s3" }); map }), "+-----+----+" "| i32 | 0 |" "+-----+----+" "| b | 1 |" "+-----+----+" "| c | s1 |" "+-----+----+" "| i32 | 1 |" "+-----+----+" "| b | 2 |" "+-----+----+" "| c | s2 |" "+-----+----+" "| i32 | 3 |" "+-----+----+" "| b | 3 |" "+-----+----+" "| c | s3 |" "+-----+----+" ); test_table!( table_emojie_utf8_style, Table::kv({ #[derive(Tabled)] struct Language { name: &'static str, designed_by: &'static str, invented_year: usize, } vec![ Language { name: "C 💕", designed_by: "Dennis Ritchie", invented_year: 1972, }, Language { name: "Rust 👍", designed_by: "Graydon Hoare", invented_year: 2010, }, Language { name: "Go 🧋", designed_by: "Rob Pike", invented_year: 2009, }, ] }).with(Style::modern().remove_horizontal()), // Note: It doesn't look good in VS Code "┌───────────────┬────────────────┐" "│ name │ C 💕 │" "│ designed_by │ Dennis Ritchie │" "│ invented_year │ 1972 │" "│ name │ Rust 👍 │" "│ designed_by │ Graydon Hoare │" "│ invented_year │ 2010 │" "│ name │ Go 🧋 │" "│ designed_by │ Rob Pike │" "│ invented_year │ 2009 │" "└───────────────┴────────────────┘" ); test_table!( table_btreeset, Table::kv({ #[derive(Tabled, PartialEq, Eq, PartialOrd, Ord)] struct A { b: u8, c: &'static str, } let mut map = BTreeSet::new(); map.insert(A { b: 1, c: "s1" }); map.insert(A { b: 2, c: "s2" }); map.insert(A { b: 3, c: "s3" }); map }), "+---+----+" "| b | 1 |" "+---+----+" "| c | s1 |" "+---+----+" "| b | 2 |" "+---+----+" "| c | s2 |" "+---+----+" "| b | 3 |" "+---+----+" "| c | s3 |" "+---+----+" ); test_table!( table_emojie, Table::kv({ #[derive(Tabled)] struct Language { name: &'static str, designed_by: &'static str, invented_year: usize, } vec![ Language { name: "C 💕", designed_by: "Dennis Ritchie", invented_year: 1972, }, Language { name: "Rust 👍", designed_by: "Graydon Hoare", invented_year: 2010, }, Language { name: "Go 🧋", designed_by: "Rob Pike", invented_year: 2009, }, ] }), "+---------------+----------------+" "| name | C 💕 |" "+---------------+----------------+" "| designed_by | Dennis Ritchie |" "+---------------+----------------+" "| invented_year | 1972 |" "+---------------+----------------+" "| name | Rust 👍 |" "+---------------+----------------+" "| designed_by | Graydon Hoare |" "+---------------+----------------+" "| invented_year | 2010 |" "+---------------+----------------+" "| name | Go 🧋 |" "+---------------+----------------+" "| designed_by | Rob Pike |" "+---------------+----------------+" "| invented_year | 2009 |" "+---------------+----------------+" ); test_table!( tuple_combination, Table::kv({ #[derive(Tabled)] enum Domain { Security, Embedded, Frontend, Unknown, } #[derive(Tabled)] struct Developer(#[tabled(rename = "name")] &'static str); vec![ (Developer("Terri Kshlerin"), Domain::Embedded), (Developer("Catalina Dicki"), Domain::Security), (Developer("Jennie Schmeler"), Domain::Frontend), (Developer("Maxim Zhiburt"), Domain::Unknown), ] }), "+----------+-----------------+" "| name | Terri Kshlerin |" "+----------+-----------------+" "| Security | |" "+----------+-----------------+" "| Embedded | + |" "+----------+-----------------+" "| Frontend | |" "+----------+-----------------+" "| Unknown | |" "+----------+-----------------+" "| name | Catalina Dicki |" "+----------+-----------------+" "| Security | + |" "+----------+-----------------+" "| Embedded | |" "+----------+-----------------+" "| Frontend | |" "+----------+-----------------+" "| Unknown | |" "+----------+-----------------+" "| name | Jennie Schmeler |" "+----------+-----------------+" "| Security | |" "+----------+-----------------+" "| Embedded | |" "+----------+-----------------+" "| Frontend | + |" "+----------+-----------------+" "| Unknown | |" "+----------+-----------------+" "| name | Maxim Zhiburt |" "+----------+-----------------+" "| Security | |" "+----------+-----------------+" "| Embedded | |" "+----------+-----------------+" "| Frontend | |" "+----------+-----------------+" "| Unknown | + |" "+----------+-----------------+" ); test_table!( table_trait, Table::kv({ #[derive(Tabled)] #[tabled(inline)] enum Domain { Security, Embedded, Frontend, Unknown, } #[derive(Tabled)] struct Developer(#[tabled(rename = "name")] &'static str); vec![ (Developer("Terri Kshlerin"), Domain::Embedded), (Developer("Catalina Dicki"), Domain::Security), (Developer("Jennie Schmeler"), Domain::Frontend), (Developer("Maxim Zhiburt"), Domain::Unknown), ] }), "+--------+-----------------+" "| name | Terri Kshlerin |" "+--------+-----------------+" "| Domain | Embedded |" "+--------+-----------------+" "| name | Catalina Dicki |" "+--------+-----------------+" "| Domain | Security |" "+--------+-----------------+" "| name | Jennie Schmeler |" "+--------+-----------------+" "| Domain | Frontend |" "+--------+-----------------+" "| name | Maxim Zhiburt |" "+--------+-----------------+" "| Domain | Unknown |" "+--------+-----------------+" ); test_table!( table_emojie_multiline, Table::kv({ #[derive(Tabled)] struct Article { name: &'static str, author: &'static str, text: &'static str, rating: usize, } vec![ Article { name: "Rebase vs Merge commit in depth 👋", author: "Rose Kuphal DVM", text: "A multiline\n text with 🤯 😳 🥵 🥶\n a bunch of emojies ☄️ 💥 🔥 🌪", rating: 43, }, Article { name: "Keep it simple", author: "Unknown", text: "🍳", rating: 100, }, ] }), "+--------+------------------------------------+" "| name | Rebase vs Merge commit in depth 👋 |" "+--------+------------------------------------+" "| author | Rose Kuphal DVM |" "+--------+------------------------------------+" "| text | A multiline |" "| | text with 🤯 😳 🥵 🥶 |" "| | a bunch of emojies ☄️ 💥 🔥 🌪 |" "+--------+------------------------------------+" "| rating | 43 |" "+--------+------------------------------------+" "| name | Keep it simple |" "+--------+------------------------------------+" "| author | Unknown |" "+--------+------------------------------------+" "| text | 🍳 |" "+--------+------------------------------------+" "| rating | 100 |" "+--------+------------------------------------+" ); } #[cfg(feature = "ansi")] #[test] fn multiline_table_test2() { use testing_table::assert_table; let data = &[ ["\u{1b}[37mThis is the 0.19 release of Nushell. If you'd like to read more about it, please check out: https://www.nushell.sh/blog/2020/09/01/nushell_0_19.html\n\nFor convenience, we are providing full builds for Windows, Linux, and macOS. These are the \"all extra features\" builds, so be sure you have the requirements to enable all capabilities: https://github.com/nushell/book/blob/master/en/installation.md#dependencies\n\u{1b}[0m"], ]; let mut table = Table::kv(data); table.with(Style::modern()); assert_table!( ansi_str::AnsiStr::ansi_strip(&table.to_string()), r#"┌───┬──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐"# r#"│ 0 │ This is the 0.19 release of Nushell. If you'd like to read more about it, please check out: https://www.nushell.sh/blog/2020/09/01/nushell_0_19.html │"# r#"│ │ │"# r#"│ │ For convenience, we are providing full builds for Windows, Linux, and macOS. These are the "all extra features" builds, so be sure you have the requirements to enable all capabilities: https://github.com/nushell/book/blob/master/en/installation.md#dependencies │"# r#"│ │ │"# r#"└───┴──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘"# ); table.with(Width::wrap(100)); assert_table!( ansi_str::AnsiStr::ansi_strip(&table.to_string()), r#"┌──┬───────────────────────────────────────────────────────────────────────────────────────────────┐"# r#"│ │ This is the 0.19 release of Nushell. If you'd like to read more about it, please check out: h │"# r#"│ │ ttps://www.nushell.sh/blog/2020/09/01/nushell_0_19.html │"# r#"│ │ │"# r#"│ │ For convenience, we are providing full builds for Windows, Linux, and macOS. These are the "a │"# r#"│ │ ll extra features" builds, so be sure you have the requirements to enable all capabilities: h │"# r#"│ │ ttps://github.com/nushell/book/blob/master/en/installation.md#dependencies │"# r#"│ │ │"# r#"└──┴───────────────────────────────────────────────────────────────────────────────────────────────┘"# ); } test_table!( table_1x1_empty, { Builder::from_iter(vec![vec![""]]).build() .with(Style::modern()) .with(Settings::new(Height::limit(0), Width::increase(10))) }, "┌────────┐" "└────────┘" ); test_table!( table_2x2_empty, { Builder::from_iter(vec![vec![" ", ""], vec![" ", ""]]).build() .with(Style::modern()) .with(Padding::zero()) .with(Height::list([1, 0])) }, "┌─┬┐" "│ ││" "├─┼┤" "└─┴┘" ); test_table!( table_2x2_empty_height_list_together_with_width_list_dont_work_0, { Builder::from_iter(vec![vec!["", ""], vec!["", ""]]).build() .with(Style::modern()) .with(Padding::zero()) .with(Height::list([1, 0])) .with(Width::list([1, 0])) }, "┌─┬┐" "│ ││" "├─┼┤" "│ ││" "└─┴┘" ); test_table!( table_2x2_empty_height_list_together_with_width_list_dont_work_1, { Builder::from_iter(vec![vec!["", ""], vec!["", ""]]).build() .with(Style::modern()) .with(Padding::zero()) .with(Width::list([1, 0])) .with(Height::list([1, 0])) }, "┌┬┐" "│││" "├┼┤" "└┴┘" ); test_table!( table_modify_test, Matrix::new(3, 3) .with(Style::markdown()) .modify((1, 0), "Hello World") .modify((0, 1), "Hello World 2"), "| N | Hello World 2 | column 1 | column 2 |" "|-------------|---------------|----------|----------|" "| Hello World | 0-0 | 0-1 | 0-2 |" "| 1 | 1-0 | 1-1 | 1-2 |" "| 2 | 2-0 | 2-1 | 2-2 |" ); test_table!( table_tuple_settings_list_0_test, Matrix::new(3, 3) .with(Style::markdown()) .modify((1, 0), ("Hello World", "Hello World 2")), "| N | column 0 | column 1 | column 2 |" "|---------------|----------|----------|----------|" "| Hello World 2 | 0-0 | 0-1 | 0-2 |" "| 1 | 1-0 | 1-1 | 1-2 |" "| 2 | 2-0 | 2-1 | 2-2 |" ); test_table!( table_tuple_settings_list_1_test, Matrix::new(3, 3) .with(Style::markdown()) .modify((1, 0), ("Hello World", Padding::new(2, 2, 1, 1), "1")), "| N | column 0 | column 1 | column 2 |" "|-----|----------|----------|----------|" "| | 0-0 | 0-1 | 0-2 |" "| 1 | | | |" "| | | | |" "| 1 | 1-0 | 1-1 | 1-2 |" "| 2 | 2-0 | 2-1 | 2-2 |" ); test_table!( table_tuple_settings_list_2_test, Matrix::new(3, 3) .with(Style::markdown()) .with((Padding::new(2, 2, 0, 0), Highlight::outline((0, 0), '*'), Shadow::new(5))), "******* " "* N * column 0 | column 1 | column 2 |▒▒▒▒▒" "*******------------|------------|------------|▒▒▒▒▒" "| 0 | 0-0 | 0-1 | 0-2 |▒▒▒▒▒" "| 1 | 1-0 | 1-1 | 1-2 |▒▒▒▒▒" "| 2 | 2-0 | 2-1 | 2-2 |▒▒▒▒▒" " ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒" " ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒" " ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒" " ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒" " ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒" ); tabled-0.18.0/tests/core/mod.rs000064400000000000000000000002321046102023000143610ustar 00000000000000mod builder_test; mod compact_table; mod extended_table_test; mod index_test; mod iter_table; mod iter_test; mod kv_test; mod pool_table; mod table_test; tabled-0.18.0/tests/core/pool_table.rs000064400000000000000000000611161046102023000157320ustar 00000000000000#![cfg(feature = "std")] use tabled::{ grid::dimension::{DimensionPriority, PoolTableDimension}, settings::{formatting::AlignmentStrategy, Alignment, Margin, Padding, Style}, tables::{PoolTable, TableValue}, }; use crate::matrix::Matrix; use testing_table::test_table; #[cfg(feature = "ansi")] use tabled::{grid::ansi::ANSIStr, settings::PaddingColor}; test_table!( pool_table, PoolTable::new(Matrix::with_no_frame(3, 3).to_vec()), "+-----+-----+-----+" "| 0-0 | 0-1 | 0-2 |" "+-----+-----+-----+" "| 1-0 | 1-1 | 1-2 |" "+-----+-----+-----+" "| 2-0 | 2-1 | 2-2 |" "+-----+-----+-----+" ); test_table!( pool_table_1, PoolTable::new([vec!["111111", "222"], vec!["111", "2233", "1", "2", "3"]]), "+-------------+----------+" "| 111111 | 222 |" "+-----+------++--+---+---+" "| 111 | 2233 | 1 | 2 | 3 |" "+-----+------+---+---+---+" ); test_table!( pool_table_2, PoolTable::new([vec!["111", "2233", "1", "2", "3"], vec!["111111", "222"]]), "+-----+------+---+---+---+" "| 111 | 2233 | 1 | 2 | 3 |" "+-----+------++--+---+---+" "| 111111 | 222 |" "+-------------+----------+" ); test_table!( pool_table_3, PoolTable::new([vec!["1\n11", "2\n2\n3\n3", "1", "\n2\n", "3"], vec!["11\n111\n1", "2\n2\n2"]]), "+----+---+---+---+---+" "| 1 | 2 | 1 | | 3 |" "| 11 | 2 | | 2 | |" "| | 3 | | | |" "| | 3 | | | |" "+----+---+--++---+---+" "| 11 | 2 |" "| 111 | 2 |" "| 1 | 2 |" "+-----------+--------+" ); test_table!( pool_table_4, PoolTable::new([vec!["11\n111\n1", "2\n2\n2"], vec!["1\n11", "2\n2\n3\n3", "1", "\n2\n", "3"]]), "+-----------+--------+" "| 11 | 2 |" "| 111 | 2 |" "| 1 | 2 |" "+----+---+--++---+---+" "| 1 | 2 | 1 | | 3 |" "| 11 | 2 | | 2 | |" "| | 3 | | | |" "| | 3 | | | |" "+----+---+---+---+---+" ); test_table!( pool_table_multiline, PoolTable::new([ ["1", "2\n2", "3\n3\n3"], ["3\n3\n3", "2\n2", "1"], ["1", "3\n3\n3", "2\n2"] ]), "+---+---+---+" "| 1 | 2 | 3 |" "| | 2 | 3 |" "| | | 3 |" "+---+---+---+" "| 3 | 2 | 1 |" "| 3 | 2 | |" "| 3 | | |" "+---+---+---+" "| 1 | 3 | 2 |" "| | 3 | 2 |" "| | 3 | |" "+---+---+---+" ); test_table!( pool_table_value, PoolTable::from(TableValue::Row(vec![ TableValue::Column(vec![TableValue::Cell(String::from("0-0")), TableValue::Cell(String::from("0-1")), TableValue::Cell(String::from("0-2"))]), TableValue::Column(vec![TableValue::Cell(String::from("1-0")), TableValue::Cell(String::from("1-1")), TableValue::Cell(String::from("1-2"))]), TableValue::Column(vec![TableValue::Cell(String::from("2-0")), TableValue::Cell(String::from("2-1")), TableValue::Cell(String::from("2-2"))]), ])) .with(Style::modern()), "┌─────┬─────┬─────┐" "│ 0-0 │ 1-0 │ 2-0 │" "├─────┼─────┼─────┤" "│ 0-1 │ 1-1 │ 2-1 │" "├─────┼─────┼─────┤" "│ 0-2 │ 1-2 │ 2-2 │" "└─────┴─────┴─────┘" ); test_table!( pool_table_value_1, PoolTable::from(TableValue::Row(vec![ TableValue::Column(vec![TableValue::Cell(String::from("0-0")), TableValue::Cell(String::from("0-1")), TableValue::Cell(String::from("0-2"))]), TableValue::Column(vec![TableValue::Cell(String::from("1-0")), TableValue::Cell(String::from("1-1")), TableValue::Cell(String::from("1-2"))]), TableValue::Column(vec![ TableValue::Column(vec![TableValue::Cell(String::from("2-01")), TableValue::Cell(String::from("2-02")), TableValue::Cell(String::from("2-03"))]), TableValue::Cell(String::from("2-1")), TableValue::Cell(String::from("2-2")), ]), ])) .with(Style::modern()), "┌─────┬─────┬──────┐" "│ 0-0 │ 1-0 │ 2-01 │" "│ │ ├──────┤" "│ │ │ 2-02 │" "├─────┼─────┼──────┤" "│ 0-1 │ 1-1 │ 2-03 │" "│ │ ├──────┤" "├─────┼─────┤ 2-1 │" "│ 0-2 │ 1-2 ├──────┤" "│ │ │ 2-2 │" "└─────┴─────┴──────┘" ); test_table!( pool_table_value_2, PoolTable::from(TableValue::Row(vec![ TableValue::Column(vec![TableValue::Cell(String::from("0-0")), TableValue::Cell(String::from("0-1")), TableValue::Cell(String::from("0-2"))]), TableValue::Column(vec![ TableValue::Row(vec![TableValue::Cell(String::from("2-01")), TableValue::Cell(String::from("2-02")), TableValue::Cell(String::from("2-03"))]), TableValue::Column(vec![TableValue::Cell(String::from("2-01")), TableValue::Cell(String::from("2-02")), TableValue::Cell(String::from("2-03"))]), TableValue::Cell(String::from("1-1")), TableValue::Cell(String::from("1-2")) ]), TableValue::Column(vec![ TableValue::Column(vec![TableValue::Cell(String::from("2-01")), TableValue::Cell(String::from("2-02")), TableValue::Cell(String::from("2-03"))]), TableValue::Cell(String::from("2-1")), TableValue::Cell(String::from("2-2")) ]), ])) .with(Style::modern()), "┌─────┬──────┬──────┬──────┬──────┐" "│ 0-0 │ 2-01 │ 2-02 │ 2-03 │ 2-01 │" "│ ├──────┴──────┴──────┤ │" "│ │ 2-01 ├──────┤" "├─────┼────────────────────┤ 2-02 │" "│ 0-1 │ 2-02 ├──────┤" "│ ├────────────────────┤ 2-03 │" "│ │ 2-03 ├──────┤" "├─────┼────────────────────┤ 2-1 │" "│ 0-2 │ 1-1 │ │" "│ ├────────────────────┼──────┤" "│ │ 1-2 │ 2-2 │" "└─────┴────────────────────┴──────┘" ); test_table!( pool_table_value_3, PoolTable::from(TableValue::Row(vec![ TableValue::Column(vec![TableValue::Cell(String::from("0-0")), TableValue::Cell(String::from("0-1")), TableValue::Cell(String::from("0-2"))]), TableValue::Column(vec![ TableValue::Row(vec![TableValue::Cell(String::from("2-01")), TableValue::Cell(String::from("2-02")), TableValue::Cell(String::from("2-03"))]), TableValue::Column(vec![TableValue::Cell(String::from("2-01")), TableValue::Cell(String::from("2-02")), TableValue::Cell(String::from("2-03"))]), TableValue::Cell(String::from("1-1")), TableValue::Row(vec![TableValue::Cell(String::from("2-01")), TableValue::Cell(String::from("2-02")), TableValue::Cell(String::from("2-03"))]), TableValue::Cell(String::from("1-2")) ]), TableValue::Column(vec![ TableValue::Column(vec![TableValue::Cell(String::from("2-\n0\n1")), TableValue::Cell(String::from("2\n-\n0\n2")), TableValue::Cell(String::from("2-03"))]), TableValue::Cell(String::from("2-1")), TableValue::Column(vec![TableValue::Cell(String::from("2-0\n1")), TableValue::Cell(String::from("2-02")), TableValue::Cell(String::from("2-03"))]), TableValue::Cell(String::from("2-2")) ]), ])) .with(Style::modern()), "┌─────┬──────┬──────┬──────┬──────┐" "│ 0-0 │ 2-01 │ 2-02 │ 2-03 │ 2- │" "│ │ │ │ │ 0 │" "│ │ │ │ │ 1 │" "│ ├──────┴──────┴──────┼──────┤" "│ │ 2-01 │ 2 │" "│ │ │ - │" "│ ├────────────────────┤ 0 │" "├─────┤ 2-02 │ 2 │" "│ 0-1 │ ├──────┤" "│ ├────────────────────┤ 2-03 │" "│ │ 2-03 ├──────┤" "│ ├────────────────────┤ 2-1 │" "│ │ 1-1 ├──────┤" "│ │ │ 2-0 │" "├─────┤ │ 1 │" "│ 0-2 ├──────┬──────┬──────┼──────┤" "│ │ 2-01 │ 2-02 │ 2-03 │ 2-02 │" "│ │ │ │ ├──────┤" "│ ├──────┴──────┴──────┤ 2-03 │" "│ │ 1-2 ├──────┤" "│ │ │ 2-2 │" "└─────┴────────────────────┴──────┘" ); test_table!( pool_table_example, { let data = vec![ vec!["Hello World", "Hello World", "Hello World"], vec!["Hello", "", "Hello"], vec!["W", "o", "r", "l", "d"], ]; let data = TableValue::Column( data.into_iter() .map(|row| { TableValue::Row( row.into_iter() .map(|text| TableValue::Cell(text.to_owned())) .collect(), ) }) .collect(), ); PoolTable::from(data) .with(Style::modern()) .with(Alignment::center()) .to_string() }, "┌─────────────┬─────────────┬─────────────┐" "│ Hello World │ Hello World │ Hello World │" "├─────────────┴─┬──────────┬┴─────────────┤" "│ Hello │ │ Hello │" "├────────┬──────┴─┬───────┬┴──────┬───────┤" "│ W │ o │ r │ l │ d │" "└────────┴────────┴───────┴───────┴───────┘" ); test_table!( pool_table_value_empty_row, PoolTable::from(TableValue::Row(vec![])) .with(Style::modern()), "┌──┐" "│ │" "└──┘" ); test_table!( pool_table_value_empty_column, PoolTable::from(TableValue::Column(vec![])) .with(Style::modern()), "┌──┐" "│ │" "└──┘" ); test_table!( pool_table_value_empty_cell, PoolTable::from(TableValue::Cell(String::from(""))) .with(Style::modern()), "┌──┐" "│ │" "└──┘" ); test_table!( pool_table_padding, PoolTable::new(Matrix::with_no_frame(3, 3).to_vec()).with(Padding::new(1, 2, 3, 4)), "+------+------+------+" "| | | |" "| | | |" "| | | |" "| 0-0 | 0-1 | 0-2 |" "| | | |" "| | | |" "| | | |" "| | | |" "+------+------+------+" "| | | |" "| | | |" "| | | |" "| 1-0 | 1-1 | 1-2 |" "| | | |" "| | | |" "| | | |" "| | | |" "+------+------+------+" "| | | |" "| | | |" "| | | |" "| 2-0 | 2-1 | 2-2 |" "| | | |" "| | | |" "| | | |" "| | | |" "+------+------+------+" ); #[cfg(feature = "ansi")] test_table!( pool_table_padding_2, PoolTable::new(Matrix::with_no_frame(3, 3).to_vec()) .with(Padding::new(1, 2, 3, 4).fill('!', '@', '#', '$')) .with(PaddingColor::new( ANSIStr::new("\u{1b}[34m", "\u{1b}[39m"), ANSIStr::new("\u{1b}[34m", "\u{1b}[39m"), ANSIStr::new("\u{1b}[34m", "\u{1b}[39m"), ANSIStr::new("\u{1b}[34m", "\u{1b}[39m"), )), "+------+------+------+" "|\u{1b}[34m######\u{1b}[39m|\u{1b}[34m######\u{1b}[39m|\u{1b}[34m######\u{1b}[39m|" "|\u{1b}[34m######\u{1b}[39m|\u{1b}[34m######\u{1b}[39m|\u{1b}[34m######\u{1b}[39m|" "|\u{1b}[34m######\u{1b}[39m|\u{1b}[34m######\u{1b}[39m|\u{1b}[34m######\u{1b}[39m|" "|\u{1b}[34m!\u{1b}[39m0-0\u{1b}[34m@@\u{1b}[39m|\u{1b}[34m!\u{1b}[39m0-1\u{1b}[34m@@\u{1b}[39m|\u{1b}[34m!\u{1b}[39m0-2\u{1b}[34m@@\u{1b}[39m|" "|\u{1b}[34m$$$$$$\u{1b}[39m|\u{1b}[34m$$$$$$\u{1b}[39m|\u{1b}[34m$$$$$$\u{1b}[39m|" "|\u{1b}[34m$$$$$$\u{1b}[39m|\u{1b}[34m$$$$$$\u{1b}[39m|\u{1b}[34m$$$$$$\u{1b}[39m|" "|\u{1b}[34m$$$$$$\u{1b}[39m|\u{1b}[34m$$$$$$\u{1b}[39m|\u{1b}[34m$$$$$$\u{1b}[39m|" "|\u{1b}[34m$$$$$$\u{1b}[39m|\u{1b}[34m$$$$$$\u{1b}[39m|\u{1b}[34m$$$$$$\u{1b}[39m|" "+------+------+------+" "|\u{1b}[34m######\u{1b}[39m|\u{1b}[34m######\u{1b}[39m|\u{1b}[34m######\u{1b}[39m|" "|\u{1b}[34m######\u{1b}[39m|\u{1b}[34m######\u{1b}[39m|\u{1b}[34m######\u{1b}[39m|" "|\u{1b}[34m######\u{1b}[39m|\u{1b}[34m######\u{1b}[39m|\u{1b}[34m######\u{1b}[39m|" "|\u{1b}[34m!\u{1b}[39m1-0\u{1b}[34m@@\u{1b}[39m|\u{1b}[34m!\u{1b}[39m1-1\u{1b}[34m@@\u{1b}[39m|\u{1b}[34m!\u{1b}[39m1-2\u{1b}[34m@@\u{1b}[39m|" "|\u{1b}[34m$$$$$$\u{1b}[39m|\u{1b}[34m$$$$$$\u{1b}[39m|\u{1b}[34m$$$$$$\u{1b}[39m|" "|\u{1b}[34m$$$$$$\u{1b}[39m|\u{1b}[34m$$$$$$\u{1b}[39m|\u{1b}[34m$$$$$$\u{1b}[39m|" "|\u{1b}[34m$$$$$$\u{1b}[39m|\u{1b}[34m$$$$$$\u{1b}[39m|\u{1b}[34m$$$$$$\u{1b}[39m|" "|\u{1b}[34m$$$$$$\u{1b}[39m|\u{1b}[34m$$$$$$\u{1b}[39m|\u{1b}[34m$$$$$$\u{1b}[39m|" "+------+------+------+" "|\u{1b}[34m######\u{1b}[39m|\u{1b}[34m######\u{1b}[39m|\u{1b}[34m######\u{1b}[39m|" "|\u{1b}[34m######\u{1b}[39m|\u{1b}[34m######\u{1b}[39m|\u{1b}[34m######\u{1b}[39m|" "|\u{1b}[34m######\u{1b}[39m|\u{1b}[34m######\u{1b}[39m|\u{1b}[34m######\u{1b}[39m|" "|\u{1b}[34m!\u{1b}[39m2-0\u{1b}[34m@@\u{1b}[39m|\u{1b}[34m!\u{1b}[39m2-1\u{1b}[34m@@\u{1b}[39m|\u{1b}[34m!\u{1b}[39m2-2\u{1b}[34m@@\u{1b}[39m|" "|\u{1b}[34m$$$$$$\u{1b}[39m|\u{1b}[34m$$$$$$\u{1b}[39m|\u{1b}[34m$$$$$$\u{1b}[39m|" "|\u{1b}[34m$$$$$$\u{1b}[39m|\u{1b}[34m$$$$$$\u{1b}[39m|\u{1b}[34m$$$$$$\u{1b}[39m|" "|\u{1b}[34m$$$$$$\u{1b}[39m|\u{1b}[34m$$$$$$\u{1b}[39m|\u{1b}[34m$$$$$$\u{1b}[39m|" "|\u{1b}[34m$$$$$$\u{1b}[39m|\u{1b}[34m$$$$$$\u{1b}[39m|\u{1b}[34m$$$$$$\u{1b}[39m|" "+------+------+------+" ); test_table!( pool_table_margin, PoolTable::new(Matrix::with_no_frame(3, 3).to_vec()).with(Margin::new(1, 2, 3, 4).fill('!', '@', '#', '$')), "!###################@@" "!###################@@" "!###################@@" "!+-----+-----+-----+@@" "!| 0-0 | 0-1 | 0-2 |@@" "!+-----+-----+-----+@@" "!| 1-0 | 1-1 | 1-2 |@@" "!+-----+-----+-----+@@" "!| 2-0 | 2-1 | 2-2 |@@" "!+-----+-----+-----+@@" "!$$$$$$$$$$$$$$$$$$$@@" "!$$$$$$$$$$$$$$$$$$$@@" "!$$$$$$$$$$$$$$$$$$$@@" "!$$$$$$$$$$$$$$$$$$$@@" ); test_table!( pool_table_alignment_bottom, PoolTable::new([ ["1", "2\n2", "3\n3\n3"], ["3\n3\n3", "2\n2", "1"], ["1", "3\n3\n3", "2\n2"] ]) .with(Alignment::bottom()), "+---+---+---+" "| | | 3 |" "| | 2 | 3 |" "| 1 | 2 | 3 |" "+---+---+---+" "| 3 | | |" "| 3 | 2 | |" "| 3 | 2 | 1 |" "+---+---+---+" "| | 3 | |" "| | 3 | 2 |" "| 1 | 3 | 2 |" "+---+---+---+" ); test_table!( pool_table_alignment_center_vertical, PoolTable::new([ ["1", "2\n2", "3\n3\n3"], ["3\n3\n3", "2\n2", "1"], ["1", "3\n3\n3", "2\n2"] ]) .with(Alignment::center_vertical()), "+---+---+---+" "| | 2 | 3 |" "| 1 | 2 | 3 |" "| | | 3 |" "+---+---+---+" "| 3 | 2 | |" "| 3 | 2 | 1 |" "| 3 | | |" "+---+---+---+" "| | 3 | 2 |" "| 1 | 3 | 2 |" "| | 3 | |" "+---+---+---+" ); test_table!( pool_table_alignment_right, PoolTable::new([ ["1 ", "2\n2 ", "3\n3\n3"], ["3\n3\n3", "2\n2", "1"], ["1", "3\n3\n3 ", "2\n2"] ]) .with(Alignment::right()), "+------------------------+----------+---+" "| 1 | 2 | 3 |" "| | 2 | 3 |" "| | | 3 |" "+-------------+----------+-+--------+---+" "| 3 | 2 | 1 |" "| 3 | 2 | |" "| 3 | | |" "+------+------+------------+-----+------+" "| 1 | 3 | 2 |" "| | 3 | 2 |" "| | 3 | |" "+------+-------------------------+------+" ); test_table!( pool_table_alignment_right_per_line, PoolTable::new([ ["1 ", "2\n2 ", "3\n3\n3"], ["3\n3\n3", "2\n2", "1"], ["1", "3\n3\n3 ", "2\n2"] ]) .with(Alignment::right()) .with(AlignmentStrategy::PerLine), "+------------------------+----------+---+" "| 1 | 2 | 3 |" "| | 2 | 3 |" "| | | 3 |" "+-------------+----------+-+--------+---+" "| 3 | 2 | 1 |" "| 3 | 2 | |" "| 3 | | |" "+------+------+------------+-----+------+" "| 1 | 3 | 2 |" "| | 3 | 2 |" "| | 3 | |" "+------+-------------------------+------+" ); test_table!( pool_table_alignment_center_horizontal, PoolTable::new([ ["1 ", "2\n2 ", "3\n3\n3"], ["3\n3\n3", "2\n2", "1"], ["1", "3\n3\n3 ", "2\n2"] ]) .with(Alignment::center()), "+------------------------+----------+---+" "| 1 | 2 | 3 |" "| | 2 | 3 |" "| | | 3 |" "+-------------+----------+-+--------+---+" "| 3 | 2 | 1 |" "| 3 | 2 | |" "| 3 | | |" "+------+------+------------+-----+------+" "| 1 | 3 | 2 |" "| | 3 | 2 |" "| | 3 | |" "+------+-------------------------+------+" ); test_table!( pool_table_alignment_center_horizontal_line_strategy, PoolTable::new([ ["1 ", "2\n22222", "3\n3\n3"], ["3\n3\n3", "2\n2", "1"], ["1", "3\n3\n3 ", "2\n2"] ]) .with(Alignment::center()) .with(AlignmentStrategy::PerLine), "+------------------------+-------+---+" "| 1 | 2 | 3 |" "| | 22222 | 3 |" "| | | 3 |" "+------------+-----------+-------+---+" "| 3 | 2 | 1 |" "| 3 | 2 | |" "| 3 | | |" "+-----+------+-----------+-----+-----+" "| 1 | 3 | 2 |" "| | 3 | 2 |" "| | 3 | |" "+-----+------------------------+-----+" ); test_table!( pool_table_style_empty, PoolTable::new(Matrix::with_no_frame(3, 3).to_vec()).with(Style::empty()), " 0-0 0-1 0-2 " " 1-0 1-1 1-2 " " 2-0 2-1 2-2 " ); test_table!( pool_table_style_markdown, PoolTable::new(Matrix::with_no_frame(3, 3).to_vec()).with(Style::markdown()), "| 0-0 | 0-1 | 0-2 |" "| 1-0 | 1-1 | 1-2 |" "| 2-0 | 2-1 | 2-2 |" ); test_table!( pool_table_style_rounded, PoolTable::new(Matrix::with_no_frame(3, 3).to_vec()).with(Style::rounded()), "╭─────┬─────┬─────╮" "│ 0-0 │ 0-1 │ 0-2 │" " ───── ───── ───── " "│ 1-0 │ 1-1 │ 1-2 │" " ───── ───── ───── " "│ 2-0 │ 2-1 │ 2-2 │" "╰─────┴─────┴─────╯" ); test_table!( pool_table_dim_ctrl_0, PoolTable::from(TableValue::Row(vec![ TableValue::Column(vec![TableValue::Cell(String::from("0-0")), TableValue::Cell(String::from("0-1")), TableValue::Cell(String::from("0-2"))]), TableValue::Column(vec![TableValue::Cell(String::from("1-0")), TableValue::Cell(String::from("1-1")), TableValue::Cell(String::from("1-2"))]), TableValue::Column(vec![ TableValue::Column(vec![TableValue::Cell(String::from("2-01")), TableValue::Cell(String::from("2-02")), TableValue::Cell(String::from("2-03"))]), TableValue::Cell(String::from("2-1")), TableValue::Cell(String::from("2-2")), ]), ])) .with(PoolTableDimension::new(DimensionPriority::Last, DimensionPriority::Last)), "+-----+-----+------+" "| 0-0 | 1-0 | 2-01 |" "+-----+-----+------+" "| 0-1 | 1-1 | 2-02 |" "+-----+-----+------+" "| 0-2 | 1-2 | 2-03 |" "| | +------+" "| | | 2-1 |" "| | +------+" "| | | 2-2 |" "+-----+-----+------+" ); test_table!( pool_table_dim_ctrl_1, PoolTable::new([ ["1 ", "2\n2 ", "3\n3\n3"], ["3\n3\n3", "2\n2", "1"], ["1", "3\n3\n3 ", "2\n2"] ]) .with(PoolTableDimension::new(DimensionPriority::List, DimensionPriority::List)), "+------------------------+----------+---+" "| 1 | 2 | 3 |" "| | 2 | 3 |" "| | | 3 |" "+-------------+----------+-+--------+---+" "| 3 | 2 | 1 |" "| 3 | 2 | |" "| 3 | | |" "+------+------+------------+-----+------+" "| 1 | 3 | 2 |" "| | 3 | 2 |" "| | 3 | |" "+------+-------------------------+------+" ); test_table!( pool_table_2_columns_1_cell, PoolTable::from(TableValue::Row(vec![ TableValue::Column(vec![ TableValue::Cell(String::from("0-0")), TableValue::Cell(String::from("0-1")), TableValue::Cell(String::from("0-2")), TableValue::Cell(String::from("0-3")), TableValue::Cell(String::from("0-4")), TableValue::Cell(String::from("0-5")), ]), TableValue::Column(vec![ TableValue::Cell(String::from("1-0")), TableValue::Cell(String::from("1-1")), TableValue::Cell(String::from("1-2")), TableValue::Cell(String::from("1-3")), TableValue::Cell(String::from("1-4")), TableValue::Cell(String::from("1-6")), TableValue::Cell(String::from("1-7")), TableValue::Cell(String::from("1-8")), TableValue::Cell(String::from("1-9")), ]), TableValue::Cell(String::from("2-0")), ])) .with(PoolTableDimension::new(DimensionPriority::Last, DimensionPriority::Last)), "+-----+-----+-----+" "| 0-0 | 1-0 | 2-0 |" "+-----+-----+ |" "| 0-1 | 1-1 | |" "+-----+-----+ |" "| 0-2 | 1-2 | |" "+-----+-----+ |" "| 0-3 | 1-3 | |" "+-----+-----+ |" "| 0-4 | 1-4 | |" "+-----+-----+ |" "| 0-5 | 1-6 | |" "| +-----+ |" "| | 1-7 | |" "| +-----+ |" "| | 1-8 | |" "| +-----+ |" "| | 1-9 | |" "+-----+-----+-----+" ); tabled-0.18.0/tests/core/table_test.rs000064400000000000000000001136031046102023000157370ustar 00000000000000#![cfg(feature = "std")] use std::iter::FromIterator; use tabled::{ builder::Builder, settings::{ formatting::Charset, Height, Highlight, Modify, Padding, Settings, Shadow, Style, Width, }, Table, }; use crate::matrix::Matrix; use testing_table::test_table; mod default_types { use super::*; test_table!( table_str_vec, Table::new(vec!["hello", "world"]), "+-------+" "| &str |" "+-------+" "| hello |" "+-------+" "| world |" "+-------+" ); test_table!( table_char_vec, Table::new(vec!['a', 'b', 'c']), "+------+" "| char |" "+------+" "| a |" "+------+" "| b |" "+------+" "| c |" "+------+" ); test_table!( table_bool_vec, Table::new(vec![true, false, true]), "+-------+" "| bool |" "+-------+" "| true |" "+-------+" "| false |" "+-------+" "| true |" "+-------+" ); test_table!( table_usize_vec, Table::new(vec![0usize, 1usize, 2usize]), "+-------+" "| usize |" "+-------+" "| 0 |" "+-------+" "| 1 |" "+-------+" "| 2 |" "+-------+" ); test_table!( table_isize_vec, Table::new(vec![0isize, 1isize, 2isize]), "+-------+" "| isize |" "+-------+" "| 0 |" "+-------+" "| 1 |" "+-------+" "| 2 |" "+-------+" ); test_table!( table_u8_vec, Table::new(vec![0u8, 1u8, 2u8]), "+----+" "| u8 |" "+----+" "| 0 |" "+----+" "| 1 |" "+----+" "| 2 |" "+----+" ); test_table!( table_u16_vec, Table::new(vec![0u16, 1u16, 2u16]), "+-----+" "| u16 |" "+-----+" "| 0 |" "+-----+" "| 1 |" "+-----+" "| 2 |" "+-----+" ); test_table!( table_u32_vec, Table::new(vec![0u32, 1u32, 2u32]), "+-----+" "| u32 |" "+-----+" "| 0 |" "+-----+" "| 1 |" "+-----+" "| 2 |" "+-----+" ); test_table!( table_u64_vec, Table::new(vec![0u64, 1u64, 2u64]), "+-----+" "| u64 |" "+-----+" "| 0 |" "+-----+" "| 1 |" "+-----+" "| 2 |" "+-----+" ); test_table!( table_u128_vec, Table::new(vec![0u128, 1u128, 2u128]), "+------+" "| u128 |" "+------+" "| 0 |" "+------+" "| 1 |" "+------+" "| 2 |" "+------+" ); test_table!( table_i8_vec, Table::new(vec![0i8, 1i8, 2i8]), "+----+" "| i8 |" "+----+" "| 0 |" "+----+" "| 1 |" "+----+" "| 2 |" "+----+" ); test_table!( table_i16_vec, Table::new(vec![0i16, 1, 2]), "+-----+" "| i16 |" "+-----+" "| 0 |" "+-----+" "| 1 |" "+-----+" "| 2 |" "+-----+" ); test_table!( table_i32_vec, Table::new(vec![0i32, 1, 2]), "+-----+" "| i32 |" "+-----+" "| 0 |" "+-----+" "| 1 |" "+-----+" "| 2 |" "+-----+" ); test_table!( table_i64_vec, Table::new(vec![0i64, 1, 2]), "+-----+" "| i64 |" "+-----+" "| 0 |" "+-----+" "| 1 |" "+-----+" "| 2 |" "+-----+" ); test_table!( table_i128_vec, Table::new(vec![0i128, 1, 2]), "+------+" "| i128 |" "+------+" "| 0 |" "+------+" "| 1 |" "+------+" "| 2 |" "+------+" ); test_table!( table_array, Table::new(vec![[0, 1, 2], [3, 4, 5], [6, 7, 8]]), "+---+---+---+" "| 0 | 1 | 2 |" "+---+---+---+" "| 0 | 1 | 2 |" "+---+---+---+" "| 3 | 4 | 5 |" "+---+---+---+" "| 6 | 7 | 8 |" "+---+---+---+" ); } test_table!( table_tuple, Table::new(vec![("we are in", 2020)]), "+-----------+------+" "| &str | i32 |" "+-----------+------+" "| we are in | 2020 |" "+-----------+------+" ); test_table!( table_single_tuple, Table::new(vec![(2020,)]), "+------+" "| i32 |" "+------+" "| 2020 |" "+------+" ); test_table!( table_tuple_vec, #[allow(unknown_lints)] #[allow(clippy::needless_borrow)] #[allow(clippy::needless_borrows_for_generic_args)] Table::new(&[(0, "Monday"), (1, "Thursday")]), "+-----+----------+" "| i32 | &str |" "+-----+----------+" "| 0 | Monday |" "+-----+----------+" "| 1 | Thursday |" "+-----+----------+" ); test_table!( build_table_from_iterator, Matrix::new(3, 3).with(Style::psql()), " N | column 0 | column 1 | column 2 " "---+----------+----------+----------" " 0 | 0-0 | 0-1 | 0-2 " " 1 | 1-0 | 1-1 | 1-2 " " 2 | 2-0 | 2-1 | 2-2 " ); test_table!( multiline_table_test_0, Table::new([["This is the 0.19 release of Nushell. If you'd like to read more about it, please check out: https://www.nushell.sh/blog/2020/09/01/nushell_0_19.html\r\n\r\nFor convenience, we are providing full builds for Windows, Linux, and macOS. These are the \"all extra features\" builds, so be sure you have the requirements to enable all capabilities: https://github.com/nushell/book/blob/master/en/installation.md#dependencies\r\n"]]) .with(Charset::clean()) .with(Style::modern()), r#"┌──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐"# r#"│ 0 │"# r#"├──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┤"# r#"│ This is the 0.19 release of Nushell. If you'd like to read more about it, please check out: https://www.nushell.sh/blog/2020/09/01/nushell_0_19.html │"# r#"│ │"# r#"│ For convenience, we are providing full builds for Windows, Linux, and macOS. These are the "all extra features" builds, so be sure you have the requirements to enable all capabilities: https://github.com/nushell/book/blob/master/en/installation.md#dependencies │"# r#"│ │"# r#"└──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘"# ); test_table!( multiline_table_test_1, Table::new([["This is the 0.19 release of Nushell. If you'd like to read more about it, please check out: https://www.nushell.sh/blog/2020/09/01/nushell_0_19.html\r\n\r\nFor convenience, we are providing full builds for Windows, Linux, and macOS. These are the \"all extra features\" builds, so be sure you have the requirements to enable all capabilities: https://github.com/nushell/book/blob/master/en/installation.md#dependencies\r\n"]]) .with(Charset::clean()) .with(Style::modern()) .with(Width::wrap(100)), "┌──────────────────────────────────────────────────────────────────────────────────────────────────┐" "│ 0 │" "├──────────────────────────────────────────────────────────────────────────────────────────────────┤" "│ This is the 0.19 release of Nushell. If you'd like to read more about it, please check out: http │" "│ s://www.nushell.sh/blog/2020/09/01/nushell_0_19.html │" "│ │" "│ For convenience, we are providing full builds for Windows, Linux, and macOS. These are the \"all │" "│ extra features\" builds, so be sure you have the requirements to enable all capabilities: https:/ │" "│ /github.com/nushell/book/blob/master/en/installation.md#dependencies │" "│ │" "└──────────────────────────────────────────────────────────────────────────────────────────────────┘" ); test_table!( multiline_table_test_2, Table::new([["This is the 0.19 release of Nushell. If you'd like to read more about it, please check out: https://www.nushell.sh/blog/2020/09/01/nushell_0_19.html\r\n\r\nFor convenience, we are providing full builds for Windows, Linux, and macOS. These are the \"all extra features\" builds, so be sure you have the requirements to enable all capabilities: https://github.com/nushell/book/blob/master/en/installation.md#dependencies\r\n"]]) .with(Modify::new((1, 0)).with(Charset::clean())) .with(Style::modern()) .with(Width::wrap(100)), "┌──────────────────────────────────────────────────────────────────────────────────────────────────┐" "│ 0 │" "├──────────────────────────────────────────────────────────────────────────────────────────────────┤" "│ This is the 0.19 release of Nushell. If you'd like to read more about it, please check out: http │" "│ s://www.nushell.sh/blog/2020/09/01/nushell_0_19.html │" "│ │" "│ For convenience, we are providing full builds for Windows, Linux, and macOS. These are the \"all │" "│ extra features\" builds, so be sure you have the requirements to enable all capabilities: https:/ │" "│ /github.com/nushell/book/blob/master/en/installation.md#dependencies │" "│ │" "└──────────────────────────────────────────────────────────────────────────────────────────────────┘" ); #[cfg(feature = "derive")] mod derived { use super::*; use std::collections::{BTreeMap, BTreeSet}; use tabled::{settings::style::Style, Tabled}; #[derive(Tabled)] struct TestType { f1: u8, f2: &'static str, } test_table!( table_vector_structures, Table::new([TestType { f1: 0, f2: "0" }, TestType { f1: 1, f2: "1" }]), "+----+----+" "| f1 | f2 |" "+----+----+" "| 0 | 0 |" "+----+----+" "| 1 | 1 |" "+----+----+" ); test_table!( table_empty_vector_structures, Table::new({let v: Vec = Vec::new(); v}), "+----+----+" "| f1 | f2 |" "+----+----+" ); test_table!( table_option, Table::new(Some(TestType { f1: 0, f2: "0" })), "+----+----+" "| f1 | f2 |" "+----+----+" "| 0 | 0 |" "+----+----+" ); test_table!( table_option_none, Table::new(Option::::None), "+----+----+" "| f1 | f2 |" "+----+----+" ); test_table!( table_tuple_with_structure_vec, Table::new([(0, TestType { f1: 0, f2: "0str" }), (1, TestType { f1: 1, f2: "1str" })]), "+-----+----+------+" "| i32 | f1 | f2 |" "+-----+----+------+" "| 0 | 0 | 0str |" "+-----+----+------+" "| 1 | 1 | 1str |" "+-----+----+------+" ); test_table!( table_vector_structures_with_hidden_tabled, Table::new({ #[derive(Tabled)] struct St { #[allow(dead_code)] #[tabled(skip)] f1: u8, f2: &'static str, } vec![St { f1: 0, f2: "0" }, St { f1: 1, f2: "1" }] }), "+----+" "| f2 |" "+----+" "| 0 |" "+----+" "| 1 |" "+----+" ); test_table!( table_enum, Table::new({ #[derive(Tabled)] enum Letters { Vowels { character: char, lang: u8 }, Consonant(char), Digit, } vec![ Letters::Vowels { character: 'a', lang: 0, }, Letters::Consonant('w'), Letters::Vowels { character: 'b', lang: 1, }, Letters::Vowels { character: 'c', lang: 2, }, Letters::Digit, ] }), "+--------+-----------+-------+" "| Vowels | Consonant | Digit |" "+--------+-----------+-------+" "| + | | |" "+--------+-----------+-------+" "| | + | |" "+--------+-----------+-------+" "| + | | |" "+--------+-----------+-------+" "| + | | |" "+--------+-----------+-------+" "| | | + |" "+--------+-----------+-------+" ); test_table!( table_enum_with_hidden_variant, Table::new({ #[allow(dead_code)] #[derive(Tabled)] enum Letters { Vowels { character: char, lang: u8, }, Consonant(char), #[tabled(skip)] Digit, } vec![ Letters::Vowels { character: 'a', lang: 0, }, Letters::Consonant('w'), Letters::Vowels { character: 'b', lang: 1, }, Letters::Vowels { character: 'c', lang: 2, }, Letters::Digit, ] }), "+--------+-----------+" "| Vowels | Consonant |" "+--------+-----------+" "| + | |" "+--------+-----------+" "| | + |" "+--------+-----------+" "| + | |" "+--------+-----------+" "| + | |" "+--------+-----------+" "| | |" "+--------+-----------+" ); test_table!( table_btreemap, Table::new({ #[derive(Tabled)] struct A { b: u8, c: &'static str, } let mut map = BTreeMap::new(); map.insert(0, A { b: 1, c: "s1" }); map.insert(1, A { b: 2, c: "s2" }); map.insert(3, A { b: 3, c: "s3" }); map }), "+-----+---+----+" "| i32 | b | c |" "+-----+---+----+" "| 0 | 1 | s1 |" "+-----+---+----+" "| 1 | 2 | s2 |" "+-----+---+----+" "| 3 | 3 | s3 |" "+-----+---+----+" ); test_table!( table_emojie_utf8_style, Table::new({ #[derive(Tabled)] struct Language { name: &'static str, designed_by: &'static str, invented_year: usize, } vec![ Language { name: "C 💕", designed_by: "Dennis Ritchie", invented_year: 1972, }, Language { name: "Rust 👍", designed_by: "Graydon Hoare", invented_year: 2010, }, Language { name: "Go 🧋", designed_by: "Rob Pike", invented_year: 2009, }, ] }).with(Style::modern().remove_horizontal()), // Note: It doesn't look good in VS Code "┌─────────┬────────────────┬───────────────┐" "│ name │ designed_by │ invented_year │" "│ C 💕 │ Dennis Ritchie │ 1972 │" "│ Rust 👍 │ Graydon Hoare │ 2010 │" "│ Go 🧋 │ Rob Pike │ 2009 │" "└─────────┴────────────────┴───────────────┘" ); test_table!( table_btreeset, Table::new({ #[derive(Tabled, PartialEq, Eq, PartialOrd, Ord)] struct A { b: u8, c: &'static str, } let mut map = BTreeSet::new(); map.insert(A { b: 1, c: "s1" }); map.insert(A { b: 2, c: "s2" }); map.insert(A { b: 3, c: "s3" }); map }), "+---+----+" "| b | c |" "+---+----+" "| 1 | s1 |" "+---+----+" "| 2 | s2 |" "+---+----+" "| 3 | s3 |" "+---+----+" ); test_table!( table_emojie, Table::new({ #[derive(Tabled)] struct Language { name: &'static str, designed_by: &'static str, invented_year: usize, } vec![ Language { name: "C 💕", designed_by: "Dennis Ritchie", invented_year: 1972, }, Language { name: "Rust 👍", designed_by: "Graydon Hoare", invented_year: 2010, }, Language { name: "Go 🧋", designed_by: "Rob Pike", invented_year: 2009, }, ] }), "+---------+----------------+---------------+" "| name | designed_by | invented_year |" "+---------+----------------+---------------+" "| C 💕 | Dennis Ritchie | 1972 |" "+---------+----------------+---------------+" "| Rust 👍 | Graydon Hoare | 2010 |" "+---------+----------------+---------------+" "| Go 🧋 | Rob Pike | 2009 |" "+---------+----------------+---------------+" ); test_table!( tuple_combination, Table::new({ #[derive(Tabled)] enum Domain { Security, Embedded, Frontend, Unknown, } #[derive(Tabled)] struct Developer(#[tabled(rename = "name")] &'static str); vec![ (Developer("Terri Kshlerin"), Domain::Embedded), (Developer("Catalina Dicki"), Domain::Security), (Developer("Jennie Schmeler"), Domain::Frontend), (Developer("Maxim Zhiburt"), Domain::Unknown), ] }), "+-----------------+----------+----------+----------+---------+" "| name | Security | Embedded | Frontend | Unknown |" "+-----------------+----------+----------+----------+---------+" "| Terri Kshlerin | | + | | |" "+-----------------+----------+----------+----------+---------+" "| Catalina Dicki | + | | | |" "+-----------------+----------+----------+----------+---------+" "| Jennie Schmeler | | | + | |" "+-----------------+----------+----------+----------+---------+" "| Maxim Zhiburt | | | | + |" "+-----------------+----------+----------+----------+---------+" ); test_table!( table_trait, Table::new({ #[derive(Tabled)] enum Domain { Security, Embedded, Frontend, Unknown, } #[derive(Tabled)] struct Developer(#[tabled(rename = "name")] &'static str); vec![ (Developer("Terri Kshlerin"), Domain::Embedded), (Developer("Catalina Dicki"), Domain::Security), (Developer("Jennie Schmeler"), Domain::Frontend), (Developer("Maxim Zhiburt"), Domain::Unknown), ] }), "+-----------------+----------+----------+----------+---------+" "| name | Security | Embedded | Frontend | Unknown |" "+-----------------+----------+----------+----------+---------+" "| Terri Kshlerin | | + | | |" "+-----------------+----------+----------+----------+---------+" "| Catalina Dicki | + | | | |" "+-----------------+----------+----------+----------+---------+" "| Jennie Schmeler | | | + | |" "+-----------------+----------+----------+----------+---------+" "| Maxim Zhiburt | | | | + |" "+-----------------+----------+----------+----------+---------+" ); test_table!( table_emojie_multiline, Table::new({ #[derive(Tabled)] struct Article { name: &'static str, author: &'static str, text: &'static str, rating: usize, } vec![ Article { name: "Rebase vs Merge commit in depth 👋", author: "Rose Kuphal DVM", text: "A multiline\n text with 🤯 😳 🥵 🥶\n a bunch of emojies ☄️ 💥 🔥 🌪", rating: 43, }, Article { name: "Keep it simple", author: "Unknown", text: "🍳", rating: 100, }, ] }), "+------------------------------------+-----------------+--------------------------------+--------+" "| name | author | text | rating |" "+------------------------------------+-----------------+--------------------------------+--------+" "| Rebase vs Merge commit in depth 👋 | Rose Kuphal DVM | A multiline | 43 |" "| | | text with 🤯 😳 🥵 🥶 | |" "| | | a bunch of emojies ☄️ 💥 🔥 🌪 | |" "+------------------------------------+-----------------+--------------------------------+--------+" "| Keep it simple | Unknown | 🍳 | 100 |" "+------------------------------------+-----------------+--------------------------------+--------+" ); } #[cfg(feature = "ansi")] #[test] fn multiline_table_test2() { use testing_table::assert_table; let data = &[ ["\u{1b}[37mThis is the 0.19 release of Nushell. If you'd like to read more about it, please check out: https://www.nushell.sh/blog/2020/09/01/nushell_0_19.html\n\nFor convenience, we are providing full builds for Windows, Linux, and macOS. These are the \"all extra features\" builds, so be sure you have the requirements to enable all capabilities: https://github.com/nushell/book/blob/master/en/installation.md#dependencies\n\u{1b}[0m"], ]; let mut table = Table::new(data); table.with(Style::modern()); assert_table!( ansi_str::AnsiStr::ansi_strip(&table.to_string()), r#"┌──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐"# r#"│ 0 │"# r#"├──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┤"# r#"│ This is the 0.19 release of Nushell. If you'd like to read more about it, please check out: https://www.nushell.sh/blog/2020/09/01/nushell_0_19.html │"# r#"│ │"# r#"│ For convenience, we are providing full builds for Windows, Linux, and macOS. These are the "all extra features" builds, so be sure you have the requirements to enable all capabilities: https://github.com/nushell/book/blob/master/en/installation.md#dependencies │"# r#"│ │"# r#"└──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘"# ); table.with(Width::wrap(100)); assert_table!( ansi_str::AnsiStr::ansi_strip(&table.to_string()), "┌──────────────────────────────────────────────────────────────────────────────────────────────────┐" "│ 0 │" "├──────────────────────────────────────────────────────────────────────────────────────────────────┤" "│ This is the 0.19 release of Nushell. If you'd like to read more about it, please check out: http │" "│ s://www.nushell.sh/blog/2020/09/01/nushell_0_19.html │" "│ │" "│ For convenience, we are providing full builds for Windows, Linux, and macOS. These are the \"all │" "│ extra features\" builds, so be sure you have the requirements to enable all capabilities: https:/ │" "│ /github.com/nushell/book/blob/master/en/installation.md#dependencies │" "│ │" "└──────────────────────────────────────────────────────────────────────────────────────────────────┘" ); } test_table!( table_1x1_empty, { Builder::from_iter(vec![vec![""]]).build() .with(Style::modern()) .with(Settings::new(Height::limit(0), Width::increase(10))) }, "┌────────┐" "└────────┘" ); test_table!( table_2x2_empty, { Builder::from_iter(vec![vec![" ", ""], vec![" ", ""]]).build() .with(Style::modern()) .with(Padding::zero()) .with(Height::list([1, 0])) }, "┌─┬┐" "│ ││" "├─┼┤" "└─┴┘" ); test_table!( table_2x2_empty_height_list_together_with_width_list_dont_work_0, { Builder::from_iter(vec![vec!["", ""], vec!["", ""]]).build() .with(Style::modern()) .with(Padding::zero()) .with(Height::list([1, 0])) .with(Width::list([1, 0])) }, "┌─┬┐" "│ ││" "├─┼┤" "│ ││" "└─┴┘" ); test_table!( table_2x2_empty_height_list_together_with_width_list_dont_work_1, { Builder::from_iter(vec![vec!["", ""], vec!["", ""]]).build() .with(Style::modern()) .with(Padding::zero()) .with(Width::list([1, 0])) .with(Height::list([1, 0])) }, "┌┬┐" "│││" "├┼┤" "└┴┘" ); test_table!( table_modify_test, Matrix::new(3, 3) .with(Style::markdown()) .modify((1, 0), "Hello World") .modify((0, 1), "Hello World 2"), "| N | Hello World 2 | column 1 | column 2 |" "|-------------|---------------|----------|----------|" "| Hello World | 0-0 | 0-1 | 0-2 |" "| 1 | 1-0 | 1-1 | 1-2 |" "| 2 | 2-0 | 2-1 | 2-2 |" ); test_table!( table_tuple_settings_list_0_test, Matrix::new(3, 3) .with(Style::markdown()) .modify((1, 0), ("Hello World", "Hello World 2")), "| N | column 0 | column 1 | column 2 |" "|---------------|----------|----------|----------|" "| Hello World 2 | 0-0 | 0-1 | 0-2 |" "| 1 | 1-0 | 1-1 | 1-2 |" "| 2 | 2-0 | 2-1 | 2-2 |" ); test_table!( table_tuple_settings_list_1_test, Matrix::new(3, 3) .with(Style::markdown()) .modify((1, 0), ("Hello World", Padding::new(2, 2, 1, 1), "1")), "| N | column 0 | column 1 | column 2 |" "|-----|----------|----------|----------|" "| | 0-0 | 0-1 | 0-2 |" "| 1 | | | |" "| | | | |" "| 1 | 1-0 | 1-1 | 1-2 |" "| 2 | 2-0 | 2-1 | 2-2 |" ); test_table!( table_tuple_settings_list_2_test, Matrix::new(3, 3) .with(Style::markdown()) .with((Padding::new(2, 2, 0, 0), Highlight::outline((0, 0), '*'), Shadow::new(5))), "******* " "* N * column 0 | column 1 | column 2 |▒▒▒▒▒" "*******------------|------------|------------|▒▒▒▒▒" "| 0 | 0-0 | 0-1 | 0-2 |▒▒▒▒▒" "| 1 | 1-0 | 1-1 | 1-2 |▒▒▒▒▒" "| 2 | 2-0 | 2-1 | 2-2 |▒▒▒▒▒" " ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒" " ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒" " ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒" " ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒" " ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒" ); tabled-0.18.0/tests/derive/derive_test.rs000064400000000000000000001631631046102023000164620ustar 00000000000000#![cfg(feature = "derive")] #![cfg(feature = "std")] use std::fmt::Display; use tabled::{Table, Tabled}; use testing_table::test_table; // https://users.rust-lang.org/t/create-a-struct-from-macro-rules/19829 macro_rules! test_tuple { ( $test_name:ident, { $(#[$struct_attr:meta])* { $( $(#[$attr:meta])* $ty:ty)* } }, { $($init:expr)* }, { $headers:expr, $fields:expr } $(,)? $(pre: { $($init_block:stmt)* })? ) => { #[test] fn $test_name() { $($($init_block)*)? #[derive(Tabled)] $(#[$struct_attr])* #[allow(dead_code)] struct TestType( $( $(#[$attr])* $ty, )* ); let value = TestType($($init,)*); let fields: Vec<&'static str> = $fields.to_vec(); let headers: Vec<&'static str> = $headers.to_vec(); assert_eq!(value.fields(), fields); assert_eq!(TestType::headers(), headers); assert_eq!(::LENGTH, headers.len()); assert_eq!(::LENGTH, fields.len()); } }; } macro_rules! test_enum { ( $test_name:ident, { $(#[$struct_attr:meta])* { $( $(#[$var_attr:meta])* $var:ident $({ $( $(#[$attr:meta])* $field:ident: $ty:ty),* $(,)? })? $(( $( $(#[$attr2:meta])* $ty2:ty),* $(,)? ))? )* } }, { $($init_block:stmt)* }, { $headers:expr }, { $($init:expr => $expected:expr,)* } ) => { #[allow(dead_code, unused_imports)] #[test] fn $test_name() { $($init_block)* #[derive(Tabled)] $(#[$struct_attr])* enum TestType { $( $(#[$var_attr])* $var $({ $( $(#[$attr])* $field: $ty,)* })? $(( $( $(#[$attr2])* $ty2,)* ))? ),* } let headers: Vec<&'static str> = $headers.to_vec(); assert_eq!(TestType::headers(), headers); assert_eq!(::LENGTH, headers.len()); { use TestType::*; $( let variant = $init; let fields: Vec<&'static str> = $expected.to_vec(); assert_eq!(variant.fields(), fields); )* } } }; } macro_rules! test_struct { ( $test_name:ident, { $(#[$struct_attr:meta])* { $( $(#[$attr:meta])* $field:ident: $ty:ty),* $(,)? } } $(,)? { $($init_block:stmt)* } $(,)? { $( $val_field:ident: $val:expr),* } $(,)? { $headers:expr, $fields:expr $(,)? } $(,)? ) => { #[allow(dead_code, unused_imports)] #[test] fn $test_name() { $($init_block)* #[derive(Tabled)] $(#[$struct_attr])* struct TestType { $( $(#[$attr])* $field: $ty, )* } let value = TestType { $($val_field: $val,)* }; let fields: Vec<&'static str> = $fields.to_vec(); let headers: Vec<&'static str> = $headers.to_vec(); assert_eq!(TestType::headers(), headers); assert_eq!(value.fields(), fields); assert_eq!(::LENGTH, headers.len()); assert_eq!(::LENGTH, fields.len()); } }; } #[allow(non_camel_case_types)] type sstr = &'static str; mod tuple { use super::*; test_tuple!(basic, { { u8 sstr } }, { 0 "v2" }, { ["0", "1"], ["0", "v2"] } ); test_tuple!(empty, { { } }, { }, { [], [] }); test_tuple!(rename, { { u8 #[tabled(rename = "field 2")] sstr } }, { 0 "123" }, { ["0", "field 2"], ["0", "123"] }); test_tuple!(skip_0, { { #[tabled(skip)] u8 #[tabled(rename = "field 2", skip)] sstr sstr } }, { 0 "v2" "123" }, { ["2"], ["123"] }); test_tuple!(skip_1, { { #[tabled(skip)] u8 #[tabled(skip)] #[tabled(rename = "field 2")] sstr sstr } }, { 0 "v2" "123" }, { ["2"], ["123"] }); test_tuple!(order_0, { { #[tabled(order = 0)] u8 u8 u8} }, { 0 1 2 }, { ["0", "1", "2"], ["0", "1", "2"] }); test_tuple!(order_1, { { #[tabled(order = 1)] u8 u8 u8} }, { 0 1 2 }, { ["1", "0", "2"], ["1", "0", "2"] }); test_tuple!(order_2, { { #[tabled(order = 2)] u8 u8 u8} }, { 0 1 2 }, { ["1", "2", "0"], ["1", "2", "0"] }); test_tuple!(order_3, { { u8 #[tabled(order = 0)] u8 u8} }, { 0 1 2 }, { ["1", "0", "2"], ["1", "0", "2"] }); test_tuple!(order_4, { { u8 #[tabled(order = 1)] u8 u8} }, { 0 1 2 }, { ["0", "1", "2"], ["0", "1", "2"] }); test_tuple!(order_5, { { u8 #[tabled(order = 2)] u8 u8} }, { 0 1 2 }, { ["0", "2", "1"], ["0", "2", "1"] }); test_tuple!(order_6, { { u8 u8 #[tabled(order = 0)] u8} }, { 0 1 2 }, { ["2", "0", "1"], ["2", "0", "1"] }); test_tuple!(order_7, { { u8 u8 #[tabled(order = 1)] u8} }, { 0 1 2 }, { ["0", "2", "1"], ["0", "2", "1"] }); test_tuple!(order_8, { { u8 u8 #[tabled(order = 2)] u8} }, { 0 1 2 }, { ["0", "1", "2"], ["0", "1", "2"] }); test_tuple!(order_9, { { #[tabled(order = 2)] u8 u8 #[tabled(order = 0)] u8} }, { 0 1 2 }, { ["2", "1", "0"], ["2", "1", "0"] }); test_tuple!(order_10, { { #[tabled(order = 2)] u8 #[tabled(order = 1)] u8 u8} }, { 0 1 2 }, { ["2", "1", "0"], ["2", "1", "0"] }); test_tuple!(order_11, { { #[tabled(order = 2)] u8 #[tabled(order = 2)] u8 #[tabled(order = 1)] u8} }, { 0 1 2 }, { ["0", "2", "1"], ["0", "2", "1"] }); test_tuple!(order_12, { { #[tabled(order = 2)] u8 #[tabled(order = 2)] u8 #[tabled(order = 2)] u8} }, { 0 1 2 }, { ["0", "1", "2"], ["0", "1", "2"] }); test_tuple!(order_13, { { #[tabled(order = 1)] u8 #[tabled(order = 1)] u8 #[tabled(order = 1)] u8} }, { 0 1 2 }, { ["0", "2", "1"], ["0", "2", "1"] }); test_tuple!(order_14, { { #[tabled(order = 2)] u8 #[tabled(order = 1)] u8 #[tabled(order = 0)] u8} }, { 0 1 2 }, { ["2", "1", "0"], ["2", "1", "0"] }); test_tuple!(rename_all, { #[tabled(rename_all = "UPPERCASE")] { u8 sstr } }, { 0 "123" }, { ["0", "1"], ["0", "123"] }); test_tuple!(rename_all_field, { { u8 #[tabled(rename_all = "UPPERCASE")] sstr } }, { 0 "123" }, { ["0", "1"], ["0", "123"] }); test_tuple!(rename_all_field_with_rename_0, { { u8 #[tabled(rename_all = "UPPERCASE", rename = "Something")] sstr } }, { 0 "123" }, { ["0", "Something"], ["0", "123"] }); test_tuple!(rename_all_field_with_rename_1, { { u8 #[tabled(rename = "Something")] #[tabled(rename_all = "UPPERCASE")] sstr } }, { 0 "123" }, { ["0", "Something"], ["0", "123"] }); test_tuple!( display_option, { { u8 #[tabled(display = "display_option")] Option } }, { 0 Some("v2") }, { ["0", "1"], ["0", "some v2"] }, pre: { fn display_option(o: &Option) -> String { match o { Some(s) => format!("some {s}"), None => "none".to_string(), } } } ); test_tuple!( display_option_args, { { u8 #[tabled(display("display_option", 1, "234"))] Option } }, { 0 Some("v2") }, { ["0", "1"], ["0", "some 1 234"] }, pre: { fn display_option(_opt: &Option, val: usize, text: &str) -> String { format!("some {val} {text}") } } ); test_tuple!( display_option_self, { { u8 #[tabled(display = "Self::display_option")] Option } }, { 0 Some("v2") }, { ["0", "1"], ["0", "some v2"] }, pre: { impl TestType { fn display_option(o: &Option) -> String { match o { Some(s) => format!("some {s}"), None => "none".to_string(), } } } } ); test_tuple!( display_option_self_2, { { u8 #[tabled(display("Self::display_option", self))] Option } }, { 0 Some("v2") }, { ["0", "1"], ["0", "some v2"] }, pre: { impl TestType { fn display_option(_opt: &Option, o: &TestType) -> String { match o.1 { Some(s) => format!("some {s}"), None => "none".to_string(), } } } } ); test_tuple!( display_option_self_3, { { u8 #[tabled(display("display_option", self))] Option } }, { 0 Some("v2") }, { ["0", "1"], ["0", "some v2"] }, pre: { fn display_option(_opt: &Option, o: &TestType) -> String { match o.1 { Some(s) => format!("some {s}"), None => "none".to_string(), } } } ); test_tuple!( display_option_self_4, { { u8 #[tabled(display("display_option", self.0, self.0))] Option } }, { 0 Some("v2") }, { ["0", "1"], ["0", "some 0.0"] }, pre: { fn display_option(_opt: &Option, o1: u8, o2: u8) -> String { format!("some {o1}.{o2}") } } ); test_tuple!(format_1, { { u8 #[tabled(format = "foo {}")] sstr } }, { 0 "v2" }, { ["0", "1"], ["0", "foo v2"] }); test_tuple!(format_2, { { u8 #[tabled(format = "foo {:?}")] sstr } }, { 0 "v2" }, { ["0", "1"], ["0", "foo \"v2\""] }); // todo : self represents the tuple here. It should be the sstr element instead. test_tuple!(format_3, { #[derive(Debug)] { u8 #[tabled(format("foo {} {:?}", 2, self))] String } }, { 0 String::from("string") }, { ["0", "1"], ["0", "foo 2 TestType(0, \"string\")"] }); // #[test] // fn order_compile_fail_when_order_is_bigger_then_count_fields() { // #[derive(Tabled)] // struct St(#[tabled(order = 3)] u8, u8, u8); // } } mod enum_ { use super::*; test_enum!( basic, { { Security Embedded Frontend Unknown } }, {}, { ["Security", "Embedded", "Frontend", "Unknown"] }, { Security => ["+", "", "", ""], Embedded => ["", "+", "", ""], Frontend => ["", "", "+", ""], Unknown => ["", "", "", "+"], } ); test_enum!( diverse, { { A { a: u8, b: i32 } B(sstr) K } }, {}, { ["A", "B", "K"] }, { A { a: 1, b: 2 } => ["+", "", ""], B("") => ["", "+", ""], K => ["", "", "+"], } ); test_enum!( rename_variant, { { #[tabled(rename = "Variant 1")] A { a: u8, b: i32 } #[tabled(rename = "Variant 2")] B(sstr) K } }, {}, { ["Variant 1", "Variant 2", "K"] }, { A { a: 1, b: 2 } => ["+", "", ""], B("") => ["", "+", ""], K => ["", "", "+"], } ); test_enum!( skip_variant, { { A { a: u8, b: i32 } #[tabled(skip)] B(sstr) K } }, {}, { ["A", "K"] }, { A { a: 1, b: 2 } => ["+", ""], B("") => ["", ""], K => ["", "+"], } ); test_enum!( inline_variant, { { #[tabled(inline("Auto::"))] Auto { #[tabled(rename = "mod")] model: sstr, engine: sstr } #[tabled(inline)] Bikecycle( #[tabled(rename = "name")] sstr, #[tabled(inline)] Bike ) Skateboard } }, { #[derive(Tabled)] struct Bike { brand: sstr, price: f32 } }, { ["Auto::mod", "Auto::engine", "name", "brand", "price", "Skateboard"] }, { Skateboard => ["", "", "", "", "", "+"], Auto { model: "Mini", engine: "v8" } => ["Mini", "v8", "", "", "", ""], Bikecycle("A bike", Bike { brand: "Canyon", price: 2000.0 })=> ["", "", "A bike", "Canyon", "2000", ""], } ); test_enum!( inline_field_with_display_function, { { #[tabled(inline("backend::"))] Backend { #[tabled(display = "display", rename = "name")] value: sstr } Frontend } }, { fn display(_: sstr) -> String { "asd".to_string() } }, { ["backend::name", "Frontend"] }, { Backend { value: "123" } => ["asd", ""], Frontend => ["", "+"], } ); test_enum!( inline_field_with_display_self_function, { { #[tabled(inline("backend::"))] Backend { #[tabled()] #[tabled(display("display", self), rename = "name")] value: sstr } Frontend } }, { fn display(_opt: &sstr, _: &T) -> String { "asd".to_string() } }, { ["backend::name", "Frontend"] }, { Backend { value: "123" } => ["asd", ""], Frontend => ["", "+"], } ); test_enum!( with_display, { { #[tabled(inline)] A( #[tabled(display = "format::<4>")] sstr ) B } }, { fn format(_: sstr) -> String { ID.to_string() } }, { ["0", "B"] }, { A("") => ["4", ""], B => ["", "+"], } ); test_enum!( with_display_self, { { #[tabled(inline)] A( #[tabled(display("Self::format::<4>", self))] sstr ) B } }, { impl TestType { fn format(_opt: &sstr, _: &Self) -> String { ID.to_string() } } }, { ["0", "B"] }, { A("") => ["4", ""], B => ["", "+"], } ); test_enum!( with_display_self_complex_0, { { #[tabled(inline)] A( #[tabled(display("Self::format::<4>", self, self.0))] sstr ) B } }, { impl TestType { fn format(_opt: &sstr, _: &Self, _: sstr) -> String { ID.to_string() } } }, { ["0", "B"] }, { A("") => ["4", ""], B => ["", "+"], } ); test_enum!( with_display_self_complex_1, { { #[tabled(inline)] A{ #[tabled(display("Self::format::<4>", self, self.asd))] asd: sstr } B } }, { impl TestType { fn format(_opt: &sstr, _: &Self, _: sstr) -> String { ID.to_string() } } }, { ["asd", "B"] }, { A { asd: "" } => ["4", ""], B => ["", "+"], } ); test_enum!( rename_all_variant, { { #[tabled(rename_all = "snake_case")] VariantName1 { a: u8, b: i32 } #[tabled(rename_all = "UPPERCASE")] VariantName2(String) K } }, {}, { ["variant_name1", "VARIANTNAME2", "K"] }, {} ); test_enum!( rename_all_enum, { #[tabled(rename_all = "snake_case")] { VariantName1 { a: u8, b: i32 } VariantName2(String) K } }, {}, { ["variant_name1", "variant_name2", "k"] }, {} ); test_enum!( rename_all_enum_inherited_inside_struct_enum, { #[tabled(rename_all = "snake_case")] { #[tabled(inline)] VariantName1 { some_field_1: u8, some_field_2: i32 } VariantName2(String) K } }, {}, { ["some_field_1", "some_field_2", "variant_name2", "k"] }, {} ); test_enum!( rename_all_enum_inherited_inside_struct_override_by_rename_enum, { #[tabled(rename_all = "snake_case")] { #[tabled(inline)] VariantName1 { #[tabled(rename = "f1")] some_field_1: u8, #[tabled(rename = "f2")] some_field_2: i32, } VariantName2(String) K } }, {}, { ["f1", "f2", "variant_name2", "k"] }, {} ); test_enum!( rename_all_enum_inherited_inside_struct_override_by_rename_all_enum, { #[tabled(rename_all = "snake_case")] { #[tabled(inline)] VariantName1 { #[tabled(rename_all = "UPPERCASE")] some_field_1: u8, #[tabled(rename_all = "CamelCase")] some_field_2: i32, } VariantName2(String) K } }, {}, { ["SOMEFIELD1", "someField2", "variant_name2", "k"] }, {} ); test_enum!( rename_all_variant_inherited_inside_struct_enum, { #[tabled(rename_all = "snake_case")] { #[tabled(inline)] #[tabled(rename_all = "snake_case")] VariantName1 { some_field_1: u8, some_field_2: i32, } VariantName2(String) K } }, {}, { ["some_field_1", "some_field_2", "variant_name2", "k"] }, {} ); test_enum!( rename_all_variant_inherited_inside_struct_enum_overridden_by_rename, { #[tabled(rename_all = "snake_case")] { #[tabled(inline, rename_all = "snake_case")] VariantName1 { #[tabled(rename = "f1")] some_field_1: u8, #[tabled(rename = "f2")] some_field_2: i32, } VariantName2(String) K } }, {}, { ["f1", "f2", "variant_name2", "k"] }, {} ); test_enum!( rename_all_variant_inherited_inside_struct_override_by_rename_all_enum, { #[tabled(rename_all = "snake_case")] { #[tabled(rename_all = "snake_case", inline)] VariantName1 { #[tabled(rename_all = "UPPERCASE")] some_field_1: u8, #[tabled(rename_all = "CamelCase")] some_field_2: i32, } VariantName2(String) K } }, {}, { ["SOMEFIELD1", "someField2", "variant_name2", "k"] }, {} ); test_enum!( inline_enum_as_whole, { #[tabled(inline)] { AbsdEgh { a: u8, b: i32 } B(String) K } }, {}, { ["TestType"] }, { AbsdEgh { a: 0, b: 0 } => ["AbsdEgh"], B(String::new()) => ["B"], K => ["K"], } ); test_enum!( inline_enum_as_whole_and_rename, { #[tabled(inline, rename_all = "snake_case")] { AbsdEgh { a: u8, b: i32 } B(String) K } }, {}, { ["TestType"] }, { AbsdEgh { a: 0, b: 0 } => ["absd_egh"], B(String::new()) => ["b"], K => ["k"], } ); test_enum!( inline_enum_as_whole_and_rename_inner, { #[tabled(inline)] { #[tabled(rename_all = "snake_case")] AbsdEgh { a: u8, b: i32 } #[tabled(rename_all = "lowercase")] B(String) K } }, {}, { ["TestType"] }, { AbsdEgh { a: 0, b: 0 } => ["absd_egh"], B(String::new()) => ["b"], K => ["K"], } ); test_enum!( inline_enum_name, { #[tabled(inline("A struct name"))] { AbsdEgh { a: u8, b: i32 } B(String) K } }, {}, { ["A struct name"] }, { AbsdEgh { a: 0, b: 0 } => ["AbsdEgh"], B(String::new()) => ["B"], K => ["K"], } ); test_enum!( enum_display_with_variant, { { #[tabled(display = "display_variant1")] AbsdEgh { a: u8, b: i32 } #[tabled(display = "display_variant2::<200>")] B(String) K } }, { fn display_variant1(_: &TestType) -> &'static str { "Hello World" } fn display_variant2(_: &TestType) -> String { format!("asd {VAL}") } }, { ["AbsdEgh", "B", "K"] }, { AbsdEgh { a: 0, b: 0 } => ["Hello World", "", ""], B(String::new()) => ["", "asd 200", ""], K => ["", "", "+"], } ); test_enum!( enum_display_with_self_variant, { { #[tabled(display("display_variant1", self))] AbsdEgh { a: u8, b: i32 } #[tabled(display("display_variant2::<200, _>", self))] B(String) K } }, { fn display_variant1(_: &TestType, _: &D) -> &'static str { "Hello World" } fn display_variant2(_: &TestType, _: &D) -> String { format!("asd {VAL}") } }, { ["AbsdEgh", "B", "K"] }, { AbsdEgh { a: 0, b: 0 } => ["Hello World", "", ""], B(String::new()) => ["", "asd 200", ""], K => ["", "", "+"], } ); test_enum!( enum_display_with_arguments, { { #[tabled(display("display1", 1, 2, self))] AbsdEgh { a: u8, b: i32 } #[tabled(display("display2::<200>", "Hello World"))] B(String) #[tabled(display("display1", 100, 200, self))] K } }, { fn display1(_: &TestType, val: usize, val2: usize, _: &D) -> String { format!("{val} {val2}") } fn display2(_: &TestType, val: &str) -> String { format!("asd {VAL} {val}") } }, { ["AbsdEgh", "B", "K"] }, { AbsdEgh { a: 0, b: 0 } => ["1 2", "", ""], B(String::new()) => ["", "asd 200 Hello World", ""], K => ["", "", "100 200"], } ); test_enum!(order_0, { { #[tabled(order = 0)] V1(u8) V2(u8) V3(u8) } }, {}, { ["V1", "V2", "V3"] }, { V1(0) => ["+", "", ""], V2(0) => ["", "+", ""], V3(0) => ["", "", "+"],}); test_enum!(order_1, { { #[tabled(order = 1)] V1(u8) V2(u8) V3(u8) } }, {}, { ["V2", "V1", "V3"] }, { V1(0) => ["", "+", ""], V2(0) => ["+", "", ""], V3(0) => ["", "", "+"],}); test_enum!(order_2, { { #[tabled(order = 2)] V1(u8) V2(u8) V3(u8) } }, {}, { ["V2", "V3", "V1"] }, { V1(0) => ["", "", "+"], V2(0) => ["+", "", ""], V3(0) => ["", "+", ""],}); test_enum!(order_3, { { V1(u8) #[tabled(order = 0)] V2(u8) V3(u8) } }, {}, { ["V2", "V1", "V3"] }, { V1(0) => ["", "+", ""], V2(0) => ["+", "", ""], V3(0) => ["", "", "+"],}); test_enum!(order_4, { { V1(u8) #[tabled(order = 1)] V2(u8) V3(u8) } }, {}, { ["V1", "V2", "V3"] }, { V1(0) => ["+", "", ""], V2(0) => ["", "+", ""], V3(0) => ["", "", "+"],}); test_enum!(order_5, { { V1(u8) #[tabled(order = 2)] V2(u8) V3(u8) } }, {}, { ["V1", "V3", "V2"] }, { V1(0) => ["+", "", ""], V2(0) => ["", "", "+"], V3(0) => ["", "+", ""],}); test_enum!(order_6, { { V1(u8) V2(u8) #[tabled(order = 0)] V3(u8) } }, {}, { ["V3", "V1", "V2"] }, { V1(0) => ["", "+", ""], V2(0) => ["", "", "+"], V3(0) => ["+", "", ""],}); test_enum!(order_7, { { V1(u8) V2(u8) #[tabled(order = 1)] V3(u8) } }, {}, { ["V1", "V3", "V2"] }, { V1(0) => ["+", "", ""], V2(0) => ["", "", "+"], V3(0) => ["", "+", ""],}); test_enum!(order_8, { { V1(u8) V2(u8) #[tabled(order = 2)] V3(u8) } }, {}, { ["V1", "V2", "V3"] }, { V1(0) => ["+", "", ""], V2(0) => ["", "+", ""], V3(0) => ["", "", "+"],}); test_enum!(order_9, { { #[tabled(order = 2)] V1(u8) V2(u8) #[tabled(order = 0)] V3(u8) } }, {}, { ["V3", "V2", "V1"] }, { V1(0) => ["", "", "+"], V2(0) => ["", "+", ""], V3(0) => ["+", "", ""],}); test_enum!(order_10, { { #[tabled(order = 2)] V1(u8) V2(u8) #[tabled(order = 1)] V3(u8) } }, {}, { ["V2", "V3", "V1"] }, { V1(0) => ["", "", "+"], V2(0) => ["+", "", ""], V3(0) => ["", "+", ""],}); test_enum!(order_11, { { #[tabled(order = 2)] V1(u8) #[tabled(order = 2)] V2(u8) #[tabled(order = 1)] V3(u8) } }, {}, { ["V1", "V3", "V2"] }, { V1(0) => ["+", "", ""], V2(0) => ["", "", "+"], V3(0) => ["", "+", ""],}); test_enum!(order_12, { { #[tabled(order = 2)] V1(u8) #[tabled(order = 1)] V2(u8) #[tabled(order = 0)] V3(u8) } }, {}, { ["V3", "V2", "V1"] }, { V1(0) => ["", "", "+"], V2(0) => ["", "+", ""], V3(0) => ["+", "", ""],}); test_enum!(order_13, { { #[tabled(order = 0)] V1(u8) #[tabled(order = 0)] V2(u8) #[tabled(order = 0)] V3(u8) } }, {}, { ["V3", "V1", "V2"] }, { V1(0) => ["", "+", ""], V2(0) => ["", "", "+"], V3(0) => ["+", "", ""],}); test_enum!(order_14, { { #[tabled(order = 1)] V1(u8) #[tabled(order = 1)] V2(u8) #[tabled(order = 1)] V3(u8) } }, {}, { ["V1", "V3", "V2"] }, { V1(0) => ["+", "", ""], V2(0) => ["", "", "+"], V3(0) => ["", "+", ""],}); test_enum!(order_15, { { #[tabled(order = 2)] V1(u8) #[tabled(order = 2)] V2(u8) #[tabled(order = 2)] V3(u8) } }, {}, { ["V1", "V2", "V3"] }, { V1(0) => ["+", "", ""], V2(0) => ["", "+", ""], V3(0) => ["", "", "+"],}); test_enum!(order_0_inlined, { #[tabled(inline)] { #[tabled(order = 1)] V1(u8) V2(u8) V3(u8) } }, {}, { ["TestType"] }, { V1(0) => ["V1"], V2(0) => ["V2"], V3(0) => ["V3"], }); test_enum!( format, { { #[tabled(inline)] AbsdEgh { #[tabled(format("{}-{}", self.a, self.b))] a: u8, b: i32 } #[tabled(format("{} s", 4))] B(String) #[tabled(inline)] C( #[tabled(rename = "C", format("{} ss", 4))] String ) #[tabled(format = "k.")] K } }, {}, { ["a", "b", "B", "C", "K"] }, { AbsdEgh { a: 0, b: 1 } => ["0-1", "1", "", "", ""], B(String::new()) => ["", "", "4 s", "", ""], C(String::new()) => ["", "", "", "4 ss", ""], K => ["", "", "", "", "k."], } ); test_enum!( format_complex, { { #[tabled(inline)] AbsdEgh { #[tabled(format("{}-{}-{}", foo(*self.a as usize), foo(*self.b as usize), self.c[2]))] a: u8, b: i32, #[tabled(skip)] c: Vec, } #[tabled(format("{} s", foo(4)))] B(String) #[tabled(inline)] C( #[tabled(rename = "C", format("{} ss {}", foo(4), self.1[2]))] String, #[tabled(skip)] Vec, ) #[tabled(format = "k.")] K } }, { fn foo(a: usize) -> String { if a > 100 { String::from(">100") } else { String::from("<100") } } }, { ["a", "b", "B", "C", "K"] }, { AbsdEgh { a: 0, b: 1, c: vec![1, 2, 3] } => ["<100-<100-3", "1", "", "", ""], B(String::new()) => ["", "", "<100 s", "", ""], C(String::new(), vec![1, 2, 3]) => ["", "", "", "<100 ss 3", ""], K => ["", "", "", "", "k."], } ); } mod unit { use super::*; #[test] fn basic() { #[derive(Tabled)] struct St; let st = St; assert!(st.fields().is_empty()); assert!(St::headers().is_empty()); assert_eq!(St::LENGTH, 0); } } mod structure { use super::*; test_struct!(empty, { {} } {} { } { [], [] }); test_struct!( general, { { f1: u8, f2: sstr, } } {} { f1: 0, f2: "v2" } { ["f1", "f2"], ["0", "v2"], } ); test_struct!( rename, { { #[tabled(rename = "field 1")] f1: u8, #[tabled(rename = "field 2")] f2: sstr, } } {} { f1: 0, f2: "v2" } { ["field 1", "field 2"], ["0", "v2"], } ); test_struct!( skip, { { #[tabled(skip)] f1: u8, #[tabled(rename = "field 2", skip)] f2: sstr, f3: sstr, } } {} { f1: 0, f2: "v2", f3: "123" } { ["f3"], ["123"], } ); test_struct!( skip_true, { { #[tabled(skip = true)] f1: u8, #[tabled(rename = "field 2", skip = true)] f2: sstr, f3: sstr, } } {} { f1: 0, f2: "v2", f3: "123" } { ["f3"], ["123"], } ); test_struct!( inline, { { #[tabled(inline = true)] id: u8, name: sstr, #[tabled(inline)] ed: Education } } { #[derive(Tabled)] struct Education { uni: sstr, graduated: bool } } { id: 0, name: "Maxim", ed: Education { uni: "BNTU", graduated: true } } { ["u8", "name","uni","graduated"], ["0", "Maxim", "BNTU", "true"] } ); test_struct!( inline_with_prefix, { { #[tabled(rename = "it's an ignored option", inline)] id: u8, name: sstr, #[tabled(inline("education::"))] ed: Education, } } { #[derive(Tabled)] struct Education { uni: sstr, graduated: bool } } { id: 0, name: "Maxim", ed: Education { uni: "BNTU", graduated: true }} { ["u8", "name","education::uni","education::graduated"], ["0", "Maxim", "BNTU", "true"] } ); test_struct!( display_with, { { f1: u8, #[tabled(display = "display_option")] f2: Option, } } { fn display_option(o: &Option) -> String { match o { Some(s) => format!("some {s}"), None => "none".to_string(), } } } { f1: 0, f2: Some("v2") } { ["f1", "f2"], ["0", "some v2"] } ); test_struct!( display_with_args, { { f1: u8, #[tabled(display("display_option", 1, 2, 3))] f2: Option, } } { fn display_option(_opt: &Option, v1: usize, v2: usize, v3: usize) -> String { format!("{v1} {v2} {v3}") } } { f1: 0, f2: Some("v2") } { ["f1", "f2"], ["0", "1 2 3"] } ); test_struct!( display_with_args_using_self, { { f1: u8, #[tabled(display("display_option", &self.f1, 2, 3))] f2: Option, } } { fn display_option(_opt: &Option, v1: &u8, v2: usize, v3: usize) -> String { format!("{v1} {v2} {v3}") } } { f1: 0, f2: Some("v2") } { ["f1", "f2"], ["0", "0 2 3"] } ); test_struct!( display_with_self_static_method, { { f1: u8, #[tabled(display = "Self::display_option")] f2: Option, } } { impl TestType { fn display_option(o: &Option) -> String { match o { Some(s) => format!("some {s}"), None => "none".to_string(), } } } } { f1: 0, f2: Some("v2") } { ["f1", "f2"], ["0", "some v2"] } ); test_struct!( display_with_self_static_method_2, { { f1: u8, #[tabled(display("Self::display_option", self))] f2: Option, } } { impl TestType { fn display_option(_opt: &Option, o: &TestType) -> String { match o.f2 { Some(s) => format!("some {s}"), None => "none".to_string(), } } } } { f1: 0, f2: Some("v2") } { ["f1", "f2"], ["0", "some v2"] } ); test_struct!( display_with_self_2_self_static_method_2, { { f1: u8, #[tabled(display("Self::display_option", self))] f2: Option, } } { impl TestType { fn display_option(_opt: &Option, s: &Self) -> String { match s.f2 { Some(s) => format!("some {s}"), None => "none".to_string(), } } } } { f1: 0, f2: Some("v2") } { ["f1", "f2"], ["0", "some v2"] } ); test_struct!( display_with_self_2_self_static_method, { { f1: u8, #[tabled(display("display_option", self))] f2: Option, } } { fn display_option(_opt: &Option, o: &TestType) -> String { match o.f2 { Some(s) => format!("some {s}"), None => "none".to_string(), } } } { f1: 0, f2: Some("v2") } { ["f1", "f2"], ["0", "some v2"] } ); test_struct!( display_with_args_using_self_array_and_func, { { #[tabled(skip)] f1: [u8; 4], #[tabled(display("display_option", &[self.f1[0], self.f1[1]], ToString::to_string(&self.f3.to_string())))] f2: Option, f3: usize, } } { fn display_option(_opt: &Option, v1: &[u8; 2], v4: String) -> String { format!("{} {} {v4}", v1[0], v1[1]) } } { f1: [0, 1, 2, 3], f2: Some("v2"), f3: 100 } { ["f2", "f3"], ["0 1 100", "100"] } ); test_struct!(order_0, { { #[tabled(order = 0)] f0: u8, f1: u8, f2: u8 } } {} { f0: 0, f1: 1, f2: 2 } { ["f0", "f1", "f2"], ["0", "1", "2"] }); test_struct!(order_1, { { #[tabled(order = 1)] f0: u8, f1: u8, f2: u8 } } {} { f0: 0, f1: 1, f2: 2 } { ["f1", "f0", "f2"], ["1", "0", "2"] }); test_struct!(order_2, { { #[tabled(order = 2)] f0: u8, f1: u8, f2: u8 } } {} { f0: 0, f1: 1, f2: 2 } { ["f1", "f2", "f0"], ["1", "2", "0"] }); test_struct!(order_3, { { f0: u8, #[tabled(order = 0)] f1: u8, f2: u8 } } {} { f0: 0, f1: 1, f2: 2 } { ["f1", "f0", "f2"], ["1", "0", "2"] }); test_struct!(order_4, { { f0: u8, #[tabled(order = 1)] f1: u8, f2: u8 } } {} { f0: 0, f1: 1, f2: 2 } { ["f0", "f1", "f2"], ["0", "1", "2"] }); test_struct!(order_5, { { f0: u8, #[tabled(order = 2)] f1: u8, f2: u8 } } {} { f0: 0, f1: 1, f2: 2 } { ["f0", "f2", "f1"], ["0", "2", "1"] }); test_struct!(order_6, { { f0: u8, f1: u8, #[tabled(order = 0)] f2: u8 } } {} { f0: 0, f1: 1, f2: 2 } { ["f2", "f0", "f1"], ["2", "0", "1"] }); test_struct!(order_7, { { f0: u8, f1: u8, #[tabled(order = 1)] f2: u8 } } {} { f0: 0, f1: 1, f2: 2 } { ["f0", "f2", "f1"], ["0", "2", "1"] }); test_struct!(order_8, { { f0: u8, f1: u8, #[tabled(order = 2)] f2: u8 } } {} { f0: 0, f1: 1, f2: 2 } { ["f0", "f1", "f2"], ["0", "1", "2"] }); test_struct!(order_9, { { #[tabled(order = 2)] f0: u8, f1: u8, #[tabled(order = 0)] f2: u8 } } {} { f0: 0, f1: 1, f2: 2 } { ["f2", "f1", "f0"], ["2", "1", "0"] }); test_struct!(order_10, { { #[tabled(order = 2)] f0: u8, #[tabled(order = 1)] f1: u8, f2: u8 } } {} { f0: 0, f1: 1, f2: 2 } { ["f2", "f1", "f0"], ["2", "1", "0"] }); test_struct!(order_11, { { #[tabled(order = 2)] f0: u8, #[tabled(order = 2)] f1: u8, #[tabled(order = 1)] f2: u8 } } {} { f0: 0, f1: 1, f2: 2 } { ["f0", "f2", "f1"], ["0", "2", "1"] }); test_struct!(order_12, { { #[tabled(order = 2)] f0: u8, #[tabled(order = 1)] f1: u8, #[tabled(order = 0)] f2: u8 } } {} { f0: 0, f1: 1, f2: 2 } { ["f2", "f1", "f0"], ["2", "1", "0"] }); test_struct!( rename_all, { #[tabled(rename_all = "UPPERCASE")] { f1: u8, f2: sstr } } {} { f1: 0, f2: "v2" } { ["F1", "F2"], ["0", "v2"] } ); test_struct!( rename_all_override_in_field_by_rename, { #[tabled(rename_all = "UPPERCASE")] { #[tabled(rename = "213213")] f1: u8, f2: sstr } } {} { f1: 0, f2: "v2" } { ["213213", "F2"], ["0", "v2"] } ); test_struct!( rename_all_override_in_field_by_rename_all, { #[tabled(rename_all = "UPPERCASE")] { #[tabled(rename_all = "lowercase")] f1: u8, f2: sstr } } {} { f1: 0, f2: "v2" } { ["f1", "F2"], ["0", "v2"] } ); test_struct!( rename_all_field, { { #[tabled(rename_all = "lowercase")] f1: u8, #[tabled(rename_all = "UPPERCASE")] f2: sstr } } {} { f1: 0, f2: "v2" } { ["f1", "F2"], ["0", "v2"] } ); test_struct!( rename_all_field_overridden_by_rename, { { #[tabled(rename_all = "lowercase", rename = "Hello")] f1: u8, #[tabled(rename_all = "UPPERCASE")] f2: sstr } } {} { f1: 0, f2: "v2" } { ["Hello", "F2"], ["0", "v2"] } ); test_struct!( format, { { #[tabled(format = "{} cc")] f1: u8, f2: u8, } } {} { f1: 0, f2: 0 } { ["f1", "f2"], ["0 cc", "0"] } ); test_struct!( format_with_args, { { #[tabled(format("{}/{} cc/kg", self.f1, self.f2))] f1: u8, f2: u8, } } {} { f1: 1, f2: 2 } { ["f1", "f2"], ["1/2 cc/kg", "2"] } ); // #[test] // fn order_compile_fail_when_order_is_bigger_then_count_fields() { // #[derive(Tabled)] // struct St { // #[tabled(order = 3)] // f0: u8, // f1: u8, // f2: u8, // } // } } test_tuple!(skipped_fields_not_implement_display_tuple, { { #[tabled(skip)] () sstr } }, { () "123" }, { ["1"], ["123"] }); test_struct!(skipped_fields_not_implement_display_struct, { { #[tabled(skip)] _unit: (), s: sstr } } {} { _unit: (), s: "123" } { ["s"], ["123"] }); test_struct!( skipped_fields_not_implement_display_struct_in_inline, { { s: sstr, #[tabled(inline)] f: S1 } } { #[derive(Tabled)] struct S1 { #[tabled(skip)] _unit: (), s: sstr, } } { s: "123", f: S1 { _unit: (), s: "..." } } { ["s", "s"], ["123", "..."] }, ); test_enum!( skipped_fields_not_implement_display_enum, { { #[tabled(inline("A::"))] A { name: sstr } #[tabled(inline("B::"))] B { issue: usize, name: sstr, #[tabled(skip)] _gem: (), } #[tabled(inline("C::"))] C(usize, #[tabled(skip)] (), sstr) D } }, {}, { ["A::name", "B::issue", "B::name", "C::0", "C::2", "D"] }, { A { name: "nrdxp" } => ["nrdxp", "", "", "", "", ""], B { _gem: (), issue: 32, name: "nrdxp" } => ["", "32", "nrdxp", "", "", ""], C(32, (), "nrdxp") => ["", "", "", "32", "nrdxp", ""], D => ["", "", "", "", "", "+"], } ); test_struct!( ignore_display_with_when_used_with_inline, { { f1: sstr, f2: sstr, #[tabled(display = "print", inline)] f3: usize } } {} { f1: "123", f2: "456", f3: 789 } { ["f1", "f2", "usize"], ["123", "456", "789"], } ); test_struct!( ignore_display_with_when_used_with_inline_2, { { f1: sstr, f2: sstr, #[tabled(display = "print", )] #[tabled(inline)] f3: usize } } {} { f1: "123", f2: "456", f3: 789 } { ["f1", "f2", "usize"], ["123", "456", "789"], } ); test_struct!( display_with_and_rename, { { f1: sstr, f2: sstr, #[tabled(display = "print", rename = "asd")] f3: usize } } { #[allow(dead_code)] fn print(_: T) -> String { String::new() } } { f1: "123", f2: "456", f3: 789 } { ["f1", "f2", "asd"], ["123", "456", ""], } ); test_struct!( display_with_and_rename_2, { { f1: sstr, f2: sstr, #[tabled(display = "print")] #[tabled(rename = "asd")] f3: usize } } { #[allow(dead_code)] fn print(_: T) -> String { String::new() } } { f1: "123", f2: "456", f3: 789 } { ["f1", "f2", "asd"], ["123", "456", ""], } ); test_struct!( display_with_and_rename_all, { { f1: sstr, f2: sstr, #[tabled(display = "print", rename_all = "UPPERCASE")] f3: usize } } { #[allow(dead_code)] fn print(_: T) -> String { String::new() } } { f1: "123", f2: "456", f3: 789 } { ["f1", "f2", "F3"], ["123", "456", ""], } ); #[test] fn rename_all_variants() { macro_rules! test_case { ( $name:ident, $case:expr ) => { #[allow(dead_code)] #[derive(Tabled)] #[tabled(rename_all = $case)] struct $name { field: usize, } }; } test_case!(S1, "UPPERCASE"); test_case!(S2, "lowercase"); test_case!(S3, "camelCase"); test_case!(S4, "PascalCase"); test_case!(S5, "snake_case"); test_case!(S6, "SCREAMING_SNAKE_CASE"); test_case!(S7, "kebab-case"); test_case!(S8, "verbatimcase"); } // #[test] // fn wrong_rename_all_panic_when_used_as_not_first() { // #[derive(Tabled)] // #[tabled(rename_all = "UPPERCASE")] // #[tabled(rename_all = "some wrong case")] // struct Struct1 { // field: usize, // } // let st = Struct1 { field: 789 }; // assert_eq!(Struct1::headers(), vec!["FIELD"],); // assert_eq!(st.fields(), vec!["789"]); // #[derive(Tabled)] // #[tabled(rename_all = "UPPERCASE", rename_all = "some wrong case")] // struct Struct2 { // field: usize, // } // let st = Struct2 { field: 789 }; // assert_eq!(Struct1::headers(), vec!["FIELD"],); // assert_eq!(st.fields(), vec!["789"]); // } #[test] fn rename_all_gets_last_value() { #[derive(Tabled)] #[tabled(rename_all = "UPPERCASE")] #[tabled(rename_all = "PascalCase")] struct Struct1 { field: usize, } let st = Struct1 { field: 789 }; assert_eq!(Struct1::headers(), vec!["Field"],); assert_eq!(st.fields(), vec!["789"]); #[derive(Tabled)] #[tabled(rename_all = "UPPERCASE", rename_all = "PascalCase")] struct Struct2 { field: usize, } let st = Struct2 { field: 789 }; assert_eq!(Struct1::headers(), vec!["Field"],); assert_eq!(st.fields(), vec!["789"]); } #[test] fn test_order_skip_usage() { #[derive(Tabled, Default)] pub struct Example { #[tabled(skip)] #[allow(dead_code)] id: usize, name: String, #[tabled(order = 0)] details: String, } #[derive(Tabled, Default)] pub struct Example2 { #[tabled(skip)] #[allow(dead_code)] id: usize, name: String, #[tabled(order = 1)] details: String, } assert_eq!(Example::headers(), vec!["details", "name"],); assert_eq!(Example::default().fields(), vec!["", ""]); } #[test] fn test_skip_enum_0() { #[allow(dead_code)] #[derive(Tabled)] enum Letters { Vowels { character: char, lang: u8, }, Consonant(char), #[tabled(skip)] Digit, } assert_eq!(Letters::headers(), vec!["Vowels", "Consonant"]); assert_eq!(Letters::Consonant('c').fields(), vec!["", "+"]); assert_eq!(Letters::Digit.fields(), vec!["", ""]); } #[test] fn test_reimport_trait_by_crate_attribute() { pub mod new_module { pub trait Tabled { const LENGTH: usize; fn fields(&self) -> Vec>; fn headers() -> Vec>; } } mod tabled {} #[allow(dead_code)] #[derive(Tabled)] #[tabled(crate = "new_module")] enum Letters { Vowels { character: char, lang: u8, }, Consonant(char), #[tabled(skip)] Digit, } assert_eq!( ::headers(), vec!["Vowels", "Consonant"] ); assert_eq!( ::fields(&Letters::Consonant('c')), vec!["", "+"] ); assert_eq!( ::fields(&Letters::Digit), vec!["", ""] ); } // #[test] // fn test_display_with_2() { // #[derive(Tabled)] // #[allow(dead_code)] // struct Struct<'a> { // #[tabled(display("std::path::Path::display"))] // path: &'a std::path::Path, // } // } #[test] fn test_format_enum_inline() { #[derive(Tabled)] enum Struct { #[tabled(inline)] Variant1 { #[tabled(rename = "cccc")] #[tabled(format("{} {}", self.character, self.lang))] character: char, lang: u8, }, Variant2(char), Variant3, } assert_eq!(Struct::headers(), ["cccc", "lang", "Variant2", "Variant3"]); assert_eq!( Struct::Variant1 { character: 'c', lang: b'c' } .fields(), ["c 99", "99", "", ""] ); assert_eq!(Struct::Variant2('c').fields(), ["", "", "+", ""]); assert_eq!(Struct::Variant3.fields(), ["", "", "", "+"]); } #[test] #[allow(dead_code)] fn test_macros_in_display_with() { #[derive(Tabled)] #[tabled(rename_all = "camelCase")] struct Country { name: String, #[tabled(display("display_capital", format!(".{}", self.capital)))] capital: String, #[tabled(display("display_perimeter", self))] area_km2: f32, #[tabled(display = "str::to_lowercase")] national_currency: String, national_currency_short: String, } fn display_perimeter(_area: &f32, country: &Country) -> sstr { if country.area_km2 > 1_000_000.0 { "Very Big Land" } else { "Big Land" } } fn display_capital(_capital: &str, country: String) -> std::borrow::Cow<'static, str> { format!("{country}!").into() } } #[test] #[allow(dead_code)] fn test_macros_in_format() { #[derive(Tabled)] struct Country { name: String, #[tabled(format("{}", format!(".{}", self.capital)))] capital: String, #[tabled(format("{}", self.field1[0]))] field1: [u8; 4], } } #[test] #[allow(dead_code)] fn test_enum_format_1() { #[derive(Tabled)] struct User { #[tabled(format("{}.{}.{}.{}", self.ip[0], self.ip[1], self.ip[2], self.ip[3]))] ip: [u8; 4], #[tabled(inline)] password: Password, } #[derive(Tabled)] enum Password { #[tabled(inline)] Mask { #[tabled(format("t={}/{}", self.text, self.factor))] text: String, #[tabled(skip)] factor: usize, }, #[tabled(inline)] Plain(#[tabled(rename = "password")] String), } } mod __ { #[test] fn dont_import_the_trait() { #[derive(tabled::Tabled)] struct __; } } test_table!( test_display_type_0, { #[derive(Tabled)] #[tabled(display(Password, "password_fmt"))] struct User { id: usize, pass: Password, mirrow: Password, } struct Password([u8; 4]); fn password_fmt(p: &Password) -> String { p.0.iter().sum::().to_string() } let data = [ User { id: 0, pass: Password([0, 1, 2, 3]), mirrow: Password([1, 1, 1, 1]), }, User { id: 1, pass: Password([1, 1, 2, 3]), mirrow: Password([2, 2, 1, 1]), }, ]; Table::new(data) }, "+----+------+--------+" "| id | pass | mirrow |" "+----+------+--------+" "| 0 | 6 | 4 |" "+----+------+--------+" "| 1 | 7 | 6 |" "+----+------+--------+" ); test_table!( test_display_type_args, { #[derive(Tabled)] #[tabled(display(Password, "password_fmt", self, 0))] struct User { id: usize, pass: Password, mirrow: Password, } struct Password([u8; 4]); fn password_fmt(p: &Password, _: &User, _: usize) -> String { p.0.iter().sum::().to_string() } let data = [ User { id: 0, pass: Password([0, 1, 2, 3]), mirrow: Password([1, 1, 1, 1]), }, User { id: 1, pass: Password([1, 1, 2, 3]), mirrow: Password([2, 2, 1, 1]), }, ]; Table::new(data) }, "+----+------+--------+" "| id | pass | mirrow |" "+----+------+--------+" "| 0 | 6 | 4 |" "+----+------+--------+" "| 1 | 7 | 6 |" "+----+------+--------+" ); test_table!( test_display_type_generic, { #[derive(Tabled)] #[tabled(display(Password, "password_fmt"))] struct User { id: usize, pass: Password, mirrow: Password, } struct Password([T; 4]); fn password_fmt(p: &Password) -> String where T: Display { p.0.iter().map(|s| s.to_string()).collect::>().join("-") } let data = [ User { id: 0, pass: Password([0, 1, 2, 3]), mirrow: Password([1, 1, 1, 1]), }, User { id: 1, pass: Password([1, 1, 2, 3]), mirrow: Password([2, 2, 1, 1]), }, ]; Table::new(data) }, "+----+---------+---------+" "| id | pass | mirrow |" "+----+---------+---------+" "| 0 | 0-1-2-3 | 1-1-1-1 |" "+----+---------+---------+" "| 1 | 1-1-2-3 | 2-2-1-1 |" "+----+---------+---------+" ); test_table!( test_display_type_generic_use, { #[derive(Tabled)] #[tabled(display(Password, "password_usize_fmt"))] #[tabled(display(Password, "password_u8_fmt"))] struct User { id: usize, pass: Password, mirrow: Password, } struct Password([T; 4]); fn password_usize_fmt(p: &Password) -> String { p.0.iter().sum::().to_string() } fn password_u8_fmt(p: &Password) -> String { p.0.iter().sum::().to_string() } let data = [ User { id: 0, pass: Password([0, 1, 2, 3]), mirrow: Password([1, 1, 1, 1]), }, User { id: 1, pass: Password([1, 1, 2, 3]), mirrow: Password([2, 2, 1, 1]), }, ]; Table::new(data) }, "+----+------+--------+" "| id | pass | mirrow |" "+----+------+--------+" "| 0 | 6 | 4 |" "+----+------+--------+" "| 1 | 7 | 6 |" "+----+------+--------+" ); #[cfg(test)] mod test_display_enum { use super::*; #[derive(Tabled)] pub enum User { #[tabled(display("some::bar::user_fmt"))] Public { id: usize }, #[tabled(display("user_fmt"))] Private { id: usize }, } fn user_fmt(_: &User) -> String { format!("...") } pub mod some { pub mod bar { pub fn user_fmt(_: &super::super::User) -> String { format!("111") } } } test_table!( test_display_enum, { let data = [ User::Public { id: 0 }, User::Private { id: 1 }, ]; Table::new(data) }, "+--------+---------+" "| Public | Private |" "+--------+---------+" "| 111 | |" "+--------+---------+" "| | ... |" "+--------+---------+" ); } // TODO: Shall we support a 'display_type' for #inlined structs?? // Seems like we have to? // But there's a small issue currently - we treat enum variants as field currently in a parsing stage. // And this particular case have no scense applied to struct field. // Soooo // Yes.. // // test_table!( // test_display_type_enum, // { // #[derive(Tabled)] // enum User { // #[tabled(inline)] // #[tabled(display(Password, "password_usize_fmt"))] // #[tabled(display(Password, "password_u8_fmt"))] // Public { // id: usize, // pass: Password, // mirrow: Password, // }, // #[tabled(display("user_fmt"))] // Private { // id: usize, // } // } // struct Password([T; 4]); // fn password_usize_fmt(p: &Password) -> String { // p.0.iter().sum::().to_string() // } // fn password_u8_fmt(p: &Password) -> String { // p.0.iter().sum::().to_string() // } // fn user_fmt(p: &User) -> String { // format!("...") // } // let data = [ // User::Public { // id: 0, // pass: Password([0, 1, 2, 3]), // mirrow: Password([1, 1, 1, 1]), // }, // User::Private { // id: 1, // }, // ]; // Table::new(data) // }, // "+----+------+--------+" // "| id | pass | mirrow |" // "+----+------+--------+" // "| 0 | 6 | 4 |" // "+----+------+--------+" // "| 1 | 7 | 6 |" // "+----+------+--------+" // ); tabled-0.18.0/tests/derive/mod.rs000064400000000000000000000000211046102023000147030ustar 00000000000000mod derive_test; tabled-0.18.0/tests/macros/col_row_test.rs000064400000000000000000000526101046102023000166500ustar 00000000000000#![cfg(feature = "macros")] #![cfg(feature = "std")] use tabled::{ col, row, settings::{format::Format, object::Segment, Alignment, Modify, Padding}, }; use crate::matrix::Matrix; use testing_table::test_table; test_table!( row_pair_test, row!( Matrix::new(3, 3) .with(Modify::new(Segment::all()).with(Alignment::left())) .with(Modify::new(Segment::all()).with(Padding::new(3, 1, 0, 0))) .with(Modify::new(Segment::all()).with(Format::content(|s| format!("[{s}]")))), Matrix::new(4, 4) .with(Modify::new(Segment::all()).with(Alignment::right())) .with(Modify::new(Segment::all()).with(Padding::new(1, 1, 1, 1))) .with(Modify::new(Segment::all()).with(Format::content(|s| format!("({s})")))), ), "+--------------------------------------------------------+-------------------------------------------------------------+" "| +-------+--------------+--------------+--------------+ | +-----+------------+------------+------------+------------+ |" "| | [N] | [column 0] | [column 1] | [column 2] | | | | | | | | |" "| +-------+--------------+--------------+--------------+ | | (N) | (column 0) | (column 1) | (column 2) | (column 3) | |" "| | [0] | [0-0] | [0-1] | [0-2] | | | | | | | | |" "| +-------+--------------+--------------+--------------+ | +-----+------------+------------+------------+------------+ |" "| | [1] | [1-0] | [1-1] | [1-2] | | | | | | | | |" "| +-------+--------------+--------------+--------------+ | | (0) | (0-0) | (0-1) | (0-2) | (0-3) | |" "| | [2] | [2-0] | [2-1] | [2-2] | | | | | | | | |" "| +-------+--------------+--------------+--------------+ | +-----+------------+------------+------------+------------+ |" "| | | | | | | | |" "| | | (1) | (1-0) | (1-1) | (1-2) | (1-3) | |" "| | | | | | | | |" "| | +-----+------------+------------+------------+------------+ |" "| | | | | | | | |" "| | | (2) | (2-0) | (2-1) | (2-2) | (2-3) | |" "| | | | | | | | |" "| | +-----+------------+------------+------------+------------+ |" "| | | | | | | | |" "| | | (3) | (3-0) | (3-1) | (3-2) | (3-3) | |" "| | | | | | | | |" "| | +-----+------------+------------+------------+------------+ |" "+--------------------------------------------------------+-------------------------------------------------------------+" ); test_table!( col_pair_test, col!( Matrix::new(3, 3) .with(Modify::new(Segment::all()).with(Alignment::left())) .with(Modify::new(Segment::all()).with(Padding::new(3, 1, 0, 0))) .with(Modify::new(Segment::all()).with(Format::content(|s| format!("[{s}]")))), Matrix::new(4, 4) .with(Modify::new(Segment::all()).with(Alignment::right())) .with(Modify::new(Segment::all()).with(Padding::new(1, 1, 1, 1))) .with(Modify::new(Segment::all()).with(Format::content(|s| format!("({s})")))), ), "+-------------------------------------------------------------+" "| +-------+--------------+--------------+--------------+ |" "| | [N] | [column 0] | [column 1] | [column 2] | |" "| +-------+--------------+--------------+--------------+ |" "| | [0] | [0-0] | [0-1] | [0-2] | |" "| +-------+--------------+--------------+--------------+ |" "| | [1] | [1-0] | [1-1] | [1-2] | |" "| +-------+--------------+--------------+--------------+ |" "| | [2] | [2-0] | [2-1] | [2-2] | |" "| +-------+--------------+--------------+--------------+ |" "+-------------------------------------------------------------+" "| +-----+------------+------------+------------+------------+ |" "| | | | | | | |" "| | (N) | (column 0) | (column 1) | (column 2) | (column 3) | |" "| | | | | | | |" "| +-----+------------+------------+------------+------------+ |" "| | | | | | | |" "| | (0) | (0-0) | (0-1) | (0-2) | (0-3) | |" "| | | | | | | |" "| +-----+------------+------------+------------+------------+ |" "| | | | | | | |" "| | (1) | (1-0) | (1-1) | (1-2) | (1-3) | |" "| | | | | | | |" "| +-----+------------+------------+------------+------------+ |" "| | | | | | | |" "| | (2) | (2-0) | (2-1) | (2-2) | (2-3) | |" "| | | | | | | |" "| +-----+------------+------------+------------+------------+ |" "| | | | | | | |" "| | (3) | (3-0) | (3-1) | (3-2) | (3-3) | |" "| | | | | | | |" "| +-----+------------+------------+------------+------------+ |" "+-------------------------------------------------------------+" ); test_table!( row_duplication_test, row!( Matrix::new(3, 3) .with(Modify::new(Segment::all()).with(Alignment::left())) .with(Modify::new(Segment::all()).with(Padding::new(3, 1, 0, 0))) .with(Modify::new(Segment::all()).with(Format::content(|s| format!("[{s}]")))); 3 ), "+--------------------------------------------------------+--------------------------------------------------------+--------------------------------------------------------+" "| +-------+--------------+--------------+--------------+ | +-------+--------------+--------------+--------------+ | +-------+--------------+--------------+--------------+ |" "| | [N] | [column 0] | [column 1] | [column 2] | | | [N] | [column 0] | [column 1] | [column 2] | | | [N] | [column 0] | [column 1] | [column 2] | |" "| +-------+--------------+--------------+--------------+ | +-------+--------------+--------------+--------------+ | +-------+--------------+--------------+--------------+ |" "| | [0] | [0-0] | [0-1] | [0-2] | | | [0] | [0-0] | [0-1] | [0-2] | | | [0] | [0-0] | [0-1] | [0-2] | |" "| +-------+--------------+--------------+--------------+ | +-------+--------------+--------------+--------------+ | +-------+--------------+--------------+--------------+ |" "| | [1] | [1-0] | [1-1] | [1-2] | | | [1] | [1-0] | [1-1] | [1-2] | | | [1] | [1-0] | [1-1] | [1-2] | |" "| +-------+--------------+--------------+--------------+ | +-------+--------------+--------------+--------------+ | +-------+--------------+--------------+--------------+ |" "| | [2] | [2-0] | [2-1] | [2-2] | | | [2] | [2-0] | [2-1] | [2-2] | | | [2] | [2-0] | [2-1] | [2-2] | |" "| +-------+--------------+--------------+--------------+ | +-------+--------------+--------------+--------------+ | +-------+--------------+--------------+--------------+ |" "+--------------------------------------------------------+--------------------------------------------------------+--------------------------------------------------------+" ); test_table!( col_and_rows_test, col!( row!( Matrix::new(3, 3) .with(Modify::new(Segment::all()).with(Alignment::left())) .with(Modify::new(Segment::all()).with(Padding::new(3, 1, 0, 0))) .with(Modify::new(Segment::all()).with(Format::content(|s| format!("[{s}]")))), Matrix::new(4, 4) .with(Modify::new(Segment::all()).with(Alignment::right())) .with(Modify::new(Segment::all()).with(Padding::new(1, 1, 1, 1))) .with(Modify::new(Segment::all()).with(Format::content(|s| format!("({s})")))), ), Matrix::new(3, 5) .with(Modify::new(Segment::all()).with(Alignment::left())) .with(Modify::new(Segment::all()).with(Padding::new(2, 2, 0, 0))) .with(Modify::new(Segment::all()).with(Format::content(|s| format!("[{s}]")))), ), "+--------------------------------------------------------------------------------------------------------------------------+" "| +--------------------------------------------------------+-------------------------------------------------------------+ |" "| | +-------+--------------+--------------+--------------+ | +-----+------------+------------+------------+------------+ | |" "| | | [N] | [column 0] | [column 1] | [column 2] | | | | | | | | | |" "| | +-------+--------------+--------------+--------------+ | | (N) | (column 0) | (column 1) | (column 2) | (column 3) | | |" "| | | [0] | [0-0] | [0-1] | [0-2] | | | | | | | | | |" "| | +-------+--------------+--------------+--------------+ | +-----+------------+------------+------------+------------+ | |" "| | | [1] | [1-0] | [1-1] | [1-2] | | | | | | | | | |" "| | +-------+--------------+--------------+--------------+ | | (0) | (0-0) | (0-1) | (0-2) | (0-3) | | |" "| | | [2] | [2-0] | [2-1] | [2-2] | | | | | | | | | |" "| | +-------+--------------+--------------+--------------+ | +-----+------------+------------+------------+------------+ | |" "| | | | | | | | | | |" "| | | | (1) | (1-0) | (1-1) | (1-2) | (1-3) | | |" "| | | | | | | | | | |" "| | | +-----+------------+------------+------------+------------+ | |" "| | | | | | | | | | |" "| | | | (2) | (2-0) | (2-1) | (2-2) | (2-3) | | |" "| | | | | | | | | | |" "| | | +-----+------------+------------+------------+------------+ | |" "| | | | | | | | | | |" "| | | | (3) | (3-0) | (3-1) | (3-2) | (3-3) | | |" "| | | | | | | | | | |" "| | | +-----+------------+------------+------------+------------+ | |" "| +--------------------------------------------------------+-------------------------------------------------------------+ |" "+--------------------------------------------------------------------------------------------------------------------------+" "| +-------+--------------+--------------+--------------+--------------+--------------+ |" "| | [N] | [column 0] | [column 1] | [column 2] | [column 3] | [column 4] | |" "| +-------+--------------+--------------+--------------+--------------+--------------+ |" "| | [0] | [0-0] | [0-1] | [0-2] | [0-3] | [0-4] | |" "| +-------+--------------+--------------+--------------+--------------+--------------+ |" "| | [1] | [1-0] | [1-1] | [1-2] | [1-3] | [1-4] | |" "| +-------+--------------+--------------+--------------+--------------+--------------+ |" "| | [2] | [2-0] | [2-1] | [2-2] | [2-3] | [2-4] | |" "| +-------+--------------+--------------+--------------+--------------+--------------+ |" "+--------------------------------------------------------------------------------------------------------------------------+" ); test_table!( row_and_col_test, row!( col!( Matrix::new(3, 3) .with(Modify::new(Segment::all()).with(Alignment::left())) .with(Modify::new(Segment::all()).with(Padding::new(3, 1, 0, 0))) .with(Modify::new(Segment::all()).with(Format::content(|s| format!("[{s}]")))), Matrix::new(4, 4) .with(Modify::new(Segment::all()).with(Alignment::right())) .with(Modify::new(Segment::all()).with(Padding::new(1, 1, 1, 1))) .with(Modify::new(Segment::all()).with(Format::content(|s| format!("({s})")))), ), Matrix::new(3, 5) .with(Modify::new(Segment::all()).with(Alignment::left())) .with(Modify::new(Segment::all()).with(Padding::new(2, 2, 0, 0))) .with(Modify::new(Segment::all()).with(Format::content(|s| format!("[{s}]")))), ), "+-----------------------------------------------------------------+--------------------------------------------------------------------------------------+" "| +-------------------------------------------------------------+ | +-------+--------------+--------------+--------------+--------------+--------------+ |" "| | +-------+--------------+--------------+--------------+ | | | [N] | [column 0] | [column 1] | [column 2] | [column 3] | [column 4] | |" "| | | [N] | [column 0] | [column 1] | [column 2] | | | +-------+--------------+--------------+--------------+--------------+--------------+ |" "| | +-------+--------------+--------------+--------------+ | | | [0] | [0-0] | [0-1] | [0-2] | [0-3] | [0-4] | |" "| | | [0] | [0-0] | [0-1] | [0-2] | | | +-------+--------------+--------------+--------------+--------------+--------------+ |" "| | +-------+--------------+--------------+--------------+ | | | [1] | [1-0] | [1-1] | [1-2] | [1-3] | [1-4] | |" "| | | [1] | [1-0] | [1-1] | [1-2] | | | +-------+--------------+--------------+--------------+--------------+--------------+ |" "| | +-------+--------------+--------------+--------------+ | | | [2] | [2-0] | [2-1] | [2-2] | [2-3] | [2-4] | |" "| | | [2] | [2-0] | [2-1] | [2-2] | | | +-------+--------------+--------------+--------------+--------------+--------------+ |" "| | +-------+--------------+--------------+--------------+ | | |" "| +-------------------------------------------------------------+ | |" "| | +-----+------------+------------+------------+------------+ | | |" "| | | | | | | | | | |" "| | | (N) | (column 0) | (column 1) | (column 2) | (column 3) | | | |" "| | | | | | | | | | |" "| | +-----+------------+------------+------------+------------+ | | |" "| | | | | | | | | | |" "| | | (0) | (0-0) | (0-1) | (0-2) | (0-3) | | | |" "| | | | | | | | | | |" "| | +-----+------------+------------+------------+------------+ | | |" "| | | | | | | | | | |" "| | | (1) | (1-0) | (1-1) | (1-2) | (1-3) | | | |" "| | | | | | | | | | |" "| | +-----+------------+------------+------------+------------+ | | |" "| | | | | | | | | | |" "| | | (2) | (2-0) | (2-1) | (2-2) | (2-3) | | | |" "| | | | | | | | | | |" "| | +-----+------------+------------+------------+------------+ | | |" "| | | | | | | | | | |" "| | | (3) | (3-0) | (3-1) | (3-2) | (3-3) | | | |" "| | | | | | | | | | |" "| | +-----+------------+------------+------------+------------+ | | |" "| +-------------------------------------------------------------+ | |" "+-----------------------------------------------------------------+--------------------------------------------------------------------------------------+" ); test_table!( row_str_test, row!("hello", "world"), "+-------+-------+" "| hello | world |" "+-------+-------+" ); test_table!( row_str_duplication_test, row!("duplicate me"; 5), "+--------------+--------------+--------------+--------------+--------------+" "| duplicate me | duplicate me | duplicate me | duplicate me | duplicate me |" "+--------------+--------------+--------------+--------------+--------------+" ); test_table!( row_display_mixed_test, row!("str", "string".to_string(), 55, false), "+-----+--------+----+-------+" "| str | string | 55 | false |" "+-----+--------+----+-------+" ); test_table!( col_display_mixed_test, col!("str", "string".to_string(), 55, false), "+--------+" "| str |" "+--------+" "| string |" "+--------+" "| 55 |" "+--------+" "| false |" "+--------+" ); tabled-0.18.0/tests/macros/mod.rs000064400000000000000000000000221046102023000147120ustar 00000000000000mod col_row_test; tabled-0.18.0/tests/main.rs000064400000000000000000000001251046102023000135770ustar 00000000000000mod core; mod derive; mod macros; mod settings; #[cfg(feature = "std")] mod matrix; tabled-0.18.0/tests/matrix/matrix.rs000064400000000000000000000075041046102023000154730ustar 00000000000000use std::{ fmt::{self, Display}, iter::FromIterator, string::ToString, }; use tabled::{ grid::config::ColoredConfig, grid::dimension::CompleteDimensionVecRecords, grid::records::vec_records::{Text, VecRecords}, settings::{object::Segment, Alignment, Modify, TableOption}, Table, Tabled, }; use super::matrix_list::MatrixList; /// A helper table factory. /// /// It uses center alignment by default, because it's more complex and may spot more issues. #[derive(Debug, Clone)] pub struct Matrix { data: Vec>, size: (usize, usize), } impl Matrix { pub fn empty() -> Self { Self { data: vec![], size: (0, 0), } } pub fn with_no_frame(rows: usize, columns: usize) -> Self { Self { data: create_matrix(rows, columns), size: (rows, columns), } } pub fn new(rows: usize, columns: usize) -> Self { Self::with_no_frame(rows, columns) .with_header() .with_index() } pub fn vec(rows: usize, columns: usize) -> Vec> { Self::new(rows, columns).to_vec() } pub fn table(rows: usize, columns: usize) -> Table { Self::new(rows, columns).to_table() } pub fn list() -> Vec> { create_list::() } pub fn iter(iter: I) -> Table where I: IntoIterator, T: Tabled, { let mut table = tabled::Table::new(iter); table.with(Modify::new(Segment::all()).with(Alignment::center())); table } pub fn with_index(mut self) -> Self { set_index(&mut self.data); self } pub fn with_header(mut self) -> Self { set_header(&mut self.data, self.size.1); self } pub fn insert(mut self, pos: tabled::grid::config::Position, value: V) -> Self { self.data[pos.row()][pos.col()] = value.to_string(); self } pub fn to_table(&self) -> Table { let mut table = tabled::Table::from_iter(self.data.clone()); table.with(Modify::new(Segment::all()).with(Alignment::center())); table } pub fn to_vec(&self) -> Vec> { self.data.clone() } pub fn with(self, opt: O) -> Table where for<'a> O: TableOption>, ColoredConfig, CompleteDimensionVecRecords<'a>>, { let mut table = self.to_table(); table.with(opt); table } } impl Display for Matrix { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { self.clone().to_table().fmt(f) } } fn create_matrix(rows: usize, columns: usize) -> Vec> { let mut arr = Vec::with_capacity(rows); for row in 0..rows { let mut data = Vec::with_capacity(columns); for column in 0..columns { let text = format!("{row}-{column}"); data.push(text); } arr.push(data); } arr } fn set_header(data: &mut Vec>, columns: usize) { data.insert( 0, (0..columns) .map(|n| format!("column {n}")) .collect::>(), ); } fn set_index(data: &mut [Vec]) { if data.is_empty() { return; } data[0].insert(0, "N".to_owned()); for (n, row) in data.iter_mut().skip(1).enumerate() { row.insert(0, n.to_string()); } } fn create_list() -> Vec> { let mut arr = Vec::with_capacity(ROWS); for row in 0..ROWS { let data = (0..COLUMNS) .map(|column| format!("{row}-{column}")) .collect::>(); let list = MatrixList::with_index(row, data); arr.push(list); } arr } tabled-0.18.0/tests/matrix/matrix_list.rs000064400000000000000000000026471046102023000165310ustar 00000000000000use std::{ borrow::Cow, iter::once, ops::{Index, IndexMut}, }; use tabled::Tabled; #[derive(Debug)] pub struct MatrixList { data: Vec, } impl MatrixList { #[allow(dead_code)] pub fn new(data: Vec) -> Self { Self { data } } } impl MatrixList { pub fn with_index(index: usize, mut data: Vec) -> Self { assert_eq!(data.len(), N); data.insert(0, index.to_string()); Self { data } } } impl Index for MatrixList { type Output = String; fn index(&self, index: usize) -> &Self::Output { &self.data[index] } } impl IndexMut for MatrixList { fn index_mut(&mut self, index: usize) -> &mut Self::Output { &mut self.data[index] } } impl Tabled for MatrixList { const LENGTH: usize = N + 1; fn fields(&self) -> Vec> { self.data.iter().cloned().map(Cow::Owned).collect() } fn headers() -> Vec> { let header = (0..N).map(|n| format!("column {n}")); match INDEX { true => once("N".to_owned()).chain(header).map(Cow::Owned).collect(), false => header.map(Cow::Owned).collect(), } } } tabled-0.18.0/tests/matrix/mod.rs000064400000000000000000000001721046102023000147400ustar 00000000000000#[allow(clippy::module_inception)] mod matrix; mod matrix_list; pub use matrix::Matrix; pub use matrix_list::MatrixList; tabled-0.18.0/tests/settings/alignment_test.rs000064400000000000000000000116121046102023000175330ustar 00000000000000#![cfg(feature = "std")] use tabled::settings::{ location::ByColumnName, object::{Columns, Rows, Segment}, Alignment, Modify, Padding, Style, }; use crate::matrix::Matrix; use testing_table::test_table; test_table!( full_alignment, Matrix::new(3, 3).with(Style::psql()).with(Modify::new(Segment::all()).with(Alignment::left())), " N | column 0 | column 1 | column 2 " "---+----------+----------+----------" " 0 | 0-0 | 0-1 | 0-2 " " 1 | 1-0 | 1-1 | 1-2 " " 2 | 2-0 | 2-1 | 2-2 " ); test_table!( head_and_data_alignment, Matrix::new(3, 3) .with(Modify::new(Rows::first()).with(Alignment::left())) .with(Modify::new(Rows::new(1..)).with(Alignment::right())), "+---+----------+----------+----------+" "| N | column 0 | column 1 | column 2 |" "+---+----------+----------+----------+" "| 0 | 0-0 | 0-1 | 0-2 |" "+---+----------+----------+----------+" "| 1 | 1-0 | 1-1 | 1-2 |" "+---+----------+----------+----------+" "| 2 | 2-0 | 2-1 | 2-2 |" "+---+----------+----------+----------+" ); test_table!( full_alignment_multiline, Matrix::new(3, 3).insert((3, 2).into(), "https://\nwww\n.\nredhat\n.com\n/en") .with(Style::psql()) .with(Modify::new(Segment::all()).with(Alignment::left())), " N | column 0 | column 1 | column 2 " "---+----------+----------+----------" " 0 | 0-0 | 0-1 | 0-2 " " 1 | 1-0 | 1-1 | 1-2 " " 2 | 2-0 | https:// | 2-2 " " | | www | " " | | . | " " | | redhat | " " | | .com | " " | | /en | " ); test_table!( vertical_alignment_test, Matrix::new(3, 3) .insert((2, 2).into(), "E\nnde\navou\nros") .insert((3, 2).into(), "Red\nHat") .insert((3, 3).into(), "https://\nwww\n.\nredhat\n.com\n/en") .with(Style::psql()) .with(Modify::new(Columns::new(1..)).with(Alignment::bottom())), " N | column 0 | column 1 | column 2 " "---+----------+----------+----------" " 0 | 0-0 | 0-1 | 0-2 " " 1 | | E | " " | | nde | " " | | avou | " " | 1-0 | ros | 1-2 " " 2 | | | https:// " " | | | www " " | | | . " " | | | redhat " " | | Red | .com " " | 2-0 | Hat | /en " ); test_table!( alignment_doesnt_change_padding, Matrix::new(3, 3) .with(Style::psql()) .with(Modify::new(Segment::all()).with(Padding::new(3, 0, 0, 0))) .with(Modify::new(Segment::all()).with(Alignment::left())), " N| column 0| column 1| column 2" "----+-----------+-----------+-----------" " 0| 0-0 | 0-1 | 0-2 " " 1| 1-0 | 1-1 | 1-2 " " 2| 2-0 | 2-1 | 2-2 " ); test_table!( alignment_global, Matrix::new(3, 3).with(Style::psql()).with(Alignment::right()), " N | column 0 | column 1 | column 2 " "---+----------+----------+----------" " 0 | 0-0 | 0-1 | 0-2 " " 1 | 1-0 | 1-1 | 1-2 " " 2 | 2-0 | 2-1 | 2-2 " ); test_table!( padding_by_column_name, Matrix::new(3, 3) .with(Style::psql()) .with(Modify::new(ByColumnName::new("column 0")).with(Padding::new(3, 3, 0, 0))) .with(Modify::new(Segment::all()).with(Alignment::center())), " N | column 0 | column 1 | column 2 " "---+--------------+----------+----------" " 0 | 0-0 | 0-1 | 0-2 " " 1 | 1-0 | 1-1 | 1-2 " " 2 | 2-0 | 2-1 | 2-2 " ); test_table!( padding_by_column_name_not_first_row, Matrix::new(3, 3) .with(Style::psql()) .with(Modify::new(ByColumnName::new("0-2")).with(Padding::new(3, 3, 0, 0))) .with(Modify::new(Segment::all()).with(Alignment::center())), " N | column 0 | column 1 | column 2 " "---+----------+----------+----------" " 0 | 0-0 | 0-1 | 0-2 " " 1 | 1-0 | 1-1 | 1-2 " " 2 | 2-0 | 2-1 | 2-2 " ); test_table!( padding_by_column_name_not_existing, Matrix::new(3, 3) .with(Style::psql()) .modify(ByColumnName::new("column 01123123"), Padding::new(3, 3, 0, 0)) .with(Alignment::center()), " N | column 0 | column 1 | column 2 " "---+----------+----------+----------" " 0 | 0-0 | 0-1 | 0-2 " " 1 | 1-0 | 1-1 | 1-2 " " 2 | 2-0 | 2-1 | 2-2 " ); tabled-0.18.0/tests/settings/color_test.rs000064400000000000000000000026741046102023000167030ustar 00000000000000#![cfg(feature = "std")] use tabled::settings::{Color, Modify}; use crate::matrix::Matrix; use testing_table::test_table; test_table!( color_global, Matrix::new(3, 3).with(Color::FG_MAGENTA), "+---+----------+----------+----------+" "| \u{1b}[35mN\u{1b}[39m | \u{1b}[35mcolumn 0\u{1b}[39m | \u{1b}[35mcolumn 1\u{1b}[39m | \u{1b}[35mcolumn 2\u{1b}[39m |" "+---+----------+----------+----------+" "| \u{1b}[35m0\u{1b}[39m | \u{1b}[35m0-0\u{1b}[39m | \u{1b}[35m0-1\u{1b}[39m | \u{1b}[35m0-2\u{1b}[39m |" "+---+----------+----------+----------+" "| \u{1b}[35m1\u{1b}[39m | \u{1b}[35m1-0\u{1b}[39m | \u{1b}[35m1-1\u{1b}[39m | \u{1b}[35m1-2\u{1b}[39m |" "+---+----------+----------+----------+" "| \u{1b}[35m2\u{1b}[39m | \u{1b}[35m2-0\u{1b}[39m | \u{1b}[35m2-1\u{1b}[39m | \u{1b}[35m2-2\u{1b}[39m |" "+---+----------+----------+----------+" ); test_table!( color_cell, Matrix::new(3, 3).with(Modify::new((0, 0)).with(Color::BG_BRIGHT_BLACK)), "+---+----------+----------+----------+" "| \u{1b}[100mN\u{1b}[49m | column 0 | column 1 | column 2 |" "+---+----------+----------+----------+" "| 0 | 0-0 | 0-1 | 0-2 |" "+---+----------+----------+----------+" "| 1 | 1-0 | 1-1 | 1-2 |" "+---+----------+----------+----------+" "| 2 | 2-0 | 2-1 | 2-2 |" "+---+----------+----------+----------+" ); tabled-0.18.0/tests/settings/colorization.rs000064400000000000000000000226441046102023000172410ustar 00000000000000#![cfg(feature = "std")] use tabled::settings::{ object::{Cell, Object}, themes::Colorization, Color, }; use crate::matrix::Matrix; use testing_table::test_table; test_table!( chess_2x3, Matrix::new(2, 3).with(Colorization::chess(color1(), color2())), "+---+----------+----------+----------+\n|\u{1b}[41m \u{1b}[49m\u{1b}[41mN\u{1b}[49m\u{1b}[41m \u{1b}[49m|\u{1b}[41m \u{1b}[49m\u{1b}[41mcolumn 0\u{1b}[49m\u{1b}[41m \u{1b}[49m|\u{1b}[106m \u{1b}[49m\u{1b}[106mcolumn 1\u{1b}[49m\u{1b}[106m \u{1b}[49m|\u{1b}[41m \u{1b}[49m\u{1b}[41mcolumn 2\u{1b}[49m\u{1b}[41m \u{1b}[49m|\n+---+----------+----------+----------+\n|\u{1b}[106m \u{1b}[49m\u{1b}[106m0\u{1b}[49m\u{1b}[106m \u{1b}[49m|\u{1b}[41m \u{1b}[49m\u{1b}[41m \u{1b}[49m\u{1b}[41m0-0\u{1b}[49m\u{1b}[41m \u{1b}[49m\u{1b}[41m \u{1b}[49m|\u{1b}[41m \u{1b}[49m\u{1b}[41m \u{1b}[49m\u{1b}[41m0-1\u{1b}[49m\u{1b}[41m \u{1b}[49m\u{1b}[41m \u{1b}[49m|\u{1b}[106m \u{1b}[49m\u{1b}[106m \u{1b}[49m\u{1b}[106m0-2\u{1b}[49m\u{1b}[106m \u{1b}[49m\u{1b}[106m \u{1b}[49m|\n+---+----------+----------+----------+\n|\u{1b}[41m \u{1b}[49m\u{1b}[41m1\u{1b}[49m\u{1b}[41m \u{1b}[49m|\u{1b}[106m \u{1b}[49m\u{1b}[106m \u{1b}[49m\u{1b}[106m1-0\u{1b}[49m\u{1b}[106m \u{1b}[49m\u{1b}[106m \u{1b}[49m|\u{1b}[41m \u{1b}[49m\u{1b}[41m \u{1b}[49m\u{1b}[41m1-1\u{1b}[49m\u{1b}[41m \u{1b}[49m\u{1b}[41m \u{1b}[49m|\u{1b}[41m \u{1b}[49m\u{1b}[41m \u{1b}[49m\u{1b}[41m1-2\u{1b}[49m\u{1b}[41m \u{1b}[49m\u{1b}[41m \u{1b}[49m|\n+---+----------+----------+----------+" ); test_table!( chess_3x3, Matrix::new(3, 3).with(Colorization::chess(color1(), color2())), "+---+----------+----------+----------+\n|\u{1b}[41m \u{1b}[49m\u{1b}[41mN\u{1b}[49m\u{1b}[41m \u{1b}[49m|\u{1b}[106m \u{1b}[49m\u{1b}[106mcolumn 0\u{1b}[49m\u{1b}[106m \u{1b}[49m|\u{1b}[41m \u{1b}[49m\u{1b}[41mcolumn 1\u{1b}[49m\u{1b}[41m \u{1b}[49m|\u{1b}[106m \u{1b}[49m\u{1b}[106mcolumn 2\u{1b}[49m\u{1b}[106m \u{1b}[49m|\n+---+----------+----------+----------+\n|\u{1b}[106m \u{1b}[49m\u{1b}[106m0\u{1b}[49m\u{1b}[106m \u{1b}[49m|\u{1b}[41m \u{1b}[49m\u{1b}[41m \u{1b}[49m\u{1b}[41m0-0\u{1b}[49m\u{1b}[41m \u{1b}[49m\u{1b}[41m \u{1b}[49m|\u{1b}[106m \u{1b}[49m\u{1b}[106m \u{1b}[49m\u{1b}[106m0-1\u{1b}[49m\u{1b}[106m \u{1b}[49m\u{1b}[106m \u{1b}[49m|\u{1b}[41m \u{1b}[49m\u{1b}[41m \u{1b}[49m\u{1b}[41m0-2\u{1b}[49m\u{1b}[41m \u{1b}[49m\u{1b}[41m \u{1b}[49m|\n+---+----------+----------+----------+\n|\u{1b}[41m \u{1b}[49m\u{1b}[41m1\u{1b}[49m\u{1b}[41m \u{1b}[49m|\u{1b}[106m \u{1b}[49m\u{1b}[106m \u{1b}[49m\u{1b}[106m1-0\u{1b}[49m\u{1b}[106m \u{1b}[49m\u{1b}[106m \u{1b}[49m|\u{1b}[41m \u{1b}[49m\u{1b}[41m \u{1b}[49m\u{1b}[41m1-1\u{1b}[49m\u{1b}[41m \u{1b}[49m\u{1b}[41m \u{1b}[49m|\u{1b}[106m \u{1b}[49m\u{1b}[106m \u{1b}[49m\u{1b}[106m1-2\u{1b}[49m\u{1b}[106m \u{1b}[49m\u{1b}[106m \u{1b}[49m|\n+---+----------+----------+----------+\n|\u{1b}[106m \u{1b}[49m\u{1b}[106m2\u{1b}[49m\u{1b}[106m \u{1b}[49m|\u{1b}[41m \u{1b}[49m\u{1b}[41m \u{1b}[49m\u{1b}[41m2-0\u{1b}[49m\u{1b}[41m \u{1b}[49m\u{1b}[41m \u{1b}[49m|\u{1b}[106m \u{1b}[49m\u{1b}[106m \u{1b}[49m\u{1b}[106m2-1\u{1b}[49m\u{1b}[106m \u{1b}[49m\u{1b}[106m \u{1b}[49m|\u{1b}[41m \u{1b}[49m\u{1b}[41m \u{1b}[49m\u{1b}[41m2-2\u{1b}[49m\u{1b}[41m \u{1b}[49m\u{1b}[41m \u{1b}[49m|\n+---+----------+----------+----------+" ); test_table!( rows, Matrix::new(2, 3).with(Colorization::rows([color1(), color2(), color3()])), "+---+----------+----------+----------+\n|\u{1b}[41m \u{1b}[49m\u{1b}[41mN\u{1b}[49m\u{1b}[41m \u{1b}[49m|\u{1b}[41m \u{1b}[49m\u{1b}[41mcolumn 0\u{1b}[49m\u{1b}[41m \u{1b}[49m|\u{1b}[41m \u{1b}[49m\u{1b}[41mcolumn 1\u{1b}[49m\u{1b}[41m \u{1b}[49m|\u{1b}[41m \u{1b}[49m\u{1b}[41mcolumn 2\u{1b}[49m\u{1b}[41m \u{1b}[49m|\n+---+----------+----------+----------+\n|\u{1b}[106m \u{1b}[49m\u{1b}[106m0\u{1b}[49m\u{1b}[106m \u{1b}[49m|\u{1b}[106m \u{1b}[49m\u{1b}[106m \u{1b}[49m\u{1b}[106m0-0\u{1b}[49m\u{1b}[106m \u{1b}[49m\u{1b}[106m \u{1b}[49m|\u{1b}[106m \u{1b}[49m\u{1b}[106m \u{1b}[49m\u{1b}[106m0-1\u{1b}[49m\u{1b}[106m \u{1b}[49m\u{1b}[106m \u{1b}[49m|\u{1b}[106m \u{1b}[49m\u{1b}[106m \u{1b}[49m\u{1b}[106m0-2\u{1b}[49m\u{1b}[106m \u{1b}[49m\u{1b}[106m \u{1b}[49m|\n+---+----------+----------+----------+\n|\u{1b}[1m \u{1b}[22m\u{1b}[1m1\u{1b}[22m\u{1b}[1m \u{1b}[22m|\u{1b}[1m \u{1b}[22m\u{1b}[1m \u{1b}[22m\u{1b}[1m1-0\u{1b}[22m\u{1b}[1m \u{1b}[22m\u{1b}[1m \u{1b}[22m|\u{1b}[1m \u{1b}[22m\u{1b}[1m \u{1b}[22m\u{1b}[1m1-1\u{1b}[22m\u{1b}[1m \u{1b}[22m\u{1b}[1m \u{1b}[22m|\u{1b}[1m \u{1b}[22m\u{1b}[1m \u{1b}[22m\u{1b}[1m1-2\u{1b}[22m\u{1b}[1m \u{1b}[22m\u{1b}[1m \u{1b}[22m|\n+---+----------+----------+----------+" ); test_table!( columns, Matrix::new(2, 3).with(Colorization::columns([color1(), color2(), color3()])), "+---+----------+----------+----------+\n|\u{1b}[41m \u{1b}[49m\u{1b}[41mN\u{1b}[49m\u{1b}[41m \u{1b}[49m|\u{1b}[106m \u{1b}[49m\u{1b}[106mcolumn 0\u{1b}[49m\u{1b}[106m \u{1b}[49m|\u{1b}[1m \u{1b}[22m\u{1b}[1mcolumn 1\u{1b}[22m\u{1b}[1m \u{1b}[22m|\u{1b}[41m \u{1b}[49m\u{1b}[41mcolumn 2\u{1b}[49m\u{1b}[41m \u{1b}[49m|\n+---+----------+----------+----------+\n|\u{1b}[41m \u{1b}[49m\u{1b}[41m0\u{1b}[49m\u{1b}[41m \u{1b}[49m|\u{1b}[106m \u{1b}[49m\u{1b}[106m \u{1b}[49m\u{1b}[106m0-0\u{1b}[49m\u{1b}[106m \u{1b}[49m\u{1b}[106m \u{1b}[49m|\u{1b}[1m \u{1b}[22m\u{1b}[1m \u{1b}[22m\u{1b}[1m0-1\u{1b}[22m\u{1b}[1m \u{1b}[22m\u{1b}[1m \u{1b}[22m|\u{1b}[41m \u{1b}[49m\u{1b}[41m \u{1b}[49m\u{1b}[41m0-2\u{1b}[49m\u{1b}[41m \u{1b}[49m\u{1b}[41m \u{1b}[49m|\n+---+----------+----------+----------+\n|\u{1b}[41m \u{1b}[49m\u{1b}[41m1\u{1b}[49m\u{1b}[41m \u{1b}[49m|\u{1b}[106m \u{1b}[49m\u{1b}[106m \u{1b}[49m\u{1b}[106m1-0\u{1b}[49m\u{1b}[106m \u{1b}[49m\u{1b}[106m \u{1b}[49m|\u{1b}[1m \u{1b}[22m\u{1b}[1m \u{1b}[22m\u{1b}[1m1-1\u{1b}[22m\u{1b}[1m \u{1b}[22m\u{1b}[1m \u{1b}[22m|\u{1b}[41m \u{1b}[49m\u{1b}[41m \u{1b}[49m\u{1b}[41m1-2\u{1b}[49m\u{1b}[41m \u{1b}[49m\u{1b}[41m \u{1b}[49m|\n+---+----------+----------+----------+" ); test_table!( by_row, Matrix::new(2, 3).with(Colorization::by_row([color1(), color2(), color3()])), "+---+----------+----------+----------+\n|\u{1b}[41m \u{1b}[49m\u{1b}[41mN\u{1b}[49m\u{1b}[41m \u{1b}[49m|\u{1b}[106m \u{1b}[49m\u{1b}[106mcolumn 0\u{1b}[49m\u{1b}[106m \u{1b}[49m|\u{1b}[1m \u{1b}[22m\u{1b}[1mcolumn 1\u{1b}[22m\u{1b}[1m \u{1b}[22m|\u{1b}[41m \u{1b}[49m\u{1b}[41mcolumn 2\u{1b}[49m\u{1b}[41m \u{1b}[49m|\n+---+----------+----------+----------+\n|\u{1b}[106m \u{1b}[49m\u{1b}[106m0\u{1b}[49m\u{1b}[106m \u{1b}[49m|\u{1b}[1m \u{1b}[22m\u{1b}[1m \u{1b}[22m\u{1b}[1m0-0\u{1b}[22m\u{1b}[1m \u{1b}[22m\u{1b}[1m \u{1b}[22m|\u{1b}[41m \u{1b}[49m\u{1b}[41m \u{1b}[49m\u{1b}[41m0-1\u{1b}[49m\u{1b}[41m \u{1b}[49m\u{1b}[41m \u{1b}[49m|\u{1b}[106m \u{1b}[49m\u{1b}[106m \u{1b}[49m\u{1b}[106m0-2\u{1b}[49m\u{1b}[106m \u{1b}[49m\u{1b}[106m \u{1b}[49m|\n+---+----------+----------+----------+\n|\u{1b}[1m \u{1b}[22m\u{1b}[1m1\u{1b}[22m\u{1b}[1m \u{1b}[22m|\u{1b}[41m \u{1b}[49m\u{1b}[41m \u{1b}[49m\u{1b}[41m1-0\u{1b}[49m\u{1b}[41m \u{1b}[49m\u{1b}[41m \u{1b}[49m|\u{1b}[106m \u{1b}[49m\u{1b}[106m \u{1b}[49m\u{1b}[106m1-1\u{1b}[49m\u{1b}[106m \u{1b}[49m\u{1b}[106m \u{1b}[49m|\u{1b}[1m \u{1b}[22m\u{1b}[1m \u{1b}[22m\u{1b}[1m1-2\u{1b}[22m\u{1b}[1m \u{1b}[22m\u{1b}[1m \u{1b}[22m|\n+---+----------+----------+----------+" ); test_table!( by_column, Matrix::new(2, 3).with(Colorization::by_column([color1(), color2(), color3()])), "+---+----------+----------+----------+\n|\u{1b}[41m \u{1b}[49m\u{1b}[41mN\u{1b}[49m\u{1b}[41m \u{1b}[49m|\u{1b}[41m \u{1b}[49m\u{1b}[41mcolumn 0\u{1b}[49m\u{1b}[41m \u{1b}[49m|\u{1b}[41m \u{1b}[49m\u{1b}[41mcolumn 1\u{1b}[49m\u{1b}[41m \u{1b}[49m|\u{1b}[41m \u{1b}[49m\u{1b}[41mcolumn 2\u{1b}[49m\u{1b}[41m \u{1b}[49m|\n+---+----------+----------+----------+\n|\u{1b}[106m \u{1b}[49m\u{1b}[106m0\u{1b}[49m\u{1b}[106m \u{1b}[49m|\u{1b}[106m \u{1b}[49m\u{1b}[106m \u{1b}[49m\u{1b}[106m0-0\u{1b}[49m\u{1b}[106m \u{1b}[49m\u{1b}[106m \u{1b}[49m|\u{1b}[106m \u{1b}[49m\u{1b}[106m \u{1b}[49m\u{1b}[106m0-1\u{1b}[49m\u{1b}[106m \u{1b}[49m\u{1b}[106m \u{1b}[49m|\u{1b}[106m \u{1b}[49m\u{1b}[106m \u{1b}[49m\u{1b}[106m0-2\u{1b}[49m\u{1b}[106m \u{1b}[49m\u{1b}[106m \u{1b}[49m|\n+---+----------+----------+----------+\n|\u{1b}[1m \u{1b}[22m\u{1b}[1m1\u{1b}[22m\u{1b}[1m \u{1b}[22m|\u{1b}[1m \u{1b}[22m\u{1b}[1m \u{1b}[22m\u{1b}[1m1-0\u{1b}[22m\u{1b}[1m \u{1b}[22m\u{1b}[1m \u{1b}[22m|\u{1b}[1m \u{1b}[22m\u{1b}[1m \u{1b}[22m\u{1b}[1m1-1\u{1b}[22m\u{1b}[1m \u{1b}[22m\u{1b}[1m \u{1b}[22m|\u{1b}[1m \u{1b}[22m\u{1b}[1m \u{1b}[22m\u{1b}[1m1-2\u{1b}[22m\u{1b}[1m \u{1b}[22m\u{1b}[1m \u{1b}[22m|\n+---+----------+----------+----------+" ); test_table!( exact, Matrix::new(2, 3).with(Colorization::exact([color1(), color2(), color3()], Cell::new(0, 0).and(Cell::new(1, 1)).and(Cell::new(2, 2)))), "+---+----------+----------+----------+\n|\u{1b}[41m \u{1b}[49m\u{1b}[41mN\u{1b}[49m\u{1b}[41m \u{1b}[49m| column 0 | column 1 | column 2 |\n+---+----------+----------+----------+\n| 0 |\u{1b}[106m \u{1b}[49m\u{1b}[106m \u{1b}[49m\u{1b}[106m0-0\u{1b}[49m\u{1b}[106m \u{1b}[49m\u{1b}[106m \u{1b}[49m| 0-1 | 0-2 |\n+---+----------+----------+----------+\n| 1 | 1-0 |\u{1b}[1m \u{1b}[22m\u{1b}[1m \u{1b}[22m\u{1b}[1m1-1\u{1b}[22m\u{1b}[1m \u{1b}[22m\u{1b}[1m \u{1b}[22m| 1-2 |\n+---+----------+----------+----------+" ); fn color1() -> Color { Color::BG_RED } fn color2() -> Color { Color::BG_BRIGHT_CYAN } fn color3() -> Color { Color::BOLD } tabled-0.18.0/tests/settings/column_names_test.rs000064400000000000000000000275341046102023000202470ustar 00000000000000#![cfg(feature = "std")] use tabled::{ settings::{themes::ColumnNames, Alignment, Color, Padding}, Table, }; use crate::matrix::Matrix; use testing_table::test_table; test_table!( new, Matrix::new(3, 3).with(ColumnNames::new(["1", "2", "3", "4"])), "+1--+2---------+3---------+4---------+" "| N | column 0 | column 1 | column 2 |" "+---+----------+----------+----------+" "| 0 | 0-0 | 0-1 | 0-2 |" "+---+----------+----------+----------+" "| 1 | 1-0 | 1-1 | 1-2 |" "+---+----------+----------+----------+" "| 2 | 2-0 | 2-1 | 2-2 |" "+---+----------+----------+----------+" ); test_table!( new_more_names_then_columns, Matrix::new(3, 3).with(ColumnNames::new(["1", "2", "3", "4", "5", "6", "7"])), "+1--+2---------+3---------+4---------+" "| N | column 0 | column 1 | column 2 |" "+---+----------+----------+----------+" "| 0 | 0-0 | 0-1 | 0-2 |" "+---+----------+----------+----------+" "| 1 | 1-0 | 1-1 | 1-2 |" "+---+----------+----------+----------+" "| 2 | 2-0 | 2-1 | 2-2 |" "+---+----------+----------+----------+" ); test_table!( new_less_names_then_columns, Matrix::new(3, 3).with(ColumnNames::new(["1", "2"])), "+1--+2---------+----------+----------+" "| N | column 0 | column 1 | column 2 |" "+---+----------+----------+----------+" "| 0 | 0-0 | 0-1 | 0-2 |" "+---+----------+----------+----------+" "| 1 | 1-0 | 1-1 | 1-2 |" "+---+----------+----------+----------+" "| 2 | 2-0 | 2-1 | 2-2 |" "+---+----------+----------+----------+" ); test_table!( new_empty, Matrix::new(3, 3).with(ColumnNames::new([""; 0])), "+---+----------+----------+----------+" "| N | column 0 | column 1 | column 2 |" "+---+----------+----------+----------+" "| 0 | 0-0 | 0-1 | 0-2 |" "+---+----------+----------+----------+" "| 1 | 1-0 | 1-1 | 1-2 |" "+---+----------+----------+----------+" "| 2 | 2-0 | 2-1 | 2-2 |" "+---+----------+----------+----------+" ); test_table!( default, Matrix::new(3, 3).with(ColumnNames::default()), "+N--+column 0+column 1+column 2+" "| 0 | 0-0 | 0-1 | 0-2 |" "+---+--------+--------+--------+" "| 1 | 1-0 | 1-1 | 1-2 |" "+---+--------+--------+--------+" "| 2 | 2-0 | 2-1 | 2-2 |" "+---+--------+--------+--------+" ); test_table!( alignment_left, Table::new([("Hello", "World"), ("and", "looooong\nword")]) .with(ColumnNames::default().alignment(Alignment::left())), "+&str---+&str------+" "| Hello | World |" "+-------+----------+" "| and | looooong |" "| | word |" "+-------+----------+" ); test_table!( alignment_right, Table::new([("Hello", "World"), ("and", "looooong\nword")]) .with(ColumnNames::default().alignment(Alignment::right())), "+---&str+------&str+" "| Hello | World |" "+-------+----------+" "| and | looooong |" "| | word |" "+-------+----------+" ); test_table!( alignment_center, Table::new([("Hello", "World"), ("and", "looooong\nword")]) .with(ColumnNames::default().alignment(Alignment::center())), "+-&str--+---&str---+" "| Hello | World |" "+-------+----------+" "| and | looooong |" "| | word |" "+-------+----------+" ); test_table!( alignment_center_long, Table::new([("Hello", "World"), ("and", "looooong\nword")]) .with(ColumnNames::new(["&&&&&&&str", "&&&&&&&str"]).alignment(Alignment::center())), "+&&&&&&&str+&&&&&&&str+" "| &str | &str |" "+----------+----------+" "| Hello | World |" "+----------+----------+" "| and | looooong |" "| | word |" "+----------+----------+" ); test_table!( alignment_array, Table::new([("Hello", "World"), ("and", "looooong\nword")]) .with(ColumnNames::default().alignment(vec![Alignment::right(), Alignment::center()])), "+---&str+---&str---+" "| Hello | World |" "+-------+----------+" "| and | looooong |" "| | word |" "+-------+----------+" ); test_table!( line, Matrix::new(3, 3).with(ColumnNames::default().line(1)), "+---+--------+--------+--------+" "| 0 | 0-0 | 0-1 | 0-2 |" "+N--+column 0+column 1+column 2+" "| 1 | 1-0 | 1-1 | 1-2 |" "+---+--------+--------+--------+" "| 2 | 2-0 | 2-1 | 2-2 |" "+---+--------+--------+--------+" ); test_table!( line_max_out, Matrix::new(3, 3).with(ColumnNames::default().line(100)), "+---+----------+----------+----------+" "| N | column 0 | column 1 | column 2 |" "+---+----------+----------+----------+" "| 0 | 0-0 | 0-1 | 0-2 |" "+---+----------+----------+----------+" "| 1 | 1-0 | 1-1 | 1-2 |" "+---+----------+----------+----------+" "| 2 | 2-0 | 2-1 | 2-2 |" "+---+----------+----------+----------+" ); test_table!( line_0, Matrix::new(3, 3).with(ColumnNames::default().line(0)), "+N--+column 0+column 1+column 2+" "| 0 | 0-0 | 0-1 | 0-2 |" "+---+--------+--------+--------+" "| 1 | 1-0 | 1-1 | 1-2 |" "+---+--------+--------+--------+" "| 2 | 2-0 | 2-1 | 2-2 |" "+---+--------+--------+--------+" ); test_table!( colors_some_some, Table::new([("Hello", "World"), ("and", "looooong\nword")]) .with(ColumnNames::default().color(vec![Color::BG_BLACK, Color::BG_BLUE])), "+\u{1b}[40m&\u{1b}[49m\u{1b}[40ms\u{1b}[49m\u{1b}[40mt\u{1b}[49m\u{1b}[40mr\u{1b}[49m---+\u{1b}[44m&\u{1b}[49m\u{1b}[44ms\u{1b}[49m\u{1b}[44mt\u{1b}[49m\u{1b}[44mr\u{1b}[49m------+" "| Hello | World |" "+-------+----------+" "| and | looooong |" "| | word |" "+-------+----------+" ); test_table!( colors_none_some, Table::new([("Hello", "World"), ("and", "looooong\nword")]) .with(ColumnNames::default().color(vec![Color::default(), Color::BG_BLUE])), "+&str---+\u{1b}[44m&\u{1b}[49m\u{1b}[44ms\u{1b}[49m\u{1b}[44mt\u{1b}[49m\u{1b}[44mr\u{1b}[49m------+" "| Hello | World |" "+-------+----------+" "| and | looooong |" "| | word |" "+-------+----------+" ); test_table!( colors_none_none, Table::new([("Hello", "World"), ("and", "looooong\nword")]) .with(ColumnNames::default().color(vec![Color::default(), Color::default()])), "+&str---+&str------+" "| Hello | World |" "+-------+----------+" "| and | looooong |" "| | word |" "+-------+----------+" ); test_table!( colors_empty, Table::new([("Hello", "World"), ("and", "looooong\nword")]) .with(ColumnNames::default().color({ Color::default(); vec![] as std::vec::Vec })), "+&str---+&str------+" "| Hello | World |" "+-------+----------+" "| and | looooong |" "| | word |" "+-------+----------+" ); test_table!( new_vertical, Matrix::new(3, 3).with(ColumnNames::new(["1", "2", "3", "4"]).alignment(Alignment::top())), "+---+----------+----------+----------+" "1 N | column 0 | column 1 | column 2 |" "+---+----------+----------+----------+" "2 0 | 0-0 | 0-1 | 0-2 |" "+---+----------+----------+----------+" "3 1 | 1-0 | 1-1 | 1-2 |" "+---+----------+----------+----------+" "4 2 | 2-0 | 2-1 | 2-2 |" "+---+----------+----------+----------+" ); test_table!( new_vertical_1, Matrix::new(2, 2).with(Padding::new(1, 1, 2, 2)).with(ColumnNames::new(["1", "2", "3", "4"]).alignment(Alignment::top())), "+---+----------+----------+" "1 | | |" "| | | |" "| N | column 0 | column 1 |" "| | | |" "| | | |" "+---+----------+----------+" "2 | | |" "| | | |" "| 0 | 0-0 | 0-1 |" "| | | |" "| | | |" "+---+----------+----------+" "3 | | |" "| | | |" "| 1 | 1-0 | 1-1 |" "| | | |" "| | | |" "+---+----------+----------+" ); test_table!( new_vertical_2, Matrix::new(2, 2).with(Padding::new(1, 1, 2, 2)).with(ColumnNames::new(["1", "2", "3", "4"]).alignment(Alignment::bottom())), "+---+----------+----------+" "| | | |" "| | | |" "| N | column 0 | column 1 |" "| | | |" "1 | | |" "+---+----------+----------+" "| | | |" "| | | |" "| 0 | 0-0 | 0-1 |" "| | | |" "2 | | |" "+---+----------+----------+" "| | | |" "| | | |" "| 1 | 1-0 | 1-1 |" "| | | |" "3 | | |" "+---+----------+----------+" ); test_table!( new_vertical_3, Matrix::new(2, 2).with(Padding::new(1, 1, 2, 2)).with(ColumnNames::new(["1", "2", "3", "4"]).alignment(Alignment::center_vertical())), "+---+----------+----------+" "| | | |" "| | | |" "1 N | column 0 | column 1 |" "| | | |" "| | | |" "+---+----------+----------+" "| | | |" "| | | |" "2 0 | 0-0 | 0-1 |" "| | | |" "| | | |" "+---+----------+----------+" "| | | |" "| | | |" "3 1 | 1-0 | 1-1 |" "| | | |" "| | | |" "+---+----------+----------+" ); test_table!( new_vertical_default_0, Matrix::new(2, 2).with(Padding::new(1, 1, 2, 2)).with(ColumnNames::default().alignment(Alignment::top())), "+---+-----+-----+" "N | | |" "| | | |" "| 0 | 0-0 | 0-1 |" "| | | |" "| | | |" "+---+-----+-----+" "c | | |" "o | | |" "l 1 | 1-0 | 1-1 |" "u | | |" "m | | |" "n | | |" " | | |" "0 | | |" "+---+-----+-----+" ); test_table!( new_vertical_default_2, Matrix::new(2, 2).with(Padding::new(1, 1, 2, 2)).with(ColumnNames::default().alignment(Alignment::bottom())), "+---+-----+-----+" "| | | |" "| | | |" "| 0 | 0-0 | 0-1 |" "| | | |" "N | | |" "+---+-----+-----+" "c | | |" "o | | |" "l 1 | 1-0 | 1-1 |" "u | | |" "m | | |" "n | | |" " | | |" "0 | | |" "+---+-----+-----+" ); test_table!( new_vertical_default_1, Matrix::new(2, 2).with(Padding::new(1, 1, 5, 5)).with(ColumnNames::default().alignment(Alignment::center_vertical())), "+---+-----+-----+" "| | | |" "| | | |" "| | | |" "| | | |" "| | | |" "N 0 | 0-0 | 0-1 |" "| | | |" "| | | |" "| | | |" "| | | |" "| | | |" "+---+-----+-----+" "| | | |" "c | | |" "o | | |" "l | | |" "u | | |" "m 1 | 1-0 | 1-1 |" "n | | |" " | | |" "0 | | |" "| | | |" "| | | |" "+---+-----+-----+" ); tabled-0.18.0/tests/settings/concat_test.rs000064400000000000000000000125601046102023000170270ustar 00000000000000#![cfg(feature = "std")] use tabled::settings::{Concat, Style}; use crate::matrix::Matrix; use testing_table::test_table; test_table!( join_vertical_0, Matrix::new(2, 3).insert((1, 0).into(), "123").with(Style::psql()) .with(Concat::vertical(Matrix::new(2, 3).to_table())) .to_string(), " N | column 0 | column 1 | column 2 " "-----+----------+----------+----------" " 123 | 0-0 | 0-1 | 0-2 " " 1 | 1-0 | 1-1 | 1-2 " " N | column 0 | column 1 | column 2 " " 0 | 0-0 | 0-1 | 0-2 " " 1 | 1-0 | 1-1 | 1-2 " ); test_table!( join_vertical_1, Matrix::new(2, 3) .with(Concat::vertical(Matrix::new(2, 3).insert((1, 0).into(), "123").with(Style::psql()))), "+-----+----------+----------+----------+" "| N | column 0 | column 1 | column 2 |" "+-----+----------+----------+----------+" "| 0 | 0-0 | 0-1 | 0-2 |" "+-----+----------+----------+----------+" "| 1 | 1-0 | 1-1 | 1-2 |" "+-----+----------+----------+----------+" "| N | column 0 | column 1 | column 2 |" "+-----+----------+----------+----------+" "| 123 | 0-0 | 0-1 | 0-2 |" "+-----+----------+----------+----------+" "| 1 | 1-0 | 1-1 | 1-2 |" "+-----+----------+----------+----------+" ); test_table!( join_horizontal_0, { let mut table1 = Matrix::table(2, 3); table1.with(Style::ascii()); let mut table2 = Matrix::table(2, 3); table2.with(Style::psql()); table2.with(Concat::horizontal(table1)).to_string() }, " N | column 0 | column 1 | column 2 | N | column 0 | column 1 | column 2 " "---+----------+----------+----------+---+----------+----------+----------" " 0 | 0-0 | 0-1 | 0-2 | 0 | 0-0 | 0-1 | 0-2 " " 1 | 1-0 | 1-1 | 1-2 | 1 | 1-0 | 1-1 | 1-2 " ); test_table!( join_horizontal_1, { let mut table1 = Matrix::table(2, 3); table1.with(Style::ascii()); let mut table2 = Matrix::table(2, 3); table2.with(Style::psql()); table1.with(Concat::horizontal(table2)).to_string() }, "+---+----------+----------+----------+---+----------+----------+----------+" "| N | column 0 | column 1 | column 2 | N | column 0 | column 1 | column 2 |" "+---+----------+----------+----------+---+----------+----------+----------+" "| 0 | 0-0 | 0-1 | 0-2 | 0 | 0-0 | 0-1 | 0-2 |" "+---+----------+----------+----------+---+----------+----------+----------+" "| 1 | 1-0 | 1-1 | 1-2 | 1 | 1-0 | 1-1 | 1-2 |" "+---+----------+----------+----------+---+----------+----------+----------+" ); test_table!( join_vertical_different_size, { let mut table1 = Matrix::table(2, 2); table1.with(Style::psql()); let mut table2 = Matrix::table(2, 3); table2.with(Style::psql()); table1.with(Concat::vertical(table2)).to_string() }, " N | column 0 | column 1 | " "---+----------+----------+----------" " 0 | 0-0 | 0-1 | " " 1 | 1-0 | 1-1 | " " N | column 0 | column 1 | column 2 " " 0 | 0-0 | 0-1 | 0-2 " " 1 | 1-0 | 1-1 | 1-2 " ); test_table!( join_horizontal_different_size, { let mut table1 = Matrix::table(2, 3); table1.with(Style::psql()); let mut table2 = Matrix::table(3, 3); table2.with(Style::psql()); table1.with(Concat::horizontal(table2)).to_string() }, " N | column 0 | column 1 | column 2 | N | column 0 | column 1 | column 2 " "---+----------+----------+----------+---+----------+----------+----------" " 0 | 0-0 | 0-1 | 0-2 | 0 | 0-0 | 0-1 | 0-2 " " 1 | 1-0 | 1-1 | 1-2 | 1 | 1-0 | 1-1 | 1-2 " " | | | | 2 | 2-0 | 2-1 | 2-2 " ); test_table!( join_horizontal_with_not_default_empty_string, { let mut table1 = Matrix::table(2, 3); table1.with(Style::psql()); let mut table2 = Matrix::table(3, 3); table2.with(Style::psql()); table1.with(Concat::horizontal(table2).default_cell("NaN")).to_string() }, " N | column 0 | column 1 | column 2 | N | column 0 | column 1 | column 2 " "-----+----------+----------+----------+---+----------+----------+----------" " 0 | 0-0 | 0-1 | 0-2 | 0 | 0-0 | 0-1 | 0-2 " " 1 | 1-0 | 1-1 | 1-2 | 1 | 1-0 | 1-1 | 1-2 " " NaN | NaN | NaN | NaN | 2 | 2-0 | 2-1 | 2-2 " ); test_table!( join_vertical_with_not_default_empty_string, { let mut table1 = Matrix::table(2, 2); table1.with(Style::psql()); let mut table2 = Matrix::table(2, 3); table2.with(Style::psql()); table1.with(Concat::vertical(table2).default_cell("NaN")).to_string() }, " N | column 0 | column 1 | NaN " "---+----------+----------+----------" " 0 | 0-0 | 0-1 | NaN " " 1 | 1-0 | 1-1 | NaN " " N | column 0 | column 1 | column 2 " " 0 | 0-0 | 0-1 | 0-2 " " 1 | 1-0 | 1-1 | 1-2 " ); tabled-0.18.0/tests/settings/disable_test.rs000064400000000000000000000047041046102023000171640ustar 00000000000000#![cfg(feature = "std")] use tabled::settings::{ location::ByColumnName, object::{Columns, Rows, Segment}, style::{HorizontalLine, Style}, Alignment, Modify, Remove, }; use crate::matrix::Matrix; use testing_table::test_table; test_table!( disable_rows, Matrix::new(3, 3).with(Remove::row(Rows::new(1..=2))), "+---+----------+----------+----------+" "| N | column 0 | column 1 | column 2 |" "+---+----------+----------+----------+" "| 2 | 2-0 | 2-1 | 2-2 |" "+---+----------+----------+----------+" ); test_table!( disable_header, Matrix::new(3, 3).with(Style::psql()).with(Remove::row(Rows::first())), " 0 | 0-0 | 0-1 | 0-2 " "---+-----+-----+-----" " 1 | 1-0 | 1-1 | 1-2 " " 2 | 2-0 | 2-1 | 2-2 " ); test_table!( disable_all_table_via_rows, Matrix::new(3, 3) .with(Style::psql()) .with(Remove::row(Columns::new(..))), "" ); test_table!( disable_header_with_new_styling, Matrix::new(3, 3) .with(Modify::new(Segment::all()).with(Alignment::left())) .with(Remove::row(Rows::new(..1))) .with(Style::modern().remove_horizontal().horizontals([(1, HorizontalLine::inherit(Style::modern()))])), "┌───┬─────┬─────┬─────┐" "│ 0 │ 0-0 │ 0-1 │ 0-2 │" "├───┼─────┼─────┼─────┤" "│ 1 │ 1-0 │ 1-1 │ 1-2 │" "│ 2 │ 2-0 │ 2-1 │ 2-2 │" "└───┴─────┴─────┴─────┘" ); test_table!( disable_columns, Matrix::new(3, 3).with(Style::psql()).with(Remove::column(Columns::first())), " column 0 | column 1 | column 2 " "----------+----------+----------" " 0-0 | 0-1 | 0-2 " " 1-0 | 1-1 | 1-2 " " 2-0 | 2-1 | 2-2 " ); test_table!( disable_column_by_name, Matrix::new(3, 3).with(Style::psql()) .with(Remove::column(ByColumnName::new("column 1"))) .with(Remove::column(ByColumnName::new("column 3"))), " N | column 0 | column 2 " "---+----------+----------" " 0 | 0-0 | 0-2 " " 1 | 1-0 | 1-2 " " 2 | 2-0 | 2-2 " ); test_table!( disable_all_table_via_columns, Matrix::new(3, 3) .with(Style::psql()) .with(Modify::new(Segment::all()).with(Alignment::left())) .with(Remove::column(Columns::new(..))), "" ); tabled-0.18.0/tests/settings/duplicate_test.rs000064400000000000000000000144001046102023000175250ustar 00000000000000#![cfg(feature = "std")] use tabled::settings::{ object::{Cell, Columns, Rows, Segment}, Dup, }; use crate::matrix::Matrix; use testing_table::test_table; test_table!( dup_cell_to_cell, Matrix::new(3, 3).with(Dup::new(Cell::new(0, 0), Cell::new(0, 1))), "+----------+----------+----------+----------+" "| column 0 | column 0 | column 1 | column 2 |" "+----------+----------+----------+----------+" "| 0 | 0-0 | 0-1 | 0-2 |" "+----------+----------+----------+----------+" "| 1 | 1-0 | 1-1 | 1-2 |" "+----------+----------+----------+----------+" "| 2 | 2-0 | 2-1 | 2-2 |" "+----------+----------+----------+----------+" ); test_table!( dup_cell_to_column, Matrix::new(3, 3).with(Dup::new(Columns::single(1), Cell::new(0, 0))), "+---+---+----------+----------+" "| N | N | column 1 | column 2 |" "+---+---+----------+----------+" "| 0 | N | 0-1 | 0-2 |" "+---+---+----------+----------+" "| 1 | N | 1-1 | 1-2 |" "+---+---+----------+----------+" "| 2 | N | 2-1 | 2-2 |" "+---+---+----------+----------+" ); test_table!( dup_row_to_row_single, Matrix::new(3, 3).with(Dup::new(Rows::single(1), Rows::single(0))), "+---+----------+----------+----------+" "| N | column 0 | column 1 | column 2 |" "+---+----------+----------+----------+" "| N | column 0 | column 1 | column 2 |" "+---+----------+----------+----------+" "| 1 | 1-0 | 1-1 | 1-2 |" "+---+----------+----------+----------+" "| 2 | 2-0 | 2-1 | 2-2 |" "+---+----------+----------+----------+" ); test_table!( dup_row_to_row_single_to_many, Matrix::new(3, 3).with(Dup::new(Rows::new(1..3), Rows::single(0))), "+---+----------+----------+----------+" "| N | column 0 | column 1 | column 2 |" "+---+----------+----------+----------+" "| N | column 0 | column 1 | column 2 |" "+---+----------+----------+----------+" "| N | column 0 | column 1 | column 2 |" "+---+----------+----------+----------+" "| 2 | 2-0 | 2-1 | 2-2 |" "+---+----------+----------+----------+" ); test_table!( dup_row_to_row_single_to_all, Matrix::new(3, 3).with(Dup::new(Rows::new(1..), Rows::single(0))), "+---+----------+----------+----------+" "| N | column 0 | column 1 | column 2 |" "+---+----------+----------+----------+" "| N | column 0 | column 1 | column 2 |" "+---+----------+----------+----------+" "| N | column 0 | column 1 | column 2 |" "+---+----------+----------+----------+" "| N | column 0 | column 1 | column 2 |" "+---+----------+----------+----------+" ); test_table!( dup_row_to_column_single, Matrix::new(3, 3).with(Dup::new(Columns::single(1), Rows::single(0))), "+---+----------+----------+----------+" "| N | N | column 1 | column 2 |" "+---+----------+----------+----------+" "| 0 | column 0 | 0-1 | 0-2 |" "+---+----------+----------+----------+" "| 1 | column 1 | 1-1 | 1-2 |" "+---+----------+----------+----------+" "| 2 | column 2 | 2-1 | 2-2 |" "+---+----------+----------+----------+" ); test_table!( dup_column_to_row_single, Matrix::new(3, 3).with(Dup::new(Columns::single(1), Columns::single(0))), "+---+---+----------+----------+" "| N | N | column 1 | column 2 |" "+---+---+----------+----------+" "| 0 | 0 | 0-1 | 0-2 |" "+---+---+----------+----------+" "| 1 | 1 | 1-1 | 1-2 |" "+---+---+----------+----------+" "| 2 | 2 | 2-1 | 2-2 |" "+---+---+----------+----------+" ); test_table!( dup_row_to_column_single_repeat, Matrix::new(4, 3).with(Dup::new(Columns::single(1), Rows::single(0))), "+---+----------+----------+----------+" "| N | N | column 1 | column 2 |" "+---+----------+----------+----------+" "| 0 | column 0 | 0-1 | 0-2 |" "+---+----------+----------+----------+" "| 1 | column 1 | 1-1 | 1-2 |" "+---+----------+----------+----------+" "| 2 | column 2 | 2-1 | 2-2 |" "+---+----------+----------+----------+" "| 3 | N | 3-1 | 3-2 |" "+---+----------+----------+----------+" ); test_table!( dup_column_to_row_single_stop, Matrix::new(4, 3).with(Dup::new(Rows::single(1), Columns::single(0))), "+---+----------+----------+----------+" "| N | column 0 | column 1 | column 2 |" "+---+----------+----------+----------+" "| N | 0 | 1 | 2 |" "+---+----------+----------+----------+" "| 1 | 1-0 | 1-1 | 1-2 |" "+---+----------+----------+----------+" "| 2 | 2-0 | 2-1 | 2-2 |" "+---+----------+----------+----------+" "| 3 | 3-0 | 3-1 | 3-2 |" "+---+----------+----------+----------+" ); test_table!( dup_row_to_global, Matrix::new(4, 3).with(Dup::new(Segment::all(), Rows::single(0))), "+---+----------+----------+----------+" "| N | column 0 | column 1 | column 2 |" "+---+----------+----------+----------+" "| N | column 0 | column 1 | column 2 |" "+---+----------+----------+----------+" "| N | column 0 | column 1 | column 2 |" "+---+----------+----------+----------+" "| N | column 0 | column 1 | column 2 |" "+---+----------+----------+----------+" "| N | column 0 | column 1 | column 2 |" "+---+----------+----------+----------+" ); test_table!( dup_column_to_global, Matrix::new(4, 3).with(Dup::new(Segment::all(), Columns::single(0))), "+---+---+---+---+" "| N | 0 | 1 | 2 |" "+---+---+---+---+" "| 3 | N | 0 | 1 |" "+---+---+---+---+" "| 2 | 3 | N | 0 |" "+---+---+---+---+" "| 1 | 2 | 3 | N |" "+---+---+---+---+" "| 0 | 1 | 2 | 3 |" "+---+---+---+---+" ); test_table!( dup_empty_table, Matrix::empty().with(Dup::new(Segment::all(), Columns::single(0))), "" ); test_table!( dup_invalid_target, Matrix::new(4, 3).with(Dup::new(Segment::all(), Columns::single(99))), Matrix::new(4, 3), ); test_table!( dup_invalid_source, Matrix::new(4, 3).with(Dup::new(Rows::single(99), Columns::first())), Matrix::new(4, 3), ); tabled-0.18.0/tests/settings/extract_test.rs000064400000000000000000000205031046102023000172260ustar 00000000000000#![cfg(feature = "std")] use tabled::{ builder::Builder, settings::{ object::{Rows, Segment}, Alignment, Extract, Format, Modify, Padding, Remove, }, }; use crate::matrix::Matrix; use testing_table::test_table; test_table!( extract_segment_full_test, Matrix::new(3, 3) .with(Modify::new(Segment::all()).with(Alignment::left())) .with(Modify::new(Segment::all()).with(Padding::new(3, 1, 0, 0))) .with(Modify::new(Segment::all()).with(Format::content(|s| format!("[{s}]")))) .with(Extract::segment(.., ..)), "+-------+--------------+--------------+--------------+" "| [N] | [column 0] | [column 1] | [column 2] |" "+-------+--------------+--------------+--------------+" "| [0] | [0-0] | [0-1] | [0-2] |" "+-------+--------------+--------------+--------------+" "| [1] | [1-0] | [1-1] | [1-2] |" "+-------+--------------+--------------+--------------+" "| [2] | [2-0] | [2-1] | [2-2] |" "+-------+--------------+--------------+--------------+" ); test_table!( extract_segment_skip_top_row_test, Matrix::new(3, 3) .with(Modify::new(Segment::all()).with(Alignment::left())) .with(Modify::new(Segment::all()).with(Padding::new(3, 1, 0, 0))) .with(Modify::new(Segment::all()).with(Format::content(|s| format!("[{s}]")))) .with(Extract::segment(1.., ..)), "+-------+---------+---------+---------+" "| [0] | [0-0] | [0-1] | [0-2] |" "+-------+---------+---------+---------+" "| [1] | [1-0] | [1-1] | [1-2] |" "+-------+---------+---------+---------+" "| [2] | [2-0] | [2-1] | [2-2] |" "+-------+---------+---------+---------+" ); test_table!( extract_segment_skip_column_test, Matrix::new(3, 3) .with(Modify::new(Segment::all()).with(Alignment::left())) .with(Modify::new(Segment::all()).with(Padding::new(3, 1, 0, 0))) .with(Modify::new(Segment::all()).with(Format::content(|s| format!("[{s}]")))) .with(Extract::segment(.., 1..)), "+--------------+--------------+--------------+" "| [column 0] | [column 1] | [column 2] |" "+--------------+--------------+--------------+" "| [0-0] | [0-1] | [0-2] |" "+--------------+--------------+--------------+" "| [1-0] | [1-1] | [1-2] |" "+--------------+--------------+--------------+" "| [2-0] | [2-1] | [2-2] |" "+--------------+--------------+--------------+" ); test_table!( extract_segment_bottom_right_square_test, Matrix::new(3, 3) .with(Modify::new(Segment::all()).with(Alignment::left())) .with(Modify::new(Segment::all()).with(Padding::new(3, 1, 0, 0))) .with(Modify::new(Segment::all()).with(Format::content(|s| format!("[{s}]")))) .with(Extract::segment(2.., 2..)), "+---------+---------+" "| [1-1] | [1-2] |" "+---------+---------+" "| [2-1] | [2-2] |" "+---------+---------+" ); test_table!( extract_segment_middle_section_test, Matrix::new(3, 3) .with(Modify::new(Segment::all()).with(Alignment::left())) .with(Modify::new(Segment::all()).with(Padding::new(3, 1, 0, 0))) .with(Modify::new(Segment::all()).with(Format::content(|s| format!("[{s}]")))) .with(Extract::segment(1..3, 1..)), "+---------+---------+---------+" "| [0-0] | [0-1] | [0-2] |" "+---------+---------+---------+" "| [1-0] | [1-1] | [1-2] |" "+---------+---------+---------+" ); test_table!( extract_segment_empty_test, Matrix::new(3, 3).with(Extract::segment(1..1, 1..1)), "" ); test_table!( extract_rows_full_test, Matrix::new(3, 3) .with(Modify::new(Segment::all()).with(Alignment::left())) .with(Modify::new(Segment::all()).with(Padding::new(3, 1, 0, 0))) .with(Modify::new(Segment::all()).with(Format::content(|s| format!("[{s}]")))) .with(Extract::rows(..)), "+-------+--------------+--------------+--------------+" "| [N] | [column 0] | [column 1] | [column 2] |" "+-------+--------------+--------------+--------------+" "| [0] | [0-0] | [0-1] | [0-2] |" "+-------+--------------+--------------+--------------+" "| [1] | [1-0] | [1-1] | [1-2] |" "+-------+--------------+--------------+--------------+" "| [2] | [2-0] | [2-1] | [2-2] |" "+-------+--------------+--------------+--------------+" ); test_table!( extract_rows_empty_test, Matrix::new(3, 3).with(Extract::rows(0..0)), "" ); test_table!( extract_rows_partial_view_test, Matrix::new(3, 3) .with(Modify::new(Segment::all()).with(Alignment::left())) .with(Modify::new(Segment::all()).with(Padding::new(3, 1, 0, 0))) .with(Modify::new(Segment::all()).with(Format::content(|s| format!("[{s}]")))) .with(Extract::rows(0..=2)), "+-------+--------------+--------------+--------------+" "| [N] | [column 0] | [column 1] | [column 2] |" "+-------+--------------+--------------+--------------+" "| [0] | [0-0] | [0-1] | [0-2] |" "+-------+--------------+--------------+--------------+" "| [1] | [1-0] | [1-1] | [1-2] |" "+-------+--------------+--------------+--------------+" ); test_table!( extract_columns_full_test, Matrix::new(3, 3) .with(Modify::new(Segment::all()).with(Alignment::left())) .with(Modify::new(Segment::all()).with(Padding::new(3, 1, 0, 0))) .with(Modify::new(Segment::all()).with(Format::content(|s| format!("[{s}]")))) .with(Extract::columns(..)), "+-------+--------------+--------------+--------------+" "| [N] | [column 0] | [column 1] | [column 2] |" "+-------+--------------+--------------+--------------+" "| [0] | [0-0] | [0-1] | [0-2] |" "+-------+--------------+--------------+--------------+" "| [1] | [1-0] | [1-1] | [1-2] |" "+-------+--------------+--------------+--------------+" "| [2] | [2-0] | [2-1] | [2-2] |" "+-------+--------------+--------------+--------------+" ); test_table!( extract_columns_empty_test, Matrix::new(3, 3).with(Extract::columns(0..0)), "" ); test_table!( extract_columns_partial_view_test, Matrix::new(3, 3) .with(Modify::new(Segment::all()).with(Alignment::left())) .with(Modify::new(Segment::all()).with(Padding::new(3, 1, 0, 0))) .with(Modify::new(Segment::all()).with(Format::content(|s| format!("[{s}]")))) .with(Extract::columns(0..2)), "+-------+--------------+" "| [N] | [column 0] |" "+-------+--------------+" "| [0] | [0-0] |" "+-------+--------------+" "| [1] | [1-0] |" "+-------+--------------+" "| [2] | [2-0] |" "+-------+--------------+" ); test_table!( extract_inside_test, Matrix::new(3, 3).with(Remove::row(Rows::first())).with(Extract::segment(1..2, 1..2)), "+-----+" "| 1-0 |" "+-----+" ); test_table!( extract_left_test, Matrix::new(3, 3).with(Remove::row(Rows::first())).with(Extract::segment(.., ..1)), "+---+" "| 0 |" "+---+" "| 1 |" "+---+" "| 2 |" "+---+" ); test_table!( extract_right_test, Matrix::new(3, 3).with(Remove::row(Rows::first())).with(Extract::segment(.., 2..)), "+-----+-----+" "| 0-1 | 0-2 |" "+-----+-----+" "| 1-1 | 1-2 |" "+-----+-----+" "| 2-1 | 2-2 |" "+-----+-----+" ); test_table!( extract_top_test, Matrix::new(3, 3).with(Remove::row(Rows::first())).with(Extract::segment(..1, ..)), "+---+-----+-----+-----+" "| 0 | 0-0 | 0-1 | 0-2 |" "+---+-----+-----+-----+" ); test_table!( extract_bottom_test, Matrix::new(3, 3).with(Remove::row(Rows::first())).with(Extract::segment(2.., ..)), "+---+-----+-----+-----+" "| 2 | 2-0 | 2-1 | 2-2 |" "+---+-----+-----+-----+" ); test_table!( extract_all_test, Matrix::new(3, 3) .with(Remove::row(Rows::first())) .with(Extract::segment(3.., 3..)), "" ); test_table!( extract_empty_test, Builder::default().build().with(Extract::segment(.., ..)), "" ); tabled-0.18.0/tests/settings/format_test.rs000064400000000000000000000265131046102023000170530ustar 00000000000000#![cfg(feature = "std")] use tabled::settings::{ object::{Cell, Columns, Object, Rows, Segment}, Alignment, Format, Modify, Padding, Style, }; use crate::matrix::Matrix; use testing_table::test_table; #[cfg(feature = "ansi")] use tabled::settings::Color; test_table!( formatting_full_test, Matrix::new(3, 3).with(Modify::new(Segment::all()).with(Format::content(|s| format!("[{s}]")))), "+-----+------------+------------+------------+" "| [N] | [column 0] | [column 1] | [column 2] |" "+-----+------------+------------+------------+" "| [0] | [0-0] | [0-1] | [0-2] |" "+-----+------------+------------+------------+" "| [1] | [1-0] | [1-1] | [1-2] |" "+-----+------------+------------+------------+" "| [2] | [2-0] | [2-1] | [2-2] |" "+-----+------------+------------+------------+" ); test_table!( formatting_head_test, Matrix::new(3, 3) .with(Style::markdown()) .with(Modify::new(Rows::first()).with(Format::content(|s| format!(":{s}")))), "| :N | :column 0 | :column 1 | :column 2 |" "|----|-----------|-----------|-----------|" "| 0 | 0-0 | 0-1 | 0-2 |" "| 1 | 1-0 | 1-1 | 1-2 |" "| 2 | 2-0 | 2-1 | 2-2 |" ); test_table!( formatting_row_test, Matrix::new(3, 3) .with(Style::psql()) .with(Modify::new(Rows::new(1..)).with(Format::content(|s| format!("<{s}>")))), " N | column 0 | column 1 | column 2 " "-----+----------+----------+----------" " <0> | <0-0> | <0-1> | <0-2> " " <1> | <1-0> | <1-1> | <1-2> " " <2> | <2-0> | <2-1> | <2-2> " ); test_table!( formatting_column_test, Matrix::new(3, 3) .with(Style::psql()) .with(Modify::new(Columns::single(0)).with(Format::content(|s| format!("(x) {s}")))), " (x) N | column 0 | column 1 | column 2 " "-------+----------+----------+----------" " (x) 0 | 0-0 | 0-1 | 0-2 " " (x) 1 | 1-0 | 1-1 | 1-2 " " (x) 2 | 2-0 | 2-1 | 2-2 " ); test_table!( formatting_multiline_test, Matrix::new(3, 3) .insert((2, 2).into(), "E\nnde\navou\nros") .insert((3, 2).into(), "Red\nHat") .insert((3, 3).into(), "https://\nwww\n.\nredhat\n.com\n/en") .with(Style::psql()) .with(Modify::new(Segment::all()).with(Format::content(|s| format!("(x) {s}")).multiline())), " (x) N | (x) column 0 | (x) column 1 | (x) column 2 " "-------+--------------+--------------+--------------" " (x) 0 | (x) 0-0 | (x) 0-1 | (x) 0-2 " " (x) 1 | (x) 1-0 | (x) E | (x) 1-2 " " | | (x) nde | " " | | (x) avou | " " | | (x) ros | " " (x) 2 | (x) 2-0 | (x) Red | (x) https:// " " | | (x) Hat | (x) www " " | | | (x) . " " | | | (x) redhat " " | | | (x) .com " " | | | (x) /en " ); test_table!( formatting_cell_test, Matrix::new(3, 3) .with(Style::psql()) .with(Modify::new(Cell::new(0, 0)).with(Format::content(|s| format!("(x) {s}")))) .with(Modify::new(Cell::new(0, 1)).with(Format::content(|s| format!("(x) {s}")))) .with(Modify::new(Cell::new(0, 2)).with(Format::content(|s| format!("(x) {s}")))), " (x) N | (x) column 0 | (x) column 1 | column 2 " "-------+--------------+--------------+----------" " 0 | 0-0 | 0-1 | 0-2 " " 1 | 1-0 | 1-1 | 1-2 " " 2 | 2-0 | 2-1 | 2-2 " ); test_table!( formatting_combination_and_test, Matrix::new(3, 3) .with(Style::psql()) .with( Modify::new(Columns::single(0).and(Rows::single(0))) .with(Format::content(|s| format!("(x) {s}"))), ), " (x) N | (x) column 0 | (x) column 1 | (x) column 2 " "-------+--------------+--------------+--------------" " (x) 0 | 0-0 | 0-1 | 0-2 " " (x) 1 | 1-0 | 1-1 | 1-2 " " (x) 2 | 2-0 | 2-1 | 2-2 " ); test_table!( formatting_combination_not_test, Matrix::new(3, 3) .with(Style::psql()) .with( Modify::new(Columns::single(0).and(Rows::single(0)).not(Cell::new(0, 0))) .with(Format::content(|s| format!("(x) {s}"))), ), " N | (x) column 0 | (x) column 1 | (x) column 2 " "-------+--------------+--------------+--------------" " (x) 0 | 0-0 | 0-1 | 0-2 " " (x) 1 | 1-0 | 1-1 | 1-2 " " (x) 2 | 2-0 | 2-1 | 2-2 " ); test_table!( formatting_combination_inverse_test, Matrix::new(3, 3) .with(Style::psql()) .with(Modify::new(Columns::single(0).inverse()).with(Format::content(|s| format!("(x) {s}")))), " N | (x) column 0 | (x) column 1 | (x) column 2 " "---+--------------+--------------+--------------" " 0 | (x) 0-0 | (x) 0-1 | (x) 0-2 " " 1 | (x) 1-0 | (x) 1-1 | (x) 1-2 " " 2 | (x) 2-0 | (x) 2-1 | (x) 2-2 " ); test_table!( formatting_combination_intersect_test, Matrix::new(3, 3) .with(Style::psql()) .with( Modify::new(Columns::new(1..3).intersect(Rows::new(1..3))) .with(Format::content(|s| format!("(x) {s}"))), ), " N | column 0 | column 1 | column 2 " "---+----------+----------+----------" " 0 | (x) 0-0 | (x) 0-1 | 0-2 " " 1 | (x) 1-0 | (x) 1-1 | 1-2 " " 2 | 2-0 | 2-1 | 2-2 " ); test_table!( formatting_using_lambda_test, Matrix::new(3, 3) .with(Style::markdown()) .with(Modify::new(Rows::first()).with(Format::content(|s| format!(":{s}")))), "| :N | :column 0 | :column 1 | :column 2 |" "|----|-----------|-----------|-----------|" "| 0 | 0-0 | 0-1 | 0-2 |" "| 1 | 1-0 | 1-1 | 1-2 |" "| 2 | 2-0 | 2-1 | 2-2 |" ); test_table!( formatting_using_function_test, Matrix::new(3, 3) .with(Style::markdown()) .with(Modify::new(Rows::first()).with(Format::content(str::to_uppercase))), "| N | COLUMN 0 | COLUMN 1 | COLUMN 2 |" "|---|----------|----------|----------|" "| 0 | 0-0 | 0-1 | 0-2 |" "| 1 | 1-0 | 1-1 | 1-2 |" "| 2 | 2-0 | 2-1 | 2-2 |" ); test_table!( format_with_index, Matrix::new(3, 3) .with(Style::markdown()) .with(Modify::new(Rows::first()).with(Format::positioned(|a, p| { let p: (_, _) = p.into(); match p { (0, 0) => "(0, 0)".to_string(), (0, 1) => "(0, 1)".to_string(), (0, 2) => "(0, 2)".to_string(), _ => a.to_string(), } }))), "| (0, 0) | (0, 1) | (0, 2) | column 2 |" "|--------|--------|--------|----------|" "| 0 | 0-0 | 0-1 | 0-2 |" "| 1 | 1-0 | 1-1 | 1-2 |" "| 2 | 2-0 | 2-1 | 2-2 |" ); test_table!( format_doesnt_change_padding, Matrix::new(3, 3) .with(Modify::new(Segment::all()).with(Alignment::left())) .with(Modify::new(Segment::all()).with(Padding::new(3, 1, 0, 0))) .with(Modify::new(Segment::all()).with(Format::content(|s| format!("[{s}]")))), "+-------+--------------+--------------+--------------+" "| [N] | [column 0] | [column 1] | [column 2] |" "+-------+--------------+--------------+--------------+" "| [0] | [0-0] | [0-1] | [0-2] |" "+-------+--------------+--------------+--------------+" "| [1] | [1-0] | [1-1] | [1-2] |" "+-------+--------------+--------------+--------------+" "| [2] | [2-0] | [2-1] | [2-2] |" "+-------+--------------+--------------+--------------+" ); test_table!( formatting_content_str_test, Matrix::new(3, 3).with(Modify::new(Segment::all()).with(Format::content(|_| String::from("Hello World")))), "+-------------+-------------+-------------+-------------+" "| Hello World | Hello World | Hello World | Hello World |" "+-------------+-------------+-------------+-------------+" "| Hello World | Hello World | Hello World | Hello World |" "+-------------+-------------+-------------+-------------+" "| Hello World | Hello World | Hello World | Hello World |" "+-------------+-------------+-------------+-------------+" "| Hello World | Hello World | Hello World | Hello World |" "+-------------+-------------+-------------+-------------+" ); #[cfg(feature = "ansi")] test_table!( color_test, Matrix::new(3, 3) .with(Style::psql()) .modify(Columns::new(..1).and(Columns::new(2..)), Format::content(|s| Color::FG_RED.colorize(s))) .modify(Columns::new(1..2), Format::content(|s| Color::FG_BLUE.colorize(s))), " \u{1b}[31mN\u{1b}[39m | \u{1b}[34mcolumn 0\u{1b}[39m | \u{1b}[31mcolumn 1\u{1b}[39m | \u{1b}[31mcolumn 2\u{1b}[39m " "---+----------+----------+----------" " \u{1b}[31m0\u{1b}[39m | \u{1b}[34m0-0\u{1b}[39m | \u{1b}[31m0-1\u{1b}[39m | \u{1b}[31m0-2\u{1b}[39m " " \u{1b}[31m1\u{1b}[39m | \u{1b}[34m1-0\u{1b}[39m | \u{1b}[31m1-1\u{1b}[39m | \u{1b}[31m1-2\u{1b}[39m " " \u{1b}[31m2\u{1b}[39m | \u{1b}[34m2-0\u{1b}[39m | \u{1b}[31m2-1\u{1b}[39m | \u{1b}[31m2-2\u{1b}[39m " ); #[cfg(feature = "ansi")] test_table!( color_multiline_test, Matrix::new(3, 3) .insert((2, 2).into(), "E\nnde\navou\nros") .insert((3, 2).into(), "Red\nHat") .insert((3, 3).into(), "https://\nwww\n.\nredhat\n.com\n/en") .with(Style::psql()) .modify(Columns::new(..1), Format::content(|s| Color::FG_RED.colorize(s)).multiline()) .modify(Columns::new(1..2), Format::content(|s| Color::FG_BLUE.colorize(s)).multiline()) .modify(Columns::new(2..), Format::content(|s| Color::FG_GREEN.colorize(s)).multiline()), " \u{1b}[31mN\u{1b}[39m | \u{1b}[34mcolumn 0\u{1b}[39m | \u{1b}[32mcolumn 1\u{1b}[39m | \u{1b}[32mcolumn 2\u{1b}[39m " "---+----------+----------+----------\n \u{1b}[31m0\u{1b}[39m | \u{1b}[34m0-0\u{1b}[39m | \u{1b}[32m0-1\u{1b}[39m | \u{1b}[32m0-2\u{1b}[39m " " \u{1b}[31m1\u{1b}[39m | \u{1b}[34m1-0\u{1b}[39m | \u{1b}[32mE\u{1b}[39m | \u{1b}[32m1-2\u{1b}[39m " " | | \u{1b}[32mnde\u{1b}[39m | " " | | \u{1b}[32mavou\u{1b}[39m | " " | | \u{1b}[32mros\u{1b}[39m | " " \u{1b}[31m2\u{1b}[39m | \u{1b}[34m2-0\u{1b}[39m | \u{1b}[32mRed\u{1b}[39m | \u{1b}[32mhttps://\u{1b}[39m " " | | \u{1b}[32mHat\u{1b}[39m | \u{1b}[32mwww\u{1b}[39m \n | | | \u{1b}[32m.\u{1b}[39m " " | | | \u{1b}[32mredhat\u{1b}[39m " " | | | \u{1b}[32m.com\u{1b}[39m " " | | | \u{1b}[32m/en\u{1b}[39m " ); tabled-0.18.0/tests/settings/formatting_test.rs000064400000000000000000000064651046102023000177410ustar 00000000000000#![cfg(feature = "std")] use tabled::settings::{formatting::Justification, object::Columns, Color, Modify}; use crate::matrix::Matrix; use testing_table::test_table; test_table!( justification, Matrix::new(3, 3).with(Justification::new('#')), "+---+----------+----------+----------+" "| N | column 0 | column 1 | column 2 |" "+---+----------+----------+----------+" "| 0 | ##0-0### | ##0-1### | ##0-2### |" "+---+----------+----------+----------+" "| 1 | ##1-0### | ##1-1### | ##1-2### |" "+---+----------+----------+----------+" "| 2 | ##2-0### | ##2-1### | ##2-2### |" "+---+----------+----------+----------+" ); test_table!( justification_color, Matrix::new(3, 3).with(Justification::new('#').color(Color::BG_RED)), "+---+----------+----------+----------+" "| N | column 0 | column 1 | column 2 |" "+---+----------+----------+----------+" "| 0 | \u{1b}[41m##\u{1b}[49m0-0\u{1b}[41m###\u{1b}[49m | \u{1b}[41m##\u{1b}[49m0-1\u{1b}[41m###\u{1b}[49m | \u{1b}[41m##\u{1b}[49m0-2\u{1b}[41m###\u{1b}[49m |" "+---+----------+----------+----------+" "| 1 | \u{1b}[41m##\u{1b}[49m1-0\u{1b}[41m###\u{1b}[49m | \u{1b}[41m##\u{1b}[49m1-1\u{1b}[41m###\u{1b}[49m | \u{1b}[41m##\u{1b}[49m1-2\u{1b}[41m###\u{1b}[49m |" "+---+----------+----------+----------+" "| 2 | \u{1b}[41m##\u{1b}[49m2-0\u{1b}[41m###\u{1b}[49m | \u{1b}[41m##\u{1b}[49m2-1\u{1b}[41m###\u{1b}[49m | \u{1b}[41m##\u{1b}[49m2-2\u{1b}[41m###\u{1b}[49m |" "+---+----------+----------+----------+" ); test_table!( justification_columns, Matrix::new(3, 3) .with(Modify::new(Columns::single(1)).with(Justification::new('#'))) .with(Modify::new(Columns::single(2)).with(Justification::new('@'))) .with(Modify::new(Columns::single(3)).with(Justification::new('$'))), "+---+----------+----------+----------+" "| N | column 0 | column 1 | column 2 |" "+---+----------+----------+----------+" "| 0 | ##0-0### | @@0-1@@@ | $$0-2$$$ |" "+---+----------+----------+----------+" "| 1 | ##1-0### | @@1-1@@@ | $$1-2$$$ |" "+---+----------+----------+----------+" "| 2 | ##2-0### | @@2-1@@@ | $$2-2$$$ |" "+---+----------+----------+----------+" ); test_table!( justification_color_columns, Matrix::new(3, 3) .with(Modify::new(Columns::single(1)).with(Justification::new('#').color(Color::BG_BLUE))) .with(Modify::new(Columns::single(2)).with(Justification::new('@').color(Color::BG_RED))) .with(Modify::new(Columns::single(3)).with(Justification::new('$').color(Color::BG_WHITE))), "+---+----------+----------+----------+" "| N | column 0 | column 1 | column 2 |" "+---+----------+----------+----------+" "| 0 | \u{1b}[44m##\u{1b}[49m0-0\u{1b}[44m###\u{1b}[49m | \u{1b}[41m@@\u{1b}[49m0-1\u{1b}[41m@@@\u{1b}[49m | \u{1b}[47m$$\u{1b}[49m0-2\u{1b}[47m$$$\u{1b}[49m |" "+---+----------+----------+----------+" "| 1 | \u{1b}[44m##\u{1b}[49m1-0\u{1b}[44m###\u{1b}[49m | \u{1b}[41m@@\u{1b}[49m1-1\u{1b}[41m@@@\u{1b}[49m | \u{1b}[47m$$\u{1b}[49m1-2\u{1b}[47m$$$\u{1b}[49m |" "+---+----------+----------+----------+" "| 2 | \u{1b}[44m##\u{1b}[49m2-0\u{1b}[44m###\u{1b}[49m | \u{1b}[41m@@\u{1b}[49m2-1\u{1b}[41m@@@\u{1b}[49m | \u{1b}[47m$$\u{1b}[49m2-2\u{1b}[47m$$$\u{1b}[49m |" "+---+----------+----------+----------+" ); tabled-0.18.0/tests/settings/height_test.rs000064400000000000000000000220031046102023000170210ustar 00000000000000#![cfg(feature = "std")] use tabled::settings::{ object::{Columns, Segment}, Alignment, Format, Height, Modify, Style, }; use crate::matrix::Matrix; use testing_table::test_table; #[cfg(feature = "ansi")] use tabled::settings::Color; test_table!( cell_height_increase, Matrix::new(3, 3) .with(Style::markdown()) .with( Modify::new(Columns::first()) .with(Height::increase(3)) ) .with(Modify::new(Segment::all()).with( Alignment::center_vertical() )), "| N | | | |" "| | column 0 | column 1 | column 2 |" "| | | | |" "|---|----------|----------|----------|" "| 0 | | | |" "| | 0-0 | 0-1 | 0-2 |" "| | | | |" "| 1 | | | |" "| | 1-0 | 1-1 | 1-2 |" "| | | | |" "| 2 | | | |" "| | 2-0 | 2-1 | 2-2 |" "| | | | |" ); test_table!( table_height_increase, Matrix::new(3, 3) .with(Style::markdown()) .with(Modify::new(Columns::first()).with(Alignment::center_vertical())) .with(Height::increase(10)), "| | column 0 | column 1 | column 2 |" "| N | | | |" "| | | | |" "|---|----------|----------|----------|" "| 0 | 0-0 | 0-1 | 0-2 |" "| | | | |" "| 1 | 1-0 | 1-1 | 1-2 |" "| | | | |" "| 2 | 2-0 | 2-1 | 2-2 |" "| | | | |" ); test_table!( cell_height_increase_zero, Matrix::new(3, 3) .with(Style::markdown()) .with( Modify::new(Columns::first()) .with(Height::increase(0)) ) .with(Modify::new(Segment::all()).with( Alignment::center_vertical() )), "| N | column 0 | column 1 | column 2 |" "|---|----------|----------|----------|" "| 0 | 0-0 | 0-1 | 0-2 |" "| 1 | 1-0 | 1-1 | 1-2 |" "| 2 | 2-0 | 2-1 | 2-2 |" ); test_table!( table_height_increase_zero, Matrix::new(3, 3) .with(Style::markdown()) .with(Modify::new(Columns::first()).with(Alignment::center_vertical())) .with(Height::increase(0)), "| N | column 0 | column 1 | column 2 |" "|---|----------|----------|----------|" "| 0 | 0-0 | 0-1 | 0-2 |" "| 1 | 1-0 | 1-1 | 1-2 |" "| 2 | 2-0 | 2-1 | 2-2 |" ); test_table!( cell_height_limit, Matrix::new(3, 3) .with(Style::markdown()) .with(Modify::new(Columns::first()).with(Format::content(|s| format!("xxxx\n{s}xxxx\nxxxx\n")))) .with( Modify::new(Columns::first()) .with(Height::limit(1)) ) .with(Modify::new(Segment::all()).with( Alignment::center_vertical() )), "| xxxx | column 0 | column 1 | column 2 |" "|------|----------|----------|----------|" "| xxxx | 0-0 | 0-1 | 0-2 |" "| xxxx | 1-0 | 1-1 | 1-2 |" "| xxxx | 2-0 | 2-1 | 2-2 |" ); test_table!( table_height_limit, Matrix::new(3, 3) .with(Style::markdown()) .with(Modify::new(Columns::first()).with(Format::content(|s| format!("xxxx\n{s}xxxx\nxxxx\n")))) .with(Modify::new(Columns::first()).with(Alignment::center_vertical())) .with(Height::limit(10)), "| xxxx | column 0 | column 1 | column 2 |" "| Nxxxx | | | |" "|-------|----------|----------|----------|" "| xxxx | 0-0 | 0-1 | 0-2 |" "| 0xxxx | | | |" "| xxxx | 1-0 | 1-1 | 1-2 |" "| 1xxxx | | | |" "| xxxx | 2-0 | 2-1 | 2-2 |" "| 2xxxx | | | |" "| xxxx | | | |" ); test_table!( table_height_limit_style_change_after, Matrix::new(3, 3) .with(Style::markdown()) .with(Modify::new(Columns::first()).with(Format::content(|s| format!("xxxx\n{s}xxxx\nxxxx\n")))) .with(Modify::new(Columns::first()).with(Alignment::center_vertical())) .with(Height::limit(7)), "| xxxx | column 0 | column 1 | column 2 |" "|-------|----------|----------|----------|" "| xxxx | 0-0 | 0-1 | 0-2 |" "| xxxx | 1-0 | 1-1 | 1-2 |" "| 1xxxx | | | |" "| xxxx | 2-0 | 2-1 | 2-2 |" "| 2xxxx | | | |" ); test_table!( cell_height_limit_zero, Matrix::new(3, 3) .with(Style::markdown()) .with(Modify::new(Columns::first()).with(Format::content(|s| format!("xxxx\n{s}xxxx\nxxxx\n")))) .with( Modify::new(Columns::first()) .with(Height::limit(0)) ) .with(Modify::new(Segment::all()).with( Alignment::center_vertical() )), "| | column 0 | column 1 | column 2 |" "|--|----------|----------|----------|" "| | 0-0 | 0-1 | 0-2 |" "| | 1-0 | 1-1 | 1-2 |" "| | 2-0 | 2-1 | 2-2 |" ); test_table!( table_height_limit_zero, Matrix::new(3, 3) .with(Style::markdown()) .with( Modify::new(Columns::new(..)) .with(Format::content(|s| format!("xxxx\n{s}xxxx\nxxxx\n"))) ) .with(Height::limit(0)), "|--|--|--|--|" ); test_table!( table_height_limit_zero_1, Matrix::new(3, 3) .with(Style::markdown()) .with(Height::limit(0)) .with( Modify::new(Columns::new(..)).with(Format::content(|s| format!("xxxx\n{s}xxxx\nxxxx\n"))) ), "| xxxx | xxxx | xxxx | xxxx |" "| xxxx | xxxx | xxxx | xxxx |" "| xxxx | xxxx | xxxx | xxxx |" "| | | | |" "|------|------|------|------|" "| xxxx | xxxx | xxxx | xxxx |" "| xxxx | xxxx | xxxx | xxxx |" "| xxxx | xxxx | xxxx | xxxx |" "| | | | |" "| xxxx | xxxx | xxxx | xxxx |" "| xxxx | xxxx | xxxx | xxxx |" "| xxxx | xxxx | xxxx | xxxx |" "| | | | |" "| xxxx | xxxx | xxxx | xxxx |" "| xxxx | xxxx | xxxx | xxxx |" "| xxxx | xxxx | xxxx | xxxx |" "| | | | |" ); #[cfg(feature = "ansi")] test_table!( cell_height_limit_colored, Matrix::new(3, 3) .with(Style::markdown()) .modify(Columns::first(), Format::content(|s| Color::FG_RED.colorize(format!("xxxx\n{s}xxxx\nxxxx\n")))) .modify(Columns::first(), Height::limit(1)) .with(Alignment::center_vertical()), "| \u{1b}[31mxxxx\u{1b}[39m | column 0 | column 1 | column 2 |" "|------|----------|----------|----------|" "| \u{1b}[31mxxxx\u{1b}[39m | 0-0 | 0-1 | 0-2 |" "| \u{1b}[31mxxxx\u{1b}[39m | 1-0 | 1-1 | 1-2 |" "| \u{1b}[31mxxxx\u{1b}[39m | 2-0 | 2-1 | 2-2 |" ); #[cfg(feature = "ansi")] test_table!( table_height_limit_colored, Matrix::new(3, 3) .with(Style::markdown()) .modify(Columns::first(), Format::content(|s| (Color::FG_BLUE | Color::BG_GREEN).colorize(format!("xxxx\n{s}xxxx\nxxxx\n")))) .modify(Columns::first(), Alignment::center_vertical()) .with(Height::limit(10)), "| \u{1b}[34m\u{1b}[42mxxxx\u{1b}[39m\u{1b}[49m | column 0 | column 1 | column 2 |" "| \u{1b}[34m\u{1b}[42mNxxxx\u{1b}[39m\u{1b}[49m | | | |" "|-------|----------|----------|----------|" "| \u{1b}[34m\u{1b}[42mxxxx\u{1b}[39m\u{1b}[49m | 0-0 | 0-1 | 0-2 |" "| \u{1b}[34m\u{1b}[42m0xxxx\u{1b}[39m\u{1b}[49m | | | |" "| \u{1b}[34m\u{1b}[42mxxxx\u{1b}[39m\u{1b}[49m | 1-0 | 1-1 | 1-2 |" "| \u{1b}[34m\u{1b}[42m1xxxx\u{1b}[39m\u{1b}[49m | | | |" "| \u{1b}[34m\u{1b}[42mxxxx\u{1b}[39m\u{1b}[49m | 2-0 | 2-1 | 2-2 |" "| \u{1b}[34m\u{1b}[42m2xxxx\u{1b}[39m\u{1b}[49m | | | |" "| \u{1b}[34m\u{1b}[42mxxxx\u{1b}[39m\u{1b}[49m | | | |" ); #[cfg(feature = "macros")] test_table!( cell_height_1x1, tabled::row![tabled::col!["SGML"].with(Height::increase(4))], "+----------+" "| +------+ |" "| | SGML | |" "| | | |" "| +------+ |" "+----------+" ); #[cfg(feature = "macros")] test_table!( cell_height_1x1_no_top_border, tabled::row![tabled::col!["SGML"].with(Style::ascii().remove_top()).with(Height::increase(4))], "+----------+" "| | SGML | |" "| | | |" "| | | |" "| +------+ |" "+----------+" ); tabled-0.18.0/tests/settings/highlingt_test.rs000064400000000000000000000463321046102023000175410ustar 00000000000000#![cfg(feature = "std")] use tabled::{ builder::Builder, settings::{ highlight::Highlight, object::{Cell, Columns, Frame, Object, Rows, Segment}, style::{Border, Style}, }, }; use crate::matrix::Matrix; use testing_table::{static_table, test_table}; test_table!( highlingt_object_exceeds_boundaries, Matrix::new(3, 3).with(Style::modern()).with(Highlight::outline(Cell::new(1000, 0), '+')), "┌───┬──────────┬──────────┬──────────┐" "│ N │ column 0 │ column 1 │ column 2 │" "├───┼──────────┼──────────┼──────────┤" "│ 0 │ 0-0 │ 0-1 │ 0-2 │" "├───┼──────────┼──────────┼──────────┤" "│ 1 │ 1-0 │ 1-1 │ 1-2 │" "├───┼──────────┼──────────┼──────────┤" "│ 2 │ 2-0 │ 2-1 │ 2-2 │" "└───┴──────────┴──────────┴──────────┘" ); test_table!( highlingt_empty_table, Builder::default() .build() .with(Highlight::outline(Segment::all(), '+')), "" ); test_table!( highlingt_cell, Matrix::new(3, 3) .with(Style::modern()) .with(Highlight::outline(Cell::new(0, 0), '+')) .with(Highlight::outline(Cell::new(1, 1), '*')), "+++++──────────┬──────────┬──────────┐" "+ N + column 0 │ column 1 │ column 2 │" "++++************──────────┼──────────┤" "│ 0 * 0-0 * 0-1 │ 0-2 │" "├───************──────────┼──────────┤" "│ 1 │ 1-0 │ 1-1 │ 1-2 │" "├───┼──────────┼──────────┼──────────┤" "│ 2 │ 2-0 │ 2-1 │ 2-2 │" "└───┴──────────┴──────────┴──────────┘" ); test_table!( highlingt_row, Matrix::new(3, 3) .with(Style::modern()) .with(Highlight::outline(Rows::single(0), '+')) .with(Highlight::outline(Rows::single(3), '*')), "++++++++++++++++++++++++++++++++++++++" "+ N │ column 0 │ column 1 │ column 2 +" "++++++++++++++++++++++++++++++++++++++" "│ 0 │ 0-0 │ 0-1 │ 0-2 │" "├───┼──────────┼──────────┼──────────┤" "│ 1 │ 1-0 │ 1-1 │ 1-2 │" "**************************************" "* 2 │ 2-0 │ 2-1 │ 2-2 *" "**************************************" ); test_table!( highlingt_column, Matrix::new(3, 3) .with(Style::modern()) .with(Highlight::new(Columns::single(0)).border(Border::filled('+'))) .with(Highlight::new(Columns::single(2)).border(Border::filled('*'))), "+++++──────────************──────────┐" "+ N + column 0 * column 1 * column 2 │" "+───+──────────*──────────*──────────┤" "+ 0 + 0-0 * 0-1 * 0-2 │" "+───+──────────*──────────*──────────┤" "+ 1 + 1-0 * 1-1 * 1-2 │" "+───+──────────*──────────*──────────┤" "+ 2 + 2-0 * 2-1 * 2-2 │" "+++++──────────************──────────┘" ); test_table!( highlingt_row_range, Matrix::new(3, 3) .with(Style::modern()) .with(Highlight::outline(Rows::new(1..3), '+')), "┌───┬──────────┬──────────┬──────────┐" "│ N │ column 0 │ column 1 │ column 2 │" "++++++++++++++++++++++++++++++++++++++" "+ 0 │ 0-0 │ 0-1 │ 0-2 +" "+───┼──────────┼──────────┼──────────+" "+ 1 │ 1-0 │ 1-1 │ 1-2 +" "++++++++++++++++++++++++++++++++++++++" "│ 2 │ 2-0 │ 2-1 │ 2-2 │" "└───┴──────────┴──────────┴──────────┘" ); test_table!( highlingt_column_range, Matrix::new(3, 3) .with(Style::modern()) .with(Highlight::outline(Columns::new(..2), '+')), "++++++++++++++++──────────┬──────────┐" "+ N │ column 0 + column 1 │ column 2 │" "+───┼──────────+──────────┼──────────┤" "+ 0 │ 0-0 + 0-1 │ 0-2 │" "+───┼──────────+──────────┼──────────┤" "+ 1 │ 1-0 + 1-1 │ 1-2 │" "+───┼──────────+──────────┼──────────┤" "+ 2 │ 2-0 + 2-1 │ 2-2 │" "++++++++++++++++──────────┴──────────┘" ); test_table!( highlingt_frame, Matrix::new(3, 3) .with(Style::modern()) .with(Highlight::new(Frame).border( Border::filled('+') .corner_top_left('*') .corner_top_right('#') .corner_bottom_left('@') .corner_bottom_right('.'), )), "*++++++++++++++++++++++++++++++++++++#" "+ N │ column 0 │ column 1 │ column 2 +" "+───┼──────────┼──────────┼──────────+" "+ 0 │ 0-0 │ 0-1 │ 0-2 +" "+───┼──────────┼──────────┼──────────+" "+ 1 │ 1-0 │ 1-1 │ 1-2 +" "+───┼──────────┼──────────┼──────────+" "+ 2 │ 2-0 │ 2-1 │ 2-2 +" "@++++++++++++++++++++++++++++++++++++." ); test_table!( highlingt_full, Matrix::new(3, 3) .with(Style::modern()) .with(Highlight::new(Segment::all()).border( Border::filled('+') .corner_top_left('*') .corner_top_right('#') .corner_bottom_left('@') .corner_bottom_right('.'), )), "*++++++++++++++++++++++++++++++++++++#" "+ N │ column 0 │ column 1 │ column 2 +" "+───┼──────────┼──────────┼──────────+" "+ 0 │ 0-0 │ 0-1 │ 0-2 +" "+───┼──────────┼──────────┼──────────+" "+ 1 │ 1-0 │ 1-1 │ 1-2 +" "+───┼──────────┼──────────┼──────────+" "+ 2 │ 2-0 │ 2-1 │ 2-2 +" "@++++++++++++++++++++++++++++++++++++." ); test_table!( highlingt_single_column, Matrix::table(3, 0) .with(Style::modern()) .with(Highlight::new(Cell::new(0, 0)).border(Border::new().left('*').top('x'))) .with(Highlight::new(Rows::new(1..3)).border(Border::new().left('n'))), "┌xxx┐" "* N │" "├───┤" "n 0 │" "n───┤" "n 1 │" "├───┤" "│ 2 │" "└───┘" ); test_table!( highlingt_several_times, Matrix::new(3, 3) .with(Style::modern()) .with(Highlight::outline(Frame, '*')) .with(Highlight::outline(Cell::new(1, 1), '#')) .with(Highlight::outline(Columns::single(3), 'x')), "**************************xxxxxxxxxxxx" "* N │ column 0 │ column 1 x column 2 x" "*───############──────────x──────────x" "* 0 # 0-0 # 0-1 x 0-2 x" "*───############──────────x──────────x" "* 1 │ 1-0 │ 1-1 x 1-2 x" "*───┼──────────┼──────────x──────────x" "* 2 │ 2-0 │ 2-1 x 2-2 x" "**************************xxxxxxxxxxxx" ); // @todo // // #[test] // fn highlingt_empty_border() { // let data = create_vector::<3, 3>(); // let table = Table::new(&data) // .with(Style::modern()) // .with(Highlight::border(Frame, Border::empty())) // .to_string(); // let expected = static_table!( // " N │ column 0 │ column 1 │ column 2 " // "─── ──────────" // " 0 0-0 │ 0-1 0-2 " // "─── ──────────┼────────── ──────────" // " 1 1-0 │ 1-1 1-2 " // "─── ──────────" // " 2 │ 2-0 │ 2-1 │ 2-2 " // ); // assert_eq!(table, expected); // } #[test] fn highlingt_complex_figures() { macro_rules! test_highlight { ($object:expr, $expected:expr,) => { let border = Border::filled('+') .corner_top_left('*') .corner_top_right('#') .corner_bottom_left('@') .corner_bottom_right('.'); let table = Matrix::new(3, 3) .with(Style::modern()) .with(Highlight::new($object).border(border)) .to_string(); assert_eq!(table, $expected); }; } test_highlight!( Segment::all().not(Segment::new(2.., 1..3)), static_table!( "*++++++++++++++++++++++++++++++++++++#" "+ N │ column 0 │ column 1 │ column 2 +" "+───┼──────────┼──────────┼──────────+" "+ 0 │ 0-0 │ 0-1 │ 0-2 +" "+───*+++++++++++++++++++++#──────────+" "+ 1 + 1-0 │ 1-1 + 1-2 +" "+───+──────────┼──────────+──────────+" "+ 2 + 2-0 │ 2-1 + 2-2 +" "@+++.──────────┴──────────@++++++++++." ), ); test_highlight!( Segment::all() .not(Segment::new(0..1, 1..3)) .not(Columns::single(0)), static_table!( "┌───┬──────────┬──────────*++++++++++#" "│ N │ column 0 │ column 1 + column 2 +" "├───*+++++++++++++++++++++.──────────+" "│ 0 + 0-0 │ 0-1 │ 0-2 +" "├───+──────────┼──────────┼──────────+" "│ 1 + 1-0 │ 1-1 │ 1-2 +" "├───+──────────┼──────────┼──────────+" "│ 2 + 2-0 │ 2-1 │ 2-2 +" "└───@++++++++++++++++++++++++++++++++." ), ); test_highlight!( Segment::all().not(Segment::new(0..1, 1..3)), static_table!( "*+++#──────────┬──────────*++++++++++#" "+ N + column 0 │ column 1 + column 2 +" "+───@+++++++++++++++++++++.──────────+" "+ 0 │ 0-0 │ 0-1 │ 0-2 +" "+───┼──────────┼──────────┼──────────+" "+ 1 │ 1-0 │ 1-1 │ 1-2 +" "+───┼──────────┼──────────┼──────────+" "+ 2 │ 2-0 │ 2-1 │ 2-2 +" "@++++++++++++++++++++++++++++++++++++." ), ); test_highlight!( Segment::all().not(Segment::new(1..2, 1..3)), static_table!( "*++++++++++++++++++++++++++++++++++++#" "+ N │ column 0 │ column 1 │ column 2 +" "+───*+++++++++++++++++++++#──────────+" "+ 0 + 0-0 │ 0-1 + 0-2 +" "+───@+++++++++++++++++++++.──────────+" "+ 1 │ 1-0 │ 1-1 │ 1-2 +" "+───┼──────────┼──────────┼──────────+" "+ 2 │ 2-0 │ 2-1 │ 2-2 +" "@++++++++++++++++++++++++++++++++++++." ), ); test_highlight!( Cell::new(0, 0) .and(Cell::new(3, 3)) .and(Cell::new(0, 3)) .and(Cell::new(3, 0)), static_table!( "*+++#──────────┬──────────*++++++++++#" "+ N + column 0 │ column 1 + column 2 +" "@+++.──────────┼──────────@++++++++++." "│ 0 │ 0-0 │ 0-1 │ 0-2 │" "├───┼──────────┼──────────┼──────────┤" "│ 1 │ 1-0 │ 1-1 │ 1-2 │" "*+++#──────────┼──────────*++++++++++#" "+ 2 + 2-0 │ 2-1 + 2-2 +" "@+++.──────────┴──────────@++++++++++." ), ); test_highlight!( Rows::single(0).and(Rows::single(3)), static_table!( "*++++++++++++++++++++++++++++++++++++#" "+ N │ column 0 │ column 1 │ column 2 +" "@++++++++++++++++++++++++++++++++++++." "│ 0 │ 0-0 │ 0-1 │ 0-2 │" "├───┼──────────┼──────────┼──────────┤" "│ 1 │ 1-0 │ 1-1 │ 1-2 │" "*++++++++++++++++++++++++++++++++++++#" "+ 2 │ 2-0 │ 2-1 │ 2-2 +" "@++++++++++++++++++++++++++++++++++++." ), ); test_highlight!( Columns::single(0).and(Columns::single(3)), static_table!( "*+++#──────────┬──────────*++++++++++#" "+ N + column 0 │ column 1 + column 2 +" "+───+──────────┼──────────+──────────+" "+ 0 + 0-0 │ 0-1 + 0-2 +" "+───+──────────┼──────────+──────────+" "+ 1 + 1-0 │ 1-1 + 1-2 +" "+───+──────────┼──────────+──────────+" "+ 2 + 2-0 │ 2-1 + 2-2 +" "@+++.──────────┴──────────@++++++++++." ), ); test_highlight!( Segment::all().not(Cell::new(3, 1).and(Cell::new(3, 2))), static_table!( "*++++++++++++++++++++++++++++++++++++#" "+ N │ column 0 │ column 1 │ column 2 +" "+───┼──────────┼──────────┼──────────+" "+ 0 │ 0-0 │ 0-1 │ 0-2 +" "+───┼──────────┼──────────┼──────────+" "+ 1 │ 1-0 │ 1-1 │ 1-2 +" "+───*+++++++++++++++++++++#──────────+" "+ 2 + 2-0 │ 2-1 + 2-2 +" "@+++.──────────┴──────────@++++++++++." ), ); test_highlight!( Rows::single(0) .and(Cell::new(1, 1).and(Cell::new(1, 2))) .and(Cell::new(2, 3)), static_table!( "*++++++++++++++++++++++++++++++++++++#" "+ N │ column 0 │ column 1 │ column 2 +" "@+++#──────────┼──────────*++++++++++." "│ 0 + 0-0 │ 0-1 + 0-2 │" "├───@+++++++++++++++++++++*++++++++++#" "│ 1 │ 1-0 │ 1-1 + 1-2 +" "├───┼──────────┼──────────@++++++++++." "│ 2 │ 2-0 │ 2-1 │ 2-2 │" "└───┴──────────┴──────────┴──────────┘" ), ); test_highlight!( Segment::all() .not(Segment::new(2.., 0..3)) .not(Cell::new(1, 0)), static_table!( "*++++++++++++++++++++++++++++++++++++#" "+ N │ column 0 │ column 1 │ column 2 +" "@+++#──────────┼──────────┼──────────+" "│ 0 + 0-0 │ 0-1 │ 0-2 +" "├───@+++++++++++++++++++++#──────────+" "│ 1 │ 1-0 │ 1-1 + 1-2 +" "├───┼──────────┼──────────+──────────+" "│ 2 │ 2-0 │ 2-1 + 2-2 +" "└───┴──────────┴──────────@++++++++++." ), ); test_highlight!( Segment::all() .not(Segment::new(..1, 1..)) .not(Segment::new(1..2, 2..)) .not(Cell::new(2, 3)), static_table!( "*+++#──────────┬──────────┬──────────┐" "+ N + column 0 │ column 1 │ column 2 │" "+───@++++++++++#──────────┼──────────┤" "+ 0 │ 0-0 + 0-1 │ 0-2 │" "+───┼──────────@++++++++++#──────────┤" "+ 1 │ 1-0 │ 1-1 + 1-2 │" "+───┼──────────┼──────────@++++++++++#" "+ 2 │ 2-0 │ 2-1 │ 2-2 +" "@++++++++++++++++++++++++++++++++++++." ), ); } tabled-0.18.0/tests/settings/layout_test.rs000064400000000000000000000161641046102023000171010ustar 00000000000000#![cfg(feature = "std")] use tabled::settings::{themes::Layout, Alignment, Style}; use crate::matrix::Matrix; use testing_table::test_table; test_table!( theme_stick_left, Matrix::new(3, 3).with(Style::modern()).with(Layout::new(Alignment::left(), false)), "┌──────────┬─────┬─────┬─────┐" "│ N │ 0 │ 1 │ 2 │" "├──────────┼─────┼─────┼─────┤" "│ column 0 │ 0-0 │ 1-0 │ 2-0 │" "├──────────┼─────┼─────┼─────┤" "│ column 1 │ 0-1 │ 1-1 │ 2-1 │" "├──────────┼─────┼─────┼─────┤" "│ column 2 │ 0-2 │ 1-2 │ 2-2 │" "└──────────┴─────┴─────┴─────┘" ); test_table!( theme_stick_right, Matrix::new(3, 3).with(Style::modern()).with(Layout::new(Alignment::right(), false)), "┌─────┬─────┬─────┬──────────┐" "│ 2 │ 1 │ 0 │ N │" "├─────┼─────┼─────┼──────────┤" "│ 2-0 │ 1-0 │ 0-0 │ column 0 │" "├─────┼─────┼─────┼──────────┤" "│ 2-1 │ 1-1 │ 0-1 │ column 1 │" "├─────┼─────┼─────┼──────────┤" "│ 2-2 │ 1-2 │ 0-2 │ column 2 │" "└─────┴─────┴─────┴──────────┘" ); test_table!( theme_stick_bottom, Matrix::new(3, 3).with(Style::modern()).with(Layout::new(Alignment::bottom(), false)), "┌───┬──────────┬──────────┬──────────┐" "│ 0 │ 0-0 │ 0-1 │ 0-2 │" "├───┼──────────┼──────────┼──────────┤" "│ 1 │ 1-0 │ 1-1 │ 1-2 │" "├───┼──────────┼──────────┼──────────┤" "│ 2 │ 2-0 │ 2-1 │ 2-2 │" "├───┼──────────┼──────────┼──────────┤" "│ N │ column 0 │ column 1 │ column 2 │" "└───┴──────────┴──────────┴──────────┘" ); test_table!( theme_footer, Matrix::new(3, 3).with(Style::modern()).with(Layout::new(Alignment::top(), true)), "┌───┬──────────┬──────────┬──────────┐" "│ N │ column 0 │ column 1 │ column 2 │" "├───┼──────────┼──────────┼──────────┤" "│ 0 │ 0-0 │ 0-1 │ 0-2 │" "├───┼──────────┼──────────┼──────────┤" "│ 1 │ 1-0 │ 1-1 │ 1-2 │" "├───┼──────────┼──────────┼──────────┤" "│ 2 │ 2-0 │ 2-1 │ 2-2 │" "├───┼──────────┼──────────┼──────────┤" "│ N │ column 0 │ column 1 │ column 2 │" "└───┴──────────┴──────────┴──────────┘" ); test_table!( theme_stick_left_with_footer, Matrix::new(3, 3).with(Style::modern()).with(Layout::new(Alignment::left(), true)), "┌──────────┬─────┬─────┬─────┬──────────┐" "│ N │ 0 │ 1 │ 2 │ N │" "├──────────┼─────┼─────┼─────┼──────────┤" "│ column 0 │ 0-0 │ 1-0 │ 2-0 │ column 0 │" "├──────────┼─────┼─────┼─────┼──────────┤" "│ column 1 │ 0-1 │ 1-1 │ 2-1 │ column 1 │" "├──────────┼─────┼─────┼─────┼──────────┤" "│ column 2 │ 0-2 │ 1-2 │ 2-2 │ column 2 │" "└──────────┴─────┴─────┴─────┴──────────┘" ); test_table!( theme_stick_right_with_footer, Matrix::new(3, 3).with(Style::modern()).with(Layout::new(Alignment::right(), true)), "┌──────────┬─────┬─────┬─────┬──────────┐" "│ N │ 2 │ 1 │ 0 │ N │" "├──────────┼─────┼─────┼─────┼──────────┤" "│ column 0 │ 2-0 │ 1-0 │ 0-0 │ column 0 │" "├──────────┼─────┼─────┼─────┼──────────┤" "│ column 1 │ 2-1 │ 1-1 │ 0-1 │ column 1 │" "├──────────┼─────┼─────┼─────┼──────────┤" "│ column 2 │ 2-2 │ 1-2 │ 0-2 │ column 2 │" "└──────────┴─────┴─────┴─────┴──────────┘" ); test_table!( theme_stick_bottom_with_footer, Matrix::new(3, 3).with(Style::modern()).with(Layout::new(Alignment::bottom(), true)), "┌───┬──────────┬──────────┬──────────┐" "│ N │ column 0 │ column 1 │ column 2 │" "├───┼──────────┼──────────┼──────────┤" "│ 0 │ 0-0 │ 0-1 │ 0-2 │" "├───┼──────────┼──────────┼──────────┤" "│ 1 │ 1-0 │ 1-1 │ 1-2 │" "├───┼──────────┼──────────┼──────────┤" "│ 2 │ 2-0 │ 2-1 │ 2-2 │" "├───┼──────────┼──────────┼──────────┤" "│ N │ column 0 │ column 1 │ column 2 │" "└───┴──────────┴──────────┴──────────┘" ); test_table!( theme_stick_1x1, Matrix::new(0, 0).with(Layout::new(Alignment::left(), false)), "+---+" "| N |" "+---+" ); test_table!( theme_stick_empty, Matrix::empty().with(Layout::new(Alignment::left(), false)), "" ); tabled-0.18.0/tests/settings/margin_test.rs000064400000000000000000000166061046102023000170420ustar 00000000000000#![cfg(feature = "std")] use tabled::settings::{object::Cell, Highlight, Margin, MarginColor, Modify, Span, Style, Width}; use crate::matrix::Matrix; use testing_table::{assert_table, assert_width, test_table}; #[cfg(feature = "ansi")] use tabled::settings::Color; test_table!( margin_with_table_based_on_grid_borders, Matrix::new(3, 3) .with(Style::extended()) .with(Highlight::outline(Cell::new(0, 0), '+')) .with(Highlight::outline(Cell::new(1, 1), '*')) .with(Margin::new(1, 2, 1, 2).fill('>', '<', 'V', '^')), "VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV" ">+++++══════════╦══════════╦══════════╗<<" ">+ N + column 0 ║ column 1 ║ column 2 ║<<" ">++++************══════════╬══════════╣<<" ">║ 0 * 0-0 * 0-1 ║ 0-2 ║<<" ">╠═══************══════════╬══════════╣<<" ">║ 1 ║ 1-0 ║ 1-1 ║ 1-2 ║<<" ">╠═══╬══════════╬══════════╬══════════╣<<" ">║ 2 ║ 2-0 ║ 2-1 ║ 2-2 ║<<" ">╚═══╩══════════╩══════════╩══════════╝<<" "^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^" "^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^" ); test_table!( margin_without_table_based_on_grid_borders, Matrix::new(3, 3) .insert((3, 2).into(), "https://\nwww\n.\nredhat\n.com\n/en") .with(Style::psql()) .with(Modify::new(Cell::new(3, 2)).with(Span::column(2))) .with(Margin::new(1, 1, 1, 1).fill('>', '<', 'V', '^')), "VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV" "> N | column 0 | column 1 | column 2 <" ">---+----------+----------+----------<" "> 0 | 0-0 | 0-1 | 0-2 <" "> 1 | 1-0 | 1-1 | 1-2 <" "> 2 | 2-0 | https:// <" "> | | www <" "> | | . <" "> | | redhat <" "> | | .com <" "> | | /en <" "^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^" ); test_table!( table_with_empty_margin, Matrix::new(3, 3) .insert((3, 2).into(), "https://\nwww\n.\nredhat\n.com\n/en") .with(Style::psql()) .with(Modify::new(Cell::new(3, 2)).with(Span::column(2))) .with(Margin::new(0, 0, 0, 0).fill('>', '<', 'V', '^')), " N | column 0 | column 1 | column 2 " "---+----------+----------+----------" " 0 | 0-0 | 0-1 | 0-2 " " 1 | 1-0 | 1-1 | 1-2 " " 2 | 2-0 | https:// " " | | www " " | | . " " | | redhat " " | | .com " " | | /en " ); #[test] fn table_with_margin_and_min_width() { let table = Matrix::new(3, 3) .with(Style::psql()) .with(Modify::new(Cell::new(1, 1)).with(Span::column(2))) .with(Margin::new(1, 1, 1, 1).fill('>', '<', 'V', '^')) .with(Width::truncate(20)) .to_string(); assert_table!( table, "VVVVVVVVVVVVVVVVVVVV" "> | co | co | col <" ">--+----+----+-----<" "> | 0-0 | 0-2 <" "> | 1- | 1- | 1-2 <" "> | 2- | 2- | 2-2 <" "^^^^^^^^^^^^^^^^^^^^" ); assert_width!(table, 20); } #[test] fn table_with_margin_and_max_width() { let table = Matrix::new(3, 3) .with(Style::psql()) .with(Modify::new(Cell::new(1, 1)).with(Span::column(2))) .with(Margin::new(1, 1, 1, 1).fill('>', '<', 'V', '^')) .with(Width::increase(50)) .to_string(); assert_width!(table, 50); assert_table!( table, "VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV" "> N | column 0 | column 1 | column 2 <" ">------+-------------+-------------+-------------<" "> 0 | 0-0 | 0-2 <" "> 1 | 1-0 | 1-1 | 1-2 <" "> 2 | 2-0 | 2-1 | 2-2 <" "^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^" ); } #[test] #[ignore = "It's not yet clear what to do with such spans"] fn table_0_spanned_with_width() { let table = Matrix::table(0, 0) .with(Modify::new(Cell::new(0, 0)).with(Span::column(0))) .with(Width::increase(50)) .to_string(); assert_eq!(table, "++\n|\n++\n"); let table = Matrix::table(0, 0) .with(Modify::new(Cell::new(0, 0)).with(Span::column(0))) .with(Width::truncate(50)) .to_string(); assert_eq!(table, "++\n|\n++\n"); } #[test] fn margin_color_test_not_colored_feature() { use tabled::settings::Color; let table = Matrix::new(3, 3) .with(Style::psql()) .with(Margin::new(2, 2, 2, 2).fill('>', '<', 'V', '^')) .with(MarginColor::new( Color::BG_GREEN, Color::BG_YELLOW, Color::BG_RED, Color::BG_BLUE, )) .to_string(); assert_table!( table, "\u{1b}[41mVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV\u{1b}[49m" "\u{1b}[41mVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV\u{1b}[49m" "\u{1b}[42m>>\u{1b}[49m N | column 0 | column 1 | column 2 \u{1b}[43m<<\u{1b}[49m" "\u{1b}[42m>>\u{1b}[49m---+----------+----------+----------\u{1b}[43m<<\u{1b}[49m" "\u{1b}[42m>>\u{1b}[49m 0 | 0-0 | 0-1 | 0-2 \u{1b}[43m<<\u{1b}[49m" "\u{1b}[42m>>\u{1b}[49m 1 | 1-0 | 1-1 | 1-2 \u{1b}[43m<<\u{1b}[49m" "\u{1b}[42m>>\u{1b}[49m 2 | 2-0 | 2-1 | 2-2 \u{1b}[43m<<\u{1b}[49m" "\u{1b}[44m^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\u{1b}[49m" "\u{1b}[44m^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\u{1b}[49m" ); } #[cfg(feature = "ansi")] #[test] fn margin_color_test() { let table = Matrix::new(3, 3) .with(Style::psql()) .with(Margin::new(2, 2, 2, 2).fill('>', '<', 'V', '^')) .with(MarginColor::new( Color::FG_RED | Color::BOLD, Color::FG_GREEN, Color::FG_RED | Color::BG_BLUE | Color::BOLD, Color::FG_BLUE | Color::BG_YELLOW, )) .to_string(); assert_table!( table, "\u{1b}[31m\u{1b}[44m\u{1b}[1mVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV\u{1b}[39m\u{1b}[49m\u{1b}[22m" "\u{1b}[31m\u{1b}[44m\u{1b}[1mVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV\u{1b}[39m\u{1b}[49m\u{1b}[22m" "\u{1b}[31m\u{1b}[1m>>\u{1b}[39m\u{1b}[22m N | column 0 | column 1 | column 2 \u{1b}[32m<<\u{1b}[39m" "\u{1b}[31m\u{1b}[1m>>\u{1b}[39m\u{1b}[22m---+----------+----------+----------\u{1b}[32m<<\u{1b}[39m" "\u{1b}[31m\u{1b}[1m>>\u{1b}[39m\u{1b}[22m 0 | 0-0 | 0-1 | 0-2 \u{1b}[32m<<\u{1b}[39m" "\u{1b}[31m\u{1b}[1m>>\u{1b}[39m\u{1b}[22m 1 | 1-0 | 1-1 | 1-2 \u{1b}[32m<<\u{1b}[39m" "\u{1b}[31m\u{1b}[1m>>\u{1b}[39m\u{1b}[22m 2 | 2-0 | 2-1 | 2-2 \u{1b}[32m<<\u{1b}[39m" "\u{1b}[34m\u{1b}[43m^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\u{1b}[39m\u{1b}[49m" "\u{1b}[34m\u{1b}[43m^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\u{1b}[39m\u{1b}[49m" ); } tabled-0.18.0/tests/settings/merge_test.rs000064400000000000000000000107511046102023000166570ustar 00000000000000#![cfg(feature = "std")] use tabled::{settings::merge::Merge, Table}; use testing_table::test_table; test_table!( merge_horizontal, Table::new([[0, 1, 1], [1, 1, 2], [1, 1, 1]]).with(Merge::horizontal()), "+---+---+---+" "| 0 | 1 | 2 |" "+---+---+---+" "| 0 | 1 |" "+---+---+---+" "| 1 | 2 |" "+---+---+---+" "| 1 |" "+---+---+---+" ); test_table!( merge_horizontal_with_no_duplicates, Table::new([[0, 1, 2], [0, 1, 2], [0, 1, 2]]).with(Merge::horizontal()), "+---+---+---+" "| 0 | 1 | 2 |" "+---+---+---+" "| 0 | 1 | 2 |" "+---+---+---+" "| 0 | 1 | 2 |" "+---+---+---+" "| 0 | 1 | 2 |" "+---+---+---+" ); test_table!( merge_horizontal_empty, Table::new([[0usize; 0]]).with(Merge::horizontal()), "" ); test_table!( merge_vertical_0, Table::new([[0, 3, 5], [0, 3, 3], [0, 2, 3]]).with(Merge::vertical()), "+---+---+---+" "| 0 | 1 | 2 |" "+ +---+---+" "| | 3 | 5 |" "+ + +---+" "+ +---+ 3 +" "| | 2 | |" "+---+---+---+" ); test_table!( merge_vertical_1, Table::new([[0, 3, 2], [0, 3, 3], [0, 2, 3]]).with(Merge::vertical()), "+---+---+---+" "| 0 | 1 | 2 |" "+ +---+ +" "+ + 3 +---+" "+ +---+ 3 +" "| | 2 | |" "+---+---+---+" ); test_table!( merge_vertical_with_no_duplicates, Table::new([[5; 3], [15; 3], [115; 3]]).with(Merge::vertical()), "+-----+-----+-----+" "| 0 | 1 | 2 |" "+-----+-----+-----+" "| 5 | 5 | 5 |" "+-----+-----+-----+" "| 15 | 15 | 15 |" "+-----+-----+-----+" "| 115 | 115 | 115 |" "+-----+-----+-----+" ); test_table!( merge_vertical_empty, Table::new([[0usize; 0]]).with(Merge::vertical()), "" ); test_table!( merge_horizontal_and_vertical_0, Table::new([[3, 3, 5], [3, 7, 8], [9, 10, 11]]).with(Merge::horizontal()).with(Merge::vertical()), "+---+----+----+" "| 0 | 1 | 2 |" "+---+----+----+" "| 3 | 5 |" "+---+----+----+" "| 3 | 7 | 8 |" "+---+----+----+" "| 9 | 10 | 11 |" "+---+----+----+" ); test_table!( merge_horizontal_and_vertical_1, Table::new([[0, 1, 1], [1, 1, 2], [1, 1, 1]]).with(Merge::horizontal()).with(Merge::vertical()), "+---+---+---+" "| 0 | 1 | 2 |" "+ +---+---+" "| | 1 |" "+---+---+---+" "| 1 | 2 |" "+---+---+---+" "| 1 |" "+---+---+---+" ); test_table!( merge_horizontal_and_vertical_2, Table::new([[3, 4, 5], [3, 3, 8], [3, 10, 11]]).with(Merge::horizontal()).with(Merge::vertical()), "+---+----+----+" "| 0 | 1 | 2 |" "+---+----+----+" "| 3 | 4 | 5 |" "+---+----+----+" "| 3 | 8 |" "+---+----+----+" "| 3 | 10 | 11 |" "+---+----+----+" ); test_table!( merge_horizontal_and_vertical_3, Table::new([[3, 4, 5], [3, 3, 8], [3, 10, 11]]).with(Merge::vertical()).with(Merge::horizontal()), "+---+----+----+" "| 0 | 1 | 2 |" "+---+----+----+" "| 3 | 4 | 5 |" "+ +----+----+" "| | 3 | 8 |" "+ +----+----+" "| | 10 | 11 |" "+---+----+----+" ); test_table!( merge_horizontal_and_vertical_4, Table::new([[3, 4, 5], [4, 4, 8], [3, 4, 11]]).with(Merge::vertical()).with(Merge::horizontal()), "+---+---+----+" "| 0 | 1 | 2 |" "+---+---+----+" "| 3 | 4 | 5 |" "+---+ +----+" "| 4 | | 8 |" "+---+ +----+" "| 3 | | 11 |" "+---+---+----+" ); test_table!( merge_horizontal_and_vertical_5, Table::new([[3, 4, 4], [4, 4, 8], [3, 4, 11]]).with(Merge::vertical()).with(Merge::horizontal()), "+---+---+----+" "| 0 | 1 | 2 |" "+---+---+----+" "| 3 | 4 | 4 |" "+---+ +----+" "| 4 | | 8 |" "+---+ +----+" "| 3 | | 11 |" "+---+---+----+" ); test_table!( merge_horizontal_and_vertical_6, Table::new([[4, 4, 4], [5, 4, 8], [3, 4, 11]]).with(Merge::vertical()).with(Merge::horizontal()), "+---+---+----+" "| 0 | 1 | 2 |" "+---+---+----+" "| 4 | 4 | 4 |" "+---+ +----+" "| 5 | | 8 |" "+---+ +----+" "| 3 | | 11 |" "+---+---+----+" ); test_table!( merge_horizontal_and_vertical_7, Table::new([[0, 0, 0], [0, 0, 1], [2, 0, 0]]).with(Merge::horizontal()).with(Merge::vertical()), "+---+---+---+" "| 0 | 1 | 2 |" "+---+---+---+" "| 0 |" "+---+---+---+" "| 0 | 1 |" "+---+---+---+" "| 2 | 0 |" "+---+---+---+" ); tabled-0.18.0/tests/settings/mod.rs000064400000000000000000000007361046102023000153020ustar 00000000000000mod alignment_test; mod color_test; mod colorization; mod column_names_test; mod concat_test; mod disable_test; mod duplicate_test; mod extract_test; mod format_test; mod formatting_test; mod height_test; mod highlingt_test; mod layout_test; mod margin_test; mod merge_test; mod object_test; mod padding_test; mod panel_test; mod render_settings; mod reverse_test; mod rotate_test; mod shadow_test; mod span_test; mod split_test; mod style_test; mod theme_test; mod width_test; tabled-0.18.0/tests/settings/object_test.rs000064400000000000000000000032201046102023000170170ustar 00000000000000#![cfg(feature = "std")] use tabled::grid::config::Entity; use tabled::settings::{ object::{Columns, Object, ObjectIterator, Segment}, Alignment, Style, }; use crate::matrix::Matrix; use testing_table::test_table; // todo: Columns::all() test_table!( skip, Matrix::new(3, 3).with(Style::psql()).modify(Columns::new(..).not(Columns::first()).skip(2), Alignment::right()), " N | column 0 | column 1 | column 2 " "---+----------+----------+----------" " 0 | 0-0 | 0-1 | 0-2 " " 1 | 1-0 | 1-1 | 1-2 " " 2 | 2-0 | 2-1 | 2-2 " ); test_table!( skip_segment_all, Matrix::new(3, 3).with(Style::psql()).modify(Segment::all().skip(1), Alignment::right()), " N | column 0 | column 1 | column 2 " "---+----------+----------+----------" " 0 | 0-0 | 0-1 | 0-2 " " 1 | 1-0 | 1-1 | 1-2 " " 2 | 2-0 | 2-1 | 2-2 " ); test_table!( step_by, Matrix::new(3, 3).with(Style::psql()).modify(Columns::new(..).step_by(3), Alignment::right()), " N | column 0 | column 1 | column 2 " "---+----------+----------+----------" " 0 | 0-0 | 0-1 | 0-2 " " 1 | 1-0 | 1-1 | 1-2 " " 2 | 2-0 | 2-1 | 2-2 " ); test_table!( filter, Matrix::new(3, 3).with(Style::psql()).modify(Columns::new(..).filter(|e| matches!(e, Entity::Column(1 | 3))), Alignment::right()), " N | column 0 | column 1 | column 2 " "---+----------+----------+----------" " 0 | 0-0 | 0-1 | 0-2 " " 1 | 1-0 | 1-1 | 1-2 " " 2 | 2-0 | 2-1 | 2-2 " ); tabled-0.18.0/tests/settings/padding_test.rs000064400000000000000000000150621046102023000171660ustar 00000000000000#![cfg(feature = "std")] use tabled::settings::{ object::{Rows, Segment}, Alignment, Modify, Padding, Style, }; use crate::matrix::Matrix; use testing_table::test_table; #[cfg(feature = "ansi")] use tabled::settings::{Color, PaddingColor}; test_table!( padding, Matrix::new(3, 3) .with(Style::psql()) .with(Modify::new(Segment::all()).with(Alignment::left())) .with(Modify::new(Rows::new(1..)).with(Padding::new(1, 1, 0, 2))), " N | column 0 | column 1 | column 2 " "---+----------+----------+----------" " 0 | 0-0 | 0-1 | 0-2 " " | | | " " | | | " " 1 | 1-0 | 1-1 | 1-2 " " | | | " " | | | " " 2 | 2-0 | 2-1 | 2-2 " " | | | " " | | | " ); test_table!( padding_with_set_characters, Matrix::new(3, 3) .with(Style::psql()) .with(Modify::new(Segment::all()).with(Padding::new(1, 2, 1, 1).fill('>', '<', 'V', '^'))), "VVVV|VVVVVVVVVVV|VVVVVVVVVVV|VVVVVVVVVVV" ">N<<|>column 0<<|>column 1<<|>column 2<<" "^^^^|^^^^^^^^^^^|^^^^^^^^^^^|^^^^^^^^^^^" "----+-----------+-----------+-----------" "VVVV|VVVVVVVVVVV|VVVVVVVVVVV|VVVVVVVVVVV" ">0<<|> 0-0 <<|> 0-1 <<|> 0-2 <<" "^^^^|^^^^^^^^^^^|^^^^^^^^^^^|^^^^^^^^^^^" "VVVV|VVVVVVVVVVV|VVVVVVVVVVV|VVVVVVVVVVV" ">1<<|> 1-0 <<|> 1-1 <<|> 1-2 <<" "^^^^|^^^^^^^^^^^|^^^^^^^^^^^|^^^^^^^^^^^" "VVVV|VVVVVVVVVVV|VVVVVVVVVVV|VVVVVVVVVVV" ">2<<|> 2-0 <<|> 2-1 <<|> 2-2 <<" "^^^^|^^^^^^^^^^^|^^^^^^^^^^^|^^^^^^^^^^^" ); test_table!( padding_with_set_characters_and_zero_ident, Matrix::new(3, 3) .with(Style::psql()) .with(Modify::new(Segment::all()).with(Padding::zero().fill('>', '<', '^', 'V'))), "N|column 0|column 1|column 2" "-+--------+--------+--------" "0| 0-0 | 0-1 | 0-2 " "1| 1-0 | 1-1 | 1-2 " "2| 2-0 | 2-1 | 2-2 " ); test_table!( padding_multiline, Matrix::new(3, 3) .with(Style::psql()) .with(Modify::new(Rows::new(1..)).with(Padding::new(1, 1, 1, 1))), " N | column 0 | column 1 | column 2 " "---+----------+----------+----------" " | | | " " 0 | 0-0 | 0-1 | 0-2 " " | | | " " | | | " " 1 | 1-0 | 1-1 | 1-2 " " | | | " " | | | " " 2 | 2-0 | 2-1 | 2-2 " " | | | " ); test_table!( padding_multiline_with_vertical_alignment, Matrix::new(3, 3) .with(Style::psql()) .with(Modify::new(Segment::all()).with(Alignment::center()).with(Alignment::center_vertical())) .with(Modify::new(Rows::new(1..)).with(Padding::new(1, 1, 1, 1))), " N | column 0 | column 1 | column 2 " "---+----------+----------+----------" " | | | " " 0 | 0-0 | 0-1 | 0-2 " " | | | " " | | | " " 1 | 1-0 | 1-1 | 1-2 " " | | | " " | | | " " 2 | 2-0 | 2-1 | 2-2 " " | | | " ); #[cfg(feature = "ansi")] test_table!( padding_color, Matrix::new(3, 3) .with(Style::psql()) .modify(Rows::new(1..), Padding::new(2, 2, 2, 2)) .modify(Rows::new(1..), PaddingColor::new( Color::BG_YELLOW, Color::BG_BLUE, Color::BG_RED, Color::BG_GREEN, )), " N | column 0 | column 1 | column 2 \n-----+----------+----------+----------\n\u{1b}[41m \u{1b}[49m|\u{1b}[41m \u{1b}[49m|\u{1b}[41m \u{1b}[49m|\u{1b}[41m \u{1b}[49m\n\u{1b}[41m \u{1b}[49m|\u{1b}[41m \u{1b}[49m|\u{1b}[41m \u{1b}[49m|\u{1b}[41m \u{1b}[49m\n\u{1b}[43m \u{1b}[49m0\u{1b}[44m \u{1b}[49m|\u{1b}[43m \u{1b}[49m 0-0 \u{1b}[44m \u{1b}[49m|\u{1b}[43m \u{1b}[49m 0-1 \u{1b}[44m \u{1b}[49m|\u{1b}[43m \u{1b}[49m 0-2 \u{1b}[44m \u{1b}[49m\n\u{1b}[42m \u{1b}[49m|\u{1b}[42m \u{1b}[49m|\u{1b}[42m \u{1b}[49m|\u{1b}[42m \u{1b}[49m\n\u{1b}[42m \u{1b}[49m|\u{1b}[42m \u{1b}[49m|\u{1b}[42m \u{1b}[49m|\u{1b}[42m \u{1b}[49m\n\u{1b}[41m \u{1b}[49m|\u{1b}[41m \u{1b}[49m|\u{1b}[41m \u{1b}[49m|\u{1b}[41m \u{1b}[49m\n\u{1b}[41m \u{1b}[49m|\u{1b}[41m \u{1b}[49m|\u{1b}[41m \u{1b}[49m|\u{1b}[41m \u{1b}[49m\n\u{1b}[43m \u{1b}[49m1\u{1b}[44m \u{1b}[49m|\u{1b}[43m \u{1b}[49m 1-0 \u{1b}[44m \u{1b}[49m|\u{1b}[43m \u{1b}[49m 1-1 \u{1b}[44m \u{1b}[49m|\u{1b}[43m \u{1b}[49m 1-2 \u{1b}[44m \u{1b}[49m\n\u{1b}[42m \u{1b}[49m|\u{1b}[42m \u{1b}[49m|\u{1b}[42m \u{1b}[49m|\u{1b}[42m \u{1b}[49m\n\u{1b}[42m \u{1b}[49m|\u{1b}[42m \u{1b}[49m|\u{1b}[42m \u{1b}[49m|\u{1b}[42m \u{1b}[49m\n\u{1b}[41m \u{1b}[49m|\u{1b}[41m \u{1b}[49m|\u{1b}[41m \u{1b}[49m|\u{1b}[41m \u{1b}[49m\n\u{1b}[41m \u{1b}[49m|\u{1b}[41m \u{1b}[49m|\u{1b}[41m \u{1b}[49m|\u{1b}[41m \u{1b}[49m\n\u{1b}[43m \u{1b}[49m2\u{1b}[44m \u{1b}[49m|\u{1b}[43m \u{1b}[49m 2-0 \u{1b}[44m \u{1b}[49m|\u{1b}[43m \u{1b}[49m 2-1 \u{1b}[44m \u{1b}[49m|\u{1b}[43m \u{1b}[49m 2-2 \u{1b}[44m \u{1b}[49m\n\u{1b}[42m \u{1b}[49m|\u{1b}[42m \u{1b}[49m|\u{1b}[42m \u{1b}[49m|\u{1b}[42m \u{1b}[49m\n\u{1b}[42m \u{1b}[49m|\u{1b}[42m \u{1b}[49m|\u{1b}[42m \u{1b}[49m|\u{1b}[42m \u{1b}[49m" ); test_table!( padding_table, Matrix::new(3, 3) .with(Style::psql()) .with(Padding::new(1, 1, 0, 2)), " N | column 0 | column 1 | column 2 " " | | | " " | | | " "---+----------+----------+----------" " 0 | 0-0 | 0-1 | 0-2 " " | | | " " | | | " " 1 | 1-0 | 1-1 | 1-2 " " | | | " " | | | " " 2 | 2-0 | 2-1 | 2-2 " " | | | " " | | | " ); tabled-0.18.0/tests/settings/panel_test.rs000064400000000000000000000333261046102023000166620ustar 00000000000000#![cfg(feature = "std")] use std::{collections::HashMap, iter::FromIterator}; use tabled::settings::{ object::{Cell, Object, Rows, Segment}, style::{BorderSpanCorrection, HorizontalLine, Style}, themes::Theme, Alignment, Highlight, Modify, Panel, Span, Width, }; use crate::matrix::Matrix; use testing_table::test_table; test_table!( panel_has_no_style_by_default, Matrix::new(3, 3).with(Style::psql()).with(Panel::horizontal(0,"Linux Distributions")), " Linux Distributions " "---+----------+----------+----------" " N | column 0 | column 1 | column 2 " " 0 | 0-0 | 0-1 | 0-2 " " 1 | 1-0 | 1-1 | 1-2 " " 2 | 2-0 | 2-1 | 2-2 " ); test_table!( highlight_panel_0, Matrix::new(3, 3) .with(Panel::horizontal(0,"Linux Distributions")) .with(Style::psql()) .with(Highlight::outline(Cell::new(0, 0), '#')), "##### " "# Linux Distributions " "#####----------+----------+----------" " N | column 0 | column 1 | column 2 " " 0 | 0-0 | 0-1 | 0-2 " " 1 | 1-0 | 1-1 | 1-2 " " 2 | 2-0 | 2-1 | 2-2 " ); test_table!( highlight_panel_1, Matrix::new(3, 3) .with(Panel::horizontal(0,"Linux Distributions")) .with(Style::psql()) .with(Highlight::outline(Cell::new(0, 0), '#')) .with(Highlight::outline(Cell::new(0, 1), '#')) .with(Highlight::outline(Cell::new(0, 2), '#')) .with(Highlight::outline(Cell::new(0, 3), '#')), "######################################" "# Linux Distributions #" "######################################" " N | column 0 | column 1 | column 2 " " 0 | 0-0 | 0-1 | 0-2 " " 1 | 1-0 | 1-1 | 1-2 " " 2 | 2-0 | 2-1 | 2-2 " ); test_table!( top_panel, Matrix::new(3, 3) .with(Panel::horizontal(0,"Linux Distributions")) .with(Modify::new(Segment::all()).with(Alignment::center())) .with(Style::psql()), " Linux Distributions " "---+----------+----------+----------" " N | column 0 | column 1 | column 2 " " 0 | 0-0 | 0-1 | 0-2 " " 1 | 1-0 | 1-1 | 1-2 " " 2 | 2-0 | 2-1 | 2-2 " ); test_table!( bottom_panel, Matrix::new(3, 3) .with(Panel::horizontal(4,"Linux Distributions")) .with(Modify::new(Segment::all()).with(Alignment::center())) .with(Style::psql()), " N | column 0 | column 1 | column 2 " "---+----------+----------+----------" " 0 | 0-0 | 0-1 | 0-2 " " 1 | 1-0 | 1-1 | 1-2 " " 2 | 2-0 | 2-1 | 2-2 " " Linux Distributions " ); test_table!( inner_panel, Matrix::new(3, 3) .with(Panel::horizontal(2,"Linux Distributions")) .with(Modify::new(Rows::new(2..)).with(Alignment::center())) .with(Style::psql()), " N | column 0 | column 1 | column 2 " "---+----------+----------+----------" " 0 | 0-0 | 0-1 | 0-2 " " Linux Distributions " " 1 | 1-0 | 1-1 | 1-2 " " 2 | 2-0 | 2-1 | 2-2 " ); test_table!( header, Matrix::new(3, 3) .with(Panel::header("Linux Distributions")) .with(Style::psql()) .with(Modify::new(Rows::new(0..1)).with(Alignment::center())), " Linux Distributions " "---+----------+----------+----------" " N | column 0 | column 1 | column 2 " " 0 | 0-0 | 0-1 | 0-2 " " 1 | 1-0 | 1-1 | 1-2 " " 2 | 2-0 | 2-1 | 2-2 " ); test_table!( footer, Matrix::new(3, 3) .with(Panel::header("Linux Distributions")) .with(Panel::footer("The end")) .with(Style::psql()) .with(Modify::new(Rows::first().and(Rows::last())).with(Alignment::center())), " Linux Distributions " "---+----------+----------+----------" " N | column 0 | column 1 | column 2 " " 0 | 0-0 | 0-1 | 0-2 " " 1 | 1-0 | 1-1 | 1-2 " " 2 | 2-0 | 2-1 | 2-2 " " The end " ); test_table!( panel_style_uses_most_left_and_right_cell_styles, Matrix::iter([(0, 1)]).with(Panel::horizontal(0,"Numbers")).with(Style::modern()), "┌─────┬─────┐" "│ Numbers │" "├─────┼─────┤" "│ i32 │ i32 │" "├─────┼─────┤" "│ 0 │ 1 │" "└─────┴─────┘" ); test_table!( panel_style_change, Matrix::iter([(0, 1)]) .with(Panel::horizontal(0,"Numbers")) .with({ let mut style = Theme::from_style(Style::modern()); style.set_borders_intersection_top('─'); style.set_horizontal_lines(HashMap::from_iter([(1, HorizontalLine::inherit(Style::modern()).intersection('┬').into_inner())])); style }) .with(Modify::new(Cell::new(0, 0)).with(Alignment::center())), "┌───────────┐" "│ Numbers │" "├─────┬─────┤" "│ i32 │ i32 │" "├─────┼─────┤" "│ 0 │ 1 │" "└─────┴─────┘" ); test_table!( panel_style_uses_most_left_and_right_cell_styles_correct, Matrix::iter([(0, 1)]) .with(Panel::horizontal(0,"Numbers")) .with(Style::modern()) .with(BorderSpanCorrection), "┌───────────┐" "│ Numbers │" "├─────┬─────┤" "│ i32 │ i32 │" "├─────┼─────┤" "│ 0 │ 1 │" "└─────┴─────┘" ); test_table!( panel_style_change_correct, Matrix::iter([(0, 1)]) .with(Panel::horizontal(0, "Numbers")) .with(Style::modern().intersection_top('─').horizontals([(1, HorizontalLine::inherit(Style::modern()).intersection('┬'))])) .with(BorderSpanCorrection) .with(Modify::new(Cell::new(0, 0)).with(Alignment::center())), "┌───────────┐" "│ Numbers │" "├───────────┤" // it's different because we use a top_intersection char by default when making style for `Panel`s. "│ i32 │ i32 │" "├─────┼─────┤" "│ 0 │ 1 │" "└─────┴─────┘" ); test_table!( panel_in_single_column, #[allow(unknown_lints)] #[allow(clippy::needless_borrow)] #[allow(clippy::needless_borrows_for_generic_args)] Matrix::iter(&[(0)]).with(Panel::horizontal(0,"Numbers")).with(Style::modern()), "┌─────────┐" "│ Numbers │" "├─────────┤" "│ i32 │" "├─────────┤" "│ 0 │" "└─────────┘" ); test_table!( panel_vertical_0, Matrix::new(3, 3).with(Style::psql()).with(Panel::vertical(0,"Linux Distributions")), " Linux Distributions | N | column 0 | column 1 | column 2 " " +---+----------+----------+----------" " | 0 | 0-0 | 0-1 | 0-2 " " | 1 | 1-0 | 1-1 | 1-2 " " | 2 | 2-0 | 2-1 | 2-2 " ); test_table!( panel_vertical_1, Matrix::new(3, 3).with(Style::psql()).with(Panel::vertical(1,"Linux Distributions")), " N | Linux Distributions | column 0 | column 1 | column 2 " "---+ +----------+----------+----------" " 0 | | 0-0 | 0-1 | 0-2 " " 1 | | 1-0 | 1-1 | 1-2 " " 2 | | 2-0 | 2-1 | 2-2 " ); test_table!( panel_vertical_2, Matrix::new(3, 3).with(Style::psql()).with(Panel::vertical(4,"Linux Distributions")), " N | column 0 | column 1 | column 2 | Linux Distributions " "---+----------+----------+----------+ " " 0 | 0-0 | 0-1 | 0-2 | " " 1 | 1-0 | 1-1 | 1-2 | " " 2 | 2-0 | 2-1 | 2-2 | " ); test_table!( panel_vertical_0_wrap, Matrix::new(3, 3).with(Style::psql()).with(Panel::vertical(0,"Linux Distributions")).with(Modify::new(Cell::new(0, 0)).with(Width::wrap(3))), " Lin | N | column 0 | column 1 | column 2 " " ux | | | | " " Dis | | | | " " tri +---+----------+----------+----------" " but | 0 | 0-0 | 0-1 | 0-2 " " ion | 1 | 1-0 | 1-1 | 1-2 " " s | 2 | 2-0 | 2-1 | 2-2 " ); test_table!( panel_vertical_0_wrap_0, Matrix::new(3, 3).with(Style::psql()).with(Panel::vertical(0,"Linux Distributions")).with(Modify::new(Cell::new(0, 0)).with(Width::wrap(0))), " | N | column 0 | column 1 | column 2 " " +---+----------+----------+----------" " | 0 | 0-0 | 0-1 | 0-2 " " | 1 | 1-0 | 1-1 | 1-2 " " | 2 | 2-0 | 2-1 | 2-2 " ); test_table!( panel_vertical_0_wrap_100, Matrix::new(3, 3).with(Style::psql()).with(Panel::vertical(0,"Linux Distributions")).with(Modify::new(Cell::new(0, 0)).with(Width::wrap(100))), " Linux Distributions | N | column 0 | column 1 | column 2 " " +---+----------+----------+----------" " | 0 | 0-0 | 0-1 | 0-2 " " | 1 | 1-0 | 1-1 | 1-2 " " | 2 | 2-0 | 2-1 | 2-2 " ); test_table!( panel_horizontal_set_0, Matrix::new(3, 3) .with(Style::psql()) .with(Panel::horizontal(0,"Linux Distributions")) .with(Panel::vertical(0,"asd")), " asd | Linux Distributions " " +---+----------+----------+----------" " | N | column 0 | column 1 | column 2 " " | 0 | 0-0 | 0-1 | 0-2 " " | 1 | 1-0 | 1-1 | 1-2 " " | 2 | 2-0 | 2-1 | 2-2 " ); test_table!( panel_horizontal_set_1, Matrix::new(3, 3) .with(Style::psql()) .with(Panel::horizontal(0,"Linux Distributions")) .with(Panel::vertical(0,"asd")) .with(Panel::vertical(5,"asd")) , " asd | Linux Distributions | asd " " +---+----------+----------+----------+ " " | N | column 0 | column 1 | column 2 | " " | 0 | 0-0 | 0-1 | 0-2 | " " | 1 | 1-0 | 1-1 | 1-2 | " " | 2 | 2-0 | 2-1 | 2-2 | " ); test_table!( ignore_col_span_intersect_with_other_span, Matrix::new(3, 3) .with(Style::psql()) .with(Panel::horizontal(0,"Linux Distributions")) .with(Panel::vertical(0,"asd")) .with(Panel::vertical(5,"zxc")) .with(Modify::new((1, 3)).with(Span::column(3)).with("wwwww")), " asd | Linux Distributions | zxc " " +---+----------+-------+----------+ " " | N | column 0 | wwwww | column 2 | " " | 0 | 0-0 | 0-1 | 0-2 | " " | 1 | 1-0 | 1-1 | 1-2 | " " | 2 | 2-0 | 2-1 | 2-2 | " ); test_table!( panel_horizontal_x_2, Matrix::new(3, 3) .with(Style::psql()) .with(Panel::horizontal(0,"Linux Distributions")) .with(Panel::vertical(0,"asd")) .with(Panel::vertical(5,"zxc")) .with(Modify::new((1, 3)).with(Span::column(2)).with("wwwww")), " asd | Linux Distributions | zxc " " +---+----------+-----+-----+ " " | N | column 0 | wwwww | " " | 0 | 0-0 | 0-1 | 0-2 | " " | 1 | 1-0 | 1-1 | 1-2 | " " | 2 | 2-0 | 2-1 | 2-2 | " ); test_table!( ignore_row_span_intersect_with_other_span, Matrix::new(3, 3) .with(Style::psql()) .with(Panel::horizontal(2,"Linux Distributions")) .with(Panel::vertical(0,"asd")) .with(Panel::vertical(5,"zxc")) .with(Modify::new((0, 3)).with(Span::row(4)).with("xxxxx")), " asd | N | column 0 | xxxxx | column 2 | zxc " " +---+----------+-------+----------+ " " | 0 | 0-0 | 0-1 | 0-2 | " " | Linux Distributions | " " | 1 | 1-0 | 1-1 | 1-2 | " " | 2 | 2-0 | 2-1 | 2-2 | " ); test_table!( panel_vertical_split, Matrix::new(3, 3).with(Style::psql()).with(Panel::vertical(0, "Linux Distributions").width(1)), " L | N | column 0 | column 1 | column 2 " " i | | | | " " n | | | | " " u | | | | " " x | | | | " " | | | | " " D +---+----------+----------+----------" " i | 0 | 0-0 | 0-1 | 0-2 " " s | | | | " " t | | | | " " r | | | | " " i | 1 | 1-0 | 1-1 | 1-2 " " b | | | | " " u | | | | " " t | | | | " " i | 2 | 2-0 | 2-1 | 2-2 " " o | | | | " " n | | | | " " s | | | | " ); tabled-0.18.0/tests/settings/render_settings.rs000064400000000000000000000311121046102023000177120ustar 00000000000000#![cfg(feature = "std")] use tabled::settings::{ formatting::{AlignmentStrategy, TabSize, TrimStrategy}, object::Segment, Alignment, Modify, Span, Style, }; use crate::matrix::{Matrix, MatrixList}; use testing_table::test_table; #[cfg(feature = "ansi")] use tabled::settings::Color; test_table!( alignment_per_line, Matrix::iter(multiline_data1()) .with(Style::psql()) .with(Modify::new(Segment::all()).with(Alignment::right()).with(AlignmentStrategy::PerLine)), " N | column 0 | column 1 | column 2 " "-----------+----------+----------+----------" " 0 | 0-0 | 0-1 | 0-2 " " asd | 1-0 | 1-1 | 1-2 " " 21213123 | | | " " | | | " " asdasd | | | " " | | | " " | | | " " 2 | 2-0 | https:// | 2-2 " " | | www | " " | | . | " " | | redhat | " " | | .com | " " | | /en | " ); test_table!( alignment_per_line_with_trim_0, Matrix::iter(multiline_data1()) .with(Style::psql()) .with(Alignment::right()) .with(AlignmentStrategy::PerLine) .with(TrimStrategy::Horizontal), " N | column 0 | column 1 | column 2 " "-----------+----------+----------+----------" " 0 | 0-0 | 0-1 | 0-2 " " asd | 1-0 | 1-1 | 1-2 " " 21213123 | | | " " | | | " " asdasd | | | " " | | | " " | | | " " 2 | 2-0 | https:// | 2-2 " " | | www | " " | | . | " " | | redhat | " " | | .com | " " | | /en | " ); test_table!( alignment_per_line_with_trim_1, Matrix::iter(multiline_data2()) .with(Style::psql()) .with(Modify::new(Segment::all()) .with(Alignment::center_vertical()) .with(Alignment::left()) .with(AlignmentStrategy::PerLine) .with(TrimStrategy::Both)), " N | column 0 | column 1 | column 2 " "-------------------+----------+----------+----------" " 0 | 0-0 | 0-1 | 0-2 " " | | | " " | | | " " | | | " " asd | 1-0 | 1-1 | 1-2 " " 21213123 asdasd | | | " " | | | " " | | | " " | | | " " | | https:// | " " | | www | " " 2 | 2-0 | . | 2-2 " " | | redhat | " " | | .com | " " | | /en | " ); test_table!( tab_isnot_handled_by_default_test, Matrix::iter(tab_data1()).with(Style::psql()), " N | column 0 | column 1 | column 2 \n----------------+----------+-----------+----------\n 0 | 0-0 | 0-1 | 0-2 \n 123\t123\tasdasd | 1-0 | 1-1 | 1-2 \n 2 | 2-0 | htt\tps:// | 2-2 \n | | www | \n | | . | \n | | red\that | \n | | .c\tom | \n | | /en | " ); test_table!( tab_size_test_0, Matrix::iter(tab_data1()).with(Style::psql()).with(TabSize::new(4)), " N | column 0 | column 1 | column 2 " "----------------------+----------+--------------+----------" " 0 | 0-0 | 0-1 | 0-2 " " 123 123 asdasd | 1-0 | 1-1 | 1-2 " " 2 | 2-0 | htt ps:// | 2-2 " " | | www | " " | | . | " " | | red hat | " " | | .c om | " " | | /en | " ); test_table!( tab_size_test_1, Matrix::iter(tab_data1()).with(Style::psql()).with(Modify::new(Segment::all()).with(Alignment::right())).with(TabSize::new(2)), " N | column 0 | column 1 | column 2 " "------------------+----------+------------+----------" " 0 | 0-0 | 0-1 | 0-2 " " 123 123 asdasd | 1-0 | 1-1 | 1-2 " " 2 | 2-0 | htt ps:// | 2-2 " " | | www | " " | | . | " " | | red hat | " " | | .c om | " " | | /en | " ); test_table!( tab_size_test_2, Matrix::iter(tab_data1()).with(Style::psql()).with(Modify::new(Segment::all()).with(Alignment::right())).with(TabSize::new(0)), " N | column 0 | column 1 | column 2 " "--------------+----------+----------+----------" " 0 | 0-0 | 0-1 | 0-2 " " 123123asdasd | 1-0 | 1-1 | 1-2 " " 2 | 2-0 | https:// | 2-2 " " | | www | " " | | . | " " | | redhat | " " | | .com | " " | | /en | " ); test_table!( tab_size_span_test, Matrix::iter(tab_data2()) .with(TabSize::new(4)) .with(Style::psql()) .with(Modify::new((0, 0)).with(Span::column(3))) .with(Modify::new((1, 0)).with(Span::column(2))) .with(Modify::new((2, 1)).with(Span::column(2))), " N | column 2 " "----------------------+-----+--------------+----------" " H ello World | 0-1 | 0-2 " " 123 123 asdasd | 1-0 | 1-2 " " 2 | 2-0 | htt ps:// | 2-2 " " | | www | " " | | . | " " | | red hat | " " | | .c om | " " | | /en | " ); test_table!( test_top_alignment_and_vertical_trim_1, Matrix::iter(vec![" \n\n\n Hello World"]) .with(Style::modern()) .with(Modify::new(Segment::all()).with(Alignment::top()).with(TrimStrategy::Vertical)), "┌─────────────────┐" "│ &str │" "├─────────────────┤" "│ Hello World │" "│ │" "│ │" "│ │" "└─────────────────┘" ); #[cfg(feature = "ansi")] test_table!( trim_colored_string_test_2, Matrix::iter(colored_data()) .with(Style::psql()) .with(Modify::new(Segment::all()).with(Alignment::right()).with(TrimStrategy::None)), " N | column 0 | column 1 | column 2 " "-----------+----------+----------+----------" " 0 | 0-0 | 0-1 | 0-2 " " \u{1b}[31masd\u{1b}[39m | 1-0 | 1-1 | 1-2 " " \u{1b}[31m21213123\u{1b}[39m | | | " " \u{1b}[31m\u{1b}[39m | | | " " \u{1b}[31m asdasd\u{1b}[39m | | | " " \u{1b}[31m\u{1b}[39m | | | " " 2 | 2-0 | \u{1b}[44mhttps://\u{1b}[49m | 2-2 " " | | \u{1b}[44mwww\u{1b}[49m | " " | | \u{1b}[44m.\u{1b}[49m | " " | | \u{1b}[44mredhat\u{1b}[49m | " " | | \u{1b}[44m.com\u{1b}[49m | " " | | \u{1b}[44m/en\u{1b}[49m | " ); #[cfg(feature = "ansi")] test_table!( trim_colored_string_test_1, Matrix::iter(colored_data()) .with(Style::psql()) .with( Modify::new(Segment::all()) .with(Alignment::right()) .with(TrimStrategy::Horizontal) .with(AlignmentStrategy::PerLine), ), " N | column 0 | column 1 | column 2 " "-----------+----------+----------+----------" " 0 | 0-0 | 0-1 | 0-2 " " \u{1b}[31masd\u{1b}[39m | 1-0 | 1-1 | 1-2 " " \u{1b}[31m21213123\u{1b}[39m | | | " " \u{1b}[31m\u{1b}[39m | | | " " \u{1b}[31masdasd\u{1b}[39m | | | " " \u{1b}[31m\u{1b}[39m | | | " " 2 | 2-0 | \u{1b}[44mhttps://\u{1b}[49m | 2-2 " " | | \u{1b}[44mwww\u{1b}[49m | " " | | \u{1b}[44m.\u{1b}[49m | " " | | \u{1b}[44mredhat\u{1b}[49m | " " | | \u{1b}[44m.com\u{1b}[49m | " " | | \u{1b}[44m/en\u{1b}[49m | " ); #[cfg(feature = "ansi")] test_table!( trim_colored_string_test_0, Matrix::iter(colored_data()) .with(Style::psql()) .with(Modify::new(Segment::all()).with(Alignment::right()).with(TrimStrategy::Horizontal)), " N | column 0 | column 1 | column 2 " "-----------+----------+----------+----------" " 0 | 0-0 | 0-1 | 0-2 " " \u{1b}[31masd\u{1b}[39m | 1-0 | 1-1 | 1-2 " " \u{1b}[31m21213123\u{1b}[39m | | | " " \u{1b}[31m\u{1b}[39m | | | " " \u{1b}[31masdasd\u{1b}[39m | | | " " \u{1b}[31m\u{1b}[39m | | | " " 2 | 2-0 | \u{1b}[44mhttps://\u{1b}[49m | 2-2 " " | | \u{1b}[44mwww\u{1b}[49m | " " | | \u{1b}[44m.\u{1b}[49m | " " | | \u{1b}[44mredhat\u{1b}[49m | " " | | \u{1b}[44m.com\u{1b}[49m | " " | | \u{1b}[44m/en\u{1b}[49m | " ); fn multiline_data1() -> Vec> { let mut data = Matrix::list::<3, 3>(); data[1][0] = String::from("asd\n21213123\n\n asdasd\n\n"); data[2][2] = String::from("https://\nwww\n.\nredhat\n.com\n/en"); data } fn multiline_data2() -> Vec> { let mut data = Matrix::list::<3, 3>(); data[1][0] = String::from("\n\n\nasd\n21213123 asdasd\n\n\n"); data[2][2] = String::from("https://\nwww\n.\nredhat\n.com\n/en"); data } fn tab_data1() -> Vec> { let mut data = Matrix::list::<3, 3>(); data[1][0] = String::from("123\t123\tasdasd"); data[2][2] = String::from("htt\tps://\nwww\n.\nred\that\n.c\tom\n/en"); data } fn tab_data2() -> Vec> { let mut data = Matrix::list::<3, 3>(); data[0][0] = String::from("\tH\t\tello\tWorld"); data[1][0] = String::from("123\t123\tasdasd"); data[2][2] = String::from("htt\tps://\nwww\n.\nred\that\n.c\tom\n/en"); data } #[cfg(feature = "ansi")] fn colored_data() -> Vec> { let mut data = Matrix::list::<3, 3>(); data[1][0] = Color::FG_RED.colorize("asd\n21213123\n\n asdasd\n\n"); data[2][2] = Color::BG_BLUE.colorize("https://\nwww\n.\nredhat\n.com\n/en"); data } tabled-0.18.0/tests/settings/reverse_test.rs000064400000000000000000000056551046102023000172420ustar 00000000000000#![cfg(feature = "std")] use tabled::settings::Reverse; use crate::matrix::Matrix; use testing_table::test_table; test_table!( test_0x0_reverse_rows, Matrix::empty().with(Reverse::rows(0, 0)), "" ); test_table!( test_0x0_reverse_columns, Matrix::empty().with(Reverse::columns(0, 0)), "" ); test_table!( test_3x3_reverse_rows, Matrix::iter([(123, 456, 789), (234, 567, 891)]).with(Reverse::rows(0, 0)), "+-----+-----+-----+" "| 234 | 567 | 891 |" "+-----+-----+-----+" "| 123 | 456 | 789 |" "+-----+-----+-----+" "| i32 | i32 | i32 |" "+-----+-----+-----+" ); test_table!( test_3x3_reverse_rows_skip_start, Matrix::iter([(123, 456, 789), (234, 567, 891)]).with(Reverse::rows(1, 0)), "+-----+-----+-----+" "| i32 | i32 | i32 |" "+-----+-----+-----+" "| 123 | 456 | 789 |" "+-----+-----+-----+" "| 234 | 567 | 891 |" "+-----+-----+-----+" ); test_table!( test_3x3_reverse_rows_skip_end, Matrix::iter([(123, 456, 789), (234, 567, 891)]).with(Reverse::rows(0, 1)), "+-----+-----+-----+" "| 123 | 456 | 789 |" "+-----+-----+-----+" "| i32 | i32 | i32 |" "+-----+-----+-----+" "| 234 | 567 | 891 |" "+-----+-----+-----+" ); test_table!( test_4x4_reverse_rows_skip_start_and_end, Matrix::iter([(123, 456, 789), (234, 567, 891), (345, 678, 901)]).with(Reverse::rows(1, 1)), "+-----+-----+-----+" "| i32 | i32 | i32 |" "+-----+-----+-----+" "| 123 | 456 | 789 |" "+-----+-----+-----+" "| 234 | 567 | 891 |" "+-----+-----+-----+" "| 345 | 678 | 901 |" "+-----+-----+-----+" ); test_table!( test_3x3_reverse_columns, Matrix::iter([(123, 456, 789), (234, 567, 891)]).with(Reverse::columns(0, 0)), "+-----+-----+-----+" "| i32 | i32 | i32 |" "+-----+-----+-----+" "| 789 | 456 | 123 |" "+-----+-----+-----+" "| 891 | 567 | 234 |" "+-----+-----+-----+" ); test_table!( test_3x3_reverse_columns_skip_start, Matrix::iter([(123, 456, 789), (234, 567, 891)]).with(Reverse::columns(1, 0)), "+-----+-----+-----+" "| i32 | i32 | i32 |" "+-----+-----+-----+" "| 123 | 456 | 789 |" "+-----+-----+-----+" "| 234 | 567 | 891 |" "+-----+-----+-----+" ); test_table!( test_3x3_reverse_columns_skip_end, Matrix::iter([(123, 456, 789), (234, 567, 891)]).with(Reverse::columns(0, 1)), "+-----+-----+-----+" "| i32 | i32 | i32 |" "+-----+-----+-----+" "| 456 | 123 | 789 |" "+-----+-----+-----+" "| 567 | 234 | 891 |" "+-----+-----+-----+" ); test_table!( test_4x3_reverse_columns_skip_start_and_end, Matrix::iter([(123, 456, 789, 123), (234, 567, 891, 234)]).with(Reverse::columns(1, 1)), "+-----+-----+-----+-----+" "| i32 | i32 | i32 | i32 |" "+-----+-----+-----+-----+" "| 123 | 456 | 789 | 123 |" "+-----+-----+-----+-----+" "| 234 | 567 | 891 | 234 |" "+-----+-----+-----+-----+" ); tabled-0.18.0/tests/settings/rotate_test.rs000064400000000000000000000132601046102023000170540ustar 00000000000000#![cfg(feature = "std")] // todo: add method for SPACING between cells. use tabled::settings::{ object::{Cell, Rows}, Border, Highlight, Rotate, }; use crate::matrix::Matrix; use testing_table::test_table; #[test] fn test_rotate() { let table = || Matrix::iter([(123, 456, 789), (234, 567, 891)]); assert_eq!( table() .with(Rotate::Left) .with(Rotate::Left) .with(Rotate::Left) .with(Rotate::Left) .to_string(), table().to_string() ); assert_eq!( table() .with(Rotate::Right) .with(Rotate::Right) .with(Rotate::Right) .with(Rotate::Right) .to_string(), table().to_string() ); assert_eq!( table().with(Rotate::Right).with(Rotate::Left).to_string(), table().to_string() ); assert_eq!( table().with(Rotate::Left).with(Rotate::Right).to_string(), table().to_string() ); assert_eq!( table().with(Rotate::Bottom).with(Rotate::Top).to_string(), table().to_string() ); assert_eq!( table() .with(Rotate::Bottom) .with(Rotate::Bottom) .to_string(), table().to_string() ); assert_eq!( table().with(Rotate::Top).with(Rotate::Top).to_string(), table().to_string() ); } test_table!( test_3x3_box_0, Matrix::iter([(123, 456, 789), (234, 567, 891)]).with(Rotate::Left), "+-----+-----+-----+" "| i32 | 789 | 891 |" "+-----+-----+-----+" "| i32 | 456 | 567 |" "+-----+-----+-----+" "| i32 | 123 | 234 |" "+-----+-----+-----+" ); test_table!( test_3x3_box_1, Matrix::iter([(123, 456, 789), (234, 567, 891)]).with(Rotate::Left).with(Rotate::Right).with(Rotate::Right), "+-----+-----+-----+" "| 234 | 123 | i32 |" "+-----+-----+-----+" "| 567 | 456 | i32 |" "+-----+-----+-----+" "| 891 | 789 | i32 |" "+-----+-----+-----+" ); test_table!( test_left_rotate, Matrix::iter([(123, 456, 789), (234, 567, 891), (111, 222, 333)]).with(Rotate::Left), "+-----+-----+-----+-----+" "| i32 | 789 | 891 | 333 |" "+-----+-----+-----+-----+" "| i32 | 456 | 567 | 222 |" "+-----+-----+-----+-----+" "| i32 | 123 | 234 | 111 |" "+-----+-----+-----+-----+" ); test_table!( test_right_rotate, Matrix::iter([(123, 456, 789), (234, 567, 891), (111, 222, 333)]).with(Rotate::Right), "+-----+-----+-----+-----+" "| 111 | 234 | 123 | i32 |" "+-----+-----+-----+-----+" "| 222 | 567 | 456 | i32 |" "+-----+-----+-----+-----+" "| 333 | 891 | 789 | i32 |" "+-----+-----+-----+-----+" ); test_table!( test_bottom_rotate, Matrix::iter([(123, 456, 789), (234, 567, 891), (111, 222, 333)]).with(Rotate::Bottom), "+-----+-----+-----+" "| 111 | 222 | 333 |" "+-----+-----+-----+" "| 234 | 567 | 891 |" "+-----+-----+-----+" "| 123 | 456 | 789 |" "+-----+-----+-----+" "| i32 | i32 | i32 |" "+-----+-----+-----+" ); test_table!( test_top_rotate, Matrix::iter([(123, 456, 789), (234, 567, 891), (111, 222, 333)]).with(Rotate::Top), "+-----+-----+-----+" "| 111 | 222 | 333 |" "+-----+-----+-----+" "| 234 | 567 | 891 |" "+-----+-----+-----+" "| 123 | 456 | 789 |" "+-----+-----+-----+" "| i32 | i32 | i32 |" "+-----+-----+-----+" ); test_table!( rotate_preserve_border_styles_test_0, Matrix::iter([(123, 456, 789), (234, 567, 891), (111, 222, 333)]) .with(Highlight::new(Rows::single(0)).border(Border::new().top('*'))) .with(Rotate::Left), "+*****************+-----+" "| i32 | 789 | 891 | 333 |" "+-----+-----+-----+-----+" "| i32 | 456 | 567 | 222 |" "+-----+-----+-----+-----+" "| i32 | 123 | 234 | 111 |" "+-----+-----+-----+-----+" ); // it's a correct behaviour because // when we sen bottom border of cell(0, 2) we also set top border of cell(1, 2) // // todo: determine if it's correct test_table!( rotate_preserve_border_styles_test_1, Matrix::iter([(123, 456, 789), (234, 567, 891), (111, 222, 333)]) .with(Highlight::new(Cell::new(0, 2)).border(Border::new().bottom('*'))) .with(Rotate::Left), "+-----+-----+-----+-----+" "| i32 | 789 | 891 | 333 |" "+-----+-----+*****+-----+" "| i32 | 456 | 567 | 222 |" "+-----+-----+-----+-----+" "| i32 | 123 | 234 | 111 |" "+-----+-----+-----+-----+" ); test_table!( test_left_rotate_1, Matrix::iter([(0, 1, 2, 3, 4, 5), (0, 1, 2, 3, 4, 5)]).with(Rotate::Left), "+-----+---+---+" "| i32 | 5 | 5 |" "+-----+---+---+" "| i32 | 4 | 4 |" "+-----+---+---+" "| i32 | 3 | 3 |" "+-----+---+---+" "| i32 | 2 | 2 |" "+-----+---+---+" "| i32 | 1 | 1 |" "+-----+---+---+" "| i32 | 0 | 0 |" "+-----+---+---+" ); test_table!( test_right_rotate_1, Matrix::iter([(0, 1, 2, 3, 4, 5), (0, 1, 2, 3, 4, 5)]).with(Rotate::Right), "+---+---+-----+" "| 0 | 0 | i32 |" "+---+---+-----+" "| 1 | 1 | i32 |" "+---+---+-----+" "| 2 | 2 | i32 |" "+---+---+-----+" "| 3 | 3 | i32 |" "+---+---+-----+" "| 4 | 4 | i32 |" "+---+---+-----+" "| 5 | 5 | i32 |" "+---+---+-----+" ); test_table!( test_bottom_rotate_1, Matrix::iter([(0, 1, 2, 3, 4, 5), (0, 1, 2, 3, 4, 5)]).with(Rotate::Bottom), "+-----+-----+-----+-----+-----+-----+" "| 0 | 1 | 2 | 3 | 4 | 5 |" "+-----+-----+-----+-----+-----+-----+" "| 0 | 1 | 2 | 3 | 4 | 5 |" "+-----+-----+-----+-----+-----+-----+" "| i32 | i32 | i32 | i32 | i32 | i32 |" "+-----+-----+-----+-----+-----+-----+" ); tabled-0.18.0/tests/settings/shadow_test.rs000064400000000000000000000066061046102023000170510ustar 00000000000000#![cfg(feature = "std")] use tabled::settings::{Shadow, Style}; use crate::matrix::Matrix; use testing_table::test_table; #[cfg(feature = "ansi")] use tabled::settings::Color; test_table!( test_shadow_bottom_right_0, Matrix::iter([(123, 456, 789), (234, 567, 891)]).with(Style::psql()).with(Shadow::new(1)), " i32 | i32 | i32 " "-----+-----+-----▒" " 123 | 456 | 789 ▒" " 234 | 567 | 891 ▒" " ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒" ); test_table!( test_shadow_bottom_left_0, Matrix::iter([(123, 456, 789), (234, 567, 891)]).with(Style::psql()).with(Shadow::new(1).set_left()), " i32 | i32 | i32 " "▒-----+-----+-----" "▒ 123 | 456 | 789 " "▒ 234 | 567 | 891 " "▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒ " ); test_table!( test_shadow_top_right_0, Matrix::iter([(123, 456, 789), (234, 567, 891)]).with(Style::psql()).with(Shadow::new(1).set_top()), " ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒" " i32 | i32 | i32 ▒" "-----+-----+-----▒" " 123 | 456 | 789 ▒" " 234 | 567 | 891 " ); test_table!( test_shadow_top_left_0, Matrix::iter([(123, 456, 789), (234, 567, 891)]).with(Style::psql()).with(Shadow::new(1).set_top().set_left()), "▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒ " "▒ i32 | i32 | i32 " "▒-----+-----+-----" "▒ 123 | 456 | 789 " " 234 | 567 | 891 " ); test_table!( test_shadow_set_fill, Matrix::iter([(123, 456, 789), (234, 567, 891)]).with(Shadow::new(1).set_fill('▓')), "+-----+-----+-----+ " "| i32 | i32 | i32 |▓" "+-----+-----+-----+▓" "| 123 | 456 | 789 |▓" "+-----+-----+-----+▓" "| 234 | 567 | 891 |▓" "+-----+-----+-----+▓" " ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓" ); test_table!( test_shadow_size_1, Matrix::iter([(123, 456, 789), (234, 567, 891)]).with(Shadow::new(2).set_fill('▓')), "+-----+-----+-----+ " "| i32 | i32 | i32 |▓▓" "+-----+-----+-----+▓▓" "| 123 | 456 | 789 |▓▓" "+-----+-----+-----+▓▓" "| 234 | 567 | 891 |▓▓" "+-----+-----+-----+▓▓" " ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓" " ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓" ); test_table!( test_shadow_set_offset_0, Matrix::iter([(123, 456, 789), (234, 567, 891)]).with(Shadow::new(2).set_offset(3)), "+-----+-----+-----+ " "| i32 | i32 | i32 | " "+-----+-----+-----+ " "| 123 | 456 | 789 |▒▒" "+-----+-----+-----+▒▒" "| 234 | 567 | 891 |▒▒" "+-----+-----+-----+▒▒" " ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒" " ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒" ); #[cfg(feature = "ansi")] test_table!( test_shadow_set_color_0, Matrix::iter([(123, 456, 789), (234, 567, 891)]) .with(Shadow::new(2).set_offset(3).set_color(Color::FG_RED)), "+-----+-----+-----+ " "| i32 | i32 | i32 | " "+-----+-----+-----+ " "| 123 | 456 | 789 |\u{1b}[31m▒▒\u{1b}[39m" "+-----+-----+-----+\u{1b}[31m▒▒\u{1b}[39m" "| 234 | 567 | 891 |\u{1b}[31m▒▒\u{1b}[39m" "+-----+-----+-----+\u{1b}[31m▒▒\u{1b}[39m" " \u{1b}[31m▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒\u{1b}[39m" " \u{1b}[31m▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒\u{1b}[39m" ); tabled-0.18.0/tests/settings/span_test.rs000064400000000000000000001505631046102023000165270ustar 00000000000000#![cfg(feature = "std")] #![allow(clippy::redundant_clone)] use std::iter::FromIterator; use tabled::{ builder::Builder, grid::config::Position, settings::{ object::{Columns, Segment}, style::{BorderSpanCorrection, Style}, Alignment, Highlight, Modify, Padding, Panel, Span, }, Table, }; use crate::matrix::Matrix; use testing_table::{static_table, test_table}; test_table!( span_column_test_0, Matrix::new(3, 3) .with(Style::psql()) .with(Modify::new(Segment::all()).with(Alignment::left())) .with(Modify::new(Columns::single(0)).with(Span::column(2))), " N | column 1 | column 2 " "-+-+----------+----------" " 0 | 0-1 | 0-2 " " 1 | 1-1 | 1-2 " " 2 | 2-1 | 2-2 " ); test_table!( span_column_test_1, Matrix::new(3, 3) .with(Style::psql()) .with(Modify::new(Segment::all()).with(Alignment::left())) .with(Modify::new(Columns::new(1..2)).with(Span::column(2))), " N | column 0 | column 2 " "---+-----+----+----------" " 0 | 0-0 | 0-2 " " 1 | 1-0 | 1-2 " " 2 | 2-0 | 2-2 " ); test_table!( span_column_test_2, Matrix::new(3, 3) .with(Style::psql()) .with(Modify::new(Segment::all()).with(Alignment::left())) .with(Modify::new(Columns::single(0)).with(Span::column(4))), " N " "+++" " 0 " " 1 " " 2 " ); test_table!( cell_span_test_0, Matrix::new(3, 3) .with(Style::psql()) .with(Modify::new(Segment::all()).with(Alignment::left())) .with(Modify::new((0, 0)).with(Span::column(2))), " N | column 1 | column 2 " "---+-----+----------+----------" " 0 | 0-0 | 0-1 | 0-2 " " 1 | 1-0 | 1-1 | 1-2 " " 2 | 2-0 | 2-1 | 2-2 " ); test_table!( cell_span_test_1, Matrix::new(3, 3) .with(Style::psql()) .with(Modify::new(Segment::all()).with(Alignment::left())) .with(Modify::new((1, 0)).with(Span::column(2))), " N | column 0 | column 1 | column 2 " "---+----------+----------+----------" " 0 | 0-1 | 0-2 " " 1 | 1-0 | 1-1 | 1-2 " " 2 | 2-0 | 2-1 | 2-2 " ); test_table!( cell_span_test_2, Matrix::new(3, 3) .with(Style::psql()) .with(Modify::new((2, 0)).with(Span::column(2))), " N | column 0 | column 1 | column 2 " "---+----------+----------+----------" " 0 | 0-0 | 0-1 | 0-2 " " 1 | 1-1 | 1-2 " " 2 | 2-0 | 2-1 | 2-2 " ); test_table!( cell_span_test_3, Matrix::new(3, 3) .with(Style::psql()) .with(Modify::new(Segment::all()).with(Alignment::left())) .with(Modify::new((3, 0)).with(Span::column(2))), " N | column 0 | column 1 | column 2 " "---+----------+----------+----------" " 0 | 0-0 | 0-1 | 0-2 " " 1 | 1-0 | 1-1 | 1-2 " " 2 | 2-1 | 2-2 " ); test_table!( cell_span_test_4, Matrix::new(3, 3) .with(Style::psql()) .with(Modify::new(Segment::all()).with(Alignment::left())) .with(Modify::new((0, 1)).with(Span::column(2))), " N | column 0 | column 2 " "---+-----+-----+----------" " 0 | 0-0 | 0-1 | 0-2 " " 1 | 1-0 | 1-1 | 1-2 " " 2 | 2-0 | 2-1 | 2-2 " ); test_table!( cell_span_test_5, Matrix::new(3, 3) .with(Style::psql()) .with(Modify::new(Segment::all()).with(Alignment::left())) .with(Modify::new((1, 1)).with(Span::column(2))), " N | column 0 | column 1 | column 2 " "---+----------+----------+----------" " 0 | 0-0 | 0-2 " " 1 | 1-0 | 1-1 | 1-2 " " 2 | 2-0 | 2-1 | 2-2 " ); test_table!( cell_span_test_6, Matrix::new(3, 3) .with(Style::psql()) .with(Modify::new(Segment::all()).with(Alignment::left())) .with(Modify::new((2, 1)).with(Span::column(2))), " N | column 0 | column 1 | column 2 " "---+----------+----------+----------" " 0 | 0-0 | 0-1 | 0-2 " " 1 | 1-0 | 1-2 " " 2 | 2-0 | 2-1 | 2-2 " ); test_table!( cell_span_test_7, Matrix::new(3, 3) .with(Style::psql()) .with(Modify::new(Segment::all()).with(Alignment::left())) .with(Modify::new((3, 1)).with(Span::column(2))), " N | column 0 | column 1 | column 2 " "---+----------+----------+----------" " 0 | 0-0 | 0-1 | 0-2 " " 1 | 1-0 | 1-1 | 1-2 " " 2 | 2-0 | 2-2 " ); test_table!( cell_span_test_8, Matrix::new(3, 3) .with(Style::psql()) .with(Modify::new((0, 2)).with(Span::column(2))), " N | column 0 | column 1 " "---+----------+-----+-----" " 0 | 0-0 | 0-1 | 0-2 " " 1 | 1-0 | 1-1 | 1-2 " " 2 | 2-0 | 2-1 | 2-2 " ); test_table!( cell_span_test_9, Matrix::new(3, 3) .with(Style::psql()) .with(Modify::new((1, 2)).with(Span::column(2))), " N | column 0 | column 1 | column 2 " "---+----------+----------+----------" " 0 | 0-0 | 0-1 " " 1 | 1-0 | 1-1 | 1-2 " " 2 | 2-0 | 2-1 | 2-2 " ); test_table!( cell_span_test_10, Matrix::new(3, 3) .with(Style::psql()) .with(Modify::new((2, 2)).with(Span::column(2))), " N | column 0 | column 1 | column 2 " "---+----------+----------+----------" " 0 | 0-0 | 0-1 | 0-2 " " 1 | 1-0 | 1-1 " " 2 | 2-0 | 2-1 | 2-2 " ); test_table!( cell_span_test_11, Matrix::new(3, 3) .with(Style::psql()) .with(Modify::new((3, 2)).with(Span::column(2))), " N | column 0 | column 1 | column 2 " "---+----------+----------+----------" " 0 | 0-0 | 0-1 | 0-2 " " 1 | 1-0 | 1-1 | 1-2 " " 2 | 2-0 | 2-1 " ); test_table!( span_multiline, Matrix::new(3, 3) .insert((3, 2).into(), "https://\nwww\n.\nredhat\n.com\n/en") .with(Style::psql()) .with(Modify::new((3, 2)).with(Span::column(2))), " N | column 0 | column 1 | column 2 " "---+----------+----------+----------" " 0 | 0-0 | 0-1 | 0-2 " " 1 | 1-0 | 1-1 | 1-2 " " 2 | 2-0 | https:// " " | | www " " | | . " " | | redhat " " | | .com " " | | /en " ); test_table!( indent_works_in_spaned_columns, Matrix::new(3, 3) .with(Style::psql()) .with(Modify::new(Segment::all()).with(Padding::new(3, 0, 0, 0))) .with(Modify::new(Segment::all()).with(Alignment::left())) .with(Modify::new((1, 1)).with(Span::column(3))) .with(Modify::new((3, 1)).with(Span::column(3))), " N| column 0| column 1| column 2" "----+-----------+-----------+-----------" " 0| 0-0 " " 1| 1-0 | 1-1 | 1-2 " " 2| 2-0 " ); test_table!( spaned_columns_with_collision, Matrix::iter([["just 1 column"; 5]; 5]) .with(Style::modern()) .with( Modify::new((0, 0)) .with(Span::column(5)) .with("span all 5 columns"), ) .with( Modify::new((1, 0)) .with(Span::column(4)) .with("span 4 columns"), ) .with( Modify::new((2, 0)) .with(Span::column(3)) .with("span 3 columns"), ) .with( Modify::new((2, 3)) .with(Span::column(2)) .with("span 2 columns"), ) .with( Modify::new((3, 0)) .with(Span::column(2)) .with("span 3 columns"), ) .with( Modify::new((3, 2)) .with(Span::column(3)) .with("span 3 columns"), ) .with( Modify::new((4, 1)) .with(Span::column(4)) .with("span 4 columns"), ), "┌───────────────┬───────────────┬───────────────┬───────────────┬───────────────┐" "│ span all 5 columns │" "├───────────────┼───────────────┼───────────────┼───────────────┼───────────────┤" "│ span 4 columns │ just 1 column │" "├───────────────┼───────────────┼───────────────┼───────────────┼───────────────┤" "│ span 3 columns │ span 2 columns │" "├───────────────┼───────────────┼───────────────┼───────────────┼───────────────┤" "│ span 3 columns │ span 3 columns │" "├───────────────┼───────────────┼───────────────┼───────────────┼───────────────┤" "│ just 1 column │ span 4 columns │" "├───────────────┼───────────────┼───────────────┼───────────────┼───────────────┤" "│ just 1 column │ just 1 column │ just 1 column │ just 1 column │ just 1 column │" "└───────────────┴───────────────┴───────────────┴───────────────┴───────────────┘" ); test_table!( span_with_panel_test_0, Matrix::iter([[1, 2, 3]]) .with(Panel::horizontal(0,"Tabled Releases")) .with(Modify::new((1, 0)).with(Span::column(2))) .with(Style::ascii()), "+-----+-----+-----+" "| Tabled Releases |" "+-----+-----+-----+" "| 0 | 2 |" "+-----+-----+-----+" "| 1 | 2 | 3 |" "+-----+-----+-----+" ); test_table!( span_with_panel_test_1, Matrix::iter([[1, 2, 3], [4, 5, 6]]) .with(Panel::horizontal(0,"Tabled Releases")) .with(Modify::new((2, 0)).with(Span::column(2))) .with(Style::ascii()), "+-----+-----+-----+" "| Tabled Releases |" "+-----+-----+-----+" "| 0 | 1 | 2 |" "+-----+-----+-----+" "| 1 | 3 |" "+-----+-----+-----+" "| 4 | 5 | 6 |" "+-----+-----+-----+" ); test_table!( span_with_panel_test_2, Matrix::iter([[1, 2, 3], [4, 5, 6]]) .with(Panel::horizontal(0,"Tabled Releases")) .with(Modify::new((1, 0)).with(Span::column(2))) .with(Modify::new((2, 0)).with(Span::column(2))) .with(Style::ascii()), "+-----+-----+-----+" "| Tabled Releases |" "+-----+-----+-----+" "| 0 | 2 |" "+-----+-----+-----+" "| 1 | 3 |" "+-----+-----+-----+" "| 4 | 5 | 6 |" "+-----+-----+-----+" ); test_table!( span_with_panel_with_correction_test_0, Matrix::iter([[1, 2, 3]]) .with(Panel::horizontal(0,"Tabled Releases")) .with(Modify::new((1, 0)).with(Span::column(2))) .with(Style::ascii()) .with(BorderSpanCorrection), "+-----------------+" "| Tabled Releases |" "+-----------+-----+" "| 0 | 2 |" "+-----+-----+-----+" "| 1 | 2 | 3 |" "+-----+-----+-----+" ); test_table!( span_with_panel_with_correction_test_1, Matrix::iter([[1, 2, 3], [4, 5, 6]]) .with(Panel::horizontal(0,"Tabled Releases")) .with(Modify::new((2, 0)).with(Span::column(2))) .with(Style::ascii()) .with(BorderSpanCorrection), "+-----------------+" "| Tabled Releases |" "+-----+-----+-----+" "| 0 | 1 | 2 |" "+-----+-----+-----+" "| 1 | 3 |" "+-----+-----+-----+" "| 4 | 5 | 6 |" "+-----+-----+-----+" ); test_table!( span_with_panel_with_correction_test_2, Matrix::iter([[1, 2, 3], [4, 5, 6]]) .with(Panel::horizontal(0,"Tabled Releases")) .with(Modify::new((1, 0)).with(Span::column(2))) .with(Modify::new((2, 0)).with(Span::column(2))) .with(Style::ascii()) .with(BorderSpanCorrection), "+-----------------+" "| Tabled Releases |" "+-----------+-----+" "| 0 | 2 |" "+-----------+-----+" "| 1 | 3 |" "+-----+-----+-----+" "| 4 | 5 | 6 |" "+-----+-----+-----+" ); #[test] #[should_panic] #[ignore = "span zero not yet decided"] fn span_column_exceeds_boundaries_test() { // todo: determine if it's the right behaviour Matrix::new(3, 3) .with(Modify::new(Columns::single(0)).with(Span::column(100))) .to_string(); } #[test] #[ignore = "span zero not yet decided"] fn span_cell_exceeds_boundaries_test() { // these tests shows that exiding boundaries causes invalid behaviour // // todo: determine if it's the right behaviour let table = Matrix::new(3, 3) .with(Style::psql()) .with(Modify::new(Segment::all()).with(Alignment::left())) .with(Modify::new((0, 0)).with(Span::column(20))) .to_string(); assert_eq!( table, static_table!( " N " "---+-----+-----+-----" " 0 | 0-0 | 0-1 | 0-2 " " 1 | 1-0 | 1-1 | 1-2 " " 2 | 2-0 | 2-1 | 2-2 " ) ); let table = Matrix::new(3, 3) .with(Style::psql()) .with(Modify::new(Segment::all()).with(Alignment::left())) .with(Modify::new((1, 1)).with(Span::column(20))) .to_string(); assert_eq!( table, static_table!( " N | column 0 | column 1 | column 2 " "---+----------+----------+----------" " 0 | 0-0 " " 1 | 1-0 | 1-1 | 1-2 " " 2 | 2-0 | 2-1 | 2-2 " ) ); let table = Matrix::new(3, 3) .with(Style::psql()) .with(Modify::new(Segment::all()).with(Alignment::left())) .with(Modify::new((1, 0)).with(Span::column(20))) .to_string(); assert_eq!( table, static_table!( " N | column 0 | column 1 | column 2 " "---+----------+----------+----------" " 0 " " 1 | 1-0 | 1-1 | 1-2 " " 2 | 2-0 | 2-1 | 2-2 " ) ); } #[test] #[ignore = "span zero not yet decided"] fn span_zero_test() { let table = Matrix::new(3, 3) .with(Style::psql()) .with(Modify::new((0, 0)).with(Span::column(0))) .to_string(); assert_eq!( table, static_table!( " column 0 | column 1 | column 2 " "----+-----+----------+----------" " 0 | 0-0 | 0-1 | 0-2 " " 1 | 1-0 | 1-1 | 1-2 " " 2 | 2-0 | 2-1 | 2-2 " ) ); let table = Matrix::new(3, 3) .with(Style::psql()) .with(Modify::new((0, 1)).with(Span::column(0))) .to_string(); assert_eq!( table, static_table!( " N | column 1 | column 2 " "---+-----+----------+----------" " 0 | 0-0 | 0-1 | 0-2 " " 1 | 1-0 | 1-1 | 1-2 " " 2 | 2-0 | 2-1 | 2-2 " ) ); let table = Matrix::new(3, 3) .with(Style::psql()) .with(Modify::new((0, 2)).with(Span::column(0))) .to_string(); assert_eq!( table, static_table!( " N | column 0 | column 2 " "---+-----+-----+----------" " 0 | 0-0 | 0-1 | 0-2 " " 1 | 1-0 | 1-1 | 1-2 " " 2 | 2-0 | 2-1 | 2-2 " ) ); let table = Matrix::new(3, 3) .with(Style::psql()) .with(Modify::new((0, 3)).with(Span::column(0))) .to_string(); assert_eq!( table, static_table!( " N | column 0 | column 1 " "---+----------+-----+-----" " 0 | 0-0 | 0-1 | 0-2 " " 1 | 1-0 | 1-1 | 1-2 " " 2 | 2-0 | 2-1 | 2-2 " ) ); let table = Matrix::new(3, 3) .with(Style::psql()) .with(Modify::new((0, 4)).with(Span::column(0))) .to_string(); assert_eq!( table, static_table!( " N | column 0 | column 1 | column 2 " "---+----------+----------+----------" " 0 | 0-0 | 0-1 | 0-2 " " 1 | 1-0 | 1-1 | 1-2 " " 2 | 2-0 | 2-1 | 2-2 " ) ); let table = Matrix::new(3, 3) .with(Style::psql()) .with(Modify::new((0, 0)).with(Span::column(0))) .with(Modify::new((1, 1)).with(Span::column(0))) .with(Modify::new((2, 2)).with(Span::column(0))) .with(Modify::new((3, 2)).with(Span::column(0))) .with(Modify::new((3, 1)).with(Span::column(0))) .to_string(); assert_eq!( table, static_table!( " column 0 | column 1 | column 2 " "------+-------+------+----------" " 0 | 0-1 | 0-2 " " 1 | 1-0 | 1-2 " " 2 | 2-2 " ) ); } #[test] #[ignore = "span zero not yet decided"] fn span_all_table_to_zero_test() { let table = Matrix::table(2, 2) .with(Style::psql()) .with(Modify::new(Segment::all()).with(Span::column(0))) .to_string(); // todo: determine whether it's correct assert_eq!(table, static_table!("\n++\n\n\n")); } mod row { use tabled::settings::object::Rows; use super::*; #[test] fn span_row_test() { let table = Matrix::new(3, 3); { let table_str = table .clone() .with(Style::ascii()) .with(Modify::new(Segment::all()).with(Alignment::left())) .with(Modify::new(Rows::single(0)).with(Span::row(2))) .to_string(); assert_eq!( table_str, static_table!( "+---+----------+----------+----------+" "+ N + column 0 + column 1 + column 2 +" "+---+----------+----------+----------+" "| 1 | 1-0 | 1-1 | 1-2 |" "+---+----------+----------+----------+" "| 2 | 2-0 | 2-1 | 2-2 |" "+---+----------+----------+----------+" ) ); let table = table .clone() .with(Style::psql()) .with(Modify::new(Segment::all()).with(Alignment::left())) .with(Modify::new(Rows::single(0)).with(Span::row(2))) .to_string(); assert_eq!( table, static_table!( " N + column 0 + column 1 + column 2 " " 1 | 1-0 | 1-1 | 1-2 " " 2 | 2-0 | 2-1 | 2-2 " ) ); } { let table = table .clone() .with(Style::psql()) .with(Modify::new(Segment::all()).with(Alignment::left())) .with(Modify::new(Rows::new(1..2)).with(Span::row(2))) .to_string(); assert_eq!( table, static_table!( " N | column 0 | column 1 | column 2 " "---+----------+----------+----------" " 0 | 0-0 | 0-1 | 0-2 " " 2 | 2-0 | 2-1 | 2-2 " ) ); } { let table = table .clone() .with(Style::psql()) .with(Modify::new(Segment::all()).with(Alignment::left())) .with(Modify::new(Rows::single(0)).with(Span::row(4))) .to_string(); assert_eq!(table, " N + column 0 + column 1 + column 2 "); } } #[test] fn cell_span_test() { let table = Matrix::new(3, 3); { // first column cells row span = 2 { let table = table .clone() .with(Style::psql()) .with(Modify::new(Segment::all()).with(Alignment::left())) .with(Modify::new((0, 0)).with(Span::row(2))) .to_string(); assert_eq!( table, static_table!( " N | column 0 | column 1 | column 2 " " +----------+----------+----------" " | 0-0 | 0-1 | 0-2 " " 1 | 1-0 | 1-1 | 1-2 " " 2 | 2-0 | 2-1 | 2-2 " ) ); } { let table = table .clone() .with(Style::psql()) .with(Modify::new(Segment::all()).with(Alignment::left())) .with(Modify::new((1, 0)).with(Span::row(2))) .to_string(); assert_eq!( table, static_table!( " N | column 0 | column 1 | column 2 " "---+----------+----------+----------" " 0 | 0-0 | 0-1 | 0-2 " " | 1-0 | 1-1 | 1-2 " " 2 | 2-0 | 2-1 | 2-2 " ) ); } { let table = table .clone() .with(Style::psql()) .with(Modify::new((2, 0)).with(Span::row(2))) .to_string(); assert_eq!( table, static_table!( " N | column 0 | column 1 | column 2 " "---+----------+----------+----------" " 0 | 0-0 | 0-1 | 0-2 " " 1 | 1-0 | 1-1 | 1-2 " " | 2-0 | 2-1 | 2-2 " ) ); } } { // first row cells row span = 2 { let table = table .clone() .with(Style::psql()) .with(Modify::new(Segment::all()).with(Alignment::left())) .with(Modify::new((0, 1)).with(Span::row(2))) .to_string(); assert_eq!( table, static_table!( " N | column 0 | column 1 | column 2 " "---+ +----------+----------" " 0 | | 0-1 | 0-2 " " 1 | 1-0 | 1-1 | 1-2 " " 2 | 2-0 | 2-1 | 2-2 " ) ); } { let table = table .clone() .with(Style::psql()) .with(Modify::new(Segment::all()).with(Alignment::left())) .with(Modify::new((0, 2)).with(Span::row(2))) .to_string(); assert_eq!( table, static_table!( " N | column 0 | column 1 | column 2 " "---+----------+ +----------" " 0 | 0-0 | | 0-2 " " 1 | 1-0 | 1-1 | 1-2 " " 2 | 2-0 | 2-1 | 2-2 " ) ); } { let table = table .clone() .with(Style::psql()) .with(Modify::new((0, 3)).with(Span::row(2))) .to_string(); assert_eq!( table, static_table!( " N | column 0 | column 1 | column 2 " "---+----------+----------+ " " 0 | 0-0 | 0-1 | " " 1 | 1-0 | 1-1 | 1-2 " " 2 | 2-0 | 2-1 | 2-2 " ) ); } } { // second column span=2 { let table = table .clone() .with(Style::psql()) .with(Modify::new(Segment::all()).with(Alignment::left())) .with(Modify::new((1, 1)).with(Span::row(2))) .to_string(); assert_eq!( table, static_table!( " N | column 0 | column 1 | column 2 " "---+----------+----------+----------" " 0 | 0-0 | 0-1 | 0-2 " " 1 | | 1-1 | 1-2 " " 2 | 2-0 | 2-1 | 2-2 " ) ); } { let table = table .clone() .with(Style::psql()) .with(Modify::new(Segment::all()).with(Alignment::left())) .with(Modify::new((2, 1)).with(Span::row(2))) .to_string(); assert_eq!( table, static_table!( " N | column 0 | column 1 | column 2 " "---+----------+----------+----------" " 0 | 0-0 | 0-1 | 0-2 " " 1 | 1-0 | 1-1 | 1-2 " " 2 | | 2-1 | 2-2 " ) ); } } { // 3rd column span=2 { let table = table .clone() .with(Style::psql()) .with(Modify::new(Segment::all()).with(Alignment::left())) .with(Modify::new((1, 2)).with(Span::row(2))) .to_string(); assert_eq!( table, static_table!( " N | column 0 | column 1 | column 2 " "---+----------+----------+----------" " 0 | 0-0 | 0-1 | 0-2 " " 1 | 1-0 | | 1-2 " " 2 | 2-0 | 2-1 | 2-2 " ) ); } { let table = table .clone() .with(Style::psql()) .with(Modify::new(Segment::all()).with(Alignment::left())) .with(Modify::new((2, 2)).with(Span::row(2))) .to_string(); assert_eq!( table, static_table!( " N | column 0 | column 1 | column 2 " "---+----------+----------+----------" " 0 | 0-0 | 0-1 | 0-2 " " 1 | 1-0 | 1-1 | 1-2 " " 2 | 2-0 | | 2-2 " ) ); } } { // 4th column span=2 { let table = table .clone() .with(Style::psql()) .with(Modify::new(Segment::all()).with(Alignment::left())) .with(Modify::new((1, 3)).with(Span::row(2))) .to_string(); assert_eq!( table, static_table!( " N | column 0 | column 1 | column 2 " "---+----------+----------+----------" " 0 | 0-0 | 0-1 | 0-2 " " 1 | 1-0 | 1-1 | " " 2 | 2-0 | 2-1 | 2-2 " ) ); } { let table = table .clone() .with(Style::psql()) .with(Modify::new(Segment::all()).with(Alignment::left())) .with(Modify::new((2, 3)).with(Span::row(2))) .to_string(); assert_eq!( table, static_table!( " N | column 0 | column 1 | column 2 " "---+----------+----------+----------" " 0 | 0-0 | 0-1 | 0-2 " " 1 | 1-0 | 1-1 | 1-2 " " 2 | 2-0 | 2-1 | " ) ); } } } #[test] fn span_with_panel_with_correction_test() { let data = [[1, 2, 3]]; let table = Table::new(data) .with(Modify::new((0, 0)).with(Span::row(2))) .with(Style::ascii()) .with(BorderSpanCorrection) .to_string(); assert_eq!( table, static_table!( "+---+---+---+" "| 0 | 1 | 2 |" "| +---+---+" "| | 2 | 3 |" "+---+---+---+" ) ); let data = [[1, 2, 3], [4, 5, 6]]; let table = Table::new(data) .with(Modify::new((1, 0)).with(Span::row(2))) .with(Modify::new((0, 2)).with(Span::row(3))) .with(Style::ascii()) .with(BorderSpanCorrection) .to_string(); assert_eq!( table, static_table!( "+---+---+---+" "| 0 | 1 | 2 |" "+---+---+ |" "| 1 | 2 | |" "| +---+ |" "| | 5 | |" "+---+---+---+" ) ); let data = [[1, 2, 3], [4, 5, 6]]; let table = Table::new(data) .with(Modify::new((1, 0)).with(Span::row(2))) .with(Modify::new((0, 2)).with(Span::row(3))) .with(Modify::new((0, 1)).with(Span::row(2))) .with(Style::ascii()) .with(BorderSpanCorrection) .to_string(); assert_eq!( table, static_table!( "+---+---+---+" "| 0 | 1 | 2 |" "+---+ | |" "| 1 +---+ |" "| | 5 | |" "+---+---+---+" ) ); let data = [[1, 2, 3], [4, 5, 6]]; let table = Table::new(data) .with(Modify::new((1, 0)).with(Span::row(2))) .with(Modify::new((0, 1)).with(Span::row(2)).with(Span::column(2))) .with(Style::ascii()) .with(BorderSpanCorrection) .to_string(); assert_eq!( table, static_table!( "+---+-------+" "| 0 | 1 |" "+---+ +" "| 1 +---+---+" "| | 5 | 6 |" "+---+---+---+" ) ); } #[test] fn span_example_test() { let data = [["just 1 column"; 5]; 5]; let h_span = |r, c, span| Modify::new((r, c)).with(Span::column(span)); let v_span = |r, c, span| Modify::new((r, c)).with(Span::row(span)); let table = Table::new(data) .with(h_span(0, 0, 5).with(String::from("span all 5 columns"))) .with(h_span(1, 0, 4).with(String::from("span 4 columns"))) .with(h_span(2, 0, 2).with(String::from("span 2 columns"))) .with(v_span(2, 4, 4).with(String::from("just 1 column\nspan\n4\ncolumns"))) .with(v_span(3, 1, 2).with(String::from("span 2 columns\nspan\n2\ncolumns"))) .with(v_span(2, 3, 3).with(String::from("just 1 column\nspan\n3\ncolumns"))) .with(h_span(3, 1, 2)) .with(Style::modern()) .with(BorderSpanCorrection) .with(Modify::new(Segment::all()).with(Alignment::center_vertical())) .to_string(); assert_eq!( table, static_table!( "┌───────────────────────────────────────────────────────────────────────────────┐" "│ span all 5 columns │" "├───────────────────────────────────────────────────────────────┬───────────────┤" "│ span 4 columns │ just 1 column │" "├───────────────────────────────┬───────────────┬───────────────┼───────────────┤" "│ span 2 columns │ just 1 column │ │ │" "├───────────────┬───────────────┴───────────────┤ just 1 column │ │" "│ just 1 column │ span 2 columns │ span │ just 1 column │" "│ │ span │ 3 │ span │" "├───────────────┤ 2 │ columns │ 4 │" "│ just 1 column │ columns │ │ columns │" "├───────────────┼───────────────┬───────────────┼───────────────┤ │" "│ just 1 column │ just 1 column │ just 1 column │ just 1 column │ │" "└───────────────┴───────────────┴───────────────┴───────────────┴───────────────┘" ) ) } #[test] fn highlight_row_span_test() { let data = [ ["1", "2\n2\n2\n2\n2\n2\n2\n2", "3"], ["4", "5", "6"], ["7", "8", "9"], ]; let table = Table::new(data) .with(Modify::new((1, 1)).with(Span::row(3))) .with(Style::modern()) .with(Highlight::outline(Columns::single(1), '*')) .to_string(); assert_eq!( table, static_table!( "┌───*****───┐" "│ 0 * 1 * 2 │" "├───*───*───┤" "│ 1 * 2 * 3 │" "│ * 2 * │" "├───* 2 *───┤" "│ 4 * 2 * 6 │" "│ * 2 * │" "├───* 2 *───┤" "│ 7 * 2 * 9 │" "│ * 2 * │" "└───*****───┘" ) ); } } #[test] fn highlight_row_col_span_test() { let data = [ ["1", "2\n2\n2\n2\n2\n2\n2\n2", "3", "0"], ["4", "5", "6", "0"], ["7", "8", "9", "0"], ]; let table = Table::new(data) .with(Modify::new((1, 1)).with(Span::row(3)).with(Span::column(2))) .with(Style::modern()) .with(Highlight::outline(Columns::new(1..3), '*')) .to_string(); assert_eq!( table, static_table!( "┌───*********───┐" "│ 0 * 1 │ 2 * 3 │" "├───*───┼───*───┤" "│ 1 * 2 * 0 │" "│ * 2 * │" "├───* 2 *───┤" "│ 4 * 2 * 0 │" "│ * 2 * │" "├───* 2 *───┤" "│ 7 * 2 * 0 │" "│ * 2 * │" "└───*********───┘" ) ); } test_table!( column_span_bigger_then_max, Matrix::new(3, 3).with(Modify::new((0, 0)).with(Span::column(100))), "+---+-----+-----+-----+" "| N |" "+---+-----+-----+-----+" "| 0 | 0-0 | 0-1 | 0-2 |" "+---+-----+-----+-----+" "| 1 | 1-0 | 1-1 | 1-2 |" "+---+-----+-----+-----+" "| 2 | 2-0 | 2-1 | 2-2 |" "+---+-----+-----+-----+" ); test_table!( row_span_bigger_then_max, Matrix::new(3, 3).with(Modify::new((0, 0)).with(Span::row(100))), "+---+----------+----------+----------+" "| N | column 0 | column 1 | column 2 |" "+ +----------+----------+----------+" "| | 0-0 | 0-1 | 0-2 |" "+ +----------+----------+----------+" "| | 1-0 | 1-1 | 1-2 |" "+ +----------+----------+----------+" "| | 2-0 | 2-1 | 2-2 |" "+---+----------+----------+----------+" ); test_table!( column_span_invalid_position_row, Matrix::new(3, 3).with(Modify::new((1000, 0)).with(Span::column(2))), "+---+----------+----------+----------+" "| N | column 0 | column 1 | column 2 |" "+---+----------+----------+----------+" "| 0 | 0-0 | 0-1 | 0-2 |" "+---+----------+----------+----------+" "| 1 | 1-0 | 1-1 | 1-2 |" "+---+----------+----------+----------+" "| 2 | 2-0 | 2-1 | 2-2 |" "+---+----------+----------+----------+" ); test_table!( column_span_invalid_position_column, Matrix::new(3, 3).with(Modify::new((0, 1000)).with(Span::column(2))), "+---+----------+----------+----------+" "| N | column 0 | column 1 | column 2 |" "+---+----------+----------+----------+" "| 0 | 0-0 | 0-1 | 0-2 |" "+---+----------+----------+----------+" "| 1 | 1-0 | 1-1 | 1-2 |" "+---+----------+----------+----------+" "| 2 | 2-0 | 2-1 | 2-2 |" "+---+----------+----------+----------+" ); test_table!( column_span_invalid_position_row_and_column, Matrix::new(3, 3).with(Modify::new((1000, 1000)).with(Span::column(2))), "+---+----------+----------+----------+" "| N | column 0 | column 1 | column 2 |" "+---+----------+----------+----------+" "| 0 | 0-0 | 0-1 | 0-2 |" "+---+----------+----------+----------+" "| 1 | 1-0 | 1-1 | 1-2 |" "+---+----------+----------+----------+" "| 2 | 2-0 | 2-1 | 2-2 |" "+---+----------+----------+----------+" ); test_table!( row_span_invalid_position_row, Matrix::new(3, 3).with(Modify::new((1000, 0)).with(Span::row(2))), "+---+----------+----------+----------+" "| N | column 0 | column 1 | column 2 |" "+---+----------+----------+----------+" "| 0 | 0-0 | 0-1 | 0-2 |" "+---+----------+----------+----------+" "| 1 | 1-0 | 1-1 | 1-2 |" "+---+----------+----------+----------+" "| 2 | 2-0 | 2-1 | 2-2 |" "+---+----------+----------+----------+" ); test_table!( row_span_invalid_position_column, Matrix::new(3, 3).with(Modify::new((0, 1000)).with(Span::row(2))), "+---+----------+----------+----------+" "| N | column 0 | column 1 | column 2 |" "+---+----------+----------+----------+" "| 0 | 0-0 | 0-1 | 0-2 |" "+---+----------+----------+----------+" "| 1 | 1-0 | 1-1 | 1-2 |" "+---+----------+----------+----------+" "| 2 | 2-0 | 2-1 | 2-2 |" "+---+----------+----------+----------+" ); test_table!( row_span_invalid_position_row_and_column, Matrix::new(3, 3).with(Modify::new((1000, 1000)).with(Span::row(2))), "+---+----------+----------+----------+" "| N | column 0 | column 1 | column 2 |" "+---+----------+----------+----------+" "| 0 | 0-0 | 0-1 | 0-2 |" "+---+----------+----------+----------+" "| 1 | 1-0 | 1-1 | 1-2 |" "+---+----------+----------+----------+" "| 2 | 2-0 | 2-1 | 2-2 |" "+---+----------+----------+----------+" ); test_table!( fix_qc_0, { let data: [[i64; 39]; 2] = [[2542785870, 2382388818, 2879895075, 2885436543, 2331131758, 219892320, 2503640226, 3754929678, 2206481860, 686909682, 3456499235, 931699300, 1556722454, 958179233, 3896072307, 2042612749, 3354379549, 3272539286, 3926297167, 4294967295, 1650407458, 3322068437, 4294967295, 446762625, 829020202, 4150192304, 3430619243, 3460609391, 2992017103, 513091574, 1514148367, 2166549688, 1401371431, 2854075038, 1286733939, 2959901405, 4152658371, 0, 4224074215], [360331598, 3736108702, 2948800064, 2121584548, 1609988995, 469935087, 3974876615, 2193609088, 3568111892, 732365859, 0, 4294967295, 2994498036, 198522721, 1784359340, 1, 2732726754, 592359359, 3016729802, 878533877, 2997437699, 3573361662, 1111570515, 4294967295, 2245782848, 1383106893, 0, 0, 2869976103, 1611436878, 1682224972, 3249055253, 1562255501, 1370527728, 240481955, 334260406, 2247343342, 3000635978, 395723768]]; let row_spans = [2, 1, 27, 111, 226, 221, 121, 22, 252, 30, 115, 85, 255, 126, 26, 245, 36, 50, 255, 211, 47, 114, 174, 173, 145, 138, 78, 198, 253, 229, 151, 243, 242, 30, 52, 116, 177, 25, 1, 32, 28, 48, 225, 103, 17, 243, 0, 128, 69, 206, 221, 105, 239, 74, 184, 48, 178, 237, 120, 228, 184, 1, 132, 118, 14, 187]; let col_spans = [7, 91, 56, 246, 73]; let data = data.iter().map(|row| row.iter().map(ToString::to_string)); let rspans = create_span_list(2, 39).zip(row_spans.iter()).map(|(pos, span)| Modify::new(pos).with(Span::column(*span))).collect::>(); let cspans = create_span_list(2, 39).zip(col_spans.iter()).map(|(pos, span)| Modify::new(pos).with(Span::row(*span))).collect::>(); Builder::from_iter(data).build().with(Style::ascii()).with(rspans).with(cspans).to_string() }, "+------+-----++++++++++++++++++++++++++++--+--+--+--+--+--+--+--+--+--+" "| 2542785870 | 2879895075 | 513091574 |" "+ + +--+--+--+--+--+--+--+--+--+--+" "| | | | | | | | | | | | |" "+------+-----++++++++++++++++++++++++++++--+--+--+--+--+--+--+--+--+--+" ); test_table!( test_span_issue_0, Table::new([["just 1 column"; 5]; 5]) .modify((3, 1), "span 2 columns\nspan\n2\ncolumns") .modify((3, 1), Span::row(2)) .modify((3, 1), Span::column(2)) .with(Style::modern()) .with(BorderSpanCorrection) .with(Alignment::center_vertical()) .to_string(), "┌───────────────┬───────────────┬───────────────┬───────────────┬───────────────┐" "│ 0 │ 1 │ 2 │ 3 │ 4 │" "├───────────────┼───────────────┼───────────────┼───────────────┼───────────────┤" "│ just 1 column │ just 1 column │ just 1 column │ just 1 column │ just 1 column │" "├───────────────┼───────────────┼───────────────┼───────────────┼───────────────┤" "│ just 1 column │ just 1 column │ just 1 column │ just 1 column │ just 1 column │" "├───────────────┼───────────────┴───────────────┼───────────────┼───────────────┤" "│ just 1 column │ span 2 columns │ just 1 column │ just 1 column │" "│ │ span │ │ │" "├───────────────┤ 2 ├───────────────┼───────────────┤" "│ just 1 column │ columns │ just 1 column │ just 1 column │" "├───────────────┼───────────────┬───────────────┼───────────────┼───────────────┤" "│ just 1 column │ just 1 column │ just 1 column │ just 1 column │ just 1 column │" "└───────────────┴───────────────┴───────────────┴───────────────┴───────────────┘" ); test_table!( span_col_example_0, { let mut table = Table::new([ (1, 2, 3), (4, 5, 6), (7, 8, 9), (10, 11, 12), (13, 14, 15), (16, 17, 18), (19, 20, 21), ]); table.with(Style::modern_rounded()); table.modify((0, 1), Span::column(2)); table.modify((1, 0), Span::column(isize::MAX)); table.modify((2, 1), Span::column(-1)); table.modify((3, 2), Span::column(isize::MIN)); table.modify((4, 0), Span::column(0)); table.modify((5, 1), Span::column(0)); table.modify((6, 2), Span::column(0)); table.modify((4, 0), Span::column(1)); table.with(BorderSpanCorrection); table.with(Alignment::center()); table.to_string() }, "╭─────┬─────────╮" "│ i32 │ i32 │" "├─────┴─────────┤" "│ 1 │" "├──────────┬────┤" "│ 5 │ 6 │" "├──────────┴────┤" "│ 9 │" "├─────┬────┬────┤" "│ 10 │ │ │" "├─────┴────┴────┤" "│ 14 │" "├───────────────┤" "│ 18 │" "├─────┬────┬────┤" "│ 19 │ 20 │ 21 │" "╰─────┴────┴────╯" ); test_table!( span_col_negative_0, Matrix::new(3, 3) .with(Modify::new((1, 2)).with(Span::column(-1))) .with(Modify::new((2, 3)).with(Span::column(-2))) .with(Modify::new((3, 3)).with(Span::column(-100))), "+---+----------+----------+----------+" "| N | column 0 | column 1 | column 2 |" "+---+----------+----------+----------+" "| 0 | 0-1 | 0-2 |" "+---+----------+----------+----------+" "| 1 | 1-2 |" "+---+----------+----------+----------+" "| 2-2 |" "+---+----------+----------+----------+" ); test_table!( span_col_zero_0, Matrix::new(3, 3) .with(Modify::new((1, 2)).with(Span::column(0))) .with(Modify::new((2, 3)).with(Span::column(0))) .with(Modify::new((3, 3)).with(Span::column(-0))), "+---+----------+----------+----------+" "| N | column 0 | column 1 | column 2 |" "+---+----------+----------+----------+" "| 0-1 |" "+---+----------+----------+----------+" "| 1-2 |" "+---+----------+----------+----------+" "| 2-2 |" "+---+----------+----------+----------+" ); test_table!( span_row_example_0, { let mut table = Table::new([ (1, 2, 3, 5, (6, 7, 8)), (9, 10, 11, 12, (13, 14, 15)), (16, 17, 18, 19, (20, 21, 22)), (23, 24, 25, 26, (27, 28, 29)), ]); table.modify((0, 0), Span::row(2)); table.modify((0, 1), Span::row(isize::MAX)); table.modify((1, 2), Span::row(-1)); table.modify((2, 3), Span::row(isize::MIN)); table.modify((0, 4), Span::row(0)); table.modify((1, 5), Span::row(0)); table.modify((2, 6), Span::row(0)); table.modify((0, 4), Span::row(1)); table.with(Style::modern_rounded()); table.with(BorderSpanCorrection); table.with(Alignment::center_vertical()); table.to_string() }, "╭─────┬─────┬────┬────┬─────┬───┬────╮" "│ │ │ │ │ i32 │ │ │" "│ i32 │ │ 3 │ ├─────┤ │ │" "│ │ │ │ 12 │ │ │ │" "├─────┤ ├────┤ ├─────┤ │ │" "│ 9 │ i32 │ 11 │ │ │ 7 │ 15 │" "├─────┤ ├────┼────┼─────┤ │ │" "│ 16 │ │ 18 │ 19 │ │ │ │" "├─────┤ ├────┼────┼─────┤ │ │" "│ 23 │ │ 25 │ 26 │ │ │ │" "╰─────┴─────┴────┴────┴─────┴───┴────╯" ); test_table!( span_row_negative_0, Matrix::new(3, 3) .with(Modify::new((2, 1)).with(Span::row(-1))) .with(Modify::new((3, 2)).with(Span::row(-2))) .with(Modify::new((3, 3)).with(Span::row(-100))), "+---+----------+----------+-----+" "| N | column 0 | column 1 | 2-2 |" "+---+----------+----------+ +" "| 0 | 1-0 | 2-1 | |" "+---+ + + +" "| 1 | | | |" "+---+----------+ + +" "| 2 | 2-0 | | |" "+---+----------+----------+-----+" ); test_table!( span_row_zero_0, Matrix::new(3, 3) .with(Modify::new((2, 1)).with(Span::row(0))) .with(Modify::new((3, 2)).with(Span::row(0))) .with(Modify::new((3, 3)).with(Span::row(-0))), "+---+-----+-----+-----+" "| N | 1-0 | 2-1 | 2-2 |" "+---+ + + +" "| 0 | | | |" "+---+ + + +" "| 1 | | | |" "+---+ + + +" "| 2 | | | |" "+---+-----+-----+-----+" ); fn create_span_list(count_rows: usize, count_cols: usize) -> impl Iterator { (0..count_rows).flat_map(move |r| (0..count_cols).map(move |c| (r, c).into())) } tabled-0.18.0/tests/settings/split_test.rs000064400000000000000000000234541046102023000167170ustar 00000000000000#![cfg(feature = "std")] use std::iter::FromIterator; use tabled::{builder::Builder, settings::split::Split, Table}; use testing_table::test_table; test_table!( split_column_test, Table::from_iter(['a'..='z']).with(Split::column(12)), "+---+---+---+---+---+---+---+---+---+---+---+---+" "| a | b | c | d | e | f | g | h | i | j | k | l |" "+---+---+---+---+---+---+---+---+---+---+---+---+" "| m | n | o | p | q | r | s | t | u | v | w | x |" "+---+---+---+---+---+---+---+---+---+---+---+---+" "| y | z | | | | | | | | | | |" "+---+---+---+---+---+---+---+---+---+---+---+---+" ); test_table!( split_column_2_test, Table::from_iter(['a'..='z']) .with(Split::column(12)) .with(Split::column(4)), "+---+---+---+---+" "| a | b | c | d |" "+---+---+---+---+" "| e | f | g | h |" "+---+---+---+---+" "| i | j | k | l |" "+---+---+---+---+" "| m | n | o | p |" "+---+---+---+---+" "| q | r | s | t |" "+---+---+---+---+" "| u | v | w | x |" "+---+---+---+---+" "| y | z | | |" "+---+---+---+---+" ); test_table!( split_column_retain_test, Table::from_iter(['a'..='z']) .with(Split::column(12)) .with(Split::column(4).retain()), "+---+---+---+---+" "| a | b | c | d |" "+---+---+---+---+" "| e | f | g | h |" "+---+---+---+---+" "| i | j | k | l |" "+---+---+---+---+" "| m | n | o | p |" "+---+---+---+---+" "| q | r | s | t |" "+---+---+---+---+" "| u | v | w | x |" "+---+---+---+---+" "| y | z | | |" "+---+---+---+---+" "| | | | |" "+---+---+---+---+" "| | | | |" "+---+---+---+---+" ); test_table!( split_row_test, Table::from_iter(['a'..='z']) .with(Split::column(12)) .with(Split::column(4)) .with(Split::row(1).concat()), // take it back to the original shape "+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+" "| a | b | c | d | e | f | g | h | i | j | k | l | m | n | o | p | q | r | s | t | u | v | w | x | y | z |" "+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+" ); test_table!( split_row_2_test, Table::from_iter(['a'..='z']) .with(Split::column(12)) .with(Split::column(4)) .with(Split::row(2).concat()), "+---+---+---+---+---+---+---+---+---+---+---+---+---+---+" "| a | b | c | d | i | j | k | l | q | r | s | t | y | z |" "+---+---+---+---+---+---+---+---+---+---+---+---+---+---+" "| e | f | g | h | m | n | o | p | u | v | w | x | | |" "+---+---+---+---+---+---+---+---+---+---+---+---+---+---+" ); test_table!( split_column_index_beyond_size_test, Table::from_iter(['a'..='z']) .with(Split::column(10000)), "+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+" "| a | b | c | d | e | f | g | h | i | j | k | l | m | n | o | p | q | r | s | t | u | v | w | x | y | z |" "+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+" ); test_table!( split_row_index_beyond_size_test, Table::from_iter(['a'..='z']) .with(Split::row(10000)), "+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+" "| a | b | c | d | e | f | g | h | i | j | k | l | m | n | o | p | q | r | s | t | u | v | w | x | y | z |" "+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+" ); test_table!( split_empty_table_test, Builder::default().build().with(Split::column(10000)), "" ); test_table!( split_column_zero_argument_test, Table::from_iter(['a'..='z']).with(Split::column(0)), "+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+" "| a | b | c | d | e | f | g | h | i | j | k | l | m | n | o | p | q | r | s | t | u | v | w | x | y | z |" "+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+" ); test_table!( split_row_zero_argument_test, Table::from_iter(['a'..='z']).with(Split::row(0)), "+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+" "| a | b | c | d | e | f | g | h | i | j | k | l | m | n | o | p | q | r | s | t | u | v | w | x | y | z |" "+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+" ); test_table!( split_blank_table_test, Table::from_iter([vec![String::new(); 26]]).with(Split::column(12)), "+--+--+--+--+--+--+--+--+--+--+--+--+" "| | | | | | | | | | | | |" // first section is protected "+--+--+--+--+--+--+--+--+--+--+--+--+" ); test_table!( split_blank_table_2_test, Table::from_iter([vec![String::new(); 26]]).with(Split::column(12).retain()), "+--+--+--+--+--+--+--+--+--+--+--+--+" "| | | | | | | | | | | | |" "+--+--+--+--+--+--+--+--+--+--+--+--+" "| | | | | | | | | | | | |" "+--+--+--+--+--+--+--+--+--+--+--+--+" "| | | | | | | | | | | | |" "+--+--+--+--+--+--+--+--+--+--+--+--+" ); test_table!( split_zip_test, Table::from_iter(['a'..='z']) .with(Split::column(6)) .with(Split::row(2)), "+---+---+---+---+---+---+---+---+---+---+---+---+---+---+" "| a | m | y | b | n | z | c | o | d | p | e | q | f | r |" "+---+---+---+---+---+---+---+---+---+---+---+---+---+---+" "| g | s | | h | t | | i | u | j | v | k | w | l | x |" "+---+---+---+---+---+---+---+---+---+---+---+---+---+---+" ); test_table!( split_concat_test, Table::from_iter(['a'..='z']) .with(Split::column(6)) .with(Split::row(2).concat()), "+---+---+---+---+---+---+---+---+---+---+---+---+---+---+" "| a | b | c | d | e | f | m | n | o | p | q | r | y | z |" "+---+---+---+---+---+---+---+---+---+---+---+---+---+---+" "| g | h | i | j | k | l | s | t | u | v | w | x | | |" "+---+---+---+---+---+---+---+---+---+---+---+---+---+---+" ); test_table!( split_clean_test, Table::from_iter(['a'..='z']) .with(Split::column(12)) .with(Split::row(2)), "+---+---+---+---+---+---+---+---+---+---+---+---+---+---+" "| a | y | b | z | c | d | e | f | g | h | i | j | k | l |" "+---+---+---+---+---+---+---+---+---+---+---+---+---+---+" "| m | | n | | o | p | q | r | s | t | u | v | w | x |" "+---+---+---+---+---+---+---+---+---+---+---+---+---+---+" ); test_table!( split_retain_test, Table::from_iter(['a'..='z']) .with(Split::column(12)) .with(Split::row(2).retain()), "+---+---+---+---+---+--+---+--+---+--+---+--+---+--+---+--+---+--+---+--+---+--+---+--+" "| a | y | b | z | c | | d | | e | | f | | g | | h | | i | | j | | k | | l | |" "+---+---+---+---+---+--+---+--+---+--+---+--+---+--+---+--+---+--+---+--+---+--+---+--+" "| m | | n | | o | | p | | q | | r | | s | | t | | u | | v | | w | | x | |" "+---+---+---+---+---+--+---+--+---+--+---+--+---+--+---+--+---+--+---+--+---+--+---+--+" ); test_table!( split_mostly_blank_test, Table::from_iter([vec![ "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "A", ]]).with(Split::column(5)) .with(Split::row(2)), "+--+--+--+---+--+" "| | | | | |" "+--+--+--+---+--+" "| | | | A | |" "+--+--+--+---+--+" ); test_table!( split_mostly_blank_retain_test, Table::from_iter([vec![ "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "A", ]]).with(Split::column(5).retain()), "+--+--+--+---+--+" "| | | | | |" "+--+--+--+---+--+" "| | | | | |" "+--+--+--+---+--+" "| | | | | |" "+--+--+--+---+--+" "| | | | | |" "+--+--+--+---+--+" "| | | | | |" "+--+--+--+---+--+" "| | | | | |" "+--+--+--+---+--+" "| | | | A | |" "+--+--+--+---+--+" ); test_table!( split_scattered_values_test, Table::from_iter([vec![ "", "", "", "", "", "g", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "A", ]]).with(Split::column(5)), "+---+--+--+---+--+" "| | | | | |" "+---+--+--+---+--+" "| g | | | | |" "+---+--+--+---+--+" "| | | | A | |" "+---+--+--+---+--+" ); test_table!( split_scattered_values_column_and_row_test, Table::from_iter([vec![ "", "", "", "", "", "g", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "A", ]]).with(Split::column(5)).with(Split::row(2)), "+---+--+--+--+---+--+" "| | | | | A | |" "+---+--+--+--+---+--+" "| g | | | | | |" "+---+--+--+--+---+--+" ); test_table!( split_scattered_values_column_and_row_retain_test, Table::from_iter([vec![ "", "", "", "", "", "g", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "A", ]]).with(Split::column(5).retain()).with(Split::row(2).retain()), "+---+--+--+--+--+--+--+--+--+--+--+--+--+--+--+---+--+--+--+--+" "| | | | | | | | | | | | | | | | A | | | | |" "+---+--+--+--+--+--+--+--+--+--+--+--+--+--+--+---+--+--+--+--+" "| g | | | | | | | | | | | | | | | | | | | |" "+---+--+--+--+--+--+--+--+--+--+--+--+--+--+--+---+--+--+--+--+" ); tabled-0.18.0/tests/settings/style_test.rs000064400000000000000000003175371046102023000167340ustar 00000000000000#![cfg(feature = "std")] use std::iter::FromIterator; use tabled::{ builder::Builder, grid::config::Border as GridBorder, settings::{ object::{Columns, Rows, Segment}, style::{ Border, BorderColor, BorderSpanCorrection, HorizontalLine, LineChar, LineText, Offset, On, Style, VerticalLine, }, themes::Theme, Alignment, Color, Format, Highlight, Modify, Padding, Span, }, Table, }; use crate::matrix::Matrix; use testing_table::{static_table, test_table}; test_table!( default_style, Matrix::new(3, 3).with(Style::ascii()), "+---+----------+----------+----------+" "| N | column 0 | column 1 | column 2 |" "+---+----------+----------+----------+" "| 0 | 0-0 | 0-1 | 0-2 |" "+---+----------+----------+----------+" "| 1 | 1-0 | 1-1 | 1-2 |" "+---+----------+----------+----------+" "| 2 | 2-0 | 2-1 | 2-2 |" "+---+----------+----------+----------+" ); test_table!( psql_style, Matrix::new(3, 3).with(Style::psql()), " N | column 0 | column 1 | column 2 " "---+----------+----------+----------" " 0 | 0-0 | 0-1 | 0-2 " " 1 | 1-0 | 1-1 | 1-2 " " 2 | 2-0 | 2-1 | 2-2 " ); test_table!( markdown_style, Matrix::new(3, 3).with(Style::markdown()), "| N | column 0 | column 1 | column 2 |" "|---|----------|----------|----------|" "| 0 | 0-0 | 0-1 | 0-2 |" "| 1 | 1-0 | 1-1 | 1-2 |" "| 2 | 2-0 | 2-1 | 2-2 |" ); test_table!( modern_style, Matrix::new(3, 3).with(Style::modern()), "┌───┬──────────┬──────────┬──────────┐" "│ N │ column 0 │ column 1 │ column 2 │" "├───┼──────────┼──────────┼──────────┤" "│ 0 │ 0-0 │ 0-1 │ 0-2 │" "├───┼──────────┼──────────┼──────────┤" "│ 1 │ 1-0 │ 1-1 │ 1-2 │" "├───┼──────────┼──────────┼──────────┤" "│ 2 │ 2-0 │ 2-1 │ 2-2 │" "└───┴──────────┴──────────┴──────────┘" ); test_table!( rounded_style, Matrix::new(3, 3).with(Style::rounded()), "╭───┬──────────┬──────────┬──────────╮" "│ N │ column 0 │ column 1 │ column 2 │" "├───┼──────────┼──────────┼──────────┤" "│ 0 │ 0-0 │ 0-1 │ 0-2 │" "│ 1 │ 1-0 │ 1-1 │ 1-2 │" "│ 2 │ 2-0 │ 2-1 │ 2-2 │" "╰───┴──────────┴──────────┴──────────╯" ); test_table!( modern_round_style, Matrix::new(3, 3).with(Style::modern_rounded()), "╭───┬──────────┬──────────┬──────────╮" "│ N │ column 0 │ column 1 │ column 2 │" "├───┼──────────┼──────────┼──────────┤" "│ 0 │ 0-0 │ 0-1 │ 0-2 │" "├───┼──────────┼──────────┼──────────┤" "│ 1 │ 1-0 │ 1-1 │ 1-2 │" "├───┼──────────┼──────────┼──────────┤" "│ 2 │ 2-0 │ 2-1 │ 2-2 │" "╰───┴──────────┴──────────┴──────────╯" ); test_table!( sharp_style, Matrix::new(3, 3).with(Style::sharp()), "┌───┬──────────┬──────────┬──────────┐" "│ N │ column 0 │ column 1 │ column 2 │" "├───┼──────────┼──────────┼──────────┤" "│ 0 │ 0-0 │ 0-1 │ 0-2 │" "│ 1 │ 1-0 │ 1-1 │ 1-2 │" "│ 2 │ 2-0 │ 2-1 │ 2-2 │" "└───┴──────────┴──────────┴──────────┘" ); test_table!( modern_clean_style, Matrix::new(3, 3).with(Style::modern().remove_horizontal().horizontals([(1, HorizontalLine::inherit(Style::modern()))])), "┌───┬──────────┬──────────┬──────────┐" "│ N │ column 0 │ column 1 │ column 2 │" "├───┼──────────┼──────────┼──────────┤" "│ 0 │ 0-0 │ 0-1 │ 0-2 │" "│ 1 │ 1-0 │ 1-1 │ 1-2 │" "│ 2 │ 2-0 │ 2-1 │ 2-2 │" "└───┴──────────┴──────────┴──────────┘" ); test_table!( blank_style, Matrix::new(3, 3).with(Style::blank()), " N column 0 column 1 column 2 " " 0 0-0 0-1 0-2 " " 1 1-0 1-1 1-2 " " 2 2-0 2-1 2-2 " ); test_table!( extended_style, Matrix::new(3, 3).with(Style::extended()), "╔═══╦══════════╦══════════╦══════════╗" "║ N ║ column 0 ║ column 1 ║ column 2 ║" "╠═══╬══════════╬══════════╬══════════╣" "║ 0 ║ 0-0 ║ 0-1 ║ 0-2 ║" "╠═══╬══════════╬══════════╬══════════╣" "║ 1 ║ 1-0 ║ 1-1 ║ 1-2 ║" "╠═══╬══════════╬══════════╬══════════╣" "║ 2 ║ 2-0 ║ 2-1 ║ 2-2 ║" "╚═══╩══════════╩══════════╩══════════╝" ); test_table!( ascii_dots_style, Matrix::new(3, 3).with(Style::dots()), "......................................" ": N : column 0 : column 1 : column 2 :" ":...:..........:..........:..........:" ": 0 : 0-0 : 0-1 : 0-2 :" ":...:..........:..........:..........:" ": 1 : 1-0 : 1-1 : 1-2 :" ":...:..........:..........:..........:" ": 2 : 2-0 : 2-1 : 2-2 :" ":...:..........:..........:..........:" ); test_table!( re_structured_text_style, Matrix::new(3, 3).with(Style::re_structured_text()), "=== ========== ========== ==========" " N column 0 column 1 column 2 " "=== ========== ========== ==========" " 0 0-0 0-1 0-2 " " 1 1-0 1-1 1-2 " " 2 2-0 2-1 2-2 " "=== ========== ========== ==========" ); test_table!( ascii_rounded_style, Matrix::new(3, 3).with(Style::ascii_rounded()), ".------------------------------------." "| N | column 0 | column 1 | column 2 |" "| 0 | 0-0 | 0-1 | 0-2 |" "| 1 | 1-0 | 1-1 | 1-2 |" "| 2 | 2-0 | 2-1 | 2-2 |" "'------------------------------------'" ); test_table!( style_head_changes, Matrix::new(3, 3).with(Style::modern().remove_horizontal()), "┌───┬──────────┬──────────┬──────────┐" "│ N │ column 0 │ column 1 │ column 2 │" "│ 0 │ 0-0 │ 0-1 │ 0-2 │" "│ 1 │ 1-0 │ 1-1 │ 1-2 │" "│ 2 │ 2-0 │ 2-1 │ 2-2 │" "└───┴──────────┴──────────┴──────────┘" ); test_table!( style_frame_changes, Matrix::new(3, 3).with(Style::modern().remove_top().remove_bottom().remove_horizontal()), "│ N │ column 0 │ column 1 │ column 2 │" "│ 0 │ 0-0 │ 0-1 │ 0-2 │" "│ 1 │ 1-0 │ 1-1 │ 1-2 │" "│ 2 │ 2-0 │ 2-1 │ 2-2 │" ); test_table!( custom_style, Matrix::new(3, 3) .with(Style::blank() .bottom('*') .vertical('\'') .horizontal('`') .intersection('\'') .intersection_bottom('\'') .horizontals([(1, HorizontalLine::new('x').intersection('*'))])), " N ' column 0 ' column 1 ' column 2 " "xxx*xxxxxxxxxx*xxxxxxxxxx*xxxxxxxxxx" " 0 ' 0-0 ' 0-1 ' 0-2 " "```'``````````'``````````'``````````" " 1 ' 1-0 ' 1-1 ' 1-2 " "```'``````````'``````````'``````````" " 2 ' 2-0 ' 2-1 ' 2-2 " "***'**********'**********'**********" ); test_table!( style_single_cell_0, Matrix::table(0, 0), "+---+" "| N |" "+---+" ); test_table!( style_single_cell_1, Matrix::table(0, 0).with(Style::blank()), " N " ); test_table!( top_border_override_first_test, Matrix::table(2, 2).with(LineText::new("-Table", Rows::first())), "-Table---------+----------+" "| N | column 0 | column 1 |" "+---+----------+----------+" "| 0 | 0-0 | 0-1 |" "+---+----------+----------+" "| 1 | 1-0 | 1-1 |" "+---+----------+----------+" ); test_table!( top_border_override_last_test, Matrix::table(2, 2).with(LineText::new("-Table", Rows::last())), "+---+----------+----------+" "| N | column 0 | column 1 |" "+---+----------+----------+" "| 0 | 0-0 | 0-1 |" "-Table---------+----------+" "| 1 | 1-0 | 1-1 |" "+---+----------+----------+" ); test_table!( top_border_override_last_1_test, Matrix::table(2, 2).with(LineText::new("-Table", Rows::last() + 1)), "+---+----------+----------+" "| N | column 0 | column 1 |" "+---+----------+----------+" "| 0 | 0-0 | 0-1 |" "+---+----------+----------+" "| 1 | 1-0 | 1-1 |" "-Table---------+----------+" ); test_table!( top_border_override_new_test, Matrix::table(2, 2) .with(LineText::new("-Table", Rows::single(1))) .with(LineText::new("-Table", Rows::single(2))), "+---+----------+----------+" "| N | column 0 | column 1 |" "-Table---------+----------+" "| 0 | 0-0 | 0-1 |" "-Table---------+----------+" "| 1 | 1-0 | 1-1 |" "+---+----------+----------+" ); test_table!( top_border_override_new_doesnt_panic_when_index_is_invalid, Matrix::table(2, 2).with(LineText::new("-Table", Rows::single(100))), "+---+----------+----------+" "| N | column 0 | column 1 |" "+---+----------+----------+" "| 0 | 0-0 | 0-1 |" "+---+----------+----------+" "| 1 | 1-0 | 1-1 |" "+---+----------+----------+" ); test_table!( top_override_doesnt_work_with_style_with_no_top_border_test, Matrix::table(2, 2) .with(Style::psql()) .with(LineText::new("-Table", Rows::first())), " N | column 0 | column 1 " "---+----------+----------" " 0 | 0-0 | 0-1 " " 1 | 1-0 | 1-1 " ); test_table!( top_border_override_cleared_after_restyling_test, Matrix::table(2, 2) .with(LineText::new("-Table", Rows::first())) .with(Style::ascii()), "+---+----------+----------+" "| N | column 0 | column 1 |" "+---+----------+----------+" "| 0 | 0-0 | 0-1 |" "+---+----------+----------+" "| 1 | 1-0 | 1-1 |" "+---+----------+----------+" ); test_table!( top_border_override_with_big_string_test, Matrix::table(2, 2) .with(LineText::new("-Tableeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee1231", Rows::first())), "-Tableeeeeeeeeeeeeeeeeeeeee" "| N | column 0 | column 1 |" "+---+----------+----------+" "| 0 | 0-0 | 0-1 |" "+---+----------+----------+" "| 1 | 1-0 | 1-1 |" "+---+----------+----------+" ); test_table!( border_text_0, Matrix::table(2, 2) .with(Style::empty()) .with(Modify::new(Rows::first()).with(Border::new().bottom('-'))) .with(LineText::new("-Table", Rows::single(1))), " N column 0 column 1 " "-Table-----------------" " 0 0-0 0-1 " " 1 1-0 1-1 " ); test_table!( border_global_0, Matrix::table(2, 2) .with(Border::full('=', '*', '!','@', '#', '$', '%', '^')), "#=========================$" "! N | column 0 | column 1 @" "!---+----------+----------@" "! 0 | 0-0 | 0-1 @" "!---+----------+----------@" "! 1 | 1-0 | 1-1 @" "%*************************^" ); test_table!( border_global_1, Matrix::table(2, 2).with(Border::new().bottom('*')), "+---+----------+----------+" "| N | column 0 | column 1 |" "+---+----------+----------+" "| 0 | 0-0 | 0-1 |" "+---+----------+----------+" "| 1 | 1-0 | 1-1 |" "+*************************+" ); test_table!( border_color_global_0, Matrix::table(2, 2) .with(BorderColor::full( Color::FG_RED, Color::FG_BLUE, Color::FG_GREEN, Color::FG_CYAN, Color::FG_BLACK, Color::FG_WHITE, Color::FG_MAGENTA, Color::FG_YELLOW )), "\u{1b}[30m+\u{1b}[39m\u{1b}[31m---+----------+----------\u{1b}[39m\u{1b}[37m+\u{1b}[39m" "\u{1b}[32m|\u{1b}[39m N | column 0 | column 1 \u{1b}[36m|\u{1b}[39m" "\u{1b}[32m+\u{1b}[39m---+----------+----------\u{1b}[36m+\u{1b}[39m" "\u{1b}[32m|\u{1b}[39m 0 | 0-0 | 0-1 \u{1b}[36m|\u{1b}[39m" "\u{1b}[32m+\u{1b}[39m---+----------+----------\u{1b}[36m+\u{1b}[39m" "\u{1b}[32m|\u{1b}[39m 1 | 1-0 | 1-1 \u{1b}[36m|\u{1b}[39m" "\u{1b}[35m+\u{1b}[39m\u{1b}[34m---+----------+----------\u{1b}[39m\u{1b}[33m+\u{1b}[39m" ); test_table!( border_color_global_1, Matrix::table(2, 2).with(BorderColor::new().bottom(Color::FG_RED)), "+---+----------+----------+" "| N | column 0 | column 1 |" "+---+----------+----------+" "| 0 | 0-0 | 0-1 |" "+---+----------+----------+" "| 1 | 1-0 | 1-1 |" "+\u{1b}[31m---+----------+----------\u{1b}[39m+" ); #[cfg(feature = "ansi")] test_table!( border_text_colored, Matrix::table(2, 2) .with(LineText::new("-Table", Rows::single(1))) .with(LineText::new("-Table213123", Rows::single(2))) .modify(Rows::single(1), BorderColor::new().bottom(Color::FG_RED)) .modify(Rows::single(2), BorderColor::new().bottom(Color::FG_BLUE | Color::BG_GREEN)), "+---+----------+----------+" "| N | column 0 | column 1 |" "-Table---------+----------+" "| 0 | 0-0 | 0-1 |" "-\u{1b}[31mTab\u{1b}[39ml\u{1b}[31me213123---\u{1b}[39m+\u{1b}[31m----------\u{1b}[39m+" "| 1 | 1-0 | 1-1 |" "+\u{1b}[34m\u{1b}[42m---\u{1b}[39m\u{1b}[49m+\u{1b}[34m\u{1b}[42m----------\u{1b}[39m\u{1b}[49m+\u{1b}[34m\u{1b}[42m----------\u{1b}[39m\u{1b}[49m+" ); test_table!( border_text_offset_test_0, Matrix::table(2, 2).with(LineText::new("-Table", Rows::single(1)).offset(Offset::Begin(5))), "+---+----------+----------+" "| N | column 0 | column 1 |" "+---+-Table----+----------+" "| 0 | 0-0 | 0-1 |" "+---+----------+----------+" "| 1 | 1-0 | 1-1 |" "+---+----------+----------+" ); test_table!( border_text_offset_test_1, Matrix::table(2, 2).with(LineText::new("-Table", Rows::single(1)).offset(Offset::Begin(15))), "+---+----------+----------+" "| N | column 0 | column 1 |" "+---+-----------Table-----+" "| 0 | 0-0 | 0-1 |" "+---+----------+----------+" "| 1 | 1-0 | 1-1 |" "+---+----------+----------+" ); test_table!( border_text_offset_test_2, Matrix::table(2, 2).with(LineText::new("Table", Rows::single(1)).offset(Offset::End(5))), "+---+----------+----------+" "| N | column 0 | column 1 |" "+---+----------+------Table" "| 0 | 0-0 | 0-1 |" "+---+----------+----------+" "| 1 | 1-0 | 1-1 |" "+---+----------+----------+" ); test_table!( border_text_offset_test_3, Matrix::table(2, 2).with(LineText::new("Table", Rows::single(1)).offset(Offset::End(15))), "+---+----------+----------+" "| N | column 0 | column 1 |" "+---+-------Table---------+" "| 0 | 0-0 | 0-1 |" "+---+----------+----------+" "| 1 | 1-0 | 1-1 |" "+---+----------+----------+" ); test_table!( border_text_offset_test_4, Matrix::table(2, 2).with(LineText::new("Table", Rows::single(1)).offset(Offset::End(21))), "+---+----------+----------+" "| N | column 0 | column 1 |" "+---+-Table----+----------+" "| 0 | 0-0 | 0-1 |" "+---+----------+----------+" "| 1 | 1-0 | 1-1 |" "+---+----------+----------+" ); test_table!( border_text_offset_test_5, Matrix::table(2, 2).with(LineText::new("Table", Rows::single(1)).offset(Offset::End(25))), "+---+----------+----------+" "| N | column 0 | column 1 |" "+-Table--------+----------+" "| 0 | 0-0 | 0-1 |" "+---+----------+----------+" "| 1 | 1-0 | 1-1 |" "+---+----------+----------+" ); test_table!( border_text_offset_test_6, Matrix::table(2, 2).with(LineText::new("-Table", Rows::single(1)).offset(Offset::Begin(21))), "+---+----------+----------+" "| N | column 0 | column 1 |" "+---+----------+------Table" "| 0 | 0-0 | 0-1 |" "+---+----------+----------+" "| 1 | 1-0 | 1-1 |" "+---+----------+----------+" ); test_table!( border_override_color, Matrix::table(2, 2).with(LineText::new("-Table", Rows::first()).color(Color::FG_BLUE)), "\u{1b}[34m-\u{1b}[39m\u{1b}[34mT\u{1b}[39m\u{1b}[34ma\u{1b}[39m\u{1b}[34mb\u{1b}[39m\u{1b}[34ml\u{1b}[39m\u{1b}[34me\u{1b}[39m---------+----------+" "| N | column 0 | column 1 |" "+---+----------+----------+" "| 0 | 0-0 | 0-1 |" "+---+----------+----------+" "| 1 | 1-0 | 1-1 |" "+---+----------+----------+" ); test_table!( empty_style, Matrix::new(3, 3) .with(Style::empty()) .with(Modify::new(Segment::all()).with(Padding::zero())), "Ncolumn 0column 1column 2" "0 0-0 0-1 0-2 " "1 1-0 1-1 1-2 " "2 2-0 2-1 2-2 " ); test_table!( single_column_style_0, Matrix::table(2, 0).with(Style::modern()), "┌───┐" "│ N │" "├───┤" "│ 0 │" "├───┤" "│ 1 │" "└───┘" ); test_table!( single_column_style_1, Matrix::table(2, 0).with(Style::blank()), " N " " 0 " " 1 " ); test_table!( single_column_last_row_style, Matrix::table(3, 0).with(Style::re_structured_text()), "===" " N " "===" " 0 " " 1 " " 2 " "===" ); test_table!( single_cell_style, Builder::from_iter([[""]]).build().with(Style::modern()), "┌──┐" "│ │" "└──┘" ); test_table!( border_test_0, Matrix::table(2, 2).with(Modify::new(Rows::single(1)).with(Border::filled('*').top('#'))), "+---+----------+----------+" "| N | column 0 | column 1 |" "*###*##########*##########*" "* 0 * 0-0 * 0-1 *" "***************************" "| 1 | 1-0 | 1-1 |" "+---+----------+----------+" ); test_table!( border_test_1, Matrix::table(2, 2) .with(Style::empty()) .with(Modify::new(Rows::single(1)).with(Border::filled('*').top('#'))), " N column 0 column 1 " "*###*##########*##########*" "* 0 * 0-0 * 0-1 *" "***************************" " 1 1-0 1-1 " ); test_table!( style_frame_test_0, Matrix::table(2, 2).with(Highlight::new(Rows::single(1)).border(Border::inherit(Style::modern()))), "+---+----------+----------+" "| N | column 0 | column 1 |" "┌─────────────────────────┐" "│ 0 | 0-0 | 0-1 │" "└─────────────────────────┘" "| 1 | 1-0 | 1-1 |" "+---+----------+----------+" ); test_table!( style_frame_test_1, Matrix::table(2, 2) .with(Style::blank()) .with(Highlight::new(Rows::single(0)).border(Border::inherit(Style::extended()))) .with(Highlight::new(Rows::single(2)).border(Border::inherit(Style::extended()))), "╔═════════════════════════╗" "║ N column 0 column 1 ║" "╚═════════════════════════╝" " 0 0-0 0-1 " "╔═════════════════════════╗" "║ 1 1-0 1-1 ║" "╚═════════════════════════╝" ); test_table!( single_column_off_horizontal_test, Matrix::table(3, 0).with(Style::ascii().remove_horizontal().remove_vertical()), "+---+" "| N |" "| 0 |" "| 1 |" "| 2 |" "+---+" ); test_table!( single_row_test, Matrix::table(0, 3).with(Style::modern()), "┌───┬──────────┬──────────┬──────────┐" "│ N │ column 0 │ column 1 │ column 2 │" "└───┴──────────┴──────────┴──────────┘" ); test_table!( empty_border_text_doesnt_panic_test, Matrix::table(2, 2).with(LineText::new("", Rows::single(0))), "+---+----------+----------+" "| N | column 0 | column 1 |" "+---+----------+----------+" "| 0 | 0-0 | 0-1 |" "+---+----------+----------+" "| 1 | 1-0 | 1-1 |" "+---+----------+----------+" ); test_table!( span_correct_test_0, Matrix::table(6, 4) .with(Modify::new((0, 3)).with(Span::column(2))) .with(Modify::new((1, 0)).with(Span::column(3))) .with(Modify::new((2, 0)).with(Span::column(2))) .with(Modify::new((2, 3)).with(Span::column(2))) .with(Modify::new((3, 0)).with(Span::column(5))) .with(Modify::new((4, 1)).with(Span::column(4))) .with(Modify::new((5, 0)).with(Span::column(5))) .with(Modify::new((6, 0)).with(Span::column(5))) .with(BorderSpanCorrection), "+---+----------+----------+-----------+" "| N | column 0 | column 1 | column 2 |" "+---+----------+----------+-----+-----+" "| 0 | 0-2 | 0-3 |" "+--------------+----------+-----+-----+" "| 1 | 1-1 | 1-2 |" "+--------------+----------+-----------+" "| 2 |" "+---+---------------------------------+" "| 3 | 3-0 |" "+---+---------------------------------+" "| 4 |" "+-------------------------------------+" "| 5 |" "+-------------------------------------+" ); test_table!( span_correct_test_1, Matrix::table(6, 4) .with(Modify::new((0, 0)).with(Span::column(5))) .with(Modify::new((1, 0)).with(Span::column(3))) .with(Modify::new((2, 0)).with(Span::column(2))) .with(Modify::new((2, 3)).with(Span::column(2))) .with(Modify::new((3, 0)).with(Span::column(5))) .with(Modify::new((4, 1)).with(Span::column(4))) .with(Modify::new((5, 0)).with(Span::column(5))) .with(Modify::new((6, 0)).with(Span::column(5))) .with(BorderSpanCorrection), "+----------------------+" "| N |" "+----------+-----+-----+" "| 0 | 0-2 | 0-3 |" "+----+-----+-----+-----+" "| 1 | 1-1 | 1-2 |" "+----+-----+-----------+" "| 2 |" "+---+------------------+" "| 3 | 3-0 |" "+---+------------------+" "| 4 |" "+----------------------+" "| 5 |" "+----------------------+" ); test_table!( style_settings_usage_test_0, Matrix::new(3, 3) .insert((1, 1).into(), "a longer string") .with({ let mut style = Theme::from_style(Style::modern()); style.set_borders_bottom('a'); style.set_borders_left('b'); style.set_borders_intersection('x'); style.remove_borders_right(); style.remove_borders_top(); style.remove_borders_intersection_top(); style.remove_borders_corner_top_left(); style.remove_borders_corner_top_right(); style }), "b N │ column 0 │ column 1 │ column 2 " "├───x─────────────────x──────────x──────────┤" "b 0 │ a longer string │ 0-1 │ 0-2 " "├───x─────────────────x──────────x──────────┤" "b 1 │ 1-0 │ 1-1 │ 1-2 " "├───x─────────────────x──────────x──────────┤" "b 2 │ 2-0 │ 2-1 │ 2-2 " "└aaa┴aaaaaaaaaaaaaaaaa┴aaaaaaaaaa┴aaaaaaaaaa┘" ); test_table!( style_settings_usage_test_1, Matrix::new(3, 3) .insert((1, 1).into(), "a longer string") .with({ let mut style = Theme::from_style(Style::modern()); style.remove_borders_bottom(); style }), "┌───┬─────────────────┬──────────┬──────────┐" "│ N │ column 0 │ column 1 │ column 2 │" "├───┼─────────────────┼──────────┼──────────┤" "│ 0 │ a longer string │ 0-1 │ 0-2 │" "├───┼─────────────────┼──────────┼──────────┤" "│ 1 │ 1-0 │ 1-1 │ 1-2 │" "├───┼─────────────────┼──────────┼──────────┤" "│ 2 │ 2-0 │ 2-1 │ 2-2 │" "└ ┴ ┴ ┴ ┘" ); test_table!( style_settings_usage_test_2, Matrix::new(3, 3) .insert((1, 1).into(), "a longer string") .with({ let mut style = Theme::from_style(Style::modern()); style.remove_borders_bottom(); style }) .with(Modify::new(Rows::last()).with(GridBorder { left_bottom_corner: Some('*'), ..Default::default() })), "┌───┬─────────────────┬──────────┬──────────┐" "│ N │ column 0 │ column 1 │ column 2 │" "├───┼─────────────────┼──────────┼──────────┤" "│ 0 │ a longer string │ 0-1 │ 0-2 │" "├───┼─────────────────┼──────────┼──────────┤" "│ 1 │ 1-0 │ 1-1 │ 1-2 │" "├───┼─────────────────┼──────────┼──────────┤" "│ 2 │ 2-0 │ 2-1 │ 2-2 │" "* * * * ┘" ); test_table!( border_none_test_0, Matrix::table(2, 2) .with(Style::ascii()) .with(Modify::new(Rows::single(1)).with(Border::filled('*').top('#'))) .with(Modify::new(Rows::single(1)).with(Border::empty())), "+---+----------+----------+" "| N | column 0 | column 1 |" "+---+----------+----------+" "| 0 | 0-0 | 0-1 |" "+---+----------+----------+" "| 1 | 1-0 | 1-1 |" "+---+----------+----------+" ); test_table!( border_none_test_1, Matrix::table(2, 2) .with(Style::empty()) .with(Modify::new(Rows::single(1)).with(Border::filled('*').top('#'))) .with(Modify::new(Columns::single(1)).with(Border::empty())), " N column 0 column 1 " "*### ##########*" "* 0 0-0 0-1 *" "**** ***********" " 1 1-0 1-1 " ); #[test] fn custom_style_test() { macro_rules! test_style { ($style:expr, $expected:expr $(,)*) => { let table = Matrix::new(3, 3).with($style).to_string(); println!("{table}"); assert_eq!(table, $expected); }; } // Single test_style!( Style::empty().top('-'), static_table!( "---------------------------------" " N column 0 column 1 column 2 " " 0 0-0 0-1 0-2 " " 1 1-0 1-1 1-2 " " 2 2-0 2-1 2-2 " ), ); test_style!( Style::empty().bottom('-'), static_table!( " N column 0 column 1 column 2 " " 0 0-0 0-1 0-2 " " 1 1-0 1-1 1-2 " " 2 2-0 2-1 2-2 " "---------------------------------" ), ); test_style!( Style::empty().left('-'), static_table!( "- N column 0 column 1 column 2 " "- 0 0-0 0-1 0-2 " "- 1 1-0 1-1 1-2 " "- 2 2-0 2-1 2-2 " ), ); test_style!( Style::empty().right('-'), static_table!( " N column 0 column 1 column 2 -" " 0 0-0 0-1 0-2 -" " 1 1-0 1-1 1-2 -" " 2 2-0 2-1 2-2 -" ), ); test_style!( Style::empty().horizontal('-'), static_table!( " N column 0 column 1 column 2 " "---------------------------------" " 0 0-0 0-1 0-2 " "---------------------------------" " 1 1-0 1-1 1-2 " "---------------------------------" " 2 2-0 2-1 2-2 " ), ); test_style!( Style::empty().horizontals([(1, HorizontalLine::new('-'))]), static_table!( " N column 0 column 1 column 2 " "---------------------------------" " 0 0-0 0-1 0-2 " " 1 1-0 1-1 1-2 " " 2 2-0 2-1 2-2 " ), ); test_style!( Style::empty().vertical('-'), static_table!( " N - column 0 - column 1 - column 2 " " 0 - 0-0 - 0-1 - 0-2 " " 1 - 1-0 - 1-1 - 1-2 " " 2 - 2-0 - 2-1 - 2-2 " ), ); // Combinations test_style!( Style::empty().top('-').bottom('+'), static_table!( "---------------------------------" " N column 0 column 1 column 2 " " 0 0-0 0-1 0-2 " " 1 1-0 1-1 1-2 " " 2 2-0 2-1 2-2 " "+++++++++++++++++++++++++++++++++" ) ); test_style!( Style::empty().top('-').left('+'), static_table!( "+---------------------------------" "+ N column 0 column 1 column 2 " "+ 0 0-0 0-1 0-2 " "+ 1 1-0 1-1 1-2 " "+ 2 2-0 2-1 2-2 " ) ); test_style!( Style::empty().top('-').right('+'), static_table!( "---------------------------------+" " N column 0 column 1 column 2 +" " 0 0-0 0-1 0-2 +" " 1 1-0 1-1 1-2 +" " 2 2-0 2-1 2-2 +" ) ); test_style!( Style::empty().top('-').horizontal('+'), static_table!( "---------------------------------" " N column 0 column 1 column 2 " "+++++++++++++++++++++++++++++++++" " 0 0-0 0-1 0-2 " "+++++++++++++++++++++++++++++++++" " 1 1-0 1-1 1-2 " "+++++++++++++++++++++++++++++++++" " 2 2-0 2-1 2-2 " ) ); test_style!( Style::empty().top('-').vertical('+'), static_table!( "---+----------+----------+----------" " N + column 0 + column 1 + column 2 " " 0 + 0-0 + 0-1 + 0-2 " " 1 + 1-0 + 1-1 + 1-2 " " 2 + 2-0 + 2-1 + 2-2 " ) ); test_style!( Style::empty() .top('-') .horizontals([(1, HorizontalLine::new('+'))]), static_table!( "---------------------------------" " N column 0 column 1 column 2 " "+++++++++++++++++++++++++++++++++" " 0 0-0 0-1 0-2 " " 1 1-0 1-1 1-2 " " 2 2-0 2-1 2-2 " ) ); test_style!( Style::empty().bottom('-').top('+'), static_table!( "+++++++++++++++++++++++++++++++++" " N column 0 column 1 column 2 " " 0 0-0 0-1 0-2 " " 1 1-0 1-1 1-2 " " 2 2-0 2-1 2-2 " "---------------------------------" ) ); test_style!( Style::empty().bottom('-').left('+'), static_table!( "+ N column 0 column 1 column 2 " "+ 0 0-0 0-1 0-2 " "+ 1 1-0 1-1 1-2 " "+ 2 2-0 2-1 2-2 " "+---------------------------------" ) ); test_style!( Style::empty().bottom('-').right('+'), static_table!( " N column 0 column 1 column 2 +" " 0 0-0 0-1 0-2 +" " 1 1-0 1-1 1-2 +" " 2 2-0 2-1 2-2 +" "---------------------------------+" ) ); test_style!( Style::empty().bottom('-').vertical('+'), static_table!( " N + column 0 + column 1 + column 2 " " 0 + 0-0 + 0-1 + 0-2 " " 1 + 1-0 + 1-1 + 1-2 " " 2 + 2-0 + 2-1 + 2-2 " "---+----------+----------+----------" ) ); test_style!( Style::empty().bottom('-').horizontal('+'), static_table!( " N column 0 column 1 column 2 " "+++++++++++++++++++++++++++++++++" " 0 0-0 0-1 0-2 " "+++++++++++++++++++++++++++++++++" " 1 1-0 1-1 1-2 " "+++++++++++++++++++++++++++++++++" " 2 2-0 2-1 2-2 " "---------------------------------" ) ); test_style!( Style::empty() .bottom('-') .horizontals([(1, HorizontalLine::new('+'))]), static_table!( " N column 0 column 1 column 2 " "+++++++++++++++++++++++++++++++++" " 0 0-0 0-1 0-2 " " 1 1-0 1-1 1-2 " " 2 2-0 2-1 2-2 " "---------------------------------" ) ); test_style!( Style::empty().left('-').top('+'), static_table!( "++++++++++++++++++++++++++++++++++" "- N column 0 column 1 column 2 " "- 0 0-0 0-1 0-2 " "- 1 1-0 1-1 1-2 " "- 2 2-0 2-1 2-2 " ) ); test_style!( Style::empty().left('-').bottom('+'), static_table!( "- N column 0 column 1 column 2 " "- 0 0-0 0-1 0-2 " "- 1 1-0 1-1 1-2 " "- 2 2-0 2-1 2-2 " "++++++++++++++++++++++++++++++++++" ) ); test_style!( Style::empty().left('-').right('+'), static_table!( "- N column 0 column 1 column 2 +" "- 0 0-0 0-1 0-2 +" "- 1 1-0 1-1 1-2 +" "- 2 2-0 2-1 2-2 +" ) ); test_style!( Style::empty().left('-').vertical('+'), static_table!( "- N + column 0 + column 1 + column 2 " "- 0 + 0-0 + 0-1 + 0-2 " "- 1 + 1-0 + 1-1 + 1-2 " "- 2 + 2-0 + 2-1 + 2-2 " ) ); test_style!( Style::empty().left('-').horizontal('+'), static_table!( "- N column 0 column 1 column 2 " "++++++++++++++++++++++++++++++++++" "- 0 0-0 0-1 0-2 " "++++++++++++++++++++++++++++++++++" "- 1 1-0 1-1 1-2 " "++++++++++++++++++++++++++++++++++" "- 2 2-0 2-1 2-2 " ) ); test_style!( Style::empty() .left('-') .horizontals([(1, HorizontalLine::new('+').left(' '))]), static_table!( "- N column 0 column 1 column 2 " " +++++++++++++++++++++++++++++++++" "- 0 0-0 0-1 0-2 " "- 1 1-0 1-1 1-2 " "- 2 2-0 2-1 2-2 " ) ); test_style!( Style::empty().right('-').top('+'), static_table!( "++++++++++++++++++++++++++++++++++" " N column 0 column 1 column 2 -" " 0 0-0 0-1 0-2 -" " 1 1-0 1-1 1-2 -" " 2 2-0 2-1 2-2 -" ) ); test_style!( Style::empty().right('-').bottom('+'), static_table!( " N column 0 column 1 column 2 -" " 0 0-0 0-1 0-2 -" " 1 1-0 1-1 1-2 -" " 2 2-0 2-1 2-2 -" "++++++++++++++++++++++++++++++++++" ) ); test_style!( Style::empty().right('-').left('+'), static_table!( "+ N column 0 column 1 column 2 -" "+ 0 0-0 0-1 0-2 -" "+ 1 1-0 1-1 1-2 -" "+ 2 2-0 2-1 2-2 -" ) ); test_style!( Style::empty().right('-').vertical('+'), static_table!( " N + column 0 + column 1 + column 2 -" " 0 + 0-0 + 0-1 + 0-2 -" " 1 + 1-0 + 1-1 + 1-2 -" " 2 + 2-0 + 2-1 + 2-2 -" ) ); test_style!( Style::empty().right('-').horizontal('+'), static_table!( " N column 0 column 1 column 2 -" "++++++++++++++++++++++++++++++++++" " 0 0-0 0-1 0-2 -" "++++++++++++++++++++++++++++++++++" " 1 1-0 1-1 1-2 -" "++++++++++++++++++++++++++++++++++" " 2 2-0 2-1 2-2 -" ) ); test_style!( Style::empty() .right('-') .horizontals([(1, HorizontalLine::new('+').right(' '))]), static_table!( " N column 0 column 1 column 2 -" "+++++++++++++++++++++++++++++++++ " " 0 0-0 0-1 0-2 -" " 1 1-0 1-1 1-2 -" " 2 2-0 2-1 2-2 -" ) ); test_style!( Style::empty().vertical('-').top('+'), static_table!( "++++++++++++++++++++++++++++++++++++" " N - column 0 - column 1 - column 2 " " 0 - 0-0 - 0-1 - 0-2 " " 1 - 1-0 - 1-1 - 1-2 " " 2 - 2-0 - 2-1 - 2-2 " ) ); test_style!( Style::empty().vertical('-').bottom('+'), static_table!( " N - column 0 - column 1 - column 2 " " 0 - 0-0 - 0-1 - 0-2 " " 1 - 1-0 - 1-1 - 1-2 " " 2 - 2-0 - 2-1 - 2-2 " "++++++++++++++++++++++++++++++++++++" ) ); test_style!( Style::empty().vertical('-').left('+'), static_table!( "+ N - column 0 - column 1 - column 2 " "+ 0 - 0-0 - 0-1 - 0-2 " "+ 1 - 1-0 - 1-1 - 1-2 " "+ 2 - 2-0 - 2-1 - 2-2 " ) ); test_style!( Style::empty().vertical('-').right('+'), static_table!( " N - column 0 - column 1 - column 2 +" " 0 - 0-0 - 0-1 - 0-2 +" " 1 - 1-0 - 1-1 - 1-2 +" " 2 - 2-0 - 2-1 - 2-2 +" ) ); test_style!( Style::empty().vertical('-').horizontal('+'), static_table!( " N - column 0 - column 1 - column 2 " "++++++++++++++++++++++++++++++++++++" " 0 - 0-0 - 0-1 - 0-2 " "++++++++++++++++++++++++++++++++++++" " 1 - 1-0 - 1-1 - 1-2 " "++++++++++++++++++++++++++++++++++++" " 2 - 2-0 - 2-1 - 2-2 " ) ); test_style!( Style::empty() .vertical('-') .horizontals([(1, HorizontalLine::new('+').intersection(' '))]), static_table!( " N - column 0 - column 1 - column 2 " "+++ ++++++++++ ++++++++++ ++++++++++" " 0 - 0-0 - 0-1 - 0-2 " " 1 - 1-0 - 1-1 - 1-2 " " 2 - 2-0 - 2-1 - 2-2 " ) ); test_style!( Style::empty().horizontal('-').top('+'), static_table!( "+++++++++++++++++++++++++++++++++" " N column 0 column 1 column 2 " "---------------------------------" " 0 0-0 0-1 0-2 " "---------------------------------" " 1 1-0 1-1 1-2 " "---------------------------------" " 2 2-0 2-1 2-2 " ) ); test_style!( Style::empty().horizontal('-').bottom('+'), static_table!( " N column 0 column 1 column 2 " "---------------------------------" " 0 0-0 0-1 0-2 " "---------------------------------" " 1 1-0 1-1 1-2 " "---------------------------------" " 2 2-0 2-1 2-2 " "+++++++++++++++++++++++++++++++++" ) ); test_style!( Style::empty().horizontal('-').left('+'), static_table!( "+ N column 0 column 1 column 2 " "+---------------------------------" "+ 0 0-0 0-1 0-2 " "+---------------------------------" "+ 1 1-0 1-1 1-2 " "+---------------------------------" "+ 2 2-0 2-1 2-2 " ) ); test_style!( Style::empty().horizontal('-').right('+'), static_table!( " N column 0 column 1 column 2 +" "---------------------------------+" " 0 0-0 0-1 0-2 +" "---------------------------------+" " 1 1-0 1-1 1-2 +" "---------------------------------+" " 2 2-0 2-1 2-2 +" ) ); test_style!( Style::empty().horizontal('-').vertical('+'), static_table!( " N + column 0 + column 1 + column 2 " "---+----------+----------+----------" " 0 + 0-0 + 0-1 + 0-2 " "---+----------+----------+----------" " 1 + 1-0 + 1-1 + 1-2 " "---+----------+----------+----------" " 2 + 2-0 + 2-1 + 2-2 " ) ); test_style!( Style::empty() .horizontal('-') .horizontals([(1, HorizontalLine::new('+'))]), static_table!( " N column 0 column 1 column 2 " "+++++++++++++++++++++++++++++++++" " 0 0-0 0-1 0-2 " "---------------------------------" " 1 1-0 1-1 1-2 " "---------------------------------" " 2 2-0 2-1 2-2 " ) ); test_style!( Style::empty() .top('+') .horizontals([(1, HorizontalLine::new('-'))]), static_table!( "+++++++++++++++++++++++++++++++++" " N column 0 column 1 column 2 " "---------------------------------" " 0 0-0 0-1 0-2 " " 1 1-0 1-1 1-2 " " 2 2-0 2-1 2-2 " ) ); test_style!( Style::empty() .horizontals([(1, HorizontalLine::new('-'))]) .bottom('+'), static_table!( " N column 0 column 1 column 2 " "---------------------------------" " 0 0-0 0-1 0-2 " " 1 1-0 1-1 1-2 " " 2 2-0 2-1 2-2 " "+++++++++++++++++++++++++++++++++" ) ); test_style!( Style::empty() .horizontals([(1, HorizontalLine::new('-'))]) .left('+'), static_table!( "+ N column 0 column 1 column 2 " "+---------------------------------" "+ 0 0-0 0-1 0-2 " "+ 1 1-0 1-1 1-2 " "+ 2 2-0 2-1 2-2 " ) ); test_style!( Style::empty() .horizontals([(1, HorizontalLine::new('-'))]) .right('+'), static_table!( " N column 0 column 1 column 2 +" "---------------------------------+" " 0 0-0 0-1 0-2 +" " 1 1-0 1-1 1-2 +" " 2 2-0 2-1 2-2 +" ) ); test_style!( Style::empty() .horizontals([(1, HorizontalLine::new('-'))]) .vertical('+'), static_table!( " N + column 0 + column 1 + column 2 " "---+----------+----------+----------" " 0 + 0-0 + 0-1 + 0-2 " " 1 + 1-0 + 1-1 + 1-2 " " 2 + 2-0 + 2-1 + 2-2 " ) ); test_style!( Style::empty() .horizontals([(1, HorizontalLine::new('-'))]) .horizontal('+'), static_table!( " N column 0 column 1 column 2 " "---------------------------------" " 0 0-0 0-1 0-2 " "+++++++++++++++++++++++++++++++++" " 1 1-0 1-1 1-2 " "+++++++++++++++++++++++++++++++++" " 2 2-0 2-1 2-2 " ) ); // Full test_style!( Style::empty() .top('-') .bottom('+') .left('|') .right('*') .horizontal('x') .horizontals([(1, HorizontalLine::filled('z').remove_intersection())]) .vertical('#'), static_table!( "|---#----------#----------#----------*" "| N # column 0 # column 1 # column 2 *" "zzzz#zzzzzzzzzz#zzzzzzzzzz#zzzzzzzzzzz" "| 0 # 0-0 # 0-1 # 0-2 *" "xxxx#xxxxxxxxxx#xxxxxxxxxx#xxxxxxxxxxx" "| 1 # 1-0 # 1-1 # 1-2 *" "xxxx#xxxxxxxxxx#xxxxxxxxxx#xxxxxxxxxxx" "| 2 # 2-0 # 2-1 # 2-2 *" "|+++#++++++++++#++++++++++#++++++++++*" ), ); let full_style = Style::empty() .top('-') .bottom('+') .left('|') .right('*') .horizontal('x') .vertical('#') .intersection_bottom('@') .intersection_top('!') .intersection_left('=') .intersection_right('$') .intersection('+') .corner_top_left(';') .corner_bottom_left('?') .corner_top_right('.') .corner_bottom_right('%') .horizontals([(1, HorizontalLine::full(',', '#', ',', ','))]); test_style!( full_style.clone(), static_table!( ";---!----------!----------!----------." "| N # column 0 # column 1 # column 2 *" ",,,,#,,,,,,,,,,#,,,,,,,,,,#,,,,,,,,,,," "| 0 # 0-0 # 0-1 # 0-2 *" "=xxx+xxxxxxxxxx+xxxxxxxxxx+xxxxxxxxxx$" "| 1 # 1-0 # 1-1 # 1-2 *" "=xxx+xxxxxxxxxx+xxxxxxxxxx+xxxxxxxxxx$" "| 2 # 2-0 # 2-1 # 2-2 *" "?+++@++++++++++@++++++++++@++++++++++%" ) ); // Overwrite intersections and corners test_style!( full_style.clone().top('q'), static_table!( "qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq" "| N # column 0 # column 1 # column 2 *" ",,,,#,,,,,,,,,,#,,,,,,,,,,#,,,,,,,,,,," "| 0 # 0-0 # 0-1 # 0-2 *" "=xxx+xxxxxxxxxx+xxxxxxxxxx+xxxxxxxxxx$" "| 1 # 1-0 # 1-1 # 1-2 *" "=xxx+xxxxxxxxxx+xxxxxxxxxx+xxxxxxxxxx$" "| 2 # 2-0 # 2-1 # 2-2 *" "?+++@++++++++++@++++++++++@++++++++++%" ) ); test_style!( full_style.clone().bottom('q'), static_table!( ";---!----------!----------!----------." "| N # column 0 # column 1 # column 2 *" ",,,,#,,,,,,,,,,#,,,,,,,,,,#,,,,,,,,,,," "| 0 # 0-0 # 0-1 # 0-2 *" "=xxx+xxxxxxxxxx+xxxxxxxxxx+xxxxxxxxxx$" "| 1 # 1-0 # 1-1 # 1-2 *" "=xxx+xxxxxxxxxx+xxxxxxxxxx+xxxxxxxxxx$" "| 2 # 2-0 # 2-1 # 2-2 *" "qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq" ) ); test_style!( full_style.clone().left('w'), static_table!( "w---!----------!----------!----------." "w N # column 0 # column 1 # column 2 *" "w,,,#,,,,,,,,,,#,,,,,,,,,,#,,,,,,,,,,," "w 0 # 0-0 # 0-1 # 0-2 *" "wxxx+xxxxxxxxxx+xxxxxxxxxx+xxxxxxxxxx$" "w 1 # 1-0 # 1-1 # 1-2 *" "wxxx+xxxxxxxxxx+xxxxxxxxxx+xxxxxxxxxx$" "w 2 # 2-0 # 2-1 # 2-2 *" "w+++@++++++++++@++++++++++@++++++++++%" ) ); test_style!( full_style.clone().right('i'), static_table!( ";---!----------!----------!----------i" "| N # column 0 # column 1 # column 2 i" ",,,,#,,,,,,,,,,#,,,,,,,,,,#,,,,,,,,,,i" "| 0 # 0-0 # 0-1 # 0-2 i" "=xxx+xxxxxxxxxx+xxxxxxxxxx+xxxxxxxxxxi" "| 1 # 1-0 # 1-1 # 1-2 i" "=xxx+xxxxxxxxxx+xxxxxxxxxx+xxxxxxxxxxi" "| 2 # 2-0 # 2-1 # 2-2 i" "?+++@++++++++++@++++++++++@++++++++++i" ) ); test_style!( full_style.clone().horizontal('q'), static_table!( ";---!----------!----------!----------." "| N # column 0 # column 1 # column 2 *" ",,,,#,,,,,,,,,,#,,,,,,,,,,#,,,,,,,,,,," "| 0 # 0-0 # 0-1 # 0-2 *" "qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq" "| 1 # 1-0 # 1-1 # 1-2 *" "qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq" "| 2 # 2-0 # 2-1 # 2-2 *" "?+++@++++++++++@++++++++++@++++++++++%" ) ); test_style!( full_style.clone().vertical('q'), static_table!( ";---q----------q----------q----------." "| N q column 0 q column 1 q column 2 *" ",,,,q,,,,,,,,,,q,,,,,,,,,,q,,,,,,,,,,," "| 0 q 0-0 q 0-1 q 0-2 *" "=xxxqxxxxxxxxxxqxxxxxxxxxxqxxxxxxxxxx$" "| 1 q 1-0 q 1-1 q 1-2 *" "=xxxqxxxxxxxxxxqxxxxxxxxxxqxxxxxxxxxx$" "| 2 q 2-0 q 2-1 q 2-2 *" "?+++q++++++++++q++++++++++q++++++++++%" ) ); test_style!( full_style .clone() .horizontals([(1, HorizontalLine::filled('q'))]), static_table!( ";---!----------!----------!----------." "| N # column 0 # column 1 # column 2 *" "qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq" "| 0 # 0-0 # 0-1 # 0-2 *" "=xxx+xxxxxxxxxx+xxxxxxxxxx+xxxxxxxxxx$" "| 1 # 1-0 # 1-1 # 1-2 *" "=xxx+xxxxxxxxxx+xxxxxxxxxx+xxxxxxxxxx$" "| 2 # 2-0 # 2-1 # 2-2 *" "?+++@++++++++++@++++++++++@++++++++++%" ) ); // Turn off borders let empty_table = static_table!( " N column 0 column 1 column 2 " " 0 0-0 0-1 0-2 " " 1 1-0 1-1 1-2 " " 2 2-0 2-1 2-2 " ); test_style!(Style::empty().top('-').remove_top(), empty_table); test_style!(Style::empty().bottom('-').remove_bottom(), empty_table); test_style!(Style::empty().right('-').remove_right(), empty_table); test_style!(Style::empty().left('-').remove_left(), empty_table); test_style!( Style::empty().horizontal('-').remove_horizontal(), empty_table ); test_style!(Style::empty().vertical('-').remove_vertical(), empty_table); test_style!( Style::empty().horizontals([(1, HorizontalLine::new('-'))]), static_table!( " N column 0 column 1 column 2 " "---------------------------------" " 0 0-0 0-1 0-2 " " 1 1-0 1-1 1-2 " " 2 2-0 2-1 2-2 " ) ); test_style!( full_style.clone().remove_top(), static_table!( "| N # column 0 # column 1 # column 2 *" ",,,,#,,,,,,,,,,#,,,,,,,,,,#,,,,,,,,,,," "| 0 # 0-0 # 0-1 # 0-2 *" "=xxx+xxxxxxxxxx+xxxxxxxxxx+xxxxxxxxxx$" "| 1 # 1-0 # 1-1 # 1-2 *" "=xxx+xxxxxxxxxx+xxxxxxxxxx+xxxxxxxxxx$" "| 2 # 2-0 # 2-1 # 2-2 *" "?+++@++++++++++@++++++++++@++++++++++%" ) ); test_style!( full_style.clone().remove_bottom(), static_table!( ";---!----------!----------!----------." "| N # column 0 # column 1 # column 2 *" ",,,,#,,,,,,,,,,#,,,,,,,,,,#,,,,,,,,,,," "| 0 # 0-0 # 0-1 # 0-2 *" "=xxx+xxxxxxxxxx+xxxxxxxxxx+xxxxxxxxxx$" "| 1 # 1-0 # 1-1 # 1-2 *" "=xxx+xxxxxxxxxx+xxxxxxxxxx+xxxxxxxxxx$" "| 2 # 2-0 # 2-1 # 2-2 *" ) ); test_style!( full_style.clone().remove_right(), static_table!( ";---!----------!----------!----------" "| N # column 0 # column 1 # column 2 " ",,,,#,,,,,,,,,,#,,,,,,,,,,#,,,,,,,,,," "| 0 # 0-0 # 0-1 # 0-2 " "=xxx+xxxxxxxxxx+xxxxxxxxxx+xxxxxxxxxx" "| 1 # 1-0 # 1-1 # 1-2 " "=xxx+xxxxxxxxxx+xxxxxxxxxx+xxxxxxxxxx" "| 2 # 2-0 # 2-1 # 2-2 " "?+++@++++++++++@++++++++++@++++++++++" ) ); test_style!( full_style.clone().remove_left(), static_table!( "---!----------!----------!----------." " N # column 0 # column 1 # column 2 *" ",,,#,,,,,,,,,,#,,,,,,,,,,#,,,,,,,,,,," " 0 # 0-0 # 0-1 # 0-2 *" "xxx+xxxxxxxxxx+xxxxxxxxxx+xxxxxxxxxx$" " 1 # 1-0 # 1-1 # 1-2 *" "xxx+xxxxxxxxxx+xxxxxxxxxx+xxxxxxxxxx$" " 2 # 2-0 # 2-1 # 2-2 *" "+++@++++++++++@++++++++++@++++++++++%" ) ); test_style!( full_style.clone().remove_horizontal(), static_table!( ";---!----------!----------!----------." "| N # column 0 # column 1 # column 2 *" ",,,,#,,,,,,,,,,#,,,,,,,,,,#,,,,,,,,,,," "| 0 # 0-0 # 0-1 # 0-2 *" "| 1 # 1-0 # 1-1 # 1-2 *" "| 2 # 2-0 # 2-1 # 2-2 *" "?+++@++++++++++@++++++++++@++++++++++%" ) ); test_style!( full_style.clone().remove_vertical(), static_table!( ";---------------------------------." "| N column 0 column 1 column 2 *" ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,," "| 0 0-0 0-1 0-2 *" "=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx$" "| 1 1-0 1-1 1-2 *" "=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx$" "| 2 2-0 2-1 2-2 *" "?+++++++++++++++++++++++++++++++++%" ) ); test_style!( full_style.remove_horizontals(), static_table!( ";---!----------!----------!----------." "| N # column 0 # column 1 # column 2 *" "=xxx+xxxxxxxxxx+xxxxxxxxxx+xxxxxxxxxx$" "| 0 # 0-0 # 0-1 # 0-2 *" "=xxx+xxxxxxxxxx+xxxxxxxxxx+xxxxxxxxxx$" "| 1 # 1-0 # 1-1 # 1-2 *" "=xxx+xxxxxxxxxx+xxxxxxxxxx+xxxxxxxxxx$" "| 2 # 2-0 # 2-1 # 2-2 *" "?+++@++++++++++@++++++++++@++++++++++%" ) ); } #[test] fn test_default_border_usage() { macro_rules! test_border { ($modify:expr, $expected:expr) => { let table = Matrix::new(3, 3) .insert((1, 1).into(), "a longer string") .with(Style::empty()) .with($modify) .to_string(); assert_eq!(table, $expected); }; } test_border! { Modify::new((3, 2)).with(Border::new().bottom(' ').left(' ').corner_bottom_left('*')), static_table!( " N column 0 column 1 column 2 " " 0 a longer string 0-1 0-2 " " 1 1-0 1-1 1-2 " " 2 2-0 2-1 2-2 " " * " ) } test_border! { Modify::new((3, 2)).with(Border::new().bottom(' ').right(' ').corner_bottom_right('*')), static_table!( " N column 0 column 1 column 2 " " 0 a longer string 0-1 0-2 " " 1 1-0 1-1 1-2 " " 2 2-0 2-1 2-2 " " * " ) } test_border! { Modify::new((3, 2)).with(Border::new().bottom('*')), static_table!( " N column 0 column 1 column 2 " " 0 a longer string 0-1 0-2 " " 1 1-0 1-1 1-2 " " 2 2-0 2-1 2-2 " " ********** " ) } test_border! { Modify::new((3, 2)).with(Border::new().bottom('*').left(' ').corner_bottom_left('#')), static_table!( " N column 0 column 1 column 2 " " 0 a longer string 0-1 0-2 " " 1 1-0 1-1 1-2 " " 2 2-0 2-1 2-2 " " #********** " ) } test_border! { Modify::new((3, 2)).with(Border::new().bottom('*').right(' ').corner_bottom_right('#')), static_table!( " N column 0 column 1 column 2 " " 0 a longer string 0-1 0-2 " " 1 1-0 1-1 1-2 " " 2 2-0 2-1 2-2 " " **********# " ) } test_border! { Modify::new((3, 2)).with(Border::new().left('*')), static_table!( " N column 0 column 1 column 2 " " 0 a longer string 0-1 0-2 " " 1 1-0 1-1 1-2 " " 2 2-0 * 2-1 2-2 " ) } test_border! { Modify::new((3, 2)).with(Border::new().top(' ').left(' ').corner_top_left('*')), static_table!( " N column 0 column 1 column 2 " " 0 a longer string 0-1 0-2 " " 1 1-0 1-1 1-2 " " * " " 2 2-0 2-1 2-2 " ) } test_border! { Modify::new((3, 2)).with(Border::new().left('#').top(' ').corner_top_left('*')), static_table!( " N column 0 column 1 column 2 " " 0 a longer string 0-1 0-2 " " 1 1-0 1-1 1-2 " " * " " 2 2-0 # 2-1 2-2 " ) } test_border! { Modify::new((3, 2)).with(Border::new().left('#').top(' ').bottom(' ').corner_bottom_left('@').corner_top_left('*')), static_table!( " N column 0 column 1 column 2 " " 0 a longer string 0-1 0-2 " " 1 1-0 1-1 1-2 " " * " " 2 2-0 # 2-1 2-2 " " @ " ) } test_border! { Modify::new((3, 2)).with(Border::new().right('*')), static_table!( " N column 0 column 1 column 2 " " 0 a longer string 0-1 0-2 " " 1 1-0 1-1 1-2 " " 2 2-0 2-1 * 2-2 " ) } test_border! { Modify::new((3, 2)).with(Border::new().top(' ').right(' ').corner_top_right('*')), static_table!( " N column 0 column 1 column 2 " " 0 a longer string 0-1 0-2 " " 1 1-0 1-1 1-2 " " * " " 2 2-0 2-1 2-2 " ) } test_border! { Modify::new((3, 2)).with(Border::new().top(' ').right('#').corner_top_right('*')), static_table!( " N column 0 column 1 column 2 " " 0 a longer string 0-1 0-2 " " 1 1-0 1-1 1-2 " " * " " 2 2-0 2-1 # 2-2 " ) } test_border! { Modify::new((3, 2)).with(Border::new().top(' ').bottom(' ').right('#').corner_top_right('*').corner_bottom_right('@')), static_table!( " N column 0 column 1 column 2 " " 0 a longer string 0-1 0-2 " " 1 1-0 1-1 1-2 " " * " " 2 2-0 2-1 # 2-2 " " @ " ) } test_border! { Modify::new((3, 2)).with(Border::new().top(' ').bottom(' ').left(' ').right('#').corner_top_right('*').corner_bottom_left('@')), static_table!( " N column 0 column 1 column 2 " " 0 a longer string 0-1 0-2 " " 1 1-0 1-1 1-2 " " * " " 2 2-0 2-1 # 2-2 " " @ " ) } test_border! { Modify::new((3, 2)).with(Border::filled('@')), static_table!( " N column 0 column 1 column 2 " " 0 a longer string 0-1 0-2 " " 1 1-0 1-1 1-2 " " @@@@@@@@@@@@ " " 2 2-0 @ 2-1 @ 2-2 " " @@@@@@@@@@@@ " ) } test_border! { Modify::new((1, 2)).with(Border::new().left(' ').bottom(' ').corner_bottom_left('*')), static_table!( " N column 0 column 1 column 2 " " 0 a longer string 0-1 0-2 " " * " " 1 1-0 1-1 1-2 " " 2 2-0 2-1 2-2 " ) } test_border! { Modify::new((1, 2)).with(Border::new().right(' ').bottom(' ').corner_bottom_right('*')), static_table!( " N column 0 column 1 column 2 " " 0 a longer string 0-1 0-2 " " * " " 1 1-0 1-1 1-2 " " 2 2-0 2-1 2-2 " ) } test_border! { Modify::new((1, 2)).with(Border::new().bottom('*')), static_table!( " N column 0 column 1 column 2 " " 0 a longer string 0-1 0-2 " " ********** " " 1 1-0 1-1 1-2 " " 2 2-0 2-1 2-2 " ) } test_border! { Modify::new((1, 2)).with(Border::new().left(' ').bottom('*').corner_bottom_left('#')), static_table!( " N column 0 column 1 column 2 " " 0 a longer string 0-1 0-2 " " #********** " " 1 1-0 1-1 1-2 " " 2 2-0 2-1 2-2 " ) } test_border! { Modify::new((1, 2)).with(Border::new().right(' ').bottom('*').corner_bottom_right('#')), static_table!( " N column 0 column 1 column 2 " " 0 a longer string 0-1 0-2 " " **********# " " 1 1-0 1-1 1-2 " " 2 2-0 2-1 2-2 " ) } test_border! { Modify::new((1, 2)).with(Border::new().left('*')), static_table!( " N column 0 column 1 column 2 " " 0 a longer string * 0-1 0-2 " " 1 1-0 1-1 1-2 " " 2 2-0 2-1 2-2 " ) } test_border! { Modify::new((1, 2)).with(Border::new().top(' ').left(' ').corner_top_left('*')), static_table!( " N column 0 column 1 column 2 " " * " " 0 a longer string 0-1 0-2 " " 1 1-0 1-1 1-2 " " 2 2-0 2-1 2-2 " ) } test_border! { Modify::new((1, 2)).with(Border::new().left('#').top(' ').corner_top_left('*')), static_table!( " N column 0 column 1 column 2 " " * " " 0 a longer string # 0-1 0-2 " " 1 1-0 1-1 1-2 " " 2 2-0 2-1 2-2 " ) } test_border! { Modify::new((1, 2)).with(Border::new().top(' ').bottom(' ').left('#').corner_bottom_left('@').corner_top_left('*')), static_table!( " N column 0 column 1 column 2 " " * " " 0 a longer string # 0-1 0-2 " " @ " " 1 1-0 1-1 1-2 " " 2 2-0 2-1 2-2 " ) } test_border! { Modify::new((1, 2)).with(Border::new().right('*')), static_table!( " N column 0 column 1 column 2 " " 0 a longer string 0-1 * 0-2 " " 1 1-0 1-1 1-2 " " 2 2-0 2-1 2-2 " ) } test_border! { Modify::new((1, 2)).with(Border::new().top(' ').right(' ').corner_top_right('*')), static_table!( " N column 0 column 1 column 2 " " * " " 0 a longer string 0-1 0-2 " " 1 1-0 1-1 1-2 " " 2 2-0 2-1 2-2 " ) } test_border! { Modify::new((1, 2)).with(Border::new().top(' ').right('#').corner_top_right('*')), static_table!( " N column 0 column 1 column 2 " " * " " 0 a longer string 0-1 # 0-2 " " 1 1-0 1-1 1-2 " " 2 2-0 2-1 2-2 " ) } test_border! { Modify::new((1, 2)).with(Border::new().top(' ').bottom(' ').right('#').corner_top_right('*').corner_bottom_right('@')), static_table!( " N column 0 column 1 column 2 " " * " " 0 a longer string 0-1 # 0-2 " " @ " " 1 1-0 1-1 1-2 " " 2 2-0 2-1 2-2 " ) } test_border! { Modify::new((1, 2)).with(Border::new().top(' ').bottom(' ').left(' ').right('#').corner_top_right('*').corner_bottom_left('@')), static_table!( " N column 0 column 1 column 2 " " * " " 0 a longer string 0-1 # 0-2 " " @ " " 1 1-0 1-1 1-2 " " 2 2-0 2-1 2-2 " ) } test_border! { Modify::new((1, 2)).with(Border::filled('@')), static_table!( " N column 0 column 1 column 2 " " @@@@@@@@@@@@ " " 0 a longer string @ 0-1 @ 0-2 " " @@@@@@@@@@@@ " " 1 1-0 1-1 1-2 " " 2 2-0 2-1 2-2 " ) } test_border! { Modify::new((0, 3)).with(Border::new().left(' ').bottom(' ').corner_bottom_left('*')), static_table!( " N column 0 column 1 column 2 " " * " " 0 a longer string 0-1 0-2 " " 1 1-0 1-1 1-2 " " 2 2-0 2-1 2-2 " ) } test_border! { Modify::new((0, 3)).with(Border::new().right(' ').bottom(' ').corner_bottom_right('*')), static_table!( " N column 0 column 1 column 2 " " *" " 0 a longer string 0-1 0-2 " " 1 1-0 1-1 1-2 " " 2 2-0 2-1 2-2 " ) } test_border! { Modify::new((0, 3)).with(Border::new().bottom('*')), static_table!( " N column 0 column 1 column 2 " " **********" " 0 a longer string 0-1 0-2 " " 1 1-0 1-1 1-2 " " 2 2-0 2-1 2-2 " ) } test_border! { Modify::new((0, 3)).with(Border::new().left(' ').bottom('*').corner_bottom_left('#')), static_table!( " N column 0 column 1 column 2 " " #**********" " 0 a longer string 0-1 0-2 " " 1 1-0 1-1 1-2 " " 2 2-0 2-1 2-2 " ) } test_border! { Modify::new((0, 3)).with(Border::new().right(' ').bottom('*').corner_bottom_right('#')), static_table!( " N column 0 column 1 column 2 " " **********#" " 0 a longer string 0-1 0-2 " " 1 1-0 1-1 1-2 " " 2 2-0 2-1 2-2 " ) } test_border! { Modify::new((0, 3)).with(Border::new().left('*')), static_table!( " N column 0 column 1 * column 2 " " 0 a longer string 0-1 0-2 " " 1 1-0 1-1 1-2 " " 2 2-0 2-1 2-2 " ) } test_border! { Modify::new((0, 3)).with(Border::new().top(' ').left(' ').corner_top_left('*')), static_table!( " * " " N column 0 column 1 column 2 " " 0 a longer string 0-1 0-2 " " 1 1-0 1-1 1-2 " " 2 2-0 2-1 2-2 " ) } test_border! { Modify::new((0, 3)).with(Border::new().top(' ').left('#').corner_top_left('*')), static_table!( " * " " N column 0 column 1 # column 2 " " 0 a longer string 0-1 0-2 " " 1 1-0 1-1 1-2 " " 2 2-0 2-1 2-2 " ) } test_border! { Modify::new((0, 3)).with(Border::new().top(' ').bottom(' ').left('#').corner_bottom_left('@').corner_top_left('*')), static_table!( " * " " N column 0 column 1 # column 2 " " @ " " 0 a longer string 0-1 0-2 " " 1 1-0 1-1 1-2 " " 2 2-0 2-1 2-2 " ) } test_border! { Modify::new((0, 3)).with(Border::new().right('*')), static_table!( " N column 0 column 1 column 2 *" " 0 a longer string 0-1 0-2 " " 1 1-0 1-1 1-2 " " 2 2-0 2-1 2-2 " ) } test_border! { Modify::new((0, 3)).with(Border::new().top(' ').right(' ').corner_top_right('*')), static_table!( " *" " N column 0 column 1 column 2 " " 0 a longer string 0-1 0-2 " " 1 1-0 1-1 1-2 " " 2 2-0 2-1 2-2 " ) } test_border! { Modify::new((0, 3)).with(Border::new().top(' ').right('#').corner_top_right('*')), static_table!( " *" " N column 0 column 1 column 2 #" " 0 a longer string 0-1 0-2 " " 1 1-0 1-1 1-2 " " 2 2-0 2-1 2-2 " ) } test_border! { Modify::new((0, 3)).with(Border::new().top(' ').bottom(' ').right('#').corner_top_right('*').corner_bottom_right('@')), static_table!( " *" " N column 0 column 1 column 2 #" " @" " 0 a longer string 0-1 0-2 " " 1 1-0 1-1 1-2 " " 2 2-0 2-1 2-2 " ) } test_border! { Modify::new((0, 3)).with(Border::new().top(' ').bottom(' ').left(' ').right('#').corner_top_right('*').corner_bottom_left('@')), static_table!( " *" " N column 0 column 1 column 2 #" " @ " " 0 a longer string 0-1 0-2 " " 1 1-0 1-1 1-2 " " 2 2-0 2-1 2-2 " ) } test_border! { Modify::new((0, 3)).with(Border::filled('@')), static_table!( " @@@@@@@@@@@@" " N column 0 column 1 @ column 2 @" " @@@@@@@@@@@@" " 0 a longer string 0-1 0-2 " " 1 1-0 1-1 1-2 " " 2 2-0 2-1 2-2 " ) } } #[cfg(feature = "ansi")] #[test] fn border_colored_test() { let table = Matrix::table(2, 2) .with(Style::ascii()) .modify( Rows::single(1), BorderColor::filled(Color::FG_BLUE).top(Color::rgb_fg(12, 220, 100)), ) .modify(Rows::single(1), Border::filled('*').top('#')) .to_string(); assert_eq!( ansi_str::AnsiStr::ansi_strip(&table), static_table!( "+---+----------+----------+" "| N | column 0 | column 1 |" "*###*##########*##########*" "* 0 * 0-0 * 0-1 *" "***************************" "| 1 | 1-0 | 1-1 |" "+---+----------+----------+" ) ); assert_eq!( table, static_table!( "+---+----------+----------+" "| N | column 0 | column 1 |" "\u{1b}[34m*\u{1b}[39m\u{1b}[38;2;12;220;100m###\u{1b}[39m\u{1b}[34m*\u{1b}[39m\u{1b}[38;2;12;220;100m##########\u{1b}[39m\u{1b}[34m*\u{1b}[39m\u{1b}[38;2;12;220;100m##########\u{1b}[39m\u{1b}[34m*\u{1b}[39m" "\u{1b}[34m*\u{1b}[39m 0 \u{1b}[34m*\u{1b}[39m 0-0 \u{1b}[34m*\u{1b}[39m 0-1 \u{1b}[34m*\u{1b}[39m" "\u{1b}[34m***************************\u{1b}[39m" "| 1 | 1-0 | 1-1 |" "+---+----------+----------+" ) ); let table = Matrix::table(2, 2) .with(Style::empty()) .modify( Rows::single(1), BorderColor::filled(Color::FG_BLUE).top(Color::rgb_fg(12, 220, 100)), ) .modify(Rows::single(1), Border::filled('*').top('#')) .to_string(); assert_eq!( ansi_str::AnsiStr::ansi_strip(&table), static_table!( " N column 0 column 1 " "*###*##########*##########*" "* 0 * 0-0 * 0-1 *" "***************************" " 1 1-0 1-1 " ) ); assert_eq!( table, " N column 0 column 1 \n\u{1b}[34m*\u{1b}[39m\u{1b}[38;2;12;220;100m###\u{1b}[39m\u{1b}[34m*\u{1b}[39m\u{1b}[38;2;12;220;100m##########\u{1b}[39m\u{1b}[34m*\u{1b}[39m\u{1b}[38;2;12;220;100m##########\u{1b}[39m\u{1b}[34m*\u{1b}[39m\n\u{1b}[34m*\u{1b}[39m 0 \u{1b}[34m*\u{1b}[39m 0-0 \u{1b}[34m*\u{1b}[39m 0-1 \u{1b}[34m*\u{1b}[39m\n\u{1b}[34m***************************\u{1b}[39m\n 1 1-0 1-1 ", ); } #[cfg(feature = "ansi")] #[test] fn style_with_color_test() { let mut style = Theme::from_style(Style::ascii()); style.set_borders_left('['); style.set_borders_right(']'); style.set_borders_top('-'); style.set_borders_bottom('-'); style.set_borders_vertical('|'); style.set_borders_intersection('+'); style.set_colors_left(Color::FG_RED); style.set_colors_right(Color::FG_RED); style.set_colors_top(Color::FG_BLUE); style.set_colors_bottom(Color::FG_BLUE); style.set_colors_vertical(Color::FG_YELLOW); style.set_colors_intersection(Color::FG_MAGENTA); let table = Matrix::new(3, 3).with(style).to_string(); assert_eq!( ansi_str::AnsiStr::ansi_strip(&table), static_table!( "+---+----------+----------+----------+" "[ N | column 0 | column 1 | column 2 ]" "+---+----------+----------+----------+" "[ 0 | 0-0 | 0-1 | 0-2 ]" "+---+----------+----------+----------+" "[ 1 | 1-0 | 1-1 | 1-2 ]" "+---+----------+----------+----------+" "[ 2 | 2-0 | 2-1 | 2-2 ]" "+---+----------+----------+----------+" ) ); assert_eq!(table, "+\u{1b}[34m---\u{1b}[39m+\u{1b}[34m----------\u{1b}[39m+\u{1b}[34m----------\u{1b}[39m+\u{1b}[34m----------\u{1b}[39m+\n\u{1b}[31m[\u{1b}[39m N \u{1b}[33m|\u{1b}[39m column 0 \u{1b}[33m|\u{1b}[39m column 1 \u{1b}[33m|\u{1b}[39m column 2 \u{1b}[31m]\u{1b}[39m\n+---\u{1b}[35m+\u{1b}[39m----------\u{1b}[35m+\u{1b}[39m----------\u{1b}[35m+\u{1b}[39m----------+\n\u{1b}[31m[\u{1b}[39m 0 \u{1b}[33m|\u{1b}[39m 0-0 \u{1b}[33m|\u{1b}[39m 0-1 \u{1b}[33m|\u{1b}[39m 0-2 \u{1b}[31m]\u{1b}[39m\n+---\u{1b}[35m+\u{1b}[39m----------\u{1b}[35m+\u{1b}[39m----------\u{1b}[35m+\u{1b}[39m----------+\n\u{1b}[31m[\u{1b}[39m 1 \u{1b}[33m|\u{1b}[39m 1-0 \u{1b}[33m|\u{1b}[39m 1-1 \u{1b}[33m|\u{1b}[39m 1-2 \u{1b}[31m]\u{1b}[39m\n+---\u{1b}[35m+\u{1b}[39m----------\u{1b}[35m+\u{1b}[39m----------\u{1b}[35m+\u{1b}[39m----------+\n\u{1b}[31m[\u{1b}[39m 2 \u{1b}[33m|\u{1b}[39m 2-0 \u{1b}[33m|\u{1b}[39m 2-1 \u{1b}[33m|\u{1b}[39m 2-2 \u{1b}[31m]\u{1b}[39m\n+\u{1b}[34m---\u{1b}[39m+\u{1b}[34m----------\u{1b}[39m+\u{1b}[34m----------\u{1b}[39m+\u{1b}[34m----------\u{1b}[39m+"); } test_table!( empty_line_clears_lines, Matrix::new(3, 3).with(Style::rounded().remove_horizontals()), "╭───┬──────────┬──────────┬──────────╮" "│ N │ column 0 │ column 1 │ column 2 │" "│ 0 │ 0-0 │ 0-1 │ 0-2 │" "│ 1 │ 1-0 │ 1-1 │ 1-2 │" "│ 2 │ 2-0 │ 2-1 │ 2-2 │" "╰───┴──────────┴──────────┴──────────╯" ); test_table!( empty_line_clears_lines_1, Matrix::new(3, 3).with(Style::rounded().remove_horizontals()), "╭───┬──────────┬──────────┬──────────╮" "│ N │ column 0 │ column 1 │ column 2 │" "│ 0 │ 0-0 │ 0-1 │ 0-2 │" "│ 1 │ 1-0 │ 1-1 │ 1-2 │" "│ 2 │ 2-0 │ 2-1 │ 2-2 │" "╰───┴──────────┴──────────┴──────────╯" ); test_table!( border_color, { use tabled::settings::Color; Matrix::new(3, 3).with(Style::psql()).with(Color::BG_GREEN) }, " \u{1b}[42mN\u{1b}[49m | \u{1b}[42mcolumn 0\u{1b}[49m | \u{1b}[42mcolumn 1\u{1b}[49m | \u{1b}[42mcolumn 2\u{1b}[49m \n---+----------+----------+----------\n \u{1b}[42m0\u{1b}[49m | \u{1b}[42m0-0\u{1b}[49m | \u{1b}[42m0-1\u{1b}[49m | \u{1b}[42m0-2\u{1b}[49m \n \u{1b}[42m1\u{1b}[49m | \u{1b}[42m1-0\u{1b}[49m | \u{1b}[42m1-1\u{1b}[49m | \u{1b}[42m1-2\u{1b}[49m \n \u{1b}[42m2\u{1b}[49m | \u{1b}[42m2-0\u{1b}[49m | \u{1b}[42m2-1\u{1b}[49m | \u{1b}[42m2-2\u{1b}[49m " ); test_table!( text_color, { use tabled::settings::Color; Matrix::new(3, 3).with(Style::psql()).with(Modify::new(Segment::all()).with(Color::BG_BLACK)) }, " \u{1b}[40mN\u{1b}[49m | \u{1b}[40mcolumn 0\u{1b}[49m | \u{1b}[40mcolumn 1\u{1b}[49m | \u{1b}[40mcolumn 2\u{1b}[49m \n---+----------+----------+----------\n \u{1b}[40m0\u{1b}[49m | \u{1b}[40m0-0\u{1b}[49m | \u{1b}[40m0-1\u{1b}[49m | \u{1b}[40m0-2\u{1b}[49m \n \u{1b}[40m1\u{1b}[49m | \u{1b}[40m1-0\u{1b}[49m | \u{1b}[40m1-1\u{1b}[49m | \u{1b}[40m1-2\u{1b}[49m \n \u{1b}[40m2\u{1b}[49m | \u{1b}[40m2-0\u{1b}[49m | \u{1b}[40m2-1\u{1b}[49m | \u{1b}[40m2-2\u{1b}[49m " ); test_table!( verticals_0, Matrix::new(3, 3) .with(Style::rounded().verticals([(0, VerticalLine::filled('+').remove_intersection()), (4, VerticalLine::filled('+').remove_intersection())])), "+───┬──────────┬──────────┬──────────+" "+ N │ column 0 │ column 1 │ column 2 +" "├───┼──────────┼──────────┼──────────┤" "+ 0 │ 0-0 │ 0-1 │ 0-2 +" "+ 1 │ 1-0 │ 1-1 │ 1-2 +" "+ 2 │ 2-0 │ 2-1 │ 2-2 +" "+───┴──────────┴──────────┴──────────+" ); test_table!( verticals_1, { let verticals = (1..4).map(|i| (i, VerticalLine::filled('+').into())).collect(); let mut style = Theme::from_style(Style::rounded()); style.set_vertical_lines(verticals); Matrix::new(3, 3).with(style) }, "╭───+──────────+──────────+──────────╮" "│ N + column 0 + column 1 + column 2 │" "├───┼──────────┼──────────┼──────────┤" "│ 0 + 0-0 + 0-1 + 0-2 │" "│ 1 + 1-0 + 1-1 + 1-2 │" "│ 2 + 2-0 + 2-1 + 2-2 │" "╰───+──────────+──────────+──────────╯" ); test_table!( verticals_2, Matrix::new(3, 3).with(Style::rounded().verticals([(1, VerticalLine::filled('+').remove_intersection())])), "╭───+──────────┬──────────┬──────────╮" "│ N + column 0 │ column 1 │ column 2 │" "├───┼──────────┼──────────┼──────────┤" "│ 0 + 0-0 │ 0-1 │ 0-2 │" "│ 1 + 1-0 │ 1-1 │ 1-2 │" "│ 2 + 2-0 │ 2-1 │ 2-2 │" "╰───+──────────┴──────────┴──────────╯" ); test_table!( verticals_3, Matrix::new(3, 3).with(Style::ascii().verticals([(1, VerticalLine::filled('*'))])), "+---*----------+----------+----------+" "| N * column 0 | column 1 | column 2 |" "+---*----------+----------+----------+" "| 0 * 0-0 | 0-1 | 0-2 |" "+---*----------+----------+----------+" "| 1 * 1-0 | 1-1 | 1-2 |" "+---*----------+----------+----------+" "| 2 * 2-0 | 2-1 | 2-2 |" "+---*----------+----------+----------+" ); test_table!( verticals_4, { let mut style = Theme::from_style(Style::ascii()); let verticals = (0..10).map(|i| (i, VerticalLine::full('*', 'x', 'c', '2').into())).collect(); style.set_vertical_lines(verticals); Matrix::new(3, 3).with(style) }, "c---c----------c----------c----------c" "* N * column 0 * column 1 * column 2 *" "x---x----------x----------x----------x" "* 0 * 0-0 * 0-1 * 0-2 *" "x---x----------x----------x----------x" "* 1 * 1-0 * 1-1 * 1-2 *" "x---x----------x----------x----------x" "* 2 * 2-0 * 2-1 * 2-2 *" "2---2----------2----------2----------2" ); test_table!( vertical_line_0, { let m = Matrix::new(3, 3); let mut style = Theme::from_style(Style::ascii()); style.insert_horizontal_line(1, HorizontalLine::full('8', '8', '8', '8')); style.insert_vertical_line(1, VerticalLine::full('*', 'x', 'c', '2')); m.with(style) }, "+---c----------+----------+----------+" "| N * column 0 | column 1 | column 2 |" "88888888888888888888888888888888888888" "| 0 * 0-0 | 0-1 | 0-2 |" "+---x----------+----------+----------+" "| 1 * 1-0 | 1-1 | 1-2 |" "+---x----------+----------+----------+" "| 2 * 2-0 | 2-1 | 2-2 |" "+---2----------+----------+----------+" ); test_table!( vertical_line_1, Matrix::new(3, 3) .with(Style::empty().verticals([(1, VerticalLine::new('*'))])), " N * column 0 column 1 column 2 " " 0 * 0-0 0-1 0-2 " " 1 * 1-0 1-1 1-2 " " 2 * 2-0 2-1 2-2 " ); test_table!( override_horizontal_border_on_line, Matrix::new(3, 3) .with(Style::markdown()) .with(Modify::new(Rows::single(1)) .with(LineChar::horizontal(':', Offset::Begin(0))) .with(LineChar::horizontal(':', Offset::End(0))) ), "| N | column 0 | column 1 | column 2 |" "|:-:|:--------:|:--------:|:--------:|" "| 0 | 0-0 | 0-1 | 0-2 |" "| 1 | 1-0 | 1-1 | 1-2 |" "| 2 | 2-0 | 2-1 | 2-2 |" ); test_table!( override_horizontal_border_on_borders, Matrix::new(3, 3) .with(Modify::new(Rows::new(..5)) .with(LineChar::horizontal(':', Offset::Begin(0))) .with(LineChar::horizontal('y', Offset::Begin(3))) .with(LineChar::horizontal(':', Offset::End(0))) .with(LineChar::horizontal('x', Offset::End(3))) ), "+:-:+:--y--x--:+:--y--x--:+:--y--x--:+" "| N | column 0 | column 1 | column 2 |" "+:-:+:--y--x--:+:--y--x--:+:--y--x--:+" "| 0 | 0-0 | 0-1 | 0-2 |" "+:-:+:--y--x--:+:--y--x--:+:--y--x--:+" "| 1 | 1-0 | 1-1 | 1-2 |" "+:-:+:--y--x--:+:--y--x--:+:--y--x--:+" "| 2 | 2-0 | 2-1 | 2-2 |" "+:-:+:--y--x--:+:--y--x--:+:--y--x--:+" ); test_table!( override_horizontal_border_on_border, Matrix::new(3, 3) .with(Modify::new(Rows::new(..5)) .with(Border::filled('[')) .with(LineChar::horizontal(':', Offset::Begin(0))) .with(LineChar::horizontal('y', Offset::Begin(3))) .with(LineChar::horizontal(':', Offset::End(0))) .with(LineChar::horizontal('x', Offset::End(3))) ), "[:[:[:[[y[[x[[:[:[[y[[x[[:[:[[y[[x[[:[" "[ N [ column 0 [ column 1 [ column 2 [" "[:[:[:[[y[[x[[:[:[[y[[x[[:[:[[y[[x[[:[" "[ 0 [ 0-0 [ 0-1 [ 0-2 [" "[:[:[:[[y[[x[[:[:[[y[[x[[:[:[[y[[x[[:[" "[ 1 [ 1-0 [ 1-1 [ 1-2 [" "[:[:[:[[y[[x[[:[:[[y[[x[[:[:[[y[[x[[:[" "[ 2 [ 2-0 [ 2-1 [ 2-2 [" "[:[:[:[[y[[x[[:[:[[y[[x[[:[:[[y[[x[[:[" ); test_table!( override_vertical_border_on_line, Matrix::new(3, 3) .with(Style::markdown()) .with(Modify::new(Columns::single(1)) .with(LineChar::vertical(':', Offset::Begin(0))) ), "| N : column 0 | column 1 | column 2 |" "|---|----------|----------|----------|" "| 0 : 0-0 | 0-1 | 0-2 |" "| 1 : 1-0 | 1-1 | 1-2 |" "| 2 : 2-0 | 2-1 | 2-2 |" ); test_table!( override_vertical_border_on_line_1, Matrix::new(3, 3) .with(Style::markdown()) .with(Modify::new(Columns::single(1)) .with(LineChar::vertical(':', Offset::End(0))) ), "| N : column 0 | column 1 | column 2 |" "|---|----------|----------|----------|" "| 0 : 0-0 | 0-1 | 0-2 |" "| 1 : 1-0 | 1-1 | 1-2 |" "| 2 : 2-0 | 2-1 | 2-2 |" ); test_table!( override_vertical_border_on_line_multiline, Matrix::new(3, 3) .with(Modify::new(Rows::single(1)).with(Format::content(|s| format!("\nsome text\ntext\n{s}\ntext\ntext\n")))) .with(Style::markdown()) .with(Modify::new(Columns::single(1)) .with(LineChar::vertical(':', Offset::Begin(4))) ), "| N | column 0 | column 1 | column 2 |" "|-----------|-----------|-----------|-----------|" "| | | | |" "| some text | some text | some text | some text |" "| text | text | text | text |" "| 0 | 0-0 | 0-1 | 0-2 |" "| text : text | text | text |" "| text | text | text | text |" "| | | | |" "| 1 | 1-0 | 1-1 | 1-2 |" "| 2 | 2-0 | 2-1 | 2-2 |" ); test_table!( override_vertical_border_on_line_multiline_2, Matrix::new(3, 3) .with(Modify::new(Rows::single(1)).with(Format::content(|s| format!("\nsome text\ntext\n{s}\ntext\ntext\n")))) .with(Style::markdown()) .with(Modify::new(Columns::single(1)) .with(LineChar::vertical(':', Offset::End(4))) ), "| N | column 0 | column 1 | column 2 |" "|-----------|-----------|-----------|-----------|" "| | | | |" "| some text | some text | some text | some text |" "| text : text | text | text |" "| 0 | 0-0 | 0-1 | 0-2 |" "| text | text | text | text |" "| text | text | text | text |" "| | | | |" "| 1 | 1-0 | 1-1 | 1-2 |" "| 2 | 2-0 | 2-1 | 2-2 |" ); test_table!( override_vertical, Matrix::new(3, 3) .with(Style::markdown()) .with(Padding::new(1, 1, 1, 1)) .modify(Columns::single(1), LineChar::vertical(':', Offset::Begin(0))) .modify(Columns::single(1), LineChar::vertical(':', Offset::End(0))), "| : | | |" "| N | column 0 | column 1 | column 2 |" "| : | | |" "|---|----------|----------|----------|" "| : | | |" "| 0 | 0-0 | 0-1 | 0-2 |" "| : | | |" "| : | | |" "| 1 | 1-0 | 1-1 | 1-2 |" "| : | | |" "| : | | |" "| 2 | 2-0 | 2-1 | 2-2 |" "| : | | |" ); test_table!( table_format_alignment_left_test, format!("{:<}", Table::new(vec!["hello", "world", "!"])), "+-------+" "| &str |" "+-------+" "| hello |" "+-------+" "| world |" "+-------+" "| ! |" "+-------+" ); test_table!( table_format_alignment_right_test, format!("{:>}", Table::new(vec!["hello", "world", "!"])), "+-------+" "| &str |" "+-------+" "| hello |" "+-------+" "| world |" "+-------+" "| ! |" "+-------+" ); test_table!( table_format_alignment_center_test, format!("{:^}", Table::new(vec!["hello", "world", "!"])), "+-------+" "| &str |" "+-------+" "| hello |" "+-------+" "| world |" "+-------+" "| ! |" "+-------+" ); test_table!( table_format_width_0_test, format!("{:<13}", Table::new(vec!["hello", "world", "!"])), " +-------+" " | &str |" " +-------+" " | hello |" " +-------+" " | world |" " +-------+" " | ! |" " +-------+" ); test_table!( table_format_width_1_test, format!("{:>13}", Table::new(vec!["hello", "world", "!"])), "+-------+ " "| &str | " "+-------+ " "| hello | " "+-------+ " "| world | " "+-------+ " "| ! | " "+-------+ " ); test_table!( table_format_width_2_test, format!("{:^13}", Table::new(vec!["hello", "world", "!"])), " +-------+ " " | &str | " " +-------+ " " | hello | " " +-------+ " " | world | " " +-------+ " " | ! | " " +-------+ " ); test_table!( table_format_width_3_test, format!("{:x^13}", Table::new(vec!["hello", "world", "!"])), "xx+-------+xx" "xx| &str |xx" "xx+-------+xx" "xx| hello |xx" "xx+-------+xx" "xx| world |xx" "xx+-------+xx" "xx| ! |xx" "xx+-------+xx" ); test_table!( table_format_width_4_test, format!("{:x<13}", Table::new(vec!["hello", "world", "!"])), "xxxx+-------+" "xxxx| &str |" "xxxx+-------+" "xxxx| hello |" "xxxx+-------+" "xxxx| world |" "xxxx+-------+" "xxxx| ! |" "xxxx+-------+" ); test_table!( table_format_width_5_test, format!("{:x>13}", Table::new(vec!["hello", "world", "!"])), "+-------+xxxx" "| &str |xxxx" "+-------+xxxx" "| hello |xxxx" "+-------+xxxx" "| world |xxxx" "+-------+xxxx" "| ! |xxxx" "+-------+xxxx" ); test_table!( table_style_no_bottom_no_new_line, Matrix::table(0, 0).with(Style::markdown().remove_horizontals()), "| N |" ); test_table!( style_const_modification, { const STYLE: Style = Style::ascii() .bottom('x') .horizontals([(1, HorizontalLine::filled('.'))]); Matrix::new(3, 3).with(STYLE) }, "+---+----------+----------+----------+" "| N | column 0 | column 1 | column 2 |" "......................................" "| 0 | 0-0 | 0-1 | 0-2 |" "+---+----------+----------+----------+" "| 1 | 1-0 | 1-1 | 1-2 |" "+---+----------+----------+----------+" "| 2 | 2-0 | 2-1 | 2-2 |" "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" ); test_table!( style_static_modification, { static STYLE: Style = Style::ascii() .bottom('x') .horizontals([(1, HorizontalLine::filled('.'))]) .verticals([(1, VerticalLine::filled('|'))]); Matrix::new(3, 3).with(STYLE.clone()) }, "+---|----------+----------+----------+" "| N | column 0 | column 1 | column 2 |" "......................................" "| 0 | 0-0 | 0-1 | 0-2 |" "+---|----------+----------+----------+" "| 1 | 1-0 | 1-1 | 1-2 |" "+---|----------+----------+----------+" "| 2 | 2-0 | 2-1 | 2-2 |" "xxxx|xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" ); test_table!( line_text_vertical_0, Matrix::table(2, 2).with(LineText::new("-Table", Columns::first())), "----+----------+----------+" "T N | column 0 | column 1 |" "a---+----------+----------+" "b 0 | 0-0 | 0-1 |" "l---+----------+----------+" "e 1 | 1-0 | 1-1 |" "+---+----------+----------+" ); test_table!( line_text_vertical_1, Matrix::table(2, 2).with(LineText::new("-Tablex", Columns::last())), "+---+----------+-----------" "| N | column 0 | column 1 T" "+---+----------+----------a" "| 0 | 0-0 | 0-1 b" "+---+----------+----------l" "| 1 | 1-0 | 1-1 e" "+---+----------+----------x" ); test_table!( line_text_vertical_2, Matrix::table(2, 2).with(LineText::new("-Tablex", Columns::single(2))), "+---+---------------------+" "| N | column 0 T column 1 |" "+---+----------a----------+" "| 0 | 0-0 b 0-1 |" "+---+----------l----------+" "| 1 | 1-0 e 1-1 |" "+---+----------x----------+" ); test_table!( line_text_vertical_3, Matrix::table(2, 2).with(LineText::new("-Tablex", Columns::single(2)).offset(2)), "+---+----------+----------+" "| N | column 0 | column 1 |" "+---+---------------------+" "| 0 | 0-0 T 0-1 |" "+---+----------a----------+" "| 1 | 1-0 b 1-1 |" "+---+----------l----------+" ); test_table!( line_text_vertical_4, Matrix::table(2, 2) .with(Padding::new(0, 0, 2, 2)) .with(LineText::new("-Tablex", Columns::single(2)).offset(2).color(Color::BG_RED)), "+-+--------+--------+" "| | | |" "| | \u{1b}[41m-\u{1b}[49m |" "|N|column 0\u{1b}[41mT\u{1b}[49mcolumn 1|" "| | \u{1b}[41ma\u{1b}[49m |" "| | \u{1b}[41mb\u{1b}[49m |" "+-+--------\u{1b}[41ml\u{1b}[49m--------+" "| | \u{1b}[41me\u{1b}[49m |" "| | \u{1b}[41mx\u{1b}[49m |" "|0| 0-0 | 0-1 |" "| | | |" "| | | |" "+-+--------+--------+" "| | | |" "| | | |" "|1| 1-0 | 1-1 |" "| | | |" "| | | |" "+-+--------+--------+" ); test_table!( border_text_alignment_test_0, Matrix::table(2, 2).with(LineText::new("TABLE", Rows::first()).align(Alignment::center())), "+---+------TABLE----------+" "| N | column 0 | column 1 |" "+---+----------+----------+" "| 0 | 0-0 | 0-1 |" "+---+----------+----------+" "| 1 | 1-0 | 1-1 |" "+---+----------+----------+" ); test_table!( border_text_alignment_test_1, Matrix::table(2, 2).with(LineText::new("TABLE", Rows::first()).align(Alignment::right())), "+---+----------+------TABLE" "| N | column 0 | column 1 |" "+---+----------+----------+" "| 0 | 0-0 | 0-1 |" "+---+----------+----------+" "| 1 | 1-0 | 1-1 |" "+---+----------+----------+" ); test_table!( border_text_alignment_test_2, Matrix::table(2, 2).with(LineText::new("TABLE", Rows::first()).align(Alignment::left())), "TABLE----------+----------+" "| N | column 0 | column 1 |" "+---+----------+----------+" "| 0 | 0-0 | 0-1 |" "+---+----------+----------+" "| 1 | 1-0 | 1-1 |" "+---+----------+----------+" ); test_table!( border_text_alignment_test_3, Matrix::table(2, 2).with(LineText::new("TABLE", Rows::first()).align(Alignment::center()).offset(5)), "+---+----------+TABLE-----+" "| N | column 0 | column 1 |" "+---+----------+----------+" "| 0 | 0-0 | 0-1 |" "+---+----------+----------+" "| 1 | 1-0 | 1-1 |" "+---+----------+----------+" ); test_table!( border_text_alignment_test_4, Matrix::table(2, 2).with(LineText::new("TABLE", Rows::first()).align(Alignment::center()).offset(-5)), "+---+-TABLE----+----------+" "| N | column 0 | column 1 |" "+---+----------+----------+" "| 0 | 0-0 | 0-1 |" "+---+----------+----------+" "| 1 | 1-0 | 1-1 |" "+---+----------+----------+" ); tabled-0.18.0/tests/settings/theme_test.rs000064400000000000000000000025331046102023000166610ustar 00000000000000#![cfg(feature = "std")] use tabled::settings::{themes::Theme, Style}; use crate::matrix::Matrix; use testing_table::test_table; test_table!( theme_0, Matrix::new(3, 3).with(Theme::from_style(Style::modern())), "┌───┬──────────┬──────────┬──────────┐" "│ N │ column 0 │ column 1 │ column 2 │" "├───┼──────────┼──────────┼──────────┤" "│ 0 │ 0-0 │ 0-1 │ 0-2 │" "├───┼──────────┼──────────┼──────────┤" "│ 1 │ 1-0 │ 1-1 │ 1-2 │" "├───┼──────────┼──────────┼──────────┤" "│ 2 │ 2-0 │ 2-1 │ 2-2 │" "└───┴──────────┴──────────┴──────────┘" ); test_table!( theme_1, Matrix::new(3, 3).with(Theme::from_style(Style::markdown())), "| N | column 0 | column 1 | column 2 |" "|---|----------|----------|----------|" "| 0 | 0-0 | 0-1 | 0-2 |" "| 1 | 1-0 | 1-1 | 1-2 |" "| 2 | 2-0 | 2-1 | 2-2 |" ); tabled-0.18.0/tests/settings/width_test.rs000064400000000000000000002674051046102023000167110ustar 00000000000000#![cfg(feature = "std")] use tabled::{ grid::util::string::get_text_width, settings::{ formatting::{TabSize, TrimStrategy}, object::{Columns, Object, Rows, Segment}, peaker::{PriorityLeft, PriorityMax, PriorityMin, PriorityRight}, width::{Justify, MinWidth, SuffixLimit, Width}, Alignment, Margin, Modify, Padding, Panel, Settings, Span, Style, }, }; use crate::matrix::Matrix; use testing_table::{assert_width, static_table, test_table}; #[cfg(feature = "ansi")] use ::{ ansi_str::AnsiStr, tabled::settings::{style::HorizontalLine, Color}, }; test_table!( max_width, Matrix::new(3, 3) .with(Style::markdown()) .with(Modify::new(Columns::new(1..).not(Rows::single(0))).with(Width::truncate(1))), "| N | column 0 | column 1 | column 2 |" "|---|----------|----------|----------|" "| 0 | 0 | 0 | 0 |" "| 1 | 1 | 1 | 1 |" "| 2 | 2 | 2 | 2 |" ); test_table!( max_width_with_suffix, Matrix::new(3, 3) .with(Style::markdown()) .with( Modify::new(Columns::new(1..).not(Rows::single(0))) .with(Width::truncate(2).suffix("...")), ), "| N | column 0 | column 1 | column 2 |" "|---|----------|----------|----------|" "| 0 | .. | .. | .. |" "| 1 | .. | .. | .. |" "| 2 | .. | .. | .. |" ); test_table!( max_width_doesnt_increase_width_if_it_is_smaller, Matrix::new(3, 3) .with(Style::markdown()) .with(Modify::new(Columns::new(1..).not(Rows::single(0))).with(Width::truncate(50))), "| N | column 0 | column 1 | column 2 |" "|---|----------|----------|----------|" "| 0 | 0-0 | 0-1 | 0-2 |" "| 1 | 1-0 | 1-1 | 1-2 |" "| 2 | 2-0 | 2-1 | 2-2 |" ); test_table!( max_width_wrapped, Matrix::new(3, 3) .with(Style::markdown()) .with(Modify::new(Columns::new(1..).not(Rows::single(0))).with(Width::wrap(2))), "| N | column 0 | column 1 | column 2 |" "|---|----------|----------|----------|" "| 0 | 0- | 0- | 0- |" "| | 0 | 1 | 2 |" "| 1 | 1- | 1- | 1- |" "| | 0 | 1 | 2 |" "| 2 | 2- | 2- | 2- |" "| | 0 | 1 | 2 |" ); test_table!( max_width_wrapped_does_nothing_if_str_is_smaller, Matrix::new(3, 3) .with(Style::markdown()) .with(Modify::new(Columns::new(1..).not(Rows::single(0))).with(Width::wrap(100))), "| N | column 0 | column 1 | column 2 |" "|---|----------|----------|----------|" "| 0 | 0-0 | 0-1 | 0-2 |" "| 1 | 1-0 | 1-1 | 1-2 |" "| 2 | 2-0 | 2-1 | 2-2 |" ); test_table!( max_width_wrapped_keep_words_0, { let table = Matrix::iter(vec!["this is a long sentence"]) .with(Style::markdown()) .with(Modify::new(Segment::all()).with(Alignment::left())) .with(Modify::new(Segment::all()).with(Width::wrap(17).keep_words(true))) .to_string(); assert_width!(table, 17 + 2 + 2); table }, "| &str |" "|-------------------|" "| this is a long |" "| sentence |" ); test_table!( max_width_wrapped_keep_words_1, { let table = Matrix::iter(vec!["this is a long sentence"]) .with(Style::markdown()) .with(Modify::new(Segment::all()).with(Alignment::left())) .with(Modify::new(Segment::all()).with(Width::wrap(17).keep_words(true))) .to_string(); assert_width!(&table, 17 + 2 + 2); table }, "| &str |" "|-------------------|" "| this is a long |" "| sentence |" ); test_table!( max_width_wrapped_keep_words_2, { let table = Matrix::iter(vec!["this is a long sentence"]) .with(Style::markdown()) .with(Modify::new(Segment::all()).with(Alignment::left())) .with(Modify::new(Segment::all()).with(Width::wrap(17).keep_words(true))) .to_string(); assert_width!(&table, 17 + 2 + 2); table }, "| &str |" "|-------------------|" "| this is a long |" "| sentence |" ); #[cfg(feature = "ansi")] test_table!( max_width_wrapped_keep_words_3, { let table = Matrix::iter(vec!["this is a long sentence"]) .with(Style::markdown()) .with(Modify::new(Segment::all()).with(Alignment::left())) .with(Modify::new(Segment::all()).with(Width::wrap(17).keep_words(true))) .to_string(); assert_width!(&table, 17 + 2 + 2); table }, // 'sentence' doesn't have a space ' sentence' because we use left alignment "| &str |" "|-------------------|" "| this is a long |" "| sentence |" ); #[cfg(not(feature = "ansi"))] test_table!( max_width_wrapped_keep_words_3, { let table = Matrix::iter(vec!["this is a long sentence"]) .with(Style::markdown()) .with(Modify::new(Segment::all()).with(Alignment::left())) .with(Modify::new(Segment::all()).with(Width::wrap(17).keep_words(true))) .to_string(); assert_width!(&table, 17 + 2 + 2); table }, // 'sentence' doesn't have a space ' sentence' because we use left alignment "| &str |" "|-------------------|" "| this is a long |" "| sentence |" ); test_table!( max_width_wrapped_keep_words_4, { let table = Matrix::iter(vec!["this"]) .with(Style::markdown()) .with(Modify::new(Segment::all()).with(Width::wrap(10).keep_words(true))) .to_string(); assert_width!(&table, 8); table }, "| &str |" "|------|" "| this |" ); #[cfg(feature = "ansi")] test_table!( max_width_wrapped_keep_words_color_0, { let table = Matrix::iter(vec![(Color::BG_BLACK | Color::FG_GREEN).colorize("this is a long sentence")]) .with(Style::markdown()) .with(Modify::new(Segment::all()).with(Alignment::left())) .with(Modify::new(Segment::all()).with(Width::wrap(17).keep_words(true))) .to_string(); AnsiStr::ansi_strip(&table).to_string() }, "| String |" "|-------------------|" "| this is a long |" "| sentence |" ); #[cfg(feature = "ansi")] test_table!( max_width_wrapped_keep_words_color_0_1, Matrix::iter(vec![(Color::BG_BLACK | Color::FG_GREEN).colorize("this is a long sentence")]) .with(Style::markdown()) .with(Modify::new(Segment::all()).with(Alignment::left())) .with(Modify::new(Segment::all()).with(Width::wrap(17).keep_words(true))), "| String |" "|-------------------|" "| \u{1b}[32m\u{1b}[40mthis is a long \u{1b}[39m\u{1b}[49m |" "| \u{1b}[32m\u{1b}[40msentence\u{1b}[39m\u{1b}[49m |" ); #[cfg(feature = "ansi")] test_table!( max_width_wrapped_keep_words_color_1, { let table = Matrix::iter(vec![(Color::BG_BLACK | Color::FG_GREEN).colorize("this is a long sentence")]) .with(Style::markdown()) .with(Modify::new(Segment::all()).with(Alignment::left())) .with(Modify::new(Segment::all()).with(Width::wrap(17).keep_words(true))) .to_string(); AnsiStr::ansi_strip(&table).to_string() }, "| String |" "|-------------------|" "| this is a long |" "| sentence |" ); #[cfg(feature = "ansi")] test_table!( max_width_wrapped_keep_words_color_1_1, Matrix::iter(vec![(Color::BG_BLACK | Color::FG_GREEN).colorize("this is a long sentence")]) .with(Style::markdown()) .with(Modify::new(Segment::all()).with(Alignment::left())) .with(Modify::new(Segment::all()).with(Width::wrap(17).keep_words(true))), "| String |" "|-------------------|" "| \u{1b}[32m\u{1b}[40mthis is a long \u{1b}[39m\u{1b}[49m |" "| \u{1b}[32m\u{1b}[40msentence\u{1b}[39m\u{1b}[49m |" ); #[cfg(feature = "ansi")] test_table!( max_width_wrapped_keep_words_color_2, { let table = Matrix::iter(vec![(Color::BG_BLACK | Color::FG_GREEN).colorize("this is a long sentence")]) .with(Style::markdown()) .with(Modify::new(Segment::all()).with(Alignment::left())) .with(Modify::new(Segment::all()).with(Width::wrap(17).keep_words(true))) .to_string(); AnsiStr::ansi_strip(&table).to_string() }, "| String |" "|-------------------|" "| this is a long |" "| sentence |" ); #[cfg(feature = "ansi")] test_table!( max_width_wrapped_keep_words_color_2_1, Matrix::iter(vec![(Color::BG_BLACK | Color::FG_GREEN).colorize("this is a long sentence")]) .with(Style::markdown()) .with(Modify::new(Segment::all()).with(Alignment::left())) .with(Modify::new(Segment::all()).with(Width::wrap(17).keep_words(true))), "| String |" "|-------------------|" "| \u{1b}[32m\u{1b}[40mthis is a long \u{1b}[39m\u{1b}[49m |" "| \u{1b}[32m\u{1b}[40msentence\u{1b}[39m\u{1b}[49m |" ); #[cfg(feature = "ansi")] test_table!( max_width_wrapped_keep_words_color_3, { let table = Matrix::iter(vec![(Color::BG_BLACK | Color::FG_GREEN).colorize("this is a long sentence")]) .with(Style::markdown()) .with(Modify::new(Segment::all()).with(Alignment::left())) .with(Modify::new(Segment::all()).with(Width::wrap(17).keep_words(true))) .to_string(); AnsiStr::ansi_strip(&table).to_string() }, "| String |" "|-------------------|" "| this is a long |" "| sentence |" ); #[cfg(feature = "ansi")] test_table!( max_width_wrapped_keep_words_color_3_1, Matrix::iter(vec![(Color::BG_BLACK | Color::FG_GREEN).colorize("this is a long sentence")]) .with(Style::markdown()) .with(Modify::new(Segment::all()).with(Alignment::left())) .with(Modify::new(Segment::all()).with(Width::wrap(17).keep_words(true))), "| String |" "|-------------------|" "| \u{1b}[32m\u{1b}[40mthis is a long \u{1b}[39m\u{1b}[49m |" "| \u{1b}[32m\u{1b}[40m sentence\u{1b}[39m\u{1b}[49m |" ); #[cfg(feature = "ansi")] test_table!( max_width_wrapped_keep_words_color_4, { let table = Matrix::iter(vec![(Color::BG_BLACK | Color::FG_GREEN).colorize("this")]) .with(Style::markdown()) .with(Modify::new(Segment::all()).with(Width::wrap(10).keep_words(true))) .to_string(); AnsiStr::ansi_strip(&table).to_string() }, "| String |" "|--------|" "| this |" ); #[cfg(feature = "ansi")] test_table!( max_width_wrapped_keep_words_color_4_1, Matrix::iter(vec![(Color::BG_BLACK | Color::FG_GREEN).colorize("this")]) .with(Style::markdown()) .with(Modify::new(Segment::all()).with(Width::wrap(10).keep_words(true))), "| String |" "|--------|" "| \u{1b}[40m\u{1b}[32mthis\u{1b}[49m\u{1b}[39m |" ); test_table!( max_width_wrapped_keep_words_long_word, Matrix::iter(["this is a long sentencesentencesentence"]) .with(Style::markdown()) .with(Modify::new(Segment::all()).with(Alignment::left())) .with(Modify::new(Segment::all()).with(Width::wrap(17).keep_words(true))), "| &str |" "|-------------------|" "| this is a long se |" "| ntencesentencesen |" "| tence |" ); #[cfg(feature = "ansi")] #[test] fn max_width_wrapped_keep_words_long_word_color() { let data = vec![ (Color::BG_BLACK | Color::FG_GREEN).colorize("this is a long sentencesentencesentence") ]; let table = Matrix::iter(data) .with(Style::markdown()) .with(Modify::new(Segment::all()).with(Alignment::left())) .with(Modify::new(Segment::all()).with(Width::wrap(17).keep_words(true))) .to_string(); assert_eq!( ansi_str::AnsiStr::ansi_strip(&table), static_table!( "| String |" "|-------------------|" "| this is a long se |" "| ntencesentencesen |" "| tence |" ) ); assert_eq!( table, static_table!( "| String |" "|-------------------|" "| \u{1b}[32m\u{1b}[40mthis is a long se\u{1b}[39m\u{1b}[49m |" "| \u{1b}[32m\u{1b}[40mntencesentencesen\u{1b}[39m\u{1b}[49m |" "| \u{1b}[32m\u{1b}[40mtence\u{1b}[39m\u{1b}[49m |" ) ); } #[cfg(feature = "ansi")] #[test] fn max_width_keep_words_1() { let table = Matrix::iter(["asdf"]) .with(Width::wrap(7).keep_words(true)) .to_string(); assert_eq!( table, static_table!( "+-----+" "| &st |" "| r |" "+-----+" "| asd |" "| f |" "+-----+" ) ); let table = Matrix::iter(["qweqw eqwe"]) .with(Width::wrap(8).keep_words(true)) .to_string(); assert_eq!( table, static_table!( "+------+" "| &str |" "+------+" "| qweq |" "| w |" "| eqwe |" "+------+" ) ); let table = Matrix::iter([ ["123 45678", "qweqw eqwe", "..."], ["0", "1", "..."], ["0", "1", "..."], ]) .with( Style::modern() .remove_horizontal() .horizontals([(1, HorizontalLine::inherit(Style::modern()))]), ) .with( Width::wrap(21) .keep_words(true) .priority(PriorityMax::right()), ) .with(Alignment::center()) .to_string(); assert_eq!( table, static_table!( "┌──────┬──────┬─────┐" "│ 0 │ 1 │ 2 │" "├──────┼──────┼─────┤" "│ 123 │ qweq │ ... │" "│ 4567 │ w │ │" "│ 8 │ eqwe │ │" "│ 0 │ 1 │ ... │" "│ 0 │ 1 │ ... │" "└──────┴──────┴─────┘" ) ); } #[cfg(feature = "ansi")] #[test] fn max_width_wrapped_collored() { let data = &[ Color::FG_RED.colorize("asd"), Color::FG_BLUE.colorize("zxc2"), (Color::FG_GREEN | Color::BG_BLACK).colorize("asdasd"), ]; let table = Matrix::iter(data) .with(Style::markdown()) .with(Modify::new(Segment::all()).with(Width::wrap(2))) .to_string(); assert_eq!( table, "| St |\n| ri |\n| ng |\n|----|\n| \u{1b}[31mas\u{1b}[39m |\n| \u{1b}[31md\u{1b}[39m |\n| \u{1b}[34mzx\u{1b}[39m |\n| \u{1b}[34mc2\u{1b}[39m |\n| \u{1b}[32m\u{1b}[40mas\u{1b}[39m\u{1b}[49m |\n| \u{1b}[32m\u{1b}[40mda\u{1b}[39m\u{1b}[49m |\n| \u{1b}[32m\u{1b}[40msd\u{1b}[39m\u{1b}[49m |" ); } #[test] fn dont_change_content_if_width_is_less_then_max_width() { let table = Matrix::new(3, 3) .with(Style::markdown()) .with(Modify::new(Segment::all()).with(Width::truncate(1000).suffix("..."))) .to_string(); assert_eq!( table, static_table!( "| N | column 0 | column 1 | column 2 |" "|---|----------|----------|----------|" "| 0 | 0-0 | 0-1 | 0-2 |" "| 1 | 1-0 | 1-1 | 1-2 |" "| 2 | 2-0 | 2-1 | 2-2 |" ) ); } #[test] fn max_width_with_emoji() { let data = &["🤠", "😳🥵🥶😱😨", "🚴🏻‍♀️🚴🏻🚴🏻‍♂️🚵🏻‍♀️🚵🏻🚵🏻‍♂️"]; let table = Matrix::iter(data) .with(Style::markdown()) .with(Modify::new(Segment::all()).with(Width::truncate(6).suffix("..."))) .to_string(); assert_eq!( table, static_table!( "| &str |" "|--------|" "| 🤠 |" "| 😳�... |" "| 🚴�... |" ) ); } #[cfg(feature = "ansi")] #[test] fn color_chars_are_stripped() { let data = &[ Color::FG_RED.colorize("asd"), Color::FG_BLUE.colorize("zxc"), (Color::FG_GREEN | Color::BG_BLACK).colorize("asdasd"), ]; let table = Matrix::iter(data) .with(Style::markdown()) .with(Modify::new(Segment::all()).with(Width::truncate(4).suffix("..."))) .to_string(); assert_eq!( ansi_str::AnsiStr::ansi_strip(&table), static_table!( "| S... |" "|------|" "| asd |" "| zxc |" "| a... |" ) ); assert_eq!( table, "| S... |\n|------|\n| \u{1b}[31masd\u{1b}[39m |\n| \u{1b}[34mzxc\u{1b}[39m |\n| \u{1b}[32m\u{1b}[40ma\u{1b}[39m\u{1b}[49m... |", ); } #[test] fn min_width() { let mut table = Matrix::table(3, 3); table .with(Style::markdown()) .with(Modify::new(Rows::single(0)).with(MinWidth::new(12))); assert_eq!( table.to_string(), static_table!( "| N | column 0 | column 1 | column 2 |" "|--------------|--------------|--------------|--------------|" "| 0 | 0-0 | 0-1 | 0-2 |" "| 1 | 1-0 | 1-1 | 1-2 |" "| 2 | 2-0 | 2-1 | 2-2 |" ), ); table.with(Modify::new(Segment::all()).with(TrimStrategy::None)); assert_eq!( table.to_string(), static_table!( "| N | column 0 | column 1 | column 2 |" "|--------------|--------------|--------------|--------------|" "| 0 | 0-0 | 0-1 | 0-2 |" "| 1 | 1-0 | 1-1 | 1-2 |" "| 2 | 2-0 | 2-1 | 2-2 |" ), ); } #[test] fn min_width_with_filler() { let table = Matrix::new(3, 3) .with(Style::markdown()) .with(Modify::new(Rows::single(0)).with(MinWidth::new(12).fill_with('.'))) .to_string(); assert_eq!( table, static_table!( "| N........... | column 0.... | column 1.... | column 2.... |" "|--------------|--------------|--------------|--------------|" "| 0 | 0-0 | 0-1 | 0-2 |" "| 1 | 1-0 | 1-1 | 1-2 |" "| 2 | 2-0 | 2-1 | 2-2 |" ) ); } #[test] fn min_width_one_column() { let mut table = Matrix::table(3, 3); table .with(Style::markdown()) .with(Modify::new((0, 0)).with(MinWidth::new(5))); assert_eq!( table.to_string(), static_table!( "| N | column 0 | column 1 | column 2 |" "|-------|----------|----------|----------|" "| 0 | 0-0 | 0-1 | 0-2 |" "| 1 | 1-0 | 1-1 | 1-2 |" "| 2 | 2-0 | 2-1 | 2-2 |" ) ); table.with(Modify::new(Segment::all()).with(TrimStrategy::None)); assert_eq!( table.to_string(), static_table!( "| N | column 0 | column 1 | column 2 |" "|-------|----------|----------|----------|" "| 0 | 0-0 | 0-1 | 0-2 |" "| 1 | 1-0 | 1-1 | 1-2 |" "| 2 | 2-0 | 2-1 | 2-2 |" ) ); } #[test] fn min_width_on_smaller_content() { assert_eq!( Matrix::new(3, 3) .with(Style::markdown()) .with(Modify::new(Rows::single(0)).with(MinWidth::new(1))) .to_string(), Matrix::new(3, 3).with(Style::markdown()).to_string() ); } #[test] fn min_with_max_width() { let mut table = Matrix::table(3, 3); table .with(Style::markdown()) .with(Modify::new(Rows::single(0)).with(MinWidth::new(3))) .with(Modify::new(Rows::single(0)).with(Width::truncate(3))); assert_eq!( table.to_string(), static_table!( "| N | col | col | col |" "|-----|-----|-----|-----|" "| 0 | 0-0 | 0-1 | 0-2 |" "| 1 | 1-0 | 1-1 | 1-2 |" "| 2 | 2-0 | 2-1 | 2-2 |" ) ); table.with(Modify::new(Segment::all()).with(TrimStrategy::None)); assert_eq!( table.to_string(), static_table!( "| N | col | col | col |" "|-----|-----|-----|-----|" "| 0 | 0-0 | 0-1 | 0-2 |" "| 1 | 1-0 | 1-1 | 1-2 |" "| 2 | 2-0 | 2-1 | 2-2 |" ) ); } #[test] fn min_with_max_width_truncate_suffix() { let mut table = Matrix::table(3, 3); table .with(Style::markdown()) .with(Modify::new(Rows::single(0)).with(MinWidth::new(3))) .with(Modify::new(Rows::single(0)).with(Width::truncate(3).suffix("..."))); assert_eq!( table.to_string(), static_table!( "| N | ... | ... | ... |" "|-----|-----|-----|-----|" "| 0 | 0-0 | 0-1 | 0-2 |" "| 1 | 1-0 | 1-1 | 1-2 |" "| 2 | 2-0 | 2-1 | 2-2 |" ) ); table.with(Modify::new(Segment::all()).with(TrimStrategy::None)); assert_eq!( table.to_string(), static_table!( "| N | ... | ... | ... |" "|-----|-----|-----|-----|" "| 0 | 0-0 | 0-1 | 0-2 |" "| 1 | 1-0 | 1-1 | 1-2 |" "| 2 | 2-0 | 2-1 | 2-2 |" ) ); } #[test] fn min_with_max_width_truncate_suffix_limit_replace() { let table = Matrix::new(3, 3) .with(Style::markdown()) .with( Modify::new(Rows::single(0)).with( Width::truncate(3) .suffix("...") .suffix_limit(SuffixLimit::Replace('x')), ), ) .to_string(); assert_eq!( table, static_table!( "| N | xxx | xxx | xxx |" "|---|-----|-----|-----|" "| 0 | 0-0 | 0-1 | 0-2 |" "| 1 | 1-0 | 1-1 | 1-2 |" "| 2 | 2-0 | 2-1 | 2-2 |" ) ); } #[test] fn min_with_max_width_truncate_suffix_limit_cut() { let table = Matrix::new(3, 3) .with(Style::markdown()) .with( Modify::new(Rows::single(0)).with( Width::truncate(3) .suffix("qwert") .suffix_limit(SuffixLimit::Cut), ), ) .to_string(); assert_eq!( table, static_table!( "| N | qwe | qwe | qwe |" "|---|-----|-----|-----|" "| 0 | 0-0 | 0-1 | 0-2 |" "| 1 | 1-0 | 1-1 | 1-2 |" "| 2 | 2-0 | 2-1 | 2-2 |" ) ); } #[test] fn min_with_max_width_truncate_suffix_limit_ignore() { let table = Matrix::new(3, 3) .with(Style::markdown()) .with( Modify::new(Rows::single(0)).with( Width::truncate(3) .suffix("qwert") .suffix_limit(SuffixLimit::Ignore), ), ) .to_string(); assert_eq!( table, static_table!( "| N | col | col | col |" "|---|-----|-----|-----|" "| 0 | 0-0 | 0-1 | 0-2 |" "| 1 | 1-0 | 1-1 | 1-2 |" "| 2 | 2-0 | 2-1 | 2-2 |" ) ); } #[cfg(feature = "ansi")] #[test] fn min_with_max_width_truncate_suffix_try_color() { let data = &[ Color::FG_RED.colorize("asd"), Color::FG_BLUE.colorize("zxc2"), (Color::FG_GREEN | Color::BG_BLACK).colorize("asdasd"), ]; let table = Matrix::iter(data) .with(Style::markdown()) .with(Width::truncate(7).suffix("..").suffix_try_color(true)) .to_string(); assert_eq!( table, static_table!( "| S.. |" "|-----|" "| \u{1b}[31masd\u{1b}[39m |" "| \u{1b}[34mz\u{1b}[39m\u{1b}[34m..\u{1b}[39m |" "| \u{1b}[32m\u{1b}[40ma\u{1b}[39m\u{1b}[49m\u{1b}[32m\u{1b}[40m..\u{1b}[39m\u{1b}[49m |" ) ); } #[cfg(feature = "ansi")] #[test] fn min_width_color() { let data = &[ Color::FG_RED.colorize("asd"), Color::FG_BLUE.colorize("zxc"), (Color::FG_GREEN | Color::BG_BLACK).colorize("asdasd"), ]; let table = Matrix::iter(data) .with(Style::markdown()) .with(Modify::new(Segment::all()).with(MinWidth::new(10))) .to_string(); assert_eq!( ansi_str::AnsiStr::ansi_strip(&table), static_table!( "| String |" "|------------|" "| asd |" "| zxc |" "| asdasd |" ) ); assert_eq!( table, "| String |\n|------------|\n| \u{1b}[31masd\u{1b}[39m |\n| \u{1b}[34mzxc\u{1b}[39m |\n| \u{1b}[32m\u{1b}[40masdasd\u{1b}[39m\u{1b}[49m |", ); } #[cfg(feature = "ansi")] #[test] fn min_width_color_with_smaller_then_width() { let data = &[ Color::FG_RED.colorize("asd"), Color::FG_BLUE.colorize("zxc2"), (Color::FG_GREEN | Color::BG_BLACK).colorize("asdasd"), ]; assert_eq!( Matrix::iter(data) .with(Modify::new(Segment::all()).with(MinWidth::new(1))) .to_string(), Matrix::iter(data).to_string() ); } #[test] fn total_width_big() { let table = Matrix::new(3, 3) .with(Style::markdown()) .with(Width::truncate(80)) .with(MinWidth::new(80)) .to_string(); assert_eq!(get_text_width(&table), 80); assert_eq!( table, static_table!( "| N | column 0 | column 1 | column 2 |" "|--------------|---------------------|--------------------|--------------------|" "| 0 | 0-0 | 0-1 | 0-2 |" "| 1 | 1-0 | 1-1 | 1-2 |" "| 2 | 2-0 | 2-1 | 2-2 |" ) ); let table = Matrix::new(3, 3) .with(Style::markdown()) .with(Modify::new(Segment::all()).with(TrimStrategy::None)) .with(Settings::new(Width::truncate(80), Width::increase(80))) .to_string(); assert_eq!(get_text_width(&table), 80); assert_eq!( table, static_table!( "| N | column 0 | column 1 | column 2 |" "|--------------|---------------------|--------------------|--------------------|" "| 0 | 0-0 | 0-1 | 0-2 |" "| 1 | 1-0 | 1-1 | 1-2 |" "| 2 | 2-0 | 2-1 | 2-2 |" ) ); } #[test] fn total_width_big_with_panel() { let table = Matrix::new(3, 3) .with(Panel::horizontal(0, "Hello World")) .with( Modify::new(Segment::all()) .with(Alignment::center()) .with(Padding::zero()), ) .with(Style::markdown()) .with(Width::truncate(80)) .with(MinWidth::new(80)) .to_string(); assert_width!(&table, 80); assert_eq!( table, static_table!( "| Hello World |" "|--------------|---------------------|--------------------|--------------------|" "| N | column 0 | column 1 | column 2 |" "| 0 | 0-0 | 0-1 | 0-2 |" "| 1 | 1-0 | 1-1 | 1-2 |" "| 2 | 2-0 | 2-1 | 2-2 |" ) ); } #[test] fn total_width_big_with_panel_with_wrapping_doesnt_affect_increase() { let table1 = Matrix::new(3, 3) .with(Panel::horizontal(0, "Hello World")) .with(Modify::new(Segment::all()).with(Alignment::center())) .with(Style::markdown()) .with(Width::wrap(80)) .with(MinWidth::new(80)) .to_string(); let table2 = Matrix::new(3, 3) .with(Panel::horizontal(0, "Hello World")) .with(Modify::new(Segment::all()).with(Alignment::center())) .with(Style::markdown()) .with(Width::truncate(80)) .with(MinWidth::new(80)) .to_string(); assert_eq!(table1, table2); } #[test] fn total_width_small() { let table = Matrix::new(3, 3) .with(Style::markdown()) .with(Width::truncate(14)) .with(MinWidth::new(14)) .to_string(); assert_eq!( table, static_table!( "| | | | c |" "|--|--|--|---|" "| | | | 0 |" "| | | | 1 |" "| | | | 2 |" ) ); assert_width!(table, 14); } #[test] fn total_width_smaller_then_content() { let table = Matrix::new(3, 3) .with(Style::markdown()) .with(Width::truncate(8)) .with(MinWidth::new(8)) .to_string(); assert_eq!( table, static_table!( "| | | | |" "|--|--|--|--|" "| | | | |" "| | | | |" "| | | | |" ) ); } #[test] fn total_width_small_with_panel() { let table = Matrix::new(3, 3) .with(Modify::new(Segment::all()).with(Alignment::center())) .with(Style::markdown()) .with(Width::truncate(20)) .with(MinWidth::new(20)) .to_string(); assert_eq!( table, static_table!( "| | co | co | col |" "|--|----|----|-----|" "| | 0- | 0- | 0-2 |" "| | 1- | 1- | 1-2 |" "| | 2- | 2- | 2-2 |" ) ); assert_width!(table, 20); let table = Matrix::iter(Vec::::new()) .with(Panel::horizontal(0, "Hello World")) .with( Modify::new(Segment::all()) .with(Alignment::center()) .with(Padding::zero()), ) .with(Width::truncate(5)) .with(MinWidth::new(5)) .to_string(); assert_eq!( table, static_table!("+---+" "|Hel|" "+---+" "|usi|" "+---+") ); assert_width!(table, 5); let table = Matrix::table(1, 2) .with(Panel::horizontal(0, "Hello World")) .with(Modify::new(Segment::all()).with(Alignment::center())) .with(Style::markdown()) .with(Width::truncate(20)) .with(MinWidth::new(20)) .to_string(); assert_eq!( table, static_table!( "| Hello World |" "|--|-------|-------|" "| | colum | colum |" "| | 0-0 | 0-1 |" ) ); assert_width!(table, 20); let table = Matrix::new(3, 3) .with(Panel::horizontal(0, "Hello World")) .with(Modify::new(Segment::all()).with(Alignment::center())) .with(Style::markdown()) .with(Width::truncate(20)) .with(MinWidth::new(20)) .to_string(); assert_eq!( table, static_table!( "| Hello World |" "|--|----|----|-----|" "| | co | co | col |" "| | 0- | 0- | 0-2 |" "| | 1- | 1- | 1-2 |" "| | 2- | 2- | 2-2 |" ) ); assert_width!(table, 20); let table = Matrix::new(3, 3) .with(Panel::horizontal(0, "Hello World")) .with(Modify::new(Segment::all()).with(Alignment::center())) .with(Style::markdown()) .with(Width::truncate(6)) .with(MinWidth::new(6)) .to_string(); assert_eq!( table, static_table!( "| Hello Wor |" "|--|--|--|--|" "| | | | |" "| | | | |" "| | | | |" "| | | | |" ) ); assert_width!(table, 13); let table = Matrix::new(3, 3) .with(Panel::horizontal(0, "Hello World")) .with(Modify::new(Segment::all()).with(Alignment::center())) .with(Style::markdown()) .with(Width::truncate(14)) .with(MinWidth::new(14)) .to_string(); assert_eq!( table, static_table!( "| Hello Worl |" "|--|--|--|---|" "| | | | c |" "| | | | 0 |" "| | | | 1 |" "| | | | 2 |" ) ); assert_width!(table, 14); let table = Matrix::new(3, 3) .with(Panel::horizontal(0, "Hello World 123")) .with(Modify::new(Segment::all()).with(Alignment::center())) .with(Style::markdown()) .with(Width::truncate(14)) .with(MinWidth::new(14)) .to_string(); assert_eq!( table, static_table!( "| Hello Worl |" "|--|--|--|---|" "| | | | c |" "| | | | 0 |" "| | | | 1 |" "| | | | 2 |" ) ); assert_width!(table, 14); } #[cfg(feature = "ansi")] #[test] fn total_width_wrapping() { let table = Matrix::new(3, 3) .with(Modify::new(Segment::all()).with(Alignment::center())) .with(Style::markdown()) .with(Width::wrap(20)) .with(MinWidth::new(20)) .to_string(); assert_eq!( table, static_table!( "| | co | co | col |" "| | lu | lu | umn |" "| | mn | mn | 2 |" "| | 0 | 1 | |" "|--|----|----|-----|" "| | 0- | 0- | 0-2 |" "| | 0 | 1 | |" "| | 1- | 1- | 1-2 |" "| | 0 | 1 | |" "| | 2- | 2- | 2-2 |" "| | 0 | 1 | |" ) ); assert_width!(table, 20); let table = Matrix::new(3, 3) .insert((3, 2).into(), "some loong string") .with(Modify::new(Segment::all()).with(Alignment::center())) .with(Style::markdown()) .with(Width::wrap(20).keep_words(true)) .with(MinWidth::new(20)) .to_string(); assert_eq!( table, static_table!( "| | | column | |" "| | | 1 | |" "|--|--|---------|--|" "| | | 0-1 | |" "| | | 1-1 | |" "| | | some | |" "| | | loong | |" "| | | string | |" ) ); assert_width!(table, 20); } #[test] fn total_width_small_with_panel_using_wrapping() { let table = Matrix::new(3, 3) .with(Panel::horizontal(0, "Hello World")) .with(Modify::new(Segment::all()).with(Alignment::center())) .with(Style::markdown()) .with(Width::wrap(20)) .with(MinWidth::new(20)) .to_string(); assert_eq!( table, static_table!( "| Hello World |" "|--|----|----|-----|" "| | co | co | col |" "| | lu | lu | umn |" "| | mn | mn | 2 |" "| | 0 | 1 | |" "| | 0- | 0- | 0-2 |" "| | 0 | 1 | |" "| | 1- | 1- | 1-2 |" "| | 0 | 1 | |" "| | 2- | 2- | 2-2 |" "| | 0 | 1 | |" ) ); assert_width!(table, 20); let table = Matrix::new(3, 3) .with(Panel::horizontal(0, "Hello World")) .with(Modify::new(Segment::all()).with(Alignment::center())) .with(Style::markdown()) .with(Width::wrap(14)) .with(MinWidth::new(14)) .to_string(); assert_eq!( table, static_table!( "| Hello Worl |" "| d |" "|--|--|--|---|" "| | | | c |" "| | | | o |" "| | | | l |" "| | | | u |" "| | | | m |" "| | | | n |" "| | | | |" "| | | | 2 |" "| | | | 0 |" "| | | | - |" "| | | | 2 |" "| | | | 1 |" "| | | | - |" "| | | | 2 |" "| | | | 2 |" "| | | | - |" "| | | | 2 |" ) ); assert_width!(table, 14); let table = Matrix::new(3, 3) .with(Panel::horizontal(0, "Hello World 123")) .with(Modify::new(Segment::all()).with(Alignment::center())) .with(Style::markdown()) .with(Width::wrap(14)) .with(MinWidth::new(14)) .to_string(); assert_eq!( table, static_table!( "| Hello Worl |" "| d 123 |" "|--|--|--|---|" "| | | | c |" "| | | | o |" "| | | | l |" "| | | | u |" "| | | | m |" "| | | | n |" "| | | | |" "| | | | 2 |" "| | | | 0 |" "| | | | - |" "| | | | 2 |" "| | | | 1 |" "| | | | - |" "| | | | 2 |" "| | | | 2 |" "| | | | - |" "| | | | 2 |" ) ); assert_width!(table, 14); } #[test] fn max_width_with_span() { let mut table = Matrix::new(3, 3) .insert((1, 1).into(), "a long string") .to_table(); table .with(Style::psql()) .with(Modify::new((1, 1)).with(Span::column(2))) .with(Modify::new((2, 2)).with(Span::column(2))); table.with(Width::truncate(40)); assert_eq!( table.to_string(), static_table!( " N | column 0 | column 1 | column 2 " "---+----------+----------+----------" " 0 | a long string | 0-2 " " 1 | 1-0 | 1-1 " " 2 | 2-0 | 2-1 | 2-2 " ) ); assert_width!(table, 36); table.with(Width::truncate(20)); assert_eq!( table.to_string(), static_table!( " | col | col | col " "--+-----+-----+-----" " | a long st | 0-2 " " | 1-0 | 1-1 " " | 2-0 | 2-1 | 2-2 " ) ); assert_width!(table, 20); table.with(Width::truncate(10)); assert_eq!( table.to_string(), static_table!( " | | | " "--+--+--+--" " | a l | " " | | 1-1 " " | | | " ) ); assert_width!(table, 11); } #[test] fn min_width_works_with_right_alignment() { let json = r#" { "some": "random", "json": [ { "1": "2" }, { "1": "2" }, { "1": "2" } ] } "#; let mut table = Matrix::iter([json]); table .with(Style::markdown()) .with( Modify::new(Segment::all()) .with(Alignment::right()) .with(TrimStrategy::None), ) .with(MinWidth::new(50)); assert_eq!(get_text_width(&table.to_string()), 50); assert_eq!( table.to_string(), static_table!( "| &str |" "|------------------------------------------------|" "| |" "| { |" "| \"some\": \"random\", |" "| \"json\": [ |" "| { \"1\": \"2\" }, |" "| { \"1\": \"2\" }, |" "| { \"1\": \"2\" } |" "| ] |" "| } |" "| |" ) ); table .with(Modify::new(Segment::all()).with(TrimStrategy::Horizontal)) .with(MinWidth::new(50)); assert_eq!( table.to_string(), static_table!( r#"| &str |"# r#"|------------------------------------------------|"# r#"| |"# r#"| { |"# r#"| "some": "random", |"# r#"| "json": [ |"# r#"| { "1": "2" }, |"# r#"| { "1": "2" }, |"# r#"| { "1": "2" } |"# r#"| ] |"# r#"| } |"# r#"| |"# ) ); assert_width!(table, 50); table .with(Modify::new(Segment::all()).with(TrimStrategy::Both)) .with(MinWidth::new(50)); assert_eq!( table.to_string(), static_table!( r#"| &str |"# r#"|------------------------------------------------|"# r#"| { |"# r#"| "some": "random", |"# r#"| "json": [ |"# r#"| { "1": "2" }, |"# r#"| { "1": "2" }, |"# r#"| { "1": "2" } |"# r#"| ] |"# r#"| } |"# r#"| |"# r#"| |"# ) ); assert_width!(table, 50); let mut table = Matrix::iter([json]); table .with(Style::markdown()) .with( Modify::new(Segment::all()) .with(Alignment::center()) .with(TrimStrategy::None), ) .with(MinWidth::new(50)); assert_eq!( table.to_string(), static_table!( "| &str |" "|------------------------------------------------|" "| |" "| { |" "| \"some\": \"random\", |" "| \"json\": [ |" "| { \"1\": \"2\" }, |" "| { \"1\": \"2\" }, |" "| { \"1\": \"2\" } |" "| ] |" "| } |" "| |" ) ); assert_eq!(get_text_width(&table.to_string()), 50); table .with(Modify::new(Segment::all()).with(TrimStrategy::Horizontal)) .with(MinWidth::new(50)); assert_eq!( table.to_string(), static_table!( r#"| &str |"# r#"|------------------------------------------------|"# r#"| |"# r#"| { |"# r#"| "some": "random", |"# r#"| "json": [ |"# r#"| { "1": "2" }, |"# r#"| { "1": "2" }, |"# r#"| { "1": "2" } |"# r#"| ] |"# r#"| } |"# r#"| |"# ) ); assert_width!(table, 50); table .with(Modify::new(Segment::all()).with(TrimStrategy::Both)) .with(MinWidth::new(50)); assert_eq!( table.to_string(), static_table!( r#"| &str |"# r#"|------------------------------------------------|"# r#"| { |"# r#"| "some": "random", |"# r#"| "json": [ |"# r#"| { "1": "2" }, |"# r#"| { "1": "2" }, |"# r#"| { "1": "2" } |"# r#"| ] |"# r#"| } |"# r#"| |"# r#"| |"# ) ); assert_width!(table, 50); } #[test] fn min_width_with_span_1() { let data = [ ["0", "1"], ["a long string which will affect min width logic", ""], ["2", "3"], ]; let table = Matrix::iter(data) .with(Style::markdown()) .with(Modify::new((1, 0)).with(Span::column(2))) .with(MinWidth::new(100)) .to_string(); assert_eq!(get_text_width(&table), 100); assert_eq!( table, static_table!( "| 0 | 1 |" "|------------------------------------------------------------------------|-------------------------|" "| 0 |" "| a long string which will affect min width logic | |" "| 2 | 3 |" ) ); assert_width!(table, 100); } #[test] fn min_width_with_span_2() { let data = [ ["0", "1"], ["a long string which will affect min width logic", ""], ["2", "3"], ]; let table = Matrix::iter(data) .with(Style::markdown()) .with(Modify::new((2, 0)).with(Span::column(2))) .with(MinWidth::new(100)) .to_string(); assert_eq!(get_text_width(&table), 100); assert_eq!( table, static_table!( "| 0 | 1 |" "|-------------------------------------------------|------------------------------------------------|" "| 0 | 1 |" "| a long string which will affect min width logic |" "| 2 | 3 |" ) ); } #[test] fn justify_width_constant_test() { let table = Matrix::new(3, 3) .with(Style::markdown()) .with(Justify::new(3)) .to_string(); assert_eq!( table, static_table!( "| N | col | col | col |" "|-----|-----|-----|-----|" "| 0 | 0-0 | 0-1 | 0-2 |" "| 1 | 1-0 | 1-1 | 1-2 |" "| 2 | 2-0 | 2-1 | 2-2 |" ) ); } #[test] fn justify_width_constant_different_sizes_test() { let table = Matrix::new(3, 3) .insert((1, 1).into(), "Hello World") .insert((3, 2).into(), "multi\nline string\n") .with(Style::markdown()) .with(Justify::new(3)) .to_string(); assert_eq!( table, static_table!( "| N | col | col | col |" "|-----|-----|-----|-----|" "| 0 | Hel | 0-1 | 0-2 |" "| 1 | 1-0 | 1-1 | 1-2 |" "| 2 | 2-0 | mul | 2-2 |" ) ); } #[test] fn justify_width_constant_0_test() { let table = Matrix::new(3, 3) .with(Style::markdown()) .with(Justify::new(0)) .to_string(); assert_eq!( table, static_table!( "| | | | |" "|--|--|--|--|" "| | | | |" "| | | | |" "| | | | |" ) ); } #[test] fn justify_width_min_test() { let table = Matrix::new(3, 3) .with(Style::markdown()) .with(Justify::min()) .to_string(); println!("{table}"); assert_eq!( table, static_table!( "| N | c | c | c |" "|---|---|---|---|" "| 0 | 0 | 0 | 0 |" "| 1 | 1 | 1 | 1 |" "| 2 | 2 | 2 | 2 |" ) ); } #[test] fn justify_width_max_test() { let table = Matrix::new(3, 3) .with(Style::markdown()) .with(Justify::max()) .to_string(); assert_eq!( table, static_table!( "| N | column 0 | column 1 | column 2 |" "|----------|----------|----------|----------|" "| 0 | 0-0 | 0-1 | 0-2 |" "| 1 | 1-0 | 1-1 | 1-2 |" "| 2 | 2-0 | 2-1 | 2-2 |" ) ); } #[test] fn max_width_when_cell_has_tabs() { let table = Matrix::new(3, 3) .insert((2, 1).into(), "\tHello\tWorld\t") .with(TabSize::new(4)) .with(Style::markdown()) .with(Modify::new(Columns::new(..)).with(Width::truncate(1))) .to_string(); assert_eq!( table, static_table!( "| N | c | c | c |" "|---|---|---|---|" "| 0 | 0 | 0 | 0 |" "| 1 | | 1 | 1 |" "| 2 | 2 | 2 | 2 |" ) ); } #[test] fn max_width_table_when_cell_has_tabs() { let table = Matrix::new(3, 3) .insert((2, 1).into(), "\tHello\tWorld\t") .with(TabSize::new(4)) .with(Style::markdown()) .with(Width::truncate(15)) .to_string(); assert_eq!( table, static_table!( "| | co | | |" "|--|----|--|--|" "| | 0- | | |" "| | | | |" "| | 2- | | |" ) ); } // WE GOT [["", "column 0", "column 1 ", "column 2 "], ["", "0-0 ", "0-1 ", "0-2 "], ["", "Hello World With Big Line; Here w", "1-1", "1-2"], ["", "2-0 ", "Hello World With Big L", "2-2"]] // [2, 10, 11, 12] // 40 55 40 // BEFORE ADJ [2, 10, 11, 12] // WE GOT [["", "column 0", "column 1", "column 2"], ["", "0-0", "0-1", "0-2"], ["", "Hello World With Big Line; Here w", "1-1", "1-2"], ["", "2-0", "Hello World With Big L", "2-2"]] // [2, 11, 12, 11] // 41 55 40 // adj [2, 10, 10, 10] #[test] fn max_width_truncate_with_big_span() { let table = Matrix::new(3, 3) .insert((2, 1).into(), "Hello World With Big Line; Here we gooooooo") .with(Style::markdown()) .with(Modify::new((2, 1)).with(Span::column(3))) .with(Width::truncate(40)) .to_string(); assert_eq!(get_text_width(&table), 40); assert_eq!( table, static_table!( "| | column 0 | column 1 | column 2 |" "|--|-----------|-----------|-----------|" "| | 0-0 | 0-1 | 0-2 |" "| | Hello World With Big Line; Here w |" "| | 2-0 | 2-1 | 2-2 |" ) ); let table = Matrix::new(3, 3) .insert((2, 1).into(), "Hello World With Big Line; Here we gooooooo") .insert((3, 2).into(), "Hello World With Big Line; Here") .with(Style::markdown()) .with(Modify::new((2, 1)).with(Span::column(3))) .with(Modify::new((3, 2)).with(Span::column(2))) .to_string(); assert_eq!( table, static_table!( "| N | column 0 | column 1 | column 2 |" "|---|-----------|----------------|----------------|" "| 0 | 0-0 | 0-1 | 0-2 |" "| 1 | Hello World With Big Line; Here we gooooooo |" "| 2 | 2-0 | Hello World With Big Line; Here |" ) ); let table = Matrix::new(3, 3) .insert((2, 1).into(), "Hello World With Big Line; Here we gooooooo") .insert((3, 2).into(), "Hello World With Big Line; Here") .with(Style::markdown()) .with(Modify::new((2, 1)).with(Span::column(3))) .with(Modify::new((3, 2)).with(Span::column(2))) .with(Width::truncate(40)) .to_string(); assert_eq!( table, static_table!( "| | colum | column 1 | column 2 |" "|--|-------|-------------|-------------|" "| | 0-0 | 0-1 | 0-2 |" "| | Hello World With Big Line; Here w |" "| | 2-0 | Hello World With Big Line |" ) ); assert_eq!(get_text_width(&table), 40); let table = Matrix::new(3, 3) .insert((2, 1).into(), "Hello World With Big Line; Here we gooooooo") .insert((3, 2).into(), "Hello World With Big Line; Here") .with(Style::markdown()) .with(Modify::new((2, 1)).with(Span::column(2))) .with(Modify::new((3, 2)).with(Span::column(2))) .with(Width::truncate(40)) .to_string(); assert_eq!(get_text_width(&table), 40); assert_eq!( table, static_table!( "| | column 0 | column 1 | c |" "|--|---------------|---------------|---|" "| | 0-0 | 0-1 | 0 |" "| | Hello World With Big Line; He | 1 |" "| | 2-0 | Hello World With |" ) ); let table = Matrix::new(3, 3) .insert((2, 1).into(), "Hello World With Big Line; Here w") .insert((3, 2).into(), "Hello World With Big L") .with(Style::markdown()) .with(Modify::new((2, 1)).with(Span::column(3))) .with(Modify::new((3, 2)).with(Span::column(2))) .to_string(); assert_eq!( table, static_table!( "| N | column 0 | column 1 | column 2 |" "|---|----------|------------|-----------|" "| 0 | 0-0 | 0-1 | 0-2 |" "| 1 | Hello World With Big Line; Here w |" "| 2 | 2-0 | Hello World With Big L |" ) ); } #[test] fn max_width_truncate_priority_max() { let table = Matrix::new(3, 3) .insert((2, 1).into(), "Hello World With Big Line") .with(Style::markdown()) .with(Width::truncate(35).priority(PriorityMax::right())) .to_string(); assert_width!(table, 35); assert_eq!( table, static_table!( "| N | column | column | column |" "|---|---------|---------|---------|" "| 0 | 0-0 | 0-1 | 0-2 |" "| 1 | Hello W | 1-1 | 1-2 |" "| 2 | 2-0 | 2-1 | 2-2 |" ) ); let table = Matrix::new(3, 3) .insert((2, 1).into(), "Hello World With Big Line") .with(Style::markdown()) .with(Width::truncate(20).priority(PriorityMax::right())) .to_string(); assert_width!(table, 20); assert_eq!( table, static_table!( "| N | co | co | co |" "|---|----|----|----|" "| 0 | 0- | 0- | 0- |" "| 1 | He | 1- | 1- |" "| 2 | 2- | 2- | 2- |" ) ); let table = Matrix::new(3, 3) .insert((2, 1).into(), "Hello World With Big Line") .with(Style::markdown()) .with(Width::truncate(0).priority(PriorityMax::right())) .to_string(); assert_width!(table, 13); assert_eq!( table, static_table!( "| | | | |" "|--|--|--|--|" "| | | | |" "| | | | |" "| | | | |" ) ); } #[test] fn max_width_truncate_priority_max_with_span() { let table = Matrix::new(3, 3) .insert((2, 1).into(), "Hello World With Big Line") .with(Style::markdown()) .with(Modify::new((2, 1)).with(Span::column(2))) .with(Width::truncate(15).priority(PriorityMax::right())) .to_string(); assert_width!(table, 15); assert_eq!( table, static_table!( "| N | c | | |" "|---|---|--|--|" "| 0 | 0 | | |" "| 1 | Hell | |" "| 2 | 2 | | |" ) ); } #[test] fn max_width_wrap_priority_max() { let table = Matrix::new(3, 3) .insert((2, 1).into(), "Hello World With Big Line") .with(Style::markdown()) .with(Width::wrap(35).priority(PriorityMax::right())) .to_string(); assert_width!(table, 35); assert_eq!( table, static_table!( "| N | column | column | column |" "| | 0 | 1 | 2 |" "|---|---------|---------|---------|" "| 0 | 0-0 | 0-1 | 0-2 |" "| 1 | Hello W | 1-1 | 1-2 |" "| | orld Wi | | |" "| | th Big | | |" "| | Line | | |" "| 2 | 2-0 | 2-1 | 2-2 |" ) ); let table = Matrix::new(3, 3) .insert((2, 1).into(), "Hello World With Big Line") .with(Style::markdown()) .with(Width::wrap(20).priority(PriorityMax::right())) .to_string(); assert_width!(table, 20); assert_eq!( table, static_table!( "| N | co | co | co |" "| | lu | lu | lu |" "| | mn | mn | mn |" "| | 0 | 1 | 2 |" "|---|----|----|----|" "| 0 | 0- | 0- | 0- |" "| | 0 | 1 | 2 |" "| 1 | He | 1- | 1- |" "| | ll | 1 | 2 |" "| | o | | |" "| | Wo | | |" "| | rl | | |" "| | d | | |" "| | Wi | | |" "| | th | | |" "| | B | | |" "| | ig | | |" "| | L | | |" "| | in | | |" "| | e | | |" "| 2 | 2- | 2- | 2- |" "| | 0 | 1 | 2 |" ) ); let table = Matrix::new(3, 3) .insert((2, 1).into(), "Hello World With Big Line") .with(Style::markdown()) .with(Width::wrap(0).priority(PriorityMax::right())) .to_string(); assert_width!(table, 13); assert_eq!( table, static_table!( "| | | | |" "|--|--|--|--|" "| | | | |" "| | | | |" "| | | | |" ) ); } #[test] fn max_width_wrap_priority_max_with_span() { let table = Matrix::new(3, 3) .insert((2, 1).into(), "Hello World With Big Line") .with(Style::markdown()) .with(Modify::new((2, 1)).with(Span::column(2))) .with(Width::wrap(15).priority(PriorityMax::right())) .to_string(); assert_width!(table, 15); assert_eq!( table, static_table!( "| N | c | | |" "| | o | | |" "| | l | | |" "| | u | | |" "| | m | | |" "| | n | | |" "| | | | |" "| | 0 | | |" "|---|---|--|--|" "| 0 | 0 | | |" "| | - | | |" "| | 0 | | |" "| 1 | Hell | |" "| | o Wo | |" "| | rld | |" "| | With | |" "| | Big | |" "| | Lin | |" "| | e | |" "| 2 | 2 | | |" "| | - | | |" "| | 0 | | |" ) ); } #[test] fn max_width_truncate_priority_min() { let table = Matrix::new(3, 3) .insert((2, 1).into(), "Hello World With Big Line") .with(Style::markdown()) .with(Width::truncate(35).priority(PriorityMin::right())) .to_string(); assert_width!(table, 35); assert_eq!( table, static_table!( "| | column 0 | | |" "|--|------------------------|--|--|" "| | 0-0 | | |" "| | Hello World With Big L | | |" "| | 2-0 | | |" ) ); let table = Matrix::new(3, 3) .insert((2, 1).into(), "Hello World With Big Line") .with(Style::markdown()) .with(Width::truncate(20).priority(PriorityMin::right())) .to_string(); assert_width!(table, 20); assert_eq!( table, static_table!( "| | column | | |" "|--|---------|--|--|" "| | 0-0 | | |" "| | Hello W | | |" "| | 2-0 | | |" ) ); let table = Matrix::new(3, 3) .insert((2, 1).into(), "Hello World With Big Line") .with(Style::markdown()) .with(Width::truncate(0).priority(PriorityMin::right())) .to_string(); assert_width!(table, 13); assert_eq!( table, static_table!( "| | | | |" "|--|--|--|--|" "| | | | |" "| | | | |" "| | | | |" ) ); } #[test] fn max_width_truncate_priority_min_with_span() { let table = Matrix::new(3, 3) .insert((2, 1).into(), "Hello World With Big Line") .with(Style::markdown()) .with(Modify::new((2, 1)).with(Span::column(2))) .with(Width::truncate(15).priority(PriorityMin::right())) .to_string(); assert_width!(table, 15); assert_eq!( table, static_table!( "| | | co | |" "|--|--|----|--|" "| | | 0- | |" "| | Hello | |" "| | | 2- | |" ) ); let table = Matrix::new(3, 3) .insert((2, 1).into(), "Hello World With Big Line") .with(Style::markdown()) .with(Modify::new((2, 1)).with(Span::column(2))) .with(Width::truncate(17).priority(PriorityMin::right())) .to_string(); assert_width!(table, 17); assert_eq!( table, static_table!( "| | | colu | |" "|--|--|------|--|" "| | | 0-1 | |" "| | Hello W | |" "| | | 2-1 | |" ) ); } #[test] fn max_width_wrap_priority_min() { let table = Matrix::new(3, 3) .insert((2, 1).into(), "Hello World With Big Line") .with(Style::markdown()) .with(Width::wrap(35).priority(PriorityMin::right())) .to_string(); assert_width!(table, 35); assert_eq!( table, static_table!( "| | column 0 | | |" "|--|------------------------|--|--|" "| | 0-0 | | |" "| | Hello World With Big L | | |" "| | ine | | |" "| | 2-0 | | |" ) ); let table = Matrix::new(3, 3) .insert((2, 1).into(), "Hello World With Big Line") .with(Style::markdown()) .with(Width::wrap(20).priority(PriorityMin::right())) .to_string(); assert_width!(table, 20); assert_eq!( table, static_table!( "| | column | | |" "| | 0 | | |" "|--|---------|--|--|" "| | 0-0 | | |" "| | Hello W | | |" "| | orld Wi | | |" "| | th Big | | |" "| | Line | | |" "| | 2-0 | | |" ) ); let table = Matrix::new(3, 3) .insert((2, 1).into(), "Hello World With Big Line") .with(Style::markdown()) .with(Width::wrap(0).priority(PriorityMin::right())) .to_string(); assert_width!(table, 13); assert_eq!( table, static_table!( "| | | | |" "|--|--|--|--|" "| | | | |" "| | | | |" "| | | | |" ) ); } #[test] fn max_width_wrap_priority_min_with_span() { let table = Matrix::new(3, 3) .insert((2, 1).into(), "Hello World With Big Line") .with(Style::markdown()) .with(Modify::new((2, 1)).with(Span::column(2))) .with(Width::wrap(15).priority(PriorityMin::right())) .to_string(); assert_width!(table, 15); assert_eq!( table, static_table!( "| | | co | |" "| | | lu | |" "| | | mn | |" "| | | 1 | |" "|--|--|----|--|" "| | | 0- | |" "| | | 1 | |" "| | Hello | |" "| | Worl | |" "| | d Wit | |" "| | h Big | |" "| | Line | |" "| | | 2- | |" "| | | 1 | |" ) ); } #[test] fn min_width_priority_max() { let table = Matrix::new(3, 3) .with(Style::markdown()) .with(MinWidth::new(60).priority(PriorityMax::right())) .to_string(); assert_eq!(get_text_width(&table), 60); assert_eq!( table, static_table!( "| N | column 0 | column 1 | column 2 |" "|---|----------|----------|--------------------------------|" "| 0 | 0-0 | 0-1 | 0-2 |" "| 1 | 1-0 | 1-1 | 1-2 |" "| 2 | 2-0 | 2-1 | 2-2 |" ), ); } #[test] fn min_width_priority_min() { let table = Matrix::new(3, 3) .with(Style::markdown()) .with(MinWidth::new(60).priority(PriorityMin::right())) .to_string(); assert_eq!(get_text_width(&table), 60); assert_eq!( table, static_table!( "| N | column 0 | column 1 | column 2 |" "|--------------|--------------|--------------|-------------|" "| 0 | 0-0 | 0-1 | 0-2 |" "| 1 | 1-0 | 1-1 | 1-2 |" "| 2 | 2-0 | 2-1 | 2-2 |" ), ); } #[test] fn max_width_tab_0() { let table = Matrix::iter(["\t\tTigre Ecuador\tOMYA Andina\t3824909999\tCalcium carbonate\tColombia\t"]) .with(TabSize::new(4)) .with(Style::markdown()) .with(Width::wrap(60)) .to_string(); assert_width!(table, 60); assert_eq!( table, static_table!( "| &str |" "|----------------------------------------------------------|" "| Tigre Ecuador OMYA Andina 3824909999 Ca |" "| lcium carbonate Colombia |" ) ); } #[test] fn min_width_is_not_used_after_padding() { let table = Matrix::new(3, 3) .with(Style::markdown()) .with(MinWidth::new(60)) .with(Modify::new((0, 0)).with(Padding::new(2, 2, 0, 0))) .to_string(); assert_eq!(get_text_width(&table), 40); assert_eq!( table, static_table!( "| N | column 0 | column 1 | column 2 |" "|-----|----------|----------|----------|" "| 0 | 0-0 | 0-1 | 0-2 |" "| 1 | 1-0 | 1-1 | 1-2 |" "| 2 | 2-0 | 2-1 | 2-2 |" ), ); } #[test] fn min_width_is_used_after_margin() { let table = Matrix::new(3, 3) .with(Style::markdown()) .with(Margin::new(1, 1, 1, 1)) .with(Width::increase(60)) .to_string(); assert_eq!(get_text_width(&table), 60); assert_eq!( table, static_table!( " " " | N | column 0 | column 1 | column 2 | " " |--------|---------------|---------------|---------------| " " | 0 | 0-0 | 0-1 | 0-2 | " " | 1 | 1-0 | 1-1 | 1-2 | " " | 2 | 2-0 | 2-1 | 2-2 | " " " ), ); } #[test] fn wrap_keeping_words_0() { let data = vec![["Hello world"]]; let table = tabled::Table::new(data) .with(Width::wrap(8).keep_words(true)) .to_string(); assert_eq!(tabled::grid::util::string::get_text_width(&table), 8); assert_eq!( table, static_table!( "+------+" "| 0 |" "+------+" "| Hell |" "| o wo |" "| rld |" "+------+" ) ); } #[test] fn cell_truncate_multiline() { let table = Matrix::new(3, 3) .insert((1, 1).into(), "H\nel\nlo World") .insert((3, 2).into(), "multi\nline string\n") .with(Style::markdown()) .with( Modify::new(Columns::new(1..2).not(Rows::single(0))) .with(Width::truncate(1).multiline(true)), ) .to_string(); assert_eq!( table, static_table!( "| N | column 0 | column 1 | column 2 |" "|---|----------|-------------|----------|" "| 0 | H | 0-1 | 0-2 |" "| | e | | |" "| | l | | |" "| 1 | 1 | 1-1 | 1-2 |" "| 2 | 2 | multi | 2-2 |" "| | | line string | |" "| | | | |" ) ); } #[test] fn cell_truncate_multiline_with_suffix() { let table = Matrix::new(3, 3) .insert((1, 1).into(), "H\nel\nlo World") .insert((3, 2).into(), "multi\nline string\n") .with(Style::markdown()) .with( Modify::new(Columns::new(1..2).not(Rows::single(0))) .with(Width::truncate(1).multiline(true).suffix(".")), ) .to_string(); assert_eq!( table, static_table!( "| N | column 0 | column 1 | column 2 |" "|---|----------|-------------|----------|" "| 0 | . | 0-1 | 0-2 |" "| | . | | |" "| | . | | |" "| 1 | . | 1-1 | 1-2 |" "| 2 | . | multi | 2-2 |" "| | | line string | |" "| | | | |" ) ); } #[test] fn table_truncate_multiline() { let table = Matrix::new(3, 3) .insert((1, 1).into(), "H\nel\nlo World") .insert((3, 2).into(), "multi\nline string\n") .with(Style::markdown()) .with(Width::truncate(20).multiline(true)) .to_string(); assert_eq!( table, static_table!( "| | c | colu | co |" "|--|---|------|----|" "| | H | 0-1 | 0- |" "| | e | | |" "| | l | | |" "| | 1 | 1-1 | 1- |" "| | 2 | mult | 2- |" "| | | line | |" "| | | | |" ) ); } #[test] fn table_truncate_multiline_with_suffix() { let table = Matrix::new(3, 3) .insert((1, 1).into(), "H\nel\nlo World") .insert((3, 2).into(), "multi\nline string\n") .with(Style::markdown()) .with(Width::truncate(20).suffix(".").multiline(true)) .to_string(); assert_eq!( table, static_table!( "| | . | col. | c. |" "|--|---|------|----|" "| | . | 0-1 | 0. |" "| | . | | |" "| | . | | |" "| | . | 1-1 | 1. |" "| | . | mul. | 2. |" "| | | lin. | |" "| | | . | |" ) ); } test_table!( test_priority_left, Matrix::new(3, 10) .with(Style::markdown()) .with(Width::wrap(60).priority(PriorityLeft::default())), "| | | | | | | | co | column 7 | column 8 | column 9 |" "| | | | | | | | lu | | | |" "| | | | | | | | mn | | | |" "| | | | | | | | 6 | | | |" "|--|--|--|--|--|--|--|----|----------|----------|----------|" "| | | | | | | | 0- | 0-7 | 0-8 | 0-9 |" "| | | | | | | | 6 | | | |" "| | | | | | | | 1- | 1-7 | 1-8 | 1-9 |" "| | | | | | | | 6 | | | |" "| | | | | | | | 2- | 2-7 | 2-8 | 2-9 |" "| | | | | | | | 6 | | | |" ); test_table!( test_priority_right, Matrix::new(3, 10) .with(Style::markdown()) .with(Width::wrap(60).priority(PriorityRight::default())), "| N | column 0 | column 1 | column 2 | c | | | | | | |" "| | | | | o | | | | | | |" "| | | | | l | | | | | | |" "| | | | | u | | | | | | |" "| | | | | m | | | | | | |" "| | | | | n | | | | | | |" "| | | | | | | | | | | |" "| | | | | 3 | | | | | | |" "|---|----------|----------|----------|---|--|--|--|--|--|--|" "| 0 | 0-0 | 0-1 | 0-2 | 0 | | | | | | |" "| | | | | - | | | | | | |" "| | | | | 3 | | | | | | |" "| 1 | 1-0 | 1-1 | 1-2 | 1 | | | | | | |" "| | | | | - | | | | | | |" "| | | | | 3 | | | | | | |" "| 2 | 2-0 | 2-1 | 2-2 | 2 | | | | | | |" "| | | | | - | | | | | | |" "| | | | | 3 | | | | | | |" ); test_table!( priority_max_left, Matrix::new(3, 3) .insert((2, 1).into(), "Hello World With Big Line") .insert((2, 2).into(), "Hello World With Big Line") .with(Style::markdown()) .with(Width::truncate(37).priority(PriorityMax::left())), "| N | column | column 1 | column 2 |" "|---|---------|----------|----------|" "| 0 | 0-0 | 0-1 | 0-2 |" "| 1 | Hello W | Hello Wo | 1-2 |" "| 2 | 2-0 | 2-1 | 2-2 |" ); test_table!( priority_max_right, Matrix::new(3, 3) .insert((2, 1).into(), "Hello World With Big Line") .insert((2, 2).into(), "Hello World With Big Line") .with(Style::markdown()) .with(Width::truncate(37).priority(PriorityMax::right())), "| N | column 0 | column 1 | column |" "|---|----------|----------|---------|" "| 0 | 0-0 | 0-1 | 0-2 |" "| 1 | Hello Wo | Hello Wo | 1-2 |" "| 2 | 2-0 | 2-1 | 2-2 |" ); test_table!( priority_min_left, Matrix::new(3, 3).with(Width::truncate(30).priority(PriorityMin::left())), "+--+----------+----------+---+" "| | column 0 | column 1 | c |" "+--+----------+----------+---+" "| | 0-0 | 0-1 | 0 |" "+--+----------+----------+---+" "| | 1-0 | 1-1 | 1 |" "+--+----------+----------+---+" "| | 2-0 | 2-1 | 2 |" "+--+----------+----------+---+" ); test_table!( priority_min_right, Matrix::new(3, 3).with(Width::truncate(30).priority(PriorityMin::right())), "+--+---+----------+----------+" "| | c | column 1 | column 2 |" "+--+---+----------+----------+" "| | 0 | 0-1 | 0-2 |" "+--+---+----------+----------+" "| | 1 | 1-1 | 1-2 |" "+--+---+----------+----------+" "| | 2 | 2-1 | 2-2 |" "+--+---+----------+----------+" ); #[cfg(feature = "derive")] mod derived { use super::*; use tabled::Tabled; #[test] fn wrapping_as_total_multiline() { #[derive(Tabled)] struct D<'a>( #[tabled(rename = "version")] &'a str, #[tabled(rename = "published_date")] &'a str, #[tabled(rename = "is_active")] &'a str, #[tabled(rename = "major_feature")] &'a str, ); let data = vec![ D("0.2.1", "2021-06-23", "true", "#[header(inline)] attribute"), D("0.2.0", "2021-06-19", "false", "API changes"), D("0.1.4", "2021-06-07", "false", "display_with attribute"), ]; let table = Matrix::iter(&data) .with(Style::markdown()) .with(Modify::new(Segment::all()).with(Alignment::left())) .with(Width::wrap(57)) .to_string(); assert_eq!( table, static_table!( "| ver | published_d | is_act | major_feature |" "| sio | ate | ive | |" "| n | | | |" "|-----|-------------|--------|--------------------------|" "| 0.2 | 2021-06-23 | true | #[header(inline)] attrib |" "| .1 | | | ute |" "| 0.2 | 2021-06-19 | false | API changes |" "| .0 | | | |" "| 0.1 | 2021-06-07 | false | display_with attribute |" "| .4 | | | |" ) ); assert_width!(table, 57); let table = Matrix::iter(&data) .with(Style::markdown()) .with(Modify::new(Segment::all()).with(Alignment::left())) .with(Width::wrap(57).keep_words(true)) .to_string(); assert_eq!( table, static_table!( "| ver | published_d | is_act | major_feature |" "| sio | ate | ive | |" "| n | | | |" "|-----|-------------|--------|--------------------------|" "| 0.2 | 2021-06-23 | true | #[header(inline)] |" "| .1 | | | attribute |" "| 0.2 | 2021-06-19 | false | API changes |" "| .0 | | | |" "| 0.1 | 2021-06-07 | false | display_with attribute |" "| .4 | | | |" ) ); assert_width!(table, 57); } #[cfg(feature = "ansi")] #[test] fn wrapping_as_total_multiline_color() { use testing_table::assert_table; #[derive(Tabled)] struct D( #[tabled(rename = "version")] String, #[tabled(rename = "published_date")] String, #[tabled(rename = "is_active")] String, #[tabled(rename = "major_feature")] String, ); let data = vec![ D( Color::FG_RED.colorize("0.2.1"), (Color::FG_RED | Color::rgb_bg(8, 10, 30)).colorize("2021-06-23"), "true".to_string(), (Color::FG_BLUE | Color::BG_GREEN).colorize("#[header(inline)] attribute"), ), D( Color::FG_RED.colorize("0.2.0"), (Color::FG_GREEN | Color::rgb_bg(8, 100, 30)).colorize("2021-06-19"), "false".to_string(), Color::FG_YELLOW.colorize("API changes"), ), D( Color::FG_WHITE.colorize("0.1.4"), (Color::FG_RED | Color::rgb_bg(8, 10, 30)).colorize("2021-06-07"), "false".to_string(), (Color::FG_RED | Color::BG_BLACK).colorize("display_with attribute"), ), ]; let table = Matrix::iter(&data) .with(Style::markdown()) .with(Alignment::left()) .with(Width::wrap(57)) .to_string(); assert_eq!( table, static_table!( "| ver | published_d | is_act | major_feature |" "| sio | ate | ive | |" "| n | | | |" "|-----|-------------|--------|--------------------------|" "| \u{1b}[31m0.2\u{1b}[39m | \u{1b}[31m\u{1b}[48;2;8;10;30m2021-06-23\u{1b}[39m\u{1b}[49m | true | \u{1b}[34m\u{1b}[42m#[header(inline)] attrib\u{1b}[39m\u{1b}[49m |" "| \u{1b}[31m.1\u{1b}[39m | | | \u{1b}[34m\u{1b}[42mute\u{1b}[39m\u{1b}[49m |" "| \u{1b}[31m0.2\u{1b}[39m | \u{1b}[32m\u{1b}[48;2;8;100;30m2021-06-19\u{1b}[39m\u{1b}[49m | false | \u{1b}[33mAPI changes\u{1b}[39m |" "| \u{1b}[31m.0\u{1b}[39m | | | |" "| \u{1b}[37m0.1\u{1b}[39m | \u{1b}[31m\u{1b}[48;2;8;10;30m2021-06-07\u{1b}[39m\u{1b}[49m | false | \u{1b}[31m\u{1b}[40mdisplay_with attribute\u{1b}[39m\u{1b}[49m |" "| \u{1b}[37m.4\u{1b}[39m | | | |" ) ); assert_eq!(get_text_width(&table), 57); let table = Matrix::iter(&data) .with(Style::markdown()) .with(Modify::new(Segment::all()).with(Alignment::left())) .with(Width::wrap(57).keep_words(true)) .to_string(); assert_table!( table, "| ver | published_d | is_act | major_feature |" "| sio | ate | ive | |" "| n | | | |" "|-----|-------------|--------|--------------------------|" "| \u{1b}[31m0.2\u{1b}[39m | \u{1b}[31m\u{1b}[48;2;8;10;30m2021-06-23\u{1b}[39m\u{1b}[49m | true | \u{1b}[34m\u{1b}[42m#[header(inline)] \u{1b}[39m\u{1b}[49m |" "| \u{1b}[31m.1\u{1b}[39m | | | \u{1b}[34m\u{1b}[42mattribute\u{1b}[39m\u{1b}[49m |" "| \u{1b}[31m0.2\u{1b}[39m | \u{1b}[32m\u{1b}[48;2;8;100;30m2021-06-19\u{1b}[39m\u{1b}[49m | false | \u{1b}[33mAPI changes\u{1b}[39m |" "| \u{1b}[31m.0\u{1b}[39m | | | |" "| \u{1b}[37m0.1\u{1b}[39m | \u{1b}[31m\u{1b}[48;2;8;10;30m2021-06-07\u{1b}[39m\u{1b}[49m | false | \u{1b}[31m\u{1b}[40mdisplay_with attribute\u{1b}[39m\u{1b}[49m |" "| \u{1b}[37m.4\u{1b}[39m | | | |" ); assert_eq!(get_text_width(&table), 57); } #[cfg(feature = "ansi")] #[test] fn truncating_as_total_multiline_color() { #[derive(Tabled)] struct D( #[tabled(rename = "version")] String, #[tabled(rename = "published_date")] String, #[tabled(rename = "is_active")] String, #[tabled(rename = "major_feature")] String, ); let data = vec![ D( Color::FG_RED.colorize("0.2.1"), (Color::FG_RED | Color::rgb_bg(8, 10, 30)).colorize("2021-06-23"), "true".to_string(), (Color::FG_BLUE | Color::BG_GREEN).colorize("#[header(inline)] attribute"), ), D( Color::FG_RED.colorize("0.2.0"), (Color::FG_GREEN | Color::rgb_bg(8, 100, 30)).colorize("2021-06-19"), "false".to_string(), Color::FG_YELLOW.colorize("API changes"), ), D( Color::FG_WHITE.colorize("0.1.4"), (Color::FG_RED | Color::rgb_bg(8, 10, 30)).colorize("2021-06-07"), "false".to_string(), (Color::FG_RED | Color::BG_BLACK).colorize("display_with attribute"), ), ]; let table = Matrix::iter(data) .with(Style::markdown()) .with(Modify::new(Segment::all()).with(Alignment::left())) .with(Width::truncate(57)) .to_string(); assert_eq!( ansi_str::AnsiStr::ansi_strip(&table), static_table!( "| ver | published_d | is_act | major_feature |" "|-----|-------------|--------|--------------------------|" "| 0.2 | 2021-06-23 | true | #[header(inline)] attrib |" "| 0.2 | 2021-06-19 | false | API changes |" "| 0.1 | 2021-06-07 | false | display_with attribute |" ) ); assert_eq!( table, "| ver | published_d | is_act | major_feature |\n|-----|-------------|--------|--------------------------|\n| \u{1b}[31m0.2\u{1b}[39m | \u{1b}[31m\u{1b}[48;2;8;10;30m2021-06-23\u{1b}[39m\u{1b}[49m | true | \u{1b}[34m\u{1b}[42m#[header(inline)] attrib\u{1b}[39m\u{1b}[49m |\n| \u{1b}[31m0.2\u{1b}[39m | \u{1b}[32m\u{1b}[48;2;8;100;30m2021-06-19\u{1b}[39m\u{1b}[49m | false | \u{1b}[33mAPI changes\u{1b}[39m |\n| \u{1b}[37m0.1\u{1b}[39m | \u{1b}[31m\u{1b}[48;2;8;10;30m2021-06-07\u{1b}[39m\u{1b}[49m | false | \u{1b}[31m\u{1b}[40mdisplay_with attribute\u{1b}[39m\u{1b}[49m |" ); assert_eq!(get_text_width(&table), 57); } #[cfg(feature = "ansi")] fn format_osc8_hyperlink(url: &str, text: &str) -> String { format!( "{osc}8;;{url}{st}{text}{osc}8;;{st}", url = url, text = text, osc = "\x1b]", st = "\x1b\\" ) } #[cfg(feature = "ansi")] #[test] fn hyperlinks() { #[derive(Tabled)] struct Distribution { name: String, is_hyperlink: bool, } let table = |text: &str| { let data = [Distribution { name: text.to_owned(), is_hyperlink: true, }]; tabled::Table::new(data) .with( Modify::new(Segment::all()) .with(Width::wrap(5).keep_words(true)) .with(Alignment::left()), ) .to_string() }; let text = format_osc8_hyperlink("https://www.debian.org/", "Debian"); assert_eq!( table(&text), "+-------+-------+\n\ | name | is_hy |\n\ | | perli |\n\ | | nk |\n\ +-------+-------+\n\ | \u{1b}]8;;https://www.debian.org/\u{1b}\\Debia\u{1b}]8;;\u{1b}\\ | true |\n\ | \u{1b}]8;;https://www.debian.org/\u{1b}\\n\u{1b}]8;;\u{1b}\\ | |\n\ +-------+-------+" ); // if there's more text than a link it will be ignored let text = format!( "{} :link", format_osc8_hyperlink("https://www.debian.org/", "Debian"), ); assert_eq!( table(&text), "+-------+-------+\n\ | name | is_hy |\n\ | | perli |\n\ | | nk |\n\ +-------+-------+\n\ | Debia | true |\n\ | n | |\n\ | :link | |\n\ +-------+-------+" ); let text = format!( "asd {} 2 links in a string {}", format_osc8_hyperlink("https://www.debian.org/", "Debian"), format_osc8_hyperlink("https://www.wikipedia.org/", "Debian"), ); assert_eq!( table(&text), static_table!( "+-------+-------+" "| name | is_hy |" "| | perli |" "| | nk |" "+-------+-------+" "| asd D | true |" "| ebian | |" "| 2 | |" "| links | |" "| in a | |" "| stri | |" "| ng De | |" "| bian | |" "+-------+-------+" ) ); } #[cfg(feature = "ansi")] #[test] fn hyperlinks_with_color() { #[derive(Tabled)] struct Distribution { name: String, is_hyperlink: bool, } let table = |text: &str| { let data = [Distribution { name: text.to_owned(), is_hyperlink: true, }]; tabled::Table::new(data) .with( Modify::new(Segment::all()) .with(Width::wrap(6).keep_words(true)) .with(Alignment::left()), ) .to_string() }; let text = format_osc8_hyperlink("https://www.debian.org/", &Color::FG_RED.colorize("Debian")); assert_eq!( table(&text), static_table!( "+--------+--------+" "| name | is_hyp |" "| | erlink |" "+--------+--------+" "| \u{1b}]8;;https://www.debian.org/\u{1b}\\\u{1b}[31mDebian\u{1b}[39m\u{1b}]8;;\u{1b}\\ | true |" "+--------+--------+" ) ); // if there's more text than a link it will be ignored let text = format!( "{} :link", format_osc8_hyperlink("https://www.debian.org/", "Debian"), ); assert_eq!( table(&text), static_table!( "+--------+--------+" "| name | is_hyp |" "| | erlink |" "+--------+--------+" "| Debian | true |" "| :link | |" "+--------+--------+" ) ); let text = format!( "asd {} 2 links in a string {}", format_osc8_hyperlink("https://www.debian.org/", "Debian"), format_osc8_hyperlink("https://www.wikipedia.org/", "Debian"), ); assert_eq!( table(&text), static_table!( "+--------+--------+" "| name | is_hyp |" "| | erlink |" "+--------+--------+" "| asd | true |" "| Debian | |" "| 2 | |" "| links | |" "| in a | |" "| string | |" "| | |" "| Debian | |" "+--------+--------+" ) ); } }