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!(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}