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