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