nu_path/
expansions.rs

1#[cfg(windows)]
2use omnipath::WinPathExt;
3use std::io;
4use std::path::{Path, PathBuf};
5
6use super::dots::{expand_dots, expand_ndots};
7use super::tilde::expand_tilde;
8
9// Join a path relative to another path. Paths starting with tilde are considered as absolute.
10fn join_path_relative<P, Q>(path: P, relative_to: Q, expand_tilde: bool) -> PathBuf
11where
12    P: AsRef<Path>,
13    Q: AsRef<Path>,
14{
15    let path = path.as_ref();
16    let relative_to = relative_to.as_ref();
17
18    if path == Path::new(".") {
19        // Joining a Path with '.' appends a '.' at the end, making the prompt
20        // more ugly - so we don't do anything, which should result in an equal
21        // path on all supported systems.
22        relative_to.into()
23    } else if path.to_string_lossy().as_ref().starts_with('~') && expand_tilde {
24        // do not end up with "/some/path/~" or "/some/path/~user"
25        path.into()
26    } else {
27        relative_to.join(path)
28    }
29}
30
31fn canonicalize(path: impl AsRef<Path>) -> io::Result<PathBuf> {
32    let path = expand_tilde(path);
33    let path = expand_ndots(path);
34    canonicalize_path(&path)
35}
36
37#[cfg(windows)]
38fn canonicalize_path(path: &std::path::Path) -> std::io::Result<std::path::PathBuf> {
39    path.canonicalize()?.to_winuser_path()
40}
41
42#[cfg(not(windows))]
43fn canonicalize_path(path: &std::path::Path) -> std::io::Result<std::path::PathBuf> {
44    path.canonicalize()
45}
46
47/// Resolve all symbolic links and all components (tilde, ., .., ...+) and return the path in its
48/// absolute form.
49///
50/// Fails under the same conditions as
51/// [`std::fs::canonicalize`](https://doc.rust-lang.org/std/fs/fn.canonicalize.html).
52/// The input path is specified relative to another path
53pub fn canonicalize_with<P, Q>(path: P, relative_to: Q) -> io::Result<PathBuf>
54where
55    P: AsRef<Path>,
56    Q: AsRef<Path>,
57{
58    let path = join_path_relative(path, relative_to, true);
59
60    canonicalize(path)
61}
62
63/// Resolve only path components (tilde, ., .., ...+), if possible.
64///
65/// Doesn't convert to absolute form or use syscalls. Output may begin with "../"
66pub fn expand_path(path: impl AsRef<Path>, need_expand_tilde: bool) -> PathBuf {
67    let path = if need_expand_tilde {
68        expand_tilde(path)
69    } else {
70        PathBuf::from(path.as_ref())
71    };
72    let path = expand_ndots(path);
73    expand_dots(path)
74}
75
76/// Resolve only path components (tilde, ., .., ...+), if possible.
77///
78/// The function works in a "best effort" mode: It does not fail but rather returns the unexpanded
79/// version if the expansion is not possible.
80///
81/// Furthermore, unlike canonicalize(), it does not use sys calls (such as readlink).
82///
83/// Converts to absolute form but does not resolve symlinks.
84/// The input path is specified relative to another path
85pub fn expand_path_with<P, Q>(path: P, relative_to: Q, expand_tilde: bool) -> PathBuf
86where
87    P: AsRef<Path>,
88    Q: AsRef<Path>,
89{
90    let path = join_path_relative(path, relative_to, expand_tilde);
91
92    expand_path(path, expand_tilde)
93}
94
95/// Resolve to a path that is accepted by the system and no further - tilde is expanded, and ndot path components are expanded.
96///
97/// This function will take a leading tilde path component, and expand it to the user's home directory;
98/// it will also expand any path elements consisting of only dots into the correct number of `..` path elements.
99/// It does not do any normalization except to what will be accepted by Path::open,
100/// and it does not touch the system at all, except for getting the home directory of the current user.
101pub fn expand_to_real_path<P>(path: P) -> PathBuf
102where
103    P: AsRef<Path>,
104{
105    let path = expand_tilde(path);
106    expand_ndots(path)
107}
108
109/// Attempts to canonicalize the path against the current directory. Failing that, if
110/// the path is relative, it attempts all of the dirs in `dirs`. If that fails, it returns
111/// the original error.
112pub fn locate_in_dirs<I, P>(
113    filename: impl AsRef<Path>,
114    cwd: impl AsRef<Path>,
115    dirs: impl FnOnce() -> I,
116) -> std::io::Result<PathBuf>
117where
118    I: IntoIterator<Item = P>,
119    P: AsRef<Path>,
120{
121    let filename = filename.as_ref();
122    let cwd = cwd.as_ref();
123    match canonicalize_with(filename, cwd) {
124        Ok(path) => Ok(path),
125        Err(err) => {
126            // Try to find it in `dirs` first, before giving up
127            let mut found = None;
128            for dir in dirs() {
129                if let Ok(path) =
130                    canonicalize_with(dir, cwd).and_then(|dir| canonicalize_with(filename, dir))
131                {
132                    found = Some(path);
133                    break;
134                }
135            }
136            found.ok_or(err)
137        }
138    }
139}