Skip to content

Commit c8db34b

Browse files
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>
1 parent ad32b2c commit c8db34b

File tree

5 files changed

+204
-21
lines changed

5 files changed

+204
-21
lines changed

README.md

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,8 +36,10 @@ and a set of options.
3636
There are three options that must be specified in the [`GridOptions`] value that
3737
dictate how the grid is formatted:
3838

39-
- [`filling`][filling]: what to put in between two columns — either a number of
40-
spaces, or a text string;
39+
- [`filling`][filling]: how to fill empty space between columns:
40+
- [`Filling::Spaces`][Spaces] number of spaces between columns;
41+
- [`Filling::Text`][Text] text string separator between columns;
42+
- [`Filling::Tabs`][Tabs] special option which allows to set number of spaces between columns and set the size of `\t` character.
4143
- [`direction`][direction]: specifies whether the cells should go along rows, or
4244
columns:
4345
- [`Direction::LeftToRight`][LeftToRight] starts them in the top left and
@@ -94,6 +96,9 @@ nine ten eleven twelve
9496
[width]: https://docs.rs/uutils_term_grid/latest/term_grid/struct.GridOptions.html#structfield.width
9597
[LeftToRight]: https://docs.rs/uutils_term_grid/latest/term_grid/enum.Direction.html#variant.LeftToRight
9698
[TopToBottom]: https://docs.rs/uutils_term_grid/latest/term_grid/enum.Direction.html#variant.TopToBottom
99+
[Spaces]: https://docs.rs/uutils_term_grid/latest/term_grid/enum.Filling.html#variant.Spaces
100+
[Text]: https://docs.rs/uutils_term_grid/latest/term_grid/enum.Filling.html#variant.Text
101+
[Tabs]:https://docs.rs/uutils_term_grid/latest/term_grid/enum.Filling.html#variant.Tabs
97102

98103
## Width of grid cells
99104

examples/basic.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ use term_grid::{Direction, Filling, Grid, GridOptions};
1111
// 8 | 1024 | 131072 | 16777216 | 2147483648 | 274877906944 | 35184372088832
1212
// 16 | 2048 | 262144 | 33554432 | 4294967296 | 549755813888 | 70368744177664
1313
// 32 | 4096 | 524288 | 67108864 | 8589934592 | 1099511627776 | 140737488355328
14-
// 64 | 8192 | 1048576 | 134217728 | 17179869184 | 2199023255552 |
14+
// 64 | 8192 | 1048576 | 134217728 | 17179869184 | 2199023255552
1515

