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