ppbert 0.2.4

Simple pretty printer for Erlang's External Term Format
Documentation
/home/vfoley/projects/ppbert
/mnt/font/Go Mono/12a/font
/mnt/font/Go Mono/16a/font
  0.0000000  59.8437500
w win win Newcol Kill Putall Dump Exit Dump /home/vfoley/projects/ppbert/acme.dump
c          0 New Cut Paste Snarf Sort Zerox Delcol 
c          1 New Cut Paste Snarf Sort Zerox Delcol Font
F          0           0           0           0   1.8216683        5388 
          1          66        5388           0           1 /home/vfoley/projects/ppbert/src/bertterm.rs Del Snarf | Look Font
use std::fmt::{self, Write};

use num::bigint;


pub const DEFAULT_INDENT_WIDTH: usize = 2;
pub const DEFAULT_MAX_TERMS_PER_LINE: usize = 4;


#[derive(Debug, PartialEq)]
pub enum BertTerm {
    Nil,
    Int(i32),
    BigInt(bigint::BigInt),
    Float(f64),
    Atom(String),
    Tuple(Vec<BertTerm>),
    List(Vec<BertTerm>),
    Map(Vec<BertTerm>, Vec<BertTerm>),
    String(Vec<u8>),
    Binary(Vec<u8>)
}

impl BertTerm {
    fn is_basic(&self) -> bool {
        match *self {
            BertTerm::Int(_)
            | BertTerm::BigInt(_)
            | BertTerm::Float(_)
            | BertTerm::Atom(_)
            | BertTerm::String(_)
            | BertTerm::Binary(_)
            | BertTerm::Nil => true,
            BertTerm::List(_)
            | BertTerm::Tuple(_)
            | BertTerm::Map(_, _) => false
        }
    }
}

impl fmt::Display for BertTerm {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        let pp = PrettyPrinter::new(self,
                                    DEFAULT_INDENT_WIDTH,
                                    DEFAULT_MAX_TERMS_PER_LINE);
        write!(f, "{}", pp)
    }
}


pub struct PrettyPrinter<'a> {
    term: &'a BertTerm,
    indent_width: usize,
    max_terms_per_line: usize
}

impl <'a> fmt::Display for PrettyPrinter<'a> {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        self.write_term(self.term, f, 0)
    }
}

impl <'a> PrettyPrinter<'a> {
    /// Creates a pretty printer for `term` where sub-terms
    /// are indented with a width of `indent_width` and a
    /// maximum of `max_terms_per_line` basic terms (i.e.,
    /// integers, floats, strings) can be printed per line.
    pub fn new(term: &'a BertTerm,
               indent_width: usize,
               max_terms_per_line: usize) -> Self {
        PrettyPrinter { term, indent_width, max_terms_per_line }
    }


    fn write_term(&self, term: &BertTerm, f: &mut fmt::Formatter, depth: usize) -> fmt::Result {
        match *term {
            BertTerm::Nil => f.write_str("[]"),
            BertTerm::Int(n) => write!(f, "{}", n),
            BertTerm::BigInt(ref n) => write!(f, "{}", n),
            BertTerm::Float(x) => write!(f, "{}", x),
            BertTerm::Atom(ref s) => f.write_str(s),
            BertTerm::String(ref bytes) => self.write_string(bytes, f, "\"", "\""),
            BertTerm::Binary(ref bytes) => self.write_string(bytes, f, "<<\"", "\">>"),
            BertTerm::List(ref terms) => self.write_collection(terms, f, depth, '[', ']'),
            BertTerm::Tuple(ref terms) => self.write_collection(terms, f, depth, '{', '}'),
            BertTerm::Map(ref keys, ref vals) => self.write_map(keys, vals, f, depth)
        }
    }


    fn write_string(&self,
                    bytes: &[u8],
                    f: &mut fmt::Formatter,
                    open: &str,
                    close: &str) -> fmt::Result {
        f.write_str(open)?;
        for &b in bytes {
            if is_printable(b) {
                f.write_char(b as char)?;
            } else {
                write!(f, "\\x{:02x}", b)?;
            }
        }
        f.write_str(close)
    }