1616
fn main() {
1717
let cells: Vec<_> = (0..48).map(|i| 2_isize.pow(i).to_string()).collect();

examples/tabs.rs

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
// For the full copyright and license information, please view the LICENSE
2+
// file that was distributed with this source code.
3+
4+
use term_grid::{Direction, Filling, Grid, GridOptions, DEFAULT_SEPARATOR_SIZE};
5+
6+
// This produces:
7+
//
8+
// 1···128↹··16384↹···2097152····268435456↹···34359738368├┤··4398046511104␊
9+
// 2···256↹··32768↹···4194304····536870912↹···68719476736├┤··8796093022208␊
10+
// 4···512↹··65536↹···8388608····1073741824···137438953472↹··17592186044416␊
11+
// 8···1024··131072···16777216···2147483648···274877906944↹··35184372088832␊
12+
// 16··2048··262144···33554432···4294967296···549755813888↹··70368744177664␊
13+
// 32··4096··524288···67108864···8589934592···1099511627776··140737488355328␊
14+
// 64··8192··1048576··134217728··17179869184··2199023255552␊
15+
16+
fn main() {
17+
let cells: Vec<_> = (0..48).map(|i| 2_isize.pow(i).to_string()).collect();
18+
19+
let grid = Grid::new(
20+
cells,
21+
GridOptions {
22+
direction: Direction::TopToBottom,
23+
filling: Filling::Tabs {
24+
spaces: DEFAULT_SEPARATOR_SIZE,
25+
tab_size: 8,
26+
},
27+
width: 80,
28+
},
29+
);
30+
31+
println!("{}", grid);
32+
}

src/lib.rs

Lines changed: 76 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,12 @@
1313
use ansi_width::ansi_width;
1414
use std::fmt;
1515

16+
/// Number of spaces in one \t.
17+
pub const SPACES_IN_TAB: usize = 8;
18+
19+
/// Default size for separator in spaces.
20+
pub const DEFAULT_SEPARATOR_SIZE: usize = 2;
21+
1622
/// Direction cells should be written in: either across or downwards.
1723
#[derive(PartialEq, Eq, Debug, Copy, Clone)]
1824
pub enum Direction {
@@ -37,13 +43,22 @@ pub enum Filling {
3743
///
3844
/// `"|"` is a common choice.
3945
Text(String),
46+
47+
/// Fill spaces with `\t`
48+
Tabs {
49+
/// A number of spaces
50+
spaces: usize,
51+
/// Size of `\t` in spaces
52+
tab_size: usize,
53+
},
4054
}
4155

4256
impl Filling {
4357
fn width(&self) -> usize {
4458
match self {
4559
Filling::Spaces(w) => *w,
4660
Filling::Text(t) => ansi_width(t),
61+
Filling::Tabs { spaces, .. } => *spaces,
4762
}
4863
}
4964
}
@@ -257,9 +272,15 @@ impl<T: AsRef<str>> Grid<T> {
257272

258273
impl<T: AsRef<str>> fmt::Display for Grid<T> {
259274
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
260-
let separator = match &self.options.filling {
261-
Filling::Spaces(n) => " ".repeat(*n),
262-
Filling::Text(s) => s.clone(),
275+
// If cells are empty then, nothing to print, skip.
276+
if self.cells.is_empty() {
277+
return Ok(());
278+
}
279+
280+
let (tab_size, separator) = match &self.options.filling {
281+
Filling::Spaces(n) => (0, " ".repeat(*n)),
282+
Filling::Text(s) => (0, s.clone()),
283+
Filling::Tabs { spaces, tab_size } => (*tab_size, " ".repeat(*spaces)),
263284
};
264285

265286
// 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> {
270291
// We overestimate how many spaces we need, but this is not
271292
// part of the loop and it's therefore not super important to
272293
// get exactly right.
273-
let padding = " ".repeat(self.widest_cell_width);
294+
let padding = " ".repeat(self.widest_cell_width + self.options.filling.width());
274295

275296
for y in 0..self.dimensions.num_lines {
297+
// Current position on the line.
298+
let mut cursor: usize = 0;
276299
for x in 0..self.dimensions.widths.len() {
277-
let num = match self.options.direction {
278-
Direction::LeftToRight => y * self.dimensions.widths.len() + x,
279-
Direction::TopToBottom => y + self.dimensions.num_lines * x,
300+
// Calculate position of the current element of the grid
301+
// in cells and widths vectors and the offset to the next value.
302+
let (current, offset) = match self.options.direction {
303+
Direction::LeftToRight => (y * self.dimensions.widths.len() + x, 1),
304+
Direction::TopToBottom => {
305+
(y + self.dimensions.num_lines * x, self.dimensions.num_lines)
306+
}
280307
};
281308

282-
// Abandon a line mid-way through if that’s where the cells end
283-
if num >= self.cells.len() {
284-
continue;
309+
// Abandon a line mid-way through if that’s where the cells end.
310+
if current >= self.cells.len() {
311+
break;
285312
}
286313

287-
let contents = &self.cells[num];
288-
let width = self.widths[num];
314+
// Last in row checks only the predefined grid width.
315+
// It does not check if there will be more entries.
316+
// For this purpose we define next value as well.
317+
// This prevents printing separator after the actual last value in a row.
289318
let last_in_row = x == self.dimensions.widths.len() - 1;
290-
319+
let contents = &self.cells[current];
320+
let width = self.widths[current];
291321
let col_width = self.dimensions.widths[x];
292322
let padding_size = col_width - width;
293323

@@ -305,11 +335,40 @@ impl<T: AsRef<str>> fmt::Display for Grid<T> {
305335
// We also only call `write_str` when we actually need padding as
306336
// another optimization.
307337
f.write_str(contents.as_ref())?;
308-
if !last_in_row {
309-
if padding_size > 0 {
310-
f.write_str(&padding[0..padding_size])?;
311-
}
338+
339+
// In case this entry was the last on the current line,
340+
// there is no need to print the separator and padding.
341+
if last_in_row || current + offset >= self.cells.len() {
342+
break;
343+
}
344+
345+
// Special case if tab size was not set. Fill with spaces and separator.
346+
if tab_size == 0 {
347+
f.write_str(&padding[..padding_size])?;
312348
f.write_str(&separator)?;
349+
} else {
350+
// Move cursor to the end of the current contents.
351+
cursor += width;
352+
let total_spaces = padding_size + self.options.filling.width();
353+
// The size of \t can be inconsistent in terminal.
354+
// Tab stops are relative to the cursor position e.g.,
355+
// * cursor = 0, \t moves to column 8;
356+
// * cursor = 5, \t moves to column 8 (3 spaces);
357+
// * cursor = 9, \t moves to column 16 (7 spaces).
358+
// Calculate the nearest \t position in relation to cursor.
359+
let closest_tab = tab_size - (cursor % tab_size);
360+
361+
if closest_tab > total_spaces {
362+
f.write_str(&padding[..total_spaces])?;
363+
} else {
364+
let rest_spaces = total_spaces - closest_tab;
365+
let tabs = 1 + (rest_spaces / tab_size);
366+
let spaces = rest_spaces % tab_size;
367+
f.write_str(&"\t".repeat(tabs))?;
368+
f.write_str(&padding[..spaces])?;
369+
}
370+
371+
cursor += total_spaces;
313372
}
314373
}
315374
f.write_str("\n")?;

tests/test.rs

Lines changed: 88 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33

44
// spell-checker:ignore underflowed
55

6-
use term_grid::{Direction, Filling, Grid, GridOptions};
6+
use term_grid::{Direction, Filling, Grid, GridOptions, DEFAULT_SEPARATOR_SIZE, SPACES_IN_TAB};
77

88
#[test]
99
fn no_items() {
@@ -238,6 +238,93 @@ fn eza_many_folders() {
238238
assert_eq!(grid.row_count(), 20);
239239
}
240240

241+
#[test]
242+
fn filling_with_tabs() {
243+
let grid = Grid::new(
244+
vec![
245+
"one", "two", "three", "four", "five", "six", "seven", "eight", "nine", "ten",
246+
"eleven", "twelve",
247+
],
248+
GridOptions {
249+
direction: Direction::LeftToRight,
250+
filling: Filling::Tabs {
251+
spaces: DEFAULT_SEPARATOR_SIZE,
252+
tab_size: 2,
253+
},
254+
width: 24,
255+
},
256+
);
257+
258+
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";
259+
assert_eq!(grid.to_string(), bits);
260+
assert_eq!(grid.row_count(), 4);
261+
}
262+
263+
#[test]
264+
fn padding_bigger_than_widest() {
265+
let grid = Grid::new(
266+
vec!["1", "2", "3"],
267+
GridOptions {
268+
direction: Direction::LeftToRight,
269+
filling: Filling::Tabs {
270+
spaces: DEFAULT_SEPARATOR_SIZE,
271+
tab_size: SPACES_IN_TAB,
272+
},
273+
width: 20,
274+
},
275+
);
276+
277+
let bits = "1 2 3\n";
278+
assert_eq!(grid.to_string(), bits);
279+
}
280+
281+
#[test]
282+
fn odd_number_of_entries() {
283+
let cells = vec!["one", "two", "three", "four", "five"];
284+
let grid = Grid::new(
285+
cells.clone(),
286+
GridOptions {
287+
direction: Direction::LeftToRight,
288+
filling: Filling::Spaces(2),
289+
width: 15,
290+
},
291+
);
292+
293+
assert_eq!(grid.to_string(), "one two\nthree four\nfive\n");
294+
295+
let grid = Grid::new(
296+
cells.clone(),
297+
GridOptions {
298+
direction: Direction::TopToBottom,
299+
filling: Filling::Spaces(2),
300+
width: 15,
301+
},
302+
);
303+
304+
assert_eq!(grid.to_string(), "one four\ntwo five\nthree\n");
305+
}
306+
307+
#[test]
308+
fn different_size_separator_with_tabs() {
309+
let grid = Grid::new(
310+
vec![
311+
"one", "two", "three", "four", "five", "six", "seven", "eight", "nine", "ten",
312+
"eleven", "twelve",
313+
],
314+
GridOptions {
315+
direction: Direction::LeftToRight,
316+
filling: Filling::Tabs {
317+
spaces: 4,
318+
tab_size: 2,
319+
},
320+
width: 40,
321+
},
322+
);
323+
324+
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";
325+
assert_eq!(grid.to_string(), bits);
326+
}
327+
241328
// These test are based on the tests in uutils ls, to ensure we won't break
242329
// it while editing this library.
243330
mod uutils_ls {

0 commit comments

Comments
 (0)