Skip to content

Commit

Permalink
[feat] padding, testing, and benchmarking (#3)
Browse files Browse the repository at this point in the history
* [bench] criterion benchmarking format! vs pad

* [feat] implement efficient padding

* [build] crate metadata, deps, and criterion bench conf

* [feat] cargo fmt and clippy lint changes
  • Loading branch information
wilhelmagren committed Dec 14, 2023
1 parent 1cfb477 commit 4909c2f
Show file tree
Hide file tree
Showing 6 changed files with 335 additions and 0 deletions.
36 changes: 36 additions & 0 deletions Cargo.toml
@@ -0,0 +1,36 @@
[package]
name = "padder"
version = "0.1.0"
edition = "2021"
description = "String formatting done right."
authors = [
"Wilhelm Ågren <wilhelmagren98@gmail.com>",
]

readme = "README.md"
license-file = "LICENSE"
homepage = "https://github.com/firelink-data/padder"
repository = "https://github.com/firelink-data/padder"

include = [
"**/*.rs",
"Cargo.toml",
"LICENSE",
"README.md",
]

[lib]
name = "padder"
path = "src/lib.rs"
test = true
bench = true
crate-type = [ "lib" ]

[dependencies]

[dev-dependencies]
criterion = "0.5.1"

[[bench]]
name = "bench_main"
harness = false
7 changes: 7 additions & 0 deletions benches/bench_main.rs
@@ -0,0 +1,7 @@
use criterion::criterion_main;
mod benchmarks;

criterion_main! {
benchmarks::format_whitespace_leftalign::formats,
benchmarks::pad_whitespace_leftalign::pads,
}
42 changes: 42 additions & 0 deletions benches/benchmarks/format_whitespace_leftalign.rs
@@ -0,0 +1,42 @@
use criterion::{black_box, criterion_group, Criterion};

pub fn format_whitespace_10_leftalign(c: &mut Criterion) {
let width: usize = 10;
c.bench_function("format! ws 10 la", |b| {
b.iter(|| black_box(format!("{:<width$}", "hej")))
});
}

pub fn format_whitespace_100_leftalign(c: &mut Criterion) {
let width: usize = 100;
c.bench_function("format! ws 100 la", |b| {
b.iter(|| black_box(format!("{:<width$}", "bingbong")))
});
}

pub fn format_whitespace_1000_leftalign(c: &mut Criterion) {
let width: usize = 1000;
c.bench_function("format! ws 1000 la", |b| {
b.iter(|| black_box(format!("{:<width$}", "Undercity is a cool capital...")))
});
}

pub fn format_whitespace_10000_leftalign(c: &mut Criterion) {
let width: usize = 10000;
c.bench_function("format! ws 10000 la", |b| {
b.iter(|| {
black_box(format!(
"{:<width$}",
"¤)(åäöåa this is a very long string... xd"
))
})
});
}

criterion_group!(
formats,
format_whitespace_10_leftalign,
format_whitespace_100_leftalign,
format_whitespace_1000_leftalign,
format_whitespace_10000_leftalign,
);
2 changes: 2 additions & 0 deletions benches/benchmarks/mod.rs
@@ -0,0 +1,2 @@
pub mod format_whitespace_leftalign;
pub mod pad_whitespace_leftalign;
51 changes: 51 additions & 0 deletions benches/benchmarks/pad_whitespace_leftalign.rs
@@ -0,0 +1,51 @@
use criterion::{black_box, criterion_group, Criterion};
use padder::whitespace;
use padder::Alignment;

pub fn pad_whitespace_10_leftalign(c: &mut Criterion) {
let width: usize = 10;
c.bench_function("pad ws 10 la", |b| {
b.iter(|| black_box(whitespace("hej", width, Alignment::Left)))
});
}

pub fn pad_whitespace_100_leftalign(c: &mut Criterion) {
let width: usize = 100;
c.bench_function("pad ws 100 la", |b| {
b.iter(|| black_box(whitespace("bingbong", width, Alignment::Left)))
});
}

pub fn pad_whitespace_1000_leftalign(c: &mut Criterion) {
let width: usize = 1000;
c.bench_function("pad ws 1000 la", |b| {
b.iter(|| {
black_box(whitespace(
"Undercity is a cool capital...",
width,
Alignment::Left,
))
})
});
}

pub fn pad_whitespace_10000_leftalign(c: &mut Criterion) {
let width: usize = 10000;
c.bench_function("pad ws 10000 la", |b| {
b.iter(|| {
black_box(whitespace(
"¤)(åäöåa this is a very long string... xd",
width,
Alignment::Left,
))
})
});
}

criterion_group!(
pads,
pad_whitespace_10_leftalign,
pad_whitespace_100_leftalign,
pad_whitespace_1000_leftalign,
pad_whitespace_10000_leftalign,
);
197 changes: 197 additions & 0 deletions src/lib.rs
@@ -0,0 +1,197 @@
/*
* MIT License
*
* Copyright (c) 2023 Firelink Data
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
* File created: 2023-12-14
* Last updated: 2023-12-14
*/

use std::fmt;

///
#[derive(Debug, Clone)]
pub struct WidthError;

///
impl fmt::Display for WidthError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(
f,
"Invalid target pad width for the provided string length."
)
}
}

