rstring 0.1.0

A comprehensive set of string manipulation utilities inspired by Apache Commons Lang3 StringUtils
Documentation
pub(crate) fn char_offset_to_byte_index(s: &str, offset: usize) -> usize {
    s.char_indices()
        .nth(offset)
        .map(|(i, _)| i)
        .unwrap_or(s.len())
}

/// Checks if a string starts with a prefix, ignoring ASCII case only.
/// This is faster but only handles A-Z/a-z case folding.
pub(crate) fn starts_with_ignore_ascii_case(s: &str, prefix: &str) -> bool {
    if prefix.len() > s.len() {
        return false;
    }
    s[..prefix.len()].eq_ignore_ascii_case(prefix)
}

/// Checks if a string starts with a prefix, ignoring case (full Unicode support).
/// Handles Unicode case folding (e.g., é/É, ñ/Ñ) but requires allocation.
pub(crate) fn starts_with_ignore_case(s: &str, prefix: &str) -> bool {
    s.to_lowercase().starts_with(&prefix.to_lowercase())
}

/// Checks if a string ends with a suffix, ignoring ASCII case only.
/// This is faster but only handles A-Z/a-z case folding.
pub(crate) fn ends_with_ignore_ascii_case(s: &str, suffix: &str) -> bool {
    if suffix.len() > s.len() {
        return false;
    }
    s[s.len() - suffix.len()..].eq_ignore_ascii_case(suffix)
}

/// Checks if a string ends with a suffix, ignoring case (full Unicode support).
/// Handles Unicode case folding (e.g., é/É, ñ/Ñ) but requires allocation.
pub(crate) fn ends_with_ignore_case(s: &str, suffix: &str) -> bool {
    s.to_lowercase().ends_with(&suffix.to_lowercase())
}

/// Early-return guard for functions that operate on two string-like values.
///
/// Returns `$default` immediately if either `$self` or `$other` is empty.
/// Both arguments must implement `is_empty()` (e.g., `&str`, `&[char]`, `&[&str]`).
///
/// # Usage
///
/// ```ignore
/// use crate::guard_empty;
///
/// fn contains_any(haystack: &str, needles: &[&str]) -> bool {
///     guard_empty!(haystack, needles, false);
///     // ... actual logic
/// }
/// ```
#[macro_export]
macro_rules! guard_empty {
    ($self:expr, $other:expr, $default:expr) => {
        if $self.is_empty() || $other.is_empty() {
            return $default;
        }
    };
}