cartulary 0.3.0-alpha.1

The knowledge layer of your project — decisions, issues, docs, all in one place.
Documentation
use std::path::PathBuf;

use crate::domain::usecases::init::WorkspaceInitializer;
use crate::infra::driven::fs::config::CURRENT_SCHEMA_VERSION;

/// Filesystem adapter for workspace initialisation.
///
/// Writes `cartulary.toml` at `root/cartulary.toml`. The template embeds
/// `CURRENT_SCHEMA_VERSION` so a fresh workspace is immediately operable
/// with the current binary (no `cartu migrate` needed).
pub struct FsWorkspaceInitializer {
    pub root: PathBuf,
}

fn default_config() -> String {
    format!(
        r#"version = {CURRENT_SCHEMA_VERSION}

[decisions]
types = ["adr"]

[decisions.adr]
dir = "docs/adr"

[issues]
dir = "docs/issues"
"#
    )
}

impl WorkspaceInitializer for FsWorkspaceInitializer {
    fn config_exists(&self) -> bool {
        self.root.join("cartulary.toml").exists()
    }

    fn write_default_config(&self) -> anyhow::Result<std::path::PathBuf> {
        let path = self.root.join("cartulary.toml");
        std::fs::write(&path, default_config())
            .map_err(|e| anyhow::anyhow!("failed to write {}: {e}", path.display()))?;
        Ok(path)
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use tempfile::TempDir;

    #[test]
    fn config_exists_returns_false_for_empty_dir() {
        let tmp = TempDir::new().unwrap();
        let adapter = FsWorkspaceInitializer {
            root: tmp.path().to_path_buf(),
        };
        assert!(!adapter.config_exists());
    }

    #[test]
    fn config_exists_returns_true_when_file_present() {
        let tmp = TempDir::new().unwrap();
        std::fs::write(tmp.path().join("cartulary.toml"), "[decisions]\n").unwrap();
        let adapter = FsWorkspaceInitializer {
            root: tmp.path().to_path_buf(),
        };
        assert!(adapter.config_exists());
    }

    #[test]
    fn write_default_config_creates_file_with_correct_content() {
        let tmp = TempDir::new().unwrap();
        let adapter = FsWorkspaceInitializer {
            root: tmp.path().to_path_buf(),
        };
        let path = adapter.write_default_config().unwrap();
        let content = std::fs::read_to_string(tmp.path().join("cartulary.toml")).unwrap();
        assert!(
            content.contains("[decisions]"),
            "missing [decisions] in {content}"
        );
        assert!(
            content.contains("[issues]"),
            "missing [issues] in {content}"
        );
        assert!(path.ends_with("cartulary.toml"));
    }

    #[test]
    fn write_default_config_embeds_current_schema_version() {
        let tmp = TempDir::new().unwrap();
        let adapter = FsWorkspaceInitializer {
            root: tmp.path().to_path_buf(),
        };
        adapter.write_default_config().unwrap();
        let content = std::fs::read_to_string(tmp.path().join("cartulary.toml")).unwrap();
        let expected = format!("version = {CURRENT_SCHEMA_VERSION}");
        assert!(
            content.contains(&expected),
            "expected `{expected}` in fresh workspace, got:\n{content}"
        );
    }
}