1use std::{
4 collections::{HashMap, VecDeque},
5 fmt::{self, Write},
6 hash::{DefaultHasher, Hash, Hasher},
7 str,
8};
9
10use facet_peek::Peek;
11use facet_trait::Facet;
12
13use crate::{ansi, color::ColorGenerator};
14
15pub struct PrettyPrinter {
17 indent_size: usize,
18 max_depth: Option<usize>,
19 color_generator: ColorGenerator,
20 use_colors: bool,
21}
22
23impl Default for PrettyPrinter {
24 fn default() -> Self {
25 Self {
26 indent_size: 2,
27 max_depth: None,
28 color_generator: ColorGenerator::default(),
29 use_colors: true,
30 }
31 }
32}
33
34enum StackState {
36 Start,
37 ProcessStructField { field_index: usize },
38 ProcessListItem { item_index: usize },
39 ProcessMapEntry,
40 Finish,
41}
42
43struct StackItem<'a> {
45 peek: Peek<'a>,
46 format_depth: usize,
47 type_depth: usize,
48 state: StackState,
49}
50
51impl PrettyPrinter {
52 pub fn new() -> Self {
54 Self::default()
55 }
56
57 pub fn with_indent_size(mut self, size: usize) -> Self {
59 self.indent_size = size;
60 self
61 }
62
63 pub fn with_max_depth(mut self, depth: usize) -> Self {
65 self.max_depth = Some(depth);
66 self
67 }
68
69 pub fn with_color_generator(mut self, generator: ColorGenerator) -> Self {
71 self.color_generator = generator;
72 self
73 }
74
75 pub fn with_colors(mut self, use_colors: bool) -> Self {
77 self.use_colors = use_colors;
78 self
79 }
80
81 pub fn format<T: Facet>(&self, value: &T) -> String {
83 let peek = Peek::new(value);
84
85 let mut output = String::new();
86 self.format_peek_internal(peek, &mut output, 0, 0, &mut HashMap::new())
87 .expect("Formatting failed");
88
89 output
90 }
91
92 pub fn format_to<T: Facet>(&self, value: &T, f: &mut fmt::Formatter<'_>) -> fmt::Result {
94 let peek = Peek::new(value);
95 self.format_peek_internal(peek, f, 0, 0, &mut HashMap::new())
96 }
97
98 pub fn format_peek(&self, peek: Peek<'_>) -> String {
100 let mut output = String::new();
101 self.format_peek_internal(peek, &mut output, 0, 0, &mut HashMap::new())
102 .expect("Formatting failed");
103 output
104 }
105
106 pub(crate) fn format_peek_internal(
108 &self,
109 peek: Peek<'_>,
110 f: &mut impl Write,
111 format_depth: usize,
112 type_depth: usize,
113 visited: &mut HashMap<*const (), usize>,
114 ) -> fmt::Result {
115 let mut stack = VecDeque::new();
117
118 stack.push_back(StackItem {
120 peek,
121 format_depth,
122 type_depth,
123 state: StackState::Start,
124 });
125
126 while let Some(mut item) = stack.pop_back() {
128 match item.state {
129 StackState::Start => {
130 if let Some(max_depth) = self.max_depth {
132 if item.format_depth > max_depth {
133 self.write_punctuation(f, "[")?;
134 write!(f, "...")?;
135 continue;
136 }
137 }
138
139 let ptr = unsafe { item.peek.data().as_ptr() };
141
142 if let Some(&ptr_type_depth) = visited.get(&ptr) {
144 if item.type_depth > ptr_type_depth + 1 {
147 self.write_type_name(f, &item.peek)?;
148 self.write_punctuation(f, " { ")?;
149 self.write_comment(
150 f,
151 &format!(
152 "/* cycle detected at {:p} (first seen at type_depth {}) */",
153 ptr, ptr_type_depth
154 ),
155 )?;
156 self.write_punctuation(f, " }")?;
157 continue;
158 }
159 } else {
160 visited.insert(ptr, item.type_depth);
162 }
163
164 match item.peek {
166 Peek::Value(value) => {
167 self.format_value(value, f)?;
168 }
169 Peek::Struct(struct_) => {
170 let new_type_depth =
173 if core::ptr::eq(unsafe { struct_.data().as_ptr() }, ptr) {
174 item.type_depth } else {
176 item.type_depth + 1 };
178
179 self.write_type_name(f, &struct_)?;
181 self.write_punctuation(f, " {")?;
182
183 if struct_.field_count() == 0 {
184 self.write_punctuation(f, " }")?;
185 continue;
186 }
187
188 writeln!(f)?;
189
190 item.state = StackState::ProcessStructField { field_index: 0 };
192 item.format_depth += 1;
193 item.type_depth = new_type_depth;
194 stack.push_back(item);
195 }
196 Peek::List(list) => {
197 let new_type_depth =
200 if core::ptr::eq(unsafe { list.data().as_ptr() }, ptr) {
201 item.type_depth } else {
203 item.type_depth + 1 };
205
206 self.write_type_name(f, &list)?;
208 self.write_punctuation(f, " [")?;
209 writeln!(f)?;
210
211 item.state = StackState::ProcessListItem { item_index: 0 };
213 item.format_depth += 1;
214 item.type_depth = new_type_depth;
215 stack.push_back(item);
216 }
217 Peek::Map(map) => {
218 self.write_type_name(f, &map)?;
220 self.write_punctuation(f, " {")?;
221 writeln!(f)?;
222
223 item.state = StackState::ProcessMapEntry;
225 item.format_depth += 1;
226 item.type_depth = if core::ptr::eq(unsafe { map.data().as_ptr() }, ptr)
229 {
230 item.type_depth } else {
232 item.type_depth + 1 };
234 stack.push_back(item);
235 }
236 _ => {
237 writeln!(f, "unsupported peek variant: {:?}", item.peek)?;
238 }
239 }
240 }
241 StackState::ProcessStructField { field_index } => {
242 if let Peek::Struct(struct_) = item.peek {
243 let fields: Vec<_> = struct_.fields_with_metadata().collect();
244
245 if field_index >= fields.len() {
246 write!(
248 f,
249 "{:width$}{}",
250 "",
251 self.style_punctuation("}"),
252 width = (item.format_depth - 1) * self.indent_size
253 )?;
254 continue;
255 }
256
257 let (_, field_name, field_value, flags) = &fields[field_index];
258
259 write!(
261 f,
262 "{:width$}",
263 "",
264 width = item.format_depth * self.indent_size
265 )?;
266
267 self.write_field_name(f, field_name)?;
269 self.write_punctuation(f, ": ")?;
270
271 if flags.contains(facet_trait::FieldFlags::SENSITIVE) {
273 self.write_redacted(f, "[REDACTED]")?;
275 self.write_punctuation(f, ",")?;
276 writeln!(f)?;
277
278 item.state = StackState::ProcessStructField {
280 field_index: field_index + 1,
281 };
282 stack.push_back(item);
283 } else {
284 item.state = StackState::ProcessStructField {
287 field_index: field_index + 1,
288 };
289
290 let finish_item = StackItem {
291 peek: *field_value,
292 format_depth: item.format_depth,
293 type_depth: item.type_depth + 1,
294 state: StackState::Finish,
295 };
296 let start_item = StackItem {
297 peek: *field_value,
298 format_depth: item.format_depth,
299 type_depth: item.type_depth + 1,
300 state: StackState::Start,
301 };
302
303 stack.push_back(item);
304 stack.push_back(finish_item);
305 stack.push_back(start_item);
306 }
307 }
308 }
309 StackState::ProcessListItem { item_index } => {
310 if let Peek::List(list) = item.peek {
311 if item_index >= list.len() {
312 write!(
314 f,
315 "{:width$}",
316 "",
317 width = (item.format_depth - 1) * self.indent_size
318 )?;
319 self.write_punctuation(f, "]")?;
320 continue;
321 }
322
323 write!(
325 f,
326 "{:width$}",
327 "",
328 width = item.format_depth * self.indent_size
329 )?;
330
331 item.state = StackState::ProcessListItem {
333 item_index: item_index + 1,
334 };
335 let next_format_depth = item.format_depth;
336 let next_type_depth = item.type_depth + 1;
337 stack.push_back(item);
338
339 let list_item = list.iter().nth(item_index).unwrap();
341 stack.push_back(StackItem {
342 peek: list_item,
343 format_depth: next_format_depth,
344 type_depth: next_type_depth,
345 state: StackState::Finish,
346 });
347
348 stack.push_back(StackItem {
350 peek: list_item,
351 format_depth: next_format_depth,
352 type_depth: next_type_depth,
353 state: StackState::Start, });
355 }
356 }
357 StackState::ProcessMapEntry => {
358 if let Peek::Map(_) = item.peek {
359 write!(
363 f,
364 "{:width$}",
365 "",
366 width = item.format_depth * self.indent_size
367 )?;
368 write!(f, "{}", self.style_comment("/* Map contents */"))?;
369 writeln!(f)?;
370
371 write!(
373 f,
374 "{:width$}{}",
375 "",
376 self.style_punctuation("}"),
377 width = (item.format_depth - 1) * self.indent_size
378 )?;
379 }
380 }
381 StackState::Finish => {
382 self.write_punctuation(f, ",")?;
385 writeln!(f)?;
386 }
387 }
388 }
389
390 Ok(())
391 }
392
393 fn format_value(&self, value: facet_peek::PeekValue, f: &mut impl Write) -> fmt::Result {
395 let mut hasher = DefaultHasher::new();
397 value.shape().def.hash(&mut hasher);
398 let hash = hasher.finish();
399 let color = self.color_generator.generate_color(hash);
400
401 if self.use_colors {
403 color.write_fg(f)?;
404 }
405
406 struct DisplayWrapper<'a>(&'a facet_peek::PeekValue<'a>);
408
409 impl fmt::Display for DisplayWrapper<'_> {
410 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
411 if self.0.display(f).is_none() {
412 if self.0.debug(f).is_none() {
414 self.0.type_name(f, facet_trait::TypeNameOpts::infinite())?;
416 write!(f, "(⋯)")?;
417 }
418 }
419 Ok(())
420 }
421 }
422
423 write!(f, "{}", DisplayWrapper(&value))?;
424
425 if self.use_colors {
427 ansi::write_reset(f)?;
428 }
429
430 Ok(())
431 }
432
433 fn write_type_name<W: fmt::Write>(
435 &self,
436 f: &mut W,
437 peek: &facet_peek::PeekValue,
438 ) -> fmt::Result {
439 struct TypeNameWriter<'a, 'b: 'a>(&'b facet_peek::PeekValue<'a>);
440
441 impl core::fmt::Display for TypeNameWriter<'_, '_> {
442 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
443 self.0.type_name(f, facet_trait::TypeNameOpts::infinite())
444 }
445 }
446 let type_name = TypeNameWriter(peek);
447
448 if self.use_colors {
449 ansi::write_bold(f)?;
450 write!(f, "{}", type_name)?;
451 ansi::write_reset(f)
452 } else {
453 write!(f, "{}", type_name)
454 }
455 }
456
457 #[allow(dead_code)]
459 fn style_type_name(&self, peek: &facet_peek::PeekValue) -> String {
460 let mut result = String::new();
461 self.write_type_name(&mut result, peek).unwrap();
462 result
463 }
464
465 fn write_field_name<W: fmt::Write>(&self, f: &mut W, name: &str) -> fmt::Result {
467 if self.use_colors {
468 ansi::write_rgb(f, 114, 160, 193)?;
469 write!(f, "{}", name)?;
470 ansi::write_reset(f)
471 } else {
472 write!(f, "{}", name)
473 }
474 }
475
476 fn write_punctuation<W: fmt::Write>(&self, f: &mut W, text: &str) -> fmt::Result {
478 if self.use_colors {
479 ansi::write_dim(f)?;
480 write!(f, "{}", text)?;
481 ansi::write_reset(f)
482 } else {
483 write!(f, "{}", text)
484 }
485 }
486
487 fn style_punctuation(&self, text: &str) -> String {
489 let mut result = String::new();
490 self.write_punctuation(&mut result, text).unwrap();
491 result
492 }
493
494 fn write_comment<W: fmt::Write>(&self, f: &mut W, text: &str) -> fmt::Result {
496 if self.use_colors {
497 ansi::write_dim(f)?;
498 write!(f, "{}", text)?;
499 ansi::write_reset(f)
500 } else {
501 write!(f, "{}", text)
502 }
503 }
504
505 fn style_comment(&self, text: &str) -> String {
507 let mut result = String::new();
508 self.write_comment(&mut result, text).unwrap();
509 result
510 }
511
512 fn write_redacted<W: fmt::Write>(&self, f: &mut W, text: &str) -> fmt::Result {
514 if self.use_colors {
515 ansi::write_rgb(f, 224, 49, 49)?; ansi::write_bold(f)?;
517 write!(f, "{}", text)?;
518 ansi::write_reset(f)
519 } else {
520 write!(f, "{}", text)
521 }
522 }
523
524 #[allow(dead_code)]
526 fn style_redacted(&self, text: &str) -> String {
527 let mut result = String::new();
528 self.write_redacted(&mut result, text).unwrap();
529 result
530 }
531}
532
533#[cfg(test)]
534mod tests {
535 use super::*;
536
537 #[test]
539 fn test_pretty_printer_default() {
540 let printer = PrettyPrinter::default();
541 assert_eq!(printer.indent_size, 2);
542 assert_eq!(printer.max_depth, None);
543 assert!(printer.use_colors);
544 }
545
546 #[test]
547 fn test_pretty_printer_with_methods() {
548 let printer = PrettyPrinter::new()
549 .with_indent_size(4)
550 .with_max_depth(3)
551 .with_colors(false);
552
553 assert_eq!(printer.indent_size, 4);
554 assert_eq!(printer.max_depth, Some(3));
555 assert!(!printer.use_colors);
556 }
557}