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
63fn expand_path(path: impl AsRef<Path>, need_expand_tilde: bool) -> PathBuf {
64    let path = if need_expand_tilde {
65        expand_tilde(path)
66    } else {
67        PathBuf::from(path.as_ref())
68    };
69    let path = expand_ndots(path);
70    expand_dots(path)
71}
72
73/// Resolve only path components (tilde, ., .., ...+), if possible.
74///
75/// The function works in a "best effort" mode: It does not fail but rather returns the unexpanded
76/// version if the expansion is not possible.
77///
78/// Furthermore, unlike canonicalize(), it does not use sys calls (such as readlink).
79///
80/// Does not convert to absolute form nor does it resolve symlinks.
81/// The input path is specified relative to another path
82pub fn expand_path_with<P, Q>(path: P, relative_to: Q, expand_tilde: bool) -> PathBuf
83where
84    P: AsRef<Path>,
85    Q: AsRef<Path>,
86{
87    let path = join_path_relative(path, relative_to, expand_tilde);
88
89    expand_path(path, expand_tilde)
90}
91
92/// Resolve to a path that is accepted by the system and no further - tilde is expanded, and ndot path components are expanded.
93///
94/// This function will take a leading tilde path component, and expand it to the user's home directory;
95/// it will also expand any path elements consisting of only dots into the correct number of `..` path elements.
96/// It does not do any normalization except to what will be accepted by Path::open,
97/// and it does not touch the system at all, except for getting the home directory of the current user.
98pub fn expand_to_real_path<P>(path: P) -> PathBuf
99where
100    P: AsRef<Path>,
101{
102    let path = expand_tilde(path);
103    expand_ndots(path)
104}
105
106/// Attempts to canonicalize the path against the current directory. Failing that, if
107/// the path is relative, it attempts all of the dirs in `dirs`. If that fails, it returns
108/// the original error.
109pub fn locate_in_dirs<I, P>(
110    filename: impl AsRef<Path>,
111    cwd: impl AsRef<Path>,
112    dirs: impl FnOnce() -> I,
113) -> std::io::Result<PathBuf>
114where
115    I: IntoIterator<Item = P>,
116    P: AsRef<Path>,
117{
118    let filename = filename.as_ref();
119    let cwd = cwd.as_ref();
120    match canonicalize_with(filename, cwd) {
121        Ok(path) => Ok(path),
122        Err(err) => {
123            // Try to find it in `dirs` first, before giving up
124            let mut found = None;
125            for dir in dirs() {
126                if let Ok(path) =
127                    canonicalize_with(dir, cwd).and_then(|dir| canonicalize_with(filename, dir))
128                {
129                    found = Some(path);
130                    break;
131                }
132            }
133            found.ok_or(err)
134        }
135    }
136}