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