use std::borrow::Borrow; use std::borrow::Cow; use std::borrow::ToOwned; use std::ffi::OsStr; use std::ffi::OsString; use std::fmt; use std::fmt::Debug; use std::fmt::Display; use std::fmt::Formatter; use std::mem; use std::ops::Deref; use std::ops::Index; use std::ops::Range; use std::ops::RangeFrom; use std::ops::RangeFull; use std::ops::RangeInclusive; use std::ops::RangeTo; use std::ops::RangeToInclusive; use std::result; use std::str; #[cfg(feature = "memchr")] use memchr::memmem::find; #[cfg(feature = "memchr")] use memchr::memmem::rfind; use super::imp; use super::imp::raw; use super::iter::Split; use super::pattern::Encoded as EncodedPattern; use super::private; use super::Pattern; if_checked_conversions! { use super::EncodingError; use super::Result; } #[cfg(not(feature = "memchr"))] fn find(string: &[u8], pat: &[u8]) -> Option { (0..=string.len().checked_sub(pat.len())?) .find(|&x| string[x..].starts_with(pat)) } #[cfg(not(feature = "memchr"))] fn rfind(string: &[u8], pat: &[u8]) -> Option { (pat.len()..=string.len()) .rfind(|&x| string[..x].ends_with(pat)) .map(|x| x - pat.len()) } #[allow(clippy::missing_safety_doc)] unsafe trait TransmuteBox { fn transmute_box(self: Box) -> Box where R: ?Sized + TransmuteBox, { let value = Box::into_raw(self); // SAFETY: This trait is only implemented for types that can be // transmuted. unsafe { Box::from_raw(mem::transmute_copy(&value)) } } } // SAFETY: This struct has a layout that makes this operation safe. unsafe impl TransmuteBox for RawOsStr {} unsafe impl TransmuteBox for [u8] {} /// A container for borrowed byte strings converted by this crate. /// /// This wrapper is intended to prevent violating the invariants of the /// [unspecified encoding] used by this crate and minimize encoding /// conversions. /// /// # Indices /// /// Methods of this struct that accept indices require that the index lie on a /// UTF-8 boundary. Although it is possible to manipulate platform strings /// based on other indices, this crate currently does not support them for /// slicing methods. They would add significant complication to the /// implementation and are generally not necessary. However, all indices /// returned by this struct can be used for slicing. /// /// On Unix, all indices are permitted, to avoid false positives. However, /// relying on this implementation detail is discouraged. Platform-specific /// indices are error-prone. /// /// # Complexity /// /// All searching methods have worst-case multiplicative time complexity (i.e., /// `O(self.raw_len() * pat.len())`). Enabling the "memchr" feature allows /// these methods to instead run in linear time in the worst case (documented /// for [`memchr::memmem::find`][memchr complexity]). /// /// # Safety /// /// Although this type is annotated with `#[repr(transparent)]`, the inner /// representation is not stable. Transmuting between this type and any other /// causes immediate undefined behavior. /// /// [memchr complexity]: memchr::memmem::find#complexity /// [unspecified encoding]: super#encoding #[derive(Eq, Hash, Ord, PartialEq, PartialOrd)] #[cfg_attr(os_str_bytes_docs_rs, doc(cfg(feature = "raw_os_str")))] #[repr(transparent)] pub struct RawOsStr([u8]); impl RawOsStr { const fn from_inner(string: &[u8]) -> &Self { // SAFETY: This struct has a layout that makes this operation safe. unsafe { mem::transmute(string) } } /// Converts a platform-native string into a representation that can be /// more easily manipulated. /// /// This method performs the necessary conversion immediately, so it can be /// expensive to call. It is recommended to continue using the returned /// instance as long as possible (instead of the original [`OsStr`]), to /// avoid repeated conversions. /// /// # Examples /// /// ``` /// use std::env; /// # use std::io; /// /// use os_str_bytes::RawOsStr; /// /// let os_string = env::current_exe()?.into_os_string(); /// println!("{:?}", RawOsStr::new(&os_string)); /// # /// # Ok::<_, io::Error>(()) /// ``` #[inline] #[must_use] pub fn new(string: &OsStr) -> Cow<'_, Self> { match imp::os_str_to_bytes(string) { Cow::Borrowed(string) => Cow::Borrowed(Self::from_inner(string)), Cow::Owned(string) => Cow::Owned(RawOsString(string)), } } /// Wraps a string, without copying or encoding conversion. /// /// This method is much more efficient than [`RawOsStr::new`], since the /// [encoding] used by this crate is compatible with UTF-8. /// /// # Examples /// /// ``` /// use os_str_bytes::RawOsStr; /// /// let string = "foobar"; /// let raw = RawOsStr::from_str(string); /// assert_eq!(string, raw); /// ``` /// /// [encoding]: super#encoding #[allow(clippy::should_implement_trait)] #[inline] #[must_use] pub fn from_str(string: &str) -> &Self { Self::from_inner(string.as_bytes()) } /// Wraps a byte string, without copying or encoding conversion. /// /// # Panics /// /// Panics if the string is not valid for the [unspecified encoding] used /// by this crate. /// /// # Examples /// /// ``` /// use std::env; /// # use std::io; /// /// use os_str_bytes::RawOsStr; /// /// let os_string = env::current_exe()?.into_os_string(); /// let raw = RawOsStr::new(&os_string); /// let raw_bytes = raw.as_raw_bytes(); /// assert_eq!(&*raw, RawOsStr::assert_from_raw_bytes(raw_bytes)); /// # /// # Ok::<_, io::Error>(()) /// ``` /// /// [unspecified encoding]: super#encoding #[inline] #[must_use = "method should not be used for validation"] #[track_caller] pub fn assert_from_raw_bytes(string: &[u8]) -> &Self { expect_encoded!(raw::validate_bytes(string)); Self::from_inner(string) } if_checked_conversions! { /// Wraps a byte string, without copying or encoding conversion. /// /// [`assert_from_raw_bytes`] should almost always be used instead. For /// more information, see [`EncodingError`]. /// /// # Errors /// /// See documentation for [`EncodingError`]. /// /// # Examples /// /// ``` /// use std::env; /// # use std::io; /// /// use os_str_bytes::RawOsStr; /// /// let os_string = env::current_exe()?.into_os_string(); /// let raw = RawOsStr::new(&os_string); /// assert_eq!(Ok(&*raw), RawOsStr::from_raw_bytes(raw.as_raw_bytes())); /// # /// # Ok::<_, io::Error>(()) /// ``` /// /// [`assert_from_raw_bytes`]: Self::assert_from_raw_bytes #[cfg_attr( os_str_bytes_docs_rs, doc(cfg(feature = "checked_conversions")) )] #[inline] pub fn from_raw_bytes(string: &[u8]) -> Result<&Self> { raw::validate_bytes(string) .map(|()| Self::from_inner(string)) .map_err(EncodingError) } } /// Wraps a byte string, without copying or encoding conversion. /// /// # Safety /// /// The string must be valid for the [unspecified encoding] used by this /// crate. /// /// # Examples /// /// ``` /// use std::env; /// # use std::io; /// /// use os_str_bytes::RawOsStr; /// /// let os_string = env::current_exe()?.into_os_string(); /// let raw = RawOsStr::new(&os_string); /// let raw_bytes = raw.as_raw_bytes(); /// assert_eq!(&*raw, unsafe { /// RawOsStr::from_raw_bytes_unchecked(raw_bytes) /// }); /// # /// # Ok::<_, io::Error>(()) /// ``` /// /// [unspecified encoding]: super#encoding #[inline] #[must_use] #[track_caller] pub unsafe fn from_raw_bytes_unchecked(string: &[u8]) -> &Self { if cfg!(debug_assertions) { expect_encoded!(raw::validate_bytes(string)); } Self::from_inner(string) } /// Returns the byte string stored by this container. /// /// The returned string will use an [unspecified encoding]. /// /// # Examples /// /// ``` /// use os_str_bytes::RawOsStr; /// /// let string = "foobar"; /// let raw = RawOsStr::from_str(string); /// assert_eq!(string.as_bytes(), raw.as_raw_bytes()); /// ``` /// /// [unspecified encoding]: super#encoding #[inline] #[must_use] pub fn as_raw_bytes(&self) -> &[u8] { &self.0 } /// Equivalent to [`str::contains`]. /// /// # Examples /// /// ``` /// use os_str_bytes::RawOsStr; /// /// let raw = RawOsStr::from_str("foobar"); /// assert!(raw.contains("oo")); /// assert!(!raw.contains("of")); /// ``` #[inline] #[must_use] pub fn contains

