vite_rust/utils/resolve_path.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
use std::path::{Path, PathBuf, Component,};
use std::env::current_dir;
/// Experimental utility for resolving the manifest path.
///
/// # Arguments
/// * `file` - the current script file path (obtained with `file!()` macro);
/// * `path` - the "path/to/manifest.json" string slice.
///
/// # Panics
/// This function might panic in various occasions:
/// - if `file` has no parent directory (e.g. "src/main.rs" parent would be "src");
/// - if it fails to find the `file` first segment somehow (e.g. "path/to/file" => "path");
/// - if the first file path [`Component`] is neither `RootDir` nor `Normal`;
/// - if final result fails to be canonicalized.
///
/// The last situation might occur if the final path doesn't really lead to
/// any existing file/directory. Or perhaps, because this function has a bug!
/// In this case, please open an issue at our GitHub repository!
///
/// # Example
/// ```plaintext
/// example/
/// |-- .dist/
/// | |-- .vite/
/// | | |-- manifest.json
/// |-- src/
/// | |-- main.rs
/// ```
/// ```ignore
///
/// // example/src/main.rs
/// let manifest_path = resolve_path(file!(), "../dist/.vite/manifest.json");
/// let mut vite_config = ViteConfig::new_with_defaults(&manifest_path);
///
/// println!("{manifest_path}");
/// // C:/totally/absolute/path/to/example/.dist/.vite/manifest.json
/// ```
///
/// [`Component`]: std::path::Component
pub fn resolve_path(file: &str, path: &str) -> String {
let path: &Path = std::path::Path::new(path);
let mut this_file_directory = Path::new(file)
.parent()
.expect("Could not get current file's directory.")
.to_path_buf();
#[allow(unused_mut)]
let mut fp_fs: String; // file path first segment
match this_file_directory.components().next() {
Some(Component::Normal(segment)) => fp_fs = segment.to_string_lossy().to_string(),
Some(Component::RootDir) => {
let component = this_file_directory
.components()
.next()
.expect(&format!(
"Failed to find first directory segment from path {}.",
this_file_directory.to_string_lossy())
);
match component {
Component::Normal(segment) => fp_fs = segment.to_string_lossy().to_string(),
_ => {
panic!(
"Failed to find first directory normal segment from path {}.",
this_file_directory.to_string_lossy()
)
}
}
},
_ => panic!("Unexpected kind of directory."),
}
let curr_dir = current_dir().unwrap();
let paths_are_redundant = curr_dir.ends_with(&fp_fs) &&
this_file_directory.starts_with(&fp_fs);
// remove the first segment from this_file_directory so that it won't get doubled
// on canonicalization
if paths_are_redundant {
let mut new_path = PathBuf::new();
let mut components = this_file_directory.components();
if let Some(_) = components.next() {
components.for_each(|component| new_path.push(component));
}
this_file_directory = new_path;
}
let joined_path = this_file_directory.join(path);
let canonicalized = joined_path.canonicalize();
match canonicalized {
Err(err) => panic!("{}\n{}\n", err, joined_path.to_string_lossy()),
Ok(path) => path.to_string_lossy().to_string()
}
}
#[cfg(test)]
mod test {
use std::io::Read;
#[test]
fn test_resolve_path() {
let abs_path = "tests/dummy.txt";
let rel_path = "../../tests/dummy.txt";
let resolved_rel_path = super::resolve_path(file!(), &rel_path);
let mut abs_file_contents = String::new();
let mut rel_file_contents = String::new();
let _ = std::fs::File::open(abs_path).unwrap().read_to_string(&mut abs_file_contents);
let _ = std::fs::File::open(resolved_rel_path).unwrap().read_to_string(&mut rel_file_contents);
assert_eq!(abs_file_contents, rel_file_contents);
}
}