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