1use std::cmp::Ordering;
4
5#[inline]
7pub fn dupstring(s: &str) -> String {
8 s.to_string()
9}
10
11pub fn dupstring_wlen(s: &str, len: usize) -> String {
13 if len >= s.len() {
14 s.to_string()
15 } else {
16 s[..len].to_string()
17 }
18}
19
20pub fn tricat(s1: &str, s2: &str, s3: &str) -> String {
22 let mut result = String::with_capacity(s1.len() + s2.len() + s3.len());
23 result.push_str(s1);
24 result.push_str(s2);
25 result.push_str(s3);
26 result
27}
28
29pub fn bicat(s1: &str, s2: &str) -> String {
31 let mut result = String::with_capacity(s1.len() + s2.len());
32 result.push_str(s1);
33 result.push_str(s2);
34 result
35}
36
37pub fn dupstrpfx(s: &str, len: usize) -> String {
39 dupstring_wlen(s, len)
40}
41
42pub fn appstr(base: &str, append: &str) -> String {
44 bicat(base, append)
45}
46
47pub fn strend(s: &str) -> Option<char> {
49 s.chars().last()
50}
51
52pub mod sort_flags {
59 use crate::ported::zsh_h;
60
61 pub const SORTIT_BACKWARDS: u32 = zsh_h::SORTIT_BACKWARDS as u32;
63 pub const SORTIT_NUMERICALLY: u32 = zsh_h::SORTIT_NUMERICALLY as u32;
65 pub const SORTIT_NUMERICALLY_SIGNED: u32 = zsh_h::SORTIT_NUMERICALLY_SIGNED as u32;
67 pub const SORTIT_IGNORING_CASE: u32 = zsh_h::SORTIT_IGNORING_CASE as u32;
69 pub const SORTIT_IGNORING_BACKSLASHES: u32 = zsh_h::SORTIT_IGNORING_BACKSLASHES as u32;
71}
72
73pub fn zstrcmp(a: &str, b: &str, flags: u32) -> Ordering {
86 if flags & sort_flags::SORTIT_IGNORING_CASE != 0 {
91 return crate::ported::sort::zstrcmp(&a.to_lowercase(), &b.to_lowercase(), flags);
92 }
93 crate::ported::sort::zstrcmp(a, b, flags)
94}
95
96pub fn strmetasort(array: &mut [String], flags: u32) {
98 if array.len() < 2 {
99 return;
100 }
101
102 let backwards = flags & sort_flags::SORTIT_BACKWARDS != 0;
103
104 array.sort_by(|a, b| {
105 let cmp = zstrcmp(a, b, flags);
106 if backwards {
107 cmp.reverse()
108 } else {
109 cmp
110 }
111 });
112}
113
114pub fn sort_strings(array: &mut [&str], flags: u32) {
116 if array.len() < 2 {
117 return;
118 }
119
120 let backwards = flags & sort_flags::SORTIT_BACKWARDS != 0;
121
122 array.sort_by(|a, b| {
123 let cmp = zstrcmp(a, b, flags);
124 if backwards {
125 cmp.reverse()
126 } else {
127 cmp
128 }
129 });
130}
131
132pub fn natural_cmp(a: &str, b: &str) -> Ordering {
134 zstrcmp(a, b, sort_flags::SORTIT_NUMERICALLY)
135}
136
137pub fn strcasecmp(a: &str, b: &str) -> Ordering {
139 a.to_lowercase().cmp(&b.to_lowercase())
140}
141
142pub fn strstr(haystack: &str, needle: &str) -> Option<usize> {
144 haystack.find(needle)
145}
146
147pub fn strprefix(s: &str, prefix: &str) -> bool {
149 s.starts_with(prefix)
150}
151
152pub fn strsuffix(s: &str, suffix: &str) -> bool {
154 s.ends_with(suffix)
155}
156
157pub fn strjoin<I, S>(iter: I, sep: &str) -> String
159where
160 I: IntoIterator<Item = S>,
161 S: AsRef<str>,
162{
163 iter.into_iter()
164 .map(|s| s.as_ref().to_string())
165 .collect::<Vec<_>>()
166 .join(sep)
167}
168
169pub fn strsplit(s: &str, sep: char) -> Vec<&str> {
171 s.split(sep).collect()
172}
173
174pub fn strtrim(s: &str) -> &str {
176 s.trim()
177}
178
179pub fn strlower(s: &str) -> String {
181 s.to_lowercase()
182}
183
184pub fn strupper(s: &str) -> String {
186 s.to_uppercase()
187}
188
189#[cfg(test)]
190mod tests {
191 use super::*;
192
193 #[test]
194 fn test_dupstring() {
195 let _g = crate::test_util::global_state_lock();
196 assert_eq!(dupstring("hello"), "hello");
197 assert_eq!(dupstring(""), "");
198 }
199
200 #[test]
201 fn test_dupstring_wlen() {
202 let _g = crate::test_util::global_state_lock();
203 assert_eq!(dupstring_wlen("hello", 3), "hel");
204 assert_eq!(dupstring_wlen("hi", 10), "hi");
205 }
206
207 #[test]
208 fn test_tricat() {
209 let _g = crate::test_util::global_state_lock();
210 assert_eq!(tricat("a", "b", "c"), "abc");
211 assert_eq!(tricat("hello", " ", "world"), "hello world");
212 }
213
214 #[test]
215 fn test_bicat() {
216 let _g = crate::test_util::global_state_lock();
217 assert_eq!(bicat("hello", "world"), "helloworld");
218 }
219
220 #[test]
221 fn test_strend() {
222 let _g = crate::test_util::global_state_lock();
223 assert_eq!(strend("hello"), Some('o'));
224 assert_eq!(strend(""), None);
225 }
226
227 #[test]
228 fn test_zstrcmp_basic() {
229 let _g = crate::test_util::global_state_lock();
230 assert_eq!(zstrcmp("abc", "abc", 0), Ordering::Equal);
231 assert_eq!(zstrcmp("abc", "abd", 0), Ordering::Less);
232 assert_eq!(zstrcmp("abd", "abc", 0), Ordering::Greater);
233 }
234
235 #[test]
236 fn test_zstrcmp_case_insensitive() {
237 let _g = crate::test_util::global_state_lock();
238 let flags = sort_flags::SORTIT_IGNORING_CASE;
239 assert_eq!(zstrcmp("ABC", "abc", flags), Ordering::Equal);
240 assert_eq!(zstrcmp("ABC", "ABD", flags), Ordering::Less);
241 }
242
243 #[test]
244 fn test_zstrcmp_ignore_backslash() {
245 let _g = crate::test_util::global_state_lock();
246 let flags = sort_flags::SORTIT_IGNORING_BACKSLASHES;
247 assert_eq!(zstrcmp("a\\bc", "abc", flags), Ordering::Equal);
248 }
249
250 #[test]
251 fn test_zstrcmp_numeric() {
252 let _g = crate::test_util::global_state_lock();
253 let flags = sort_flags::SORTIT_NUMERICALLY;
254 assert_eq!(zstrcmp("file2", "file10", flags), Ordering::Less);
255 assert_eq!(zstrcmp("file10", "file2", flags), Ordering::Greater);
256 assert_eq!(zstrcmp("file10", "file10", flags), Ordering::Equal);
257 }
258
259 #[test]
260 fn test_zstrcmp_numeric_signed() {
261 let _g = crate::test_util::global_state_lock();
262 let flags = sort_flags::SORTIT_NUMERICALLY_SIGNED;
263 assert_eq!(zstrcmp("-5", "3", flags), Ordering::Less);
264 assert_eq!(zstrcmp("-10", "-2", flags), Ordering::Less);
265 }
266
267 #[test]
268 fn test_strmetasort() {
269 let _g = crate::test_util::global_state_lock();
270 let mut arr = vec![
271 "file10".to_string(),
272 "file2".to_string(),
273 "file1".to_string(),
274 ];
275 strmetasort(&mut arr, sort_flags::SORTIT_NUMERICALLY);
276 assert_eq!(arr, vec!["file1", "file2", "file10"]);
277 }
278
279 #[test]
280 fn test_strmetasort_backwards() {
281 let _g = crate::test_util::global_state_lock();
282 let mut arr = vec!["a".to_string(), "c".to_string(), "b".to_string()];
283 strmetasort(&mut arr, sort_flags::SORTIT_BACKWARDS);
284 assert_eq!(arr, vec!["c", "b", "a"]);
285 }
286
287 #[test]
288 fn test_natural_cmp() {
289 let _g = crate::test_util::global_state_lock();
290 assert_eq!(natural_cmp("item2", "item10"), Ordering::Less);
291 }
292
293 #[test]
294 fn test_strcasecmp() {
295 let _g = crate::test_util::global_state_lock();
296 assert_eq!(strcasecmp("Hello", "HELLO"), Ordering::Equal);
297 assert_eq!(strcasecmp("abc", "ABD"), Ordering::Less);
298 }
299
300 #[test]
301 fn test_strstr() {
302 let _g = crate::test_util::global_state_lock();
303 assert_eq!(strstr("hello world", "world"), Some(6));
304 assert_eq!(strstr("hello", "xyz"), None);
305 }
306
307 #[test]
308 fn test_strprefix_suffix() {
309 let _g = crate::test_util::global_state_lock();
310 assert!(strprefix("hello", "hel"));
311 assert!(!strprefix("hello", "ell"));
312 assert!(strsuffix("hello", "llo"));
313 assert!(!strsuffix("hello", "ell"));
314 }
315
316 #[test]
317 fn test_strjoin() {
318 let _g = crate::test_util::global_state_lock();
319 assert_eq!(strjoin(["a", "b", "c"], ","), "a,b,c");
320 assert_eq!(strjoin(Vec::<&str>::new(), ","), "");
321 }
322
323 #[test]
324 fn test_strsplit() {
325 let _g = crate::test_util::global_state_lock();
326 assert_eq!(strsplit("a,b,c", ','), vec!["a", "b", "c"]);
327 }
328
329 #[test]
330 fn test_strtrim() {
331 let _g = crate::test_util::global_state_lock();
332 assert_eq!(strtrim(" hello "), "hello");
333 }
334
335 #[test]
336 fn test_case_conversion() {
337 let _g = crate::test_util::global_state_lock();
338 assert_eq!(strlower("HeLLo"), "hello");
339 assert_eq!(strupper("HeLLo"), "HELLO");
340 }
341}