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}