use std::{hash::Hash,
collections::{HashMap, hash_map::{Entry, OccupiedEntry}, BTreeMap, btree_map},
borrow::Borrow,
time::Duration,
fmt::{Display, Debug},
ffi::{OsString, OsStr},
path::PathBuf,
fs::create_dir_all, env::VarError};
use anyhow::{Result, anyhow, Context, bail};
use num::CheckedAdd;
pub fn enum_name<T: Debug>(v: T) -> String {
let mut s = format!("{v:?}");
if let Some(i) = s.find(|c| c == '(') {
s.shrink_to(i);
s
} else {
s
}
}
pub fn autovivify_last<T>(v: &mut Vec<T>, create: impl FnOnce() -> T) -> &mut T {
let v : *mut _ = v; if let Some(last) = unsafe{&mut *v}.last_mut() {
last
} else {
unsafe{&mut *v}.push(create());
unsafe{&mut *v}.last_mut().unwrap()
}
}
pub fn hashmap_get_mut<'m, K: Eq + Hash, P: Eq + Hash + ?Sized, V>(
m: &'m mut HashMap<K, V>,
k: &P,
) -> Result<&'m mut V,
&'m mut HashMap<K, V>>
where K: Borrow<P>
{
let pm: *mut _ = m;
if let Some(v) = unsafe{&mut *pm}.get_mut(k) {
Ok(v)
} else {
Err(unsafe{&mut *pm})
}
}
pub fn btreemap_get_mut<'m, K: Ord, P: Ord + ?Sized, V>(
m: &'m mut BTreeMap<K, V>,
k: &P,
) -> Result<&'m mut V,
&'m mut BTreeMap<K, V>>
where K: Borrow<P>
{
let pm: *mut _ = m;
if let Some(v) = unsafe{&mut *pm}.get_mut(k) {
Ok(v)
} else {
Err(unsafe{&mut *pm})
}
}
pub fn hashmap_try_insert<'m, K: Eq + Hash, V>(
m: &'m mut HashMap<K, V>,
key: K,
value: V
) -> Result<&mut V, OccupiedEntry<K, V>>
{
match m.entry(key) {
Entry::Occupied(entry) => Err(entry),
Entry::Vacant(entry) => Ok(entry.insert(value)),
}
}
pub fn btreemap_try_insert<'m, K: Ord, V>(
m: &'m mut BTreeMap<K, V>,
key: K,
value: V
) -> Result<&'m mut V, btree_map::OccupiedEntry<K, V>>
{
match m.entry(key) {
btree_map::Entry::Occupied(entry) => Err(entry),
btree_map::Entry::Vacant(entry) => Ok(entry.insert(value)),
}
}
#[macro_export]
macro_rules! or_return_none {
($e:expr) => {{
let res = $e;
if let Some(val) = res {
val
} else {
return Ok(None)
}
}}
}
pub fn duration_mul_div(orig: Duration, multiplier: u64, divider: u64)
-> Option<Duration>
{
let nanos: u64 = orig.as_nanos().checked_mul(multiplier as u128)?
.checked_div(divider as u128)?
.try_into().ok()?;
Some(Duration::from_nanos(nanos))
}
pub fn debug_stringlikes<S: Display>(v: &[S]) -> Vec<String> {
v.iter().map(|s| s.to_string()).collect()
}
#[macro_export]
macro_rules! loop_try {
( $($body_parts:tt)* ) => {{
let default_error_sleep_duration = Duration::from_millis(500);
let mut error_sleep_duration = default_error_sleep_duration;
loop {
match (|| -> Result<()> { $($body_parts)* })() {
Ok(()) => {
error_sleep_duration = default_error_sleep_duration;
}
Err(e) => {
eprintln!("loop_try: got error {e:#}, sleeping for \
{error_sleep_duration:?}");
thread::sleep(error_sleep_duration);
error_sleep_duration =
$crate::util::duration_mul_div(error_sleep_duration,
1200,
1000)
.unwrap_or(default_error_sleep_duration);
}
}
}
}}
}
#[macro_export]
macro_rules! try_do {
( $($b:tt)* ) => ( (|| { $($b)* })() )
}
#[macro_export]
macro_rules! try_result {
( $($b:tt)* ) => ( (|| -> Result<_, _> { $($b)* })() )
}
#[macro_export]
macro_rules! try_option {
( $($b:tt)* ) => ( (|| -> Option<_> { $($b)* })() )
}
pub fn infinite_sequence<T: CheckedAdd + Copy>(
start: T,
inc: T
) -> impl FnMut() -> T {
let mut current = start;
move || -> T {
let n = current;
current = n.checked_add(&inc).expect("number not overflowing");
n
}
}
pub fn alphanumber(i: u32) -> String {
let mut s = Vec::new();
let mut j: i64 = i.into();
while j >= 0 {
s.push(b'a' + ((j % 26) as u8));
j = (j / 26) - 1;
}
s.reverse();
String::from_utf8(s).expect("all ascii")
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn t_alphanumber() {
fn t(i: u32, s: &str) {
assert_eq!(&alphanumber(i), s);
}
t(0, "a");
t(1, "b");
t(26, "aa");
t(27, "ab");
t(27*26-1, "zz");
t(27*26, "aaa");
t(27*26+1, "aab");
t(27*26+26, "aba");
}
}
pub fn osstring_into_string(s: OsString) -> Result<String> {
match s.into_string() {
Ok(s2) => Ok(s2),
Err(s) => bail!("can't properly decode to string {:?}",
s.to_string_lossy())
}
}
pub fn osstr_to_str(s: &OsStr) -> Result<&str> {
match s.to_str() {
Some(s2) => Ok(s2),
None => bail!("can't properly decode to string {:?}",
s.to_string_lossy())
}
}
pub fn program_path() -> Result<String> {
let path = std::env::args_os().into_iter().next().ok_or_else(
|| anyhow!("missing program executable path in args_os"))?;
osstring_into_string(path).with_context(
|| anyhow!("decoding of program executable path failed"))
}
pub fn program_name() -> Result<String> {
let path = std::env::args_os().into_iter().next().ok_or_else(
|| anyhow!("missing program executable path in args_os"))?;
let pb = PathBuf::from(path);
let fname = pb.file_name().ok_or_else(|| anyhow!("cannot get file name from path {:?}",
pb.to_string_lossy()))?;
Ok(osstr_to_str(fname).with_context(
|| anyhow!("cannot decode file name {:?}",
fname.to_string_lossy()))?
.to_string())
}
pub fn log_basedir() -> Result<String> {
let logbasedir = format!("{}/log/{}",
std::env::var("HOME").with_context(
|| anyhow!("can't get HOME env var"))?,
program_name()?);
create_dir_all(&logbasedir).with_context(
|| anyhow!("can't create log base directory {:?}",
logbasedir))?;
Ok(logbasedir)
}
pub fn getenv_or(name: &str, fallbackvalue: Option<&str>) -> Result<String> {
match std::env::var(name) {
Ok(s) => Ok(s),
Err(e) => match e {
VarError::NotPresent =>
match fallbackvalue {
Some(v) => Ok(v.to_string()),
None => bail!("{name:?} env var is missing and \
no default provided"),
},
VarError::NotUnicode(_) => bail!("{name:?} env var is not unicode"),
}
}
}
pub fn getenv(name: &str) -> Result<Option<String>> {
match std::env::var(name) {
Ok(s) => Ok(Some(s)),
Err(e) => match e {
VarError::NotPresent => Ok(None),
VarError::NotUnicode(_) => bail!("{name:?} env var is not unicode"),
}
}
}
pub fn xgetenv(name: &str) -> Result<String> {
getenv(name)?.ok_or_else(
|| anyhow!("missing env var {name:?}"))
}
pub fn getenv_bool(name: &str) -> Result<bool> {
if let Some(s) = getenv(name)? {
match &*s {
"" => Ok(true), "1" => Ok(true),
"true" => Ok(true),
"yes" => Ok(true),
"0" => Ok(false),
"false" => Ok(false),
"no" => Ok(false),
_ => bail!("boolean env var {name:?} has invalid contents {s:?}")
}
} else {
Ok(false)
}
}
#[macro_export]
macro_rules! oerr {
{ $var:expr, $e:expr } => {
if let Some(v) = &mut $var {
v
} else {
$var = Some($e);
$var.as_mut().unwrap()
}
}
}