shapely_pretty/
printer.rs

1//! Pretty printer implementation for Shapely types
2
3use std::{
4    collections::HashSet,
5    fmt::{self, Write},
6    hash::{DefaultHasher, Hash, Hasher},
7    str,
8};
9
10use shapely_core::{FieldFlags, Innards, Scalar, ScalarContents, Shape, ShapeDesc, Shapely};
11
12use crate::{
13    ansi,
14    color::{self, ColorGenerator},
15};
16
17/// A formatter for pretty-printing Shapely types
18pub struct PrettyPrinter {
19    indent_size: usize,
20    max_depth: Option<usize>,
21    color_generator: ColorGenerator,
22    use_colors: bool,
23}
24
25impl Default for PrettyPrinter {
26    fn default() -> Self {
27        Self {
28            indent_size: 2,
29            max_depth: None,
30            color_generator: ColorGenerator::default(),
31            use_colors: true,
32        }
33    }
34}
35
36impl PrettyPrinter {
37    /// Create a new PrettyPrinter with default settings
38    pub fn new() -> Self {
39        Self::default()
40    }
41
42    /// Set the indentation size
43    pub fn with_indent_size(mut self, size: usize) -> Self {
44        self.indent_size = size;
45        self
46    }
47
48    /// Set the maximum depth for recursive printing
49    pub fn with_max_depth(mut self, depth: usize) -> Self {
50        self.max_depth = Some(depth);
51        self
52    }
53
54    /// Set the color generator
55    pub fn with_color_generator(mut self, generator: ColorGenerator) -> Self {
56        self.color_generator = generator;
57        self
58    }
59
60    /// Enable or disable colors
61    pub fn with_colors(mut self, use_colors: bool) -> Self {
62        self.use_colors = use_colors;
63        self
64    }
65
66    /// Pretty-print a value that implements Shapely
67    pub fn print<T: Shapely>(&self, value: &T) {
68        let shape_desc = T::shape_desc();
69        let ptr = value as *const T as *mut u8;
70
71        let mut output = String::new();
72        self.format_value(ptr, shape_desc, &mut output, 0, &mut HashSet::new())
73            .expect("Formatting failed");
74
75        print!("{}", output);
76    }
77
78    /// Format a value to a string
79    pub fn format<T: Shapely>(&self, value: &T) -> String {
80        let shape_desc = T::shape_desc();
81        let ptr = value as *const T as *mut u8;
82
83        let mut output = String::new();
84        self.format_value(ptr, shape_desc, &mut output, 0, &mut HashSet::new())
85            .expect("Formatting failed");
86
87        output
88    }
89
90    /// Format a value to a formatter
91    pub fn format_to<T: Shapely>(&self, value: &T, f: &mut fmt::Formatter<'_>) -> fmt::Result {
92        let shape_desc = T::shape_desc();
93        let ptr = value as *const T as *mut u8;
94
95        self.format_value(ptr, shape_desc, f, 0, &mut HashSet::new())
96    }
97
98    /// Internal method to format a value at a specific memory address
99    pub(crate) fn format_value(
100        &self,
101        ptr: *mut u8,
102        shape_desc: ShapeDesc,
103        f: &mut impl Write,
104        depth: usize,
105        visited: &mut HashSet<*mut u8>,
106    ) -> fmt::Result {
107        // Check if we've reached the maximum depth
108        if let Some(max_depth) = self.max_depth {
109            if depth > max_depth {
110                self.write_punctuation(f, "[")?;
111                write!(f, "...")?;
112                return Ok(());
113            }
114        }
115
116        // Get the shape
117        let shape = shape_desc.get();
118
119        // Generate a color for this shape
120        let mut hasher = DefaultHasher::new();
121        shape.typeid.hash(&mut hasher);
122        let hash = hasher.finish();
123        let color = self.color_generator.generate_color(hash);
124
125        // Format based on the shape's innards
126        match &shape.innards {
127            Innards::Scalar(scalar) => self.format_scalar(ptr, *scalar, f, color),
128            Innards::Struct { fields }
129            | Innards::TupleStruct { fields }
130            | Innards::Tuple { fields } => {
131                self.format_struct(ptr, shape, fields, f, depth, visited)
132            }
133            Innards::Map {
134                vtable: _,
135                value_shape,
136            } => self.format_hashmap(ptr, shape, *value_shape, f, depth, visited),
137            Innards::List {
138                vtable: _,
139                item_shape,
140            } => self.format_array(ptr, shape, *item_shape, f, depth, visited),
141            Innards::Transparent(inner_shape) => {
142                self.format_transparent(ptr, shape, *inner_shape, f, depth, visited)
143            }
144            Innards::Enum { variants, repr: _ } => {
145                self.format_enum(ptr, shape, variants, f, depth, visited)
146            }
147        }
148    }
149
150    /// Format a scalar value
151    fn format_scalar(
152        &self,
153        ptr: *mut u8,
154        scalar: Scalar,
155        f: &mut impl Write,
156        color: color::RGB,
157    ) -> fmt::Result {
158        // Use Scalar::get_contents for safe access to the scalar value
159        let contents = unsafe { scalar.get_contents(ptr) };
160
161        // Apply color if needed
162        if self.use_colors {
163            color.write_fg(f)?;
164        }
165
166        // Format the content
167        match contents {
168            ScalarContents::String(s) => {
169                write!(f, "\"")?;
170                for c in s.escape_debug() {
171                    write!(f, "{}", c)?;
172                }
173                write!(f, "\"")?;
174            }
175            ScalarContents::Bytes(b) => {
176                write!(f, "b\"")?;
177                for &byte in b.iter().take(64) {
178                    write!(f, "\\x{:02x}", byte)?;
179                }
180                if b.len() > 64 {
181                    write!(f, "...")?;
182                }
183                write!(f, "\"")?;
184            }
185            ScalarContents::I8(v) => write!(f, "{}", v)?,
186            ScalarContents::I16(v) => write!(f, "{}", v)?,
187            ScalarContents::I32(v) => write!(f, "{}", v)?,
188            ScalarContents::I64(v) => write!(f, "{}", v)?,
189            ScalarContents::I128(v) => write!(f, "{}", v)?,
190            ScalarContents::U8(v) => write!(f, "{}", v)?,
191            ScalarContents::U16(v) => write!(f, "{}", v)?,
192            ScalarContents::U32(v) => write!(f, "{}", v)?,
193            ScalarContents::U64(v) => write!(f, "{}", v)?,
194            ScalarContents::U128(v) => write!(f, "{}", v)?,
195            ScalarContents::F32(v) => write!(f, "{}", v)?,
196            ScalarContents::F64(v) => write!(f, "{}", v)?,
197            ScalarContents::Boolean(v) => write!(f, "{}", v)?,
198            ScalarContents::Nothing => write!(f, "()")?,
199            ScalarContents::Unknown => write!(f, "<unknown scalar>")?,
200            // Handle future variants that might be added to the non-exhaustive enum
201            _ => write!(f, "<unknown scalar type>")?,
202        }
203
204        // Reset color if needed
205        if self.use_colors {
206            ansi::write_reset(f)?;
207        }
208
209        Ok(())
210    }
211
212    /// Format a struct
213    fn format_struct(
214        &self,
215        ptr: *mut u8,
216        shape: Shape,
217        fields: &'static [shapely_core::Field],
218        f: &mut impl Write,
219        depth: usize,
220        visited: &mut HashSet<*mut u8>,
221    ) -> fmt::Result {
222        // Check for cycles
223        if !visited.insert(ptr) {
224            self.write_type_name(f, &shape.to_string())?;
225            self.write_punctuation(f, " { ")?;
226            self.write_comment(f, "/* cycle detected */")?;
227            self.write_punctuation(f, " }")?;
228            return Ok(());
229        }
230
231        // Print the struct name
232        self.write_type_name(f, &shape.to_string())?;
233        self.write_punctuation(f, " {")?;
234
235        if fields.is_empty() {
236            self.write_punctuation(f, " }")?;
237            visited.remove(&ptr);
238            return Ok(());
239        }
240
241        writeln!(f)?;
242
243        // Print each field
244        for field in fields {
245            // Indent
246            write!(f, "{:width$}", "", width = (depth + 1) * self.indent_size)?;
247
248            // Field name
249            write!(f, "{}: ", self.style_field_name(field.name))?;
250
251            // Check if field is sensitive
252            if field.flags.contains(FieldFlags::SENSITIVE) {
253                // For sensitive fields, display [REDACTED] instead of the actual value
254                write!(f, "{}", self.style_redacted("[REDACTED]"))?;
255            } else {
256                // Field value - compute the field address
257                let field_ptr = unsafe { ptr.add(field.offset) };
258                self.format_value(field_ptr, field.shape, f, depth + 1, visited)?;
259            }
260
261            writeln!(f, "{}", self.style_punctuation(","))?;
262        }
263
264        // Closing brace with proper indentation
265        write!(
266            f,
267            "{:width$}{}",
268            "",
269            self.style_punctuation("}"),
270            width = depth * self.indent_size
271        )?;
272
273        // Remove from visited set when we're done with this struct
274        visited.remove(&ptr);
275
276        Ok(())
277    }
278
279    /// Format a HashMap
280    fn format_hashmap(
281        &self,
282        _ptr: *mut u8,
283        shape: Shape,
284        _value_shape: ShapeDesc,
285        f: &mut impl Write,
286        depth: usize,
287        _visited: &mut HashSet<*mut u8>,
288    ) -> fmt::Result {
289        // In a real implementation, we would need to iterate over the HashMap
290        // For now, we'll just print a placeholder
291
292        write!(f, "{}", self.style_type_name(&shape.to_string()))?;
293        write!(f, "{}", self.style_punctuation(" {"))?;
294        writeln!(f)?;
295
296        // Indent
297        write!(f, "{:width$}", "", width = (depth + 1) * self.indent_size)?;
298        write!(f, "{}", self.style_comment("/* HashMap contents */"))?;
299        writeln!(f)?;
300
301        // Closing brace with proper indentation
302        write!(
303            f,
304            "{:width$}{}",
305            "",
306            self.style_punctuation("}"),
307            width = depth * self.indent_size
308        )
309    }
310
311    /// Format an array
312    fn format_array(
313        &self,
314        _ptr: *mut u8,
315        shape: Shape,
316        _elem_shape: ShapeDesc,
317        f: &mut impl Write,
318        depth: usize,
319        _visited: &mut HashSet<*mut u8>,
320    ) -> fmt::Result {
321        // In a real implementation, we would need to iterate over the array
322        // For now, we'll just print a placeholder
323
324        write!(f, "{}", self.style_type_name(&shape.to_string()))?;
325        write!(f, "{}", self.style_punctuation(" ["))?;
326        writeln!(f)?;
327
328        // Indent
329        write!(f, "{:width$}", "", width = (depth + 1) * self.indent_size)?;
330        write!(f, "{}", self.style_comment("/* Array contents */"))?;
331        writeln!(f)?;
332
333        // Closing bracket with proper indentation
334        write!(
335            f,
336            "{:width$}{}",
337            "",
338            self.style_punctuation("]"),
339            width = depth * self.indent_size
340        )
341    }
342
343    /// Format a transparent wrapper
344    fn format_transparent(
345        &self,
346        ptr: *mut u8,
347        shape: Shape,
348        inner_shape: ShapeDesc,
349        f: &mut impl Write,
350        depth: usize,
351        visited: &mut HashSet<*mut u8>,
352    ) -> fmt::Result {
353        // Print the wrapper type name
354        write!(f, "{}", self.style_type_name(&shape.to_string()))?;
355        write!(f, "{}", self.style_punctuation("("))?;
356
357        // Format the inner value
358        self.format_value(ptr, inner_shape, f, depth, visited)?;
359
360        // Closing parenthesis
361        write!(f, "{}", self.style_punctuation(")"))
362    }
363
364    /// Formats an enum value
365    fn format_enum(
366        &self,
367        _ptr: *mut u8,
368        shape: Shape,
369        _variants: &'static [shapely_core::Variant],
370        f: &mut impl Write,
371        depth: usize,
372        _visited: &mut HashSet<*mut u8>,
373    ) -> fmt::Result {
374        // Basic enum rendering for now - just show the type name with {} placeholder
375        // since we don't have runtime variant/field access implemented yet
376        self.write_type_name(f, &format!("{}", shape))?;
377        writeln!(f, " {{")?;
378        if let Some(max_depth) = self.max_depth {
379            if depth >= max_depth {
380                writeln!(
381                    f,
382                    "{}{}",
383                    " ".repeat(self.indent_size),
384                    self.style_comment("// Enum contents omitted due to depth limit")
385                )?;
386                writeln!(f, "}}")?;
387                return Ok(());
388            }
389        }
390        writeln!(
391            f,
392            "{}{}",
393            " ".repeat(self.indent_size),
394            self.style_comment(
395                format!(
396                    "// Enum with {} variants (variant access not yet implemented)",
397                    _variants.len()
398                )
399                .as_str()
400            )
401        )?;
402        writeln!(f, "}}")?;
403        Ok(())
404    }
405
406    /// Write styled type name to formatter
407    fn write_type_name<W: fmt::Write>(&self, f: &mut W, name: &str) -> fmt::Result {
408        if self.use_colors {
409            ansi::write_bold(f)?;
410            write!(f, "{}", name)?;
411            ansi::write_reset(f)
412        } else {
413            write!(f, "{}", name)
414        }
415    }
416
417    /// Style a type name and return it as a string
418    fn style_type_name(&self, name: &str) -> String {
419        let mut result = String::new();
420        self.write_type_name(&mut result, name).unwrap();
421        result
422    }
423
424    /// Write styled field name to formatter
425    fn write_field_name<W: fmt::Write>(&self, f: &mut W, name: &str) -> fmt::Result {
426        if self.use_colors {
427            ansi::write_rgb(f, 114, 160, 193)?;
428            write!(f, "{}", name)?;
429            ansi::write_reset(f)
430        } else {
431            write!(f, "{}", name)
432        }
433    }
434
435    /// Style a field name and return it as a string
436    fn style_field_name(&self, name: &str) -> String {
437        let mut result = String::new();
438        self.write_field_name(&mut result, name).unwrap();
439        result
440    }
441
442    /// Write styled punctuation to formatter
443    fn write_punctuation<W: fmt::Write>(&self, f: &mut W, text: &str) -> fmt::Result {
444        if self.use_colors {
445            ansi::write_dim(f)?;
446            write!(f, "{}", text)?;
447            ansi::write_reset(f)
448        } else {
449            write!(f, "{}", text)
450        }
451    }
452
453    /// Style punctuation and return it as a string
454    fn style_punctuation(&self, text: &str) -> String {
455        let mut result = String::new();
456        self.write_punctuation(&mut result, text).unwrap();
457        result
458    }
459
460    /// Write styled comment to formatter
461    fn write_comment<W: fmt::Write>(&self, f: &mut W, text: &str) -> fmt::Result {
462        if self.use_colors {
463            ansi::write_dim(f)?;
464            write!(f, "{}", text)?;
465            ansi::write_reset(f)
466        } else {
467            write!(f, "{}", text)
468        }
469    }
470
471    /// Style a comment and return it as a string
472    fn style_comment(&self, text: &str) -> String {
473        let mut result = String::new();
474        self.write_comment(&mut result, text).unwrap();
475        result
476    }
477
478    /// Write styled redacted value to formatter
479    fn write_redacted<W: fmt::Write>(&self, f: &mut W, text: &str) -> fmt::Result {
480        if self.use_colors {
481            ansi::write_rgb(f, 224, 49, 49)?; // Use bright red for redacted values
482            ansi::write_bold(f)?;
483            write!(f, "{}", text)?;
484            ansi::write_reset(f)
485        } else {
486            write!(f, "{}", text)
487        }
488    }
489
490    /// Style a redacted value and return it as a string
491    fn style_redacted(&self, text: &str) -> String {
492        let mut result = String::new();
493        self.write_redacted(&mut result, text).unwrap();
494        result
495    }
496}
497
498#[cfg(test)]
499mod tests {
500    use super::*;
501
502    // Basic tests for the PrettyPrinter
503    #[test]
504    fn test_pretty_printer_default() {
505        let printer = PrettyPrinter::default();
506        assert_eq!(printer.indent_size, 2);
507        assert_eq!(printer.max_depth, None);
508        assert!(printer.use_colors);
509    }
510
511    #[test]
512    fn test_pretty_printer_with_methods() {
513        let printer = PrettyPrinter::new()
514            .with_indent_size(4)
515            .with_max_depth(3)
516            .with_colors(false);
517
518        assert_eq!(printer.indent_size, 4);
519        assert_eq!(printer.max_depth, Some(3));
520        assert!(!printer.use_colors);
521    }
522
523    #[test]
524    fn test_style_methods() {
525        let printer_with_colors = PrettyPrinter::new().with_colors(true);
526        let printer_without_colors = PrettyPrinter::new().with_colors(false);
527
528        // With colors
529        assert_eq!(
530            printer_with_colors.style_type_name("Test"),
531            format!("{}Test{}", ansi::BOLD, ansi::RESET)
532        );
533
534        // Without colors
535        assert_eq!(printer_without_colors.style_type_name("Test"), "Test");
536    }
537}