Skip to main content

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 collecting them to separate values.",
63                Some('f'),
64            )
65            .allow_variants_without_examples(true)
66            .category(Category::Filters)
67    }
68
69    fn examples(&self) -> Vec<Example<'_>> {
70        vec![
71            Example {
72                example: "[1 2 3] | each {|e| 2 * $e }",
73                description: "Multiplies elements in the list.",
74                result: Some(Value::test_list(vec![
75                    Value::test_int(2),
76                    Value::test_int(4),
77                    Value::test_int(6),
78                ])),
79            },
80            Example {
81                example: "{major:2, minor:1, patch:4} | values | each {|| into string }",
82                description: "Produce a list of values in the record, converted to string.",
83                result: Some(Value::test_list(vec![
84                    Value::test_string("2"),
85                    Value::test_string("1"),
86                    Value::test_string("4"),
87                ])),
88            },
89            Example {
90                example: r#"[1 2 3 2] | each {|e| if $e == 2 { "two" } }"#,
91                description: "'null' items will be dropped from the result list. It has the same effect as 'filter_map' in other languages.",
92                result: Some(Value::test_list(vec![
93                    Value::test_string("two"),
94                    Value::test_string("two"),
95                ])),
96            },
97            Example {
98                example: r#"[1 2 3] | enumerate | each {|e| if $e.item == 2 { $"found 2 at ($e.index)!"} }"#,
99                description: "Iterate over each element, producing a list showing indexes of any 2s.",
100                result: Some(Value::test_list(vec![Value::test_string("found 2 at 1!")])),
101            },
102            Example {
103                example: r#"[1 2 3] | each --keep-empty {|e| if $e == 2 { "found 2!"} }"#,
104                description: "Iterate over each element, keeping null results.",
105                result: Some(Value::test_list(vec![
106                    Value::nothing(Span::test_data()),
107                    Value::test_string("found 2!"),
108                    Value::nothing(Span::test_data()),
109                ])),
110            },
111            Example {
112                example: r#"$env.name? | each { $"hello ($in)" } | default "bye""#,
113                description: "Update value if not null, otherwise do nothing.",
114                result: None,
115            },
116            Example {
117                description: "Scan through multiple files without pause.",
118                example: "\
119                    ls *.txt \
120                    | each --flatten {|f| open $f.name | lines } \
121                    | find -i 'note: ' \
122                    | str join \"\\n\"\
123                    ",
124                result: None,
125            },
126        ]
127    }
128
129    fn run(
130        &self,
131        engine_state: &EngineState,
132        stack: &mut Stack,
133        call: &Call,
134        mut input: PipelineData,
135    ) -> Result<PipelineData, ShellError> {
136        let head = call.head;
137        let closure: Closure = call.req(engine_state, stack, 0)?;
138        let keep_empty = call.has_flag(engine_state, stack, "keep-empty")?;
139        let flatten = call.has_flag(engine_state, stack, "flatten")?;
140
141        let result = match input {
142            PipelineData::Empty | PipelineData::Value(Value::Nothing { .. }, ..) => {
143                return Ok(input);
144            }
145            PipelineData::Value(Value::Custom { ref val, .. }, ..)
146                if val.type_name() == "matrix" =>
147            {
148                return Err(ShellError::Generic(
149                    nu_protocol::shell_error::generic::GenericError::new(
150                        "Unsupported type",
151                        "Use `matrix map` for element-wise operations or `matrix reduce` to fold values.",
152                        call.head,
153                    ),
154                ));
155            }
156            PipelineData::Value(Value::Range { .. }, ..)
157            | PipelineData::Value(Value::List { .. }, ..)
158            | PipelineData::ListStream(..) => {
159                let metadata = input.take_metadata();
160                let mut closure = ClosureEval::new(engine_state, stack, closure);
161
162                let out = if flatten {
163                    input
164                        .into_iter()
165                        .flat_map(move |value| {
166                            closure.run_with_value(value).unwrap_or_else(|error| {
167                                Value::error(error, head).into_pipeline_data()
168                            })
169                        })
170                        .into_pipeline_data(head, engine_state.signals().clone())
171                } else {
172                    input
173                        .into_iter()
174                        .map(move |value| {
175                            each_map(value, &mut closure, head)
176                                .unwrap_or_else(|error| Value::error(error, head))
177                        })
178                        .into_pipeline_data(head, engine_state.signals().clone())
179                };
180                Ok(out.set_metadata(metadata))
181            }
182            // Handle iterable custom values (like SQLiteQueryBuilder)
183            #[expect(deprecated)]
184            PipelineData::Value(Value::Custom { ref val, .. }, ..)
185                if val.is_iterable() && val.type_name() != "matrix" =>
186            {
187                let metadata = input.take_metadata();
188                let mut closure = ClosureEval::new(engine_state, stack, closure);
189
190                let out = if flatten {
191                    input
192                        .into_iter()
193                        .flat_map(move |value| {
194                            closure.run_with_value(value).unwrap_or_else(|error| {
195                                Value::error(error, head).into_pipeline_data()
196                            })
197                        })
198                        .into_pipeline_data(head, engine_state.signals().clone())
199                } else {
200                    input
201                        .into_iter()
202                        .map(move |value| {
203                            each_map(value, &mut closure, head)
204                                .unwrap_or_else(|error| Value::error(error, head))
205                        })
206                        .into_pipeline_data(head, engine_state.signals().clone())
207                };
208                Ok(out.set_metadata(metadata))
209            }
210            PipelineData::ByteStream(stream, metadata) => {
211                let Some(chunks) = stream.chunks() else {
212                    return Ok(PipelineData::empty());
213                };
214
215                let mut closure = ClosureEval::new(engine_state, stack, closure);
216                let out = if flatten {
217                    chunks
218                        .flat_map(move |result| {
219                            result
220                                .and_then(|value| closure.run_with_value(value))
221                                .unwrap_or_else(|error| {
222                                    Value::error(error, head).into_pipeline_data()
223                                })
224                        })
225                        .into_pipeline_data(head, engine_state.signals().clone())
226                } else {
227                    chunks
228                        .map(move |result| {
229                            result
230                                .and_then(|value| each_map(value, &mut closure, head))
231                                .unwrap_or_else(|error| Value::error(error, head))
232                        })
233                        .into_pipeline_data(head, engine_state.signals().clone())
234                };
235                Ok(out.set_metadata(metadata))
236            }
237            PipelineData::Value(Value::Custom { ref val, .. }, ..)
238                if val.type_name() == "matrix" =>
239            {
240                return Err(ShellError::Generic(
241                    nu_protocol::shell_error::generic::GenericError::new(
242                        "Unsupported type",
243                        "Use `matrix map` for element-wise operations.",
244                        call.head,
245                    ),
246                ));
247            }
248            // This match allows non-iterables to be accepted,
249            // which is currently considered undesirable (Nov 2022).
250            PipelineData::Value(value, metadata) => {
251                ClosureEvalOnce::new(engine_state, stack, closure)
252                    .run_with_value_with_metadata(value, metadata)
253            }
254        };
255
256        if keep_empty {
257            result
258        } else {
259            result.and_then(|x| x.filter(|v| !v.is_nothing(), engine_state.signals()))
260        }
261    }
262}
263
264#[inline]
265fn each_map(value: Value, closure: &mut ClosureEval, head: Span) -> Result<Value, ShellError> {
266    let span = value.span();
267    let is_error = value.is_error();
268    closure
269        .run_with_value(value)
270        .and_then(|pipeline_data| pipeline_data.into_value(head))
271        .map_err(|error| chain_error_with_input(error, is_error, span))
272}
273
274#[cfg(test)]
275mod test {
276    use super::*;
277
278    #[test]
279    fn test_examples() -> nu_test_support::Result {
280        nu_test_support::test().examples(Each)
281    }
282}