use rand::distributions::{Bernoulli, Distribution};
use rand::rngs::SmallRng;
use rand::{Rng, SeedableRng};
use rand_distr::WeightedAliasIndex;
use std::fmt::{Debug, Display, Formatter};
use std::iter::repeat;
pub type Seed = <SmallRng as SeedableRng>::Seed;
#[derive(Debug, Clone)]
pub struct PrettierPrinter {
rng: SmallRng,
}
impl PrettierPrinter {
pub fn new_with_seed(seed: Seed) -> Self {
Self {
rng: SmallRng::from_seed(seed),
}
}
pub fn gen_seed(rng: &mut SmallRng) -> Seed {
let mut seed = Seed::default();
rng.fill(&mut seed);
seed
}
pub fn print<'a, T>(&mut self, inner: &'a T) -> PrettierPrintDisplayer<'a, T> {
PrettierPrintDisplayer {
seed: PrettierPrinter::gen_seed(&mut self.rng),
inner,
}
}
}
impl Default for PrettierPrinter {
fn default() -> Self {
Self {
rng: SmallRng::from_entropy(),
}
}
}
#[derive(Debug, Clone)]
pub struct PrettierPrintDisplayer<'a, T> {
seed: Seed,
inner: &'a T,
}
impl<T> PrettierPrintDisplayer<'_, T> {
pub fn output(seed: Seed, debug_str: &str) -> String {
const RAINBOW: char = '🌈';
const STARS: &[char] = &['⭐', '🌟', '☀', '🦀'];
let weights: Vec<u16> = vec![1500, 300, 100, 1];
let mut rng = SmallRng::from_seed(seed);
let mut line_rng = Bernoulli::from_ratio(3, 5)
.unwrap() .sample_iter(SmallRng::from_seed(PrettierPrinter::gen_seed(&mut rng)));
let mut star_rng = WeightedAliasIndex::new(weights.to_vec())
.unwrap()
.sample_iter(SmallRng::from_seed(PrettierPrinter::gen_seed(&mut rng)));
let width = debug_str
.lines()
.map(|s| s.len())
.max()
.map_or(0, |n| n + n / 10 + 2);
let mut result = RAINBOW.to_string();
result.extend(repeat(' ').take(width - 2));
result.push(RAINBOW);
result.push('\n');
for line in debug_str.lines() {
result.push(' ');
let leading_space_count = line.bytes().take_while(|&b| b == b' ').count();
if leading_space_count > 0 && line_rng.next().unwrap() {
let star_index = rng.gen_range(0..leading_space_count);
result.extend(repeat(' ').take(star_index));
result.push(STARS[star_rng.next().unwrap()]);
result.extend(repeat(' ').take(leading_space_count - star_index - 1));
result += line.split_at(leading_space_count).1;
} else {
result.push_str(line);
}
if line_rng.next().unwrap() {
let star_index = rng.gen_range(0..width - line.len());
result.extend(repeat(' ').take(star_index));
result.push(STARS[star_rng.next().unwrap()]);
}
while result.ends_with(' ') {
result.pop();
}
result.push('\n');
}
result.push(RAINBOW);
result.extend(repeat(' ').take(width - 2));
result.push(RAINBOW);
result.push('\n');
result
}
}
impl<T> Display for PrettierPrintDisplayer<'_, T>
where
T: Debug,
{
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(
f,
"{}",
PrettierPrintDisplayer::<T>::output(self.seed, &format!("{:#?}", self.inner))
)
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::collections::HashMap;
#[test]
fn prettier_printer() {
let seed = {
let mut seed = Seed::default();
seed[0] = 180; seed
};
{
let result = PrettierPrinter::new_with_seed(seed).print(&0).to_string();
assert!(result.starts_with("🌈 🌈\n"));
assert!(result.ends_with("🌈 🌈\n"));
assert!(result.contains(' '));
}
{
#[derive(Debug, Clone)]
struct Type {
a: String,
b: Vec<i32>,
c: HashMap<&'static str, &'static str>,
}
let input = Type {
a: "a".to_string(),
b: vec![0, 1],
c: {
let mut map = HashMap::new();
map.insert("So", "pretty");
map
},
};
let displayer = PrettierPrinter::new_with_seed(seed).print(&input);
let result = displayer.to_string();
assert!(result.starts_with("🌈 🌈\n"));
assert!(result.ends_with("🌈 🌈\n"));
assert_eq!(result, displayer.clone().to_string());
println!("\n{:#?}\n\n", &input);
println!("{}\n", result);
}
}
}