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