summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorClement Tsang <34804052+ClementTsang@users.noreply.github.com>2024-01-01 22:39:10 +0000
committerGitHub <noreply@github.com>2024-01-01 17:39:10 -0500
commitc242b4aff31696e24ebbae49cfb5e761d0c6a9c0 (patch)
treefb454f6d82b661d287376fcdd31c5723ec4ae29f
parent228da99489f876780feaf829f82933e07a37c084 (diff)
refactor: move around some configuration code again (#1371)
-rw-r--r--src/bin/main.rs2
-rw-r--r--src/canvas/styling.rs4
-rw-r--r--src/constants.rs2
-rw-r--r--src/lib.rs2
-rw-r--r--src/options.rs1146
-rw-r--r--src/options/config.rs1148
-rw-r--r--src/widgets/cpu_graph.rs2
-rw-r--r--tests/layout_management_tests.rs2
8 files changed, 1155 insertions, 1153 deletions
diff --git a/src/bin/main.rs b/src/bin/main.rs
index 4d588a94..0ee3289f 100644
--- a/src/bin/main.rs
+++ b/src/bin/main.rs
@@ -20,7 +20,7 @@ use bottom::{
create_or_get_config,
data_conversion::*,
handle_key_event_or_break, handle_mouse_event,
- options::config::{get_color_scheme, get_widget_layout, init_app},
+ options::{get_color_scheme, get_widget_layout, init_app},
panic_hook, read_config, try_drawing, update_data, BottomEvent,
};
use crossterm::{
diff --git a/src/canvas/styling.rs b/src/canvas/styling.rs
index c317e8a0..dec1800f 100644
--- a/src/canvas/styling.rs
+++ b/src/canvas/styling.rs
@@ -5,7 +5,7 @@ use tui::style::{Color, Style};
use super::ColourScheme;
use crate::{
constants::*,
- options::config::{Config, ConfigColours},
+ options::{Config, ConfigColours},
utils::error,
};
mod colour_utils;
@@ -238,7 +238,7 @@ mod test {
use tui::style::{Color, Style};
use super::{CanvasStyling, ColourScheme};
- use crate::Config;
+ use crate::options::Config;
#[test]
fn default_selected_colour_works() {
diff --git a/src/constants.rs b/src/constants.rs
index 6d312aa4..d736e928 100644
--- a/src/constants.rs
+++ b/src/constants.rs
@@ -1,6 +1,6 @@
use tui::widgets::Borders;
-use crate::options::config::ConfigColours;
+use crate::options::ConfigColours;
// Default widget ID
pub const DEFAULT_WIDGET_ID: u64 = 56709;
diff --git a/src/lib.rs b/src/lib.rs
index 380dbf7d..80b9af75 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -60,7 +60,7 @@ use crossterm::{
};
use data_conversion::*;
pub use options::args;
-use options::config::Config;
+use options::Config;
use utils::error;
#[allow(unused_imports)]
pub use utils::logging::*;
diff --git a/src/options.rs b/src/options.rs
index 459d4320..86d44e75 100644
--- a/src/options.rs
+++ b/src/options.rs
@@ -1,2 +1,1148 @@
+//! How to handle config files and arguments.
+
+// TODO: Break this apart or do something a bit smarter.
+
pub mod args;
pub mod config;
+
+use std::{
+ borrow::Cow,
+ convert::TryInto,
+ str::FromStr,
+ time::{Duration, Instant},
+};
+
+use anyhow::{Context, Result};
+use clap::ArgMatches;
+use hashbrown::{HashMap, HashSet};
+use indexmap::IndexSet;
+use regex::Regex;
+use serde::{Deserialize, Serialize};
+#[cfg(feature = "battery")]
+use starship_battery::Manager;
+
+use self::config::{cpu::CpuConfig, layout::Row, process_columns::ProcessConfig};
+use crate::{
+ app::{filter::Filter, layout_manager::*, *},
+ canvas::{styling::CanvasStyling, ColourScheme},
+ constants::*,
+ data_collection::temperature::TemperatureType,
+ utils::{
+ data_units::DataUnit,
+ error::{self, BottomError},
+ },
+ widgets::*,
+};
+
+#[derive(Clone, Debug, Default, Deserialize)]
+pub struct Config {
+ pub flags: Option<ConfigFlags>,
+ pub colors: Option<ConfigColours>,
+ pub row: Option<Vec<Row>>,
+ pub disk_filter: Option<IgnoreList>,
+ pub mount_filter: Option<IgnoreList>,
+ pub temp_filter: Option<IgnoreList>,
+ pub net_filter: Option<IgnoreList>,
+ pub processes: Option<ProcessConfig>,
+ pub cpu: Option<CpuConfig>,
+}
+
+#[derive(Clone, Debug, Deserialize, Serialize)]
+#[serde(untagged)]
+enum StringOrNum {
+ String(String),
+ Num(u64),
+}
+
+impl From<String> for StringOrNum {
+ fn from(value: String) -> Self {
+ StringOrNum::String(value)
+ }
+}
+
+impl From<u64> for StringOrNum {
+ fn from(value: u64) -> Self {
+ StringOrNum::Num(value)
+ }
+}
+
+#[derive(Clone, Debug, Default, Deserialize, Serialize)]
+pub struct ConfigFlags {
+ hide_avg_cpu: Option<bool>,
+ dot_marker: Option<bool>,
+ temperature_type: Option<String>,
+ rate: Option<StringOrNum>,
+ left_legend: Option<bool>,
+ current_usage: Option<bool>,
+ unnormalized_cpu: Option<bool>,
+ group_processes: Option<bool>,
+ case_sensitive: Option<bool>,
+ whole_word: Option<bool>,
+ regex: Option<bool>,
+ basic: Option<bool>,
+ default_time_value: Option<StringOrNum>,
+ time_delta: Option<StringOrNum>,
+ autohide_time: Option<bool>,
+ hide_time: Option<bool>,
+ default_widget_type: Option<String>,
+ default_widget_count: Option<u64>,
+ expanded_on_startup: Option<bool>,
+ use_old_network_legend: Option<bool>,
+ hide_table_gap: Option<bool>,
+ battery: Option<bool>,
+ disable_click: Option<bool>,
+ no_write: Option<bool>,
+ /// For built-in colour palettes.
+ color: Option<String>,
+ mem_as_value: Option<bool>,
+ tree: Option<bool>,
+ show_table_scroll_position: Option<bool>,
+ process_command: Option<bool>,
+ disable_advanced_kill: Option<bool>,
+ network_use_bytes: Option<bool>,
+ network_use_log: Option<bool>,
+ network_use_binary_prefix: Option<bool>,
+ enable_gpu: Option<bool>,
+ enable_cache_memory: Option<bool>,
+ retention: Option<StringOrNum>,
+}
+
+#[derive(Clone, Debug, Default, Deserialize, Serialize)]
+pub struct ConfigColours {
+ pub table_header_color: Option<Cow<'static, str>>,
+ pub all_cpu_color: Option<Cow<'static, str>>,
+ pub avg_cpu_color: Option<Cow<'static, str>>,
+ pub cpu_core_colors: Option<Vec<Cow<'static, str>>>,
+ pub ram_color: Option<Cow<'static, str>>,
+ #[cfg(not(target_os = "windows"))]
+ pub cache_color: Option<Cow<'static, str>>,
+ pub swap_color: Option<Cow<'static, str>>,
+ pub arc_color: Option<Cow<'static, str>>,
+ pub gpu_core_colors: Option<Vec<Cow<'static, str>>>,
+ pub rx_color: Option<Cow<'static, str>>,
+ pub tx_color: Option<Cow<'static, str>>,
+ pub rx_total_color: Option<Cow<'static, str>>, // These only affect basic mode.
+ pub tx_total_color: Option<Cow<'static, str>>, // These only affect basic mode.
+ pub border_color: Option<Cow<'static, str>>,
+ pub highlighted_border_color: Option<Cow<'static, str>>,
+ pub disabled_text_color: Option<Cow<'static, str>>,
+ pub text_color: Option<Cow<'static, str>>,
+ pub selected_text_color: Option<Cow<'static, str>>,
+ pub selected_bg_color: Option<Cow<'static, str>>,
+ pub widget_title_color: Option<Cow<'static, str>>,
+ pub graph_color: Option<Cow<'static, str>>,
+ pub high_battery_color: Option<Cow<'static, str>>,
+ pub medium_battery_color: Option<Cow<'static, str>>,
+ pub low_battery_color: Option<Cow<'static, str>>,
+}
+
+impl ConfigColours {
+ /// Returns `true` if there is a [`ConfigColours`] that is empty or there isn't one at all.
+ pub fn is_empty(&self) -> bool {
+ if let Ok(serialized_string) = toml_edit::ser::to_string(self) {
+ return serialized_string.is_empty();
+ }
+
+ true
+ }
+}
+
+/// Workaround as per https://github.com/serde-rs/serde/issues/1030
+fn default_as_true() -> bool {
+ true
+}
+
+#[derive(Clone, Debug, Default, Deserialize, Serialize)]
+pub struct IgnoreList {
+ #[serde(default = "default_as_true")]
+ // TODO: Deprecate and/or rename, current name sounds awful.
+ // Maybe to something like "deny_entries"? Currently it defaults to a denylist anyways, so maybe "allow_entries"?
+ pub is_list_ignored: bool,
+ pub list: Vec<String>,
+ #[serde(default = "bool::default")]
+ pub regex: bool,
+ #[serde(default = "bool::default")]
+ pub case_sensitive: bool,
+ #[serde(default = "bool::default")]
+ pub whole_word: bool,
+}
+
+macro_rules! is_flag_enabled {
+ ($flag_name:ident, $matches:expr, $config:expr) => {
+ if $matches.get_flag(stringify!($flag_name)) {
+ true
+ } else if let Some(flags) = &$config.flags {
+ flags.$flag_name.unwrap_or(false)
+ } else {
+ false
+ }
+ };
+
+ ($cmd_flag:literal, $cfg_flag:ident, $matches:expr, $config:expr) => {
+ if $matches.get_flag($cmd_flag) {
+ true
+ } else if let Some(flags) = &$config.flags {
+ flags.$cfg_flag.unwrap_or(false)
+ } else {
+ false
+ }
+ };
+}
+
+pub fn init_app(
+ matches: ArgMatches, config: Config, widget_layout: &BottomLayout, default_widget_id: u64,
+ default_widget_type_option: &Option<BottomWidgetType>, styling: &CanvasStyling,
+) -> Result<App> {
+ use BottomWidgetType::*;
+
+ // Since everything takes a reference, but we want to take ownership here to drop matches/config later...
+ let matches = &matches;
+ let config = &config;
+
+ let retention_ms =
+ get_retention(matches, config).context("Update `retention` in your config file.")?;
+ let autohide_time = is_flag_enabled!(autohide_time, matches, config);
+ let default_time_value = get_default_time_value(matches, config, retention_ms)
+ .context("Update 'default_time_value' in your config file.")?;
+
+ let use_basic_mode = is_flag_enabled!(basic, matches, config);
+ let expanded_upon_startup = is_flag_enabled!(expanded_on_startup, matches, config);
+
+ // For processes
+ let is_grouped = is_flag_enabled!(group_processes, matches, config);
+ let is_case_sensitive = is_flag_enabled!(case_sensitive, matches, config);
+ let is_match_whole_word = is_flag_enabled!(whole_word, matches, config);
+ let is_use_regex = is_flag_enabled!(regex, matches, config);
+
+ let mut widget_map = HashMap::new();
+ let mut cpu_state_map: HashMap<u64, CpuWidgetState> = HashMap::new();
+ let mut mem_state_map: HashMap<u64, MemWidgetState> = HashMap::new();
+ let mut net_state_map: HashMap<u64, NetWidgetState> = HashMap::new();
+ let mut proc_state_map: HashMap<u64, ProcWidgetState> = HashMap::new();
+ let mut temp_state_map: HashMap<u64, TempWidgetState> = HashMap::new();
+ let mut disk_state_map: HashMap<u64, DiskTableWidget> = HashMap::new();
+ let mut battery_state_map: HashMap<u64, BatteryWidgetState> = HashMap::new();
+
+ let autohide_timer = if autohide_time {
+ Some(Instant::now())
+ } else {
+ None
+ };
+
+ let mut initial_widget_id: u64 = default_widget_id;
+ let mut initial_widget_type = Proc;
+ let is_custom_layout = config.row.is_some();
+ let mut used_widget_set = HashSet::new();
+
+ let show_memory_as_values = is_flag_enabled!(mem_as_value, matches, config);
+ let is_default_tree = is_flag_enabled!(tree, matches, config);
+ let is_default_command = is_flag_enabled!(process_command, matches, config);
+ let is_advanced_kill = !(is_flag_enabled!(disable_advanced_kill, matches, config));
+
+ let network_unit_type = get_network_unit_type(matches, config);
+ let network_scale_type = get_network_scale_type(matches, config);
+ let network_use_binary_prefix = is_flag_enabled!(network_use_binary_prefix, matches, config);
+
+ let proc_columns: Option<IndexSet<ProcWidgetColumn>> = {
+ let columns = config
+ .processes
+ .as_ref()
+ .and_then(|cfg| cfg.columns.clone());
+
+ match columns {
+ Some(columns) => {
+ if columns.is_empty() {
+ None
+ } else {
+ Some(IndexSet::from_iter(columns))
+ }
+ }
+ None => None,
+ }
+ };
+
+ let app_config_fields = AppConfigFields {
+ update_rate: get_update_rate(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: is_flag_enabled!(dot_marker, matches, config),
+ left_legend: is_flag_enabled!(left_legend, matches, config),
+ use_current_cpu_total: is_flag_enabled!(current_usage, matches, config),
+ unnormalized_cpu: is_flag_enabled!(unnormalized_cpu, matches, config),
+ use_basic_mode,
+ default_time_value,
+ time_interval: get_time_interval(matches, config, retention_ms)
+ .context("Update 'time_delta' in your config file.")?,
+ hide_time: is_flag_enabled!(hide_time, matches, config),
+ autohide_time,
+ use_old_network_legend: is_flag_enabled!(use_old_network_legend, matches, config),
+ table_gap: u16::from(!(is_flag_enabled!(hide_table_gap, matches, config))),
+ disable_click: is_flag_enabled!(disable_click, matches, config),
+ enable_gpu: get_enable_gpu(matches, config),
+ enable_cache_memory: get_enable_cache_memory(matches, config),
+ show_table_scroll_position: is_flag_enabled!(show_table_scroll_position, matches, config),
+ is_advanced_kill,
+ network_scale_type,
+ network_unit_type,
+ network_use_binary_prefix,
+ retention_ms,
+ };
+
+ let table_config = ProcTableConfig {
+ is_case_sensitive,
+ is_match_whole_word,
+ is_use_regex,
+ show_memory_as_values,
+ is_command: is_default_command,
+ };
+
+ for row in &widget_layout.rows {
+ for col in &row.children {
+ for col_row in &col.children {
+ for widget in &col_row.children {
+ widget_map.insert(widget.widget_id, widget.clone());
+ if let Some(default_widget_type) = &default_widget_type_option {
+ if !is_custom_layout || use_basic_mode {
+ match widget.widget_type {
+ BasicCpu => {
+ if let Cpu = *default_widget_type {
+ initial_widget_id = widget.widget_id;
+ initial_widget_type = Cpu;
+ }
+ }
+ BasicMem => {
+ if let Mem = *default_widget_type {
+ initial_widget_id = widget.widget_id;
+ initial_widget_type = Cpu;
+ }
+ }
+ BasicNet => {
+ if let Net = *default_widget_type {
+ initial_widget_id = widget.widget_id;
+ initial_widget_type = Cpu;
+ }
+ }
+ _ => {
+ if *default_widget_type == widget.widget_type {
+ initial_widget_id = widget.widget_id;
+ initial_widget_type = widget.widget_type.clone();
+ }
+ }
+ }
+ }
+ }
+
+ used_widget_set.insert(widget.widget_type.clone());
+
+ match widget.widget_type {
+ Cpu => {
+ cpu_state_map.insert(
+ widget.widget_id,
+ CpuWidgetState::new(
+ &app_config_fields,
+ config
+ .cpu
+ .as_ref()
+ .map(|cfg| cfg.default)
+ .unwrap_or_default(),
+ default_time_value,
+ autohide_timer,
+ styling,
+ ),
+ );
+ }
+ Mem => {
+ mem_state_map.insert(
+ widget.widget_id,
+ MemWidgetState::init(default_time_value, autohide_timer),
+ );
+ }
+ Net => {
+ net_state_map.insert(
+ widget.widget_id,
+ NetWidgetState::init(default_time_value, autohide_timer),
+ );
+ }
+ Proc => {
+ let mode = if is_grouped {
+ ProcWidgetMode::Grouped
+ } else if is_default_tree {
+ ProcWidgetMode::Tree {
+ collapsed_pids: Default::default(),
+ }
+ } else {
+ ProcWidgetMode::Normal
+ };
+
+ proc_state_map.insert(
+ widget.widget_id,
+ ProcWidgetState::new(
+ &app_config_fields,
+ mode,
+ table_config,
+ styling,
+ &proc_columns,
+ ),
+ );
+ }
+ Disk => {
+ disk_state_map.insert(
+ widget.widget_id,
+ DiskTableWidget::new(&app_config_fields, styling),
+ );
+ }
+ Temp => {
+ temp_state_map.insert(
+ widget.widget_id,
+ TempWidgetState::new(&app_config_fields, styling),
+ );
+ }
+ Battery => {
+ battery_state_map
+ .insert(widget.widget_id, BatteryWidgetState::default());
+ }
+ _ => {}
+ }
+ }
+ }
+ }
+ }
+
+ let basic_table_widget_state = if use_basic_mode {
+ Some(match initial_widget_type {
+ Proc | Disk | Temp => BasicTableWidgetState {
+ currently_displayed_widget_type: initial_widget_type,
+ currently_displayed_widget_id: initial_widget_id,
+ widget_id: 100,
+ left_tlc: None,
+ left_brc: None,
+ right_tlc: None,
+ right_brc: None,
+ },
+ _ => BasicTableWidgetState {
+ currently_displayed_widget_type: Proc,
+ currently_displayed_widget_id: DEFAULT_WIDGET_ID,
+ widget_id: 100,
+ left_tlc: None,
+ left_brc: None,
+ right_tlc: None,
+ right_brc: None,
+ },
+ })
+ } else {
+ None
+ };
+
+ let use_mem = used_widget_set.get(&Mem).is_some() || used_widget_set.get(&BasicMem).is_some();
+ let used_widgets = UsedWidgets {
+ use_cpu: used_widget_set.get(&Cpu).is_some() || used_widget_set.get(&BasicCpu).is_some(),
+ use_mem,
+ use_cache: use_mem && get_enable_cache_memory(matches, config),
+ use_gpu: get_enable_gpu(matches, config),
+ use_net: used_widget_set.get(&Net).is_some() || used_widget_set.get(&BasicNet).is_some(),
+ use_proc: used_widget_set.get(&Proc).is_some(),
+ use_disk: used_widget_set.get(&Disk).is_some(),
+ use_temp: used_widget_set.get(&Temp).is_some(),
+ use_battery: used_widget_set.get(&Battery).is_some(),
+ };
+
+ let disk_filter =
+ get_ignore_list(&config.disk_filter).context("Update 'disk_filter' in your config file")?;
+ let mount_filter = get_ignore_list(&config.mount_filter)
+ .context("Update 'mount_filter' in your config file")?;
+ let temp_filter =
+ get_ignore_list(&config.temp_filter).context("Update 'temp_filter' in your config file")?;
+ let net_filter =
+ get_ignore_list(&config.net_filter).context("Update 'net_filter' in your config file")?;
+
+ let states = AppWidgetStates {
+ cpu_state: CpuState::init(cpu_state_map),
+ mem_state: MemState::init(mem_state_map),
+ net_state: NetState::init(net_state_map),
+ proc_state: ProcState::init(proc_state_map),
+ temp_state: TempState::init(temp_state_map),
+ disk_state: DiskState::init(disk_state_map),
+ battery_state: BatteryState::init(battery_state_map),
+ basic_table_widget_state,
+ };
+
+ let current_widget = widget_map.get(&initial_widget_id).unwrap().clone();
+ let filters = DataFilters {
+ disk_filter,
+ mount_filter,
+ temp_filter,
+ net_filter,
+ };
+ let is_expanded = expanded_upon_startup && !use_basic_mode;
+
+ Ok(App::new(
+ app_config_fields,
+ states,
+ widget_map,
+ current_widget,
+ used_widgets,
+ filters,
+ is_expanded,
+ ))
+}
+
+pub fn get_widget_layout(
+ matches: &ArgMatches, config: &Config,
+) -> error::Result<(BottomLayout, u64, Option<BottomWidgetType>)> {
+ let left_legend = is_flag_enabled!(left_legend, matches, config);
+
+ let (default_widget_type, mut default_widget_count) =
+ get_default_widget_and_count(matches, config)?;
+ let mut default_widget_id = 1;
+
+ let bottom_layout = if is_flag_enabled!(basic, matches, config) {
+ default_widget_id = DEFAULT_WIDGET_ID;
+
+ BottomLayout::init_basic_default(get_use_battery(matches, config))
+ } else {
+ let ref_row: Vec<Row>; // Required to handle reference
+ let rows = match &config.row {
+ Some(r) => r,
+ None => {
+ // This cannot (like it really shouldn't) fail!
+ ref_row = toml_edit::de::from_str::<Config>(if get_use_battery(matches, config) {
+ DEFAULT_BATTERY_LAYOUT
+ } else {
+ DEFAULT_LAYOUT
+ })?
+ .row
+ .unwrap();
+ &ref_row
+ }
+ };
+
+ let mut iter_id = 0; // A lazy way of forcing unique IDs *shrugs*
+ let mut total_height_ratio = 0;
+
+ let mut ret_bottom_layout = BottomLayout {
+ rows: rows
+ .iter()
+ .map(|row| {
+ row.convert_row_to_bottom_row(
+ &mut iter_id,
+ &mut total_height_ratio,
+ &mut default_widget_id,
+ &default_widget_type,
+ &mut default_widget_count,
+ left_legend,
+ )
+ })
+ .collect::<error::Result<Vec<_>>>()?,
+ total_row_height_ratio: total_height_ratio,
+ };
+
+ // Confirm that we have at least ONE widget left - if not, error out!
+ if iter_id > 0 {
+ ret_bottom_layout.get_movement_mappings();
+ // debug!("Bottom layout: {ret_bottom_layout:#?}");
+
+ ret_bottom_layout
+ } else {
+ return Err(BottomError::ConfigError(
+ "please have at least one widget under the '[[row]]' section.".to_string(),
+ ));
+ }
+ };
+
+ Ok((bottom_layout, default_widget_id, default_widget_type))
+}
+
+fn get_update_rate(matches: &ArgMatches, config: &Config) -> error::Result<u64> {
+ let update_rate = if let Some(update_rate) = matches.get_one::<String>("rate") {
+ try_parse_ms(update_rate)?
+ } else if let Some(flags) = &config.flags {
+ if let Some(rate) = &flags.rate {
+ match rate {
+ StringOrNum::String(s) => try_parse_ms(s)?,
+ StringOrNum::Num(n) => *n,
+ }
+ } else {
+ DEFAULT_REFRESH_RATE_IN_MILLISECONDS
+ }
+ } else {
+ DEFAULT_REFRESH_RATE_IN_MILLISECONDS
+ };
+
+ if update_rate < 250 {
+ return Err(BottomError::ConfigError(
+ "set your update rate to be at least 250 ms.".to_string(),
+ ));
+ }
+
+ Ok(update_rate)
+}
+
+fn get_temperature(matches: &ArgMatches, config: &Config) -> error::Result<TemperatureType> {
+ if matches.get_flag("fahrenheit") {
+ return Ok(TemperatureType::Fahrenheit);
+ } else if matches.get_flag("kelvin") {
+ return Ok(TemperatureType::Kelvin);
+ } else if matches.get_flag("celsius") {
+ return Ok(TemperatureType::Celsius);
+ } else if let Some(flags) = &config.flags {
+ if let Some(temp_type) = &flags.temperature_type {
+ // Give lowest priority to config.
+ return match temp_type.as_str() {
+ "fahrenheit" | "f" => Ok(TemperatureType::Fahrenheit),
+ "kelvin" | "k" => Ok(TemperatureType::Kelvin),
+ "celsius" | "c" => Ok(TemperatureType::Celsius),
+ _ => Err(BottomError::ConfigError(format!(
+ "\"{temp_type}\" is an invalid temperature type, use \"<kelvin|k|celsius|c|fahrenheit|f>\"."
+ ))),
+ };
+ }
+ }
+ Ok(TemperatureType::Celsius)
+}
+
+/// Yes, this function gets whether to show average CPU (true) or not (false)
+fn get_show_average_cpu(matches: &ArgMatches, config: &Config) -> bool {
+ if matches.get_flag("hide_avg_cpu") {
+ return false;
+ } else if let Some(flags) = &config.flags {
+ if let Some(avg_cpu) = flags.hide_avg_cpu {
+ return !avg_cpu;
+ }
+ }
+
+ true
+}
+
+fn try_parse_ms(s: &str) -> error::Result<u64> {
+ if let Ok(val) = humantime::parse_duration(s) {
+ Ok(val.as_millis().try_into()?)
+ } else if let Ok(val) = s.parse::<u64>() {
+ Ok(val)
+ } else {
+ Err(BottomError::ConfigError(
+ "could not parse as a valid 64-bit unsigned integer or a human time".to_string(),
+ ))
+ }
+}
+
+fn get_default_time_value(
+ matches: &ArgMatches, config: &Config, retention_ms: u64,
+) -> error::Result<u64> {
+ let default_time =
+ if let Some(default_time_value) = matches.get_one::<String>("default_time_value") {
+ try_parse_ms(default_time_value)?
+ } else if let Some(flags) = &config.flags {
+ if let Some(default_time_value) = &flags.default_time_value {
+ match default_time_value {
+ StringOrNum::String(s) => try_parse_ms(s)?,
+ StringOrNum::Num(n) => *n,
+ }
+ } else {
+ DEFAULT_TIME_MILLISECONDS
+ }
+ } else {
+ DEFAULT_TIME_MILLISECONDS
+ };
+
+ if default_time < 30000 {
+ return Err(BottomError::ConfigError(
+ "set your default value to be at least 30s.".to_string(),
+ ));
+ } else if default_time > retention_ms {
+ return Err(BottomError::ConfigError(format!(
+ "set your default value to be at most {}.",
+ humantime::Duration::from(Duration::from_millis(retention_ms))
+ )));
+ }
+
+ Ok(default_time)
+}
+
+fn get_time_interval(
+ matches: &ArgMatches, config: &Config, retention_ms: u64,
+) -> error::Result<u64> {
+ let time_interval = if let Some(time_interval) = matches.get_one::<String>("time_delta") {
+ try_parse_ms(time_interval)?
+ } else if let Some(flags) = &config.flags {
+ if let Some(time_interval) = &flags.time_delta {
+ match time_interval {
+ StringOrNum::String(s) => try_parse_ms(s)?,
+ StringOrNum::Num(n) => *n,
+ }
+ } else {
+ TIME_CHANGE_MILLISECONDS
+ }
+ } else {
+ TIME_CHANGE_MILLISECONDS
+ };
+
+ if time_interval < 1000 {
+ return Err(BottomError::ConfigError(
+ "set your time delta to be at least 1s.".to_string(),
+ ));
+ } else if time_interval > retention_ms {
+ return Err(BottomError::ConfigError(format!(
+ "set your time delta to be at most {}.",
+ humantime::Duration::from(Duration::from_millis(retention_ms))
+ )));
+ }
+
+ Ok(time_interval)
+}
+
+fn get_default_widget_and_count(
+ matches: &ArgMatches, config: &Config,
+) -> error::Result<(Option<BottomWidgetType>, u64)> {
+ let widget_type = if let Some(widget_type) = matches.get_one::<String>("default_widget_type") {
+ let parsed_widget = widget_type.parse::<BottomWidgetType>()?;
+ if let BottomWidgetType::Empty = parsed_widget {
+ None
+ } else {
+ Some(parsed_widget)
+ }
+ } else if let Some(flags) = &config.flags {
+ if let Some(widget_type) = &flags.default_widget_type {
+ let parsed_widget = widget_type.parse::<BottomWidgetType>()?;
+ if let BottomWidgetType::Empty = parsed_widget {
+ None
+ } else {
+ Some(parsed_widget)
+ }
+ } else {
+ None
+ }
+ } else {
+ None
+ };
+
+ let widget_count = if let Some(widget_count) = matches.get_one::<String>("default_widget_count")
+ {
+ Some(widget_count.parse::<u128>()?)
+ } else if let Some(flags) = &config.flags {
+ flags
+ .default_widget_count
+ .map(|widget_count| widget_count.into())
+ } else {
+ None
+ };
+
+ match (widget_type, widget_count) {
+ (Some(widget_type), Some(widget_count)) => {
+ let widget_count = widget_count.try_into().map_err(|_| BottomError::ConfigError(
+ "set your widget count to be at most unsigned INT_MAX.".to_string()
+ ))?;
+ Ok((Some(widget_type), widget_count))
+ }
+ (Some(widget_type), None) => Ok((Some(widget_type), 1)),
+ (None, Some(_widget_count)) => Err(BottomError::ConfigError(
+ "cannot set 'default_widget_count' by itself, it must be used with 'default_widget_type'.".to_string(),
+ )),
+ (None, None) => Ok((None, 1))
+ }
+}
+
+#[allow(unused_variables)]
+fn get_use_battery(matches: &ArgMatches, config: &Config) -> bool {
+ #[cfg(feature = "battery")]
+ {
+ if matches.get_flag("battery") {
+ return true;
+ } else if let Some(flags) = &config.flags {
+ if let Some(battery) = flags.battery {
+ return battery;
+ }
+ }
+
+ if let Ok(battery_manager) = Manager::new() {
+ if let Ok(batteries) = battery_manager.batteries() {
+ if batteries.count() == 0 {
+ return false;
+ }
+ }
+ }
+ }
+
+ false
+}
+
+#[allow(unused_variables)]
+fn get_enable_gpu(matches: &ArgMatches, config: &Config) -> bool {
+ #[cfg(feature = "gpu")]
+ {
+ if matches.get_flag("enable_gpu") {
+ return true;
+ } else if let Some(flags) = &config.flags {
+ if let Some(enable_gpu) = flags.enable_gpu {
+ return enable_gpu;
+ }
+ }
+ }
+
+ false
+}
+
+#[allow(unused_variables)]
+fn get_enable_cache_memory(matches: &ArgMatches, config: &Config) -> bool {
+ #[cfg(not(target_os = "windows"))]
+ {
+ if matches.get_flag("enable_cache_memory") {
+ return true;
+ } else if let Some(flags) = &config.flags {
+ if let Some(enable_cache_memory) = flags.enable_cache_memory {
+ return enable_cache_memory;
+ }
+ }
+ }
+
+ false
+}
+
+fn get_ignore_list(ignore_list: &Option<IgnoreList>) -> error::Result<Option<Filter>> {
+ if let Some(ignore_list) = ignore_list {
+ let list: Result<Vec<_>, _> = ignore_list
+ .list
+ .iter()
+ .map(|name| {
+ let escaped_string: String;
+ let res = format!(
+ "{}{}{}{}",
+ if ignore_list.whole_word { "^" } else { "" },
+ if ignore_list.case_sensitive {
+ ""
+ } else {
+ "(?i)"
+ },
+ if ignore_list.regex {
+ name
+ } else {
+ escaped_string = regex::escape(name);
+ &escaped_string
+ },
+ if ignore_list.whole_word { "$" } else { "" },
+ );
+
+ Regex::new(&res)
+ })
+ .collect();
+
+ Ok(Some(Filter {
+ list: list?,
+ is_list_ignored: ignore_list.is_list_ignored,
+ }))
+ } else {
+ Ok(None)
+ }
+}
+
+pub fn get_color_scheme(matches: &ArgMatches, config: &Config) -> error::Result<ColourScheme> {
+ if let Some(color) = matches.get_one::<String>("color") {
+ // Highest priority is always command line flags...
+ return ColourScheme::from_str(color);
+ } else if let Some(colors) = &config.colors {