1use std::{borrow::Cow, collections::BTreeMap, time::Duration};
2
3use console::{Color, StyledObject, style};
4use termbg::Theme;
5use tracing::{Level, debug, field::Field};
6use tracing_subscriber::Layer;
7
8pub use termbg;
9
10use crate::writer::{Writeln, Writer};
11
12#[derive(Clone)]
13enum StyleFollow {
14 OmaTheme,
15 TermTheme,
16}
17
18pub enum Action {
19 Emphasis,
20 Foreground,
21 Secondary,
22 EmphasisSecondary,
23 WARN,
24 Purple,
25 Note,
26 UpgradeTips,
27 PendingBg,
28}
29
30impl Action {
31 fn dark(&self) -> u8 {
32 match self {
33 Action::Emphasis => 148,
34 Action::Foreground => 72,
35 Action::Secondary => 182,
36 Action::EmphasisSecondary => 114,
37 Action::WARN => 214,
38 Action::Purple => 141,
39 Action::Note => 178,
40 Action::UpgradeTips => 87,
41 Action::PendingBg => 25,
42 }
43 }
44
45 fn light(&self) -> u8 {
46 match self {
47 Action::Emphasis => 142,
48 Action::Foreground => 72,
49 Action::Secondary => 167,
50 Action::EmphasisSecondary => 106,
51 Action::WARN => 208,
52 Action::Purple => 141,
53 Action::Note => 172,
54 Action::UpgradeTips => 63,
55 Action::PendingBg => 189,
56 }
57 }
58}
59pub struct OmaColorFormat {
63 follow: StyleFollow,
65 pub theme: Option<Theme>,
67}
68
69impl OmaColorFormat {
70 pub fn new(follow: bool, duration: Duration) -> Self {
71 Self {
72 follow: if follow {
73 StyleFollow::TermTheme
74 } else {
75 StyleFollow::OmaTheme
76 },
77 theme: if !follow {
78 termbg::theme(duration)
79 .map_err(|e| {
80 debug!(
81 "Failed to apply oma color schemes, falling back to default terminal colors: {e:?}."
82 );
83 e
84 })
85 .ok()
86 } else {
87 None
88 },
89 }
90 }
91 pub fn color_str<D>(&self, input: D, color: Action) -> StyledObject<D> {
104 match self.follow {
105 StyleFollow::OmaTheme => match self.theme {
106 Some(Theme::Dark) => match color {
107 x @ Action::PendingBg => style(input).bg(Color::Color256(x.dark())).bold(),
108 x => style(input).color256(x.dark()),
109 },
110 Some(Theme::Light) => match color {
111 x @ Action::PendingBg => style(input).bg(Color::Color256(x.light())).bold(),
112 x => style(input).color256(x.light()),
113 },
114 None => term_color(input, color),
115 },
116 StyleFollow::TermTheme => term_color(input, color),
117 }
118 }
119}
120
121fn term_color<D>(input: D, color: Action) -> StyledObject<D> {
122 match color {
123 Action::Emphasis => style(input).green(),
124 Action::Secondary => style(input).dim(),
125 Action::EmphasisSecondary => style(input).cyan(),
126 Action::WARN => style(input).yellow().bold(),
127 Action::Purple => style(input).magenta(),
128 Action::Note => style(input).yellow(),
129 Action::Foreground => style(input).cyan().bold(),
130 Action::UpgradeTips => style(input).blue().bold(),
131 Action::PendingBg => style(input).bg(Color::Blue).bold(),
132 }
133}
134pub struct OmaLayer {
151 with_ansi: bool,
153 writer: Writer,
155}
156
157impl Default for OmaLayer {
158 fn default() -> Self {
159 Self {
160 with_ansi: true,
161 writer: Writer::default(),
162 }
163 }
164}
165
166impl OmaLayer {
167 pub fn new() -> Self {
168 OmaLayer::default()
169 }
170
171 pub fn with_ansi(mut self, with_ansi: bool) -> Self {
175 self.with_ansi = with_ansi;
176 self
177 }
178}
179
180impl<S> Layer<S> for OmaLayer
181where
182 S: tracing::Subscriber,
183 S: for<'lookup> tracing_subscriber::registry::LookupSpan<'lookup>,
184{
185 fn on_event(
186 &self,
187 event: &tracing::Event<'_>,
188 _ctx: tracing_subscriber::layer::Context<'_, S>,
189 ) {
190 let level = *event.metadata().level();
191
192 let prefix = if self.with_ansi {
193 Cow::Owned(match level {
194 Level::DEBUG => console::style("DEBUG").dim().to_string(),
195 Level::INFO => console::style("INFO").blue().bold().to_string(),
196 Level::WARN => console::style("WARNING").yellow().bold().to_string(),
197 Level::ERROR => console::style("ERROR").red().bold().to_string(),
198 Level::TRACE => console::style("TRACE").dim().to_string(),
199 })
200 } else {
201 Cow::Borrowed(match level {
202 Level::DEBUG => "DEBUG",
203 Level::INFO => "INFO",
204 Level::WARN => "WARNING",
205 Level::ERROR => "ERROR",
206 Level::TRACE => "TRACE",
207 })
208 };
209
210 let mut visitor = OmaRecorder(BTreeMap::new());
211 event.record(&mut visitor);
212
213 for (k, v) in visitor.0 {
214 if k == "message" {
215 if self.with_ansi {
216 self.writer.writeln(&prefix, &v).ok();
217 } else {
218 self.writer
219 .writeln(&prefix, &console::strip_ansi_codes(&v))
220 .ok();
221 }
222 }
223 }
224 }
225}
226struct OmaRecorder<'a>(BTreeMap<&'a str, String>);
240
241impl tracing::field::Visit for OmaRecorder<'_> {
242 fn record_f64(&mut self, field: &Field, value: f64) {
243 self.0.insert(field.name(), value.to_string());
244 }
245
246 fn record_i64(&mut self, field: &Field, value: i64) {
247 self.0.insert(field.name(), value.to_string());
248 }
249
250 fn record_u64(&mut self, field: &Field, value: u64) {
251 self.0.insert(field.name(), value.to_string());
252 }
253
254 fn record_bool(&mut self, field: &Field, value: bool) {
255 self.0.insert(field.name(), value.to_string());
256 }
257
258 fn record_str(&mut self, field: &Field, value: &str) {
259 self.0.insert(field.name(), value.to_string());
260 }
261
262 fn record_error(&mut self, field: &Field, value: &(dyn std::error::Error + 'static)) {
263 self.0.insert(field.name(), format!("{value:#?}"));
264 }
265
266 fn record_debug(&mut self, field: &Field, value: &dyn std::fmt::Debug) {
267 self.0.insert(field.name(), format!("{value:#?}"));
268 }
269}