Skip to main content

nu_command/filters/
reject.rs

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    // remove path_columns that are available in new_columns
254    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}