diff --git a/libimagentrymarkdown/Cargo.toml b/libimagentrymarkdown/Cargo.toml new file mode 100644 index 00000000..c752ccc7 --- /dev/null +++ b/libimagentrymarkdown/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "libimagentrymarkdown" +version = "0.1.0" +authors = ["Matthias Beyer "] + +[dependencies] +log = "0.3" +hoedown = "5.0.0" +crossbeam = "0.2" +url = "1.1" + +[dependencies.libimagstore] +path = "../libimagstore" + +[dependencies.libimagerror] +path = "../libimagerror" + diff --git a/libimagentrymarkdown/README.md b/libimagentrymarkdown/README.md new file mode 100644 index 00000000..0f858534 --- /dev/null +++ b/libimagentrymarkdown/README.md @@ -0,0 +1,9 @@ +# libimagentrymarkdown + +Helper crate to add useful functionality in a wrapper around +[hoedown](https://crates.io/crates/hoedown) for imag. + +Adds functionality to extract links, parse content into HTML and other things +which might be useful for markdown rendering in imag. + + diff --git a/libimagentrymarkdown/src/error.rs b/libimagentrymarkdown/src/error.rs new file mode 100644 index 00000000..3e42e21b --- /dev/null +++ b/libimagentrymarkdown/src/error.rs @@ -0,0 +1,11 @@ +generate_error_module!( + generate_error_types!(MarkdownError, MarkdownErrorKind, + MarkdownRenderError => "Markdown render error", + LinkParsingError => "Link parsing error" + ); +); + +pub use self::error::MarkdownError; +pub use self::error::MarkdownErrorKind; + + diff --git a/libimagentrymarkdown/src/html.rs b/libimagentrymarkdown/src/html.rs new file mode 100644 index 00000000..f9d251a8 --- /dev/null +++ b/libimagentrymarkdown/src/html.rs @@ -0,0 +1,82 @@ +use hoedown::{Markdown, Html as MdHtml}; +use hoedown::renderer::html::Flags as HtmlFlags; +use hoedown::renderer::Render; + +use result::Result; +use error::MarkdownErrorKind; +use libimagerror::into::IntoError; + +pub type HTML = String; + +pub fn to_html(buffer: &str) -> Result { + let md = Markdown::new(buffer); + let mut html = MdHtml::new(HtmlFlags::empty(), 0); + html.render(&md) + .to_str() + .map(String::from) + .map_err(Box::new) + .map_err(|e| MarkdownErrorKind::MarkdownRenderError.into_error_with_cause(e)) +} + +pub mod iter { + use result::Result; + use libimagstore::store::Entry; + use super::HTML; + use super::to_html; + + pub struct ToHtmlIterator> { + i: I + } + + impl> ToHtmlIterator { + + fn new(i: I) -> ToHtmlIterator { + ToHtmlIterator { i: i } + } + + } + + impl> Iterator for ToHtmlIterator { + type Item = Result; + + fn next(&mut self) -> Option { + self.i.next().map(|entry| to_html(&entry.get_content()[..])) + } + + } + + impl> From for ToHtmlIterator { + + fn from(obj: I) -> ToHtmlIterator { + ToHtmlIterator::new(obj) + } + + } + + + /// Iterate over `(Entry, Result)` tuples + pub struct WithHtmlIterator> { + i: I + } + + impl> WithHtmlIterator { + + fn new(i: I) -> WithHtmlIterator { + WithHtmlIterator { i: i } + } + + } + + impl> Iterator for WithHtmlIterator { + type Item = (Entry, Result); + + fn next(&mut self) -> Option { + self.i.next().map(|entry| { + let html = to_html(&entry.get_content()[..]); + (entry, html) + }) + } + + } + +} diff --git a/libimagentrymarkdown/src/lib.rs b/libimagentrymarkdown/src/lib.rs new file mode 100644 index 00000000..0f130c96 --- /dev/null +++ b/libimagentrymarkdown/src/lib.rs @@ -0,0 +1,12 @@ +#[macro_use] extern crate log; +extern crate crossbeam; +extern crate hoedown; +extern crate url; +extern crate libimagstore; +#[macro_use] extern crate libimagerror; + +pub mod error; +pub mod html; +pub mod link; +pub mod result; + diff --git a/libimagentrymarkdown/src/link.rs b/libimagentrymarkdown/src/link.rs new file mode 100644 index 00000000..8ac3500c --- /dev/null +++ b/libimagentrymarkdown/src/link.rs @@ -0,0 +1,143 @@ +use error::MarkdownErrorKind as MEK; +use result::Result; + +use hoedown::renderer::Render; +use hoedown::Buffer; +use hoedown::Markdown; +use url::Url; + +use libimagerror::into::IntoError; + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct Link { + pub title: String, + pub link: String, +} + +impl Link { + + /// Translate a `Link` into a `UrlLink` + fn into_urllink(self) -> Result { + Url::parse(&self.link[..]) + .map(move |link| UrlLink { title: self.title, link: link, }) + .map_err(Box::new) + .map_err(|e| MEK::LinkParsingError.into_error_with_cause(e)) + } + +} + +pub struct UrlLink { + pub title: String, + pub link: Url, +} + +struct LinkExtractor { + links: Vec, +} + +impl LinkExtractor { + + pub fn new() -> LinkExtractor { + LinkExtractor { links: vec![] } + } + + pub fn links(self) -> Vec { + self.links + } + +} + +impl Render for LinkExtractor { + + fn link(&mut self, + _: &mut Buffer, + content: Option<&Buffer>, + link: Option<&Buffer>, + _: Option<&Buffer>) + -> bool + { + let link = link.and_then(|l| l.to_str().ok()).map(String::from); + let content = content.and_then(|l| l.to_str().ok()).map(String::from); + + match (link, content) { + (Some(link), Some(content)) => { + self.links.push(Link { link: link, title: content }); + false + }, + + (_, _) => { + false + }, + } + + } + +} + +pub fn extract_links(buf: &str) -> Vec { + let mut le = LinkExtractor::new(); + le.render(&Markdown::new(buf)); + le.links() +} + +#[cfg(test)] +mod test { + use super::{Link, extract_links}; + + #[test] + fn test_one_link() { + let testtext = "Some [example text](http://example.com)."; + + let exp = Link { + title: String::from("example text"), + link: String::from("http://example.com"), + }; + + let mut links = extract_links(testtext); + assert_eq!(1, links.len()); + assert_eq!(exp, links.pop().unwrap()) + } + + #[test] + fn test_two_similar_links() { + let testtext = r#" +Some [example text](http://example.com). +Some more [example text](http://example.com). + "#; + + let exp = Link { + title: String::from("example text"), + link: String::from("http://example.com"), + }; + + let mut links = extract_links(&testtext[..]); + assert_eq!(2, links.len()); + assert_eq!(exp, links.pop().unwrap()); + assert_eq!(exp, links.pop().unwrap()); + } + + #[test] + fn test_two_links() { + let testtext = r#" +Some [example text](http://example.com). +Some more [foo](http://example.com/foo). + "#; + + let exp1 = Link { + title: String::from("example text"), + link: String::from("http://example.com"), + }; + + let exp2 = Link { + title: String::from("foo"), + link: String::from("http://example.com/foo"), + }; + + let mut links = extract_links(&testtext[..]); + assert_eq!(2, links.len()); + assert_eq!(exp2, links.pop().unwrap()); + assert_eq!(exp1, links.pop().unwrap()); + } + +} + diff --git a/libimagentrymarkdown/src/result.rs b/libimagentrymarkdown/src/result.rs new file mode 100644 index 00000000..55aa68e5 --- /dev/null +++ b/libimagentrymarkdown/src/result.rs @@ -0,0 +1,6 @@ +use std::result::Result as RResult; + +use error::MarkdownError; + +pub type Result = RResult; +