Skip to main content

path_dedot/
unix.rs

1use std::{
2    borrow::Cow,
3    ffi::{OsStr, OsString},
4    io,
5    path::{Component, Path, PathBuf, MAIN_SEPARATOR_STR},
6};
7
8use crate::ParseDot;
9
10impl ParseDot for Path {
11    #[inline]
12    fn parse_dot(&self) -> io::Result<Cow<'_, Path>> {
13        match self.components().next() {
14            Some(Component::CurDir | Component::ParentDir) => {
15                let cwd = get_cwd!();
16
17                self.parse_dot_from(cwd)
18            },
19            // the cwd is only used when the path starts with a single dot or double dots, so an empty path is enough here
20            _ => self.parse_dot_from(Path::new("")),
21        }
22    }
23
24    fn parse_dot_from(&self, cwd: impl AsRef<Path>) -> io::Result<Cow<'_, Path>> {
25        let main_separator = OsStr::new(MAIN_SEPARATOR_STR);
26
27        let mut iter = self.components();
28
29        let mut has_dots = false;
30
31        if let Some(first_component) = iter.next() {
32            let mut tokens = Vec::new();
33
34            let first_is_root = match first_component {
35                Component::RootDir => {
36                    tokens.push(main_separator);
37
38                    true
39                },
40                Component::CurDir => {
41                    has_dots = true;
42
43                    let cwd = cwd.as_ref();
44
45                    for token in cwd.iter() {
46                        tokens.push(token);
47                    }
48
49                    !tokens.is_empty() && tokens[0] == main_separator
50                },
51                Component::ParentDir => {
52                    has_dots = true;
53
54                    let cwd = cwd.as_ref();
55
56                    match cwd.parent() {
57                        Some(cwd_parent) => {
58                            for token in cwd_parent.iter() {
59                                tokens.push(token);
60                            }
61
62                            !tokens.is_empty() && tokens[0] == main_separator
63                        },
64                        None => {
65                            // don't care about `cwd` is "//" or "///"
66                            if cwd == main_separator {
67                                tokens.push(main_separator);
68
69                                true
70                            } else {
71                                false
72                            }
73                        },
74                    }
75                },
76                _ => {
77                    tokens.push(first_component.as_os_str());
78
79                    false
80                },
81            };
82
83            for component in iter {
84                match component {
85                    Component::CurDir => {
86                        // may be unreachable
87                        has_dots = true;
88                    },
89                    Component::ParentDir => {
90                        let tokens_length = tokens.len();
91
92                        if tokens_length > 0 && (tokens_length != 1 || !first_is_root) {
93                            tokens.pop();
94                        }
95
96                        has_dots = true;
97                    },
98                    _ => {
99                        tokens.push(component.as_os_str());
100                    },
101                }
102            }
103
104            // all tokens can be removed by double dots (e.g. `a/../..`), and in this case the result is an empty path
105            if tokens.is_empty() {
106                return Ok(Cow::from(PathBuf::new()));
107            }
108
109            let tokens_length = tokens.len();
110
111            let mut size = tokens.iter().fold(tokens_length - 1, |acc, &x| acc + x.len());
112
113            if first_is_root && tokens_length > 1 {
114                size -= 1;
115            }
116
117            if has_dots || size != self.as_os_str().len() {
118                let mut path_string = OsString::with_capacity(size);
119
120                let mut iter = tokens.iter();
121
122                path_string.push(iter.next().unwrap());
123
124                if tokens_length > 1 {
125                    if !first_is_root {
126                        path_string.push(main_separator);
127                    }
128
129                    for token in iter.take(tokens_length - 2) {
130                        path_string.push(token);
131
132                        path_string.push(main_separator);
133                    }
134
135                    path_string.push(tokens[tokens_length - 1]);
136                }
137
138                let path_buf = PathBuf::from(path_string);
139
140                Ok(Cow::from(path_buf))
141            } else {
142                Ok(Cow::from(self))
143            }
144        } else {
145            Ok(Cow::from(self))
146        }
147    }
148}