use core::iter::FromIterator;
use std::{fmt, io};
use crate::{
grid::{
colors::NoColors,
config::{AlignmentHorizontal, CompactConfig, Indent, Sides, SpannedConfig},
dimension::{CompactGridDimension, DimensionValue, StaticDimension, ZeroDimension},
records::{
into_records::{BufRecords, LimitColumns, LimitRows, TruncateContent},
IntoRecords, IterRecords,
},
IterGrid,
},
settings::{Style, TableOption},
};
use crate::util::utf8_writer::UTF8Writer;
#[derive(Debug, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)]
pub struct IterTable<I> {
records: I,
cfg: CompactConfig,
table: Settings,
}
#[derive(Debug, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)]
struct Settings {
sniff: usize,
count_columns: Option<usize>,
count_rows: Option<usize>,
width: Option<usize>,
height: Option<usize>,
}
impl<I> IterTable<I> {
pub fn new(iter: I) -> Self
where
I: IntoRecords,
{
Self {
records: iter,
cfg: create_config(),
table: Settings {
sniff: 1000,
count_columns: None,
count_rows: None,
height: None,
width: None,
},
}
}
pub fn with<O>(&mut self, option: O) -> &mut Self
where
O: TableOption<I, CompactConfig, ZeroDimension>,
{
let mut dims = ZeroDimension::new();
option.change(&mut self.records, &mut self.cfg, &mut dims);
self
}
pub fn columns(&mut self, count_columns: usize) -> &mut Self {
self.table.count_columns = Some(count_columns);
self
}
pub fn rows(&mut self, count_rows: usize) -> &mut Self {
self.table.count_rows = Some(count_rows);
self
}
pub fn sniff(&mut self, count: usize) -> &mut Self {
self.table.sniff = count;
self
}
pub fn height(&mut self, size: usize) -> &mut Self {
self.table.height = Some(size);
self
}
pub fn width(&mut self, size: usize) -> &mut Self {
self.table.width = Some(size);
self
}
#[allow(clippy::inherent_to_string)]
pub fn to_string(self) -> String
where
I: IntoRecords,
I::Cell: AsRef<str>,
{
let mut buf = String::new();
self.fmt(&mut buf)
.expect("according to a doc is safe to fmt() a string");
buf
}
pub fn build<W>(self, writer: W) -> io::Result<()>
where
W: io::Write,
I: IntoRecords,
I::Cell: AsRef<str>,
{
let writer = UTF8Writer::new(writer);
self.fmt(writer).map_err(io::Error::other)
}
pub fn fmt<W>(self, writer: W) -> fmt::Result
where
W: fmt::Write,
I: IntoRecords,
I::Cell: AsRef<str>,
{
build_grid(writer, self.records, self.cfg, self.table)
}
}
impl<T> FromIterator<T> for IterTable<Vec<Vec<T::Item>>>
where
T: IntoIterator,
T::Item: AsRef<str>,
{
fn from_iter<I: IntoIterator<Item = T>>(iter: I) -> Self {
let data = iter
.into_iter()
.map(|row| row.into_iter().collect())
.collect();
Self::new(data)
}
}
fn build_grid<W, I>(f: W, iter: I, cfg: CompactConfig, opts: Settings) -> fmt::Result
where
W: fmt::Write,
I: IntoRecords,
I::Cell: AsRef<str>,
{
let width_config = opts.width.is_some() && opts.count_columns.is_some();
if width_config {
build_table_with_static_dims(f, iter, cfg, opts)
} else if opts.width.is_some() {
build_table_sniffing_with_width(f, iter, cfg, opts)
} else {
build_table_sniffing(f, iter, cfg, opts)
}
}
fn build_table_with_static_dims<W, I>(
f: W,
iter: I,
cfg: CompactConfig,
opts: Settings,
) -> fmt::Result
where
W: fmt::Write,
I: IntoRecords,
I::Cell: AsRef<str>,
{
let count_columns = opts.count_columns.unwrap();
let width = opts.width.unwrap();
let height = opts.height.unwrap_or(1);
let pad = cfg.get_padding();
let contentw = DimensionValue::Exact(width);
let width = DimensionValue::Exact(width + pad.left.size + pad.right.size);
let height = DimensionValue::Exact(height + pad.top.size + pad.bottom.size);
let dims = StaticDimension::new(width, height);
let cfg = SpannedConfig::from(cfg);
match opts.count_rows {
Some(limit) => {
let records = LimitRows::new(iter, limit);
let records = build_records(records, contentw, count_columns, Some(limit));
IterGrid::new(records, cfg, dims, NoColors).build(f)
}
None => {
let records = build_records(iter, contentw, count_columns, None);
IterGrid::new(records, cfg, dims, NoColors).build(f)
}
}
}
fn build_table_sniffing<W, I>(f: W, iter: I, cfg: CompactConfig, opts: Settings) -> fmt::Result
where
W: fmt::Write,
I: IntoRecords,
I::Cell: AsRef<str>,
{
let records = BufRecords::new(iter, opts.sniff);
let count_columns = get_count_columns(&opts, records.as_slice());
let (mut width, height) = {
let records = LimitColumns::new(records.as_slice(), count_columns);
let records = IterRecords::new(records, count_columns, None);
CompactGridDimension::dimension(records, &cfg)
};
let padding = cfg.get_padding();
let pad = padding.left.size + padding.right.size;
let padv = padding.top.size + padding.bottom.size;
if opts.sniff == 0 {
width = std::iter::repeat_n(pad, count_columns).collect::<Vec<_>>();
}
let content_width = DimensionValue::List(width.iter().map(|i| i.saturating_sub(pad)).collect());
let dims_width = DimensionValue::List(width);
let height_exact = opts.height.unwrap_or(1) + padv;
let mut dims_height = DimensionValue::Partial(height, height_exact);
if opts.height.is_some() {
dims_height = DimensionValue::Exact(height_exact);
}
let dims = StaticDimension::new(dims_width, dims_height);
let cfg = SpannedConfig::from(cfg);
match opts.count_rows {
Some(limit) => {
let records = LimitRows::new(records, limit);
let records = build_records(records, content_width, count_columns, Some(limit));
IterGrid::new(records, cfg, dims, NoColors).build(f)
}
None => {
let records = build_records(records, content_width, count_columns, None);
IterGrid::new(records, cfg, dims, NoColors).build(f)
}
}
}
fn build_table_sniffing_with_width<W, I>(
f: W,
iter: I,
cfg: CompactConfig,
opts: Settings,
) -> fmt::Result
where
W: fmt::Write,
I: IntoRecords,
I::Cell: AsRef<str>,
{
let records = BufRecords::new(iter, opts.sniff);
let count_columns = get_count_columns(&opts, records.as_slice());
let width = opts.width.unwrap();
let contentw = DimensionValue::Exact(width);
let padding = cfg.get_padding();
let pad = padding.left.size + padding.right.size;
let padv = padding.top.size + padding.bottom.size;
let height = opts.height.unwrap_or(1) + padv;
let dimsh = DimensionValue::Exact(height);
let dimsw = DimensionValue::Exact(width + pad);
let dims = StaticDimension::new(dimsw, dimsh);
let cfg = SpannedConfig::from(cfg);
match opts.count_rows {
Some(limit) => {
let records = LimitRows::new(records, limit);
let records = build_records(records, contentw, count_columns, Some(limit));
IterGrid::new(records, cfg, dims, NoColors).build(f)
}
None => {
let records = build_records(records, contentw, count_columns, None);
IterGrid::new(records, cfg, dims, NoColors).build(f)
}
}
}
fn get_count_columns<T>(opts: &Settings, buf: &[Vec<T>]) -> usize {
match opts.count_columns {
Some(size) => size,
None => buf.iter().map(|row| row.len()).max().unwrap_or(0),
}
}
const fn create_config() -> CompactConfig {
CompactConfig::new()
.set_padding(Sides::new(
Indent::spaced(1),
Indent::spaced(1),
Indent::zero(),
Indent::zero(),
))
.set_alignment_horizontal(AlignmentHorizontal::Left)
.set_borders(Style::ascii().get_borders())
}
fn build_records<I>(
records: I,
width: DimensionValue,
count_columns: usize,
count_rows: Option<usize>,
) -> IterRecords<LimitColumns<TruncateContent<I, StaticDimension>>>
where
I: IntoRecords,
{
let dims = StaticDimension::new(width, DimensionValue::Exact(0));
let records = TruncateContent::new(records, dims);
let records = LimitColumns::new(records, count_columns);
IterRecords::new(records, count_columns, count_rows)
}