color_print/lib.rs
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
288pub use color_print_proc_macro::{
289 ceprint, ceprintln, cformat, cprint, cprintln, cstr, cwrite, cwriteln, untagged,
290};
291
292#[cfg(feature = "terminfo")]
293mod terminfo;
294#[cfg(feature = "terminfo")]
295pub use terminfo::*;
296
297#[cfg(test)]
298mod 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}