pub(crate) mod progress_options;
use std::{collections::HashMap, path::PathBuf};
use abscissa_core::config::Config;
use abscissa_core::path::AbsPathBuf;
use abscissa_core::FrameworkError;
use clap::{Parser, ValueHint};
use directories::ProjectDirs;
use itertools::Itertools;
use log::Level;
use merge::Merge;
use rustic_backend::BackendOptions;
use rustic_core::RepositoryOptions;
use serde::{Deserialize, Serialize};
use serde_with::{serde_as, OneOrMany};
#[cfg(feature = "webdav")]
use crate::commands::webdav::WebDavCmd;
use crate::{
commands::{backup::BackupCmd, copy::Targets, forget::ForgetOptions},
config::progress_options::ProgressOptions,
filtering::SnapshotFilter,
};
#[derive(Clone, Default, Debug, Parser, Deserialize, Serialize, Merge)]
#[serde(default, rename_all = "kebab-case", deny_unknown_fields)]
pub struct RusticConfig {
#[clap(flatten, next_help_heading = "Global options")]
pub global: GlobalOptions,
#[clap(flatten, next_help_heading = "Repository options")]
pub repository: AllRepositoryOptions,
#[clap(flatten, next_help_heading = "Snapshot filter options")]
pub snapshot_filter: SnapshotFilter,
#[clap(skip)]
pub backup: BackupCmd,
#[clap(skip)]
pub copy: Targets,
#[clap(skip)]
pub forget: ForgetOptions,
#[cfg(feature = "webdav")]
#[clap(skip)]
pub webdav: WebDavCmd,
}
#[derive(Clone, Default, Debug, Parser, Serialize, Deserialize, Merge)]
#[serde(default, rename_all = "kebab-case")]
pub struct AllRepositoryOptions {
#[clap(flatten)]
#[serde(flatten)]
pub be: BackendOptions,
#[clap(flatten)]
#[serde(flatten)]
pub repo: RepositoryOptions,
}
impl RusticConfig {
pub fn merge_profile(
&mut self,
profile: &str,
merge_logs: &mut Vec<(Level, String)>,
level_missing: Level,
) -> Result<(), FrameworkError> {
let profile_filename = profile.to_string() + ".toml";
let paths = get_config_paths(&profile_filename);
if let Some(path) = paths.iter().find(|path| path.exists()) {
merge_logs.push((Level::Info, format!("using config {}", path.display())));
let mut config = Self::load_toml_file(AbsPathBuf::canonicalize(path)?)?;
for profile in &config.global.use_profile.clone() {
config.merge_profile(profile, merge_logs, Level::Warn)?;
}
self.merge(config);
} else {
let paths_string = paths.iter().map(|path| path.display()).join(", ");
merge_logs.push((
level_missing,
format!(
"using no config file, none of these exist: {}",
&paths_string
),
));
};
Ok(())
}
}
#[serde_as]
#[derive(Default, Debug, Parser, Clone, Deserialize, Serialize, Merge)]
#[serde(default, rename_all = "kebab-case", deny_unknown_fields)]
pub struct GlobalOptions {
#[clap(
short = 'P',
long,
global = true,
value_name = "PROFILE",
env = "RUSTIC_USE_PROFILE"
)]
#[merge(strategy = merge::vec::append)]
#[serde_as(as = "OneOrMany<_>")]
pub use_profile: Vec<String>,
#[clap(long, short = 'n', global = true, env = "RUSTIC_DRY_RUN")]
#[merge(strategy = merge::bool::overwrite_false)]
pub dry_run: bool,
#[clap(long, global = true, env = "RUSTIC_CHECK_INDEX")]
#[merge(strategy = merge::bool::overwrite_false)]
pub check_index: bool,
#[clap(long, global = true, env = "RUSTIC_LOG_LEVEL")]
pub log_level: Option<String>,
#[clap(long, global = true, env = "RUSTIC_LOG_FILE", value_name = "LOGFILE", value_hint = ValueHint::FilePath)]
pub log_file: Option<PathBuf>,
#[clap(flatten)]
#[serde(flatten)]
pub progress_options: ProgressOptions,
#[clap(skip)]
#[merge(strategy = extend)]
pub env: HashMap<String, String>,
}
fn extend(left: &mut HashMap<String, String>, right: HashMap<String, String>) {
left.extend(right);
}
fn get_config_paths(filename: &str) -> Vec<PathBuf> {
[
ProjectDirs::from("", "", "rustic")
.map(|project_dirs| project_dirs.config_dir().to_path_buf()),
get_global_config_path(),
Some(PathBuf::from(".")),
]
.into_iter()
.filter_map(|path| {
path.map(|mut p| {
p.push(filename);
p
})
})
.collect()
}
#[cfg(target_os = "windows")]
fn get_global_config_path() -> Option<PathBuf> {
std::env::var_os("PROGRAMDATA").map(|program_data| {
let mut path = PathBuf::from(program_data);
path.push(r"rustic\config");
path
})
}
#[cfg(any(target_os = "ios", target_arch = "wasm32"))]
fn get_global_config_path() -> Option<PathBuf> {
None
}
#[cfg(not(any(target_os = "windows", target_os = "ios", target_arch = "wasm32")))]
fn get_global_config_path() -> Option<PathBuf> {
Some(PathBuf::from("/etc/rustic"))
}