nu_protocol/ast/
cell_path.rs

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/// One level of access of a [`CellPath`]
8#[derive(Debug, Clone, Serialize, Deserialize)]
9pub enum PathMember {
10    /// Accessing a member by string (i.e. columns of a table or [`Record`](crate::Record))
11    String {
12        val: String,
13        span: Span,
14        /// If marked as optional don't throw an error if not found but perform default handling
15        /// (e.g. return `Value::Nothing`)
16        optional: bool,
17        /// Affects column lookup
18        casing: Casing,
19    },
20    /// Accessing a member by index (i.e. row of a table or item in a list)
21    Int {
22        val: usize,
23        span: Span,
24        /// If marked as optional don't throw an error if not found but perform default handling
25        /// (e.g. return `Value::Nothing`)
26        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/// Represents the potentially nested access to fields/cells of a container type
169///
170/// In our current implementation for table access the order of row/column is commutative.
171/// This limits the number of possible rows to select in one [`CellPath`] to 1 as it could
172/// otherwise be ambiguous
173///
174/// ```nushell
175/// col1.0
176/// 0.col1
177/// col2
178/// 42
179/// ```
180#[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    // Formats the cell-path as a column name, i.e. without quoting and optional markers ('?').
199    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(); // Easier than checking whether to insert the '.' on every iteration.
216        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        // Empty cell-paths are `$.` not `$`
251        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}