diff --git a/docs/content/configuration/config-file/index.md b/docs/content/configuration/config-file/index.md index 8ccf31f4..433d4f3c 100644 --- a/docs/content/configuration/config-file/index.md +++ b/docs/content/configuration/config-file/index.md @@ -12,8 +12,8 @@ If no config file argument is given, it will automatically look for a config fil | Linux | `~/.config/bottom/bottom.toml`
`$XDG_CONFIG_HOME/bottom/bottom.toml` | | Windows | `C:\Users\\AppData\Roaming\bottom\bottom.toml` | -Like if a path is passed with `-C`/`--config`, if a file doesn't exist at the path, bottom will automatically create a -new, default config file at that location. +If the config file doesn't exist at the path, bottom will automatically try to create a new config file at the location +with default values. ## JSON Schema diff --git a/src/main.rs b/src/main.rs index b6e703ab..c7c979ce 100644 --- a/src/main.rs +++ b/src/main.rs @@ -27,7 +27,6 @@ use std::{ boxed::Box, io::{stderr, stdout, Write}, panic::{self, PanicInfo}, - path::Path, sync::{ mpsc::{self, Receiver, Sender}, Arc, Condvar, Mutex, @@ -36,7 +35,6 @@ use std::{ time::{Duration, Instant}, }; -use anyhow::Context; use app::{layout_manager::UsedWidgets, App, AppConfigFields, DataFilters}; use crossterm::{ event::{ @@ -49,7 +47,7 @@ use crossterm::{ }; use data_conversion::*; use event::{handle_key_event_or_break, handle_mouse_event, BottomEvent, CollectionThreadEvent}; -use options::{args, get_config_path, get_or_create_config, init_app}; +use options::{args, get_or_create_config, init_app}; use tui::{backend::CrosstermBackend, Terminal}; #[allow(unused_imports)] use utils::logging::*; @@ -330,18 +328,8 @@ fn main() -> anyhow::Result<()> { } // Read from config file. - let config = { - let config_path = get_config_path(args.general.config_location.as_deref()); - get_or_create_config(config_path.as_deref()).with_context(|| { - format!( - "Unable to parse or create the config file at: {}", - config_path - .as_deref() - .unwrap_or_else(|| Path::new("")) - .display() - ) - })? - }; + + let config = get_or_create_config(args.general.config_location.as_deref())?; // Create the "app" and initialize a bunch of stuff. let (mut app, widget_layout, styling) = init_app(args, config)?; diff --git a/src/options.rs b/src/options.rs index b1500329..b770b347 100644 --- a/src/options.rs +++ b/src/options.rs @@ -68,7 +68,7 @@ macro_rules! is_flag_enabled { /// /// For more details on this, see [dirs](https://docs.rs/dirs/latest/dirs/fn.config_dir.html)' /// documentation. -pub fn get_config_path(override_config_path: Option<&Path>) -> Option { +fn get_config_path(override_config_path: Option<&Path>) -> Option { const DEFAULT_CONFIG_FILE_PATH: &str = "bottom/bottom.toml"; if let Some(conf_loc) = override_config_path { @@ -92,29 +92,73 @@ pub fn get_config_path(override_config_path: Option<&Path>) -> Option { }) } +fn create_config_at_path(path: &Path) -> anyhow::Result { + if let Some(parent_path) = path.parent() { + fs::create_dir_all(parent_path)?; + } + + let mut file = fs::File::create(path)?; + file.write_all(CONFIG_TEXT.as_bytes())?; + + Ok(Config::default()) +} + /// Get the config at `config_path`. If there is no config file at the specified /// path, it will try to create a new file with the default settings, and return -/// the default config. If bottom fails to write a new config, it will silently -/// just return the default config. -pub fn get_or_create_config(config_path: Option<&Path>) -> OptionResult { - match &config_path { +/// the default config. +/// +/// We're going to use the following behaviour on when we'll return an error rather +/// than just "silently" continuing on: +/// - If the user passed in a path explicitly, then we will be loud and error out. +/// - If the user does NOT pass in a path explicitly, then just show a warning, +/// but continue. This is in case they do not want to write a default config file at +/// the XDG locations, for example. +pub fn get_or_create_config(config_path: Option<&Path>) -> anyhow::Result { + let adjusted_config_path = get_config_path(config_path); + + match &adjusted_config_path { Some(path) => { if let Ok(config_string) = fs::read_to_string(path) { Ok(toml_edit::de::from_str(&config_string)?) } else { - if let Some(parent_path) = path.parent() { - fs::create_dir_all(parent_path)?; - } + match create_config_at_path(path) { + Ok(cfg) => Ok(cfg), + Err(err) => { + if config_path.is_some() { + Err(err.context(format!( + "bottom could not create a new config file at '{}'.", + path.display() + ))) + } else { + indoc::eprintdoc!( + "Note: bottom couldn't create a default config file at '{}', and the \ + application has fallen back to the default configuration. + + Caused by: + {err} + ", + path.display() + ); - fs::File::create(path)?.write_all(CONFIG_TEXT.as_bytes())?; - Ok(Config::default()) + Ok(Config::default()) + } + } + } } } None => { // If we somehow don't have any config path, then just assume the default config // but don't write to any file. // - // TODO: Maybe make this "show" an error, but don't crash. + // TODO: For now, just print a message to stderr indicating this. In the future, + // probably show in-app (too). + + eprintln!( + "Note: bottom couldn't find a location to create or read a config file, so \ + the application has fallen back to the default configuration. \ + This could be for a variety of reasons, such as issues with file permissions." + ); + Ok(Config::default()) } }