Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion packages/copper/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "pistonite-cu"
version = "0.9.5"
version = "0.9.6"
edition = "2024"
description = "Battery-included common utils to speed up development of rust tools"
repository = "https://github.com/Pistonite/cu"
Expand Down Expand Up @@ -64,6 +64,7 @@ features = [ "macros", "rt-multi-thread", "time" ]
[features]
default = []
full = [
"regex",
"cli",
"coroutine-heavy",
"prompt",
Expand All @@ -74,6 +75,9 @@ full = [
"derive",
]

# --- Extra bits of utils ---
regex = ["dep:regex"]

# --- Command Line Interface ---
print = ["dep:oneshot", "dep:regex", "dep:env_filter", "dep:terminal_size", "dep:unicode-width", "dep:ctrlc"]
cli = ["dep:clap", "print"]
Expand Down
18 changes: 15 additions & 3 deletions packages/copper/src/errhand.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,9 @@ pub use anyhow::{Context, Error, Ok, Result, anyhow as fmterr, bail};
/// - `anyhow::Ok` is `cu::Ok`
///
/// Here are custom utilities from `cu` that integrates with `anyhow`
/// - `cu::check!` wraps `.with_context()`
/// - `cu::check!` wraps `.with_context()`. This is the general-purpose way to add context
/// to errors that can happen at runtime even when the program is correct, such as
/// invalid user input or system errors (e.g. file does not exist)
/// ```rust
/// # use pistonite_cu as cu;
/// use cu::pre::*;
Expand All @@ -45,9 +47,12 @@ pub use anyhow::{Context, Error, Ok, Result, anyhow as fmterr, bail};
/// - [`cu::unimplemented!`](macro@crate::unimplemented)
/// and [`cu::unreachable!`](macro@crate::unreachable)
/// that are similar to the std macros, but instead of `panic!`, they will `bail!`
/// - [`cu::ensure!`](macro@crate::ensure) is unlike `anyhow::ensure`, that
/// - [`cu::ensure!`](macro@crate::ensure) is an assert-like check for invariants that
/// should almost never fail if the program is correct (i.e. a debugging aid, like `assert!`,
/// but returns an error instead of panicking). Unlike `anyhow::ensure`,
/// it evaluates to a `Result<()>` instead of generates a return.
/// It also does not automatically generate debug information.
/// Do not use it to validate user input or check for system errors - use `check!` or `bail!` for those.
/// - [`cu::some!`] checks an `Option` and returns `Ok(None)` if the option is `None`.
///
/// Here are other `anyhow` re-exports that are less commonly used
Expand Down Expand Up @@ -197,7 +202,14 @@ macro_rules! unreachable {
}}
}

/// Check if an expression is `true`
/// Check if an expression is `true`, as an assertion
///
/// This is meant to be assert-like: the condition is an invariant that should
/// almost never fail if everything is correct, so a failure indicates a bug
/// (i.e. it's a debugging aid, like `assert!`, but returns an error instead of panicking).
/// For errors that are expected to happen at runtime, such as invalid user input
/// or system errors (e.g. file does not exist), use [`cu::check!`](macro@crate::check)
/// or `cu::bail!` with a descriptive message instead.
///
/// Unlike `anyhow::ensure`, if the condition fail, this will generate an `Error`
/// instead of returning an error directly, so you need to add a `?`.
Expand Down
2 changes: 2 additions & 0 deletions packages/copper/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,8 @@ extern crate self as cu;

// --- Basic stuff (no feature needed) ---
pub mod str;
#[cfg(feature = "regex")]
pub use str::Regex;
pub use str::{ByteFormat, ZString};

mod env_var;
Expand Down
5 changes: 5 additions & 0 deletions packages/copper/src/str/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,8 @@ pub use path::PathExtension;

// path macro just depends on the std path functions and does not require fs
mod path_macro;

#[cfg(feature = "regex")]
mod regex;
#[cfg(feature = "regex")]
pub use regex::Regex;
87 changes: 87 additions & 0 deletions packages/copper/src/str/regex.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
use cu::pre::*;

/// Deserializable regex
#[derive(Clone)]
pub struct Regex {
inner: regex::Regex,

#[cfg(feature = "serde")]
lit: String,
}
impl Regex {
pub fn new(s: &str) -> cu::Result<Self> {
let inner = cu::check!(regex::Regex::new(s), "failed to build regex from {s:?}")?;
#[cfg(feature = "serde")]
let s = Self {
inner,
lit: s.to_string(),
};
#[cfg(not(feature = "serde"))]
let s = Self { inner };
Ok(s)
}
}
impl std::ops::Deref for Regex {
type Target = regex::Regex;
fn deref(&self) -> &Self::Target {
&self.inner
}
}
impl std::ops::DerefMut for Regex {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.inner
}
}
impl std::fmt::Display for Regex {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.lit)
}
}
impl std::fmt::Debug for Regex {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "Regex({})", self.inner)
}
}
impl Regex {
/// Get the string that this regex was created from
pub fn to_str(&self) -> &str {
&self.lit
}
}
impl From<Regex> for regex::Regex {
fn from(value: Regex) -> Self {
value.inner
}
}

#[cfg(feature = "serde")]
impl Serialize for Regex {
fn serialize<S: serde::Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
s.serialize_str(&self.lit)
}
}

#[cfg(feature = "serde")]
impl<'de> Deserialize<'de> for Regex {
fn deserialize<D: serde::Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
return d.deserialize_str(Visitor);
struct Visitor;
impl<'de> serde::de::Visitor<'de> for Visitor {
type Value = Regex;
fn expecting(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f, "a regular expression")
}
fn visit_str<E: serde::de::Error>(self, v: &str) -> Result<Self::Value, E> {
match regex::Regex::new(v) {
Err(e) => Err(serde::de::Error::custom(format!(
"invalid regular expression '{v}': {e}"
))),
Ok(x) => Ok(Regex {
inner: x,
lit: v.to_string(),
}),
}
}
}
}
}