1use nu_engine::command_prelude::*;
2use nu_protocol::{DeprecationEntry, DeprecationType, ReportMode, ast::PathMember, casing::Casing};
3use std::{cmp::Reverse, collections::HashSet};
4
5#[derive(Clone)]
6pub struct Reject;
7
8impl Command for Reject {
9 fn name(&self) -> &str {
10 "reject"
11 }
12
13 fn signature(&self) -> Signature {
14 Signature::build("reject")
15 .input_output_types(vec![
16 (Type::record(), Type::record()),
17 (Type::table(), Type::table()),
18 (Type::list(Type::Any), Type::list(Type::Any)),
19 ])
20 .switch(
21 "optional",
22 "Make all cell path members optional.",
23 Some('o'),
24 )
25 .switch(
26 "ignore-case",
27 "Make all cell path members case insensitive.",
28 None,
29 )
30 .switch(
31 "ignore-errors",
32 "Ignore missing data (make all cell path members optional) (deprecated).",
33 Some('i'),
34 )
35 .rest(
36 "rest",
37 SyntaxShape::CellPath,
38 "The names of columns to remove from the table.",
39 )
40 .category(Category::Filters)
41 }
42
43 fn description(&self) -> &str {
44 "Remove the given columns or rows from the table. Opposite of `select`."
45 }
46
47 fn extra_description(&self) -> &str {
48 "To remove a quantity of rows or columns, use `skip`, `drop`, or `drop column`. To keep/retain only specific columns, use `select`."
49 }
50
51 fn search_terms(&self) -> Vec<&str> {
52 vec!["drop", "key"]
53 }
54
55 fn run(
56 &self,
57 engine_state: &EngineState,
58 stack: &mut Stack,
59 call: &Call,
60 input: PipelineData,
61 ) -> Result<PipelineData, ShellError> {
62 let columns: Vec<Value> = call.rest(engine_state, stack, 0)?;
63 let mut new_columns: Vec<CellPath> = vec![];
64 for col_val in columns {
65 let col_span = &col_val.span();
66 match col_val {
67 Value::CellPath { val, .. } => {
68 new_columns.push(val);
69 }
70 Value::String { val, .. } => {
71 let cv = CellPath {
72 members: vec![PathMember::String {
73 val: val.clone(),
74 span: *col_span,
75 optional: false,
76 casing: Casing::Sensitive,
77 }],
78 };
79 new_columns.push(cv.clone());
80 }
81 Value::Int { val, .. } => {
82 let cv = CellPath {
83 members: vec![PathMember::Int {
84 val: val as usize,
85 span: *col_span,
86 optional: false,
87 }],
88 };
89 new_columns.push(cv.clone());
90 }
91 x => {
92 return Err(ShellError::CantConvert {
93 to_type: "cell path".into(),
94 from_type: x.get_type().to_string(),
95 span: x.span(),
96 help: None,
97 });
98 }
99 }
100 }
101 let span = call.head;
102
103 let optional = call.has_flag(engine_state, stack, "optional")?
104 || call.has_flag(engine_state, stack, "ignore-errors")?;
105 let ignore_case = call.has_flag(engine_state, stack, "ignore-case")?;
106
107 if optional {
108 for cell_path in &mut new_columns {
109 cell_path.make_optional();
110 }
111 }
112
113 if ignore_case {
114 for cell_path in &mut new_columns {
115 cell_path.make_insensitive();
116 }
117 }
118
119 reject(engine_state, span, input, new_columns)
120 }
121
122 fn deprecation_info(&self) -> Vec<DeprecationEntry> {
123 vec![DeprecationEntry {
124 ty: DeprecationType::Flag("ignore-errors".into()),
125 report_mode: ReportMode::FirstUse,
126 since: Some("0.106.0".into()),
127 expected_removal: None,
128 help: Some(
129 "This flag has been renamed to `--optional (-o)` to better reflect its behavior."
130 .into(),
131 ),
132 }]
133 }
134
135 fn examples(&self) -> Vec<Example<'_>> {
136 vec![
137 Example {
138 description: "Reject a column in the `ls` table.",
139 example: "ls | reject modified",
140 result: None,
141 },
142 Example {
143 description: "Reject a column in a table.",
144 example: "[[a, b]; [1, 2]] | reject a",
145 result: Some(Value::test_list(vec![Value::test_record(record! {
146 "b" => Value::test_int(2),
147 })])),
148 },
149 Example {
150 description: "Reject a row in a table.",
151 example: "[[a, b]; [1, 2] [3, 4]] | reject 1",
152 result: Some(Value::test_list(vec![Value::test_record(record! {
153 "a" => Value::test_int(1),
154 "b" => Value::test_int(2),
155 })])),
156 },
157 Example {
158 description: "Reject the specified field in a record.",
159 example: "{a: 1, b: 2} | reject a",
160 result: Some(Value::test_record(record! {
161 "b" => Value::test_int(2),
162 })),
163 },
164 Example {
165 description: "Reject a nested field in a record.",
166 example: "{a: {b: 3, c: 5}} | reject a.b",
167 result: Some(Value::test_record(record! {
168 "a" => Value::test_record(record! {
169 "c" => Value::test_int(5),
170 }),
171 })),
172 },
173 Example {
174 description: "Reject multiple rows.",
175 example: "[[name type size]; [Cargo.toml toml 1kb] [Cargo.lock toml 2kb] [file.json json 3kb]] | reject 0 2",
176 result: None,
177 },
178 Example {
179 description: "Reject multiple columns.",
180 example: "[[name type size]; [Cargo.toml toml 1kb] [Cargo.lock toml 2kb]] | reject type size",
181 result: Some(Value::test_list(vec![
182 Value::test_record(record! { "name" => Value::test_string("Cargo.toml") }),
183 Value::test_record(record! { "name" => Value::test_string("Cargo.lock") }),
184 ])),
185 },
186 Example {
187 description: "Reject multiple columns by spreading a list.",
188 example: "let cols = [type size]; [[name type size]; [Cargo.toml toml 1kb] [Cargo.lock toml 2kb]] | reject ...$cols",
189 result: Some(Value::test_list(vec![
190 Value::test_record(record! { "name" => Value::test_string("Cargo.toml") }),
191 Value::test_record(record! { "name" => Value::test_string("Cargo.lock") }),
192 ])),
193 },
194 Example {
195 description: "Reject item in list.",
196 example: "[1 2 3] | reject 1",
197 result: Some(Value::test_list(vec![
198 Value::test_int(1),
199 Value::test_int(3),
200 ])),
201 },
202 ]
203 }
204}
205
206fn reject(
207 engine_state: &EngineState,
208 span: Span,
209 input: PipelineData,
210 cell_paths: Vec<CellPath>,
211) -> Result<PipelineData, ShellError> {
212 let input = match input.try_into_stream(engine_state) {
213 Ok(input) | Err(input) => input,
214 };
215 let mut unique_rows: HashSet<usize> = HashSet::new();
216 let metadata = input.metadata();
217 let mut new_columns = vec![];
218 let mut new_rows = vec![];
219 for column in cell_paths {
220 let CellPath { ref members } = column;
221 match members.first() {
222 Some(PathMember::Int { val, span, .. }) => {
223 if members.len() > 1 {
224 return Err(ShellError::GenericError {
225 error: "Reject only allows row numbers for rows".into(),
226 msg: "extra after row number".into(),
227 span: Some(*span),
228 help: None,
229 inner: vec![],
230 });
231 }
232 if !unique_rows.contains(val) {
233 unique_rows.insert(*val);
234 new_rows.push(column);
235 }
236 }
237 _ => {
238 if !new_columns.contains(&column) {
239 new_columns.push(column)
240 }
241 }
242 };
243 }
244 new_rows.sort_unstable_by_key(|k| {
245 Reverse({
246 match k.members[0] {
247 PathMember::Int { val, .. } => val,
248 PathMember::String { .. } => usize::MIN,
249 }
250 })
251 });
252
253 new_columns.append(&mut new_rows);
254
255 let has_integer_path_member = new_columns.iter().any(|path| {
256 path.members
257 .iter()
258 .any(|member| matches!(member, PathMember::Int { .. }))
259 });
260
261 match input {
262 PipelineData::ListStream(stream, ..) if !has_integer_path_member => {
263 let result = stream
264 .into_iter()
265 .map(move |mut value| {
266 let span = value.span();
267
268 for cell_path in new_columns.iter() {
269 if let Err(error) = value.remove_data_at_cell_path(&cell_path.members) {
270 return Value::error(error, span);
271 }
272 }
273
274 value
275 })
276 .into_pipeline_data(span, engine_state.signals().clone());
277
278 Ok(result)
279 }
280
281 input => {
282 let mut val = input.into_value(span)?;
283
284 for cell_path in new_columns {
285 val.remove_data_at_cell_path(&cell_path.members)?;
286 }
287
288 Ok(val.into_pipeline_data_with_metadata(metadata))
289 }
290 }
291}
292
293#[cfg(test)]
294mod test {
295 #[test]
296 fn test_examples() {
297 use super::Reject;
298 use crate::test_examples;
299 test_examples(Reject {})
300 }
301}