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 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 facet_core::VariantKind::Unit => {
343 // Unit variant has no fields, nothing more to print
344 }
345 facet_core::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 facet_core::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 // Define consistent indentation
414 let field_indent = " "; // Use 2 spaces for all fields
415
416 // Field doc comment
417 if !field.doc.is_empty() {
418 // Only add new line if not the first field
419 if field_index > 0 {
420 writeln!(f)?;
421 }
422 // Hard-code consistent indentation for doc comments
423 for line in field.doc {
424 // Use exactly the same indentation as fields (2 spaces)
425 write!(f, "{}", field_indent)?;
426 self.write_comment(f, &format!("///{}", line))?;
427 writeln!(f)?;
428 }
429 // Rewrite indentation after doc comments with consistent spacing
430 write!(f, "{}", field_indent)?;
431 }
432
433 // Field name
434 self.write_field_name(f, field_name)?;
435 self.write_punctuation(f, ": ")?;
436
437 // Check if field is sensitive
438 if field.flags.contains(facet_core::FieldFlags::SENSITIVE) {
439 // Field value is sensitive, use write_redacted
440 self.write_redacted(f, "[REDACTED]")?;
441 self.write_punctuation(f, ",")?;
442 writeln!(f)?;
443
444 item.state = StackState::ProcessStructField {
445 field_index: field_index + 1,
446 };
447 stack.push_back(item);
448 } else {
449 // Field value is not sensitive, format normally
450 // Push back current item to continue after formatting field value
451 item.state = StackState::ProcessStructField {
452 field_index: field_index + 1,
453 };
454
455 let finish_item = StackItem {
456 peek: *field_value,
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_value,
463 format_depth: item.format_depth,
464 type_depth: item.type_depth + 1,
465 state: StackState::Start,
466 };
467
468 stack.push_back(item);
469 stack.push_back(finish_item);
470 stack.push_back(start_item);
471 }
472 } else if let Peek::Enum(enum_val) = item.peek {
473 // Since PeekEnum implements Copy, we can use it directly
474
475 // Get all fields with their metadata
476 let fields: Vec<_> = enum_val.fields_with_metadata().collect();
477
478 // Check if we're done processing fields
479 if field_index >= fields.len() {
480 // Determine variant kind to use the right closing delimiter
481 match enum_val.variant_kind_active() {
482 facet_core::VariantKind::Tuple { .. } => {
483 // Close tuple variant with )
484 write!(
485 f,
486 "{:width$}{}",
487 "",
488 self.style_punctuation(")"),
489 width = (item.format_depth - 1) * self.indent_size
490 )?;
491 }
492 facet_core::VariantKind::Struct { .. } => {
493 // Close struct variant with }
494 write!(
495 f,
496 "{:width$}{}",
497 "",
498 self.style_punctuation("}"),
499 width = (item.format_depth - 1) * self.indent_size
500 )?;
501 }
502 _ => {}
503 }
504 continue;
505 }
506
507 // Get the current field with metadata
508 let (_, field_name, field_peek, field) = fields[field_index];
509
510 // Define consistent indentation
511 let field_indent = " "; // Use 2 spaces for all fields
512
513 // Add field doc comments if available
514 if !field.doc.is_empty() {
515 // Only add new line if not the first field
516 if field_index > 0 {
517 writeln!(f)?;
518 }
519 for line in field.doc {
520 // Hard-code consistent indentation (2 spaces)
521 write!(f, " ")?;
522 self.write_comment(f, &format!("///{}", line))?;
523 writeln!(f)?;
524 }
525 // Rewrite indentation after doc comments
526 write!(f, "{}", field_indent)?;
527 }
528
529 // For struct variants, print field name
530 if let facet_core::VariantKind::Struct { .. } =
531 enum_val.variant_kind_active()
532 {
533 self.write_field_name(f, field_name)?;
534 self.write_punctuation(f, ": ")?;
535 }
536
537 // Set up to process the next field after this one
538 item.state = StackState::ProcessStructField {
539 field_index: field_index + 1,
540 };
541
542 // Create finish and start items for processing the field value
543 let finish_item = StackItem {
544 peek: field_peek, // field_peek is already a Peek which is Copy
545 format_depth: item.format_depth,
546 type_depth: item.type_depth + 1,
547 state: StackState::Finish,
548 };
549 let start_item = StackItem {
550 peek: field_peek, // field_peek is already a Peek which is Copy
551 format_depth: item.format_depth,
552 type_depth: item.type_depth + 1,
553 state: StackState::Start,
554 };
555
556 // Push items to stack in the right order
557 stack.push_back(item);
558 stack.push_back(finish_item);
559 stack.push_back(start_item);
560 }
561 }
562 StackState::ProcessListItem { item_index } => {
563 if let Peek::List(list) = item.peek {
564 if item_index >= list.len() {
565 // All items processed, write closing bracket
566 write!(
567 f,
568 "{:width$}",
569 "",
570 width = (item.format_depth - 1) * self.indent_size
571 )?;
572 self.write_punctuation(f, "]")?;
573 continue;
574 }
575
576 // Indent
577 write!(
578 f,
579 "{:width$}",
580 "",
581 width = item.format_depth * self.indent_size
582 )?;
583
584 // Push back current item to continue after formatting list item
585 item.state = StackState::ProcessListItem {
586 item_index: item_index + 1,
587 };
588 let next_format_depth = item.format_depth;
589 let next_type_depth = item.type_depth + 1;
590 stack.push_back(item);
591
592 // Push list item to format first
593 let list_item = list.iter().nth(item_index).unwrap();
594 stack.push_back(StackItem {
595 peek: list_item,
596 format_depth: next_format_depth,
597 type_depth: next_type_depth,
598 state: StackState::Finish,
599 });
600
601 // When we push a list item to format, we need to process it from the beginning
602 stack.push_back(StackItem {
603 peek: list_item,
604 format_depth: next_format_depth,
605 type_depth: next_type_depth,
606 state: StackState::Start, // Use Start state to properly process the item
607 });
608 }
609 }
610 StackState::ProcessBytesItem { item_index } => {
611 if let Peek::List(list) = item.peek {
612 if item_index >= list.len() {
613 // All items processed, write closing bracket
614 write!(
615 f,
616 "{:width$}",
617 "",
618 width = (item.format_depth - 1) * self.indent_size
619 )?;
620 continue;
621 }
622
623 // On the first byte, write the opening byte sequence indicator
624 if item_index == 0 {
625 write!(f, " ")?;
626 }
627
628 // Only display 16 bytes per line
629 if item_index > 0 && item_index % 16 == 0 {
630 writeln!(f)?;
631 write!(
632 f,
633 "{:width$}",
634 "",
635 width = item.format_depth * self.indent_size
636 )?;
637 } else if item_index > 0 {
638 write!(f, " ")?;
639 }
640
641 // Get the byte
642 if let Some(Peek::Value(value)) = list.iter().nth(item_index) {
643 let byte = unsafe { value.data().read::<u8>() };
644
645 // Generate a color for this byte based on its value
646 let mut hasher = DefaultHasher::new();
647 byte.hash(&mut hasher);
648 let hash = hasher.finish();
649 let color = self.color_generator.generate_color(hash);
650
651 // Apply color if needed
652 if self.use_colors {
653 color.write_fg(f)?;
654 }
655
656 // Display the byte in hex format
657 write!(f, "{:02x}", byte)?;
658
659 // Reset color if needed
660 if self.use_colors {
661 ansi::write_reset(f)?;
662 }
663 } else {
664 unreachable!()
665 }
666
667 // Push back current item to continue after formatting byte
668 item.state = StackState::ProcessBytesItem {
669 item_index: item_index + 1,
670 };
671 stack.push_back(item);
672 }
673 }
674 StackState::ProcessMapEntry => {
675 if let Peek::Map(_) = item.peek {
676 // TODO: Implement proper map iteration when available in facet_peek
677
678 // Indent
679 write!(
680 f,
681 "{:width$}",
682 "",
683 width = item.format_depth * self.indent_size
684 )?;
685 write!(f, "{}", self.style_comment("/* Map contents */"))?;
686 writeln!(f)?;
687
688 // Closing brace with proper indentation
689 write!(
690 f,
691 "{:width$}{}",
692 "",
693 self.style_punctuation("}"),
694 width = (item.format_depth - 1) * self.indent_size
695 )?;
696 }
697 }
698 StackState::Finish => {
699 // Add comma and newline for struct fields and list items
700 self.write_punctuation(f, ",")?;
701 writeln!(f)?;
702 }
703 StackState::OptionFinish => {
704 // Just close the Option::Some parenthesis, with no comma
705 self.write_punctuation(f, ")")?;
706 }
707 }
708 }
709
710 Ok(())
711 }
712
713 /// Format a scalar value
714 fn format_value(&self, value: facet_peek::PeekValue, f: &mut impl Write) -> fmt::Result {
715 // Generate a color for this shape
716 let mut hasher = DefaultHasher::new();
717 value.shape().def.hash(&mut hasher);
718 let hash = hasher.finish();
719 let color = self.color_generator.generate_color(hash);
720
721 // Apply color if needed
722 if self.use_colors {
723 color.write_fg(f)?;
724 }
725
726 // Display the value
727 struct DisplayWrapper<'a>(&'a facet_peek::PeekValue<'a>);
728
729 impl fmt::Display for DisplayWrapper<'_> {
730 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
731 if self.0.display(f).is_none() {
732 // If the value doesn't implement Display, use Debug
733 if self.0.debug(f).is_none() {
734 // If the value doesn't implement Debug either, just show the type name
735 self.0.type_name(f, facet_core::TypeNameOpts::infinite())?;
736 write!(f, "(⋯)")?;
737 }
738 }
739 Ok(())
740 }
741 }
742
743 write!(f, "{}", DisplayWrapper(&value))?;
744
745 // Reset color if needed
746 if self.use_colors {
747 ansi::write_reset(f)?;
748 }
749
750 Ok(())
751 }
752
753 /// Write styled type name to formatter
754 fn write_type_name<W: fmt::Write>(
755 &self,
756 f: &mut W,
757 peek: &facet_peek::PeekValue,
758 ) -> fmt::Result {
759 struct TypeNameWriter<'a, 'b: 'a>(&'b facet_peek::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, facet_core::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: &facet_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}