mirror of
https://github.com/askama-rs/askama.git
synced 2025-10-04 00:05:12 +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"]
|
default = ["config", "humansize", "num-traits", "urlencode"]
|
||||||
config = ["askama_derive/config", "askama_shared/config"]
|
config = ["askama_derive/config", "askama_shared/config"]
|
||||||
humansize = ["askama_shared/humansize"]
|
humansize = ["askama_shared/humansize"]
|
||||||
|
markdown = ["askama_shared/markdown"]
|
||||||
urlencode = ["askama_shared/percent-encoding"]
|
urlencode = ["askama_shared/percent-encoding"]
|
||||||
serde-json = ["askama_derive/json", "askama_shared/json"]
|
serde-json = ["askama_derive/json", "askama_shared/json"]
|
||||||
serde-yaml = ["askama_derive/yaml", "askama_shared/yaml"]
|
serde-yaml = ["askama_derive/yaml", "askama_shared/yaml"]
|
||||||
|
@ -13,10 +13,12 @@ edition = "2018"
|
|||||||
default = ["config", "humansize", "num-traits", "percent-encoding"]
|
default = ["config", "humansize", "num-traits", "percent-encoding"]
|
||||||
config = ["serde", "toml"]
|
config = ["serde", "toml"]
|
||||||
json = ["serde", "serde_json"]
|
json = ["serde", "serde_json"]
|
||||||
|
markdown = ["comrak"]
|
||||||
yaml = ["serde", "serde_yaml"]
|
yaml = ["serde", "serde_yaml"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
askama_escape = { version = "0.10.2", path = "../askama_escape" }
|
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 }
|
humansize = { version = "1.1.0", optional = true }
|
||||||
mime = "0.3"
|
mime = "0.3"
|
||||||
mime_guess = "2"
|
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
|
// 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
|
// filters shipped with Askama, even the optional ones (since optional inclusion
|
||||||
// in the const vector based on features seems impossible right now).
|
// 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",
|
"abs",
|
||||||
"capitalize",
|
"capitalize",
|
||||||
"center",
|
"center",
|
||||||
@ -73,8 +73,10 @@ pub const BUILT_IN_FILTERS: [&str; 27] = [
|
|||||||
"urlencode",
|
"urlencode",
|
||||||
"urlencode_strict",
|
"urlencode_strict",
|
||||||
"wordcount",
|
"wordcount",
|
||||||
"json", // Optional feature; reserve the name anyway
|
// optional features, reserve the names anyway:
|
||||||
"yaml", // Optional feature; reserve the name anyway
|
"json",
|
||||||
|
"markdown",
|
||||||
|
"yaml",
|
||||||
];
|
];
|
||||||
|
|
||||||
/// Marks a string (or other `Display` type) as safe
|
/// 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())
|
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)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
@ -1097,6 +1097,45 @@ impl<'a, S: std::hash::BuildHasher> Generator<'a, S> {
|
|||||||
DisplayWrap::Unwrapped
|
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(
|
fn visit_filter(
|
||||||
&mut self,
|
&mut self,
|
||||||
buf: &mut Buffer,
|
buf: &mut Buffer,
|
||||||
@ -1115,6 +1154,8 @@ impl<'a, S: std::hash::BuildHasher> Generator<'a, S> {
|
|||||||
} else if name == "join" {
|
} else if name == "join" {
|
||||||
self._visit_join_filter(buf, args)?;
|
self._visit_join_filter(buf, args)?;
|
||||||
return Ok(DisplayWrap::Unwrapped);
|
return Ok(DisplayWrap::Unwrapped);
|
||||||
|
} else if name == "markdown" {
|
||||||
|
return self._visit_markdown_filter(buf, args);
|
||||||
}
|
}
|
||||||
|
|
||||||
if name == "tojson" {
|
if name == "tojson" {
|
||||||
|
@ -7,10 +7,13 @@ edition = "2018"
|
|||||||
publish = false
|
publish = false
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = ["serde_json", "askama/serde-json"]
|
default = ["serde-json", "markdown"]
|
||||||
|
serde-json = ["serde_json", "askama/serde-json"]
|
||||||
|
markdown = ["comrak", "askama/markdown"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
askama = { path = "../askama", version = "0.11.0-beta.1" }
|
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 }
|
serde_json = { version = "1.0", optional = true }
|
||||||
|
|
||||||
[dev-dependencies]
|
[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