diff --git a/CHANGELOG.md b/CHANGELOG.md index 832eaaf..e856349 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,15 @@ # Changelog + + +## [0.1.3](https://github.com/Blobfolio/trimothy/releases/tag/v0.1.3) - 2022-05-30 + +### Changed + +* Minor performance improvements for `TrimSlice` implementations + + + ## [0.1.2](https://github.com/Blobfolio/trimothy/releases/tag/v0.1.2) - 2022-04-30 ### Changed diff --git a/CREDITS.md b/CREDITS.md index f09417c..66b072f 100644 --- a/CREDITS.md +++ b/CREDITS.md @@ -1,6 +1,6 @@ # Project Dependencies Package: trimothy - Version: 0.1.2 - Generated: 2022-04-30 19:39:20 UTC + Version: 0.1.3 + Generated: 2022-05-31 02:54:49 UTC This package has no dependencies. diff --git a/Cargo.toml b/Cargo.toml index ae670dc..4a99a62 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "trimothy" -version = "0.1.2" +version = "0.1.3" authors = ["Blobfolio, LLC. "] edition = "2021" rust-version = "1.60" @@ -25,12 +25,12 @@ man-dir = "./" credits-dir = "./" [dev-dependencies] -brunch = "0.2.*" +brunch = "0.2.*, >=0.2.5" [[bench]] name = "fn_trim_slice" harness = false [[bench]] -name = "fn_trim_mut_vec" +name = "fn_trim_mut" harness = false diff --git a/README.md b/README.md index 0949034..86b5c83 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,7 @@ [![crates.io](https://img.shields.io/crates/v/trimothy.svg)](https://crates.io/crates/trimothy) [![Build Status](https://github.com/Blobfolio/trimothy/workflows/Build/badge.svg)](https://github.com/Blobfolio/trimothy/actions) [![Dependency Status](https://deps.rs/repo/github/blobfolio/trimothy/status.svg)](https://deps.rs/repo/github/blobfolio/trimothy) +[![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=flat-square)](https://github.com/Blobfolio/trimothy) Trimothy is a small library that expands on the limited String- and slice-trimming capabilities provided by the standard library. diff --git a/benches/fn_trim_mut_vec.rs b/benches/fn_trim_mut.rs similarity index 100% rename from benches/fn_trim_mut_vec.rs rename to benches/fn_trim_mut.rs diff --git a/benches/fn_trim_slice.rs b/benches/fn_trim_slice.rs index 20931bb..dae9468 100644 --- a/benches/fn_trim_slice.rs +++ b/benches/fn_trim_slice.rs @@ -6,7 +6,10 @@ use brunch::{ Bench, benches, }; -use trimothy::TrimSlice; +use trimothy::{ + TrimSlice, + TrimSliceMatches, +}; use std::time::Duration; @@ -44,4 +47,14 @@ benches!( Bench::new("&str", "trim_end()") .timed(Duration::from_secs(1)) .with(|| STR.trim_end()), + + Bench::spacer(), + + Bench::new("&[u8]", "trim_start_matches()") + .timed(Duration::from_secs(1)) + .with(|| BYTES.trim_start_matches(|b| matches!(b, b'\t' | b' ' | b'\n' | b'H' | b'e'))), + + Bench::new("&str", "trim_start_matches()") + .timed(Duration::from_secs(1)) + .with(|| STR.trim_start_matches(|c| matches!(c, '\t' | ' ' | '\n' | 'H' | 'e'))), ); diff --git a/src/trim_mut.rs b/src/trim_mut.rs index 1789145..381d8a4 100644 --- a/src/trim_mut.rs +++ b/src/trim_mut.rs @@ -108,6 +108,31 @@ pub trait TrimMatchesMut { +/// # Helper: String Trim. +macro_rules! string_trim { + ($lhs:ident, $trimmed:expr) => ( + let trimmed = $trimmed; + let trimmed_len = trimmed.len(); + + if trimmed_len < $lhs.len() { + if 0 < trimmed_len { + let trimmed_ptr = trimmed.as_ptr(); + + // Safety: we're just moving the trimmed portion to the start + // of the buffer and chopping the length to match. + unsafe { + let v = $lhs.as_mut_vec(); + copy(trimmed_ptr, v.as_mut_ptr(), trimmed_len); + v.set_len(trimmed_len); + } + } + else { $lhs.truncate(0); } + } + ); +} + + + impl TrimMut for String { #[allow(unsafe_code)] /// # Trim Mut. @@ -123,25 +148,7 @@ impl TrimMut for String { /// s.trim_mut(); /// assert_eq!(s, "Hello World!"); /// ``` - fn trim_mut(&mut self) { - let trimmed = self.trim(); - let trimmed_len = trimmed.len(); - - if trimmed_len < self.len() { - if 0 < trimmed_len { - let trimmed_ptr = trimmed.as_ptr(); - - // Safety: we're just moving the trimmed portion to the start - // of the buffer and chopping the length to match. - unsafe { - let v = self.as_mut_vec(); - copy(trimmed_ptr, v.as_mut_ptr(), trimmed_len); - v.set_len(trimmed_len); - } - } - else { self.truncate(0); } - } - } + fn trim_mut(&mut self) { string_trim!(self, self.trim()); } #[allow(unsafe_code)] /// # Trim Start Mut. @@ -157,25 +164,7 @@ impl TrimMut for String { /// s.trim_start_mut(); /// assert_eq!(s, "Hello World! "); /// ``` - fn trim_start_mut(&mut self) { - let trimmed = self.trim_start(); - let trimmed_len = trimmed.len(); - - if trimmed_len < self.len() { - if 0 < trimmed_len { - let trimmed_ptr = trimmed.as_ptr(); - - // Safety: we're just moving the trimmed portion to the start - // of the buffer and chopping the length to match. - unsafe { - let v = self.as_mut_vec(); - copy(trimmed_ptr, v.as_mut_ptr(), trimmed_len); - v.set_len(trimmed_len); - } - } - else { self.truncate(0); } - } - } + fn trim_start_mut(&mut self) { string_trim!(self, self.trim_start()); } /// # Trim End Mut. /// @@ -217,25 +206,7 @@ impl TrimMatchesMut for String { /// assert_eq!(s, "ello World!"); /// ``` fn trim_matches_mut(&mut self, cb: F) - where F: Fn(Self::MatchUnit) -> bool { - let trimmed = self.trim_matches(cb); - let trimmed_len = trimmed.len(); - - if trimmed_len < self.len() { - if 0 < trimmed_len { - let trimmed_ptr = trimmed.as_ptr(); - - // Safety: we're just moving the trimmed portion to the start - // of the buffer and chopping the length to match. - unsafe { - let v = self.as_mut_vec(); - copy(trimmed_ptr, v.as_mut_ptr(), trimmed_len); - v.set_len(trimmed_len); - } - } - else { self.truncate(0); } - } - } + where F: Fn(Self::MatchUnit) -> bool { string_trim!(self, self.trim_matches(cb)); } #[allow(unsafe_code)] /// # Trim Start Matches Mut. @@ -255,23 +226,7 @@ impl TrimMatchesMut for String { /// ``` fn trim_start_matches_mut(&mut self, cb: F) where F: Fn(Self::MatchUnit) -> bool { - let trimmed = self.trim_start_matches(cb); - let trimmed_len = trimmed.len(); - - if trimmed_len < self.len() { - if 0 < trimmed_len { - let trimmed_ptr = trimmed.as_ptr(); - - // Safety: we're just moving the trimmed portion to the start - // of the buffer and chopping the length to match. - unsafe { - let v = self.as_mut_vec(); - copy(trimmed_ptr, v.as_mut_ptr(), trimmed_len); - v.set_len(trimmed_len); - } - } - else { self.truncate(0); } - } + string_trim!(self, self.trim_start_matches(cb)); } /// # Trim End Matches Mut. diff --git a/src/trim_slice.rs b/src/trim_slice.rs index 5578aff..2f1fc99 100644 --- a/src/trim_slice.rs +++ b/src/trim_slice.rs @@ -6,7 +6,6 @@ use alloc::{ boxed::Box, vec::Vec, }; -use crate::not_whitespace; @@ -136,33 +135,19 @@ macro_rules! trim_slice { /// # Trim. /// /// Trim leading and trailing (ASCII) whitespace from a slice. - fn trim(&self) -> &[u8] { - self.iter() - .position(not_whitespace) - .map_or(&[], |start| { - // We know there is an end because there's a beginning. - let end = self.iter().rposition(not_whitespace).unwrap(); - &self[start..=end] - }) - } + fn trim(&self) -> &[u8] { trim_end(trim_start(&self)) } + #[inline] /// # Trim Start. /// /// Trim leading (ASCII) whitespace from a slice. - fn trim_start(&self) -> &[u8] { - self.iter() - .position(not_whitespace) - .map_or(&[], |p| &self[p..]) - } + fn trim_start(&self) -> &[u8] { trim_start(&self) } + #[inline] /// # Trim End. /// /// Trim trailing (ASCII) whitespace from a slice. - fn trim_end(&self) -> &[u8] { - self.iter() - .rposition(not_whitespace) - .map_or(&[], |p| &self[..=p]) - } + fn trim_end(&self) -> &[u8] { trim_end(&self) } } impl TrimSliceMatches for $ty { @@ -208,7 +193,33 @@ macro_rules! trim_slice { )+); } -trim_slice!(&[u8], Box<[u8]>, Vec); +trim_slice!([u8], Box<[u8]>, Vec); + + + +/// # Trim Slice Start. +/// +/// This is a copy of the nightly `trim_ascii_start` so it can be used on +/// stable. If/when that feature is stabilized, we'll use it directly. +const fn trim_start(mut src: &[u8]) -> &[u8] { + while let [first, rest @ ..] = src { + if first.is_ascii_whitespace() { src = rest; } + else { break; } + } + src +} + +/// # Trim Slice End. +/// +/// This is a copy of the nightly `trim_ascii_end` so it can be used on +/// stable. If/when that feature is stabilized, we'll use it directly. +const fn trim_end(mut src: &[u8]) -> &[u8] { + while let [rest @ .., last] = src { + if last.is_ascii_whitespace() { src = rest; } + else { break; } + } + src +} @@ -255,6 +266,10 @@ mod tests { assert_eq!(T_HELLO_E.trim_matches(|b| b'h' == b), b"ello\t"); assert_eq!(T_HELLO_E.to_vec().trim_matches(|b| b'h' == b), b"ello\t"); assert_eq!(Box::<[u8]>::from(T_HELLO_E).trim_matches(|b| b'h' == b), b"ello\t"); + + // This should also work on arrays. + let arr: [u8; 5] = [b' ', b' ', b'.', b' ', b' ']; + assert_eq!(arr.trim(), &[b'.']); } #[test]