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        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 metadata = input.metadata();
142        let result = match input {
143            empty @ (PipelineData::Empty | PipelineData::Value(Value::Nothing { .. }, ..)) => {
144                return Ok(empty);
145            }
146            PipelineData::Value(Value::Range { .. }, ..)
147            | PipelineData::Value(Value::List { .. }, ..)
148            | PipelineData::ListStream(..) => {
149                let mut closure = ClosureEval::new(engine_state, stack, closure);
150
151                let out = if flatten {
152                    input
153                        .into_iter()
154                        .flat_map(move |value| {
155                            closure.run_with_value(value).unwrap_or_else(|error| {
156                                Value::error(error, head).into_pipeline_data()
157                            })
158                        })
159                        .into_pipeline_data(head, engine_state.signals().clone())
160                } else {
161                    input
162                        .into_iter()
163                        .map(move |value| {
164                            each_map(value, &mut closure, head)
165                                .unwrap_or_else(|error| Value::error(error, head))
166                        })
167                        .into_pipeline_data(head, engine_state.signals().clone())
168                };
169                Ok(out)
170            }
171            // Handle iterable custom values (like SQLiteQueryBuilder)
172            PipelineData::Value(Value::Custom { ref val, .. }, ..) if val.is_iterable() => {
173                let mut closure = ClosureEval::new(engine_state, stack, closure);
174
175                let out = if flatten {
176                    input
177                        .into_iter()
178                        .flat_map(move |value| {
179                            closure.run_with_value(value).unwrap_or_else(|error| {
180                                Value::error(error, head).into_pipeline_data()
181                            })
182                        })
183                        .into_pipeline_data(head, engine_state.signals().clone())
184                } else {
185                    input
186                        .into_iter()
187                        .map(move |value| {
188                            each_map(value, &mut closure, head)
189                                .unwrap_or_else(|error| Value::error(error, head))
190                        })
191                        .into_pipeline_data(head, engine_state.signals().clone())
192                };
193                Ok(out)
194            }
195            PipelineData::ByteStream(stream, ..) => {
196                let Some(chunks) = stream.chunks() else {
197                    return Ok(PipelineData::empty().set_metadata(metadata));
198                };
199
200                let mut closure = ClosureEval::new(engine_state, stack, closure);
201                let out = if flatten {
202                    chunks
203                        .flat_map(move |result| {
204                            result
205                                .and_then(|value| closure.run_with_value(value))
206                                .unwrap_or_else(|error| {
207                                    Value::error(error, head).into_pipeline_data()
208                                })
209                        })
210                        .into_pipeline_data(head, engine_state.signals().clone())
211                } else {
212                    chunks
213                        .map(move |result| {
214                            result
215                                .and_then(|value| each_map(value, &mut closure, head))
216                                .unwrap_or_else(|error| Value::error(error, head))
217                        })
218                        .into_pipeline_data(head, engine_state.signals().clone())
219                };
220                Ok(out)
221            }
222            // This match allows non-iterables to be accepted,
223            // which is currently considered undesirable (Nov 2022).
224            PipelineData::Value(value, ..) => {
225                ClosureEvalOnce::new(engine_state, stack, closure).run_with_value(value)
226            }
227        };
228
229        if keep_empty {
230            result
231        } else {
232            result.and_then(|x| x.filter(|v| !v.is_nothing(), engine_state.signals()))
233        }
234        .map(|data| data.set_metadata(metadata))
235    }
236}
237
238#[inline]
239fn each_map(value: Value, closure: &mut ClosureEval, head: Span) -> Result<Value, ShellError> {
240    let span = value.span();
241    let is_error = value.is_error();
242    closure
243        .run_with_value(value)
244        .and_then(|pipeline_data| pipeline_data.into_value(head))
245        .map_err(|error| chain_error_with_input(error, is_error, span))
246}
247
248#[cfg(test)]
249mod test {
250    use super::*;
251
252    #[test]
253    fn test_examples() {
254        use crate::test_examples;
255
256        test_examples(Each {})
257    }
258}