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