1use nu_engine::{ClosureEval, command_prelude::*};
2use nu_protocol::engine::{Closure, CommandType};
3
4#[derive(Clone)]
5pub struct Where;
6
7impl Command for Where {
8 fn name(&self) -> &str {
9 "where"
10 }
11
12 fn description(&self) -> &str {
13 "Filter values of an input list based on a condition."
14 }
15
16 fn extra_description(&self) -> &str {
17 r#"A condition is evaluated for each element of the input, and only elements which meet the condition are included in the output.
18
19A condition can be either a "row condition" or a closure. A row condition is a special short-hand syntax to makes accessing fields easier.
20Each element of the input can be accessed through the `$it` variable.
21
22On the left hand side of a row condition, any field name is automatically expanded to use `$it`.
23For example, `where type == dir` is equivalent to `where $it.type == dir`. This expansion does not happen when passing a subexpression or closure to `where`.
24
25When using a closure, the element is passed as an argument and as pipeline input (`$in`) to the closure. Unlike row conditions, the `$it` variable isn't available inside closures.
26
27Row conditions cannot be stored in a variable. To pass a condition with a variable, use a closure instead."#
28 }
29
30 fn command_type(&self) -> CommandType {
31 CommandType::Keyword
32 }
33
34 fn signature(&self) -> nu_protocol::Signature {
35 Signature::build("where")
36 .input_output_types(vec![
37 (
38 Type::List(Box::new(Type::Any)),
39 Type::List(Box::new(Type::Any)),
40 ),
41 (Type::table(), Type::table()),
42 (Type::Range, Type::Any),
43 ])
44 .required(
45 "condition",
46 SyntaxShape::RowCondition,
47 "Filter row condition or closure.",
48 )
49 .allow_variants_without_examples(true)
50 .category(Category::Filters)
51 }
52
53 fn search_terms(&self) -> Vec<&str> {
54 vec!["filter", "find", "search", "condition"]
55 }
56
57 fn run(
58 &self,
59 engine_state: &EngineState,
60 stack: &mut Stack,
61 call: &Call,
62 input: PipelineData,
63 ) -> Result<PipelineData, ShellError> {
64 let head = call.head;
65 let closure: Closure = call.req(engine_state, stack, 0)?;
66
67 let mut closure = ClosureEval::new(engine_state, stack, closure);
68
69 let metadata = input.metadata();
70 Ok(input
71 .into_iter_strict(head)?
72 .filter_map(move |value| {
73 match closure
74 .run_with_value(value.clone())
75 .and_then(|data| data.into_value(head))
76 {
77 Ok(cond) => cond.is_true().then_some(value),
78 Err(err) => Some(Value::error(err, head)),
79 }
80 })
81 .into_pipeline_data_with_metadata(head, engine_state.signals().clone(), metadata))
82 }
83
84 fn examples(&self) -> Vec<Example<'_>> {
85 vec![
86 Example {
87 description: "Filter rows of a table according to a condition",
88 example: "[{a: 1} {a: 2}] | where a > 1",
89 result: Some(Value::test_list(vec![Value::test_record(record! {
90 "a" => Value::test_int(2),
91 })])),
92 },
93 Example {
94 description: "List only the files in the current directory",
95 example: "ls | where type == file",
96 result: None,
97 },
98 Example {
99 description: "List all files in the current directory with sizes greater than 2kb",
100 example: "ls | where size > 2kb",
101 result: None,
102 },
103 Example {
104 description: r#"List all files with names that contain "Car""#,
105 example: r#"ls | where name =~ "Car""#,
106 result: None,
107 },
108 Example {
109 description: "List all files that were modified in the last two weeks",
110 example: "ls | where modified >= (date now) - 2wk",
111 result: None,
112 },
113 Example {
114 description: "Filter items of a list with a row condition",
115 example: "[1 2 3 4 5] | where $it > 2",
116 result: Some(Value::test_list(vec![
117 Value::test_int(3),
118 Value::test_int(4),
119 Value::test_int(5),
120 ])),
121 },
122 Example {
123 description: "Filter items of a list with a closure",
124 example: "[1 2 3 4 5] | where {|x| $x > 2 }",
125 result: Some(Value::test_list(vec![
126 Value::test_int(3),
127 Value::test_int(4),
128 Value::test_int(5),
129 ])),
130 },
131 Example {
132 description: "Find files whose filenames don't begin with the correct sequential number",
133 example: "ls | where type == file | sort-by name --natural | enumerate | where {|e| $e.item.name !~ $'^($e.index + 1)' } | get item",
134 result: None,
135 },
136 Example {
137 description: r#"Find case-insensitively files called "readme", with a subexpression inside the row condition"#,
138 example: "ls | where ($it.name | str downcase) =~ readme",
139 result: None,
140 },
141 Example {
142 description: r#"Find case-insensitively files called "readme", with regex only"#,
143 example: "ls | where name =~ '(?i)readme'",
144 result: None,
145 },
146 Example {
147 description: "Filter rows of a table according to a stored condition",
148 example: "let cond = {|x| $x.a > 1}; [{a: 1} {a: 2}] | where $cond",
149 result: Some(Value::test_list(vec![Value::test_record(record! {
150 "a" => Value::test_int(2),
151 })])),
152 },
153 Example {
154 description: "List all numbers above 3, using an existing closure condition",
155 example: "let a = {$in > 3}; [1, 2, 5, 6] | where $a",
156 result: Some(Value::test_list(vec![
157 Value::test_int(5),
158 Value::test_int(6),
159 ])),
160 },
161 ]
162 }
163}
164
165#[cfg(test)]
166mod test {
167 use super::*;
168
169 #[test]
170 fn test_examples() {
171 use crate::test_examples;
172
173 test_examples(Where {})
174 }
175}