askama/filters/alloc.rs
1use alloc::str;
2use alloc::string::String;
3use core::convert::Infallible;
4use core::fmt::{self, Write};
5
6use crate::{FastWritable, Result};
7
8/// Return an ephemeral `&str` for `$src: impl fmt::Display`
9///
10/// If `$str` is `&str` or `String`, this macro simply passes on its content.
11/// If it is neither, then the formatted data is collection into `&buffer`.
12///
13/// `return`s with an error if the formatting failed.
14macro_rules! try_to_str {
15 ($src:expr => $buffer:ident) => {
16 match format_args!("{}", $src) {
17 args => {
18 if let Some(s) = args.as_str() {
19 s
20 } else {
21 $buffer = String::new();
22 $buffer.write_fmt(args)?;
23 &$buffer
24 }
25 }
26 }
27 };
28}
29
30/// Formats arguments according to the specified format
31///
32/// The *second* argument to this filter must be a string literal (as in normal
33/// Rust). The two arguments are passed through to the `format!()`
34/// [macro](https://doc.rust-lang.org/stable/std/macro.format.html) by
35/// the Askama code generator, but the order is swapped to support filter
36/// composition.
37///
38/// ```ignore
39/// {{ value|fmt("{:?}") }}
40/// ```
41///
42/// ```
43/// # #[cfg(feature = "code-in-doc")] {
44/// # use askama::Template;
45/// /// ```jinja
46/// /// <div>{{ value|fmt("{:?}") }}</div>
47/// /// ```
48/// #[derive(Template)]
49/// #[template(ext = "html", in_doc = true)]
50/// struct Example {
51/// value: (usize, usize),
52/// }
53///
54/// assert_eq!(
55/// Example { value: (3, 4) }.to_string(),
56/// "<div>(3, 4)</div>"
57/// );
58/// # }
59/// ```
60///
61/// Compare with [format](./fn.format.html).
62pub fn fmt() {}
63
64/// Formats arguments according to the specified format
65///
66/// The first argument to this filter must be a string literal (as in normal
67/// Rust). All arguments are passed through to the `format!()`
68/// [macro](https://doc.rust-lang.org/stable/std/macro.format.html) by
69/// the Askama code generator.
70///
71/// ```ignore
72/// {{ "{:?}{:?}"|format(value, other_value) }}
73/// ```
74///
75/// ```
76/// # #[cfg(feature = "code-in-doc")] {
77/// # use askama::Template;
78/// /// ```jinja
79/// /// <div>{{ "{:?}"|format(value) }}</div>
80/// /// ```
81/// #[derive(Template)]
82/// #[template(ext = "html", in_doc = true)]
83/// struct Example {
84/// value: (usize, usize),
85/// }
86///
87/// assert_eq!(
88/// Example { value: (3, 4) }.to_string(),
89/// "<div>(3, 4)</div>"
90/// );
91/// # }
92/// ```
93///
94/// Compare with [fmt](./fn.fmt.html).
95pub fn format() {}
96
97/// Converts to lowercase
98///
99/// ```
100/// # #[cfg(feature = "code-in-doc")] {
101/// # use askama::Template;
102/// /// ```jinja
103/// /// <div>{{ word|lower }}</div>
104/// /// ```
105/// #[derive(Template)]
106/// #[template(ext = "html", in_doc = true)]
107/// struct Example<'a> {
108/// word: &'a str,
109/// }
110///
111/// assert_eq!(
112/// Example { word: "FOO" }.to_string(),
113/// "<div>foo</div>"
114/// );
115///
116/// assert_eq!(
117/// Example { word: "FooBar" }.to_string(),
118/// "<div>foobar</div>"
119/// );
120/// # }
121/// ```
122#[inline]
123pub fn lower<S: fmt::Display>(source: S) -> Result<Lower<S>, Infallible> {
124 Ok(Lower(source))
125}
126
127pub struct Lower<S>(S);
128
129impl<S: fmt::Display> fmt::Display for Lower<S> {
130 #[inline]
131 fn fmt(&self, dest: &mut fmt::Formatter<'_>) -> fmt::Result {
132 let mut buffer;
133 flush_lower(dest, try_to_str!(self.0 => buffer))
134 }
135}
136
137impl<S: FastWritable> FastWritable for Lower<S> {
138 #[inline]
139 fn write_into(
140 &self,
141 dest: &mut dyn fmt::Write,
142 values: &dyn crate::Values,
143 ) -> crate::Result<()> {
144 let mut buffer = String::new();
145 self.0.write_into(&mut buffer, values)?;
146 Ok(flush_lower(dest, &buffer)?)
147 }
148}
149
150fn flush_lower(dest: &mut (impl fmt::Write + ?Sized), s: &str) -> fmt::Result {
151 dest.write_str(&s.to_lowercase())
152}
153
154/// Converts to lowercase, alias for the `|lower` filter
155///
156/// ```
157/// # #[cfg(feature = "code-in-doc")] {
158/// # use askama::Template;
159/// /// ```jinja
160/// /// <div>{{ word|lowercase }}</div>
161/// /// ```
162/// #[derive(Template)]
163/// #[template(ext = "html", in_doc = true)]
164/// struct Example<'a> {
165/// word: &'a str,
166/// }
167///
168/// assert_eq!(
169/// Example { word: "FOO" }.to_string(),
170/// "<div>foo</div>"
171/// );
172///
173/// assert_eq!(
174/// Example { word: "FooBar" }.to_string(),
175/// "<div>foobar</div>"
176/// );
177/// # }
178/// ```
179#[inline]
180pub fn lowercase<S: fmt::Display>(source: S) -> Result<Lower<S>, Infallible> {
181 lower(source)
182}
183
184/// Converts to uppercase
185///
186/// ```
187/// # #[cfg(feature = "code-in-doc")] {
188/// # use askama::Template;
189/// /// ```jinja
190/// /// <div>{{ word|upper }}</div>
191/// /// ```
192/// #[derive(Template)]
193/// #[template(ext = "html", in_doc = true)]
194/// struct Example<'a> {
195/// word: &'a str,
196/// }
197///
198/// assert_eq!(
199/// Example { word: "foo" }.to_string(),
200/// "<div>FOO</div>"
201/// );
202///
203/// assert_eq!(
204/// Example { word: "FooBar" }.to_string(),
205/// "<div>FOOBAR</div>"
206/// );
207/// # }
208/// ```
209#[inline]
210pub fn upper<S: fmt::Display>(source: S) -> Result<Upper<S>, Infallible> {
211 Ok(Upper(source))
212}
213
214pub struct Upper<S>(S);
215
216impl<S: fmt::Display> fmt::Display for Upper<S> {
217 #[inline]
218 fn fmt(&self, dest: &mut fmt::Formatter<'_>) -> fmt::Result {
219 let mut buffer;
220 flush_upper(dest, try_to_str!(self.0 => buffer))
221 }
222}
223
224impl<S: FastWritable> FastWritable for Upper<S> {
225 #[inline]
226 fn write_into(
227 &self,
228 dest: &mut dyn fmt::Write,
229 values: &dyn crate::Values,
230 ) -> crate::Result<()> {
231 let mut buffer = String::new();
232 self.0.write_into(&mut buffer, values)?;
233 Ok(flush_upper(dest, &buffer)?)
234 }
235}
236
237fn flush_upper(dest: &mut (impl fmt::Write + ?Sized), s: &str) -> fmt::Result {
238 dest.write_str(&s.to_uppercase())
239}
240
241/// Converts to uppercase, alias for the `|upper` filter
242///
243/// ```
244/// # #[cfg(feature = "code-in-doc")] {
245/// # use askama::Template;
246/// /// ```jinja
247/// /// <div>{{ word|uppercase }}</div>
248/// /// ```
249/// #[derive(Template)]
250/// #[template(ext = "html", in_doc = true)]
251/// struct Example<'a> {
252/// word: &'a str,
253/// }
254///
255/// assert_eq!(
256/// Example { word: "foo" }.to_string(),
257/// "<div>FOO</div>"
258/// );
259///
260/// assert_eq!(
261/// Example { word: "FooBar" }.to_string(),
262/// "<div>FOOBAR</div>"
263/// );
264/// # }
265/// ```
266#[inline]
267pub fn uppercase<S: fmt::Display>(source: S) -> Result<Upper<S>, Infallible> {
268 upper(source)
269}
270
271/// Strip leading and trailing whitespace
272///
273/// ```
274/// # #[cfg(feature = "code-in-doc")] {
275/// # use askama::Template;
276/// /// ```jinja
277/// /// <div>{{ example|trim }}</div>
278/// /// ```
279/// #[derive(Template)]
280/// #[template(ext = "html", in_doc = true)]
281/// struct Example<'a> {
282/// example: &'a str,
283/// }
284///
285/// assert_eq!(
286/// Example { example: " Hello\tworld\t" }.to_string(),
287/// "<div>Hello\tworld</div>"
288/// );
289/// # }
290/// ```
291#[inline]
292pub fn trim<S: fmt::Display>(source: S) -> Result<Trim<S>, Infallible> {
293 Ok(Trim(source))
294}
295
296pub struct Trim<S>(S);
297
298impl<S: fmt::Display> fmt::Display for Trim<S> {
299 #[inline]
300 fn fmt(&self, dest: &mut fmt::Formatter<'_>) -> fmt::Result {
301 let mut collector = TrimCollector(String::new());
302 write!(collector, "{}", self.0)?;
303 flush_trim(dest, collector)
304 }
305}
306
307impl<S: FastWritable> FastWritable for Trim<S> {
308 #[inline]
309 fn write_into(
310 &self,
311 dest: &mut dyn fmt::Write,
312 values: &dyn crate::Values,
313 ) -> crate::Result<()> {
314 let mut collector = TrimCollector(String::new());
315 self.0.write_into(&mut collector, values)?;
316 Ok(flush_trim(dest, collector)?)
317 }
318}
319
320struct TrimCollector(String);
321
322impl fmt::Write for TrimCollector {
323 fn write_str(&mut self, s: &str) -> fmt::Result {
324 match self.0.is_empty() {
325 true => self.0.write_str(s.trim_start()),
326 false => self.0.write_str(s),
327 }
328 }
329}
330
331fn flush_trim(dest: &mut (impl fmt::Write + ?Sized), collector: TrimCollector) -> fmt::Result {
332 dest.write_str(collector.0.trim_end())
333}
334
335/// Capitalize a value. The first character will be uppercase, all others lowercase.
336///
337/// ```
338/// # #[cfg(feature = "code-in-doc")] {
339/// # use askama::Template;
340/// /// ```jinja
341/// /// <div>{{ example|capitalize }}</div>
342/// /// ```
343/// #[derive(Template)]
344/// #[template(ext = "html", in_doc = true)]
345/// struct Example<'a> {
346/// example: &'a str,
347/// }
348///
349/// assert_eq!(
350/// Example { example: "hello" }.to_string(),
351/// "<div>Hello</div>"
352/// );
353///
354/// assert_eq!(
355/// Example { example: "hElLO" }.to_string(),
356/// "<div>Hello</div>"
357/// );
358/// # }
359/// ```
360#[inline]
361pub fn capitalize<S: fmt::Display>(source: S) -> Result<Capitalize<S>, Infallible> {
362 Ok(Capitalize(source))
363}
364
365pub struct Capitalize<S>(S);
366
367impl<S: fmt::Display> fmt::Display for Capitalize<S> {
368 #[inline]
369 fn fmt(&self, dest: &mut fmt::Formatter<'_>) -> fmt::Result {
370 let mut buffer;
371 flush_capitalize(dest, try_to_str!(self.0 => buffer))
372 }
373}
374
375impl<S: FastWritable> FastWritable for Capitalize<S> {
376 #[inline]
377 fn write_into(
378 &self,
379 dest: &mut dyn fmt::Write,
380 values: &dyn crate::Values,
381 ) -> crate::Result<()> {
382 let mut buffer = String::new();
383 self.0.write_into(&mut buffer, values)?;
384 Ok(flush_capitalize(dest, &buffer)?)
385 }
386}
387
388fn flush_capitalize(dest: &mut (impl fmt::Write + ?Sized), s: &str) -> fmt::Result {
389 let mut chars = s.chars();
390 if let Some(c) = chars.next() {
391 write!(
392 dest,
393 "{}{}",
394 c.to_uppercase(),
395 chars.as_str().to_lowercase()
396 )
397 } else {
398 Ok(())
399 }
400}
401
402/// Return a title cased version of the value. Words will start with uppercase letters, all
403/// remaining characters are lowercase.
404///
405/// ```
406/// # #[cfg(feature = "code-in-doc")] {
407/// # use askama::Template;
408/// /// ```jinja
409/// /// <div>{{ example|title }}</div>
410/// /// ```
411/// #[derive(Template)]
412/// #[template(ext = "html", in_doc = true)]
413/// struct Example<'a> {
414/// example: &'a str,
415/// }
416///
417/// assert_eq!(
418/// Example { example: "hello WORLD" }.to_string(),
419/// "<div>Hello World</div>"
420/// );
421/// # }
422/// ```
423#[inline]
424pub fn title<S: fmt::Display>(source: S) -> Result<Title<S>, Infallible> {
425 Ok(Title(source))
426}
427
428pub struct Title<S>(S);
429
430impl<S: fmt::Display> fmt::Display for Title<S> {
431 #[inline]
432 fn fmt(&self, dest: &mut fmt::Formatter<'_>) -> fmt::Result {
433 let mut buffer;
434 flush_title(dest, try_to_str!(self.0 => buffer))
435 }
436}
437
438impl<S: FastWritable> FastWritable for Title<S> {
439 #[inline]
440 fn write_into(
441 &self,
442 dest: &mut dyn fmt::Write,
443 values: &dyn crate::Values,
444 ) -> crate::Result<()> {
445 let mut buffer = String::new();
446 self.0.write_into(&mut buffer, values)?;
447 Ok(flush_title(dest, &buffer)?)
448 }
449}
450
451fn flush_title(dest: &mut (impl fmt::Write + ?Sized), s: &str) -> fmt::Result {
452 for word in s.split_inclusive(char::is_whitespace) {
453 flush_capitalize(dest, word)?;
454 }
455 Ok(())
456}
457
458/// Return a title cased version of the value. Alias for the [`|title`](title) filter.
459///
460/// ```
461/// # #[cfg(feature = "code-in-doc")] {
462/// # use askama::Template;
463/// /// ```jinja
464/// /// <div>{{ example|titlecase }}</div>
465/// /// ```
466/// #[derive(Template)]
467/// #[template(ext = "html", in_doc = true)]
468/// struct Example<'a> {
469/// example: &'a str,
470/// }
471///
472/// assert_eq!(
473/// Example { example: "hello WORLD" }.to_string(),
474/// "<div>Hello World</div>"
475/// );
476/// # }
477/// ```
478#[inline]
479pub fn titlecase<S: fmt::Display>(source: S) -> Result<Title<S>, Infallible> {
480 title(source)
481}
482
483#[cfg(test)]
484mod tests {
485 use alloc::string::ToString;
486
487 use super::*;
488
489 #[test]
490 fn test_lower() {
491 assert_eq!(lower("Foo").unwrap().to_string(), "foo");
492 assert_eq!(lower("FOO").unwrap().to_string(), "foo");
493 assert_eq!(lower("FooBar").unwrap().to_string(), "foobar");
494 assert_eq!(lower("foo").unwrap().to_string(), "foo");
495 }
496
497 #[test]
498 fn test_upper() {
499 assert_eq!(upper("Foo").unwrap().to_string(), "FOO");
500 assert_eq!(upper("FOO").unwrap().to_string(), "FOO");
501 assert_eq!(upper("FooBar").unwrap().to_string(), "FOOBAR");
502 assert_eq!(upper("foo").unwrap().to_string(), "FOO");
503 }
504
505 #[test]
506 fn test_trim() {
507 assert_eq!(trim(" Hello\tworld\t").unwrap().to_string(), "Hello\tworld");
508 }
509
510 #[test]
511 fn test_capitalize() {
512 assert_eq!(capitalize("foo").unwrap().to_string(), "Foo".to_string());
513 assert_eq!(capitalize("f").unwrap().to_string(), "F".to_string());
514 assert_eq!(capitalize("fO").unwrap().to_string(), "Fo".to_string());
515 assert_eq!(capitalize("").unwrap().to_string(), String::new());
516 assert_eq!(capitalize("FoO").unwrap().to_string(), "Foo".to_string());
517 assert_eq!(
518 capitalize("foO BAR").unwrap().to_string(),
519 "Foo bar".to_string()
520 );
521 assert_eq!(
522 capitalize("äØÄÅÖ").unwrap().to_string(),
523 "Äøäåö".to_string()
524 );
525 assert_eq!(capitalize("ß").unwrap().to_string(), "SS".to_string());
526 assert_eq!(capitalize("ßß").unwrap().to_string(), "SSß".to_string());
527 }
528
529 #[test]
530 fn test_title() {
531 assert_eq!(&title("").unwrap().to_string(), "");
532 assert_eq!(&title(" \n\t").unwrap().to_string(), " \n\t");
533 assert_eq!(&title("foo").unwrap().to_string(), "Foo");
534 assert_eq!(&title(" foo").unwrap().to_string(), " Foo");
535 assert_eq!(&title("foo bar").unwrap().to_string(), "Foo Bar");
536 assert_eq!(&title("foo bar ").unwrap().to_string(), "Foo Bar ");
537 assert_eq!(&title("fOO").unwrap().to_string(), "Foo");
538 assert_eq!(&title("fOo BaR").unwrap().to_string(), "Foo Bar");
539 assert_eq!(&title("foo\r\nbar").unwrap().to_string(), "Foo\r\nBar");
540 assert_eq!(
541 &title("Fo\x0boo\x0coO\u{2002}OO\u{3000}baR")
542 .unwrap()
543 .to_string(),
544 "Fo\x0bOo\x0cOo\u{2002}Oo\u{3000}Bar"
545 );
546 }
547}