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, PointerType, StructKind, Type, TypeNameOpts, UserType};
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 and type
171                    match (item.value.shape().def, item.value.shape().ty) {
172                        // Handle scalar values
173                        (Def::Scalar(_def), _) => {
174                            self.format_scalar(item.value, f)?;
175                        }
176                        // Handle option types
177                        (Def::Option(_def), _) => {
178                            let option = item.value.into_option().unwrap();
179
180                            // Print the Option name
181                            self.write_type_name(f, &item.value)?;
182
183                            if option.is_some() {
184                                self.write_punctuation(f, "::Some(")?;
185
186                                if let Some(inner_value) = option.value() {
187                                    // Create a custom stack item for Option::Some value
188                                    let start_item = StackItem {
189                                        value: inner_value,
190                                        format_depth: item.format_depth,
191                                        type_depth: item.type_depth + 1,
192                                        state: StackState::Start,
193                                    };
194
195                                    // Add a special close parenthesis item
196                                    let close_paren_item = StackItem {
197                                        value: item.value,
198                                        format_depth: item.format_depth,
199                                        type_depth: item.type_depth,
200                                        state: StackState::OptionFinish,
201                                    };
202
203                                    // Process the value first, then handle closing
204                                    stack.push_back(close_paren_item);
205                                    stack.push_back(start_item);
206                                }
207
208                                // Skip to next item
209                                continue;
210                            } else {
211                                self.write_punctuation(f, "::None")?;
212                            }
213                        }
214                        // Handle struct types
215                        (_, Type::User(UserType::Struct(_))) => {
216                            let struct_ = item.value.into_struct().unwrap();
217
218                            // Get struct doc comments from the shape
219                            let doc_comments = item.value.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, &item.value)?;
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                            stack.push_back(item);
242                        }
243                        (Def::List(_), _) => {
244                            let list = item.value.into_list().unwrap();
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                                // Incrementing type_depth for all list operations
249                                item.type_depth + 1; // Always increment type_depth for list operations
250
251                            // Print the list name
252                            self.write_type_name(f, &item.value)?;
253
254                            if list.def().t().is_type::<u8>() && self.list_u8_as_bytes {
255                                // Push back the item with the next state to continue processing list items
256                                item.state = StackState::ProcessBytesItem { item_index: 0 };
257                                writeln!(f)?;
258                                write!(f, " ")?;
259
260                                // TODO: write all the bytes here instead?
261                            } else {
262                                // Push back the item with the next state to continue processing list items
263                                item.state = StackState::ProcessListItem { item_index: 0 };
264                                self.write_punctuation(f, " [")?;
265                                writeln!(f)?;
266                            }
267
268                            item.format_depth += 1;
269                            item.type_depth = new_type_depth;
270                            stack.push_back(item);
271                        }
272                        (Def::Map(_), _) => {
273                            let _map = item.value.into_map().unwrap();
274                            // Print the map name
275                            self.write_type_name(f, &item.value)?;
276                            self.write_punctuation(f, " {")?;
277                            writeln!(f)?;
278
279                            // Push back the item with the next state to continue processing map
280                            item.state = StackState::ProcessMapEntry;
281                            item.format_depth += 1;
282                            // When recursing into a map, always increment format_depth
283                            item.type_depth += 1; // Always increment type_depth for map operations
284                            stack.push_back(item);
285                        }
286                        (_, Type::User(UserType::Enum(_))) => {
287                            // When recursing into an enum, increment format_depth
288                            // Only increment type_depth if we're moving to a different address
289                            let enum_peek = item.value.into_enum().unwrap();
290
291                            // Get the active variant or handle error
292                            let variant = match enum_peek.active_variant() {
293                                Ok(v) => v,
294                                Err(_) => {
295                                    // Print the enum name
296                                    self.write_type_name(f, &item.value)?;
297                                    write!(f, " /* cannot determine variant */")?;
298                                    continue;
299                                }
300                            };
301
302                            // Get enum and variant doc comments
303                            let doc_comments = item.value.shape().doc;
304
305                            // Display doc comments before the type name
306                            for line in doc_comments {
307                                self.write_comment(f, &format!("///{}", line))?;
308                                writeln!(f)?;
309                            }
310
311                            // Show variant docs
312                            for line in variant.doc {
313                                self.write_comment(f, &format!("///{}", line))?;
314                                writeln!(f)?;
315                            }
316
317                            // Print the enum name and separator
318                            self.write_type_name(f, &item.value)?;
319                            self.write_punctuation(f, "::")?;
320
321                            // Variant docs are already handled above
322
323                            // Get the active variant name - we've already checked above that we can get it
324                            // This is the same variant, but we're repeating the code here to ensure consistency
325
326                            // Apply color for variant name
327                            if self.use_colors {
328                                write!(f, "{}", variant.name.bold())?;
329                            } else {
330                                write!(f, "{}", variant.name)?;
331                            }
332
333                            // Process the variant fields based on the variant kind
334                            match variant.data.kind {
335                                StructKind::Unit => {
336                                    // Unit variant has no fields, nothing more to print
337                                }
338                                StructKind::Tuple => {
339                                    // Tuple variant, print the fields like a tuple
340                                    self.write_punctuation(f, "(")?;
341
342                                    // Check if there are any fields to print
343                                    if variant.data.fields.is_empty() {
344                                        self.write_punctuation(f, ")")?;
345                                        continue;
346                                    }
347
348                                    writeln!(f)?;
349
350                                    // Push back item to process fields
351                                    item.state = StackState::ProcessStructField { field_index: 0 };
352                                    item.format_depth += 1;
353                                    stack.push_back(item);
354                                }
355                                StructKind::Struct => {
356                                    // Struct variant, print the fields like a struct
357                                    self.write_punctuation(f, " {")?;
358
359                                    // Check if there are any fields to print
360                                    let has_fields = !variant.data.fields.is_empty();
361
362                                    if !has_fields {
363                                        self.write_punctuation(f, " }")?;
364                                        continue;
365                                    }
366
367                                    writeln!(f)?;
368
369                                    // Push back item to process fields
370                                    item.state = StackState::ProcessStructField { field_index: 0 };
371                                    item.format_depth += 1;
372                                    stack.push_back(item);
373                                }
374                                _ => {
375                                    // Other variant kinds that might be added in the future
376                                    write!(f, " /* unsupported variant kind */")?;
377                                }
378                            }
379                        }
380                        (_, Type::Pointer(PointerType::Function(_))) => {
381                            // Just print the type name for function pointers
382                            self.write_type_name(f, &item.value)?;
383                            write!(f, " /* function pointer (not yet supported) */")?;
384                        }
385                        _ => {
386                            write!(f, "unsupported peek variant: {:?}", item.value)?;
387                        }
388                    }
389                }
390                StackState::ProcessStructField { field_index } => {
391                    // Handle both struct and enum fields
392                    if let Type::User(UserType::Struct(struct_)) = item.value.shape().ty {
393                        let peek_struct = item.value.into_struct().unwrap();
394                        if field_index >= struct_.fields.len() {
395                            // All fields processed, write closing brace
396                            write!(
397                                f,
398                                "{:width$}{}",
399                                "",
400                                self.style_punctuation("}"),
401                                width = (item.format_depth - 1) * self.indent_size
402                            )?;
403                            continue;
404                        }
405
406                        let field = struct_.fields[field_index];
407                        let field_value = peek_struct.field(field_index).unwrap();
408
409                        // Field doc comment
410                        if !field.doc.is_empty() {
411                            // Only add new line if not the first field
412                            if field_index > 0 {
413                                writeln!(f)?;
414                            }
415                            // Hard-code consistent indentation for doc comments
416                            for line in field.doc {
417                                // Use exactly the same indentation as fields (2 spaces)
418                                write!(
419                                    f,
420                                    "{:width$}",
421                                    "",
422                                    width = item.format_depth * self.indent_size
423                                )?;
424                                self.write_comment(f, &format!("///{}", line))?;
425                                writeln!(f)?;
426                            }
427                        }
428
429                        // Field name
430                        write!(
431                            f,
432                            "{:width$}",
433                            "",
434                            width = item.format_depth * self.indent_size
435                        )?;
436                        self.write_field_name(f, field.name)?;
437                        self.write_punctuation(f, ": ")?;
438
439                        // Check if field is sensitive
440                        if field.flags.contains(FieldFlags::SENSITIVE) {
441                            // Field value is sensitive, use write_redacted
442                            self.write_redacted(f, "[REDACTED]")?;
443                            self.write_punctuation(f, ",")?;
444                            writeln!(f)?;
445
446                            item.state = StackState::ProcessStructField {
447                                field_index: field_index + 1,
448                            };
449                            stack.push_back(item);
450                        } else {
451                            // Field value is not sensitive, format normally
452                            // Push back current item to continue after formatting field value
453                            item.state = StackState::ProcessStructField {
454                                field_index: field_index + 1,
455                            };
456
457                            let finish_item = StackItem {
458                                value: field_value,
459                                format_depth: item.format_depth,
460                                type_depth: item.type_depth + 1,
461                                state: StackState::Finish,
462                            };
463                            let start_item = StackItem {
464                                value: field_value,
465                                format_depth: item.format_depth,
466                                type_depth: item.type_depth + 1,
467                                state: StackState::Start,
468                            };
469
470                            stack.push_back(item);
471                            stack.push_back(finish_item);
472                            stack.push_back(start_item);
473                        }
474                    } else if let Type::User(UserType::Enum(_def)) = item.value.shape().ty {
475                        let enum_val = item.value.into_enum().unwrap();
476
477                        // Get active variant or skip this field processing
478                        let variant = match enum_val.active_variant() {
479                            Ok(v) => v,
480                            Err(_) => {
481                                // Skip field processing for this enum
482                                continue;
483                            }
484                        };
485                        if field_index >= variant.data.fields.len() {
486                            // Determine variant kind to use the right closing delimiter
487                            match variant.data.kind {
488                                StructKind::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                                StructKind::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                        let field = variant.data.fields[field_index];
514
515                        // Get field value or skip this field
516                        let field_value = match enum_val.field(field_index) {
517                            Ok(Some(v)) => v,
518                            _ => {
519                                // Can't get the field value, skip this field
520                                item.state = StackState::ProcessStructField {
521                                    field_index: field_index + 1,
522                                };
523                                stack.push_back(item);
524                                continue;
525                            }
526                        };
527
528                        // Add field doc comments if available
529                        // Only add new line if not the first field
530                        write!(
531                            f,
532                            "{:width$}",
533                            "",
534                            width = item.format_depth * self.indent_size
535                        )?;
536
537                        if !field.doc.is_empty() {
538                            for line in field.doc {
539                                self.write_comment(f, &format!("///{}", line))?;
540                                write!(
541                                    f,
542                                    "\n{:width$}",
543                                    "",
544                                    width = item.format_depth * self.indent_size
545                                )?;
546                            }
547                        }
548
549                        // For struct variants, print field name
550                        if let StructKind::Struct = variant.data.kind {
551                            self.write_field_name(f, field.name)?;
552                            self.write_punctuation(f, ": ")?;
553                        }
554
555                        // Set up to process the next field after this one
556                        item.state = StackState::ProcessStructField {
557                            field_index: field_index + 1,
558                        };
559
560                        // Create finish and start items for processing the field value
561                        let finish_item = StackItem {
562                            value: field_value,
563                            format_depth: item.format_depth,
564                            type_depth: item.type_depth + 1,
565                            state: StackState::Finish,
566                        };
567                        let start_item = StackItem {
568                            value: field_value,
569                            format_depth: item.format_depth,
570                            type_depth: item.type_depth + 1,
571                            state: StackState::Start,
572                        };
573
574                        // Push items to stack in the right order
575                        stack.push_back(item);
576                        stack.push_back(finish_item);
577                        stack.push_back(start_item);
578                    }
579                }
580                StackState::ProcessListItem { item_index } => {
581                    let list = item.value.into_list().unwrap();
582                    if item_index >= list.len() {
583                        // All items processed, write closing bracket
584                        write!(
585                            f,
586                            "{:width$}",
587                            "",
588                            width = (item.format_depth - 1) * self.indent_size
589                        )?;
590                        self.write_punctuation(f, "]")?;
591                        continue;
592                    }
593
594                    // Indent
595                    write!(
596                        f,
597                        "{:width$}",
598                        "",
599                        width = item.format_depth * self.indent_size
600                    )?;
601
602                    // Push back current item to continue after formatting list item
603                    item.state = StackState::ProcessListItem {
604                        item_index: item_index + 1,
605                    };
606                    let next_format_depth = item.format_depth;
607                    let next_type_depth = item.type_depth + 1;
608                    stack.push_back(item);
609
610                    // Push list item to format first
611                    let list_item = list.get(item_index).unwrap();
612                    stack.push_back(StackItem {
613                        value: list_item,
614                        format_depth: next_format_depth,
615                        type_depth: next_type_depth,
616                        state: StackState::Finish,
617                    });
618
619                    // When we push a list item to format, we need to process it from the beginning
620                    stack.push_back(StackItem {
621                        value: list_item,
622                        format_depth: next_format_depth,
623                        type_depth: next_type_depth,
624                        state: StackState::Start, // Use Start state to properly process the item
625                    });
626                }
627                StackState::ProcessBytesItem { item_index } => {
628                    let list = item.value.into_list().unwrap();
629                    if item_index >= list.len() {
630                        // All items processed, write closing bracket
631                        write!(
632                            f,
633                            "{:width$}",
634                            "",
635                            width = (item.format_depth - 1) * self.indent_size
636                        )?;
637                        continue;
638                    }
639
640                    // On the first byte, write the opening byte sequence indicator
641                    if item_index == 0 {
642                        write!(f, " ")?;
643                    }
644
645                    // Only display 16 bytes per line
646                    if item_index > 0 && item_index % 16 == 0 {
647                        writeln!(f)?;
648                        write!(
649                            f,
650                            "{:width$}",
651                            "",
652                            width = item.format_depth * self.indent_size
653                        )?;
654                    } else if item_index > 0 {
655                        write!(f, " ")?;
656                    }
657
658                    // Get the byte
659                    let byte_value = list.get(item_index).unwrap();
660                    // Get the byte value as u8
661                    let byte = byte_value.get::<u8>().unwrap_or(&0);
662
663                    // Generate a color for this byte based on its value
664                    let mut hasher = DefaultHasher::new();
665                    byte.hash(&mut hasher);
666                    let hash = hasher.finish();
667                    let color = self.color_generator.generate_color(hash);
668
669                    // Apply color if needed
670                    if self.use_colors {
671                        write!(f, "\x1b[38;2;{};{};{}m", color.r, color.g, color.b)?;
672                    }
673
674                    // Display the byte in hex format
675                    write!(f, "{:02x}", *byte)?;
676
677                    // Reset color if needed
678                    // Reset color already handled by stylize
679
680                    // Push back current item to continue after formatting byte
681                    item.state = StackState::ProcessBytesItem {
682                        item_index: item_index + 1,
683                    };
684                    stack.push_back(item);
685                }
686                StackState::ProcessMapEntry => {
687                    // TODO: Implement proper map iteration when available in facet
688
689                    // Indent
690                    write!(
691                        f,
692                        "{:width$}",
693                        "",
694                        width = item.format_depth * self.indent_size
695                    )?;
696                    write!(f, "{}", self.style_comment("/* Map contents */"))?;
697                    writeln!(f)?;
698
699                    // Closing brace with proper indentation
700                    write!(
701                        f,
702                        "{:width$}{}",
703                        "",
704                        self.style_punctuation("}"),
705                        width = (item.format_depth - 1) * self.indent_size
706                    )?;
707                }
708                StackState::Finish => {
709                    // Add comma and newline for struct fields and list items
710                    self.write_punctuation(f, ",")?;
711                    writeln!(f)?;
712                }
713                StackState::OptionFinish => {
714                    // Just close the Option::Some parenthesis, with no comma
715                    self.write_punctuation(f, ")")?;
716                }
717            }
718        }
719
720        Ok(())
721    }
722
723    /// Format a scalar value
724    fn format_scalar(&self, value: Peek, f: &mut impl Write) -> fmt::Result {
725        // Generate a color for this shape
726        let mut hasher = DefaultHasher::new();
727        value.shape().id.hash(&mut hasher);
728        let hash = hasher.finish();
729        let color = self.color_generator.generate_color(hash);
730
731        // Display the value
732        struct DisplayWrapper<'a, 'facet_lifetime>(&'a Peek<'a, 'facet_lifetime>);
733
734        impl fmt::Display for DisplayWrapper<'_, '_> {
735            fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
736                if self.0.shape().is_display() {
737                    write!(f, "{}", self.0)?;
738                } else if self.0.shape().is_debug() {
739                    write!(f, "{:?}", self.0)?;
740                } else {
741                    write!(f, "{}", self.0.shape())?;
742                    write!(f, "(⋯)")?;
743                }
744                Ok(())
745            }
746        }
747
748        // Apply color if needed and display
749        if self.use_colors {
750            // We need to use direct ANSI codes for RGB colors
751            write!(
752                f,
753                "\x1b[38;2;{};{};{}m{}",
754                color.r,
755                color.g,
756                color.b,
757                DisplayWrapper(&value)
758            )?;
759            write!(f, "\x1b[0m")?;
760        } else {
761            write!(f, "{}", DisplayWrapper(&value))?;
762        }
763
764        Ok(())
765    }
766
767    /// Write styled type name to formatter
768    fn write_type_name<W: fmt::Write>(&self, f: &mut W, peek: &Peek) -> fmt::Result {
769        struct TypeNameWriter<'a, 'facet_lifetime>(&'a Peek<'a, 'facet_lifetime>);
770
771        impl core::fmt::Display for TypeNameWriter<'_, '_> {
772            fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
773                self.0.type_name(f, TypeNameOpts::infinite())
774            }
775        }
776        let type_name = TypeNameWriter(peek);
777
778        if self.use_colors {
779            write!(f, "{}", type_name.bold())
780        } else {
781            write!(f, "{}", type_name)
782        }
783    }
784
785    /// Style a type name and return it as a string
786    #[allow(dead_code)]
787    fn style_type_name(&self, peek: &Peek) -> String {
788        let mut result = String::new();
789        self.write_type_name(&mut result, peek).unwrap();
790        result
791    }
792
793    /// Write styled field name to formatter
794    fn write_field_name<W: fmt::Write>(&self, f: &mut W, name: &str) -> fmt::Result {
795        if self.use_colors {
796            // Use cyan color for field names (approximating original RGB color)
797            write!(f, "{}", name.cyan())
798        } else {
799            write!(f, "{}", name)
800        }
801    }
802
803    /// Write styled punctuation to formatter
804    fn write_punctuation<W: fmt::Write>(&self, f: &mut W, text: &str) -> fmt::Result {
805        if self.use_colors {
806            write!(f, "{}", text.dim())
807        } else {
808            write!(f, "{}", text)
809        }
810    }
811
812    /// Style punctuation and return it as a string
813    fn style_punctuation(&self, text: &str) -> String {
814        let mut result = String::new();
815        self.write_punctuation(&mut result, text).unwrap();
816        result
817    }
818
819    /// Write styled comment to formatter
820    fn write_comment<W: fmt::Write>(&self, f: &mut W, text: &str) -> fmt::Result {
821        if self.use_colors {
822            write!(f, "{}", text.dim())
823        } else {
824            write!(f, "{}", text)
825        }
826    }
827
828    /// Style a comment and return it as a string
829    fn style_comment(&self, text: &str) -> String {
830        let mut result = String::new();
831        self.write_comment(&mut result, text).unwrap();
832        result
833    }
834
835    /// Write styled redacted value to formatter
836    fn write_redacted<W: fmt::Write>(&self, f: &mut W, text: &str) -> fmt::Result {
837        if self.use_colors {
838            // Use bright red and bold for redacted values
839            write!(f, "{}", text.bright_red().bold())
840        } else {
841            write!(f, "{}", text)
842        }
843    }
844
845    /// Style a redacted value and return it as a string
846    #[allow(dead_code)]
847    fn style_redacted(&self, text: &str) -> String {
848        let mut result = String::new();
849        self.write_redacted(&mut result, text).unwrap();
850        result
851    }
852}
853
854#[cfg(test)]
855mod tests {
856    use super::*;
857
858    // Basic tests for the PrettyPrinter
859    #[test]
860    fn test_pretty_printer_default() {
861        let printer = PrettyPrinter::default();
862        assert_eq!(printer.indent_size, 2);
863        assert_eq!(printer.max_depth, None);
864        assert!(printer.use_colors);
865    }
866
867    #[test]
868    fn test_pretty_printer_with_methods() {
869        let printer = PrettyPrinter::new()
870            .with_indent_size(4)
871            .with_max_depth(3)
872            .with_colors(false);
873
874        assert_eq!(printer.indent_size, 4);
875        assert_eq!(printer.max_depth, Some(3));
876        assert!(!printer.use_colors);
877    }
878}