Create initial implementation
This commit is contained in:
		
							parent
							
								
									57780855b4
								
							
						
					
					
						commit
						d7e6b99c30
					
				| 
						 | 
				
			
			@ -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
									
								
							
							
						
						
									
										146
									
								
								src/lib.rs
									
									
									
									
									
										Normal 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
									
								
							
							
						
						
									
										55
									
								
								src/line.rs
									
									
									
									
									
										Normal 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,
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
		Loading…
	
		Reference in a new issue