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