(&self, pat: P) -> bool where P: Pattern, { self.find(pat).is_some() } /// Equivalent to [`str::ends_with`]. /// /// # Examples /// /// ``` /// use os_str_bytes::RawOsStr; /// /// let raw = RawOsStr::from_str("foobar"); /// assert!(raw.ends_with("bar")); /// assert!(!raw.ends_with("foo")); /// ``` #[inline] #[must_use] pub fn ends_with

(&self, pat: P) -> bool where P: Pattern, { let pat = pat.__encode(); let pat = pat.__get(); self.0.ends_with(pat) } /// Equivalent to [`str::ends_with`] but accepts this type for the pattern. /// /// # Examples /// /// ``` /// use os_str_bytes::RawOsStr; /// /// let raw = RawOsStr::from_str("foobar"); /// assert!(raw.ends_with_os(RawOsStr::from_str("bar"))); /// assert!(!raw.ends_with_os(RawOsStr::from_str("foo"))); /// ``` #[inline] #[must_use] pub fn ends_with_os(&self, pat: &Self) -> bool { raw::ends_with(&self.0, &pat.0) } /// Equivalent to [`str::find`]. /// /// # Examples /// /// ``` /// use os_str_bytes::RawOsStr; /// /// let raw = RawOsStr::from_str("foobar"); /// assert_eq!(Some(1), raw.find("o")); /// assert_eq!(None, raw.find("of")); /// ``` #[inline] #[must_use] pub fn find

