text_table 0.0.4

A library to create formatted plain-text tables from arbitrary data.
use {Alignment, Row, RowOrSep, Table};
use std;

pub struct Renderer<'a> {
    table: &'a Table
}

impl<'a> Renderer<'a> {
    pub fn new(table: &'a Table) -> Renderer<'a> {
        Renderer {
            table: table
        }
    }

    pub fn write_to<T: std::io::Write>(&self, writer: &mut T) -> std::io::Result<()> {
        fn separator<T: std::io::Write>(writer: &mut T, widths: &[usize]) -> std::io::Result<()> {
            try!(write!(writer, "+"));
            for &width in widths.iter() {
                for _ in 0..width + 2 {
                    try!(write!(writer, "-"));
                }
                try!(write!(writer, "+"));
            }
            writeln!(writer, "")
        }

        fn row<T: std::io::Write>(writer: &mut T, row: &Row, widths: &[usize]) -> std::io::Result<()> {
            let cells = row.cells().iter().map(|cell| {
                cell.string().split('\n').collect::<Vec<_>>()
            }).collect::<Vec<_>>();

            let height = cells.iter().map(|lines| lines.len()).max().unwrap_or(1);

            for i in 0..height {
                try!(write!(writer, "|"));
                for j in 0..cells.len() {
                    let ref cell = row.cells()[j];
                    let width = widths[j];
                    let len = cells[j].get(i).map_or(0, |line| line.len());
                    let (left_padding, right_padding) = match *cell.alignment() {
                        Alignment::Left => (1, width - len + 1),
                        Alignment::Center => {
                            let diff = width - len;
                            if diff % 2 == 0 {
                                (diff / 2 + 1, diff / 2 + 1)
                            } else {
                                (diff / 2 + 1, diff / 2 + 2)
                            }
                        },
                        Alignment::Right => (width - len + 1, 1)
                    };

                    for _ in 0..left_padding {
                        try!(write!(writer, " "));
                    }

                    if let Some(line) = cells[j].get(i) {
                        try!(write!(writer, "{}", line));
                    }

                    for _ in 0..right_padding {
                        try!(write!(writer, " "));
                    }
                    try!(write!(writer, "|"));
                }
                try!(writeln!(writer, ""))
            }
            Ok(())
        }

        let columns = self.table.rows().iter().filter_map(|row_or_sep| {
            row_or_sep.row().map(|row| row.cells().len())
        }).max().unwrap_or(0);

        let widths = (0..columns).map(|i| {
            self.table.rows().iter().filter_map(|row_or_sep| {
                row_or_sep.row().map(|row| {
                    row.cells().get(i).map(|cell| {
                        cell.string().lines().map(|line| {
                            line.len()
                        }).max().unwrap_or(0)
                    }).unwrap_or(0)
                })
            }).max().unwrap_or(0)
        }).collect::<Vec<_>>();

        try!(separator(writer, &widths));

        for row_or_sep in self.table.rows().iter() {
            match *row_or_sep {
                RowOrSep::Row(ref r) => try!(row(writer, r, &widths)),
                RowOrSep::Sep => try!(separator(writer, &widths))
            }
        }

        separator(writer, &widths)
    }

    pub fn write_to_string(&self) -> String {
        let mut buf = Vec::new();
        self.write_to(&mut buf).unwrap();
        String::from_utf8(buf).unwrap()
    }
}