agentmux_srv\backend\utilfn/
strutil.rs1#![allow(dead_code)]
2use std::io;
7use std::path::Path;
8
9pub fn has_binary_data(data: &[u8]) -> bool {
14 data.iter().any(|&b| b < 32 && b != b'\n' && b != b'\r' && b != b'\t' && b != 0x0C && b != 0x08)
15}
16
17pub fn is_binary_content(data: &[u8]) -> bool {
20 if data.is_empty() {
21 return false;
22 }
23 let sample_size = std::cmp::min(8192, data.len());
24 let sample = &data[..sample_size];
25
26 let null_count = sample.iter().filter(|&&b| b == 0).count();
27 if null_count as f64 / sample.len() as f64 > 0.01 {
28 return true;
29 }
30
31 if std::str::from_utf8(sample).is_err() {
32 return true;
33 }
34
35 false
36}
37
38pub fn star_match_string(pattern: &str, s: &str, delimiter: &str) -> bool {
43 let pattern_parts: Vec<&str> = pattern.split(delimiter).collect();
44 let string_parts: Vec<&str> = s.split(delimiter).collect();
45 let p_len = pattern_parts.len();
46 let s_len = string_parts.len();
47
48 for i in 0..p_len {
49 if pattern_parts[i] == "**" {
50 return i == p_len - 1;
51 }
52 if i >= s_len {
53 return false;
54 }
55 if pattern_parts[i] != "*" && pattern_parts[i] != string_parts[i] {
56 return false;
57 }
58 }
59 p_len == s_len
60}
61
62pub fn slice_idx<T: PartialEq>(arr: &[T], elem: &T) -> i32 {
66 for (idx, e) in arr.iter().enumerate() {
67 if e == elem {
68 return idx as i32;
69 }
70 }
71 -1
72}
73
74pub fn remove_elem<T: PartialEq + Clone>(arr: &[T], elem: &T) -> Vec<T> {
76 let idx = slice_idx(arr, elem);
77 if idx == -1 {
78 return arr.to_vec();
79 }
80 let idx = idx as usize;
81 let mut result = Vec::with_capacity(arr.len() - 1);
82 result.extend_from_slice(&arr[..idx]);
83 result.extend_from_slice(&arr[idx + 1..]);
84 result
85}
86
87pub fn add_elem_uniq<T: PartialEq + Clone>(arr: &[T], elem: T) -> Vec<T> {
89 if slice_idx(arr, &elem) != -1 {
90 return arr.to_vec();
91 }
92 let mut result = arr.to_vec();
93 result.push(elem);
94 result
95}
96
97pub fn move_to_front<T: Clone>(arr: &[T], idx: usize) -> Vec<T> {
99 if idx == 0 || idx >= arr.len() {
100 return arr.to_vec();
101 }
102 let mut rtn = Vec::with_capacity(arr.len());
103 rtn.push(arr[idx].clone());
104 rtn.extend_from_slice(&arr[..idx]);
105 rtn.extend_from_slice(&arr[idx + 1..]);
106 rtn
107}
108
109pub fn ellipsis_str(s: &str, max_len: usize) -> String {
113 let max_len = if max_len < 4 { 4 } else { max_len };
114 let char_count = s.chars().count();
115 if char_count > max_len {
116 let truncated: String = s.chars().take(max_len - 3).collect();
117 format!("{}...", truncated)
118 } else {
119 s.to_string()
120 }
121}
122
123pub fn truncate_string(s: &str, max_len: usize) -> String {
125 let char_count = s.chars().count();
126 if char_count <= max_len {
127 return s.to_string();
128 }
129 let max_len = if max_len < 4 { 4 } else { max_len };
130 let truncated: String = s.chars().take(max_len - 3).collect();
131 format!("{}...", truncated)
132}
133
134pub fn get_first_line(s: &str) -> &str {
136 match s.find('\n') {
137 Some(idx) => &s[..idx],
138 None => s,
139 }
140}
141
142pub fn atoi_no_err(s: &str) -> i32 {
144 s.parse::<i32>().unwrap_or(0)
145}
146
147pub fn random_hex_string(num_hex_digits: usize) -> Result<String, io::Error> {
149 use std::fmt::Write;
150 let num_bytes = num_hex_digits.div_ceil(2);
151 let mut bytes = vec![0u8; num_bytes];
152 getrandom(&mut bytes)?;
153 let mut hex = String::with_capacity(num_hex_digits);
154 for b in &bytes {
155 write!(hex, "{:02x}", b).unwrap();
156 }
157 hex.truncate(num_hex_digits);
158 Ok(hex)
159}
160
161fn getrandom(buf: &mut [u8]) -> Result<(), io::Error> {
164 #[cfg(unix)]
165 {
166 use std::fs::File;
167 use std::io::Read;
168 let mut f = File::open("/dev/urandom")?;
169 f.read_exact(buf)?;
170 }
171 #[cfg(windows)]
172 {
173 use std::collections::hash_map::RandomState;
174 use std::hash::{BuildHasher, Hasher};
175 let mut offset = 0;
176 while offset < buf.len() {
177 let state = RandomState::new();
178 let hash = state.build_hasher().finish();
179 let bytes = hash.to_ne_bytes();
180 let copy_len = std::cmp::min(bytes.len(), buf.len() - offset);
181 buf[offset..offset + copy_len].copy_from_slice(&bytes[..copy_len]);
182 offset += copy_len;
183 }
184 }
185 Ok(())
186}
187
188pub fn filter_valid_arch(arch: &str) -> Result<&'static str, String> {
190 match arch.trim().to_lowercase().as_str() {
191 "amd64" | "x86_64" | "x64" => Ok("x64"),
192 "arm64" | "aarch64" => Ok("arm64"),
193 other => Err(format!("unknown architecture: {}", other)),
194 }
195}
196
197pub fn atomic_rename_copy(dst_path: &Path, src_path: &Path, _perms: u32) -> io::Result<()> {
202 use std::fs;
203
204 let temp_name = format!("{}.new", dst_path.display());
205 let temp_path = Path::new(&temp_name);
206 fs::copy(src_path, temp_path)?;
207 #[cfg(unix)]
208 {
209 use std::os::unix::fs::PermissionsExt;
210 fs::set_permissions(temp_path, fs::Permissions::from_mode(_perms))?;
211 }
212 fs::rename(temp_path, dst_path)?;
213 Ok(())
214}
215
216pub fn write_file_if_different(file_name: &Path, contents: &[u8]) -> io::Result<bool> {
218 use std::fs;
219 if let Ok(old_contents) = fs::read(file_name) {
220 if old_contents == contents {
221 return Ok(false);
222 }
223 }
224 fs::write(file_name, contents)?;
225 Ok(true)
226}
227
228pub fn get_line_col_from_offset(data: &[u8], offset: usize) -> (usize, usize) {
232 let mut line = 1;
233 let mut col = 1;
234 for &byte in data.iter().take(std::cmp::min(offset, data.len())) {
235 if byte == b'\n' {
236 line += 1;
237 col = 1;
238 } else {
239 col += 1;
240 }
241 }
242 (line, col)
243}
244
245pub fn longest_prefix(root: &str, strs: &[&str]) -> String {
247 if strs.is_empty() {
248 return root.to_string();
249 }
250 if strs.len() == 1 {
251 let comp = strs[0];
252 if comp.len() >= root.len() && comp.starts_with(root) {
253 return comp.to_string();
254 }
255 }
256 let mut lcp = strs[0].to_string();
257 for s in &strs[1..] {
258 let mut end = 0;
259 for (j, (a, b)) in lcp.chars().zip(s.chars()).enumerate() {
260 if a != b {
261 break;
262 }
263 end = j + a.len_utf8();
264 }
265 lcp.truncate(end);
266 }
267 if lcp.len() < root.len() || !lcp.starts_with(root) {
268 return root.to_string();
269 }
270 lcp
271}
272
273pub fn indent_string(indent: &str, s: &str) -> String {
275 let mut result = String::new();
276 for line in s.split('\n') {
277 if line.is_empty() {
278 result.push('\n');
279 } else {
280 result.push_str(indent);
281 result.push_str(line);
282 result.push('\n');
283 }
284 }
285 result
286}
287
288pub fn shell_hex_escape(s: &str) -> String {
290 let mut result = String::new();
291 for b in s.bytes() {
292 result.push_str(&format!("\\x{:02x}", b));
293 }
294 result
295}
296
297pub fn combine_str_arrays(arr1: &[String], arr2: &[String]) -> Vec<String> {
299 let mut seen = std::collections::HashSet::new();
300 let mut result = Vec::new();
301 for s in arr1.iter().chain(arr2.iter()) {
302 if seen.insert(s.clone()) {
303 result.push(s.clone());
304 }
305 }
306 result
307}
308
309pub const NO_STR_POS: i32 = -1;
313
314#[derive(Debug, Clone, PartialEq)]
316pub struct StrWithPos {
317 pub str_val: String,
318 pub pos: i32,
319}
320
321impl StrWithPos {
322 pub fn new(s: String, pos: i32) -> Self {
323 Self { str_val: s, pos }
324 }
325
326 pub fn parse(s: &str) -> Self {
328 match s.find("[*]") {
329 None => Self { str_val: s.to_string(), pos: NO_STR_POS },
330 Some(idx) => {
331 let before = &s[..idx];
332 let after = &s[idx + 3..];
333 let pos = before.chars().count() as i32;
334 Self {
335 str_val: format!("{}{}", before, after),
336 pos,
337 }
338 }
339 }
340 }
341
342 pub fn prepend(&self, prefix: &str) -> Self {
343 Self {
344 str_val: format!("{}{}", prefix, self.str_val),
345 pos: prefix.chars().count() as i32 + self.pos,
346 }
347 }
348
349 pub fn append(&self, suffix: &str) -> Self {
350 Self {
351 str_val: format!("{}{}", self.str_val, suffix),
352 pos: self.pos,
353 }
354 }
355}
356
357impl std::fmt::Display for StrWithPos {
358 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
359 if self.pos == NO_STR_POS {
360 write!(f, "{}", self.str_val)
361 } else if self.pos < 0 {
362 write!(f, "[*]_{}", self.str_val)
363 } else {
364 let pos = self.pos as usize;
365 let mut chars: Vec<char> = Vec::new();
366 for (i, ch) in self.str_val.chars().enumerate() {
367 if i == pos {
368 chars.extend(['[', '*', ']']);
369 }
370 chars.push(ch);
371 }
372 if pos >= self.str_val.chars().count() {
373 chars.extend(['[', '*', ']']);
374 }
375 write!(f, "{}", chars.iter().collect::<String>())
376 }
377 }
378}