Skip to main content

bel7_cli/
truncate.rs

1// Copyright (C) 2025-2026 Michael S. Klishin and Contributors
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15//! String truncation utilities that can be used by [`std::fmt::Display`] implementations.
16
17/// Default suffix appended to truncated strings.
18pub const DEFAULT_TRUNCATION_SUFFIX: &str = "...";
19
20/// Truncates a string to a maximum number of characters.
21///
22/// If the string exceeds `max_chars`, it's truncated and the suffix
23/// (default "...") is appended. The total length including suffix
24/// will not exceed `max_chars`.
25///
26/// # Example
27///
28/// ```
29/// use bel7_cli::truncate_string;
30///
31/// assert_eq!(truncate_string("Hello", 10), "Hello");
32/// assert_eq!(truncate_string("Hello, World!", 8), "Hello...");
33/// ```
34pub fn truncate_string(s: &str, max_chars: usize) -> String {
35    truncate_with_suffix(s, max_chars, DEFAULT_TRUNCATION_SUFFIX)
36}
37
38/// Truncates a string with a custom suffix.
39///
40/// # Example
41///
42/// ```
43/// use bel7_cli::truncate_with_suffix;
44///
45/// assert_eq!(truncate_with_suffix("Hello, World!", 8, "…"), "Hello, …");
46/// ```
47pub fn truncate_with_suffix(s: &str, max_chars: usize, suffix: &str) -> String {
48    let char_count = s.chars().count();
49
50    if char_count <= max_chars {
51        return s.to_string();
52    }
53
54    let suffix_len = suffix.chars().count();
55    let take_chars = max_chars.saturating_sub(suffix_len);
56
57    let truncated: String = s.chars().take(take_chars).collect();
58    format!("{}{}", truncated, suffix)
59}
60
61/// Truncates a string in the middle, keeping start and end.
62///
63/// Useful for file paths or long identifiers where both ends are important.
64///
65/// # Example
66///
67/// ```
68/// use bel7_cli::truncate_middle;
69///
70/// let result = truncate_middle("/very/long/path/to/file.txt", 20);
71/// assert!(result.len() <= 20);
72/// assert!(result.contains("..."));
73/// ```
74pub fn truncate_middle(s: &str, max_chars: usize) -> String {
75    let char_count = s.chars().count();
76
77    if char_count <= max_chars {
78        return s.to_string();
79    }
80
81    let suffix = "...";
82    let suffix_char_count = suffix.chars().count();
83
84    if max_chars <= suffix_char_count {
85        return suffix.chars().take(max_chars).collect();
86    }
87
88    let available = max_chars - suffix_char_count;
89    let start_len = available.div_ceil(2);
90    let end_len = available / 2;
91
92    let start: String = s.chars().take(start_len).collect();
93    let end: String = s.chars().skip(char_count - end_len).collect();
94
95    format!("{}{}{}", start, suffix, end)
96}