Skip to main content

apimock_config/
path_util.rs

1use std::{
2    env, io,
3    path::{Path, PathBuf},
4};
5
6#[cfg(test)]
7mod tests;
8
9/// Relative path from the current working directory to the parent
10/// directory of the given file.
11///
12/// # Why a file whose parent we can't determine is an `io::Error`
13///
14/// `Path::parent()` returns `None` for root-only paths (`"/"`) and empty
15/// paths. Neither is a valid config file location, so treating them as an
16/// I/O error keeps the caller's `?` chain clean instead of forcing them
17/// to handle an `Option` separately.
18pub fn current_dir_to_file_parent_dir_relative_path(file_path: &str) -> io::Result<PathBuf> {
19    let parent = Path::new(file_path).parent().ok_or_else(|| {
20        io::Error::new(
21            io::ErrorKind::InvalidInput,
22            format!("failed to get parent dir: {}", file_path),
23        )
24    })?;
25    relative_path(env::current_dir()?.as_path(), parent)
26}
27
28/// relative path between two paths
29pub fn relative_path(from: &Path, to: &Path) -> io::Result<PathBuf> {
30    let from_abs = std::fs::canonicalize(from)?;
31    let to_abs = std::fs::canonicalize(to)?;
32
33    let mut from_iter = from_abs.components();
34    let mut to_iter = to_abs.components();
35
36    let mut from_rest = vec![];
37    let mut to_rest = vec![];
38    // collect common prefix
39    let mut common_prefix = vec![];
40    loop {
41        match (from_iter.next(), to_iter.next()) {
42            (Some(f), Some(t)) if f == t => {
43                common_prefix.push(f);
44            }
45            (Some(f), Some(t)) => {
46                from_rest.push(f);
47                to_rest.push(t);
48                from_rest.extend(from_iter);
49                to_rest.extend(to_iter);
50                break;
51            }
52            (Some(f), None) => {
53                from_rest.push(f);
54                from_rest.extend(from_iter);
55                break;
56            }
57            (None, Some(t)) => {
58                to_rest.push(t);
59                to_rest.extend(to_iter);
60                break;
61            }
62            (None, None) => break,
63        }
64    }
65
66    let mut result = PathBuf::new();
67
68    for _ in from_rest {
69        result.push("..");
70    }
71
72    for t in to_rest {
73        result.push(t.as_os_str());
74    }
75
76    if result.as_os_str().is_empty() {
77        Ok(PathBuf::from("."))
78    } else {
79        Ok(result)
80    }
81}