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// --- status line -------
128
129#[cfg(all(feature = "crossterm", not(target_arch = "wasm32")))]
130fn isatty (fd: c_int) -> c_int {unsafe {libc::isatty (fd)}}
131
132#[cfg(all(feature = "crossterm", target_arch = "wasm32"))]
133fn isatty (_fd: c_int) -> c_int {0}
134
135#[cfg(feature = "crossterm")]
136static mut STATUS_LINE: Mutex<String> = Mutex::new (String::new());
137
138#[cfg(feature = "crossterm")]
139pub struct IsTty {pub is_tty: AtomicI8}
140#[cfg(feature = "crossterm")]
141impl core::ops::Deref for IsTty {
142  type Target = bool;
143  fn deref (&self) -> &Self::Target {
144    let mut is_tty = self.is_tty.load (Ordering::Relaxed);
145    if is_tty == 0 {
146      // https://man7.org/linux/man-pages/man3/isatty.3.html
147      is_tty = if isatty (1) != 0 {1} else {-1};
148      self.is_tty.store (is_tty, Ordering::Relaxed)}
149    if is_tty == 1 {&true} else {&false}}}
150
151/// `1` if standard output is a terminal, `0` if unknown
152#[cfg(feature = "crossterm")]
153pub static ISATTY: IsTty = IsTty {is_tty: AtomicI8::new (0)};
154
155/// The time of the last status line update, in milliseconds.  
156/// Tracked in order to help the calling code with implementing debounce strategies
157/// (for sending each and every update to the terminal might be a bottleneck,
158/// plus it might flicker
159/// and might be changing too fast for a user to register the content).
160#[cfg(feature = "crossterm")]
161pub static STATUS_LINE_LM: AtomicUsize = AtomicUsize::new (0);
162
163/// Clear the rest of the line.
164#[cfg(feature = "crossterm")]
165fn delete_line (stdout: &mut io::Stdout) {
166  use crossterm::{terminal, QueueableCommand};
167
168  let _ = stdout.queue (terminal::Clear (terminal::ClearType::UntilNewLine));}
169
170  // NB: term's `delete_line` is really screwed.
171  // Sometimes it doesn't work. And when it does, it does it wrong.
172  // Documentation says it "Deletes the text from the cursor location to the end of the line"
173  // but when it works it clears the *entire* line instead.
174  // let _ = stdout.write (b"\x1B[K");}}  // EL0. Clear right.
175
176/// Clears the line to the right, prints the given text, moves the caret all the way to the left.
177///
178/// Will only work if the terminal `isatty`.
179///
180/// Repeating this call will keep updating the same line (effectively a status line).
181///
182/// And it's kind of compatible with the normal stdout logging because the latter will overwrite the status line.
183///
184/// One can also call `status_line_clear()` to clear the line before the normal output comes forth
185/// in order to avoid leaving stray characters on the screen.
186///
187/// Skips spewing the output if the hash of the generated status line is identical to the existing one.
188///
189/// The function is intended to be used with a macros, for example:
190///
191///     use gstuff::{status_line, ISATTY};
192///     macro_rules! status_line {($($args: tt)+) => {if *ISATTY {
193///       status_line (file!(), line!(), &fomat! ($($args)+))}}}
194#[cfg(all(feature = "crossterm"))]
195pub fn status_line (file: &str, line: u32, status: &str) {
196  use crossterm::{QueueableCommand, cursor};
197  use io::{stdout, Write};
198  use std::collections::hash_map::DefaultHasher;
199  use std::hash::Hasher;
200
201    if let Ok (mut status_line) = unsafe {STATUS_LINE.lock()} {
202      let mut stdout = stdout();
203      let old_hash = {let mut hasher = DefaultHasher::new(); hasher.write (status_line.as_bytes()); hasher.finish()};
204      status_line.clear();
205      use std::fmt::Write;
206      let _ = write! (&mut *status_line, "{}:{}] {}", filename (file), line, status);
207      let new_hash = {let mut hasher = DefaultHasher::new(); hasher.write (status_line.as_bytes()); hasher.finish()};
208      if old_hash != new_hash {
209        STATUS_LINE_LM.s (now_ms() as usize);
210
211        // Try to keep the status line withing the terminal bounds.
212        match crossterm::terminal::size() {
213          Ok ((w, _)) if status_line.chars().count() >= w as usize => {
214            let mut tmp = String::with_capacity (w as usize - 1);
215            for ch in status_line.chars().take (w as usize - 1) {tmp.push (ch)}
216            let _ = stdout.write (tmp.as_bytes());},
217          _ => {let _ = stdout.write (status_line.as_bytes());}};
218
219        delete_line (&mut stdout);
220        let _ = stdout.queue (cursor::MoveToColumn (0));
221        let _ = stdout.flush();}}}
222
223/// Clears the status line if stdout `isatty` and `status_line` isn't empty.
224#[cfg(feature = "crossterm")]
225pub fn status_line_clear() -> String {
226  use io::{stdout, Write};
227  let mut ret = String::new();
228  if let Ok (mut status_line) = unsafe {STATUS_LINE.lock()} {
229    if *ISATTY && !status_line.is_empty() {
230      let mut stdout = stdout();
231        STATUS_LINE_LM.s (now_ms() as usize);
232        core::mem::swap (&mut ret, &mut status_line);
233        delete_line (&mut stdout);
234        let _ = stdout.flush();}}
235  ret}
236
237/// Clear the status line, run the code, then restore the status line.
238///
239/// Simply runs the `code` if the stdout is not `isatty` or if the status line is empty.
240#[cfg(feature = "crossterm")]
241pub fn with_status_line (code: &dyn Fn()) {
242  use crossterm::{QueueableCommand, cursor};
243  use io::{stdout, Write};
244
245  if let Ok (status_line) = unsafe {STATUS_LINE.lock()} {
246    if !*ISATTY || status_line.is_empty() {
247      code()
248    } else {
249      let mut stdout = stdout();
250      delete_line (&mut stdout);
251      let _ = stdout.flush();  // We need to send this EL0 out because the $code might be writing to stderr and thus miss it.
252      code();
253      // TODO: Should probably use `term_size::dimensions` to limit the status line size, just like in `fn status_line`.
254      let _ = stdout.write (status_line.as_bytes());
255      let _ = stdout.queue (cursor::MoveToColumn (0));
256      let _ = stdout.flush();}}}
257
258#[cfg(feature = "crossterm")]
259#[test] fn test_status_line() {
260  with_status_line (&|| println! ("hello world"));}
261
262#[cfg(all(feature = "inlinable_string", feature = "fomat-macros"))]
263pub fn short_log_time (ms: u64) -> inlinable_string::InlinableString {
264  use fomat_macros::wite;
265  let iso = ms2iso8601 (ms as i64);
266  ifomat! ((&iso[8..10]) ' ' (&iso[11..19]))}
267
268#[cfg(all(feature = "crossterm", feature = "inlinable_string", feature = "fomat-macros"))]
269#[macro_export] macro_rules! log {
270
271  ($on: literal, $($args: tt)+) => {  // a flip to (temporarily) disable
272    if $on & 1 == 1 {log! ($($args)+)}};
273
274  (t $time: expr => $delay: expr, $($args: tt)+) => {{  // $delay repeat by $time
275    static LL: core::sync::atomic::AtomicI64 = core::sync::atomic::AtomicI64::new (0);
276    let now = $time as i64;
277    let Δ = now - LL.load (core::sync::atomic::Ordering::Relaxed);
278    if $delay <= Δ {
279      LL.store (now, core::sync::atomic::Ordering::Relaxed);
280      log! ($($args)+)}}};
281
282  (q $command: expr, $($args: tt)+) => {{
283    $crate::with_status_line (&|| {
284      use crossterm::QueueableCommand;
285      use fomat_macros::{wite, fomat};
286      use std::io::Write;
287      let tty = *$crate::ISATTY;
288      let mut stdout = std::io::stdout();
289      if tty {let _ = stdout.queue ($command);}
290      let _ = wite! (&mut stdout,
291        ($crate::short_log_time ($crate::now_ms())) ' '
292        ($crate::filename (file!())) ':' (line!()) "] "
293        $($args)+ '\n');
294      if tty {let _ = stdout.queue (crossterm::style::ResetColor);}
295      let _ = stdout.flush();})}};
296
297  // https://docs.rs/crossterm/latest/crossterm/style/enum.Color.html
298  (c $color: expr, $($args: tt)+) => {
299    log! (q crossterm::style::SetForegroundColor ($color), $($args)+)};
300
301  // https://en.wikipedia.org/wiki/ANSI_escape_code#8-bit
302  // https://www.ditig.com/256-colors-cheat-sheet
303  (a $ansi: expr, $($args: tt)+) => {
304    log! (q crossterm::style::SetForegroundColor (
305      crossterm::style::Color::AnsiValue ($ansi)), $($args)+)};
306
307  ($($args: tt)+) => {{
308    $crate::with_status_line (&|| {
309      use fomat_macros::{pintln, fomat};
310      pintln! (
311        ($crate::short_log_time ($crate::now_ms())) ' '
312        ($crate::filename (file!())) ':' (line!()) "] "
313        $($args)+);})}};}
314
315#[cfg(all(feature = "crossterm", feature = "inlinable_string", feature = "fomat-macros"))]
316#[test] fn test_log() {
317  log! ([= 2 + 2])}
318
319/// A helper to build a string on the stack.
320///
321/// Given an array it makes a writeable cursor available to the passed code block.
322///
323/// Returns a &str slice pointing at memory between the array start and the cursor.
324///
325/// Example:
326///
327///     use core::mem::MaybeUninit;
328///     use std::io::Write;
329///     #[allow(invalid_value)] let mut foobar: [u8; 128] = unsafe {MaybeUninit::uninit().assume_init()};
330///     let foobar = gstring! (foobar, {
331///         write! (foobar, "foo") .expect ("!write");
332///         write! (foobar, "bar") .expect ("!write");
333///     });
334///
335/// Alternatives: https://crates.io/crates/stack.
336#[macro_export] macro_rules! gstring {($array: ident, $code: block) => {{
337  let end = {
338    let mut $array = ::std::io::Cursor::new (&mut $array[..]);
339    let $array = &mut $array;
340    $code;
341    $array.position() as usize};
342  let s = unsafe {::core::str::from_utf8_unchecked (&$array[0..end])};
343  s}}}
344
345/// Fomat into a small string.
346/// 
347/// Typical imports:
348/// 
349///     use inlinable_string::{InlinableString, StringExt};
350///     use std::fmt::{Write as FmtWrite};
351#[cfg(feature = "fomat-macros")]
352#[macro_export] macro_rules! ifomat {
353  ($($args: tt)+) => ({
354    use inlinable_string::{InlinableString, StringExt};
355    use std::fmt::Write as FmtWrite;
356    let mut is = InlinableString::new();
357    wite! (&mut is, $($args)+) .expect ("!wite");
358    is})}
359
360/// now_ms / 1000 / 86400 ↦ year, month, day UTC
361/// 
362/// https://stackoverflow.com/a/32158604/257568, http://howardhinnant.github.io/date_algorithms.html
363pub const fn civil_from_days (mut z: i32) -> (i32, u32, u32) {
364  z += 719468;
365  let era = if 0 <= z {z} else {z - 146096} / 146097;
366  let doe = (z - era * 146097) as u32;  // 0..=146096
367  let yoe = (doe - doe/1460 + doe/36524 - doe/146096) / 365;  // 0..=399
368  let y = yoe as i32 + era * 400;
369  let doy = doe - (365 * yoe + yoe / 4 - yoe / 100);  // 0..=365
370  let mp = (5 * doy + 2) / 153;  // 0..=11
371  let d = doy - (153 * mp + 2) / 5 + 1;  // 1..=31
372  let m = (mp as i32 + if (mp as i32) < 10 {3} else {-9}) as u32;  // 1..=12
373  (y + if m <= 2 {1} else {0}, m, d)}
374
375/// year, month 1..=12, day 1..=31 UTC ↦ UNIX milliseconds (aka now_ms) / 1000 / 86400
376pub const fn days_from_civil (mut y: i32, m: u32, d: u32) -> i32 {
377  y -= if m <= 2 {1} else {0};
378  let era = if 0 <= y {y} else {y - 399} / 400;
379  let yoe = (y - era * 400) as u32;      // 0..=399
380  let doy = (153 * (m as i32 + (if 2 < m {-3} else {9})) + 2) as u32 / 5 + d - 1;  // 0..=365
381  let doe = yoe * 365 + yoe / 4 - yoe / 100 + doy;  // 0..=146096
382  era * 146097 + doe as i32 - 719468}
383
384/// into integer with centiseconds "%y%m%d%H%M%S%.2f"
385#[cfg(feature = "chrono")]
386pub fn ldt2ics (dt: &chrono::DateTime<chrono::Local>) -> i64 {
387  use chrono::{Datelike, Timelike};
388  let y = dt.year() as i64;
389  let m = dt.month() as i64;
390  let d = dt.day() as i64;
391  let ti = dt.time();
392  let h = ti.hour() as i64;
393  let min = ti.minute() as i64;
394  let s = ti.second() as i64;
395  let cs = ti.nanosecond() as i64 / 10000000;
396  y%1000 * 1e12 as i64 + m * 10000000000 + d * 100000000 + h * 1000000 + min * 10000 + s * 100 + cs}
397
398/// UNIX time into ISO 8601 "%Y-%m-%dT%H:%M:%S%.3fZ", UTC
399#[cfg(feature = "inlinable_string")]
400pub fn ms2iso8601 (ms: i64) -> inlinable_string::InlinableString {
401  use inlinable_string::{InlinableString, StringExt};
402  use std::fmt::Write as FmtWrite;
403  let day = (ms / 1000 / 86400) as i32;
404  let h = ((ms / 1000) % 86400) / 3600;
405  let min = ((ms / 1000) % 3600) / 60;
406  let s = (ms / 1000) % 60;
407  let ms = ms % 1000;
408  let (y, m, d) = civil_from_days (day);
409  let mut is = inlinable_string::InlinableString::new();
410  // NB: Here `write!` is faster than `wite!`
411  write! (&mut is, "{:04}-{:02}-{:02}T{:02}:{:02}:{:02}.{:03}Z", y, m, d, h, min, s, ms) .expect ("!write!");
412  is}
413
414/// UNIX time into integer with centiseconds "%y%m%d%H%M%S%.2f", UTC
415pub const fn ms2ics (ms: i64) -> i64 {
416  let day = (ms / 1000 / 86400) as i32;
417  let h = ((ms / 1000) % 86400) / 3600;
418  let min = ((ms / 1000) % 3600) / 60;
419  let s = (ms / 1000) % 60;
420  let cs = ms % 1000 / 10;
421  let (y, m, d) = civil_from_days (day);
422  let y = y as i64; let m = m as i64; let d = d as i64;
423  y%1000 * 1e12 as i64 + m * 10000000000 + d * 100000000 + h * 1000000 + min * 10000 + s * 100 + cs}
424
425/// integer with centiseconds "%y%m%d%H%M%S%.2f" into UNIX time in milliseconds
426pub const fn ics2ms (ics: i64) -> i64 {
427  let day = days_from_civil (
428    (ics / 1000000000000 + 2000) as i32,
429    (ics / 10000000000 % 100) as u32,
430    (ics / 100000000 % 100) as u32) as i64;
431  let tm_hour = (ics / 1000000 % 100) as i64;
432  let tm_min = (ics / 10000 % 100) as i64;
433  let tm_sec = (ics / 100 % 100) as i64;
434  let ms = (ics % 100 * 10) as i64;
435  ms + tm_sec * 1000 + tm_min * 60000 + tm_hour * 3600000 + day * 86400000}
436
437/// from integer with centiseconds "%y%m%d%H%M%S%.2f"
438#[cfg(all(feature = "chrono", feature = "re"))]
439pub fn ics2ldt (ims: i64) -> re::Re<chrono::DateTime<chrono::Local>> {
440  use chrono::{TimeZone, Timelike};
441  let dt = chrono::Local.with_ymd_and_hms (
442    (ims / 1000000000000 + 2000) as i32,
443    (ims / 10000000000 % 100) as u32,
444    (ims / 100000000 % 100) as u32,
445    (ims / 1000000 % 100) as u32,
446    (ims / 10000 % 100) as u32,
447    (ims / 100 % 100) as u32) .earliest()?;
448  let dt = dt.with_nanosecond ((ims % 100) as u32 * 10000000)?;
449  re::Re::Ok (dt)}
450
451/// from integer with centiseconds "%y%m%d%H%M%S%.2f"
452#[cfg(all(feature = "chrono", feature = "re"))]
453pub fn ics2ndt (ims: i64) -> re::Re<chrono::NaiveDateTime> {
454  use chrono::{TimeZone, Timelike};
455  let dt = chrono::NaiveDateTime::new (
456    chrono::NaiveDate::from_ymd_opt (
457      (ims / 1000000000000 + 2000) as i32,
458      (ims / 10000000000 % 100) as u32,
459      (ims / 100000000 % 100) as u32)?,
460    chrono::NaiveTime::from_hms_nano_opt (
461      (ims / 1000000 % 100) as u32,
462      (ims / 10000 % 100) as u32,
463      (ims / 100 % 100) as u32,
464      (ims % 100) as u32 * 10000000)?);
465  re::Re::Ok (dt)}
466
467/// Extend ISO 8601 shorthand into full RFC 3339 timestamp.  
468/// “2022-12-12T12” → “2022-12-12T12:00:00Z”
469#[cfg(feature = "fomat-macros")]
470#[macro_export] macro_rules! iso8601z {
471  ($date_or_time: expr) => {{
472    let sufff = ($date_or_time.len() as i32 - 10) .max (0) as usize;
473    ifomat! (($date_or_time) if sufff < 10 {(&"T00:00:00Z"[sufff..])})}}}
474
475/// ISO 8601 shorthand “2022-12-12T12” parsed as a Local
476#[cfg(feature = "fomat-macros")]
477#[macro_export] macro_rules! iso8601toL {($short: expr) => {
478  Local.from_local_datetime (&(DateTime::parse_from_rfc3339 (&iso8601z! ($short))?) .naive_utc()) .earliest()?}}
479
480/// ISO 8601 shorthand “2022-12-12T12” converted into integer with centiseconds "%y%m%d%H%M%S%.2f"
481pub fn iso8601ics (iso: &[u8]) -> i64 {
482  let mut ics: [u8; 14] = *b"00000000000000";
483  if 4 <= iso.len() {
484    ics[0] = iso[2]; ics[1] = iso[3];
485    if 7 <= iso.len() && iso[4] == b'-' {
486      ics[2] = iso[5]; ics[3] = iso[6];
487      if 10 <= iso.len() && iso[7] == b'-' {
488        ics[4] = iso[8]; ics[5] = iso[9];
489        if 13 <= iso.len() && (iso[10] == b'T' || iso[10] == b' ') {
490          ics[6] = iso[11]; ics[7] = iso[12];
491          if 16 <= iso.len() && iso[13] == b':' {
492            ics[8] = iso[14]; ics[9] = iso[15];
493            if 19 <= iso.len() && iso[16] == b':' {
494              ics[10] = iso[17]; ics[11] = iso[18];
495              if 22 <= iso.len() && iso[19] == b'.' {
496                ics[12] = iso[20]; ics[13] = iso[21]}}}}}}}
497  match b2s (&ics) .parse() {Ok (k) => k, Err (_err) => 0}}
498
499#[cfg(all(test, feature = "nightly", feature = "chrono", feature = "inlinable_string", feature = "re"))] mod time_bench {
500  extern crate test;
501  use chrono::{DateTime, Datelike, Local, NaiveDateTime, TimeZone, Timelike, Utc};
502  use crate::{civil_from_days, days_from_civil, ics2ldt, ics2ms, ics2ndt, iso8601ics, ldt2ics, ms2ics, ms2iso8601, now_ms, re::Re};
503  use fomat_macros::wite;
504  use inlinable_string::InlinableString;
505  use rand::{rngs::SmallRng, seq::index::sample, Rng, SeedableRng};
506  use test::black_box;
507
508  #[bench] fn duration (bm: &mut test::Bencher) {
509    assert! (946684800000 == days_from_civil (2000, 1, 1) as i64 * 86400 * 1000);
510    let duration = 118050;
511    assert! ("00:01:58.050" == &ms2iso8601 (946684800000 + duration) [11..23]);
512    assert! (      15805 == ms2ics (946684800000 + duration) - 10100000000);
513    let mut ms = 0;
514    bm.iter (|| {  // verify that `ms2ics` can be reused to examine a time delta
515      let ics = ms2ics (946684800000 + ms as i64) - 10100000000;
516      let tm_min = (ics / 10000 % 100) as i64;
517      let tm_sec = (ics / 100 % 100) as i64;
518      let ims = (ics % 100 * 10) as i64;
519      let msʹ = ims + tm_sec * 1000 + tm_min * 60000;
520      assert! (ms == msʹ);
521      ms += 10;
522      if 3600 * 1000 <= ms {ms = 0}})}
523
524  #[bench] fn iso8601icsᵇ (bm: &mut test::Bencher) {
525    assert! (00000000000000 == iso8601ics (b""));
526    assert! (24000000000000 == iso8601ics (b"2024"));
527    assert! (24120000000000 == iso8601ics (b"2024-12"));
528    assert! (24121300000000 == iso8601ics (b"2024-12-13"));
529    assert! (24121321000000 == iso8601ics (b"2024-12-13T21"));
530    assert! (24121321000000 == iso8601ics (b"2024-12-13T21+03"));
531    assert! (24121314150000 == iso8601ics (b"2024-12-13T14:15"));
532    assert! (24121314150000 == iso8601ics (b"2024-12-13 14:15"));
533    assert! (24121314151600 == iso8601ics (b"2024-12-13T14:15:16"));
534    assert! (24121314151600 == iso8601ics (b"2024-12-13 14:15:16"));
535    assert! (24121314151698 == iso8601ics (b"2024-12-13T14:15:16.98"));
536    assert! (24121314151698 == iso8601ics (b"2024-12-13T14:15:16.980"));
537    assert! (24121314151698 == iso8601ics (b"3024-12-13T14:15:16.980Z"));
538    assert! (24121314151698 == iso8601ics (b"2024-12-13T14:15:16.980-03"));
539    bm.iter (|| {
540      let ics = iso8601ics (b"4321-12-23T13:14:15.987");
541      assert! (black_box (ics) == 21122313141598, "{}", ics)})}
542
543  #[bench] fn ms2iso8601ᵇ (bm: &mut test::Bencher) {
544    let mut rng = SmallRng::seed_from_u64 (now_ms());
545    bm.iter (|| {
546      let it = ms2iso8601 (rng.gen::<i64>().abs());
547      assert! (black_box (it) .ends_with ('Z'))})}
548
549  #[bench] fn chrono_iso8601 (bm: &mut test::Bencher) {
550    let dt = Utc::now();
551    bm.iter (|| {
552      let it = ifomat! ((dt.format ("%Y-%m-%dT%H:%M:%S%.3fZ")));
553      assert! (it.ends_with ('Z'))})}
554
555  fn make_samples() -> Vec<(DateTime<Local>, InlinableString)> {
556    let mut rng = SmallRng::seed_from_u64 (now_ms());
557    let mut samples = Vec::with_capacity (65536);
558    while samples.len() < samples.capacity() {
559      let ms = rng.gen::<i64>().abs() / 10 * 10;
560      if let Some (udt) = Utc.timestamp_millis_opt (ms) .earliest() {
561        let day = (ms / 1000 / 86400) as i32;
562        let (y, m, d) = civil_from_days (day);
563        assert! (udt.year() == y && udt.month() == m && udt.day() == d, "{:?}, y{}, m{}, d{}", udt, y, m, d);
564        assert! (days_from_civil (y, m, d) == day);
565        let cit = ifomat! ((udt.format ("%Y-%m-%dT%H:%M:%S%.3fZ")));
566        let cit = if cit.starts_with ('+') {&cit[1..]} else {&cit};
567        let dit = ms2iso8601 (ms);
568        assert! (cit == dit, "{} <> {}", cit, dit);
569        if let Some (udt) = udt.with_year (2000 + udt.year() % 100) {
570          let ms = udt.timestamp_millis();
571          let ics = ms2ics (ms);
572          let udtʹ = Utc.from_utc_datetime (&ics2ndt (ics) .unwrap());
573          assert! (udt == udtʹ, "{:?} <> {:?}", udt, udtʹ);
574          let msʹ = ics2ms (ics);
575          assert! (ms == msʹ, "{} <> {}", ms, msʹ)}}
576      if let Some (ldt) = Local.timestamp_millis_opt (ms) .earliest() {
577        if let Some (ldt) = ldt.with_year (2000 + ldt.year() % 100) {
578          let sdt = ifomat! ((ldt.format ("%Y-%m-%dT%H:%M:%S%.3f")));
579          samples.push ((ldt, sdt))}}}
580    samples}
581
582  #[bench] fn iso8601tol (bm: &mut test::Bencher) {
583    let (samples, mut sx) = (make_samples(), 0);
584    bm.iter (|| {
585      let ics = iso8601ics (samples[sx].1.as_bytes());
586      let dt = ics2ldt (ics) .unwrap();
587      let odt = &samples[sx].0;
588      assert! (
589        dt.year() == odt.year() && dt.month() == odt.month() && dt.day() == odt.day()
590         && dt.hour() == odt.hour() && dt.minute() == odt.minute() && dt.second() == odt.second()
591         && dt.nanosecond() == odt.nanosecond(),
592        "{} {} <> {} {}", ics, dt, samples[sx].1, odt);
593      sx += 1; if samples.len() <= sx {sx = 0}})}
594
595  #[bench] fn iso8601ton (bm: &mut test::Bencher) {
596    let (samples, mut sx) = (make_samples(), 0);
597    bm.iter (|| {
598      let ics = iso8601ics (samples[sx].1.as_bytes());
599      let dt = ics2ndt (ics) .unwrap();
600      let odt = &samples[sx].0;
601      assert! (
602        dt.year() == odt.year() && dt.month() == odt.month() && dt.day() == odt.day()
603         && dt.hour() == odt.hour() && dt.minute() == odt.minute() && dt.second() == odt.second()
604         && dt.nanosecond() == odt.nanosecond(),
605        "{} {} <> {} {}", ics, dt, samples[sx].1, odt);
606      sx += 1; if samples.len() <= sx {sx = 0}})}
607
608  #[bench] fn chrono_from_str (bm: &mut test::Bencher) {bm.iter (|| {
609    let dt = NaiveDateTime::parse_from_str ("4321-12-23T13:14:15", "%Y-%m-%dT%H:%M:%S") .unwrap();
610    assert! (dt.year() == 4321 && dt.month() == 12 && dt.day() == 23 
611      && dt.hour() == 13 && dt.minute() == 14 && dt.second() == 15)})}
612
613  #[bench] fn chrono_from_rfc3339 (bm: &mut test::Bencher) {bm.iter (|| {
614    let dt = DateTime::parse_from_rfc3339 ("4321-12-23T13:14:15Z") .unwrap();
615    assert! (dt.year() == 4321 && dt.month() == 12 && dt.day() == 23
616      && dt.hour() == 13 && dt.minute() == 14 && dt.second() == 15)})}
617
618  #[bench] fn iso8601tol_macro (bm: &mut test::Bencher) {bm.iter (|| {
619    fn f() -> Re<DateTime<Local>> {Re::Ok (iso8601toL! ("4321-12-23T13:14:15"))}
620    let dt = f().unwrap();
621    assert! (dt.year() == 4321 && dt.month() == 12 && dt.day() == 23
622      && dt.hour() == 13 && dt.minute() == 14 && dt.second() == 15)})}
623
624  #[bench] fn iso8601_ics_ms (bm: &mut test::Bencher) {bm.iter (|| {
625    let ics = iso8601ics (black_box (b"4321-12-23T13:14:15"));
626    assert! (ics == 21122313141500);
627    assert! (ics2ms (black_box (ics)) == 1640265255000)})}}
628
629/// Takes a netstring from the front of the slice.
630///
631/// Returns the unpacked netstring and the remainder of the slice.
632///
633/// NB: Netstring encoding is not included as a separate function
634/// because it is as simple as `wite! (&mut buf, (payload.len()) ':' (payload) ',')?;`.
635pub fn netstring (at: &[u8]) -> Result<(&[u8], &[u8]), String> {
636  let length_end = match at.iter().position (|&ch| ch < b'0' || ch > b'9') {Some (l) if l > 0 => l, _ => return ERR! ("No len.")};
637  match at.get (length_end) {Some (&ch) if ch == b':' => (), _ => return ERR! ("No colon.")};
638  let length = b2s (&at[0 .. length_end]);
639  let length: usize = try_s! (length.parse());
640  let bulk_pos = 0 + length_end + 1;
641  let bulk_end = bulk_pos + length;
642  match at.get (bulk_end) {Some (&ch) if ch == b',' => (), _ => return ERR! ("No comma.")}
643  Ok ((&at[bulk_pos .. bulk_end], &at[bulk_end + 1 ..]))}
644
645/// Wraps `gethostname` to fetch the current hostname into a temporary buffer.
646#[cfg(unix)]
647pub fn with_hostname (visitor: &mut dyn FnMut (&[u8])) -> Result<(), std::io::Error> {
648  use libc::{size_t, gethostname};  // http://man7.org/linux/man-pages/man2/gethostname.2.html
649  use std::ffi::CStr;
650
651  let mut buf = [0; 128];
652  let rc = unsafe {gethostname (buf.as_mut_ptr(), (buf.len() - 1) as size_t)};
653  if rc == 0 {
654    let cs = unsafe {CStr::from_ptr (buf.as_ptr())};
655    Ok (visitor (cs.to_bytes()))
656  } else {
657    Err (io::Error::last_os_error())}}
658
659#[cfg(unix)] #[test] fn test_hostname() {
660  let mut hostname = String::new();
661  with_hostname (&mut |bytes| hostname = String::from_utf8_lossy (bytes) .into_owned()) .unwrap();}
662
663/// Read contents of the file into a `Vec`.  
664/// Similar to `std::fs::read`.
665///
666/// Returns an empty `Vec` if the file is not present under the given path.
667pub fn slurp (path: &dyn AsRef<Path>) -> Vec<u8> {
668  let Ok (mut file) = fs::File::open (path) else {return Vec::new()};
669  let mut buf = Vec::new();
670  // Might issue a `stat` / `metadata` call to reserve space in the `buf`, aka `buffer_capacity_required`
671  if let Err (_err) = file.read_to_end (&mut buf) {return Vec::new()}
672  buf}
673
674/// Runs a command in a shell, returning stderr+stdout on success.
675///
676/// Sometimes we need something simpler than constructing a Command.
677///
678/// If the command failed then returns it's stderr
679pub fn slurp_prog (command: &str) -> Result<String, String> {
680  let output = match Command::new ("dash") .arg ("-c") .arg (command) .output() {
681    Ok (output) => output,
682    Err (ref err) if err.kind() == io::ErrorKind::NotFound => {  // "dash" was not found, try a different name.
683      try_s! (Command::new ("sh") .arg ("-c") .arg (command) .output())},
684    Err (err) => return ERR! ("{}", err)};
685
686  let combined_output: String = if output.stderr.is_empty() {
687    try_s! (String::from_utf8 (output.stdout))
688  } else if output.stdout.is_empty() {
689    try_s! (String::from_utf8 (output.stderr))
690  } else {
691    let mut buf = String::with_capacity (output.stderr.len() + output.stdout.len());
692    buf.push_str (try_s! (std::str::from_utf8 (&output.stderr[..])));
693    buf.push_str (try_s! (std::str::from_utf8 (&output.stdout[..])));
694    buf};
695
696  if output.status.success() {Ok (combined_output)} else {Err (combined_output)}}
697
698#[test] fn test_slurp_prog() {
699  if cfg! (windows) {println! ("Skipping as dash might be missing on Windows sometimes"); return}
700  let foo = match slurp_prog ("echo foo") {Ok (foo) => foo, Err (err) => panic! ("{}", err)};
701  assert_eq! (foo.trim(), "foo");}
702
703/// Run a command, printing it first. Stdout and stderr are forwarded through (`inherit`).
704pub fn cmd (cmd: &str) -> Result<(), String> {
705  println! ("$ {}", cmd);
706  let status = try_s! (Command::new ("dash") .arg ("-c") .arg (cmd) .stdout (Stdio::inherit()) .stderr (Stdio::inherit()) .status());
707  if !status.success() {Err (format! ("Command returned an error status: {}", status))} else {Ok(())}}
708
709/// Useful with panic handlers.
710/// 
711/// For example:
712/// 
713///     if let Err (err) = catch_unwind (AssertUnwindSafe (move || {
714///       let mut core = tokio_core::reactor::Core::new().expect ("!core");
715///       loop {core.turn (None)}
716///     })) {println! ("CORE panic! {:?}", any_to_str (&*err)); std::process::abort()}
717pub fn any_to_str<'a> (message: &'a dyn Any) -> Option<&'a str> {
718  if let Some (message) = message.downcast_ref::<&str>() {return Some (message)}
719  if let Some (message) = message.downcast_ref::<String>() {return Some (&message[..])}
720  return None}
721
722/// Converts the duration into a number of seconds with fractions.
723/// 
724/// (There's now a native `Duration::as_secs_f64`.)
725pub fn duration_to_float (duration: Duration) -> f64 {
726  duration.as_secs() as f64 + ((duration.subsec_nanos() as f64) / 1000000000.0)}
727
728/// Converts the time into a number of seconds with fractions.
729#[cfg(feature = "chrono")]
730pub fn dtl2float (dt: chrono::DateTime<chrono::Local>) -> f64 {
731  dt.timestamp() as f64 + ((dt.timestamp_subsec_nanos() as f64) / 1000000000.0)}
732
733/// Converts time in milliseconds into a number of seconds with fractions.
734pub fn ms2sec (ms: u64) -> f64 {
735  (ms / 1000) as f64 + ((ms % 1000) as f64 / 1000.0)}
736
737/// The current number of seconds since UNIX epoch, with fractions.
738///
739/// cf. http://stackoverflow.com/a/26878367/257568 (C++, Boost).
740pub fn now_float() -> f64 {
741  let now = SystemTime::now().duration_since (UNIX_EPOCH) .expect ("!duration_since");
742  duration_to_float (now)}
743
744#[test] fn test_now_float() {
745  let now = SystemTime::now().duration_since (UNIX_EPOCH) .expect ("!duration_since") .as_secs();
746  let t1 = now_float();
747  assert_eq! (now, t1 as u64);
748  thread::sleep (Duration::from_millis (100));
749  let t2 = now_float();
750  let delta = t2 - t1;
751  assert! (delta >= 0.098 && delta <= 0.150, "delta: {}", delta);}
752
753/// Converts the duration into a number of milliseconds.
754pub fn duration_to_ms (duration: Duration) -> u64 {
755  duration.as_secs() * 1000 + (duration.subsec_nanos() / 1000000) as u64}
756
757/// The current number of milliseconds since UNIX epoch.
758pub fn now_ms() -> u64 {
759  let now = SystemTime::now().duration_since (UNIX_EPOCH) .expect ("!duration_since");
760  duration_to_ms (now)}
761
762#[test] fn test_now_ms() {
763  let t1 = now_ms();
764  thread::sleep (Duration::from_millis (100));
765  let t2 = now_ms();
766  let delta = t2 - t1;
767  assert! (delta >= 98 && delta <= 150, "delta: {}", delta);}
768
769/// Last-modified of the file in seconds since the UNIX epoch, with fractions.  
770/// Returns 0 if the file does not exists.
771///
772/// A typical use case:
773///
774///     if (now_float() - try_s! (last_modified_sec (&path)) > 600.) {update (&path)}
775pub fn last_modified_sec (path: &dyn AsRef<Path>) -> Result<f64, String> {
776  let meta = match path.as_ref().metadata() {
777    Ok (m) => m,
778    Err (ref err) if err.kind() == std::io::ErrorKind::NotFound => return Ok (0.),
779    Err (err) => return ERR! ("{}", err)};
780  let lm = try_s! (meta.modified());
781  let lm = duration_to_float (try_s! (lm.duration_since (UNIX_EPOCH)));
782  Ok (lm)}
783
784// Consider targeting a CPU here.
785// https://github.com/rust-lang/rust/issues/44036
786
787/// On `x86_64` it is Time Stamp Counter (number of cycles).  
788/// Fall backs to `SystemTime` `as_nanos` otherwise.
789#[cfg(target_arch="x86_64")]
790pub fn rdtsc() -> u64 {unsafe {core::arch::x86_64::_rdtsc()}}
791
792#[cfg(not (target_arch="x86_64"))]
793pub fn rdtsc() -> u64 {SystemTime::now().duration_since (SystemTime::UNIX_EPOCH) .expect ("!now") .as_nanos() as u64}
794
795#[cfg(target_arch="x86_64")]
796#[test] fn test_rdtsc() {
797  assert! (rdtsc() != rdtsc())}
798
799/// Allows several threads or processes to compete for a shared resource by tracking resource ownership with a file.  
800/// If the lock file is older than `ttl_sec` then it is removed, allowing us to recover from a thread or process dying while holding the lock.
801pub struct FileLock<'a> {
802  /// Filesystem path of the lock file.
803  pub lock_path: &'a dyn AsRef<Path>,
804  /// The time in seconds after which an outdated lock file can be removed.
805  pub ttl_sec: f64,
806  /// The owned lock file. Removed upon unlock.
807  pub file: std::fs::File}
808impl<'a> FileLock<'a> {
809  /// Tries to obtain a file lock.
810  /// 
811  /// No blocking. Returns `None` if the file already exists and is recent enough (= locked).
812  ///
813  /// The returned structure will automatically remove the lock file when dropped.
814  /// 
815  ///     let Some (lock) = FileLock::lock (&lockᵖ, 123.)? else {log! ("Locked."); return Re::Ok(())};
816  ///     // ... Your code here ...
817  ///     drop (lock)
818  pub fn lock (lock_path: &'a dyn AsRef<Path>, ttl_sec: f64) -> Result<Option<FileLock<'a>>, String> {
819    let mut cycle = 0u8;
820    loop {
821      if cycle > 1 {break Ok (None)}  // A second chance.
822      cycle += 1;
823      let mut fo = std::fs::OpenOptions::new();
824      match fo.read (true) .write (true) .create_new (true) .open (lock_path.as_ref()) {
825        Ok (file) => break Ok (Some (FileLock {lock_path, ttl_sec, file})),
826        Err (ref ie) if ie.kind() == std::io::ErrorKind::AlreadyExists => {
827          // See if the existing lock is old enough to be discarded.
828          let lm = match last_modified_sec (lock_path) {
829            Ok (lm) => lm,
830            Err (ie) => break ERR! ("Error checking {:?}: {}", lock_path.as_ref(), ie)};
831          if lm == 0. {continue}  // Unlocked already?
832          if now_float() - lm > ttl_sec {
833            if let Err (err) = std::fs::remove_file (lock_path.as_ref()) {break ERR! ("Error removing {:?}: {}", lock_path.as_ref(), err)}
834            continue}
835          break Ok (None)},
836        Err (ie) => break ERR! ("Error creating {:?}: {}", lock_path.as_ref(), ie)}}}
837  /// Updates the modification time on the lock file.
838  /// Compile on Linux only as UTIME_NOW and futimens is absent on MacOS.
839  #[cfg(target_os = "linux")]
840  pub fn touch (&self) -> Result<(), String> {
841    //⌥ switch to https://doc.rust-lang.org/nightly/std/fs/struct.File.html#method.set_times
842    let ts = libc::timespec {tv_sec: 0, tv_nsec: libc::UTIME_NOW};
843    let times = [ts, ts];
844    use std::os::unix::io::AsRawFd;
845    let rc = unsafe {libc::futimens (self.file.as_raw_fd(), &times[0])};
846    if rc != 0 {
847      let err = std::io::Error::last_os_error();
848      return ERR! ("Can't touch {:?}: {}", self.lock_path.as_ref(), err)}
849    Ok(())}}
850impl<'a> Drop for FileLock<'a> {
851  fn drop (&mut self) {
852    let _ = std::fs::remove_file (self.lock_path);}}
853impl<'a> fmt::Debug for FileLock<'a> {
854  fn fmt (&self, ft: &mut fmt::Formatter) -> fmt::Result {
855    write! (ft, "FileLock ({:?}, {})", self.lock_path.as_ref(), self.ttl_sec)}}
856
857/// Process entry in /proc.
858#[derive(Debug)]
859pub struct ProcEn {
860  pub name: String,
861  pub path: std::path::PathBuf,
862  /// NB: cmdline is NUL-separated (meaning there will be an empty string at the end)
863  pub cmdline: Vec<String>}
864impl ProcEn {
865  pub fn pid (&self) -> Option<u32> {
866    if let Some (file_name) = self.path.file_name() {
867      if let Some (file_name) = file_name.to_str() {
868        if let Ok (pid) = file_name.parse() {
869          return Some (pid)}}}
870    None}}
871/// Iterate over processes in /proc.
872///
873///     if ProcIt::new().any (|proc_en| proc_en.cmdline.iter().any (|line_en| line_en.contains ("overfiend"))) {
874///       println! ("Overfiend the daemon is live!");
875///     }
876///
877///     let overfiends: Vec<ProcEn> = ProcIt::new().filter_map (|proc_en|
878///       if proc_en.cmdline.iter().any (|line_en| line_en.contains ("overfiend")) {Some (proc_en)}
879///       else {None}
880///     ) .collect();
881pub struct ProcIt {read_dir: std::fs::ReadDir}
882impl ProcIt {
883  pub fn new() -> ProcIt {
884    ProcIt {
885      read_dir: match Path::new ("/proc") .read_dir() {Ok (it) => it, Err (err) => panic! ("!proc: {}", err)}}}}
886impl Iterator for ProcIt {
887  type Item = ProcEn;
888  fn next (&mut self) -> Option<ProcEn> {
889    match self.read_dir.next() {
890      None => return None,
891      Some (Err (err)) => panic! ("ProcIt] !read_dir: {}", err),
892      Some (Ok (proc_en)) => {
893        let file_type = match proc_en.file_type() {
894          Ok (ft) => ft,
895          Err (err) => {
896            if matches! (err.kind(), io::ErrorKind::NotFound) {
897              return self.next()
898            } else {
899              panic! ("!file_type ({:?}): {}", proc_en.path(), err)}}};
900        if !file_type.is_dir() {return self.next()}
901        let name = proc_en.file_name();
902        let name = match name.to_str() {Some (name) => name, None => panic! ("ProcIt] !to_str")};
903        if !name.as_bytes().iter().all (|&b| b >= b'0' && b <= b'9') {  // Looks like PID?
904          return self.next()}
905        let path = proc_en.path();
906        let cmdline = String::from_utf8 (slurp (&path.join ("cmdline"))) .expect ("!from_utf8");  // NB: cmdline is NUL-separated.
907        if cmdline.is_empty() {return self.next()}
908        Some (ProcEn {name: name.into(), path: path, cmdline: cmdline.split ('\0') .map (String::from) .collect()})}}}}
909
910pub static mut SPIN_OUT: u32 = 1234567;
911
912fn pause_yield() {
913  spin_loop();
914  thread::yield_now();  // cf. https://stackoverflow.com/a/69847156/257568
915  spin_loop()}
916
917pub struct IniMutex<T> {au: AtomicI8, vc: UnsafeCell<MaybeUninit<T>>}
918#[must_use = "if unused the Mutex will immediately unlock"]
919pub struct IniMutexGuard<'a, T> {lo: &'a IniMutex<T>}
920
921unsafe impl<T: Send> Send for IniMutex<T> {}
922unsafe impl<T: Send> Sync for IniMutex<T> {}
923
924impl<T> IniMutex<T> {
925  pub const fn none() -> IniMutex<T> {
926    IniMutex {
927      au: AtomicI8::new (0),
928      vc: UnsafeCell::new (MaybeUninit::uninit())}}
929
930  pub const fn new (init: T) -> IniMutex<T> {
931    IniMutex {
932      au: AtomicI8::new (1),
933      vc: UnsafeCell::new (MaybeUninit::new (init))}}
934
935  /// `true` if the value is neither locked nor initialized
936  pub fn is_none (&self) -> bool {
937    0 == self.au.load (Ordering::Relaxed)}
938
939  /// `true` if the value is initialized
940  pub fn is_some (&self) -> bool {
941    0 != self.au.load (Ordering::Relaxed)}
942
943  /// Attempts to get a lock, returning immediately if locked or uninitialized
944  pub fn lock (&self) -> Result<IniMutexGuard<'_, T>, i8> {
945    match self.au.compare_exchange (1, 2, Ordering::Acquire, Ordering::Relaxed) {
946      Ok (1) => Ok (IniMutexGuard {lo: self}),
947      Ok (lock) => Err (lock),
948      Err (au) => Err (au)}}
949
950  //⌥ add a method to spin for a limited amount of time and/or CPU ticks, `spinʷ`
951  pub fn spin (&self) -> IniMutexGuard<'_, T> {
952    loop {
953      if let Ok (lock) = self.lock() {return lock}
954      pause_yield()}}
955
956  #[cfg(feature = "re")]
957  pub fn lock_init (&self, init: &mut dyn FnMut() -> re::Re<T>) -> Result<IniMutexGuard<'_, T>, LockInitErr> {
958    match self.au.compare_exchange (1, 2, Ordering::Acquire, Ordering::Relaxed) {
959      Ok (1) => Ok (IniMutexGuard {lo: self}),
960      Err (0) => match self.au.compare_exchange (0, 2, Ordering::Acquire, Ordering::Relaxed) {
961        Ok (0) => {
962          let vc = unsafe {&mut *self.vc.get()}; 
963          match init() {
964            re::Re::Ok (vi) => {
965              *vc = MaybeUninit::new (vi);
966              Ok (IniMutexGuard {lo: self})},
967            re::Re::Err (err) => {
968              self.au.store (0, Ordering::Relaxed);
969              Err (LockInitErr::Init (err))}}},
970        Ok (au) => Err (LockInitErr::Lock (au)),
971        Err (au) => Err (LockInitErr::Lock (au))},
972      Ok (au) => Err (LockInitErr::Lock (au)),
973      Err (au) => Err (LockInitErr::Lock (au))}}
974
975  #[cfg(feature = "re")]
976  pub fn spin_init (&self, init: &mut dyn FnMut() -> re::Re<T>) -> Result<IniMutexGuard<'_, T>, String> {
977    loop {
978      match self.lock_init (init) {
979        Ok (lock) => break Ok (lock),
980        Err (LockInitErr::Lock (_l)) => pause_yield(),
981        Err (LockInitErr::Init (err)) => break Err (err)}}}
982
983  /// `drop` the instance and reset the mutex to uninitialized
984  pub fn evict (lock: IniMutexGuard<'_, T>) {unsafe {
985    let vc = &mut *lock.lo.vc.get();
986    let mut swap: T = MaybeUninit::zeroed().assume_init();
987    core::mem::swap (&mut swap, vc.assume_init_mut());
988    lock.lo.au.store (0, Ordering::Release);
989    core::mem::forget (lock);
990    drop (swap)}}}
991
992impl<T> Default for IniMutex<T> {
993  /// Defaults to `none` (no value in mutex)
994  fn default() -> Self {
995    IniMutex::none()}}
996
997#[derive (Debug)]
998pub enum LockInitErr {Lock (i8), Init (String)}
999
1000impl fmt::Display for LockInitErr {
1001  fn fmt (&self, fm: &mut fmt::Formatter) -> fmt::Result {
1002    match *self {
1003      LockInitErr::Lock (au) => fm.write_fmt (format_args! ("{}", au)),
1004      LockInitErr::Init (ref err) => fm.write_str (err)}}}
1005
1006impl<T: Default> IniMutex<T> {
1007  pub fn lock_default (&self) -> Result<IniMutexGuard<'_, T>, i8> {
1008    match self.au.compare_exchange (1, 2, Ordering::Acquire, Ordering::Relaxed) {
1009      Ok (1) => Ok (IniMutexGuard {lo: self}),
1010      Err (0) => match self.au.compare_exchange (0, 2, Ordering::Acquire, Ordering::Relaxed) {
1011        Ok (0) => {
1012          let vc = unsafe {&mut *self.vc.get()}; 
1013          *vc = MaybeUninit::new (Default::default());
1014          Ok (IniMutexGuard {lo: self})},
1015        Ok (au) => Err (au),
1016        Err (au) => Err (au)},
1017      Ok (au) => Err (au),
1018      Err (au) => Err (au)}}
1019
1020  pub fn spin_default (&self) -> IniMutexGuard<'_, T> {
1021    loop {
1022      if let Ok (lock) = self.lock_default() {return lock}
1023      pause_yield()}}}
1024
1025impl<T> Deref for IniMutexGuard<'_, T> {
1026  type Target = T;
1027  fn deref (&self) -> &T {
1028    let vc = unsafe {&mut *self.lo.vc.get()};
1029    unsafe {&*vc.as_ptr()}}}
1030
1031impl<T> DerefMut for IniMutexGuard<'_, T> {
1032  fn deref_mut (&mut self) -> &mut T {
1033    let vc = unsafe {&mut *self.lo.vc.get()};
1034    unsafe {&mut *vc.as_mut_ptr()}}}
1035
1036impl<T: fmt::Debug> fmt::Debug for IniMutexGuard<'_, T> {
1037  fn fmt (&self, ft: &mut fmt::Formatter) -> fmt::Result {
1038    let vc = unsafe {&mut *self.lo.vc.get()};
1039    unsafe {fmt::Debug::fmt (&*vc.as_ptr(), ft)}}}
1040
1041impl<T: fmt::Display> fmt::Display for IniMutexGuard<'_, T> {
1042  fn fmt (&self, fm: &mut fmt::Formatter) -> fmt::Result {
1043    let vc = unsafe {&mut *self.lo.vc.get()};
1044    unsafe {(*vc.as_ptr()) .fmt (fm)}}}
1045
1046impl<T> Drop for IniMutexGuard<'_, T> {
1047  fn drop (&mut self) {
1048    self.lo.au.store (1, Ordering::Release)}}
1049
1050impl<T> Drop for IniMutex<T> {
1051  fn drop (&mut self) {
1052    if self.au.load (Ordering::Acquire) != 0 {
1053      self.au.store (0, Ordering::Relaxed);
1054      let vc = unsafe {&mut *self.vc.get()};
1055      unsafe {vc.assume_init_drop()}}}}
1056
1057/// Cached hostname
1058#[cfg(feature = "inlinable_string")]
1059pub static HOST: IniMutex<inlinable_string::InlinableString> = IniMutex::none();
1060
1061pub struct TSafe<T> (pub T);
1062unsafe impl<T> Send for TSafe<T> {}
1063unsafe impl<T> Sync for TSafe<T> {}
1064impl<T: Default> Default for TSafe<T> {fn default() -> Self {TSafe(T::default())}}
1065impl<T: Clone> Clone for TSafe<T> {fn clone (&self) -> Self {TSafe (self.0.clone())}}
1066impl<T: fmt::Debug> fmt::Debug for TSafe<T> {fn fmt (&self, ft: &mut fmt::Formatter<'_>) -> fmt::Result {self.0.fmt (ft)}}
1067impl<T: fmt::Display> fmt::Display for TSafe<T> {fn fmt (&self, ft: &mut fmt::Formatter<'_>) -> fmt::Result {self.0.fmt (ft)}}
1068
1069#[cfg(all(feature = "re", feature = "reffers"))]
1070pub trait SpinA<T> {
1071  /// Exclusive “write” lock. Assuming that there is but little contention, fails after spinning a while.
1072  fn spinʷ (self: &Self) -> re::Re<reffers::arc::RefMut<T>>;
1073  /// Shared “read” lock. Assuming that there is but little contention, fails after spinning a while.
1074  fn spinʳ (self: &Self) -> re::Re<reffers::arc::Ref<T>>;}
1075
1076#[cfg(all(feature = "re", feature = "reffers"))]
1077impl<T> SpinA<T> for reffers::arc::Strong<T> {
1078  fn spinʷ (&self) -> re::Re<reffers::arc::RefMut<T>> {
1079    let timeout = unsafe {SPIN_OUT};
1080    for spin in 0..timeout {
1081      if let Ok (lock) = self.try_get_refmut() {return re::Re::Ok (lock)}
1082      if spin % 10 == 0 {spin_loop()} else {thread::yield_now()}}
1083    re::Re::Err ("spin-out".into())}
1084
1085  fn spinʳ (&self) -> re::Re<reffers::arc::Ref<T>> {
1086    let timeout = unsafe {SPIN_OUT};
1087    for spin in 0..timeout {
1088      if let Ok (lock) = self.try_get_ref() {return re::Re::Ok (lock)}
1089      if spin % 10 == 0 {spin_loop()} else {thread::yield_now()}}
1090    re::Re::Err ("spin-out".into())}}
1091
1092/// Helps logging binary data (particularly with text-readable parts, such as bencode, netstring)
1093/// by replacing all the non-printable bytes with the `blank` character.
1094pub fn binprint (bin: &[u8], blank: u8) -> String {
1095  let mut bin: Vec<u8> = bin.into();
1096  for ch in bin.iter_mut() {if *ch < 0x20 || *ch >= 0x7F {*ch = blank}}
1097  unsafe {String::from_utf8_unchecked (bin)}}
1098
1099/// Row-major bits, 2x3, to [Bedstead](https://i.imgur.com/f3myFgM.png)
1100/// 
1101/// cf. https://youtu.be/5yoWxctJsYo graphics with teletext; Windows font rendering glitch
1102pub fn bits2bedstead (ch: u32) -> char {
1103  let ch =
1104    if 0b111111 < ch {0xEE00}
1105    else if 32 <= ch {0xEE40 + ch - 32}
1106    else if 0 < ch {0xEE00 + ch}
1107    else {0xEE00 + ch};
1108  unsafe {char::from_u32_unchecked (ch)}}
1109
1110/// [Bedstead](https://i.imgur.com/f3myFgM.png) to row-major bits, 2x3
1111pub fn bedstead2bits (ch: char) -> u32 {
1112  let ch = ch as u32;
1113  if 0xEE5F < ch {0}  // Past G1
1114  else if 0xEE40 <= ch {ch - 0xEE40 + 32}
1115  else if 0xEE00 <= ch {ch - 0xEE00}
1116  else {0}}  // Below G1
1117
1118#[test]
1119fn test_bedstead() {
1120  for bits in 0 ..= 0b111111 {
1121    let ch = bits2bedstead (bits);
1122    assert_eq! (bits, bedstead2bits (ch))}}
1123
1124pub fn round_to (decimals: u32, num: f32) -> f32 {
1125  let r = 10u32 .pow (decimals) as f32;
1126  (num * r) .round() / r}
1127
1128pub fn round8 (decimals: u32, num: f64) -> f64 {
1129  let r = 10u32 .pow (decimals) as f64;
1130  (num * r) .round() / r}
1131
1132/// Allows to sort by float, but panics if there's a NaN or infinity
1133#[derive(Clone, Copy, Debug, PartialOrd, PartialEq)]
1134pub struct OrdFloat (pub f64);
1135impl Eq for OrdFloat {}
1136impl Ord for OrdFloat {
1137  fn cmp (&self, other: &Self) -> std::cmp::Ordering {
1138    // cf. https://doc.rust-lang.org/nightly/std/primitive.f64.html#method.total_cmp
1139    self.0.partial_cmp (&other.0) .expect ("!partial_cmp")}}
1140use std::hash::{Hash, Hasher};
1141impl Hash for OrdFloat {
1142  fn hash<H: Hasher> (&self, state: &mut H) {
1143    self.0.to_bits().hash (state)}}
1144impl fmt::Display for OrdFloat {
1145  fn fmt (&self, fm: &mut fmt::Formatter) -> fmt::Result {
1146    self.0.fmt (fm)}}
1147
1148/// Allows to sort by float, but panics if there's a NaN or infinity
1149#[derive(Clone, Copy, Debug, PartialOrd, PartialEq)]
1150pub struct OrdF32 (pub f32);
1151impl Eq for OrdF32 {}
1152impl Ord for OrdF32 {
1153  fn cmp (&self, other: &Self) -> std::cmp::Ordering {
1154    // cf. https://doc.rust-lang.org/nightly/std/primitive.f64.html#method.total_cmp
1155    self.0.partial_cmp (&other.0) .expect ("!partial_cmp")}}
1156impl Hash for OrdF32 {
1157  fn hash<H: Hasher> (&self, state: &mut H) {
1158    self.0.to_bits().hash (state)}}
1159impl fmt::Display for OrdF32 {
1160  fn fmt (&self, fm: &mut fmt::Formatter) -> fmt::Result {
1161    self.0.fmt (fm)}}
1162
1163/*⌥ consider implementing SQLite VARINTs
1164https://sqlite.org/src4/doc/trunk/www/varint.wiki
1165https://softwareengineering.stackexchange.com/questions/455589/sqlite-design-use-of-variable-length-integers-explain-the-design-flaw
1166#[cfg(feature = "re")]
1167pub fn length_coded (by: &[u8]) -> re::Re<(u64, usize)> {  // MySQLClientServerProtocol, #Elements
1168  // cf. https://github.com/hunter-packages/mysql-client/blob/3d95211/sql-common/pack.c#L93, net_store_length
1169  // cf. https://github.com/blackbeam/rust_mysql_common/blob/88ce581/src/io.rs#L347, read_lenenc_int
1170  use fomat_macros::fomat;
1171  if by.is_empty() {fail! ("!length_coded_binary: is_empty")}
1172  if by[0] <= 250 {return re::Re::Ok ((by[0] as u64, 1))}
1173  if by[0] == 251 {return re::Re::Ok ((0, 1))}  // NULL
1174  if by[0] == 252 {  // “value of following 16-bit word”
1175    if by.len() < 3 {fail! ("!length_coded_binary: incomplete 16-bit")}
1176    return re::Re::Ok ((u16::from_le_bytes ([by[1], by[2]]) as u64, 3))}
1177  if by[0] == 253 {  // “value of following 24-bit word”
1178    if by.len() < 4 {fail! ("!length_coded_binary: incomplete 24-bit")}
1179    return re::Re::Ok ((u32::from_le_bytes ([by[1], by[2], by[3], 0]) as u64, 4))}
1180  if by[0] == 254 { // “value of following 64-bit word”
1181    if by.len() < 9 {fail! ("!length_coded_binary: incomplete 64-bit")}
1182    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))}
1183  fail! ("!length_coded_binary: " [by[0]])}
1184*/
1185
1186pub trait AtBool {
1187  /// load with `Ordering::Relaxed`
1188  fn l (&self) -> bool;
1189  /// store with `Ordering::Relaxed`
1190  fn s (&self, val: bool);}
1191impl AtBool for core::sync::atomic::AtomicBool {
1192  fn l (&self) -> bool {
1193    self.load (Ordering::Relaxed)}
1194  fn s (&self, val: bool) {
1195    self.store (val, Ordering::Relaxed)}}
1196
1197pub trait AtI8 {
1198  /// load with `Ordering::Relaxed`
1199  fn l (&self) -> i8;
1200  /// store with `Ordering::Relaxed`
1201  fn s (&self, val: i8);}
1202impl AtI8 for core::sync::atomic::AtomicI8 {
1203  fn l (&self) -> i8 {
1204    self.load (Ordering::Relaxed)}
1205  fn s (&self, val: i8) {
1206    self.store (val, Ordering::Relaxed)}}
1207
1208pub trait AtI32 {
1209  /// swap with `Ordering::Relaxed`
1210  fn cas (&self, current: i32, new: i32) -> Result<i32, i32>;
1211  /// load with `Ordering::Relaxed`
1212  fn l (&self) -> i32;
1213  /// store with `Ordering::Relaxed`
1214  fn s (&self, val: i32);}
1215impl AtI32 for core::sync::atomic::AtomicI32 {
1216  fn cas (&self, current: i32, new: i32) -> Result<i32, i32> {
1217    self.compare_exchange (current, new, Ordering::Relaxed, Ordering::Relaxed)}
1218  fn l (&self) -> i32 {
1219    self.load (Ordering::Relaxed)}
1220  fn s (&self, val: i32) {
1221    self.store (val, Ordering::Relaxed)}}
1222
1223pub trait AtI64 {
1224  /// swap with `Ordering::Relaxed`
1225  fn cas (&self, current: i64, new: i64) -> Result<i64, i64>;
1226  /// load with `Ordering::Relaxed`
1227  fn l (&self) -> i64;
1228  /// store with `Ordering::Relaxed`
1229  fn s (&self, val: i64);}
1230impl AtI64 for core::sync::atomic::AtomicI64 {
1231  fn cas (&self, current: i64, new: i64) -> Result<i64, i64> {
1232    self.compare_exchange (current, new, Ordering::Relaxed, Ordering::Relaxed)}
1233  fn l (&self) -> i64 {
1234    self.load (Ordering::Relaxed)}
1235  fn s (&self, val: i64) {
1236    self.store (val, Ordering::Relaxed)}}
1237
1238pub trait AtUsize {
1239  /// swap with `Ordering::Relaxed`
1240  fn cas (&self, current: usize, new: usize) -> Result<usize, usize>;
1241  /// load with `Ordering::Relaxed`
1242  fn l (&self) -> usize;
1243  /// store with `Ordering::Relaxed`
1244  fn s (&self, val: usize);}
1245impl AtUsize for AtomicUsize {
1246  fn cas (&self, current: usize, new: usize) -> Result<usize, usize> {
1247    self.compare_exchange (current, new, Ordering::Relaxed, Ordering::Relaxed)}
1248  fn l (&self) -> usize {
1249    self.load (Ordering::Relaxed)}
1250  fn s (&self, val: usize) {
1251    self.store (val, Ordering::Relaxed)}}
1252
1253/// Grok long lines with `memchr`. Consider using
1254/// [slice::Split](https://doc.rust-lang.org/nightly/std/slice/struct.Split.html)
1255/// when the lines are short.
1256/// 
1257/// Skips blanks.
1258#[cfg(feature = "memchr")]
1259pub struct LinesIt<'a> {
1260  pub lines: &'a [u8],
1261  pub head: usize,
1262  pub tail: usize}
1263
1264#[cfg(feature = "memchr")]
1265impl<'a> LinesIt<'a> {
1266  pub fn new (lines: &'a [u8]) -> LinesIt<'a> {
1267    let (mut head, mut tail) = (0, lines.len());
1268
1269    loop {
1270      if tail <= head {break}
1271      if lines[head] == b'\n' {head += 1; continue}
1272      break}
1273
1274    loop {
1275      if tail <= head {break}
1276      if lines[tail-1] == b'\n' {tail -= 1; continue}
1277      break}
1278
1279    LinesIt {lines, head, tail}}
1280
1281  /// seek to a line at the given byte `pos`ition
1282  pub fn heads_up (lines: &'a [u8], pos: usize) -> LinesIt<'a> {
1283    let len = lines.len();
1284    if len < pos {
1285      LinesIt {lines, head: len, tail: len}
1286    } else {
1287      LinesIt {lines,
1288        head: memrchr (b'\n', &lines[..pos]) .unwrap_or_default(),
1289        tail: len}}}}
1290
1291#[cfg(feature = "memchr")]
1292impl<'a> Iterator for LinesIt<'a> {
1293  type Item = &'a [u8];
1294  fn next (&mut self) -> Option<Self::Item> {
1295    loop {
1296      if self.tail <= self.head {return None}
1297      if self.lines[self.head] == b'\n' {self.head += 1; continue}
1298      break}
1299    if let Some (mut lf) = memchr (b'\n', &self.lines[self.head .. self.tail]) {
1300      lf += self.head;
1301      let line = &self.lines[self.head .. lf];
1302      self.head = lf + 1;
1303      Some (line)
1304    } else {
1305      let line = &self.lines[self.head .. self.tail];
1306      self.head = self.tail;
1307      Some (line)}}}
1308
1309#[cfg(feature = "memchr")]
1310impl<'a> DoubleEndedIterator for LinesIt<'a> {
1311  fn next_back (&mut self) -> Option<Self::Item> {
1312    loop {
1313      if self.tail <= self.head {return None}
1314      if self.lines[self.tail-1] == b'\n' {self.tail -= 1; continue}
1315      break}
1316    if let Some (mut lf) = memrchr (b'\n', &self.lines[self.head .. self.tail]) {
1317      lf += self.head;
1318      let line = &self.lines[lf + 1 .. self.tail];
1319      self.tail = lf;
1320      Some (line)
1321    } else {
1322      let line = &self.lines[self.head .. self.tail];
1323      self.tail = self.head;
1324      Some (line)}}}
1325
1326/// Pool which `join`s `thread`s on `drop`.
1327#[cfg(all(feature = "crossterm", feature = "fomat-macros", feature = "inlinable_string", feature = "reffers", feature = "re"))]
1328pub mod tpool {
1329  use crate::{any_to_str, IniMutex};
1330  use crate::re::Re;
1331  use inlinable_string::InlinableString;
1332  use reffers::arc::{Strong as StrongA, Ref as RefA, RefMut as RefMutA};
1333  use std::collections::VecDeque;
1334  use std::hint::spin_loop;
1335  use std::panic::{catch_unwind, AssertUnwindSafe};
1336  use std::sync::{Mutex, Condvar};
1337  use std::sync::atomic::{AtomicBool, AtomicI16, Ordering};
1338  use std::thread::{self, JoinHandle};
1339  use std::time::Duration;
1340
1341  struct TJobs {
1342    queue: Mutex<VecDeque<Box<dyn FnOnce() -> Re<()> + Send + 'static>>>,
1343    alarm: Condvar,
1344    running: AtomicI16,
1345    bye: AtomicBool}
1346  impl Default for TJobs {
1347    fn default() -> TJobs {
1348      TJobs {
1349        queue: Mutex::new (VecDeque::new()),
1350        alarm: Condvar::new(),
1351        running: AtomicI16::new (0),
1352        bye: AtomicBool::new (false)}}}
1353
1354  #[derive (Default)]
1355  pub struct TPool {
1356    jobs: StrongA<TJobs>,
1357    pub threads: Vec<(InlinableString, JoinHandle<Re<()>>)>,
1358    pub finalizers: Vec<(InlinableString, Box<dyn FnOnce() -> Re<()> + Send + 'static>)>}
1359
1360  unsafe impl Send for TPool {}
1361  unsafe impl Sync for TPool {}
1362
1363  impl TPool {
1364    /// Add thread to pool.  
1365    /// `false` if `tag` was already in thread pool.
1366    pub fn sponsor (&mut self, tag: InlinableString) -> Re<bool> {
1367      if self.threads.iter().any (|(tagʹ, _)| *tagʹ == tag) {return Re::Ok (false)}
1368      let jobs = self.jobs.clone();
1369      self.threads.push ((tag,
1370        thread::Builder::new().name ("TPool".into()) .spawn (move || -> Re<()> {
1371          loop {
1372            let task = {
1373              let jobs = jobs.get_ref();
1374              let mut queue = jobs.queue.lock()?;
1375              match queue.pop_front() {
1376                Some (j) => {jobs.running.fetch_add (1, Ordering::Relaxed); j}
1377                None if jobs.bye.load (Ordering::Relaxed) => {break Re::Ok(())}
1378                None => {
1379                  let (mut queue, _rc) = jobs.alarm.wait_timeout (queue, Duration::from_secs_f32 (0.31))?;
1380                  if let Some (job) = queue.pop_front() {jobs.running.fetch_add (1, Ordering::Relaxed); job} else {continue}}}};
1381            let rc = catch_unwind (AssertUnwindSafe (task));
1382            let running = jobs.get_ref().running.fetch_sub (1, Ordering::Relaxed);
1383            if running < 0 {log! (a 202, [=running])}
1384            match rc {
1385              Ok (Re::Ok(())) => {}
1386              Ok (Re::Err (err)) => {log! (a 1, (err))}
1387              Err (err) => {log! (a 1, [any_to_str (&*err)])}}}})?));
1388      Re::Ok (true)}
1389
1390    /// Run given callback from one of `sponsor`ed threads.
1391    pub fn post (&self, task: Box<dyn FnOnce() -> Re<()> + Send + Sync + 'static>) -> Re<()> {
1392      let jobs = self.jobs.get_ref();
1393      let mut queue = jobs.queue.lock()?;
1394      queue.push_back (task);
1395      jobs.alarm.notify_one();
1396      Re::Ok(())}
1397
1398    /// Run given callback after pool threads are joined in `drop`.  
1399    /// `false` if non-empty `tag` was already registered.
1400    pub fn fin (&mut self, tag: InlinableString, finalizer: Box<dyn FnOnce() -> Re<()> + Send + Sync + 'static>) -> bool {
1401      if !tag.is_empty() && self.finalizers.iter().any (|(tagʹ, _)| *tagʹ == tag) {false}
1402      else {self.finalizers.push ((tag, finalizer)); true}}
1403
1404    /// Number of jobs queued or running.
1405    pub fn jobsⁿ (&self) -> Re<usize> {
1406      let jobs = self.jobs.get_ref();
1407      let queue = jobs.queue.lock()?;
1408      Re::Ok (queue.len() + jobs.running.load (Ordering::Relaxed) .max (0) as usize)}}
1409
1410  impl Drop for TPool {
1411    fn drop (&mut self) {
1412      { let jobs = self.jobs.get_ref();
1413        jobs.bye.store (true, Ordering::Relaxed);
1414        let _queue = jobs.queue.lock();
1415        jobs.alarm.notify_all(); }  // Flash `bye`
1416      for (_tag, th) in self.threads.drain (..) {
1417        match th.join() {
1418          Ok (Re::Ok(())) => {}
1419          Ok (Re::Err (err)) => {log! (a 1, (err))}
1420          Err (err) => {log! (a 1, [any_to_str (&*err)])}}}
1421      for (tag, finalizer) in self.finalizers.drain (..) {
1422        let rc = catch_unwind (AssertUnwindSafe (finalizer));
1423        match rc {
1424          Ok (Re::Ok(())) => {}
1425          Ok (Re::Err (err)) => {log! (a 1, (tag) "] " (err))}
1426          Err (err) => {log! (a 1, (tag) "] " [any_to_str (&*err)])}}}}}
1427
1428  /// Shared thread pool.
1429  pub static TPOOL: IniMutex<TPool> = IniMutex::none();
1430
1431  /// Post `task` to shared `TPOOL` if available, or run it on current thread otherwise.  
1432  /// Returns `false` if `task` was invoked directly.
1433  /// * `spin` - Try to obtain `TPOOL` this many times before falling back to direct invocation of `task`.
1434  /// * `threads` - Use direct invocation if there is less than the given number of threads in the pool.
1435  pub fn tpost (mut spin: u32, threads: u8, task: Box<dyn FnOnce() -> Re<()> + Send + Sync + 'static>) -> Re<bool> {
1436    loop {
1437      spin -= 1; if spin == 0 {break}
1438      let Ok (pool) = TPOOL.lock() else {spin_loop(); thread::yield_now(); continue};
1439      if pool.threads.len() < threads as usize {break}
1440      pool.post (task)?;
1441      return Re::Ok (true)}
1442    task()?;
1443    Re::Ok (false)}}