elegance 0.4.0

A pretty-printing library for Rust with a focus on speed and compactness.
Documentation
use std::{
    cell::{Cell, RefCell},
    ffi::OsString,
    rc::Rc,
};

use elegance::{
    render::{Io, Render},
    Printer,
};

#[track_caller]
fn test_printer(
    f: impl FnOnce(&mut Printer) -> Result<(), std::convert::Infallible>,
    expected: &str,
) {
    let mut pp = Printer::new(String::new(), 40);
    f(&mut pp).unwrap();
    assert_eq!(pp.finish().unwrap(), expected);
}

#[test]
fn test_text() {
    test_printer(|pp| pp.text("Hello, world!"), "Hello, world!");
}

#[test]
fn test_space() {
    test_printer(|pp| pp.space(), " ");
}

#[test]
fn test_spaces() {
    test_printer(|pp| pp.spaces(5), "     ");
}

#[test]
fn test_hard_break() {
    test_printer(|pp| pp.hard_break(), "\n");
}

#[test]
fn test_zero_break() {
    test_printer(|pp| pp.zero_break(), "");
}

#[test]
fn test_group_horizontal() {
    test_printer(
        |pp| {
            pp.cgroup(2, |pp| {
                pp.text("Hello,")?;
                pp.space()?;
                pp.text("world!")
            })
        },
        "Hello, world!",
    );
}

#[test]
fn test_group_vertical() {
    test_printer(
        |pp| {
            pp.cgroup(2, |pp| {
                pp.text("Hello,")?;
                pp.hard_break()?;
                pp.text("world!")
            })
        },
        "Hello,\n  world!",
    );
}

#[test]
fn test_igroup() {
    test_printer(
        |pp| {
            pp.igroup(2, |pp| {
                for _ in 0..40 {
                    pp.text("x")?;
                    pp.zero_break()?;
                }
                pp.text("x")?;
                pp.space()?;
                pp.cgroup(0, |pp| {
                    pp.text("Hello,")?;
                    pp.hard_break()?;
                    pp.text("world!")
                })
            })
        },
        &("x".repeat(40) + "\n  x Hello,\n  world!"),
    );
}

#[test]
fn test_text_overflow() {
    test_printer(
        |pp| {
            pp.text_owned("x".repeat(40))?;
            pp.zero_break()?;
            pp.text("Hello,world!")
        },
        &("x".repeat(40) + "\nHello,world!"),
    );
}

#[test]
fn test_multiple_newlines() {
    test_printer(
        |pp| {
            pp.cgroup(0, |pp| {
                pp.zero_break()?;
                pp.space()?;
                pp.hard_break()?;
                pp.hard_break()
            })
        },
        "\n\n\n\n",
    );
}

#[test]
fn test_break_indent() {
    test_printer(
        |pp| {
            pp.cgroup(2, |pp| {
                pp.zero_break()?;
                pp.text("Hello,")?;
                pp.scan_break(40, 2)?;
                pp.text("world!")
            })
        },
        "\n  Hello,\n    world!",
    );
}

#[test]
fn test_text_len_forces_wrap() {
    let mut pp = Printer::new(String::new(), 3);
    pp.cgroup(2, |pp| {
        pp.text("ab")?;
        pp.space()?;
        pp.text("cd")
    })
    .unwrap();

    assert_eq!(pp.finish().unwrap(), "ab\n  cd");
}

#[test]
fn test_osstring_renderer_spaces() {
    let mut pp = Printer::new(OsString::new(), 16);
    pp.text("hi")
        .and_then(|_| pp.space())
        .and_then(|_| pp.spaces(2))
        .and_then(|_| pp.text("there"))
        .unwrap();
    assert_eq!(pp.finish().unwrap(), OsString::from("hi   there"));
}

#[test]
fn test_io_renderer_writes_all() {
    let mut pp = Printer::new(Io(Vec::new()), 8);
    pp.text("foo")
        .and_then(|_| pp.space())
        .and_then(|_| pp.text("bar"))
        .unwrap();

    let Io(buf) = pp.finish().unwrap();
    assert_eq!(buf, b"foo bar");
}

#[test]
fn test_long_text_wraps_by_length() {
    let mut pp = Printer::new(String::new(), 5);
    pp.text("123456")
        .and_then(|_| pp.space())
        .and_then(|_| pp.text("x"))
        .unwrap();

    assert_eq!(pp.finish().unwrap(), "123456\nx");
}

#[derive(Clone)]
struct SharedRender {
    buf: Rc<RefCell<String>>,
    writes: Rc<Cell<usize>>,
}

impl Render for SharedRender {
    type Error = std::convert::Infallible;

    fn write_str(&mut self, s: &str) -> Result<(), Self::Error> {
        self.writes.set(self.writes.get() + 1);
        self.buf.borrow_mut().push_str(s);
        Ok(())
    }

    fn write_spaces(&mut self, n: usize) -> Result<(), Self::Error> {
        self.writes.set(self.writes.get() + 1);
        self.buf.borrow_mut().extend(std::iter::repeat(' ').take(n));
        Ok(())
    }
}

struct NoZeroSpacesRender(String);

impl Render for NoZeroSpacesRender {
    type Error = std::convert::Infallible;

    fn write_str(&mut self, s: &str) -> Result<(), Self::Error> {
        self.0.push_str(s);
        Ok(())
    }

    fn write_spaces(&mut self, n: usize) -> Result<(), Self::Error> {
        if n == 0 {
            panic!("zero-space write should be skipped");
        }
        self.0.extend(std::iter::repeat(' ').take(n));
        Ok(())
    }
}

#[test]
fn test_prune_flushes_on_overflow() {
    let buf = Rc::new(RefCell::new(String::new()));
    let writes = Rc::new(Cell::new(0));
    let render = SharedRender {
        buf: buf.clone(),
        writes: writes.clone(),
    };

    let mut pp = Printer::new(render, 5);
    pp.text("123456").unwrap();

    assert!(writes.get() > 0, "prune should flush immediately");
    let renderer = pp.finish().unwrap();
    assert!(Rc::ptr_eq(&renderer.buf, &buf));
    assert_eq!(buf.borrow().as_str(), "123456");
}

#[test]
fn test_prune_not_triggered_on_exact_fit() {
    let mut pp = Printer::new(String::new(), 5);
    pp.cgroup(0, |pp| {
        pp.text("ab")?;
        pp.space()?;
        pp.text("cd")
    })
    .unwrap();

    assert_eq!(pp.finish().unwrap(), "ab cd");
}

#[cfg(feature = "unicode-width")]
#[test]
fn test_unicode_width_wraps() {
    let mut pp = Printer::new(String::new(), 3);
    pp.cgroup(0, |pp| {
        pp.text("")?;
        pp.space()?;
        pp.text("")
    })
    .unwrap();

    assert_eq!(pp.finish().unwrap(), "\n");
}

#[cfg(not(feature = "unicode-width"))]
#[test]
fn test_ascii_width_fallback_wraps() {
    let mut pp = Printer::new(String::new(), 3);
    pp.cgroup(0, |pp| {
        pp.text("abcd")?;
        pp.space()?;
        pp.text("e")
    })
    .unwrap();

    assert_eq!(pp.finish().unwrap(), "abcd\ne");
}

#[test]
fn test_no_zero_space_write() {
    let mut pp = Printer::new(NoZeroSpacesRender(String::new()), 10);
    pp.text("abc").unwrap();

    let NoZeroSpacesRender(out) = pp.finish().unwrap();
    assert_eq!(out, "abc");
}