refactor: Update error messages w/ anyhow and thiserror (#216)

Refactoring and updating of error messages + tests to be more useful.
This commit is contained in:
Clement Tsang 2020-08-31 23:59:33 -04:00 committed by GitHub
parent 5ed573157c
commit a4ddd649e1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 240 additions and 130 deletions

2
.cargo-husky/hooks/pre-push Executable file
View File

@ -0,0 +1,2 @@
echo "Running pre-push hook: cargo +nightly clippy -- -D clippy::all"
cargo +nightly clippy -- -D clippy::all

View File

@ -38,6 +38,7 @@
"curr", "curr",
"czvf", "czvf",
"fpath", "fpath",
"fract",
"gotop", "gotop",
"gtop", "gtop",
"haase", "haase",

28
Cargo.lock generated
View File

@ -27,6 +27,12 @@ dependencies = [
"winapi 0.3.9", "winapi 0.3.9",
] ]
[[package]]
name = "anyhow"
version = "1.0.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6b602bfe940d21c130f3895acd65221e8a61270debe89d628b9cb4e3ccb8569b"
[[package]] [[package]]
name = "arc-swap" name = "arc-swap"
version = "0.4.6" version = "0.4.6"
@ -131,6 +137,7 @@ dependencies = [
name = "bottom" name = "bottom"
version = "0.4.7" version = "0.4.7"
dependencies = [ dependencies = [
"anyhow",
"assert_cmd", "assert_cmd",
"backtrace", "backtrace",
"battery", "battery",
@ -151,6 +158,7 @@ dependencies = [
"regex", "regex",
"serde", "serde",
"sysinfo", "sysinfo",
"thiserror",
"toml", "toml",
"tui", "tui",
"typed-builder", "typed-builder",
@ -1273,6 +1281,26 @@ dependencies = [
"unicode-width", "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]] [[package]]
name = "thread_local" name = "thread_local"
version = "1.0.1" version = "1.0.1"

View File

@ -24,14 +24,17 @@ lto = "fat"
codegen-units = 1 codegen-units = 1
[dependencies] [dependencies]
anyhow = "1.0.32"
battery = "0.7.6" battery = "0.7.6"
crossterm = "0.17"
chrono = "0.4.15" chrono = "0.4.15"
crossterm = "0.17"
ctrlc = {version = "3.1", features = ["termination"]}
clap = "2.33" clap = "2.33"
dirs = "3.0.1" dirs = "3.0.1"
futures = "0.3.5" futures = "0.3.5"
heim = "0.0.10" heim = "0.0.10"
itertools = "0.9.0" itertools = "0.9.0"
libc = "0.2"
regex = "1.3" regex = "1.3"
sysinfo = "0.15.1" sysinfo = "0.15.1"
toml = "0.5.6" toml = "0.5.6"
@ -41,8 +44,7 @@ backtrace = "0.3"
serde = {version = "1.0", features = ["derive"] } serde = {version = "1.0", features = ["derive"] }
unicode-segmentation = "1.6.0" unicode-segmentation = "1.6.0"
unicode-width = "0.1" unicode-width = "0.1"
libc = "0.2" thiserror = "1.0.20"
ctrlc = {version = "3.1", features = ["termination"]}
tui = {version = "0.9.5", features = ["crossterm"], default-features = false } tui = {version = "0.9.5", features = ["crossterm"], default-features = false }
# For debugging only... # For debugging only...
@ -83,4 +85,4 @@ output = "bottom_x86_64_installer.msi"
[dev-dependencies.cargo-husky] [dev-dependencies.cargo-husky]
version = "1" version = "1"
default-features = false default-features = false
features = ["prepush-hook", "run-cargo-clippy"] features = ["user-hooks"]

View File

@ -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"` | | 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 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"` | | 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 #### Layout

View File

@ -253,11 +253,11 @@ fn read_proc<S: core::hash::BuildHasher>(
.splitn(2, '(') .splitn(2, '(')
.collect::<Vec<_>>() .collect::<Vec<_>>()
.last() .last()
.ok_or(BottomError::MinorError())? .ok_or(BottomError::MinorError)?
.rsplitn(2, ')') .rsplitn(2, ')')
.collect::<Vec<_>>() .collect::<Vec<_>>()
.last() .last()
.ok_or(BottomError::MinorError())? .ok_or(BottomError::MinorError)?
.to_string(); .to_string();
let command = { let command = {
let cmd = read_path_contents(&pid_stat.proc_cmdline_path)?; let cmd = read_path_contents(&pid_stat.proc_cmdline_path)?;
@ -271,7 +271,7 @@ fn read_proc<S: core::hash::BuildHasher>(
.split(')') .split(')')
.collect::<Vec<_>>() .collect::<Vec<_>>()
.last() .last()
.ok_or(BottomError::MinorError())? .ok_or(BottomError::MinorError)?
.split_whitespace() .split_whitespace()
.collect::<Vec<&str>>(); .collect::<Vec<&str>>();
let (process_state_char, process_state) = get_linux_process_state(&stat); let (process_state_char, process_state) = get_linux_process_state(&stat);

View File

@ -943,7 +943,25 @@ impl std::str::FromStr for BottomWidgetType {
"empty" => Ok(BottomWidgetType::Empty), "empty" => Ok(BottomWidgetType::Empty),
"battery" | "batt" => Ok(BottomWidgetType::Battery), "battery" | "batt" => Ok(BottomWidgetType::Battery),
_ => Err(BottomError::ConfigError(format!( _ => 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 s
))), ))),
} }

View File

@ -3,7 +3,7 @@
#[macro_use] #[macro_use]
extern crate log; extern crate log;
use bottom::{canvas, constants::*, data_conversion::*, options::*, utils::error, *}; use bottom::{canvas, constants::*, data_conversion::*, options::*, *};
use std::{ use std::{
boxed::Box, boxed::Box,
@ -17,6 +17,7 @@ use std::{
time::Duration, time::Duration,
}; };
use anyhow::{Context, Result};
use crossterm::{ use crossterm::{
event::EnableMouseCapture, event::EnableMouseCapture,
execute, execute,
@ -24,18 +25,22 @@ use crossterm::{
}; };
use tui::{backend::CrosstermBackend, Terminal}; use tui::{backend::CrosstermBackend, Terminal};
fn main() -> error::Result<()> { fn main() -> Result<()> {
#[cfg(debug_assertions)] #[cfg(debug_assertions)]
{ {
utils::logging::init_logger()?; utils::logging::init_logger()?;
} }
let matches = clap::get_matches(); 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 // Get widget layout separately
let (widget_layout, default_widget_id, default_widget_type_option) = 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 // Create "app" struct, which will control most of the program and store settings/state
let mut app = build_app( let mut app = build_app(

View File

@ -1,5 +1,4 @@
use tui::style::{Color, Style}; use tui::style::{Color, Style};
// use tui::style::Modifier;
use colour_utils::*; use colour_utils::*;
@ -175,11 +174,10 @@ impl CanvasColours {
Ok(()) 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() { if colours.is_empty() {
Err(error::BottomError::ConfigError( Err(error::BottomError::ConfigError(
"invalid colour config: battery colour list must have at least one colour!" "battery colour list must have at least one colour.".to_string(),
.to_string(),
)) ))
} else { } else {
let generated_colours: Result<Vec<_>, _> = colours let generated_colours: Result<Vec<_>, _> = colours

View File

@ -100,7 +100,7 @@ pub fn convert_hex_to_color(hex: &str) -> error::Result<Color> {
fn hex_err(hex: &str) -> error::Result<u8> { fn hex_err(hex: &str) -> error::Result<u8> {
Err( Err(
error::BottomError::ConfigError(format!( 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)) , hex))
) )
} }
@ -124,7 +124,7 @@ pub fn convert_hex_to_color(hex: &str) -> error::Result<Color> {
} }
Err(error::BottomError::ConfigError(format!( 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 hex
))) )))
} }
@ -144,7 +144,7 @@ pub fn get_style_from_config(input_val: &str) -> error::Result<Style> {
} }
} else { } else {
Err(error::BottomError::ConfigError(format!( Err(error::BottomError::ConfigError(format!(
"invalid color: value {} is not valid.", "value \"{}\" is not valid.",
input_val input_val
))) )))
} }
@ -161,7 +161,7 @@ pub fn get_colour_from_config(input_val: &str) -> error::Result<Color> {
} }
} else { } else {
Err(error::BottomError::ConfigError(format!( Err(error::BottomError::ConfigError(format!(
"invalid color: value {} is not valid.", "value \"{}\" is not valid.",
input_val 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>>(); let rgb_list = rgb_str.split(',').collect::<Vec<&str>>();
if rgb_list.len() != 3 { if rgb_list.len() != 3 {
return Err(error::BottomError::ConfigError(format!( 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 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])) Ok(Color::Rgb(rgb[0], rgb[1], rgb[2]))
} else { } else {
Err(error::BottomError::ConfigError(format!( 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 rgb_str
))) )))
} }
@ -211,9 +211,23 @@ fn convert_name_to_color(color_name: &str) -> error::Result<Color> {
} }
Err(error::BottomError::ConfigError(format!( Err(error::BottomError::ConfigError(format!(
"invalid named color: value {} is not a supported named colour. The following are supported strings: \ "\"{}\" is an invalid named colour.
Reset, Black, Red, Green, Yellow, Blue, Magenta, Cyan, Gray, DarkGray, LightRed, LightGreen, \
LightYellow, LightBlue, LightMagenta, LightCyan, White", The following are supported strings:
+--------+------------+--------------+
| Reset | Magenta | LightYellow |
+--------+------------+--------------+
| Black | Cyan | LightBlue |
+--------+------------+--------------+
| Red | Gray | LightMagenta |
+--------+------------+--------------+
| Green | DarkGray | LightCyan |
+--------+------------+--------------+
| Yellow | LightRed | White |
+--------+------------+--------------+
| Blue | LightGreen | |
+--------+------------+--------------+
",
color_name color_name
))) )))
} }

View File

@ -239,7 +239,7 @@ For example, suppose we have a layout that looks like:
Setting '--default_widget_type Temp' will make the Temperature Setting '--default_widget_type Temp' will make the Temperature
widget selected by default. widget selected by default.
Supported widget types: Supported widget names:
+--------------------------+ +--------------------------+
| cpu | | cpu |
+--------------------------+ +--------------------------+

View File

@ -5,8 +5,10 @@ extern crate log;
use std::{ use std::{
boxed::Box, boxed::Box,
fs,
io::{stdout, Write}, io::{stdout, Write},
panic::PanicInfo, panic::PanicInfo,
path::PathBuf,
thread, thread,
time::{Duration, Instant}, time::{Duration, Instant},
}; };
@ -18,6 +20,8 @@ use crossterm::{
terminal::{disable_raw_mode, LeaveAlternateScreen}, terminal::{disable_raw_mode, LeaveAlternateScreen},
}; };
use anyhow::Context;
use app::{ use app::{
data_harvester::{self, processes::ProcessSorting}, data_harvester::{self, processes::ProcessSorting},
layout_manager::{UsedWidgets, WidgetDirection}, layout_manager::{UsedWidgets, WidgetDirection},
@ -164,15 +168,14 @@ pub fn handle_key_event_or_break(
false false
} }
pub fn create_config(flag_config_location: Option<&str>) -> error::Result<Config> { pub fn read_config(config_location: Option<&str>) -> error::Result<Option<PathBuf>> {
use std::{ffi::OsString, fs}; let config_path = if let Some(conf_loc) = config_location {
let config_path = if let Some(conf_loc) = flag_config_location { Some(PathBuf::from(conf_loc))
Some(OsString::from(conf_loc))
} else if cfg!(target_os = "windows") { } else if cfg!(target_os = "windows") {
if let Some(home_path) = dirs::config_dir() { if let Some(home_path) = dirs::config_dir() {
let mut path = home_path; let mut path = home_path;
path.push(DEFAULT_CONFIG_FILE_PATH); path.push(DEFAULT_CONFIG_FILE_PATH);
Some(path.into_os_string()) Some(path)
} else { } else {
None None
} }
@ -182,13 +185,13 @@ pub fn create_config(flag_config_location: Option<&str>) -> error::Result<Config
path.push(DEFAULT_CONFIG_FILE_PATH); path.push(DEFAULT_CONFIG_FILE_PATH);
if path.exists() { if path.exists() {
// If it already exists, use the old one. // If it already exists, use the old one.
Some(path.into_os_string()) Some(path)
} else { } else {
// If it does not, use the new one! // If it does not, use the new one!
if let Some(config_path) = dirs::config_dir() { if let Some(config_path) = dirs::config_dir() {
let mut path = config_path; let mut path = config_path;
path.push(DEFAULT_CONFIG_FILE_PATH); path.push(DEFAULT_CONFIG_FILE_PATH);
Some(path.into_os_string()) Some(path)
} else { } else {
None None
} }
@ -197,9 +200,11 @@ pub fn create_config(flag_config_location: Option<&str>) -> error::Result<Config
None None
}; };
if let Some(config_path) = config_path { Ok(config_path)
let path = std::path::Path::new(&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) { if let Ok(config_string) = fs::read_to_string(path) {
Ok(toml::from_str(config_string.as_str())?) Ok(toml::from_str(config_string.as_str())?)
} else { } else {
@ -229,48 +234,76 @@ pub fn try_drawing(
pub fn generate_config_colours( pub fn generate_config_colours(
config: &Config, painter: &mut canvas::Painter, config: &Config, painter: &mut canvas::Painter,
) -> error::Result<()> { ) -> anyhow::Result<()> {
if let Some(colours) = &config.colors { if let Some(colours) = &config.colors {
if let Some(border_color) = &colours.border_color { 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 { if let Some(highlighted_border_color) = &colours.highlighted_border_color {
painter painter
.colours .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 { 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 { 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 { 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 { 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 { 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 { 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 { 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 { 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 { // 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 { if let Some(table_header_color) = &colours.table_header_color {
painter painter
.colours .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 { if let Some(scroll_entry_text_color) = &colours.selected_text_color {
painter painter
.colours .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 { if let Some(scroll_entry_bg_color) = &colours.selected_bg_color {
painter painter
.colours .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 { if let Some(widget_title_color) = &colours.widget_title_color {
painter painter
.colours .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 { 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 { 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.")?;
} }
} }

View File

@ -12,6 +12,8 @@ use layout_options::*;
pub mod layout_options; pub mod layout_options;
use anyhow::{Context, Result};
#[derive(Default, Deserialize)] #[derive(Default, Deserialize)]
pub struct Config { pub struct Config {
pub flags: Option<ConfigFlags>, pub flags: Option<ConfigFlags>,
@ -70,10 +72,11 @@ pub struct ConfigColours {
pub fn build_app( pub fn build_app(
matches: &clap::ArgMatches<'static>, config: &Config, widget_layout: &BottomLayout, matches: &clap::ArgMatches<'static>, config: &Config, widget_layout: &BottomLayout,
default_widget_id: u64, default_widget_type_option: &Option<BottomWidgetType>, default_widget_id: u64, default_widget_type_option: &Option<BottomWidgetType>,
) -> error::Result<App> { ) -> Result<App> {
use BottomWidgetType::*; use BottomWidgetType::*;
let autohide_time = get_autohide_time(&matches, &config); 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); let use_basic_mode = get_use_basic_mode(&matches, &config);
// For processes // For processes
@ -213,15 +216,18 @@ pub fn build_app(
}; };
let app_config_fields = AppConfigFields { let app_config_fields = AppConfigFields {
update_rate_in_milliseconds: get_update_rate_in_milliseconds(matches, config)?, update_rate_in_milliseconds: get_update_rate_in_milliseconds(matches, config)
temperature_type: get_temperature(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), show_average_cpu: get_show_average_cpu(matches, config),
use_dot: get_use_dot(matches, config), use_dot: get_use_dot(matches, config),
left_legend: get_use_left_legend(matches, config), left_legend: get_use_left_legend(matches, config),
use_current_cpu_total: get_use_current_cpu_total(matches, config), use_current_cpu_total: get_use_current_cpu_total(matches, config),
use_basic_mode, use_basic_mode,
default_time_value, 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), hide_time: get_hide_time(matches, config),
autohide_time, autohide_time,
use_old_network_legend: get_use_old_network_legend(matches, config), use_old_network_legend: get_use_old_network_legend(matches, config),
@ -316,7 +322,7 @@ pub fn get_widget_layout(
ret_bottom_layout ret_bottom_layout
} else { } else {
return Err(error::BottomError::ConfigError( 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 { if update_rate_in_milliseconds < 250 {
return Err(BottomError::InvalidArg( return Err(BottomError::ConfigError(
"Please set your update rate to be at least 250 milliseconds.".to_string(), "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 { } else if update_rate_in_milliseconds as u128 > std::u64::MAX as u128 {
return Err(BottomError::InvalidArg( return Err(BottomError::ConfigError(
"Please set your update rate to be at most unsigned INT_MAX.".to_string(), "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), "fahrenheit" | "f" => Ok(data_harvester::temperature::TemperatureType::Fahrenheit),
"kelvin" | "k" => Ok(data_harvester::temperature::TemperatureType::Kelvin), "kelvin" | "k" => Ok(data_harvester::temperature::TemperatureType::Kelvin),
"celsius" | "c" => Ok(data_harvester::temperature::TemperatureType::Celsius), "celsius" | "c" => Ok(data_harvester::temperature::TemperatureType::Celsius),
_ => Err(BottomError::ConfigError( _ => Err(BottomError::ConfigError(format!(
"invalid temperature type: please have the value be of the form \ "\"{}\" is an invalid temperature type, use \"<kelvin|k|celsius|c|fahrenheit|f>\".",
<kelvin|k|celsius|c|fahrenheit|f>" temp_type
.to_string(), ))),
)),
}; };
} }
} }
@ -455,12 +460,12 @@ fn get_default_time_value(
}; };
if default_time < 30000 { if default_time < 30000 {
return Err(BottomError::InvalidArg( return Err(BottomError::ConfigError(
"Please set your default value to be at least 30000 milliseconds.".to_string(), "set your default value to be at least 30000 milliseconds.".to_string(),
)); ));
} else if default_time as u128 > STALE_MAX_MILLISECONDS as u128 { } else if default_time as u128 > STALE_MAX_MILLISECONDS as u128 {
return Err(BottomError::InvalidArg(format!( return Err(BottomError::ConfigError(format!(
"Please set your default value to be at most {} milliseconds.", "set your default value to be at most {} milliseconds.",
STALE_MAX_MILLISECONDS STALE_MAX_MILLISECONDS
))); )));
} }
@ -482,12 +487,12 @@ fn get_time_interval(matches: &clap::ArgMatches<'static>, config: &Config) -> er
}; };
if time_interval < 1000 { if time_interval < 1000 {
return Err(BottomError::InvalidArg( return Err(BottomError::ConfigError(
"Please set your time delta to be at least 1000 milliseconds.".to_string(), "set your time delta to be at least 1000 milliseconds.".to_string(),
)); ));
} else if time_interval > STALE_MAX_MILLISECONDS as u128 { } else if time_interval > STALE_MAX_MILLISECONDS as u128 {
return Err(BottomError::InvalidArg(format!( return Err(BottomError::ConfigError(format!(
"Please set your time delta to be at most {} milliseconds.", "set your time delta to be at most {} milliseconds.",
STALE_MAX_MILLISECONDS STALE_MAX_MILLISECONDS
))); )));
} }
@ -601,8 +606,8 @@ fn get_default_widget_and_count(
}; };
if widget_count > std::u64::MAX as u128 { if widget_count > std::u64::MAX as u128 {
Err(BottomError::InvalidArg( Err(BottomError::ConfigError(
"Please set your widget count to be at most unsigned INT_MAX.".to_string(), "set your widget count to be at most unsigned INT_MAX.".to_string(),
)) ))
} else { } else {
Ok((widget_type, widget_count as u64)) Ok((widget_type, widget_count as u64))

View File

@ -1,60 +1,39 @@
use std::{borrow::Cow, result}; use std::{borrow::Cow, result};
use thiserror::Error;
/// A type alias for handling errors related to Bottom. /// A type alias for handling errors related to Bottom.
pub type Result<T> = result::Result<T, BottomError>; pub type Result<T> = result::Result<T, BottomError>;
/// An error that can occur while Bottom runs. /// An error that can occur while Bottom runs.
#[derive(Debug)] #[derive(Debug, Error)]
pub enum BottomError { pub enum BottomError {
/// An error when there is an IO exception. /// An error when there is an IO exception.
#[error("IO exception, {0}")]
InvalidIO(String), InvalidIO(String),
/// An error when there is an invalid argument passed in.
InvalidArg(String),
/// An error when the heim library encounters a problem. /// An error when the heim library encounters a problem.
#[error("Error caused by Heim, {0}")]
InvalidHeim(String), InvalidHeim(String),
/// An error when the Crossterm library encounters a problem. /// An error when the Crossterm library encounters a problem.
#[error("Error caused by Crossterm, {0}")]
CrosstermError(String), CrosstermError(String),
/// An error to represent generic errors. /// An error to represent generic errors.
#[error("Generic error, {0}")]
GenericError(String), GenericError(String),
/// An error to represent errors with fern. /// An error to represent errors with fern.
#[error("Fern error, {0}")]
FernError(String), FernError(String),
/// An error to represent errors with the config. /// An error to represent errors with the config.
#[error("Configuration file error, {0}")]
ConfigError(String), ConfigError(String),
/// An error to represent errors with converting between data types. /// An error to represent errors with converting between data types.
#[error("Conversion error, {0}")]
ConversionError(String), ConversionError(String),
/// An error to represent errors with querying. /// An error to represent errors with querying.
#[error("Query error, {0}")]
QueryError(Cow<'static, str>), QueryError(Cow<'static, str>),
/// An error that just signifies something minor went wrong; no message. /// An error that just signifies something minor went wrong; no message.
MinorError(), #[error("Minor error.")]
} 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."),
}
}
} }
impl From<std::io::Error> for BottomError { impl From<std::io::Error> for BottomError {
@ -77,7 +56,7 @@ impl From<crossterm::ErrorKind> for BottomError {
impl From<std::num::ParseIntError> for BottomError { impl From<std::num::ParseIntError> for BottomError {
fn from(err: std::num::ParseIntError) -> Self { fn from(err: std::num::ParseIntError) -> Self {
BottomError::InvalidArg(err.to_string()) BottomError::ConfigError(err.to_string())
} }
} }

View File

@ -18,7 +18,7 @@ fn test_small_rate() -> Result<(), Box<dyn std::error::Error>> {
.assert() .assert()
.failure() .failure()
.stderr(predicate::str::contains( .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(()) Ok(())
} }
@ -31,7 +31,7 @@ fn test_large_default_time() -> Result<(), Box<dyn std::error::Error>> {
.assert() .assert()
.failure() .failure()
.stderr(predicate::str::contains( .stderr(predicate::str::contains(
"Please set your default value to be at most", "set your default value to be at most",
)); ));
Ok(()) Ok(())
} }
@ -44,7 +44,7 @@ fn test_small_default_time() -> Result<(), Box<dyn std::error::Error>> {
.assert() .assert()
.failure() .failure()
.stderr(predicate::str::contains( .stderr(predicate::str::contains(
"Please set your default value to be at least", "set your default value to be at least",
)); ));
Ok(()) Ok(())
} }
@ -57,7 +57,7 @@ fn test_large_delta_time() -> Result<(), Box<dyn std::error::Error>> {
.assert() .assert()
.failure() .failure()
.stderr(predicate::str::contains( .stderr(predicate::str::contains(
"Please set your time delta to be at most", "set your time delta to be at most",
)); ));
Ok(()) Ok(())
} }
@ -70,7 +70,7 @@ fn test_small_delta_time() -> Result<(), Box<dyn std::error::Error>> {
.assert() .assert()
.failure() .failure()
.stderr(predicate::str::contains( .stderr(predicate::str::contains(
"Please set your time delta to be at least", "set your time delta to be at least",
)); ));
Ok(()) Ok(())
} }
@ -83,7 +83,7 @@ fn test_large_rate() -> Result<(), Box<dyn std::error::Error>> {
.assert() .assert()
.failure() .failure()
.stderr(predicate::str::contains( .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(()) Ok(())
} }
@ -136,7 +136,7 @@ fn test_invalid_default_widget_1() -> Result<(), Box<dyn std::error::Error>> {
.arg("fake_widget") .arg("fake_widget")
.assert() .assert()
.failure() .failure()
.stderr(predicate::str::contains("invalid widget type")); .stderr(predicate::str::contains("invalid widget name"));
Ok(()) Ok(())
} }
@ -151,7 +151,7 @@ fn test_invalid_default_widget_2() -> Result<(), Box<dyn std::error::Error>> {
.assert() .assert()
.failure() .failure()
.stderr(predicate::str::contains( .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(()) Ok(())

View File

@ -26,7 +26,7 @@ fn test_empty_layout() -> Result<(), Box<dyn std::error::Error>> {
.arg("./tests/invalid_configs/empty_layout.toml") .arg("./tests/invalid_configs/empty_layout.toml")
.assert() .assert()
.failure() .failure()
.stderr(predicate::str::contains("invalid layout config")); .stderr(predicate::str::contains("at least one widget"));
Ok(()) 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") .arg("./tests/invalid_configs/invalid_layout_widget_type.toml")
.assert() .assert()
.failure() .failure()
.stderr(predicate::str::contains("invalid widget type")); .stderr(predicate::str::contains("invalid widget name"));
Ok(()) Ok(())
} }
@ -62,7 +62,7 @@ fn test_invalid_colour_hex() -> Result<(), Box<dyn std::error::Error>> {
.arg("./tests/invalid_configs/invalid_colour_hex.toml") .arg("./tests/invalid_configs/invalid_colour_hex.toml")
.assert() .assert()
.failure() .failure()
.stderr(predicate::str::contains("invalid color hex")); .stderr(predicate::str::contains("invalid hex colour"));
Ok(()) 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") .arg("./tests/invalid_configs/invalid_colour_hex_2.toml")
.assert() .assert()
.failure() .failure()
.stderr(predicate::str::contains("invalid color hex")); .stderr(predicate::str::contains("invalid hex colour"));
Ok(()) 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") .arg("./tests/invalid_configs/invalid_colour_hex_3.toml")
.assert() .assert()
.failure() .failure()
.stderr(predicate::str::contains("invalid color hex")); .stderr(predicate::str::contains("invalid hex colour"));
Ok(()) Ok(())
} }
@ -98,7 +98,7 @@ fn test_invalid_colour_name() -> Result<(), Box<dyn std::error::Error>> {
.arg("./tests/invalid_configs/invalid_colour_name.toml") .arg("./tests/invalid_configs/invalid_colour_name.toml")
.assert() .assert()
.failure() .failure()
.stderr(predicate::str::contains("invalid named color")); .stderr(predicate::str::contains("invalid named colour"));
Ok(()) Ok(())
} }
@ -109,7 +109,7 @@ fn test_invalid_colour_rgb() -> Result<(), Box<dyn std::error::Error>> {
.arg("./tests/invalid_configs/invalid_colour_rgb.toml") .arg("./tests/invalid_configs/invalid_colour_rgb.toml")
.assert() .assert()
.failure() .failure()
.stderr(predicate::str::contains("invalid RGB color")); .stderr(predicate::str::contains("invalid RGB"));
Ok(()) 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") .arg("./tests/invalid_configs/invalid_colour_rgb_2.toml")
.assert() .assert()
.failure() .failure()
.stderr(predicate::str::contains("invalid RGB color")); .stderr(predicate::str::contains("invalid RGB"));
Ok(()) Ok(())
} }
@ -131,6 +131,19 @@ fn test_invalid_colour_string() -> Result<(), Box<dyn std::error::Error>> {
.arg("./tests/invalid_configs/invalid_colour_string.toml") .arg("./tests/invalid_configs/invalid_colour_string.toml")
.assert() .assert()
.failure() .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(()) Ok(())
} }

View File

@ -0,0 +1,2 @@
[colors]
battery_colors=[]