Skip to content

Commit

Permalink
Add markdown filter
Browse files Browse the repository at this point in the history
  • Loading branch information
Kijewski authored and djc committed Feb 7, 2022
1 parent 0aab78c commit fd8bfa4
Show file tree
Hide file tree
Showing 6 changed files with 176 additions and 4 deletions.
1 change: 1 addition & 0 deletions askama/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ maintenance = { status = "actively-developed" }
default = ["config", "humansize", "num-traits", "urlencode"]
config = ["askama_derive/config", "askama_shared/config"]
humansize = ["askama_shared/humansize"]
markdown = ["askama_shared/markdown"]
urlencode = ["askama_shared/percent-encoding"]
serde-json = ["askama_derive/json", "askama_shared/json"]
serde-yaml = ["askama_derive/yaml", "askama_shared/yaml"]
Expand Down
2 changes: 2 additions & 0 deletions askama_shared/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,12 @@ edition = "2018"
default = ["config", "humansize", "num-traits", "percent-encoding"]
config = ["serde", "toml"]
json = ["serde", "serde_json"]
markdown = ["comrak"]
yaml = ["serde", "serde_yaml"]

[dependencies]
askama_escape = { version = "0.10.2", path = "../askama_escape" }
comrak = { version = "0.12", optional = true, default-features = false }
humansize = { version = "1.1.0", optional = true }
mime = "0.3"
mime_guess = "2"
Expand Down
56 changes: 53 additions & 3 deletions askama_shared/src/filters/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ const URLENCODE_SET: &AsciiSet = &URLENCODE_STRICT_SET.remove(b'/');
// Askama or should refer to a local `filters` module. It should contain all the
// filters shipped with Askama, even the optional ones (since optional inclusion
// in the const vector based on features seems impossible right now).
pub const BUILT_IN_FILTERS: [&str; 27] = [
pub const BUILT_IN_FILTERS: &[&str] = &[
"abs",
"capitalize",
"center",
Expand All @@ -73,8 +73,10 @@ pub const BUILT_IN_FILTERS: [&str; 27] = [
"urlencode",
"urlencode_strict",
"wordcount",
"json", // Optional feature; reserve the name anyway
"yaml", // Optional feature; reserve the name anyway
// optional features, reserve the names anyway:
"json",
"markdown",
"yaml",
];

/// Marks a string (or other `Display` type) as safe
Expand Down Expand Up @@ -379,6 +381,54 @@ pub fn wordcount<T: fmt::Display>(s: T) -> Result<usize> {
Ok(s.split_whitespace().count())
}

#[cfg(feature = "markdown")]
pub fn markdown<E, S>(
e: E,
s: S,
options: Option<&comrak::ComrakOptions>,
) -> Result<MarkupDisplay<E, String>>
where
E: Escaper,
S: AsRef<str>,
{
use comrak::{
markdown_to_html, ComrakExtensionOptions, ComrakOptions, ComrakParseOptions,
ComrakRenderOptions,
};

const DEFAULT_OPTIONS: ComrakOptions = ComrakOptions {
extension: ComrakExtensionOptions {
strikethrough: true,
tagfilter: true,
table: true,
autolink: true,
// default:
tasklist: false,
superscript: false,
header_ids: None,
footnotes: false,
description_lists: false,
front_matter_delimiter: None,
},
parse: ComrakParseOptions {
// default:
smart: false,
default_info_string: None,
},
render: ComrakRenderOptions {
unsafe_: false,
escape: true,
// default:
hardbreaks: false,
github_pre_lang: false,
width: 0,
},
};

let s = markdown_to_html(s.as_ref(), options.unwrap_or(&DEFAULT_OPTIONS));
Ok(MarkupDisplay::new_safe(s, e))
}

#[cfg(test)]
mod tests {
use super::*;
Expand Down
41 changes: 41 additions & 0 deletions askama_shared/src/generator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1097,6 +1097,45 @@ impl<'a, S: std::hash::BuildHasher> Generator<'a, S> {
DisplayWrap::Unwrapped
}

#[cfg(not(feature = "markdown"))]
fn _visit_markdown_filter(
&mut self,
_buf: &mut Buffer,
_args: &[Expr<'_>],
) -> Result<DisplayWrap, CompileError> {
Err("the `markdown` filter requires the `markdown` feature to be enabled".into())
}

#[cfg(feature = "markdown")]
fn _visit_markdown_filter(
&mut self,
buf: &mut Buffer,
args: &[Expr<'_>],
) -> Result<DisplayWrap, CompileError> {
let (md, options) = match args {
[md] => (md, None),
[md, options] => (md, Some(options)),
_ => return Err("markdown filter expects no more than one option argument".into()),
};

buf.write(&format!(
"::askama::filters::markdown({}, ",
self.input.escaper
));
self.visit_expr(buf, md)?;
match options {
Some(options) => {
buf.write(", ::core::option::Option::Some(");
self.visit_expr(buf, options)?;
buf.write(")");
}
None => buf.write(", ::core::option::Option::None"),
}
buf.write(")?");

Ok(DisplayWrap::Wrapped)
}

fn visit_filter(
&mut self,
buf: &mut Buffer,
Expand All @@ -1115,6 +1154,8 @@ impl<'a, S: std::hash::BuildHasher> Generator<'a, S> {
} else if name == "join" {
self._visit_join_filter(buf, args)?;
return Ok(DisplayWrap::Unwrapped);
} else if name == "markdown" {
return self._visit_markdown_filter(buf, args);
}

if name == "tojson" {
Expand Down
5 changes: 4 additions & 1 deletion testing/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,13 @@ edition = "2018"
publish = false

[features]
default = ["serde_json", "askama/serde-json"]
default = ["serde-json", "markdown"]
serde-json = ["serde_json", "askama/serde-json"]
markdown = ["comrak", "askama/markdown"]

[dependencies]
askama = { path = "../askama", version = "0.11.0-beta.1" }
comrak = { version = "0.12", default-features = false, optional = true }
serde_json = { version = "1.0", optional = true }

[dev-dependencies]
Expand Down
75 changes: 75 additions & 0 deletions testing/tests/markdown.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
#![cfg(feature = "markdown")]

use askama::Template;
use comrak::{ComrakOptions, ComrakRenderOptions};

#[derive(Template)]
#[template(source = "{{before}}{{content|markdown}}{{after}}", ext = "html")]
struct MarkdownTemplate<'a> {
before: &'a str,
after: &'a str,
content: &'a str,
}

#[test]
fn test_markdown() {
let s = MarkdownTemplate {
before: "before",
after: "after",
content: "* 1\n* <script>alert('Lol, hacked!')</script>\n* 3",
};
assert_eq!(
s.render().unwrap(),
"\
before\
<ul>\n\
<li>1</li>\n\
<li>\n\
&lt;script&gt;alert('Lol, hacked!')&lt;/script&gt;\n\
</li>\n\
<li>3</li>\n\
</ul>\n\
after",
);
}

#[derive(Template)]
#[template(
source = "{{before}}{{content|markdown(options)}}{{after}}",
ext = "html"
)]
struct MarkdownWithOptionsTemplate<'a> {
before: &'a str,
after: &'a str,
content: &'a str,
options: &'a ComrakOptions,
}

#[test]
fn test_markdown_with_options() {
let s = MarkdownWithOptionsTemplate {
before: "before",
after: "after",
content: "* 1\n* <script>alert('Lol, hacked!')</script>\n* 3",
options: &ComrakOptions {
render: ComrakRenderOptions {
unsafe_: true,
..Default::default()
},
..Default::default()
},
};
assert_eq!(
s.render().unwrap(),
"\
before\
<ul>\n\
<li>1</li>\n\
<li>\n\
<script>alert('Lol, hacked!')</script>\n\
</li>\n\
<li>3</li>\n\
</ul>\n\
after",
);
}

0 comments on commit fd8bfa4

Please sign in to comment.