    fn write_collection(&self,
                        terms: &[BertTerm],
                        f: &mut fmt::Formatter,
                        depth: usize,
                        open: char,
                        close: char) -> fmt::Result {
        let multi_line = !self.is_small_collection(terms);

        // Every element will have the same indentation,
        // so pre-compute it once.
        let prefix =
            if multi_line {
                self.indentation(depth+1)
            } else {
                String::new()
            };

        f.write_char(open)?;
        let mut comma = "";
        for t in terms {
            f.write_str(comma)?;
            f.write_str(&prefix)?;
            self.write_term(t, f, depth + 1)?;
            comma = ", ";
        }

        if multi_line {
            f.write_str(&self.indentation(depth))?;
        }

        f.write_char(close)
    }


    fn write_map(&self,
                 keys: &[BertTerm],
                 vals: &[BertTerm],
                 f: &mut fmt::Formatter,
                 depth: usize) -> fmt::Result {
        let multi_line =
            !self.is_small_collection(keys) || !self.is_small_collection(vals);
        let prefix =
            if multi_line {
                self.indentation(depth+1)
            } else {
                String::new()
            };

        f.write_str("#{")?;
        let mut comma = "";
        for i in 0 .. keys.len() {
            f.write_str(comma)?;
            f.write_str(&prefix)?;
            self.write_term(&keys[i], f, depth + 1)?;
            f.write_str(" => ")?;
            self.write_term(&vals[i], f, depth + 1)?;
            comma = ", ";
        }

        if multi_line {
            f.write_str(&self.indentation(depth))?;
        }
        f.write_str("}")
    }

    fn is_small_collection(&self, terms: &[BertTerm]) -> bool {
        terms.len() <= self.max_terms_per_line &&
            terms.iter().all(BertTerm::is_basic)
    }

    fn indentation(&self, depth: usize) -> String {
        ::std::iter::once('\n')
            .chain((0 .. depth * self.indent_width).map(|_| ' '))
            .collect()
    }
}



fn is_printable(b: u8) -> bool {
    b >= 0x20 && b <= 0x7e
}
f          1           2           0           0   1.8216683 
          2          54         113           1           0 /home/vfoley/projects/ppbert/ Del Snarf Get | Look win
f          1           3           0           0   5.3691275 
          3          55          57           1           0 /home/vfoley/projects/ppbert/src/ Del Snarf Get | Look 
F          1           2        1162        1162  13.8063279        1162 
          4          58        1162           0           1 /home/vfoley/projects/ppbert/src/+Errors Del Snarf | Look 
   Compiling ppbert v0.2.3 (file:///home/vfoley/projects/ppbert)
    Finished dev [unoptimized + debuginfo] target(s) in 6.19 secs
   Compiling ppbert v0.2.3 (file:///home/vfoley/projects/ppbert)
    Finished dev [unoptimized + debuginfo] target(s) in 1.19 secs
     Running /home/vfoley/projects/ppbert/target/debug/deps/parser_integration-627579b80b29e501

running 15 tests
test atom ... ok
test atom_utf8 ... ok
test binary ... ok
test bigint ... ok
test integer ... ok
test map ... ok
test list ... ok
test new_float ... ok
test nil ... ok
test old_float ... ok
test small_integer ... ok
test string ... ok
test tuple ... ok
test magic_number ... ok
test consume_all ... ok

test result: ok. 15 passed; 0 failed; 0 ignored; 0 measured

     Running /home/vfoley/projects/ppbert/target/debug/deps/ppbert-c4fa1d43729b2869

running 0 tests

test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured

     Running /home/vfoley/projects/ppbert/target/debug/deps/ppbert-68ce8e46f7510931

running 0 tests

test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured

   Doc-tests ppbert

running 0 tests

test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured

e          1           0           0           0  41.8024928 
          7          57          20           0           1 /home/vfoley/projects/ppbert/-t470 Del Snarf | Look  Send
/home/vfoley/projects/ppbert/
win
F          1           4          12          22  78.5234899          22 
          5          56          22           0           1 /home/vfoley/projects/ppbert/src/guide Del Snarf | Look 
cargo build
cargo test