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};
10use yansi::Paint as _;
11
12use facet_core::{Def, Facet, FieldFlags, StructKind, TypeNameOpts};
13use facet_reflect::{Peek, ValueId};
14
15use crate::color::ColorGenerator;
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: std::env::var_os("NO_COLOR").is_none(),
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, 'facet_lifetime> {
51    value: Peek<'a, 'facet_lifetime>,
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<'a, T: Facet<'a>>(&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<'a, T: Facet<'a>>(
100        &self,
101        value: &T,
102        f: &mut fmt::Formatter<'_>,
103    ) -> fmt::Result {
104        let value = Peek::new(value);
105        self.format_peek_internal(value, f, &mut HashMap::new())
106    }
107
108    /// Format a value to a string
109    pub fn format_peek(&self, value: Peek<'_, '_>) -> String {
110        let mut output = String::new();
111        self.format_peek_internal(value, &mut output, &mut HashMap::new())
112            .expect("Formatting failed");
113        output
114    }
115
116    /// Internal method to format a Peek value
117    pub(crate) fn format_peek_internal(
118        &self,
119        initial_value: Peek<'_, '_>,
120        f: &mut impl Write,
121        visited: &mut HashMap<ValueId, usize>,
122    ) -> fmt::Result {
123        // Create a queue for our stack items
124        let mut stack = VecDeque::new();
125
126        // Push the initial item
127        stack.push_back(StackItem {
128            value: initial_value,
129            format_depth: 0,
130            type_depth: 0,
131            state: StackState::Start,
132        });
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                        Def::FunctionPointer(_) => {
370                            self.write_type_name(f, &item.value)?;
371                        }
372                        _ => {
373                            write!(f, "unsupported peek variant: {:?}", item.value)?;
374                        }
375                    }
376                }
377                StackState::ProcessStructField { field_index } => {
378                    // Handle both struct and enum fields
379                    if let Def::Struct(struct_) = item.value.shape().def {
380                        let peek_struct = item.value.into_struct().unwrap();
381                        if field_index >= struct_.fields.len() {
382                            // All fields processed, write closing brace
383                            write!(
384                                f,
385                                "{:width$}{}",
386                                "",
387                                self.style_punctuation("}"),
388                                width = (item.format_depth - 1) * self.indent_size
389                            )?;
390                            continue;
391                        }
392
393                        let field = struct_.fields[field_index];
394                        let field_value = peek_struct.field(field_index).unwrap();
395
396                        // Field doc comment
397                        if !field.doc.is_empty() {
398                            // Only add new line if not the first field
399                            if field_index > 0 {
400                                writeln!(f)?;
401                            }
402                            // Hard-code consistent indentation for doc comments
403                            for line in field.doc {
404                                // Use exactly the same indentation as fields (2 spaces)
405                                write!(
406                                    f,
407                                    "{:width$}",
408                                    "",
409                                    width = item.format_depth * self.indent_size
410                                )?;
411                                self.write_comment(f, &format!("///{}", line))?;
412                                writeln!(f)?;
413                            }
414                        }
415
416                        // Field name
417                        write!(
418                            f,
419                            "{:width$}",
420                            "",
421                            width = item.format_depth * self.indent_size
422                        )?;
423                        self.write_field_name(f, field.name)?;
424                        self.write_punctuation(f, ": ")?;
425
426                        // Check if field is sensitive
427                        if field.flags.contains(FieldFlags::SENSITIVE) {
428                            // Field value is sensitive, use write_redacted
429                            self.write_redacted(f, "[REDACTED]")?;
430                            self.write_punctuation(f, ",")?;
431                            writeln!(f)?;
432
433                            item.state = StackState::ProcessStructField {
434                                field_index: field_index + 1,
435                            };
436                            stack.push_back(item);
437                        } else {
438                            // Field value is not sensitive, format normally
439                            // Push back current item to continue after formatting field value
440                            item.state = StackState::ProcessStructField {
441                                field_index: field_index + 1,
442                            };
443
444                            let finish_item = StackItem {
445                                value: field_value,
446                                format_depth: item.format_depth,
447                                type_depth: item.type_depth + 1,
448                                state: StackState::Finish,
449                            };
450                            let start_item = StackItem {
451                                value: field_value,
452                                format_depth: item.format_depth,
453                                type_depth: item.type_depth + 1,
454                                state: StackState::Start,
455                            };
456
457                            stack.push_back(item);
458                            stack.push_back(finish_item);
459                            stack.push_back(start_item);
460                        }
461                    } else if let Def::Enum(_def) = item.value.shape().def {
462                        let enum_val = item.value.into_enum().unwrap();
463
464                        let variant = enum_val.active_variant();
465                        if field_index >= variant.data.fields.len() {
466                            // Determine variant kind to use the right closing delimiter
467                            match variant.data.kind {
468                                StructKind::Tuple => {
469                                    // Close tuple variant with )
470                                    write!(
471                                        f,
472                                        "{:width$}{}",
473                                        "",
474                                        self.style_punctuation(")"),
475                                        width = (item.format_depth - 1) * self.indent_size
476                                    )?;
477                                }
478                                StructKind::Struct => {
479                                    // Close struct variant with }
480                                    write!(
481                                        f,
482                                        "{:width$}{}",
483                                        "",
484                                        self.style_punctuation("}"),
485                                        width = (item.format_depth - 1) * self.indent_size
486                                    )?;
487                                }
488                                _ => {}
489                            }
490                            continue;
491                        }
492
493                        let field = variant.data.fields[field_index];
494                        let field_value = enum_val.field(field_index).unwrap();
495
496                        // Add field doc comments if available
497                        // Only add new line if not the first field
498                        write!(
499                            f,
500                            "{:width$}",
501                            "",
502                            width = item.format_depth * self.indent_size
503                        )?;
504
505                        if !field.doc.is_empty() {
506                            for line in field.doc {
507                                self.write_comment(f, &format!("///{}", line))?;
508                                write!(
509                                    f,
510                                    "\n{:width$}",
511                                    "",
512                                    width = item.format_depth * self.indent_size
513                                )?;
514                            }
515                        }
516
517                        // For struct variants, print field name
518                        if let StructKind::Struct = enum_val.active_variant().data.kind {
519                            self.write_field_name(f, field.name)?;
520                            self.write_punctuation(f, ": ")?;
521                        }
522
523                        // Set up to process the next field after this one
524                        item.state = StackState::ProcessStructField {
525                            field_index: field_index + 1,
526                        };
527
528                        // Create finish and start items for processing the field value
529                        let finish_item = StackItem {
530                            value: field_value,
531                            format_depth: item.format_depth,
532                            type_depth: item.type_depth + 1,
533                            state: StackState::Finish,
534                        };
535                        let start_item = StackItem {
536                            value: field_value,
537                            format_depth: item.format_depth,
538                            type_depth: item.type_depth + 1,
539                            state: StackState::Start,
540                        };
541
542                        // Push items to stack in the right order
543                        stack.push_back(item);
544                        stack.push_back(finish_item);
545                        stack.push_back(start_item);
546                    }
547                }
548                StackState::ProcessListItem { item_index } => {
549                    let list = item.value.into_list().unwrap();
550                    if item_index >= list.len() {
551                        // All items processed, write closing bracket
552                        write!(
553                            f,
554                            "{:width$}",
555                            "",
556                            width = (item.format_depth - 1) * self.indent_size
557                        )?;
558                        self.write_punctuation(f, "]")?;
559                        continue;
560                    }
561
562                    // Indent
563                    write!(
564                        f,
565                        "{:width$}",
566                        "",
567                        width = item.format_depth * self.indent_size
568                    )?;
569
570                    // Push back current item to continue after formatting list item
571                    item.state = StackState::ProcessListItem {
572                        item_index: item_index + 1,
573                    };
574                    let next_format_depth = item.format_depth;
575                    let next_type_depth = item.type_depth + 1;
576                    stack.push_back(item);
577
578                    // Push list item to format first
579                    let list_item = list.get(item_index).unwrap();
580                    stack.push_back(StackItem {
581                        value: list_item,
582                        format_depth: next_format_depth,
583                        type_depth: next_type_depth,
584                        state: StackState::Finish,
585                    });
586
587                    // When we push a list item to format, we need to process it from the beginning
588                    stack.push_back(StackItem {
589                        value: list_item,
590                        format_depth: next_format_depth,
591                        type_depth: next_type_depth,
592                        state: StackState::Start, // Use Start state to properly process the item
593                    });
594                }
595                StackState::ProcessBytesItem { item_index } => {
596                    let list = item.value.into_list().unwrap();
597                    if item_index >= list.len() {
598                        // All items processed, write closing bracket
599                        write!(
600                            f,
601                            "{:width$}",
602                            "",
603                            width = (item.format_depth - 1) * self.indent_size
604                        )?;
605                        continue;
606                    }
607
608                    // On the first byte, write the opening byte sequence indicator
609                    if item_index == 0 {
610                        write!(f, " ")?;
611                    }
612
613                    // Only display 16 bytes per line
614                    if item_index > 0 && item_index % 16 == 0 {
615                        writeln!(f)?;
616                        write!(
617                            f,
618                            "{:width$}",
619                            "",
620                            width = item.format_depth * self.indent_size
621                        )?;
622                    } else if item_index > 0 {
623                        write!(f, " ")?;
624                    }
625
626                    // Get the byte
627                    let byte_value = list.get(item_index).unwrap();
628                    // Get the byte value as u8
629                    let byte = byte_value.get::<u8>().unwrap_or(&0);
630
631                    // Generate a color for this byte based on its value
632                    let mut hasher = DefaultHasher::new();
633                    byte.hash(&mut hasher);
634                    let hash = hasher.finish();
635                    let color = self.color_generator.generate_color(hash);
636
637                    // Apply color if needed
638                    if self.use_colors {
639                        write!(f, "\x1b[38;2;{};{};{}m", color.r, color.g, color.b)?;
640                    }
641
642                    // Display the byte in hex format
643                    write!(f, "{:02x}", *byte)?;
644
645                    // Reset color if needed
646                    // Reset color already handled by stylize
647
648                    // Push back current item to continue after formatting byte
649                    item.state = StackState::ProcessBytesItem {
650                        item_index: item_index + 1,
651                    };
652                    stack.push_back(item);
653                }
654                StackState::ProcessMapEntry => {
655                    // TODO: Implement proper map iteration when available in facet
656
657                    // Indent
658                    write!(
659                        f,
660                        "{:width$}",
661                        "",
662                        width = item.format_depth * self.indent_size
663                    )?;
664                    write!(f, "{}", self.style_comment("/* Map contents */"))?;
665                    writeln!(f)?;
666
667                    // Closing brace with proper indentation
668                    write!(
669                        f,
670                        "{:width$}{}",
671                        "",
672                        self.style_punctuation("}"),
673                        width = (item.format_depth - 1) * self.indent_size
674                    )?;
675                }
676                StackState::Finish => {
677                    // Add comma and newline for struct fields and list items
678                    self.write_punctuation(f, ",")?;
679                    writeln!(f)?;
680                }
681                StackState::OptionFinish => {
682                    // Just close the Option::Some parenthesis, with no comma
683                    self.write_punctuation(f, ")")?;
684                }
685            }
686        }
687
688        Ok(())
689    }
690
691    /// Format a scalar value
692    fn format_scalar(&self, value: Peek, f: &mut impl Write) -> fmt::Result {
693        // Generate a color for this shape
694        let mut hasher = DefaultHasher::new();
695        value.shape().id.hash(&mut hasher);
696        let hash = hasher.finish();
697        let color = self.color_generator.generate_color(hash);
698
699        // Display the value
700        struct DisplayWrapper<'a, 'facet_lifetime>(&'a Peek<'a, 'facet_lifetime>);
701
702        impl fmt::Display for DisplayWrapper<'_, '_> {
703            fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
704                if self.0.shape().is_display() {
705                    write!(f, "{}", self.0)?;
706                } else if self.0.shape().is_debug() {
707                    write!(f, "{:?}", self.0)?;
708                } else {
709                    write!(f, "{}", self.0.shape())?;
710                    write!(f, "(⋯)")?;
711                }
712                Ok(())
713            }
714        }
715
716        // Apply color if needed and display
717        if self.use_colors {
718            // We need to use direct ANSI codes for RGB colors
719            write!(
720                f,
721                "\x1b[38;2;{};{};{}m{}",
722                color.r,
723                color.g,
724                color.b,
725                DisplayWrapper(&value)
726            )?;
727            write!(f, "\x1b[0m")?;
728        } else {
729            write!(f, "{}", DisplayWrapper(&value))?;
730        }
731
732        Ok(())
733    }
734
735    /// Write styled type name to formatter
736    fn write_type_name<W: fmt::Write>(&self, f: &mut W, peek: &Peek) -> fmt::Result {
737        struct TypeNameWriter<'a, 'facet_lifetime>(&'a Peek<'a, 'facet_lifetime>);
738
739        impl core::fmt::Display for TypeNameWriter<'_, '_> {
740            fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
741                self.0.type_name(f, TypeNameOpts::infinite())
742            }
743        }
744        let type_name = TypeNameWriter(peek);
745
746        if self.use_colors {
747            write!(f, "{}", type_name.bold())
748        } else {
749            write!(f, "{}", type_name)
750        }
751    }
752
753    /// Style a type name and return it as a string
754    #[allow(dead_code)]
755    fn style_type_name(&self, peek: &Peek) -> String {
756        let mut result = String::new();
757        self.write_type_name(&mut result, peek).unwrap();
758        result
759    }
760
761    /// Write styled field name to formatter
762    fn write_field_name<W: fmt::Write>(&self, f: &mut W, name: &str) -> fmt::Result {
763        if self.use_colors {
764            // Use cyan color for field names (approximating original RGB color)
765            write!(f, "{}", name.cyan())
766        } else {
767            write!(f, "{}", name)
768        }
769    }
770
771    /// Write styled punctuation to formatter
772    fn write_punctuation<W: fmt::Write>(&self, f: &mut W, text: &str) -> fmt::Result {
773        if self.use_colors {
774            write!(f, "{}", text.dim())
775        } else {
776            write!(f, "{}", text)
777        }
778    }
779
780    /// Style punctuation and return it as a string
781    fn style_punctuation(&self, text: &str) -> String {
782        let mut result = String::new();
783        self.write_punctuation(&mut result, text).unwrap();
784        result
785    }
786
787    /// Write styled comment to formatter
788    fn write_comment<W: fmt::Write>(&self, f: &mut W, text: &str) -> fmt::Result {
789        if self.use_colors {
790            write!(f, "{}", text.dim())
791        } else {
792            write!(f, "{}", text)
793        }
794    }
795
796    /// Style a comment and return it as a string
797    fn style_comment(&self, text: &str) -> String {
798        let mut result = String::new();
799        self.write_comment(&mut result, text).unwrap();
800        result
801    }
802
803    /// Write styled redacted value to formatter
804    fn write_redacted<W: fmt::Write>(&self, f: &mut W, text: &str) -> fmt::Result {
805        if self.use_colors {
806            // Use bright red and bold for redacted values
807            write!(f, "{}", text.bright_red().bold())
808        } else {
809            write!(f, "{}", text)
810        }
811    }
812
813    /// Style a redacted value and return it as a string
814    #[allow(dead_code)]
815    fn style_redacted(&self, text: &str) -> String {
816        let mut result = String::new();
817        self.write_redacted(&mut result, text).unwrap();
818        result
819    }
820}
821
822#[cfg(test)]
823mod tests {
824    use super::*;
825
826    // Basic tests for the PrettyPrinter
827    #[test]
828    fn test_pretty_printer_default() {
829        let printer = PrettyPrinter::default();
830        assert_eq!(printer.indent_size, 2);
831        assert_eq!(printer.max_depth, None);
832        assert!(printer.use_colors);
833    }
834
835    #[test]
836    fn test_pretty_printer_with_methods() {
837        let printer = PrettyPrinter::new()
838            .with_indent_size(4)
839            .with_max_depth(3)
840            .with_colors(false);
841
842        assert_eq!(printer.indent_size, 4);
843        assert_eq!(printer.max_depth, Some(3));
844        assert!(!printer.use_colors);
845    }
846}