lexarg_parser/
ext.rs

1use std::ffi::OsStr;
2
3pub(crate) trait OsStrExt: private::Sealed {
4    /// Converts to a string slice.
5    /// The `Utf8Error` is guaranteed to have a valid UTF8 boundary
6    /// in its `valid_up_to()`
7    fn try_str(&self) -> Result<&str, std::str::Utf8Error>;
8    /// Returns `true` if the given pattern matches a sub-slice of
9    /// this string slice.
10    ///
11    /// Returns `false` if it does not.
12    #[allow(dead_code)]
13    fn contains(&self, needle: &str) -> bool;
14    /// Returns the byte index of the first character of this string slice that
15    /// matches the pattern.
16    ///
17    /// Returns [`None`] if the pattern doesn't match.
18    fn find(&self, needle: &str) -> Option<usize>;
19    /// Returns a string slice with the prefix removed.
20    ///
21    /// If the string starts with the pattern `prefix`, returns substring after the prefix, wrapped
22    /// in `Some`.
23    ///
24    /// If the string does not start with `prefix`, returns `None`.
25    fn strip_prefix(&self, prefix: &str) -> Option<&OsStr>;
26    /// Returns `true` if the given pattern matches a prefix of this
27    /// string slice.
28    ///
29    /// Returns `false` if it does not.
30    fn starts_with(&self, prefix: &str) -> bool;
31    /// An iterator over substrings of this string slice, separated by
32    /// characters matched by a pattern.
33    #[allow(dead_code)]
34    fn split<'s, 'n>(&'s self, needle: &'n str) -> Split<'s, 'n>;
35    /// Splits the string on the first occurrence of the specified delimiter and
36    /// returns prefix before delimiter and suffix after delimiter.
37    fn split_once(&self, needle: &'_ str) -> Option<(&OsStr, &OsStr)>;
38}
39
40impl OsStrExt for OsStr {
41    fn try_str(&self) -> Result<&str, std::str::Utf8Error> {
42        let bytes = self.as_encoded_bytes();
43        std::str::from_utf8(bytes)
44    }
45
46    fn contains(&self, needle: &str) -> bool {
47        self.find(needle).is_some()
48    }
49
50    fn find(&self, needle: &str) -> Option<usize> {
51        let bytes = self.as_encoded_bytes();
52        (0..=self.len().checked_sub(needle.len())?)
53            .find(|&x| bytes[x..].starts_with(needle.as_bytes()))
54    }
55
56    fn strip_prefix(&self, prefix: &str) -> Option<&OsStr> {
57        let bytes = self.as_encoded_bytes();
58        bytes.strip_prefix(prefix.as_bytes()).map(|s| {
59            // SAFETY:
60            // - This came from `as_encoded_bytes`
61            // - Since `prefix` is `&str`, any split will be along UTF-8 boundary
62            unsafe { OsStr::from_encoded_bytes_unchecked(s) }
63        })
64    }
65    fn starts_with(&self, prefix: &str) -> bool {
66        let bytes = self.as_encoded_bytes();
67        bytes.starts_with(prefix.as_bytes())
68    }
69
70    fn split<'s, 'n>(&'s self, needle: &'n str) -> Split<'s, 'n> {
71        assert_ne!(needle, "");
72        Split {
73            haystack: Some(self),
74            needle,
75        }
76    }
77
78    fn split_once(&self, needle: &'_ str) -> Option<(&OsStr, &OsStr)> {
79        let start = self.find(needle)?;
80        let end = start + needle.len();
81        let haystack = self.as_encoded_bytes();
82        let first = &haystack[0..start];
83        let second = &haystack[end..];
84        // SAFETY:
85        // - This came from `as_encoded_bytes`
86        // - Since `needle` is `&str`, any split will be along UTF-8 boundary
87        unsafe {
88            Some((
89                OsStr::from_encoded_bytes_unchecked(first),
90                OsStr::from_encoded_bytes_unchecked(second),
91            ))
92        }
93    }
94}
95
96mod private {
97    pub(crate) trait Sealed {}
98
99    impl Sealed for std::ffi::OsStr {}
100}
101
102#[allow(dead_code)]
103pub(crate) struct Split<'s, 'n> {
104    haystack: Option<&'s OsStr>,
105    needle: &'n str,
106}
107
108impl<'s> Iterator for Split<'s, '_> {
109    type Item = &'s OsStr;
110
111    fn next(&mut self) -> Option<Self::Item> {
112        let haystack = self.haystack?;
113        match haystack.split_once(self.needle) {
114            Some((first, second)) => {
115                if !haystack.is_empty() {
116                    debug_assert_ne!(haystack, second);
117                }
118                self.haystack = Some(second);
119                Some(first)
120            }
121            None => {
122                self.haystack = None;
123                Some(haystack)
124            }
125        }
126    }
127}
128
129/// Split an `OsStr`
130///
131/// # Safety
132///
133/// `index` must be at a valid UTF-8 boundary
134pub(crate) unsafe fn split_at(os: &OsStr, index: usize) -> (&OsStr, &OsStr) {
135    unsafe {
136        let bytes = os.as_encoded_bytes();
137        let (first, second) = bytes.split_at(index);
138        (
139            OsStr::from_encoded_bytes_unchecked(first),
140            OsStr::from_encoded_bytes_unchecked(second),
141        )
142    }
143}