nu_command/strings/str_/
join.rs

1use chrono::Datelike;
2use nu_engine::command_prelude::*;
3use nu_protocol::{Signals, shell_error::io::IoError};
4
5use std::io::Write;
6
7#[derive(Clone)]
8pub struct StrJoin;
9
10impl Command for StrJoin {
11    fn name(&self) -> &str {
12        "str join"
13    }
14
15    fn signature(&self) -> Signature {
16        Signature::build("str join")
17            .input_output_types(vec![
18                (Type::List(Box::new(Type::Any)), Type::String),
19                (Type::String, Type::String),
20            ])
21            .optional(
22                "separator",
23                SyntaxShape::String,
24                "Optional separator to use when creating string.",
25            )
26            .allow_variants_without_examples(true)
27            .category(Category::Strings)
28    }
29
30    fn description(&self) -> &str {
31        "Concatenate multiple strings into a single string, with an optional separator between each."
32    }
33
34    fn search_terms(&self) -> Vec<&str> {
35        vec!["collect", "concatenate"]
36    }
37
38    fn is_const(&self) -> bool {
39        true
40    }
41
42    fn run(
43        &self,
44        engine_state: &EngineState,
45        stack: &mut Stack,
46        call: &Call,
47        input: PipelineData,
48    ) -> Result<PipelineData, ShellError> {
49        let separator: Option<String> = call.opt(engine_state, stack, 0)?;
50        run(engine_state, call, input, separator)
51    }
52
53    fn run_const(
54        &self,
55        working_set: &StateWorkingSet,
56        call: &Call,
57        input: PipelineData,
58    ) -> Result<PipelineData, ShellError> {
59        let separator: Option<String> = call.opt_const(working_set, 0)?;
60        run(working_set.permanent(), call, input, separator)
61    }
62
63    fn examples(&self) -> Vec<Example<'_>> {
64        vec![
65            Example {
66                description: "Create a string from input",
67                example: "['nu', 'shell'] | str join",
68                result: Some(Value::test_string("nushell")),
69            },
70            Example {
71                description: "Create a string from input with a separator",
72                example: "['nu', 'shell'] | str join '-'",
73                result: Some(Value::test_string("nu-shell")),
74            },
75        ]
76    }
77}
78
79fn run(
80    engine_state: &EngineState,
81    call: &Call,
82    input: PipelineData,
83    separator: Option<String>,
84) -> Result<PipelineData, ShellError> {
85    let config = engine_state.config.clone();
86
87    let span = call.head;
88
89    let metadata = input.metadata();
90    let mut iter = input.into_iter();
91    let mut first = true;
92
93    let output = ByteStream::from_fn(
94        span,
95        Signals::empty(),
96        ByteStreamType::String,
97        move |buffer| {
98            let from_io_error = IoError::factory(span, None);
99
100            // Write each input to the buffer
101            if let Some(value) = iter.next() {
102                // Write the separator if this is not the first
103                if first {
104                    first = false;
105                } else if let Some(separator) = &separator {
106                    write!(buffer, "{separator}").map_err(&from_io_error)?;
107                }
108
109                match value {
110                    Value::Error { error, .. } => {
111                        return Err(*error);
112                    }
113                    Value::Date { val, .. } => {
114                        let date_str = if val.year() >= 0 {
115                            val.to_rfc2822()
116                        } else {
117                            val.to_rfc3339()
118                        };
119                        write!(buffer, "{date_str}").map_err(&from_io_error)?
120                    }
121                    value => write!(buffer, "{}", value.to_expanded_string("\n", &config))
122                        .map_err(&from_io_error)?,
123                }
124                Ok(true)
125            } else {
126                Ok(false)
127            }
128        },
129    );
130
131    Ok(PipelineData::byte_stream(output, metadata))
132}
133
134#[cfg(test)]
135mod tests {
136    use super::*;
137    #[test]
138    fn test_examples() {
139        use crate::test_examples;
140
141        test_examples(StrJoin {})
142    }
143}