use regex::Regex; use std::str; #[derive(PartialOrd, Ord, PartialEq, Eq, Clone, Debug)] pub enum Org { Unknown(String), Header(u16, String), Properties(Vec<(String, String)>) } type OrgDocument = Vec; macro_rules! matchers { ( $($id:ident <- $e:expr;)* ) => { lazy_static! { $(static ref $id: Regex = Regex::new($e).unwrap();)* } } } matchers! { header <- r"^(\*)+ (.*)"; properties_start <- r"^\s*:PROPERTIES:\s*$"; property <- r"^\s*:([^:]*): (.*)$"; properties_end <- r"^\s*:END:\s*$"; } struct Parser> { doc: OrgDocument, iter: I, } impl> Parser { fn go(&mut self) { while let Some(line) = self.iter.next() { if let Some(g) = header.captures(&line) { self.doc.push( Org::Header( g.get(1).unwrap().as_str().len() as u16, g.get(2).unwrap().as_str().to_string())); } else if properties_start.is_match(&line) { &mut self.go_properties(line.clone()); } else { self.doc.push(Org::Unknown(line.to_string())); } } } fn go_properties(&mut self, first_line: String) { let mut fallback = vec!(first_line); let mut properties = vec!(); while let Some(line) = self.iter.next() { fallback.push(line.clone()); if let Some(g) = property.captures(&line) { properties.push(( g.get(1).unwrap().as_str().to_string(), g.get(2).unwrap().as_str().to_string())); } else if properties_end.is_match(&line) { break; } else { self.doc.push(Org::Unknown(fallback.join("\n"))); return; } } self.doc.push(Org::Properties(properties)); } } pub fn parse(input: &str) -> OrgDocument { let mut parser = Parser{ doc: vec!(), iter: input.split('\n').map(|line| line.to_string()) }; parser.go(); parser.doc } #[cfg(test)] mod tests { use super::*; fn s>(m: S) -> String { m.as_ref().to_string() } #[test] fn parse_unknown() { let doc = "hello\nhello"; assert_eq!(parse(doc), vec!(Org::Unknown("hello".to_string()), Org::Unknown("hello".to_string()))); } #[test] fn parse_header() { let doc = "* hello"; assert_eq!(parse(doc), vec!(Org::Header(1, "hello".to_string()))); assert_eq!(parse(" * hello"), vec!(Org::Unknown(" * hello".to_string()))); } #[test] fn parse_properties() { let doc = ":PROPERTIES: :VERSION: 1.0 :ANIMAL: dog :END:"; assert_eq!(parse(doc), vec!(Org::Properties(vec!( (s("VERSION"), s("1.0")), (s("ANIMAL"), s("dog")))))); let doc = ":PROPERTIES:\ninvalid\n:END:"; assert_eq!(parse(doc), vec!(Org::Unknown(s(":PROPERTIES:\ninvalid")), Org::Unknown(s(":END:")))); } }