1use std::{
6 env::{current_dir, current_exe},
7 ffi::OsStr,
8 path::{Component, MAIN_SEPARATOR, Path, PathBuf},
9 sync::{Arc, LazyLock},
10};
11
12use path_clean::PathClean;
13
14static CWD: LazyLock<Arc<Path>> = LazyLock::new(create_cwd);
15static EXE: LazyLock<Arc<Path>> = LazyLock::new(create_exe);
16
17fn create_cwd() -> Arc<Path> {
18 let mut cwd = current_dir()
19 .expect("failed to find current working directory")
20 .to_str()
21 .expect("current working directory is not valid UTF-8")
22 .to_string();
23 if !cwd.ends_with(MAIN_SEPARATOR) {
24 cwd.push(MAIN_SEPARATOR);
25 }
26 dunce::canonicalize(cwd)
27 .expect("failed to canonicalize current working directory")
28 .into()
29}
30
31fn create_exe() -> Arc<Path> {
32 let exe = current_exe()
33 .expect("failed to find current executable")
34 .to_str()
35 .expect("current executable path is not valid UTF-8")
36 .to_string();
37 dunce::canonicalize(exe)
38 .expect("failed to canonicalize current executable path")
39 .into()
40}
41
42#[must_use]
54pub fn get_current_dir() -> Arc<Path> {
55 Arc::clone(&CWD)
56}
57
58#[must_use]
69pub fn get_current_exe() -> Arc<Path> {
70 Arc::clone(&EXE)
71}
72
73#[must_use]
79pub fn clean_path(path: impl AsRef<Path>) -> PathBuf {
80 path.as_ref().clean()
81}
82
83#[must_use]
91pub fn clean_path_and_make_absolute(path: impl AsRef<Path>) -> PathBuf {
92 let path = path.as_ref();
93 if path.is_relative() {
94 CWD.join(path).clean()
95 } else {
96 path.clean()
97 }
98}
99
100#[must_use]
106pub fn append_extension(path: impl AsRef<Path>, ext: impl AsRef<OsStr>) -> PathBuf {
107 let path = path.as_ref();
108 match path.extension() {
109 None => path.with_extension(ext),
110 Some(curr_ext) => {
111 let mut new_ext = curr_ext.to_os_string();
112 new_ext.push(".");
113 new_ext.push(ext);
114 path.with_extension(new_ext)
115 }
116 }
117}
118
119#[must_use]
126pub fn relative_path_normalize(path: impl AsRef<Path>) -> PathBuf {
127 let path = clean_path(path);
128
129 let mut it = path.components().peekable();
130 if it.peek().is_none_or(|c| matches!(c, Component::Normal(..))) {
131 std::iter::once(Component::CurDir).chain(it).collect()
132 } else {
133 path
134 }
135}
136
137pub fn relative_path_parent(rel: &mut PathBuf) {
145 if rel.as_os_str() == Component::CurDir.as_os_str() {
146 *rel = PathBuf::from(Component::ParentDir.as_os_str());
147 } else if rel.components().all(|c| c == Component::ParentDir) {
148 rel.push(Component::ParentDir.as_os_str());
149 } else {
150 rel.pop();
151 }
152}