use std::{
borrow::Borrow,
collections::{
btree_map,
hash_map::{Entry, OccupiedEntry},
BTreeMap, HashMap,
},
env::VarError,
ffi::{OsStr, OsString},
fmt::{Debug, Display},
fs::create_dir_all,
hash::Hash,
path::PathBuf,
time::Duration,
};
use anyhow::{anyhow, bail, Context, Result};
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()
}
}
}