Skip to content

Commit

Permalink
fix(pad): center alignment bug on uneven width (#22)
Browse files Browse the repository at this point in the history
* fix(alignemnt): center alignment bug on uneven fixed, tests added

* fix(format): cargo fmt changes

* fix(docs): update README overview
  • Loading branch information
wilhelmagren committed May 5, 2024
1 parent 44569fc commit 95a9df7
Show file tree
Hide file tree
Showing 3 changed files with 149 additions and 26 deletions.
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "padder"
version = "1.0.0"
version = "1.1.0"
edition = "2021"
description = "Highly efficient data and string formatting library for Rust."
authors = [
Expand Down
22 changes: 12 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,20 +15,21 @@

</div>


## 🔎 Overview

Pad and format string slices and generic vectors efficiently with minimal memory allocation.
This crate has guaranteed performance improvements over the standard library `format!` macro.
Clone this repo and run `cargo bench` to see benchmark comparisons between our implementation and the standard library.
Pad and format virtually any generic slice or vector efficiently with minimal memory overhead. This crate has guaranteed performance improvements over the standard
library `format!` macro. Clone this repository and run `cargo bench` to see benchmark comparisons between this implementation and the standard library.

The library defines a core trait called `Source` which enables efficient padding on the type. It is currently implemented on three main types of datastructures:
the string slice `&str`, the generic slice `&[T]`, and also the generic vector `Vec<T>`. Note that the type `T` has to adhere to the trait bound `T: From<Symbol>`,
where `Symbol` is the Enum representing the available characters/symbols to pad and format with. If you want to extend the padding capabilities of the `Source` trait
with your own type `T`, then you need to also implement the `From<Symbol>` trait for your type `T`. See the implementations for examples on how to do this, [link](https://github.com/firelink-data/padder/blob/main/src/lib.rs).

The library defines a core trait called `Source` and also has this implemented on two main
types of datastructures, the string slice `&str` and a generic vector `Vec<T>` with some trait bounds on the type `T`.
This allows for any developer to implement the trait on any datastructure that they want to be
able to pad or format using any of the padding modes defined in the crate.

## 📦 Installation

The easiest way to include *padder* in your projects is by using the [Cargo](https://crates.io/) package manager.
The easiest way to include *padder* in your own crate is by using the [Cargo](https://crates.io/) package manager.
```
$ cargo add padder
```
Expand All @@ -40,6 +41,7 @@ $ cd padder
$ cargo build --release
```


## 🚀 Examples

Adding *padder* to your crate dependecy will bring the `Source` trait into scope and allow padding.
Expand All @@ -50,7 +52,7 @@ You can for example pad string slices very easily in the following way:
let padded: String = "cool".pad(10, Alignment::Center, Symbol::Zero);
```

which would produce the padded String `000cool000`. You can also pad to an already allocated buffer, allowing you full control of heap allocations, like below:
which would produce the padded String `000cool000`. You can also pad to an already allocated buffer, granting you full control of any heap allocations, like below:

```rust
let width: usize = 8;
Expand All @@ -59,7 +61,7 @@ let original = vec![13u8, 9, 128, 81];
original.pad_and_push_to_buffer(width, Alignment::Right, Symbol::Whitespace, output);
```

There also exists two wrapper methods simply called `pad` and `pad_and_push_to_buffer` which allows padding any object as long as it implements the `Source` trait.
There also exists two wrapper methods simply called `pad` and `pad_and_push_to_buffer` which allows padding on any type as long as it implements the `Source` trait.
You can for example use these functions like below:
```rust
// pad
Expand Down
151 changes: 136 additions & 15 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -174,9 +174,9 @@ where
match mode {
Alignment::Left => self[0..width].to_string(),
Alignment::Right => self[(self.len() - width)..].to_string(),
Alignment::Center => {
self[(self.len() / 2 - width / 2)..(self.len() / 2 + width / 2)].to_string()
}
Alignment::Center => self
[(self.len() / 2 - width / 2)..(self.len() / 2 + width / 2 + width % 2)]
.to_string(),
}
}

Expand All @@ -185,16 +185,15 @@ where
return self.slice_to_fit(width, mode);
}

let mut output = String::with_capacity(width);
let diff: usize = width - self.len();

if diff == 0 {
return self.to_string();
}

let (lpad, rpad) = mode.left_right_padding(diff);
let pad_char: char = symbol.into();

let mut output = String::with_capacity(width);
(0..lpad).for_each(|_| output.push(pad_char));
output.push_str(self);
(0..rpad).for_each(|_| output.push(pad_char));
Expand Down Expand Up @@ -227,9 +226,9 @@ where
match mode {
Alignment::Left => self[0..width].to_vec(),
Alignment::Right => self[(self.len() - width)..].to_vec(),
Alignment::Center => {
self[(self.len() / 2 - width / 2)..(self.len() / 2 + width / 2)].to_vec()
}
Alignment::Center => self
[(self.len() / 2 - width / 2)..(self.len() / 2 + width / 2 + width % 2)]
.to_vec(),
}
}

Expand All @@ -238,16 +237,15 @@ where
return self.slice_to_fit(width, mode);
}

let mut output: Vec<T> = Vec::with_capacity(width);
let diff: usize = width - self.len();

if diff == 0 {
return self.to_vec();
}

let (lpad, rpad) = mode.left_right_padding(diff);
let pad_type: &[T] = symbol.into();

let mut output: Vec<T> = Vec::with_capacity(width);
(0..lpad).for_each(|_| output.extend_from_slice(pad_type));
output.extend_from_slice(self);
(0..rpad).for_each(|_| output.extend_from_slice(pad_type));
Expand Down Expand Up @@ -280,9 +278,9 @@ where
match mode {
Alignment::Left => self[..width].to_vec(),
Alignment::Right => self[(self.len() - width)..].to_vec(),
Alignment::Center => {
self[(self.len() / 2 - width / 2)..(self.len() / 2 + width / 2)].to_vec()
}
Alignment::Center => self
[(self.len() / 2 - width / 2)..(self.len() / 2 + width / 2 + width % 2)]
.to_vec(),
}
}

Expand All @@ -291,16 +289,15 @@ where
return self.slice_to_fit(width, mode);
}

let mut output: Vec<T> = Vec::with_capacity(width);
let diff: usize = width - self.len();

if diff == 0 {
return self.to_vec();
}

let (lpad, rpad) = mode.left_right_padding(diff);
let pad_type: &[T] = symbol.into();

let mut output: Vec<T> = Vec::with_capacity(width);
(0..lpad).for_each(|_| output.extend_from_slice(pad_type));
output.extend_from_slice(self);
(0..rpad).for_each(|_| output.extend_from_slice(pad_type));
Expand Down Expand Up @@ -540,4 +537,128 @@ mod tests {
let expected = " hejjj ".to_string();
assert_eq!(expected, output);
}

#[test]
fn str_truncate_left_uneven() {
let output = "kappa".pad(3, Alignment::Left, Symbol::Hyphen);
let expected = "kap".to_string();
assert_eq!(expected, output);
}

#[test]
fn str_truncate_left_even() {
let output = "kappa".pad(2, Alignment::Left, Symbol::Hyphen);
let expected = "ka".to_string();
assert_eq!(expected, output);
}

#[test]
fn str_truncate_right() {
let output = "kappa".pad(3, Alignment::Right, Symbol::Hyphen);
let expected = "ppa".to_string();
assert_eq!(expected, output);
}

#[test]
fn str_truncate_center_uneven() {
let output = "kappa".pad(3, Alignment::Center, Symbol::Hyphen);
let expected = "app".to_string();
assert_eq!(expected, output);
}

#[test]
fn str_truncate_center_even() {
let output = "kappa".pad(2, Alignment::Center, Symbol::Hyphen);
let expected = "ap".to_string();
assert_eq!(expected, output);
}

#[test]
fn str_no_pad_required() {
let output = "kappa".pad(5, Alignment::Right, Symbol::Hyphen);
let expected = "kappa".to_string();
assert_eq!(expected, output);
}

#[test]
fn slice_truncate_left() {
let output = vec![0u8, 1, 2, 3, 4]
.as_slice()
.pad(3, Alignment::Left, Symbol::Whitespace);
let expected = vec![0u8, 1, 2];
assert_eq!(expected, output);
}

#[test]
fn slice_truncate_right() {
let output =
vec![0u8, 1, 2, 3, 4, 5, 6]
.as_slice()
.pad(5, Alignment::Right, Symbol::Hyphen);
let expected = vec![2u8, 3, 4, 5, 6];
assert_eq!(expected, output);
}

#[test]
fn slice_truncate_center_uneven() {
let output = vec![0u8, 1, 2, 3, 4]
.as_slice()
.pad(3, Alignment::Center, Symbol::Whitespace);
let expected = vec![1u8, 2, 3];
assert_eq!(expected, output);
}

#[test]
fn slice_truncate_center_even() {
let output = vec![0u8, 1, 2, 3, 4]
.as_slice()
.pad(4, Alignment::Center, Symbol::Whitespace);
let expected = vec![0u8, 1, 2, 3];
assert_eq!(expected, output);
}

#[test]
fn vec_truncate_left() {
let output = vec![0u8, 1, 2, 3, 4].pad(3, Alignment::Left, Symbol::Whitespace);
let expected = vec![0u8, 1, 2];
assert_eq!(expected, output);
}

#[test]
fn vec_truncate_right() {
let output = vec![0u8, 1, 2, 3, 4, 5, 6].pad(5, Alignment::Right, Symbol::Hyphen);
let expected = vec![2u8, 3, 4, 5, 6];
assert_eq!(expected, output);
}

#[test]
fn vec_truncate_center_uneven() {
let output = vec![0u8, 1, 2, 3, 4].pad(3, Alignment::Center, Symbol::Whitespace);
let expected = vec![1u8, 2, 3];
assert_eq!(expected, output);
}

#[test]
fn vec_truncate_center_even() {
let output = vec![0u8, 1, 2, 3, 4].pad(4, Alignment::Center, Symbol::Whitespace);
let expected = vec![0u8, 1, 2, 3];
assert_eq!(expected, output);
}

#[test]
fn vec_pad_and_push_to_buffer_right_zero() {
let width: usize = 8;
let source = vec!['a', 'b', 'c', 'd', 'e'];
let mut buffer = Vec::with_capacity(width);
source.pad_and_push_to_buffer(width, Alignment::Right, Symbol::Zero, &mut buffer);
let expected = vec!['0', '0', '0', 'a', 'b', 'c', 'd', 'e'];
assert_eq!(expected, buffer);
}

#[test]
fn vec_no_pad_required() {
let output = vec![100u8, 14, 15, 98].pad(4, Alignment::Center, Symbol::Hyphen);
let expected = vec![100u8, 14, 15, 98];
assert_eq!(expected, output);
}
}

0 comments on commit 95a9df7

Please sign in to comment.