chroma_dbg/
lib.rs

1mod config;
2mod util;
3
4#[cfg(test)]
5mod tests;
6
7pub use config::{ChromaConfig, Color, InlineThreshold, IntegerFormat};
8
9use core::fmt;
10use std::fmt::{Debug, Write};
11
12use pest::{iterators::Pair, Parser};
13use pest_derive::Parser;
14use util::IndentedWriter;
15
16#[derive(Parser)]
17#[grammar = "dbg.pest"]
18struct DbgParser;
19
20pub trait ChromaDebug: Debug {
21    fn dbg_chroma(&self) -> String;
22}
23
24impl<T: Debug> ChromaDebug for T {
25    fn dbg_chroma(&self) -> String {
26        ChromaConfig::DEFAULT.format(self)
27    }
28}
29
30impl ChromaConfig {
31    pub fn format(&self, value: &impl Debug) -> String {
32        self.try_format(value)
33            .unwrap_or_else(|_| format!("{:#?}", value))
34    }
35
36    pub fn try_format(&self, value: &impl Debug) -> Result<String, pest::error::Error<Rule>> {
37        let original = format!("{:?}", value);
38        self.try_format_string(&original)
39    }
40
41    pub fn try_format_string(&self, value: &str) -> Result<String, pest::error::Error<Rule>> {
42        let pairs = DbgParser::parse(Rule::main, value)?;
43        let mut output = String::new();
44        let mut writer = IndentedWriter::new(&mut output);
45        for pair in pairs {
46            self.emit_value(&mut writer, pair);
47        }
48        drop(writer);
49        Ok(output)
50    }
51
52    fn emit_value<W: fmt::Write>(&self, w: &mut IndentedWriter<W>, pair: Pair<'_, Rule>) {
53        match pair.as_rule() {
54            Rule::r#struct => {
55                let mut pairs = pair.into_inner();
56                let name = pairs.next().unwrap().as_str();
57                Self::emit_colored(w, name, self.identifier_color);
58                Self::emit_plain(w, " { ");
59                let inline = self.inline_struct.should_inline(pairs.as_str().len());
60                if !inline {
61                    Self::emit_plain(w, "\n");
62                }
63                let fields = pairs.next().unwrap().into_inner();
64                let field_count = fields.len();
65                for (i, field) in fields.enumerate() {
66                    let mut field = field.into_inner();
67                    let name = field.next().unwrap().as_str();
68                    if !inline {
69                        w.push_indent();
70                    }
71                    Self::emit_colored(w, name, self.field_color);
72                    Self::emit_plain(w, ": ");
73                    self.emit_value(w, field.next().unwrap());
74                    if i < field_count - 1 || !inline {
75                        Self::emit_plain(w, ", ");
76                    } else {
77                        Self::emit_plain(w, " ");
78                    }
79
80                    if !inline {
81                        Self::emit_plain(w, "\n");
82                        w.pop_indent();
83                    }
84                }
85                Self::emit_plain(w, "}");
86            }
87            Rule::map_jsonlike => {
88                let pairs = pair.into_inner();
89                Self::emit_plain(w, "{");
90                let fields = pairs.clone().next().map(|p| p.into_inner());
91                if let Some(fields) = fields {
92                    Self::emit_plain(w, " ");
93                    let inline = self.inline_struct.should_inline(pairs.as_str().len());
94                    if !inline {
95                        Self::emit_plain(w, "\n");
96                    }
97                    let field_count = fields.len();
98                    for (i, field) in fields.enumerate() {
99                        let mut field = field.into_inner();
100                        let name = field.next().unwrap().as_str();
101                        if !inline {
102                            w.push_indent();
103                        }
104                        Self::emit_colored(w, name, self.string_color);
105                        Self::emit_plain(w, ": ");
106                        self.emit_value(w, field.next().unwrap());
107                        if i < field_count - 1 || !inline {
108                            Self::emit_plain(w, ", ");
109                        } else {
110                            Self::emit_plain(w, " ");
111                        }
112
113                        if !inline {
114                            Self::emit_plain(w, "\n");
115                            w.pop_indent();
116                        }
117                    }
118                }
119                Self::emit_plain(w, "}");
120            }
121            Rule::tuple_struct => {
122                let mut pairs = pair.into_inner();
123                let name = pairs.next().unwrap().as_str();
124                Self::emit_colored(w, name, self.identifier_color);
125                Self::emit_plain(w, "(");
126                let fields = pairs.next().unwrap().into_inner();
127                let field_count = fields.len();
128                for (i, field) in fields.enumerate() {
129                    self.emit_value(w, field);
130                    if i < field_count - 1 {
131                        Self::emit_plain(w, ", ");
132                    }
133                }
134                Self::emit_plain(w, ")");
135            }
136            Rule::bitflags_struct => {
137                let mut pairs = pair.into_inner();
138                let name = pairs.next().unwrap().as_str();
139                Self::emit_colored(w, name, self.identifier_color);
140                Self::emit_plain(w, "(");
141                let fields = pairs.next().unwrap().into_inner();
142                let field_count = fields.len();
143                for (i, field) in fields.enumerate() {
144                    self.emit_value(w, field);
145                    if i < field_count - 1 {
146                        Self::emit_plain(w, " | ");
147                    }
148                }
149                Self::emit_plain(w, ")");
150            }
151            Rule::number => {
152                let num = pair.into_inner().next().unwrap();
153                match num.as_rule() {
154                    Rule::integer => {
155                        // Try to parse the number as an integer. If it fails (eg. it's too large/unsigned), just print it normally
156                        if let Ok(num) = num.as_str().parse::<u64>() {
157                            Self::emit_colored(
158                                w,
159                                self.integer_format.format(num).as_str(),
160                                self.numerical_color,
161                            );
162                        } else {
163                            Self::emit_colored(w, num.as_str(), self.numerical_color);
164                        }
165                    }
166                    // If it's not a (decimal) integer, print it normally
167                    _ => {
168                        Self::emit_colored(w, num.as_str(), self.numerical_color);
169                    }
170                }
171            }
172            Rule::string => {
173                Self::emit_colored(w, "\"", self.string_color);
174                let chars = pair.into_inner();
175                for char in chars {
176                    let char = char.into_inner().next().unwrap();
177                    match char.as_rule() {
178                        Rule::escape_sequence => {
179                            Self::emit_colored(w, char.as_str(), self.string_escape_color);
180                        }
181                        _ => {
182                            Self::emit_colored(w, char.as_str(), self.string_color);
183                        }
184                    }
185                }
186                Self::emit_colored(w, "\"", self.string_color);
187            }
188            Rule::char_literal => {
189                Self::emit_colored(w, "'", self.string_color);
190                let char = pair.into_inner().next().unwrap();
191                println!("{:?}", char.as_rule());
192                if char.as_rule() == Rule::char {
193                    let char = char.into_inner().next().unwrap();
194                    match char.as_rule() {
195                        Rule::escape_sequence => {
196                            Self::emit_colored(w, char.as_str(), self.string_escape_color);
197                        }
198                        _ => {
199                            Self::emit_colored(w, char.as_str(), self.string_color);
200                        }
201                    }
202                }
203                Self::emit_colored(w, "'", self.string_color);
204            }
205            Rule::enum_variant => {
206                Self::emit_colored(w, pair.as_str(), self.identifier_color);
207            }
208            Rule::array => {
209                Self::emit_plain(w, "[");
210                let inline = self.inline_array.should_inline(pair.as_str().len());
211                let elements = pair.into_inner();
212                let element_count = elements.len();
213                for (i, field) in elements.enumerate() {
214                    if i == 0 {
215                        if inline {
216                            Self::emit_plain(w, " ");
217                        } else {
218                            Self::emit_plain(w, "\n");
219                        }
220                    }
221                    if !inline {
222                        w.push_indent();
223                    }
224                    self.emit_value(w, field);
225                    if i < element_count - 1 || !inline {
226                        Self::emit_plain(w, ", ");
227                    } else {
228                        Self::emit_plain(w, " ");
229                    }
230
231                    if !inline {
232                        Self::emit_plain(w, "\n");
233                        w.pop_indent();
234                    }
235                }
236                Self::emit_plain(w, "]");
237            }
238            Rule::boolean => {
239                Self::emit_colored(w, pair.as_str(), self.numerical_color);
240            }
241            _ => {
242                let value = pair.as_str();
243                Self::emit_plain(w, value);
244            }
245        }
246    }
247
248    fn emit_plain<W: fmt::Write>(w: &mut IndentedWriter<W>, s: &str) {
249        w.write_str(s).ok();
250    }
251
252    fn emit_colored<W: fmt::Write>(w: &mut IndentedWriter<W>, s: &str, color: Color) {
253        let style = anstyle::Style::new().fg_color(Some(anstyle::Color::Rgb(color.into())));
254        let reset = anstyle::Reset;
255        write!(w, "{style}{s}{reset}").ok();
256    }
257}