nu_command/conversions/
split_cell_path.rs

1use nu_engine::command_prelude::*;
2use nu_protocol::{IntoValue, ast::PathMember, casing::Casing};
3
4#[derive(Clone)]
5pub struct SplitCellPath;
6
7impl Command for SplitCellPath {
8    fn name(&self) -> &str {
9        "split cell-path"
10    }
11
12    fn signature(&self) -> Signature {
13        Signature::build(self.name())
14            .input_output_types(vec![
15                (Type::CellPath, Type::List(Box::new(Type::Any))),
16                (
17                    Type::CellPath,
18                    Type::List(Box::new(Type::Record(
19                        [
20                            ("value".into(), Type::Any),
21                            ("optional".into(), Type::Bool),
22                            ("insensitive".into(), Type::Bool),
23                        ]
24                        .into(),
25                    ))),
26                ),
27            ])
28            .category(Category::Conversions)
29            .allow_variants_without_examples(true)
30    }
31
32    fn description(&self) -> &str {
33        "Split a cell-path into its components."
34    }
35
36    fn search_terms(&self) -> Vec<&str> {
37        vec!["convert"]
38    }
39
40    fn run(
41        &self,
42        _engine_state: &EngineState,
43        _stack: &mut Stack,
44        call: &Call,
45        input: PipelineData,
46    ) -> Result<PipelineData, ShellError> {
47        let head = call.head;
48        let input_type = input.get_type();
49
50        let src_span = match input {
51            // Early return on correct type and empty pipeline
52            PipelineData::Value(Value::CellPath { val, .. }, _) => {
53                return Ok(split_cell_path(val, head)?.into_pipeline_data());
54            }
55            PipelineData::Empty => return Err(ShellError::PipelineEmpty { dst_span: head }),
56
57            // Extract span from incorrect pipeline types
58            // NOTE: Match arms can't be combined, `stream`s are of different types
59            PipelineData::Value(other, _) => other.span(),
60            PipelineData::ListStream(stream, ..) => stream.span(),
61            PipelineData::ByteStream(stream, ..) => stream.span(),
62        };
63        Err(ShellError::OnlySupportsThisInputType {
64            exp_input_type: "cell-path".into(),
65            wrong_type: input_type.to_string(),
66            dst_span: head,
67            src_span,
68        })
69    }
70
71    fn examples(&self) -> Vec<Example> {
72        vec![
73            Example {
74                description: "Split a cell-path into its components",
75                example: "$.5?.c | split cell-path",
76                result: Some(Value::test_list(vec![
77                    Value::test_record(record! {
78                        "value" => Value::test_int(5),
79                        "optional" => Value::test_bool(true),
80                        "insensitive" => Value::test_bool(false),
81                    }),
82                    Value::test_record(record! {
83                        "value" => Value::test_string("c"),
84                        "optional" => Value::test_bool(false),
85                        "insensitive" => Value::test_bool(false),
86                    }),
87                ])),
88            },
89            Example {
90                description: "Split a complex cell-path",
91                example: r#"$.a!.b?.1."2"."c.d" | split cell-path"#,
92                result: Some(Value::test_list(vec![
93                    Value::test_record(record! {
94                        "value" => Value::test_string("a"),
95                        "optional" => Value::test_bool(false),
96                        "insensitive" => Value::test_bool(true),
97                    }),
98                    Value::test_record(record! {
99                        "value" => Value::test_string("b"),
100                        "optional" => Value::test_bool(true),
101                        "insensitive" => Value::test_bool(false),
102                    }),
103                    Value::test_record(record! {
104                        "value" => Value::test_int(1),
105                        "optional" => Value::test_bool(false),
106                        "insensitive" => Value::test_bool(false),
107                    }),
108                    Value::test_record(record! {
109                        "value" => Value::test_string("2"),
110                        "optional" => Value::test_bool(false),
111                        "insensitive" => Value::test_bool(false),
112                    }),
113                    Value::test_record(record! {
114                        "value" => Value::test_string("c.d"),
115                        "optional" => Value::test_bool(false),
116                        "insensitive" => Value::test_bool(false),
117                    }),
118                ])),
119            },
120        ]
121    }
122}
123
124fn split_cell_path(val: CellPath, span: Span) -> Result<Value, ShellError> {
125    #[derive(IntoValue)]
126    struct PathMemberRecord {
127        value: Value,
128        optional: bool,
129        insensitive: bool,
130    }
131
132    impl PathMemberRecord {
133        fn from_path_member(pm: PathMember) -> Self {
134            let (optional, insensitive, internal_span) = match pm {
135                PathMember::String {
136                    optional,
137                    casing,
138                    span,
139                    ..
140                } => (optional, casing == Casing::Insensitive, span),
141                PathMember::Int { optional, span, .. } => (optional, false, span),
142            };
143            let value = match pm {
144                PathMember::String { val, .. } => Value::string(val, internal_span),
145                PathMember::Int { val, .. } => Value::int(val as i64, internal_span),
146            };
147            Self {
148                value,
149                optional,
150                insensitive,
151            }
152        }
153    }
154
155    let members = val
156        .members
157        .into_iter()
158        .map(|pm| {
159            let span = match pm {
160                PathMember::String { span, .. } | PathMember::Int { span, .. } => span,
161            };
162            PathMemberRecord::from_path_member(pm).into_value(span)
163        })
164        .collect();
165
166    Ok(Value::list(members, span))
167}
168
169#[cfg(test)]
170mod test {
171    use super::*;
172
173    #[test]
174    fn test_examples() {
175        use crate::test_examples;
176
177        test_examples(SplitCellPath {})
178    }
179}