diff --git a/CHANGELOG.md b/CHANGELOG.md index b107af42..6906d01e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -66,6 +66,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - The `TypeName` strings defined in `radicle-cob` are restricted to reflect the size limits on domain names as specified in [RFC-1035](https://www.rfc-editor.org/rfc/rfc1035#section-2.3.4). +- The `--timeout` flag, which is available on a number of CLI subcommands, now + takes a human-readable duration as parameter, E.G. "9s" or "1min" instead of + "9" or "60". ## 1.6.1 diff --git a/Cargo.lock b/Cargo.lock index a24bc88e..6cce09d1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1854,6 +1854,12 @@ dependencies = [ "uuid", ] +[[package]] +name = "humantime" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "135b12329e5e3ce057a9f972339ea52bc954fe1e9358ef27f95e89716fbc5424" + [[package]] name = "iana-time-zone" version = "0.1.60" @@ -2939,6 +2945,7 @@ dependencies = [ "clap_complete", "dunce", "human-panic", + "humantime", "itertools", "log", "nonempty", diff --git a/Cargo.toml b/Cargo.toml index 4be2a705..d97126fe 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -33,6 +33,7 @@ git2 = { version = "0.20.4", default-features = false, features = ["vendored-lib gix-hash = { version = "0.22.1", default-features = false, features = ["sha1"] } gix-packetline = { version = "0.21.1", default-features = false } human-panic = "2.0.6" +humantime = "2.3" itertools = "0.14" lexopt = "0.3.0" libc = "0.2.137" diff --git a/crates/radicle-cli/Cargo.toml b/crates/radicle-cli/Cargo.toml index 6012fa43..5164734c 100644 --- a/crates/radicle-cli/Cargo.toml +++ b/crates/radicle-cli/Cargo.toml @@ -20,6 +20,7 @@ clap = { version = "4.5.44", features = ["derive"] } clap_complete = "4.5" dunce = { workspace = true } human-panic.workspace = true +humantime.workspace = true itertools.workspace = true log = { workspace = true, features = ["std"] } nonempty = { workspace = true } diff --git a/crates/radicle-cli/examples/rad-clone-partial-fail.md b/crates/radicle-cli/examples/rad-clone-partial-fail.md index 56f6bed4..18af1cfd 100644 --- a/crates/radicle-cli/examples/rad-clone-partial-fail.md +++ b/crates/radicle-cli/examples/rad-clone-partial-fail.md @@ -15,7 +15,7 @@ When she tries to clone, one of those will fail to fetch. But the clone command still returns successfully. ``` -$ rad clone rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji --timeout 3 +$ rad clone rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji --timeout 3s ✓ Seeding policy updated for rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji with scope 'followed' Fetching rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji from the network, found 3 potential seed(s). ✓ Target met: 1 seed(s) diff --git a/crates/radicle-cli/examples/rad-id-collaboration.md b/crates/radicle-cli/examples/rad-id-collaboration.md index d36892e2..8320df20 100644 --- a/crates/radicle-cli/examples/rad-id-collaboration.md +++ b/crates/radicle-cli/examples/rad-id-collaboration.md @@ -156,7 +156,7 @@ At this point, when Alice runs `rad sync`, she will fetch Eve's fork since she has become a delegate: ``` ~alice -$ rad sync --timeout 3 +$ rad sync --timeout 3s ✓ Fetching rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji from z6MkvVv…Z1Ct4tD@[..].. ✓ Fetching rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji from z6MkuPZ…xEuaPUp@[..].. ✓ Fetched repository from 2 seed(s) diff --git a/crates/radicle-cli/examples/rad-id-multi-delegate.md b/crates/radicle-cli/examples/rad-id-multi-delegate.md index dc2dfff6..4cf1192a 100644 --- a/crates/radicle-cli/examples/rad-id-multi-delegate.md +++ b/crates/radicle-cli/examples/rad-id-multi-delegate.md @@ -4,7 +4,7 @@ $ rad id update --repo rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji --title "Add Bob" --des ``` ``` ~bob -$ rad watch --repo rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji --node z6Mkux1aUQD2voWWukVb5nNUR7thrHveQG4pDQua8nVhib7Z -r 'refs/rad/sigrefs' -t c9a828fc2fb01f893d6e6e9e17b9092dea2b3aba -i 500 --timeout 5000 +$ rad watch --repo rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji --node z6Mkux1aUQD2voWWukVb5nNUR7thrHveQG4pDQua8nVhib7Z -r 'refs/rad/sigrefs' -t c9a828fc2fb01f893d6e6e9e17b9092dea2b3aba -i 500 --timeout 5000ms $ rad sync --fetch rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji Fetching rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji from the network, found 1 potential seed(s). ✓ Target met: 1 seed(s) diff --git a/crates/radicle-cli/examples/rad-init-private-clone-seed.md b/crates/radicle-cli/examples/rad-init-private-clone-seed.md index 7d92732e..17fc6124 100644 --- a/crates/radicle-cli/examples/rad-init-private-clone-seed.md +++ b/crates/radicle-cli/examples/rad-init-private-clone-seed.md @@ -29,7 +29,7 @@ $ rad inspect --identity ``` ~bob $ rad ls --all --private -$ rad clone rad:z2ug5mwNKZB8KGpBDRTrWHAMbvHCu --seed z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi --timeout 1 +$ rad clone rad:z2ug5mwNKZB8KGpBDRTrWHAMbvHCu --seed z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi --timeout 1s ✓ Seeding policy updated for rad:z2ug5mwNKZB8KGpBDRTrWHAMbvHCu with scope 'followed' Fetching rad:z2ug5mwNKZB8KGpBDRTrWHAMbvHCu from the network, found 1 potential seed(s). ✓ Target met: 1 preferred seed(s). @@ -48,7 +48,7 @@ Run `cd ./heartwood` to go to the repository directory. We can also use `rad seed` to seed and fetch without creating a checkout. ``` ~bob -$ rad seed rad:z2ug5mwNKZB8KGpBDRTrWHAMbvHCu --from z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi --timeout 1 +$ rad seed rad:z2ug5mwNKZB8KGpBDRTrWHAMbvHCu --from z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi --timeout 1s ✓ Seeding policy exists for rad:z2ug5mwNKZB8KGpBDRTrWHAMbvHCu with scope 'followed' Fetching rad:z2ug5mwNKZB8KGpBDRTrWHAMbvHCu from the network, found 1 potential seed(s). ✓ Target met: 1 seed(s) diff --git a/crates/radicle-cli/examples/rad-init-private-clone.md b/crates/radicle-cli/examples/rad-init-private-clone.md index bb0c0d77..5f0f2519 100644 --- a/crates/radicle-cli/examples/rad-init-private-clone.md +++ b/crates/radicle-cli/examples/rad-init-private-clone.md @@ -5,7 +5,7 @@ Bob tries to fetch it, and even though he's connected to Alice, it fails. $ rad ls ``` ``` ~bob (fail) -$ rad clone rad:z2ug5mwNKZB8KGpBDRTrWHAMbvHCu --seed z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi --timeout 1 +$ rad clone rad:z2ug5mwNKZB8KGpBDRTrWHAMbvHCu --seed z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi --timeout 1s ✓ Seeding policy updated for rad:z2ug5mwNKZB8KGpBDRTrWHAMbvHCu with scope 'followed' Fetching rad:z2ug5mwNKZB8KGpBDRTrWHAMbvHCu from the network, found 1 potential seed(s). ✗ Target not met: could not fetch from [z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi], and required 1 more seed(s) @@ -20,7 +20,7 @@ the refs. ``` ~alice $ rad id update --title "Allow Bob" --description "" --allow did:key:z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk -q ... -$ rad sync --announce --timeout 3 +$ rad sync --announce --timeout 3s ✓ Synced with 1 seed(s) ``` diff --git a/crates/radicle-cli/src/commands/clone/args.rs b/crates/radicle-cli/src/commands/clone/args.rs index 0f987f07..bebc00d2 100644 --- a/crates/radicle-cli/src/commands/clone/args.rs +++ b/crates/radicle-cli/src/commands/clone/args.rs @@ -1,5 +1,4 @@ use std::path::PathBuf; -use std::time; use clap::Parser; @@ -31,15 +30,17 @@ pub(super) struct SyncArgs { #[arg(short, long = "seed", value_name = "NID", action = clap::ArgAction::Append)] seeds: Vec, - /// Timeout for fetching repository in seconds - #[arg(long, default_value_t = 9, value_name = "SECS")] - timeout: usize, + /// Timeout for fetching repository + /// + /// Valid arguments are for example "10s", "5min" or "2h 37min" + #[arg(long, value_parser = humantime::parse_duration, default_value = "9s")] + timeout: std::time::Duration, } impl From for SyncSettings { fn from(args: SyncArgs) -> Self { SyncSettings { - timeout: time::Duration::from_secs(args.timeout as u64), + timeout: args.timeout, seeds: args.seeds.into_iter().collect(), ..SyncSettings::default() } diff --git a/crates/radicle-cli/src/commands/node.rs b/crates/radicle-cli/src/commands/node.rs index 2d901df3..4e227bf4 100644 --- a/crates/radicle-cli/src/commands/node.rs +++ b/crates/radicle-cli/src/commands/node.rs @@ -29,9 +29,7 @@ pub fn run(args: Args, ctx: impl term::Context) -> anyhow::Result<()> { match command { Command::Connect { addr, timeout } => { - let timeout = timeout - .map(time::Duration::from_secs) - .unwrap_or(time::Duration::MAX); + let timeout = timeout.unwrap_or(time::Duration::MAX); match addr { Addr::Peer(addr) => control::connect(&mut node, addr.id, addr.addr, timeout)?, Addr::Node(nid) => { @@ -70,9 +68,7 @@ pub fn run(args: Args, ctx: impl term::Context) -> anyhow::Result<()> { } Command::Events { timeout, count } => { let count = count.unwrap_or(usize::MAX); - let timeout = timeout - .map(time::Duration::from_secs) - .unwrap_or(time::Duration::MAX); + let timeout = timeout.unwrap_or(time::Duration::MAX); events::run(node, count, timeout)?; } diff --git a/crates/radicle-cli/src/commands/node/args.rs b/crates/radicle-cli/src/commands/node/args.rs index db1f025b..44412b04 100644 --- a/crates/radicle-cli/src/commands/node/args.rs +++ b/crates/radicle-cli/src/commands/node/args.rs @@ -105,8 +105,10 @@ pub(super) enum Command { addr: Addr, /// How long to wait for the connection to be established - #[arg(long, value_name = "SECS")] - timeout: Option, + /// + /// Valid arguments are for example "10s", "5min" or "2h 37min" + #[arg(long, value_parser = humantime::parse_duration)] + timeout: Option, }, /// Show the config @@ -128,8 +130,10 @@ pub(super) enum Command { /// If no timeout or count is specified, it will run indefinitely. Events { /// How long to wait to receive an event before giving up - #[arg(long, value_name = "SECS")] - timeout: Option, + /// + /// Valid arguments are for example "10s", "5min" or "2h 37min" + #[arg(long, value_parser = humantime::parse_duration)] + timeout: Option, /// Exit after events #[arg(long, short = 'n')] diff --git a/crates/radicle-cli/src/commands/seed/args.rs b/crates/radicle-cli/src/commands/seed/args.rs index db457061..cedf2b6a 100644 --- a/crates/radicle-cli/src/commands/seed/args.rs +++ b/crates/radicle-cli/src/commands/seed/args.rs @@ -43,8 +43,10 @@ pub struct Args { pub(super) from: Vec, /// Fetch timeout in seconds - #[arg(long, short, value_name = "SECS", default_value_t = 9)] - timeout: u64, + /// + /// Valid arguments are for example "10s", "5min" or "2h 37min" + #[arg(long, short, value_parser = humantime::parse_duration, default_value = "9s")] + timeout: std::time::Duration, /// Peer follow scope for this repository #[arg(long, value_parser = terminal::args::ScopeParser)] @@ -86,7 +88,7 @@ impl From for Operation { impl Args { fn timeout(&self) -> time::Duration { - time::Duration::from_secs(self.timeout) + self.timeout } fn should_fetch(&self) -> bool { diff --git a/crates/radicle-cli/src/commands/sync/args.rs b/crates/radicle-cli/src/commands/sync/args.rs index ee4a02f1..df1f5509 100644 --- a/crates/radicle-cli/src/commands/sync/args.rs +++ b/crates/radicle-cli/src/commands/sync/args.rs @@ -89,15 +89,17 @@ pub(super) struct SyncArgs { )] seeds: Vec, - /// How many seconds to wait while synchronizing + /// How long to wait while synchronizing + /// + /// Valid arguments are for example "10s", "5min" or "2h 37min" #[arg( long, short, - default_value_t = 9, - value_name = "SECS", + default_value = "9s", + value_parser = humantime::parse_duration, conflicts_with = "inventory" )] - timeout: u64, + timeout: std::time::Duration, /// The repository to perform the synchronizing for [default: cwd] rid: Option, @@ -146,7 +148,7 @@ impl SyncArgs { } fn timeout(&self) -> time::Duration { - time::Duration::from_secs(self.timeout) + self.timeout } fn replication(&self) -> sync::ReplicationFactor { diff --git a/crates/radicle-cli/src/commands/watch/args.rs b/crates/radicle-cli/src/commands/watch/args.rs index a67f011b..23b21b3d 100644 --- a/crates/radicle-cli/src/commands/watch/args.rs +++ b/crates/radicle-cli/src/commands/watch/args.rs @@ -43,8 +43,10 @@ pub struct Args { interval: u64, /// Timeout, in milliseconds - #[arg(long, value_name = "MILLIS")] - timeout: Option, + /// + /// Valid arguments are for example "10s", "5min" or "2h 37min" + #[arg(long, value_parser = humantime::parse_duration)] + timeout: Option, } impl Args { @@ -55,7 +57,8 @@ impl Args { /// Provide the timeout duration in milliseconds. pub(super) fn timeout(&self) -> time::Duration { - time::Duration::from_millis(self.timeout.unwrap_or(u64::MAX)) + self.timeout + .unwrap_or_else(|| time::Duration::from_millis(u64::MAX)) } }