use crate::prompt::{self, AllowDecision};
use crate::types::{AllowScope, Allowlist, AllowlistEntry, Task};
use std::fs;
use std::path::{Path, PathBuf};
fn allowlist_path() -> Result<PathBuf, String> {
let home =
std::env::var("HOME").map_err(|_| "HOME environment variable not set".to_string())?;
Ok(PathBuf::from(home).join(".dela").join("allowlist.toml"))
}
pub fn load_allowlist() -> Result<Allowlist, String> {
let path = allowlist_path()?;
let dela_dir = path.parent().ok_or("Invalid allowlist path")?;
if !dela_dir.exists() {
return Err("Dela is not initialized. Please run 'dela init' first.".to_string());
}
if !path.exists() {
return Ok(Allowlist::default());
}
let contents =
fs::read_to_string(&path).map_err(|e| format!("Failed to read allowlist file: {}", e))?;
match toml::from_str::<Allowlist>(&contents) {
Ok(allowlist) => Ok(allowlist),
Err(e) => Err(format!("Failed to parse allowlist TOML: {}", e)),
}
}
pub fn save_allowlist(allowlist: &Allowlist) -> Result<(), String> {
let path = allowlist_path()?;
if let Some(parent) = path.parent() {
fs::create_dir_all(parent)
.map_err(|e| format!("Failed to create .dela directory: {}", e))?;
}
let toml = toml::to_string_pretty(&allowlist)
.map_err(|e| format!("Failed to serialize allowlist: {}", e))?;
fs::write(&path, toml).map_err(|e| format!("Failed to create allowlist file: {}", e))?;
Ok(())
}
fn path_matches(task_path: &Path, allowlist_path: &Path, allow_subdirs: bool) -> bool {
if allow_subdirs {
task_path.starts_with(allowlist_path)
} else {
task_path == allowlist_path
}
}
pub fn check_task_allowed(task: &Task) -> Result<bool, String> {
let mut allowlist = load_allowlist()?;
for entry in &allowlist.entries {
match entry.scope {
AllowScope::Deny => {
if path_matches(&task.file_path, &entry.path, true) {
return Ok(false);
}
}
AllowScope::Directory => {
if path_matches(&task.file_path, &entry.path, true) {
return Ok(true);
}
}
AllowScope::File => {
if path_matches(&task.file_path, &entry.path, false) {
return Ok(true);
}
}
AllowScope::Task => {
if path_matches(&task.file_path, &entry.path, false) {
if let Some(ref tasks) = entry.tasks {
if tasks.contains(&task.name) {
return Ok(true);
}
}
}
}
AllowScope::Once => {
continue;
}
}
}
match prompt::prompt_for_task(task)? {
AllowDecision::Allow(scope) => {
match scope {
AllowScope::Once => {
Ok(true)
}
scope => {
let mut entry = AllowlistEntry {
path: task.file_path.clone(),
scope: scope.clone(),
tasks: None,
};
if scope == AllowScope::Task {
entry.tasks = Some(vec![task.name.clone()]);
}
allowlist.entries.push(entry);
save_allowlist(&allowlist)?;
Ok(true)
}
}
}
AllowDecision::Deny => {
let entry = AllowlistEntry {
path: task.file_path.clone(),
scope: AllowScope::Deny,
tasks: None,
};
allowlist.entries.push(entry);
save_allowlist(&allowlist)?;
Ok(false)
}
}
}
pub fn check_task_allowed_with_scope(task: &Task, scope: AllowScope) -> Result<bool, String> {
let mut allowlist = load_allowlist()?;
let mut entry = AllowlistEntry {
path: task.file_path.clone(),
scope: scope.clone(),
tasks: None,
};
if scope == AllowScope::Task {
entry.tasks = Some(vec![task.name.clone()]);
}
allowlist.entries.push(entry);
save_allowlist(&allowlist)?;
Ok(true)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::types::{Task, TaskDefinitionType, TaskRunner};
use serial_test::serial;
use std::env;
use std::fs;
use tempfile::TempDir;
fn create_test_task(name: &str, file_path: PathBuf) -> Task {
Task {
name: name.to_string(),
file_path,
definition_type: TaskDefinitionType::Makefile,
runner: TaskRunner::Make,
source_name: name.to_string(),
description: None,
shadowed_by: None,
disambiguated_name: None,
}
}
fn setup_test_env() -> (TempDir, Task) {
let temp_dir = TempDir::new().unwrap();
env::set_var("HOME", temp_dir.path());
fs::create_dir_all(temp_dir.path().join(".dela"))
.expect("Failed to create .dela directory");
let task = create_test_task("test-task", PathBuf::from("Makefile"));
(temp_dir, task)
}
#[test]
#[serial]
fn test_empty_allowlist() {
let (_temp_dir, _task) = setup_test_env();
let allowlist = load_allowlist().unwrap();
assert!(allowlist.entries.is_empty());
}
#[test]
#[serial]
fn test_save_and_load_allowlist() {
let (temp_dir, _task) = setup_test_env();
let mut allowlist = Allowlist::default();
let entry = AllowlistEntry {
path: PathBuf::from("Makefile"),
scope: AllowScope::File,
tasks: None,
};
allowlist.entries.push(entry);
save_allowlist(&allowlist).unwrap();
let path = allowlist_path().unwrap();
println!("Allowlist path: {}", path.display());
println!("Allowlist exists: {}", path.exists());
if path.exists() {
let contents = std::fs::read_to_string(&path).unwrap();
println!("Allowlist contents: {}", contents);
let loaded_from_file: Allowlist = toml::from_str(&contents).unwrap();
println!("Loaded from file: {:?}", loaded_from_file);
}
let loaded = load_allowlist().unwrap();
println!("Loaded from function: {:?}", loaded);
assert_eq!(loaded.entries.len(), 1);
assert_eq!(loaded.entries[0].scope, AllowScope::File);
drop(temp_dir);
}
#[test]
#[serial]
fn test_path_matches() {
let base = PathBuf::from("/home/user/project");
let file = base.join("Makefile");
let subdir = base.join("subdir").join("Makefile");
assert!(path_matches(&file, &file, false));
assert!(!path_matches(&subdir, &file, false));
assert!(path_matches(&file, &base, true));
assert!(path_matches(&subdir, &base, true));
}
}