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
 | 
					# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[dependencies]
 | 
					[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