Skip to content

Commit 525fc99

Browse files
committed
Add OsStr inherent fns to test for and strip 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`.
1 parent 8cc75b5 commit 525fc99

File tree

7 files changed

+223
-0
lines changed

7 files changed

+223
-0
lines changed

library/std/src/ffi/os_str.rs

+64
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

@@ -978,6 +979,69 @@ impl OsStr {
978979
pub fn eq_ignore_ascii_case<S: AsRef<OsStr>>(&self, other: S) -> bool {
979980
self.inner.eq_ignore_ascii_case(&other.as_ref().inner)
980981
}
982+
983+
/// Returns `true` if the given pattern matches a prefix of this `OsStr`.
984+
///
985+
/// Returns `false` if it does not.
986+
///
987+
/// The [pattern] can be a `&str`, [`char`], a slice of [`char`]s, or a
988+
/// function or closure that determines if a character matches.
989+
///
990+
/// [`char`]: prim@char
991+
/// [pattern]: crate::str::pattern
992+
///
993+
/// # Examples
994+
///
995+
/// Basic usage:
996+
///
997+
/// ```
998+
/// #![feature(osstr_str_prefix_fns)]
999+
///
1000+
/// use std::ffi::OsString;
1001+
///
1002+
/// let bananas = OsString::from("bananas");
1003+
///
1004+
/// assert!(bananas.starts_with("bana"));
1005+
/// assert!(!bananas.starts_with("nana"));
1006+
/// ```
1007+
#[unstable(feature = "osstr_str_prefix_fns", issue = "none")]
1008+
#[must_use]
1009+
#[inline]
1010+
pub fn starts_with<'a, P: Pattern<'a>>(&'a self, pattern: P) -> bool {
1011+
self.inner.starts_with(pattern)
1012+
}
1013+
1014+
/// Returns this `OsStr` with the given prefix removed.
1015+
///
1016+
/// If the `OsStr` starts with the pattern `prefix`, returns the substring
1017+
/// after the prefix, wrapped in `Some`.
1018+
///
1019+
/// If the `OsStr` does not start with `prefix`, returns `None`.
1020+
///
1021+
/// The [pattern] can be a `&str`, [`char`], a slice of [`char`]s, or a
1022+
/// function or closure that determines if a character matches.
1023+
///
1024+
/// [`char`]: prim@char
1025+
/// [pattern]: crate::str::pattern
1026+
///
1027+
/// # Examples
1028+
///
1029+
/// ```
1030+
/// #![feature(osstr_str_prefix_fns)]
1031+
///
1032+
/// use std::ffi::{OsStr, OsString};
1033+
///
1034+
/// let foobar = OsString::from("foo:bar");
1035+
///
1036+
/// assert_eq!(foobar.strip_prefix("foo:"), Some(OsStr::new("bar")));
1037+
/// assert_eq!(foobar.strip_prefix("bar"), None);
1038+
/// ```
1039+
#[unstable(feature = "osstr_str_prefix_fns", issue = "none")]
1040+
#[must_use]
1041+
#[inline]
1042+
pub fn strip_prefix<'a, P: Pattern<'a>>(&'a self, prefix: P) -> Option<&'a OsStr> {
1043+
Some(OsStr::from_inner(self.inner.strip_prefix(prefix)?))
1044+
}
9811045
}
9821046

9831047
#[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

+35
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

