Skip to main content

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