use std::borrow::Cow;
use crate::shared::{
ends_with_ignore_ascii_case, ends_with_ignore_case, starts_with_ignore_ascii_case,
starts_with_ignore_case,
};
pub trait StringAffixes {
#[must_use]
fn append_if_missing(&self, suffix: &str) -> Cow<'_, str>;
#[must_use]
fn append_if_missing_ignore_ascii_case(&self, suffix: &str) -> Cow<'_, str>;
#[must_use]
fn append_if_missing_ignore_case(&self, suffix: &str) -> Cow<'_, str>;
#[must_use]
fn prepend_if_missing(&self, prefix: &str) -> Cow<'_, str>;
#[must_use]
fn prepend_if_missing_ignore_ascii_case(&self, prefix: &str) -> Cow<'_, str>;
#[must_use]
fn prepend_if_missing_ignore_case(&self, prefix: &str) -> Cow<'_, str>;
}
macro_rules! impl_append_if_missing {
($fn_name:ident, $ends_with_check:expr) => {
fn $fn_name(&self, suffix: &str) -> Cow<'_, str> {
if suffix.is_empty() || $ends_with_check(self, suffix) {
Cow::Borrowed(self)
} else {
let mut result = String::with_capacity(self.len() + suffix.len());
result.push_str(self);
result.push_str(suffix);
Cow::Owned(result)
}
}
};
}
macro_rules! impl_prepend_if_missing {
($fn_name:ident, $starts_with_check:expr) => {
fn $fn_name(&self, prefix: &str) -> Cow<'_, str> {
if prefix.is_empty() || $starts_with_check(self, prefix) {
Cow::Borrowed(self)
} else {
let mut result = String::with_capacity(self.len() + prefix.len());
result.push_str(prefix);
result.push_str(self);
Cow::Owned(result)
}
}
};
}
impl StringAffixes for str {
impl_append_if_missing!(append_if_missing, |s: &str, suf| s.ends_with(suf));
impl_append_if_missing!(
append_if_missing_ignore_ascii_case,
ends_with_ignore_ascii_case
);
impl_append_if_missing!(append_if_missing_ignore_case, ends_with_ignore_case);
impl_prepend_if_missing!(prepend_if_missing, |s: &str, pre| s.starts_with(pre));
impl_prepend_if_missing!(
prepend_if_missing_ignore_ascii_case,
starts_with_ignore_ascii_case
);
impl_prepend_if_missing!(prepend_if_missing_ignore_case, starts_with_ignore_case);
}
#[cfg(test)]
mod tests {
use super::*;
mod append_if_missing {
use super::*;
#[test]
fn empty_string() {
assert_eq!("".append_if_missing("xyz"), "xyz");
}
#[test]
fn empty_suffix() {
assert_eq!("abc".append_if_missing(""), "abc");
}
#[test]
fn suffix_not_present() {
assert_eq!("abc".append_if_missing("xyz"), "abcxyz");
}
#[test]
fn suffix_already_present() {
assert_eq!("abcxyz".append_if_missing("xyz"), "abcxyz");
}
#[test]
fn suffix_different_case() {
assert_eq!("aXYZ".append_if_missing("xyz"), "aXYZxyz");
}
#[test]
fn suffix_partial_match() {
assert_eq!("abcxy".append_if_missing("xyz"), "abcxyxyz");
}
#[test]
fn unicode_suffix_present() {
assert_eq!("café".append_if_missing("é"), "café");
}
#[test]
fn unicode_suffix_not_present() {
assert_eq!("café".append_if_missing("e"), "cafée");
}
}
mod append_if_missing_ignore_case {
use super::*;
#[test]
fn empty_string() {
assert_eq!("".append_if_missing_ignore_case("xyz"), "xyz");
}
#[test]
fn empty_suffix() {
assert_eq!("abc".append_if_missing_ignore_case(""), "abc");
}
#[test]
fn suffix_not_present() {
assert_eq!("abc".append_if_missing_ignore_case("xyz"), "abcxyz");
}
#[test]
fn suffix_already_present() {
assert_eq!("abcxyz".append_if_missing_ignore_case("xyz"), "abcxyz");
}
#[test]
fn suffix_different_case() {
assert_eq!("abcXYZ".append_if_missing_ignore_case("xyz"), "abcXYZ");
}
#[test]
fn suffix_mixed_case() {
assert_eq!("abcXyZ".append_if_missing_ignore_case("xyz"), "abcXyZ");
}
#[test]
fn unicode_suffix_same_case() {
assert_eq!("café".append_if_missing_ignore_case("é"), "café");
}
#[test]
fn unicode_suffix_different_case() {
assert_eq!("café".append_if_missing_ignore_case("É"), "café");
}
#[test]
fn unicode_suffix_different_case_inv() {
assert_eq!("cafÉ".append_if_missing_ignore_case("é"), "cafÉ");
}
}
mod prepend_if_missing {
use super::*;
#[test]
fn empty_string() {
assert_eq!("".prepend_if_missing("xyz"), "xyz");
}
#[test]
fn empty_prefix() {
assert_eq!("abc".prepend_if_missing(""), "abc");
}
#[test]
fn prefix_not_present() {
assert_eq!("abc".prepend_if_missing("xyz"), "xyzabc");
}
#[test]
fn prefix_already_present() {
assert_eq!("xyzabc".prepend_if_missing("xyz"), "xyzabc");
}
#[test]
fn prefix_different_case() {
assert_eq!("XYZabc".prepend_if_missing("xyz"), "xyzXYZabc");
}
#[test]
fn prefix_partial_match() {
assert_eq!("yzabc".prepend_if_missing("xyz"), "xyzyzabc");
}
#[test]
fn unicode_prefix_present() {
assert_eq!("élan".prepend_if_missing("é"), "élan");
}
#[test]
fn unicode_prefix_not_present() {
assert_eq!("élan".prepend_if_missing("e"), "eélan");
}
}
mod prepend_if_missing_ignore_case {
use super::*;
#[test]
fn empty_string() {
assert_eq!("".prepend_if_missing_ignore_case("xyz"), "xyz");
}
#[test]
fn empty_prefix() {
assert_eq!("abc".prepend_if_missing_ignore_case(""), "abc");
}
#[test]
fn prefix_not_present() {
assert_eq!("abc".prepend_if_missing_ignore_case("xyz"), "xyzabc");
}
#[test]
fn prefix_already_present() {
assert_eq!("xyzabc".prepend_if_missing_ignore_case("xyz"), "xyzabc");
}
#[test]
fn prefix_different_case() {
assert_eq!("XYZabc".prepend_if_missing_ignore_case("xyz"), "XYZabc");
}
#[test]
fn prefix_mixed_case() {
assert_eq!("XyZabc".prepend_if_missing_ignore_case("xyz"), "XyZabc");
}
#[test]
fn unicode_prefix_same_case() {
assert_eq!("élan".prepend_if_missing_ignore_case("é"), "élan");
}
#[test]
fn unicode_prefix_different_case() {
assert_eq!("Élan".prepend_if_missing_ignore_case("é"), "Élan");
}
#[test]
fn unicode_prefix_different_case_inv() {
assert_eq!("élan".prepend_if_missing_ignore_case("É"), "élan");
}
}
mod string_types {
use super::*;
#[test]
fn string_type() {
assert_eq!(String::from("abc").append_if_missing("xyz"), "abcxyz");
assert_eq!(String::from("abc").prepend_if_missing("xyz"), "xyzabc");
}
}
}