Allow parse function to accept multiple lines
This commit is contained in:
parent
8a962bd25c
commit
80fe0fcfef
189
src/lib.rs
189
src/lib.rs
|
@ -37,8 +37,8 @@
|
||||||
//! ```
|
//! ```
|
||||||
//! let msg = "@id=123;name=rick :nick!user@host.tmi.twitch.tv PRIVMSG #rickastley :Never gonna give you up!";
|
//! let msg = "@id=123;name=rick :nick!user@host.tmi.twitch.tv PRIVMSG #rickastley :Never gonna give you up!";
|
||||||
//! match ircparser::parse(msg) {
|
//! match ircparser::parse(msg) {
|
||||||
//! Ok(x) => {
|
//! Ok(mut x) => {
|
||||||
//! let line = x;
|
//! let line = x.pop_front().unwrap();
|
||||||
//!
|
//!
|
||||||
//! assert_eq!(&line.tags["id"], "123");
|
//! assert_eq!(&line.tags["id"], "123");
|
||||||
//! if line.source.is_some() {
|
//! if line.source.is_some() {
|
||||||
|
@ -58,7 +58,7 @@
|
||||||
mod line;
|
mod line;
|
||||||
|
|
||||||
pub use line::Line;
|
pub use line::Line;
|
||||||
use std::collections::HashMap;
|
use std::collections::{HashMap, VecDeque};
|
||||||
|
|
||||||
type ParseResult<T> = Result<T, ParseError>;
|
type ParseResult<T> = Result<T, ParseError>;
|
||||||
|
|
||||||
|
@ -107,17 +107,20 @@ fn find_index(text: &str, char: char, start: usize) -> Option<usize> {
|
||||||
/// Parses an IRC message.
|
/// Parses an IRC message.
|
||||||
///
|
///
|
||||||
/// # Arguments
|
/// # Arguments
|
||||||
/// - `line` - The line you want to parse.
|
/// - `text` - The text you want to parse. This can comprise of multiple
|
||||||
|
/// lines. In this case, each line (separated by a newline character)
|
||||||
|
/// will be a separate element in the return value.
|
||||||
///
|
///
|
||||||
/// # Returns
|
/// # Returns
|
||||||
/// - [`Line`] - An instance representing a parsed line.
|
/// - [`VecDeque<Line>`] - A [`VecDeque`] of all parsed [`Line`]s.
|
||||||
///
|
///
|
||||||
/// # Example
|
/// # Example
|
||||||
/// ```
|
/// ```
|
||||||
/// let msg = "@id=123;name=rick :nick!user@host.tmi.twitch.tv PRIVMSG #rickastley :Never gonna give you up!";
|
/// let msg = "@id=123;name=rick :nick!user@host.tmi.twitch.tv PRIVMSG #rickastley :Never gonna give you up!";
|
||||||
|
///
|
||||||
/// match ircparser::parse(msg) {
|
/// match ircparser::parse(msg) {
|
||||||
/// Ok(x) => {
|
/// Ok(mut x) => {
|
||||||
/// let line = x;
|
/// let line = x.pop_front().unwrap();
|
||||||
///
|
///
|
||||||
/// assert_eq!(&line.tags["id"], "123");
|
/// assert_eq!(&line.tags["id"], "123");
|
||||||
/// if line.source.is_some() {
|
/// if line.source.is_some() {
|
||||||
|
@ -133,51 +136,62 @@ fn find_index(text: &str, char: char, start: usize) -> Option<usize> {
|
||||||
/// }
|
/// }
|
||||||
/// };
|
/// };
|
||||||
/// ```
|
/// ```
|
||||||
pub fn parse(line: &str) -> ParseResult<Line> {
|
///
|
||||||
if line.is_empty() {
|
/// # Notice
|
||||||
return Err(ParseError::new("line length cannot be 0"));
|
/// The behaviour of this function changed in v0.2.0. It can now accept
|
||||||
}
|
/// multiple lines at once, but as a consequence, now returns a
|
||||||
|
/// [`VecDeque`] of [`Line`] objects instead of a single [`Line`].
|
||||||
|
pub fn parse(text: &str) -> ParseResult<VecDeque<Line>> {
|
||||||
|
let mut parsed_lines: VecDeque<Line> = VecDeque::new();
|
||||||
|
|
||||||
let mut idx = 0;
|
for line in text.replace("\r", "").split("\n") {
|
||||||
let mut tags: HashMap<String, String> = HashMap::new();
|
if line.is_empty() {
|
||||||
let mut source: Option<String> = None;
|
return Err(ParseError::new("line length cannot be 0"));
|
||||||
|
|
||||||
// Parse tags component.
|
|
||||||
if line.starts_with('@') {
|
|
||||||
idx = line.find(' ').unwrap();
|
|
||||||
|
|
||||||
for part in Some(&line[1..idx]).unwrap().split(';') {
|
|
||||||
let kv: Vec<&str> = part.split('=').collect();
|
|
||||||
tags.insert(kv[0].to_string(), kv[1].to_string());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
idx += 1;
|
let mut idx = 0;
|
||||||
}
|
let mut tags: HashMap<String, String> = HashMap::new();
|
||||||
|
let mut source: Option<String> = None;
|
||||||
|
|
||||||
// Parse source component.
|
// Parse tags component.
|
||||||
if line.chars().nth(idx).unwrap() == ':' {
|
if line.starts_with('@') {
|
||||||
|
idx = line.find(' ').unwrap();
|
||||||
|
|
||||||
|
for part in Some(&line[1..idx]).unwrap().split(';') {
|
||||||
|
let kv: Vec<&str> = part.split('=').collect();
|
||||||
|
tags.insert(kv[0].to_string(), kv[1].to_string());
|
||||||
|
}
|
||||||
|
|
||||||
|
idx += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse source component.
|
||||||
|
if line.chars().nth(idx).unwrap() == ':' {
|
||||||
|
let end_idx = find_index(line, ' ', idx).unwrap();
|
||||||
|
source = Some(line[idx..end_idx].to_string());
|
||||||
|
idx = end_idx + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse command component.
|
||||||
let end_idx = find_index(line, ' ', idx).unwrap();
|
let end_idx = find_index(line, ' ', idx).unwrap();
|
||||||
source = Some(line[idx..end_idx].to_string());
|
let command = &line[idx..end_idx];
|
||||||
idx = end_idx + 1;
|
idx = end_idx + 1;
|
||||||
|
|
||||||
|
let c_idx = match find_index(line, ':', idx) {
|
||||||
|
Some(x) => x - 1,
|
||||||
|
None => line.len(),
|
||||||
|
};
|
||||||
|
|
||||||
|
// Parse params component.
|
||||||
|
let mut params: Vec<String> = line[idx..c_idx].split(' ').map(|x| x.to_string()).collect();
|
||||||
|
if c_idx != line.len() {
|
||||||
|
params.push(line[c_idx + 2..].to_string());
|
||||||
|
}
|
||||||
|
|
||||||
|
parsed_lines.push_back(Line::new(tags, source, command, params));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parse command component.
|
Ok(parsed_lines)
|
||||||
let end_idx = find_index(line, ' ', idx).unwrap();
|
|
||||||
let command = &line[idx..end_idx];
|
|
||||||
idx = end_idx + 1;
|
|
||||||
|
|
||||||
let c_idx = match find_index(line, ':', idx) {
|
|
||||||
Some(x) => x - 1,
|
|
||||||
None => line.len(),
|
|
||||||
};
|
|
||||||
|
|
||||||
// Parse params component.
|
|
||||||
let mut params: Vec<String> = line[idx..c_idx].split(' ').map(|x| x.to_string()).collect();
|
|
||||||
if c_idx != line.len() {
|
|
||||||
params.push(line[c_idx + 2..].to_string());
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(Line::new(tags, source, command, params))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
@ -187,38 +201,58 @@ mod test_lib {
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_partial() {
|
fn test_single_partial() {
|
||||||
let line = parse("PRIVMSG #rickastley :Never gonna give you up!").unwrap();
|
let msg = "PRIVMSG #rickastley :Never gonna give you up!";
|
||||||
assert_eq!(line.tags, HashMap::new());
|
match parse(msg) {
|
||||||
assert_eq!(line.source, None);
|
Ok(mut x) => {
|
||||||
assert_eq!(line.command, "PRIVMSG");
|
let line = x.pop_front().unwrap();
|
||||||
assert_eq!(line.params, vec!["#rickastley", "Never gonna give you up!"]);
|
|
||||||
|
assert_eq!(line.tags, HashMap::new());
|
||||||
|
assert_eq!(line.source, None);
|
||||||
|
assert_eq!(line.command, "PRIVMSG");
|
||||||
|
assert_eq!(line.params, vec!["#rickastley", "Never gonna give you up!"]);
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
println!("A parsing error occured: {e}");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_full() {
|
fn test_single_full() {
|
||||||
let line = parse("@id=123;name=rick :nick!user@host.tmi.twitch.tv PRIVMSG #rickastley :Never gonna give you up!").unwrap();
|
let msg = "@id=123;name=rick :nick!user@host.tmi.twitch.tv PRIVMSG #rickastley :Never gonna give you up!";
|
||||||
assert_eq!(
|
match parse(msg) {
|
||||||
line.tags,
|
Ok(mut x) => {
|
||||||
hashmap! {
|
let line = x.pop_front().unwrap();
|
||||||
String::from("id") => String::from("123"),
|
|
||||||
String::from("name") => String::from("rick"),
|
assert_eq!(
|
||||||
|
line.tags,
|
||||||
|
hashmap! {
|
||||||
|
String::from("id") => String::from("123"),
|
||||||
|
String::from("name") => String::from("rick"),
|
||||||
|
}
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
line.source,
|
||||||
|
Some(String::from(":nick!user@host.tmi.twitch.tv"))
|
||||||
|
);
|
||||||
|
assert_eq!(line.command, "PRIVMSG");
|
||||||
|
assert_eq!(line.params, vec!["#rickastley", "Never gonna give you up!"]);
|
||||||
}
|
}
|
||||||
);
|
Err(e) => {
|
||||||
assert_eq!(
|
println!("A parsing error occured: {e}");
|
||||||
line.source,
|
return;
|
||||||
Some(String::from(":nick!user@host.tmi.twitch.tv"))
|
}
|
||||||
);
|
};
|
||||||
assert_eq!(line.command, "PRIVMSG");
|
|
||||||
assert_eq!(line.params, vec!["#rickastley", "Never gonna give you up!"]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_readme_example() {
|
fn test_readme_example() {
|
||||||
let msg = "@id=123;name=rick :nick!user@host.tmi.twitch.tv PRIVMSG #rickastley :Never gonna give you up!";
|
let msg = "@id=123;name=rick :nick!user@host.tmi.twitch.tv PRIVMSG #rickastley :Never gonna give you up!";
|
||||||
match parse(msg) {
|
match parse(msg) {
|
||||||
Ok(x) => {
|
Ok(mut x) => {
|
||||||
let line = x;
|
let line = x.pop_front().unwrap();
|
||||||
|
|
||||||
assert_eq!(&line.tags["id"], "123");
|
assert_eq!(&line.tags["id"], "123");
|
||||||
if line.source.is_some() {
|
if line.source.is_some() {
|
||||||
|
@ -234,4 +268,27 @@ mod test_lib {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_multiline() {
|
||||||
|
let msg = "@id=123 PRIVMSG #rickastley :Never gonna give you up!\n@id=456 PRIVMSG #rickastley :Never gonna let you down!";
|
||||||
|
match parse(msg) {
|
||||||
|
Ok(mut x) => {
|
||||||
|
assert_eq!(x.len(), 2);
|
||||||
|
|
||||||
|
let l1 = x.pop_front().unwrap();
|
||||||
|
let l2 = x.pop_front().unwrap();
|
||||||
|
|
||||||
|
assert_eq!(&l1.tags["id"], "123");
|
||||||
|
assert_eq!(&l2.tags["id"], "456");
|
||||||
|
assert_eq!(l1.command, l2.command);
|
||||||
|
assert_eq!(l1.params[1], "Never gonna give you up!");
|
||||||
|
assert_eq!(l2.params[1], "Never gonna let you down!");
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
println!("A parsing error occured: {e}");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue