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}