1use std::env;
12use std::fs;
13use std::path::{Path, PathBuf};
14use std::time::{Duration, Instant, SystemTime, UNIX_EPOCH};
15
16#[derive(Debug, Clone, Copy, Default)]
18pub struct TimeSpec {
19 pub tv_sec: i64,
20 pub tv_nsec: i64,
21}
22
23impl TimeSpec {
24 pub fn new(sec: i64, nsec: i64) -> Self {
25 TimeSpec {
26 tv_sec: sec,
27 tv_nsec: nsec,
28 }
29 }
30
31 pub fn now() -> Self {
32 match SystemTime::now().duration_since(UNIX_EPOCH) {
33 Ok(d) => TimeSpec {
34 tv_sec: d.as_secs() as i64,
35 tv_nsec: d.subsec_nanos() as i64,
36 },
37 Err(_) => TimeSpec::default(),
38 }
39 }
40
41 pub fn as_duration(&self) -> Duration {
42 Duration::new(self.tv_sec as u64, self.tv_nsec as u32)
43 }
44
45 pub fn as_secs_f64(&self) -> f64 {
46 self.tv_sec as f64 + (self.tv_nsec as f64 / 1_000_000_000.0)
47 }
48}
49
50impl std::ops::Sub for TimeSpec {
51 type Output = TimeSpec;
52
53 fn sub(self, other: TimeSpec) -> TimeSpec {
54 let mut sec = self.tv_sec - other.tv_sec;
55 let mut nsec = self.tv_nsec - other.tv_nsec;
56 if nsec < 0 {
57 sec -= 1;
58 nsec += 1_000_000_000;
59 }
60 TimeSpec {
61 tv_sec: sec,
62 tv_nsec: nsec,
63 }
64 }
65}
66
67pub fn zgettime() -> TimeSpec {
69 TimeSpec::now()
70}
71
72static MONOTONIC_START: std::sync::OnceLock<Instant> = std::sync::OnceLock::new();
74
75pub fn zgettime_monotonic() -> TimeSpec {
77 let start = MONOTONIC_START.get_or_init(Instant::now);
78 let elapsed = start.elapsed();
79 TimeSpec {
80 tv_sec: elapsed.as_secs() as i64,
81 tv_nsec: elapsed.subsec_nanos() as i64,
82 }
83}
84
85pub fn difftime(t2: i64, t1: i64) -> f64 {
87 (t2 - t1) as f64
88}
89
90pub fn zopenmax() -> i64 {
92 #[cfg(unix)]
93 {
94 unsafe {
96 let max = libc::sysconf(libc::_SC_OPEN_MAX);
97 if max > 0 {
98 return max.min(1024 * 1024);
100 }
101 }
102 }
103
104 1024
106}
107
108pub fn zgetcwd() -> Option<String> {
110 env::current_dir()
111 .ok()
112 .and_then(|p| p.to_str().map(|s| s.to_string()))
113}
114
115pub struct DirSav {
117 pub dirname: Option<String>,
118 #[cfg(unix)]
119 pub ino: u64,
120 #[cfg(unix)]
121 pub dev: u64,
122}
123
124impl Default for DirSav {
125 fn default() -> Self {
126 DirSav {
127 dirname: None,
128 #[cfg(unix)]
129 ino: 0,
130 #[cfg(unix)]
131 dev: 0,
132 }
133 }
134}
135
136pub fn zgetdir(d: Option<&mut DirSav>) -> Option<String> {
138 let cwd = env::current_dir().ok()?;
139 let cwd_str = cwd.to_str()?.to_string();
140
141 #[cfg(unix)]
142 if let Some(dirsav) = d {
143 use std::os::unix::fs::MetadataExt;
144 if let Ok(meta) = fs::metadata(&cwd) {
145 dirsav.ino = meta.ino();
146 dirsav.dev = meta.dev();
147 }
148 dirsav.dirname = Some(cwd_str.clone());
149 }
150
151 #[cfg(not(unix))]
152 if let Some(dirsav) = d {
153 dirsav.dirname = Some(cwd_str.clone());
154 }
155
156 Some(cwd_str)
157}
158
159pub fn zchdir(dir: &str) -> i32 {
162 if dir.is_empty() {
163 return 0;
164 }
165
166 if env::set_current_dir(dir).is_ok() {
168 return 0;
169 }
170
171 let path = Path::new(dir);
173 if !path.is_absolute() {
174 return -1;
175 }
176
177 let saved_dir = env::current_dir().ok();
179
180 let mut current = PathBuf::from("/");
182 for component in path.components().skip(1) {
183 current.push(component);
184 if env::set_current_dir(¤t).is_err() {
185 if let Some(ref saved) = saved_dir {
187 if env::set_current_dir(saved).is_err() {
188 return -2; }
190 }
191 return -1;
192 }
193 }
194
195 0
196}
197
198pub fn output64(val: i64) -> String {
200 val.to_string()
201}
202
203pub fn output64u(val: u64) -> String {
205 val.to_string()
206}
207
208pub fn convbase(val: i64, base: u32) -> String {
210 if base == 0 || base == 10 {
211 return val.to_string();
212 }
213
214 let is_negative = val < 0;
215 let mut n = val.unsigned_abs();
216 let mut result = String::new();
217
218 if n == 0 {
219 return "0".to_string();
220 }
221
222 let digits = b"0123456789abcdefghijklmnopqrstuvwxyz";
223 while n > 0 {
224 let digit = (n % base as u64) as usize;
225 result.push(digits[digit] as char);
226 n /= base as u64;
227 }
228
229 if is_negative {
230 result.push('-');
231 }
232
233 result.chars().rev().collect()
234}
235
236pub fn convbaseu(val: u64, base: u32) -> String {
238 if base == 0 || base == 10 {
239 return val.to_string();
240 }
241
242 let mut n = val;
243 let mut result = String::new();
244
245 if n == 0 {
246 return "0".to_string();
247 }
248
249 let digits = b"0123456789abcdefghijklmnopqrstuvwxyz";
250 while n > 0 {
251 let digit = (n % base as u64) as usize;
252 result.push(digits[digit] as char);
253 n /= base as u64;
254 }
255
256 result.chars().rev().collect()
257}
258
259pub fn gethostname() -> Option<String> {
261 hostname::get().ok().and_then(|h| h.into_string().ok())
262}
263
264pub fn isprint_safe(c: char) -> bool {
266 let b = c as u32;
267 b >= 0x20 && b <= 0x7e
268}
269
270pub fn wcwidth(c: char) -> i32 {
272 unicode_width::UnicodeWidthChar::width(c)
273 .map(|w| w as i32)
274 .unwrap_or(if c.is_control() { -1 } else { 1 })
275}
276
277pub fn iswprint(c: char) -> bool {
279 !c.is_control() && wcwidth(c) >= 0
280}
281
282pub fn strwidth(s: &str) -> usize {
284 unicode_width::UnicodeWidthStr::width(s)
285}
286
287pub fn metafy(s: &str) -> String {
289 let mut result = String::with_capacity(s.len() * 2);
290 for c in s.chars() {
291 let b = c as u32;
292 if b < 32 || (b >= 0x83 && b <= 0x9b) {
293 result.push('\u{83}'); result.push(char::from_u32(b ^ 32).unwrap_or(c));
295 } else {
296 result.push(c);
297 }
298 }
299 result
300}
301
302pub fn unmetafy(s: &str) -> String {
304 let mut result = String::with_capacity(s.len());
305 let mut chars = s.chars().peekable();
306
307 while let Some(c) = chars.next() {
308 if c == '\u{83}' {
309 if let Some(&next) = chars.peek() {
310 chars.next();
311 let b = next as u32;
312 result.push(char::from_u32(b ^ 32).unwrap_or(next));
313 } else {
314 result.push(c);
315 }
316 } else {
317 result.push(c);
318 }
319 }
320 result
321}
322
323#[cfg(test)]
324mod tests {
325 use super::*;
326
327 #[test]
328 fn test_timespec() {
329 let t1 = TimeSpec::new(10, 500_000_000);
330 let t2 = TimeSpec::new(12, 200_000_000);
331 let diff = t2 - t1;
332 assert_eq!(diff.tv_sec, 1);
333 assert_eq!(diff.tv_nsec, 700_000_000);
334 }
335
336 #[test]
337 fn test_timespec_negative() {
338 let t1 = TimeSpec::new(10, 800_000_000);
339 let t2 = TimeSpec::new(12, 200_000_000);
340 let diff = t2 - t1;
341 assert_eq!(diff.tv_sec, 1);
342 assert_eq!(diff.tv_nsec, 400_000_000);
343 }
344
345 #[test]
346 fn test_zgettime() {
347 let t = zgettime();
348 assert!(t.tv_sec > 0);
349 }
350
351 #[test]
352 fn test_zgettime_monotonic() {
353 let t1 = zgettime_monotonic();
354 std::thread::sleep(std::time::Duration::from_millis(10));
355 let t2 = zgettime_monotonic();
356 let diff = t2 - t1;
357 assert!(diff.tv_sec > 0 || diff.tv_nsec > 0);
358 }
359
360 #[test]
361 fn test_convbase() {
362 assert_eq!(convbase(255, 16), "ff");
363 assert_eq!(convbase(8, 2), "1000");
364 assert_eq!(convbase(-10, 10), "-10");
365 assert_eq!(convbase(0, 16), "0");
366 }
367
368 #[test]
369 fn test_convbaseu() {
370 assert_eq!(convbaseu(255, 16), "ff");
371 assert_eq!(convbaseu(8, 8), "10");
372 }
373
374 #[test]
375 fn test_zgetcwd() {
376 let cwd = zgetcwd();
377 assert!(cwd.is_some());
378 assert!(!cwd.unwrap().is_empty());
379 }
380
381 #[test]
382 fn test_zopenmax() {
383 let max = zopenmax();
384 assert!(max > 0);
385 }
386
387 #[test]
388 fn test_gethostname() {
389 let host = gethostname();
390 assert!(host.is_some());
391 }
392
393 #[test]
394 fn test_isprint_safe() {
395 assert!(isprint_safe('a'));
396 assert!(isprint_safe('Z'));
397 assert!(isprint_safe(' '));
398 assert!(!isprint_safe('\x00'));
399 assert!(!isprint_safe('\x1f'));
400 }
401
402 #[test]
403 fn test_wcwidth() {
404 assert_eq!(wcwidth('a'), 1);
405 assert_eq!(wcwidth('中'), 2);
406 assert!(wcwidth('\x00') <= 0);
407 }
408
409 #[test]
410 fn test_strwidth() {
411 assert_eq!(strwidth("hello"), 5);
412 assert_eq!(strwidth("中文"), 4);
413 }
414
415 #[test]
416 fn test_metafy_unmetafy() {
417 let original = "hello\x00world";
418 let meta = metafy(original);
419 let unmeta = unmetafy(&meta);
420 assert_eq!(unmeta, original);
421 }
422}