#[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('&', "&")
.replace('<', "<")
.replace('>', ">")
.replace('"', """)
.replace('\'', "'")
}
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>"), "<div>");
}
}
}