Create initial implementation

This commit is contained in:
Ethan Henderson 2022-07-30 02:04:45 +01:00
parent 57780855b4
commit d7e6b99c30
3 changed files with 204 additions and 0 deletions

View file

@ -6,3 +6,6 @@ edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
[dev-dependencies]
collection_macros = "0.2.0"

146
src/lib.rs Normal file
View file

@ -0,0 +1,146 @@
// BSD 3-Clause License
//
// Copyright (c) 2022-present, Ethan Henderson
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this
// list of conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation
// and/or other materials provided with the distribution.
//
// 3. Neither the name of the copyright holder nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
mod line;
pub use line::Line;
use std::collections::HashMap;
type ParseResult<T> = Result<T, ParseError>;
#[derive(Debug, Clone)]
pub struct ParseError {
pub details: String,
}
impl ParseError {
pub fn new(details: &str) -> Self {
Self {
details: details.into(),
}
}
}
impl std::fmt::Display for ParseError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.details)
}
}
fn find_index(text: &str, char: char, start: usize) -> Option<usize> {
for (k, _) in text.match_indices(char) {
if k > start {
return Some(k);
}
}
None
}
pub fn parse(line: &str) -> ParseResult<Line> {
if line.is_empty() {
return Err(ParseError::new("line length cannot be 0"));
}
let mut idx = 0;
let mut tags: HashMap<String, String> = HashMap::new();
let mut source: Option<String> = None;
// 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;
}
// 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 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)]
mod test_lib {
use super::parse;
use collection_macros::hashmap;
use std::collections::HashMap;
#[test]
fn test_partial() {
let line = parse("PRIVMSG #rickastley :Never gonna give you up!").unwrap();
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!"]);
}
#[test]
fn test_full() {
let line = parse("@id=123;name=rick :nick!user@host.tmi.twitch.tv PRIVMSG #rickastley :Never gonna give you up!").unwrap();
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!"]);
}
}

55
src/line.rs Normal file
View file

@ -0,0 +1,55 @@
// BSD 3-Clause License
//
// Copyright (c) 2022-present, Ethan Henderson
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this
// list of conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation
// and/or other materials provided with the distribution.
//
// 3. Neither the name of the copyright holder nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
use std::collections::HashMap;
#[derive(Debug, Clone, Default)]
pub struct Line {
pub tags: HashMap<String, String>,
pub source: Option<String>,
pub command: String,
pub params: Vec<String>,
}
impl Line {
pub fn new(
tags: HashMap<String, String>,
source: Option<String>,
command: &str,
params: Vec<String>,
) -> Self {
Self {
tags,
source,
command: command.to_string(),
params,
}
}
}