1 | //! Colorize and stylize strings for terminal at compile-time, by using an HTML-like syntax. |
2 | //! |
3 | //! This library provides the following macros: |
4 | //! |
5 | //! - `cformat!(<FORMAT_STRING> [, ARGS...])` |
6 | //! - `cprint!(<FORMAT_STRING> [, ARGS...])` |
7 | //! - `cprintln!(<FORMAT_STRING> [, ARGS...])` |
8 | //! - `ceprint!(<FORMAT_STRING> [, ARGS...])` |
9 | //! - `ceprintln!(<FORMAT_STRING> [, ARGS...])` |
10 | //! - `cwrite!(f, <FORMAT_STRING> [, ARGS...])` |
11 | //! - `cwriteln!(f, <FORMAT_STRING> [, ARGS...])` |
12 | //! - `cstr!(<FORMAT_STRING>)` |
13 | //! - `untagged!(<FORMAT_STRING>)` |
14 | //! |
15 | //! The macros have the same syntax as their corresponding [`std`] variants: |
16 | //! - [`cformat!()`] as [`format!()`] |
17 | //! - [`cprint!()`] as [`print!()`] |
18 | //! - [`cprintln!()`] as [`println!()`] |
19 | //! - [`ceprint!()`] as [`eprint!()`] |
20 | //! - [`ceprintln!()`] as [`eprintln!()`] |
21 | //! - [`cwrite!()`] as [`write!()`] |
22 | //! - [`cwriteln!()`] as [`writeln!()`] |
23 | //! |
24 | //! But they accept an additional syntax inside the |
25 | //! format string: HTML-like tags which add ANSI colors/styles at compile-time. |
26 | //! |
27 | //! [`cstr!()`] only transforms the given string literal into another string literal, without |
28 | //! formatting anything else than the colors tag. |
29 | //! |
30 | //! [`untagged!()`] removes all the tags found in the given string literal. |
31 | //! |
32 | //! ## What does it do ? |
33 | //! |
34 | //! By default, the provided macros will replace the tags found in the format string by ANSI |
35 | //! hexadecimal escape codes. E.g.: |
36 | //! |
37 | //! ``` |
38 | //! # use color_print::cprintln; |
39 | //! # fn main() { |
40 | //! cprintln!("HELLO <green>WORLD</green>" ); |
41 | //! cprintln!("HELLO <green>WORLD</>" ); // Alternative, shorter syntax |
42 | //! # } |
43 | //! ``` |
44 | //! |
45 | //! will be replaced by: |
46 | //! |
47 | //! ``` |
48 | //! # use color_print::cprintln; |
49 | //! # fn main() { |
50 | //! println!("HELLO \u{1b}[31mWORLD \u{1b}[39m" ) |
51 | //! # } |
52 | //! ``` |
53 | //! |
54 | //! *Note*: it is possible to change this behaviour by activating the feature `terminfo`. Then it |
55 | //! will question the `terminfo` database at runtime in order to know which sequence to write for |
56 | //! each kind of styling/colorizing (see below for more detail). |
57 | //! |
58 | //! # Pros/cons of this crate |
59 | //! |
60 | //! ## Pros |
61 | //! |
62 | //! * Styling is processed at compile-time, so the runtime payload is inexistant (unless the |
63 | //! feature `terminfo` is activated); |
64 | //! * Nested tags are well handled, e.g. `"<green>...<blue>...</blue>...</green>"`; |
65 | //! * Some optimizations are performed to avoid redundant ANSI sequences, because these |
66 | //! optimizations can be done at compile-time without impacting the runtime; |
67 | //! * Almost every tag has a short name, so colorizing can be done quickly: `"my <b>blue</> word"`; |
68 | //! * Each provided macro can be used exactly in the same way as the standard `format!`-like |
69 | //! macros; e.g., positional arguments and named arguments can be used as usual; |
70 | //! * Supports 16, 256 and 16M colors; |
71 | //! * Fine-grained error handling (errors will be given at compile-time). |
72 | //! |
73 | //! ## Cons |
74 | //! |
75 | //! * Not compatible with non-ANSI terminals. |
76 | //! |
77 | //! # Introduction |
78 | //! |
79 | //! ## Basic example |
80 | //! |
81 | //! ``` |
82 | //! use color_print::cprintln; |
83 | //! cprintln!("Hello <green>world</green>!" ); |
84 | //! ``` |
85 | //! |
86 | //! ## Closing a tag more simply: the `</>` tag |
87 | //! |
88 | //! Basically, tags must be closed by giving *exactly* the same colors/styles as their matching |
89 | //! open tag (with a slash `/` at the beginning), e.g: `<blue,bold>...</blue,bold>`. But it can be |
90 | //! tedious! |
91 | //! |
92 | //! So, it is also possible to close the last open tag simply with `</>`: |
93 | //! |
94 | //! ``` |
95 | //! # use color_print::cprintln; |
96 | //! # fn main() { |
97 | //! cprintln!("Hello <green>world</>!" ); |
98 | //! # } |
99 | //! ``` |
100 | //! |
101 | //! ## Combining colors and styles |
102 | //! |
103 | //! Multiple styles and colors can be combined into a single tag by separating them with the `,` |
104 | //! comma character: |
105 | //! |
106 | //! ``` |
107 | //! # use color_print::cprintln; |
108 | //! # fn main() { |
109 | //! cprintln!("This a <green,bold>green and bold text</green,bold>." ); |
110 | //! // The same, but closing with the </> tag: |
111 | //! cprintln!("This a <green,bold>green and bold text</>." ); |
112 | //! # } |
113 | //! ``` |
114 | //! |
115 | //! ## Nesting tags |
116 | //! |
117 | //! Any tag can be nested with any other. |
118 | //! |
119 | //! *Note*: The closing tags must match correctly (following the basic rules of nesting for HTML |
120 | //! tags), but it can always be simplified by using the tag `</>`. |
121 | //! |
122 | //! Example of nested tags: |
123 | //! |
124 | //! ``` |
125 | //! # use color_print::cprintln; |
126 | //! # fn main() { |
127 | //! cprintln!("<green>This is green, <bold>then green and bold</bold>, then green again</green>" ); |
128 | //! cprintln!("<green>This is green, <bold>then green and bold</>, then green again</>" ); |
129 | //! |
130 | //! // Colors can be nested as well: |
131 | //! cprintln!("<green>This is green, <blue>then blue</blue>, then green again</green>" ); |
132 | //! cprintln!("<green>This is green, <blue>then blue</>, then green again</>" ); |
133 | //! # } |
134 | //! ``` |
135 | //! |
136 | //! ## Unclosed tags are automatically closed at the end of the format string |
137 | //! |
138 | //! Tags which have not been closed manually will be closed automatically, which means that the |
139 | //! ANSI sequences needed to go back to the original state will be added: |
140 | //! |
141 | //! ``` |
142 | //! # use color_print::cprintln; |
143 | //! # fn main() { |
144 | //! // The two following lines are strictly equivalent: |
145 | //! cprintln!("<green><bold>Hello" ); |
146 | //! cprintln!("<green><bold>Hello</></>" ); |
147 | //! # } |
148 | //! ``` |
149 | //! |
150 | //! ## How to display the chars `<` and `>` verbatim |
151 | //! |
152 | //! As for `{` and `}` in standard format strings, the chars `<` and `>` have to be doubled in |
153 | //! order to display them verbatim: |
154 | //! |
155 | //! ``` |
156 | //! # use color_print::cprintln; |
157 | //! # fn main() { |
158 | //! cprintln!("This is an angle bracket character: <<, and here is another one: >>" ); |
159 | //! # } |
160 | //! ``` |
161 | //! |
162 | //! # Optimization: no redundant ANSI codes |
163 | //! |
164 | //! The expanded format string will only contain the *needed* ANSI codes. This is done by making a |
165 | //! diff of the different style attributes, each time a tag is encountered, instead of mechanically |
166 | //! adding the ANSI codes. |
167 | //! |
168 | //! E.g., several nested `<bold>` tags will only produce one bold ANSI sequence: |
169 | //! |
170 | //! ``` |
171 | //! # use color_print::cprintln; |
172 | //! # fn main() { |
173 | //! cprintln!("<bold><bold> A <bold,blue> B </> C </></>" ) |
174 | //! # } |
175 | //! ``` |
176 | //! |
177 | //! will be replaced by: |
178 | //! |
179 | //! ``` |
180 | //! # use color_print::cprintln; |
181 | //! # fn main() { |
182 | //! println!(" \u{1b}[1m A \u{1b}[34m B \u{1b}[39m C \u{1b}[22m" ) |
183 | //! // ^-------^ ^--------^ ^--------^ ^--------^ |
184 | //! // bold blue color bold |
185 | //! // reset reset |
186 | //! # } |
187 | //! ``` |
188 | //! |
189 | //! # The feature `terminfo` |
190 | //! |
191 | //! Instead of inserting ANSI sequences directly into the format string, it is possible to activate |
192 | //! the feature `terminfo`: this will add the format sequences at runtime, by consulting the |
193 | //! `terminfo` database. |
194 | //! |
195 | //! This has one pro and several cons: |
196 | //! |
197 | //! #### Pros |
198 | //! |
199 | //! * This adds a level of compatibility for some terminals. |
200 | //! |
201 | //! #### Cons |
202 | //! |
203 | //! * This adds a little runtime payload; |
204 | //! * This adds two dependencies: [`lazy_static`] and [`terminfo`]; |
205 | //! * The styles `<strike>` and `<conceal>` are not handled; |
206 | //! * With `terminfo`, many styles are not resettable individually, which implies longer format |
207 | //! sequences for the same result; |
208 | //! * For now, the provided macros can only be used in one thread. |
209 | //! |
210 | //! [`lazy_static`]: https://crates.io/crates/lazy_static |
211 | //! [`terminfo`]: https://crates.io/crates/terminfo |
212 | //! |
213 | //! # Naming rules of the tags: |
214 | //! |
215 | //! Each tag has at least a **long name**, like `<magenta>` or `<underline>`. |
216 | //! |
217 | //! The tags directly relative to *colors* (like `<red>`, `<bg:blue>`, `<bg:bright-green>`..., as |
218 | //! opposed to *style* tags like `<bold>`, `<italics>`...) have some common naming rules: |
219 | //! |
220 | //! * Each tag has four variants: |
221 | //! - `<mycolor>`: the normal, foreground color; |
222 | //! - `<bright-mycolor>` or `<mycolor!>`: the bright, foreground color; |
223 | //! - `<bg:mycolor>`, `<MYCOLOR>`: the normal, background color; |
224 | //! - `<bg:bright-mycolor>`, `<bg:mycolor!>`, `<BRIGHT-MYCOLOR>` or `<MYCOLOR!>`: the bright, |
225 | //! background color; |
226 | //! * Each tag has a *shortcut*, with a base letter for each color; example with the `x` letter: |
227 | //! - `<x>`: the normal, foreground color; |
228 | //! - `<x!>`: the bright, foreground color; |
229 | //! - `<bg:x>`, `<X>`: the normal, background color; |
230 | //! - `<bg:x!>`, `<X!>`: the bright, background color; |
231 | //! * Each color's shortcut letter is simply the **first letter of its name** (excepted for `<k>` |
232 | //! which is the shortcut for `<black>`), e.g. `<y>` is the shortcut for `<yellow>`; |
233 | //! * Each color's tag which is uppercase is a **background color**; |
234 | //! * Each tag which has a trailing exclamation point `!` is a **bright color**; |
235 | //! |
236 | //! # List of accepted tags: |
237 | //! |
238 | //! The two first columns show which styles are supported, respectively with the default crate |
239 | //! features (ANSI column), and with the feature `terminfo` being activated. |
240 | //! |
241 | //! | ANSI | Terminfo | Shortcuts | Long names | Aliases | |
242 | //! | ---- | -------- | --------- | ----------------------- | ----------------------------------------------- | |
243 | //! | X | X | `<s>` | `<strong>` | `<em>` `<bold>` | |
244 | //! | X | X | | `<dim>` | | |
245 | //! | X | X | `<u>` | `<underline>` | | |
246 | //! | X | | | `<strike>` | | |
247 | //! | X | X | | `<reverse>` | `<rev>` | |
248 | //! | X | | | `<conceal>` | `<hide>` | |
249 | //! | X | X | `<i>` | `<italics>` | `<italic>` | |
250 | //! | X | X | | `<blink>` | | |
251 | //! | X | X | `<k>` | `<black>` | | |
252 | //! | X | X | `<r>` | `<red>` | | |
253 | //! | X | X | `<g>` | `<green>` | | |
254 | //! | X | X | `<y>` | `<yellow>` | | |
255 | //! | X | X | `<b>` | `<blue>` | | |
256 | //! | X | X | `<m>` | `<magenta>` | | |
257 | //! | X | X | `<c>` | `<cyan>` | | |
258 | //! | X | X | `<w>` | `<white>` | | |
259 | //! | X | X | `<k!>` | `<bright-black>` | `<black!>` | |
260 | //! | X | X | `<r!>` | `<bright-red>` | `<red!>` | |
261 | //! | X | X | `<g!>` | `<bright-green>` | `<green!>` | |
262 | //! | X | X | `<y!>` | `<bright-yellow>` | `<yellow!>` | |
263 | //! | X | X | `<b!>` | `<bright-blue>` | `<blue!>` | |
264 | //! | X | X | `<m!>` | `<bright-magenta>` | `<magenta!>` | |
265 | //! | X | X | `<c!>` | `<bright-cyan>` | `<cyan!>` | |
266 | //! | X | X | `<w!>` | `<bright-white>` | `<white!>` | |
267 | //! | X | X | `<K>` | `<bg:black>` | `<BLACK>` | |
268 | //! | X | X | `<R>` | `<bg:red>` | `<RED>` | |
269 | //! | X | X | `<G>` | `<bg:green>` | `<GREEN>` | |
270 | //! | X | X | `<Y>` | `<bg:yellow>` | `<YELLOW>` | |
271 | //! | X | X | `<B>` | `<bg:blue>` | `<BLUE>` | |
272 | //! | X | X | `<M>` | `<bg:magenta>` | `<MAGENTA>` | |
273 | //! | X | X | `<C>` | `<bg:cyan>` | `<CYAN>` | |
274 | //! | X | X | `<W>` | `<bg:white>` | `<WHITE>` | |
275 | //! | X | X | `<K!>` | `<bg:bright-black>` | `<BLACK!>` `<bg:black!>` `<BRIGHT-BLACK>` | |
276 | //! | X | X | `<R!>` | `<bg:bright-red>` | `<RED!>` `<bg:red!>` `<BRIGHT-RED>` | |
277 | //! | X | X | `<G!>` | `<bg:bright-green>` | `<GREEN!>` `<bg:green!>` `<BRIGHT-GREEN>` | |
278 | //! | X | X | `<Y!>` | `<bg:bright-yellow>` | `<YELLOW!>` `<bg:yellow!>` `<BRIGHT-YELLOW>` | |
279 | //! | X | X | `<B!>` | `<bg:bright-blue>` | `<BLUE!>` `<bg:blue!>` `<BRIGHT-BLUE>` | |
280 | //! | X | X | `<M!>` | `<bg:bright-magenta>` | `<MAGENTA!>` `<bg:magenta!>` `<BRIGHT-MAGENTA>` | |
281 | //! | X | X | `<C!>` | `<bg:bright-cyan>` | `<CYAN!>` `<bg:cyan!>` `<BRIGHT-CYAN>` | |
282 | //! | X | X | `<W!>` | `<bg:bright-white>` | `<WHITE!>` `<bg:white!>` `<BRIGHT-WHITE>` | |
283 | //! | X | | | `<rgb(r,g,b)>` | `<#RRGGBB>` | |
284 | //! | X | | | `<bg:rgb(r,g,b)>` | `<bg:#RRGGBB>` `<RGB(r,g,b)>` | |
285 | //! | X | | `<0>`...`<255>` | `<palette(...)>` | `<p(...)>` `<pal(...)>` | |
286 | //! | X | | `<P(...)>` | `<bg:palette(...)>` | `<PALETTE(...)>` `<PAL(...)>` `<bg:p(...)>` `<bg:pal(...)>` | |
287 | |
288 | pub use color_print_proc_macro::{ |
289 | ceprint, ceprintln, cformat, cprint, cprintln, cstr, cwrite, cwriteln, untagged, |
290 | }; |
291 | |
292 | #[cfg (feature = "terminfo" )] |
293 | mod terminfo; |
294 | #[cfg (feature = "terminfo" )] |
295 | pub use terminfo::*; |
296 | |
297 | #[cfg (test)] |
298 | mod tests { |
299 | use std::fmt::Write as _; |
300 | |
301 | use super::*; |
302 | |
303 | #[cfg (feature = "terminfo" )] |
304 | pub mod color_print { |
305 | pub use super::*; |
306 | } |
307 | |
308 | #[test ] |
309 | fn format_no_arg() { |
310 | assert_eq!(cformat!(), "" ); |
311 | cprint!(); |
312 | cprintln!(); |
313 | } |
314 | |
315 | #[test ] |
316 | fn format_no_color() { |
317 | assert_eq!(cformat!("" ), "" ); |
318 | assert_eq!(cformat!("Hi" ), "Hi" ); |
319 | assert_eq!(cformat!("Hi {}" , 12), "Hi 12" ); |
320 | assert_eq!(cformat!("Hi {n} {}" , 12, n = 24), "Hi 24 12" ); |
321 | |
322 | let mut s = String::new(); |
323 | cwrite!(&mut s, "" ).unwrap(); |
324 | assert_eq!(s, "" ); |
325 | |
326 | let mut s = String::new(); |
327 | cwrite!(&mut s, "Hi" ).unwrap(); |
328 | assert_eq!(s, "Hi" ); |
329 | |
330 | let mut s = String::new(); |
331 | cwrite!(&mut s, "Hi {}" , 12).unwrap(); |
332 | assert_eq!(s, "Hi 12" ); |
333 | |
334 | let mut s = String::new(); |
335 | cwrite!(&mut s, "Hi {n} {}" , 12, n = 24).unwrap(); |
336 | assert_eq!(s, "Hi 24 12" ); |
337 | } |
338 | |
339 | #[test ] |
340 | #[cfg (not(feature = "terminfo" ))] |
341 | #[rustfmt::skip] |
342 | fn format_basic() { |
343 | assert_eq!(cformat!("<red>Hi</red>" ), " \u{1b}[31mHi \u{1b}[39m" ); |
344 | assert_eq!(cformat!("<red>Hi</r>" ), " \u{1b}[31mHi \u{1b}[39m" ); |
345 | assert_eq!(cformat!("<red>Hi</>" ), " \u{1b}[31mHi \u{1b}[39m" ); |
346 | |
347 | assert_eq!(cformat!("<bg:red>Hi</bg:red>" ), " \u{1b}[41mHi \u{1b}[49m" ); |
348 | assert_eq!(cformat!("<bg:red>Hi</R>" ), " \u{1b}[41mHi \u{1b}[49m" ); |
349 | assert_eq!(cformat!("<bg:red>Hi</>" ), " \u{1b}[41mHi \u{1b}[49m" ); |
350 | |
351 | assert_eq!( |
352 | cformat!("Hi <bold>word</bold> !" ), |
353 | "Hi \u{1b}[1mword \u{1b}[22m !" |
354 | ); |
355 | assert_eq!(cformat!("Hi <em>word</em> !" ), "Hi \u{1b}[1mword \u{1b}[22m !" ); |
356 | assert_eq!(cformat!("Hi <em>word</> !" ), "Hi \u{1b}[1mword \u{1b}[22m !" ); |
357 | |
358 | assert_eq!( |
359 | cformat!(" |
360 | <bold>bold</> |
361 | <dim>dim</> |
362 | <underline>underline</> |
363 | <strike>strike</> |
364 | <reverse>reverse</> |
365 | <conceal>conceal</> |
366 | <italics>italics</> |
367 | <blink>blink</> |
368 | " ), |
369 | " |
370 | \u{1b}[1mbold \u{1b}[22m |
371 | \u{1b}[2mdim \u{1b}[22m |
372 | \u{1b}[4munderline \u{1b}[24m |
373 | \u{1b}[9mstrike \u{1b}[29m |
374 | \u{1b}[7mreverse \u{1b}[27m |
375 | \u{1b}[8mconceal \u{1b}[28m |
376 | \u{1b}[3mitalics \u{1b}[23m |
377 | \u{1b}[5mblink \u{1b}[25m |
378 | " |
379 | ); |
380 | |
381 | let mut s = String::new(); |
382 | cwrite!(&mut s, "Hi <r>{v}</> {}" , 12, v = "Hi" ).unwrap(); |
383 | assert_eq!(s, "Hi \u{1b}[31mHi \u{1b}[39m 12" ); |
384 | |
385 | let mut s = String::new(); |
386 | cwriteln!(&mut s, "Hi <r>{v} {}" , 12, v = "Hi" ).unwrap(); |
387 | assert_eq!(s, "Hi \u{1b}[31mHi 12 \u{1b}[39m \n" ); |
388 | } |
389 | |
390 | #[test ] |
391 | #[ignore ] |
392 | #[cfg (not(feature = "terminfo" ))] |
393 | fn bold_and_dim_should_be_optimized() { |
394 | assert_eq!( |
395 | cformat!("<bold>BOLD</><dim>DIM</>" ), |
396 | " \u{1b}[1mBOLD \u{1b}[2mDIM \u{1b}[22m" |
397 | ); |
398 | } |
399 | |
400 | #[test ] |
401 | #[cfg (not(feature = "terminfo" ))] |
402 | fn format_multiple() { |
403 | assert_eq!( |
404 | cformat!("Hi <bold>word</bold> <red>red</red> !" ), |
405 | "Hi \u{1b}[1mword \u{1b}[22m \u{1b}[31mred \u{1b}[39m !" |
406 | ); |
407 | } |
408 | |
409 | #[test ] |
410 | #[cfg (not(feature = "terminfo" ))] |
411 | fn format_optimization() { |
412 | assert_eq!( |
413 | cformat!("<red>RED<blue>BLUE</>RED</>" ), |
414 | " \u{1b}[31mRED \u{1b}[34mBLUE \u{1b}[31mRED \u{1b}[39m" |
415 | ); |
416 | assert_eq!( |
417 | cformat!("<red><blue>BLUE</>RED</>" ), |
418 | " \u{1b}[34mBLUE \u{1b}[31mRED \u{1b}[39m" |
419 | ); |
420 | assert_eq!(cformat!("<red></>Text" ), "Text" ); |
421 | } |
422 | |
423 | #[test ] |
424 | #[cfg (not(feature = "terminfo" ))] |
425 | #[rustfmt::skip] |
426 | fn format_auto_close_tag() { |
427 | assert_eq!( |
428 | cformat!("<red>RED<blue>BLUE" ), |
429 | " \u{1b}[31mRED \u{1b}[34mBLUE \u{1b}[39m" |
430 | ); |
431 | assert!( |
432 | cformat!("<red>RED<em>BOLD" ) == " \u{1b}[31mRED \u{1b}[1mBOLD \u{1b}[22m \u{1b}[39m" |
433 | || |
434 | cformat!("<red>RED<em>BOLD" ) == " \u{1b}[31mRED \u{1b}[1mBOLD \u{1b}[39m \u{1b}[22m" |
435 | ); |
436 | } |
437 | |
438 | #[test ] |
439 | #[cfg (feature = "terminfo" )] |
440 | fn terminfo_format_basic() { |
441 | assert_eq!(cformat!("<red>Hi</red>" ), format!("{}Hi{}" , *RED, *CLEAR)); |
442 | assert_eq!( |
443 | cformat!("Hi <bold>word</bold> !" ), |
444 | format!("Hi {}word{} !" , *BOLD, *CLEAR) |
445 | ); |
446 | |
447 | let mut s = String::new(); |
448 | cwrite!(&mut s, "<r>Hi</> {}" , 12).unwrap(); |
449 | assert_eq!(s, format!("{}Hi{} 12" , *RED, *CLEAR)); |
450 | } |
451 | |
452 | #[test ] |
453 | #[cfg (feature = "terminfo" )] |
454 | fn terminfo_format_multiple() { |
455 | assert_eq!( |
456 | cformat!("Hi <bold>word</bold> <red>red</red> !" ), |
457 | format!("Hi {}word{} {}red{} !" , *BOLD, *CLEAR, *RED, *CLEAR) |
458 | ); |
459 | } |
460 | |
461 | #[test ] |
462 | #[cfg (feature = "terminfo" )] |
463 | fn terminfo_format_auto_close_tag() { |
464 | assert_eq!( |
465 | cformat!("<red>RED<blue>BLUE" ), |
466 | format!("{}RED{}BLUE{}" , *RED, *BLUE, *CLEAR) |
467 | ); |
468 | assert_eq!( |
469 | cformat!("<red>RED<em>BOLD" ), |
470 | format!("{}RED{}BOLD{}" , *RED, *BOLD, *CLEAR) |
471 | ); |
472 | } |
473 | |
474 | #[test ] |
475 | fn untagged() { |
476 | assert_eq!(untagged!("" ), "" ); |
477 | assert_eq!(untagged!("hi" ), "hi" ); |
478 | assert_eq!(untagged!("<red>hi" ), "hi" ); |
479 | assert_eq!(untagged!("<red>hi</>" ), "hi" ); |
480 | assert_eq!(untagged!("<red>hi <em,blue>all" ), "hi all" ); |
481 | assert_eq!(untagged!("<red>hi <em>all</></>" ), "hi all" ); |
482 | } |
483 | } |
484 | |