clap_builder/builder/
styled_str.rs

1#![cfg_attr(not(feature = "usage"), allow(dead_code))]
2
3/// Terminal-styling container
4///
5/// Styling may be encoded as [ANSI Escape Code](https://en.wikipedia.org/wiki/ANSI_escape_code)
6///
7/// # Examples
8///
9/// ```rust
10/// # use clap_builder as clap;
11/// // `cstr!` converts tags to ANSI codes
12/// let after_help: &'static str = color_print::cstr!(
13/// r#"<bold><underline>Examples</underline></bold>
14///
15///   <dim>$</dim> <bold>mybin --input file.toml</bold>
16/// "#);
17///
18/// let cmd = clap::Command::new("mybin")
19///     .after_help(after_help)  // The `&str` gets converted into a `StyledStr`
20///     // ...
21/// #   ;
22/// ```
23#[derive(Clone, Default, Debug, PartialEq, Eq, PartialOrd, Ord)]
24pub struct StyledStr(String);
25
26impl StyledStr {
27    /// Create an empty buffer
28    pub const fn new() -> Self {
29        Self(String::new())
30    }
31
32    /// Display using [ANSI Escape Code](https://en.wikipedia.org/wiki/ANSI_escape_code) styling
33    #[cfg(feature = "color")]
34    pub fn ansi(&self) -> impl std::fmt::Display + '_ {
35        self.0.as_str()
36    }
37
38    /// May allow the compiler to consolidate the `Drop`s for `msg`, reducing code size compared to
39    /// `styled.push_str(&msg)`
40    pub(crate) fn push_string(&mut self, msg: String) {
41        self.0.push_str(&msg);
42    }
43
44    /// Appends a given string slice onto the end of this `StyledStr`.
45    pub fn push_str(&mut self, msg: &str) {
46        self.0.push_str(msg);
47    }
48
49    pub(crate) fn trim_start_lines(&mut self) {
50        if let Some(pos) = self.0.find('\n') {
51            let (leading, help) = self.0.split_at(pos + 1);
52            if leading.trim().is_empty() {
53                self.0 = help.to_owned();
54            }
55        }
56    }
57
58    pub(crate) fn trim_end(&mut self) {
59        self.0 = self.0.trim_end().to_owned();
60    }
61
62    #[cfg(feature = "help")]
63    pub(crate) fn replace_newline_var(&mut self) {
64        self.0 = self.0.replace("{n}", "\n");
65    }
66
67    #[cfg(feature = "help")]
68    pub(crate) fn indent(&mut self, initial: &str, trailing: &str) {
69        self.0.insert_str(0, initial);
70
71        let mut line_sep = "\n".to_owned();
72        line_sep.push_str(trailing);
73        self.0 = self.0.replace('\n', &line_sep);
74    }
75
76    #[cfg(all(not(feature = "wrap_help"), feature = "help"))]
77    pub(crate) fn wrap(&mut self, _hard_width: usize) {}
78
79    #[cfg(feature = "wrap_help")]
80    pub(crate) fn wrap(&mut self, hard_width: usize) {
81        let mut new = String::with_capacity(self.0.len());
82
83        let mut last = 0;
84        let mut wrapper = crate::output::textwrap::wrap_algorithms::LineWrapper::new(hard_width);
85        for content in self.iter_text() {
86            // Preserve styling
87            let current = content.as_ptr() as usize - self.0.as_str().as_ptr() as usize;
88            if last != current {
89                new.push_str(&self.0.as_str()[last..current]);
90            }
91            last = current + content.len();
92
93            for (i, line) in content.split_inclusive('\n').enumerate() {
94                if 0 < i {
95                    // reset char count on newline, skipping the start as we might have carried
96                    // over from a prior block of styled text
97                    wrapper.reset();
98                }
99                let line = crate::output::textwrap::word_separators::find_words_ascii_space(line)
100                    .collect::<Vec<_>>();
101                new.extend(wrapper.wrap(line));
102            }
103        }
104        if last != self.0.len() {
105            new.push_str(&self.0.as_str()[last..]);
106        }
107        new = new.trim_end().to_owned();
108
109        self.0 = new;
110    }
111
112    #[inline(never)]
113    #[cfg(feature = "help")]
114    pub(crate) fn display_width(&self) -> usize {
115        let mut width = 0;
116        for c in self.iter_text() {
117            width += crate::output::display_width(c);
118        }
119        width
120    }
121
122    #[cfg(feature = "help")]
123    pub(crate) fn is_empty(&self) -> bool {
124        self.0.is_empty()
125    }
126
127    #[cfg(feature = "help")]
128    pub(crate) fn as_styled_str(&self) -> &str {
129        &self.0
130    }
131
132    #[cfg(feature = "color")]
133    pub(crate) fn iter_text(&self) -> impl Iterator<Item = &str> {
134        anstream::adapter::strip_str(&self.0)
135    }
136
137    #[cfg(not(feature = "color"))]
138    pub(crate) fn iter_text(&self) -> impl Iterator<Item = &str> {
139        [self.0.as_str()].into_iter()
140    }
141
142    pub(crate) fn push_styled(&mut self, other: &Self) {
143        self.0.push_str(&other.0);
144    }
145
146    pub(crate) fn write_to(&self, buffer: &mut dyn std::io::Write) -> std::io::Result<()> {
147        ok!(buffer.write_all(self.0.as_bytes()));
148
149        Ok(())
150    }
151}
152
153impl Default for &'_ StyledStr {
154    fn default() -> Self {
155        static DEFAULT: StyledStr = StyledStr::new();
156        &DEFAULT
157    }
158}
159
160impl From<String> for StyledStr {
161    fn from(name: String) -> Self {
162        StyledStr(name)
163    }
164}
165
166impl From<&'_ String> for StyledStr {
167    fn from(name: &'_ String) -> Self {
168        let mut styled = StyledStr::new();
169        styled.push_str(name);
170        styled
171    }
172}
173
174impl From<&'static str> for StyledStr {
175    fn from(name: &'static str) -> Self {
176        let mut styled = StyledStr::new();
177        styled.push_str(name);
178        styled
179    }
180}
181
182impl From<&'_ &'static str> for StyledStr {
183    fn from(name: &'_ &'static str) -> Self {
184        StyledStr::from(*name)
185    }
186}
187
188impl std::fmt::Write for StyledStr {
189    #[inline]
190    fn write_str(&mut self, s: &str) -> Result<(), std::fmt::Error> {
191        self.0.push_str(s);
192        Ok(())
193    }
194
195    #[inline]
196    fn write_char(&mut self, c: char) -> Result<(), std::fmt::Error> {
197        self.0.push(c);
198        Ok(())
199    }
200}
201
202/// Color-unaware printing. Never uses coloring.
203impl std::fmt::Display for StyledStr {
204    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
205        for part in self.iter_text() {
206            part.fmt(f)?;
207        }
208
209        Ok(())
210    }
211}