Skip to main content

nu_command/filters/
get.rs

1use std::borrow::Cow;
2
3use nu_engine::command_prelude::*;
4use nu_protocol::{DeprecationEntry, DeprecationType, ReportMode, Signals, ast::PathMember};
5
6#[derive(Clone)]
7pub struct Get;
8
9impl Command for Get {
10    fn name(&self) -> &str {
11        "get"
12    }
13
14    fn description(&self) -> &str {
15        "Extract data using a cell path."
16    }
17
18    fn extra_description(&self) -> &str {
19        "This is equivalent to using the cell path access syntax: `$env.OS` is the same as `$env | get OS`.
20
21If multiple cell paths are given, this will produce a list of values."
22    }
23
24    fn signature(&self) -> nu_protocol::Signature {
25        Signature::build("get")
26            .input_output_types(vec![
27                (
28                    // TODO: This is too permissive; if we could express this
29                    // using a type parameter it would be List<T> -> T.
30                    Type::List(Box::new(Type::Any)),
31                    Type::Any,
32                ),
33                (Type::table(), Type::Any),
34                (Type::record(), Type::Any),
35                (Type::Nothing, Type::Nothing),
36            ])
37            .required(
38                "cell_path",
39                SyntaxShape::CellPath,
40                "The cell path to the data.",
41            )
42            .rest("rest", SyntaxShape::CellPath, "Additional cell paths.")
43            .switch(
44                "optional",
45                "Make all cell path members optional (returns `null` for missing values).",
46                Some('o'),
47            )
48            .switch(
49                "ignore-case",
50                "Make all cell path members case insensitive.",
51                None,
52            )
53            .switch(
54                "ignore-errors",
55                "Ignore missing data (make all cell path members optional) (deprecated).",
56                Some('i'),
57            )
58            .switch(
59                "sensitive",
60                "Get path in a case sensitive manner (deprecated).",
61                Some('s'),
62            )
63            .allow_variants_without_examples(true)
64            .category(Category::Filters)
65    }
66
67    fn examples(&self) -> Vec<Example<'_>> {
68        vec![
69            Example {
70                description: "Get an item from a list.",
71                example: "[0 1 2] | get 1",
72                result: Some(Value::test_int(1)),
73            },
74            Example {
75                description: "Get a column from a table.",
76                example: "[{A: A0}] | get A",
77                result: Some(Value::list(
78                    vec![Value::test_string("A0")],
79                    Span::test_data(),
80                )),
81            },
82            Example {
83                description: "Get a column from a table where some rows don't have that column, using optional cell-path syntax.",
84                example: "[{A: A0, B: B0}, {B: B1}, {A: A2, B: B2}] | get A?",
85                result: Some(Value::list(
86                    vec![
87                        Value::test_string("A0"),
88                        Value::test_nothing(),
89                        Value::test_string("A2"),
90                    ],
91                    Span::test_data(),
92                )),
93            },
94            Example {
95                description: "Get a column from a table where some rows don't have that column, using the optional flag.",
96                example: "[{A: A0, B: B0}, {B: B1}, {A: A2, B: B2}] | get -o A",
97                result: Some(Value::list(
98                    vec![
99                        Value::test_string("A0"),
100                        Value::test_nothing(),
101                        Value::test_string("A2"),
102                    ],
103                    Span::test_data(),
104                )),
105            },
106            Example {
107                description: "Get a cell from a table.",
108                example: "[{A: A0}] | get 0.A",
109                result: Some(Value::test_string("A0")),
110            },
111            Example {
112                description: "Extract the name of the 3rd record in a list (same as `ls | $in.name.2`).",
113                example: "ls | get name.2",
114                result: None,
115            },
116            Example {
117                description: "Extract the name of the 3rd record in a list.",
118                example: "ls | get 2.name",
119                result: None,
120            },
121            Example {
122                description: "Getting environment variables in a case insensitive way, using case insensitive cell-path syntax.",
123                example: "$env | get home! path!",
124                result: None,
125            },
126            Example {
127                description: "Getting environment variables in a case insensitive way, using the '--ignore-case' flag.",
128                example: "$env | get --ignore-case home path",
129                result: None,
130            },
131            Example {
132                description: "Getting Path in a case sensitive way, won't work for 'PATH'.",
133                example: "$env | get Path",
134                result: None,
135            },
136        ]
137    }
138
139    fn is_const(&self) -> bool {
140        true
141    }
142
143    fn run_const(
144        &self,
145        working_set: &StateWorkingSet,
146        call: &Call,
147        input: PipelineData,
148    ) -> Result<PipelineData, ShellError> {
149        let cell_path: CellPath = call.req_const(working_set, 0)?;
150        let rest: Vec<CellPath> = call.rest_const(working_set, 1)?;
151        let optional = call.has_flag_const(working_set, "optional")?
152            || call.has_flag_const(working_set, "ignore-errors")?;
153        let ignore_case = call.has_flag_const(working_set, "ignore-case")?;
154        action(
155            input,
156            cell_path,
157            rest,
158            optional,
159            ignore_case,
160            working_set.permanent().signals().clone(),
161            call.head,
162        )
163    }
164
165    fn run(
166        &self,
167        engine_state: &EngineState,
168        stack: &mut Stack,
169        call: &Call,
170        input: PipelineData,
171    ) -> Result<PipelineData, ShellError> {
172        let cell_path: CellPath = call.req(engine_state, stack, 0)?;
173        let rest: Vec<CellPath> = call.rest(engine_state, stack, 1)?;
174        let optional = call.has_flag(engine_state, stack, "optional")?
175            || call.has_flag(engine_state, stack, "ignore-errors")?;
176        let ignore_case = call.has_flag(engine_state, stack, "ignore-case")?;
177        action(
178            input,
179            cell_path,
180            rest,
181            optional,
182            ignore_case,
183            engine_state.signals().clone(),
184            call.head,
185        )
186    }
187
188    fn deprecation_info(&self) -> Vec<DeprecationEntry> {
189        vec![
190            DeprecationEntry {
191                ty: DeprecationType::Flag("sensitive".into()),
192                report_mode: ReportMode::FirstUse,
193                since: Some("0.105.0".into()),
194                expected_removal: None,
195                help: Some("Cell-paths are now case-sensitive by default.\nTo access fields case-insensitively, add `!` after the relevant path member.".into())
196            },
197            DeprecationEntry {
198                ty: DeprecationType::Flag("ignore-errors".into()),
199                report_mode: ReportMode::FirstUse,
200                since: Some("0.106.0".into()),
201                expected_removal: None,
202                help: Some("This flag has been renamed to `--optional (-o)` to better reflect its behavior.".into())
203            }
204        ]
205    }
206}
207
208fn action(
209    mut input: PipelineData,
210    mut cell_path: CellPath,
211    mut rest: Vec<CellPath>,
212    optional: bool,
213    ignore_case: bool,
214    signals: Signals,
215    span: Span,
216) -> Result<PipelineData, ShellError> {
217    if optional {
218        cell_path.make_optional();
219        for path in &mut rest {
220            path.make_optional();
221        }
222    }
223
224    if ignore_case {
225        cell_path.make_insensitive();
226        for path in &mut rest {
227            path.make_insensitive();
228        }
229    }
230
231    if let PipelineData::Empty = input {
232        return Err(ShellError::PipelineEmpty { dst_span: span });
233    }
234
235    let mut metadata = input.take_metadata();
236
237    if rest.is_empty() {
238        let cell_path_is_index = matches!(&cell_path.members[..], &[PathMember::Int { .. }]);
239        follow_cell_path_into_stream(input, signals, cell_path.members, span).map(|data| {
240            if !cell_path_is_index && let Some(metadata) = &mut metadata {
241                metadata.path_columns.clear();
242            }
243            data.set_metadata(metadata)
244        })
245    } else {
246        let mut output = vec![];
247
248        let paths = std::iter::once(cell_path).chain(rest);
249
250        let input = input.into_value(span)?;
251
252        let mut any_cell_path_is_index = false;
253
254        for path in paths {
255            any_cell_path_is_index |= matches!(&path.members[..], &[PathMember::Int { .. }]);
256            output.push(input.follow_cell_path(&path.members)?.into_owned());
257        }
258
259        if !any_cell_path_is_index && let Some(metadata) = &mut metadata {
260            metadata.path_columns.clear();
261        }
262
263        Ok(output
264            .into_iter()
265            .into_pipeline_data_with_metadata(span, signals, metadata))
266    }
267}
268
269// the PipelineData.follow_cell_path function, when given a
270// stream, collects it into a vec before doing its job
271//
272// this is fine, since it returns a Result<Value ShellError>,
273// but if we want to follow a PipelineData into a cell path and
274// return another PipelineData, then we have to take care to
275// make sure it streams
276pub fn follow_cell_path_into_stream(
277    data: PipelineData,
278    signals: Signals,
279    cell_path: Vec<PathMember>,
280    head: Span,
281) -> Result<PipelineData, ShellError> {
282    // when given an integer/indexing, we fallback to
283    // the default nushell indexing behaviour
284    let has_int_member = cell_path
285        .iter()
286        .any(|it| matches!(it, PathMember::Int { .. }));
287    match data {
288        PipelineData::ListStream(stream, ..) if !has_int_member => {
289            let result = stream
290                .into_iter()
291                .map(move |value| {
292                    let span = value.span();
293
294                    value
295                        .follow_cell_path(&cell_path)
296                        .map(Cow::into_owned)
297                        .unwrap_or_else(|error| Value::error(error, span))
298                })
299                .into_pipeline_data(head, signals);
300
301            Ok(result)
302        }
303
304        _ => data
305            .follow_cell_path(&cell_path, head)
306            .map(|x| x.into_pipeline_data()),
307    }
308}
309
310#[cfg(test)]
311mod tests {
312    use super::*;
313
314    #[test]
315    fn test_examples() -> nu_test_support::Result {
316        nu_test_support::test().examples(Get)
317    }
318}