Skip to content

Commit 44f4967

Browse files
committed
crate::path::Path improvements
1 parent fa48f9a commit 44f4967

File tree

2 files changed

+95
-15
lines changed

2 files changed

+95
-15
lines changed

src/path/mod.rs

Lines changed: 55 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@
1717

1818
//! Path abstraction for Object Storage
1919
20-
use itertools::Itertools;
2120
use percent_encoding::percent_decode;
2221
use std::fmt::Formatter;
2322
#[cfg(not(target_arch = "wasm32"))]
@@ -29,9 +28,12 @@ pub const DELIMITER: &str = "/";
2928
/// The path delimiter as a single byte
3029
pub const DELIMITER_BYTE: u8 = DELIMITER.as_bytes()[0];
3130

31+
/// The path delimiter as a single char
32+
pub const DELIMITER_CHAR: char = DELIMITER_BYTE as char;
33+
3234
mod parts;
3335

34-
pub use parts::{InvalidPart, PathPart};
36+
pub use parts::{InvalidPart, PathPart, PathParts};
3537

3638
/// Error returned by [`Path::parse`]
3739
#[derive(Debug, thiserror::Error)]
@@ -157,6 +159,11 @@ pub struct Path {
157159
}
158160

159161
impl Path {
162+
/// Create an empty [`Path`], equivalent to `Path::from("/")`.
163+
pub const fn empty() -> Self {
164+
Self { raw: String::new() }
165+
}
166+
160167
/// Parse a string as a [`Path`], returning a [`Error`] if invalid,
161168
/// as defined on the docstring for [`Path`]
162169
///
@@ -255,14 +262,39 @@ impl Path {
255262
Self::parse(decoded)
256263
}
257264

258-
/// Returns the [`PathPart`] of this [`Path`]
259-
pub fn parts(&self) -> impl Iterator<Item = PathPart<'_>> {
260-
self.raw
261-
.split_terminator(DELIMITER)
262-
.map(|s| PathPart { raw: s.into() })
265+
/// Returns the number of [`PathPart`]s in this [`Path`]
266+
///
267+
/// # Performance
268+
///
269+
/// This operation is `O(n)`, similar to calling `.parts().count()` manually.
270+
pub fn len(&self) -> usize {
271+
self.raw.split_terminator(DELIMITER).count()
272+
}
273+
274+
/// True if this [`Path`] has zero segments, equivalent to `Path::from("/")`
275+
pub fn is_empty(&self) -> bool {
276+
self.raw.is_empty()
277+
}
278+
279+
/// Returns the [`PathPart`]s of this [`Path`]
280+
pub fn parts(&self) -> PathParts<'_> {
281+
PathParts::new(self)
282+
}
283+
284+
/// Returns a copy of this [`Path`] with the last path segment removed
285+
///
286+
/// Returns `None` if this path has zero segments.
287+
pub fn prefix(&self) -> Option<Self> {
288+
let prefix = self.raw.rsplit_once(DELIMITER)?.1;
289+
290+
Some(Self {
291+
raw: prefix.to_string(),
292+
})
263293
}
264294

265295
/// Returns the last path segment containing the filename stored in this [`Path`]
296+
///
297+
/// Returns `None` only if this path has zero segments.
266298
pub fn filename(&self) -> Option<&str> {
267299
match self.raw.is_empty() {
268300
true => None,
@@ -343,18 +375,27 @@ impl std::fmt::Display for Path {
343375
}
344376
}
345377

378+
impl<'a, I: Into<PathPart<'a>>> Extend<I> for Path {
379+
fn extend<T: IntoIterator<Item = I>>(&mut self, iter: T) {
380+
for s in iter.into_iter() {
381+
let s = s.into();
382+
if s.raw.is_empty() {
383+
continue;
384+
}
385+
self.raw.push_str(DELIMITER);
386+
self.raw.push_str(&s.raw);
387+
}
388+
}
389+
}
390+
346391
impl<'a, I> FromIterator<I> for Path
347392
where
348393
I: Into<PathPart<'a>>,
349394
{
350395
fn from_iter<T: IntoIterator<Item = I>>(iter: T) -> Self {
351-
let raw = T::into_iter(iter)
352-
.map(|s| s.into())
353-
.filter(|s| !s.raw.is_empty())
354-
.map(|s| s.raw)
355-
.join(DELIMITER);
356-
357-
Self { raw }
396+
let mut this = Self::empty();
397+
this.extend(iter);
398+
this
358399
}
359400
}
360401

src/path/parts.rs

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,11 @@
1616
// under the License.
1717

1818
use percent_encoding::{AsciiSet, CONTROLS, percent_encode};
19-
use std::borrow::Cow;
19+
use std::{
20+
borrow::Cow,
21+
iter::{self, FusedIterator},
22+
str::SplitTerminator,
23+
};
2024

2125
use crate::path::DELIMITER_BYTE;
2226

@@ -131,6 +135,41 @@ impl AsRef<str> for PathPart<'_> {
131135
}
132136
}
133137

138+
/// See [`Path::parts`](super::Path::parts)
139+
#[derive(Debug, Clone)]
140+
pub struct PathParts<'a>(iter::Map<SplitTerminator<'a, char>, fn(&str) -> PathPart<'_>>);
141+
142+
impl<'a> PathParts<'a> {
143+
/// Create an iterator over the parts of the provided [`Path`]
144+
pub fn new(path: &'a super::Path) -> Self {
145+
Self(
146+
path.raw
147+
.split_terminator(super::DELIMITER_CHAR)
148+
.map(path_part_from_raw),
149+
)
150+
}
151+
}
152+
153+
fn path_part_from_raw(s: &str) -> PathPart<'_> {
154+
PathPart { raw: s.into() }
155+
}
156+
157+
impl<'a> Iterator for PathParts<'a> {
158+
type Item = PathPart<'a>;
159+
160+
fn next(&mut self) -> Option<Self::Item> {
161+
self.0.next()
162+
}
163+
}
164+
165+
impl<'a> FusedIterator for PathParts<'a> {}
166+
167+
impl<'a> DoubleEndedIterator for PathParts<'a> {
168+
fn next_back(&mut self) -> Option<Self::Item> {
169+
self.0.next_back()
170+
}
171+
}
172+
134173
#[cfg(test)]
135174
mod tests {
136175
use super::*;

0 commit comments

Comments
 (0)