use std::ffi::OsString;
use std::fmt::Debug;
use std::path::PathBuf;
use std::str::FromStr;
use thiserror::Error;
use clap::{Parser, Subcommand};
use radicle::crypto::{PublicKey, PublicKeyError};
use radicle::node::{Address, NodeId, PeerAddr, PeerAddrParseError};
use radicle::prelude::RepoId;
const ABOUT: &str = "Control and query the Radicle Node";
#[derive(Parser, Debug)]
#[command(about = ABOUT, long_about, disable_version_flag = true)]
pub struct Args {
#[command(subcommand)]
pub(super) command: Option<Command>,
}
#[derive(Clone, Debug)]
pub(super) enum Addr {
Peer(PeerAddr<NodeId, Address>),
Node(NodeId),
}
#[derive(Error, Debug)]
pub(super) enum AddrParseError {
#[error("{0}, expected <NID> or <NID>@<ADDR>")]
PeerAddr(#[from] PeerAddrParseError<PublicKey>),
#[error(transparent)]
NodeId(#[from] PublicKeyError),
}
impl FromStr for Addr {
type Err = AddrParseError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
if s.contains("@") {
PeerAddr::from_str(s)
.map(Self::Peer)
.map_err(AddrParseError::PeerAddr)
} else {
NodeId::from_str(s)
.map(Self::Node)
.map_err(AddrParseError::NodeId)
}
}
}
#[derive(Clone, Debug)]
pub enum Only {
Nid,
}
#[derive(Error, Debug)]
#[error("could not parse value `{0}`")]
pub struct OnlyParseError(String);
impl FromStr for Only {
type Err = OnlyParseError;
fn from_str(value: &str) -> Result<Self, Self::Err> {
match value {
"nid" => Ok(Only::Nid),
_ => Err(OnlyParseError(value.to_string())),
}
}
}
#[derive(Clone, Debug)]
struct OnlyParser;
impl clap::builder::TypedValueParser for OnlyParser {
type Value = Only;
fn parse_ref(
&self,
cmd: &clap::Command,
arg: Option<&clap::Arg>,
value: &std::ffi::OsStr,
) -> Result<Self::Value, clap::Error> {
<Only as std::str::FromStr>::from_str.parse_ref(cmd, arg, value)
}
fn possible_values(
&self,
) -> Option<Box<dyn Iterator<Item = clap::builder::PossibleValue> + '_>> {
use clap::builder::PossibleValue;
Some(Box::new([PossibleValue::new("nid")].into_iter()))
}
}
#[derive(Subcommand, Debug)]
pub(super) enum Command {
Connect {
#[arg(value_name = "NID[@ADDR]")]
addr: Addr,
#[arg(long, value_parser = humantime::parse_duration)]
timeout: Option<std::time::Duration>,
},
Config {
#[arg(long)]
addresses: bool,
},
#[command(subcommand, hide = true)]
Db(DbOperation),
Events {
#[arg(long, value_parser = humantime::parse_duration)]
timeout: Option<std::time::Duration>,
#[arg(long, short = 'n')]
count: Option<usize>,
},
Routing {
#[arg(long)]
json: bool,
#[arg(long)]
rid: Option<RepoId>,
#[arg(long)]
nid: Option<NodeId>,
},
Start {
#[arg(long)]
foreground: bool,
#[arg(long, short)]
verbose: bool,
#[arg(long, default_value = "radicle-node")]
path: PathBuf,
#[arg(value_name = "NODE_OPTIONS", last = true, num_args = 1..)]
options: Vec<OsString>,
},
Logs {
#[arg(long, value_name = "COUNT", default_value_t = 60)]
lines: usize,
},
Status {
#[arg(long, value_parser = OnlyParser)]
only: Option<Only>,
},
Inventory {
#[arg(long)]
nid: Option<NodeId>,
},
Debug,
#[command(hide = true)]
Sessions,
Stop,
}
impl Default for Command {
fn default() -> Self {
Command::Status { only: None }
}
}
#[derive(Debug, Subcommand)]
pub(super) enum DbOperation {
Exec {
#[arg(value_name = "SQL")]
query: String,
},
}