(&self, pat: P) -> Option where P: Pattern, { let pat = pat.__encode(); let pat = pat.__get(); find(&self.0, pat) } /// Equivalent to [`str::is_empty`]. /// /// # Examples /// /// ``` /// use os_str_bytes::RawOsStr; /// /// assert!(RawOsStr::from_str("").is_empty()); /// assert!(!RawOsStr::from_str("foobar").is_empty()); /// ``` #[inline] #[must_use] pub fn is_empty(&self) -> bool { self.0.is_empty() } /// Returns the length of the byte string stored by this container. /// /// Only the following assumptions can be made about the result: /// - The length of any Unicode character is the length of its UTF-8 /// representation (i.e., [`char::len_utf8`]). /// - Splitting a string at a UTF-8 boundary will return two strings with /// lengths that sum to the length of the original string. /// /// This method may return a different result than would [`OsStr::len`] /// when called on same string, since [`OsStr`] uses an unspecified /// encoding. /// /// # Examples /// /// ``` /// use os_str_bytes::RawOsStr; /// /// assert_eq!(6, RawOsStr::from_str("foobar").raw_len()); /// assert_eq!(0, RawOsStr::from_str("").raw_len()); /// ``` #[inline] #[must_use] pub fn raw_len(&self) -> usize { self.0.len() } /// Equivalent to [`str::rfind`]. /// /// # Examples /// /// ``` /// use os_str_bytes::RawOsStr; /// /// let raw = RawOsStr::from_str("foobar"); /// assert_eq!(Some(2), raw.rfind("o")); /// assert_eq!(None, raw.rfind("of")); /// ``` #[inline] #[must_use] pub fn rfind

(&self, pat: P) -> Option where P: Pattern, { let pat = pat.__encode(); let pat = pat.__get(); rfind(&self.0, pat) } fn split_once_raw_with( &self, pat: &P, find_fn: F, ) -> Option<(&Self, &Self)> where F: FnOnce(&[u8], &[u8]) -> Option, P: EncodedPattern, { let pat = pat.__get(); let index = find_fn(&self.0, pat)?; let prefix = &self.0[..index]; let suffix = &self.0[index + pat.len()..]; Some((Self::from_inner(prefix), Self::from_inner(suffix))) } pub(super) fn rsplit_once_raw

(&self, pat: &P) -> Option<(&Self, &Self)> where P: EncodedPattern, { self.split_once_raw_with(pat, rfind) } /// Equivalent to [`str::rsplit_once`]. /// /// # Examples /// /// ``` /// use os_str_bytes::RawOsStr; /// /// let raw = RawOsStr::from_str("foobar"); /// assert_eq!( /// Some((RawOsStr::from_str("fo"), RawOsStr::from_str("bar"))), /// raw.rsplit_once("o"), /// ); /// assert_eq!(None, raw.rsplit_once("of")); /// ``` #[inline] #[must_use] pub fn rsplit_once

(&self, pat: P) -> Option<(&Self, &Self)> where P: Pattern, { self.rsplit_once_raw(&pat.__encode()) } // https://github.com/rust-lang/rust/blob/49c68bd53f90e375bfb3cbba8c1c67a9e0adb9c0/src/libcore/str/mod.rs#L2184-L2221 #[cold] #[inline(never)] #[track_caller] fn index_boundary_error(&self, index: usize) -> ! { debug_assert!(raw::is_continuation(self.0[index])); let start = expect_encoded!(self.0[..index] .iter() .rposition(|&x| !raw::is_continuation(x))); let mut end = index + 1; end += self.0[end..] .iter() .take_while(|&&x| raw::is_continuation(x)) .count(); let code_point = raw::decode_code_point(&self.0[start..end]); panic!( "byte index {} is not a valid boundary; it is inside U+{:04X} \ (bytes {}..{})", index, code_point, start, end, ); } #[track_caller] fn check_bound(&self, index: usize) { if let Some(&byte) = self.0.get(index) { if raw::is_continuation(byte) { self.index_boundary_error(index); } } } /// Equivalent to [`str::split`], but empty patterns are not accepted. /// /// # Panics /// /// Panics if the pattern is empty. /// /// # Examples /// /// ``` /// use os_str_bytes::RawOsStr; /// /// let raw = RawOsStr::from_str("foobar"); /// assert_eq!(["f", "", "bar"], *raw.split("o").collect::>()); /// ``` #[inline] #[must_use] #[track_caller] pub fn split

