From 2d6785b71414cc70adfb24ee742e3defa5841bbb Mon Sep 17 00:00:00 2001 From: Anthony Nowell Date: Sun, 13 Aug 2017 00:46:04 -0600 Subject: Adding optional json filter --- askama/Cargo.toml | 9 ++++ askama/src/filters.rs | 120 ----------------------------------------- askama/src/filters/json.rs | 30 +++++++++++ askama/src/filters/mod.rs | 128 ++++++++++++++++++++++++++++++++++++++++++++ askama/src/lib.rs | 6 +++ testing/Cargo.toml | 5 +- testing/templates/json.html | 5 +- testing/tests/simple.rs | 22 ++++++-- 8 files changed, 198 insertions(+), 127 deletions(-) delete mode 100644 askama/src/filters.rs create mode 100644 askama/src/filters/json.rs create mode 100644 askama/src/filters/mod.rs diff --git a/askama/Cargo.toml b/askama/Cargo.toml index 57708ee..32a8c03 100644 --- a/askama/Cargo.toml +++ b/askama/Cargo.toml @@ -18,3 +18,12 @@ travis-ci = { repository = "djc/askama" } [dependencies] askama_derive = { path = "../askama_derive", version = "0.3.4" } error-chain = "0.10" +serde = { version = "1.0", optional = true } +serde_json = { version = "1.0", optional = true } + +[features] +serde-json = ["serde", "serde_json"] +default = [] + +[package.metadata.docs.rs] +features = [ "serde-json" ] diff --git a/askama/src/filters.rs b/askama/src/filters.rs deleted file mode 100644 index b8673e6..0000000 --- a/askama/src/filters.rs +++ /dev/null @@ -1,120 +0,0 @@ -//! Module for built-in filter functions -//! -//! Contains all the built-in filter functions for use in templates. -//! Currently, there is no way to define filters outside this module. -use std::fmt; -use super::Result; - -fn escapable(b: &u8) -> bool { - *b == b'<' || *b == b'>' || *b == b'&' -} - -/// Escapes `&`, `<` and `>` in strings -pub fn escape(s: &fmt::Display) -> Result { - let s = format!("{}", s); - let mut found = Vec::new(); - for (i, b) in s.as_bytes().iter().enumerate() { - if escapable(b) { - found.push(i); - } - } - if found.is_empty() { - return Ok(s); - } - - let bytes = s.as_bytes(); - let max_len = bytes.len() + found.len() * 3; - let mut res = Vec::::with_capacity(max_len); - let mut start = 0; - for idx in &found { - if start < *idx { - res.extend(&bytes[start..*idx]); - } - start = *idx + 1; - match bytes[*idx] { - b'<' => { res.extend(b"<"); }, - b'>' => { res.extend(b">"); }, - b'&' => { res.extend(b"&"); }, - _ => panic!("incorrect indexing"), - } - } - if start < bytes.len() - 1 { - res.extend(&bytes[start..]); - } - - Ok(String::from_utf8(res).unwrap()) -} - -/// Alias for the `escape()` filter -pub fn e(s: &fmt::Display) -> Result { - escape(s) -} - -/// Formats arguments according to the specified format -/// -/// The first argument to this filter must be a string literal (as in normal -/// Rust). All arguments are passed through to the `format!()` -/// [macro](https://doc.rust-lang.org/stable/std/macro.format.html) by -/// the Askama code generator. -pub fn format() { } - -/// Converts to lowercase. -pub fn lower(s: &fmt::Display) -> Result { - let s = format!("{}", s); - Ok(s.to_lowercase()) -} - -/// Alias for the `lower()` filter. -pub fn lowercase(s: &fmt::Display) -> Result { - lower(s) -} - -/// Converts to uppercase. -pub fn upper(s: &fmt::Display) -> Result { - let s = format!("{}", s); - Ok(s.to_uppercase()) -} - -/// Alias for the `upper()` filter. -pub fn uppercase(s: &fmt::Display) -> Result { - upper(s) -} - -/// Strip leading and trailing whitespace. -pub fn trim(s: &fmt::Display) -> Result { - let s = format!("{}", s); - Ok(s.trim().to_owned()) -} - -#[cfg(test)] -mod tests { - use super::*; - #[test] - fn test_escape() { - assert_eq!(escape(&"").unwrap(), ""); - assert_eq!(escape(&"<&>").unwrap(), "<&>"); - assert_eq!(escape(&"bla&").unwrap(), "bla&"); - assert_eq!(escape(&"(s: &S) -> Result { + serde_json::to_string_pretty(s).map_err(Error::from) +} + + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_json() { + assert_eq!(json(&true).unwrap(), "true"); + assert_eq!(json(&"foo").unwrap(), r#""foo""#); + assert_eq!(json(&vec!["foo", "bar"]).unwrap(), +r#"[ + "foo", + "bar" +]"#); + } +} diff --git a/askama/src/filters/mod.rs b/askama/src/filters/mod.rs new file mode 100644 index 0000000..ca229ca --- /dev/null +++ b/askama/src/filters/mod.rs @@ -0,0 +1,128 @@ +//! Module for built-in filter functions +//! +//! Contains all the built-in filter functions for use in templates. +//! Currently, there is no way to define filters outside this module. + +#[cfg(feature = "serde-json")] +mod json; + +#[cfg(feature = "serde-json")] +pub use self::json::json; + +use std::fmt; +use super::Result; + + +fn escapable(b: &u8) -> bool { + *b == b'<' || *b == b'>' || *b == b'&' +} + +/// Escapes `&`, `<` and `>` in strings +pub fn escape(s: &fmt::Display) -> Result { + let s = format!("{}", s); + let mut found = Vec::new(); + for (i, b) in s.as_bytes().iter().enumerate() { + if escapable(b) { + found.push(i); + } + } + if found.is_empty() { + return Ok(s); + } + + let bytes = s.as_bytes(); + let max_len = bytes.len() + found.len() * 3; + let mut res = Vec::::with_capacity(max_len); + let mut start = 0; + for idx in &found { + if start < *idx { + res.extend(&bytes[start..*idx]); + } + start = *idx + 1; + match bytes[*idx] { + b'<' => { res.extend(b"<"); }, + b'>' => { res.extend(b">"); }, + b'&' => { res.extend(b"&"); }, + _ => panic!("incorrect indexing"), + } + } + if start < bytes.len() - 1 { + res.extend(&bytes[start..]); + } + + Ok(String::from_utf8(res).unwrap()) +} + +/// Alias for the `escape()` filter +pub fn e(s: &fmt::Display) -> Result { + escape(s) +} + +/// Formats arguments according to the specified format +/// +/// The first argument to this filter must be a string literal (as in normal +/// Rust). All arguments are passed through to the `format!()` +/// [macro](https://doc.rust-lang.org/stable/std/macro.format.html) by +/// the Askama code generator. +pub fn format() { } + +/// Converts to lowercase. +pub fn lower(s: &fmt::Display) -> Result { + let s = format!("{}", s); + Ok(s.to_lowercase()) +} + +/// Alias for the `lower()` filter. +pub fn lowercase(s: &fmt::Display) -> Result { + lower(s) +} + +/// Converts to uppercase. +pub fn upper(s: &fmt::Display) -> Result { + let s = format!("{}", s); + Ok(s.to_uppercase()) +} + +/// Alias for the `upper()` filter. +pub fn uppercase(s: &fmt::Display) -> Result { + upper(s) +} + +/// Strip leading and trailing whitespace. +pub fn trim(s: &fmt::Display) -> Result { + let s = format!("{}", s); + Ok(s.trim().to_owned()) +} + +#[cfg(test)] +mod tests { + use super::*; + #[test] + fn test_escape() { + assert_eq!(escape(&"").unwrap(), ""); + assert_eq!(escape(&"<&>").unwrap(), "<&>"); + assert_eq!(escape(&"bla&").unwrap(), "bla&"); + assert_eq!(escape(&" { foo: &'a str, - bar: &'a str, + bar: &'a Value, } #[test] fn test_json() { - let t = JsonTemplate { foo: "a", bar: "b" }; - assert_eq!(t.render().unwrap(), "{\"foo\": \"a\", \"bar\": \"b\"}"); + let val = json!({"arr": [ "one", 2, true, null ]}); + let t = JsonTemplate { foo: "a", bar: &val }; + // Note: the json filter lacks a way to specify initial indentation + assert_eq!(t.render().unwrap(), r#"{ + "foo": "a", + "bar": { + "arr": [ + "one", + 2, + true, + null + ] +} +}"#); } - #[derive(Template)] #[template(path = "composition.html")] struct CompositionTemplate { -- cgit