diff --git a/Cargo.lock b/Cargo.lock index 728c74604..abae9a9f4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1258,6 +1258,17 @@ dependencies = [ "serde", ] +[[package]] +name = "derive-getters" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74ef43543e701c01ad77d3a5922755c6a1d71b22d942cb8042be4994b380caff" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.87", +] + [[package]] name = "derive_destructure2" version = "0.1.3" @@ -3024,15 +3035,6 @@ dependencies = [ "libc", ] -[[package]] -name = "num_threads" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c7398b9c8b70908f6371f47ed36737907c87c52af34c268fed0bf0ceb92ead9" -dependencies = [ - "libc", -] - [[package]] name = "number_prefix" version = "0.4.0" @@ -4161,6 +4163,7 @@ dependencies = [ "assert_cmd", "bytesize", "cached 0.53.1", + "canonical-path", "cfg-if 1.0.0", "chrono", "clap", @@ -4171,6 +4174,7 @@ dependencies = [ "crossterm", "dateparser", "dav-server", + "derive-getters", "derive_more", "dialoguer", "dircmp", @@ -4187,7 +4191,6 @@ dependencies = [ "itertools 0.13.0", "jemallocator-global", "libc", - "log", "mimalloc", "open", "predicates", @@ -4206,7 +4209,6 @@ dependencies = [ "serde", "serde_json", "serde_with", - "simplelog", "tar", "tempfile", "thiserror 1.0.69", @@ -4331,9 +4333,9 @@ dependencies = [ [[package]] name = "rustix" -version = "0.38.40" +version = "0.38.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99e4ea3e1cdc4b559b8e5650f9c8e5998e3e5c1343b4eaf034565f32318d63c0" +checksum = "d7f649912bc1495e167a6edee79151c84b1bad49748cb4f1f1167f459f6224f6" dependencies = [ "bitflags 2.6.0", "errno", @@ -4792,17 +4794,6 @@ dependencies = [ "time", ] -[[package]] -name = "simplelog" -version = "0.12.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16257adbfaef1ee58b1363bdc0664c9b8e1e30aed86049635fb5f147d065a9c0" -dependencies = [ - "log", - "termcolor", - "time", -] - [[package]] name = "slab" version = "0.4.9" @@ -5170,9 +5161,7 @@ checksum = "5dfd88e563464686c916c7e46e623e520ddc6d79fa6641390f2e3fa86e83e885" dependencies = [ "deranged", "itoa", - "libc", "num-conv", - "num_threads", "powerfmt", "serde", "time-core", diff --git a/Cargo.toml b/Cargo.toml index 01cb3c7bd..7f5407a1f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -64,9 +64,6 @@ crossterm = { version = "0.28", optional = true } ratatui = { version = "0.29.0", optional = true } tui-textarea = { version = "0.7.0", optional = true } -# logging -log = "0.4" - # errors anyhow = "1" displaydoc = "0.2.5" @@ -84,16 +81,17 @@ comfy-table = "7" rhai = { version = "1", features = ["sync", "serde", "no_optimize", "no_module", "no_custom_syntax", "only_i64"] } scopeguard = "1" semver = { version = "1", optional = true } -simplelog = "0.12" # commands bytesize = "1" cached = "0.53.1" +canonical-path = "2.0.2" clap = { version = "4", features = ["derive", "env", "wrap_help"] } clap_complete = "4" conflate = "0.3.1" convert_case = "0.6.0" dateparser = "0.2.1" +derive-getters = { version = "0.5.0", features = ["auto_copy_getters"] } derive_more = { version = "1", features = ["debug"] } dialoguer = "0.11.0" directories = "5" diff --git a/src/application.rs b/src/application.rs index cd43fa7a3..4a5d0307f 100644 --- a/src/application.rs +++ b/src/application.rs @@ -1,17 +1,28 @@ //! Rustic Abscissa Application -use std::{env, process}; +use std::{ + env, + path::{Path, PathBuf}, + process, +}; use abscissa_core::{ application::{self, fatal_error, AppCell}, config::{self, CfgCell}, + path::{AbsPath, AbsPathBuf, ExePath, RootPath, SecretsPath}, terminal::component::Terminal, - Application, Component, FrameworkError, FrameworkErrorKind, Shutdown, StandardPaths, + trace::{self, Tracing}, + tracing::debug, + Application, Component, Configurable, FrameworkError, FrameworkErrorKind, Shutdown, }; - use anyhow::Result; +use derive_getters::Getters; +use directories::ProjectDirs; // use crate::helpers::*; -use crate::{commands::EntryPoint, config::RusticConfig}; +use crate::{ + commands::EntryPoint, + config::{get_global_config_path, RusticConfig}, +}; /// Application state pub static RUSTIC_APP: AppCell = AppCell::new(); @@ -22,8 +33,10 @@ pub mod constants { pub const RUSTIC_DEV_DOCS_URL: &str = "https://rustic.cli.rs/dev-docs"; pub const RUSTIC_CONFIG_DOCS_URL: &str = "https://github.com/rustic-rs/rustic/blob/main/config/README.md"; + /// Name of the application's secrets directory + pub(crate) const SECRETS_DIR: &str = "secrets"; + pub(crate) const LOGS_DIR: &str = "logs"; } - /// Rustic Application #[derive(Debug)] pub struct RusticApp { @@ -34,6 +47,123 @@ pub struct RusticApp { state: application::State, } +#[derive(Clone, Debug, Getters)] +pub struct RusticPaths { + /// Path to the application's executable. + exe: AbsPathBuf, + + /// Path to the application's root directory + root: AbsPathBuf, + + /// Path to the application's secrets + secrets: AbsPathBuf, + + /// Path to the application's cache directory + cache: AbsPathBuf, + + /// Path to the application's log directory + logs: AbsPathBuf, + + /// Path to the application's configuration directories + configs: Vec, +} + +impl ExePath for RusticPaths { + fn exe(&self) -> &AbsPath { + self.exe.as_ref() + } +} + +impl RootPath for RusticPaths { + fn root(&self) -> &AbsPath { + self.root.as_ref() + } +} + +impl SecretsPath for RusticPaths { + fn secrets(&self) -> &AbsPath { + self.secrets.as_ref() + } +} + +impl RusticPaths { + fn from_project_dirs() -> Result { + let project_dirs = ProjectDirs::from("", "", "rustic").ok_or_else(|| { + FrameworkErrorKind::PathError { + name: Some("project_dirs".into()), + } + .context("failed to determine project directories") + })?; + + let data = project_dirs.data_dir(); + let root = data.parent().ok_or_else(|| { + FrameworkErrorKind::PathError { + name: Some(data.to_path_buf()), + } + .context("failed to determine parent directory") + })?; + let secrets = data.join(constants::SECRETS_DIR); + let logs = root.join(constants::LOGS_DIR); + let config = project_dirs.config_dir(); + let cache = project_dirs.cache_dir(); + + let global_config = get_global_config_path().ok_or_else(|| { + FrameworkErrorKind::PathError { + name: Some("global config path".into()), + } + .context("failed to determine global config paths") + })?; + + let tmp_config = PathBuf::from("."); + + let dirs = [ + root, + data, + secrets.as_path(), + logs.as_path(), + config, + global_config.as_path(), + cache, + ]; + + Self::create_dirs(&dirs)?; + + Ok(Self { + exe: canonical_path::current_exe()?, + root: canonical_path::CanonicalPathBuf::new(root.canonicalize()?)?, + secrets: canonical_path::CanonicalPathBuf::new(secrets.canonicalize()?)?, + logs: canonical_path::CanonicalPathBuf::new(logs.canonicalize()?)?, + cache: canonical_path::CanonicalPathBuf::new(cache.canonicalize()?)?, + configs: vec![ + canonical_path::CanonicalPathBuf::new(config.canonicalize()?)?, + canonical_path::CanonicalPathBuf::new(global_config.canonicalize()?)?, + canonical_path::CanonicalPathBuf::new(tmp_config.canonicalize()?)?, + ], + }) + } + + fn create_dirs(dirs: &[&Path]) -> Result<(), FrameworkError> { + for dir in dirs { + if !dir.exists() { + debug!("Creating directory: {}", dir.display()); + std::fs::create_dir_all(dir).map_err(|err| { + FrameworkErrorKind::PathError { + name: Some(dir.to_path_buf()), + } + .context(err) + })?; + } + } + Ok(()) + } +} + +impl Default for RusticPaths { + fn default() -> Self { + Self::from_project_dirs().expect("failed to populate default impl for RusticPaths") + } +} + /// Initialize a new application instance. /// /// By default no configuration is loaded, and the framework state is @@ -55,7 +185,7 @@ impl Application for RusticApp { type Cfg = RusticConfig; /// Paths to resources within the application. - type Paths = StandardPaths; + type Paths = RusticPaths; /// Accessor for application configuration. fn config(&self) -> config::Reader { @@ -67,15 +197,34 @@ impl Application for RusticApp { &self.state } - /// Returns the framework components used by this application. + /// Load this application's configuration and initialize its components. + fn init(&mut self, command: &Self::Cmd) -> Result<(), FrameworkError> { + // Create and register components with the application. + // We do this first to calculate a proper dependency ordering before + // application configuration is processed + self.register_components(command)?; + + // Load configuration + let config = command.config.clone(); + + // Fire callback regardless of whether any config was loaded to + // in order to signal state in the application lifecycle + self.after_config(command.process_config(config)?)?; + + Ok(()) + } + + /// Initialize the framework's default set of components, potentially + /// sourcing terminal and tracing options from command line arguments. fn framework_components( &mut self, command: &Self::Cmd, ) -> Result>>, FrameworkError> { - // we only use the terminal component let terminal = Terminal::new(self.term_colors(command)); + let tracing = Tracing::new(self.tracing_config(command), self.term_colors(command)) + .expect("tracing subsystem failed to initialize"); - Ok(vec![Box::new(terminal)]) + Ok(vec![Box::new(terminal), Box::new(tracing)]) } /// Register all components used by this application. @@ -137,4 +286,20 @@ impl Application for RusticApp { process::exit(exit_code); } + + /// Get tracing configuration from command-line options + fn tracing_config(&self, command: &EntryPoint) -> trace::Config { + if command.verbose { + trace::Config::verbose() + } else { + command + .config + .global + .log_level + .as_ref() + .map_or_else(trace::Config::default, |level| { + trace::Config::from(level.to_owned()) + }) + } + } } diff --git a/src/commands.rs b/src/commands.rs index 0b242ea10..e2ba1d10b 100644 --- a/src/commands.rs +++ b/src/commands.rs @@ -32,30 +32,28 @@ pub(crate) mod tui; pub(crate) mod webdav; use std::fmt::Debug; -use std::fs::File; -use std::path::PathBuf; -use std::str::FromStr; #[cfg(feature = "mount")] use crate::commands::mount::MountCmd; #[cfg(feature = "webdav")] use crate::commands::webdav::WebDavCmd; use crate::{ + application::RusticPaths, 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, + find::FindCmd, 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, + config::Override, + tracing::log::{log, Level}, + Command, Configurable, FrameworkError, Runnable, }; use anyhow::Result; use clap::builder::{ @@ -64,10 +62,6 @@ use clap::builder::{ }; 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; /// Rustic Subcommands /// Subcommands need to be listed in an enum. @@ -172,6 +166,14 @@ pub struct EntryPoint { #[command(subcommand)] commands: RusticCmd, + + /// Enable verbose logging + #[arg(short, long)] + pub verbose: bool, + + /// Paths used by the application + #[clap(skip)] + pub paths: RusticPaths, } impl Runnable for EntryPoint { @@ -180,27 +182,11 @@ impl Runnable for EntryPoint { setup_panic!(); self.commands.run(); - RUSTIC_APP.shutdown(Shutdown::Graceful) } } -/// This trait allows you to define how application configuration is loaded. -impl Configurable for EntryPoint { - /// Location of the configuration file - fn config_path(&self) -> Option { - // Actually abscissa itself reads a config from `config_path`, but I have now returned None, - // i.e. no config is read. - None - } - - /// Apply changes to the config after it's been loaded, e.g. overriding - /// values in a config file using command-line options. - fn process_config(&self, _config: RusticConfig) -> Result { - // Note: The config that is "not read" is then read here in `process_config()` by the - // rustic logic and merged with the CLI options. - // That's why it says `_config`, because it's not read at all and therefore not needed. - let mut config = self.config.clone(); - +impl Override for EntryPoint { + fn override_config(&self, mut config: RusticConfig) -> Result { // collect "RUSTIC_REPO_OPT*" and "OPENDAL_*" env variables for (var, value) in std::env::vars() { if let Some(var) = var.strip_prefix("RUSTIC_REPO_OPT_") { @@ -221,58 +207,14 @@ impl Configurable for EntryPoint { // collect logs during merging as we start the logger *after* merging let mut merge_logs = Vec::new(); + let config_paths = self.paths.configs(); + // get global options from command line / env and config file if config.global.use_profiles.is_empty() { - config.merge_profile("rustic", &mut merge_logs, Level::Info)?; + config.merge_profile("rustic", &mut merge_logs, Level::Info, config_paths)?; } else { for profile in &config.global.use_profiles.clone() { - config.merge_profile(profile, &mut merge_logs, Level::Warn)?; - } - } - - // start logger - 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))?; + config.merge_profile(profile, &mut merge_logs, Level::Warn, config_paths)?; } } @@ -295,6 +237,15 @@ impl Configurable for EntryPoint { } } +/// This trait allows you to define how application configuration is loaded. +impl Configurable for EntryPoint { + /// Apply changes to the config after it's been loaded, e.g. overriding + /// values in a config file using command-line options. + fn process_config(&self, config: RusticConfig) -> Result { + self.override_config(config) + } +} + #[cfg(test)] mod tests { use crate::commands::EntryPoint; diff --git a/src/commands/backup.rs b/src/commands/backup.rs index 31dca3224..337bc52d8 100644 --- a/src/commands/backup.rs +++ b/src/commands/backup.rs @@ -10,12 +10,14 @@ use crate::{ status_err, Application, RUSTIC_APP, }; -use abscissa_core::{Command, Runnable, Shutdown}; +use abscissa_core::{ + tracing::{debug, error, info, warn}, + Command, Runnable, Shutdown, +}; use anyhow::{anyhow, bail, Context, Result}; use clap::ValueHint; use comfy_table::Cell; use conflate::Merge; -use log::{debug, error, info, warn}; use serde::{Deserialize, Serialize}; use serde_with::serde_as; diff --git a/src/commands/copy.rs b/src/commands/copy.rs index 8cc728ca1..8b814555c 100644 --- a/src/commands/copy.rs +++ b/src/commands/copy.rs @@ -6,10 +6,16 @@ use crate::{ repository::{CliIndexedRepo, CliRepo}, status_err, Application, RusticConfig, RUSTIC_APP, }; -use abscissa_core::{config::Override, Command, FrameworkError, Runnable, Shutdown}; +use abscissa_core::{ + config::Override, + tracing::{ + error, info, + log::{log, Level}, + }, + Command, FrameworkError, Runnable, Shutdown, +}; use anyhow::{bail, Result}; use conflate::Merge; -use log::{error, info, log, Level}; use serde::{Deserialize, Serialize}; use rustic_core::{repofile::SnapshotFile, CopySnapshot, Id, KeyOptions}; @@ -82,7 +88,12 @@ impl CopyCmd { for target in &config.copy.targets { let mut merge_logs = Vec::new(); let mut target_config = RusticConfig::default(); - target_config.merge_profile(target, &mut merge_logs, Level::Error)?; + target_config.merge_profile( + target, + &mut merge_logs, + Level::Error, + RUSTIC_APP.state().paths().configs(), + )?; // display logs from merging for (level, merge_log) in merge_logs { log!(level, "{}", merge_log); diff --git a/src/commands/diff.rs b/src/commands/diff.rs index 5333ba9c9..5ed8748fe 100644 --- a/src/commands/diff.rs +++ b/src/commands/diff.rs @@ -2,9 +2,8 @@ use crate::{repository::CliIndexedRepo, status_err, Application, RUSTIC_APP}; -use abscissa_core::{Command, Runnable, Shutdown}; +use abscissa_core::{tracing::debug, Command, Runnable, Shutdown}; use clap::ValueHint; -use log::debug; use std::{ fmt::Display, diff --git a/src/commands/dump.rs b/src/commands/dump.rs index 9476f4919..7784ae582 100644 --- a/src/commands/dump.rs +++ b/src/commands/dump.rs @@ -4,9 +4,8 @@ use std::io::{Read, Write}; use crate::{repository::CliIndexedRepo, status_err, Application, RUSTIC_APP}; -use abscissa_core::{Command, Runnable, Shutdown}; +use abscissa_core::{tracing::warn, Command, Runnable, Shutdown}; use anyhow::Result; -use log::warn; use rustic_core::{ repofile::{Node, NodeType}, vfs::OpenFile, diff --git a/src/commands/key.rs b/src/commands/key.rs index 9670a378a..b184efecf 100644 --- a/src/commands/key.rs +++ b/src/commands/key.rs @@ -4,10 +4,9 @@ use crate::{repository::CliOpenRepo, status_err, Application, RUSTIC_APP}; use std::path::PathBuf; -use abscissa_core::{Command, Runnable, Shutdown}; +use abscissa_core::{tracing::info, Command, Runnable, Shutdown}; use anyhow::Result; use dialoguer::Password; -use log::info; use rustic_core::{CommandInput, KeyOptions, RepositoryOptions}; diff --git a/src/commands/merge.rs b/src/commands/merge.rs index bb1525e03..74b6e4407 100644 --- a/src/commands/merge.rs +++ b/src/commands/merge.rs @@ -1,9 +1,8 @@ //! `merge` subcommand use crate::{repository::CliOpenRepo, status_err, Application, RUSTIC_APP}; -use abscissa_core::{Command, Runnable, Shutdown}; +use abscissa_core::{tracing::info, Command, Runnable, Shutdown}; use anyhow::Result; -use log::info; use chrono::Local; diff --git a/src/commands/prune.rs b/src/commands/prune.rs index 5fcc6f7bc..de336e4a2 100644 --- a/src/commands/prune.rs +++ b/src/commands/prune.rs @@ -3,8 +3,7 @@ use crate::{ helpers::bytes_size_to_string, repository::CliOpenRepo, status_err, Application, RUSTIC_APP, }; -use abscissa_core::{Command, Runnable, Shutdown}; -use log::debug; +use abscissa_core::{tracing::debug, Command, Runnable, Shutdown}; use anyhow::Result; diff --git a/src/commands/restore.rs b/src/commands/restore.rs index b651a24aa..56b65f65d 100644 --- a/src/commands/restore.rs +++ b/src/commands/restore.rs @@ -4,9 +4,8 @@ use crate::{ helpers::bytes_size_to_string, repository::CliIndexedRepo, status_err, Application, RUSTIC_APP, }; -use abscissa_core::{Command, Runnable, Shutdown}; +use abscissa_core::{tracing::info, Command, Runnable, Shutdown}; use anyhow::Result; -use log::info; use rustic_core::{LocalDestination, LsOptions, RestoreOptions}; diff --git a/src/config.rs b/src/config.rs index 80434c99e..fb847d34d 100644 --- a/src/config.rs +++ b/src/config.rs @@ -10,13 +10,12 @@ pub(crate) mod progress_options; use std::fmt::Debug; use std::{collections::HashMap, path::PathBuf}; -use abscissa_core::{config::Config, path::AbsPathBuf, FrameworkError}; +use abscissa_core::{config::Config, path::AbsPathBuf, tracing::log::Level, FrameworkError}; use anyhow::Result; +use canonical_path::CanonicalPathBuf; use clap::{Parser, ValueHint}; use conflate::Merge; -use directories::ProjectDirs; use itertools::Itertools; -use log::Level; use serde::{Deserialize, Serialize}; #[cfg(not(all(feature = "mount", feature = "webdav")))] use toml::Value; @@ -97,20 +96,31 @@ impl RusticConfig { profile: &str, merge_logs: &mut Vec<(Level, String)>, level_missing: Level, + paths: &[CanonicalPathBuf], ) -> 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()) { + let paths_with_filenames: Vec = paths + .iter() + .map(|path| { + let mut path = (*path).clone().into_path_buf(); + path.push(profile.to_string() + ".toml"); + path + }) + .collect(); + + if let Some(path) = paths_with_filenames.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)?)?; // if "use_profile" is defined in config file, merge the referenced profiles first for profile in &config.global.use_profiles.clone() { - config.merge_profile(profile, merge_logs, Level::Warn)?; + config.merge_profile(profile, merge_logs, Level::Warn, paths)?; } self.merge(config); } else { - let paths_string = paths.iter().map(|path| path.display()).join(", "); + let paths_string = paths_with_filenames + .iter() + .map(|path| path.display()) + .join(", "); merge_logs.push(( level_missing, format!( @@ -180,32 +190,6 @@ pub struct GlobalOptions { pub env: HashMap, } -/// Get the paths to the config file -/// -/// # Arguments -/// -/// * `filename` - name of the config file -/// -/// # Returns -/// -/// A vector of [`PathBuf`]s to the config files -fn get_config_paths(filename: &str) -> Vec { - [ - 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() -} - /// Get the path to the global config directory on Windows. /// /// # Returns @@ -213,7 +197,7 @@ fn get_config_paths(filename: &str) -> Vec { /// The path to the global config directory on Windows. /// If the environment variable `PROGRAMDATA` is not set, `None` is returned. #[cfg(target_os = "windows")] -fn get_global_config_path() -> Option { +pub(crate) fn get_global_config_path() -> Option { std::env::var_os("PROGRAMDATA").map(|program_data| { let mut path = PathBuf::from(program_data); path.push(r"rustic\config"); @@ -227,7 +211,7 @@ fn get_global_config_path() -> Option { /// /// `None` is returned. #[cfg(any(target_os = "ios", target_arch = "wasm32"))] -fn get_global_config_path() -> Option { +pub(crate) fn get_global_config_path() -> Option { None } @@ -238,7 +222,7 @@ fn get_global_config_path() -> Option { /// /// "/etc/rustic" is returned. #[cfg(not(any(target_os = "windows", target_os = "ios", target_arch = "wasm32")))] -fn get_global_config_path() -> Option { +pub(crate) fn get_global_config_path() -> Option { Some(PathBuf::from("/etc/rustic")) } diff --git a/src/filtering.rs b/src/filtering.rs index 93327c0ed..18bc46238 100644 --- a/src/filtering.rs +++ b/src/filtering.rs @@ -1,8 +1,8 @@ use crate::error::RhaiErrorKinds; +use abscissa_core::tracing::warn; use bytesize::ByteSize; use derive_more::derive::Display; -use log::warn; use rustic_core::{repofile::SnapshotFile, StringList}; use std::{ error::Error, diff --git a/tests/backup_restore.rs b/tests/backup_restore.rs index 0beaf5b66..762af0544 100644 --- a/tests/backup_restore.rs +++ b/tests/backup_restore.rs @@ -37,8 +37,8 @@ fn setup() -> TestResult { .args(["init"]) .assert() .success() - .stderr(predicate::str::contains("successfully created.")) - .stderr(predicate::str::contains("successfully added.")); + .stdout(predicate::str::contains("successfully created.")) + .stdout(predicate::str::contains("successfully added.")); Ok(temp_dir) } @@ -93,8 +93,8 @@ fn test_backup_and_check_passes() -> TestResult<()> { .args(["check", "--read-data"]) .assert() .success() - .stderr(predicate::str::contains("WARN").not()) - .stderr(predicate::str::contains("ERROR").not()); + .stdout(predicate::str::contains("WARN").not()) + .stdout(predicate::str::contains("ERROR").not()); } Ok(()) diff --git a/tests/config.rs b/tests/config.rs index 1741c71ed..9f2fc85c8 100644 --- a/tests/config.rs +++ b/tests/config.rs @@ -15,3 +15,8 @@ fn test_parse_rustic_configs_is_ok( Ok(()) } + +#[test] +fn test_debug_config_passes() { + insta::assert_debug_snapshot!(RusticConfig::default()); +} diff --git a/tests/hooks.rs b/tests/hooks.rs index 897f32c14..52ec8330c 100644 --- a/tests/hooks.rs +++ b/tests/hooks.rs @@ -82,8 +82,8 @@ fn setup(with_backup: BackupAction) -> TestResult { .args(["init"]) .assert() .success() - .stderr(predicate::str::contains("successfully created.")) - .stderr(predicate::str::contains("successfully added.")); + .stdout(predicate::str::contains("successfully created.")) + .stdout(predicate::str::contains("successfully added.")); match with_backup { BackupAction::WithBackup => { diff --git a/tests/show-config.rs b/tests/show-config.rs index e2aef531c..d524b3bfe 100644 --- a/tests/show-config.rs +++ b/tests/show-config.rs @@ -5,6 +5,7 @@ use std::{io::Read, sync::LazyLock}; use abscissa_core::testing::prelude::*; use insta::assert_snapshot; + use rustic_testing::TestResult; // Storing this value as a [`Lazy`] static ensures that all instances of @@ -23,19 +24,19 @@ fn cmd_runner() -> CmdRunner { #[test] fn test_show_config_passes() -> TestResult<()> { - { - let mut runner = cmd_runner(); + + let mut runner = cmd_runner(); - let mut cmd = runner.args(["show-config"]).run(); + let mut cmd = runner.args(["show-config"]).run(); - let mut output = String::new(); + let mut output = String::new(); - cmd.stdout().read_to_string(&mut output)?; + cmd.stdout().read_to_string(&mut output)?; - assert_snapshot!(output); + assert_snapshot!(output); - cmd.wait()?.expect_success(); - } + cmd.wait()?.expect_success(); + Ok(()) } diff --git a/tests/snapshots/config__debug_config_passes.snap b/tests/snapshots/config__debug_config_passes.snap new file mode 100644 index 000000000..96fcabf0f --- /dev/null +++ b/tests/snapshots/config__debug_config_passes.snap @@ -0,0 +1,194 @@ +--- +source: tests/config.rs +expression: "RusticConfig::default()" +--- +RusticConfig { + global: GlobalOptions { + use_profiles: [], + dry_run: false, + check_index: false, + log_level: None, + log_file: None, + progress_options: ProgressOptions { + no_progress: false, + progress_interval: None, + }, + hooks: Hooks { + run_before: [], + run_after: [], + run_failed: [], + run_finally: [], + context: "", + }, + env: {}, + }, + repository: AllRepositoryOptions { + be: BackendOptions { + repository: None, + repo_hot: None, + options: {}, + options_hot: {}, + options_cold: {}, + }, + repo: RepositoryOptions { + password: None, + password_file: None, + password_command: None, + no_cache: false, + cache_dir: None, + warm_up: false, + warm_up_command: None, + warm_up_wait: None, + }, + hooks: Hooks { + run_before: [], + run_after: [], + run_failed: [], + run_finally: [], + context: "", + }, + }, + snapshot_filter: SnapshotFilter { + filter_hosts: [], + filter_labels: [], + filter_paths: [], + filter_paths_exact: [], + filter_tags: [], + filter_tags_exact: [], + filter_after: None, + filter_before: None, + filter_size: None, + filter_size_added: None, + filter_fn: None, + }, + backup: BackupCmd { + cli_sources: [], + stdin_filename: "", + stdin_command: None, + as_path: None, + ignore_save_opts: LocalSourceSaveOptions { + with_atime: false, + ignore_devid: false, + }, + no_scan: false, + json: false, + long: false, + quiet: false, + init: false, + parent_opts: ParentOptions { + group_by: None, + parent: None, + skip_identical_parent: false, + force: false, + ignore_ctime: false, + ignore_inode: false, + }, + ignore_filter_opts: LocalSourceFilterOptions { + globs: [], + iglobs: [], + glob_files: [], + iglob_files: [], + git_ignore: false, + no_require_git: false, + custom_ignorefiles: [], + exclude_if_present: [], + one_file_system: false, + exclude_larger_than: None, + }, + snap_opts: SnapshotOptions { + label: None, + tags: [], + description: None, + description_from: None, + time: None, + delete_never: false, + delete_after: None, + host: None, + command: None, + }, + key_opts: KeyOptions { + hostname: None, + username: None, + with_created: false, + }, + config_opts: ConfigOptions { + set_compression: None, + set_version: None, + set_append_only: None, + set_treepack_size: None, + set_treepack_size_limit: None, + set_treepack_growfactor: None, + set_datapack_size: None, + set_datapack_growfactor: None, + set_datapack_size_limit: None, + set_min_packsize_tolerate_percent: None, + set_max_packsize_tolerate_percent: None, + set_extra_verify: None, + }, + hooks: Hooks { + run_before: [], + run_after: [], + run_failed: [], + run_finally: [], + context: "", + }, + snapshots: [], + sources: [], + }, + copy: CopyCmd { + ids: [], + init: false, + targets: [], + key_opts: KeyOptions { + hostname: None, + username: None, + with_created: false, + }, + }, + forget: ForgetOptions { + group_by: None, + prune: false, + filter: SnapshotFilter { + filter_hosts: [], + filter_labels: [], + filter_paths: [], + filter_paths_exact: [], + filter_tags: [], + filter_tags_exact: [], + filter_after: None, + filter_before: None, + filter_size: None, + filter_size_added: None, + filter_fn: None, + }, + keep: KeepOptions { + keep_tags: [], + keep_ids: [], + keep_last: None, + keep_hourly: None, + keep_daily: None, + keep_weekly: None, + keep_monthly: None, + keep_quarter_yearly: None, + keep_half_yearly: None, + keep_yearly: None, + keep_within: None, + keep_within_hourly: None, + keep_within_daily: None, + keep_within_weekly: None, + keep_within_monthly: None, + keep_within_quarter_yearly: None, + keep_within_half_yearly: None, + keep_within_yearly: None, + keep_none: false, + }, + }, + webdav: WebDavCmd { + address: None, + path_template: None, + time_template: None, + symlinks: false, + file_access: None, + snapshot_path: None, + }, +}