// Cuddle actions is a two part action, it is called from cuddle itself, second the cli uses a provided sdk to expose functionality use std::{collections::BTreeMap, ffi::OsString, io::Write}; use serde::Serialize; pub use cuddle_actions_sdk_derive::Actions; // Fix design make it so that it works like axum! type ActionFn = dyn Fn() -> anyhow::Result<()> + 'static; struct Action { name: String, description: Option, f: Box, } #[derive(Clone, Debug, Serialize)] struct ActionSchema<'a> { name: &'a str, description: Option<&'a str>, } #[derive(Clone, Debug, Serialize)] struct ActionsSchema<'a> { actions: Vec>, } #[derive(Default)] pub struct CuddleActions { actions: BTreeMap, } #[derive(Default, Debug)] pub struct AddActionOptions { description: Option, } impl CuddleActions { pub fn add_action( &mut self, name: &str, action_fn: F, options: &AddActionOptions, ) -> &mut Self where F: Fn() -> anyhow::Result<()> + 'static, { self.actions.insert( name.into(), Action { name: name.into(), description: options.description.clone(), f: Box::new(action_fn), }, ); self } pub fn execute(&mut self) -> anyhow::Result<()> { self.execute_from(std::env::args())?; Ok(()) } pub fn execute_from(&mut self, items: I) -> anyhow::Result<()> where I: IntoIterator, T: Into + 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 std::io::stdout().write_all(output.as_bytes())?; std::io::stdout().write_all("\n".as_bytes())?; 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 get_pretty_actions(&self) -> anyhow::Result { let schema = self .actions .values() .map(|a| ActionSchema { name: &a.name, description: a.description.as_deref(), }) .collect::>(); let output = serde_json::to_string_pretty(&ActionsSchema { actions: schema })?; Ok(output) } }