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}