Skip to content

Commit 7077331

Browse files
committed
Add OsStr methods for stripping and splitting str prefixes.
* `OsStr::starts_with()` tests whether an `OsStr` has a prefix matching the given `Pattern`. * `OsStr::strip_prefix()` returns the `OsStr` after removing a prefix matching the given `Pattern`. * `OsStr::split_once()` splits an `OsStr` into a `(&str, &OsStr)` pair, where the delimiter matches a given `Pattern`. * `OsStr::starts_with_str()` and `OsStr::strip_prefix_str()` are specialized variants that are implemented more efficiently than the `Pattern` cases. In all cases, the prefix must be Unicode because the current `Pattern` trait is built around the `&str` type.
1 parent 0f93dae commit 7077331

File tree

7 files changed

+391
-1
lines changed

7 files changed

+391
-1
lines changed

library/std/src/ffi/os_str.rs

+143
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ use crate::fmt;
88
use crate::hash::{Hash, Hasher};
99
use crate::ops;
1010
use crate::rc::Rc;
11+
use crate::str::pattern::Pattern;
1112
use crate::str::FromStr;
1213
use crate::sync::Arc;
1314

@@ -1034,6 +1035,148 @@ impl OsStr {
10341035
pub fn eq_ignore_ascii_case<S: AsRef<OsStr>>(&self, other: S) -> bool {
10351036
self.inner.eq_ignore_ascii_case(&other.as_ref().inner)
10361037
}
1038+
1039+
/// Returns `true` if the given pattern matches a prefix of this `OsStr`.
1040+
///
1041+
/// Returns `false` if it does not.
1042+
///
1043+
/// The [pattern] can be a `&str`, [`char`], a slice of [`char`]s, or a
1044+
/// function or closure that determines if a character matches.
1045+
///
1046+
/// [`char`]: prim@char
1047+
/// [pattern]: crate::str::pattern
1048+
///
1049+
/// # Examples
1050+
///
1051+
/// Basic usage:
1052+
///
1053+
/// ```
1054+
/// #![feature(osstr_str_prefix_ops)]
1055+
///
1056+
/// use std::ffi::OsString;
1057+
///
1058+
/// let bananas = OsString::from("bananas");
1059+
///
1060+
/// assert!(bananas.starts_with("bana"));
1061+
/// assert!(!bananas.starts_with("nana"));
1062+
/// ```
1063+
#[unstable(feature = "osstr_str_prefix_ops", issue = "none")]
1064+
#[must_use]
1065+
#[inline]
1066+
pub fn starts_with<'a, P: Pattern<'a>>(&'a self, pattern: P) -> bool {
1067+
let (p, _) = self.inner.to_str_split();
1068+
p.starts_with(pattern)
1069+
}
1070+
1071+
/// Returns `true` if the given `str` matches a prefix of this `OsStr`.
1072+
///
1073+
/// Same as [`OsStr::starts_with`], but is easier to optimize to a
1074+
/// direct bitwise comparison.
1075+
///
1076+
/// # Examples
1077+
///
1078+
/// Basic usage:
1079+
///
1080+
/// ```
1081+
/// #![feature(osstr_str_prefix_ops)]
1082+
///
1083+
/// use std::ffi::OsString;
1084+
///
1085+
/// let bananas = OsString::from("bananas");
1086+
///
1087+
/// assert!(bananas.starts_with_str("bana"));
1088+
/// assert!(!bananas.starts_with_str("nana"));
1089+
/// ```
1090+
#[unstable(feature = "osstr_str_prefix_ops", issue = "none")]
1091+
#[must_use]
1092+
#[inline]
1093+
pub fn starts_with_str(&self, prefix: &str) -> bool {
1094+
self.inner.starts_with_str(prefix)
1095+
}
1096+
1097+
/// Returns this `OsStr` with the given prefix removed.
1098+
///
1099+
/// If the `OsStr` starts with the pattern `prefix`, returns the substring
1100+
/// after the prefix, wrapped in `Some`.
1101+
///
1102+
/// If the `OsStr` does not start with `prefix`, returns `None`.
1103+
///
1104+
/// The [pattern] can be a `&str`, [`char`], a slice of [`char`]s, or a
1105+
/// function or closure that determines if a character matches.
1106+
///
1107+
/// [`char`]: prim@char
1108+
/// [pattern]: crate::str::pattern
1109+
///
1110+
/// # Examples
1111+
///
1112+
/// ```
1113+
/// #![feature(osstr_str_prefix_ops)]
1114+
///
1115+
/// use std::ffi::{OsStr, OsString};
1116+
///
1117+
/// let foobar = OsString::from("foo:bar");
1118+
///
1119+
/// assert_eq!(foobar.strip_prefix("foo:"), Some(OsStr::new("bar")));
1120+
/// assert_eq!(foobar.strip_prefix("bar"), None);
1121+
/// ```
1122+
#[unstable(feature = "osstr_str_prefix_ops", issue = "none")]
1123+
#[must_use]
1124+
#[inline]
1125+
pub fn strip_prefix<'a, P: Pattern<'a>>(&'a self, prefix: P) -> Option<&'a OsStr> {
1126+
Some(OsStr::from_inner(self.inner.strip_prefix(prefix)?))
1127+
}
1128+
1129+
/// Returns this `OsStr` with the given prefix removed.
1130+
///
1131+
/// Same as [`OsStr::strip_prefix`], but is easier to optimize to a
1132+
/// direct bitwise comparison.
1133+
///
1134+
/// # Examples
1135+
///
1136+
/// ```
1137+
/// #![feature(osstr_str_prefix_ops)]
1138+
///
1139+
/// use std::ffi::{OsStr, OsString};
1140+
///
1141+
/// let foobar = OsString::from("foo:bar");
1142+
///
1143+
/// assert_eq!(foobar.strip_prefix("foo:"), Some(OsStr::new("bar")));
1144+
/// assert_eq!(foobar.strip_prefix_str("bar"), None);
1145+
/// ```
1146+
#[unstable(feature = "osstr_str_prefix_ops", issue = "none")]
1147+
#[must_use]
1148+
#[inline]
1149+
pub fn strip_prefix_str(&self, prefix: &str) -> Option<&OsStr> {
1150+
Some(OsStr::from_inner(self.inner.strip_prefix_str(prefix)?))
1151+
}
1152+
1153+
/// Splits this `OsStr` on the first occurrence of the specified delimiter,
1154+
/// returning the prefix before delimiter and suffix after delimiter.
1155+
///
1156+
/// The prefix is returned as a `str`, because a successful `Pattern` match
1157+
/// implies its matching prefix was valid Unicode.
1158+
///
1159+
/// # Examples
1160+
///
1161+
/// ```
1162+
/// #![feature(osstr_str_prefix_ops)]
1163+
///
1164+
/// use std::ffi::{OsStr, OsString};
1165+
///
1166+
/// let foo = OsString::from("foo:");
1167+
/// let foobar = OsString::from("foo:bar");
1168+
///
1169+
/// assert_eq!(foo.split_once(':'), Some(("foo", OsStr::new(""))));
1170+
/// assert_eq!(foobar.split_once(':'), Some(("foo", OsStr::new("bar"))));
1171+
/// assert_eq!(foobar.split_once('='), None);
1172+
/// ```
1173+
#[unstable(feature = "osstr_str_prefix_ops", issue = "none")]
1174+
#[must_use]
1175+
#[inline]
1176+
pub fn split_once<'a, P: Pattern<'a>>(&'a self, delimiter: P) -> Option<(&'a str, &'a OsStr)> {
1177+
let (before, after) = self.inner.split_once(delimiter)?;
1178+
Some((before, OsStr::from_inner(after)))
1179+
}
10371180
}
10381181

10391182
#[stable(feature = "box_from_os_str", since = "1.17.0")]

library/std/src/lib.rs

+1
Original file line numberDiff line numberDiff line change
@@ -264,6 +264,7 @@
264264
#![feature(needs_panic_runtime)]
265265
#![feature(negative_impls)]
266266
#![feature(never_type)]
267+
#![feature(pattern)]
267268
#![feature(platform_intrinsics)]
268269
#![feature(prelude_import)]
269270
#![feature(rustc_attrs)]

library/std/src/sys/unix/os_str.rs

+48
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ use crate::fmt::Write;
88
use crate::mem;
99
use crate::rc::Rc;
1010
use crate::str;
11+
use crate::str::pattern::{Pattern, SearchStep, Searcher};
1112
use crate::sync::Arc;
1213
use crate::sys_common::{AsInner, IntoInner};
1314

@@ -305,4 +306,51 @@ impl Slice {
305306
pub fn eq_ignore_ascii_case(&self, other: &Self) -> bool {
306307
self.inner.eq_ignore_ascii_case(&other.inner)
307308
}
309+
310+
#[inline]
311+
pub fn starts_with_str(&self, prefix: &str) -> bool {
312+
self.inner.starts_with(prefix.as_bytes())
313+
}
314+
315+
pub fn strip_prefix<'a, P: Pattern<'a>>(&'a self, prefix: P) -> Option<&'a Slice> {
316+
let (p, _) = self.to_str_split();
317+
let prefix_len = match prefix.into_searcher(p).next() {
318+
SearchStep::Match(0, prefix_len) => prefix_len,
319+
_ => return None,
320+
};
321+
322+
// SAFETY: `p` is guaranteed to be a prefix of `self.inner`,
323+
// and `Searcher` is known to return valid indices.
324+
unsafe {
325+
let suffix = self.inner.get_unchecked(prefix_len..);
326+
Some(Slice::from_u8_slice(suffix))
327+
}
328+
}
329+
330+
#[inline]
331+
pub fn strip_prefix_str(&self, prefix: &str) -> Option<&Slice> {
332+
if !self.starts_with_str(prefix) {
333+
return None;
334+
}
335+
336+
// SAFETY: `prefix` is a prefix of `self.inner`.
337+
unsafe {
338+
let suffix = self.inner.get_unchecked(prefix.len()..);
339+
Some(Slice::from_u8_slice(suffix))
340+
}
341+
}
342+
343+
pub fn split_once<'a, P: Pattern<'a>>(&'a self, delimiter: P) -> Option<(&'a str, &'a Slice)> {
344+
let (p, _) = self.to_str_split();
345+
let (start, end) = delimiter.into_searcher(p).next_match()?;
346+
347+
// SAFETY: `p` is guaranteed to be a prefix of `self.inner`,
348+
// and `Searcher` is known to return valid indices.
349+
unsafe {
350+
let before = p.get_unchecked(..start);
351+
let after = self.inner.get_unchecked(end..);
352+
353+
Some((before, Slice::from_u8_slice(after)))
354+
}
355+
}
308356
}

library/std/src/sys/unix/os_str/tests.rs

+61
Original file line numberDiff line numberDiff line change
@@ -50,3 +50,64 @@ fn slice_to_str_split() {
5050
assert_eq!(&suffix.inner, b"\xFF");
5151
}
5252
}
53+
54+
#[test]
55+
fn slice_starts_with_str() {
56+
let mut string = Buf::from_string(String::from("héllô="));
57+
string.push_slice(Slice::from_u8_slice(b"\xFF"));
58+
string.push_slice(Slice::from_str("wørld"));
59+
let slice = string.as_slice();
60+
61+
assert!(slice.starts_with_str("héllô"));
62+
assert!(!slice.starts_with_str("héllô=wørld"));
63+
}
64+
65+
#[test]
66+
fn slice_strip_prefix() {
67+
let mut string = Buf::from_string(String::from("héllô="));
68+
string.push_slice(Slice::from_u8_slice(b"\xFF"));
69+
string.push_slice(Slice::from_str("wørld"));
70+
let slice = string.as_slice();
71+
72+
assert!(slice.strip_prefix("héllô=wørld").is_none());
73+
74+
{
75+
let suffix = slice.strip_prefix('h');
76+
assert!(suffix.is_some());
77+
assert_eq!(&suffix.unwrap().inner, b"\xC3\xA9ll\xC3\xB4=\xFFw\xC3\xB8rld",);
78+
}
79+
80+
{
81+
let suffix = slice.strip_prefix("héllô");
82+
assert!(suffix.is_some());
83+
assert_eq!(&suffix.unwrap().inner, b"=\xFFw\xC3\xB8rld");
84+
}
85+
}
86+
87+
#[test]
88+
fn slice_strip_prefix_str() {
89+
let mut string = Buf::from_string(String::from("héllô="));
90+
string.push_slice(Slice::from_u8_slice(b"\xFF"));
91+
string.push_slice(Slice::from_str("wørld"));
92+
let slice = string.as_slice();
93+
94+
assert!(slice.strip_prefix_str("héllô=wørld").is_none());
95+
96+
let suffix = slice.strip_prefix_str("héllô");
97+
assert!(suffix.is_some());
98+
assert_eq!(&suffix.unwrap().inner, b"=\xFFw\xC3\xB8rld");
99+
}
100+
101+
#[test]
102+
fn slice_split_once() {
103+
let mut string = Buf::from_string(String::from("héllô="));
104+
string.push_slice(Slice::from_u8_slice(b"\xFF"));
105+
string.push_slice(Slice::from_str("wørld"));
106+
let slice = string.as_slice();
107+
108+
let split = slice.split_once('=');
109+
assert!(split.is_some());
110+
let (prefix, suffix) = split.unwrap();
111+
assert_eq!(prefix, "héllô");
112+
assert_eq!(&suffix.inner, b"\xFFw\xC3\xB8rld");
113+
}

library/std/src/sys/windows/os_str.rs

+30-1
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ use crate::collections::TryReserveError;
55
use crate::fmt;
66
use crate::mem;
77
use crate::rc::Rc;
8+
use crate::str::pattern::Pattern;
89
use crate::sync::Arc;
910
use crate::sys_common::wtf8::{Wtf8, Wtf8Buf};
1011
use crate::sys_common::{AsInner, FromInner, IntoInner};
@@ -160,13 +161,20 @@ impl Slice {
160161
unsafe { mem::transmute(Wtf8::from_str(s)) }
161162
}
162163

164+
#[inline]
165+
fn from_inner(inner: &Wtf8) -> &Slice {
166+
// SAFETY: Slice is just a wrapper of Wtf8,
167+
// therefore converting &Wtf8 to &Slice is safe.
168+
unsafe { &*(inner as *const Wtf8 as *const Slice) }
169+
}
170+
163171
pub fn to_str(&self) -> Option<&str> {
164172
self.inner.as_str()
165173
}
166174

167175
pub fn to_str_split(&self) -> (&str, &Slice) {
168176
let (prefix, suffix) = self.inner.to_str_split();
169-
(prefix, Slice { inner: suffix })
177+
(prefix, Slice::from_inner(suffix))
170178
}
171179

172180
pub fn to_string_lossy(&self) -> Cow<'_, str> {
@@ -231,4 +239,25 @@ impl Slice {
231239
pub fn eq_ignore_ascii_case(&self, other: &Self) -> bool {
232240
self.inner.eq_ignore_ascii_case(&other.inner)
233241
}
242+
243+
#[inline]
244+
pub fn starts_with_str(&self, prefix: &str) -> bool {
245+
self.inner.starts_with_str(prefix)
246+
}
247+
248+
#[inline]
249+
pub fn strip_prefix<'a, P: Pattern<'a>>(&'a self, prefix: P) -> Option<&'a Slice> {
250+
Some(Slice::from_inner(self.inner.strip_prefix(prefix)?))
251+
}
252+
253+
#[inline]
254+
pub fn strip_prefix_str(&self, prefix: &str) -> Option<&Slice> {
255+
Some(Slice::from_inner(self.inner.strip_prefix_str(prefix)?))
256+
}
257+
258+
#[inline]
259+
pub fn split_once<'a, P: Pattern<'a>>(&'a self, delimiter: P) -> Option<(&'a str, &'a Slice)> {
260+
let (before, after) = self.inner.split_once(delimiter)?;
261+
Some((before, Slice::from_inner(after)))
262+
}
234263
}

0 commit comments

Comments
 (0)