1//! This module permits to determine which ANSI sequences have to be added at a given position in
2//! the format string, by saving the current tags in a "context". When a new tag is encountered, a
3//! diff between the old state and the new state is performed to determine the right ANSI sequences
4//! to add.
5
6use std::convert::TryFrom;
7
8use proc_macro2::Span;
9
10use crate::error::{Error, SpanError};
11
12/// Stores all the current open tags encountered in the format string.
13#[derive(Debug, PartialEq, Default)]
14pub struct Context<'a>(Vec<ColorTag<'a>>);
15
16impl<'a> Context<'a> {
17 pub fn new() -> Self {
18 Self::default()
19 }
20
21 /// Applies a group of tags to the current context, and returns a list of the terminfo
22 /// constants (available in the `color-print` package) to be added as named arguments at the
23 /// end of the format arguments.
24 ///
25 /// For each given tag:
26 /// - if the tag is an open tag, push it into the context;
27 /// - if it's a valid close tag, pop the last open tag.
28 #[cfg(feature = "terminfo")]
29 pub fn terminfo_apply_tags(
30 &mut self,
31 tag_group: Vec<ColorTag<'a>>,
32 ) -> Result<Vec<String>, SpanError> {
33 let state_diff = self.apply_tags_and_get_diff(tag_group)?;
34 Ok(state_diff.terminfo_token_streams())
35 }
36
37 /// Applies a group of tags to the current context, and returns the ANSI sequences to be
38 /// added into the format string.
39 ///
40 /// For each given tag:
41 /// - if the tag is an open tag, push it into the context;
42 /// - if it's a valid close tag, pop the last open tag.
43 #[cfg(not(feature = "terminfo"))]
44 pub fn ansi_apply_tags(&mut self, tag_group: Vec<ColorTag<'a>>) -> Result<String, SpanError> {
45 let state_diff = self.apply_tags_and_get_diff(tag_group)?;
46 Ok(state_diff.ansi_string())
47 }
48
49 /// Applies a group of tags to the current context, with no return on success. Used by the
50 /// macro [`untagged!()`].
51 ///
52 /// For each given tag:
53 /// - if the tag is an open tag, push it into the context;
54 /// - if it's a valid close tag, pop the last open tag.
55 pub fn apply_tags(&mut self, tag_group: Vec<ColorTag<'a>>) -> Result<(), SpanError> {
56 self.apply_tags_and_get_diff(tag_group).map(|_| ())
57 }
58
59 /// Returns the actual color/style state, which is the result of the changes made by each tag
60 /// sequentially.
61 pub fn state(&self) -> State {
62 let mut state = State::default();
63 for tag in &self.0 {
64 if let Some(ref color) = tag.change_set.foreground {
65 state.foreground = ExtColor::Color(color.clone());
66 }
67 if let Some(ref color) = tag.change_set.background {
68 state.background = ExtColor::Color(color.clone());
69 }
70 state.bold |= tag.change_set.bold;
71 state.dim |= tag.change_set.dim;
72 state.underline |= tag.change_set.underline;
73 state.italics |= tag.change_set.italics;
74 state.blink |= tag.change_set.blink;
75 state.strike |= tag.change_set.strike;
76 state.reverse |= tag.change_set.reverse;
77 state.conceal |= tag.change_set.conceal;
78 }
79 state
80 }
81
82 #[allow(rustdoc::broken_intra_doc_links)]
83 /// Common code betwwen [Self::terminfo_apply_tag()] and [Self::ansi_apply_tag()].
84 fn apply_tags_and_get_diff(&mut self, tags: Vec<ColorTag<'a>>) -> Result<StateDiff, SpanError> {
85 let old_state = self.state();
86
87 for tag in tags {
88 if tag.is_close {
89 let last_tag = self.0.last()
90 .ok_or_else(|| SpanError::new(Error::NoTagToClose, tag.span))?;
91 // If the tag is "void" (it is a "</>" tag), we don't need to check if the change
92 // sets are matching:
93 if !tag.change_set.is_void() && last_tag.change_set != tag.change_set {
94 let (last_src, src) = (
95 // We can unwrap the last tag source, because we know that all the tags
96 // stored inside the context are *open tags*, and open tag are always taken
97 // from the source input:
98 last_tag.source.unwrap(),
99 // We can unwrap the source of the tag currently being processed, because
100 // we just checked above that the tag is not void, and non-void tags are
101 // always taken from the source input:
102 tag.source.unwrap(),
103 );
104 return Err(SpanError::new(
105 Error::MismatchCloseTag(last_src.to_owned(), src.to_owned()),
106 tag.span,
107 ));
108 }
109 self.0.pop().unwrap();
110 } else {
111 self.0.push(tag);
112 }
113 }
114
115 let new_state = self.state();
116 Ok(StateDiff::from_diff(&old_state, &new_state))
117 }
118}
119
120/// Describes the state of each color and style attributes at a given position in the format
121/// string. Two states can be compared together by creating a [`StateDiff`] instance.
122#[derive(Debug, PartialEq, Default)]
123pub struct State {
124 foreground: ExtColor,
125 background: ExtColor,
126 bold: bool,
127 dim: bool,
128 underline: bool,
129 italics: bool,
130 blink: bool,
131 strike: bool,
132 reverse: bool,
133 conceal: bool,
134}
135
136/// The result of the comparison between two [`State`]s.
137///
138/// Each field is an [`Action`], which indicates if the given value has to be changed or left
139/// unchanged in order to reach the new state.
140#[derive(Debug)]
141pub struct StateDiff {
142 foreground: Action<ExtColor>,
143 background: Action<ExtColor>,
144 bold: Action<bool>,
145 dim: Action<bool>,
146 underline: Action<bool>,
147 italics: Action<bool>,
148 blink: Action<bool>,
149 strike: Action<bool>,
150 reverse: Action<bool>,
151 conceal: Action<bool>,
152}
153
154impl StateDiff {
155 /// Creates a new [`StateDiff`] by comparing two [`State`]s.
156 pub fn from_diff(old: &State, new: &State) -> Self {
157 StateDiff {
158 foreground: Action::from_diff(Some(old.foreground.clone()), Some(new.foreground.clone())),
159 background: Action::from_diff(Some(old.background.clone()), Some(new.background.clone())),
160 bold: Action::from_diff(Some(old.bold), Some(new.bold)),
161 dim: Action::from_diff(Some(old.dim), Some(new.dim)),
162 underline: Action::from_diff(Some(old.underline), Some(new.underline)),
163 italics: Action::from_diff(Some(old.italics), Some(new.italics)),
164 blink: Action::from_diff(Some(old.blink), Some(new.blink)),
165 strike: Action::from_diff(Some(old.strike), Some(new.strike)),
166 reverse: Action::from_diff(Some(old.reverse), Some(new.reverse)),
167 conceal: Action::from_diff(Some(old.conceal), Some(new.conceal)),
168 }
169 }
170
171 /// Returns the list of terminfo constants (available in the `color-print` package) which have
172 /// to be used in order to reach the new state.
173 #[cfg(feature = "terminfo")]
174 pub fn terminfo_token_streams(&self) -> Vec<String> {
175 let mut constants = vec![];
176
177 macro_rules! push_constant {
178 ($s:expr) => {{
179 constants.push($s.to_owned());
180 }};
181 }
182
183 let have_to_reset = or!(
184 matches!(self.foreground, Action::Change(ExtColor::Normal)),
185 matches!(self.background, Action::Change(ExtColor::Normal)),
186 matches!(self.bold, Action::Change(false)),
187 matches!(self.dim, Action::Change(false)),
188 matches!(self.blink, Action::Change(false)),
189 matches!(self.reverse, Action::Change(false)),
190 );
191
192 if have_to_reset {
193 push_constant!("CLEAR");
194 if let Some(ExtColor::Color(Color::Color16(color))) = self.foreground.actual_value() {
195 push_constant!(color.terminfo_constant(true));
196 }
197 if let Some(ExtColor::Color(Color::Color16(color))) = self.background.actual_value() {
198 push_constant!(color.terminfo_constant(false));
199 }
200 if matches!(self.bold.actual_value(), Some(true)) {
201 push_constant!("BOLD");
202 }
203 if matches!(self.dim.actual_value(), Some(true)) {
204 push_constant!("DIM");
205 }
206 if matches!(self.blink.actual_value(), Some(true)) {
207 push_constant!("BLINK");
208 }
209 if matches!(self.underline.actual_value(), Some(true)) {
210 push_constant!("UNDERLINE");
211 }
212 if matches!(self.italics.actual_value(), Some(true)) {
213 push_constant!("ITALICS");
214 }
215 if matches!(self.reverse.actual_value(), Some(true)) {
216 push_constant!("REVERSE");
217 }
218 } else {
219 if let Action::Change(ExtColor::Color(Color::Color16(ref color))) = self.foreground {
220 push_constant!(color.terminfo_constant(true));
221 }
222 if let Action::Change(ExtColor::Color(Color::Color16(ref color))) = self.background {
223 push_constant!(color.terminfo_constant(false));
224 }
225 if let Action::Change(true) = self.bold {
226 push_constant!("BOLD");
227 }
228 if let Action::Change(true) = self.dim {
229 push_constant!("DIM");
230 }
231 if let Action::Change(true) = self.blink {
232 push_constant!("BLINK");
233 }
234 if let Action::Change(true) = self.reverse {
235 push_constant!("REVERSE");
236 }
237 if let Action::Change(underline) = self.underline {
238 let constant = if underline { "UNDERLINE" } else { "NO_UNDERLINE" };
239 push_constant!(constant);
240 }
241 if let Action::Change(italics) = self.italics {
242 let constant = if italics { "ITALICS" } else { "NO_ITALICS" };
243 push_constant!(constant);
244 }
245 }
246
247 constants
248 }
249
250 /// Returns the ANSI sequence(s) which has to added to the format string in order to reach the
251 /// new state.
252 #[cfg(not(feature = "terminfo"))]
253 pub fn ansi_string(&self) -> String {
254 use crate::ansi_constants::*;
255
256 let mut output = String::new();
257
258 macro_rules! push_code {
259 ($($codes:expr),*) => { output.push_str(&generate_ansi_code(&[$($codes),*])) };
260 }
261
262 if let Action::Change(ref ext_color) = self.foreground {
263 match ext_color {
264 ExtColor::Normal => push_code!(DEFAULT_FOREGROUND),
265 ExtColor::Color(Color::Color16(color)) => match color.intensity {
266 Intensity::Normal => {
267 push_code!(SET_FOREGROUND_BASE + color.base_color.index())
268 }
269 Intensity::Bright => {
270 push_code!(SET_BRIGHT_FOREGROUND_BASE + color.base_color.index())
271 }
272 },
273 ExtColor::Color(Color::Color256(color)) => {
274 push_code!(SET_FOREGROUND, 5, color.0);
275 },
276 ExtColor::Color(Color::ColorRgb(color)) => {
277 push_code!(SET_FOREGROUND, 2, color.r, color.g, color.b);
278 },
279 }
280 }
281
282 if let Action::Change(ref ext_color) = self.background {
283 match ext_color {
284 ExtColor::Normal => push_code!(DEFAULT_BACKGROUND),
285 ExtColor::Color(Color::Color16(color)) => match color.intensity {
286 Intensity::Normal => {
287 push_code!(SET_BACKGROUND_BASE + color.base_color.index())
288 }
289 Intensity::Bright => {
290 push_code!(SET_BRIGHT_BACKGROUND_BASE + color.base_color.index())
291 }
292 },
293 ExtColor::Color(Color::Color256(color)) => {
294 push_code!(SET_BACKGROUND, 5, color.0);
295 },
296 ExtColor::Color(Color::ColorRgb(color)) => {
297 push_code!(SET_BACKGROUND, 2, color.r, color.g, color.b);
298 },
299 }
300 }
301
302 macro_rules! handle_attr {
303 ($attr:expr, $true_val:expr, $false_val:expr) => {
304 match $attr {
305 Action::Change(true) => push_code!($true_val),
306 Action::Change(false) => push_code!($false_val),
307 _ => (),
308 }
309 };
310 }
311
312 handle_attr!(self.bold, BOLD, NO_BOLD);
313 handle_attr!(self.dim, DIM, NO_BOLD);
314 handle_attr!(self.underline, UNDERLINE, NO_UNDERLINE);
315 handle_attr!(self.italics, ITALIC, NO_ITALIC);
316 handle_attr!(self.blink, BLINK, NO_BLINK);
317 handle_attr!(self.strike, STRIKE, NO_STRIKE);
318 handle_attr!(self.reverse, REVERSE, NO_REVERSE);
319 handle_attr!(self.conceal, CONCEAL, NO_CONCEAL);
320
321 output
322 }
323}
324
325/// The action to be performed on a given color/style attribute in order to reach a new state.
326#[derive(Debug, PartialEq)]
327pub enum Action<T> {
328 /// Nothing has to be done, because this value was never modified.
329 None,
330 /// This attribute has to be kept the same.
331 /// With the terminfo implementation, it's not possible to reset each style/color
332 /// independently, so we have to keep track of the values, even with the `Keep` variant.
333 Keep(T),
334 /// This attribute value has to be changed.
335 Change(T),
336}
337
338#[cfg(feature = "terminfo")]
339impl<T> Action<T> {
340 pub fn actual_value(&self) -> Option<&T> {
341 match self {
342 Action::Keep(val) | Action::Change(val) => Some(val),
343 Action::None => None,
344 }
345 }
346}
347
348impl<T> Action<T>
349where
350 T: PartialEq,
351{
352 /// Creates a new [`Action`].
353 pub fn from_diff(old: Option<T>, new: Option<T>) -> Self {
354 let eq: bool = old == new;
355 match (old, new, eq) {
356 (Some(old_val: T), Some(_), true) | (Some(old_val: T), None, _) => Action::Keep(old_val),
357 (_, Some(new_val: T), _) => Action::Change(new_val),
358 _ => Action::None,
359 }
360 }
361}
362
363/// A parsed color/style tag.
364#[derive(Debug, Default)]
365pub struct ColorTag<'a> {
366 /// Source of the tag in the format string.
367 pub source: Option<&'a str>,
368 /// Span of the tag in the format string.
369 pub span: Option<Span>,
370 /// Is it a close tag like `</red>`.
371 pub is_close: bool,
372 /// The changes that are implied by this tag.
373 pub change_set: ChangeSet,
374}
375
376impl<'a> PartialEq for ColorTag<'a> {
377 fn eq(&self, other: &ColorTag<'a>) -> bool {
378 and!(
379 self.source == other.source,
380 self.is_close == other.is_close,
381 self.change_set == other.change_set,
382 )
383 }
384}
385
386impl<'a> ColorTag<'a> {
387 /// Creates a new close tag; only used in order to auto-close unclosed tags at the end of the
388 /// format string.
389 pub fn new_close() -> Self {
390 ColorTag {
391 source: None,
392 span: None,
393 is_close: true,
394 change_set: ChangeSet::default(),
395 }
396 }
397
398 /// Sets the span of the tag.
399 pub fn set_span(&mut self, span: Span) {
400 self.span = Some(span);
401 }
402}
403
404/// The changes that are implied by a tag.
405#[derive(Debug, PartialEq, Default)]
406pub struct ChangeSet {
407 /// If it is `Some`, then the foreground color has to be changed.
408 pub foreground: Option<Color>,
409 /// If it is `Some`, then the background color has to be changed.
410 pub background: Option<Color>,
411 /// If it is `true`, then the bold attribute has to be set (or unset for a close tag).
412 pub bold: bool,
413 /// If it is `true`, then the dim attribute has to be set (or unset for a close tag).
414 pub dim: bool,
415 /// If it is `true`, then the underline attribute has to be set (or unset for a close tag).
416 pub underline: bool,
417 /// If it is `true`, then the italics attribute has to be set (or unset for a close tag).
418 pub italics: bool,
419 /// If it is `true`, then the blink attribute has to be set (or unset for a close tag).
420 pub blink: bool,
421 /// If it is `true`, then the strike attribute has to be set (or unset for a close tag).
422 pub strike: bool,
423 /// If it is `true`, then the reverse attribute has to be set (or unset for a close tag).
424 pub reverse: bool,
425 /// If it is `true`, then the conceal attribute has to be set (or unset for a close tag).
426 pub conceal: bool,
427}
428
429impl ChangeSet {
430 /// Checks if there is nothing to change (used to detect the `</>` tag).
431 pub fn is_void(&self) -> bool {
432 and!(
433 self.foreground.is_none(),
434 self.background.is_none(),
435 !self.bold,
436 !self.dim,
437 !self.underline,
438 !self.italics,
439 !self.blink,
440 !self.strike,
441 !self.reverse,
442 !self.conceal,
443 )
444 }
445}
446
447impl From<&[Change]> for ChangeSet {
448 fn from(changes: &[Change]) -> ChangeSet {
449 let mut change_set: ChangeSet = ChangeSet::default();
450 for change: &Change in changes {
451 match change {
452 Change::Foreground(color: &Color) => change_set.foreground = Some(color.clone()),
453 Change::Background(color: &Color) => change_set.background = Some(color.clone()),
454 Change::Bold => change_set.bold = true,
455 Change::Dim => change_set.dim = true,
456 Change::Underline => change_set.underline = true,
457 Change::Italics => change_set.italics = true,
458 Change::Blink => change_set.blink = true,
459 Change::Strike => change_set.strike = true,
460 Change::Reverse => change_set.reverse = true,
461 Change::Conceal => change_set.conceal = true,
462 }
463 }
464 change_set
465 }
466}
467
468/// A single change to be done inside a tag. Tags with multiple keywords like `<red;bold>` will
469/// have multiple [`Change`]s.
470#[derive(Debug, PartialEq, Clone)]
471pub enum Change {
472 Foreground(Color),
473 Background(Color),
474 Bold,
475 Dim,
476 Underline,
477 Italics,
478 Blink,
479 Strike,
480 Reverse,
481 Conceal,
482}
483
484impl TryFrom<&str> for Change {
485 type Error = ();
486
487 /// Tries to convert a keyword like `red`, `bold` into a [`Change`] instance.
488 #[rustfmt::skip]
489 fn try_from(input: &str) -> Result<Self, Self::Error> {
490 macro_rules! color16 {
491 ($kind:ident $intensity:ident $base_color:ident) => {
492 Change::$kind(Color::Color16(Color16::new(
493 BaseColor::$base_color,
494 Intensity::$intensity,
495 )))
496 };
497 }
498
499 let change = match input {
500 "s" | "strong" | "bold" | "em" => Change::Bold,
501 "dim" => Change::Dim,
502 "u" | "underline" => Change::Underline,
503 "i" | "italic" | "italics" => Change::Italics,
504 "blink" => Change::Blink,
505 "strike" => Change::Strike,
506 "reverse" | "rev" => Change::Reverse,
507 "conceal" | "hide" => Change::Conceal,
508
509 "k" | "black" => color16!(Foreground Normal Black),
510 "r" | "red" => color16!(Foreground Normal Red),
511 "g" | "green" => color16!(Foreground Normal Green),
512 "y" | "yellow" => color16!(Foreground Normal Yellow),
513 "b" | "blue" => color16!(Foreground Normal Blue),
514 "m" | "magenta" => color16!(Foreground Normal Magenta),
515 "c" | "cyan" => color16!(Foreground Normal Cyan),
516 "w" | "white" => color16!(Foreground Normal White),
517
518 "k!" | "black!" | "bright-black" => color16!(Foreground Bright Black),
519 "r!" | "red!" | "bright-red" => color16!(Foreground Bright Red),
520 "g!" | "green!" | "bright-green" => color16!(Foreground Bright Green),
521 "y!" | "yellow!" | "bright-yellow" => color16!(Foreground Bright Yellow),
522 "b!" | "blue!" | "bright-blue" => color16!(Foreground Bright Blue),
523 "m!" | "magenta!" | "bright-magenta" => color16!(Foreground Bright Magenta),
524 "c!" | "cyan!" | "bright-cyan" => color16!(Foreground Bright Cyan),
525 "w!" | "white!" | "bright-white" => color16!(Foreground Bright White),
526
527 "K" | "bg-black" => color16!(Background Normal Black),
528 "R" | "bg-red" => color16!(Background Normal Red),
529 "G" | "bg-green" => color16!(Background Normal Green),
530 "Y" | "bg-yellow" => color16!(Background Normal Yellow),
531 "B" | "bg-blue" => color16!(Background Normal Blue),
532 "M" | "bg-magenta" => color16!(Background Normal Magenta),
533 "C" | "bg-cyan" => color16!(Background Normal Cyan),
534 "W" | "bg-white" => color16!(Background Normal White),
535
536 "K!" | "bg-black!" | "bg-bright-black" => color16!(Background Bright Black),
537 "R!" | "bg-red!" | "bg-bright-red" => color16!(Background Bright Red),
538 "G!" | "bg-green!" | "bg-bright-green" => color16!(Background Bright Green),
539 "Y!" | "bg-yellow!" | "bg-bright-yellow" => color16!(Background Bright Yellow),
540 "B!" | "bg-blue!" | "bg-bright-blue" => color16!(Background Bright Blue),
541 "M!" | "bg-magenta!" | "bg-bright-magenta" => color16!(Background Bright Magenta),
542 "C!" | "bg-cyan!" | "bg-bright-cyan" => color16!(Background Bright Cyan),
543 "W!" | "bg-white!" | "bg-bright-white" => color16!(Background Bright White),
544
545 _ => return Err(()),
546 };
547
548 Ok(change)
549 }
550}
551
552/// Which "kind" of color has to be changed.
553#[derive(Debug, PartialEq, Clone)]
554pub enum ColorKind {
555 Background,
556 Foreground,
557}
558
559impl ColorKind {
560 pub fn to_change(&self, color: Color) -> Change {
561 match self {
562 Self::Foreground => Change::Foreground(color),
563 Self::Background => Change::Background(color),
564 }
565 }
566}
567
568/// An "extended" color, which can be either a real color or the "normal", default color.
569#[derive(Debug, PartialEq, Clone)]
570pub enum ExtColor {
571 Normal,
572 Color(Color),
573}
574
575impl Default for ExtColor {
576 fn default() -> Self {
577 Self::Normal
578 }
579}
580
581#[derive(Debug, PartialEq, Clone)]
582pub enum Color {
583 Color16(Color16),
584 Color256(Color256),
585 ColorRgb(ColorRgb),
586}
587
588/// A terminal color.
589#[derive(Debug, PartialEq, Clone)]
590pub struct Color16 {
591 base_color: BaseColor,
592 intensity: Intensity,
593}
594
595impl Color16 {
596 pub fn new(base_color: BaseColor, intensity: Intensity) -> Self {
597 Self { base_color, intensity }
598 }
599
600 /// Converts a color to a terminfo constant name (available in the `color-print` package).
601 #[cfg(feature = "terminfo")]
602 pub fn terminfo_constant(&self, is_foreground: bool) -> String {
603 let mut constant = if is_foreground {
604 String::new()
605 } else {
606 "BG_".to_string()
607 };
608
609 if matches!(self.intensity, Intensity::Bright) {
610 constant.push_str("BRIGHT_");
611 }
612
613 constant.push_str(self.base_color.uppercase_str());
614
615 constant
616 }
617}
618
619/// The intensity of a terminal color.
620#[derive(Debug, PartialEq, Copy, Clone)]
621pub enum Intensity {
622 Normal,
623 Bright,
624}
625
626impl Intensity {
627 pub fn new(is_bright: bool) -> Self {
628 if is_bright {
629 Self::Bright
630 } else {
631 Self::Normal
632 }
633 }
634}
635
636/// A "base" terminal color, which has to be completed with an [`Intensity`] in order to describe a
637/// whole terminal color.
638#[derive(Debug, PartialEq, Copy, Clone)]
639pub enum BaseColor {
640 Black,
641 Red,
642 Green,
643 Yellow,
644 Blue,
645 Magenta,
646 Cyan,
647 White,
648}
649
650impl BaseColor {
651 /// Return the index of a color, in the same ordering as the ANSI color sequences.
652 #[cfg(not(feature = "terminfo"))]
653 pub fn index(&self) -> u8 {
654 match self {
655 Self::Black => 0,
656 Self::Red => 1,
657 Self::Green => 2,
658 Self::Yellow => 3,
659 Self::Blue => 4,
660 Self::Magenta => 5,
661 Self::Cyan => 6,
662 Self::White => 7,
663 }
664 }
665
666 /// Used to generate terminfo constants, see [`Color16::terminfo_constant()`].
667 #[cfg(feature = "terminfo")]
668 pub fn uppercase_str(&self) -> &'static str {
669 match self {
670 Self::Black => "BLACK",
671 Self::Red => "RED",
672 Self::Green => "GREEN",
673 Self::Yellow => "YELLOW",
674 Self::Blue => "BLUE",
675 Self::Magenta => "MAGENTA",
676 Self::Cyan => "CYAN",
677 Self::White => "WHITE",
678 }
679 }
680}
681
682/// A color in the 256-color palette.
683#[derive(Debug, PartialEq, Clone)]
684pub struct Color256(pub u8);
685
686/// An RGB color.
687#[derive(Debug, PartialEq, Clone)]
688pub struct ColorRgb {
689 pub r: u8,
690 pub g: u8,
691 pub b: u8,
692}
693
694#[cfg(test)]
695mod tests {
696 #[cfg(feature = "terminfo")]
697 use super::*;
698 #[cfg(feature = "terminfo")]
699 use crate::parse::color_tag;
700
701 #[test]
702 #[cfg(feature = "terminfo")]
703 fn terminfo_apply_tag_to_context() {
704 let mut context = Context::new();
705
706 macro_rules! apply_tag {
707 ($s:expr) => {
708 context
709 .terminfo_apply_tags(vec![color_tag($s).unwrap().1])
710 .unwrap()
711 };
712 }
713
714 let constants = apply_tag!("<r>");
715 assert_eq!(constants, ["RED"]);
716 let constants = apply_tag!("</r>");
717 assert_eq!(constants, ["CLEAR"]);
718 let constants = apply_tag!("<r>");
719 assert_eq!(constants, ["RED"]);
720 let constants = apply_tag!("<s>");
721 assert_eq!(constants, ["BOLD"]);
722 let constants = apply_tag!("</s>");
723 assert_eq!(constants, ["CLEAR", "RED"]);
724 let constants = apply_tag!("</r>");
725 assert_eq!(constants, ["CLEAR"]);
726 }
727
728 #[test]
729 #[cfg(feature = "terminfo")]
730 fn terminfo_apply_tag_to_context_2() {
731 let mut context = Context::new();
732
733 macro_rules! apply_tag {
734 ($s:expr) => {
735 context
736 .terminfo_apply_tags(vec![color_tag($s).unwrap().1])
737 .unwrap()
738 };
739 }
740
741 let constants = apply_tag!("<r>");
742 assert_eq!(constants, ["RED"]);
743 let constants = apply_tag!("<Y>");
744 assert_eq!(constants, ["BG_YELLOW"]);
745 let constants = apply_tag!("<s>");
746 assert_eq!(constants, ["BOLD"]);
747 let constants = apply_tag!("<u>");
748 assert_eq!(constants, ["UNDERLINE"]);
749 let constants = apply_tag!("</u>");
750 assert_eq!(constants, ["NO_UNDERLINE"]);
751 let constants = apply_tag!("</s>");
752 assert_eq!(constants, ["CLEAR", "RED", "BG_YELLOW"]);
753 }
754
755 #[test]
756 #[cfg(feature = "terminfo")]
757 fn terminfo_apply_tag_to_context_3() {
758 let mut context = Context::new();
759
760 macro_rules! apply_tag {
761 ($s:expr) => {
762 context.terminfo_apply_tags(vec![color_tag($s).unwrap().1])
763 };
764 }
765
766 let res = apply_tag!("</r>");
767 assert_eq!(res, Err(SpanError::new(Error::NoTagToClose, None)));
768 apply_tag!("<r>").unwrap();
769 let res = apply_tag!("</s>");
770 assert_eq!(
771 res,
772 Err(SpanError::new(
773 Error::MismatchCloseTag("<r>".to_owned(), "</s>".to_owned()),
774 None
775 ))
776 );
777 }
778}
779