utilz_rs/
lib.rs

1//! # Utilz
2//!
3//! **Ergonomic utility traits for Rust with zero dependencies by default.**
4//!
5//! `utilz` provides a curated set of extension traits for common Rust types like
6//! `Option`, `Result`, `Vec`, `bool`, `&str`, `HashMap`, and more — to help you
7//! write cleaner, shorter, and more expressive code.
8//!
9//! ---
10//!
11//! ## Highlights
12//!
13//! - **`Log`** – Simple in-memory logger with optional async support
14//!   – `.log_info()`, `.log_warn()`, `.print_logs()`, `.set_up_logger()`, `.clear()`
15//!
16//! - **`OptionUtils`** – More ergonomic handling of `Option<T>`
17//!   – `.if_some()`, `.or_default_with()`
18//!
19//! - **`ResultUtils`** – Sugar methods for `Result<T, E>`
20//!   – `.if_ok()`, `.if_err()`, `.unwrap_or_exit()`
21//!
22//! - **`BoolUtils`** – Conditionals made fancy
23//!   – `.toggle()`, `.not()`, `.then_val()`, `.if_true()`, `.if_false()`
24//!
25//! - **`VecUtils`** – Push conditionally into vectors
26//!   – `.push_if()`, `.push_if_with()`
27//!
28//! - **`StrUtils`** – Extensions for `&str`
29//!   – `.contains_all()`, `.contains_any()`, `.to_title_case()`
30//!
31//! - **`MapUtils`** – `HashMap` helpers
32//!   – `.insert_if()`, `.get_or()`
33//!
34//! - **`MemUtils`** – Reflection-like methods
35//!   – `.type_name()`, `.mem_size()`, `.view()`
36//!
37//! - **`IdentityUtils`** – Tap-style chaining
38//!   – `.tap()`
39//!
40//! - **`PanicUtils`** – Fatal exit helpers
41//!   – `.unwrap_or_exit()`
42//!
43//! - **`DurationUtils`** – Duration formatting
44//!   – `.pretty()` → `"1h 2m 3s"`
45//!
46//! - **`ConvertUtils`** – Easy type conversions with `TryFrom`
47//!   – `.to()`, `.to_or()`, `.to_result()`
48//!
49//! - **`ClampUtils`** – Range limiting for numbers
50//!   – `.clamp_to(min, max)`
51//!
52//! - **`NumberUtils`** – Integer extensions
53//!   – `.is_even()`, `.is_odd()`
54//!
55//! - **`IteratorUtils`** – Fallback logic for iterators
56//!   – `.find_map_or(f, fallback)`
57//!
58//! ---
59//!
60//! ## ✨ Quick Example
61//!
62//! ```rust
63//! use utilz_rs::*;
64//!
65//! let value = Some("hi");
66//! value.if_some(|v| println!("Got: {v}"));
67//!
68//! let mut enabled = true;
69//! enabled.toggle();
70//!
71//! let name = "hello world";
72//! assert!(name.contains_all(["hello", "world"]));
73//!
74//! let duration = std::time::Duration::from_secs(3666);
75//! println!("{}", duration.pretty()); // → "1h 1m 6s"
76//! ```
77//!
78//! ---
79//!
80//! ## Philosophy
81//!
82//! - ✅ 100% Rust standard library
83//! - 🔧 Zero dependencies by default
84//! - 🔌 Async logging via optional feature flag
85//! - 🙌 Opt-in trait imports: use only what you need
86//!
87//! _Use what you want, ignore the rest. No macros. No surprises._
88
89use std::{any::type_name, collections::HashMap, hash::Hash, time::Duration};
90
91pub mod bool_utils;
92pub mod logger;
93pub mod option_utils;
94pub mod str_utils;
95
96pub mod prelude {
97    pub use crate::bool_utils::*;
98    pub use crate::logger::Loggable;
99    pub use crate::option_utils::*;
100    pub use crate::str_utils::*;
101    pub use crate::*;
102}
103
104/// Provides sugar methods for comparing values.
105pub trait EqUtils<T: PartialEq> {
106    /// Returns true if `self == other`.
107    fn eq_to(&self, other: &T) -> bool;
108
109    /// Returns true if `self != other`.
110    fn not_eq_to(&self, other: &T) -> bool;
111}
112
113impl<T: PartialEq> EqUtils<T> for T {
114    fn eq_to(&self, other: &T) -> bool {
115        self == other
116    }
117    fn not_eq_to(&self, other: &T) -> bool {
118        self != other
119    }
120}
121
122/// Reflection helpers: type name and memory size.
123pub trait MemUtils {
124    /// Returns the type name of `self`.
125    fn type_name(&self) -> &'static str;
126
127    /// Returns memory size in bytes of the type.
128    fn mem_size(&self) -> usize;
129
130    /// Prints type and memory size to stdout.
131    fn view(&self);
132}
133
134impl<T> MemUtils for T {
135    fn type_name(&self) -> &'static str {
136        type_name::<T>()
137    }
138    fn mem_size(&self) -> usize {
139        std::mem::size_of::<T>()
140    }
141    fn view(&self) {
142        println!(
143            "[view] Type: {}, Size: {} bytes",
144            self.type_name(),
145            self.mem_size()
146        );
147    }
148}
149
150pub trait ConvertUtils: Sized {
151    fn to<T: TryFrom<Self>>(self) -> Option<T>;
152    fn to_or<T: TryFrom<Self>>(self, fallback: T) -> T;
153    fn to_result<T: TryFrom<Self>>(self) -> Result<T, T::Error>;
154}
155
156impl<T> ConvertUtils for T {
157    fn to<U: TryFrom<T>>(self) -> Option<U> {
158        U::try_from(self).ok()
159    }
160
161    fn to_or<U: TryFrom<T>>(self, fallback: U) -> U {
162        self.to().unwrap_or(fallback)
163    }
164
165    fn to_result<U: TryFrom<T>>(self) -> Result<U, U::Error> {
166        U::try_from(self)
167    }
168}
169
170/// Conditional vector push helpers.
171pub trait VecUtils<T> {
172    /// Pushes the value if `cond` is `true`.
173    fn push_if(&mut self, push: T, cond: bool);
174
175    /// Lazily evaluates and pushes the value if `cond` is `true`.
176    fn push_if_with<F: FnOnce() -> T>(&mut self, cond: bool, f: F);
177}
178
179impl<T> VecUtils<T> for Vec<T> {
180    fn push_if(&mut self, push: T, cond: bool) {
181        if cond {
182            self.push(push);
183        }
184    }
185    fn push_if_with<F: FnOnce() -> T>(&mut self, cond: bool, f: F) {
186        if cond {
187            self.push(f());
188        }
189    }
190}
191
192pub trait MapUtils<K, V> {
193    fn get_or<'a>(&'a self, key: &K, fallback: &'a V) -> &'a V;
194    fn insert_if(&mut self, key: K, value: V, cond: bool);
195}
196
197impl<K: Eq + Hash, V> MapUtils<K, V> for HashMap<K, V> {
198    fn get_or<'a>(&'a self, key: &K, fallback: &'a V) -> &'a V {
199        self.get(key).unwrap_or(fallback)
200    }
201
202    fn insert_if(&mut self, key: K, value: V, cond: bool) {
203        if cond {
204            self.insert(key, value);
205        }
206    }
207}
208
209pub trait ResultUtils<T, E> {
210    fn if_ok<F: FnOnce(&T)>(self, f: F) -> Self;
211    fn if_err<F: FnOnce(&E)>(self, f: F) -> Self;
212}
213
214impl<T, E: std::fmt::Debug> ResultUtils<T, E> for Result<T, E> {
215    fn if_ok<F: FnOnce(&T)>(self, f: F) -> Self {
216        if let Ok(ref val) = self {
217            f(val);
218        }
219        self
220    }
221
222    fn if_err<F: FnOnce(&E)>(self, f: F) -> Self {
223        if let Err(ref err) = self {
224            f(err);
225        }
226        self
227    }
228}
229
230/// Pretty-formatting for `Duration`.
231pub trait DurationUtils {
232    /// Returns a formatted string like `"1h 20m 5s"`.
233    fn pretty(&self) -> String;
234}
235
236impl DurationUtils for Duration {
237    fn pretty(&self) -> String {
238        let total_secs = self.as_secs();
239        let hours = total_secs / 3600;
240        let mins = (total_secs % 3600) / 60;
241        let secs = total_secs % 60;
242        format!("{}h {}m {}s", hours, mins, secs)
243    }
244}
245
246pub trait IteratorUtils: Iterator + Sized {
247    fn find_map_or<T, F: FnMut(Self::Item) -> Option<T>>(self, f: F, fallback: T) -> T;
248}
249
250impl<I: Iterator> IteratorUtils for I {
251    fn find_map_or<T, F: FnMut(Self::Item) -> Option<T>>(mut self, f: F, fallback: T) -> T {
252        self.find_map(f).unwrap_or(fallback)
253    }
254}
255
256pub trait IdentityUtils: Sized {
257    fn tap<F: FnOnce(&Self)>(self, f: F) -> Self;
258}
259
260impl<T> IdentityUtils for T {
261    fn tap<F: FnOnce(&Self)>(self, f: F) -> Self {
262        f(&self);
263        self
264    }
265}
266
267/// Helpers to panic or exit cleanly with messages.
268pub trait PanicUtils<T> {
269    /// If `None` or `Err`, logs the message and exits the program.
270    fn unwrap_or_exit(self, msg: &str) -> T;
271}
272
273impl<T> PanicUtils<T> for Option<T> {
274    fn unwrap_or_exit(self, msg: &str) -> T {
275        self.unwrap_or_else(|| {
276            eprintln!("[FATAL]: {}", msg);
277            std::process::exit(1);
278        })
279    }
280}
281
282impl<T, U> PanicUtils<T> for Result<T, U> {
283    fn unwrap_or_exit(self, msg: &str) -> T {
284        self.unwrap_or_else(|_| {
285            eprintln!("[FATAL]: {}", msg);
286            std::process::exit(1);
287        })
288    }
289}
290
291pub trait ClampUtils {
292    fn clamp_to(self, min: Self, max: Self) -> Self;
293}
294impl ClampUtils for i32 {
295    fn clamp_to(self, min: Self, max: Self) -> Self {
296        self.max(min).min(max)
297    }
298}
299
300pub trait NumberUtils {
301    #[must_use]
302    fn is_even(&self) -> bool;
303    #[must_use]
304    fn is_odd(&self) -> bool;
305}
306
307impl NumberUtils for i32 {
308    fn is_even(&self) -> bool {
309        self % 2 == 0
310    }
311    fn is_odd(&self) -> bool {
312        self % 2 != 0
313    }
314}