path_utils/
lib.rs

1//! Utility library for [`std::path::Path`] and [`std::path::PathBuf`].
2//!
3//! ```
4//! use std::path::Path;
5//! use path_utils::PathExt;
6//!
7//! let path = Path::new("file.tar.gz");
8//! let extensions = path.extensions_lossy().collect::<Vec<String>>();
9//! assert_eq!(extensions, ["gz", "tar"]);
10//! ```
11
12#![deny(clippy::all, missing_docs)]
13#![warn(clippy::pedantic, clippy::nursery, clippy::cargo)]
14
15use std::path::{Path, PathBuf};
16
17/// [`Path`] and [`PathBuf`] extensions.
18pub trait PathExt {
19    /// Returns an iterator over the extensions of a path, lossily converted.
20    ///
21    /// ```
22    /// use std::path::Path;
23    /// use path_utils::PathExt;
24    ///
25    /// let path = Path::new("file.tar.gz");
26    /// let extensions = path.extensions_lossy().collect::<Vec<String>>();
27    /// assert_eq!(extensions, ["gz", "tar"]);
28    fn extensions_lossy(&self) -> ExtensionsLossy;
29}
30
31impl<T> PathExt for T
32where
33    T: AsRef<Path>,
34{
35    fn extensions_lossy(&self) -> ExtensionsLossy {
36        ExtensionsLossy {
37            path: self.as_ref().to_path_buf(),
38        }
39    }
40}
41
42/// An iterator over the [`Path::extension`]s of a path, lossily converted.
43#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)]
44#[must_use = "iterators are lazy and do nothing unless consumed"]
45pub struct ExtensionsLossy {
46    path: PathBuf,
47}
48
49impl Iterator for ExtensionsLossy {
50    type Item = String;
51    fn next(&mut self) -> Option<Self::Item> {
52        let s = self
53            .path
54            .extension()
55            .map(|s| String::from(s.to_string_lossy()));
56        self.path.set_extension("");
57        s
58    }
59}
60
61// ----------------------------------------------------------------------------
62// tests
63// ----------------------------------------------------------------------------
64
65#[cfg(test)]
66mod tests {
67    use super::*;
68
69    fn exts_lossy(p: impl AsRef<Path>) -> Vec<String> {
70        p.as_ref().extensions_lossy().collect()
71    }
72
73    #[test]
74    fn extensions_lossy() {
75        assert!(Path::new("/path/to/file")
76            .extensions_lossy()
77            .next()
78            .is_none());
79
80        assert!(PathBuf::from("/path/to/.file")
81            .extensions_lossy()
82            .next()
83            .is_none());
84
85        assert_eq!(exts_lossy("/path/to/file.tar"), ["tar"]);
86        assert_eq!(exts_lossy("/path/to/.file.tar"), ["tar"]);
87        assert_eq!(exts_lossy("/path/to/file.tar.gz"), ["gz", "tar"]);
88    }
89}