diff --git a/Cargo.lock b/Cargo.lock index cb0b3e5..df2dee9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -11,6 +11,21 @@ dependencies = [ "memchr", ] +[[package]] +name = "android-tzdata" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" + +[[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" @@ -74,6 +89,12 @@ version = "1.0.75" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6" +[[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" @@ -86,6 +107,21 @@ version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07" +[[package]] +name = "bumpalo" +version = "3.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f30e7476521f6f8af1a1c4c0b8cc94f0bee37d91763d0ca2665f299b6cd8aec" + +[[package]] +name = "cc" +version = "1.0.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0" +dependencies = [ + "libc", +] + [[package]] name = "cfg-if" version = "0.1.10" @@ -98,6 +134,20 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "chrono" +version = "0.4.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f2c685bad3eb3d45a01354cedb7d5faa66194d1d58ba6e267a8de788f79db38" +dependencies = [ + "android-tzdata", + "iana-time-zone", + "js-sys", + "num-traits", + "wasm-bindgen", + "windows-targets 0.48.5", +] + [[package]] name = "clap" version = "4.4.10" @@ -178,6 +228,12 @@ dependencies = [ "windows-sys 0.45.0", ] +[[package]] +name = "core-foundation-sys" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa" + [[package]] name = "dialoguer" version = "0.11.0" @@ -327,6 +383,29 @@ version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d77f7ec81a6d05a3abb01ab6eb7590f6083d08449fe5a1c8b1e620283546ccb7" +[[package]] +name = "iana-time-zone" +version = "0.1.58" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8326b86b6cff230b97d0d312a6c40a60726df3332e721f72a1b035f451663b20" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "wasm-bindgen", + "windows-core", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + [[package]] name = "indicatif" version = "0.17.7" @@ -366,6 +445,15 @@ version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38" +[[package]] +name = "js-sys" +version = "0.3.66" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cee9c64da59eae3b50095c18d3e74f8b73c0b86d2792824ff01bbce68ba229ca" +dependencies = [ + "wasm-bindgen", +] + [[package]] name = "lazy_static" version = "1.4.0" @@ -412,6 +500,7 @@ name = "machine_setup" version = "1.3.6" dependencies = [ "ansi_term", + "chrono", "clap", "clap_complete", "dialoguer", @@ -423,6 +512,7 @@ dependencies = [ "once_cell", "rand 0.8.5", "regex", + "serde", "serde_json", "symlink", "tempfile", @@ -447,6 +537,15 @@ dependencies = [ "winapi", ] +[[package]] +name = "num-traits" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39e3200413f237f41ab11ad6d161bc7239c84dcb631773ccd7de3dfe4b5c267c" +dependencies = [ + "autocfg", +] + [[package]] name = "num_cpus" version = "1.16.0" @@ -962,6 +1061,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.89" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ed0d4f68a3015cc185aff4db9506a015f4b96f95303897bfa23f846db54064e" +dependencies = [ + "cfg-if 1.0.0", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.89" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b56f625e64f3a1084ded111c4d5f477df9f8c92df113852fa5a374dbda78826" +dependencies = [ + "bumpalo", + "log", + "once_cell", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.89" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0162dbf37223cd2afce98f3d0785506dcb8d266223983e4b5b525859e6e182b2" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.89" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0eb82fcb7930ae6219a7ecfd55b217f5f0893484b7a13022ebb2b2bf20b5283" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.89" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ab9b36309365056cd639da3134bf87fa8f3d86008abf99e612384a6eecd459f" + [[package]] name = "winapi" version = "0.3.9" @@ -993,6 +1146,15 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +[[package]] +name = "windows-core" +version = "0.51.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1f8cf84f35d2db49a46868f947758c7a1138116f7fac3bc844f43ade1292e64" +dependencies = [ + "windows-targets 0.48.5", +] + [[package]] name = "windows-sys" version = "0.45.0" diff --git a/Cargo.toml b/Cargo.toml index 128d586..34fecae 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.11" regex = "1.10.2" +chrono = "0.4" diff --git a/TODOS.md b/TODOS.md index 1f40852..d4c9c59 100644 --- a/TODOS.md +++ b/TODOS.md @@ -1,11 +1,7 @@ # TODOs -- only show 'run command environment' when log level is info/debug -- 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 c770130..52f75f8 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 30631ad..1e13caa 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 5616ee5..9c85708 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..14c92a8 --- /dev/null +++ b/src/config/history.rs @@ -0,0 +1,418 @@ +use crate::task_runner::TaskRunnerMode; +use chrono::Utc; +use ergo_fs::{IoWrite, Path, Read}; +use serde::{Deserialize, Serialize}; +use std::collections::BTreeMap; +use std::fs::{File, OpenOptions}; + +#[derive(Serialize, Deserialize, Debug, Clone, Default, PartialEq)] +pub struct TaskEntry { + installed_at: Option, + updated_at: Option, + uninstalled_at: Option, +} + +pub fn get_history_file(temp_dir: &str) -> Result { + let path = Path::new(temp_dir); + + if !path.exists() { + std::fs::create_dir_all(temp_dir)?; + } + + OpenOptions::new() + .read(true) + .write(true) + .create(true) + .open(path.join("history.json")) +} + +pub fn get_task_entry(temp_dir: &str, task_name: &str) -> Result { + let file = get_history_file(temp_dir); + if let Err(file_err) = file { + return Err(file_err.to_string()); + } + let mut file = file.unwrap(); + + let mut file_contents = String::new(); + if let Err(file_err) = file.read_to_string(&mut file_contents) { + return Err(format!("[{}] {}", task_name, file_err)); + } + + 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.to_owned()) +} + +pub fn is_logged(temp_dir: &str, mode: &TaskRunnerMode, task_name: &str) -> bool { + let entry = get_task_entry(temp_dir, task_name); + + if entry.is_err() { + return false; + } + + let entry = entry.unwrap(); + + 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(temp_dir: &str, mode: &TaskRunnerMode, task_name: &str) -> Result<(), String> { + let file = get_history_file(temp_dir); + if let Err(file_err) = file { + return Err(file_err.to_string()); + } + let mut file = file.unwrap(); + + let mut file_contents = String::new(); + if let Err(file_err) = file.read_to_string(&mut file_contents) { + return Err(format!("[{}] {}", task_name, file_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 = None, + TaskRunnerMode::Update => task_entry.updated_at = None, + TaskRunnerMode::Uninstall => task_entry.uninstalled_at = None, + } + + file.set_len(0).unwrap_or_default(); + if let Err(write_err) = file.write_all(serde_json::to_string(&entries).unwrap().as_bytes()) { + return Err(format!("{}: {}", task_name, write_err)); + } + + Ok(()) +} + +pub fn update_entry(temp_dir: &str, mode: &TaskRunnerMode, task_name: &str) -> Result<(), String> { + let file = get_history_file(temp_dir); + + if let Err(file_err) = file { + return Err(file_err.to_string()); + } + let mut file = file.unwrap(); + + let timestamp = Utc::now().to_rfc3339(); + + let mut file_contents = String::new(); + + if let Err(file_err) = file.read_to_string(&mut file_contents) { + return Err(format!("[{}] {}", task_name, file_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(trunc_err) = file.set_len(0) { + return Err(format!("{}: {}", task_name, trunc_err)); + } + + if let Err(write_err) = file.write_all(serde_json::to_string(&entries).unwrap().as_bytes()) { + return Err(format!("{}: {}", task_name, write_err)); + } + + Ok(()) +} + +#[cfg(test)] +mod test { + use chrono::DateTime; + use std::env::temp_dir; + use std::fs::read_to_string; + + use super::*; + + #[test] + fn it_clears_task_entry() { + let temp_dir = temp_dir(); + let file_path = temp_dir.join("history.json"); + + std::fs::write( + file_path.to_str().unwrap(), + 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( + temp_dir.to_str().unwrap(), + &TaskRunnerMode::Install, + "task1", + ); + assert!(result.is_ok()); + let result = clear_entry( + temp_dir.to_str().unwrap(), + &TaskRunnerMode::Uninstall, + "task1", + ); + assert!(result.is_ok()); + let result = clear_entry(temp_dir.to_str().unwrap(), &TaskRunnerMode::Update, "task1"); + assert!(result.is_ok()); + + let file_contents = 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 temp_dir = temp_dir(); + let result = get_task_entry(temp_dir.join("nope").to_str().unwrap(), "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 temp_dir = temp_dir(); + let file_path = temp_dir.join("history.json"); + + std::fs::write( + file_path.to_str().unwrap(), + 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(temp_dir.to_str().unwrap(), "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 temp_dir = temp_dir(); + let file_path = temp_dir.join("history.json"); + + std::fs::write( + file_path.to_str().unwrap(), + 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 logged = is_logged( + temp_dir.to_str().unwrap(), + &TaskRunnerMode::Install, + "task1", + ); + assert!(logged); + + let logged = is_logged( + temp_dir.to_str().unwrap(), + &TaskRunnerMode::Install, + "task2", + ); + assert!(!logged); + } + + #[test] + fn it_updates_entry_in_existing_file() { + let temp_dir = temp_dir(); + let file_path = temp_dir.join("history.json"); + + std::fs::write( + file_path.to_str().unwrap(), + 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( + temp_dir.to_str().unwrap(), + &TaskRunnerMode::Install, + "task2", + ); + assert!(result.is_ok()); + + let file_contents = 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( + temp_dir.to_str().unwrap(), + &TaskRunnerMode::Uninstall, + "task1", + ); + assert!(result.is_ok()); + + let file_contents = 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 temp_dir = temp_dir(); + let file_path = temp_dir.join("history.json"); + + std::fs::write( + file_path.to_str().unwrap(), + r#"{ + "task1": { + "installed_at": null, + "updated_at": null, + "uninstalled_at": null + }, + }"# + .as_bytes(), + ) + .unwrap(); + + let result = update_entry( + temp_dir.to_str().unwrap(), + &TaskRunnerMode::Install, + "task2", + ); + assert!(result.is_ok()); + + let file_contents = 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 temp_dir = temp_dir(); + + let result = update_entry( + temp_dir.to_str().unwrap(), + &TaskRunnerMode::Install, + "task1", + ); + assert!(result.is_ok()); + + let file_path = temp_dir.join("history.json"); + let file_contents = 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 result = update_entry( + temp_dir.to_str().unwrap(), + &TaskRunnerMode::Install, + "task1", + ); + assert!(result.is_ok()); + + let file_path = temp_dir.join("history.json"); + let file_contents = 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/main.rs b/src/main.rs index 760184a..33f1cbf 100644 --- a/src/main.rs +++ b/src/main.rs @@ -4,7 +4,7 @@ use clap::Parser; use once_cell::sync::OnceCell; use terminal::{cli::Args, command::execute_command}; use tracing::metadata::LevelFilter; -use tracing::Level; +use tracing::{Level, error}; use tracing_subscriber::prelude::*; pub mod command; @@ -21,6 +21,9 @@ static DEBUG_MODE: OnceCell = OnceCell::new(); fn main() { let args = Args::parse(); + LOG_LEVEL.set(args.level).unwrap_or_default(); + DEBUG_MODE.set(args.debug).unwrap_or_default(); + let fmt_layer = tracing_subscriber::fmt::layer() .pretty() .with_level(args.debug) @@ -36,11 +39,9 @@ fn main() { .try_init(); if let Err(sub_err) = subscriber { - println!("{sub_err:?}"); + error!("{sub_err:?}"); } - LOG_LEVEL.set(args.level).unwrap(); - DEBUG_MODE.set(args.debug).unwrap(); execute_command(args) } diff --git a/src/task.rs b/src/task.rs index 904c3a7..785ecb7 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::{is_logged, update_entry}; use crate::{ command::{get_command, CommandConfig, CommandInterface}, config::{base_config::Command, config_value::ConfigValue, os::Os}, @@ -66,7 +68,13 @@ impl Task { ); } - let task_name = self.name.clone(); + // TODO: Implement --force option... + if mode != TaskRunnerMode::Update && is_logged(&config.temp_dir, &mode, &self.name) + { + // TODO: Show something like 'skipped' as status instead of 'OK' + 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 +94,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(); @@ -155,7 +163,7 @@ 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") )); @@ -163,10 +171,22 @@ impl Task { } else { progress_bar.lock().unwrap().finish_with_message(format!( "✅ {} ➡️ {}", - Green.paint(&task_name), + Green.paint(&self.name), Green.bold().paint("OK") )); + // FIXME: Add locks... + let temp_dir = config.temp_dir.clone(); + let task = self.name.clone(); + thread::spawn(move || { + debug!("[{}] Updating history...", task); + + let result = update_entry(&temp_dir, &mode, &task); + if let Err(update_err) = result { + warn!("[{}] Couldn't update timestamp\n\t{}", task, update_err); + } + }); + 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 { diff --git a/src/utils/terminal.rs b/src/utils/terminal.rs index acb1ee4..2836dc0 100644 --- a/src/utils/terminal.rs +++ b/src/utils/terminal.rs @@ -1,6 +1,7 @@ use ansi_term::Color::White; use ergo_fs::PathBuf; use std::env; +use tracing::info; use crate::config::config_value::ConfigValue; use crate::config::validator::arguments_are_named; @@ -27,13 +28,12 @@ pub fn set_environment_variables(args: &ConfigValue) -> Result<(), String> { let env = parse_environment_variables(args.to_owned())?; if let Some(env) = env { - println!("Environment"); - println!("-----------------------------"); - if !env.is_hash() { return Err(String::from("Environment needs to be defined as a map")); } + let mut values: Vec = vec![]; + for (key, value) in env.as_hash().unwrap() { let env_value_raw = value.as_str().unwrap(); @@ -43,10 +43,14 @@ pub fn set_environment_variables(args: &ConfigValue) -> Result<(), String> { let env_value = expanded_value.to_str().unwrap(); env::set_var(key, env_value); - println!("{}={}", key, White.bold().paint(env_value)); + + values.push(format!("{}={}", key, White.bold().paint(env_value))); } - println!("-----------------------------"); + info!( + "Environment\n-----------------------------\n{}\n-----------------------------", + values.join("\n") + ); } Ok(())