diff --git a/.cargo-husky/hooks/pre-push b/.cargo-husky/hooks/pre-push new file mode 100755 index 00000000..46fb32d2 --- /dev/null +++ b/.cargo-husky/hooks/pre-push @@ -0,0 +1,2 @@ +echo "Running pre-push hook: cargo +nightly clippy -- -D clippy::all" +cargo +nightly clippy -- -D clippy::all \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json index d3f49cfa..b5030dc0 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -38,6 +38,7 @@ "curr", "czvf", "fpath", + "fract", "gotop", "gtop", "haase", diff --git a/Cargo.lock b/Cargo.lock index 9c458943..20f2221f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -27,6 +27,12 @@ dependencies = [ "winapi 0.3.9", ] +[[package]] +name = "anyhow" +version = "1.0.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b602bfe940d21c130f3895acd65221e8a61270debe89d628b9cb4e3ccb8569b" + [[package]] name = "arc-swap" version = "0.4.6" @@ -131,6 +137,7 @@ dependencies = [ name = "bottom" version = "0.4.7" dependencies = [ + "anyhow", "assert_cmd", "backtrace", "battery", @@ -151,6 +158,7 @@ dependencies = [ "regex", "serde", "sysinfo", + "thiserror", "toml", "tui", "typed-builder", @@ -1273,6 +1281,26 @@ dependencies = [ "unicode-width", ] +[[package]] +name = "thiserror" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dfdd070ccd8ccb78f4ad66bf1982dc37f620ef696c6b5028fe2ed83dd3d0d08" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd80fc12f73063ac132ac92aceea36734f04a1d93c1240c6944e23a3b8841793" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "thread_local" version = "1.0.1" diff --git a/Cargo.toml b/Cargo.toml index ce60333a..cdac5df4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,14 +24,17 @@ lto = "fat" codegen-units = 1 [dependencies] +anyhow = "1.0.32" battery = "0.7.6" -crossterm = "0.17" chrono = "0.4.15" +crossterm = "0.17" +ctrlc = {version = "3.1", features = ["termination"]} clap = "2.33" dirs = "3.0.1" futures = "0.3.5" heim = "0.0.10" itertools = "0.9.0" +libc = "0.2" regex = "1.3" sysinfo = "0.15.1" toml = "0.5.6" @@ -41,8 +44,7 @@ backtrace = "0.3" serde = {version = "1.0", features = ["derive"] } unicode-segmentation = "1.6.0" unicode-width = "0.1" -libc = "0.2" -ctrlc = {version = "3.1", features = ["termination"]} +thiserror = "1.0.20" tui = {version = "0.9.5", features = ["crossterm"], default-features = false } # For debugging only... @@ -82,5 +84,5 @@ output = "bottom_x86_64_installer.msi" [dev-dependencies.cargo-husky] version = "1" -default-features = false -features = ["prepush-hook", "run-cargo-clippy"] \ No newline at end of file +default-features = false +features = ["user-hooks"] \ No newline at end of file diff --git a/README.md b/README.md index 0083f3f7..cbf5b55e 100644 --- a/README.md +++ b/README.md @@ -509,7 +509,7 @@ Supported named colours are one of the following strings: `Reset, Black, Red, Gr | Cursor colour | The cursor's colour | `cursor_color="#ffffff"` | | Selected text colour | The colour of text that is selected | `scroll_entry_text_color="#ffffff"` | | Selected text background colour | The background colour of text that is selected | `scroll_entry_bg_color="#ffffff"` | -| Battery bar colours | Colour used is based on percentage and no. of colours | `battery_colours=["green", "yellow", "red"]` | +| Battery bar colours | Colour used is based on percentage and no. of colours | `battery_colors=["green", "yellow", "red"]` | #### Layout diff --git a/src/app/data_harvester/processes.rs b/src/app/data_harvester/processes.rs index bdd6a609..dcd6d647 100644 --- a/src/app/data_harvester/processes.rs +++ b/src/app/data_harvester/processes.rs @@ -253,11 +253,11 @@ fn read_proc<S: core::hash::BuildHasher>( .splitn(2, '(') .collect::<Vec<_>>() .last() - .ok_or(BottomError::MinorError())? + .ok_or(BottomError::MinorError)? .rsplitn(2, ')') .collect::<Vec<_>>() .last() - .ok_or(BottomError::MinorError())? + .ok_or(BottomError::MinorError)? .to_string(); let command = { let cmd = read_path_contents(&pid_stat.proc_cmdline_path)?; @@ -271,7 +271,7 @@ fn read_proc<S: core::hash::BuildHasher>( .split(')') .collect::<Vec<_>>() .last() - .ok_or(BottomError::MinorError())? + .ok_or(BottomError::MinorError)? .split_whitespace() .collect::<Vec<&str>>(); let (process_state_char, process_state) = get_linux_process_state(&stat); diff --git a/src/app/layout_manager.rs b/src/app/layout_manager.rs index 35c6e02f..6036c2a5 100644 --- a/src/app/layout_manager.rs +++ b/src/app/layout_manager.rs @@ -943,7 +943,25 @@ impl std::str::FromStr for BottomWidgetType { "empty" => Ok(BottomWidgetType::Empty), "battery" | "batt" => Ok(BottomWidgetType::Battery), _ => Err(BottomError::ConfigError(format!( - "invalid widget type: {}", // FIXME: Make this more helpful, specify valid widget types (just go through the list) + "\"{}\" is an invalid widget name. + +Supported widget names: ++--------------------------+ +| cpu | ++--------------------------+ +| mem, memory | ++--------------------------+ +| net, network | ++--------------------------+ +| proc, process, processes | ++--------------------------+ +| temp, temperature | ++--------------------------+ +| disk | ++--------------------------+ +| batt, battery | ++--------------------------+ + ", s ))), } diff --git a/src/bin/main.rs b/src/bin/main.rs index 95444491..7f8b9900 100644 --- a/src/bin/main.rs +++ b/src/bin/main.rs @@ -3,7 +3,7 @@ #[macro_use] extern crate log; -use bottom::{canvas, constants::*, data_conversion::*, options::*, utils::error, *}; +use bottom::{canvas, constants::*, data_conversion::*, options::*, *}; use std::{ boxed::Box, @@ -17,6 +17,7 @@ use std::{ time::Duration, }; +use anyhow::{Context, Result}; use crossterm::{ event::EnableMouseCapture, execute, @@ -24,18 +25,22 @@ use crossterm::{ }; use tui::{backend::CrosstermBackend, Terminal}; -fn main() -> error::Result<()> { +fn main() -> Result<()> { #[cfg(debug_assertions)] { utils::logging::init_logger()?; } let matches = clap::get_matches(); - let config: Config = create_config(matches.value_of("CONFIG_LOCATION"))?; + let config_path = read_config(matches.value_of("CONFIG_LOCATION")) + .context("Unable to access the given config file location.")?; + let config: Config = create_or_get_config(&config_path) + .context("Unable to properly parse or create the config file.")?; // Get widget layout separately let (widget_layout, default_widget_id, default_widget_type_option) = - get_widget_layout(&matches, &config)?; + get_widget_layout(&matches, &config) + .context("Found an issue while trying to build the widget layout.")?; // Create "app" struct, which will control most of the program and store settings/state let mut app = build_app( diff --git a/src/canvas/canvas_colours.rs b/src/canvas/canvas_colours.rs index 86c96f4b..98932d5d 100644 --- a/src/canvas/canvas_colours.rs +++ b/src/canvas/canvas_colours.rs @@ -1,5 +1,4 @@ use tui::style::{Color, Style}; -// use tui::style::Modifier; use colour_utils::*; @@ -175,11 +174,10 @@ impl CanvasColours { Ok(()) } - pub fn set_battery_colours(&mut self, colours: &[String]) -> error::Result<()> { + pub fn set_battery_colors(&mut self, colours: &[String]) -> error::Result<()> { if colours.is_empty() { Err(error::BottomError::ConfigError( - "invalid colour config: battery colour list must have at least one colour!" - .to_string(), + "battery colour list must have at least one colour.".to_string(), )) } else { let generated_colours: Result<Vec<_>, _> = colours diff --git a/src/canvas/canvas_colours/colour_utils.rs b/src/canvas/canvas_colours/colour_utils.rs index 8b2f0594..f94b9bd9 100644 --- a/src/canvas/canvas_colours/colour_utils.rs +++ b/src/canvas/canvas_colours/colour_utils.rs @@ -100,7 +100,7 @@ pub fn convert_hex_to_color(hex: &str) -> error::Result<Color> { fn hex_err(hex: &str) -> error::Result<u8> { Err( error::BottomError::ConfigError(format!( - "invalid color hex: error when parsing hex value {}. It must be a valid 7 character hex string of the (ie: \"#112233\")." + "\"{}\" is an invalid hex colour. It must be a valid 7 character hex string of the (ie: \"#112233\")." , hex)) ) } @@ -124,7 +124,7 @@ pub fn convert_hex_to_color(hex: &str) -> error::Result<Color> { } Err(error::BottomError::ConfigError(format!( - "invalid color hex: value {} is not of valid length. It must be a 7 character string of the form \"#112233\".", + "\"{}\" is an invalid hex colour. It must be a 7 character string of the form \"#112233\".", hex ))) } @@ -144,7 +144,7 @@ pub fn get_style_from_config(input_val: &str) -> error::Result<Style> { } } else { Err(error::BottomError::ConfigError(format!( - "invalid color: value {} is not valid.", + "value \"{}\" is not valid.", input_val ))) } @@ -161,7 +161,7 @@ pub fn get_colour_from_config(input_val: &str) -> error::Result<Color> { } } else { Err(error::BottomError::ConfigError(format!( - "invalid color: value {} is not valid.", + "value \"{}\" is not valid.", input_val ))) } @@ -175,7 +175,7 @@ fn convert_rgb_to_color(rgb_str: &str) -> error::Result<Color> { let rgb_list = rgb_str.split(',').collect::<Vec<&str>>(); if rgb_list.len() != 3 { return Err(error::BottomError::ConfigError(format!( - "invalid RGB color: value {} is not of valid length. It must be a comma separated value with 3 integers from 0 to 255 (ie: \"255, 0, 155\").", + "value \"{}\" is an invalid RGB colour. It must be a comma separated value with 3 integers from 0 to 255 (ie: \"255, 0, 155\").", rgb_str ))); } @@ -194,7 +194,7 @@ fn convert_rgb_to_color(rgb_str: &str) -> error::Result<Color> { Ok(Color::Rgb(rgb[0], rgb[1], rgb[2])) } else { Err(error::BottomError::ConfigError(format!( - "invalid RGB color: value {} contained invalid RGB values. It must be a comma separated value with 3 integers from 0 to 255 (ie: \"255, 0, 155\").", + "value \"{}\" contained invalid RGB values. It must be a comma separated value with 3 integers from 0 to 255 (ie: \"255, 0, 155\").", rgb_str ))) } @@ -211,9 +211,23 @@ fn convert_name_to_color(color_name: &str) -> error::Result<Color> { } Err(error::BottomError::ConfigError(format!( - "invalid named color: value {} is not a supported named colour. The following are supported strings: \ - Reset, Black, Red, Green, Yellow, Blue, Magenta, Cyan, Gray, DarkGray, LightRed, LightGreen, \ - LightYellow, LightBlue, LightMagenta, LightCyan, White", + "\"{}\" is an invalid named colour. + +The following are supported strings: ++--------+------------+--------------+ +| Reset | Magenta | LightYellow | ++--------+------------+--------------+ +| Black | Cyan | LightBlue | ++--------+------------+--------------+ +| Red | Gray | LightMagenta | ++--------+------------+--------------+ +| Green | DarkGray | LightCyan | ++--------+------------+--------------+ +| Yellow | LightRed | White | ++--------+------------+--------------+ +| Blue | LightGreen | | ++--------+------------+--------------+ + ", color_name ))) } diff --git a/src/clap.rs b/src/clap.rs index e4bed377..6b63ba42 100644 --- a/src/clap.rs +++ b/src/clap.rs @@ -239,7 +239,7 @@ For example, suppose we have a layout that looks like: Setting '--default_widget_type Temp' will make the Temperature widget selected by default. -Supported widget types: +Supported widget names: +--------------------------+ | cpu | +--------------------------+ diff --git a/src/lib.rs b/src/lib.rs index 4850d48c..207c2963 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -5,8 +5,10 @@ extern crate log; use std::{ boxed::Box, + fs, io::{stdout, Write}, panic::PanicInfo, + path::PathBuf, thread, time::{Duration, Instant}, }; @@ -18,6 +20,8 @@ use crossterm::{ terminal::{disable_raw_mode, LeaveAlternateScreen}, }; +use anyhow::Context; + use app::{ data_harvester::{self, processes::ProcessSorting}, layout_manager::{UsedWidgets, WidgetDirection}, @@ -164,15 +168,14 @@ pub fn handle_key_event_or_break( false } -pub fn create_config(flag_config_location: Option<&str>) -> error::Result<Config> { - use std::{ffi::OsString, fs}; - let config_path = if let Some(conf_loc) = flag_config_location { - Some(OsString::from(conf_loc)) +pub fn read_config(config_location: Option<&str>) -> error::Result<Option<PathBuf>> { + let config_path = if let Some(conf_loc) = config_location { + Some(PathBuf::from(conf_loc)) } else if cfg!(target_os = "windows") { if let Some(home_path) = dirs::config_dir() { let mut path = home_path; path.push(DEFAULT_CONFIG_FILE_PATH); - Some(path.into_os_string()) + Some(path) } else { None } @@ -182,13 +185,13 @@ pub fn create_config(flag_config_location: Option<&str>) -> error::Result<Config path.push(DEFAULT_CONFIG_FILE_PATH); if path.exists() { // If it already exists, use the old one. - Some(path.into_os_string()) + Some(path) } else { // If it does not, use the new one! if let Some(config_path) = dirs::config_dir() { let mut path = config_path; path.push(DEFAULT_CONFIG_FILE_PATH); - Some(path.into_os_string()) + Some(path) } else { None } @@ -197,9 +200,11 @@ pub fn create_config(flag_config_location: Option<&str>) -> error::Result<Config None }; - if let Some(config_path) = config_path { - let path = std::path::Path::new(&config_path); + Ok(config_path) +} +pub fn create_or_get_config(config_path: &Option<PathBuf>) -> error::Result<Config> { + if let Some(path) = config_path { if let Ok(config_string) = fs::read_to_string(path) { Ok(toml::from_str(config_string.as_str())?) } else { @@ -229,48 +234,76 @@ pub fn try_drawing( pub fn generate_config_colours( config: &Config, painter: &mut canvas::Painter, -) -> error::Result<()> { +) -> anyhow::Result<()> { if let Some(colours) = &config.colors { if let Some(border_color) = &colours.border_color { - painter.colours.set_border_colour(border_color)?; + painter + .colours + .set_border_colour(border_color) + .context("Update 'border_color' in your config file..")?; } if let Some(highlighted_border_color) = &colours.highlighted_border_color { painter .colours - .set_highlighted_border_colour(highlighted_border_color)?; + .set_highlighted_border_colour(highlighted_border_color) + .context("Update 'highlighted_border_color' in your config file..")?; } if let Some(text_color) = &colours.text_color { - painter.colours.set_text_colour(text_color)?; + painter + .colours + .set_text_colour(text_color) + .context("Update 'text_color' in your config file..")?; } if let Some(avg_cpu_color) = &colours.avg_cpu_color { - painter.colours.set_avg_cpu_colour(avg_cpu_color)?; + painter + .colours + .set_avg_cpu_colour(avg_cpu_color) + .context("Update 'avg_cpu_color' in your config file..")?; } if let Some(all_cpu_color) = &colours.all_cpu_color { - painter.colours.set_all_cpu_colour(all_cpu_color)?; + painter + .colours + .set_all_cpu_colour(all_cpu_color) + .context("Update 'all_cpu_color' in your config file..")?; } if let Some(cpu_core_colors) = &colours.cpu_core_colors { - painter.colours.set_cpu_colours(cpu_core_colors)?; + painter + .colours + .set_cpu_colours(cpu_core_colors) + .context("Update 'cpu_core_colors' in your config file..")?; } if let Some(ram_color) = &colours.ram_color { - painter.colours.set_ram_colour(ram_color)?; + painter + .colours + .set_ram_colour(ram_color) + .context("Update 'ram_color' in your config file..")?; } if let Some(swap_color) = &colours.swap_color { - painter.colours.set_swap_colour(swap_color)?; + painter + .colours + .set_swap_colour(swap_color) + .context("Update 'swap_color' in your config file..")?; } if let Some(rx_color) = &colours.rx_color { - painter.colours.set_rx_colour(rx_color)?; + painter + .colours + .set_rx_colour(rx_color) + .context("Update 'rx_color' in your config file..")?; } if let Some(tx_color) = &colours.tx_color { - painter.colours.set_tx_colour(tx_color)?; + painter + .colours + .set_tx_colour(tx_color) + .context("Update 'tx_color' in your config file..")?; } // if let Some(rx_total_color) = &colours.rx_total_color { @@ -284,33 +317,43 @@ pub fn generate_config_colours( if let Some(table_header_color) = &colours.table_header_color { painter .colours - .set_table_header_colour(table_header_color)?; + .set_table_header_colour(table_header_color) + .context("Update 'table_header_color' in your config file..")?; } if let Some(scroll_entry_text_color) = &colours.selected_text_color { painter .colours - .set_scroll_entry_text_color(scroll_entry_text_color)?; + .set_scroll_entry_text_color(scroll_entry_text_color) + .context("Update 'selected_text_color' in your config file..")?; } if let Some(scroll_entry_bg_color) = &colours.selected_bg_color { painter .colours - .set_scroll_entry_bg_color(scroll_entry_bg_color)?; + .set_scroll_entry_bg_color(scroll_entry_bg_color) + .context("Update 'selected_bg_color' in your config file..")?; } if let Some(widget_title_color) = &colours.widget_title_color { painter .colours - .set_widget_title_colour(widget_title_color)?; + .set_widget_title_colour(widget_title_color) + .context("Update 'widget_title_color' in your config file..")?; } if let Some(graph_color) = &colours.graph_color { - painter.colours.set_graph_colour(graph_color)?; + painter + .colours + .set_graph_colour(graph_color) + .context("Update 'graph_color' in your config file..")?; } if let Some(battery_colors) = &colours.battery_colors { - painter.colours.set_battery_colours(battery_colors)?; + painter + .colours + .set_battery_colors(battery_colors) + .context("Update 'battery_colors' in your config file.")?; } } diff --git a/src/options.rs b/src/options.rs index 41059f9c..c2e33993 100644 --- a/src/options.rs +++ b/src/options.rs @@ -12,6 +12,8 @@ use layout_options::*; pub mod layout_options; +use anyhow::{Context, Result}; + #[derive(Default, Deserialize)] pub struct Config { pub flags: Option<ConfigFlags>, @@ -70,10 +72,11 @@ pub struct ConfigColours { pub fn build_app( matches: &clap::ArgMatches<'static>, config: &Config, widget_layout: &BottomLayout, default_widget_id: u64, default_widget_type_option: &Option<BottomWidgetType>, -) -> error::Result<App> { +) -> Result<App> { use BottomWidgetType::*; let autohide_time = get_autohide_time(&matches, &config); - let default_time_value = get_default_time_value(&matches, &config)?; + let default_time_value = get_default_time_value(&matches, &config) + .context("Update 'default_time_value' in your config file.")?; let use_basic_mode = get_use_basic_mode(&matches, &config); // For processes @@ -213,15 +216,18 @@ pub fn build_app( }; let app_config_fields = AppConfigFields { - update_rate_in_milliseconds: get_update_rate_in_milliseconds(matches, config)?, - temperature_type: get_temperature(matches, config)?, + update_rate_in_milliseconds: get_update_rate_in_milliseconds(matches, config) + .context("Update 'rate' in your config file.")?, + temperature_type: get_temperature(matches, config) + .context("Update 'temperature_type' in your config file.")?, show_average_cpu: get_show_average_cpu(matches, config), use_dot: get_use_dot(matches, config), left_legend: get_use_left_legend(matches, config), use_current_cpu_total: get_use_current_cpu_total(matches, config), use_basic_mode, default_time_value, - time_interval: get_time_interval(matches, config)?, + time_interval: get_time_interval(matches, config) + .context("Update 'time_delta' in your config file.")?, hide_time: get_hide_time(matches, config), autohide_time, use_old_network_legend: get_use_old_network_legend(matches, config), @@ -316,7 +322,7 @@ pub fn get_widget_layout( ret_bottom_layout } else { return Err(error::BottomError::ConfigError( - "invalid layout config: please have at least one widget.".to_string(), + "please have at least one widget under the '[[row]]' section.".to_string(), )); } }; @@ -340,12 +346,12 @@ fn get_update_rate_in_milliseconds( }; if update_rate_in_milliseconds < 250 { - return Err(BottomError::InvalidArg( - "Please set your update rate to be at least 250 milliseconds.".to_string(), + return Err(BottomError::ConfigError( + "set your update rate to be at least 250 milliseconds.".to_string(), )); } else if update_rate_in_milliseconds as u128 > std::u64::MAX as u128 { - return Err(BottomError::InvalidArg( - "Please set your update rate to be at most unsigned INT_MAX.".to_string(), + return Err(BottomError::ConfigError( + "set your update rate to be at most unsigned INT_MAX.".to_string(), )); } @@ -368,11 +374,10 @@ fn get_temperature( "fahrenheit" | "f" => Ok(data_harvester::temperature::TemperatureType::Fahrenheit), "kelvin" | "k" => Ok(data_harvester::temperature::TemperatureType::Kelvin), "celsius" | "c" => Ok(data_harvester::temperature::TemperatureType::Celsius), - _ => Err(BottomError::ConfigError( - "invalid temperature type: please have the value be of the form \ - <kelvin|k|celsius|c|fahrenheit|f>" - .to_string(), - )), + _ => Err(BottomError::ConfigError(format!( + "\"{}\" is an invalid temperature type, use \"<kelvin|k|celsius|c|fahrenheit|f>\".", + temp_type + ))), }; } } @@ -455,12 +460,12 @@ fn get_default_time_value( }; if default_time < 30000 { - return Err(BottomError::InvalidArg( - "Please set your default value to be at least 30000 milliseconds.".to_string(), + return Err(BottomError::ConfigError( + "set your default value to be at least 30000 milliseconds.".to_string(), )); } else if default_time as u128 > STALE_MAX_MILLISECONDS as u128 { - return Err(BottomError::InvalidArg(format!( - "Please set your default value to be at most {} milliseconds.", + return Err(BottomError::ConfigError(format!( + "set your default value to be at most {} milliseconds.", STALE_MAX_MILLISECONDS ))); } @@ -482,12 +487,12 @@ fn get_time_interval(matches: &clap::ArgMatches<'static>, config: &Config) -> er }; if time_interval < 1000 { - return Err(BottomError::InvalidArg( - "Please set your time delta to be at least 1000 milliseconds.".to_string(), + return Err(BottomError::ConfigError( + "set your time delta to be at least 1000 milliseconds.".to_string(), )); } else if time_interval > STALE_MAX_MILLISECONDS as u128 { - return Err(BottomError::InvalidArg(format!( - "Please set your time delta to be at most {} milliseconds.", + return Err(BottomError::ConfigError(format!( + "set your time delta to be at most {} milliseconds.", STALE_MAX_MILLISECONDS ))); } @@ -601,8 +606,8 @@ fn get_default_widget_and_count( }; if widget_count > std::u64::MAX as u128 { - Err(BottomError::InvalidArg( - "Please set your widget count to be at most unsigned INT_MAX.".to_string(), + Err(BottomError::ConfigError( + "set your widget count to be at most unsigned INT_MAX.".to_string(), )) } else { Ok((widget_type, widget_count as u64)) diff --git a/src/utils/error.rs b/src/utils/error.rs index 4c607ebe..1cb17595 100644 --- a/src/utils/error.rs +++ b/src/utils/error.rs @@ -1,60 +1,39 @@ use std::{borrow::Cow, result}; +use thiserror::Error; /// A type alias for handling errors related to Bottom. pub type Result<T> = result::Result<T, BottomError>; /// An error that can occur while Bottom runs. -#[derive(Debug)] +#[derive(Debug, Error)] pub enum BottomError { /// An error when there is an IO exception. + #[error("IO exception, {0}")] InvalidIO(String), - /// An error when there is an invalid argument passed in. - InvalidArg(String), /// An error when the heim library encounters a problem. + #[error("Error caused by Heim, {0}")] InvalidHeim(String), /// An error when the Crossterm library encounters a problem. + #[error("Error caused by Crossterm, {0}")] CrosstermError(String), /// An error to represent generic errors. + #[error("Generic error, {0}")] GenericError(String), /// An error to represent errors with fern. + #[error("Fern error, {0}")] FernError(String), /// An error to represent errors with the config. + #[error("Configuration file error, {0}")] ConfigError(String), /// An error to represent errors with converting between data types. + #[error("Conversion error, {0}")] ConversionError(String), /// An error to represent errors with querying. + #[error("Query error, {0}")] QueryError(Cow<'static, str>), /// An error that just signifies something minor went wrong; no message. - MinorError(), -} - -impl std::fmt::Display for BottomError { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match *self { - BottomError::InvalidIO(ref message) => { - write!(f, "encountered an IO exception: {}", message) - } - BottomError::InvalidArg(ref message) => write!(f, "Invalid argument: {}", message), - BottomError::InvalidHeim(ref message) => write!( - f, - "invalid error during data collection due to heim: {}", - message - ), - BottomError::CrosstermError(ref message) => { - write!(f, "invalid error due to Crossterm: {}", message) - } - BottomError::GenericError(ref message) => write!(f, "{}", message), - BottomError::FernError(ref message) => write!(f, "Invalid fern error: {}", message), - BottomError::ConfigError(ref message) => { - write!(f, "invalid config file error: {}", message) - } - BottomError::ConversionError(ref message) => { - write!(f, "unable to convert: {}", message) - } - BottomError::QueryError(ref message) => write!(f, "{}", message), - BottomError::MinorError() => write!(f, "Minor error."), - } - } + #[error("Minor error.")] + MinorError, } impl From<std::io::Error> for BottomError { @@ -77,7 +56,7 @@ impl From<crossterm::ErrorKind> for BottomError { impl From<std::num::ParseIntError> for BottomError { fn from(err: std::num::ParseIntError) -> Self { - BottomError::InvalidArg(err.to_string()) + BottomError::ConfigError(err.to_string()) } } diff --git a/tests/arg_tests.rs b/tests/arg_tests.rs index 134d9615..f6dac340 100644 --- a/tests/arg_tests.rs +++ b/tests/arg_tests.rs @@ -18,7 +18,7 @@ fn test_small_rate() -> Result<(), Box<dyn std::error::Error>> { .assert() .failure() .stderr(predicate::str::contains( - "Please set your update rate to be at least 250 milliseconds.", + "set your update rate to be at least 250 milliseconds.", )); Ok(()) } @@ -31,7 +31,7 @@ fn test_large_default_time() -> Result<(), Box<dyn std::error::Error>> { .assert() .failure() .stderr(predicate::str::contains( - "Please set your default value to be at most", + "set your default value to be at most", )); Ok(()) } @@ -44,7 +44,7 @@ fn test_small_default_time() -> Result<(), Box<dyn std::error::Error>> { .assert() .failure() .stderr(predicate::str::contains( - "Please set your default value to be at least", + "set your default value to be at least", )); Ok(()) } @@ -57,7 +57,7 @@ fn test_large_delta_time() -> Result<(), Box<dyn std::error::Error>> { .assert() .failure() .stderr(predicate::str::contains( - "Please set your time delta to be at most", + "set your time delta to be at most", )); Ok(()) } @@ -70,7 +70,7 @@ fn test_small_delta_time() -> Result<(), Box<dyn std::error::Error>> { .assert() .failure() .stderr(predicate::str::contains( - "Please set your time delta to be at least", + "set your time delta to be at least", )); Ok(()) } @@ -83,7 +83,7 @@ fn test_large_rate() -> Result<(), Box<dyn std::error::Error>> { .assert() .failure() .stderr(predicate::str::contains( - "Please set your update rate to be at most unsigned INT_MAX.", + "set your update rate to be at most unsigned INT_MAX.", )); Ok(()) } @@ -136,7 +136,7 @@ fn test_invalid_default_widget_1() -> Result<(), Box<dyn std::error::Error>> { .arg("fake_widget") .assert() .failure() - .stderr(predicate::str::contains("invalid widget type")); + .stderr(predicate::str::contains("invalid widget name")); Ok(()) } @@ -151,7 +151,7 @@ fn test_invalid_default_widget_2() -> Result<(), Box<dyn std::error::Error>> { .assert() .failure() .stderr(predicate::str::contains( - "Please set your widget count to be at most unsigned INT_MAX", + "set your widget count to be at most unsigned INT_MAX", )); Ok(()) diff --git a/tests/invalid_config_tests.rs b/tests/invalid_config_tests.rs index 34b8e06e..cda6beb5 100644 --- a/tests/invalid_config_tests.rs +++ b/tests/invalid_config_tests.rs @@ -26,7 +26,7 @@ fn test_empty_layout() -> Result<(), Box<dyn std::error::Error>> { .arg("./tests/invalid_configs/empty_layout.toml") .assert() .failure() - .stderr(predicate::str::contains("invalid layout config")); + .stderr(predicate::str::contains("at least one widget")); Ok(()) } @@ -37,7 +37,7 @@ fn test_invalid_layout_widget_type() -> Result<(), Box<dyn std::error::Error>> { .arg("./tests/invalid_configs/invalid_layout_widget_type.toml") .assert() .failure() - .stderr(predicate::str::contains("invalid widget type")); + .stderr(predicate::str::contains("invalid widget name")); Ok(()) } @@ -62,7 +62,7 @@ fn test_invalid_colour_hex() -> Result<(), Box<dyn std::error::Error>> { .arg("./tests/invalid_configs/invalid_colour_hex.toml") .assert() .failure() - .stderr(predicate::str::contains("invalid color hex")); + .stderr(predicate::str::contains("invalid hex colour")); Ok(()) } @@ -74,7 +74,7 @@ fn test_invalid_colour_hex_2() -> Result<(), Box<dyn std::error::Error>> { .arg("./tests/invalid_configs/invalid_colour_hex_2.toml") .assert() .failure() - .stderr(predicate::str::contains("invalid color hex")); + .stderr(predicate::str::contains("invalid hex colour")); Ok(()) } @@ -87,7 +87,7 @@ fn test_invalid_colour_hex_3() -> Result<(), Box<dyn std::error::Error>> { .arg("./tests/invalid_configs/invalid_colour_hex_3.toml") .assert() .failure() - .stderr(predicate::str::contains("invalid color hex")); + .stderr(predicate::str::contains("invalid hex colour")); Ok(()) } @@ -98,7 +98,7 @@ fn test_invalid_colour_name() -> Result<(), Box<dyn std::error::Error>> { .arg("./tests/invalid_configs/invalid_colour_name.toml") .assert() .failure() - .stderr(predicate::str::contains("invalid named color")); + .stderr(predicate::str::contains("invalid named colour")); Ok(()) } @@ -109,7 +109,7 @@ fn test_invalid_colour_rgb() -> Result<(), Box<dyn std::error::Error>> { .arg("./tests/invalid_configs/invalid_colour_rgb.toml") .assert() .failure() - .stderr(predicate::str::contains("invalid RGB color")); + .stderr(predicate::str::contains("invalid RGB")); Ok(()) } @@ -120,7 +120,7 @@ fn test_invalid_colour_rgb_2() -> Result<(), Box<dyn std::error::Error>> { .arg("./tests/invalid_configs/invalid_colour_rgb_2.toml") .assert() .failure() - .stderr(predicate::str::contains("invalid RGB color")); + .stderr(predicate::str::contains("invalid RGB")); Ok(()) } @@ -131,6 +131,19 @@ fn test_invalid_colour_string() -> Result<(), Box<dyn std::error::Error>> { .arg("./tests/invalid_configs/invalid_colour_string.toml") .assert() .failure() - .stderr(predicate::str::contains("invalid named color")); + .stderr(predicate::str::contains("invalid named colour")); + Ok(()) +} + +#[test] +fn test_empty_battery() -> Result<(), Box<dyn std::error::Error>> { + Command::new(get_binary_location()) + .arg("-C") + .arg("./tests/invalid_configs/empty_battery.toml") + .assert() + .failure() + .stderr(predicate::str::contains( + "battery colour list must have at least one colour.", + )); Ok(()) } diff --git a/tests/invalid_configs/empty_battery.toml b/tests/invalid_configs/empty_battery.toml new file mode 100644 index 00000000..651f7b58 --- /dev/null +++ b/tests/invalid_configs/empty_battery.toml @@ -0,0 +1,2 @@ +[colors] +battery_colors=[] \ No newline at end of file