mirror of
https://github.com/uutils/uutils-term-grid.git
synced 2026-06-10 16:13:01 -07:00
add \t filling option (#46)
* chore(deps): update codecov/codecov-action action to v5 * add new Tabs filling and rework fmt process * add filling_with_tabs test * add tabs example * update readme * fix misspelling * fix fmt in tests * optimize padding size calc * remove separator duplication to reduce memory usage * make DEFAULT_SEPARATOR_SIZE pub * fix padding calculation * fix required padding is bigger than widest cell * add next entry check before printing the separator * update docs for tabs * update comments * add diff size separator with Tabs * simplify the tabs number calculation * update readme * remove comments * change last in row check to break * fix some grammar in readme * update docs with quotes for \t * rename postion vars and add comment * rename neares tab var and update comment --------- Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
This commit is contained in:
co-authored by
renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
parent
ad32b2c6b3
commit
c8db34bd41
@@ -36,8 +36,10 @@ and a set of options.
|
||||
There are three options that must be specified in the [`GridOptions`] value that
|
||||
dictate how the grid is formatted:
|
||||
|
||||
- [`filling`][filling]: what to put in between two columns — either a number of
|
||||
spaces, or a text string;
|
||||
- [`filling`][filling]: how to fill empty space between columns:
|
||||
- [`Filling::Spaces`][Spaces] number of spaces between columns;
|
||||
- [`Filling::Text`][Text] text string separator between columns;
|
||||
- [`Filling::Tabs`][Tabs] special option which allows to set number of spaces between columns and set the size of `\t` character.
|
||||
- [`direction`][direction]: specifies whether the cells should go along rows, or
|
||||
columns:
|
||||
- [`Direction::LeftToRight`][LeftToRight] starts them in the top left and
|
||||
@@ -94,6 +96,9 @@ nine ten eleven twelve
|
||||
[width]: https://docs.rs/uutils_term_grid/latest/term_grid/struct.GridOptions.html#structfield.width
|
||||
[LeftToRight]: https://docs.rs/uutils_term_grid/latest/term_grid/enum.Direction.html#variant.LeftToRight
|
||||
[TopToBottom]: https://docs.rs/uutils_term_grid/latest/term_grid/enum.Direction.html#variant.TopToBottom
|
||||
[Spaces]: https://docs.rs/uutils_term_grid/latest/term_grid/enum.Filling.html#variant.Spaces
|
||||
[Text]: https://docs.rs/uutils_term_grid/latest/term_grid/enum.Filling.html#variant.Text
|
||||
[Tabs]:https://docs.rs/uutils_term_grid/latest/term_grid/enum.Filling.html#variant.Tabs
|
||||
|
||||
## Width of grid cells
|
||||
|
||||
|
||||
+1
-1
@@ -11,7 +11,7 @@ use term_grid::{Direction, Filling, Grid, GridOptions};
|
||||
// 8 | 1024 | 131072 | 16777216 | 2147483648 | 274877906944 | 35184372088832
|
||||
// 16 | 2048 | 262144 | 33554432 | 4294967296 | 549755813888 | 70368744177664
|
||||
// 32 | 4096 | 524288 | 67108864 | 8589934592 | 1099511627776 | 140737488355328
|
||||
// 64 | 8192 | 1048576 | 134217728 | 17179869184 | 2199023255552 |
|
||||
// 64 | 8192 | 1048576 | 134217728 | 17179869184 | 2199023255552
|
||||
|
||||
fn main() {
|
||||
let cells: Vec<_> = (0..48).map(|i| 2_isize.pow(i).to_string()).collect();
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
// 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, DEFAULT_SEPARATOR_SIZE};
|
||||
|
||||
// This produces:
|
||||
//
|
||||
// 1···128↹··16384↹···2097152····268435456↹···34359738368├┤··4398046511104␊
|
||||
// 2···256↹··32768↹···4194304····536870912↹···68719476736├┤··8796093022208␊
|
||||
// 4···512↹··65536↹···8388608····1073741824···137438953472↹··17592186044416␊
|
||||
// 8···1024··131072···16777216···2147483648···274877906944↹··35184372088832␊
|
||||
// 16··2048··262144···33554432···4294967296···549755813888↹··70368744177664␊
|
||||
// 32··4096··524288···67108864···8589934592···1099511627776··140737488355328␊
|
||||
// 64··8192··1048576··134217728··17179869184··2199023255552␊
|
||||
|
||||
fn main() {
|
||||
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::Tabs {
|
||||
spaces: DEFAULT_SEPARATOR_SIZE,
|
||||
tab_size: 8,
|
||||
},
|
||||
width: 80,
|
||||
},
|
||||
);
|
||||
|
||||
println!("{}", grid);
|
||||
}
|
||||
+76
-17
@@ -13,6 +13,12 @@
|
||||
use ansi_width::ansi_width;
|
||||
use std::fmt;
|
||||
|
||||
/// Number of spaces in one \t.
|
||||
pub const SPACES_IN_TAB: usize = 8;
|
||||
|
||||
/// Default size for separator in spaces.
|
||||
pub const DEFAULT_SEPARATOR_SIZE: usize = 2;
|
||||
|
||||
/// Direction cells should be written in: either across or downwards.
|
||||
#[derive(PartialEq, Eq, Debug, Copy, Clone)]
|
||||
pub enum Direction {
|
||||
@@ -37,6 +43,14 @@ pub enum Filling {
|
||||
///
|
||||
/// `"|"` is a common choice.
|
||||
Text(String),
|
||||
|
||||
/// Fill spaces with `\t`
|
||||
Tabs {
|
||||
/// A number of spaces
|
||||
spaces: usize,
|
||||
/// Size of `\t` in spaces
|
||||
tab_size: usize,
|
||||
},
|
||||
}
|
||||
|
||||
impl Filling {
|
||||
@@ -44,6 +58,7 @@ impl Filling {
|
||||
match self {
|
||||
Filling::Spaces(w) => *w,
|
||||
Filling::Text(t) => ansi_width(t),
|
||||
Filling::Tabs { spaces, .. } => *spaces,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -257,9 +272,15 @@ impl<T: AsRef<str>> Grid<T> {
|
||||
|
||||
impl<T: AsRef<str>> fmt::Display for Grid<T> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
|
||||
let separator = match &self.options.filling {
|
||||
Filling::Spaces(n) => " ".repeat(*n),
|
||||
Filling::Text(s) => s.clone(),
|
||||
// If cells are empty then, nothing to print, skip.
|
||||
if self.cells.is_empty() {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let (tab_size, separator) = match &self.options.filling {
|
||||
Filling::Spaces(n) => (0, " ".repeat(*n)),
|
||||
Filling::Text(s) => (0, s.clone()),
|
||||
Filling::Tabs { spaces, tab_size } => (*tab_size, " ".repeat(*spaces)),
|
||||
};
|
||||
|
||||
// Initialize a buffer of spaces. The idea here is that any cell
|
||||
@@ -270,24 +291,33 @@ impl<T: AsRef<str>> fmt::Display for Grid<T> {
|
||||
// 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.widest_cell_width);
|
||||
let padding = " ".repeat(self.widest_cell_width + self.options.filling.width());
|
||||
|
||||
for y in 0..self.dimensions.num_lines {
|
||||
// Current position on the line.
|
||||
let mut cursor: usize = 0;
|
||||
for x in 0..self.dimensions.widths.len() {
|
||||
let num = match self.options.direction {
|
||||
Direction::LeftToRight => y * self.dimensions.widths.len() + x,
|
||||
Direction::TopToBottom => y + self.dimensions.num_lines * x,
|
||||
// Calculate position of the current element of the grid
|
||||
// in cells and widths vectors and the offset to the next value.
|
||||
let (current, offset) = match self.options.direction {
|
||||
Direction::LeftToRight => (y * self.dimensions.widths.len() + x, 1),
|
||||
Direction::TopToBottom => {
|
||||
(y + self.dimensions.num_lines * x, self.dimensions.num_lines)
|
||||
}
|
||||
};
|
||||
|
||||
// Abandon a line mid-way through if that’s where the cells end
|
||||
if num >= self.cells.len() {
|
||||
continue;
|
||||
// Abandon a line mid-way through if that’s where the cells end.
|
||||
if current >= self.cells.len() {
|
||||
break;
|
||||
}
|
||||
|
||||
let contents = &self.cells[num];
|
||||
let width = self.widths[num];
|
||||
// Last in row checks only the predefined grid width.
|
||||
// It does not check if there will be more entries.
|
||||
// For this purpose we define next value as well.
|
||||
// This prevents printing separator after the actual last value in a row.
|
||||
let last_in_row = x == self.dimensions.widths.len() - 1;
|
||||
|
||||
let contents = &self.cells[current];
|
||||
let width = self.widths[current];
|
||||
let col_width = self.dimensions.widths[x];
|
||||
let padding_size = col_width - width;
|
||||
|
||||
@@ -305,11 +335,40 @@ impl<T: AsRef<str>> fmt::Display for Grid<T> {
|
||||
// We also only call `write_str` when we actually need padding as
|
||||
// another optimization.
|
||||
f.write_str(contents.as_ref())?;
|
||||
if !last_in_row {
|
||||
if padding_size > 0 {
|
||||
f.write_str(&padding[0..padding_size])?;
|
||||
}
|
||||
|
||||
// In case this entry was the last on the current line,
|
||||
// there is no need to print the separator and padding.
|
||||
if last_in_row || current + offset >= self.cells.len() {
|
||||
break;
|
||||
}
|
||||
|
||||
// Special case if tab size was not set. Fill with spaces and separator.
|
||||
if tab_size == 0 {
|
||||
f.write_str(&padding[..padding_size])?;
|
||||
f.write_str(&separator)?;
|
||||
} else {
|
||||
// Move cursor to the end of the current contents.
|
||||
cursor += width;
|
||||
let total_spaces = padding_size + self.options.filling.width();
|
||||
// The size of \t can be inconsistent in terminal.
|
||||
// Tab stops are relative to the cursor position e.g.,
|
||||
// * cursor = 0, \t moves to column 8;
|
||||
// * cursor = 5, \t moves to column 8 (3 spaces);
|
||||
// * cursor = 9, \t moves to column 16 (7 spaces).
|
||||
// Calculate the nearest \t position in relation to cursor.
|
||||
let closest_tab = tab_size - (cursor % tab_size);
|
||||
|
||||
if closest_tab > total_spaces {
|
||||
f.write_str(&padding[..total_spaces])?;
|
||||
} else {
|
||||
let rest_spaces = total_spaces - closest_tab;
|
||||
let tabs = 1 + (rest_spaces / tab_size);
|
||||
let spaces = rest_spaces % tab_size;
|
||||
f.write_str(&"\t".repeat(tabs))?;
|
||||
f.write_str(&padding[..spaces])?;
|
||||
}
|
||||
|
||||
cursor += total_spaces;
|
||||
}
|
||||
}
|
||||
f.write_str("\n")?;
|
||||
|
||||
+88
-1
@@ -3,7 +3,7 @@
|
||||
|
||||
// spell-checker:ignore underflowed
|
||||
|
||||
use term_grid::{Direction, Filling, Grid, GridOptions};
|
||||
use term_grid::{Direction, Filling, Grid, GridOptions, DEFAULT_SEPARATOR_SIZE, SPACES_IN_TAB};
|
||||
|
||||
#[test]
|
||||
fn no_items() {
|
||||
@@ -238,6 +238,93 @@ fn eza_many_folders() {
|
||||
assert_eq!(grid.row_count(), 20);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn filling_with_tabs() {
|
||||
let grid = Grid::new(
|
||||
vec![
|
||||
"one", "two", "three", "four", "five", "six", "seven", "eight", "nine", "ten",
|
||||
"eleven", "twelve",
|
||||
],
|
||||
GridOptions {
|
||||
direction: Direction::LeftToRight,
|
||||
filling: Filling::Tabs {
|
||||
spaces: DEFAULT_SEPARATOR_SIZE,
|
||||
tab_size: 2,
|
||||
},
|
||||
width: 24,
|
||||
},
|
||||
);
|
||||
|
||||
let bits = "one\t\t two\t\t three\nfour\t five\t\t six\nseven\t eight\t nine\nten\t\t eleven\t twelve\n";
|
||||
assert_eq!(grid.to_string(), bits);
|
||||
assert_eq!(grid.row_count(), 4);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn padding_bigger_than_widest() {
|
||||
let grid = Grid::new(
|
||||
vec!["1", "2", "3"],
|
||||
GridOptions {
|
||||
direction: Direction::LeftToRight,
|
||||
filling: Filling::Tabs {
|
||||
spaces: DEFAULT_SEPARATOR_SIZE,
|
||||
tab_size: SPACES_IN_TAB,
|
||||
},
|
||||
width: 20,
|
||||
},
|
||||
);
|
||||
|
||||
let bits = "1 2 3\n";
|
||||
assert_eq!(grid.to_string(), bits);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn odd_number_of_entries() {
|
||||
let cells = vec!["one", "two", "three", "four", "five"];
|
||||
let grid = Grid::new(
|
||||
cells.clone(),
|
||||
GridOptions {
|
||||
direction: Direction::LeftToRight,
|
||||
filling: Filling::Spaces(2),
|
||||
width: 15,
|
||||
},
|
||||
);
|
||||
|
||||
assert_eq!(grid.to_string(), "one two\nthree four\nfive\n");
|
||||
|
||||
let grid = Grid::new(
|
||||
cells.clone(),
|
||||
GridOptions {
|
||||
direction: Direction::TopToBottom,
|
||||
filling: Filling::Spaces(2),
|
||||
width: 15,
|
||||
},
|
||||
);
|
||||
|
||||
assert_eq!(grid.to_string(), "one four\ntwo five\nthree\n");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn different_size_separator_with_tabs() {
|
||||
let grid = Grid::new(
|
||||
vec![
|
||||
"one", "two", "three", "four", "five", "six", "seven", "eight", "nine", "ten",
|
||||
"eleven", "twelve",
|
||||
],
|
||||
GridOptions {
|
||||
direction: Direction::LeftToRight,
|
||||
filling: Filling::Tabs {
|
||||
spaces: 4,
|
||||
tab_size: 2,
|
||||
},
|
||||
width: 40,
|
||||
},
|
||||
);
|
||||
|
||||
let bits = "one\t\t\ttwo\t\t three\t\t four\nfive\t\tsix\t\t seven\t\t eight\nnine\t\tten\t\t eleven\t\t twelve\n";
|
||||
assert_eq!(grid.to_string(), bits);
|
||||
}
|
||||
|
||||
// These test are based on the tests in uutils ls, to ensure we won't break
|
||||
// it while editing this library.
|
||||
mod uutils_ls {
|
||||
|
||||
Reference in New Issue
Block a user