use crate::directive::Directive; use crate::error::SiteError; use crate::token::{Token, TokenParser, TokenPatterns}; use crate::wikitext::{ParsedDirective, Snippet, WikiLink}; use std::collections::HashMap; use std::convert::TryFrom; #[derive(Debug)] pub struct WikitextParser { tokens: Vec, } impl WikitextParser { pub fn new(input: &str, patterns: &TokenPatterns) -> Self { let mut p = TokenParser::new(input, patterns); let mut tokens = vec![]; loop { let token = p.parse(); if token == Token::End { break; } tokens.push(token); } Self { tokens } } pub fn parse(&mut self) -> Result, SiteError> { if self.tokens.is_empty() { return Ok(None); } let snippet = match &self.tokens[..] { [Token::OpenBrackets, Token::Word(target), Token::CloseBrackets, ..] => { let wikilink = WikiLink::new(target, target); let snippet = Snippet::WikiLink(wikilink); self.tokens.drain(..3); snippet } [Token::OpenBrackets, Token::Word(word), ..] => { let mut link_text = word.to_string(); let mut target = None; self.tokens.drain(..2); loop { match &self.tokens[..] { [Token::Spaces(_), ..] => { self.tokens.drain(..1); link_text.push(' '); } [Token::Word(word), ..] => { link_text.push_str(word); self.tokens.drain(..1); } [Token::CloseBrackets, ..] => { self.tokens.drain(..1); break; } [Token::Pipe, Token::Word(word), Token::CloseBrackets, ..] => { target = Some(word.to_string()); self.tokens.drain(..3); break; } _ => panic!("yikes: {:?}", self.tokens), } } if target.is_none() { target = Some(link_text.clone()); } let wikilink = WikiLink::new(&link_text, &target.unwrap()); Snippet::WikiLink(wikilink) } [Token::OpenBrackets, Token::Bang, Token::Word(name), ..] => { let name = name.to_string(); let mut args = HashMap::new(); self.tokens.drain(..3); loop { match &self.tokens[..] { [Token::Spaces(_), ..] => { self.tokens.drain(..1); } [Token::CloseBrackets, ..] => { self.tokens.drain(..1); break; } [Token::Word(word), Token::Spaces(_), ..] => { args.insert(word.to_string(), "".to_string()); self.tokens.drain(..2); } [Token::Word(word), Token::CloseBrackets, ..] => { args.insert(word.to_string(), "".to_string()); self.tokens.drain(..2); break; } [Token::Word(name), Token::Equals, Token::Word(value), ..] => { args.insert(name.to_string(), value.to_string()); self.tokens.drain(..3); } [Token::Word(name), Token::Equals, Token::QuotedValue(value), ..] => { args.insert(name.to_string(), value.to_string()); self.tokens.drain(..3); } _ => panic!("yikes: {:?}", self.tokens), } } let p = ParsedDirective::new(&name, args)?; Snippet::Directive(Directive::try_from(p)?) } [Token::Bang, Token::OpenBracket, ..] => { let mut link_text = String::new(); #[allow(unused_assignments)] let mut target = None; self.tokens.drain(..2); loop { match &self.tokens[..] { [Token::Word(word), ..] => { link_text.push_str(word); self.tokens.drain(..1); } [Token::Spaces(_), ..] => { link_text.push(' '); self.tokens.drain(..1); } [Token::ClosedBracket, Token::OpenParens, Token::Word(word), Token::ClosedParens, ..] => { target = Some(word.to_string()); self.tokens.drain(..4); break; } _ => panic!("yikes: {:?}", self.tokens), } } Snippet::Markdown(format!("![{}]({})", link_text, target.unwrap())) } [Token::Markdown(text), ..] => { let snippet = Snippet::Markdown(text.to_string()); self.tokens.drain(..1); snippet } [Token::Spaces(s), ..] => { let snippet = Snippet::Markdown(s.to_string()); self.tokens.drain(..1); snippet } [Token::Word(text), ..] => { let snippet = Snippet::Markdown(text.to_string()); self.tokens.drain(..1); snippet } [Token::Equals, ..] => { self.tokens.drain(..1); Snippet::Markdown("=".into()) } [Token::Pipe, ..] => { self.tokens.drain(..1); Snippet::Markdown("|".into()) } [Token::OpenParens, ..] => { self.tokens.drain(..1); Snippet::Markdown("(".into()) } [Token::ClosedParens, ..] => { self.tokens.drain(..1); Snippet::Markdown(")".into()) } [Token::OpenBracket, ..] => { self.tokens.drain(..1); Snippet::Markdown("[".into()) } [Token::ClosedBracket, ..] => { self.tokens.drain(..1); Snippet::Markdown("]".into()) } [Token::OpenBrackets, ..] => { self.tokens.drain(..1); Snippet::Markdown("[[".into()) } [Token::CloseBrackets, ..] => { self.tokens.drain(..1); Snippet::Markdown("]]".into()) } _ => panic!("eeek: {:?}", self.tokens), }; Ok(Some(snippet)) } } #[cfg(test)] mod test { use super::{Directive, Snippet, TokenPatterns, WikiLink, WikitextParser}; use crate::directive::Img; #[test] fn empty_input() { let patterns = TokenPatterns::default(); let mut p = WikitextParser::new("", &patterns); assert_eq!(p.parse().unwrap(), None); } #[test] fn plain_markdown() { let patterns = TokenPatterns::default(); let mut p = WikitextParser::new("hello, world", &patterns); assert_eq!(p.parse().unwrap(), Some(Snippet::Markdown("hello".into()))); assert_eq!( p.parse().unwrap(), Some(Snippet::Markdown(", world".into())) ); assert_eq!(p.parse().unwrap(), None); } #[test] fn simple_wikilink() { let patterns = TokenPatterns::default(); let mut p = WikitextParser::new("hello, [[planet-earth]]", &patterns); assert_eq!(p.parse().unwrap(), Some(Snippet::Markdown("hello".into()))); assert_eq!(p.parse().unwrap(), Some(Snippet::Markdown(", ".into()))); assert_eq!( p.parse().unwrap(), Some(Snippet::WikiLink(WikiLink::new( "planet-earth", "planet-earth" ))) ); assert_eq!(p.parse().unwrap(), None); } #[test] fn simple_wikilink_to_subpage() { let patterns = TokenPatterns::default(); let mut p = WikitextParser::new("hello, [[planets/earth]]", &patterns); assert_eq!(p.parse().unwrap(), Some(Snippet::Markdown("hello".into()))); assert_eq!(p.parse().unwrap(), Some(Snippet::Markdown(", ".into()))); assert_eq!( p.parse().unwrap(), Some(Snippet::WikiLink(WikiLink::new( "planets/earth", "planets/earth" ))) ); assert_eq!(p.parse().unwrap(), None); } #[test] fn complex_wikilink() { let patterns = TokenPatterns::default(); let mut p = WikitextParser::new("hello, [[whomever we greet|planets/earth]]", &patterns); assert_eq!(p.parse().unwrap(), Some(Snippet::Markdown("hello".into()))); assert_eq!(p.parse().unwrap(), Some(Snippet::Markdown(", ".into()))); assert_eq!( p.parse().unwrap(), Some(Snippet::WikiLink(WikiLink::new( "whomever we greet", "planets/earth" ))) ); assert_eq!(p.parse().unwrap(), None); } #[test] fn bracket() { let patterns = TokenPatterns::default(); let mut p = WikitextParser::new("[world", &patterns); assert_eq!(p.parse().unwrap(), Some(Snippet::Markdown("[".into()))); assert_eq!(p.parse().unwrap(), Some(Snippet::Markdown("world".into()))); assert_eq!(p.parse().unwrap(), None); } #[test] fn simple_directive() { let patterns = TokenPatterns::default(); let mut p = WikitextParser::new("[[!simple]]", &patterns); assert_eq!( p.parse().unwrap(), Some(Snippet::Directive(Directive::Simple)) ); assert_eq!(p.parse().unwrap(), None); } #[test] fn directive_unnamed_arg() { let patterns = TokenPatterns::default(); let mut p = WikitextParser::new("[[!unnamedarg foo.jpg]]", &patterns); assert_eq!( p.parse().unwrap(), Some(Snippet::Directive(Directive::UnnamedArg)) ); assert_eq!(p.parse().unwrap(), None); } #[test] fn directive_simple_arg() { let patterns = TokenPatterns::default(); let mut p = WikitextParser::new("[[!simplearg foo=bar]]", &patterns); assert_eq!( p.parse().unwrap(), Some(Snippet::Directive(Directive::SimpleArg)) ); assert_eq!(p.parse().unwrap(), None); } #[test] fn directive_quoted_arg() { let patterns = TokenPatterns::default(); let mut p = WikitextParser::new(r#"[[!quotedarg bar="foobar"]]"#, &patterns); assert_eq!( p.parse().unwrap(), Some(Snippet::Directive(Directive::QuotedArg)) ); assert_eq!(p.parse().unwrap(), None); } #[test] fn directive_multiline_arg() { let patterns = TokenPatterns::default(); let mut p = WikitextParser::new( r#"[[!multilinearg yo="""foo bar"""]]"#, &patterns, ); assert_eq!( p.parse().unwrap(), Some(Snippet::Directive(Directive::MultilineArg)) ); assert_eq!(p.parse().unwrap(), None); } #[test] fn directive_multiple_args() { let patterns = TokenPatterns::default(); let mut p = WikitextParser::new(r#"[[!img foo.jpg class=image]]"#, &patterns); assert_eq!( p.parse().unwrap(), Some(Snippet::Directive(Directive::Img(Img::new( "foo.jpg".into() )))) ); assert_eq!(p.parse().unwrap(), None); } }