@@ -270,4 +271,38 @@ impl Slice {
270271
pub fn eq_ignore_ascii_case(&self, other: &Self) -> bool {
271272
self.inner.eq_ignore_ascii_case(&other.inner)
272273
}
274+
275+
fn to_str_prefix(&self) -> &str {
276+
let utf8_err = match str::from_utf8(&self.inner) {
277+
Ok(prefix) => return prefix,
278+
Err(err) => err,
279+
};
280+
let utf8_len = utf8_err.valid_up_to();
281+
if utf8_len == 0 {
282+
return "";
283+
}
284+
// SAFETY: `Utf8Error::valid_up_to()` returns an index up to which
285+
// valid UTF-8 has been verified.
286+
unsafe { str::from_utf8_unchecked(&self.inner[..utf8_len]) }
287+
}
288+
289+
#[inline]
290+
pub fn starts_with<'a, P: Pattern<'a>>(&'a self, pattern: P) -> bool {
291+
self.to_str_prefix().starts_with(pattern)
292+
}
293+
294+
pub fn strip_prefix<'a, P: Pattern<'a>>(&'a self, prefix: P) -> Option<&'a Slice> {
295+
let p = self.to_str_prefix();
296+
let prefix_len = match prefix.into_searcher(p).next() {
297+
SearchStep::Match(0, prefix_len) => prefix_len,
298+
_ => return None,
299+
};
300+
301+
// SAFETY: `p` is guaranteed to be a prefix of `self.inner`,
302+
// and `Searcher` is known to return valid indices.
303+
unsafe {
304+
let suffix = self.inner.get_unchecked(prefix_len..);
305+
Some(Slice::from_u8_slice(suffix))
306+
}
307+
}
273308
}

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

+34
Original file line numberDiff line numberDiff line change
@@ -16,3 +16,37 @@ fn display() {
1616
Slice::from_u8_slice(b"Hello\xC0\x80 There\xE6\x83 Goodbye").to_string(),
1717
);
1818
}
19+
20+
#[test]
21+
fn slice_starts_with() {
22+
let mut string = Buf::from_string(String::from("héllô="));
23+
string.push_slice(Slice::from_u8_slice(b"\xFF"));
24+
string.push_slice(Slice::from_str("wørld"));
25+
let slice = string.as_slice();
26+
27+
assert!(slice.starts_with('h'));
28+
assert!(slice.starts_with("héllô"));
29+
assert!(!slice.starts_with("héllô=wørld"));
30+
}
31+
32+
#[test]
33+
fn slice_strip_prefix() {
34+
let mut string = Buf::from_string(String::from("héllô="));
35+
string.push_slice(Slice::from_u8_slice(b"\xFF"));
36+
string.push_slice(Slice::from_str("wørld"));
37+
let slice = string.as_slice();
38+
39+
assert!(slice.strip_prefix("héllô=wørld").is_none());
40+
41+
{
42+
let suffix = slice.strip_prefix('h');
43+
assert!(suffix.is_some());
44+
assert_eq!(&suffix.unwrap().inner, b"\xC3\xA9ll\xC3\xB4=\xFFw\xC3\xB8rld",);
45+
}
46+
47+
{
48+
let suffix = slice.strip_prefix("héllô");
49+
assert!(suffix.is_some());
50+
assert_eq!(&suffix.unwrap().inner, b"=\xFFw\xC3\xB8rld");
51+
}
52+
}

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

+18
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};
@@ -156,6 +157,13 @@ impl Slice {
156157
unsafe { mem::transmute(Wtf8::from_str(s)) }
157158
}
158159

