nu_command/strings/str_/
join.rs1use 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 if let Some(value) = iter.next() {
102 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}