nu_command/strings/split/
chars.rs

1use crate::{grapheme_flags, grapheme_flags_const};
2use nu_engine::command_prelude::*;
3
4use unicode_segmentation::UnicodeSegmentation;
5
6#[derive(Clone)]
7pub struct SplitChars;
8
9impl Command for SplitChars {
10    fn name(&self) -> &str {
11        "split chars"
12    }
13
14    fn signature(&self) -> Signature {
15        Signature::build("split chars")
16            .input_output_types(vec![
17                (Type::String, Type::List(Box::new(Type::String))),
18                (
19                    Type::List(Box::new(Type::String)),
20                    Type::List(Box::new(Type::List(Box::new(Type::String)))),
21                ),
22            ])
23            .allow_variants_without_examples(true)
24            .switch("grapheme-clusters", "split on grapheme clusters", Some('g'))
25            .switch(
26                "code-points",
27                "split on code points (default; splits combined characters)",
28                Some('c'),
29            )
30            .category(Category::Strings)
31    }
32
33    fn description(&self) -> &str {
34        "Split a string into a list of characters."
35    }
36
37    fn search_terms(&self) -> Vec<&str> {
38        vec!["character", "separate", "divide"]
39    }
40
41    fn examples(&self) -> Vec<Example> {
42        vec![
43            Example {
44                description: "Split the string into a list of characters",
45                example: "'hello' | split chars",
46                result: Some(Value::list(
47                    vec![
48                        Value::test_string("h"),
49                        Value::test_string("e"),
50                        Value::test_string("l"),
51                        Value::test_string("l"),
52                        Value::test_string("o"),
53                    ],
54                    Span::test_data(),
55                )),
56            },
57            Example {
58                description: "Split on grapheme clusters",
59                example: "'🇯🇵ほげ' | split chars --grapheme-clusters",
60                result: Some(Value::list(
61                    vec![
62                        Value::test_string("🇯🇵"),
63                        Value::test_string("ほ"),
64                        Value::test_string("げ"),
65                    ],
66                    Span::test_data(),
67                )),
68            },
69            Example {
70                description: "Split multiple strings into lists of characters",
71                example: "['hello', 'world'] | split chars",
72                result: Some(Value::test_list(vec![
73                    Value::test_list(vec![
74                        Value::test_string("h"),
75                        Value::test_string("e"),
76                        Value::test_string("l"),
77                        Value::test_string("l"),
78                        Value::test_string("o"),
79                    ]),
80                    Value::test_list(vec![
81                        Value::test_string("w"),
82                        Value::test_string("o"),
83                        Value::test_string("r"),
84                        Value::test_string("l"),
85                        Value::test_string("d"),
86                    ]),
87                ])),
88            },
89        ]
90    }
91
92    fn is_const(&self) -> bool {
93        true
94    }
95
96    fn run(
97        &self,
98        engine_state: &EngineState,
99        stack: &mut Stack,
100        call: &Call,
101        input: PipelineData,
102    ) -> Result<PipelineData, ShellError> {
103        let graphemes = grapheme_flags(engine_state, stack, call)?;
104        split_chars(engine_state, call, input, graphemes)
105    }
106
107    fn run_const(
108        &self,
109        working_set: &StateWorkingSet,
110        call: &Call,
111        input: PipelineData,
112    ) -> Result<PipelineData, ShellError> {
113        let graphemes = grapheme_flags_const(working_set, call)?;
114        split_chars(working_set.permanent(), call, input, graphemes)
115    }
116}
117
118fn split_chars(
119    engine_state: &EngineState,
120    call: &Call,
121    input: PipelineData,
122    graphemes: bool,
123) -> Result<PipelineData, ShellError> {
124    let span = call.head;
125    input.map(
126        move |x| split_chars_helper(&x, span, graphemes),
127        engine_state.signals(),
128    )
129}
130
131fn split_chars_helper(v: &Value, name: Span, graphemes: bool) -> Value {
132    let span = v.span();
133    match v {
134        Value::Error { error, .. } => Value::error(*error.clone(), span),
135        v => {
136            let v_span = v.span();
137            if let Ok(s) = v.coerce_str() {
138                Value::list(
139                    if graphemes {
140                        s.graphemes(true)
141                            .collect::<Vec<_>>()
142                            .into_iter()
143                            .map(move |x| Value::string(x, v_span))
144                            .collect()
145                    } else {
146                        s.chars()
147                            .collect::<Vec<_>>()
148                            .into_iter()
149                            .map(move |x| Value::string(x, v_span))
150                            .collect()
151                    },
152                    v_span,
153                )
154            } else {
155                Value::error(
156                    ShellError::OnlySupportsThisInputType {
157                        exp_input_type: "string".into(),
158                        wrong_type: v.get_type().to_string(),
159                        dst_span: name,
160                        src_span: v_span,
161                    },
162                    name,
163                )
164            }
165        }
166    }
167}
168
169#[cfg(test)]
170mod test {
171    use super::*;
172
173    #[test]
174    fn test_examples() {
175        use crate::test_examples;
176
177        test_examples(SplitChars {})
178    }
179}