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 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 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}