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