Added ability to include directories as an extension of the config file (#421)
This commit is contained in:
37
Cargo.lock
generated
37
Cargo.lock
generated
@@ -1088,6 +1088,28 @@ dependencies = [
|
|||||||
"autocfg",
|
"autocfg",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "merge"
|
||||||
|
version = "0.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "10bbef93abb1da61525bbc45eeaff6473a41907d19f8f9aa5168d214e10693e9"
|
||||||
|
dependencies = [
|
||||||
|
"merge_derive",
|
||||||
|
"num-traits",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "merge_derive"
|
||||||
|
version = "0.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "209d075476da2e63b4b29e72a2ef627b840589588e71400a25e3565c4f849d07"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro-error",
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn 1.0.109",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "mime"
|
name = "mime"
|
||||||
version = "0.3.17"
|
version = "0.3.17"
|
||||||
@@ -1494,9 +1516,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "regex"
|
name = "regex"
|
||||||
version = "1.5.6"
|
version = "1.7.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d83f127d94bdbcda4c8cc2e50f6f84f4b611f69c902699ca385a39c3a75f9ff1"
|
checksum = "8b1f693b24f6ac912f4893ef08244d70b6067480d2f1a46e950c9691e6749d1d"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"aho-corasick",
|
"aho-corasick",
|
||||||
"memchr",
|
"memchr",
|
||||||
@@ -1512,6 +1534,15 @@ dependencies = [
|
|||||||
"regex-syntax",
|
"regex-syntax",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "regex-split"
|
||||||
|
version = "0.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "544861d1810a3e429bb5e80266e537138b0e69e59bed9334326ae129a4c3e676"
|
||||||
|
dependencies = [
|
||||||
|
"regex",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "regex-syntax"
|
name = "regex-syntax"
|
||||||
version = "0.6.29"
|
version = "0.6.29"
|
||||||
@@ -2137,11 +2168,13 @@ dependencies = [
|
|||||||
"home",
|
"home",
|
||||||
"lazy_static",
|
"lazy_static",
|
||||||
"libc",
|
"libc",
|
||||||
|
"merge",
|
||||||
"nix 0.24.3",
|
"nix 0.24.3",
|
||||||
"notify-rust",
|
"notify-rust",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"parselnk",
|
"parselnk",
|
||||||
"regex",
|
"regex",
|
||||||
|
"regex-split",
|
||||||
"rust-ini",
|
"rust-ini",
|
||||||
"self_update",
|
"self_update",
|
||||||
"semver",
|
"semver",
|
||||||
|
|||||||
@@ -41,12 +41,14 @@ tempfile = "~3.2"
|
|||||||
cfg-if = "~1.0"
|
cfg-if = "~1.0"
|
||||||
tokio = { version = "~1.18", features = ["process", "rt-multi-thread"] }
|
tokio = { version = "~1.18", features = ["process", "rt-multi-thread"] }
|
||||||
futures = "~0.3"
|
futures = "~0.3"
|
||||||
regex = "~1.5"
|
regex = "~1.7"
|
||||||
semver = "~1.0"
|
semver = "~1.0"
|
||||||
shell-words = "~1.1"
|
shell-words = "~1.1"
|
||||||
color-eyre = "~0.6"
|
color-eyre = "~0.6"
|
||||||
tracing = { version = "~0.1", features = ["attributes", "log"] }
|
tracing = { version = "~0.1", features = ["attributes", "log"] }
|
||||||
tracing-subscriber = { version = "~0.3", features = ["env-filter", "time"] }
|
tracing-subscriber = { version = "~0.3", features = ["env-filter", "time"] }
|
||||||
|
merge = "0.1.0"
|
||||||
|
regex-split = "0.1.0"
|
||||||
|
|
||||||
[target.'cfg(target_os = "macos")'.dependencies]
|
[target.'cfg(target_os = "macos")'.dependencies]
|
||||||
notify-rust = "~4.5"
|
notify-rust = "~4.5"
|
||||||
|
|||||||
@@ -1,3 +1,10 @@
|
|||||||
|
# Include any additional configuration file(s)
|
||||||
|
# [include] sections are processed in the order you write them
|
||||||
|
# Files in $CONFIG_DIR/topgrade/topgrade.d/ are automatically included at the beginning of this file
|
||||||
|
[include]
|
||||||
|
#paths = ["/etc/topgrade.toml"]
|
||||||
|
|
||||||
|
[misc]
|
||||||
# Don't ask for confirmations
|
# Don't ask for confirmations
|
||||||
#assume_yes = true
|
#assume_yes = true
|
||||||
|
|
||||||
|
|||||||
588
src/config.rs
588
src/config.rs
@@ -1,17 +1,20 @@
|
|||||||
#![allow(dead_code)]
|
#![allow(dead_code)]
|
||||||
|
|
||||||
use std::collections::BTreeMap;
|
use std::collections::BTreeMap;
|
||||||
use std::fs::write;
|
use std::fs::{write, File};
|
||||||
use std::path::PathBuf;
|
use std::io::Write;
|
||||||
|
use std::path::{Path, PathBuf};
|
||||||
use std::process::Command;
|
use std::process::Command;
|
||||||
use std::{env, fs};
|
use std::{env, fs};
|
||||||
|
|
||||||
use clap::{ArgEnum, Parser};
|
use clap::{ArgEnum, Parser};
|
||||||
use clap_complete::Shell;
|
use clap_complete::Shell;
|
||||||
use color_eyre::eyre;
|
|
||||||
use color_eyre::eyre::Context;
|
use color_eyre::eyre::Context;
|
||||||
use color_eyre::eyre::Result;
|
use color_eyre::eyre::Result;
|
||||||
use etcetera::base_strategy::BaseStrategy;
|
use etcetera::base_strategy::BaseStrategy;
|
||||||
|
use merge::Merge;
|
||||||
use regex::Regex;
|
use regex::Regex;
|
||||||
|
use regex_split::RegexSplit;
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use strum::{EnumIter, EnumString, EnumVariantNames, IntoEnumIterator};
|
use strum::{EnumIter, EnumString, EnumVariantNames, IntoEnumIterator};
|
||||||
use tracing::debug;
|
use tracing::debug;
|
||||||
@@ -19,6 +22,7 @@ use which_crate::which;
|
|||||||
|
|
||||||
use crate::command::CommandExt;
|
use crate::command::CommandExt;
|
||||||
use crate::sudo::SudoKind;
|
use crate::sudo::SudoKind;
|
||||||
|
use crate::utils::string_prepend_str;
|
||||||
|
|
||||||
use super::utils::{editor, hostname};
|
use super::utils::{editor, hostname};
|
||||||
|
|
||||||
@@ -51,21 +55,43 @@ macro_rules! check_deprecated {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
macro_rules! get_deprecated {
|
|
||||||
($config:expr, $old:ident, $section:ident, $new:ident) => {
|
/// Get a deprecated option moved from a section to another
|
||||||
if $config.$old.is_some() {
|
macro_rules! get_deprecated_moved_opt {
|
||||||
&$config.$old
|
($old_section:expr, $old:ident, $new_section:expr, $new:ident) => {{
|
||||||
} else {
|
if let Some(old_section) = &$old_section {
|
||||||
if let Some(section) = &$config.$section {
|
if old_section.$old.is_some() {
|
||||||
§ion.$new
|
return &old_section.$old;
|
||||||
} else {
|
|
||||||
&None
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
|
||||||
|
if let Some(new_section) = &$new_section {
|
||||||
|
return &new_section.$new;
|
||||||
|
}
|
||||||
|
|
||||||
|
return &None;
|
||||||
|
}};
|
||||||
}
|
}
|
||||||
|
|
||||||
type Commands = BTreeMap<String, String>;
|
macro_rules! get_deprecated_moved_or_default_to {
|
||||||
|
($old_section:expr, $old:ident, $new_section:expr, $new:ident, $default_ret:ident) => {{
|
||||||
|
if let Some(old_section) = &$old_section {
|
||||||
|
if let Some(old) = old_section.$old {
|
||||||
|
return old;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(new_section) = &$new_section {
|
||||||
|
if let Some(new) = new_section.$new {
|
||||||
|
return new;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $default_ret;
|
||||||
|
}};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub type Commands = BTreeMap<String, String>;
|
||||||
|
|
||||||
#[derive(ArgEnum, EnumString, EnumVariantNames, Debug, Clone, PartialEq, Eq, Deserialize, EnumIter, Copy)]
|
#[derive(ArgEnum, EnumString, EnumVariantNames, Debug, Clone, PartialEq, Eq, Deserialize, EnumIter, Copy)]
|
||||||
#[clap(rename_all = "snake_case")]
|
#[clap(rename_all = "snake_case")]
|
||||||
@@ -169,24 +195,38 @@ pub enum Step {
|
|||||||
Yarn,
|
Yarn,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize, Default, Debug)]
|
#[derive(Deserialize, Default, Debug, Merge)]
|
||||||
|
#[serde(deny_unknown_fields)]
|
||||||
|
pub struct Include {
|
||||||
|
#[merge(strategy = merge::vec::append)]
|
||||||
|
paths: Vec<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Default, Debug, Merge)]
|
||||||
#[serde(deny_unknown_fields)]
|
#[serde(deny_unknown_fields)]
|
||||||
pub struct Git {
|
pub struct Git {
|
||||||
max_concurrency: Option<usize>,
|
max_concurrency: Option<usize>,
|
||||||
|
|
||||||
|
#[merge(strategy = crate::utils::merge_strategies::string_append_opt)]
|
||||||
arguments: Option<String>,
|
arguments: Option<String>,
|
||||||
|
|
||||||
|
#[merge(strategy = crate::utils::merge_strategies::vec_prepend_opt)]
|
||||||
repos: Option<Vec<String>>,
|
repos: Option<Vec<String>>,
|
||||||
|
|
||||||
pull_predefined: Option<bool>,
|
pull_predefined: Option<bool>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize, Default, Debug)]
|
#[derive(Deserialize, Default, Debug, Merge)]
|
||||||
#[serde(deny_unknown_fields)]
|
#[serde(deny_unknown_fields)]
|
||||||
pub struct Vagrant {
|
pub struct Vagrant {
|
||||||
|
#[merge(strategy = crate::utils::merge_strategies::vec_prepend_opt)]
|
||||||
directories: Option<Vec<String>>,
|
directories: Option<Vec<String>>,
|
||||||
|
|
||||||
power_on: Option<bool>,
|
power_on: Option<bool>,
|
||||||
always_suspend: Option<bool>,
|
always_suspend: Option<bool>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize, Default, Debug)]
|
#[derive(Deserialize, Default, Debug, Merge)]
|
||||||
#[serde(deny_unknown_fields)]
|
#[serde(deny_unknown_fields)]
|
||||||
pub struct Windows {
|
pub struct Windows {
|
||||||
accept_all_updates: Option<bool>,
|
accept_all_updates: Option<bool>,
|
||||||
@@ -197,7 +237,7 @@ pub struct Windows {
|
|||||||
wsl_update_use_web_download: Option<bool>,
|
wsl_update_use_web_download: Option<bool>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize, Default, Debug)]
|
#[derive(Deserialize, Default, Debug, Merge)]
|
||||||
#[serde(deny_unknown_fields)]
|
#[serde(deny_unknown_fields)]
|
||||||
pub struct Python {
|
pub struct Python {
|
||||||
enable_pip_review: Option<bool>,
|
enable_pip_review: Option<bool>,
|
||||||
@@ -205,43 +245,45 @@ pub struct Python {
|
|||||||
enable_pipupgrade: Option<bool>,
|
enable_pipupgrade: Option<bool>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize, Default, Debug)]
|
#[derive(Deserialize, Default, Debug, Merge)]
|
||||||
#[serde(deny_unknown_fields)]
|
#[serde(deny_unknown_fields)]
|
||||||
#[allow(clippy::upper_case_acronyms)]
|
#[allow(clippy::upper_case_acronyms)]
|
||||||
pub struct Distrobox {
|
pub struct Distrobox {
|
||||||
use_root: Option<bool>,
|
use_root: Option<bool>,
|
||||||
|
|
||||||
|
#[merge(strategy = crate::utils::merge_strategies::vec_prepend_opt)]
|
||||||
containers: Option<Vec<String>>,
|
containers: Option<Vec<String>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize, Default, Debug)]
|
#[derive(Deserialize, Default, Debug, Merge)]
|
||||||
#[serde(deny_unknown_fields)]
|
#[serde(deny_unknown_fields)]
|
||||||
#[allow(clippy::upper_case_acronyms)]
|
#[allow(clippy::upper_case_acronyms)]
|
||||||
pub struct Yarn {
|
pub struct Yarn {
|
||||||
use_sudo: Option<bool>,
|
use_sudo: Option<bool>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize, Default, Debug)]
|
#[derive(Deserialize, Default, Debug, Merge)]
|
||||||
#[serde(deny_unknown_fields)]
|
#[serde(deny_unknown_fields)]
|
||||||
#[allow(clippy::upper_case_acronyms)]
|
#[allow(clippy::upper_case_acronyms)]
|
||||||
pub struct NPM {
|
pub struct NPM {
|
||||||
use_sudo: Option<bool>,
|
use_sudo: Option<bool>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize, Default, Debug)]
|
#[derive(Deserialize, Default, Debug, Merge)]
|
||||||
#[serde(deny_unknown_fields)]
|
#[serde(deny_unknown_fields)]
|
||||||
#[allow(clippy::upper_case_acronyms)]
|
#[allow(clippy::upper_case_acronyms)]
|
||||||
pub struct Firmware {
|
pub struct Firmware {
|
||||||
upgrade: Option<bool>,
|
upgrade: Option<bool>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize, Default, Debug)]
|
#[derive(Deserialize, Default, Debug, Merge)]
|
||||||
#[serde(deny_unknown_fields)]
|
#[serde(deny_unknown_fields)]
|
||||||
#[allow(clippy::upper_case_acronyms)]
|
#[allow(clippy::upper_case_acronyms)]
|
||||||
pub struct Flatpak {
|
pub struct Flatpak {
|
||||||
use_sudo: Option<bool>,
|
use_sudo: Option<bool>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize, Default, Debug)]
|
#[derive(Deserialize, Default, Debug, Merge)]
|
||||||
#[serde(deny_unknown_fields)]
|
#[serde(deny_unknown_fields)]
|
||||||
pub struct Brew {
|
pub struct Brew {
|
||||||
greedy_cask: Option<bool>,
|
greedy_cask: Option<bool>,
|
||||||
@@ -262,88 +304,191 @@ pub enum ArchPackageManager {
|
|||||||
Yay,
|
Yay,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize, Default, Debug)]
|
#[derive(Deserialize, Default, Debug, Merge)]
|
||||||
#[serde(deny_unknown_fields)]
|
#[serde(deny_unknown_fields)]
|
||||||
pub struct Linux {
|
pub struct Linux {
|
||||||
|
#[merge(strategy = crate::utils::merge_strategies::string_append_opt)]
|
||||||
yay_arguments: Option<String>,
|
yay_arguments: Option<String>,
|
||||||
|
|
||||||
|
#[merge(strategy = crate::utils::merge_strategies::string_append_opt)]
|
||||||
aura_aur_arguments: Option<String>,
|
aura_aur_arguments: Option<String>,
|
||||||
|
|
||||||
|
#[merge(strategy = crate::utils::merge_strategies::string_append_opt)]
|
||||||
aura_pacman_arguments: Option<String>,
|
aura_pacman_arguments: Option<String>,
|
||||||
arch_package_manager: Option<ArchPackageManager>,
|
arch_package_manager: Option<ArchPackageManager>,
|
||||||
show_arch_news: Option<bool>,
|
show_arch_news: Option<bool>,
|
||||||
|
|
||||||
|
#[merge(strategy = crate::utils::merge_strategies::string_append_opt)]
|
||||||
garuda_update_arguments: Option<String>,
|
garuda_update_arguments: Option<String>,
|
||||||
|
|
||||||
|
#[merge(strategy = crate::utils::merge_strategies::string_append_opt)]
|
||||||
trizen_arguments: Option<String>,
|
trizen_arguments: Option<String>,
|
||||||
|
|
||||||
|
#[merge(strategy = crate::utils::merge_strategies::string_append_opt)]
|
||||||
pikaur_arguments: Option<String>,
|
pikaur_arguments: Option<String>,
|
||||||
|
|
||||||
|
#[merge(strategy = crate::utils::merge_strategies::string_append_opt)]
|
||||||
pamac_arguments: Option<String>,
|
pamac_arguments: Option<String>,
|
||||||
|
|
||||||
|
#[merge(strategy = crate::utils::merge_strategies::string_append_opt)]
|
||||||
dnf_arguments: Option<String>,
|
dnf_arguments: Option<String>,
|
||||||
|
|
||||||
|
#[merge(strategy = crate::utils::merge_strategies::string_append_opt)]
|
||||||
nix_arguments: Option<String>,
|
nix_arguments: Option<String>,
|
||||||
|
|
||||||
|
#[merge(strategy = crate::utils::merge_strategies::string_append_opt)]
|
||||||
apt_arguments: Option<String>,
|
apt_arguments: Option<String>,
|
||||||
|
|
||||||
enable_tlmgr: Option<bool>,
|
enable_tlmgr: Option<bool>,
|
||||||
redhat_distro_sync: Option<bool>,
|
redhat_distro_sync: Option<bool>,
|
||||||
suse_dup: Option<bool>,
|
suse_dup: Option<bool>,
|
||||||
rpm_ostree: Option<bool>,
|
rpm_ostree: Option<bool>,
|
||||||
|
|
||||||
|
#[merge(strategy = crate::utils::merge_strategies::string_append_opt)]
|
||||||
emerge_sync_flags: Option<String>,
|
emerge_sync_flags: Option<String>,
|
||||||
|
|
||||||
|
#[merge(strategy = crate::utils::merge_strategies::string_append_opt)]
|
||||||
emerge_update_flags: Option<String>,
|
emerge_update_flags: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize, Default, Debug)]
|
#[derive(Deserialize, Default, Debug, Merge)]
|
||||||
#[serde(deny_unknown_fields)]
|
#[serde(deny_unknown_fields)]
|
||||||
pub struct Composer {
|
pub struct Composer {
|
||||||
self_update: Option<bool>,
|
self_update: Option<bool>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize, Default, Debug)]
|
#[derive(Deserialize, Default, Debug, Merge)]
|
||||||
#[serde(deny_unknown_fields)]
|
#[serde(deny_unknown_fields)]
|
||||||
pub struct Vim {
|
pub struct Vim {
|
||||||
force_plug_update: Option<bool>,
|
force_plug_update: Option<bool>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize, Default, Debug)]
|
#[derive(Deserialize, Default, Debug, Merge)]
|
||||||
|
#[serde(deny_unknown_fields)]
|
||||||
|
pub struct Misc {
|
||||||
|
pre_sudo: Option<bool>,
|
||||||
|
|
||||||
|
#[merge(strategy = crate::utils::merge_strategies::vec_prepend_opt)]
|
||||||
|
git_repos: Option<Vec<String>>,
|
||||||
|
|
||||||
|
predefined_git_repos: Option<bool>,
|
||||||
|
|
||||||
|
#[merge(strategy = crate::utils::merge_strategies::vec_prepend_opt)]
|
||||||
|
disable: Option<Vec<Step>>,
|
||||||
|
|
||||||
|
#[merge(strategy = crate::utils::merge_strategies::vec_prepend_opt)]
|
||||||
|
ignore_failures: Option<Vec<Step>>,
|
||||||
|
|
||||||
|
#[merge(strategy = crate::utils::merge_strategies::vec_prepend_opt)]
|
||||||
|
remote_topgrades: Option<Vec<String>>,
|
||||||
|
|
||||||
|
remote_topgrade_path: Option<String>,
|
||||||
|
|
||||||
|
#[merge(strategy = crate::utils::merge_strategies::string_append_opt)]
|
||||||
|
ssh_arguments: Option<String>,
|
||||||
|
|
||||||
|
#[merge(strategy = crate::utils::merge_strategies::string_append_opt)]
|
||||||
|
git_arguments: Option<String>,
|
||||||
|
|
||||||
|
#[merge(strategy = crate::utils::merge_strategies::string_append_opt)]
|
||||||
|
tmux_arguments: Option<String>,
|
||||||
|
|
||||||
|
set_title: Option<bool>,
|
||||||
|
|
||||||
|
display_time: Option<bool>,
|
||||||
|
|
||||||
|
display_preamble: Option<bool>,
|
||||||
|
|
||||||
|
assume_yes: Option<bool>,
|
||||||
|
|
||||||
|
#[merge(strategy = crate::utils::merge_strategies::string_append_opt)]
|
||||||
|
yay_arguments: Option<String>,
|
||||||
|
|
||||||
|
#[merge(strategy = crate::utils::merge_strategies::string_append_opt)]
|
||||||
|
aura_aur_arguments: Option<String>,
|
||||||
|
|
||||||
|
#[merge(strategy = crate::utils::merge_strategies::string_append_opt)]
|
||||||
|
aura_pacman_arguments: Option<String>,
|
||||||
|
|
||||||
|
no_retry: Option<bool>,
|
||||||
|
|
||||||
|
run_in_tmux: Option<bool>,
|
||||||
|
|
||||||
|
cleanup: Option<bool>,
|
||||||
|
|
||||||
|
notify_each_step: Option<bool>,
|
||||||
|
|
||||||
|
accept_all_windows_updates: Option<bool>,
|
||||||
|
|
||||||
|
skip_notify: Option<bool>,
|
||||||
|
|
||||||
|
bashit_branch: Option<String>,
|
||||||
|
|
||||||
|
#[merge(strategy = crate::utils::merge_strategies::vec_prepend_opt)]
|
||||||
|
only: Option<Vec<Step>>,
|
||||||
|
|
||||||
|
no_self_update: Option<bool>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Default, Debug, Merge)]
|
||||||
#[serde(deny_unknown_fields)]
|
#[serde(deny_unknown_fields)]
|
||||||
/// Configuration file
|
/// Configuration file
|
||||||
pub struct ConfigFile {
|
pub struct ConfigFile {
|
||||||
|
#[merge(strategy = crate::utils::merge_strategies::inner_merge_opt)]
|
||||||
|
include: Option<Include>,
|
||||||
|
|
||||||
|
#[merge(strategy = crate::utils::merge_strategies::inner_merge_opt)]
|
||||||
|
misc: Option<Misc>,
|
||||||
|
|
||||||
sudo_command: Option<SudoKind>,
|
sudo_command: Option<SudoKind>,
|
||||||
pre_sudo: Option<bool>,
|
|
||||||
|
#[merge(strategy = crate::utils::merge_strategies::commands_merge_opt)]
|
||||||
pre_commands: Option<Commands>,
|
pre_commands: Option<Commands>,
|
||||||
|
|
||||||
|
#[merge(strategy = crate::utils::merge_strategies::commands_merge_opt)]
|
||||||
post_commands: Option<Commands>,
|
post_commands: Option<Commands>,
|
||||||
|
|
||||||
|
#[merge(strategy = crate::utils::merge_strategies::commands_merge_opt)]
|
||||||
commands: Option<Commands>,
|
commands: Option<Commands>,
|
||||||
git_repos: Option<Vec<String>>,
|
|
||||||
predefined_git_repos: Option<bool>,
|
#[merge(strategy = crate::utils::merge_strategies::inner_merge_opt)]
|
||||||
disable: Option<Vec<Step>>,
|
|
||||||
ignore_failures: Option<Vec<Step>>,
|
|
||||||
remote_topgrades: Option<Vec<String>>,
|
|
||||||
remote_topgrade_path: Option<String>,
|
|
||||||
ssh_arguments: Option<String>,
|
|
||||||
git_arguments: Option<String>,
|
|
||||||
tmux_arguments: Option<String>,
|
|
||||||
set_title: Option<bool>,
|
|
||||||
display_time: Option<bool>,
|
|
||||||
display_preamble: Option<bool>,
|
|
||||||
assume_yes: Option<bool>,
|
|
||||||
yay_arguments: Option<String>,
|
|
||||||
aura_aur_arguments: Option<String>,
|
|
||||||
aura_pacman_arguments: Option<String>,
|
|
||||||
python: Option<Python>,
|
python: Option<Python>,
|
||||||
no_retry: Option<bool>,
|
|
||||||
run_in_tmux: Option<bool>,
|
#[merge(strategy = crate::utils::merge_strategies::inner_merge_opt)]
|
||||||
cleanup: Option<bool>,
|
|
||||||
notify_each_step: Option<bool>,
|
|
||||||
accept_all_windows_updates: Option<bool>,
|
|
||||||
skip_notify: Option<bool>,
|
|
||||||
bashit_branch: Option<String>,
|
|
||||||
only: Option<Vec<Step>>,
|
|
||||||
composer: Option<Composer>,
|
composer: Option<Composer>,
|
||||||
|
|
||||||
|
#[merge(strategy = crate::utils::merge_strategies::inner_merge_opt)]
|
||||||
brew: Option<Brew>,
|
brew: Option<Brew>,
|
||||||
|
|
||||||
|
#[merge(strategy = crate::utils::merge_strategies::inner_merge_opt)]
|
||||||
linux: Option<Linux>,
|
linux: Option<Linux>,
|
||||||
|
|
||||||
|
#[merge(strategy = crate::utils::merge_strategies::inner_merge_opt)]
|
||||||
git: Option<Git>,
|
git: Option<Git>,
|
||||||
|
|
||||||
|
#[merge(strategy = crate::utils::merge_strategies::inner_merge_opt)]
|
||||||
windows: Option<Windows>,
|
windows: Option<Windows>,
|
||||||
|
|
||||||
|
#[merge(strategy = crate::utils::merge_strategies::inner_merge_opt)]
|
||||||
npm: Option<NPM>,
|
npm: Option<NPM>,
|
||||||
|
|
||||||
|
#[merge(strategy = crate::utils::merge_strategies::inner_merge_opt)]
|
||||||
yarn: Option<Yarn>,
|
yarn: Option<Yarn>,
|
||||||
|
|
||||||
|
#[merge(strategy = crate::utils::merge_strategies::inner_merge_opt)]
|
||||||
vim: Option<Vim>,
|
vim: Option<Vim>,
|
||||||
|
|
||||||
|
#[merge(strategy = crate::utils::merge_strategies::inner_merge_opt)]
|
||||||
firmware: Option<Firmware>,
|
firmware: Option<Firmware>,
|
||||||
|
|
||||||
|
#[merge(strategy = crate::utils::merge_strategies::inner_merge_opt)]
|
||||||
vagrant: Option<Vagrant>,
|
vagrant: Option<Vagrant>,
|
||||||
|
|
||||||
|
#[merge(strategy = crate::utils::merge_strategies::inner_merge_opt)]
|
||||||
flatpak: Option<Flatpak>,
|
flatpak: Option<Flatpak>,
|
||||||
|
|
||||||
|
#[merge(strategy = crate::utils::merge_strategies::inner_merge_opt)]
|
||||||
distrobox: Option<Distrobox>,
|
distrobox: Option<Distrobox>,
|
||||||
no_self_update: Option<bool>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn config_directory() -> PathBuf {
|
fn config_directory() -> PathBuf {
|
||||||
@@ -354,58 +499,164 @@ fn config_directory() -> PathBuf {
|
|||||||
return crate::WINDOWS_DIRS.config_dir();
|
return crate::WINDOWS_DIRS.config_dir();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The only purpose of this struct is to deserialize only the `include` field of the config file.
|
||||||
|
#[derive(Deserialize, Default, Debug)]
|
||||||
|
struct ConfigFileIncludeOnly {
|
||||||
|
include: Option<Include>,
|
||||||
|
}
|
||||||
|
|
||||||
impl ConfigFile {
|
impl ConfigFile {
|
||||||
fn ensure() -> Result<PathBuf> {
|
/// Returns the main config file and any additional config files
|
||||||
|
/// 0 = main config file
|
||||||
|
/// 1 = additional config files coming from topgrade.d
|
||||||
|
fn ensure() -> Result<(PathBuf, Vec<PathBuf>)> {
|
||||||
|
let mut res = (PathBuf::new(), Vec::new());
|
||||||
|
|
||||||
let config_directory = config_directory();
|
let config_directory = config_directory();
|
||||||
|
|
||||||
let config_path = config_directory.join("topgrade.toml");
|
let possible_config_paths = vec![
|
||||||
let alt_config_path = config_directory.join("topgrade/topgrade.toml");
|
config_directory.join("topgrade.toml"),
|
||||||
|
config_directory.join("topgrade/topgrade.toml"),
|
||||||
|
];
|
||||||
|
|
||||||
if config_path.exists() {
|
// Search for the main config file
|
||||||
debug!("Configuration at {}", config_path.display());
|
for path in possible_config_paths.iter() {
|
||||||
Ok(config_path)
|
if path.exists() {
|
||||||
} else if alt_config_path.exists() {
|
debug!("Configuration at {}", path.display());
|
||||||
debug!("Configuration at {}", alt_config_path.display());
|
res.0 = path.clone();
|
||||||
Ok(alt_config_path)
|
break;
|
||||||
} else {
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
res.1 = Self::ensure_topgrade_d(&config_directory)?;
|
||||||
|
|
||||||
|
// If no config file exists, create a default one in the config directory
|
||||||
|
if !res.0.exists() && res.1.is_empty() {
|
||||||
debug!("No configuration exists");
|
debug!("No configuration exists");
|
||||||
write(&config_path, EXAMPLE_CONFIG).map_err(|e| {
|
write(&res.0, EXAMPLE_CONFIG).map_err(|e| {
|
||||||
debug!(
|
debug!(
|
||||||
"Unable to write the example configuration file to {}: {}. Using blank config.",
|
"Unable to write the example configuration file to {}: {}. Using blank config.",
|
||||||
config_path.display(),
|
&res.0.display(),
|
||||||
e
|
e
|
||||||
);
|
);
|
||||||
e
|
e
|
||||||
})?;
|
})?;
|
||||||
Ok(config_path)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Ok(res)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Searches topgrade.d for additional config files
|
||||||
|
fn ensure_topgrade_d(config_directory: &Path) -> Result<Vec<PathBuf>> {
|
||||||
|
let mut res = Vec::new();
|
||||||
|
let dir_to_search = config_directory.join("topgrade.d");
|
||||||
|
|
||||||
|
if dir_to_search.exists() {
|
||||||
|
for entry in fs::read_dir(dir_to_search)? {
|
||||||
|
let entry = entry?;
|
||||||
|
if entry.file_type()?.is_file() {
|
||||||
|
debug!(
|
||||||
|
"Found additional (directory) configuration file at {}",
|
||||||
|
entry.path().display()
|
||||||
|
);
|
||||||
|
res.push(entry.path());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
res.sort();
|
||||||
|
} else {
|
||||||
|
debug!("No additional configuration directory exists, creating one");
|
||||||
|
fs::create_dir_all(&dir_to_search)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(res)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Read the configuration file.
|
/// Read the configuration file.
|
||||||
///
|
///
|
||||||
/// If the configuration file does not exist the function returns the default ConfigFile.
|
/// If the configuration file does not exist, the function returns the default ConfigFile.
|
||||||
fn read(config_path: Option<PathBuf>) -> Result<ConfigFile> {
|
fn read(config_path: Option<PathBuf>) -> Result<ConfigFile> {
|
||||||
|
let mut result = Self::default();
|
||||||
|
|
||||||
let config_path = if let Some(path) = config_path {
|
let config_path = if let Some(path) = config_path {
|
||||||
path
|
path
|
||||||
} else {
|
} else {
|
||||||
Self::ensure()?
|
let (path, dir_include) = Self::ensure()?;
|
||||||
|
|
||||||
|
/*
|
||||||
|
The Function was called without a config_path, we need
|
||||||
|
to read the include directory before returning the main config path
|
||||||
|
*/
|
||||||
|
for include in dir_include {
|
||||||
|
let include_contents = fs::read_to_string(&include).map_err(|e| {
|
||||||
|
tracing::error!("Unable to read {}", include.display());
|
||||||
|
e
|
||||||
|
})?;
|
||||||
|
let include_contents_parsed = toml::from_str(include_contents.as_str()).map_err(|e| {
|
||||||
|
tracing::error!("Failed to deserialize {}", include.display());
|
||||||
|
e
|
||||||
|
})?;
|
||||||
|
|
||||||
|
result.merge(include_contents_parsed);
|
||||||
|
}
|
||||||
|
|
||||||
|
path
|
||||||
};
|
};
|
||||||
|
|
||||||
let contents = fs::read_to_string(&config_path).map_err(|e| {
|
let mut contents_non_split = fs::read_to_string(&config_path).map_err(|e| {
|
||||||
tracing::error!("Unable to read {}", config_path.display());
|
tracing::error!("Unable to read {}", config_path.display());
|
||||||
e
|
e
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
let mut result: Self = toml::from_str(&contents).map_err(|e| {
|
Self::ensure_misc_is_present(&mut contents_non_split, &config_path);
|
||||||
tracing::error!("Failed to deserialize {}", config_path.display());
|
|
||||||
e
|
|
||||||
})?;
|
|
||||||
|
|
||||||
if let Some(ref mut paths) = &mut result.git_repos {
|
// To parse [include] sections in the order as they are written,
|
||||||
for path in paths.iter_mut() {
|
// we split the file and parse each part as a separate file
|
||||||
let expanded = shellexpand::tilde::<&str>(&path.as_ref()).into_owned();
|
let regex_match_include = Regex::new(r"\[include]").expect("Failed to compile regex");
|
||||||
debug!("Path {} expanded to {}", path, expanded);
|
let contents_split = regex_match_include.split_inclusive_left(contents_non_split.as_str());
|
||||||
*path = expanded;
|
|
||||||
|
for contents in contents_split {
|
||||||
|
let config_file_include_only: ConfigFileIncludeOnly = toml::from_str(contents).map_err(|e| {
|
||||||
|
tracing::error!("Failed to deserialize an include section of {}", config_path.display());
|
||||||
|
e
|
||||||
|
})?;
|
||||||
|
|
||||||
|
if let Some(includes) = &config_file_include_only.include {
|
||||||
|
// Parses the [include] section present in the slice
|
||||||
|
for include in includes.paths.iter().rev() {
|
||||||
|
let include_path = shellexpand::tilde::<&str>(&include.as_ref()).into_owned();
|
||||||
|
let include_path = PathBuf::from(include_path);
|
||||||
|
let include_contents = match fs::read_to_string(&include_path) {
|
||||||
|
Ok(c) => c,
|
||||||
|
Err(e) => {
|
||||||
|
tracing::error!("Unable to read {}: {}", include_path.display(), e);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
match toml::from_str::<Self>(&include_contents) {
|
||||||
|
Ok(include_parsed) => result.merge(include_parsed),
|
||||||
|
Err(e) => {
|
||||||
|
tracing::error!("Failed to deserialize {}: {}", include_path.display(), e);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
debug!("Configuration include found: {}", include_path.display());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
match toml::from_str::<Self>(contents) {
|
||||||
|
Ok(contents) => result.merge(contents),
|
||||||
|
Err(e) => tracing::error!("Failed to deserialize {}: {}", config_path.display(), e),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(misc) = &mut result.misc {
|
||||||
|
if let Some(ref mut paths) = &mut misc.git_repos {
|
||||||
|
for path in paths.iter_mut() {
|
||||||
|
let expanded = shellexpand::tilde::<&str>(&path.as_ref()).into_owned();
|
||||||
|
debug!("Path {} expanded to {}", path, expanded);
|
||||||
|
*path = expanded;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -423,7 +674,7 @@ impl ConfigFile {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn edit() -> Result<()> {
|
fn edit() -> Result<()> {
|
||||||
let config_path = Self::ensure()?;
|
let config_path = Self::ensure()?.0;
|
||||||
let editor = editor();
|
let editor = editor();
|
||||||
debug!("Editor: {:?}", editor);
|
debug!("Editor: {:?}", editor);
|
||||||
|
|
||||||
@@ -436,6 +687,18 @@ impl ConfigFile {
|
|||||||
.status_checked()
|
.status_checked()
|
||||||
.context("Failed to open configuration file editor")
|
.context("Failed to open configuration file editor")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// [Misc] was added later, here we check if it is present in the config file and add it if not
|
||||||
|
fn ensure_misc_is_present(contents: &mut String, path: &PathBuf) {
|
||||||
|
if !contents.contains("[misc]") {
|
||||||
|
debug!("Adding [misc] section to {}", path.display());
|
||||||
|
string_prepend_str(contents, "[misc]\n");
|
||||||
|
|
||||||
|
File::create(path)
|
||||||
|
.and_then(|mut f| f.write_all(contents.as_bytes()))
|
||||||
|
.expect("Tried to auto-migrate the config file, unable to write to config file.\nPlease add \"[misc]\" section manually to the first line of the file.\nError");
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Command line arguments
|
// Command line arguments
|
||||||
@@ -565,7 +828,7 @@ impl CommandLineArgs {
|
|||||||
/// Represents the application configuration
|
/// Represents the application configuration
|
||||||
///
|
///
|
||||||
/// The struct holds the loaded configuration file, as well as the arguments parsed from the command line.
|
/// 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
|
/// Its provided methods decide the appropriate options based on combining the configuration file and the
|
||||||
/// command line arguments.
|
/// command line arguments.
|
||||||
pub struct Config {
|
pub struct Config {
|
||||||
opt: CommandLineArgs,
|
opt: CommandLineArgs,
|
||||||
@@ -576,7 +839,7 @@ pub struct Config {
|
|||||||
impl Config {
|
impl Config {
|
||||||
/// Load the configuration.
|
/// Load the configuration.
|
||||||
///
|
///
|
||||||
/// The function parses the command line arguments and reading the configuration file.
|
/// The function parses the command line arguments and reads the configuration file.
|
||||||
pub fn load(opt: CommandLineArgs) -> Result<Self> {
|
pub fn load(opt: CommandLineArgs) -> Result<Self> {
|
||||||
let config_directory = config_directory();
|
let config_directory = config_directory();
|
||||||
let config_file = if config_directory.is_dir() {
|
let config_file = if config_directory.is_dir() {
|
||||||
@@ -587,15 +850,17 @@ impl Config {
|
|||||||
ConfigFile::default()
|
ConfigFile::default()
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
tracing::debug!("Configuration directory {} does not exist", config_directory.display());
|
debug!("Configuration directory {} does not exist", config_directory.display());
|
||||||
ConfigFile::default()
|
ConfigFile::default()
|
||||||
};
|
};
|
||||||
|
|
||||||
check_deprecated!(config_file, git_arguments, git, arguments);
|
if let Some(misc) = &config_file.misc {
|
||||||
check_deprecated!(config_file, git_repos, git, repos);
|
check_deprecated!(misc, git_arguments, git, arguments);
|
||||||
check_deprecated!(config_file, predefined_git_repos, git, pull_predefined);
|
check_deprecated!(misc, git_repos, git, repos);
|
||||||
check_deprecated!(config_file, yay_arguments, linux, yay_arguments);
|
check_deprecated!(misc, predefined_git_repos, git, pull_predefined);
|
||||||
check_deprecated!(config_file, accept_all_windows_updates, windows, accept_all_updates);
|
check_deprecated!(misc, yay_arguments, linux, yay_arguments);
|
||||||
|
check_deprecated!(misc, accept_all_windows_updates, windows, accept_all_updates);
|
||||||
|
}
|
||||||
|
|
||||||
let allowed_steps = Self::allowed_steps(&opt, &config_file);
|
let allowed_steps = Self::allowed_steps(&opt, &config_file);
|
||||||
|
|
||||||
@@ -628,7 +893,7 @@ impl Config {
|
|||||||
|
|
||||||
/// The list of additional git repositories to pull.
|
/// The list of additional git repositories to pull.
|
||||||
pub fn git_repos(&self) -> &Option<Vec<String>> {
|
pub fn git_repos(&self) -> &Option<Vec<String>> {
|
||||||
get_deprecated!(self.config_file, git_repos, git, repos)
|
get_deprecated_moved_opt!(&self.config_file.misc, git_repos, &self.config_file.git, repos)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Tell whether the specified step should run.
|
/// Tell whether the specified step should run.
|
||||||
@@ -643,8 +908,10 @@ impl Config {
|
|||||||
let mut enabled_steps: Vec<Step> = Vec::new();
|
let mut enabled_steps: Vec<Step> = Vec::new();
|
||||||
enabled_steps.extend(&opt.only);
|
enabled_steps.extend(&opt.only);
|
||||||
|
|
||||||
if let Some(only) = config_file.only.as_ref() {
|
if let Some(misc) = config_file.misc.as_ref() {
|
||||||
enabled_steps.extend(only)
|
if let Some(only) = misc.only.as_ref() {
|
||||||
|
enabled_steps.extend(only);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if enabled_steps.is_empty() {
|
if enabled_steps.is_empty() {
|
||||||
@@ -653,27 +920,47 @@ impl Config {
|
|||||||
|
|
||||||
let mut disabled_steps: Vec<Step> = Vec::new();
|
let mut disabled_steps: Vec<Step> = Vec::new();
|
||||||
disabled_steps.extend(&opt.disable);
|
disabled_steps.extend(&opt.disable);
|
||||||
if let Some(disabled) = config_file.disable.as_ref() {
|
if let Some(misc) = config_file.misc.as_ref() {
|
||||||
disabled_steps.extend(disabled);
|
if let Some(disabled) = misc.disable.as_ref() {
|
||||||
|
disabled_steps.extend(disabled);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
enabled_steps.retain(|e| !disabled_steps.contains(e) || opt.only.contains(e));
|
enabled_steps.retain(|e| !disabled_steps.contains(e) || opt.only.contains(e));
|
||||||
enabled_steps
|
enabled_steps
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Tell whether we should run a self update.
|
/// Tell whether we should run a self-update.
|
||||||
pub fn no_self_update(&self) -> bool {
|
pub fn no_self_update(&self) -> bool {
|
||||||
self.opt.no_self_update || self.config_file.no_self_update.unwrap_or(false)
|
self.opt.no_self_update
|
||||||
|
|| self
|
||||||
|
.config_file
|
||||||
|
.misc
|
||||||
|
.as_ref()
|
||||||
|
.and_then(|misc| misc.no_self_update)
|
||||||
|
.unwrap_or(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Tell whether we should run in tmux.
|
/// Tell whether we should run in tmux.
|
||||||
pub fn run_in_tmux(&self) -> bool {
|
pub fn run_in_tmux(&self) -> bool {
|
||||||
self.opt.run_in_tmux || self.config_file.run_in_tmux.unwrap_or(false)
|
self.opt.run_in_tmux
|
||||||
|
|| self
|
||||||
|
.config_file
|
||||||
|
.misc
|
||||||
|
.as_ref()
|
||||||
|
.and_then(|misc| misc.run_in_tmux)
|
||||||
|
.unwrap_or(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Tell whether we should perform cleanup steps.
|
/// Tell whether we should perform cleanup steps.
|
||||||
pub fn cleanup(&self) -> bool {
|
pub fn cleanup(&self) -> bool {
|
||||||
self.opt.cleanup || self.config_file.cleanup.unwrap_or(false)
|
self.opt.cleanup
|
||||||
|
|| self
|
||||||
|
.config_file
|
||||||
|
.misc
|
||||||
|
.as_ref()
|
||||||
|
.and_then(|misc| misc.cleanup)
|
||||||
|
.unwrap_or(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Tell whether we are dry-running.
|
/// Tell whether we are dry-running.
|
||||||
@@ -683,32 +970,54 @@ impl Config {
|
|||||||
|
|
||||||
/// Tell whether we should not attempt to retry anything.
|
/// Tell whether we should not attempt to retry anything.
|
||||||
pub fn no_retry(&self) -> bool {
|
pub fn no_retry(&self) -> bool {
|
||||||
self.opt.no_retry || self.config_file.no_retry.unwrap_or(false)
|
self.opt.no_retry
|
||||||
|
|| self
|
||||||
|
.config_file
|
||||||
|
.misc
|
||||||
|
.as_ref()
|
||||||
|
.and_then(|misc| misc.no_retry)
|
||||||
|
.unwrap_or(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// List of remote hosts to run Topgrade in
|
/// List of remote hosts to run Topgrade in
|
||||||
pub fn remote_topgrades(&self) -> &Option<Vec<String>> {
|
pub fn remote_topgrades(&self) -> Option<&Vec<String>> {
|
||||||
&self.config_file.remote_topgrades
|
self.config_file
|
||||||
|
.misc
|
||||||
|
.as_ref()
|
||||||
|
.and_then(|misc| misc.remote_topgrades.as_ref())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Path to Topgrade executable used for all remote hosts
|
/// Path to Topgrade executable used for all remote hosts
|
||||||
pub fn remote_topgrade_path(&self) -> &str {
|
pub fn remote_topgrade_path(&self) -> &str {
|
||||||
self.config_file.remote_topgrade_path.as_deref().unwrap_or("topgrade")
|
self.config_file
|
||||||
|
.misc
|
||||||
|
.as_ref()
|
||||||
|
.and_then(|misc| misc.remote_topgrade_path.as_deref())
|
||||||
|
.unwrap_or("topgrade")
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Extra SSH arguments
|
/// Extra SSH arguments
|
||||||
pub fn ssh_arguments(&self) -> &Option<String> {
|
pub fn ssh_arguments(&self) -> Option<&String> {
|
||||||
&self.config_file.ssh_arguments
|
self.config_file
|
||||||
|
.misc
|
||||||
|
.as_ref()
|
||||||
|
.and_then(|misc| misc.ssh_arguments.as_ref())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Extra Git arguments
|
/// Extra Git arguments
|
||||||
pub fn git_arguments(&self) -> &Option<String> {
|
pub fn git_arguments(&self) -> &Option<String> {
|
||||||
get_deprecated!(self.config_file, git_arguments, git, arguments)
|
get_deprecated_moved_opt!(&self.config_file.misc, git_arguments, &self.config_file.git, arguments)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Extra Tmux arguments
|
/// Extra Tmux arguments
|
||||||
pub fn tmux_arguments(&self) -> eyre::Result<Vec<String>> {
|
pub fn tmux_arguments(&self) -> Result<Vec<String>> {
|
||||||
let args = &self.config_file.tmux_arguments.as_deref().unwrap_or_default();
|
let args = &self
|
||||||
|
.config_file
|
||||||
|
.misc
|
||||||
|
.as_ref()
|
||||||
|
.and_then(|misc| misc.tmux_arguments.as_ref())
|
||||||
|
.map(String::to_owned)
|
||||||
|
.unwrap_or_default();
|
||||||
shell_words::split(args)
|
shell_words::split(args)
|
||||||
// The only time the parse failed is in case of a missing close quote.
|
// The only time the parse failed is in case of a missing close quote.
|
||||||
// The error message looks like this:
|
// The error message looks like this:
|
||||||
@@ -726,7 +1035,7 @@ impl Config {
|
|||||||
|
|
||||||
/// Skip sending a notification at the end of a run
|
/// Skip sending a notification at the end of a run
|
||||||
pub fn skip_notify(&self) -> bool {
|
pub fn skip_notify(&self) -> bool {
|
||||||
if let Some(yes) = self.config_file.skip_notify {
|
if let Some(yes) = self.config_file.misc.as_ref().and_then(|misc| misc.skip_notify) {
|
||||||
return yes;
|
return yes;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -735,12 +1044,16 @@ impl Config {
|
|||||||
|
|
||||||
/// Whether to set the terminal title
|
/// Whether to set the terminal title
|
||||||
pub fn set_title(&self) -> bool {
|
pub fn set_title(&self) -> bool {
|
||||||
self.config_file.set_title.unwrap_or(true)
|
self.config_file
|
||||||
|
.misc
|
||||||
|
.as_ref()
|
||||||
|
.and_then(|misc| misc.set_title)
|
||||||
|
.unwrap_or(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Whether to say yes to package managers
|
/// Whether to say yes to package managers
|
||||||
pub fn yes(&self, step: Step) -> bool {
|
pub fn yes(&self, step: Step) -> bool {
|
||||||
if let Some(yes) = self.config_file.assume_yes {
|
if let Some(yes) = self.config_file.misc.as_ref().and_then(|misc| misc.assume_yes) {
|
||||||
return yes;
|
return yes;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -757,18 +1070,22 @@ impl Config {
|
|||||||
|
|
||||||
/// Bash-it branch
|
/// Bash-it branch
|
||||||
pub fn bashit_branch(&self) -> &str {
|
pub fn bashit_branch(&self) -> &str {
|
||||||
self.config_file.bashit_branch.as_deref().unwrap_or("stable")
|
self.config_file
|
||||||
|
.misc
|
||||||
|
.as_ref()
|
||||||
|
.and_then(|misc| misc.bashit_branch.as_deref())
|
||||||
|
.unwrap_or("stable")
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Whether to accept all Windows updates
|
/// Whether to accept all Windows updates
|
||||||
pub fn accept_all_windows_updates(&self) -> bool {
|
pub fn accept_all_windows_updates(&self) -> bool {
|
||||||
get_deprecated!(
|
get_deprecated_moved_or_default_to!(
|
||||||
self.config_file,
|
&self.config_file.misc,
|
||||||
accept_all_windows_updates,
|
accept_all_windows_updates,
|
||||||
windows,
|
&self.config_file.windows,
|
||||||
accept_all_updates
|
accept_all_updates,
|
||||||
|
true
|
||||||
)
|
)
|
||||||
.unwrap_or(true)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Whether to self rename the Topgrade executable during the run
|
/// Whether to self rename the Topgrade executable during the run
|
||||||
@@ -836,7 +1153,11 @@ impl Config {
|
|||||||
|
|
||||||
/// Whether to send a desktop notification at the beginning of every step
|
/// Whether to send a desktop notification at the beginning of every step
|
||||||
pub fn notify_each_step(&self) -> bool {
|
pub fn notify_each_step(&self) -> bool {
|
||||||
self.config_file.notify_each_step.unwrap_or(false)
|
self.config_file
|
||||||
|
.misc
|
||||||
|
.as_ref()
|
||||||
|
.and_then(|misc| misc.notify_each_step)
|
||||||
|
.unwrap_or(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Extra garuda-update arguments
|
/// Extra garuda-update arguments
|
||||||
@@ -962,7 +1283,7 @@ impl Config {
|
|||||||
self.config_file.git.as_ref().and_then(|git| git.max_concurrency)
|
self.config_file.git.as_ref().and_then(|git| git.max_concurrency)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Should we power on vagrant boxes if needed
|
/// Determine whether we should power on vagrant boxes
|
||||||
pub fn vagrant_power_on(&self) -> Option<bool> {
|
pub fn vagrant_power_on(&self) -> Option<bool> {
|
||||||
self.config_file.vagrant.as_ref().and_then(|vagrant| vagrant.power_on)
|
self.config_file.vagrant.as_ref().and_then(|vagrant| vagrant.power_on)
|
||||||
}
|
}
|
||||||
@@ -992,7 +1313,7 @@ impl Config {
|
|||||||
.unwrap_or(false)
|
.unwrap_or(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Use distro-sync in Red Hat based distrbutions
|
/// Use distro-sync in Red Hat based distributions
|
||||||
pub fn redhat_distro_sync(&self) -> bool {
|
pub fn redhat_distro_sync(&self) -> bool {
|
||||||
self.config_file
|
self.config_file
|
||||||
.linux
|
.linux
|
||||||
@@ -1019,18 +1340,25 @@ impl Config {
|
|||||||
.unwrap_or(false)
|
.unwrap_or(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Should we ignore failures for this step
|
/// Determine if we should ignore failures for this step
|
||||||
pub fn ignore_failure(&self, step: Step) -> bool {
|
pub fn ignore_failure(&self, step: Step) -> bool {
|
||||||
self.config_file
|
self.config_file
|
||||||
.ignore_failures
|
.misc
|
||||||
.as_ref()
|
.as_ref()
|
||||||
|
.and_then(|misc| misc.ignore_failures.as_ref())
|
||||||
.map(|v| v.contains(&step))
|
.map(|v| v.contains(&step))
|
||||||
.unwrap_or(false)
|
.unwrap_or(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn use_predefined_git_repos(&self) -> bool {
|
pub fn use_predefined_git_repos(&self) -> bool {
|
||||||
!self.opt.disable_predefined_git_repos
|
!self.opt.disable_predefined_git_repos
|
||||||
&& get_deprecated!(self.config_file, predefined_git_repos, git, pull_predefined).unwrap_or(true)
|
&& get_deprecated_moved_or_default_to!(
|
||||||
|
&self.config_file.misc,
|
||||||
|
predefined_git_repos,
|
||||||
|
&self.config_file.git,
|
||||||
|
pull_predefined,
|
||||||
|
true
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn verbose(&self) -> bool {
|
pub fn verbose(&self) -> bool {
|
||||||
@@ -1056,7 +1384,11 @@ impl Config {
|
|||||||
/// If `true`, `sudo` should be called after `pre_commands` in order to elevate at the
|
/// If `true`, `sudo` should be called after `pre_commands` in order to elevate at the
|
||||||
/// start of the session (and not in the middle).
|
/// start of the session (and not in the middle).
|
||||||
pub fn pre_sudo(&self) -> bool {
|
pub fn pre_sudo(&self) -> bool {
|
||||||
self.config_file.pre_sudo.unwrap_or(false)
|
self.config_file
|
||||||
|
.misc
|
||||||
|
.as_ref()
|
||||||
|
.and_then(|misc| misc.pre_sudo)
|
||||||
|
.unwrap_or(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(target_os = "linux")]
|
#[cfg(target_os = "linux")]
|
||||||
@@ -1150,11 +1482,19 @@ impl Config {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn display_time(&self) -> bool {
|
pub fn display_time(&self) -> bool {
|
||||||
self.config_file.display_time.unwrap_or(true)
|
self.config_file
|
||||||
|
.misc
|
||||||
|
.as_ref()
|
||||||
|
.and_then(|misc| misc.display_time)
|
||||||
|
.unwrap_or(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn display_preamble(&self) -> bool {
|
pub fn display_preamble(&self) -> bool {
|
||||||
self.config_file.display_preamble.unwrap_or(true)
|
self.config_file
|
||||||
|
.misc
|
||||||
|
.as_ref()
|
||||||
|
.and_then(|misc| misc.display_preamble)
|
||||||
|
.unwrap_or(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn should_run_custom_command(&self, name: &str) -> bool {
|
pub fn should_run_custom_command(&self, name: &str) -> bool {
|
||||||
|
|||||||
67
src/utils.rs
67
src/utils.rs
@@ -1,12 +1,13 @@
|
|||||||
use crate::error::SkipStep;
|
|
||||||
use color_eyre::eyre::Result;
|
|
||||||
|
|
||||||
use std::env;
|
use std::env;
|
||||||
use std::ffi::OsStr;
|
use std::ffi::OsStr;
|
||||||
use std::fmt::Debug;
|
use std::fmt::Debug;
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
|
use color_eyre::eyre::Result;
|
||||||
use tracing::{debug, error};
|
use tracing::{debug, error};
|
||||||
|
|
||||||
|
use crate::error::SkipStep;
|
||||||
|
|
||||||
pub trait PathExt
|
pub trait PathExt
|
||||||
where
|
where
|
||||||
Self: Sized,
|
Self: Sized,
|
||||||
@@ -101,6 +102,13 @@ pub fn require_option<T>(option: Option<T>, cause: String) -> Result<T> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn string_prepend_str(string: &mut String, s: &str) {
|
||||||
|
let mut new_string = String::with_capacity(string.len() + s.len());
|
||||||
|
new_string.push_str(s);
|
||||||
|
new_string.push_str(string);
|
||||||
|
*string = new_string;
|
||||||
|
}
|
||||||
|
|
||||||
/* sys-info-rs
|
/* sys-info-rs
|
||||||
*
|
*
|
||||||
* Copyright (c) 2015 Siyu Wang
|
* Copyright (c) 2015 Siyu Wang
|
||||||
@@ -152,3 +160,56 @@ pub fn hostname() -> Result<String> {
|
|||||||
.map_err(|err| SkipStep(format!("Failed to get hostname: {err}")).into())
|
.map_err(|err| SkipStep(format!("Failed to get hostname: {err}")).into())
|
||||||
.map(|output| output.stdout.trim().to_owned())
|
.map(|output| output.stdout.trim().to_owned())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub mod merge_strategies {
|
||||||
|
use merge::Merge;
|
||||||
|
|
||||||
|
use crate::config::Commands;
|
||||||
|
|
||||||
|
/// Prepends right to left (both Option<Vec<T>>)
|
||||||
|
pub fn vec_prepend_opt<T>(left: &mut Option<Vec<T>>, right: Option<Vec<T>>) {
|
||||||
|
if let Some(left_vec) = left {
|
||||||
|
if let Some(mut right_vec) = right {
|
||||||
|
right_vec.append(left_vec);
|
||||||
|
let _ = std::mem::replace(left, Some(right_vec));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
*left = right;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Appends an Option<String> to another Option<String>
|
||||||
|
pub fn string_append_opt(left: &mut Option<String>, right: Option<String>) {
|
||||||
|
if let Some(left_str) = left {
|
||||||
|
if let Some(right_str) = right {
|
||||||
|
left_str.push(' ');
|
||||||
|
left_str.push_str(&right_str);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
*left = right;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn inner_merge_opt<T>(left: &mut Option<T>, right: Option<T>)
|
||||||
|
where
|
||||||
|
T: Merge,
|
||||||
|
{
|
||||||
|
if let Some(ref mut left_inner) = left {
|
||||||
|
if let Some(right_inner) = right {
|
||||||
|
left_inner.merge(right_inner);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
*left = right;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn commands_merge_opt(left: &mut Option<Commands>, right: Option<Commands>) {
|
||||||
|
if let Some(ref mut left_inner) = left {
|
||||||
|
if let Some(right_inner) = right {
|
||||||
|
left_inner.extend(right_inner);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
*left = right;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user