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