cuddle-v2/cuddle_cli/src/context.rs
2022-08-10 17:33:31 +02:00

179 lines
5.1 KiB
Rust

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<Arc<Mutex<Vec<CuddleContext>>>> {
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::<CuddlePlan>(cuddle_yaml.as_str())?;
log::debug!(cuddle_plan=log::as_debug!(cuddle_yaml); "parse cuddle plan");
let context: Arc<Mutex<Vec<CuddleContext>>> = 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<PathBuf> {
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<PathBuf> {
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<Mutex<Vec<CuddleContext>>>) -> anyhow::Result<()> {
let cuddle_contents = find_cuddle(path.clone())?;
let cuddle_plan = serde_yaml::from_str::<CuddlePlan>(&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<String> {
// TODO: Make recursive towards root
let current_dir = env::current_dir()?;
find_cuddle(current_dir)
}
fn find_cuddle(path: PathBuf) -> anyhow::Result<String> {
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"
))
}