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
87impl PartialEq for PathMember {
88 fn eq(&self, other: &Self) -> bool {
89 match (self, other) {
90 (
91 Self::String {
92 val: l_val,
93 optional: l_opt,
94 ..
95 },
96 Self::String {
97 val: r_val,
98 optional: r_opt,
99 ..
100 },
101 ) => l_val == r_val && l_opt == r_opt,
102 (
103 Self::Int {
104 val: l_val,
105 optional: l_opt,
106 ..
107 },
108 Self::Int {
109 val: r_val,
110 optional: r_opt,
111 ..
112 },
113 ) => l_val == r_val && l_opt == r_opt,
114 _ => false,
115 }
116 }
117}
118
119impl PartialOrd for PathMember {
120 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
121 match (self, other) {
122 (
123 PathMember::String {
124 val: l_val,
125 optional: l_opt,
126 ..
127 },
128 PathMember::String {
129 val: r_val,
130 optional: r_opt,
131 ..
132 },
133 ) => {
134 let val_ord = Some(l_val.cmp(r_val));
135
136 if let Some(Ordering::Equal) = val_ord {
137 Some(l_opt.cmp(r_opt))
138 } else {
139 val_ord
140 }
141 }
142 (
143 PathMember::Int {
144 val: l_val,
145 optional: l_opt,
146 ..
147 },
148 PathMember::Int {
149 val: r_val,
150 optional: r_opt,
151 ..
152 },
153 ) => {
154 let val_ord = Some(l_val.cmp(r_val));
155
156 if let Some(Ordering::Equal) = val_ord {
157 Some(l_opt.cmp(r_opt))
158 } else {
159 val_ord
160 }
161 }
162 (PathMember::Int { .. }, PathMember::String { .. }) => Some(Ordering::Greater),
163 (PathMember::String { .. }, PathMember::Int { .. }) => Some(Ordering::Less),
164 }
165 }
166}
167
168#[derive(Debug, Clone, PartialEq, PartialOrd, Serialize, Deserialize)]
181pub struct CellPath {
182 pub members: Vec<PathMember>,
183}
184
185impl CellPath {
186 pub fn make_optional(&mut self) {
187 for member in &mut self.members {
188 member.make_optional();
189 }
190 }
191
192 pub fn make_insensitive(&mut self) {
193 for member in &mut self.members {
194 member.make_insensitive();
195 }
196 }
197
198 pub fn to_column_name(&self) -> String {
200 let mut s = String::new();
201
202 for member in &self.members {
203 match member {
204 PathMember::Int { val, .. } => {
205 s += &val.to_string();
206 }
207 PathMember::String { val, .. } => {
208 s += val;
209 }
210 }
211
212 s.push('.');
213 }
214
215 s.pop(); s
217 }
218}
219
220impl Display for CellPath {
221 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
222 write!(f, "$")?;
223 for member in self.members.iter() {
224 match member {
225 PathMember::Int { val, optional, .. } => {
226 let question_mark = if *optional { "?" } else { "" };
227 write!(f, ".{val}{question_mark}")?
228 }
229 PathMember::String {
230 val,
231 optional,
232 casing,
233 ..
234 } => {
235 let question_mark = if *optional { "?" } else { "" };
236 let exclamation_mark = if *casing == Casing::Insensitive {
237 "!"
238 } else {
239 ""
240 };
241 let val = if needs_quoting(val) {
242 &escape_quote_string(val)
243 } else {
244 val
245 };
246 write!(f, ".{val}{exclamation_mark}{question_mark}")?
247 }
248 }
249 }
250 if self.members.is_empty() {
252 write!(f, ".")?;
253 }
254 Ok(())
255 }
256}
257
258#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
259pub struct FullCellPath {
260 pub head: Expression,
261 pub tail: Vec<PathMember>,
262}
263
264#[cfg(test)]
265mod test {
266 use super::*;
267 use std::cmp::Ordering::Greater;
268
269 #[test]
270 fn path_member_partial_ord() {
271 assert_eq!(
272 Some(Greater),
273 PathMember::test_int(5, true).partial_cmp(&PathMember::test_string(
274 "e".into(),
275 true,
276 Casing::Sensitive
277 ))
278 );
279
280 assert_eq!(
281 Some(Greater),
282 PathMember::test_int(5, true).partial_cmp(&PathMember::test_int(5, false))
283 );
284
285 assert_eq!(
286 Some(Greater),
287 PathMember::test_int(6, true).partial_cmp(&PathMember::test_int(5, true))
288 );
289
290 assert_eq!(
291 Some(Greater),
292 PathMember::test_string("e".into(), true, Casing::Sensitive).partial_cmp(
293 &PathMember::test_string("e".into(), false, Casing::Sensitive)
294 )
295 );
296
297 assert_eq!(
298 Some(Greater),
299 PathMember::test_string("f".into(), true, Casing::Sensitive).partial_cmp(
300 &PathMember::test_string("e".into(), true, Casing::Sensitive)
301 )
302 );
303 }
304}