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)]
24 pub struct StyledStr(String);
25 
26 impl StyledStr {
27     /// Create an empty buffer
new() -> Self28     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")]
ansi(&self) -> impl std::fmt::Display + '_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)`
push_string(&mut self, msg: String)40     pub(crate) fn push_string(&mut self, msg: String) {
41         self.0.push_str(&msg);
42     }
43 
push_str(&mut self, msg: &str)44     pub(crate) fn push_str(&mut self, msg: &str) {
45         self.0.push_str(msg);
46     }
47 
trim_start_lines(&mut self)48     pub(crate) fn trim_start_lines(&mut self) {
49         if let Some(pos) = self.0.find('\n') {
50             let (leading, help) = self.0.split_at(pos + 1);
51             if leading.trim().is_empty() {
52                 self.0 = help.to_owned()
53             }
54         }
55     }
56 
trim_end(&mut self)57     pub(crate) fn trim_end(&mut self) {
58         self.0 = self.0.trim_end().to_owned()
59     }
60 
61     #[cfg(feature = "help")]
replace_newline_var(&mut self)62     pub(crate) fn replace_newline_var(&mut self) {
63         self.0 = self.0.replace("{n}", "\n");
64     }
65 
66     #[cfg(feature = "help")]
indent(&mut self, initial: &str, trailing: &str)67     pub(crate) fn indent(&mut self, initial: &str, trailing: &str) {
68         self.0.insert_str(0, initial);
69 
70         let mut line_sep = "\n".to_owned();
71         line_sep.push_str(trailing);
72         self.0 = self.0.replace('\n', &line_sep);
73     }
74 
75     #[cfg(all(not(feature = "wrap_help"), feature = "help"))]
wrap(&mut self, _hard_width: usize)76     pub(crate) fn wrap(&mut self, _hard_width: usize) {}
77 
78     #[cfg(feature = "wrap_help")]
wrap(&mut self, hard_width: usize)79     pub(crate) fn wrap(&mut self, hard_width: usize) {
80         let mut new = String::with_capacity(self.0.len());
81 
82         let mut last = 0;
83         let mut wrapper = crate::output::textwrap::wrap_algorithms::LineWrapper::new(hard_width);
84         for content in self.iter_text() {
85             // Preserve styling
86             let current = content.as_ptr() as usize - self.0.as_str().as_ptr() as usize;
87             if last != current {
88                 new.push_str(&self.0.as_str()[last..current]);
89             }
90             last = current + content.len();
91 
92             for (i, line) in content.split_inclusive('\n').enumerate() {
93                 if 0 < i {
94                     // reset char count on newline, skipping the start as we might have carried
95                     // over from a prior block of styled text
96                     wrapper.reset();
97                 }
98                 let line = crate::output::textwrap::word_separators::find_words_ascii_space(line)
99                     .collect::<Vec<_>>();
100                 new.extend(wrapper.wrap(line));
101             }
102         }
103         if last != self.0.len() {
104             new.push_str(&self.0.as_str()[last..]);
105         }
106         new = new.trim_end().to_owned();
107 
108         self.0 = new;
109     }
110 
111     #[inline(never)]
112     #[cfg(feature = "help")]
display_width(&self) -> usize113     pub(crate) fn display_width(&self) -> usize {
114         let mut width = 0;
115         for c in self.iter_text() {
116             width += crate::output::display_width(c);
117         }
118         width
119     }
120 
121     #[cfg(feature = "help")]
is_empty(&self) -> bool122     pub(crate) fn is_empty(&self) -> bool {
123         self.0.is_empty()
124     }
125 
126     #[cfg(feature = "help")]
as_styled_str(&self) -> &str127     pub(crate) fn as_styled_str(&self) -> &str {
128         &self.0
129     }
130 
131     #[cfg(feature = "color")]
iter_text(&self) -> impl Iterator<Item = &str>132     pub(crate) fn iter_text(&self) -> impl Iterator<Item = &str> {
133         anstream::adapter::strip_str(&self.0)
134     }
135 
136     #[cfg(not(feature = "color"))]
iter_text(&self) -> impl Iterator<Item = &str>137     pub(crate) fn iter_text(&self) -> impl Iterator<Item = &str> {
138         [self.0.as_str()].into_iter()
139     }
140 
push_styled(&mut self, other: &Self)141     pub(crate) fn push_styled(&mut self, other: &Self) {
142         self.0.push_str(&other.0);
143     }
144 
write_to(&self, buffer: &mut dyn std::io::Write) -> std::io::Result<()>145     pub(crate) fn write_to(&self, buffer: &mut dyn std::io::Write) -> std::io::Result<()> {
146         ok!(buffer.write_all(self.0.as_bytes()));
147 
148         Ok(())
149     }
150 }
151 
152 impl Default for &'_ StyledStr {
default() -> Self153     fn default() -> Self {
154         static DEFAULT: StyledStr = StyledStr::new();
155         &DEFAULT
156     }
157 }
158 
159 impl From<std::string::String> for StyledStr {
from(name: std::string::String) -> Self160     fn from(name: std::string::String) -> Self {
161         StyledStr(name)
162     }
163 }
164 
165 impl From<&'_ std::string::String> for StyledStr {
from(name: &'_ std::string::String) -> Self166     fn from(name: &'_ std::string::String) -> Self {
167         let mut styled = StyledStr::new();
168         styled.push_str(name);
169         styled
170     }
171 }
172 
173 impl From<&'static str> for StyledStr {
from(name: &'static str) -> Self174     fn from(name: &'static str) -> Self {
175         let mut styled = StyledStr::new();
176         styled.push_str(name);
177         styled
178     }
179 }
180 
181 impl From<&'_ &'static str> for StyledStr {
from(name: &'_ &'static str) -> Self182     fn from(name: &'_ &'static str) -> Self {
183         StyledStr::from(*name)
184     }
185 }
186 
187 impl std::fmt::Write for StyledStr {
188     #[inline]
write_str(&mut self, s: &str) -> Result<(), std::fmt::Error>189     fn write_str(&mut self, s: &str) -> Result<(), std::fmt::Error> {
190         self.0.push_str(s);
191         Ok(())
192     }
193 
194     #[inline]
write_char(&mut self, c: char) -> Result<(), std::fmt::Error>195     fn write_char(&mut self, c: char) -> Result<(), std::fmt::Error> {
196         self.0.push(c);
197         Ok(())
198     }
199 }
200 
201 /// Color-unaware printing. Never uses coloring.
202 impl std::fmt::Display for StyledStr {
fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result203     fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
204         for part in self.iter_text() {
205             part.fmt(f)?;
206         }
207 
208         Ok(())
209     }
210 }
211