console/
utils.rs

1use std::borrow::Cow;
2use std::collections::BTreeSet;
3use std::env;
4use std::fmt;
5use std::sync::atomic::{AtomicBool, Ordering};
6
7use once_cell::sync::Lazy;
8
9use crate::term::{wants_emoji, Term};
10
11#[cfg(feature = "ansi-parsing")]
12use crate::ansi::{strip_ansi_codes, AnsiCodeIterator};
13
14#[cfg(not(feature = "ansi-parsing"))]
15fn strip_ansi_codes(s: &str) -> &str {
16    s
17}
18
19fn default_colors_enabled(out: &Term) -> bool {
20    (out.features().colors_supported()
21        && &env::var("CLICOLOR").unwrap_or_else(|_| "1".into()) != "0")
22        || &env::var("CLICOLOR_FORCE").unwrap_or_else(|_| "0".into()) != "0"
23}
24
25static STDOUT_COLORS: Lazy<AtomicBool> =
26    Lazy::new(|| AtomicBool::new(default_colors_enabled(&Term::stdout())));
27static STDERR_COLORS: Lazy<AtomicBool> =
28    Lazy::new(|| AtomicBool::new(default_colors_enabled(&Term::stderr())));
29
30/// Returns `true` if colors should be enabled for stdout.
31///
32/// This honors the [clicolors spec](http://bixense.com/clicolors/).
33///
34/// * `CLICOLOR != 0`: ANSI colors are supported and should be used when the program isn't piped.
35/// * `CLICOLOR == 0`: Don't output ANSI color escape codes.
36/// * `CLICOLOR_FORCE != 0`: ANSI colors should be enabled no matter what.
37#[inline]
38pub fn colors_enabled() -> bool {
39    STDOUT_COLORS.load(Ordering::Relaxed)
40}
41
42/// Forces colorization on or off for stdout.
43///
44/// This overrides the default for the current process and changes the return value of the
45/// `colors_enabled` function.
46#[inline]
47pub fn set_colors_enabled(val: bool) {
48    STDOUT_COLORS.store(val, Ordering::Relaxed)
49}
50
51/// Returns `true` if colors should be enabled for stderr.
52///
53/// This honors the [clicolors spec](http://bixense.com/clicolors/).
54///
55/// * `CLICOLOR != 0`: ANSI colors are supported and should be used when the program isn't piped.
56/// * `CLICOLOR == 0`: Don't output ANSI color escape codes.
57/// * `CLICOLOR_FORCE != 0`: ANSI colors should be enabled no matter what.
58#[inline]
59pub fn colors_enabled_stderr() -> bool {
60    STDERR_COLORS.load(Ordering::Relaxed)
61}
62
63/// Forces colorization on or off for stderr.
64///
65/// This overrides the default for the current process and changes the return value of the
66/// `colors_enabled` function.
67#[inline]
68pub fn set_colors_enabled_stderr(val: bool) {
69    STDERR_COLORS.store(val, Ordering::Relaxed)
70}
71
72/// Measure the width of a string in terminal characters.
73pub fn measure_text_width(s: &str) -> usize {
74    str_width(&strip_ansi_codes(s))
75}
76
77/// A terminal color.
78#[derive(Copy, Clone, Debug, PartialEq, Eq)]
79pub enum Color {
80    Black,
81    Red,
82    Green,
83    Yellow,
84    Blue,
85    Magenta,
86    Cyan,
87    White,
88    Color256(u8),
89}
90
91impl Color {
92    #[inline]
93    fn ansi_num(self) -> usize {
94        match self {
95            Color::Black => 0,
96            Color::Red => 1,
97            Color::Green => 2,
98            Color::Yellow => 3,
99            Color::Blue => 4,
100            Color::Magenta => 5,
101            Color::Cyan => 6,
102            Color::White => 7,
103            Color::Color256(x) => x as usize,
104        }
105    }
106
107    #[inline]
108    fn is_color256(self) -> bool {
109        #[allow(clippy::match_like_matches_macro)]
110        match self {
111            Color::Color256(_) => true,
112            _ => false,
113        }
114    }
115}
116
117/// A terminal style attribute.
118#[derive(Copy, Clone, Debug, PartialEq, Eq, Ord, PartialOrd)]
119pub enum Attribute {
120    Bold,
121    Dim,
122    Italic,
123    Underlined,
124    Blink,
125    BlinkFast,
126    Reverse,
127    Hidden,
128    StrikeThrough,
129}
130
131impl Attribute {
132    #[inline]
133    fn ansi_num(self) -> usize {
134        match self {
135            Attribute::Bold => 1,
136            Attribute::Dim => 2,
137            Attribute::Italic => 3,
138            Attribute::Underlined => 4,
139            Attribute::Blink => 5,
140            Attribute::BlinkFast => 6,
141            Attribute::Reverse => 7,
142            Attribute::Hidden => 8,
143            Attribute::StrikeThrough => 9,
144        }
145    }
146}
147
148/// Defines the alignment for padding operations.
149#[derive(Copy, Clone, Debug, PartialEq, Eq)]
150pub enum Alignment {
151    Left,
152    Center,
153    Right,
154}
155
156/// A stored style that can be applied.
157#[derive(Clone, Debug, PartialEq, Eq)]
158pub struct Style {
159    fg: Option<Color>,
160    bg: Option<Color>,
161    fg_bright: bool,
162    bg_bright: bool,
163    attrs: BTreeSet<Attribute>,
164    force: Option<bool>,
165    for_stderr: bool,
166}
167
168impl Default for Style {
169    fn default() -> Self {
170        Self::new()
171    }
172}
173
174impl Style {
175    /// Returns an empty default style.
176    pub const fn new() -> Self {
177        Self {
178            fg: None,
179            bg: None,
180            fg_bright: false,
181            bg_bright: false,
182            attrs: BTreeSet::new(),
183            force: None,
184            for_stderr: false,
185        }
186    }
187
188    /// Creates a style from a dotted string.
189    ///
190    /// Effectively the string is split at each dot and then the
191    /// terms in between are applied.  For instance `red.on_blue` will
192    /// create a string that is red on blue background. `9.on_12` is
193    /// the same, but using 256 color numbers. Unknown terms are
194    /// ignored.
195    pub fn from_dotted_str(s: &str) -> Self {
196        let mut rv = Self::new();
197        for part in s.split('.') {
198            rv = match part {
199                "black" => rv.black(),
200                "red" => rv.red(),
201                "green" => rv.green(),
202                "yellow" => rv.yellow(),
203                "blue" => rv.blue(),
204                "magenta" => rv.magenta(),
205                "cyan" => rv.cyan(),
206                "white" => rv.white(),
207                "bright" => rv.bright(),
208                "on_black" => rv.on_black(),
209                "on_red" => rv.on_red(),
210                "on_green" => rv.on_green(),
211                "on_yellow" => rv.on_yellow(),
212                "on_blue" => rv.on_blue(),
213                "on_magenta" => rv.on_magenta(),
214                "on_cyan" => rv.on_cyan(),
215                "on_white" => rv.on_white(),
216                "on_bright" => rv.on_bright(),
217                "bold" => rv.bold(),
218                "dim" => rv.dim(),
219                "underlined" => rv.underlined(),
220                "blink" => rv.blink(),
221                "blink_fast" => rv.blink_fast(),
222                "reverse" => rv.reverse(),
223                "hidden" => rv.hidden(),
224                "strikethrough" => rv.strikethrough(),
225                on_c if on_c.starts_with("on_") => {
226                    if let Ok(n) = on_c[3..].parse::<u8>() {
227                        rv.on_color256(n)
228                    } else {
229                        continue;
230                    }
231                }
232                c => {
233                    if let Ok(n) = c.parse::<u8>() {
234                        rv.color256(n)
235                    } else {
236                        continue;
237                    }
238                }
239            };
240        }
241        rv
242    }
243
244    /// Apply the style to something that can be displayed.
245    pub fn apply_to<D>(&self, val: D) -> StyledObject<D> {
246        StyledObject {
247            style: self.clone(),
248            val,
249        }
250    }
251
252    /// Forces styling on or off.
253    ///
254    /// This overrides the automatic detection.
255    #[inline]
256    pub const fn force_styling(mut self, value: bool) -> Self {
257        self.force = Some(value);
258        self
259    }
260
261    /// Specifies that style is applying to something being written on stderr.
262    #[inline]
263    pub const fn for_stderr(mut self) -> Self {
264        self.for_stderr = true;
265        self
266    }
267
268    /// Specifies that style is applying to something being written on stdout.
269    ///
270    /// This is the default behaviour.
271    #[inline]
272    pub const fn for_stdout(mut self) -> Self {
273        self.for_stderr = false;
274        self
275    }
276
277    /// Sets a foreground color.
278    #[inline]
279    pub const fn fg(mut self, color: Color) -> Self {
280        self.fg = Some(color);
281        self
282    }
283
284    /// Sets a background color.
285    #[inline]
286    pub const fn bg(mut self, color: Color) -> Self {
287        self.bg = Some(color);
288        self
289    }
290
291    /// Adds a attr.
292    #[inline]
293    pub fn attr(mut self, attr: Attribute) -> Self {
294        self.attrs.insert(attr);
295        self
296    }
297
298    #[inline]
299    pub const fn black(self) -> Self {
300        self.fg(Color::Black)
301    }
302    #[inline]
303    pub const fn red(self) -> Self {
304        self.fg(Color::Red)
305    }
306    #[inline]
307    pub const fn green(self) -> Self {
308        self.fg(Color::Green)
309    }
310    #[inline]
311    pub const fn yellow(self) -> Self {
312        self.fg(Color::Yellow)
313    }
314    #[inline]
315    pub const fn blue(self) -> Self {
316        self.fg(Color::Blue)
317    }
318    #[inline]
319    pub const fn magenta(self) -> Self {
320        self.fg(Color::Magenta)
321    }
322    #[inline]
323    pub const fn cyan(self) -> Self {
324        self.fg(Color::Cyan)
325    }
326    #[inline]
327    pub const fn white(self) -> Self {
328        self.fg(Color::White)
329    }
330    #[inline]
331    pub const fn color256(self, color: u8) -> Self {
332        self.fg(Color::Color256(color))
333    }
334
335    #[inline]
336    pub const fn bright(mut self) -> Self {
337        self.fg_bright = true;
338        self
339    }
340
341    #[inline]
342    pub const fn on_black(self) -> Self {
343        self.bg(Color::Black)
344    }
345    #[inline]
346    pub const fn on_red(self) -> Self {
347        self.bg(Color::Red)
348    }
349    #[inline]
350    pub const fn on_green(self) -> Self {
351        self.bg(Color::Green)
352    }
353    #[inline]
354    pub const fn on_yellow(self) -> Self {
355        self.bg(Color::Yellow)
356    }
357    #[inline]
358    pub const fn on_blue(self) -> Self {
359        self.bg(Color::Blue)
360    }
361    #[inline]
362    pub const fn on_magenta(self) -> Self {
363        self.bg(Color::Magenta)
364    }
365    #[inline]
366    pub const fn on_cyan(self) -> Self {
367        self.bg(Color::Cyan)
368    }
369    #[inline]
370    pub const fn on_white(self) -> Self {
371        self.bg(Color::White)
372    }
373    #[inline]
374    pub const fn on_color256(self, color: u8) -> Self {
375        self.bg(Color::Color256(color))
376    }
377
378    #[inline]
379    pub const fn on_bright(mut self) -> Self {
380        self.bg_bright = true;
381        self
382    }
383
384    #[inline]
385    pub fn bold(self) -> Self {
386        self.attr(Attribute::Bold)
387    }
388    #[inline]
389    pub fn dim(self) -> Self {
390        self.attr(Attribute::Dim)
391    }
392    #[inline]
393    pub fn italic(self) -> Self {
394        self.attr(Attribute::Italic)
395    }
396    #[inline]
397    pub fn underlined(self) -> Self {
398        self.attr(Attribute::Underlined)
399    }
400    #[inline]
401    pub fn blink(self) -> Self {
402        self.attr(Attribute::Blink)
403    }
404    #[inline]
405    pub fn blink_fast(self) -> Self {
406        self.attr(Attribute::BlinkFast)
407    }
408    #[inline]
409    pub fn reverse(self) -> Self {
410        self.attr(Attribute::Reverse)
411    }
412    #[inline]
413    pub fn hidden(self) -> Self {
414        self.attr(Attribute::Hidden)
415    }
416    #[inline]
417    pub fn strikethrough(self) -> Self {
418        self.attr(Attribute::StrikeThrough)
419    }
420}
421
422/// Wraps an object for formatting for styling.
423///
424/// Example:
425///
426/// ```rust,no_run
427/// # use console::style;
428/// format!("Hello {}", style("World").cyan());
429/// ```
430///
431/// This is a shortcut for making a new style and applying it
432/// to a value:
433///
434/// ```rust,no_run
435/// # use console::Style;
436/// format!("Hello {}", Style::new().cyan().apply_to("World"));
437/// ```
438pub fn style<D>(val: D) -> StyledObject<D> {
439    Style::new().apply_to(val)
440}
441
442/// A formatting wrapper that can be styled for a terminal.
443#[derive(Clone)]
444pub struct StyledObject<D> {
445    style: Style,
446    val: D,
447}
448
449impl<D> StyledObject<D> {
450    /// Forces styling on or off.
451    ///
452    /// This overrides the automatic detection.
453    #[inline]
454    pub fn force_styling(mut self, value: bool) -> StyledObject<D> {
455        self.style = self.style.force_styling(value);
456        self
457    }
458
459    /// Specifies that style is applying to something being written on stderr
460    #[inline]
461    pub fn for_stderr(mut self) -> StyledObject<D> {
462        self.style = self.style.for_stderr();
463        self
464    }
465
466    /// Specifies that style is applying to something being written on stdout
467    ///
468    /// This is the default
469    #[inline]
470    pub fn for_stdout(mut self) -> StyledObject<D> {
471        self.style = self.style.for_stdout();
472        self
473    }
474
475    /// Sets a foreground color.
476    #[inline]
477    pub fn fg(mut self, color: Color) -> StyledObject<D> {
478        self.style = self.style.fg(color);
479        self
480    }
481
482    /// Sets a background color.
483    #[inline]
484    pub fn bg(mut self, color: Color) -> StyledObject<D> {
485        self.style = self.style.bg(color);
486        self
487    }
488
489    /// Adds a attr.
490    #[inline]
491    pub fn attr(mut self, attr: Attribute) -> StyledObject<D> {
492        self.style = self.style.attr(attr);
493        self
494    }
495
496    #[inline]
497    pub fn black(self) -> StyledObject<D> {
498        self.fg(Color::Black)
499    }
500    #[inline]
501    pub fn red(self) -> StyledObject<D> {
502        self.fg(Color::Red)
503    }
504    #[inline]
505    pub fn green(self) -> StyledObject<D> {
506        self.fg(Color::Green)
507    }
508    #[inline]
509    pub fn yellow(self) -> StyledObject<D> {
510        self.fg(Color::Yellow)
511    }
512    #[inline]
513    pub fn blue(self) -> StyledObject<D> {
514        self.fg(Color::Blue)
515    }
516    #[inline]
517    pub fn magenta(self) -> StyledObject<D> {
518        self.fg(Color::Magenta)
519    }
520    #[inline]
521    pub fn cyan(self) -> StyledObject<D> {
522        self.fg(Color::Cyan)
523    }
524    #[inline]
525    pub fn white(self) -> StyledObject<D> {
526        self.fg(Color::White)
527    }
528    #[inline]
529    pub fn color256(self, color: u8) -> StyledObject<D> {
530        self.fg(Color::Color256(color))
531    }
532
533    #[inline]
534    pub fn bright(mut self) -> StyledObject<D> {
535        self.style = self.style.bright();
536        self
537    }
538
539    #[inline]
540    pub fn on_black(self) -> StyledObject<D> {
541        self.bg(Color::Black)
542    }
543    #[inline]
544    pub fn on_red(self) -> StyledObject<D> {
545        self.bg(Color::Red)
546    }
547    #[inline]
548    pub fn on_green(self) -> StyledObject<D> {
549        self.bg(Color::Green)
550    }
551    #[inline]
552    pub fn on_yellow(self) -> StyledObject<D> {
553        self.bg(Color::Yellow)
554    }
555    #[inline]
556    pub fn on_blue(self) -> StyledObject<D> {
557        self.bg(Color::Blue)
558    }
559    #[inline]
560    pub fn on_magenta(self) -> StyledObject<D> {
561        self.bg(Color::Magenta)
562    }
563    #[inline]
564    pub fn on_cyan(self) -> StyledObject<D> {
565        self.bg(Color::Cyan)
566    }
567    #[inline]
568    pub fn on_white(self) -> StyledObject<D> {
569        self.bg(Color::White)
570    }
571    #[inline]
572    pub fn on_color256(self, color: u8) -> StyledObject<D> {
573        self.bg(Color::Color256(color))
574    }
575
576    #[inline]
577    pub fn on_bright(mut self) -> StyledObject<D> {
578        self.style = self.style.on_bright();
579        self
580    }
581
582    #[inline]
583    pub fn bold(self) -> StyledObject<D> {
584        self.attr(Attribute::Bold)
585    }
586    #[inline]
587    pub fn dim(self) -> StyledObject<D> {
588        self.attr(Attribute::Dim)
589    }
590    #[inline]
591    pub fn italic(self) -> StyledObject<D> {
592        self.attr(Attribute::Italic)
593    }
594    #[inline]
595    pub fn underlined(self) -> StyledObject<D> {
596        self.attr(Attribute::Underlined)
597    }
598    #[inline]
599    pub fn blink(self) -> StyledObject<D> {
600        self.attr(Attribute::Blink)
601    }
602    #[inline]
603    pub fn blink_fast(self) -> StyledObject<D> {
604        self.attr(Attribute::BlinkFast)
605    }
606    #[inline]
607    pub fn reverse(self) -> StyledObject<D> {
608        self.attr(Attribute::Reverse)
609    }
610    #[inline]
611    pub fn hidden(self) -> StyledObject<D> {
612        self.attr(Attribute::Hidden)
613    }
614    #[inline]
615    pub fn strikethrough(self) -> StyledObject<D> {
616        self.attr(Attribute::StrikeThrough)
617    }
618}
619
620macro_rules! impl_fmt {
621    ($name:ident) => {
622        impl<D: fmt::$name> fmt::$name for StyledObject<D> {
623            fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
624                let mut reset = false;
625                if self
626                    .style
627                    .force
628                    .unwrap_or_else(|| match self.style.for_stderr {
629                        true => colors_enabled_stderr(),
630                        false => colors_enabled(),
631                    })
632                {
633                    if let Some(fg) = self.style.fg {
634                        if fg.is_color256() {
635                            write!(f, "\x1b[38;5;{}m", fg.ansi_num())?;
636                        } else if self.style.fg_bright {
637                            write!(f, "\x1b[38;5;{}m", fg.ansi_num() + 8)?;
638                        } else {
639                            write!(f, "\x1b[{}m", fg.ansi_num() + 30)?;
640                        }
641                        reset = true;
642                    }
643                    if let Some(bg) = self.style.bg {
644                        if bg.is_color256() {
645                            write!(f, "\x1b[48;5;{}m", bg.ansi_num())?;
646                        } else if self.style.bg_bright {
647                            write!(f, "\x1b[48;5;{}m", bg.ansi_num() + 8)?;
648                        } else {
649                            write!(f, "\x1b[{}m", bg.ansi_num() + 40)?;
650                        }
651                        reset = true;
652                    }
653                    for attr in &self.style.attrs {
654                        write!(f, "\x1b[{}m", attr.ansi_num())?;
655                        reset = true;
656                    }
657                }
658                fmt::$name::fmt(&self.val, f)?;
659                if reset {
660                    write!(f, "\x1b[0m")?;
661                }
662                Ok(())
663            }
664        }
665    };
666}
667
668impl_fmt!(Binary);
669impl_fmt!(Debug);
670impl_fmt!(Display);
671impl_fmt!(LowerExp);
672impl_fmt!(LowerHex);
673impl_fmt!(Octal);
674impl_fmt!(Pointer);
675impl_fmt!(UpperExp);
676impl_fmt!(UpperHex);
677
678/// "Intelligent" emoji formatter.
679///
680/// This struct intelligently wraps an emoji so that it is rendered
681/// only on systems that want emojis and renders a fallback on others.
682///
683/// Example:
684///
685/// ```rust
686/// use console::Emoji;
687/// println!("[3/4] {}Downloading ...", Emoji("🚚 ", ""));
688/// println!("[4/4] {} Done!", Emoji("✨", ":-)"));
689/// ```
690#[derive(Copy, Clone)]
691pub struct Emoji<'a, 'b>(pub &'a str, pub &'b str);
692
693impl<'a, 'b> Emoji<'a, 'b> {
694    pub fn new(emoji: &'a str, fallback: &'b str) -> Emoji<'a, 'b> {
695        Emoji(emoji, fallback)
696    }
697}
698
699impl fmt::Display for Emoji<'_, '_> {
700    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
701        if wants_emoji() {
702            write!(f, "{}", self.0)
703        } else {
704            write!(f, "{}", self.1)
705        }
706    }
707}
708
709fn str_width(s: &str) -> usize {
710    #[cfg(feature = "unicode-width")]
711    {
712        use unicode_width::UnicodeWidthStr;
713        s.width()
714    }
715    #[cfg(not(feature = "unicode-width"))]
716    {
717        s.chars().count()
718    }
719}
720
721#[cfg(feature = "ansi-parsing")]
722pub(crate) fn char_width(c: char) -> usize {
723    #[cfg(feature = "unicode-width")]
724    {
725        use unicode_width::UnicodeWidthChar;
726        c.width().unwrap_or(0)
727    }
728    #[cfg(not(feature = "unicode-width"))]
729    {
730        let _c = c;
731        1
732    }
733}
734
735#[cfg(not(feature = "ansi-parsing"))]
736pub(crate) fn char_width(_c: char) -> usize {
737    1
738}
739
740/// Truncates a string to a certain number of characters.
741///
742/// This ensures that escape codes are not screwed up in the process.
743/// If the maximum length is hit the string will be truncated but
744/// escapes code will still be honored.  If truncation takes place
745/// the tail string will be appended.
746pub fn truncate_str<'a>(s: &'a str, width: usize, tail: &str) -> Cow<'a, str> {
747    #[cfg(feature = "ansi-parsing")]
748    {
749        use std::cmp::Ordering;
750        let mut iter = AnsiCodeIterator::new(s);
751        let mut length = 0;
752        let mut rv = None;
753
754        while let Some(item) = iter.next() {
755            match item {
756                (s, false) => {
757                    if rv.is_none() {
758                        if str_width(s) + length > width - str_width(tail) {
759                            let ts = iter.current_slice();
760
761                            let mut s_byte = 0;
762                            let mut s_width = 0;
763                            let rest_width = width - str_width(tail) - length;
764                            for c in s.chars() {
765                                s_byte += c.len_utf8();
766                                s_width += char_width(c);
767                                match s_width.cmp(&rest_width) {
768                                    Ordering::Equal => break,
769                                    Ordering::Greater => {
770                                        s_byte -= c.len_utf8();
771                                        break;
772                                    }
773                                    Ordering::Less => continue,
774                                }
775                            }
776
777                            let idx = ts.len() - s.len() + s_byte;
778                            let mut buf = ts[..idx].to_string();
779                            buf.push_str(tail);
780                            rv = Some(buf);
781                        }
782                        length += str_width(s);
783                    }
784                }
785                (s, true) => {
786                    if let Some(ref mut rv) = rv {
787                        rv.push_str(s);
788                    }
789                }
790            }
791        }
792
793        if let Some(buf) = rv {
794            Cow::Owned(buf)
795        } else {
796            Cow::Borrowed(s)
797        }
798    }
799
800    #[cfg(not(feature = "ansi-parsing"))]
801    {
802        if s.len() <= width - tail.len() {
803            Cow::Borrowed(s)
804        } else {
805            Cow::Owned(format!(
806                "{}{}",
807                s.get(..width - tail.len()).unwrap_or_default(),
808                tail
809            ))
810        }
811    }
812}
813
814/// Pads a string to fill a certain number of characters.
815///
816/// This will honor ansi codes correctly and allows you to align a string
817/// on the left, right or centered.  Additionally truncation can be enabled
818/// by setting `truncate` to a string that should be used as a truncation
819/// marker.
820pub fn pad_str<'a>(
821    s: &'a str,
822    width: usize,
823    align: Alignment,
824    truncate: Option<&str>,
825) -> Cow<'a, str> {
826    pad_str_with(s, width, align, truncate, ' ')
827}
828/// Pads a string with specific padding to fill a certain number of characters.
829///
830/// This will honor ansi codes correctly and allows you to align a string
831/// on the left, right or centered.  Additionally truncation can be enabled
832/// by setting `truncate` to a string that should be used as a truncation
833/// marker.
834pub fn pad_str_with<'a>(
835    s: &'a str,
836    width: usize,
837    align: Alignment,
838    truncate: Option<&str>,
839    pad: char,
840) -> Cow<'a, str> {
841    let cols = measure_text_width(s);
842
843    if cols >= width {
844        return match truncate {
845            None => Cow::Borrowed(s),
846            Some(tail) => truncate_str(s, width, tail),
847        };
848    }
849
850    let diff = width - cols;
851
852    let (left_pad, right_pad) = match align {
853        Alignment::Left => (0, diff),
854        Alignment::Right => (diff, 0),
855        Alignment::Center => (diff / 2, diff - diff / 2),
856    };
857
858    let mut rv = String::new();
859    for _ in 0..left_pad {
860        rv.push(pad);
861    }
862    rv.push_str(s);
863    for _ in 0..right_pad {
864        rv.push(pad);
865    }
866    Cow::Owned(rv)
867}
868
869#[test]
870fn test_text_width() {
871    let s = style("foo")
872        .red()
873        .on_black()
874        .bold()
875        .force_styling(true)
876        .to_string();
877    assert_eq!(
878        measure_text_width(&s),
879        if cfg!(feature = "ansi-parsing") {
880            3
881        } else if cfg!(feature = "unicode-width") {
882            17
883        } else {
884            21
885        }
886    );
887}
888
889#[test]
890#[cfg(all(feature = "unicode-width", feature = "ansi-parsing"))]
891fn test_truncate_str() {
892    let s = format!("foo {}", style("bar").red().force_styling(true));
893    assert_eq!(
894        &truncate_str(&s, 5, ""),
895        &format!("foo {}", style("b").red().force_styling(true))
896    );
897    let s = format!("foo {}", style("bar").red().force_styling(true));
898    assert_eq!(
899        &truncate_str(&s, 5, "!"),
900        &format!("foo {}", style("!").red().force_styling(true))
901    );
902    let s = format!("foo {} baz", style("bar").red().force_styling(true));
903    assert_eq!(
904        &truncate_str(&s, 10, "..."),
905        &format!("foo {}...", style("bar").red().force_styling(true))
906    );
907    let s = format!("foo {}", style("バー").red().force_styling(true));
908    assert_eq!(
909        &truncate_str(&s, 5, ""),
910        &format!("foo {}", style("").red().force_styling(true))
911    );
912    let s = format!("foo {}", style("バー").red().force_styling(true));
913    assert_eq!(
914        &truncate_str(&s, 6, ""),
915        &format!("foo {}", style("バ").red().force_styling(true))
916    );
917}
918
919#[test]
920fn test_truncate_str_no_ansi() {
921    assert_eq!(&truncate_str("foo bar", 5, ""), "foo b");
922    assert_eq!(&truncate_str("foo bar", 5, "!"), "foo !");
923    assert_eq!(&truncate_str("foo bar baz", 10, "..."), "foo bar...");
924}
925
926#[test]
927fn test_pad_str() {
928    assert_eq!(pad_str("foo", 7, Alignment::Center, None), "  foo  ");
929    assert_eq!(pad_str("foo", 7, Alignment::Left, None), "foo    ");
930    assert_eq!(pad_str("foo", 7, Alignment::Right, None), "    foo");
931    assert_eq!(pad_str("foo", 3, Alignment::Left, None), "foo");
932    assert_eq!(pad_str("foobar", 3, Alignment::Left, None), "foobar");
933    assert_eq!(pad_str("foobar", 3, Alignment::Left, Some("")), "foo");
934    assert_eq!(
935        pad_str("foobarbaz", 6, Alignment::Left, Some("...")),
936        "foo..."
937    );
938}
939
940#[test]
941fn test_pad_str_with() {
942    assert_eq!(
943        pad_str_with("foo", 7, Alignment::Center, None, '#'),
944        "##foo##"
945    );
946    assert_eq!(
947        pad_str_with("foo", 7, Alignment::Left, None, '#'),
948        "foo####"
949    );
950    assert_eq!(
951        pad_str_with("foo", 7, Alignment::Right, None, '#'),
952        "####foo"
953    );
954    assert_eq!(pad_str_with("foo", 3, Alignment::Left, None, '#'), "foo");
955    assert_eq!(
956        pad_str_with("foobar", 3, Alignment::Left, None, '#'),
957        "foobar"
958    );
959    assert_eq!(
960        pad_str_with("foobar", 3, Alignment::Left, Some(""), '#'),
961        "foo"
962    );
963    assert_eq!(
964        pad_str_with("foobarbaz", 6, Alignment::Left, Some("..."), '#'),
965        "foo..."
966    );
967}