use core::fmt;
use std::{
borrow::Cow,
ops::{Add, AddAssign, Deref},
};
mod error;
pub use error::Error;
#[derive(Debug, Clone, Eq, PartialEq, PartialOrd, Ord, Hash)]
pub struct Identifier<'a> {
inner: Cow<'a, str>,
quoted: String,
}
impl<'a> TryFrom<Cow<'a, str>> for Identifier<'a> {
type Error = Error;
fn try_from(value: Cow<'a, str>) -> Result<Self, Self::Error> {
if value.find('\0').is_some() {
return Err(Error::NullCharacter);
}
let len = 2 + value.chars().filter(|&c| c == '"').count() + value.len();
let mut quoted = String::new();
quoted.reserve_exact(len);
{
quoted.push('"');
let mut value: &str = &value;
loop {
match value.find('"') {
Some(index) => {
quoted.push_str(&value[..=index]);
quoted.push('"');
value = &value[index + 1..];
}
None => {
quoted.push_str(value);
break;
}
}
}
quoted.push('"');
}
Ok(Identifier {
inner: value,
quoted,
})
}
}
impl<'a> TryFrom<&'a str> for Identifier<'a> {
type Error = Error;
fn try_from(value: &'a str) -> Result<Self, Self::Error> {
Identifier::try_from(Cow::Borrowed(value))
}
}
impl TryFrom<String> for Identifier<'static> {
type Error = Error;
fn try_from(value: String) -> Result<Self, Self::Error> {
Identifier::try_from(Cow::Owned(value))
}
}
impl<'a> From<Identifier<'a>> for Cow<'a, str> {
fn from(value: Identifier<'a>) -> Self {
value.inner
}
}
impl<'a> Deref for Identifier<'a> {
type Target = str;
fn deref(&self) -> &Self::Target {
&self.inner
}
}
impl<'a, T> AsRef<T> for Identifier<'a>
where
T: ?Sized,
<Identifier<'a> as Deref>::Target: AsRef<T>,
{
fn as_ref(&self) -> &T {
self.deref().as_ref()
}
}
impl<'a, 'b> Add<&Identifier<'b>> for Identifier<'a> {
type Output = Identifier<'a>;
fn add(mut self, rhs: &Identifier) -> Self::Output {
self += rhs;
self
}
}
impl<'a, 'b> AddAssign<&Identifier<'b>> for Identifier<'a> {
fn add_assign(&mut self, rhs: &Identifier) {
let mut inner = self.inner.to_mut();
inner.reserve_exact(rhs.inner.len());
*inner += &rhs.inner;
self.quoted.reserve_exact(rhs.quoted.len() - 2);
self.quoted.pop();
self.quoted += &rhs.quoted[1..];
}
}
impl<'a> fmt::Display for Identifier<'a> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.quoted.fmt(f)
}
}
#[cfg(test)]
mod test {
use super::Identifier;
#[test]
fn tests() {
assert_eq!(
Identifier::try_from("main").unwrap().to_string(),
String::from("\"main\""),
);
assert_eq!(
Identifier::try_from(String::from("ma\"in"))
.unwrap()
.to_string(),
String::from("\"ma\"\"in\""),
);
assert_eq!(
Identifier::try_from(String::from("\"main"))
.unwrap()
.to_string(),
String::from("\"\"\"main\""),
);
assert_eq!(
Identifier::try_from(String::from("main\""))
.unwrap()
.to_string(),
String::from("\"main\"\"\""),
);
assert!(Identifier::try_from(String::from("ma\0in")).is_err());
}
#[test]
fn test_add() {
assert_eq!(
(Identifier::try_from(String::from("main")).unwrap()
+ &String::from("_after").try_into().unwrap())
.to_string(),
"\"main_after\"",
);
}
}