vite_rust/utils/
resolve_path.rs

1use std::env::current_dir;
2use std::path::{Component, Path, PathBuf};
3
4/// Experimental utility for resolving the manifest path.
5///
6/// # Arguments
7/// * `file`    - the current script file path (obtained with `file!()` macro);
8/// * `path`    - the "path/to/manifest.json" string slice.
9///
10/// # Panics
11/// This function might panic in various occasions:
12/// - if `file` has no parent directory (e.g. "src/main.rs" parent would be "src");
13/// - if it fails to find the `file` first segment somehow (e.g. "path/to/file" => "path");
14/// - if the first file path [`Component`] is neither `RootDir` nor `Normal`;
15/// - if final result fails to be canonicalized.
16///
17/// The last situation might occur if the final path doesn't really lead to
18/// any existing file/directory. Or perhaps, because this function has a bug!
19/// In this case, please open an issue at our GitHub repository!
20///
21/// # Example
22/// ```plaintext
23/// example/
24/// |-- .dist/
25/// |   |-- .vite/
26/// |   |   |-- manifest.json
27/// |-- src/
28/// |   |-- main.rs
29/// ```
30/// ```ignore
31///
32/// // example/src/main.rs
33/// let manifest_path = resolve_path(file!(), "../dist/.vite/manifest.json");
34/// let mut vite_config = ViteConfig::new_with_defaults(&manifest_path);
35///
36/// println!("{manifest_path}");
37/// // C:/totally/absolute/path/to/example/.dist/.vite/manifest.json
38/// ```
39///
40/// [`Component`]: std::path::Component
41pub fn resolve_path(file: &str, path: &str) -> String {
42    let path: &Path = std::path::Path::new(path);
43    let mut this_file_directory = Path::new(file)
44        .parent()
45        .expect("Could not get current file's directory.")
46        .to_path_buf();
47
48    #[allow(unused_mut)]
49    let mut fp_fs: String; // file path first segment
50    match this_file_directory.components().next() {
51        Some(Component::Normal(segment)) => fp_fs = segment.to_string_lossy().to_string(),
52        Some(Component::RootDir) => {
53            let component = this_file_directory.components().next().unwrap_or_else(|| {
54                panic!(
55                    "Failed to find first directory segment from path {}.",
56                    this_file_directory.to_string_lossy()
57                )
58            });
59
60            match component {
61                Component::Normal(segment) => fp_fs = segment.to_string_lossy().to_string(),
62                _ => {
63                    panic!(
64                        "Failed to find first directory normal segment from path {}.",
65                        this_file_directory.to_string_lossy()
66                    )
67                }
68            }
69        }
70        _ => panic!("Unexpected kind of directory."),
71    }
72
73    let curr_dir = current_dir().unwrap();
74
75    let paths_are_redundant = curr_dir.ends_with(&fp_fs) && this_file_directory.starts_with(&fp_fs);
76
77    // remove the first segment from this_file_directory so that it won't get doubled
78    // on canonicalization
79    if paths_are_redundant {
80        let mut new_path = PathBuf::new();
81        let mut components = this_file_directory.components();
82
83        if components.next().is_some() {
84            components.for_each(|component| new_path.push(component));
85        }
86
87        this_file_directory = new_path;
88    }
89
90    let joined_path = this_file_directory.join(path);
91    let canonicalized = joined_path.canonicalize();
92    match canonicalized {
93        Err(err) => panic!("{}\n{}\n", err, joined_path.to_string_lossy()),
94        Ok(path) => path.to_string_lossy().to_string(),
95    }
96}
97
98#[cfg(test)]
99mod test {
100    use std::io::Read;
101
102    #[test]
103    fn test_resolve_path() {
104        let abs_path = "tests/dummy.txt";
105        let rel_path = "../../tests/dummy.txt";
106        let resolved_rel_path = super::resolve_path(file!(), rel_path);
107
108        let mut abs_file_contents = String::new();
109        let mut rel_file_contents = String::new();
110
111        let _ = std::fs::File::open(abs_path)
112            .unwrap()
113            .read_to_string(&mut abs_file_contents);
114        let _ = std::fs::File::open(resolved_rel_path)
115            .unwrap()
116            .read_to_string(&mut rel_file_contents);
117
118        assert_eq!(abs_file_contents, rel_file_contents);
119    }
120}