nu_path/
expansions.rs

1#[cfg(windows)]
2use omnipath::WinPathExt;
3use std::io;
4use std::path::{Path, PathBuf, absolute};
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/// Consider using [`absolute_with`] if you don't need to resolve symlinks.
51///
52/// Fails under the same conditions as
53/// [`std::fs::canonicalize`](https://doc.rust-lang.org/std/fs/fn.canonicalize.html).
54/// The input path is specified relative to another path
55pub fn canonicalize_with<P, Q>(path: P, relative_to: Q) -> io::Result<PathBuf>
56where
57    P: AsRef<Path>,
58    Q: AsRef<Path>,
59{
60    let path = join_path_relative(path, relative_to, true);
61
62    canonicalize(path)
63}
64
65/// Returns the path in its absolute form without resolving symlinks.
66///
67/// On Unix systems, .. components are a kind of link so these aren't resolved.
68/// On Windows .. components are resoloved.
69///
70/// Fails under the same conditions as
71/// [`std::path::absolute`](https://doc.rust-lang.org/std/path/fn.absolute.html).
72/// In practice this is only likely to fail if both paths are empty.
73pub fn absolute_with<P, Q>(path: P, relative_to: Q) -> io::Result<PathBuf>
74where
75    P: AsRef<Path>,
76    Q: AsRef<Path>,
77{
78    let path = join_path_relative(path, relative_to, true);
79    let path = expand_tilde(path);
80    let path = expand_ndots(path);
81    absolute(path)
82}
83
84/// Resolve only path components (tilde, ., .., ...+), if possible.
85///
86/// Doesn't convert to absolute form or use syscalls. Output may begin with "../"
87pub fn expand_path(path: impl AsRef<Path>, need_expand_tilde: bool) -> PathBuf {
88    let path = if need_expand_tilde {
89        expand_tilde(path)
90    } else {
91        PathBuf::from(path.as_ref())
92    };
93    let path = expand_ndots(path);
94    expand_dots(path)
95}
96
97/// Resolve only path components (tilde, ., .., ...+), if possible.
98///
99/// The function works in a "best effort" mode: It does not fail but rather returns the unexpanded
100/// version if the expansion is not possible.
101///
102/// Furthermore, unlike canonicalize(), it does not use sys calls (such as readlink).
103///
104/// Converts to absolute form but does not resolve symlinks.
105/// The input path is specified relative to another path
106pub fn expand_path_with<P, Q>(path: P, relative_to: Q, expand_tilde: bool) -> PathBuf
107where
108    P: AsRef<Path>,
109    Q: AsRef<Path>,
110{
111    let path = join_path_relative(path, relative_to, expand_tilde);
112
113    expand_path(path, expand_tilde)
114}
115
116/// Resolve to a path that is accepted by the system and no further - tilde is expanded, and ndot path components are expanded.
117///
118/// This function will take a leading tilde path component, and expand it to the user's home directory;
119/// it will also expand any path elements consisting of only dots into the correct number of `..` path elements.
120/// It does not do any normalization except to what will be accepted by Path::open,
121/// and it does not touch the system at all, except for getting the home directory of the current user.
122pub fn expand_to_real_path<P>(path: P) -> PathBuf
123where
124    P: AsRef<Path>,
125{
126    let path = expand_tilde(path);
127    expand_ndots(path)
128}
129
130/// Attempts to canonicalize the path against the current directory. Failing that, if
131/// the path is relative, it attempts all of the dirs in `dirs`. If that fails, it returns
132/// the original error.
133pub fn locate_in_dirs<I, P>(
134    filename: impl AsRef<Path>,
135    cwd: impl AsRef<Path>,
136    dirs: impl FnOnce() -> I,
137) -> std::io::Result<PathBuf>
138where
139    I: IntoIterator<Item = P>,
140    P: AsRef<Path>,
141{
142    let filename = filename.as_ref();
143    let cwd = cwd.as_ref();
144    match canonicalize_with(filename, cwd) {
145        Ok(path) => Ok(path),
146        Err(err) => {
147            // Try to find it in `dirs` first, before giving up
148            let mut found = None;
149            for dir in dirs() {
150                if let Ok(path) =
151                    canonicalize_with(dir, cwd).and_then(|dir| canonicalize_with(filename, dir))
152                {
153                    found = Some(path);
154                    break;
155                }
156            }
157            found.ok_or(err)
158        }
159    }
160}