Skip to main content

nu_command/filters/
lines.rs

1use nu_engine::command_prelude::*;
2
3#[derive(Clone)]
4pub struct Lines;
5
6impl Command for Lines {
7    fn name(&self) -> &str {
8        "lines"
9    }
10
11    fn description(&self) -> &str {
12        "Converts input to lines."
13    }
14
15    fn signature(&self) -> nu_protocol::Signature {
16        Signature::build("lines")
17            .input_output_types(vec![(Type::Any, Type::List(Box::new(Type::String)))])
18            .switch("skip-empty", "Skip empty lines.", Some('s'))
19            .switch("strict", "Validate UTF-8 strictly.", None)
20            .category(Category::Filters)
21    }
22    fn run(
23        &self,
24        engine_state: &EngineState,
25        stack: &mut Stack,
26        call: &Call,
27        input: PipelineData,
28    ) -> Result<PipelineData, ShellError> {
29        let head = call.head;
30        let skip_empty = call.has_flag(engine_state, stack, "skip-empty")?;
31        let strict = call.has_flag(engine_state, stack, "strict")?;
32
33        let span = input.span().unwrap_or(call.head);
34        match input {
35            PipelineData::Value(value, ..) => match value {
36                Value::String { val, .. } => {
37                    let lines = if skip_empty {
38                        val.lines()
39                            .filter_map(|s| {
40                                if s.trim().is_empty() {
41                                    None
42                                } else {
43                                    Some(Value::string(s, span))
44                                }
45                            })
46                            .collect()
47                    } else {
48                        val.lines().map(|s| Value::string(s, span)).collect()
49                    };
50
51                    Ok(Value::list(lines, span).into_pipeline_data())
52                }
53                // Propagate existing errors
54                Value::Error { error, .. } => Err(*error),
55                value => Err(ShellError::OnlySupportsThisInputType {
56                    exp_input_type: "string or byte stream".into(),
57                    wrong_type: value.get_type().to_string(),
58                    dst_span: head,
59                    src_span: value.span(),
60                }),
61            },
62            PipelineData::Empty => Ok(PipelineData::empty()),
63            PipelineData::ListStream(stream, metadata) => {
64                let stream = stream.modify(|iter| {
65                    iter.filter_map(move |value| {
66                        let span = value.span();
67                        if let Value::String { val, .. } = value {
68                            Some(
69                                val.lines()
70                                    .filter_map(|s| {
71                                        if skip_empty && s.trim().is_empty() {
72                                            None
73                                        } else {
74                                            Some(Value::string(s, span))
75                                        }
76                                    })
77                                    .collect::<Vec<_>>(),
78                            )
79                        } else {
80                            None
81                        }
82                    })
83                    .flatten()
84                });
85
86                Ok(PipelineData::list_stream(stream, metadata))
87            }
88            PipelineData::ByteStream(stream, ..) => {
89                if let Some(lines) = stream.lines().map(|l| l.strict(strict)) {
90                    Ok(lines
91                        .map(move |line| match line {
92                            Ok(line) => Value::string(line, head),
93                            Err(err) => Value::error(err, head),
94                        })
95                        .into_pipeline_data(head, engine_state.signals().clone()))
96                } else {
97                    Ok(PipelineData::empty())
98                }
99            }
100        }
101    }
102
103    fn examples(&self) -> Vec<Example<'_>> {
104        vec![Example {
105            description: "Split multi-line string into lines",
106            example: r#"$"two\nlines" | lines"#,
107            result: Some(Value::list(
108                vec![Value::test_string("two"), Value::test_string("lines")],
109                Span::test_data(),
110            )),
111        }]
112    }
113}
114
115#[cfg(test)]
116mod test {
117    use super::*;
118
119    #[test]
120    fn test_examples() -> nu_test_support::Result {
121        nu_test_support::test().examples(Lines)
122    }
123}