diff --git a/CHANGELOG.md b/CHANGELOG.md index 664df96..ecf990b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,25 +2,38 @@ Notable changes only. -## [0.5.0] - unreleased +## [unreleased] -## Added +### Added + +- add optional `bstr` crate support +- add `truncate`, `pop`, and `clear` +- add `concat` and `join` + +### Fixed + +- some clippy lints +- fix truncating `set_len` on borrowed + +## [0.5.0] - 2024-06-24 + +### Added - new conversion to `Cow`s - a niche to make `Option` the same size as `HipStr` -## Changed +### Changed - update MSRV to 1.77 - backend complete overall with its own custom RC implementation -## Fixed +### Fixed - fixed some clippy lints ## [0.4.0] - 2023-12-01 -## Added +### Added - add `HipPath` and `HipOsStr` (#11) - more methods to keep working with `Hip*` types when possible (#13) @@ -29,21 +42,21 @@ Notable changes only. Most of those addition are breaking because they shadows `str`'s methods. -## Changed +### Changed - make equality more efficient (#12 then #18) - better coverage with more tests (#14) ## [0.3.3] - 2023-10-30 -## Fixed +### Fixed - fix clippy lint in `bytes/cmp.rs` - fix missing `std::error::Error` impl for `string::SliceError` ## [0.3.2] - 2023-08-18 -## Added +### Added - Add trait impls for OsStr/Path compatibility - Add push_str and push @@ -94,6 +107,8 @@ Most of those addition are breaking because they shadows `str`'s methods. Initial release +[unreleased]: https://github.com/polazarus/hipstr/compare/0.4.0...HEAD +[0.5.0]: https://github.com/polazarus/hipstr/compare/0.4.0...0.5.0 [0.4.0]: https://github.com/polazarus/hipstr/compare/0.3.3...0.4.0 [0.3.3]: https://github.com/polazarus/hipstr/compare/0.3.2...0.3.3 [0.3.2]: https://github.com/polazarus/hipstr/compare/0.3.1...0.3.2 diff --git a/Cargo.toml b/Cargo.toml index 109a38b..88348fc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,6 +17,7 @@ default = ["std"] std = ["serde/std"] unstable = [] serde = ["dep:serde", "dep:serde_bytes"] +bstr = ["dep:bstr"] [dev-dependencies] fastrand = "2.0.0" @@ -25,6 +26,7 @@ serde = { version = "1.0.69", default-features = false, features = ["derive", "a serde_json = { version = "1.0", default-features = false, features = ["alloc"] } [dependencies] +bstr = { version = "1.9.1", optional = true, features = ["alloc"] } serde = { version = "1.0.69", optional = true, default-features = false, features = [] } serde_bytes = { version = "0.11.5", optional = true, default-features = false, features = [ "alloc", diff --git a/src/bytes.rs b/src/bytes.rs index 67f2bab..4888fd1 100644 --- a/src/bytes.rs +++ b/src/bytes.rs @@ -20,9 +20,24 @@ mod convert; #[cfg(feature = "serde")] pub mod serde; +#[cfg(feature = "bstr")] +mod bstr; + #[cfg(test)] mod tests; +#[cfg(feature = "bstr")] +type Owned = ::bstr::BString; + +#[cfg(not(feature = "bstr"))] +type Owned = Vec; + +#[cfg(feature = "bstr")] +type Slice = ::bstr::BStr; + +#[cfg(not(feature = "bstr"))] +type Slice = [u8]; + /// Smart bytes, i.e. cheaply clonable and sliceable byte string. /// /// # Examples @@ -490,6 +505,10 @@ where #[must_use] pub fn mutate(&mut self) -> RefMut<'_, 'borrow, B> { let owned = self.0.take_vec(); + + #[cfg(feature = "bstr")] + let owned = owned.into(); + RefMut { result: self, owned, @@ -1095,11 +1114,11 @@ impl<'borrow, B> Deref for HipByt<'borrow, B> where B: Backend, { - type Target = [u8]; + type Target = Slice; #[inline] fn deref(&self) -> &Self::Target { - self.as_slice() + self.as_ref() } } @@ -1323,7 +1342,7 @@ where B: Backend, { result: &'a mut HipByt<'borrow, B>, - owned: Vec, + owned: Owned, } impl<'a, 'borrow, B> Drop for RefMut<'a, 'borrow, B> @@ -1340,7 +1359,8 @@ impl<'a, 'borrow, B> Deref for RefMut<'a, 'borrow, B> where B: Backend, { - type Target = Vec; + type Target = Owned; + fn deref(&self) -> &Self::Target { &self.owned } diff --git a/src/bytes/bstr.rs b/src/bytes/bstr.rs new file mode 100644 index 0000000..7825f13 --- /dev/null +++ b/src/bytes/bstr.rs @@ -0,0 +1,136 @@ +use alloc::borrow::Cow; +use core::borrow::Borrow; + +use bstr::{BStr, BString}; + +use super::HipByt; +use crate::Backend; + +impl<'borrow, B> Borrow for HipByt<'borrow, B> +where + B: Backend, +{ + #[inline] + fn borrow(&self) -> &BStr { + BStr::new(self.as_slice()) + } +} + +impl<'borrow, B> AsRef for HipByt<'borrow, B> +where + B: Backend, +{ + #[inline] + fn as_ref(&self) -> &BStr { + BStr::new(self.as_slice()) + } +} + +impl<'borrow, B> From<&'borrow BStr> for HipByt<'borrow, B> +where + B: Backend, +{ + #[inline] + fn from(value: &'borrow BStr) -> Self { + HipByt::borrowed(value.as_ref()) + } +} + +impl<'borrow, B> From for HipByt<'borrow, B> +where + B: Backend, +{ + #[inline] + fn from(value: BString) -> Self { + HipByt::from(Vec::from(value)) + } +} + +impl<'borrow, B> From> for HipByt<'borrow, B> +where + B: Backend, +{ + #[inline] + fn from(value: Cow<'borrow, BStr>) -> Self { + match value { + Cow::Borrowed(b) => Self::from(b), + Cow::Owned(o) => Self::from(o), + } + } +} + +impl<'borrow, B> From> for BString +where + B: Backend, +{ + #[inline] + fn from(value: HipByt<'borrow, B>) -> Self { + value + .into_vec() + .map(Self::from) + .unwrap_or_else(|h| Self::from(h.as_slice())) + } +} + +#[cfg(test)] +mod tests { + use bstr::{ByteSlice, ByteVec}; + + use super::*; + use crate::HipByt; + + #[test] + fn test_borrow_bstr() { + let b = HipByt::from(b"Hello, World!"); + let s: &BStr = b.borrow(); + assert_eq!(s, "Hello, World!"); + assert!(s.contains_str("World")); + } + + #[test] + fn test_as_ref_bstr() { + let b = HipByt::from(b"Hello, World!"); + let s: &BStr = b.as_ref(); + assert_eq!(s, "Hello, World!"); + assert!(s.contains_str("World")); + } + + #[test] + fn test_from_bstr() { + let b = HipByt::from(BStr::new("Hello, World!")); + assert_eq!(&*b, "Hello, World!"); + } + + #[test] + fn test_from_bstring() { + let b = HipByt::from(BString::from("Hello, World!")); + assert_eq!(&*b, "Hello, World!"); + } + + #[test] + fn test_from_cow() { + let b = HipByt::from(Cow::Borrowed(BStr::new(b"Hello, World!"))); + assert_eq!(&*b, "Hello, World!"); + let b: HipByt = HipByt::from(Cow::::Owned(BString::from(b"Hello, World!"))); + assert_eq!(&*b, "Hello, World!"); + } + + #[test] + fn test_into_bstring() { + let b: HipByt<'static> = HipByt::from(b"Hello, World!"); + let bstring: BString = b.into(); + assert_eq!(bstring, "Hello, World!"); + } + + #[test] + fn test_mutate() { + let mut b = HipByt::from(b"Hello, World!"); + { + let mut m = b.mutate(); + let r: &mut BString = &mut m; + r.push_char('!'); + assert_eq!(r, "Hello, World!!"); + } + assert_eq!(b, b"Hello, World!!"); + } +}