Skip to main content

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