use anyhow::{Context, Result};
use std::path::{Path, PathBuf};
pub(crate) fn path_to_string(path: &Path) -> Result<String> {
Ok(path
.to_str()
.context("cannot convert path to str")?
.to_owned())
}
pub(crate) fn find_parent_dir_with_file(
path_start: &Path,
probe_file_name: &str,
) -> Option<PathBuf> {
let mut path = path_start.to_owned();
loop {
if path.join(probe_file_name).is_file() {
return Some(path);
}
if !path.pop() {
break;
}
}
None
}
pub(crate) fn find_dart_package_dir(dart_file_path: &Path) -> Result<PathBuf> {
find_parent_dir_with_file(dart_file_path, "pubspec.yaml").with_context(|| {
format!("Fail to detect dart package from dart_file_path={dart_file_path:?}")
})
}
pub(crate) fn normalize_windows_unc_path(path: &str) -> &str {
path.strip_prefix(r"\\?\").unwrap_or(path)
}
pub(crate) fn canonicalize_with_error_message(path: &Path) -> Result<PathBuf> {
path.canonicalize()
.with_context(|| format!("Fail to canonicalize path={path:?}"))
}
#[cfg(test)]
mod tests {
use crate::utils::path_utils::{
canonicalize_with_error_message, find_dart_package_dir, find_parent_dir_with_file,
normalize_windows_unc_path, path_to_string,
};
use anyhow::Result;
use std::fs;
use std::path::PathBuf;
use tempfile::tempdir;
#[test]
fn test_find_parent_dir_with_file_failure_case() {
assert_eq!(
find_parent_dir_with_file(&PathBuf::new(), "whatever_not_exist_file"),
None
);
}
#[allow(clippy::join_absolute_paths)]
#[test]
fn test_glob_path_simple() -> Result<()> {
let temp_dir = tempdir()?;
fs::write(temp_dir.path().join("apple.rs"), "")?;
fs::write(temp_dir.path().join("orange.rs"), "")?;
fs::write(temp_dir.path().join("aha.rs"), "")?;
assert_eq!(
PathBuf::from("/a/b").join("*.rs"),
PathBuf::from("/a/b/*.rs")
);
assert_eq!(
PathBuf::from("/a/b").join("c/*.rs"),
PathBuf::from("/a/b/c/*.rs")
);
assert_eq!(
PathBuf::from("/a/b").join("/c/*.rs"),
PathBuf::from("/c/*.rs")
);
Ok(())
}
#[test]
fn test_find_parent_dir_with_file_returns_nearest_matching_ancestor() -> Result<()> {
let temp_dir = tempdir()?;
let package_dir = temp_dir.path().join("package");
let nested_dir = package_dir.join("lib/src");
fs::create_dir_all(&nested_dir)?;
fs::write(package_dir.join("pubspec.yaml"), "name: package")?;
fs::write(temp_dir.path().join("pubspec.yaml"), "name: outer")?;
assert_eq!(
find_parent_dir_with_file(&nested_dir, "pubspec.yaml"),
Some(package_dir),
);
Ok(())
}
#[test]
fn test_find_dart_package_dir_finds_immediate_and_nested_pubspec() -> Result<()> {
let temp_dir = tempdir()?;
let package_dir = temp_dir.path().join("package");
let nested_file = package_dir.join("lib/src/api.dart");
fs::create_dir_all(nested_file.parent().unwrap())?;
fs::write(package_dir.join("pubspec.yaml"), "name: package")?;
assert_eq!(
find_dart_package_dir(&package_dir.join("main.dart"))?,
package_dir
);
assert_eq!(find_dart_package_dir(&nested_file)?, package_dir);
Ok(())
}
#[test]
fn test_find_dart_package_dir_returns_contextual_error_when_absent() -> Result<()> {
let temp_dir = tempdir()?;
let error = find_dart_package_dir(&temp_dir.path().join("lib/api.dart")).unwrap_err();
assert!(error.to_string().contains("Fail to detect dart package"));
Ok(())
}
#[test]
fn test_path_to_string_and_normalize_windows_unc_path() -> Result<()> {
assert_eq!(path_to_string(PathBuf::from("a/é").as_path())?, "a/é");
assert_eq!(normalize_windows_unc_path(r"\\?\C:\work"), r"C:\work");
assert_eq!(
normalize_windows_unc_path(r"\\server\share"),
r"\\server\share"
);
assert_eq!(normalize_windows_unc_path("ordinary/path"), "ordinary/path");
Ok(())
}
#[test]
fn test_canonicalize_with_error_message_handles_existing_and_missing_paths() -> Result<()> {
let temp_dir = tempdir()?;
let existing_path = temp_dir.path().join("existing.txt");
fs::write(&existing_path, "contents")?;
assert_eq!(
canonicalize_with_error_message(&existing_path)?,
existing_path.canonicalize()?
);
let missing_path = temp_dir.path().join("missing.txt");
let error = canonicalize_with_error_message(&missing_path).unwrap_err();
assert!(error.to_string().contains("Fail to canonicalize path="));
Ok(())
}
}