use std::borrow::Cow;
pub trait StringWrap {
#[must_use]
fn wrap_with_char(&self, wrap_with: char) -> String;
#[must_use]
fn wrap_with_str(&self, wrap_with: &str) -> String;
#[must_use]
fn wrap_if_missing_char(&self, wrap_with: char) -> Cow<'_, str>;
#[must_use]
fn wrap_if_missing_str(&self, wrap_with: &str) -> Cow<'_, str>;
#[must_use]
fn unwrap_with_char(&self, wrap_char: char) -> &str;
#[must_use]
fn unwrap_with_str(&self, wrap_str: &str) -> &str;
}
macro_rules! impl_wrap {
($fn_name:ident, $wrap_ty:ty, $skip_check:expr) => {
fn $fn_name(&self, wrap_with: $wrap_ty) -> String {
if $skip_check(self, wrap_with) {
return self.to_string();
}
format!("{}{}{}", wrap_with, self, wrap_with)
}
};
}
macro_rules! impl_wrap_if_missing {
($fn_name:ident, $wrap_ty:ty, $skip_check:expr) => {
fn $fn_name(&self, wrap_with: $wrap_ty) -> Cow<'_, str> {
if $skip_check(self, wrap_with) {
return Cow::Borrowed(self);
}
let starts = self.starts_with(wrap_with);
let ends = self.ends_with(wrap_with);
match (starts, ends) {
(true, true) => Cow::Borrowed(self),
(true, false) => Cow::Owned(format!("{}{}", self, wrap_with)),
(false, true) => Cow::Owned(format!("{}{}", wrap_with, self)),
(false, false) => Cow::Owned(format!("{}{}{}", wrap_with, self, wrap_with)),
}
}
};
}
macro_rules! impl_unwrap {
($fn_name:ident, $wrap_ty:ty, $len_fn:expr) => {
fn $fn_name(&self, wrap_with: $wrap_ty) -> &str {
let wrap_len = $len_fn(wrap_with);
if wrap_len == 0 || self.len() < wrap_len * 2 {
return self;
}
if self.starts_with(wrap_with) && self.ends_with(wrap_with) {
&self[wrap_len..self.len() - wrap_len]
} else {
self
}
}
};
}
impl StringWrap for str {
impl_wrap!(wrap_with_char, char, |s: &str, c: char| s.is_empty()
|| c == '\0');
impl_wrap!(wrap_with_str, &str, |s: &str, w: &str| s.is_empty()
|| w.is_empty());
impl_wrap_if_missing!(wrap_if_missing_char, char, |s: &str, c: char| s.is_empty()
|| c == '\0');
impl_wrap_if_missing!(wrap_if_missing_str, &str, |s: &str, w: &str| s.is_empty()
|| w.is_empty());
impl_unwrap!(unwrap_with_char, char, |c: char| c.len_utf8());
impl_unwrap!(unwrap_with_str, &str, |s: &str| s.len());
}
#[cfg(test)]
mod tests {
use super::*;
mod wrap_with_char {
use super::*;
#[test]
fn empty_string() {
assert_eq!("".wrap_with_char('x'), "");
}
#[test]
fn null_char() {
assert_eq!("ab".wrap_with_char('\0'), "ab");
}
#[test]
fn simple_wrap() {
assert_eq!("ab".wrap_with_char('x'), "xabx");
}
#[test]
fn single_quote() {
assert_eq!("ab".wrap_with_char('\''), "'ab'");
}
#[test]
fn double_quote_already_present() {
assert_eq!("\"ab\"".wrap_with_char('"'), "\"\"ab\"\"");
}
#[test]
fn unicode_char() {
assert_eq!("hello".wrap_with_char('§'), "§hello§");
}
#[test]
fn emoji_wrapper() {
assert_eq!("hello".wrap_with_char('🦀'), "🦀hello🦀");
}
#[test]
fn mixed_ascii_and_emoji_content() {
assert_eq!("hello🌍world".wrap_with_char('*'), "*hello🌍world*");
}
}
mod wrap_with_str {
use super::*;
#[test]
fn empty_string() {
assert_eq!("".wrap_with_str("x"), "");
}
#[test]
fn empty_wrapper() {
assert_eq!("ab".wrap_with_str(""), "ab");
}
#[test]
fn simple_wrap() {
assert_eq!("ab".wrap_with_str("x"), "xabx");
}
#[test]
fn double_quote() {
assert_eq!("ab".wrap_with_str("\""), "\"ab\"");
}
#[test]
fn double_quote_already_present() {
assert_eq!("\"ab\"".wrap_with_str("\""), "\"\"ab\"\"");
}
#[test]
fn single_quote() {
assert_eq!("ab".wrap_with_str("'"), "'ab'");
}
#[test]
fn single_quote_already_present() {
assert_eq!("'abcd'".wrap_with_str("'"), "''abcd''");
}
#[test]
fn mixed_quotes_double_in_single() {
assert_eq!("\"abcd\"".wrap_with_str("'"), "'\"abcd\"'");
}
#[test]
fn mixed_quotes_single_in_double() {
assert_eq!("'abcd'".wrap_with_str("\""), "\"'abcd'\"");
}
#[test]
fn multi_char_wrapper() {
assert_eq!("hello".wrap_with_str("**"), "**hello**");
}
#[test]
fn emoji_wrapper() {
assert_eq!("hello".wrap_with_str("🦀"), "🦀hello🦀");
}
#[test]
fn multi_char_emoji_wrapper() {
assert_eq!("hello".wrap_with_str("🦀🦀"), "🦀🦀hello🦀🦀");
}
#[test]
fn cjk_content() {
assert_eq!("你好".wrap_with_str("【"), "【你好【");
}
}
mod wrap_if_missing_char {
use super::*;
#[test]
fn empty_string() {
assert_eq!("".wrap_if_missing_char('x'), "");
}
#[test]
fn null_char() {
assert_eq!("ab".wrap_if_missing_char('\0'), "ab");
}
#[test]
fn not_wrapped() {
assert_eq!("ab".wrap_if_missing_char('x'), "xabx");
}
#[test]
fn already_wrapped() {
assert_eq!("xabx".wrap_if_missing_char('x'), "xabx");
}
#[test]
fn only_starts_with() {
assert_eq!("xab".wrap_if_missing_char('x'), "xabx");
}
#[test]
fn only_ends_with() {
assert_eq!("abx".wrap_if_missing_char('x'), "xabx");
}
#[test]
fn single_quote_already_wrapped() {
assert_eq!("'ab'".wrap_if_missing_char('\''), "'ab'");
}
#[test]
fn single_char_string() {
assert_eq!("x".wrap_if_missing_char('x'), "x");
}
#[test]
fn single_emoji_equals_wrapper() {
assert_eq!("🦀".wrap_if_missing_char('🦀'), "🦀");
}
}
mod wrap_if_missing_str {
use super::*;
#[test]
fn empty_string() {
assert_eq!("".wrap_if_missing_str("x"), "");
}
#[test]
fn empty_wrapper() {
assert_eq!("ab".wrap_if_missing_str(""), "ab");
}
#[test]
fn not_wrapped() {
assert_eq!("ab".wrap_if_missing_str("x"), "xabx");
}
#[test]
fn already_wrapped() {
assert_eq!("xabx".wrap_if_missing_str("x"), "xabx");
}
#[test]
fn only_starts_with() {
assert_eq!("xab".wrap_if_missing_str("x"), "xabx");
}
#[test]
fn only_ends_with() {
assert_eq!("abx".wrap_if_missing_str("x"), "xabx");
}
#[test]
fn double_quote_already_wrapped() {
assert_eq!("\"ab\"".wrap_if_missing_str("\""), "\"ab\"");
}
#[test]
fn multi_char_wrapper_already_wrapped() {
assert_eq!("**hello**".wrap_if_missing_str("**"), "**hello**");
}
#[test]
fn multi_char_wrapper_only_starts() {
assert_eq!("**hello".wrap_if_missing_str("**"), "**hello**");
}
#[test]
fn multi_char_wrapper_only_ends() {
assert_eq!("hello**".wrap_if_missing_str("**"), "**hello**");
}
#[test]
fn emoji_not_wrapped() {
assert_eq!("hello".wrap_if_missing_str("🦀"), "🦀hello🦀");
}
#[test]
fn already_wrapped_with_emoji() {
assert_eq!("🦀hello🦀".wrap_if_missing_str("🦀"), "🦀hello🦀");
}
}
mod unwrap_with_char {
use super::*;
#[test]
fn empty_string() {
assert_eq!("".unwrap_with_char('x'), "");
}
#[test]
fn single_char_string() {
assert_eq!("x".unwrap_with_char('x'), "x");
}
#[test]
fn wrapped_string() {
assert_eq!("xabx".unwrap_with_char('x'), "ab");
}
#[test]
fn only_starts_with() {
assert_eq!("xab".unwrap_with_char('x'), "xab");
}
#[test]
fn only_ends_with() {
assert_eq!("abx".unwrap_with_char('x'), "abx");
}
#[test]
fn single_quote() {
assert_eq!("'ab'".unwrap_with_char('\''), "ab");
}
#[test]
fn double_quote() {
assert_eq!("\"ab\"".unwrap_with_char('"'), "ab");
}
#[test]
fn just_wrapper_chars() {
assert_eq!("xx".unwrap_with_char('x'), "");
}
#[test]
fn unicode_wrapper() {
assert_eq!("§hello§".unwrap_with_char('§'), "hello");
}
#[test]
fn not_wrapped() {
assert_eq!("ab".unwrap_with_char('x'), "ab");
}
#[test]
fn emoji_wrapper() {
assert_eq!("🦀hello🦀".unwrap_with_char('🦀'), "hello");
}
#[test]
fn string_too_short_for_emoji_wrapper() {
assert_eq!("ab".unwrap_with_char('🦀'), "ab");
}
#[test]
fn mixed_ascii_and_emoji_content() {
assert_eq!("*hello🌍world*".unwrap_with_char('*'), "hello🌍world");
}
}
mod unwrap_with_str {
use super::*;
#[test]
fn empty_string() {
assert_eq!("".unwrap_with_str("x"), "");
}
#[test]
fn empty_wrapper() {
assert_eq!("ab".unwrap_with_str(""), "ab");
}
#[test]
fn single_char_wrapper() {
assert_eq!("x".unwrap_with_str("x"), "x");
}
#[test]
fn wrapped_string() {
assert_eq!("xabx".unwrap_with_str("x"), "ab");
}
#[test]
fn only_starts_with() {
assert_eq!("xab".unwrap_with_str("x"), "xab");
}
#[test]
fn only_ends_with() {
assert_eq!("abx".unwrap_with_str("x"), "abx");
}
#[test]
fn multi_char_wrapper() {
assert_eq!("**hello**".unwrap_with_str("**"), "hello");
}
#[test]
fn multi_char_wrapper_only_starts() {
assert_eq!("**hello".unwrap_with_str("**"), "**hello");
}
#[test]
fn just_wrapper_strings() {
assert_eq!("****".unwrap_with_str("**"), "");
}
#[test]
fn not_wrapped() {
assert_eq!("ab".unwrap_with_str("x"), "ab");
}
#[test]
fn wrapper_longer_than_half_string() {
assert_eq!("abc".unwrap_with_str("ab"), "abc");
}
#[test]
fn multi_char_emoji_wrapper() {
assert_eq!("🦀🦀hello🦀🦀".unwrap_with_str("🦀🦀"), "hello");
}
#[test]
fn just_emoji_wrappers() {
assert_eq!("🦀🦀".unwrap_with_str("🦀"), "");
}
#[test]
fn cjk_wrapper() {
assert_eq!("「hello」".unwrap_with_str("「"), "「hello」");
assert_eq!("「hello「".unwrap_with_str("「"), "hello");
}
}
mod string_types {
use super::*;
#[test]
fn string_type_wrap() {
assert_eq!(String::from("hello").wrap_with_char('"'), "\"hello\"");
}
#[test]
fn string_ref_unwrap() {
let s = String::from("\"hello\"");
assert_eq!(s.unwrap_with_char('"'), "hello");
}
#[test]
fn boxed_str() {
let s: Box<str> = "hello".into();
assert_eq!(s.wrap_with_str("**"), "**hello**");
}
}
}