From 257b6a61bc02ee2033a2593b25823a1018e3bed8 Mon Sep 17 00:00:00 2001 From: Josh Stoik Date: Mon, 11 Apr 2022 21:25:25 -0700 Subject: [PATCH 01/10] misc: prioritize likely branch --- src/trim_mut.rs | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/src/trim_mut.rs b/src/trim_mut.rs index 50441ca..c0526a8 100644 --- a/src/trim_mut.rs +++ b/src/trim_mut.rs @@ -131,8 +131,7 @@ impl TrimMut for String { let trimmed_len = trimmed.len(); if trimmed_len < self.len() { - if trimmed_len == 0 { self.truncate(0); } - else { + if 0 < trimmed_len { let trimmed_ptr = trimmed.as_ptr(); // Safety: we're just moving the trimmed portion to the start. It @@ -143,6 +142,7 @@ impl TrimMut for String { v.set_len(trimmed_len); } } + else { self.truncate(0); } } } @@ -164,8 +164,7 @@ impl TrimMut for String { let trimmed_len = trimmed.len(); if trimmed_len < self.len() { - if trimmed_len == 0 { self.truncate(0); } - else { + if 0 < trimmed_len { let trimmed_ptr = trimmed.as_ptr(); // Safety: we're just moving the trimmed portion to the start. It @@ -176,6 +175,7 @@ impl TrimMut for String { v.set_len(trimmed_len); } } + else { self.truncate(0); } } } @@ -223,8 +223,7 @@ impl TrimMatchesMut for String { let trimmed_len = trimmed.len(); if trimmed_len < self.len() { - if trimmed_len == 0 { self.truncate(0); } - else { + if 0 < trimmed_len { let trimmed_ptr = trimmed.as_ptr(); // Safety: we're just moving the trimmed portion to the start. It @@ -235,6 +234,7 @@ impl TrimMatchesMut for String { v.set_len(trimmed_len); } } + else { self.truncate(0); } } } @@ -259,8 +259,7 @@ impl TrimMatchesMut for String { let trimmed_len = trimmed.len(); if trimmed_len < self.len() { - if trimmed_len == 0 { self.truncate(0); } - else { + if 0 < trimmed_len { let trimmed_ptr = trimmed.as_ptr(); // Safety: we're just moving the trimmed portion to the start. It @@ -271,6 +270,7 @@ impl TrimMatchesMut for String { v.set_len(trimmed_len); } } + else { self.truncate(0); } } } @@ -446,8 +446,7 @@ impl TrimMut for Vec { let trimmed_len = trimmed.len(); if trimmed_len < self.len() { - if trimmed_len == 0 { self.truncate(0); } - else { + if 0 < trimmed_len { let trimmed_ptr = trimmed.as_ptr(); // Safety: we're just moving the trimmed portion to the start. It @@ -457,6 +456,7 @@ impl TrimMut for Vec { self.set_len(trimmed_len); } } + else { self.truncate(0); } } } @@ -478,8 +478,7 @@ impl TrimMut for Vec { let trimmed_len = trimmed.len(); if trimmed_len < self.len() { - if trimmed_len == 0 { self.truncate(0); } - else { + if 0 < trimmed_len { let trimmed_ptr = trimmed.as_ptr(); // Safety: we're just moving the trimmed portion to the start. It @@ -489,6 +488,7 @@ impl TrimMut for Vec { self.set_len(trimmed_len); } } + else { self.truncate(0); } } } @@ -536,8 +536,7 @@ impl TrimMatchesMut for Vec { let trimmed_len = trimmed.len(); if trimmed_len < self.len() { - if trimmed_len == 0 { self.truncate(0); } - else { + if 0 < trimmed_len { let trimmed_ptr = trimmed.as_ptr(); // Safety: we're just moving the trimmed portion to the start. It @@ -547,6 +546,7 @@ impl TrimMatchesMut for Vec { self.set_len(trimmed_len); } } + else { self.truncate(0); } } } @@ -571,8 +571,7 @@ impl TrimMatchesMut for Vec { let trimmed_len = trimmed.len(); if trimmed_len < self.len() { - if trimmed_len == 0 { self.truncate(0); } - else { + if 0 < trimmed_len { let trimmed_ptr = trimmed.as_ptr(); // Safety: we're just moving the trimmed portion to the start. It @@ -582,6 +581,7 @@ impl TrimMatchesMut for Vec { self.set_len(trimmed_len); } } + else { self.truncate(0); } } } From c02879f5029a25763a3629e80c0f67e1eb8175f2 Mon Sep 17 00:00:00 2001 From: Josh Stoik Date: Mon, 11 Apr 2022 21:25:36 -0700 Subject: [PATCH 02/10] misc: bench mut --- Cargo.toml | 5 +++++ benches/fn_trim_mut_vec.rs | 33 +++++++++++++++++++++++++++++++++ 2 files changed, 38 insertions(+) create mode 100644 benches/fn_trim_mut_vec.rs diff --git a/Cargo.toml b/Cargo.toml index 4619024..725397c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -34,3 +34,8 @@ std = [] name = "fn_trim_slice" harness = false required-features = [ "std" ] + +[[bench]] +name = "fn_trim_mut_vec" +harness = false +required-features = [ "std" ] diff --git a/benches/fn_trim_mut_vec.rs b/benches/fn_trim_mut_vec.rs new file mode 100644 index 0000000..4cd7ac2 --- /dev/null +++ b/benches/fn_trim_mut_vec.rs @@ -0,0 +1,33 @@ +/*! +# Benchmark: Trim Slice +*/ + +use brunch::{ + Bench, + benches, +}; +use trimothy::TrimMut; +use std::time::Duration; + + + +const BYTES: &[u8] = b" \t\nHello World!\n\t "; +const STR: &str = " \t\nHello World!\n\t "; + + + +benches!( + Bench::new("Vec", "trim_mut()") + .timed(Duration::from_secs(1)) + .with_setup(BYTES.to_vec(), |mut v| v.trim_mut()), + + Bench::spacer(), + + Bench::new("String", "trim_mut()") + .timed(Duration::from_secs(1)) + .with_setup(STR.to_owned(), |mut s| s.trim_mut()), + + Bench::new("String.trim()", "to_owned()") + .timed(Duration::from_secs(1)) + .with_setup(STR.to_owned(), |s| s.trim().to_owned()), +); From 54fae62ebc0fc0136fa9d6cebf0cc67abe98e00e Mon Sep 17 00:00:00 2001 From: Josh Stoik Date: Mon, 11 Apr 2022 21:27:32 -0700 Subject: [PATCH 03/10] fix: return empty slice on no-match --- src/trim_slice.rs | 175 +++++++++++++++++++++++++++++----------------- 1 file changed, 110 insertions(+), 65 deletions(-) diff --git a/src/trim_slice.rs b/src/trim_slice.rs index 2737331..09f6579 100644 --- a/src/trim_slice.rs +++ b/src/trim_slice.rs @@ -130,17 +130,18 @@ pub trait TrimSliceMatches { impl TrimSlice for &[u8] { + #[allow(clippy::option_if_let_else)] /// # Trim. /// /// Trim leading and trailing (ASCII) whitespace from a slice. fn trim(&self) -> &[u8] { - let start: usize = self.iter() - .position(|b| ! b.is_ascii_whitespace()) - .unwrap_or(0); + if let Some(start) = self.iter().position(|b| ! b.is_ascii_whitespace()) { + if let Some(end) = self.iter().rposition(|b| ! b.is_ascii_whitespace()) { + return &self[start..=end]; + } + } - self.iter() - .rposition(|b| ! b.is_ascii_whitespace()) - .map_or_else(|| &self[start..], |p| &self[start..=p]) + &[] } /// # Trim Start. @@ -149,7 +150,7 @@ impl TrimSlice for &[u8] { fn trim_start(&self) -> &[u8] { self.iter() .position(|b| ! b.is_ascii_whitespace()) - .map_or(self, |p| &self[p..]) + .map_or(&[], |p| &self[p..]) } /// # Trim End. @@ -158,11 +159,12 @@ impl TrimSlice for &[u8] { fn trim_end(&self) -> &[u8] { self.iter() .rposition(|b| ! b.is_ascii_whitespace()) - .map_or(self, |p| &self[..=p]) + .map_or(&[], |p| &self[..=p]) } } impl TrimSliceMatches for &[u8] { + #[allow(clippy::option_if_let_else)] /// # Trim Matches. /// /// Trim arbitrary leading and trailing bytes as determined by the provided @@ -171,13 +173,13 @@ impl TrimSliceMatches for &[u8] { where F: Fn(u8) -> bool { let cb = |b: &u8| ! cb(*b); - let start: usize = self.iter() - .position(cb) - .unwrap_or(0); + if let Some(start) = self.iter().position(cb) { + if let Some(end) = self.iter().rposition(cb) { + return &self[start..=end]; + } + } - self.iter() - .rposition(cb) - .map_or_else(|| &self[start..], |p| &self[start..=p]) + &[] } /// # Trim Start Matches. @@ -188,7 +190,7 @@ impl TrimSliceMatches for &[u8] { where F: Fn(u8) -> bool { self.iter() .position(|b: &u8| ! cb(*b)) - .map_or(self, |p| &self[p..]) + .map_or(&[], |p| &self[p..]) } /// # Trim Start Matches. @@ -199,7 +201,7 @@ impl TrimSliceMatches for &[u8] { where F: Fn(u8) -> bool { self.iter() .rposition(|b: &u8| ! cb(*b)) - .map_or(self, |p| &self[..=p]) + .map_or(&[], |p| &self[..=p]) } } @@ -210,13 +212,13 @@ macro_rules! trim_slice_alloc { /// /// Trim leading and trailing (ASCII) whitespace from a slice. fn trim(&self) -> &[u8] { - let start: usize = self.iter() - .position(|b| ! b.is_ascii_whitespace()) - .unwrap_or(0); + if let Some(start) = self.iter().position(|b| ! b.is_ascii_whitespace()) { + if let Some(end) = self.iter().rposition(|b| ! b.is_ascii_whitespace()) { + return &self[start..=end]; + } + } - self.iter() - .rposition(|b| ! b.is_ascii_whitespace()) - .map_or_else(|| &self[start..], |p| &self[start..=p]) + &[] } /// # Trim Start. @@ -225,7 +227,7 @@ macro_rules! trim_slice_alloc { fn trim_start(&self) -> &[u8] { self.iter() .position(|b| ! b.is_ascii_whitespace()) - .map_or(&self, |p| &self[p..]) + .map_or(&[], |p| &self[p..]) } /// # Trim End. @@ -234,7 +236,7 @@ macro_rules! trim_slice_alloc { fn trim_end(&self) -> &[u8] { self.iter() .rposition(|b| ! b.is_ascii_whitespace()) - .map_or(&self, |p| &self[..=p]) + .map_or(&[], |p| &self[..=p]) } } @@ -247,13 +249,13 @@ macro_rules! trim_slice_alloc { where F: Fn(u8) -> bool { let cb = |b: &u8| ! cb(*b); - let start: usize = self.iter() - .position(cb) - .unwrap_or(0); + if let Some(start) = self.iter().position(cb) { + if let Some(end) = self.iter().rposition(cb) { + return &self[start..=end]; + } + } - self.iter() - .rposition(cb) - .map_or_else(|| &self[start..], |p| &self[start..=p]) + &[] } /// # Trim Start Matches. @@ -264,7 +266,7 @@ macro_rules! trim_slice_alloc { where F: Fn(u8) -> bool { self.iter() .position(|b: &u8| ! cb(*b)) - .map_or(&self, |p| &self[p..]) + .map_or(&[], |p| &self[p..]) } /// # Trim Start Matches. @@ -275,7 +277,7 @@ macro_rules! trim_slice_alloc { where F: Fn(u8) -> bool { self.iter() .rposition(|b: &u8| ! cb(*b)) - .map_or(&self, |p| &self[..=p]) + .map_or(&[], |p| &self[..=p]) } } )+); @@ -293,46 +295,74 @@ mod tests { const T_EMPTY: &[u8] = b""; const T_HELLO: &[u8] = b"hello"; const T_HELLO_E: &[u8] = b"hello\t"; - const T_HELLO_S: &[u8] = b"\thello"; - const T_HELLO_SE: &[u8] = b"\n hello \t"; #[test] fn t_trim() { - let tests: &[(&[u8], &[u8])] = &[ - (T_EMPTY, T_EMPTY), - (T_HELLO, T_HELLO), - (T_HELLO_S, T_HELLO), - (T_HELLO_E, T_HELLO), - (T_HELLO_SE, T_HELLO), + let tests: [(&str, &str); 6] = [ + ("", ""), + (" \t\n\r", ""), + ("hello", "hello"), + ("hello\t", "hello"), + ("\thello", "hello"), + ("\n hello world! \t", "hello world!"), ]; - for &(src, expected) in tests.iter() { - assert_eq!(src.trim(), expected); - assert_eq!(Box::<[u8]>::from(src).trim(), expected); - assert_eq!(src.to_vec().trim(), expected); + for (raw, expected) in tests.iter() { + let a = raw.as_bytes(); + let b = expected.as_bytes(); + assert_eq!(a.trim(), b); + + let a = a.to_vec(); + assert_eq!(a.trim(), b); + + let a = a.into_boxed_slice(); + assert_eq!(a.trim(), b); } + assert_eq!(T_EMPTY.trim_matches(|b| b.is_ascii_whitespace()), T_EMPTY); + assert_eq!(T_EMPTY.to_vec().trim_matches(|b| b.is_ascii_whitespace()), T_EMPTY); + assert_eq!(Box::<[u8]>::from(T_EMPTY).trim_matches(|b| b.is_ascii_whitespace()), T_EMPTY); + + assert_eq!(" ".as_bytes().trim_matches(|b| b.is_ascii_whitespace()), T_EMPTY); + assert_eq!(" ".as_bytes().to_vec().trim_matches(|b| b.is_ascii_whitespace()), T_EMPTY); + assert_eq!(Box::<[u8]>::from(" ".as_bytes()).trim_matches(|b| b.is_ascii_whitespace()), T_EMPTY); + assert_eq!(T_HELLO_E.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"); 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"); } #[test] fn t_trim_start() { - let tests: &[(&[u8], &[u8])] = &[ - (T_EMPTY, T_EMPTY), - (T_HELLO, T_HELLO), - (T_HELLO_S, T_HELLO), - (T_HELLO_E, T_HELLO_E), - (T_HELLO_SE, b"hello \t"), + let tests: [(&str, &str); 6] = [ + ("", ""), + (" \t\n\r", ""), + ("hello", "hello"), + ("hello\t", "hello\t"), + ("\thello", "hello"), + ("\n hello world! \t", "hello world! \t"), ]; - for &(src, expected) in tests.iter() { - assert_eq!(src.trim_start(), expected); - assert_eq!(Box::<[u8]>::from(src).trim_start(), expected); - assert_eq!(src.to_vec().trim_start(), expected); + for (raw, expected) in tests.iter() { + let a = raw.as_bytes(); + let b = expected.as_bytes(); + assert_eq!(a.trim_start(), b); + + let a = a.to_vec(); + assert_eq!(a.trim_start(), b); + + let a = a.into_boxed_slice(); + assert_eq!(a.trim_start(), b); } + assert_eq!(T_EMPTY.trim_start_matches(|b| b.is_ascii_whitespace()), T_EMPTY); + assert_eq!(T_EMPTY.to_vec().trim_start_matches(|b| b.is_ascii_whitespace()), T_EMPTY); + assert_eq!(Box::<[u8]>::from(T_EMPTY).trim_start_matches(|b| b.is_ascii_whitespace()), T_EMPTY); + + assert_eq!(" ".as_bytes().trim_start_matches(|b| b.is_ascii_whitespace()), T_EMPTY); + assert_eq!(" ".as_bytes().to_vec().trim_start_matches(|b| b.is_ascii_whitespace()), T_EMPTY); + assert_eq!(Box::<[u8]>::from(" ".as_bytes()).trim_start_matches(|b| b.is_ascii_whitespace()), T_EMPTY); + assert_eq!(T_HELLO_E.trim_start_matches(|b| b'h' == b), b"ello\t"); assert_eq!(Box::<[u8]>::from(T_HELLO_E).trim_start_matches(|b| b'h' == b), b"ello\t"); assert_eq!(T_HELLO_E.to_vec().trim_start_matches(|b| b'h' == b), b"ello\t"); @@ -340,20 +370,35 @@ mod tests { #[test] fn t_trim_end() { - let tests: &[(&[u8], &[u8])] = &[ - (T_EMPTY, T_EMPTY), - (T_HELLO, T_HELLO), - (T_HELLO_S, T_HELLO_S), - (T_HELLO_E, T_HELLO), - (T_HELLO_SE, b"\n hello"), + let tests: [(&str, &str); 6] = [ + ("", ""), + (" \t\n\r", ""), + ("hello", "hello"), + ("hello\t", "hello"), + ("\thello", "\thello"), + ("\n hello world! \t", "\n hello world!"), ]; - for &(src, expected) in tests.iter() { - assert_eq!(src.trim_end(), expected); - assert_eq!(Box::<[u8]>::from(src).trim_end(), expected); - assert_eq!(src.to_vec().trim_end(), expected); + for (raw, expected) in tests.iter() { + let a = raw.as_bytes(); + let b = expected.as_bytes(); + assert_eq!(a.trim_end(), b); + + let a = a.to_vec(); + assert_eq!(a.trim_end(), b); + + let a = a.into_boxed_slice(); + assert_eq!(a.trim_end(), b); } + assert_eq!(T_EMPTY.trim_end_matches(|b| b.is_ascii_whitespace()), T_EMPTY); + assert_eq!(T_EMPTY.to_vec().trim_end_matches(|b| b.is_ascii_whitespace()), T_EMPTY); + assert_eq!(Box::<[u8]>::from(T_EMPTY).trim_end_matches(|b| b.is_ascii_whitespace()), T_EMPTY); + + assert_eq!(" ".as_bytes().trim_end_matches(|b| b.is_ascii_whitespace()), T_EMPTY); + assert_eq!(" ".as_bytes().to_vec().trim_end_matches(|b| b.is_ascii_whitespace()), T_EMPTY); + assert_eq!(Box::<[u8]>::from(" ".as_bytes()).trim_end_matches(|b| b.is_ascii_whitespace()), T_EMPTY); + assert_eq!(T_HELLO_E.trim_matches(|b| b'\t' == b), T_HELLO); assert_eq!(Box::<[u8]>::from(T_HELLO_E).trim_matches(|b| b'\t' == b), T_HELLO); assert_eq!(T_HELLO_E.to_vec().trim_matches(|b| b'\t' == b), T_HELLO); From 00ae5293ffc758aacc7ece918dbd37d02b908360 Mon Sep 17 00:00:00 2001 From: Josh Stoik Date: Mon, 11 Apr 2022 21:47:09 -0700 Subject: [PATCH 04/10] cleanup: merge all slicey definitions --- src/trim_slice.rs | 123 +++++++++++----------------------------------- 1 file changed, 28 insertions(+), 95 deletions(-) diff --git a/src/trim_slice.rs b/src/trim_slice.rs index 09f6579..3c60f5b 100644 --- a/src/trim_slice.rs +++ b/src/trim_slice.rs @@ -128,97 +128,20 @@ pub trait TrimSliceMatches { } - -impl TrimSlice for &[u8] { - #[allow(clippy::option_if_let_else)] - /// # Trim. - /// - /// Trim leading and trailing (ASCII) whitespace from a slice. - fn trim(&self) -> &[u8] { - if let Some(start) = self.iter().position(|b| ! b.is_ascii_whitespace()) { - if let Some(end) = self.iter().rposition(|b| ! b.is_ascii_whitespace()) { - return &self[start..=end]; - } - } - - &[] - } - - /// # Trim Start. - /// - /// Trim leading (ASCII) whitespace from a slice. - fn trim_start(&self) -> &[u8] { - self.iter() - .position(|b| ! b.is_ascii_whitespace()) - .map_or(&[], |p| &self[p..]) - } - - /// # Trim End. - /// - /// Trim trailing (ASCII) whitespace from a slice. - fn trim_end(&self) -> &[u8] { - self.iter() - .rposition(|b| ! b.is_ascii_whitespace()) - .map_or(&[], |p| &self[..=p]) - } -} - -impl TrimSliceMatches for &[u8] { - #[allow(clippy::option_if_let_else)] - /// # Trim Matches. - /// - /// Trim arbitrary leading and trailing bytes as determined by the provided - /// callback, where a return value of `true` means trim. - fn trim_matches(&self, cb: F) -> &[u8] - where F: Fn(u8) -> bool { - let cb = |b: &u8| ! cb(*b); - - if let Some(start) = self.iter().position(cb) { - if let Some(end) = self.iter().rposition(cb) { - return &self[start..=end]; - } - } - - &[] - } - - /// # Trim Start Matches. - /// - /// Trim arbitrary leading bytes as determined by the provided callback, - /// where a return value of `true` means trim. - fn trim_start_matches(&self, cb: F) -> &[u8] - where F: Fn(u8) -> bool { - self.iter() - .position(|b: &u8| ! cb(*b)) - .map_or(&[], |p| &self[p..]) - } - - /// # Trim Start Matches. - /// - /// Trim arbitrary leading bytes as determined by the provided callback, - /// where a return value of `true` means trim. - fn trim_end_matches(&self, cb: F) -> &[u8] - where F: Fn(u8) -> bool { - self.iter() - .rposition(|b: &u8| ! cb(*b)) - .map_or(&[], |p| &self[..=p]) - } -} - -macro_rules! trim_slice_alloc { +macro_rules! trim_slice { ($($ty:ty),+ $(,)?) => ($( impl TrimSlice for $ty { /// # Trim. /// /// Trim leading and trailing (ASCII) whitespace from a slice. fn trim(&self) -> &[u8] { - if let Some(start) = self.iter().position(|b| ! b.is_ascii_whitespace()) { - if let Some(end) = self.iter().rposition(|b| ! b.is_ascii_whitespace()) { - return &self[start..=end]; - } - } - - &[] + 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] + }) } /// # Trim Start. @@ -226,7 +149,7 @@ macro_rules! trim_slice_alloc { /// Trim leading (ASCII) whitespace from a slice. fn trim_start(&self) -> &[u8] { self.iter() - .position(|b| ! b.is_ascii_whitespace()) + .position(not_whitespace) .map_or(&[], |p| &self[p..]) } @@ -235,7 +158,7 @@ macro_rules! trim_slice_alloc { /// Trim trailing (ASCII) whitespace from a slice. fn trim_end(&self) -> &[u8] { self.iter() - .rposition(|b| ! b.is_ascii_whitespace()) + .rposition(not_whitespace) .map_or(&[], |p| &self[..=p]) } } @@ -249,13 +172,13 @@ macro_rules! trim_slice_alloc { where F: Fn(u8) -> bool { let cb = |b: &u8| ! cb(*b); - if let Some(start) = self.iter().position(cb) { - if let Some(end) = self.iter().rposition(cb) { - return &self[start..=end]; - } - } - - &[] + self.iter() + .position(cb) + .map_or(&[], |start| { + // We know there is an end because there's a beginning. + let end = self.iter().rposition(cb).unwrap(); + &self[start..=end] + }) } /// # Trim Start Matches. @@ -283,7 +206,17 @@ macro_rules! trim_slice_alloc { )+); } -trim_slice_alloc!(Box<[u8]>, Vec); +trim_slice!(&[u8], Box<[u8]>, Vec); + + + +#[allow(clippy::trivially_copy_pass_by_ref)] // It's the signature iterator wants. +#[inline] +/// # Not Whitespace. +/// +/// This callback is used to find the first or last non-whitespace byte in a +/// slice. It is only split off into its own method to enforce consistency. +const fn not_whitespace(b: &u8) -> bool { ! b.is_ascii_whitespace() } From 75cb9cdca58e48dd64e20bca2d31cbb44d1470a3 Mon Sep 17 00:00:00 2001 From: Josh Stoik Date: Mon, 11 Apr 2022 21:47:16 -0700 Subject: [PATCH 05/10] docs --- CHANGELOG.md | 8 ++++++++ src/trim_mut.rs | 11 +++++++---- src/trim_slice.rs | 4 +++- 3 files changed, 18 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6d1436d..9e10e83 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,13 @@ # Changelog +## [0.1.1](https://github.com/Blobfolio/trimothy/releases/tag/v0.1.1) - 2022-04-11 + +## Fixed + +* Return empty slice on non-empty, all match. + + + ## [0.1.0](https://github.com/Blobfolio/trimothy/releases/tag/v0.1.0) - 2022-04-11 Initial release! diff --git a/src/trim_mut.rs b/src/trim_mut.rs index c0526a8..df1ea4d 100644 --- a/src/trim_mut.rs +++ b/src/trim_mut.rs @@ -28,13 +28,14 @@ use core::intrinsics::copy; /// `Vec`, and `Box<[u8]>`. /// /// The trait methods included are: +/// /// | Method | Description | /// | ------ | ----------- | /// | `trim_mut` | Trim leading and trailing whitespace (mutably). | /// | `trim_start_mut` | Trim leading whitespace (mutably). | /// | `trim_end_mut` | Trim trailing whitespace (mutably). | /// -/// **Note:** these behaviors of these methods are consistent with their immutable +/// **Note:** These behaviors of these methods are consistent with their immutable /// counterparts, meaning that Strings will trim [`char::is_whitespace`], while /// slices will only trim [`u8::is_ascii_whitespace`]. /// @@ -67,15 +68,17 @@ pub trait TrimMut { /// `String`, `Vec`, and `Box<[u8]>`. /// /// The trait methods included are: +/// /// | Method | Description | /// | ------ | ----------- | /// | `trim_matches_mut` | Trim arbitrary leading and trailing bytes via callback (mutably). | /// | `trim_start_matches_mut` | Trim arbitrary leading bytes via callback (mutably). | /// | `trim_end_matches_mut` | Trim arbitrary trailing bytes via callback (mutably). | /// -/// **Note:** The atom being matched varies by implementation to keep behaviors -/// consistent with their immutable counterparts. In other words, `String` uses -/// `char`, while `Vec` and `Box<[u8]>` use `u8`. +/// **Note:** The "atom" being matched varies by implementation to keep behaviors +/// consistent with their immutable counterparts. In other words, the `String` +/// callback accepts a `char`, while the `Vec` and `Box<[u8]>` callbacks +/// accept `u8`. /// /// Refer to the individual implementations for examples. pub trait TrimMatchesMut { diff --git a/src/trim_slice.rs b/src/trim_slice.rs index 3c60f5b..348d25d 100644 --- a/src/trim_slice.rs +++ b/src/trim_slice.rs @@ -17,13 +17,14 @@ use alloc::{ /// `String`/`&str`. /// /// The trait methods included are: +/// /// | Method | Description | /// | ------ | ----------- | /// | `trim` | Trim leading and trailing (ASCII) whitespace. | /// | `trim_start` | Trim leading (ASCII) whitespace. | /// | `trim_end` | Trim trailing (ASCII) whitespace. | /// -/// **Note:** because these methods work with individual bytes — rather than chars +/// **Note:** Because these methods work with individual bytes — rather than chars /// — these methods only trim [`u8::is_ascii_whitespace`], not [`char::is_whitespace`]. pub trait TrimSlice { /// # Trim. @@ -78,6 +79,7 @@ pub trait TrimSlice { /// enjoyed by `String`/`&str`. /// /// The trait methods included are: +/// /// | Method | Description | /// | ------ | ----------- | /// | `trim_matches` | Trim arbitrary leading and trailing bytes via callback. | From ad39f1e81853b060749adbf2db570cbbba75bef0 Mon Sep 17 00:00:00 2001 From: Josh Stoik Date: Mon, 11 Apr 2022 21:48:48 -0700 Subject: [PATCH 06/10] misc: add no_std tests to ci --- .github/workflows/ci.yaml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 78dd674..b5b5886 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -55,11 +55,14 @@ jobs: - name: Build run: | cargo build --release --target ${{ matrix.target }} + cargo build --release --no-default-features --target ${{ matrix.target }} - name: Clippy run: | cargo clippy --release --target ${{ matrix.target }} + cargo clippy --release --no-default-features --target ${{ matrix.target }} - name: Tests run: | cargo test --release --target ${{ matrix.target }} + cargo test --release --no-default-features --target ${{ matrix.target }} From 1f54580991137f8479491ea2b11b0e7cc0babfbb Mon Sep 17 00:00:00 2001 From: Josh Stoik Date: Mon, 11 Apr 2022 21:55:39 -0700 Subject: [PATCH 07/10] docs --- CHANGELOG.md | 1 + src/trim_mut.rs | 9 ++++----- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9e10e83..25ee47a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ ## Fixed * Return empty slice on non-empty, all match. +* Markdown (docs) formatting issues. diff --git a/src/trim_mut.rs b/src/trim_mut.rs index df1ea4d..5cb531e 100644 --- a/src/trim_mut.rs +++ b/src/trim_mut.rs @@ -35,7 +35,7 @@ use core::intrinsics::copy; /// | `trim_start_mut` | Trim leading whitespace (mutably). | /// | `trim_end_mut` | Trim trailing whitespace (mutably). | /// -/// **Note:** These behaviors of these methods are consistent with their immutable +/// **Note:** The behaviors of these methods are consistent with their immutable /// counterparts, meaning that Strings will trim [`char::is_whitespace`], while /// slices will only trim [`u8::is_ascii_whitespace`]. /// @@ -75,10 +75,9 @@ pub trait TrimMut { /// | `trim_start_matches_mut` | Trim arbitrary leading bytes via callback (mutably). | /// | `trim_end_matches_mut` | Trim arbitrary trailing bytes via callback (mutably). | /// -/// **Note:** The "atom" being matched varies by implementation to keep behaviors -/// consistent with their immutable counterparts. In other words, the `String` -/// callback accepts a `char`, while the `Vec` and `Box<[u8]>` callbacks -/// accept `u8`. +/// **Note:** To maintain consistency with their immutable counterparts, the +/// `String` implementation expects callbacks that match a `char`, while the +/// `Vec` and `Box<[u8]>` implementations expect callbacks that match a `u8`. /// /// Refer to the individual implementations for examples. pub trait TrimMatchesMut { From f6e4f7f9576858ddfdf6bfae813c0293b028900f Mon Sep 17 00:00:00 2001 From: Josh Stoik Date: Mon, 11 Apr 2022 22:18:16 -0700 Subject: [PATCH 08/10] misc: optimize some mutable trim ops --- src/lib.rs | 10 ++++++ src/trim_mut.rs | 87 +++++++++++++++++++---------------------------- src/trim_slice.rs | 12 ++----- 3 files changed, 47 insertions(+), 62 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index ee42978..1dadf9b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -108,3 +108,13 @@ pub use trim_slice::{ TrimSlice, TrimSliceMatches, }; + + + +#[allow(clippy::trivially_copy_pass_by_ref)] // It's the signature iterator wants. +#[inline] +/// # Not Whitespace. +/// +/// This callback is used to find the first or last non-whitespace byte in a +/// slice. It is only split off into its own method to enforce consistency. +pub(crate) const fn not_whitespace(b: &u8) -> bool { ! b.is_ascii_whitespace() } diff --git a/src/trim_mut.rs b/src/trim_mut.rs index 5cb531e..a02b99b 100644 --- a/src/trim_mut.rs +++ b/src/trim_mut.rs @@ -10,6 +10,7 @@ use alloc::{ }; use crate::{ + not_whitespace, TrimSlice, TrimSliceMatches, }; @@ -136,8 +137,8 @@ impl TrimMut for String { if 0 < trimmed_len { let trimmed_ptr = trimmed.as_ptr(); - // Safety: we're just moving the trimmed portion to the start. It - // should be A-OK. + // 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); @@ -169,8 +170,8 @@ impl TrimMut for String { if 0 < trimmed_len { let trimmed_ptr = trimmed.as_ptr(); - // Safety: we're just moving the trimmed portion to the start. It - // should be A-OK. + // 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); @@ -228,8 +229,8 @@ impl TrimMatchesMut for String { if 0 < trimmed_len { let trimmed_ptr = trimmed.as_ptr(); - // Safety: we're just moving the trimmed portion to the start. It - // should be A-OK. + // 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); @@ -264,8 +265,8 @@ impl TrimMatchesMut for String { if 0 < trimmed_len { let trimmed_ptr = trimmed.as_ptr(); - // Safety: we're just moving the trimmed portion to the start. It - // should be A-OK. + // 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); @@ -444,22 +445,8 @@ impl TrimMut for Vec { /// assert_eq!(v, b"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. It - // should be A-OK. - unsafe { - copy(trimmed_ptr, self.as_mut_ptr(), trimmed_len); - self.set_len(trimmed_len); - } - } - else { self.truncate(0); } - } + self.trim_start_mut(); + self.trim_end_mut(); } /// # Trim Start Mut. @@ -476,22 +463,19 @@ impl TrimMut for Vec { /// assert_eq!(v, b"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(); + if let Some(start) = self.iter().position(not_whitespace) { + if 0 < start { + let trimmed_len = self.len() - start; - // Safety: we're just moving the trimmed portion to the start. It - // should be A-OK. + // Safety: we're just moving the trimmed portion to the start + // of the buffer and chopping the length to match. unsafe { - copy(trimmed_ptr, self.as_mut_ptr(), trimmed_len); + copy(self.as_ptr().add(start), self.as_mut_ptr(), trimmed_len); self.set_len(trimmed_len); } } - else { self.truncate(0); } } + else { self.truncate(0); } } /// # Trim End Mut. @@ -508,9 +492,10 @@ impl TrimMut for Vec { /// assert_eq!(v, b" Hello World!"); /// ``` fn trim_end_mut(&mut self) { - let trimmed = self.trim_end(); - let trimmed_len = trimmed.len(); - self.truncate(trimmed_len); + if let Some(end) = self.iter().rposition(not_whitespace) { + self.truncate(end + 1); + } + else { self.truncate(0); } } } @@ -541,8 +526,8 @@ impl TrimMatchesMut for Vec { if 0 < trimmed_len { let trimmed_ptr = trimmed.as_ptr(); - // Safety: we're just moving the trimmed portion to the start. It - // should be A-OK. + // Safety: we're just moving the trimmed portion to the start + // of the buffer and chopping the length to match. unsafe { copy(trimmed_ptr, self.as_mut_ptr(), trimmed_len); self.set_len(trimmed_len); @@ -569,22 +554,19 @@ impl TrimMatchesMut for Vec { /// ``` 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(); + if let Some(start) = self.iter().position(|b: &u8| ! cb(*b)) { + if 0 < start { + let trimmed_len = self.len() - start; - // Safety: we're just moving the trimmed portion to the start. It - // should be A-OK. + // Safety: we're just moving the trimmed portion to the start + // of the buffer and chopping the length to match. unsafe { - copy(trimmed_ptr, self.as_mut_ptr(), trimmed_len); + copy(self.as_ptr().add(start), self.as_mut_ptr(), trimmed_len); self.set_len(trimmed_len); } } - else { self.truncate(0); } } + else { self.truncate(0); } } /// # Trim End Matches Mut. @@ -604,8 +586,9 @@ impl TrimMatchesMut for Vec { /// ``` fn trim_end_matches_mut(&mut self, cb: F) where F: Fn(Self::MatchUnit) -> bool { - let trimmed = self.trim_end_matches(cb); - let trimmed_len = trimmed.len(); - self.truncate(trimmed_len); + if let Some(end) = self.iter().rposition(|b: &u8| ! cb(*b)) { + self.truncate(end + 1); + } + else { self.truncate(0); } } } diff --git a/src/trim_slice.rs b/src/trim_slice.rs index 348d25d..1add00f 100644 --- a/src/trim_slice.rs +++ b/src/trim_slice.rs @@ -8,6 +8,8 @@ use alloc::{ vec::Vec, }; +use crate::not_whitespace; + /// # Trim Slice. @@ -212,16 +214,6 @@ trim_slice!(&[u8], Box<[u8]>, Vec); -#[allow(clippy::trivially_copy_pass_by_ref)] // It's the signature iterator wants. -#[inline] -/// # Not Whitespace. -/// -/// This callback is used to find the first or last non-whitespace byte in a -/// slice. It is only split off into its own method to enforce consistency. -const fn not_whitespace(b: &u8) -> bool { ! b.is_ascii_whitespace() } - - - #[cfg(test)] mod tests { use super::*; From 73b269a140f5f310c7d25cfc0a687c5d3c18ba80 Mon Sep 17 00:00:00 2001 From: Josh Stoik Date: Mon, 11 Apr 2022 22:23:27 -0700 Subject: [PATCH 09/10] docs --- CHANGELOG.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 25ee47a..d38ffe7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,9 +2,13 @@ ## [0.1.1](https://github.com/Blobfolio/trimothy/releases/tag/v0.1.1) - 2022-04-11 +## Changes + +* Minor performance improvements + ## Fixed -* Return empty slice on non-empty, all match. +* Return empty slice when all bytes match trim predicate. * Markdown (docs) formatting issues. From a579b0adaae2496686a6123fd19607f54d176175 Mon Sep 17 00:00:00 2001 From: Josh Stoik Date: Mon, 11 Apr 2022 22:23:45 -0700 Subject: [PATCH 10/10] bump: 0.1.1 --- CREDITS.md | 4 ++-- Cargo.toml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/CREDITS.md b/CREDITS.md index e7a5c30..ef608ed 100644 --- a/CREDITS.md +++ b/CREDITS.md @@ -1,6 +1,6 @@ # Project Dependencies Package: trimothy - Version: 0.1.0 - Generated: 2022-04-11 21:07:33 UTC + Version: 0.1.1 + Generated: 2022-04-12 05:23:34 UTC This package has no dependencies. diff --git a/Cargo.toml b/Cargo.toml index 725397c..8bf54a1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "trimothy" -version = "0.1.0" +version = "0.1.1" authors = ["Blobfolio, LLC. "] edition = "2021" rust-version = "1.60"