(&self, pat: P) -> Split<'_, P> where P: Pattern, { Split::new(self, pat) } /// Equivalent to [`str::split_at`]. /// /// # Panics /// /// Panics if the index is not a [valid boundary]. /// /// # Examples /// /// ``` /// use os_str_bytes::RawOsStr; /// /// let raw = RawOsStr::from_str("foobar"); /// assert_eq!( /// ((RawOsStr::from_str("fo"), RawOsStr::from_str("obar"))), /// raw.split_at(2), /// ); /// ``` /// /// [valid boundary]: #indices #[inline] #[must_use] #[track_caller] pub fn split_at(&self, mid: usize) -> (&Self, &Self) { self.check_bound(mid); let (prefix, suffix) = self.0.split_at(mid); (Self::from_inner(prefix), Self::from_inner(suffix)) } pub(super) fn split_once_raw

(&self, pat: &P) -> Option<(&Self, &Self)> where P: EncodedPattern, { self.split_once_raw_with(pat, find) } /// Equivalent to [`str::split_once`]. /// /// # Examples /// /// ``` /// use os_str_bytes::RawOsStr; /// /// let raw = RawOsStr::from_str("foobar"); /// assert_eq!( /// Some((RawOsStr::from_str("f"), RawOsStr::from_str("obar"))), /// raw.split_once("o"), /// ); /// assert_eq!(None, raw.split_once("of")); /// ``` #[inline] #[must_use] pub fn split_once

(&self, pat: P) -> Option<(&Self, &Self)> where P: Pattern, { self.split_once_raw(&pat.__encode()) } /// Equivalent to [`str::starts_with`]. /// /// # Examples /// /// ``` /// use os_str_bytes::RawOsStr; /// /// let raw = RawOsStr::from_str("foobar"); /// assert!(raw.starts_with("foo")); /// assert!(!raw.starts_with("bar")); /// ``` #[inline] #[must_use] pub fn starts_with

(&self, pat: P) -> bool where P: Pattern, { let pat = pat.__encode(); let pat = pat.__get(); self.0.starts_with(pat) } /// Equivalent to [`str::starts_with`] but accepts this type for the /// pattern. /// /// # Examples /// /// ``` /// use os_str_bytes::RawOsStr; /// /// let raw = RawOsStr::from_str("foobar"); /// assert!(raw.starts_with_os(RawOsStr::from_str("foo"))); /// assert!(!raw.starts_with_os(RawOsStr::from_str("bar"))); /// ``` #[inline] #[must_use] pub fn starts_with_os(&self, pat: &Self) -> bool { raw::starts_with(&self.0, &pat.0) } /// Equivalent to [`str::strip_prefix`]. /// /// # Examples /// /// ``` /// use os_str_bytes::RawOsStr; /// /// let raw = RawOsStr::from_str("111foo1bar111"); /// assert_eq!( /// Some(RawOsStr::from_str("11foo1bar111")), /// raw.strip_prefix("1"), /// ); /// assert_eq!(None, raw.strip_prefix("o")); /// ``` #[inline] #[must_use] pub fn strip_prefix

(&self, pat: P) -> Option<&Self> where P: Pattern, { let pat = pat.__encode(); let pat = pat.__get(); self.0.strip_prefix(pat).map(Self::from_inner) } /// Equivalent to [`str::strip_suffix`]. /// /// # Examples /// /// ``` /// use os_str_bytes::RawOsStr; /// /// let raw = RawOsStr::from_str("111foo1bar111"); /// assert_eq!( /// Some(RawOsStr::from_str("111foo1bar11")), /// raw.strip_suffix("1"), /// ); /// assert_eq!(None, raw.strip_suffix("o")); /// ``` #[inline] #[must_use] pub fn strip_suffix

