use std::path::{Path, PathBuf};
pub(crate) use super::home::effective_home_dir;
pub(crate) fn default_config_path() -> Option<PathBuf> {
#[cfg(test)]
{
let honor_guarded_environment = crate::test_support::current_thread_holds_test_env_lock();
crate::test_support::with_test_env_lock(|| {
if honor_guarded_environment {
default_config_path_from_environment()
} else {
Some(
crate::test_support::isolated_test_state_root()
.join(codewhale_config::CONFIG_FILE_NAME),
)
}
})
}
#[cfg(not(test))]
default_config_path_from_environment()
}
fn default_config_path_from_environment() -> Option<PathBuf> {
env_config_path_unlocked().or_else(home_config_path)
}
pub(crate) fn codewhale_home_dir() -> Option<PathBuf> {
codewhale_paths::codewhale_home_override()
}
pub(crate) fn home_config_path() -> Option<PathBuf> {
if let Some(home) = codewhale_home_dir() {
return Some(home.join("config.toml"));
}
effective_home_dir().map(|home| {
let primary = home.join(".codewhale").join("config.toml");
if primary.exists() {
return primary;
}
let legacy = home.join(".deepseek").join("config.toml");
if legacy.exists() {
return legacy;
}
primary
})
}
pub(crate) fn workspace_config_key(workspace: &Path) -> String {
canonicalize_or_keep(workspace)
.to_string_lossy()
.into_owned()
}
pub(crate) fn canonicalize_or_keep(path: &Path) -> PathBuf {
path.canonicalize().unwrap_or_else(|_| path.to_path_buf())
}
pub(crate) fn env_config_path() -> Option<PathBuf> {
#[cfg(test)]
{
crate::test_support::with_test_env_lock(env_config_path_unlocked)
}
#[cfg(not(test))]
{
env_config_path_unlocked()
}
}
fn env_config_path_unlocked() -> Option<PathBuf> {
if let Ok(path) = std::env::var("CODEWHALE_CONFIG_PATH") {
let trimmed = path.trim();
if !trimmed.is_empty() {
return Some(expand_path(trimmed));
}
}
if let Ok(path) = std::env::var("DEEPSEEK_CONFIG_PATH") {
let trimmed = path.trim();
if !trimmed.is_empty() {
return Some(expand_path(trimmed));
}
}
None
}
pub(crate) fn expand_pathbuf(path: PathBuf) -> PathBuf {
if let Some(raw) = path.to_str() {
return expand_path(raw);
}
path
}
pub(crate) fn default_managed_config_path() -> Option<PathBuf> {
#[cfg(unix)]
{
Some(PathBuf::from("/etc/deepseek/managed_config.toml"))
}
#[cfg(not(unix))]
{
effective_home_dir().map(|home| {
let primary = home.join(".codewhale").join("managed_config.toml");
if primary.exists() {
return primary;
}
home.join(".deepseek").join("managed_config.toml")
})
}
}
pub(crate) fn default_requirements_path() -> Option<PathBuf> {
#[cfg(unix)]
{
Some(PathBuf::from("/etc/deepseek/requirements.toml"))
}
#[cfg(not(unix))]
{
effective_home_dir().map(|home| {
let primary = home.join(".codewhale").join("requirements.toml");
if primary.exists() {
return primary;
}
home.join(".deepseek").join("requirements.toml")
})
}
}
pub(crate) fn expand_path(path: &str) -> PathBuf {
if let Some(stripped) = path.strip_prefix('~')
&& (stripped.is_empty() || stripped.starts_with('/') || stripped.starts_with('\\'))
&& let Some(mut home) = effective_home_dir()
{
let suffix = stripped.trim_start_matches(['/', '\\']);
if !suffix.is_empty() {
home.push(suffix);
}
return home;
}
let expanded = shellexpand::tilde(path);
PathBuf::from(expanded.as_ref())
}
pub(crate) fn default_skills_dir() -> Option<PathBuf> {
default_user_state_path("skills")
}
pub(crate) fn default_mcp_config_path() -> Option<PathBuf> {
default_user_state_path("mcp.json")
}
pub(crate) fn default_notes_path() -> Option<PathBuf> {
default_user_state_path("notes.txt")
}
pub(crate) fn default_memory_path() -> Option<PathBuf> {
default_user_state_path("memory.md")
}
fn default_user_state_path(name: &str) -> Option<PathBuf> {
if let Some(home) = codewhale_home_dir() {
return Some(home.join(name));
}
effective_home_dir().map(|home| {
let primary = home.join(".codewhale").join(name);
if primary.exists() {
return primary;
}
let legacy = home.join(".deepseek").join(name);
if legacy.exists() {
return legacy;
}
primary
})
}