feat: working cuddle actions,

although I am not entirely happy with it, as we're missing args, state and project variables

Signed-off-by: kjuulh <contact@kjuulh.io>
This commit is contained in:
Kasper Juul Hermansen 2024-08-26 22:02:50 +02:00
parent 186f13a16c
commit 62a677ffcd
Signed by: kjuulh
GPG Key ID: D85D7535F18F35FA
3 changed files with 89 additions and 26 deletions

View File

@ -5,7 +5,7 @@ version.workspace = true
[dependencies] [dependencies]
anyhow.workspace = true anyhow.workspace = true
clap.workspace = true clap = { workspace = true, features = ["string"] }
serde.workspace = true serde.workspace = true
serde_json.workspace = true serde_json.workspace = true

View File

@ -6,7 +6,7 @@ use serde::Serialize;
// Fix design make it so that it works like axum! // Fix design make it so that it works like axum!
type ActionFn = dyn Fn() + 'static; type ActionFn = dyn Fn() -> anyhow::Result<()> + 'static;
struct Action { struct Action {
name: String, name: String,
@ -38,7 +38,7 @@ impl CuddleActions {
options: &AddActionOptions, options: &AddActionOptions,
) -> &mut Self ) -> &mut Self
where where
F: Fn() + 'static, F: Fn() -> anyhow::Result<()> + 'static,
{ {
self.actions.insert( self.actions.insert(
name.into(), name.into(),
@ -53,7 +53,36 @@ impl CuddleActions {
} }
pub fn execute(&mut self) -> anyhow::Result<()> { pub fn execute(&mut self) -> anyhow::Result<()> {
let output = self.execute_from(std::env::args())?; self.execute_from(std::env::args())?;
Ok(())
}
pub fn execute_from<I, T>(&mut self, items: I) -> anyhow::Result<()>
where
I: IntoIterator<Item = T>,
T: Into<OsString> + Clone,
{
let mut do_cmd = clap::Command::new("do").subcommand_required(true);
for action in self.actions.values() {
let mut do_action_cmd = clap::Command::new(action.name.clone());
if let Some(description) = &action.description {
do_action_cmd = do_action_cmd.about(description.clone());
}
do_cmd = do_cmd.subcommand(do_action_cmd);
}
let root = clap::Command::new("cuddle-action")
.subcommand_required(true)
.subcommand(clap::Command::new("schema"))
.subcommand(do_cmd);
let matches = root.try_get_matches_from(items)?;
match matches.subcommand().expect("subcommand to be required") {
("schema", _args) => {
let output = self.get_pretty_actions()?;
// Write all stdout to buffer // Write all stdout to buffer
std::io::stdout().write_all(output.as_bytes())?; std::io::stdout().write_all(output.as_bytes())?;
@ -61,25 +90,26 @@ impl CuddleActions {
Ok(()) Ok(())
} }
("do", args) => {
let (command_name, _args) = args.subcommand().unwrap();
match self.actions.get_mut(command_name) {
Some(action) => (*action.f)(),
None => {
anyhow::bail!("command not found: {}", command_name);
}
}
}
_ => anyhow::bail!("no command found"),
}
}
pub fn execute_from<I, T>(&mut self, items: I) -> anyhow::Result<String> pub fn get_pretty_actions(&self) -> anyhow::Result<String> {
where
I: IntoIterator<Item = T>,
T: Into<OsString> + Clone,
{
let root = clap::Command::new("cuddle-action")
.subcommand_required(true)
.subcommand(clap::Command::new("schema"));
let matches = root.get_matches_from(items);
match matches.subcommand().expect("subcommand to be required") {
("schema", _args) => {
let schema = self let schema = self
.actions .actions
.values() .values()
.map(|a| ActionSchema { .map(|a| ActionSchema {
name: &a.name, name: &a.name,
description: a.description.as_ref().map(|d| d.as_str()), description: a.description.as_deref(),
}) })
.collect::<Vec<_>>(); .collect::<Vec<_>>();
@ -87,7 +117,4 @@ impl CuddleActions {
Ok(output) Ok(output)
} }
_ => Ok("".into()),
}
}
} }

View File

@ -3,8 +3,7 @@ use pretty_assertions::assert_eq;
#[test] #[test]
fn test_can_schema_no_actions() -> anyhow::Result<()> { fn test_can_schema_no_actions() -> anyhow::Result<()> {
let output = let output = cuddle_actions::CuddleActions::default().get_pretty_actions()?;
cuddle_actions::CuddleActions::default().execute_from(vec!["cuddle-actions", "schema"])?;
assert_eq!("[]", &output); assert_eq!("[]", &output);
@ -14,8 +13,8 @@ fn test_can_schema_no_actions() -> anyhow::Result<()> {
#[test] #[test]
fn test_can_schema_simple_action() -> anyhow::Result<()> { fn test_can_schema_simple_action() -> anyhow::Result<()> {
let output = cuddle_actions::CuddleActions::default() let output = cuddle_actions::CuddleActions::default()
.add_action("something", || {}, &AddActionOptions::default()) .add_action("something", || Ok(()), &AddActionOptions::default())
.execute_from(vec!["cuddle-actions", "schema"])?; .get_pretty_actions()?;
assert_eq!( assert_eq!(
r#"[ r#"[
@ -29,3 +28,40 @@ fn test_can_schema_simple_action() -> anyhow::Result<()> {
Ok(()) Ok(())
} }
#[test]
fn test_can_call_simple_action() -> anyhow::Result<()> {
cuddle_actions::CuddleActions::default()
.add_action("something", || Ok(()), &AddActionOptions::default())
.execute_from(vec!["cuddle-actions", "do", "something"])?;
Ok(())
}
#[test]
fn test_can_fail_on_unknown_command() -> anyhow::Result<()> {
let res = cuddle_actions::CuddleActions::default().execute_from(vec![
"cuddle-actions",
"do",
"something",
]);
assert!(res.is_err());
Ok(())
}
#[test]
fn test_can_cmd_can_fail() -> anyhow::Result<()> {
let res = cuddle_actions::CuddleActions::default()
.add_action(
"something",
|| anyhow::bail!("failed to run cmd"),
&AddActionOptions::default(),
)
.execute_from(vec!["cuddle-actions", "do", "something"]);
assert!(res.is_err());
Ok(())
}