Skip to main content

gstuff/
gstuff.rs

1#![allow(unknown_lints, uncommon_codepoints)]
2#![allow(unused_imports)]
3
4#![cfg_attr(feature = "nightly", feature(test))]
5
6#![cfg_attr(feature = "re", feature(try_trait_v2))]
7#![cfg_attr(feature = "re", feature(never_type))]
8
9extern crate libc;
10#[cfg(feature = "crossterm")] extern crate crossterm;
11
12#[cfg(feature = "memchr")] use memchr::{memchr, memrchr};
13use core::any::Any;
14use core::arch::asm;
15use core::cell::UnsafeCell;
16use core::str::from_utf8_unchecked;
17use core::sync::atomic::{AtomicI8, AtomicUsize, Ordering};
18use std::fmt::{self, Write};
19use std::fs;
20use std::hint::spin_loop;
21use std::io::{self, Read};
22use std::mem::MaybeUninit;
23use std::ops::{Deref, DerefMut};
24use std::os::raw::c_int;
25use std::path::Path;
26use std::process::{Command, Stdio};
27use std::sync::Mutex;
28use std::thread;
29use std::time::{Duration, SystemTime, UNIX_EPOCH};
30
31#[inline] pub fn b2s (b: &[u8]) -> &str {unsafe {std::str::from_utf8_unchecked (b)}}
32
33/// Shortcut to path->filename conversion.
34///
35/// Returns the unchanged `path` if there is a character encoding error or something.
36pub fn filename<'a> (path: &'a str) -> &'a str {
37  // NB: `Path::new (path) .file_name()` only works for file separators of the current operating system,
38  // whereas the error trace might be coming from another operating system.
39  // In particular, I see `file_name` failing with WASM.
40
41  let name = match path.rfind (|ch| ch == '/' || ch == '\\') {
42    Some (ofs) => &path[ofs+1..],
43    None => path};
44
45  if name.ends_with (".rs") {&name[0 .. name.len() - 3]} else {name}}
46
47// A trick to use the `try_s` macro from both the outside and inside of the crate.
48//mod gstuff {pub fn filename<'a> (path: &'a str) -> &'a str {::filename (path)}}
49
50/// Returns on error, converting the `Err` value to `String` and prepending the current location.
51///
52/// cf. http://www.reddit.com/r/rust/comments/29wwzw/error_handling_and_result_types/cipcm9a
53#[macro_export] macro_rules! try_s {
54  ($e: expr) => {match $e {
55    Ok (ok) => ok,
56    Err (err) => {return Err (format! ("{}:{}] {}", $crate::filename (file!()), line!(), err));}}}}
57
58/// Returns on error, prepending the current location to a stringified error, then passing the string to `From::from`.
59#[macro_export] macro_rules! try_f {
60   ($e: expr) => {match $e {
61     Ok (ok) => ok,
62     Err (err) => {return Err (From::from (format! ("{}:{}] {}", $crate::filename (file!()), line!(), err)));}}}}
63
64/// Like `try_s`, but takes a reference.
65#[macro_export] macro_rules! try_sp {
66  ($e: expr) => {match $e {&Ok (ref ok) => ok,
67    &Err (ref err) => {return Err (From::from (format! ("{}:{}] {:?}", $crate::filename (file!()), line!(), err)));}}}}
68
69/// Lifts an error into a boxed future. `Box<Future<Item=_, Error=_>>`.
70///
71/// For example:
72///
73///     Box::new (PGA.execute (select) .and_then (move |pr| -> Box<Future<Item=Vec<PgResult>, Error=PgFutureErr> + Send> {
74///       let day = try_fu! (pr[0].row (0) .col_str (0));
75///       let json = try_fu! (pr[0].row (0) .col_json (1, "stats"));
76///       let mut stats: S = try_fu! (json::from_value (json));
77///       ...
78///       Box::new (PGA.execute (update))
79///     }))
80#[macro_export] macro_rules! try_fu {
81  ($e: expr) => {match $e {
82    Ok (ok) => ok,
83    Err (err) => {return Box::new (futures01::future::err (From::from (err)))}}}}
84
85/// Lifts an error into a boxed future. `Box<Future<Item=_, Error=String>>`.
86///
87///     fn foo() -> Box<Future<Item=u32, Error=String>> {
88///       try_fus! (bar());
89///       let another_future = try_fus! (whatever());
90///       Box::new (another_future.then (move |something| Ok (123)))
91///     }
92#[macro_export] macro_rules! try_fus {
93  ($e: expr) => {match $e {
94    Ok (ok) => ok,
95    Err (err) => {return Box::new (futures01::future::err (ERRL! ("{}", err)))}}}}
96
97/// Prepends file name and line number to the given message.
98#[macro_export] macro_rules! ERRL {
99  ($format: expr, $($args: tt)+) => {format! (concat! ("{}:{}] ", $format), $crate::filename (file!()), line!(), $($args)+)};
100  ($format: expr) => {format! (concat! ("{}:{}] ", $format), $crate::filename (file!()), line!())}}
101
102/// Returns a `Err(String)`, prepending the current location (file name and line number) to the string.
103///
104/// Examples: `ERR! ("too bad")`; `ERR! ("{}", foo)`;
105#[macro_export] macro_rules! ERR {
106  ($format: expr, $($args: tt)+) => {Err ($crate::ERRL! ($format, $($args)+))};
107  ($format: expr) => {Err ($crate::ERRL! ($format))}}
108
109#[cfg(feature = "base62")]
110pub mod base62;
111
112#[cfg(feature = "base62j")]
113pub mod base62j;
114
115#[cfg(all(feature = "base91", feature = "nightly"))]
116pub mod base91;
117
118#[cfg(feature = "re")]
119pub mod re;
120
121#[cfg(feature = "lines")]
122pub mod lines;
123
124#[cfg(feature = "link")]
125pub mod link;
126
127#[cfg(all(feature = "re"))]
128pub mod aarc;
129
130// --- status line -------
131
132#[cfg(all(feature = "crossterm", not(target_arch = "wasm32")))]
133fn isatty (fd: c_int) -> c_int {unsafe {libc::isatty (fd)}}
134
135#[cfg(all(feature = "crossterm", target_arch = "wasm32"))]
136fn isatty (_fd: c_int) -> c_int {0}
137
138#[cfg(feature = "crossterm")]
139static mut STATUS_LINE: IniMutex<String> = IniMutex::none();
140
141#[cfg(feature = "crossterm")]
142pub struct IsTty {pub is_tty: AtomicI8}
143#[cfg(feature = "crossterm")]
144impl core::ops::Deref for IsTty {
145  type Target = bool;
146  fn deref (&self) -> &Self::Target {
147    let mut is_tty = self.is_tty.load (Ordering::Relaxed);
148    if is_tty == 0 {
149      // https://man7.org/linux/man-pages/man3/isatty.3.html
150      is_tty = if isatty (1) != 0 {1} else {-1};
151      self.is_tty.store (is_tty, Ordering::Relaxed)}
152    if is_tty == 1 {&true} else {&false}}}
153
154/// `1` if standard output is a terminal, `0` if unknown
155#[cfg(feature = "crossterm")]
156pub static ISATTY: IsTty = IsTty {is_tty: AtomicI8::new (0)};
157
158/// The time of the last status line update, in milliseconds.  
159/// Tracked in order to help the calling code with implementing debounce strategies
160/// (for sending each and every update to the terminal might be a bottleneck,
161/// plus it might flicker
162/// and might be changing too fast for a user to register the content).
163#[cfg(feature = "crossterm")]
164pub static STATUS_LINE_LM: AtomicUsize = AtomicUsize::new (0);
165
166/// Clear the rest of the line.
167#[cfg(feature = "crossterm")]
168fn delete_line (stdout: &mut io::Stdout) {
169  use crossterm::{terminal, QueueableCommand};
170
171  let _ = stdout.queue (terminal::Clear (terminal::ClearType::UntilNewLine));}
172
173  // NB: term's `delete_line` is really screwed.
174  // Sometimes it doesn't work. And when it does, it does it wrong.
175  // Documentation says it "Deletes the text from the cursor location to the end of the line"
176  // but when it works it clears the *entire* line instead.
177  // let _ = stdout.write (b"\x1B[K");}}  // EL0. Clear right.
178
179/// Clears the line to the right, prints the given text, moves the caret all the way to the left.
180///
181/// Will only work if the terminal `isatty`.
182///
183/// Repeating this call will keep updating the same line (effectively a status line).
184///
185/// And it's kind of compatible with the normal stdout logging because the latter will overwrite the status line.
186///
187/// One can also call `status_line_clear()` to clear the line before the normal output comes forth
188/// in order to avoid leaving stray characters on the screen.
189///
190/// Skips spewing the output if the hash of the generated status line is identical to the existing one.
191///
192/// The function is intended to be used with a macros, for example:
193///
194///     use gstuff::{status_line, ISATTY};
195///     macro_rules! status_line {($($args: tt)+) => {if *ISATTY {
196///       status_line (file!(), line!(), &fomat! ($($args)+))}}}
197#[cfg(all(feature = "crossterm"))]
198pub fn status_line (file: &str, line: u32, status: &str) {
199  use crossterm::{QueueableCommand, cursor};
200  use io::{stdout, Write};
201  use std::collections::hash_map::DefaultHasher;
202  use std::hash::Hasher;
203
204  #[allow(static_mut_refs)]
205    if let Ok (mut status_line) = unsafe {STATUS_LINE.spin_defaultᵗ (SPIN_OUT)} {
206      let mut stdout = stdout();
207      let old_hash = {let mut hasher = DefaultHasher::new(); hasher.write (status_line.as_bytes()); hasher.finish()};
208      status_line.clear();
209      use std::fmt::Write;
210      let _ = write! (&mut *status_line, "{}:{}] {}", filename (file), line, status);
211      let new_hash = {let mut hasher = DefaultHasher::new(); hasher.write (status_line.as_bytes()); hasher.finish()};
212      if old_hash != new_hash {
213        STATUS_LINE_LM.s (now_ms() as usize);
214
215        // Try to keep the status line withing the terminal bounds.
216        match crossterm::terminal::size() {
217          Ok ((w, _)) if status_line.chars().count() >= w as usize => {
218            let mut tmp = String::with_capacity (w as usize - 1);
219            for ch in status_line.chars().take (w as usize - 1) {tmp.push (ch)}
220            let _ = stdout.write (tmp.as_bytes());},
221          _ => {let _ = stdout.write (status_line.as_bytes());}};
222
223        delete_line (&mut stdout);
224        let _ = stdout.queue (cursor::MoveToColumn (0));
225        let _ = stdout.flush();}}}
226
227/// Clears the status line if stdout `isatty` and `status_line` isn't empty.
228#[cfg(feature = "crossterm")]
229pub fn status_line_clear() -> String {
230  use io::{stdout, Write};
231  let mut ret = String::new();
232  #[allow(static_mut_refs)]
233  if let Ok (mut status_line) = unsafe {STATUS_LINE.spin_defaultᵗ (SPIN_OUT)} {
234    if *ISATTY && !status_line.is_empty() {
235      let mut stdout = stdout();
236        STATUS_LINE_LM.s (now_ms() as usize);
237        core::mem::swap (&mut ret, &mut status_line);
238        delete_line (&mut stdout);
239        let _ = stdout.flush();}}
240  ret}
241
242/// Clear the status line, run the code, then restore the status line.
243///
244/// Simply runs the `code` if the stdout is not `isatty` or if the status line is empty.
245#[cfg(feature = "crossterm")]
246pub fn with_status_line (code: &dyn Fn()) {
247  use crossterm::{QueueableCommand, cursor};
248  use io::{stdout, Write};
249
250  #[allow(static_mut_refs)]
251  if let Ok (status_line) = unsafe {STATUS_LINE.spin_defaultᵗ (SPIN_OUT)} {
252    if !*ISATTY || status_line.is_empty() {
253      code()
254    } else {
255      let mut stdout = stdout();
256      delete_line (&mut stdout);
257      let _ = stdout.flush();  // We need to send this EL0 out because the $code might be writing to stderr and thus miss it.
258      code();
259      // TODO: Should probably use `term_size::dimensions` to limit the status line size, just like in `fn status_line`.
260      let _ = stdout.write (status_line.as_bytes());
261      let _ = stdout.queue (cursor::MoveToColumn (0));
262      let _ = stdout.flush();}}}
263
264#[cfg(feature = "crossterm")]
265#[test] fn test_status_line() {
266  with_status_line (&|| println! ("hello world"));}
267
268#[cfg(all(feature = "inlinable_string", feature = "fomat-macros"))]
269pub fn short_log_time (ms: u64) -> inlinable_string::InlinableString {
270  use fomat_macros::wite;
271  let iso = ms2iso8601 (ms as i64);
272  ifomat! ((&iso[8..10]) ' ' (&iso[11..19]))}
273
274/// Indent and count. Negative count hides `log! (1, …)` but not `log! (2, …)`.
275#[cfg(all(feature = "crossterm", feature = "inlinable_string", feature = "fomat-macros"))]
276pub static INDENT: IniMutex<(inlinable_string::InlinableString, i8)> = IniMutex::none();
277
278#[cfg(all(feature = "crossterm", feature = "inlinable_string", feature = "fomat-macros"))]
279#[macro_export] macro_rules! log {
280
281  ($on: literal, $($args: tt)+) => {  // a flip to (temporarily) disable
282    if $on == 2 {
283      log! ($($args)+)
284    } else if $on == 1 {  // Display 1s only if indent count is positive
285      let i1 = $crate::INDENT.spin_default().1;
286      if 0 <= i1 {log! ($($args)+)}}};
287
288  (t $time: expr => $delay: expr, $($args: tt)+) => {{  // $delay repeat by $time
289    static LL: core::sync::atomic::AtomicI64 = core::sync::atomic::AtomicI64::new (0);
290    let now = $time as i64;
291    let Δ = now - LL.load (core::sync::atomic::Ordering::Relaxed);
292    if $delay <= Δ {
293      LL.store (now, core::sync::atomic::Ordering::Relaxed);
294      log! ($($args)+)}}};
295
296  (q $command: expr, $($args: tt)+) => {{
297    $crate::with_status_line (&|| {
298      use crossterm::QueueableCommand;
299      use fomat_macros::{wite, fomat};
300      use std::io::Write;
301      let tty = *$crate::ISATTY;
302      let i0 = $crate::INDENT.spin_default().0.clone();
303      let mut stdout = std::io::stdout();
304      if tty {let _ = stdout.queue ($command);}
305      let _ = wite! (&mut stdout,
306        ($crate::short_log_time ($crate::now_ms())) ' '
307        (i0)
308        ($crate::filename (file!())) ':' (line!()) "] "
309        $($args)+ '\n');
310      if tty {let _ = stdout.queue (crossterm::style::ResetColor);}
311      let _ = stdout.flush();})}};
312
313  // https://docs.rs/crossterm/latest/crossterm/style/enum.Color.html
314  (c $color: expr, $($args: tt)+) => {
315    log! (q crossterm::style::SetForegroundColor ($color), $($args)+)};
316
317  // https://en.wikipedia.org/wiki/ANSI_escape_code#8-bit
318  // https://www.ditig.com/256-colors-cheat-sheet
319  (a $ansi: expr, $($args: tt)+) => {
320    log! (q crossterm::style::SetForegroundColor (
321      crossterm::style::Color::AnsiValue ($ansi)), $($args)+)};
322
323  ($($args: tt)+) => {{
324    $crate::with_status_line (&|| {
325      use fomat_macros::{pintln, fomat};
326      let i0 = $crate::INDENT.spin_default().0.clone();
327      pintln! (
328        ($crate::short_log_time ($crate::now_ms())) ' '
329        (i0)
330        ($crate::filename (file!())) ':' (line!()) "] "
331        $($args)+);})}};}
332
333#[cfg(all(feature = "crossterm", feature = "inlinable_string", feature = "fomat-macros"))]
334#[test] fn test_log() {
335  log! ([= 2 + 2])}
336
337/// A helper to build a string on the stack.
338///
339/// Given an array it makes a writeable cursor available to the passed code block.
340///
341/// Returns a &str slice pointing at memory between the array start and the cursor.
342///
343/// Example:
344///
345///     use core::mem::MaybeUninit;
346///     use std::io::Write;
347///     #[allow(invalid_value)] let mut foobar: [u8; 128] = unsafe {MaybeUninit::uninit().assume_init()};
348///     let foobar = gstring! (foobar, {
349///         write! (foobar, "foo") .expect ("!write");
350///         write! (foobar, "bar") .expect ("!write");
351///     });
352///
353/// Alternatives: https://crates.io/crates/stack.
354#[macro_export] macro_rules! gstring {($array: ident, $code: block) => {{
355  let end = {
356    let mut $array = ::std::io::Cursor::new (&mut $array[..]);
357    let $array = &mut $array;
358    $code;
359    $array.position() as usize};
360  let s = unsafe {::core::str::from_utf8_unchecked (&$array[0..end])};
361  s}}}
362
363/// Fomat into a small string.
364/// 
365/// Typical imports:
366/// 
367///     use inlinable_string::{InlinableString, StringExt};
368///     use std::fmt::{Write as FmtWrite};
369#[cfg(feature = "fomat-macros")]
370#[macro_export] macro_rules! ifomat {
371  ($($args: tt)+) => ({
372    use inlinable_string::{InlinableString, StringExt};
373    use std::fmt::Write as FmtWrite;
374    let mut is = InlinableString::new();
375    wite! (&mut is, $($args)+) .expect ("!wite");
376    is})}
377
378/// now_ms / 1000 / 86400 ↦ year, month, day UTC
379/// 
380/// https://stackoverflow.com/a/32158604/257568, http://howardhinnant.github.io/date_algorithms.html
381pub const fn civil_from_days (mut z: i32) -> (i32, u32, u32) {
382  z += 719468;
383  let era = if 0 <= z {z} else {z - 146096} / 146097;
384  let doe = (z - era * 146097) as u32;  // 0..=146096
385  let yoe = (doe - doe/1460 + doe/36524 - doe/146096) / 365;  // 0..=399
386  let y = yoe as i32 + era * 400;
387  let doy = doe - (365 * yoe + yoe / 4 - yoe / 100);  // 0..=365
388  let mp = (5 * doy + 2) / 153;  // 0..=11
389  let d = doy - (153 * mp + 2) / 5 + 1;  // 1..=31
390  let m = (mp as i32 + if (mp as i32) < 10 {3} else {-9}) as u32;  // 1..=12
391  (y + if m <= 2 {1} else {0}, m, d)}
392
393/// year, month 1..=12, day 1..=31 UTC ↦ UNIX milliseconds (aka now_ms) / 1000 / 86400
394pub const fn days_from_civil (mut y: i32, m: u32, d: u32) -> i32 {
395  y -= if m <= 2 {1} else {0};
396  let era = if 0 <= y {y} else {y - 399} / 400;
397  let yoe = (y - era * 400) as u32;      // 0..=399
398  let doy = (153 * (m as i32 + (if 2 < m {-3} else {9})) + 2) as u32 / 5 + d - 1;  // 0..=365
399  let doe = yoe * 365 + yoe / 4 - yoe / 100 + doy;  // 0..=146096
400  era * 146097 + doe as i32 - 719468}
401
402/// 0 = Sun, 1 = Mon .. 6 = Sat
403pub const fn ymd2dow (mut y: i32, m: i32, mut d: i32) -> i32 {
404  if m < 3 {y -= 1} else {d -= 2}
405  (23*m/9 + d + 4 + y/4 - y/100 + y/400 + y) % 7}
406
407/// into integer with centiseconds "%y%m%d%H%M%S%.2f"
408#[cfg(feature = "chrono")]
409pub fn ldt2ics (dt: &chrono::DateTime<chrono::Local>) -> i64 {
410  use chrono::{Datelike, Timelike};
411  let y = dt.year() as i64;
412  let m = dt.month() as i64;
413  let d = dt.day() as i64;
414  let ti = dt.time();
415  let h = ti.hour() as i64;
416  let min = ti.minute() as i64;
417  let s = ti.second() as i64;
418  let cs = ti.nanosecond() as i64 / 10000000;
419  y%1000 * 1e12 as i64 + m * 10000000000 + d * 100000000 + h * 1000000 + min * 10000 + s * 100 + cs}
420
421/// UNIX time into ISO 8601 "%Y-%m-%dT%H:%M:%S%.3fZ", UTC
422#[cfg(feature = "inlinable_string")]
423pub fn ms2iso8601 (ms: i64) -> inlinable_string::InlinableString {
424  use inlinable_string::{InlinableString, StringExt};
425  use std::fmt::Write as FmtWrite;
426  let day = (ms / 1000 / 86400) as i32;
427  let h = ((ms / 1000) % 86400) / 3600;
428  let min = ((ms / 1000) % 3600) / 60;
429  let s = (ms / 1000) % 60;
430  let ms = ms % 1000;
431  let (y, m, d) = civil_from_days (day);
432  let mut is = inlinable_string::InlinableString::new();
433  // NB: Here `write!` is faster than `wite!`
434  write! (&mut is, "{:04}-{:02}-{:02}T{:02}:{:02}:{:02}.{:03}Z", y, m, d, h, min, s, ms) .expect ("!write!");
435  is}
436
437/// UNIX time into integer with centiseconds "%y%m%d%H%M%S%.2f", UTC
438pub const fn ms2ics (ms: i64) -> i64 {
439  let day = (ms / 1000 / 86400) as i32;
440  let h = ((ms / 1000) % 86400) / 3600;
441  let min = ((ms / 1000) % 3600) / 60;
442  let s = (ms / 1000) % 60;
443  let cs = ms % 1000 / 10;
444  let (y, m, d) = civil_from_days (day);
445  let y = y as i64; let m = m as i64; let d = d as i64;
446  y%1000 * 1e12 as i64 + m * 10000000000 + d * 100000000 + h * 1000000 + min * 10000 + s * 100 + cs}
447
448/// integer with centiseconds "%y%m%d%H%M%S%.2f" into UNIX time in milliseconds
449pub const fn ics2ms (ics: i64) -> i64 {
450  let day = days_from_civil (
451    (ics / 1000000000000 + 2000) as i32,
452    (ics / 10000000000 % 100) as u32,
453    (ics / 100000000 % 100) as u32) as i64;
454  let tm_hour = (ics / 1000000 % 100) as i64;
455  let tm_min = (ics / 10000 % 100) as i64;
456  let tm_sec = (ics / 100 % 100) as i64;
457  let ms = (ics % 100 * 10) as i64;
458  ms + tm_sec * 1000 + tm_min * 60000 + tm_hour * 3600000 + day * 86400000}
459
460/// from integer with centiseconds "%y%m%d%H%M%S%.2f"
461#[cfg(all(feature = "chrono", feature = "re"))]
462pub fn ics2ldt (ims: i64) -> re::Re<chrono::DateTime<chrono::Local>> {
463  use chrono::{TimeZone, Timelike};
464  let dt = chrono::Local.with_ymd_and_hms (
465    (ims / 1000000000000 + 2000) as i32,
466    (ims / 10000000000 % 100) as u32,
467    (ims / 100000000 % 100) as u32,
468    (ims / 1000000 % 100) as u32,
469    (ims / 10000 % 100) as u32,
470    (ims / 100 % 100) as u32) .earliest()?;
471  let dt = dt.with_nanosecond ((ims % 100) as u32 * 10000000)?;
472  re::Re::Ok (dt)}
473
474/// from integer with centiseconds "%y%m%d%H%M%S%.2f"
475#[cfg(all(feature = "chrono", feature = "re"))]
476pub fn ics2ndt (ims: i64) -> re::Re<chrono::NaiveDateTime> {
477  use chrono::{TimeZone, Timelike};
478  let dt = chrono::NaiveDateTime::new (
479    chrono::NaiveDate::from_ymd_opt (
480      (ims / 1000000000000 + 2000) as i32,
481      (ims / 10000000000 % 100) as u32,
482      (ims / 100000000 % 100) as u32)?,
483    chrono::NaiveTime::from_hms_nano_opt (
484      (ims / 1000000 % 100) as u32,
485      (ims / 10000 % 100) as u32,
486      (ims / 100 % 100) as u32,
487      (ims % 100) as u32 * 10000000)?);
488  re::Re::Ok (dt)}
489
490/// Extend ISO 8601 shorthand into full RFC 3339 timestamp.  
491/// “2022-12-12T12” → “2022-12-12T12:00:00Z”
492#[cfg(feature = "fomat-macros")]
493#[macro_export] macro_rules! iso8601z {
494  ($date_or_time: expr) => {{
495    let sufff = ($date_or_time.len() as i32 - 10) .max (0) as usize;
496    ifomat! (($date_or_time) if sufff < 10 {(&"T00:00:00Z"[sufff..])})}}}
497
498/// ISO 8601 shorthand “2022-12-12T12” parsed as a Local
499#[cfg(feature = "fomat-macros")]
500#[macro_export] macro_rules! iso8601toL {($short: expr) => {
501  Local.from_local_datetime (&(DateTime::parse_from_rfc3339 (&iso8601z! ($short))?) .naive_utc()) .earliest()?}}
502
503/// ISO 8601 shorthand “2022-12-12T12” converted into integer with centiseconds "%y%m%d%H%M%S%.2f".  
504/// Expects `iso[0] == '2'`.
505pub fn iso8601ics (iso: &[u8]) -> i64 {
506  let mut ics: [u8; 15] = *b"000000000000000";
507  if 4 <= iso.len() {
508    ics[0] = iso[1]; ics[1] = iso[2]; ics[2] = iso[3];
509    if 7 <= iso.len() && iso[4] == b'-' {
510      ics[3] = iso[5]; ics[4] = iso[6];
511      if 10 <= iso.len() && iso[7] == b'-' {
512        ics[5] = iso[8]; ics[6] = iso[9];
513        if 13 <= iso.len() && (iso[10] == b'T' || iso[10] == b' ') {
514          ics[7] = iso[11]; ics[8] = iso[12];
515          if 16 <= iso.len() && iso[13] == b':' {
516            ics[9] = iso[14]; ics[10] = iso[15];
517            if 19 <= iso.len() && iso[16] == b':' {
518              ics[11] = iso[17]; ics[12] = iso[18];
519              if 22 <= iso.len() && iso[19] == b'.' {
520                ics[13] = iso[20]; ics[14] = iso[21]}}}}}}}
521  match b2s (&ics) .parse() {Ok (k) => k, Err (_err) => 0}}
522
523#[cfg(all(test, feature = "nightly", feature = "chrono", feature = "inlinable_string", feature = "re"))]
524mod time_bench {
525  extern crate test;
526  use chrono::{DateTime, Datelike, Local, NaiveDateTime, TimeZone, Timelike, Utc};
527  use crate::{civil_from_days, days_from_civil, ics2ldt, ics2ms, ics2ndt, iso8601ics, ldt2ics, ms2ics, ms2iso8601, now_ms, re::Re};
528  use fomat_macros::wite;
529  use inlinable_string::InlinableString;
530  use rand::{rngs::SmallRng, seq::index::sample, Rng, SeedableRng};
531  use test::black_box;
532
533  #[bench] fn duration (bm: &mut test::Bencher) {
534    assert! (946684800000 == days_from_civil (2000, 1, 1) as i64 * 86400 * 1000);
535    let duration = 118050;
536    assert! ("00:01:58.050" == &ms2iso8601 (946684800000 + duration) [11..23]);
537    assert! (      15805 == ms2ics (946684800000 + duration) - 10100000000);
538    let mut ms = 0;
539    bm.iter (|| {  // verify that `ms2ics` can be reused to examine a time delta
540      let ics = ms2ics (946684800000 + ms as i64) - 10100000000;
541      let tm_min = (ics / 10000 % 100) as i64;
542      let tm_sec = (ics / 100 % 100) as i64;
543      let ims = (ics % 100 * 10) as i64;
544      let msʹ = ims + tm_sec * 1000 + tm_min * 60000;
545      assert! (ms == msʹ);
546      ms += 10;
547      if 3600 * 1000 <= ms {ms = 0}})}
548
549  #[bench] fn iso8601icsᵇ (bm: &mut test::Bencher) {
550    assert! (00000000000000 == iso8601ics (b""));
551    assert! (24000000000000 == iso8601ics (b"2024"));
552    assert! (24120000000000 == iso8601ics (b"2024-12"));
553    assert! (24121300000000 == iso8601ics (b"2024-12-13"));
554    assert! (24121321000000 == iso8601ics (b"2024-12-13T21"));
555    assert! (24121321000000 == iso8601ics (b"2024-12-13T21+03"));
556    assert! (24121314150000 == iso8601ics (b"2024-12-13T14:15"));
557    assert! (24121314150000 == iso8601ics (b"2024-12-13 14:15"));
558    assert! (24121314151600 == iso8601ics (b"2024-12-13T14:15:16"));
559    assert! (24121314151600 == iso8601ics (b"2024-12-13 14:15:16"));
560    assert! (24121314151698 == iso8601ics (b"2024-12-13T14:15:16.98"));
561    assert! (24121314151698 == iso8601ics (b"2024-12-13T14:15:16.980"));
562    assert! (24121314151698 == iso8601ics (b"3024-12-13T14:15:16.980Z"));
563    assert! (24121314151698 == iso8601ics (b"2024-12-13T14:15:16.980-03"));
564    assert! (999121314151698 == iso8601ics (b"2999-12-13T14:15:16.980-03"));
565    bm.iter (|| {
566      let ics = iso8601ics (b"4321-12-23T13:14:15.987");
567      assert! (black_box (ics) == 321122313141598, "{}", ics)})}
568
569  #[bench] fn ms2iso8601ᵇ (bm: &mut test::Bencher) {
570    let mut rng = SmallRng::seed_from_u64 (now_ms());
571    bm.iter (|| {
572      let it = ms2iso8601 (rng.random::<i64>().abs());
573      assert! (black_box (it) .ends_with ('Z'))})}
574
575  #[bench] fn chrono_iso8601 (bm: &mut test::Bencher) {
576    let dt = Utc::now();
577    bm.iter (|| {
578      let it = ifomat! ((dt.format ("%Y-%m-%dT%H:%M:%S%.3fZ")));
579      assert! (it.ends_with ('Z'))})}
580
581  fn make_samples() -> Vec<(DateTime<Local>, InlinableString)> {
582    let mut rng = SmallRng::seed_from_u64 (now_ms());
583    let mut samples = Vec::with_capacity (65536);
584    while samples.len() < samples.capacity() {
585      let ms = rng.random::<i64>().abs() / 10 * 10;
586      if let Some (udt) = Utc.timestamp_millis_opt (ms) .earliest() {
587        let day = (ms / 1000 / 86400) as i32;
588        let (y, m, d) = civil_from_days (day);
589        assert! (udt.year() == y && udt.month() == m && udt.day() == d, "{:?}, y{}, m{}, d{}", udt, y, m, d);
590        assert! (days_from_civil (y, m, d) == day);
591        let cit = ifomat! ((udt.format ("%Y-%m-%dT%H:%M:%S%.3fZ")));
592        let cit = if cit.starts_with ('+') {&cit[1..]} else {&cit};
593        let dit = ms2iso8601 (ms);
594        assert! (cit == dit, "{} <> {}", cit, dit);
595        if let Some (udt) = udt.with_year (2000 + udt.year() % 1000) {
596          let ms = udt.timestamp_millis();
597          let ics = ms2ics (ms);
598          let udtʹ = Utc.from_utc_datetime (&ics2ndt (ics) .unwrap());
599          assert! (udt == udtʹ, "{:?} <> {:?}", udt, udtʹ);
600          let msʹ = ics2ms (ics);
601          assert! (ms == msʹ, "{} <> {}", ms, msʹ)}}
602      if let Some (ldt) = Local.timestamp_millis_opt (ms) .earliest() {
603        if let Some (ldt) = ldt.with_year (2000 + ldt.year() % 1000) {
604          let sdt = ifomat! ((ldt.format ("%Y-%m-%dT%H:%M:%S%.3f")));
605          samples.push ((ldt, sdt))}}}
606    samples}
607
608  #[bench] fn iso8601tol (bm: &mut test::Bencher) {
609    let (samples, mut sx) = (make_samples(), 0);
610    bm.iter (|| {
611      let ics = iso8601ics (samples[sx].1.as_bytes());
612      let dt = ics2ldt (ics) .unwrap();
613      let odt = &samples[sx].0;
614      assert! (
615        dt.year() == odt.year() && dt.month() == odt.month() && dt.day() == odt.day()
616         && dt.hour() == odt.hour() && dt.minute() == odt.minute() && dt.second() == odt.second()
617         && dt.nanosecond() == odt.nanosecond(),
618        "{} {} <> {} {}", ics, dt, samples[sx].1, odt);
619      sx += 1; if samples.len() <= sx {sx = 0}})}
620
621  #[bench] fn iso8601ton (bm: &mut test::Bencher) {
622    let (samples, mut sx) = (make_samples(), 0);
623    bm.iter (|| {
624      let ics = iso8601ics (samples[sx].1.as_bytes());
625      let dt = ics2ndt (ics) .unwrap();
626      let odt = &samples[sx].0;
627      assert! (
628        dt.year() == odt.year() && dt.month() == odt.month() && dt.day() == odt.day()
629         && dt.hour() == odt.hour() && dt.minute() == odt.minute() && dt.second() == odt.second()
630         && dt.nanosecond() == odt.nanosecond(),
631        "{} {} <> {} {}", ics, dt, samples[sx].1, odt);
632      sx += 1; if samples.len() <= sx {sx = 0}})}
633
634  #[bench] fn chrono_from_str (bm: &mut test::Bencher) {bm.iter (|| {
635    let dt = NaiveDateTime::parse_from_str ("4321-12-23T13:14:15", "%Y-%m-%dT%H:%M:%S") .unwrap();
636    assert! (dt.year() == 4321 && dt.month() == 12 && dt.day() == 23 
637      && dt.hour() == 13 && dt.minute() == 14 && dt.second() == 15)})}
638
639  #[bench] fn chrono_from_rfc3339 (bm: &mut test::Bencher) {bm.iter (|| {
640    let dt = DateTime::parse_from_rfc3339 ("4321-12-23T13:14:15Z") .unwrap();
641    assert! (dt.year() == 4321 && dt.month() == 12 && dt.day() == 23
642      && dt.hour() == 13 && dt.minute() == 14 && dt.second() == 15)})}
643
644  #[bench] fn iso8601tol_macro (bm: &mut test::Bencher) {bm.iter (|| {
645    fn f() -> Re<DateTime<Local>> {Re::Ok (iso8601toL! ("4321-12-23T13:14:15"))}
646    let dt = f().unwrap();
647    assert! (dt.year() == 4321 && dt.month() == 12 && dt.day() == 23
648      && dt.hour() == 13 && dt.minute() == 14 && dt.second() == 15)})}
649
650  #[bench] fn iso8601_ics_ms (bm: &mut test::Bencher) {bm.iter (|| {
651    let ics = iso8601ics (black_box (b"4321-12-23T13:14:15"));
652    assert! (ics == 321122313141500);
653    // new Date ('2321-12-23T13:14:15Z') .getTime() == 11107286055000
654    assert_eq! (ics2ms (black_box (ics)), 11107286055000)})}}
655
656/// Takes a netstring from the front of the slice.
657///
658/// Returns the unpacked netstring and the remainder of the slice.
659///
660/// NB: Netstring encoding is not included as a separate function
661/// because it is as simple as `wite! (&mut buf, (payload.len()) ':' (payload) ',')?;`.
662pub fn netstring (at: &[u8]) -> Result<(&[u8], &[u8]), String> {
663  let length_end = match at.iter().position (|&ch| ch < b'0' || ch > b'9') {Some (l) if l > 0 => l, _ => return ERR! ("No len.")};
664  match at.get (length_end) {Some (&ch) if ch == b':' => (), _ => return ERR! ("No colon.")};
665  let length = b2s (&at[0 .. length_end]);
666  let length: usize = try_s! (length.parse());
667  let bulk_pos = 0 + length_end + 1;
668  let bulk_end = bulk_pos + length;
669  match at.get (bulk_end) {Some (&ch) if ch == b',' => (), _ => return ERR! ("No comma.")}
670  Ok ((&at[bulk_pos .. bulk_end], &at[bulk_end + 1 ..]))}
671
672/// Wraps `gethostname` to fetch the current hostname into a temporary buffer.
673#[cfg(unix)]
674pub fn with_hostname (visitor: &mut dyn FnMut (&[u8])) -> Result<(), std::io::Error> {
675  use libc::{size_t, gethostname};  // http://man7.org/linux/man-pages/man2/gethostname.2.html
676  use std::ffi::CStr;
677
678  let mut buf = [0; 128];
679  let rc = unsafe {gethostname (buf.as_mut_ptr(), (buf.len() - 1) as size_t)};
680  if rc == 0 {
681    let cs = unsafe {CStr::from_ptr (buf.as_ptr())};
682    Ok (visitor (cs.to_bytes()))
683  } else {
684    Err (io::Error::last_os_error())}}
685
686#[cfg(unix)] #[test] fn test_hostname() {
687  let mut hostname = String::new();
688  with_hostname (&mut |bytes| hostname = String::from_utf8_lossy (bytes) .into_owned()) .unwrap();}
689
690/// Read contents of the file into a `Vec`.  
691/// Similar to `std::fs::read`.
692///
693/// Returns an empty `Vec` if the file is not present under the given path.
694pub fn slurp (path: &dyn AsRef<Path>) -> Vec<u8> {
695  let Ok (mut file) = fs::File::open (path) else {return Vec::new()};
696  let mut buf = Vec::new();
697  // Might issue a `stat` / `metadata` call to reserve space in the `buf`, aka `buffer_capacity_required`
698  if let Err (_err) = file.read_to_end (&mut buf) {return Vec::new()}
699  buf}
700
701/// Runs a command in a shell, returning stderr+stdout on success.
702///
703/// Sometimes we need something simpler than constructing a Command.
704///
705/// If the command failed then returns it's stderr
706pub fn slurp_prog (command: &str) -> Result<String, String> {
707  let output = match Command::new ("dash") .arg ("-c") .arg (command) .output() {
708    Ok (output) => output,
709    Err (ref err) if err.kind() == io::ErrorKind::NotFound => {  // "dash" was not found, try a different name.
710      try_s! (Command::new ("sh") .arg ("-c") .arg (command) .output())},
711    Err (err) => return ERR! ("{}", err)};
712
713  let combined_output: String = if output.stderr.is_empty() {
714    try_s! (String::from_utf8 (output.stdout))
715  } else if output.stdout.is_empty() {
716    try_s! (String::from_utf8 (output.stderr))
717  } else {
718    let mut buf = String::with_capacity (output.stderr.len() + output.stdout.len());
719    buf.push_str (try_s! (std::str::from_utf8 (&output.stderr[..])));
720    buf.push_str (try_s! (std::str::from_utf8 (&output.stdout[..])));
721    buf};
722
723  if output.status.success() {Ok (combined_output)} else {Err (combined_output)}}
724
725#[test] fn test_slurp_prog() {
726  if cfg! (windows) {println! ("Skipping as dash might be missing on Windows sometimes"); return}
727  let foo = match slurp_prog ("echo foo") {Ok (foo) => foo, Err (err) => panic! ("{}", err)};
728  assert_eq! (foo.trim(), "foo");}
729
730/// Run a command, printing it first. Stdout and stderr are forwarded through (`inherit`).
731pub fn cmd (cmd: &str) -> Result<(), String> {
732  println! ("$ {}", cmd);
733  let status = try_s! (Command::new ("dash") .arg ("-c") .arg (cmd) .stdout (Stdio::inherit()) .stderr (Stdio::inherit()) .status());
734  if !status.success() {Err (format! ("Command returned an error status: {}", status))} else {Ok(())}}
735
736/// Useful with panic handlers.
737/// 
738/// For example:
739/// 
740///     if let Err (err) = catch_unwind (AssertUnwindSafe (move || {
741///       let mut core = tokio_core::reactor::Core::new().expect ("!core");
742///       loop {core.turn (None)}
743///     })) {println! ("CORE panic! {:?}", any_to_str (&*err)); std::process::abort()}
744pub fn any_to_str<'a> (message: &'a dyn Any) -> Option<&'a str> {
745  if let Some (message) = message.downcast_ref::<&str>() {return Some (message)}
746  if let Some (message) = message.downcast_ref::<String>() {return Some (&message[..])}
747  return None}
748
749/// Converts the duration into a number of seconds with fractions.
750/// 
751/// (There's now a native `Duration::as_secs_f64`.)
752pub fn duration_to_float (duration: Duration) -> f64 {
753  duration.as_secs() as f64 + ((duration.subsec_nanos() as f64) / 1000000000.0)}
754
755/// Converts the time into a number of seconds with fractions.
756#[cfg(feature = "chrono")]
757pub fn dtl2float (dt: chrono::DateTime<chrono::Local>) -> f64 {
758  dt.timestamp() as f64 + ((dt.timestamp_subsec_nanos() as f64) / 1000000000.0)}
759
760/// Converts time in milliseconds into a number of seconds with fractions.
761pub fn ms2sec (ms: u64) -> f64 {
762  (ms / 1000) as f64 + ((ms % 1000) as f64 / 1000.0)}
763
764/// The current number of seconds since UNIX epoch, with fractions.
765///
766/// cf. http://stackoverflow.com/a/26878367/257568 (C++, Boost).
767pub fn now_float() -> f64 {
768  let now = SystemTime::now().duration_since (UNIX_EPOCH) .expect ("!duration_since");
769  duration_to_float (now)}
770
771#[test] fn test_now_float() {
772  let now = SystemTime::now().duration_since (UNIX_EPOCH) .expect ("!duration_since") .as_secs();
773  let t1 = now_float();
774  assert_eq! (now, t1 as u64);
775  thread::sleep (Duration::from_millis (100));
776  let t2 = now_float();
777  let delta = t2 - t1;
778  assert! (delta >= 0.098 && delta <= 0.150, "delta: {}", delta);}
779
780/// Converts the duration into a number of milliseconds.
781pub fn duration_to_ms (duration: Duration) -> u64 {
782  duration.as_secs() * 1000 + (duration.subsec_nanos() / 1000000) as u64}
783
784/// The current number of milliseconds since UNIX epoch.
785pub fn now_ms() -> u64 {
786  let now = SystemTime::now().duration_since (UNIX_EPOCH) .expect ("!duration_since");
787  duration_to_ms (now)}
788
789#[test] fn test_now_ms() {
790  let t1 = now_ms();
791  thread::sleep (Duration::from_millis (100));
792  let t2 = now_ms();
793  let delta = t2 - t1;
794  assert! (delta >= 98 && delta <= 150, "delta: {}", delta);}
795
796/// Last-modified of the file in seconds since the UNIX epoch, with fractions.  
797/// Returns 0 if the file does not exists.
798///
799/// A typical use case:
800///
801///     if (now_float() - try_s! (last_modified_sec (&path)) > 600.) {update (&path)}
802pub fn last_modified_sec (path: &dyn AsRef<Path>) -> Result<f64, String> {
803  let meta = match path.as_ref().metadata() {
804    Ok (m) => m,
805    Err (ref err) if err.kind() == std::io::ErrorKind::NotFound => return Ok (0.),
806    Err (err) => return ERR! ("{}", err)};
807  let lm = try_s! (meta.modified());
808  let lm = duration_to_float (try_s! (lm.duration_since (UNIX_EPOCH)));
809  Ok (lm)}
810
811// Consider targeting a CPU here.
812// https://github.com/rust-lang/rust/issues/44036
813
814/// On `x86_64` it is Time Stamp Counter (number of cycles).  
815/// Fall backs to `SystemTime` `as_nanos` otherwise.
816#[cfg(target_arch="x86_64")]
817pub fn rdtsc() -> u64 {unsafe {core::arch::x86_64::_rdtsc()}}
818
819#[cfg(not (target_arch="x86_64"))]
820pub fn rdtsc() -> u64 {SystemTime::now().duration_since (SystemTime::UNIX_EPOCH) .expect ("!now") .as_nanos() as u64}
821
822#[cfg(target_arch="x86_64")]
823#[test] fn test_rdtsc() {
824  assert! (rdtsc() != rdtsc())}
825
826/// Allows several threads or processes to compete for a shared resource by tracking resource ownership with a file.  
827/// If the lock file is older than `ttl_sec` then it is removed,
828/// allowing us to recover from a thread or process dying while holding the lock.
829pub struct FileLock<'a> {
830  /// Filesystem path of the lock file.
831  pub lock_path: &'a dyn AsRef<Path>,
832  /// The time in seconds after which an outdated lock file can be removed.
833  pub ttl_sec: f64,
834  /// The owned lock file. Removed upon unlock.
835  pub file: std::fs::File}
836impl<'a> FileLock<'a> {
837  /// Tries to obtain a file lock.
838  /// 
839  /// No blocking. Returns `None` if the file already exists and is recent enough (= locked).
840  ///
841  /// The returned guard will automatically remove the lock file when dropped.
842  /// 
843  ///     let Some (lock) = FileLock::lock (&lockᵖ, 123.)? else {log! ("Locked."); return Re::Ok(())};
844  ///     // ... Your code here ...
845  ///     drop (lock)
846  pub fn lock (lock_path: &'a dyn AsRef<Path>, ttl_sec: f64) -> Result<Option<FileLock<'a>>, String> {
847    let mut cycle = 0u8;
848    loop {
849      if cycle > 1 {break Ok (None)}  // A second chance.
850      cycle += 1;
851      let mut fo = std::fs::OpenOptions::new();
852      match fo.read (true) .write (true) .create_new (true) .open (lock_path.as_ref()) {
853        Ok (file) => break Ok (Some (FileLock {lock_path, ttl_sec, file})),
854        Err (ref ie) if ie.kind() == std::io::ErrorKind::AlreadyExists => {
855          // See if the existing lock is old enough to be discarded.
856          let lm = match last_modified_sec (lock_path) {
857            Ok (lm) => lm,
858            Err (ie) => break ERR! ("Error checking {:?}: {}", lock_path.as_ref(), ie)};
859          if lm == 0. {continue}  // Unlocked already?
860          if now_float() - lm > ttl_sec {
861            if let Err (err) = std::fs::remove_file (lock_path.as_ref()) {break ERR! ("Error removing {:?}: {}", lock_path.as_ref(), err)}
862            continue}
863          break Ok (None)},
864        Err (ie) => break ERR! ("Error creating {:?}: {}", lock_path.as_ref(), ie)}}}
865  /// Updates the modification time on the lock file.
866  /// Compile on Linux only as UTIME_NOW and futimens is absent on MacOS.
867  #[cfg(target_os = "linux")]
868  pub fn touch (&self) -> Result<(), String> {
869    //⌥ switch to https://doc.rust-lang.org/nightly/std/fs/struct.File.html#method.set_times
870    let ts = libc::timespec {tv_sec: 0, tv_nsec: libc::UTIME_NOW};
871    let times = [ts, ts];
872    use std::os::unix::io::AsRawFd;
873    let rc = unsafe {libc::futimens (self.file.as_raw_fd(), &times[0])};
874    if rc != 0 {
875      let err = std::io::Error::last_os_error();
876      return ERR! ("Can't touch {:?}: {}", self.lock_path.as_ref(), err)}
877    Ok(())}}
878impl<'a> Drop for FileLock<'a> {
879  fn drop (&mut self) {
880    let _ = std::fs::remove_file (self.lock_path);}}
881impl<'a> fmt::Debug for FileLock<'a> {
882  fn fmt (&self, ft: &mut fmt::Formatter) -> fmt::Result {
883    write! (ft, "FileLock ({:?}, {})", self.lock_path.as_ref(), self.ttl_sec)}}
884
885/// Process entry in /proc.
886#[derive(Debug)]
887pub struct ProcEn {
888  pub name: String,
889  pub path: std::path::PathBuf,
890  /// NB: cmdline is NUL-separated (meaning there will be an empty string at the end)
891  pub cmdline: Vec<String>}
892impl ProcEn {
893  pub fn pid (&self) -> Option<u32> {
894    if let Some (file_name) = self.path.file_name() {
895      if let Some (file_name) = file_name.to_str() {
896        if let Ok (pid) = file_name.parse() {
897          return Some (pid)}}}
898    None}}
899/// Iterate over processes in /proc.
900///
901///     if ProcIt::new().any (|proc_en| proc_en.cmdline.iter().any (|line_en| line_en.contains ("overfiend"))) {
902///       println! ("Overfiend the daemon is live!");
903///     }
904///
905///     let overfiends: Vec<ProcEn> = ProcIt::new().filter_map (|proc_en|
906///       if proc_en.cmdline.iter().any (|line_en| line_en.contains ("overfiend")) {Some (proc_en)}
907///       else {None}
908///     ) .collect();
909pub struct ProcIt {read_dir: std::fs::ReadDir}
910impl ProcIt {
911  pub fn new() -> ProcIt {
912    ProcIt {
913      read_dir: match Path::new ("/proc") .read_dir() {Ok (it) => it, Err (err) => panic! ("!proc: {}", err)}}}}
914impl Iterator for ProcIt {
915  type Item = ProcEn;
916  fn next (&mut self) -> Option<ProcEn> {
917    match self.read_dir.next() {
918      None => return None,
919      Some (Err (err)) => panic! ("ProcIt] !read_dir: {}", err),
920      Some (Ok (proc_en)) => {
921        let file_type = match proc_en.file_type() {
922          Ok (ft) => ft,
923          Err (err) => {
924            if matches! (err.kind(), io::ErrorKind::NotFound) {
925              return self.next()
926            } else {
927              panic! ("!file_type ({:?}): {}", proc_en.path(), err)}}};
928        if !file_type.is_dir() {return self.next()}
929        let name = proc_en.file_name();
930        let name = match name.to_str() {Some (name) => name, None => panic! ("ProcIt] !to_str")};
931        if !name.as_bytes().iter().all (|&b| b >= b'0' && b <= b'9') {  // Looks like PID?
932          return self.next()}
933        let path = proc_en.path();
934        let cmdline = String::from_utf8 (slurp (&path.join ("cmdline"))) .expect ("!from_utf8");  // NB: cmdline is NUL-separated.
935        if cmdline.is_empty() {return self.next()}
936        Some (ProcEn {name: name.into(), path: path, cmdline: cmdline.split ('\0') .map (String::from) .collect()})}}}}
937
938pub static mut SPIN_OUT: u32 = 1234567;
939
940fn spin_yield() {
941  spin_loop();
942  thread::yield_now();  // cf. https://stackoverflow.com/a/69847156/257568
943  spin_loop()}
944
945pub struct IniMutex<T> {au: AtomicI8, vc: UnsafeCell<MaybeUninit<T>>}
946#[must_use = "if unused the Mutex will immediately unlock"]
947pub struct IniMutexGuard<'a, T> {lo: &'a IniMutex<T>}
948
949unsafe impl<T: Send> Send for IniMutex<T> {}
950unsafe impl<T: Send> Sync for IniMutex<T> {}
951
952/// reset IniMutex to uninitialized on `drop`, handling initialization panics
953struct ResetTo0<'a, T> (&'a IniMutex<T>, bool);
954impl<'a, T> Drop for ResetTo0<'a, T> {
955  fn drop (&mut self) {
956    if self.1 {self.0.au.store (0, Ordering::Relaxed)}}}
957
958impl<T> IniMutex<T> {
959  pub const fn none() -> IniMutex<T> {
960    IniMutex {
961      au: AtomicI8::new (0),
962      vc: UnsafeCell::new (MaybeUninit::uninit())}}
963
964  pub const fn new (init: T) -> IniMutex<T> {
965    IniMutex {
966      au: AtomicI8::new (1),
967      vc: UnsafeCell::new (MaybeUninit::new (init))}}
968
969  /// `true` if the value is neither locked nor initialized
970  pub fn is_none (&self) -> bool {
971    0 == self.au.load (Ordering::Relaxed)}
972
973  /// `true` if the value is initialized
974  pub fn is_some (&self) -> bool {
975    0 != self.au.load (Ordering::Relaxed)}
976
977  /// Attempts to get a lock, returning immediately if locked or uninitialized
978  pub fn lock (&self) -> Result<IniMutexGuard<'_, T>, i8> {
979    match self.au.compare_exchange (1, 2, Ordering::Acquire, Ordering::Relaxed) {
980      Ok (1) => Ok (IniMutexGuard {lo: self}),
981      Ok (lock) => Err (lock),
982      Err (au) => Err (au)}}
983
984  pub fn spin (&self) -> IniMutexGuard<'_, T> {
985    loop {
986      if let Ok (lock) = self.lock() {return lock}
987      spin_yield()}}
988
989  /// “spin-out” if out of `spins`
990  pub fn spinᵗ (&self, spins: u32) -> Result<IniMutexGuard<'_, T>, &'static str> {
991    for spin in 0..spins {
992      if let Ok (lock) = self.lock() {return Ok (lock)}
993      if spin % 10 == 0 {spin_loop()} else {thread::yield_now()}}
994    Err ("spin-out")}
995
996  #[cfg(feature = "re")]
997  pub fn lock_init (&self, init: &mut dyn FnMut() -> re::Re<T>) -> Result<IniMutexGuard<'_, T>, LockInitErr> {
998    match self.au.compare_exchange (1, 2, Ordering::Acquire, Ordering::Relaxed) {
999      Ok (1) => Ok (IniMutexGuard {lo: self}),
1000      Err (0) => match self.au.compare_exchange (0, 2, Ordering::Acquire, Ordering::Relaxed) {
1001        Ok (0) => {
1002          let vc = unsafe {&mut *self.vc.get()};
1003          let mut reset = ResetTo0 (self, true);
1004          match init() {
1005            re::Re::Ok (vi) => {
1006              *vc = MaybeUninit::new (vi);
1007              reset.1 = false;
1008              Ok (IniMutexGuard {lo: self})},
1009            re::Re::Err (err) => {
1010              Err (LockInitErr::Init (err))}}},
1011        Ok (au) => Err (LockInitErr::Lock (au)),
1012        Err (au) => Err (LockInitErr::Lock (au))},
1013      Ok (au) => Err (LockInitErr::Lock (au)),
1014      Err (au) => Err (LockInitErr::Lock (au))}}
1015
1016  #[cfg(feature = "re")]
1017  pub fn spin_init (&self, init: &mut dyn FnMut() -> re::Re<T>) -> Result<IniMutexGuard<'_, T>, String> {
1018    loop {
1019      match self.lock_init (init) {
1020        Ok (lock) => break Ok (lock),
1021        Err (LockInitErr::Lock (_l)) => spin_yield(),
1022        Err (LockInitErr::Init (err)) => break Err (err)}}}
1023
1024  /// “spin-out” if out of `spins`
1025  #[cfg(feature = "re")]
1026  pub fn spin_initᵗ (&self, spins: u32, init: &mut dyn FnMut() -> re::Re<T>) -> Result<IniMutexGuard<'_, T>, String> {
1027    for spin in 0..spins {
1028      match self.lock_init (init) {
1029        Ok (lock) => return Ok (lock),
1030        Err (LockInitErr::Lock (_l)) => {
1031          if spin % 10 == 0 {spin_loop()} else {thread::yield_now()}},
1032        Err (LockInitErr::Init (err)) => return Err (err)}}
1033    Err ("spin-out".to_string())}
1034
1035  /// `drop` the instance and reset the mutex to uninitialized
1036  pub fn evict (lock: IniMutexGuard<'_, T>) {unsafe {
1037    let vc = &mut *lock.lo.vc.get();
1038    let val = core::ptr::read (vc) .assume_init();
1039    lock.lo.au.store (0, Ordering::Release);
1040    // We used to clean released memory with zeroes via `zeroed`,
1041    // but compiler would now erroneously emits `ud2` upon seeing this.
1042    core::mem::forget (lock);
1043    drop (val)}}}
1044
1045impl<T> Default for IniMutex<T> {
1046  /// Defaults to `none` (no value in mutex)
1047  fn default() -> Self {
1048    IniMutex::none()}}
1049
1050#[derive (Debug)]
1051pub enum LockInitErr {Lock (i8), Init (String)}
1052
1053impl fmt::Display for LockInitErr {
1054  fn fmt (&self, fm: &mut fmt::Formatter) -> fmt::Result {
1055    match *self {
1056      LockInitErr::Lock (au) => fm.write_fmt (format_args! ("{}", au)),
1057      LockInitErr::Init (ref err) => fm.write_str (err)}}}
1058
1059impl<T: Default> IniMutex<T> {
1060  pub fn lock_default (&self) -> Result<IniMutexGuard<'_, T>, i8> {
1061    match self.au.compare_exchange (1, 2, Ordering::Acquire, Ordering::Relaxed) {
1062      Ok (1) => Ok (IniMutexGuard {lo: self}),
1063      Err (0) => match self.au.compare_exchange (0, 2, Ordering::Acquire, Ordering::Relaxed) {
1064        Ok (0) => {
1065          let vc = unsafe {&mut *self.vc.get()}; 
1066          let mut reset = ResetTo0 (self, true);
1067          *vc = MaybeUninit::new (T::default());
1068          reset.1 = false;
1069          Ok (IniMutexGuard {lo: self})},
1070        Ok (au) => Err (au),
1071        Err (au) => Err (au)},
1072      Ok (au) => Err (au),
1073      Err (au) => Err (au)}}
1074
1075  pub fn spin_default (&self) -> IniMutexGuard<'_, T> {
1076    loop {
1077      if let Ok (lock) = self.lock_default() {return lock}
1078      spin_yield()}}
1079
1080  pub fn spin_defaultᵗ (&self, mut spins: u32) -> Result<IniMutexGuard<'_, T>, i8> {
1081    loop {
1082      match self.lock_default() {
1083        Ok (lock) => return Ok (lock),
1084        Err (err) => {
1085          if spins == 0 {return Err (err)}
1086          spins -= 1;
1087          if spins % 10 == 0 {spin_loop()} else {thread::yield_now()}}}}}}
1088
1089impl<T> Deref for IniMutexGuard<'_, T> {
1090  type Target = T;
1091  fn deref (&self) -> &T {
1092    let vc = unsafe {&mut *self.lo.vc.get()};
1093    unsafe {&*vc.as_ptr()}}}
1094
1095impl<T> DerefMut for IniMutexGuard<'_, T> {
1096  fn deref_mut (&mut self) -> &mut T {
1097    let vc = unsafe {&mut *self.lo.vc.get()};
1098    unsafe {&mut *vc.as_mut_ptr()}}}
1099
1100impl<T: fmt::Debug> fmt::Debug for IniMutexGuard<'_, T> {
1101  fn fmt (&self, fm: &mut fmt::Formatter) -> fmt::Result {
1102    let vc = unsafe {&mut *self.lo.vc.get()};
1103    unsafe {fmt::Debug::fmt (&*vc.as_ptr(), fm)}}}
1104
1105impl<T: fmt::Display> fmt::Display for IniMutexGuard<'_, T> {
1106  fn fmt (&self, fm: &mut fmt::Formatter) -> fmt::Result {
1107    let vc = unsafe {&mut *self.lo.vc.get()};
1108    unsafe {(*vc.as_ptr()) .fmt (fm)}}}
1109
1110impl<T> Drop for IniMutexGuard<'_, T> {
1111  fn drop (&mut self) {
1112    self.lo.au.store (1, Ordering::Release)}}
1113
1114impl<T> Drop for IniMutex<T> {
1115  fn drop (&mut self) {
1116    if self.au.load (Ordering::Acquire) != 0 {
1117      self.au.store (0, Ordering::Relaxed);
1118      let vc = unsafe {&mut *self.vc.get()};
1119      unsafe {vc.assume_init_drop()}}}}
1120
1121/// Cached hostname
1122#[cfg(feature = "inlinable_string")]
1123pub static HOST: IniMutex<inlinable_string::InlinableString> = IniMutex::none();
1124
1125pub struct TSafe<T> (pub T);
1126unsafe impl<T> Send for TSafe<T> {}
1127unsafe impl<T> Sync for TSafe<T> {}
1128impl<T: Default> Default for TSafe<T> {fn default() -> Self {TSafe(T::default())}}
1129impl<T: Clone> Clone for TSafe<T> {fn clone (&self) -> Self {TSafe (self.0.clone())}}
1130impl<T: fmt::Debug> fmt::Debug for TSafe<T> {fn fmt (&self, ft: &mut fmt::Formatter<'_>) -> fmt::Result {self.0.fmt (ft)}}
1131impl<T: fmt::Display> fmt::Display for TSafe<T> {fn fmt (&self, ft: &mut fmt::Formatter<'_>) -> fmt::Result {self.0.fmt (ft)}}
1132
1133/// Helps logging binary data (particularly with text-readable parts, such as bencode, netstring)
1134/// by replacing all the non-printable bytes with the `blank` character.
1135pub fn binprint (bin: &[u8], blank: u8) -> String {
1136  let mut bin: Vec<u8> = bin.into();
1137  for ch in bin.iter_mut() {if *ch < 0x20 || *ch >= 0x7F {*ch = blank}}
1138  unsafe {String::from_utf8_unchecked (bin)}}
1139
1140/// Row-major bits, 2x3, to [Bedstead](https://i.imgur.com/f3myFgM.png)
1141/// 
1142/// cf. https://youtu.be/5yoWxctJsYo graphics with teletext; Windows font rendering glitch
1143pub fn bits2bedstead (ch: u32) -> char {
1144  let ch =
1145    if 0b111111 < ch {0xEE00}
1146    else if 32 <= ch {0xEE40 + ch - 32}
1147    else if 0 < ch {0xEE00 + ch}
1148    else {0xEE00 + ch};
1149  unsafe {char::from_u32_unchecked (ch)}}
1150
1151/// [Bedstead](https://i.imgur.com/f3myFgM.png) to row-major bits, 2x3
1152pub fn bedstead2bits (ch: char) -> u32 {
1153  let ch = ch as u32;
1154  if 0xEE5F < ch {0}  // Past G1
1155  else if 0xEE40 <= ch {ch - 0xEE40 + 32}
1156  else if 0xEE00 <= ch {ch - 0xEE00}
1157  else {0}}  // Below G1
1158
1159#[test]
1160fn test_bedstead() {
1161  for bits in 0 ..= 0b111111 {
1162    let ch = bits2bedstead (bits);
1163    assert_eq! (bits, bedstead2bits (ch))}}
1164
1165pub fn round_to (decimals: u32, num: f32) -> f32 {
1166  let r = 10u32 .pow (decimals) as f32;
1167  (num * r) .round() / r}
1168
1169pub fn round8 (decimals: u32, num: f64) -> f64 {
1170  let r = 10u32 .pow (decimals) as f64;
1171  (num * r) .round() / r}
1172
1173/// Allows to sort by float, but panics if there's a NaN or infinity
1174#[derive(Clone, Copy, Debug, PartialOrd, PartialEq)]
1175pub struct OrdFloat (pub f64);
1176impl Eq for OrdFloat {}
1177impl Ord for OrdFloat {
1178  fn cmp (&self, other: &Self) -> std::cmp::Ordering {
1179    // cf. https://doc.rust-lang.org/nightly/std/primitive.f64.html#method.total_cmp
1180    self.0.partial_cmp (&other.0) .expect ("!partial_cmp")}}
1181use std::hash::{Hash, Hasher};
1182impl Hash for OrdFloat {
1183  fn hash<H: Hasher> (&self, state: &mut H) {
1184    self.0.to_bits().hash (state)}}
1185impl fmt::Display for OrdFloat {
1186  fn fmt (&self, fm: &mut fmt::Formatter) -> fmt::Result {
1187    self.0.fmt (fm)}}
1188
1189/// Allows to sort by float, but panics if there's a NaN or infinity
1190#[derive(Clone, Copy, Debug, PartialOrd, PartialEq)]
1191pub struct OrdF32 (pub f32);
1192impl Eq for OrdF32 {}
1193impl Ord for OrdF32 {
1194  fn cmp (&self, other: &Self) -> std::cmp::Ordering {
1195    // cf. https://doc.rust-lang.org/nightly/std/primitive.f64.html#method.total_cmp
1196    self.0.partial_cmp (&other.0) .expect ("!partial_cmp")}}
1197impl Hash for OrdF32 {
1198  fn hash<H: Hasher> (&self, state: &mut H) {
1199    self.0.to_bits().hash (state)}}
1200impl fmt::Display for OrdF32 {
1201  fn fmt (&self, fm: &mut fmt::Formatter) -> fmt::Result {
1202    self.0.fmt (fm)}}
1203
1204/*⌥ consider implementing SQLite VARINTs
1205https://sqlite.org/src4/doc/trunk/www/varint.wiki
1206https://softwareengineering.stackexchange.com/questions/455589/sqlite-design-use-of-variable-length-integers-explain-the-design-flaw
1207#[cfg(feature = "re")]
1208pub fn length_coded (by: &[u8]) -> re::Re<(u64, usize)> {  // MySQLClientServerProtocol, #Elements
1209  // cf. https://github.com/hunter-packages/mysql-client/blob/3d95211/sql-common/pack.c#L93, net_store_length
1210  // cf. https://github.com/blackbeam/rust_mysql_common/blob/88ce581/src/io.rs#L347, read_lenenc_int
1211  use fomat_macros::fomat;
1212  if by.is_empty() {fail! ("!length_coded_binary: is_empty")}
1213  if by[0] <= 250 {return re::Re::Ok ((by[0] as u64, 1))}
1214  if by[0] == 251 {return re::Re::Ok ((0, 1))}  // NULL
1215  if by[0] == 252 {  // “value of following 16-bit word”
1216    if by.len() < 3 {fail! ("!length_coded_binary: incomplete 16-bit")}
1217    return re::Re::Ok ((u16::from_le_bytes ([by[1], by[2]]) as u64, 3))}
1218  if by[0] == 253 {  // “value of following 24-bit word”
1219    if by.len() < 4 {fail! ("!length_coded_binary: incomplete 24-bit")}
1220    return re::Re::Ok ((u32::from_le_bytes ([by[1], by[2], by[3], 0]) as u64, 4))}
1221  if by[0] == 254 { // “value of following 64-bit word”
1222    if by.len() < 9 {fail! ("!length_coded_binary: incomplete 64-bit")}
1223    return re::Re::Ok ((u64::from_le_bytes ([by[1], by[2], by[3], by[4], by[5], by[6], by[7], by[8]]), 9))}
1224  fail! ("!length_coded_binary: " [by[0]])}
1225*/
1226
1227pub trait AtBool {
1228  /// load with `Ordering::Relaxed`
1229  fn l (&self) -> bool;
1230  /// store with `Ordering::Relaxed`
1231  fn s (&self, val: bool);}
1232impl AtBool for core::sync::atomic::AtomicBool {
1233  fn l (&self) -> bool {
1234    self.load (Ordering::Relaxed)}
1235  fn s (&self, val: bool) {
1236    self.store (val, Ordering::Relaxed)}}
1237
1238pub trait AtI8 {
1239  /// swap with `Ordering::Relaxed`
1240  fn cas (&self, current: i8, new: i8) -> Result<i8, i8>;
1241  /// load with `Ordering::Relaxed`
1242  fn l (&self) -> i8;
1243  /// store with `Ordering::Relaxed`
1244  fn s (&self, val: i8);}
1245impl AtI8 for core::sync::atomic::AtomicI8 {
1246  fn cas (&self, current: i8, new: i8) -> Result<i8, i8> {
1247    self.compare_exchange (current, new, Ordering::Relaxed, Ordering::Relaxed)}
1248  fn l (&self) -> i8 {
1249    self.load (Ordering::Relaxed)}
1250  fn s (&self, val: i8) {
1251    self.store (val, Ordering::Relaxed)}}
1252
1253pub trait AtI32 {
1254  /// swap with `Ordering::Relaxed`
1255  fn cas (&self, current: i32, new: i32) -> Result<i32, i32>;
1256  /// load with `Ordering::Relaxed`
1257  fn l (&self) -> i32;
1258  /// store with `Ordering::Relaxed`
1259  fn s (&self, val: i32);}
1260impl AtI32 for core::sync::atomic::AtomicI32 {
1261  fn cas (&self, current: i32, new: i32) -> Result<i32, i32> {
1262    self.compare_exchange (current, new, Ordering::Relaxed, Ordering::Relaxed)}
1263  fn l (&self) -> i32 {
1264    self.load (Ordering::Relaxed)}
1265  fn s (&self, val: i32) {
1266    self.store (val, Ordering::Relaxed)}}
1267
1268pub trait AtI64 {
1269  /// swap with `Ordering::Relaxed`
1270  fn cas (&self, current: i64, new: i64) -> Result<i64, i64>;
1271  /// load with `Ordering::Relaxed`
1272  fn l (&self) -> i64;
1273  /// store with `Ordering::Relaxed`
1274  fn s (&self, val: i64);}
1275impl AtI64 for core::sync::atomic::AtomicI64 {
1276  fn cas (&self, current: i64, new: i64) -> Result<i64, i64> {
1277    self.compare_exchange (current, new, Ordering::Relaxed, Ordering::Relaxed)}
1278  fn l (&self) -> i64 {
1279    self.load (Ordering::Relaxed)}
1280  fn s (&self, val: i64) {
1281    self.store (val, Ordering::Relaxed)}}
1282
1283pub trait AtU64 {
1284  /// swap with `Ordering::Relaxed`
1285  fn cas (&self, current: u64, new: u64) -> Result<u64, u64>;
1286  /// load with `Ordering::Relaxed`
1287  fn l (&self) -> u64;
1288  /// store with `Ordering::Relaxed`
1289  fn s (&self, val: u64);}
1290impl AtU64 for core::sync::atomic::AtomicU64 {
1291  fn cas (&self, current: u64, new: u64) -> Result<u64, u64> {
1292    self.compare_exchange (current, new, Ordering::Relaxed, Ordering::Relaxed)}
1293  fn l (&self) -> u64 {
1294    self.load (Ordering::Relaxed)}
1295  fn s (&self, val: u64) {
1296    self.store (val, Ordering::Relaxed)}}
1297
1298pub trait AtUsize {
1299  /// swap with `Ordering::Relaxed`
1300  fn cas (&self, current: usize, new: usize) -> Result<usize, usize>;
1301  /// load with `Ordering::Relaxed`
1302  fn l (&self) -> usize;
1303  /// store with `Ordering::Relaxed`
1304  fn s (&self, val: usize);}
1305impl AtUsize for AtomicUsize {
1306  fn cas (&self, current: usize, new: usize) -> Result<usize, usize> {
1307    self.compare_exchange (current, new, Ordering::Relaxed, Ordering::Relaxed)}
1308  fn l (&self) -> usize {
1309    self.load (Ordering::Relaxed)}
1310  fn s (&self, val: usize) {
1311    self.store (val, Ordering::Relaxed)}}
1312
1313/// Grok long lines with `memchr`. Consider using
1314/// [slice::Split](https://doc.rust-lang.org/nightly/std/slice/struct.Split.html)
1315/// when the lines are short.
1316/// 
1317/// Skips blanks.
1318#[cfg(feature = "memchr")]
1319pub struct LinesIt<'a> {
1320  pub lines: &'a [u8],
1321  pub head: usize,
1322  pub tail: usize}
1323
1324#[cfg(feature = "memchr")]
1325impl<'a> LinesIt<'a> {
1326  pub fn new (lines: &'a [u8]) -> LinesIt<'a> {
1327    let (mut head, mut tail) = (0, lines.len());
1328
1329    loop {
1330      if tail <= head {break}
1331      if lines[head] == b'\n' {head += 1; continue}
1332      break}
1333
1334    loop {
1335      if tail <= head {break}
1336      if lines[tail-1] == b'\n' {tail -= 1; continue}
1337      break}
1338
1339    LinesIt {lines, head, tail}}
1340
1341  /// seek to a line at the given byte `pos`ition
1342  pub fn heads_up (lines: &'a [u8], pos: usize) -> LinesIt<'a> {
1343    let len = lines.len();
1344    if len < pos {
1345      LinesIt {lines, head: len, tail: len}
1346    } else {
1347      LinesIt {lines,
1348        head: memrchr (b'\n', &lines[..pos]) .unwrap_or_default(),
1349        tail: len}}}}
1350
1351#[cfg(feature = "memchr")]
1352impl<'a> Iterator for LinesIt<'a> {
1353  type Item = &'a [u8];
1354  fn next (&mut self) -> Option<Self::Item> {
1355    loop {
1356      if self.tail <= self.head {return None}
1357      if self.lines[self.head] == b'\n' {self.head += 1; continue}
1358      break}
1359    if let Some (mut lf) = memchr (b'\n', &self.lines[self.head .. self.tail]) {
1360      lf += self.head;
1361      let line = &self.lines[self.head .. lf];
1362      self.head = lf + 1;
1363      Some (line)
1364    } else {
1365      let line = &self.lines[self.head .. self.tail];
1366      self.head = self.tail;
1367      Some (line)}}}
1368
1369#[cfg(feature = "memchr")]
1370impl<'a> DoubleEndedIterator for LinesIt<'a> {
1371  fn next_back (&mut self) -> Option<Self::Item> {
1372    loop {
1373      if self.tail <= self.head {return None}
1374      if self.lines[self.tail-1] == b'\n' {self.tail -= 1; continue}
1375      break}
1376    if let Some (mut lf) = memrchr (b'\n', &self.lines[self.head .. self.tail]) {
1377      lf += self.head;
1378      let line = &self.lines[lf + 1 .. self.tail];
1379      self.tail = lf;
1380      Some (line)
1381    } else {
1382      let line = &self.lines[self.head .. self.tail];
1383      self.tail = self.head;
1384      Some (line)}}}
1385
1386// cf. https://github.com/TimNN/shuffled-iter
1387pub mod shuffled_iter {
1388  //! Iterates over `0..len` or a slice in pseudo-random order, with zero allocation.
1389  //! Uses a Feistel-like bijective permutation combined with cycle-walking to visit every index exactly once.
1390  //! ### `ShuffledIter` Methods
1391  //! * `fn with_seed (len: usize, seed: u64) -> ShuffledIter`
1392  //!   Creates new iterator over `0..len` using given seed.
1393  //! ### Functions
1394  //! * `fn shuffled<T> (seed: u64, v: &[T]) -> impl Iterator<Item = (usize, &T)>`
1395  //!   Iterates over slice in random order, yielding `(index, &T)`.
1396
1397  use crate::now_ms;
1398
1399  /// Iterates over `0..len` in pseudo-random order, no allocation.
1400  pub struct ShuffledIter {
1401    len: usize,
1402    mask: u64,  // next power-of-two minus 1, >= len-1
1403    counter: u64,
1404    emitted: u64,
1405    // Feistel-like bijective permutation parameters (3 rounds).
1406    // Each multiply-by-odd is a bijection mod 2^n; XOR is a bijection; composition is bijection.
1407    f: [u64; 3],  // odd factors (bijective multipliers mod 2^bits)
1408    x: [u64; 3]}  // xor constants
1409
1410  impl ShuffledIter {
1411    pub fn with_seed (len: usize, seed: u64) -> ShuffledIter {
1412      let mask = if len <= 1 {0} else {
1413        let bits = 64 - ((len - 1) as u64) .leading_zeros();
1414        (1u64 << bits) - 1};
1415      // Derive 3 rounds of (odd-factor, xor-constant) from the seed.
1416      // splitmix64-style expansion ensures good bit diversity.
1417      let mut s = seed;
1418      let mut next = || -> u64 {
1419        s = s.wrapping_add (0x9e3779b97f4a7c15);
1420        let mut z = s;
1421        z = (z ^ (z >> 30)).wrapping_mul (0xbf58476d1ce4e5b9);
1422        z = (z ^ (z >> 27)).wrapping_mul (0x94d049bb133111eb);
1423        z ^ (z >> 31)};
1424      let f = [next() | 1, next() | 1, next() | 1];  // `| 1` ensures odd → bijective multiplier mod 2^n
1425      let x = [next(), next(), next()];
1426      ShuffledIter {len, mask, counter: 0, emitted: 0, f, x}}
1427
1428    /// Bijective mixing: maps counter to a pseudo-random value within `0..=mask`.
1429    ///
1430    /// Each round does `val = (val * odd) ^ xor_const`, masked to `bits` bits.
1431    /// Multiplying by an odd number is a bijection mod 2^n (it has a multiplicative
1432    /// inverse mod 2^n because gcd(odd, 2^n)==1). XOR is trivially a bijection.
1433    /// The composition of bijections is a bijection, so every input in `0..=mask`
1434    /// maps to a unique output in `0..=mask`. Cycle-walking (skipping values ≥ len)
1435    /// then gives a permutation of `0..len` — every index visited exactly once.
1436    #[inline]
1437    fn mix (&self, val: u64) -> u64 {
1438      let mut v = val;
1439      for i in 0..3 {v = (v.wrapping_mul (self.f[i]) ^ self.x[i]) & self.mask}
1440      v}}
1441
1442  impl Iterator for ShuffledIter {
1443    type Item = usize;
1444
1445    #[inline]
1446    fn next (&mut self) -> Option<usize> {
1447      if self.len == 0 {return None}
1448      if self.emitted >= self.len as u64 {return None}
1449      loop {
1450        let candidate = self.mix (self.counter);
1451        self.counter += 1;
1452        if candidate < self.len as u64 {
1453          self.emitted += 1;
1454          return Some (candidate as usize)}}}}
1455
1456  /// Iterate over a slice in random order, yielding `(index, &T)`.
1457  pub fn shuffled<T> (seed: u64, v: &[T]) -> impl Iterator<Item = (usize, &T)> {
1458    let len = v.len();
1459    ShuffledIter::with_seed (len, seed) .map (move |ix| (ix, &v[ix]))}
1460
1461  #[test]
1462  fn every_index_visited () {
1463    for len in [0, 1, 2, 3, 7, 8, 15, 16, 100, 1000, 1024, 1025] {
1464      let mut seen = vec![false; len];
1465      let it = ShuffledIter::with_seed (len, now_ms());
1466      let mut count = 0usize;
1467      for ix in it {
1468        assert! (ix < len, "index {ix} out of bounds for len {len}");
1469        assert! (!seen[ix], "index {ix} visited twice (len {len})");
1470        seen[ix] = true;
1471        count += 1;}
1472      assert_eq! (count, len, "expected {len} items, got {count}");
1473      assert! (seen.iter().all (|&v| v || len == 0), "not every index visited (len {len})");}}
1474
1475  #[test]
1476  fn shuffled_vec_demo() {
1477    let data = vec!["alpha", "beta", "gamma", "delta", "epsilon"];
1478    let mut visited = vec![false; data.len()];
1479    for (ix, val) in shuffled (now_ms(), &data) {
1480      assert! (!visited[ix]);
1481      visited[ix] = true;
1482      assert_eq! (*val, data[ix]);}
1483    assert! (visited.iter().all (|&v| v));}
1484
1485  #[test]
1486  fn deterministic_with_same_seed() {
1487    let seed = 42u64;
1488    let a: Vec<usize> = ShuffledIter::with_seed (50, seed) .collect();
1489    let b: Vec<usize> = ShuffledIter::with_seed (50, seed) .collect();
1490    assert_eq! (a, b);}
1491
1492  #[test]
1493  fn likely_not_sequential() {
1494    let data: Vec<usize> = (0..100) .collect();
1495    let mut same_pos = 0;
1496    for (ix, &val) in shuffled (now_ms(), &data) {assert_eq! (ix, val)}
1497    let yielded_indices: Vec<usize> = shuffled (now_ms(), &data) .map (|p| p.0) .collect();
1498    for (i, &ix) in yielded_indices.iter().enumerate() {
1499      if i == ix {same_pos += 1}}
1500    // Improbable that a shuffled array of 100 elements has more than a few elements in their original positions
1501    assert! (same_pos < 9, "{}", same_pos)}}
1502
1503/// Pool which `join`s `thread`s on `drop`.
1504#[cfg(feature = "tpool")] pub mod tpool {
1505  //! ### `TPool` Methods
1506  //! * `fn post (&self, task: Box<dyn FnOnce() -> Re<()> + Send + Sync + 'static>) -> Re<()>`
1507  //!   Runs given callback from one of `sponsor`ed threads.
1508  //! * `fn fin (&self, tag: InlinableString, fin: Box<dyn FnOnce() -> Re<()> + Send + Sync + 'static>) -> bool`
1509  //!   Registers given callback, if not already per tag, to be run (FIFO) after pool threads are joined in `stop`.
1510  //! * `fn jobsⁿ (&self) -> Re<usize>` Number of jobs queued or running.
1511  //! * `fn threadsⁿ (&self) -> usize` Number of threads in the pool.
1512  //! * `fn bye (&self)` Signals threads to exit and prevents new jobs from being posted.
1513  //! * `fn stop (&self) -> usize` Non-blocking stop: joins finished threads and runs finalizers.
1514  //!
1515  //! ### Global Functions
1516  //! * `fn tpool() -> Result<AReadGuard<'static, TPool, TPool>, AArcErr>` Shared thread pool.
1517  //! * `fn tpost (spin: u32, threads: u8, task: Box<dyn FnOnce() -> Re<()> + Send + Sync + 'static>) -> Re<bool>`
1518  //!   Posts `task` to shared `TPOOL` if `threads` are in it, or runs on current thread otherwise.
1519
1520  use crate::any_to_str;
1521  use crate::aarc::{AArc, AArcErr, AReadGuard};
1522  use crate::re::Re;
1523  use fomat_macros::fomat;
1524  use inlinable_string::InlinableString;
1525  use std::collections::VecDeque;
1526  use std::hint::spin_loop;
1527  use std::panic::{catch_unwind, AssertUnwindSafe};
1528  use std::sync::atomic::{AtomicBool, AtomicI16, Ordering};
1529  use std::thread::{self, JoinHandle};
1530  use std::time::Duration;
1531  use parking_lot::{Condvar, Mutex};
1532
1533  #[derive (Default)]
1534  struct TPoolState {
1535    queue: VecDeque<Box<dyn FnOnce() -> Re<()> + Send + 'static>>,
1536    threads: Vec<(InlinableString, JoinHandle<Re<()>>)>,
1537    finalizers: Vec<(InlinableString, Box<dyn FnOnce() -> Re<()> + Send + 'static>)>}
1538
1539  #[derive (Default)]
1540  pub struct TPool {
1541    state: Mutex<TPoolState>,
1542    alarm: Condvar,
1543    running: AtomicI16,
1544    bye: AtomicBool}
1545
1546  unsafe impl Send for TPool {}
1547  unsafe impl Sync for TPool {}
1548
1549  pub trait Sponsor {
1550    /// Add thread to pool.  
1551    /// `false` if `tag` was already in thread pool.
1552    fn sponsor (&self, tag: InlinableString) -> Re<bool>;}
1553
1554  impl Sponsor for AReadGuard<'_, TPool, TPool> {
1555    /// > tpool()?.sponsor ("threadName13c".into())?;
1556    fn sponsor (&self, tag: InlinableString) -> Re<bool> {
1557      let mut state = self.state.lock();
1558      if state.threads.iter().any (|(tagʹ, _)| *tagʹ == tag) {return Re::Ok (false)}
1559      let tname = fomat! ("TP" (tag));  // Effective Linux length is 15 characters.
1560      let pool = self.aarc();
1561      state.threads.push ((tag,
1562        thread::Builder::new().name (tname) .spawn (move || -> Re<()> {
1563          let po = pool.spin_rd()?;  // NB: Threads hold read lock over pool.
1564          loop {
1565            let task = {
1566              let mut state = po.state.lock();
1567              match state.queue.pop_front() {
1568                Some (j) => {po.running.fetch_add (1, Ordering::Relaxed); j}
1569                // NB: We only check `bye` when out of jobs, in order for jobs to run to completion at shutdown
1570                None if po.bye.load (Ordering::Relaxed) => {break Re::Ok(())}
1571                None => {
1572                  po.alarm.wait_for (&mut state, Duration::from_secs_f32 (0.31));
1573                  if let Some (job) = state.queue.pop_front() {po.running.fetch_add (1, Ordering::Relaxed); job} else {continue}}}};
1574            let rc = catch_unwind (AssertUnwindSafe (task));
1575            let running = po.running.fetch_sub (1, Ordering::Relaxed);
1576            if running < 0 {log! (a 202, [=running])}
1577            match rc {
1578              Ok (Re::Ok(())) => {}
1579              Ok (Re::Err (err)) => {log! (a 1, (err))}
1580              Err (err) => {log! (a 1, [any_to_str (&*err)])}}}})?));
1581      Re::Ok (true)}}
1582
1583  impl TPool {
1584    /// Run given callback from one of `sponsor`ed threads.
1585    pub fn post (&self, task: Box<dyn FnOnce() -> Re<()> + Send + Sync + 'static>) -> Re<()> {
1586      // Lock before checking `bye` so worker threads can't observe empty queue and exit before we push
1587      let mut state = self.state.lock();
1588      if self.bye.load (Ordering::Relaxed) {return Re::Err ("TPool is stopping".into())}
1589      state.queue.push_back (task);
1590      self.alarm.notify_one();
1591      Re::Ok(())}
1592
1593    /// Run given callback (FIFO) after pool threads are joined in `stop`.  
1594    /// `false` if non-empty `tag` was already registered.
1595    pub fn fin (&self, tag: InlinableString, finalizer: Box<dyn FnOnce() -> Re<()> + Send + Sync + 'static>) -> bool {
1596      let mut state = self.state.lock();
1597      if !tag.is_empty() && state.finalizers.iter().any (|(tagʹ, _)| *tagʹ == tag) {false}
1598      else {state.finalizers.push ((tag, finalizer)); true}}
1599
1600    /// Number of jobs queued or running.
1601    pub fn jobsⁿ (&self) -> Re<usize> {
1602      let state = self.state.lock();
1603      Re::Ok (state.queue.len() + self.running.load (Ordering::Relaxed) .max (0) as usize)}
1604
1605    /// Number of sponsored threads.
1606    pub fn threadsⁿ (&self) -> usize {
1607      self.state.lock().threads.len()}
1608
1609    /// Signal threads to exit and prevent new jobs from being posted.
1610    pub fn bye (&self) -> usize {
1611      self.bye.store (true, Ordering::Relaxed);
1612      self.alarm.notify_all()}
1613
1614    /// Non-blocking stop: joins finished threads and runs finalizers if all threads are done.<br>
1615    /// Returns the number of threads still running, should reach 0 when pool is stopped.
1616    pub fn stop (&self) -> usize {
1617      let mut finalizers = Vec::new();
1618      let len = {
1619        let mut state = self.state.lock();
1620        let mut i = 0;
1621        while i < state.threads.len() {
1622          if state.threads[i].1.is_finished() {
1623            let (_tag, th) = state.threads.remove (i);
1624            match th.join() {
1625              Ok (Re::Ok(())) => {}
1626              Ok (Re::Err (err)) => {log! (a 1, (err))}
1627              Err (err) => {log! (a 1, [any_to_str (&*err)])}}
1628          } else {i += 1}}
1629        if state.threads.is_empty() {
1630          finalizers = std::mem::take (&mut state.finalizers)}
1631        state.threads.len()};
1632      for (tag, finalizer) in finalizers {  // FIFO
1633        let rc = catch_unwind (AssertUnwindSafe (finalizer));
1634        match rc {
1635          Ok (Re::Ok(())) => {}
1636          Ok (Re::Err (err)) => {log! (a 1, (tag) "] " (err))}
1637          Err (err) => {log! (a 1, (tag) "] " [any_to_str (&*err)])}}}
1638      len}}
1639
1640  pub static _TPOOL: AArc<TPool> = AArc::none();
1641
1642  /// Shared thread pool.
1643  pub fn tpool() -> Result<AReadGuard<'static, TPool, TPool>, AArcErr> {_TPOOL.spid()}
1644
1645  /// Post `task` to shared `TPOOL` if available, or run it on current thread otherwise.  
1646  /// Returns `false` if `task` was invoked directly.
1647  /// 
1648  ///     tpost (123, 1, Box::new (move || -> Re<()> {
1649  ///       log! ("t " [thread::current().id()] ',' [thread::current().name().unwrap_or ("-")]);
1650  ///       Re::Ok(())}))?;
1651  /// 
1652  /// * `spin` - Try to obtain `TPOOL` this many times before falling back to direct invocation of `task`.
1653  /// * `threads` - Use direct invocation if there is less than the given number of threads in the pool.
1654  pub fn tpost (spin: i32, threads: u8, task: Box<dyn FnOnce() -> Re<()> + Send + Sync + 'static>) -> Re<bool> {
1655    let pool = _TPOOL.spidʳ (spin)?;
1656    if pool.threadsⁿ() < threads as usize {
1657      task()?;
1658      Re::Ok (false)
1659    } else {
1660      pool.post (task)?;
1661      Re::Ok (true)}}
1662
1663  #[cfg(all(test, feature = "nightly", feature = "re"))]
1664  mod tests {
1665    use super::*;
1666    use crate::aarc::AArc;
1667    use crate::re::Re;
1668    use std::thread;
1669    use std::time::Duration;
1670
1671    #[test]
1672    fn jobs_n_gets_to_zero() {
1673      let pool_arc = AArc::<TPool>::new (TPool::default());
1674      let pool = pool_arc.spin_rd().unwrap();
1675
1676      // Sponsor a worker thread
1677      pool.sponsor ("test_worker".into()) .unwrap();
1678
1679      // Initially, jobs should be 0
1680      assert_eq! (pool.jobsⁿ().unwrap(), 0);
1681
1682      // Post a task that sleeps for a short duration
1683      pool.post (Box::new (|| {
1684        thread::sleep (Duration::from_millis (50));
1685        Re::Ok(())})) .unwrap();
1686
1687      // Right after posting, jobsⁿ should be at least 1 (queued or running)
1688      assert_eq! (1, pool.jobsⁿ().unwrap());
1689
1690      // Wait for the job to complete
1691      let mut cleared = false;
1692      for _ in 0..90 {
1693        if pool.jobsⁿ().unwrap() == 0 {
1694          cleared = true;
1695          break;}
1696        thread::sleep (Duration::from_millis (10))}
1697
1698      // Assert that the jobs count successfully reached 0
1699      assert! (cleared, "jobsⁿ did not reach 0 in time");
1700      assert_eq! (pool.jobsⁿ().unwrap(), 0);
1701      
1702      pool.bye();
1703      while pool.stop() > 0 {
1704        thread::sleep (Duration::from_millis (10));}}
1705
1706    #[test]
1707    fn aarc_oneshot() {
1708      { let tpool = tpool().unwrap();
1709        if tpool.threadsⁿ() == 0 {
1710          tpool.sponsor ("oneshot".into()) .unwrap();
1711          assert_eq! (tpool.threadsⁿ(), 1)} }
1712
1713      let rx = AArc::<&'static str>::empty();
1714      let tx = rx.clone();
1715      tpost (9, 1, Box::new (move || {
1716        thread::sleep (Duration::from_millis (20));
1717        tx.spinˢ (1, "done")?;
1718        Re::Ok(())})) .unwrap();
1719      let val = rx.spinʷ (-1) .unwrap().take();
1720      assert_eq! (*val, "done")}
1721
1722    #[test]
1723    fn vs_system() {
1724      use std::time::Instant;
1725
1726      { let tpool = tpool().unwrap();
1727        if tpool.threadsⁿ() == 0 {
1728          tpool.sponsor ("vs_system".into()) .unwrap();
1729          assert_eq! (tpool.threadsⁿ(), 1)} }
1730
1731      let tpost_done = AArc::<u64>::empty();
1732      let tpost_done_c = tpost_done.clone();
1733      fn cps_tpost (count: u32, start: Instant, done: AArc<u64>) {
1734        if count == 123 {
1735          done.spin_set (start.elapsed().as_micros() as u64) .unwrap();
1736          return}
1737        assert! (tpost (9, 1, Box::new (move || {
1738          cps_tpost (count + 1, start, done);
1739          Re::Ok(())})) .unwrap())}
1740      cps_tpost (0, Instant::now(), tpost_done_c);
1741
1742      let thread_done = AArc::<u64>::empty();
1743      let thread_done_c = thread_done.clone();
1744      fn cps_thread (count: u32, start: Instant, done: AArc<u64>) {
1745        if count == 123 {
1746          done.spin_set (start.elapsed().as_micros() as u64) .unwrap();
1747          return}
1748        thread::spawn (move || cps_thread (count + 1, start, done));}
1749      cps_thread (0, Instant::now(), thread_done_c);
1750
1751      loop {
1752        if let (Ok (tpost_time), Ok (thread_time)) = (tpost_done.spin_rd(), thread_done.spin_rd()) {
1753          // tpost: 122µs, thread: 5666µs
1754          if true {print! ("tpost: {}µs, thread: {}µs ", *tpost_time, *thread_time)}
1755          assert! (*tpost_time < *thread_time);
1756          break}
1757        thread::sleep (Duration::from_millis (20))}}}}
1758
1759/*
1760# udiff: let's move tpool test into a separate (sub)module protected with cfg from non-nightly build
1761
1762    --- gemini/gemini-3.1-pro-preview, 31ᵏ 781ˡ 45% Σ 32ᵏ -------
1763
1764The goal is to move the tests within the `tpool` module into a separate `tests` submodule that is conditionally compiled only for tests and when the `nightly` feature is enabled.
1765
1766Here is the plan:
17671. Wrap the `#[test]` functions at the end of the `tpool` module inside a `mod tests { ... }`.
17682. Add the `#[cfg(all(test, feature = "nightly"))]` attribute to the new `tests` module.
17693. Include necessary imports (`use super::*;`, `use crate::aarc::AArc;`, `use crate::re::Re;`, `use std::thread;`, `use std::time::Duration;`) at the top of the `tests` module so the test code resolves correctly.
1770
1771*/