Try macros

This commit is contained in:
ClementTsang 2024-01-10 23:07:42 -05:00
parent baa7167e80
commit 93c7306f8a
No known key found for this signature in database
GPG Key ID: DC3B7867D8D97095
2 changed files with 86 additions and 135 deletions

View File

@ -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")]
{

View File

@ -78,10 +78,6 @@ Supported widget names:
}
};
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
@ -118,10 +114,25 @@ trait CommandBuilder {
fn add_args(self) -> Self;
}
macro_rules! load_args {
( $cmd:expr, $heading:expr, $arg:expr $(,)?) => {
$cmd.arg($arg.help_heading($heading))
};
( $cmd:expr, $heading:expr, $( $arg:expr ),+ $(,)? ) => {
{
let mut args = [
$(
$arg.help_heading($heading),
)*
];
args.sort_unstable_by(sort_args);
$cmd.args(args)
}
};
}
impl CommandBuilder for Command {
fn general_args(self) -> Command {
const HEADING: &str = "General Options";
let autohide_time = Arg::new("autohide_time")
.long("autohide_time")
.action(ArgAction::SetTrue)
@ -130,8 +141,7 @@ impl CommandBuilder for Command {
"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.",
)
.help_heading(HEADING);
);
let basic = Arg::new("basic")
.short('b')
@ -140,45 +150,39 @@ impl CommandBuilder for Command {
.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.",
)
.help_heading(HEADING);
);
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 the program.")
.help_heading(HEADING);
.long_help("Disables mouse clicks from interacting with the program.");
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.")
.help_heading(HEADING);
.long_help("Uses a dot marker for graphs as opposed to the default braille marker.");
let hide_table_gap = Arg::new("hide_table_gap")
.long("hide_table_gap")
.action(ArgAction::SetTrue)
.help("Hides spacing between table headers and entries.")
.long_help("Hides the spacing between table headers and entries.")
.help_heading(HEADING);
.long_help("Hides the spacing between table headers and entries.");
let hide_time = Arg::new("hide_time")
.long("hide_time")
.action(ArgAction::SetTrue)
.help("Hides the time scale.")
.long_help("Completely hides the time scale from being shown.")
.help_heading(HEADING);
.long_help("Completely hides the time scale from being shown.");
let left_legend = Arg::new("left_legend")
.short('l')
.long("left_legend")
.action(ArgAction::SetTrue)
.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.")
.help_heading(HEADING);
.long_help("Puts the CPU chart legend to the left side rather than the right side.");
let show_table_scroll_position = Arg::new("show_table_scroll_position")
.long("show_table_scroll_position")
@ -186,8 +190,7 @@ impl CommandBuilder for Command {
.help("Shows the scroll position tracker in table widgets.")
.long_help(
"Shows the list scroll position tracker in the widget title for table widgets.",
)
.help_heading(HEADING);
);
let config_location = Arg::new("config_location")
.short('C')
@ -199,8 +202,7 @@ impl CommandBuilder for Command {
"Sets the location of the config file. Expects a config file in the TOML format. \
If it doesn't exist, one is created.",
)
.value_hint(ValueHint::AnyPath)
.help_heading(HEADING);
.value_hint(ValueHint::AnyPath);
let default_time_value = Arg::new("default_time_value")
.short('t')
@ -210,8 +212,7 @@ impl CommandBuilder for Command {
.help("Default time value for graphs.")
.long_help(
"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.",
)
.help_heading(HEADING);
);
// TODO: Charts are broken in the manpage
let default_widget_count = Arg::new("default_widget_count")
@ -238,24 +239,21 @@ And we set our default widget type to 'CPU'. If we set
the default widget. If we set '--default_widget_count 3', it would
use CPU (3) as the default instead.
",
)
.help_heading(HEADING);
);
let default_widget_type = Arg::new("default_widget_type")
.long("default_widget_type")
.action(ArgAction::Set)
.value_name("WIDGET TYPE")
.help("Sets the default widget type, use --help for info.")
.long_help(DEFAULT_WIDGET_TYPE_STR)
.help_heading(HEADING);
.long_help(DEFAULT_WIDGET_TYPE_STR);
let expanded_on_startup = Arg::new("expanded_on_startup")
.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. Same as pressing \"e\" inside the app. Use with \"default_widget_type\" and \"default_widget_count\" to select desired expanded widget. This flag has no effect in basic mode (--basic)")
.help_heading(HEADING);
.long_help("Expand the default widget upon starting the app. Same as pressing \"e\" inside the app. Use with \"default_widget_type\" and \"default_widget_count\" to select desired expanded widget. This flag has no effect in basic mode (--basic).");
let rate = Arg::new("rate")
.short('r')
@ -263,8 +261,7 @@ use CPU (3) as the default instead.
.action(ArgAction::Set)
.value_name("TIME")
.help("Sets the data refresh rate.")
.long_help("Sets the data refresh rate. Takes a number in milliseconds or a human duration (e.g. 5s). The minimum is 250ms, and defaults to 1000ms. Smaller values may take more computer resources.")
.help_heading(HEADING);
.long_help("Sets the data refresh rate. Takes a number in milliseconds or a human duration (e.g. 5s). The minimum is 250ms, and defaults to 1000ms. Smaller values may take more computer resources.");
let time_delta = Arg::new("time_delta")
.short('d')
@ -272,18 +269,18 @@ use CPU (3) as the default instead.
.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 milliseconds or a human duration (e.g. 30s). The minimum is 1s, and defaults to 15s.")
.help_heading(HEADING);
.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.");
let retention = Arg::new("retention")
.long("retention")
.action(ArgAction::Set)
.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 duration (e.g. 20m), with a minimum of 1 minute. Note higher values will take up more memory. Defaults to 10 minutes.")
.help_heading(HEADING);
.long_help("How much data is stored at once in terms of time. Takes a number in milliseconds or a human duration (e.g. 20m), with a minimum of 1 minute. Note higher values will take up more memory. Defaults to 10 minutes.");
let mut args = [
load_args!(
self,
"General Options",
autohide_time,
basic,
disable_click,
@ -300,15 +297,10 @@ use CPU (3) as the default instead.
rate,
time_delta,
retention,
];
args.sort_unstable_by(sort_args);
self.args(args)
)
}
fn style_args(self) -> Command {
const HEADING: &str = "Style Options";
// TODO: File an issue with manpage, it cannot render charts correctly.
let color = Arg::new("color")
.long("color")
@ -342,36 +334,30 @@ Use a pre-defined color scheme. Currently supported values are:
+------------------------------------------------------------+
Defaults to \"default\".
",
)
.help_heading(HEADING);
);
self.arg(color)
load_args!(self, "Style Options", color)
}
fn temperature_args(self) -> Command {
const HEADING: &str = "Temperature Options";
let celsius = Arg::new("celsius")
.short('c')
.long("celsius")
.action(ArgAction::SetTrue)
.help("Use Celsius as the temperature unit.")
.long_help("Use Celsius as the temperature unit. This is the default option.")
.help_heading(HEADING);
.long_help("Use Celsius as the temperature unit. This is the default option.");
let fahrenheit = Arg::new("fahrenheit")
.short('f')
.long("fahrenheit")
.action(ArgAction::SetTrue)
.help("Use Fahrenheit as the temperature unit.")
.help_heading(HEADING);
.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.")
.help_heading(HEADING);
.help("Use Kelvin as the temperature unit.");
let temperature_group = ArgGroup::new("TEMPERATURE_TYPE").args([
celsius.get_id(),
@ -379,29 +365,24 @@ Defaults to \"default\".
kelvin.get_id(),
]);
let args = [celsius, fahrenheit, kelvin];
self.args(args).group(temperature_group)
load_args!(self, "Temperature Options", celsius, fahrenheit, kelvin)
.group(temperature_group)
}
fn process_args(self) -> Command {
const HEADING: &str = "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("When searching for a process, enables case sensitivity by default.")
.help_heading(HEADING);
.long_help("When searching for a process, enables case sensitivity by default.");
let current_usage = Arg::new("current_usage")
.short('u')
.long("current_usage")
.action(ArgAction::SetTrue)
.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.")
.help_heading(HEADING);
.long_help("Sets process CPU% usage to be based on the current system CPU% usage rather than total CPU usage.");
let unnormalized_cpu = Arg::new("unnormalized_cpu")
.short('n')
@ -410,38 +391,33 @@ Defaults to \"default\".
.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.",
)
.help_heading(HEADING);
);
let group_processes = Arg::new("group_processes")
.short('g')
.long("group_processes")
.action(ArgAction::SetTrue)
.help("Groups processes with the same name by default.")
.long_help("Groups processes with the same name by default.")
.help_heading(HEADING);
.long_help("Groups processes with the same name by default.");
let process_command = Arg::new("process_command")
.long("process_command")
.action(ArgAction::SetTrue)
.help("Show processes as their commands by default.")
.long_help("Show processes as their commands by default in the process widget.")
.help_heading(HEADING);
.long_help("Show processes as their commands by default in the process widget.");
let regex = Arg::new("regex")
.short('R')
.long("regex")
.action(ArgAction::SetTrue)
.help("Enables regex by default.")
.long_help("When searching for a process, enables regex by default.")
.help_heading(HEADING);
.long_help("When searching for a process, enables regex by default.");
let disable_advanced_kill = Arg::new("disable_advanced_kill")
.long("disable_advanced_kill")
.action(ArgAction::SetTrue)
.help("Hides advanced process killing.")
.long_help("Hides advanced options to stop a process on Unix-like systems. The only option shown is 15 (TERM).")
.help_heading(HEADING);
.long_help("Hides advanced options to stop a process on Unix-like systems. The only option shown is 15 (TERM).");
let whole_word = Arg::new("whole_word")
.short('W')
@ -450,18 +426,18 @@ Defaults to \"default\".
.help("Enables whole-word matching by default.")
.long_help(
"When searching for a process, return results that match the entire query by default.",
)
.help_heading(HEADING);
);
let tree = Arg::new("tree")
.short('T')
.long("tree")
.action(ArgAction::SetTrue)
.help("Defaults the process widget be in tree mode.")
.long_help("Defaults to showing the process widget in tree mode.")
.help_heading(HEADING);
.long_help("Defaults to showing the process widget in tree mode.");
let mut args = [
load_args!(
self,
"Style Options",
case_sensitive,
current_usage,
unnormalized_cpu,
@ -471,60 +447,45 @@ Defaults to \"default\".
whole_word,
disable_advanced_kill,
tree,
];
args.sort_unstable_by(sort_args);
self.args(args)
)
}
fn cpu_args(self) -> Command {
const HEADING: &str = "CPU Options";
let hide_avg_cpu = Arg::new("hide_avg_cpu")
.short('a')
.long("hide_avg_cpu")
.action(ArgAction::SetTrue)
.help("Hides the average CPU usage.")
.long_help("Hides the average CPU usage from being shown.")
.help_heading(HEADING);
.long_help("Hides the average CPU usage from being shown.");
// let default_avg_cpu = Arg::new("");
let mut args = [hide_avg_cpu];
args.sort_unstable_by(sort_args);
self.args(args)
load_args!(self, "CPU Options", hide_avg_cpu)
}
fn mem_args(self) -> Command {
const HEADING: &str = "Memory Options";
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.")
.help_heading(HEADING);
.long_help("Defaults to showing process memory usage by value. Otherwise, it defaults to showing it by percentage.");
#[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.")
.help_heading(HEADING);
.help("Enable collecting and displaying cache and buffer memory.");
self.args([mem_as_value, enable_cache_memory])
load_args!(self, "Memory Options", mem_as_value, enable_cache_memory)
}
#[cfg(target_os = "windows")]
{
self.arg(mem_as_value)
load_args!(self, "Memory Options", mem_as_value)
}
}
fn network_args(self) -> Command {
const HEADING: &str = "Network Options";
let use_old_network_legend = Arg::new("use_old_network_legend")
.long("use_old_network_legend")
.action(ArgAction::SetTrue)
@ -532,22 +493,21 @@ Defaults to \"default\".
.long_help(
"DEPRECATED - uses an older (pre-0.4), separate network widget legend. This display is not \
tested anymore and could be broken.",
)
.help_heading(HEADING);
);
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.")
.help_heading(HEADING);
.long_help("Displays the network widget using bytes. Defaults to bits.");
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.")
.help_heading(HEADING);
.long_help(
"Displays the network widget with a log scale. Defaults to a non-log scale.",
);
let network_use_binary_prefix = Arg::new("network_use_binary_prefix")
.long("network_use_binary_prefix")
@ -555,34 +515,30 @@ Defaults to \"default\".
.help("Displays the network widget with binary prefixes.")
.long_help(
"Displays the network widget with binary prefixes (i.e. kibibits, mebibits) rather than a decimal prefix (i.e. kilobits, megabits). Defaults to decimal prefixes.",
)
.help_heading(HEADING);
);
let mut args = [
load_args!(
self,
"Network Options",
use_old_network_legend,
network_use_bytes,
network_use_log,
network_use_binary_prefix,
];
args.sort_unstable_by(sort_args);
self.args(args)
)
}
fn battery_args(self) -> Command {
#[cfg(feature = "battery")]
{
let battery = Arg::new("battery")
.long("battery")
.action(ArgAction::SetTrue)
.help("Shows the battery widget.")
.long_help(
"Shows the battery widget in default or basic mode. No effect on custom layouts.",
)
.help_heading("Battery Options");
.long("battery")
.action(ArgAction::SetTrue)
.help("Shows the battery widget.")
.long_help(
"Shows the battery widget in default or basic mode. No effect on custom layouts.",
);
self.arg(battery)
load_args!(self, "Battery Options", battery)
}
#[cfg(not(feature = "battery"))]
{
@ -596,10 +552,9 @@ Defaults to \"default\".
let enable_gpu = Arg::new("enable_gpu")
.long("enable_gpu")
.action(ArgAction::SetTrue)
.help("Enable collecting and displaying GPU usage.")
.help_heading("GPU Options");
.help("Enable collecting and displaying GPU usage.");
self.arg(enable_gpu)
load_args!(self, "GPU Options", enable_gpu)
}
#[cfg(not(feature = "gpu"))]
{
@ -608,23 +563,19 @@ Defaults to \"default\".
}
fn other(self) -> Command {
const HEADING: &str = "Other Options";
let help = Arg::new("help")
.short('h')
.long("help")
.action(ArgAction::Help)
.help("Prints help (see more with '--help').")
.help_heading(HEADING);
.help("Prints help (see more with '--help').");
let version = Arg::new("version")
.short('V')
.long("version")
.action(ArgAction::Version)
.help("Prints version information.")
.help_heading(HEADING);
.help("Prints version information.");
self.args([help, version])
load_args!(self, "Other Options", help, version)
}
fn add_args(self) -> Self {