diff --git a/Cargo.lock b/Cargo.lock index 56f820d..d1074ec 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -183,6 +183,23 @@ dependencies = [ "uuid", ] +[[package]] +name = "cuddle-actions" +version = "0.1.0" +dependencies = [ + "anyhow", + "clap", + "pretty_assertions", + "serde", + "serde_json", +] + +[[package]] +name = "diff" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8" + [[package]] name = "dotenv" version = "0.15.0" @@ -373,6 +390,16 @@ version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02" +[[package]] +name = "pretty_assertions" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af7cee1a6c8a5b9208b3cb1061f10c0cb689087b3d8ce85fb9d2dd7a29b6ba66" +dependencies = [ + "diff", + "yansi", +] + [[package]] name = "proc-macro2" version = "1.0.86" @@ -783,3 +810,9 @@ checksum = "68a9bda4691f099d435ad181000724da8e5899daa10713c2d432552b9ccd3a6f" dependencies = [ "memchr", ] + +[[package]] +name = "yansi" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09041cd90cf85f7f8b2df60c646f853b7f535ce68f85244eb6731cf89fa498ec" diff --git a/Cargo.toml b/Cargo.toml index 32856b3..3592167 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,3 +14,6 @@ tracing = { version = "0.1", features = ["log"] } tracing-subscriber = { version = "0.3.18" } clap = { version = "4", features = ["derive", "env"] } dotenv = { version = "0.15" } +serde = { version = "1.0.197", features = ["derive"] } +serde_json = "1.0.127" +uuid = { version = "1.7.0", features = ["v4"] } diff --git a/crates/cuddle-actions/Cargo.toml b/crates/cuddle-actions/Cargo.toml new file mode 100644 index 0000000..2f51434 --- /dev/null +++ b/crates/cuddle-actions/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "cuddle-actions" +edition = "2021" +version.workspace = true + +[dependencies] +anyhow.workspace = true +clap.workspace = true +serde.workspace = true +serde_json.workspace = true + +[dev-dependencies] +pretty_assertions = "1.4.0" diff --git a/crates/cuddle-actions/src/lib.rs b/crates/cuddle-actions/src/lib.rs new file mode 100644 index 0000000..4ebb3e6 --- /dev/null +++ b/crates/cuddle-actions/src/lib.rs @@ -0,0 +1,93 @@ +// 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; + +// Fix design make it so that it works like axum! + +type ActionFn = dyn Fn() + 'static; + +struct Action { + name: String, + description: Option, + f: Box, +} + +#[derive(Clone, Debug, Serialize)] +struct ActionSchema<'a> { + name: &'a str, + description: Option<&'a str>, +} + +#[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() + '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<()> { + let output = self.execute_from(std::env::args())?; + + // Write all stdout to buffer + std::io::stdout().write_all(output.as_bytes())?; + std::io::stdout().write_all("\n".as_bytes())?; + + Ok(()) + } + + pub fn execute_from(&mut self, items: I) -> anyhow::Result + where + I: IntoIterator, + T: Into + 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 + .actions + .values() + .map(|a| ActionSchema { + name: &a.name, + description: a.description.as_ref().map(|d| d.as_str()), + }) + .collect::>(); + + let output = serde_json::to_string_pretty(&schema)?; + + Ok(output) + } + _ => Ok("".into()), + } + } +} diff --git a/crates/cuddle-actions/tests/mod.rs b/crates/cuddle-actions/tests/mod.rs new file mode 100644 index 0000000..7940278 --- /dev/null +++ b/crates/cuddle-actions/tests/mod.rs @@ -0,0 +1,31 @@ +use cuddle_actions::AddActionOptions; +use pretty_assertions::assert_eq; + +#[test] +fn test_can_schema_no_actions() -> anyhow::Result<()> { + let output = + cuddle_actions::CuddleActions::default().execute_from(vec!["cuddle-actions", "schema"])?; + + assert_eq!("[]", &output); + + Ok(()) +} + +#[test] +fn test_can_schema_simple_action() -> anyhow::Result<()> { + let output = cuddle_actions::CuddleActions::default() + .add_action("something", || {}, &AddActionOptions::default()) + .execute_from(vec!["cuddle-actions", "schema"])?; + + assert_eq!( + r#"[ + { + "name": "something", + "description": null + } +]"#, + &output + ); + + Ok(()) +} diff --git a/crates/cuddle/Cargo.toml b/crates/cuddle/Cargo.toml index 0fc8a20..718e49a 100644 --- a/crates/cuddle/Cargo.toml +++ b/crates/cuddle/Cargo.toml @@ -11,9 +11,9 @@ tracing.workspace = true tracing-subscriber.workspace = true clap.workspace = true dotenv.workspace = true +serde.workspace = true +serde_json.workspace = true +uuid.workspace = true -serde = { version = "1.0.197", features = ["derive"] } -uuid = { version = "1.7.0", features = ["v4"] } toml = "0.8.19" fs_extra = "1.3.0" -serde_json = "1.0.127" diff --git a/crates/cuddle/examples/schema/plan/.nickel/doc/schema.json b/crates/cuddle/examples/schema/plan/.nickel/doc/schema.json new file mode 100644 index 0000000..e39156f --- /dev/null +++ b/crates/cuddle/examples/schema/plan/.nickel/doc/schema.json @@ -0,0 +1 @@ +{"ProjectSchema":{"fields":{"name":{"fields":null,"type":null,"contracts":["String"],"documentation":null}},"type":null,"contracts":[],"documentation":null}} \ No newline at end of file