opencode-ralph-loop-cli 0.1.0

Scaffolder CLI for OpenCode Ralph Loop plugin — one command setup
Documentation
use std::fs::{self, File};
use std::io::Write;
use std::path::Path;

use crate::error::CliError;

pub fn write_atomic(dest: &Path, content: &[u8]) -> Result<(), CliError> {
    let parent = dest.parent().ok_or_else(|| {
        CliError::io(
            dest.to_string_lossy().into_owned(),
            std::io::Error::new(
                std::io::ErrorKind::InvalidInput,
                "path has no parent directory",
            ),
        )
    })?;

    fs::create_dir_all(parent)
        .map_err(|e| CliError::io(parent.to_string_lossy().into_owned(), e))?;

    let tmp_path = dest.with_extension("tmp");

    let write_result = (|| -> std::io::Result<()> {
        let mut file = File::create(&tmp_path)?;
        file.write_all(content)?;
        file.flush()?;
        file.sync_all()?;
        Ok(())
    })();

    if let Err(e) = write_result {
        let _ = fs::remove_file(&tmp_path);
        return Err(CliError::io(tmp_path.to_string_lossy().into_owned(), e));
    }

    fs::rename(&tmp_path, dest)
        .map_err(|e| CliError::io(dest.to_string_lossy().into_owned(), e))?;

    Ok(())
}

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

    #[test]
    fn atomic_write_creates_file() {
        let dir = TempDir::new().unwrap();
        let dest = dir.path().join("file.txt");
        write_atomic(&dest, b"test content").unwrap();
        let read_back = std::fs::read(&dest).unwrap();
        assert_eq!(read_back, b"test content");
    }

    #[test]
    fn atomic_write_creates_directories() {
        let dir = TempDir::new().unwrap();
        let dest = dir.path().join("subdir").join("other").join("file.txt");
        write_atomic(&dest, b"test").unwrap();
        assert!(dest.exists());
    }

    #[test]
    fn atomic_write_overwrites_existing() {
        let dir = TempDir::new().unwrap();
        let dest = dir.path().join("file.txt");
        write_atomic(&dest, b"first version").unwrap();
        write_atomic(&dest, b"second version").unwrap();
        let read_back = std::fs::read(&dest).unwrap();
        assert_eq!(read_back, b"second version");
    }
}