feat(cli): enhance duration parsing with humantime
Allow fractional and composite duration inputs for `--duration` and `--interval` options.
This commit is contained in:
7 files changed
+29
-56
No files matched your search
@@ -42,9 +42,9 @@ doc = false
|
||||
|
||||
[features]
|
||||
default = ["client", "server", "tui"]
|
||||
client = ["dep:tokio", "dep:irtt-client", "irtt-client/ancillary", "irtt-client/tokio", "dep:irtt-proto", "dep:irtt-stats"]
|
||||
client = ["dep:humantime", "dep:tokio", "dep:irtt-client", "irtt-client/ancillary", "irtt-client/tokio", "dep:irtt-proto", "dep:irtt-stats"]
|
||||
server = ["dep:irtt-server", "dep:tokio"]
|
||||
tui =["dep:tokio", "dep:futures-core", "dep:irtt-client", "irtt-client/ancillary", "irtt-client/tokio", "dep:irtt-proto", "dep:irtt-stats", "dep:ratatui", "dep:crossterm", "dep:chrono"]
|
||||
tui =["dep:humantime", "dep:tokio", "dep:futures-core", "dep:irtt-client", "irtt-client/ancillary", "irtt-client/tokio", "dep:irtt-proto", "dep:irtt-stats", "dep:ratatui", "dep:crossterm", "dep:chrono"]
|
||||
|
||||
[dependencies]
|
||||
chrono = { version = "0.4.45", default-features = false, features = ["clock"], optional = true }
|
||||
@@ -53,6 +53,7 @@ clap-num = "=1.2.0"
|
||||
crossterm = { version = "0.29.0", features = ["event-stream"], optional = true }
|
||||
futures-core = { version = "0.3.34", default-features = false, optional = true }
|
||||
hex = "0.4.3"
|
||||
humantime = { version = "2.4.0", optional = true }
|
||||
ctrlc = { version = "3.5.2", features = ["termination"] }
|
||||
irtt-client = { workspace = true, optional = true }
|
||||
irtt-proto = { workspace = true, optional = true }
|
||||
|
||||
@@ -40,8 +40,8 @@ pub struct ClientArgs {
|
||||
long,
|
||||
default_value = "10s",
|
||||
value_parser = parse_test_duration,
|
||||
help = "Test duration; use 0 for continuous mode",
|
||||
long_help = "Test duration; use 0 for continuous mode.\n\nFinite runs retain exact statistics for final summaries. Continuous mode uses bounded-memory running statistics and prints a final summary when interrupted; --targets-stdin also prints the final retained summary on EOF."
|
||||
help = "Test duration (e.g. 2.5s, 1m30s, 1h); use 0 for continuous mode",
|
||||
long_help = "Test duration (e.g. 2.5s, 1m30s, 500us, 100ns, 1h); use 0 for continuous mode.\n\nFinite runs retain exact statistics for final summaries. Continuous mode uses bounded-memory running statistics and prints a final summary when interrupted; --targets-stdin also prints the final retained summary on EOF."
|
||||
)]
|
||||
pub duration: Duration,
|
||||
|
||||
|
||||
@@ -35,8 +35,8 @@ pub struct TuiArgs {
|
||||
long,
|
||||
default_value = "0",
|
||||
value_parser = parse_test_duration,
|
||||
help = "Test duration; use 0 for continuous mode",
|
||||
long_help = "Test duration; use 0 for continuous mode. The TUI defaults to continuous mode."
|
||||
help = "Test duration (e.g. 2.5s, 1m30s, 1h); use 0 for continuous mode",
|
||||
long_help = "Test duration (e.g. 2.5s, 1m30s, 500us, 100ns, 1h); use 0 for continuous mode. The TUI defaults to continuous mode."
|
||||
)]
|
||||
pub duration: Duration,
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ pub struct CommonClientArgs {
|
||||
#[arg(long, conflicts_with_all = ["ipv4", "ipv6"], long_help = "Probe available IPv4 and IPv6 paths separately for each hostname. Labels gain /v4 or /v6; IP literals remain single targets. Families are discovered when a target is added; reconnects resolve the original hostname again within the chosen family.")]
|
||||
pub dual_stack: bool,
|
||||
|
||||
/// Probe interval.
|
||||
/// Positive probe interval (e.g. 2.5s, 1m30s, 500us, 100ns, 1h).
|
||||
#[arg(long, default_value = "1s", value_parser = parse_duration)]
|
||||
pub interval: Duration,
|
||||
|
||||
@@ -200,62 +200,18 @@ fn parse_bind_addr(input: &str) -> Result<SocketAddr, String> {
|
||||
}
|
||||
|
||||
pub fn parse_duration(input: &str) -> Result<Duration, String> {
|
||||
let (number, unit) = split_duration(input)?;
|
||||
let value: u64 = number
|
||||
.parse()
|
||||
.map_err(|_| format!("invalid duration value {input:?}"))?;
|
||||
if value == 0 {
|
||||
let duration = parse_test_duration(input)?;
|
||||
if duration.is_zero() {
|
||||
return Err("duration must be greater than zero".to_owned());
|
||||
}
|
||||
match unit {
|
||||
"ms" => Ok(Duration::from_millis(value)),
|
||||
"s" => Ok(Duration::from_secs(value)),
|
||||
"m" => value
|
||||
.checked_mul(60)
|
||||
.map(Duration::from_secs)
|
||||
.ok_or_else(|| "duration is too large".to_owned()),
|
||||
_ => Err(format!(
|
||||
"unsupported duration unit {unit:?}; use ms, s, or m"
|
||||
)),
|
||||
}
|
||||
Ok(duration)
|
||||
}
|
||||
|
||||
pub fn parse_test_duration(input: &str) -> Result<Duration, String> {
|
||||
if input == "0" {
|
||||
return Ok(Duration::ZERO);
|
||||
}
|
||||
let (number, unit) = split_duration(input)?;
|
||||
let value: u64 = number
|
||||
.parse()
|
||||
.map_err(|_| format!("invalid duration value {input:?}"))?;
|
||||
if value == 0 {
|
||||
return Ok(Duration::ZERO);
|
||||
}
|
||||
match unit {
|
||||
"ms" => Ok(Duration::from_millis(value)),
|
||||
"s" => Ok(Duration::from_secs(value)),
|
||||
"m" => value
|
||||
.checked_mul(60)
|
||||
.map(Duration::from_secs)
|
||||
.ok_or_else(|| "duration is too large".to_owned()),
|
||||
_ => Err(format!(
|
||||
"unsupported duration unit {unit:?}; use ms, s, or m"
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
fn split_duration(input: &str) -> Result<(&str, &str), String> {
|
||||
let split = input
|
||||
.find(|ch: char| !ch.is_ascii_digit())
|
||||
.ok_or_else(|| "duration must include a unit: ms, s, or m".to_owned())?;
|
||||
let (number, unit) = input.split_at(split);
|
||||
if number.is_empty() || unit.is_empty() {
|
||||
return Err("duration must include a positive value and unit".to_owned());
|
||||
}
|
||||
if number.starts_with('-') {
|
||||
return Err("duration must be greater than zero".to_owned());
|
||||
}
|
||||
Ok((number, unit))
|
||||
humantime::parse_duration(input).map_err(|error| error.to_string())
|
||||
}
|
||||
|
||||
pub fn parse_length(input: &str) -> Result<u32, String> {
|
||||
|
||||
Reference in new issue
Block a user