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}