use std::{ collections::HashMap, env::{self, current_dir}, ffi::OsStr, path::{Path, PathBuf}, sync::{Arc, Mutex}, }; use git2::{build::RepoBuilder, FetchOptions, RemoteCallbacks}; use serde::Deserialize; #[derive(Debug, Clone, PartialEq, Deserialize)] #[serde(untagged)] enum CuddleBase { Bool(bool), String(String), } #[derive(Debug, Clone, PartialEq, Deserialize)] struct CuddleShellScript {} #[derive(Debug, Clone, PartialEq, Deserialize)] struct CuddleDaggerScript {} #[derive(Debug, Clone, PartialEq, Deserialize)] #[serde(tag = "type")] enum CuddleScript { #[serde(alias = "shell")] Shell(CuddleShellScript), #[serde(alias = "dagger")] Dagger(CuddleDaggerScript), } #[derive(Debug, Clone, PartialEq, Deserialize)] struct CuddlePlan { pub base: CuddleBase, pub scripts: Option>, } #[derive(Debug)] struct CuddleContext { pub plan: CuddlePlan, pub path: PathBuf, } fn main() -> anyhow::Result<()> { let mut curr_dir = current_dir()?; curr_dir.push(".cuddle/"); if let Err(res) = std::fs::remove_dir_all(curr_dir) { println!("{}", res); } // Load main cuddle file let cuddle_yaml = find_root_cuddle()?; // TODO: Set trace println!("{}", cuddle_yaml); let cuddle_plan = serde_yaml::from_str::(cuddle_yaml.as_str())?; // TODO: Set debug println!("{:?}", 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) => { println!("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"); pull_parent_cuddle_into_local(parent_plan, cuddle_dest.clone())?; recurse_parent(cuddle_dest, context.clone())?; } } if let Ok(ctx) = context.lock() { println!("{:?}", ctx) } else { return Err(anyhow::anyhow!("could not acquire lock")); } Ok(()) } fn create_cuddle_local() -> anyhow::Result { let mut curr_dir = current_dir()?; curr_dir.push(".cuddle/"); if curr_dir.exists() { println!(".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() { println!(".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)?; println!("pulled: {}", parent_cuddle); 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) => { println!("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" )) }