use std::path::{Path, PathBuf};
use directories::ProjectDirs;
pub const DEFAULT_AIW_TEMPLATE: &str = include_str!("../templates/fence.jsonc");
#[derive(Debug, thiserror::Error)]
pub enum ConfigInitError {
#[error("Configuration already exists: {0}")]
AlreadyExists(PathBuf),
#[error("Could not determine user configuration directory")]
UserConfigPathNotFound,
#[error("I/O error during config initialization: {0}")]
Io(#[from] std::io::Error),
}
pub struct ConfigInitializer;
impl ConfigInitializer {
#[must_use]
pub fn user_config_path() -> Option<PathBuf> {
ProjectDirs::from("", "", "aiw").map(|proj| proj.config_dir().join("fence.jsonc"))
}
pub fn init_user_config(force: bool) -> Result<PathBuf, ConfigInitError> {
let path = Self::user_config_path().ok_or(ConfigInitError::UserConfigPathNotFound)?;
if path.exists() && !force {
return Err(ConfigInitError::AlreadyExists(path));
}
if let Some(parent) = path.parent() {
std::fs::create_dir_all(parent)?;
}
std::fs::write(&path, DEFAULT_AIW_TEMPLATE)?;
Ok(path)
}
pub fn init_project_config(target_dir: &Path, force: bool) -> Result<PathBuf, ConfigInitError> {
let fence_jsonc = target_dir.join("fence.jsonc");
let fence_json = target_dir.join("fence.json");
if !force {
if fence_jsonc.exists() {
return Err(ConfigInitError::AlreadyExists(fence_jsonc));
}
if fence_json.exists() {
return Err(ConfigInitError::AlreadyExists(fence_json));
}
}
let base_path_str = Self::user_config_path()
.map(|p| p.to_string_lossy().into_owned())
.unwrap_or_else(|| "~/.config/aiw/fence.jsonc".to_string());
let content = format!(
r#"{{
"$schema": "https://raw.githubusercontent.com/fencesandbox/fence/main/docs/schema/fence.schema.json",
"extends": "{base_path_str}",
"filesystem": {{
"allowRead": []
}}
}}
"#
);
std::fs::write(&fence_jsonc, content)?;
Ok(fence_jsonc)
}
}
#[cfg(test)]
mod tests {
use super::*;
use googletest::prelude::*;
#[googletest::test]
fn default_aiw_template_contains_schema_and_extends() {
expect_that!(
DEFAULT_AIW_TEMPLATE,
contains_substring("fence.schema.json")
);
expect_that!(
DEFAULT_AIW_TEMPLATE,
contains_substring("\"extends\": \"code\"")
);
expect_that!(DEFAULT_AIW_TEMPLATE, contains_substring("\"/nix\""));
}
#[googletest::test]
fn init_project_config_creates_fence_jsonc_with_extends()
-> Result<(), Box<dyn std::error::Error>> {
let temp_dir = tempfile::tempdir()?;
let project_dir = temp_dir.path();
let created = ConfigInitializer::init_project_config(project_dir, false)?;
expect_that!(created, eq(&project_dir.join("fence.jsonc")));
let fence_file = project_dir.join("fence.jsonc");
expect_that!(fence_file.exists(), is_true());
let content = std::fs::read_to_string(&fence_file)?;
expect_that!(&content, contains_substring("fence.schema.json"));
expect_that!(&content, contains_substring("\"extends\":"));
let second = ConfigInitializer::init_project_config(project_dir, false);
expect_that!(
second,
err(matches_pattern!(ConfigInitError::AlreadyExists(_)))
);
let forced = ConfigInitializer::init_project_config(project_dir, true)?;
expect_that!(forced, eq(&project_dir.join("fence.jsonc")));
Ok(())
}
}