From b1027b3b04c22d31ecb738dc0c5f8b216bb6b26c Mon Sep 17 00:00:00 2001 From: Awiteb Date: Sun, 8 Jun 2025 20:12:32 +0000 Subject: [PATCH] refactor: Improve commands running and make the signer optional for some commands Signed-off-by: Awiteb --- CHANGELOG.md | 1 + src/cli/commands/config/mod.rs | 5 +-- src/cli/commands/config/pow.rs | 4 +-- src/cli/commands/config/relays.rs | 4 +-- src/cli/commands/issue/mod.rs | 4 +-- src/cli/commands/mod.rs | 38 ++++++++++----------- src/cli/commands/patch/mod.rs | 4 +-- src/cli/commands/repo/announce.rs | 4 +-- src/cli/commands/repo/mod.rs | 5 +-- src/cli/commands/repo/view.rs | 4 +-- src/cli/commands/sets/mod.rs | 7 +--- src/cli/commands/sets/new.rs | 3 +- src/cli/commands/sets/remove.rs | 3 +- src/cli/commands/sets/show.rs | 3 +- src/cli/commands/sets/update.rs | 3 +- src/cli/macros.rs | 55 +++++++++++++++++++++++++++++++ src/cli/mod.rs | 2 ++ src/cli/traits.rs | 7 ++++ src/error.rs | 2 ++ 19 files changed, 107 insertions(+), 51 deletions(-) create mode 100644 src/cli/macros.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index cfa5b40..82905e1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/src/cli/commands/config/mod.rs b/src/cli/commands/config/mod.rs index 0174e72..10864e6 100644 --- a/src/cli/commands/config/mod.rs +++ b/src/cli/commands/config/mod.rs @@ -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) } } diff --git a/src/cli/commands/config/pow.rs b/src/cli/commands/config/pow.rs index 49ff1d5..9655498 100644 --- a/src/cli/commands/config/pow.rs +++ b/src/cli/commands/config/pow.rs @@ -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() } diff --git a/src/cli/commands/config/relays.rs b/src/cli/commands/config/relays.rs index 3656f43..3e4d23f 100644 --- a/src/cli/commands/config/relays.rs +++ b/src/cli/commands/config/relays.rs @@ -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 { diff --git a/src/cli/commands/issue/mod.rs b/src/cli/commands/issue/mod.rs index fa6f463..495c729 100644 --- a/src/cli/commands/issue/mod.rs +++ b/src/cli/commands/issue/mod.rs @@ -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) } } diff --git a/src/cli/commands/mod.rs b/src/cli/commands/mod.rs index ba34c2a..48f240b 100644 --- a/src/cli/commands/mod.rs +++ b/src/cli/commands/mod.rs @@ -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 { @@ -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) + } +} diff --git a/src/cli/commands/patch/mod.rs b/src/cli/commands/patch/mod.rs index 0ed4b4c..da83c49 100644 --- a/src/cli/commands/patch/mod.rs +++ b/src/cli/commands/patch/mod.rs @@ -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) } } diff --git a/src/cli/commands/repo/announce.rs b/src/cli/commands/repo/announce.rs index 65717a4..3dd2e0d 100644 --- a/src/cli/commands/repo/announce.rs +++ b/src/cli/commands/repo/announce.rs @@ -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?; diff --git a/src/cli/commands/repo/mod.rs b/src/cli/commands/repo/mod.rs index a77fc73..343f7ba 100644 --- a/src/cli/commands/repo/mod.rs +++ b/src/cli/commands/repo/mod.rs @@ -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) } } diff --git a/src/cli/commands/repo/view.rs b/src/cli/commands/repo/view.rs index fff6e90..80b15ed 100644 --- a/src/cli/commands/repo/view.rs +++ b/src/cli/commands/repo/view.rs @@ -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()?, diff --git a/src/cli/commands/sets/mod.rs b/src/cli/commands/sets/mod.rs index 11465bd..f6863cb 100644 --- a/src/cli/commands/sets/mod.rs +++ b/src/cli/commands/sets/mod.rs @@ -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) } } diff --git a/src/cli/commands/sets/new.rs b/src/cli/commands/sets/new.rs index ddc6f04..609f319 100644 --- a/src/cli/commands/sets/new.rs +++ b/src/cli/commands/sets/new.rs @@ -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)?; diff --git a/src/cli/commands/sets/remove.rs b/src/cli/commands/sets/remove.rs index def354e..2096b13 100644 --- a/src/cli/commands/sets/remove.rs +++ b/src/cli/commands/sets/remove.rs @@ -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)?; diff --git a/src/cli/commands/sets/show.rs b/src/cli/commands/sets/show.rs index bb51b88..d4cd045 100644 --- a/src/cli/commands/sets/show.rs +++ b/src/cli/commands/sets/show.rs @@ -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!( diff --git a/src/cli/commands/sets/update.rs b/src/cli/commands/sets/update.rs index ddc864a..4930108 100644 --- a/src/cli/commands/sets/update.rs +++ b/src/cli/commands/sets/update.rs @@ -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)?; diff --git a/src/cli/macros.rs b/src/cli/macros.rs new file mode 100644 index 0000000..b5ba9f6 --- /dev/null +++ b/src/cli/macros.rs @@ -0,0 +1,55 @@ +// n34 - A CLI to interact with NIP-34 and other stuff related to codes in nostr +// Copyright (C) 2025 Awiteb +// +// 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 . + +use super::traits::CommandRunner; + + +/// Returns whether the command runner type `T` requires relays. +pub fn get_relays_state(_v: &T) -> bool { + T::NEED_RELAYS +} + +/// Returns whether the command runner type `T` requires a signer. +pub fn get_signer_state(_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), + )* + } + }; +} diff --git a/src/cli/mod.rs b/src/cli/mod.rs index 156d2cd..d5cd925 100644 --- a/src/cli/mod.rs +++ b/src/cli/mod.rs @@ -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 diff --git a/src/cli/traits.rs b/src/cli/traits.rs index 905192f..917338a 100644 --- a/src/cli/traits.rs +++ b/src/cli/traits.rs @@ -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> + Send; } diff --git a/src/error.rs b/src/error.rs index 7c22a5a..e60669c 100644 --- a/src/error.rs +++ b/src/error.rs @@ -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: `/`\n- Valid NIP-19 naddr string (starts with \