///
pub enum Alignment {
Left,
Right,
Center,
}

///
pub fn whitespace(string: &str, width: usize, mode: Alignment) -> String {
pad(string, width, mode, ' ')
}

///
pub fn zeros(string: &str, width: usize, mode: Alignment) -> String {
pad(string, width, mode, '0')
}

///
pub fn pad_into_bytes(string: &str, width: usize, mode: Alignment, pad_char: char) -> Vec<u8> {
pad(string, width, mode, pad_char).into_bytes()
}

///
pub fn pad_and_push_to_buffer(
string: &str,
width: usize,
mode: Alignment,
pad_char: char,
buffer: &mut Vec<u8>,
) {
buffer.extend_from_slice(pad(string, width, mode, pad_char).as_bytes());
}

///
/// Panics
/// Iff the target pad width is less than the provided string length.
fn pad(string: &str, width: usize, mode: Alignment, pad_char: char) -> String {
if width < string.len() {
panic!("Invalid target pad width for the provide string length.")
}

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

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

let (lpad, rpad) = match mode {
Alignment::Left => (0, diff),
Alignment::Right => (diff, 0),
Alignment::Center => (diff / 2, diff - diff / 2),
};

(0..lpad).for_each(|_| output.push(pad_char));
output.push_str(string);
(0..rpad).for_each(|_| output.push(pad_char));

output
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
#[should_panic]
fn pad_should_panic() {
let _ = whitespace("lol hahahah xd test", 15, Alignment::Center);
}

#[test]
fn pad_whitespace_right_align_into_bytes() {
let width: usize = 30;
let bytes = pad_into_bytes("this is cool", width, Alignment::Right, ' ');

assert_eq!(format!("{:>width$}", "this is cool").into_bytes(), bytes,);
}

#[test]
fn pad_zeros_left_align_push_to_buffer() {
let width: usize = 128;
let expected: Vec<u8> =
format!("{:0<width$}", "testing buffer reuse smart memory").into_bytes();

let mut buffer: Vec<u8> = Vec::with_capacity(1024 * 1024);
pad_and_push_to_buffer(
"testing buffer reuse smart memory",
width,
Alignment::Left,
'0',
&mut buffer,
);

assert_eq!(expected.len(), buffer.len());
assert_eq!(expected, buffer);
}

#[test]
fn pad_whitespace_left_align() {
let width: usize = 30;
let pad = whitespace("this is cool", width, Alignment::Left);

assert_eq!(format!("{:<width$}", "this is cool"), pad);
}

#[test]
fn pad_whitespace_right_align() {
let width: usize = 30;
let pad = whitespace("this is cool", width, Alignment::Right);

assert_eq!(format!("{:>width$}", "this is cool"), pad);
}

#[test]
fn pad_whitespace_center_align() {
let width: usize = 30;
let pad = whitespace("this is cool", width, Alignment::Center);

assert_eq!(format!("{:^width$}", "this is cool"), pad);
}

#[test]
fn pad_zeros_left_align() {
let width: usize = 30;
let pad = zeros("this is cool", width, Alignment::Left);

assert_eq!(format!("{:0<width$}", "this is cool"), pad);
}

#[test]
fn pad_zeros_right_align() {
let width: usize = 30;
let pad = zeros("this is cool", width, Alignment::Right);

assert_eq!(format!("{:0>width$}", "this is cool"), pad);
}

#[test]
fn pad_zeros_center_align() {
let width: usize = 30;
let pad = zeros("this is cool", width, Alignment::Center);

assert_eq!(format!("{:0^width$}", "this is cool"), pad);
}

#[test]
fn pad_1000000_zeros_center() {
let width: usize = 100_000_000;
let pad = zeros("this is cool", width, Alignment::Center);

assert_eq!(format!("{:0^width$}", "this is cool"), pad);
}
}

0 comments on commit 4909c2f

Please sign in to comment.