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