cli/follow: Use clap

This commit is contained in:
Erik Kundt 2025-10-14 19:18:26 +02:00 committed by Fintan Halpenny
parent deaf77e5b4
commit 407abc6a2c
4 changed files with 83 additions and 94 deletions

View File

@ -1,106 +1,26 @@
use std::ffi::OsString;
use anyhow::anyhow;
mod args;
use radicle::node::{policy, Alias, AliasStore, Handle, NodeId};
use radicle::{prelude::*, Node};
use radicle_term::{Element as _, Paint, Table};
use crate::terminal as term;
use crate::terminal::args::{Args, Error, Help};
pub const HELP: Help = Help {
name: "follow",
description: "Manage node follow policies",
version: env!("RADICLE_VERSION"),
usage: r#"
Usage
pub use args::Args;
use args::Operation;
pub(crate) use args::ABOUT;
rad follow [<nid>] [--alias <name>] [<option>...]
The `follow` command will print all nodes being followed, optionally filtered by alias, if no
Node ID is provided.
Otherwise, it takes a Node ID, optionally in DID format, and updates the follow policy
for that peer, optionally giving the peer the alias provided.
Options
--alias <name> Associate an alias to a followed peer
--verbose, -v Verbose output
--help Print help
"#,
};
#[derive(Debug)]
pub enum Operation {
Follow { nid: NodeId, alias: Option<Alias> },
List { alias: Option<Alias> },
}
#[derive(Debug, Default)]
pub enum OperationName {
Follow,
#[default]
List,
}
#[derive(Debug)]
pub struct Options {
pub op: Operation,
pub verbose: bool,
}
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 verbose = false;
let mut nid: Option<NodeId> = None;
let mut alias: Option<Alias> = None;
while let Some(arg) = parser.next()? {
match &arg {
Value(val) if nid.is_none() => {
if let Ok(did) = term::args::did(val) {
nid = Some(did.into());
} else if let Ok(val) = term::args::nid(val) {
nid = Some(val);
} else {
anyhow::bail!("invalid Node ID `{}` specified", val.to_string_lossy());
}
}
Long("alias") if alias.is_none() => {
let name = parser.value()?;
let name = term::args::alias(&name)?;
alias = Some(name.to_owned());
}
Long("verbose") | Short('v') => verbose = true,
Long("help") | Short('h') => {
return Err(Error::Help.into());
}
_ => {
return Err(anyhow!(arg.unexpected()));
}
}
}
let op = match nid {
Some(nid) => Operation::Follow { nid, alias },
None => Operation::List { alias },
};
Ok((Options { op, verbose }, 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 mut node = radicle::Node::new(profile.socket());
match options.op {
Operation::Follow { nid, alias } => follow(nid, alias, &mut node, &profile)?,
Operation::List { alias } => following(&profile, alias)?,
match Operation::from(args) {
Operation::Follow {
nid,
alias,
verbose: _,
} => follow(nid, alias, &mut node, &profile)?,
Operation::List { alias, verbose: _ } => following(&profile, alias)?,
}
Ok(())

View File

@ -0,0 +1,63 @@
use clap::Parser;
use radicle::node::{Alias, NodeId};
use crate::terminal as term;
pub(crate) const ABOUT: &str = "Manage node follow policies";
const LONG_ABOUT: &str = r#"
The `follow` command will print all nodes being followed, optionally filtered by alias, if no
Node ID is provided.
Otherwise, it takes a Node ID, optionally in DID format, and updates the follow policy
for that peer, optionally giving the peer the alias provided.
"#;
#[derive(Parser, Debug)]
#[command(about = ABOUT, long_about = LONG_ABOUT, disable_version_flag = true)]
pub struct Args {
/// The DID or Node ID of the peer to follow
#[arg(value_parser = term::args::parse_nid)]
nid: Option<NodeId>,
/// Associate an alias to a followed peer
#[arg(long)]
alias: Option<Alias>,
/// Verbose output
#[arg(long, short)]
verbose: bool,
}
pub(super) enum Operation {
Follow {
nid: NodeId,
alias: Option<Alias>,
#[allow(dead_code)]
verbose: bool,
},
List {
alias: Option<Alias>,
#[allow(dead_code)]
verbose: bool,
},
}
impl From<Args> for Operation {
fn from(
Args {
nid,
alias,
verbose,
}: Args,
) -> Self {
match nid {
Some(nid) => Self::Follow {
nid,
alias,
verbose,
},
None => Self::List { alias, verbose },
}
}
}

View File

@ -100,7 +100,10 @@ const COMMANDS: &[CommandItem] = &[
name: "seed",
about: crate::commands::seed::ABOUT,
},
CommandItem::Lexopt(crate::commands::follow::HELP),
CommandItem::Clap {
name: "follow",
about: crate::commands::follow::ABOUT,
},
CommandItem::Clap {
name: "unblock",
about: crate::commands::unblock::ABOUT,

View File

@ -63,6 +63,7 @@ enum Commands {
#[command(external_subcommand, hide = true)]
Diff(Vec<OsString>),
Follow(follow::Args),
Fork(fork::Args),
Id(id::Args),
Init(init::Args),
@ -238,7 +239,9 @@ pub(crate) fn run_other(exe: &str, args: &[OsString]) -> Result<(), Option<anyho
}
}
"follow" => {
term::run_command_args::<follow::Options, _>(follow::HELP, follow::run, args.to_vec());
if let Some(Commands::Follow(args)) = CliArgs::parse().command {
term::run_command_fn(follow::run, args);
}
}
"fork" => {
if let Some(Commands::Fork(args)) = CliArgs::parse().command {