davis 0.1.1

An CLI client for MPD.
use crate::ansi::{is_dumb_terminal, FormattedString};
use std::collections::HashMap;
use std::fmt;

pub struct Row<'a> {
    fields: Vec<FormattedString<'a>>,
}

impl<'a> Row<'a> {
    pub fn new(fields: Vec<FormattedString<'a>>) -> Row<'a> {
        Row { fields }
    }
}

pub struct Table<'a> {
    pub rows: &'a [Row<'a>],
}

impl<'a> fmt::Display for Table<'a> {
    fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
        let widths = self
            .rows
            .iter()
            .flat_map(|Row { fields }| fields.iter().enumerate().map(|(i, f)| (i, f.string.len())))
            .fold(HashMap::<usize, usize>::new(), |mut m, (i, len)| {
                m.entry(i)
                    .and_modify(|cur| *cur = (*cur).max(len))
                    .or_insert(len);
                m
            });

        for (i, Row { fields }) in self.rows.iter().enumerate() {
            if i != 0 {
                writeln!(formatter)?;
            }
            for (i, f) in fields.iter().enumerate() {
                if i + 1 == fields.len() {
                    write!(formatter, "{}", f)?;
                } else if is_dumb_terminal() {
                    write!(formatter, "{}:", f)?;
                } else {
                    write!(formatter, "{:width$}", f, width = widths[&i] + 1)?;
                }
            }
        }
        Ok(())
    }
}

#[cfg(test)]
mod test {
    use super::*;
    use crate::ansi::Style;

    #[test]
    fn basic_functionality() {
        let key1 = FormattedString::new("long_key");
        let val1 = FormattedString::new("val");
        let key2 = FormattedString::new("key");
        let val2 = FormattedString::new("val");
        let rows = [Row::new(vec![key1, val1]), Row::new(vec![key2, val2])];
        let table = Table { rows: &rows };
        let result = format!("{:100}", table);
        let expected = "long_key val\n\
                        key      val";
        assert_eq!(&*result, expected);
    }

    #[test]
    fn basic_functionality_with_formatting() {
        let key1 = FormattedString::new("long_key").style(Style::Faint);
        let val1 = FormattedString::new("val").style(Style::Bold);
        let key2 = FormattedString::new("key").style(Style::Faint);
        let val2 = FormattedString::new("val").style(Style::Bold);
        let rows = [Row::new(vec![key1, val1]), Row::new(vec![key2, val2])];
        let table = Table { rows: &rows };
        let result = format!("{}", table);
        let expected = "\x1B[2mlong_key \x1B[0m\x1B[1mval\x1B[0m\n\
                        \x1B[2mkey      \x1B[0m\x1B[1mval\x1B[0m";
        assert_eq!(&*result, expected);
    }
}