about summary refs log tree commit diff
diff options
context:
space:
mode:
authorProfpatsch <mail@profpatsch.de>2021-02-14T15·53+0100
committerProfpatsch <mail@profpatsch.de>2021-04-23T20·45+0000
commit5ca71308a8f1ab61a3991991dd27bbb373a0d25f (patch)
tree49e162af9ccb417561c6e22ab4b82d7871bf0086
parent9d7c3ee877837070c51760cb621bc409c6d0f6a7 (diff)
feat(users/Profpatsch/netencode): add pretty printer r/2544
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 <mail@profpatsch.de>
-rw-r--r--users/Profpatsch/netencode/default.nix32
-rw-r--r--users/Profpatsch/netencode/pretty.rs140
2 files changed, 172 insertions, 0 deletions
diff --git a/users/Profpatsch/netencode/default.nix b/users/Profpatsch/netencode/default.nix
index 3088ee66bf..843408941d 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 0000000000..8fec24a60e
--- /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<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)
+                }
+            },
+        }
+    }
+}