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