Skip to main content

nu_command/filters/
lines.rs

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