(&self, pat: P) -> Option<&Self> where P: Pattern, { let pat = pat.__encode(); let pat = pat.__get(); self.0.strip_suffix(pat).map(Self::from_inner) } /// Converts this representation back to a platform-native string. /// /// When possible, use [`RawOsStrCow::into_os_str`] for a more efficient /// conversion on some platforms. /// /// # Examples /// /// ``` /// use std::env; /// # use std::io; /// /// use os_str_bytes::RawOsStr; /// /// let os_string = env::current_exe()?.into_os_string(); /// let raw = RawOsStr::new(&os_string); /// assert_eq!(os_string, raw.to_os_str()); /// # /// # Ok::<_, io::Error>(()) /// ``` #[inline] #[must_use] pub fn to_os_str(&self) -> Cow<'_, OsStr> { expect_encoded!(imp::os_str_from_bytes(&self.0)) } /// Equivalent to [`OsStr::to_str`]. /// /// # Examples /// /// ``` /// use os_str_bytes::RawOsStr; /// /// let string = "foobar"; /// let raw = RawOsStr::from_str(string); /// assert_eq!(Some(string), raw.to_str()); /// ``` #[inline] #[must_use] pub fn to_str(&self) -> Option<&str> { str::from_utf8(&self.0).ok() } /// Converts this string to the best UTF-8 representation possible. /// /// Invalid sequences will be replaced with /// [`char::REPLACEMENT_CHARACTER`]. /// /// This method may return a different result than would /// [`OsStr::to_string_lossy`] when called on same string, since [`OsStr`] /// uses an unspecified encoding. /// /// # Examples /// /// ``` /// use std::env; /// # use std::io; /// /// use os_str_bytes::RawOsStr; /// /// let os_string = env::current_exe()?.into_os_string(); /// let raw = RawOsStr::new(&os_string); /// println!("{}", raw.to_str_lossy()); /// # /// # Ok::<_, io::Error>(()) /// ``` #[inline] #[must_use] pub fn to_str_lossy(&self) -> Cow<'_, str> { String::from_utf8_lossy(&self.0) } fn trim_matches_raw_with(&self, pat: &P, strip_fn: F) -> &Self where F: for<'a> Fn(&'a [u8], &[u8]) -> Option<&'a [u8]>, P: EncodedPattern, { let pat = pat.__get(); if pat.is_empty() { return self; } let mut string = &self.0; while let Some(substring) = strip_fn(string, pat) { string = substring; } Self::from_inner(string) } fn trim_end_matches_raw

(&self, pat: &P) -> &Self where P: EncodedPattern, { self.trim_matches_raw_with(pat, <[_]>::strip_suffix) } /// Equivalent to [`str::trim_end_matches`]. /// /// # Examples /// /// ``` /// use os_str_bytes::RawOsStr; /// /// let raw = RawOsStr::from_str("111foo1bar111"); /// assert_eq!("111foo1bar", raw.trim_end_matches("1")); /// assert_eq!("111foo1bar111", raw.trim_end_matches("o")); /// ``` #[inline] #[must_use] pub fn trim_end_matches

(&self, pat: P) -> &Self where P: Pattern, { self.trim_end_matches_raw(&pat.__encode()) } /// Equivalent to [`str::trim_matches`]. /// /// # Examples /// /// ``` /// use os_str_bytes::RawOsStr; /// /// let raw = RawOsStr::from_str("111foo1bar111"); /// assert_eq!("foo1bar", raw.trim_matches("1")); /// assert_eq!("111foo1bar111", raw.trim_matches("o")); /// ``` #[inline] #[must_use] pub fn trim_matches

(&self, pat: P) -> &Self where P: Pattern, { let pat = pat.__encode(); self.trim_start_matches_raw(&pat).trim_end_matches_raw(&pat) } fn trim_start_matches_raw

(&self, pat: &P) -> &Self where P: EncodedPattern, { self.trim_matches_raw_with(pat, <[_]>::strip_prefix) } /// Equivalent to [`str::trim_start_matches`]. /// /// # Examples /// /// ``` /// use os_str_bytes::RawOsStr; /// /// let raw = RawOsStr::from_str("111foo1bar111"); /// assert_eq!("foo1bar111", raw.trim_start_matches("1")); /// assert_eq!("111foo1bar111", raw.trim_start_matches("o")); /// ``` #[inline] #[must_use] pub fn trim_start_matches

