//! Client-to-Server IRC protocol. pub mod client; mod prelude; pub mod server; #[cfg(test)] mod testkit; pub mod user; use crate::prelude::Str; use nom::{ branch::alt, bytes::complete::{tag, take, take_while}, IResult, }; use tokio::io::{AsyncWrite, AsyncWriteExt}; /// Single message tag value. #[derive(Clone, Debug, PartialEq, Eq)] pub struct Tag { key: Str, value: Option, } fn receiver(input: &str) -> IResult<&str, &str> { take_while(|i| i != '\n' && i != '\r' && i != ' ')(input) } fn token(input: &str) -> IResult<&str, &str> { take_while(|i| i != '\n' && i != '\r')(input) } fn params(input: &str) -> IResult<&str, &str> { take_while(|i| i != '\n' && i != '\r' && i != ':')(input) } #[derive(Clone, Debug, PartialEq, Eq)] pub enum Chan { /// # — network-global channel, available from any server in the network. Global(Str), /// & — server-local channel, available only to connections to the same server. Rarely used in practice. Local(Str), } impl Chan { pub async fn write_async(&self, writer: &mut (impl AsyncWrite + Unpin)) -> std::io::Result<()> { match self { Chan::Global(name) => { writer.write_all(b"#").await?; writer.write_all(name.as_bytes()).await?; } Chan::Local(name) => { writer.write_all(b"&").await?; writer.write_all(name.as_bytes()).await?; } } Ok(()) } } fn chan(input: &str) -> IResult<&str, Chan> { fn chan_global(input: &str) -> IResult<&str, Chan> { let (input, _) = tag("#")(input)?; let (input, name) = receiver(input)?; Ok((input, Chan::Global(name.into()))) } fn chan_local(input: &str) -> IResult<&str, Chan> { let (input, _) = tag("&")(input)?; let (input, name) = receiver(input)?; Ok((input, Chan::Local(name.into()))) } alt((chan_global, chan_local))(input) } #[derive(Clone, Debug, PartialEq, Eq)] pub enum Recipient { Nick(Str), Chan(Chan), } impl Recipient { pub async fn write_async(&self, writer: &mut (impl AsyncWrite + Unpin)) -> std::io::Result<()> { match self { Recipient::Nick(nick) => writer.write_all(nick.as_bytes()).await?, Recipient::Chan(chan) => chan.write_async(writer).await?, } Ok(()) } } fn recipient(input: &str) -> IResult<&str, Recipient> { fn recipient_chan(input: &str) -> IResult<&str, Recipient> { let (input, chan) = chan(input)?; Ok((input, Recipient::Chan(chan))) } fn recipient_nick(input: &str) -> IResult<&str, Recipient> { let (input, nick) = receiver(input)?; Ok((input, Recipient::Nick(nick.into()))) } alt((recipient_chan, recipient_nick))(input) } #[cfg(test)] mod test { use assert_matches::*; use super::*; use crate::testkit::*; #[test] fn test_chan_global() { let input = "#testchan"; let expected = Chan::Global("testchan".into()); let result = chan(input); assert_matches!(result, Ok((_, result)) => assert_eq!(expected, result)); let mut bytes = vec![]; sync_future(expected.write_async(&mut bytes)) .unwrap() .unwrap(); assert_eq!(bytes.as_slice(), input.as_bytes()); } #[test] fn test_chan_local() { let input = "&localchan"; let expected = Chan::Local("localchan".into()); let result = chan(input); assert_matches!(result, Ok((_, result)) => assert_eq!(expected, result)); let mut bytes = vec![]; sync_future(expected.write_async(&mut bytes)) .unwrap() .unwrap(); assert_eq!(bytes.as_slice(), input.as_bytes()); } #[test] fn test_recipient_user() { let input = "User"; let expected = Recipient::Nick("User".into()); let result = recipient(input); assert_matches!(result, Ok((_, result)) => assert_eq!(expected, result)); let mut bytes = vec![]; sync_future(expected.write_async(&mut bytes)) .unwrap() .unwrap(); assert_eq!(bytes.as_slice(), input.as_bytes()); } }