prettier_print/
prettier_printer.rs

1use rand::distributions::{Bernoulli, Distribution};
2use rand::rngs::SmallRng;
3use rand::{Rng, SeedableRng};
4use rand_distr::WeightedAliasIndex;
5use std::fmt::{Debug, Display, Formatter};
6use std::iter::repeat;
7
8pub type Seed = <SmallRng as SeedableRng>::Seed;
9
10/// Outputs a prettier-printed version of the `Debug` string of a variable.
11#[derive(Debug, Clone)]
12pub struct PrettierPrinter {
13    rng: SmallRng,
14}
15
16impl PrettierPrinter {
17    /// Instantiates `PrettierPrinter` with given seed. See also [`PrettierPrinter::default()`].
18    pub fn new_with_seed(seed: Seed) -> Self {
19        Self {
20            rng: SmallRng::from_seed(seed),
21        }
22    }
23
24    /// Generates a `Seed` from given `SmallRng`.
25    pub fn gen_seed(rng: &mut SmallRng) -> Seed {
26        let mut seed = Seed::default();
27        rng.fill(&mut seed);
28        seed
29    }
30
31    /// Pass your variable to this.
32    pub fn print<'a, T>(&mut self, inner: &'a T) -> PrettierPrintDisplayer<'a, T> {
33        PrettierPrintDisplayer {
34            seed: PrettierPrinter::gen_seed(&mut self.rng),
35            inner,
36        }
37    }
38}
39
40impl Default for PrettierPrinter {
41    /// Use this if you want to keep things simple. Calls `getrandom()` to get seed.
42    fn default() -> Self {
43        Self {
44            rng: SmallRng::from_entropy(),
45        }
46    }
47}
48
49/// Implements `Display` to output the prettier-printed debug string. Use `PrettierPrinter` to
50/// get a `PrettierPrintDisplayer`.
51#[derive(Debug, Clone)]
52pub struct PrettierPrintDisplayer<'a, T> {
53    seed: Seed,
54    inner: &'a T,
55}
56
57impl<T> PrettierPrintDisplayer<'_, T> {
58    pub fn output(seed: Seed, debug_str: &str) -> String {
59        const RAINBOW: char = '🌈';
60        const STARS: &[char] = &['⭐', '🌟', '☀', '🦀'];
61        let weights: Vec<u16> = vec![1500, 300, 100, 1];
62
63        let mut rng = SmallRng::from_seed(seed);
64        let mut line_rng = Bernoulli::from_ratio(3, 5)
65            .unwrap() // Can be unwrap_unchecked() when API is stabilized
66            .sample_iter(SmallRng::from_seed(PrettierPrinter::gen_seed(&mut rng)));
67
68        let mut star_rng = WeightedAliasIndex::new(weights.to_vec())
69            .unwrap()
70            .sample_iter(SmallRng::from_seed(PrettierPrinter::gen_seed(&mut rng)));
71
72        let width = debug_str
73            .lines()
74            .map(|s| s.len())
75            .max()
76            .map_or(0, |n| n + n / 10 + 2);
77
78        let mut result = RAINBOW.to_string();
79        result.extend(repeat(' ').take(width - 2));
80        result.push(RAINBOW);
81        result.push('\n');
82
83        for line in debug_str.lines() {
84            result.push(' ');
85
86            let leading_space_count = line.bytes().take_while(|&b| b == b' ').count();
87
88            // Leading space and content
89            if leading_space_count > 0 && line_rng.next().unwrap() {
90                // Add star to line
91                let star_index = rng.gen_range(0..leading_space_count);
92                result.extend(repeat(' ').take(star_index));
93
94                result.push(STARS[star_rng.next().unwrap()]);
95                result.extend(repeat(' ').take(leading_space_count - star_index - 1));
96
97                result += line.split_at(leading_space_count).1;
98            } else {
99                // No star
100                result.push_str(line);
101            }
102
103            // Trailing stars
104            if line_rng.next().unwrap() {
105                let star_index = rng.gen_range(0..width - line.len());
106                result.extend(repeat(' ').take(star_index));
107                result.push(STARS[star_rng.next().unwrap()]);
108            }
109
110            // Remove extra spaces
111            while result.ends_with(' ') {
112                result.pop();
113            }
114
115            result.push('\n');
116        }
117
118        result.push(RAINBOW);
119        result.extend(repeat(' ').take(width - 2));
120        result.push(RAINBOW);
121        result.push('\n');
122        result
123    }
124}
125
126impl<T> Display for PrettierPrintDisplayer<'_, T>
127where
128    T: Debug,
129{
130    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
131        write!(
132            f,
133            "{}",
134            PrettierPrintDisplayer::<T>::output(self.seed, &format!("{:#?}", self.inner))
135        )
136    }
137}
138
139#[cfg(test)]
140mod tests {
141    use super::*;
142    use std::collections::HashMap;
143
144    #[test]
145    fn prettier_printer() {
146        let seed = {
147            let mut seed = Seed::default();
148            seed[0] = 180; // Good seed for example
149            seed
150        };
151        {
152            let result = PrettierPrinter::new_with_seed(seed).print(&0).to_string();
153            assert!(result.starts_with("🌈 🌈\n"));
154            assert!(result.ends_with("🌈 🌈\n"));
155            assert!(result.contains(' '));
156        }
157        {
158            #[derive(Debug, Clone)]
159            struct Type {
160                a: String,
161                b: Vec<i32>,
162                c: HashMap<&'static str, &'static str>,
163            }
164
165            let input = Type {
166                a: "a".to_string(),
167                b: vec![0, 1],
168                c: {
169                    let mut map = HashMap::new();
170                    map.insert("So", "pretty");
171                    map
172                },
173            };
174
175            let displayer = PrettierPrinter::new_with_seed(seed).print(&input);
176
177            let result = displayer.to_string();
178            assert!(result.starts_with("🌈                         🌈\n"));
179            assert!(result.ends_with("🌈                         🌈\n"));
180            // Check if cloned Displayer outputs the same string
181            assert_eq!(result, displayer.clone().to_string());
182
183            println!("\n{:#?}\n\n", &input);
184            println!("{}\n", result);
185        }
186    }
187}