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};
10
11use facet_core::{
12 Def, Facet, FieldFlags, PointerType, PrimitiveType, SequenceType, StructKind, TextualType,
13 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<'mem, 'facet, 'shape> {
58 value: Peek<'mem, 'facet, 'shape>,
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<'shape>(
125 &self,
126 initial_value: Peek<'_, '_, 'shape>,
127 f: &mut impl Write,
128 visited: &mut HashMap<ValueId<'shape>, 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 tuple struct types
222 (_, Type::User(UserType::Struct(struct_def)))
223 if struct_def.kind == StructKind::Tuple =>
224 {
225 self.write_type_name(f, &item.value)?;
226 item.state = StackState::ProcessSeqItem {
227 item_index: 0,
228 kind: SeqKind::Tuple,
229 };
230 self.write_punctuation(f, " (")?;
231 writeln!(f)?;
232 item.format_depth += 1;
233 item.type_depth += 1;
234 stack.push_back(item);
235 }
236 // Handle regular struct types
237 (_, Type::User(UserType::Struct(_))) => {
238 let struct_ = item.value.into_struct().unwrap();
239
240 // Get struct doc comments from the shape
241 let doc_comments = item.value.shape().doc;
242 if !doc_comments.is_empty() {
243 for line in doc_comments {
244 self.write_comment(f, &format!("///{}", line))?;
245 writeln!(f)?;
246 }
247 }
248
249 // Print the struct name
250 self.write_type_name(f, &item.value)?;
251 self.write_punctuation(f, " {")?;
252
253 if struct_.field_count() == 0 {
254 self.write_punctuation(f, "}")?;
255 continue;
256 }
257
258 writeln!(f)?;
259
260 // Push back the item with the next state to continue processing fields
261 item.state = StackState::ProcessStructField { field_index: 0 };
262 item.format_depth += 1;
263 stack.push_back(item);
264 }
265 (Def::List(_), _) => {
266 self.handle_list(&mut stack, item, f)?;
267 continue;
268 }
269 (_, Type::Pointer(PointerType::Reference(r))) => {
270 'handle: {
271 let target = (r.target)();
272 match target.ty {
273 Type::Sequence(
274 SequenceType::Slice(_) | SequenceType::Array(_),
275 ) => {
276 self.handle_list(&mut stack, item, f)?;
277 break 'handle;
278 }
279 Type::Primitive(primitive_type) => match primitive_type {
280 PrimitiveType::Boolean => {}
281 PrimitiveType::Numeric(_numeric_type) => {}
282 PrimitiveType::Textual(textual_type) => {
283 match textual_type {
284 TextualType::Char => todo!(),
285 TextualType::Str => {
286 // well we can print a string slice, that's no issue.
287 // `Peek` implements `Display` which forwards to the
288 // `Display` implementation of the underlying type.
289 if self.use_colors {
290 write!(f, "\x1b[33m{}\x1b[0m", item.value)?; // yellow
291 } else {
292 write!(f, "{}", item.value)?;
293 }
294 break 'handle;
295 }
296 }
297 }
298 PrimitiveType::Never => {}
299 },
300 _ => {
301 write!(f, "unsupported reference type: {:?}", item.value)?;
302 }
303 }
304 }
305 }
306 (Def::Map(_), _) => {
307 let _map = item.value.into_map().unwrap();
308 // Print the map name
309 self.write_type_name(f, &item.value)?;
310 self.write_punctuation(f, " {")?;
311 writeln!(f)?;
312
313 // Push back the item with the next state to continue processing map
314 item.state = StackState::ProcessMapEntry;
315 item.format_depth += 1;
316 // When recursing into a map, always increment format_depth
317 item.type_depth += 1; // Always increment type_depth for map operations
318 stack.push_back(item);
319 }
320 (_, Type::User(UserType::Enum(_))) => {
321 // When recursing into an enum, increment format_depth
322 // Only increment type_depth if we're moving to a different address
323 let enum_peek = item.value.into_enum().unwrap();
324
325 // Get the active variant or handle error
326 let variant = match enum_peek.active_variant() {
327 Ok(v) => v,
328 Err(_) => {
329 // Print the enum name
330 self.write_type_name(f, &item.value)?;
331 write!(f, " /* cannot determine variant */")?;
332 continue;
333 }
334 };
335
336 // Get enum and variant doc comments
337 let doc_comments = item.value.shape().doc;
338
339 // Display doc comments before the type name
340 for line in doc_comments {
341 self.write_comment(f, &format!("///{}", line))?;
342 writeln!(f)?;
343 }
344
345 // Show variant docs
346 for line in variant.doc {
347 self.write_comment(f, &format!("///{}", line))?;
348 writeln!(f)?;
349 }
350
351 // Print the enum name and separator
352 self.write_type_name(f, &item.value)?;
353 self.write_punctuation(f, "::")?;
354
355 // Variant docs are already handled above
356
357 // Get the active variant name - we've already checked above that we can get it
358 // This is the same variant, but we're repeating the code here to ensure consistency
359
360 // Apply color for variant name
361 if self.use_colors {
362 if self.use_colors {
363 write!(f, "\x1b[1m{}\x1b[0m", variant.name)?; // bold
364 } else {
365 write!(f, "{}", variant.name)?;
366 }
367 } else {
368 write!(f, "{}", variant.name)?;
369 }
370
371 // Process the variant fields based on the variant kind
372 match variant.data.kind {
373 StructKind::Unit => {
374 // Unit variant has no fields, nothing more to print
375 }
376 StructKind::Tuple => {
377 // Tuple variant, print the fields like a tuple
378 self.write_punctuation(f, "(")?;
379
380 // Check if there are any fields to print
381 if variant.data.fields.is_empty() {
382 self.write_punctuation(f, ")")?;
383 continue;
384 }
385
386 writeln!(f)?;
387
388 // Push back item to process fields
389 item.state = StackState::ProcessStructField { field_index: 0 };
390 item.format_depth += 1;
391 stack.push_back(item);
392 }
393 StructKind::Struct => {
394 // Struct variant, print the fields like a struct
395 self.write_punctuation(f, " {")?;
396
397 // Check if there are any fields to print
398 let has_fields = !variant.data.fields.is_empty();
399
400 if !has_fields {
401 self.write_punctuation(f, " }")?;
402 continue;
403 }
404
405 writeln!(f)?;
406
407 // Push back item to process fields
408 item.state = StackState::ProcessStructField { field_index: 0 };
409 item.format_depth += 1;
410 stack.push_back(item);
411 }
412 _ => {
413 // Other variant kinds that might be added in the future
414 write!(f, " /* unsupported variant kind */")?;
415 }
416 }
417 }
418 (_, Type::Pointer(PointerType::Function(_))) => {
419 // Just print the type name for function pointers
420 self.write_type_name(f, &item.value)?;
421 write!(f, " /* function pointer (not yet supported) */")?;
422 }
423 _ => {
424 write!(f, "unsupported peek variant: {:?}", item.value)?;
425 }
426 }
427 }
428 StackState::ProcessStructField { field_index } => {
429 // Handle both struct and enum fields
430 if let Type::User(UserType::Struct(struct_)) = item.value.shape().ty {
431 let peek_struct = item.value.into_struct().unwrap();
432 if field_index >= struct_.fields.len() {
433 // All fields processed, write closing brace
434 write!(
435 f,
436 "{:width$}{}",
437 "",
438 self.style_punctuation("}"),
439 width = (item.format_depth - 1) * self.indent_size
440 )?;
441 continue;
442 }
443
444 let field = struct_.fields[field_index];
445 let field_value = peek_struct.field(field_index).unwrap();
446
447 // Field doc comment
448 if !field.doc.is_empty() {
449 // Only add new line if not the first field
450 if field_index > 0 {
451 writeln!(f)?;
452 }
453 // Hard-code consistent indentation for doc comments
454 for line in field.doc {
455 // Use exactly the same indentation as fields (2 spaces)
456 write!(
457 f,
458 "{:width$}",
459 "",
460 width = item.format_depth * self.indent_size
461 )?;
462 self.write_comment(f, &format!("///{}", line))?;
463 writeln!(f)?;
464 }
465 }
466
467 // Field name
468 write!(
469 f,
470 "{:width$}",
471 "",
472 width = item.format_depth * self.indent_size
473 )?;
474 self.write_field_name(f, field.name)?;
475 self.write_punctuation(f, ": ")?;
476
477 // Check if field is sensitive
478 if field.flags.contains(FieldFlags::SENSITIVE) {
479 // Field value is sensitive, use write_redacted
480 self.write_redacted(f, "[REDACTED]")?;
481 self.write_punctuation(f, ",")?;
482 writeln!(f)?;
483
484 item.state = StackState::ProcessStructField {
485 field_index: field_index + 1,
486 };
487 stack.push_back(item);
488 } else {
489 // Field value is not sensitive, format normally
490 // Push back current item to continue after formatting field value
491 item.state = StackState::ProcessStructField {
492 field_index: field_index + 1,
493 };
494
495 let finish_item = StackItem {
496 value: field_value,
497 format_depth: item.format_depth,
498 type_depth: item.type_depth + 1,
499 state: StackState::Finish,
500 };
501 let start_item = StackItem {
502 value: field_value,
503 format_depth: item.format_depth,
504 type_depth: item.type_depth + 1,
505 state: StackState::Start,
506 };
507
508 stack.push_back(item);
509 stack.push_back(finish_item);
510 stack.push_back(start_item);
511 }
512 } else if let Type::User(UserType::Enum(_def)) = item.value.shape().ty {
513 let enum_val = item.value.into_enum().unwrap();
514
515 // Get active variant or skip this field processing
516 let variant = match enum_val.active_variant() {
517 Ok(v) => v,
518 Err(_) => {
519 // Skip field processing for this enum
520 continue;
521 }
522 };
523 if field_index >= variant.data.fields.len() {
524 // Determine variant kind to use the right closing delimiter
525 match variant.data.kind {
526 StructKind::Tuple => {
527 // Close tuple variant with )
528 write!(
529 f,
530 "{:width$}{}",
531 "",
532 self.style_punctuation(")"),
533 width = (item.format_depth - 1) * self.indent_size
534 )?;
535 }
536 StructKind::Struct => {
537 // Close struct variant with }
538 write!(
539 f,
540 "{:width$}{}",
541 "",
542 self.style_punctuation("}"),
543 width = (item.format_depth - 1) * self.indent_size
544 )?;
545 }
546 _ => {}
547 }
548 continue;
549 }
550
551 let field = variant.data.fields[field_index];
552
553 // Get field value or skip this field
554 let field_value = match enum_val.field(field_index) {
555 Ok(Some(v)) => v,
556 _ => {
557 // Can't get the field value, skip this field
558 item.state = StackState::ProcessStructField {
559 field_index: field_index + 1,
560 };
561 stack.push_back(item);
562 continue;
563 }
564 };
565
566 // Add field doc comments if available
567 // Only add new line if not the first field
568 write!(
569 f,
570 "{:width$}",
571 "",
572 width = item.format_depth * self.indent_size
573 )?;
574
575 if !field.doc.is_empty() {
576 for line in field.doc {
577 self.write_comment(f, &format!("///{}", line))?;
578 write!(
579 f,
580 "\n{:width$}",
581 "",
582 width = item.format_depth * self.indent_size
583 )?;
584 }
585 }
586
587 // For struct variants, print field name
588 if let StructKind::Struct = variant.data.kind {
589 self.write_field_name(f, field.name)?;
590 self.write_punctuation(f, ": ")?;
591 }
592
593 // Set up to process the next field after this one
594 item.state = StackState::ProcessStructField {
595 field_index: field_index + 1,
596 };
597
598 // Create finish and start items for processing the field value
599 let finish_item = StackItem {
600 value: field_value,
601 format_depth: item.format_depth,
602 type_depth: item.type_depth + 1,
603 state: StackState::Finish,
604 };
605 let start_item = StackItem {
606 value: field_value,
607 format_depth: item.format_depth,
608 type_depth: item.type_depth + 1,
609 state: StackState::Start,
610 };
611
612 // Push items to stack in the right order
613 stack.push_back(item);
614 stack.push_back(finish_item);
615 stack.push_back(start_item);
616 }
617 }
618 StackState::ProcessSeqItem { item_index, kind } => {
619 let (len, elem) = match kind {
620 SeqKind::List => {
621 let list = item.value.into_list_like().unwrap();
622 (list.len(), list.get(item_index))
623 }
624 SeqKind::Tuple => {
625 let tuple = item.value.into_tuple().unwrap();
626 (tuple.len(), tuple.field(item_index))
627 }
628 };
629 if item_index >= 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 self.write_punctuation(
638 f,
639 match kind {
640 SeqKind::List => "]",
641 SeqKind::Tuple => ")",
642 },
643 )?;
644 continue;
645 }
646
647 // Indent
648 write!(
649 f,
650 "{:width$}",
651 "",
652 width = item.format_depth * self.indent_size
653 )?;
654
655 // Push back current item to continue after formatting list item
656 item.state = StackState::ProcessSeqItem {
657 item_index: item_index + 1,
658 kind,
659 };
660 let next_format_depth = item.format_depth;
661 let next_type_depth = item.type_depth + 1;
662 stack.push_back(item);
663
664 let elem = elem.unwrap();
665
666 // Push list item to format first
667 stack.push_back(StackItem {
668 value: elem,
669 format_depth: next_format_depth,
670 type_depth: next_type_depth,
671 state: StackState::Finish,
672 });
673
674 // When we push a list item to format, we need to process it from the beginning
675 stack.push_back(StackItem {
676 value: elem,
677 format_depth: next_format_depth,
678 type_depth: next_type_depth,
679 state: StackState::Start, // Use Start state to properly process the item
680 });
681 }
682 StackState::ProcessBytesItem { item_index } => {
683 let list = item.value.into_list().unwrap();
684 if item_index >= list.len() {
685 // All items processed, write closing bracket
686 write!(
687 f,
688 "{:width$}",
689 "",
690 width = (item.format_depth - 1) * self.indent_size
691 )?;
692 continue;
693 }
694
695 // On the first byte, write the opening byte sequence indicator
696 if item_index == 0 {
697 write!(f, " ")?;
698 }
699
700 // Only display 16 bytes per line
701 if item_index > 0 && item_index % 16 == 0 {
702 writeln!(f)?;
703 write!(
704 f,
705 "{:width$}",
706 "",
707 width = item.format_depth * self.indent_size
708 )?;
709 } else if item_index > 0 {
710 write!(f, " ")?;
711 }
712
713 // Get the byte
714 let byte_value = list.get(item_index).unwrap();
715 // Get the byte value as u8
716 let byte = byte_value.get::<u8>().unwrap_or(&0);
717
718 // Generate a color for this byte based on its value
719 let mut hasher = DefaultHasher::new();
720 byte.hash(&mut hasher);
721 let hash = hasher.finish();
722 let color = self.color_generator.generate_color(hash);
723
724 // Apply color if needed
725 if self.use_colors {
726 write!(f, "\x1b[38;2;{};{};{}m", color.r, color.g, color.b)?;
727 }
728
729 // Display the byte in hex format
730 write!(f, "{:02x}", *byte)?;
731
732 // Reset color if needed
733 // Reset color already handled by stylize
734
735 // Push back current item to continue after formatting byte
736 item.state = StackState::ProcessBytesItem {
737 item_index: item_index + 1,
738 };
739 stack.push_back(item);
740 }
741 StackState::ProcessMapEntry => {
742 // TODO: Implement proper map iteration when available in facet
743
744 // Indent
745 write!(
746 f,
747 "{:width$}",
748 "",
749 width = item.format_depth * self.indent_size
750 )?;
751 write!(f, "{}", self.style_comment("/* Map contents */"))?;
752 writeln!(f)?;
753
754 // Closing brace with proper indentation
755 write!(
756 f,
757 "{:width$}{}",
758 "",
759 self.style_punctuation("}"),
760 width = (item.format_depth - 1) * self.indent_size
761 )?;
762 }
763 StackState::Finish => {
764 // Add comma and newline for struct fields and list items
765 self.write_punctuation(f, ",")?;
766 writeln!(f)?;
767 }
768 StackState::OptionFinish => {
769 // Just close the Option::Some parenthesis, with no comma
770 self.write_punctuation(f, ")")?;
771 }
772 }
773 }
774
775 Ok(())
776 }
777
778 fn handle_list<'mem, 'facet, 'shape>(
779 &self,
780 stack: &mut VecDeque<StackItem<'mem, 'facet, 'shape>>,
781 mut item: StackItem<'mem, 'facet, 'shape>,
782 f: &mut impl Write,
783 ) -> fmt::Result {
784 let list = item.value.into_list_like().unwrap();
785
786 // When recursing into a list, always increment format_depth
787 // Only increment type_depth if we're moving to a different address
788 let new_type_depth =
789 // Incrementing type_depth for all list operations
790 item.type_depth + 1; // Always increment type_depth for list operations
791
792 // Print the list name
793 self.write_type_name(f, &item.value)?;
794
795 if list.def().t().is_type::<u8>() && self.list_u8_as_bytes {
796 // Push back the item with the next state to continue processing list items
797 item.state = StackState::ProcessBytesItem { item_index: 0 };
798 writeln!(f)?;
799 write!(f, " ")?;
800
801 // TODO: write all the bytes here instead?
802 } else {
803 // Push back the item with the next state to continue processing list items
804 item.state = StackState::ProcessSeqItem {
805 item_index: 0,
806 kind: SeqKind::List,
807 };
808 self.write_punctuation(f, " [")?;
809 writeln!(f)?;
810 }
811
812 item.format_depth += 1;
813 item.type_depth = new_type_depth;
814 stack.push_back(item);
815
816 Ok(())
817 }
818
819 /// Format a scalar value
820 fn format_scalar(&self, value: Peek, f: &mut impl Write) -> fmt::Result {
821 // Generate a color for this shape
822 let mut hasher = DefaultHasher::new();
823 value.shape().id.hash(&mut hasher);
824 let hash = hasher.finish();
825 let color = self.color_generator.generate_color(hash);
826
827 // Display the value
828 struct DisplayWrapper<'mem, 'facet, 'shape>(&'mem Peek<'mem, 'facet, 'shape>);
829
830 impl fmt::Display for DisplayWrapper<'_, '_, '_> {
831 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
832 if self.0.shape().is_display() {
833 write!(f, "{}", self.0)?;
834 } else if self.0.shape().is_debug() {
835 write!(f, "{:?}", self.0)?;
836 } else {
837 write!(f, "{}", self.0.shape())?;
838 write!(f, "(⋯)")?;
839 }
840 Ok(())
841 }
842 }
843
844 // Apply color if needed and display
845 if self.use_colors {
846 // We need to use direct ANSI codes for RGB colors
847 write!(
848 f,
849 "\x1b[38;2;{};{};{}m{}",
850 color.r,
851 color.g,
852 color.b,
853 DisplayWrapper(&value)
854 )?;
855 write!(f, "\x1b[0m")?;
856 } else {
857 write!(f, "{}", DisplayWrapper(&value))?;
858 }
859
860 Ok(())
861 }
862
863 /// Write styled type name to formatter
864 fn write_type_name<W: fmt::Write>(&self, f: &mut W, peek: &Peek) -> fmt::Result {
865 struct TypeNameWriter<'mem, 'facet, 'shape>(&'mem Peek<'mem, 'facet, 'shape>);
866
867 impl core::fmt::Display for TypeNameWriter<'_, '_, '_> {
868 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
869 self.0.type_name(f, TypeNameOpts::infinite())
870 }
871 }
872 let type_name = TypeNameWriter(peek);
873
874 if self.use_colors {
875 write!(f, "\x1b[1m{}\x1b[0m", type_name) // bold
876 } else {
877 write!(f, "{}", type_name)
878 }
879 }
880
881 /// Style a type name and return it as a string
882 #[allow(dead_code)]
883 fn style_type_name(&self, peek: &Peek) -> String {
884 let mut result = String::new();
885 self.write_type_name(&mut result, peek).unwrap();
886 result
887 }
888
889 /// Write styled field name to formatter
890 fn write_field_name<W: fmt::Write>(&self, f: &mut W, name: &str) -> fmt::Result {
891 if self.use_colors {
892 // Use cyan color for field names (approximating original RGB color)
893 write!(f, "\x1b[36m{}\x1b[0m", name) // cyan
894 } else {
895 write!(f, "{}", name)
896 }
897 }
898
899 /// Write styled punctuation to formatter
900 fn write_punctuation<W: fmt::Write>(&self, f: &mut W, text: &str) -> fmt::Result {
901 if self.use_colors {
902 write!(f, "\x1b[2m{}\x1b[0m", text) // dim
903 } else {
904 write!(f, "{}", text)
905 }
906 }
907
908 /// Style punctuation and return it as a string
909 fn style_punctuation(&self, text: &str) -> String {
910 let mut result = String::new();
911 self.write_punctuation(&mut result, text).unwrap();
912 result
913 }
914
915 /// Write styled comment to formatter
916 fn write_comment<W: fmt::Write>(&self, f: &mut W, text: &str) -> fmt::Result {
917 if self.use_colors {
918 write!(f, "\x1b[2m{}\x1b[0m", text) // dim
919 } else {
920 write!(f, "{}", text)
921 }
922 }
923
924 /// Style a comment and return it as a string
925 fn style_comment(&self, text: &str) -> String {
926 let mut result = String::new();
927 self.write_comment(&mut result, text).unwrap();
928 result
929 }
930
931 /// Write styled redacted value to formatter
932 fn write_redacted<W: fmt::Write>(&self, f: &mut W, text: &str) -> fmt::Result {
933 if self.use_colors {
934 // Use bright red and bold for redacted values
935 if self.use_colors {
936 write!(f, "\x1b[91;1m{}\x1b[0m", text) // bright red + bold
937 } else {
938 write!(f, "{}", text)
939 }
940 } else {
941 write!(f, "{}", text)
942 }
943 }
944
945 /// Style a redacted value and return it as a string
946 #[allow(dead_code)]
947 fn style_redacted(&self, text: &str) -> String {
948 let mut result = String::new();
949 self.write_redacted(&mut result, text).unwrap();
950 result
951 }
952}
953
954#[cfg(test)]
955mod tests {
956 use super::*;
957
958 // Basic tests for the PrettyPrinter
959 #[test]
960 fn test_pretty_printer_default() {
961 let printer = PrettyPrinter::default();
962 assert_eq!(printer.indent_size, 2);
963 assert_eq!(printer.max_depth, None);
964 assert!(printer.use_colors);
965 }
966
967 #[test]
968 fn test_pretty_printer_with_methods() {
969 let printer = PrettyPrinter::new()
970 .with_indent_size(4)
971 .with_max_depth(3)
972 .with_colors(false);
973
974 assert_eq!(printer.indent_size, 4);
975 assert_eq!(printer.max_depth, Some(3));
976 assert!(!printer.use_colors);
977 }
978}