(&self, pat: P) -> &Self where P: Pattern, { self.trim_start_matches_raw(&pat.__encode()) } } impl AsRef for RawOsStr { #[inline] fn as_ref(&self) -> &Self { self } } impl AsRef for str { #[inline] fn as_ref(&self) -> &RawOsStr { RawOsStr::from_str(self) } } impl AsRef for String { #[inline] fn as_ref(&self) -> &RawOsStr { (**self).as_ref() } } impl Default for &RawOsStr { #[inline] fn default() -> Self { RawOsStr::from_str("") } } impl<'a> From<&'a RawOsStr> for Cow<'a, RawOsStr> { #[inline] fn from(value: &'a RawOsStr) -> Self { Cow::Borrowed(value) } } impl From> for Box { #[inline] fn from(value: Box) -> Self { value.into_boxed_bytes().transmute_box() } } impl ToOwned for RawOsStr { type Owned = RawOsString; #[inline] fn to_owned(&self) -> Self::Owned { RawOsString(self.0.to_owned()) } } /// Extensions to [`Cow`] for additional conversions. /// /// [`Cow`]: Cow #[cfg_attr(os_str_bytes_docs_rs, doc(cfg(feature = "raw_os_str")))] pub trait RawOsStrCow<'a>: private::Sealed { /// Converts this representation back to a platform-native string. /// /// # Examples /// /// ``` /// use std::env; /// # use std::io; /// /// use os_str_bytes::RawOsStr; /// use os_str_bytes::RawOsStrCow; /// /// let os_string = env::current_exe()?.into_os_string(); /// let raw = RawOsStr::new(&os_string); /// assert_eq!(os_string, raw.into_os_str()); /// # /// # Ok::<_, io::Error>(()) /// ``` #[must_use] fn into_os_str(self) -> Cow<'a, OsStr>; /// Returns the byte string stored by this container. /// /// The returned string will use an [unspecified encoding]. /// /// # Examples /// /// ``` /// use std::borrow::Cow; /// /// use os_str_bytes::RawOsStr; /// use os_str_bytes::RawOsStrCow; /// /// let string = "foobar"; /// let raw = Cow::Borrowed(RawOsStr::from_str(string)); /// assert_eq!(string.as_bytes(), &*raw.into_raw_bytes()); /// ``` /// /// [unspecified encoding]: super#encoding #[must_use] fn into_raw_bytes(self) -> Cow<'a, [u8]>; } impl<'a> RawOsStrCow<'a> for Cow<'a, RawOsStr> { #[inline] fn into_os_str(self) -> Cow<'a, OsStr> { match self { Cow::Borrowed(string) => string.to_os_str(), Cow::Owned(string) => Cow::Owned(string.into_os_string()), } } #[inline] fn into_raw_bytes(self) -> Cow<'a, [u8]> { match self { Cow::Borrowed(string) => Cow::Borrowed(&string.0), Cow::Owned(string) => Cow::Owned(string.0), } } } /// A container for owned byte strings converted by this crate. /// /// For more information, see [`RawOsStr`]. #[derive(Clone, Default, Eq, Hash, Ord, PartialEq, PartialOrd)] #[cfg_attr(os_str_bytes_docs_rs, doc(cfg(feature = "raw_os_str")))] pub struct RawOsString(Vec); impl RawOsString { /// Converts a platform-native string into a representation that can be /// more easily manipulated. /// /// For more information, see [`RawOsStr::new`]. /// /// # Examples /// /// ``` /// use std::env; /// # use std::io; /// /// use os_str_bytes::RawOsString; /// /// let os_string = env::current_exe()?.into_os_string(); /// println!("{:?}", RawOsString::new(os_string)); /// # /// # Ok::<_, io::Error>(()) /// ``` #[inline] #[must_use] pub fn new(string: OsString) -> Self { Self(imp::os_string_into_vec(string)) } /// Wraps a string, without copying or encoding conversion. /// /// This method is much more efficient than [`RawOsString::new`], since the /// [encoding] used by this crate is compatible with UTF-8. /// /// # Examples /// /// ``` /// use os_str_bytes::RawOsString; /// /// let string = "foobar".to_owned(); /// let raw = RawOsString::from_string(string.clone()); /// assert_eq!(string, raw); /// ``` /// /// [encoding]: super#encoding #[inline] #[must_use] pub fn from_string(string: String) -> Self { Self(string.into_bytes()) } /// Wraps a byte string, without copying or encoding conversion. /// /// # Panics /// /// Panics if the string is not valid for the [unspecified encoding] used /// by this crate. /// /// # Examples /// /// ``` /// use std::env; /// # use std::io; /// /// use os_str_bytes::RawOsString; /// /// let os_string = env::current_exe()?.into_os_string(); /// let raw = RawOsString::new(os_string); /// let raw_bytes = raw.clone().into_raw_vec(); /// assert_eq!(raw, RawOsString::assert_from_raw_vec(raw_bytes)); /// # /// # Ok::<_, io::Error>(()) /// ``` /// /// [unspecified encoding]: super#encoding #[inline] #[must_use = "method should not be used for validation"] #[track_caller] pub fn assert_from_raw_vec(string: Vec) -> Self { expect_encoded!(raw::validate_bytes(&string)); Self(string) } if_checked_conversions! { /// Wraps a byte string, without copying or encoding conversion. /// /// [`assert_from_raw_vec`] should almost always be used instead. For /// more information, see [`EncodingError`]. /// /// # Errors /// /// See documentation for [`EncodingError`]. /// /// # Examples /// /// ``` /// use std::env; /// # use std::io; /// /// use os_str_bytes::RawOsString; /// /// let os_string = env::current_exe()?.into_os_string(); /// let raw = RawOsString::new(os_string); /// let raw_clone = raw.clone(); /// assert_eq!(Ok(raw), RawOsString::from_raw_vec(raw_clone.into_raw_vec())); /// # /// # Ok::<_, io::Error>(()) /// ``` /// /// [`assert_from_raw_vec`]: Self::assert_from_raw_vec #[cfg_attr( os_str_bytes_docs_rs, doc(cfg(feature = "checked_conversions")) )] #[inline] pub fn from_raw_vec(string: Vec) -> Result { raw::validate_bytes(&string) .map(|()| Self(string)) .map_err(EncodingError) } } /// Wraps a byte string, without copying or encoding conversion. /// /// # Safety /// /// The string must be valid for the [unspecified encoding] used by this /// crate. /// /// # Examples /// /// ``` /// use std::env; /// # use std::io; /// /// use os_str_bytes::RawOsString; /// /// let os_string = env::current_exe()?.into_os_string(); /// let raw = RawOsString::new(os_string); /// let raw_bytes = raw.clone().into_raw_vec(); /// assert_eq!(raw, unsafe { /// RawOsString::from_raw_vec_unchecked(raw_bytes) /// }); /// # /// # Ok::<_, io::Error>(()) /// ``` /// /// [unspecified encoding]: super#encoding #[inline] #[must_use] #[track_caller] pub unsafe fn from_raw_vec_unchecked(string: Vec) -> Self { if cfg!(debug_assertions) { expect_encoded!(raw::validate_bytes(&string)); } Self(string) } /// Equivalent to [`String::clear`]. /// /// # Examples /// /// ``` /// use std::env; /// # use std::io; /// /// use os_str_bytes::RawOsString; /// /// let os_string = env::current_exe()?.into_os_string(); /// let mut raw = RawOsString::new(os_string); /// raw.clear(); /// assert!(raw.is_empty()); /// # /// # Ok::<_, io::Error>(()) /// ``` #[inline] pub fn clear(&mut self) { self.0.clear(); } /// Equivalent to [`String::into_boxed_str`]. /// /// # Examples /// /// ``` /// use os_str_bytes::RawOsString; /// /// let string = "foobar".to_owned(); /// let raw = RawOsString::from_string(string.clone()); /// assert_eq!(string, *raw.into_box()); /// ``` #[inline] #[must_use] pub fn into_box(self) -> Box { self.0.into_boxed_slice().transmute_box() } /// Converts this representation back to a platform-native string. /// /// # Examples /// /// ``` /// use std::env; /// # use std::io; /// /// use os_str_bytes::RawOsString; /// /// let os_string = env::current_exe()?.into_os_string(); /// let raw = RawOsString::new(os_string.clone()); /// assert_eq!(os_string, raw.into_os_string()); /// # /// # Ok::<_, io::Error>(()) /// ``` #[inline] #[must_use] pub fn into_os_string(self) -> OsString { expect_encoded!(imp::os_string_from_vec(self.0)) } /// Returns the byte string stored by this container. /// /// The returned string will use an [unspecified encoding]. /// /// # Examples /// /// ``` /// use os_str_bytes::RawOsString; /// /// let string = "foobar".to_owned(); /// let raw = RawOsString::from_string(string.clone()); /// assert_eq!(string.into_bytes(), raw.into_raw_vec()); /// ``` /// /// [unspecified encoding]: super#encoding #[inline] #[must_use] pub fn into_raw_vec(self) -> Vec { self.0 } /// Equivalent to [`OsString::into_string`]. /// /// # Examples /// /// ``` /// use os_str_bytes::RawOsString; /// /// let string = "foobar".to_owned(); /// let raw = RawOsString::from_string(string.clone()); /// assert_eq!(Ok(string), raw.into_string()); /// ``` #[inline] pub fn into_string(self) -> result::Result { String::from_utf8(self.0).map_err(|x| Self(x.into_bytes())) } /// Equivalent to [`String::shrink_to_fit`]. /// /// # Examples /// /// ``` /// use os_str_bytes::RawOsString; /// /// let string = "foobar".to_owned(); /// let mut raw = RawOsString::from_string(string.clone()); /// raw.shrink_to_fit(); /// assert_eq!(string, raw); /// ``` #[inline] pub fn shrink_to_fit(&mut self) { self.0.shrink_to_fit(); } /// Equivalent to [`String::split_off`]. /// /// # Panics /// /// Panics if the index is not a [valid boundary]. /// /// # Examples /// /// ``` /// use os_str_bytes::RawOsString; /// /// let mut raw = RawOsString::from_string("foobar".to_owned()); /// assert_eq!("bar", raw.split_off(3)); /// assert_eq!("foo", raw); /// ``` /// /// [valid boundary]: RawOsStr#indices #[inline] #[must_use] #[track_caller] pub fn split_off(&mut self, at: usize) -> Self { self.check_bound(at); Self(self.0.split_off(at)) } /// Equivalent to [`String::truncate`]. /// /// # Panics /// /// Panics if the index is not a [valid boundary]. /// /// # Examples /// /// ``` /// use os_str_bytes::RawOsString; /// /// let mut raw = RawOsString::from_string("foobar".to_owned()); /// raw.truncate(3); /// assert_eq!("foo", raw); /// ``` /// /// [valid boundary]: RawOsStr#indices #[inline] #[track_caller] pub fn truncate(&mut self, new_len: usize) { self.check_bound(new_len); self.0.truncate(new_len); } } impl AsRef for RawOsString { #[inline] fn as_ref(&self) -> &RawOsStr { self } } impl Borrow for RawOsString { #[inline] fn borrow(&self) -> &RawOsStr { self } } impl Deref for RawOsString { type Target = RawOsStr; #[inline] fn deref(&self) -> &Self::Target { RawOsStr::from_inner(&self.0) } } impl From for Box { #[inline] fn from(value: RawOsString) -> Self { value.into_box() } } impl From> for RawOsString { #[inline] fn from(value: Box) -> Self { Self(value.transmute_box::<[_]>().into_vec()) } } impl From for Cow<'_, RawOsStr> { #[inline] fn from(value: RawOsString) -> Self { Cow::Owned(value) } } impl From for RawOsString { #[inline] fn from(value: String) -> Self { Self::from_string(value) } } struct DebugBuffer<'a>(&'a [u8]); impl Debug for DebugBuffer<'_> { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { f.write_str("\"")?; let mut string = self.0; let mut invalid_length = 0; while !string.is_empty() { let (invalid, substring) = string.split_at(invalid_length); let valid = match str::from_utf8(substring) { Ok(valid) => { string = &[]; valid } Err(error) => { let (valid, substring) = substring.split_at(error.valid_up_to()); let invalid_char_length = error.error_len().unwrap_or_else(|| substring.len()); if valid.is_empty() { invalid_length += invalid_char_length; continue; } string = substring; invalid_length = invalid_char_length; // SAFETY: This slice was validated to be UTF-8. unsafe { str::from_utf8_unchecked(valid) } } }; raw::debug(invalid, f)?; Display::fmt(&valid.escape_debug(), f)?; } f.write_str("\"") } } macro_rules! r#impl { ( $type:ty ) => { impl Debug for $type { #[inline] fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { f.debug_tuple(stringify!($type)) .field(&DebugBuffer(&self.0)) .finish() } } }; } r#impl!(RawOsStr); r#impl!(RawOsString); macro_rules! r#impl { ( $index_type:ty $(, $index_var:ident , $($bound:expr),+)? ) => { impl Index<$index_type> for RawOsStr { type Output = Self; #[inline] fn index(&self, idx: $index_type) -> &Self::Output { $( let $index_var = &idx; $(self.check_bound($bound);)+ )? Self::from_inner(&self.0[idx]) } } impl Index<$index_type> for RawOsString { type Output = RawOsStr; #[allow(clippy::indexing_slicing)] #[inline] fn index(&self, idx: $index_type) -> &Self::Output { &(**self)[idx] } } }; } r#impl!(Range, x, x.start, x.end); r#impl!(RangeFrom, x, x.start); r#impl!(RangeFull); // [usize::MAX] will always be a valid inclusive end index. #[rustfmt::skip] r#impl!(RangeInclusive, x, *x.start(), x.end().wrapping_add(1)); r#impl!(RangeTo, x, x.end); r#impl!(RangeToInclusive, x, x.end.wrapping_add(1)); macro_rules! r#impl { ( $type:ty , $other_type:ty ) => { impl PartialEq<$other_type> for $type { #[inline] fn eq(&self, other: &$other_type) -> bool { let raw: &RawOsStr = self; let other: &RawOsStr = other.as_ref(); raw == other } } impl PartialEq<$type> for $other_type { #[inline] fn eq(&self, other: &$type) -> bool { other == self } } }; } r#impl!(RawOsStr, RawOsString); r#impl!(&RawOsStr, RawOsString); r#impl!(RawOsStr, str); r#impl!(RawOsStr, String); r#impl!(&RawOsStr, String); r#impl!(RawOsString, str); r#impl!(RawOsString, &str); r#impl!(RawOsString, String); #[cfg(feature = "print_bytes")] #[cfg_attr(os_str_bytes_docs_rs, doc(cfg(feature = "print_bytes")))] mod print_bytes { use print_bytes::ByteStr; use print_bytes::ToBytes; #[cfg(windows)] use print_bytes::WideStr; #[cfg(windows)] use crate::imp::raw; use super::RawOsStr; use super::RawOsString; impl ToBytes for RawOsStr { #[inline] fn to_bytes(&self) -> ByteStr<'_> { self.0.to_bytes() } #[cfg(windows)] #[inline] fn to_wide(&self) -> Option { Some(WideStr::new(raw::encode_wide_unchecked(&self.0).collect())) } } impl ToBytes for RawOsString { #[inline] fn to_bytes(&self) -> ByteStr<'_> { (**self).to_bytes() } #[cfg(windows)] #[inline] fn to_wide(&self) -> Option { (**self).to_wide() } } } #[cfg(feature = "uniquote")] #[cfg_attr(os_str_bytes_docs_rs, doc(cfg(feature = "uniquote")))] mod uniquote { use uniquote::Formatter; use uniquote::Quote; use uniquote::Result; use crate::imp::raw; use super::RawOsStr; use super::RawOsString; impl Quote for RawOsStr { #[inline] fn escape(&self, f: &mut Formatter<'_>) -> Result { raw::uniquote::escape(&self.0, f) } } impl Quote for RawOsString { #[inline] fn escape(&self, f: &mut Formatter<'_>) -> Result { (**self).escape(f) } } }