diff --git a/CHANGELOG.md b/CHANGELOG.md index 7718cde3..cfc75a10 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,12 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [0.9.2] - Unreleased + +## Features + +- [#1172](https://github.com/ClementTsang/bottom/pull/1172): Support human times for `time_delta` and `default_time_value`. + ## [0.9.1] - 2023-05-14 ## Bug Fixes diff --git a/Cargo.lock b/Cargo.lock index a0c3d88d..1f035890 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -136,7 +136,7 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bottom" -version = "0.9.1" +version = "0.9.2" dependencies = [ "anyhow", "assert_cmd", diff --git a/Cargo.toml b/Cargo.toml index 71141942..f637c690 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "bottom" -version = "0.9.1" +version = "0.9.2" authors = ["Clement Tsang "] edition = "2021" repository = "https://github.com/ClementTsang/bottom" diff --git a/src/options.rs b/src/options.rs index 5644ddcc..367d6233 100644 --- a/src/options.rs +++ b/src/options.rs @@ -57,8 +57,8 @@ pub struct ConfigFlags { pub whole_word: Option, pub regex: Option, pub basic: Option, - pub default_time_value: Option, - pub time_delta: Option, + pub default_time_value: Option, + pub time_delta: Option, pub autohide_time: Option, pub hide_time: Option, pub default_widget_type: Option, @@ -582,20 +582,27 @@ fn get_show_average_cpu(matches: &ArgMatches, config: &Config) -> bool { true } -/// FIXME: Let this accept human times. +fn try_parse_ms(s: &str) -> error::Result { + if let Ok(val) = humantime::parse_duration(s) { + Ok(val.as_millis().try_into()?) + } else if let Ok(val) = s.parse::() { + Ok(val) + } else { + Err(BottomError::ConfigError( + "could not parse as a valid 64-bit unsigned integer or a human time".to_string(), + )) + } +} + fn get_default_time_value( matches: &ArgMatches, config: &Config, retention_ms: u64, ) -> error::Result { let default_time = if let Some(default_time_value) = matches.get_one::("default_time_value") { - default_time_value.parse::().map_err(|_| { - BottomError::ConfigError( - "could not parse as a valid 64-bit unsigned integer".to_string(), - ) - })? + try_parse_ms(default_time_value)? } else if let Some(flags) = &config.flags { - if let Some(default_time_value) = flags.default_time_value { - default_time_value + if let Some(default_time_value) = &flags.default_time_value { + try_parse_ms(default_time_value)? } else { DEFAULT_TIME_MILLISECONDS } @@ -621,14 +628,10 @@ fn get_time_interval( matches: &ArgMatches, config: &Config, retention_ms: u64, ) -> error::Result { let time_interval = if let Some(time_interval) = matches.get_one::("time_delta") { - time_interval.parse::().map_err(|_| { - BottomError::ConfigError( - "could not parse as a valid 64-bit unsigned integer".to_string(), - ) - })? + try_parse_ms(time_interval)? } else if let Some(flags) = &config.flags { - if let Some(time_interval) = flags.time_delta { - time_interval + if let Some(time_interval) = &flags.time_delta { + try_parse_ms(time_interval)? } else { TIME_CHANGE_MILLISECONDS } @@ -869,8 +872,135 @@ fn get_retention_ms(matches: &ArgMatches, config: &Config) -> error::Result mod test { use clap::ArgMatches; - use super::{get_color_scheme, get_widget_layout, Config}; - use crate::{app::App, canvas::canvas_styling::CanvasStyling}; + use super::{get_color_scheme, get_time_interval, get_widget_layout, Config}; + use crate::{ + app::App, + canvas::canvas_styling::CanvasStyling, + options::{get_default_time_value, try_parse_ms, ConfigFlags}, + }; + + #[test] + fn verify_try_parse_ms() { + let a = "100s"; + let b = "100"; + let c = "1 min"; + let d = "1 hour 1 min"; + + assert_eq!(try_parse_ms(a), Ok(100 * 1000)); + assert_eq!(try_parse_ms(b), Ok(100)); + assert_eq!(try_parse_ms(c), Ok(60 * 1000)); + assert_eq!(try_parse_ms(d), Ok(3660 * 1000)); + + let a_bad = "1 test"; + let b_bad = "-100"; + + assert!(try_parse_ms(a_bad).is_err()); + assert!(try_parse_ms(b_bad).is_err()); + } + + #[test] + fn matches_human_times() { + let config = Config::default(); + let app = crate::clap::build_app(); + + { + let app = app.clone(); + let delta_args = vec!["btm", "--time_delta", "2 min"]; + let matches = app.get_matches_from(delta_args); + + assert_eq!( + get_time_interval(&matches, &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); + + assert_eq!( + get_default_time_value(&matches, &config, 60 * 60 * 1000), + Ok(5 * 60 * 1000) + ); + } + } + + #[test] + fn matches_number_times() { + let config = Config::default(); + let app = crate::clap::build_app(); + + { + let app = app.clone(); + let delta_args = vec!["btm", "--time_delta", "120000"]; + let matches = app.get_matches_from(delta_args); + + assert_eq!( + get_time_interval(&matches, &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); + + assert_eq!( + get_default_time_value(&matches, &config, 60 * 60 * 1000), + Ok(5 * 60 * 1000) + ); + } + } + + #[test] + fn config_human_times() { + let app = crate::clap::build_app(); + let matches = app.get_matches_from(["btm"]); + + let mut config = Config::default(); + let flags = ConfigFlags { + time_delta: Some("2 min".to_string()), + default_time_value: Some("300s".to_string()), + ..Default::default() + }; + + config.flags = Some(flags); + + assert_eq!( + get_time_interval(&matches, &config, 60 * 60 * 1000), + Ok(2 * 60 * 1000) + ); + + assert_eq!( + get_default_time_value(&matches, &config, 60 * 60 * 1000), + Ok(5 * 60 * 1000) + ); + } + + #[test] + fn config_number_times() { + let app = crate::clap::build_app(); + let matches = app.get_matches_from(["btm"]); + + let mut config = Config::default(); + let flags = ConfigFlags { + time_delta: Some("120000".to_string()), + default_time_value: Some("300000".to_string()), + ..Default::default() + }; + + config.flags = Some(flags); + + assert_eq!( + get_time_interval(&matches, &config, 60 * 60 * 1000), + Ok(2 * 60 * 1000) + ); + + assert_eq!( + get_default_time_value(&matches, &config, 60 * 60 * 1000), + Ok(5 * 60 * 1000) + ); + } fn create_app(mut config: Config, matches: ArgMatches) -> App { let (layout, id, ty) = get_widget_layout(&matches, &config).unwrap();