1pub use af_core_macros::{path_join as join, path_normalize as normalize, path_resolve as resolve};
10
11#[doc(inline)]
12pub use std::path::{is_separator, MAIN_SEPARATOR as SEPARATOR};
13
14use crate::{env, prelude::*};
15use std::cell::RefCell;
16use std::path::Path;
17
18thread_local! {
19 static THREAD_BUFFER: RefCell<String> = default();
21}
22
23pub fn append(base: &mut String, relative: &str) {
27 if is_absolute(relative) {
28 base.replace_range(.., relative);
29 return;
30 }
31
32 match base.chars().rev().next() {
33 None => base.replace_range(.., relative),
34 Some(c) if is_separator(c) => base.push_str(relative),
35 Some(_) => {
36 base.reserve(relative.len() + 1);
37 base.push(SEPARATOR);
38 base.push_str(relative);
39 }
40 }
41}
42
43pub fn is_absolute(path: &str) -> bool {
45 as_std(path).is_absolute()
46}
47
48pub fn join<'a, 'b>(base: impl PathLike<'b>, relative: impl PathLike<'a>) -> Cow<'a, str> {
52 let relative = relative.to_cow();
53
54 if is_absolute(&relative) {
55 return relative;
56 }
57
58 let mut output = base.to_owned();
59
60 append(&mut output, &relative);
61
62 output.into()
63}
64
65pub fn last(path: &str) -> Option<&str> {
69 as_std(path).file_name()?.to_str()
70}
71
72pub fn normalize(path: &mut String) {
74 THREAD_BUFFER.with(|buffer| {
75 let mut buffer = buffer.borrow_mut();
76
77 buffer.clear();
78
79 normalize_into(path, &mut buffer);
80
81 path.replace_range(.., &buffer);
82 })
83}
84
85pub fn normalized<'a>(path: impl PathLike<'a>) -> Cow<'a, str> {
87 let path = path.to_cow();
88
89 THREAD_BUFFER.with(|buffer| {
90 let mut buffer = buffer.borrow_mut();
91
92 buffer.clear();
93 normalize_into(&path, &mut buffer);
94
95 match path.as_ref() == *buffer {
96 true => path,
97 false => buffer.clone().into(),
98 }
99 })
100}
101
102pub fn parent(path: &str) -> Option<&str> {
106 as_std(path).parent()?.to_str()
107}
108
109pub fn pop(path: &mut String) -> Option<String> {
114 let split_at = parent(&path)?.len();
115 let lead_seps = path[split_at..].chars().take_while(|c| is_separator(*c)).count();
116 let trail_seps = path.chars().rev().take_while(|c| is_separator(*c)).count();
117 let mut last = path.split_off(split_at + lead_seps);
118
119 path.truncate(split_at);
120 last.truncate(last.len() - trail_seps);
121
122 Some(last)
123}
124
125pub fn resolve(path: &mut String) -> Result<(), env::WorkingPathError> {
127 if !is_absolute(&path) {
128 let mut buf = env::working_path()?;
129
130 mem::swap(path, &mut buf);
131 append(path, &buf);
132 }
133
134 normalize(path);
135
136 Ok(())
137}
138
139pub fn resolved<'a>(path: impl PathLike<'a>) -> Result<Cow<'a, str>, env::WorkingPathError> {
141 let mut path = path.to_cow();
142
143 if is_absolute(&path) {
144 return Ok(normalized(path));
145 }
146
147 resolve(path.to_mut())?;
148
149 Ok(path)
150}
151
152pub fn starts_with(path: &str, prefix: &str) -> bool {
154 as_std(path).starts_with(prefix)
155}
156
157pub fn with_trailing_sep<'a>(path: impl PathLike<'a>) -> Cow<'a, str> {
160 let mut path = path.to_cow();
161
162 match path.chars().rev().next() {
163 Some(c) if is_separator(c) => path,
164
165 _ => {
166 path.to_mut().push(SEPARATOR);
167 path
168 }
169 }
170}
171
172pub fn as_std(path: &str) -> &Path {
174 path.as_ref()
175}
176
177fn normalize_into(path: &str, output: &mut String) {
181 for component in as_std(path).components() {
182 match component {
183 std::path::Component::CurDir => continue,
184 std::path::Component::Normal(component) => append(output, component.to_str().unwrap()),
185 std::path::Component::Prefix(prefix) => output.push_str(prefix.as_os_str().to_str().unwrap()),
186 std::path::Component::RootDir => output.push(SEPARATOR),
187
188 std::path::Component::ParentDir => {
189 pop(output);
190 }
191 }
192 }
193}
194
195pub trait PathLike<'a>: Sized {
197 fn to_cow(self) -> Cow<'a, str>;
199
200 fn to_owned(self) -> String {
202 self.to_cow().into()
203 }
204}
205
206impl<'a> PathLike<'a> for &'a str {
209 fn to_cow(self) -> Cow<'a, str> {
210 self.into()
211 }
212}
213
214impl<'a> PathLike<'a> for &'a &'_ mut str {
215 fn to_cow(self) -> Cow<'a, str> {
216 (&**self).into()
217 }
218}
219
220impl<'a> PathLike<'a> for String {
221 fn to_cow(self) -> Cow<'a, str> {
222 self.into()
223 }
224}
225
226impl<'a> PathLike<'a> for &'a String {
227 fn to_cow(self) -> Cow<'a, str> {
228 self.into()
229 }
230}
231
232impl<'a> PathLike<'a> for &'a &'_ mut String {
233 fn to_cow(self) -> Cow<'a, str> {
234 (&**self).into()
235 }
236}
237
238impl<'a> PathLike<'a> for Cow<'a, str> {
239 fn to_cow(self) -> Cow<'a, str> {
240 self
241 }
242}
243
244impl<'a> PathLike<'a> for &'a Cow<'_, str> {
245 fn to_cow(self) -> Cow<'a, str> {
246 self.as_ref().into()
247 }
248}
249
250impl<'a> PathLike<'a> for &'a &'_ mut Cow<'_, str> {
251 fn to_cow(self) -> Cow<'a, str> {
252 self.as_ref().into()
253 }
254}
255
256impl<'a> PathLike<'a> for &'a std::path::PathBuf {
257 fn to_cow(self) -> Cow<'a, str> {
258 self.to_string_lossy().to_cow()
259 }
260}