extern crate netencode; use netencode::{Tag, T, U}; pub enum Pretty { Single { r#type: char, length: String, val: String, trailer: char, }, Tag { r#type: char, length: String, key: String, inner: char, val: Box<Pretty>, }, Multi { r#type: char, length: String, vals: Vec<Pretty>, trailer: char, }, } impl Pretty { pub fn from_u<'a>(u: U<'a>) -> Pretty { match u { U::Unit => Self::scalar('u', "", ""), U::N1(b) => Self::scalar('n', "1:", if b { "1" } else { "0" }), U::N3(n) => Self::scalar('n', "3:", n), U::N6(n) => Self::scalar('n', "6:", n), U::N7(n) => Self::scalar('n', "7:", n), U::I3(i) => Self::scalar('i', "3:", i), U::I6(i) => Self::scalar('i', "6:", i), U::I7(i) => Self::scalar('i', "7:", i), U::Text(s) => Pretty::Single { r#type: 't', length: format!("{}:", s.len()), val: s.to_string(), trailer: ',', }, U::Binary(s) => Pretty::Single { r#type: 'b', length: format!("{}:", s.len()), // For pretty printing we want the string to be visible obviously. // Instead of not supporting binary, let’s use lossy conversion. val: String::from_utf8_lossy(s).into_owned(), trailer: ',', }, U::Sum(Tag { tag, val }) => Self::pretty_tag(tag, Self::from_u(*val)), U::Record(m) => Pretty::Multi { r#type: '{', // TODO: we are losing the size here, should we recompute it? Keep it? length: String::from(""), vals: m .into_iter() .map(|(k, v)| Self::pretty_tag(k, Self::from_u(v))) .collect(), trailer: '}', }, U::List(l) => Pretty::Multi { r#type: '[', // TODO: we are losing the size here, should we recompute it? Keep it? length: String::from(""), vals: l.into_iter().map(|v| Self::from_u(v)).collect(), trailer: ']', }, } } fn scalar<D>(r#type: char, length: &str, d: D) -> Pretty where D: std::fmt::Display, { Pretty::Single { r#type, length: length.to_string(), val: format!("{}", d), trailer: ',', } } fn pretty_tag(tag: &str, val: Pretty) -> Pretty { Pretty::Tag { r#type: '<', length: format!("{}:", tag.len()), key: tag.to_string(), inner: '|', val: Box::new(val), } } pub fn print_multiline<W>(&self, mut w: &mut W) -> std::io::Result<()> where W: std::io::Write, { Self::go(&mut w, self, 0, true); write!(w, "\n") } fn go<W>(mut w: &mut W, p: &Pretty, depth: usize, is_newline: bool) -> std::io::Result<()> where W: std::io::Write, { const full: usize = 4; const half: usize = 2; let i = &vec![b' '; depth * full]; let iandhalf = &vec![b' '; depth * full + half]; let (i, iandhalf) = unsafe { ( std::str::from_utf8_unchecked(i), std::str::from_utf8_unchecked(iandhalf), ) }; if is_newline { write!(&mut w, "{}", i); } match p { Pretty::Single { r#type, length, val, trailer, } => write!(&mut w, "{} {}{}", r#type, val, trailer), Pretty::Tag { r#type, length, key, inner, val, } => { write!(&mut w, "{} {} {}", r#type, key, inner)?; Self::go::<W>(&mut w, val, depth, false) } // if the length is 0 or 1, we print on one line, // only if there’s more than one element we split the resulting value. // we never break lines on arbitrary column sizes, since that is just silly. Pretty::Multi { r#type, length, vals, trailer, } => match vals.len() { 0 => write!(&mut w, "{} {}", r#type, trailer), 1 => { write!(&mut w, "{} ", r#type); Self::go::<W>(&mut w, &vals[0], depth, false)?; write!(&mut w, "{}", trailer) } more => { write!(&mut w, "\n{}{} \n", iandhalf, r#type)?; for v in vals { Self::go::<W>(&mut w, v, depth + 1, true)?; write!(&mut w, "\n")?; } write!(&mut w, "{}{}", iandhalf, trailer) } }, } } }