refactor: Improve commands running and make the signer optional for some commands

Signed-off-by: Awiteb <a@4rs.nl>
This commit is contained in:
Awiteb
2025-06-08 20:12:32 +00:00
parent 5dc8e31eb3
commit b1027b3b04
19 changed files with 107 additions and 51 deletions

View File

@@ -18,6 +18,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Refactor
- Store the config in `CliOptions` instead of its path - by Awiteb
- Improve commands running and make the signer optional for some commands - by Awiteb
## [0.2.0] - 2025-06-01

View File

@@ -38,9 +38,6 @@ pub enum ConfigSubcommands {
impl CommandRunner for ConfigSubcommands {
async fn run(self, options: CliOptions) -> N34Result<()> {
match self {
Self::Pow(args) => args.run(options).await,
Self::Relays(args) => args.run(options).await,
}
crate::run_command!(self, options, & Pow Relays)
}
}

View File

@@ -28,9 +28,9 @@ pub struct PowArgs {
}
impl CommandRunner for PowArgs {
async fn run(self, mut options: CliOptions) -> N34Result<()> {
// FIXME: The signer is not required here
const NEED_SIGNER: bool = false;
async fn run(self, mut options: CliOptions) -> N34Result<()> {
options.config.pow = Some(self.difficulty);
options.config.dump()
}

View File

@@ -33,9 +33,9 @@ pub struct RelaysArgs {
}
impl CommandRunner for RelaysArgs {
async fn run(self, mut options: CliOptions) -> N34Result<()> {
// FIXME: The signer is not required here
const NEED_SIGNER: bool = false;
async fn run(self, mut options: CliOptions) -> N34Result<()> {
if self.relays.is_empty() {
options.config.fallback_relays = None;
} else if self.override_relays {

View File

@@ -34,8 +34,6 @@ pub enum IssueSubcommands {
impl CommandRunner for IssueSubcommands {
async fn run(self, options: CliOptions) -> N34Result<()> {
match self {
Self::New(args) => args.run(options).await,
}
crate::run_command!(self, options, &New)
}
}

View File

@@ -48,14 +48,13 @@ use crate::error::{N34Error, N34Result};
/// This is a workaround since Clap doesn't support lazy evaluation of defaults.
pub const DEFAULT_FALLBACK_PATH: &str = "I_DO_NOT_KNOW_WHY_CLAP_DO_NOT_SUPPORT_LAZY_DEFAULT";
// TODO: Make the `signer` group optional
/// The command-line interface options
#[derive(Args, Clone)]
#[clap(
group(
ArgGroup::new("signer")
.args(&["secret_key"])
.required(true)
.required(false)
)
)]
pub struct CliOptions {
@@ -109,23 +108,6 @@ pub enum Commands {
}
impl CommandRunner for Commands {
async fn run(self, options: CliOptions) -> N34Result<()> {
tracing::trace!("Options: {options:#?}");
tracing::trace!("Handling: {self:#?}");
match self {
Self::Repo { subcommands } => subcommands.run(options).await,
Self::Issue { subcommands } => subcommands.run(options).await,
Self::Reply(args) => args.run(options).await,
Self::Sets { subcommands } => subcommands.run(options).await,
Self::Patch { subcommands } => subcommands.run(options).await,
Self::Config { subcommands } => subcommands.run(options).await,
}
}
}
// TODO: Implement `ensure_signer` function with similar functionality to
// `ensure_relays`
impl CliOptions {
/// Gets the public key of the user.
pub async fn pubkey(&self) -> N34Result<PublicKey> {
@@ -142,6 +124,15 @@ impl CliOptions {
}
Ok(())
}
/// Returns an error if there are no signers
pub fn ensure_signer(&self) -> N34Result<()> {
if self.secret_key.is_none() {
Err(N34Error::SignerRequired)
} else {
Ok(())
}
}
}
impl fmt::Debug for CliOptions {
@@ -154,3 +145,12 @@ impl fmt::Debug for CliOptions {
.finish()
}
}
impl CommandRunner for Commands {
async fn run(self, options: CliOptions) -> N34Result<()> {
tracing::trace!("Options: {options:#?}");
tracing::trace!("Handling: {self:#?}");
crate::run_command!(self, options, Repo Issue Sets Patch Config & Reply)
}
}

View File

@@ -54,9 +54,7 @@ pub struct GitPatch {
impl CommandRunner for PatchSubcommands {
async fn run(self, options: CliOptions) -> N34Result<()> {
match self {
Self::Send(args) => args.run(options).await,
}
crate::run_command!(self, options, &Send)
}
}

View File

@@ -78,9 +78,9 @@ pub struct AnnounceArgs {
}
impl CommandRunner for AnnounceArgs {
async fn run(mut self, options: CliOptions) -> N34Result<()> {
options.ensure_relays()?;
const NEED_RELAYS: bool = true;
async fn run(mut self, options: CliOptions) -> N34Result<()> {
let relays = options.relays.clone().flat_relays(&options.config.sets)?;
let client = NostrClient::init(&options, &relays).await;
let user_pubk = options.pubkey().await?;

View File

@@ -38,9 +38,6 @@ pub enum RepoSubcommands {
impl CommandRunner for RepoSubcommands {
async fn run(self, options: CliOptions) -> N34Result<()> {
match self {
Self::View(args) => args.run(options).await,
Self::Announce(args) => args.run(options).await,
}
crate::run_command!(self, options, & View Announce)
}
}

View File

@@ -40,9 +40,9 @@ pub struct ViewArgs {
}
impl CommandRunner for ViewArgs {
async fn run(self, options: CliOptions) -> N34Result<()> {
// FIXME: The signer is not required here
const NEED_SIGNER: bool = false;
async fn run(self, options: CliOptions) -> N34Result<()> {
let naddrs = utils::naddrs_or_file(
self.naddrs.flat_naddrs(&options.config.sets)?,
&utils::nostr_address_path()?,

View File

@@ -47,11 +47,6 @@ pub enum SetsSubcommands {
impl CommandRunner for SetsSubcommands {
async fn run(self, options: CliOptions) -> N34Result<()> {
match self {
Self::Remove(args) => args.run(options).await,
Self::New(args) => args.run(options).await,
Self::Update(args) => args.run(options).await,
Self::Show(args) => args.run(options).await,
}
crate::run_command!(self, options, & Remove New Update Show)
}
}

View File

@@ -42,7 +42,8 @@ pub struct NewArgs {
}
impl CommandRunner for NewArgs {
// FIXME: The signer is not required here
const NEED_SIGNER: bool = false;
async fn run(self, mut options: CliOptions) -> N34Result<()> {
let naddrs = self.naddrs.flat_naddrs(&options.config.sets)?;
let relays = self.relays.flat_relays(&options.config.sets)?;

View File

@@ -42,7 +42,8 @@ pub struct RemoveArgs {
}
impl CommandRunner for RemoveArgs {
// FIXME: The signer is not required here
const NEED_SIGNER: bool = false;
async fn run(self, mut options: CliOptions) -> N34Result<()> {
let naddrs = self.naddrs.flat_naddrs(&options.config.sets)?;
let relays = self.relays.flat_relays(&options.config.sets)?;

View File

@@ -29,7 +29,8 @@ pub struct ShowArgs {
}
impl CommandRunner for ShowArgs {
// FIXME: The signer is not required here
const NEED_SIGNER: bool = false;
async fn run(self, options: CliOptions) -> N34Result<()> {
if let Some(name) = self.name {
println!(

View File

@@ -46,7 +46,8 @@ pub struct UpdateArgs {
}
impl CommandRunner for UpdateArgs {
// FIXME: The signer is not required here
const NEED_SIGNER: bool = false;
async fn run(self, mut options: CliOptions) -> N34Result<()> {
let naddrs = self.naddrs.flat_naddrs(&options.config.sets)?;
let relays = self.relays.flat_relays(&options.config.sets)?;

55
src/cli/macros.rs Normal file
View File

@@ -0,0 +1,55 @@
// n34 - A CLI to interact with NIP-34 and other stuff related to codes in nostr
// Copyright (C) 2025 Awiteb <a@4rs.nl>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://gnu.org/licenses/gpl-3.0.html>.
use super::traits::CommandRunner;
/// Returns whether the command runner type `T` requires relays.
pub fn get_relays_state<T: CommandRunner>(_v: &T) -> bool {
T::NEED_RELAYS
}
/// Returns whether the command runner type `T` requires a signer.
pub fn get_signer_state<T: CommandRunner>(_v: &T) -> bool {
T::NEED_SIGNER
}
/// Executes a command with required setup checks. The first parameter is the
/// command to match on (often `self`), followed by options. Optional
/// subcommands come next, and commands with arguments (after `&`) are listed
/// last.
#[macro_export]
macro_rules! run_command {
(r $command:ident $options:ident) => {{
if $crate::cli::macros::get_relays_state(&$command) {
$options.ensure_relays()?;
}
if $crate::cli::macros::get_signer_state(&$command) {
$options.ensure_signer()?;
}
$command.run($options).await
}};
($command:ident, $options:ident, $($subcommands:ident)* & $($commands:ident)*) => {
match $command {
$(
Self::$subcommands { subcommands } => subcommands.run($options).await,
)*
$(
Self::$commands ( args ) => $crate::run_command!(r args $options),
)*
}
};
}

View File

@@ -20,6 +20,8 @@ pub mod commands;
pub mod config;
/// Default lazy values for CLI arguments
pub mod defaults;
/// Macros for CLI application.
pub mod macros;
/// CLI arguments parsers
pub mod parsers;
/// CLI traits

View File

@@ -19,6 +19,13 @@ use crate::error::N34Result;
/// A trait defining the interface for command runners in the CLI.
pub trait CommandRunner {
/// Whether this command needs the relays option (false by default).
/// Only applies to commands, not subcommands.
const NEED_RELAYS: bool = false;
/// Indicates if this command requires the signer. Defaults to true.
/// Only applies to commands, not subcommands.
const NEED_SIGNER: bool = true;
/// Executes the command and returns a Result indicating success or failure.
fn run(self, options: CliOptions) -> impl Future<Output = N34Result<()>> + Send;
}

View File

@@ -66,6 +66,8 @@ pub enum N34Error {
InvalidNostrAddressFileContent(String),
#[error("This command requires at least one relay, but none were provided")]
EmptyRelays,
#[error("This command requires a signer to sign events")]
SignerRequired,
#[error(
"Invalid repository address. Expected one of these formats:\n- NIP-05 identifier with \
repository ID: `<user@domain.com>/<repo_id>`\n- Valid NIP-19 naddr string (starts with \