1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
pub trait TruncateEllipsis {
fn truncate_ellipsis(&self, max_len: usize) -> String;
}
impl TruncateEllipsis for str {
/// Truncate a string to a maximum length, adding an ellipsis if the string is longer than
/// `max_len`. The string is truncated to the number UTF-8 characters so it may be longer in
/// bytes than what the `max_len` specifies.
///
/// # Example
///
/// ```
/// # use ahiru_tpm::truncate_ellipsis::*;
/// # fn main() {
/// let str = "string with 20 chars";
/// let str_truncated = str.truncate_ellipsis(12);
/// assert_eq!(str_truncated, "string with…");
/// # }
/// ```
fn truncate_ellipsis(&self, max_len: usize) -> String {
assert!(max_len > 0, "The max length must be greater than 0");
//let char_indices = self.char_indices().try_len().expect("A str should have a known length");
let mut char_indices = self.char_indices();
// The last char when an ellipsis is applied. It's one less than the last possible char
// when the string is exactly max_len and no ellipsis needs to be added.
let last_char_with_ellipsis = char_indices.nth(max_len - 2);
// The first truncated char. If this is `Some`, the string is longer than max_len.
let truncated_char = char_indices.nth(1);
match truncated_char {
// When the string is longer, we need to add an ellipsis:
Some(_) => {
let (n, _) = last_char_with_ellipsis.expect("If truncated char is `Some`, the chars before it should also be `Some`");
let n = n+1;
let truncated = &self[..n];
format!("{}…", truncated)
}
// When the string is exactly max_len or shorter, we can just return a copy:
None => self.to_string(),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn should_not_truncate_shorter_strings() {
let str = "string with 20 chars";
let str_truncated = str.truncate_ellipsis(21);
assert_eq!(str_truncated, str);
}
#[test]
fn should_not_truncate_string_with_exactly_max_length() {
let str = "string with 20 chars";
let str_truncated = str.truncate_ellipsis(20);
assert_eq!(str_truncated, str);
}
#[test]
fn should_truncate_longer_strings() {
let str = "string with 20 chars";
let str_truncated = str.truncate_ellipsis(19);
assert_eq!(str_truncated, "string with 20 cha…");
}
#[test]
#[should_panic(expected = "The max length must be greater than 0")]
fn zero_max_length() {
"string with 20 chars".truncate_ellipsis(0);
}
#[test]
fn should_correctly_truncate_strings_with_utf8_chars() {
let str = "string with 82 chars and fancy 'Æ’' with two-bytes length, making it 83 bytes long.";
let str_truncated = str.truncate_ellipsis(81);
// Original string should be 83 bytes but only 82 chars long
assert_eq!(str.len(), 83);
assert_eq!(str.chars().count(), 82);
assert_eq!(str_truncated, "string with 82 chars and fancy 'ƒ' with two-bytes length, making it 83 bytes lon…");
// Truncated string contains a fancy "f" (two bytes) and an ellipsis char (three bytes) so
// it should have three more bytes than it has chars.
assert_eq!(str_truncated.len(), 84);
assert_eq!(str_truncated.chars().count(), 81);
}
}