#![cfg_attr(feature = "derive", doc = "```")]
#![cfg_attr(not(feature = "derive"), doc = "```ignore")]
use papergrid::util::{cut_str_basic, string_width};
use crate::Tabled;
#[derive(Debug, Clone)]
pub struct ExpandedDisplay {
fields: Vec<String>,
records: Vec<Vec<String>>,
}
impl ExpandedDisplay {
pub fn new<T>(iter: impl IntoIterator<Item = T>) -> Self
where
T: Tabled,
{
let data = iter
.into_iter()
.map(|i| {
i.fields()
.into_iter()
.map(|s| s.escape_debug().to_string())
.collect()
})
.collect();
let header = T::headers()
.into_iter()
.map(|s| s.escape_debug().to_string())
.collect();
Self {
records: data,
fields: header,
}
}
pub fn truncate(&mut self, max: usize, suffix: &str) -> bool {
let teplate_width = self.records.len().to_string().len() + 13;
let min_width = teplate_width;
if max < min_width {
return false;
}
let suffix_width = string_width(suffix);
if max < suffix_width {
return false;
}
let max = max - suffix_width;
let fields_max_width = self
.fields
.iter()
.map(|s| string_width(s))
.max()
.unwrap_or_default();
let fields_affected = max < fields_max_width + 3;
if fields_affected {
if max < 3 {
return false;
}
let max = max - 3;
if max < suffix_width {
return false;
}
let max = max - suffix_width;
truncate_fields(&mut self.fields, max, suffix);
truncate_records(&mut self.records, 0, suffix);
} else {
let max = max - fields_max_width - 3 - suffix_width;
truncate_records(&mut self.records, max, suffix);
}
true
}
}
impl std::fmt::Display for ExpandedDisplay {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
if self.records.is_empty() {
return Ok(());
}
let fields = self.fields.iter().collect::<Vec<_>>();
let max_field_width = fields
.iter()
.map(|s| string_width(s))
.max()
.unwrap_or_default();
let max_values_length = self
.records
.iter()
.map(|record| record.iter().map(|s| string_width(s)).max())
.max()
.unwrap_or_default()
.unwrap_or_default();
for (i, records) in self.records.iter().enumerate() {
write_header_template(f, i, max_field_width, max_values_length)?;
for (value, field) in records.iter().zip(fields.iter()) {
writeln!(f)?;
write_record(f, field, value, max_field_width)?;
}
let is_last_record = i + 1 == self.records.len();
if !is_last_record {
writeln!(f)?;
}
}
Ok(())
}
}
fn truncate_records(records: &mut Vec<Vec<String>>, max_width: usize, suffix: &str) {
for fields in records {
truncate_fields(fields, max_width, suffix);
}
}
fn truncate_fields(records: &mut Vec<String>, max_width: usize, suffix: &str) {
for text in records {
truncate(text, max_width, suffix);
}
}
fn write_header_template(
f: &mut std::fmt::Formatter<'_>,
index: usize,
max_field_width: usize,
max_values_length: usize,
) -> std::fmt::Result {
let mut template = format!("-[ RECORD {} ]-", index);
let default_template_length = template.len();
let max_line_width = std::cmp::max(
max_field_width + 3 + max_values_length,
default_template_length,
);
let rest_to_print = max_line_width - default_template_length;
if rest_to_print > 0 {
if max_field_width + 2 > default_template_length {
let part1 = (max_field_width + 1) - default_template_length;
let part2 = rest_to_print - part1 - 1;
template.extend(
std::iter::repeat('-')
.take(part1)
.chain(std::iter::once('+'))
.chain(std::iter::repeat('-').take(part2)),
);
} else {
template.extend(std::iter::repeat('-').take(rest_to_print));
}
}
write!(f, "{}", template)?;
Ok(())
}
fn write_record(
f: &mut std::fmt::Formatter<'_>,
field: &str,
value: &str,
max_field_width: usize,
) -> std::fmt::Result {
write!(f, "{:width$} | {}", field, value, width = max_field_width)
}
fn truncate(text: &mut String, max: usize, suffix: &str) {
let original_len = text.len();
if max == 0 || text.is_empty() {
*text = String::new();
} else {
*text = cut_str_basic(text, max).into_owned();
}
let cut_was_done = text.len() < original_len;
if !suffix.is_empty() && cut_was_done {
text.push_str(suffix);
}
}