1 use std::borrow::Cow;
2 use std::collections::BTreeSet;
3 use std::fmt;
4 
5 use crate::term::wants_emoji;
6 
7 #[cfg(feature = "ansi-parsing")]
8 use crate::ansi::{strip_ansi_codes, AnsiCodeIterator};
9 
10 #[cfg(not(feature = "ansi-parsing"))]
strip_ansi_codes(s: &str) -> &str11 fn strip_ansi_codes(s: &str) -> &str {
12     s
13 }
14 
15 /// Returns `true` if colors should be enabled.
16 ///
17 /// This honors the [clicolors spec](http://bixense.com/clicolors/).
18 ///
19 /// * `CLICOLOR != 0`: ANSI colors are supported and should be used when the program isn't piped.
20 /// * `CLICOLOR == 0`: Don't output ANSI color escape codes.
21 /// * `CLICOLOR_FORCE != 0`: ANSI colors should be enabled no matter what.
22 ///
23 /// This internally uses `clicolors-control`.
24 #[inline]
colors_enabled() -> bool25 pub fn colors_enabled() -> bool {
26     clicolors_control::colors_enabled()
27 }
28 
29 /// Forces colorization on or off.
30 ///
31 /// This overrides the default for the current process and changes the return value of the
32 /// `colors_enabled` function.
33 ///
34 /// This internally uses `clicolors-control`.
35 #[inline]
set_colors_enabled(val: bool)36 pub fn set_colors_enabled(val: bool) {
37     clicolors_control::set_colors_enabled(val)
38 }
39 
40 /// Measure the width of a string in terminal characters.
measure_text_width(s: &str) -> usize41 pub fn measure_text_width(s: &str) -> usize {
42     str_width(&strip_ansi_codes(s))
43 }
44 
45 /// A terminal color.
46 #[derive(Copy, Clone, Debug, PartialEq, Eq)]
47 pub enum Color {
48     Black,
49     Red,
50     Green,
51     Yellow,
52     Blue,
53     Magenta,
54     Cyan,
55     White,
56 }
57 
58 impl Color {
59     #[inline]
ansi_num(self) -> usize60     fn ansi_num(self) -> usize {
61         match self {
62             Color::Black => 0,
63             Color::Red => 1,
64             Color::Green => 2,
65             Color::Yellow => 3,
66             Color::Blue => 4,
67             Color::Magenta => 5,
68             Color::Cyan => 6,
69             Color::White => 7,
70         }
71     }
72 }
73 
74 /// A terminal style attribute.
75 #[derive(Copy, Clone, Debug, PartialEq, Eq, Ord, PartialOrd)]
76 pub enum Attribute {
77     Bold,
78     Dim,
79     Italic,
80     Underlined,
81     Blink,
82     Reverse,
83     Hidden,
84 }
85 
86 impl Attribute {
87     #[inline]
ansi_num(self) -> usize88     fn ansi_num(self) -> usize {
89         match self {
90             Attribute::Bold => 1,
91             Attribute::Dim => 2,
92             Attribute::Italic => 3,
93             Attribute::Underlined => 4,
94             Attribute::Blink => 5,
95             Attribute::Reverse => 7,
96             Attribute::Hidden => 8,
97         }
98     }
99 }
100 
101 /// Defines the alignment for padding operations.
102 #[derive(Copy, Clone, Debug, PartialEq, Eq)]
103 pub enum Alignment {
104     Left,
105     Center,
106     Right,
107 }
108 
109 /// A stored style that can be applied.
110 #[derive(Clone, Debug, PartialEq, Eq)]
111 pub struct Style {
112     fg: Option<Color>,
113     bg: Option<Color>,
114     attrs: BTreeSet<Attribute>,
115     force: Option<bool>,
116 }
117 
118 impl Default for Style {
default() -> Style119     fn default() -> Style {
120         Style::new()
121     }
122 }
123 
124 impl Style {
125     /// Returns an empty default style.
new() -> Style126     pub fn new() -> Style {
127         Style {
128             fg: None,
129             bg: None,
130             attrs: BTreeSet::new(),
131             force: None,
132         }
133     }
134 
135     /// Creates a style from a dotted string.
136     ///
137     /// Effectively the string is split at each dot and then the
138     /// terms in between are applied.  For instance `red.on_blue` will
139     /// create a string that is red on blue background.  Unknown terms
140     /// are ignored.
from_dotted_str(s: &str) -> Style141     pub fn from_dotted_str(s: &str) -> Style {
142         let mut rv = Style::new();
143         for part in s.split('.') {
144             rv = match part {
145                 "black" => rv.black(),
146                 "red" => rv.red(),
147                 "green" => rv.green(),
148                 "yellow" => rv.yellow(),
149                 "blue" => rv.blue(),
150                 "magenta" => rv.magenta(),
151                 "cyan" => rv.cyan(),
152                 "white" => rv.white(),
153                 "on_black" => rv.on_black(),
154                 "on_red" => rv.on_red(),
155                 "on_green" => rv.on_green(),
156                 "on_yellow" => rv.on_yellow(),
157                 "on_blue" => rv.on_blue(),
158                 "on_magenta" => rv.on_magenta(),
159                 "on_cyan" => rv.on_cyan(),
160                 "on_white" => rv.on_white(),
161                 "bold" => rv.bold(),
162                 "dim" => rv.dim(),
163                 "underlined" => rv.underlined(),
164                 "blink" => rv.blink(),
165                 "reverse" => rv.reverse(),
166                 "hidden" => rv.hidden(),
167                 _ => {
168                     continue;
169                 }
170             };
171         }
172         rv
173     }
174 
175     /// Apply the style to something that can be displayed.
apply_to<D>(&self, val: D) -> StyledObject<D>176     pub fn apply_to<D>(&self, val: D) -> StyledObject<D> {
177         StyledObject {
178             style: self.clone(),
179             val,
180         }
181     }
182 
183     /// Forces styling on or off.
184     ///
185     /// This overrides the detection from `clicolors-control`.
186     #[inline]
force_styling(mut self, value: bool) -> Style187     pub fn force_styling(mut self, value: bool) -> Style {
188         self.force = Some(value);
189         self
190     }
191 
192     /// Sets a foreground color.
193     #[inline]
fg(mut self, color: Color) -> Style194     pub fn fg(mut self, color: Color) -> Style {
195         self.fg = Some(color);
196         self
197     }
198 
199     /// Sets a background color.
200     #[inline]
bg(mut self, color: Color) -> Style201     pub fn bg(mut self, color: Color) -> Style {
202         self.bg = Some(color);
203         self
204     }
205 
206     /// Adds a attr.
207     #[inline]
attr(mut self, attr: Attribute) -> Style208     pub fn attr(mut self, attr: Attribute) -> Style {
209         self.attrs.insert(attr);
210         self
211     }
212 
213     #[inline]
black(self) -> Style214     pub fn black(self) -> Style {
215         self.fg(Color::Black)
216     }
217     #[inline]
red(self) -> Style218     pub fn red(self) -> Style {
219         self.fg(Color::Red)
220     }
221     #[inline]
green(self) -> Style222     pub fn green(self) -> Style {
223         self.fg(Color::Green)
224     }
225     #[inline]
yellow(self) -> Style226     pub fn yellow(self) -> Style {
227         self.fg(Color::Yellow)
228     }
229     #[inline]
blue(self) -> Style230     pub fn blue(self) -> Style {
231         self.fg(Color::Blue)
232     }
233     #[inline]
magenta(self) -> Style234     pub fn magenta(self) -> Style {
235         self.fg(Color::Magenta)
236     }
237     #[inline]
cyan(self) -> Style238     pub fn cyan(self) -> Style {
239         self.fg(Color::Cyan)
240     }
241     #[inline]
white(self) -> Style242     pub fn white(self) -> Style {
243         self.fg(Color::White)
244     }
245     #[inline]
on_black(self) -> Style246     pub fn on_black(self) -> Style {
247         self.bg(Color::Black)
248     }
249     #[inline]
on_red(self) -> Style250     pub fn on_red(self) -> Style {
251         self.bg(Color::Red)
252     }
253     #[inline]
on_green(self) -> Style254     pub fn on_green(self) -> Style {
255         self.bg(Color::Green)
256     }
257     #[inline]
on_yellow(self) -> Style258     pub fn on_yellow(self) -> Style {
259         self.bg(Color::Yellow)
260     }
261     #[inline]
on_blue(self) -> Style262     pub fn on_blue(self) -> Style {
263         self.bg(Color::Blue)
264     }
265     #[inline]
on_magenta(self) -> Style266     pub fn on_magenta(self) -> Style {
267         self.bg(Color::Magenta)
268     }
269     #[inline]
on_cyan(self) -> Style270     pub fn on_cyan(self) -> Style {
271         self.bg(Color::Cyan)
272     }
273     #[inline]
on_white(self) -> Style274     pub fn on_white(self) -> Style {
275         self.bg(Color::White)
276     }
277     #[inline]
bold(self) -> Style278     pub fn bold(self) -> Style {
279         self.attr(Attribute::Bold)
280     }
281     #[inline]
dim(self) -> Style282     pub fn dim(self) -> Style {
283         self.attr(Attribute::Dim)
284     }
285     #[inline]
italic(self) -> Style286     pub fn italic(self) -> Style {
287         self.attr(Attribute::Italic)
288     }
289     #[inline]
underlined(self) -> Style290     pub fn underlined(self) -> Style {
291         self.attr(Attribute::Underlined)
292     }
293     #[inline]
blink(self) -> Style294     pub fn blink(self) -> Style {
295         self.attr(Attribute::Blink)
296     }
297     #[inline]
reverse(self) -> Style298     pub fn reverse(self) -> Style {
299         self.attr(Attribute::Reverse)
300     }
301     #[inline]
hidden(self) -> Style302     pub fn hidden(self) -> Style {
303         self.attr(Attribute::Hidden)
304     }
305 }
306 
307 /// Wraps an object for formatting for styling.
308 ///
309 /// Example:
310 ///
311 /// ```rust,no_run
312 /// # use console::style;
313 /// format!("Hello {}", style("World").cyan());
314 /// ```
315 ///
316 /// This is a shortcut for making a new style and applying it
317 /// to a value:
318 ///
319 /// ```rust,no_run
320 /// # use console::Style;
321 /// format!("Hello {}", Style::new().cyan().apply_to("World"));
322 /// ```
style<D>(val: D) -> StyledObject<D>323 pub fn style<D>(val: D) -> StyledObject<D> {
324     Style::new().apply_to(val)
325 }
326 
327 /// A formatting wrapper that can be styled for a terminal.
328 #[derive(Clone)]
329 pub struct StyledObject<D> {
330     style: Style,
331     val: D,
332 }
333 
334 impl<D> StyledObject<D> {
335     /// Forces styling on or off.
336     ///
337     /// This overrides the detection from `clicolors-control`.
338     #[inline]
force_styling(mut self, value: bool) -> StyledObject<D>339     pub fn force_styling(mut self, value: bool) -> StyledObject<D> {
340         self.style = self.style.force_styling(value);
341         self
342     }
343 
344     /// Sets a foreground color.
345     #[inline]
fg(mut self, color: Color) -> StyledObject<D>346     pub fn fg(mut self, color: Color) -> StyledObject<D> {
347         self.style = self.style.fg(color);
348         self
349     }
350 
351     /// Sets a background color.
352     #[inline]
bg(mut self, color: Color) -> StyledObject<D>353     pub fn bg(mut self, color: Color) -> StyledObject<D> {
354         self.style = self.style.bg(color);
355         self
356     }
357 
358     /// Adds a attr.
359     #[inline]
attr(mut self, attr: Attribute) -> StyledObject<D>360     pub fn attr(mut self, attr: Attribute) -> StyledObject<D> {
361         self.style = self.style.attr(attr);
362         self
363     }
364 
365     #[inline]
black(self) -> StyledObject<D>366     pub fn black(self) -> StyledObject<D> {
367         self.fg(Color::Black)
368     }
369     #[inline]
red(self) -> StyledObject<D>370     pub fn red(self) -> StyledObject<D> {
371         self.fg(Color::Red)
372     }
373     #[inline]
green(self) -> StyledObject<D>374     pub fn green(self) -> StyledObject<D> {
375         self.fg(Color::Green)
376     }
377     #[inline]
yellow(self) -> StyledObject<D>378     pub fn yellow(self) -> StyledObject<D> {
379         self.fg(Color::Yellow)
380     }
381     #[inline]
blue(self) -> StyledObject<D>382     pub fn blue(self) -> StyledObject<D> {
383         self.fg(Color::Blue)
384     }
385     #[inline]
magenta(self) -> StyledObject<D>386     pub fn magenta(self) -> StyledObject<D> {
387         self.fg(Color::Magenta)
388     }
389     #[inline]
cyan(self) -> StyledObject<D>390     pub fn cyan(self) -> StyledObject<D> {
391         self.fg(Color::Cyan)
392     }
393     #[inline]
white(self) -> StyledObject<D>394     pub fn white(self) -> StyledObject<D> {
395         self.fg(Color::White)
396     }
397     #[inline]
on_black(self) -> StyledObject<D>398     pub fn on_black(self) -> StyledObject<D> {
399         self.bg(Color::Black)
400     }
401     #[inline]
on_red(self) -> StyledObject<D>402     pub fn on_red(self) -> StyledObject<D> {
403         self.bg(Color::Red)
404     }
405     #[inline]
on_green(self) -> StyledObject<D>406     pub fn on_green(self) -> StyledObject<D> {
407         self.bg(Color::Green)
408     }
409     #[inline]
on_yellow(self) -> StyledObject<D>410     pub fn on_yellow(self) -> StyledObject<D> {
411         self.bg(Color::Yellow)
412     }
413     #[inline]
on_blue(self) -> StyledObject<D>414     pub fn on_blue(self) -> StyledObject<D> {
415         self.bg(Color::Blue)
416     }
417     #[inline]
on_magenta(self) -> StyledObject<D>418     pub fn on_magenta(self) -> StyledObject<D> {
419         self.bg(Color::Magenta)
420     }
421     #[inline]
on_cyan(self) -> StyledObject<D>422     pub fn on_cyan(self) -> StyledObject<D> {
423         self.bg(Color::Cyan)
424     }
425     #[inline]
on_white(self) -> StyledObject<D>426     pub fn on_white(self) -> StyledObject<D> {
427         self.bg(Color::White)
428     }
429     #[inline]
bold(self) -> StyledObject<D>430     pub fn bold(self) -> StyledObject<D> {
431         self.attr(Attribute::Bold)
432     }
433     #[inline]
dim(self) -> StyledObject<D>434     pub fn dim(self) -> StyledObject<D> {
435         self.attr(Attribute::Dim)
436     }
437     #[inline]
italic(self) -> StyledObject<D>438     pub fn italic(self) -> StyledObject<D> {
439         self.attr(Attribute::Italic)
440     }
441     #[inline]
underlined(self) -> StyledObject<D>442     pub fn underlined(self) -> StyledObject<D> {
443         self.attr(Attribute::Underlined)
444     }
445     #[inline]
blink(self) -> StyledObject<D>446     pub fn blink(self) -> StyledObject<D> {
447         self.attr(Attribute::Blink)
448     }
449     #[inline]
reverse(self) -> StyledObject<D>450     pub fn reverse(self) -> StyledObject<D> {
451         self.attr(Attribute::Reverse)
452     }
453     #[inline]
hidden(self) -> StyledObject<D>454     pub fn hidden(self) -> StyledObject<D> {
455         self.attr(Attribute::Hidden)
456     }
457 }
458 
459 macro_rules! impl_fmt {
460     ($name:ident) => {
461         impl<D: fmt::$name> fmt::$name for StyledObject<D> {
462             fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
463                 let mut reset = false;
464                 if self.style.force.unwrap_or_else(colors_enabled) {
465                     if let Some(fg) = self.style.fg {
466                         write!(f, "\x1b[{}m", fg.ansi_num() + 30)?;
467                         reset = true;
468                     }
469                     if let Some(bg) = self.style.bg {
470                         write!(f, "\x1b[{}m", bg.ansi_num() + 40)?;
471                         reset = true;
472                     }
473                     for attr in &self.style.attrs {
474                         write!(f, "\x1b[{}m", attr.ansi_num())?;
475                         reset = true;
476                     }
477                 }
478                 fmt::$name::fmt(&self.val, f)?;
479                 if reset {
480                     write!(f, "\x1b[0m")?;
481                 }
482                 Ok(())
483             }
484         }
485     };
486 }
487 
488 impl_fmt!(Binary);
489 impl_fmt!(Debug);
490 impl_fmt!(Display);
491 impl_fmt!(LowerExp);
492 impl_fmt!(LowerHex);
493 impl_fmt!(Octal);
494 impl_fmt!(Pointer);
495 impl_fmt!(UpperExp);
496 impl_fmt!(UpperHex);
497 
498 /// "Intelligent" emoji formatter.
499 ///
500 /// This struct intelligently wraps an emoji so that it is rendered
501 /// only on systems that want emojis and renders a fallback on others.
502 ///
503 /// Example:
504 ///
505 /// ```rust
506 /// use console::Emoji;
507 /// println!("[3/4] {}Downloading ...", Emoji("�� ", ""));
508 /// println!("[4/4] {} Done!", Emoji("✨", ":-)"));
509 /// ```
510 #[derive(Copy, Clone)]
511 pub struct Emoji<'a, 'b>(pub &'a str, pub &'b str);
512 
513 impl<'a, 'b> Emoji<'a, 'b> {
new(emoji: &'a str, fallback: &'b str) -> Emoji<'a, 'b>514     pub fn new(emoji: &'a str, fallback: &'b str) -> Emoji<'a, 'b> {
515         Emoji(emoji, fallback)
516     }
517 }
518 
519 impl<'a, 'b> fmt::Display for Emoji<'a, 'b> {
fmt(&self, f: &mut fmt::Formatter) -> fmt::Result520     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
521         if wants_emoji() {
522             write!(f, "{}", self.0)
523         } else {
524             write!(f, "{}", self.1)
525         }
526     }
527 }
528 
str_width(s: &str) -> usize529 fn str_width(s: &str) -> usize {
530     #[cfg(feature = "unicode-width")]
531     {
532         use unicode_width::UnicodeWidthStr;
533         s.width()
534     }
535     #[cfg(not(feature = "unicode-width"))]
536     {
537         s.chars().count()
538     }
539 }
540 
541 #[cfg(feature = "ansi-parsing")]
char_width(c: char) -> usize542 fn char_width(c: char) -> usize {
543     #[cfg(feature = "unicode-width")]
544     {
545         use unicode_width::UnicodeWidthChar;
546         c.width().unwrap_or(0)
547     }
548     #[cfg(not(feature = "unicode-width"))]
549     {
550         let _c = c;
551         1
552     }
553 }
554 
555 /// Truncates a string to a certain number of characters.
556 ///
557 /// This ensures that escape codes are not screwed up in the process.
558 /// If the maximum length is hit the string will be truncated but
559 /// escapes code will still be honored.  If truncation takes place
560 /// the tail string will be appended.
truncate_str<'a>(s: &'a str, width: usize, tail: &str) -> Cow<'a, str>561 pub fn truncate_str<'a>(s: &'a str, width: usize, tail: &str) -> Cow<'a, str> {
562     #[cfg(feature = "ansi-parsing")]
563     {
564         use std::cmp::Ordering;
565         let mut iter = AnsiCodeIterator::new(s);
566         let mut length = 0;
567         let mut rv = None;
568 
569         while let Some(item) = iter.next() {
570             match item {
571                 (s, false) => {
572                     if rv.is_none() {
573                         if str_width(s) + length > width - str_width(tail) {
574                             let ts = iter.current_slice();
575 
576                             let mut s_byte = 0;
577                             let mut s_width = 0;
578                             let rest_width = width - str_width(tail) - length;
579                             for c in s.chars() {
580                                 s_byte += c.len_utf8();
581                                 s_width += char_width(c);
582                                 match s_width.cmp(&rest_width) {
583                                     Ordering::Equal => break,
584                                     Ordering::Greater => {
585                                         s_byte -= c.len_utf8();
586                                         break;
587                                     }
588                                     Ordering::Less => continue,
589                                 }
590                             }
591 
592                             let idx = ts.len() - s.len() + s_byte;
593                             let mut buf = ts[..idx].to_string();
594                             buf.push_str(tail);
595                             rv = Some(buf);
596                         }
597                         length += str_width(s);
598                     }
599                 }
600                 (s, true) => {
601                     if rv.is_some() {
602                         rv.as_mut().unwrap().push_str(s);
603                     }
604                 }
605             }
606         }
607 
608         if let Some(buf) = rv {
609             Cow::Owned(buf)
610         } else {
611             Cow::Borrowed(s)
612         }
613     }
614 
615     #[cfg(not(feature = "ansi-parsing"))]
616     {
617         if s.len() <= width - tail.len() {
618             Cow::Borrowed(s)
619         } else {
620             Cow::Owned(format!(
621                 "{}{}",
622                 s.get(..width - tail.len()).unwrap_or_default(),
623                 tail
624             ))
625         }
626     }
627 }
628 
629 /// Pads a string to fill a certain number of characters.
630 ///
631 /// This will honor ansi codes correctly and allows you to align a string
632 /// on the left, right or centered.  Additionally truncation can be enabled
633 /// by setting `truncate` to a string that should be used as a truncation
634 /// marker.
pad_str<'a>( s: &'a str, width: usize, align: Alignment, truncate: Option<&str>, ) -> Cow<'a, str>635 pub fn pad_str<'a>(
636     s: &'a str,
637     width: usize,
638     align: Alignment,
639     truncate: Option<&str>,
640 ) -> Cow<'a, str> {
641     pad_str_with(s, width, align, truncate, ' ')
642 }
643 /// Pads a string with specific padding to fill a certain number of characters.
644 ///
645 /// This will honor ansi codes correctly and allows you to align a string
646 /// on the left, right or centered.  Additionally truncation can be enabled
647 /// by setting `truncate` to a string that should be used as a truncation
648 /// marker.
pad_str_with<'a>( s: &'a str, width: usize, align: Alignment, truncate: Option<&str>, pad: char, ) -> Cow<'a, str>649 pub fn pad_str_with<'a>(
650     s: &'a str,
651     width: usize,
652     align: Alignment,
653     truncate: Option<&str>,
654     pad: char,
655 ) -> Cow<'a, str> {
656     let cols = measure_text_width(s);
657 
658     if cols >= width {
659         return match truncate {
660             None => Cow::Borrowed(s),
661             Some(tail) => truncate_str(s, width, tail),
662         };
663     }
664 
665     let diff = width - cols;
666 
667     let (left_pad, right_pad) = match align {
668         Alignment::Left => (0, diff),
669         Alignment::Right => (diff, 0),
670         Alignment::Center => (diff / 2, diff - diff / 2),
671     };
672 
673     let mut rv = String::new();
674     for _ in 0..left_pad {
675         rv.push(pad);
676     }
677     rv.push_str(s);
678     for _ in 0..right_pad {
679         rv.push(pad);
680     }
681     Cow::Owned(rv)
682 }
683 
684 #[test]
test_text_width()685 fn test_text_width() {
686     let s = style("foo")
687         .red()
688         .on_black()
689         .bold()
690         .force_styling(true)
691         .to_string();
692     assert_eq!(
693         measure_text_width(&s),
694         if cfg!(feature = "ansi-parsing") {
695             3
696         } else {
697             21
698         }
699     );
700 }
701 
702 #[test]
703 #[cfg(all(feature = "unicode-width", feature = "ansi-parsing"))]
test_truncate_str()704 fn test_truncate_str() {
705     let s = format!("foo {}", style("bar").red().force_styling(true));
706     assert_eq!(
707         &truncate_str(&s, 5, ""),
708         &format!("foo {}", style("b").red().force_styling(true))
709     );
710     let s = format!("foo {}", style("bar").red().force_styling(true));
711     assert_eq!(
712         &truncate_str(&s, 5, "!"),
713         &format!("foo {}", style("!").red().force_styling(true))
714     );
715     let s = format!("foo {} baz", style("bar").red().force_styling(true));
716     assert_eq!(
717         &truncate_str(&s, 10, "..."),
718         &format!("foo {}...", style("bar").red().force_styling(true))
719     );
720     let s = format!("foo {}", style("バー").red().force_styling(true));
721     assert_eq!(
722         &truncate_str(&s, 5, ""),
723         &format!("foo {}", style("").red().force_styling(true))
724     );
725     let s = format!("foo {}", style("バー").red().force_styling(true));
726     assert_eq!(
727         &truncate_str(&s, 6, ""),
728         &format!("foo {}", style("バ").red().force_styling(true))
729     );
730 }
731 
732 #[test]
test_truncate_str_no_ansi()733 fn test_truncate_str_no_ansi() {
734     assert_eq!(&truncate_str("foo bar", 5, ""), "foo b");
735     assert_eq!(&truncate_str("foo bar", 5, "!"), "foo !");
736     assert_eq!(&truncate_str("foo bar baz", 10, "..."), "foo bar...");
737 }
738 
739 #[test]
test_pad_str()740 fn test_pad_str() {
741     assert_eq!(pad_str("foo", 7, Alignment::Center, None), "  foo  ");
742     assert_eq!(pad_str("foo", 7, Alignment::Left, None), "foo    ");
743     assert_eq!(pad_str("foo", 7, Alignment::Right, None), "    foo");
744     assert_eq!(pad_str("foo", 3, Alignment::Left, None), "foo");
745     assert_eq!(pad_str("foobar", 3, Alignment::Left, None), "foobar");
746     assert_eq!(pad_str("foobar", 3, Alignment::Left, Some("")), "foo");
747     assert_eq!(
748         pad_str("foobarbaz", 6, Alignment::Left, Some("...")),
749         "foo..."
750     );
751 }
752 
753 #[test]
test_pad_str_with()754 fn test_pad_str_with() {
755     assert_eq!(
756         pad_str_with("foo", 7, Alignment::Center, None, '#'),
757         "##foo##"
758     );
759     assert_eq!(
760         pad_str_with("foo", 7, Alignment::Left, None, '#'),
761         "foo####"
762     );
763     assert_eq!(
764         pad_str_with("foo", 7, Alignment::Right, None, '#'),
765         "####foo"
766     );
767     assert_eq!(pad_str_with("foo", 3, Alignment::Left, None, '#'), "foo");
768     assert_eq!(
769         pad_str_with("foobar", 3, Alignment::Left, None, '#'),
770         "foobar"
771     );
772     assert_eq!(
773         pad_str_with("foobar", 3, Alignment::Left, Some(""), '#'),
774         "foo"
775     );
776     assert_eq!(
777         pad_str_with("foobarbaz", 6, Alignment::Left, Some("..."), '#'),
778         "foo..."
779     );
780 }
781