From 1b6a04ca5504955c571d1c97504fb45ea0befee4 Mon Sep 17 00:00:00 2001 From: Valentin Popov Date: Mon, 8 Jan 2024 01:21:28 +0400 Subject: Initial vendor packages Signed-off-by: Valentin Popov --- vendor/console/src/utils.rs | 962 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 962 insertions(+) create mode 100644 vendor/console/src/utils.rs (limited to 'vendor/console/src/utils.rs') diff --git a/vendor/console/src/utils.rs b/vendor/console/src/utils.rs new file mode 100644 index 0000000..9e6b942 --- /dev/null +++ b/vendor/console/src/utils.rs @@ -0,0 +1,962 @@ +use std::borrow::Cow; +use std::collections::BTreeSet; +use std::env; +use std::fmt; +use std::sync::atomic::{AtomicBool, Ordering}; + +use lazy_static::lazy_static; + +use crate::term::{wants_emoji, Term}; + +#[cfg(feature = "ansi-parsing")] +use crate::ansi::{strip_ansi_codes, AnsiCodeIterator}; + +#[cfg(not(feature = "ansi-parsing"))] +fn strip_ansi_codes(s: &str) -> &str { + s +} + +fn default_colors_enabled(out: &Term) -> bool { + (out.features().colors_supported() + && &env::var("CLICOLOR").unwrap_or_else(|_| "1".into()) != "0") + || &env::var("CLICOLOR_FORCE").unwrap_or_else(|_| "0".into()) != "0" +} + +lazy_static! { + static ref STDOUT_COLORS: AtomicBool = AtomicBool::new(default_colors_enabled(&Term::stdout())); + static ref STDERR_COLORS: AtomicBool = AtomicBool::new(default_colors_enabled(&Term::stderr())); +} + +/// Returns `true` if colors should be enabled for stdout. +/// +/// This honors the [clicolors spec](http://bixense.com/clicolors/). +/// +/// * `CLICOLOR != 0`: ANSI colors are supported and should be used when the program isn't piped. +/// * `CLICOLOR == 0`: Don't output ANSI color escape codes. +/// * `CLICOLOR_FORCE != 0`: ANSI colors should be enabled no matter what. +#[inline] +pub fn colors_enabled() -> bool { + STDOUT_COLORS.load(Ordering::Relaxed) +} + +/// Forces colorization on or off for stdout. +/// +/// This overrides the default for the current process and changes the return value of the +/// `colors_enabled` function. +#[inline] +pub fn set_colors_enabled(val: bool) { + STDOUT_COLORS.store(val, Ordering::Relaxed) +} + +/// Returns `true` if colors should be enabled for stderr. +/// +/// This honors the [clicolors spec](http://bixense.com/clicolors/). +/// +/// * `CLICOLOR != 0`: ANSI colors are supported and should be used when the program isn't piped. +/// * `CLICOLOR == 0`: Don't output ANSI color escape codes. +/// * `CLICOLOR_FORCE != 0`: ANSI colors should be enabled no matter what. +#[inline] +pub fn colors_enabled_stderr() -> bool { + STDERR_COLORS.load(Ordering::Relaxed) +} + +/// Forces colorization on or off for stderr. +/// +/// This overrides the default for the current process and changes the return value of the +/// `colors_enabled` function. +#[inline] +pub fn set_colors_enabled_stderr(val: bool) { + STDERR_COLORS.store(val, Ordering::Relaxed) +} + +/// Measure the width of a string in terminal characters. +pub fn measure_text_width(s: &str) -> usize { + str_width(&strip_ansi_codes(s)) +} + +/// A terminal color. +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub enum Color { + Black, + Red, + Green, + Yellow, + Blue, + Magenta, + Cyan, + White, + Color256(u8), +} + +impl Color { + #[inline] + fn ansi_num(self) -> usize { + match self { + Color::Black => 0, + Color::Red => 1, + Color::Green => 2, + Color::Yellow => 3, + Color::Blue => 4, + Color::Magenta => 5, + Color::Cyan => 6, + Color::White => 7, + Color::Color256(x) => x as usize, + } + } + + #[inline] + fn is_color256(self) -> bool { + #[allow(clippy::match_like_matches_macro)] + match self { + Color::Color256(_) => true, + _ => false, + } + } +} + +/// A terminal style attribute. +#[derive(Copy, Clone, Debug, PartialEq, Eq, Ord, PartialOrd)] +pub enum Attribute { + Bold, + Dim, + Italic, + Underlined, + Blink, + BlinkFast, + Reverse, + Hidden, + StrikeThrough, +} + +impl Attribute { + #[inline] + fn ansi_num(self) -> usize { + match self { + Attribute::Bold => 1, + Attribute::Dim => 2, + Attribute::Italic => 3, + Attribute::Underlined => 4, + Attribute::Blink => 5, + Attribute::BlinkFast => 6, + Attribute::Reverse => 7, + Attribute::Hidden => 8, + Attribute::StrikeThrough => 9, + } + } +} + +/// Defines the alignment for padding operations. +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub enum Alignment { + Left, + Center, + Right, +} + +/// A stored style that can be applied. +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct Style { + fg: Option, + bg: Option, + fg_bright: bool, + bg_bright: bool, + attrs: BTreeSet, + force: Option, + for_stderr: bool, +} + +impl Default for Style { + fn default() -> Style { + Style::new() + } +} + +impl Style { + /// Returns an empty default style. + pub fn new() -> Style { + Style { + fg: None, + bg: None, + fg_bright: false, + bg_bright: false, + attrs: BTreeSet::new(), + force: None, + for_stderr: false, + } + } + + /// Creates a style from a dotted string. + /// + /// Effectively the string is split at each dot and then the + /// terms in between are applied. For instance `red.on_blue` will + /// create a string that is red on blue background. `9.on_12` is + /// the same, but using 256 color numbers. Unknown terms are + /// ignored. + pub fn from_dotted_str(s: &str) -> Style { + let mut rv = Style::new(); + for part in s.split('.') { + rv = match part { + "black" => rv.black(), + "red" => rv.red(), + "green" => rv.green(), + "yellow" => rv.yellow(), + "blue" => rv.blue(), + "magenta" => rv.magenta(), + "cyan" => rv.cyan(), + "white" => rv.white(), + "bright" => rv.bright(), + "on_black" => rv.on_black(), + "on_red" => rv.on_red(), + "on_green" => rv.on_green(), + "on_yellow" => rv.on_yellow(), + "on_blue" => rv.on_blue(), + "on_magenta" => rv.on_magenta(), + "on_cyan" => rv.on_cyan(), + "on_white" => rv.on_white(), + "on_bright" => rv.on_bright(), + "bold" => rv.bold(), + "dim" => rv.dim(), + "underlined" => rv.underlined(), + "blink" => rv.blink(), + "blink_fast" => rv.blink_fast(), + "reverse" => rv.reverse(), + "hidden" => rv.hidden(), + "strikethrough" => rv.strikethrough(), + on_c if on_c.starts_with("on_") => { + if let Ok(n) = on_c[3..].parse::() { + rv.on_color256(n) + } else { + continue; + } + } + c => { + if let Ok(n) = c.parse::() { + rv.color256(n) + } else { + continue; + } + } + }; + } + rv + } + + /// Apply the style to something that can be displayed. + pub fn apply_to(&self, val: D) -> StyledObject { + StyledObject { + style: self.clone(), + val, + } + } + + /// Forces styling on or off. + /// + /// This overrides the automatic detection. + #[inline] + pub fn force_styling(mut self, value: bool) -> Style { + self.force = Some(value); + self + } + + /// Specifies that style is applying to something being written on stderr. + #[inline] + pub fn for_stderr(mut self) -> Style { + self.for_stderr = true; + self + } + + /// Specifies that style is applying to something being written on stdout. + /// + /// This is the default behaviour. + #[inline] + pub fn for_stdout(mut self) -> Style { + self.for_stderr = false; + self + } + + /// Sets a foreground color. + #[inline] + pub fn fg(mut self, color: Color) -> Style { + self.fg = Some(color); + self + } + + /// Sets a background color. + #[inline] + pub fn bg(mut self, color: Color) -> Style { + self.bg = Some(color); + self + } + + /// Adds a attr. + #[inline] + pub fn attr(mut self, attr: Attribute) -> Style { + self.attrs.insert(attr); + self + } + + #[inline] + pub fn black(self) -> Style { + self.fg(Color::Black) + } + #[inline] + pub fn red(self) -> Style { + self.fg(Color::Red) + } + #[inline] + pub fn green(self) -> Style { + self.fg(Color::Green) + } + #[inline] + pub fn yellow(self) -> Style { + self.fg(Color::Yellow) + } + #[inline] + pub fn blue(self) -> Style { + self.fg(Color::Blue) + } + #[inline] + pub fn magenta(self) -> Style { + self.fg(Color::Magenta) + } + #[inline] + pub fn cyan(self) -> Style { + self.fg(Color::Cyan) + } + #[inline] + pub fn white(self) -> Style { + self.fg(Color::White) + } + #[inline] + pub fn color256(self, color: u8) -> Style { + self.fg(Color::Color256(color)) + } + + #[inline] + pub fn bright(mut self) -> Style { + self.fg_bright = true; + self + } + + #[inline] + pub fn on_black(self) -> Style { + self.bg(Color::Black) + } + #[inline] + pub fn on_red(self) -> Style { + self.bg(Color::Red) + } + #[inline] + pub fn on_green(self) -> Style { + self.bg(Color::Green) + } + #[inline] + pub fn on_yellow(self) -> Style { + self.bg(Color::Yellow) + } + #[inline] + pub fn on_blue(self) -> Style { + self.bg(Color::Blue) + } + #[inline] + pub fn on_magenta(self) -> Style { + self.bg(Color::Magenta) + } + #[inline] + pub fn on_cyan(self) -> Style { + self.bg(Color::Cyan) + } + #[inline] + pub fn on_white(self) -> Style { + self.bg(Color::White) + } + #[inline] + pub fn on_color256(self, color: u8) -> Style { + self.bg(Color::Color256(color)) + } + + #[inline] + pub fn on_bright(mut self) -> Style { + self.bg_bright = true; + self + } + + #[inline] + pub fn bold(self) -> Style { + self.attr(Attribute::Bold) + } + #[inline] + pub fn dim(self) -> Style { + self.attr(Attribute::Dim) + } + #[inline] + pub fn italic(self) -> Style { + self.attr(Attribute::Italic) + } + #[inline] + pub fn underlined(self) -> Style { + self.attr(Attribute::Underlined) + } + #[inline] + pub fn blink(self) -> Style { + self.attr(Attribute::Blink) + } + #[inline] + pub fn blink_fast(self) -> Style { + self.attr(Attribute::BlinkFast) + } + #[inline] + pub fn reverse(self) -> Style { + self.attr(Attribute::Reverse) + } + #[inline] + pub fn hidden(self) -> Style { + self.attr(Attribute::Hidden) + } + #[inline] + pub fn strikethrough(self) -> Style { + self.attr(Attribute::StrikeThrough) + } +} + +/// Wraps an object for formatting for styling. +/// +/// Example: +/// +/// ```rust,no_run +/// # use console::style; +/// format!("Hello {}", style("World").cyan()); +/// ``` +/// +/// This is a shortcut for making a new style and applying it +/// to a value: +/// +/// ```rust,no_run +/// # use console::Style; +/// format!("Hello {}", Style::new().cyan().apply_to("World")); +/// ``` +pub fn style(val: D) -> StyledObject { + Style::new().apply_to(val) +} + +/// A formatting wrapper that can be styled for a terminal. +#[derive(Clone)] +pub struct StyledObject { + style: Style, + val: D, +} + +impl StyledObject { + /// Forces styling on or off. + /// + /// This overrides the automatic detection. + #[inline] + pub fn force_styling(mut self, value: bool) -> StyledObject { + self.style = self.style.force_styling(value); + self + } + + /// Specifies that style is applying to something being written on stderr + #[inline] + pub fn for_stderr(mut self) -> StyledObject { + self.style = self.style.for_stderr(); + self + } + + /// Specifies that style is applying to something being written on stdout + /// + /// This is the default + #[inline] + pub fn for_stdout(mut self) -> StyledObject { + self.style = self.style.for_stdout(); + self + } + + /// Sets a foreground color. + #[inline] + pub fn fg(mut self, color: Color) -> StyledObject { + self.style = self.style.fg(color); + self + } + + /// Sets a background color. + #[inline] + pub fn bg(mut self, color: Color) -> StyledObject { + self.style = self.style.bg(color); + self + } + + /// Adds a attr. + #[inline] + pub fn attr(mut self, attr: Attribute) -> StyledObject { + self.style = self.style.attr(attr); + self + } + + #[inline] + pub fn black(self) -> StyledObject { + self.fg(Color::Black) + } + #[inline] + pub fn red(self) -> StyledObject { + self.fg(Color::Red) + } + #[inline] + pub fn green(self) -> StyledObject { + self.fg(Color::Green) + } + #[inline] + pub fn yellow(self) -> StyledObject { + self.fg(Color::Yellow) + } + #[inline] + pub fn blue(self) -> StyledObject { + self.fg(Color::Blue) + } + #[inline] + pub fn magenta(self) -> StyledObject { + self.fg(Color::Magenta) + } + #[inline] + pub fn cyan(self) -> StyledObject { + self.fg(Color::Cyan) + } + #[inline] + pub fn white(self) -> StyledObject { + self.fg(Color::White) + } + #[inline] + pub fn color256(self, color: u8) -> StyledObject { + self.fg(Color::Color256(color)) + } + + #[inline] + pub fn bright(mut self) -> StyledObject { + self.style = self.style.bright(); + self + } + + #[inline] + pub fn on_black(self) -> StyledObject { + self.bg(Color::Black) + } + #[inline] + pub fn on_red(self) -> StyledObject { + self.bg(Color::Red) + } + #[inline] + pub fn on_green(self) -> StyledObject { + self.bg(Color::Green) + } + #[inline] + pub fn on_yellow(self) -> StyledObject { + self.bg(Color::Yellow) + } + #[inline] + pub fn on_blue(self) -> StyledObject { + self.bg(Color::Blue) + } + #[inline] + pub fn on_magenta(self) -> StyledObject { + self.bg(Color::Magenta) + } + #[inline] + pub fn on_cyan(self) -> StyledObject { + self.bg(Color::Cyan) + } + #[inline] + pub fn on_white(self) -> StyledObject { + self.bg(Color::White) + } + #[inline] + pub fn on_color256(self, color: u8) -> StyledObject { + self.bg(Color::Color256(color)) + } + + #[inline] + pub fn on_bright(mut self) -> StyledObject { + self.style = self.style.on_bright(); + self + } + + #[inline] + pub fn bold(self) -> StyledObject { + self.attr(Attribute::Bold) + } + #[inline] + pub fn dim(self) -> StyledObject { + self.attr(Attribute::Dim) + } + #[inline] + pub fn italic(self) -> StyledObject { + self.attr(Attribute::Italic) + } + #[inline] + pub fn underlined(self) -> StyledObject { + self.attr(Attribute::Underlined) + } + #[inline] + pub fn blink(self) -> StyledObject { + self.attr(Attribute::Blink) + } + #[inline] + pub fn blink_fast(self) -> StyledObject { + self.attr(Attribute::BlinkFast) + } + #[inline] + pub fn reverse(self) -> StyledObject { + self.attr(Attribute::Reverse) + } + #[inline] + pub fn hidden(self) -> StyledObject { + self.attr(Attribute::Hidden) + } + #[inline] + pub fn strikethrough(self) -> StyledObject { + self.attr(Attribute::StrikeThrough) + } +} + +macro_rules! impl_fmt { + ($name:ident) => { + impl fmt::$name for StyledObject { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let mut reset = false; + if self + .style + .force + .unwrap_or_else(|| match self.style.for_stderr { + true => colors_enabled_stderr(), + false => colors_enabled(), + }) + { + if let Some(fg) = self.style.fg { + if fg.is_color256() { + write!(f, "\x1b[38;5;{}m", fg.ansi_num())?; + } else if self.style.fg_bright { + write!(f, "\x1b[38;5;{}m", fg.ansi_num() + 8)?; + } else { + write!(f, "\x1b[{}m", fg.ansi_num() + 30)?; + } + reset = true; + } + if let Some(bg) = self.style.bg { + if bg.is_color256() { + write!(f, "\x1b[48;5;{}m", bg.ansi_num())?; + } else if self.style.bg_bright { + write!(f, "\x1b[48;5;{}m", bg.ansi_num() + 8)?; + } else { + write!(f, "\x1b[{}m", bg.ansi_num() + 40)?; + } + reset = true; + } + for attr in &self.style.attrs { + write!(f, "\x1b[{}m", attr.ansi_num())?; + reset = true; + } + } + fmt::$name::fmt(&self.val, f)?; + if reset { + write!(f, "\x1b[0m")?; + } + Ok(()) + } + } + }; +} + +impl_fmt!(Binary); +impl_fmt!(Debug); +impl_fmt!(Display); +impl_fmt!(LowerExp); +impl_fmt!(LowerHex); +impl_fmt!(Octal); +impl_fmt!(Pointer); +impl_fmt!(UpperExp); +impl_fmt!(UpperHex); + +/// "Intelligent" emoji formatter. +/// +/// This struct intelligently wraps an emoji so that it is rendered +/// only on systems that want emojis and renders a fallback on others. +/// +/// Example: +/// +/// ```rust +/// use console::Emoji; +/// println!("[3/4] {}Downloading ...", Emoji("🚚 ", "")); +/// println!("[4/4] {} Done!", Emoji("✨", ":-)")); +/// ``` +#[derive(Copy, Clone)] +pub struct Emoji<'a, 'b>(pub &'a str, pub &'b str); + +impl<'a, 'b> Emoji<'a, 'b> { + pub fn new(emoji: &'a str, fallback: &'b str) -> Emoji<'a, 'b> { + Emoji(emoji, fallback) + } +} + +impl<'a, 'b> fmt::Display for Emoji<'a, 'b> { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + if wants_emoji() { + write!(f, "{}", self.0) + } else { + write!(f, "{}", self.1) + } + } +} + +fn str_width(s: &str) -> usize { + #[cfg(feature = "unicode-width")] + { + use unicode_width::UnicodeWidthStr; + s.width() + } + #[cfg(not(feature = "unicode-width"))] + { + s.chars().count() + } +} + +#[cfg(feature = "ansi-parsing")] +fn char_width(c: char) -> usize { + #[cfg(feature = "unicode-width")] + { + use unicode_width::UnicodeWidthChar; + c.width().unwrap_or(0) + } + #[cfg(not(feature = "unicode-width"))] + { + let _c = c; + 1 + } +} + +/// Truncates a string to a certain number of characters. +/// +/// This ensures that escape codes are not screwed up in the process. +/// If the maximum length is hit the string will be truncated but +/// escapes code will still be honored. If truncation takes place +/// the tail string will be appended. +pub fn truncate_str<'a>(s: &'a str, width: usize, tail: &str) -> Cow<'a, str> { + #[cfg(feature = "ansi-parsing")] + { + use std::cmp::Ordering; + let mut iter = AnsiCodeIterator::new(s); + let mut length = 0; + let mut rv = None; + + while let Some(item) = iter.next() { + match item { + (s, false) => { + if rv.is_none() { + if str_width(s) + length > width - str_width(tail) { + let ts = iter.current_slice(); + + let mut s_byte = 0; + let mut s_width = 0; + let rest_width = width - str_width(tail) - length; + for c in s.chars() { + s_byte += c.len_utf8(); + s_width += char_width(c); + match s_width.cmp(&rest_width) { + Ordering::Equal => break, + Ordering::Greater => { + s_byte -= c.len_utf8(); + break; + } + Ordering::Less => continue, + } + } + + let idx = ts.len() - s.len() + s_byte; + let mut buf = ts[..idx].to_string(); + buf.push_str(tail); + rv = Some(buf); + } + length += str_width(s); + } + } + (s, true) => { + if rv.is_some() { + rv.as_mut().unwrap().push_str(s); + } + } + } + } + + if let Some(buf) = rv { + Cow::Owned(buf) + } else { + Cow::Borrowed(s) + } + } + + #[cfg(not(feature = "ansi-parsing"))] + { + if s.len() <= width - tail.len() { + Cow::Borrowed(s) + } else { + Cow::Owned(format!( + "{}{}", + s.get(..width - tail.len()).unwrap_or_default(), + tail + )) + } + } +} + +/// Pads a string to fill a certain number of characters. +/// +/// This will honor ansi codes correctly and allows you to align a string +/// on the left, right or centered. Additionally truncation can be enabled +/// by setting `truncate` to a string that should be used as a truncation +/// marker. +pub fn pad_str<'a>( + s: &'a str, + width: usize, + align: Alignment, + truncate: Option<&str>, +) -> Cow<'a, str> { + pad_str_with(s, width, align, truncate, ' ') +} +/// Pads a string with specific padding to fill a certain number of characters. +/// +/// This will honor ansi codes correctly and allows you to align a string +/// on the left, right or centered. Additionally truncation can be enabled +/// by setting `truncate` to a string that should be used as a truncation +/// marker. +pub fn pad_str_with<'a>( + s: &'a str, + width: usize, + align: Alignment, + truncate: Option<&str>, + pad: char, +) -> Cow<'a, str> { + let cols = measure_text_width(s); + + if cols >= width { + return match truncate { + None => Cow::Borrowed(s), + Some(tail) => truncate_str(s, width, tail), + }; + } + + let diff = width - cols; + + let (left_pad, right_pad) = match align { + Alignment::Left => (0, diff), + Alignment::Right => (diff, 0), + Alignment::Center => (diff / 2, diff - diff / 2), + }; + + let mut rv = String::new(); + for _ in 0..left_pad { + rv.push(pad); + } + rv.push_str(s); + for _ in 0..right_pad { + rv.push(pad); + } + Cow::Owned(rv) +} + +#[test] +fn test_text_width() { + let s = style("foo") + .red() + .on_black() + .bold() + .force_styling(true) + .to_string(); + assert_eq!( + measure_text_width(&s), + if cfg!(feature = "ansi-parsing") { + 3 + } else if cfg!(feature = "unicode-width") { + 17 + } else { + 21 + } + ); +} + +#[test] +#[cfg(all(feature = "unicode-width", feature = "ansi-parsing"))] +fn test_truncate_str() { + let s = format!("foo {}", style("bar").red().force_styling(true)); + assert_eq!( + &truncate_str(&s, 5, ""), + &format!("foo {}", style("b").red().force_styling(true)) + ); + let s = format!("foo {}", style("bar").red().force_styling(true)); + assert_eq!( + &truncate_str(&s, 5, "!"), + &format!("foo {}", style("!").red().force_styling(true)) + ); + let s = format!("foo {} baz", style("bar").red().force_styling(true)); + assert_eq!( + &truncate_str(&s, 10, "..."), + &format!("foo {}...", style("bar").red().force_styling(true)) + ); + let s = format!("foo {}", style("バー").red().force_styling(true)); + assert_eq!( + &truncate_str(&s, 5, ""), + &format!("foo {}", style("").red().force_styling(true)) + ); + let s = format!("foo {}", style("バー").red().force_styling(true)); + assert_eq!( + &truncate_str(&s, 6, ""), + &format!("foo {}", style("バ").red().force_styling(true)) + ); +} + +#[test] +fn test_truncate_str_no_ansi() { + assert_eq!(&truncate_str("foo bar", 5, ""), "foo b"); + assert_eq!(&truncate_str("foo bar", 5, "!"), "foo !"); + assert_eq!(&truncate_str("foo bar baz", 10, "..."), "foo bar..."); +} + +#[test] +fn test_pad_str() { + assert_eq!(pad_str("foo", 7, Alignment::Center, None), " foo "); + assert_eq!(pad_str("foo", 7, Alignment::Left, None), "foo "); + assert_eq!(pad_str("foo", 7, Alignment::Right, None), " foo"); + assert_eq!(pad_str("foo", 3, Alignment::Left, None), "foo"); + assert_eq!(pad_str("foobar", 3, Alignment::Left, None), "foobar"); + assert_eq!(pad_str("foobar", 3, Alignment::Left, Some("")), "foo"); + assert_eq!( + pad_str("foobarbaz", 6, Alignment::Left, Some("...")), + "foo..." + ); +} + +#[test] +fn test_pad_str_with() { + assert_eq!( + pad_str_with("foo", 7, Alignment::Center, None, '#'), + "##foo##" + ); + assert_eq!( + pad_str_with("foo", 7, Alignment::Left, None, '#'), + "foo####" + ); + assert_eq!( + pad_str_with("foo", 7, Alignment::Right, None, '#'), + "####foo" + ); + assert_eq!(pad_str_with("foo", 3, Alignment::Left, None, '#'), "foo"); + assert_eq!( + pad_str_with("foobar", 3, Alignment::Left, None, '#'), + "foobar" + ); + assert_eq!( + pad_str_with("foobar", 3, Alignment::Left, Some(""), '#'), + "foo" + ); + assert_eq!( + pad_str_with("foobarbaz", 6, Alignment::Left, Some("..."), '#'), + "foo..." + ); +} -- cgit v1.2.3