prettytable-rs-0.10.0/.cargo_vcs_info.json0000644000000001360000000000100140730ustar { "git": { "sha1": "4d66e6ebddcd52b641369042b68959ad323d9ad0" }, "path_in_vcs": "" }prettytable-rs-0.10.0/.github/dependabot.yml000064400000000000000000000002771046102023000170610ustar 00000000000000version: 2 updates: - package-ecosystem: cargo directory: "/" schedule: interval: daily time: "04:00" open-pull-requests-limit: 10 reviewers: - phsym assignees: - phsym prettytable-rs-0.10.0/.gitignore000064400000000000000000000000401046102023000146450ustar 00000000000000/target/ /Cargo.lock \.vscode/ prettytable-rs-0.10.0/.travis.yml000064400000000000000000000047561046102023000150100ustar 00000000000000language: rust rust: - 1.32.0 - 1.33.0 - 1.34.0 - 1.35.0 - 1.36.0 - 1.37.0 - stable - beta - nightly script: - cargo build --verbose --no-default-features - cargo test --verbose --no-default-features - cargo build --verbose - cargo test --verbose - cargo build --verbose --all-features - cargo test --verbose --all-features env: global: - RUSTFLAGS="-C link-dead-code" - secure: k+5s8j7arJSoqS/7BnX7vBEXb1csFsn/cr+WCxRQtlV7bK8JkQ/3t3E1MCUpCSHJLb6K+GlRSkN6tWkhPVUpYA57J7+bSADJ2cAWBq2ArMubXMkMl/t7ibuOArGggDRLulYZ83kDZEkVcMs3QyAv7cGvSMnj6VehTeUrZsIreHmNGJnpsxuXqsfaHhiToWkO/KTRGHOuro7xQczCKzV54g7NAfIgWvcy3T5zVpkaNZWGd/BaRvkBRP8fZpqNBQSlG3Unq3q6wWIeLIJd3QWAQCrzDDMNIbiwsU/KHOJfVvvDFDJF/rzn1EwVvkWRQmT+GtPmLDCRV5OD4hmjVyEtdFU1aLaxxeQBNdSUb3SsDbnUkfyX+WgHEAYRxRAOGW8vhA7+9gaMI2fStkc5JwAcfrZxKkDd9YsUX4iYNk207zsRz/5M6gTWCw2e7jLj9kUGMiTy+008TRxAjSNbN9sl+FRMH5BPMDlgDM4Ohp1+JRq0Mfu1qT6hoYXb+AoRvHijw9HoqtaU2lTamuSN6+LFNJ0CDt2Qhy4jn+Dmp5ZlivcUVzpQpdZoPG00BnLK6YfYoCF9gFX194TM2T6ljhYGaL7ITZI9Cz4qMxD3r459aGz8sUAcTkSbTRMGpTb4fJVfvCgsP2IDfKO7WS/W4SbCzYMh7PfpQg03BAvld0y69O8= - secure: sF8l0788UwTlEjw+ctKwiw+73zLErGuah6Oh/zYjogwms27HDR7JxrWVXc/dvogwP0nuKoj5m8g4sbtIRkQDZ80OjEr1kefoE71fNDskDYXe4+lTP/RVHF4myTjVZ0HbrHLhiNzpP7NzzCURuOsXa6RGN7A6tSnpX5MZ7euW1HxF9Vjjebsrpko86AJbDESaJaBrFf40P+BJ0xKEEaygtAtnoYMEmSXzAAmOb6SAyDRx8do7433P6At2AEOTuUAocl/nefb36X4aEVfPPd23PVR1wICZmn1LwYccXbGZmW7OvhgU82P9Lb4SgsQtcDJdzOsH0yjsTimf/EDESLQhcD35MDGMUvL0nZ/sOWuTigZw8ax1CLna2kbxYwhZ3lfA8hqt9dkjzi6lwRRSP1CZD0YmaBA0S6H9zcmBdv551wn4nCGu4+uCYcvhqHh6cSZZGTSgiCZ3zr59TeMvAV5Fy3up4IhSYwrQYuPeZ3Wzzm3UEvGaTfmcgIEyqxRfBobNxzQI0I5PcvfXleHxuuOlZiMDFNQEO/dw5pbMaUdABggc0AR6Ph8dmScX3iCfuhzlErUFwNWGEifoojNquuj3+4FOkoAJ3hwqw+hdK3DS7cI2EFjTO9f6bBD72bbeo8lIylrbJo5KDAeIPzs0q1PX2s4DlSoRV39LggSaeZt/xAA= after_success: | sudo apt-get update && sudo apt-get install libcurl4-openssl-dev libelf-dev libdw-dev cmake gcc binutils-dev libiberty-dev && wget https://github.com/SimonKagstrom/kcov/archive/master.tar.gz && tar xzf master.tar.gz && mkdir kcov-master/build && cd kcov-master/build && cmake .. && make && sudo make install && cd ../.. && for file in target/debug/prettytable-*[^\.d]; do mkdir -p "target/cov/$(basename $file)"; kcov --exclude-pattern=/.cargo,/usr/lib --verify "target/cov/$(basename $file)" "$file"; done && bash <(curl -s https://codecov.io/bash) rm -Rf master.tar.gz kcov-master git clone https://gist.github.com/deaf3815d2eecce72279.git ./publish_doc && sh ./publish_doc/publish_doc.sh prettytable yes prettytable-rs-0.10.0/CHANGELOG.md000064400000000000000000000017101046102023000144730ustar 00000000000000# Changelog All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). # 0.10.0 (2022-12-27) ## Fixed - Fix panic due to incorrect ANSI escape handling ([#137]) - Fix display of empty tables ([#127]) ## Changed - Remove the unsafe code in `Table::as_ref` ([#146]) - Switch `atty` to `is-terminal` ([#151]) - Minimal Supported Rust Version bumped to 1.56 ## Thanks - @alexanderkjall and @5225225 fuzzer work and fixing panics - @david0u0 fixing ([#145]) Undefined behavior (UB) on `Table::as_ref` [#127]: https://github.com/phsym/prettytable-rs/pull/127 [#137]: https://github.com/phsym/prettytable-rs/pull/137 [#145]: https://github.com/phsym/prettytable-rs/issues/145 [#146]: https://github.com/phsym/prettytable-rs/pull/146 [#151]: https://github.com/phsym/prettytable-rs/pull/151 prettytable-rs-0.10.0/Cargo.lock0000644000000250600000000000100120510ustar # This file is automatically @generated by Cargo. # It is not intended for manual editing. version = 3 [[package]] name = "bitflags" version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bstr" version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ba3569f383e8f1598449f1a423e72e99569137b47740b1da11ef19af3d5c3223" dependencies = [ "lazy_static", "memchr", "regex-automata", "serde", ] [[package]] name = "cc" version = "1.0.78" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a20104e2335ce8a659d6dd92a51a767a0c062599c73b343fd152cb401e828c3d" [[package]] name = "cfg-if" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "csv" version = "1.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "22813a6dc45b335f9bade10bf7271dc477e81113e89eb251a0bc2a8a81c536e1" dependencies = [ "bstr", "csv-core", "itoa", "ryu", "serde", ] [[package]] name = "csv-core" version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b2466559f260f48ad25fe6317b3c8dac77b5bdb5763ac7d9d6103530663bc90" dependencies = [ "memchr", ] [[package]] name = "dirs-next" version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b98cf8ebf19c3d1b223e151f99a4f9f0690dca41414773390fc824184ac833e1" dependencies = [ "cfg-if", "dirs-sys-next", ] [[package]] name = "dirs-sys-next" version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d" dependencies = [ "libc", "redox_users", "winapi", ] [[package]] name = "encode_unicode" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34aa73646ffb006b8f5147f3dc182bd4bcb190227ce861fc4a4844bf8e3cb2c0" [[package]] name = "errno" version = "0.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f639046355ee4f37944e44f60642c6f3a7efa3cf6b78c78a0d989a8ce6c396a1" dependencies = [ "errno-dragonfly", "libc", "winapi", ] [[package]] name = "errno-dragonfly" version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" dependencies = [ "cc", "libc", ] [[package]] name = "getrandom" version = "0.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c05aeb6a22b8f62540c194aac980f2115af067bfe15a0734d7277a768d396b31" dependencies = [ "cfg-if", "libc", "wasi", ] [[package]] name = "hermit-abi" version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ee512640fe35acbfb4bb779db6f0d80704c2cacfa2e39b601ef3e3f47d1ae4c7" dependencies = [ "libc", ] [[package]] name = "io-lifetimes" version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "46112a93252b123d31a119a8d1a1ac19deac4fac6e0e8b0df58f0d4e5870e63c" dependencies = [ "libc", "windows-sys", ] [[package]] name = "is-terminal" version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "28dfb6c8100ccc63462345b67d1bbc3679177c75ee4bf59bf29c8b1d110b8189" dependencies = [ "hermit-abi", "io-lifetimes", "rustix", "windows-sys", ] [[package]] name = "itoa" version = "0.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b71991ff56294aa922b450139ee08b3bfc70982c6b2c7562771375cf73542dd4" [[package]] name = "lazy_static" version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" version = "0.2.139" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "201de327520df007757c1f0adce6e827fe8562fbc28bfd9c15571c66ca1f5f79" [[package]] name = "linux-raw-sys" version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f051f77a7c8e6957c0696eac88f26b0117e54f52d3fc682ab19397a8812846a4" [[package]] name = "memchr" version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" [[package]] name = "prettytable-rs" version = "0.10.0" dependencies = [ "csv", "encode_unicode", "is-terminal", "lazy_static", "term", "unicode-width", ] [[package]] name = "proc-macro2" version = "1.0.49" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "57a8eca9f9c4ffde41714334dee777596264c7825420f521abc92b5b5deb63a5" dependencies = [ "unicode-ident", ] [[package]] name = "quote" version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8856d8364d252a14d474036ea1358d63c9e6965c8e5c1885c18f73d70bff9c7b" dependencies = [ "proc-macro2", ] [[package]] name = "redox_syscall" version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" dependencies = [ "bitflags", ] [[package]] name = "redox_users" version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b033d837a7cf162d7993aded9304e30a83213c648b6e389db233191f891e5c2b" dependencies = [ "getrandom", "redox_syscall", "thiserror", ] [[package]] name = "regex-automata" version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" [[package]] name = "rustix" version = "0.36.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4feacf7db682c6c329c4ede12649cd36ecab0f3be5b7d74e6a20304725db4549" dependencies = [ "bitflags", "errno", "io-lifetimes", "libc", "linux-raw-sys", "windows-sys", ] [[package]] name = "rustversion" version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5583e89e108996506031660fe09baa5011b9dd0341b89029313006d1fb508d70" [[package]] name = "ryu" version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7b4b9743ed687d4b4bcedf9ff5eaa7398495ae14e61cba0a295704edbc7decde" [[package]] name = "serde" version = "1.0.152" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bb7d1f0d3021d347a83e556fc4683dea2ea09d87bccdf88ff5c12545d89d5efb" [[package]] name = "syn" version = "1.0.107" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1f4064b5b16e03ae50984a5a8ed5d4f8803e6bc1fd170a3cda91a1be4b18e3f5" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] [[package]] name = "term" version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c59df8ac95d96ff9bede18eb7300b0fda5e5d8d90960e76f8e14ae765eedbf1f" dependencies = [ "dirs-next", "rustversion", "winapi", ] [[package]] name = "thiserror" version = "1.0.38" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6a9cd18aa97d5c45c6603caea1da6628790b37f7a34b6ca89522331c5180fed0" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" version = "1.0.38" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fb327af4685e4d03fa8cbcf1716380da910eeb2bb8be417e7f9fd3fb164f36f" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "unicode-ident" version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "84a22b9f218b40614adcb3f4ff08b703773ad44fa9423e4e0d346d5db86e4ebc" [[package]] name = "unicode-width" version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b" [[package]] name = "wasi" version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "winapi" version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" dependencies = [ "winapi-i686-pc-windows-gnu", "winapi-x86_64-pc-windows-gnu", ] [[package]] name = "winapi-i686-pc-windows-gnu" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" [[package]] name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] name = "windows-sys" version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7" dependencies = [ "windows_aarch64_gnullvm", "windows_aarch64_msvc", "windows_i686_gnu", "windows_i686_msvc", "windows_x86_64_gnu", "windows_x86_64_gnullvm", "windows_x86_64_msvc", ] [[package]] name = "windows_aarch64_gnullvm" version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "41d2aa71f6f0cbe00ae5167d90ef3cfe66527d6f613ca78ac8024c3ccab9a19e" [[package]] name = "windows_aarch64_msvc" version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dd0f252f5a35cac83d6311b2e795981f5ee6e67eb1f9a7f64eb4500fbc4dcdb4" [[package]] name = "windows_i686_gnu" version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fbeae19f6716841636c28d695375df17562ca208b2b7d0dc47635a50ae6c5de7" [[package]] name = "windows_i686_msvc" version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "84c12f65daa39dd2babe6e442988fc329d6243fdce47d7d2d155b8d874862246" [[package]] name = "windows_x86_64_gnu" version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bf7b1b21b5362cbc318f686150e5bcea75ecedc74dd157d874d754a2ca44b0ed" [[package]] name = "windows_x86_64_gnullvm" version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09d525d2ba30eeb3297665bd434a54297e4170c7f1a44cad4ef58095b4cd2028" [[package]] name = "windows_x86_64_msvc" version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f40009d85759725a34da6d89a94e63d7bdc50a862acf0dbc7c8e488f1edcb6f5" prettytable-rs-0.10.0/Cargo.toml0000644000000033430000000000100120740ustar # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g., crates.io) dependencies. # # If you are reading this file be aware that the original Cargo.toml # will likely look very different (and much more reasonable). # See Cargo.toml.orig for the original contents. [package] edition = "2018" name = "prettytable-rs" version = "0.10.0" authors = ["Pierre-Henri Symoneaux"] exclude = ["prettytable-evcxr.png"] description = "A library for printing pretty formatted tables in terminal" homepage = "https://github.com/phsym/prettytable-rs" documentation = "https://docs.rs/crate/prettytable-rs/" readme = "README.md" keywords = [ "tab", "table", "format", "pretty", "print", ] categories = ["command-line-interface"] license = "BSD-3-Clause" repository = "https://github.com/phsym/prettytable-rs" [lib] name = "prettytable" [[bin]] name = "main" path = "src/main.rs" edition = "2018" [dependencies.csv] version = "1.1" optional = true [dependencies.encode_unicode] version = "1.0" [dependencies.is-terminal] version = "0.4" [dependencies.lazy_static] version = "1.4" [dependencies.term] version = "0.7" [dependencies.unicode-width] version = "0.1" [features] default = [ "win_crlf", "csv", ] evcxr = [] win_crlf = [] [badges.appveyor] branch = "master" repository = "phsym/prettytable-rs" service = "github" [badges.codecov] branch = "master" repository = "phsym/prettytable-rs" service = "github" [badges.maintenance] status = "passively-maintained" [badges.travis-ci] branch = "master" repository = "phsym/prettytable-rs" prettytable-rs-0.10.0/Cargo.toml.orig000064400000000000000000000022611046102023000155530ustar 00000000000000[package] name = "prettytable-rs" version = "0.10.0" description = "A library for printing pretty formatted tables in terminal" homepage = "https://github.com/phsym/prettytable-rs" repository = "https://github.com/phsym/prettytable-rs" documentation = "https://docs.rs/crate/prettytable-rs/" readme = "README.md" authors = [ "Pierre-Henri Symoneaux" ] keywords = ["tab", "table", "format", "pretty", "print"] categories = ["command-line-interface"] license = "BSD-3-Clause" edition = "2018" exclude = [ "prettytable-evcxr.png" ] [badges] appveyor = { repository = "phsym/prettytable-rs", branch = "master", service = "github" } travis-ci = { repository = "phsym/prettytable-rs", branch = "master" } codecov = { repository = "phsym/prettytable-rs", branch = "master", service = "github" } maintenance = { status = "passively-maintained" } [features] default = ["win_crlf", "csv"] evcxr = [] win_crlf = [] [[bin]] name = "main" edition = "2018" path = "src/main.rs" [lib] name = "prettytable" [dependencies] unicode-width = "0.1" term = "0.7" lazy_static = "1.4" is-terminal = "0.4" encode_unicode = "1.0" csv = { version = "1.1", optional = true } prettytable-rs-0.10.0/LICENSE.txt000064400000000000000000000027571046102023000145210ustar 00000000000000Copyright (c) 2022, Pierre-Henri Symoneaux All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of prettytable-rs nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.prettytable-rs-0.10.0/README.md000064400000000000000000000320431046102023000141440ustar 00000000000000![License](http://img.shields.io/badge/license-BSD-lightgrey.svg) [![Build Status](https://travis-ci.org/phsym/prettytable-rs.svg?branch=master)](https://travis-ci.org/phsym/prettytable-rs) [![Build status](https://ci.appveyor.com/api/projects/status/wdh9klb35fed6ik9?svg=true)](https://ci.appveyor.com/project/phsym/tabprint) [![codecov](https://codecov.io/gh/phsym/prettytable-rs/branch/master/graph/badge.svg)](https://codecov.io/gh/phsym/prettytable-rs) [![Crates.io](https://img.shields.io/crates/v/prettytable-rs.svg)](https://crates.io/crates/prettytable-rs) [![Doc.rs](https://docs.rs/prettytable-rs/badge.svg)](https://docs.rs/crate/prettytable-rs/) [![Doc.rs](https://img.shields.io/badge/docs-master-blue.svg)](http://phsym.github.io/prettytable-rs/master) # prettytable-rs A formatted and aligned table printer library for [Rust](https://www.rust-lang.org). *Copyright © 2022 Pierre-Henri Symoneaux* > THIS SOFTWARE IS DISTRIBUTED WITHOUT ANY WARRANTY
> Check LICENSE.txt file for more information.
# How to use * [Including](#user-content-including) * [Basic usage](#user-content-basic-usage) * [Using macros](#user-content-using-macros) * [Do it with style](#user-content-do-it-with-style) * [List of style specifiers](#user-content-list-of-style-specifiers) * [List of color specifiers](#user-content-list-of-color-specifiers) * [Slicing](#user-content-slicing) * [Customize look and feel of a table](#customize-look-and-feel-of-a-table) * [CSV import/export](#user-content-csv-importexport) * [Importing](#user-content-importing) * [Exporting](#user-content-exporting) * [Note on line endings](#user-content-note-on-line-endings) * [Evcxr Integration](#evcxr-integration) ## Including Include the library as a dependency to your project by adding the following lines to your **Cargo.toml** file: ```toml [dependencies] prettytable-rs = "^0.10" ``` The library requires at least `rust v1.56`. Any changes to the MSRV will be done with a minor version bump. ## SemVer Policy * Pre-1.0.0 breaking changes will follow a minor version bump * Post-1.0.0 All default features of this library are covered by SemVer * MSRV is considered exempt from SemVer as noted above ## Basic usage Start using it like this: ```rust #[macro_use] extern crate prettytable; use prettytable::{Table, Row, Cell}; fn main() { // Create the table let mut table = Table::new(); // Add a row per time table.add_row(row!["ABC", "DEFG", "HIJKLMN"]); table.add_row(row!["foobar", "bar", "foo"]); // A more complicated way to add a row: table.add_row(Row::new(vec![ Cell::new("foobar2"), Cell::new("bar2"), Cell::new("foo2")])); // Print the table to stdout table.printstd(); } ``` The code above will output ```text +---------+------+---------+ | ABC | DEFG | HIJKLMN | +---------+------+---------+ | foobar | bar | foo | +---------+------+---------+ | foobar2 | bar2 | foo2 | +---------+------+---------+ ``` ## Using macros For everyday usage consider `table!` macro. This code will produce the same output as above: ```rust #[macro_use] extern crate prettytable; fn main() { let table = table!(["ABC", "DEFG", "HIJKLMN"], ["foobar", "bar", "foo"], ["foobar2", "bar2", "foo2"]); table.printstd(); } ``` The `ptable!` macro combines creating and printing a table: ```rust #[macro_use] extern crate prettytable; fn main() { let table = ptable!(["ABC", "DEFG", "HIJKLMN"], ["foobar", "bar", "foo"], ["foobar2", "bar2", "foo2"]); } ``` Tables also support multiline cells content. As a result, you can print a table into another table (yo dawg ;). For example: ```rust let table1 = table!(["ABC", "DEFG", "HIJKLMN"], ["foobar", "bar", "foo"], ["foobar2", "bar2", "foo2"]); let table2 = table!(["Title 1", "Title 2"], ["This is\na multiline\ncell", "foo"], ["Yo dawg ;) You can even\nprint tables\ninto tables", table1]); table2.printstd(); ``` will print ```text +-------------------------+------------------------------+ | Title 1 | Title 2 | +-------------------------+------------------------------+ | This is | foo | | a multiline | | | cell | | +-------------------------+------------------------------+ | Yo dawg ;) You can even | +---------+------+---------+ | | print tables | | ABC | DEFG | HIJKLMN | | | into tables | +---------+------+---------+ | | | | foobar | bar | foo | | | | +---------+------+---------+ | | | | foobar2 | bar2 | foo2 | | | | +---------+------+---------+ | +-------------------------+------------------------------+ ``` Rows may have different numbers of cells. The table will automatically adapt to the largest row by printing additional empty cells in smaller rows. ## Do it with style! Tables can have a styled output with background and foreground colors, bold and italic as configurable settings, thanks to the `term` crate. Alignment in cells can also be set (Left, Right, Center), and a cell can span accross multiple columns. `term` style attributes are reexported - directly: ```rust use prettytable::{Attr, color}; /* ... */ table.add_row(Row::new(vec![ Cell::new("foobar") .with_style(Attr::Bold) .with_style(Attr::ForegroundColor(color::GREEN)), Cell::new("bar") .with_style(Attr::BackgroundColor(color::RED)) .with_style(Attr::Italic(true)) .with_hspan(2), Cell::new("foo") ])); ``` - through style strings: ```rust table.add_row(Row::new(vec![ Cell::new("foobar").style_spec("bFg"), Cell::new("bar").style_spec("BriH2"), Cell::new("foo")])); ``` - using `row!` macro: ```rust table.add_row(row![bFg->"foobar", BriH2->"bar", "foo"]); ``` - using `table!` macro (this one creates a new table, unlike previous examples): ```rust table!([bFg->"foobar", BriH2->"bar", "foo"]); ``` Here - **bFg** means **bold**, **F**oreground: **g**reen, - **BriH2** means **B**ackground: **r**ed, **i**talic, **H**orizontal span of **2**. Another example: **FrBybc** means **F**oreground: **r**ed, **B**ackground: **y**ellow, **b**old, **c**enter. All cases of styling cells in macros: - With `row!`, for each cell separately: ```rust row![FrByb->"ABC", FrByb->"DEFG", "HIJKLMN"]; ``` - With `row!`, for the whole row: ```rust row![FY => "styled", "bar", "foo"]; ``` - With `table!`, for each cell separately: ```rust table!([FrBybl->"A", FrBybc->"B", FrBybr->"C"], [123, 234, 345, 456]); ``` - With `table!`, for whole rows: ```rust table!([Frb => "A", "B", "C"], [Frb => 1, 2, 3, 4], [1, 2, 3]); ``` - With `table!`, mixed styling: ```rust table!([Frb => "A", "B", "C"], [Frb->1, Fgi->2, 3, 4], [1, 2, 3]); ``` ### List of style specifiers: * **F** : **F**oreground (must be followed by a color specifier) * **B** : **B**ackground (must be followed by a color specifier) * **H** : **H**orizontal span (must be followed by a number) * **b** : **b**old * **i** : **i**talic * **u** : **u**nderline * **c** : Align **c**enter * **l** : Align **l**eft * **r** : Align **r**ight * **d** : **d**efault style ### List of color specifiers: Lowercase letters stand for **usual** colors: * **r** : Red * **b** : Blue * **g** : Green * **y** : Yellow * **c** : Cyan * **m** : Magenta * **w** : White * **d** : Black Uppercase letters stand for **bright** counterparts of the above colors: * **R** : Bright Red * **B** : Bright Blue * ... and so on ... ## Slicing Tables can be sliced into immutable borrowed subtables. Slices are of type `prettytable::TableSlice<'a>`. For example, ```rust use prettytable::Slice; /* ... */ let slice = table.slice(2..5); table.printstd(); ``` will print a table with only lines 2, 3 and 4 from `table`. Other `Range` syntaxes are supported. For example: ```rust table.slice(..); // Returns a borrowed immutable table with all rows table.slice(2..); // Returns a table with rows starting at index 2 table.slice(..3); // Returns a table with rows until the one at index 3 ``` ## Customize look and feel of a table The look and feel of a table can be customized with `prettytable::format::TableFormat`. Configurable settings include: - Borders (left and right) - Junctions - Column separators - Line separators - Titles (using `table.set_titles()`) To do this, either: - create a new `TableFormat` object, then call setters until you get the desired configuration; - or use the convenient `FormatBuilder` and Builder pattern, shown below ```rust let mut table = Table::new(); let format = format::FormatBuilder::new() .column_separator('|') .borders('|') .separators(&[format::LinePosition::Top, format::LinePosition::Bottom], format::LineSeparator::new('-', '+', '+', '+')) .padding(1, 1) .build(); table.set_format(format); table.set_titles(row!["Title 1", "Title 2"]); table.add_row(row!["Value 1", "Value 2"]); table.add_row(row!["Value three", "Value four"]); ``` The code above will make the table look like ``` +-------------+------------+ | Title 1 | Title 2 | | Value 1 | Value 2 | | Value three | Value four | +-------------+------------+ ``` For convenience, several formats are predefined in `prettytable::format::consts` module. Some formats and their respective outputs: - ```rust use prettytable::format; table.set_format(*format::consts::FORMAT_NO_LINESEP_WITH_TITLE); ``` ``` +-------------+------------+ | Title 1 | Title 2 | +-------------+------------+ | Value 1 | Value 2 | | Value three | Value four | +-------------+------------+ ``` - ```rust use prettytable::format; table.set_format(*format::consts::FORMAT_NO_BORDER_LINE_SEPARATOR); ``` ``` Title 1 | Title 2 ------------+------------ Value 1 | Value 2 Value three | Value four ``` Check API documentation for the full list of available predefined formats. ## CSV import/export Tables can be imported from and exported to **CSV**. This is possible thanks to the default & optional feature `csv`. > The `csv` feature may become deactivated by default on future major releases. ### Importing A `Table` can be imported from a string: ```rust let table = Table::from_csv_string("ABC,DEFG,HIJKLMN\n\ foobar,bar,foo\n\ foobar2,bar2,foo2")?; ``` or from CSV files: ```rust let table = Table::from_csv_file("input_csv.txt")?; ``` > Those 2 ways of importing CSV assumes a CSV format with `no headers`, and delimited with `commas` Import can also be done from a CSV reader which allows more customization around the CSV format: ```rust let reader = /* create a reader */; /* do something with the reader */ let table = Table::from_csv(reader); ``` ### Exporting Export to a generic `Write`: ```rust let out = File::create("output_csv.txt")?; table.to_csv(out)?; ``` or to a `csv::Writer`: ```rust let writer = /* create a writer */; /* do something with the writer */ table.to_csv_writer(writer)?; ``` ## Note on line endings By default, the library prints tables with platform specific line ending. This means on Windows, newlines will be rendered with `\r\n` while on other platforms they will be rendered with `\n`. Since `v0.6.3`, platform specific line endings are activated though the default feature `win_crlf`, which can be deactivated. When this feature is deactivated (for instance with the `--no-default-features` flag in cargo), line endings will be rendered with `\n` on any platform. This customization capability will probably move to Formatting API in a future release. Additional examples are provided in the documentation and in [examples](./examples/) directory. ## Evcxr Integration [Evcxr][evcxr] is a Rust REPL and a [Jupyter notebook kernel][evcxr-jupyter]. This crate integrates into Evcxr and the Jupyter notebooks using the `evcxr` feature flag, which enables native displays of tables. This includes support for displaying colors and various formattings. You can include prettytable as a dependency using this line: ``` :dep prettytable = { git = "https://github.com/phsym/prettytable-rs", package = "prettytable-rs", features = ["evcxr"] } ``` ![prettytable being used in a Jupyter notebook with Evcxr Rust kernel.](./prettytable-evcxr.png) [evcxr]: https://github.com/google/evcxr/ [evcxr-jupyter]: https://github.com/google/evcxr/blob/master/evcxr_jupyter/README.md prettytable-rs-0.10.0/appveyor.yml000064400000000000000000000013101046102023000152460ustar 00000000000000environment: matrix: - TARGET: i686-pc-windows-gnu - TARGET: i686-pc-windows-msvc - TARGET: x86_64-pc-windows-gnu - TARGET: x86_64-pc-windows-msvc install: - ps: Start-FileDownload "https://static.rust-lang.org/rustup/dist/x86_64-pc-windows-msvc/rustup-init.exe" - rustup-init.exe -y --default-host %TARGET% --default-toolchain stable - set PATH=%PATH%;C:\Users\appveyor\.cargo\bin - rustc -V - cargo -V build_script: - cargo build --verbose - cargo build --verbose --no-default-features - cargo build --verbose --all-features test_script: - cargo test --verbose - cargo test --verbose --no-default-features - cargo test --verbose --all-features prettytable-rs-0.10.0/examples/basic.rs000064400000000000000000000025061046102023000161330ustar 00000000000000use prettytable::{ptable, row, table, Cell, Row, Table}; /* Following main function will print : +---------+------+---------+ | ABC | DEFG | HIJKLMN | +---------+------+---------+ | foobar | bar | foo | +---------+------+---------+ | foobar2 | bar2 | foo2 | +---------+------+---------+ Modified : +---------+------+---------+ | ABC | DEFG | HIJKLMN | +---------+------+---------+ | foobar | bar | foo | +---------+------+---------+ | foobar2 | bar2 | new_foo | +---------+------+---------+ */ fn main() { let mut table = Table::new(); table.add_row(row!["ABC", "DEFG", "HIJKLMN"]); table.add_row(row!["foobar", "bar", "foo"]); table.add_row(Row::new(vec![ Cell::new("foobar2"), Cell::new("bar2"), Cell::new("foo2"), ])); table.printstd(); println!("Modified : "); table.set_element("new_foo", 2, 1).unwrap(); table.printstd(); // The same table can be built the following way : let _table = table!( ["ABC", "DEFG", "HIJKLMN"], ["foobar", "bar", "foo"], ["foobar2", "bar2", "foo2"] ); // Or directly print it like this let _table = ptable!( ["ABC", "DEFG", "HIJKLMN"], ["foobar", "bar", "foo"], ["foobar2", "bar2", "foo2"] ); } prettytable-rs-0.10.0/examples/csv.rs000064400000000000000000000014731046102023000156470ustar 00000000000000/* Following main function will print : +---------+------+---------+ | ABC | DEFG | HIJKLMN | +---------+------+---------+ | foobar | bar | foo | +---------+------+---------+ | foobar2 | bar2 | foo2 | +---------+------+---------+ ABC,DEFG,HIJKLMN foobar,bar,foo foobar2,bar2,foo2 */ #[cfg(feature = "csv")] fn main() { use prettytable::Table; let table = Table::from_csv_string( "ABC,DEFG,HIJKLMN\n\ foobar,bar,foo\n\ foobar2,bar2,foo2", ) .unwrap(); table.printstd(); println!(""); println!( "{}", String::from_utf8(table.to_csv(Vec::new()).unwrap().into_inner().unwrap()).unwrap() ); } #[cfg(not(feature = "csv"))] fn main() {} prettytable-rs-0.10.0/examples/formatting.rs000064400000000000000000000077171046102023000172350ustar 00000000000000use prettytable::{format, row, table}; fn main() { let mut table = table!(["Value 1", "Value 2"], ["Value three", "Value four"]); table.set_titles(row!["Title 1", "Title 2"]); // Print // +-------------+------------+ // | Title 1 | Title 2 | // +-------------+------------+ // | Value 1 | Value 2 | // | Value three | Value four | // +-------------+------------+ println!("FORMAT_NO_LINESEP_WITH_TITLE :"); table.set_format(*format::consts::FORMAT_NO_LINESEP_WITH_TITLE); table.printstd(); println!(""); // Print // ------------------------- // Title 1 Title 2 // ========================= // Value 1 Value 2 // ------------------------- // Value three Value four // ------------------------- println!("FORMAT_NO_COLSEP :"); table.set_format(*format::consts::FORMAT_NO_COLSEP); table.printstd(); println!(""); // Print // +-------------------------+ // | Title 1 Title 2 | // +=========================+ // | Value 1 Value 2 | // | Value three Value four | // +-------------------------+ println!("FORMAT_BORDERS_ONLY :"); table.set_format(*format::consts::FORMAT_BORDERS_ONLY); table.printstd(); println!(""); // Custom format can be implemented using `prettytable::format::FormatBuilder` // Example to print // +-------------+------------+ // | Title 1 | Title 2 | // | Value 1 | Value 2 | // | Value three | Value four | // +-------------+------------+ println!("Custom :"); table.set_format( format::FormatBuilder::new() .column_separator('|') .borders('|') .separators( &[format::LinePosition::Top, format::LinePosition::Bottom], format::LineSeparator::new('-', '+', '+', '+'), ) .padding(1, 1) .build(), ); table.printstd(); // Customized format with unicode // Example to print // ┌─────────────┬────────────┐ // │ Title 1 │ Title 2 │ // ├─────────────┼────────────┤ // │ Value 1 │ Value 2 │ // ├─────────────┼────────────┤ // │ Value three │ Value four │ // └─────────────┴────────────┘ println!("With unicode:"); table.set_format( format::FormatBuilder::new() .column_separator('│') .borders('│') .separators( &[format::LinePosition::Top], format::LineSeparator::new('─', '┬', '┌', '┐'), ) .separators( &[format::LinePosition::Intern], format::LineSeparator::new('─', '┼', '├', '┤'), ) .separators( &[format::LinePosition::Bottom], format::LineSeparator::new('─', '┴', '└', '┘'), ) .padding(1, 1) .build(), ); table.printstd(); // Customized format with unicode and different padding // Example to print // ┌───────────────┬──────────────┐ // │ Title 1 │ Title 2 │ // ├───────────────┼──────────────┤ // │ Value 1 │ Value 2 │ // ├───────────────┼──────────────┤ // │ Value three │ Value four │ // └───────────────┴──────────────┘ // Change individual format settings println!("With unicode and padding:"); table.get_format().padding(2, 2); table.printstd(); } prettytable-rs-0.10.0/examples/multiline.rs000064400000000000000000000025351046102023000170560ustar 00000000000000use prettytable::table; /* Following main function will print : +-------------------------+------------------------------+ | Title 1 | Title 2 | +-------------------------+------------------------------+ | This is | foo | | a multiline | | | cell | | +-------------------------+------------------------------+ | Yo dawg ;) You can even | +---------+------+---------+ | | print tables | | ABC | DEFG | HIJKLMN | | | into tables | +---------+------+---------+ | | | | foobar | bar | foo | | | | +---------+------+---------+ | | | | foobar2 | bar2 | foo2 | | | | +---------+------+---------+ | +-------------------------+------------------------------+ */ fn main() { let table1 = table!( ["ABC", "DEFG", "HIJKLMN"], ["foobar", "bar", "foo"], ["foobar2", "bar2", "foo2"] ); let table2 = table!( ["Title 1", "Title 2"], ["This is\na multiline\ncell", "foo"], ["Yo dawg ;) You can even\nprint tables\ninto tables", table1] ); table2.printstd(); } prettytable-rs-0.10.0/examples/slices.rs000064400000000000000000000013151046102023000163310ustar 00000000000000use prettytable::{row, table, Slice}; fn main() { let mut table = table![ [0, 0, 0], [1, 1, 1], [2, 2, 2], [3, 3, 3], [4, 4, 4], [5, 5, 5] ]; table.set_titles(row!["t1", "t2", "t3"]); let slice = table.slice(..); let slice = slice.slice(2..); let slice = slice.slice(..3); /* Will print +----+----+----+ | t1 | t2 | t3 | +====+====+====+ | 2 | 2 | 2 | +----+----+----+ | 3 | 3 | 3 | +----+----+----+ | 4 | 4 | 4 | +----+----+----+ */ slice.printstd(); // This is equivalent to let slice = table.slice(2..5); slice.printstd(); } prettytable-rs-0.10.0/examples/span.rs000064400000000000000000000017761046102023000160230ustar 00000000000000use prettytable::{format::Alignment, table, Cell, Row}; fn main() { /* The following code will output +---------------+---------------+--------------+ | A table with horizontal span | +===============+===============+==============+ | This is a cell with span of 2 | span of 1 | +---------------+---------------+--------------+ | span of 1 | span of 1 | span of 1 | +---------------+---------------+--------------+ | This cell with a span of 3 is centered | +---------------+---------------+--------------+ */ let mut table: prettytable::Table = table![ [H2 -> "This is a cell with span of 2", "span of 1"], ["span of 1", "span of 1", "span of 1"], [H03c -> "This cell with a span of 3 is centered"] ]; table.set_titles(Row::new(vec![Cell::new_align( "A table with horizontal span", Alignment::CENTER, ) .with_hspan(3)])); table.printstd(); } prettytable-rs-0.10.0/examples/style.rs000064400000000000000000000024071046102023000162120ustar 00000000000000use prettytable::{cell, ptable, row, table}; use prettytable::{color, Attr}; use prettytable::{Cell, Row, Table}; #[allow(dead_code)] fn main() { let _ = table!(); let mut table = Table::new(); // Add style to a cell table.add_row(row![FrByb->"ABC", "DEFG", "HIJKLMN"]); // Add style to a full row table.add_row(row![FY => "styled", "bar", "foo"]); table.add_row(Row::new(vec![ Cell::new("foobar2"), // Create a cell with a red foreground color Cell::new("bar2").with_style(Attr::ForegroundColor(color::RED)), // Create a cell with red foreground color, yellow background color, with bold characters Cell::new("foo2").style_spec("FrByb"), // Using the cell! macro cell!(Fr->"red"), ])); table.printstd(); // Print a table with some styles on it : // FrBybl means : Foregound red, Background yellow, bold, left align ptable!([FrBybl->"A", "B", FrBybr->"C"], [123, 234, 345, 456], [Fg => 1, 2, 3]); // You can also apply style to full rows : let mut table = table!([Frb => "A", "B", "C"], [1, 2, 3, 4], ["A\nBCCZZZ\nDDD", 2, table]); // Set a title line, with all text centered in the cell table.set_titles(row![c => "Title 1", "Title 2"]); table.printstd(); } prettytable-rs-0.10.0/examples/tictactoe.rs000064400000000000000000000056571046102023000170430ustar 00000000000000use prettytable::{cell, table, Table}; use std::io; use std::io::Write; use std::str::FromStr; const CROSS: &'static str = "X"; const EMPTY: &'static str = " "; const ROUND: &'static str = "O"; fn main() { let mut table = table![ [EMPTY, EMPTY, EMPTY], [EMPTY, EMPTY, EMPTY], [EMPTY, EMPTY, EMPTY] ]; let mut height = table.print_tty(false).unwrap(); let stdin = io::stdin(); let mut stdout = io::stdout(); let mut current = CROSS; let mut terminal = term::stdout().unwrap(); loop { let mut line = String::new(); print!("{} plays > ", current); height += 1; stdout.flush().unwrap(); stdin.read_line(&mut line).expect("Cannot read input"); let i = match usize::from_str(line.trim()) { Ok(i) => i, _ => { println!("Bad input"); height += 1; continue; } }; if i < 1 || i > 9 { println!("Bad input, should be between 1 and 9"); height += 1; continue; } let x = (i - 1) % 3; let y = (i - 1) / 3; { let row = table.get_mut_row(y).unwrap(); if row.get_cell(x).unwrap().to_string() != EMPTY { println!("There's already someone there"); height += 1; continue; } row.set_cell(cell!(current), x).unwrap(); } for _ in 0..height { terminal.cursor_up().unwrap(); terminal.delete_line().unwrap(); } height = table.print_tty(false).unwrap(); if check(&table) { return; } if current == CROSS { current = ROUND; } else { current = CROSS; } } } fn get(table: &Table, x: usize, y: usize) -> String { match table.get_row(y) { Some(r) => match r.get_cell(x) { Some(c) => c.to_string(), _ => EMPTY.to_string(), }, _ => EMPTY.to_string(), } } fn is(table: &Table, s: &str, x: usize, y: usize) -> bool { get(table, x, y).as_str() == s } fn check(table: &Table) -> bool { let mut full = true; for y in 0..3 { for x in 0..3 { if is(table, EMPTY, x, y) { full = false; continue; } let current = get(table, x, y); let c = current.as_str(); if is(table, c, x + 1, y) && is(table, c, x + 2, y) || is(table, c, x + 1, y + 1) && is(table, c, x + 2, y + 2) || x >= 2 && is(table, c, x - 1, y + 1) && is(table, c, x - 2, y + 2) || is(table, c, x, y + 1) && is(table, c, x, y + 2) { println!("Game is over. {} is the winner", current); return true; } } } if full { println!("Game is over. It's a draw"); } full } prettytable-rs-0.10.0/src/cell.rs000064400000000000000000000425671046102023000147550ustar 00000000000000//! This module contains definition of table/row cells stuff use super::format::Alignment; use super::utils::{display_width, print_align, HtmlEscape}; use super::{color, Attr, Terminal}; use std::io::{Error, Write}; use std::str::FromStr; use std::string::ToString; /// Represent a table cell containing a string. /// /// Once created, a cell's content cannot be modified. /// The cell would have to be replaced by another one #[derive(Clone, Debug, Hash, PartialEq, Eq)] pub struct Cell { content: Vec, width: usize, align: Alignment, style: Vec, hspan: usize, } impl Cell { /// Create a new `Cell` initialized with content from `string`. /// Text alignment in cell is configurable with the `align` argument pub fn new_align(string: &str, align: Alignment) -> Cell { let content: Vec = string.lines().map(|x| x.to_string()).collect(); let mut width = 0; for cont in &content { let l = display_width(&cont[..]); if l > width { width = l; } } Cell { content, width, align, style: Vec::new(), hspan: 1, } } /// Create a new `Cell` initialized with content from `string`. /// By default, content is align to `LEFT` pub fn new(string: &str) -> Cell { Cell::new_align(string, Alignment::LEFT) } /// Set text alignment in the cell pub fn align(&mut self, align: Alignment) { self.align = align; } /// Add a style attribute to the cell pub fn style(&mut self, attr: Attr) { self.style.push(attr); } /// Add a style attribute to the cell. Can be chained pub fn with_style(mut self, attr: Attr) -> Cell { self.style(attr); self } /// Add horizontal spanning to the cell pub fn with_hspan(mut self, hspan: usize) -> Cell { self.set_hspan(hspan); self } /// Remove all style attributes and reset alignment to default (LEFT) pub fn reset_style(&mut self) { self.style.clear(); self.align(Alignment::LEFT); } /// Set the cell's style by applying the given specifier string /// /// # Style spec syntax /// /// The syntax for the style specifier looks like this : /// **FrBybl** which means **F**oreground **r**ed **B**ackground **y**ellow **b**old **l**eft /// /// ### List of supported specifiers : /// /// * **F** : **F**oreground (must be followed by a color specifier) /// * **B** : **B**ackground (must be followed by a color specifier) /// * **H** : **H**orizontal span (must be followed by a number) /// * **b** : **b**old /// * **i** : **i**talic /// * **u** : **u**nderline /// * **c** : Align **c**enter /// * **l** : Align **l**eft /// * **r** : Align **r**ight /// * **d** : **d**efault style /// /// ### List of color specifiers : /// /// * **r** : Red /// * **b** : Blue /// * **g** : Green /// * **y** : Yellow /// * **c** : Cyan /// * **m** : Magenta /// * **w** : White /// * **d** : Black /// /// And capital letters are for **bright** colors. /// Eg : /// /// * **R** : Bright Red /// * **B** : Bright Blue /// * ... and so on ... pub fn style_spec(mut self, spec: &str) -> Cell { self.reset_style(); let mut foreground = false; let mut background = false; let mut it = spec.chars().peekable(); while let Some(c) = it.next() { if foreground || background { let color = match c { 'r' => color::RED, 'R' => color::BRIGHT_RED, 'b' => color::BLUE, 'B' => color::BRIGHT_BLUE, 'g' => color::GREEN, 'G' => color::BRIGHT_GREEN, 'y' => color::YELLOW, 'Y' => color::BRIGHT_YELLOW, 'c' => color::CYAN, 'C' => color::BRIGHT_CYAN, 'm' => color::MAGENTA, 'M' => color::BRIGHT_MAGENTA, 'w' => color::WHITE, 'W' => color::BRIGHT_WHITE, 'd' => color::BLACK, 'D' => color::BRIGHT_BLACK, _ => { // Silently ignore unknown tags foreground = false; background = false; continue; } }; if foreground { self.style(Attr::ForegroundColor(color)); } else if background { self.style(Attr::BackgroundColor(color)); } foreground = false; background = false; } else { match c { 'F' => foreground = true, 'B' => background = true, 'b' => self.style(Attr::Bold), 'i' => self.style(Attr::Italic(true)), 'u' => self.style(Attr::Underline(true)), 'c' => self.align(Alignment::CENTER), 'l' => self.align(Alignment::LEFT), 'r' => self.align(Alignment::RIGHT), 'H' => { let mut span_s = String::new(); while let Some('0'..='9') = it.peek() { span_s.push(it.next().unwrap()); } let span = usize::from_str(&span_s).unwrap(); self.set_hspan(span); } _ => { /* Silently ignore unknown tags */ } } } } self } /// Return the height of the cell // #[deprecated(since="0.8.0", note="Will become private in future release. See [issue #87](https://github.com/phsym/prettytable-rs/issues/87)")] pub(crate) fn get_height(&self) -> usize { self.content.len() } /// Return the width of the cell // #[deprecated(since="0.8.0", note="Will become private in future release. See [issue #87](https://github.com/phsym/prettytable-rs/issues/87)")] pub(crate) fn get_width(&self) -> usize { self.width } /// Set horizontal span for this cell (must be > 0) pub fn set_hspan(&mut self, hspan: usize) { self.hspan = if hspan == 0 { 1 } else { hspan }; } /// Get horizontal span of this cell (> 0) pub fn get_hspan(&self) -> usize { self.hspan } /// Return a copy of the full string contained in the cell pub fn get_content(&self) -> String { self.content.join("\n") } /// Print a partial cell to `out`. Since the cell may be multi-lined, /// `idx` is the line index to print. `col_width` is the column width used to /// fill the cells with blanks so it fits in the table. /// If `ìdx` is higher than this cell's height, it will print empty content // #[deprecated(since="0.8.0", note="Will become private in future release. See [issue #87](https://github.com/phsym/prettytable-rs/issues/87)")] pub(crate) fn print( &self, out: &mut T, idx: usize, col_width: usize, skip_right_fill: bool, ) -> Result<(), Error> { let c = self.content.get(idx).map(|s| s.as_ref()).unwrap_or(""); print_align(out, self.align, c, ' ', col_width, skip_right_fill) } /// Apply style then call `print` to print the cell into a terminal // #[deprecated(since="0.8.0", note="Will become private in future release. See [issue #87](https://github.com/phsym/prettytable-rs/issues/87)")] pub(crate) fn print_term( &self, out: &mut T, idx: usize, col_width: usize, skip_right_fill: bool, ) -> Result<(), Error> { for a in &self.style { match out.attr(*a) { Ok(..) | Err(::term::Error::NotSupported) | Err(::term::Error::ColorOutOfRange) => { } // Ignore unsupported attributes Err(e) => return Err(term_error_to_io_error(e)), }; } self.print(out, idx, col_width, skip_right_fill)?; match out.reset() { Ok(..) | Err(::term::Error::NotSupported) | Err(::term::Error::ColorOutOfRange) => { Ok(()) } Err(e) => Err(term_error_to_io_error(e)), } } /// Print the cell in HTML format to `out`. pub fn print_html(&self, out: &mut T) -> Result { /// Convert the color to a hex value useful in CSS fn color2hex(color: color::Color) -> &'static str { match color { color::BLACK => "#000000", color::RED => "#aa0000", color::GREEN => "#00aa00", color::YELLOW => "#aa5500", color::BLUE => "#0000aa", color::MAGENTA => "#aa00aa", color::CYAN => "#00aaaa", color::WHITE => "#aaaaaa", color::BRIGHT_BLACK => "#555555", color::BRIGHT_RED => "#ff5555", color::BRIGHT_GREEN => "#55ff55", color::BRIGHT_YELLOW => "#ffff55", color::BRIGHT_BLUE => "#5555ff", color::BRIGHT_MAGENTA => "#ff55ff", color::BRIGHT_CYAN => "#55ffff", color::BRIGHT_WHITE => "#ffffff", // Unknown colors, fallback to blakc _ => "#000000", } } let colspan = if self.hspan > 1 { format!(" colspan=\"{}\"", self.hspan) } else { String::new() }; // Process style properties like color let mut styles = String::new(); for style in &self.style { match style { Attr::Bold => styles += "font-weight: bold;", Attr::Italic(true) => styles += "font-style: italic;", Attr::Underline(true) => styles += "text-decoration: underline;", Attr::ForegroundColor(c) => { styles += "color: "; styles += color2hex(*c); styles += ";"; } Attr::BackgroundColor(c) => { styles += "background-color: "; styles += color2hex(*c); styles += ";"; } _ => {} } } // Process alignment match self.align { Alignment::LEFT => styles += "text-align: left;", Alignment::CENTER => styles += "text-align: center;", Alignment::RIGHT => styles += "text-align: right;", } let content = self.content.join("
"); out.write_all( format!( "{0}", HtmlEscape(&content), colspan, styles ) .as_bytes(), )?; Ok(self.hspan) } } fn term_error_to_io_error(te: ::term::Error) -> Error { match te { ::term::Error::Io(why) => why, _ => Error::new(::std::io::ErrorKind::Other, te), } } impl<'a, T: ToString> From<&'a T> for Cell { fn from(f: &T) -> Cell { Cell::new(&f.to_string()) } } impl ToString for Cell { fn to_string(&self) -> String { self.get_content() } } impl Default for Cell { /// Return a cell initialized with a single empty `String`, with LEFT alignment fn default() -> Cell { Cell { content: vec!["".to_string(); 1], width: 0, align: Alignment::LEFT, style: Vec::new(), hspan: 1, } } } /// This macro simplifies `Cell` creation /// /// Support 2 syntax : With and without style specification. /// # Syntax /// ```text /// cell!(value); /// ``` /// or /// /// ```text /// cell!(spec->value); /// ``` /// Value must implement the `std::string::ToString` trait /// /// For details about style specifier syntax, check doc for [`Cell::style_spec`](cell/struct.Cell.html#method.style_spec) method /// # Example /// ``` /// # #[macro_use] extern crate prettytable; /// # fn main() { /// let cell = cell!("value"); /// // Do something with the cell /// # drop(cell); /// // Create a cell with style (Red foreground, Bold, aligned to left); /// let styled = cell!(Frbl->"value"); /// # drop(styled); /// # } /// ``` #[macro_export] macro_rules! cell { () => { $crate::Cell::default() }; ($value:expr) => { $crate::Cell::new(&$value.to_string()) }; ($style:ident -> $value:expr) => { $crate::cell!($value).style_spec(stringify!($style)) }; } #[cfg(test)] mod tests { use super::Cell; use crate::format::Alignment; use crate::utils::StringWriter; use term::{color, Attr}; #[test] fn get_content() { let cell = Cell::new("test"); assert_eq!(cell.get_content(), "test"); } #[test] fn print_ascii() { let ascii_cell = Cell::new("hello"); assert_eq!(ascii_cell.get_width(), 5); let mut out = StringWriter::new(); let _ = ascii_cell.print(&mut out, 0, 10, false); assert_eq!(out.as_string(), "hello "); } #[test] fn print_unicode() { let unicode_cell = Cell::new("привет"); assert_eq!(unicode_cell.get_width(), 6); let mut out = StringWriter::new(); let _ = unicode_cell.print(&mut out, 0, 10, false); assert_eq!(out.as_string(), "привет "); } #[test] fn print_cjk() { let unicode_cell = Cell::new("由系统自动更新"); assert_eq!(unicode_cell.get_width(), 14); let mut out = StringWriter::new(); let _ = unicode_cell.print(&mut out, 0, 20, false); assert_eq!(out.as_string(), "由系统自动更新 "); } #[test] fn print_ascii_html() { let ascii_cell = Cell::new("hello"); assert_eq!(ascii_cell.get_width(), 5); let mut out = StringWriter::new(); let _ = ascii_cell.print_html(&mut out); assert_eq!( out.as_string(), r#"hello"# ); } #[test] fn print_html_special_chars() { let ascii_cell = Cell::new("&'"); let mut out = StringWriter::new(); let _ = ascii_cell.print_html(&mut out); assert_eq!( out.as_string(), r#"<abc">&'"# ); } #[test] fn align_left() { let cell = Cell::new_align("test", Alignment::LEFT); let mut out = StringWriter::new(); let _ = cell.print(&mut out, 0, 10, false); assert_eq!(out.as_string(), "test "); } #[test] fn align_center() { let cell = Cell::new_align("test", Alignment::CENTER); let mut out = StringWriter::new(); let _ = cell.print(&mut out, 0, 10, false); assert_eq!(out.as_string(), " test "); } #[test] fn align_right() { let cell = Cell::new_align("test", Alignment::RIGHT); let mut out = StringWriter::new(); let _ = cell.print(&mut out, 0, 10, false); assert_eq!(out.as_string(), " test"); } #[test] fn style_spec() { let mut cell = Cell::new("test").style_spec("FrBBbuic"); assert_eq!(cell.style.len(), 5); assert!(cell.style.contains(&Attr::Underline(true))); assert!(cell.style.contains(&Attr::Italic(true))); assert!(cell.style.contains(&Attr::Bold)); assert!(cell.style.contains(&Attr::ForegroundColor(color::RED))); assert!(cell .style .contains(&Attr::BackgroundColor(color::BRIGHT_BLUE))); assert_eq!(cell.align, Alignment::CENTER); cell = cell.style_spec("FDBwr"); assert_eq!(cell.style.len(), 2); assert!(cell .style .contains(&Attr::ForegroundColor(color::BRIGHT_BLACK))); assert!(cell.style.contains(&Attr::BackgroundColor(color::WHITE))); assert_eq!(cell.align, Alignment::RIGHT); // Test with invalid sepcifier chars cell = cell.clone(); cell = cell.style_spec("FzBr"); assert!(cell.style.contains(&Attr::BackgroundColor(color::RED))); assert_eq!(cell.style.len(), 1); cell = cell.style_spec("zzz"); assert!(cell.style.is_empty()); assert_eq!(cell.get_hspan(), 1); cell = cell.style_spec("FDBwH03r"); assert_eq!(cell.get_hspan(), 3); } #[test] fn reset_style() { let mut cell = Cell::new("test") .with_style(Attr::ForegroundColor(color::BRIGHT_BLACK)) .with_style(Attr::BackgroundColor(color::WHITE)); cell.align(Alignment::RIGHT); //style_spec("FDBwr"); assert_eq!(cell.style.len(), 2); assert_eq!(cell.align, Alignment::RIGHT); cell.reset_style(); assert_eq!(cell.style.len(), 0); assert_eq!(cell.align, Alignment::LEFT); } #[test] fn default_empty_cell() { let cell = Cell::default(); assert_eq!(cell.align, Alignment::LEFT); assert!(cell.style.is_empty()); assert_eq!(cell.get_content(), ""); assert_eq!(cell.to_string(), ""); assert_eq!(cell.get_height(), 1); assert_eq!(cell.get_width(), 0); } } prettytable-rs-0.10.0/src/csv.rs000064400000000000000000000112521046102023000146140ustar 00000000000000//! CSV impl and reexported types use csv; pub use self::csv::{Reader, ReaderBuilder, Result, Writer}; use crate::AsTableSlice; use std::io::{Read, Write}; use std::path::Path; impl<'a> super::TableSlice<'a> { /// Write the table to the specified writer. pub fn to_csv(&self, w: W) -> Result> { self.to_csv_writer(Writer::from_writer(w)) } /// Write the table to the specified writer. /// /// This allows for format customisation. pub fn to_csv_writer(&self, mut writer: Writer) -> Result> { for title in self.titles { writer.write_record(title.iter().map(|c| c.get_content()))?; } for row in self.rows { writer.write_record(row.iter().map(|c| c.get_content()))?; } writer.flush()?; Ok(writer) } } impl super::Table { /// Create a table from a CSV string /// /// For more customisability use `from_csv()` pub fn from_csv_string(csv_s: &str) -> Result { Ok(Self::from_csv( &mut ReaderBuilder::new() .has_headers(false) .from_reader(csv_s.as_bytes()), )) } /// Create a table from a CSV file /// /// For more customisability use `from_csv()` pub fn from_csv_file>(csv_p: P) -> Result { Ok(Self::from_csv( &mut ReaderBuilder::new().has_headers(false).from_path(csv_p)?, )) } /// Create a table from a CSV reader pub fn from_csv(reader: &mut Reader) -> Self { Self::init( reader .records() .map(|row| { super::Row::new( row.unwrap() .into_iter() .map(super::Cell::new) .collect(), ) }) .collect(), ) } /// Write the table to the specified writer. pub fn to_csv(&self, w: W) -> Result> { self.as_slice().to_csv(w) } /// Write the table to the specified writer. /// /// This allows for format customisation. pub fn to_csv_writer(&self, writer: Writer) -> Result> { self.as_slice().to_csv_writer(writer) } } #[cfg(test)] mod tests { use crate::{Cell, Row, Table}; static CSV_S: &str = "ABC,DEFG,HIJKLMN\n\ foobar,bar,foo\n\ foobar2,bar2,foo2\n"; fn test_table() -> Table { let mut table = Table::new(); table.add_row(Row::new(vec![ Cell::new("ABC"), Cell::new("DEFG"), Cell::new("HIJKLMN"), ])); table.add_row(Row::new(vec![ Cell::new("foobar"), Cell::new("bar"), Cell::new("foo"), ])); table.add_row(Row::new(vec![ Cell::new("foobar2"), Cell::new("bar2"), Cell::new("foo2"), ])); table } #[test] fn from() { assert_eq!( test_table().to_string().replace("\r\n", "\n"), Table::from_csv_string(CSV_S) .unwrap() .to_string() .replace("\r\n", "\n") ); } #[test] fn to() { assert_eq!( String::from_utf8( test_table() .to_csv(Vec::new()) .unwrap() .into_inner() .unwrap() ) .unwrap(), CSV_S ); } #[test] fn trans() { assert_eq!( Table::from_csv_string( &String::from_utf8( test_table() .to_csv(Vec::new()) .unwrap() .into_inner() .unwrap() ) .unwrap() ) .unwrap() .to_string() .replace("\r\n", "\n"), test_table().to_string().replace("\r\n", "\n") ); } #[test] fn extend_table() { let mut table = Table::new(); table.add_row(Row::new(vec![ Cell::new("ABC"), Cell::new("DEFG"), Cell::new("HIJKLMN"), ])); table.extend(vec![vec!["A", "B", "C"]]); let t2 = table.clone(); table.extend(t2.rows); assert_eq!( table.get_row(1).unwrap().get_cell(2).unwrap().get_content(), "C" ); assert_eq!( table.get_row(2).unwrap().get_cell(1).unwrap().get_content(), "DEFG" ); } } prettytable-rs-0.10.0/src/evcxr.rs000064400000000000000000000016001046102023000151440ustar 00000000000000//! This modules contains traits and implementations to work within Evcxr use super::utils::StringWriter; use super::AsTableSlice; use std::io::Write; /// Evcxr specific output trait pub trait EvcxrDisplay { /// Print self in one or multiple Evcxr compatile types. fn evcxr_display(&self); } impl EvcxrDisplay for T where T: AsTableSlice, { fn evcxr_display(&self) { let mut writer = StringWriter::new(); // Plain Text let _ = writer.write_all(b"EVCXR_BEGIN_CONTENT text/plain\n"); let _ = self.as_slice().print(&mut writer); let _ = writer.write_all(b"\nEVCXR_END_CONTENT\n"); // Html let _ = writer.write_all(b"EVCXR_BEGIN_CONTENT text/html\n"); let _ = self.as_slice().print_html(&mut writer); let _ = writer.write_all(b"\nEVCXR_END_CONTENT\n"); println!("{}", writer.as_string()); } } prettytable-rs-0.10.0/src/format.rs000064400000000000000000000502001046102023000153050ustar 00000000000000//! Define table formatting utilities use std::io::{Error, Write}; use encode_unicode::Utf8Char; use super::utils::NEWLINE; /// Alignment for cell's content #[derive(Clone, Debug, PartialEq, Copy, Hash, Eq)] pub enum Alignment { /// Align left LEFT, /// Align in the center CENTER, /// Align right RIGHT, } /// Position of a line separator in a table #[derive(Clone, Debug, PartialEq, Copy, Hash, Eq)] pub enum LinePosition { /// Table's border on top Top, /// Line separator between the titles row, /// and the first data row Title, /// Line separator between data rows Intern, /// Bottom table's border Bottom, } /// Position of a column separator in a row #[derive(Clone, Debug, PartialEq, Copy, Hash, Eq)] pub enum ColumnPosition { /// Left table's border Left, /// Internal column separators Intern, /// Rigth table's border Right, } /// Contains the character used for printing a line separator #[derive(Clone, Debug, Copy, Hash, PartialEq, Eq)] pub struct LineSeparator { /// Line separator line: char, /// Internal junction separator junc: char, /// Left junction separator ljunc: char, /// Right junction separator rjunc: char, } impl LineSeparator { /// Create a new line separator instance where `line` is the character used to separate 2 lines /// and `junc` is the one used for junctions between columns and lines pub fn new(line: char, junc: char, ljunc: char, rjunc: char) -> LineSeparator { LineSeparator { line, junc, ljunc, rjunc, } } /// Print a full line separator to `out`. `col_width` is a slice containing the width of each column. /// Returns the number of printed lines fn print( &self, out: &mut T, col_width: &[usize], padding: (usize, usize), colsep: bool, lborder: bool, rborder: bool, ) -> Result { if lborder { out.write_all(Utf8Char::from(self.ljunc).as_bytes())?; } let mut iter = col_width.iter().peekable(); while let Some(width) = iter.next() { for _ in 0..width + padding.0 + padding.1 { out.write_all(Utf8Char::from(self.line).as_bytes())?; } if colsep && iter.peek().is_some() { out.write_all(Utf8Char::from(self.junc).as_bytes())?; } } if rborder { out.write_all(Utf8Char::from(self.rjunc).as_bytes())?; } out.write_all(NEWLINE)?; Ok(1) } } impl Default for LineSeparator { fn default() -> Self { LineSeparator::new('-', '+', '+', '+') } } /// Contains the table formatting rules #[derive(Clone, Debug, Copy, Hash, PartialEq, Eq)] pub struct TableFormat { /// Optional column separator character csep: Option, /// Optional left border character lborder: Option, /// Optional right border character rborder: Option, /// Optional internal line separator lsep: Option, /// Optional title line separator tsep: Option, /// Optional top line separator top_sep: Option, /// Optional bottom line separator bottom_sep: Option, /// Left padding pad_left: usize, /// Right padding pad_right: usize, /// Global indentation when rendering the table indent: usize, } impl TableFormat { /// Create a new empty TableFormat. pub fn new() -> TableFormat { TableFormat { csep: None, lborder: None, rborder: None, lsep: None, tsep: None, top_sep: None, bottom_sep: None, pad_left: 0, pad_right: 0, indent: 0, } } /// Return a tuple with left and right padding pub fn get_padding(&self) -> (usize, usize) { (self.pad_left, self.pad_right) } /// Set left and right padding pub fn padding(&mut self, left: usize, right: usize) { self.pad_left = left; self.pad_right = right; } /// Set the character used for internal column separation pub fn column_separator(&mut self, separator: char) { self.csep = Some(separator); } /// Set the character used for table borders pub fn borders(&mut self, border: char) { self.lborder = Some(border); self.rborder = Some(border); } /// Set the character used for left table border pub fn left_border(&mut self, border: char) { self.lborder = Some(border); } /// Set the character used for right table border pub fn right_border(&mut self, border: char) { self.rborder = Some(border); } /// Set a line separator pub fn separator(&mut self, what: LinePosition, separator: LineSeparator) { *match what { LinePosition::Top => &mut self.top_sep, LinePosition::Bottom => &mut self.bottom_sep, LinePosition::Title => &mut self.tsep, LinePosition::Intern => &mut self.lsep, } = Some(separator); } /// Set format for multiple kind of line separator pub fn separators(&mut self, what: &[LinePosition], separator: LineSeparator) { for pos in what { self.separator(*pos, separator); } } fn get_sep_for_line(&self, pos: LinePosition) -> &Option { match pos { LinePosition::Intern => &self.lsep, LinePosition::Top => &self.top_sep, LinePosition::Bottom => &self.bottom_sep, LinePosition::Title => match &self.tsep { s @ &Some(_) => s, &None => &self.lsep, }, } } /// Set global indentation in spaces used when rendering a table pub fn indent(&mut self, spaces: usize) { self.indent = spaces; } /// Get global indentation in spaces used when rendering a table pub fn get_indent(&self) -> usize { self.indent } /// Print a full line separator to `out`. `col_width` is a slice containing the width of each column. /// Returns the number of printed lines // #[deprecated(since="0.8.0", note="Will become private in future release. See [issue #87](https://github.com/phsym/prettytable-rs/issues/87)")] pub(crate) fn print_line_separator( &self, out: &mut T, col_width: &[usize], pos: LinePosition, ) -> Result { match *self.get_sep_for_line(pos) { Some(ref l) => { //TODO: Wrap this into dedicated function one day out.write_all(&vec![b' '; self.get_indent()])?; l.print( out, col_width, self.get_padding(), self.csep.is_some(), self.lborder.is_some(), self.rborder.is_some(), ) } None => Ok(0), } } /// Returns the character used to separate columns. /// `pos` specify if the separator is left/right final or internal to the table pub fn get_column_separator(&self, pos: ColumnPosition) -> Option { match pos { ColumnPosition::Left => self.lborder, ColumnPosition::Intern => self.csep, ColumnPosition::Right => self.rborder, } } /// Print a column separator or a table border // #[deprecated(since="0.8.0", note="Will become private in future release. See [issue #87](https://github.com/phsym/prettytable-rs/issues/87)")] pub(crate) fn print_column_separator( &self, out: &mut T, pos: ColumnPosition, ) -> Result<(), Error> { match self.get_column_separator(pos) { Some(s) => out.write_all(Utf8Char::from(s).as_bytes()), None => Ok(()), } } } impl Default for TableFormat { fn default() -> Self { TableFormat::new() } } /// A builder to create a `TableFormat` #[derive(Default)] pub struct FormatBuilder { format: Box, } impl FormatBuilder { /// Creates a new builder pub fn new() -> FormatBuilder { FormatBuilder { format: Box::new(TableFormat::new()), } } /// Set left and right padding pub fn padding(mut self, left: usize, right: usize) -> Self { self.format.padding(left, right); self } /// Set the character used for internal column separation pub fn column_separator(mut self, separator: char) -> Self { self.format.column_separator(separator); self } /// Set the character used for table borders pub fn borders(mut self, border: char) -> Self { self.format.borders(border); self } /// Set the character used for left table border pub fn left_border(mut self, border: char) -> Self { self.format.left_border(border); self } /// Set the character used for right table border pub fn right_border(mut self, border: char) -> Self { self.format.right_border(border); self } /// Set a line separator format pub fn separator(mut self, what: LinePosition, separator: LineSeparator) -> Self { self.format.separator(what, separator); self } /// Set separator format for multiple kind of line separators pub fn separators(mut self, what: &[LinePosition], separator: LineSeparator) -> Self { self.format.separators(what, separator); self } /// Set global indentation in spaces used when rendering a table pub fn indent(mut self, spaces: usize) -> Self { self.format.indent(spaces); self } /// Return the generated `TableFormat` pub fn build(&self) -> TableFormat { *self.format } } impl From for FormatBuilder { fn from(fmt: TableFormat) -> Self { FormatBuilder { format: Box::new(fmt), } } } /// Predifined formats. Those constants are lazily evaluated when /// the corresponding struct is dereferenced pub mod consts { use super::{FormatBuilder, LinePosition, LineSeparator, TableFormat}; lazy_static! { /// A line separator made of `-` and `+` static ref MINUS_PLUS_SEP: LineSeparator = LineSeparator::new('-', '+', '+', '+'); /// A line separator made of `=` and `+` static ref EQU_PLUS_SEP: LineSeparator = LineSeparator::new('=', '+', '+', '+'); /// Default table format /// /// # Example /// ```text /// +----+----+ /// | T1 | T2 | /// +====+====+ /// | a | b | /// +----+----+ /// | d | c | /// +----+----+ /// ``` pub static ref FORMAT_DEFAULT: TableFormat = FormatBuilder::new() .column_separator('|') .borders('|') .separator(LinePosition::Intern, *MINUS_PLUS_SEP) .separator(LinePosition::Title, *EQU_PLUS_SEP) .separator(LinePosition::Bottom, *MINUS_PLUS_SEP) .separator(LinePosition::Top, *MINUS_PLUS_SEP) .padding(1, 1) .build(); /// Similar to `FORMAT_DEFAULT` but without special separator after title line /// /// # Example /// ```text /// +----+----+ /// | T1 | T2 | /// +----+----+ /// | a | b | /// +----+----+ /// | c | d | /// +----+----+ /// ``` pub static ref FORMAT_NO_TITLE: TableFormat = FormatBuilder::new() .column_separator('|') .borders('|') .separator(LinePosition::Intern, *MINUS_PLUS_SEP) .separator(LinePosition::Title, *MINUS_PLUS_SEP) .separator(LinePosition::Bottom, *MINUS_PLUS_SEP) .separator(LinePosition::Top, *MINUS_PLUS_SEP) .padding(1, 1) .build(); /// With no line separator, but with title separator /// /// # Example /// ```text /// +----+----+ /// | T1 | T2 | /// +----+----+ /// | a | b | /// | c | d | /// +----+----+ /// ``` pub static ref FORMAT_NO_LINESEP_WITH_TITLE: TableFormat = FormatBuilder::new() .column_separator('|') .borders('|') .separator(LinePosition::Title, *MINUS_PLUS_SEP) .separator(LinePosition::Bottom, *MINUS_PLUS_SEP) .separator(LinePosition::Top, *MINUS_PLUS_SEP) .padding(1, 1) .build(); /// With no line or title separator /// /// # Example /// ```text /// +----+----+ /// | T1 | T2 | /// | a | b | /// | c | d | /// +----+----+ /// ``` pub static ref FORMAT_NO_LINESEP: TableFormat = FormatBuilder::new() .column_separator('|') .borders('|') .separator(LinePosition::Bottom, *MINUS_PLUS_SEP) .separator(LinePosition::Top, *MINUS_PLUS_SEP) .padding(1, 1) .build(); /// No column separator /// /// # Example /// ```text /// -------- /// T1 T2 /// ======== /// a b /// -------- /// d c /// -------- /// ``` pub static ref FORMAT_NO_COLSEP: TableFormat = FormatBuilder::new() .separator(LinePosition::Intern, *MINUS_PLUS_SEP) .separator(LinePosition::Title, *EQU_PLUS_SEP) .separator(LinePosition::Bottom, *MINUS_PLUS_SEP) .separator(LinePosition::Top, *MINUS_PLUS_SEP) .padding(1, 1) .build(); /// Format for printing a table without any separators (only alignment) /// /// # Example /// ```text /// T1 T2 /// a b /// d c /// ``` pub static ref FORMAT_CLEAN: TableFormat = FormatBuilder::new() .padding(1, 1) .build(); /// Format for a table with only external borders and title separator /// /// # Example /// ```text /// +--------+ /// | T1 T2 | /// +========+ /// | a b | /// | c d | /// +--------+ /// ``` pub static ref FORMAT_BORDERS_ONLY: TableFormat = FormatBuilder::new() .padding(1, 1) .separator(LinePosition::Title, *EQU_PLUS_SEP) .separator(LinePosition::Bottom, *MINUS_PLUS_SEP) .separator(LinePosition::Top, *MINUS_PLUS_SEP) .borders('|') .build(); /// A table with no external border /// /// # Example /// ```text /// T1 | T2 /// ====+==== /// a | b /// ----+---- /// c | d /// ``` pub static ref FORMAT_NO_BORDER: TableFormat = FormatBuilder::new() .padding(1, 1) .separator(LinePosition::Intern, *MINUS_PLUS_SEP) .separator(LinePosition::Title, *EQU_PLUS_SEP) .column_separator('|') .build(); /// A table with no external border and no line separation /// /// # Example /// ```text /// T1 | T2 /// ----+---- /// a | b /// c | d /// ``` pub static ref FORMAT_NO_BORDER_LINE_SEPARATOR: TableFormat = FormatBuilder::new() .padding(1, 1) .separator(LinePosition::Title, *MINUS_PLUS_SEP) .column_separator('|') .build(); /// A table with borders and delimiters made with box characters /// /// # Example /// ```text /// ┌────┬────┬────┐ /// │ t1 │ t2 │ t3 │ /// ├────┼────┼────┤ /// │ 1 │ 1 │ 1 │ /// ├────┼────┼────┤ /// │ 2 │ 2 │ 2 │ /// └────┴────┴────┘ /// ``` pub static ref FORMAT_BOX_CHARS: TableFormat = FormatBuilder::new() .column_separator('│') .borders('│') .separators(&[LinePosition::Top], LineSeparator::new('─', '┬', '┌', '┐')) .separators(&[LinePosition::Intern], LineSeparator::new('─', '┼', '├', '┤')) .separators(&[LinePosition::Bottom], LineSeparator::new('─', '┴', '└', '┘')) .padding(1, 1) .build(); } } prettytable-rs-0.10.0/src/lib.rs000064400000000000000000001175461046102023000146040ustar 00000000000000#![warn( missing_docs, unused_extern_crates, unused_import_braces, unused_qualifications )] //! A formatted and aligned table printer written in rust #[macro_use] extern crate lazy_static; use std::fmt; use std::io::{self, Error, Write}; use std::iter::{FromIterator, IntoIterator}; use std::ops::{Index, IndexMut}; use std::slice::{Iter, IterMut}; pub use term::{color, Attr}; pub(crate) use term::{stdout, Terminal}; mod cell; pub mod format; mod row; mod utils; #[cfg(feature = "csv")] pub mod csv; #[cfg(feature = "evcxr")] pub mod evcxr; pub use cell::Cell; use format::{consts, LinePosition, TableFormat}; pub use row::Row; use utils::StringWriter; /// An owned printable table #[derive(Default, Clone, Debug, Hash, PartialEq, Eq)] pub struct Table { format: Box, titles: Box>, rows: Vec, } /// A borrowed immutable `Table` slice /// A `TableSlice` is obtained by slicing a `Table` with the `Slice::slice` method. /// /// # Examples /// ```rust /// # #[macro_use] extern crate prettytable; /// use prettytable::{Table, Slice}; /// # fn main() { /// let table = table![[1, 2, 3], [4, 5, 6], [7, 8, 9]]; /// let slice = table.slice(1..); /// slice.printstd(); // Prints only rows 1 and 2 /// /// //Also supports other syntax : /// table.slice(..); /// table.slice(..2); /// table.slice(1..3); /// # } /// ``` /// #[derive(Copy, Clone, Debug, Hash, PartialEq, Eq)] pub struct TableSlice<'a> { format: &'a TableFormat, titles: &'a Option, rows: &'a [Row], } impl<'a> TableSlice<'a> { /// Compute and return the number of column // #[deprecated(since="0.8.0", note="Will become private in future release. See [issue #87](https://github.com/phsym/prettytable-rs/issues/87)")] fn get_column_num(&self) -> usize { let mut cnum = match *self.titles { Some(ref t) => t.column_count(), None => 0, }; for r in self.rows { let l = r.column_count(); if l > cnum { cnum = l; } } cnum } /// Get the number of rows pub fn len(&self) -> usize { self.rows.len() } /// Check if the table slice is empty pub fn is_empty(&self) -> bool { self.rows.is_empty() } /// Get an immutable reference to a row pub fn get_row(&self, row: usize) -> Option<&Row> { self.rows.get(row) } /// Get the width of the column at position `col_idx`. /// Return 0 if the column does not exists; fn get_column_width(&self, col_idx: usize) -> usize { let mut width = match *self.titles { Some(ref t) => t.get_column_width(col_idx, self.format), None => 0, }; for r in self.rows { let l = r.get_column_width(col_idx, self.format); if l > width { width = l; } } width } /// Get the width of all columns, and return a slice /// with the result for each column fn get_all_column_width(&self) -> Vec { let colnum = self.get_column_num(); let mut col_width = vec![0usize; colnum]; #[allow(clippy::needless_range_loop)] for i in 0..colnum { // TODO: calling "get_column_width()" in a loop is inefficient col_width[i] = self.get_column_width(i); } col_width } /// Returns an iterator over the immutable cells of the column specified by `column` pub fn column_iter(&self, column: usize) -> ColumnIter { ColumnIter(self.rows.iter(), column) } /// Returns an iterator over immutable rows pub fn row_iter(&self) -> Iter { self.rows.iter() } /// Internal only fn __print(&self, out: &mut T, f: F) -> Result where F: Fn(&Row, &mut T, &TableFormat, &[usize]) -> Result, { let mut height = 0; // Compute columns width let col_width = self.get_all_column_width(); height += self .format .print_line_separator(out, &col_width, LinePosition::Top)?; if let Some(ref t) = *self.titles { height += f(t, out, self.format, &col_width)?; height += self .format .print_line_separator(out, &col_width, LinePosition::Title)?; } // Print rows let mut iter = self.rows.iter().peekable(); while let Some(r) = iter.next() { height += f(r, out, self.format, &col_width)?; if iter.peek().is_some() { height += self.format .print_line_separator(out, &col_width, LinePosition::Intern)?; } } height += self .format .print_line_separator(out, &col_width, LinePosition::Bottom)?; out.flush()?; Ok(height) } /// Print the table to `out` and returns the number of /// line printed, or an error pub fn print(&self, out: &mut T) -> Result { self.__print(out, Row::print) } /// Print the table to terminal `out`, applying styles when needed and returns the number of /// line printed, or an error pub fn print_term(&self, out: &mut T) -> Result { self.__print(out, Row::print_term) } /// Print the table to standard output. Colors won't be displayed unless /// stdout is a tty terminal, or `force_colorize` is set to `true`. /// In ANSI terminals, colors are displayed using ANSI escape characters. When for example the /// output is redirected to a file, or piped to another program, the output is considered /// as not beeing tty, and ANSI escape characters won't be displayed unless `force colorize` /// is set to `true`. /// # Returns /// A `Result` holding the number of lines printed, or an `io::Error` if any failure happens pub fn print_tty(&self, force_colorize: bool) -> Result { use is_terminal::IsTerminal; match (stdout(), io::stdout().is_terminal() || force_colorize) { (Some(mut o), true) => self.print_term(&mut *o), _ => self.print(&mut io::stdout()), } } /// Print the table to standard output. Colors won't be displayed unless /// stdout is a tty terminal. This means that if stdout is redirected to a file, or piped /// to another program, no color will be displayed. /// To force colors rendering, use `print_tty()` method. /// Any failure to print is ignored. For better control, use `print_tty()`. /// Calling `printstd()` is equivalent to calling `print_tty(false)` and ignoring the result. pub fn printstd(&self) { let _ = self.print_tty(false); // Ignore result } /// Print table in HTML format to `out`. pub fn print_html(&self, out: &mut T) -> Result<(), Error> { // Compute column width let column_num = self.get_column_num(); out.write_all(b"")?; // Print titles / table header if let Some(ref t) = *self.titles { out.write_all(b"")?; } // Print rows for r in self.rows { out.write_all(b"")?; r.print_html(out, column_num)?; out.write_all(b"")?; } out.write_all(b"
")?; t.print_html(out, column_num)?; out.write_all(b"
")?; out.flush()?; Ok(()) } } impl<'a> IntoIterator for &'a TableSlice<'a> { type Item = &'a Row; type IntoIter = Iter<'a, Row>; fn into_iter(self) -> Self::IntoIter { self.row_iter() } } impl Table { /// Create an empty table pub fn new() -> Table { Self::init(Vec::new()) } /// Create a table initialized with `rows` pub fn init(rows: Vec) -> Table { Table { rows, titles: Box::new(None), format: Box::new(*consts::FORMAT_DEFAULT), } } /// Change the table format. Eg : Separators pub fn set_format(&mut self, format: TableFormat) { *self.format = format; } /// Get a mutable reference to the internal format pub fn get_format(&mut self) -> &mut TableFormat { &mut self.format } /// Compute and return the number of column // #[deprecated(since="0.8.0", note="Will become private in future release. See [issue #87](https://github.com/phsym/prettytable-rs/issues/87)")] #[cfg(test)] // Only used for testing for now pub(crate) fn get_column_num(&self) -> usize { self.as_slice().get_column_num() } /// Get the number of rows pub fn len(&self) -> usize { self.rows.len() } /// Check if the table is empty pub fn is_empty(&self) -> bool { self.rows.is_empty() } /// Set the optional title lines pub fn set_titles(&mut self, titles: Row) { *self.titles = Some(titles); } /// Unset the title line pub fn unset_titles(&mut self) { *self.titles = None; } /// Get a mutable reference to a row pub fn get_mut_row(&mut self, row: usize) -> Option<&mut Row> { self.rows.get_mut(row) } /// Get an immutable reference to a row pub fn get_row(&self, row: usize) -> Option<&Row> { self.rows.get(row) } /// Append a row in the table, transferring ownership of this row to the table /// and returning a mutable reference to the row pub fn add_row(&mut self, row: Row) -> &mut Row { self.rows.push(row); let l = self.rows.len() - 1; &mut self.rows[l] } /// Append an empty row in the table. Return a mutable reference to this new row. pub fn add_empty_row(&mut self) -> &mut Row { self.add_row(Row::default()) } /// Insert `row` at the position `index`, and return a mutable reference to this row. /// If index is higher than current numbers of rows, `row` is appended at the end of the table pub fn insert_row(&mut self, index: usize, row: Row) -> &mut Row { if index < self.rows.len() { self.rows.insert(index, row); &mut self.rows[index] } else { self.add_row(row) } } /// Modify a single element in the table pub fn set_element(&mut self, element: &str, column: usize, row: usize) -> Result<(), &str> { let rowline = self.get_mut_row(row).ok_or("Cannot find row")?; // TODO: If a cell already exist, copy it's alignment parameter rowline.set_cell(Cell::new(element), column) } /// Remove the row at position `index`. Silently skip if the row does not exist pub fn remove_row(&mut self, index: usize) { if index < self.rows.len() { self.rows.remove(index); } } /// Return an iterator over the immutable cells of the column specified by `column` pub fn column_iter(&self, column: usize) -> ColumnIter { ColumnIter(self.rows.iter(), column) } /// Return an iterator over the mutable cells of the column specified by `column` pub fn column_iter_mut(&mut self, column: usize) -> ColumnIterMut { ColumnIterMut(self.rows.iter_mut(), column) } /// Returns an iterator over immutable rows pub fn row_iter(&self) -> Iter { self.rows.iter() } /// Returns an iterator over mutable rows pub fn row_iter_mut(&mut self) -> IterMut { self.rows.iter_mut() } /// Print the table to `out` and returns the number /// of lines printed, or an error pub fn print(&self, out: &mut T) -> Result { self.as_slice().print(out) } /// Print the table to terminal `out`, applying styles when needed and returns the number /// of lines printed, or an error pub fn print_term(&self, out: &mut T) -> Result { self.as_slice().print_term(out) } /// Print the table to standard output. Colors won't be displayed unless /// stdout is a tty terminal, or `force_colorize` is set to `true`. /// In ANSI terminals, colors are displayed using ANSI escape characters. When for example the /// output is redirected to a file, or piped to another program, the output is considered /// as not beeing tty, and ANSI escape characters won't be displayed unless `force colorize` /// is set to `true`. /// # Returns /// A `Result` holding the number of lines printed, or an `io::Error` if any failure happens pub fn print_tty(&self, force_colorize: bool) -> Result { self.as_slice().print_tty(force_colorize) } /// Print the table to standard output. Colors won't be displayed unless /// stdout is a tty terminal. This means that if stdout is redirected to a file, or piped /// to another program, no color will be displayed. /// To force colors rendering, use `print_tty()` method. /// Any failure to print is ignored. For better control, use `print_tty()`. /// Calling `printstd()` is equivalent to calling `print_tty(false)` and ignoring the result. pub fn printstd(&self) { self.as_slice().printstd() } /// Print table in HTML format to `out`. pub fn print_html(&self, out: &mut T) -> Result<(), Error> { self.as_slice().print_html(out) } } /// Trait implemented by types which can be sliced pub trait AsTableSlice { /// Get a slice from self fn as_slice(&self) -> TableSlice<'_>; } impl AsTableSlice for Table { fn as_slice(&self) -> TableSlice<'_> { TableSlice { format: &self.format, titles: &self.titles, rows: &self.rows, } } } impl AsTableSlice for T where T: AsRef, { fn as_slice(&self) -> TableSlice<'_> { self.as_ref().as_slice() } } impl Index for Table { type Output = Row; fn index(&self, idx: usize) -> &Self::Output { &self.rows[idx] } } impl<'a> Index for TableSlice<'a> { type Output = Row; fn index(&self, idx: usize) -> &Self::Output { &self.rows[idx] } } impl IndexMut for Table { fn index_mut(&mut self, idx: usize) -> &mut Self::Output { &mut self.rows[idx] } } impl fmt::Display for Table { fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> { self.as_slice().fmt(fmt) } } impl<'a> fmt::Display for TableSlice<'a> { fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> { let mut writer = StringWriter::new(); if self.print(&mut writer).is_err() { return Err(fmt::Error); } fmt.write_str(writer.as_string()) } } impl> FromIterator for Table { fn from_iter(iterator: T) -> Table where T: IntoIterator, { Self::init(iterator.into_iter().map(Row::from).collect()) } } impl FromIterator for Table { fn from_iter(iterator: T) -> Table where T: IntoIterator, { Self::init(iterator.into_iter().collect()) } } impl From for Table where B: ToString, A: IntoIterator, T: IntoIterator, { fn from(it: T) -> Table { Self::from_iter(it) } } impl<'a> IntoIterator for &'a Table { type Item = &'a Row; type IntoIter = Iter<'a, Row>; fn into_iter(self) -> Self::IntoIter { self.row_iter() } } impl<'a> IntoIterator for &'a mut Table { type Item = &'a mut Row; type IntoIter = IterMut<'a, Row>; fn into_iter(self) -> Self::IntoIter { self.row_iter_mut() } } // impl IntoIterator for Table { // type Item = Row; // type IntoIter = std::vec::IntoIter; // fn into_iter(self) -> Self::IntoIter { // self.rows.into_iter() // } // } impl> Extend for Table { fn extend>(&mut self, iter: T) { self.rows.extend(iter.into_iter().map(|r| r.into())); } } /// Iterator over immutable cells in a column pub struct ColumnIter<'a>(Iter<'a, Row>, usize); impl<'a> Iterator for ColumnIter<'a> { type Item = &'a Cell; fn next(&mut self) -> Option<&'a Cell> { self.0.next().and_then(|row| row.get_cell(self.1)) } } /// Iterator over mutable cells in a column pub struct ColumnIterMut<'a>(IterMut<'a, Row>, usize); impl<'a> Iterator for ColumnIterMut<'a> { type Item = &'a mut Cell; fn next(&mut self) -> Option<&'a mut Cell> { self.0.next().and_then(|row| row.get_mut_cell(self.1)) } } impl<'a> AsTableSlice for TableSlice<'a> { fn as_slice(&self) -> TableSlice<'_> { *self } } impl<'a> AsRef> for TableSlice<'a> { fn as_ref(&self) -> &TableSlice<'a> { self } } /// Trait implemented by types which can be sliced pub trait Slice<'a, E> { /// Type output after slicing type Output: 'a; /// Get a slice from self fn slice(&'a self, arg: E) -> Self::Output; } impl<'a, T, E> Slice<'a, E> for T where T: AsTableSlice, [Row]: Index, { type Output = TableSlice<'a>; fn slice(&'a self, arg: E) -> Self::Output { let mut sl = self.as_slice(); sl.rows = sl.rows.index(arg); sl } } /// Create a table filled with some values /// /// All the arguments used for elements must implement the `std::string::ToString` trait /// # Syntax /// ```text /// table!([Element1_ row1, Element2_ row1, ...], [Element1_row2, ...], ...); /// ``` /// /// # Example /// ``` /// # #[macro_use] extern crate prettytable; /// # fn main() { /// // Create a table initialized with some rows : /// let tab = table!(["Element1", "Element2", "Element3"], /// [1, 2, 3], /// ["A", "B", "C"] /// ); /// # drop(tab); /// # } /// ``` /// /// Some style can also be given in table creation /// /// ``` /// # #[macro_use] extern crate prettytable; /// # fn main() { /// let tab = table!([FrByl->"Element1", Fgc->"Element2", "Element3"], /// [FrBy => 1, 2, 3], /// ["A", "B", "C"] /// ); /// # drop(tab); /// # } /// ``` /// /// For details about style specifier syntax, check doc for [`Cell::style_spec`](cell/struct.Cell.html#method.style_spec) method #[macro_export] macro_rules! table { ($([$($content:tt)*]), *) => ( $crate::Table::init(vec![$($crate::row![$($content)*]), *]) ); } /// Create a table with `table!` macro, print it to standard output, then return this table for future usage. /// /// The syntax is the same that the one for the `table!` macro #[macro_export] macro_rules! ptable { ($($content:tt)*) => ( { let tab = $crate::table!($($content)*); tab.printstd(); tab } ); } #[cfg(test)] mod tests { use crate::utils::StringWriter; use crate::{format, AsTableSlice, Cell, Row, Slice, Table}; use format::consts::{ FORMAT_BOX_CHARS, FORMAT_CLEAN, FORMAT_DEFAULT, FORMAT_NO_COLSEP, FORMAT_NO_LINESEP, }; #[test] fn table() { let mut table = Table::new(); table.add_row(Row::new(vec![ Cell::new("a"), Cell::new("bc"), Cell::new("def"), ])); table.add_row(Row::new(vec![ Cell::new("def"), Cell::new("bc"), Cell::new("a"), ])); table.set_titles(Row::new(vec![ Cell::new("t1"), Cell::new("t2"), Cell::new("t3"), ])); let out = "\ +-----+----+-----+ | t1 | t2 | t3 | +=====+====+=====+ | a | bc | def | +-----+----+-----+ | def | bc | a | +-----+----+-----+ "; assert_eq!(table.to_string().replace("\r\n", "\n"), out); assert_eq!(7, table.print(&mut StringWriter::new()).unwrap()); table.unset_titles(); let out = "\ +-----+----+-----+ | a | bc | def | +-----+----+-----+ | def | bc | a | +-----+----+-----+ "; assert_eq!(table.to_string().replace("\r\n", "\n"), out); assert_eq!(5, table.print(&mut StringWriter::new()).unwrap()); } #[test] fn index() { let mut table = Table::new(); table.add_row(Row::new(vec![ Cell::new("a"), Cell::new("bc"), Cell::new("def"), ])); table.add_row(Row::new(vec![ Cell::new("def"), Cell::new("bc"), Cell::new("a"), ])); table.set_titles(Row::new(vec![ Cell::new("t1"), Cell::new("t2"), Cell::new("t3"), ])); assert_eq!(table[1][1].get_content(), "bc"); table[1][1] = Cell::new("newval"); assert_eq!(table[1][1].get_content(), "newval"); let out = "\ +-----+--------+-----+ | t1 | t2 | t3 | +=====+========+=====+ | a | bc | def | +-----+--------+-----+ | def | newval | a | +-----+--------+-----+ "; assert_eq!(table.to_string().replace("\r\n", "\n"), out); assert_eq!(7, table.print(&mut StringWriter::new()).unwrap()); } #[test] fn table_size() { let mut table = Table::new(); assert!(table.is_empty()); assert!(table.as_slice().is_empty()); assert_eq!(table.len(), 0); assert_eq!(table.as_slice().len(), 0); assert_eq!(table.get_column_num(), 0); assert_eq!(table.as_slice().get_column_num(), 0); table.add_empty_row(); assert!(!table.is_empty()); assert!(!table.as_slice().is_empty()); assert_eq!(table.len(), 1); assert_eq!(table.as_slice().len(), 1); assert_eq!(table.get_column_num(), 0); assert_eq!(table.as_slice().get_column_num(), 0); table[0].add_cell(Cell::default()); assert_eq!(table.get_column_num(), 1); assert_eq!(table.as_slice().get_column_num(), 1); } #[test] fn get_row() { let mut table = Table::new(); table.add_row(Row::new(vec![ Cell::new("a"), Cell::new("bc"), Cell::new("def"), ])); table.add_row(Row::new(vec![ Cell::new("def"), Cell::new("bc"), Cell::new("a"), ])); assert!(table.get_row(12).is_none()); assert!(table.get_row(1).is_some()); assert_eq!(table.get_row(1).unwrap()[0].get_content(), "def"); assert!(table.get_mut_row(12).is_none()); assert!(table.get_mut_row(1).is_some()); table.get_mut_row(1).unwrap().add_cell(Cell::new("z")); assert_eq!(table.get_row(1).unwrap()[3].get_content(), "z"); } #[test] fn add_empty_row() { let mut table = Table::new(); assert_eq!(table.len(), 0); table.add_empty_row(); assert_eq!(table.len(), 1); assert_eq!(table[0].len(), 0); } #[test] fn remove_row() { let mut table = Table::new(); table.add_row(Row::new(vec![ Cell::new("a"), Cell::new("bc"), Cell::new("def"), ])); table.add_row(Row::new(vec![ Cell::new("def"), Cell::new("bc"), Cell::new("a"), ])); table.remove_row(12); assert_eq!(table.len(), 2); table.remove_row(0); assert_eq!(table.len(), 1); assert_eq!(table[0][0].get_content(), "def"); } #[test] fn insert_row() { let mut table = Table::new(); table.add_row(Row::new(vec![ Cell::new("a"), Cell::new("bc"), Cell::new("def"), ])); table.add_row(Row::new(vec![ Cell::new("def"), Cell::new("bc"), Cell::new("a"), ])); table.insert_row( 12, Row::new(vec![Cell::new("1"), Cell::new("2"), Cell::new("3")]), ); assert_eq!(table.len(), 3); assert_eq!(table[2][1].get_content(), "2"); table.insert_row( 1, Row::new(vec![Cell::new("3"), Cell::new("4"), Cell::new("5")]), ); assert_eq!(table.len(), 4); assert_eq!(table[1][1].get_content(), "4"); assert_eq!(table[2][1].get_content(), "bc"); } #[test] fn set_element() { let mut table = Table::new(); table.add_row(Row::new(vec![ Cell::new("a"), Cell::new("bc"), Cell::new("def"), ])); table.add_row(Row::new(vec![ Cell::new("def"), Cell::new("bc"), Cell::new("a"), ])); assert!(table.set_element("foo", 12, 12).is_err()); assert!(table.set_element("foo", 1, 1).is_ok()); assert_eq!(table[1][1].get_content(), "foo"); } #[test] fn no_linesep() { let mut table = Table::new(); table.set_format(*FORMAT_NO_LINESEP); table.add_row(Row::new(vec![ Cell::new("a"), Cell::new("bc"), Cell::new("def"), ])); table.add_row(Row::new(vec![ Cell::new("def"), Cell::new("bc"), Cell::new("a"), ])); table.set_titles(Row::new(vec![ Cell::new("t1"), Cell::new("t2"), Cell::new("t3"), ])); assert_eq!(table[1][1].get_content(), "bc"); table[1][1] = Cell::new("newval"); assert_eq!(table[1][1].get_content(), "newval"); let out = "\ +-----+--------+-----+ | t1 | t2 | t3 | | a | bc | def | | def | newval | a | +-----+--------+-----+ "; assert_eq!(table.to_string().replace("\r\n", "\n"), out); assert_eq!(5, table.print(&mut StringWriter::new()).unwrap()); } #[test] fn no_colsep() { let mut table = Table::new(); table.set_format(*FORMAT_NO_COLSEP); table.add_row(Row::new(vec![ Cell::new("a"), Cell::new("bc"), Cell::new("def"), ])); table.add_row(Row::new(vec![ Cell::new("def"), Cell::new("bc"), Cell::new("a"), ])); table.set_titles(Row::new(vec![ Cell::new("t1"), Cell::new("t2"), Cell::new("t3"), ])); assert_eq!(table[1][1].get_content(), "bc"); table[1][1] = Cell::new("newval"); assert_eq!(table[1][1].get_content(), "newval"); let out = "\ ------------------ t1 t2 t3 \n\ ================== a bc def \n\ ------------------ def newval a \n\ ------------------ "; println!("{}", out); println!("____"); println!("{}", table.to_string().replace("\r\n", "\n")); assert_eq!(table.to_string().replace("\r\n", "\n"), out); assert_eq!(7, table.print(&mut StringWriter::new()).unwrap()); } #[test] fn clean() { let mut table = Table::new(); table.set_format(*FORMAT_CLEAN); table.add_row(Row::new(vec![ Cell::new("a"), Cell::new("bc"), Cell::new("def"), ])); table.add_row(Row::new(vec![ Cell::new("def"), Cell::new("bc"), Cell::new("a"), ])); table.set_titles(Row::new(vec![ Cell::new("t1"), Cell::new("t2"), Cell::new("t3"), ])); assert_eq!(table[1][1].get_content(), "bc"); table[1][1] = Cell::new("newval"); assert_eq!(table[1][1].get_content(), "newval"); let out = "\ \u{0020}t1 t2 t3 \n\ \u{0020}a bc def \n\ \u{0020}def newval a \n\ "; println!("{}", out); println!("____"); println!("{}", table.to_string().replace("\r\n", "\n")); assert_eq!(out, table.to_string().replace("\r\n", "\n")); assert_eq!(3, table.print(&mut StringWriter::new()).unwrap()); } #[test] fn padding() { let mut table = Table::new(); let mut format = *FORMAT_DEFAULT; format.padding(2, 2); table.set_format(format); table.add_row(Row::new(vec![ Cell::new("a"), Cell::new("bc"), Cell::new("def"), ])); table.add_row(Row::new(vec![ Cell::new("def"), Cell::new("bc"), Cell::new("a"), ])); table.set_titles(Row::new(vec![ Cell::new("t1"), Cell::new("t2"), Cell::new("t3"), ])); assert_eq!(table[1][1].get_content(), "bc"); table[1][1] = Cell::new("newval"); assert_eq!(table[1][1].get_content(), "newval"); let out = "\ +-------+----------+-------+ | t1 | t2 | t3 | +=======+==========+=======+ | a | bc | def | +-------+----------+-------+ | def | newval | a | +-------+----------+-------+ "; println!("{}", out); println!("____"); println!("{}", table.to_string().replace("\r\n", "\n")); assert_eq!(out, table.to_string().replace("\r\n", "\n")); assert_eq!(7, table.print(&mut StringWriter::new()).unwrap()); } #[test] fn indent() { let mut table = Table::new(); table.add_row(Row::new(vec![ Cell::new("a"), Cell::new("bc"), Cell::new("def"), ])); table.add_row(Row::new(vec![ Cell::new("def"), Cell::new("bc"), Cell::new("a"), ])); table.set_titles(Row::new(vec![ Cell::new("t1"), Cell::new("t2"), Cell::new("t3"), ])); table.get_format().indent(8); let out = " +-----+----+-----+ | t1 | t2 | t3 | +=====+====+=====+ | a | bc | def | +-----+----+-----+ | def | bc | a | +-----+----+-----+ "; assert_eq!(table.to_string().replace("\r\n", "\n"), out); assert_eq!(7, table.print(&mut StringWriter::new()).unwrap()); } #[test] fn slices() { let mut table = Table::new(); table.set_titles(Row::new(vec![ Cell::new("t1"), Cell::new("t2"), Cell::new("t3"), ])); table.add_row(Row::new(vec![ Cell::new("0"), Cell::new("0"), Cell::new("0"), ])); table.add_row(Row::new(vec![ Cell::new("1"), Cell::new("1"), Cell::new("1"), ])); table.add_row(Row::new(vec![ Cell::new("2"), Cell::new("2"), Cell::new("2"), ])); table.add_row(Row::new(vec![ Cell::new("3"), Cell::new("3"), Cell::new("3"), ])); table.add_row(Row::new(vec![ Cell::new("4"), Cell::new("4"), Cell::new("4"), ])); table.add_row(Row::new(vec![ Cell::new("5"), Cell::new("5"), Cell::new("5"), ])); let out = "\ +----+----+----+ | t1 | t2 | t3 | +====+====+====+ | 1 | 1 | 1 | +----+----+----+ | 2 | 2 | 2 | +----+----+----+ | 3 | 3 | 3 | +----+----+----+ "; let slice = table.slice(..); let slice = slice.slice(1..); let slice = slice.slice(..3); assert_eq!(out, slice.to_string().replace("\r\n", "\n")); assert_eq!(9, slice.print(&mut StringWriter::new()).unwrap()); assert_eq!(out, table.slice(1..4).to_string().replace("\r\n", "\n")); assert_eq!( 9, table.slice(1..4).print(&mut StringWriter::new()).unwrap() ); } #[test] fn test_unicode_separators() { let mut table = Table::new(); table.set_format(*FORMAT_BOX_CHARS); table.add_row(Row::new(vec![ Cell::new("1"), Cell::new("1"), Cell::new("1"), ])); table.add_row(Row::new(vec![ Cell::new("2"), Cell::new("2"), Cell::new("2"), ])); table.set_titles(Row::new(vec![ Cell::new("t1"), Cell::new("t2"), Cell::new("t3"), ])); let out = "\ ┌────┬────┬────┐ │ t1 │ t2 │ t3 │ ├────┼────┼────┤ │ 1 │ 1 │ 1 │ ├────┼────┼────┤ │ 2 │ 2 │ 2 │ └────┴────┴────┘ "; println!("{}", out); println!("____"); println!("{}", table.to_string().replace("\r\n", "\n")); assert_eq!(out, table.to_string().replace("\r\n", "\n")); assert_eq!(7, table.print(&mut StringWriter::new()).unwrap()); } #[test] fn test_readme_format() { // The below is lifted from the README let mut table = Table::new(); let format = format::FormatBuilder::new() .column_separator('|') .borders('|') .separators( &[format::LinePosition::Top, format::LinePosition::Bottom], format::LineSeparator::new('-', '+', '+', '+'), ) .padding(1, 1) .build(); table.set_format(format); table.set_titles(Row::new(vec![Cell::new("Title 1"), Cell::new("Title 2")])); table.add_row(Row::new(vec![Cell::new("Value 1"), Cell::new("Value 2")])); table.add_row(Row::new(vec![ Cell::new("Value three"), Cell::new("Value four"), ])); let out = "\ +-------------+------------+ | Title 1 | Title 2 | | Value 1 | Value 2 | | Value three | Value four | +-------------+------------+ "; println!("{}", out); println!("____"); println!("{}", table.to_string().replace("\r\n", "\n")); assert_eq!(out, table.to_string().replace("\r\n", "\n")); assert_eq!(5, table.print(&mut StringWriter::new()).unwrap()); } #[test] fn test_readme_format_with_title() { let mut table = Table::new(); table.set_format(*format::consts::FORMAT_NO_LINESEP_WITH_TITLE); table.set_titles(Row::new(vec![Cell::new("Title 1"), Cell::new("Title 2")])); table.add_row(Row::new(vec![Cell::new("Value 1"), Cell::new("Value 2")])); table.add_row(Row::new(vec![ Cell::new("Value three"), Cell::new("Value four"), ])); let out = "\ +-------------+------------+ | Title 1 | Title 2 | +-------------+------------+ | Value 1 | Value 2 | | Value three | Value four | +-------------+------------+ "; println!("{}", out); println!("____"); println!("{}", table.to_string().replace("\r\n", "\n")); assert_eq!(out, table.to_string().replace("\r\n", "\n")); assert_eq!(6, table.print(&mut StringWriter::new()).unwrap()); } #[test] fn test_empty_table_with_title() { let mut table = Table::new(); table.set_format(*format::consts::FORMAT_NO_LINESEP_WITH_TITLE); table.set_titles(Row::new(vec![Cell::new("Title 1"), Cell::new("Title 2")])); let out = "\ +---------+---------+ | Title 1 | Title 2 | +---------+---------+ +---------+---------+ "; println!("{}", out); println!("____"); println!("{}", table.to_string().replace("\r\n", "\n")); assert_eq!(out, table.to_string().replace("\r\n", "\n")); assert_eq!(4, table.print(&mut StringWriter::new()).unwrap()); } #[test] fn test_horizontal_span() { let mut table = Table::new(); table.set_titles(Row::new(vec![ Cell::new("t1"), Cell::new("t2").with_hspan(2), ])); table.add_row(Row::new(vec![ Cell::new("a"), Cell::new("bc"), Cell::new("def"), ])); table.add_row(Row::new(vec![ Cell::new("def").style_spec("H02c"), Cell::new("a"), ])); let out = "\ +----+----+-----+ | t1 | t2 | +====+====+=====+ | a | bc | def | +----+----+-----+ | def | a | +----+----+-----+ "; println!("{}", out); println!("____"); println!("{}", table.to_string().replace("\r\n", "\n")); assert_eq!(out, table.to_string().replace("\r\n", "\n")); assert_eq!(7, table.print(&mut StringWriter::new()).unwrap()); } #[test] fn table_html() { let mut table = Table::new(); table.add_row(Row::new(vec![ Cell::new("a"), Cell::new("bc"), Cell::new("def"), ])); table.add_row(Row::new(vec![ Cell::new("def"), Cell::new("bc"), Cell::new("a"), ])); table.set_titles(Row::new(vec![ Cell::new("t1"), Cell::new("t2"), Cell::new("t3"), ])); let out = "\
\ \ \ \
t1t2t3
abcdef
defbca
"; let mut writer = StringWriter::new(); assert!(table.print_html(&mut writer).is_ok()); assert_eq!(writer.as_string().replace("\r\n", "\n"), out); table.unset_titles(); let out = "\ \ \ \
abcdef
defbca
"; let mut writer = StringWriter::new(); assert!(table.print_html(&mut writer).is_ok()); assert_eq!(writer.as_string().replace("\r\n", "\n"), out); } #[test] fn table_html_colors() { let mut table = Table::new(); table.add_row(Row::new(vec![ Cell::new("bold").style_spec("b"), Cell::new("italic").style_spec("i"), Cell::new("underline").style_spec("u"), ])); table.add_row(Row::new(vec![ Cell::new("left").style_spec("l"), Cell::new("center").style_spec("c"), Cell::new("right").style_spec("r"), ])); table.add_row(Row::new(vec![ Cell::new("red").style_spec("Fr"), Cell::new("black").style_spec("Fd"), Cell::new("yellow").style_spec("Fy"), ])); table.add_row(Row::new(vec![ Cell::new("bright magenta on cyan").style_spec("FMBc"), Cell::new("white on bright green").style_spec("FwBG"), Cell::new("default on blue").style_spec("Bb"), ])); table.set_titles(Row::new( vec![Cell::new("span horizontal").style_spec("H3")], )); let out = "\ \ \ \ \ \ \
span horizontal
bolditalicunderline
leftcenterright
redblackyellow
bright magenta on cyanwhite on bright greendefault on blue
"; let mut writer = StringWriter::new(); assert!(table.print_html(&mut writer).is_ok()); assert_eq!(writer.as_string().replace("\r\n", "\n"), out); } #[test] fn test_panic() { let mut table = Table::new(); table.add_row(Row::new(vec![Cell::new("\u{1b}[\u{1b}\u{0}\u{0}")])); let out = "+--+ | \u{1b}[\u{1b}\u{0}\u{0} | +--+ "; assert_eq!(table.to_string().replace("\r\n", "\n"), out); assert_eq!(3, table.print(&mut StringWriter::new()).unwrap()); } } prettytable-rs-0.10.0/src/main.rs000064400000000000000000000034701046102023000147500ustar 00000000000000use prettytable::format::*; use prettytable::Cell; use prettytable::Row; use prettytable::Table; use prettytable::{color, Attr}; use prettytable::{ptable, row, table}; // Import macros // trace_macros!(true); #[allow(dead_code)] fn main() { let _ = table!(); let mut table = Table::new(); table.add_row(row![FrByH2b->"This is a long spanning cell", "DEFG", "HIJKLMN"]); table.add_row(row!["foobar", "bar", "foo"]); table.add_row(row![]); // Add style to a full row table.add_row(row![FY => "styled", "bar", "foo"]); table.add_row(Row::new(vec![ Cell::new("foobar2"), // Create a cell with a red foreground color Cell::new_align("bar2", Alignment::CENTER) .with_style(Attr::ForegroundColor(color::RED)) .with_hspan(2), // Create a cell with red foreground color, yellow background color, with bold characters Cell::new("foo2").style_spec("FrByb"), ])); for cell in table.column_iter_mut(2) { cell.align(Alignment::RIGHT); } for cell in table.column_iter_mut(1) { cell.align(Alignment::CENTER); } table.printstd(); println!("Modified : "); table.set_element("new_foo", 2, 1).unwrap(); table.printstd(); // table.get_format().indent(8); // Print a table with some styles on it : // FrBybl means : Foregound red, Background yellow, bold, left align // d means : Default, do nothing ptable!([FrBybl->"A", "B", FrBybr->"C"], [d->123, 234, 345, 456]); // You can also apply style to full rows : let mut table = table!([Frb => "A", "B", "C"], [1, 2, 3, 4], ["A\nBCCZZZ\nDDD", 2, table]); table.set_titles(row!["Title 1", "Title 2"]); table.set_format(*consts::FORMAT_DEFAULT); table.get_format().indent(8); table.printstd(); // println!("{:#?}", table); } prettytable-rs-0.10.0/src/row.rs000064400000000000000000000341551046102023000146370ustar 00000000000000//! This module contains definition of table rows stuff use std::io::{Error, Write}; use std::iter::FromIterator; use std::slice::{Iter, IterMut}; // use std::vec::IntoIter; use std::ops::{Index, IndexMut}; use super::Terminal; use super::format::{ColumnPosition, TableFormat}; use super::utils::NEWLINE; use super::Cell; /// Represent a table row made of cells #[derive(Clone, Debug, Hash, PartialEq, Eq)] pub struct Row { cells: Vec, } impl Row { /// Create a new `Row` backed with `cells` vector pub fn new(cells: Vec) -> Row { Row { cells } } /// Create an row of length `size`, with empty strings stored pub fn empty() -> Row { Self::new(vec![Cell::default(); 0]) } /// Count the number of column required in the table grid. /// It takes into account horizontal spanning of cells. For /// example, a cell with an hspan of 3 will add 3 column to the grid // #[deprecated(since="0.8.0", note="Will become private in future release. See [issue #87](https://github.com/phsym/prettytable-rs/issues/87)")] pub(crate) fn column_count(&self) -> usize { self.cells.iter().map(|c| c.get_hspan()).sum() } /// Get the number of cells in this row pub fn len(&self) -> usize { self.cells.len() // self.cells.iter().map(|c| c.get_hspan()).sum() } /// Check if the row is empty (has no cell) pub fn is_empty(&self) -> bool { self.cells.is_empty() } /// Get the height of this row // #[deprecated(since="0.8.0", note="Will become private in future release. See [issue #87](https://github.com/phsym/prettytable-rs/issues/87)")] fn get_height(&self) -> usize { let mut height = 1; // Minimum height must be 1 to print empty rows for cell in &self.cells { let h = cell.get_height(); if h > height { height = h; } } height } /// Get the minimum width required by the cell in the column `column`. /// Return 0 if the cell does not exist in this row // #[deprecated(since="0.8.0", note="Will become private in future release. See [issue #87](https://github.com/phsym/prettytable-rs/issues/87)")] pub(crate) fn get_column_width(&self, column: usize, format: &TableFormat) -> usize { let mut i = 0; for c in &self.cells { if i + c.get_hspan() > column { if c.get_hspan() == 1 { return c.get_width(); } let (lp, rp) = format.get_padding(); let sep = format .get_column_separator(ColumnPosition::Intern) .map(|_| 1) .unwrap_or_default(); let rem = lp + rp + sep; let mut w = c.get_width(); if w > rem { w -= rem; } else { w = 0; } return (w as f64 / c.get_hspan() as f64).ceil() as usize; } i += c.get_hspan(); } 0 } /// Get the cell at index `idx` pub fn get_cell(&self, idx: usize) -> Option<&Cell> { self.cells.get(idx) } /// Get the mutable cell at index `idx` pub fn get_mut_cell(&mut self, idx: usize) -> Option<&mut Cell> { self.cells.get_mut(idx) } /// Set the `cell` in the row at the given `idx` index pub fn set_cell(&mut self, cell: Cell, idx: usize) -> Result<(), &str> { if idx >= self.len() { return Err("Cannot find cell"); } self.cells[idx] = cell; Ok(()) } /// Append a `cell` at the end of the row pub fn add_cell(&mut self, cell: Cell) { self.cells.push(cell); } /// Insert `cell` at position `index`. If `index` is higher than the row length, /// the cell will be appended at the end pub fn insert_cell(&mut self, index: usize, cell: Cell) { if index < self.cells.len() { self.cells.insert(index, cell); } else { self.add_cell(cell); } } /// Remove the cell at position `index`. Silently skip if this cell does not exist pub fn remove_cell(&mut self, index: usize) { if index < self.cells.len() { self.cells.remove(index); } } /// Returns an immutable iterator over cells pub fn iter(&self) -> Iter { self.cells.iter() } /// Returns an mutable iterator over cells pub fn iter_mut(&mut self) -> IterMut { self.cells.iter_mut() } /// Internal only fn __print( &self, out: &mut T, format: &TableFormat, col_width: &[usize], f: F, ) -> Result where F: Fn(&Cell, &mut T, usize, usize, bool) -> Result<(), Error>, { let height = self.get_height(); for i in 0..height { //TODO: Wrap this into dedicated function one day out.write_all(&vec![b' '; format.get_indent()])?; format.print_column_separator(out, ColumnPosition::Left)?; let (lp, rp) = format.get_padding(); let mut j = 0; let mut hspan = 0; // The additional offset caused by cell's horizontal spanning while j + hspan < col_width.len() { out.write_all(&vec![b' '; lp])?; // Left padding // skip_r_fill skip filling the end of the last cell if there's no character // delimiting the end of the table let skip_r_fill = (j == col_width.len() - 1) && format.get_column_separator(ColumnPosition::Right).is_none(); match self.get_cell(j) { Some(c) => { // In case of horizontal spanning, width is the sum of all spanned columns' width let mut w = col_width[j + hspan..j + hspan + c.get_hspan()].iter().sum(); let real_span = c.get_hspan() - 1; w += real_span * (lp + rp) + real_span * format .get_column_separator(ColumnPosition::Intern) .map(|_| 1) .unwrap_or_default(); // Print cell content f(c, out, i, w, skip_r_fill)?; hspan += real_span; // Add span to offset } None => f(&Cell::default(), out, i, col_width[j + hspan], skip_r_fill)?, }; out.write_all(&vec![b' '; rp])?; // Right padding if j + hspan < col_width.len() - 1 { format.print_column_separator(out, ColumnPosition::Intern)?; } j += 1; } format.print_column_separator(out, ColumnPosition::Right)?; out.write_all(NEWLINE)?; } Ok(height) } /// Print the row to `out`, with `separator` as column separator, and `col_width` /// specifying the width of each columns. Returns the number of printed lines // #[deprecated(since="0.8.0", note="Will become private in future release. See [issue #87](https://github.com/phsym/prettytable-rs/issues/87)")] pub(crate) fn print( &self, out: &mut T, format: &TableFormat, col_width: &[usize], ) -> Result { self.__print(out, format, col_width, Cell::print) } /// Print the row to terminal `out`, with `separator` as column separator, and `col_width` /// specifying the width of each columns. Apply style when needed. returns the number of printed lines // #[deprecated(since="0.8.0", note="Will become private in future release. See [issue #87](https://github.com/phsym/prettytable-rs/issues/87)")] pub(crate) fn print_term( &self, out: &mut T, format: &TableFormat, col_width: &[usize], ) -> Result { self.__print(out, format, col_width, Cell::print_term) } /// Print the row in HTML format to `out`. /// /// If the row is has fewer columns than `col_num`, the row is padded with empty cells. pub fn print_html(&self, out: &mut T, col_num: usize) -> Result<(), Error> { let mut printed_columns = 0; for cell in self.iter() { printed_columns += cell.print_html(out)?; } // Pad with empty cells, if target width is not reached for _ in 0..col_num - printed_columns { Cell::default().print_html(out)?; } Ok(()) } } impl Default for Row { fn default() -> Row { Row::empty() } } impl Index for Row { type Output = Cell; fn index(&self, idx: usize) -> &Self::Output { &self.cells[idx] } } impl IndexMut for Row { fn index_mut(&mut self, idx: usize) -> &mut Self::Output { &mut self.cells[idx] } } impl FromIterator
for Row { fn from_iter(iterator: T) -> Row where T: IntoIterator, { Self::new(iterator.into_iter().map(|ref e| Cell::from(e)).collect()) } } impl From for Row where A: ToString, T: IntoIterator, { fn from(it: T) -> Row { Self::from_iter(it) } } impl<'a> IntoIterator for &'a Row { type Item = &'a Cell; type IntoIter = Iter<'a, Cell>; fn into_iter(self) -> Self::IntoIter { self.iter() } } // impl IntoIterator for Row { // type Item = Cell; // type IntoIter = IntoIter; // fn into_iter(self) -> Self::IntoIter { // self.cells.into_iter() // } // } impl<'a> IntoIterator for &'a mut Row { type Item = &'a mut Cell; type IntoIter = IterMut<'a, Cell>; fn into_iter(self) -> Self::IntoIter { self.iter_mut() } } impl Extend for Row { fn extend>(&mut self, iter: T) { self.cells .extend(iter.into_iter().map(|s| Cell::new(&s.to_string()))); } } // impl > Extend for Row { // fn extend>(&mut self, iter: T) { // self.cells.extend(iter.into_iter().map(|s| s.into())); // } // } /// This macro simplifies `Row` creation /// /// The syntax support style spec /// # Example /// ``` /// # #[macro_use] extern crate prettytable; /// # fn main() { /// // Create a normal row /// let row1 = row!["Element 1", "Element 2", "Element 3"]; /// // Create a row with all cells formatted with red foreground color, yellow background color /// // bold, italic, align in the center of the cell /// let row2 = row![FrBybic => "Element 1", "Element 2", "Element 3"]; /// // Create a row with first cell in blue, second one in red, and last one with default style /// let row3 = row![Fb->"blue", Fr->"red", "normal"]; /// // Do something with rows /// # drop(row1); /// # drop(row2); /// # drop(row3); /// # } /// ``` /// /// For details about style specifier syntax, check doc for [`Cell::style_spec`](cell/struct.Cell.html#method.style_spec) method #[macro_export] macro_rules! row { (($($out:tt)*);) => (vec![$($out)*]); (($($out:tt)*); $value:expr) => (vec![$($out)* $crate::cell!($value)]); (($($out:tt)*); $value:expr, $($n:tt)*) => ($crate::row!(($($out)* $crate::cell!($value),); $($n)*)); (($($out:tt)*); $style:ident -> $value:expr) => (vec![$($out)* $crate::cell!($style -> $value)]); (($($out:tt)*); $style:ident -> $value:expr, $($n: tt)*) => ($crate::row!(($($out)* $crate::cell!($style -> $value),); $($n)*)); ($($content:expr), *) => ($crate::Row::new(vec![$($crate::cell!($content)), *])); // This line may not be needed starting from Rust 1.20 ($style:ident => $($content:expr), *) => ($crate::Row::new(vec![$($crate::cell!($style -> $content)), *])); ($style:ident => $($content:expr,) *) => ($crate::Row::new(vec![$($crate::cell!($style -> $content)), *])); ($($content:tt)*) => ($crate::Row::new($crate::row!((); $($content)*))); } #[cfg(test)] mod tests { use super::*; use Cell; #[test] fn row_default_empty() { let row1 = Row::default(); assert_eq!(row1.len(), 0); assert!(row1.is_empty()); } #[test] fn get_add_set_cell() { let mut row = Row::from(vec!["foo", "bar", "foobar"]); assert_eq!(row.len(), 3); assert!(row.get_mut_cell(12).is_none()); let c1 = row.get_mut_cell(0).unwrap().clone(); assert_eq!(c1.get_content(), "foo"); let c1 = Cell::from(&"baz"); assert!(row.set_cell(c1.clone(), 1000).is_err()); assert!(row.set_cell(c1.clone(), 0).is_ok()); assert_eq!(row.get_cell(0).unwrap().get_content(), "baz"); row.add_cell(c1.clone()); assert_eq!(row.len(), 4); assert_eq!(row.get_cell(3).unwrap().get_content(), "baz"); } #[test] fn insert_cell() { let mut row = Row::from(vec!["foo", "bar", "foobar"]); assert_eq!(row.len(), 3); let cell = Cell::new("baz"); row.insert_cell(1000, cell.clone()); assert_eq!(row.len(), 4); assert_eq!(row.get_cell(3).unwrap().get_content(), "baz"); row.insert_cell(1, cell.clone()); assert_eq!(row.len(), 5); assert_eq!(row.get_cell(1).unwrap().get_content(), "baz"); } #[test] fn remove_cell() { let mut row = Row::from(vec!["foo", "bar", "foobar"]); assert_eq!(row.len(), 3); row.remove_cell(1000); assert_eq!(row.len(), 3); row.remove_cell(1); assert_eq!(row.len(), 2); assert_eq!(row.get_cell(0).unwrap().get_content(), "foo"); assert_eq!(row.get_cell(1).unwrap().get_content(), "foobar"); } #[test] fn extend_row() { let mut row = Row::from(vec!["foo", "bar", "foobar"]); row.extend(vec!["A", "B", "C"]); assert_eq!(row.len(), 6); assert_eq!(row.get_cell(3).unwrap().get_content(), "A"); assert_eq!(row.get_cell(4).unwrap().get_content(), "B"); assert_eq!(row.get_cell(5).unwrap().get_content(), "C"); } } prettytable-rs-0.10.0/src/utils.rs000064400000000000000000000155121046102023000151640ustar 00000000000000//! Internal only utilities use std::fmt; use std::io::{Error, ErrorKind, Write}; use std::str; use unicode_width::{UnicodeWidthChar, UnicodeWidthStr}; use super::format::Alignment; #[cfg(any(not(windows), not(feature = "win_crlf")))] pub static NEWLINE: &[u8] = b"\n"; #[cfg(all(windows, feature = "win_crlf"))] pub static NEWLINE: &[u8] = b"\r\n"; /// Internal utility for writing data into a string pub struct StringWriter { string: String, } impl StringWriter { /// Create a new `StringWriter` pub fn new() -> StringWriter { StringWriter { string: String::new(), } } /// Return a reference to the internally written `String` pub fn as_string(&self) -> &str { &self.string } } impl Write for StringWriter { fn write(&mut self, data: &[u8]) -> Result { let string = match str::from_utf8(data) { Ok(s) => s, Err(e) => { return Err(Error::new( ErrorKind::Other, format!("Cannot decode utf8 string : {}", e), )) } }; self.string.push_str(string); Ok(data.len()) } fn flush(&mut self) -> Result<(), Error> { // Nothing to do here Ok(()) } } /// Align/fill a string and print it to `out` /// If `skip_right_fill` is set to `true`, then no space will be added after the string /// to complete alignment pub fn print_align( out: &mut T, align: Alignment, text: &str, fill: char, size: usize, skip_right_fill: bool, ) -> Result<(), Error> { let text_len = display_width(text); let mut nfill = if text_len < size { size - text_len } else { 0 }; let n = match align { Alignment::LEFT => 0, Alignment::RIGHT => nfill, Alignment::CENTER => nfill / 2, }; if n > 0 { out.write_all(&vec![fill as u8; n])?; nfill -= n; } out.write_all(text.as_bytes())?; if nfill > 0 && !skip_right_fill { out.write_all(&vec![fill as u8; nfill])?; } Ok(()) } /// Return the display width of a unicode string. /// This functions takes ANSI-escaped color codes into account. pub fn display_width(text: &str) -> usize { #[derive(PartialEq, Eq, Clone, Copy)] enum State { /// We are not inside any terminal escape. Normal, /// We have just seen a \u{1b} EscapeChar, /// We have just seen a [ OpenBracket, /// We just ended the escape by seeing an m AfterEscape, } let width = UnicodeWidthStr::width(text); let mut state = State::Normal; let mut hidden = 0; for c in text.chars() { state = match (state, c) { (State::Normal, '\u{1b}') => State::EscapeChar, (State::EscapeChar, '[') => State::OpenBracket, (State::EscapeChar, _) => State::Normal, (State::OpenBracket, 'm') => State::AfterEscape, _ => state, }; // We don't count escape characters as hidden as // UnicodeWidthStr::width already considers them. if matches!(state, State::OpenBracket | State::AfterEscape) { // but if we see an escape char *inside* the ANSI escape, we should ignore it. if UnicodeWidthChar::width(c).unwrap_or(0) > 0 { hidden += 1; } } if state == State::AfterEscape { state = State::Normal; } } assert!( width >= hidden, "internal error: width {} less than hidden {} on string {:?}", width, hidden, text ); width - hidden } /// Wrapper struct which will emit the HTML-escaped version of the contained /// string when passed to a format string. pub struct HtmlEscape<'a>(pub &'a str); impl<'a> fmt::Display for HtmlEscape<'a> { fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { // Because the internet is always right, turns out there's not that many // characters to escape: http://stackoverflow.com/questions/7381974 let HtmlEscape(s) = *self; let pile_o_bits = s; let mut last = 0; for (i, ch) in s.bytes().enumerate() { match ch as char { '<' | '>' | '&' | '\'' | '"' => { fmt.write_str(&pile_o_bits[last..i])?; let s = match ch as char { '>' => ">", '<' => "<", '&' => "&", '\'' => "'", '"' => """, _ => unreachable!(), }; fmt.write_str(s)?; last = i + 1; } _ => {} } } if last < s.len() { fmt.write_str(&pile_o_bits[last..])?; } Ok(()) } } #[cfg(test)] mod tests { use super::*; use crate::format::Alignment; use std::io::Write; #[test] fn string_writer() { let mut out = StringWriter::new(); out.write_all(b"foo").unwrap(); out.write_all(b" ").unwrap(); out.write_all(b"").unwrap(); out.write_all(b"bar").unwrap(); assert_eq!(out.as_string(), "foo bar"); } #[test] fn fill_align() { let mut out = StringWriter::new(); print_align(&mut out, Alignment::RIGHT, "foo", '*', 10, false).unwrap(); assert_eq!(out.as_string(), "*******foo"); let mut out = StringWriter::new(); print_align(&mut out, Alignment::LEFT, "foo", '*', 10, false).unwrap(); assert_eq!(out.as_string(), "foo*******"); let mut out = StringWriter::new(); print_align(&mut out, Alignment::CENTER, "foo", '*', 10, false).unwrap(); assert_eq!(out.as_string(), "***foo****"); let mut out = StringWriter::new(); print_align(&mut out, Alignment::CENTER, "foo", '*', 1, false).unwrap(); assert_eq!(out.as_string(), "foo"); } #[test] fn skip_right_fill() { let mut out = StringWriter::new(); print_align(&mut out, Alignment::RIGHT, "foo", '*', 10, true).unwrap(); assert_eq!(out.as_string(), "*******foo"); let mut out = StringWriter::new(); print_align(&mut out, Alignment::LEFT, "foo", '*', 10, true).unwrap(); assert_eq!(out.as_string(), "foo"); let mut out = StringWriter::new(); print_align(&mut out, Alignment::CENTER, "foo", '*', 10, true).unwrap(); assert_eq!(out.as_string(), "***foo"); let mut out = StringWriter::new(); print_align(&mut out, Alignment::CENTER, "foo", '*', 1, false).unwrap(); assert_eq!(out.as_string(), "foo"); } #[test] fn utf8_error() { let mut out = StringWriter::new(); let res = out.write_all(&[0, 255]); assert!(res.is_err()); } }