use crate::{Cell, TableStyle};
use itertools::Itertools;
use std::fmt::{self, Write};
#[derive(Debug, Clone)]
pub struct Row<'data> {
pub(crate) cells: Vec<Cell<'data>>,
pub(crate) has_separator: bool,
}
impl<'data> Default for Row<'data> {
fn default() -> Self {
Self {
cells: vec![],
has_separator: true,
}
}
}
impl<'data> Row<'data> {
pub fn new() -> Self {
Default::default()
}
pub fn with_separator(mut self, has_separator: bool) -> Self {
self.set_has_separator(has_separator);
self
}
pub fn set_has_separator(&mut self, has_separator: bool) -> &mut Self {
self.has_separator = has_separator;
self
}
pub fn add_cell(&mut self, cell: impl Into<Cell<'data>>) -> &mut Self {
self.cells.push(cell.into());
self
}
pub fn with_cell(mut self, cell: impl Into<Cell<'data>>) -> Self {
self.add_cell(cell);
self
}
pub(crate) fn columns(&self) -> usize {
self.cells.iter().map(|cell| cell.col_span).sum()
}
pub(crate) fn layout(&self, column_widths: &[usize], border_width: usize) -> usize {
let mut max_lines = 0;
let mut idx = 0;
for cell in self.cells.iter() {
let mut width = (cell.col_span - 1) * border_width;
for w in column_widths[idx..idx + cell.col_span].iter().copied() {
width += w;
}
let num_lines = cell.layout(Some(width));
idx += cell.col_span;
max_lines = max_lines.max(num_lines);
}
max_lines
}
pub fn render_top_separator(
&self,
cell_widths: &[usize],
style: &TableStyle,
f: &mut fmt::Formatter,
) -> fmt::Result {
if !self.has_separator {
return Ok(());
}
f.write_char(style.top_left_corner)?;
let mut widths = cell_widths;
let mut width;
let mut cells = self.cells.iter();
if let Some(first_cell) = cells.next() {
(width, widths) = first_cell.width(style.border_width(), widths);
for _ in 0..width {
f.write_char(style.horizontal)?;
}
}
for cell in cells {
f.write_char(style.outer_top_horizontal)?;
(width, widths) = cell.width(style.border_width(), widths);
for _ in 0..width {
f.write_char(style.horizontal)?;
}
}
f.write_char(style.top_right_corner)?;
writeln!(f)
}
pub fn render_bottom_separator(
&self,
cell_widths: &[usize],
style: &TableStyle,
f: &mut fmt::Formatter,
) -> fmt::Result {
if !self.has_separator {
return Ok(());
}
f.write_char(style.bottom_left_corner)?;
let mut widths = cell_widths;
let mut width;
let mut cells = self.cells.iter();
if let Some(first_cell) = cells.next() {
(width, widths) = first_cell.width(style.border_width(), widths);
for _ in 0..width {
f.write_char(style.horizontal)?;
}
}
for cell in cells {
f.write_char(style.outer_bottom_horizontal)?;
(width, widths) = cell.width(style.border_width(), widths);
for _ in 0..width {
f.write_char(style.horizontal)?;
}
}
f.write_char(style.bottom_right_corner)?;
writeln!(f)
}
pub fn render_separator(
&self,
prev: &Row,
cell_widths: &[usize],
style: &TableStyle,
f: &mut fmt::Formatter,
) -> fmt::Result {
if !self.has_separator {
return Ok(());
}
f.write_char(style.outer_left_vertical)?;
let mut iter = cell_widths
.iter()
.copied()
.zip(self.iter_junctions(prev).skip(1))
.peekable();
while let Some((width, borders)) = iter.next() {
for _ in 0..width {
f.write_char(style.horizontal)?;
}
f.write_char(borders.joiner(style, iter.peek().is_none()))?;
}
writeln!(f)
}
pub(crate) fn render_content(
&self,
column_widths: &[usize],
num_lines: usize,
style: &TableStyle,
f: &mut fmt::Formatter,
) -> fmt::Result {
for line_num in 0..num_lines {
let mut width;
let mut widths = column_widths;
for cell in &self.cells {
f.write_char(style.vertical)?;
(width, widths) = cell.width(style.border_width(), widths);
cell.render_line(line_num, width, f)?;
}
f.write_char(style.vertical)?;
writeln!(f)?;
}
Ok(())
}
pub fn num_columns(&self) -> usize {
self.cells.iter().map(|x| x.col_span).sum()
}
fn iter_joins(&'data self) -> impl Iterator<Item = BorderTy> + 'data {
struct IterJoins<'a> {
inner: std::slice::Iter<'a, Cell<'a>>,
cols_remaining: usize,
}
impl<'a> Iterator for IterJoins<'a> {
type Item = BorderTy;
fn next(&mut self) -> Option<Self::Item> {
let out = Some(match &mut self.cols_remaining {
0 => BorderTy::Empty,
n @ 1 => {
*n = self.inner.next().map(|cell| cell.col_span).unwrap_or(0);
BorderTy::End
}
n => {
*n -= 1;
BorderTy::Middle
}
});
out
}
}
IterJoins {
inner: self.cells.iter(),
cols_remaining: 1,
}
}
fn iter_junctions(&'data self, prev: &'data Self) -> impl Iterator<Item = Borders> + 'data {
prev.iter_joins()
.zip(self.iter_joins())
.map(|(above, below)| {
let borders = Borders { above, below };
if borders == Borders::EMPTY {
None
} else {
Some(borders)
}
})
.while_some()
}
}
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
struct Borders {
above: BorderTy,
below: BorderTy,
}
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
enum BorderTy {
Empty,
Middle,
End,
}
impl Borders {
const EMPTY: Borders = Borders {
above: BorderTy::Empty,
below: BorderTy::Empty,
};
fn joiner(&self, style: &TableStyle, final_end: bool) -> char {
use BorderTy::*;
match (self.above, self.below) {
(Empty, Empty) => unreachable!(),
(Empty, Middle) | (Middle, Empty) | (Middle, Middle) => style.horizontal,
(Empty, End) | (Middle, End) => {
if final_end {
style.top_right_corner
} else {
style.outer_top_horizontal
}
}
(End, Empty) | (End, Middle) => {
if final_end {
style.bottom_right_corner
} else {
style.outer_bottom_horizontal
}
}
(End, End) => {
if final_end {
style.outer_right_vertical
} else {
style.intersection
}
}
}
}
}
pub trait IntoRow {
fn headers(&self) -> Row;
fn into_row(&self) -> Row;
}
macro_rules! impl_row_for_tuple {
() => {};
(($first_label:expr, $first_ty:ident) $(,($rest_label:expr, $rest_ty:ident))*) => {
impl<$first_ty, $($rest_ty,)*> IntoRow for ($first_ty, $($rest_ty),*)
where $first_ty: ::std::fmt::Display,
$(
$rest_ty: ::std::fmt::Display,
)*
{
fn headers(&self) -> Row {
let mut row = Row::default();
row.add_cell(stringify!($first_ty));
$(
row.add_cell(stringify!($rest_ty));
)*
row
}
fn into_row(&self) -> Row {
#[allow(non_snake_case)]
let (
ref $first_ty,
$(
ref $rest_ty
),*
) = &self;
let mut row = Row::default();
row.add_cell($first_ty.to_string());
$(
row.add_cell($rest_ty.to_string());
)*
row
}
}
impl_row_for_tuple!($(($rest_label, $rest_ty)),*);
};
}
impl_row_for_tuple!(
("_0", D0),
("_1", D1),
("_2", D2),
("_3", D3),
("_4", D4),
("_5", D5),
("_6", D6),
("_7", D7),
("_8", D8),
("_9", D9)
);