• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /// Terminal-styling container
2 ///
3 /// For now, this is the same as a [`Str`][crate::builder::Str].  This exists to reserve space in
4 /// the API for exposing terminal styling.
5 #[derive(Clone, Default, Debug, PartialEq, Eq)]
6 pub struct StyledStr {
7     #[cfg(feature = "color")]
8     pieces: Vec<(Option<Style>, String)>,
9     #[cfg(not(feature = "color"))]
10     pieces: String,
11 }
12 
13 impl StyledStr {
14     /// Create an empty buffer
15     #[cfg(feature = "color")]
new() -> Self16     pub const fn new() -> Self {
17         Self { pieces: Vec::new() }
18     }
19 
20     /// Create an empty buffer
21     #[cfg(not(feature = "color"))]
new() -> Self22     pub const fn new() -> Self {
23         Self {
24             pieces: String::new(),
25         }
26     }
27 
28     /// Display using [ANSI Escape Code](https://en.wikipedia.org/wiki/ANSI_escape_code) styling
29     #[cfg(feature = "color")]
ansi(&self) -> impl std::fmt::Display + '_30     pub fn ansi(&self) -> impl std::fmt::Display + '_ {
31         AnsiDisplay { styled: self }
32     }
33 
header(&mut self, msg: impl Into<String>)34     pub(crate) fn header(&mut self, msg: impl Into<String>) {
35         self.stylize_(Some(Style::Header), msg.into());
36     }
37 
literal(&mut self, msg: impl Into<String>)38     pub(crate) fn literal(&mut self, msg: impl Into<String>) {
39         self.stylize_(Some(Style::Literal), msg.into());
40     }
41 
placeholder(&mut self, msg: impl Into<String>)42     pub(crate) fn placeholder(&mut self, msg: impl Into<String>) {
43         self.stylize_(Some(Style::Placeholder), msg.into());
44     }
45 
46     #[cfg_attr(not(feature = "error-context"), allow(dead_code))]
good(&mut self, msg: impl Into<String>)47     pub(crate) fn good(&mut self, msg: impl Into<String>) {
48         self.stylize_(Some(Style::Good), msg.into());
49     }
50 
51     #[cfg_attr(not(feature = "error-context"), allow(dead_code))]
warning(&mut self, msg: impl Into<String>)52     pub(crate) fn warning(&mut self, msg: impl Into<String>) {
53         self.stylize_(Some(Style::Warning), msg.into());
54     }
55 
error(&mut self, msg: impl Into<String>)56     pub(crate) fn error(&mut self, msg: impl Into<String>) {
57         self.stylize_(Some(Style::Error), msg.into());
58     }
59 
60     #[allow(dead_code)]
hint(&mut self, msg: impl Into<String>)61     pub(crate) fn hint(&mut self, msg: impl Into<String>) {
62         self.stylize_(Some(Style::Hint), msg.into());
63     }
64 
none(&mut self, msg: impl Into<String>)65     pub(crate) fn none(&mut self, msg: impl Into<String>) {
66         self.stylize_(None, msg.into());
67     }
68 
stylize(&mut self, style: impl Into<Option<Style>>, msg: impl Into<String>)69     pub(crate) fn stylize(&mut self, style: impl Into<Option<Style>>, msg: impl Into<String>) {
70         self.stylize_(style.into(), msg.into());
71     }
72 
trim(&mut self)73     pub(crate) fn trim(&mut self) {
74         self.trim_start();
75         self.trim_end();
76     }
77 
trim_start(&mut self)78     pub(crate) fn trim_start(&mut self) {
79         if let Some((_, item)) = self.iter_mut().next() {
80             *item = item.trim_start().to_owned();
81         }
82     }
83 
84     #[cfg(feature = "color")]
trim_end(&mut self)85     pub(crate) fn trim_end(&mut self) {
86         if let Some((_, item)) = self.pieces.last_mut() {
87             *item = item.trim_end().to_owned();
88         }
89     }
90 
91     #[cfg(not(feature = "color"))]
trim_end(&mut self)92     pub(crate) fn trim_end(&mut self) {
93         self.pieces = self.pieces.trim_end().to_owned();
94     }
95 
96     #[cfg(feature = "help")]
indent(&mut self, initial: &str, trailing: &str)97     pub(crate) fn indent(&mut self, initial: &str, trailing: &str) {
98         if let Some((_, first)) = self.iter_mut().next() {
99             first.insert_str(0, initial);
100         }
101         let mut line_sep = "\n".to_owned();
102         line_sep.push_str(trailing);
103         for (_, content) in self.iter_mut() {
104             *content = content.replace('\n', &line_sep);
105         }
106     }
107 
108     #[cfg(all(not(feature = "wrap_help"), feature = "help"))]
wrap(&mut self, _hard_width: usize)109     pub(crate) fn wrap(&mut self, _hard_width: usize) {}
110 
111     #[cfg(feature = "wrap_help")]
wrap(&mut self, hard_width: usize)112     pub(crate) fn wrap(&mut self, hard_width: usize) {
113         let mut wrapper = crate::output::textwrap::wrap_algorithms::LineWrapper::new(hard_width);
114         for (_, content) in self.iter_mut() {
115             let mut total = Vec::new();
116             for (i, line) in content.split_inclusive('\n').enumerate() {
117                 if 0 < i {
118                     // start of a section does not imply newline
119                     wrapper.reset();
120                 }
121                 let line = crate::output::textwrap::word_separators::find_words_ascii_space(line)
122                     .collect::<Vec<_>>();
123                 total.extend(wrapper.wrap(line));
124             }
125             let total = total.join("");
126             *content = total;
127         }
128 
129         self.trim_end();
130     }
131 
132     #[cfg(feature = "color")]
stylize_(&mut self, style: Option<Style>, msg: String)133     fn stylize_(&mut self, style: Option<Style>, msg: String) {
134         if !msg.is_empty() {
135             self.pieces.push((style, msg));
136         }
137     }
138 
139     #[cfg(not(feature = "color"))]
stylize_(&mut self, _style: Option<Style>, msg: String)140     fn stylize_(&mut self, _style: Option<Style>, msg: String) {
141         self.pieces.push_str(&msg);
142     }
143 
144     #[inline(never)]
145     #[cfg(feature = "help")]
display_width(&self) -> usize146     pub(crate) fn display_width(&self) -> usize {
147         let mut width = 0;
148         for (_, c) in self.iter() {
149             width += crate::output::display_width(c);
150         }
151         width
152     }
153 
154     #[cfg(feature = "help")]
is_empty(&self) -> bool155     pub(crate) fn is_empty(&self) -> bool {
156         self.pieces.is_empty()
157     }
158 
159     #[cfg(feature = "color")]
iter(&self) -> impl Iterator<Item = (Option<Style>, &str)>160     pub(crate) fn iter(&self) -> impl Iterator<Item = (Option<Style>, &str)> {
161         self.pieces.iter().map(|(s, c)| (*s, c.as_str()))
162     }
163 
164     #[cfg(not(feature = "color"))]
iter(&self) -> impl Iterator<Item = (Option<Style>, &str)>165     pub(crate) fn iter(&self) -> impl Iterator<Item = (Option<Style>, &str)> {
166         [(None, self.pieces.as_str())].into_iter()
167     }
168 
169     #[cfg(feature = "color")]
iter_mut(&mut self) -> impl Iterator<Item = (Option<Style>, &mut String)>170     pub(crate) fn iter_mut(&mut self) -> impl Iterator<Item = (Option<Style>, &mut String)> {
171         self.pieces.iter_mut().map(|(s, c)| (*s, c))
172     }
173 
174     #[cfg(not(feature = "color"))]
iter_mut(&mut self) -> impl Iterator<Item = (Option<Style>, &mut String)>175     pub(crate) fn iter_mut(&mut self) -> impl Iterator<Item = (Option<Style>, &mut String)> {
176         [(None, &mut self.pieces)].into_iter()
177     }
178 
179     #[cfg(feature = "color")]
into_iter(self) -> impl Iterator<Item = (Option<Style>, String)>180     pub(crate) fn into_iter(self) -> impl Iterator<Item = (Option<Style>, String)> {
181         self.pieces.into_iter()
182     }
183 
184     #[cfg(not(feature = "color"))]
into_iter(self) -> impl Iterator<Item = (Option<Style>, String)>185     pub(crate) fn into_iter(self) -> impl Iterator<Item = (Option<Style>, String)> {
186         [(None, self.pieces)].into_iter()
187     }
188 
extend( &mut self, other: impl IntoIterator<Item = (impl Into<Option<Style>>, impl Into<String>)>, )189     pub(crate) fn extend(
190         &mut self,
191         other: impl IntoIterator<Item = (impl Into<Option<Style>>, impl Into<String>)>,
192     ) {
193         for (style, content) in other {
194             self.stylize(style.into(), content.into());
195         }
196     }
197 
198     #[cfg(feature = "color")]
write_colored(&self, buffer: &mut termcolor::Buffer) -> std::io::Result<()>199     pub(crate) fn write_colored(&self, buffer: &mut termcolor::Buffer) -> std::io::Result<()> {
200         use std::io::Write;
201         use termcolor::WriteColor;
202 
203         for (style, content) in &self.pieces {
204             let mut color = termcolor::ColorSpec::new();
205             match style {
206                 Some(Style::Header) => {
207                     color.set_bold(true);
208                     color.set_underline(true);
209                 }
210                 Some(Style::Literal) => {
211                     color.set_bold(true);
212                 }
213                 Some(Style::Placeholder) => {}
214                 Some(Style::Good) => {
215                     color.set_fg(Some(termcolor::Color::Green));
216                 }
217                 Some(Style::Warning) => {
218                     color.set_fg(Some(termcolor::Color::Yellow));
219                 }
220                 Some(Style::Error) => {
221                     color.set_fg(Some(termcolor::Color::Red));
222                     color.set_bold(true);
223                 }
224                 Some(Style::Hint) => {
225                     color.set_dimmed(true);
226                 }
227                 None => {}
228             }
229 
230             ok!(buffer.set_color(&color));
231             ok!(buffer.write_all(content.as_bytes()));
232             ok!(buffer.reset());
233         }
234 
235         Ok(())
236     }
237 }
238 
239 impl Default for &'_ StyledStr {
default() -> Self240     fn default() -> Self {
241         static DEFAULT: StyledStr = StyledStr::new();
242         &DEFAULT
243     }
244 }
245 
246 impl From<std::string::String> for StyledStr {
from(name: std::string::String) -> Self247     fn from(name: std::string::String) -> Self {
248         let mut styled = StyledStr::new();
249         styled.none(name);
250         styled
251     }
252 }
253 
254 impl From<&'_ std::string::String> for StyledStr {
from(name: &'_ std::string::String) -> Self255     fn from(name: &'_ std::string::String) -> Self {
256         let mut styled = StyledStr::new();
257         styled.none(name);
258         styled
259     }
260 }
261 
262 impl From<&'static str> for StyledStr {
from(name: &'static str) -> Self263     fn from(name: &'static str) -> Self {
264         let mut styled = StyledStr::new();
265         styled.none(name);
266         styled
267     }
268 }
269 
270 impl From<&'_ &'static str> for StyledStr {
from(name: &'_ &'static str) -> Self271     fn from(name: &'_ &'static str) -> Self {
272         StyledStr::from(*name)
273     }
274 }
275 
276 impl PartialOrd for StyledStr {
partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering>277     fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
278         Some(self.cmp(other))
279     }
280 }
281 
282 impl Ord for StyledStr {
cmp(&self, other: &Self) -> std::cmp::Ordering283     fn cmp(&self, other: &Self) -> std::cmp::Ordering {
284         self.iter().map(cmp_key).cmp(other.iter().map(cmp_key))
285     }
286 }
287 
cmp_key(c: (Option<Style>, &str)) -> (Option<usize>, &str)288 fn cmp_key(c: (Option<Style>, &str)) -> (Option<usize>, &str) {
289     let style = c.0.map(|s| s.as_usize());
290     let content = c.1;
291     (style, content)
292 }
293 
294 /// Color-unaware printing. Never uses coloring.
295 impl std::fmt::Display for StyledStr {
fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result296     fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
297         for (_, content) in self.iter() {
298             ok!(std::fmt::Display::fmt(content, f));
299         }
300 
301         Ok(())
302     }
303 }
304 
305 #[cfg(feature = "color")]
306 struct AnsiDisplay<'s> {
307     styled: &'s StyledStr,
308 }
309 
310 #[cfg(feature = "color")]
311 impl std::fmt::Display for AnsiDisplay<'_> {
fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result312     fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
313         let mut buffer = termcolor::Buffer::ansi();
314         ok!(self
315             .styled
316             .write_colored(&mut buffer)
317             .map_err(|_| std::fmt::Error));
318         let buffer = buffer.into_inner();
319         let buffer = ok!(String::from_utf8(buffer).map_err(|_| std::fmt::Error));
320         ok!(std::fmt::Display::fmt(&buffer, f));
321 
322         Ok(())
323     }
324 }
325 
326 #[derive(Copy, Clone, Debug, PartialEq, Eq)]
327 pub(crate) enum Style {
328     Header,
329     Literal,
330     Placeholder,
331     Good,
332     Warning,
333     Error,
334     Hint,
335 }
336 
337 impl Style {
as_usize(&self) -> usize338     fn as_usize(&self) -> usize {
339         match self {
340             Self::Header => 0,
341             Self::Literal => 1,
342             Self::Placeholder => 2,
343             Self::Good => 3,
344             Self::Warning => 4,
345             Self::Error => 5,
346             Self::Hint => 6,
347         }
348     }
349 }
350