mirror of
https://github.com/askama-rs/askama.git
synced 2025-10-02 15:25:19 +00:00
Add markdown filter
This commit is contained in:
parent
0aab78c677
commit
fd8bfa43c0
@ -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"]
|
||||
|
@ -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"
|
||||
|
@ -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",
|
||||
@ -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
|
||||
@ -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::*;
|
||||
|
@ -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,
|
||||
@ -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" {
|
||||
|
@ -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]
|
||||
|
75
testing/tests/markdown.rs
Normal file
75
testing/tests/markdown.rs
Normal file
@ -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\
|
||||
<script>alert('Lol, hacked!')</script>\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",
|
||||
);
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user