use std::cell::RefCell;
use std::collections::HashMap;
use std::path::{Path, PathBuf};
use crate::error::{MacroResult, err_call_site};
pub(super) fn current_crate_tag() -> String {
std::env::var("CARGO_PKG_NAME").unwrap_or_else(|_| "default".to_string())
}
pub fn find_folder_path(folder_name: &str) -> MacroResult<std::path::PathBuf> {
let root = std::env::var("CARGO_MANIFEST_DIR").map_err(|_| {
err_call_site(
"CARGO_MANIFEST_DIR is not set. vespera macros must be used within a cargo build.",
)
})?;
let path = format!("{root}/src/{folder_name}");
let path = Path::new(&path);
if path.exists() && path.is_dir() {
return Ok(path.to_path_buf());
}
Ok(Path::new(folder_name).to_path_buf())
}
thread_local! {
static TARGET_DIR_CACHE: RefCell<HashMap<PathBuf, PathBuf>> =
RefCell::new(HashMap::new());
}
pub fn find_target_dir(manifest_path: &Path) -> PathBuf {
if let Some(cached) = TARGET_DIR_CACHE.with(|c| c.borrow().get(manifest_path).cloned()) {
return cached;
}
let resolved = find_target_dir_uncached(manifest_path);
TARGET_DIR_CACHE.with(|c| {
c.borrow_mut()
.insert(manifest_path.to_path_buf(), resolved.clone());
});
resolved
}
fn find_target_dir_uncached(manifest_path: &Path) -> PathBuf {
let mut current = Some(manifest_path);
let mut last_with_lock = None;
while let Some(dir) = current {
if dir.join("Cargo.lock").exists() {
last_with_lock = Some(dir.to_path_buf());
}
if let Ok(contents) = std::fs::read_to_string(dir.join("Cargo.toml"))
&& contents.contains("[workspace]")
{
return dir.join("target");
}
current = dir.parent();
}
if let Some(lock_dir) = last_with_lock {
return lock_dir.join("target");
}
manifest_path.join("target")
}
#[cfg(test)]
mod tests {
use std::fs;
use tempfile::TempDir;
use super::*;
#[test]
fn test_find_folder_path_nonexistent_returns_path() {
let result = find_folder_path("nonexistent_folder_xyz").unwrap();
assert!(result.to_string_lossy().contains("nonexistent_folder_xyz"));
}
#[test]
fn test_find_target_dir_no_workspace() {
let temp_dir = TempDir::new().expect("Failed to create temp dir");
let manifest_path = temp_dir.path();
let result = find_target_dir(manifest_path);
assert_eq!(result, manifest_path.join("target"));
}
#[test]
fn test_find_target_dir_with_cargo_lock() {
let temp_dir = TempDir::new().expect("Failed to create temp dir");
let manifest_path = temp_dir.path();
fs::write(manifest_path.join("Cargo.lock"), "").expect("Failed to write Cargo.lock");
let result = find_target_dir(manifest_path);
assert_eq!(result, manifest_path.join("target"));
}
#[test]
fn test_find_target_dir_with_workspace() {
let temp_dir = TempDir::new().expect("Failed to create temp dir");
let workspace_root = temp_dir.path();
fs::write(
workspace_root.join("Cargo.toml"),
"[workspace]\nmembers = [\"crate1\"]",
)
.expect("Failed to write Cargo.toml");
let crate_dir = workspace_root.join("crate1");
fs::create_dir(&crate_dir).expect("Failed to create crate dir");
fs::write(crate_dir.join("Cargo.toml"), "[package]\nname = \"crate1\"")
.expect("Failed to write Cargo.toml");
let result = find_target_dir(&crate_dir);
assert_eq!(result, workspace_root.join("target"));
}
#[test]
fn test_find_target_dir_workspace_with_cargo_lock() {
let temp_dir = TempDir::new().expect("Failed to create temp dir");
let workspace_root = temp_dir.path();
fs::write(
workspace_root.join("Cargo.toml"),
"[workspace]\nmembers = [\"crate1\"]",
)
.expect("Failed to write Cargo.toml");
fs::write(workspace_root.join("Cargo.lock"), "").expect("Failed to write Cargo.lock");
let crate_dir = workspace_root.join("crate1");
fs::create_dir(&crate_dir).expect("Failed to create crate dir");
fs::write(crate_dir.join("Cargo.toml"), "[package]\nname = \"crate1\"")
.expect("Failed to write Cargo.toml");
let result = find_target_dir(&crate_dir);
assert_eq!(result, workspace_root.join("target"));
}
#[test]
fn test_find_target_dir_deeply_nested() {
let temp_dir = TempDir::new().expect("Failed to create temp dir");
let workspace_root = temp_dir.path();
fs::write(
workspace_root.join("Cargo.toml"),
"[workspace]\nmembers = [\"crates/*\"]",
)
.expect("Failed to write Cargo.toml");
let deep_crate = workspace_root.join("crates/group/my-crate");
fs::create_dir_all(&deep_crate).expect("Failed to create nested dirs");
fs::write(deep_crate.join("Cargo.toml"), "[package]").expect("Failed to write Cargo.toml");
let result = find_target_dir(&deep_crate);
assert_eq!(result, workspace_root.join("target"));
}
#[test]
fn test_find_folder_path_absolute_path() {
let temp_dir = TempDir::new().expect("Failed to create temp dir");
let absolute_path = temp_dir.path().to_string_lossy().to_string();
let result = find_folder_path(&absolute_path).unwrap();
assert!(
result.to_string_lossy().contains(&absolute_path)
|| result == Path::new(&absolute_path)
);
}
#[serial_test::serial]
#[test]
fn test_find_folder_path_with_src_folder() {
let temp_dir = TempDir::new().expect("Failed to create temp dir");
let src_routes = temp_dir.path().join("src").join("routes");
fs::create_dir_all(&src_routes).expect("Failed to create src/routes dir");
let old_manifest_dir = std::env::var("CARGO_MANIFEST_DIR").ok();
unsafe { std::env::set_var("CARGO_MANIFEST_DIR", temp_dir.path()) };
let result = find_folder_path("routes").unwrap();
if let Some(old_value) = old_manifest_dir {
unsafe { std::env::set_var("CARGO_MANIFEST_DIR", old_value) };
}
assert!(
result.to_string_lossy().contains("src") && result.to_string_lossy().contains("routes")
);
}
#[test]
#[serial_test::serial]
fn find_folder_path_reports_missing_manifest_environment() {
let original = std::env::var("CARGO_MANIFEST_DIR").ok();
unsafe { std::env::remove_var("CARGO_MANIFEST_DIR") };
let error = find_folder_path("routes").expect_err("manifest directory is required");
unsafe {
if let Some(value) = original {
std::env::set_var("CARGO_MANIFEST_DIR", value);
}
}
assert!(error.to_string().contains("CARGO_MANIFEST_DIR is not set"));
}
}