use crate::api::{PadzApi, PadzPaths};
use crate::config::PadzConfig;
use crate::model::Scope;
use crate::store::fs::FileStore;
use directories::{BaseDirs, ProjectDirs};
use std::path::{Path, PathBuf};
pub struct PadzContext {
pub api: PadzApi<FileStore>,
pub scope: Scope,
pub config: PadzConfig,
}
pub fn find_project_root(cwd: &Path) -> Option<PathBuf> {
let home_dir = BaseDirs::new().map(|bd| bd.home_dir().to_path_buf());
let mut current = cwd.to_path_buf();
loop {
let git_dir = current.join(".git");
let padz_dir = current.join(".padz");
if git_dir.exists() && padz_dir.exists() {
return Some(current);
}
if let Some(ref home) = home_dir {
if ¤t == home {
return None;
}
}
match current.parent() {
Some(parent) if parent != current => {
current = parent.to_path_buf();
}
_ => {
return None;
}
}
}
}
pub fn initialize(cwd: &Path, use_global: bool, data_override: Option<PathBuf>) -> PadzContext {
let project_padz_dir = match data_override {
Some(path) => {
if path.file_name().is_some_and(|name| name == ".padz") {
path
} else {
path.join(".padz")
}
}
None => find_project_root(cwd)
.map(|root| root.join(".padz"))
.unwrap_or_else(|| cwd.join(".padz")),
};
let proj_dirs =
ProjectDirs::from("com", "padz", "padz").expect("Could not determine config dir");
let global_data_dir = proj_dirs.data_dir().to_path_buf();
let scope = if use_global {
Scope::Global
} else {
Scope::Project
};
let config_dir = match scope {
Scope::Project => &project_padz_dir,
Scope::Global => &global_data_dir,
};
let config = PadzConfig::load(config_dir).unwrap_or_default();
let file_ext = config.get_file_ext().to_string();
let store = FileStore::new(Some(project_padz_dir.clone()), global_data_dir.clone())
.with_file_ext(&file_ext);
let paths = PadzPaths {
project: Some(project_padz_dir),
global: global_data_dir,
};
let api = PadzApi::new(store, paths);
PadzContext { api, scope, config }
}
#[cfg(test)]
mod tests {
use super::*;
use std::fs;
use tempfile::TempDir;
#[test]
fn test_find_project_root_with_git_and_padz() {
let temp = TempDir::new().unwrap();
let root = temp.path();
fs::create_dir(root.join(".git")).unwrap();
fs::create_dir(root.join(".padz")).unwrap();
let result = find_project_root(root);
assert_eq!(result, Some(root.to_path_buf()));
}
#[test]
fn test_find_project_root_git_only_continues_up() {
let temp = TempDir::new().unwrap();
let parent = temp.path();
let child = parent.join("child-repo");
fs::create_dir(&child).unwrap();
fs::create_dir(parent.join(".git")).unwrap();
fs::create_dir(parent.join(".padz")).unwrap();
fs::create_dir(child.join(".git")).unwrap();
let result = find_project_root(&child);
assert_eq!(result, Some(parent.to_path_buf()));
}
#[test]
fn test_find_project_root_nested_repos_child_has_padz() {
let temp = TempDir::new().unwrap();
let parent = temp.path();
let child = parent.join("child-repo");
fs::create_dir(&child).unwrap();
fs::create_dir(parent.join(".git")).unwrap();
fs::create_dir(parent.join(".padz")).unwrap();
fs::create_dir(child.join(".git")).unwrap();
fs::create_dir(child.join(".padz")).unwrap();
let result = find_project_root(&child);
assert_eq!(result, Some(child.clone()));
}
#[test]
fn test_find_project_root_deep_nested() {
let temp = TempDir::new().unwrap();
let grandparent = temp.path();
let parent = grandparent.join("parent");
let child = parent.join("child");
fs::create_dir_all(&child).unwrap();
fs::create_dir(grandparent.join(".git")).unwrap();
fs::create_dir(grandparent.join(".padz")).unwrap();
let result = find_project_root(&child);
assert_eq!(result, Some(grandparent.to_path_buf()));
}
#[test]
fn test_find_project_root_no_git_no_padz() {
let temp = TempDir::new().unwrap();
let dir = temp.path().join("some").join("deep").join("path");
fs::create_dir_all(&dir).unwrap();
let result = find_project_root(&dir);
assert_eq!(result, None);
}
#[test]
fn test_find_project_root_padz_only_no_git() {
let temp = TempDir::new().unwrap();
let root = temp.path();
fs::create_dir(root.join(".padz")).unwrap();
let result = find_project_root(root);
assert_eq!(result, None);
}
#[test]
fn test_find_project_root_multiple_nested_git_only_repos() {
let temp = TempDir::new().unwrap();
let grandparent = temp.path();
let parent = grandparent.join("parent-repo");
let child = parent.join("child-repo");
fs::create_dir_all(&child).unwrap();
fs::create_dir(grandparent.join(".git")).unwrap();
fs::create_dir(grandparent.join(".padz")).unwrap();
fs::create_dir(parent.join(".git")).unwrap();
fs::create_dir(child.join(".git")).unwrap();
let result = find_project_root(&child);
assert_eq!(result, Some(grandparent.to_path_buf()));
let result = find_project_root(&parent);
assert_eq!(result, Some(grandparent.to_path_buf()));
}
#[test]
fn test_initialize_with_data_override_ending_in_padz() {
let temp = TempDir::new().unwrap();
let repo = temp.path();
fs::create_dir(repo.join(".git")).unwrap();
fs::create_dir(repo.join(".padz")).unwrap();
let override_dir = temp.path().join("custom-data").join(".padz");
fs::create_dir_all(&override_dir).unwrap();
let ctx = initialize(repo, false, Some(override_dir.clone()));
assert_eq!(ctx.api.paths().project, Some(override_dir));
assert_eq!(ctx.scope, crate::model::Scope::Project);
}
#[test]
fn test_initialize_with_data_override_not_ending_in_padz() {
let temp = TempDir::new().unwrap();
let repo = temp.path();
fs::create_dir(repo.join(".git")).unwrap();
fs::create_dir(repo.join(".padz")).unwrap();
let override_dir = temp.path().join("custom-project");
fs::create_dir_all(&override_dir).unwrap();
let ctx = initialize(repo, false, Some(override_dir.clone()));
assert_eq!(ctx.api.paths().project, Some(override_dir.join(".padz")));
assert_eq!(ctx.scope, crate::model::Scope::Project);
}
#[test]
fn test_initialize_without_override_uses_detection() {
let temp = TempDir::new().unwrap();
let repo = temp.path();
fs::create_dir(repo.join(".git")).unwrap();
fs::create_dir(repo.join(".padz")).unwrap();
let ctx = initialize(repo, false, None);
assert_eq!(ctx.api.paths().project, Some(repo.join(".padz")));
assert_eq!(ctx.scope, crate::model::Scope::Project);
}
#[test]
fn test_initialize_data_override_with_global_flag() {
let temp = TempDir::new().unwrap();
let repo = temp.path();
fs::create_dir(repo.join(".git")).unwrap();
fs::create_dir(repo.join(".padz")).unwrap();
let override_dir = temp.path().join("custom-data").join(".padz");
fs::create_dir_all(&override_dir).unwrap();
let ctx = initialize(repo, true, Some(override_dir.clone()));
assert_eq!(ctx.api.paths().project, Some(override_dir));
assert_eq!(ctx.scope, crate::model::Scope::Global);
}
#[test]
fn test_initialize_data_override_from_unrelated_directory() {
let temp = TempDir::new().unwrap();
let project = temp.path().join("project");
fs::create_dir_all(project.join(".padz")).unwrap();
fs::create_dir(project.join(".git")).unwrap();
let workdir = temp.path().join("workdir");
fs::create_dir_all(&workdir).unwrap();
let ctx = initialize(&workdir, false, Some(project.join(".padz")));
assert_eq!(ctx.api.paths().project, Some(project.join(".padz")));
}
}