diff options
author | ClementTsang <34804052+ClementTsang@users.noreply.github.com> | 2024-01-25 02:53:21 -0500 |
---|---|---|
committer | ClementTsang <34804052+ClementTsang@users.noreply.github.com> | 2024-02-19 20:08:53 -0500 |
commit | 155d8a37e9facc3531ff9436be9ef504a0ad6a85 (patch) | |
tree | 64f229c987346d226a243b2309096ba32df9c48d | |
parent | 555ef9fd642afbb615706365c43c49ffb6d003fe (diff) |
switch to using derive-based args
-rw-r--r-- | Cargo.lock | 13 | ||||
-rw-r--r-- | Cargo.toml | 5 | ||||
-rw-r--r-- | src/bin/main.rs | 2 | ||||
-rw-r--r-- | src/options/args.rs | 496 | ||||
-rw-r--r-- | src/options/config/cpu.rs | 3 | ||||
-rw-r--r-- | src/options/config/general.rs | 32 | ||||
-rw-r--r-- | src/options/config/network.rs | 12 | ||||
-rw-r--r-- | src/options/config/process.rs | 29 | ||||
-rw-r--r-- | src/options/config/style.rs | 2 | ||||
-rw-r--r-- | src/options/config/style/colours.rs | 1 |
10 files changed, 545 insertions, 50 deletions
@@ -255,6 +255,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "80c21025abd42669a92efc996ef13cfb2c5c627858421ea58d5c3b331a6c134f" dependencies = [ "clap_builder", + "clap_derive", ] [[package]] @@ -300,6 +301,18 @@ dependencies = [ ] [[package]] +name = "clap_derive" +version = "4.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "307bc0538d5f0f83b8248db3087aa92fe504e4691294d0c96c0eabc33f47ba47" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn 2.0.48", +] + +[[package]] name = "clap_lex" version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -72,10 +72,11 @@ deploy = ["battery", "gpu", "zfs"] default = ["deploy"] [dependencies] + anyhow = "1.0.79" backtrace = "0.3.69" cfg-if = "1.0.0" -clap = { version = "4.5.0", features = ["default", "cargo", "wrap_help"] } +clap = { version = "4.5.0", features = ["default", "cargo", "wrap_help", "derive"] } concat-string = "1.0.1" crossterm = "0.27.0" ctrlc = { version = "3.4.2", features = ["termination"] } @@ -136,7 +137,7 @@ predicates = "3.1.0" portable-pty = "0.8.1" [build-dependencies] -clap = { version = "4.5.0", features = ["default", "cargo", "wrap_help"] } +clap = { version = "4.5.0", features = ["default", "cargo", "wrap_help", "derive"] } clap_complete = "4.5.0" clap_complete_fig = "4.5.0" clap_complete_nushell = "4.5.0" diff --git a/src/bin/main.rs b/src/bin/main.rs index 0ee3289f..64d2a526 100644 --- a/src/bin/main.rs +++ b/src/bin/main.rs @@ -37,7 +37,7 @@ use tui::{backend::CrosstermBackend, Terminal}; fn main() -> Result<()> { // let _profiler = dhat::Profiler::new_heap(); - let matches = args::get_matches(); + let matches = args::build_app().get_matches(); #[cfg(feature = "logging")] { diff --git a/src/options/args.rs b/src/options/args.rs index d6c569bb..d2ee7518 100644 --- a/src/options/args.rs +++ b/src/options/args.rs @@ -10,13 +10,9 @@ use std::cmp::Ordering; use clap::*; use indoc::indoc; -pub fn get_matches() -> ArgMatches { - build_app().get_matches() -} - /// Returns an [`Ordering`] for two [`Arg`] values. /// -/// Note this assumes that they both have a _long_ name, and will +/// Note this assumes that _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(); @@ -41,6 +37,475 @@ macro_rules! args { }; } +const TEMPLATE: &str = indoc! { + "{name} {version} + {author} + + {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!(), +}; + +/// The arguments for 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(crate) struct Args { + #[command(flatten)] + pub(crate) general_args: GeneralArgs, + + #[command(flatten)] + pub(crate) process_args: ProcessArgs, + + #[command(flatten)] + pub(crate) temperature_args: TemperatureArgs, + + #[command(flatten)] + pub(crate) cpu_args: CpuArgs, + + #[command(flatten)] + pub(crate) mem_args: MemArgs, + + #[command(flatten)] + pub(crate) network_args: NetworkArgs, + + #[cfg(feature = "battery")] + #[command(flatten)] + pub(crate) battery_args: BatteryArgs, + + #[cfg(feature = "gpu")] + #[command(flatten)] + pub(crate) gpu_args: GpuArgs, + + #[command(flatten)] + pub(crate) style_args: StyleArgs, + + #[command(flatten)] + pub(crate) other_args: OtherArgs, +} + +#[derive(Args, Clone, Debug)] +#[command(next_help_heading = "General Options")] +pub(crate) struct GeneralArgs { + #[arg( + long, + 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." + )] + pub(crate) autohide_time: bool, + + #[arg( + short = 'b', + long, + help = "Hides graphs and uses a more basic look.", + long_help = "Hides graphs and uses a more basic look. Design is largely inspired by htop's." + )] + pub(crate) basic: bool, + + #[arg( + short = 'C', + long, + value_name = "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. \ + If it doesn't exist, a default config file is created at the path." + )] + pub(crate) config_location: String, + + #[arg( + short = 't', + long, + value_name = "TIME", + help = "Default time value for graphs.", + long_help = "The default time value for graphs. Takes a number in milliseconds or a human \ + duration (e.g. 60s). The minimum time is 30s, and the default is 60s." + )] + pub(crate) default_time_value: String, + + // TODO: Charts are broken in the manpage + #[arg( + long, + requires_all = ["default_widget_type"], + value_name = "N", + help = "Sets the n'th selected widget type as the default. Use --help for more info.", + 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. + + This reads from left to right, top to bottom. For example, suppose + we have a layout that looks like: + +-------------------+-----------------------+ + | CPU (1) | CPU (2) | + +---------+---------+-------------+---------+ + | Process | CPU (3) | Temperature | CPU (4) | + +---------+---------+-------------+---------+ + + 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(crate) default_widget_count: u32, + + #[arg( + long, + value_name = "WIDGET", + value_parser = [ + "cpu", + "mem", + "net", + "network", + "proc", + "process", + "processes", + "temp", + "temperature", + "disk", + #[cfg(not(feature = "battery"))] + "batt", + #[cfg(not(feature = "battery"))] + "battery", + ], + 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. + + For example, suppose we have a layout that looks like: + +-------------------+-----------------------+ + | CPU (1) | CPU (2) | + +---------+---------+-------------+---------+ + | Process | CPU (3) | Temperature | CPU (4) | + +---------+---------+-------------+---------+ + + Setting '--default_widget_type Temp' will make the temperature widget selected by default." + } + )] + pub(crate) default_widget_type: String, + + #[arg( + long, + help = "Disables mouse clicks.", + long_help = "Disables mouse clicks from interacting with bottom." + )] + pub(crate) disable_click: bool, + + #[arg( + short = 'm', + long, + help = "Uses a dot marker for graphs.", + long_help = "Uses a dot marker for graphs as opposed to the default braille marker." + )] + pub(crate) dot_marker: bool, + + #[arg( + short = 'e', + long, + 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(crate) expanded: bool, + + #[arg(long, help = "Hides spacing between table headers and entries.")] + pub(crate) hide_table_gap: bool, + + #[arg( + long, + help = "Hides the time scale.", + long_help = "Completely hides the time scale from being shown." + )] + pub(crate) hide_time: bool, + + #[arg( + short = 'r', + long, + value_name = "TIME", + help = "Sets how often data is refreshed.", + long_help = "Sets how often data is refreshed. Takes a number in milliseconds or a human-readable duration \ + (e.g. 5s). The minimum is 250ms, and defaults to 1000ms. Smaller values may result in higher \ + system usage by bottom." + )] + pub(crate) rate: String, + + #[arg( + long, + value_name = "TIME", + help = "The timespan of data stored.", + long_help = "How much data is stored at once in terms of time. Takes a number in milliseconds or a \ + human-readable duration (e.g. 20m), with a minimum of 1 minute. Note that higher values \ + will take up more memory. Defaults to 10 minutes." + )] + pub(crate) retention: String, + + #[arg( + long, + help = "Shows the scroll position tracker in table widgets.", + long_help = "Shows the list scroll position tracker in the widget title for table widgets." + )] + pub(crate) show_table_scroll_position: bool, + + #[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-readable duration (e.g. 30s). The minimum is 1s, and defaults to 15s." + )] + pub(crate) time_delta: String, +} + +#[derive(Args, Clone, Debug)] +#[command(next_help_heading = "Process Options")] +pub(crate) struct ProcessArgs { + #[arg( + short = 'S', + long, + help = "Enables case sensitivity by default.", + long_help = "When searching for a process, enables case sensitivity by default." + )] + pub(crate) case_sensitive: bool, + + // TODO: Rename this. + #[arg( + short = 'u', + long, + help = "Sets process CPU% to be based on current CPU%.", + long_help = "Sets process CPU% usage to be based on the current system CPU% usage rather than total CPU usage." + )] + pub(crate) current_usage: bool, + + // TODO: Disable this on Windows? + #[arg( + long, + help = "Hides advanced process killing options.", + long_help = "Hides advanced options to stop a process on Unix-like systems. The only \ + option shown is 15 (TERM)." + )] + pub(crate) disable_advanced_kill: bool, + + #[arg( + short = 'g', + long, + help = "Groups processes with the same name by default." + )] + pub(crate) group_processes: bool, + + #[arg(long, help = "Show processes as their commands by default.")] + pub(crate) process_command: bool, + + #[arg(short = 'R', long, help = "Enables regex by default while searching.")] + pub(crate) regex: bool, + + #[arg( + short = 'T', + long, + help = "Defaults the process widget be in tree mode." + )] + pub(crate) tree: bool, + + #[arg( + short = 'n', + long, + help = "Show process CPU% usage without normalizing over the number of cores.", + long_help = "Shows all process CPU% usage without averaging over the number of CPU cores in the system." + )] + pub(crate) unnormalized_cpu: bool, + + #[arg( + short = 'W', + long, + help = "Enables whole-word matching by default while searching." + )] + pub(crate) whole_word: bool, +} + +#[derive(Args, Clone, Debug)] +#[command(next_help_heading = "Temperature Options")] +#[group(multiple = false)] +pub(crate) struct TemperatureArgs { + #[arg( + short = 'c', + long, + 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(crate) celsius: bool, + + #[arg( + short = 'f', + long, + group = "temperature_unit", + help = "Use Fahrenheit as the temperature unit. Default." + )] + pub(crate) fahrenheit: bool, + + #[arg( + short = 'k', + long, + group = "temperature_unit", + help = "Use Kelvin as the temperature unit." + )] + pub(crate) kelvin: bool, +} + +#[derive(Args, Clone, Debug)] +#[command(next_help_heading = "CPU Options")] +pub(crate) struct CpuArgs { + #[arg( + short = 'a', + long, + help = "Hides the average CPU usage entry.", + long = "Hides the average CPU usage entry from being shown." + )] + pub(crate) hide_avg_cpu: bool, + + // TODO: Maybe rename this or fix this? Should this apply to all "left legends"? + #[arg( + short = 'l', + long, + help = "Puts the CPU chart legend to the left side.", + long_help = "Puts the CPU chart legend to the left side rather than the right side." + )] + pub(crate) left_legend: bool, +} + +#[derive(Args, Clone, Debug)] +#[command(next_help_heading = "Memory Options")] +pub(crate) struct MemArgs { + #[cfg(not(target_os = "windows"))] + #[arg( + long, + help = "Enables collecting and displaying cache and buffer memory." + )] + pub(crate) enable_cache_memory: bool, + + #[arg( + long, + 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(crate) mem_as_value: bool, +} + +#[derive(Args, Clone, Debug)] +#[command(next_help_heading = "Network Options")] +pub(crate) struct NetworkArgs { + #[arg( + long, + help = "Displays the network widget using bytes.", + long_help = "Displays the network widget using bytes. Defaults to bits." + )] + pub(crate) network_use_bytes: bool, + + #[arg( + long, + 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(crate) network_use_binary_prefix: bool, + + #[arg( + long, + 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(crate) network_use_log: bool, + + #[arg( + long, + 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(crate) use_old_network_legend: bool, +} + +#[cfg(feature = "battery")] +#[derive(Args, Clone, Debug)] +#[command(next_help_heading = "Battery Options")] +pub(crate) struct BatteryArgs { + #[arg( + long, + help = "Shows the battery widget in default/basic mode.", + 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." + )] + pub(crate) battery: bool, +} + +#[cfg(feature = "gpu")] +#[derive(Args, Clone, Debug)] +#[command(next_help_heading = "GPU Options")] +pub(crate) struct GpuArgs { + #[arg(long, help = "Enables collecting and displaying GPU usage.")] + pub(crate) enable_gpu: bool, +} + +#[derive(Args, Clone, Debug)] +#[command(next_help_heading = "Style Options")] +pub(crate) struct StyleArgs { + #[arg( + long, + value_name="SCHEME", + value_parser=[ + "default", + "default-light", + "gruvbox", + "gruvbox-light", + "nord", + "nord-light", + + ], + hide_possible_values=true, + help = "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! { + "Use a pre-defined color scheme. Currently supported values are: + - default + - default-light (default but adjusted for lighter backgrounds) + - gruvbox (a bright theme with 'retro groove' colors) + - gruvbox-light (gruvbox but adjusted for lighter backgrounds) + - nord (an arctic, north-bluish color palette) + - nord-light (nord but adjusted for lighter backgrounds)" + } + )] + pub(crate) color: String, +} + +#[derive(Args, Clone, Debug)] +#[command(next_help_heading = "Other Options")] +pub(crate) struct OtherArgs { + #[arg(short='h', long, action=ArgAction::Help, help="Prints help info (for more details use `--help`.")] + help: (), + + #[arg(short='v', long, action=ArgAction::Version, help="Prints version information.")] + version: (), +} + fn general_args(cmd: Command) -> Command { let cmd = cmd.next_help_heading("General Options"); @@ -539,22 +1004,6 @@ fn other_args(cmd: Command) -> Command { } pub fn build_app() -> Command { - const TEMPLATE: &str = indoc! { - "{name} {version} - {author} - - {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!()) @@ -583,6 +1032,11 @@ pub fn build_app() -> Command { .fold(cmd, |c, f| f(c)) } +/// Returns an [`Args`]. +pub fn new_build_app() -> Args { + Args::parse() +} + #[cfg(test)] mod test { use super::*; diff --git a/src/options/config/cpu.rs b/src/options/config/cpu.rs index 07be9632..e6bec93c 100644 --- a/src/options/config/cpu.rs +++ b/src/options/config/cpu.rs @@ -14,7 +14,8 @@ pub enum CpuDefault { #[derive(Clone, Debug, Default, Deserialize)] pub(crate) struct CpuConfig { - pub(crate) hide_avg_cpu: Option<bool>, + #[serde(default)] + pub(crate) hide_avg_cpu: bool, #[serde(default)] pub(crate) default: CpuDefault, } diff --git a/src/options/config/general.rs b/src/options/config/general.rs index 5d3da95a..e39279ed 100644 --- a/src/options/config/general.rs +++ b/src/options/config/general.rs @@ -1,21 +1,35 @@ +use clap::ArgMatches; use serde::Deserialize; use super::StringOrNum; #[derive(Clone, Debug, Default, Deserialize)] pub(crate) struct GeneralConfig { - pub(crate) autohide_time: Option<bool>, - pub(crate) basic: Option<bool>, + #[serde(default)] + pub(crate) autohide_time: bool, + #[serde(default)] + pub(crate) basic: bool, pub(crate) default_time_value: Option<StringOrNum>, pub(crate) default_widget_type: Option<String>, - pub(crate) disable_click: Option<bool>, - pub(crate) dot_marker: Option<bool>, // TODO: Support other markers! - pub(crate) expanded: Option<bool>, - pub(crate) hide_table_gap: Option<bool>, - pub(crate) hide_time: Option<bool>, // TODO: Combine with autohide_time - pub(crate) left_legend: Option<bool>, + #[serde(default)] + pub(crate) disable_click: bool, + #[serde(default)] + pub(crate) dot_marker: bool, // TODO: Support other markers! + #[serde(default)] + pub(crate) expanded: bool, + #[serde(default)] + pub(crate) hide_table_gap: bool, + #[serde(default)] + pub(crate) hide_time: bool, // TODO: Combine with autohide_time + #[serde(default)] + pub(crate) left_legend: bool, pub(crate) rate: Option<StringOrNum>, pub(crate) retention: Option<StringOrNum>, - pub(crate) show_table_scroll_position: Option<bool>, + #[serde(default)] + pub(crate) show_table_scroll_position: bool, pub(crate) time_delta: Option<StringOrNum>, } + +impl GeneralConfig { + pub(crate) fn merge_with_args(&mut self, args: &ArgMatches) {} +} diff --git a/src/options/config/network.rs b/src/options/config/network.rs index 48345469..3eb7bb7b 100644 --- a/src/options/config/network.rs +++ b/src/options/config/network.rs @@ -2,8 +2,12 @@ use serde::Deserialize; #[derive(Clone, Debug, Default, Deserialize)] pub(crate) struct NetworkConfig { - pub(crate) network_use_bytes: Option<bool>, - pub(crate) network_use_log: Option<bool>, - pub(crate) network_use_binary_prefix: Option<bool>, - pub(crate) use_old_network_legend: Option<bool>, + #[serde(default)] + pub(crate) network_use_bytes: bool, + #[serde(default)] + pub(crate) network_use_log: bool, + #[serde(default)] + pub(crate) network_use_binary_prefix: bool, + #[serde(default)] + pub(crate) use_old_network_legend: bool, } diff --git a/src/options/config/process.rs b/src/options/config/process.rs index 0e13f920..b677937a 100644 --- a/src/options/config/process.rs +++ b/src/options/config/process.rs @@ -5,15 +5,24 @@ use crate::widgets::ProcWidgetColumn; /// Process column settings. #[derive(Clone, Debug, Default, Deserialize)] pub(crate) struct ProcessConfig { - pub(crate) case_sensitive: Option<bool>, - pub(crate) current_usage: Option<bool>, - pub(crate) disable_advanced_kill: Option<bool>, - pub(crate) group_processes: Option<bool>, - pub(crate) process_command: Option<bool>, - pub(crate) regex: Option<bool>, - pub(crate) tree: Option<bool>, - pub(crate) unnormalized_cpu: Option<bool>, - pub(crate) whole_word: Option<bool>, + #[serde(default)] + pub(crate) case_sensitive: bool, + #[serde(default)] + pub(crate) current_usage: bool, + #[serde(default)] + pub(crate) disable_advanced_kill: bool, + #[serde(default)] + pub(crate) group_processes: bool, + #[serde(default)] + pub(crate) process_command: bool, + #[serde(default)] + pub(crate) regex: bool, + #[serde(default)] + pub(crate) tree: bool, + #[serde(default)] + pub(crate) unnormalized_cpu: bool, + #[serde(default)] + pub(crate) whole_word: bool, pub(crate) columns: Option<Vec<ProcWidgetColumn>>, } @@ -56,7 +65,7 @@ mod test { #[test] fn process_column_settings_2() { - let config = r#"columns = ["MEM", "TWrite", "Cpuz", "read", "wps"]"#; + let config = r#"columns = ["MEM", "TWrite", "fake", "read", "wps"]"#; toml_edit::de::from_str::<ProcessConfig>(config).expect_err("Should error out!"); } diff --git a/src/options/config/style.rs b/src/options/config/style.rs index e0c10d8a..5c09ea8e 100644 --- a/src/options/config/style.rs +++ b/src/options/config/style.rs @@ -5,5 +5,5 @@ use serde::Deserialize; #[derive(Clone, Debug, Default, Deserialize)] pub(crate) struct StyleConfig { - pub(crate) color: Option<String>, + pub(crate) color: Option<String>, // TODO: parse enum instead? And how should this react with colour schemes? } diff --git a/src/options/config/style/colours.rs b/src/options/config/style/colours.rs index 7068bfdb..04a800d1 100644 --- a/src/options/config/style/colours.rs +++ b/src/options/config/style/colours.rs @@ -9,7 +9,6 @@ pub struct ColourConfig { pub avg_cpu_color: Option<Cow<'static, str>>, pub cpu_core_colors: Option<Vec<Cow<'static, str>>>, pub ram_color: Option<Cow<'static, str>>, - #[cfg(not(target_os = "windows"))] pub cache_color: Option<Cow<'static, str>>, pub swap_color: Option<Cow<'static, str>>, pub arc_color: Option<Cow<'static, str>>, |