#[cfg(feature = "feat-string-ext-base64")]
mod base64;
#[cfg(feature = "feat-string-ext-hex")]
mod hex;
mod number;
#[cfg(feature = "feat-string-ext-rand")]
mod rand;
mod slice_sep;
use std::{borrow::Cow, ops, rc::Rc, sync::Arc};
#[cfg(feature = "feat-string-ext-base64")]
pub use base64::{b64_padding, Base64Str};
#[cfg(feature = "feat-string-ext-hex")]
pub use hex::HexStr;
pub use number::NumStr;
#[cfg(feature = "feat-string-ext-rand")]
pub use rand::{RandHexStr, RandStr};
pub use slice_sep::SliceSep;
#[macro_export]
macro_rules! str_concat {
($($x:expr),*) => {
{
let mut string_final = $crate::string::StringExt::with_capacity(512);
$(
string_final.push($x);
)*
string_final.into_string()
}
};
(str = $str:expr; $($x:expr),*) => {
{
let mut string_final = $crate::string::StringExt::from($str);
$(
string_final.push($x);
)*
string_final.into_string()
}
};
(cap = $cap:expr; $($x:expr),*) => {
{
let mut string_final = $crate::string::StringExt::with_capacity($cap);
$(
string_final.push($x);
)*
string_final.into_string()
}
};
(sep = $sep:expr; $($x:expr),*) => {
{
let mut string_final = $crate::string::StringExt::with_capacity(512);
$(
string_final.push_with_separator($x, $sep);
)*
string_final.into_string_remove_tail($sep)
}
};
}
#[derive(Debug, PartialEq, PartialOrd, Eq, Ord)]
pub struct StringExt {
inner: Vec<u8>,
}
impl StringExt {
#[inline]
#[must_use]
pub fn with_capacity(cap: usize) -> Self {
Self {
inner: Vec::with_capacity(cap),
}
}
#[inline]
pub fn from_value(value: impl StringExtT) -> Self {
let mut this = Self::with_capacity(64);
this.push(value);
this
}
#[inline]
pub fn from_value_with_separator(value: impl StringExtT, separator: impl SeparatorT) -> Self {
let mut this = Self::with_capacity(64);
this.push_with_separator(value, separator);
this
}
#[allow(unsafe_code)]
#[inline]
#[must_use]
pub unsafe fn from_utf8_unchecked(bytes: Vec<u8>) -> Self {
Self { inner: bytes }
}
#[allow(unsafe_code)]
#[inline]
#[must_use = "`self` will be dropped if the result is not used"]
pub unsafe fn into_bytes(self) -> Vec<u8> {
self.inner
}
#[inline]
#[must_use]
pub fn as_str(&self) -> &str {
#[allow(unsafe_code)]
unsafe {
std::str::from_utf8_unchecked(self.inner.as_slice())
}
}
#[inline]
#[must_use]
pub fn as_mut_str(&mut self) -> &mut str {
#[allow(unsafe_code)]
unsafe {
std::str::from_utf8_unchecked_mut(self.inner.as_mut_slice())
}
}
#[inline]
pub fn push(&mut self, value: impl StringExtT) {
value.push_to_string(&mut self.inner);
}
#[inline]
pub fn push_with_separator(&mut self, value: impl StringExtT, separator: impl SeparatorT) {
value.push_to_string_with_separator(&mut self.inner, separator);
}
#[inline]
#[must_use]
pub fn capacity(&self) -> usize {
self.inner.capacity()
}
#[inline]
pub fn reserve(&mut self, additional: usize) {
self.inner.reserve(additional);
}
#[inline]
pub fn reserve_exact(&mut self, additional: usize) {
self.inner.reserve_exact(additional);
}
#[inline]
pub fn as_bytes(&self) -> &[u8] {
&self.inner
}
#[inline]
#[must_use]
pub fn len(&self) -> usize {
self.inner.len()
}
#[inline]
#[must_use]
pub fn is_empty(&self) -> bool {
self.inner.is_empty()
}
#[allow(unsafe_code)]
#[inline]
pub unsafe fn as_mut_vec(&mut self) -> &mut Vec<u8> {
&mut self.inner
}
#[inline]
pub fn into_string(self) -> String {
#[allow(unsafe_code)]
unsafe {
String::from_utf8_unchecked(self.inner)
}
}
#[inline]
pub fn into_string_remove_tail(mut self, separator: impl SeparatorT) -> String {
separator.remove_end(&mut self.inner);
self.into_string()
}
}
impl From<String> for StringExt {
#[inline]
fn from(inner: String) -> Self {
Self {
inner: inner.into_bytes(),
}
}
}
impl From<&str> for StringExt {
#[inline]
fn from(inner: &str) -> Self {
let mut new_inner = Vec::with_capacity(inner.len() + 128);
new_inner.extend(inner.as_bytes());
Self { inner: new_inner }
}
}
impl From<&String> for StringExt {
#[inline]
fn from(inner: &String) -> Self {
let mut new_inner = Vec::with_capacity(inner.len() + 128);
new_inner.extend(inner.as_bytes());
Self { inner: new_inner }
}
}
impl AsRef<str> for StringExt {
#[inline]
fn as_ref(&self) -> &str {
self.as_str()
}
}
impl AsRef<[u8]> for StringExt {
#[inline]
fn as_ref(&self) -> &[u8] {
&self.inner
}
}
impl ops::Deref for StringExt {
type Target = str;
#[inline]
fn deref(&self) -> &str {
self.as_str()
}
}
#[cfg(feature = "feat-string-ext-axum")]
impl axum_core::response::IntoResponse for StringExt {
#[inline]
fn into_response(mut self) -> axum_core::response::Response {
self.inner.truncate(self.inner.len());
axum_core::response::Response::new(bytes::Bytes::from(self.inner).into())
}
}
#[cfg(feature = "feat-string-ext-http")]
impl TryInto<http::HeaderName> for StringExt {
type Error = http::header::InvalidHeaderName;
fn try_into(self) -> Result<http::HeaderName, Self::Error> {
http::HeaderName::from_bytes(&self.inner)
}
}
#[cfg(feature = "feat-string-ext-http")]
impl TryInto<http::HeaderValue> for StringExt {
type Error = http::header::InvalidHeaderValue;
fn try_into(mut self) -> Result<http::HeaderValue, Self::Error> {
self.inner.truncate(self.inner.len());
http::HeaderValue::from_maybe_shared(bytes::Bytes::from(self.inner))
}
}
pub trait StringExtT: Sized {
fn push_to_string(self, string: &mut Vec<u8>);
#[inline]
fn push_to_string_with_separator(self, string: &mut Vec<u8>, separator: impl SeparatorT) {
self.push_to_string(string);
separator.push_to_string(string);
}
#[inline]
fn to_string_ext(self) -> String {
let mut string = StringExt::with_capacity(128);
string.push(self);
string.into_string()
}
#[inline]
fn to_string_ext_with_sep(self, separator: impl SeparatorT) -> String {
let mut string = StringExt::with_capacity(128);
string.push_with_separator(self, separator);
string.into_string_remove_tail(separator)
}
#[inline]
fn with_prefix<P: StringExtT>(self, prefix: P) -> impl StringExtT {
SeplessTuple((prefix, self))
}
#[inline]
fn with_suffix<S: StringExtT>(self, suffix: S) -> impl StringExtT {
SeplessTuple((self, suffix))
}
}
impl StringExtT for () {
#[inline]
fn push_to_string(self, _: &mut Vec<u8>) {}
#[inline]
fn push_to_string_with_separator(self, _string: &mut Vec<u8>, _separator: impl SeparatorT) {}
#[inline]
fn to_string_ext(self) -> String {
String::new()
}
#[inline]
fn to_string_ext_with_sep(self, _separator: impl SeparatorT) -> String {
String::new()
}
}
impl StringExtT for bool {
#[inline]
fn push_to_string(self, string: &mut Vec<u8>) {
string.extend(if self { &b"true"[..] } else { &b"false"[..] });
}
}
impl StringExtT for char {
#[inline]
fn push_to_string(self, string: &mut Vec<u8>) {
match self.len_utf8() {
1 => string.push(self as u8),
_ => string.extend(self.encode_utf8(&mut [0; 4]).as_bytes()),
}
}
}
macro_rules! impl_for_string {
($($ty:ty),*) => {
$(
impl_for_string!(INTERNAL IMPL $ty);
impl_for_string!(INTERNAL IMPL &$ty);
impl_for_string!(INTERNAL IMPL &mut $ty);
impl_for_string!(INTERNAL IMPL &&$ty);
impl_for_string!(INTERNAL IMPL &&mut $ty);
impl_for_string!(INTERNAL IMPL &mut &$ty); impl_for_string!(INTERNAL IMPL &mut &mut $ty);
)*
};
(INTERNAL IMPL $ty:ty) => {
impl StringExtT for $ty {
#[inline]
fn push_to_string(self, string: &mut Vec<u8>) {
string.extend(self.as_bytes());
}
#[inline]
fn to_string_ext(self) -> String {
self.to_string()
}
#[inline]
fn to_string_ext_with_sep(self, separator: impl SeparatorT) -> String {
let mut string = StringExt::with_capacity(self.len() + 4);
string.push_with_separator(self, separator);
string.into_string_remove_tail(separator)
}
}
}
}
impl_for_string!(
&str,
String,
Rc<str>,
Rc<String>,
Arc<str>,
Arc<String>,
Cow<'_, str>
);
impl<T> StringExtT for &[T]
where
T: StringExtT + Copy,
{
#[inline]
fn push_to_string(self, string: &mut Vec<u8>) {
for item in self {
item.push_to_string(string);
}
}
#[inline]
fn push_to_string_with_separator(self, string: &mut Vec<u8>, separator: impl SeparatorT) {
let is_empty = self.is_empty();
for item in self {
item.push_to_string_with_separator(string, separator);
}
if !is_empty {
separator.remove_end(string);
separator.push_to_string(string)
}
}
}
impl<T, const N: usize> StringExtT for [T; N]
where
T: StringExtT,
{
#[inline]
fn push_to_string(self, string: &mut Vec<u8>) {
for item in self {
item.push_to_string(string);
}
}
#[inline]
fn push_to_string_with_separator(self, string: &mut Vec<u8>, separator: impl SeparatorT) {
for item in self {
item.push_to_string_with_separator(string, separator);
}
if N != 0 {
separator.remove_end(string);
separator.push_to_string(string)
}
}
}
impl<T, const N: usize> StringExtT for &[T; N]
where
T: StringExtT + Copy,
{
#[inline]
fn push_to_string(self, string: &mut Vec<u8>) {
for item in self {
item.push_to_string(string);
}
}
#[inline]
fn push_to_string_with_separator(self, string: &mut Vec<u8>, separator: impl SeparatorT) {
for item in self {
item.push_to_string_with_separator(string, separator);
}
if N != 0 {
separator.remove_end(string);
separator.push_to_string(string)
}
}
}
impl<T> StringExtT for Vec<T>
where
T: StringExtT,
{
#[inline]
fn push_to_string(self, string: &mut Vec<u8>) {
for item in self {
item.push_to_string(string);
}
}
#[inline]
fn push_to_string_with_separator(self, string: &mut Vec<u8>, separator: impl SeparatorT) {
let is_empty = self.is_empty();
for item in self {
item.push_to_string_with_separator(string, separator);
}
if !is_empty {
separator.remove_end(string);
separator.push_to_string(string)
}
}
}
impl<T, I, F> StringExtT for std::iter::Map<I, F>
where
T: StringExtT,
I: Iterator,
F: FnMut(I::Item) -> T,
{
#[inline]
fn push_to_string(self, string: &mut Vec<u8>) {
self.into_iter()
.for_each(|item| item.push_to_string(string));
}
#[inline]
fn push_to_string_with_separator(self, string: &mut Vec<u8>, separator: impl SeparatorT) {
let mut is_empty = true;
self.into_iter().for_each(|item| {
is_empty = false;
item.push_to_string_with_separator(string, separator);
});
if !is_empty {
separator.remove_end(string);
separator.push_to_string(string)
}
}
}
impl<T> StringExtT for Option<T>
where
T: StringExtT,
{
#[inline]
fn push_to_string(self, string: &mut Vec<u8>) {
if let Some(item) = self {
item.push_to_string(string);
}
}
#[inline]
fn push_to_string_with_separator(self, string: &mut Vec<u8>, separator: impl SeparatorT) {
if let Some(item) = self {
item.push_to_string_with_separator(string, separator);
}
}
#[inline]
fn with_prefix<P: StringExtT>(self, prefix: P) -> impl StringExtT {
self.map(|item| SeplessTuple((prefix, item)))
}
#[inline]
fn with_suffix<S: StringExtT>(self, suffix: S) -> impl StringExtT {
self.map(|item| SeplessTuple((item, suffix)))
}
}
impl<T, E> StringExtT for Result<T, E>
where
T: StringExtT,
{
#[inline]
fn push_to_string(self, string: &mut Vec<u8>) {
if let Ok(item) = self {
item.push_to_string(string);
}
}
#[inline]
fn push_to_string_with_separator(self, string: &mut Vec<u8>, separator: impl SeparatorT) {
if let Ok(item) = self {
item.push_to_string_with_separator(string, separator);
}
}
#[inline]
fn with_prefix<P: StringExtT>(self, prefix: P) -> impl StringExtT {
self.map(|item| SeplessTuple((prefix, item)))
}
#[inline]
fn with_suffix<S: StringExtT>(self, suffix: S) -> impl StringExtT {
self.map(|item| SeplessTuple((item, suffix)))
}
}
#[derive(Debug, Clone)]
#[repr(transparent)]
pub struct SeplessTuple<T: StringExtT>(pub T);
macro_rules! impl_for_tuple {
( $( $name:ident )+ ) => {
#[allow(non_snake_case)]
impl<$($name: StringExtT),+> StringExtT for ($($name,)+)
{
#[inline]
fn push_to_string(self, string: &mut Vec<u8>) {
let ($($name,)+) = self;
$($name.push_to_string(string);)+
}
#[inline]
fn push_to_string_with_separator(self, string: &mut Vec<u8>, separator: impl SeparatorT) {
let ($($name,)+) = self;
$(
$name.push_to_string_with_separator(string, separator);
)+
}
}
#[allow(non_snake_case)]
impl<$($name: StringExtT),+> StringExtT for SeplessTuple<($($name,)+)>
{
#[inline]
fn push_to_string(self, string: &mut Vec<u8>) {
let ($($name,)+) = self.0;
$($name.push_to_string(string);)+
}
}
};
}
impl_for_tuple!(T1);
impl_for_tuple!(T1 T2);
impl_for_tuple!(T1 T2 T3);
impl_for_tuple!(T1 T2 T3 T4);
impl_for_tuple!(T1 T2 T3 T4 T5);
impl_for_tuple!(T1 T2 T3 T4 T5 T6);
impl_for_tuple!(T1 T2 T3 T4 T5 T6 T7);
impl_for_tuple!(T1 T2 T3 T4 T5 T6 T7 T8);
impl_for_tuple!(T1 T2 T3 T4 T5 T6 T7 T8 T9);
impl_for_tuple!(T1 T2 T3 T4 T5 T6 T7 T8 T9 T10);
impl_for_tuple!(T1 T2 T3 T4 T5 T6 T7 T8 T9 T10 T11);
impl_for_tuple!(T1 T2 T3 T4 T5 T6 T7 T8 T9 T10 T11 T12);
impl_for_tuple!(T1 T2 T3 T4 T5 T6 T7 T8 T9 T10 T11 T12 T13);
impl_for_tuple!(T1 T2 T3 T4 T5 T6 T7 T8 T9 T10 T11 T12 T13 T14);
impl_for_tuple!(T1 T2 T3 T4 T5 T6 T7 T8 T9 T10 T11 T12 T13 T14 T15);
impl_for_tuple!(T1 T2 T3 T4 T5 T6 T7 T8 T9 T10 T11 T12 T13 T14 T15 T16);
impl_for_tuple!(T1 T2 T3 T4 T5 T6 T7 T8 T9 T10 T11 T12 T13 T14 T15 T16 T17);
impl_for_tuple!(T1 T2 T3 T4 T5 T6 T7 T8 T9 T10 T11 T12 T13 T14 T15 T16 T17 T18);
impl_for_tuple!(T1 T2 T3 T4 T5 T6 T7 T8 T9 T10 T11 T12 T13 T14 T15 T16 T17 T18 T19);
impl_for_tuple!(T1 T2 T3 T4 T5 T6 T7 T8 T9 T10 T11 T12 T13 T14 T15 T16 T17 T18 T19 T20);
impl_for_tuple!(T1 T2 T3 T4 T5 T6 T7 T8 T9 T10 T11 T12 T13 T14 T15 T16 T17 T18 T19 T20 T21);
impl_for_tuple!(T1 T2 T3 T4 T5 T6 T7 T8 T9 T10 T11 T12 T13 T14 T15 T16 T17 T18 T19 T20 T21 T22);
impl_for_tuple!(T1 T2 T3 T4 T5 T6 T7 T8 T9 T10 T11 T12 T13 T14 T15 T16 T17 T18 T19 T20 T21 T22 T23);
impl_for_tuple!(T1 T2 T3 T4 T5 T6 T7 T8 T9 T10 T11 T12 T13 T14 T15 T16 T17 T18 T19 T20 T21 T22 T23 T24);
impl_for_tuple!(T1 T2 T3 T4 T5 T6 T7 T8 T9 T10 T11 T12 T13 T14 T15 T16 T17 T18 T19 T20 T21 T22 T23 T24 T25);
impl_for_tuple!(T1 T2 T3 T4 T5 T6 T7 T8 T9 T10 T11 T12 T13 T14 T15 T16 T17 T18 T19 T20 T21 T22 T23 T24 T25 T26);
impl_for_tuple!(T1 T2 T3 T4 T5 T6 T7 T8 T9 T10 T11 T12 T13 T14 T15 T16 T17 T18 T19 T20 T21 T22 T23 T24 T25 T26 T27);
macro_rules! impl_ref_deref {
($($ty:ty),*) => {
$(
impl StringExtT for &$ty {
#[inline]
fn push_to_string(self, string: &mut Vec<u8>) {
(*self).push_to_string(string);
}
#[inline]
fn push_to_string_with_separator(self, string: &mut Vec<u8>, separator: impl SeparatorT) {
(*self).push_to_string_with_separator(string, separator);
}
#[inline]
fn to_string_ext(self) -> String {
(*self).to_string_ext()
}
#[inline]
fn to_string_ext_with_sep(self, separator: impl SeparatorT) -> String {
(*self).to_string_ext_with_sep(separator)
}
}
impl StringExtT for &mut $ty {
#[inline]
fn push_to_string(self, string: &mut Vec<u8>) {
(*self).push_to_string(string);
}
#[inline]
fn push_to_string_with_separator(self, string: &mut Vec<u8>, separator: impl SeparatorT) {
(*self).push_to_string_with_separator(string, separator);
}
#[inline]
fn to_string_ext(self) -> String {
(*self).to_string_ext()
}
#[inline]
fn to_string_ext_with_sep(self, separator: impl SeparatorT) -> String {
(*self).to_string_ext_with_sep(separator)
}
}
)*
};
($($($ge:ident),* => $ty:ty),*) => {
$(
impl<$($ge: StringExtT)*> StringExtT for $ty {
#[inline]
fn push_to_string(self, string: &mut Vec<u8>) {
(*self).push_to_string(string);
}
#[inline]
fn push_to_string_with_separator(self, string: &mut Vec<u8>, separator: impl SeparatorT) {
(*self).push_to_string_with_separator(string, separator);
}
#[inline]
fn to_string_ext(self) -> String {
(*self).to_string_ext()
}
#[inline]
fn to_string_ext_with_sep(self, separator: impl SeparatorT) -> String {
(*self).to_string_ext_with_sep(separator)
}
}
)*
};
}
impl_ref_deref!(bool, &bool, char, &char);
impl_ref_deref!(u8, u16, u32, u64, u128, usize, i8, i16, i32, i64, i128, isize, f32, f64);
impl_ref_deref!(
&u8, &u16, &u32, &u64, &u128, &usize, &i8, &i16, &i32, &i64, &i128, &isize, &f32, &f64
);
impl_ref_deref!(T => Box<T>);
#[cfg(feature = "feat-string-ext-ammonia")]
impl StringExtT for ammonia::Document {
fn push_to_string(self, string: &mut Vec<u8>) {
self.write_to(string)
.expect("Writing to a string should not fail (except on OOM)");
}
}
#[cfg(feature = "feat-string-ext-chrono")]
impl<'a, I: Iterator<Item = B> + Clone, B: std::borrow::Borrow<chrono::format::Item<'a>>> StringExtT
for chrono::format::DelayedFormat<I>
{
fn push_to_string(self, string: &mut Vec<u8>) {
string.extend(self.to_string().as_bytes());
}
}
#[cfg(feature = "feat-string-ext-http")]
impl StringExtT for http::HeaderName {
fn push_to_string(self, string: &mut Vec<u8>) {
string.extend(self.as_str().as_bytes());
}
}
#[cfg(feature = "feat-string-ext-http")]
impl StringExtT for http::Method {
fn push_to_string(self, string: &mut Vec<u8>) {
string.extend(self.as_str().as_bytes());
}
}
#[cfg(feature = "feat-string-ext-http")]
impl StringExtT for http::StatusCode {
fn push_to_string(self, string: &mut Vec<u8>) {
string.extend(self.as_str().as_bytes());
}
}
#[cfg(feature = "feat-string-ext-http")]
impl StringExtT for http::Uri {
fn push_to_string(self, string: &mut Vec<u8>) {
if let Some(scheme) = self.scheme() {
string.extend(scheme.as_str().as_bytes());
string.extend(b"://");
}
if let Some(authority) = self.authority() {
string.extend(authority.as_str().as_bytes());
}
string.extend(self.path().as_bytes());
if let Some(query) = self.query() {
string.push(b'?');
string.extend(query.as_bytes());
}
}
}
#[cfg(feature = "feat-string-ext-http")]
impl StringExtT for http::Version {
fn push_to_string(self, string: &mut Vec<u8>) {
let str_byte = match self {
http::Version::HTTP_09 => &b"HTTP/0.9"[..],
http::Version::HTTP_10 => &b"HTTP/1.0"[..],
http::Version::HTTP_11 => &b"HTTP/1.1"[..],
http::Version::HTTP_2 => &b"HTTP/2.0"[..],
http::Version::HTTP_3 => &b"HTTP/3.0"[..],
_ => {
string.extend(format!("{self:?}").as_bytes());
return;
}
};
string.extend(str_byte);
}
}
#[allow(clippy::len_without_is_empty)]
pub trait SeparatorT: StringExtT + Copy {
fn remove_end(&self, string: &mut Vec<u8>);
}
impl SeparatorT for char {
#[inline]
fn remove_end(&self, string: &mut Vec<u8>) {
let len = self.len_utf8();
match len {
1 => {
if string.last() == Some(&(*self as u8)) {
string.pop();
}
}
_ => {
let buf = &mut [0; 4];
let buf = self.encode_utf8(buf);
if string.get(string.len() - len..) == Some(buf.as_bytes()) {
string.truncate(string.len() - 4);
}
}
}
}
}
impl SeparatorT for &str {
#[inline]
fn remove_end(&self, string: &mut Vec<u8>) {
string.truncate(string.len() - self.len());
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_prefix_or_suffix() {
let exp1 = "world".with_prefix("hello");
assert_eq!(exp1.to_string_ext(), "helloworld");
let exp2 = str_concat!(sep = ' '; ("hello", "world"));
assert_eq!(exp2, "hello world");
let exp3 = str_concat!(
sep = ' ';
("hello", "world"),
"world".with_prefix("prefix-"),
"2world".with_prefix("2prefix-"),
"hello".with_suffix(Some("-suffix")),
"3hello".with_suffix(None::<()>),
None::<()>.with_suffix("-suffix").with_prefix("prefix-")
);
assert_eq!(
exp3,
"hello world prefix-world 2prefix-2world hello-suffix 3hello"
);
}
#[test]
fn test_string_ext() {
let mut s = StringExt::with_capacity(32);
s.push('[');
s.push('�');
s.push('😀');
s.push(['A']);
s.push(&['a', '1'][..]);
s.push(']');
s.push(vec!["[Hello World � 😀]", "[ä½ å¥½ � 😀]"]);
s.push_with_separator("[Hello World � 😀".to_owned(), ']');
s.push(Some("[ä½ å¥½ � 😀]".to_owned()));
s.push(Cow::Borrowed("[Hello World � 😀]"));
s.push(Cow::Owned("[ä½ å¥½ � 😀]".to_string()));
s.push(0u8);
s.push(123usize);
s.push(45u8);
s.push(6789u16);
s.push(123456789u32);
s.push(123456789i32);
s.push(Box::new(-123456789i32));
s.push(NumStr::hex_default(0xabcdefusize));
s.push(NumStr::hex_default(0xabcdefusize).set_uppercase::<true>());
assert_eq!(
s.as_str(),
"[�😀Aa1][Hello World � 😀][ä½ å¥½ � 😀][Hello World � 😀][ä½ å¥½ � 😀][Hello World � \
😀][ä½ å¥½ � 😀]0123456789123456789123456789-123456789abcdefABCDEF"
);
}
#[test]
fn test_separator() {
let mut s = StringExt::with_capacity(128);
s.push_with_separator("Hello", ',');
s.push_with_separator("World", ',');
s.push_with_separator(vec!["ä½ å¥½", "世界"], ',');
s.push_with_separator(Some(vec![vec!["ä½ å¥½"], vec!["世界"]]), ',');
s.push_with_separator(Some(&["ä½ å¥½", "世界"]), ',');
s.push_with_separator(
(
"ä½ å¥½",
"世界",
vec!["ä½ å¥½", "世界"],
Some(&["ä½ å¥½", "世界"]),
None::<&str>,
),
',',
);
assert_eq!(
s.into_string_remove_tail(','),
"Hello,World,ä½ å¥½,世界,ä½ å¥½,世界,ä½ å¥½,世界,ä½ å¥½,世界,ä½ å¥½,世界,ä½ å¥½,世界"
);
}
}