1use std::{
2 borrow::Borrow,
3 collections::{
4 btree_map,
5 hash_map::{Entry, OccupiedEntry},
6 BTreeMap, HashMap,
7 },
8 env::VarError,
9 ffi::{OsStr, OsString},
10 fmt::{Debug, Display},
11 fs::create_dir_all,
12 hash::Hash,
13 path::PathBuf,
14 time::Duration,
15};
16
17use anyhow::{anyhow, bail, Context, Result};
18use num::CheckedAdd;
19
20pub fn enum_name<T: Debug>(v: T) -> String {
24 let mut s = format!("{v:?}");
25 if let Some(i) = s.find(|c| c == '(') {
26 s.shrink_to(i);
27 s
28 } else {
29 s
30 }
31}
32
33pub fn autovivify_last<T>(v: &mut Vec<T>, create: impl FnOnce() -> T) -> &mut T {
35 let v: *mut _ = v; if let Some(last) = unsafe { &mut *v }.last_mut() {
37 last
38 } else {
39 unsafe { &mut *v }.push(create());
40 unsafe { &mut *v }.last_mut().unwrap()
41 }
42}
43
44pub fn hashmap_get_mut<'m, K: Eq + Hash, P: Eq + Hash + ?Sized, V>(
62 m: &'m mut HashMap<K, V>,
63 k: &P,
64) -> Result<&'m mut V, &'m mut HashMap<K, V>>
65where
66 K: Borrow<P>,
67{
68 let pm: *mut _ = m;
69 if let Some(v) = unsafe { &mut *pm }.get_mut(k) {
73 Ok(v)
74 } else {
75 Err(unsafe { &mut *pm })
76 }
77}
78
79pub fn btreemap_get_mut<'m, K: Ord, P: Ord + ?Sized, V>(
81 m: &'m mut BTreeMap<K, V>,
82 k: &P,
83) -> Result<&'m mut V, &'m mut BTreeMap<K, V>>
84where
85 K: Borrow<P>,
86{
87 let pm: *mut _ = m;
88 if let Some(v) = unsafe { &mut *pm }.get_mut(k) {
92 Ok(v)
93 } else {
94 Err(unsafe { &mut *pm })
95 }
96}
97
98pub fn hashmap_try_insert<'m, K: Eq + Hash, V>(
104 m: &'m mut HashMap<K, V>,
105 key: K,
106 value: V,
107) -> Result<&mut V, OccupiedEntry<K, V>> {
108 match m.entry(key) {
109 Entry::Occupied(entry) => Err(entry),
110 Entry::Vacant(entry) => Ok(entry.insert(value)),
111 }
112}
113
114pub fn btreemap_try_insert<'m, K: Ord, V>(
119 m: &'m mut BTreeMap<K, V>,
120 key: K,
121 value: V,
122) -> Result<&'m mut V, btree_map::OccupiedEntry<K, V>> {
123 match m.entry(key) {
124 btree_map::Entry::Occupied(entry) => Err(entry),
125 btree_map::Entry::Vacant(entry) => Ok(entry.insert(value)),
126 }
127}
128
129#[macro_export]
133macro_rules! or_return_none {
134 ($e:expr) => {{
135 let res = $e;
136 if let Some(val) = res {
137 val
138 } else {
139 return Ok(None);
140 }
141 }};
142}
143
144pub fn duration_mul_div(orig: Duration, multiplier: u64, divider: u64) -> Option<Duration> {
146 let nanos: u64 = orig
147 .as_nanos()
148 .checked_mul(multiplier as u128)?
149 .checked_div(divider as u128)?
150 .try_into()
151 .ok()?;
152 Some(Duration::from_nanos(nanos))
153}
154
155pub fn debug_stringlikes<S: Display>(v: &[S]) -> Vec<String> {
156 v.iter().map(|s| s.to_string()).collect()
157}
158
159#[macro_export]
163macro_rules! loop_try {
164 ( $($body_parts:tt)* ) => {{
165 let default_error_sleep_duration = Duration::from_millis(500);
166 let mut error_sleep_duration = default_error_sleep_duration;
167 loop {
168 match (|| -> Result<()> { $($body_parts)* })() {
169 Ok(()) => {
170 error_sleep_duration = default_error_sleep_duration;
171 }
172 Err(e) => {
173 eprintln!("loop_try: got error {e:#}, sleeping for \
174 {error_sleep_duration:?}");
175 thread::sleep(error_sleep_duration);
176 error_sleep_duration =
177 $crate::util::duration_mul_div(error_sleep_duration,
178 1200,
179 1000)
180 .unwrap_or(default_error_sleep_duration);
181 }
182 }
183 }
184 }}
185}
186
187#[macro_export]
188macro_rules! try_do {
189 ( $($b:tt)* ) => ( (|| { $($b)* })() )
190}
191
192#[macro_export]
193macro_rules! try_result {
194 ( $($b:tt)* ) => ( (|| -> Result<_, _> { $($b)* })() )
195}
196
197#[macro_export]
198macro_rules! try_option {
199 ( $($b:tt)* ) => ( (|| -> Option<_> { $($b)* })() )
200}
201
202pub fn infinite_sequence<T: CheckedAdd + Copy>(start: T, inc: T) -> impl FnMut() -> T {
204 let mut current = start;
205 move || -> T {
206 let n = current;
207 current = n.checked_add(&inc).expect("number not overflowing");
208 n
209 }
210}
211
212pub fn alphanumber(i: u32) -> String {
213 let mut s = Vec::new();
214 let mut j: i64 = i.into();
215 while j >= 0 {
216 s.push(b'a' + ((j % 26) as u8));
217 j = (j / 26) - 1;
218 }
219 s.reverse();
220 String::from_utf8(s).expect("all ascii")
221}
222
223#[cfg(test)]
224mod tests {
225 use super::*;
226
227 #[test]
228 fn t_alphanumber() {
229 fn t(i: u32, s: &str) {
230 assert_eq!(&alphanumber(i), s);
231 }
232 t(0, "a");
233 t(1, "b");
234 t(26, "aa");
235 t(27, "ab");
236 t(27 * 26 - 1, "zz");
237 t(27 * 26, "aaa");
238 t(27 * 26 + 1, "aab");
239 t(27 * 26 + 26, "aba");
240 }
241}
242
243pub fn osstring_into_string(s: OsString) -> Result<String> {
244 match s.into_string() {
245 Ok(s2) => Ok(s2),
246 Err(s) => bail!("can't properly decode to string {:?}", s.to_string_lossy()),
247 }
248}
249
250pub fn osstr_to_str(s: &OsStr) -> Result<&str> {
251 match s.to_str() {
252 Some(s2) => Ok(s2),
253 None => bail!("can't properly decode to string {:?}", s.to_string_lossy()),
254 }
255}
256
257pub fn program_path() -> Result<String> {
258 let path = std::env::args_os()
259 .into_iter()
260 .next()
261 .ok_or_else(|| anyhow!("missing program executable path in args_os"))?;
262 osstring_into_string(path)
263 .with_context(|| anyhow!("decoding of program executable path failed"))
264}
265
266pub fn program_name() -> Result<String> {
267 let path = std::env::args_os()
268 .into_iter()
269 .next()
270 .ok_or_else(|| anyhow!("missing program executable path in args_os"))?;
271 let pb = PathBuf::from(path);
272 let fname = pb
273 .file_name()
274 .ok_or_else(|| anyhow!("cannot get file name from path {:?}", pb.to_string_lossy()))?;
275 Ok(osstr_to_str(fname)
276 .with_context(|| anyhow!("cannot decode file name {:?}", fname.to_string_lossy()))?
277 .to_string())
278}
279
280pub fn log_basedir() -> Result<String> {
281 let logbasedir = format!(
282 "{}/log/{}",
283 std::env::var("HOME").with_context(|| anyhow!("can't get HOME env var"))?,
284 program_name()?
285 );
286 create_dir_all(&logbasedir)
288 .with_context(|| anyhow!("can't create log base directory {:?}", logbasedir))?;
289 Ok(logbasedir)
290}
291
292pub fn getenv_or(name: &str, fallbackvalue: Option<&str>) -> Result<String> {
296 match std::env::var(name) {
297 Ok(s) => Ok(s),
298 Err(e) => match e {
299 VarError::NotPresent => match fallbackvalue {
300 Some(v) => Ok(v.to_string()),
301 None => bail!(
302 "{name:?} env var is missing and \
303 no default provided"
304 ),
305 },
306 VarError::NotUnicode(_) => bail!("{name:?} env var is not unicode"),
307 },
308 }
309}
310
311pub fn getenv(name: &str) -> Result<Option<String>> {
314 match std::env::var(name) {
315 Ok(s) => Ok(Some(s)),
316 Err(e) => match e {
317 VarError::NotPresent => Ok(None),
318 VarError::NotUnicode(_) => bail!("{name:?} env var is not unicode"),
319 },
320 }
321}
322
323pub fn xgetenv(name: &str) -> Result<String> {
326 getenv(name)?.ok_or_else(|| anyhow!("missing env var {name:?}"))
327}
328
329pub fn getenv_bool(name: &str) -> Result<bool> {
333 if let Some(s) = getenv(name)? {
334 match &*s {
335 "" => Ok(true), "1" => Ok(true),
337 "true" => Ok(true),
338 "yes" => Ok(true),
339 "0" => Ok(false),
340 "false" => Ok(false),
341 "no" => Ok(false),
342 _ => bail!("boolean env var {name:?} has invalid contents {s:?}"),
343 }
344 } else {
345 Ok(false)
346 }
347}
348
349#[macro_export]
358macro_rules! oerr {
359 { $var:expr, $e:expr } => {
360 if let Some(v) = &mut $var {
361 v
362 } else {
363 $var = Some($e);
364 $var.as_mut().unwrap()
365 }
366 }
367}