1use itertools::Either;
2use nu_engine::command_prelude::*;
3use nu_protocol::{PipelineIterator, Range};
4use std::ops::Bound;
5
6#[derive(Clone)]
7pub struct DropNth;
8
9impl Command for DropNth {
10 fn name(&self) -> &str {
11 "drop nth"
12 }
13
14 fn signature(&self) -> Signature {
15 Signature::build("drop nth")
16 .input_output_types(vec![
17 (Type::Range, Type::list(Type::Number)),
18 (Type::list(Type::Any), Type::list(Type::Any)),
19 ])
20 .allow_variants_without_examples(true)
21 .required(
22 "row number or row range",
23 SyntaxShape::Any,
25 "The number of the row to drop or a range to drop consecutive rows.",
26 )
27 .rest("rest", SyntaxShape::Any, "The number of the row to drop.")
28 .category(Category::Filters)
29 }
30
31 fn description(&self) -> &str {
32 "Drop the selected rows."
33 }
34
35 fn search_terms(&self) -> Vec<&str> {
36 vec!["delete", "remove", "index"]
37 }
38
39 fn examples(&self) -> Vec<Example> {
40 vec![
41 Example {
42 example: "[sam,sarah,2,3,4,5] | drop nth 0 1 2",
43 description: "Drop the first, second, and third row",
44 result: Some(Value::list(
45 vec![Value::test_int(3), Value::test_int(4), Value::test_int(5)],
46 Span::test_data(),
47 )),
48 },
49 Example {
50 example: "[0,1,2,3,4,5] | drop nth 0 1 2",
51 description: "Drop the first, second, and third row",
52 result: Some(Value::list(
53 vec![Value::test_int(3), Value::test_int(4), Value::test_int(5)],
54 Span::test_data(),
55 )),
56 },
57 Example {
58 example: "[0,1,2,3,4,5] | drop nth 0 2 4",
59 description: "Drop rows 0 2 4",
60 result: Some(Value::list(
61 vec![Value::test_int(1), Value::test_int(3), Value::test_int(5)],
62 Span::test_data(),
63 )),
64 },
65 Example {
66 example: "[0,1,2,3,4,5] | drop nth 2 0 4",
67 description: "Drop rows 2 0 4",
68 result: Some(Value::list(
69 vec![Value::test_int(1), Value::test_int(3), Value::test_int(5)],
70 Span::test_data(),
71 )),
72 },
73 Example {
74 description: "Drop range rows from second to fourth",
75 example: "[first second third fourth fifth] | drop nth (1..3)",
76 result: Some(Value::list(
77 vec![Value::test_string("first"), Value::test_string("fifth")],
78 Span::test_data(),
79 )),
80 },
81 Example {
82 example: "[0,1,2,3,4,5] | drop nth 1..",
83 description: "Drop all rows except first row",
84 result: Some(Value::list(vec![Value::test_int(0)], Span::test_data())),
85 },
86 Example {
87 example: "[0,1,2,3,4,5] | drop nth 3..",
88 description: "Drop rows 3,4,5",
89 result: Some(Value::list(
90 vec![Value::test_int(0), Value::test_int(1), Value::test_int(2)],
91 Span::test_data(),
92 )),
93 },
94 ]
95 }
96
97 fn run(
98 &self,
99 engine_state: &EngineState,
100 stack: &mut Stack,
101 call: &Call,
102 input: PipelineData,
103 ) -> Result<PipelineData, ShellError> {
104 let head = call.head;
105 let metadata = input.metadata();
106 let number_or_range = extract_int_or_range(engine_state, stack, call)?;
107
108 let rows = match number_or_range.item {
109 Either::Left(row_number) => {
110 let and_rows: Vec<Spanned<i64>> = call.rest(engine_state, stack, 1)?;
111 let mut rows: Vec<_> = and_rows.into_iter().map(|x| x.item as usize).collect();
112 rows.push(row_number as usize);
113 rows.sort_unstable();
114 rows
115 }
116 Either::Right(Range::FloatRange(_)) => {
117 return Err(ShellError::UnsupportedInput {
118 msg: "float range".into(),
119 input: "value originates from here".into(),
120 msg_span: head,
121 input_span: number_or_range.span,
122 });
123 }
124 Either::Right(Range::IntRange(range)) => {
125 let end_negative = match range.end() {
127 Bound::Included(end) | Bound::Excluded(end) => end < 0,
128 Bound::Unbounded => false,
129 };
130 if range.start().is_negative() || end_negative {
131 return Err(ShellError::UnsupportedInput {
132 msg: "drop nth accepts only positive ints".into(),
133 input: "value originates from here".into(),
134 msg_span: head,
135 input_span: number_or_range.span,
136 });
137 }
138 if range.step() < 0 {
140 return Err(ShellError::UnsupportedInput {
141 msg: "The upper bound needs to be equal or larger to the lower bound"
142 .into(),
143 input: "value originates from here".into(),
144 msg_span: head,
145 input_span: number_or_range.span,
146 });
147 }
148
149 let start = range.start() as usize;
150
151 let end = match range.end() {
152 Bound::Included(end) => end as usize,
153 Bound::Excluded(end) => (end - 1) as usize,
154 Bound::Unbounded => {
155 return Ok(input
156 .into_iter()
157 .take(start)
158 .into_pipeline_data_with_metadata(
159 head,
160 engine_state.signals().clone(),
161 metadata,
162 ));
163 }
164 };
165
166 let end = if let PipelineData::Value(Value::List { vals, .. }, _) = &input {
167 end.min(vals.len() - 1)
168 } else {
169 end
170 };
171
172 (start..=end).collect()
173 }
174 };
175
176 Ok(DropNthIterator {
177 input: input.into_iter(),
178 rows,
179 current: 0,
180 }
181 .into_pipeline_data_with_metadata(head, engine_state.signals().clone(), metadata))
182 }
183}
184
185fn extract_int_or_range(
186 engine_state: &EngineState,
187 stack: &mut Stack,
188 call: &Call,
189) -> Result<Spanned<Either<i64, Range>>, ShellError> {
190 let value: Value = call.req(engine_state, stack, 0)?;
191
192 let int_opt = value.as_int().map(Either::Left).ok();
193 let range_opt = value.as_range().map(Either::Right).ok();
194
195 int_opt
196 .or(range_opt)
197 .ok_or_else(|| ShellError::TypeMismatch {
198 err_message: "int or range".into(),
199 span: value.span(),
200 })
201 .map(|either| Spanned {
202 item: either,
203 span: value.span(),
204 })
205}
206
207struct DropNthIterator {
208 input: PipelineIterator,
209 rows: Vec<usize>,
210 current: usize,
211}
212
213impl Iterator for DropNthIterator {
214 type Item = Value;
215
216 fn next(&mut self) -> Option<Self::Item> {
217 loop {
218 if let Some(row) = self.rows.first() {
219 if self.current == *row {
220 self.rows.remove(0);
221 self.current += 1;
222 let _ = self.input.next();
223 continue;
224 } else {
225 self.current += 1;
226 return self.input.next();
227 }
228 } else {
229 return self.input.next();
230 }
231 }
232 }
233}
234
235#[cfg(test)]
236mod test {
237 use super::*;
238
239 #[test]
240 fn test_examples() {
241 use crate::test_examples;
242
243 test_examples(DropNth {})
244 }
245}