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}