nu_command/filters/
each.rs

1use super::utils::chain_error_with_input;
2use nu_engine::{ClosureEval, ClosureEvalOnce, command_prelude::*};
3use nu_protocol::engine::Closure;
4
5#[derive(Clone)]
6pub struct Each;
7
8impl Command for Each {
9    fn name(&self) -> &str {
10        "each"
11    }
12
13    fn description(&self) -> &str {
14        "Run a closure on each row of the input list, creating a new list with the results."
15    }
16
17    fn extra_description(&self) -> &str {
18        r#"Since tables are lists of records, passing a table into 'each' will
19iterate over each record, not necessarily each cell within it.
20
21Avoid passing single records to this command. Since a record is a
22one-row structure, 'each' will only run once, behaving similar to 'do'.
23To iterate over a record's values, use 'items' or try converting it to a table
24with 'transpose' first.
25
26
27By default, for each input there is a single output value.
28If the closure returns a stream rather than value, the stream is collected
29completely, and the resulting value becomes one of the items in `each`'s output.
30
31To receive items from those streams without waiting for the whole stream to be
32collected, `each --flatten` can be used.
33Instead of waiting for the stream to be collected before returning the result as
34a single item, `each --flatten` will return each item as soon as they are received.
35
36This "flattens" the output, turning an output that would otherwise be a
37list of lists like `list<list<string>>` into a flat list like `list<string>`."#
38    }
39
40    fn search_terms(&self) -> Vec<&str> {
41        vec!["for", "loop", "iterate", "map"]
42    }
43
44    fn signature(&self) -> nu_protocol::Signature {
45        Signature::build("each")
46            .input_output_types(vec![
47                (
48                    Type::List(Box::new(Type::Any)),
49                    Type::List(Box::new(Type::Any)),
50                ),
51                (Type::table(), Type::List(Box::new(Type::Any))),
52                (Type::Any, Type::Any),
53            ])
54            .required(
55                "closure",
56                SyntaxShape::Closure(Some(vec![SyntaxShape::Any])),
57                "The closure to run.",
58            )
59            .switch("keep-empty", "keep empty result cells", Some('k'))
60            .switch(
61                "flatten",
62                "combine outputs into a single stream instead of\
63                    collecting them to separate values",
64                Some('f'),
65            )
66            .allow_variants_without_examples(true)
67            .category(Category::Filters)
68    }
69
70    fn examples(&self) -> Vec<Example<'_>> {
71        vec![
72            Example {
73                example: "[1 2 3] | each {|e| 2 * $e }",
74                description: "Multiplies elements in the list",
75                result: Some(Value::test_list(vec![
76                    Value::test_int(2),
77                    Value::test_int(4),
78                    Value::test_int(6),
79                ])),
80            },
81            Example {
82                example: "{major:2, minor:1, patch:4} | values | each {|| into string }",
83                description: "Produce a list of values in the record, converted to string",
84                result: Some(Value::test_list(vec![
85                    Value::test_string("2"),
86                    Value::test_string("1"),
87                    Value::test_string("4"),
88                ])),
89            },
90            Example {
91                example: r#"[1 2 3 2] | each {|e| if $e == 2 { "two" } }"#,
92                description: "'null' items will be dropped from the result list. It has the same effect as 'filter_map' in other languages.",
93                result: Some(Value::test_list(vec![
94                    Value::test_string("two"),
95                    Value::test_string("two"),
96                ])),
97            },
98            Example {
99                example: r#"[1 2 3] | enumerate | each {|e| if $e.item == 2 { $"found 2 at ($e.index)!"} }"#,
100                description: "Iterate over each element, producing a list showing indexes of any 2s",
101                result: Some(Value::test_list(vec![Value::test_string("found 2 at 1!")])),
102            },
103            Example {
104                example: r#"[1 2 3] | each --keep-empty {|e| if $e == 2 { "found 2!"} }"#,
105                description: "Iterate over each element, keeping null results",
106                result: Some(Value::test_list(vec![
107                    Value::nothing(Span::test_data()),
108                    Value::test_string("found 2!"),
109                    Value::nothing(Span::test_data()),
110                ])),
111            },
112            Example {
113                example: r#"$env.name? | each { $"hello ($in)" } | default "bye""#,
114                description: "Update value if not null, otherwise do nothing",
115                result: None,
116            },
117            Example {
118                description: "Scan through multiple files without pause",
119                example: "\
120                    ls *.txt \
121                    | each --flatten {|f| open $f.name | lines } \
122                    | find -i 'note: ' \
123                    | str join \"\\n\"\
124                    ",
125                result: None,
126            },
127        ]
128    }
129
130    fn run(
131        &self,
132        engine_state: &EngineState,
133        stack: &mut Stack,
134        call: &Call,
135        input: PipelineData,
136    ) -> Result<PipelineData, ShellError> {
137        let head = call.head;
138        let closure: Closure = call.req(engine_state, stack, 0)?;
139        let keep_empty = call.has_flag(engine_state, stack, "keep-empty")?;
140        let flatten = call.has_flag(engine_state, stack, "flatten")?;
141
142        let metadata = input.metadata();
143        let result = match input {
144            empty @ (PipelineData::Empty | PipelineData::Value(Value::Nothing { .. }, ..)) => {
145                return Ok(empty);
146            }
147            PipelineData::Value(Value::Range { .. }, ..)
148            | PipelineData::Value(Value::List { .. }, ..)
149            | PipelineData::ListStream(..) => {
150                let mut closure = ClosureEval::new(engine_state, stack, closure);
151
152                let out = if flatten {
153                    input
154                        .into_iter()
155                        .flat_map(move |value| {
156                            closure.run_with_value(value).unwrap_or_else(|error| {
157                                Value::error(error, head).into_pipeline_data()
158                            })
159                        })
160                        .into_pipeline_data(head, engine_state.signals().clone())
161                } else {
162                    input
163                        .into_iter()
164                        .map(move |value| {
165                            each_map(value, &mut closure, head)
166                                .unwrap_or_else(|error| Value::error(error, head))
167                        })
168                        .into_pipeline_data(head, engine_state.signals().clone())
169                };
170                Ok(out)
171            }
172            PipelineData::ByteStream(stream, ..) => {
173                let Some(chunks) = stream.chunks() else {
174                    return Ok(PipelineData::empty().set_metadata(metadata));
175                };
176
177                let mut closure = ClosureEval::new(engine_state, stack, closure);
178                let out = if flatten {
179                    chunks
180                        .flat_map(move |result| {
181                            result
182                                .and_then(|value| closure.run_with_value(value))
183                                .unwrap_or_else(|error| {
184                                    Value::error(error, head).into_pipeline_data()
185                                })
186                        })
187                        .into_pipeline_data(head, engine_state.signals().clone())
188                } else {
189                    chunks
190                        .map(move |result| {
191                            result
192                                .and_then(|value| each_map(value, &mut closure, head))
193                                .unwrap_or_else(|error| Value::error(error, head))
194                        })
195                        .into_pipeline_data(head, engine_state.signals().clone())
196                };
197                Ok(out)
198            }
199            // This match allows non-iterables to be accepted,
200            // which is currently considered undesirable (Nov 2022).
201            PipelineData::Value(value, ..) => {
202                ClosureEvalOnce::new(engine_state, stack, closure).run_with_value(value)
203            }
204        };
205
206        if keep_empty {
207            result
208        } else {
209            result.and_then(|x| x.filter(|v| !v.is_nothing(), engine_state.signals()))
210        }
211        .map(|data| data.set_metadata(metadata))
212    }
213}
214
215#[inline]
216fn each_map(value: Value, closure: &mut ClosureEval, head: Span) -> Result<Value, ShellError> {
217    let span = value.span();
218    let is_error = value.is_error();
219    closure
220        .run_with_value(value)
221        .and_then(|pipeline_data| pipeline_data.into_value(head))
222        .map_err(|error| chain_error_with_input(error, is_error, span))
223}
224
225#[cfg(test)]
226mod test {
227    use super::*;
228
229    #[test]
230    fn test_examples() {
231        use crate::test_examples;
232
233        test_examples(Each {})
234    }
235}