pub(crate) mod backup;
pub(crate) mod cat;
pub(crate) mod check;
pub(crate) mod completions;
pub(crate) mod config;
pub(crate) mod copy;
pub(crate) mod diff;
pub(crate) mod docs;
pub(crate) mod dump;
pub(crate) mod find;
pub(crate) mod forget;
pub(crate) mod init;
pub(crate) mod key;
pub(crate) mod list;
pub(crate) mod ls;
pub(crate) mod merge;
pub(crate) mod prune;
pub(crate) mod repair;
pub(crate) mod repoinfo;
pub(crate) mod restore;
pub(crate) mod self_update;
pub(crate) mod show_config;
pub(crate) mod snapshots;
pub(crate) mod tag;
#[cfg(feature = "tui")]
pub(crate) mod tui;
#[cfg(feature = "webdav")]
pub(crate) mod webdav;
use std::fmt::Debug;
use std::fs::File;
use std::path::PathBuf;
use std::str::FromStr;
#[cfg(feature = "webdav")]
use crate::commands::webdav::WebDavCmd;
use crate::{
commands::{
backup::BackupCmd, cat::CatCmd, check::CheckCmd, completions::CompletionsCmd,
config::ConfigCmd, copy::CopyCmd, diff::DiffCmd, docs::DocsCmd, dump::DumpCmd,
forget::ForgetCmd, init::InitCmd, key::KeyCmd, list::ListCmd, ls::LsCmd, merge::MergeCmd,
prune::PruneCmd, repair::RepairCmd, repoinfo::RepoInfoCmd, restore::RestoreCmd,
self_update::SelfUpdateCmd, show_config::ShowConfigCmd, snapshots::SnapshotCmd,
tag::TagCmd,
},
config::RusticConfig,
Application, RUSTIC_APP,
};
use abscissa_core::{
config::Override, terminal::ColorChoice, Command, Configurable, FrameworkError,
FrameworkErrorKind, Runnable, Shutdown,
};
use anyhow::Result;
use clap::builder::{
styling::{AnsiColor, Effects},
Styles,
};
use convert_case::{Case, Casing};
use human_panic::setup_panic;
use log::{log, Level};
use simplelog::{CombinedLogger, LevelFilter, TermLogger, TerminalMode, WriteLogger};
use self::find::FindCmd;
#[derive(clap::Parser, Command, Debug, Runnable)]
enum RusticCmd {
Backup(BackupCmd),
Cat(CatCmd),
Config(ConfigCmd),
Completions(CompletionsCmd),
Check(CheckCmd),
Copy(CopyCmd),
Diff(DiffCmd),
Docs(DocsCmd),
Dump(DumpCmd),
Find(FindCmd),
Forget(ForgetCmd),
Init(InitCmd),
Key(KeyCmd),
List(ListCmd),
Ls(LsCmd),
Merge(MergeCmd),
Snapshots(SnapshotCmd),
ShowConfig(ShowConfigCmd),
#[cfg_attr(not(feature = "self-update"), clap(hide = true))]
SelfUpdate(SelfUpdateCmd),
Prune(PruneCmd),
Restore(RestoreCmd),
Repair(RepairCmd),
Repoinfo(RepoInfoCmd),
Tag(TagCmd),
#[cfg(feature = "webdav")]
Webdav(WebDavCmd),
}
fn styles() -> Styles {
Styles::styled()
.header(AnsiColor::Red.on_default() | Effects::BOLD)
.usage(AnsiColor::Red.on_default() | Effects::BOLD)
.literal(AnsiColor::Blue.on_default() | Effects::BOLD)
.placeholder(AnsiColor::Green.on_default())
}
#[derive(clap::Parser, Command, Debug)]
#[command(author, about, name="rustic", styles=styles(), version = option_env!("PROJECT_VERSION").unwrap_or(env!("CARGO_PKG_VERSION")))]
pub struct EntryPoint {
#[command(flatten)]
pub config: RusticConfig,
#[command(subcommand)]
commands: RusticCmd,
}
impl Runnable for EntryPoint {
fn run(&self) {
setup_panic!();
self.commands.run();
RUSTIC_APP.shutdown(Shutdown::Graceful)
}
}
impl Configurable<RusticConfig> for EntryPoint {
fn config_path(&self) -> Option<PathBuf> {
None
}
fn process_config(&self, _config: RusticConfig) -> Result<RusticConfig, FrameworkError> {
let mut config = self.config.clone();
for (var, value) in std::env::vars() {
if let Some(var) = var.strip_prefix("RUSTIC_REPO_OPT_") {
let var = var.from_case(Case::UpperSnake).to_case(Case::Kebab);
_ = config.repository.be.options.insert(var, value);
} else if let Some(var) = var.strip_prefix("OPENDAL_") {
let var = var.from_case(Case::UpperSnake).to_case(Case::Snake);
_ = config.repository.be.options.insert(var, value);
} else if let Some(var) = var.strip_prefix("RUSTIC_REPO_OPTHOT_") {
let var = var.from_case(Case::UpperSnake).to_case(Case::Kebab);
_ = config.repository.be.options_hot.insert(var, value);
} else if let Some(var) = var.strip_prefix("RUSTIC_REPO_OPTCOLD_") {
let var = var.from_case(Case::UpperSnake).to_case(Case::Kebab);
_ = config.repository.be.options_cold.insert(var, value);
}
}
let mut merge_logs = Vec::new();
if config.global.use_profiles.is_empty() {
config.merge_profile("rustic", &mut merge_logs, Level::Info)?;
} else {
for profile in &config.global.use_profiles.clone() {
config.merge_profile(profile, &mut merge_logs, Level::Warn)?;
}
}
let level_filter = match &config.global.log_level {
Some(level) => LevelFilter::from_str(level)
.map_err(|e| FrameworkErrorKind::ConfigError.context(e))?,
None => LevelFilter::Info,
};
let term_config = simplelog::ConfigBuilder::new()
.set_time_level(LevelFilter::Off)
.build();
match &config.global.log_file {
None => TermLogger::init(
level_filter,
term_config,
TerminalMode::Stderr,
ColorChoice::Auto,
)
.map_err(|e| FrameworkErrorKind::ConfigError.context(e))?,
Some(file) => {
let file_config = simplelog::ConfigBuilder::new()
.set_time_format_rfc3339()
.build();
let file = File::options()
.create(true)
.append(true)
.open(file)
.map_err(|e| {
FrameworkErrorKind::PathError {
name: Some(file.clone()),
}
.context(e)
})?;
let term_logger = TermLogger::new(
level_filter.min(LevelFilter::Warn),
term_config,
TerminalMode::Stderr,
ColorChoice::Auto,
);
CombinedLogger::init(vec![
term_logger,
WriteLogger::new(level_filter, file_config, file),
])
.map_err(|e| FrameworkErrorKind::ConfigError.context(e))?;
}
}
for (level, merge_log) in merge_logs {
log!(level, "{}", merge_log);
}
match &self.commands {
RusticCmd::Forget(cmd) => cmd.override_config(config),
RusticCmd::Copy(cmd) => cmd.override_config(config),
#[cfg(feature = "webdav")]
RusticCmd::Webdav(cmd) => cmd.override_config(config),
_ => Ok(config),
}
}
}
#[cfg(test)]
mod tests {
use crate::commands::EntryPoint;
use clap::CommandFactory;
#[test]
fn verify_cli() {
EntryPoint::command().debug_assert();
}
}