1use super::parse::*;
2use std::{
3 borrow::Cow,
4 cmp::Ordering,
5 fmt,
6 hash::{Hash, Hasher},
7 iter,
8};
9
10#[derive(Debug, Copy, Clone)]
29pub struct Value<'a>(&'a str);
30
31impl<'a> Value<'a> {
32 #[must_use]
36 pub fn new(s: &'a str) -> Option<Self> {
37 if let Some(quoted) = s.strip_prefix('\"') {
38 if quoted.len() > 1 && parse_quoted_value(quoted) == Ok(quoted.len()) {
39 return Some(Self(s));
40 }
41 } else if is_restricted_str(s) {
42 return Some(Self(s));
43 }
44 None
45 }
46
47 #[must_use]
49 pub const fn as_str(&self) -> &'a str {
50 self.0
51 }
52
53 #[must_use]
55 pub fn unquoted_str(&self) -> Cow<'_, str> {
56 if self.0.starts_with('"') {
57 let inner = &self.0[1..self.0.len() - 1];
58 if inner.contains('\\') {
59 let mut s = String::with_capacity(inner.len());
60 let mut quoted = false;
61 for c in inner.chars() {
62 match c {
63 _ if quoted => {
64 quoted = false;
65 s.push(c);
66 }
67 '\\' => {
68 quoted = true;
69 }
70 _ => {
71 s.push(c);
72 }
73 }
74 }
75 Cow::Owned(s)
76 } else {
77 Cow::Borrowed(inner)
78 }
79 } else {
80 Cow::Borrowed(self.0)
81 }
82 }
83
84 #[must_use]
96 pub fn quote(s: &str) -> Cow<'_, str> {
97 if is_restricted_str(s) {
98 Cow::Borrowed(s)
99 } else {
100 let inner = s.chars().flat_map(|c| {
101 if is_restricted_char(c) || c == ' ' {
102 vec![c]
103 } else {
104 vec!['\\', c]
105 }
106 });
107 let quoted = iter::once('"').chain(inner).chain(iter::once('"'));
108 Cow::Owned(quoted.collect())
109 }
110 }
111
112 pub(crate) const fn new_unchecked(s: &'a str) -> Self {
113 Self(s)
114 }
115}
116
117impl fmt::Display for Value<'_> {
118 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
119 f.write_str(self.0)
120 }
121}
122
123impl PartialEq for Value<'_> {
124 fn eq(&self, other: &Self) -> bool {
125 self.unquoted_str() == other.unquoted_str()
126 }
127}
128
129impl Eq for Value<'_> {}
130
131impl PartialOrd for Value<'_> {
132 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
133 Some(self.cmp(other))
134 }
135}
136
137impl Ord for Value<'_> {
138 fn cmp(&self, other: &Self) -> Ordering {
139 self.unquoted_str().cmp(&other.unquoted_str())
140 }
141}
142
143impl Hash for Value<'_> {
144 fn hash<H: Hasher>(&self, state: &mut H) {
145 self.unquoted_str().hash(state);
146 }
147}
148
149impl PartialEq<String> for Value<'_> {
150 fn eq(&self, other: &String) -> bool {
151 self.eq(other.as_str())
152 }
153}
154
155impl PartialOrd<String> for Value<'_> {
156 fn partial_cmp(&self, other: &String) -> Option<Ordering> {
157 self.partial_cmp(other.as_str())
158 }
159}
160
161impl PartialEq<&String> for Value<'_> {
162 fn eq(&self, other: &&String) -> bool {
163 self.eq(other.as_str())
164 }
165}
166
167impl PartialOrd<&String> for Value<'_> {
168 fn partial_cmp(&self, other: &&String) -> Option<Ordering> {
169 self.partial_cmp(other.as_str())
170 }
171}
172
173impl PartialEq<str> for Value<'_> {
174 fn eq(&self, other: &str) -> bool {
175 self.unquoted_str() == other
176 }
177}
178
179impl PartialOrd<str> for Value<'_> {
180 fn partial_cmp(&self, other: &str) -> Option<Ordering> {
181 Some(self.unquoted_str().as_ref().cmp(other))
182 }
183}
184
185impl PartialEq<&str> for Value<'_> {
186 fn eq(&self, other: &&str) -> bool {
187 self.eq(*other)
188 }
189}
190
191impl PartialOrd<&str> for Value<'_> {
192 fn partial_cmp(&self, other: &&str) -> Option<Ordering> {
193 self.partial_cmp(*other)
194 }
195}
196
197impl PartialEq<Cow<'_, str>> for Value<'_> {
198 fn eq(&self, other: &Cow<'_, str>) -> bool {
199 self.eq(other.as_ref())
200 }
201}
202
203impl PartialOrd<Cow<'_, str>> for Value<'_> {
204 fn partial_cmp(&self, other: &Cow<'_, str>) -> Option<Ordering> {
205 self.partial_cmp(other.as_ref())
206 }
207}
208
209impl PartialEq<&Cow<'_, str>> for Value<'_> {
210 fn eq(&self, other: &&Cow<'_, str>) -> bool {
211 self.eq(other.as_ref())
212 }
213}
214
215impl PartialOrd<&Cow<'_, str>> for Value<'_> {
216 fn partial_cmp(&self, other: &&Cow<'_, str>) -> Option<Ordering> {
217 self.partial_cmp(other.as_ref())
218 }
219}
220
221#[cfg(test)]
222mod tests {
223 use super::*;
224
225 #[test]
226 fn unquoted_str() {
227 assert_eq!(Value::new("\"\\a\\\\\"").unwrap().unquoted_str(), "a\\");
228 assert_eq!(Value::new("\"\\\"\"").unwrap().unquoted_str(), "\"");
229 assert_eq!(Value::new("\"\\a\\b\\c\"").unwrap().unquoted_str(), "abc");
230 assert_eq!(
231 Value::new("\" \\\"What's wrong\\?\\\" \"")
232 .unwrap()
233 .unquoted_str(),
234 r#" "What's wrong?" "#
235 );
236 }
237}