1use std::borrow::Cow;
2use std::collections::BTreeSet;
3use std::env;
4use std::fmt;
5use std::sync::atomic::{AtomicBool, Ordering};
6
7use lazy_static::lazy_static;
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
25lazy_static! {
26 static ref STDOUT_COLORS: AtomicBool = AtomicBool::new(default_colors_enabled(&Term::stdout()));
27 static ref STDERR_COLORS: AtomicBool = AtomicBool::new(default_colors_enabled(&Term::stderr()));
28}
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(order: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, order: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(order: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, order: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() -> Style {
170 Style::new()
171 }
172}
173
174impl Style {
175 /// Returns an empty default style.
176 pub fn new() -> Style {
177 Style {
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) -> Style {
196 let mut rv = Style::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 fn force_styling(mut self, value: bool) -> Style {
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 fn for_stderr(mut self) -> Style {
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 fn for_stdout(mut self) -> Style {
273 self.for_stderr = false;
274 self
275 }
276
277 /// Sets a foreground color.
278 #[inline]
279 pub fn fg(mut self, color: Color) -> Style {
280 self.fg = Some(color);
281 self
282 }
283
284 /// Sets a background color.
285 #[inline]
286 pub fn bg(mut self, color: Color) -> Style {
287 self.bg = Some(color);
288 self
289 }
290
291 /// Adds a attr.
292 #[inline]
293 pub fn attr(mut self, attr: Attribute) -> Style {
294 self.attrs.insert(attr);
295 self
296 }
297
298 #[inline]
299 pub fn black(self) -> Style {
300 self.fg(Color::Black)
301 }
302 #[inline]
303 pub fn red(self) -> Style {
304 self.fg(Color::Red)
305 }
306 #[inline]
307 pub fn green(self) -> Style {
308 self.fg(Color::Green)
309 }
310 #[inline]
311 pub fn yellow(self) -> Style {
312 self.fg(Color::Yellow)
313 }
314 #[inline]
315 pub fn blue(self) -> Style {
316 self.fg(Color::Blue)
317 }
318 #[inline]
319 pub fn magenta(self) -> Style {
320 self.fg(Color::Magenta)
321 }
322 #[inline]
323 pub fn cyan(self) -> Style {
324 self.fg(Color::Cyan)
325 }
326 #[inline]
327 pub fn white(self) -> Style {
328 self.fg(Color::White)
329 }
330 #[inline]
331 pub fn color256(self, color: u8) -> Style {
332 self.fg(Color::Color256(color))
333 }
334
335 #[inline]
336 pub fn bright(mut self) -> Style {
337 self.fg_bright = true;
338 self
339 }
340
341 #[inline]
342 pub fn on_black(self) -> Style {
343 self.bg(Color::Black)
344 }
345 #[inline]
346 pub fn on_red(self) -> Style {
347 self.bg(Color::Red)
348 }
349 #[inline]
350 pub fn on_green(self) -> Style {
351 self.bg(Color::Green)
352 }
353 #[inline]
354 pub fn on_yellow(self) -> Style {
355 self.bg(Color::Yellow)
356 }
357 #[inline]
358 pub fn on_blue(self) -> Style {
359 self.bg(Color::Blue)
360 }
361 #[inline]
362 pub fn on_magenta(self) -> Style {
363 self.bg(Color::Magenta)
364 }
365 #[inline]
366 pub fn on_cyan(self) -> Style {
367 self.bg(Color::Cyan)
368 }
369 #[inline]
370 pub fn on_white(self) -> Style {
371 self.bg(Color::White)
372 }
373 #[inline]
374 pub fn on_color256(self, color: u8) -> Style {
375 self.bg(Color::Color256(color))
376 }
377
378 #[inline]
379 pub fn on_bright(mut self) -> Style {
380 self.bg_bright = true;
381 self
382 }
383
384 #[inline]
385 pub fn bold(self) -> Style {
386 self.attr(Attribute::Bold)
387 }
388 #[inline]
389 pub fn dim(self) -> Style {
390 self.attr(Attribute::Dim)
391 }
392 #[inline]
393 pub fn italic(self) -> Style {
394 self.attr(Attribute::Italic)
395 }
396 #[inline]
397 pub fn underlined(self) -> Style {
398 self.attr(Attribute::Underlined)
399 }
400 #[inline]
401 pub fn blink(self) -> Style {
402 self.attr(Attribute::Blink)
403 }
404 #[inline]
405 pub fn blink_fast(self) -> Style {
406 self.attr(Attribute::BlinkFast)
407 }
408 #[inline]
409 pub fn reverse(self) -> Style {
410 self.attr(Attribute::Reverse)
411 }
412 #[inline]
413 pub fn hidden(self) -> Style {
414 self.attr(Attribute::Hidden)
415 }
416 #[inline]
417 pub fn strikethrough(self) -> Style {
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<'a, 'b> fmt::Display for Emoji<'a, 'b> {
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")]
722fn 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/// Truncates a string to a certain number of characters.
736///
737/// This ensures that escape codes are not screwed up in the process.
738/// If the maximum length is hit the string will be truncated but
739/// escapes code will still be honored. If truncation takes place
740/// the tail string will be appended.
741pub fn truncate_str<'a>(s: &'a str, width: usize, tail: &str) -> Cow<'a, str> {
742 #[cfg(feature = "ansi-parsing")]
743 {
744 use std::cmp::Ordering;
745 let mut iter = AnsiCodeIterator::new(s);
746 let mut length = 0;
747 let mut rv = None;
748
749 while let Some(item) = iter.next() {
750 match item {
751 (s, false) => {
752 if rv.is_none() {
753 if str_width(s) + length > width - str_width(tail) {
754 let ts = iter.current_slice();
755
756 let mut s_byte = 0;
757 let mut s_width = 0;
758 let rest_width = width - str_width(tail) - length;
759 for c in s.chars() {
760 s_byte += c.len_utf8();
761 s_width += char_width(c);
762 match s_width.cmp(&rest_width) {
763 Ordering::Equal => break,
764 Ordering::Greater => {
765 s_byte -= c.len_utf8();
766 break;
767 }
768 Ordering::Less => continue,
769 }
770 }
771
772 let idx = ts.len() - s.len() + s_byte;
773 let mut buf = ts[..idx].to_string();
774 buf.push_str(tail);
775 rv = Some(buf);
776 }
777 length += str_width(s);
778 }
779 }
780 (s, true) => {
781 if rv.is_some() {
782 rv.as_mut().unwrap().push_str(s);
783 }
784 }
785 }
786 }
787
788 if let Some(buf) = rv {
789 Cow::Owned(buf)
790 } else {
791 Cow::Borrowed(s)
792 }
793 }
794
795 #[cfg(not(feature = "ansi-parsing"))]
796 {
797 if s.len() <= width - tail.len() {
798 Cow::Borrowed(s)
799 } else {
800 Cow::Owned(format!(
801 "{}{}",
802 s.get(..width - tail.len()).unwrap_or_default(),
803 tail
804 ))
805 }
806 }
807}
808
809/// Pads a string to fill a certain number of characters.
810///
811/// This will honor ansi codes correctly and allows you to align a string
812/// on the left, right or centered. Additionally truncation can be enabled
813/// by setting `truncate` to a string that should be used as a truncation
814/// marker.
815pub fn pad_str<'a>(
816 s: &'a str,
817 width: usize,
818 align: Alignment,
819 truncate: Option<&str>,
820) -> Cow<'a, str> {
821 pad_str_with(s, width, align, truncate, pad:' ')
822}
823/// Pads a string with specific padding to fill a certain number of characters.
824///
825/// This will honor ansi codes correctly and allows you to align a string
826/// on the left, right or centered. Additionally truncation can be enabled
827/// by setting `truncate` to a string that should be used as a truncation
828/// marker.
829pub fn pad_str_with<'a>(
830 s: &'a str,
831 width: usize,
832 align: Alignment,
833 truncate: Option<&str>,
834 pad: char,
835) -> Cow<'a, str> {
836 let cols = measure_text_width(s);
837
838 if cols >= width {
839 return match truncate {
840 None => Cow::Borrowed(s),
841 Some(tail) => truncate_str(s, width, tail),
842 };
843 }
844
845 let diff = width - cols;
846
847 let (left_pad, right_pad) = match align {
848 Alignment::Left => (0, diff),
849 Alignment::Right => (diff, 0),
850 Alignment::Center => (diff / 2, diff - diff / 2),
851 };
852
853 let mut rv = String::new();
854 for _ in 0..left_pad {
855 rv.push(pad);
856 }
857 rv.push_str(s);
858 for _ in 0..right_pad {
859 rv.push(pad);
860 }
861 Cow::Owned(rv)
862}
863
864#[test]
865fn test_text_width() {
866 let s: String = styleStyledObject<&str>(val:"foo")
867 .red()
868 .on_black()
869 .bold()
870 .force_styling(true)
871 .to_string();
872 assert_eq!(
873 measure_text_width(&s),
874 if cfg!(feature = "ansi-parsing") {
875 3
876 } else if cfg!(feature = "unicode-width") {
877 17
878 } else {
879 21
880 }
881 );
882}
883
884#[test]
885#[cfg(all(feature = "unicode-width", feature = "ansi-parsing"))]
886fn test_truncate_str() {
887 let s = format!("foo {}", style("bar").red().force_styling(true));
888 assert_eq!(
889 &truncate_str(&s, 5, ""),
890 &format!("foo {}", style("b").red().force_styling(true))
891 );
892 let s = format!("foo {}", style("bar").red().force_styling(true));
893 assert_eq!(
894 &truncate_str(&s, 5, "!"),
895 &format!("foo {}", style("!").red().force_styling(true))
896 );
897 let s = format!("foo {} baz", style("bar").red().force_styling(true));
898 assert_eq!(
899 &truncate_str(&s, 10, "..."),
900 &format!("foo {}...", style("bar").red().force_styling(true))
901 );
902 let s = format!("foo {}", style("バー").red().force_styling(true));
903 assert_eq!(
904 &truncate_str(&s, 5, ""),
905 &format!("foo {}", style("").red().force_styling(true))
906 );
907 let s = format!("foo {}", style("バー").red().force_styling(true));
908 assert_eq!(
909 &truncate_str(&s, 6, ""),
910 &format!("foo {}", style("バ").red().force_styling(true))
911 );
912}
913
914#[test]
915fn test_truncate_str_no_ansi() {
916 assert_eq!(&truncate_str("foo bar", 5, ""), "foo b");
917 assert_eq!(&truncate_str("foo bar", 5, "!"), "foo !");
918 assert_eq!(&truncate_str("foo bar baz", 10, "..."), "foo bar...");
919}
920
921#[test]
922fn test_pad_str() {
923 assert_eq!(pad_str("foo", 7, Alignment::Center, None), " foo ");
924 assert_eq!(pad_str("foo", 7, Alignment::Left, None), "foo ");
925 assert_eq!(pad_str("foo", 7, Alignment::Right, None), " foo");
926 assert_eq!(pad_str("foo", 3, Alignment::Left, None), "foo");
927 assert_eq!(pad_str("foobar", 3, Alignment::Left, None), "foobar");
928 assert_eq!(pad_str("foobar", 3, Alignment::Left, Some("")), "foo");
929 assert_eq!(
930 pad_str("foobarbaz", 6, Alignment::Left, Some("...")),
931 "foo..."
932 );
933}
934
935#[test]
936fn test_pad_str_with() {
937 assert_eq!(
938 pad_str_with("foo", 7, Alignment::Center, None, '#'),
939 "##foo##"
940 );
941 assert_eq!(
942 pad_str_with("foo", 7, Alignment::Left, None, '#'),
943 "foo####"
944 );
945 assert_eq!(
946 pad_str_with("foo", 7, Alignment::Right, None, '#'),
947 "####foo"
948 );
949 assert_eq!(pad_str_with("foo", 3, Alignment::Left, None, '#'), "foo");
950 assert_eq!(
951 pad_str_with("foobar", 3, Alignment::Left, None, '#'),
952 "foobar"
953 );
954 assert_eq!(
955 pad_str_with("foobar", 3, Alignment::Left, Some(""), '#'),
956 "foo"
957 );
958 assert_eq!(
959 pad_str_with("foobarbaz", 6, Alignment::Left, Some("..."), '#'),
960 "foo..."
961 );
962}
963