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