nu_command/filters/
first.rs

1use nu_engine::command_prelude::*;
2use nu_protocol::{Signals, shell_error::io::IoError};
3use std::io::Read;
4
5#[derive(Clone)]
6pub struct First;
7
8impl Command for First {
9    fn name(&self) -> &str {
10        "first"
11    }
12
13    fn signature(&self) -> Signature {
14        Signature::build("first")
15            .input_output_types(vec![
16                (
17                    // TODO: This is too permissive; if we could express this
18                    // using a type parameter it would be List<T> -> T.
19                    Type::List(Box::new(Type::Any)),
20                    Type::Any,
21                ),
22                (Type::Binary, Type::Binary),
23                (Type::Range, Type::Any),
24            ])
25            .optional(
26                "rows",
27                SyntaxShape::Int,
28                "Starting from the front, the number of rows to return.",
29            )
30            .switch("strict", "Throw an error if input is empty", Some('s'))
31            .allow_variants_without_examples(true)
32            .category(Category::Filters)
33    }
34
35    fn description(&self) -> &str {
36        "Return only the first several rows of the input. Counterpart of `last`. Opposite of `skip`."
37    }
38
39    fn run(
40        &self,
41        engine_state: &EngineState,
42        stack: &mut Stack,
43        call: &Call,
44        input: PipelineData,
45    ) -> Result<PipelineData, ShellError> {
46        first_helper(engine_state, stack, call, input)
47    }
48
49    fn examples(&self) -> Vec<Example<'_>> {
50        vec![
51            Example {
52                description: "Return the first item of a list/table",
53                example: "[1 2 3] | first",
54                result: Some(Value::test_int(1)),
55            },
56            Example {
57                description: "Return the first 2 items of a list/table",
58                example: "[1 2 3] | first 2",
59                result: Some(Value::list(
60                    vec![Value::test_int(1), Value::test_int(2)],
61                    Span::test_data(),
62                )),
63            },
64            Example {
65                description: "Return the first 2 bytes of a binary value",
66                example: "0x[01 23 45] | first 2",
67                result: Some(Value::binary(vec![0x01, 0x23], Span::test_data())),
68            },
69            Example {
70                description: "Return the first item of a range",
71                example: "1..3 | first",
72                result: Some(Value::test_int(1)),
73            },
74        ]
75    }
76}
77
78fn first_helper(
79    engine_state: &EngineState,
80    stack: &mut Stack,
81    call: &Call,
82    input: PipelineData,
83) -> Result<PipelineData, ShellError> {
84    let head = call.head;
85    let rows: Option<Spanned<i64>> = call.opt(engine_state, stack, 0)?;
86    let strict_mode = call.has_flag(engine_state, stack, "strict")?;
87
88    // FIXME: for backwards compatibility reasons, if `rows` is not specified we
89    // return a single element and otherwise we return a single list. We should probably
90    // remove `rows` so that `first` always returns a single element; getting a list of
91    // the first N elements is covered by `take`
92    let return_single_element = rows.is_none();
93    let rows = if let Some(rows) = rows {
94        if rows.item < 0 {
95            return Err(ShellError::NeedsPositiveValue { span: rows.span });
96        } else {
97            rows.item as usize
98        }
99    } else {
100        1
101    };
102
103    // first 5 bytes of an image/png are not image/png themselves
104    let metadata = input.metadata().map(|m| m.with_content_type(None));
105
106    // early exit for `first 0`
107    if rows == 0 {
108        return Ok(Value::list(Vec::new(), head).into_pipeline_data_with_metadata(metadata));
109    }
110
111    match input {
112        PipelineData::Value(val, _) => {
113            let span = val.span();
114            match val {
115                Value::List { mut vals, .. } => {
116                    if return_single_element {
117                        if let Some(val) = vals.first_mut() {
118                            Ok(std::mem::take(val).into_pipeline_data())
119                        } else if strict_mode {
120                            Err(ShellError::AccessEmptyContent { span: head })
121                        } else {
122                            // There are no values, so return nothing instead of an error so
123                            // that users can pipe this through 'default' if they want to.
124                            Ok(Value::nothing(head).into_pipeline_data_with_metadata(metadata))
125                        }
126                    } else {
127                        vals.truncate(rows);
128                        Ok(Value::list(vals, span).into_pipeline_data_with_metadata(metadata))
129                    }
130                }
131                Value::Binary { mut val, .. } => {
132                    if return_single_element {
133                        if let Some(&val) = val.first() {
134                            Ok(Value::int(val.into(), span).into_pipeline_data())
135                        } else if strict_mode {
136                            Err(ShellError::AccessEmptyContent { span: head })
137                        } else {
138                            // There are no values, so return nothing instead of an error so
139                            // that users can pipe this through 'default' if they want to.
140                            Ok(Value::nothing(head).into_pipeline_data_with_metadata(metadata))
141                        }
142                    } else {
143                        val.truncate(rows);
144                        Ok(Value::binary(val, span).into_pipeline_data_with_metadata(metadata))
145                    }
146                }
147                Value::Range { val, .. } => {
148                    let mut iter = val.into_range_iter(span, Signals::empty());
149                    if return_single_element {
150                        if let Some(v) = iter.next() {
151                            Ok(v.into_pipeline_data())
152                        } else if strict_mode {
153                            Err(ShellError::AccessEmptyContent { span: head })
154                        } else {
155                            // There are no values, so return nothing instead of an error so
156                            // that users can pipe this through 'default' if they want to.
157                            Ok(Value::nothing(head).into_pipeline_data_with_metadata(metadata))
158                        }
159                    } else {
160                        Ok(iter.take(rows).into_pipeline_data_with_metadata(
161                            span,
162                            engine_state.signals().clone(),
163                            metadata,
164                        ))
165                    }
166                }
167                // Propagate errors by explicitly matching them before the final case.
168                Value::Error { error, .. } => Err(*error),
169                other => Err(ShellError::OnlySupportsThisInputType {
170                    exp_input_type: "list, binary or range".into(),
171                    wrong_type: other.get_type().to_string(),
172                    dst_span: head,
173                    src_span: other.span(),
174                }),
175            }
176        }
177        PipelineData::ListStream(stream, metadata) => {
178            if return_single_element {
179                if let Some(v) = stream.into_iter().next() {
180                    Ok(v.into_pipeline_data())
181                } else if strict_mode {
182                    Err(ShellError::AccessEmptyContent { span: head })
183                } else {
184                    // There are no values, so return nothing instead of an error so
185                    // that users can pipe this through 'default' if they want to.
186                    Ok(Value::nothing(head).into_pipeline_data_with_metadata(metadata))
187                }
188            } else {
189                Ok(PipelineData::list_stream(
190                    stream.modify(|iter| iter.take(rows)),
191                    metadata,
192                ))
193            }
194        }
195        PipelineData::ByteStream(stream, metadata) => {
196            if stream.type_().is_binary_coercible() {
197                let span = stream.span();
198                let metadata = metadata.map(|m| m.with_content_type(None));
199                if let Some(mut reader) = stream.reader() {
200                    if return_single_element {
201                        // Take a single byte
202                        let mut byte = [0u8];
203                        if reader
204                            .read(&mut byte)
205                            .map_err(|err| IoError::new(err, span, None))?
206                            > 0
207                        {
208                            Ok(Value::int(byte[0] as i64, head).into_pipeline_data())
209                        } else {
210                            Err(ShellError::AccessEmptyContent { span: head })
211                        }
212                    } else {
213                        // Just take 'rows' bytes off the stream, mimicking the binary behavior
214                        Ok(PipelineData::byte_stream(
215                            ByteStream::read(
216                                reader.take(rows as u64),
217                                head,
218                                Signals::empty(),
219                                ByteStreamType::Binary,
220                            ),
221                            metadata,
222                        ))
223                    }
224                } else {
225                    Ok(PipelineData::empty())
226                }
227            } else {
228                Err(ShellError::OnlySupportsThisInputType {
229                    exp_input_type: "list, binary or range".into(),
230                    wrong_type: stream.type_().describe().into(),
231                    dst_span: head,
232                    src_span: stream.span(),
233                })
234            }
235        }
236        PipelineData::Empty => Err(ShellError::OnlySupportsThisInputType {
237            exp_input_type: "list, binary or range".into(),
238            wrong_type: "null".into(),
239            dst_span: call.head,
240            src_span: call.head,
241        }),
242    }
243}
244#[cfg(test)]
245mod test {
246    use super::*;
247    #[test]
248    fn test_examples() {
249        use crate::test_examples;
250
251        test_examples(First {})
252    }
253}