sql_fun_core/
highlighter.rs1use std::{io::IsTerminal, path::Path, str::FromStr};
2
3#[derive(thiserror::Error, Debug)]
5pub enum HighlighterThemeError {
6 #[error("Can not to parse as color : {0}")]
8 ParseColorError(String),
9 #[error(transparent)]
11 IoError(#[from] std::io::Error),
12 #[error(transparent)]
14 Serde(#[from] toml::de::Error),
15}
16
17#[derive(Debug, Clone, Copy, serde::Serialize, serde::Deserialize, clap::ValueEnum)]
19pub enum TerminalColor {
20 Auto,
22 Never,
24 Ansi,
26}
27
28impl TerminalColor {
29 #[must_use]
31 pub fn get_value(&self) -> Self {
32 if matches!(self, Self::Auto) {
33 if std::io::stderr().is_terminal() {
34 Self::Ansi
35 } else {
36 Self::Never
37 }
38 } else {
39 *self
40 }
41 }
42
43 #[must_use]
45 pub fn is_never(&self) -> bool {
46 matches!(self, Self::Never)
47 }
48}
49
50#[expect(clippy::struct_excessive_bools)]
52#[derive(Debug, serde::Serialize, serde::Deserialize, PartialEq, Default)]
53pub struct HighlighterStyle {
54 foreground: Option<String>,
55 background: Option<String>,
56 #[serde(default, skip_serializing_if = "is_false")]
57 bold: bool,
58 #[serde(default, skip_serializing_if = "is_false")]
59 dimmed: bool,
60 #[serde(default, skip_serializing_if = "is_false")]
61 italic: bool,
62 #[serde(default, skip_serializing_if = "is_false")]
63 underline: bool,
64 #[serde(default, skip_serializing_if = "is_false")]
65 strikethrough: bool,
66 #[serde(default, skip_serializing_if = "is_false")]
67 hidden: bool,
68}
69
70#[expect(clippy::trivially_copy_pass_by_ref)]
71fn is_false(value: &bool) -> bool {
72 !value
73}
74
75impl HighlighterStyle {
76 fn is_default(&self) -> bool {
77 self == &Self::default()
78 }
79}
80
81#[derive(Debug, serde::Serialize, serde::Deserialize, Default)]
83pub struct HighlighterTheme {
84 #[serde(default, skip_serializing_if = "HighlighterStyle::is_default")]
85 identifier: HighlighterStyle,
86 #[serde(default, skip_serializing_if = "HighlighterStyle::is_default")]
87 constant: HighlighterStyle,
88 #[serde(default, skip_serializing_if = "HighlighterStyle::is_default")]
89 operator: HighlighterStyle,
90 #[serde(default, skip_serializing_if = "HighlighterStyle::is_default")]
91 keyword: HighlighterStyle,
92 #[serde(default, skip_serializing_if = "HighlighterStyle::is_default")]
93 delimiter: HighlighterStyle,
94 #[serde(default, skip_serializing_if = "HighlighterStyle::is_default")]
95 comment: HighlighterStyle,
96}
97
98impl HighlighterTheme {
99 pub fn from_toml_str(toml: &str) -> Result<Self, HighlighterThemeError> {
105 Ok(toml::from_str(toml)?)
106 }
107
108 #[must_use]
110 pub fn identifier(&self) -> &HighlighterStyle {
111 &self.identifier
112 }
113
114 #[must_use]
116 pub fn constant(&self) -> &HighlighterStyle {
117 &self.constant
118 }
119
120 #[must_use]
122 pub fn operator(&self) -> &HighlighterStyle {
123 &self.operator
124 }
125
126 #[must_use]
128 pub fn keyword(&self) -> &HighlighterStyle {
129 &self.keyword
130 }
131
132 #[must_use]
134 pub fn comment(&self) -> &HighlighterStyle {
135 &self.comment
136 }
137
138 #[must_use]
140 pub fn delimiter(&self) -> &HighlighterStyle {
141 &self.delimiter
142 }
143}
144
145#[cfg(test)]
146mod test_highlighter_theme_from_toml_str {
147 use testresult::TestResult;
148
149 use crate::HighlighterTheme;
150
151 #[rstest::rstest]
152 #[case(
153 r#"
154identifier = { foreground= "blue" }
155constant = { foreground= "blue" }
156comment = { foreground= "green", dimmed = true }
157"#
158 )]
159 fn test_from_toml_str(#[case] toml: &str) -> TestResult {
160 let _theme = HighlighterTheme::from_toml_str(toml)?;
161 Ok(())
162 }
163}
164
165impl HighlighterTheme {
166 pub fn from_toml<P: AsRef<Path>>(path: P) -> Result<Self, HighlighterThemeError> {
172 let toml = fs_err::read_to_string(path)?;
173 Self::from_toml_str(&toml)
174 }
175}
176
177impl HighlighterStyle {
178 pub fn get_style(&self) -> Result<owo_colors::Style, HighlighterThemeError> {
184 let style = owo_colors::Style::default();
185 let style = if let Some(foreground) = &self.foreground {
186 let fore_color = owo_colors::DynColors::from_str(foreground)
187 .map_err(|_| HighlighterThemeError::ParseColorError(foreground.clone()))?;
188 style.color(fore_color)
189 } else {
190 style
191 };
192 let style = if let Some(background) = &self.background {
193 let back_color = owo_colors::DynColors::from_str(background)
194 .map_err(|_| HighlighterThemeError::ParseColorError(background.clone()))?;
195 style.on_color(back_color)
196 } else {
197 style
198 };
199
200 if self.bold {
201 style.bold()
202 } else {
203 style
204 };
205 let style = if self.dimmed { style.dimmed() } else { style };
206 let style = if self.italic { style.italic() } else { style };
207 let style = if self.strikethrough {
208 style.strikethrough()
209 } else {
210 style
211 };
212 let style = if self.hidden { style.hidden() } else { style };
213
214 Ok(style)
215 }
216}