tera_plaintext_filters/
lib.rs

1//! Filters for the [Tera](https://github.com/Keats/tera) engine, useful for ascii-text file generation.
2//!
3//! To generate a Markdown-Table like this:
4//! ```markdown
5//!| No |      *Name*        |  Score |
6//!|----|--------------------|--------|
7//!| 1. |      Charly        |   3000 |
8//!| 2. |     Alexander      |    800 |
9//!| 3. |     Josephine      |    760 |
10//!```
11//!
12//! A Tera-template could look like:
13//! ```markdown
14//! | No |       *Name*       |  Score |
15//! |----|--------------------|--------|
16//! {% for member in team | slice(end=10) %}
17//! | {{ loop.index ~ '.' | left_align(length=4) }} | {{
18//!     member.name | center(length=20) }} | {{
19//!     member.score | right_align(length=10) }} |
20//! {% endfor %}
21//! ```
22//!
23use std::{collections::HashMap, hash::BuildHasher};
24use tera::{to_value, try_get_value, Error, Value};
25
26/// Right-aligns the token to a given length.   
27///
28/// # Usage in Tera-Templates
29/// `{{ name | right_align(length=20) }}`
30///
31/// # Example
32///
33/// ```
34/// use tera::{Context, Tera};
35/// use tera_plaintext_filters::right_align;
36///
37/// let mut ctx = Context::new();
38/// ctx.insert("i", "some text");
39///
40/// let mut tera = Tera::default();
41/// tera.register_filter("right_align", right_align);
42///
43/// let i = "{{ i | right_align(length=20) }}";
44/// let rendered = tera.render_str(i, &ctx).unwrap();
45/// assert_eq!(rendered, "           some text");
46/// ```
47pub fn right_align<S: BuildHasher>(
48    value: &Value,
49    args: &HashMap<String, Value, S>,
50) -> tera::Result<Value> {
51    let (text, len) = eval_value(value, args, "right_align")?;
52    Ok(to_value(format!("{text:>len$}")).unwrap())
53}
54
55/// Left-aligns the token to a given length.
56///
57/// # Usage in Tera-Templates
58/// `{{ name | left_align(length=20) }}`
59///
60/// # Example
61///
62/// ```
63/// use tera::{Context, Tera};
64/// use tera_plaintext_filters::left_align;
65///
66/// let mut ctx = Context::new();
67/// ctx.insert("i", "some text");
68///
69/// let mut tera = Tera::default();
70/// tera.register_filter("left_align", left_align);
71///
72/// let i = "{{ i | left_align(length=20) }}";
73/// let rendered = tera.render_str(i, &ctx).unwrap();
74/// assert_eq!(rendered, "some text           ");
75/// ```
76pub fn left_align<S: BuildHasher>(
77    value: &Value,
78    args: &HashMap<String, Value, S>,
79) -> tera::Result<Value> {
80    let (text, len) = eval_value(value, args, "left_align")?;
81    Ok(to_value(format!("{text:len$}")).unwrap())
82}
83
84/// Centers the token to a given length.   
85///
86/// # Usage in Tera-Templates
87/// `{{ name | center(length=20) }}`
88///
89/// # Example
90///
91/// ```
92/// use tera::{Context, Tera};
93/// use tera_plaintext_filters::center;
94///
95/// let mut ctx = Context::new();
96/// ctx.insert("i", "some text");
97///
98/// let mut tera = Tera::default();
99/// tera.register_filter("center", center);
100///
101/// let i = "{{ i | center(length=20) }}";
102/// let rendered = tera.render_str(i, &ctx).unwrap();
103/// assert_eq!(rendered, "     some text      ");
104/// ```
105pub fn center<S: BuildHasher>(
106    value: &Value,
107    args: &HashMap<String, Value, S>,
108) -> tera::Result<Value> {
109    let (text, len) = eval_value(value, args, "center")?;
110    Ok(to_value(format!("{text:^len$}")).unwrap())
111}
112
113fn eval_value<S: BuildHasher>(
114    value: &Value,
115    args: &HashMap<String, Value, S>,
116    filter_name: &'static str,
117) -> tera::Result<(String, usize)> {
118    if value.is_object() || value.is_array() {
119        return Err(Error::msg(format!(
120            "Filter `{filter_name}` was called on an incorrect value: got `{value}` \
121                        but expected a text or number",
122        )));
123    }
124    let len = match args.get("length") {
125        Some(length) => {
126            try_get_value!(filter_name, "length", usize, length)
127        }
128        None => {
129            return Err(Error::msg(format!(
130                "Filter `{filter_name}` expected an arg called `length`",
131            )))
132        }
133    };
134    Ok(match value.as_str() {
135        Some(str) => (str.to_string(), len),
136        // null => ""
137        None if value.is_null() => (String::new(), len),
138        None => (value.to_string(), len),
139    })
140}
141
142#[cfg(test)]
143mod should {
144    use super::*;
145    use serde_json::json;
146    use std::collections::HashMap;
147
148    #[test]
149    fn check_alignment() {
150        let v = json!("Shorttext");
151        let mut hm = HashMap::new();
152        let r = center(&v, &hm);
153        assert!(r.is_err());
154
155        hm.insert("length".to_string(), json!(20));
156        let r = center(&v, &hm).unwrap();
157        assert_eq!("     Shorttext      ", r.as_str().unwrap());
158        let r = left_align(&v, &hm).unwrap();
159        assert_eq!("Shorttext           ", r.as_str().unwrap());
160        let r = right_align(&v, &hm).unwrap();
161        assert_eq!("           Shorttext", r.as_str().unwrap());
162    }
163
164    #[test]
165    fn check_input() {
166        let v = json!("12.23");
167        let mut hm = HashMap::new();
168        hm.insert("length".to_string(), json!(20));
169        let r = center(&v, &hm).unwrap();
170        assert_eq!("       12.23        ", r.as_str().unwrap());
171        let v = json!(12.23);
172        let r = center(&v, &hm).unwrap();
173        assert_eq!("       12.23        ", r.as_str().unwrap());
174        assert_eq!("                    ", center(&json!(null), &hm).unwrap());
175
176        assert!(center(&json!({ "a": "notice", "b": 124.0 }), &hm).is_err());
177        assert!(center(&json!(["notice", "the", "trailing", "comma -->",]), &hm).is_err());
178    }
179}