160+
#[inline]
161+
fn from_inner(inner: &Wtf8) -> &Slice {
162+
// SAFETY: Slice is just a wrapper of Wtf8,
163+
// therefore converting &Wtf8 to &Slice is safe.
164+
unsafe { &*(inner as *const Wtf8 as *const Slice) }
165+
}
166+
159167
pub fn to_str(&self) -> Option<&str> {
160168
self.inner.as_str()
161169
}
@@ -222,4 +230,14 @@ impl Slice {
222230
pub fn eq_ignore_ascii_case(&self, other: &Self) -> bool {
223231
self.inner.eq_ignore_ascii_case(&other.inner)
224232
}
233+
234+
#[inline]
235+
pub fn starts_with<'a, P: Pattern<'a>>(&'a self, pattern: P) -> bool {
236+
self.inner.starts_with(pattern)
237+
}
238+
239+
#[inline]
240+
pub fn strip_prefix<'a, P: Pattern<'a>>(&'a self, prefix: P) -> Option<&'a Slice> {
241+
Some(Slice::from_inner(self.inner.strip_prefix(prefix)?))
242+
}
225243
}

library/std/src/sys_common/wtf8.rs

+37
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ use crate::ops;
3131
use crate::rc::Rc;
3232
use crate::slice;
3333
use crate::str;
34+
use crate::str::pattern::{Pattern, SearchStep, Searcher};
3435
use crate::sync::Arc;
3536
use crate::sys_common::AsInner;
3637

@@ -781,6 +782,42 @@ impl Wtf8 {
781782
pub fn eq_ignore_ascii_case(&self, other: &Self) -> bool {
782783
self.bytes.eq_ignore_ascii_case(&other.bytes)
783784
}
785+
786+
fn to_str_prefix(&self) -> &str {
787+
let utf8_bytes = match self.next_surrogate(0) {
788+
None => &self.bytes,
789+
Some((0, _)) => b"",
790+
Some((surrogate_pos, _)) => {
791+
let (utf8_bytes, _) = self.bytes.split_at(surrogate_pos);
792+
utf8_bytes
793+
}
794+
};
795+
796+
// SAFETY: `utf8_bytes` is a prefix of a WTF-8 value that contains no
797+
// surrogates, and well-formed WTF-8 that contains no surrogates is
798+
// also well-formed UTF-8.
799+
unsafe { str::from_utf8_unchecked(utf8_bytes) }
800+
}
801+
802+
#[inline]
803+
pub fn starts_with<'a, P: Pattern<'a>>(&'a self, pattern: P) -> bool {
804+
self.to_str_prefix().starts_with(pattern)
805+
}
806+
807+
pub fn strip_prefix<'a, P: Pattern<'a>>(&'a self, prefix: P) -> Option<&'a Wtf8> {
808+
let p = self.to_str_prefix();
809+
let prefix_len = match prefix.into_searcher(p).next() {
810+
SearchStep::Match(0, prefix_len) => prefix_len,
811+
_ => return None,
812+
};
813+
814+
// SAFETY: `p` is guaranteed to be a prefix of `self.bytes`,
815+
// and `Searcher` is known to return valid indices.
816+
unsafe {
817+
let suffix = self.bytes.get_unchecked(prefix_len..);
818+
Some(Wtf8::from_bytes_unchecked(suffix))
819+
}
820+
}
784821
}
785822

786823
/// Returns a slice of the given string for the byte range \[`begin`..`end`).

library/std/src/sys_common/wtf8/tests.rs

+34
Original file line numberDiff line numberDiff line change
@@ -664,3 +664,37 @@ fn wtf8_to_owned() {
664664
assert_eq!(string.bytes, b"\xED\xA0\x80");
665665
assert!(!string.is_known_utf8);
666666
}
667+
668+
#[test]
669+
fn wtf8_starts_with() {
670+
let mut string = Wtf8Buf::from_str("héllô=");
671+
string.push(CodePoint::from_u32(0xD800).unwrap());
672+
string.push_str("wørld");
673+
let slice = string.as_slice();
674+
675+
assert!(slice.starts_with('h'));
676+
assert!(slice.starts_with("héllô"));
677+
assert!(!slice.starts_with("héllô=wørld"));
678+
}
679+
680+
#[test]
681+
fn wtf8_strip_prefix() {
682+
let mut string = Wtf8Buf::from_str("héllô=");
683+
string.push(CodePoint::from_u32(0xD800).unwrap());
684+
string.push_str("wørld");
685+
let slice = string.as_slice();
686+
687+
assert!(slice.strip_prefix("héllô=wørld").is_none());
688+
689+
{
690+
let suffix = slice.strip_prefix('h');
691+
assert!(suffix.is_some());
692+
assert_eq!(&suffix.unwrap().bytes, b"\xC3\xA9ll\xC3\xB4=\xED\xA0\x80w\xC3\xB8rld",);
693+
}
694+
695+
{
696+
let suffix = slice.strip_prefix("héllô");
697+
assert!(suffix.is_some());
698+
assert_eq!(&suffix.unwrap().bytes, b"=\xED\xA0\x80w\xC3\xB8rld");
699+
}
700+
}

0 commit comments

Comments
 (0)