1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
//! Tools for parsing and managing ANSI-styled strings.
//!
//! This library allows to:
//!
//! - [Parse ANSI-styled strings](#parsing-ansi-escapes).
//! - Create styled strings from [human-readable format](#rich-format), including in compile time.
//! - Compare styled strings with [rich diff info](#comparing-styled-strings).
//! - Manipulate styled strings, e.g. split them into lines, split off parts etc.
//!
//! One of guiding use cases for the library is hassle-free snapshot testing for styled strings,
//! without the need to compare literal strings with ANSI escapes (which is brittle and not human-readable),
//! and outputting more informative diff info than a simple `assert_eq!` would.
//!
//! For the example of real-world usage, see the [`term-transcript`](https://docs.rs/term-transcript/) crate.
//!
//! # Styled strings
//!
//! The core types exposed by this crate are [`StyledStr`] and [`StyledString`]. Both are a container for text + styling
//! signaled via [ANSI escape codes]. `StyledStr` is borrowed (analog to `&str`), while `StyledString`
//! is owned (analog to `String`). Styling is represented as a sequence of spans ([`SpanStr`] type)
//! that cover the text in its entirety. Styles reuse the model from [`anstyle`]; i.e., a style
//! is just a [`Style`](anstyle::Style).
//!
//! # Rich syntax
//!
//! One way to create `Styled` strings is parsing [`rich`]-inspired syntax, either in compile time
//! via the [`styled!`] macro, or in runtime via [`FromStr`](core::str::FromStr).
//! Conversely, a `Styled` string can be converted to the rich format via its [`Display`](core::fmt::Display)
//! implementation.
//!
//! The format is as follows:
//!
//! - Styling directives are enclosed in double brackets: `[[` + `]]`.
//! - A directive is a list of *tokens* separated by whitespace and/or commas `,` or semicolons `;`.
//! - A token represents an effect, e.g. `bold` or `underline`, a foreground [color](#color-tokens) (e.g., `red`
//! or `#fed`), or a background color (`on` + a color token, e.g. `on blue`).
//! - By default, a directive completely overrides the previously applied directive. Hence, there is no need
//! for special closing directives.
//! - A directive can be made to inherit from the previously applied style by preceding all tokens with a `*`.
//! This also allows to *subtract* effects by specifying them with a `-` or `!` in front, like `-bold` or `!italic`.
//! Similarly, `-color` / `!color` (or `-fg` / `!fg`) switches off the foreground color, and
//! `-on`, `!on`, `-bg`, or `!bg` switches off the background color.
//! - A directive may be empty: `[[]]`. As a special case, `[[/]]` (i.e., a single `/` token) is equivalent
//! to an empty directive.
//!
//! ## Effects
//!
//! The following effects are supported:
//!
//! | Effect | Aliases |
//! |:-------|:--------|
//! | `bold` | `b` |
//! | `italic` | `it`, `i` |
//! | `underline`| `ul`, `u` |
//! | `strikethrough` | `strike`, `s` |
//! | `dimmed` | `dim` |
//! | `invert` | `inverted`, `inv` |
//! | `blink` | |
//! | `concealed` | `conceal`, `hide`, `hidden` |
//!
//! ## Color tokens
//!
//! A color may be represented as follows:
//!
//! - One of the 8 base terminal colors (`black`, `red`, `green`, `yellow`, `blue`, `magenta`, `cyan`, `white`)
//! - One of the 8 bright terminal colors signaled via `!` suffix or `bright-` prefix (e.g., `blue!` or `bright-blue`).
//! - One of [256 indexed ANSI colors](https://en.wikipedia.org/wiki/ANSI_escape_code#8-bit) specified
//! as `color$idx` or `color($idx)`, e.g. `color23` or `color(254)`.
//! - A 24-bit RGB color written in CSS-like hex format, e.g. `#fa4` or `#c0ffee`.
//!
//! ## Syntax examples
//!
//! ```
//! use styled_str::{styled, StyledStr};
//!
//! const STYLED: StyledStr = styled!("[[green! on white, bold]]Hello,[[/]] world[[it]]!");
//! assert_eq!(STYLED.text(), "Hello, world!");
//! assert_eq!(STYLED.spans().len(), 3);
//! ```
//!
//! Here, the first style applies to `Hello,`, then it is completely reset for ` world`,
//! and finally, the italic effect (and only it) is applied to `!`.
//!
//! ```
//! # use styled_str::{styled, StyledStr};
//! const STYLED: StyledStr = styled!("[[bold green!]]Hello[[* -bold]], world[[* invert]]!");
//! ```
//!
//! Here, there are again 3 styled spans, but the second and third ones inherit the preceding style.
//! The second span removes the bold effect, and the third one inverts foreground and background colors.
//!
//! # Other string functionality
//!
//! ## Parsing ANSI escapes
//!
//! [`StyledString::from_ansi()`] and [`StyledString::from_ansi_bytes()`] allow parsing a styled string
//! from ANSI escapes (e.g., captured from a terminal).
//!
//! ```
//! # use styled_str::{styled, StyledString};
//! let str = StyledString::from_ansi(
//! "\u{1b}[32mHello,\u{1b}[m world\u{1b}[1m!\u{1b}[m",
//! )?;
//! assert_eq!(str.text(), "Hello, world!");
//! assert_eq!(str.to_string(), "[[green]]Hello,[[/]] world[[bold]]!");
//! # anyhow::Ok(())
//! ```
//!
//! ## Comparing styled strings
//!
//! [`StyledStr::diff()`] allows comparing two styled strings both in terms of text and styles.
//! [`TextDiff`] and [`StyleDiff`] provide more fine-grained control over comparison logic.
//! These types can be [`Display`](core::fmt::Display)ed / [`Debug`](core::fmt::Debug)ged
//! in order to provide rich human-readable info about differences (e.g., in the test code).
//!
//! ```
//! # use styled_str::{styled, Diff};
//! # use assert_matches::assert_matches;
//! let left = styled!("Hello, [[bold dim white on #fa4]]world!");
//! let right = styled!("Hello, [[bold]]world[[/]]!");
//! let diff = left.diff(right).unwrap_err();
//! assert_matches!(&diff, Diff::Style(_));
//!
//! // Will output detailed human-readable info about the diff.
//! println!("{diff}");
//! ```
//!
//! # Limitations
//!
//! - ANSI escape sequences other than [SGR] ones are either dropped (in case of [CSI] sequences),
//! or lead to [an error](AnsiError).
//!
//! # Alternatives and similar tools
//!
//! - This crate builds on the [`anstyle`] library, using its styling data model. `anstyle` together
//! with [`anstream`](https://docs.rs/anstream/) provides tools to create / output ANSI-styled strings in runtime.
//! It doesn't cover creating strings in compile time, parsing ANSI-styled strings, or comparing styled strings.
//! - [`color_print`](https://docs.rs/color-print/) provides proc macros to create / output ANSI-styled strings
//! using `rich`-like syntax.
//! - [`parse-style`](https://docs.rs/parse-style/) allows parsing `rich`-like style specs.
//!
//! # Crate features
//!
//! ## `std`
//!
//! *(On by default)*
//!
//! Enables std-specific functionality, such as [`Error`](std::error::Error) trait implementations.
//! Note that disabling this feature for now doesn't make this crate no-std compatible.
//!
//! [ANSI escape codes]: https://en.wikipedia.org/wiki/ANSI_escape_code
//! [`rich`]: https://rich.readthedocs.io/en/stable/index.html
//! [SGR]: https://en.wikipedia.org/wiki/ANSI_escape_code#SGR
//! [CSI]: https://en.wikipedia.org/wiki/ANSI_escape_code#CSI_(Control_Sequence_Introducer)_sequences
// Documentation settings
// Conditional compilation
extern crate core;
pub use crate::;
/// Parses [rich syntax](crate#rich-syntax) into a [`StyledStr`] in compile time.
///
/// # Examples
///
/// ```
/// use styled_str::{styled, StyledStr};
///
/// const STYLED: StyledStr = styled!(
/// "[[bold red on white]]ERROR:[[/]] [[it]]Something[[/]] \
/// [[strike]]bad[[/]] happened"
/// );
/// assert_eq!(STYLED.text(), "ERROR: Something bad happened");
/// assert_eq!(STYLED.span(0).unwrap().text, "ERROR:");
/// ```
;
}
doctest!;