1use std::{
2 ffi::OsStr,
3 path::{Path, PathBuf},
4};
5
6pub trait IntoBoxPath {
10 fn into_box_path(self) -> Box<Path>;
11}
12
13impl IntoBoxPath for &str {
14 fn into_box_path(self) -> Box<Path> {
15 PathBuf::from(self).into()
16 }
17}
18impl IntoBoxPath for String {
19 fn into_box_path(self) -> Box<Path> {
20 PathBuf::from(self).into()
21 }
22}
23impl IntoBoxPath for PathBuf {
24 fn into_box_path(self) -> Box<Path> {
25 self.into()
26 }
27}
28impl IntoBoxPath for &PathBuf {
29 fn into_box_path(self) -> Box<Path> {
30 (**self).into()
32 }
33}
34impl IntoBoxPath for &Path {
35 fn into_box_path(self) -> Box<Path> {
36 self.into()
37 }
38}
39impl IntoBoxPath for Box<Path> {
40 fn into_box_path(self) -> Box<Path> {
41 self
42 }
43}
44impl IntoBoxPath for &Box<Path> {
45 fn into_box_path(self) -> Box<Path> {
46 self.clone()
47 }
48}
49pub fn _base_and_suffix<T: AsRef<[u8]> + ?Sized>(s: &T) -> Option<(&[u8], &str)> {
55 let bs: &[u8] = s.as_ref();
56 let len = bs.len();
57 for (i, c) in bs.iter().rev().enumerate() {
58 match c {
59 b'/' => return None,
60 b'.' => {
61 return Some((
62 &bs[..(len - i - 1)],
63 std::str::from_utf8(&bs[(len - i)..]).unwrap(),
64 ))
65 }
66 _ => {
67 if !c.is_ascii_alphanumeric() {
68 return None;
69 }
70 }
71 }
72 }
73 None
74}
75
76pub fn base_and_suffix(s: &str) -> Option<(&str, &str)> {
77 let (base, suffix) = _base_and_suffix(s)?;
78 Some((std::str::from_utf8(base).unwrap(), suffix))
79}
80
81pub fn base(s: &str) -> Option<&str> {
82 let (base, _suffix) = _base_and_suffix(s)?;
83 Some(std::str::from_utf8(base).unwrap())
84}
85
86pub fn suffix<T: AsRef<[u8]> + ?Sized>(s: &T) -> Option<&str> {
92 _base_and_suffix(s).and_then(|(_, suffix)| Some(suffix))
93}
94
95#[cfg(test)]
96mod tests {
97 use super::*;
98
99 macro_rules! t {
100 ($e:expr, $r:expr) => {
101 assert_eq!(suffix($e), $r);
102 };
103 }
104
105 #[test]
106 fn t_suffix() {
107 t!("foo", None);
108 t!("foo.md", Some("md"));
109 t!("foo.", Some("")); t!("foo. md", None);
111 t!("foo.md/bar", None);
112 t!("foo.md/", None);
113 t!("foo.mäd", None);
114 }
115 #[test]
116 fn t_base_and_suffix() {
117 assert_eq!(base_and_suffix("foo"), None);
118 assert_eq!(base_and_suffix("bar.md"), Some(("bar", "md")));
119 assert_eq!(base_and_suffix("foo.md/bar"), None);
120 assert_eq!(base_and_suffix("foo.md/bar.md"), Some(("foo.md/bar", "md")));
121 }
122}
123
124pub fn path_append<P: AsRef<Path>>(base: &Path, rel: &P) -> PathBuf {
134 let mut p = PathBuf::from(base);
135 p.push(rel);
136 p
137}
138
139pub fn path_segments<'s>(s: &'s str) -> impl Iterator<Item = &'s str> {
144 s.split('/').filter(|s| !s.is_empty())
145}
146
147pub fn path_replace_extension<P: AsRef<Path>>(
151 p: &P,
152 orig_extension: &str,
153 new_extension: &str,
154) -> Option<PathBuf> {
155 let p: &Path = p.as_ref();
156 let ext = p.extension()?;
157 if ext == orig_extension {
158 Some(p.with_extension(new_extension))
159 } else {
160 None
161 }
162}
163
164pub fn extension_eq<P: AsRef<Path> + ?Sized, E: AsRef<OsStr> + ?Sized>(path: &P, ext: &E) -> bool {
166 let p: &Path = path.as_ref();
169 let ext: &OsStr = ext.as_ref();
170 p.extension() == Some(ext)
171}
172
173#[cfg(test)]
174mod tests_extension_eq {
175 use super::*;
176
177 macro_rules! t {
178 ($e:expr, $r:expr) => {
179 let r: Option<&str> = $r;
180 if let Some(r) = r {
181 assert!(extension_eq($e, r));
182 } else {
183 assert!(!extension_eq($e, "")); }
185 };
186 }
187
188 #[test]
190 fn t_suffix() {
191 t!("foo", None);
192 t!("foo.md", Some("md"));
193 t!("foo.", Some("")); t!("foo. md", None);
195 t!("foo.md/bar", None);
196 t!("foo.md/", None);
197 t!("foo.mäd", None);
198 }
199}