From 4909c2f98b0d361cdee3f9e5002bd47f400adeab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wilhelm=20=C3=85gren?= <36638274+wilhelmagren@users.noreply.github.com> Date: Thu, 14 Dec 2023 12:41:55 +0100 Subject: [PATCH] [feat] padding, testing, and benchmarking (#3) * [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 --- Cargo.toml | 36 ++++ benches/bench_main.rs | 7 + .../benchmarks/format_whitespace_leftalign.rs | 42 ++++ benches/benchmarks/mod.rs | 2 + .../benchmarks/pad_whitespace_leftalign.rs | 51 +++++ src/lib.rs | 197 ++++++++++++++++++ 6 files changed, 335 insertions(+) create mode 100644 Cargo.toml create mode 100644 benches/bench_main.rs create mode 100644 benches/benchmarks/format_whitespace_leftalign.rs create mode 100644 benches/benchmarks/mod.rs create mode 100644 benches/benchmarks/pad_whitespace_leftalign.rs create mode 100644 src/lib.rs diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..75061aa --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,36 @@ +[package] +name = "padder" +version = "0.1.0" +edition = "2021" +description = "String formatting done right." +authors = [ + "Wilhelm Ågren ", +] + +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 diff --git a/benches/bench_main.rs b/benches/bench_main.rs new file mode 100644 index 0000000..ddf6759 --- /dev/null +++ b/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, +} diff --git a/benches/benchmarks/format_whitespace_leftalign.rs b/benches/benchmarks/format_whitespace_leftalign.rs new file mode 100644 index 0000000..13170ff --- /dev/null +++ b/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!("{: 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 { + 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, +) { + 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 = + format!("{:0 = 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_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!("{:0width$}", "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); + } +}