1 | //! Module for built-in filter functions |
2 | //! |
3 | //! Contains all the built-in filter functions for use in templates. |
4 | //! You can define your own filters, as well. |
5 | //! For more information, read the [book](https://djc.github.io/askama/filters.html). |
6 | #![allow (clippy::trivially_copy_pass_by_ref)] |
7 | |
8 | use std::fmt::{self, Write}; |
9 | |
10 | #[cfg (feature = "serde-json" )] |
11 | mod json; |
12 | #[cfg (feature = "serde-json" )] |
13 | pub use self::json::json; |
14 | |
15 | #[cfg (feature = "serde-yaml" )] |
16 | mod yaml; |
17 | #[cfg (feature = "serde-yaml" )] |
18 | pub use self::yaml::yaml; |
19 | |
20 | #[allow (unused_imports)] |
21 | use crate::error::Error::Fmt; |
22 | use askama_escape::{Escaper, MarkupDisplay}; |
23 | #[cfg (feature = "humansize" )] |
24 | use dep_humansize::{format_size_i, ToF64, DECIMAL}; |
25 | #[cfg (feature = "num-traits" )] |
26 | use dep_num_traits::{cast::NumCast, Signed}; |
27 | #[cfg (feature = "percent-encoding" )] |
28 | use percent_encoding::{utf8_percent_encode, AsciiSet, NON_ALPHANUMERIC}; |
29 | |
30 | use super::Result; |
31 | |
32 | #[cfg (feature = "percent-encoding" )] |
33 | // Urlencode char encoding set. Only the characters in the unreserved set don't |
34 | // have any special purpose in any part of a URI and can be safely left |
35 | // unencoded as specified in https://tools.ietf.org/html/rfc3986.html#section-2.3 |
36 | const URLENCODE_STRICT_SET: &AsciiSet = &NON_ALPHANUMERIC |
37 | .remove(b'_' ) |
38 | .remove(b'.' ) |
39 | .remove(b'-' ) |
40 | .remove(b'~' ); |
41 | |
42 | #[cfg (feature = "percent-encoding" )] |
43 | // Same as URLENCODE_STRICT_SET, but preserves forward slashes for encoding paths |
44 | const URLENCODE_SET: &AsciiSet = &URLENCODE_STRICT_SET.remove(b'/' ); |
45 | |
46 | /// Marks a string (or other `Display` type) as safe |
47 | /// |
48 | /// Use this is you want to allow markup in an expression, or if you know |
49 | /// that the expression's contents don't need to be escaped. |
50 | /// |
51 | /// Askama will automatically insert the first (`Escaper`) argument, |
52 | /// so this filter only takes a single argument of any type that implements |
53 | /// `Display`. |
54 | pub fn safe<E, T>(e: E, v: T) -> Result<MarkupDisplay<E, T>> |
55 | where |
56 | E: Escaper, |
57 | T: fmt::Display, |
58 | { |
59 | Ok(MarkupDisplay::new_safe(value:v, escaper:e)) |
60 | } |
61 | |
62 | /// Escapes strings according to the escape mode. |
63 | /// |
64 | /// Askama will automatically insert the first (`Escaper`) argument, |
65 | /// so this filter only takes a single argument of any type that implements |
66 | /// `Display`. |
67 | /// |
68 | /// It is possible to optionally specify an escaper other than the default for |
69 | /// the template's extension, like `{{ val|escape("txt") }}`. |
70 | pub fn escape<E, T>(e: E, v: T) -> Result<MarkupDisplay<E, T>> |
71 | where |
72 | E: Escaper, |
73 | T: fmt::Display, |
74 | { |
75 | Ok(MarkupDisplay::new_unsafe(value:v, escaper:e)) |
76 | } |
77 | |
78 | #[cfg (feature = "humansize" )] |
79 | /// Returns adequate string representation (in KB, ..) of number of bytes |
80 | pub fn filesizeformat(b: &(impl ToF64 + Copy)) -> Result<String> { |
81 | Ok(format_size_i(*b, DECIMAL)) |
82 | } |
83 | |
84 | #[cfg (feature = "percent-encoding" )] |
85 | /// Percent-encodes the argument for safe use in URI; does not encode `/`. |
86 | /// |
87 | /// This should be safe for all parts of URI (paths segments, query keys, query |
88 | /// values). In the rare case that the server can't deal with forward slashes in |
89 | /// the query string, use [`urlencode_strict`], which encodes them as well. |
90 | /// |
91 | /// Encodes all characters except ASCII letters, digits, and `_.-~/`. In other |
92 | /// words, encodes all characters which are not in the unreserved set, |
93 | /// as specified by [RFC3986](https://tools.ietf.org/html/rfc3986#section-2.3), |
94 | /// with the exception of `/`. |
95 | /// |
96 | /// ```none,ignore |
97 | /// <a href="/metro{{ "/stations/Château d'Eau"|urlencode }}">Station</a> |
98 | /// <a href="/page?text={{ "look, unicode/emojis ✨"|urlencode }}">Page</a> |
99 | /// ``` |
100 | /// |
101 | /// To encode `/` as well, see [`urlencode_strict`](./fn.urlencode_strict.html). |
102 | /// |
103 | /// [`urlencode_strict`]: ./fn.urlencode_strict.html |
104 | pub fn urlencode<T: fmt::Display>(s: T) -> Result<String> { |
105 | let s = s.to_string(); |
106 | Ok(utf8_percent_encode(&s, URLENCODE_SET).to_string()) |
107 | } |
108 | |
109 | #[cfg (feature = "percent-encoding" )] |
110 | /// Percent-encodes the argument for safe use in URI; encodes `/`. |
111 | /// |
112 | /// Use this filter for encoding query keys and values in the rare case that |
113 | /// the server can't process them unencoded. |
114 | /// |
115 | /// Encodes all characters except ASCII letters, digits, and `_.-~`. In other |
116 | /// words, encodes all characters which are not in the unreserved set, |
117 | /// as specified by [RFC3986](https://tools.ietf.org/html/rfc3986#section-2.3). |
118 | /// |
119 | /// ```none,ignore |
120 | /// <a href="/page?text={{ "look, unicode/emojis ✨"|urlencode_strict }}">Page</a> |
121 | /// ``` |
122 | /// |
123 | /// If you want to preserve `/`, see [`urlencode`](./fn.urlencode.html). |
124 | pub fn urlencode_strict<T: fmt::Display>(s: T) -> Result<String> { |
125 | let s = s.to_string(); |
126 | Ok(utf8_percent_encode(&s, URLENCODE_STRICT_SET).to_string()) |
127 | } |
128 | |
129 | /// Formats arguments according to the specified format |
130 | /// |
131 | /// The *second* argument to this filter must be a string literal (as in normal |
132 | /// Rust). The two arguments are passed through to the `format!()` |
133 | /// [macro](https://doc.rust-lang.org/stable/std/macro.format.html) by |
134 | /// the Askama code generator, but the order is swapped to support filter |
135 | /// composition. |
136 | /// |
137 | /// ```ignore |
138 | /// {{ value | fmt("{:?}" ) }} |
139 | /// ``` |
140 | /// |
141 | /// Compare with [format](./fn.format.html). |
142 | pub fn fmt() {} |
143 | |
144 | /// Formats arguments according to the specified format |
145 | /// |
146 | /// The first argument to this filter must be a string literal (as in normal |
147 | /// Rust). All arguments are passed through to the `format!()` |
148 | /// [macro](https://doc.rust-lang.org/stable/std/macro.format.html) by |
149 | /// the Askama code generator. |
150 | /// |
151 | /// ```ignore |
152 | /// {{ "{:?}{:?}" | format(value, other_value) }} |
153 | /// ``` |
154 | /// |
155 | /// Compare with [fmt](./fn.fmt.html). |
156 | pub fn format() {} |
157 | |
158 | /// Replaces line breaks in plain text with appropriate HTML |
159 | /// |
160 | /// A single newline becomes an HTML line break `<br>` and a new line |
161 | /// followed by a blank line becomes a paragraph break `<p>`. |
162 | pub fn linebreaks<T: fmt::Display>(s: T) -> Result<String> { |
163 | let s: String = s.to_string(); |
164 | let linebroken: String = s.replace(" \n\n" , "</p><p>" ).replace(from:' \n' , to:"<br/>" ); |
165 | |
166 | Ok(format!("<p> {linebroken}</p>" )) |
167 | } |
168 | |
169 | /// Converts all newlines in a piece of plain text to HTML line breaks |
170 | pub fn linebreaksbr<T: fmt::Display>(s: T) -> Result<String> { |
171 | let s: String = s.to_string(); |
172 | Ok(s.replace(from:' \n' , to:"<br/>" )) |
173 | } |
174 | |
175 | /// Replaces only paragraph breaks in plain text with appropriate HTML |
176 | /// |
177 | /// A new line followed by a blank line becomes a paragraph break `<p>`. |
178 | /// Paragraph tags only wrap content; empty paragraphs are removed. |
179 | /// No `<br/>` tags are added. |
180 | pub fn paragraphbreaks<T: fmt::Display>(s: T) -> Result<String> { |
181 | let s: String = s.to_string(); |
182 | let linebroken: String = s.replace(" \n\n" , "</p><p>" ).replace(from:"<p></p>" , to:"" ); |
183 | |
184 | Ok(format!("<p> {linebroken}</p>" )) |
185 | } |
186 | |
187 | /// Converts to lowercase |
188 | pub fn lower<T: fmt::Display>(s: T) -> Result<String> { |
189 | let s: String = s.to_string(); |
190 | Ok(s.to_lowercase()) |
191 | } |
192 | |
193 | /// Alias for the `lower()` filter |
194 | pub fn lowercase<T: fmt::Display>(s: T) -> Result<String> { |
195 | lower(s) |
196 | } |
197 | |
198 | /// Converts to uppercase |
199 | pub fn upper<T: fmt::Display>(s: T) -> Result<String> { |
200 | let s: String = s.to_string(); |
201 | Ok(s.to_uppercase()) |
202 | } |
203 | |
204 | /// Alias for the `upper()` filter |
205 | pub fn uppercase<T: fmt::Display>(s: T) -> Result<String> { |
206 | upper(s) |
207 | } |
208 | |
209 | /// Strip leading and trailing whitespace |
210 | pub fn trim<T: fmt::Display>(s: T) -> Result<String> { |
211 | let s: String = s.to_string(); |
212 | Ok(s.trim().to_owned()) |
213 | } |
214 | |
215 | /// Limit string length, appends '...' if truncated |
216 | pub fn truncate<T: fmt::Display>(s: T, len: usize) -> Result<String> { |
217 | let mut s: String = s.to_string(); |
218 | if s.len() > len { |
219 | let mut real_len: usize = len; |
220 | while !s.is_char_boundary(index:real_len) { |
221 | real_len += 1; |
222 | } |
223 | s.truncate(new_len:real_len); |
224 | s.push_str(string:"..." ); |
225 | } |
226 | Ok(s) |
227 | } |
228 | |
229 | /// Indent lines with `width` spaces |
230 | pub fn indent<T: fmt::Display>(s: T, width: usize) -> Result<String> { |
231 | let s: String = s.to_string(); |
232 | |
233 | let mut indented: String = String::new(); |
234 | |
235 | for (i: usize, c: char) in s.char_indices() { |
236 | indented.push(ch:c); |
237 | |
238 | if c == ' \n' && i < s.len() - 1 { |
239 | for _ in 0..width { |
240 | indented.push(ch:' ' ); |
241 | } |
242 | } |
243 | } |
244 | |
245 | Ok(indented) |
246 | } |
247 | |
248 | #[cfg (feature = "num-traits" )] |
249 | /// Casts number to f64 |
250 | pub fn into_f64<T>(number: T) -> Result<f64> |
251 | where |
252 | T: NumCast, |
253 | { |
254 | number.to_f64().ok_or(Fmt(fmt::Error)) |
255 | } |
256 | |
257 | #[cfg (feature = "num-traits" )] |
258 | /// Casts number to isize |
259 | pub fn into_isize<T>(number: T) -> Result<isize> |
260 | where |
261 | T: NumCast, |
262 | { |
263 | number.to_isize().ok_or(Fmt(fmt::Error)) |
264 | } |
265 | |
266 | /// Joins iterable into a string separated by provided argument |
267 | pub fn join<T, I, S>(input: I, separator: S) -> Result<String> |
268 | where |
269 | T: fmt::Display, |
270 | I: Iterator<Item = T>, |
271 | S: AsRef<str>, |
272 | { |
273 | let separator: &str = separator.as_ref(); |
274 | |
275 | let mut rv: String = String::new(); |
276 | |
277 | for (num: usize, item: T) in input.enumerate() { |
278 | if num > 0 { |
279 | rv.push_str(string:separator); |
280 | } |
281 | |
282 | write!(rv, " {item}" )?; |
283 | } |
284 | |
285 | Ok(rv) |
286 | } |
287 | |
288 | #[cfg (feature = "num-traits" )] |
289 | /// Absolute value |
290 | pub fn abs<T>(number: T) -> Result<T> |
291 | where |
292 | T: Signed, |
293 | { |
294 | Ok(number.abs()) |
295 | } |
296 | |
297 | /// Capitalize a value. The first character will be uppercase, all others lowercase. |
298 | pub fn capitalize<T: fmt::Display>(s: T) -> Result<String> { |
299 | let s: String = s.to_string(); |
300 | match s.chars().next() { |
301 | Some(c: char) => { |
302 | let mut replacement: String = c.to_uppercase().collect(); |
303 | replacement.push_str(&s[c.len_utf8()..].to_lowercase()); |
304 | Ok(replacement) |
305 | } |
306 | _ => Ok(s), |
307 | } |
308 | } |
309 | |
310 | /// Centers the value in a field of a given width |
311 | pub fn center(src: &dyn fmt::Display, dst_len: usize) -> Result<String> { |
312 | let src = src.to_string(); |
313 | let len = src.len(); |
314 | |
315 | if dst_len <= len { |
316 | Ok(src) |
317 | } else { |
318 | let diff = dst_len - len; |
319 | let mid = diff / 2; |
320 | let r = diff % 2; |
321 | let mut buf = String::with_capacity(dst_len); |
322 | |
323 | for _ in 0..mid { |
324 | buf.push(' ' ); |
325 | } |
326 | |
327 | buf.push_str(&src); |
328 | |
329 | for _ in 0..mid + r { |
330 | buf.push(' ' ); |
331 | } |
332 | |
333 | Ok(buf) |
334 | } |
335 | } |
336 | |
337 | /// Count the words in that string |
338 | pub fn wordcount<T: fmt::Display>(s: T) -> Result<usize> { |
339 | let s: String = s.to_string(); |
340 | |
341 | Ok(s.split_whitespace().count()) |
342 | } |
343 | |
344 | #[cfg (feature = "markdown" )] |
345 | pub fn markdown<E, S>( |
346 | e: E, |
347 | s: S, |
348 | options: Option<&comrak::ComrakOptions>, |
349 | ) -> Result<MarkupDisplay<E, String>> |
350 | where |
351 | E: Escaper, |
352 | S: AsRef<str>, |
353 | { |
354 | use comrak::{ |
355 | markdown_to_html, ComrakExtensionOptions, ComrakOptions, ComrakParseOptions, |
356 | ComrakRenderOptions, ListStyleType, |
357 | }; |
358 | |
359 | const DEFAULT_OPTIONS: ComrakOptions = ComrakOptions { |
360 | extension: ComrakExtensionOptions { |
361 | strikethrough: true, |
362 | tagfilter: true, |
363 | table: true, |
364 | autolink: true, |
365 | // default: |
366 | tasklist: false, |
367 | superscript: false, |
368 | header_ids: None, |
369 | footnotes: false, |
370 | description_lists: false, |
371 | front_matter_delimiter: None, |
372 | }, |
373 | parse: ComrakParseOptions { |
374 | // default: |
375 | smart: false, |
376 | default_info_string: None, |
377 | relaxed_tasklist_matching: false, |
378 | }, |
379 | render: ComrakRenderOptions { |
380 | escape: true, |
381 | // default: |
382 | hardbreaks: false, |
383 | github_pre_lang: false, |
384 | full_info_string: false, |
385 | width: 0, |
386 | unsafe_: false, |
387 | list_style: ListStyleType::Dash, |
388 | sourcepos: false, |
389 | }, |
390 | }; |
391 | |
392 | let s = markdown_to_html(s.as_ref(), options.unwrap_or(&DEFAULT_OPTIONS)); |
393 | Ok(MarkupDisplay::new_safe(s, e)) |
394 | } |
395 | |
396 | #[cfg (test)] |
397 | mod tests { |
398 | use super::*; |
399 | #[cfg (feature = "num-traits" )] |
400 | use std::f64::INFINITY; |
401 | |
402 | #[cfg (feature = "humansize" )] |
403 | #[test ] |
404 | fn test_filesizeformat() { |
405 | assert_eq!(filesizeformat(&0).unwrap(), "0 B" ); |
406 | assert_eq!(filesizeformat(&999u64).unwrap(), "999 B" ); |
407 | assert_eq!(filesizeformat(&1000i32).unwrap(), "1 kB" ); |
408 | assert_eq!(filesizeformat(&1023).unwrap(), "1.02 kB" ); |
409 | assert_eq!(filesizeformat(&1024usize).unwrap(), "1.02 kB" ); |
410 | } |
411 | |
412 | #[cfg (feature = "percent-encoding" )] |
413 | #[test ] |
414 | fn test_urlencoding() { |
415 | // Unreserved (https://tools.ietf.org/html/rfc3986.html#section-2.3) |
416 | // alpha / digit |
417 | assert_eq!(urlencode("AZaz09" ).unwrap(), "AZaz09" ); |
418 | assert_eq!(urlencode_strict("AZaz09" ).unwrap(), "AZaz09" ); |
419 | // other |
420 | assert_eq!(urlencode("_.-~" ).unwrap(), "_.-~" ); |
421 | assert_eq!(urlencode_strict("_.-~" ).unwrap(), "_.-~" ); |
422 | |
423 | // Reserved (https://tools.ietf.org/html/rfc3986.html#section-2.2) |
424 | // gen-delims |
425 | assert_eq!(urlencode(":/?#[]@" ).unwrap(), "%3A/%3F%23%5B%5D%40" ); |
426 | assert_eq!( |
427 | urlencode_strict(":/?#[]@" ).unwrap(), |
428 | "%3A%2F%3F%23%5B%5D%40" |
429 | ); |
430 | // sub-delims |
431 | assert_eq!( |
432 | urlencode("!$&'()*+,;=" ).unwrap(), |
433 | "%21%24%26%27%28%29%2A%2B%2C%3B%3D" |
434 | ); |
435 | assert_eq!( |
436 | urlencode_strict("!$&'()*+,;=" ).unwrap(), |
437 | "%21%24%26%27%28%29%2A%2B%2C%3B%3D" |
438 | ); |
439 | |
440 | // Other |
441 | assert_eq!( |
442 | urlencode("žŠďŤňĚáÉóŮ" ).unwrap(), |
443 | "%C5%BE%C5%A0%C4%8F%C5%A4%C5%88%C4%9A%C3%A1%C3%89%C3%B3%C5%AE" |
444 | ); |
445 | assert_eq!( |
446 | urlencode_strict("žŠďŤňĚáÉóŮ" ).unwrap(), |
447 | "%C5%BE%C5%A0%C4%8F%C5%A4%C5%88%C4%9A%C3%A1%C3%89%C3%B3%C5%AE" |
448 | ); |
449 | |
450 | // Ferris |
451 | assert_eq!(urlencode("🦀" ).unwrap(), "%F0%9F%A6%80" ); |
452 | assert_eq!(urlencode_strict("🦀" ).unwrap(), "%F0%9F%A6%80" ); |
453 | } |
454 | |
455 | #[test ] |
456 | fn test_linebreaks() { |
457 | assert_eq!( |
458 | linebreaks("Foo \nBar Baz" ).unwrap(), |
459 | "<p>Foo<br/>Bar Baz</p>" |
460 | ); |
461 | assert_eq!( |
462 | linebreaks("Foo \nBar \n\nBaz" ).unwrap(), |
463 | "<p>Foo<br/>Bar</p><p>Baz</p>" |
464 | ); |
465 | } |
466 | |
467 | #[test ] |
468 | fn test_linebreaksbr() { |
469 | assert_eq!(linebreaksbr("Foo \nBar" ).unwrap(), "Foo<br/>Bar" ); |
470 | assert_eq!( |
471 | linebreaksbr("Foo \nBar \n\nBaz" ).unwrap(), |
472 | "Foo<br/>Bar<br/><br/>Baz" |
473 | ); |
474 | } |
475 | |
476 | #[test ] |
477 | fn test_paragraphbreaks() { |
478 | assert_eq!( |
479 | paragraphbreaks("Foo \nBar Baz" ).unwrap(), |
480 | "<p>Foo \nBar Baz</p>" |
481 | ); |
482 | assert_eq!( |
483 | paragraphbreaks("Foo \nBar \n\nBaz" ).unwrap(), |
484 | "<p>Foo \nBar</p><p>Baz</p>" |
485 | ); |
486 | assert_eq!( |
487 | paragraphbreaks("Foo \n\n\n\n\nBar \n\nBaz" ).unwrap(), |
488 | "<p>Foo</p><p> \nBar</p><p>Baz</p>" |
489 | ); |
490 | } |
491 | |
492 | #[test ] |
493 | fn test_lower() { |
494 | assert_eq!(lower("Foo" ).unwrap(), "foo" ); |
495 | assert_eq!(lower("FOO" ).unwrap(), "foo" ); |
496 | assert_eq!(lower("FooBar" ).unwrap(), "foobar" ); |
497 | assert_eq!(lower("foo" ).unwrap(), "foo" ); |
498 | } |
499 | |
500 | #[test ] |
501 | fn test_upper() { |
502 | assert_eq!(upper("Foo" ).unwrap(), "FOO" ); |
503 | assert_eq!(upper("FOO" ).unwrap(), "FOO" ); |
504 | assert_eq!(upper("FooBar" ).unwrap(), "FOOBAR" ); |
505 | assert_eq!(upper("foo" ).unwrap(), "FOO" ); |
506 | } |
507 | |
508 | #[test ] |
509 | fn test_trim() { |
510 | assert_eq!(trim(" Hello \tworld \t" ).unwrap(), "Hello \tworld" ); |
511 | } |
512 | |
513 | #[test ] |
514 | fn test_truncate() { |
515 | assert_eq!(truncate("hello" , 2).unwrap(), "he..." ); |
516 | let a = String::from("您好" ); |
517 | assert_eq!(a.len(), 6); |
518 | assert_eq!(String::from("您" ).len(), 3); |
519 | assert_eq!(truncate("您好" , 1).unwrap(), "您..." ); |
520 | assert_eq!(truncate("您好" , 2).unwrap(), "您..." ); |
521 | assert_eq!(truncate("您好" , 3).unwrap(), "您..." ); |
522 | assert_eq!(truncate("您好" , 4).unwrap(), "您好..." ); |
523 | assert_eq!(truncate("您好" , 6).unwrap(), "您好" ); |
524 | assert_eq!(truncate("您好" , 7).unwrap(), "您好" ); |
525 | let s = String::from("🤚a🤚" ); |
526 | assert_eq!(s.len(), 9); |
527 | assert_eq!(String::from("🤚" ).len(), 4); |
528 | assert_eq!(truncate("🤚a🤚" , 1).unwrap(), "🤚..." ); |
529 | assert_eq!(truncate("🤚a🤚" , 2).unwrap(), "🤚..." ); |
530 | assert_eq!(truncate("🤚a🤚" , 3).unwrap(), "🤚..." ); |
531 | assert_eq!(truncate("🤚a🤚" , 4).unwrap(), "🤚..." ); |
532 | assert_eq!(truncate("🤚a🤚" , 5).unwrap(), "🤚a..." ); |
533 | assert_eq!(truncate("🤚a🤚" , 6).unwrap(), "🤚a🤚..." ); |
534 | assert_eq!(truncate("🤚a🤚" , 9).unwrap(), "🤚a🤚" ); |
535 | assert_eq!(truncate("🤚a🤚" , 10).unwrap(), "🤚a🤚" ); |
536 | } |
537 | |
538 | #[test ] |
539 | fn test_indent() { |
540 | assert_eq!(indent("hello" , 2).unwrap(), "hello" ); |
541 | assert_eq!(indent("hello \n" , 2).unwrap(), "hello \n" ); |
542 | assert_eq!(indent("hello \nfoo" , 2).unwrap(), "hello \n foo" ); |
543 | assert_eq!( |
544 | indent("hello \nfoo \n bar" , 4).unwrap(), |
545 | "hello \n foo \n bar" |
546 | ); |
547 | } |
548 | |
549 | #[cfg (feature = "num-traits" )] |
550 | #[test ] |
551 | #[allow (clippy::float_cmp)] |
552 | fn test_into_f64() { |
553 | assert_eq!(into_f64(1).unwrap(), 1.0_f64); |
554 | assert_eq!(into_f64(1.9).unwrap(), 1.9_f64); |
555 | assert_eq!(into_f64(-1.9).unwrap(), -1.9_f64); |
556 | assert_eq!(into_f64(INFINITY as f32).unwrap(), INFINITY); |
557 | assert_eq!(into_f64(-INFINITY as f32).unwrap(), -INFINITY); |
558 | } |
559 | |
560 | #[cfg (feature = "num-traits" )] |
561 | #[test ] |
562 | fn test_into_isize() { |
563 | assert_eq!(into_isize(1).unwrap(), 1_isize); |
564 | assert_eq!(into_isize(1.9).unwrap(), 1_isize); |
565 | assert_eq!(into_isize(-1.9).unwrap(), -1_isize); |
566 | assert_eq!(into_isize(1.5_f64).unwrap(), 1_isize); |
567 | assert_eq!(into_isize(-1.5_f64).unwrap(), -1_isize); |
568 | match into_isize(INFINITY) { |
569 | Err(Fmt(fmt::Error)) => {} |
570 | _ => panic!("Should return error of type Err(Fmt(fmt::Error))" ), |
571 | }; |
572 | } |
573 | |
574 | #[allow (clippy::needless_borrow)] |
575 | #[test ] |
576 | fn test_join() { |
577 | assert_eq!( |
578 | join((&["hello" , "world" ]).iter(), ", " ).unwrap(), |
579 | "hello, world" |
580 | ); |
581 | assert_eq!(join((&["hello" ]).iter(), ", " ).unwrap(), "hello" ); |
582 | |
583 | let empty: &[&str] = &[]; |
584 | assert_eq!(join(empty.iter(), ", " ).unwrap(), "" ); |
585 | |
586 | let input: Vec<String> = vec!["foo" .into(), "bar" .into(), "bazz" .into()]; |
587 | assert_eq!(join(input.iter(), ":" ).unwrap(), "foo:bar:bazz" ); |
588 | |
589 | let input: &[String] = &["foo" .into(), "bar" .into()]; |
590 | assert_eq!(join(input.iter(), ":" ).unwrap(), "foo:bar" ); |
591 | |
592 | let real: String = "blah" .into(); |
593 | let input: Vec<&str> = vec![&real]; |
594 | assert_eq!(join(input.iter(), ";" ).unwrap(), "blah" ); |
595 | |
596 | assert_eq!( |
597 | join((&&&&&["foo" , "bar" ]).iter(), ", " ).unwrap(), |
598 | "foo, bar" |
599 | ); |
600 | } |
601 | |
602 | #[cfg (feature = "num-traits" )] |
603 | #[test ] |
604 | #[allow (clippy::float_cmp)] |
605 | fn test_abs() { |
606 | assert_eq!(abs(1).unwrap(), 1); |
607 | assert_eq!(abs(-1).unwrap(), 1); |
608 | assert_eq!(abs(1.0).unwrap(), 1.0); |
609 | assert_eq!(abs(-1.0).unwrap(), 1.0); |
610 | assert_eq!(abs(1.0_f64).unwrap(), 1.0_f64); |
611 | assert_eq!(abs(-1.0_f64).unwrap(), 1.0_f64); |
612 | } |
613 | |
614 | #[test ] |
615 | fn test_capitalize() { |
616 | assert_eq!(capitalize("foo" ).unwrap(), "Foo" .to_string()); |
617 | assert_eq!(capitalize("f" ).unwrap(), "F" .to_string()); |
618 | assert_eq!(capitalize("fO" ).unwrap(), "Fo" .to_string()); |
619 | assert_eq!(capitalize("" ).unwrap(), "" .to_string()); |
620 | assert_eq!(capitalize("FoO" ).unwrap(), "Foo" .to_string()); |
621 | assert_eq!(capitalize("foO BAR" ).unwrap(), "Foo bar" .to_string()); |
622 | assert_eq!(capitalize("äØÄÅÖ" ).unwrap(), "Äøäåö" .to_string()); |
623 | assert_eq!(capitalize("ß" ).unwrap(), "SS" .to_string()); |
624 | assert_eq!(capitalize("ßß" ).unwrap(), "SSß" .to_string()); |
625 | } |
626 | |
627 | #[test ] |
628 | fn test_center() { |
629 | assert_eq!(center(&"f" , 3).unwrap(), " f " .to_string()); |
630 | assert_eq!(center(&"f" , 4).unwrap(), " f " .to_string()); |
631 | assert_eq!(center(&"foo" , 1).unwrap(), "foo" .to_string()); |
632 | assert_eq!(center(&"foo bar" , 8).unwrap(), "foo bar " .to_string()); |
633 | } |
634 | |
635 | #[test ] |
636 | fn test_wordcount() { |
637 | assert_eq!(wordcount("" ).unwrap(), 0); |
638 | assert_eq!(wordcount(" \n\t" ).unwrap(), 0); |
639 | assert_eq!(wordcount("foo" ).unwrap(), 1); |
640 | assert_eq!(wordcount("foo bar" ).unwrap(), 2); |
641 | } |
642 | } |
643 | |