diff --git a/Cargo.toml b/Cargo.toml
index 935913d..5080079 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -15,4 +15,4 @@ rust-version = "1.70"
name = "term_grid"
[dependencies]
-unicode-width = "0.1.11"
+textwrap = { version = "0.16.0", default-features = false, features = ["unicode-width"] }
diff --git a/README.md b/README.md
index 0cf0d4c..a56a4ba 100644
--- a/README.md
+++ b/README.md
@@ -2,20 +2,24 @@
[![dependency status](https://deps.rs/repo/github/uutils/uutils-term-grid/status.svg)](https://deps.rs/repo/github/uutils/uutils-term-grid)
[![CodeCov](https://codecov.io/gh/uutils/uutils-term-grid/branch/master/graph/badge.svg)](https://codecov.io/gh/uutils/uutils-term-grid)
-
# uutils-term-grid
-This library arranges textual data in a grid format suitable for fixed-width fonts, using an algorithm to minimise the amount of space needed.
+This library arranges textual data in a grid format suitable for fixed-width
+fonts, using an algorithm to minimise the amount of space needed.
---
-This library is forked from the [`rust-term-grid`](https://github.com/ogham/rust-term-grid) library.
+This library is forked from the unmaintained
+[`rust-term-grid`](https://github.com/ogham/rust-term-grid) library. The core
+functionality has remained the same, with some additional bugfixes, performance
+improvements and a new API.
---
# Installation
-This crate works with `cargo`. Add the following to your `Cargo.toml` dependencies section:
+This crate works with `cargo`. Add the following to your `Cargo.toml`
+dependencies section:
```toml
[dependencies]
@@ -24,70 +28,81 @@ uutils_term_grid = "0.3"
The Minimum Supported Rust Version is 1.70.
+## Creating a grid
-## Usage
+To add data to a grid, first create a new [`Grid`] value with a list of strings
+and a set of options.
-This library arranges textual data in a grid format suitable for fixed-width fonts, using an algorithm to minimise the amount of space needed.
-For example:
+There are three options that must be specified in the [`GridOptions`] value that
+dictate how the grid is formatted:
-```rust
-use term_grid::{Grid, GridOptions, Direction, Filling, Cell};
+- [`filling`][filling]: what to put in between two columns — either a number of
+ spaces, or a text string;
+- [`direction`][direction]: specifies whether the cells should go along rows, or
+ columns:
+ - [`Direction::LeftToRight`][LeftToRight] starts them in the top left and
+ moves _rightwards_, going to the start of a new row after reaching the final
+ column;
+ - [`Direction::TopToBottom`][TopToBottom] starts them in the top left and
+ moves _downwards_, going to the top of a new column after reaching the final
+ row.
+- [`width`][width]: the width to fill the grid into. Usually, this should be the
+ width of the terminal.
-let mut grid = Grid::new(GridOptions {
- filling: Filling::Spaces(1),
- direction: Direction::LeftToRight,
-});
+In practice, creating a grid can be done as follows:
-for s in &["one", "two", "three", "four", "five", "six", "seven",
- "eight", "nine", "ten", "eleven", "twelve"]
-{
- grid.add(Cell::from(*s));
-}
-
-println!("{}", grid.fit_into_width(24).unwrap());
+```rust
+use term_grid::{Grid, GridOptions, Direction, Filling};
+
+// Create a `Vec` of text to put in the grid
+let cells = vec![
+ "one", "two", "three", "four", "five", "six",
+ "seven", "eight", "nine", "ten", "eleven", "twelve"
+];
+
+// Then create a `Grid` with those cells.
+// The grid requires several options:
+// - The filling determines the string used as separator
+// between the columns.
+// - The direction specifies whether the layout should
+// be done row-wise or column-wise.
+// - The width is the maximum width that the grid might
+// have.
+let grid = Grid::new(
+ cells,
+ GridOptions {
+ filling: Filling::Spaces(1),
+ direction: Direction::LeftToRight,
+ width: 24,
+ }
+);
+
+// A `Grid` implements `Display` and can be printed directly.
+println!("{grid}");
```
Produces the following tabular result:
-```
+```text
one two three four
five six seven eight
nine ten eleven twelve
```
+[filling]: struct.GridOptions.html#structfield.filling
+[direction]: struct.GridOptions.html#structfield.direction
+[width]: struct.GridOptions.html#structfield.width
+[LeftToRight]: enum.Direction.html#variant.LeftToRight
+[TopToBottom]: enum.Direction.html#variant.TopToBottom
-## Creating a grid
-
-To add data to a grid, first create a new `Grid` value, and then add cells to them with the `add` method.
-
-There are two options that must be specified in the `GridOptions` value that dictate how the grid is formatted:
-
-- `filling`: what to put in between two columns - either a number of spaces, or a text string;
-- `direction`, which specifies whether the cells should go along rows, or columns:
- - `Direction::LeftToRight` starts them in the top left and moves *rightwards*, going to the start of a new row after reaching the final column;
- - `Direction::TopToBottom` starts them in the top left and moves *downwards*, going to the top of a new column after reaching the final row.
-
-
-## Displaying a grid
-
-When display a grid, you can either specify the number of columns in advance, or try to find the maximum number of columns that can fit in an area of a given width.
-
-Splitting a series of cells into columns - or, in other words, starting a new row every *n* cells - is achieved with the `fit_into_columns` method on a `Grid` value.
-It takes as its argument the number of columns.
-
-Trying to fit as much data onto one screen as possible is the main use case for specifying a maximum width instead.
-This is achieved with the `fit_into_width` method.
-It takes the maximum allowed width, including separators, as its argument.
-However, it returns an *optional* `Display` value, depending on whether any of the cells actually had a width greater than the maximum width!
-If this is the case, your best bet is to just output the cells with one per line.
-
-
-## Cells and data
+## Width of grid cells
-Grids do not take `String`s or `&str`s - they take `Cells`.
+This library calculates the width of strings as displayed in the terminal using
+the [`textwrap`][textwrap] library (with the [`display_width`][display_width] function).
+This takes into account the width of characters and ignores ANSI codes.
-A **Cell** is a struct containing an individual cell’s contents, as a string, and its pre-computed length, which gets used when calculating a grid’s final dimensions.
-Usually, you want the *Unicode width* of the string to be used for this, so you can turn a `String` into a `Cell` with the `.into()` method.
+The width calculation is currently not configurable. If you have a use-case for
+which this calculation is wrong, please open an issue.
-However, you may also want to supply your own width: when you already know the width in advance, or when you want to change the measurement, such as skipping over terminal control characters.
-For cases like these, the fields on the `Cell` values are public, meaning you can construct your own instances as necessary.
+[textwrap]: https://docs.rs/textwrap/latest/textwrap/index.html
+[display_width]: https://docs.rs/textwrap/latest/textwrap/core/fn.display_width.html
diff --git a/examples/basic.rs b/examples/basic.rs
index 11a81c3..876d131 100644
--- a/examples/basic.rs
+++ b/examples/basic.rs
@@ -1,5 +1,7 @@
-extern crate term_grid;
-use term_grid::{Cell, Direction, Filling, Grid, GridOptions};
+// For the full copyright and license information, please view the LICENSE
+// file that was distributed with this source code.
+
+use term_grid::{Direction, Filling, Grid, GridOptions};
// This produces:
//
@@ -12,19 +14,16 @@ use term_grid::{Cell, Direction, Filling, Grid, GridOptions};
// 64 | 8192 | 1048576 | 134217728 | 17179869184 | 2199023255552 |
fn main() {
- let mut grid = Grid::new(GridOptions {
- direction: Direction::TopToBottom,
- filling: Filling::Text(" | ".into()),
- });
+ let cells: Vec<_> = (0..48).map(|i| 2_isize.pow(i).to_string()).collect();
- for i in 0..48 {
- let cell = Cell::from(2_isize.pow(i).to_string());
- grid.add(cell)
- }
+ let grid = Grid::new(
+ cells,
+ GridOptions {
+ direction: Direction::TopToBottom,
+ filling: Filling::Text(" | ".into()),
+ width: 80,
+ },
+ );
- if let Some(grid_display) = grid.fit_into_width(80) {
- println!("{}", grid_display);
- } else {
- println!("Couldn't fit grid into 80 columns!");
- }
+ println!("{}", grid);
}
diff --git a/examples/big.rs b/examples/big.rs
index de42113..6456497 100644
--- a/examples/big.rs
+++ b/examples/big.rs
@@ -1,26 +1,26 @@
// For the full copyright and license information, please view the LICENSE
// file that was distributed with this source code.
-extern crate term_grid;
-use term_grid::{Cell, Direction, Filling, Grid, GridOptions};
+use term_grid::{Direction, Filling, Grid, GridOptions};
fn main() {
- let mut grid = Grid::new(GridOptions {
- direction: Direction::TopToBottom,
- filling: Filling::Text(" | ".into()),
- });
-
let mut n: u64 = 1234;
for _ in 0..50 {
+ let mut cells = Vec::new();
for _ in 0..10000 {
- grid.add(Cell::from(n.to_string()));
+ cells.push(n.to_string());
n = n.overflowing_pow(2).0 % 100000000;
}
- if let Some(grid_display) = grid.fit_into_width(80) {
- println!("{}", grid_display);
- } else {
- println!("Couldn't fit grid into 80 columns!");
- }
+ let grid = Grid::new(
+ cells,
+ GridOptions {
+ direction: Direction::TopToBottom,
+ filling: Filling::Text(" | ".into()),
+ width: 80,
+ },
+ );
+
+ println!("{grid}");
}
}
diff --git a/src/lib.rs b/src/lib.rs
index e2bbfae..6106871 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -1,3 +1,6 @@
+// For the full copyright and license information, please view the LICENSE
+// file that was distributed with this source code.
+
#![warn(future_incompatible)]
#![warn(missing_copy_implementations)]
#![warn(missing_docs)]
@@ -5,134 +8,12 @@
#![warn(trivial_casts, trivial_numeric_casts)]
#![warn(unused)]
#![deny(unsafe_code)]
-
-//! This library arranges textual data in a grid format suitable for
-//! fixed-width fonts, using an algorithm to minimise the amount of space
-//! needed. For example:
-//!
-//! ```rust
-//! use term_grid::{Grid, GridOptions, Direction, Filling, Cell};
-//!
-//! let mut grid = Grid::new(GridOptions {
-//! filling: Filling::Spaces(1),
-//! direction: Direction::LeftToRight,
-//! });
-//!
-//! for s in &["one", "two", "three", "four", "five", "six", "seven",
-//! "eight", "nine", "ten", "eleven", "twelve"]
-//! {
-//! grid.add(Cell::from(*s));
-//! }
-//!
-//! println!("{}", grid.fit_into_width(24).unwrap());
-//! ```
-//!
-//! Produces the following tabular result:
-//!
-//! ```text
-//! one two three four
-//! five six seven eight
-//! nine ten eleven twelve
-//! ```
-//!
-//!
-//! ## Creating a grid
-//!
-//! To add data to a grid, first create a new [`Grid`] value, and then add
-//! cells to them with the `add` function.
-//!
-//! There are two options that must be specified in the [`GridOptions`] value
-//! that dictate how the grid is formatted:
-//!
-//! - `filling`: what to put in between two columns — either a number of
-//! spaces, or a text string;
-//! - `direction`, which specifies whether the cells should go along
-//! rows, or columns:
-//! - `Direction::LeftToRight` starts them in the top left and
-//! moves *rightwards*, going to the start of a new row after reaching the
-//! final column;
-//! - `Direction::TopToBottom` starts them in the top left and moves
-//! *downwards*, going to the top of a new column after reaching the final
-//! row.
-//!
-//!
-//! ## Displaying a grid
-//!
-//! When display a grid, you can either specify the number of columns in advance,
-//! or try to find the maximum number of columns that can fit in an area of a
-//! given width.
-//!
-//! Splitting a series of cells into columns — or, in other words, starting a new
-//! row every n cells — is achieved with the [`fit_into_columns`] function
-//! on a `Grid` value. It takes as its argument the number of columns.
-//!
-//! Trying to fit as much data onto one screen as possible is the main use case
-//! for specifying a maximum width instead. This is achieved with the
-//! [`fit_into_width`] function. It takes the maximum allowed width, including
-//! separators, as its argument. However, it returns an *optional* [`Display`]
-//! value, depending on whether any of the cells actually had a width greater than
-//! the maximum width! If this is the case, your best bet is to just output the
-//! cells with one per line.
-//!
-//!
-//! ## Cells and data
-//!
-//! Grids to not take `String`s or `&str`s — they take [`Cell`] values.
-//!
-//! A **Cell** is a struct containing an individual cell’s contents, as a string,
-//! and its pre-computed length, which gets used when calculating a grid’s final
-//! dimensions. Usually, you want the *Unicode width* of the string to be used for
-//! this, so you can turn a `String` into a `Cell` with the `.into()` function.
-//!
-//! However, you may also want to supply your own width: when you already know the
-//! width in advance, or when you want to change the measurement, such as skipping
-//! over terminal control characters. For cases like these, the fields on the
-//! `Cell` values are public, meaning you can construct your own instances as
-//! necessary.
-//!
-//! [`Cell`]: ./struct.Cell.html
-//! [`Display`]: ./struct.Display.html
-//! [`Grid`]: ./struct.Grid.html
-//! [`fit_into_columns`]: ./struct.Grid.html#method.fit_into_columns
-//! [`fit_into_width`]: ./struct.Grid.html#method.fit_into_width
-//! [`GridOptions`]: ./struct.GridOptions.html
+#![doc = include_str!("../README.md")]
use std::fmt;
-use unicode_width::UnicodeWidthStr;
-
-/// A **Cell** is the combination of a string and its pre-computed length.
-///
-/// The easiest way to create a Cell is just by using `string.into()`, which
-/// uses the **unicode width** of the string (see the `unicode_width` crate).
-/// However, the fields are public, if you wish to provide your own length.
-#[derive(PartialEq, Eq, Debug, Clone)]
-pub struct Cell {
- /// The string to display when this cell gets rendered.
- pub contents: String,
-
- /// The pre-computed length of the string.
- pub width: Width,
-}
-
-impl From for Cell {
- fn from(string: String) -> Self {
- Self {
- width: UnicodeWidthStr::width(&*string),
- contents: string,
- }
- }
-}
+use textwrap::core::display_width;
-impl<'a> From<&'a str> for Cell {
- fn from(string: &'a str) -> Self {
- Self {
- width: UnicodeWidthStr::width(string),
- contents: string.into(),
- }
- }
-}
-
-/// Direction cells should be written in — either across, or downwards.
+/// Direction cells should be written in: either across or downwards.
#[derive(PartialEq, Eq, Debug, Copy, Clone)]
pub enum Direction {
/// Starts at the top left and moves rightwards, going back to the first
@@ -144,58 +25,58 @@ pub enum Direction {
TopToBottom,
}
-/// The width of a cell, in columns.
-pub type Width = usize;
-
/// The text to put in between each pair of columns.
+///
/// This does not include any spaces used when aligning cells.
#[derive(PartialEq, Eq, Debug)]
pub enum Filling {
- /// A certain number of spaces should be used as the separator.
- Spaces(Width),
+ /// A number of spaces
+ Spaces(usize),
- /// An arbitrary string.
+ /// An arbitrary string
+ ///
/// `"|"` is a common choice.
Text(String),
}
impl Filling {
- fn width(&self) -> Width {
- match *self {
- Filling::Spaces(w) => w,
- Filling::Text(ref t) => UnicodeWidthStr::width(&t[..]),
+ fn width(&self) -> usize {
+ match self {
+ Filling::Spaces(w) => *w,
+ Filling::Text(t) => display_width(t),
}
}
}
-/// The user-assignable options for a grid view that should be passed to
-/// [`Grid::new()`](struct.Grid.html#method.new).
+/// The options for a grid view that should be passed to [`Grid::new`]
#[derive(Debug)]
pub struct GridOptions {
- /// The direction that the cells should be written in — either
- /// across, or downwards.
+ /// The direction that the cells should be written in
pub direction: Direction,
- /// The number of spaces to put in between each column of cells.
+ /// The string to put in between each column of cells
pub filling: Filling,
+
+ /// The width to fill with the grid
+ pub width: usize,
}
#[derive(PartialEq, Eq, Debug)]
struct Dimensions {
/// The number of lines in the grid.
- num_lines: Width,
+ num_lines: usize,
/// The width of each column in the grid. The length of this vector serves
/// as the number of columns.
- widths: Vec,
+ widths: Vec,
}
impl Dimensions {
- fn total_width(&self, separator_width: Width) -> Width {
+ fn total_width(&self, separator_width: usize) -> usize {
if self.widths.is_empty() {
0
} else {
- let values = self.widths.iter().sum::();
+ let values = self.widths.iter().sum::();
let separators = separator_width * (self.widths.len() - 1);
values + separators
}
@@ -203,90 +84,84 @@ impl Dimensions {
}
/// Everything needed to format the cells with the grid options.
-///
-/// For more information, see the [`term_grid` crate documentation](index.html).
#[derive(Debug)]
-pub struct Grid {
+pub struct Grid> {
options: GridOptions,
- cells: Vec,
- widest_cell_length: Width,
- width_sum: Width,
- cell_count: usize,
+ cells: Vec,
+ widths: Vec,
+ widest_cell_width: usize,
+ dimensions: Dimensions,
}
-impl Grid {
- /// Creates a new grid view with the given options.
- pub fn new(options: GridOptions) -> Self {
- let cells = Vec::new();
- Self {
+impl> Grid {
+ /// Creates a new grid view with the given cells and options
+ pub fn new(cells: Vec, options: GridOptions) -> Self {
+ let widths: Vec = cells.iter().map(|c| display_width(c.as_ref())).collect();
+ let widest_cell_width = widths.iter().copied().max().unwrap_or(0);
+ let width = options.width;
+
+ let mut grid = Self {
options,
cells,
- widest_cell_length: 0,
- width_sum: 0,
- cell_count: 0,
- }
- }
+ widths,
+ widest_cell_width,
+ dimensions: Dimensions {
+ num_lines: 0,
+ widths: Vec::new(),
+ },
+ };
- /// Reserves space in the vector for the given number of additional cells
- /// to be added. (See the `Vec::reserve` function.)
- pub fn reserve(&mut self, additional: usize) {
- self.cells.reserve(additional)
- }
+ grid.dimensions = grid.width_dimensions(width).unwrap_or(Dimensions {
+ num_lines: grid.cells.len(),
+ widths: vec![widest_cell_width],
+ });
- /// Adds another cell onto the vector.
- pub fn add(&mut self, cell: Cell) {
- if cell.width > self.widest_cell_length {
- self.widest_cell_length = cell.width;
- }
- self.width_sum += cell.width;
- self.cell_count += 1;
- self.cells.push(cell)
+ grid
}
- /// Returns a displayable grid that’s been packed to fit into the given
- /// width in the fewest number of rows.
- ///
- /// Returns `None` if any of the cells has a width greater than the
- /// maximum width.
- pub fn fit_into_width(&self, maximum_width: Width) -> Option> {
- self.width_dimensions(maximum_width).map(|dims| Display {
- grid: self,
- dimensions: dims,
- })
+ /// The number of terminal columns this display takes up, based on the separator
+ /// width and the number and width of the columns.
+ pub fn width(&self) -> usize {
+ self.dimensions.total_width(self.options.filling.width())
}
- /// Returns a displayable grid with the given number of columns, and no
- /// maximum width.
- pub fn fit_into_columns(&self, num_columns: usize) -> Display<'_> {
- Display {
- grid: self,
- dimensions: self.columns_dimensions(num_columns),
- }
+ /// The number of rows this display takes up.
+ pub fn row_count(&self) -> usize {
+ self.dimensions.num_lines
}
- fn columns_dimensions(&self, num_columns: usize) -> Dimensions {
- let num_lines = div_ceil(self.cells.len(), num_columns);
- self.column_widths(num_lines, num_columns)
+ /// Returns whether this display takes up as many columns as were allotted
+ /// to it.
+ ///
+ /// It’s possible to construct tables that don’t actually use up all the
+ /// columns that they could, such as when there are more columns than
+ /// cells! In this case, a column would have a width of zero. This just
+ /// checks for that.
+ pub fn is_complete(&self) -> bool {
+ self.dimensions.widths.iter().all(|&x| x > 0)
}
fn column_widths(&self, num_lines: usize, num_columns: usize) -> Dimensions {
- let mut widths = vec![0; num_columns];
- for (index, cell) in self.cells.iter().enumerate() {
+ let mut column_widths = vec![0; num_columns];
+ for (index, cell_width) in self.widths.iter().copied().enumerate() {
let index = match self.options.direction {
Direction::LeftToRight => index % num_columns,
Direction::TopToBottom => index / num_lines,
};
- if cell.width > widths[index] {
- widths[index] = cell.width;
+ if cell_width > column_widths[index] {
+ column_widths[index] = cell_width;
}
}
- Dimensions { num_lines, widths }
+ Dimensions {
+ num_lines,
+ widths: column_widths,
+ }
}
fn theoretical_max_num_lines(&self, maximum_width: usize) -> usize {
// TODO: Make code readable / efficient.
- let mut widths: Vec<_> = self.cells.iter().map(|c| c.width).collect();
+ let mut widths = self.widths.clone();
// Sort widths in reverse order
widths.sort_unstable_by(|a, b| b.cmp(a));
@@ -296,7 +171,7 @@ impl Grid {
if width + col_total_width_so_far <= maximum_width {
col_total_width_so_far += self.options.filling.width() + width;
} else {
- return div_ceil(self.cell_count, i);
+ return div_ceil(self.cells.len(), i);
}
}
@@ -306,34 +181,34 @@ impl Grid {
1
}
- fn width_dimensions(&self, maximum_width: Width) -> Option {
- if self.widest_cell_length > maximum_width {
+ fn width_dimensions(&self, maximum_width: usize) -> Option {
+ if self.widest_cell_width > maximum_width {
// Largest cell is wider than maximum width; it is impossible to fit.
return None;
}
- if self.cell_count == 0 {
+ if self.cells.is_empty() {
return Some(Dimensions {
num_lines: 0,
widths: Vec::new(),
});
}
- if self.cell_count == 1 {
- let the_cell = &self.cells[0];
+ if self.cells.len() == 1 {
+ let cell_widths = self.widths[0];
return Some(Dimensions {
num_lines: 1,
- widths: vec![the_cell.width],
+ widths: vec![cell_widths],
});
}
let theoretical_max_num_lines = self.theoretical_max_num_lines(maximum_width);
if theoretical_max_num_lines == 1 {
- // This if—statement is neccesary for the function to work correctly
+ // This if—statement is necessary for the function to work correctly
// for small inputs.
return Some(Dimensions {
num_lines: 1,
- widths: self.cells.iter().map(|cell| cell.width).collect(),
+ widths: self.widths.clone(),
});
}
// Instead of numbers of columns, try to find the fewest number of *lines*
@@ -342,7 +217,7 @@ impl Grid {
for num_lines in (1..=theoretical_max_num_lines).rev() {
// The number of columns is the number of cells divided by the number
// of lines, *rounded up*.
- let num_columns = div_ceil(self.cell_count, num_lines);
+ let num_columns = div_ceil(self.cells.len(), num_lines);
// Early abort: if there are so many columns that the width of the
// *column separators* is bigger than the width of the screen, then
@@ -359,7 +234,7 @@ impl Grid {
let adjusted_width = maximum_width - total_separator_width;
let potential_dimensions = self.column_widths(num_lines, num_columns);
- if potential_dimensions.widths.iter().sum::() < adjusted_width {
+ if potential_dimensions.widths.iter().sum::() < adjusted_width {
smallest_dimensions_yet = Some(potential_dimensions);
} else {
return smallest_dimensions_yet;
@@ -370,47 +245,9 @@ impl Grid {
}
}
-/// A displayable representation of a [`Grid`](struct.Grid.html).
-///
-/// This type implements `Display`, so you can get the textual version
-/// of the grid by calling `.to_string()`.
-#[derive(Debug)]
-pub struct Display<'grid> {
- /// The grid to display.
- grid: &'grid Grid,
-
- /// The pre-computed column widths for this grid.
- dimensions: Dimensions,
-}
-
-impl Display<'_> {
- /// Returns how many columns this display takes up, based on the separator
- /// width and the number and width of the columns.
- pub fn width(&self) -> Width {
- self.dimensions
- .total_width(self.grid.options.filling.width())
- }
-
- /// Returns how many rows this display takes up.
- pub fn row_count(&self) -> usize {
- self.dimensions.num_lines
- }
-
- /// Returns whether this display takes up as many columns as were allotted
- /// to it.
- ///
- /// It’s possible to construct tables that don’t actually use up all the
- /// columns that they could, such as when there are more columns than
- /// cells! In this case, a column would have a width of zero. This just
- /// checks for that.
- pub fn is_complete(&self) -> bool {
- self.dimensions.widths.iter().all(|&x| x > 0)
- }
-}
-
-impl fmt::Display for Display<'_> {
+impl> fmt::Display for Grid {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
- let separator = match &self.grid.options.filling {
+ let separator = match &self.options.filling {
Filling::Spaces(n) => " ".repeat(*n),
Filling::Text(s) => s.clone(),
};
@@ -423,26 +260,26 @@ impl fmt::Display for Display<'_> {
// We overestimate how many spaces we need, but this is not
// part of the loop and it's therefore not super important to
// get exactly right.
- let padding = " ".repeat(self.grid.widest_cell_length);
+ let padding = " ".repeat(self.widest_cell_width);
for y in 0..self.dimensions.num_lines {
for x in 0..self.dimensions.widths.len() {
- let num = match self.grid.options.direction {
+ let num = match self.options.direction {
Direction::LeftToRight => y * self.dimensions.widths.len() + x,
Direction::TopToBottom => y + self.dimensions.num_lines * x,
};
// Abandon a line mid-way through if that’s where the cells end
- if num >= self.grid.cells.len() {
+ if num >= self.cells.len() {
continue;
}
- let cell = &self.grid.cells[num];
- let contents = &cell.contents;
+ let contents = &self.cells[num];
+ let width = self.widths[num];
let last_in_row = x == self.dimensions.widths.len() - 1;
let col_width = self.dimensions.widths[x];
- let padding_size = col_width - cell.width;
+ let padding_size = col_width - width;
// The final column doesn’t need to have trailing spaces,
// as long as it’s left-aligned.
@@ -457,7 +294,7 @@ impl fmt::Display for Display<'_> {
// above, so we don't need to call `" ".repeat(n)` each loop.
// We also only call `write_str` when we actually need padding as
// another optimization.
- f.write_str(contents)?;
+ f.write_str(contents.as_ref())?;
if !last_in_row {
if padding_size > 0 {
f.write_str(&padding[0..padding_size])?;
@@ -474,7 +311,8 @@ impl fmt::Display for Display<'_> {
// Adapted from the unstable API:
// https://doc.rust-lang.org/std/primitive.usize.html#method.div_ceil
-/// Division with upward rouding
+// Can be removed on MSRV 1.73.
+/// Division with upward rounding
pub const fn div_ceil(lhs: usize, rhs: usize) -> usize {
let d = lhs / rhs;
let r = lhs % rhs;
diff --git a/tests/test.rs b/tests/test.rs
index 5913ca8..03532d5 100644
--- a/tests/test.rs
+++ b/tests/test.rs
@@ -1,167 +1,176 @@
-use term_grid::{Cell, Direction, Filling, Grid, GridOptions};
+// For the full copyright and license information, please view the LICENSE
+// file that was distributed with this source code.
+
+// spell-checker:ignore underflowed
+
+use term_grid::{Direction, Filling, Grid, GridOptions};
#[test]
fn no_items() {
- let grid = Grid::new(GridOptions {
- direction: Direction::TopToBottom,
- filling: Filling::Spaces(2),
- });
+ let grid = Grid::new(
+ Vec::::new(),
+ GridOptions {
+ direction: Direction::TopToBottom,
+ filling: Filling::Spaces(2),
+ width: 40,
+ },
+ );
- let display = grid.fit_into_width(40).unwrap();
- assert_eq!("", display.to_string());
+ assert_eq!("", grid.to_string());
}
#[test]
fn one_item() {
- let mut grid = Grid::new(GridOptions {
- direction: Direction::TopToBottom,
- filling: Filling::Spaces(2),
- });
-
- grid.add(Cell::from("1"));
-
- let display = grid.fit_into_width(40).unwrap();
- assert_eq!("1\n", display.to_string());
+ let grid = Grid::new(
+ vec!["1"],
+ GridOptions {
+ direction: Direction::TopToBottom,
+ filling: Filling::Spaces(2),
+ width: 40,
+ },
+ );
+ assert_eq!("1\n", grid.to_string());
}
#[test]
fn one_item_exact_width() {
- let mut grid = Grid::new(GridOptions {
- direction: Direction::TopToBottom,
- filling: Filling::Spaces(2),
- });
-
- grid.add(Cell::from("1234567890"));
+ let grid = Grid::new(
+ vec!["1234567890"],
+ GridOptions {
+ direction: Direction::TopToBottom,
+ filling: Filling::Spaces(2),
+ width: 10,
+ },
+ );
- let display = grid.fit_into_width(10).unwrap();
- assert_eq!("1234567890\n", display.to_string());
+ assert_eq!("1234567890\n", grid.to_string());
}
#[test]
fn one_item_just_over() {
- let mut grid = Grid::new(GridOptions {
- direction: Direction::TopToBottom,
- filling: Filling::Spaces(2),
- });
-
- grid.add(Cell::from("1234567890!"));
+ let grid = Grid::new(
+ vec!["1234567890!"],
+ GridOptions {
+ direction: Direction::TopToBottom,
+ filling: Filling::Spaces(2),
+ width: 10,
+ },
+ );
- assert!(grid.fit_into_width(10).is_none());
+ assert_eq!(grid.row_count(), 1);
}
#[test]
fn two_small_items() {
- let mut grid = Grid::new(GridOptions {
- direction: Direction::TopToBottom,
- filling: Filling::Spaces(2),
- });
-
- grid.add(Cell::from("1"));
- grid.add(Cell::from("2"));
-
- let display = grid.fit_into_width(40).unwrap();
+ let grid = Grid::new(
+ vec!["1", "2"],
+ GridOptions {
+ direction: Direction::TopToBottom,
+ filling: Filling::Spaces(2),
+ width: 40,
+ },
+ );
- assert_eq!(display.width(), 1 + 2 + 1);
- assert_eq!("1 2\n", display.to_string());
+ assert_eq!(grid.width(), 1 + 2 + 1);
+ assert_eq!("1 2\n", grid.to_string());
}
#[test]
fn two_medium_size_items() {
- let mut grid = Grid::new(GridOptions {
- direction: Direction::TopToBottom,
- filling: Filling::Spaces(2),
- });
-
- grid.add(Cell::from("hello there"));
- grid.add(Cell::from("how are you today?"));
-
- let display = grid.fit_into_width(40).unwrap();
+ let grid = Grid::new(
+ vec!["hello there", "how are you today?"],
+ GridOptions {
+ direction: Direction::TopToBottom,
+ filling: Filling::Spaces(2),
+ width: 40,
+ },
+ );
- assert_eq!(display.width(), 11 + 2 + 18);
- assert_eq!("hello there how are you today?\n", display.to_string());
+ assert_eq!(grid.width(), 11 + 2 + 18);
+ assert_eq!("hello there how are you today?\n", grid.to_string());
}
#[test]
fn two_big_items() {
- let mut grid = Grid::new(GridOptions {
- direction: Direction::TopToBottom,
- filling: Filling::Spaces(2),
- });
-
- grid.add(Cell::from(
- "nuihuneihsoenhisenouiuteinhdauisdonhuisudoiosadiuohnteihaosdinhteuieudi",
- ));
- grid.add(Cell::from(
- "oudisnuthasuouneohbueobaugceoduhbsauglcobeuhnaeouosbubaoecgueoubeohubeo",
- ));
-
- assert!(grid.fit_into_width(40).is_none());
+ let grid = Grid::new(
+ vec![
+ "nuihuneihsoenhisenouiuteinhdauisdonhuisudoiosadiuohnteihaosdinhteuieudi",
+ "oudisnuthasuouneohbueobaugceoduhbsauglcobeuhnaeouosbubaoecgueoubeohubeo",
+ ],
+ GridOptions {
+ direction: Direction::TopToBottom,
+ filling: Filling::Spaces(2),
+ width: 40,
+ },
+ );
+
+ assert_eq!(grid.row_count(), 2);
}
#[test]
fn that_example_from_earlier() {
- let mut grid = Grid::new(GridOptions {
- filling: Filling::Spaces(1),
- direction: Direction::LeftToRight,
- });
-
- for s in &[
- "one", "two", "three", "four", "five", "six", "seven", "eight", "nine", "ten", "eleven",
- "twelve",
- ] {
- grid.add(Cell::from(*s));
- }
+ let grid = Grid::new(
+ vec![
+ "one", "two", "three", "four", "five", "six", "seven", "eight", "nine", "ten",
+ "eleven", "twelve",
+ ],
+ GridOptions {
+ filling: Filling::Spaces(1),
+ direction: Direction::LeftToRight,
+ width: 24,
+ },
+ );
let bits = "one two three four\nfive six seven eight\nnine ten eleven twelve\n";
- assert_eq!(grid.fit_into_width(24).unwrap().to_string(), bits);
- assert_eq!(grid.fit_into_width(24).unwrap().row_count(), 3);
+ assert_eq!(grid.to_string(), bits);
+ assert_eq!(grid.row_count(), 3);
}
#[test]
fn number_grid_with_pipe() {
- let mut grid = Grid::new(GridOptions {
- filling: Filling::Text("|".into()),
- direction: Direction::LeftToRight,
- });
-
- for s in &[
- "one", "two", "three", "four", "five", "six", "seven", "eight", "nine", "ten", "eleven",
- "twelve",
- ] {
- grid.add(Cell::from(*s));
- }
+ let grid = Grid::new(
+ vec![
+ "one", "two", "three", "four", "five", "six", "seven", "eight", "nine", "ten",
+ "eleven", "twelve",
+ ],
+ GridOptions {
+ filling: Filling::Text("|".into()),
+ direction: Direction::LeftToRight,
+ width: 24,
+ },
+ );
let bits = "one |two|three |four\nfive|six|seven |eight\nnine|ten|eleven|twelve\n";
- assert_eq!(grid.fit_into_width(24).unwrap().to_string(), bits);
- assert_eq!(grid.fit_into_width(24).unwrap().row_count(), 3);
+ assert_eq!(grid.to_string(), bits);
+ assert_eq!(grid.row_count(), 3);
}
#[test]
fn huge_separator() {
- let mut grid = Grid::new(GridOptions {
- filling: Filling::Spaces(100),
- direction: Direction::LeftToRight,
- });
-
- grid.add("a".into());
- grid.add("b".into());
-
- assert!(grid.fit_into_width(99).is_none());
+ let grid = Grid::new(
+ vec!["a", "b"],
+ GridOptions {
+ filling: Filling::Spaces(100),
+ direction: Direction::LeftToRight,
+ width: 99,
+ },
+ );
+ assert_eq!(grid.row_count(), 2);
}
#[test]
fn huge_yet_unused_separator() {
- let mut grid = Grid::new(GridOptions {
- filling: Filling::Spaces(100),
- direction: Direction::LeftToRight,
- });
-
- grid.add("abcd".into());
-
- let display = grid.fit_into_width(99).unwrap();
+ let grid = Grid::new(
+ vec!["abcd"],
+ GridOptions {
+ filling: Filling::Spaces(100),
+ direction: Direction::LeftToRight,
+ width: 99,
+ },
+ );
- assert_eq!(display.width(), 4);
- assert_eq!("abcd\n", display.to_string());
+ assert_eq!(grid.width(), 4);
+ assert_eq!("abcd\n", grid.to_string());
}
// Note: This behaviour is right or wrong depending on your terminal
@@ -169,17 +178,33 @@ fn huge_yet_unused_separator() {
// behaviour, unless we explicitly want to do that.
#[test]
fn emoji() {
- let mut grid = Grid::new(GridOptions {
- direction: Direction::LeftToRight,
- filling: Filling::Spaces(2),
- });
+ let grid = Grid::new(
+ vec!["🦀", "hello", "👩🔬", "hello"],
+ GridOptions {
+ direction: Direction::LeftToRight,
+ filling: Filling::Spaces(2),
+ width: 12,
+ },
+ );
+ assert_eq!("🦀 hello\n👩🔬 hello\n", grid.to_string());
+}
- for s in ["🦀", "hello", "👩🔬", "hello"] {
- grid.add(s.into());
- }
+// This test once underflowed, which should never happen. The test is just
+// checking that we do not get a panic.
+#[test]
+fn possible_underflow() {
+ let cells: Vec<_> = (0..48).map(|i| 2_isize.pow(i).to_string()).collect();
+
+ let grid = Grid::new(
+ cells,
+ GridOptions {
+ direction: Direction::TopToBottom,
+ filling: Filling::Text(" | ".into()),
+ width: 15,
+ },
+ );
- let display = grid.fit_into_width(12).unwrap();
- assert_eq!("🦀 hello\n👩🔬 hello\n", display.to_string());
+ println!("{}", grid);
}
// These test are based on the tests in uutils ls, to ensure we won't break
@@ -203,83 +228,78 @@ mod uutils_ls {
"test-width-1\ntest-width-2\ntest-width-3\ntest-width-4\n",
),
] {
- let mut grid = Grid::new(GridOptions {
- direction: Direction::TopToBottom,
- filling: Filling::Spaces(2),
- });
-
- for s in [
- "test-width-1",
- "test-width-2",
- "test-width-3",
- "test-width-4",
- ] {
- grid.add(s.into());
- }
-
- let display = grid.fit_into_width(width).unwrap();
- assert_eq!(expected, display.to_string());
+ let grid = Grid::new(
+ vec![
+ "test-width-1",
+ "test-width-2",
+ "test-width-3",
+ "test-width-4",
+ ],
+ GridOptions {
+ direction: Direction::TopToBottom,
+ filling: Filling::Spaces(2),
+ width,
+ },
+ );
+ assert_eq!(expected, grid.to_string());
}
}
#[test]
fn across_width_30() {
- let mut grid = Grid::new(GridOptions {
- direction: Direction::LeftToRight,
- filling: Filling::Spaces(2),
- });
-
- for s in [
- "test-across1",
- "test-across2",
- "test-across3",
- "test-across4",
- ] {
- grid.add(s.into());
- }
+ let grid = Grid::new(
+ vec![
+ "test-across1",
+ "test-across2",
+ "test-across3",
+ "test-across4",
+ ],
+ GridOptions {
+ direction: Direction::LeftToRight,
+ filling: Filling::Spaces(2),
+ width: 30,
+ },
+ );
- let display = grid.fit_into_width(30).unwrap();
assert_eq!(
"test-across1 test-across2\ntest-across3 test-across4\n",
- display.to_string()
+ grid.to_string()
);
}
#[test]
fn columns_width_30() {
- let mut grid = Grid::new(GridOptions {
- direction: Direction::TopToBottom,
- filling: Filling::Spaces(2),
- });
-
- for s in [
- "test-columns1",
- "test-columns2",
- "test-columns3",
- "test-columns4",
- ] {
- grid.add(s.into());
- }
+ let grid = Grid::new(
+ vec![
+ "test-columns1",
+ "test-columns2",
+ "test-columns3",
+ "test-columns4",
+ ],
+ GridOptions {
+ direction: Direction::TopToBottom,
+ filling: Filling::Spaces(2),
+ width: 30,
+ },
+ );
- let display = grid.fit_into_width(30).unwrap();
assert_eq!(
"test-columns1 test-columns3\ntest-columns2 test-columns4\n",
- display.to_string()
+ grid.to_string()
);
}
#[test]
fn three_short_one_long() {
- let mut grid = Grid::new(GridOptions {
- direction: Direction::TopToBottom,
- filling: Filling::Spaces(2),
- });
-
- for s in ["a", "b", "a-long-name", "z"] {
- grid.add(s.into());
- }
+ let grid = Grid::new(
+ vec!["a", "b", "a-long-name", "z"],
+ GridOptions {
+ direction: Direction::TopToBottom,
+ filling: Filling::Spaces(2),
+ width: 15,
+ },
+ );
- let display = grid.fit_into_width(15).unwrap();
- assert_eq!("a a-long-name\nb z\n", display.to_string());
+ assert_eq!("a a-long-name\nb z\n", grid.to_string());
}
}
|