nu_command/strings/split/
row.rs

1use 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, &regex, 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}