1#[cfg(feature = "sqlite")]
2use crate::database::QueryPlan;
3use nu_engine::command_prelude::*;
4use nu_protocol::{Signals, shell_error::io::IoError};
5use std::io::Read;
6
7#[derive(Clone)]
8pub struct First;
9
10impl Command for First {
11 fn name(&self) -> &str {
12 "first"
13 }
14
15 fn signature(&self) -> Signature {
16 Signature::build("first")
17 .input_output_types(vec![
18 (
19 Type::List(Box::new(Type::Any)),
22 Type::Any,
23 ),
24 (Type::Binary, Type::Binary),
25 (Type::Range, Type::Any),
26 ])
27 .optional(
28 "rows",
29 SyntaxShape::OneOf(vec![SyntaxShape::Int, SyntaxShape::Filesize]),
30 "Starting from the front, the number of rows to return.",
31 )
32 .switch("strict", "Throw an error if input is empty.", Some('s'))
33 .allow_variants_without_examples(true)
34 .category(Category::Filters)
35 }
36
37 fn search_terms(&self) -> Vec<&str> {
38 vec!["head"]
39 }
40
41 fn description(&self) -> &str {
42 "Return only the first several rows of the input. Counterpart of `last`. Opposite of `skip`. For binary input, rows can also be specified as a filesize."
43 }
44
45 fn run(
46 &self,
47 engine_state: &EngineState,
48 stack: &mut Stack,
49 call: &Call,
50 input: PipelineData,
51 ) -> Result<PipelineData, ShellError> {
52 first_helper(engine_state, stack, call, input)
53 }
54
55 fn examples(&self) -> Vec<Example<'_>> {
56 vec![
57 Example {
58 description: "Return the first item of a list/table.",
59 example: "[1 2 3] | first",
60 result: Some(Value::test_int(1)),
61 },
62 Example {
63 description: "Return the first 2 items of a list/table.",
64 example: "[1 2 3] | first 2",
65 result: Some(Value::list(
66 vec![Value::test_int(1), Value::test_int(2)],
67 Span::test_data(),
68 )),
69 },
70 Example {
71 description: "Return the first 2 bytes of a binary value.",
72 example: "0x[01 23 45] | first 2",
73 result: Some(Value::binary(vec![0x01, 0x23], Span::test_data())),
74 },
75 Example {
76 description: "Return the first item of a range.",
77 example: "1..3 | first",
78 result: Some(Value::test_int(1)),
79 },
80 Example {
81 description: "Return the first 2 bytes of a binary value, using a filesize argument.",
82 example: "0x[01 23 45] | first 2b",
83 result: Some(Value::test_binary(vec![0x01, 0x23])),
84 },
85 ]
86 }
87}
88
89fn first_helper(
90 engine_state: &EngineState,
91 stack: &mut Stack,
92 call: &Call,
93 input: PipelineData,
94) -> Result<PipelineData, ShellError> {
95 let head = call.head;
96 let rows_val: Option<Value> = call.opt(engine_state, stack, 0)?;
97 let is_filesize = rows_val
98 .as_ref()
99 .is_some_and(|v| matches!(v, Value::Filesize { .. }));
100 let strict_mode = call.has_flag(engine_state, stack, "strict")?;
101
102 let rows: Option<usize> = match rows_val {
103 Some(v) => {
104 let span = v.span();
105 match v {
106 Value::Int { val, .. } => Some(
107 usize::try_from(val).map_err(|_| ShellError::NeedsPositiveValue { span })?,
108 ),
109 Value::Filesize { val, .. } => Some(
110 usize::try_from(val).map_err(|_| ShellError::NeedsPositiveValue { span })?,
111 ),
112 ref val => {
113 return Err(ShellError::RuntimeTypeMismatch {
114 expected: Type::custom("int or filesize"),
115 actual: val.get_type(),
116 span: val.span(),
117 });
118 }
119 }
120 }
121 None => None,
122 };
123
124 let return_single_element = rows.is_none();
129 let rows = rows.unwrap_or(1);
130
131 let mut input = input;
132 let input_meta = input.take_metadata();
133
134 if is_filesize {
135 let is_binary = matches!(
136 &input,
137 PipelineData::Value(Value::Binary { .. }, _) | PipelineData::ByteStream(..)
138 );
139 if !is_binary {
140 return Err(ShellError::IncompatibleParametersSingle {
141 msg: "Filesize is only supported for binary/byte stream input".into(),
142 span: head,
143 });
144 }
145 }
146
147 if rows == 0 {
154 return match input {
155 PipelineData::Value(val, _) if matches!(&val, Value::Binary { .. }) => Ok(
156 Value::binary(Vec::new(), val.span()).into_pipeline_data_with_metadata(
157 input_meta.map(|m| m.with_content_type(None)),
158 ),
159 ),
160 PipelineData::ByteStream(stream, _) => {
161 if stream.type_().is_binary_coercible() {
162 let span = stream.span();
163 Ok(
164 Value::binary(Vec::new(), span).into_pipeline_data_with_metadata(
165 input_meta.map(|m| m.with_content_type(None)),
166 ),
167 )
168 } else {
169 Ok(Value::list(Vec::new(), head).into_pipeline_data_with_metadata(input_meta))
170 }
171 }
172 _ => Ok(Value::list(Vec::new(), head).into_pipeline_data_with_metadata(input_meta)),
173 };
174 }
175
176 match input {
177 PipelineData::Value(val, _) => {
178 let span = val.span();
179 match val {
180 Value::List { vals, .. } => {
181 if return_single_element {
182 if let Some(val) = vals.first() {
183 Ok(val.clone().into_pipeline_data_with_metadata(input_meta))
184 } else if strict_mode {
185 Err(ShellError::AccessEmptyContent { span: head })
186 } else {
187 Ok(Value::nothing(head).into_pipeline_data_with_metadata(input_meta))
190 }
191 } else {
192 let value = if rows >= vals.len() {
193 Value::list_shared(vals, span)
194 } else {
195 Value::list(vals.iter().take(rows).cloned().collect(), span)
196 };
197 Ok(value.into_pipeline_data_with_metadata(input_meta))
198 }
199 }
200 Value::Binary { val, .. } => {
201 let binary_meta = input_meta.map(|m| m.with_content_type(None));
203 if return_single_element {
204 if let Some(&val) = val.first() {
205 Ok(Value::int(val.into(), span)
206 .into_pipeline_data_with_metadata(binary_meta))
207 } else if strict_mode {
208 Err(ShellError::AccessEmptyContent { span: head })
209 } else {
210 Ok(Value::nothing(head).into_pipeline_data_with_metadata(binary_meta))
213 }
214 } else {
215 let mut val = val.into_owned();
216 val.truncate(rows);
217 Ok(Value::binary(val, span).into_pipeline_data_with_metadata(binary_meta))
218 }
219 }
220 Value::Range { val, .. } => {
221 let mut iter = val.into_range_iter(span, Signals::empty());
222 if return_single_element {
223 if let Some(v) = iter.next() {
224 Ok(v.into_pipeline_data_with_metadata(input_meta))
225 } else if strict_mode {
226 Err(ShellError::AccessEmptyContent { span: head })
227 } else {
228 Ok(Value::nothing(head).into_pipeline_data_with_metadata(input_meta))
231 }
232 } else {
233 Ok(iter.take(rows).into_pipeline_data_with_metadata(
234 span,
235 engine_state.signals().clone(),
236 input_meta,
237 ))
238 }
239 }
240 Value::Error { error, .. } => Err(*error),
242 #[cfg(feature = "sqlite")]
243 Value::Custom {
245 val: custom_val,
246 internal_span,
247 ..
248 } => {
249 if let Some(plan) = QueryPlan::try_from_any(custom_val.as_any()) {
250 if return_single_element {
251 let plan = plan.with_limit(1);
253 let result = plan.execute(head)?;
254 let value = result.into_value(head)?;
255 if let Value::List { vals, .. } = value {
256 if let Some(val) = vals.into_iter().next() {
257 Ok(val.into_pipeline_data_with_metadata(input_meta))
258 } else if strict_mode {
259 Err(ShellError::AccessEmptyContent { span: head })
260 } else {
261 Ok(Value::nothing(head)
264 .into_pipeline_data_with_metadata(input_meta))
265 }
266 } else {
267 Err(ShellError::NushellFailed {
268 msg: "Expected list from query plan".into(),
269 })
270 }
271 } else {
272 let plan = plan.with_limit(rows as i64);
274 plan.execute(head).map(|data| data.set_metadata(input_meta))
275 }
276 } else {
277 Err(ShellError::OnlySupportsThisInputType {
278 exp_input_type: "list, binary or range".into(),
279 wrong_type: custom_val.type_name(),
280 dst_span: head,
281 src_span: internal_span,
282 })
283 }
284 }
285 other => Err(ShellError::OnlySupportsThisInputType {
286 exp_input_type: "list, binary or range".into(),
287 wrong_type: other.get_type().to_string(),
288 dst_span: head,
289 src_span: other.span(),
290 }),
291 }
292 }
293 PipelineData::ListStream(stream, _) => {
294 if return_single_element {
295 if let Some(v) = stream.into_iter().next() {
296 Ok(v.into_pipeline_data_with_metadata(input_meta))
297 } else if strict_mode {
298 Err(ShellError::AccessEmptyContent { span: head })
299 } else {
300 Ok(Value::nothing(head).into_pipeline_data_with_metadata(input_meta))
303 }
304 } else {
305 Ok(PipelineData::list_stream(
306 stream.modify(|iter| iter.take(rows)),
307 input_meta,
308 ))
309 }
310 }
311 PipelineData::ByteStream(stream, _) => {
312 if stream.type_().is_binary_coercible() {
313 let span = stream.span();
314 let metadata = input_meta.map(|m| m.with_content_type(None));
315 if let Some(mut reader) = stream.reader() {
316 if return_single_element {
317 let mut byte = [0u8];
319 if reader
320 .read(&mut byte)
321 .map_err(|err| IoError::new(err, span, None))?
322 > 0
323 {
324 Ok(Value::int(byte[0] as i64, head)
325 .into_pipeline_data_with_metadata(metadata))
326 } else {
327 Err(ShellError::AccessEmptyContent { span: head })
328 }
329 } else {
330 Ok(PipelineData::byte_stream(
332 ByteStream::read(
333 reader.take(rows as u64),
334 head,
335 Signals::empty(),
336 ByteStreamType::Binary,
337 ),
338 metadata,
339 ))
340 }
341 } else {
342 Ok(Value::nothing(head).into_pipeline_data_with_metadata(metadata))
343 }
344 } else {
345 Err(ShellError::OnlySupportsThisInputType {
346 exp_input_type: "list, binary or range".into(),
347 wrong_type: stream.type_().describe().into(),
348 dst_span: head,
349 src_span: stream.span(),
350 })
351 }
352 }
353 PipelineData::Empty => Err(ShellError::OnlySupportsThisInputType {
354 exp_input_type: "list, binary or range".into(),
355 wrong_type: "null".into(),
356 dst_span: call.head,
357 src_span: call.head,
358 }),
359 }
360}
361#[cfg(test)]
362mod test {
363 use super::*;
364 #[test]
365 fn test_examples() -> nu_test_support::Result {
366 nu_test_support::test().examples(First)
367 }
368}