#![allow(unknown_lints, uncommon_codepoints)]
#![allow(unused_imports)]
#![cfg_attr(feature = "nightly", feature(test))]
#![cfg_attr(feature = "re", feature(try_trait_v2))]
#![cfg_attr(feature = "re", feature(never_type))]
extern crate libc;
#[cfg(feature = "crossterm")] extern crate crossterm;
use core::any::Any;
use core::arch::asm;
use core::str::{from_utf8_unchecked}; use core::sync::atomic::{AtomicI8, AtomicUsize, Ordering};
use std::fs;
use std::io::{self, Read};
use std::os::raw::c_int;
use std::path::Path;
use std::process::{Command, Stdio};
use std::sync::Mutex;
use std::time::{Duration, SystemTime, UNIX_EPOCH};
#[cfg(test)] use std::thread::sleep;
pub fn filename<'a> (path: &'a str) -> &'a str {
let name = match path.rfind (|ch| ch == '/' || ch == '\\') {
Some (ofs) => &path[ofs+1..],
None => path};
if name.ends_with (".rs") {&name[0 .. name.len() - 3]} else {name}}
#[macro_export] macro_rules! try_s {
($e: expr) => {match $e {
Ok (ok) => ok,
Err (err) => {return Err (format! ("{}:{}] {}", $crate::filename (file!()), line!(), err));}}}}
#[macro_export] macro_rules! try_f {
($e: expr) => {match $e {
Ok (ok) => ok,
Err (err) => {return Err (From::from (format! ("{}:{}] {}", $crate::filename (file!()), line!(), err)));}}}}
#[macro_export] macro_rules! try_sp {
($e: expr) => {match $e {&Ok (ref ok) => ok,
&Err (ref err) => {return Err (From::from (format! ("{}:{}] {:?}", $crate::filename (file!()), line!(), err)));}}}}
#[macro_export] macro_rules! try_fu {
($e: expr) => {match $e {
Ok (ok) => ok,
Err (err) => {return Box::new (futures01::future::err (From::from (err)))}}}}
#[macro_export] macro_rules! try_fus {
($e: expr) => {match $e {
Ok (ok) => ok,
Err (err) => {return Box::new (futures01::future::err (ERRL! ("{}", err)))}}}}
#[macro_export] macro_rules! ERRL {
($format: expr, $($args: tt)+) => {format! (concat! ("{}:{}] ", $format), $crate::filename (file!()), line!(), $($args)+)};
($format: expr) => {format! (concat! ("{}:{}] ", $format), $crate::filename (file!()), line!())}}
#[macro_export] macro_rules! ERR {
($format: expr, $($args: tt)+) => {Err (ERRL! ($format, $($args)+))};
($format: expr) => {Err (ERRL! ($format))}}
#[cfg(feature = "base62")]
pub mod base62;
#[cfg(feature = "base62j")]
pub mod base62j;
#[cfg(all(feature = "base91", feature = "nightly"))]
pub mod base91;
#[cfg(feature = "re")]
pub mod re;
#[cfg(feature = "lines")]
pub mod lines;
#[cfg(all(feature = "crossterm", not(target_arch = "wasm32")))]
fn isatty (fd: c_int) -> c_int {unsafe {libc::isatty (fd)}}
#[cfg(all(feature = "crossterm", target_arch = "wasm32"))]
fn isatty (_fd: c_int) -> c_int {0}
#[cfg(feature = "crossterm")]
static mut STATUS_LINE: Mutex<String> = Mutex::new (String::new());
#[cfg(feature = "crossterm")]
pub struct IsTty {pub is_tty: AtomicI8}
#[cfg(feature = "crossterm")]
impl core::ops::Deref for IsTty {
type Target = bool;
fn deref (&self) -> &Self::Target {
let mut is_tty = self.is_tty.load (Ordering::Relaxed);
if is_tty == 0 {
is_tty = if isatty (1) != 0 {1} else {-1};
self.is_tty.store (is_tty, Ordering::Relaxed)}
if is_tty == 1 {&true} else {&false}}}
#[cfg(feature = "crossterm")]
pub static ISATTY: IsTty = IsTty {is_tty: AtomicI8::new (0)};
#[cfg(feature = "crossterm")]
pub static STATUS_LINE_LM: AtomicUsize = AtomicUsize::new (0);
#[cfg(feature = "crossterm")]
pub fn status_line_lm() -> u64 {STATUS_LINE_LM.load (Ordering::Relaxed) as u64}
#[cfg(feature = "crossterm")]
pub fn status_line_lm0() {STATUS_LINE_LM.store (0, Ordering::Relaxed)}
#[cfg(feature = "crossterm")]
fn delete_line (stdout: &mut io::Stdout) {
use crossterm::{terminal, QueueableCommand};
let _ = stdout.queue (terminal::Clear (terminal::ClearType::UntilNewLine));}
#[cfg(all(feature = "crossterm"))]
pub fn status_line (file: &str, line: u32, status: String) {
use crossterm::{QueueableCommand, cursor};
use io::{stdout, Write};
use std::collections::hash_map::DefaultHasher;
use std::hash::Hasher;
if let Ok (mut status_line) = unsafe {STATUS_LINE.lock()} {
let mut stdout = stdout();
let old_hash = {let mut hasher = DefaultHasher::new(); hasher.write (status_line.as_bytes()); hasher.finish()};
status_line.clear();
use std::fmt::Write;
let _ = write! (&mut *status_line, "{}:{}] {}", filename (file), line, status);
let new_hash = {let mut hasher = DefaultHasher::new(); hasher.write (status_line.as_bytes()); hasher.finish()};
if old_hash != new_hash {
STATUS_LINE_LM.store (now_ms() as usize, Ordering::Relaxed);
match crossterm::terminal::size() {
Ok ((w, _)) if status_line.chars().count() >= w as usize => {
let mut tmp = String::with_capacity (w as usize - 1);
for ch in status_line.chars().take (w as usize - 1) {tmp.push (ch)}
let _ = stdout.write (tmp.as_bytes());},
_ => {let _ = stdout.write (status_line.as_bytes());}};
delete_line (&mut stdout);
let _ = stdout.queue (cursor::MoveToColumn (0));
let _ = stdout.flush();}}}
#[cfg(feature = "crossterm")]
pub fn status_line_clear() {
use io::{stdout, Write};
if let Ok (mut status_line) = unsafe {STATUS_LINE.lock()} {
if *ISATTY && !status_line.is_empty() {
let mut stdout = stdout();
STATUS_LINE_LM.store (now_ms() as usize, Ordering::Relaxed);
status_line.clear();
delete_line (&mut stdout);
let _ = stdout.flush();}}}
#[cfg(feature = "crossterm")]
pub fn with_status_line (code: &dyn Fn()) {
use crossterm::{QueueableCommand, cursor};
use io::{stdout, Write};
if let Ok (status_line) = unsafe {STATUS_LINE.lock()} {
if !*ISATTY || status_line.is_empty() {
code()
} else {
let mut stdout = stdout();
delete_line (&mut stdout);
let _ = stdout.flush(); code();
let _ = stdout.write (status_line.as_bytes());
let _ = stdout.queue (cursor::MoveToColumn (0));
let _ = stdout.flush();}}}
#[cfg(feature = "crossterm")]
#[test] fn test_status_line() {
with_status_line (&|| println! ("hello world"));}
#[cfg(all(feature = "crossterm", feature = "chrono"))]
pub fn short_log_time (ms: u64)
-> chrono::format::DelayedFormat<chrono::format::strftime::StrftimeItems<'static>> {
use chrono::{Local, LocalResult, TimeZone};
if let Some (time) = Local.timestamp_millis_opt (ms as i64) .earliest() {
time.format ("%d %H:%M:%S")
} else {
Local::now().format ("00 00:00:00")}}
#[cfg(all(feature = "crossterm", feature = "chrono", feature = "fomat-macros"))]
#[macro_export] macro_rules! log {
(q $command: expr, $($args: tt)+) => {{
$crate::with_status_line (&|| {
use crossterm::QueueableCommand;
use fomat_macros::{wite, fomat};
use std::io::Write;
let mut stdout = std::io::stdout();
let _ = stdout.queue ($command);
let _ = wite! (&mut stdout,
($crate::short_log_time ($crate::now_ms())) ' '
($crate::filename (file!())) ':' (line!()) "] "
$($args)+ '\n');
let _ = stdout.queue (crossterm::style::ResetColor);
let _ = stdout.flush();})}};
(c $color: expr, $($args: tt)+) => {
log! (q crossterm::style::SetForegroundColor ($color), $($args)+)};
(a $ansi: expr, $($args: tt)+) => {
log! (q crossterm::style::SetForegroundColor (
crossterm::style::Color::AnsiValue ($ansi)), $($args)+)};
($($args: tt)+) => {{
$crate::with_status_line (&|| {
use fomat_macros::{pintln, fomat};
pintln! (
($crate::short_log_time ($crate::now_ms())) ' '
($crate::filename (file!())) ':' (line!()) "] "
$($args)+);})}};}
#[cfg(all(feature = "crossterm", feature = "chrono", feature = "fomat-macros"))]
#[test] fn test_log() {
log! ([= 2 + 2])}
#[macro_export] macro_rules! gstring {($array: ident, $code: block) => {{
let end = {
let mut $array = ::std::io::Cursor::new (&mut $array[..]);
let $array = &mut $array;
$code;
$array.position() as usize};
let s = unsafe {::core::str::from_utf8_unchecked (&$array[0..end])};
s}}}
#[cfg(feature = "fomat-macros")]
#[macro_export] macro_rules! ifomat {
($($args: tt)+) => ({
use inlinable_string::{InlinableString, StringExt};
use std::fmt::Write as FmtWrite;
let mut is = InlinableString::new();
wite! (&mut is, $($args)+) .expect ("!wite");
is})}
#[cfg(feature = "fomat-macros")]
#[macro_export] macro_rules! iso8601z {
($date_or_time: expr) => {{
let sufff = ($date_or_time.len() as i32 - 10) .max (0) as usize;
ifomat! (($date_or_time) (&"T00:00:00Z"[sufff..]))}}}
#[cfg(feature = "fomat-macros")]
#[macro_export] macro_rules! iso8601toL {($short: expr) => {
Local.from_local_datetime (&(DateTime::parse_from_rfc3339 (&iso8601z! ($short))?) .naive_utc()) .earliest()?}}
pub fn netstring (at: &[u8]) -> Result<(&[u8], &[u8]), String> {
let length_end = match at.iter().position (|&ch| ch < b'0' || ch > b'9') {Some (l) if l > 0 => l, _ => return ERR! ("No len.")};
match at.get (length_end) {Some (&ch) if ch == b':' => (), _ => return ERR! ("No colon.")};
let length = unsafe {from_utf8_unchecked (&at[0 .. length_end])};
let length: usize = try_s! (length.parse());
let bulk_pos = 0 + length_end + 1;
let bulk_end = bulk_pos + length;
match at.get (bulk_end) {Some (&ch) if ch == b',' => (), _ => return ERR! ("No comma.")}
Ok ((&at[bulk_pos .. bulk_end], &at[bulk_end + 1 ..]))}
#[cfg(unix)]
pub fn with_hostname (visitor: &mut dyn FnMut (&[u8])) -> Result<(), std::io::Error> {
use libc::{size_t, gethostname}; use std::ffi::CStr;
let mut buf = [0; 128];
let rc = unsafe {gethostname (buf.as_mut_ptr(), (buf.len() - 1) as size_t)};
if rc == 0 {
let cs = unsafe {CStr::from_ptr (buf.as_ptr())};
Ok (visitor (cs.to_bytes()))
} else {
Err (io::Error::last_os_error())}}
#[cfg(unix)] #[test] fn test_hostname() {
let mut hostname = String::new();
with_hostname (&mut |bytes| hostname = String::from_utf8_lossy (bytes) .into_owned()) .unwrap();}
pub fn slurp (path: &dyn AsRef<Path>) -> Vec<u8> {
let mut file = match fs::File::open (path) {
Ok (f) => f,
Err (ref err) if err.kind() == io::ErrorKind::NotFound => return Vec::new(),
Err (err) => panic! ("Can't open {:?}: {}", path.as_ref(), err)};
let mut buf = Vec::new();
file.read_to_end (&mut buf) .expect ("!read");
buf}
pub fn slurp_prog (command: &str) -> Result<String, String> {
let output = match Command::new ("dash") .arg ("-c") .arg (command) .output() {
Ok (output) => output,
Err (ref err) if err.kind() == io::ErrorKind::NotFound => { try_s! (Command::new ("sh") .arg ("-c") .arg (command) .output())},
Err (err) => return ERR! ("{}", err)};
let combined_output: String = if output.stderr.is_empty() {
try_s! (String::from_utf8 (output.stdout))
} else if output.stdout.is_empty() {
try_s! (String::from_utf8 (output.stderr))
} else {
let mut buf = String::with_capacity (output.stderr.len() + output.stdout.len());
buf.push_str (try_s! (std::str::from_utf8 (&output.stderr[..])));
buf.push_str (try_s! (std::str::from_utf8 (&output.stdout[..])));
buf};
if output.status.success() {Ok (combined_output)} else {Err (combined_output)}}
#[test] fn test_slurp_prog() {
if cfg! (windows) {println! ("Skipping as dash might be missing on Windows sometimes"); return}
let foo = match slurp_prog ("echo foo") {Ok (foo) => foo, Err (err) => panic! ("{}", err)};
assert_eq! (foo.trim(), "foo");}
pub fn cmd (cmd: &str) -> Result<(), String> {
println! ("$ {}", cmd);
let status = try_s! (Command::new ("dash") .arg ("-c") .arg (cmd) .stdout (Stdio::inherit()) .stderr (Stdio::inherit()) .status());
if !status.success() {Err (format! ("Command returned an error status: {}", status))} else {Ok(())}}
pub fn any_to_str<'a> (message: &'a dyn Any) -> Option<&'a str> {
if let Some (message) = message.downcast_ref::<&str>() {return Some (message)}
if let Some (message) = message.downcast_ref::<String>() {return Some (&message[..])}
return None}
pub fn duration_to_float (duration: Duration) -> f64 {
duration.as_secs() as f64 + ((duration.subsec_nanos() as f64) / 1000000000.0)}
#[cfg(feature = "chrono")]
pub fn dtl2float (dt: chrono::DateTime<chrono::Local>) -> f64 {
dt.timestamp() as f64 + ((dt.timestamp_subsec_nanos() as f64) / 1000000000.0)}
pub fn ms2sec (ms: u64) -> f64 {
(ms / 1000) as f64 + ((ms % 1000) as f64 / 1000.0)}
pub fn now_float() -> f64 {
let now = SystemTime::now().duration_since (UNIX_EPOCH) .expect ("!duration_since");
duration_to_float (now)}
#[test] fn test_now_float() {
let now = SystemTime::now().duration_since (UNIX_EPOCH) .expect ("!duration_since") .as_secs();
let t1 = now_float();
assert_eq! (now, t1 as u64);
sleep (Duration::from_millis (100));
let t2 = now_float();
let delta = t2 - t1;
assert! (delta >= 0.098 && delta <= 0.150, "delta: {}", delta);}
pub fn duration_to_ms (duration: Duration) -> u64 {
duration.as_secs() * 1000 + (duration.subsec_nanos() / 1000000) as u64}
pub fn now_ms() -> u64 {
let now = SystemTime::now().duration_since (UNIX_EPOCH) .expect ("!duration_since");
duration_to_ms (now)}
#[test] fn test_now_ms() {
let t1 = now_ms();
sleep (Duration::from_millis (100));
let t2 = now_ms();
let delta = t2 - t1;
assert! (delta >= 98 && delta <= 150, "delta: {}", delta);}
pub fn last_modified_sec (path: &dyn AsRef<Path>) -> Result<f64, String> {
let meta = match path.as_ref().metadata() {
Ok (m) => m,
Err (ref err) if err.kind() == std::io::ErrorKind::NotFound => return Ok (0.),
Err (err) => return ERR! ("{}", err)};
let lm = try_s! (meta.modified());
let lm = duration_to_float (try_s! (lm.duration_since (UNIX_EPOCH)));
Ok (lm)}
#[cfg(all(feature = "nightly", feature = "rdtsc"))]
pub fn rdtsc() -> u64 {
unsafe {
let mut low: u32; let mut high: u32;
asm! ("rdtsc", lateout ("eax") low, lateout ("edx") high, options (nomem, nostack));
((high as u64) << 32) | (low as u64)}}
#[cfg(all(feature = "nightly", feature = "rdtsc"))]
#[test] fn test_rdtsc() {
assert! (rdtsc() != rdtsc())}
pub struct FileLock<'a> {
pub lock_path: &'a dyn AsRef<Path>,
pub ttl_sec: f64,
pub file: std::fs::File}
impl<'a> FileLock<'a> {
pub fn lock (lock_path: &'a dyn AsRef<Path>, ttl_sec: f64) -> Result<Option<FileLock<'a>>, String> {
let mut cycle = 0u8;
loop {
if cycle > 1 {break Ok (None)} cycle += 1;
let mut fo = std::fs::OpenOptions::new();
match fo.read (true) .write (true) .create_new (true) .open (lock_path.as_ref()) {
Ok (file) => break Ok (Some (FileLock {lock_path, ttl_sec, file})),
Err (ref ie) if ie.kind() == std::io::ErrorKind::AlreadyExists => {
let lm = match last_modified_sec (lock_path) {
Ok (lm) => lm,
Err (ie) => break ERR! ("Error checking {:?}: {}", lock_path.as_ref(), ie)};
if lm == 0. {continue} if now_float() - lm > ttl_sec {
if let Err (err) = std::fs::remove_file (lock_path.as_ref()) {break ERR! ("Error removing {:?}: {}", lock_path.as_ref(), err)}
continue}
break Ok (None)},
Err (ie) => break ERR! ("Error creating {:?}: {}", lock_path.as_ref(), ie)}}}
#[cfg(target_os = "linux")]
pub fn touch (&self) -> Result<(), String> {
let ts = libc::timespec {tv_sec: 0, tv_nsec: libc::UTIME_NOW};
let times = [ts, ts];
use std::os::unix::io::AsRawFd;
let rc = unsafe {libc::futimens (self.file.as_raw_fd(), ×[0])};
if rc != 0 {
let err = std::io::Error::last_os_error();
return ERR! ("Can't touch {:?}: {}", self.lock_path.as_ref(), err)}
Ok(())}}
impl<'a> Drop for FileLock<'a> {
fn drop (&mut self) {
let _ = std::fs::remove_file (self.lock_path);}}
impl<'a> std::fmt::Debug for FileLock<'a> {
fn fmt (&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write! (f, "FileLock ({:?}, {})", self.lock_path.as_ref(), self.ttl_sec)}}
#[derive(Debug)]
pub struct ProcEn {
pub name: String,
pub path: std::path::PathBuf,
pub cmdline: Vec<String>}
impl ProcEn {
pub fn pid (&self) -> Option<u32> {
if let Some (file_name) = self.path.file_name() {
if let Some (file_name) = file_name.to_str() {
if let Ok (pid) = file_name.parse() {
return Some (pid)}}}
None}}
pub struct ProcIt {read_dir: std::fs::ReadDir}
impl ProcIt {
pub fn new() -> ProcIt {
ProcIt {
read_dir: match Path::new ("/proc") .read_dir() {Ok (it) => it, Err (err) => panic! ("!proc: {}", err)}}}}
impl Iterator for ProcIt {
type Item = ProcEn;
fn next (&mut self) -> Option<ProcEn> {
match self.read_dir.next() {
None => return None,
Some (Err (err)) => panic! ("ProcIt] !read_dir: {}", err),
Some (Ok (proc_en)) => {
let file_type = match proc_en.file_type() {
Ok (ft) => ft,
Err (err) => {
if matches! (err.kind(), io::ErrorKind::NotFound) {
return self.next()
} else {
panic! ("!file_type ({:?}): {}", proc_en.path(), err)}}};
if !file_type.is_dir() {return self.next()}
let name = proc_en.file_name();
let name = match name.to_str() {Some (name) => name, None => panic! ("ProcIt] !to_str")};
if !name.as_bytes().iter().all (|&b| b >= b'0' && b <= b'9') { return self.next()}
let path = proc_en.path();
let cmdline = String::from_utf8 (slurp (&path.join ("cmdline"))) .expect ("!from_utf8"); if cmdline.is_empty() {return self.next()}
Some (ProcEn {name: name.into(), path: path, cmdline: cmdline.split ('\0') .map (String::from) .collect()})}}}}
pub mod oneshot {
use std::panic::AssertUnwindSafe;
use std::sync::{Arc, Condvar, Mutex};
pub struct Sender<T> (Arc<Mutex<Option<T>>>, Arc<AssertUnwindSafe<Condvar>>);
impl<T> Sender<T> {
pub fn send (self, v: T) {
{ let arc_mutex = self.0; { let lr = arc_mutex.lock();
if let Ok (mut lock) = lr {*lock = Some (v)} } }
self.1.notify_one()}}
pub struct Receiver<T> (Arc<Mutex<Option<T>>>, Arc<AssertUnwindSafe<Condvar>>);
impl<T> Receiver<T> {
pub fn recv (self) -> Result<T, String> {
let mut arc_mutex = self.0;
let arc_condvar = self.1;
loop {
match Arc::try_unwrap (arc_mutex) {
Ok (mutex) => {
if let Some (value) = try_s! (mutex.into_inner()) {return Ok (value)}
else {return ERR! ("recv] Sender gone without providing a value")}},
Err (am) => {
arc_mutex = am;
let locked_value = try_s! (arc_mutex.lock());
if locked_value.is_none() {let _locked_value = try_s! (arc_condvar.wait (locked_value));}}}}}}
pub fn oneshot<T>() -> (Sender<T>, Receiver<T>) {
let arc_mutex = Arc::new (Mutex::new (None));
let arc_condvar = Arc::new (AssertUnwindSafe (Condvar::new()));
(Sender (arc_mutex.clone(), arc_condvar.clone()), Receiver (arc_mutex, arc_condvar))}}
#[test] fn test_oneshot() {
let (tx, rx) = oneshot::oneshot();
std::thread::spawn (|| tx.send (42));
assert_eq! (42, rx.recv().expect("!recv"))}
pub fn binprint (bin: &[u8], blank: u8) -> String {
let mut bin: Vec<u8> = bin.into();
for ch in bin.iter_mut() {if *ch < 0x20 || *ch >= 0x7F {*ch = blank}}
unsafe {String::from_utf8_unchecked (bin)}}
pub fn bits2bedstead (ch: u32) -> char {
let ch =
if 0b111111 < ch {0xEE00}
else if 32 <= ch {0xEE40 + ch - 32}
else if 0 < ch {0xEE00 + ch}
else {0xEE00 + ch};
unsafe {char::from_u32_unchecked (ch)}}
pub fn bedstead2bits (ch: char) -> u32 {
let ch = ch as u32;
if 0xEE5F < ch {0} else if 0xEE40 <= ch {ch - 0xEE40 + 32}
else if 0xEE00 <= ch {ch - 0xEE00}
else {0}} #[test]
fn test_bedstead() {
for bits in 0 ..= 0b111111 {
let ch = bits2bedstead (bits);
assert_eq! (bits, bedstead2bits (ch))}}
pub fn round_to (decimals: u32, num: f32) -> f32 {
let r = 10u32 .pow (decimals) as f32;
(num * r) .round() / r}
#[derive(Clone, Copy, Debug, PartialOrd, PartialEq)]
pub struct OrdFloat (pub f64);
impl Eq for OrdFloat {}
impl Ord for OrdFloat {
fn cmp (&self, other: &Self) -> std::cmp::Ordering {
self.0.partial_cmp (&other.0) .expect ("!partial_cmp")}}
use std::hash::{Hash, Hasher};
impl Hash for OrdFloat {
fn hash<H: Hasher> (&self, state: &mut H) {
self.0.to_bits().hash (state)}}
impl std::fmt::Display for OrdFloat {
fn fmt (&self, fm: &mut std::fmt::Formatter) -> std::fmt::Result {
self.0.fmt (fm)}}