diff --git a/Cargo.lock b/Cargo.lock index 4110862..257f14e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8,6 +8,17 @@ version = "1.0.60" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c794e162a5eff65c72ef524dfe393eb923c354e350bb78b9c7383df13f3bc142" +[[package]] +name = "atty" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" +dependencies = [ + "hermit-abi", + "libc", + "winapi", +] + [[package]] name = "autocfg" version = "1.1.0" @@ -39,11 +50,36 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "clap" +version = "3.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3dbbb6653e7c55cc8595ad3e1f7be8f32aba4eb7ff7f0fd1163d4f3d137c0a9" +dependencies = [ + "atty", + "bitflags", + "clap_lex", + "indexmap", + "strsim", + "termcolor", + "textwrap", +] + +[[package]] +name = "clap_lex" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2850f2f5a82cbf437dd5af4d49848fbdfc27c157c3d010345776f952765261c5" +dependencies = [ + "os_str_bytes", +] + [[package]] name = "cuddle_cli" version = "0.1.0" dependencies = [ "anyhow", + "clap", "git2", "serde", "serde_yaml", @@ -81,6 +117,15 @@ version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" +[[package]] +name = "hermit-abi" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" +dependencies = [ + "libc", +] + [[package]] name = "idna" version = "0.2.3" @@ -197,6 +242,12 @@ dependencies = [ "vcpkg", ] +[[package]] +name = "os_str_bytes" +version = "6.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "648001efe5d5c0102d8cea768e348da85d90af8ba91f0bea908f157951493cd4" + [[package]] name = "percent-encoding" version = "2.1.0" @@ -275,6 +326,12 @@ dependencies = [ "unsafe-libyaml", ] +[[package]] +name = "strsim" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" + [[package]] name = "syn" version = "1.0.99" @@ -286,6 +343,21 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "termcolor" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bab24d30b911b2376f3a13cc2cd443142f0c81dda04c118693e35b3835757755" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "textwrap" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1141d4d61095b28419e22cb0bbf02755f5e54e0526f97f1e3d1d160e60885fb" + [[package]] name = "tinyvec" version = "1.6.0" diff --git "a/\\" "b/\\" new file mode 100644 index 0000000..0c29557 --- /dev/null +++ "b/\\" @@ -0,0 +1,5 @@ +#[derive(Debug)] +pub struct CuddleContext { + pub plan: CuddlePlan, + pub path: PathBuf, +} diff --git a/cuddle_cli/Cargo.toml b/cuddle_cli/Cargo.toml index e9a4d8d..c2ebe7e 100644 --- a/cuddle_cli/Cargo.toml +++ b/cuddle_cli/Cargo.toml @@ -11,3 +11,4 @@ serde = { version = "1.0.143", features = ["derive"] } serde_yaml = "0.9.4" walkdir = "2.3.2" git2 = { version = "0.15.0", features = ["ssh"] } +clap = "3.2.16" diff --git a/cuddle_cli/src/cli.rs b/cuddle_cli/src/cli.rs new file mode 100644 index 0000000..7816950 --- /dev/null +++ b/cuddle_cli/src/cli.rs @@ -0,0 +1,97 @@ +use std::{ + path::PathBuf, + sync::{Arc, Mutex}, +}; + +use clap::Command; + +use crate::{context::CuddleContext, model::CuddleScript}; + +#[derive(Debug, Clone)] +#[allow(dead_code)] +struct CuddleAction { + script: CuddleScript, + path: PathBuf, + name: String, +} +#[allow(dead_code)] +impl CuddleAction { + pub fn new(script: CuddleScript, path: PathBuf, name: String) -> Self { + Self { script, path, name } + } + + pub fn execute(self) { + match self.script { + CuddleScript::Shell(s) => {} + CuddleScript::Dagger(d) => {} + } + } +} + +#[derive(Debug, Clone)] +pub struct CuddleCli<'a> { + scripts: Vec, + context: Arc>>, + command: Option>, +} + +impl<'a> CuddleCli<'a> { + pub fn new(context: Arc>>) -> anyhow::Result> { + let mut cli = CuddleCli { + scripts: vec![], + context: context.clone(), + command: None, + }; + + cli = cli.process_scripts().build_cli(); + + Ok(cli) + } + + fn process_scripts(mut self) -> Self { + if let Ok(context_iter) = self.context.clone().lock() { + for ctx in context_iter.iter() { + if let Some(scripts) = ctx.plan.scripts.clone() { + for (name, script) in scripts { + self.scripts + .push(CuddleAction::new(script.clone(), ctx.path.clone(), name)) + } + } + } + } + + self + } + + fn build_cli(mut self) -> Self { + let mut root_cmd = Command::new("cuddle") + .version("1.0") + .author("kjuulh ") + .about("cuddle is your domain specific organization tool. It enabled widespread sharing through repositories, as well as collaborating while maintaining speed and integrity") + .propagate_version(true) + .arg_required_else_help(true); + + let mut execute_cmd = Command::new("x").about("x is your entry into your domains scripts, scripts inherited from parents will also be present here"); + + for script in self.scripts.iter() { + let action_cmd = Command::new(script.name.clone()); + + // TODO: Some way to add an about for clap, requires conversion from String -> &str + execute_cmd = execute_cmd.subcommand(action_cmd); + } + + root_cmd = root_cmd.subcommand(execute_cmd); + + self.command = Some(root_cmd); + + self + } + + pub fn execute(self) -> Self { + if let Some(cli) = self.command.clone() { + let _ = cli.get_matches(); + } + + self + } +} diff --git a/cuddle_cli/src/context.rs b/cuddle_cli/src/context.rs new file mode 100644 index 0000000..dba0e60 --- /dev/null +++ b/cuddle_cli/src/context.rs @@ -0,0 +1,179 @@ +use std::{ + env::{self, current_dir}, + ffi::OsStr, + path::{Path, PathBuf}, + sync::{Arc, Mutex}, +}; + +use git2::{build::RepoBuilder, FetchOptions, RemoteCallbacks}; + +use crate::model::{CuddleBase, CuddlePlan}; + +#[derive(Debug)] +pub struct CuddleContext { + pub plan: CuddlePlan, + pub path: PathBuf, +} + +pub fn extract_cuddle() -> 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.clone().lock() { + // TODO: set trace + println!("{:?}", ctx) + } else { + return Err(anyhow::anyhow!("could not acquire lock")); + } + + Ok(context) +} + +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" + )) +} diff --git a/cuddle_cli/src/main.rs b/cuddle_cli/src/main.rs index d069101..c01aea3 100644 --- a/cuddle_cli/src/main.rs +++ b/cuddle_cli/src/main.rs @@ -1,205 +1,13 @@ -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, -} +mod cli; +mod context; +mod model; 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); - } + let context = context::extract_cuddle()?; + let mut cuddle_cli = cli::CuddleCli::new(context.clone())?; + cuddle_cli = cuddle_cli.execute(); - // 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")); - } + println!("{:?}", cuddle_cli); 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" - )) -} diff --git a/cuddle_cli/src/model.rs b/cuddle_cli/src/model.rs new file mode 100644 index 0000000..020d8a4 --- /dev/null +++ b/cuddle_cli/src/model.rs @@ -0,0 +1,34 @@ +use std::collections::HashMap; + +use serde::Deserialize; + +#[derive(Debug, Clone, PartialEq, Deserialize)] +#[serde(untagged)] +pub enum CuddleBase { + Bool(bool), + String(String), +} + +#[derive(Debug, Clone, PartialEq, Deserialize)] +pub struct CuddleShellScript { + pub description: Option, +} +#[derive(Debug, Clone, PartialEq, Deserialize)] +pub struct CuddleDaggerScript { + pub description: Option, +} + +#[derive(Debug, Clone, PartialEq, Deserialize)] +#[serde(tag = "type")] +pub enum CuddleScript { + #[serde(alias = "shell")] + Shell(CuddleShellScript), + #[serde(alias = "dagger")] + Dagger(CuddleDaggerScript), +} + +#[derive(Debug, Clone, PartialEq, Deserialize)] +pub struct CuddlePlan { + pub base: CuddleBase, + pub scripts: Option>, +} diff --git a/examples/base/cuddle.yaml b/examples/base/cuddle.yaml index 4e2437b..b175ef4 100644 --- a/examples/base/cuddle.yaml +++ b/examples/base/cuddle.yaml @@ -5,3 +5,4 @@ base: "git@git.front.kjuulh.io:kjuulh/cuddle-rust-plan.git" scripts: build: type: shell + description: "build rust plan" diff --git a/schemas/base.json b/schemas/base.json index 27d84e9..49690ad 100644 --- a/schemas/base.json +++ b/schemas/base.json @@ -31,6 +31,9 @@ "shell", "dagger" ] + }, + "description": { + "type": "string" } } }