1use super::Expression;
2use crate::{Span, casing::Casing};
3use nu_utils::{escape_quote_string, needs_quoting};
4use serde::{Deserialize, Serialize};
5use std::{cmp::Ordering, fmt::Display};
6
7#[derive(Debug, Clone, Serialize, Deserialize)]
9pub enum PathMember {
10 String {
12 val: String,
13 span: Span,
14 optional: bool,
17 casing: Casing,
19 },
20 Int {
22 val: usize,
23 span: Span,
24 optional: bool,
27 },
28}
29
30impl PathMember {
31 pub fn int(val: usize, optional: bool, span: Span) -> Self {
32 PathMember::Int {
33 val,
34 span,
35 optional,
36 }
37 }
38
39 pub fn string(val: String, optional: bool, casing: Casing, span: Span) -> Self {
40 PathMember::String {
41 val,
42 span,
43 optional,
44 casing,
45 }
46 }
47
48 pub fn test_int(val: usize, optional: bool) -> Self {
49 PathMember::Int {
50 val,
51 optional,
52 span: Span::test_data(),
53 }
54 }
55
56 pub fn test_string(val: String, optional: bool, casing: Casing) -> Self {
57 PathMember::String {
58 val,
59 optional,
60 casing,
61 span: Span::test_data(),
62 }
63 }
64
65 pub fn make_optional(&mut self) {
66 match self {
67 PathMember::String { optional, .. } => *optional = true,
68 PathMember::Int { optional, .. } => *optional = true,
69 }
70 }
71
72 pub fn make_insensitive(&mut self) {
73 match self {
74 PathMember::String { casing, .. } => *casing = Casing::Insensitive,
75 PathMember::Int { .. } => {}
76 }
77 }
78
79 pub fn span(&self) -> Span {
80 match self {
81 PathMember::String { span, .. } => *span,
82 PathMember::Int { span, .. } => *span,
83 }
84 }
85
86 pub fn memory_size(&self) -> usize {
88 match self {
89 PathMember::String { val, .. } => std::mem::size_of::<Self>() + val.capacity(),
90 PathMember::Int { .. } => std::mem::size_of::<Self>(),
91 }
92 }
93}
94
95impl PartialEq for PathMember {
96 fn eq(&self, other: &Self) -> bool {
97 match (self, other) {
98 (
99 Self::String {
100 val: l_val,
101 optional: l_opt,
102 ..
103 },
104 Self::String {
105 val: r_val,
106 optional: r_opt,
107 ..
108 },
109 ) => l_val == r_val && l_opt == r_opt,
110 (
111 Self::Int {
112 val: l_val,
113 optional: l_opt,
114 ..
115 },
116 Self::Int {
117 val: r_val,
118 optional: r_opt,
119 ..
120 },
121 ) => l_val == r_val && l_opt == r_opt,
122 _ => false,
123 }
124 }
125}
126
127impl PartialOrd for PathMember {
128 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
129 match (self, other) {
130 (
131 PathMember::String {
132 val: l_val,
133 optional: l_opt,
134 ..
135 },
136 PathMember::String {
137 val: r_val,
138 optional: r_opt,
139 ..
140 },
141 ) => {
142 let val_ord = Some(l_val.cmp(r_val));
143
144 if let Some(Ordering::Equal) = val_ord {
145 Some(l_opt.cmp(r_opt))
146 } else {
147 val_ord
148 }
149 }
150 (
151 PathMember::Int {
152 val: l_val,
153 optional: l_opt,
154 ..
155 },
156 PathMember::Int {
157 val: r_val,
158 optional: r_opt,
159 ..
160 },
161 ) => {
162 let val_ord = Some(l_val.cmp(r_val));
163
164 if let Some(Ordering::Equal) = val_ord {
165 Some(l_opt.cmp(r_opt))
166 } else {
167 val_ord
168 }
169 }
170 (PathMember::Int { .. }, PathMember::String { .. }) => Some(Ordering::Greater),
171 (PathMember::String { .. }, PathMember::Int { .. }) => Some(Ordering::Less),
172 }
173 }
174}
175
176#[doc(hidden)]
181pub struct TestPathMember<T>(T);
182
183impl<S: Into<String>> From<S> for TestPathMember<String> {
184 fn from(value: S) -> Self {
185 Self(value.into())
186 }
187}
188
189impl TestPathMember<String> {
190 pub fn into_path_member(self) -> PathMember {
191 PathMember::test_string(self.0, false, Casing::Sensitive)
192 }
193}
194
195impl From<usize> for TestPathMember<usize> {
196 fn from(value: usize) -> Self {
197 Self(value)
198 }
199}
200
201impl TestPathMember<usize> {
202 pub fn into_path_member(self) -> PathMember {
203 PathMember::test_int(self.0, false)
204 }
205}
206
207#[derive(Debug, Clone, PartialEq, PartialOrd, Serialize, Deserialize)]
220pub struct CellPath {
221 pub members: Vec<PathMember>,
222}
223
224impl CellPath {
225 pub fn make_optional(&mut self) {
226 for member in &mut self.members {
227 member.make_optional();
228 }
229 }
230
231 pub fn make_insensitive(&mut self) {
232 for member in &mut self.members {
233 member.make_insensitive();
234 }
235 }
236
237 pub fn to_column_name(&self) -> String {
239 let mut s = String::new();
240
241 for member in &self.members {
242 match member {
243 PathMember::Int { val, .. } => {
244 s += &val.to_string();
245 }
246 PathMember::String { val, .. } => {
247 s += val;
248 }
249 }
250
251 s.push('.');
252 }
253
254 s.pop(); s
256 }
257
258 pub fn memory_size(&self) -> usize {
260 std::mem::size_of::<Self>() + self.members.iter().map(|m| m.memory_size()).sum::<usize>()
261 }
262}
263
264impl Display for CellPath {
265 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
266 write!(f, "$")?;
267 for member in self.members.iter() {
268 match member {
269 PathMember::Int { val, optional, .. } => {
270 let question_mark = if *optional { "?" } else { "" };
271 write!(f, ".{val}{question_mark}")?
272 }
273 PathMember::String {
274 val,
275 optional,
276 casing,
277 ..
278 } => {
279 let question_mark = if *optional { "?" } else { "" };
280 let exclamation_mark = if *casing == Casing::Insensitive {
281 "!"
282 } else {
283 ""
284 };
285 let val = if needs_quoting(val) {
286 &escape_quote_string(val)
287 } else {
288 val
289 };
290 write!(f, ".{val}{exclamation_mark}{question_mark}")?
291 }
292 }
293 }
294 if self.members.is_empty() {
296 write!(f, ".")?;
297 }
298 Ok(())
299 }
300}
301
302#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
303pub struct FullCellPath {
304 pub head: Expression,
305 pub tail: Vec<PathMember>,
306}
307
308#[cfg(test)]
309mod test {
310 use super::*;
311 use std::cmp::Ordering::Greater;
312
313 #[test]
314 fn path_member_partial_ord() {
315 assert_eq!(
316 Some(Greater),
317 PathMember::test_int(5, true).partial_cmp(&PathMember::test_string(
318 "e".into(),
319 true,
320 Casing::Sensitive
321 ))
322 );
323
324 assert_eq!(
325 Some(Greater),
326 PathMember::test_int(5, true).partial_cmp(&PathMember::test_int(5, false))
327 );
328
329 assert_eq!(
330 Some(Greater),
331 PathMember::test_int(6, true).partial_cmp(&PathMember::test_int(5, true))
332 );
333
334 assert_eq!(
335 Some(Greater),
336 PathMember::test_string("e".into(), true, Casing::Sensitive).partial_cmp(
337 &PathMember::test_string("e".into(), false, Casing::Sensitive)
338 )
339 );
340
341 assert_eq!(
342 Some(Greater),
343 PathMember::test_string("f".into(), true, Casing::Sensitive).partial_cmp(
344 &PathMember::test_string("e".into(), true, Casing::Sensitive)
345 )
346 );
347 }
348}