cli/publish: Use clap

This commit is contained in:
Lorenz Leutgeb 2025-10-01 23:21:50 +02:00 committed by Erik Kundt
parent 7effa7c64c
commit 8c1073b9c9
5 changed files with 67 additions and 67 deletions

View File

@ -23,6 +23,7 @@ Common `rad` commands used in various situations:
node Control and query the Radicle Node node Control and query the Radicle Node
patch Manage patches patch Manage patches
path Display the Radicle home path path Display the Radicle home path
publish Publish a repository to the network
clean Remove all remotes from a repository clean Remove all remotes from a repository
self Show information about your identity and device self Show information about your identity and device
seed Manage repository seeding policies seed Manage repository seeding policies

View File

@ -72,6 +72,10 @@ const COMMANDS: &[CommandItem] = &[
name: "path", name: "path",
about: crate::commands::path::ABOUT, about: crate::commands::path::ABOUT,
}, },
CommandItem::Clap {
name: "publish",
about: crate::commands::publish::ABOUT,
},
CommandItem::Clap { CommandItem::Clap {
name: "clean", name: "clean",
about: crate::commands::clean::ABOUT, about: crate::commands::clean::ABOUT,

View File

@ -1,75 +1,20 @@
use std::ffi::OsString; mod args;
use anyhow::{anyhow, Context as _}; use anyhow::{anyhow, Context as _};
use radicle::cob; use radicle::cob;
use radicle::identity::{Identity, Visibility}; use radicle::identity::{Identity, Visibility};
use radicle::node::Handle as _; use radicle::node::Handle as _;
use radicle::prelude::RepoId;
use radicle::storage::{SignRepository, ValidateRepository, WriteRepository, WriteStorage}; use radicle::storage::{SignRepository, ValidateRepository, WriteRepository, WriteStorage};
use crate::terminal as term; use crate::terminal as term;
use crate::terminal::args::{Args, Error, Help};
pub const HELP: Help = Help { pub use args::Args;
name: "publish", pub(crate) use args::ABOUT;
description: "Publish a repository to the network",
version: env!("RADICLE_VERSION"),
usage: r#"
Usage
rad publish [<rid>] [<option>...] pub fn run(args: Args, ctx: impl term::Context) -> anyhow::Result<()> {
Publishing a private repository makes it public and discoverable
on the network.
By default, this command will publish the current repository.
If an `<rid>` is specified, that repository will be published instead.
Note that this command can only be run for repositories with a
single delegate. The delegate must be the currently authenticated
user. For repositories with more than one delegate, the `rad id`
command must be used.
Options
--help Print help
"#,
};
#[derive(Default, Debug)]
pub struct Options {
pub rid: Option<RepoId>,
}
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;
while let Some(arg) = parser.next()? {
match arg {
Long("help") | Short('h') => {
return Err(Error::Help.into());
}
Value(val) if rid.is_none() => {
rid = Some(term::args::rid(&val)?);
}
arg => {
return Err(anyhow!(arg.unexpected()));
}
}
}
Ok((Options { rid }, vec![]))
}
}
pub fn run(options: Options, ctx: impl term::Context) -> anyhow::Result<()> {
let profile = ctx.profile()?; let profile = ctx.profile()?;
let rid = match options.rid { let rid = match args.rid {
Some(rid) => rid, Some(rid) => rid,
None => radicle::rad::cwd() None => radicle::rad::cwd()
.map(|(_, rid)| rid) .map(|(_, rid)| rid)
@ -81,7 +26,7 @@ pub fn run(options: Options, ctx: impl term::Context) -> anyhow::Result<()> {
let doc = identity.doc(); let doc = identity.doc();
if doc.is_public() { if doc.is_public() {
return Err(Error::WithHint { return Err(term::Error::WithHint {
err: anyhow!("repository is already public"), err: anyhow!("repository is already public"),
hint: "to announce the repository to the network, run `rad sync --inventory`", hint: "to announce the repository to the network, run `rad sync --inventory`",
} }
@ -91,7 +36,7 @@ pub fn run(options: Options, ctx: impl term::Context) -> anyhow::Result<()> {
return Err(anyhow!("only the repository delegate can publish it")); return Err(anyhow!("only the repository delegate can publish it"));
} }
if doc.delegates().len() > 1 { if doc.delegates().len() > 1 {
return Err(Error::WithHint { return Err(term::Error::WithHint {
err: anyhow!( err: anyhow!(
"only repositories with a single delegate can be published with this command" "only repositories with a single delegate can be published with this command"
), ),

View File

@ -0,0 +1,51 @@
use radicle::identity::RepoId;
pub(crate) const ABOUT: &str = "Publish a repository to the network";
const LONG_ABOUT: &str = r#"
Publishing a private repository makes it public and discoverable
on the network.
By default, this command will publish the current repository.
If an `<rid>` is specified, that repository will be published instead.
Note that this command can only be run for repositories with a
single delegate. The delegate must be the currently authenticated
user. For repositories with more than one delegate, the `rad id`
command must be used."#;
#[derive(Debug, clap::Parser)]
#[command(about = ABOUT, long_about = LONG_ABOUT, disable_version_flag = true)]
pub struct Args {
/// The Repository ID of the repository to publish
///
/// [example values: rad:z3Tr6bC7ctEg2EHmLvknUr29mEDLH, z3Tr6bC7ctEg2EHmLvknUr29mEDLH]
#[arg(value_name = "RID")]
pub(super) rid: Option<RepoId>,
}
#[cfg(test)]
mod test {
use super::Args;
use clap::error::ErrorKind;
use clap::Parser;
#[test]
fn should_parse_rid_non_urn() {
let args = Args::try_parse_from(["publish", "z3Tr6bC7ctEg2EHmLvknUr29mEDLH"]);
assert!(args.is_ok())
}
#[test]
fn should_parse_rid_urn() {
let args = Args::try_parse_from(["publish", "rad:z3Tr6bC7ctEg2EHmLvknUr29mEDLH"]);
assert!(args.is_ok())
}
#[test]
fn should_not_parse_rid_url() {
let err =
Args::try_parse_from(["publish", "rad://z3Tr6bC7ctEg2EHmLvknUr29mEDLH"]).unwrap_err();
assert_eq!(err.kind(), ErrorKind::ValueValidation);
}
}

View File

@ -52,6 +52,7 @@ enum Commands {
Fork(fork::Args), Fork(fork::Args),
Issue(issue::Args), Issue(issue::Args),
Path(path::Args), Path(path::Args),
Publish(publish::Args),
Stats(stats::Args), Stats(stats::Args),
Unfollow(unfollow::Args), Unfollow(unfollow::Args),
Unseed(unseed::Args), Unseed(unseed::Args),
@ -259,11 +260,9 @@ pub(crate) fn run_other(exe: &str, args: &[OsString]) -> Result<(), Option<anyho
} }
} }
"publish" => { "publish" => {
term::run_command_args::<publish::Options, _>( if let Some(Commands::Publish(args)) = CliArgs::parse().command {
publish::HELP, term::run_command_fn(publish::run, args);
publish::run, }
args.to_vec(),
);
} }
"clean" => { "clean" => {
if let Some(Commands::Clean(args)) = CliArgs::parse().command { if let Some(Commands::Clean(args)) = CliArgs::parse().command {