lazin 1.0.4

Primarily a dotfiles manager
use std::{
    fs::{self, OpenOptions},
    io::{ErrorKind, Write},
    path::Path,
};

use lazin_error::{Context, LazinResult};

use crate::error::LazinError;

const LAZIN_DEFAULT_WORKSPACE_FILE: &str = "workspace.toml";
const LAZIN_DEFAULT_MODULE_FILE: &str = "modules.toml";

const LAZIN_DEFAULT_WORKSPACE: &str = r#"#workspace_name = [
#   "module1",
#   "module2",
#]
"#;

const LAZIN_DEFAULT_MODULE: &str = r#"#[my_module]
#config = {
#    encrypt = false, # When setting to false, recipient field is optional
#}
#source_file="/.config/my_program/source_file"
#source_dir="/.config/source_dir"
#"more/specific/path/to/source_file"="/.config/my_program/source_file"
#source_file_with_attributes = {
#    path = "/.config/my_program/source_file_with_attributes",
#    config = {
#        encrypt = true # override module level encrypt
#        recipient = "gpg-recipient"
#    }
#}
"#;

enum WriteFileState {
    Written,
    Skipped,
}

fn try_write_file(base_dir: &Path, filename: &str, data: &[u8]) -> LazinResult<WriteFileState> {
    let mut write_state = WriteFileState::Written;

    let new_file = OpenOptions::new()
        .create_new(true)
        .write(true)
        .open(base_dir.to_path_buf().join(filename));

    if let Ok(mut file) = new_file {
        file.write_all(data).context("Failed to write file")?;
    } else if let Err(e) = new_file {
        match e.kind() {
            ErrorKind::AlreadyExists => write_state = WriteFileState::Skipped,
            _ => return Err(LazinError::Io(e)).context("File write error"),
        }
    }

    Ok(write_state)
}

pub fn init_directory(directory: &Path) -> LazinResult<()> {
    fs::create_dir_all(directory).context("Failed to create directories")
}

pub fn init_default_config(base_dir: &Path) -> LazinResult<()> {
    let lazin_default_workspace = try_write_file(
        base_dir,
        LAZIN_DEFAULT_WORKSPACE_FILE,
        LAZIN_DEFAULT_WORKSPACE.as_bytes(),
    )?;
    match lazin_default_workspace {
        WriteFileState::Written => (),
        WriteFileState::Skipped => lazin_logger::info!(
            "Skipped writing {} file. File already exists.",
            LAZIN_DEFAULT_WORKSPACE_FILE
        ),
    }

    let lazin_default_module = try_write_file(
        base_dir,
        LAZIN_DEFAULT_MODULE_FILE,
        LAZIN_DEFAULT_MODULE.as_bytes(),
    )?;
    match lazin_default_module {
        WriteFileState::Written => (),
        WriteFileState::Skipped => lazin_logger::info!(
            "Skipped writing {} file. File already exists.",
            LAZIN_DEFAULT_MODULE_FILE
        ),
    }

    Ok(())
}

#[cfg(test)]
mod test {
    use std::fs;

    use crate::{
        filesystem::init::{init_default_config, init_directory},
        test::filesystem::tmp::TempDir,
    };

    #[test]
    fn temp_default_config() {
        let temp_dir = TempDir::new();
        init_default_config(temp_dir.path()).expect("initialized config file examples")
    }

    #[test]
    fn temp_init_directory() {
        let temp_dir = TempDir::new();
        init_directory(temp_dir.path()).expect("initialized temp directory");
        assert!(
            fs::metadata(temp_dir.path())
                .expect("valid metadata")
                .is_dir()
        );
    }

    #[test]
    fn temp_init_directory_nested() {
        let temp_dir = TempDir::new();
        let temp_dir = temp_dir.path().join("child1").join("child2");
        init_directory(&temp_dir).expect("initialized temp directory");
        assert!(fs::metadata(&temp_dir).expect("valid metadata").is_dir());
    }
}