use crate::core::config::UserOwnedPaths;
use std::path::Path;
const CONFIG_FILE_NAME: &str = "alef.toml";
pub(crate) fn declared_user_owned(base_dir: &Path) -> anyhow::Result<UserOwnedPaths> {
let config_path = base_dir.join(CONFIG_FILE_NAME);
let Ok(content) = std::fs::read_to_string(&config_path) else {
return Ok(UserOwnedPaths::none());
};
let declares_ownership = content.contains("[workspace.ownership]") || content.contains("user_owned");
match toml::from_str::<crate::core::config::NewAlefConfig>(&content) {
Ok(config) => UserOwnedPaths::compile(&config.workspace.ownership.user_owned),
Err(error) if declares_ownership => Err(anyhow::anyhow!(
"{} declares [workspace.ownership] but could not be parsed ({error}). alef refuses to \
continue rather than write over paths the declaration was meant to protect.",
config_path.display()
)),
Err(_) => Ok(UserOwnedPaths::none()),
}
}
pub(super) fn skip_declared_existing(
declared: &UserOwnedPaths,
base_dir: &Path,
full_path: &Path,
report: &mut super::write::WriteReport,
) -> bool {
if !declared.matches(base_dir, full_path) || !full_path.exists() {
return false;
}
report.expected_paths.insert(full_path.to_path_buf());
report.user_owned_paths.insert(full_path.to_path_buf());
tracing::debug!(" declared user-owned (not written): {}", full_path.display());
true
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn a_tree_with_no_config_declares_nothing() {
let temp = tempfile::tempdir().expect("tempdir");
let declared = declared_user_owned(temp.path()).expect("no config must fail open");
assert!(declared.is_empty());
}
#[test]
fn a_declared_glob_is_compiled_from_the_workspace_section() {
let temp = tempfile::tempdir().expect("tempdir");
std::fs::write(
temp.path().join(CONFIG_FILE_NAME),
"[workspace.ownership]\nuser_owned = [\"e2e/*/package.json\"]\n\n\
[[crates]]\nname = \"sample_core\"\nsources = [\"src/lib.rs\"]\n",
)
.expect("write config");
let declared = declared_user_owned(temp.path()).expect("valid config");
assert!(declared.matches(temp.path(), &temp.path().join("e2e/node/package.json")));
assert!(!declared.matches(temp.path(), &temp.path().join("packages/node/index.js")));
}
#[test]
fn an_unparseable_config_that_declares_ownership_is_a_hard_error_not_a_silent_fail_open() {
let temp = tempfile::tempdir().expect("tempdir");
std::fs::write(
temp.path().join(CONFIG_FILE_NAME),
"[workspace.ownership]\nuser_owned = [\"e2e/*/package.json\"]\nthis is not toml\n",
)
.expect("write config");
let error = declared_user_owned(temp.path()).expect_err("must not fail open");
assert!(
format!("{error:#}").contains("[workspace.ownership]"),
"the error must name the declaration whose protection would otherwise be silently lost"
);
}
#[test]
fn an_unparseable_config_that_declares_nothing_still_fails_open() {
let temp = tempfile::tempdir().expect("tempdir");
std::fs::write(temp.path().join(CONFIG_FILE_NAME), "this is not toml\n").expect("write config");
let declared = declared_user_owned(temp.path()).expect("must fail open");
assert!(declared.is_empty());
}
}