Skip to main content

nu_command/filters/
get.rs

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