ascii_table-4.0.7/.cargo_vcs_info.json0000644000000001360000000000100133030ustar { "git": { "sha1": "f23678f2f9dc9a8fc7cdee29463d19c6364638c6" }, "path_in_vcs": "" }ascii_table-4.0.7/.gitignore000064400000000000000000000000521046102023000140600ustar 00000000000000/target **/*.rs.bk /Cargo.lock /mutants.* ascii_table-4.0.7/CHANGELOG.md000064400000000000000000000023151046102023000137050ustar 00000000000000# v4.0.7 (2025-05-14) - Replaced the crate `lazy_static` with `std::sync::LazyLock`. This change is only for the `color_codes` feature. # v4.0.6 (2025-02-25) - Bumped Rust edition to 2024. - Replaced `termion` with the more lightweight `termsize`. This will only affect the `auto_table_width` feature. # v4.0.5 (2024-11-20) - Bumped crate unicode-width from 0.1 to 0.2. # v4.0.4 (2024-08-28) - Bumped versions on 3rd party crates. # v4.0.3 (2023-08-28) - Added documentation to explain Ascii Table's features. - Some minor refactoring. # v4.0.2 (2022-01-25) - Created a new feature `auto_table_width`. It will set the default max width of your ascii table to the width of your terminal. Note that `AsciiTable::set_max_width` will override this value. - Changed ascii table default max width from 80 to 100. # v4.0.1 (2022-01-24) - Moved color code parsing to its own feature `color_codes`. Enable this feature when you want to display colors in the terminal using the `colorful` crate. - Created a new feature `wide_characters` who will use unicode rules to determine the width of strings. Use this feature when you want to display emoji's or other wide characters. # v4.0.0 (2022-01-23) - Revamped API. ascii_table-4.0.7/Cargo.lock0000644000000055000000000000100112560ustar # This file is automatically @generated by Cargo. # It is not intended for manual editing. version = 4 [[package]] name = "aho-corasick" version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" dependencies = [ "memchr", ] [[package]] name = "ascii_table" version = "4.0.7" dependencies = [ "colorful", "regex", "termsize", "unicode-width", ] [[package]] name = "colorful" version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ffb474a9c3219a8254ead020421ecf1b90427f29b55f6aae9a2471fa62c126ef" [[package]] name = "libc" version = "0.2.172" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa" [[package]] name = "memchr" version = "2.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" [[package]] name = "regex" version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" dependencies = [ "aho-corasick", "memchr", "regex-automata", "regex-syntax", ] [[package]] name = "regex-automata" version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" dependencies = [ "aho-corasick", "memchr", "regex-syntax", ] [[package]] name = "regex-syntax" version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" [[package]] name = "termsize" version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6f11ff5c25c172608d5b85e2fb43ee9a6d683a7f4ab7f96ae07b3d8b590368fd" dependencies = [ "libc", "winapi", ] [[package]] name = "unicode-width" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fc81956842c57dac11422a97c3b8195a1ff727f06e85c84ed2e8aa277c9a0fd" [[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" ascii_table-4.0.7/Cargo.toml0000644000000030110000000000100112740ustar # 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 = "2024" name = "ascii_table" version = "4.0.7" authors = ["Gerrit Viljoen "] build = false autolib = false autobins = false autoexamples = false autotests = false autobenches = false description = "Print ASCII tables to the terminal" homepage = "https://gitlab.com/d5b4b2/ascii-table" documentation = "https://docs.rs/ascii_table" readme = "readme.md" keywords = [ "ascii", "table", ] categories = ["command-line-interface"] license = "MIT" repository = "https://gitlab.com/d5b4b2/ascii-table" [package.metadata.cargo-all-features] allowlist = [ "auto_table_width", "color_codes", "wide_characters", ] [features] auto_table_width = ["termsize"] color_codes = ["regex"] wide_characters = ["unicode-width"] [lib] name = "ascii_table" path = "src/lib.rs" [[test]] name = "mod" path = "tests/mod.rs" [dependencies.regex] version = "1" optional = true [dependencies.termsize] version = "0.1" optional = true [dependencies.unicode-width] version = "0.2" optional = true [dev-dependencies.colorful] version = "0.3" ascii_table-4.0.7/Cargo.toml.orig000064400000000000000000000015031046102023000147610ustar 00000000000000[package] name = "ascii_table" version = "4.0.7" authors = ["Gerrit Viljoen "] license = "MIT" edition = "2024" description = "Print ASCII tables to the terminal" repository = "https://gitlab.com/d5b4b2/ascii-table" homepage = "https://gitlab.com/d5b4b2/ascii-table" documentation = "https://docs.rs/ascii_table" readme = "readme.md" categories = ["command-line-interface"] keywords = ["ascii", "table"] [dependencies] regex = { version = "1", optional = true } termsize = { version = "0.1", optional = true } unicode-width = { version = "0.2", optional = true } [dev-dependencies] colorful = "0.3" [features] auto_table_width = ["termsize"] color_codes = ["regex"] wide_characters = ["unicode-width"] [package.metadata.cargo-all-features] allowlist = ["auto_table_width", "color_codes", "wide_characters"] ascii_table-4.0.7/LICENSE000064400000000000000000000020421046102023000130760ustar 00000000000000Copyright (c) 2020 Gerrit Viljoen Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ascii_table-4.0.7/readme.md000064400000000000000000000031021046102023000136460ustar 00000000000000# ascii-table Print ASCII tables to the terminal. ## Example ``` use ascii_table::AsciiTable; let ascii_table = AsciiTable::default(); let data = vec![&[1, 2, 3], &[4, 5, 6], &[7, 8, 9]]; ascii_table.print(data); // ┌───┬───┬───┐ // │ 1 │ 2 │ 3 │ // │ 4 │ 5 │ 6 │ // │ 7 │ 8 │ 9 │ // └───┴───┴───┘ ``` ## Example ``` use std::fmt::Display; use ascii_table::{AsciiTable, Align}; let mut ascii_table = AsciiTable::default(); ascii_table.set_max_width(26); ascii_table.column(0).set_header("H1").set_align(Align::Left); ascii_table.column(1).set_header("H2").set_align(Align::Center); ascii_table.column(2).set_header("H3").set_align(Align::Right); let data: Vec> = vec![ vec![&'v', &'v', &'v'], vec![&123, &456, &789, &"abcdef"] ]; ascii_table.print(data); // ┌─────┬─────┬─────┬──────┐ // │ H1 │ H2 │ H3 │ │ // ├─────┼─────┼─────┼──────┤ // │ v │ v │ v │ │ // │ 123 │ 456 │ 789 │ abc+ │ // └─────┴─────┴─────┴──────┘ ``` ## Features - `auto_table_width`: Sets the default max width of the ascii table to the width of the terminal. - `color_codes`: Correctly calculates the width of a string when terminal color codes are present (like those from the `colorful` crate). - `wide_characters`: Correctly calculates the width of a string when wide characters are present (like emoji's). ascii_table-4.0.7/src/lib.rs000064400000000000000000000415371046102023000140100ustar 00000000000000//! Print ASCII tables to the terminal. //! //! # Example //! //! ``` //! use ascii_table::AsciiTable; //! //! let ascii_table = AsciiTable::default(); //! let data = vec![&[1, 2, 3], &[4, 5, 6], &[7, 8, 9]]; //! ascii_table.print(data); //! // ┌───┬───┬───┐ //! // │ 1 │ 2 │ 3 │ //! // │ 4 │ 5 │ 6 │ //! // │ 7 │ 8 │ 9 │ //! // └───┴───┴───┘ //! ``` //! //! # Example //! //! ``` //! use std::fmt::Display; //! use ascii_table::{AsciiTable, Align}; //! //! let mut ascii_table = AsciiTable::default(); //! ascii_table.set_max_width(26); //! ascii_table.column(0).set_header("H1").set_align(Align::Left); //! ascii_table.column(1).set_header("H2").set_align(Align::Center); //! ascii_table.column(2).set_header("H3").set_align(Align::Right); //! //! let data: Vec> = vec![ //! vec![&'v', &'v', &'v'], //! vec![&123, &456, &789, &"abcdef"] //! ]; //! ascii_table.print(data); //! // ┌─────┬─────┬─────┬──────┐ //! // │ H1 │ H2 │ H3 │ │ //! // ├─────┼─────┼─────┼──────┤ //! // │ v │ v │ v │ │ //! // │ 123 │ 456 │ 789 │ abc+ │ //! // └─────┴─────┴─────┴──────┘ //! ``` //! //! ## Features //! //! - `auto_table_width`: Sets the default max width of the ascii table to the width of the terminal. //! - `color_codes`: Correctly calculates the width of a string when terminal color codes are present //! (like those from the `colorful` crate). //! - `wide_characters`: Correctly calculates the width of a string when wide characters are present //! (like emoji's). #[cfg(test)] mod tests; #[cfg(feature = "color_codes")] use ::regex::Regex; #[cfg(feature = "wide_characters")] use ::unicode_width::UnicodeWidthStr; use ::std::collections::BTreeMap; use ::std::fmt::Display; #[cfg(feature = "color_codes")] use ::std::sync::LazyLock; const DEFAULT_WIDTH: usize = 100; const SE: &str = "┌"; const NW: &str = "┘"; const SW: &str = "┐"; const NS: &str = "│"; const NE: &str = "└"; const EWS: &str = "┬"; const NES: &str = "├"; const NWS: &str = "┤"; const NEW: &str = "┴"; const NEWS: &str = "┼"; const EW: &str = "─"; #[cfg(feature = "color_codes")] static COLOR_CODE_PARSER: LazyLock = LazyLock::new(|| Regex::new("\u{1b}\\[([0-9]+;)*[0-9]+m").expect("Regex compilation error")); #[derive(Clone, Debug, Default, Eq, PartialEq)] pub struct AsciiTable { max_width: Option, columns: BTreeMap, } impl AsciiTable { /// Sets the maximum width of the table. /// /// When you use the feature `auto_table_width` the maximum width will be calculated when you /// render the table. Note that the value set by this function will take precedence over the value /// generated by `auto_table_width`. pub fn set_max_width(&mut self, max_width: usize) -> &mut Self { self.max_width = Some(max_width); self } /// Gets the maximum width used to render tables. This is either the default width, the width calculated /// by the feature `auto_table_width` or the width specified by `set_max_width`. pub fn max_width(&self) -> usize { match self.max_width { Some(width) => width, None => default_table_width(), } } pub fn column(&mut self, index: usize) -> &mut Column { self.columns.entry(index).or_default() } } #[cfg(feature = "auto_table_width")] fn default_table_width() -> usize { ::termsize::get() .map(|size| size.cols.into()) .unwrap_or(DEFAULT_WIDTH) } #[cfg(not(feature = "auto_table_width"))] fn default_table_width() -> usize { DEFAULT_WIDTH } #[derive(Clone, Debug, Eq, PartialEq)] pub struct Column { header: String, align: Align, max_width: usize, } impl Column { /// Sets the value for the header row. When none of the table's columns has a header value then /// the header row wont be rendered. pub fn set_header(&mut self, header: T) -> &mut Self where T: Into, { self.header = header.into(); self } pub fn header(&self) -> &str { &self.header } pub fn set_align(&mut self, align: Align) -> &mut Self { self.align = align; self } pub fn align(&self) -> Align { self.align } /// Sets the maximum width of the content (rendered text) for this column. /// /// The maximum width of the table takes precedence over the maximum width of its columns. So you /// can't use the columns maximum width to extends the table beyond its maximum width. pub fn set_max_width(&mut self, max_width: usize) -> &mut Self { self.max_width = max_width; self } pub fn max_width(&self) -> usize { self.max_width } } impl Default for Column { fn default() -> Self { Column { header: Default::default(), align: Default::default(), max_width: usize::max_value(), } } } /// Alignment of text in a cell. #[derive(Clone, Copy, Debug, Default, Eq, PartialEq, Hash, Ord, PartialOrd)] pub enum Align { #[default] Left, Center, Right, } impl AsciiTable { pub fn print(&self, data: L1) where L1: IntoIterator, L2: IntoIterator, T: Display, { print!("{}", self.format(data)) } pub fn format(&self, data: L1) -> String where L1: IntoIterator, L2: IntoIterator, T: Display, { self.format_inner(self.stringify(data)) } fn format_inner(&self, data: Vec>) -> String { let num_cols = data.iter().map(|row| row.len()).max().unwrap_or(0); let table_max_width = self.max_width(); if !self.valid(&data, num_cols, table_max_width) { return self.format_empty(); } let header = self.stringify_header(num_cols); let data = self.square_data(data, num_cols); let has_header = header.iter().any(|text| !text.is_empty()); let widths = self.column_widths(&header, &data, table_max_width); let mut result = String::new(); result.push_str(&self.format_first(&widths)); if has_header { result.push_str(&self.format_header_row(&header, &widths)); result.push_str(&self.format_middle(&widths)); } for row in data { result.push_str(&self.format_row(&row, &widths)); } result.push_str(&self.format_last(&widths)); result } fn valid(&self, data: &Vec>, num_cols: usize, table_max_width: usize) -> bool { if data.len() == 0 { false } else if num_cols == 0 { false } else if table_max_width < Self::smallest_width(num_cols) { false } else { true } } fn smallest_width(num_cols: usize) -> usize { ((num_cols - 1) * 3) + 4 } fn stringify(&self, data: L1) -> Vec> where L1: IntoIterator, L2: IntoIterator, T: Display, { data.into_iter() .map(|row| { row.into_iter() .map(|cell| SmartString::from(cell.to_string())) .collect() }) .collect() } fn stringify_header(&self, num_cols: usize) -> Vec { (0..num_cols) .map(|n| { let value = self .columns .get(&n) .map(|column| column.header.as_str()) .unwrap_or(""); SmartString::from(value) }) .collect() } fn square_data( &self, mut data: Vec>, num_cols: usize, ) -> Vec> { for row in &mut data { while row.len() < num_cols { row.push(SmartString::new()); } } data } fn column_widths( &self, header: &[SmartString], data: &[Vec], table_max_width: usize, ) -> Vec { let default_conf = &Default::default(); let result: Vec<_> = (0..header.len()) .map(|n| { let conf = self.columns.get(&n).unwrap_or(default_conf); let column_width = data.iter().map(|row| row[n].width()).max().unwrap(); let header_width = header[n].width(); column_width.max(header_width).min(conf.max_width) }) .collect(); self.truncate_widths(result, table_max_width) } fn truncate_widths(&self, mut widths: Vec, table_max_width: usize) -> Vec { let table_padding = Self::smallest_width(widths.len()); while widths.iter().sum::() + table_padding > table_max_width && *widths.iter().max().unwrap() > 0 { let max = widths.iter().max().unwrap(); let idx = widths.iter().rposition(|x| x == max).unwrap(); widths[idx] -= 1; } widths } fn format_line(&self, row: &[SmartString], head: &str, delim: &str, tail: &str) -> String { let mut result = String::new(); result.push_str(head); for cell in row { result.push_str(&format!("{}{}", cell, delim)); } for _ in 0..delim.chars().count() { result.pop(); } result.push_str(tail); result.push('\n'); result } fn format_empty(&self) -> String { self.format_first(&vec![0]) + &self.format_line( &[SmartString::new()], &format!("{}{}", NS, ' '), &format!("{}{}{}", ' ', NS, ' '), &format!("{}{}", ' ', NS), ) + &self.format_last(&[0]) } fn format_first(&self, widths: &[usize]) -> String { let row: Vec<_> = widths .iter() .map(|&x| SmartString::from_visible(EW.repeat(x))) .collect(); self.format_line( &row, &format!("{}{}", SE, EW), &format!("{}{}{}", EW, EWS, EW), &format!("{}{}", EW, SW), ) } fn format_middle(&self, widths: &[usize]) -> String { let row: Vec<_> = widths .iter() .map(|&x| SmartString::from_visible(EW.repeat(x))) .collect(); self.format_line( &row, &format!("{}{}", NES, EW), &format!("{}{}{}", EW, NEWS, EW), &format!("{}{}", EW, NWS), ) } fn format_row(&self, row: &[SmartString], widths: &[usize]) -> String { let default_conf = &Default::default(); let row: Vec<_> = (0..widths.len()) .map(|a| { let cell = &row[a]; let width = widths[a]; let conf = self.columns.get(&a).unwrap_or(default_conf); self.format_cell(cell, width, ' ', conf.align) }) .collect(); self.format_line( &row, &format!("{}{}", NS, ' '), &format!("{}{}{}", ' ', NS, ' '), &format!("{}{}", ' ', NS), ) } fn format_header_row(&self, row: &[SmartString], widths: &[usize]) -> String { let row: Vec<_> = row .iter() .zip(widths.iter()) .map(|(cell, &width)| self.format_cell(cell, width, ' ', Align::Left)) .collect(); self.format_line( &row, &format!("{}{}", NS, ' '), &format!("{}{}{}", ' ', NS, ' '), &format!("{}{}", ' ', NS), ) } fn format_last(&self, widths: &[usize]) -> String { let row: Vec<_> = widths .iter() .map(|&x| SmartString::from_visible(EW.repeat(x))) .collect(); self.format_line( &row, &format!("{}{}", NE, EW), &format!("{}{}{}", EW, NEW, EW), &format!("{}{}", EW, NW), ) } fn format_cell(&self, text: &SmartString, len: usize, pad: char, align: Align) -> SmartString { if text.width() > len { let mut result = text.clone(); while result.width() > len { result.pop(); } if result.pop().is_some() { result.push_visible('+') } result } else { let mut result = text.clone(); match align { Align::Left => { while result.width() < len { result.push_visible(pad) } } Align::Right => { while result.width() < len { result.lpush_visible(pad) } } Align::Center => { while result.width() < len { result.push_visible(pad); if result.width() < len { result.lpush_visible(pad) } } } } result } } } #[derive(Clone, Debug)] struct SmartString { fragments: Vec, } #[derive(Clone, Debug)] struct SmartStringFragment { string: String, visible: bool, } impl SmartString { fn new() -> Self { Self { fragments: Vec::new(), } } #[cfg(feature = "color_codes")] fn from(string: T) -> Self where T: AsRef, { let string = string.as_ref(); let mut fragments = Vec::new(); let mut last = 0; for r#match in COLOR_CODE_PARSER.find_iter(string) { let start = r#match.start(); let end = r#match.end(); if last < start { fragments.push(SmartStringFragment::new(&string[last..start], true)); } fragments.push(SmartStringFragment::new(&string[start..end], false)); last = end; } if last < string.len() { fragments.push(SmartStringFragment::new(&string[last..], true)); } Self { fragments } } #[cfg(not(feature = "color_codes"))] fn from(string: T) -> Self where T: Into, { Self { fragments: vec![SmartStringFragment::new(string, true)], } } fn from_visible(string: T) -> Self where T: Into, { Self { fragments: vec![SmartStringFragment::new(string, true)], } } fn width(&self) -> usize { self.fragments .iter() .filter(|fragment| fragment.visible) .map(|fragment| fragment.width()) .sum() } fn is_empty(&self) -> bool { self.fragments .iter() .filter(|fragment| fragment.visible) .all(|fragment| fragment.string.is_empty()) } fn pop(&mut self) -> Option { self.fragments .iter_mut() .filter(|fragment| fragment.visible && !fragment.string.is_empty()) .last() .and_then(|fragment| fragment.string.pop()) } fn push_visible(&mut self, ch: char) { let last_fragment = self .fragments .iter_mut() .filter(|fragment| fragment.visible) .map(|fragment| &mut fragment.string) .last(); if let Some(fragment) = last_fragment { fragment.push(ch); } else { self.fragments.push(SmartStringFragment::new(ch, true)); } } fn lpush_visible(&mut self, ch: char) { let first_fragment = self .fragments .iter_mut() .filter(|fragment| fragment.visible) .map(|fragment| &mut fragment.string) .next(); if let Some(fragment) = first_fragment { fragment.insert(0, ch); } else { self.fragments.insert(0, SmartStringFragment::new(ch, true)); } } } impl Display for SmartString { fn fmt(&self, fmt: &mut ::std::fmt::Formatter) -> Result<(), ::std::fmt::Error> { self.fragments .iter() .try_for_each(|fragment| fragment.string.fmt(fmt)) } } impl SmartStringFragment { fn new(string: T, visible: bool) -> Self where T: Into, { Self { string: string.into(), visible, } } #[cfg(feature = "wide_characters")] fn width(&self) -> usize { UnicodeWidthStr::width(self.string.as_str()) } #[cfg(not(feature = "wide_characters"))] fn width(&self) -> usize { self.string.chars().count() } } ascii_table-4.0.7/src/tests.rs000064400000000000000000000514511046102023000144000ustar 00000000000000#[cfg(feature = "color_codes")] use ::colorful::{Color, Colorful}; use crate::Align::*; use crate::AsciiTable; use ::std::fmt::Display; fn cube_config() -> AsciiTable { let mut result = AsciiTable::default(); result.column(0).set_header("a"); result.column(1).set_header("b"); result.column(2).set_header("c"); result } #[test] fn empty_rows() { let config = AsciiTable::default(); let input: Vec> = vec![]; let expected = "┌──┐\n\ │ │\n\ └──┘\n"; assert_eq!(expected, config.format(input)); } #[test] fn empty_columns() { let config = AsciiTable::default(); let input: Vec> = vec![vec![]]; let expected = "┌──┐\n\ │ │\n\ └──┘\n"; assert_eq!(expected, config.format(input)); } #[test] fn cube_with_header() { let config = cube_config(); let input = vec![&[1, 2, 3], &[4, 5, 6], &[7, 8, 9]]; let expected = "┌───┬───┬───┐\n\ │ a │ b │ c │\n\ ├───┼───┼───┤\n\ │ 1 │ 2 │ 3 │\n\ │ 4 │ 5 │ 6 │\n\ │ 7 │ 8 │ 9 │\n\ └───┴───┴───┘\n"; assert_eq!(expected, config.format(input)); } #[test] fn cube_with_no_header() { let config = AsciiTable::default(); let input = vec![&[1, 2, 3], &[4, 5, 6], &[7, 8, 9]]; let expected = "┌───┬───┬───┐\n\ │ 1 │ 2 │ 3 │\n\ │ 4 │ 5 │ 6 │\n\ │ 7 │ 8 │ 9 │\n\ └───┴───┴───┘\n"; assert_eq!(expected, config.format(input)); } #[test] fn one_cell() { let config = AsciiTable::default(); let input = vec![&[1]]; let expected = "┌───┐\n\ │ 1 │\n\ └───┘\n"; assert_eq!(expected, config.format(input)); } #[test] fn smallest_cell() { let mut config = AsciiTable::default(); config.set_max_width(4); let input = vec![&[123]]; let expected = "┌──┐\n\ │ │\n\ └──┘\n"; assert_eq!(expected, config.format(input)); } #[test] fn smallest_cell2() { let mut config = AsciiTable::default(); config.column(0).set_max_width(0); let input = vec![&[123]]; let expected = "┌──┐\n\ │ │\n\ └──┘\n"; assert_eq!(expected, config.format(input)); } #[test] fn smallest_cube() { let mut config = AsciiTable::default(); config.set_max_width(10); let input = vec![&[1, 2, 3], &[4, 5, 6], &[7, 8, 9]]; let expected = "┌──┬──┬──┐\n\ │ │ │ │\n\ │ │ │ │\n\ │ │ │ │\n\ └──┴──┴──┘\n"; assert_eq!(expected, config.format(input)); } #[test] fn smallest_cube2() { let mut config = AsciiTable::default(); config.column(0).set_max_width(0); config.column(1).set_max_width(0); config.column(2).set_max_width(0); let input = vec![&[1, 2, 3], &[4, 5, 6], &[7, 8, 9]]; let expected = "┌──┬──┬──┐\n\ │ │ │ │\n\ │ │ │ │\n\ │ │ │ │\n\ └──┴──┴──┘\n"; assert_eq!(expected, config.format(input)); } #[test] fn show_no_content_for_cell() { let mut config = AsciiTable::default(); config.set_max_width(5); let input = vec![&[123]]; let expected = "┌───┐\n\ │ + │\n\ └───┘\n"; assert_eq!(expected, config.format(input)); } #[test] fn show_no_content_for_cell2() { let mut config = AsciiTable::default(); config.column(0).set_max_width(1); let input = vec![&[123]]; let expected = "┌───┐\n\ │ + │\n\ └───┘\n"; assert_eq!(expected, config.format(input)); } #[test] fn show_one_character_for_cell() { let mut config = AsciiTable::default(); config.set_max_width(6); let input = vec![&[123]]; let expected = "┌────┐\n\ │ 1+ │\n\ └────┘\n"; assert_eq!(expected, config.format(input)); } #[test] fn show_one_character_for_cell2() { let mut config = AsciiTable::default(); config.column(0).set_max_width(2); let input = vec![&[123]]; let expected = "┌────┐\n\ │ 1+ │\n\ └────┘\n"; assert_eq!(expected, config.format(input)); } #[test] fn smallest_cell_with_header() { let mut config = AsciiTable::default(); config.set_max_width(4); config.column(0).set_header("foo"); let input = vec![&[123]]; let expected = "┌──┐\n\ │ │\n\ ├──┤\n\ │ │\n\ └──┘\n"; assert_eq!(expected, config.format(input)); } #[test] fn smallest_cell_with_header2() { let mut config = AsciiTable::default(); config.column(0).set_max_width(0).set_header("foo"); let input = vec![&[123]]; let expected = "┌──┐\n\ │ │\n\ ├──┤\n\ │ │\n\ └──┘\n"; assert_eq!(expected, config.format(input)); } #[test] fn smallest_cube_with_header() { let mut config = AsciiTable::default(); config.set_max_width(10); config.column(0).set_header("abc"); config.column(1).set_header("def"); config.column(2).set_header("ghi"); let input = vec![&[1, 2, 3], &[4, 5, 6], &[7, 8, 9]]; let expected = "┌──┬──┬──┐\n\ │ │ │ │\n\ ├──┼──┼──┤\n\ │ │ │ │\n\ │ │ │ │\n\ │ │ │ │\n\ └──┴──┴──┘\n"; assert_eq!(expected, config.format(input)); } #[test] fn smallest_cube_with_header2() { let mut config = AsciiTable::default(); config.column(0).set_header("abc").set_max_width(0); config.column(1).set_header("def").set_max_width(0); config.column(2).set_header("ghi").set_max_width(0); let input = vec![&[1, 2, 3], &[4, 5, 6], &[7, 8, 9]]; let expected = "┌──┬──┬──┐\n\ │ │ │ │\n\ ├──┼──┼──┤\n\ │ │ │ │\n\ │ │ │ │\n\ │ │ │ │\n\ └──┴──┴──┘\n"; assert_eq!(expected, config.format(input)); } #[test] fn show_no_content_for_header() { let mut config = AsciiTable::default(); config.set_max_width(5); config.column(0).set_header("abc"); let input = vec![&[""]]; let expected = "┌───┐\n\ │ + │\n\ ├───┤\n\ │ │\n\ └───┘\n"; assert_eq!(expected, config.format(input)); } #[test] fn show_no_content_for_header2() { let mut config = AsciiTable::default(); config.column(0).set_header("abc").set_max_width(1); let input = vec![&[""]]; let expected = "┌───┐\n\ │ + │\n\ ├───┤\n\ │ │\n\ └───┘\n"; assert_eq!(expected, config.format(input)); } #[test] fn show_one_character_for_header() { let mut config = AsciiTable::default(); config.set_max_width(6); config.column(0).set_header("abc"); let input = vec![&[""]]; let expected = "┌────┐\n\ │ a+ │\n\ ├────┤\n\ │ │\n\ └────┘\n"; assert_eq!(expected, config.format(input)); } #[test] fn show_one_character_for_header2() { let mut config = AsciiTable::default(); config.column(0).set_header("abc").set_max_width(2); let input = vec![&[""]]; let expected = "┌────┐\n\ │ a+ │\n\ ├────┤\n\ │ │\n\ └────┘\n"; assert_eq!(expected, config.format(input)); } #[test] fn cube_with_partial_content() { let config = cube_config(); let input: Vec<&[i32]> = vec![&[1, 2, 3], &[4, 5], &[7]]; let expected = "┌───┬───┬───┐\n\ │ a │ b │ c │\n\ ├───┼───┼───┤\n\ │ 1 │ 2 │ 3 │\n\ │ 4 │ 5 │ │\n\ │ 7 │ │ │\n\ └───┴───┴───┘\n"; assert_eq!(expected, config.format(input)); } #[test] fn cube_with_partial_content_reversed() { let config = cube_config(); let input: Vec<&[i32]> = vec![&[1], &[4, 5], &[7, 8, 9]]; let expected = "┌───┬───┬───┐\n\ │ a │ b │ c │\n\ ├───┼───┼───┤\n\ │ 1 │ │ │\n\ │ 4 │ 5 │ │\n\ │ 7 │ 8 │ 9 │\n\ └───┴───┴───┘\n"; assert_eq!(expected, config.format(input)); } #[test] fn resize_column() { let config = cube_config(); let input = vec![&[1], &[23], &[456]]; let expected = "┌─────┐\n\ │ a │\n\ ├─────┤\n\ │ 1 │\n\ │ 23 │\n\ │ 456 │\n\ └─────┘\n"; assert_eq!(expected, config.format(input)); } #[test] fn resize_column_reversed() { let config = cube_config(); let input = vec![&[123], &[45], &[6]]; let expected = "┌─────┐\n\ │ a │\n\ ├─────┤\n\ │ 123 │\n\ │ 45 │\n\ │ 6 │\n\ └─────┘\n"; assert_eq!(expected, config.format(input)); } #[test] fn resize_column_via_header() { let mut config = AsciiTable::default(); config.column(0).set_header("foo"); let input = vec![&[1], &[2], &[3]]; let expected = "┌─────┐\n\ │ foo │\n\ ├─────┤\n\ │ 1 │\n\ │ 2 │\n\ │ 3 │\n\ └─────┘\n"; assert_eq!(expected, config.format(input)); } #[test] fn partial_header_at_start() { let config = cube_config(); let input = vec![&[1, 2, 3, 0], &[4, 5, 6, 0], &[7, 8, 9, 0]]; let expected = "┌───┬───┬───┬───┐\n\ │ a │ b │ c │ │\n\ ├───┼───┼───┼───┤\n\ │ 1 │ 2 │ 3 │ 0 │\n\ │ 4 │ 5 │ 6 │ 0 │\n\ │ 7 │ 8 │ 9 │ 0 │\n\ └───┴───┴───┴───┘\n"; assert_eq!(expected, config.format(input)); } #[test] fn partial_header_at_end() { let mut config = AsciiTable::default(); config.column(2).set_header("c"); let input = vec![&[1, 2, 3], &[4, 5, 6], &[7, 8, 9]]; let expected = "┌───┬───┬───┐\n\ │ │ │ c │\n\ ├───┼───┼───┤\n\ │ 1 │ 2 │ 3 │\n\ │ 4 │ 5 │ 6 │\n\ │ 7 │ 8 │ 9 │\n\ └───┴───┴───┘\n"; assert_eq!(expected, config.format(input)); } #[test] fn ignore_unused_header() { let config = cube_config(); let input = vec![&[1], &[2], &[3]]; let expected = "┌───┐\n\ │ a │\n\ ├───┤\n\ │ 1 │\n\ │ 2 │\n\ │ 3 │\n\ └───┘\n"; assert_eq!(expected, config.format(input)); } #[test] fn align_right() { let mut config = AsciiTable::default(); config.column(0).set_header("a").set_align(Right); let input = vec![&[1], &[23], &[456]]; let expected = "┌─────┐\n\ │ a │\n\ ├─────┤\n\ │ 1 │\n\ │ 23 │\n\ │ 456 │\n\ └─────┘\n"; assert_eq!(expected, config.format(input)); } #[test] fn align_center() { let mut config = AsciiTable::default(); config.column(0).set_header("a").set_align(Center); let input = vec![&[1], &[23], &[456], &[7890], &[12345]]; let expected = "┌───────┐\n\ │ a │\n\ ├───────┤\n\ │ 1 │\n\ │ 23 │\n\ │ 456 │\n\ │ 7890 │\n\ │ 12345 │\n\ └───────┘\n"; assert_eq!(expected, config.format(input)); } #[test] fn mixed_types() { let config = cube_config(); let input: Vec> = vec![ vec![&1, &'2', &"3"], vec![&"4", &5, &'6'], vec![&'7', &"8", &9], ]; let expected = "┌───┬───┬───┐\n\ │ a │ b │ c │\n\ ├───┼───┼───┤\n\ │ 1 │ 2 │ 3 │\n\ │ 4 │ 5 │ 6 │\n\ │ 7 │ 8 │ 9 │\n\ └───┴───┴───┘\n"; assert_eq!(expected, config.format(input)); } #[cfg(feature = "color_codes")] #[test] fn color_codes_zero() { let config = AsciiTable::default(); let input = vec![vec!["\u{1b}[0mHello\u{1b}[0m"]]; let expected = "┌───────┐\n\ │ \u{1b}[0mHello\u{1b}[0m │\n\ └───────┘\n"; assert_eq!(expected, config.format(input)); } #[cfg(feature = "color_codes")] #[test] fn color_codes_zero_inbetween() { let config = AsciiTable::default(); let input = vec![vec!["He\u{1b}[0ml\u{1b}[0mlo"]]; let expected = "┌───────┐\n\ │ He\u{1b}[0ml\u{1b}[0mlo │\n\ └───────┘\n"; assert_eq!(expected, config.format(input)); } #[cfg(feature = "color_codes")] #[test] fn color_codes_m5() { let config = AsciiTable::default(); let input = vec![vec![ "mmmmm".color(Color::Blue).bg_color(Color::Yellow).bold(), ]]; let expected = "┌───────┐\n\ │ \u{1b}[38;5;4m\u{1b}[48;5;3;1mmmmmm\u{1b}[0m │\n\ └───────┘\n"; assert_eq!(expected, config.format(input)); } #[cfg(feature = "color_codes")] #[test] fn color_codes_b5() { let config = AsciiTable::default(); let input = vec![vec![ "[[[[[".color(Color::Blue).bg_color(Color::Yellow).bold(), ]]; let expected = "┌───────┐\n\ │ \u{1b}[38;5;4m\u{1b}[48;5;3;1m[[[[[\u{1b}[0m │\n\ └───────┘\n"; assert_eq!(expected, config.format(input)); } #[cfg(feature = "color_codes")] #[test] fn color_codes_s5() { let config = AsciiTable::default(); let input = vec![vec![ ";;;;;".color(Color::Blue).bg_color(Color::Yellow).bold(), ]]; let expected = "┌───────┐\n\ │ \u{1b}[38;5;4m\u{1b}[48;5;3;1m;;;;;\u{1b}[0m │\n\ └───────┘\n"; assert_eq!(expected, config.format(input)); } #[cfg(feature = "color_codes")] #[test] fn color_codes_n5() { let config = AsciiTable::default(); let input = vec![vec![ "00000".color(Color::Blue).bg_color(Color::Yellow).bold(), ]]; let expected = "┌───────┐\n\ │ \u{1b}[38;5;4m\u{1b}[48;5;3;1m00000\u{1b}[0m │\n\ └───────┘\n"; assert_eq!(expected, config.format(input)); } #[cfg(feature = "color_codes")] #[test] fn color_codes_missing_m() { let config = AsciiTable::default(); let input = vec![vec!["\u{1b}[0Hello\u{1b}[0"]]; let expected = "┌─────────────┐\n\ │ \u{1b}[0Hello\u{1b}[0 │\n\ └─────────────┘\n"; assert_eq!(expected, config.format(input)); } #[cfg(feature = "color_codes")] #[test] fn color_codes() { let config = AsciiTable::default(); let input = vec![ vec!["Hello".color(Color::Blue).bg_color(Color::Yellow).bold()], vec!["Hello".gradient(Color::Red)], ]; let expected = "┌───────┐\n\ │ \u{1b}[38;5;4m\u{1b}[48;5;3;1mHello\u{1b}[0m │\n\ │ \u{1b}[38;2;255;0;0mH\u{1b}[38;2;255;6;0me\u{1b}[38;2;255;13;0ml\u{1b}[38;2;255;19;0ml\u{1b}[38;2;255;26;0mo\u{1b}[0m │\n\ └───────┘\n"; assert_eq!(expected, config.format(input)); } #[cfg(feature = "color_codes")] #[test] fn color_codes_in_header() { let mut config = AsciiTable::default(); let text = "Hello".color(Color::Blue).bg_color(Color::Yellow).bold(); config.column(0).set_header(text.to_string()); let input = vec![&[""]]; let expected = "┌───────┐\n\ │ \u{1b}[38;5;4m\u{1b}[48;5;3;1mHello\u{1b}[0m │\n\ ├───────┤\n\ │ │\n\ └───────┘\n"; assert_eq!(expected, config.format(input)); } #[cfg(feature = "color_codes")] #[test] fn color_codes_pad_right() { let config = AsciiTable::default(); let input = vec![ vec!["Hello".color(Color::Blue).bg_color(Color::Yellow).bold()], vec!["H".color(Color::Blue).bg_color(Color::Yellow).bold()], ]; let expected = "┌───────┐\n\ │ \u{1b}[38;5;4m\u{1b}[48;5;3;1mHello\u{1b}[0m │\n\ │ \u{1b}[38;5;4m\u{1b}[48;5;3;1mH \u{1b}[0m │\n\ └───────┘\n"; assert_eq!(expected, config.format(input)); } #[cfg(feature = "color_codes")] #[test] fn color_codes_pad_left() { let mut config = AsciiTable::default(); config.column(0).set_align(Right); let input = vec![ vec!["Hello".color(Color::Blue).bg_color(Color::Yellow).bold()], vec!["H".color(Color::Blue).bg_color(Color::Yellow).bold()], ]; let expected = "┌───────┐\n\ │ \u{1b}[38;5;4m\u{1b}[48;5;3;1mHello\u{1b}[0m │\n\ │ \u{1b}[38;5;4m\u{1b}[48;5;3;1m H\u{1b}[0m │\n\ └───────┘\n"; assert_eq!(expected, config.format(input)); } #[cfg(feature = "color_codes")] #[test] fn color_codes_trunc() { let mut config = AsciiTable::default(); config.column(0).set_max_width(2); let input = vec![ vec!["Hello".color(Color::Blue).bg_color(Color::Yellow).bold()], vec!["H".color(Color::Blue).bg_color(Color::Yellow).bold()], ]; let expected = "┌────┐\n\ │ \u{1b}[38;5;4m\u{1b}[48;5;3;1mH+\u{1b}[0m │\n\ │ \u{1b}[38;5;4m\u{1b}[48;5;3;1mH \u{1b}[0m │\n\ └────┘\n"; assert_eq!(expected, config.format(input)); } #[cfg(feature = "wide_characters")] #[test] fn wide_characters() { let config = AsciiTable::default(); let input = vec![vec!["👩"]]; let expected = "┌────┐\n\ │ 👩 │\n\ └────┘\n"; assert_eq!(expected, config.format(input)); } #[cfg(feature = "wide_characters")] #[test] fn wide_characters2() { let config = AsciiTable::default(); let input = vec![vec!["👩🔬"]]; let expected = "┌──────┐\n\ │ 👩🔬 │\n\ └──────┘\n"; assert_eq!(expected, config.format(input)); } ascii_table-4.0.7/tests/mod.rs000064400000000000000000000024261046102023000143660ustar 00000000000000use ::ascii_table::{Align, AsciiTable, Column}; use ::std::fmt::Display; #[test] fn protect_public_api() { let mut table = AsciiTable::default(); let _: &mut AsciiTable = table.set_max_width(10usize); let _: usize = table.max_width(); let _: &mut Column = table.column(0usize).set_header("HEADER"); let _: &mut Column = table.column(0usize).set_header("HEADER".to_string()); let _: &str = table.column(0usize).header(); let _: &mut Column = table.column(0usize).set_align(Align::Left); let _: Align = table.column(0usize).align(); let _: &mut Column = table.column(0usize).set_max_width(10usize); let _: usize = table.column(0usize).max_width(); table.print(Vec::>::new()); table.print(Vec::>::new()); table.print(Vec::>::new()); table.print(Vec::<&[&dyn Display]>::new()); table.print(Vec::<&[String]>::new()); table.print(Vec::<&[&str]>::new()); table.print(<&[Vec<&dyn Display>]>::default()); table.print(<&[Vec]>::default()); table.print(<&[Vec<&str>]>::default()); let _ = table.clone(); let _ = table == table; println!("{table:?}"); match Align::Left { Align::Left => (), Align::Center => (), Align::Right => (), }; }