diff --git a/src/cli/commands/issue/new.rs b/src/cli/commands/issue/new.rs index 827189d..bcaf097 100644 --- a/src/cli/commands/issue/new.rs +++ b/src/cli/commands/issue/new.rs @@ -22,7 +22,8 @@ use crate::{ cli::{ CliOptions, CommandRunner, - types::{NaddrOrSet, OptionNaddrOrSetVecExt, RelayOrSetVecExt}, + traits::{OptionNaddrOrSetVecExt, RelayOrSetVecExt}, + types::NaddrOrSet, }, error::N34Result, nostr_utils::{ diff --git a/src/cli/commands/issue/view.rs b/src/cli/commands/issue/view.rs index f56568d..3925074 100644 --- a/src/cli/commands/issue/view.rs +++ b/src/cli/commands/issue/view.rs @@ -20,8 +20,8 @@ use nostr::{event::Kind, filter::Filter}; use crate::{ cli::{ CliOptions, - traits::CommandRunner, - types::{NaddrOrSet, NostrEvent, OptionNaddrOrSetVecExt, RelayOrSetVecExt}, + traits::{CommandRunner, OptionNaddrOrSetVecExt, RelayOrSetVecExt}, + types::{NaddrOrSet, NostrEvent}, }, error::{N34Error, N34Result}, nostr_utils::{ diff --git a/src/cli/commands/patch/fetch.rs b/src/cli/commands/patch/fetch.rs index 2ea2fae..515869f 100644 --- a/src/cli/commands/patch/fetch.rs +++ b/src/cli/commands/patch/fetch.rs @@ -30,8 +30,8 @@ use nostr::{ use crate::{ cli::{ CliOptions, - traits::CommandRunner, - types::{NaddrOrSet, NostrEvent, OptionNaddrOrSetVecExt, RelayOrSetVecExt}, + traits::{CommandRunner, OptionNaddrOrSetVecExt, RelayOrSetVecExt}, + types::{NaddrOrSet, NostrEvent}, }, error::{N34Error, N34Result}, nostr_utils::{ diff --git a/src/cli/commands/patch/send.rs b/src/cli/commands/patch/send.rs index 666ce09..9feab2a 100644 --- a/src/cli/commands/patch/send.rs +++ b/src/cli/commands/patch/send.rs @@ -30,8 +30,8 @@ use super::GitPatch; use crate::{ cli::{ CliOptions, - traits::CommandRunner, - types::{NaddrOrSet, NostrEvent, OptionNaddrOrSetVecExt, RelayOrSetVecExt}, + traits::{CommandRunner, OptionNaddrOrSetVecExt, RelayOrSetVecExt}, + types::{NaddrOrSet, NostrEvent}, }, error::N34Result, nostr_utils::{ diff --git a/src/cli/commands/reply.rs b/src/cli/commands/reply.rs index b5a7ebe..9c25fab 100644 --- a/src/cli/commands/reply.rs +++ b/src/cli/commands/reply.rs @@ -27,7 +27,10 @@ use nostr::{ use super::{CliOptions, CommandRunner}; use crate::{ - cli::types::{NaddrOrSet, NostrEvent, OptionNaddrOrSetVecExt, RelayOrSetVecExt}, + cli::{ + traits::{OptionNaddrOrSetVecExt, RelayOrSetVecExt}, + types::{NaddrOrSet, NostrEvent}, + }, error::{N34Error, N34Result}, nostr_utils::{ NostrClient, diff --git a/src/cli/commands/repo/announce.rs b/src/cli/commands/repo/announce.rs index 3dd2e0d..faaea7a 100644 --- a/src/cli/commands/repo/announce.rs +++ b/src/cli/commands/repo/announce.rs @@ -21,7 +21,7 @@ use futures::future; use nostr::{event::EventBuilder, key::PublicKey, types::Url}; use crate::{ - cli::{CliOptions, CommandRunner, NOSTR_ADDRESS_FILE, types::RelayOrSetVecExt}, + cli::{CliOptions, CommandRunner, NOSTR_ADDRESS_FILE, traits::RelayOrSetVecExt}, error::N34Result, nostr_utils::{NostrClient, traits::NewGitRepositoryAnnouncement, utils}, }; diff --git a/src/cli/commands/repo/view.rs b/src/cli/commands/repo/view.rs index 7f02e17..db753ed 100644 --- a/src/cli/commands/repo/view.rs +++ b/src/cli/commands/repo/view.rs @@ -23,7 +23,8 @@ use crate::{ cli::{ CliOptions, CommandRunner, - types::{NaddrOrSet, OptionNaddrOrSetVecExt, RelayOrSetVecExt}, + traits::{OptionNaddrOrSetVecExt, RelayOrSetVecExt}, + types::NaddrOrSet, }, error::N34Result, nostr_utils::{NostrClient, traits::NaddrsUtils, utils}, diff --git a/src/cli/commands/sets/new.rs b/src/cli/commands/sets/new.rs index 609f319..2f6c2f3 100644 --- a/src/cli/commands/sets/new.rs +++ b/src/cli/commands/sets/new.rs @@ -20,9 +20,8 @@ use crate::{ cli::{ CliOptions, ConfigError, - MutRepoRelaySetsExt, - traits::CommandRunner, - types::{NaddrOrSet, NaddrOrSetVecExt, RelayOrSet, RelayOrSetVecExt}, + traits::{CommandRunner, MutRepoRelaySetsExt, NaddrOrSetVecExt, RelayOrSetVecExt}, + types::{NaddrOrSet, RelayOrSet}, }, error::N34Result, }; diff --git a/src/cli/commands/sets/remove.rs b/src/cli/commands/sets/remove.rs index 2096b13..40147b5 100644 --- a/src/cli/commands/sets/remove.rs +++ b/src/cli/commands/sets/remove.rs @@ -19,9 +19,8 @@ use clap::Args; use crate::{ cli::{ CliOptions, - MutRepoRelaySetsExt, - traits::CommandRunner, - types::{NaddrOrSet, NaddrOrSetVecExt, RelayOrSet, RelayOrSetVecExt}, + traits::{CommandRunner, MutRepoRelaySetsExt, NaddrOrSetVecExt, RelayOrSetVecExt}, + types::{NaddrOrSet, RelayOrSet}, }, error::N34Result, }; diff --git a/src/cli/commands/sets/show.rs b/src/cli/commands/sets/show.rs index d4cd045..f2268aa 100644 --- a/src/cli/commands/sets/show.rs +++ b/src/cli/commands/sets/show.rs @@ -18,7 +18,11 @@ use clap::Args; use nostr::nips::nip19::ToBech32; use crate::{ - cli::{CliOptions, RepoRelaySet, RepoRelaySetsExt, traits::CommandRunner}, + cli::{ + CliOptions, + RepoRelaySet, + traits::{CommandRunner, RepoRelaySetsExt}, + }, error::N34Result, }; diff --git a/src/cli/commands/sets/update.rs b/src/cli/commands/sets/update.rs index 4930108..7307fe2 100644 --- a/src/cli/commands/sets/update.rs +++ b/src/cli/commands/sets/update.rs @@ -21,9 +21,8 @@ use clap::Args; use crate::{ cli::{ CliOptions, - MutRepoRelaySetsExt, - traits::CommandRunner, - types::{NaddrOrSet, NaddrOrSetVecExt, RelayOrSet, RelayOrSetVecExt}, + traits::{CommandRunner, MutRepoRelaySetsExt, NaddrOrSetVecExt, RelayOrSetVecExt}, + types::{NaddrOrSet, RelayOrSet}, }, error::N34Result, }; diff --git a/src/cli/common_commands.rs b/src/cli/common_commands.rs index f65ce04..ccd43cb 100644 --- a/src/cli/common_commands.rs +++ b/src/cli/common_commands.rs @@ -32,7 +32,7 @@ use super::{ types::{NaddrOrSet, NostrEvent}, }; use crate::{ - cli::types::{OptionNaddrOrSetVecExt, RelayOrSetVecExt}, + cli::traits::{OptionNaddrOrSetVecExt, RelayOrSetVecExt}, nostr_utils::{NostrClient, traits::NaddrsUtils, utils}, }; use crate::{ diff --git a/src/cli/config.rs b/src/cli/config.rs index 9e70476..a495202 100644 --- a/src/cli/config.rs +++ b/src/cli/config.rs @@ -21,7 +21,10 @@ use nostr::{ types::RelayUrl, }; -use crate::error::{N34Error, N34Result}; +use crate::{ + cli::traits::{MutRepoRelaySetsExt, RepoRelaySetsExt}, + error::N34Result, +}; /// Errors that can occur when working with configuration files. #[derive(thiserror::Error, Debug)] @@ -162,139 +165,3 @@ impl RepoRelaySet { self.naddrs.retain(|n| seen.insert(n.coordinate.clone())); } } - -#[easy_ext::ext(MutRepoRelaySetsExt)] -impl Vec { - /// Removes duplicate repository addresses from each set. - /// - /// Relays are automatically deduplicated by the HashSet, but - /// repository addresses may appear duplicated if relays are sorted - /// differently or when relay counts vary. This compares addresses by - /// their coordinates, ignoring any embedded relay details. - pub fn dedup_naddrs(&mut self) { - self.iter_mut().for_each(RepoRelaySet::dedup_naddrs); - } - - /// Finds and returns a mutable reference a set with the given name. Returns - /// an error if no set with this name exists. - pub fn get_mut_set(&mut self, name: impl AsRef) -> N34Result<&mut RepoRelaySet> { - let name = name.as_ref(); - let set = self - .iter_mut() - .find(|set| set.name == name) - .ok_or_else(|| N34Error::from(ConfigError::SetNotFound(name.to_owned())))?; - - tracing::trace!( - name = %name, set = ?set, - "Successfully located a set with the giving name" - ); - - Ok(set) - } - - /// Creates and pushes a new set with the given name. - /// - /// Returns an error if a set with the same name already exists. - pub fn push_set( - &mut self, - name: impl Into, - repos: impl IntoIterator, - relays: impl IntoIterator, - ) -> N34Result<()> { - let set_name: String = name.into(); - tracing::trace!(sets = ?self, "Pushing set '{set_name}' to sets collection"); - - if self.as_slice().exists(&set_name) { - return Err(ConfigError::SetDuplicateName(set_name).into()); - } - - self.push(RepoRelaySet::new(set_name, repos, relays)); - - Ok(()) - } - - /// Removes the set with the given name if it exists. Returns an error if - /// the set is not found. - pub fn remove_set(&mut self, name: impl Into) -> N34Result<()> { - let set_name: String = name.into(); - tracing::trace!(set_name, sets = ?self, "Removing set '{set_name}' from sets collection"); - - if !self.as_slice().exists(&set_name) { - return Err(ConfigError::SetNotFound(set_name).into()); - } - - self.retain(|s| s.name != set_name); - - Ok(()) - } - - /// Removes the given relays from the specified set. - pub fn remove_relays( - &mut self, - name: impl Into, - relays: impl Iterator, - ) -> N34Result<()> { - let relays = Vec::from_iter(relays); - let set = self.get_mut_set(name.into())?; - - set.relays.retain(|r| !relays.contains(r)); - - Ok(()) - } - - /// Removes the given naddrs from the specified set. - pub fn remove_naddrs( - &mut self, - name: impl Into, - naddrs: impl Iterator, - ) -> N34Result<()> { - let coordinates = Vec::from_iter(naddrs.map(|n| n.coordinate)); - let set = self.get_mut_set(name.into())?; - - set.naddrs.retain(|n| !coordinates.contains(&n.coordinate)); - - Ok(()) - } -} - -#[easy_ext::ext(RepoRelaySetsExt)] -impl &[RepoRelaySet] { - /// Checks for duplicate set names. Returns an error if any duplicates are - /// found. - pub fn ensure_names(&self) -> N34Result<()> { - let mut names = Vec::with_capacity(self.len()); - names.extend(self.iter().map(|s| s.name.to_owned())); - - names.sort_unstable(); - - if let Some(duplicate) = duplicate_in_sorted(&names) { - return Err(ConfigError::SetDuplicateName(duplicate.clone()).into()); - } - Ok(()) - } - - /// Check if a set with the given name exists. - pub fn exists(&self, set_name: &str) -> bool { - self.iter().any(|set| set.name == set_name) - } - - /// Finds and returns a reference a set with the given name. Returns an - /// error if no set with this name exists. - pub fn get_set(&self, name: impl AsRef) -> N34Result<&RepoRelaySet> { - let name = name.as_ref(); - let set = self - .iter() - .find(|set| set.name == name) - .ok_or_else(|| N34Error::from(ConfigError::SetNotFound(name.to_owned())))?; - tracing::trace!( - name = %name, set = ?set, - "Successfully located a set with the giving name" - ); - Ok(set) - } -} - -/// Helper function that checks for duplicates in a sorted slice -fn duplicate_in_sorted(items: &[T]) -> Option<&T> { - items.windows(2).find(|w| w[0] == w[1]).map(|w| &w[0]) -} diff --git a/src/cli/traits.rs b/src/cli/traits.rs index 52cc767..7e9a646 100644 --- a/src/cli/traits.rs +++ b/src/cli/traits.rs @@ -14,10 +14,17 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -use nostr::event::EventId; +use nostr::{event::EventId, nips::nip19::Nip19Coordinate, types::RelayUrl}; use super::CliOptions; -use crate::{cli::types::NostrEvent, error::N34Result}; +use crate::{ + cli::{ + ConfigError, + RepoRelaySet, + types::{NaddrOrSet, NostrEvent, RelayOrSet}, + }, + error::{N34Error, N34Result}, +}; /// A trait defining the interface for command runners in the CLI. pub trait CommandRunner { @@ -40,3 +47,180 @@ impl Vec { self.into_iter().map(|e| e.event_id).collect() } } + +#[easy_ext::ext(NaddrOrSetVecExt)] +impl Vec { + /// Converts this vector of [`NaddrOrSet`] into a flat vector of + /// [`Nip19Coordinate`] using the given sets. + pub fn flat_naddrs(self, sets: &[RepoRelaySet]) -> N34Result> { + self.into_iter() + .map(|n| n.get_naddrs(sets)) + .try_fold(Vec::new(), |mut acc, item| { + acc.extend(item?); + Ok(acc) + }) + } +} + +#[easy_ext::ext(RelayOrSetVecExt)] +impl Vec { + /// Converts this vector of [`RelayOrSet`] into a flat vector of + /// [`RelayUrl`] using the given sets. + pub fn flat_relays(self, sets: &[RepoRelaySet]) -> N34Result> { + self.into_iter() + .map(|n| n.get_relays(sets)) + .try_fold(Vec::new(), |mut acc, item| { + acc.extend(item?); + Ok(acc) + }) + } +} + + +#[easy_ext::ext(OptionNaddrOrSetVecExt)] +impl Option> { + /// Converts this vector of [`NaddrOrSet`] into a flat vector of + /// [`Nip19Coordinate`] using the given sets. + pub fn flat_naddrs(&self, sets: &[RepoRelaySet]) -> N34Result>> { + // Clones self here to simplify command code + self.clone() + .map(|naddrs| naddrs.flat_naddrs(sets)) + .transpose() + } +} + +#[easy_ext::ext(MutRepoRelaySetsExt)] +impl Vec { + /// Removes duplicate repository addresses from each set. + /// + /// Relays are automatically deduplicated by the HashSet, but + /// repository addresses may appear duplicated if relays are sorted + /// differently or when relay counts vary. This compares addresses by + /// their coordinates, ignoring any embedded relay details. + pub fn dedup_naddrs(&mut self) { + self.iter_mut().for_each(RepoRelaySet::dedup_naddrs); + } + + /// Finds and returns a mutable reference a set with the given name. Returns + /// an error if no set with this name exists. + pub fn get_mut_set(&mut self, name: impl AsRef) -> N34Result<&mut RepoRelaySet> { + let name = name.as_ref(); + let set = self + .iter_mut() + .find(|set| set.name == name) + .ok_or_else(|| N34Error::from(ConfigError::SetNotFound(name.to_owned())))?; + + tracing::trace!( + name = %name, set = ?set, + "Successfully located a set with the giving name" + ); + + Ok(set) + } + + /// Creates and pushes a new set with the given name. + /// + /// Returns an error if a set with the same name already exists. + pub fn push_set( + &mut self, + name: impl Into, + repos: impl IntoIterator, + relays: impl IntoIterator, + ) -> N34Result<()> { + let set_name: String = name.into(); + tracing::trace!(sets = ?self, "Pushing set '{set_name}' to sets collection"); + + if self.as_slice().exists(&set_name) { + return Err(ConfigError::SetDuplicateName(set_name).into()); + } + + self.push(RepoRelaySet::new(set_name, repos, relays)); + + Ok(()) + } + + /// Removes the set with the given name if it exists. Returns an error if + /// the set is not found. + pub fn remove_set(&mut self, name: impl Into) -> N34Result<()> { + let set_name: String = name.into(); + tracing::trace!(set_name, sets = ?self, "Removing set '{set_name}' from sets collection"); + + if !self.as_slice().exists(&set_name) { + return Err(ConfigError::SetNotFound(set_name).into()); + } + + self.retain(|s| s.name != set_name); + + Ok(()) + } + + /// Removes the given relays from the specified set. + pub fn remove_relays( + &mut self, + name: impl Into, + relays: impl Iterator, + ) -> N34Result<()> { + let relays = Vec::from_iter(relays); + let set = self.get_mut_set(name.into())?; + + set.relays.retain(|r| !relays.contains(r)); + + Ok(()) + } + + /// Removes the given naddrs from the specified set. + pub fn remove_naddrs( + &mut self, + name: impl Into, + naddrs: impl Iterator, + ) -> N34Result<()> { + let coordinates = Vec::from_iter(naddrs.map(|n| n.coordinate)); + let set = self.get_mut_set(name.into())?; + + set.naddrs.retain(|n| !coordinates.contains(&n.coordinate)); + + Ok(()) + } +} + +#[easy_ext::ext(RepoRelaySetsExt)] +impl &[RepoRelaySet] { + /// Checks for duplicate set names. Returns an error if any duplicates are + /// found. + pub fn ensure_names(&self) -> N34Result<()> { + let mut names = Vec::with_capacity(self.len()); + names.extend(self.iter().map(|s| s.name.to_owned())); + + names.sort_unstable(); + + if let Some(duplicate) = duplicate_in_sorted(&names) { + return Err(ConfigError::SetDuplicateName(duplicate.clone()).into()); + } + Ok(()) + } + + /// Check if a set with the given name exists. + pub fn exists(&self, set_name: &str) -> bool { + self.iter().any(|set| set.name == set_name) + } + + /// Finds and returns a reference a set with the given name. Returns an + /// error if no set with this name exists. + pub fn get_set(&self, name: impl AsRef) -> N34Result<&RepoRelaySet> { + let name = name.as_ref(); + let set = self + .iter() + .find(|set| set.name == name) + .ok_or_else(|| N34Error::from(ConfigError::SetNotFound(name.to_owned())))?; + tracing::trace!( + name = %name, set = ?set, + "Successfully located a set with the giving name" + ); + Ok(set) + } +} + +/// Helper function that checks for duplicates in a sorted slice +fn duplicate_in_sorted(items: &[T]) -> Option<&T> { + items.windows(2).find(|w| w[0] == w[1]).map(|w| &w[0]) +} diff --git a/src/cli/types.rs b/src/cli/types.rs index 28d1de5..efd55be 100644 --- a/src/cli/types.rs +++ b/src/cli/types.rs @@ -29,9 +29,9 @@ use nostr::{ use nostr_connect::client::AuthUrlHandler; use tokio::runtime::Handle; -use super::{RepoRelaySetsExt, parsers}; +use super::parsers; use crate::{ - cli::RepoRelaySet, + cli::{RepoRelaySet, traits::RepoRelaySetsExt}, error::{N34Error, N34Result}, }; @@ -191,47 +191,6 @@ impl FromStr for NostrEvent { } } -#[easy_ext::ext(NaddrOrSetVecExt)] -impl Vec { - /// Converts this vector of [`NaddrOrSet`] into a flat vector of - /// [`Nip19Coordinate`] using the given sets. - pub fn flat_naddrs(self, sets: &[RepoRelaySet]) -> N34Result> { - self.into_iter() - .map(|n| n.get_naddrs(sets)) - .try_fold(Vec::new(), |mut acc, item| { - acc.extend(item?); - Ok(acc) - }) - } -} - -#[easy_ext::ext(RelayOrSetVecExt)] -impl Vec { - /// Converts this vector of [`RelayOrSet`] into a flat vector of - /// [`RelayUrl`] using the given sets. - pub fn flat_relays(self, sets: &[RepoRelaySet]) -> N34Result> { - self.into_iter() - .map(|n| n.get_relays(sets)) - .try_fold(Vec::new(), |mut acc, item| { - acc.extend(item?); - Ok(acc) - }) - } -} - - -#[easy_ext::ext(OptionNaddrOrSetVecExt)] -impl Option> { - /// Converts this vector of [`NaddrOrSet`] into a flat vector of - /// [`Nip19Coordinate`] using the given sets. - pub fn flat_naddrs(&self, sets: &[RepoRelaySet]) -> N34Result>> { - // Clones self here to simplify command code - self.clone() - .map(|naddrs| naddrs.flat_naddrs(sets)) - .transpose() - } -} - fn parse_nip5_repo(nip5: &str, repo_id: &str) -> Result { let (username, domain) = nip5.split_once("@").unwrap_or(("_", nip5));