rok-utils 0.2.1

Laravel/AdonisJS-inspired utility helpers for the Rok ecosystem
Documentation
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Str {
    inner: String,
}

unsafe impl Send for Str {}
unsafe impl Sync for Str {}

impl Str {
    pub fn of(s: impl Into<String>) -> Self {
        Self { inner: s.into() }
    }

    pub fn slug(mut self) -> Self {
        self.inner = crate::string::slug(&self.inner, '-');
        self
    }

    pub fn snake(mut self) -> Self {
        self.inner = crate::string::to_snake_case(&self.inner);
        self
    }

    pub fn camel(mut self) -> Self {
        self.inner = crate::string::to_camel_case(&self.inner);
        self
    }

    pub fn pascal(mut self) -> Self {
        self.inner = crate::string::to_pascal_case(&self.inner);
        self
    }

    pub fn kebab(mut self) -> Self {
        self.inner = crate::string::to_kebab_case(&self.inner);
        self
    }

    pub fn title(mut self) -> Self {
        self.inner = crate::string::to_title_case(&self.inner);
        self
    }

    pub fn upper(mut self) -> Self {
        self.inner = crate::string::to_upper(&self.inner);
        self
    }

    pub fn lower(mut self) -> Self {
        self.inner = crate::string::to_lower(&self.inner);
        self
    }

    pub fn trim(mut self) -> Self {
        self.inner = self.inner.trim().to_string();
        self
    }

    pub fn ltrim(mut self) -> Self {
        self.inner = self.inner.trim_start().to_string();
        self
    }

    pub fn rtrim(mut self) -> Self {
        self.inner = self.inner.trim_end().to_string();
        self
    }

    pub fn squish(mut self) -> Self {
        self.inner = crate::string::squish(&self.inner);
        self
    }

    pub fn truncate(mut self, limit: usize) -> Self {
        self.inner = crate::string::truncate(&self.inner, limit);
        self
    }

    pub fn reverse(mut self) -> Self {
        self.inner = crate::string::reverse(&self.inner);
        self
    }

    pub fn repeat(mut self, times: usize) -> Self {
        self.inner = crate::string::repeat(&self.inner, times);
        self
    }

    pub fn append(mut self, s: &str) -> Self {
        self.inner.push_str(s);
        self
    }

    pub fn prepend(mut self, s: &str) -> Self {
        self.inner = format!("{}{}", s, self.inner);
        self
    }

    pub fn replace(mut self, from: &str, to: &str) -> Self {
        self.inner = self.inner.replace(from, to);
        self
    }

    pub fn replace_first(mut self, from: &str, to: &str) -> Self {
        self.inner = crate::string::replace_first(&self.inner, from, to);
        self
    }

    pub fn replace_last(mut self, from: &str, to: &str) -> Self {
        self.inner = crate::string::replace_last(&self.inner, from, to);
        self
    }

    pub fn finish(mut self, cap: &str) -> Self {
        self.inner = crate::string::finish(&self.inner, cap);
        self
    }

    pub fn ensure_start(mut self, prefix: &str) -> Self {
        self.inner = crate::string::ensure_start(&self.inner, prefix);
        self
    }

    pub fn wrap(mut self, before: &str, after: &str) -> Self {
        self.inner = crate::string::wrap(&self.inner, before, after);
        self
    }

    pub fn pad_left(mut self, n: usize) -> Self {
        self.inner = crate::string::pad_left(&self.inner, n, ' ');
        self
    }

    pub fn pad_right(mut self, n: usize) -> Self {
        self.inner = crate::string::pad_right(&self.inner, n, ' ');
        self
    }

    pub fn pad_both(mut self, n: usize) -> Self {
        self.inner = crate::string::pad_both(&self.inner, n, ' ');
        self
    }

    pub fn mask(mut self, mask_char: char, from: usize) -> Self {
        self.inner = crate::string::mask(&self.inner, mask_char, from);
        self
    }

    pub fn escape_html(mut self) -> Self {
        self.inner = escape_html_impl(&self.inner);
        self
    }

    pub fn when(self, condition: bool, f: impl FnOnce(Self) -> Self) -> Self {
        if condition {
            f(self)
        } else {
            self
        }
    }

