Added cli for subcommands

This commit is contained in:
Kasper Juul Hermansen 2022-08-10 12:34:04 +02:00
parent 6f1f21f710
commit f5e1e3027a
Signed by: kjuulh
GPG Key ID: 0F95C140730F2F23
9 changed files with 399 additions and 199 deletions

72
Cargo.lock generated
View File

@ -8,6 +8,17 @@ version = "1.0.60"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c794e162a5eff65c72ef524dfe393eb923c354e350bb78b9c7383df13f3bc142" 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]] [[package]]
name = "autocfg" name = "autocfg"
version = "1.1.0" version = "1.1.0"
@ -39,11 +50,36 @@ version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 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]] [[package]]
name = "cuddle_cli" name = "cuddle_cli"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"clap",
"git2", "git2",
"serde", "serde",
"serde_yaml", "serde_yaml",
@ -81,6 +117,15 @@ version = "0.12.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" 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]] [[package]]
name = "idna" name = "idna"
version = "0.2.3" version = "0.2.3"
@ -197,6 +242,12 @@ dependencies = [
"vcpkg", "vcpkg",
] ]
[[package]]
name = "os_str_bytes"
version = "6.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "648001efe5d5c0102d8cea768e348da85d90af8ba91f0bea908f157951493cd4"
[[package]] [[package]]
name = "percent-encoding" name = "percent-encoding"
version = "2.1.0" version = "2.1.0"
@ -275,6 +326,12 @@ dependencies = [
"unsafe-libyaml", "unsafe-libyaml",
] ]
[[package]]
name = "strsim"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
[[package]] [[package]]
name = "syn" name = "syn"
version = "1.0.99" version = "1.0.99"
@ -286,6 +343,21 @@ dependencies = [
"unicode-ident", "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]] [[package]]
name = "tinyvec" name = "tinyvec"
version = "1.6.0" version = "1.6.0"

5
\ Normal file
View File

@ -0,0 +1,5 @@
#[derive(Debug)]
pub struct CuddleContext {
pub plan: CuddlePlan,
pub path: PathBuf,
}

View File

@ -11,3 +11,4 @@ serde = { version = "1.0.143", features = ["derive"] }
serde_yaml = "0.9.4" serde_yaml = "0.9.4"
walkdir = "2.3.2" walkdir = "2.3.2"
git2 = { version = "0.15.0", features = ["ssh"] } git2 = { version = "0.15.0", features = ["ssh"] }
clap = "3.2.16"

97
cuddle_cli/src/cli.rs Normal file
View File

@ -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<CuddleAction>,
context: Arc<Mutex<Vec<CuddleContext>>>,
command: Option<Command<'a>>,
}
impl<'a> CuddleCli<'a> {
pub fn new(context: Arc<Mutex<Vec<CuddleContext>>>) -> anyhow::Result<CuddleCli<'a>> {
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 <contact@kasperhermansen.com>")
.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
}
}

179
cuddle_cli/src/context.rs Normal file
View File

@ -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<Arc<Mutex<Vec<CuddleContext>>>> {
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::<CuddlePlan>(cuddle_yaml.as_str())?;
// TODO: Set debug
println!("{:?}", 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) => {
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<PathBuf> {
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<PathBuf> {
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<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) => {
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<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"
))
}

View File

@ -1,205 +1,13 @@
use std::{ mod cli;
collections::HashMap, mod context;
env::{self, current_dir}, mod model;
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<HashMap<String, CuddleScript>>,
}
#[derive(Debug)]
struct CuddleContext {
pub plan: CuddlePlan,
pub path: PathBuf,
}
fn main() -> anyhow::Result<()> { fn main() -> anyhow::Result<()> {
let mut curr_dir = current_dir()?; let context = context::extract_cuddle()?;
curr_dir.push(".cuddle/"); let mut cuddle_cli = cli::CuddleCli::new(context.clone())?;
if let Err(res) = std::fs::remove_dir_all(curr_dir) { cuddle_cli = cuddle_cli.execute();
println!("{}", res);
}
// Load main cuddle file println!("{:?}", cuddle_cli);
let cuddle_yaml = find_root_cuddle()?;
// TODO: Set trace
println!("{}", cuddle_yaml);
let cuddle_plan = serde_yaml::from_str::<CuddlePlan>(cuddle_yaml.as_str())?;
// TODO: Set debug
println!("{:?}", 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) => {
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(()) Ok(())
} }
fn create_cuddle_local() -> anyhow::Result<PathBuf> {
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<PathBuf> {
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<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) => {
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<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"
))
}

34
cuddle_cli/src/model.rs Normal file
View File

@ -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<String>,
}
#[derive(Debug, Clone, PartialEq, Deserialize)]
pub struct CuddleDaggerScript {
pub description: Option<String>,
}
#[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<HashMap<String, CuddleScript>>,
}

View File

@ -5,3 +5,4 @@ base: "git@git.front.kjuulh.io:kjuulh/cuddle-rust-plan.git"
scripts: scripts:
build: build:
type: shell type: shell
description: "build rust plan"

View File

@ -31,6 +31,9 @@
"shell", "shell",
"dagger" "dagger"
] ]
},
"description": {
"type": "string"
} }
} }
} }