nu_command/filters/
lines.rs1use 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 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 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}