Skip to main content

shannon_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 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}