1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
use alloc::borrow::Cow;
use alloc::string::String;
use alloc::vec::Vec;
use core::borrow::Borrow;
use crate::table::Table;
use crate::renderer::{pad, wrap, Renderer, NEWLINE, RenderHint};
use crate::style::{HAlign, Style};

#[derive(Default)]
pub struct Markdown();

impl Renderer for Markdown {
    type Output = String;

    fn render_with_hints(&self, table: &Table, _: &[RenderHint]) -> Self::Output {
        assert!(!table.is_empty(), "table cannot be empty");
        let col_widths = table.col_widths(self);
        let mut buf = String::new();

        // print the header
        print_row(self, table, &col_widths, 0, &mut buf);

        // print the line between the header and the body
        print_header_format(table, &col_widths, &mut buf);

        // print the body
        for row in 1..table.num_rows() {
            print_row(self, table, &col_widths, row, &mut buf);
        }

        buf
    }
}

fn print_header_format(table: &Table, col_widths: &[usize], buf: &mut String) {
    buf.push('|');
    for (col, &width) in col_widths.iter().enumerate() {
        let col = table.col(col);
        let styles = col.blended_styles();
        let alignment = HAlign::resolve_or_default(&styles);
        let alignment = alignment.borrow();
        match alignment {
            HAlign::Left | HAlign::Right => {
                // the smallest format is `-:` or `:-`; i.e., no fewer than 2 characters wide
                let width = usize::max(2, width);
                buf.push_str(&pad(":", '-', width, alignment));
            }
            HAlign::Centred => {
                // the smallest format is `:-:`; i.e., no fewer than 3 characters wide
                let width = usize::max(3, width);
                buf.push(':');
                (2..width).for_each(|_| buf.push('-'));
                buf.push(':');
            }
        }
        buf.push('|');
    }
    buf.push_str(NEWLINE);
}

fn print_row(renderer: &Markdown, table: &Table, col_widths: &[usize], row: usize, buf: &mut String) {
    // first pass: wrap individual cell data over multiple rows
    let cell_lines = (0..col_widths.len())
        .into_iter()
        .map(|col| {
            let cell = table.cell(col, row);
            let data = cell.as_ref().map_or(Cow::Borrowed(""), |cell| cell.data().render(renderer));
            wrap(&data, col_widths[col])
        })
        .collect::<Vec<_>>();

    // second pass: obtain the combined styles of each cell
    let cell_styles = (0..col_widths.len())
        .into_iter()
        .map(|col| {
            let cell = table.cell(col, row);
            cell.blended_styles()
        })
        .collect::<Vec<_>>();

    // third pass: render each line in the row
    let max_lines = cell_lines.iter().map(Vec::len).max().unwrap();
    for line in 0..max_lines {
        buf.push('|');
        for col in 0..col_widths.len() {
            let line = cell_lines[col].get(line).map_or("", |line| &line[..]);
            let styles = &cell_styles[col];
            let alignment = HAlign::resolve_or_default(styles);
            let line = pad(line, ' ', col_widths[col], &alignment);
            buf.push_str(&line);
            buf.push('|');
        }
        buf.push_str(NEWLINE);
    }
}