diff --git a/Cargo.lock b/Cargo.lock index 5e5f467..1415b53 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -11,6 +11,15 @@ dependencies = [ "memchr", ] +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + [[package]] name = "ansi_term" version = "0.12.1" @@ -37,12 +46,24 @@ dependencies = [ "winapi", ] +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + [[package]] name = "bitflags" version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" +[[package]] +name = "bumpalo" +version = "3.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d261e256854913907f67ed06efbc3338dfe6179796deefc1ff763fc1aee5535" + [[package]] name = "cc" version = "1.0.79" @@ -61,6 +82,21 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "chrono" +version = "0.4.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16b0a3d9ed01224b22057780a37bb8c5dbfe1be8ba48678e7bf57ec4b385411f" +dependencies = [ + "iana-time-zone", + "js-sys", + "num-integer", + "num-traits", + "time", + "wasm-bindgen", + "winapi", +] + [[package]] name = "clap" version = "4.1.8" @@ -108,6 +144,16 @@ dependencies = [ "os_str_bytes", ] +[[package]] +name = "codespan-reporting" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3538270d33cc669650c4b093848450d380def10c331d38c768e34cac80576e6e" +dependencies = [ + "termcolor", + "unicode-width", +] + [[package]] name = "colored" version = "2.0.0" @@ -132,6 +178,56 @@ dependencies = [ "windows-sys 0.42.0", ] +[[package]] +name = "core-foundation-sys" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc" + +[[package]] +name = "cxx" +version = "1.0.91" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86d3488e7665a7a483b57e25bdd90d0aeb2bc7608c8d0346acf2ad3f1caf1d62" +dependencies = [ + "cc", + "cxxbridge-flags", + "cxxbridge-macro", + "link-cplusplus", +] + +[[package]] +name = "cxx-build" +version = "1.0.91" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48fcaf066a053a41a81dfb14d57d99738b767febb8b735c3016e469fac5da690" +dependencies = [ + "cc", + "codespan-reporting", + "once_cell", + "proc-macro2", + "quote", + "scratch", + "syn", +] + +[[package]] +name = "cxxbridge-flags" +version = "1.0.91" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2ef98b8b717a829ca5603af80e1f9e2e48013ab227b68ef37872ef84ee479bf" + +[[package]] +name = "cxxbridge-macro" +version = "1.0.91" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "086c685979a698443656e5cf7856c95c642295a38599f12fb1ff76fb28d19892" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "dialoguer" version = "0.10.3" @@ -251,7 +347,7 @@ checksum = "c05aeb6a22b8f62540c194aac980f2115af067bfe15a0734d7277a768d396b31" dependencies = [ "cfg-if 1.0.0", "libc", - "wasi", + "wasi 0.11.0+wasi-snapshot-preview1", ] [[package]] @@ -300,6 +396,30 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fed44880c466736ef9a5c5b5facefb5ed0785676d0c02d612db14e54f0d84286" +[[package]] +name = "iana-time-zone" +version = "0.1.53" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64c122667b287044802d6ce17ee2ddf13207ed924c712de9a66a5814d5b64765" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "wasm-bindgen", + "winapi", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0703ae284fc167426161c2e3f1da3ea71d94b21bedbcc9494e92b28e334e3dca" +dependencies = [ + "cxx", + "cxx-build", +] + [[package]] name = "indicatif" version = "0.17.3" @@ -345,9 +465,18 @@ dependencies = [ [[package]] name = "itoa" -version = "1.0.5" +version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fad582f4b9e86b6caa621cabeb0963332d92eea04729ab12892c2533951e6440" +checksum = "453ad9f582a441959e5f0d088b02ce04cfe8d51a8eaf077f12ac6d3e94164ca6" + +[[package]] +name = "js-sys" +version = "0.3.61" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "445dde2150c55e483f3d8416706b97ec8e8237c307e5b7b4b8dd15e6af2a0730" +dependencies = [ + "wasm-bindgen", +] [[package]] name = "lazy_static" @@ -361,6 +490,15 @@ version = "0.2.139" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "201de327520df007757c1f0adce6e827fe8562fbc28bfd9c15571c66ca1f5f79" +[[package]] +name = "link-cplusplus" +version = "1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecd207c9c713c34f95a097a5b029ac2ce6010530c7b49d7fea24d977dede04f5" +dependencies = [ + "cc", +] + [[package]] name = "linked-hash-map" version = "0.5.6" @@ -387,6 +525,7 @@ name = "machine_setup" version = "1.3.4" dependencies = [ "ansi_term", + "chrono", "clap", "clap_complete", "dialoguer", @@ -398,6 +537,7 @@ dependencies = [ "once_cell", "rand 0.8.5", "regex", + "serde", "serde_json", "symlink", "tempfile", @@ -422,6 +562,25 @@ dependencies = [ "winapi", ] +[[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 = "num_cpus" version = "1.15.0" @@ -643,9 +802,9 @@ dependencies = [ [[package]] name = "rustix" -version = "0.36.8" +version = "0.36.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f43abb88211988493c1abb44a70efa56ff0ce98f233b7b276146f1f3f7ba9644" +checksum = "fd5c6ff11fecd55b40746d1995a02f2eb375bf8c00d192d521ee09f42bef37bc" dependencies = [ "bitflags", "errno", @@ -657,9 +816,9 @@ dependencies = [ [[package]] name = "ryu" -version = "1.0.12" +version = "1.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b4b9743ed687d4b4bcedf9ff5eaa7398495ae14e61cba0a295704edbc7decde" +checksum = "f91339c0467de62360649f8d3e185ca8de4224ff281f66000de5eb2a77a79041" [[package]] name = "same-file" @@ -670,11 +829,20 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "scratch" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d5e082f6ea090deaf0e6dd04b68360fd5cddb152af6ce8927c9d25db299f98c" + [[package]] name = "serde" version = "1.0.152" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bb7d1f0d3021d347a83e556fc4683dea2ea09d87bccdf88ff5c12545d89d5efb" +dependencies = [ + "serde_derive", +] [[package]] name = "serde_derive" @@ -850,6 +1018,17 @@ dependencies = [ "once_cell", ] +[[package]] +name = "time" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b797afad3f312d1c66a56d11d0316f916356d11bd158fbc6ca6389ff6bf805a" +dependencies = [ + "libc", + "wasi 0.10.0+wasi-snapshot-preview1", + "winapi", +] + [[package]] name = "tracing" version = "0.1.37" @@ -910,9 +1089,9 @@ dependencies = [ [[package]] name = "unicode-ident" -version = "1.0.6" +version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84a22b9f218b40614adcb3f4ff08b703773ad44fa9423e4e0d346d5db86e4ebc" +checksum = "775c11906edafc97bc378816b94585fbd9a054eabaf86fdd0ced94af449efab7" [[package]] name = "unicode-width" @@ -943,12 +1122,72 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "wasi" +version = "0.10.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f" + [[package]] name = "wasi" 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.84" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "31f8dcbc21f30d9b8f2ea926ecb58f6b91192c17e9d33594b3df58b2007ca53b" +dependencies = [ + "cfg-if 1.0.0", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.84" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95ce90fd5bcc06af55a641a86428ee4229e44e07033963a2290a8e241607ccb9" +dependencies = [ + "bumpalo", + "log", + "once_cell", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.84" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c21f77c0bedc37fd5dc21f897894a5ca01e7bb159884559461862ae90c0b4c5" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.84" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2aff81306fcac3c7515ad4e177f521b5c9a15f2b08f4e32d823066102f35a5f6" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.84" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0046fef7e28c3804e5e38bfa31ea2a0f73905319b677e57ebe37e49358989b5d" + [[package]] name = "winapi" version = "0.3.9" diff --git a/Cargo.toml b/Cargo.toml index f22235b..a7923cc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -35,10 +35,12 @@ num_cpus = "1" rand = "0.8" ergo_fs = "0.2" yaml-rust = "0.4" -serde_json = "1.0" +serde = { version = "1", features = ["derive"] } +serde_json = "1" symlink = "0.1" git_commands = "0.2" clap = { version = "4", features = ["derive", "wrap_help"] } ansi_term = "0.12" dialoguer = "0.10" regex = "1.7.1" +chrono = "0.4" diff --git a/TODOS.md b/TODOS.md index 2a8ad28..d4c9c59 100644 --- a/TODOS.md +++ b/TODOS.md @@ -1,10 +1,7 @@ # TODOs -- save timestamps (at least for install), so the next install only runs new tasks - only update/uninstall should affect already run tasks - ---- - +- list skipped tasks (additionally to the errors) + - return Error struct instead of String from tasks (that can indicate if it's a hard failure or not) - Fix homebrew/tap setup - Add option to run `copy` and `symlink` as root user (needed to move/link some system files) diff --git a/completions/_machine_setup b/completions/_machine_setup index 981c99a..812ce69 100644 --- a/completions/_machine_setup +++ b/completions/_machine_setup @@ -19,12 +19,14 @@ _machine_setup() { '--config=[path to the config file]:CONFIG: ' \ '-t+[run a single task]:TASK: ' \ '--task=[run a single task]:TASK: ' \ -'-l+[Set log level]:LEVEL: ' \ -'--level=[Set log level]:LEVEL: ' \ -'-s[Select a task to run]' \ -'--select[Select a task to run]' \ -'-d[Add debug information]' \ -'--debug[Add debug information]' \ +'-l+[set log level]:LEVEL: ' \ +'--level=[set log level]:LEVEL: ' \ +'-s[select a task to run]' \ +'--select[select a task to run]' \ +'-d[add debug information]' \ +'--debug[add debug information]' \ +'-f[force installation/uninstallation]' \ +'--force[force installation/uninstallation]' \ '-h[Print help]' \ '--help[Print help]' \ '-V[Print version]' \ @@ -44,12 +46,14 @@ _arguments "${_arguments_options[@]}" \ '--config=[path to the config file]:CONFIG: ' \ '-t+[run a single task]:TASK: ' \ '--task=[run a single task]:TASK: ' \ -'-l+[Set log level]:LEVEL: ' \ -'--level=[Set log level]:LEVEL: ' \ -'-s[Select a task to run]' \ -'--select[Select a task to run]' \ -'-d[Add debug information]' \ -'--debug[Add debug information]' \ +'-l+[set log level]:LEVEL: ' \ +'--level=[set log level]:LEVEL: ' \ +'-s[select a task to run]' \ +'--select[select a task to run]' \ +'-d[add debug information]' \ +'--debug[add debug information]' \ +'-f[force installation/uninstallation]' \ +'--force[force installation/uninstallation]' \ '-h[Print help]' \ '--help[Print help]' \ '-V[Print version]' \ @@ -62,12 +66,14 @@ _arguments "${_arguments_options[@]}" \ '--config=[path to the config file]:CONFIG: ' \ '-t+[run a single task]:TASK: ' \ '--task=[run a single task]:TASK: ' \ -'-l+[Set log level]:LEVEL: ' \ -'--level=[Set log level]:LEVEL: ' \ -'-s[Select a task to run]' \ -'--select[Select a task to run]' \ -'-d[Add debug information]' \ -'--debug[Add debug information]' \ +'-l+[set log level]:LEVEL: ' \ +'--level=[set log level]:LEVEL: ' \ +'-s[select a task to run]' \ +'--select[select a task to run]' \ +'-d[add debug information]' \ +'--debug[add debug information]' \ +'-f[force installation/uninstallation]' \ +'--force[force installation/uninstallation]' \ '-h[Print help]' \ '--help[Print help]' \ '-V[Print version]' \ @@ -80,12 +86,14 @@ _arguments "${_arguments_options[@]}" \ '--config=[path to the config file]:CONFIG: ' \ '-t+[run a single task]:TASK: ' \ '--task=[run a single task]:TASK: ' \ -'-l+[Set log level]:LEVEL: ' \ -'--level=[Set log level]:LEVEL: ' \ -'-s[Select a task to run]' \ -'--select[Select a task to run]' \ -'-d[Add debug information]' \ -'--debug[Add debug information]' \ +'-l+[set log level]:LEVEL: ' \ +'--level=[set log level]:LEVEL: ' \ +'-s[select a task to run]' \ +'--select[select a task to run]' \ +'-d[add debug information]' \ +'--debug[add debug information]' \ +'-f[force installation/uninstallation]' \ +'--force[force installation/uninstallation]' \ '-h[Print help]' \ '--help[Print help]' \ '-V[Print version]' \ @@ -98,12 +106,14 @@ _arguments "${_arguments_options[@]}" \ '--config=[path to the config file]:CONFIG: ' \ '-t+[run a single task]:TASK: ' \ '--task=[run a single task]:TASK: ' \ -'-l+[Set log level]:LEVEL: ' \ -'--level=[Set log level]:LEVEL: ' \ -'-s[Select a task to run]' \ -'--select[Select a task to run]' \ -'-d[Add debug information]' \ -'--debug[Add debug information]' \ +'-l+[set log level]:LEVEL: ' \ +'--level=[set log level]:LEVEL: ' \ +'-s[select a task to run]' \ +'--select[select a task to run]' \ +'-d[add debug information]' \ +'--debug[add debug information]' \ +'-f[force installation/uninstallation]' \ +'--force[force installation/uninstallation]' \ '-h[Print help]' \ '--help[Print help]' \ '-V[Print version]' \ diff --git a/completions/_machine_setup.ps1 b/completions/_machine_setup.ps1 index 57804e6..adb6613 100644 --- a/completions/_machine_setup.ps1 +++ b/completions/_machine_setup.ps1 @@ -25,12 +25,14 @@ Register-ArgumentCompleter -Native -CommandName 'machine_setup' -ScriptBlock { [CompletionResult]::new('--config', 'config', [CompletionResultType]::ParameterName, 'path to the config file') [CompletionResult]::new('-t', 't', [CompletionResultType]::ParameterName, 'run a single task') [CompletionResult]::new('--task', 'task', [CompletionResultType]::ParameterName, 'run a single task') - [CompletionResult]::new('-l', 'l', [CompletionResultType]::ParameterName, 'Set log level') - [CompletionResult]::new('--level', 'level', [CompletionResultType]::ParameterName, 'Set log level') - [CompletionResult]::new('-s', 's', [CompletionResultType]::ParameterName, 'Select a task to run') - [CompletionResult]::new('--select', 'select', [CompletionResultType]::ParameterName, 'Select a task to run') - [CompletionResult]::new('-d', 'd', [CompletionResultType]::ParameterName, 'Add debug information') - [CompletionResult]::new('--debug', 'debug', [CompletionResultType]::ParameterName, 'Add debug information') + [CompletionResult]::new('-l', 'l', [CompletionResultType]::ParameterName, 'set log level') + [CompletionResult]::new('--level', 'level', [CompletionResultType]::ParameterName, 'set log level') + [CompletionResult]::new('-s', 's', [CompletionResultType]::ParameterName, 'select a task to run') + [CompletionResult]::new('--select', 'select', [CompletionResultType]::ParameterName, 'select a task to run') + [CompletionResult]::new('-d', 'd', [CompletionResultType]::ParameterName, 'add debug information') + [CompletionResult]::new('--debug', 'debug', [CompletionResultType]::ParameterName, 'add debug information') + [CompletionResult]::new('-f', 'f', [CompletionResultType]::ParameterName, 'force installation/uninstallation') + [CompletionResult]::new('--force', 'force', [CompletionResultType]::ParameterName, 'force installation/uninstallation') [CompletionResult]::new('-h', 'h', [CompletionResultType]::ParameterName, 'Print help') [CompletionResult]::new('--help', 'help', [CompletionResultType]::ParameterName, 'Print help') [CompletionResult]::new('-V', 'V', [CompletionResultType]::ParameterName, 'Print version') @@ -47,12 +49,14 @@ Register-ArgumentCompleter -Native -CommandName 'machine_setup' -ScriptBlock { [CompletionResult]::new('--config', 'config', [CompletionResultType]::ParameterName, 'path to the config file') [CompletionResult]::new('-t', 't', [CompletionResultType]::ParameterName, 'run a single task') [CompletionResult]::new('--task', 'task', [CompletionResultType]::ParameterName, 'run a single task') - [CompletionResult]::new('-l', 'l', [CompletionResultType]::ParameterName, 'Set log level') - [CompletionResult]::new('--level', 'level', [CompletionResultType]::ParameterName, 'Set log level') - [CompletionResult]::new('-s', 's', [CompletionResultType]::ParameterName, 'Select a task to run') - [CompletionResult]::new('--select', 'select', [CompletionResultType]::ParameterName, 'Select a task to run') - [CompletionResult]::new('-d', 'd', [CompletionResultType]::ParameterName, 'Add debug information') - [CompletionResult]::new('--debug', 'debug', [CompletionResultType]::ParameterName, 'Add debug information') + [CompletionResult]::new('-l', 'l', [CompletionResultType]::ParameterName, 'set log level') + [CompletionResult]::new('--level', 'level', [CompletionResultType]::ParameterName, 'set log level') + [CompletionResult]::new('-s', 's', [CompletionResultType]::ParameterName, 'select a task to run') + [CompletionResult]::new('--select', 'select', [CompletionResultType]::ParameterName, 'select a task to run') + [CompletionResult]::new('-d', 'd', [CompletionResultType]::ParameterName, 'add debug information') + [CompletionResult]::new('--debug', 'debug', [CompletionResultType]::ParameterName, 'add debug information') + [CompletionResult]::new('-f', 'f', [CompletionResultType]::ParameterName, 'force installation/uninstallation') + [CompletionResult]::new('--force', 'force', [CompletionResultType]::ParameterName, 'force installation/uninstallation') [CompletionResult]::new('-h', 'h', [CompletionResultType]::ParameterName, 'Print help') [CompletionResult]::new('--help', 'help', [CompletionResultType]::ParameterName, 'Print help') [CompletionResult]::new('-V', 'V', [CompletionResultType]::ParameterName, 'Print version') @@ -64,12 +68,14 @@ Register-ArgumentCompleter -Native -CommandName 'machine_setup' -ScriptBlock { [CompletionResult]::new('--config', 'config', [CompletionResultType]::ParameterName, 'path to the config file') [CompletionResult]::new('-t', 't', [CompletionResultType]::ParameterName, 'run a single task') [CompletionResult]::new('--task', 'task', [CompletionResultType]::ParameterName, 'run a single task') - [CompletionResult]::new('-l', 'l', [CompletionResultType]::ParameterName, 'Set log level') - [CompletionResult]::new('--level', 'level', [CompletionResultType]::ParameterName, 'Set log level') - [CompletionResult]::new('-s', 's', [CompletionResultType]::ParameterName, 'Select a task to run') - [CompletionResult]::new('--select', 'select', [CompletionResultType]::ParameterName, 'Select a task to run') - [CompletionResult]::new('-d', 'd', [CompletionResultType]::ParameterName, 'Add debug information') - [CompletionResult]::new('--debug', 'debug', [CompletionResultType]::ParameterName, 'Add debug information') + [CompletionResult]::new('-l', 'l', [CompletionResultType]::ParameterName, 'set log level') + [CompletionResult]::new('--level', 'level', [CompletionResultType]::ParameterName, 'set log level') + [CompletionResult]::new('-s', 's', [CompletionResultType]::ParameterName, 'select a task to run') + [CompletionResult]::new('--select', 'select', [CompletionResultType]::ParameterName, 'select a task to run') + [CompletionResult]::new('-d', 'd', [CompletionResultType]::ParameterName, 'add debug information') + [CompletionResult]::new('--debug', 'debug', [CompletionResultType]::ParameterName, 'add debug information') + [CompletionResult]::new('-f', 'f', [CompletionResultType]::ParameterName, 'force installation/uninstallation') + [CompletionResult]::new('--force', 'force', [CompletionResultType]::ParameterName, 'force installation/uninstallation') [CompletionResult]::new('-h', 'h', [CompletionResultType]::ParameterName, 'Print help') [CompletionResult]::new('--help', 'help', [CompletionResultType]::ParameterName, 'Print help') [CompletionResult]::new('-V', 'V', [CompletionResultType]::ParameterName, 'Print version') @@ -81,12 +87,14 @@ Register-ArgumentCompleter -Native -CommandName 'machine_setup' -ScriptBlock { [CompletionResult]::new('--config', 'config', [CompletionResultType]::ParameterName, 'path to the config file') [CompletionResult]::new('-t', 't', [CompletionResultType]::ParameterName, 'run a single task') [CompletionResult]::new('--task', 'task', [CompletionResultType]::ParameterName, 'run a single task') - [CompletionResult]::new('-l', 'l', [CompletionResultType]::ParameterName, 'Set log level') - [CompletionResult]::new('--level', 'level', [CompletionResultType]::ParameterName, 'Set log level') - [CompletionResult]::new('-s', 's', [CompletionResultType]::ParameterName, 'Select a task to run') - [CompletionResult]::new('--select', 'select', [CompletionResultType]::ParameterName, 'Select a task to run') - [CompletionResult]::new('-d', 'd', [CompletionResultType]::ParameterName, 'Add debug information') - [CompletionResult]::new('--debug', 'debug', [CompletionResultType]::ParameterName, 'Add debug information') + [CompletionResult]::new('-l', 'l', [CompletionResultType]::ParameterName, 'set log level') + [CompletionResult]::new('--level', 'level', [CompletionResultType]::ParameterName, 'set log level') + [CompletionResult]::new('-s', 's', [CompletionResultType]::ParameterName, 'select a task to run') + [CompletionResult]::new('--select', 'select', [CompletionResultType]::ParameterName, 'select a task to run') + [CompletionResult]::new('-d', 'd', [CompletionResultType]::ParameterName, 'add debug information') + [CompletionResult]::new('--debug', 'debug', [CompletionResultType]::ParameterName, 'add debug information') + [CompletionResult]::new('-f', 'f', [CompletionResultType]::ParameterName, 'force installation/uninstallation') + [CompletionResult]::new('--force', 'force', [CompletionResultType]::ParameterName, 'force installation/uninstallation') [CompletionResult]::new('-h', 'h', [CompletionResultType]::ParameterName, 'Print help') [CompletionResult]::new('--help', 'help', [CompletionResultType]::ParameterName, 'Print help') [CompletionResult]::new('-V', 'V', [CompletionResultType]::ParameterName, 'Print version') @@ -98,12 +106,14 @@ Register-ArgumentCompleter -Native -CommandName 'machine_setup' -ScriptBlock { [CompletionResult]::new('--config', 'config', [CompletionResultType]::ParameterName, 'path to the config file') [CompletionResult]::new('-t', 't', [CompletionResultType]::ParameterName, 'run a single task') [CompletionResult]::new('--task', 'task', [CompletionResultType]::ParameterName, 'run a single task') - [CompletionResult]::new('-l', 'l', [CompletionResultType]::ParameterName, 'Set log level') - [CompletionResult]::new('--level', 'level', [CompletionResultType]::ParameterName, 'Set log level') - [CompletionResult]::new('-s', 's', [CompletionResultType]::ParameterName, 'Select a task to run') - [CompletionResult]::new('--select', 'select', [CompletionResultType]::ParameterName, 'Select a task to run') - [CompletionResult]::new('-d', 'd', [CompletionResultType]::ParameterName, 'Add debug information') - [CompletionResult]::new('--debug', 'debug', [CompletionResultType]::ParameterName, 'Add debug information') + [CompletionResult]::new('-l', 'l', [CompletionResultType]::ParameterName, 'set log level') + [CompletionResult]::new('--level', 'level', [CompletionResultType]::ParameterName, 'set log level') + [CompletionResult]::new('-s', 's', [CompletionResultType]::ParameterName, 'select a task to run') + [CompletionResult]::new('--select', 'select', [CompletionResultType]::ParameterName, 'select a task to run') + [CompletionResult]::new('-d', 'd', [CompletionResultType]::ParameterName, 'add debug information') + [CompletionResult]::new('--debug', 'debug', [CompletionResultType]::ParameterName, 'add debug information') + [CompletionResult]::new('-f', 'f', [CompletionResultType]::ParameterName, 'force installation/uninstallation') + [CompletionResult]::new('--force', 'force', [CompletionResultType]::ParameterName, 'force installation/uninstallation') [CompletionResult]::new('-h', 'h', [CompletionResultType]::ParameterName, 'Print help') [CompletionResult]::new('--help', 'help', [CompletionResultType]::ParameterName, 'Print help') [CompletionResult]::new('-V', 'V', [CompletionResultType]::ParameterName, 'Print version') diff --git a/completions/machine_setup.bash b/completions/machine_setup.bash index 61b4173..d1f080b 100644 --- a/completions/machine_setup.bash +++ b/completions/machine_setup.bash @@ -49,7 +49,7 @@ _machine_setup() { case "${cmd}" in machine_setup) - opts="-c -t -s -d -l -h -V --config --task --select --debug --level --help --version install update uninstall list help" + opts="-c -t -s -d -l -f -h -V --config --task --select --debug --level --force --help --version install update uninstall list help" if [[ ${cur} == -* || ${COMP_CWORD} -eq 1 ]] ; then COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") ) return 0 @@ -171,7 +171,7 @@ _machine_setup() { return 0 ;; machine_setup__install) - opts="-c -t -s -d -l -h -V --config --task --select --debug --level --help --version" + opts="-c -t -s -d -l -f -h -V --config --task --select --debug --level --force --help --version" if [[ ${cur} == -* || ${COMP_CWORD} -eq 2 ]] ; then COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") ) return 0 @@ -209,7 +209,7 @@ _machine_setup() { return 0 ;; machine_setup__list) - opts="-c -t -s -d -l -h -V --config --task --select --debug --level --help --version" + opts="-c -t -s -d -l -f -h -V --config --task --select --debug --level --force --help --version" if [[ ${cur} == -* || ${COMP_CWORD} -eq 2 ]] ; then COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") ) return 0 @@ -247,7 +247,7 @@ _machine_setup() { return 0 ;; machine_setup__uninstall) - opts="-c -t -s -d -l -h -V --config --task --select --debug --level --help --version" + opts="-c -t -s -d -l -f -h -V --config --task --select --debug --level --force --help --version" if [[ ${cur} == -* || ${COMP_CWORD} -eq 2 ]] ; then COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") ) return 0 @@ -285,7 +285,7 @@ _machine_setup() { return 0 ;; machine_setup__update) - opts="-c -t -s -d -l -h -V --config --task --select --debug --level --help --version" + opts="-c -t -s -d -l -f -h -V --config --task --select --debug --level --force --help --version" if [[ ${cur} == -* || ${COMP_CWORD} -eq 2 ]] ; then COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") ) return 0 diff --git a/completions/machine_setup.elv b/completions/machine_setup.elv index 6a48a25..06f858c 100644 --- a/completions/machine_setup.elv +++ b/completions/machine_setup.elv @@ -22,12 +22,14 @@ set edit:completion:arg-completer[machine_setup] = {|@words| cand --config 'path to the config file' cand -t 'run a single task' cand --task 'run a single task' - cand -l 'Set log level' - cand --level 'Set log level' - cand -s 'Select a task to run' - cand --select 'Select a task to run' - cand -d 'Add debug information' - cand --debug 'Add debug information' + cand -l 'set log level' + cand --level 'set log level' + cand -s 'select a task to run' + cand --select 'select a task to run' + cand -d 'add debug information' + cand --debug 'add debug information' + cand -f 'force installation/uninstallation' + cand --force 'force installation/uninstallation' cand -h 'Print help' cand --help 'Print help' cand -V 'Print version' @@ -43,12 +45,14 @@ set edit:completion:arg-completer[machine_setup] = {|@words| cand --config 'path to the config file' cand -t 'run a single task' cand --task 'run a single task' - cand -l 'Set log level' - cand --level 'Set log level' - cand -s 'Select a task to run' - cand --select 'Select a task to run' - cand -d 'Add debug information' - cand --debug 'Add debug information' + cand -l 'set log level' + cand --level 'set log level' + cand -s 'select a task to run' + cand --select 'select a task to run' + cand -d 'add debug information' + cand --debug 'add debug information' + cand -f 'force installation/uninstallation' + cand --force 'force installation/uninstallation' cand -h 'Print help' cand --help 'Print help' cand -V 'Print version' @@ -59,12 +63,14 @@ set edit:completion:arg-completer[machine_setup] = {|@words| cand --config 'path to the config file' cand -t 'run a single task' cand --task 'run a single task' - cand -l 'Set log level' - cand --level 'Set log level' - cand -s 'Select a task to run' - cand --select 'Select a task to run' - cand -d 'Add debug information' - cand --debug 'Add debug information' + cand -l 'set log level' + cand --level 'set log level' + cand -s 'select a task to run' + cand --select 'select a task to run' + cand -d 'add debug information' + cand --debug 'add debug information' + cand -f 'force installation/uninstallation' + cand --force 'force installation/uninstallation' cand -h 'Print help' cand --help 'Print help' cand -V 'Print version' @@ -75,12 +81,14 @@ set edit:completion:arg-completer[machine_setup] = {|@words| cand --config 'path to the config file' cand -t 'run a single task' cand --task 'run a single task' - cand -l 'Set log level' - cand --level 'Set log level' - cand -s 'Select a task to run' - cand --select 'Select a task to run' - cand -d 'Add debug information' - cand --debug 'Add debug information' + cand -l 'set log level' + cand --level 'set log level' + cand -s 'select a task to run' + cand --select 'select a task to run' + cand -d 'add debug information' + cand --debug 'add debug information' + cand -f 'force installation/uninstallation' + cand --force 'force installation/uninstallation' cand -h 'Print help' cand --help 'Print help' cand -V 'Print version' @@ -91,12 +99,14 @@ set edit:completion:arg-completer[machine_setup] = {|@words| cand --config 'path to the config file' cand -t 'run a single task' cand --task 'run a single task' - cand -l 'Set log level' - cand --level 'Set log level' - cand -s 'Select a task to run' - cand --select 'Select a task to run' - cand -d 'Add debug information' - cand --debug 'Add debug information' + cand -l 'set log level' + cand --level 'set log level' + cand -s 'select a task to run' + cand --select 'select a task to run' + cand -d 'add debug information' + cand --debug 'add debug information' + cand -f 'force installation/uninstallation' + cand --force 'force installation/uninstallation' cand -h 'Print help' cand --help 'Print help' cand -V 'Print version' diff --git a/completions/machine_setup.fish b/completions/machine_setup.fish index 37e6a91..7df376b 100644 --- a/completions/machine_setup.fish +++ b/completions/machine_setup.fish @@ -1,8 +1,9 @@ complete -c machine_setup -n "__fish_use_subcommand" -s c -l config -d 'path to the config file' -r complete -c machine_setup -n "__fish_use_subcommand" -s t -l task -d 'run a single task' -r -complete -c machine_setup -n "__fish_use_subcommand" -s l -l level -d 'Set log level' -r -complete -c machine_setup -n "__fish_use_subcommand" -s s -l select -d 'Select a task to run' -complete -c machine_setup -n "__fish_use_subcommand" -s d -l debug -d 'Add debug information' +complete -c machine_setup -n "__fish_use_subcommand" -s l -l level -d 'set log level' -r +complete -c machine_setup -n "__fish_use_subcommand" -s s -l select -d 'select a task to run' +complete -c machine_setup -n "__fish_use_subcommand" -s d -l debug -d 'add debug information' +complete -c machine_setup -n "__fish_use_subcommand" -s f -l force -d 'force installation/uninstallation' complete -c machine_setup -n "__fish_use_subcommand" -s h -l help -d 'Print help' complete -c machine_setup -n "__fish_use_subcommand" -s V -l version -d 'Print version' complete -c machine_setup -n "__fish_use_subcommand" -f -a "install" -d 'Install all of the defined tasks' @@ -12,30 +13,34 @@ complete -c machine_setup -n "__fish_use_subcommand" -f -a "list" -d 'List defin complete -c machine_setup -n "__fish_use_subcommand" -f -a "help" -d 'Print this message or the help of the given subcommand(s)' complete -c machine_setup -n "__fish_seen_subcommand_from install" -s c -l config -d 'path to the config file' -r complete -c machine_setup -n "__fish_seen_subcommand_from install" -s t -l task -d 'run a single task' -r -complete -c machine_setup -n "__fish_seen_subcommand_from install" -s l -l level -d 'Set log level' -r -complete -c machine_setup -n "__fish_seen_subcommand_from install" -s s -l select -d 'Select a task to run' -complete -c machine_setup -n "__fish_seen_subcommand_from install" -s d -l debug -d 'Add debug information' +complete -c machine_setup -n "__fish_seen_subcommand_from install" -s l -l level -d 'set log level' -r +complete -c machine_setup -n "__fish_seen_subcommand_from install" -s s -l select -d 'select a task to run' +complete -c machine_setup -n "__fish_seen_subcommand_from install" -s d -l debug -d 'add debug information' +complete -c machine_setup -n "__fish_seen_subcommand_from install" -s f -l force -d 'force installation/uninstallation' complete -c machine_setup -n "__fish_seen_subcommand_from install" -s h -l help -d 'Print help' complete -c machine_setup -n "__fish_seen_subcommand_from install" -s V -l version -d 'Print version' complete -c machine_setup -n "__fish_seen_subcommand_from update" -s c -l config -d 'path to the config file' -r complete -c machine_setup -n "__fish_seen_subcommand_from update" -s t -l task -d 'run a single task' -r -complete -c machine_setup -n "__fish_seen_subcommand_from update" -s l -l level -d 'Set log level' -r -complete -c machine_setup -n "__fish_seen_subcommand_from update" -s s -l select -d 'Select a task to run' -complete -c machine_setup -n "__fish_seen_subcommand_from update" -s d -l debug -d 'Add debug information' +complete -c machine_setup -n "__fish_seen_subcommand_from update" -s l -l level -d 'set log level' -r +complete -c machine_setup -n "__fish_seen_subcommand_from update" -s s -l select -d 'select a task to run' +complete -c machine_setup -n "__fish_seen_subcommand_from update" -s d -l debug -d 'add debug information' +complete -c machine_setup -n "__fish_seen_subcommand_from update" -s f -l force -d 'force installation/uninstallation' complete -c machine_setup -n "__fish_seen_subcommand_from update" -s h -l help -d 'Print help' complete -c machine_setup -n "__fish_seen_subcommand_from update" -s V -l version -d 'Print version' complete -c machine_setup -n "__fish_seen_subcommand_from uninstall" -s c -l config -d 'path to the config file' -r complete -c machine_setup -n "__fish_seen_subcommand_from uninstall" -s t -l task -d 'run a single task' -r -complete -c machine_setup -n "__fish_seen_subcommand_from uninstall" -s l -l level -d 'Set log level' -r -complete -c machine_setup -n "__fish_seen_subcommand_from uninstall" -s s -l select -d 'Select a task to run' -complete -c machine_setup -n "__fish_seen_subcommand_from uninstall" -s d -l debug -d 'Add debug information' +complete -c machine_setup -n "__fish_seen_subcommand_from uninstall" -s l -l level -d 'set log level' -r +complete -c machine_setup -n "__fish_seen_subcommand_from uninstall" -s s -l select -d 'select a task to run' +complete -c machine_setup -n "__fish_seen_subcommand_from uninstall" -s d -l debug -d 'add debug information' +complete -c machine_setup -n "__fish_seen_subcommand_from uninstall" -s f -l force -d 'force installation/uninstallation' complete -c machine_setup -n "__fish_seen_subcommand_from uninstall" -s h -l help -d 'Print help' complete -c machine_setup -n "__fish_seen_subcommand_from uninstall" -s V -l version -d 'Print version' complete -c machine_setup -n "__fish_seen_subcommand_from list" -s c -l config -d 'path to the config file' -r complete -c machine_setup -n "__fish_seen_subcommand_from list" -s t -l task -d 'run a single task' -r -complete -c machine_setup -n "__fish_seen_subcommand_from list" -s l -l level -d 'Set log level' -r -complete -c machine_setup -n "__fish_seen_subcommand_from list" -s s -l select -d 'Select a task to run' -complete -c machine_setup -n "__fish_seen_subcommand_from list" -s d -l debug -d 'Add debug information' +complete -c machine_setup -n "__fish_seen_subcommand_from list" -s l -l level -d 'set log level' -r +complete -c machine_setup -n "__fish_seen_subcommand_from list" -s s -l select -d 'select a task to run' +complete -c machine_setup -n "__fish_seen_subcommand_from list" -s d -l debug -d 'add debug information' +complete -c machine_setup -n "__fish_seen_subcommand_from list" -s f -l force -d 'force installation/uninstallation' complete -c machine_setup -n "__fish_seen_subcommand_from list" -s h -l help -d 'Print help' complete -c machine_setup -n "__fish_seen_subcommand_from list" -s V -l version -d 'Print version' complete -c machine_setup -n "__fish_seen_subcommand_from help; and not __fish_seen_subcommand_from install; and not __fish_seen_subcommand_from update; and not __fish_seen_subcommand_from uninstall; and not __fish_seen_subcommand_from list; and not __fish_seen_subcommand_from help" -f -a "install" -d 'Install all of the defined tasks' diff --git a/src/commands/machine_setup.rs b/src/commands/machine_setup.rs index 346b6a5..4fa6bec 100644 --- a/src/commands/machine_setup.rs +++ b/src/commands/machine_setup.rs @@ -57,6 +57,7 @@ fn execute_config(command: SubCommand, args: ConfigValue) -> Result<(), String> task, debug: false, level: Level::WARN, + force: false, }; execute_command(args); diff --git a/src/commands/run.rs b/src/commands/run.rs index 3bc6e67..a99649d 100644 --- a/src/commands/run.rs +++ b/src/commands/run.rs @@ -9,7 +9,6 @@ use std::{ use ansi_term::Color::White; use indicatif::ProgressBar; -use tracing::info; use crate::{ command::{CommandConfig, CommandInterface}, @@ -52,9 +51,7 @@ fn get_commands(args: ConfigValue, mode: TaskRunnerMode) -> Result, let method = method_name.clone(); if !named_args.as_hash().unwrap().contains_key(&method) { - info!("{} is not defined...", White.bold().paint(&method)); - - return Ok(vec![]); + return Err(format!("{} is not defined...", White.bold().paint(&method))); } validate_named_args(named_args, HashMap::from([(method, rules)]))?; @@ -354,7 +351,8 @@ mod test { let commands = get_commands(ConfigValue::Hash(commands.clone()), TaskRunnerMode::Install); - assert_eq!(commands.unwrap(), vec![] as Vec); + assert!(commands.is_err()); + assert!(commands.unwrap_err().contains("is not defined")); } #[test] diff --git a/src/config/history.rs b/src/config/history.rs new file mode 100644 index 0000000..23cfbd3 --- /dev/null +++ b/src/config/history.rs @@ -0,0 +1,376 @@ +use crate::task_runner::TaskRunnerMode; +use chrono::Utc; +use ergo_fs::path_abs::Error; +use ergo_fs::{Path, PathAbs, Read}; +use serde::{Deserialize, Serialize}; +use std::collections::BTreeMap; +use std::fs::{self, OpenOptions}; + +#[derive(Serialize, Deserialize, Debug, Clone, Default, PartialEq)] +pub struct TaskEntry { + installed_at: Option, + updated_at: Option, + uninstalled_at: Option, +} + +pub fn get_history_path(temp_dir: &str) -> Result { + Ok(PathAbs::new(temp_dir)?.join("history.json").to_string()) +} + +pub fn get_task_entry(file_path: &str, task_name: &str) -> Result { + if !Path::new(file_path).exists() { + return Ok(TaskEntry { + installed_at: None, + updated_at: None, + uninstalled_at: None, + }); + } + + let file_contents = fs::read_to_string(file_path); + if let Err(read_err) = file_contents { + return Err(read_err.to_string()); + } + let file_contents = file_contents.unwrap(); + + let mut entries: BTreeMap = + serde_json::from_str(&file_contents).unwrap_or_else(|_| BTreeMap::new()); + + let entry: &TaskEntry = entries.entry(task_name.to_owned()).or_insert(TaskEntry { + installed_at: None, + updated_at: None, + uninstalled_at: None, + }); + + Ok(entry.clone()) +} + +pub fn is_logged(file_path: &str, mode: &TaskRunnerMode, task_name: &str) -> Result { + let entry = get_task_entry(file_path, task_name)?; + + Ok(match mode { + TaskRunnerMode::Install => entry.installed_at.is_some(), + TaskRunnerMode::Update => entry.updated_at.is_some(), + TaskRunnerMode::Uninstall => entry.uninstalled_at.is_some(), + }) +} + +pub fn clear_entry(file_path: &str, mode: &TaskRunnerMode, task_name: &str) -> Result<(), String> { + if !Path::new(file_path).exists() { + return Ok(()); + } + + let file_contents = fs::read_to_string(file_path); + if let Err(file_err) = file_contents { + return Err(format!("{}: {}", file_path, file_err)); + } + let file_contents = file_contents.unwrap(); + + let mut entries: BTreeMap = + serde_json::from_str(&file_contents).unwrap_or_else(|_| BTreeMap::new()); + + let task_entry = entries.entry(task_name.to_owned()).or_insert(TaskEntry { + installed_at: None, + updated_at: None, + uninstalled_at: None, + }); + + match mode { + TaskRunnerMode::Install => task_entry.installed_at = None, + TaskRunnerMode::Update => task_entry.updated_at = None, + TaskRunnerMode::Uninstall => task_entry.uninstalled_at = None, + } + + if let Err(write_err) = fs::write( + file_path, + serde_json::to_string_pretty(&entries).unwrap().as_bytes(), + ) { + return Err(format!("{}: {}", file_path, write_err)); + } + + Ok(()) +} + +pub fn update_entry(file_path: &str, mode: &TaskRunnerMode, task_name: &str) -> Result<(), String> { + let timestamp = Utc::now().to_rfc3339(); + + let file = OpenOptions::new() + .read(true) + .write(true) + .create(true) + .open(file_path); + + if let Err(file_err) = file { + return Err(format!("{}: {}", file_path, file_err)); + } + let mut file = file.unwrap(); + + let mut file_contents = String::new(); + if let Err(read_err) = file.read_to_string(&mut file_contents) { + return Err(format!("{}: {}", file_path, read_err)); + } + + let mut entries: BTreeMap = + serde_json::from_str(&file_contents).unwrap_or_else(|_| BTreeMap::new()); + + let task_entry = entries.entry(task_name.to_owned()).or_insert(TaskEntry { + installed_at: None, + updated_at: None, + uninstalled_at: None, + }); + + match mode { + TaskRunnerMode::Install => { + task_entry.installed_at = Some(timestamp); + task_entry.uninstalled_at = None; + } + TaskRunnerMode::Update => task_entry.updated_at = Some(timestamp), + TaskRunnerMode::Uninstall => { + task_entry.uninstalled_at = Some(timestamp); + task_entry.installed_at = None; + } + } + + if let Err(write_err) = fs::write( + file_path, + serde_json::to_string_pretty(&entries).unwrap().as_bytes(), + ) { + return Err(format!("{}: {}", file_path, write_err)); + } + + Ok(()) +} + +#[cfg(test)] +mod test { + + use std::env::temp_dir; + + use chrono::DateTime; + use ergo_fs::IoWrite; + use tempfile::NamedTempFile; + + use super::*; + + #[test] + fn it_clears_task_entry() { + let mut file = NamedTempFile::new().unwrap(); + let file_path = file.path().to_string_lossy().to_string(); + + file.write_all( + r#"{ + "task1": { + "installed_at": "2022-12-31T23:59:59+00:00", + "updated_at": "2022-12-31T23:59:59+00:00", + "uninstalled_at": "2022-12-31T23:59:59+00:00" + } + }"# + .as_bytes(), + ) + .unwrap(); + + let result = clear_entry(&file_path, &TaskRunnerMode::Install, "task1"); + assert!(result.is_ok()); + let result = clear_entry(&file_path, &TaskRunnerMode::Uninstall, "task1"); + assert!(result.is_ok()); + let result = clear_entry(&file_path, &TaskRunnerMode::Update, "task1"); + assert!(result.is_ok()); + + let file_contents = fs::read_to_string(&file_path).unwrap(); + let entries: BTreeMap = serde_json::from_str(&file_contents).unwrap(); + let entry = &entries["task1"]; + + assert_eq!( + entry, + &TaskEntry { + installed_at: None, + uninstalled_at: None, + updated_at: None + } + ); + } + + #[test] + fn it_gets_empty_entry_if_file_doesnt_exist() { + let result = get_task_entry("/nope", "task"); + assert!(result.is_ok()); + assert_eq!( + result.unwrap(), + TaskEntry { + updated_at: None, + uninstalled_at: None, + installed_at: None + } + ) + } + + #[test] + fn it_gets_task_entry() { + let mut file = NamedTempFile::new().unwrap(); + let file_path = file.path().to_string_lossy().to_string(); + + file.write_all( + r#"{ + "task1": { + "installed_at": "2022-12-31T23:59:59+00:00", + "updated_at": null, + "uninstalled_at": null + }, + "task2": { + "installed_at": "2023-01-31T23:59:59+00:00", + "updated_at": null, + "uninstalled_at": null + } + }"# + .as_bytes(), + ) + .unwrap(); + + let result = get_task_entry(&file_path, "task2"); + assert!(result.is_ok()); + + assert_eq!( + result.unwrap().installed_at.unwrap(), + "2023-01-31T23:59:59+00:00" + ); + } + + #[test] + fn it_gets_installation_status_correctly() { + let mut file = NamedTempFile::new().unwrap(); + let file_path = file.path().to_string_lossy().to_string(); + + file.write_all( + r#"{ + "task1": { + "installed_at": "2022-12-31T23:59:59+00:00", + "updated_at": null, + "uninstalled_at": null + }, + "task2": { + "installed_at": null, + "updated_at": null, + "uninstalled_at": null + } + }"# + .as_bytes(), + ) + .unwrap(); + + let result = is_logged(&file_path, &TaskRunnerMode::Install, "task1"); + assert!(result.is_ok()); + assert!(result.unwrap()); + + let result = is_logged(&file_path, &TaskRunnerMode::Install, "task2"); + assert!(result.is_ok()); + assert!(!result.unwrap()); + } + + #[test] + fn it_updates_entry_in_existing_file() { + let mut file = NamedTempFile::new().unwrap(); + let file_path = file.path().to_string_lossy().to_string(); + + file.write_all( + r#"{ + "task1": { + "installed_at": "2022-12-31T23:59:59+00:00", + "updated_at": null, + "uninstalled_at": null + }, + "task2": { + "installed_at": null, + "updated_at": null, + "uninstalled_at": "2022-12-31T23:59:59+00:00" + } + }"# + .as_bytes(), + ) + .unwrap(); + + let result = update_entry(&file_path, &TaskRunnerMode::Install, "task2"); + assert!(result.is_ok()); + + let file_contents = fs::read_to_string(&file_path).unwrap(); + let entries: BTreeMap = serde_json::from_str(&file_contents).unwrap(); + let entry = &entries["task2"]; + + assert!(DateTime::parse_from_rfc3339(entry.installed_at.as_ref().unwrap()).is_ok()); + assert!(entry.uninstalled_at.is_none()); + + let result = update_entry(&file_path, &TaskRunnerMode::Uninstall, "task1"); + assert!(result.is_ok()); + + let file_contents = fs::read_to_string(&file_path).unwrap(); + let entries: BTreeMap = serde_json::from_str(&file_contents).unwrap(); + let entry = &entries["task1"]; + + assert!(DateTime::parse_from_rfc3339(entry.uninstalled_at.as_ref().unwrap()).is_ok()); + assert!(entry.installed_at.is_none()); + } + + #[test] + fn it_inserts_entry_into_existing_file() { + let mut file = NamedTempFile::new().unwrap(); + let file_path = file.path().to_string_lossy().to_string(); + + file.write_all( + r#"{ + "task1": { + "installed_at": null, + "updated_at": null, + "uninstalled_at": null + }, + }"# + .as_bytes(), + ) + .unwrap(); + + let result = update_entry(&file_path, &TaskRunnerMode::Install, "task2"); + assert!(result.is_ok()); + + let file_contents = fs::read_to_string(file_path).unwrap(); + let entries: BTreeMap = serde_json::from_str(&file_contents).unwrap(); + let entry = &entries["task2"]; + + assert!(DateTime::parse_from_rfc3339(entry.installed_at.as_ref().unwrap()).is_ok()); + assert!(entry.uninstalled_at.is_none()); + } + + #[test] + fn it_inserts_entry_into_fresh_file() { + let file = NamedTempFile::new().unwrap(); + let file_path = file.path().to_string_lossy().to_string(); + + let result = update_entry(&file_path, &TaskRunnerMode::Install, "task1"); + assert!(result.is_ok()); + + let file_contents = fs::read_to_string(file_path).unwrap(); + + let entries: BTreeMap = serde_json::from_str(&file_contents).unwrap(); + let entry = &entries["task1"]; + + assert!(DateTime::parse_from_rfc3339(entry.installed_at.as_ref().unwrap()).is_ok()); + assert!(entry.uninstalled_at.is_none()); + } + + #[test] + fn it_creates_history_file_if_needed() { + let temp_dir = temp_dir(); + let file_path = temp_dir.join("history.json"); + + let result = update_entry( + file_path.to_str().unwrap(), + &TaskRunnerMode::Install, + "task1", + ); + assert!(result.is_ok()); + + let file_contents = fs::read_to_string(file_path).unwrap(); + + let entries: BTreeMap = serde_json::from_str(&file_contents).unwrap(); + let entry = &entries["task1"]; + + assert!(DateTime::parse_from_rfc3339(entry.installed_at.as_ref().unwrap()).is_ok()); + assert!(entry.uninstalled_at.is_none()); + } +} diff --git a/src/config/mod.rs b/src/config/mod.rs index 7736265..a59b6d5 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -1,5 +1,6 @@ pub mod base_config; pub mod config_value; +pub mod history; pub mod json_config; pub mod os; pub mod validation_rules; diff --git a/src/task.rs b/src/task.rs index 904c3a7..12cfe60 100644 --- a/src/task.rs +++ b/src/task.rs @@ -1,11 +1,13 @@ use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::{Arc, Mutex}; +use std::thread; use std::{env, str::FromStr}; use ansi_term::Color::{Green, Red, White, Yellow}; use indicatif::{MultiProgress, ProgressBar, ProgressStyle}; -use tracing::{debug, error, info}; +use tracing::{debug, error, info, warn}; +use crate::config::history::{clear_entry, get_history_path, is_logged, update_entry}; use crate::{ command::{get_command, CommandConfig, CommandInterface}, config::{base_config::Command, config_value::ConfigValue, os::Os}, @@ -66,7 +68,20 @@ impl Task { ); } - let task_name = self.name.clone(); + let history_path = get_history_path(&config.temp_dir); + if let Err(history_err) = history_path { + warn!("[{}] {}", &self.name, history_err); + } else { + let history_path = history_path.unwrap(); + + // TODO: Implement --force option... + if mode != TaskRunnerMode::Update + && is_logged(&history_path, &mode, &self.name).unwrap_or(false) + { + debug!("[{}] {}: Command was already run.", &self.name, mode); + return Ok(()); + } + } let pb = ProgressBar::new(commands.len().try_into().unwrap()).with_style( ProgressStyle::default_bar() @@ -86,7 +101,7 @@ impl Task { let c = config.clone(); let errors = Arc::clone(&has_errors); let progress = Arc::clone(&progress_bar); - let task = task_name.clone(); + let task = self.name.clone(); let run = move || { let p = progress.lock().unwrap(); @@ -146,6 +161,23 @@ impl Task { )); p.inc(1); drop(p); + + // FIXME: Add locks... + thread::spawn(move || { + debug!("Updating timestamp..."); + + let history_path = get_history_path(&c.temp_dir); + if let Err(history_err) = history_path { + warn!("[{}] {}", task, history_err); + return; + } + let history_path = history_path.unwrap(); + + let result = update_entry(&history_path, &mode, &task); + if let Err(update_err) = result { + warn!("[{}] Couldn't update timestamp\n\t{}", task, update_err); + } + }); }; thread_pool.execute(run); @@ -155,15 +187,34 @@ impl Task { if has_errors.load(Ordering::Relaxed) { progress_bar.lock().unwrap().finish_with_message(format!( "❌ {} ➡️ {}", - Red.paint(&task_name), + Red.paint(&self.name), Red.bold().paint("ERR") )); + // FIXME: Add locks... + let temp_dir = config.temp_dir.clone(); + let task = self.name.clone(); + thread::spawn(move || { + debug!("Clearing timestamp..."); + + let history_path = get_history_path(&temp_dir); + if let Err(history_err) = history_path { + warn!("[{}] {}", task, history_err); + return; + } + let history_path = history_path.unwrap(); + + let result = clear_entry(&history_path, &mode, &task); + if let Err(clear_err) = result { + warn!("[{}] Couldn't clear timestamp\n\t{}", task, clear_err); + } + }); + Err(format!("{}", Red.paint("Task has errors"))) } else { progress_bar.lock().unwrap().finish_with_message(format!( "✅ {} ➡️ {}", - Green.paint(&task_name), + Green.paint(&self.name), Green.bold().paint("OK") )); diff --git a/src/task_runner.rs b/src/task_runner.rs index 6e3f7c7..6836678 100644 --- a/src/task_runner.rs +++ b/src/task_runner.rs @@ -7,7 +7,7 @@ use tracing::debug; use crate::{command::CommandConfig, config::base_config::TaskList, utils::threads::ThreadPool}; -#[derive(Debug, Clone, Copy)] +#[derive(Debug, Clone, Copy, PartialEq)] pub enum TaskRunnerMode { Install, Update, diff --git a/src/terminal/cli.rs b/src/terminal/cli.rs index c918469..c3f63d8 100644 --- a/src/terminal/cli.rs +++ b/src/terminal/cli.rs @@ -52,18 +52,23 @@ pub struct Args { #[clap(global = true)] pub task: Option, - /// Select a task to run + /// select a task to run #[clap(short, long)] #[clap(global = true)] pub select: bool, - /// Add debug information + /// add debug information #[clap(short, long)] #[clap(global = true)] pub debug: bool, - /// Set log level + /// set log level #[clap(short, long, default_value = "warn")] #[clap(global = true)] pub level: Level, + + /// force installation/uninstallation + #[clap(short, long, default_value_t = false)] + #[clap(global = true)] + pub force: bool, } diff --git a/src/terminal/command.rs b/src/terminal/command.rs index 99cfcaf..b9411b3 100644 --- a/src/terminal/command.rs +++ b/src/terminal/command.rs @@ -149,6 +149,7 @@ mod test { select: false, level: Level::ERROR, debug: false, + force: false, }; let tasks = vec![Task { @@ -172,6 +173,7 @@ mod test { select: true, level: Level::ERROR, debug: false, + force: false, }; let tasks = vec![Task {