1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
//! IRC message parsing.

//!

//! Twitch's chat is based on IRC, but not strictly conformant to the RFC-1459 and RFC-2812 specs.

//!

//! This provides a _Twitch-flavored_ IRC parser and types.

//!

//! # Parsing

//! You can use this crate to parse Twitch (IRC) messages from a `&str`.

//!

//! This will just borrow from the input `&str`.

//!

//! With the parsed type, you can further refine it into specific Twitch-oriented messages.

//!

//! ```

//! use twitchchat::{IrcMessage, MessageError};

//! // a raw message from the server

//! let input = "@key1=val;key2=true :user!user@user PRIVMSG #some_channel :\x01ACTION hello world\x01\r\n";

//!

//! type MsgResult<'a> = Result<IrcMessage<'a>, MessageError>;

//! // this'll return an iterator over any messages in the `input` string.

//! let mut messages: Vec<MsgResult<'_>> = twitchchat::irc::parse(input).collect();

//! // we should have only gotten 1 message

//! assert_eq!(messages.len(), 1);

//! // and unwrap whether it was an invalid message or not

//! messages.pop().unwrap();

//! ```

use crate::MaybeOwned;

/// A trait to convert an `IrcMessage` into `Self`.

pub trait FromIrcMessage<'a>: Sized {
    /// An error returned if this message could not be parsed.

    type Error;
    /// This method consumes an `IrcMessage` and tries to produce an instance of `Self`

    fn from_irc(msg: IrcMessage<'a>) -> Result<Self, Self::Error>;

    /// Consumes self returning the raw `MaybeOwned<'a>`

    fn into_inner(self) -> MaybeOwned<'a>;
}

/// A trait to convert a `Self` into an `IrcMessage`

pub trait IntoIrcMessage<'a>: Sized + 'a
where
    Self: FromIrcMessage<'a>,
{
    /// Consumes self producing an `IrcMessage`

    fn into_irc(self) -> IrcMessage<'a>;
}

impl<'a, T: 'a> IntoIrcMessage<'a> for T
where
    T: FromIrcMessage<'a>,
{
    fn into_irc(self) -> IrcMessage<'a> {
        IrcMessage::parse(self.into_inner()).expect("identity conversion")
    }
}

mod message;
pub use message::IrcMessage;

mod prefix;
pub use prefix::{Prefix, PrefixIndex};

pub(crate) mod tags;
pub use tags::{Tags, TagsIter};

mod tag_indices;
pub use tag_indices::TagIndices;

mod error;
pub use error::MessageError;

mod parser;
pub use parser::IrcParserIter;

/// Parses a string and returns an iterator over the `IrcMessages` in it.

///

/// This borrows from the input string.

pub fn parse(input: &str) -> IrcParserIter<'_> {
    IrcParserIter::new(input)
}

/// Attempts to parse one message.

///

/// This returns the index of the /next/ message (e.g, 0 for a single message) and the parsed message

pub fn parse_one(input: &str) -> Result<(usize, IrcMessage<'_>), MessageError> {
    const CRLF: &str = "\r\n";

    let pos = input
        .find(CRLF)
        .ok_or(MessageError::IncompleteMessage { pos: 0 })?
        + CRLF.len();

    let next = &input[..pos];
    let done = next.len() == input.len();

    let msg = IrcMessage::parse(crate::MaybeOwned::Borrowed(next))?;
    Ok((if done { 0 } else { pos }, msg))
}

// TODO add a test for parse_one. it was wrong