1use nu_cmd_base::input_handler::{CmdArgument, operate};
2use nu_engine::command_prelude::*;
3
4use nu_utils::IgnoreCaseExt;
5
6#[derive(Clone)]
7pub struct StrContains;
8
9struct Arguments {
10 substring: String,
11 cell_paths: Option<Vec<CellPath>>,
12 case_insensitive: bool,
13}
14
15impl CmdArgument for Arguments {
16 fn take_cell_paths(&mut self) -> Option<Vec<CellPath>> {
17 self.cell_paths.take()
18 }
19}
20
21impl Command for StrContains {
22 fn name(&self) -> &str {
23 "str contains"
24 }
25
26 fn signature(&self) -> Signature {
27 Signature::build("str contains")
28 .input_output_types(vec![
29 (Type::String, Type::Bool),
30 (Type::table(), Type::table()),
32 (Type::record(), Type::record()),
33 (Type::List(Box::new(Type::String)), Type::List(Box::new(Type::Bool)))
34 ])
35 .required("string", SyntaxShape::String, "The substring to find.")
36 .rest(
37 "rest",
38 SyntaxShape::CellPath,
39 "For a data structure input, check strings at the given cell paths, and replace with result.",
40 )
41 .switch("ignore-case", "search is case insensitive", Some('i'))
42 .category(Category::Strings)
43 }
44
45 fn description(&self) -> &str {
46 "Checks if string input contains a substring."
47 }
48
49 fn search_terms(&self) -> Vec<&str> {
50 vec!["substring", "match", "find", "search"]
51 }
52
53 fn is_const(&self) -> bool {
54 true
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 cell_paths: Vec<CellPath> = call.rest(engine_state, stack, 1)?;
65 let cell_paths = (!cell_paths.is_empty()).then_some(cell_paths);
66 let args = Arguments {
67 substring: call.req::<String>(engine_state, stack, 0)?,
68 cell_paths,
69 case_insensitive: call.has_flag(engine_state, stack, "ignore-case")?,
70 };
71 operate(action, args, input, call.head, engine_state.signals())
72 }
73
74 fn run_const(
75 &self,
76 working_set: &StateWorkingSet,
77 call: &Call,
78 input: PipelineData,
79 ) -> Result<PipelineData, ShellError> {
80 let cell_paths: Vec<CellPath> = call.rest_const(working_set, 1)?;
81 let cell_paths = (!cell_paths.is_empty()).then_some(cell_paths);
82 let args = Arguments {
83 substring: call.req_const::<String>(working_set, 0)?,
84 cell_paths,
85 case_insensitive: call.has_flag_const(working_set, "ignore-case")?,
86 };
87 operate(
88 action,
89 args,
90 input,
91 call.head,
92 working_set.permanent().signals(),
93 )
94 }
95
96 fn examples(&self) -> Vec<Example> {
97 vec![
98 Example {
99 description: "Check if input contains string",
100 example: "'my_library.rb' | str contains '.rb'",
101 result: Some(Value::test_bool(true)),
102 },
103 Example {
104 description: "Check if input contains string case insensitive",
105 example: "'my_library.rb' | str contains --ignore-case '.RB'",
106 result: Some(Value::test_bool(true)),
107 },
108 Example {
109 description: "Check if input contains string in a record",
110 example: "{ ColA: test, ColB: 100 } | str contains 'e' ColA",
111 result: Some(Value::test_record(record! {
112 "ColA" => Value::test_bool(true),
113 "ColB" => Value::test_int(100),
114 })),
115 },
116 Example {
117 description: "Check if input contains string in a table",
118 example: " [[ColA ColB]; [test 100]] | str contains --ignore-case 'E' ColA",
119 result: Some(Value::test_list(vec![Value::test_record(record! {
120 "ColA" => Value::test_bool(true),
121 "ColB" => Value::test_int(100),
122 })])),
123 },
124 Example {
125 description: "Check if input contains string in a table",
126 example: " [[ColA ColB]; [test hello]] | str contains 'e' ColA ColB",
127 result: Some(Value::test_list(vec![Value::test_record(record! {
128 "ColA" => Value::test_bool(true),
129 "ColB" => Value::test_bool(true),
130 })])),
131 },
132 Example {
133 description: "Check if input string contains 'banana'",
134 example: "'hello' | str contains 'banana'",
135 result: Some(Value::test_bool(false)),
136 },
137 Example {
138 description: "Check if list contains string",
139 example: "[one two three] | str contains o",
140 result: Some(Value::test_list(vec![
141 Value::test_bool(true),
142 Value::test_bool(true),
143 Value::test_bool(false),
144 ])),
145 },
146 ]
147 }
148}
149
150fn action(
151 input: &Value,
152 Arguments {
153 case_insensitive,
154 substring,
155 ..
156 }: &Arguments,
157 head: Span,
158) -> Value {
159 match input {
160 Value::String { val, .. } => Value::bool(
161 if *case_insensitive {
162 val.to_folded_case()
163 .contains(substring.to_folded_case().as_str())
164 } else {
165 val.contains(substring)
166 },
167 head,
168 ),
169 Value::Error { .. } => input.clone(),
170 _ => Value::error(
171 ShellError::OnlySupportsThisInputType {
172 exp_input_type: "string".into(),
173 wrong_type: input.get_type().to_string(),
174 dst_span: head,
175 src_span: input.span(),
176 },
177 head,
178 ),
179 }
180}
181
182#[cfg(test)]
183mod test {
184 use super::*;
185
186 #[test]
187 fn test_examples() {
188 use crate::test_examples;
189
190 test_examples(StrContains {})
191 }
192}