tabled-0.14.0/.cargo_vcs_info.json0000644000000001440000000000100123500ustar { "git": { "sha1": "22cf27341116b8ac30d98ea9b84dca6ee21dfe83" }, "path_in_vcs": "tabled" }tabled-0.14.0/Cargo.lock0000644000000124630000000000100103320ustar # This file is automatically @generated by Cargo. # It is not intended for manual editing. version = 3 [[package]] name = "ansi-str" version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1cf4578926a981ab0ca955dc023541d19de37112bc24c1a197bd806d3d86ad1d" dependencies = [ "ansitok", ] [[package]] name = "ansitok" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "220044e6a1bb31ddee4e3db724d29767f352de47445a6cd75e1a173142136c83" dependencies = [ "nom", "vte", ] [[package]] name = "arrayvec" version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b" [[package]] name = "bytecount" version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2c676a478f63e9fa2dd5368a42f28bba0d6c560b775f38583c8bbaa7fcd67c9c" [[package]] name = "fnv" version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" [[package]] name = "heck" version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" [[package]] name = "memchr" version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" [[package]] name = "minimal-lexical" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" [[package]] name = "nom" version = "7.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" dependencies = [ "memchr", "minimal-lexical", ] [[package]] name = "owo-colors" version = "3.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c1b04fb49957986fdce4d6ee7a65027d55d4b6d2265e5848bbb507b58ccfdb6f" [[package]] name = "papergrid" version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2ccbe15f2b6db62f9a9871642746427e297b0ceb85f9a7f1ee5ff47d184d0c8" dependencies = [ "ansi-str", "ansitok", "bytecount", "fnv", "unicode-width", ] [[package]] name = "proc-macro-error" version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" dependencies = [ "proc-macro-error-attr", "proc-macro2", "quote", "syn", "version_check", ] [[package]] name = "proc-macro-error-attr" version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" dependencies = [ "proc-macro2", "quote", "version_check", ] [[package]] name = "proc-macro2" version = "1.0.56" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b63bdb0cd06f1f4dedf69b254734f9b45af66e4a031e42a7480257d9898b435" dependencies = [ "unicode-ident", ] [[package]] name = "quote" version = "1.0.26" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4424af4bf778aae2051a77b60283332f386554255d722233d09fbfc7e30da2fc" dependencies = [ "proc-macro2", ] [[package]] name = "syn" version = "1.0.109" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] [[package]] name = "tabled" version = "0.14.0" dependencies = [ "ansi-str", "ansitok", "owo-colors", "papergrid", "tabled_derive", "unicode-width", ] [[package]] name = "tabled_derive" version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "99f688a08b54f4f02f0a3c382aefdb7884d3d69609f785bd253dc033243e3fe4" dependencies = [ "heck", "proc-macro-error", "proc-macro2", "quote", "syn", ] [[package]] name = "unicode-ident" version = "1.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e5464a87b239f13a63a501f2701565754bae92d243d4bb7eb12f6d57d2269bf4" [[package]] name = "unicode-width" version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b" [[package]] name = "utf8parse" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" [[package]] name = "version_check" version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" [[package]] name = "vte" version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6cbce692ab4ca2f1f3047fcf732430249c0e971bfdd2b234cf2c47ad93af5983" dependencies = [ "arrayvec", "utf8parse", "vte_generate_state_changes", ] [[package]] name = "vte_generate_state_changes" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d257817081c7dffcdbab24b9e62d2def62e2ff7d00b1c20062551e6cccc145ff" dependencies = [ "proc-macro2", "quote", ] tabled-0.14.0/Cargo.toml0000644000000152470000000000100103600ustar # 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" rust-version = "1.61.0" name = "tabled" version = "0.14.0" authors = ["Maxim Zhiburt "] 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"] [[example]] name = "color" required-features = [ "color", "derive", ] [[example]] name = "colored_borders" required-features = ["derive"] [[example]] name = "colored_padding" path = "examples/colored_padding.rs" required-features = [ "color", "derive", ] [[example]] name = "disable" required-features = ["derive"] [[example]] name = "rename_all" path = "examples/derive/rename_all.rs" required-features = ["derive"] [[example]] name = "rename" path = "examples/derive/rename.rs" required-features = ["derive"] [[example]] name = "order" path = "examples/derive/order.rs" required-features = ["derive"] [[example]] name = "skip" path = "examples/derive/skip.rs" required-features = ["derive"] [[example]] name = "inline" path = "examples/derive/inline.rs" required-features = ["derive"] [[example]] name = "inline_enum" path = "examples/derive/inline_enum.rs" required-features = ["derive"] [[example]] name = "display_with" path = "examples/derive/display_with.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" path = "examples/merge_duplicates.rs" required-features = ["derive"] [[example]] name = "merge_duplicates_2" path = "examples/merge_duplicates_2.rs" required-features = ["derive"] [[example]] name = "hyperlink" path = "examples/hyperlink.rs" required-features = [ "derive", "color", ] [[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 = "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"] [dependencies.ansi-str] version = "0.8" optional = true [dependencies.ansitok] version = "0.2" optional = true [dependencies.papergrid] version = "0.10" default-features = false [dependencies.tabled_derive] version = "0.6" optional = true [dependencies.unicode-width] version = "0.1" [dev-dependencies.owo-colors] version = "3.5" [features] color = [ "papergrid/color", "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.14.0/Cargo.toml.orig000064400000000000000000000144041046102023000140330ustar 00000000000000[package] name = "tabled" version = "0.14.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" rust-version = "1.61.0" [badges] coveralls = { repository = "https://github.com/zhiburt/tabled", branch = "master", service = "github" } maintenance = { status = "actively-developed" } [[example]] name = "color" required-features = ["color", "derive"] [[example]] name = "colored_borders" required-features = ["derive"] [[example]] name = "colored_padding" path = "examples/colored_padding.rs" required-features = ["color", "derive"] [[example]] name = "disable" required-features = ["derive"] [[example]] name = "rename_all" path = "examples/derive/rename_all.rs" required-features = ["derive"] [[example]] name = "rename" path = "examples/derive/rename.rs" required-features = ["derive"] [[example]] name = "order" path = "examples/derive/order.rs" required-features = ["derive"] [[example]] name = "skip" path = "examples/derive/skip.rs" required-features = ["derive"] [[example]] name = "inline" path = "examples/derive/inline.rs" required-features = ["derive"] [[example]] name = "inline_enum" path = "examples/derive/inline_enum.rs" required-features = ["derive"] [[example]] name = "display_with" path = "examples/derive/display_with.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" path = "examples/merge_duplicates.rs" required-features = ["derive"] [[example]] name = "merge_duplicates_2" path = "examples/merge_duplicates_2.rs" required-features = ["derive"] [[example]] name = "hyperlink" path = "examples/hyperlink.rs" required-features = ["derive", "color"] [[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 = "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"] [features] default = ["derive", "macros"] std = ["papergrid/std"] derive = ["tabled_derive", "std"] color = ["papergrid/color", "ansi-str", "ansitok", "std"] macros = ["std"] [dependencies] papergrid = { version = "0.10", default-features = false } tabled_derive = { version = "0.6", optional = true } ansi-str = { version = "0.8", optional = true } ansitok = { version = "0.2", optional = true } unicode-width = "0.1" [dev-dependencies] owo-colors = "3.5" testing_table = { path = "../testing_table", features = ["color"] } # 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.14.0/LICENSE-MIT000064400000000000000000000020561046102023000126000ustar 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.14.0/README.md000064400000000000000000000070221046102023000124210ustar 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}; #[derive(Tabled)] struct Language { name: String, designed_by: String, invented_year: usize, } impl Language { fn new(name: &str, designed_by: &str, invented_year: usize) -> Self { Self { name: name.to_string(), designed_by: designed_by.to_string(), invented_year, } } } let languages = vec![ Language::new("C", "Dennis Ritchie", 1972), Language::new("Go", "Rob Pike", 2009), Language::new("Rust", "Graydon Hoare", 2010), Language::new("Hare", "Drew DeVault", 2022), ]; let table = Table::new(languages).to_string(); assert_eq!( table, "+------+----------------+---------------+\n\ | name | designed_by | invented_year |\n\ +------+----------------+---------------+\n\ | C | Dennis Ritchie | 1972 |\n\ +------+----------------+---------------+\n\ | Go | Rob Pike | 2009 |\n\ +------+----------------+---------------+\n\ | Rust | Graydon Hoare | 2010 |\n\ +------+----------------+---------------+\n\ | Hare | Drew DeVault | 2022 |\n\ +------+----------------+---------------+" ); ``` The same example but we are building a table step by step. ```rust use tabled::{builder::Builder, settings::Style}; 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 table = builder.build() .with(Style::ascii_rounded()) .to_string(); assert_eq!( table, concat!( ".------------------------------.\n", "| C | Dennis Ritchie | 1972 |\n", "| Go | Rob Pike | 2009 |\n", "| Rust | Graydon Hoare | 2010 |\n", "| Hare | Drew DeVault | 2022 |\n", "'------------------------------'" ) ); ```tabled-0.14.0/examples/README.md000064400000000000000000001426061046102023000142470ustar 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/ | +--------------+------------------------+---------------------------+--------------------------+ | destribution | 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.14.0/examples/alphabet.rs000064400000000000000000000006331046102023000151070ustar 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.14.0/examples/border_text.rs000064400000000000000000000026371046102023000156560ustar 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::{BorderText, HorizontalLine, Style}, }, Table, }; fn main() { let data = [[5, 6, 7, 8, 9], [10, 11, 12, 13, 14]]; let table = Table::new(data) .with( Style::modern() .remove_horizontal() .horizontals([HorizontalLine::new(1, Style::modern().get_horizontal())]), ) .with(BorderText::new(" Numbers ").horizontal(Rows::first())) .with(BorderText::new(" More numbers ").horizontal(1)) .with(BorderText::new(" end. ").horizontal(Rows::last())) .to_string(); println!("{table}"); } tabled-0.14.0/examples/builder.rs000064400000000000000000000030501046102023000147510ustar 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::{object::Rows, Modify, Panel, Style, Width}, }; fn main() { let message = r#"The terms "the ocean" or "the sea" used without specification refer to the interconnected body of salt water covering the majority of the Earth's surface"#; let link = r#"https://en.wikipedia.org/wiki/Ocean"#; let oceans = ["Atlantic", "Pacific", "Indian", "Southern", "Arctic"]; let mut builder = Builder::default(); builder.set_header(["#", "Ocean"]); for (i, ocean) in oceans.iter().enumerate() { builder.push_record([i.to_string(), ocean.to_string()]); } let table = builder .build() .with(Panel::header(message)) .with(Panel::header(link)) .with(Panel::horizontal(2, "=".repeat(link.len()))) .with(Modify::new(Rows::single(1)).with(Width::wrap(link.len()))) .with(Style::markdown()) .to_string(); println!("{table}"); } tabled-0.14.0/examples/builder_index.rs000064400000000000000000000025711046102023000161470ustar 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: &'static str, based_on: &'static str, is_active: bool, is_cool: bool, } impl Distribution { fn new(name: &'static str, based_on: &'static str, is_active: bool, is_cool: bool) -> Self { Self { name, based_on, is_active, is_cool, } } } fn main() { let data = [ 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) .name(None) .transpose() .build(); table.with(Style::modern()); println!("{table}"); } tabled-0.14.0/examples/chess.rs000064400000000000000000000023771046102023000144430ustar 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 color_white = Color::BG_WHITE | Color::FG_BLACK; let color_black = Color::FG_WHITE | Color::BG_BLACK; let mut table = Builder::from_iter(board).build(); table .with(Style::empty()) .with(Colorization::chess(color_white, color_black)); println!("{table}"); } tabled-0.14.0/examples/col_row_macros.rs000064400000000000000000000033421046102023000163370ustar 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)]; let not_validated = [ Person::new("Jack Black", 51, false), Person::new("Michelle Goldstein", 44, true), ]; 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 table_a = Table::new(&validated).with(Style::ascii()).to_string(); let table_b = Table::new(¬_validated).with(Style::modern()).to_string(); let table_c = Table::new(&unsure).with(Style::ascii_rounded()).to_string(); let row_table = row![table_c, table_b]; let col_table = col![table_c; 3]; let mut row_col_table = col![row![table_a, table_b].with(Style::empty()), table_c]; row_col_table.with(Alignment::center()); println!("{row_table}\n{col_table}\n{row_col_table}",); } tabled-0.14.0/examples/color.rs000064400000000000000000000041321046102023000144430ustar 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::convert::TryFrom; use owo_colors::OwoColorize; use tabled::{ settings::{ object::{Columns, Rows}, style::{BorderColor, Style}, Color, Format, Modify, }, Table, Tabled, }; #[derive(Tabled)] struct Bsd { distribution: &'static str, year_of_first_release: usize, is_active: bool, } impl Bsd { fn new(distribution: &'static str, year_of_first_release: usize, is_active: bool) -> Self { Self { distribution, 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 red = Format::content(|s| s.red().on_bright_white().to_string()); let blue = Format::content(|s| s.blue().to_string()); let green = Format::content(|s| s.green().to_string()); let color_red = Color::try_from(' '.red().to_string()).unwrap(); let color_purple = Color::try_from(' '.purple().to_string()).unwrap(); let yellow_color = Color::try_from(' '.yellow().to_string()).unwrap(); let first_row_style = Modify::new(Rows::first()).with( BorderColor::default() .bottom(color_red) .corner_bottom_left(color_purple.clone()) .corner_bottom_right(color_purple), ); let mut table = Table::new(data); table .with(Style::psql()) .with(yellow_color) .with(first_row_style) .with(Modify::new(Columns::single(0)).with(red)) .with(Modify::new(Columns::single(1)).with(green)) .with(Modify::new(Columns::single(2)).with(blue)); println!("{table}"); } tabled-0.14.0/examples/colored_borders.rs000064400000000000000000000040351046102023000164760ustar 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`] containts 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::{RawStyle, Style}, Color, }, 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, } } } fn main() { let mut style = RawStyle::from(Style::extended()); style .set_color_top(Color::FG_RED) .set_color_bottom(Color::FG_CYAN) .set_color_left(Color::FG_BLUE) .set_color_right(Color::FG_GREEN) .set_color_corner_top_left(Color::FG_BLUE) .set_color_corner_top_right(Color::FG_RED) .set_color_corner_bottom_left(Color::FG_CYAN) .set_color_corner_bottom_right(Color::FG_GREEN) .set_color_intersection_bottom(Color::FG_CYAN) .set_color_intersection_top(Color::FG_RED) .set_color_intersection_right(Color::FG_GREEN) .set_color_intersection_left(Color::FG_BLUE) .set_color_intersection(Color::FG_MAGENTA) .set_color_horizontal(Color::FG_MAGENTA) .set_color_vertical(Color::FG_MAGENTA); 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 table = Table::new(data).with(style).to_string(); println!("{table}"); } tabled-0.14.0/examples/colored_padding.rs000064400000000000000000000120061046102023000164410ustar 00000000000000//! This example demonstrates using the [`Padding::colorize()`] function in several ways //! to give a [`Table`] display a vibrant asthetic. //! //! * 🚩 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 std::convert::TryFrom; use owo_colors::OwoColorize; use tabled::{ grid::{ config::{ColoredConfig, Entity}, dimension::SpannedGridDimension, records::{ vec_records::{Cell, VecRecords}, ExactRecords, PeekableRecords, Records, }, util::string::string_width_multiline, }, settings::{ object::{Columns, Object, Rows, Segment}, Alignment, CellOption, Color, Format, Margin, Modify, Padding, Style, }, Table, Tabled, }; #[derive(Tabled)] #[tabled(rename_all = "PascalCase")] struct Fundamental { quantity: &'static str, symbol: &'static str, value: &'static str, unit: &'static str, } impl Fundamental { fn new( quantity: &'static str, symbol: &'static str, value: &'static str, unit: &'static str, ) -> Self { Self { quantity, symbol, value, unit, } } } fn main() { // data source: https://www.britannica.com/science/physical-constant 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::try_from(' '.bg_rgb::<220, 220, 220>().to_string()).unwrap(); let border_color = Color::try_from(' '.bg_rgb::<200, 200, 220>().bold().to_string()).unwrap(); let data_color = Color::try_from(' '.bg_rgb::<200, 200, 220>().to_string()).unwrap(); let header_settings = Modify::new(Rows::first()) .with(Padding::new(1, 1, 2, 2).colorize( Color::BG_GREEN, Color::BG_YELLOW, Color::BG_MAGENTA, Color::BG_CYAN, )) .with(MakeMaxPadding) .with(Format::content(|s| s.on_black().white().to_string())); let data_settings = Modify::new(Rows::first().inverse()) .with(Alignment::left()) .with(MakeMaxPadding) .with(Padding::new(1, 1, 0, 0).colorize( Color::default(), Color::default(), data_color.clone(), data_color.clone(), )); let symbol_settings = Modify::new(Columns::single(1).not(Rows::first())) .with(Format::content(|s| s.bold().to_string())); let unit_settings = Modify::new(Columns::single(3).not(Rows::first())) .with(Format::content(|s| s.italic().to_string())); let table = Table::new(data) .with(Style::rounded()) .with(Margin::new(1, 2, 1, 1).colorize( pane_color.clone(), pane_color.clone(), pane_color.clone(), pane_color, )) .with(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"); } #[derive(Debug, Clone)] struct MakeMaxPadding; impl CellOption, ColoredConfig> for MakeMaxPadding where T: Cell + AsRef, { fn change(self, records: &mut VecRecords, cfg: &mut ColoredConfig, entity: Entity) { let widths = SpannedGridDimension::width(&*records, cfg); let count_rows = records.count_rows(); let count_cols = records.count_columns(); for (row, col) in entity.iter(count_rows, count_cols) { let column_width = widths[col]; let text = records.get_text((row, col)); let width = string_width_multiline(text); if width < column_width { let available_width = column_width - width; let left = available_width / 2; let right = available_width - left; let pos = (row, col).into(); let mut pad = cfg.get_padding(pos); pad.left.size = left; pad.right.size = right; cfg.set_padding(pos, pad); } } } } tabled-0.14.0/examples/colorization.rs000064400000000000000000000041601046102023000160420ustar 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::{ builder::Builder, 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 = Builder::from(vec![vec![ String::from(""), String::from(""), String::from("TOTAL"), total.to_string(), ]]) .build(); let color_data_primary = Color::BG_WHITE | Color::FG_BLACK; let color_data_second = Color::BG_BRIGHT_WHITE | Color::FG_BLACK; let color_head = Color::BOLD | Color::BG_CYAN | Color::FG_BLACK; let color_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([color_data_primary, color_data_second])) .with(Colorization::exact([color_head], Rows::first())) .with(Colorization::exact([color_footer], Rows::last())); println!("{table}"); } tabled-0.14.0/examples/column_names.rs000064400000000000000000000027511046102023000160120ustar 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::{ grid::config::AlignmentHorizontal, settings::{themes::ColumnNames, Color, Style}, 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() { 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()).with( ColumnNames::default() .set_color(Color::BOLD | Color::BG_BLUE | Color::FG_WHITE) .set_alignment(AlignmentHorizontal::Center), ); println!("{table}"); } tabled-0.14.0/examples/compact_table.rs000064400000000000000000000012151046102023000161210ustar 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. use tabled::{settings::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.14.0/examples/compact_table_2.rs000064400000000000000000000011701046102023000163420ustar 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). use tabled::{settings::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.14.0/examples/compact_table_3.rs000064400000000000000000000010141046102023000163400ustar 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. use tabled::{settings::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.14.0/examples/concat.rs000064400000000000000000000027011046102023000145740ustar 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::{object::Segment, Alignment, Concat, Modify, 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(Modify::new(Segment::all()).with(Alignment::left())); println!("{weather_table}"); } tabled-0.14.0/examples/custom_style.rs000064400000000000000000000030631046102023000160610ustar 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: &'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, } } } 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() .remove_horizontal() .remove_vertical() .horizontals([HorizontalLine::new(1, Style::modern().get_horizontal()).intersection(None)]) .verticals([VerticalLine::new(1, Style::modern().get_vertical())]); let mut table = Table::new(data); table.with(theme).with(Alignment::left()); println!("{table}"); } tabled-0.14.0/examples/derive/display_with.rs000064400000000000000000000037061046102023000173110ustar 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 std::borrow::Cow; use tabled::{Table, Tabled}; #[derive(Tabled)] #[tabled(rename_all = "camelCase")] struct Country { name: &'static str, capital_city: &'static str, #[tabled(display_with("display_perimeter", self))] surface_area_km2: f32, #[tabled(display_with = "str::to_lowercase")] national_currency: &'static str, national_currency_short: &'static str, } fn display_perimeter(country: &Country) -> Cow<'_, str> { if country.surface_area_km2 > 1_000_000.0 { "Very Big Land".into() } else { "Big Land".into() } } impl Country { fn new( name: &'static str, national_currency: &'static str, national_currency_short: &'static str, capital_city: &'static str, surface_area_km2: f32, ) -> Self { Self { name, national_currency, national_currency_short, capital_city, surface_area_km2, } } } fn main() { let data = [ Country::new("Afghanistan", "Afghani", "AFN", "Kabul", 652867.0), Country::new("Angola", "Kwanza", "AOA", "Luanda", 1246700.0), Country::new("Canada", "Canadian Dollar", "CAD", "Ottawa", 9984670.0), ]; let table = Table::new(data); println!("{table}"); } tabled-0.14.0/examples/derive/inline.rs000064400000000000000000000027451046102023000160710ustar 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: &'static str, capital_city: &'static str, surface_area_km2: f32, #[tabled(inline)] currency: Currency, } #[derive(Tabled)] struct Currency { str: &'static str, short: &'static str, } impl Country { fn new( name: &'static str, national_currency: &'static str, national_currency_short: &'static str, capital_city: &'static str, surface_area_km2: f32, ) -> Self { Self { name, capital_city, surface_area_km2, currency: Currency { str: national_currency, short: national_currency_short, }, } } } fn main() { let data = [ Country::new("Afghanistan", "Afghani", "AFN", "Kabul", 652867.0), Country::new("Angola", "Kwanza", "AOA", "Luanda", 1246700.0), Country::new("Canada", "Canadian Dollar", "CAD", "Ottawa", 9984670.0), ]; let table = Table::new(data); println!("{table}"); } tabled-0.14.0/examples/derive/inline_enum.rs000064400000000000000000000026021046102023000171050ustar 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 repetative 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::"))] Telegram { username: &'static str, #[tabled(inline("telegram::"))] number: Number, }, #[tabled(inline)] Local(#[tabled(inline("local::"))] Number), } #[derive(Tabled)] struct Number { number: &'static str, code: usize, } impl Number { fn new(number: &'static str, code: usize) -> Self { Self { number, code } } } fn main() { let data = [ Contact::Local(Number::new("654321", 123)), Contact::Telegram { username: "no2Presley", number: Number::new("123456", 123), }, ]; let table = Table::new(data); println!("{table}"); } tabled-0.14.0/examples/derive/order.rs000064400000000000000000000025061046102023000157210ustar 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: &'static str, capital_city: &'static str, surface_area_km2: f32, #[tabled(order = 1)] national_currency: &'static str, #[tabled(order = 2)] national_currency_short: &'static str, } impl Country { fn new( name: &'static str, national_currency: &'static str, national_currency_short: &'static str, capital_city: &'static str, surface_area_km2: f32, ) -> Self { Self { name, national_currency, national_currency_short, capital_city, surface_area_km2, } } } fn main() { let data = [ Country::new("Afghanistan", "Afghani", "AFN", "Kabul", 652867.0), Country::new("Angola", "Kwanza", "AOA", "Luanda", 1246700.0), Country::new("Canada", "Canadian Dollar", "CAD", "Ottawa", 9984670.0), ]; let table = Table::new(data); println!("{table}"); } tabled-0.14.0/examples/derive/rename.rs000064400000000000000000000023151046102023000160530ustar 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: &'static str, capital_city: &'static str, surface_area_km2: f32, #[tabled(rename = "Currency")] national_currency: &'static str, #[tabled(rename = "Currency-ISO")] national_currency_short: &'static str, } impl Country { fn new( name: &'static str, national_currency: &'static str, national_currency_short: &'static str, capital_city: &'static str, surface_area_km2: f32, ) -> Self { Self { name, national_currency, national_currency_short, capital_city, surface_area_km2, } } } fn main() { let data = [ Country::new("Afghanistan", "Afghani", "AFN", "Kabul", 652867.0), Country::new("Angola", "Kwanza", "AOA", "Luanda", 1246700.0), Country::new("Canada", "Canadian Dollar", "CAD", "Ottawa", 9984670.0), ]; let table = Table::new(data); println!("{table}"); } tabled-0.14.0/examples/derive/rename_all.rs000064400000000000000000000027651046102023000167140ustar 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: &'static str, capital_city: &'static str, surface_area_km2: f32, #[tabled(rename_all = "kebab-case")] national_currency: &'static str, #[tabled(rename_all = "kebab-case")] national_currency_short: &'static str, } impl Country { fn new( name: &'static str, national_currency: &'static str, national_currency_short: &'static str, capital_city: &'static str, surface_area_km2: f32, ) -> Self { Self { name, national_currency, national_currency_short, capital_city, surface_area_km2, } } } fn main() { let data = [ Country::new("Afghanistan", "Afghani", "AFN", "Kabul", 652867.0), Country::new("Angola", "Kwanza", "AOA", "Luanda", 1246700.0), Country::new("Canada", "Canadian Dollar", "CAD", "Ottawa", 9984670.0), ]; let table = Table::new(data); println!("{table}"); } tabled-0.14.0/examples/derive/skip.rs000064400000000000000000000026121046102023000155520ustar 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)]`. use tabled::{Table, Tabled}; #[allow(dead_code)] #[derive(Tabled)] struct Country { name: &'static str, capital_city: &'static str, #[tabled(skip)] surface_area_km2: f32, national_currency: &'static str, #[tabled(skip)] national_currency_short: &'static str, } impl Country { fn new( name: &'static str, national_currency: &'static str, national_currency_short: &'static str, capital_city: &'static str, surface_area_km2: f32, ) -> Self { Self { name, national_currency, national_currency_short, capital_city, surface_area_km2, } } } fn main() { let data = [ Country::new("Afghanistan", "Afghani", "AFN", "Kabul", 652867.0), Country::new("Angola", "Kwanza", "AOA", "Luanda", 1246700.0), Country::new("Canada", "Canadian Dollar", "CAD", "Ottawa", 9984670.0), ]; let table = Table::new(data); println!("{table}"); } tabled-0.14.0/examples/disable.rs000064400000000000000000000024021046102023000147260ustar 00000000000000//! This example demonstrates using the [`Disable`] [`TableOption`] to remove specific //! cell data from a [`Table`] display. //! //! * ⚠️ Using [`Disable`] in combination with other [`Style`] customizations may yield unexpected results. //! It is safest to use [`Disable`] last in a chain of alterations. use tabled::{ settings::{ locator::ByColumnName, style::{Border, Style}, Disable, Modify, }, Table, Tabled, }; #[derive(Tabled)] struct Distribution { name: &'static str, based_on: &'static str, is_active: bool, is_cool: bool, } impl Distribution { fn new(name: &'static str, based_on: &'static str, is_active: bool, is_cool: bool) -> Self { Self { name, based_on, 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(Style::markdown()) .with(Disable::column(ByColumnName::new("is_active"))) .with(Modify::new(ByColumnName::new("name")).with(Border::filled('#'))); println!("{table}"); } tabled-0.14.0/examples/extended_display.rs000064400000000000000000000015111046102023000166500ustar 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: &'static str, based_on: &'static str, is_active: bool, is_cool: bool, } impl Distribution { fn new(name: &'static str, based_on: &'static str, is_active: bool, is_cool: bool) -> Self { Self { name, based_on, 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.14.0/examples/extract.rs000064400000000000000000000050031046102023000147750ustar 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 std::fmt::{Display, Formatter}; use tabled::{ settings::{ object::{Columns, Rows}, Alignment, Extract, Format, Modify, Style, }, Table, Tabled, }; #[derive(Tabled)] struct Album { artist: &'static str, name: &'static str, released: &'static str, level_of_greatness: LevelOfGreatness, } impl Album { fn new( artist: &'static str, name: &'static str, released: &'static str, level_of_greatness: LevelOfGreatness, ) -> Self { Self { artist, name, released, level_of_greatness, } } } #[derive(Debug)] enum LevelOfGreatness { Supreme, Outstanding, Unparalleled, } impl Display for LevelOfGreatness { fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), std::fmt::Error> { std::fmt::Debug::fmt(&self, f) } } fn main() { use LevelOfGreatness::*; 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"); let mut table = Table::new(data); table .with(Style::modern()) .with(Modify::new(Rows::first()).with(Alignment::center())) .with(Modify::new(Rows::new(1..)).with(Alignment::left())); println!("{table}"); println!("Segment row: (1..=2) column: (1..)"); let table = table.with(Extract::segment(1..=2, 1..)); println!("{table}"); println!("Refinished segment"); let highlight = Format::content(|s| { if s == "Outstanding" { format!("+{s}+") } else { s.to_string() } }); let table = table .with(Style::modern()) .with(Modify::new(Columns::new(1..)).with(highlight)); println!("{table}"); } tabled-0.14.0/examples/format.rs000064400000000000000000000031201046102023000146110ustar 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, Modify, 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 table = Table::new(data) .with(Style::psql()) .with( Modify::new(Rows::first()) .with(Format::positioned(|_, (_, column)| column.to_string())), ) .with( Modify::new(Columns::first().not(Rows::first())) .with(Format::content(|s| format!("{s}..."))), ) .to_string(); println!("{table}"); } tabled-0.14.0/examples/formatting_settings.rs000064400000000000000000000023221046102023000174160ustar 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}, object::Segment, Alignment, Modify, 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(Modify::new(Segment::all()).with(Alignment::center())); println!("A default Alignment settings\n{table}"); table.with(Modify::new(Segment::all()).with(AlignmentStrategy::PerLine)); println!("Per line Alignment strategy\n{table}"); table.with( Modify::new(Segment::all()) .with(AlignmentStrategy::PerCell) .with(TrimStrategy::Both), ); println!("A default Alignment; allowing vertical and horizontal trim\n{table}"); } tabled-0.14.0/examples/grid_colors.rs000064400000000000000000000022341046102023000156340ustar 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, Modify, Style}, Table, Tabled, }; #[derive(Tabled)] struct Bsd { distribution: &'static str, year_of_first_release: usize, is_active: bool, } impl Bsd { fn new(distribution: &'static str, year_of_first_release: usize, is_active: bool) -> Self { Self { distribution, 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()) .with(Modify::new((0, 0)).with(Color::BG_BLUE)) .with(Modify::new((1, 1)).with(Color::BG_GREEN)) .with(Modify::new((2, 2)).with(Color::BG_RED)); println!("{table}"); } tabled-0.14.0/examples/height.rs000064400000000000000000000030051046102023000145730ustar 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::PriorityMax, 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::()) .to_string(); println!("Table decrease height to 4\n"); println!("{table_}"); let table_ = table .clone() .with(Height::limit(0).priority::()) .to_string(); println!("Table decrease height to 0\n"); println!("{table_}"); } tabled-0.14.0/examples/highlight.rs000064400000000000000000000013311046102023000152720ustar 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::{Border, Style}, Highlight, }, Table, }; fn main() { let data = vec![["A", "B", "C"], ["D", "E", "F"], ["G", "H", "I"]]; let table = Table::new(data) .with(Style::modern()) .with(Highlight::new( Rows::first().and(Columns::single(1)), Border::filled('*'), )) .to_string(); println!("{table}"); } tabled-0.14.0/examples/highlight_color.rs000064400000000000000000000014151046102023000164730ustar 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}, style::BorderColor, Color, Highlight, Style, }, Table, }; fn main() { let data = vec![["A", "B", "C"], ["D", "E", "F"], ["G", "H", "I"]]; let table = Table::new(data) .with(Style::modern()) .with(Highlight::colored( Rows::first().and(Columns::single(1)), BorderColor::filled(Color::BG_BRIGHT_BLACK), )) .to_string(); println!("{table}"); } tabled-0.14.0/examples/hyperlink.rs000064400000000000000000000052411046102023000153340ustar 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, Modify, 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()) .with(Modify::new(Segment::all()).with(Width::wrap(16).keep_words())); println!("{table}"); let mut table = Table::new(&data); table .with(Style::ascii_rounded()) .with(Alignment::left()) .with(Modify::new(Segment::all()).with(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.14.0/examples/iter_table.rs000064400000000000000000000020551046102023000154410ustar 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::io::BufRead; use tabled::{settings::Style, tables::IterTable}; fn main() { let path = file!(); let file = std::fs::File::open(path).unwrap(); let reader = std::io::BufReader::new(file); let iterator = reader.lines().enumerate().map(|(i, line)| match line { Ok(line) => [i.to_string(), String::from("ok"), line], Err(err) => [i.to_string(), String::from("error"), err.to_string()], }); let table = IterTable::new(iterator).with(Style::ascii_rounded()); table.build(std::io::stdout()).unwrap(); println!() } tabled-0.14.0/examples/margin.rs000064400000000000000000000010721046102023000146020ustar 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.14.0/examples/matrix.rs000064400000000000000000000014041046102023000146300ustar 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.14.0/examples/merge_duplicates.rs000064400000000000000000000031671046102023000166500ustar 00000000000000//! This example demonstrates using the [`Merge`] [`TableOption`] to clarify //! redundancies in a [`Table`] display. //! //! * Note how repetative 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, }; #[derive(Tabled)] struct DatabaseTable { #[tabled(rename = "db")] db_name: &'static str, #[tabled(rename = "table")] table_name: &'static str, total: usize, } impl DatabaseTable { fn new(db_name: &'static str, table_name: &'static str, total: usize) -> Self { Self { db_name, table_name, total, } } } 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}"); } tabled-0.14.0/examples/merge_duplicates_2.rs000064400000000000000000000044041046102023000170640ustar 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::{ object::{Cell, Columns, Object, Rows}, style::{Border, BorderSpanCorrection, Style}, Merge, Modify, }, Table, Tabled, }; fn main() { let data = [ DatabaseTable::new("database_1", "database_1", "table_1", 10712), DatabaseTable::new("database_1", "database_1", "table_2", 57), DatabaseTable::new("database_1", "database_1", "table_3", 57), DatabaseTable::new("database_2", "", "table_1", 72), DatabaseTable::new("database_2", "", "table_2", 75), DatabaseTable::new("database_3", "database_3", "table_1", 20), DatabaseTable::new("database_3", "", "table_2", 21339), DatabaseTable::new("database_3", "", "table_3", 141723), ]; let mut table = Table::builder(data).index().transpose().build(); config_theme(&mut table); table.with(Merge::horizontal()).with(BorderSpanCorrection); println!("{table}"); } #[derive(Tabled)] struct DatabaseTable { #[tabled(rename = "db")] db_name: &'static str, origin_db: &'static str, #[tabled(rename = "table")] table_name: &'static str, total: usize, } impl DatabaseTable { fn new( db_name: &'static str, origin_db: &'static str, table_name: &'static str, total: usize, ) -> Self { Self { db_name, origin_db, table_name, total, } } } fn config_theme(table: &mut Table) { table .with(Style::rounded().remove_vertical()) .with(Modify::new(Columns::first()).with(Border::default().right('│'))) .with( Modify::new(Cell::new(0, 0)).with( Border::default() .corner_top_right('┬') .corner_bottom_right('┼'), ), ) .with( Modify::new(Columns::first().intersect(Rows::last())) .with(Border::default().corner_bottom_right('┴')), ); } tabled-0.14.0/examples/nested_table.rs000064400000000000000000000067651046102023000157740ustar 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 mut table = Builder::from_iter([ [animal.to_string()], [String::from("▲")], [String::from("|")], [String::from("|")], [duck.to_string()], ]) .build(); table.with(Style::ascii().remove_horizontal()).with( Modify::new(Segment::all()) .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 mut table = Builder::from_iter([ [name.to_string()], [table_fields.to_string()], [table_methods.to_string()], ]) .build(); table .with( Style::ascii() .horizontals([HorizontalLine::new(1, Style::ascii().get_horizontal())]) .remove_horizontal() .remove_vertical(), ) .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.14.0/examples/nested_table_2.rs000064400000000000000000000043451046102023000162050ustar 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}; #[derive(Tabled)] struct Vendor { name: &'static str, #[tabled(display_with = "display_distribution")] main_os: Distribution, #[tabled(display_with = "display_distribution")] switch_os: Distribution, } impl Vendor { fn new(name: &'static str, main_os: Distribution, switch_os: Distribution) -> Self { Self { name, main_os, switch_os, } } } fn display_distribution(d: &Distribution) -> String { Table::new([d]).with(Style::extended()).to_string() } #[derive(Tabled)] struct Distribution { name: &'static str, #[tabled(display_with = "Self::display_based_on")] based_on: Option<&'static str>, is_active: bool, is_cool: bool, } impl Distribution { fn display_based_on(o: &Option<&'static str>) -> String { match o { &Some(s) => s.into(), None => "Independent".into(), } } } impl Distribution { fn new( name: &'static str, based_on: Option<&'static str>, is_active: bool, is_cool: bool, ) -> Self { Self { name, based_on, is_active, is_cool, } } } fn main() { let data = [ Vendor::new( "Azure", Distribution::new("Windows", None, true, true), Distribution::new("Manjaro", Some("Arch"), true, true), ), Vendor::new( "AWS", Distribution::new("Debian", None, true, true), Distribution::new("Arch", None, true, true), ), Vendor::new( "GCP", Distribution::new("Debian", None, true, true), Distribution::new("Arch", None, true, true), ), ]; let table = Table::new(data).with(Style::modern()).to_string(); println!("{table}"); } tabled-0.14.0/examples/nested_table_3.rs000064400000000000000000000031701046102023000162010ustar 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::{Cell, Segment}, Alignment, Border, Extract, Highlight, Modify, Panel, Style, }, 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(Modify::new(Segment::all()).with(Alignment::center())) .to_string(); let issues_table = Table::new(issuers) .with(Panel::header("Issuers")) .with(Modify::new(Segment::all()).with(Alignment::center())) .to_string(); let mut a_welcome_table = Table::new([String::from("Thank You"), committers_table, issues_table]); a_welcome_table .with(Extract::rows(1..)) .with(Style::ascii().remove_horizontal()) .with(Modify::new(Segment::all()).with(Alignment::center())) .with(Highlight::new(Cell::new(0, 0), Border::filled('*'))); println!("{a_welcome_table}"); } tabled-0.14.0/examples/panel.rs000064400000000000000000000033731046102023000144320ustar 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( version: &'static str, published_date: &'static str, is_active: bool, major_feature: &'static str, ) -> Self { Self { version, published_date, is_active, major_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.14.0/examples/pool_table.rs000064400000000000000000000014361046102023000154510ustar 00000000000000//! This example demonstrates a [`PoolTable`] usage. use tabled::{ settings::{Alignment, Style}, 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.14.0/examples/pool_table2.rs000064400000000000000000000007721046102023000155350ustar 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.14.0/examples/rotate.rs000064400000000000000000000016411046102023000146250ustar 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.14.0/examples/settings_list.rs000064400000000000000000000030771046102023000162270ustar 00000000000000//! This example demonstrates using the [`Settings`] [`TableOption`] to array //! [`Table`] configurations in a separate step from instantiation. //! //! * Note how this methodoly can lead to huge performance gains //! with compile-time constants. use tabled::{ settings::{ object::{FirstRow, Rows}, style::On, Alignment, Modify, ModifyList, Padding, Settings, Style, }, 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.14.0/examples/shadow.rs000064400000000000000000000110371046102023000146140ustar 00000000000000//! This example can be run with the following command: //! //! `echo -e -n 'Some text\nIn the box' | cargo run --example shadow` //! //! 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::{io::Read, iter::FromIterator}; use tabled::{ builder::Builder, grid::util::string, row, settings::{ object::Cell, style::{BorderChar, Offset, RawStyle, Style}, Height, Modify, 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 mut buf = String::new(); std::io::stdin().read_to_string(&mut buf).unwrap(); buf } fn create_small_table_list(width_available: usize) -> String { let mut tables = [ create_small_table(Style::modern().into()), create_small_table(Style::extended().into()), create_small_table( Style::modern() .left('║') .right('║') .intersection_left('╟') .intersection_right('╢') .corner_top_right('╖') .corner_top_left('╓') .corner_bottom_right('╜') .corner_bottom_left('╙') .into(), ), create_small_table( 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('╪') .into(), ), ]; 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: RawStyle) -> Table { let mut table = Builder::from_iter(vec![vec![" ", ""], vec![" ", ""]]).build(); table .with(style) .with(Padding::zero()) .with(Height::list([1, 0])); table } fn create_main_table(message: &str) -> Table { let (count_lines, message_width) = string::string_dimension(message); let count_additional_separators = if count_lines > 2 { count_lines - 2 } else { 0 }; let left_table = format!( " ╔═══╗ \n ╚═╦═╝ \n{}═╤══╩══╤\n ├──┬──┤\n └──┴──┘", (0..count_additional_separators) .map(|_| " ║ \n") .collect::() ); let message = if count_lines < 2 { let mut i = count_lines; let mut buf = message.to_string(); while i < 2 { buf.push('\n'); i += 1; } buf } else { message.to_owned() }; 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()) .with( Modify::new(Cell::new(0, 0)) .with(BorderChar::vertical('╞', Offset::Begin(count_lines))), ) .with( Modify::new(Cell::new(0, 2)) .with(BorderChar::vertical('╡', Offset::Begin(count_lines))), ) .with(Shadow::new(2)); table } tabled-0.14.0/examples/span.rs000064400000000000000000000026561046102023000142770ustar 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.14.0/examples/split.rs000064400000000000000000000023601046102023000144610ustar 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.14.0/examples/table.rs000064400000000000000000000035651046102023000144250ustar 00000000000000//! This example demonstrates the fundemental 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, Modify, 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()) .with(Modify::new(Rows::first()).with(Alignment::center())); println!("{table}"); } tabled-0.14.0/examples/table_width.rs000064400000000000000000000023251046102023000156150ustar 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.14.0/examples/table_width_2.rs000064400000000000000000000013531046102023000160360ustar 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, Alignment, Modify, 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()).with( Modify::new(Segment::all()) .with(Width::wrap(30).keep_words()) .with(Alignment::left()), ); println!("{table}"); } tabled-0.14.0/src/builder/index_builder.rs000064400000000000000000000205701046102023000165450ustar 00000000000000use crate::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.set_header(["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>, } 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.set_header(["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.set_header(["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; 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.set_header(["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 >= matrix_count_columns(&self.data) { return self; } self.index = get_column(&mut self.data, column); let name = remove_or_default(&mut self.index, 0); self.name = Some(name); self } /// Transpose index and columns. /// /// # Example /// /// ``` /// use tabled::builder::Builder; /// /// let mut builder = Builder::default(); /// builder.set_header(["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 { let columns = &mut self.data[0]; std::mem::swap(&mut self.index, columns); let columns = self.data.remove(0); make_rows_columns(&mut self.data); self.data.insert(0, columns); self.transposed = !self.transposed; 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 has_header = builder.has_header(); let mut data: Vec> = builder.into(); if !has_header { let count_columns = matrix_count_columns(&data); data.insert(0, build_range_index(count_columns)); } // we exclude first row which contains a header let data_len = data.len().saturating_sub(1); let index = build_range_index(data_len); Self { index, name: None, print_index: true, transposed: false, data, } } } impl From for Builder { fn from(b: IndexBuilder) -> Self { build_index(b) } } fn build_index(mut b: IndexBuilder) -> Builder { if b.index.is_empty() { return Builder::default(); } // add index column if b.print_index { b.index.insert(0, String::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 { b.data.insert(1, vec![name]); } } Builder::from(b.data) } fn build_range_index(n: usize) -> Vec { (0..n).map(|i| i.to_string()).collect() } fn remove_or_default(v: &mut Vec, i: usize) -> T { if v.len() > i { v.remove(i) } else { T::default() } } fn get_column(v: &mut [Vec], col: usize) -> Vec { let mut column = Vec::with_capacity(v.len()); for row in v.iter_mut() { let value = remove_or_default(row, col); column.push(value); } column } fn make_rows_columns(v: &mut Vec>) { let count_columns = matrix_count_columns(v); let mut columns = Vec::with_capacity(count_columns); for _ in 0..count_columns { let column = get_column(v, 0); columns.push(column); } v.clear(); for column in columns { v.push(column); } } fn insert_column(v: &mut [Vec], mut column: Vec, col: usize) { for row in v.iter_mut() { let value = remove_or_default(&mut column, col); row.insert(col, value); } } fn matrix_count_columns(v: &[Vec]) -> usize { v.first().map_or(0, |row| row.len()) } tabled-0.14.0/src/builder/mod.rs000064400000000000000000000077511046102023000145150ustar 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 mut builder = Table::builder(&data); //! builder.clean(); //! //! let table = builder.build() //! .with(Style::modern()) //! .to_string(); //! //! println!("{}", table); //! //! assert_eq!( //! table, //! concat!( //! "┌───────────────────┬────────────────────┬─────────┐\n", //! "│ started_timestamp │ finihsed_timestamp │ 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.14.0/src/builder/table_builder.rs000064400000000000000000000311401046102023000165200ustar 00000000000000use std::iter::FromIterator; use crate::{grid::records::vec_records::CellInfo, 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.set_header(["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 columns row. columns: Option>>, /// A number of columns. count_columns: usize, /// A flag that the rows are not consistent. is_consistent: bool, /// A content of cells which are created in case rows has different length. empty_cell_text: Option, } 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); /// builder.push_record((0..3).map(|i| i.to_string())); /// builder.push_record(["i", "surname", "lastname"]); /// ``` pub fn with_capacity(capacity: usize) -> Self { let mut b = Self::new(); b.data = Vec::with_capacity(capacity); b } /// Sets a [`Table`] header. /// /// ``` /// # use tabled::builder::Builder; /// let mut builder = Builder::default(); /// builder.set_header((0..3).map(|i| i.to_string())); /// ``` pub fn set_header(&mut self, columns: H) -> &mut Self where H: IntoIterator, T: Into, { let list = create_row(columns, self.count_columns); self.update_size(list.len()); self.columns = Some(list); self } /// Sets off a [`Table`] header. /// /// If not set its a nop. /// /// ```rust /// use tabled::Table; /// /// let data = [("Hello", 1u8, false), ("World", 21u8, true)]; /// /// let table = Table::builder(data).build().to_string(); /// /// assert_eq!( /// table, /// "+-------+----+-------+\n\ /// | &str | u8 | bool |\n\ /// +-------+----+-------+\n\ /// | Hello | 1 | false |\n\ /// +-------+----+-------+\n\ /// | World | 21 | true |\n\ /// +-------+----+-------+" /// ); /// /// /// let mut builder = Table::builder(data); /// builder.remove_header(); /// let table = builder.build().to_string(); /// /// assert_eq!( /// table, /// "+-------+----+-------+\n\ /// | Hello | 1 | false |\n\ /// +-------+----+-------+\n\ /// | World | 21 | true |\n\ /// +-------+----+-------+" /// ); /// /// ``` pub fn remove_header(&mut self) -> &mut Self { self.columns = None; self.count_columns = self.get_size(); self } /// 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_default_text("undefined") /// .set_header((0..3).map(|i| i.to_string())) /// .push_record(["i"]); /// ``` pub fn set_default_text(&mut self, text: T) -> &mut Self where T: Into, { self.empty_cell_text = Some(text.into()); self } /// Build creates a [`Table`] instance. /// /// ```rust /// use tabled::builder::Builder; /// /// let mut builder = Builder::default(); /// builder.set_header(["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, row: R) -> &mut Self where R: IntoIterator, T: Into, { let list = create_row(row, self.count_columns); self.update_size(list.len()); self.data.push(list); self } /// Insert a row into a specific position. /// /// # Panics /// /// Panics if `index > count_rows`. pub fn insert_record(&mut self, index: usize, record: R) -> bool where R: IntoIterator, R::Item: Into, { let list = create_row(record, self.count_columns); self.update_size(list.len()); self.data.insert(index, list); true } /// 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) -> &mut Self { self.clean_columns(); self.clean_rows(); self } /// Set a column size. /// /// If it make it lower then it was originally it is considered NOP. pub fn hint_column_size(&mut self, size: usize) -> &mut Self { self.count_columns = size; self.is_consistent = true; self } /// 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. pub fn count_rows(&self) -> usize { self.data.len() } /// Checks whether a builder contains a header set. pub fn has_header(&self) -> bool { self.columns.is_some() } fn clean_columns(&mut self) { let mut i = 0; for col in 0..self.count_columns { let col = col - i; let mut is_empty = true; for row in 0..self.data.len() { let cell = &self.data[row][col]; if !cell.as_ref().is_empty() { is_empty = false; break; } } if is_empty { for row in 0..self.data.len() { let _ = self.data[row].remove(col); } if let Some(columns) = self.columns.as_mut() { if columns.len() > col { let _ = columns.remove(col); } } i += 1; } } self.count_columns -= i; } fn clean_rows(&mut self) { for row in (0..self.data.len()).rev() { let mut is_empty = true; for col in 0..self.count_columns { let cell = &self.data[row][col]; if !cell.as_ref().is_empty() { is_empty = false; break; } } if is_empty { let _ = self.data.remove(row); } if row == 0 { break; } } } fn update_size(&mut self, size: usize) { use std::cmp::Ordering; match size.cmp(&self.count_columns) { Ordering::Less => { if !self.data.is_empty() { self.is_consistent = false; } } Ordering::Greater => { self.count_columns = size; if !self.data.is_empty() || self.columns.is_some() { self.is_consistent = false; } } Ordering::Equal => (), } } fn get_size(&mut self) -> usize { let mut max = self.columns.as_ref().map_or(0, Vec::len); let max_records = self.data.iter().map(Vec::len).max().unwrap_or(0); max = std::cmp::max(max_records, max); max } fn fix_rows(&mut self) { let empty_cell = self.empty_cell_text.to_owned().unwrap_or_default(); let empty = CellInfo::new(empty_cell); if let Some(header) = self.columns.as_mut() { if self.count_columns > header.len() { let count = self.count_columns - header.len(); append_vec(header, empty.clone(), count); } } for row in &mut self.data { if self.count_columns > row.len() { let count = self.count_columns - row.len(); append_vec(row, empty.clone(), count); } } } } impl From for Vec> { fn from(mut builder: Builder) -> Self { if !builder.is_consistent { builder.fix_rows(); } if let Some(columns) = builder.columns { builder.data.insert(0, columns); } builder .data .into_iter() .map(|row| row.into_iter().map(|c| c.into_inner()).collect()) .collect() } } impl From for Vec>> { fn from(mut builder: Builder) -> Self { if !builder.is_consistent { builder.fix_rows(); } if let Some(columns) = builder.columns { builder.data.insert(0, columns); } builder.data } } impl FromIterator for Builder where R: IntoIterator, V: Into, { fn from_iter>(iter: T) -> Self { let mut builder = Self::default(); for row in iter { let _ = builder.push_record(row); } builder } } impl Extend for Builder where D: Into, { fn extend>(&mut self, iter: T) { let _ = self.push_record(iter); } } impl From>> for Builder { fn from(data: Vec>) -> Self { let count_columns = data.get(0).map_or(0, |row| row.len()); let data = data .into_iter() .map(|row| row.into_iter().map(CellInfo::new).collect()) .collect(); Self { data, count_columns, columns: None, is_consistent: false, empty_cell_text: None, } } } impl From>>> for Builder { fn from(data: Vec>>) -> Self { let count_columns = data.get(0).map_or(0, |row| row.len()); Self { data, count_columns, columns: None, is_consistent: false, empty_cell_text: None, } } } fn create_row(row: R, size: usize) -> Vec> where R: IntoIterator, T: Into, { let mut list = Vec::with_capacity(size); for text in row { let text = text.into(); let info = CellInfo::new(text); list.push(info); } list } fn append_vec(v: &mut Vec, value: T, n: usize) { v.extend((0..n).map(|_| value.clone())); } tabled-0.14.0/src/grid/colored_config.rs000064400000000000000000000053601046102023000162030ustar 00000000000000use std::ops::{Deref, DerefMut}; use crate::grid::{ color::AnsiColor, 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: AnsiColor<'static>) -> &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 = AnsiColor<'static>; fn get_color(&self, (row, col): (usize, usize)) -> Option<&Self::Color> { self.0.as_ref().map(|map| map.get(Entity::Cell(row, col))) } } tabled-0.14.0/src/grid/compact_multiline_config.rs000064400000000000000000000143241046102023000202640ustar 00000000000000use crate::grid::color::StaticColor; use crate::grid::config::{ AlignmentHorizontal, AlignmentVertical, Borders, CompactConfig, Indent, Line, Sides, }; /// A [`CompactConfig`] configuration plus vertical alignment. #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] pub struct CompactMultilineConfig { config: CompactConfig, alignment_vertical: AlignmentVertical, formatting: Formatting, } impl CompactMultilineConfig { /// Create a new colored config. pub fn new(config: CompactConfig) -> Self { Self::from(config) } /// Set a horizontal alignment. pub const fn set_alignment_vertical(mut self, alignment: AlignmentVertical) -> Self { self.alignment_vertical = alignment; self } /// Get a alignment horizontal. pub const fn get_alignment_vertical(&self) -> AlignmentVertical { self.alignment_vertical } /// Set grid margin. pub const fn set_margin(mut self, margin: Sides) -> Self { self.config = self.config.set_margin(margin); self } /// Returns a grid margin. pub const fn get_margin(&self) -> &Sides { self.config.get_margin() } /// Set the [`Borders`] value as correct one. pub const fn set_borders(mut self, borders: Borders) -> Self { self.config = self.config.set_borders(borders); self } /// Set the first horizontal line. /// /// It ignores the [`Borders`] horizontal value if set for 1st row. pub const fn set_first_horizontal_line(mut self, line: Line) -> Self { self.config = self.config.set_first_horizontal_line(line); self } /// Set the first horizontal line. /// /// It ignores the [`Borders`] horizontal value if set for 1st row. pub const fn get_first_horizontal_line(&self) -> Option> { self.config.get_first_horizontal_line() } /// 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 const fn set_padding(mut self, padding: Sides) -> Self { self.config = self.config.set_padding(padding); self } /// Get a padding for a given. pub const fn get_padding(&self) -> &Sides { self.config.get_padding() } /// Set a horizontal alignment. pub const fn set_alignment_horizontal(mut self, alignment: AlignmentHorizontal) -> Self { self.config = self.config.set_alignment_horizontal(alignment); self } /// 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 const fn set_borders_color(mut self, borders: Borders) -> Self { self.config = self.config.set_borders_color(borders); self } /// Set colors for a margin. pub const fn set_margin_color(mut self, color: Sides) -> Self { self.config = self.config.set_margin_color(color); self } /// 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 const fn set_padding_color(mut self, color: Sides) -> Self { self.config = self.config.set_padding_color(color); self } /// get a padding color. pub const fn get_padding_color(&self) -> Sides { self.config.get_padding_color() } /// Set formatting. pub const fn set_formatting(mut self, formatting: Formatting) -> Self { self.formatting = formatting; self } /// 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 CompactMultilineConfig { fn from(config: CompactConfig) -> Self { Self { config, alignment_vertical: AlignmentVertical::Top, formatting: Formatting::default(), } } } impl AsRef for CompactMultilineConfig { fn as_ref(&self) -> &CompactConfig { &self.config } } impl AsMut for CompactMultilineConfig { fn as_mut(&mut self) -> &mut CompactConfig { &mut self.config } } #[cfg(feature = "std")] impl From for crate::grid::config::SpannedConfig { fn from(compact: CompactMultilineConfig) -> Self { use crate::grid::config::Entity; let mut cfg = crate::grid::config::SpannedConfig::from(compact.config); cfg.set_alignment_vertical(Entity::Global, compact.alignment_vertical); cfg.set_formatting(Entity::Global, compact.formatting.into()); cfg } } /// Formatting represent a logic of formatting of a cell. #[derive(Debug, Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] pub struct Formatting { /// An setting to allow horizontal trim. pub horizontal_trim: bool, /// An setting to allow vertical trim. pub vertical_trim: bool, /// An setting to allow alignment per line. pub allow_lines_alignment: bool, } impl Formatting { /// Creates a new [`Formatting`] structure. pub fn new(horizontal_trim: bool, vertical_trim: bool, allow_lines_alignment: bool) -> Self { Self { horizontal_trim, vertical_trim, allow_lines_alignment, } } } #[cfg(feature = "std")] impl From for crate::grid::config::Formatting { fn from(val: Formatting) -> Self { crate::grid::config::Formatting { allow_lines_alignment: val.allow_lines_alignment, horizontal_trim: val.horizontal_trim, vertical_trim: val.vertical_trim, } } } tabled-0.14.0/src/grid/dimension/complete_dimension.rs000064400000000000000000000102321046102023000210630ustar 00000000000000use std::borrow::Cow; use crate::grid::{ config::{ColoredConfig, SpannedConfig}, dimension::{Dimension, Estimate, SpannedGridDimension}, records::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<'_> { 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<'_> { fn estimate(&mut self, records: R, cfg: &ColoredConfig) { 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)); } } } } tabled-0.14.0/src/grid/dimension/complete_dimension_vec_records.rs000064400000000000000000000114121046102023000234420ustar 00000000000000use std::borrow::Cow; use papergrid::{ dimension::spanned_vec_records::SpannedVecRecordsDimension, records::vec_records::VecRecords, }; use crate::grid::{ config::{ColoredConfig, SpannedConfig}, dimension::{Dimension, Estimate}, records::vec_records::Cell, }; /// 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.14.0/src/grid/dimension/const_dimension.rs000064400000000000000000000040311046102023000204010ustar 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.14.0/src/grid/dimension/mod.rs000064400000000000000000000020131046102023000157630ustar 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.14.0/src/grid/dimension/peekable_dimension.rs000064400000000000000000000222451046102023000210320ustar 00000000000000use papergrid::records::vec_records::{CellInfo, VecRecords}; use crate::grid::{ config::SpannedConfig, dimension::{Dimension, Estimate}, 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 papergrid::{ config::Position, records::vec_records::{Cell, CellInfo, VecRecords}, }; 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); if !cfg.is_cell_visible(pos) { continue; } let height = cell.count_lines(); let width = cell.width(); let pad = cfg.get_padding(pos.into()); let width = width + pad.left.size + pad.right.size; let height = height + pad.top.size + pad.bottom.size; match cfg.get_column_span(pos) { Some(n) if n > 1 => { 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.0, k.1), *v)) .collect::>(); spans_ordered.sort_unstable_by(|(arow, acol), (brow, bcol)| match arow.cmp(brow) { Ordering::Equal => acol.cmp(bcol), ord => ord, }); for ((row, _), (span, height)) in spans_ordered { adjust_row_range(cfg, height, len, row, row + span, heights); } } fn adjust_row_range( cfg: &SpannedConfig, max_span_height: usize, len: usize, start: usize, end: usize, heights: &mut [usize], ) { let range_height = range_height(cfg, len, start, end, heights); if range_height >= max_span_height { return; } inc_range(heights, max_span_height - range_height, start, end); } fn range_height( cfg: &SpannedConfig, len: usize, start: usize, end: usize, heights: &[usize], ) -> usize { let count_borders = count_horizontal_borders(cfg, len, start, end); let range_height = heights[start..end].iter().sum::(); 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.0, k.1), *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 ((_, col), (span, width)) in spans_ordered { adjust_column_range(cfg, width, len, col, col + span, widths); } } fn adjust_column_range( cfg: &SpannedConfig, max_span_width: usize, len: usize, start: usize, end: usize, widths: &mut [usize], ) { let range_width = range_width(cfg, len, start, end, widths); if range_width >= max_span_width { return; } inc_range(widths, max_span_width - range_width, start, end); } fn 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); 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); 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.14.0/src/grid/dimension/pool_table_dimension.rs000064400000000000000000000020611046102023000213740ustar 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.14.0/src/grid/dimension/static_dimension.rs000064400000000000000000000030201046102023000205370ustar 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.14.0/src/grid/mod.rs000064400000000000000000000026161046102023000140070ustar 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 #[cfg(feature = "std")] mod colored_config; mod compact_multiline_config; pub mod dimension; pub mod records; pub use papergrid::color; pub use papergrid::colors; pub use papergrid::util; pub mod config { //! Module contains a list of configs for varios tables/grids. pub use papergrid::config::{ compact::CompactConfig, AlignmentHorizontal, AlignmentVertical, Border, Borders, Entity, EntityIterator, Indent, Line, Position, Sides, }; #[cfg(feature = "std")] #[cfg_attr(docsrs, doc(cfg(feature = "std")))] pub use papergrid::config::spanned::{ EntityMap, Formatting, HorizontalLine, Offset, SpannedConfig, VerticalLine, }; #[cfg(feature = "std")] #[cfg_attr(docsrs, doc(cfg(feature = "std")))] pub use super::colored_config::{ColorMap, ColoredConfig}; pub use super::compact_multiline_config::CompactMultilineConfig; } 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.14.0/src/grid/records/empty_records.rs000064400000000000000000000016461046102023000175520ustar 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.14.0/src/grid/records/into_records/buf_records.rs000064400000000000000000000121101046102023000216460ustar 00000000000000//! A module contains [`BufRows`] and [`BufColumns`] iterators. //! //! Almoust always they both can be used interchangeably but [`BufRows`] is supposed to be lighter cause it //! does not reads columns. use crate::grid::records::IntoRecords; use super::either_string::EitherString; /// BufRecords inspects [`IntoRecords`] iterator and keeps read data buffered. /// So it can be checking before hand. #[derive(Debug)] pub struct BufRows { iter: I, buf: Vec, } impl BufRows<(), ()> { /// Creates a new [`BufRows`] structure, filling the buffer. pub fn new( records: I, sniff: usize, ) -> BufRows<::IntoIter, I::IterColumns> { let mut buf = vec![]; let mut iter = records.iter_rows().into_iter(); for _ in 0..sniff { match iter.next() { Some(row) => buf.push(row), None => break, } } BufRows { iter, buf } } } impl BufRows { /// Returns a slice of a record buffer. pub fn as_slice(&self) -> &[T] { &self.buf } } impl From> for BufColumns where T: IntoIterator, T::Item: AsRef, { fn from(value: BufRows) -> Self { let buf = value .buf .into_iter() .map(|row| row.into_iter().map(|s| s.as_ref().to_string()).collect()) .collect(); BufColumns { iter: value.iter, buf, } } } impl IntoRecords for BufRows where I: Iterator, T: IntoIterator, T::Item: AsRef, { type Cell = T::Item; type IterColumns = T; type IterRows = BufRowIter; fn iter_rows(self) -> Self::IterRows { BufRowIter { buf: self.buf.into_iter(), iter: self.iter, } } } /// Buffered [`Iterator`]. #[derive(Debug)] pub struct BufRowIter { buf: std::vec::IntoIter, iter: I, } impl Iterator for BufRowIter where I: Iterator, T: IntoIterator, T::Item: AsRef, { type Item = T; fn next(&mut self) -> Option { match self.buf.next() { Some(i) => Some(i), None => self.iter.next(), } } } /// BufRecords inspects [`IntoRecords`] iterator and keeps read data buffered. /// So it can be checking before hand. /// /// In contrast to [`BufRows`] it keeps records by columns. #[derive(Debug)] pub struct BufColumns { iter: I, buf: Vec>, } impl BufColumns<()> { /// Creates new [`BufColumns`] structure, filling the buffer. pub fn new( records: I, sniff: usize, ) -> BufColumns<::IntoIter> { let mut buf = vec![]; let mut iter = records.iter_rows().into_iter(); for _ in 0..sniff { match iter.next() { Some(row) => { let row = row .into_iter() .map(|cell| cell.as_ref().to_string()) .collect::>(); buf.push(row) } None => break, } } BufColumns { iter, buf } } } impl BufColumns { /// Returns a slice of a keeping buffer. pub fn as_slice(&self) -> &[Vec] { &self.buf } } impl IntoRecords for BufColumns where I: Iterator, I::Item: IntoIterator, ::Item: AsRef, { type Cell = EitherString<::Item>; type IterColumns = EitherRowIterator<::IntoIter>; type IterRows = BufColumnIter; fn iter_rows(self) -> Self::IterRows { BufColumnIter { buf: self.buf.into_iter(), iter: self.iter, } } } /// A row iterator for [`BufColumns`] #[derive(Debug)] pub struct BufColumnIter { buf: std::vec::IntoIter>, iter: I, } impl Iterator for BufColumnIter where I: Iterator, I::Item: IntoIterator, ::Item: AsRef, { type Item = EitherRowIterator<::IntoIter>; fn next(&mut self) -> Option { match self.buf.next() { Some(i) => Some(EitherRowIterator::Owned(i.into_iter())), None => self .iter .next() .map(|i| EitherRowIterator::Some(i.into_iter())), } } } /// An iterator over some iterator or allocated buffer. #[derive(Debug)] pub enum EitherRowIterator { /// Allocated iterator. Owned(std::vec::IntoIter), /// Given iterator. Some(I), } impl Iterator for EitherRowIterator where I: Iterator, { type Item = EitherString; fn next(&mut self) -> Option { match self { EitherRowIterator::Owned(iter) => iter.next().map(EitherString::Owned), EitherRowIterator::Some(iter) => iter.next().map(EitherString::Some), } } } tabled-0.14.0/src/grid/records/into_records/either_string.rs000064400000000000000000000010011046102023000222140ustar 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.14.0/src/grid/records/into_records/limit_column_records.rs000064400000000000000000000035061046102023000235760ustar 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, ::Item: AsRef, { type Item = LimitColumnsColumnsIter<::IntoIter>; fn next(&mut self) -> Option { let iter = self.iter.next()?; Some(LimitColumnsColumnsIter { iter: iter.into_iter(), limit: self.limit, }) } } /// An iterator over columns for [`LimitColumns`]. #[derive(Debug)] pub struct LimitColumnsColumnsIter { iter: I, limit: usize, } impl Iterator for LimitColumnsColumnsIter where I: Iterator, I::Item: AsRef, { type Item = I::Item; fn next(&mut self) -> Option { if self.limit == 0 { return None; } self.limit -= 1; self.iter.next() } } tabled-0.14.0/src/grid/records/into_records/limit_row_records.rs000064400000000000000000000023631046102023000231100ustar 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, ::Item: AsRef, { type Item = I::Item; fn next(&mut self) -> Option { if self.limit == 0 { return None; } self.limit -= 1; self.iter.next() } } tabled-0.14.0/src/grid/records/into_records/mod.rs000064400000000000000000000014101046102023000201310ustar 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::{BufColumns, BufRows}; #[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.14.0/src/grid/records/into_records/truncate_records.rs000064400000000000000000000064361046102023000227350ustar 00000000000000//! The module contains [`TruncateContent`] records iterator. use std::borrow::Cow; use crate::{ grid::records::IntoRecords, grid::util::string::string_width_multiline, settings::width::Truncate, }; use super::either_string::EitherString; /// A records iterator which truncates all cells to a given width. #[derive(Debug)] pub struct TruncateContent<'a, I> { records: I, width: ExactValue<'a>, } impl TruncateContent<'_, ()> { /// Creates new [`TruncateContent`] object. pub fn new(records: I, width: ExactValue<'_>) -> TruncateContent<'_, I> { TruncateContent { records, width } } } impl<'a, I> IntoRecords for TruncateContent<'a, I> where I: IntoRecords, { type Cell = EitherString; type IterColumns = TruncateContentColumnsIter<'a, ::IntoIter>; type IterRows = TruncateContentIter<'a, ::IntoIter>; fn iter_rows(self) -> Self::IterRows { TruncateContentIter { iter: self.records.iter_rows().into_iter(), width: self.width.clone(), } } } /// A row iterator for [`TruncateContent`]. #[derive(Debug)] pub struct TruncateContentIter<'a, I> { iter: I, width: ExactValue<'a>, } impl<'a, I> Iterator for TruncateContentIter<'a, I> where I: Iterator, I::Item: IntoIterator, ::Item: AsRef, { type Item = TruncateContentColumnsIter<'a, ::IntoIter>; fn next(&mut self) -> Option { let iter = self.iter.next()?; Some(TruncateContentColumnsIter { iter: iter.into_iter(), current: 0, width: self.width.clone(), }) } } /// A column iterator for [`TruncateContent`]. #[derive(Debug)] pub struct TruncateContentColumnsIter<'a, I> { iter: I, width: ExactValue<'a>, current: usize, } impl Iterator for TruncateContentColumnsIter<'_, I> where I: Iterator, I::Item: AsRef, { type Item = EitherString; fn next(&mut self) -> Option { let s = self.iter.next()?; let width = self.width.get(self.current); self.current += 1; let text_width = string_width_multiline(s.as_ref()); if text_width <= width { return Some(EitherString::Some(s)); } let text = Truncate::truncate_text(s.as_ref(), width); let text = text.into_owned(); let text = EitherString::Owned(text); Some(text) } } /// A width value. #[derive(Debug, Clone)] pub enum ExactValue<'a> { /// Const width value. Exact(usize), /// A list of width values for columns. List(Cow<'a, [usize]>), } impl<'a> From<&'a [usize]> for ExactValue<'a> { fn from(value: &'a [usize]) -> Self { Self::List(value.into()) } } impl From> for ExactValue<'_> { fn from(value: Vec) -> Self { Self::List(value.into()) } } impl From for ExactValue<'_> { fn from(value: usize) -> Self { Self::Exact(value) } } impl ExactValue<'_> { /// Get a width by column. pub fn get(&self, col: usize) -> usize { match self { ExactValue::Exact(val) => *val, ExactValue::List(cols) => cols[col], } } } tabled-0.14.0/src/grid/records/mod.rs000064400000000000000000000011271046102023000154440ustar 00000000000000//! The module contains [`Records`], [`ExactRecords`], [`RecordsMut`], [`Resizable`] traits //! and its implementations. //! //! Also it provies 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.14.0/src/grid/records/records_mut.rs000064400000000000000000000016721046102023000172200ustar 00000000000000use crate::grid::config::Position; #[cfg(feature = "std")] use crate::grid::records::vec_records::{CellInfo, 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, (row, col): Position, text: String) { self[row][col] = CellInfo::new(text); } } #[cfg(feature = "std")] impl RecordsMut<&str> for VecRecords> { fn set(&mut self, (row, col): Position, text: &str) { self[row][col] = CellInfo::new(text.to_string()); } } tabled-0.14.0/src/grid/records/resizable.rs000064400000000000000000000135021046102023000166450ustar 00000000000000use papergrid::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.0][lhs.1]); let t = std::mem::replace(&mut self[rhs.0][rhs.1], t); let _ = std::mem::replace(&mut self[lhs.0][lhs.1], 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.get(0).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.get(0).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.0][lhs.1]); let t = std::mem::replace(&mut self[rhs.0][rhs.1], t); let _ = std::mem::replace(&mut self[lhs.0][lhs.1], 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.get(0).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.get(0).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.14.0/src/lib.rs000064400000000000000000000340051046102023000130460ustar 00000000000000//! An easy to use library for pretty print tables of Rust `struct`s and `enum`s. //! //! The library supports different approaches of table building. //! You can use [`Tabled`] trait if the data type is known. //! Or you can use [`Builder`] to construct the table from scratch. //! //! ## Usage //! //! If you want to build a table for your custom type. //! A starting point is to a anotate your type with `#[derive(Tabled)]`. //! //! Then to provide your collection to [`Table::new`] and you will be set to render table. //! #![cfg_attr(all(feature = "derive", feature = "std"), doc = "```")] #![cfg_attr(not(all(feature = "derive", feature = "std")), doc = "```ignore")] //! use tabled::{Tabled, 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 table = Table::new(languages).to_string(); //! //! let expected = "+------+----------------+---------------+\n\ //! | name | designed_by | invented_year |\n\ //! +------+----------------+---------------+\n\ //! | C | Dennis Ritchie | 1972 |\n\ //! +------+----------------+---------------+\n\ //! | Rust | Graydon Hoare | 2010 |\n\ //! +------+----------------+---------------+\n\ //! | Go | Rob Pike | 2009 |\n\ //! +------+----------------+---------------+"; //! //! assert_eq!(table, expected); //! ``` //! //! Not all types can derive [`Tabled`] trait though. //! The example below can't be compiled. //! //! ```rust,compile_fail //! # use tabled::Tabled; //! #[derive(Tabled)] //! struct SomeType { //! field1: SomeOtherType, //! } //! //! struct SomeOtherType; //! ``` //! //! Because `tabled` must know what we're up to print as a field, so //! each (almoust) field must implement [`std::fmt::Display`]. //! //! ### Default implementations //! //! [`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::{Tabled, Table}; //! let table = Table::new(&[1, 2, 3]); //! # let expected = "+-----+\n\ //! # | i32 |\n\ //! # +-----+\n\ //! # | 1 |\n\ //! # +-----+\n\ //! # | 2 |\n\ //! # +-----+\n\ //! # | 3 |\n\ //! # +-----+"; //! # assert_eq!(table.to_string(), expected); //! ``` //! //! ### Dynamic table //! //! When you data scheme is not known at compile time. //! You most likely will not able to relay on [`Tabled`] trait. //! //! So one option would be is to use [`Builder`]. //! #![cfg_attr(feature = "std", doc = "```")] #![cfg_attr(not(feature = "std"), doc = "```ignore")] //! use std::iter; //! //! use tabled::{ //! builder::Builder, //! settings::{Modify, object::Rows, Alignment, Style} //! }; //! //! let (x, y) = (3, 10); //! //! let mut builder = Builder::default(); //! //! let header = iter::once(String::from("i")) //! .chain((0..y) //! .map(|i| i.to_string())); //! builder.set_header(header); //! //! for i in 0..x { //! let row = iter::once(i) //! .chain((0..y).map(|j| i * j)) //! .map(|i| i.to_string()); //! builder.push_record(row); //! } //! //! let table = builder.build() //! .with(Style::rounded()) //! .with(Modify::new(Rows::new(1..)).with(Alignment::left())) //! .to_string(); //! //! assert_eq!( //! table, //! concat!( //! "╭───┬───┬───┬───┬───┬───┬────┬────┬────┬────┬────╮\n", //! "│ i │ 0 │ 1 │ 2 │ 3 │ 4 │ 5 │ 6 │ 7 │ 8 │ 9 │\n", //! "├───┼───┼───┼───┼───┼───┼────┼────┼────┼────┼────┤\n", //! "│ 0 │ 0 │ 0 │ 0 │ 0 │ 0 │ 0 │ 0 │ 0 │ 0 │ 0 │\n", //! "│ 1 │ 0 │ 1 │ 2 │ 3 │ 4 │ 5 │ 6 │ 7 │ 8 │ 9 │\n", //! "│ 2 │ 0 │ 2 │ 4 │ 6 │ 8 │ 10 │ 12 │ 14 │ 16 │ 18 │\n", //! "╰───┴───┴───┴───┴───┴───┴────┴────┴────┴────┴────╯", //! ) //! ); //! ``` //! //! ### 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}; //! //! let table = row![ //! col!["Hello", "World", "!"], //! col!["Hello"; 3], //! col!["World"; 3], //! ]; //! //! assert_eq!( //! table.to_string(), //! concat!( //! "+-----------+-----------+-----------+\n", //! "| +-------+ | +-------+ | +-------+ |\n", //! "| | Hello | | | Hello | | | World | |\n", //! "| +-------+ | +-------+ | +-------+ |\n", //! "| | World | | | Hello | | | World | |\n", //! "| +-------+ | +-------+ | +-------+ |\n", //! "| | ! | | | Hello | | | World | |\n", //! "| +-------+ | +-------+ | +-------+ |\n", //! "+-----------+-----------+-----------+", //! ) //! ); //! ``` //! //! ### Settings //! //! You can use many settings which is found in [`tabled::settings`] module. //! //! # Advanced //! //! ## Alloc //! //! [`Table`] keeps data buffered, which sometimes not ideal choise. //! For such reason there is [`IterTable`] and [`CompactTable`]. //! //! ### Less allocations //! //! [`IterTable`] stands on a middle ground between [`Table`] and [`CompactTable`]. //! //! It does allocate memory but in a much smaller chunks that a [`Table`] does. //! The benefit is that it can be used interchangebly with [`Table`]. //! #![cfg_attr(feature = "std", doc = "```")] #![cfg_attr(not(feature = "std"), doc = "```ignore")] //! use tabled::tables::IterTable; //! //! let iterator = (0..3).map(|row| (0..4).map(move |col| format!("{}-{}", row, col))); //! //! let table = IterTable::new(iterator).to_string(); //! //! assert_eq!( //! table, //! "+-----+-----+-----+-----+\n\ //! | 0-0 | 0-1 | 0-2 | 0-3 |\n\ //! +-----+-----+-----+-----+\n\ //! | 1-0 | 1-1 | 1-2 | 1-3 |\n\ //! +-----+-----+-----+-----+\n\ //! | 2-0 | 2-1 | 2-2 | 2-3 |\n\ //! +-----+-----+-----+-----+", //! ); //! ``` //! //! ## Alloc free (`#nostd`) //! //! [`CompactTable`] can be configured ('1) to not make any allocations. //! But the price is that the set of settings which can be applied to it is limited. //! //! It also can be printed directly to [`fmt::Write`] to not have any intermidiaries. //! //! '1. It does not make any allocations in case you provide it with `width` and `count_rows`. //! //! ``` //! use tabled::{settings::Style, tables::CompactTable}; //! use core::fmt::{Write, Result}; //! //! struct StubWriter; //! //! impl Write for StubWriter { //! fn write_str(&mut self, _: &str) -> Result { //! Ok(()) //! } //! } //! //! 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()); //! //! table.fmt(StubWriter); //! ``` //! //! ## 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 #![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 = "std")] mod tabled; #[cfg(feature = "std")] #[cfg_attr(docsrs, doc(cfg(feature = "std")))] pub mod builder; pub mod settings; pub mod tables; #[cfg(feature = "macros")] #[cfg_attr(docsrs, doc(cfg(feature = "macros")))] pub mod macros; pub mod grid; #[cfg(feature = "std")] #[cfg_attr(docsrs, doc(cfg(feature = "std")))] pub use crate::{tabled::Tabled, tables::Table}; /// A derive to implement a [`Tabled`] trait. /// /// The macros available only when `derive` feature in turned on (and it is by default). /// /// To be able to use the derive each field must implement `std::fmt::Display`. /// The following example will cause a error because of that. /// /// ```rust,compile_fail /// use tabled::Tabled; /// #[derive(Tabled)] /// struct SomeType { /// field1: SomeOtherType, /// } /// /// struct SomeOtherType; /// ``` /// /// Bellow you'll find available options for it. /// /// ### Override a column name /// /// You can use a `#[tabled(rename = "")]` attribute to override a column name. /// /// ```rust,no_run /// use tabled::Tabled; /// /// #[derive(Tabled)] /// struct Person { /// #[tabled(rename = "Name")] /// first_name: &'static str, /// #[tabled(rename = "Surname")] /// last_name: &'static str, /// } /// ``` /// /// ### Hide a column /// /// You can mark fields as hidden in which case they fill be ignored and not be present on a sheet. /// /// A similar affect could be achieved by the means of a `Disable` setting. /// /// ```rust,no_run /// use tabled::Tabled; /// /// #[derive(Tabled)] /// struct Person { /// id: u8, /// #[tabled(skip)] /// number: &'static str, /// name: &'static str, /// } /// ``` /// /// ### Set column order /// /// You can change the order in which they will be displayed in table. /// /// ```rust,no_run /// use tabled::Tabled; /// /// #[derive(Tabled)] /// struct Person { /// id: u8, /// #[tabled(order = 0)] /// number: &'static str, /// #[tabled(order = 1)] /// name: &'static str, /// } /// ``` /// /// ### Format fields /// /// As was said already, using `#[derive(Tabled)]` is possible only when all fields implement a `Display` trait. /// However, this may be often not the case 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. /// /// Alternatively, you can use the `#[tabled(display_with = "func")]` attribute for the field to specify a display function. /// /// ```rust,no_run /// use tabled::Tabled; /// /// #[derive(Tabled)] /// pub struct MyRecord { /// pub id: i64, /// #[tabled(display_with = "display_option")] /// pub valid: Option /// } /// /// fn display_option(o: &Option) -> String { /// match o { /// Some(s) => format!("is valid thing = {}", s), /// None => format!("is not valid"), /// } /// } /// ``` /// /// It's also possible to change function argument to be `&self`, /// using `#[tabled(display_with("some_function", self))]` /// /// ```rust,no_run /// use tabled::Tabled; /// /// #[derive(Tabled)] /// pub struct MyRecord { /// pub id: i64, /// #[tabled(display_with("Self::display_valid", self))] /// pub valid: Option /// } /// /// impl MyRecord { /// fn display_valid(&self) -> String { /// match self.valid { /// Some(s) => format!("is valid thing = {}", s), /// None => format!("is not valid"), /// } /// } /// } /// ``` /// /// ### Format headers /// /// Beside `#[tabled(rename = "")]` you can change a format of a column name using /// `#[tabled(rename_all = "UPPERCASE")]`. /// /// ```rust,no_run /// use tabled::Tabled; /// /// #[derive(Tabled)] /// #[tabled(rename_all = "CamelCase")] /// struct Person { /// id: u8, /// number: &'static str, /// name: &'static str, /// #[tabled(rename_all = "snake_case")] /// middle_name: &'static str, /// } /// ``` /// /// ### Inline /// /// It's possible to inline internal data if it implements the `Tabled` trait using `#[tabled(inline)]`. /// You can also set a prefix which will be used for all inlined elements by `#[tabled(inline("prefix>>"))]`. /// /// ```rust,no_run /// use tabled::Tabled; /// /// #[derive(Tabled)] /// struct Person { /// id: u8, /// name: &'static str, /// #[tabled(inline)] /// ed: Education, /// } /// /// #[derive(Tabled)] /// struct Education { /// uni: &'static str, /// graduated: bool, /// } /// ``` /// /// And it works for enums as well. /// /// ```rust,no_run /// use tabled::Tabled; /// /// #[derive(Tabled)] /// enum Vehicle { /// #[tabled(inline("Auto::"))] /// Auto { /// model: &'static str, /// engine: &'static str, /// }, /// #[tabled(inline)] /// Bikecycle( /// &'static str, /// #[tabled(inline)] Bike, /// ), /// } /// /// #[derive(Tabled)] /// struct Bike { /// brand: &'static str, /// price: f32, /// } /// ``` #[cfg(feature = "derive")] #[cfg_attr(docsrs, doc(cfg(feature = "derive")))] pub use tabled_derive::Tabled; tabled-0.14.0/src/macros/col.rs000064400000000000000000000024261046102023000143430ustar 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.14.0/src/macros/mod.rs000064400000000000000000000002001046102023000143310ustar 00000000000000//! This module contains macro functions for dynamic [`Table`] construction. //! //! [`Table`]: crate::Table mod col; mod row; tabled-0.14.0/src/macros/row.rs000064400000000000000000000023261046102023000143740ustar 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.14.0/src/settings/alignment/mod.rs000064400000000000000000000147431046102023000167040ustar 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 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 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) } /// 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 = cfg.set_alignment_horizontal(a), Vertical(a) => *cfg = cfg.set_alignment_vertical(a), } } fn hint_change(&self) -> Option { None } } tabled-0.14.0/src/settings/cell_option.rs000064400000000000000000000042411046102023000164460ustar 00000000000000use crate::grid::{ config::Entity, records::{ExactRecords, Records, RecordsMut}, }; /// A trait for configuring a single cell. /// /// ~~~~ Where cell represented by 'row' and 'column' indexes. ~~~~ /// /// A cell can be targeted by [`Cell`]. /// /// [`Cell`]: crate::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 unnessary calculations if they're not needed, after using the [`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); } } } tabled-0.14.0/src/settings/color/mod.rs000064400000000000000000000326161046102023000160430ustar 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::{borrow::Cow, ops::BitOr}; use crate::{ grid::{ color::{AnsiColor, 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, Default, Clone, PartialEq, Eq, PartialOrd, Ord)] pub struct Color(AnsiColor<'static>); #[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(AnsiColor::new(Cow::Borrowed("\u{1b}[30m"), Cow::Borrowed("\u{1b}[39m"))); /// A color representation. /// /// Notice that the colors are constants so you can't combine them. pub const FG_BLUE: Self = Self(AnsiColor::new(Cow::Borrowed("\u{1b}[34m"), Cow::Borrowed("\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(AnsiColor::new(Cow::Borrowed("\u{1b}[90m"), Cow::Borrowed("\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(AnsiColor::new(Cow::Borrowed("\u{1b}[94m"), Cow::Borrowed("\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(AnsiColor::new(Cow::Borrowed("\u{1b}[96m"), Cow::Borrowed("\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(AnsiColor::new(Cow::Borrowed("\u{1b}[92m"), Cow::Borrowed("\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(AnsiColor::new(Cow::Borrowed("\u{1b}[95m"), Cow::Borrowed("\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(AnsiColor::new(Cow::Borrowed("\u{1b}[91m"), Cow::Borrowed("\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(AnsiColor::new(Cow::Borrowed("\u{1b}[97m"), Cow::Borrowed("\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(AnsiColor::new(Cow::Borrowed("\u{1b}[93m"), Cow::Borrowed("\u{1b}[39m"))); /// A color representation. /// /// Notice that the colors are constants so you can't combine them. pub const FG_CYAN: Self = Self(AnsiColor::new(Cow::Borrowed("\u{1b}[36m"), Cow::Borrowed("\u{1b}[39m"))); /// A color representation. /// /// Notice that the colors are constants so you can't combine them. pub const FG_GREEN: Self = Self(AnsiColor::new(Cow::Borrowed("\u{1b}[32m"), Cow::Borrowed("\u{1b}[39m"))); /// A color representation. /// /// Notice that the colors are constants so you can't combine them. pub const FG_MAGENTA: Self = Self(AnsiColor::new(Cow::Borrowed("\u{1b}[35m"), Cow::Borrowed("\u{1b}[39m"))); /// A color representation. /// /// Notice that the colors are constants so you can't combine them. pub const FG_RED: Self = Self(AnsiColor::new(Cow::Borrowed("\u{1b}[31m"), Cow::Borrowed("\u{1b}[39m"))); /// A color representation. /// /// Notice that the colors are constants so you can't combine them. pub const FG_WHITE: Self = Self(AnsiColor::new(Cow::Borrowed("\u{1b}[37m"), Cow::Borrowed("\u{1b}[39m"))); /// A color representation. /// /// Notice that the colors are constants so you can't combine them. pub const FG_YELLOW: Self = Self(AnsiColor::new(Cow::Borrowed("\u{1b}[33m"), Cow::Borrowed("\u{1b}[39m"))); /// A color representation. /// /// Notice that the colors are constants so you can't combine them. pub const BG_BLACK: Self = Self(AnsiColor::new(Cow::Borrowed("\u{1b}[40m"), Cow::Borrowed("\u{1b}[49m"))); /// A color representation. /// /// Notice that the colors are constants so you can't combine them. pub const BG_BLUE: Self = Self(AnsiColor::new(Cow::Borrowed("\u{1b}[44m"), Cow::Borrowed("\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(AnsiColor::new(Cow::Borrowed("\u{1b}[100m"), Cow::Borrowed("\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(AnsiColor::new(Cow::Borrowed("\u{1b}[104m"), Cow::Borrowed("\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(AnsiColor::new(Cow::Borrowed("\u{1b}[106m"), Cow::Borrowed("\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(AnsiColor::new(Cow::Borrowed("\u{1b}[102m"), Cow::Borrowed("\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(AnsiColor::new(Cow::Borrowed("\u{1b}[105m"), Cow::Borrowed("\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(AnsiColor::new(Cow::Borrowed("\u{1b}[101m"), Cow::Borrowed("\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(AnsiColor::new(Cow::Borrowed("\u{1b}[107m"), Cow::Borrowed("\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(AnsiColor::new(Cow::Borrowed("\u{1b}[103m"), Cow::Borrowed("\u{1b}[49m"))); /// A color representation. /// /// Notice that the colors are constants so you can't combine them. pub const BG_CYAN: Self = Self(AnsiColor::new(Cow::Borrowed("\u{1b}[46m"), Cow::Borrowed("\u{1b}[49m"))); /// A color representation. /// /// Notice that the colors are constants so you can't combine them. pub const BG_GREEN: Self = Self(AnsiColor::new(Cow::Borrowed("\u{1b}[42m"), Cow::Borrowed("\u{1b}[49m"))); /// A color representation. /// /// Notice that the colors are constants so you can't combine them. pub const BG_MAGENTA: Self = Self(AnsiColor::new(Cow::Borrowed("\u{1b}[45m"), Cow::Borrowed("\u{1b}[49m"))); /// A color representation. /// /// Notice that the colors are constants so you can't combine them. pub const BG_RED: Self = Self(AnsiColor::new(Cow::Borrowed("\u{1b}[41m"), Cow::Borrowed("\u{1b}[49m"))); /// A color representation. /// /// Notice that the colors are constants so you can't combine them. pub const BG_WHITE: Self = Self(AnsiColor::new(Cow::Borrowed("\u{1b}[47m"), Cow::Borrowed("\u{1b}[49m"))); /// A color representation. /// /// Notice that the colors are constants so you can't combine them. pub const BG_YELLOW: Self = Self(AnsiColor::new(Cow::Borrowed("\u{1b}[43m"), Cow::Borrowed("\u{1b}[49m"))); /// A color representation. /// /// Notice that the colors are constants so you can't combine them. pub const BOLD: Self = Self(AnsiColor::new(Cow::Borrowed("\u{1b}[1m"), Cow::Borrowed("\u{1b}[22m"))); } 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: String, suffix: String) -> Self { Self(AnsiColor::new(prefix.into(), suffix.into())) } } impl From for AnsiColor<'static> { fn from(c: Color) -> Self { c.0 } } impl From> for Color { fn from(c: AnsiColor<'static>) -> Self { Self(c) } } impl From for Color { fn from(c: StaticColor) -> Self { Self(AnsiColor::new( Cow::Borrowed(c.get_prefix()), Cow::Borrowed(c.get_suffix()), )) } } impl BitOr for Color { type Output = Color; fn bitor(self, rhs: Self) -> Self::Output { let l_prefix = self.0.get_prefix(); let l_suffix = self.0.get_suffix(); let r_prefix = rhs.0.get_prefix(); let r_suffix = rhs.0.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 = "color")] impl std::convert::TryFrom<&str> for Color { type Error = (); fn try_from(value: &str) -> Result { AnsiColor::try_from(value).map(Color) } } #[cfg(feature = "color")] impl std::convert::TryFrom for Color { type Error = (); fn try_from(value: String) -> Result { AnsiColor::try_from(value).map(Color) } } impl TableOption for Color { fn change(self, _: &mut R, cfg: &mut ColoredConfig, _: &mut D) { let _ = cfg.set_color(Entity::Global, self.0.clone()); } fn hint_change(&self) -> Option { None } } impl CellOption for Color { fn change(self, _: &mut R, cfg: &mut ColoredConfig, entity: Entity) { let _ = cfg.set_color(entity, self.0.clone()); } fn hint_change(&self) -> Option { None } } impl CellOption for &Color { fn change(self, _: &mut R, cfg: &mut ColoredConfig, entity: Entity) { let _ = cfg.set_color(entity, self.0.clone()); } fn hint_change(&self) -> Option { None } } impl crate::grid::color::Color for Color { fn fmt_prefix(&self, f: &mut W) -> std::fmt::Result { self.0.fmt_prefix(f) } fn fmt_suffix(&self, f: &mut W) -> std::fmt::Result { self.0.fmt_suffix(f) } fn colorize(&self, f: &mut W, text: &str) -> std::fmt::Result { self.0.colorize(f, text) } } #[cfg(test)] mod tests { use super::*; #[cfg(feature = "color")] use ::{owo_colors::OwoColorize, std::convert::TryFrom}; #[test] fn test_xor_operation() { assert_eq!( Color::FG_BLACK | Color::FG_BLUE, Color::new( String::from("\u{1b}[30m\u{1b}[34m"), String::from("\u{1b}[39m") ) ); assert_eq!( Color::FG_BRIGHT_GREEN | Color::BG_BLUE, Color::new( String::from("\u{1b}[92m\u{1b}[44m"), String::from("\u{1b}[39m\u{1b}[49m") ) ); assert_eq!( Color::new(String::from("..."), String::from("!!!")) | Color::new(String::from("@@@"), String::from("###")), Color::new(String::from("...@@@"), String::from("!!!###")) ); assert_eq!( Color::new(String::from("..."), String::from("!!!")) | Color::new(String::from("@@@"), String::from("###")) | Color::new(String::from("$$$"), String::from("%%%")), Color::new(String::from("...@@@$$$"), String::from("!!!###%%%")) ); } #[cfg(feature = "color")] #[test] fn test_try_from() { assert_eq!(Color::try_from(""), Err(())); assert_eq!(Color::try_from("".red().on_green().to_string()), Err(())); assert_eq!( Color::try_from("."), Ok(Color::new(String::new(), String::new())) ); assert_eq!( Color::try_from("...."), Ok(Color::new(String::new(), String::new())) ); assert_eq!( Color::try_from(".".red().on_green().to_string()), Ok(Color::new( String::from("\u{1b}[31m\u{1b}[42m"), String::from("\u{1b}[39m\u{1b}[49m") )) ); assert_eq!( Color::try_from("....".red().on_green().to_string()), Ok(Color::new( String::from("\u{1b}[31m\u{1b}[42m"), String::from("\u{1b}[39m\u{1b}[49m") )) ); } } tabled-0.14.0/src/settings/concat/mod.rs000064400000000000000000000126631046102023000161740ustar 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 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. /// /// # 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((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((row, col)).to_string(); let col = col + count_cols; records.set((row, col), 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((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((row, col)).to_string(); let row = row + count_rows; records.set((row, col), text); } } } } } } tabled-0.14.0/src/settings/disable/mod.rs000064400000000000000000000125271046102023000163270ustar 00000000000000//! This module contains a [`Disable`] structure which helps to //! remove an etheir column or row from a [`Table`]. //! //! # Example //! //! ```rust,no_run //! # use tabled::{Table, settings::{Disable, object::Rows}}; //! # let data: Vec<&'static str> = Vec::new(); //! let table = Table::new(&data).with(Disable::row(Rows::first())); //! ``` //! //! [`Table`]: crate::Table use std::marker::PhantomData; use crate::{ grid::records::{ExactRecords, Records, Resizable}, settings::{locator::Locator, TableOption}, }; /// Disable 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 [`Disable`] because it's a slow function and modifies the underlying records. /// Providing correct data right away is better. /// /// # Example /// /// ``` /// use tabled::{Table, settings::{Disable, object::Rows}}; /// /// let data = vec!["Hello", "World", "!!!"]; /// /// let table = Table::new(data).with(Disable::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 Disable { locator: L, target: PhantomData, } impl Disable { /// Disable columns. /// /// Available locators are: /// /// - [`Columns`] /// - [`Column`] /// - [`FirstColumn`] /// - [`LastColumn`] /// - [`ByColumnName`] /// /// ```rust /// use tabled::{builder::Builder, settings::{Disable, locator::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(Disable::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::locator::ByColumnName pub fn column(locator: L) -> Self { Self { locator, target: PhantomData, } } } impl Disable { /// Disable rows. /// /// Available locators are: /// /// - [`Rows`] /// - [`Row`] /// - [`FirstRow`] /// - [`LastRow`] /// /// ```rust /// use tabled::{settings::{Disable, 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(Disable::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 [`Disable`]. #[derive(Debug)] pub struct TargetRow; /// A marker struct for [`Disable`]. #[derive(Debug)] pub struct TargetColumn; impl TableOption for Disable where for<'a> L: Locator<&'a R, Coordinate = usize>, 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 Disable where for<'a> L: Locator<&'a R, Coordinate = usize>, 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.14.0/src/settings/duplicate/mod.rs000064400000000000000000000077041046102023000166770ustar 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 papergrid::config::Position; use crate::{ 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((row, col): Position, count_rows: usize, count_columns: usize) -> bool { if row > count_rows { return false; } if col > count_columns { return false; } true } tabled-0.14.0/src/settings/extract/mod.rs000064400000000000000000000173111046102023000163720ustar 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.14.0/src/settings/format/format_config.rs000064400000000000000000000005231046102023000202430ustar 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.14.0/src/settings/format/format_content.rs000064400000000000000000000046031046102023000204530ustar 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) { let is_valid_pos = pos.0 < count_rows && pos.1 < count_cols; if !is_valid_pos { 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.14.0/src/settings/format/format_positioned.rs000064400000000000000000000032031046102023000211510ustar 00000000000000use 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, (usize, usize)) -> 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, (usize, usize)) -> 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) { let is_valid_pos = pos.0 < count_rows && pos.1 < count_cols; if !is_valid_pos { continue; } let content = records.get_text(pos); let content = (self.0)(content, pos); records.set(pos, content); } } } tabled-0.14.0/src/settings/format/mod.rs000064400000000000000000000105361046102023000162120ustar 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; /// 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) /// .with(Modify::new(Rows::single(0)).with(Format::positioned(|_, (_, col)| 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, (usize, usize)) -> 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) } } tabled-0.14.0/src/settings/formatting/alignment_strategy.rs000064400000000000000000000225201046102023000222110ustar 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 mut formatting = *cfg.get_formatting(entity); match &self { AlignmentStrategy::PerCell => formatting.allow_lines_alignment = false, AlignmentStrategy::PerLine => formatting.allow_lines_alignment = true, } cfg.set_formatting(entity, formatting); } } 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 = cfg.set_formatting(f); } fn hint_change(&self) -> Option { None } } tabled-0.14.0/src/settings/formatting/charset.rs000064400000000000000000000061321046102023000177430ustar 00000000000000use papergrid::{ config::Entity, records::{ExactRecords, PeekableRecords}, }; use crate::{ grid::records::{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` occurences. /// /// 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` occurences. /// /// # Example /// /// ``` /// use tabled::{Table, settings::formatting::Charset}; /// /// let text = "Some text which was created on windows \r\n yes they use this \\r\\n"; /// /// let mut builder = Table::builder([text]); /// builder.set_header(["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 TableOption for CleanCharset where for<'a> &'a R: Records, R: RecordsMut, { fn change(self, records: &mut R, _: &mut C, _: &mut D) { let mut list = vec![]; for (row, cells) in records.iter_rows().into_iter().enumerate() { for (col, text) in cells.into_iter().enumerate() { let text = text.as_ref().replace(['\t', '\r'], ""); list.push(((row, col), text)); } } for (pos, text) in list { 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 = text.replace(['\t', '\r'], ""); records.set(pos, text); } } } tabled-0.14.0/src/settings/formatting/justification.rs000064400000000000000000000064501046102023000211700ustar 00000000000000use crate::{ grid::{ color::AnsiColor, 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.14.0/src/settings/formatting/mod.rs000064400000000000000000000012301046102023000170630ustar 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.14.0/src/settings/formatting/tab_size.rs000064400000000000000000000031021046102023000201040ustar 00000000000000use crate::{ grid::records::{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 for<'a> &'a R: Records, R: RecordsMut, { fn change(self, records: &mut R, _: &mut C, _: &mut D) { let mut list = vec![]; for (row, cells) in records.iter_rows().into_iter().enumerate() { for (col, text) in cells.into_iter().enumerate() { let text = text.as_ref().replace('\t', &" ".repeat(self.0)); list.push(((row, col), text)); } } for (pos, text) in list { records.set(pos, text); } } } tabled-0.14.0/src/settings/formatting/trim_strategy.rs000064400000000000000000000072671046102023000212210ustar 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) { let mut formatting = *cfg.get_formatting(entity); // todo: could be changed to be a struct an enum like consts in `impl` block. match self { TrimStrategy::Vertical => { formatting.vertical_trim = true; } TrimStrategy::Horizontal => { formatting.horizontal_trim = true; } TrimStrategy::Both => { formatting.vertical_trim = true; formatting.horizontal_trim = true; } TrimStrategy::None => { formatting.vertical_trim = false; formatting.horizontal_trim = false; } } cfg.set_formatting(entity, formatting); } } 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.14.0/src/settings/height/cell_height_increase.rs000064400000000000000000000055521046102023000215350ustar 00000000000000use crate::{ grid::config::ColoredConfig, grid::config::Entity, grid::dimension::CompleteDimensionVecRecords, grid::records::{ExactRecords, 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 scence only for table, so the function /// converts it to [`TableHeightIncrease`] with a given priority. pub fn priority

