1use derive_new::new;
4use std::{fmt::Debug, hash::Hash};
5
6use indexmap::IndexSet;
7
8#[derive(Copy, Clone)]
9pub struct Attribute {
10 attr: console::Attribute,
11}
12
13impl Debug for Attribute {
14 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
15 write!(
16 f,
17 "{}",
18 match self.attr {
19 console::Attribute::Bold => "bold",
20 console::Attribute::Dim => "dim",
21 console::Attribute::Italic => "italic",
22 console::Attribute::Underlined => "underlined",
23 console::Attribute::Blink => "blink",
24 console::Attribute::Reverse => "reverse",
25 console::Attribute::Hidden => "hidden",
26 }
27 )
28 }
29}
30
31impl PartialOrd for Attribute {
32 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
33 self.attr.partial_cmp(&other.attr)
34 }
35}
36
37impl Ord for Attribute {
38 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
39 self.attr.cmp(&other.attr)
40 }
41}
42
43impl PartialEq for Attribute {
44 fn eq(&self, other: &Self) -> bool {
45 self.attr == other.attr
46 }
47}
48
49impl Eq for Attribute {}
50
51impl Hash for Attribute {
52 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
53 match self.attr {
54 console::Attribute::Bold => 0.hash(state),
55 console::Attribute::Dim => 1.hash(state),
56 console::Attribute::Italic => 2.hash(state),
57 console::Attribute::Underlined => 3.hash(state),
58 console::Attribute::Blink => 4.hash(state),
59 console::Attribute::Reverse => 5.hash(state),
60 console::Attribute::Hidden => 6.hash(state),
61 }
62 }
63}
64
65impl Into<Attribute> for console::Attribute {
66 fn into(self) -> Attribute {
67 Attribute { attr: self }
68 }
69}
70
71#[derive(Clone, new)]
72pub struct Style {
73 #[new(value = "None")]
74 fg: Option<console::Color>,
75 #[new(value = "None")]
76 bg: Option<console::Color>,
77 #[new(default)]
78 attrs: IndexSet<Attribute>,
79}
80
81impl Default for Style {
82 fn default() -> Self {
83 Style::new()
84 }
85}
86
87impl Style {
88 pub fn apply_to<D>(&self, fragment: D) -> console::StyledObject<D> {
89 let style: console::Style = self.into();
90 style.apply_to(fragment)
91 }
92
93 pub fn fg(mut self, color: impl Into<console::Color>) -> Style {
94 self.fg = Some(color.into());
95 self
96 }
97
98 pub fn bg(mut self, color: impl Into<console::Color>) -> Style {
99 self.bg = Some(color.into());
100 self
101 }
102
103 pub fn attr(mut self, attr: impl Into<Attribute>) -> Style {
104 self.attrs.insert(attr.into());
105 self
106 }
107}
108
109impl<'a> From<&'a Style> for console::Style {
110 fn from(style: &'a Style) -> Self {
111 let style = style.clone();
112 style.into()
113 }
114}
115
116impl From<Style> for console::Style {
117 fn from(style: Style) -> Self {
118 let mut console_style = console::Style::new();
119
120 if let Some(fg) = style.fg {
121 console_style = console_style.fg(fg);
122 }
123
124 if let Some(bg) = style.bg {
125 console_style = console_style.bg(bg);
126 }
127
128 for attr in style.attrs {
129 console_style = console_style.attr(attr.attr);
130 }
131
132 console_style
133 }
134}
135
136impl Debug for Style {
137 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
138 let Self { fg, bg, attrs } = self;
139
140 let mut desc = String::new();
141 let mut short = true;
142
143 match (fg, bg) {
144 (Some(fg), None) => {
145 desc.push_str(&format!("{:?}", fg));
146 }
147 (None, Some(bg)) => {
148 desc.push_str(&format!("normal on {:?}", bg));
149 short = false;
150 }
151 (None, None) => {
152 desc.push_str("normal");
153 }
154 (Some(fg), Some(bg)) => {
155 desc.push_str(&format!("{:?} on {:?}", fg, bg));
156 short = false;
157 }
158 }
159
160 let mut debug_attrs = String::new();
161
162 for attr in itertools::sorted(attrs.iter()) {
163 debug_attrs.push_str(", ");
164
165 debug_attrs.push_str(&format!("{:?}", attr));
166 }
167
168 if !debug_attrs.is_empty() {
169 short = false;
170 }
171
172 if short {
173 write!(f, "{}", desc)
174 } else {
175 write!(f, "[{}{}]", desc, debug_attrs)
176 }
177 }
178}