cli/help: remove the help module

The `help` module is no longer needed, since `clap` handles that.

This meant that the re-exports of each command's `ABOUT` could be removed and
the `const`s can now be private.
This commit is contained in:
Fintan Halpenny 2025-10-29 10:35:05 +00:00 committed by Lorenz Leutgeb
parent 23332fa318
commit d41ac59828
58 changed files with 31 additions and 345 deletions

View File

@ -9,7 +9,6 @@ pub mod debug;
pub mod diff;
pub mod follow;
pub mod fork;
pub mod help;
pub mod id;
pub mod inbox;
pub mod init;

View File

@ -14,7 +14,6 @@ use radicle::{profile, Profile};
use crate::terminal as term;
pub use args::Args;
pub(crate) use args::ABOUT;
pub fn run(args: Args, ctx: impl term::Context) -> anyhow::Result<()> {
match ctx.profile() {

View File

@ -1,7 +1,7 @@
use clap::Parser;
use radicle::node::Alias;
pub(crate) const ABOUT: &str = "Manage identities and profiles";
const ABOUT: &str = "Manage identities and profiles";
const LONG_ABOUT: &str = r#"
A passphrase may be given via the environment variable `RAD_PASSPHRASE` or
via the standard input stream if `--stdin` is used. Using either of these

View File

@ -7,7 +7,6 @@ use crate::terminal as term;
use term::args::BlockTarget;
pub use args::Args;
pub(crate) use args::ABOUT;
pub fn run(args: Args, ctx: impl term::Context) -> anyhow::Result<()> {
let profile = ctx.profile()?;

View File

@ -2,7 +2,7 @@ use clap::Parser;
use crate::terminal::args::BlockTarget;
pub(crate) const ABOUT: &str = "Block repositories or nodes from being seeded or followed";
const ABOUT: &str = "Block repositories or nodes from being seeded or followed";
#[derive(Parser, Debug)]
#[command(about = ABOUT, disable_version_flag = true)]

View File

@ -15,7 +15,6 @@ use crate::project;
use crate::terminal as term;
pub use args::Args;
pub(crate) use args::ABOUT;
pub fn run(args: Args, ctx: impl term::Context) -> anyhow::Result<()> {
let profile = ctx.profile()?;

View File

@ -1,7 +1,7 @@
use clap::Parser;
use radicle::prelude::{Did, RepoId};
pub(crate) const ABOUT: &str = "Checkout a repository into the local directory";
const ABOUT: &str = "Checkout a repository into the local directory";
const LONG_ABOUT: &str = r#"
Creates a working copy from a repository in local storage.
"#;

View File

@ -6,7 +6,6 @@ use radicle::storage::WriteStorage;
use crate::terminal as term;
pub use args::Args;
pub(crate) use args::ABOUT;
pub fn run(args: Args, ctx: impl term::Context) -> anyhow::Result<()> {
let profile = ctx.profile()?;

View File

@ -2,7 +2,7 @@ use clap::Parser;
use radicle::prelude::RepoId;
pub const ABOUT: &str = "Remove all remotes from a repository";
const ABOUT: &str = "Remove all remotes from a repository";
const LONG_ABOUT: &str = r#"
Removes all remotes from a repository, as long as they are not the

View File

@ -26,7 +26,6 @@ use crate::terminal as term;
use crate::terminal::Element as _;
pub use args::Args;
pub(crate) use args::ABOUT;
pub fn run(args: Args, ctx: impl term::Context) -> anyhow::Result<()> {
let profile = ctx.profile()?;

View File

@ -11,7 +11,7 @@ use radicle::prelude::*;
use crate::terminal;
pub(crate) const ABOUT: &str = "Clone a Radicle repository";
const ABOUT: &str = "Clone a Radicle repository";
const LONG_ABOUT: &str = r#"
The `clone` command will use your local node's routing table to find seeds from

View File

@ -2,7 +2,6 @@ mod args;
pub use args::Args;
use args::Command;
pub(crate) use args::ABOUT;
use std::path::Path;

View File

@ -1,7 +1,7 @@
use clap::{Parser, Subcommand};
use radicle::node::Alias;
pub(crate) const ABOUT: &str = "Manage your local Radicle configuration";
const ABOUT: &str = "Manage your local Radicle configuration";
const LONG_ABOUT: &str = r#"
If no argument is specified, prints the current radicle configuration as JSON.

View File

@ -13,7 +13,6 @@ use radicle::Profile;
use crate::terminal as term;
pub use args::Args;
pub(crate) use args::ABOUT;
pub const NAME: &str = "rad";
pub const VERSION: &str = env!("RADICLE_VERSION");

View File

@ -1,6 +1,6 @@
use clap::Parser;
pub const ABOUT: &str = "Write out information to help debug your Radicle node remotely";
const ABOUT: &str = "Write out information to help debug your Radicle node remotely";
const LONG_ABOUT: &str = r#"
Run this if you are reporting a problem in Radicle. The output is

View File

@ -8,7 +8,6 @@ use crate::terminal as term;
pub use args::Args;
use args::Operation;
pub(crate) use args::ABOUT;
pub fn run(args: Args, ctx: impl term::Context) -> anyhow::Result<()> {
let profile = ctx.profile()?;

View File

@ -4,7 +4,7 @@ use radicle::node::{Alias, NodeId};
use crate::terminal as term;
pub(crate) const ABOUT: &str = "Manage node follow policies";
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

View File

@ -7,7 +7,6 @@ use radicle::rad;
use crate::terminal as term;
pub use args::Args;
pub(crate) use args::ABOUT;
pub fn run(args: Args, ctx: impl term::Context) -> anyhow::Result<()> {
let profile = ctx.profile()?;

View File

@ -1,6 +1,6 @@
use radicle::identity::RepoId;
pub(crate) const ABOUT: &str = "Create a fork of a repository";
const ABOUT: &str = "Create a fork of a repository";
#[derive(Debug, clap::Parser)]
#[command(about = ABOUT, disable_version_flag = true)]

View File

@ -1,213 +0,0 @@
use std::ffi::OsString;
use crate::terminal as term;
use crate::terminal::args::{Args, Error, Help};
pub const HELP: Help = Help {
name: "help",
description: "CLI help",
version: env!("RADICLE_VERSION"),
usage: "Usage: rad help [--help]",
};
enum CommandItem {
Lexopt(Help),
Clap {
name: &'static str,
about: &'static str,
},
}
impl CommandItem {
fn name(&self) -> &str {
match self {
CommandItem::Lexopt(help) => help.name,
CommandItem::Clap { name, .. } => name,
}
}
fn description(&self) -> &str {
match self {
CommandItem::Lexopt(help) => help.description,
CommandItem::Clap {
about: description, ..
} => description,
}
}
}
const COMMANDS: &[CommandItem] = &[
CommandItem::Clap {
name: "auth",
about: crate::commands::auth::ABOUT,
},
CommandItem::Clap {
name: "block",
about: crate::commands::block::ABOUT,
},
CommandItem::Clap {
name: "checkout",
about: crate::commands::checkout::ABOUT,
},
CommandItem::Clap {
name: "clone",
about: crate::commands::clone::ABOUT,
},
CommandItem::Clap {
name: "config",
about: crate::commands::config::ABOUT,
},
CommandItem::Clap {
name: "debug",
about: crate::commands::debug::ABOUT,
},
CommandItem::Clap {
name: "fork",
about: crate::commands::fork::ABOUT,
},
CommandItem::Lexopt(crate::commands::help::HELP),
CommandItem::Clap {
name: "id",
about: crate::commands::id::ABOUT,
},
CommandItem::Clap {
name: "init",
about: crate::commands::init::ABOUT,
},
CommandItem::Clap {
name: "inbox",
about: crate::commands::inbox::ABOUT,
},
CommandItem::Clap {
name: "inspect",
about: crate::commands::inspect::ABOUT,
},
CommandItem::Clap {
name: "issue",
about: crate::commands::issue::ABOUT,
},
CommandItem::Clap {
name: "ls",
about: crate::commands::ls::ABOUT,
},
CommandItem::Clap {
name: "node",
about: crate::commands::node::ABOUT,
},
CommandItem::Clap {
name: "patch",
about: crate::commands::patch::ABOUT,
},
CommandItem::Clap {
name: "path",
about: crate::commands::path::ABOUT,
},
CommandItem::Clap {
name: "publish",
about: crate::commands::publish::ABOUT,
},
CommandItem::Clap {
name: "clean",
about: crate::commands::clean::ABOUT,
},
CommandItem::Clap {
name: "self",
about: crate::commands::rad_self::ABOUT,
},
CommandItem::Clap {
name: "seed",
about: crate::commands::seed::ABOUT,
},
CommandItem::Clap {
name: "follow",
about: crate::commands::follow::ABOUT,
},
CommandItem::Clap {
name: "unblock",
about: crate::commands::unblock::ABOUT,
},
CommandItem::Clap {
name: "unfollow",
about: crate::commands::unfollow::ABOUT,
},
CommandItem::Clap {
name: "unseed",
about: crate::commands::unseed::ABOUT,
},
CommandItem::Clap {
name: "remote",
about: crate::commands::remote::ABOUT,
},
CommandItem::Clap {
name: "stats",
about: crate::commands::stats::ABOUT,
},
CommandItem::Clap {
name: "sync",
about: crate::commands::sync::ABOUT,
},
CommandItem::Clap {
name: "watch",
about: crate::commands::watch::ABOUT,
},
];
#[derive(Default)]
pub struct Options {}
impl Args for Options {
fn from_args(args: Vec<OsString>) -> anyhow::Result<(Self, Vec<OsString>)> {
let mut parser = lexopt::Parser::from_args(args);
if let Some(arg) = parser.next()? {
anyhow::bail!(arg.unexpected());
}
Err(Error::HelpManual { name: "rad" }.into())
}
}
pub fn run(_options: Options, ctx: impl term::Context) -> anyhow::Result<()> {
term::print("Usage: rad <command> [--help]");
if let Err(e) = ctx.profile() {
term::blank();
match e.downcast_ref() {
Some(term::args::Error::WithHint { err, hint }) => {
term::print(term::format::yellow(err));
term::print(term::format::yellow(hint));
}
Some(e) => {
term::error(e);
}
None => {
term::error(e);
}
}
term::blank();
}
term::print("Common `rad` commands used in various situations:");
term::blank();
for help in COMMANDS {
term::info!(
"\t{} {}",
term::format::bold(format!("{:-12}", help.name())),
term::format::dim(help.description())
);
}
term::blank();
term::print("See `rad <command> --help` to learn about a specific command.");
term::blank();
term::print("Do you have feedback?");
term::print(
" - Chat <\x1b]8;;https://radicle.zulipchat.com\x1b\\radicle.zulipchat.com\x1b]8;;\x1b\\>",
);
term::print(
" - Mail <\x1b]8;;mailto:feedback@radicle.xyz\x1b\\feedback@radicle.xyz\x1b]8;;\x1b\\>",
);
term::print(" (Messages are automatically posted to the public #feedback channel on Zulip.)");
Ok(())
}

View File

@ -23,7 +23,6 @@ use crate::terminal::patch::Message;
pub use args::Args;
use args::Command;
pub(crate) use args::ABOUT;
pub fn run(args: Args, ctx: impl term::Context) -> anyhow::Result<()> {
let profile = ctx.profile()?;

View File

@ -17,7 +17,7 @@ use crate::git::Rev;
use crate::terminal::Interactive;
pub(crate) const ABOUT: &str = "Manage repository identities";
const ABOUT: &str = "Manage repository identities";
const LONG_ABOUT: &str = r#"
The `id` command is used to manage and propose changes to the
identity of a Radicle repository.

View File

@ -1,7 +1,6 @@
mod args;
pub use args::Args;
pub(crate) use args::ABOUT;
use std::path::Path;
use std::process;

View File

@ -3,7 +3,7 @@ use std::{fmt::Display, str::FromStr};
use clap::{Parser, Subcommand, ValueEnum};
use radicle::{node::notifications::NotificationId, prelude::RepoId};
pub(crate) const ABOUT: &str = "Manage your Radicle notifications";
const ABOUT: &str = "Manage your Radicle notifications";
const LONG_ABOUT: &str = r#"
By default, this command lists all items in your inbox.

View File

@ -4,7 +4,6 @@
mod args;
pub use args::Args;
pub(crate) use args::ABOUT;
use std::collections::HashSet;
use std::convert::TryFrom;

View File

@ -8,7 +8,7 @@ use radicle::{
};
use radicle_term::Interactive;
pub(crate) const ABOUT: &str = "Initialize a Radicle repository";
const ABOUT: &str = "Initialize a Radicle repository";
#[derive(Debug, Parser)]
#[command(about = ABOUT, disable_version_flag = true)]

View File

@ -23,7 +23,6 @@ use crate::terminal::Element;
pub use args::Args;
use args::Target;
pub(crate) use args::ABOUT;
pub fn run(args: Args, ctx: impl term::Context) -> anyhow::Result<()> {
let rid = match args.repo {

View File

@ -1,6 +1,6 @@
use clap::Parser;
pub(crate) const ABOUT: &str = "Inspect a Radicle repository";
const ABOUT: &str = "Inspect a Radicle repository";
const LONG_ABOUT: &str = r#"Inspects the given path or RID. If neither is specified,
the current repository is inspected.
"#;

View File

@ -30,7 +30,7 @@ use crate::terminal::format::Author;
use crate::terminal::issue::Format;
use crate::terminal::Element;
pub(crate) const ABOUT: &str = "Manage issues";
const ABOUT: &str = "Manage issues";
pub fn run(args: Args, ctx: impl term::Context) -> anyhow::Result<()> {
let profile = ctx.profile()?;

View File

@ -1,7 +1,6 @@
mod args;
pub use args::Args;
pub(crate) use args::ABOUT;
use radicle::storage::{ReadStorage, RepositoryInfo};

View File

@ -1,6 +1,6 @@
use clap::Parser;
pub(crate) const ABOUT: &str = "List repositories";
const ABOUT: &str = "List repositories";
const LONG_ABOUT: &str = r#"
By default, this command shows you all repositories that you have forked or initialized.
If you wish to see all seeded repositories, use the `--seeded` option.

View File

@ -19,7 +19,6 @@ use crate::terminal::Element as _;
use crate::warning;
pub use args::Args;
pub(crate) use args::ABOUT;
use args::{Addr, Command};
pub fn run(args: Args, ctx: impl term::Context) -> anyhow::Result<()> {

View File

@ -11,7 +11,7 @@ use radicle::crypto::{PublicKey, PublicKeyError};
use radicle::node::{Address, NodeId, PeerAddr, PeerAddrParseError};
use radicle::prelude::RepoId;
pub(crate) const ABOUT: &str = "Control and query the Radicle Node";
const ABOUT: &str = "Control and query the Radicle Node";
#[derive(Parser, Debug)]
#[command(about = ABOUT, long_about, disable_version_flag = true)]

View File

@ -33,7 +33,6 @@ use crate::terminal as term;
use crate::terminal::patch::Message;
pub use args::Args;
pub(crate) use args::ABOUT;
use args::{AssignArgs, Command, CommentAction, LabelArgs};

View File

@ -14,7 +14,7 @@ use crate::commands::patch::review;
use crate::git::Rev;
use crate::terminal::patch::Message;
pub(crate) const ABOUT: &str = "Manage patches";
const ABOUT: &str = "Manage patches";
#[derive(Debug, Parser)]
#[command(about = ABOUT, disable_version_flag = true)]

View File

@ -5,7 +5,6 @@ use radicle::profile;
use crate::terminal as term;
pub use args::Args;
pub(crate) use args::ABOUT;
pub fn run(_args: Args, _ctx: impl term::Context) -> anyhow::Result<()> {
let home = profile::home()?;

View File

@ -1,6 +1,6 @@
use clap::Parser;
pub const ABOUT: &str = "Display the Radicle home path";
const ABOUT: &str = "Display the Radicle home path";
#[derive(Parser, Debug)]
#[command(about = ABOUT, disable_version_flag = true)]

View File

@ -10,7 +10,6 @@ use radicle::storage::{SignRepository, ValidateRepository, WriteRepository, Writ
use crate::terminal as term;
pub use args::Args;
pub(crate) use args::ABOUT;
pub fn run(args: Args, ctx: impl term::Context) -> anyhow::Result<()> {
let profile = ctx.profile()?;

View File

@ -1,6 +1,6 @@
use radicle::identity::RepoId;
pub(crate) const ABOUT: &str = "Publish a repository to the network";
const ABOUT: &str = "Publish a repository to the network";
const LONG_ABOUT: &str = r#"
Publishing a private repository makes it public and discoverable

View File

@ -14,7 +14,6 @@ use crate::terminal as term;
use crate::terminal::Context;
pub use args::Args;
pub(crate) use args::ABOUT;
use args::{Command, ListOption};
pub fn run(args: Args, ctx: impl Context) -> anyhow::Result<()> {

View File

@ -6,7 +6,7 @@ use radicle::node::NodeId;
use crate::terminal as term;
pub(crate) const ABOUT: &str = "Manage a repository's remotes";
const ABOUT: &str = "Manage a repository's remotes";
#[derive(Parser, Debug)]
#[command(about = ABOUT, disable_version_flag = true)]

View File

@ -10,7 +10,6 @@ use crate::commands::sync;
use crate::terminal as term;
pub use args::Args;
pub(crate) use args::ABOUT;
pub fn run(args: Args, ctx: impl term::Context) -> anyhow::Result<()> {
let profile = ctx.profile()?;

View File

@ -9,7 +9,7 @@ use radicle::prelude::*;
use crate::node::SyncSettings;
use crate::terminal;
pub(crate) const ABOUT: &str = "Manage repository seeding policies";
const ABOUT: &str = "Manage repository seeding policies";
const LONG_ABOUT: &str = r#"
The `seed` command, when no Repository ID is provided, will list the

View File

@ -2,7 +2,6 @@
mod args;
pub use args::Args;
pub(crate) use args::ABOUT;
use radicle::crypto::ssh;
use radicle::node::Handle as _;

View File

@ -1,6 +1,6 @@
use clap::Parser;
pub(crate) const ABOUT: &str = "Show information about your identity and device";
const ABOUT: &str = "Show information about your identity and device";
#[derive(Debug, Parser)]
#[command(about = ABOUT, disable_version_flag = true)]

View File

@ -16,7 +16,6 @@ use serde::Serialize;
use crate::terminal as term;
pub use args::Args;
pub(crate) use args::ABOUT;
#[derive(Default, Serialize)]
#[serde(rename_all = "camelCase")]

View File

@ -1,6 +1,6 @@
use clap::Parser;
pub(crate) const ABOUT: &str = "Displays aggregated repository and node metrics";
const ABOUT: &str = "Displays aggregated repository and node metrics";
#[derive(Debug, Parser)]
#[command(about = ABOUT, disable_version_flag = true)]

View File

@ -26,7 +26,6 @@ use crate::terminal::format::Author;
use crate::terminal::{Table, TableOptions};
pub use args::Args;
pub(crate) use args::ABOUT;
use args::{Command, SortBy, SyncDirection, SyncMode};
pub fn run(args: Args, ctx: impl term::Context) -> anyhow::Result<()> {

View File

@ -10,7 +10,7 @@ use radicle::{
use crate::node::SyncSettings;
pub(crate) const ABOUT: &str = "Sync repositories to the network";
const ABOUT: &str = "Sync repositories to the network";
const LONG_ABOUT: &str = r#"
By default, the current repository is synchronized both ways.

View File

@ -5,7 +5,6 @@ use crate::terminal as term;
use term::args::BlockTarget;
pub use args::Args;
pub(crate) use args::ABOUT;
pub fn run(args: Args, ctx: impl term::Context) -> anyhow::Result<()> {
let profile = ctx.profile()?;

View File

@ -2,8 +2,7 @@ use clap::Parser;
use crate::terminal::args::BlockTarget;
pub(crate) const ABOUT: &str =
"Unblock repositories or nodes to allow them to be seeded or followed";
const ABOUT: &str = "Unblock repositories or nodes to allow them to be seeded or followed";
#[derive(Parser, Debug)]
#[command(about = ABOUT, disable_version_flag = true)]

View File

@ -5,7 +5,6 @@ use radicle::node::Handle;
use crate::terminal as term;
pub use args::Args;
pub(crate) use args::ABOUT;
pub fn run(args: Args, ctx: impl term::Context) -> anyhow::Result<()> {
let profile = ctx.profile()?;

View File

@ -4,7 +4,7 @@ use radicle::node::NodeId;
use crate::terminal as term;
pub(crate) const ABOUT: &str = "Unfollow a peer";
const ABOUT: &str = "Unfollow a peer";
const LONG_ABOUT: &str = r#"
The `unfollow` command takes a Node ID, optionally in DID format,

View File

@ -5,7 +5,6 @@ use radicle::{prelude::*, Node};
use crate::terminal as term;
pub use args::Args;
pub(crate) use args::ABOUT;
pub fn run(args: Args, ctx: impl term::Context) -> anyhow::Result<()> {
let profile = ctx.profile()?;

View File

@ -1,7 +1,7 @@
use clap::Parser;
use radicle::prelude::RepoId;
pub(crate) const ABOUT: &str = "Remove repository seeding policies";
const ABOUT: &str = "Remove repository seeding policies";
const LONG_ABOUT: &str = r#"
The `unseed` command removes the seeding policy, if found,

View File

@ -12,7 +12,6 @@ use radicle::storage::{ReadRepository, ReadStorage};
use crate::terminal as term;
pub use args::Args;
pub(crate) use args::ABOUT;
pub fn run(args: Args, ctx: impl term::Context) -> anyhow::Result<()> {
let profile = ctx.profile()?;

View File

@ -7,7 +7,7 @@ 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 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.

View File

@ -1,7 +1,10 @@
pub mod args;
use std::process;
pub use args::{Args, Error, Help};
pub mod format;
pub mod io;
use clap::Parser;
pub use io::signer;
pub mod cob;
pub mod comment;
@ -11,17 +14,10 @@ pub mod json;
pub mod patch;
pub mod upload_pack;
use std::ffi::OsString;
use std::process;
use clap::Parser;
pub use radicle_term::*;
use radicle::profile::{Home, Profile};
use crate::terminal;
/// Context passed to all commands.
pub trait Context {
/// Return the currently active profile, or an error if no profile is active.
@ -72,74 +68,6 @@ where
}
}
pub fn run_command<A, C>(help: Help, cmd: C) -> !
where
A: Args,
C: Command<A, DefaultContext>,
{
let args = std::env::args_os().skip(1).collect();
run_command_args(help, cmd, args)
}
pub fn run_command_args<A, C>(help: Help, cmd: C, args: Vec<OsString>) -> !
where
A: Args,
C: Command<A, DefaultContext>,
{
use io as term;
let options = match A::from_args(args) {
Ok((opts, unparsed)) => {
if let Err(err) = args::finish(unparsed) {
term::error(err);
process::exit(1);
}
opts
}
Err(err) => {
let hint = match err.downcast_ref::<Error>() {
Some(Error::Help) => {
help.print();
process::exit(0);
}
// Print the manual, or the regular help if there's an error.
Some(Error::HelpManual { name }) => {
let Ok(status) = term::manual(name) else {
help.print();
process::exit(0);
};
if !status.success() {
help.print();
process::exit(0);
}
process::exit(status.code().unwrap_or(0));
}
Some(Error::Usage) => {
term::usage(help.name, help.usage);
process::exit(1);
}
Some(Error::WithHint { hint, .. }) => Some(hint),
None => None,
};
io::error(format!("rad {}: {err}", help.name));
if let Some(hint) = hint {
io::hint(hint);
}
process::exit(1);
}
};
match cmd.run(options, DefaultContext) {
Ok(()) => process::exit(0),
Err(err) => {
terminal::fail(help.name, &err);
process::exit(1);
}
}
}
/// Gets the default profile. Fails if there is no profile.
pub struct DefaultContext;