nu_command/filters/
reject.rs

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}