mod iterators;
use crate::{decode_byte_pair, fmt, zalgo_encode, EncodeError};
use core::{ops::Index, slice::SliceIndex};
pub use iterators::{DecodedBytes, DecodedChars};
#[cfg(feature = "rkyv")]
use rkyv::bytecheck::{
rancor::{fail, Fallible, Source},
CheckBytes, Verify,
};
use alloc::{borrow::Cow, string::String, vec::Vec};
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serde", serde(try_from = "MaybeZalgoString"))]
#[cfg_attr(
feature = "rkyv",
derive(rkyv::Serialize, rkyv::Deserialize, rkyv::Archive, CheckBytes)
)]
#[cfg_attr(feature = "rkyv", bytecheck(verify))]
pub struct ZalgoString(String);
#[cfg(feature = "rkyv")]
unsafe impl<C> Verify<C> for ZalgoString
where
C: Fallible + ?Sized,
C::Error: Source,
{
#[inline]
fn verify(&self, _context: &mut C) -> Result<(), C::Error> {
if let Err(e) = crate::zalgo_decode(&self.0) {
fail!(e);
}
Ok(())
}
}
#[cfg(feature = "serde")]
#[cfg_attr(feature = "serde", derive(serde::Deserialize))]
struct MaybeZalgoString(String);
#[cfg(feature = "serde")]
impl TryFrom<MaybeZalgoString> for ZalgoString {
type Error = crate::DecodeError;
fn try_from(MaybeZalgoString(encoded_string): MaybeZalgoString) -> Result<Self, Self::Error> {
if let Err(e) = crate::zalgo_decode(&encoded_string) {
Err(e)
} else {
Ok(ZalgoString(encoded_string))
}
}
}
impl Default for ZalgoString {
#[inline]
fn default() -> Self {
Self::new()
}
}
impl ZalgoString {
#[inline]
pub const fn new() -> Self {
Self(String::new())
}
#[inline]
#[must_use = "this associated method return a new `ZalgoString` and does not modify the input"]
pub fn with_capacity(capacity: usize) -> Self {
Self(String::with_capacity(capacity))
}
#[inline]
#[must_use = "the method returns a reference and does not modify `self`"]
pub fn as_str(&self) -> &str {
&self.0
}
#[inline]
pub fn get<I>(&self, index: I) -> Option<&<I as SliceIndex<str>>::Output>
where
I: SliceIndex<str>,
{
self.0.get(index)
}
#[inline]
pub unsafe fn get_unchecked<I>(&self, index: I) -> &<I as SliceIndex<str>>::Output
where
I: SliceIndex<str>,
{
self.0.get_unchecked(index)
}
#[inline]
pub fn chars(&self) -> core::str::Chars<'_> {
self.0.chars()
}
#[inline]
pub fn char_indices(&self) -> core::str::CharIndices<'_> {
self.0.char_indices()
}
#[inline]
pub fn decoded_chars(&self) -> DecodedChars<'_> {
DecodedChars::new(self)
}
#[inline]
#[must_use = "`self` will be dropped if the result is not used"]
pub fn into_string(self) -> String {
self.0
}
#[must_use = "`self` will be dropped if the result is not used"]
pub fn into_decoded_string(self) -> String {
unsafe { String::from_utf8_unchecked(self.into_decoded_bytes()) }
}
#[inline]
#[must_use = "the method returns a reference and does not modify `self`"]
pub fn as_bytes(&self) -> &[u8] {
self.0.as_bytes()
}
#[inline]
pub fn bytes(&self) -> core::str::Bytes<'_> {
self.0.bytes()
}
#[inline]
pub fn decoded_bytes(&self) -> DecodedBytes<'_> {
DecodedBytes::new(self)
}
#[inline]
#[must_use = "`self` will be dropped if the result is not used"]
pub fn into_bytes(self) -> Vec<u8> {
self.0.into_bytes()
}
#[must_use = "`self` will be dropped if the result is not used"]
pub fn into_decoded_bytes(self) -> Vec<u8> {
let mut w = 0;
let mut bytes = self.into_bytes();
for r in (0..bytes.len()).step_by(2) {
bytes[w] = decode_byte_pair(bytes[r], bytes[r + 1]);
w += 1;
}
bytes.truncate(w);
bytes
}
#[inline]
#[must_use = "the method returns a new value and does not modify `self`"]
pub fn len(&self) -> usize {
self.0.len()
}
#[inline]
#[must_use = "the method returns a new value and does not modify `self`"]
pub fn capacity(&self) -> usize {
self.0.capacity()
}
#[inline]
#[must_use = "the method returns a new value and does not modify `self`"]
pub fn decoded_len(&self) -> usize {
self.len() / 2
}
#[inline]
#[must_use = "the method returns a new value and does not modify `self`"]
pub fn is_empty(&self) -> bool {
self.len() == 0
}
#[inline]
pub fn push_zalgo_str(&mut self, zalgo_string: &Self) {
self.0.push_str(zalgo_string.as_str());
}
pub fn encode_and_push_str(&mut self, string: &str) -> Result<(), EncodeError> {
self.push_zalgo_str(&ZalgoString::try_from(string)?);
Ok(())
}
#[inline]
pub fn reserve(&mut self, additional: usize) {
self.0.reserve(additional)
}
#[inline]
pub fn reserve_exact(&mut self, additional: usize) {
self.0.reserve_exact(additional)
}
#[inline]
pub fn truncate(&mut self, new_len: usize) {
if new_len <= self.len() {
assert_eq!(new_len % 2, 0, "the new length must be even");
self.0.truncate(new_len)
}
}
pub fn clear(&mut self) {
self.0.clear()
}
}
impl TryFrom<&str> for ZalgoString {
type Error = EncodeError;
fn try_from(s: &str) -> Result<Self, Self::Error> {
zalgo_encode(s).map(Self)
}
}
impl core::ops::Add<&ZalgoString> for ZalgoString {
type Output = ZalgoString;
#[inline]
fn add(mut self, rhs: &Self) -> Self::Output {
self.push_zalgo_str(rhs);
self
}
}
impl core::ops::AddAssign<&ZalgoString> for ZalgoString {
#[inline]
fn add_assign(&mut self, rhs: &ZalgoString) {
self.push_zalgo_str(rhs);
}
}
macro_rules! impl_partial_eq {
($($rhs:ty),+) => {
$(
impl PartialEq<$rhs> for ZalgoString {
#[inline]
fn eq(&self, other: &$rhs) -> bool {
&self.0 == other
}
}
impl PartialEq<ZalgoString> for $rhs {
#[inline]
fn eq(&self, other: &ZalgoString) -> bool {
self == &other.0
}
}
)+
};
}
impl_partial_eq! {String, &str, str, Cow<'_, str>}
impl fmt::Display for ZalgoString {
#[inline]
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", self.0)
}
}
impl<I: SliceIndex<str>> Index<I> for ZalgoString {
type Output = I::Output;
#[inline]
fn index(&self, index: I) -> &Self::Output {
self.0.as_str().index(index)
}
}
#[cfg(test)]
mod test {
use super::*;
use alloc::{
format,
string::{String, ToString},
};
#[test]
fn check_into_decoded_string() {
let s = "Zalgo\n He comes!";
let zs: ZalgoString = ZalgoString::try_from(s).unwrap();
assert_eq!(zs.into_decoded_string(), s);
let zs = ZalgoString::new();
assert_eq!(zs.into_decoded_string(), String::new());
}
#[test]
fn check_string_from_zalgo_string() {
let zs = ZalgoString::try_from("Zalgo\n He comes!").unwrap();
assert_eq!(zs.to_string(), "̺͇́͌͏̨ͯ̀̀̓ͅ͏͍͓́ͅ");
assert_eq!(zs.into_string(), "̺͇́͌͏̨ͯ̀̀̓ͅ͏͍͓́ͅ");
let zs = ZalgoString::new();
assert_eq!(zs.into_string(), String::new());
}
#[test]
fn check_partial_eq() {
let enc = "̺͇́͌͏̨ͯ̀̀̓ͅ͏͍͓́ͅ";
let zs = ZalgoString::try_from("Zalgo\n He comes!").unwrap();
assert_eq!(zs, enc);
assert_eq!(zs, String::from(enc));
assert_eq!(zs, Cow::from(enc));
assert_eq!(String::from(enc), zs);
assert_eq!(Cow::from(enc), zs);
}
#[test]
fn check_push_str() {
let s1 = "Zalgo";
let s2 = ", He comes";
let mut zs = ZalgoString::try_from(s1).unwrap();
let zs2 = ZalgoString::try_from(s2).unwrap();
zs.push_zalgo_str(&zs2);
assert_eq!(zs.clone().into_decoded_string(), format!("{s1}{s2}"));
zs += &zs2;
assert_eq!(
(zs + &zs2).into_decoded_string(),
format!("{s1}{s2}{s2}{s2}")
);
}
#[test]
fn check_as_str() {
assert_eq!(
ZalgoString::try_from("Hi").unwrap().as_str(),
"\u{328}\u{349}"
);
assert_eq!(ZalgoString::try_from("").unwrap().as_str(), "");
}
#[test]
fn check_decoded_chars() {
let zs = ZalgoString::try_from("Zalgo").unwrap();
assert_eq!("oglaZ", zs.decoded_chars().rev().collect::<String>());
}
#[test]
fn test_reserve() {
let mut zs = ZalgoString::try_from("Zalgo").unwrap();
zs.reserve(5);
assert!(zs.capacity() >= 10 + 5);
let c = zs.capacity();
zs.reserve(1);
assert_eq!(zs.capacity(), c);
}
#[test]
fn test_reserve_exact() {
let mut zs = ZalgoString::try_from("Zalgo").unwrap();
zs.reserve_exact(5);
assert_eq!(zs.capacity(), 10 + 5);
let c = zs.capacity();
zs.reserve_exact(1);
assert_eq!(zs.capacity(), c);
}
#[test]
fn test_truncate() {
let mut zs = ZalgoString::try_from("Zalgo").unwrap();
zs.truncate(100);
assert_eq!(zs, "\u{33a}\u{341}\u{34c}\u{347}\u{34f}");
zs.truncate(4);
assert_eq!(zs, "\u{33a}\u{341}");
assert_eq!(zs.into_decoded_string(), "Za");
}
#[test]
#[should_panic]
fn test_truncate_panic() {
let mut zs = ZalgoString::try_from("Zalgo").unwrap();
zs.truncate(1)
}
#[test]
fn test_default() {
assert_eq!(ZalgoString::try_from("").unwrap(), ZalgoString::default());
}
#[test]
fn test_with_capacity() {
let mut zs = ZalgoString::with_capacity(10.try_into().unwrap());
assert_eq!(zs.capacity(), 10);
zs.encode_and_push_str("Hi!").unwrap();
assert_eq!(zs.capacity(), 10);
zs.encode_and_push_str("I am a dinosaur!").unwrap();
assert!(zs.capacity() > 10);
}
#[test]
fn test_as_str() {
fn test_fn(_: &str) {}
let s = "Zalgo";
let zs = ZalgoString::try_from(s).unwrap();
let encd = zalgo_encode(s).unwrap();
test_fn(zs.as_str());
assert_eq!(zs.as_str(), encd);
}
#[test]
fn test_chars() {
let s = "Zalgo";
let zs = ZalgoString::try_from(s).unwrap();
let encd = zalgo_encode(s).unwrap();
for (a, b) in zs.chars().zip(encd.chars()) {
assert_eq!(a, b);
}
assert_eq!(zs.chars().nth(1), Some('\u{341}'));
}
#[test]
fn test_char_indices() {
let s = "Zalgo";
let zs = ZalgoString::try_from(s).unwrap();
let encd = zalgo_encode(s).unwrap();
for (a, b) in zs.char_indices().zip(encd.char_indices()) {
assert_eq!(a, b);
}
assert_eq!(zs.char_indices().nth(1), Some((2, '\u{341}')));
}
#[test]
fn test_as_bytes() {
let zs = ZalgoString::try_from("Zalgo").unwrap();
assert_eq!(
zs.as_bytes(),
&[204, 186, 205, 129, 205, 140, 205, 135, 205, 143]
);
}
#[test]
fn test_bytes() {
let zs = ZalgoString::try_from("Zalgo").unwrap();
assert_eq!(zs.bytes().next(), Some(204));
assert_eq!(zs.bytes().nth(2), Some(205));
}
#[test]
fn test_is_empty() {
let zs = ZalgoString::try_from("Zalgo").unwrap();
assert!(!zs.is_empty());
assert!(ZalgoString::default().is_empty());
}
#[test]
fn test_encode_and_push_str() {
let mut zs = ZalgoString::default();
assert!(zs.encode_and_push_str("Zalgo").is_ok());
assert!(zs.encode_and_push_str("Å").is_err());
assert_eq!(zs.into_decoded_string(), "Zalgo");
}
#[test]
fn test_clear() {
let mut zs = ZalgoString::try_from("Zalgo").unwrap();
let c = zs.capacity();
zs.clear();
assert_eq!(zs.capacity(), c);
assert_eq!(zs.len(), 0);
assert_eq!(zs.decoded_len(), 0);
assert!(zs.is_empty());
assert!(zs.into_decoded_string().is_empty());
}
#[test]
fn test_get() {
let zs = ZalgoString::try_from("Zalgo").unwrap();
assert_eq!(zs.get(0..2), Some("\u{33a}"));
assert!(zs.get(0..1).is_none());
assert!(zs.get(0..42).is_none());
}
#[test]
fn test_get_unchecked() {
let zs = ZalgoString::try_from("Zalgo").unwrap();
unsafe {
assert_eq!(zs.get_unchecked(..2), "\u{33a}");
}
}
#[test]
fn test_indexing() {
let zs = ZalgoString::try_from("Zalgo").unwrap();
assert_eq!(&zs[0..2], "\u{33a}");
assert_eq!(&zs[..2], "\u{33a}");
assert_eq!(&zs[0..=1], "\u{33a}");
assert_eq!(&zs[..=1], "\u{33a}");
assert_eq!(zs[..], zs);
}
#[test]
#[should_panic]
fn test_index_panic() {
let zs = ZalgoString::try_from("Zalgo").unwrap();
let _a = &zs[0..3];
}
#[test]
fn test_decoded_bytes() {
let zs = ZalgoString::try_from("Zalgo").unwrap();
assert_eq!(zs.decoded_bytes().next(), Some(b'Z'));
assert_eq!(zs.decoded_bytes().nth(2), Some(b'l'));
assert_eq!(zs.decoded_bytes().last(), Some(b'o'));
let mut dcb = zs.decoded_bytes();
assert_eq!(dcb.next(), Some(b'Z'));
let dcb2 = dcb.clone();
assert_eq!(dcb.count(), 4);
assert_eq!(dcb2.last(), Some(b'o'));
}
#[test]
fn test_decoded_chars() {
let zs = ZalgoString::try_from("Zalgo").unwrap();
assert_eq!(zs.decoded_chars().next(), Some('Z'));
assert_eq!(zs.decoded_chars().nth(2), Some('l'));
assert_eq!(zs.decoded_chars().last(), Some('o'));
let mut dcc = zs.decoded_chars();
assert_eq!(dcc.next(), Some('Z'));
let dcc2 = dcc.clone();
assert_eq!(dcc.count(), 4);
assert_eq!(dcc2.last(), Some('o'));
}
#[test]
fn test_into_string() {
let zs = ZalgoString::try_from("Hi").unwrap();
assert_eq!(zs.into_string(), "\u{328}\u{349}");
let zs = ZalgoString::try_from("").unwrap();
assert_eq!(zs.into_string(), "");
}
#[cfg(feature = "serde")]
#[test]
fn serde_deserialize_zalgo_string() {
use serde_json::from_str;
let s = "Zalgo";
let zs = ZalgoString::try_from(s).unwrap();
let json = format!(r#""{}""#, zs);
let deserialized: ZalgoString = from_str(&json).unwrap();
assert_eq!(deserialized, zs);
assert!(from_str::<ZalgoString>("Horse").is_err());
}
}