1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99
extern crate pulldown_cmark; extern crate regex; use pulldown_cmark::{CowStr, Event, LinkType, Tag}; use regex::Regex; static URL_REGEX: &str = r#"((https?|ftp)://|www.)[^\s/$.?#].[^\s]*[^.^\s]"#; enum LinkState { Open, Label, Close, } enum AutoLinkerState<'a> { Clear, Link(LinkState, CowStr<'a>, CowStr<'a>), TrailingText(CowStr<'a>), } pub struct AutoLinker<'a, I> { iter: I, state: AutoLinkerState<'a>, regex: Regex, } impl<'a, I> AutoLinker<'a, I> { pub fn new(iter: I) -> Self { Self { iter, state: AutoLinkerState::Clear, regex: Regex::new(URL_REGEX).unwrap(), } } } impl<'a, I> Iterator for AutoLinker<'a, I> where I: Iterator<Item = Event<'a>>, { type Item = Event<'a>; fn next(&mut self) -> Option<Self::Item> { let text = match std::mem::replace(&mut self.state, AutoLinkerState::Clear) { AutoLinkerState::Clear => match self.iter.next() { Some(Event::Text(text)) => text, x => return x, }, AutoLinkerState::TrailingText(text) => text, AutoLinkerState::Link(link_state, link_text, trailing_text) => match link_state { LinkState::Open => { self.state = AutoLinkerState::Link( LinkState::Label, link_text.clone(), trailing_text.clone(), ); return Some(Event::Start(Tag::Link( LinkType::Inline, link_text, "".into(), ))); } LinkState::Label => { self.state = AutoLinkerState::Link( LinkState::Close, link_text.clone(), trailing_text.clone(), ); return Some(Event::Text(link_text)); } LinkState::Close => { self.state = AutoLinkerState::TrailingText(trailing_text); return Some(Event::End(Tag::Link( LinkType::Inline, link_text, "".into(), ))); } }, }; match self.regex.find(&text) { Some(reg_match) => { let link_text = reg_match.as_str(); let leading_text = &text.as_ref()[..reg_match.start()]; let trailing_text = &text.as_ref()[reg_match.end()..]; self.state = AutoLinkerState::Link( LinkState::Open, link_text.to_owned().into(), trailing_text.to_owned().into(), ); Some(Event::Text(leading_text.to_owned().into())) } None => Some(Event::Text(text)), } } }