From 5ca71308a8f1ab61a3991991dd27bbb373a0d25f Mon Sep 17 00:00:00 2001 From: Profpatsch Date: Sun, 14 Feb 2021 16:53:16 +0100 Subject: [PATCH] feat(users/Profpatsch/netencode): add pretty printer Simple pretty printer for netencode values, as a rust library and an accompanying command line tool which takes netencode on stdin and prints the pretty version to stdout. Change-Id: I0a57c644985162bc08a9bf1ee78f7be278400199 Reviewed-on: https://cl.tvl.fyi/c/depot/+/2532 Tested-by: BuildkiteCI Reviewed-by: Profpatsch --- users/Profpatsch/netencode/default.nix | 32 ++++++ users/Profpatsch/netencode/pretty.rs | 140 +++++++++++++++++++++++++ 2 files changed, 172 insertions(+) create mode 100644 users/Profpatsch/netencode/pretty.rs diff --git a/users/Profpatsch/netencode/default.nix b/users/Profpatsch/netencode/default.nix index 3088ee66b..843408941 100644 --- a/users/Profpatsch/netencode/default.nix +++ b/users/Profpatsch/netencode/default.nix @@ -18,6 +18,36 @@ let gen = import ./gen.nix { inherit lib; }; + pretty-rs = imports.writers.rustSimpleLib { + name = "netencode-pretty"; + dependencies = [ + netencode-rs + ]; + } (builtins.readFile ./pretty.rs); + + pretty = depot.users.Profpatsch.writers.rustSimple { + name = "netencode-pretty"; + dependencies = [ + netencode-rs + pretty-rs + depot.users.Profpatsch.execline.exec-helpers + ]; + } '' + extern crate netencode; + extern crate netencode_pretty; + extern crate exec_helpers; + + fn main() { + let (_, prog) = exec_helpers::args_for_exec("eprint-stdin-netencode", 0); + let mut buf = vec![]; + let u = netencode::u_from_stdin_or_die_user_error("eprint-stdin-netencode", &mut buf); + match netencode_pretty::Pretty::from_u(u).print_multiline(&mut std::io::stdout()) { + Ok(()) => {}, + Err(err) => exec_helpers::die_temporary("eprint-stdin-netencode", format!("could not write to stdout: {}", err)) + } + } + ''; + netencode-mustache = imports.writers.rustSimple { name = "netencode_mustache"; dependencies = [ @@ -115,6 +145,8 @@ let in depot.nix.utils.drvTargets { inherit netencode-rs + pretty-rs + pretty netencode-mustache record-get record-splice-env diff --git a/users/Profpatsch/netencode/pretty.rs b/users/Profpatsch/netencode/pretty.rs new file mode 100644 index 000000000..8fec24a60 --- /dev/null +++ b/users/Profpatsch/netencode/pretty.rs @@ -0,0 +1,140 @@ +extern crate netencode; + +use netencode::{U, T, Tag}; + +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, + }, + Multi { + r#type: char, + length: String, + vals: Vec, + 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(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(&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(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::(&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::(&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::(&mut w, v, depth + 1, true)?; + write!(&mut w, "\n")?; + } + write!(&mut w, "{}{}", iandhalf, trailer) + } + }, + } + } +}