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
use std::path::PathBuf;

/// the error returned by [`realpath()`][super::realpath()].
#[derive(Debug, thiserror::Error)]
pub enum Error {
    #[error("The maximum allowed number {} of symlinks in path is exceeded", .max_symlinks)]
    MaxSymlinksExceeded { max_symlinks: u8 },
    #[error(transparent)]
    ReadLink(#[from] std::io::Error),
    #[error("Empty is not a valid path")]
    EmptyPath,
    #[error("Parent component of {} does not exist, {}", .path.display(), .msg)]
    MissingParent { path: PathBuf, msg: &'static str },
}

pub(crate) mod function {
    use std::path::{
        Component::{CurDir, Normal, ParentDir, Prefix, RootDir},
        Path, PathBuf,
    };

    use super::Error;

    // TODO
    #[allow(missing_docs)]
    pub fn realpath(path: impl AsRef<Path>, cwd: impl AsRef<Path>) -> Result<PathBuf, Error> {
        let git_default = 32;
        realpath_opts(path, cwd, git_default)
    }

    // TODO
    #[allow(missing_docs)]
    pub fn realpath_opts(path: impl AsRef<Path>, cwd: impl AsRef<Path>, max_symlinks: u8) -> Result<PathBuf, Error> {
        let path = path.as_ref();
        if path.as_os_str().is_empty() {
            return Err(Error::EmptyPath);
        }

        let mut real_path = PathBuf::new();
        if path.is_relative() {
            real_path.push(cwd);
        }

        let mut num_symlinks = 0;
        let mut path_backing: PathBuf;
        let mut components = path.components();
        while let Some(component) = components.next() {
            match component {
                part @ RootDir | part @ Prefix(_) => real_path.push(part),
                CurDir => {}
                ParentDir => {
                    if !real_path.pop() {
                        return Err(Error::MissingParent {
                            path: real_path,
                            msg: "parent path must exist",
                        });
                    }
                }
                Normal(part) => {
                    real_path.push(part);
                    if real_path.is_symlink() {
                        num_symlinks += 1;
                        if num_symlinks > max_symlinks {
                            return Err(Error::MaxSymlinksExceeded { max_symlinks });
                        }
                        let mut link_destination = std::fs::read_link(real_path.as_path())?;
                        if link_destination.is_absolute() {
                            // pushing absolute path to real_path resets it to the pushed absolute path
                            // real_path.clear();
                        } else if !real_path.pop() {
                            return Err(Error::MissingParent {
                                path: real_path,
                                msg: "we just pushed a component",
                            });
                        }
                        link_destination.extend(components);
                        path_backing = link_destination;
                        components = path_backing.components();
                    }
                }
            }
        }
        Ok(real_path)
    }
}