1use std::{
4 ffi::{OsStr, OsString},
5 path::{Path, PathBuf},
6};
7
8pub trait PathExt {
10 fn extensions(&'_ self) -> PathExtensions<'_>;
38}
39
40impl PathExt for Path {
41 fn extensions(&'_ self) -> PathExtensions<'_> {
42 PathExtensions(self)
43 }
44}
45
46pub trait PathBufExt {
48 fn add_extension<S: AsRef<OsStr>>(&mut self, extension: S) -> bool;
54}
55
56impl PathBufExt for PathBuf {
57 fn add_extension<S: AsRef<OsStr>>(&mut self, extension: S) -> bool {
58 if self.file_name().is_none() {
59 return false;
60 }
61
62 let mut stem = match self.file_name() {
63 Some(stem) => stem.to_os_string(),
64 None => OsString::new(),
65 };
66
67 if !extension.as_ref().is_empty() {
68 stem.push(".");
69 stem.push(extension.as_ref());
70 }
71 self.set_file_name(&stem);
72
73 true
74 }
75}
76
77#[derive(Copy, Clone, Debug)]
105pub struct PathExtensions<'a>(&'a Path);
106
107impl<'a> Iterator for PathExtensions<'a> {
108 type Item = &'a OsStr;
109
110 fn next(&mut self) -> Option<&'a OsStr> {
111 let (new_filestem, new_extension) = (self.0.file_stem(), self.0.extension());
112 if new_extension.is_none() {
113 self.0 = Path::new("");
114 None
115 } else {
116 if let Some(new_filestem) = new_filestem {
117 self.0 = Path::new(new_filestem);
118 } else {
119 self.0 = Path::new("");
120 };
121 new_extension
122 }
123 }
124}
125
126#[test]
127fn test_path_extensions() {
128 let p = &Path::new("/home/user/projects/misc_utils/Cargo.toml");
129 assert_eq!(p.extensions().collect::<Vec<_>>(), vec![OsStr::new("toml")]);
130 let p = &Path::new("/home/user/projects/misc_utils/This.File.has.many.extensions");
131 assert_eq!(
132 p.extensions().collect::<Vec<_>>(),
133 vec![
134 OsStr::new("extensions"),
135 OsStr::new("many"),
136 OsStr::new("has"),
137 OsStr::new("File")
138 ]
139 );
140 let p = &Path::new("/home/user/projects/misc_utils/.hidden");
141 assert_eq!(p.extensions().collect::<Vec<_>>(), Vec::<&OsStr>::new());
142 let p = &Path::new("Just-A.file");
143 assert_eq!(p.extensions().collect::<Vec<_>>(), vec![OsStr::new("file")]);
144}
145
146#[test]
147fn test_pathbuf_extensions() {
148 let p = PathBuf::from("/home/user/projects/misc_utils/Cargo.toml");
149 assert_eq!(p.extensions().collect::<Vec<_>>(), vec![OsStr::new("toml")]);
150 let p = PathBuf::from("/home/user/projects/misc_utils/This.File.has.many.extensions");
151 assert_eq!(
152 p.extensions().collect::<Vec<_>>(),
153 vec![
154 OsStr::new("extensions"),
155 OsStr::new("many"),
156 OsStr::new("has"),
157 OsStr::new("File")
158 ]
159 );
160 let p = PathBuf::from("/home/user/projects/misc_utils/.hidden");
161 assert_eq!(p.extensions().collect::<Vec<_>>(), Vec::<&OsStr>::new());
162 let p = PathBuf::from("Just-A.file");
163 assert_eq!(p.extensions().collect::<Vec<_>>(), vec![OsStr::new("file")]);
164}
165
166#[test]
167fn test_add_extension() {
168 let mut pb = PathBuf::from("some.file");
169 assert_eq!(pb, Path::new("some.file"));
170 assert!(PathBufExt::add_extension(&mut pb, "a"));
171 assert_eq!(pb, Path::new("some.file.a"));
172 assert!(PathBufExt::add_extension(&mut pb, "b"));
173 assert_eq!(pb, Path::new("some.file.a.b"));
174 assert!(PathBufExt::add_extension(&mut pb, "c"));
175 assert_eq!(pb, Path::new("some.file.a.b.c"));
176
177 let mut pb = PathBuf::from("/");
178 assert!(!PathBufExt::add_extension(&mut pb, "ext"));
179}