    pub fn when_empty(self, f: impl FnOnce(Self) -> Self) -> Self {
        if self.inner.is_empty() {
            f(self)
        } else {
            self
        }
    }

    pub fn when_not_empty(self, f: impl FnOnce(Self) -> Self) -> Self {
        if !self.inner.is_empty() {
            f(self)
        } else {
            self
        }
    }

    pub fn when_contains(self, needle: &str, f: impl FnOnce(Self) -> Self) -> Self {
        if self.inner.contains(needle) {
            f(self)
        } else {
            self
        }
    }

    pub fn when_starts_with(self, prefix: &str, f: impl FnOnce(Self) -> Self) -> Self {
        if self.inner.starts_with(prefix) {
            f(self)
        } else {
            self
        }
    }

    pub fn when_ends_with(self, suffix: &str, f: impl FnOnce(Self) -> Self) -> Self {
        if self.inner.ends_with(suffix) {
            f(self)
        } else {
            self
        }
    }

    pub fn tap(self, f: impl FnOnce(&str)) -> Self {
        f(&self.inner);
        self
    }

    pub fn pipe<F: FnOnce(String) -> String>(self, f: F) -> Self {
        Self {
            inner: f(self.inner),
        }
    }

    #[allow(clippy::inherent_to_string)]
    pub fn to_string(self) -> String {
        self.inner
    }

    pub fn len(&self) -> usize {
        self.inner.len()
    }

    pub fn is_empty(&self) -> bool {
        self.inner.is_empty()
    }

    pub fn contains(&self, needle: &str) -> bool {
        self.inner.contains(needle)
    }

    pub fn starts_with(&self, prefix: &str) -> bool {
        self.inner.starts_with(prefix)
    }

    pub fn ends_with(&self, suffix: &str) -> bool {
        self.inner.ends_with(suffix)
    }

    pub fn word_count(&self) -> usize {
        crate::string::word_count(&self.inner)
    }

    pub fn to_base64(self) -> String {
        to_base64(&self.inner)
    }

    pub fn split(self, delimiter: &str) -> Vec<String> {
        self.inner.split(delimiter).map(|s| s.to_string()).collect()
    }

    pub fn exactly(&self, other: &str) -> bool {
        self.inner == other
    }

    pub fn value(self) -> String {
        self.inner
    }
}

fn escape_html_impl(s: &str) -> String {
    s.replace('&', "&amp;")
        .replace('<', "&lt;")
        .replace('>', "&gt;")
        .replace('"', "&quot;")
        .replace('\'', "&#39;")
}

pub fn to_base64(s: &str) -> String {
    use base64::Engine;
    base64::engine::general_purpose::STANDARD.encode(s.as_bytes())
}

#[cfg(feature = "json")]
pub fn escape_html(s: &str) -> String {
    escape_html_impl(s)
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn fluent_basic() {
        let result = Str::of("hello world").trim().snake().value();
        assert_eq!(result, "hello_world");
    }

    #[test]
    fn fluent_when() {
        let result = Str::of("hello").when(false, |s| s.append(" world")).value();
        assert_eq!(result, "hello");

        let result = Str::of("hello").when(true, |s| s.append(" world")).value();
        assert_eq!(result, "hello world");
    }

    #[test]
    fn fluent_when_empty() {
        let result = Str::of("").when_empty(|s| s.append("default")).value();
        assert_eq!(result, "default");

        let result = Str::of("hello").when_empty(|s| s.append("default")).value();
        assert_eq!(result, "hello");
    }

    #[test]
    fn fluent_tap() {
        let mut seen = String::new();
        let _ = Str::of("test").tap(|s| seen.push_str(s));
        assert_eq!(seen, "test");
    }

    #[test]
    fn fluent_pipe() {
        let result = Str::of("hello").pipe(|s| s.to_uppercase()).value();
        assert_eq!(result, "HELLO");
    }

    #[test]
    fn to_base64_basic() {
        assert_eq!(to_base64("hello"), "aGVsbG8=");
    }

    #[test]
    fn escape_html_basic() {
        #[cfg(feature = "json")]
        {
            assert_eq!(escape_html("<div>"), "&lt;div&gt;");
        }
    }
}