From 07a525da9c0e0306b68cdc8eb1badecf3084e7dd Mon Sep 17 00:00:00 2001 From: Roey Darwish Dror Date: Tue, 22 Jan 2019 22:37:32 +0200 Subject: [PATCH] Allow disabling steps from the configuration file --- README.md | 3 ++ src/config.rs | 111 +++++++++++++++++++++++++++++++++++++--------- src/main.rs | 120 ++++++++++++++++++++++++++++---------------------- 3 files changed, 160 insertions(+), 74 deletions(-) diff --git a/README.md b/README.md index b34922e3..0f639aef 100644 --- a/README.md +++ b/README.md @@ -127,6 +127,9 @@ git_repos = [ "~/dev/topgrade", ] +# Same options as the command line flag +disable = ["system", "emacs"] + [pre_commands] "Emacs Snapshot" = "rm -rf ~/.emacs.d/elpa.bak && cp -rl ~/.emacs.d/elpa ~/.emacs.d/elpa.bak" diff --git a/src/config.rs b/src/config.rs index 3bc57324..7a393f99 100644 --- a/src/config.rs +++ b/src/config.rs @@ -27,7 +27,8 @@ lazy_static! { }; } -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone, PartialEq, Deserialize)] +#[serde(rename_all = "lowercase")] pub enum Step { /// Don't perform system upgrade System, @@ -59,14 +60,19 @@ impl std::str::FromStr for Step { } #[derive(Deserialize, Default)] -pub struct Config { +/// Configuration file +pub struct ConfigFile { pre_commands: Option, commands: Option, git_repos: Option>, + disable: Option>, } -impl Config { - pub fn read(base_dirs: &BaseDirs) -> Result { +impl ConfigFile { + /// Read the configuration file. + /// + /// If the configuration file does not exist the function returns the default ConfigFile. + fn read(base_dirs: &BaseDirs) -> Result { let config_path = base_dirs.config_dir().join("topgrade.toml"); if !config_path.exists() { return Ok(Default::default()); @@ -83,40 +89,101 @@ impl Config { Ok(result) } - - pub fn pre_commands(&self) -> &Option { - &self.pre_commands - } - - pub fn commands(&self) -> &Option { - &self.commands - } - - pub fn git_repos(&self) -> &Option> { - &self.git_repos - } } #[derive(StructOpt, Debug)] #[structopt(name = "Topgrade")] -pub struct Opt { +/// Command line arguments +pub struct CommandLineArgs { /// Run inside tmux #[structopt(short = "t", long = "tmux")] - pub run_in_tmux: bool, + run_in_tmux: bool, /// Cleanup temporary or old files #[structopt(short = "c", long = "cleanup")] - pub cleanup: bool, + cleanup: bool, /// Print what would be done #[structopt(short = "n", long = "dry-run")] - pub dry_run: bool, + dry_run: bool, /// Do not ask to retry failed steps #[structopt(long = "no-retry")] - pub no_retry: bool, + no_retry: bool, /// Do not perform upgrades for the given steps #[structopt(long = "disable", raw(possible_values = "&Step::possible_values()"))] - pub disable: Vec, + disable: Vec, +} + +/// Represents the application configuration +/// +/// The struct holds the loaded configuration file, as well as the arguments parsed from the command line. +/// Its provided methods decide the appropriate options based on combining the configuraiton file and the +/// command line arguments. +pub struct Config { + opt: CommandLineArgs, + config_file: ConfigFile, +} + +impl Config { + /// Load the configuration. + /// + /// The function parses the command line arguments and reading the configuration file. + pub fn load(base_dirs: &BaseDirs) -> Result { + Ok(Self { + opt: CommandLineArgs::from_args(), + config_file: ConfigFile::read(base_dirs)?, + }) + } + + /// The list of commands to run before performing any step. + pub fn pre_commands(&self) -> &Option { + &self.config_file.pre_commands + } + + /// The list of custom steps. + pub fn commands(&self) -> &Option { + &self.config_file.commands + } + + /// The list of additional git repositories to pull. + pub fn git_repos(&self) -> &Option> { + &self.config_file.git_repos + } + + /// Tell whether the specified step should run. + /// + /// If the step appears either in the `--disable` command line argument + /// or the `disable` option in the configuration, the function returns false. + pub fn should_run(&self, step: Step) -> bool { + !(self + .config_file + .disable + .as_ref() + .map(|d| d.contains(&step)) + .unwrap_or(false) + || self.opt.disable.contains(&step)) + } + + /// Tell whether we should run in tmux. + pub fn run_in_tmux(&self) -> bool { + self.opt.run_in_tmux + } + + /// Tell whether we should perform cleanup steps. + #[cfg(not(windows))] + pub fn cleanup(&self) -> bool { + self.opt.cleanup + } + + /// Tell whether we are dry-running. + pub fn dry_run(&self) -> bool { + self.opt.dry_run + } + + /// Tell whether we should not attempt to retry anything. + pub fn no_retry(&self) -> bool { + self.opt.no_retry + } } diff --git a/src/main.rs b/src/main.rs index e9e3c3ce..11b16fcb 100644 --- a/src/main.rs +++ b/src/main.rs @@ -21,7 +21,6 @@ use std::io; #[cfg(windows)] use std::path::PathBuf; use std::process::exit; -use structopt::StructOpt; fn execute<'a, F, M>(func: F, no_retry: bool) -> Result, Error> where @@ -52,9 +51,10 @@ where fn run() -> Result<(), Error> { ctrlc::set_handler(); - let opt = config::Opt::from_args(); + let base_dirs = directories::BaseDirs::new().ok_or(ErrorKind::NoBaseDirectories)?; + let config = Config::load(&base_dirs)?; - if opt.run_in_tmux && env::var("TMUX").is_err() { + if config.run_in_tmux() && env::var("TMUX").is_err() { #[cfg(unix)] { tmux::run_in_tmux(); @@ -63,16 +63,14 @@ fn run() -> Result<(), Error> { env_logger::init(); - let base_dirs = directories::BaseDirs::new().ok_or(ErrorKind::NoBaseDirectories)?; let git = git::Git::new(); let mut git_repos = git::Repositories::new(&git); - let config = Config::read(&base_dirs)?; let mut report = Report::new(); #[cfg(any(target_os = "freebsd", target_os = "linux"))] let sudo = utils::which("sudo"); - let run_type = executor::RunType::new(opt.dry_run); + let run_type = executor::RunType::new(config.dry_run()); #[cfg(feature = "self-update")] { @@ -97,8 +95,8 @@ fn run() -> Result<(), Error> { #[cfg(windows)] { - if powershell.profile().is_some() && !opt.disable.contains(&Step::Powershell) { - report.push_result(execute(|| powershell.update_modules(run_type), opt.no_retry)?); + if powershell.profile().is_some() && config.should_run(Step::Powershell) { + report.push_result(execute(|| powershell.update_modules(run_type), config.no_retry())?); } } @@ -107,36 +105,42 @@ fn run() -> Result<(), Error> { #[cfg(target_os = "linux")] { - if !opt.disable.contains(&Step::System) { + if config.should_run(Step::System) { match &distribution { Ok(distribution) => { report.push_result(execute( - || distribution.upgrade(&sudo, opt.cleanup, run_type), - opt.no_retry, + || distribution.upgrade(&sudo, config.cleanup(), run_type), + config.no_retry(), )?); } Err(e) => { println!("Error detecting current distribution: {}", e); } } - report.push_result(execute(|| linux::run_etc_update(&sudo, run_type), opt.no_retry)?); + report.push_result(execute(|| linux::run_etc_update(&sudo, run_type), config.no_retry())?); } } #[cfg(windows)] - report.push_result(execute(|| windows::run_chocolatey(run_type), opt.no_retry)?); + report.push_result(execute(|| windows::run_chocolatey(run_type), config.no_retry())?); #[cfg(windows)] - report.push_result(execute(|| windows::run_scoop(run_type), opt.no_retry)?); + report.push_result(execute(|| windows::run_scoop(run_type), config.no_retry())?); #[cfg(unix)] - report.push_result(execute(|| unix::run_homebrew(opt.cleanup, run_type), opt.no_retry)?); + report.push_result(execute( + || unix::run_homebrew(config.cleanup(), run_type), + config.no_retry(), + )?); #[cfg(target_os = "freebsd")] - report.push_result(execute(|| freebsd::upgrade_packages(&sudo, run_type), opt.no_retry)?); + report.push_result(execute( + || freebsd::upgrade_packages(&sudo, run_type), + config.no_retry(), + )?); #[cfg(unix)] - report.push_result(execute(|| unix::run_nix(run_type), opt.no_retry)?); + report.push_result(execute(|| unix::run_nix(run_type), config.no_retry())?); - if !opt.disable.contains(&Step::Emacs) { + if config.should_run(Step::Emacs) { #[cfg(unix)] git_repos.insert(base_dirs.home_dir().join(".emacs.d")); @@ -149,7 +153,7 @@ fn run() -> Result<(), Error> { } } - if !opt.disable.contains(&Step::Vim) { + if config.should_run(Step::Vim) { git_repos.insert(base_dirs.home_dir().join(".vim")); git_repos.insert(base_dirs.home_dir().join(".config/nvim")); } @@ -170,7 +174,7 @@ fn run() -> Result<(), Error> { } } - if !opt.disable.contains(&Step::GitRepos) { + if config.should_run(Step::GitRepos) { if let Some(custom_git_repos) = config.git_repos() { for git_repo in custom_git_repos { git_repos.insert(git_repo); @@ -178,39 +182,48 @@ fn run() -> Result<(), Error> { } } for repo in git_repos.repositories() { - report.push_result(execute(|| git.pull(&repo, run_type), opt.no_retry)?); + report.push_result(execute(|| git.pull(&repo, run_type), config.no_retry())?); } #[cfg(unix)] { - report.push_result(execute(|| unix::run_zplug(&base_dirs, run_type), opt.no_retry)?); - report.push_result(execute(|| unix::run_fisher(&base_dirs, run_type), opt.no_retry)?); - report.push_result(execute(|| tmux::run_tpm(&base_dirs, run_type), opt.no_retry)?); + report.push_result(execute(|| unix::run_zplug(&base_dirs, run_type), config.no_retry())?); + report.push_result(execute(|| unix::run_fisher(&base_dirs, run_type), config.no_retry())?); + report.push_result(execute(|| tmux::run_tpm(&base_dirs, run_type), config.no_retry())?); } - report.push_result(execute(|| generic::run_rustup(&base_dirs, run_type), opt.no_retry)?); - report.push_result(execute(|| generic::run_cargo_update(run_type), opt.no_retry)?); + report.push_result(execute( + || generic::run_rustup(&base_dirs, run_type), + config.no_retry(), + )?); + report.push_result(execute(|| generic::run_cargo_update(run_type), config.no_retry())?); - if !opt.disable.contains(&Step::Emacs) { - report.push_result(execute(|| generic::run_emacs(&base_dirs, run_type), opt.no_retry)?); + if config.should_run(Step::Emacs) { + report.push_result(execute(|| generic::run_emacs(&base_dirs, run_type), config.no_retry())?); } - report.push_result(execute(|| generic::run_opam_update(run_type), opt.no_retry)?); - report.push_result(execute(|| generic::run_vcpkg_update(run_type), opt.no_retry)?); - report.push_result(execute(|| generic::run_pipx_update(run_type), opt.no_retry)?); - report.push_result(execute(|| generic::run_jetpack(run_type), opt.no_retry)?); + report.push_result(execute(|| generic::run_opam_update(run_type), config.no_retry())?); + report.push_result(execute(|| generic::run_vcpkg_update(run_type), config.no_retry())?); + report.push_result(execute(|| generic::run_pipx_update(run_type), config.no_retry())?); + report.push_result(execute(|| generic::run_jetpack(run_type), config.no_retry())?); - if !opt.disable.contains(&Step::Vim) { - report.push_result(execute(|| vim::upgrade_vim(&base_dirs, run_type), opt.no_retry)?); - report.push_result(execute(|| vim::upgrade_neovim(&base_dirs, run_type), opt.no_retry)?); + if config.should_run(Step::Vim) { + report.push_result(execute(|| vim::upgrade_vim(&base_dirs, run_type), config.no_retry())?); + report.push_result(execute( + || vim::upgrade_neovim(&base_dirs, run_type), + config.no_retry(), + )?); } - report.push_result(execute(|| node::run_npm_upgrade(&base_dirs, run_type), opt.no_retry)?); + report.push_result(execute( + || node::run_npm_upgrade(&base_dirs, run_type), + config.no_retry(), + )?); report.push_result(execute( || generic::run_composer_update(&base_dirs, run_type), - opt.no_retry, + config.no_retry(), )?); - report.push_result(execute(|| node::yarn_global_update(run_type), opt.no_retry)?); + report.push_result(execute(|| node::yarn_global_update(run_type), config.no_retry())?); #[cfg(not(any( target_os = "freebsd", @@ -218,51 +231,54 @@ fn run() -> Result<(), Error> { target_os = "netbsd", target_os = "dragonfly" )))] - report.push_result(execute(|| generic::run_apm(run_type), opt.no_retry)?); + report.push_result(execute(|| generic::run_apm(run_type), config.no_retry())?); - if !opt.disable.contains(&Step::Gem) { - report.push_result(execute(|| generic::run_gem(&base_dirs, run_type), opt.no_retry)?); + if config.should_run(Step::Gem) { + report.push_result(execute(|| generic::run_gem(&base_dirs, run_type), config.no_retry())?); } #[cfg(target_os = "linux")] { - report.push_result(execute(|| linux::flatpak_update(run_type), opt.no_retry)?); - report.push_result(execute(|| linux::run_snap(&sudo, run_type), opt.no_retry)?); + report.push_result(execute(|| linux::flatpak_update(run_type), config.no_retry())?); + report.push_result(execute(|| linux::run_snap(&sudo, run_type), config.no_retry())?); } if let Some(commands) = config.commands() { for (name, command) in commands { report.push_result(execute( || Some((name, generic::run_custom_command(&name, &command, run_type).is_ok())), - opt.no_retry, + config.no_retry(), )?); } } #[cfg(target_os = "linux")] { - report.push_result(execute(|| linux::run_fwupdmgr(run_type), opt.no_retry)?); - report.push_result(execute(|| linux::run_needrestart(&sudo, run_type), opt.no_retry)?); + report.push_result(execute(|| linux::run_fwupdmgr(run_type), config.no_retry())?); + report.push_result(execute(|| linux::run_needrestart(&sudo, run_type), config.no_retry())?); } #[cfg(target_os = "macos")] { - if !opt.disable.contains(&Step::System) { - report.push_result(execute(|| macos::upgrade_macos(run_type), opt.no_retry)?); + if config.should_run(Step::System) { + report.push_result(execute(|| macos::upgrade_macos(run_type), config.no_retry())?); } } #[cfg(target_os = "freebsd")] { - if !opt.disable.contains(&Step::System) { - report.push_result(execute(|| freebsd::upgrade_freebsd(&sudo, run_type), opt.no_retry)?); + if config.should_run(Step::System) { + report.push_result(execute( + || freebsd::upgrade_freebsd(&sudo, run_type), + config.no_retry(), + )?); } } #[cfg(windows)] { - if !opt.disable.contains(&Step::System) { - report.push_result(execute(|| powershell.windows_update(run_type), opt.no_retry)?); + if config.should_run(Step::System) { + report.push_result(execute(|| powershell.windows_update(run_type), config.no_retry())?); } }