2024-08-31 02:13:23 +00:00
|
|
|
mod between;
|
2025-12-19 20:57:40 +00:00
|
|
|
pub mod de;
|
2024-08-31 02:13:23 +00:00
|
|
|
mod split;
|
|
|
|
|
mod tests;
|
|
|
|
|
mod unquote;
|
|
|
|
|
mod unquoted;
|
|
|
|
|
|
2025-12-23 23:18:48 +00:00
|
|
|
use std::{mem::replace, ops::Range};
|
2025-11-27 06:09:26 +00:00
|
|
|
|
2024-08-31 02:13:23 +00:00
|
|
|
pub use self::{between::Between, split::SplitInfallible, unquote::Unquote, unquoted::Unquoted};
|
2025-11-27 06:09:26 +00:00
|
|
|
use crate::{Result, smallstr::SmallString};
|
2024-07-03 00:47:58 +00:00
|
|
|
|
2024-07-03 23:12:43 +00:00
|
|
|
pub const EMPTY: &str = "";
|
|
|
|
|
|
2024-07-13 21:02:43 +00:00
|
|
|
/// Constant expression to bypass format! if the argument is a string literal
|
|
|
|
|
/// but not a format string. If the literal is a format string then String is
|
|
|
|
|
/// returned otherwise the input (i.e. &'static str) is returned. If multiple
|
|
|
|
|
/// arguments are provided the first is assumed to be a format string.
|
|
|
|
|
#[macro_export]
|
2025-04-04 23:04:13 +00:00
|
|
|
#[collapse_debuginfo(yes)]
|
2024-07-13 21:02:43 +00:00
|
|
|
macro_rules! format_maybe {
|
2024-08-01 08:41:47 +00:00
|
|
|
($s:literal $(,)?) => {
|
2024-07-13 21:02:43 +00:00
|
|
|
if $crate::is_format!($s) { std::format!($s).into() } else { $s.into() }
|
|
|
|
|
};
|
|
|
|
|
|
2024-08-01 08:41:47 +00:00
|
|
|
($s:literal, $($args:tt)+) => {
|
|
|
|
|
std::format!($s, $($args)+).into()
|
2024-07-13 21:02:43 +00:00
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Constant expression to decide if a literal is a format string. Note: could
|
|
|
|
|
/// use some improvement.
|
|
|
|
|
#[macro_export]
|
2025-04-04 23:04:13 +00:00
|
|
|
#[collapse_debuginfo(yes)]
|
2024-07-13 21:02:43 +00:00
|
|
|
macro_rules! is_format {
|
|
|
|
|
($s:literal) => {
|
|
|
|
|
::const_str::contains!($s, "{") && ::const_str::contains!($s, "}")
|
|
|
|
|
};
|
2024-08-01 08:41:47 +00:00
|
|
|
|
|
|
|
|
($($s:tt)+) => {
|
|
|
|
|
false
|
|
|
|
|
};
|
2024-07-13 21:02:43 +00:00
|
|
|
}
|
|
|
|
|
|
2024-08-03 02:16:52 +00:00
|
|
|
#[inline]
|
|
|
|
|
pub fn collect_stream<F>(func: F) -> Result<String>
|
|
|
|
|
where
|
2025-07-08 12:08:13 +00:00
|
|
|
F: FnOnce(&mut dyn std::fmt::Write) -> Result,
|
2024-08-03 02:16:52 +00:00
|
|
|
{
|
|
|
|
|
let mut out = String::new();
|
|
|
|
|
func(&mut out)?;
|
|
|
|
|
Ok(out)
|
|
|
|
|
}
|
|
|
|
|
|
2024-07-23 07:03:33 +00:00
|
|
|
#[inline]
|
|
|
|
|
#[must_use]
|
|
|
|
|
pub fn camel_to_snake_string(s: &str) -> String {
|
|
|
|
|
let est_len = s
|
|
|
|
|
.chars()
|
|
|
|
|
.fold(s.len(), |est, c| est.saturating_add(usize::from(c.is_ascii_uppercase())));
|
|
|
|
|
|
|
|
|
|
let mut ret = String::with_capacity(est_len);
|
|
|
|
|
camel_to_snake_case(&mut ret, s.as_bytes()).expect("string-to-string stream error");
|
|
|
|
|
ret
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[inline]
|
2026-01-22 21:48:41 +05:00
|
|
|
#[expect(clippy::unbuffered_bytes)] // these are allocated string utilities, not file I/O utils
|
2025-07-08 12:08:13 +00:00
|
|
|
pub fn camel_to_snake_case<I, O>(output: &mut O, input: I) -> Result
|
2024-07-23 07:03:33 +00:00
|
|
|
where
|
|
|
|
|
I: std::io::Read,
|
|
|
|
|
O: std::fmt::Write,
|
|
|
|
|
{
|
|
|
|
|
let mut state = false;
|
|
|
|
|
input
|
|
|
|
|
.bytes()
|
|
|
|
|
.take_while(Result::is_ok)
|
|
|
|
|
.map(Result::unwrap)
|
|
|
|
|
.map(char::from)
|
|
|
|
|
.try_for_each(|ch| {
|
|
|
|
|
let m = ch.is_ascii_uppercase();
|
2025-11-27 06:09:26 +00:00
|
|
|
let s = replace(&mut state, !m);
|
2024-07-23 07:03:33 +00:00
|
|
|
if m && s {
|
|
|
|
|
output.write_char('_')?;
|
|
|
|
|
}
|
|
|
|
|
output.write_char(ch.to_ascii_lowercase())?;
|
|
|
|
|
Result::<()>::Ok(())
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
2024-07-05 07:01:11 +00:00
|
|
|
/// Find the common prefix from a collection of strings and return a slice
|
|
|
|
|
/// ```
|
2025-04-22 01:41:02 +00:00
|
|
|
/// use tuwunel_core::utils::string::common_prefix;
|
2024-07-05 07:01:11 +00:00
|
|
|
/// let input = ["conduwuit", "conduit", "construct"];
|
|
|
|
|
/// common_prefix(&input) == "con";
|
|
|
|
|
/// ```
|
|
|
|
|
#[must_use]
|
2026-01-22 21:48:41 +05:00
|
|
|
#[expect(clippy::string_slice)]
|
2025-11-01 01:52:33 +00:00
|
|
|
pub fn common_prefix<T: AsRef<str>>(choice: &[T]) -> &str {
|
2024-07-05 07:01:11 +00:00
|
|
|
choice.first().map_or(EMPTY, move |best| {
|
2025-11-01 01:52:33 +00:00
|
|
|
choice
|
|
|
|
|
.iter()
|
|
|
|
|
.skip(1)
|
|
|
|
|
.fold(best.as_ref(), |best, choice| {
|
|
|
|
|
&best[0..choice
|
|
|
|
|
.as_ref()
|
|
|
|
|
.char_indices()
|
|
|
|
|
.zip(best.char_indices())
|
|
|
|
|
.take_while(|&(a, b)| a == b)
|
|
|
|
|
.count()]
|
|
|
|
|
})
|
2024-07-05 07:01:11 +00:00
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-23 23:18:48 +00:00
|
|
|
#[inline]
|
|
|
|
|
#[must_use]
|
2026-01-22 21:48:41 +05:00
|
|
|
#[expect(clippy::arithmetic_side_effects)]
|
2025-12-23 23:18:48 +00:00
|
|
|
pub fn truncate_deterministic(str: &str, range: Option<Range<usize>>) -> &str {
|
|
|
|
|
let range = range.unwrap_or(0..str.len());
|
|
|
|
|
let len = str
|
|
|
|
|
.as_bytes()
|
|
|
|
|
.iter()
|
|
|
|
|
.copied()
|
|
|
|
|
.map(Into::into)
|
|
|
|
|
.fold(0_usize, usize::wrapping_add)
|
|
|
|
|
.wrapping_rem(str.len().max(1))
|
|
|
|
|
.clamp(range.start, range.end);
|
|
|
|
|
|
|
|
|
|
str.char_indices()
|
|
|
|
|
.nth(len)
|
|
|
|
|
.map(|(i, _)| str.split_at(i).0)
|
|
|
|
|
.unwrap_or(str)
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-28 23:08:14 +00:00
|
|
|
pub fn to_small_string<const CAP: usize, T>(t: T) -> SmallString<[u8; CAP]>
|
|
|
|
|
where
|
|
|
|
|
T: std::fmt::Display,
|
|
|
|
|
{
|
|
|
|
|
use std::fmt::Write;
|
|
|
|
|
|
|
|
|
|
let mut ret = SmallString::<[u8; CAP]>::new();
|
|
|
|
|
write!(&mut ret, "{t}").expect("Failed to Display type in SmallString");
|
|
|
|
|
|
|
|
|
|
ret
|
|
|
|
|
}
|
|
|
|
|
|
2024-07-03 00:47:58 +00:00
|
|
|
/// Parses the bytes into a string.
|
|
|
|
|
pub fn string_from_bytes(bytes: &[u8]) -> Result<String> {
|
|
|
|
|
let str: &str = str_from_bytes(bytes)?;
|
|
|
|
|
Ok(str.to_owned())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Parses the bytes into a string.
|
|
|
|
|
#[inline]
|
|
|
|
|
pub fn str_from_bytes(bytes: &[u8]) -> Result<&str> { Ok(std::str::from_utf8(bytes)?) }
|