From f20c52b872654b6fb48702f48c395db297cbea6f Mon Sep 17 00:00:00 2001 From: Vincent Prouillet Date: Sat, 28 Nov 2020 13:04:49 +0100 Subject: [PATCH] Re-use markdown parser for markdown filter --- Cargo.lock | 86 +++++++---------- components/rendering/src/context.rs | 23 ++++- components/rendering/src/markdown.rs | 4 +- components/rendering/tests/markdown.rs | 2 +- components/site/src/tpls.rs | 4 +- components/templates/Cargo.toml | 4 +- components/templates/src/filters.rs | 96 ++++++++++++------- .../templates/src/global_fns/load_data.rs | 2 +- components/templates/src/global_fns/mod.rs | 6 +- components/templates/src/lib.rs | 1 - 10 files changed, 129 insertions(+), 99 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index fd2d6086..60a502e5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -343,7 +343,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dca26ee1f8d361640700bde38b2c37d8c22b3ce2d360e1fc1c74ea4b0aa7d775" dependencies = [ "cfg-if 1.0.0", - "crossbeam-utils 0.8.0", + "crossbeam-utils", ] [[package]] @@ -354,18 +354,18 @@ checksum = "94af6efb46fef72616855b036a624cf27ba656ffc9be1b9a3c931cfc7749a9a9" dependencies = [ "cfg-if 1.0.0", "crossbeam-epoch", - "crossbeam-utils 0.8.0", + "crossbeam-utils", ] [[package]] name = "crossbeam-epoch" -version = "0.9.0" +version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec0f606a85340376eef0d6d8fec399e6d4a544d648386c6645eb6d0653b27d9f" +checksum = "a1aaa739f95311c2c7887a76863f500026092fb1dce0161dab577e559ef3569d" dependencies = [ "cfg-if 1.0.0", "const_fn", - "crossbeam-utils 0.8.0", + "crossbeam-utils", "lazy_static", "memoffset", "scopeguard", @@ -373,32 +373,20 @@ dependencies = [ [[package]] name = "crossbeam-utils" -version = "0.7.2" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3c7c73a2d1e9fc0886a08b93e98eb643461230d5f1925e4036204d5f2e261a8" -dependencies = [ - "autocfg", - "cfg-if 0.1.10", - "lazy_static", -] - -[[package]] -name = "crossbeam-utils" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec91540d98355f690a86367e566ecad2e9e579f230230eb7c21398372be73ea5" +checksum = "02d96d1e189ef58269ebe5b97953da3274d83a93af647c2ddd6f9dab28cedb8d" dependencies = [ "autocfg", "cfg-if 1.0.0", - "const_fn", "lazy_static", ] [[package]] name = "csv" -version = "1.1.4" +version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc4666154fd004af3fd6f1da2e81a96fd5a81927fe8ddb6ecc79e2aa6e138b54" +checksum = "f9d58633299b24b515ac72a3f869f8b91306a3cec616a602843a383acd6f9e97" dependencies = [ "bstr", "csv-core", @@ -1026,11 +1014,11 @@ dependencies = [ [[package]] name = "ignore" -version = "0.4.16" +version = "0.4.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22dcbf2a4a289528dbef21686354904e1c694ac642610a9bff9e7df730d9ec72" +checksum = "b287fb45c60bb826a0dc68ff08742b9d88a2fea13d6e0c286b3172065aaf878c" dependencies = [ - "crossbeam-utils 0.7.2", + "crossbeam-utils", "globset", "lazy_static", "log", @@ -1381,9 +1369,9 @@ checksum = "0ee1c47aaa256ecabcaea351eae4a9b01ef39ed810004e298d2511ed284b1525" [[package]] name = "memoffset" -version = "0.5.6" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "043175f069eda7b85febe4a74abbaeff828d9f8b448515d3151a14a3542811aa" +checksum = "157b4208e3059a8f9e78d559edc658e13df41410cb3ae03979c83130067fdd87" dependencies = [ "autocfg", ] @@ -1467,9 +1455,9 @@ dependencies = [ [[package]] name = "miow" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c1f2f3b1cf331de6896aabf6e9d55dca90356cc9960cca7eaaf408a355ae919" +checksum = "ebd808424166322d4a38da87083bfddd3ac4c131334ed55856112eb06d46944d" dependencies = [ "kernel32-sys", "net2", @@ -1497,9 +1485,9 @@ dependencies = [ [[package]] name = "net2" -version = "0.2.35" +version = "0.2.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ebc3ec692ed7c9a255596c67808dee269f64655d8baf7b4f0638e51ba1d6853" +checksum = "d7cf75f38f16cb05ea017784dc6dbfd354f76c223dba37701734c4f5a9337d02" dependencies = [ "cfg-if 0.1.10", "libc", @@ -1668,9 +1656,9 @@ checksum = "13bd41f508810a131401606d54ac32a467c97172d74ba7662562ebba5ad07fa0" [[package]] name = "onig" -version = "6.1.0" +version = "6.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a155d13862da85473665694f4c05d77fb96598bdceeaf696433c84ea9567e20" +checksum = "30b46fd9edbc018f0be4e366c24c46db44fac49cd01c039ae85308088b089dd5" dependencies = [ "bitflags", "lazy_static", @@ -1680,9 +1668,9 @@ dependencies = [ [[package]] name = "onig_sys" -version = "69.5.1" +version = "69.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9bff06597a6b17855040955cae613af000fc0bfc8ad49ea68b9479a74e59292d" +checksum = "ed063c96cf4c0f2e5d09324409d158b38a0a85a7b90fbd68c8cad75c495d5775" dependencies = [ "cc", "pkg-config", @@ -2049,7 +2037,7 @@ checksum = "9ab346ac5921dc62ffa9f89b7a773907511cdfa5490c572ae9be1be33e8afa4a" dependencies = [ "crossbeam-channel", "crossbeam-deque", - "crossbeam-utils 0.8.0", + "crossbeam-utils", "lazy_static", "num_cpus", ] @@ -2169,9 +2157,9 @@ dependencies = [ [[package]] name = "ring" -version = "0.16.16" +version = "0.16.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b72b84d47e8ec5a4f2872e8262b8f8256c5be1c938a7d6d3a867a3ba8f722f74" +checksum = "70017ed5c555d79ee3538fc63ca09c70ad8f317dcadc1adc2c496b60c22bb24f" dependencies = [ "cc", "libc", @@ -2440,11 +2428,11 @@ checksum = "7acad6f34eb9e8a259d3283d1e8c1d34d7415943d4895f65cc73813c7396fc85" [[package]] name = "socket2" -version = "0.3.16" +version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fd8b795c389288baa5f355489c65e71fd48a02104600d15c4cfbc561e9e429d" +checksum = "2c29947abdee2a218277abeca306f25789c938e500ea5a9d4b12a5a504466902" dependencies = [ - "cfg-if 0.1.10", + "cfg-if 1.0.0", "libc", "redox_syscall", "winapi 0.3.9", @@ -2525,9 +2513,9 @@ dependencies = [ [[package]] name = "syn" -version = "1.0.48" +version = "1.0.52" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc371affeffc477f42a221a1e4297aedcea33d47d19b61455588bd9d8f6b19ac" +checksum = "6c1e438504729046a5cfae47f97c30d6d083c7d91d94603efdae3477fc070d4c" dependencies = [ "proc-macro2", "quote", @@ -2584,7 +2572,7 @@ dependencies = [ "library", "mockito", "nom-bibtex", - "pulldown-cmark", + "rendering", "reqwest", "serde_json", "sha2", @@ -2765,13 +2753,13 @@ checksum = "e987b6bf443f4b5b3b6f38704195592cca41c5bb7aedd3c3693c7081f8289860" [[package]] name = "tracing" -version = "0.1.21" +version = "0.1.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0987850db3733619253fe60e17cb59b82d37c7e6c0236bb81e4d6b87c879f27" +checksum = "9f47026cdc4080c07e49b37087de021820269d996f581aac150ef9e5583eefe3" dependencies = [ - "cfg-if 0.1.10", + "cfg-if 1.0.0", "log", - "pin-project-lite 0.1.11", + "pin-project-lite 0.2.0", "tracing-core", ] @@ -2891,9 +2879,9 @@ dependencies = [ [[package]] name = "unicode-segmentation" -version = "1.7.0" +version = "1.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db8716a166f290ff49dabc18b44aa407cb7c6dbe1aa0971b44b8a24b0ca35aae" +checksum = "bb0d2e7be6ae3a5fa87eed5fb451aff96f2573d2694942e40543ae0bbe19c796" [[package]] name = "unicode-width" diff --git a/components/rendering/src/context.rs b/components/rendering/src/context.rs index 8d877d1e..30249d36 100644 --- a/components/rendering/src/context.rs +++ b/components/rendering/src/context.rs @@ -1,3 +1,4 @@ +use std::borrow::Cow; use std::collections::HashMap; use config::Config; @@ -7,11 +8,11 @@ use tera::{Context, Tera}; /// All the information from the zola site that is needed to render HTML from markdown #[derive(Debug)] pub struct RenderContext<'a> { - pub tera: &'a Tera, + pub tera: Cow<'a, Tera>, pub config: &'a Config, pub tera_context: Context, pub current_page_permalink: &'a str, - pub permalinks: &'a HashMap, + pub permalinks: Cow<'a, HashMap>, pub insert_anchor: InsertAnchor, } @@ -25,13 +26,25 @@ impl<'a> RenderContext<'a> { ) -> RenderContext<'a> { let mut tera_context = Context::new(); tera_context.insert("config", config); - RenderContext { - tera, + Self { + tera: Cow::Borrowed(tera), tera_context, current_page_permalink, - permalinks, + permalinks: Cow::Borrowed(permalinks), insert_anchor, config, } } + + // In use in the markdown filter + pub fn from_config(config: &'a Config) -> RenderContext<'a> { + Self { + tera: Cow::Owned(Tera::default()), + tera_context: Context::new(), + current_page_permalink: "", + permalinks: Cow::Owned(HashMap::new()), + insert_anchor: InsertAnchor::None, + config, + } + } } diff --git a/components/rendering/src/markdown.rs b/components/rendering/src/markdown.rs index d7975cbc..15154c07 100644 --- a/components/rendering/src/markdown.rs +++ b/components/rendering/src/markdown.rs @@ -105,7 +105,7 @@ fn fix_link( // - it could be a link to a co-located asset // - it could be a normal link let result = if link.starts_with("@/") { - match resolve_internal_link(&link, context.permalinks) { + match resolve_internal_link(&link, &context.permalinks) { Ok(resolved) => { if resolved.anchor.is_some() { internal_links_with_anchors @@ -384,7 +384,7 @@ pub fn markdown_to_html(content: &str, context: &RenderContext) -> ResultThis – is β€œit”…

\n"); -} \ No newline at end of file +} diff --git a/components/site/src/tpls.rs b/components/site/src/tpls.rs index 6a2c9a11..2b224b57 100644 --- a/components/site/src/tpls.rs +++ b/components/site/src/tpls.rs @@ -5,7 +5,7 @@ use tera::Tera; use crate::Site; use config::Config; use errors::{bail, Error, Result}; -use templates::{global_fns, ZOLA_TERA}; +use templates::{filters, global_fns, ZOLA_TERA}; use utils::templates::rewrite_theme_paths; pub fn load_tera(path: &Path, config: &Config) -> Result { @@ -50,6 +50,8 @@ pub fn load_tera(path: &Path, config: &Config) -> Result { /// Adds global fns that are to be available to shortcodes while rendering markdown pub fn register_early_global_fns(site: &mut Site) { + site.tera.register_filter("markdown", filters::MarkdownFilter::new(site.config.clone())); + site.tera.register_function( "get_url", global_fns::GetUrl::new( diff --git a/components/templates/Cargo.toml b/components/templates/Cargo.toml index b8757a94..e1f95006 100644 --- a/components/templates/Cargo.toml +++ b/components/templates/Cargo.toml @@ -8,7 +8,6 @@ edition = "2018" tera = "1" base64 = "0.13" lazy_static = "1" -pulldown-cmark = { version = "0.8", default-features = false } toml = "0.5" csv = "1" image = "0.23" @@ -16,13 +15,14 @@ serde_json = "1.0" sha2 = "0.9" url = "2" nom-bibtex = "0.3" +svg_metadata = "0.4.1" errors = { path = "../errors" } utils = { path = "../utils" } library = { path = "../library" } config = { path = "../config" } imageproc = { path = "../imageproc" } -svg_metadata = "0.4.1" +rendering = { path = "../rendering" } [dependencies.reqwest] version = "0.10" diff --git a/components/templates/src/filters.rs b/components/templates/src/filters.rs index 5f4e521a..c9b6d6cd 100644 --- a/components/templates/src/filters.rs +++ b/components/templates/src/filters.rs @@ -2,38 +2,44 @@ use std::collections::HashMap; use std::hash::BuildHasher; use base64::{decode, encode}; -use pulldown_cmark as cmark; -use tera::{to_value, try_get_value, Result as TeraResult, Value}; +use config::Config; +use rendering::{render_content, RenderContext}; +use tera::{to_value, try_get_value, Filter as TeraFilter, Result as TeraResult, Value}; -pub fn markdown( - value: &Value, - args: &HashMap, -) -> TeraResult { - let s = try_get_value!("markdown", "value", String, value); - let inline = match args.get("inline") { - Some(val) => try_get_value!("markdown", "inline", bool, val), - None => false, - }; +#[derive(Debug)] +pub struct MarkdownFilter { + config: Config, +} - let mut opts = cmark::Options::empty(); - opts.insert(cmark::Options::ENABLE_TABLES); - opts.insert(cmark::Options::ENABLE_FOOTNOTES); - opts.insert(cmark::Options::ENABLE_STRIKETHROUGH); - opts.insert(cmark::Options::ENABLE_TASKLISTS); - - let mut html = String::new(); - let parser = cmark::Parser::new_ext(&s, opts); - cmark::html::push_html(&mut html, parser); - - if inline { - html = html - .trim_start_matches("

") - // pulldown_cmark finishes a paragraph with `

\n` - .trim_end_matches("

\n") - .to_string(); +impl MarkdownFilter { + pub fn new(config: Config) -> Self { + Self { config } } +} - Ok(to_value(&html).unwrap()) +impl TeraFilter for MarkdownFilter { + fn filter(&self, value: &Value, args: &HashMap) -> TeraResult { + let context = RenderContext::from_config(&self.config); + let s = try_get_value!("markdown", "value", String, value); + let inline = match args.get("inline") { + Some(val) => try_get_value!("markdown", "inline", bool, val), + None => false, + }; + let mut html = match render_content(&s, &context) { + Ok(res) => res.body, + Err(e) => return Err(format!("Failed to render markdown filter: {:?}", e).into()), + }; + + if inline { + html = html + .trim_start_matches("

") + // pulldown_cmark finishes a paragraph with `

\n` + .trim_end_matches("

\n") + .to_string(); + } + + Ok(to_value(&html).unwrap()) + } } pub fn base64_encode( @@ -56,22 +62,24 @@ pub fn base64_decode( mod tests { use std::collections::HashMap; - use tera::to_value; + use tera::{to_value, Filter}; - use super::{base64_decode, base64_encode, markdown}; + use super::{base64_decode, base64_encode, MarkdownFilter}; + use config::Config; #[test] fn markdown_filter() { - let result = markdown(&to_value(&"# Hey").unwrap(), &HashMap::new()); + let result = MarkdownFilter::new(Config::default()) + .filter(&to_value(&"# Hey").unwrap(), &HashMap::new()); assert!(result.is_ok()); - assert_eq!(result.unwrap(), to_value(&"

Hey

\n").unwrap()); + assert_eq!(result.unwrap(), to_value(&"

Hey

\n").unwrap()); } #[test] fn markdown_filter_inline() { let mut args = HashMap::new(); args.insert("inline".to_string(), to_value(true).unwrap()); - let result = markdown( + let result = MarkdownFilter::new(Config::default()).filter( &to_value(&"Using `map`, `filter`, and `fold` instead of `for`").unwrap(), &args, ); @@ -84,7 +92,7 @@ mod tests { fn markdown_filter_inline_tables() { let mut args = HashMap::new(); args.insert("inline".to_string(), to_value(true).unwrap()); - let result = markdown( + let result = MarkdownFilter::new(Config::default()).filter( &to_value( &r#" |id|author_id| timestamp_created|title |content | @@ -100,6 +108,26 @@ mod tests { assert!(result.unwrap().as_str().unwrap().contains("")); } + #[test] + fn markdown_filter_use_config_options() { + let mut config = Config::default(); + config.markdown.highlight_code = true; + config.markdown.smart_punctuation = true; + config.markdown.render_emoji = true; + config.markdown.external_links_target_blank = true; + + let md = "Hello :smile: ..."; + let result = + MarkdownFilter::new(config.clone()).filter(&to_value(&md).unwrap(), &HashMap::new()); + assert!(result.is_ok()); + assert_eq!(result.unwrap(), to_value(&"

Hello https://google.com πŸ˜„ …

\n").unwrap()); + + let md = "```py\ni=0\n```"; + let result = MarkdownFilter::new(config).filter(&to_value(&md).unwrap(), &HashMap::new()); + assert!(result.is_ok()); + assert!(result.unwrap().as_str().unwrap().contains("