nu_command/strings/split/
row.rs1use fancy_regex::{Regex, escape};
2use nu_engine::command_prelude::*;
3use nu_protocol::shell_error::generic::GenericError;
4
5#[derive(Clone)]
6pub struct SplitRow;
7
8impl Command for SplitRow {
9 fn name(&self) -> &str {
10 "split row"
11 }
12
13 fn signature(&self) -> Signature {
14 Signature::build("split row")
15 .input_output_types(vec![
16 (Type::String, Type::List(Box::new(Type::String))),
17 (
18 Type::List(Box::new(Type::String)),
19 (Type::List(Box::new(Type::String))),
20 ),
21 ])
22 .allow_variants_without_examples(true)
23 .required(
24 "separator",
25 SyntaxShape::String,
26 "A character or regex that denotes what separates rows.",
27 )
28 .named(
29 "number",
30 SyntaxShape::Int,
31 "Split into maximum number of items.",
32 Some('n'),
33 )
34 .switch("regex", "Use regex syntax for separator.", Some('r'))
35 .category(Category::Strings)
36 }
37
38 fn description(&self) -> &str {
39 "Split a string into multiple rows using a separator."
40 }
41
42 fn search_terms(&self) -> Vec<&str> {
43 vec!["separate", "divide", "regex"]
44 }
45
46 fn examples(&self) -> Vec<Example<'_>> {
47 vec![
48 Example {
49 description: "Split a string into rows of char.",
50 example: "'abc' | split row ''",
51 result: Some(Value::list(
52 vec![
53 Value::test_string(""),
54 Value::test_string("a"),
55 Value::test_string("b"),
56 Value::test_string("c"),
57 Value::test_string(""),
58 ],
59 Span::test_data(),
60 )),
61 },
62 Example {
63 description: "Split a string into rows by the specified separator.",
64 example: "'a--b--c' | split row '--'",
65 result: Some(Value::list(
66 vec![
67 Value::test_string("a"),
68 Value::test_string("b"),
69 Value::test_string("c"),
70 ],
71 Span::test_data(),
72 )),
73 },
74 Example {
75 description: "Split a string by '-'.",
76 example: "'-a-b-c-' | split row '-'",
77 result: Some(Value::list(
78 vec![
79 Value::test_string(""),
80 Value::test_string("a"),
81 Value::test_string("b"),
82 Value::test_string("c"),
83 Value::test_string(""),
84 ],
85 Span::test_data(),
86 )),
87 },
88 Example {
89 description: "Split a string by regex.",
90 example: r"'a b c' | split row -r '\s+'",
91 result: Some(Value::list(
92 vec![
93 Value::test_string("a"),
94 Value::test_string("b"),
95 Value::test_string("c"),
96 ],
97 Span::test_data(),
98 )),
99 },
100 ]
101 }
102
103 fn is_const(&self) -> bool {
104 true
105 }
106
107 fn run(
108 &self,
109 engine_state: &EngineState,
110 stack: &mut Stack,
111 call: &Call,
112 input: PipelineData,
113 ) -> Result<PipelineData, ShellError> {
114 let separator: Spanned<String> = call.req(engine_state, stack, 0)?;
115 let max_split: Option<usize> = call.get_flag(engine_state, stack, "number")?;
116 let has_regex = call.has_flag(engine_state, stack, "regex")?;
117
118 let args = Arguments {
119 separator,
120 max_split,
121 has_regex,
122 };
123 split_row(engine_state, call, input, args)
124 }
125
126 fn run_const(
127 &self,
128 working_set: &StateWorkingSet,
129 call: &Call,
130 input: PipelineData,
131 ) -> Result<PipelineData, ShellError> {
132 let separator: Spanned<String> = call.req_const(working_set, 0)?;
133 let max_split: Option<usize> = call.get_flag_const(working_set, "number")?;
134 let has_regex = call.has_flag_const(working_set, "regex")?;
135
136 let args = Arguments {
137 separator,
138 max_split,
139 has_regex,
140 };
141 split_row(working_set.permanent(), call, input, args)
142 }
143}
144
145struct Arguments {
146 has_regex: bool,
147 separator: Spanned<String>,
148 max_split: Option<usize>,
149}
150
151fn split_row(
152 engine_state: &EngineState,
153 call: &Call,
154 input: PipelineData,
155 args: Arguments,
156) -> Result<PipelineData, ShellError> {
157 let name_span = call.head;
158 let regex = if args.has_regex {
159 Regex::new(&args.separator.item)
160 } else {
161 let escaped = escape(&args.separator.item);
162 Regex::new(&escaped)
163 }
164 .map_err(|e| {
165 ShellError::Generic(GenericError::new(
166 "Error with regular expression",
167 e.to_string(),
168 args.separator.span,
169 ))
170 })?;
171 input.flat_map(
172 move |x| split_row_helper(&x, ®ex, args.max_split, name_span),
173 engine_state.signals(),
174 )
175}
176
177fn split_row_helper(v: &Value, regex: &Regex, max_split: Option<usize>, name: Span) -> Vec<Value> {
178 let span = v.span();
179 match v {
180 Value::Error { error, .. } => {
181 vec![Value::error(*error.clone(), span)]
182 }
183 v => {
184 let v_span = v.span();
185
186 if let Ok(s) = v.coerce_str() {
187 match max_split {
188 Some(max_split) => regex
189 .splitn(&s, max_split)
190 .map(|x| match x {
191 Ok(val) => Value::string(val, v_span),
192 Err(err) => Value::error(
193 ShellError::Generic(GenericError::new(
194 "Error with regular expression",
195 err.to_string(),
196 v_span,
197 )),
198 v_span,
199 ),
200 })
201 .collect(),
202 None => regex
203 .split(&s)
204 .map(|x| match x {
205 Ok(val) => Value::string(val, v_span),
206 Err(err) => Value::error(
207 ShellError::Generic(GenericError::new(
208 "Error with regular expression",
209 err.to_string(),
210 v_span,
211 )),
212 v_span,
213 ),
214 })
215 .collect(),
216 }
217 } else {
218 vec![Value::error(
219 ShellError::OnlySupportsThisInputType {
220 exp_input_type: "string".into(),
221 wrong_type: v.get_type().to_string(),
222 dst_span: name,
223 src_span: v_span,
224 },
225 name,
226 )]
227 }
228 }
229 }
230}
231
232#[cfg(test)]
233mod test {
234 use super::*;
235
236 #[test]
237 fn test_examples() -> nu_test_support::Result {
238 nu_test_support::test().examples(SplitRow)
239 }
240}