facet_pretty/printer.rs
1//! Pretty printer implementation for Facet types
2
3use std::{
4 collections::{HashMap, VecDeque},
5 fmt::{self, Write},
6 hash::{DefaultHasher, Hash, Hasher},
7 str,
8};
9
10use facet_core::Facet;
11use facet_peek::Peek;
12
13use crate::{ansi, color::ColorGenerator};
14
15/// A formatter for pretty-printing Facet types
16pub struct PrettyPrinter {
17 indent_size: usize,
18 max_depth: Option<usize>,
19 color_generator: ColorGenerator,
20 use_colors: bool,
21 list_u8_as_bytes: bool,
22}
23
24impl Default for PrettyPrinter {
25 fn default() -> Self {
26 Self {
27 indent_size: 2,
28 max_depth: None,
29 color_generator: ColorGenerator::default(),
30 use_colors: true,
31 list_u8_as_bytes: true,
32 }
33 }
34}
35
36/// Stack state for iterative formatting
37enum StackState {
38 Start,
39 ProcessStructField { field_index: usize },
40 ProcessListItem { item_index: usize },
41 ProcessBytesItem { item_index: usize },
42 ProcessMapEntry,
43 Finish,
44}
45
46/// Stack item for iterative traversal
47struct StackItem<'a> {
48 peek: Peek<'a>,
49 format_depth: usize,
50 type_depth: usize,
51 state: StackState,
52}
53
54impl PrettyPrinter {
55 /// Create a new PrettyPrinter with default settings
56 pub fn new() -> Self {
57 Self::default()
58 }
59
60 /// Set the indentation size
61 pub fn with_indent_size(mut self, size: usize) -> Self {
62 self.indent_size = size;
63 self
64 }
65
66 /// Set the maximum depth for recursive printing
67 pub fn with_max_depth(mut self, depth: usize) -> Self {
68 self.max_depth = Some(depth);
69 self
70 }
71
72 /// Set the color generator
73 pub fn with_color_generator(mut self, generator: ColorGenerator) -> Self {
74 self.color_generator = generator;
75 self
76 }
77
78 /// Enable or disable colors
79 pub fn with_colors(mut self, use_colors: bool) -> Self {
80 self.use_colors = use_colors;
81 self
82 }
83
84 /// Format a value to a string
85 pub fn format<T: Facet>(&self, value: &T) -> String {
86 let peek = Peek::new(value);
87
88 let mut output = String::new();
89 self.format_peek_internal(peek, &mut output, 0, 0, &mut HashMap::new())
90 .expect("Formatting failed");
91
92 output
93 }
94
95 /// Format a value to a formatter
96 pub fn format_to<T: Facet>(&self, value: &T, f: &mut fmt::Formatter<'_>) -> fmt::Result {
97 let peek = Peek::new(value);
98 self.format_peek_internal(peek, f, 0, 0, &mut HashMap::new())
99 }
100
101 /// Format a Peek value to a string
102 pub fn format_peek(&self, peek: Peek<'_>) -> String {
103 let mut output = String::new();
104 self.format_peek_internal(peek, &mut output, 0, 0, &mut HashMap::new())
105 .expect("Formatting failed");
106 output
107 }
108
109 /// Internal method to format a Peek value
110 pub(crate) fn format_peek_internal(
111 &self,
112 peek: Peek<'_>,
113 f: &mut impl Write,
114 format_depth: usize,
115 type_depth: usize,
116 visited: &mut HashMap<*const (), usize>,
117 ) -> fmt::Result {
118 // Create a queue for our stack items
119 let mut stack = VecDeque::new();
120
121 // Push the initial item
122 stack.push_back(StackItem {
123 peek,
124 format_depth,
125 type_depth,
126 state: StackState::Start,
127 });
128
129 // Process items until the stack is empty
130 while let Some(mut item) = stack.pop_back() {
131 match item.state {
132 StackState::Start => {
133 // Check if we've reached the maximum depth
134 if let Some(max_depth) = self.max_depth {
135 if item.format_depth > max_depth {
136 self.write_punctuation(f, "[")?;
137 write!(f, "...")?;
138 continue;
139 }
140 }
141
142 // Get the data pointer for cycle detection
143 let ptr = unsafe { item.peek.data().as_ptr() };
144
145 // Check for cycles - if we've seen this pointer before at a different type_depth
146 if let Some(&ptr_type_depth) = visited.get(&ptr) {
147 // If the current type_depth is significantly deeper than when we first saw this pointer,
148 // we have a true cycle, not just a transparent wrapper
149 if item.type_depth > ptr_type_depth + 1 {
150 self.write_type_name(f, &item.peek)?;
151 self.write_punctuation(f, " { ")?;
152 self.write_comment(
153 f,
154 &format!(
155 "/* cycle detected at {:p} (first seen at type_depth {}) */",
156 ptr, ptr_type_depth
157 ),
158 )?;
159 self.write_punctuation(f, " }")?;
160 continue;
161 }
162 } else {
163 // First time seeing this pointer, record its type_depth
164 visited.insert(ptr, item.type_depth);
165 }
166
167 // Process based on the peek variant
168 match item.peek {
169 Peek::Value(value) => {
170 self.format_value(value, f)?;
171 }
172 Peek::Struct(struct_) => {
173 // When recursing into a struct, always increment format_depth
174 // Only increment type_depth if we're moving to a different address
175 let new_type_depth =
176 if core::ptr::eq(unsafe { struct_.data().as_ptr() }, ptr) {
177 item.type_depth // Same pointer, don't increment type_depth
178 } else {
179 item.type_depth + 1 // Different pointer, increment type_depth
180 };
181
182 // Print the struct name
183 self.write_type_name(f, &struct_)?;
184 self.write_punctuation(f, " {")?;
185
186 if struct_.field_count() == 0 {
187 self.write_punctuation(f, " }")?;
188 continue;
189 }
190
191 writeln!(f)?;
192
193 // Push back the item with the next state to continue processing fields
194 item.state = StackState::ProcessStructField { field_index: 0 };
195 item.format_depth += 1;
196 item.type_depth = new_type_depth;
197 stack.push_back(item);
198 }
199 Peek::List(list) => {
200 // When recursing into a list, always increment format_depth
201 // Only increment type_depth if we're moving to a different address
202 let new_type_depth =
203 if core::ptr::eq(unsafe { list.data().as_ptr() }, ptr) {
204 item.type_depth // Same pointer, don't increment type_depth
205 } else {
206 item.type_depth + 1 // Different pointer, increment type_depth
207 };
208
209 // Print the list name
210 self.write_type_name(f, &list)?;
211
212 if list.def().t.is_type::<u8>() && self.list_u8_as_bytes {
213 // Push back the item with the next state to continue processing list items
214 item.state = StackState::ProcessBytesItem { item_index: 0 };
215 writeln!(f)?;
216 write!(f, " ")?;
217
218 // TODO: write all the bytes here instead?
219 } else {
220 // Push back the item with the next state to continue processing list items
221 item.state = StackState::ProcessListItem { item_index: 0 };
222 self.write_punctuation(f, " [")?;
223 writeln!(f)?;
224 }
225
226 item.format_depth += 1;
227 item.type_depth = new_type_depth;
228 stack.push_back(item);
229 }
230 Peek::Map(map) => {
231 // Print the map name
232 self.write_type_name(f, &map)?;
233 self.write_punctuation(f, " {")?;
234 writeln!(f)?;
235
236 // Push back the item with the next state to continue processing map
237 item.state = StackState::ProcessMapEntry;
238 item.format_depth += 1;
239 // When recursing into a map, always increment format_depth
240 // Only increment type_depth if we're moving to a different address
241 item.type_depth = if core::ptr::eq(unsafe { map.data().as_ptr() }, ptr)
242 {
243 item.type_depth // Same pointer, don't increment type_depth
244 } else {
245 item.type_depth + 1 // Different pointer, increment type_depth
246 };
247 stack.push_back(item);
248 }
249 Peek::Enum(enum_) => {
250 // When recursing into an enum, increment format_depth
251 // Only increment type_depth if we're moving to a different address
252 let _new_type_depth =
253 if core::ptr::eq(unsafe { enum_.data().as_ptr() }, ptr) {
254 item.type_depth // Same pointer, don't increment type_depth
255 } else {
256 item.type_depth + 1 // Different pointer, increment type_depth
257 };
258
259 // Print the enum name
260 self.write_type_name(f, &enum_)?;
261 self.write_punctuation(f, "::")?;
262
263 // Get the active variant name
264 let variant_name = enum_.variant_name_active();
265
266 // Apply color for variant name
267 if self.use_colors {
268 ansi::write_bold(f)?;
269 write!(f, "{}", variant_name)?;
270 ansi::write_reset(f)?;
271 } else {
272 write!(f, "{}", variant_name)?;
273 }
274
275 // Process the variant fields based on the variant kind
276 match enum_.variant_kind_active() {
277 facet_core::VariantKind::Unit => {
278 // Unit variant has no fields, nothing more to print
279 }
280 facet_core::VariantKind::Tuple { .. } => {
281 // Tuple variant, print the fields like a tuple
282 self.write_punctuation(f, "(")?;
283
284 // Check if there are any fields to print
285 let has_fields = enum_.fields().count() > 0;
286
287 if !has_fields {
288 self.write_punctuation(f, ")")?;
289 continue;
290 }
291
292 writeln!(f)?;
293
294 // Push back item to process fields
295 item.state = StackState::ProcessStructField { field_index: 0 };
296 item.format_depth += 1;
297 stack.push_back(item);
298 }
299 facet_core::VariantKind::Struct { .. } => {
300 // Struct variant, print the fields like a struct
301 self.write_punctuation(f, " {")?;
302
303 // Check if there are any fields to print
304 let has_fields = enum_.fields().count() > 0;
305
306 if !has_fields {
307 self.write_punctuation(f, " }")?;
308 continue;
309 }
310
311 writeln!(f)?;
312
313 // Push back item to process fields
314 item.state = StackState::ProcessStructField { field_index: 0 };
315 item.format_depth += 1;
316 stack.push_back(item);
317 }
318 _ => {
319 // Other variant kinds that might be added in the future
320 write!(f, " /* unsupported variant kind */")?;
321 }
322 }
323 }
324 _ => {
325 write!(f, "unsupported peek variant: {:?}", item.peek)?;
326 }
327 }
328 }
329 StackState::ProcessStructField { field_index } => {
330 // Handle both struct and enum fields
331 if let Peek::Struct(struct_) = item.peek {
332 let fields: Vec<_> = struct_.fields_with_metadata().collect();
333
334 if field_index >= fields.len() {
335 // All fields processed, write closing brace
336 write!(
337 f,
338 "{:width$}{}",
339 "",
340 self.style_punctuation("}"),
341 width = (item.format_depth - 1) * self.indent_size
342 )?;
343 continue;
344 }
345
346 let (_, field_name, field_value, flags) = &fields[field_index];
347
348 // Indent
349 write!(
350 f,
351 "{:width$}",
352 "",
353 width = item.format_depth * self.indent_size
354 )?;
355
356 // Field name
357 self.write_field_name(f, field_name)?;
358 self.write_punctuation(f, ": ")?;
359
360 // Check if field is sensitive
361 if flags.contains(facet_core::FieldFlags::SENSITIVE) {
362 // Field value is sensitive, use write_redacted
363 self.write_redacted(f, "[REDACTED]")?;
364 self.write_punctuation(f, ",")?;
365 writeln!(f)?;
366
367 item.state = StackState::ProcessStructField {
368 field_index: field_index + 1,
369 };
370 stack.push_back(item);
371 } else {
372 // Field value is not sensitive, format normally
373 // Push back current item to continue after formatting field value
374 item.state = StackState::ProcessStructField {
375 field_index: field_index + 1,
376 };
377
378 let finish_item = StackItem {
379 peek: *field_value,
380 format_depth: item.format_depth,
381 type_depth: item.type_depth + 1,
382 state: StackState::Finish,
383 };
384 let start_item = StackItem {
385 peek: *field_value,
386 format_depth: item.format_depth,
387 type_depth: item.type_depth + 1,
388 state: StackState::Start,
389 };
390
391 stack.push_back(item);
392 stack.push_back(finish_item);
393 stack.push_back(start_item);
394 }
395 } else if let Peek::Enum(enum_val) = item.peek {
396 // Since PeekEnum implements Copy, we can use it directly
397
398 // Get all fields directly
399 let fields: Vec<_> = enum_val.fields().collect();
400
401 // Check if we're done processing fields
402 if field_index >= fields.len() {
403 // Determine variant kind to use the right closing delimiter
404 match enum_val.variant_kind_active() {
405 facet_core::VariantKind::Tuple { .. } => {
406 // Close tuple variant with )
407 write!(
408 f,
409 "{:width$}{}",
410 "",
411 self.style_punctuation(")"),
412 width = (item.format_depth - 1) * self.indent_size
413 )?;
414 }
415 facet_core::VariantKind::Struct { .. } => {
416 // Close struct variant with }
417 write!(
418 f,
419 "{:width$}{}",
420 "",
421 self.style_punctuation("}"),
422 width = (item.format_depth - 1) * self.indent_size
423 )?;
424 }
425 _ => {}
426 }
427 continue;
428 }
429
430 // Get the current field
431 let (field_name, field_peek) = fields[field_index];
432
433 // Add indentation
434 write!(
435 f,
436 "{:width$}",
437 "",
438 width = item.format_depth * self.indent_size
439 )?;
440
441 // For struct variants, print field name
442 if let facet_core::VariantKind::Struct { .. } =
443 enum_val.variant_kind_active()
444 {
445 self.write_field_name(f, field_name)?;
446 self.write_punctuation(f, ": ")?;
447 }
448
449 // Set up to process the next field after this one
450 item.state = StackState::ProcessStructField {
451 field_index: field_index + 1,
452 };
453
454 // Create finish and start items for processing the field value
455 let finish_item = StackItem {
456 peek: field_peek, // field_peek is already a Peek which is Copy
457 format_depth: item.format_depth,
458 type_depth: item.type_depth + 1,
459 state: StackState::Finish,
460 };
461 let start_item = StackItem {
462 peek: field_peek, // field_peek is already a Peek which is Copy
463 format_depth: item.format_depth,
464 type_depth: item.type_depth + 1,
465 state: StackState::Start,
466 };
467
468 // Push items to stack in the right order
469 stack.push_back(item);
470 stack.push_back(finish_item);
471 stack.push_back(start_item);
472 }
473 }
474 StackState::ProcessListItem { item_index } => {
475 if let Peek::List(list) = item.peek {
476 if item_index >= list.len() {
477 // All items processed, write closing bracket
478 write!(
479 f,
480 "{:width$}",
481 "",
482 width = (item.format_depth - 1) * self.indent_size
483 )?;
484 self.write_punctuation(f, "]")?;
485 continue;
486 }
487
488 // Indent
489 write!(
490 f,
491 "{:width$}",
492 "",
493 width = item.format_depth * self.indent_size
494 )?;
495
496 // Push back current item to continue after formatting list item
497 item.state = StackState::ProcessListItem {
498 item_index: item_index + 1,
499 };
500 let next_format_depth = item.format_depth;
501 let next_type_depth = item.type_depth + 1;
502 stack.push_back(item);
503
504 // Push list item to format first
505 let list_item = list.iter().nth(item_index).unwrap();
506 stack.push_back(StackItem {
507 peek: list_item,
508 format_depth: next_format_depth,
509 type_depth: next_type_depth,
510 state: StackState::Finish,
511 });
512
513 // When we push a list item to format, we need to process it from the beginning
514 stack.push_back(StackItem {
515 peek: list_item,
516 format_depth: next_format_depth,
517 type_depth: next_type_depth,
518 state: StackState::Start, // Use Start state to properly process the item
519 });
520 }
521 }
522 StackState::ProcessBytesItem { item_index } => {
523 if let Peek::List(list) = item.peek {
524 if item_index >= list.len() {
525 // All items processed, write closing bracket
526 write!(
527 f,
528 "{:width$}",
529 "",
530 width = (item.format_depth - 1) * self.indent_size
531 )?;
532 continue;
533 }
534
535 // On the first byte, write the opening byte sequence indicator
536 if item_index == 0 {
537 write!(f, " ")?;
538 }
539
540 // Only display 16 bytes per line
541 if item_index > 0 && item_index % 16 == 0 {
542 writeln!(f)?;
543 write!(
544 f,
545 "{:width$}",
546 "",
547 width = item.format_depth * self.indent_size
548 )?;
549 } else if item_index > 0 {
550 write!(f, " ")?;
551 }
552
553 // Get the byte
554 if let Some(Peek::Value(value)) = list.iter().nth(item_index) {
555 let byte = unsafe { value.data().read::<u8>() };
556
557 // Generate a color for this byte based on its value
558 let mut hasher = DefaultHasher::new();
559 byte.hash(&mut hasher);
560 let hash = hasher.finish();
561 let color = self.color_generator.generate_color(hash);
562
563 // Apply color if needed
564 if self.use_colors {
565 color.write_fg(f)?;
566 }
567
568 // Display the byte in hex format
569 write!(f, "{:02x}", byte)?;
570
571 // Reset color if needed
572 if self.use_colors {
573 ansi::write_reset(f)?;
574 }
575 } else {
576 unreachable!()
577 }
578
579 // Push back current item to continue after formatting byte
580 item.state = StackState::ProcessBytesItem {
581 item_index: item_index + 1,
582 };
583 stack.push_back(item);
584 }
585 }
586 StackState::ProcessMapEntry => {
587 if let Peek::Map(_) = item.peek {
588 // TODO: Implement proper map iteration when available in facet_peek
589
590 // Indent
591 write!(
592 f,
593 "{:width$}",
594 "",
595 width = item.format_depth * self.indent_size
596 )?;
597 write!(f, "{}", self.style_comment("/* Map contents */"))?;
598 writeln!(f)?;
599
600 // Closing brace with proper indentation
601 write!(
602 f,
603 "{:width$}{}",
604 "",
605 self.style_punctuation("}"),
606 width = (item.format_depth - 1) * self.indent_size
607 )?;
608 }
609 }
610 StackState::Finish => {
611 // This state is reached after processing a field or list item
612 // Add comma and newline for struct fields and list items
613 self.write_punctuation(f, ",")?;
614 writeln!(f)?;
615 }
616 }
617 }
618
619 Ok(())
620 }
621
622 /// Format a scalar value
623 fn format_value(&self, value: facet_peek::PeekValue, f: &mut impl Write) -> fmt::Result {
624 // Generate a color for this shape
625 let mut hasher = DefaultHasher::new();
626 value.shape().def.hash(&mut hasher);
627 let hash = hasher.finish();
628 let color = self.color_generator.generate_color(hash);
629
630 // Apply color if needed
631 if self.use_colors {
632 color.write_fg(f)?;
633 }
634
635 // Display the value
636 struct DisplayWrapper<'a>(&'a facet_peek::PeekValue<'a>);
637
638 impl fmt::Display for DisplayWrapper<'_> {
639 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
640 if self.0.display(f).is_none() {
641 // If the value doesn't implement Display, use Debug
642 if self.0.debug(f).is_none() {
643 // If the value doesn't implement Debug either, just show the type name
644 self.0.type_name(f, facet_core::TypeNameOpts::infinite())?;
645 write!(f, "(⋯)")?;
646 }
647 }
648 Ok(())
649 }
650 }
651
652 write!(f, "{}", DisplayWrapper(&value))?;
653
654 // Reset color if needed
655 if self.use_colors {
656 ansi::write_reset(f)?;
657 }
658
659 Ok(())
660 }
661
662 /// Write styled type name to formatter
663 fn write_type_name<W: fmt::Write>(
664 &self,
665 f: &mut W,
666 peek: &facet_peek::PeekValue,
667 ) -> fmt::Result {
668 struct TypeNameWriter<'a, 'b: 'a>(&'b facet_peek::PeekValue<'a>);
669
670 impl core::fmt::Display for TypeNameWriter<'_, '_> {
671 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
672 self.0.type_name(f, facet_core::TypeNameOpts::infinite())
673 }
674 }
675 let type_name = TypeNameWriter(peek);
676
677 if self.use_colors {
678 ansi::write_bold(f)?;
679 write!(f, "{}", type_name)?;
680 ansi::write_reset(f)
681 } else {
682 write!(f, "{}", type_name)
683 }
684 }
685
686 /// Style a type name and return it as a string
687 #[allow(dead_code)]
688 fn style_type_name(&self, peek: &facet_peek::PeekValue) -> String {
689 let mut result = String::new();
690 self.write_type_name(&mut result, peek).unwrap();
691 result
692 }
693
694 /// Write styled field name to formatter
695 fn write_field_name<W: fmt::Write>(&self, f: &mut W, name: &str) -> fmt::Result {
696 if self.use_colors {
697 ansi::write_rgb(f, 114, 160, 193)?;
698 write!(f, "{}", name)?;
699 ansi::write_reset(f)
700 } else {
701 write!(f, "{}", name)
702 }
703 }
704
705 /// Write styled punctuation to formatter
706 fn write_punctuation<W: fmt::Write>(&self, f: &mut W, text: &str) -> fmt::Result {
707 if self.use_colors {
708 ansi::write_dim(f)?;
709 write!(f, "{}", text)?;
710 ansi::write_reset(f)
711 } else {
712 write!(f, "{}", text)
713 }
714 }
715
716 /// Style punctuation and return it as a string
717 fn style_punctuation(&self, text: &str) -> String {
718 let mut result = String::new();
719 self.write_punctuation(&mut result, text).unwrap();
720 result
721 }
722
723 /// Write styled comment to formatter
724 fn write_comment<W: fmt::Write>(&self, f: &mut W, text: &str) -> fmt::Result {
725 if self.use_colors {
726 ansi::write_dim(f)?;
727 write!(f, "{}", text)?;
728 ansi::write_reset(f)
729 } else {
730 write!(f, "{}", text)
731 }
732 }
733
734 /// Style a comment and return it as a string
735 fn style_comment(&self, text: &str) -> String {
736 let mut result = String::new();
737 self.write_comment(&mut result, text).unwrap();
738 result
739 }
740
741 /// Write styled redacted value to formatter
742 fn write_redacted<W: fmt::Write>(&self, f: &mut W, text: &str) -> fmt::Result {
743 if self.use_colors {
744 ansi::write_rgb(f, 224, 49, 49)?; // Use bright red for redacted values
745 ansi::write_bold(f)?;
746 write!(f, "{}", text)?;
747 ansi::write_reset(f)
748 } else {
749 write!(f, "{}", text)
750 }
751 }
752
753 /// Style a redacted value and return it as a string
754 #[allow(dead_code)]
755 fn style_redacted(&self, text: &str) -> String {
756 let mut result = String::new();
757 self.write_redacted(&mut result, text).unwrap();
758 result
759 }
760}
761
762#[cfg(test)]
763mod tests {
764 use super::*;
765
766 // Basic tests for the PrettyPrinter
767 #[test]
768 fn test_pretty_printer_default() {
769 let printer = PrettyPrinter::default();
770 assert_eq!(printer.indent_size, 2);
771 assert_eq!(printer.max_depth, None);
772 assert!(printer.use_colors);
773 }
774
775 #[test]
776 fn test_pretty_printer_with_methods() {
777 let printer = PrettyPrinter::new()
778 .with_indent_size(4)
779 .with_max_depth(3)
780 .with_colors(false);
781
782 assert_eq!(printer.indent_size, 4);
783 assert_eq!(printer.max_depth, Some(3));
784 assert!(!printer.use_colors);
785 }
786}