nu_path/
expansions.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
#[cfg(windows)]
use omnipath::WinPathExt;
use std::io;
use std::path::{Path, PathBuf};

use super::dots::{expand_dots, expand_ndots};
use super::tilde::expand_tilde;

// Join a path relative to another path. Paths starting with tilde are considered as absolute.
fn join_path_relative<P, Q>(path: P, relative_to: Q, expand_tilde: bool) -> PathBuf
where
    P: AsRef<Path>,
    Q: AsRef<Path>,
{
    let path = path.as_ref();
    let relative_to = relative_to.as_ref();

    if path == Path::new(".") {
        // Joining a Path with '.' appends a '.' at the end, making the prompt
        // more ugly - so we don't do anything, which should result in an equal
        // path on all supported systems.
        relative_to.into()
    } else if path.to_string_lossy().as_ref().starts_with('~') && expand_tilde {
        // do not end up with "/some/path/~" or "/some/path/~user"
        path.into()
    } else {
        relative_to.join(path)
    }
}

fn canonicalize(path: impl AsRef<Path>) -> io::Result<PathBuf> {
    let path = expand_tilde(path);
    let path = expand_ndots(path);
    canonicalize_path(&path)
}

#[cfg(windows)]
fn canonicalize_path(path: &std::path::Path) -> std::io::Result<std::path::PathBuf> {
    path.canonicalize()?.to_winuser_path()
}

#[cfg(not(windows))]
fn canonicalize_path(path: &std::path::Path) -> std::io::Result<std::path::PathBuf> {
    path.canonicalize()
}

/// Resolve all symbolic links and all components (tilde, ., .., ...+) and return the path in its
/// absolute form.
///
/// Fails under the same conditions as
/// [`std::fs::canonicalize`](https://doc.rust-lang.org/std/fs/fn.canonicalize.html).
/// The input path is specified relative to another path
pub fn canonicalize_with<P, Q>(path: P, relative_to: Q) -> io::Result<PathBuf>
where
    P: AsRef<Path>,
    Q: AsRef<Path>,
{
    let path = join_path_relative(path, relative_to, true);

    canonicalize(path)
}

fn expand_path(path: impl AsRef<Path>, need_expand_tilde: bool) -> PathBuf {
    let path = if need_expand_tilde {
        expand_tilde(path)
    } else {
        PathBuf::from(path.as_ref())
    };
    let path = expand_ndots(path);
    expand_dots(path)
}

/// Resolve only path components (tilde, ., .., ...+), if possible.
///
/// The function works in a "best effort" mode: It does not fail but rather returns the unexpanded
/// version if the expansion is not possible.
///
/// Furthermore, unlike canonicalize(), it does not use sys calls (such as readlink).
///
/// Does not convert to absolute form nor does it resolve symlinks.
/// The input path is specified relative to another path
pub fn expand_path_with<P, Q>(path: P, relative_to: Q, expand_tilde: bool) -> PathBuf
where
    P: AsRef<Path>,
    Q: AsRef<Path>,
{
    let path = join_path_relative(path, relative_to, expand_tilde);

    expand_path(path, expand_tilde)
}

/// Resolve to a path that is accepted by the system and no further - tilde is expanded, and ndot path components are expanded.
///
/// This function will take a leading tilde path component, and expand it to the user's home directory;
/// it will also expand any path elements consisting of only dots into the correct number of `..` path elements.
/// It does not do any normalization except to what will be accepted by Path::open,
/// and it does not touch the system at all, except for getting the home directory of the current user.
pub fn expand_to_real_path<P>(path: P) -> PathBuf
where
    P: AsRef<Path>,
{
    let path = expand_tilde(path);
    expand_ndots(path)
}

/// Attempts to canonicalize the path against the current directory. Failing that, if
/// the path is relative, it attempts all of the dirs in `dirs`. If that fails, it returns
/// the original error.
pub fn locate_in_dirs<I, P>(
    filename: impl AsRef<Path>,
    cwd: impl AsRef<Path>,
    dirs: impl FnOnce() -> I,
) -> std::io::Result<PathBuf>
where
    I: IntoIterator<Item = P>,
    P: AsRef<Path>,
{
    let filename = filename.as_ref();
    let cwd = cwd.as_ref();
    match canonicalize_with(filename, cwd) {
        Ok(path) => Ok(path),
        Err(err) => {
            // Try to find it in `dirs` first, before giving up
            let mut found = None;
            for dir in dirs() {
                if let Ok(path) =
                    canonicalize_with(dir, cwd).and_then(|dir| canonicalize_with(filename, dir))
                {
                    found = Some(path);
                    break;
                }
            }
            found.ok_or(err)
        }
    }
}