@@ 1,11 @@
+[package]
+name = "pdcm-linkify"
+version = "0.1.0"
+authors = ["Marcus Klaas <mail@marcusklaas.nl>", "Colin Reeder <vpzomtrrfrt@gmail.com>"]
+edition = "2015"
+
+# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+
+[dependencies]
+pulldown-cmark = "0.8.0"
+regex = "1.4.2"
@@ 1,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)),
+ }
+ }
+}