Skip to main content

doido_core/
core_ext.rs

1//! Core extensions (Rails `blank?`/`present?` and friends).
2
3/// `blank?`/`present?` for strings, options, and collections.
4pub trait Blank {
5    /// Whether the value is "blank": empty/whitespace/None.
6    fn is_blank(&self) -> bool;
7    /// The inverse of [`is_blank`](Self::is_blank).
8    fn is_present(&self) -> bool {
9        !self.is_blank()
10    }
11}
12
13impl Blank for str {
14    fn is_blank(&self) -> bool {
15        self.trim().is_empty()
16    }
17}
18
19impl<T: Blank + ?Sized> Blank for &T {
20    fn is_blank(&self) -> bool {
21        (**self).is_blank()
22    }
23}
24
25impl Blank for String {
26    fn is_blank(&self) -> bool {
27        self.as_str().is_blank()
28    }
29}
30
31impl<T: Blank> Blank for Option<T> {
32    fn is_blank(&self) -> bool {
33        self.as_ref().map(Blank::is_blank).unwrap_or(true)
34    }
35}
36
37impl<T> Blank for Vec<T> {
38    fn is_blank(&self) -> bool {
39        self.is_empty()
40    }
41}
42
43/// String conveniences (Rails `truncate`).
44pub trait StringExt {
45    /// Truncate to at most `len` characters, appending `…` when cut.
46    fn truncate_chars(&self, len: usize) -> String;
47}
48
49impl StringExt for str {
50    fn truncate_chars(&self, len: usize) -> String {
51        if self.chars().count() <= len {
52            self.to_string()
53        } else {
54            let kept: String = self.chars().take(len.saturating_sub(1)).collect();
55            format!("{kept}…")
56        }
57    }
58}