use super::parse::*;
use std::{
borrow::Cow,
cmp::Ordering,
fmt,
hash::{Hash, Hasher},
iter,
};
#[derive(Debug, Copy, Clone)]
pub struct Value<'a>(&'a str);
impl<'a> Value<'a> {
#[must_use]
pub fn new(s: &'a str) -> Option<Self> {
if let Some(quoted) = s.strip_prefix('\"') {
if quoted.len() > 1 && parse_quoted_value(quoted) == Ok(quoted.len()) {
return Some(Self(s));
}
} else if is_restricted_str(s) {
return Some(Self(s));
}
None
}
#[must_use]
pub const fn as_str(&self) -> &str {
self.0
}
#[must_use]
pub fn unquoted_str(&self) -> Cow<'_, str> {
if self.0.starts_with('"') {
let inner = &self.0[1..self.0.len() - 1];
if inner.contains('\\') {
let mut s = String::with_capacity(inner.len());
let mut quoted = false;
for c in inner.chars() {
match c {
_ if quoted => {
quoted = false;
s.push(c);
}
'\\' => {
quoted = true;
}
_ => {
s.push(c);
}
}
}
Cow::Owned(s)
} else {
Cow::Borrowed(inner)
}
} else {
Cow::Borrowed(self.0)
}
}
#[must_use]
pub fn quote(s: &str) -> Cow<'_, str> {
if is_restricted_str(s) {
Cow::Borrowed(s)
} else {
let inner = s.chars().flat_map(|c| {
if is_restricted_char(c) || c == ' ' {
vec![c]
} else {
vec!['\\', c]
}
});
let quoted = iter::once('"').chain(inner).chain(iter::once('"'));
Cow::Owned(quoted.collect())
}
}
pub(crate) const fn new_unchecked(s: &'a str) -> Self {
Self(s)
}
}
impl<'a> fmt::Display for Value<'a> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.0)
}
}
impl<'a> PartialEq for Value<'a> {
fn eq(&self, other: &Self) -> bool {
self.unquoted_str() == other.unquoted_str()
}
}
impl<'a> Eq for Value<'a> {}
impl<'a> PartialOrd for Value<'a> {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl<'a> Ord for Value<'a> {
fn cmp(&self, other: &Self) -> Ordering {
self.unquoted_str().cmp(&other.unquoted_str())
}
}
impl<'a> Hash for Value<'a> {
fn hash<H: Hasher>(&self, state: &mut H) {
self.unquoted_str().hash(state);
}
}
impl<'a> PartialEq<String> for Value<'a> {
fn eq(&self, other: &String) -> bool {
self.eq(other.as_str())
}
}
impl<'a> PartialOrd<String> for Value<'a> {
fn partial_cmp(&self, other: &String) -> Option<Ordering> {
self.partial_cmp(other.as_str())
}
}
impl<'a> PartialEq<&String> for Value<'a> {
fn eq(&self, other: &&String) -> bool {
self.eq(other.as_str())
}
}
impl<'a> PartialOrd<&String> for Value<'a> {
fn partial_cmp(&self, other: &&String) -> Option<Ordering> {
self.partial_cmp(other.as_str())
}
}
impl<'a> PartialEq<str> for Value<'a> {
fn eq(&self, other: &str) -> bool {
self.unquoted_str() == other
}
}
impl<'a> PartialOrd<str> for Value<'a> {
fn partial_cmp(&self, other: &str) -> Option<Ordering> {
Some(self.unquoted_str().as_ref().cmp(other))
}
}
impl<'a> PartialEq<&str> for Value<'a> {
fn eq(&self, other: &&str) -> bool {
self.eq(*other)
}
}
impl<'a> PartialOrd<&str> for Value<'a> {
fn partial_cmp(&self, other: &&str) -> Option<Ordering> {
self.partial_cmp(*other)
}
}
impl<'a> PartialEq<Cow<'_, str>> for Value<'a> {
fn eq(&self, other: &Cow<'_, str>) -> bool {
self.eq(other.as_ref())
}
}
impl<'a> PartialOrd<Cow<'_, str>> for Value<'a> {
fn partial_cmp(&self, other: &Cow<'_, str>) -> Option<Ordering> {
self.partial_cmp(other.as_ref())
}
}
impl<'a> PartialEq<&Cow<'_, str>> for Value<'a> {
fn eq(&self, other: &&Cow<'_, str>) -> bool {
self.eq(other.as_ref())
}
}
impl<'a> PartialOrd<&Cow<'_, str>> for Value<'a> {
fn partial_cmp(&self, other: &&Cow<'_, str>) -> Option<Ordering> {
self.partial_cmp(other.as_ref())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn unquoted_str() {
assert_eq!(Value::new("\"\\a\\\\\"").unwrap().unquoted_str(), "a\\");
assert_eq!(Value::new("\"\\\"\"").unwrap().unquoted_str(), "\"");
assert_eq!(Value::new("\"\\a\\b\\c\"").unwrap().unquoted_str(), "abc");
assert_eq!(
Value::new("\" \\\"What's wrong\\?\\\" \"")
.unwrap()
.unquoted_str(),
r#" "What's wrong?" "#
);
}
}