nu_command/conversions/
split_cell_path.rs

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