diff --git a/Cargo.lock b/Cargo.lock index 31a7cf4..060de00 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,24 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "aho-corasick" +version = "0.7.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f" +dependencies = [ + "memchr", +] + +[[package]] +name = "android_system_properties" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7ed72e1635e121ca3e79420540282af22da58be50de153d36f81ddc6b83aa9e" +dependencies = [ + "libc", +] + [[package]] name = "ansi_term" version = "0.12.1" @@ -44,6 +62,30 @@ version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" +[[package]] +name = "block-buffer" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bf7fe51849ea569fd452f37822f606a5cabb684dc918707a0193fd4664ff324" +dependencies = [ + "generic-array", +] + +[[package]] +name = "bstr" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba3569f383e8f1598449f1a423e72e99569137b47740b1da11ef19af3d5c3223" +dependencies = [ + "memchr", +] + +[[package]] +name = "bumpalo" +version = "3.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37ccbd214614c6783386c1af30caf03192f17891059cecc394b4fb119e363de3" + [[package]] name = "cc" version = "1.0.73" @@ -59,6 +101,40 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "chrono" +version = "0.4.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfd4d1b31faaa3a89d7934dbded3111da0d2ef28e3ebccdb4f0179f5929d1ef1" +dependencies = [ + "iana-time-zone", + "num-integer", + "num-traits", + "winapi", +] + +[[package]] +name = "chrono-tz" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29c39203181991a7dd4343b8005bd804e7a9a37afb8ac070e43771e8c820bbde" +dependencies = [ + "chrono", + "chrono-tz-build", + "phf", +] + +[[package]] +name = "chrono-tz-build" +version = "0.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f509c3a87b33437b05e2458750a0700e5bdd6956176773e6c7d6dd15a283a0c" +dependencies = [ + "parse-zoneinfo", + "phf", + "phf_codegen", +] + [[package]] name = "clap" version = "3.2.16" @@ -83,6 +159,41 @@ dependencies = [ "os_str_bytes", ] +[[package]] +name = "core-foundation-sys" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc" + +[[package]] +name = "cpufeatures" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59a6001667ab124aebae2a495118e11d30984c3a653e99d86d58971708cf5e4b" +dependencies = [ + "libc", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51887d4adc7b564537b15adcfb307936f8075dfcd5f00dde9a9f1d29383682bc" +dependencies = [ + "cfg-if", + "once_cell", +] + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + [[package]] name = "ctor" version = "0.1.23" @@ -106,11 +217,28 @@ dependencies = [ "openssl", "serde", "serde_yaml", + "tera", "tracing", "tracing-subscriber", "walkdir", ] +[[package]] +name = "deunicode" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "850878694b7933ca4c9569d30a34b55031b9b139ee1fc7b94a527c4ef960d690" + +[[package]] +name = "digest" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2fb860ca6fafa5552fb6d0e816a69c8e49f0908bf524e30a90d97c85892d506" +dependencies = [ + "block-buffer", + "crypto-common", +] + [[package]] name = "dirs" version = "4.0.0" @@ -151,6 +279,12 @@ dependencies = [ "syn", ] +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + [[package]] name = "foreign-types" version = "0.3.2" @@ -176,6 +310,16 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "generic-array" +version = "0.14.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bff49e947297f3312447abdca79f45f4738097cc82b06e72054d2223f601f1b9" +dependencies = [ + "typenum", + "version_check", +] + [[package]] name = "getrandom" version = "0.2.7" @@ -202,6 +346,30 @@ dependencies = [ "url", ] +[[package]] +name = "globset" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a1e17342619edbc21a964c2afbeb6c820c6a2560032872f397bb97ea127bd0a" +dependencies = [ + "aho-corasick", + "bstr", + "fnv", + "log", + "regex", +] + +[[package]] +name = "globwalk" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93e3af942408868f6934a7b85134a3230832b9977cf66125df2f9edcfce4ddcc" +dependencies = [ + "bitflags", + "ignore", + "walkdir", +] + [[package]] name = "hashbrown" version = "0.12.3" @@ -217,6 +385,25 @@ dependencies = [ "libc", ] +[[package]] +name = "humansize" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02296996cb8796d7c6e3bc2d9211b7802812d36999a51bb754123ead7d37d026" + +[[package]] +name = "iana-time-zone" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "808cf7d67cf4a22adc5be66e75ebdf769b3f2ea032041437a7061f97a63dad4b" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "js-sys", + "wasm-bindgen", + "winapi", +] + [[package]] name = "idna" version = "0.2.3" @@ -228,6 +415,24 @@ dependencies = [ "unicode-normalization", ] +[[package]] +name = "ignore" +version = "0.4.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "713f1b139373f96a2e0ce3ac931cd01ee973c3c5dd7c40c0c2efe96ad2b6751d" +dependencies = [ + "crossbeam-utils", + "globset", + "lazy_static", + "log", + "memchr", + "regex", + "same-file", + "thread_local", + "walkdir", + "winapi-util", +] + [[package]] name = "indexmap" version = "1.9.1" @@ -253,6 +458,15 @@ dependencies = [ "libc", ] +[[package]] +name = "js-sys" +version = "0.3.59" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "258451ab10b34f8af53416d1fdab72c22e805f0c92a1136d59470ec0b11138b2" +dependencies = [ + "wasm-bindgen", +] + [[package]] name = "lazy_static" version = "1.4.0" @@ -321,6 +535,31 @@ version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a3e378b66a060d48947b590737b30a1be76706c8dd7b8ba0f2fe3989c68a853f" +[[package]] +name = "memchr" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" + +[[package]] +name = "num-integer" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" +dependencies = [ + "autocfg", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" +dependencies = [ + "autocfg", +] + [[package]] name = "once_cell" version = "1.13.0" @@ -388,12 +627,104 @@ version = "6.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "648001efe5d5c0102d8cea768e348da85d90af8ba91f0bea908f157951493cd4" +[[package]] +name = "parse-zoneinfo" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c705f256449c60da65e11ff6626e0c16a0a0b96aaa348de61376b249bc340f41" +dependencies = [ + "regex", +] + [[package]] name = "percent-encoding" version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" +[[package]] +name = "pest" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69486e2b8c2d2aeb9762db7b4e00b0331156393555cff467f4163ff06821eef8" +dependencies = [ + "thiserror", + "ucd-trie", +] + +[[package]] +name = "pest_derive" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b13570633aff33c6d22ce47dd566b10a3b9122c2fe9d8e7501895905be532b91" +dependencies = [ + "pest", + "pest_generator", +] + +[[package]] +name = "pest_generator" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3c567e5702efdc79fb18859ea74c3eb36e14c43da7b8c1f098a4ed6514ec7a0" +dependencies = [ + "pest", + "pest_meta", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "pest_meta" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5eb32be5ee3bbdafa8c7a18b0a8a8d962b66cfa2ceee4037f49267a50ee821fe" +dependencies = [ + "once_cell", + "pest", + "sha-1", +] + +[[package]] +name = "phf" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "928c6535de93548188ef63bb7c4036bd415cd8f36ad25af44b9789b2ee72a48c" +dependencies = [ + "phf_shared", +] + +[[package]] +name = "phf_codegen" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a56ac890c5e3ca598bbdeaa99964edb5b0258a583a9eb6ef4e89fc85d9224770" +dependencies = [ + "phf_generator", + "phf_shared", +] + +[[package]] +name = "phf_generator" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1181c94580fa345f50f19d738aaa39c0ed30a600d95cb2d3e23f94266f14fbf" +dependencies = [ + "phf_shared", + "rand", +] + +[[package]] +name = "phf_shared" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1fb5f6f826b772a8d4c0394209441e7d37cbbb967ae9c7e0e8134365c9ee676" +dependencies = [ + "siphasher", + "uncased", +] + [[package]] name = "pin-project-lite" version = "0.2.9" @@ -406,6 +737,12 @@ version = "0.3.25" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1df8c4ec4b0627e53bdf214615ad287367e482558cf84b109250b37464dc03ae" +[[package]] +name = "ppv-lite86" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872" + [[package]] name = "proc-macro2" version = "1.0.43" @@ -424,6 +761,36 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7" +dependencies = [ + "getrandom", +] + [[package]] name = "redox_syscall" version = "0.2.16" @@ -444,6 +811,23 @@ dependencies = [ "thiserror", ] +[[package]] +name = "regex" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c4eb3267174b8c6c2f654116623910a0fef09c4753f8dd83db29c48a0df988b" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.6.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3f87b73ce11b1619a3c6332f45341e0047173771e8b8b73f87bfeefb7b56244" + [[package]] name = "ryu" version = "1.0.11" @@ -503,6 +887,17 @@ dependencies = [ "unsafe-libyaml", ] +[[package]] +name = "sha-1" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "028f48d513f9678cda28f6e4064755b3fbb2af6acd672f2c209b62323f7aea0f" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + [[package]] name = "sharded-slab" version = "0.1.4" @@ -512,6 +907,21 @@ dependencies = [ "lazy_static", ] +[[package]] +name = "siphasher" +version = "0.3.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7bd3e3206899af3f8b12af284fafc038cc1dc2b41d1b89dd17297221c5d225de" + +[[package]] +name = "slug" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3bc762e6a4b6c6fcaade73e77f9ebc6991b676f88bb2358bddb56560f073373" +dependencies = [ + "deunicode", +] + [[package]] name = "smallvec" version = "1.9.0" @@ -535,6 +945,28 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "tera" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d4685e72cb35f0eb74319c8fe2d3b61e93da5609841cde2cb87fcc3bea56d20" +dependencies = [ + "chrono", + "chrono-tz", + "globwalk", + "humansize", + "lazy_static", + "percent-encoding", + "pest", + "pest_derive", + "rand", + "regex", + "serde", + "serde_json", + "slug", + "unic-segment", +] + [[package]] name = "termcolor" version = "1.1.3" @@ -665,6 +1097,77 @@ dependencies = [ "tracing-serde", ] +[[package]] +name = "typenum" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcf81ac59edc17cc8697ff311e8f5ef2d99fcbd9817b34cec66f90b6c3dfd987" + +[[package]] +name = "ucd-trie" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89570599c4fe5585de2b388aab47e99f7fa4e9238a1399f707a02e356058141c" + +[[package]] +name = "uncased" +version = "0.9.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09b01702b0fd0b3fadcf98e098780badda8742d4f4a7676615cad90e8ac73622" +dependencies = [ + "version_check", +] + +[[package]] +name = "unic-char-property" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8c57a407d9b6fa02b4795eb81c5b6652060a15a7903ea981f3d723e6c0be221" +dependencies = [ + "unic-char-range", +] + +[[package]] +name = "unic-char-range" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0398022d5f700414f6b899e10b8348231abf9173fa93144cbc1a43b9793c1fbc" + +[[package]] +name = "unic-common" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80d7ff825a6a654ee85a63e80f92f054f904f21e7d12da4e22f9834a4aaa35bc" + +[[package]] +name = "unic-segment" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4ed5d26be57f84f176157270c112ef57b86debac9cd21daaabbe56db0f88f23" +dependencies = [ + "unic-ucd-segment", +] + +[[package]] +name = "unic-ucd-segment" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2079c122a62205b421f499da10f3ee0f7697f012f55b675e002483c73ea34700" +dependencies = [ + "unic-char-property", + "unic-char-range", + "unic-ucd-version", +] + +[[package]] +name = "unic-ucd-version" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96bd2f2237fe450fcd0a1d2f5f4e91711124f7857ba2e964247776ebeeb7b0c4" +dependencies = [ + "unic-common", +] + [[package]] name = "unicode-bidi" version = "0.3.8" @@ -749,6 +1252,60 @@ version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" +[[package]] +name = "wasm-bindgen" +version = "0.2.82" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc7652e3f6c4706c8d9cd54832c4a4ccb9b5336e2c3bd154d5cccfbf1c1f5f7d" +dependencies = [ + "cfg-if", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.82" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "662cd44805586bd52971b9586b1df85cdbbd9112e4ef4d8f41559c334dc6ac3f" +dependencies = [ + "bumpalo", + "log", + "once_cell", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.82" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b260f13d3012071dfb1512849c033b1925038373aea48ced3012c09df952c602" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.82" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5be8e654bdd9b79216c2929ab90721aa82faf65c48cdf08bdc4e7f51357b80da" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.82" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6598dd0bd3c7d51095ff6531a5b23e02acdc81804e30d8f07afb77b7215a140a" + [[package]] name = "winapi" version = "0.3.9" diff --git a/cuddle_cli/Cargo.toml b/cuddle_cli/Cargo.toml index 64db5e1..26ea04c 100644 --- a/cuddle_cli/Cargo.toml +++ b/cuddle_cli/Cargo.toml @@ -18,3 +18,4 @@ tracing = "0.1.36" tracing-subscriber = { version = "0.3.15", features = ["json"] } log = { version = "0.4.17", features = ["std", "kv_unstable"] } openssl = {version = "0.10", features = ["vendored"]} +tera = "1.17.0" diff --git a/cuddle_cli/src/actions/mod.rs b/cuddle_cli/src/actions/mod.rs index 327cf1b..4df62a6 100644 --- a/cuddle_cli/src/actions/mod.rs +++ b/cuddle_cli/src/actions/mod.rs @@ -1 +1,78 @@ +use std::path::PathBuf; + +use crate::{ + actions::shell::ShellAction, + model::{CuddleScript, CuddleShellScriptArg, CuddleVariable}, +}; + pub mod shell; + +#[derive(Debug, Clone)] +#[allow(dead_code)] +pub struct CuddleAction { + pub script: CuddleScript, + pub path: PathBuf, + pub 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, variables: Vec) -> anyhow::Result<()> { + match self.script { + CuddleScript::Shell(s) => { + let mut arg_variables: Vec = vec![]; + if let Some(args) = s.args { + for (k, v) in args { + let var = match v { + CuddleShellScriptArg::Env(e) => { + let env_var = match std::env::var(e.key.clone()) { + Ok(var) => var, + Err(e) => { + log::error!("env_variable not found: {}", k); + return Err(anyhow::anyhow!(e)); + } + }; + CuddleVariable::new(k.clone(), env_var) + } + }; + + arg_variables.push(var); + } + } else { + arg_variables = vec![] + }; + + let mut vars = variables.clone(); + vars.append(&mut arg_variables); + + log::trace!("preparing to run action"); + + match ShellAction::new( + self.name.clone(), + format!( + "{}/scripts/{}.sh", + self.path + .to_str() + .expect("action doesn't have a name, this should never happen"), + self.name + ), + ) + .execute(vars) + { + Ok(()) => { + log::trace!("finished running action"); + Ok(()) + } + Err(e) => { + log::error!("{}", e); + Err(e) + } + } + } + CuddleScript::Dagger(_d) => Err(anyhow::anyhow!("not implemented yet!")), + } + } +} diff --git a/cuddle_cli/src/actions/shell.rs b/cuddle_cli/src/actions/shell.rs index 312beb4..007353f 100644 --- a/cuddle_cli/src/actions/shell.rs +++ b/cuddle_cli/src/actions/shell.rs @@ -1,6 +1,6 @@ use std::{env::current_dir, path::PathBuf, process::Command}; -use crate::cli::CuddleVariable; +use crate::model::CuddleVariable; #[allow(dead_code)] pub struct ShellAction { diff --git a/cuddle_cli/src/cli.rs b/cuddle_cli/src/cli.rs deleted file mode 100644 index 8f3d661..0000000 --- a/cuddle_cli/src/cli.rs +++ /dev/null @@ -1,314 +0,0 @@ -use std::{ - env::{self, current_dir}, - path::PathBuf, - sync::{Arc, Mutex}, -}; - -use anyhow::anyhow; -use clap::Command; -use git2::Repository; - -use crate::{ - actions, - context::{CuddleContext, CuddleTreeType}, - model::{CuddleScript, CuddleShellScriptArg}, -}; - -#[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, variables: Vec) -> anyhow::Result<()> { - match self.script { - CuddleScript::Shell(s) => { - let mut arg_variables: Vec = vec![]; - if let Some(args) = s.args { - for (k, v) in args { - let var = match v { - CuddleShellScriptArg::Env(e) => { - let env_var = match env::var(e.key.clone()) { - Ok(var) => var, - Err(e) => { - log::error!("env_variable not found: {}", k); - return Err(anyhow::anyhow!(e)); - } - }; - CuddleVariable::new(k.clone(), env_var) - } - }; - - arg_variables.push(var); - } - } else { - arg_variables = vec![] - }; - - let mut vars = variables.clone(); - vars.append(&mut arg_variables); - - log::trace!("preparing to run action"); - - match actions::shell::ShellAction::new( - self.name.clone(), - format!( - "{}/scripts/{}.sh", - self.path - .to_str() - .expect("action doesn't have a name, this should never happen"), - self.name - ), - ) - .execute(vars) - { - Ok(()) => { - log::trace!("finished running action"); - Ok(()) - } - Err(e) => { - log::error!("{}", e); - Err(e) - } - } - } - CuddleScript::Dagger(_d) => Err(anyhow::anyhow!("not implemented yet!")), - } - } -} - -#[derive(Debug, Clone)] -#[allow(dead_code)] -pub struct CuddleVariable { - pub name: String, - pub value: String, -} - -impl CuddleVariable { - pub fn new(name: String, value: String) -> Self { - Self { name, value } - } -} - -#[derive(Debug)] -struct GitCommit { - commit_sha: String, -} - -impl GitCommit { - fn new() -> anyhow::Result { - let repo = Repository::open(current_dir().expect("having current_dir available")).map_err( - |e| { - log::debug!("{}", e); - anyhow::anyhow!("could not open repository") - }, - )?; - let head_ref = repo - .head() - .map_err(|e| { - log::warn!("{}", e); - anyhow::anyhow!("could not get HEAD") - })? - .target() - .ok_or(anyhow::anyhow!( - "could not extract head -> target to commit_sha" - ))?; - - Ok(Self { - commit_sha: head_ref.to_string(), - }) - } -} - -#[derive(Debug, Clone)] -pub struct CuddleCli<'a> { - scripts: Vec, - variables: Vec, - context: Arc>>, - command: Option>, - tmp_dir: Option, -} - -impl<'a> CuddleCli<'a> { - pub fn new(context: Arc>>) -> anyhow::Result> { - let mut cli = CuddleCli { - scripts: vec![], - variables: vec![], - context: context.clone(), - command: None, - tmp_dir: None, - }; - - cli = cli - .process_variables() - .process_scripts() - .process_templates()? - .build_cli(); - - Ok(cli) - } - - fn process_variables(mut self) -> Self { - if let Ok(context_iter) = self.context.clone().lock() { - for ctx in context_iter.iter() { - if let Some(variables) = ctx.plan.vars.clone() { - for (name, var) in variables { - self.variables.push(CuddleVariable::new(name, var)) - } - } - - if let CuddleTreeType::Root = ctx.node_type { - let mut temp_path = ctx.path.clone(); - temp_path.push(".cuddle/tmp/"); - - self.variables.push(CuddleVariable::new( - "tmp".into(), - temp_path.clone().to_string_lossy().to_string(), - )); - - self.tmp_dir = Some(temp_path); - } - } - } - - match GitCommit::new() { - Ok(commit) => self.variables.push(CuddleVariable::new( - "commit_sha".into(), - commit.commit_sha.clone(), - )), - Err(e) => { - log::debug!("{}", e); - } - } - - self - } - - 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 process_templates(self) -> anyhow::Result { - if let None = self.tmp_dir { - log::debug!("cannot process template as bare bones cli"); - return Ok(self); - } - - // Make sure tmp_dir exists and clean it up first - let tmp_dir = self - .tmp_dir - .clone() - .ok_or(anyhow::anyhow!("tmp_dir does not exist aborting"))?; - if tmp_dir.exists() && tmp_dir.ends_with("tmp") { - std::fs::remove_dir_all(tmp_dir.clone())?; - } - std::fs::create_dir_all(tmp_dir.clone())?; - // Handle all templating with variables and such. - // TODO: use actual templating engine, for new we just copy templates to the final folder - - if let Ok(context_iter) = self.context.clone().lock() { - for ctx in context_iter.iter() { - let mut template_path = ctx.path.clone(); - template_path.push("templates"); - - log::trace!("template path: {}", template_path.clone().to_string_lossy()); - - if !template_path.exists() { - continue; - } - - for file in std::fs::read_dir(template_path)?.into_iter() { - let f = file?; - let mut dest_file = tmp_dir.clone(); - dest_file.push(f.file_name()); - - std::fs::copy(f.path(), dest_file)?; - } - } - } - - Ok(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") - .subcommand_required(true) - .propagate_version(true); - - if self.scripts.len() > 0 { - 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").subcommand_required(true); - - 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) -> anyhow::Result { - if let Some(mut cli) = self.command.clone() { - let matches = cli.clone().get_matches(); - - let res = match matches.subcommand() { - Some(("x", exe_submatch)) => { - log::trace!("executing x"); - - match exe_submatch.subcommand() { - Some((name, _action_matches)) => { - log::trace!(action=name; "running action; name={}", name); - match self.scripts.iter().find(|ele| ele.name == name) { - Some(script) => { - script.clone().execute(self.variables.clone())?; - Ok(()) - } - _ => Err(anyhow!("could not find a match")), - } - } - _ => Err(anyhow!("could not find a match")), - } - } - _ => Err(anyhow!("could not find a match")), - }; - - match res { - Ok(()) => {} - Err(e) => { - let _ = cli.print_long_help(); - return Err(e); - } - } - } - - Ok(self) - } -} diff --git a/cuddle_cli/src/cli/mod.rs b/cuddle_cli/src/cli/mod.rs new file mode 100644 index 0000000..a780de9 --- /dev/null +++ b/cuddle_cli/src/cli/mod.rs @@ -0,0 +1,182 @@ +mod subcommands; + +use std::{ + path::PathBuf, + sync::{Arc, Mutex}, +}; + +use clap::Command; + +use crate::{ + actions::CuddleAction, + context::{CuddleContext, CuddleTreeType}, + model::*, + util::git::GitCommit, +}; + +use self::subcommands::render_template::RenderTemplateCommand; + +#[derive(Debug, Clone)] +pub struct CuddleCli<'a> { + scripts: Vec, + variables: Vec, + context: Arc>>, + command: Option>, + tmp_dir: Option, +} + +impl<'a> CuddleCli<'a> { + pub fn new(context: Arc>>) -> anyhow::Result> { + let mut cli = CuddleCli { + scripts: vec![], + variables: vec![], + context: context.clone(), + command: None, + tmp_dir: None, + }; + + cli = cli + .process_variables() + .process_scripts() + .process_templates()? + .build_cli(); + + Ok(cli) + } + + fn process_variables(mut self) -> Self { + if let Ok(context_iter) = self.context.clone().lock() { + for ctx in context_iter.iter() { + if let Some(variables) = ctx.plan.vars.clone() { + for (name, var) in variables { + self.variables.push(CuddleVariable::new(name, var)) + } + } + + if let CuddleTreeType::Root = ctx.node_type { + let mut temp_path = ctx.path.clone(); + temp_path.push(".cuddle/tmp"); + + self.variables.push(CuddleVariable::new( + "tmp".into(), + temp_path.clone().to_string_lossy().to_string(), + )); + + self.tmp_dir = Some(temp_path); + } + } + } + + match GitCommit::new() { + Ok(commit) => self.variables.push(CuddleVariable::new( + "commit_sha".into(), + commit.commit_sha.clone(), + )), + Err(e) => { + log::debug!("{}", e); + } + } + + self + } + + 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 process_templates(self) -> anyhow::Result { + if let None = self.tmp_dir { + log::debug!("cannot process template as bare bones cli"); + return Ok(self); + } + + // Make sure tmp_dir exists and clean it up first + let tmp_dir = self + .tmp_dir + .clone() + .ok_or(anyhow::anyhow!("tmp_dir does not exist aborting"))?; + if tmp_dir.exists() && tmp_dir.ends_with("tmp") { + std::fs::remove_dir_all(tmp_dir.clone())?; + } + std::fs::create_dir_all(tmp_dir.clone())?; + // Handle all templating with variables and such. + // TODO: use actual templating engine, for new we just copy templates to the final folder + + if let Ok(context_iter) = self.context.clone().lock() { + for ctx in context_iter.iter() { + let mut template_path = ctx.path.clone(); + template_path.push("templates"); + + log::trace!("template path: {}", template_path.clone().to_string_lossy()); + + if !template_path.exists() { + continue; + } + + for file in std::fs::read_dir(template_path)?.into_iter() { + let f = file?; + let mut dest_file = tmp_dir.clone(); + dest_file.push(f.file_name()); + + std::fs::copy(f.path(), dest_file)?; + } + } + } + + Ok(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") + .subcommand_required(true) + .arg_required_else_help(true) + .propagate_version(true); + + root_cmd = subcommands::x::build_command(root_cmd, self.clone()); + root_cmd = subcommands::render_template::build_command(root_cmd); + + self.command = Some(root_cmd); + + self + } + + pub fn execute(self) -> anyhow::Result { + if let Some(mut cli) = self.command.clone() { + let matches = cli.clone().get_matches(); + + let res = match matches.subcommand() { + Some(("x", exe_submatch)) => subcommands::x::execute_x(exe_submatch, self.clone()), + Some(("render_template", sub_matches)) => { + RenderTemplateCommand::from_matches(sub_matches, self.clone()) + .and_then(|cmd| cmd.execute())?; + Ok(()) + } + _ => Err(anyhow::anyhow!("could not find a match")), + }; + + match res { + Ok(()) => {} + Err(e) => { + let _ = cli.print_long_help(); + return Err(e); + } + } + } + + Ok(self) + } +} diff --git a/cuddle_cli/src/cli/subcommands/mod.rs b/cuddle_cli/src/cli/subcommands/mod.rs new file mode 100644 index 0000000..19ddcea --- /dev/null +++ b/cuddle_cli/src/cli/subcommands/mod.rs @@ -0,0 +1,2 @@ +pub mod render_template; +pub mod x; diff --git a/cuddle_cli/src/cli/subcommands/render_template.rs b/cuddle_cli/src/cli/subcommands/render_template.rs new file mode 100644 index 0000000..8d744f8 --- /dev/null +++ b/cuddle_cli/src/cli/subcommands/render_template.rs @@ -0,0 +1,150 @@ +use std::{path::PathBuf, str::FromStr}; + +use clap::{Arg, ArgMatches, Command}; + +use crate::{cli::CuddleCli, model::CuddleVariable}; + +pub fn build_command<'a>(root_cmd: Command<'a>) -> Command<'a> { + root_cmd.subcommand( + Command::new("render_template") + .about("renders a jinja compatible template") + .args(&[ + Arg::new("template-file") + .alias("template") + .short('t') + .long("template-file") + .required(true) + .action(clap::ArgAction::Set).long_help("template-file is the input file path of the .tmpl file (or inferred) that you would like to render"), + Arg::new("destination") + .alias("dest") + .short('d') + .long("destination") + .required(true) + .action(clap::ArgAction::Set) + .long_help("destination is the output path of the template once done, but default .tmpl is stripped and the normal file extension is used. this can be overwritten if a file path is entered instead. I.e. (/some/file/name.txt)"), + Arg::new("extra-var") + .long("extra-var") + .required(false) + .action(clap::ArgAction::Set), + ])) +} + +pub struct RenderTemplateCommand { + variables: Vec, + template_file: PathBuf, + destination: PathBuf, +} + +impl RenderTemplateCommand { + pub fn from_matches(matches: &ArgMatches, cli: CuddleCli) -> anyhow::Result { + let template_file = matches + .get_one::("template-file") + .ok_or(anyhow::anyhow!("template-file was not found")) + .and_then(get_path_buf_and_check_exists)?; + + let destination = matches + .get_one::("destination") + .ok_or(anyhow::anyhow!("destination was not found")) + .and_then(get_path_buf_and_check_dir_exists) + .and_then(RenderTemplateCommand::transform_extension)?; + + let mut extra_vars: Vec = + if let Some(extra_vars) = matches.get_many::("extra-var") { + let mut vars = Vec::with_capacity(extra_vars.len()); + for var in extra_vars.into_iter() { + let parts: Vec<&str> = var.split("=").collect(); + if parts.len() != 2 { + return Err(anyhow::anyhow!("extra-var: is not set correctly: {}", var)); + } + + vars.push(CuddleVariable::new(parts[0].into(), parts[1].into())); + } + vars + } else { + vec![] + }; + + extra_vars.append(&mut cli.variables.clone()); + + Ok(Self { + variables: extra_vars, + template_file, + destination, + }) + } + + pub fn execute(self) -> anyhow::Result<()> { + // Prepare context + let mut context = tera::Context::new(); + for var in self.variables { + context.insert( + var.name.to_lowercase().replace(" ", "_").replace("-", "_"), + &var.value, + ) + } + + // Load source template + let source = std::fs::read_to_string(self.template_file)?; + + let output = tera::Tera::one_off(source.as_str(), &context, false)?; + + // Put template in final destination + std::fs::write(&self.destination, output)?; + + log::info!( + "finished writing template to: {}", + &self.destination.to_string_lossy() + ); + + Ok(()) + } + + fn transform_extension(template_path: PathBuf) -> anyhow::Result { + if template_path.is_file() { + let ext = template_path.extension().ok_or(anyhow::anyhow!( + "destination path does not have an extension" + ))?; + if ext.to_string_lossy().ends_with("tmpl") { + let template_dest = template_path + .to_str() + .and_then(|s| s.strip_suffix(".tmpl")) + .ok_or(anyhow::anyhow!("string does not end in .tmpl"))?; + + return PathBuf::from_str(template_dest).map_err(|e| anyhow::anyhow!(e)); + } + } + + Ok(template_path) + } +} + +fn get_path_buf_and_check_exists(raw_path: &String) -> anyhow::Result { + match PathBuf::from_str(&raw_path) { + Ok(pb) => { + if pb.exists() { + Ok(pb) + } else { + Err(anyhow::anyhow!( + "path: {}, could not be found", + pb.to_string_lossy() + )) + } + } + Err(e) => Err(anyhow::anyhow!(e)), + } +} + +fn get_path_buf_and_check_dir_exists(raw_path: &String) -> anyhow::Result { + match PathBuf::from_str(&raw_path) { + Ok(pb) => { + if pb.is_dir() && pb.exists() { + Ok(pb) + } else if pb.is_file() { + Ok(pb) + } else { + Ok(pb) + } + } + Err(e) => Err(anyhow::anyhow!(e)), + } +} diff --git a/cuddle_cli/src/cli/subcommands/x.rs b/cuddle_cli/src/cli/subcommands/x.rs new file mode 100644 index 0000000..dc2fe12 --- /dev/null +++ b/cuddle_cli/src/cli/subcommands/x.rs @@ -0,0 +1,37 @@ +use clap::{ArgMatches, Command}; + +use crate::cli::CuddleCli; + +pub fn build_command<'a>(root_cmd: Command<'a>, cli: CuddleCli<'a>) -> Command<'a> { + if cli.scripts.len() > 0 { + 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").subcommand_required(true); + + for script in cli.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.subcommand(execute_cmd) + } else { + root_cmd + } +} + +pub fn execute_x(exe_submatch: &ArgMatches, cli: CuddleCli) -> anyhow::Result<()> { + match exe_submatch.subcommand() { + Some((name, _action_matches)) => { + log::trace!(action=name; "running action; name={}", name); + match cli.scripts.iter().find(|ele| ele.name == name) { + Some(script) => { + script.clone().execute(cli.variables.clone())?; + Ok(()) + } + _ => Err(anyhow::anyhow!("could not find a match")), + } + } + _ => Err(anyhow::anyhow!("could not find a match")), + } +} diff --git a/cuddle_cli/src/config.rs b/cuddle_cli/src/config.rs index cb88458..8a2325a 100644 --- a/cuddle_cli/src/config.rs +++ b/cuddle_cli/src/config.rs @@ -3,6 +3,7 @@ use envconfig::Envconfig; pub enum CuddleFetchPolicy { Always, Once, + Never, } #[derive(Envconfig, Clone)] @@ -20,6 +21,7 @@ impl CuddleConfig { match self.fetch_policy.clone().to_lowercase().as_str() { "always" => Ok(CuddleFetchPolicy::Always), "once" => Ok(CuddleFetchPolicy::Once), + "never" => Ok(CuddleFetchPolicy::Never), _ => Err(anyhow::anyhow!("could not parse fetch policy")), } } diff --git a/cuddle_cli/src/main.rs b/cuddle_cli/src/main.rs index c44d445..e410620 100644 --- a/cuddle_cli/src/main.rs +++ b/cuddle_cli/src/main.rs @@ -1,8 +1,3 @@ -use std::{ - env::current_dir, - sync::{Arc, Mutex}, -}; - use config::CuddleConfig; use tracing::Level; @@ -11,23 +6,15 @@ mod cli; mod config; mod context; mod model; +mod util; fn main() -> anyhow::Result<()> { init_logging()?; let config = CuddleConfig::from_env()?; - match git2::Repository::open(current_dir()?) { - Ok(_) => { - let context = context::extract_cuddle(config.clone())?; - _ = cli::CuddleCli::new(context)?.execute()?; - } - Err(_) => { - // Only build bare bones cli - log::info!("was not opened in a repo with git, only showing bare-bones options"); - _ = cli::CuddleCli::new(Arc::new(Mutex::new(vec![])))?.execute()?; - } - } + let context = context::extract_cuddle(config.clone())?; + _ = cli::CuddleCli::new(context)?.execute()?; Ok(()) } @@ -35,7 +22,7 @@ fn main() -> anyhow::Result<()> { fn init_logging() -> anyhow::Result<()> { tracing_subscriber::fmt() .pretty() - .with_max_level(Level::DEBUG) + .with_max_level(Level::INFO) .init(); Ok(()) diff --git a/cuddle_cli/src/model.rs b/cuddle_cli/src/model.rs index d870c70..00cb436 100644 --- a/cuddle_cli/src/model.rs +++ b/cuddle_cli/src/model.rs @@ -46,3 +46,16 @@ pub struct CuddlePlan { pub vars: Option>, pub scripts: Option>, } + +#[derive(Debug, Clone)] +#[allow(dead_code)] +pub struct CuddleVariable { + pub name: String, + pub value: String, +} + +impl CuddleVariable { + pub fn new(name: String, value: String) -> Self { + Self { name, value } + } +} diff --git a/cuddle_cli/src/util/git.rs b/cuddle_cli/src/util/git.rs new file mode 100644 index 0000000..d66b680 --- /dev/null +++ b/cuddle_cli/src/util/git.rs @@ -0,0 +1,33 @@ +use std::env::current_dir; + +use git2::Repository; + +#[derive(Debug)] +pub struct GitCommit { + pub commit_sha: String, +} + +impl GitCommit { + pub fn new() -> anyhow::Result { + let repo = Repository::open(current_dir().expect("having current_dir available")).map_err( + |e| { + log::debug!("{}", e); + anyhow::anyhow!("could not open repository") + }, + )?; + let head_ref = repo + .head() + .map_err(|e| { + log::warn!("{}", e); + anyhow::anyhow!("could not get HEAD") + })? + .target() + .ok_or(anyhow::anyhow!( + "could not extract head -> target to commit_sha" + ))?; + + Ok(Self { + commit_sha: head_ref.to_string(), + }) + } +} diff --git a/cuddle_cli/src/util/mod.rs b/cuddle_cli/src/util/mod.rs new file mode 100644 index 0000000..c2bf1c3 --- /dev/null +++ b/cuddle_cli/src/util/mod.rs @@ -0,0 +1 @@ +pub mod git; diff --git a/examples/templates/cuddle.yaml b/examples/templates/cuddle.yaml new file mode 100644 index 0000000..f01d114 --- /dev/null +++ b/examples/templates/cuddle.yaml @@ -0,0 +1,14 @@ +# yaml-language-server: $schema=../../schemas/base.json + +base: false + +vars: + service: "some-service" + +scripts: + test_render_template: + type: shell + args: + extravar: + type: env + key: "HOME" diff --git a/examples/templates/scripts/test_render_template.sh b/examples/templates/scripts/test_render_template.sh new file mode 100755 index 0000000..690b84e --- /dev/null +++ b/examples/templates/scripts/test_render_template.sh @@ -0,0 +1,6 @@ +#!/bin/bash + +CUDDLE_FETCH_POLICY=never cuddle_cli render_template \ + --template-file "$TMP/input.txt.tmpl" \ + --destination "$TMP/input.txt" \ + --extra-var "extravar=someextravar" diff --git a/examples/templates/templates/input.txt.tmpl b/examples/templates/templates/input.txt.tmpl new file mode 100644 index 0000000..8bd845a --- /dev/null +++ b/examples/templates/templates/input.txt.tmpl @@ -0,0 +1,3 @@ +some {{ service }} name + +- {{ extravar }}