1use std::{
4 collections::HashSet,
5 fmt::{self, Write},
6 hash::{DefaultHasher, Hash, Hasher},
7 str,
8};
9
10use shapely_core::{Innards, Scalar, ScalarContents, Shape, ShapeDesc, Shapely};
11
12use crate::{
13 ansi,
14 color::{self, ColorGenerator},
15};
16
17pub 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 pub fn new() -> Self {
39 Self::default()
40 }
41
42 pub fn with_indent_size(mut self, size: usize) -> Self {
44 self.indent_size = size;
45 self
46 }
47
48 pub fn with_max_depth(mut self, depth: usize) -> Self {
50 self.max_depth = Some(depth);
51 self
52 }
53
54 pub fn with_color_generator(mut self, generator: ColorGenerator) -> Self {
56 self.color_generator = generator;
57 self
58 }
59
60 pub fn with_colors(mut self, use_colors: bool) -> Self {
62 self.use_colors = use_colors;
63 self
64 }
65
66 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 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 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 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 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 let shape = shape_desc.get();
118
119 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 match &shape.innards {
127 Innards::Scalar(scalar) => self.format_scalar(ptr, *scalar, f, color),
128 Innards::Struct { fields } => self.format_struct(ptr, shape, fields, f, depth, visited),
129 Innards::HashMap { value_shape } => {
130 self.format_hashmap(ptr, shape, *value_shape, f, depth, visited)
131 }
132 Innards::Array(elem_shape) => {
133 self.format_array(ptr, shape, *elem_shape, f, depth, visited)
134 }
135 Innards::Transparent(inner_shape) => {
136 self.format_transparent(ptr, shape, *inner_shape, f, depth, visited)
137 }
138 }
139 }
140
141 fn format_scalar(
143 &self,
144 ptr: *mut u8,
145 scalar: Scalar,
146 f: &mut impl Write,
147 color: color::RGB,
148 ) -> fmt::Result {
149 let contents = unsafe { scalar.get_contents(ptr) };
151
152 if self.use_colors {
154 color.write_fg(f)?;
155 }
156
157 match contents {
159 ScalarContents::String(s) => {
160 write!(f, "\"")?;
161 for c in s.escape_debug() {
162 write!(f, "{}", c)?;
163 }
164 write!(f, "\"")?;
165 }
166 ScalarContents::Bytes(b) => {
167 write!(f, "b\"")?;
168 for &byte in b.iter().take(64) {
169 write!(f, "\\x{:02x}", byte)?;
170 }
171 if b.len() > 64 {
172 write!(f, "...")?;
173 }
174 write!(f, "\"")?;
175 }
176 ScalarContents::I8(v) => write!(f, "{}", v)?,
177 ScalarContents::I16(v) => write!(f, "{}", v)?,
178 ScalarContents::I32(v) => write!(f, "{}", v)?,
179 ScalarContents::I64(v) => write!(f, "{}", v)?,
180 ScalarContents::I128(v) => write!(f, "{}", v)?,
181 ScalarContents::U8(v) => write!(f, "{}", v)?,
182 ScalarContents::U16(v) => write!(f, "{}", v)?,
183 ScalarContents::U32(v) => write!(f, "{}", v)?,
184 ScalarContents::U64(v) => write!(f, "{}", v)?,
185 ScalarContents::U128(v) => write!(f, "{}", v)?,
186 ScalarContents::F32(v) => write!(f, "{}", v)?,
187 ScalarContents::F64(v) => write!(f, "{}", v)?,
188 ScalarContents::Boolean(v) => write!(f, "{}", v)?,
189 ScalarContents::Nothing => write!(f, "()")?,
190 ScalarContents::Unknown => write!(f, "<unknown scalar>")?,
191 _ => write!(f, "<unknown scalar type>")?,
193 }
194
195 if self.use_colors {
197 ansi::write_reset(f)?;
198 }
199
200 Ok(())
201 }
202
203 fn format_struct(
205 &self,
206 ptr: *mut u8,
207 shape: Shape,
208 fields: &'static [shapely_core::Field],
209 f: &mut impl Write,
210 depth: usize,
211 visited: &mut HashSet<*mut u8>,
212 ) -> fmt::Result {
213 if !visited.insert(ptr) {
215 self.write_type_name(f, &shape.to_string())?;
216 self.write_punctuation(f, " { ")?;
217 self.write_comment(f, "/* cycle detected */")?;
218 self.write_punctuation(f, " }")?;
219 return Ok(());
220 }
221
222 self.write_type_name(f, &shape.to_string())?;
224 self.write_punctuation(f, " {")?;
225
226 if fields.is_empty() {
227 self.write_punctuation(f, " }")?;
228 visited.remove(&ptr);
229 return Ok(());
230 }
231
232 writeln!(f)?;
233
234 for field in fields {
236 write!(f, "{:width$}", "", width = (depth + 1) * self.indent_size)?;
238
239 write!(f, "{}: ", self.style_field_name(field.name))?;
241
242 if field.flags.is_sensitive() {
244 write!(f, "{}", self.style_redacted("[REDACTED]"))?;
246 } else {
247 let field_ptr = unsafe { ptr.add(field.offset) };
249 self.format_value(field_ptr, field.shape, f, depth + 1, visited)?;
250 }
251
252 writeln!(f, "{}", self.style_punctuation(","))?;
253 }
254
255 write!(
257 f,
258 "{:width$}{}",
259 "",
260 self.style_punctuation("}"),
261 width = depth * self.indent_size
262 )?;
263
264 visited.remove(&ptr);
266
267 Ok(())
268 }
269
270 fn format_hashmap(
272 &self,
273 _ptr: *mut u8,
274 shape: Shape,
275 _value_shape: ShapeDesc,
276 f: &mut impl Write,
277 depth: usize,
278 _visited: &mut HashSet<*mut u8>,
279 ) -> fmt::Result {
280 write!(f, "{}", self.style_type_name(&shape.to_string()))?;
284 write!(f, "{}", self.style_punctuation(" {"))?;
285 writeln!(f)?;
286
287 write!(f, "{:width$}", "", width = (depth + 1) * self.indent_size)?;
289 write!(f, "{}", self.style_comment("/* HashMap contents */"))?;
290 writeln!(f)?;
291
292 write!(
294 f,
295 "{:width$}{}",
296 "",
297 self.style_punctuation("}"),
298 width = depth * self.indent_size
299 )
300 }
301
302 fn format_array(
304 &self,
305 _ptr: *mut u8,
306 shape: Shape,
307 _elem_shape: ShapeDesc,
308 f: &mut impl Write,
309 depth: usize,
310 _visited: &mut HashSet<*mut u8>,
311 ) -> fmt::Result {
312 write!(f, "{}", self.style_type_name(&shape.to_string()))?;
316 write!(f, "{}", self.style_punctuation(" ["))?;
317 writeln!(f)?;
318
319 write!(f, "{:width$}", "", width = (depth + 1) * self.indent_size)?;
321 write!(f, "{}", self.style_comment("/* Array contents */"))?;
322 writeln!(f)?;
323
324 write!(
326 f,
327 "{:width$}{}",
328 "",
329 self.style_punctuation("]"),
330 width = depth * self.indent_size
331 )
332 }
333
334 fn format_transparent(
336 &self,
337 ptr: *mut u8,
338 shape: Shape,
339 inner_shape: ShapeDesc,
340 f: &mut impl Write,
341 depth: usize,
342 visited: &mut HashSet<*mut u8>,
343 ) -> fmt::Result {
344 write!(f, "{}", self.style_type_name(&shape.to_string()))?;
346 write!(f, "{}", self.style_punctuation("("))?;
347
348 self.format_value(ptr, inner_shape, f, depth, visited)?;
350
351 write!(f, "{}", self.style_punctuation(")"))
353 }
354
355 fn write_type_name<W: fmt::Write>(&self, f: &mut W, name: &str) -> fmt::Result {
357 if self.use_colors {
358 ansi::write_bold(f)?;
359 write!(f, "{}", name)?;
360 ansi::write_reset(f)
361 } else {
362 write!(f, "{}", name)
363 }
364 }
365
366 fn style_type_name(&self, name: &str) -> String {
368 let mut result = String::new();
369 self.write_type_name(&mut result, name).unwrap();
370 result
371 }
372
373 fn write_field_name<W: fmt::Write>(&self, f: &mut W, name: &str) -> fmt::Result {
375 if self.use_colors {
376 ansi::write_rgb(f, 114, 160, 193)?;
377 write!(f, "{}", name)?;
378 ansi::write_reset(f)
379 } else {
380 write!(f, "{}", name)
381 }
382 }
383
384 fn style_field_name(&self, name: &str) -> String {
386 let mut result = String::new();
387 self.write_field_name(&mut result, name).unwrap();
388 result
389 }
390
391 fn write_punctuation<W: fmt::Write>(&self, f: &mut W, text: &str) -> fmt::Result {
393 if self.use_colors {
394 ansi::write_dim(f)?;
395 write!(f, "{}", text)?;
396 ansi::write_reset(f)
397 } else {
398 write!(f, "{}", text)
399 }
400 }
401
402 fn style_punctuation(&self, text: &str) -> String {
404 let mut result = String::new();
405 self.write_punctuation(&mut result, text).unwrap();
406 result
407 }
408
409 fn write_comment<W: fmt::Write>(&self, f: &mut W, text: &str) -> fmt::Result {
411 if self.use_colors {
412 ansi::write_dim(f)?;
413 write!(f, "{}", text)?;
414 ansi::write_reset(f)
415 } else {
416 write!(f, "{}", text)
417 }
418 }
419
420 fn style_comment(&self, text: &str) -> String {
422 let mut result = String::new();
423 self.write_comment(&mut result, text).unwrap();
424 result
425 }
426
427 fn write_redacted<W: fmt::Write>(&self, f: &mut W, text: &str) -> fmt::Result {
429 if self.use_colors {
430 ansi::write_rgb(f, 224, 49, 49)?; ansi::write_bold(f)?;
432 write!(f, "{}", text)?;
433 ansi::write_reset(f)
434 } else {
435 write!(f, "{}", text)
436 }
437 }
438
439 fn style_redacted(&self, text: &str) -> String {
441 let mut result = String::new();
442 self.write_redacted(&mut result, text).unwrap();
443 result
444 }
445}
446
447#[cfg(test)]
448mod tests {
449 use super::*;
450
451 #[test]
453 fn test_pretty_printer_default() {
454 let printer = PrettyPrinter::default();
455 assert_eq!(printer.indent_size, 2);
456 assert_eq!(printer.max_depth, None);
457 assert!(printer.use_colors);
458 }
459
460 #[test]
461 fn test_pretty_printer_with_methods() {
462 let printer = PrettyPrinter::new()
463 .with_indent_size(4)
464 .with_max_depth(3)
465 .with_colors(false);
466
467 assert_eq!(printer.indent_size, 4);
468 assert_eq!(printer.max_depth, Some(3));
469 assert!(!printer.use_colors);
470 }
471
472 #[test]
473 fn test_style_methods() {
474 let printer_with_colors = PrettyPrinter::new().with_colors(true);
475 let printer_without_colors = PrettyPrinter::new().with_colors(false);
476
477 assert_eq!(
479 printer_with_colors.style_type_name("Test"),
480 format!("{}Test{}", ansi::BOLD, ansi::RESET)
481 );
482
483 assert_eq!(printer_without_colors.style_type_name("Test"), "Test");
485 }
486}