clap_builder/builder/
styled_str.rs1#![cfg_attr(not(feature = "usage"), allow(dead_code))]
2use std::borrow::Cow;
3
4#[derive(Clone, Default, Debug, PartialEq, Eq, PartialOrd, Ord)]
25pub struct StyledStr(String);
26
27impl StyledStr {
28 pub const fn new() -> Self {
30 Self(String::new())
31 }
32
33 #[cfg(feature = "color")]
35 pub fn ansi(&self) -> impl std::fmt::Display + '_ {
36 self.0.as_str()
37 }
38
39 pub(crate) fn push_string(&mut self, msg: String) {
42 self.0.push_str(&msg);
43 }
44
45 pub fn push_str(&mut self, msg: &str) {
47 self.0.push_str(msg);
48 }
49
50 pub(crate) fn trim_start_lines(&mut self) {
51 if let Some(pos) = self.0.find('\n') {
52 let (leading, help) = self.0.split_at(pos + 1);
53 if leading.trim().is_empty() {
54 self.0 = help.to_owned();
55 }
56 }
57 }
58
59 pub(crate) fn trim_end(&mut self) {
60 self.0 = self.0.trim_end().to_owned();
61 }
62
63 #[cfg(feature = "help")]
64 pub(crate) fn replace_newline_var(&mut self) {
65 self.0 = self.0.replace("{n}", "\n");
66 }
67
68 #[cfg(feature = "help")]
69 pub(crate) fn indent(&mut self, initial: &str, trailing: &str) {
70 self.0.insert_str(0, initial);
71
72 let mut line_sep = "\n".to_owned();
73 line_sep.push_str(trailing);
74 self.0 = self.0.replace('\n', &line_sep);
75 }
76
77 #[cfg(all(not(feature = "wrap_help"), feature = "help"))]
78 pub(crate) fn wrap(&mut self, _hard_width: usize) {}
79
80 #[cfg(feature = "wrap_help")]
81 pub(crate) fn wrap(&mut self, hard_width: usize) {
82 let mut new = String::with_capacity(self.0.len());
83
84 let mut last = 0;
85 let mut wrapper = crate::output::textwrap::wrap_algorithms::LineWrapper::new(hard_width);
86 for content in self.iter_text() {
87 let current = content.as_ptr() as usize - self.0.as_str().as_ptr() as usize;
89 if last != current {
90 new.push_str(&self.0.as_str()[last..current]);
91 }
92 last = current + content.len();
93
94 for (i, line) in content.split_inclusive('\n').enumerate() {
95 if 0 < i {
96 wrapper.reset();
99 }
100 let line = crate::output::textwrap::word_separators::find_words_ascii_space(line)
101 .collect::<Vec<_>>();
102 new.extend(wrapper.wrap(line));
103 }
104 }
105 if last != self.0.len() {
106 new.push_str(&self.0.as_str()[last..]);
107 }
108 new = new.trim_end().to_owned();
109
110 self.0 = new;
111 }
112
113 #[inline(never)]
114 #[cfg(feature = "help")]
115 pub(crate) fn display_width(&self) -> usize {
116 let mut width = 0;
117 for c in self.iter_text() {
118 width += crate::output::display_width(c);
119 }
120 width
121 }
122
123 #[cfg(feature = "help")]
124 pub(crate) fn is_empty(&self) -> bool {
125 self.0.is_empty()
126 }
127
128 #[cfg(feature = "help")]
129 pub(crate) fn as_styled_str(&self) -> &str {
130 &self.0
131 }
132
133 #[cfg(feature = "color")]
134 pub(crate) fn iter_text(&self) -> impl Iterator<Item = &str> {
135 anstream::adapter::strip_str(&self.0)
136 }
137
138 #[cfg(not(feature = "color"))]
139 pub(crate) fn iter_text(&self) -> impl Iterator<Item = &str> {
140 [self.0.as_str()].into_iter()
141 }
142
143 pub(crate) fn push_styled(&mut self, other: &Self) {
144 self.0.push_str(&other.0);
145 }
146
147 pub(crate) fn write_to(&self, buffer: &mut dyn std::io::Write) -> std::io::Result<()> {
148 ok!(buffer.write_all(self.0.as_bytes()));
149
150 Ok(())
151 }
152}
153
154impl Default for &'_ StyledStr {
155 fn default() -> Self {
156 static DEFAULT: StyledStr = StyledStr::new();
157 &DEFAULT
158 }
159}
160
161impl From<String> for StyledStr {
162 fn from(name: String) -> Self {
163 StyledStr(name)
164 }
165}
166
167impl From<&'_ String> for StyledStr {
168 fn from(name: &'_ String) -> Self {
169 let mut styled = StyledStr::new();
170 styled.push_str(name);
171 styled
172 }
173}
174
175impl From<&'static str> for StyledStr {
176 fn from(name: &'static str) -> Self {
177 let mut styled = StyledStr::new();
178 styled.push_str(name);
179 styled
180 }
181}
182
183impl From<&'_ &'static str> for StyledStr {
184 fn from(name: &'_ &'static str) -> Self {
185 StyledStr::from(*name)
186 }
187}
188
189impl From<Cow<'static, str>> for StyledStr {
190 fn from(cow: Cow<'static, str>) -> Self {
191 match cow {
192 Cow::Borrowed(s) => StyledStr::from(s),
193 Cow::Owned(s) => StyledStr::from(s),
194 }
195 }
196}
197
198impl std::fmt::Write for StyledStr {
199 #[inline]
200 fn write_str(&mut self, s: &str) -> Result<(), std::fmt::Error> {
201 self.0.push_str(s);
202 Ok(())
203 }
204
205 #[inline]
206 fn write_char(&mut self, c: char) -> Result<(), std::fmt::Error> {
207 self.0.push(c);
208 Ok(())
209 }
210}
211
212impl std::fmt::Display for StyledStr {
214 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
215 for part in self.iter_text() {
216 part.fmt(f)?;
217 }
218
219 Ok(())
220 }
221}
222
223#[cfg(test)]
224#[cfg(feature = "wrap_help")]
225mod wrap_tests {
226 use super::*;
227
228 use snapbox::assert_data_eq;
229 use snapbox::str;
230
231 #[test]
232 #[cfg(feature = "wrap_help")]
233 fn wrap_unstyled() {
234 let style = anstyle::Style::new();
235 let input = format!("{style}12345{style:#} {style}12345{style:#} {style}12345{style:#} {style}12345{style:#}");
236 let mut actual = StyledStr::new();
237 actual.push_string(input);
238 actual.wrap(20);
239 assert_data_eq!(
240 actual.ansi().to_string(),
241 str![[r#"
24212345 12345 12345
24312345
244"#]]
245 );
246 }
247
248 #[test]
249 #[cfg(feature = "wrap_help")]
250 fn wrap_styled() {
251 let style = anstyle::Style::new().bold();
252 let input = format!("{style}12345{style:#} {style}12345{style:#} {style}12345{style:#} {style}12345{style:#}");
253 let mut actual = StyledStr::new();
254 actual.push_string(input);
255 actual.wrap(20);
256 assert_data_eq!(
257 actual.ansi().to_string(),
258 str![[r#"
259[1m12345[0m [1m12345[0m [1m12345[0m [1m
26012345[0m
261"#]]
262 );
263 }
264}
265
266#[cfg(test)]
267mod tests {
268 use super::*;
269
270 #[test]
271 fn from_cow_borrowed() {
272 let cow = Cow::Borrowed("hello");
273 let styled = StyledStr::from(cow);
274 assert_eq!(styled, StyledStr::from("hello"));
275 }
276
277 #[test]
278 fn from_cow_owned() {
279 let cow = Cow::Owned("world".to_string());
280 let styled = StyledStr::from(cow);
281 assert_eq!(styled, StyledStr::from("world"));
282 }
283}