1#![deny(clippy::all, missing_docs)]
13#![warn(clippy::pedantic, clippy::nursery, clippy::cargo)]
14
15use std::path::{Path, PathBuf};
16
17pub trait PathExt {
19 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#[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#[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!(
76 Path::new("/path/to/file")
77 .extensions_lossy()
78 .next()
79 .is_none()
80 );
81
82 assert!(
83 PathBuf::from("/path/to/.file")
84 .extensions_lossy()
85 .next()
86 .is_none()
87 );
88
89 assert_eq!(exts_lossy("/path/to/file.tar"), ["tar"]);
90 assert_eq!(exts_lossy("/path/to/.file.tar"), ["tar"]);
91 assert_eq!(exts_lossy("/path/to/file.tar.gz"), ["gz", "tar"]);
92 }
93}