mirror of
https://github.com/ClementTsang/bottom.git
synced 2024-07-14 15:00:44 +03:00
refactor: use struct for args instead of builder interface (#1472)
* start moving args * tmp * refactor config * port over ags * update changelog
This commit is contained in:
parent
71f6136a1e
commit
ee2e1fee1c
@ -21,9 +21,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
- [#1436](https://github.com/ClementTsang/bottom/pull/1436): Use actual "swap" value for Windows.
|
||||
- [#1441](https://github.com/ClementTsang/bottom/pull/1441): The following arguments have changed names:
|
||||
- `--left_legend/-l` is now `--cpu_left_legend`.
|
||||
- [#1441](https://github.com/ClementTsang/bottom/pull/1441): The following config arguments have changed names:
|
||||
- [#1441](https://github.com/ClementTsang/bottom/pull/1441): The following config fields have changed names:
|
||||
- `expanded_on_startup` is now `expanded`.
|
||||
- `left_legend` is now `cpu_left_legend`.
|
||||
- [#1472](https://github.com/ClementTsang/bottom/pull/1472): The following arguments have changed names:
|
||||
- `mem_as_value` is now `process_memory_as_value`.
|
||||
- [#1472](https://github.com/ClementTsang/bottom/pull/1472): The following config fields have changed names:
|
||||
- `mem_as_value` is now `process_memory_as_value`.
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
|
23
Cargo.lock
generated
23
Cargo.lock
generated
@ -258,6 +258,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "90bc066a67923782aa8515dbaea16946c5bcc5addbd668bb80af688e53e548a0"
|
||||
dependencies = [
|
||||
"clap_builder",
|
||||
"clap_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -302,6 +303,18 @@ dependencies = [
|
||||
"clap_complete",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap_derive"
|
||||
version = "4.5.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "528131438037fd55894f62d6e9f068b8f45ac57ffa77517819645d10aed04f64"
|
||||
dependencies = [
|
||||
"heck 0.5.0",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap_lex"
|
||||
version = "0.7.0"
|
||||
@ -514,7 +527,7 @@ version = "0.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5ffccbb6966c05b32ef8fbac435df276c4ae4d3dc55a8cd0eb9745e6c12f546a"
|
||||
dependencies = [
|
||||
"heck",
|
||||
"heck 0.4.1",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
@ -604,6 +617,12 @@ version = "0.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8"
|
||||
|
||||
[[package]]
|
||||
name = "heck"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
|
||||
|
||||
[[package]]
|
||||
name = "humantime"
|
||||
version = "2.1.0"
|
||||
@ -1333,7 +1352,7 @@ version = "0.26.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c6cf59daf282c0a494ba14fd21610a0325f9f90ec9d1231dea26bcb1d696c946"
|
||||
dependencies = [
|
||||
"heck",
|
||||
"heck 0.4.1",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"rustversion",
|
||||
|
@ -19,7 +19,9 @@ exclude = [
|
||||
"desktop/",
|
||||
"docs/",
|
||||
"sample_configs/",
|
||||
"schema",
|
||||
"scripts/",
|
||||
"wix/",
|
||||
".all-contributorsrc",
|
||||
".cirrus.yml",
|
||||
".gitignore",
|
||||
@ -29,6 +31,9 @@ exclude = [
|
||||
"codecov.yml",
|
||||
"CONTRIBUTING.md",
|
||||
"Cross.toml",
|
||||
"debug.log",
|
||||
"flamegraph.svg",
|
||||
"perf.data",
|
||||
"rustfmt.toml",
|
||||
]
|
||||
rust-version = "1.74.0" # The oldest version I've tested that should still build - note this is not an official MSRV!
|
||||
@ -75,7 +80,7 @@ default = ["deploy"]
|
||||
anyhow = "1.0.86"
|
||||
backtrace = "0.3.71"
|
||||
cfg-if = "1.0.0"
|
||||
clap = { version = "4.5.4", features = ["default", "cargo", "wrap_help"] }
|
||||
clap = { version = "4.5.4", features = ["default", "cargo", "wrap_help", "derive"] }
|
||||
concat-string = "1.0.1"
|
||||
crossterm = "0.27.0"
|
||||
ctrlc = { version = "3.4.4", features = ["termination"] }
|
||||
@ -135,7 +140,7 @@ predicates = "3.1.0"
|
||||
portable-pty = "0.8.1"
|
||||
|
||||
[build-dependencies]
|
||||
clap = { version = "4.5.4", features = ["default", "cargo", "wrap_help"] }
|
||||
clap = { version = "4.5.4", features = ["default", "cargo", "wrap_help", "derive"] }
|
||||
clap_complete = "4.5.2"
|
||||
clap_complete_nushell = "4.5.1"
|
||||
clap_complete_fig = "4.5.0"
|
||||
|
6
build.rs
6
build.rs
@ -7,12 +7,12 @@ use std::{
|
||||
path::{Path, PathBuf},
|
||||
};
|
||||
|
||||
use clap::Command;
|
||||
use clap::{Command, CommandFactory};
|
||||
use clap_complete::{generate_to, shells::Shell, Generator};
|
||||
use clap_complete_fig::Fig;
|
||||
use clap_complete_nushell::Nushell;
|
||||
|
||||
use crate::args::build_app;
|
||||
use crate::args::BottomArgs;
|
||||
|
||||
fn create_dir(dir: &Path) -> io::Result<()> {
|
||||
let res = fs::create_dir_all(dir);
|
||||
@ -48,7 +48,7 @@ fn btm_generate() -> io::Result<()> {
|
||||
create_dir(&manpage_out_dir)?;
|
||||
|
||||
// Generate completions
|
||||
let mut app = build_app();
|
||||
let mut app = BottomArgs::command();
|
||||
generate_completions(Shell::Bash, &mut app, &completion_out_dir)?;
|
||||
generate_completions(Shell::Zsh, &mut app, &completion_out_dir)?;
|
||||
generate_completions(Shell::Fish, &mut app, &completion_out_dir)?;
|
||||
|
@ -26,12 +26,12 @@ see information on these options by running `btm -h`, or run `btm --help` to dis
|
||||
## Process Options
|
||||
|
||||
| Option | Behaviour |
|
||||
| ------------------------- | -------------------------------------------------------------------------------------- |
|
||||
| --------------------------- | -------------------------------------------------------------------------------------- |
|
||||
| `-S, --case_sensitive` | Enables case sensitivity by default. |
|
||||
| `-u, --current_usage` | Calculates process CPU usage as a percentage of current usage rather than total usage. |
|
||||
| `--disable_advanced_kill` | Hides additional stopping options Unix-like systems. |
|
||||
| `-g, --group_processes` | Groups processes with the same name by default. |
|
||||
| `--mem_as_value` | Defaults to showing process memory usage by value. |
|
||||
| `--process_memory_as_value` | Defaults to showing process memory usage by value. |
|
||||
| `--process_command` | Shows the full command name instead of the process name by default. |
|
||||
| `-R, --regex` | Enables regex by default while searching. |
|
||||
| `-T, --tree` | Makes the process widget use tree mode by default. |
|
||||
|
@ -37,7 +37,7 @@ each time:
|
||||
| `disable_click` | Boolean | Disables mouse clicks. |
|
||||
| `color` | String (one of ["default", "default-light", "gruvbox", "gruvbox-light", "nord", "nord-light"]) | Use a color scheme, use --help for supported values. |
|
||||
| `enable_cache_memory` | Boolean | Enable cache and buffer memory stats (not available on Windows). |
|
||||
| `mem_as_value` | Boolean | Defaults to showing process memory usage by value. |
|
||||
| `process_memory_as_value` | Boolean | Defaults to showing process memory usage by value. |
|
||||
| `tree` | Boolean | Defaults to showing the process widget in tree mode. |
|
||||
| `show_table_scroll_position` | Boolean | Shows the scroll position tracker in table widgets. |
|
||||
| `process_command` | Boolean | Show processes as their commands by default. |
|
||||
|
@ -58,7 +58,7 @@
|
||||
# Built-in themes. Valid values are "default", "default-light", "gruvbox", "gruvbox-light", "nord", "nord-light"
|
||||
#color = "default"
|
||||
# Show memory values in the processes widget as values by default
|
||||
#mem_as_value = false
|
||||
#process_memory_as_value = false
|
||||
# Show tree mode by default in the processes widget.
|
||||
#tree = false
|
||||
# Shows an indicator in table widgets tracking where in the list you are.
|
||||
|
@ -184,7 +184,7 @@
|
||||
"description": "Built-in themes",
|
||||
"type": "string"
|
||||
},
|
||||
"mem_as_value": {
|
||||
"process_memory_as_value": {
|
||||
"default": false,
|
||||
"description": "Show memory values in the processes widget as values by default",
|
||||
"type": "boolean"
|
||||
|
@ -17,11 +17,10 @@ use bottom::{
|
||||
args,
|
||||
canvas::{self, styling::CanvasStyling},
|
||||
check_if_terminal, cleanup_terminal, create_collection_thread, create_input_thread,
|
||||
create_or_get_config,
|
||||
data_conversion::*,
|
||||
handle_key_event_or_break, handle_mouse_event,
|
||||
options::{get_color_scheme, get_widget_layout, init_app},
|
||||
panic_hook, read_config, try_drawing, update_data, BottomEvent,
|
||||
get_or_create_config, handle_key_event_or_break, handle_mouse_event,
|
||||
options::{get_color_scheme, init_app},
|
||||
panic_hook, try_drawing, update_data, BottomEvent,
|
||||
};
|
||||
use crossterm::{
|
||||
event::{EnableBracketedPaste, EnableMouseCapture},
|
||||
@ -37,7 +36,7 @@ use tui::{backend::CrosstermBackend, Terminal};
|
||||
fn main() -> Result<()> {
|
||||
// let _profiler = dhat::Profiler::new_heap();
|
||||
|
||||
let matches = args::get_matches();
|
||||
let args = args::get_args();
|
||||
|
||||
#[cfg(feature = "logging")]
|
||||
{
|
||||
@ -50,34 +49,17 @@ fn main() -> Result<()> {
|
||||
}
|
||||
|
||||
// Read from config file.
|
||||
let config = {
|
||||
let config_path = read_config(matches.get_one::<String>("config_location"))
|
||||
.context("Unable to access the given config file location.")?;
|
||||
|
||||
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)
|
||||
.context("Found an issue while trying to build the widget layout.")?;
|
||||
let config = get_or_create_config(args.general.config_location.as_deref())
|
||||
.context("Unable to parse or create the config file.")?;
|
||||
|
||||
// FIXME: Should move this into build app or config
|
||||
let styling = {
|
||||
let colour_scheme = get_color_scheme(&matches, &config)?;
|
||||
let colour_scheme = get_color_scheme(&args, &config)?;
|
||||
CanvasStyling::new(colour_scheme, &config)?
|
||||
};
|
||||
|
||||
// Create an "app" struct, which will control most of the program and store settings/state
|
||||
let mut app = init_app(
|
||||
matches,
|
||||
config,
|
||||
&widget_layout,
|
||||
default_widget_id,
|
||||
&default_widget_type_option,
|
||||
&styling,
|
||||
)?;
|
||||
let (mut app, widget_layout) = init_app(args, config, &styling)?;
|
||||
|
||||
// Create painter and set colours.
|
||||
let mut painter = canvas::Painter::init(widget_layout, styling)?;
|
||||
|
@ -5,7 +5,7 @@ use colour_utils::*;
|
||||
use tui::style::{Color, Style};
|
||||
|
||||
use super::ColourScheme;
|
||||
pub use crate::options::Config;
|
||||
pub use crate::options::ConfigV1;
|
||||
use crate::{constants::*, options::colours::ConfigColours, utils::error};
|
||||
|
||||
pub struct CanvasStyling {
|
||||
@ -124,7 +124,7 @@ macro_rules! try_set_colour_list {
|
||||
}
|
||||
|
||||
impl CanvasStyling {
|
||||
pub fn new(colour_scheme: ColourScheme, config: &Config) -> anyhow::Result<Self> {
|
||||
pub fn new(colour_scheme: ColourScheme, config: &ConfigV1) -> anyhow::Result<Self> {
|
||||
let mut canvas_colours = Self::default();
|
||||
|
||||
match colour_scheme {
|
||||
@ -236,7 +236,7 @@ mod test {
|
||||
use tui::style::{Color, Style};
|
||||
|
||||
use super::{CanvasStyling, ColourScheme};
|
||||
use crate::options::Config;
|
||||
use crate::options::ConfigV1;
|
||||
|
||||
#[test]
|
||||
fn default_selected_colour_works() {
|
||||
@ -282,7 +282,7 @@ mod test {
|
||||
|
||||
#[test]
|
||||
fn built_in_colour_schemes_work() {
|
||||
let config = Config::default();
|
||||
let config = ConfigV1::default();
|
||||
CanvasStyling::new(ColourScheme::Default, &config).unwrap();
|
||||
CanvasStyling::new(ColourScheme::DefaultLight, &config).unwrap();
|
||||
CanvasStyling::new(ColourScheme::Gruvbox, &config).unwrap();
|
||||
|
@ -576,7 +576,7 @@ pub const CONFIG_TEXT: &str = r#"# This is a default config file for bottom. Al
|
||||
# Built-in themes. Valid values are "default", "default-light", "gruvbox", "gruvbox-light", "nord", "nord-light"
|
||||
#color = "default"
|
||||
# Show memory values in the processes widget as values by default
|
||||
#mem_as_value = false
|
||||
#process_memory_as_value = false
|
||||
# Show tree mode by default in the processes widget.
|
||||
#tree = false
|
||||
# Shows an indicator in table widgets tracking where in the list you are.
|
||||
|
@ -13,6 +13,8 @@ cfg_if::cfg_if! {
|
||||
}
|
||||
}
|
||||
|
||||
use std::str::FromStr;
|
||||
|
||||
use crate::app::filter::Filter;
|
||||
|
||||
#[derive(Default, Debug, Clone)]
|
||||
@ -29,6 +31,21 @@ pub enum TemperatureType {
|
||||
Fahrenheit,
|
||||
}
|
||||
|
||||
impl FromStr for TemperatureType {
|
||||
type Err = String;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
match s {
|
||||
"fahrenheit" | "f" => Ok(TemperatureType::Fahrenheit),
|
||||
"kelvin" | "k" => Ok(TemperatureType::Kelvin),
|
||||
"celsius" | "c" => Ok(TemperatureType::Celsius),
|
||||
_ => Err(format!(
|
||||
"\"{s}\" is an invalid temperature type, use \"<kelvin|k|celsius|c|fahrenheit|f>\"."
|
||||
)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TemperatureType {
|
||||
/// Given a temperature in Celsius, covert it if necessary for a different unit.
|
||||
pub fn convert_temp_unit(&self, temp_celsius: f32) -> f32 {
|
||||
|
26
src/lib.rs
26
src/lib.rs
@ -35,7 +35,7 @@ use std::{
|
||||
fs,
|
||||
io::{stderr, stdout, Write},
|
||||
panic::PanicInfo,
|
||||
path::PathBuf,
|
||||
path::{Path, PathBuf},
|
||||
sync::{
|
||||
mpsc::{Receiver, Sender},
|
||||
Arc, Condvar, Mutex,
|
||||
@ -61,7 +61,7 @@ use crossterm::{
|
||||
};
|
||||
use data_conversion::*;
|
||||
pub use options::args;
|
||||
use options::Config;
|
||||
use options::ConfigV1;
|
||||
use utils::error;
|
||||
#[allow(unused_imports)]
|
||||
pub use utils::logging::*;
|
||||
@ -202,9 +202,9 @@ pub fn handle_key_event_or_break(
|
||||
false
|
||||
}
|
||||
|
||||
pub fn read_config(config_location: Option<&String>) -> error::Result<Option<PathBuf>> {
|
||||
let config_path = if let Some(conf_loc) = config_location {
|
||||
Some(PathBuf::from(conf_loc.as_str()))
|
||||
pub fn get_config_path(override_config_path: Option<&Path>) -> Option<PathBuf> {
|
||||
if let Some(conf_loc) = override_config_path {
|
||||
Some(conf_loc.to_path_buf())
|
||||
} else if cfg!(target_os = "windows") {
|
||||
if let Some(home_path) = dirs::config_dir() {
|
||||
let mut path = home_path;
|
||||
@ -232,13 +232,13 @@ pub fn read_config(config_location: Option<&String>) -> error::Result<Option<Pat
|
||||
}
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
Ok(config_path)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn create_or_get_config(config_path: &Option<PathBuf>) -> error::Result<Config> {
|
||||
if let Some(path) = config_path {
|
||||
pub fn get_or_create_config(override_config_path: Option<&Path>) -> error::Result<ConfigV1> {
|
||||
let config_path = get_config_path(override_config_path);
|
||||
|
||||
if let Some(path) = &config_path {
|
||||
if let Ok(config_string) = fs::read_to_string(path) {
|
||||
Ok(toml_edit::de::from_str(config_string.as_str())?)
|
||||
} else {
|
||||
@ -247,11 +247,11 @@ pub fn create_or_get_config(config_path: &Option<PathBuf>) -> error::Result<Conf
|
||||
}
|
||||
|
||||
fs::File::create(path)?.write_all(CONFIG_TEXT.as_bytes())?;
|
||||
Ok(Config::default())
|
||||
Ok(ConfigV1::default())
|
||||
}
|
||||
} else {
|
||||
// Don't write...
|
||||
Ok(Config::default())
|
||||
// If we somehow don't have any config path, then just assume the default config but don't write to any file.
|
||||
Ok(ConfigV1::default())
|
||||
}
|
||||
}
|
||||
|
||||
|
363
src/options.rs
363
src/options.rs
@ -13,16 +13,18 @@ use std::{
|
||||
};
|
||||
|
||||
use anyhow::{Context, Result};
|
||||
use clap::ArgMatches;
|
||||
pub use colours::ConfigColours;
|
||||
pub use config::Config;
|
||||
pub use config::ConfigV1;
|
||||
use hashbrown::{HashMap, HashSet};
|
||||
use indexmap::IndexSet;
|
||||
use regex::Regex;
|
||||
#[cfg(feature = "battery")]
|
||||
use starship_battery::Manager;
|
||||
|
||||
use self::config::{layout::Row, IgnoreList, StringOrNum};
|
||||
use self::{
|
||||
args::BottomArgs,
|
||||
config::{layout::Row, IgnoreList, StringOrNum},
|
||||
};
|
||||
use crate::{
|
||||
app::{filter::Filter, layout_manager::*, *},
|
||||
canvas::{components::time_chart::LegendPosition, styling::CanvasStyling, ColourScheme},
|
||||
@ -36,8 +38,8 @@ use crate::{
|
||||
};
|
||||
|
||||
macro_rules! is_flag_enabled {
|
||||
($flag_name:ident, $matches:expr, $config:expr) => {
|
||||
if $matches.get_flag(stringify!($flag_name)) {
|
||||
($flag_name:ident, $arg:expr, $config:expr) => {
|
||||
if $arg.$flag_name {
|
||||
true
|
||||
} else if let Some(flags) = &$config.flags {
|
||||
flags.$flag_name.unwrap_or(false)
|
||||
@ -58,29 +60,34 @@ macro_rules! is_flag_enabled {
|
||||
}
|
||||
|
||||
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> {
|
||||
args: BottomArgs, config: ConfigV1, styling: &CanvasStyling,
|
||||
) -> Result<(App, BottomLayout)> {
|
||||
use BottomWidgetType::*;
|
||||
|
||||
// Since everything takes a reference, but we want to take ownership here to drop matches/config later...
|
||||
let matches = &matches;
|
||||
let args = &args;
|
||||
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 (widget_layout, default_widget_id, default_widget_type_option) =
|
||||
get_widget_layout(args, config)
|
||||
.context("Found an issue while trying to build the widget layout.")?;
|
||||
|
||||
let use_basic_mode = is_flag_enabled!(basic, matches, config);
|
||||
let expanded = is_flag_enabled!(expanded, matches, config);
|
||||
let retention_ms = get_retention(args, config)?;
|
||||
let autohide_time = is_flag_enabled!(autohide_time, args.general, config);
|
||||
let default_time_value = get_default_time_value(args, config, retention_ms)?;
|
||||
|
||||
let use_basic_mode = is_flag_enabled!(basic, args.general, config);
|
||||
let expanded = is_flag_enabled!(expanded, args.general, 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 is_grouped = is_flag_enabled!(group_processes, args.process, config);
|
||||
let is_case_sensitive = is_flag_enabled!(case_sensitive, args.process, config);
|
||||
let is_match_whole_word = is_flag_enabled!(whole_word, args.process, config);
|
||||
let is_use_regex = is_flag_enabled!(regex, args.process, config);
|
||||
let is_default_tree = is_flag_enabled!(tree, args.process, config);
|
||||
let is_default_command = is_flag_enabled!(process_command, args.process, config);
|
||||
let is_advanced_kill = !(is_flag_enabled!(disable_advanced_kill, args.process, config));
|
||||
let process_memory_as_value = is_flag_enabled!(process_memory_as_value, args.process, config);
|
||||
|
||||
let mut widget_map = HashMap::new();
|
||||
let mut cpu_state_map: HashMap<u64, CpuWidgetState> = HashMap::new();
|
||||
@ -102,14 +109,10 @@ pub fn init_app(
|
||||
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 network_unit_type = get_network_unit_type(args, config);
|
||||
let network_scale_type = get_network_scale_type(args, config);
|
||||
let network_use_binary_prefix =
|
||||
is_flag_enabled!(network_use_binary_prefix, args.network, config);
|
||||
|
||||
let proc_columns: Option<IndexSet<ProcWidgetColumn>> = {
|
||||
let columns = config.processes.as_ref().map(|cfg| cfg.columns.clone());
|
||||
@ -126,32 +129,34 @@ pub fn init_app(
|
||||
}
|
||||
};
|
||||
|
||||
let network_legend_position = get_network_legend(matches, config)?;
|
||||
let memory_legend_position = get_memory_legend(matches, config)?;
|
||||
let network_legend_position = get_network_legend_position(args, config)?;
|
||||
let memory_legend_position = get_memory_legend_position(args, config)?;
|
||||
|
||||
// TODO: Can probably just reuse the options struct.
|
||||
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)
|
||||
update_rate: get_update_rate(args, config)?,
|
||||
temperature_type: get_temperature(args, 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),
|
||||
cpu_left_legend: is_flag_enabled!(cpu_left_legend, matches, config),
|
||||
use_current_cpu_total: is_flag_enabled!(current_usage, matches, config),
|
||||
unnormalized_cpu: is_flag_enabled!(unnormalized_cpu, matches, config),
|
||||
show_average_cpu: get_show_average_cpu(args, config),
|
||||
use_dot: is_flag_enabled!(dot_marker, args.general, config),
|
||||
cpu_left_legend: is_flag_enabled!(cpu_left_legend, args.cpu, config),
|
||||
use_current_cpu_total: is_flag_enabled!(current_usage, args.process, config),
|
||||
unnormalized_cpu: is_flag_enabled!(unnormalized_cpu, args.process, 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),
|
||||
time_interval: get_time_interval(args, config, retention_ms)?,
|
||||
hide_time: is_flag_enabled!(hide_time, args.general, 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),
|
||||
use_old_network_legend: is_flag_enabled!(use_old_network_legend, args.network, config),
|
||||
table_gap: u16::from(!(is_flag_enabled!(hide_table_gap, args.general, config))),
|
||||
disable_click: is_flag_enabled!(disable_click, args.general, config),
|
||||
enable_gpu: get_enable_gpu(args, config),
|
||||
enable_cache_memory: get_enable_cache_memory(args, config),
|
||||
show_table_scroll_position: is_flag_enabled!(
|
||||
show_table_scroll_position,
|
||||
args.general,
|
||||
config
|
||||
),
|
||||
is_advanced_kill,
|
||||
memory_legend_position,
|
||||
network_legend_position,
|
||||
@ -165,7 +170,7 @@ pub fn init_app(
|
||||
is_case_sensitive,
|
||||
is_match_whole_word,
|
||||
is_use_regex,
|
||||
show_memory_as_values,
|
||||
show_memory_as_values: process_memory_as_value,
|
||||
is_command: is_default_command,
|
||||
};
|
||||
|
||||
@ -310,8 +315,8 @@ pub fn init_app(
|
||||
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_cache: use_mem && get_enable_cache_memory(args, config),
|
||||
use_gpu: get_enable_gpu(args, 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(),
|
||||
@ -348,7 +353,8 @@ pub fn init_app(
|
||||
};
|
||||
let is_expanded = expanded && !use_basic_mode;
|
||||
|
||||
Ok(App::new(
|
||||
Ok((
|
||||
App::new(
|
||||
app_config_fields,
|
||||
states,
|
||||
widget_map,
|
||||
@ -356,29 +362,31 @@ pub fn init_app(
|
||||
used_widgets,
|
||||
filters,
|
||||
is_expanded,
|
||||
),
|
||||
widget_layout,
|
||||
))
|
||||
}
|
||||
|
||||
pub fn get_widget_layout(
|
||||
matches: &ArgMatches, config: &Config,
|
||||
args: &BottomArgs, config: &ConfigV1,
|
||||
) -> error::Result<(BottomLayout, u64, Option<BottomWidgetType>)> {
|
||||
let cpu_left_legend = is_flag_enabled!(cpu_left_legend, matches, config);
|
||||
let cpu_left_legend = is_flag_enabled!(cpu_left_legend, args.cpu, config);
|
||||
|
||||
let (default_widget_type, mut default_widget_count) =
|
||||
get_default_widget_and_count(matches, config)?;
|
||||
get_default_widget_and_count(args, config)?;
|
||||
let mut default_widget_id = 1;
|
||||
|
||||
let bottom_layout = if is_flag_enabled!(basic, matches, config) {
|
||||
let bottom_layout = if is_flag_enabled!(basic, args.general, config) {
|
||||
default_widget_id = DEFAULT_WIDGET_ID;
|
||||
|
||||
BottomLayout::init_basic_default(get_use_battery(matches, config))
|
||||
BottomLayout::init_basic_default(get_use_battery(args, 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) {
|
||||
ref_row = toml_edit::de::from_str::<ConfigV1>(if get_use_battery(args, config) {
|
||||
DEFAULT_BATTERY_LAYOUT
|
||||
} else {
|
||||
DEFAULT_LAYOUT
|
||||
@ -423,13 +431,17 @@ pub fn get_widget_layout(
|
||||
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)?
|
||||
fn get_update_rate(args: &BottomArgs, config: &ConfigV1) -> error::Result<u64> {
|
||||
let update_rate = if let Some(update_rate) = &args.general.rate {
|
||||
try_parse_ms(update_rate).map_err(|_| {
|
||||
BottomError::ArgumentError("set your update rate to be valid".to_string())
|
||||
})?
|
||||
} else if let Some(flags) = &config.flags {
|
||||
if let Some(rate) = &flags.rate {
|
||||
match rate {
|
||||
StringOrNum::String(s) => try_parse_ms(s)?,
|
||||
StringOrNum::String(s) => try_parse_ms(s).map_err(|_| {
|
||||
BottomError::ConfigError("set your update rate to be valid".to_string())
|
||||
})?,
|
||||
StringOrNum::Num(n) => *n,
|
||||
}
|
||||
} else {
|
||||
@ -448,32 +460,24 @@ fn get_update_rate(matches: &ArgMatches, config: &Config) -> error::Result<u64>
|
||||
Ok(update_rate)
|
||||
}
|
||||
|
||||
fn get_temperature(matches: &ArgMatches, config: &Config) -> error::Result<TemperatureType> {
|
||||
if matches.get_flag("fahrenheit") {
|
||||
fn get_temperature(args: &BottomArgs, config: &ConfigV1) -> error::Result<TemperatureType> {
|
||||
if args.temperature.fahrenheit {
|
||||
return Ok(TemperatureType::Fahrenheit);
|
||||
} else if matches.get_flag("kelvin") {
|
||||
} else if args.temperature.kelvin {
|
||||
return Ok(TemperatureType::Kelvin);
|
||||
} else if matches.get_flag("celsius") {
|
||||
} else if args.temperature.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>\"."
|
||||
))),
|
||||
};
|
||||
return TemperatureType::from_str(temp_type).map_err(BottomError::ConfigError);
|
||||
}
|
||||
}
|
||||
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") {
|
||||
/// Yes, this function gets whether to show average CPU (true) or not (false).
|
||||
fn get_show_average_cpu(args: &BottomArgs, config: &ConfigV1) -> bool {
|
||||
if args.cpu.hide_avg_cpu {
|
||||
return false;
|
||||
} else if let Some(flags) = &config.flags {
|
||||
if let Some(avg_cpu) = flags.hide_avg_cpu {
|
||||
@ -497,15 +501,18 @@ fn try_parse_ms(s: &str) -> error::Result<u64> {
|
||||
}
|
||||
|
||||
fn get_default_time_value(
|
||||
matches: &ArgMatches, config: &Config, retention_ms: u64,
|
||||
args: &BottomArgs, config: &ConfigV1, 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)?
|
||||
let default_time = if let Some(default_time_value) = &args.general.default_time_value {
|
||||
try_parse_ms(default_time_value).map_err(|_| {
|
||||
BottomError::ArgumentError("set your default time to be valid".to_string())
|
||||
})?
|
||||
} 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::String(s) => try_parse_ms(s).map_err(|_| {
|
||||
BottomError::ConfigError("set your default time to be valid".to_string())
|
||||
})?,
|
||||
StringOrNum::Num(n) => *n,
|
||||
}
|
||||
} else {
|
||||
@ -517,11 +524,11 @@ fn get_default_time_value(
|
||||
|
||||
if default_time < 30000 {
|
||||
return Err(BottomError::ConfigError(
|
||||
"set your default value to be at least 30s.".to_string(),
|
||||
"set your default time 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 {}.",
|
||||
"set your default time to be at most {}.",
|
||||
humantime::Duration::from(Duration::from_millis(retention_ms))
|
||||
)));
|
||||
}
|
||||
@ -530,14 +537,18 @@ fn get_default_time_value(
|
||||
}
|
||||
|
||||
fn get_time_interval(
|
||||
matches: &ArgMatches, config: &Config, retention_ms: u64,
|
||||
args: &BottomArgs, config: &ConfigV1, 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)?
|
||||
let time_interval = if let Some(time_interval) = &args.general.time_delta {
|
||||
try_parse_ms(time_interval).map_err(|_| {
|
||||
BottomError::ArgumentError("set your time delta to be valid".to_string())
|
||||
})?
|
||||
} 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::String(s) => try_parse_ms(s).map_err(|_| {
|
||||
BottomError::ArgumentError("set your time delta to be valid".to_string())
|
||||
})?,
|
||||
StringOrNum::Num(n) => *n,
|
||||
}
|
||||
} else {
|
||||
@ -562,9 +573,9 @@ fn get_time_interval(
|
||||
}
|
||||
|
||||
fn get_default_widget_and_count(
|
||||
matches: &ArgMatches, config: &Config,
|
||||
args: &BottomArgs, config: &ConfigV1,
|
||||
) -> error::Result<(Option<BottomWidgetType>, u64)> {
|
||||
let widget_type = if let Some(widget_type) = matches.get_one::<String>("default_widget_type") {
|
||||
let widget_type = if let Some(widget_type) = &args.general.default_widget_type {
|
||||
let parsed_widget = widget_type.parse::<BottomWidgetType>()?;
|
||||
if let BottomWidgetType::Empty = parsed_widget {
|
||||
None
|
||||
@ -586,21 +597,20 @@ fn get_default_widget_and_count(
|
||||
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 {
|
||||
let widget_count: Option<u128> = if let Some(widget_count) = args.general.default_widget_count {
|
||||
Some(widget_count.into())
|
||||
} else {
|
||||
config.flags.as_ref().and_then(|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()
|
||||
"set your widget count to be at most 18446744073709551615.".to_string()
|
||||
))?;
|
||||
Ok((Some(widget_type), widget_count))
|
||||
}
|
||||
@ -613,9 +623,10 @@ fn get_default_widget_and_count(
|
||||
}
|
||||
|
||||
#[allow(unused_variables)]
|
||||
fn get_use_battery(matches: &ArgMatches, config: &Config) -> bool {
|
||||
fn get_use_battery(args: &BottomArgs, config: &ConfigV1) -> bool {
|
||||
#[cfg(feature = "battery")]
|
||||
{
|
||||
// TODO: Move this so it's dynamic in the app itself and automatically hide if there are no batteries?
|
||||
if let Ok(battery_manager) = Manager::new() {
|
||||
if let Ok(batteries) = battery_manager.batteries() {
|
||||
if batteries.count() == 0 {
|
||||
@ -624,7 +635,7 @@ fn get_use_battery(matches: &ArgMatches, config: &Config) -> bool {
|
||||
}
|
||||
}
|
||||
|
||||
if matches.get_flag("battery") {
|
||||
if args.battery.battery {
|
||||
return true;
|
||||
} else if let Some(flags) = &config.flags {
|
||||
if let Some(battery) = flags.battery {
|
||||
@ -637,10 +648,10 @@ fn get_use_battery(matches: &ArgMatches, config: &Config) -> bool {
|
||||
}
|
||||
|
||||
#[allow(unused_variables)]
|
||||
fn get_enable_gpu(matches: &ArgMatches, config: &Config) -> bool {
|
||||
fn get_enable_gpu(args: &BottomArgs, config: &ConfigV1) -> bool {
|
||||
#[cfg(feature = "gpu")]
|
||||
{
|
||||
if matches.get_flag("enable_gpu") {
|
||||
if args.gpu.enable_gpu {
|
||||
return true;
|
||||
} else if let Some(flags) = &config.flags {
|
||||
if let Some(enable_gpu) = flags.enable_gpu {
|
||||
@ -653,10 +664,10 @@ fn get_enable_gpu(matches: &ArgMatches, config: &Config) -> bool {
|
||||
}
|
||||
|
||||
#[allow(unused_variables)]
|
||||
fn get_enable_cache_memory(matches: &ArgMatches, config: &Config) -> bool {
|
||||
fn get_enable_cache_memory(args: &BottomArgs, config: &ConfigV1) -> bool {
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
{
|
||||
if matches.get_flag("enable_cache_memory") {
|
||||
if args.memory.enable_cache_memory {
|
||||
return true;
|
||||
} else if let Some(flags) = &config.flags {
|
||||
if let Some(enable_cache_memory) = flags.enable_cache_memory {
|
||||
@ -705,8 +716,8 @@ fn get_ignore_list(ignore_list: &Option<IgnoreList>) -> error::Result<Option<Fil
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_color_scheme(matches: &ArgMatches, config: &Config) -> error::Result<ColourScheme> {
|
||||
if let Some(color) = matches.get_one::<String>("color") {
|
||||
pub fn get_color_scheme(args: &BottomArgs, config: &ConfigV1) -> error::Result<ColourScheme> {
|
||||
if let Some(color) = &args.style.color {
|
||||
// Highest priority is always command line flags...
|
||||
return ColourScheme::from_str(color);
|
||||
} else if let Some(colors) = &config.colors {
|
||||
@ -730,8 +741,8 @@ pub fn get_color_scheme(matches: &ArgMatches, config: &Config) -> error::Result<
|
||||
Ok(ColourScheme::Default)
|
||||
}
|
||||
|
||||
fn get_network_unit_type(matches: &ArgMatches, config: &Config) -> DataUnit {
|
||||
if matches.get_flag("network_use_bytes") {
|
||||
fn get_network_unit_type(args: &BottomArgs, config: &ConfigV1) -> DataUnit {
|
||||
if args.network.network_use_bytes {
|
||||
return DataUnit::Byte;
|
||||
} else if let Some(flags) = &config.flags {
|
||||
if let Some(network_use_bytes) = flags.network_use_bytes {
|
||||
@ -744,8 +755,8 @@ fn get_network_unit_type(matches: &ArgMatches, config: &Config) -> DataUnit {
|
||||
DataUnit::Bit
|
||||
}
|
||||
|
||||
fn get_network_scale_type(matches: &ArgMatches, config: &Config) -> AxisScaling {
|
||||
if matches.get_flag("network_use_log") {
|
||||
fn get_network_scale_type(args: &BottomArgs, config: &ConfigV1) -> AxisScaling {
|
||||
if args.network.network_use_log {
|
||||
return AxisScaling::Log;
|
||||
} else if let Some(flags) = &config.flags {
|
||||
if let Some(network_use_log) = flags.network_use_log {
|
||||
@ -758,15 +769,18 @@ fn get_network_scale_type(matches: &ArgMatches, config: &Config) -> AxisScaling
|
||||
AxisScaling::Linear
|
||||
}
|
||||
|
||||
fn get_retention(matches: &ArgMatches, config: &Config) -> error::Result<u64> {
|
||||
fn get_retention(args: &BottomArgs, config: &ConfigV1) -> error::Result<u64> {
|
||||
const DEFAULT_RETENTION_MS: u64 = 600 * 1000; // Keep 10 minutes of data.
|
||||
|
||||
if let Some(retention) = matches.get_one::<String>("retention") {
|
||||
if let Some(retention) = &args.general.retention {
|
||||
try_parse_ms(retention)
|
||||
.map_err(|_| BottomError::ArgumentError("`retention` is an invalid value".to_string()))
|
||||
} else if let Some(flags) = &config.flags {
|
||||
if let Some(retention) = &flags.retention {
|
||||
Ok(match retention {
|
||||
StringOrNum::String(s) => try_parse_ms(s)?,
|
||||
StringOrNum::String(s) => try_parse_ms(s).map_err(|_| {
|
||||
BottomError::ConfigError("`retention` is an invalid value".to_string())
|
||||
})?,
|
||||
StringOrNum::Num(n) => *n,
|
||||
})
|
||||
} else {
|
||||
@ -777,19 +791,21 @@ fn get_retention(matches: &ArgMatches, config: &Config) -> error::Result<u64> {
|
||||
}
|
||||
}
|
||||
|
||||
fn get_network_legend(
|
||||
matches: &ArgMatches, config: &Config,
|
||||
fn get_network_legend_position(
|
||||
args: &BottomArgs, config: &ConfigV1,
|
||||
) -> error::Result<Option<LegendPosition>> {
|
||||
let error =
|
||||
|_| BottomError::ConfigError("network_legend is set to an invalid value".to_string());
|
||||
if let Some(s) = matches.get_one::<String>("network_legend") {
|
||||
if let Some(s) = &args.network.network_legend {
|
||||
match s.to_ascii_lowercase().trim() {
|
||||
"none" => Ok(None),
|
||||
position => Ok(Some(position.parse::<LegendPosition>().map_err(error)?)),
|
||||
position => Ok(Some(position.parse::<LegendPosition>().map_err(|_| {
|
||||
BottomError::ArgumentError("`network_legend` is an invalid value".to_string())
|
||||
})?)),
|
||||
}
|
||||
} else if let Some(flags) = &config.flags {
|
||||
if let Some(legend) = &flags.network_legend {
|
||||
Ok(Some(legend.parse::<LegendPosition>().map_err(error)?))
|
||||
Ok(Some(legend.parse::<LegendPosition>().map_err(|_| {
|
||||
BottomError::ConfigError("`network_legend` is an invalid value".to_string())
|
||||
})?))
|
||||
} else {
|
||||
Ok(Some(LegendPosition::default()))
|
||||
}
|
||||
@ -798,19 +814,21 @@ fn get_network_legend(
|
||||
}
|
||||
}
|
||||
|
||||
fn get_memory_legend(
|
||||
matches: &ArgMatches, config: &Config,
|
||||
fn get_memory_legend_position(
|
||||
args: &BottomArgs, config: &ConfigV1,
|
||||
) -> error::Result<Option<LegendPosition>> {
|
||||
let error =
|
||||
|_| BottomError::ConfigError("memory_legend is set to an invalid value".to_string());
|
||||
if let Some(s) = matches.get_one::<String>("memory_legend") {
|
||||
if let Some(s) = &args.memory.memory_legend {
|
||||
match s.to_ascii_lowercase().trim() {
|
||||
"none" => Ok(None),
|
||||
position => Ok(Some(position.parse::<LegendPosition>().map_err(error)?)),
|
||||
position => Ok(Some(position.parse::<LegendPosition>().map_err(|_| {
|
||||
BottomError::ArgumentError("`memory_legend` is an invalid value".to_string())
|
||||
})?)),
|
||||
}
|
||||
} else if let Some(flags) = &config.flags {
|
||||
if let Some(legend) = &flags.memory_legend {
|
||||
Ok(Some(legend.parse::<LegendPosition>().map_err(error)?))
|
||||
Ok(Some(legend.parse::<LegendPosition>().map_err(|_| {
|
||||
BottomError::ConfigError("`memory_legend` is an invalid value".to_string())
|
||||
})?))
|
||||
} else {
|
||||
Ok(Some(LegendPosition::default()))
|
||||
}
|
||||
@ -821,11 +839,12 @@ fn get_memory_legend(
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use clap::ArgMatches;
|
||||
use clap::Parser;
|
||||
|
||||
use super::{get_color_scheme, get_time_interval, get_widget_layout, Config};
|
||||
use super::{get_color_scheme, get_time_interval, ConfigV1};
|
||||
use crate::{
|
||||
app::App,
|
||||
args::BottomArgs,
|
||||
canvas::styling::CanvasStyling,
|
||||
options::{
|
||||
config::ConfigFlags, get_default_time_value, get_retention, get_update_rate,
|
||||
@ -854,26 +873,24 @@ mod test {
|
||||
|
||||
#[test]
|
||||
fn matches_human_times() {
|
||||
let config = Config::default();
|
||||
let app = crate::args::build_app();
|
||||
let config = ConfigV1::default();
|
||||
|
||||
{
|
||||
let app = app.clone();
|
||||
let delta_args = vec!["btm", "--time_delta", "2 min"];
|
||||
let matches = app.get_matches_from(delta_args);
|
||||
let args = BottomArgs::parse_from(delta_args);
|
||||
|
||||
assert_eq!(
|
||||
get_time_interval(&matches, &config, 60 * 60 * 1000),
|
||||
get_time_interval(&args, &config, 60 * 60 * 1000),
|
||||
Ok(2 * 60 * 1000)
|
||||
);
|
||||
}
|
||||
|
||||
{
|
||||
let default_time_args = vec!["btm", "--default_time_value", "300s"];
|
||||
let matches = app.get_matches_from(default_time_args);
|
||||
let args = BottomArgs::parse_from(default_time_args);
|
||||
|
||||
assert_eq!(
|
||||
get_default_time_value(&matches, &config, 60 * 60 * 1000),
|
||||
get_default_time_value(&args, &config, 60 * 60 * 1000),
|
||||
Ok(5 * 60 * 1000)
|
||||
);
|
||||
}
|
||||
@ -881,26 +898,24 @@ mod test {
|
||||
|
||||
#[test]
|
||||
fn matches_number_times() {
|
||||
let config = Config::default();
|
||||
let app = crate::args::build_app();
|
||||
let config = ConfigV1::default();
|
||||
|
||||
{
|
||||
let app = app.clone();
|
||||
let delta_args = vec!["btm", "--time_delta", "120000"];
|
||||
let matches = app.get_matches_from(delta_args);
|
||||
let args = BottomArgs::parse_from(delta_args);
|
||||
|
||||
assert_eq!(
|
||||
get_time_interval(&matches, &config, 60 * 60 * 1000),
|
||||
get_time_interval(&args, &config, 60 * 60 * 1000),
|
||||
Ok(2 * 60 * 1000)
|
||||
);
|
||||
}
|
||||
|
||||
{
|
||||
let default_time_args = vec!["btm", "--default_time_value", "300000"];
|
||||
let matches = app.get_matches_from(default_time_args);
|
||||
let args = BottomArgs::parse_from(default_time_args);
|
||||
|
||||
assert_eq!(
|
||||
get_default_time_value(&matches, &config, 60 * 60 * 1000),
|
||||
get_default_time_value(&args, &config, 60 * 60 * 1000),
|
||||
Ok(5 * 60 * 1000)
|
||||
);
|
||||
}
|
||||
@ -908,10 +923,9 @@ mod test {
|
||||
|
||||
#[test]
|
||||
fn config_human_times() {
|
||||
let app = crate::args::build_app();
|
||||
let matches = app.get_matches_from(["btm"]);
|
||||
let args = BottomArgs::parse_from(["btm"]);
|
||||
|
||||
let mut config = Config::default();
|
||||
let mut config = ConfigV1::default();
|
||||
let flags = ConfigFlags {
|
||||
time_delta: Some("2 min".to_string().into()),
|
||||
default_time_value: Some("300s".to_string().into()),
|
||||
@ -923,26 +937,25 @@ mod test {
|
||||
config.flags = Some(flags);
|
||||
|
||||
assert_eq!(
|
||||
get_time_interval(&matches, &config, 60 * 60 * 1000),
|
||||
get_time_interval(&args, &config, 60 * 60 * 1000),
|
||||
Ok(2 * 60 * 1000)
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
get_default_time_value(&matches, &config, 60 * 60 * 1000),
|
||||
get_default_time_value(&args, &config, 60 * 60 * 1000),
|
||||
Ok(5 * 60 * 1000)
|
||||
);
|
||||
|
||||
assert_eq!(get_update_rate(&matches, &config), Ok(1000));
|
||||
assert_eq!(get_update_rate(&args, &config), Ok(1000));
|
||||
|
||||
assert_eq!(get_retention(&matches, &config), Ok(600000));
|
||||
assert_eq!(get_retention(&args, &config), Ok(600000));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn config_number_times_as_string() {
|
||||
let app = crate::args::build_app();
|
||||
let matches = app.get_matches_from(["btm"]);
|
||||
let args = BottomArgs::parse_from(["btm"]);
|
||||
|
||||
let mut config = Config::default();
|
||||
let mut config = ConfigV1::default();
|
||||
let flags = ConfigFlags {
|
||||
time_delta: Some("120000".to_string().into()),
|
||||
default_time_value: Some("300000".to_string().into()),
|
||||
@ -954,26 +967,25 @@ mod test {
|
||||
config.flags = Some(flags);
|
||||
|
||||
assert_eq!(
|
||||
get_time_interval(&matches, &config, 60 * 60 * 1000),
|
||||
get_time_interval(&args, &config, 60 * 60 * 1000),
|
||||
Ok(2 * 60 * 1000)
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
get_default_time_value(&matches, &config, 60 * 60 * 1000),
|
||||
get_default_time_value(&args, &config, 60 * 60 * 1000),
|
||||
Ok(5 * 60 * 1000)
|
||||
);
|
||||
|
||||
assert_eq!(get_update_rate(&matches, &config), Ok(1000));
|
||||
assert_eq!(get_update_rate(&args, &config), Ok(1000));
|
||||
|
||||
assert_eq!(get_retention(&matches, &config), Ok(600000));
|
||||
assert_eq!(get_retention(&args, &config), Ok(600000));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn config_number_times_as_num() {
|
||||
let app = crate::args::build_app();
|
||||
let matches = app.get_matches_from(["btm"]);
|
||||
let args = BottomArgs::parse_from(["btm"]);
|
||||
|
||||
let mut config = Config::default();
|
||||
let mut config = ConfigV1::default();
|
||||
let flags = ConfigFlags {
|
||||
time_delta: Some(120000.into()),
|
||||
default_time_value: Some(300000.into()),
|
||||
@ -985,41 +997,35 @@ mod test {
|
||||
config.flags = Some(flags);
|
||||
|
||||
assert_eq!(
|
||||
get_time_interval(&matches, &config, 60 * 60 * 1000),
|
||||
get_time_interval(&args, &config, 60 * 60 * 1000),
|
||||
Ok(2 * 60 * 1000)
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
get_default_time_value(&matches, &config, 60 * 60 * 1000),
|
||||
get_default_time_value(&args, &config, 60 * 60 * 1000),
|
||||
Ok(5 * 60 * 1000)
|
||||
);
|
||||
|
||||
assert_eq!(get_update_rate(&matches, &config), Ok(1000));
|
||||
assert_eq!(get_update_rate(&args, &config), Ok(1000));
|
||||
|
||||
assert_eq!(get_retention(&matches, &config), Ok(600000));
|
||||
assert_eq!(get_retention(&args, &config), Ok(600000));
|
||||
}
|
||||
|
||||
fn create_app(config: Config, matches: ArgMatches) -> App {
|
||||
let (layout, id, ty) = get_widget_layout(&matches, &config).unwrap();
|
||||
fn create_app(args: BottomArgs) -> App {
|
||||
let config = ConfigV1::default();
|
||||
let styling =
|
||||
CanvasStyling::new(get_color_scheme(&matches, &config).unwrap(), &config).unwrap();
|
||||
CanvasStyling::new(get_color_scheme(&args, &config).unwrap(), &config).unwrap();
|
||||
|
||||
super::init_app(matches, config, &layout, id, &ty, &styling).unwrap()
|
||||
super::init_app(args, config, &styling).unwrap().0
|
||||
}
|
||||
|
||||
// TODO: There's probably a better way to create clap options AND unify together to avoid the possibility of
|
||||
// typos/mixing up. Use proc macros to unify on one struct?
|
||||
#[test]
|
||||
fn verify_cli_options_build() {
|
||||
let app = crate::args::build_app();
|
||||
let app = crate::args::build_cmd();
|
||||
|
||||
let default_app = {
|
||||
let app = app.clone();
|
||||
let config = Config::default();
|
||||
let matches = app.get_matches_from([""]);
|
||||
|
||||
create_app(config, matches)
|
||||
};
|
||||
let default_app = create_app(BottomArgs::parse_from(["btm"]));
|
||||
|
||||
// Skip battery since it's tricky to test depending on the platform/features we're testing with.
|
||||
let skip = ["help", "version", "celsius", "battery"];
|
||||
@ -1036,11 +1042,8 @@ mod test {
|
||||
let arg = format!("--{arg_name}");
|
||||
|
||||
let arguments = vec!["btm", &arg];
|
||||
let app = app.clone();
|
||||
let config = Config::default();
|
||||
let matches = app.get_matches_from(arguments);
|
||||
|
||||
let testing_app = create_app(config, matches);
|
||||
let args = BottomArgs::parse_from(arguments);
|
||||
let testing_app = create_app(args);
|
||||
|
||||
if (default_app.app_config_fields == testing_app.app_config_fields)
|
||||
&& default_app.is_expanded == testing_app.is_expanded
|
||||
|
@ -5,41 +5,28 @@
|
||||
|
||||
// TODO: New sections are misaligned! See if we can get that fixed.
|
||||
|
||||
use std::cmp::Ordering;
|
||||
use std::path::PathBuf;
|
||||
|
||||
use clap::*;
|
||||
use clap::{builder::PossibleValue, *};
|
||||
use indoc::indoc;
|
||||
|
||||
pub fn get_matches() -> ArgMatches {
|
||||
build_app().get_matches()
|
||||
}
|
||||
const TEMPLATE: &str = indoc! {
|
||||
"{name} {version}
|
||||
{author}
|
||||
|
||||
/// Returns an [`Ordering`] for two [`Arg`] values.
|
||||
///
|
||||
/// Note this assumes that they both have a _long_ name, and will
|
||||
/// panic if either are missing!
|
||||
fn sort_args(a: &Arg, b: &Arg) -> Ordering {
|
||||
let a = a.get_long().unwrap();
|
||||
let b = b.get_long().unwrap();
|
||||
{about}
|
||||
|
||||
a.cmp(b)
|
||||
}
|
||||
{usage-heading} {usage}
|
||||
|
||||
/// Create an array of [`Arg`] values. If there is more than one value, then
|
||||
/// they will be sorted by their long name. Note this sort will panic if
|
||||
/// any [`Arg`] does not have a long name!
|
||||
macro_rules! args {
|
||||
( $arg:expr $(,)?) => {
|
||||
[$arg]
|
||||
{all-args}"
|
||||
};
|
||||
( $( $arg:expr ),+ $(,)? ) => {
|
||||
{
|
||||
let mut args = [ $( $arg, )* ];
|
||||
args.sort_unstable_by(sort_args);
|
||||
args
|
||||
}
|
||||
|
||||
const USAGE: &str = "btm [OPTIONS]";
|
||||
|
||||
const VERSION: &str = match option_env!("NIGHTLY_VERSION") {
|
||||
Some(nightly_version) => nightly_version,
|
||||
None => crate_version!(),
|
||||
};
|
||||
}
|
||||
|
||||
const CHART_WIDGET_POSITIONS: [&str; 9] = [
|
||||
"none",
|
||||
@ -53,57 +40,104 @@ const CHART_WIDGET_POSITIONS: [&str; 9] = [
|
||||
"bottom-right",
|
||||
];
|
||||
|
||||
fn general_args(cmd: Command) -> Command {
|
||||
let cmd = cmd.next_help_heading("General Options");
|
||||
/// Represents the arguments that can be passed in to bottom.
|
||||
#[derive(Parser, Debug)]
|
||||
#[command(
|
||||
name = crate_name!(),
|
||||
version = VERSION,
|
||||
author = crate_authors!(),
|
||||
about = crate_description!(),
|
||||
disable_help_flag = true,
|
||||
disable_version_flag = true,
|
||||
color = ColorChoice::Auto,
|
||||
help_template = TEMPLATE,
|
||||
override_usage = USAGE,
|
||||
)]
|
||||
pub struct BottomArgs {
|
||||
#[command(flatten)]
|
||||
pub general: GeneralArgs,
|
||||
|
||||
let autohide_time = Arg::new("autohide_time")
|
||||
.long("autohide_time")
|
||||
.action(ArgAction::SetTrue)
|
||||
.help("Temporarily shows the time scale in graphs.")
|
||||
.long_help(
|
||||
"Automatically hides the time scale in graphs after being shown for a brief moment when zoomed \
|
||||
in/out. If time is disabled via --hide_time then this will have no effect."
|
||||
);
|
||||
#[command(flatten)]
|
||||
pub process: ProcessArgs,
|
||||
|
||||
let basic = Arg::new("basic")
|
||||
.short('b')
|
||||
.long("basic")
|
||||
.action(ArgAction::SetTrue)
|
||||
.help("Hides graphs and uses a more basic look.")
|
||||
.long_help("Hides graphs and uses a more basic look, largely inspired by htop's design.");
|
||||
#[command(flatten)]
|
||||
pub temperature: TemperatureArgs,
|
||||
|
||||
let config_location = Arg::new("config_location")
|
||||
.short('C')
|
||||
.long("config")
|
||||
.action(ArgAction::Set)
|
||||
.value_name("CONFIG PATH")
|
||||
.help("Sets the location of the config file.")
|
||||
.long_help(
|
||||
"Sets the location of the config file. Expects a config file in the TOML format. \
|
||||
#[command(flatten)]
|
||||
pub cpu: CpuArgs,
|
||||
|
||||
#[command(flatten)]
|
||||
pub memory: MemoryArgs,
|
||||
|
||||
#[command(flatten)]
|
||||
pub network: NetworkArgs,
|
||||
|
||||
#[cfg(feature = "battery")]
|
||||
#[command(flatten)]
|
||||
pub battery: BatteryArgs,
|
||||
|
||||
#[cfg(feature = "gpu")]
|
||||
#[command(flatten)]
|
||||
pub gpu: GpuArgs,
|
||||
|
||||
#[command(flatten)]
|
||||
pub style: StyleArgs,
|
||||
|
||||
#[command(flatten)]
|
||||
pub other: OtherArgs,
|
||||
}
|
||||
|
||||
/// General arguments/config options.
|
||||
#[derive(Args, Clone, Debug)]
|
||||
#[command(next_help_heading = "General Options", rename_all = "snake_case")]
|
||||
pub struct GeneralArgs {
|
||||
#[arg(
|
||||
long,
|
||||
action = ArgAction::SetTrue,
|
||||
help = "Temporarily shows the time scale in graphs.",
|
||||
long = "Automatically hides the time scale in graphs after being shown for a brief moment when zoomed \
|
||||
in/out. If time is disabled using --hide_time then this will have no effect."
|
||||
)]
|
||||
pub autohide_time: bool,
|
||||
|
||||
#[arg(
|
||||
short = 'b',
|
||||
long,
|
||||
action = ArgAction::SetTrue,
|
||||
help = "Hides graphs and uses a more basic look.",
|
||||
long_help = "Hides graphs and uses a more basic look, largely inspired by htop's design."
|
||||
)]
|
||||
pub basic: bool,
|
||||
|
||||
#[arg(
|
||||
short = 'C',
|
||||
long,
|
||||
value_name = "PATH",
|
||||
value_hint = ValueHint::AnyPath,
|
||||
help = "Sets the location of the config file.",
|
||||
long_help = "Sets the location of the config file. Expects a config file in the TOML format. \
|
||||
If it doesn't exist, a default config file is created at the path. If no path is provided, \
|
||||
the default config location will be used."
|
||||
)
|
||||
.value_hint(ValueHint::AnyPath);
|
||||
)]
|
||||
pub config_location: Option<PathBuf>,
|
||||
|
||||
let default_time_value = Arg::new("default_time_value")
|
||||
.short('t')
|
||||
.long("default_time_value")
|
||||
.action(ArgAction::Set)
|
||||
.value_name("TIME")
|
||||
.help("Default time value for graphs.")
|
||||
.long_help(
|
||||
"Default time value for graphs. Either a number in milliseconds or a 'human duration' \
|
||||
(e.g. 60s, 10m). Defaults to 60s, must be at least 30s.",
|
||||
);
|
||||
#[arg(
|
||||
short = 't',
|
||||
long,
|
||||
value_name = "TIME",
|
||||
help = "Default time value for graphs.",
|
||||
long_help = "Default time value for graphs. Either a number in milliseconds or a 'human duration' \
|
||||
(e.g. 60s, 10m). Defaults to 60s, must be at least 30s."
|
||||
)]
|
||||
pub default_time_value: Option<String>,
|
||||
|
||||
// TODO: Charts are broken in the manpage
|
||||
let default_widget_count = Arg::new("default_widget_count")
|
||||
.long("default_widget_count")
|
||||
.action(ArgAction::Set)
|
||||
.requires_all(["default_widget_type"])
|
||||
.value_name("N")
|
||||
.help("Sets the N'th selected widget type as the default.")
|
||||
.long_help(indoc! {
|
||||
#[arg(
|
||||
long,
|
||||
requires_all = ["default_widget_type"],
|
||||
value_name = "N",
|
||||
help = "Sets the N'th selected widget type as the default.",
|
||||
long_help = indoc! {
|
||||
"Sets the N'th selected widget type to use as the default widget. Requires 'default_widget_type' to also be \
|
||||
set, and defaults to 1.
|
||||
|
||||
@ -117,14 +151,15 @@ fn general_args(cmd: Command) -> Command {
|
||||
And we set our default widget type to 'CPU'. If we set '--default_widget_count 1', then it would use the \
|
||||
CPU (1) as the default widget. If we set '--default_widget_count 3', it would use CPU (3) as the default \
|
||||
instead."
|
||||
});
|
||||
}
|
||||
)]
|
||||
pub default_widget_count: Option<u64>,
|
||||
|
||||
let default_widget_type = Arg::new("default_widget_type")
|
||||
.long("default_widget_type")
|
||||
.action(ArgAction::Set)
|
||||
.value_name("WIDGET")
|
||||
.help("Sets the default widget type, use `--help` for info.")
|
||||
.long_help(indoc!{
|
||||
#[arg(
|
||||
long,
|
||||
value_name = "WIDGET",
|
||||
help = "Sets the default widget type. Use --help for more info.",
|
||||
long_help = indoc!{
|
||||
"Sets which widget type to use as the default widget. For the default \
|
||||
layout, this defaults to the 'process' widget. For a custom layout, it defaults \
|
||||
to the first widget it sees.
|
||||
@ -136,9 +171,9 @@ fn general_args(cmd: Command) -> Command {
|
||||
| Process | CPU (3) | Temperature | CPU (4) |
|
||||
+---------+---------+-------------+---------+
|
||||
|
||||
Setting '--default_widget_type temperature' will make the temperature widget selected by default."
|
||||
})
|
||||
.value_parser([
|
||||
Then, setting '--default_widget_type temperature' will make the temperature widget selected by default."
|
||||
},
|
||||
value_parser = [
|
||||
"cpu",
|
||||
"mem",
|
||||
"net",
|
||||
@ -153,359 +188,363 @@ fn general_args(cmd: Command) -> Command {
|
||||
"batt",
|
||||
#[cfg(feature = "battery")]
|
||||
"battery",
|
||||
]);
|
||||
],
|
||||
)]
|
||||
pub default_widget_type: Option<String>,
|
||||
|
||||
let disable_click = Arg::new("disable_click")
|
||||
.long("disable_click")
|
||||
.action(ArgAction::SetTrue)
|
||||
.help("Disables mouse clicks.")
|
||||
.long_help("Disables mouse clicks from interacting with bottom.");
|
||||
#[arg(
|
||||
long,
|
||||
action = ArgAction::SetTrue,
|
||||
help = "Disables mouse clicks.",
|
||||
long_help = "Disables mouse clicks from interacting with bottom."
|
||||
)]
|
||||
pub disable_click: bool,
|
||||
|
||||
// TODO: Change this to accept a string with the type of marker.
|
||||
let dot_marker = Arg::new("dot_marker")
|
||||
.short('m')
|
||||
.long("dot_marker")
|
||||
.action(ArgAction::SetTrue)
|
||||
.help("Uses a dot marker for graphs.")
|
||||
.long_help("Uses a dot marker for graphs as opposed to the default braille marker.");
|
||||
#[arg(
|
||||
short = 'm',
|
||||
long,
|
||||
action = ArgAction::SetTrue,
|
||||
help = "Uses a dot marker for graphs.",
|
||||
long_help = "Uses a dot marker for graphs as opposed to the default braille marker."
|
||||
)]
|
||||
pub dot_marker: bool,
|
||||
|
||||
let expanded = Arg::new("expanded")
|
||||
.short('e')
|
||||
.long("expanded")
|
||||
.action(ArgAction::SetTrue)
|
||||
.help("Expand the default widget upon starting the app.")
|
||||
.long_help("Expand the default widget upon starting the app. This flag has no effect in basic mode (--basic).");
|
||||
#[arg(
|
||||
short = 'e',
|
||||
long,
|
||||
action = ArgAction::SetTrue,
|
||||
help = "Expand the default widget upon starting the app.",
|
||||
long_help = "Expand the default widget upon starting the app. This flag has no effect in basic mode (--basic)."
|
||||
)]
|
||||
pub expanded: bool,
|
||||
|
||||
let hide_table_gap = Arg::new("hide_table_gap")
|
||||
.long("hide_table_gap")
|
||||
.action(ArgAction::SetTrue)
|
||||
.help("Hides spacing between table headers and entries.");
|
||||
#[arg(long, action = ArgAction::SetTrue, help = "Hides spacing between table headers and entries.")]
|
||||
pub hide_table_gap: bool,
|
||||
|
||||
let hide_time = Arg::new("hide_time")
|
||||
.long("hide_time")
|
||||
.action(ArgAction::SetTrue)
|
||||
.help("Hides the time scale from being shown.");
|
||||
#[arg(long, action = ArgAction::SetTrue, help = "Hides the time scale from being shown.")]
|
||||
pub hide_time: bool,
|
||||
|
||||
let rate = Arg::new("rate")
|
||||
.short('r')
|
||||
.long("rate")
|
||||
.action(ArgAction::Set)
|
||||
.value_name("TIME")
|
||||
.help("Sets how often data is refreshed.")
|
||||
.long_help(
|
||||
"Sets how often data is refreshed. Either a number in milliseconds or a 'human duration' \
|
||||
#[arg(
|
||||
short = 'r',
|
||||
long,
|
||||
value_name = "TIME",
|
||||
help = "Sets how often data is refreshed.",
|
||||
long_help = "Sets how often data is refreshed. Either a number in milliseconds or a 'human duration' \
|
||||
(e.g. 1s, 1m). Defaults to 1s, must be at least 250ms. Smaller values may result in \
|
||||
higher system resource usage."
|
||||
);
|
||||
)]
|
||||
pub rate: Option<String>,
|
||||
|
||||
// TODO: Unify how we do defaults.
|
||||
let retention = Arg::new("retention")
|
||||
.long("retention")
|
||||
.action(ArgAction::Set)
|
||||
.value_name("TIME")
|
||||
.help("How far back data will be stored up to.")
|
||||
.long_help(
|
||||
"How far back data will be stored up to. Either a number in milliseconds or a 'human duration' \
|
||||
#[arg(
|
||||
long,
|
||||
value_name = "TIME",
|
||||
help = "How far back data will be stored up to.",
|
||||
long_help = "How far back data will be stored up to. Either a number in milliseconds or a 'human duration' \
|
||||
(e.g. 10m, 1h). Defaults to 10 minutes, and must be at least 1 minute. Larger values \
|
||||
may result in higher memory usage."
|
||||
);
|
||||
)]
|
||||
pub retention: Option<String>,
|
||||
|
||||
let show_table_scroll_position = Arg::new("show_table_scroll_position")
|
||||
.long("show_table_scroll_position")
|
||||
.action(ArgAction::SetTrue)
|
||||
.help("Shows the scroll position tracker in table widgets.")
|
||||
.long_help("Shows the list scroll position tracker in the widget title for table widgets.");
|
||||
#[arg(
|
||||
long,
|
||||
action = ArgAction::SetTrue,
|
||||
help = "Shows the list scroll position tracker in the widget title for table widgets."
|
||||
)]
|
||||
pub show_table_scroll_position: bool,
|
||||
|
||||
let time_delta = Arg::new("time_delta")
|
||||
.short('d')
|
||||
.long("time_delta")
|
||||
.action(ArgAction::Set)
|
||||
.value_name("TIME")
|
||||
.help("The amount of time changed upon zooming.")
|
||||
.long_help(
|
||||
"The amount of time changed when zooming in/out. Takes a number in \
|
||||
#[arg(
|
||||
short = 'd',
|
||||
long,
|
||||
value_name = "TIME",
|
||||
help = "The amount of time changed upon zooming.",
|
||||
long_help = "The amount of time changed when zooming in/out. Takes a number in \
|
||||
milliseconds or a human duration (e.g. 30s). The minimum is 1s, and \
|
||||
defaults to 15s.",
|
||||
);
|
||||
|
||||
cmd.args(args![
|
||||
autohide_time,
|
||||
basic,
|
||||
config_location,
|
||||
default_widget_count,
|
||||
default_time_value,
|
||||
default_widget_type,
|
||||
disable_click,
|
||||
dot_marker,
|
||||
expanded,
|
||||
hide_table_gap,
|
||||
hide_time,
|
||||
rate,
|
||||
retention,
|
||||
show_table_scroll_position,
|
||||
time_delta,
|
||||
])
|
||||
defaults to 15s."
|
||||
)]
|
||||
pub time_delta: Option<String>,
|
||||
}
|
||||
|
||||
fn process_args(cmd: Command) -> Command {
|
||||
let cmd = cmd.next_help_heading("Process Options");
|
||||
|
||||
let case_sensitive = Arg::new("case_sensitive")
|
||||
.short('S')
|
||||
.long("case_sensitive")
|
||||
.action(ArgAction::SetTrue)
|
||||
.help("Enables case sensitivity by default.")
|
||||
.long_help("Enables case sensitivity by default when searching for a process.");
|
||||
/// Process arguments/config options.
|
||||
#[derive(Args, Clone, Debug, Default)]
|
||||
#[command(next_help_heading = "Process Options", rename_all = "snake_case")]
|
||||
pub struct ProcessArgs {
|
||||
#[arg(
|
||||
short = 'S',
|
||||
long,
|
||||
action = ArgAction::SetTrue,
|
||||
help = "Enables case sensitivity by default.",
|
||||
long_help = "Enables case sensitivity by default when searching for a process."
|
||||
)]
|
||||
pub case_sensitive: bool,
|
||||
|
||||
// TODO: Rename this.
|
||||
let current_usage = Arg::new("current_usage")
|
||||
.short('u')
|
||||
.long("current_usage")
|
||||
.action(ArgAction::SetTrue)
|
||||
.help("Calculates process CPU usage as a percentage of current usage rather than total usage.");
|
||||
#[arg(
|
||||
short = 'u',
|
||||
long,
|
||||
action = ArgAction::SetTrue,
|
||||
help = "Calculates process CPU usage as a percentage of current usage rather than total usage."
|
||||
)]
|
||||
pub current_usage: bool,
|
||||
|
||||
// TODO: Disable this on Windows?
|
||||
let disable_advanced_kill = Arg::new("disable_advanced_kill")
|
||||
.long("disable_advanced_kill")
|
||||
.action(ArgAction::SetTrue)
|
||||
.help("Hides additional stopping options Unix-like systems.")
|
||||
.long_help(
|
||||
"Hides additional stopping options Unix-like systems. Signal 15 (TERM) will be sent when \
|
||||
stopping a process.",
|
||||
);
|
||||
#[arg(
|
||||
long,
|
||||
action = ArgAction::SetTrue,
|
||||
help = "Hides additional stopping options Unix-like systems.",
|
||||
long_help = "Hides additional stopping options Unix-like systems. Signal 15 (TERM) will be sent when \
|
||||
stopping a process."
|
||||
)]
|
||||
pub disable_advanced_kill: bool,
|
||||
|
||||
let group_processes = Arg::new("group_processes")
|
||||
.short('g')
|
||||
.long("group_processes")
|
||||
.action(ArgAction::SetTrue)
|
||||
.help("Groups processes with the same name by default.");
|
||||
#[arg(
|
||||
short = 'g',
|
||||
long,
|
||||
action = ArgAction::SetTrue,
|
||||
help = "Groups processes with the same name by default."
|
||||
)]
|
||||
pub group_processes: bool,
|
||||
|
||||
let mem_as_value = Arg::new("mem_as_value")
|
||||
.long("mem_as_value")
|
||||
.action(ArgAction::SetTrue)
|
||||
.help("Defaults to showing process memory usage by value.")
|
||||
.long_help("Defaults to showing process memory usage by value. Otherwise, it defaults to showing it by percentage.");
|
||||
#[arg(
|
||||
long,
|
||||
action = ArgAction::SetTrue,
|
||||
help = "Defaults to showing process memory usage by value.",
|
||||
long_help = "Defaults to showing process memory usage by value. Otherwise, it defaults to showing it by percentage."
|
||||
)]
|
||||
pub process_memory_as_value: bool,
|
||||
|
||||
let process_command = Arg::new("process_command")
|
||||
.long("process_command")
|
||||
.action(ArgAction::SetTrue)
|
||||
.help("Shows the full command name instead of the process name by default.");
|
||||
#[arg(
|
||||
long,
|
||||
action = ArgAction::SetTrue,
|
||||
help = "Shows the full command name instead of the process name by default."
|
||||
)]
|
||||
pub process_command: bool,
|
||||
|
||||
let regex = Arg::new("regex")
|
||||
.short('R')
|
||||
.long("regex")
|
||||
.action(ArgAction::SetTrue)
|
||||
.help("Enables regex by default while searching.");
|
||||
#[arg(short = 'R', long, action = ArgAction::SetTrue, help = "Enables regex by default while searching.")]
|
||||
pub regex: bool,
|
||||
|
||||
let tree = Arg::new("tree")
|
||||
.short('T')
|
||||
.long("tree")
|
||||
.action(ArgAction::SetTrue)
|
||||
.help("Makes the process widget use tree mode by default.");
|
||||
#[arg(
|
||||
short = 'T',
|
||||
long,
|
||||
action = ArgAction::SetTrue,
|
||||
help = "Makes the process widget use tree mode by default."
|
||||
)]
|
||||
pub tree: bool,
|
||||
|
||||
let unnormalized_cpu = Arg::new("unnormalized_cpu")
|
||||
.short('n')
|
||||
.long("unnormalized_cpu")
|
||||
.action(ArgAction::SetTrue)
|
||||
.help("Show process CPU% usage without averaging over the number of CPU cores.");
|
||||
#[arg(
|
||||
short = 'n',
|
||||
long,
|
||||
action = ArgAction::SetTrue,
|
||||
help = "Show process CPU% usage without averaging over the number of CPU cores."
|
||||
)]
|
||||
pub unnormalized_cpu: bool,
|
||||
|
||||
let whole_word = Arg::new("whole_word")
|
||||
.short('W')
|
||||
.long("whole_word")
|
||||
.action(ArgAction::SetTrue)
|
||||
.help("Enables whole-word matching by default while searching.");
|
||||
|
||||
let args = args![
|
||||
case_sensitive,
|
||||
current_usage,
|
||||
disable_advanced_kill,
|
||||
group_processes,
|
||||
mem_as_value,
|
||||
process_command,
|
||||
regex,
|
||||
tree,
|
||||
unnormalized_cpu,
|
||||
whole_word,
|
||||
];
|
||||
|
||||
cmd.args(args)
|
||||
#[arg(
|
||||
short = 'W',
|
||||
long,
|
||||
action = ArgAction::SetTrue,
|
||||
help = "Enables whole-word matching by default while searching."
|
||||
)]
|
||||
pub whole_word: bool,
|
||||
}
|
||||
|
||||
fn temperature_args(cmd: Command) -> Command {
|
||||
let cmd = cmd.next_help_heading("Temperature Options");
|
||||
/// Temperature arguments/config options.
|
||||
#[derive(Args, Clone, Debug, Default)]
|
||||
#[command(next_help_heading = "Temperature Options", rename_all = "snake_case")]
|
||||
#[group(id = "temperature_unit", multiple = false)]
|
||||
pub struct TemperatureArgs {
|
||||
#[arg(
|
||||
short = 'c',
|
||||
long,
|
||||
action = ArgAction::SetTrue,
|
||||
group = "temperature_unit",
|
||||
help = "Use Celsius as the temperature unit. Default.",
|
||||
long_help = "Use Celsius as the temperature unit. This is the default option."
|
||||
)]
|
||||
pub celsius: bool,
|
||||
|
||||
let celsius = Arg::new("celsius")
|
||||
.short('c')
|
||||
.long("celsius")
|
||||
.action(ArgAction::SetTrue)
|
||||
.help("Use Celsius as the temperature unit. Default.")
|
||||
.long_help("Use Celsius as the temperature unit. This is the default option.");
|
||||
#[arg(
|
||||
short = 'f',
|
||||
long,
|
||||
action = ArgAction::SetTrue,
|
||||
group = "temperature_unit",
|
||||
help = "Use Fahrenheit as the temperature unit."
|
||||
)]
|
||||
pub fahrenheit: bool,
|
||||
|
||||
let fahrenheit = Arg::new("fahrenheit")
|
||||
.short('f')
|
||||
.long("fahrenheit")
|
||||
.action(ArgAction::SetTrue)
|
||||
.help("Use Fahrenheit as the temperature unit.");
|
||||
|
||||
let kelvin = Arg::new("kelvin")
|
||||
.short('k')
|
||||
.long("kelvin")
|
||||
.action(ArgAction::SetTrue)
|
||||
.help("Use Kelvin as the temperature unit.");
|
||||
|
||||
let temperature_group = ArgGroup::new("TEMPERATURE_TYPE").args([
|
||||
celsius.get_id(),
|
||||
fahrenheit.get_id(),
|
||||
kelvin.get_id(),
|
||||
]);
|
||||
|
||||
cmd.args(args![celsius, fahrenheit, kelvin])
|
||||
.group(temperature_group)
|
||||
#[arg(
|
||||
short = 'k',
|
||||
long,
|
||||
action = ArgAction::SetTrue,
|
||||
group = "temperature_unit",
|
||||
help = "Use Kelvin as the temperature unit."
|
||||
)]
|
||||
pub kelvin: bool,
|
||||
}
|
||||
|
||||
fn cpu_args(cmd: Command) -> Command {
|
||||
let cmd = cmd.next_help_heading("CPU Options");
|
||||
|
||||
// let default_cpu_entry = Arg::new("");
|
||||
|
||||
let hide_avg_cpu = Arg::new("hide_avg_cpu")
|
||||
.short('a')
|
||||
.long("hide_avg_cpu")
|
||||
.action(ArgAction::SetTrue)
|
||||
.help("Hides the average CPU usage entry.");
|
||||
|
||||
let cpu_left_legend = Arg::new("cpu_left_legend")
|
||||
.long("cpu_left_legend")
|
||||
.action(ArgAction::SetTrue)
|
||||
.help("Puts the CPU chart legend on the left side.");
|
||||
|
||||
cmd.args(args![hide_avg_cpu, cpu_left_legend])
|
||||
/// The default selection of the CPU widget. If the given selection is invalid,
|
||||
/// we will fall back to all.
|
||||
#[derive(Clone, Copy, Debug, Default)]
|
||||
pub enum CpuDefault {
|
||||
#[default]
|
||||
All,
|
||||
Average,
|
||||
}
|
||||
|
||||
fn mem_args(cmd: Command) -> Command {
|
||||
let cmd = cmd.next_help_heading("Memory Options");
|
||||
impl ValueEnum for CpuDefault {
|
||||
fn value_variants<'a>() -> &'a [Self] {
|
||||
&[CpuDefault::All, CpuDefault::Average]
|
||||
}
|
||||
|
||||
let memory_legend = Arg::new("memory_legend")
|
||||
.long("memory_legend")
|
||||
.action(ArgAction::Set)
|
||||
.value_name("POSITION")
|
||||
.ignore_case(true)
|
||||
.help("Where to place the legend for the memory chart widget.")
|
||||
.value_parser(CHART_WIDGET_POSITIONS);
|
||||
fn to_possible_value(&self) -> Option<PossibleValue> {
|
||||
match self {
|
||||
CpuDefault::All => Some(PossibleValue::new("all")),
|
||||
CpuDefault::Average => Some(PossibleValue::new("avg").alias("average")),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// CPU arguments/config options.
|
||||
#[derive(Args, Clone, Debug, Default)]
|
||||
#[command(next_help_heading = "CPU Options", rename_all = "snake_case")]
|
||||
pub struct CpuArgs {
|
||||
#[arg(
|
||||
long,
|
||||
help = "Sets which CPU entry type is selected by default.",
|
||||
value_name = "ENTRY",
|
||||
value_parser = value_parser!(CpuDefault),
|
||||
default_value = "all"
|
||||
)]
|
||||
pub default_cpu_entry: CpuDefault,
|
||||
|
||||
#[arg(short = 'a', long, action = ArgAction::SetTrue, help = "Hides the average CPU usage entry.")]
|
||||
pub hide_avg_cpu: bool,
|
||||
|
||||
// TODO: Maybe rename this or fix this? Should this apply to all "left legends"?
|
||||
#[arg(
|
||||
short = 'l',
|
||||
long,
|
||||
action = ArgAction::SetTrue,
|
||||
help = "Puts the CPU chart legend on the left side."
|
||||
)]
|
||||
pub cpu_left_legend: bool,
|
||||
}
|
||||
|
||||
/// Memory argument/config options.
|
||||
#[derive(Args, Clone, Debug, Default)]
|
||||
#[command(next_help_heading = "Memory Options", rename_all = "snake_case")]
|
||||
pub struct MemoryArgs {
|
||||
#[arg(
|
||||
long,
|
||||
value_parser = CHART_WIDGET_POSITIONS,
|
||||
value_name = "POSITION",
|
||||
ignore_case = true,
|
||||
help = "Where to place the legend for the memory chart widget.",
|
||||
)]
|
||||
pub memory_legend: Option<String>,
|
||||
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
{
|
||||
let enable_cache_memory = Arg::new("enable_cache_memory")
|
||||
.long("enable_cache_memory")
|
||||
.action(ArgAction::SetTrue)
|
||||
.help("Enable collecting and displaying cache and buffer memory.");
|
||||
|
||||
cmd.args(args![enable_cache_memory, memory_legend])
|
||||
}
|
||||
#[cfg(target_os = "windows")]
|
||||
{
|
||||
cmd.arg(memory_legend)
|
||||
}
|
||||
#[arg(
|
||||
long,
|
||||
action = ArgAction::SetTrue,
|
||||
help = "Enables collecting and displaying cache and buffer memory."
|
||||
)]
|
||||
pub enable_cache_memory: bool,
|
||||
}
|
||||
|
||||
fn network_args(cmd: Command) -> Command {
|
||||
let cmd = cmd.next_help_heading("Network Options");
|
||||
/// Network arguments/config options.
|
||||
#[derive(Args, Clone, Debug, Default)]
|
||||
#[command(next_help_heading = "Network Options", rename_all = "snake_case")]
|
||||
pub struct NetworkArgs {
|
||||
#[arg(
|
||||
long,
|
||||
value_parser = CHART_WIDGET_POSITIONS,
|
||||
value_name = "POSITION",
|
||||
ignore_case = true,
|
||||
help = "Where to place the legend for the network chart widget.",
|
||||
)]
|
||||
pub network_legend: Option<String>,
|
||||
|
||||
let network_legend = Arg::new("network_legend")
|
||||
.long("network_legend")
|
||||
.action(ArgAction::Set)
|
||||
.value_name("POSITION")
|
||||
.ignore_case(true)
|
||||
.help("Where to place the legend for the network chart widget.")
|
||||
.value_parser(CHART_WIDGET_POSITIONS);
|
||||
// TODO: Rename some of these to remove the network prefix for serde.
|
||||
#[arg(
|
||||
long,
|
||||
action = ArgAction::SetTrue,
|
||||
help = "Displays the network widget using bytes.",
|
||||
long_help = "Displays the network widget using bytes. Defaults to bits."
|
||||
)]
|
||||
pub network_use_bytes: bool,
|
||||
|
||||
let network_use_bytes = Arg::new("network_use_bytes")
|
||||
.long("network_use_bytes")
|
||||
.action(ArgAction::SetTrue)
|
||||
.help("Displays the network widget using bytes.")
|
||||
.long_help("Displays the network widget using bytes. Defaults to bits.");
|
||||
|
||||
let network_use_binary_prefix = Arg::new("network_use_binary_prefix")
|
||||
.long("network_use_binary_prefix")
|
||||
.action(ArgAction::SetTrue)
|
||||
.help("Displays the network widget with binary prefixes.")
|
||||
.long_help(
|
||||
"Displays the network widget with binary prefixes (e.g. kibibits, mebibits) rather than a decimal \
|
||||
#[arg(
|
||||
long,
|
||||
action = ArgAction::SetTrue,
|
||||
help = "Displays the network widget with binary prefixes.",
|
||||
long_help = "Displays the network widget with binary prefixes (e.g. kibibits, mebibits) rather than a decimal \
|
||||
prefixes (e.g. kilobits, megabits). Defaults to decimal prefixes."
|
||||
);
|
||||
)]
|
||||
pub network_use_binary_prefix: bool,
|
||||
|
||||
let network_use_log = Arg::new("network_use_log")
|
||||
.long("network_use_log")
|
||||
.action(ArgAction::SetTrue)
|
||||
.help("Displays the network widget with a log scale.")
|
||||
.long_help("Displays the network widget with a log scale. Defaults to a non-log scale.");
|
||||
#[arg(
|
||||
long,
|
||||
action = ArgAction::SetTrue,
|
||||
help = "Displays the network widget with a log scale.",
|
||||
long_help = "Displays the network widget with a log scale. Defaults to a non-log scale."
|
||||
)]
|
||||
pub network_use_log: bool,
|
||||
|
||||
// TODO: Change this to be configured as network graph type?
|
||||
let use_old_network_legend = Arg::new("use_old_network_legend")
|
||||
.long("use_old_network_legend")
|
||||
.action(ArgAction::SetTrue)
|
||||
.help("(DEPRECATED) Uses a separated network legend.")
|
||||
.long_help("(DEPRECATED) Uses separated network widget legend. This display is not tested and may be broken.");
|
||||
|
||||
cmd.args(args![
|
||||
network_legend,
|
||||
network_use_bytes,
|
||||
network_use_log,
|
||||
network_use_binary_prefix,
|
||||
use_old_network_legend,
|
||||
])
|
||||
#[arg(
|
||||
long,
|
||||
action = ArgAction::SetTrue,
|
||||
help = "(DEPRECATED) Uses a separate network legend.",
|
||||
long_help = "(DEPRECATED) Uses separate network widget legend. This display is not tested and may be broken."
|
||||
)]
|
||||
pub use_old_network_legend: bool,
|
||||
}
|
||||
|
||||
/// Battery arguments/config options.
|
||||
#[cfg(feature = "battery")]
|
||||
fn battery_args(cmd: Command) -> Command {
|
||||
let cmd = cmd.next_help_heading("Battery Options");
|
||||
|
||||
let battery = Arg::new("battery")
|
||||
.long("battery")
|
||||
.action(ArgAction::SetTrue)
|
||||
.help("Shows the battery widget in non-custom layouts.")
|
||||
.long_help(
|
||||
"Shows the battery widget in default or basic mode, if there is as battery available. This \
|
||||
#[derive(Args, Clone, Debug, Default)]
|
||||
#[command(next_help_heading = "Battery Options", rename_all = "snake_case")]
|
||||
pub struct BatteryArgs {
|
||||
#[arg(
|
||||
long,
|
||||
action = ArgAction::SetTrue,
|
||||
help = "Shows the battery widget in non-custom layouts.",
|
||||
long_help = "Shows the battery widget in default or basic mode, if there is as battery available. This \
|
||||
has no effect on custom layouts; if the battery widget is desired for a custom layout, explicitly \
|
||||
specify it."
|
||||
);
|
||||
|
||||
cmd.arg(battery)
|
||||
)]
|
||||
pub battery: bool,
|
||||
}
|
||||
|
||||
/// GPU arguments/config options.
|
||||
#[cfg(feature = "gpu")]
|
||||
fn gpu_args(cmd: Command) -> Command {
|
||||
let cmd = cmd.next_help_heading("GPU Options");
|
||||
|
||||
let enable_gpu = Arg::new("enable_gpu")
|
||||
.long("enable_gpu")
|
||||
.action(ArgAction::SetTrue)
|
||||
.help("Enable collecting and displaying GPU usage.");
|
||||
|
||||
cmd.arg(enable_gpu)
|
||||
#[derive(Args, Clone, Debug, Default)]
|
||||
#[command(next_help_heading = "GPU Options", rename_all = "snake_case")]
|
||||
pub struct GpuArgs {
|
||||
#[arg(long, action = ArgAction::SetTrue, help = "Enable collecting and displaying GPU usage.")]
|
||||
pub enable_gpu: bool,
|
||||
}
|
||||
|
||||
fn style_args(cmd: Command) -> Command {
|
||||
let cmd = cmd.next_help_heading("Style Options");
|
||||
|
||||
// TODO: File an issue with manpage, it cannot render charts correctly.
|
||||
let color = Arg::new("color")
|
||||
.long("color")
|
||||
.action(ArgAction::Set)
|
||||
.value_name("SCHEME")
|
||||
.value_parser([
|
||||
/// Style arguments/config options.
|
||||
#[derive(Args, Clone, Debug, Default)]
|
||||
#[command(next_help_heading = "Style Options", rename_all = "snake_case")]
|
||||
pub struct StyleArgs {
|
||||
#[arg(
|
||||
long,
|
||||
value_name = "SCHEME",
|
||||
value_parser = [
|
||||
"default",
|
||||
"default-light",
|
||||
"gruvbox",
|
||||
"gruvbox-light",
|
||||
"nord",
|
||||
"nord-light",
|
||||
])
|
||||
.hide_possible_values(true)
|
||||
.help(indoc! {
|
||||
],
|
||||
hide_possible_values = true,
|
||||
help = indoc! {
|
||||
"Use a color scheme, use `--help` for info on the colors. [possible values: default, default-light, gruvbox, gruvbox-light, nord, nord-light]",
|
||||
})
|
||||
.long_help(indoc! {
|
||||
},
|
||||
long_help = indoc! {
|
||||
"Use a pre-defined color scheme. Currently supported values are:
|
||||
- default
|
||||
- default-light (default but adjusted for lighter backgrounds)
|
||||
@ -513,72 +552,31 @@ fn style_args(cmd: Command) -> Command {
|
||||
- gruvbox-light (gruvbox but adjusted for lighter backgrounds)
|
||||
- nord (an arctic, north-bluish color palette)
|
||||
- nord-light (nord but adjusted for lighter backgrounds)"
|
||||
});
|
||||
|
||||
cmd.arg(color)
|
||||
}
|
||||
)]
|
||||
pub color: Option<String>,
|
||||
}
|
||||
|
||||
fn other_args(cmd: Command) -> Command {
|
||||
let cmd = cmd.next_help_heading("Other Options");
|
||||
/// Other arguments. This just handle options that are for help/version displaying.
|
||||
#[derive(Args, Clone, Debug)]
|
||||
#[command(next_help_heading = "Other Options", rename_all = "snake_case")]
|
||||
pub struct OtherArgs {
|
||||
#[arg(short = 'h', long, action = ArgAction::Help, help = "Prints help info (for more details use `--help`.")]
|
||||
help: (),
|
||||
|
||||
let help = Arg::new("help")
|
||||
.short('h')
|
||||
.long("help")
|
||||
.action(ArgAction::Help)
|
||||
.help("Prints help info (for more details use `--help`.");
|
||||
|
||||
let version = Arg::new("version")
|
||||
.short('V')
|
||||
.long("version")
|
||||
.action(ArgAction::Version)
|
||||
.help("Prints version information.");
|
||||
|
||||
cmd.args([help, version])
|
||||
#[arg(short = 'v', long, action = ArgAction::Version, help = "Prints version information.")]
|
||||
version: (),
|
||||
}
|
||||
|
||||
pub fn build_app() -> Command {
|
||||
const TEMPLATE: &str = indoc! {
|
||||
"{name} {version}
|
||||
{author}
|
||||
/// Returns a [`BottomArgs`].
|
||||
pub fn get_args() -> BottomArgs {
|
||||
BottomArgs::parse()
|
||||
}
|
||||
|
||||
{about}
|
||||
|
||||
{usage-heading} {usage}
|
||||
|
||||
{all-args}"
|
||||
};
|
||||
const USAGE: &str = "btm [OPTIONS]";
|
||||
const VERSION: &str = match option_env!("NIGHTLY_VERSION") {
|
||||
Some(nightly_version) => nightly_version,
|
||||
None => crate_version!(),
|
||||
};
|
||||
|
||||
let cmd = Command::new(crate_name!())
|
||||
.author(crate_authors!())
|
||||
.about(crate_description!())
|
||||
.disable_help_flag(true)
|
||||
.disable_version_flag(true)
|
||||
.color(ColorChoice::Auto)
|
||||
.help_template(TEMPLATE)
|
||||
.override_usage(USAGE)
|
||||
.version(VERSION);
|
||||
|
||||
[
|
||||
general_args,
|
||||
process_args,
|
||||
temperature_args,
|
||||
cpu_args,
|
||||
mem_args,
|
||||
network_args,
|
||||
#[cfg(feature = "battery")]
|
||||
battery_args,
|
||||
#[cfg(feature = "gpu")]
|
||||
gpu_args,
|
||||
style_args,
|
||||
other_args,
|
||||
]
|
||||
.into_iter()
|
||||
.fold(cmd, |c, f| f(c))
|
||||
/// Returns an [`Command`] based off of [`BottomArgs`].
|
||||
#[cfg(test)]
|
||||
pub(crate) fn build_cmd() -> Command {
|
||||
BottomArgs::command()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
@ -587,13 +585,13 @@ mod test {
|
||||
|
||||
#[test]
|
||||
fn verify_cli() {
|
||||
build_app().debug_assert();
|
||||
build_cmd().debug_assert();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn no_default_help_heading() {
|
||||
let mut app = build_app();
|
||||
let help_str = app.render_help();
|
||||
let mut cmd = build_cmd();
|
||||
let help_str = cmd.render_help();
|
||||
|
||||
assert!(
|
||||
!help_str.to_string().contains("\nOptions:\n"),
|
||||
|
@ -10,7 +10,7 @@ use self::{cpu::CpuConfig, layout::Row, process_columns::ProcessConfig};
|
||||
use super::ConfigColours;
|
||||
|
||||
#[derive(Clone, Debug, Default, Deserialize)]
|
||||
pub struct Config {
|
||||
pub struct ConfigV1 {
|
||||
pub(crate) flags: Option<ConfigFlags>,
|
||||
pub(crate) colors: Option<ConfigColours>,
|
||||
pub(crate) row: Option<Vec<Row>>,
|
||||
@ -71,7 +71,7 @@ pub(crate) struct ConfigFlags {
|
||||
pub(crate) memory_legend: Option<String>,
|
||||
/// For built-in colour palettes.
|
||||
pub(crate) color: Option<String>,
|
||||
pub(crate) mem_as_value: Option<bool>,
|
||||
pub(crate) process_memory_as_value: Option<bool>,
|
||||
pub(crate) tree: Option<bool>,
|
||||
pub(crate) show_table_scroll_position: Option<bool>,
|
||||
pub(crate) process_command: Option<bool>,
|
||||
|
@ -239,7 +239,7 @@ mod test {
|
||||
use super::*;
|
||||
use crate::{
|
||||
constants::{DEFAULT_LAYOUT, DEFAULT_WIDGET_ID},
|
||||
options::Config,
|
||||
options::ConfigV1,
|
||||
utils::error,
|
||||
};
|
||||
|
||||
@ -293,7 +293,7 @@ mod test {
|
||||
#[test]
|
||||
/// Tests the default setup.
|
||||
fn test_default_movement() {
|
||||
let rows = from_str::<Config>(DEFAULT_LAYOUT).unwrap().row.unwrap();
|
||||
let rows = from_str::<ConfigV1>(DEFAULT_LAYOUT).unwrap().row.unwrap();
|
||||
let ret_bottom_layout = test_create_layout(&rows, DEFAULT_WIDGET_ID, None, 1, false);
|
||||
|
||||
// Simple tests for the top CPU widget
|
||||
@ -367,7 +367,7 @@ mod test {
|
||||
fn test_default_battery_movement() {
|
||||
use crate::constants::DEFAULT_BATTERY_LAYOUT;
|
||||
|
||||
let rows = from_str::<Config>(DEFAULT_BATTERY_LAYOUT)
|
||||
let rows = from_str::<ConfigV1>(DEFAULT_BATTERY_LAYOUT)
|
||||
.unwrap()
|
||||
.row
|
||||
.unwrap();
|
||||
@ -413,7 +413,7 @@ mod test {
|
||||
#[test]
|
||||
/// Tests using cpu_left_legend.
|
||||
fn test_cpu_left_legend() {
|
||||
let rows = from_str::<Config>(DEFAULT_LAYOUT).unwrap().row.unwrap();
|
||||
let rows = from_str::<ConfigV1>(DEFAULT_LAYOUT).unwrap().row.unwrap();
|
||||
let ret_bottom_layout = test_create_layout(&rows, DEFAULT_WIDGET_ID, None, 1, true);
|
||||
|
||||
// Legend
|
||||
@ -473,7 +473,7 @@ mod test {
|
||||
type="proc"
|
||||
"#;
|
||||
|
||||
let rows = from_str::<Config>(proc_layout).unwrap().row.unwrap();
|
||||
let rows = from_str::<ConfigV1>(proc_layout).unwrap().row.unwrap();
|
||||
let mut iter_id = 0; // A lazy way of forcing unique IDs *shrugs*
|
||||
let mut total_height_ratio = 0;
|
||||
let mut default_widget_count = 1;
|
||||
@ -506,7 +506,7 @@ mod test {
|
||||
#[test]
|
||||
/// Tests default widget by setting type and count.
|
||||
fn test_default_widget_by_option() {
|
||||
let rows = from_str::<Config>(PROC_LAYOUT).unwrap().row.unwrap();
|
||||
let rows = from_str::<ConfigV1>(PROC_LAYOUT).unwrap().row.unwrap();
|
||||
let mut iter_id = 0; // A lazy way of forcing unique IDs *shrugs*
|
||||
let mut total_height_ratio = 0;
|
||||
let mut default_widget_count = 3;
|
||||
@ -538,7 +538,7 @@ mod test {
|
||||
|
||||
#[test]
|
||||
fn test_proc_custom_layout() {
|
||||
let rows = from_str::<Config>(PROC_LAYOUT).unwrap().row.unwrap();
|
||||
let rows = from_str::<ConfigV1>(PROC_LAYOUT).unwrap().row.unwrap();
|
||||
let ret_bottom_layout = test_create_layout(&rows, DEFAULT_WIDGET_ID, None, 1, false);
|
||||
|
||||
// First proc widget
|
||||
|
@ -17,21 +17,22 @@ pub enum BottomError {
|
||||
/// An error to represent generic errors.
|
||||
#[error("Error, {0}")]
|
||||
GenericError(String),
|
||||
#[cfg(feature = "fern")]
|
||||
/// An error to represent errors with fern.
|
||||
#[error("Fern error, {0}")]
|
||||
FernError(String),
|
||||
/// An error to represent invalid command-line arguments.
|
||||
#[error("Invalid argument, {0}")]
|
||||
ArgumentError(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.
|
||||
/// An error to represent errors with a query.
|
||||
#[error("Query error, {0}")]
|
||||
QueryError(Cow<'static, str>),
|
||||
/// An error that just signifies something minor went wrong; no message.
|
||||
#[error("Minor error.")]
|
||||
MinorError,
|
||||
#[error("Error casting integers {0}")]
|
||||
TryFromIntError(#[from] std::num::TryFromIntError),
|
||||
}
|
||||
|
@ -255,7 +255,7 @@ impl ProcWidgetState {
|
||||
|
||||
let is_count = matches!(mode, ProcWidgetMode::Grouped);
|
||||
let is_command = table_config.is_command;
|
||||
let mem_vals = table_config.show_memory_as_values;
|
||||
let mem_as_values = table_config.show_memory_as_values;
|
||||
|
||||
match config_columns {
|
||||
Some(columns) if !columns.is_empty() => columns
|
||||
@ -278,7 +278,7 @@ impl ProcWidgetState {
|
||||
}
|
||||
ProcWidgetColumn::Cpu => CpuPercent,
|
||||
ProcWidgetColumn::Mem => {
|
||||
if mem_vals {
|
||||
if mem_as_values {
|
||||
MemoryVal
|
||||
} else {
|
||||
MemoryPercent
|
||||
@ -293,7 +293,7 @@ impl ProcWidgetState {
|
||||
ProcWidgetColumn::Time => Time,
|
||||
#[cfg(feature = "gpu")]
|
||||
ProcWidgetColumn::GpuMem => {
|
||||
if mem_vals {
|
||||
if mem_as_values {
|
||||
GpuMem
|
||||
} else {
|
||||
GpuMemPercent
|
||||
@ -311,7 +311,11 @@ impl ProcWidgetState {
|
||||
if is_count { Count } else { Pid },
|
||||
if is_command { Command } else { Name },
|
||||
CpuPercent,
|
||||
if mem_vals { MemoryVal } else { MemoryPercent },
|
||||
if mem_as_values {
|
||||
MemoryVal
|
||||
} else {
|
||||
MemoryPercent
|
||||
},
|
||||
ReadPerSecond,
|
||||
WritePerSecond,
|
||||
TotalRead,
|
||||
|
@ -24,7 +24,9 @@ fn test_large_default_time() {
|
||||
.arg("18446744073709551616")
|
||||
.assert()
|
||||
.failure()
|
||||
.stderr(predicate::str::contains("could not parse"));
|
||||
.stderr(predicate::str::contains(
|
||||
"set your default time to be valid",
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -35,7 +37,7 @@ fn test_small_default_time() {
|
||||
.assert()
|
||||
.failure()
|
||||
.stderr(predicate::str::contains(
|
||||
"set your default value to be at least",
|
||||
"set your default time to be at least",
|
||||
));
|
||||
}
|
||||
|
||||
@ -46,7 +48,7 @@ fn test_large_delta_time() {
|
||||
.arg("18446744073709551616")
|
||||
.assert()
|
||||
.failure()
|
||||
.stderr(predicate::str::contains("could not parse"));
|
||||
.stderr(predicate::str::contains("set your time delta to be valid"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -68,7 +70,7 @@ fn test_large_rate() {
|
||||
.arg("18446744073709551616")
|
||||
.assert()
|
||||
.failure()
|
||||
.stderr(predicate::str::contains("could not parse"));
|
||||
.stderr(predicate::str::contains("set your update rate"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -89,7 +91,7 @@ fn test_invalid_rate() {
|
||||
.arg("100-1000")
|
||||
.assert()
|
||||
.failure()
|
||||
.stderr(predicate::str::contains("could not parse"));
|
||||
.stderr(predicate::str::contains("set your update rate"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -121,9 +123,7 @@ fn test_invalid_default_widget_2() {
|
||||
.arg("18446744073709551616")
|
||||
.assert()
|
||||
.failure()
|
||||
.stderr(predicate::str::contains(
|
||||
"set your widget count to be at most unsigned INT_MAX",
|
||||
));
|
||||
.stderr(predicate::str::contains("number too large"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
Loading…
Reference in New Issue
Block a user