1use colored::*;
4
5use std::cmp::min;
6use std::fmt;
7use std::time::Duration;
8
9pub trait TryUnwrap {
14 type Val;
15 fn try_unwrap(self) -> Result<Self::Val, String>;
16}
17
18impl<T> TryUnwrap for Option<T> {
19 type Val = T;
20
21 fn try_unwrap(self) -> Result<Self::Val, String> {
22 if let Some(val) = self {
23 Ok(val)
24 } else {
25 Err("empty output".to_string())
26 }
27 }
28}
29
30impl<T, E: fmt::Display> TryUnwrap for Result<T, E> {
31 type Val = T;
32
33 fn try_unwrap(self) -> Result<Self::Val, String> {
34 self.map_err(|err| format!("{}", err))
35 }
36}
37
38const DEFAULT_WIDTH: usize = 30;
43
44pub struct Line {
47 text: String,
48 duration: Option<Duration>,
49 state: Option<ColoredString>,
50}
51
52impl Line {
53 pub fn new(text: impl Into<String>) -> Self {
54 Self {
55 text: text.into(),
56 duration: None,
57 state: None,
58 }
59 }
60
61 pub fn with_duration(mut self, duration: Duration) -> Self {
62 self.duration = Some(duration);
63 self
64 }
65
66 pub fn with_state(mut self, state: impl Into<ColoredString>) -> Self {
67 self.state = Some(state.into());
68 self
69 }
70}
71
72impl fmt::Display for Line {
73 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
74 let display_width = f.width().unwrap_or(DEFAULT_WIDTH);
75
76 let duration = self
77 .duration
78 .map(|duration| format!(" ({:.2?})", duration))
79 .unwrap_or_else(String::new);
80
81 write!(f, "{}{}", self.text, duration.bright_black())?;
82
83 if let Some(state) = &self.state {
84 let width = self.text.chars().count() + 1 + duration.chars().count();
85 let dots = display_width - min(display_width - 5, width) - 2;
86 let dots = ".".repeat(dots);
87 write!(f, " {}", dots.bright_black())?;
88
89 if state.contains('\n') {
90 for line in state.trim_matches('\n').lines() {
91 write!(f, "\n {}", line.bold())?;
92 }
93 } else {
94 write!(f, " {}", state.clone().bold())?;
95 }
96 }
97
98 Ok(())
99 }
100}