1717
1818//! Path abstraction for Object Storage
1919
20- use itertools:: Itertools ;
2120use percent_encoding:: percent_decode;
2221use 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
3029pub 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+
3234mod 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
159161impl 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+
346391impl < ' a , I > FromIterator < I > for Path
347392where
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
0 commit comments