use std::{ env::{self, current_dir}, ffi::OsStr, path::{Path, PathBuf}, sync::{Arc, Mutex}, }; use git2::{build::RepoBuilder, FetchOptions, RemoteCallbacks}; use crate::{ config::{CuddleConfig, CuddleFetchPolicy}, model::{CuddleBase, CuddlePlan}, }; #[derive(Debug)] pub struct CuddleContext { pub plan: CuddlePlan, pub path: PathBuf, } pub fn extract_cuddle(config: CuddleConfig) -> anyhow::Result>>> { let mut curr_dir = current_dir()?; curr_dir.push(".cuddle/"); let fetch_policy = config.get_fetch_policy()?; if let CuddleFetchPolicy::Always = fetch_policy { if let Err(res) = std::fs::remove_dir_all(curr_dir) { panic!("{}", res) } } // Load main cuddle file let cuddle_yaml = find_root_cuddle()?; log::trace!(cuddle_yaml=log::as_debug!(cuddle_yaml); "Find root cuddle"); let cuddle_plan = serde_yaml::from_str::(cuddle_yaml.as_str())?; log::debug!(cuddle_plan=log::as_debug!(cuddle_yaml); "parse cuddle plan"); let context: Arc>> = Arc::new(Mutex::new(Vec::new())); context.lock().unwrap().push(CuddleContext { plan: cuddle_plan.clone(), path: current_dir()?, }); // pull parent plan and execute recursive descent match cuddle_plan.base { CuddleBase::Bool(true) => { return Err(anyhow::anyhow!( "plan cannot be enabled without specifying a plan" )) } CuddleBase::Bool(false) => { log::debug!("plan is root: skipping"); } CuddleBase::String(parent_plan) => { let destination_path = create_cuddle_local()?; let mut cuddle_dest = destination_path.clone(); cuddle_dest.push("base"); if !cuddle_dest.exists() { pull_parent_cuddle_into_local(parent_plan, cuddle_dest.clone())?; recurse_parent(cuddle_dest, context.clone())?; } } } Ok(context) } fn create_cuddle_local() -> anyhow::Result { let mut curr_dir = current_dir()?; curr_dir.push(".cuddle/"); if curr_dir.exists() { log::debug!(".cuddle/ already exists: skipping"); return Ok(curr_dir); } std::fs::create_dir(curr_dir.clone())?; Ok(curr_dir) } fn create_cuddle(path: PathBuf) -> anyhow::Result { let mut curr_dir = path.clone(); curr_dir.push(".cuddle/"); if curr_dir.exists() { log::debug!(".cuddle/ already exists: skipping"); return Ok(curr_dir); } std::fs::create_dir(curr_dir.clone())?; Ok(curr_dir) } fn pull_parent_cuddle_into_local( parent_cuddle: String, destination: PathBuf, ) -> anyhow::Result<()> { let mut rc = RemoteCallbacks::new(); rc.credentials(|_url, username_from_url, _allowed_types| { git2::Cred::ssh_key( username_from_url.unwrap(), None, Path::new(&format!("{}/.ssh/id_ed25519", env::var("HOME").unwrap())), None, ) }); let mut fo = FetchOptions::new(); fo.remote_callbacks(rc); RepoBuilder::new() .fetch_options(fo) .clone(&parent_cuddle, &destination)?; log::debug!(parent_cuddle=log::as_display!(parent_cuddle); "pulled repository"); Ok(()) } fn recurse_parent(path: PathBuf, context: Arc>>) -> anyhow::Result<()> { let cuddle_contents = find_cuddle(path.clone())?; let cuddle_plan = serde_yaml::from_str::(&cuddle_contents)?; let ctx = context.clone(); if let Ok(mut ctxs) = ctx.lock() { ctxs.push(CuddleContext { plan: cuddle_plan.clone(), path: path.clone(), }); } else { return Err(anyhow::anyhow!("Could not acquire lock, aborting")); } match cuddle_plan.base { CuddleBase::Bool(true) => { return Err(anyhow::anyhow!( "plan cannot be enabled without specifying a plan" )) } CuddleBase::Bool(false) => { log::debug!("plan is root: finishing up"); return Ok(()); } CuddleBase::String(parent_plan) => { let destination_path = create_cuddle(path.clone())?; let mut cuddle_dest = destination_path.clone(); cuddle_dest.push("base"); pull_parent_cuddle_into_local(parent_plan, cuddle_dest.clone())?; return recurse_parent(cuddle_dest, context.clone()); } } } fn find_root_cuddle() -> anyhow::Result { // TODO: Make recursive towards root let current_dir = env::current_dir()?; find_cuddle(current_dir) } fn find_cuddle(path: PathBuf) -> anyhow::Result { for entry in std::fs::read_dir(path)? { let entry = entry?; let path = entry.path(); let metadata = std::fs::metadata(&path)?; if metadata.is_file() && path.file_name().unwrap() == OsStr::new("cuddle.yaml") { return Ok(std::fs::read_to_string(path)?); } } Err(anyhow::anyhow!( "Could not find 'cuddle.yaml' in the current directory" )) }