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
276pub use color_print_proc_macro::{cformat, cprint, cprintln, cstr, untagged};
277
278#[cfg(feature = "terminfo")]
279mod terminfo;
280#[cfg(feature = "terminfo")]
281pub use terminfo::*;
282
283#[cfg(test)]
284mod 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