cli/watch: Use clap
This commit is contained in:
parent
efe10f95be
commit
dfd35480be
|
|
@ -34,6 +34,7 @@ Common `rad` commands used in various situations:
|
|||
remote Manage a repository's remotes
|
||||
stats Displays aggregated repository and node metrics
|
||||
sync Sync repositories to the network
|
||||
watch Wait for some state to be updated
|
||||
|
||||
See `rad <command> --help` to learn about a specific command.
|
||||
|
||||
|
|
|
|||
|
|
@ -104,6 +104,10 @@ const COMMANDS: &[CommandItem] = &[
|
|||
about: crate::commands::stats::ABOUT,
|
||||
},
|
||||
CommandItem::Lexopt(crate::commands::sync::HELP),
|
||||
CommandItem::Clap {
|
||||
name: "watch",
|
||||
about: crate::commands::watch::ABOUT,
|
||||
},
|
||||
];
|
||||
|
||||
#[derive(Default)]
|
||||
|
|
|
|||
|
|
@ -1,133 +1,28 @@
|
|||
use std::ffi::OsString;
|
||||
mod args;
|
||||
|
||||
use std::{thread, time};
|
||||
|
||||
use anyhow::{anyhow, Context as _};
|
||||
|
||||
use radicle::git;
|
||||
use radicle::git::raw::ErrorExt as _;
|
||||
use radicle::prelude::{NodeId, RepoId};
|
||||
use radicle::prelude::NodeId;
|
||||
use radicle::storage::{ReadRepository, ReadStorage};
|
||||
|
||||
use crate::terminal as term;
|
||||
use crate::terminal::args::{Args, Error, Help};
|
||||
|
||||
pub const HELP: Help = Help {
|
||||
name: "wait",
|
||||
description: "Wait for some state to be updated",
|
||||
version: env!("RADICLE_VERSION"),
|
||||
usage: r#"
|
||||
Usage
|
||||
pub use args::Args;
|
||||
pub(crate) use args::ABOUT;
|
||||
|
||||
rad watch -r <ref> [-t <oid>] [--repo <rid>] [<option>...]
|
||||
|
||||
Watches a Git reference, and optionally exits when it reaches a target value.
|
||||
If no target value is passed, exits when the target changes.
|
||||
|
||||
Options
|
||||
|
||||
--repo <rid> The repository to watch (default: `rad .`)
|
||||
--node <nid> The namespace under which this reference exists
|
||||
(default: NID of the profile)
|
||||
-r, --ref <ref> The fully-qualified Git reference (branch, tag, etc.) to watch,
|
||||
eg. 'refs/heads/master'
|
||||
-t, --target <oid> The target OID (commit hash) that when reached,
|
||||
will cause the command to exit
|
||||
-i, --interval <millis> How often, in milliseconds, to check the reference target
|
||||
(default: 1000)
|
||||
--timeout <millis> Timeout, in milliseconds (default: none)
|
||||
-h, --help Print help
|
||||
"#,
|
||||
};
|
||||
|
||||
pub struct Options {
|
||||
rid: Option<RepoId>,
|
||||
refstr: git::fmt::RefString,
|
||||
target: Option<git::Oid>,
|
||||
nid: Option<NodeId>,
|
||||
interval: time::Duration,
|
||||
timeout: time::Duration,
|
||||
}
|
||||
|
||||
impl Args for Options {
|
||||
fn from_args(args: Vec<OsString>) -> anyhow::Result<(Self, Vec<OsString>)> {
|
||||
use lexopt::prelude::*;
|
||||
|
||||
let mut parser = lexopt::Parser::from_args(args);
|
||||
let mut rid = None;
|
||||
let mut nid: Option<NodeId> = None;
|
||||
let mut target: Option<git::Oid> = None;
|
||||
let mut refstr: Option<git::fmt::RefString> = None;
|
||||
let mut interval: Option<time::Duration> = None;
|
||||
let mut timeout: time::Duration = time::Duration::MAX;
|
||||
|
||||
while let Some(arg) = parser.next()? {
|
||||
match arg {
|
||||
Long("repo") => {
|
||||
let value = parser.value()?;
|
||||
let value = term::args::rid(&value)?;
|
||||
|
||||
rid = Some(value);
|
||||
}
|
||||
Long("node") => {
|
||||
let value = parser.value()?;
|
||||
let value = term::args::nid(&value)?;
|
||||
|
||||
nid = Some(value);
|
||||
}
|
||||
Long("ref") | Short('r') => {
|
||||
let value = parser.value()?;
|
||||
let value = term::args::refstring("ref", value)?;
|
||||
|
||||
refstr = Some(value);
|
||||
}
|
||||
Long("target") | Short('t') => {
|
||||
let value = parser.value()?;
|
||||
let value = term::args::oid(&value)?;
|
||||
|
||||
target = Some(value);
|
||||
}
|
||||
Long("interval") | Short('i') => {
|
||||
let value = parser.value()?;
|
||||
let value = term::args::milliseconds(&value)?;
|
||||
|
||||
interval = Some(value);
|
||||
}
|
||||
Long("timeout") => {
|
||||
let value = parser.value()?;
|
||||
let value = term::args::milliseconds(&value)?;
|
||||
|
||||
timeout = value;
|
||||
}
|
||||
Long("help") | Short('h') => {
|
||||
return Err(Error::Help.into());
|
||||
}
|
||||
_ => anyhow::bail!(arg.unexpected()),
|
||||
}
|
||||
}
|
||||
|
||||
Ok((
|
||||
Options {
|
||||
rid,
|
||||
refstr: refstr.ok_or_else(|| anyhow!("a reference must be provided"))?,
|
||||
nid,
|
||||
target,
|
||||
interval: interval.unwrap_or(time::Duration::from_secs(1)),
|
||||
timeout,
|
||||
},
|
||||
vec![],
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn run(options: Options, ctx: impl term::Context) -> anyhow::Result<()> {
|
||||
pub fn run(args: Args, ctx: impl term::Context) -> anyhow::Result<()> {
|
||||
let profile = ctx.profile()?;
|
||||
let storage = &profile.storage;
|
||||
let qualified = options
|
||||
let qualified = args
|
||||
.refstr
|
||||
.qualified()
|
||||
.ok_or_else(|| anyhow!("reference must be fully-qualified, eg. 'refs/heads/master'"))?;
|
||||
let nid = options.nid.unwrap_or(profile.public_key);
|
||||
let rid = match options.rid {
|
||||
let nid = args.node.unwrap_or(profile.public_key);
|
||||
let rid = match args.repo {
|
||||
Some(rid) => rid,
|
||||
None => {
|
||||
let (_, rid) =
|
||||
|
|
@ -137,26 +32,28 @@ pub fn run(options: Options, ctx: impl term::Context) -> anyhow::Result<()> {
|
|||
};
|
||||
let repo = storage.repository(rid)?;
|
||||
let now = time::SystemTime::now();
|
||||
let timeout = args.timeout();
|
||||
let interval = args.interval();
|
||||
|
||||
if let Some(target) = options.target {
|
||||
if let Some(target) = args.target {
|
||||
while reference(&repo, &nid, &qualified)? != Some(target) {
|
||||
thread::sleep(options.interval);
|
||||
if now.elapsed()? >= options.timeout {
|
||||
anyhow::bail!("timed out after {}ms", options.timeout.as_millis());
|
||||
thread::sleep(interval);
|
||||
if now.elapsed()? >= timeout {
|
||||
anyhow::bail!("timed out after {}ms", timeout.as_millis());
|
||||
}
|
||||
}
|
||||
} else {
|
||||
let initial = reference(&repo, &nid, &qualified)?;
|
||||
|
||||
loop {
|
||||
thread::sleep(options.interval);
|
||||
thread::sleep(interval);
|
||||
let oid = reference(&repo, &nid, &qualified)?;
|
||||
if oid != initial {
|
||||
term::info!("{}", oid.unwrap_or(git::raw::Oid::zero().into()));
|
||||
break;
|
||||
}
|
||||
if now.elapsed()? >= options.timeout {
|
||||
anyhow::bail!("timed out after {}ms", options.timeout.as_millis());
|
||||
if now.elapsed()? >= timeout {
|
||||
anyhow::bail!("timed out after {}ms", timeout.as_millis());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,72 @@
|
|||
use std::time;
|
||||
|
||||
#[allow(rustdoc::broken_intra_doc_links)]
|
||||
use clap::Parser;
|
||||
|
||||
use radicle::git;
|
||||
use radicle::git::fmt::RefString;
|
||||
use radicle::prelude::{NodeId, RepoId};
|
||||
|
||||
pub(crate) const ABOUT: &str = "Wait for some state to be updated";
|
||||
|
||||
const LONG_ABOUT: &str = r#"
|
||||
Watches a Git reference, and optionally exits when it reaches a target value.
|
||||
If no target value is passed, exits when the target changes."#;
|
||||
|
||||
fn parse_refstr(refstr: &str) -> Result<RefString, git::fmt::Error> {
|
||||
RefString::try_from(refstr)
|
||||
}
|
||||
|
||||
#[derive(Parser, Debug)]
|
||||
#[command(about = ABOUT, long_about = LONG_ABOUT,disable_version_flag = true)]
|
||||
pub struct Args {
|
||||
/// The repository to watch, defaults to `rad .`
|
||||
#[arg(long)]
|
||||
pub(super) repo: Option<RepoId>,
|
||||
|
||||
/// The fully-qualified Git reference (branch, tag, etc.) to watch
|
||||
///
|
||||
/// [example value: 'refs/heads/master']
|
||||
#[arg(long, short, alias = "ref", value_name = "REF", value_parser = parse_refstr)]
|
||||
pub(super) refstr: git::fmt::RefString,
|
||||
|
||||
/// The target OID (commit hash) that when reached, will cause the command to exit
|
||||
#[arg(long, short, value_name = "OID")]
|
||||
pub(super) target: Option<git::Oid>,
|
||||
|
||||
/// The namespace under which this reference exists, defaults to the profiles' NID
|
||||
#[arg(long, short, value_name = "NID")]
|
||||
pub(super) node: Option<NodeId>,
|
||||
|
||||
/// How often, in milliseconds, to check the reference target
|
||||
#[arg(long, short, value_name = "MILLIS", default_value_t = 1000)]
|
||||
interval: u64,
|
||||
|
||||
/// Timeout, in milliseconds
|
||||
#[arg(long, value_name = "MILLIS")]
|
||||
timeout: Option<u64>,
|
||||
}
|
||||
|
||||
impl Args {
|
||||
/// Provide the interval duration in milliseconds.
|
||||
pub(super) fn interval(&self) -> time::Duration {
|
||||
time::Duration::from_millis(self.interval)
|
||||
}
|
||||
|
||||
/// Provide the timeout duration in milliseconds.
|
||||
pub(super) fn timeout(&self) -> time::Duration {
|
||||
time::Duration::from_millis(self.timeout.unwrap_or(u64::MAX))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::Args;
|
||||
use clap::Parser;
|
||||
|
||||
#[test]
|
||||
fn should_parse_ref_str() {
|
||||
let args = Args::try_parse_from(["watch", "--ref", "refs/heads/master"]);
|
||||
assert!(args.is_ok())
|
||||
}
|
||||
}
|
||||
|
|
@ -58,6 +58,7 @@ enum Commands {
|
|||
Stats(stats::Args),
|
||||
Unfollow(unfollow::Args),
|
||||
Unseed(unseed::Args),
|
||||
Watch(watch::Args),
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
|
|
@ -314,7 +315,9 @@ pub(crate) fn run_other(exe: &str, args: &[OsString]) -> Result<(), Option<anyho
|
|||
}
|
||||
}
|
||||
"watch" => {
|
||||
term::run_command_args::<watch::Options, _>(watch::HELP, watch::run, args.to_vec())
|
||||
if let Some(Commands::Watch(args)) = CliArgs::parse().command {
|
||||
term::run_command_fn(watch::run, args);
|
||||
}
|
||||
}
|
||||
other => {
|
||||
let exe = format!("{NAME}-{exe}");
|
||||
|
|
|
|||
Loading…
Reference in New Issue