#![no_std]
#![doc = include_str!("../README.md")]
extern crate alloc;
use alloc::borrow::Cow;
use alloc::string::String;
use alloc::vec;
use alloc::vec::Vec;
use core::fmt;
use unicode_width::UnicodeWidthStr;
#[cfg(test)]
mod tests;
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
pub enum Alignment {
Left,
Right,
}
impl Default for Alignment {
#[inline]
fn default() -> Self {
Self::Left
}
}
#[derive(Debug, Default, Clone, PartialEq, Eq)]
pub struct GridCell<D: fmt::Display> {
pub contents: D,
pub width: usize,
pub alignment: Alignment,
}
impl<D: fmt::Display> GridCell<D> {
pub(crate) fn write<F: fmt::Write>(&self, f: &mut F, width: usize) -> fmt::Result {
let pad_width: usize = if width <= self.width {
0
} else {
width - self.width
};
if pad_width == 0 {
write!(f, "{}", self.contents)
} else if self.alignment == Alignment::Left {
write!(f, "{}{}", self.contents, " ".repeat(pad_width))
} else {
write!(f, "{}{}", " ".repeat(pad_width), self.contents)
}
}
}
impl From<String> for GridCell<String> {
fn from(value: String) -> Self {
let width = UnicodeWidthStr::width(&*value);
Self {
contents: value,
width: width,
alignment: Alignment::Left,
}
}
}
#[derive(Debug, Default)]
pub struct Grid<'cells, 'seperator, D: fmt::Display> {
cells: &'cells [GridCell<D>],
seperator: Cow<'seperator, str>,
seperator_width: usize,
direction: Direction,
}
impl<'cells, 'seperator, D: fmt::Display> Grid<'cells, 'seperator, D> {
pub fn new<S>(seperator: S, direction: Direction, cells: &'cells [GridCell<D>]) -> Self
where
S: Into<Cow<'seperator, str>>,
{
let seperator: Cow<'_, str> = seperator.into();
let seperator_width = UnicodeWidthStr::width(&*seperator);
Self {
cells,
seperator,
seperator_width,
direction,
}
}
#[inline]
pub(crate) fn total_cell_count(&self) -> usize {
self.cells.len()
}
pub fn fit_into_columns(&self, num_columns: usize) -> Display<'_, D> {
let dimentions = self.calculate_dimentions(num_columns);
Display {
dimentions: dimentions,
grid: self,
}
}
pub fn fit_into_width(&self, display_width: usize) -> Option<Display<'_, D>> {
if self.cells.is_empty() {
return Some(Display {
dimentions: Dimentions::one_row(0),
grid: self,
});
}
let max_cell_width: usize = self.cells.iter().map(|cell| cell.width).max().unwrap_or(0);
if max_cell_width >= display_width {
None
} else {
let total_width: usize = (self.cells.iter().map(|cell| cell.width).sum::<usize>())
+ (self.total_cell_count() - 1) * self.seperator_width;
if total_width <= display_width {
Some(Display {
dimentions: Dimentions::one_row(self.total_cell_count()),
grid: self,
})
} else {
Some(self.internal_fit_into_width(max_cell_width, display_width))
}
}
}
fn internal_fit_into_width(
&self,
max_cell_width: usize,
display_width: usize,
) -> Display<'_, D> {
let total_cell_count = self.total_cell_count();
let mut num_columns = display_width / (max_cell_width + self.seperator_width);
let mut dimentions = self.calculate_dimentions(num_columns);
loop {
num_columns += 1;
let new_dimentions = self.calculate_dimentions(num_columns);
if new_dimentions.total_width(self.seperator_width) > display_width {
break;
}
else if new_dimentions.is_well_packed(total_cell_count, dimentions.num_rows) {
dimentions = new_dimentions;
}
}
Display {
dimentions: dimentions,
grid: self,
}
}
fn calculate_dimentions(&self, num_columns: usize) -> Dimentions {
let num_rows = usize_div_ceil(self.total_cell_count(), num_columns);
let mut column_widths: Vec<usize> = vec![0; num_columns];
for (cell_index, cell) in self.cells.iter().enumerate() {
let column_index = match self.direction {
Direction::LeftToRight => cell_index % num_columns,
Direction::TopToBottom => cell_index / num_rows,
};
column_widths[column_index] = column_widths[column_index].max(cell.width);
}
Dimentions {
num_rows: num_rows,
column_widths: column_widths,
}
}
}
#[derive(Debug)]
pub struct Display<'grid, D: fmt::Display> {
dimentions: Dimentions,
grid: &'grid Grid<'grid, 'grid, D>,
}
impl<D: fmt::Display> fmt::Display for Display<'_, D> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let total_cell_count = self.grid.total_cell_count();
if total_cell_count == 0 {
return writeln!(f);
}
let mut cell_count: usize = 0;
let last_cell_index = total_cell_count - 1;
let num_columns = self.dimentions.column_widths.len();
let last_column_index = num_columns - 1;
for row_index in 0..self.dimentions.num_rows {
for column_index in 0..num_columns {
let cell_index = match self.grid.direction {
Direction::LeftToRight => row_index * num_columns + column_index,
Direction::TopToBottom => row_index + self.dimentions.num_rows * column_index,
};
if cell_index > last_cell_index {
continue;
}
cell_count += 1;
let cell = &self.grid.cells[cell_index];
if ((column_index == last_column_index) || (cell_count == total_cell_count))
&& cell.alignment == Alignment::Left
{
write!(f, "{}", cell.contents)?;
} else {
cell.write(f, self.dimentions.column_widths[column_index])?;
write!(f, "{}", self.grid.seperator)?;
}
}
writeln!(f)?;
}
Ok(())
}
}
#[derive(Debug, PartialEq, Eq)]
pub enum Direction {
LeftToRight,
TopToBottom,
}
impl Default for Direction {
#[inline]
fn default() -> Self {
Self::LeftToRight
}
}
#[derive(Debug, Default)]
struct Dimentions {
num_rows: usize,
column_widths: Vec<usize>,
}
impl Dimentions {
pub fn total_width(&self, spaces: usize) -> usize {
self.column_widths.iter().sum::<usize>() + ((self.column_widths.len() - 1) * spaces)
}
#[inline]
pub fn is_well_packed(&self, cell_count: usize, previous_num_rows: usize) -> bool {
let last_col_cell_count = cell_count % (self.column_widths.len() - 1);
(last_col_cell_count <= self.num_rows) && (self.num_rows != previous_num_rows)
}
pub fn one_row(cell_count: usize) -> Self {
Self {
num_rows: 1,
column_widths: vec![0; cell_count],
}
}
}
#[inline]
fn usize_div_ceil(lhs: usize, rhs: usize) -> usize {
let d = lhs / rhs;
let r = lhs % rhs;
if r > 0 && rhs > 0 {
d + 1
} else {
d
}
}