(self) -> TableHeightIncrease where P: Peaker, W: Measurement, { TableHeightIncrease::new(self.height).priority::

() } } impl CellOption for CellHeightIncrease where W: Measurement, R: Records + ExactRecords + PeekableRecords + RecordsMut, for<'a> &'a R: Records, { 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) { let is_valid_pos = pos.0 < count_rows && pos.1 < count_columns; if !is_valid_pos { 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, ColoredConfig> for CellHeightIncrease where W: Measurement, R: Records + ExactRecords + PeekableRecords, for<'a> &'a R: Records, { 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.14.0/src/settings/height/cell_height_limit.rs000064400000000000000000000054161046102023000210610ustar 00000000000000use crate::{ grid::{ config::{ColoredConfig, Entity}, dimension::CompleteDimensionVecRecords, records::{ExactRecords, 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) -> TableHeightLimit where P: Peaker, W: Measurement, { TableHeightLimit::new(self.height).priority::

() } } impl CellOption for CellHeightLimit where W: Measurement, R: Records + ExactRecords + PeekableRecords + RecordsMut, for<'a> &'a R: Records, { 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) { let is_valid_pos = pos.0 < count_rows && pos.1 < count_columns; if !is_valid_pos { 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, ColoredConfig> for CellHeightLimit where W: Measurement, R: Records + ExactRecords + PeekableRecords + RecordsMut, for<'a> &'a R: Records, { 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.14.0/src/settings/height/height_list.rs000064400000000000000000000020631046102023000177120ustar 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, C> 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.14.0/src/settings/height/mod.rs000064400000000000000000000166421046102023000161760ustar 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>(width: W) -> CellHeightIncrease { CellHeightIncrease::new(width) } /// 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>(width: W) -> CellHeightLimit { CellHeightLimit::new(width) } /// 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.14.0/src/settings/height/table_height_increase.rs000064400000000000000000000042721046102023000217030ustar 00000000000000use crate::{ grid::{ config::{ColoredConfig, Entity}, dimension::CompleteDimensionVecRecords, records::{ExactRecords, 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) -> TableHeightIncrease where P: Peaker, { TableHeightIncrease { priority: P::create(), height: self.height, } } } impl TableOption, ColoredConfig> for TableHeightIncrease where W: Measurement, P: Peaker + Clone, R: Records + ExactRecords + PeekableRecords, for<'a> &'a R: Records, { 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.14.0/src/settings/height/table_height_limit.rs000064400000000000000000000056031046102023000212270ustar 00000000000000use crate::{ grid::{ config::ColoredConfig, dimension::CompleteDimensionVecRecords, records::{ExactRecords, 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) -> TableHeightLimit where P: Peaker, { TableHeightLimit { priority: P::create(), height: self.height, } } } impl TableOption, ColoredConfig> for TableHeightLimit where W: Measurement, P: Peaker + Clone, R: ExactRecords + PeekableRecords + RecordsMut, for<'a> &'a R: Records, { 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)); let count_lines = count_lines(text); if count_lines <= height { continue; } let text = limit_lines(text, height); records.set((row, col), 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.14.0/src/settings/height/util.rs000064400000000000000000000011331046102023000163610ustar 00000000000000use crate::grid::{ config::SpannedConfig, dimension::SpannedGridDimension, records::{ExactRecords, Records}, }; pub(crate) fn get_table_height( records: R, cfg: &SpannedConfig, ) -> (usize, Vec) { 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.14.0/src/settings/highlight/mod.rs000064400000000000000000000337361046102023000167000ustar 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::{ config::{Border as GridBorder, ColoredConfig, Entity, Position, SpannedConfig}, records::{ExactRecords, Records}, }, settings::{object::Object, style::BorderColor, Border, TableOption}, }; /// Highlight modifies a table style by changing a border of a target [`Table`] segment. /// /// # Example /// /// ``` /// use tabled::{ /// Table, /// settings::{Highlight, Border, Style, object::Segment} /// }; /// /// 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::new(Segment::all(), Border::default().top('^').bottom('v'))) /// .to_string(); /// /// assert_eq!( /// table, /// concat!( /// " ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ \n", /// "| usize | &str | &str | bool |\n", /// "|-------|-------|---------------------------|-------|\n", /// "| 0 | ELF | Extensible Linking Format | true |\n", /// "| 1 | DWARF | | true |\n", /// "| 2 | PE | Portable Executable | false |\n", /// " vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv ", /// ), /// ); /// ``` /// /// It's possible to use [`Highlight`] for many kinds of figures. /// /// ``` /// 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::new(Segment::all().not((0,0).and((1, 0)).and((0, 1)).and((0, 3))), Border::filled('*'))) /// .to_string(); /// /// println!("{}", table); /// /// 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: Border, } // todo: Add BorderColor. impl Highlight { /// Build a new instance of [`Highlight`] /// /// BE AWARE: if target exceeds boundaries it may panic. pub fn new(target: O, border: Border) -> Self { Self { target, border } } } impl Highlight { /// Build a new instance of [`HighlightColored`] pub fn colored(target: O, border: BorderColor) -> HighlightColored { HighlightColored { target, border } } } 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); for sector in segments { set_border(cfg, §or, self.border); } } fn hint_change(&self) -> Option { None } } /// A [`Highlight`] object which works with a [`BorderColored`] /// /// [`BorderColored`]: crate::settings::style::BorderColor #[derive(Debug)] pub struct HighlightColored { target: O, border: BorderColor, } impl TableOption for HighlightColored 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); for sector in segments { set_border_color(cfg, sector, &self.border); } } fn hint_change(&self) -> Option { None } } fn set_border_color( cfg: &mut SpannedConfig, sector: HashSet<(usize, usize)>, border: &BorderColor, ) { if sector.is_empty() { return; } let color = border.clone().into(); for &(row, col) in §or { let border = build_cell_border(§or, (row, col), &color); cfg.set_border_color((row, col), 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((row1, col1): (usize, usize), (row2, col2): (usize, usize)) -> bool { if col1 == col2 && row1 == row2 + 1 { return true; } if col1 == col2 && (row2 > 0 && row1 == row2 - 1) { return true; } if row1 == row2 && col1 == col2 + 1 { return true; } if row1 == row2 && (col2 > 0 && col1 == col2 - 1) { return true; } false } fn is_segment_connected( segment1: &HashSet<(usize, usize)>, segment2: &HashSet<(usize, usize)>, ) -> 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<(usize, usize)>, border: Border) { if sector.is_empty() { return; } let border = border.into(); for &pos in sector { let border = build_cell_border(sector, pos, &border); cfg.set_border(pos, border); } } fn build_cell_border( sector: &HashSet<(usize, usize)>, (row, col): Position, border: &GridBorder, ) -> GridBorder where T: Default + Clone, { let cell_has_top_neighbor = cell_has_top_neighbor(sector, row, col); let cell_has_bottom_neighbor = cell_has_bottom_neighbor(sector, row, col); let cell_has_left_neighbor = cell_has_left_neighbor(sector, row, col); let cell_has_right_neighbor = cell_has_right_neighbor(sector, row, col); let this_has_left_top_neighbor = is_there_left_top_cell(sector, row, col); let this_has_right_top_neighbor = is_there_right_top_cell(sector, row, col); let this_has_left_bottom_neighbor = is_there_left_bottom_cell(sector, row, col); let this_has_right_bottom_neighbor = is_there_right_bottom_cell(sector, row, col); let mut cell_border = GridBorder::default(); if let Some(c) = border.top.clone() { if !cell_has_top_neighbor { cell_border.top = Some(c.clone()); if cell_has_right_neighbor && !this_has_right_top_neighbor { cell_border.right_top_corner = Some(c); } } } if let Some(c) = border.bottom.clone() { if !cell_has_bottom_neighbor { cell_border.bottom = Some(c.clone()); if cell_has_right_neighbor && !this_has_right_bottom_neighbor { cell_border.right_bottom_corner = Some(c); } } } if let Some(c) = border.left.clone() { if !cell_has_left_neighbor { cell_border.left = Some(c.clone()); if cell_has_bottom_neighbor && !this_has_left_bottom_neighbor { cell_border.left_bottom_corner = Some(c); } } } if let Some(c) = border.right.clone() { if !cell_has_right_neighbor { cell_border.right = Some(c.clone()); if cell_has_bottom_neighbor && !this_has_right_bottom_neighbor { cell_border.right_bottom_corner = Some(c); } } } if let Some(c) = border.left_top_corner.clone() { if !cell_has_left_neighbor && !cell_has_top_neighbor { cell_border.left_top_corner = Some(c); } } if let Some(c) = border.left_bottom_corner.clone() { if !cell_has_left_neighbor && !cell_has_bottom_neighbor { cell_border.left_bottom_corner = Some(c); } } if let Some(c) = border.right_top_corner.clone() { if !cell_has_right_neighbor && !cell_has_top_neighbor { cell_border.right_top_corner = Some(c); } } if let Some(c) = border.right_bottom_corner.clone() { if !cell_has_right_neighbor && !cell_has_bottom_neighbor { cell_border.right_bottom_corner = Some(c); } } { if !cell_has_bottom_neighbor { if !cell_has_left_neighbor && this_has_left_top_neighbor { if let Some(c) = border.right_top_corner.clone() { cell_border.left_top_corner = Some(c); } } if cell_has_left_neighbor && this_has_left_bottom_neighbor { if let Some(c) = border.left_top_corner.clone() { cell_border.left_bottom_corner = Some(c); } } if !cell_has_right_neighbor && this_has_right_top_neighbor { if let Some(c) = border.left_top_corner.clone() { cell_border.right_top_corner = Some(c); } } if cell_has_right_neighbor && this_has_right_bottom_neighbor { if let Some(c) = border.right_top_corner.clone() { cell_border.right_bottom_corner = Some(c); } } } if !cell_has_top_neighbor { if !cell_has_left_neighbor && this_has_left_bottom_neighbor { if let Some(c) = border.right_bottom_corner.clone() { cell_border.left_bottom_corner = Some(c); } } if cell_has_left_neighbor && this_has_left_top_neighbor { if let Some(c) = border.left_bottom_corner.clone() { cell_border.left_top_corner = Some(c); } } if !cell_has_right_neighbor && this_has_right_bottom_neighbor { if let Some(c) = border.left_bottom_corner.clone() { cell_border.right_bottom_corner = Some(c); } } if cell_has_right_neighbor && this_has_right_top_neighbor { if let Some(c) = border.right_bottom_corner.clone() { cell_border.right_top_corner = Some(c); } } } } cell_border } fn cell_has_top_neighbor(sector: &HashSet<(usize, usize)>, row: usize, col: usize) -> bool { row > 0 && sector.contains(&(row - 1, col)) } fn cell_has_bottom_neighbor(sector: &HashSet<(usize, usize)>, row: usize, col: usize) -> bool { sector.contains(&(row + 1, col)) } fn cell_has_left_neighbor(sector: &HashSet<(usize, usize)>, row: usize, col: usize) -> bool { col > 0 && sector.contains(&(row, col - 1)) } fn cell_has_right_neighbor(sector: &HashSet<(usize, usize)>, row: usize, col: usize) -> bool { sector.contains(&(row, col + 1)) } fn is_there_left_top_cell(sector: &HashSet<(usize, usize)>, row: usize, col: usize) -> bool { row > 0 && col > 0 && sector.contains(&(row - 1, col - 1)) } fn is_there_right_top_cell(sector: &HashSet<(usize, usize)>, row: usize, col: usize) -> bool { row > 0 && sector.contains(&(row - 1, col + 1)) } fn is_there_left_bottom_cell(sector: &HashSet<(usize, usize)>, row: usize, col: usize) -> bool { col > 0 && sector.contains(&(row + 1, col - 1)) } fn is_there_right_bottom_cell(sector: &HashSet<(usize, usize)>, row: usize, col: usize) -> bool { sector.contains(&(row + 1, col + 1)) } #[cfg(test)] mod tests { use super::*; #[test] fn test_is_connected() { assert!(is_cell_connected((0, 0), (0, 1))); assert!(is_cell_connected((0, 0), (1, 0))); assert!(!is_cell_connected((0, 0), (1, 1))); assert!(is_cell_connected((0, 1), (0, 0))); assert!(is_cell_connected((1, 0), (0, 0))); assert!(!is_cell_connected((1, 1), (0, 0))); assert!(is_cell_connected((1, 1), (0, 1))); assert!(is_cell_connected((1, 1), (1, 0))); assert!(is_cell_connected((1, 1), (2, 1))); assert!(is_cell_connected((1, 1), (1, 2))); assert!(!is_cell_connected((1, 1), (1, 1))); } } tabled-0.14.0/src/settings/locator/mod.rs000064400000000000000000000122531046102023000163630ustar 00000000000000//! The module contains a [`Locator`] trait and implementations for it. use core::ops::Bound; use std::{ iter::{self, Once}, ops::{Range, RangeBounds}, }; use crate::{ grid::config::Entity, grid::records::{ExactRecords, PeekableRecords, Records}, settings::object::{ Column, Columns, FirstColumn, FirstRow, LastColumn, LastRow, Object, Row, Rows, }, }; /// Locator is an interface which searches for a particular thing in the [`Records`], /// and returns coordinate of the foundings if any. pub trait Locator { /// 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 Locator 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 Locator for Column { type Coordinate = usize; type IntoIter = Once; fn locate(&mut self, _: R) -> Self::IntoIter { iter::once((*self).into()) } } impl Locator for FirstColumn { type Coordinate = usize; type IntoIter = Once; fn locate(&mut self, _: R) -> Self::IntoIter { iter::once(0) } } impl Locator 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 Locator for Rows where R: Records, 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_columns(), ); from..to } } impl Locator for Row { type Coordinate = usize; type IntoIter = Once; fn locate(&mut self, _: R) -> Self::IntoIter { iter::once((*self).into()) } } impl Locator for FirstRow { type Coordinate = usize; type IntoIter = Once; fn locate(&mut self, _: R) -> Self::IntoIter { iter::once(0) } } impl Locator 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) } } } /// The structure is an implementation of [`Locator`] 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)] 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 Locator 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)) == 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)) == self.0.as_ref()) .map(Entity::Column) .collect::>() .into_iter() } } 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.14.0/src/settings/margin/mod.rs000064400000000000000000000100561046102023000161740ustar 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, 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", //! " | ! | ", //! ) //! ); //! ``` //! //! [`Table`]: crate::Table use crate::{ grid::{ color::StaticColor, config::{CompactConfig, CompactMultilineConfig}, config::{Indent, Sides}, }, settings::TableOption, }; #[cfg(feature = "std")] use crate::grid::{color::AnsiColor, config::ColoredConfig}; /// Margin is responsible for a left/right/top/bottom outer indent of a grid. /// #[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', '^')); /// ``` #[derive(Debug, Clone)] pub struct Margin { indent: Sides, colors: Option>, } 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), ), colors: None, } } } impl Margin { /// 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 } /// The function, sets a characters for the margin on an each side. pub fn colorize(self, left: C, right: C, top: C, bottom: C) -> Margin { Margin { indent: self.indent, colors: Some(Sides::new(left, right, top, bottom)), } } } #[cfg(feature = "std")] impl TableOption for Margin where C: Into> + Clone, { 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); if let Some(colors) = &self.colors { let margin = Sides::new( Some(colors.left.clone().into()), Some(colors.right.clone().into()), Some(colors.top.clone().into()), Some(colors.bottom.clone().into()), ); cfg.set_margin_color(margin); } } } impl TableOption for Margin where C: Into + Clone, { fn change(self, _: &mut R, cfg: &mut CompactConfig, _: &mut D) { *cfg = cfg.set_margin(self.indent); if let Some(c) = self.colors { // todo: make a new method (BECAUSE INTO doesn't work) try_into(); let colors = Sides::new(c.left.into(), c.right.into(), c.top.into(), c.bottom.into()); *cfg = cfg.set_margin_color(colors); } } } impl TableOption for Margin where C: Into + Clone, { fn change(self, records: &mut R, cfg: &mut CompactMultilineConfig, dimension: &mut D) { self.change(records, cfg.as_mut(), dimension) } } tabled-0.14.0/src/settings/measurement/mod.rs000064400000000000000000000102611046102023000172420ustar 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, PeekableRecords, Records}, grid::util::string::{self, string_width_multiline}, settings::{Height, Width}, }; /// 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; } 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, { let (_, total) = get_table_widths_with_total(records, cfg); (total * self.0) / 100 } } impl Measurement for Percent { fn measure(&self, records: R, cfg: &SpannedConfig) -> usize where R: Records + ExactRecords, { let (_, total) = get_table_heights_width_total(records, cfg); (total * self.0) / 100 } } fn grid_widths( records: &R, ) -> impl Iterator + '_> + '_ { let (count_rows, count_cols) = (records.count_rows(), records.count_columns()); (0..count_rows).map(move |row| { (0..count_cols).map(move |col| string_width_multiline(records.get_text((row, col)))) }) } fn get_table_widths_with_total(records: R, cfg: &SpannedConfig) -> (Vec, usize) where R: Records, { 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 total = list.iter().sum::(); total + cfg.count_vertical(list.len()) } 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)))) }) } fn get_table_heights_width_total(records: R, cfg: &SpannedConfig) -> (Vec, usize) where R: Records, { let list = SpannedGridDimension::height(records, cfg); let total = get_table_total_height(&list, cfg); (list, total) } fn get_table_total_height(list: &[usize], cfg: &SpannedConfig) -> usize { let total = list.iter().sum::(); let counth = cfg.count_horizontal(list.len()); total + counth } tabled-0.14.0/src/settings/merge/mod.rs000064400000000000000000000134731046102023000160240ustar 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, { 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)); let is_row_span_cell = cfg.get_column_span((row, column)).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)).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)); let is_duplicate = text == repeat_value; if is_duplicate { repeat_length += 1; continue; } if repeat_length > 1 { cfg.set_row_span((row + 1, column), repeat_length); } repeat_length = 1; repeat_value = records.get_text((row, column)).to_owned(); } if repeat_length > 1 { cfg.set_row_span((0, column), 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, { 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)); let is_col_span_cell = cfg.get_row_span((row, column)).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)).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)); let is_duplicate = text == repeat_value; if is_duplicate { repeat_length += 1; continue; } if repeat_length > 1 { cfg.set_column_span((row, column + 1), repeat_length); } repeat_length = 1; repeat_value = records.get_text((row, column)).to_owned(); } if repeat_length > 1 { cfg.set_column_span((row, 0), repeat_length); } } } } tabled-0.14.0/src/settings/mod.rs000064400000000000000000000123171046102023000147210ustar 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 possble 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; #[cfg(feature = "std")] #[cfg_attr(docsrs, doc(cfg(feature = "std")))] pub mod object; #[cfg(feature = "std")] mod modify; mod alignment; mod extract; mod margin; mod padding; mod rotate; #[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 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 locator; #[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 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; #[cfg(feature = "std")] #[cfg_attr(docsrs, doc(cfg(feature = "std")))] pub use modify::{Modify, ModifyList}; pub use self::{ alignment::Alignment, extract::Extract, margin::Margin, padding::Padding, rotate::Rotate, style::Style, }; #[cfg(feature = "std")] #[cfg_attr(docsrs, doc(cfg(feature = "std")))] pub use self::{ color::Color, concat::Concat, disable::Disable, duplicate::Dup, format::Format, height::Height, highlight::Highlight, merge::Merge, panel::Panel, shadow::Shadow, span::Span, style::Border, width::Width, }; tabled-0.14.0/src/settings/modify.rs000064400000000000000000000071111046102023000154250ustar 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.14.0/src/settings/object/cell.rs000064400000000000000000000026741046102023000163340ustar 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, 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((row, col): Position) -> Self { Self(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.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.14.0/src/settings/object/columns.rs000064400000000000000000000110631046102023000170650ustar 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.14.0/src/settings/object/frame.rs000064400000000000000000000026611046102023000165030ustar 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.14.0/src/settings/object/mod.rs000064400000000000000000000533701046102023000161730ustar 00000000000000//! This module contains a list of primitives that implement a [`Object`] trait. //! They help to locate a necessary segment on a [`Table`]. //! //! [`Table`]: crate::Table mod cell; mod columns; mod frame; 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 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<(usize, usize)>, 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(Entity::Cell(p.0, p.1)); } } 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(Entity::Cell(p.0, p.1)); } } 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(Entity::Cell(p.0, p.1)); } } } 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<(usize, usize)>, 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(Entity::Cell(p.0, p.1)); } } } 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(Entity::Cell(p.0, p.1)); } } } 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<(usize, usize)>, 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(Entity::Cell(p.0, p.1)); } } } 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(Entity::Cell(p.0, p.1)); } } } 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<(usize, usize)>, } 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(Entity::Cell(p.0, p.1)); } } 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.14.0/src/settings/object/rows.rs000064400000000000000000000107741046102023000164070ustar 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 } } /// 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 = if count_rows == 0 { 0 } else { count_rows - 1 }; EntityOnce::new(Some(Entity::Row(row))) } } impl Sub for LastRow { type Output = LastRowOffset; fn sub(self, rhs: usize) -> Self::Output { LastRowOffset { offset: rhs } } } /// A row which is located by an offset from the last row. #[derive(Debug)] pub struct LastRowOffset { offset: usize, } 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 row = if count_rows == 0 { 0 } else { count_rows - 1 }; if self.offset > row { return EntityOnce::new(None); } let row = 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.14.0/src/settings/object/segment.rs000064400000000000000000000065361046102023000170600ustar 00000000000000use std::ops::{RangeBounds, RangeFull}; 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 { let (row, col) = self.iter.next()?; Some(Entity::Cell(row, col)) } } #[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 = (usize, usize); 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)) } } tabled-0.14.0/src/settings/object/util.rs000064400000000000000000000015131046102023000163610ustar 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.14.0/src/settings/padding/mod.rs000064400000000000000000000121261046102023000163250ustar 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::{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", //! "└──────┘", //! ), //! ); //! ``` //! //! [`Table`]: crate::Table use crate::{ grid::{ color::StaticColor, config::{CompactConfig, CompactMultilineConfig}, config::{Indent, Sides}, }, settings::TableOption, }; #[cfg(feature = "std")] use crate::grid::{color::AnsiColor, config::ColoredConfig, config::Entity}; #[cfg(feature = "std")] use crate::settings::CellOption; /// Padding is responsible for a left/right/top/bottom inner indent of a particular cell. /// #[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'))); /// ``` #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] pub struct Padding { indent: Sides, colors: Option>, } 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), ), colors: None, } } /// 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) } } impl Padding { /// 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 } /// The function, sets a characters for the padding on an each side. pub fn colorize(self, left: C, right: C, top: C, bottom: C) -> Padding { Padding { indent: self.indent, colors: Some(Sides::new(left, right, top, bottom)), } } } #[cfg(feature = "std")] impl CellOption for Padding where C: Into> + Clone, { 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); if let Some(colors) = &self.colors { let pad = Sides::new( Some(colors.left.clone().into()), Some(colors.right.clone().into()), Some(colors.top.clone().into()), Some(colors.bottom.clone().into()), ); cfg.set_padding_color(entity, pad); } } } #[cfg(feature = "std")] impl TableOption for Padding where C: Into> + Clone, { fn change(self, records: &mut R, cfg: &mut ColoredConfig, _: &mut D) { >::change(self, records, cfg, Entity::Global) } } impl TableOption for Padding where C: Into + Clone, { fn change(self, _: &mut R, cfg: &mut CompactConfig, _: &mut D) { *cfg = cfg.set_padding(self.indent); if let Some(c) = self.colors { 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 Padding where C: Into + Clone, { fn change(self, records: &mut R, cfg: &mut CompactMultilineConfig, dimension: &mut D) { self.change(records, cfg.as_mut(), dimension) } } tabled-0.14.0/src/settings/panel/footer.rs000064400000000000000000000013711046102023000165350ustar 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.14.0/src/settings/panel/header.rs000064400000000000000000000013431046102023000164660ustar 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.14.0/src/settings/panel/horizontal_panel.rs000064400000000000000000000040641046102023000206110ustar 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))); 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), text); cfg.set_column_span((self.row, 0), 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 ((row, col), span) in cfg.get_column_spans() { if row < target_row { continue; } cfg.set_column_span((row, col), 1); cfg.set_column_span((row + 1, col), span); } for ((row, col), span) in cfg.get_row_spans() { if row < target_row { continue; } cfg.set_row_span((row, col), 1); cfg.set_row_span((row + 1, col), span); } } tabled-0.14.0/src/settings/panel/mod.rs000064400000000000000000000064651046102023000160270ustar 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.14.0/src/settings/panel/vertical_panel.rs000064400000000000000000000041711046102023000202300ustar 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 } } } 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))); 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), text); cfg.set_row_span((0, self.col), 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 ((row, col), span) in cfg.get_column_spans() { if col < target_column { continue; } cfg.set_column_span((row, col), 1); cfg.set_column_span((row, col + 1), span); } for ((row, col), span) in cfg.get_row_spans() { if col < target_column { continue; } cfg.set_row_span((row, col), 1); cfg.set_row_span((row, col + 1), span); } } tabled-0.14.0/src/settings/peaker/mod.rs000064400000000000000000000043751046102023000161750ustar 00000000000000//! The module contains [`Peaker`] trait and its implementations to be used in [`Height`] and [`Width`]. //! //! [`Width`]: crate::settings::width::Width //! [`Height`]: crate::settings::height::Height /// A strategy of width function. /// It determines the order how the function is applied. pub trait Peaker { /// Creates a new instance. fn create() -> Self; /// This function returns a column index which will be changed. /// Or `None` if no changes are necessary. fn peak(&mut self, min_widths: &[usize], widths: &[usize]) -> Option; } /// A Peaker which goes over column 1 by 1. #[derive(Debug, Default, Clone)] pub struct PriorityNone { i: usize, } impl Peaker for PriorityNone { fn create() -> Self { Self { i: 0 } } 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) } } /// A Peaker which goes over the biggest column first. #[derive(Debug, Default, Clone)] pub struct PriorityMax; impl Peaker for PriorityMax { fn create() -> Self { Self } fn peak(&mut self, _: &[usize], widths: &[usize]) -> Option { let col = (0..widths.len()).max_by_key(|&i| widths[i]).unwrap(); if widths[col] == 0 { None } else { Some(col) } } } /// A Peaker which goes over the smallest column first. #[derive(Debug, Default, Clone)] pub struct PriorityMin; impl Peaker for PriorityMin { fn create() -> Self { Self } fn peak(&mut self, min_widths: &[usize], widths: &[usize]) -> Option { let col = (0..widths.len()) .filter(|&i| min_widths.is_empty() || widths[i] > min_widths[i]) .min_by_key(|&i| widths[i]) .unwrap(); if widths[col] == 0 { None } else { Some(col) } } } tabled-0.14.0/src/settings/rotate/mod.rs000064400000000000000000000104221046102023000162120ustar 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) { let count_rows = records.count_rows(); let count_cols = records.count_columns(); match self { Self::Left => { 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), (row, col)); } } 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); } } } Self::Right => { 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), (row, col)); } } 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); } } } Self::Bottom | Self::Top => { 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), (row, col)); } } } } } } tabled-0.14.0/src/settings/settings_list.rs000064400000000000000000000057561046102023000170460ustar 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) {} } fn combine_entity(x1: Entity, x2: Entity) -> Entity { use Entity::*; match (x1, x2) { (_, Global) => Global, (Global, _) => Global, (Column(_), Row(_)) => Global, (Column(a), Column(_)) => Column(a), (Column(a), Cell(_, _)) => Column(a), (Row(_), Column(_)) => Global, (Row(a), Row(_)) => Row(a), (Row(a), Cell(_, _)) => Row(a), (Cell(_, _), Column(a)) => Column(a), (Cell(_, _), Row(a)) => Row(a), (Cell(a, b), Cell(_, _)) => Cell(a, b), } } tabled-0.14.0/src/settings/shadow/mod.rs000064400000000000000000000115601046102023000162050ustar 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::color::AnsiColor, 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: AnsiColor<'static>, 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.14.0/src/settings/span/column.rs000064400000000000000000000057111046102023000164000ustar 00000000000000use crate::{ grid::{ config::{ColoredConfig, Entity, Position, SpannedConfig}, records::{ExactRecords, Records}, }, settings::CellOption, }; /// Columns (Vertical) span. #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] pub struct ColumnSpan { size: usize, } impl ColumnSpan { /// Creates a new column (vertical) span. pub fn new(size: usize) -> Self { Self { size } } /// Creates a new column (vertical) span with a maximux value possible. pub fn max() -> Self { Self::new(usize::MAX) } } impl CellOption for ColumnSpan where R: Records + ExactRecords, { fn change(self, records: &mut R, cfg: &mut ColoredConfig, entity: Entity) { let count_rows = records.count_rows(); let count_cols = records.count_columns(); set_col_spans(cfg, self.size, entity, (count_rows, count_cols)); remove_false_spans(cfg); } } fn set_col_spans(cfg: &mut SpannedConfig, span: usize, entity: Entity, shape: (usize, usize)) { for pos in entity.iter(shape.0, shape.1) { if !is_valid_pos(pos, shape) { continue; } let mut span = span; if !is_column_span_valid(pos.1, span, shape.1) { span = shape.1 - pos.1; } if span_has_intersections(cfg, pos, span) { continue; } set_span_column(cfg, pos, span); } } fn set_span_column(cfg: &mut SpannedConfig, pos: (usize, usize), span: usize) { if span == 0 { let (row, col) = pos; if col == 0 { return; } if let Some(closecol) = closest_visible(cfg, (row, col - 1)) { let span = col + 1 - closecol; cfg.set_column_span((row, closecol), span); } } cfg.set_column_span(pos, span); } fn closest_visible(cfg: &SpannedConfig, mut pos: Position) -> Option { loop { if cfg.is_cell_visible(pos) { return Some(pos.1); } if pos.1 == 0 { return None; } pos.1 -= 1; } } fn is_column_span_valid(col: usize, span: usize, count_cols: usize) -> bool { span + col <= count_cols } fn is_valid_pos((row, col): Position, (count_rows, count_cols): (usize, usize)) -> bool { row < count_rows && col < count_cols } fn span_has_intersections(cfg: &SpannedConfig, (row, col): Position, span: usize) -> bool { for col in col..col + span { if !cfg.is_cell_visible((row, col)) { 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.14.0/src/settings/span/mod.rs000064400000000000000000000034161046102023000156620ustar 00000000000000//! This module contains a [`Span`] settings, it helps to //! make a cell take more space then it generally takes. //! //! # Example //! //! ``` //! use tabled::{settings::{Span, Modify}, Table}; //! //! let data = [[1, 2, 3], [4, 5, 6]]; //! //! let table = Table::new(data) //! .with(Modify::new((2, 0)).with(Span::column(2))) //! .with(Modify::new((0, 1)).with(Span::column(2))) //! .to_string(); //! //! assert_eq!( //! table, //! concat!( //! "+---+---+---+\n", //! "| 0 | 1 |\n", //! "+---+---+---+\n", //! "| 1 | 2 | 3 |\n", //! "+---+---+---+\n", //! "| 4 | 6 |\n", //! "+---+---+---+", //! ) //! ) //! ``` 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`]. /// /// It will be ignored if: /// - cell position is out of scope /// - size is bigger then the total number of columns. /// - size is bigger then the total number of rows. /// /// ```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))); /// ``` /// /// [`Table`]: crate::Table #[derive(Debug)] pub struct Span; impl Span { /// New constructs a horizontal/column [`Span`]. /// /// If size is bigger then the total number of columns it will be ignored. pub fn column(size: usize) -> ColumnSpan { ColumnSpan::new(size) } /// New constructs a vertical/row [`Span`]. /// /// If size is bigger then the total number of rows it will be ignored. pub fn row(size: usize) -> RowSpan { RowSpan::new(size) } } tabled-0.14.0/src/settings/span/row.rs000064400000000000000000000057751046102023000157240ustar 00000000000000use crate::{ grid::{ config::{ColoredConfig, Entity, Position, SpannedConfig}, records::{ExactRecords, Records}, }, settings::CellOption, }; /// Row (vertical) span. #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] pub struct RowSpan { size: usize, } impl RowSpan { /// Creates a new row (vertical) span. pub const fn new(size: usize) -> Self { Self { size } } /// Creates a new row (vertical) span with a maximux value possible. pub const fn max() -> Self { Self::new(usize::MAX) } } impl CellOption for RowSpan where R: Records + ExactRecords, { fn change(self, records: &mut R, cfg: &mut ColoredConfig, entity: Entity) { let count_rows = records.count_rows(); let count_cols = records.count_columns(); set_row_spans(cfg, self.size, entity, (count_rows, count_cols)); remove_false_spans(cfg); } } fn set_row_spans(cfg: &mut SpannedConfig, span: usize, entity: Entity, shape: (usize, usize)) { for pos in entity.iter(shape.0, shape.1) { if !is_valid_pos(pos, shape) { continue; } let mut span = span; if !is_row_span_valid(pos.0, span, shape.0) { span = shape.0 - pos.0; } if span_has_intersections(cfg, pos, span) { continue; } set_span_row(cfg, pos, span); } } fn set_span_row(cfg: &mut SpannedConfig, pos: (usize, usize), span: usize) { if span == 0 { let (row, col) = pos; if row == 0 { return; } if let Some(closerow) = closest_visible_row(cfg, (row - 1, col)) { let span = row + 1 - closerow; cfg.set_row_span((closerow, col), span); } } cfg.set_row_span(pos, span); } fn closest_visible_row(cfg: &SpannedConfig, mut pos: Position) -> Option { loop { if cfg.is_cell_visible(pos) { return Some(pos.0); } if pos.0 == 0 { // can happen if we have a above horizontal spanned cell return None; } pos.0 -= 1; } } fn is_row_span_valid(row: usize, span: usize, count_rows: usize) -> bool { span + row <= count_rows } fn is_valid_pos((row, col): Position, (count_rows, count_cols): (usize, usize)) -> bool { row < count_rows && col < count_cols } fn span_has_intersections(cfg: &SpannedConfig, (row, col): Position, span: usize) -> bool { for row in row..row + span { if !cfg.is_cell_visible((row, col)) { 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.14.0/src/settings/split/mod.rs000064400000000000000000000334521046102023000160570ustar 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 papergrid::{config::Position, records::PeekableRecords}; use crate::grid::records::{ExactRecords, 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 /// /// # Example /// /// ```rust,no_run /// 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 Cfg, _: &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) != "" { 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), Direction::Row => (primary_index, secondary_index), } } /// 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.14.0/src/settings/style/border.rs000064400000000000000000000104161046102023000165550ustar 00000000000000use crate::{ grid::{ config::{Border as GBorder, ColoredConfig, Entity}, records::{ExactRecords, Records}, }, settings::CellOption, }; /// 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::{Modify, style::{Style, Border}, object::Rows}}; /// # let data: Vec<&'static str> = Vec::new(); /// let table = Table::new(&data) /// .with(Style::ascii()) /// .with(Modify::new(Rows::single(0)).with(Border::default().top('x'))); /// ``` #[derive(Debug, Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] pub struct Border(GBorder); 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 { Self(GBorder::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 } /// Set a top border character. pub const fn top(mut self, c: char) -> Self { self.0.top = Some(c); self } /// Set a bottom border character. pub const fn bottom(mut self, c: char) -> Self { self.0.bottom = Some(c); self } /// Set a left border character. pub const fn left(mut self, c: char) -> Self { self.0.left = Some(c); self } /// Set a right border character. pub const fn right(mut self, c: char) -> Self { self.0.right = Some(c); self } /// Set a top left intersection character. pub const fn corner_top_left(mut self, c: char) -> Self { self.0.left_top_corner = Some(c); self } /// Set a top right intersection character. pub const fn corner_top_right(mut self, c: char) -> Self { self.0.right_top_corner = Some(c); self } /// Set a bottom left intersection character. pub const fn corner_bottom_left(mut self, c: char) -> Self { self.0.left_bottom_corner = Some(c); self } /// Set a bottom right intersection character. pub const fn corner_bottom_right(mut self, c: char) -> Self { self.0.right_bottom_corner = Some(c); self } } impl CellOption for Border 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.0); } } } impl From> for Border { fn from(b: GBorder) -> Border { Border(b) } } impl From for GBorder { fn from(value: Border) -> Self { value.0 } } #[derive(Debug, Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] pub struct EmptyBorder; 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); } } } tabled-0.14.0/src/settings/style/border_char.rs000064400000000000000000000047071046102023000175600ustar 00000000000000use crate::{ grid::config::{ColoredConfig, Entity, Position, SpannedConfig}, grid::records::{ExactRecords, Records}, settings::CellOption, }; use super::Offset; /// [`BorderChar`] sets a char to a specific location on a horizontal line. /// /// # Example /// /// ```rust /// use tabled::{Table, settings::{style::{Style, BorderChar, Offset}, Modify, object::{Object, Rows, Columns}}}; /// /// let mut table = Table::new(["Hello World"]); /// table /// .with(Style::markdown()) /// .with(Modify::new(Rows::single(1)) /// .with(BorderChar::horizontal(':', Offset::Begin(0))) /// .with(BorderChar::horizontal(':', Offset::End(0))) /// ) /// .with(Modify::new((1, 0).and((1, 1))).with(BorderChar::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 BorderChar { c: char, offset: Offset, horizontal: bool, } impl BorderChar { /// Creates a [`BorderChar`] which overrides horizontal line. pub fn horizontal(c: char, offset: Offset) -> Self { Self { c, offset, horizontal: true, } } /// Creates a [`BorderChar`] which overrides vertical line. pub fn vertical(c: char, offset: Offset) -> Self { Self { c, offset, horizontal: false, } } } impl CellOption for BorderChar 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.14.0/src/settings/style/border_color.rs000064400000000000000000000104621046102023000177540ustar 00000000000000//! This module contains a configuration of a Border to set its color via [`BorderColor`]. use crate::{ grid::{ color::AnsiColor, config::{Border, ColoredConfig, Entity}, records::{ExactRecords, Records}, }, settings::{color::Color, CellOption, TableOption}, }; /// BorderColored represents a colored border of a Cell. /// /// ```rust,no_run /// # use tabled::{settings::{style::BorderColor, Style, Color, object::Rows, Modify}, Table}; /// # /// # let data: Vec<&'static str> = Vec::new(); /// # /// let table = Table::new(&data) /// .with(Style::ascii()) /// .with(Modify::new(Rows::single(0)).with(BorderColor::default().top(Color::FG_RED))); /// ``` #[derive(Debug, Clone, Default, Eq, PartialEq)] pub struct BorderColor(Border>); impl BorderColor { /// This function constructs a cell borders with all sides set. #[allow(clippy::too_many_arguments)] pub fn full( top: Color, bottom: Color, left: Color, right: Color, top_left: Color, top_right: Color, bottom_left: Color, bottom_right: Color, ) -> Self { Self(Border::full( top.into(), bottom.into(), left.into(), right.into(), top_left.into(), top_right.into(), bottom_left.into(), bottom_right.into(), )) } /// This function constructs a cell borders with all sides's char set to a given character. /// It behaves like [`Border::full`] with the same character set to each side. pub fn filled(c: Color) -> Self { let c: AnsiColor<'_> = c.into(); Self(Border { top: Some(c.clone()), bottom: Some(c.clone()), left: Some(c.clone()), right: Some(c.clone()), left_bottom_corner: Some(c.clone()), left_top_corner: Some(c.clone()), right_bottom_corner: Some(c.clone()), right_top_corner: Some(c), }) } /// Set a top border character. pub fn top(mut self, c: Color) -> Self { self.0.top = Some(c.into()); self } /// Set a bottom border character. pub fn bottom(mut self, c: Color) -> Self { self.0.bottom = Some(c.into()); self } /// Set a left border character. pub fn left(mut self, c: Color) -> Self { self.0.left = Some(c.into()); self } /// Set a right border character. pub fn right(mut self, c: Color) -> Self { self.0.right = Some(c.into()); self } /// Set a top left intersection character. pub fn corner_top_left(mut self, c: Color) -> Self { self.0.left_top_corner = Some(c.into()); self } /// Set a top right intersection character. pub fn corner_top_right(mut self, c: Color) -> Self { self.0.right_top_corner = Some(c.into()); self } /// Set a bottom left intersection character. pub fn corner_bottom_left(mut self, c: Color) -> Self { self.0.left_bottom_corner = Some(c.into()); self } /// Set a bottom right intersection character. pub fn corner_bottom_right(mut self, c: Color) -> Self { self.0.right_bottom_corner = Some(c.into()); self } } impl CellOption for BorderColor where R: Records + ExactRecords, { fn change(self, records: &mut R, cfg: &mut ColoredConfig, entity: Entity) { let count_rows = records.count_rows(); let count_columns = records.count_columns(); let border_color = &self.0; for pos in entity.iter(count_rows, count_columns) { cfg.set_border_color(pos, border_color.clone()); } } } impl TableOption for BorderColor 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 border_color = &self.0; for row in 0..count_rows { for col in 0..count_columns { cfg.set_border_color((row, col), border_color.clone()); } } } } impl From for Border> { fn from(val: BorderColor) -> Self { val.0 } } tabled-0.14.0/src/settings/style/border_text.rs000064400000000000000000000155751046102023000176340ustar 00000000000000use crate::{ grid::{ color::AnsiColor, config::{self, ColoredConfig, SpannedConfig}, dimension::{Dimension, Estimate}, records::{ExactRecords, Records}, }, settings::{ object::{FirstRow, LastRow}, Color, TableOption, }, }; use super::Offset; /// [`BorderText`] writes a custom text on a border. /// /// # Example /// /// ```rust /// use tabled::{Table, settings::style::BorderText, settings::object::Rows}; /// /// let mut table = Table::new(["Hello World"]); /// table /// .with(BorderText::new("+-.table").horizontal(Rows::first())); /// /// assert_eq!( /// table.to_string(), /// "+-.table------+\n\ /// | &str |\n\ /// +-------------+\n\ /// | Hello World |\n\ /// +-------------+" /// ); /// ``` #[derive(Debug)] pub struct BorderText { // todo: change to T and specify to be As text: String, offset: Offset, color: Option>, line: L, } impl BorderText<()> { /// Creates a [`BorderText`] instance. /// /// Lines are numbered from 0 to the `count_rows` included /// (`line >= 0 && line <= count_rows`). pub fn new>(text: S) -> Self { BorderText { text: text.into(), line: (), offset: Offset::Begin(0), color: None, } } } impl BorderText { /// Set a line on which we will set the text. pub fn horizontal(self, line: L) -> BorderText { BorderText { line, text: self.text, offset: self.offset, color: self.color, } } /// Set an offset from which the text will be started. pub fn offset(self, offset: Offset) -> Self { BorderText { offset, text: self.text, line: self.line, color: self.color, } } /// Set a color of the text. pub fn color(self, color: Color) -> Self { BorderText { color: Some(color.into()), text: self.text, line: self.line, offset: self.offset, } } } impl TableOption for BorderText 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) { dims.estimate(records, cfg); let shape = (records.count_rows(), records.count_columns()); let line = self.line; set_horizontal_chars(cfg, dims, self.offset, line, &self.text, &self.color, shape); } } impl TableOption for BorderText 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) { dims.estimate(records, cfg); let shape = (records.count_rows(), records.count_columns()); let line = 0; set_horizontal_chars(cfg, dims, self.offset, line, &self.text, &self.color, shape); } } impl TableOption for BorderText 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) { dims.estimate(records, cfg); let shape = (records.count_rows(), records.count_columns()); let line = records.count_rows(); set_horizontal_chars(cfg, dims, self.offset, line, &self.text, &self.color, shape); } } fn set_horizontal_chars( cfg: &mut SpannedConfig, dims: &D, offset: Offset, line: usize, text: &str, color: &Option>, shape: (usize, usize), ) { let (_, count_columns) = shape; let pos = get_start_pos(cfg, dims, offset, count_columns); 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), shape); b.left_top_corner = b.left_top_corner.map(|_| c); cfg.set_border((line, 0), b); if let Some(color) = color.as_ref() { let mut b = cfg.get_border_color((line, 0), shape).cloned(); b.left_top_corner = Some(color.clone()); cfg.set_border_color((line, 0), 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), c, config::Offset::Begin(off)); if let Some(color) = color.as_ref() { cfg.set_horizontal_color( (line, col), 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), shape); b.right_top_corner = b.right_top_corner.map(|_| c); cfg.set_border((line, col), b); if let Some(color) = color.as_ref() { let mut b = cfg.get_border_color((line, col), shape).cloned(); b.right_top_corner = Some(color.clone()); cfg.set_border_color((line, col), b); } } } } } fn get_start_pos( cfg: &SpannedConfig, dims: &D, offset: Offset, count_columns: usize, ) -> Option { let totalw = total_width(cfg, dims, count_columns); match offset { Offset::Begin(i) => { if i > totalw { None } else { Some(i) } } Offset::End(i) => { if i > totalw { None } else { Some(totalw - i) } } } } fn total_width(cfg: &SpannedConfig, dims: &D, count_columns: usize) -> usize { let mut totalw = cfg.has_vertical(0, count_columns) as usize; for col in 0..count_columns { totalw += dims.get_width(col); totalw += cfg.has_vertical(col + 1, count_columns) as usize; } totalw } tabled-0.14.0/src/settings/style/builder.rs000064400000000000000000001205671046102023000167370ustar 00000000000000//! This module contains a compile time style builder [`Style`]. use core::marker::PhantomData; use crate::{ grid::config::{Borders, CompactConfig, CompactMultilineConfig}, settings::TableOption, }; #[cfg(feature = "std")] use crate::grid::config::ColoredConfig; use super::{HorizontalLine, Line, VerticalLine}; /// 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 [`RawStyle`] to have more control using [`Into`] implementation. /// /// # Example /// #[cfg_attr(feature = "std", doc = "```")] #[cfg_attr(not(feature = "std"), doc = "```ignore")] /// use tabled::{Table, settings::Style}; /// /// let style = Style::ascii() /// .bottom('*') /// .intersection(' '); /// /// let data = vec!["Hello", "2021"]; /// let table = Table::new(&data).with(style).to_string(); /// /// println!("{}", table); /// ``` /// /// [`Table`]: crate::Table /// [`RawStyle`]: crate::settings::style::RawStyle /// [`Style::corner_top_left`]: Style::corner_top_left /// [`Style::left`]: Style.left /// [`Style::top`]: Style.function.top #[derive(Debug, Clone)] pub struct Style, VLines = VLineArray<0>> { borders: Borders, horizontals: HLines, verticals: VLines, _top: PhantomData, _bottom: PhantomData, _left: PhantomData, _right: PhantomData, _horizontal: PhantomData, _vertical: PhantomData, } type HLineArray = [HorizontalLine; N]; type VLineArray = [VerticalLine; N]; /// A marker struct which is used in [`Style`]. #[derive(Debug, Clone)] pub struct On; impl Style<(), (), (), (), (), (), (), ()> { /// This style is a style with no styling options on, /// /// ```text /// id destribution 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; /// let style = Style::empty() /// .top('*') /// .bottom('*') /// .vertical('#') /// .intersection_top('*'); /// ``` pub const fn empty() -> Style<(), (), (), (), (), ()> { Style::new( create_borders( Line::empty(), Line::empty(), Line::empty(), None, None, None, ), [], [], ) } /// This style is analog of `empty` but with a vertical space(' ') line. /// /// ```text /// id destribution link /// 0 Fedora https://getfedora.org/ /// 2 OpenSUSE https://www.opensuse.org/ /// 3 Endeavouros https://endeavouros.com/ /// ``` pub const fn blank() -> Style<(), (), (), (), (), On> { Style::new( create_borders( Line::empty(), Line::empty(), Line::empty(), None, None, Some(' '), ), [], [], ) } /// This is a style which relays only on ASCII charset. /// /// It has horizontal and vertical lines. /// /// ```text /// +----+--------------+---------------------------+ /// | id | destribution | 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( Line::full('-', '+', '+', '+'), Line::full('-', '+', '+', '+'), Line::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 | destribution | link /// ----+--------------+--------------------------- /// 0 | Fedora | https://getfedora.org/ /// 2 | OpenSUSE | https://www.opensuse.org/ /// 3 | Endeavouros | https://endeavouros.com/ /// ``` pub const fn psql() -> Style<(), (), (), (), (), On, HLineArray<1>> { Style::new( create_borders( Line::empty(), Line::empty(), Line::empty(), None, None, Some('|'), ), [HorizontalLine::new(1, Line::empty()) .main(Some('-')) .intersection(Some('+'))], [], ) } /// `markdown` style mimics a `Markdown` table style. /// /// ```text /// | id | destribution | 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, HLineArray<1>> { Style::new( create_borders( Line::empty(), Line::empty(), Line::empty(), Some('|'), Some('|'), Some('|'), ), [HorizontalLine::new(1, Line::full('-', '|', '|', '|'))], [], ) } /// This style is analog of [`Style::ascii`] which uses UTF-8 charset. /// /// It has vertical and horizontal split lines. /// /// ```text /// ┌────┬──────────────┬───────────────────────────┐ /// │ id │ destribution │ 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( Line::full('─', '┬', '┌', '┐'), Line::full('─', '┴', '└', '┘'), Line::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 │ destribution │ 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( Line::full('─', '┬', '┌', '┐'), Line::full('─', '┴', '└', '┘'), Line::empty(), Some('│'), Some('│'), Some('│'), ), [HorizontalLine::new(1, Line::full('─', '┼', '├', '┤'))], [], ) } /// This style looks like a [`Style::sharp`] but with rounded corners. /// /// Beware: It uses UTF-8 characters. /// /// ```text /// ╭────┬──────────────┬───────────────────────────╮ /// │ id │ destribution │ 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( Line::full('─', '┬', '╭', '╮'), Line::full('─', '┴', '╰', '╯'), Line::empty(), Some('│'), Some('│'), Some('│'), ), [HorizontalLine::new(1, Line::full('─', '┼', '├', '┤'))], [], ) } /// This style uses a chars which resembles '2 lines'. /// /// Beware: It uses UTF8 characters. /// /// ```text /// ╔════╦══════════════╦═══════════════════════════╗ /// ║ id ║ destribution ║ 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( Line::full('═', '╦', '╔', '╗'), Line::full('═', '╩', '╚', '╝'), Line::full('═', '╬', '╠', '╣'), Some('║'), Some('║'), Some('║'), ), [], [], ) } /// This is a style uses only '.' and ':' chars. /// It has a vertical and horizontal split lines. /// /// ```text /// ................................................. /// : id : destribution : 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( Line::full('.', '.', '.', '.'), Line::full('.', ':', ':', ':'), Line::full('.', ':', ':', ':'), Some(':'), Some(':'), Some(':'), ), [], [], ) } /// This style is one of table views in `ReStructuredText`. /// /// ```text /// ==== ============== =========================== /// id destribution 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( Line::new(Some('='), Some(' '), None, None), Line::new(Some('='), Some(' '), None, None), Line::empty(), None, None, Some(' '), ), [HorizontalLine::new( 1, Line::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 | destribution | 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( Line::full('-', '-', '.', '.'), Line::full('-', '-', '\'', '\''), Line::empty(), Some('|'), Some('|'), Some('|'), ), [], [], ) } } impl Style { /// Frame function returns a frame as a border. /// /// # Example /// /// ``` /// use tabled::{Table, settings::{Style, Highlight, object::Rows}}; /// /// let data = [["10:52:19", "Hello"], ["10:52:20", "World"]]; /// let table = Table::new(data) /// .with(Highlight::new(Rows::first(), Style::modern().get_frame())) /// .to_string(); /// /// assert_eq!( /// table, /// concat!( /// "┌──────────────────┐\n", /// "│ 0 | 1 │\n", /// "└──────────────────┘\n", /// "| 10:52:19 | Hello |\n", /// "+----------+-------+\n", /// "| 10:52:20 | World |\n", /// "+----------+-------+", /// ) /// ); /// ``` #[cfg(feature = "std")] #[cfg_attr(docsrs, doc(cfg(feature = "std")))] pub const fn get_frame(&self) -> super::Border { let mut border = super::Border::filled(' '); if let Some(c) = self.borders.top { border = border.top(c); } if let Some(c) = self.borders.bottom { border = border.bottom(c); } if let Some(c) = self.borders.left { border = border.left(c); } if let Some(c) = self.borders.right { border = border.right(c); } if let Some(c) = self.borders.top_left { border = border.corner_top_left(c); } if let Some(c) = self.borders.bottom_left { border = border.corner_bottom_left(c); } if let Some(c) = self.borders.top_right { border = border.corner_top_right(c); } if let Some(c) = self.borders.bottom_right { border = border.corner_bottom_right(c); } border } /// Get a [`Style`]'s default horizontal line. /// /// It doesn't return an overloaded line via [`Style::horizontals`]. /// /// # Example /// #[cfg_attr(feature = "std", doc = "```")] #[cfg_attr(not(feature = "std"), doc = "```ignore")] /// use tabled::{settings::style::{Style, HorizontalLine, Line}, Table}; /// /// let table = Table::new((0..3).map(|i| ("Hello", "World", i))) /// .with(Style::ascii().remove_horizontal().horizontals([HorizontalLine::new(1, Style::modern().get_horizontal())])) /// .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 get_horizontal(&self) -> Line { Line::new( self.borders.horizontal, self.borders.intersection, self.borders.left_intersection, self.borders.right_intersection, ) } /// Get a [`Style`]'s default horizontal line. /// /// It doesn't return an overloaded line via [`Style::verticals`]. /// /// # Example /// #[cfg_attr(feature = "std", doc = "```")] #[cfg_attr(not(feature = "std"), doc = "```ignore")] /// use tabled::{settings::style::{Style, VerticalLine, Line}, Table}; /// /// let table = Table::new((0..3).map(|i| ("Hello", "World", i))) /// .with(Style::ascii().remove_horizontal().verticals([VerticalLine::new(1, Style::modern().get_vertical())])) /// .to_string(); /// /// assert_eq!( /// table, /// concat!( /// "+-------┬-------+-----+\n", /// "| &str │ &str | i32 |\n", /// "| Hello │ World | 0 |\n", /// "| Hello │ World | 1 |\n", /// "| Hello │ World | 2 |\n", /// "+-------┴-------+-----+", /// ) /// ) /// ``` pub const fn get_vertical(&self) -> Line { Line::new( self.borders.vertical, self.borders.intersection, self.borders.top_intersection, self.borders.bottom_intersection, ) } /// Sets a top border. /// /// Any corners and intersections which were set will be overridden. pub fn top(mut self, c: char) -> Style where for<'a> &'a mut VLines: IntoIterator, { 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); } for vl in &mut self.verticals { vl.line.connector1 = Some(c); } Style::new(self.borders, self.horizontals, self.verticals) } /// Sets a bottom border. /// /// Any corners and intersections which were set will be overridden. pub fn bottom(mut self, c: char) -> Style where for<'a> &'a mut VLines: IntoIterator, { 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); } for vl in &mut self.verticals { vl.line.connector2 = Some(c); } Style::new(self.borders, self.horizontals, self.verticals) } /// Sets a left border. /// /// Any corners and intersections which were set will be overridden. pub fn left(mut self, c: char) -> Style where for<'a> &'a mut HLines: IntoIterator, { 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); } for hl in &mut self.horizontals { hl.line.connector1 = Some(c); } Style::new(self.borders, self.horizontals, self.verticals) } /// Sets a right border. /// /// Any corners and intersections which were set will be overridden. pub fn right(mut self, c: char) -> Style where for<'a> &'a mut HLines: IntoIterator, { 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); } for hl in &mut self.horizontals { hl.line.connector2 = Some(c); } Style::new(self.borders, self.horizontals, self.verticals) } /// Sets a horizontal split line. /// /// Any corners and intersections which were set will be overridden. pub fn horizontal(mut self, c: char) -> Style where for<'a> &'a mut VLines: IntoIterator, { 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); } for vl in &mut self.verticals { vl.line.intersection = Some(c); } Style::new(self.borders, self.horizontals, self.verticals) } /// Sets a vertical split line. /// /// Any corners and intersections which were set will be overridden. pub fn vertical(mut self, c: char) -> Style where for<'a> &'a mut HLines: IntoIterator, { 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); } for hl in &mut self.horizontals { hl.line.intersection = Some(c); } Style::new(self.borders, self.horizontals, self.verticals) } /// Set border horizontal lines. /// /// # Example /// #[cfg_attr(feature = "derive", doc = "```")] #[cfg_attr(not(feature = "derive"), doc = "```ignore")] /// use tabled::{settings::style::{Style, HorizontalLine, Line}, Table}; /// /// let table = Table::new((0..3).map(|i| ("Hello", i))) /// .with(Style::rounded().horizontals((1..4).map(|i| HorizontalLine::new(i, Line::filled('#'))))) /// .to_string(); /// /// assert_eq!( /// table, /// concat!( /// "╭───────┬─────╮\n", /// "│ &str │ i32 │\n", /// "###############\n", /// "│ Hello │ 0 │\n", /// "###############\n", /// "│ Hello │ 1 │\n", /// "###############\n", /// "│ Hello │ 2 │\n", /// "╰───────┴─────╯", /// ) /// ) /// ``` pub fn horizontals(self, lines: NewLines) -> Style where NewLines: IntoIterator + Clone, { Style::new(self.borders, lines, self.verticals) } /// Set border vertical lines. /// /// # Example /// #[cfg_attr(feature = "derive", doc = "```")] #[cfg_attr(not(feature = "derive"), doc = "```ignore")] /// use tabled::{Table, settings::style::{Style, VerticalLine, Line}}; /// /// let table = Table::new((0..3).map(|i| ("Hello", i))) /// .with(Style::rounded().verticals((0..3).map(|i| VerticalLine::new(i, Line::filled('#'))))) /// .to_string(); /// /// assert_eq!( /// table, /// concat!( /// "#───────#─────#\n", /// "# &str # i32 #\n", /// "├───────┼─────┤\n", /// "# Hello # 0 #\n", /// "# Hello # 1 #\n", /// "# Hello # 2 #\n", /// "#───────#─────#", /// ) /// ) /// ``` pub fn verticals(self, lines: NewLines) -> Style where NewLines: IntoIterator + Clone, { Style::new(self.borders, self.horizontals, lines) } /// Removes all horizontal lines set by [`Style::horizontals`] pub fn remove_horizontals(self) -> Style, VLines> { Style::new(self.borders, [], self.verticals) } /// Removes all verticals lines set by [`Style::verticals`] pub fn remove_verticals(self) -> Style> { Style::new(self.borders, self.horizontals, []) } } impl Style { /// Sets a top left corner. pub 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 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 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 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 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 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 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 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 fn intersection(mut self, c: char) -> Self { self.borders.intersection = Some(c); Style::new(self.borders, self.horizontals, self.verticals) } } impl Style { /// Removes top border. pub fn remove_top( mut self, ) -> Style<(), B, L, R, H, V, HLines, VerticalLineIter> where VLines: IntoIterator + Clone, { self.borders.top = None; self.borders.top_intersection = None; self.borders.top_left = None; self.borders.top_right = None; let iter = VerticalLineIter::new(self.verticals.into_iter(), false, true, false); Style::new(self.borders, self.horizontals, iter) } } impl Style { /// Removes bottom border. pub fn remove_bottom( mut self, ) -> Style> where VLines: IntoIterator + Clone, { self.borders.bottom = None; self.borders.bottom_intersection = None; self.borders.bottom_left = None; self.borders.bottom_right = None; let iter = VerticalLineIter::new(self.verticals.into_iter(), false, false, true); Style::new(self.borders, self.horizontals, iter) } } impl Style { /// Removes left border. pub fn remove_left( mut self, ) -> Style, VLines> where HLines: IntoIterator + Clone, { self.borders.left = None; self.borders.left_intersection = None; self.borders.top_left = None; self.borders.bottom_left = None; let iter = HorizontalLineIter::new(self.horizontals.into_iter(), false, true, false); Style::new(self.borders, iter, self.verticals) } } impl Style { /// Removes right border. pub fn remove_right( mut self, ) -> Style, VLines> where HLines: IntoIterator + Clone, { self.borders.right = None; self.borders.right_intersection = None; self.borders.top_right = None; self.borders.bottom_right = None; let iter = HorizontalLineIter::new(self.horizontals.into_iter(), false, false, true); Style::new(self.borders, iter, self.verticals) } } impl Style { /// Removes horizontal split lines. /// /// Not including custom split lines. pub fn remove_horizontal( mut self, ) -> Style> where VLines: IntoIterator + Clone, { self.borders.horizontal = None; self.borders.left_intersection = None; self.borders.right_intersection = None; self.borders.intersection = None; let iter = VerticalLineIter::new(self.verticals.into_iter(), true, false, false); Style::new(self.borders, self.horizontals, iter) } } impl Style { /// Removes vertical split lines. pub fn remove_vertical( mut self, ) -> Style, VLines> where HLines: IntoIterator + Clone, { self.borders.vertical = None; self.borders.top_intersection = None; self.borders.bottom_intersection = None; self.borders.intersection = None; let iter = HorizontalLineIter::new(self.horizontals.into_iter(), true, false, false); Style::new(self.borders, iter, self.verticals) } } impl Style { const fn new(borders: Borders, horizontals: HLines, verticals: VLines) -> Self { Self { borders, horizontals, verticals, _top: PhantomData, _bottom: PhantomData, _left: PhantomData, _right: PhantomData, _horizontal: PhantomData, _vertical: PhantomData, } } /// Return borders of a table. pub const fn get_borders(&self) -> &Borders { &self.borders } /// Return custom horizontals which were set. pub const fn get_horizontals(&self) -> &HLines { &self.horizontals } /// Return custom verticals which were set. pub const fn get_verticals(&self) -> &VLines { &self.verticals } } #[cfg(feature = "std")] impl TableOption for Style where HLines: IntoIterator + Clone, VLines: IntoIterator + Clone, { fn change(self, records: &mut I, cfg: &mut ColoredConfig, dimension: &mut D) { cfg.clear_theme(); cfg.set_borders(self.borders); for hl in self.horizontals { hl.change(records, cfg, dimension); } for vl in self.verticals { vl.change(records, cfg, dimension); } } } impl TableOption for Style where HLines: IntoIterator + Clone, { fn change(self, records: &mut I, cfg: &mut CompactConfig, dimension: &mut D) { *cfg = cfg.set_borders(self.borders); let first_line = self.horizontals.into_iter().next(); if let Some(line) = first_line { line.change(records, cfg, dimension); } } } impl TableOption for Style where HLines: IntoIterator + Clone, { fn change(self, records: &mut I, cfg: &mut CompactMultilineConfig, dimension: &mut D) { self.change(records, cfg.as_mut(), dimension) } } /// An iterator which limits [`Line`] influence on iterations over lines for in [`Style`]. #[derive(Debug, Clone)] pub struct HorizontalLineIter { iter: I, intersection: bool, left: bool, right: bool, } impl HorizontalLineIter { fn new(iter: I, intersection: bool, left: bool, right: bool) -> Self { Self { iter, intersection, left, right, } } } impl Iterator for HorizontalLineIter where I: Iterator, { type Item = HorizontalLine; fn next(&mut self) -> Option { let mut hl = self.iter.next()?; if self.intersection { hl.line.intersection = None; } if self.left { hl.line.connector1 = None; } if self.right { hl.line.connector2 = None; } Some(hl) } } /// An iterator which limits [`Line`] influence on iterations over lines for in [`Style`]. #[derive(Debug, Clone)] pub struct VerticalLineIter { iter: I, intersection: bool, top: bool, bottom: bool, } impl VerticalLineIter { fn new(iter: I, intersection: bool, top: bool, bottom: bool) -> Self { Self { iter, intersection, top, bottom, } } } impl Iterator for VerticalLineIter where I: Iterator, { type Item = VerticalLine; fn next(&mut self) -> Option { let mut hl = self.iter.next()?; if self.intersection { hl.line.intersection = None; } if self.top { hl.line.connector1 = None; } if self.bottom { hl.line.connector2 = None; } Some(hl) } } const fn create_borders( top: Line, bottom: Line, horizontal: Line, left: Option, right: Option, vertical: Option, ) -> Borders { Borders { top: top.main, bottom: bottom.main, top_left: top.connector1, top_right: top.connector2, bottom_left: bottom.connector1, bottom_right: bottom.connector2, top_intersection: top.intersection, bottom_intersection: bottom.intersection, left_intersection: horizontal.connector1, right_intersection: horizontal.connector2, horizontal: horizontal.main, intersection: horizontal.intersection, left, right, vertical, } } tabled-0.14.0/src/settings/style/horizontal_line.rs000064400000000000000000000036401046102023000205010ustar 00000000000000use crate::{ grid::config::{CompactConfig, CompactMultilineConfig}, settings::TableOption, }; #[cfg(feature = "std")] use crate::grid::config::{ColoredConfig, HorizontalLine as GridLine}; use super::Line; /// A horizontal split line which can be used to set a border. #[cfg_attr(not(feature = "std"), allow(dead_code))] #[derive(Debug, Clone)] pub struct HorizontalLine { pub(super) index: usize, pub(super) line: Line, } impl HorizontalLine { /// Creates a new horizontal split line. pub const fn new(index: usize, line: Line) -> Self { Self { index, line } } /// Sets a horizontal character. pub const fn main(mut self, c: Option) -> Self { self.line.main = c; self } /// Sets a vertical intersection character. pub const fn intersection(mut self, c: Option) -> Self { self.line.intersection = c; self } /// Sets a left character. pub const fn left(mut self, c: Option) -> Self { self.line.connector1 = c; self } /// Sets a right character. pub const fn right(mut self, c: Option) -> Self { self.line.connector2 = c; self } } #[cfg(feature = "std")] impl TableOption for HorizontalLine { fn change(self, _: &mut R, cfg: &mut ColoredConfig, _: &mut D) { cfg.insert_horizontal_line(self.index, GridLine::from(self.line)) } } impl TableOption for HorizontalLine { fn change(self, _: &mut R, cfg: &mut CompactConfig, _: &mut D) { if self.index == 1 { *cfg = cfg.set_first_horizontal_line(papergrid::config::Line::from(self.line)); } } } impl TableOption for HorizontalLine { fn change(self, records: &mut R, cfg: &mut CompactMultilineConfig, dimension: &mut D) { self.change(records, cfg.as_mut(), dimension) } } tabled-0.14.0/src/settings/style/line.rs000064400000000000000000000044651046102023000162360ustar 00000000000000#[cfg(feature = "std")] use crate::grid::config::{HorizontalLine, VerticalLine}; /// The structure represent a vertical or horizontal line. #[derive(Debug, Default, Clone, Copy)] pub struct Line { pub(crate) main: Option, pub(crate) intersection: Option, pub(crate) connector1: Option, pub(crate) connector2: Option, } impl Line { /// Creates a new [`Line`] object. pub const fn new( main: Option, intersection: Option, connector1: Option, connector2: Option, ) -> Self { Self { main, intersection, connector1, connector2, } } /// Creates a new [`Line`] object with all chars set. pub const fn full(main: char, intersection: char, connector1: char, connector2: char) -> Self { Self::new( Some(main), Some(intersection), Some(connector1), Some(connector2), ) } /// Creates a new [`Line`] object with all chars set to the provided one. pub const fn filled(c: char) -> Self { Self::full(c, c, c, c) } /// Creates a new [`Line`] object with all chars not set. pub const fn empty() -> Self { Self::new(None, None, None, None) } /// Checks if the line has nothing set. pub const fn is_empty(&self) -> bool { self.main.is_none() && self.intersection.is_none() && self.connector1.is_none() && self.connector2.is_none() } } #[cfg(feature = "std")] impl From for HorizontalLine { fn from(l: Line) -> Self { Self { main: l.main, intersection: l.intersection, left: l.connector1, right: l.connector2, } } } #[cfg(feature = "std")] impl From for VerticalLine { fn from(l: Line) -> Self { Self { main: l.main, intersection: l.intersection, top: l.connector1, bottom: l.connector2, } } } impl From for papergrid::config::Line { fn from(l: Line) -> Self { Self { main: l.main.unwrap_or(' '), intersection: l.intersection, connect1: l.connector1, connect2: l.connector2, } } } tabled-0.14.0/src/settings/style/mod.rs000064400000000000000000000061501046102023000160570ustar 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 ", //! ) //! ) //! ``` //! //! ## [`BorderText`] //! //! 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::{BorderText, Style}}; //! //! let data = vec!["Hello", "2022"]; //! let table = Table::new(&data) //! .with(Style::psql()) //! .with(BorderText::new("Santa").horizontal(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}}; //! //! let data = vec!["Hello", "2022"]; //! let table = Table::new(&data) //! .with(Style::psql()) //! .with(Modify::new((0, 0)).with(Style::modern().get_frame())) //! .to_string(); //! //! assert_eq!( //! table, //! concat!( //! "┌───────┐\n", //! "│ &str │\n", //! "└───────┘\n", //! " Hello \n", //! " 2022 ", //! ) //! ) //! ``` //! //! ## [`RawStyle`] //! //! A different representation of [`Style`]. //! With no checks in place. //! //! It also contains a list of types to support colors. //! //! [`Table`]: crate::Table //! [`BorderText`]: crate::settings::style::BorderText //! [`RawStyle`]: crate::settings::style::RawStyle #[cfg(feature = "std")] mod border; #[cfg(feature = "std")] mod border_char; #[cfg(feature = "std")] mod border_color; #[cfg(feature = "std")] mod border_text; #[cfg(feature = "std")] mod offset; #[cfg(feature = "std")] mod raw_style; #[cfg(feature = "std")] mod span_border_correction; mod builder; mod horizontal_line; mod line; mod vertical_line; #[cfg(feature = "std")] #[cfg_attr(docsrs, doc(cfg(feature = "std")))] pub use self::{ border::Border, border_char::BorderChar, border_color::BorderColor, border_text::BorderText, offset::Offset, raw_style::RawStyle, span_border_correction::BorderSpanCorrection, }; pub use builder::{HorizontalLineIter, On, Style, VerticalLineIter}; pub use horizontal_line::HorizontalLine; pub use line::Line; pub use vertical_line::VerticalLine; tabled-0.14.0/src/settings/style/offset.rs000064400000000000000000000007451046102023000165720ustar 00000000000000use 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. Begin(usize), /// An offset from the end. End(usize), } 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), } } } tabled-0.14.0/src/settings/style/raw_style.rs000064400000000000000000000311051046102023000173070ustar 00000000000000//! This module contains [`RawStyle`] structure, which is analogues to [`Style`] but not generic, //! so sometimes it can be used more conviently. use std::collections::HashMap; use crate::{ grid::{color::AnsiColor, config, config::Borders, config::ColoredConfig, records::Records}, settings::{Color, TableOption}, }; use super::{Border, HorizontalLine, Line, Style, VerticalLine}; /// 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(Default, Debug, Clone)] pub struct RawStyle { borders: Borders, colors: Borders>, horizontals: HashMap, verticals: HashMap, } impl RawStyle { /// Set a top border character. pub fn set_top(&mut self, s: Option) -> &mut Self { self.borders.top = s; self } /// Set a top border color. pub fn set_color_top(&mut self, color: Color) -> &mut Self { self.colors.top = Some(color.into()); self } /// Set a bottom border character. pub fn set_bottom(&mut self, s: Option) -> &mut Self { self.borders.bottom = s; self } /// Set a bottom border color. pub fn set_color_bottom(&mut self, color: Color) -> &mut Self { self.colors.bottom = Some(color.into()); self } /// Set a left border character. pub fn set_left(&mut self, s: Option) -> &mut Self { self.borders.left = s; self } /// Set a left border color. pub fn set_color_left(&mut self, color: Color) -> &mut Self { self.colors.left = Some(color.into()); self } /// Set a right border character. pub fn set_right(&mut self, s: Option) -> &mut Self { self.borders.right = s; self } /// Set a right border color. pub fn set_color_right(&mut self, color: Color) -> &mut Self { self.colors.right = Some(color.into()); self } /// Set a top intersection character. pub fn set_intersection_top(&mut self, s: Option) -> &mut Self { self.borders.top_intersection = s; self } /// Set a top intersection color. pub fn set_color_intersection_top(&mut self, color: Color) -> &mut Self { self.colors.top_intersection = Some(color.into()); self } /// Set a bottom intersection character. pub fn set_intersection_bottom(&mut self, s: Option) -> &mut Self { self.borders.bottom_intersection = s; self } /// Set a bottom intersection color. pub fn set_color_intersection_bottom(&mut self, color: Color) -> &mut Self { self.colors.bottom_intersection = Some(color.into()); self } /// Set a left split character. pub fn set_intersection_left(&mut self, s: Option) -> &mut Self { self.borders.left_intersection = s; self } /// Set a bottom intersection color. pub fn set_color_intersection_left(&mut self, color: Color) -> &mut Self { self.colors.left_intersection = Some(color.into()); self } /// Set a right split character. pub fn set_intersection_right(&mut self, s: Option) -> &mut Self { self.borders.right_intersection = s; self } /// Set a bottom intersection color. pub fn set_color_intersection_right(&mut self, color: Color) -> &mut Self { self.colors.right_intersection = Some(color.into()); self } /// Set an internal character. pub fn set_intersection(&mut self, s: Option) -> &mut Self { self.borders.intersection = s; self } /// Set a bottom intersection color. pub fn set_color_intersection(&mut self, color: Color) -> &mut Self { self.colors.intersection = Some(color.into()); self } /// Set a vertical character. pub fn set_vertical(&mut self, s: Option) -> &mut Self { self.borders.vertical = s; self } /// Set a bottom intersection color. pub fn set_color_vertical(&mut self, color: Color) -> &mut Self { self.colors.vertical = Some(color.into()); self } /// Set a horizontal character. pub fn set_horizontal(&mut self, s: Option) -> &mut Self { self.borders.horizontal = s; self } /// Set a bottom intersection color. pub fn set_color_horizontal(&mut self, color: Color) -> &mut Self { self.colors.horizontal = Some(color.into()); self } /// Set a character for a top left corner. pub fn set_corner_top_left(&mut self, s: Option) -> &mut Self { self.borders.top_left = s; self } /// Set a bottom intersection color. pub fn set_color_corner_top_left(&mut self, color: Color) -> &mut Self { self.colors.top_left = Some(color.into()); self } /// Set a character for a top right corner. pub fn set_corner_top_right(&mut self, s: Option) -> &mut Self { self.borders.top_right = s; self } /// Set a bottom intersection color. pub fn set_color_corner_top_right(&mut self, color: Color) -> &mut Self { self.colors.top_right = Some(color.into()); self } /// Set a character for a bottom left corner. pub fn set_corner_bottom_left(&mut self, s: Option) -> &mut Self { self.borders.bottom_left = s; self } /// Set a bottom intersection color. pub fn set_color_corner_bottom_left(&mut self, color: Color) -> &mut Self { self.colors.bottom_left = Some(color.into()); self } /// Set a character for a bottom right corner. pub fn set_corner_bottom_right(&mut self, s: Option) -> &mut Self { self.borders.bottom_right = s; self } /// Set a bottom intersection color. pub fn set_color_corner_bottom_right(&mut self, color: Color) -> &mut Self { self.colors.bottom_right = Some(color.into()); self } /// Set horizontal border lines. /// /// # Example /// /// ``` /// use std::collections::HashMap; /// use tabled::{Table, settings::style::{Style, Line, RawStyle}}; /// /// let mut style = RawStyle::from(Style::re_structured_text()); /// /// let mut lines = HashMap::new(); /// lines.insert(1, Style::extended().get_horizontal()); /// style.set_horizontals(lines); /// /// let table = Table::new((0..3).map(|i| ("Hello", i))) /// .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_horizontals(&mut self, lines: HashMap) -> &mut Self { self.horizontals = lines; self } /// Insert a horizontal line to a specific row location. pub fn insert_horizontal(&mut self, row: usize, line: Line) -> &mut Self { let _ = self.horizontals.insert(row, line); self } /// Insert a horizontal line to a specific row location. pub fn get_horizontal(&self, row: usize) -> Option { self.horizontals.get(&row).cloned() } /// Set vertical border lines. /// /// # Example /// /// ``` /// use std::collections::HashMap; /// use tabled::{Table, settings::style::{Style, Line, RawStyle}}; /// /// let mut style = RawStyle::from(Style::re_structured_text()); /// /// let mut lines = HashMap::new(); /// lines.insert(1, Style::extended().get_horizontal()); /// style.set_verticals(lines); /// /// let table = Table::new((0..3).map(|i| ("Hello", i))) /// .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_verticals(&mut self, lines: HashMap) -> &mut Self { self.verticals = lines; self } /// Insert a vertical line into specific column location. pub fn insert_vertical(&mut self, column: usize, line: Line) -> &mut Self { let _ = self.verticals.insert(column, line); self } /// Get a left char. pub fn get_left(&self) -> Option { self.borders.left } /// Get a left intersection char. pub fn get_left_intersection(&self) -> Option { self.borders.left_intersection } /// Get a right char. pub fn get_right(&self) -> Option { self.borders.right } /// Get a right intersection char. pub fn get_right_intersection(&self) -> Option { self.borders.right_intersection } /// Get a top char. pub fn get_top(&self) -> Option { self.borders.top } /// Get a top left char. pub fn get_top_left(&self) -> Option { self.borders.top_left } /// Get a top right char. pub fn get_top_right(&self) -> Option { self.borders.top_right } /// Get a top intersection char. pub fn get_top_intersection(&self) -> Option { self.borders.top_intersection } /// Get a bottom intersection char. pub fn get_bottom(&self) -> Option { self.borders.bottom } /// Get a bottom intersection char. pub fn get_bottom_left(&self) -> Option { self.borders.bottom_left } /// Get a bottom intersection char. pub fn get_bottom_right(&self) -> Option { self.borders.bottom_right } /// Get a bottom intersection char. pub fn get_bottom_intersection(&self) -> Option { self.borders.bottom_intersection } /// Returns an outer border of the style. pub fn get_frame(&self) -> Border { Border::from(crate::grid::config::Border { top: self.borders.top, bottom: self.borders.bottom, left: self.borders.left, right: self.borders.right, left_top_corner: self.borders.top_left, right_top_corner: self.borders.top_right, left_bottom_corner: self.borders.bottom_left, right_bottom_corner: self.borders.bottom_right, }) } /// Returns an general borders configuration of the style. pub fn get_borders(&self) -> Borders { self.borders } } impl From> for RawStyle { fn from(borders: Borders) -> Self { Self { borders, horizontals: HashMap::new(), verticals: HashMap::new(), colors: Borders::default(), } } } impl TableOption for RawStyle where R: Records, { fn change(self, records: &mut R, cfg: &mut ColoredConfig, dimension: &mut D) { (&self).change(records, cfg, dimension) } } impl TableOption for &RawStyle { fn change(self, _: &mut R, cfg: &mut ColoredConfig, _: &mut D) { cfg.clear_theme(); cfg.set_borders(self.borders); for (&row, line) in &self.horizontals { cfg.insert_horizontal_line(row, config::HorizontalLine::from(*line)); } for (&col, line) in &self.verticals { cfg.insert_vertical_line(col, config::VerticalLine::from(*line)); } if !self.colors.is_empty() { cfg.set_borders_color(self.colors.clone()); } } } impl From> for RawStyle where HLines: IntoIterator + Clone, VLines: IntoIterator + Clone, { fn from(style: Style) -> Self { let horizontals = style .get_horizontals() .clone() .into_iter() .map(|hr| (hr.index, hr.line)) .collect(); let verticals = style .get_verticals() .clone() .into_iter() .map(|hr| (hr.index, hr.line)) .collect(); Self { borders: *style.get_borders(), horizontals, verticals, colors: Borders::default(), } } } tabled-0.14.0/src/settings/style/span_border_correction.rs000064400000000000000000000157001046102023000220260ustar 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 ((row, c), span) in cfg.get_column_spans() { for col in c..c + span { if col == 0 { continue; } let is_first = col == c; let has_up = row > 0 && has_left(cfg, (row - 1, col), shape); let has_down = row + 1 < shape.0 && has_left(cfg, (row + 1, col), shape); let borders = cfg.get_borders(); let mut border = cfg.get_border((row, col), 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((row, col), border); } } for ((r, col), span) in cfg.get_row_spans() { for row in r + 1..r + span { let mut border = cfg.get_border((row, col), 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), 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), shape); if has_right { border.right_top_corner = borders.left_intersection; } else { border.right_top_corner = borders.vertical; } } cfg.set_border((row, col), border); } } let cells = iter_totaly_spanned_cells(cfg, shape).collect::>(); for (row, col) in cells { if row == 0 { continue; } let mut border = cfg.get_border((row, col), shape); let borders = cfg.get_borders(); let has_right = col + 1 < shape.1 && has_top(cfg, (row, col + 1), shape); let has_up = has_left(cfg, (row - 1, col), shape); if has_up && !has_right { border.right_top_corner = borders.right_intersection; } let has_down = row + 1 < shape.0 && has_left(cfg, (row + 1, col), shape); if has_down { border.left_bottom_corner = borders.top_intersection; } cfg.set_border((row, col), 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_totaly_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)) .filter(move |&p| cfg.is_cell_covered_by_both_spans(p)) }) } tabled-0.14.0/src/settings/style/vertical_line.rs000064400000000000000000000025041046102023000201170ustar 00000000000000#[cfg(feature = "std")] use crate::grid::config::{ColoredConfig, VerticalLine as VLine}; use super::Line; /// A horizontal split line which can be used to set a border. #[cfg_attr(not(feature = "std"), allow(dead_code))] #[derive(Debug, Clone)] pub struct VerticalLine { pub(crate) index: usize, pub(crate) line: Line, } impl VerticalLine { /// Creates a new horizontal split line. pub const fn new(index: usize, line: Line) -> Self { Self { index, line } } /// Sets a horizontal character. pub const fn main(mut self, c: Option) -> Self { self.line.main = c; self } /// Sets a vertical intersection character. pub const fn intersection(mut self, c: Option) -> Self { self.line.intersection = c; self } /// Sets a top character. pub const fn top(mut self, c: Option) -> Self { self.line.connector1 = c; self } /// Sets a bottom character. pub const fn bottom(mut self, c: Option) -> Self { self.line.connector2 = c; self } } #[cfg(feature = "std")] impl crate::settings::TableOption for VerticalLine { fn change(self, _: &mut R, cfg: &mut ColoredConfig, _: &mut D) { cfg.insert_vertical_line(self.index, VLine::from(self.line)); } } tabled-0.14.0/src/settings/table_option.rs000064400000000000000000000050661046102023000166240ustar 00000000000000use crate::grid::config::Entity; /// A trait which is responsible for configuration of a [`Table`]. /// /// [`Table`]: crate::Table pub trait TableOption { /// The function allows modification 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 unnessary 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 unnessary 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) } } } tabled-0.14.0/src/settings/themes/colorization.rs000064400000000000000000000260411046102023000201420ustar 00000000000000use papergrid::{ color::AnsiColor, config::{Entity, Sides}, }; use crate::{ grid::{ config::ColoredConfig, 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 [`RawStyle`] /// /// # 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}"); /// ``` /// /// [`RawStyle`]: crate::settings::style::RawStyle /// [`BorderColor`]: crate::settings::style::BorderColor #[derive(Debug, Clone)] pub struct Colorization { pattern: ColorizationPattern, colors: Vec, } #[derive(Debug, Clone)] 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 = AnsiColor::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.14.0/src/settings/themes/column_names.rs000064400000000000000000000232161046102023000201070ustar 00000000000000use std::cmp; use crate::{ grid::{ config::{AlignmentHorizontal, ColoredConfig, Position}, dimension::{CompleteDimensionVecRecords, Dimension, Estimate}, records::{ vec_records::{CellInfo, VecRecords}, ExactRecords, PeekableRecords, Records, Resizable, }, util::string::string_width, }, settings::{ style::{BorderText, Offset}, 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, grid::config::AlignmentHorizontal}; /// /// let data = vec![ /// vec!["Hello", "World"], /// vec!["Hello", "World"], /// ]; /// /// let mut table = Table::from_iter(data); /// table.with(ColumnNames::new(["head1", "head2"]).set_line(2).set_alignment(AlignmentHorizontal::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::from(AlignmentHorizontal::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"]).set_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 set_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"]).set_line(1)); /// /// assert_eq!( /// table.to_string(), /// "+-------+-------+\n\ /// | Hello | World |\n\ /// +head1--+head2--+" /// ); /// ``` pub fn set_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, /// grid::config::AlignmentHorizontal, /// }; /// /// let mut table = Table::from_iter(vec![vec!["Hello", "World"]]); /// table.with(ColumnNames::new(["head1", "head2"]).set_alignment(AlignmentHorizontal::Right)); /// /// assert_eq!( /// table.to_string(), /// "+--head1+--head2+\n\ /// | Hello | World |\n\ /// +-------+-------+" /// ); /// ``` pub fn set_alignment(self, alignment: T) -> Self where T: Into>, { Self { names: self.names, line: self.line, alignments: alignment.into(), colors: self.colors, } } } impl TableOption>, CompleteDimensionVecRecords<'_>, ColoredConfig> 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 names = match self.names { Some(names) => names, None => collect_head(records), }; let names = names .iter() .take(count_columns) .map(|name| name.lines().next().unwrap_or("")) .collect::>(); dims.estimate(&*records, cfg); let widths = (0..count_columns) .map(|column| { let text = names.get(column).unwrap_or(&""); let width = dims.get_width(column); (width, text) }) .map(|(width, text)| cmp::max(string_width(text), width)) .collect::>(); dims.set_widths(widths.clone()); let mut total_width = 0; for (i, (width, name)) in widths.into_iter().zip(names).enumerate() { let color = get_color(&self.colors, i); let alignment = get_alignment(&self.alignments, i); let left_vertical = get_vertical_width(cfg, (self.line, i), count_columns); let grid_offset = total_width + left_vertical; let btext = get_border_text(name, grid_offset, width, alignment, self.line, color); btext.change(records, cfg, dims); total_width += width + left_vertical; } } } 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_text((0, column))) .map(ToString::to_string) .collect(); records.remove_row(0); names } fn get_border_text( text: &str, offset: usize, available: usize, align: AlignmentHorizontal, line: usize, color: Option<&Color>, ) -> BorderText { let left_indent = get_indent(text, align, available); let offset = Offset::Begin(offset + left_indent); let mut btext = BorderText::new(text).horizontal(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_alignment(alignments: &ListValue, i: usize) -> AlignmentHorizontal { match alignments { ListValue::List(list) => list.get(i).copied().unwrap_or(AlignmentHorizontal::Left), ListValue::Static(alignment) => *alignment, } } fn get_indent(text: &str, align: AlignmentHorizontal, available: usize) -> usize { match align { AlignmentHorizontal::Left => 0, AlignmentHorizontal::Right => available - string_width(text), AlignmentHorizontal::Center => (available - string_width(text)) / 2, } } fn get_vertical_width(cfg: &mut ColoredConfig, pos: Position, count_columns: usize) -> usize { cfg.get_vertical(pos, count_columns) .and_then(unicode_width::UnicodeWidthChar::width) .unwrap_or(0) } #[derive(Debug, Clone)] pub enum ListValue { List(Vec), Static(T), } 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.14.0/src/settings/themes/mod.rs000064400000000000000000000004701046102023000162030ustar 00000000000000//! The module contains a varieity 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; pub use colorization::{Colorization, ExactColorization}; pub use column_names::ColumnNames; tabled-0.14.0/src/settings/width/justify.rs000064400000000000000000000051301046102023000167510ustar 00000000000000//! This module contains [`Justify`] structure, used to set an exact width to each column. use crate::{ grid::config::ColoredConfig, grid::records::{ExactRecords, 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, { 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.14.0/src/settings/width/min_width.rs000064400000000000000000000135311046102023000172420ustar 00000000000000//! This module contains [`MinWidth`] structure, used to increase width of a [`Table`]s or a cell on a [`Table`]. //! //! [`Table`]: crate::Table use std::marker::PhantomData; use crate::{ grid::config::ColoredConfig, grid::config::Entity, grid::dimension::CompleteDimensionVecRecords, grid::records::{ExactRecords, PeekableRecords, Records, RecordsMut}, grid::util::string::{get_lines, string_width_multiline}, 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: PhantomData

, } impl MinWidth where W: Measurement, { /// Creates a new instance of [`MinWidth`]. pub fn new(width: W) -> Self { Self { width, fill: ' ', _priority: PhantomData, } } } 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 chaning 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) -> MinWidth { MinWidth { fill: self.fill, width: self.width, _priority: PhantomData, } } } impl CellOption for MinWidth where W: Measurement, R: Records + ExactRecords + PeekableRecords + RecordsMut, for<'a> &'a R: Records, { 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) { let is_valid_pos = pos.0 < count_rows && pos.1 < count_columns; if !is_valid_pos { continue; } let cell = records.get_text(pos); let cell_width = string_width_multiline(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, ColoredConfig> for MinWidth where W: Measurement, P: Peaker, R: Records + ExactRecords + PeekableRecords, for<'a> &'a R: Records, { fn change( self, records: &mut R, cfg: &mut ColoredConfig, dims: &mut CompleteDimensionVecRecords<'_>, ) { if records.count_rows() == 0 || records.count_columns() == 0 { return; } let nessary_width = self.width.measure(&*records, cfg); let (widths, total_width) = get_table_widths_with_total(&*records, cfg); if total_width >= nessary_width { return; } let widths = get_increase_list(widths, nessary_width, total_width, P::create()); 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::string_width; use std::{borrow::Cow, iter::repeat}; get_lines(s) .map(|line| { let length = string_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.14.0/src/settings/width/mod.rs000064400000000000000000000111641046102023000160370ustar 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.14.0/src/settings/width/truncate.rs000064400000000000000000000343521046102023000171110ustar 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, marker::PhantomData}; use crate::{ grid::{ config::{ColoredConfig, SpannedConfig}, dimension::CompleteDimensionVecRecords, records::{EmptyRecords, ExactRecords, PeekableRecords, Records, RecordsMut}, util::string::{string_width, string_width_multiline}, }, settings::{ measurement::Measurement, peaker::{Peaker, PriorityNone}, CellOption, TableOption, Width, }, }; use super::util::{cut_str, get_table_widths, get_table_widths_with_total}; /// 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: PhantomData

, } #[cfg(feature = "color")] #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] struct TruncateSuffix<'a> { text: Cow<'a, str>, limit: SuffixLimit, try_color: bool, } #[cfg(not(feature = "color"))] #[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 = "color")] 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 fn new(width: W) -> Truncate<'static, W> { Self { width, multiline: false, suffix: None, _priority: PhantomData, } } } 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, suffix: Some(suff), _priority: PhantomData, } } /// 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, suffix: Some(suff), _priority: PhantomData, } } /// Use trancate logic per line, not as a string as a whole. pub fn multiline(self) -> Truncate<'a, W, P> { Truncate { width: self.width, multiline: true, suffix: self.suffix, _priority: self._priority, } } #[cfg(feature = "color")] /// 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, suffix: Some(suff), _priority: PhantomData, } } } 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) -> Truncate<'a, W, PP> { Truncate { width: self.width, multiline: self.multiline, suffix: self.suffix, _priority: PhantomData, } } } impl Truncate<'_, (), ()> { /// Truncate a given string pub fn truncate_text(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, { fn change(self, records: &mut R, cfg: &mut ColoredConfig, entity: papergrid::config::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 (cutted_suffix, rest_width) = make_suffix(x, width); suffix = cutted_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) { let is_valid_pos = pos.0 < count_rows && pos.1 < count_columns; if !is_valid_pos { continue; } let text = records.get_text(pos); let cell_width = string_width_multiline(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 = "color"))] { false } #[cfg(feature = "color")] { _suffix.as_ref().map_or(false, |s| s.try_color) } } fn make_suffix<'a>(suffix: &'a TruncateSuffix<'_>, width: usize) -> (Cow<'a, str>, usize) { let suffix_length = string_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, ColoredConfig> for Truncate<'_, W, P> where W: Measurement, P: Peaker, R: Records + ExactRecords + PeekableRecords + RecordsMut, for<'a> &'a R: Records, { 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 = "color")] try_color: s.try_color, }); let priority = P::create(); let multiline = self.multiline; let widths = truncate_total_width( records, cfg, widths, total, width, 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 for<'a> &'a R: Records, P: Peaker, R: Records + PeekableRecords + ExactRecords + RecordsMut, { 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 = suffix.clone(); 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 = "color")] { 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 = "color"))] { 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))) .for_each(|row| { let (width, width_min) = match cfg.get_column_span((row, col)) { 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.14.0/src/settings/width/util.rs000064400000000000000000000211041046102023000162300ustar 00000000000000use std::borrow::Cow; use crate::{ grid::config::SpannedConfig, grid::dimension::SpannedGridDimension, grid::records::Records, }; pub(crate) fn get_table_widths(records: R, cfg: &SpannedConfig) -> Vec { SpannedGridDimension::width(records, cfg) } pub(crate) fn get_table_widths_with_total( records: R, cfg: &SpannedConfig, ) -> (Vec, usize) { 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 } /// The function cuts the string to a specific width. /// /// BE AWARE: width is expected to be in bytes. pub(crate) fn cut_str(s: &str, width: usize) -> Cow<'_, str> { #[cfg(feature = "color")] { const REPLACEMENT: char = '\u{FFFD}'; let stripped = ansi_str::AnsiStr::ansi_strip(s); let (length, count_unknowns, _) = split_at_pos(&stripped, width); let mut buf = ansi_str::AnsiStr::ansi_cut(s, ..length); if count_unknowns > 0 { let mut b = buf.into_owned(); b.extend(std::iter::repeat(REPLACEMENT).take(count_unknowns)); buf = Cow::Owned(b); } buf } #[cfg(not(feature = "color"))] { cut_str_basic(s, width) } } /// The function cuts the string to a specific width. /// /// BE AWARE: width is expected to be in bytes. #[cfg(not(feature = "color"))] pub(crate) fn cut_str_basic(s: &str, width: usize) -> Cow<'_, str> { const REPLACEMENT: char = '\u{FFFD}'; let (length, count_unknowns, _) = split_at_pos(s, width); let buf = &s[..length]; if count_unknowns == 0 { return Cow::Borrowed(buf); } let mut buf = buf.to_owned(); 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 splited in. /// /// BE AWARE: pos is expected to be in bytes. pub(crate) fn split_at_pos(s: &str, pos: usize) -> (usize, usize, usize) { let mut length = 0; let mut i = 0; for c in s.chars() { if i == pos { break; }; let c_width = unicode_width::UnicodeWidthChar::width(c).unwrap_or_default(); // We cut the chars which takes more then 1 symbol to display, // in order to archive the necessary width. if i + c_width > pos { let count = pos - i; return (length, count, c.len_utf8()); } i += c_width; length += c.len_utf8(); } (length, 0, 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 = "color")] 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::string_width; #[cfg(feature = "color")] use owo_colors::{colors::Yellow, OwoColorize}; #[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!(string_width("🏳️🏳️"), string_width("🏳\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 = "color")] #[test] fn strip_color_test() { let numbers = "123456".red().on_bright_black().to_string(); 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}[0m"); let emojies = "😳😳😳😳😳".red().on_bright_black().to_string(); 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}[0m"); let emojies = "🏳️🏳️".red().on_bright_black().to_string(); 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!( string_width(&emojies), string_width("\u{1b}[31;100m🏳\u{fe0f}🏳\u{1b}[39m\u{1b}[49m") ); } #[test] #[cfg(feature = "color")] fn test_color_strip() { let s = "Collored string" .fg::() .on_truecolor(12, 200, 100) .blink() .to_string(); assert_eq!( cut_str(&s, 1), "\u{1b}[5m\u{1b}[48;2;12;200;100m\u{1b}[33mC\u{1b}[25m\u{1b}[39m\u{1b}[49m" ) } } tabled-0.14.0/src/settings/width/width_list.rs000064400000000000000000000020241046102023000174250ustar 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, C> 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.14.0/src/settings/width/wrap.rs000064400000000000000000001424431046102023000162360ustar 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 std::marker::PhantomData; use crate::{ grid::config::ColoredConfig, grid::dimension::CompleteDimensionVecRecords, grid::records::{EmptyRecords, ExactRecords, PeekableRecords, Records, RecordsMut}, grid::{config::Entity, config::SpannedConfig, util::string::string_width_multiline}, settings::{ measurement::Measurement, peaker::{Peaker, PriorityNone}, width::Width, CellOption, TableOption, }, }; use super::util::{get_table_widths, get_table_widths_with_total, split_at_pos}; /// 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: PhantomData

, } impl Wrap { /// Creates a [`Wrap`] object pub fn new(width: W) -> Self where W: Measurement, { Wrap { width, keep_words: false, _priority: PhantomData, } } } 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) -> Wrap { Wrap { width: self.width, keep_words: self.keep_words, _priority: PhantomData, } } /// 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) -> Self { self.keep_words = true; self } } impl Wrap<(), ()> { /// Wrap a given string pub fn wrap_text(text: &str, width: usize, keeping_words: bool) -> String { wrap_text(text, width, keeping_words) } } impl TableOption, ColoredConfig> for Wrap where W: Measurement, P: Peaker, R: Records + ExactRecords + PeekableRecords + RecordsMut, for<'a> &'a R: Records, { 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 = P::create(); 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, { 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) { let is_valid_pos = pos.0 < records.count_rows() && pos.1 < records.count_columns(); if !is_valid_pos { continue; } let text = records.get_text(pos); let cell_width = string_width_multiline(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, { 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 = "color"))] 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 = "color")] pub(crate) fn wrap_text(text: &str, width: usize, keep_words: bool) -> String { use super::util::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 = "color")] 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 = "color"))] fn chunks(s: &str, width: usize) -> Vec { if width == 0 { return Vec::new(); } const REPLACEMENT: char = '\u{FFFD}'; let mut buf = String::with_capacity(width); let mut list = Vec::new(); let mut i = 0; for c in s.chars() { let c_width = unicode_width::UnicodeWidthChar::width(c).unwrap_or_default(); 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; } } if !buf.is_empty() { list.push(buf); } list } #[cfg(feature = "color")] 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 = unicode_width::UnicodeWidthStr::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 += unicode_width::UnicodeWidthStr::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 = "color"))] 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 = unicode_width::UnicodeWidthStr::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 += unicode_width::UnicodeWidthStr::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 = "color")] 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 += unicode_width::UnicodeWidthChar::width(c).unwrap_or(0); 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 = "color")] mod parsing { 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!(unicode_width::UnicodeWidthChar::width(c), Some(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 = unicode_width::UnicodeWidthChar::width(c).unwrap_or(0); 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 = unicode_width::UnicodeWidthChar::width(c).unwrap_or(0); 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, count_unknowns, split_char_size) = split_at_pos(text, at); 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))) .for_each(|row| { let (width, width_min) = match cfg.get_column_span((row, col)) { 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 = "color"))] let split = |text, width| chunks(text, width).join("\n"); #[cfg(feature = "color")] 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 = "color"))] let chunks = |text, width| chunks(text, width); #[cfg(feature = "color")] 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 = "color"))] #[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 = "color")] #[test] fn split_by_line_keeping_words_test() { #[cfg(feature = "color")] 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 = "color")] #[test] fn split_by_line_keeping_words_color_test() { #[cfg(feature = "color")] let split_keeping_words = |text, width| split_keeping_words(text, width, "", ""); #[cfg(not(feature = "color"))] 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 = "color")] #[test] fn split_by_line_keeping_words_color_2_test() { use ansi_str::AnsiStr; #[cfg(feature = "color")] let split_keeping_words = |text, width| split_keeping_words(text, width, "", ""); #[cfg(not(feature = "color"))] 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 = "color")] #[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 = "color"))] #[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 = "color")] #[test] fn split_keeping_words_4_test() { let split_keeping_words = |text, width| split_keeping_words(text, width, "", ""); #[cfg(not(feature = "color"))] 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 = "color")] #[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 = "color")] #[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 = "color")] #[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 = "color")] #[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 = "color")] #[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 = "color")] #[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 = "color")] #[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 = "color")] #[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 ", ) ); } } // \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\u{1b}[49m\n // \u{1b}[37m\u{1b}[41m(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 " // // // \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\u{1b}[49m\n // \u{1b}[37m\u{1b}[41m(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 " // "\u{1b}[37mReturns\u{1b}[37m \u{1b}[39m\n // \u{1b}[37mthe\u{1b}[37m floor\u{1b}[37m \u{1b}[39m\n // \u{1b}[37mof\u{1b}[37m a\u{1b}[37m \u{1b}[39m\n // \u{1b}[37mnumber\u{1b}[37m \u{1b}[39m\u{1b}[49m\n // \u{1b}[37m\u{1b}[41m(l\u{1b}[39m\u{1b}[37m\u{1b}[41marg\u{1b}[39m\u{1b}[49m\u{1b}[37mest\u{1b}[37m \u{1b}[39m\n // \u{1b}[37minteger\u{1b}[37m \u{1b}[39m\n // \u{1b}[37mless\u{1b}[37m than\u{1b}[37m \u{1b}[39m\n // \u{1b}[37mor\u{1b}[37m equal\u{1b}[37m \u{1b}[39m\n // \u{1b}[37mto\u{1b}[37m that\u{1b}[37m \u{1b}[39m\n // \u{1b}[37mnumber).\u{1b}[39m " tabled-0.14.0/src/tabled.rs000064400000000000000000000066241046102023000135410ustar 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() } } 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.14.0/src/tables/compact.rs000064400000000000000000000216711046102023000152050ustar 00000000000000//! This module contains a [`CompactTable`] table. //! //! 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", //! "-------------+------+------------------------------+---\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 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::string_width, CompactGrid, }, settings::{Style, TableOption}, }; /// A table which consumes an [`IntoRecords`] iterator. /// It assumes that the content has only single line. #[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, D, CompactConfig>, { 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, 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, D: Dimension, W: std::io::Write, { let writer = super::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, D: Dimension, { let mut buf = String::new(); self.fmt(&mut buf).unwrap(); 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 = string_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> { 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::empty() .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.14.0/src/tables/extended.rs000064400000000000000000000222401046102023000153500ustar 00000000000000//! This module contains an [`ExtendedTable`] structure which is useful in cases where //! a structure has a lot of fields. //! #![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); //! ``` use std::borrow::Cow; use std::fmt::{self, Display}; use crate::grid::util::string::string_width; use crate::Tabled; /// `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. /// /// ``` /// use tabled::tables::ExtendedTable; /// /// let data = vec!["Hello", "2021"]; /// let table = ExtendedTable::new(&data).to_string(); /// /// assert_eq!( /// table, /// concat!( /// "-[ RECORD 0 ]-\n", /// "&str | Hello\n", /// "-[ RECORD 1 ]-\n", /// "&str | 2021", /// ) /// ); /// ``` #[derive(Debug, Clone)] pub struct ExtendedTable { fields: Vec, records: Vec>, } 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, } } /// 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 = string_width(suffix); if max < suffix_width { return false; } let max = max - suffix_width; let fields_max_width = self .fields .iter() .map(|s| string_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 } } impl From>> for ExtendedTable { fn from(mut data: Vec>) -> Self { if data.is_empty() { return Self { fields: vec![], records: vec![], }; } let fields = data.remove(0); Self { fields, records: data, } } } 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| string_width(s)) .max() .unwrap_or_default(); let max_values_length = self .records .iter() .map(|record| record.iter().map(|s| string_width(s)).max()) .max() .unwrap_or_default() .unwrap_or_default(); for (i, records) in self.records.iter().enumerate() { write_header_template(f, 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(()) } } 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<'_>, index: usize, max_field_width: usize, max_values_length: usize, ) -> fmt::Result { let mut template = format!("-[ RECORD {index} ]-"); 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 = cut_str_basic(text, max).into_owned(); } let cut_was_done = text.len() < original_len; if !suffix.is_empty() && cut_was_done { text.push_str(suffix); } } fn cut_str_basic(s: &str, width: usize) -> Cow<'_, str> { const REPLACEMENT: char = '\u{FFFD}'; let (length, count_unknowns, _) = split_at_pos(s, width); let buf = &s[..length]; if count_unknowns == 0 { return Cow::Borrowed(buf); } let mut buf = buf.to_owned(); buf.extend(std::iter::repeat(REPLACEMENT).take(count_unknowns)); Cow::Owned(buf) } fn split_at_pos(s: &str, pos: usize) -> (usize, usize, usize) { let mut length = 0; let mut i = 0; for c in s.chars() { if i == pos { break; }; let c_width = unicode_width::UnicodeWidthChar::width(c).unwrap_or(0); // We cut the chars which takes more then 1 symbol to display, // in order to archive the necessary width. if i + c_width > pos { let count = pos - i; return (length, count, c.len_utf8()); } i += c_width; length += c.len_utf8(); } (length, 0, 0) } tabled-0.14.0/src/tables/iter.rs000064400000000000000000000236071046102023000145230ustar 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, DimensionValue, StaticDimension}, records::{ into_records::{ truncate_records::ExactValue, BufColumns, BufRows, LimitColumns, LimitRows, TruncateContent, }, IntoRecords, IterRecords, }, Grid, }, settings::{Style, TableOption}, }; use super::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`]). #[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, StaticDimension, CompactConfig>, { 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. 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, { let mut buf = String::new(); self.fmt(&mut buf).expect("safe"); buf } /// Format table into [`io::Write`]r. pub fn build(self, writer: W) -> io::Result<()> where I: IntoRecords, W: io::Write, { 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 I: IntoRecords, W: fmt::Write, { build_grid(writer, self.records, self.cfg, self.table) } } fn build_grid( f: W, iter: I, cfg: CompactConfig, opts: Settings, ) -> fmt::Result { let dont_sniff = opts.width.is_some() && opts.count_columns.is_some(); if dont_sniff { build_table_with_static_dims(f, iter, cfg, opts) } else if opts.width.is_none() { build_table_sniffing_with_unknown_width(f, iter, cfg, opts) } else { build_table_sniffing_with_known_width(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, { let count_columns = opts.count_columns.unwrap(); let width = opts.width.unwrap(); let height = opts.height.unwrap_or(1); let contentw = ExactValue::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_with_unknown_width( f: W, iter: I, cfg: CompactConfig, opts: Settings, ) -> fmt::Result where W: fmt::Write, I: IntoRecords, { let records = BufRows::new(iter, opts.sniff); let records = BufColumns::from(records); 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 = ExactValue::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_known_width( f: W, iter: I, cfg: CompactConfig, opts: Settings, ) -> fmt::Result where W: fmt::Write, I: IntoRecords, { let records = BufRows::new(iter, opts.sniff); let records = BufColumns::from(records); let count_columns = get_count_columns(&opts, records.as_slice()); let width = opts.width.unwrap(); let contentw = ExactValue::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), } } fn create_config() -> CompactConfig { CompactConfig::default() .set_padding(Sides::new( Indent::spaced(1), Indent::spaced(1), Indent::default(), Indent::default(), )) .set_alignment_horizontal(AlignmentHorizontal::Left) .set_borders(*Style::ascii().get_borders()) } fn build_records( records: I, width: ExactValue<'_>, count_columns: usize, count_rows: Option, ) -> IterRecords>> { let records = TruncateContent::new(records, width); let records = LimitColumns::new(records, count_columns); IterRecords::new(records, count_columns, count_rows) } tabled-0.14.0/src/tables/mod.rs000064400000000000000000000020311046102023000143230ustar 00000000000000//! Module contains a list of table representatives. //! //! ## [`Table`] //! //! A default table implementation. //! //! ## [`IterTable`] //! //! Just like [`Table`] but it's API is a bit different to serve better in context //! where there is a memory limit. //! //! ## [`ExtendedTable`] //! //! It's a table which is useful for large amount of data. //! //! ## [`PoolTable`] //! //! A table with a greather controll of a layout. mod compact; mod util; #[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; tabled-0.14.0/src/tables/table.rs000064400000000000000000000355661046102023000146560ustar 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, Formatting, Indent, Sides, SpannedConfig, }, dimension::{CompleteDimensionVecRecords, Dimension, Estimate, PeekableDimension}, records::{ vec_records::{CellInfo, VecRecords}, ExactRecords, Records, }, PeekableGrid, }, settings::{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 1 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()); /// /// println!("{}", table); /// ``` /// /// [`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 { /// New creates a Table instance. /// /// If you use a reference iterator you'd better use [`FromIterator`] instead. /// As it has a different lifetime constraints and make less copies therefore. 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 = CellInfo::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 = CellInfo::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 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() /// .with(Modify::new(Segment::new(1.., 1..)).with(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 records = Vec::new(); for row in iter { let mut list = Vec::with_capacity(T::LENGTH); for text in row.fields().into_iter() { list.push(text.into_owned()); } records.push(list); } let mut b = Builder::from(records); let _ = b.set_header(T::headers()).hint_column_size(T::LENGTH); b } /// With is 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< VecRecords>, CompleteDimensionVecRecords<'a>, ColoredConfig, >, { 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 } /// 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, V: 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: Vec>> = 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 count_columns = val.count_columns(); let data: Vec>> = val.records.into(); let mut builder = Builder::from(data); let _ = builder.hint_column_size(count_columns); builder } } 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_formatting(Entity::Global, Formatting::new(false, false, 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(); } } } tabled-0.14.0/src/tables/table_pool.rs000064400000000000000000001435611046102023000157020ustar 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 { let value = TableValue::Column( iter.iter_rows() .into_iter() .map(|row| { TableValue::Row( row.into_iter() .map(|cell| cell.as_ref().to_string()) .map(TableValue::Cell) .collect(), ) }) .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::default(), Indent::default(), ); CompactMultilineConfig::default() .set_padding(pad) .set_alignment_horizontal(AlignmentHorizontal::Left) .set_borders(*Style::ascii().get_borders()) } 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 papergrid::{ color::StaticColor, config::{Border, Borders}, util::string::string_width_multiline, }; use crate::{ builder::Builder, grid::{ config::{ AlignmentHorizontal, AlignmentVertical, ColoredConfig, CompactMultilineConfig, Indent, Offset, Sides, }, dimension::{Dimension, DimensionPriority, Estimate, PoolTableDimension}, records::Records, util::string::{count_lines, get_lines, string_dimension, string_width}, }, settings::{Padding, Style, TableOption}, }; 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: &Vec, 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); let _ = 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: &Vec, 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 b = Builder::with_capacity(1); let _ = b.hint_column_size(buf.len()).push_record(buf); let table = b .build() .with(Style::empty()) .with(Padding::zero()) .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 = cfg.get_padding_color(); let pad_color = convert_border_colors(pad_color); let lines_alignemnt = cfg.get_formatting().allow_lines_alignment; let mut borders = *cfg.get_borders(); let bottom_intesection = 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_intesection, 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_alignemnt { for line in get_lines(text) { let line_width = string_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 = string_width_multiline(text); let (left, _) = indent_horizontal(halignment, width, text_width); for line in get_lines(text) { let line_width = string_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); } } struct ConfigCell(PrintContext); impl TableOption for ConfigCell { fn change(self, _: &mut R, cfg: &mut ColoredConfig, _: &mut D) { { // we set a horizontal lines to borders to not complicate logic with cleaning it let mut borders = *cfg.get_borders(); if let Some(line) = cfg.get_horizontal_line(0) { borders.top = line.main; borders.top_left = line.left; borders.top_right = line.right; } if let Some(line) = cfg.get_horizontal_line(1) { borders.bottom = line.main; borders.bottom_left = line.left; borders.bottom_right = line.right; } cfg.clear_theme(); cfg.set_borders(borders); } let mut ctx = self.0; let has_vertical = cfg.get_borders().has_left(); if !ctx.intersections_horizontal.is_empty() && has_vertical { let mut splits = short_splits(&mut ctx.intersections_horizontal, ctx.size.width); squash_splits(&mut splits); let c = cfg.get_borders().bottom_intersection.unwrap_or(' '); cfg_set_top_chars(cfg, &splits, c) } let has_horizontal = cfg.get_borders().has_top(); if !ctx.intersections_vertical.is_empty() && has_horizontal { let mut splits = short_splits(&mut ctx.intersections_vertical, ctx.size.height); squash_splits(&mut splits); let c = cfg.get_borders().right_intersection.unwrap_or(' '); cfg_set_left_chars(cfg, &splits, c) } let mut borders = *cfg.get_borders(); // 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(&mut borders); } if ctx.no_right { cfg_no_right_borders(&mut borders); } cfg.set_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; } fn cfg_set_top_chars(cfg: &mut ColoredConfig, list: &[usize], c: char) { for &split in list { let offset = split; cfg.set_horizontal_char((0, 0), c, Offset::Begin(offset)); } } fn cfg_set_left_chars(cfg: &mut ColoredConfig, list: &[usize], c: char) { for &offset in list { cfg.set_vertical_char((0, 0), c, Offset::Begin(offset)); } } struct NoTopBorders; impl TableOption for NoTopBorders { fn change(self, _: &mut R, cfg: &mut ColoredConfig, _: &mut D) { let mut borders = *cfg.get_borders(); borders.top = None; borders.top_intersection = None; borders.top_left = None; borders.top_right = None; cfg.set_borders(borders); } } struct NoBottomBorders; impl TableOption for NoBottomBorders { fn change(self, _: &mut R, cfg: &mut ColoredConfig, _: &mut D) { let mut borders = *cfg.get_borders(); borders.bottom = None; borders.bottom_intersection = None; borders.bottom_left = None; borders.bottom_right = None; cfg.set_borders(borders); } } struct NoRightBorders; impl TableOption for NoRightBorders { fn change(self, _: &mut R, cfg: &mut ColoredConfig, _: &mut D) { let mut borders = *cfg.get_borders(); borders.top_right = None; borders.bottom_right = None; borders.right = None; borders.right_intersection = None; cfg.set_borders(borders); } } struct NoLeftBorders; impl TableOption for NoLeftBorders { fn change(self, _: &mut R, cfg: &mut ColoredConfig, _: &mut D) { let mut borders = *cfg.get_borders(); borders.top_left = None; borders.bottom_left = None; borders.left = None; borders.left_intersection = None; cfg.set_borders(borders); } } struct TopLeftChangeTopIntersection; impl TableOption for TopLeftChangeTopIntersection { fn change(self, _: &mut R, cfg: &mut ColoredConfig, _: &mut D) { let mut borders = *cfg.get_borders(); borders.top_left = borders.top_intersection; cfg.set_borders(borders); } } struct TopLeftChangeIntersection; impl TableOption for TopLeftChangeIntersection { fn change(self, _: &mut R, cfg: &mut ColoredConfig, _: &mut D) { let mut borders = *cfg.get_borders(); borders.top_left = borders.intersection; cfg.set_borders(borders); } } struct TopLeftChangeToLeft; impl TableOption for TopLeftChangeToLeft { fn change(self, _: &mut R, cfg: &mut ColoredConfig, _: &mut D) { let mut borders = *cfg.get_borders(); borders.top_left = borders.left_intersection; cfg.set_borders(borders); } } struct TopRightChangeToRight; impl TableOption for TopRightChangeToRight { fn change(self, _: &mut R, cfg: &mut ColoredConfig, _: &mut D) { let mut borders = *cfg.get_borders(); borders.top_right = borders.right_intersection; cfg.set_borders(borders); } } struct BottomLeftChangeSplit; impl TableOption for BottomLeftChangeSplit { fn change(self, _: &mut R, cfg: &mut ColoredConfig, _: &mut D) { let mut borders = *cfg.get_borders(); borders.bottom_left = borders.left_intersection; cfg.set_borders(borders); } } struct BottomLeftChangeSplitToIntersection; impl TableOption for BottomLeftChangeSplitToIntersection { fn change(self, _: &mut R, cfg: &mut ColoredConfig, _: &mut D) { let mut borders = *cfg.get_borders(); borders.bottom_left = borders.intersection; cfg.set_borders(borders); } } struct BottomRightChangeToRight; impl TableOption for BottomRightChangeToRight { fn change(self, _: &mut R, cfg: &mut ColoredConfig, _: &mut D) { let mut borders = *cfg.get_borders(); borders.bottom_right = borders.right_intersection; cfg.set_borders(borders); } } struct BottomLeftChangeToBottomIntersection; impl TableOption for BottomLeftChangeToBottomIntersection { fn change(self, _: &mut R, cfg: &mut ColoredConfig, _: &mut D) { let mut borders = *cfg.get_borders(); borders.bottom_left = borders.bottom_intersection; cfg.set_borders(borders); } } struct SetBottomChars<'a>(&'a [usize], char); impl TableOption for SetBottomChars<'_> where R: Records, for<'a> &'a R: Records, for<'a> D: Dimension + Estimate<&'a R, ColoredConfig>, { fn change(self, records: &mut R, cfg: &mut ColoredConfig, dims: &mut D) { dims.estimate(&*records, cfg); let table_width = (0..records.count_columns()) .map(|col| dims.get_width(col)) .sum::() + cfg.count_vertical(records.count_columns()); let mut current_width = 0; for pos in self.0 { current_width += pos; if current_width > table_width { break; } let split_char = self.1; cfg.set_horizontal_char((1, 0), split_char, Offset::Begin(current_width)); current_width += 1; } } } struct SetTopChars<'a>(&'a [usize], char); impl TableOption for SetTopChars<'_> { fn change(self, _: &mut R, cfg: &mut ColoredConfig, _: &mut D) { for &split in self.0 { let offset = split; cfg.set_horizontal_char((0, 0), self.1, Offset::Begin(offset)); } } } struct SetLeftChars<'a>(&'a [usize], char); impl TableOption for SetLeftChars<'_> { fn change(self, _: &mut R, cfg: &mut ColoredConfig, _: &mut D) { for &offset in self.0 { cfg.set_vertical_char((0, 0), self.1, Offset::Begin(offset)); } } } struct GetTopIntersection(char); impl TableOption for &mut GetTopIntersection { fn change(self, _: &mut R, cfg: &mut ColoredConfig, _: &mut D) { self.0 = cfg.get_borders().top_intersection.unwrap_or(' '); } } struct GetBottomIntersection(char); impl TableOption for &mut GetBottomIntersection { fn change(self, _: &mut R, cfg: &mut ColoredConfig, _: &mut D) { self.0 = cfg.get_borders().bottom_intersection.unwrap_or(' '); } } #[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) = string_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 = string_width_multiline(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(|| pad_color.left), (!pad_color.right.is_empty()).then(|| pad_color.right), (!pad_color.top.is_empty()).then(|| pad_color.top), (!pad_color.bottom.is_empty()).then(|| pad_color.bottom), ) } } tabled-0.14.0/src/tables/util/mod.rs000064400000000000000000000000641046102023000153040ustar 00000000000000#[cfg(feature = "std")] pub(crate) mod utf8_writer; tabled-0.14.0/src/tables/util/utf8_writer.rs000064400000000000000000000010171046102023000170060ustar 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.14.0/tests/core/builder_test.rs000064400000000000000000000420721046102023000162730ustar 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!( set_header, { let mut b = Builder::default(); b.set_header(["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.set_header(["1", "2", "3"]); b.push_record(["a", "b", "c"]); b.push_record(["d", "e", "f"]); b.remove_header(); b.build() }, "+---+---+---+" "| a | b | c |" "+---+---+---+" "| d | e | f |" "+---+---+---+" ); test_table!( header_remove_1, { let mut b = Builder::default(); b.set_header(["1", "2", "3", "4", "5"]); b.push_record(["a", "b", "c"]); b.push_record(["d", "e", "f"]); b.remove_header(); b.build() }, "+---+---+---+" "| a | b | c |" "+---+---+---+" "| d | e | f |" "+---+---+---+" ); 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.set_header(["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.set_header(["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.set_header(["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.set_header(["1", "2"]).set_default_text("NaN").push_record(["a", "b", "c"]).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.set_header(["1"]).set_default_text("NaN").push_record(["a", "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(Builder::from_iter([["1", "2", "3"], ["a", "b", "c"], ["d", "e", "f"]]).set_header(["col1", "col2", "col3"]).clone()), "+------+------+------+" "| col1 | col2 | col3 |" "+------+------+------+" "| 1 | 2 | 3 |" "+------+------+------+" "| a | b | c |" "+------+------+------+" "| d | e | f |" "+------+------+------+" ); test_table!( clean_with_columns_1, clean(Builder::from_iter([["", "2", "3"], ["", "b", "c"], ["", "e", "f"]]).set_header(["col1", "col2", "col3"]).clone()), "+------+------+" "| col2 | col3 |" "+------+------+" "| 2 | 3 |" "+------+------+" "| b | c |" "+------+------+" "| e | f |" "+------+------+" ); test_table!( clean_with_columns_2, clean(Builder::from_iter([["1", "", "3"], ["a", "", "c"], ["d", "", "f"]]).set_header(["col1", "col2", "col3"]).clone()), "+------+------+" "| col1 | col3 |" "+------+------+" "| 1 | 3 |" "+------+------+" "| a | c |" "+------+------+" "| d | f |" "+------+------+" ); test_table!( clean_with_columns_3, clean(Builder::from_iter([["1", "2", ""], ["a", "b", ""], ["d", "e", ""]]).set_header(["col1", "col2", "col3"]).clone()), "+------+------+" "| col1 | col2 |" "+------+------+" "| 1 | 2 |" "+------+------+" "| a | b |" "+------+------+" "| d | e |" "+------+------+" ); test_table!( clean_with_columns_4, clean(Builder::from_iter([["", "", "3"], ["", "", "c"], ["", "", "f"]]).set_header(["col1", "col2", "col3"]).clone()), "+------+" "| col3 |" "+------+" "| 3 |" "+------+" "| c |" "+------+" "| f |" "+------+" ); test_table!( clean_with_columns_5, clean(Builder::from_iter([["1", "", ""], ["a", "", ""], ["d", "", ""]]).set_header(["col1", "col2", "col3"]).clone()), "+------+" "| col1 |" "+------+" "| 1 |" "+------+" "| a |" "+------+" "| d |" "+------+" ); test_table!( clean_with_columns_6, clean(Builder::from_iter([["", "2", ""], ["", "b", ""], ["", "e", ""]]).set_header(["col1", "col2", "col3"]).clone()), "+------+" "| col2 |" "+------+" "| 2 |" "+------+" "| b |" "+------+" "| e |" "+------+" ); test_table!( clean_with_columns_7, clean( Builder::from_iter([["", "", ""], ["", "", ""], ["", "", ""]]) .set_header(["col1", "col2", "col3"]) .clone() ), "" ); test_table!( clean_with_columns_8, clean(Builder::from_iter([["", "", ""], ["a", "b", "c"], ["d", "e", "f"]]).set_header(["col1", "col2", "col3"]).clone()), "+------+------+------+" "| col1 | col2 | col3 |" "+------+------+------+" "| a | b | c |" "+------+------+------+" "| d | e | f |" "+------+------+------+" ); test_table!( clean_with_columns_9, clean(Builder::from_iter([["1", "2", "3"], ["", "", ""], ["d", "e", "f"]]).set_header(["col1", "col2", "col3"]).clone()), "+------+------+------+" "| col1 | col2 | col3 |" "+------+------+------+" "| 1 | 2 | 3 |" "+------+------+------+" "| d | e | f |" "+------+------+------+" ); test_table!( clean_with_columns_10, clean(Builder::from_iter([["1", "2", "3"], ["a", "b", "c"], ["", "", ""]]).set_header(["col1", "col2", "col3"]).clone()), "+------+------+------+" "| col1 | col2 | col3 |" "+------+------+------+" "| 1 | 2 | 3 |" "+------+------+------+" "| a | b | c |" "+------+------+------+" ); test_table!( clean_with_columns_11, clean(Builder::from_iter([["", "", ""], ["", "", ""], ["d", "e", "f"]]).set_header(["col1", "col2", "col3"]).clone()), "+------+------+------+" "| col1 | col2 | col3 |" "+------+------+------+" "| d | e | f |" "+------+------+------+" ); test_table!( clean_with_columns_12, clean(Builder::from_iter([["1", "2", "3"], ["", "", ""], ["", "", ""]]).set_header(["col1", "col2", "col3"]).clone()), "+------+------+------+" "| col1 | col2 | col3 |" "+------+------+------+" "| 1 | 2 | 3 |" "+------+------+------+" ); test_table!( clean_with_columns_13, clean(Builder::from_iter([["", "", ""], ["a", "b", "c"], ["", "", ""]]).set_header(["col1", "col2", "col3"]).clone()), "+------+------+------+" "| col1 | col2 | col3 |" "+------+------+------+" "| a | b | c |" "+------+------+------+" ); test_table!( clean_with_columns_14, clean(Builder::from_iter([["1", "", "3"], ["", "", ""], ["d", "", "f"]]).set_header(["col1", "col2", "col3"]).clone()), "+------+------+" "| 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(), "+---+---+-----------+" "| | 0 | 1 |" "+---+---+-----------+" "| 0 | n | name |" "+---+---+-----------+" "| 1 | 0 | Dmitriy |" "+---+---+-----------+" "| 2 | 1 | Vladislav |" "+---+---+-----------+" ); test_table!( index_set_name, Builder::from_iter([["n", "name"], ["0", "Dmitriy"], ["1", "Vladislav"]]) .index() .name(Some("A index name".into())) .build(), "+--------------+---+-----------+" "| | 0 | 1 |" "+--------------+---+-----------+" "| A index name | | |" "+--------------+---+-----------+" "| 0 | n | name |" "+--------------+---+-----------+" "| 1 | 0 | Dmitriy |" "+--------------+---+-----------+" "| 2 | 1 | Vladislav |" "+--------------+---+-----------+" ); test_table!( index_enumeration, Builder::from_iter([["n", "name"], ["0", "Dmitriy"], ["1", "Vladislav"]]) .index() .hide() .build(), "+---+-----------+" "| 0 | 1 |" "+---+-----------+" "| n | name |" "+---+-----------+" "| 0 | Dmitriy |" "+---+-----------+" "| 1 | Vladislav |" "+---+-----------+" ); test_table!( set_index, Builder::from_iter([["n", "name"], ["0", "Dmitriy"], ["1", "Vladislav"]]) .index() .column(1) .build(), "+-----------+---+" "| | 0 |" "+-----------+---+" "| 1 | |" "+-----------+---+" "| name | n |" "+-----------+---+" "| 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(), "+-----------+---+" "| | 0 |" "+-----------+---+" "| Hello | |" "+-----------+---+" "| name | n |" "+-----------+---+" "| 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(), "+-----------+---+" "| | 0 |" "+-----------+---+" "| name | 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 | 2 |" "+---+------+---------+-----------+" "| 0 | n | 0 | 1 |" "+---+------+---------+-----------+" "| 1 | name | Dmitriy | Vladislav |" "+---+------+---------+-----------+" "| 2 | zz | 123 | 123 |" "+---+------+---------+-----------+" ); fn clean(mut b: Builder) -> String { b.clean(); b.build().to_string() } tabled-0.14.0/tests/core/compact_table.rs000064400000000000000000000055001046102023000163760ustar 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.14.0/tests/core/extended_table_test.rs000064400000000000000000000276421046102023000176220ustar 00000000000000#![cfg(feature = "std")] #[cfg(feature = "color")] use owo_colors::{AnsiColors, OwoColorize}; use tabled::{tables::ExtendedTable, Tabled}; use crate::matrix::Matrix; use testing_table::{static_table, test_table}; 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), "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), "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), "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 = "color")] #[test] fn display_colored() { let mut data = Matrix::list::<3, 3>(); data[0][2] = "https://getfedora.org/" .red() .on_color(AnsiColors::Blue) .to_string(); data[1][2] = "https://www.opensuse.org/" .green() .on_color(AnsiColors::Black) .to_string(); data[2][2] = "https://endeavouros.com/".blue().underline().to_string(); 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 = "color")] #[test] fn display_with_truncate_colored() { let mut data = Matrix::list::<2, 3>(); data[0][2] = "https://getfedora.org/".red().to_string(); data[1][1] = "https://endeavouros.com/" .white() .on_color(AnsiColors::Black) .to_string(); 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" ) ); } tabled-0.14.0/tests/core/index_test.rs000064400000000000000000000134121046102023000157500ustar 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(), "+---+---+---+---+" "| | 0 | 1 | 2 |" "+---+---+---+---+" "| 0 | 1 | 2 | 3 |" "+---+---+---+---+" "| 1 | a | b | c |" "+---+---+---+---+" "| 2 | 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("Hello World".into())) .build(), "+-------------+---+---+---+" "| | 0 | 1 | 2 |" "+-------------+---+---+---+" "| Hello World | | | |" "+-------------+---+---+---+" "| 0 | 1 | 2 | 3 |" "+-------------+---+---+---+" "| 1 | a | b | c |" "+-------------+---+---+---+" "| 2 | 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 | 2 |" "+---+---+---+---+" "| 0 | 1 | a | d |" "+---+---+---+---+" "| 1 | 2 | b | e |" "+---+---+---+---+" "| 2 | 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_dosnt_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] 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,); } tabled-0.14.0/tests/core/iter_table.rs000064400000000000000000000126471046102023000157250ustar 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.14.0/tests/core/mod.rs000064400000000000000000000001761046102023000143640ustar 00000000000000mod builder_test; mod compact_table; mod extended_table_test; mod index_test; mod iter_table; mod pool_table; mod table_test; tabled-0.14.0/tests/core/pool_table.rs000064400000000000000000000611521046102023000157260ustar 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 = "color")] use tabled::grid::color::StaticColor; 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 = "color")] test_table!( pool_table_padding_2, PoolTable::new(Matrix::with_no_frame(3, 3).to_vec()) .with(Padding::new(1, 2, 3, 4) .fill('!', '@', '#', '$') .colorize( StaticColor::new("\u{1b}[34m", "\u{1b}[39m"), StaticColor::new("\u{1b}[34m", "\u{1b}[39m"), StaticColor::new("\u{1b}[34m", "\u{1b}[39m"), StaticColor::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.14.0/tests/core/table_test.rs000064400000000000000000001064621046102023000157400ustar 00000000000000#![cfg(feature = "std")] use std::iter::FromIterator; use tabled::{ builder::Builder, settings::{formatting::Charset, Height, Modify, Padding, Settings, 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(clippy::needless_borrow)] 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 build │" "│ s 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/inst │" "│ allation.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 build │" "│ s 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/inst │" "│ allation.md#dependencies │" "│ │" "└──────────────────────────────────────────────────────────────────────────────────────────────────┘" ); #[cfg(feature = "derive")] mod derived { use super::*; use std::collections::{BTreeMap, BTreeSet}; use tabled::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 ☄\u{fe0f} 💥 🔥 🌪 | |" "+------------------------------------+-----------------+-------------------------------+--------+" "| Keep it simple | Unknown | 🍳 | 100 |" "+------------------------------------+-----------------+-------------------------------+--------+" ); } #[cfg(feature = "color")] #[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 build │" "│ s 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/inst │" "│ allation.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])) }, "┌┬┐" "│││" "├┼┤" "└┴┘" ); tabled-0.14.0/tests/derive/derive_test.rs000064400000000000000000001152431046102023000164520ustar 00000000000000#![cfg(feature = "derive")] #![cfg(feature = "std")] use tabled::Tabled; // https://users.rust-lang.org/t/create-a-struct-from-macro-rules/19829 macro_rules! test_tuple { ( $test_name:ident, t: $(#[$struct_attr:meta])* { $( $(#[$attr:meta])* $ty:ty)* }, init: { $($init:expr)* }, expected: $headers:expr, $fields:expr, $(pre: { $($init_block:stmt)* })? ) => { #[test] fn $test_name() { $($($init_block)*)? #[derive(Tabled)] 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, t: $(#[$struct_attr:meta])* { $( $(#[$var_attr:meta])* $var:ident $({ $( $(#[$attr:meta])* $field:ident: $ty:ty),* $(,)? })? $(( $( $(#[$attr2:meta])* $ty2:ty),* $(,)? ))? )* }, $(pre: { $($init_block:stmt)* })? headers: $headers:expr, tests: $($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, t: $(#[$struct_attr:meta])* { $( $(#[$attr:meta])* $field:ident: $ty:ty),* $(,)?} $(pre: { $($init_block:stmt)* })? init: { $( $val_field:ident: $val:expr),* $(,)?} expected: $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, t: { u8 sstr }, init: { 0 "v2" }, expected: ["0", "1"], ["0", "v2"],); test_tuple!(empty, t: { }, init: { }, expected: [], [],); test_tuple!(rename, t: { u8 #[tabled(rename = "field 2")] sstr }, init: { 0 "123" }, expected: ["0", "field 2"], ["0", "123"],); test_tuple!(skip_0, t: { #[tabled(skip)] u8 #[tabled(rename = "field 2", skip)] sstr sstr }, init: { 0 "v2" "123" }, expected: ["2"], ["123"],); test_tuple!(skip_1, t: { #[tabled(skip)] u8 #[tabled(skip)] #[tabled(rename = "field 2")] sstr sstr }, init: { 0 "v2" "123" }, expected: ["2"], ["123"],); test_tuple!(order_0, t: { #[tabled(order = 0)] u8 u8 u8}, init: { 0 1 2 }, expected: ["0", "1", "2"], ["0", "1", "2"],); test_tuple!(order_1, t: { #[tabled(order = 1)] u8 u8 u8}, init: { 0 1 2 }, expected: ["1", "0", "2"], ["1", "0", "2"],); test_tuple!(order_2, t: { #[tabled(order = 2)] u8 u8 u8}, init: { 0 1 2 }, expected: ["1", "2", "0"], ["1", "2", "0"],); test_tuple!(order_3, t: { u8 #[tabled(order = 0)] u8 u8}, init: { 0 1 2 }, expected: ["1", "0", "2"], ["1", "0", "2"],); test_tuple!(order_4, t: { u8 #[tabled(order = 1)] u8 u8}, init: { 0 1 2 }, expected: ["0", "1", "2"], ["0", "1", "2"],); test_tuple!(order_5, t: { u8 #[tabled(order = 2)] u8 u8}, init: { 0 1 2 }, expected: ["0", "2", "1"], ["0", "2", "1"],); test_tuple!(order_6, t: { u8 u8 #[tabled(order = 0)] u8}, init: { 0 1 2 }, expected: ["2", "0", "1"], ["2", "0", "1"],); test_tuple!(order_7, t: { u8 u8 #[tabled(order = 1)] u8}, init: { 0 1 2 }, expected: ["0", "2", "1"], ["0", "2", "1"],); test_tuple!(order_8, t: { u8 u8 #[tabled(order = 2)] u8}, init: { 0 1 2 }, expected: ["0", "1", "2"], ["0", "1", "2"],); test_tuple!(order_9, t: { #[tabled(order = 2)] u8 u8 #[tabled(order = 0)] u8}, init: { 0 1 2 }, expected: ["2", "1", "0"], ["2", "1", "0"],); test_tuple!(order_10, t: { #[tabled(order = 2)] u8 #[tabled(order = 1)] u8 u8}, init: { 0 1 2 }, expected: ["2", "1", "0"], ["2", "1", "0"],); test_tuple!(order_11, t: { #[tabled(order = 2)] u8 #[tabled(order = 2)] u8 #[tabled(order = 1)] u8}, init: { 0 1 2 }, expected: ["0", "2", "1"], ["0", "2", "1"],); test_tuple!(order_12, t: { #[tabled(order = 2)] u8 #[tabled(order = 2)] u8 #[tabled(order = 2)] u8}, init: { 0 1 2 }, expected: ["0", "1", "2"], ["0", "1", "2"],); test_tuple!(order_13, t: { #[tabled(order = 1)] u8 #[tabled(order = 1)] u8 #[tabled(order = 1)] u8}, init: { 0 1 2 }, expected: ["0", "2", "1"], ["0", "2", "1"],); test_tuple!(order_14, t: { #[tabled(order = 2)] u8 #[tabled(order = 1)] u8 #[tabled(order = 0)] u8}, init: { 0 1 2 }, expected: ["2", "1", "0"], ["2", "1", "0"],); test_tuple!(rename_all, t: #[tabled(rename_all = "UPPERCASE")] { u8 sstr}, init: { 0 "123" }, expected: ["0", "1"], ["0", "123"],); test_tuple!(rename_all_field, t: { u8 #[tabled(rename_all = "UPPERCASE")] sstr}, init: { 0 "123" }, expected: ["0", "1"], ["0", "123"],); test_tuple!(rename_all_field_with_rename_0, t: { u8 #[tabled(rename_all = "UPPERCASE", rename = "Something")] sstr}, init: { 0 "123" }, expected: ["0", "Something"], ["0", "123"],); test_tuple!(rename_all_field_with_rename_1, t: { u8 #[tabled(rename = "Something")] #[tabled(rename_all = "UPPERCASE")] sstr}, init: { 0 "123" }, expected: ["0", "Something"], ["0", "123"],); test_tuple!( display_option, t: { u8 #[tabled(display_with = "display_option")] Option }, init: { 0 Some("v2") }, expected: ["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, t: { u8 #[tabled(display_with("display_option", 1, "234"))] Option }, init: { 0 Some("v2") }, expected: ["0", "1"], ["0", "some 1 234"], pre: { fn display_option(val: usize, text: &str) -> String { format!("some {val} {text}") } } ); test_tuple!( display_option_self, t: { u8 #[tabled(display_with = "Self::display_option")] Option }, init: { 0 Some("v2") }, expected: ["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, t: { u8 #[tabled(display_with("Self::display_option", self))] Option }, init: { 0 Some("v2") }, expected: ["0", "1"], ["0", "some v2"], pre: { impl TestType { fn display_option(o: &TestType) -> String { match o.1 { Some(s) => format!("some {s}"), None => "none".to_string(), } } } } ); test_tuple!( display_option_self_3, t: { u8 #[tabled(display_with("display_option", self))] Option }, init: { 0 Some("v2") }, expected: ["0", "1"], ["0", "some v2"], pre: { fn display_option(o: &TestType) -> String { match o.1 { Some(s) => format!("some {s}"), None => "none".to_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, t: { Security Embedded Frontend Unknown }, headers: ["Security", "Embedded", "Frontend", "Unknown"], tests: Security => ["+", "", "", ""], Embedded => ["", "+", "", ""], Frontend => ["", "", "+", ""], Unknown => ["", "", "", "+"], ); test_enum!( diverse, t: { A { a: u8, b: i32 } B(sstr) K }, headers: ["A", "B", "K"], tests: A { a: 1, b: 2 } => ["+", "", ""], B("") => ["", "+", ""], K => ["", "", "+"], ); test_enum!( rename_variant, t: { #[tabled(rename = "Variant 1")] A { a: u8, b: i32 } #[tabled(rename = "Variant 2")] B(sstr) K }, headers: ["Variant 1", "Variant 2", "K"], tests: A { a: 1, b: 2 } => ["+", "", ""], B("") => ["", "+", ""], K => ["", "", "+"], ); test_enum!( skip_variant, t: { A { a: u8, b: i32 } #[tabled(skip)] B(sstr) K }, headers: ["A", "K"], tests: A { a: 1, b: 2 } => ["+", ""], B("") => ["", ""], K => ["", "+"], ); test_enum!( inline_variant, t: { #[tabled(inline("Auto::"))] Auto { #[tabled(rename = "mod")] model: sstr, engine: sstr } #[tabled(inline)] Bikecycle( #[tabled(rename = "name")] sstr, #[tabled(inline)] Bike ) Skateboard }, pre: { #[derive(Tabled)] struct Bike { brand: sstr, price: f32 } } headers: ["Auto::mod", "Auto::engine", "name", "brand", "price", "Skateboard"], tests: 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, t: { #[tabled(inline("backend::"))] Backend { #[tabled(display_with = "display", rename = "name")] value: sstr } Frontend }, pre: { fn display(_: sstr) -> String { "asd".to_string() } } headers: ["backend::name", "Frontend"], tests: Backend { value: "123" } => ["asd", ""], Frontend => ["", "+"], ); test_enum!( inline_field_with_display_self_function, t: { #[tabled(inline("backend::"))] Backend { #[tabled()] #[tabled(display_with("display", self), rename = "name")] value: sstr } Frontend }, pre: { fn display(_: &T) -> String { "asd".to_string() } } headers: ["backend::name", "Frontend"], tests: Backend { value: "123" } => ["asd", ""], Frontend => ["", "+"], ); test_enum!( with_display, t: { #[tabled(inline)] A(#[tabled(display_with = "format::<4>")] sstr) B }, pre: { fn format(_: sstr) -> String { ID.to_string() } } headers: ["0", "B"], tests: A("") => ["4", ""], B => ["", "+"], ); test_enum!( with_display_self, t: { #[tabled(inline)] A(#[tabled(display_with("Self::format::<4>", self))] sstr) B }, pre: { impl TestType { fn format(&self) -> String { ID.to_string() } } } headers: ["0", "B"], tests: A("") => ["4", ""], B => ["", "+"], ); test_enum!( rename_all_variant, t: { #[tabled(rename_all = "snake_case")] VariantName1 { a: u8, b: i32 } #[tabled(rename_all = "UPPERCASE")] VariantName2(String) K }, headers: ["variant_name1", "VARIANTNAME2", "K"], tests: ); test_enum!( rename_all_enum, t: #[tabled(rename_all = "snake_case")] { VariantName1 { a: u8, b: i32 } VariantName2(String) K }, headers: ["variant_name1", "variant_name2", "k"], tests: ); test_enum!( rename_all_enum_inherited_inside_struct_enum, t: #[tabled(rename_all = "snake_case")] { #[tabled(inline)] VariantName1 { some_field_1: u8, some_field_2: i32 } VariantName2(String) K }, headers: ["some_field_1", "some_field_2", "variant_name2", "k"], tests: ); test_enum!( rename_all_enum_inherited_inside_struct_override_by_rename_enum, t: #[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 }, headers: ["f1", "f2", "variant_name2", "k"], tests: ); test_enum!( rename_all_enum_inherited_inside_struct_override_by_rename_all_enum, t: #[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 }, headers: ["SOMEFIELD1", "someField2", "variant_name2", "k"], tests: ); test_enum!( rename_all_variant_inherited_inside_struct_enum, t: #[tabled(rename_all = "snake_case")] { #[tabled(inline)] #[tabled(rename_all = "snake_case")] VariantName1 { some_field_1: u8, some_field_2: i32, } VariantName2(String) K }, headers: ["some_field_1", "some_field_2", "variant_name2", "k"], tests: ); test_enum!( rename_all_variant_inherited_inside_struct_enum_overridden_by_rename, t: #[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 }, headers: ["f1", "f2", "variant_name2", "k"], tests: ); test_enum!( rename_all_variant_inherited_inside_struct_override_by_rename_all_enum, t: #[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 }, headers: ["SOMEFIELD1", "someField2", "variant_name2", "k"], tests: ); test_enum!( inline_enum_as_whole, t: #[tabled(inline)] { AbsdEgh { a: u8, b: i32 } B(String) K }, headers: ["TestType"], tests: AbsdEgh { a: 0, b: 0 } => ["AbsdEgh"], B(String::new()) => ["B"], K => ["K"], ); test_enum!( inline_enum_as_whole_and_rename, t: #[tabled(inline, rename_all = "snake_case")] { AbsdEgh { a: u8, b: i32 } B(String) K }, headers: ["TestType"], tests: AbsdEgh { a: 0, b: 0 } => ["absd_egh"], B(String::new()) => ["b"], K => ["k"], ); test_enum!( inline_enum_as_whole_and_rename_inner, t: #[tabled(inline)] { #[tabled(rename_all = "snake_case")] AbsdEgh { a: u8, b: i32 } #[tabled(rename_all = "lowercase")] B(String) K }, headers: ["TestType"], tests: AbsdEgh { a: 0, b: 0 } => ["absd_egh"], B(String::new()) => ["b"], K => ["K"], ); test_enum!( inline_enum_name, t: #[tabled(inline("A struct name"))] { AbsdEgh { a: u8, b: i32 } B(String) K }, headers: ["A struct name"], tests: AbsdEgh { a: 0, b: 0 } => ["AbsdEgh"], B(String::new()) => ["B"], K => ["K"], ); test_enum!( enum_display_with_variant, t: { #[tabled(display_with = "display_variant1")] AbsdEgh { a: u8, b: i32 } #[tabled(display_with = "display_variant2::<200>")] B(String) #[tabled(display_with = "some::bar::display_variant1")] K }, pre: { fn display_variant1() -> &'static str { "Hello World" } fn display_variant2() -> String { format!("asd {VAL}") } pub mod some { pub mod bar { pub fn display_variant1() -> &'static str { "Hello World 123" } } } } headers: ["AbsdEgh", "B", "K"], tests: AbsdEgh { a: 0, b: 0 } => ["Hello World", "", ""], B(String::new()) => ["", "asd 200", ""], K => ["", "", "Hello World 123"], ); test_enum!( enum_display_with_self_variant, t: { #[tabled(display_with("display_variant1", self))] AbsdEgh { a: u8, b: i32 } #[tabled(display_with("display_variant2::<200, _>", self))] B(String) #[tabled(display_with("some::bar::display_variant1", self))] K }, pre: { fn display_variant1(_: &D) -> &'static str { "Hello World" } fn display_variant2(_: &D) -> String { format!("asd {VAL}") } pub mod some { pub mod bar { pub fn display_variant1(_: &D) -> &'static str { "Hello World 123" } } } } headers: ["AbsdEgh", "B", "K"], tests: AbsdEgh { a: 0, b: 0 } => ["Hello World", "", ""], B(String::new()) => ["", "asd 200", ""], K => ["", "", "Hello World 123"], ); test_enum!( enum_display_with_arguments, t: { #[tabled(display_with("display1", 1, 2, self))] AbsdEgh { a: u8, b: i32 } #[tabled(display_with("display2::<200>", "Hello World"))] B(String) #[tabled(display_with("display1", 100, 200, self))] K }, pre: { fn display1(val: usize, val2: usize, _: &D) -> String { format!("{val} {val2}") } fn display2(val: &str) -> String { format!("asd {VAL} {val}") } } headers: ["AbsdEgh", "B", "K"], tests: AbsdEgh { a: 0, b: 0 } => ["1 2", "", ""], B(String::new()) => ["", "asd 200 Hello World", ""], K => ["", "", "100 200"], ); test_enum!(order_0, t: { #[tabled(order = 0)] V1(u8) V2(u8) V3(u8) }, headers: ["V1", "V2", "V3"], tests: V1(0) => ["+", "", ""], V2(0) => ["", "+", ""], V3(0) => ["", "", "+"],); test_enum!(order_1, t: { #[tabled(order = 1)] V1(u8) V2(u8) V3(u8) }, headers: ["V2", "V1", "V3"], tests: V1(0) => ["", "+", ""], V2(0) => ["+", "", ""], V3(0) => ["", "", "+"],); test_enum!(order_2, t: { #[tabled(order = 2)] V1(u8) V2(u8) V3(u8) }, headers: ["V2", "V3", "V1"], tests: V1(0) => ["", "", "+"], V2(0) => ["+", "", ""], V3(0) => ["", "+", ""],); test_enum!(order_3, t: { V1(u8) #[tabled(order = 0)] V2(u8) V3(u8) }, headers: ["V2", "V1", "V3"], tests: V1(0) => ["", "+", ""], V2(0) => ["+", "", ""], V3(0) => ["", "", "+"],); test_enum!(order_4, t: { V1(u8) #[tabled(order = 1)] V2(u8) V3(u8) }, headers: ["V1", "V2", "V3"], tests: V1(0) => ["+", "", ""], V2(0) => ["", "+", ""], V3(0) => ["", "", "+"],); test_enum!(order_5, t: { V1(u8) #[tabled(order = 2)] V2(u8) V3(u8) }, headers: ["V1", "V3", "V2"], tests: V1(0) => ["+", "", ""], V2(0) => ["", "", "+"], V3(0) => ["", "+", ""],); test_enum!(order_6, t: { V1(u8) V2(u8) #[tabled(order = 0)] V3(u8) }, headers: ["V3", "V1", "V2"], tests: V1(0) => ["", "+", ""], V2(0) => ["", "", "+"], V3(0) => ["+", "", ""],); test_enum!(order_7, t: { V1(u8) V2(u8) #[tabled(order = 1)] V3(u8) }, headers: ["V1", "V3", "V2"], tests: V1(0) => ["+", "", ""], V2(0) => ["", "", "+"], V3(0) => ["", "+", ""],); test_enum!(order_8, t: { V1(u8) V2(u8) #[tabled(order = 2)] V3(u8) }, headers: ["V1", "V2", "V3"], tests: V1(0) => ["+", "", ""], V2(0) => ["", "+", ""], V3(0) => ["", "", "+"],); test_enum!(order_9, t: { #[tabled(order = 2)] V1(u8) V2(u8) #[tabled(order = 0)] V3(u8) }, headers: ["V3", "V2", "V1"], tests: V1(0) => ["", "", "+"], V2(0) => ["", "+", ""], V3(0) => ["+", "", ""],); test_enum!(order_10, t: { #[tabled(order = 2)] V1(u8) V2(u8) #[tabled(order = 1)] V3(u8) }, headers: ["V2", "V3", "V1"], tests: V1(0) => ["", "", "+"], V2(0) => ["+", "", ""], V3(0) => ["", "+", ""],); test_enum!(order_11, t: { #[tabled(order = 2)] V1(u8) #[tabled(order = 2)] V2(u8) #[tabled(order = 1)] V3(u8) }, headers: ["V1", "V3", "V2"], tests: V1(0) => ["+", "", ""], V2(0) => ["", "", "+"], V3(0) => ["", "+", ""],); test_enum!(order_12, t: { #[tabled(order = 2)] V1(u8) #[tabled(order = 1)] V2(u8) #[tabled(order = 0)] V3(u8) }, headers: ["V3", "V2", "V1"], tests: V1(0) => ["", "", "+"], V2(0) => ["", "+", ""], V3(0) => ["+", "", ""],); test_enum!(order_13, t: { #[tabled(order = 0)] V1(u8) #[tabled(order = 0)] V2(u8) #[tabled(order = 0)] V3(u8) }, headers: ["V3", "V1", "V2"], tests: V1(0) => ["", "+", ""], V2(0) => ["", "", "+"], V3(0) => ["+", "", ""],); test_enum!(order_14, t: { #[tabled(order = 1)] V1(u8) #[tabled(order = 1)] V2(u8) #[tabled(order = 1)] V3(u8) }, headers: ["V1", "V3", "V2"], tests: V1(0) => ["+", "", ""], V2(0) => ["", "", "+"], V3(0) => ["", "+", ""],); test_enum!(order_15, t: { #[tabled(order = 2)] V1(u8) #[tabled(order = 2)] V2(u8) #[tabled(order = 2)] V3(u8) }, headers: ["V1", "V2", "V3"], tests: V1(0) => ["+", "", ""], V2(0) => ["", "+", ""], V3(0) => ["", "", "+"],); test_enum!(order_0_inlined, t: #[tabled(inline)] { #[tabled(order = 1)] V1(u8) V2(u8) V3(u8) }, headers: ["TestType"], tests: V1(0) => ["V1"], V2(0) => ["V2"], V3(0) => ["V3"],); } 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, t: { } init: { } expected: [], []); test_struct!(general, t: { f1: u8, f2: sstr } init: { f1: 0, f2: "v2" } expected: ["f1", "f2"], ["0", "v2"]); test_struct!(rename, t: { #[tabled(rename = "field 1")] f1: u8, #[tabled(rename = "field 2")] f2: sstr } init: { f1: 0, f2: "v2" } expected: ["field 1", "field 2"], ["0", "v2"]); test_struct!(skip, t: { #[tabled(skip)] f1: u8, #[tabled(rename = "field 2", skip)] f2: sstr, f3: sstr } init: { f1: 0, f2: "v2", f3: "123" } expected: ["f3"], ["123"]); test_struct!(skip_true, t: { #[tabled(skip = true)] f1: u8, #[tabled(rename = "field 2", skip = true)] f2: sstr, f3: sstr } init: { f1: 0, f2: "v2", f3: "123" } expected: ["f3"], ["123"]); test_struct!( inline, t: { #[tabled(inline = true)] id: u8, name: sstr, #[tabled(inline)] ed: Education } pre: { #[derive(Tabled)] struct Education { uni: sstr, graduated: bool } } init: { id: 0, name: "Maxim", ed: Education { uni: "BNTU", graduated: true }} expected: ["u8", "name","uni","graduated"], ["0", "Maxim", "BNTU", "true"] ); test_struct!( inline_with_prefix, t: { #[tabled(rename = "it's an ignored option", inline)] id: u8, name: sstr, #[tabled(inline("education::"))] ed: Education, } pre: { #[derive(Tabled)] struct Education { uni: sstr, graduated: bool } } init: { id: 0, name: "Maxim", ed: Education { uni: "BNTU", graduated: true }} expected: ["u8", "name","education::uni","education::graduated"], ["0", "Maxim", "BNTU", "true"] ); test_struct!( display_with, t: { f1: u8, #[tabled(display_with = "display_option")] f2: Option, } pre: { fn display_option(o: &Option) -> String { match o { Some(s) => format!("some {s}"), None => "none".to_string(), } } } init: { f1: 0, f2: Some("v2") } expected: ["f1", "f2"], ["0", "some v2"] ); test_struct!( display_with_args, t: { f1: u8, #[tabled(display_with("display_option", 1, 2, 3))] f2: Option, } pre: { fn display_option(v1: usize, v2: usize, v3: usize) -> String { format!("{v1} {v2} {v3}") } } init: { f1: 0, f2: Some("v2") } expected: ["f1", "f2"], ["0", "1 2 3"] ); test_struct!( display_with_self_static_method, t: { f1: u8, #[tabled(display_with = "Self::display_option")] f2: Option, } pre: { impl TestType { fn display_option(o: &Option) -> String { match o { Some(s) => format!("some {s}"), None => "none".to_string(), } } } } init: { f1: 0, f2: Some("v2") } expected: ["f1", "f2"], ["0", "some v2"] ); test_struct!( display_with_self_static_method_2, t: { f1: u8, #[tabled(display_with("Self::display_option", self))] f2: Option, } pre: { impl TestType { fn display_option(o: &TestType) -> String { match o.f2 { Some(s) => format!("some {s}"), None => "none".to_string(), } } } } init: { f1: 0, f2: Some("v2") } expected: ["f1", "f2"], ["0", "some v2"] ); test_struct!( display_with_self_2_self_static_method_2, t: { f1: u8, #[tabled(display_with("Self::display_option", self))] f2: Option, } pre: { impl TestType { fn display_option(&self) -> String { match self.f2 { Some(s) => format!("some {s}"), None => "none".to_string(), } } } } init: { f1: 0, f2: Some("v2") } expected: ["f1", "f2"], ["0", "some v2"] ); test_struct!( display_with_self_2_self_static_method, t: { f1: u8, #[tabled(display_with("display_option", self))] f2: Option, } pre: { fn display_option(o: &TestType) -> String { match o.f2 { Some(s) => format!("some {s}"), None => "none".to_string(), } } } init: { f1: 0, f2: Some("v2") } expected: ["f1", "f2"], ["0", "some v2"] ); test_struct!(order_0, t: { #[tabled(order = 0)] f0: u8, f1: u8, f2: u8 } init: { f0: 0, f1: 1, f2: 2 } expected: ["f0", "f1", "f2"], ["0", "1", "2"]); test_struct!(order_1, t: { #[tabled(order = 1)] f0: u8, f1: u8, f2: u8 } init: { f0: 0, f1: 1, f2: 2 } expected: ["f1", "f0", "f2"], ["1", "0", "2"]); test_struct!(order_2, t: { #[tabled(order = 2)] f0: u8, f1: u8, f2: u8 } init: { f0: 0, f1: 1, f2: 2 } expected: ["f1", "f2", "f0"], ["1", "2", "0"]); test_struct!(order_3, t: { f0: u8, #[tabled(order = 0)] f1: u8, f2: u8 } init: { f0: 0, f1: 1, f2: 2 } expected: ["f1", "f0", "f2"], ["1", "0", "2"]); test_struct!(order_4, t: { f0: u8, #[tabled(order = 1)] f1: u8, f2: u8 } init: { f0: 0, f1: 1, f2: 2 } expected: ["f0", "f1", "f2"], ["0", "1", "2"]); test_struct!(order_5, t: { f0: u8, #[tabled(order = 2)] f1: u8, f2: u8 } init: { f0: 0, f1: 1, f2: 2 } expected: ["f0", "f2", "f1"], ["0", "2", "1"]); test_struct!(order_6, t: { f0: u8, f1: u8, #[tabled(order = 0)] f2: u8 } init: { f0: 0, f1: 1, f2: 2 } expected: ["f2", "f0", "f1"], ["2", "0", "1"]); test_struct!(order_7, t: { f0: u8, f1: u8, #[tabled(order = 1)] f2: u8 } init: { f0: 0, f1: 1, f2: 2 } expected: ["f0", "f2", "f1"], ["0", "2", "1"]); test_struct!(order_8, t: { f0: u8, f1: u8, #[tabled(order = 2)] f2: u8 } init: { f0: 0, f1: 1, f2: 2 } expected: ["f0", "f1", "f2"], ["0", "1", "2"]); test_struct!(order_9, t: { #[tabled(order = 2)] f0: u8, f1: u8, #[tabled(order = 0)] f2: u8 } init: { f0: 0, f1: 1, f2: 2 } expected: ["f2", "f1", "f0"], ["2", "1", "0"]); test_struct!(order_10, t: { #[tabled(order = 2)] f0: u8, #[tabled(order = 1)] f1: u8, f2: u8 } init: { f0: 0, f1: 1, f2: 2 } expected: ["f2", "f1", "f0"], ["2", "1", "0"]); test_struct!(order_11, t: { #[tabled(order = 2)] f0: u8, #[tabled(order = 2)] f1: u8, #[tabled(order = 1)] f2: u8 } init: { f0: 0, f1: 1, f2: 2 } expected: ["f0", "f2", "f1"], ["0", "2", "1"]); test_struct!(order_12, t: { #[tabled(order = 2)] f0: u8, #[tabled(order = 1)] f1: u8, #[tabled(order = 0)] f2: u8 } init: { f0: 0, f1: 1, f2: 2 } expected: ["f2", "f1", "f0"], ["2", "1", "0"]); test_struct!( rename_all, t: #[tabled(rename_all = "UPPERCASE")] { f1: u8, f2: sstr } init: { f1: 0, f2: "v2" } expected: ["F1", "F2"], ["0", "v2"] ); test_struct!( rename_all_override_in_field_by_rename, t: #[tabled(rename_all = "UPPERCASE")] { #[tabled(rename = "213213")] f1: u8, f2: sstr } init: { f1: 0, f2: "v2" } expected: ["213213", "F2"], ["0", "v2"] ); test_struct!( rename_all_override_in_field_by_rename_all, t: #[tabled(rename_all = "UPPERCASE")] { #[tabled(rename_all = "lowercase")] f1: u8, f2: sstr } init: { f1: 0, f2: "v2" } expected: ["f1", "F2"], ["0", "v2"] ); test_struct!( rename_all_field, t: { #[tabled(rename_all = "lowercase")] f1: u8, #[tabled(rename_all = "UPPERCASE")] f2: sstr } init: { f1: 0, f2: "v2" } expected: ["f1", "F2"], ["0", "v2"] ); test_struct!( rename_all_field_overridden_by_rename, t: { #[tabled(rename_all = "lowercase", rename = "Hello")] f1: u8, #[tabled(rename_all = "UPPERCASE")] f2: sstr } init: { f1: 0, f2: "v2" } expected: ["Hello", "F2"], ["0", "v2"] ); // #[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, t: { #[tabled(skip)] () sstr }, init: { () "123" }, expected: ["1"], ["123"],); test_struct!(skipped_fields_not_implement_display_struct, t: { #[tabled(skip)] _unit: (), s: sstr } init: { _unit: (), s: "123" } expected: ["s"], ["123"],); test_struct!( skipped_fields_not_implement_display_struct_in_inline, t: { s: sstr, #[tabled(inline)] f: S1 } pre: { #[derive(Tabled)] struct S1 { #[tabled(skip)] _unit: (), s: sstr, } } init: { s: "123", f: S1 { _unit: (), s: "..." } } expected: ["s", "s"], ["123", "..."], ); test_enum!( skipped_fields_not_implement_display_enum, t: { #[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 }, headers: ["A::name", "B::issue", "B::name", "C::0", "C::2", "D"], tests: 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, t: { f1: sstr, f2: sstr, #[tabled(display_with = "print", inline)] f3: usize } init: { f1: "123", f2: "456", f3: 789 } expected: ["f1", "f2", "usize"], ["123", "456", "789"], ); test_struct!( ignore_display_with_when_used_with_inline_2, t: { f1: sstr, f2: sstr, #[tabled(display_with = "print", )] #[tabled(inline)] f3: usize } init: { f1: "123", f2: "456", f3: 789 } expected: ["f1", "f2", "usize"], ["123", "456", "789"], ); test_struct!( display_with_and_rename, t: { f1: sstr, f2: sstr, #[tabled(display_with = "print", rename = "asd")] f3: usize } pre: { #[allow(dead_code)] fn print(_: T) -> String { String::new() } } init: { f1: "123", f2: "456", f3: 789 } expected: ["f1", "f2", "asd"], ["123", "456", ""], ); test_struct!( display_with_and_rename_2, t: { f1: sstr, f2: sstr, #[tabled(display_with = "print")] #[tabled(rename = "asd")] f3: usize } pre: { #[allow(dead_code)] fn print(_: T) -> String { String::new() } } init: { f1: "123", f2: "456", f3: 789 } expected: ["f1", "f2", "asd"], ["123", "456", ""], ); test_struct!( display_with_and_rename_all, t: { f1: sstr, f2: sstr, #[tabled(display_with = "print", rename_all = "UPPERCASE")] f3: usize } pre: { #[allow(dead_code)] fn print(_: T) -> String { String::new() } } init: { f1: "123", f2: "456", f3: 789 } expected: ["f1", "f2", "F3"], ["123", "456", ""], ); #[test] fn rename_all_variants() { macro_rules! test_case { ( $name:ident, $case:expr ) => { #[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!["", ""]); } mod __ { #[test] fn dont_import_the_trait() { #[derive(tabled::Tabled)] struct __; } } tabled-0.14.0/tests/derive/mod.rs000064400000000000000000000000211046102023000146770ustar 00000000000000mod derive_test; tabled-0.14.0/tests/macros/col_row_test.rs000064400000000000000000000526101046102023000166440ustar 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.14.0/tests/macros/mod.rs000064400000000000000000000000221046102023000147060ustar 00000000000000mod col_row_test; tabled-0.14.0/tests/main.rs000064400000000000000000000001251046102023000135730ustar 00000000000000mod core; mod derive; mod macros; mod settings; #[cfg(feature = "std")] mod matrix; tabled-0.14.0/tests/matrix/matrix.rs000064400000000000000000000075471046102023000154760ustar 00000000000000use std::{ fmt::{self, Display}, iter::FromIterator, string::ToString, }; use tabled::{ grid::config::ColoredConfig, grid::dimension::CompleteDimensionVecRecords, grid::records::vec_records::{CellInfo, 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.0][pos.1] = 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< VecRecords>, CompleteDimensionVecRecords<'a>, ColoredConfig, >, { 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.14.0/tests/matrix/matrix_list.rs000064400000000000000000000026471046102023000165250ustar 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.14.0/tests/matrix/mod.rs000064400000000000000000000001721046102023000147340ustar 00000000000000#[allow(clippy::module_inception)] mod matrix; mod matrix_list; pub use matrix::Matrix; pub use matrix_list::MatrixList; tabled-0.14.0/tests/settings/alignment_test.rs000064400000000000000000000116371046102023000175360ustar 00000000000000#![cfg(feature = "std")] use tabled::settings::{ locator::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), "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), "E\nnde\navou\nros") .insert((3, 2), "Red\nHat") .insert((3, 3), "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()) .with(Modify::new(ByColumnName::new("column 01123123")).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 " ); tabled-0.14.0/tests/settings/color_test.rs000064400000000000000000000026741046102023000166770ustar 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.14.0/tests/settings/colorization.rs000064400000000000000000000226441046102023000172350ustar 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.14.0/tests/settings/column_names_test.rs000064400000000000000000000154301046102023000202330ustar 00000000000000#![cfg(feature = "std")] use tabled::{ grid::config::AlignmentHorizontal, settings::{themes::ColumnNames, Color}, 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().set_alignment(AlignmentHorizontal::Left)), "+&str---+&str------+" "| Hello | World |" "+-------+----------+" "| and | looooong |" "| | word |" "+-------+----------+" ); test_table!( alignment_right, Table::new([("Hello", "World"), ("and", "looooong\nword")]) .with(ColumnNames::default().set_alignment(AlignmentHorizontal::Right)), "+---&str+------&str+" "| Hello | World |" "+-------+----------+" "| and | looooong |" "| | word |" "+-------+----------+" ); test_table!( alignment_center, Table::new([("Hello", "World"), ("and", "looooong\nword")]) .with(ColumnNames::default().set_alignment(AlignmentHorizontal::Center)), "+-&str--+---&str---+" "| Hello | World |" "+-------+----------+" "| and | looooong |" "| | word |" "+-------+----------+" ); test_table!( alignment_array, Table::new([("Hello", "World"), ("and", "looooong\nword")]) .with(ColumnNames::default().set_alignment(vec![AlignmentHorizontal::Right, AlignmentHorizontal::Center])), "+---&str+---&str---+" "| Hello | World |" "+-------+----------+" "| and | looooong |" "| | word |" "+-------+----------+" ); test_table!( set_line, Matrix::new(3, 3).with(ColumnNames::default().set_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!( set_line_max_out, Matrix::new(3, 3).with(ColumnNames::default().set_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!( set_line_0, Matrix::new(3, 3).with(ColumnNames::default().set_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!( set_colors_some_some, Table::new([("Hello", "World"), ("and", "looooong\nword")]) .with(ColumnNames::default().set_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!( set_colors_none_some, Table::new([("Hello", "World"), ("and", "looooong\nword")]) .with(ColumnNames::default().set_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!( set_colors_none_none, Table::new([("Hello", "World"), ("and", "looooong\nword")]) .with(ColumnNames::default().set_color(vec![Color::default(), Color::default()])), "+&str---+&str------+" "| Hello | World |" "+-------+----------+" "| and | looooong |" "| | word |" "+-------+----------+" ); test_table!( set_colors_empty, Table::new([("Hello", "World"), ("and", "looooong\nword")]) .with(ColumnNames::default().set_color(vec![Color::default(); 0])), "+&str---+&str------+" "| Hello | World |" "+-------+----------+" "| and | looooong |" "| | word |" "+-------+----------+" ); tabled-0.14.0/tests/settings/concat_test.rs000064400000000000000000000125421046102023000170230ustar 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), "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), "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.14.0/tests/settings/disable_test.rs000064400000000000000000000047271046102023000171650ustar 00000000000000#![cfg(feature = "std")] use tabled::settings::{ locator::ByColumnName, object::{Columns, Rows, Segment}, style::{HorizontalLine, Style}, Alignment, Disable, Modify, }; use crate::matrix::Matrix; use testing_table::test_table; test_table!( disable_rows, Matrix::new(3, 3).with(Disable::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(Disable::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(Disable::row(Columns::new(..))), "" ); test_table!( disable_header_with_new_styling, Matrix::new(3, 3) .with(Modify::new(Segment::all()).with(Alignment::left())) .with(Disable::row(Rows::new(..1))) .with(Style::modern().remove_horizontal().horizontals([HorizontalLine::new(1, Style::modern().get_horizontal())])), "┌───┬─────┬─────┬─────┐" "│ 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(Disable::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(Disable::column(ByColumnName::new("column 1"))) .with(Disable::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(Disable::column(Columns::new(..))), "" ); tabled-0.14.0/tests/settings/duplicate_test.rs000064400000000000000000000144001046102023000175210ustar 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.14.0/tests/settings/extract_test.rs000064400000000000000000000205121046102023000172220ustar 00000000000000#![cfg(feature = "std")] use tabled::{ builder::Builder, settings::{ object::{Rows, Segment}, Alignment, Disable, Extract, Format, Modify, Padding, }, }; 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(Disable::row(Rows::first())).with(Extract::segment(1..2, 1..2)), "+-----+" "| 1-0 |" "+-----+" ); test_table!( extract_left_test, Matrix::new(3, 3).with(Disable::row(Rows::first())).with(Extract::segment(.., ..1)), "+---+" "| 0 |" "+---+" "| 1 |" "+---+" "| 2 |" "+---+" ); test_table!( extract_right_test, Matrix::new(3, 3).with(Disable::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(Disable::row(Rows::first())).with(Extract::segment(..1, ..)), "+---+-----+-----+-----+" "| 0 | 0-0 | 0-1 | 0-2 |" "+---+-----+-----+-----+" ); test_table!( extract_bottom_test, Matrix::new(3, 3).with(Disable::row(Rows::first())).with(Extract::segment(2.., ..)), "+---+-----+-----+-----+" "| 2 | 2-0 | 2-1 | 2-2 |" "+---+-----+-----+-----+" ); test_table!( extract_all_test, Matrix::new(3, 3) .with(Disable::row(Rows::first())) .with(Extract::segment(3.., 3..)), "" ); test_table!( extract_empty_test, Builder::default().build().with(Extract::segment(.., ..)), "" ); tabled-0.14.0/tests/settings/format_test.rs000064400000000000000000000264661046102023000170560ustar 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 = "color")] use owo_colors::OwoColorize; 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), "E\nnde\navou\nros") .insert((3, 2), "Red\nHat") .insert((3, 3), "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, (b, c)| match (b, c) { (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 = "color")] test_table!( color_test, Matrix::new(3, 3) .with(Style::psql()) .with( Modify::new(Columns::new(..1).and(Columns::new(2..))) .with(Format::content(|s| s.red().to_string())), ) .with(Modify::new(Columns::new(1..2)).with(Format::content(|s| s.blue().to_string()))), " \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 = "color")] test_table!( color_multiline_test, Matrix::new(3, 3) .insert((2, 2), "E\nnde\navou\nros") .insert((3, 2), "Red\nHat") .insert((3, 3), "https://\nwww\n.\nredhat\n.com\n/en") .with(Style::psql()) .with(Modify::new(Columns::new(..1)).with(Format::content(|s| s.red().to_string()).multiline())) .with(Modify::new(Columns::new(1..2)).with(Format::content(|s| s.blue().to_string()).multiline())) .with(Modify::new(Columns::new(2..)).with(Format::content(|s| s.green().to_string()).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.14.0/tests/settings/formatting_test.rs000064400000000000000000000064651046102023000177350ustar 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.14.0/tests/settings/height_test.rs000064400000000000000000000222341046102023000170230ustar 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 = "color")] use owo_colors::OwoColorize; 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 = "color")] test_table!( cell_height_limit_colored, Matrix::new(3, 3) .with(Style::markdown()) .with(Modify::new(Columns::first()).with(Format::content(|s| format!("xxxx\n{s}xxxx\nxxxx\n").red().to_string()))) .with( Modify::new(Columns::first()) .with(Height::limit(1)) ) .with(Modify::new(Segment::all()).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 = "color")] test_table!( table_height_limit_colored, Matrix::new(3, 3) .with(Style::markdown()) .with(Modify::new(Columns::first()).with(Format::content(|s| format!("xxxx\n{s}xxxx\nxxxx\n").blue().on_green().to_string()))) .with(Modify::new(Columns::first()).with(Alignment::center_vertical())) .with(Height::limit(10)), "| \u{1b}[34;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}[34;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}[34;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}[34;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.14.0/tests/settings/highlingt_test.rs000064400000000000000000000465121046102023000175350ustar 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::new(Cell::new(1000, 0), 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_empty_table, Builder::default() .build() .with(Highlight::new(Segment::all(), Border::filled('+'))), "" ); test_table!( highlingt_cell, Matrix::new(3, 3) .with(Style::modern()) .with(Highlight::new(Cell::new(0, 0), Border::filled('+'))) .with(Highlight::new(Cell::new(1, 1), 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, Matrix::new(3, 3) .with(Style::modern()) .with(Highlight::new(Rows::single(0), Border::filled('+'))) .with(Highlight::new(Rows::single(3), 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_column, Matrix::new(3, 3) .with(Style::modern()) .with(Highlight::new(Columns::single(0), Border::filled('+'))) .with(Highlight::new(Columns::single(2), 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::new(Rows::new(1..3), 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_column_range, Matrix::new(3, 3) .with(Style::modern()) .with(Highlight::new(Columns::new(..2), 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_frame, Matrix::new(3, 3) .with(Style::modern()) .with(Highlight::new( Frame, 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::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::default().left('*').top('x'))) .with(Highlight::new(Rows::new(1..3), Border::default().left('n'))), "┌xxx┐" "* N │" "├───┤" "n 0 │" "n───┤" "n 1 │" "├───┤" "│ 2 │" "└───┘" ); test_table!( highlingt_several_times, Matrix::new(3, 3) .with(Style::modern()) .with(Highlight::new(Frame, Border::filled('*'))) .with(Highlight::new(Cell::new(1, 1), Border::filled('#'))) .with(Highlight::new(Columns::single(3), Border::filled('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::new(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)) .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.14.0/tests/settings/margin_test.rs000064400000000000000000000174631046102023000170400ustar 00000000000000#![cfg(feature = "std")] use tabled::settings::{object::Cell, Border, Highlight, Margin, Modify, Span, Style, Width}; use crate::matrix::Matrix; use testing_table::{is_lines_equal, static_table, test_table}; #[cfg(feature = "color")] use ::{owo_colors::OwoColorize, std::convert::TryFrom, tabled::settings::Color}; test_table!( margin_with_table_based_on_grid_borders, Matrix::new(3, 3) .with(Style::extended()) .with(Highlight::new(Cell::new(0, 0), Border::filled('+'))) .with(Highlight::new(Cell::new(1, 1), Border::filled('*'))) .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), "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), "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_eq!( table, static_table!( "VVVVVVVVVVVVVVVVVVVV" "> | co | co | col <" ">--+----+----+-----<" "> | 0-0 | 0-2 <" "> | 1- | 1- | 1-2 <" "> | 2- | 2- | 2-2 <" "^^^^^^^^^^^^^^^^^^^^" ) ); assert!(is_lines_equal(&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_eq!(papergrid::util::string::string_width_multiline(&table), 50); assert_eq!( table, static_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', '^').colorize( Color::BG_GREEN, Color::BG_YELLOW, Color::BG_RED, Color::BG_BLUE, )) .to_string(); assert_eq!( table, static_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 = "color")] #[test] fn margin_color_test() { let table = Matrix::new(3, 3) .with(Style::psql()) .with(Margin::new(2, 2, 2, 2).fill('>', '<', 'V', '^').colorize( Color::try_from(" ".red().bold().to_string()).unwrap(), Color::try_from(" ".green().to_string()).unwrap(), Color::try_from(" ".on_blue().red().bold().to_string()).unwrap(), Color::try_from(" ".on_yellow().blue().to_string()).unwrap(), )) .to_string(); assert_eq!( table, static_table!( "\u{1b}[1m\u{1b}[31m\u{1b}[44mVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV\u{1b}[22m\u{1b}[39m\u{1b}[49m" "\u{1b}[1m\u{1b}[31m\u{1b}[44mVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV\u{1b}[22m\u{1b}[39m\u{1b}[49m" "\u{1b}[1m\u{1b}[31m>>\u{1b}[22m\u{1b}[39m N | column 0 | column 1 | column 2 \u{1b}[32m<<\u{1b}[39m" "\u{1b}[1m\u{1b}[31m>>\u{1b}[22m\u{1b}[39m---+----------+----------+----------\u{1b}[32m<<\u{1b}[39m" "\u{1b}[1m\u{1b}[31m>>\u{1b}[22m\u{1b}[39m 0 | 0-0 | 0-1 | 0-2 \u{1b}[32m<<\u{1b}[39m" "\u{1b}[1m\u{1b}[31m>>\u{1b}[22m\u{1b}[39m 1 | 1-0 | 1-1 | 1-2 \u{1b}[32m<<\u{1b}[39m" "\u{1b}[1m\u{1b}[31m>>\u{1b}[22m\u{1b}[39m 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.14.0/tests/settings/merge_test.rs000064400000000000000000000107511046102023000166530ustar 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.14.0/tests/settings/mod.rs000064400000000000000000000006321046102023000152710ustar 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 margin_test; mod merge_test; mod padding_test; mod panel_test; mod render_settings; mod rotate_test; mod shadow_test; mod span_test; mod split_test; mod style_test; mod width_test; tabled-0.14.0/tests/settings/padding_test.rs000064400000000000000000000154121046102023000171610ustar 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 = "color")] use ::{owo_colors::OwoColorize, std::convert::TryFrom, tabled::settings::Color}; 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 = "color")] test_table!( padding_color, { let padding = Padding::new(2, 2, 2, 2).colorize( Color::try_from(' '.on_yellow().to_string()).unwrap(), Color::try_from(' '.on_blue().to_string()).unwrap(), Color::try_from(' '.on_red().to_string()).unwrap(), Color::try_from(' '.on_green().to_string()).unwrap(), ); Matrix::new(3, 3) .with(Style::psql()) .with(Modify::new(Rows::new(1..)).with(padding)) }, " 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.14.0/tests/settings/panel_test.rs000064400000000000000000000307471046102023000166620ustar 00000000000000#![cfg(feature = "std")] use tabled::settings::{ object::{Cell, Object, Rows, Segment}, style::{BorderSpanCorrection, HorizontalLine}, Alignment, Border, Highlight, Modify, Panel, Span, Style, 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::new(Cell::new(0, 0), Border::filled('#'))), "##### " "# 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::new(Cell::new(0, 0), Border::filled('#'))) .with(Highlight::new(Cell::new(0, 1), Border::filled('#'))) .with(Highlight::new(Cell::new(0, 2), Border::filled('#'))) .with(Highlight::new(Cell::new(0, 3), Border::filled('#'))), "######################################" "# 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(Style::modern().intersection_top('─').horizontals([HorizontalLine::new(1, Style::modern().get_horizontal()).intersection(Some('┬'))])) .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([HorizontalLine::new(1, Style::modern().get_horizontal()).intersection(Some('┬'))])) .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(clippy::needless_borrow)] 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 | " ); tabled-0.14.0/tests/settings/render_settings.rs000064400000000000000000000312671046102023000177210ustar 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 = "color")] use owo_colors::OwoColorize; 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 " "--------------+----------+----------+----------" " 0 | 0-0 | 0-1 | 0-2 " " 123\t123\tasdasd | 1-0 | 1-1 | 1-2 " " 2 | 2-0 | htt\tps:// | 2-2 " " | | www | " " | | . | " " | | red\that | " " | | .c\tom | " " | | /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 = "color")] 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 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 = "color")] 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}[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 = "color")] 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}[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 = "color")] fn colored_data() -> Vec> { let mut data = Matrix::list::<3, 3>(); data[1][0] = "asd\n21213123\n\n asdasd\n\n".red().to_string(); data[2][2] = "https://\nwww\n.\nredhat\n.com\n/en".on_blue().to_string(); data } tabled-0.14.0/tests/settings/rotate_test.rs000064400000000000000000000132521046102023000170510ustar 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::default().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::default().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.14.0/tests/settings/shadow_test.rs000064400000000000000000000067251046102023000170470ustar 00000000000000#![cfg(feature = "std")] use tabled::settings::{Shadow, Style}; use crate::matrix::Matrix; use testing_table::test_table; #[cfg(feature = "color")] use ::{owo_colors::OwoColorize, std::convert::TryFrom, 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 = "color")] 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::try_from(' '.red().to_string()).unwrap())), "+-----+-----+-----+ " "| 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.14.0/tests/settings/span_test.rs000064400000000000000000001312401046102023000165120ustar 00000000000000#![cfg(feature = "std")] #![allow(clippy::redundant_clone)] use std::iter::FromIterator; use tabled::{ builder::Builder, grid::config::Position, settings::{ object::{Columns, Segment}, style::{Border, 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), "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 behaiviour 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 behaiviour // // todo: determine if it's the right behaiviour 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::new(Columns::single(1), Border::filled('*'))) .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::new(Columns::new(1..3), Border::filled('*'))) .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 |" "+ + +------------+------------+------------+------------+------------+-----------+-----------+------------+------------+-----------+" "| | | 1611436878 | 1682224972 | 3249055253 | 1562255501 | 1370527728 | 240481955 | 334260406 | 2247343342 | 3000635978 | 395723768 |" "+------+-----++++++++++++++++++++++++++++------------+------------+------------+------------+------------+-----------+-----------+------------+------------+-----------+" ); 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))) } tabled-0.14.0/tests/settings/split_test.rs000064400000000000000000000234531046102023000167120ustar 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_colum_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.14.0/tests/settings/style_test.rs000064400000000000000000003017061046102023000167170ustar 00000000000000#![cfg(feature = "std")] use std::iter::FromIterator; use tabled::{ builder::Builder, settings::{ object::{Columns, Rows, Segment}, style::{ Border, BorderChar, BorderColor, BorderSpanCorrection, BorderText, HorizontalLine, Line, Offset, RawStyle, Style, VerticalLine, }, Color, Format, Highlight, Modify, Padding, Span, }, Table, }; use crate::matrix::Matrix; use testing_table::{static_table, test_table}; #[cfg(feature = "color")] use ::{owo_colors::OwoColorize, std::convert::TryFrom}; 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!( 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(vec![HorizontalLine::new(1, Style::modern().get_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!( 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(vec![HorizontalLine::new(1, Line::full('x', '*', 'q', 'w'))])), " N ' column 0 ' column 1 ' column 2 " "qxxx*xxxxxxxxxx*xxxxxxxxxx*xxxxxxxxxxw" " 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(BorderText::new("-Table").horizontal(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(BorderText::new("-Table").horizontal(Rows::last())), "+---+----------+----------+" "| 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(BorderText::new("-Table").horizontal(1)) .with(BorderText::new("-Table").horizontal(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(BorderText::new("-Table").horizontal(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(BorderText::new("-Table").horizontal(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(BorderText::new("-Table").horizontal(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(BorderText::new("-Tableeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee1231").horizontal(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::default().bottom('-'))) .with(BorderText::new("-Table").horizontal(1)), " N column 0 column 1 " "-Table-----------------" " 0 0-0 0-1 " " 1 1-0 1-1 " ); test_table!( border_color_global, { Matrix::table(2, 2).with(BorderColor::default().bottom(Color::FG_RED)) }, "+---+----------+----------+\n\ | N | column 0 | column 1 |\n\ +\u{1b}[31m---\u{1b}[39m+\u{1b}[31m----------\u{1b}[39m+\u{1b}[31m----------\u{1b}[39m+\n\ | 0 | 0-0 | 0-1 |\n\ +\u{1b}[31m---\u{1b}[39m+\u{1b}[31m----------\u{1b}[39m+\u{1b}[31m----------\u{1b}[39m+\n\ | 1 | 1-0 | 1-1 |\n\ +\u{1b}[31m---\u{1b}[39m+\u{1b}[31m----------\u{1b}[39m+\u{1b}[31m----------\u{1b}[39m+" ); #[cfg(feature = "color")] test_table!( border_text_colored, Matrix::table(2, 2) .with(BorderText::new("-Table").horizontal(1)) .with(BorderText::new("-Table213123").horizontal(2)) .with(Modify::new(Rows::single(1)).with(BorderColor::default().bottom(Color::FG_RED))) .with(Modify::new(Rows::single(2)).with(BorderColor::default().bottom(Color::try_from(" ".blue().on_green().to_string()).unwrap()))), "+---+----------+----------+" "| 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(BorderText::new("-Table").horizontal(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(BorderText::new("-Table").horizontal(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(BorderText::new("Table").horizontal(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(BorderText::new("Table").horizontal(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(BorderText::new("Table").horizontal(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(BorderText::new("Table").horizontal(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(BorderText::new("-Table").horizontal(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(BorderText::new("-Table").horizontal(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), Style::modern().get_frame())), "+---+----------+----------+" "| 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), Style::extended().get_frame())) .with(Highlight::new(Rows::single(2), Style::extended().get_frame())), "╔═════════════════════════╗" "║ 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(BorderText::new("").horizontal(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), "a longer string") .with({ let mut style: RawStyle = Style::modern().into(); style .set_bottom(Some('a')) .set_left(Some('b')) .set_right(None) .set_top(None) .set_intersection(Some('x')) .set_intersection_top(None) .set_corner_top_left(None) .set_corner_top_right(None); 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), "a longer string") .with({ let mut style: RawStyle = Style::modern().into(); style.set_bottom(None); 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), "a longer string") .with({ let mut style: RawStyle = Style::modern().into(); style.set_bottom(None); style }) .with(Modify::new(Rows::last()).with(Border::default().corner_bottom_left('*'))), "┌───┬─────────────────┬──────────┬──────────┐" "│ 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(); 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(vec![HorizontalLine::new(1, Line::default()).main(Some('-'))]), 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(vec![HorizontalLine::new(1, Line::default()).main(Some('+'))]), 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(vec![HorizontalLine::new(1, Line::default()).main(Some('+'))]), 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(vec![HorizontalLine::new(1, Line::default()).main(Some('+'))]), 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(vec![HorizontalLine::new(1, Line::default()).main(Some('+'))]), 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(vec![HorizontalLine::new(1, Line::default()).main(Some('+'))]), 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(vec![HorizontalLine::new(1, Line::default()).main(Some('+'))]), 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(vec![HorizontalLine::new(1, Line::default()).main(Some('-'))]) .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() .horizontals(vec![HorizontalLine::new(1, Line::default()).main(Some('-'))]) .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(vec![HorizontalLine::new(1, Line::default()).main(Some('-'))]) .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(vec![HorizontalLine::new(1, Line::default()).main(Some('-'))]) .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(vec![HorizontalLine::new(1, Line::default()).main(Some('-'))]) .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(vec![HorizontalLine::new(1, Line::default()).main(Some('-'))]) .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(vec![HorizontalLine::new(1, Line::filled('z'))]) .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') .horizontals(vec![HorizontalLine::new(1, Line::filled(','))]) .vertical('#') .intersection_bottom('@') .intersection_top('!') .intersection_left('=') .intersection_right('$') .intersection('+') .corner_top_left(';') .corner_bottom_left('?') .corner_top_right('.') .corner_bottom_right('%'); 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(vec![HorizontalLine::new(1, Line::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(vec![HorizontalLine::new( 1, Line::new(Some('-'), None, None, None) )]), 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), "a longer string") .with(Style::empty()) .with($modify) .to_string(); assert_eq!(table, $expected); }; } test_border! { Modify::new((3, 2)).with(Border::default().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::default().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::default().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::default().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((3, 2)).with(Border::default().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((3, 2)).with(Border::default().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::default().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::default().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::default().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((3, 2)).with(Border::default().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::default().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::default().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::default().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::default().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::default().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::default().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::default().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::default().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::default().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::default().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::default().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::default().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::default().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::default().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::default().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::default().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::default().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::default().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::default().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::default().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::default().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::default().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::default().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::default().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::default().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::default().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::default().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::default().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::default().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::default().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::default().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::default().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 = "color")] #[test] fn border_colored_test() { let table = Matrix::table(2, 2) .with(Style::ascii()) .with( Modify::new(Rows::single(1)) .with( BorderColor::filled(Color::try_from('*'.blue().to_string()).unwrap()) .top(Color::try_from('#'.truecolor(12, 220, 100).to_string()).unwrap()), ) .with(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()) .with( Modify::new(Rows::single(1)) .with( BorderColor::filled(Color::try_from('*'.blue().to_string()).unwrap()) .top(Color::try_from('#'.truecolor(12, 220, 100).to_string()).unwrap()), ) .with(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 = "color")] #[test] fn style_with_color_test() { let mut style: RawStyle = Style::ascii().into(); style .set_left(Some('[')) .set_right(Some(']')) .set_top(Some('-')) .set_bottom(Some('-')) .set_vertical(Some('|')) .set_intersection(Some('+')); style .set_color_left(Color::FG_RED) .set_color_right(Color::FG_RED) .set_color_top(Color::FG_BLUE) .set_color_bottom(Color::FG_BLUE) .set_color_vertical(Color::FG_YELLOW) .set_color_intersection(Color::try_from(' '.purple().to_string()).unwrap()); 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(vec![VerticalLine::new(0, Line::filled('+')), VerticalLine::new(4, Line::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_1, Matrix::new(3, 3) .with(Style::rounded().verticals((1..4).map(|i| VerticalLine::new(i, Line::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_2, Matrix::new(3, 3).with(Style::rounded().verticals(vec![VerticalLine::new(1, Line::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_3, Matrix::new(3, 3).with(Style::ascii().verticals([VerticalLine::new(1, Line::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, Matrix::new(3, 3).with(Style::ascii().verticals((0..10).map(|i| VerticalLine::new(i, Line::new(Some('*'), Some('x'), Some('c'), Some('2')))))), "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, Matrix::new(3, 3) .with(HorizontalLine::new(1, Line::new(Some('8'), Some('8'), Some('8'), Some('8')))) .with(VerticalLine::new(1, Line::new(Some('*'), Some('x'), Some('c'), Some('2')))), "+---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()) .with(VerticalLine::new(1, Line::new(Some('*'), Some('x'), Some('c'), Some('2')))), " c " " 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 " " 2 " ); test_table!( vertical_line_2, Matrix::new(3, 3) .with(Style::empty()) .with(VerticalLine::new(1, Line::new(None, Some('x'), Some('c'), Some('2')))), " c " " 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 " " 2 " ); test_table!( vertical_line_3, Matrix::new(3, 3) .with(Style::empty()) .with(VerticalLine::new(1, Line::new(Some('*'), Some('x'), None, None))), " 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(BorderChar::horizontal(':', Offset::Begin(0))) .with(BorderChar::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(BorderChar::horizontal(':', Offset::Begin(0))) .with(BorderChar::horizontal('y', Offset::Begin(3))) .with(BorderChar::horizontal(':', Offset::End(0))) .with(BorderChar::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(BorderChar::horizontal(':', Offset::Begin(0))) .with(BorderChar::horizontal('y', Offset::Begin(3))) .with(BorderChar::horizontal(':', Offset::End(0))) .with(BorderChar::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(BorderChar::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(BorderChar::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(BorderChar::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(BorderChar::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_and_horizontal_border_on_line, 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::new(..5)) .with(BorderChar::vertical('y', Offset::Begin(0))) .with(BorderChar::vertical('^', Offset::End(0))) ) .with(Modify::new(Rows::single(1)) .with(BorderChar::horizontal('x', Offset::Begin(0))) .with(BorderChar::horizontal('@', Offset::End(0))) ), "y N y column 0 y column 1 y column 2 y" "|x---------@|x---------@|x---------@|x---------@|" "y y y y y" "| 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 |" "^ ^ ^ ^ ^" "y 1 y 1-0 y 1-1 y 1-2 y" "y 2 y 2-0 y 2-1 y 2-2 y" ); 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 |" ); tabled-0.14.0/tests/settings/width_test.rs000064400000000000000000002575351046102023000167100ustar 00000000000000#![cfg(feature = "std")] use tabled::{ grid::util::string::string_width_multiline, settings::{ formatting::{TabSize, TrimStrategy}, object::{Columns, Object, Rows, Segment}, peaker::{PriorityMax, PriorityMin}, width::{Justify, MinWidth, SuffixLimit, Width}, Alignment, Margin, Modify, Padding, Panel, Settings, Span, Style, }, }; use crate::matrix::Matrix; use testing_table::{is_lines_equal, static_table, test_table}; #[cfg(feature = "color")] use ::{ansi_str::AnsiStr, owo_colors::OwoColorize}; #[cfg(all(feature = "derive", feature = "color"))] use ::owo_colors::AnsiColors; 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_icrease_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())) .to_string(); assert!(is_lines_equal(&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())) .to_string(); assert!(is_lines_equal(&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())) .to_string(); assert!(is_lines_equal(&table, 17 + 2 + 2)); table }, "| &str |" "|-------------------|" "| this is a long |" "| sentence |" ); #[cfg(feature = "color")] 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())) .to_string(); assert!(is_lines_equal(&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 = "color"))] 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())) .to_string(); assert!(is_lines_equal(&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())) .to_string(); assert!(is_lines_equal(&table, 8)); table }, "| &str |" "|------|" "| this |" ); #[cfg(feature = "color")] test_table!( max_width_wrapped_keep_words_color_0, { let table = Matrix::iter(vec!["this is a long sentence".on_black().green().to_string()]) .with(Style::markdown()) .with(Modify::new(Segment::all()).with(Alignment::left())) .with(Modify::new(Segment::all()).with(Width::wrap(17).keep_words())) .to_string(); AnsiStr::ansi_strip(&table).to_string() }, "| String |" "|-------------------|" "| this is a long |" "| sentence |" ); #[cfg(feature = "color")] test_table!( max_width_wrapped_keep_words_color_0_1, Matrix::iter(vec!["this is a long sentence".on_black().green().to_string()]) .with(Style::markdown()) .with(Modify::new(Segment::all()).with(Alignment::left())) .with(Modify::new(Segment::all()).with(Width::wrap(17).keep_words())), "| 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 = "color")] test_table!( max_width_wrapped_keep_words_color_1, { let table = Matrix::iter(vec!["this is a long sentence".on_black().green().to_string()]) .with(Style::markdown()) .with(Modify::new(Segment::all()).with(Alignment::left())) .with(Modify::new(Segment::all()).with(Width::wrap(17).keep_words())) .to_string(); AnsiStr::ansi_strip(&table).to_string() }, "| String |" "|-------------------|" "| this is a long |" "| sentence |" ); #[cfg(feature = "color")] test_table!( max_width_wrapped_keep_words_color_1_1, Matrix::iter(vec!["this is a long sentence".on_black().green().to_string()]) .with(Style::markdown()) .with(Modify::new(Segment::all()).with(Alignment::left())) .with(Modify::new(Segment::all()).with(Width::wrap(17).keep_words())), "| 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 = "color")] test_table!( max_width_wrapped_keep_words_color_2, { let table = Matrix::iter(vec!["this is a long sentence".on_black().green().to_string()]) .with(Style::markdown()) .with(Modify::new(Segment::all()).with(Alignment::left())) .with(Modify::new(Segment::all()).with(Width::wrap(17).keep_words())) .to_string(); AnsiStr::ansi_strip(&table).to_string() }, "| String |" "|-------------------|" "| this is a long |" "| sentence |" ); #[cfg(feature = "color")] test_table!( max_width_wrapped_keep_words_color_2_1, Matrix::iter(vec!["this is a long sentence".on_black().green().to_string()]) .with(Style::markdown()) .with(Modify::new(Segment::all()).with(Alignment::left())) .with(Modify::new(Segment::all()).with(Width::wrap(17).keep_words())), "| 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 = "color")] test_table!( max_width_wrapped_keep_words_color_3, { let table = Matrix::iter(vec!["this is a long sentence".on_black().green().to_string()]) .with(Style::markdown()) .with(Modify::new(Segment::all()).with(Alignment::left())) .with(Modify::new(Segment::all()).with(Width::wrap(17).keep_words())) .to_string(); AnsiStr::ansi_strip(&table).to_string() }, "| String |" "|-------------------|" "| this is a long |" "| sentence |" ); #[cfg(feature = "color")] test_table!( max_width_wrapped_keep_words_color_3_1, Matrix::iter(vec!["this is a long sentence".on_black().green().to_string()]) .with(Style::markdown()) .with(Modify::new(Segment::all()).with(Alignment::left())) .with(Modify::new(Segment::all()).with(Width::wrap(17).keep_words())), "| 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 = "color")] test_table!( max_width_wrapped_keep_words_color_4, { let table = Matrix::iter(vec!["this".on_black().green().to_string()]) .with(Style::markdown()) .with(Modify::new(Segment::all()).with(Width::wrap(10).keep_words())) .to_string(); AnsiStr::ansi_strip(&table).to_string() }, "| String |" "|--------|" "| this |" ); #[cfg(feature = "color")] test_table!( max_width_wrapped_keep_words_color_4_1, Matrix::iter(vec!["this".on_black().green().to_string()]) .with(Style::markdown()) .with(Modify::new(Segment::all()).with(Width::wrap(10).keep_words())), "| String |" "|--------|" "| \u{1b}[32;40mthis\u{1b}[0m |" ); 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())), "| &str |" "|-------------------|" "| this is a long se |" "| ntencesentencesen |" "| tence |" ); #[cfg(feature = "color")] #[test] fn max_width_wrapped_keep_words_long_word_color() { let data = vec!["this is a long sentencesentencesentence" .on_black() .green() .to_string()]; 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())) .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 = "color")] #[test] fn max_width_keep_words_1() { use tabled::settings::style::HorizontalLine; let table = Matrix::iter(["asdf"]) .with(Width::wrap(7).keep_words()) .to_string(); assert_eq!( table, static_table!( "+-----+" "| &st |" "| r |" "+-----+" "| asd |" "| f |" "+-----+" ) ); let table = Matrix::iter(["qweqw eqwe"]) .with(Width::wrap(8).keep_words()) .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([HorizontalLine::new(1, Style::modern().get_horizontal())]), ) .with(Width::wrap(21).keep_words().priority::()) .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 = "color")] #[test] fn max_width_wrapped_collored() { let data = &[ "asd".red().to_string(), "zxc2".blue().to_string(), "asdasd".on_black().green().to_string(), ]; 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 = "color")] #[test] fn color_chars_are_stripped() { let data = &[ "asd".red().to_string(), "zxc".blue().to_string(), "asdasd".on_black().green().to_string(), ]; 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}[32;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 = "color")] #[test] fn min_with_max_width_truncate_suffix_try_color() { let data = &[ "asd".red().to_string(), "zxc".blue().to_string(), "asdasd".on_black().green().to_string(), ]; 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}[34mzxc\u{1b}[39m |" "| \u{1b}[32;40ma\u{1b}[39m\u{1b}[49m\u{1b}[32m\u{1b}[40m..\u{1b}[39m\u{1b}[49m |" ) ); } #[cfg(feature = "color")] #[test] fn min_width_color() { let data = &[ "asd".red().to_string(), "zxc".blue().to_string(), "asdasd".on_black().green().to_string(), ]; 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}[32;40masdasd\u{1b}[0m |", ); } #[cfg(feature = "color")] #[test] fn min_width_color_with_smaller_then_width() { let data = &[ "asd".red().to_string(), "zxc".blue().to_string(), "asdasd".on_black().green().to_string(), ]; 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!(string_width_multiline(&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!(string_width_multiline(&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!(is_lines_equal(&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!(is_lines_equal(&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!(is_lines_equal(&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!(is_lines_equal(&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!(is_lines_equal(&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!(is_lines_equal(&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!(is_lines_equal(&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!(is_lines_equal(&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!(is_lines_equal(&table, 14)); } #[cfg(feature = "color")] #[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!(is_lines_equal(&table, 20)); let table = Matrix::new(3, 3) .insert((3, 2), "some loong string") .with(Modify::new(Segment::all()).with(Alignment::center())) .with(Style::markdown()) .with(Width::wrap(20).keep_words()) .with(MinWidth::new(20)) .to_string(); assert_eq!( table, static_table!( "| | | column | |" "| | | 1 | |" "|--|--|---------|--|" "| | | 0-1 | |" "| | | 1-1 | |" "| | | some | |" "| | | loong | |" "| | | string | |" ) ); assert!(is_lines_equal(&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!(is_lines_equal(&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!(is_lines_equal(&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!(is_lines_equal(&table, 14)); } #[test] fn max_width_with_span() { let mut table = Matrix::new(3, 3).insert((1, 1), "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!(is_lines_equal(&table.to_string(), 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!(is_lines_equal(&table.to_string(), 20)); table.with(Width::truncate(10)); assert_eq!( table.to_string(), static_table!( " | | | " "--+--+--+--" " | a l | " " | | 1-1 " " | | | " ) ); assert!(is_lines_equal(&table.to_string(), 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!(string_width_multiline(&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!(is_lines_equal(&table.to_string(), 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!(is_lines_equal(&table.to_string(), 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!(string_width_multiline(&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!(is_lines_equal(&table.to_string(), 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!(is_lines_equal(&table.to_string(), 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!(string_width_multiline(&table), 100); assert_eq!( table, static_table!( "| 0 | 1 |" "|------------------------------------------------------------------------|-------------------------|" "| 0 |" "| a long string which will affect min width logic | |" "| 2 | 3 |" ) ); assert!(is_lines_equal(&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!(string_width_multiline(&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), "Hello World") .insert((3, 2), "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), "\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), "\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), "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!(string_width_multiline(&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), "Hello World With Big Line; Here we gooooooo") .insert((3, 2), "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), "Hello World With Big Line; Here we gooooooo") .insert((3, 2), "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!(string_width_multiline(&table), 40); let table = Matrix::new(3, 3) .insert((2, 1), "Hello World With Big Line; Here we gooooooo") .insert((3, 2), "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!(string_width_multiline(&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), "Hello World With Big Line; Here w") .insert((3, 2), "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), "Hello World With Big Line") .with(Style::markdown()) .with(Width::truncate(35).priority::()) .to_string(); assert!(is_lines_equal(&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), "Hello World With Big Line") .with(Style::markdown()) .with(Width::truncate(20).priority::()) .to_string(); assert!(is_lines_equal(&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), "Hello World With Big Line") .with(Style::markdown()) .with(Width::truncate(0).priority::()) .to_string(); assert!(is_lines_equal(&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), "Hello World With Big Line") .with(Style::markdown()) .with(Modify::new((2, 1)).with(Span::column(2))) .with(Width::truncate(15).priority::()) .to_string(); assert!(is_lines_equal(&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), "Hello World With Big Line") .with(Style::markdown()) .with(Width::wrap(35).priority::()) .to_string(); assert!(is_lines_equal(&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), "Hello World With Big Line") .with(Style::markdown()) .with(Width::wrap(20).priority::()) .to_string(); assert!(is_lines_equal(&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), "Hello World With Big Line") .with(Style::markdown()) .with(Width::wrap(0).priority::()) .to_string(); assert!(is_lines_equal(&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), "Hello World With Big Line") .with(Style::markdown()) .with(Modify::new((2, 1)).with(Span::column(2))) .with(Width::wrap(15).priority::()) .to_string(); assert!(is_lines_equal(&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), "Hello World With Big Line") .with(Style::markdown()) .with(Width::truncate(35).priority::()) .to_string(); assert!(is_lines_equal(&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), "Hello World With Big Line") .with(Style::markdown()) .with(Width::truncate(20).priority::()) .to_string(); assert!(is_lines_equal(&table, 20)); assert_eq!( table, static_table!( "| | column | | |" "|--|---------|--|--|" "| | 0-0 | | |" "| | Hello W | | |" "| | 2-0 | | |" ) ); let table = Matrix::new(3, 3) .insert((2, 1), "Hello World With Big Line") .with(Style::markdown()) .with(Width::truncate(0).priority::()) .to_string(); assert!(is_lines_equal(&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), "Hello World With Big Line") .with(Style::markdown()) .with(Modify::new((2, 1)).with(Span::column(2))) .with(Width::truncate(15).priority::()) .to_string(); assert!(is_lines_equal(&table, 15)); assert_eq!( table, static_table!( "| | | co | |" "|--|--|----|--|" "| | | 0- | |" "| | Hello | |" "| | | 2- | |" ) ); let table = Matrix::new(3, 3) .insert((2, 1), "Hello World With Big Line") .with(Style::markdown()) .with(Modify::new((2, 1)).with(Span::column(2))) .with(Width::truncate(17).priority::()) .to_string(); assert!(is_lines_equal(&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), "Hello World With Big Line") .with(Style::markdown()) .with(Width::wrap(35).priority::()) .to_string(); assert!(is_lines_equal(&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), "Hello World With Big Line") .with(Style::markdown()) .with(Width::wrap(20).priority::()) .to_string(); assert!(is_lines_equal(&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), "Hello World With Big Line") .with(Style::markdown()) .with(Width::wrap(0).priority::()) .to_string(); assert!(is_lines_equal(&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), "Hello World With Big Line") .with(Style::markdown()) .with(Modify::new((2, 1)).with(Span::column(2))) .with(Width::wrap(15).priority::()) .to_string(); assert!(is_lines_equal(&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::()) .to_string(); assert_eq!(string_width_multiline(&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::()) .to_string(); assert_eq!(string_width_multiline(&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!(is_lines_equal(&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!(string_width_multiline(&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!(string_width_multiline(&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()) .to_string(); assert_eq!( tabled::grid::util::string::string_width_multiline(&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), "H\nel\nlo World") .insert((3, 2), "multi\nline string\n") .with(Style::markdown()) .with( Modify::new(Columns::new(1..2).not(Rows::single(0))) .with(Width::truncate(1).multiline()), ) .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), "H\nel\nlo World") .insert((3, 2), "multi\nline string\n") .with(Style::markdown()) .with( Modify::new(Columns::new(1..2).not(Rows::single(0))) .with(Width::truncate(1).multiline().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), "H\nel\nlo World") .insert((3, 2), "multi\nline string\n") .with(Style::markdown()) .with(Width::truncate(20).multiline()) .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), "H\nel\nlo World") .insert((3, 2), "multi\nline string\n") .with(Style::markdown()) .with(Width::truncate(20).suffix(".").multiline()) .to_string(); assert_eq!( table, static_table!( "| | . | col. | c. |" "|--|---|------|----|" "| | . | 0-1 | 0. |" "| | . | | |" "| | . | | |" "| | . | 1-1 | 1. |" "| | . | mul. | 2. |" "| | | lin. | |" "| | | . | |" ) ); } #[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!(is_lines_equal(&table, 57)); let table = Matrix::iter(&data) .with(Style::markdown()) .with(Modify::new(Segment::all()).with(Alignment::left())) .with(Width::wrap(57).keep_words()) .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!(is_lines_equal(&table, 57)); } #[cfg(feature = "color")] #[test] fn wrapping_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( "0.2.1".red().to_string(), "2021-06-23".red().on_truecolor(8, 10, 30).to_string(), "true".to_string(), "#[header(inline)] attribute" .blue() .on_color(AnsiColors::Green) .to_string(), ), D( "0.2.0".red().to_string(), "2021-06-19".green().on_truecolor(8, 100, 30).to_string(), "false".to_string(), "API changes".yellow().to_string(), ), D( "0.1.4".white().to_string(), "2021-06-07".red().on_truecolor(8, 10, 30).to_string(), "false".to_string(), "display_with attribute" .red() .on_color(AnsiColors::Black) .to_string(), ), ]; 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 | | | |" "|-----|-------------|--------|--------------------------|" "| \u{1b}[31m0.2\u{1b}[39m | \u{1b}[48;2;8;10;30m\u{1b}[31m2021-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}[48;2;8;100;30m\u{1b}[32m2021-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}[48;2;8;10;30m\u{1b}[31m2021-06-07\u{1b}[39m\u{1b}[49m | false | \u{1b}[31;40mdisplay_with attribute\u{1b}[0m |" "| \u{1b}[37m.4\u{1b}[39m | | | |" ) ); assert_eq!(string_width_multiline(&table), 57); let table = Matrix::iter(&data) .with(Style::markdown()) .with(Modify::new(Segment::all()).with(Alignment::left())) .with(Width::wrap(57).keep_words()) .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}[48;2;8;10;30m\u{1b}[31m2021-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}[48;2;8;100;30m\u{1b}[32m2021-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}[48;2;8;10;30m\u{1b}[31m2021-06-07\u{1b}[39m\u{1b}[49m | false | \u{1b}[31;40mdisplay_with attribute\u{1b}[0m |" "| \u{1b}[37m.4\u{1b}[39m | | | |" ) ); assert_eq!(string_width_multiline(&table), 57); } #[cfg(feature = "color")] #[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( "0.2.1".red().to_string(), "2021-06-23".red().on_truecolor(8, 10, 30).to_string(), "true".to_string(), "#[header(inline)] attribute" .blue() .on_color(AnsiColors::Green) .to_string(), ), D( "0.2.0".red().to_string(), "2021-06-19".green().on_truecolor(8, 100, 30).to_string(), "false".to_string(), "API changes".yellow().to_string(), ), D( "0.1.4".white().to_string(), "2021-06-07".red().on_truecolor(8, 10, 30).to_string(), "false".to_string(), "display_with attribute" .red() .on_color(AnsiColors::Black) .to_string(), ), ]; 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}[48;2;8;10;30m\u{1b}[31m2021-06-23\u{1b}[39m\u{1b}[49m | true | \u{1b}[34;42m#[header(inline)] attrib\u{1b}[39m\u{1b}[49m |\n| \u{1b}[31m0.2\u{1b}[39m | \u{1b}[48;2;8;100;30m\u{1b}[32m2021-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}[48;2;8;10;30m\u{1b}[31m2021-06-07\u{1b}[39m\u{1b}[49m | false | \u{1b}[31;40mdisplay_with attribute\u{1b}[0m |" ); assert_eq!(string_width_multiline(&table), 57); } #[cfg(feature = "color")] 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 = "color")] #[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()) .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 = "color")] #[test] fn hyperlinks_with_color() { use owo_colors::OwoColorize; #[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()) .with(Alignment::left()), ) .to_string() }; let text = format_osc8_hyperlink( "https://www.debian.org/", "Debian".red().to_string().as_str(), ); 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 | |" "+--------+--------+" ) ); } }