use crate::{
error::ParseError,
string::{Builder, Nick, Splitter, User, Word},
};
use std::io::Write;
#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
#[cfg_attr(feature = "serde", derive(serde_derive::Serialize, serde_derive::Deserialize))]
pub struct Source<'a> {
pub nick: Nick<'a>,
pub userhost: Option<UserHost<'a>>,
}
#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
#[cfg_attr(feature = "serde", derive(serde_derive::Serialize, serde_derive::Deserialize))]
pub struct UserHost<'a> {
pub user: Option<User<'a>>,
pub host: Word<'a>,
}
#[allow(clippy::len_without_is_empty)]
impl<'a> Source<'a> {
pub const fn new_server(server_name: Nick<'a>) -> Self {
Source { nick: server_name, userhost: None }
}
pub const fn new_user(nick: Nick<'a>, user: User<'a>, host: Word<'a>) -> Self {
Source { nick, userhost: Some(UserHost { user: Some(user), host }) }
}
pub const fn user(&self) -> Option<&User<'a>> {
if let Some(uh) = &self.userhost {
if let Some(u) = &uh.user {
return Some(u);
}
}
None
}
pub const fn host(&self) -> Option<&Word<'a>> {
if let Some(uh) = &self.userhost {
Some(&uh.host)
} else {
None
}
}
pub fn len(&self) -> usize {
if let Some(address) = self.userhost.as_ref() {
self.nick.len() + 1 + address.len()
} else {
self.nick.len()
}
}
pub fn write_to(&self, w: &mut (impl Write + ?Sized)) -> std::io::Result<()> {
if let Some(address) = self.userhost.as_ref() {
w.write_all(self.nick.as_ref())?;
if let Some(user) = &address.user {
w.write_all(b"!")?;
w.write_all(user.as_ref())?;
}
w.write_all(b"@")?;
w.write_all(address.host.as_ref())
} else {
w.write_all(self.nick.as_ref())
}
}
pub fn parse(word: impl Into<Word<'a>>) -> Result<Self, ParseError> {
let mut word = Splitter::new(word.into());
let nick = word.string::<Nick>(false).map_err(ParseError::InvalidNick)?;
match word.next_byte() {
Some(b'!') => {
let user = word
.save_end()
.until_byte_eq(b'@')
.string::<User>(true)
.map_err(ParseError::InvalidUser)?;
word.next_byte();
let host: Word = word.rest_or_default();
if host.is_empty() {
Err(ParseError::InvalidHost(crate::error::InvalidString::Empty))
} else {
Ok(Source { nick, userhost: Some(UserHost { user: Some(user), host }) })
}
}
Some(b'@') => {
let host: Word = word.rest_or_default();
if host.is_empty() {
Err(ParseError::InvalidHost(crate::error::InvalidString::Empty))
} else {
let address = UserHost { user: None, host };
Ok(Source { nick, userhost: Some(address) })
}
}
_ => Ok(Source { nick, userhost: None }),
}
}
pub fn owning(self) -> Source<'static> {
Source { nick: self.nick.owning(), userhost: self.userhost.map(UserHost::owning) }
}
pub fn owning_merged(self) -> Source<'static> {
let len_nick = self.nick.len();
let mut len_user = None;
let mut len_host = None;
let mut concat = Builder::<Word>::new(self.nick.clone().into());
if let Some(uh) = self.userhost {
len_host = Some(uh.host.len());
if let Some(u) = uh.user {
len_user = Some(u.len());
concat.append(u);
}
concat.append(uh.host);
}
let mut splitter = Splitter::new(concat.build());
let nick = splitter.save_end().until_count(len_nick).string(true).unwrap();
if let Some(lh) = len_host {
let user = len_user.map(|lu| {
splitter.save_end().until_count(lu).string(true).unwrap()
});
let host = splitter.save_end().until_count(lh).string(true).unwrap();
let userhost = UserHost { user, host };
Source { nick, userhost: Some(userhost) }
} else {
Source { nick, userhost: None }
}
}
}
#[allow(clippy::len_without_is_empty)]
impl UserHost<'_> {
pub fn owning(self) -> UserHost<'static> {
UserHost { host: self.host.owning(), user: self.user.map(User::owning) }
}
pub fn has_ident(&self) -> bool {
!matches!(self.user.as_ref().and_then(|user| user.first()), Some(b'~'))
}
pub fn len(&self) -> usize {
if let Some(user) = self.user.as_ref() {
user.len() + 1 + self.host.len()
} else {
self.host.len()
}
}
pub fn write_to(&self, w: &mut (impl Write + ?Sized)) -> std::io::Result<()> {
if let Some(user) = self.user.as_ref() {
w.write_all(user.as_ref())?;
w.write_all(b"@")?;
}
w.write_all(self.host.as_ref())
}
}
impl std::fmt::Display for Source<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let nick = &self.nick;
if let Some(address) = self.userhost.as_ref() {
let host = &address.host;
if let Some(user) = &address.user {
write!(f, "{nick}!{user}@{host}")
} else {
write!(f, "{nick}@{host}")
}
} else {
write!(f, "{nick}")
}
}
}
impl std::fmt::Display for UserHost<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let host = &self.host;
if let Some(user) = self.user.as_ref() {
write!(f, "{user}@{host}")
} else {
write!(f, "{host}")
}
}
}
#[repr(transparent)]
#[derive(Clone, PartialEq, Eq, Hash, Debug)]
#[cfg_attr(feature = "serde", derive(serde_derive::Serialize, serde_derive::Deserialize))]
#[cfg_attr(feature = "serde", serde(transparent))]
pub struct SharedSource<'a>(std::sync::Arc<Source<'a>>);
impl<'a> SharedSource<'a> {
pub fn new(source: Source<'a>) -> Self {
Self(std::sync::Arc::new(source))
}
pub fn owning(self) -> Source<'static> {
match std::sync::Arc::try_unwrap(self.0) {
Ok(src) => src.owning(),
Err(arc) => (*arc).clone().owning(),
}
}
pub fn owning_merged(self) -> Source<'static> {
match std::sync::Arc::try_unwrap(self.0) {
Ok(src) => src.owning_merged(),
Err(arc) => (*arc).clone().owning_merged(),
}
}
}
impl<'a> std::ops::Deref for SharedSource<'a> {
type Target = Source<'a>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl std::fmt::Display for SharedSource<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.0)
}
}