use std::{
fmt::Display,
io::{Result, Write},
};
use termcolor::{Buffer, BufferWriter, Color, ColorSpec, WriteColor};
use crate::{
row::Dimension as RowDimension,
style::{Style, StyleStruct},
utils::display_width,
};
pub struct CellStruct {
data: Vec<String>,
format: CellFormat,
style: StyleStruct,
}
impl CellStruct {
pub fn justify(mut self, justify: Justify) -> CellStruct {
self.format.justify = justify;
self
}
pub fn align(mut self, align: Align) -> CellStruct {
self.format.align = align;
self
}
pub fn padding(mut self, padding: Padding) -> CellStruct {
self.format.padding = padding;
self
}
fn color_spec(&self) -> ColorSpec {
self.style.color_spec()
}
pub(crate) fn required_dimension(&self) -> Dimension {
let height = self.data.len() + self.format.padding.top + self.format.padding.bottom;
let width = self
.data
.iter()
.map(|x| display_width(x))
.max()
.unwrap_or_default()
+ self.format.padding.left
+ self.format.padding.right;
Dimension { width, height }
}
pub(crate) fn buffers(
&self,
writer: &BufferWriter,
available_dimension: Dimension,
) -> Result<Vec<Buffer>> {
let required_dimension = self.required_dimension();
let mut buffers = Vec::with_capacity(available_dimension.height);
assert!(
available_dimension >= required_dimension,
"Available dimensions for a cell are smaller than required. Please create an issue in https://github.com/devashishdxt/cli-table"
);
let top_blank_lines = self.top_blank_lines(available_dimension, required_dimension);
for _ in 0..top_blank_lines {
buffers.push(self.buffer(writer, available_dimension, required_dimension, "")?);
}
for line in self.data.clone().iter() {
buffers.push(self.buffer(writer, available_dimension, required_dimension, line)?);
}
for _ in 0..(available_dimension.height - (self.data.len() + top_blank_lines)) {
buffers.push(self.buffer(writer, available_dimension, required_dimension, "")?);
}
Ok(buffers)
}
fn buffer(
&self,
writer: &BufferWriter,
available_dimension: Dimension,
required_dimension: Dimension,
data: &str,
) -> Result<Buffer> {
let empty_chars = match self.format.justify {
Justify::Left => self.format.padding.left,
Justify::Right => {
(available_dimension.width - required_dimension.width) + self.format.padding.left
}
Justify::Center => {
((available_dimension.width - required_dimension.width) / 2)
+ self.format.padding.left
}
};
let mut buffer = writer.buffer();
buffer.set_color(&self.color_spec())?;
for _ in 0..empty_chars {
write!(buffer, " ")?;
}
write!(buffer, "{}", data)?;
for _ in 0..(available_dimension.width - (display_width(data) + empty_chars)) {
write!(buffer, " ")?;
}
Ok(buffer)
}
fn top_blank_lines(
&self,
available_dimension: Dimension,
required_dimension: Dimension,
) -> usize {
match self.format.align {
Align::Top => self.format.padding.top,
Align::Bottom => {
(available_dimension.height - required_dimension.height) + self.format.padding.top
}
Align::Center => {
((available_dimension.height - required_dimension.height) / 2)
+ self.format.padding.top
}
}
}
}
pub trait Cell {
fn cell(self) -> CellStruct;
}
impl<T> Cell for T
where
T: Display,
{
fn cell(self) -> CellStruct {
let data = self.to_string().lines().map(ToString::to_string).collect();
CellStruct {
data,
format: Default::default(),
style: Default::default(),
}
}
}
impl Cell for CellStruct {
fn cell(self) -> CellStruct {
self
}
}
impl Style for CellStruct {
fn foreground_color(mut self, foreground_color: Option<Color>) -> Self {
self.style = self.style.foreground_color(foreground_color);
self
}
fn background_color(mut self, background_color: Option<Color>) -> Self {
self.style = self.style.background_color(background_color);
self
}
fn bold(mut self, bold: bool) -> Self {
self.style = self.style.bold(bold);
self
}
fn underline(mut self, underline: bool) -> Self {
self.style = self.style.underline(underline);
self
}
fn italic(mut self, italic: bool) -> Self {
self.style = self.style.italic(italic);
self
}
fn intense(mut self, intense: bool) -> Self {
self.style = self.style.intense(intense);
self
}
fn dimmed(mut self, dimmed: bool) -> Self {
self.style = self.style.dimmed(dimmed);
self
}
}
#[derive(Debug, Clone, Copy, Default)]
struct CellFormat {
justify: Justify,
align: Align,
padding: Padding,
}
#[derive(Debug, Clone, Copy)]
pub enum Justify {
Left,
Right,
Center,
}
impl Default for Justify {
fn default() -> Self {
Self::Left
}
}
#[derive(Debug, Clone, Copy)]
pub enum Align {
Top,
Bottom,
Center,
}
impl Default for Align {
fn default() -> Self {
Self::Top
}
}
#[derive(Debug, Clone, Copy, Default)]
pub struct Padding {
pub(crate) left: usize,
pub(crate) right: usize,
pub(crate) top: usize,
pub(crate) bottom: usize,
}
impl Padding {
pub fn builder() -> PaddingBuilder {
Default::default()
}
}
#[derive(Debug, Default)]
pub struct PaddingBuilder(Padding);
impl PaddingBuilder {
pub fn left(mut self, left_padding: usize) -> Self {
self.0.left = left_padding;
self
}
pub fn right(mut self, right_padding: usize) -> Self {
self.0.right = right_padding;
self
}
pub fn top(mut self, top_padding: usize) -> Self {
self.0.top = top_padding;
self
}
pub fn bottom(mut self, bottom_padding: usize) -> Self {
self.0.bottom = bottom_padding;
self
}
pub fn build(self) -> Padding {
self.0
}
}
#[derive(Debug, Copy, Clone, Ord, PartialOrd, Eq, PartialEq)]
pub(crate) struct Dimension {
pub(crate) width: usize,
pub(crate) height: usize,
}
impl From<RowDimension> for Vec<Dimension> {
fn from(row_dimension: RowDimension) -> Self {
let height = row_dimension.height;
row_dimension
.widths
.into_iter()
.map(|width| Dimension { width, height })
.collect()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_into_cell() {
let cell = "Hello".cell();
assert_eq!(1, cell.data.len());
assert_eq!("Hello", cell.data[0]);
}
}