Skip to content

Commit 4841a34

Browse files
authored
Add rcutils_join function for concatenating strings (ros2#490)
This function complements the rcutils_split function. It joins an array of string tokens together with the given separator inserted between each token. Signed-off-by: Scott K Logan <logans@cottsay.net>
1 parent 2af780a commit 4841a34

4 files changed

Lines changed: 228 additions & 0 deletions

File tree

CMakeLists.txt

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@ set(rcutils_sources
7575
src/find.c
7676
src/format_string.c
7777
src/hash_map.c
78+
src/join.c
7879
src/logging.c
7980
src/process.c
8081
src/qsort.c
@@ -302,6 +303,13 @@ if(BUILD_TESTING)
302303
target_link_libraries(test_split ${PROJECT_NAME})
303304
endif()
304305

306+
ament_add_gtest(test_join
307+
test/test_join.cpp
308+
)
309+
if(TARGET test_join)
310+
target_link_libraries(test_join ${PROJECT_NAME})
311+
endif()
312+
305313
ament_add_gtest(test_find
306314
test/test_find.cpp
307315
)

include/rcutils/join.h

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
// Copyright 2025 Open Source Robotics Foundation, Inc.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
/// \file
16+
17+
#ifndef RCUTILS__JOIN_H_
18+
#define RCUTILS__JOIN_H_
19+
20+
#ifdef __cplusplus
21+
extern "C"
22+
{
23+
#endif
24+
25+
#include "rcutils/allocator.h"
26+
#include "rcutils/types.h"
27+
#include "rcutils/visibility_control.h"
28+
29+
/// Concatenate members of an array into a single string
30+
/**
31+
* \param[in] string_array with the tokens to concatenate
32+
* \param[in] separator string to be inserted between tokens
33+
* \param[in] allocator for allocating new memory for the output string
34+
* \return concatenated string, or
35+
* \return `NULL` if there is an error.
36+
*/
37+
RCUTILS_PUBLIC
38+
char *
39+
rcutils_join(
40+
const rcutils_string_array_t * string_array,
41+
const char * separator,
42+
rcutils_allocator_t allocator);
43+
44+
#ifdef __cplusplus
45+
}
46+
#endif
47+
48+
#endif // RCUTILS__JOIN_H_

src/join.c

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
// Copyright 2025 Open Source Robotics Foundation, Inc.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
#ifdef __cplusplus
16+
extern "C"
17+
{
18+
#endif
19+
20+
#include <string.h>
21+
22+
#include "rcutils/error_handling.h"
23+
#include "rcutils/join.h"
24+
#include "rcutils/macros.h"
25+
#include "rcutils/strdup.h"
26+
#include "rcutils/types.h"
27+
28+
char *
29+
rcutils_join(
30+
const rcutils_string_array_t * string_array,
31+
const char * separator,
32+
rcutils_allocator_t allocator)
33+
{
34+
RCUTILS_CAN_RETURN_WITH_ERROR_OF(NULL);
35+
36+
RCUTILS_CHECK_ARGUMENT_FOR_NULL(string_array, NULL);
37+
RCUTILS_CHECK_ARGUMENT_FOR_NULL(separator, NULL);
38+
RCUTILS_CHECK_ALLOCATOR_WITH_MSG(
39+
&allocator, "allocator is invalid", return NULL);
40+
41+
if (string_array->size < 1) {
42+
return rcutils_strdup("", allocator);
43+
}
44+
45+
size_t sep_length = strlen(separator);
46+
size_t string_length = sep_length * (string_array->size - 1);
47+
48+
for (size_t i = 0; i < string_array->size; i++) {
49+
if (string_array->data[i]) {
50+
string_length += strlen(string_array->data[i]);
51+
}
52+
}
53+
54+
char * new_string = allocator.allocate(string_length + 1, allocator.state);
55+
if (NULL == new_string) {
56+
RCUTILS_SET_ERROR_MSG("failed to allocate memory for new string");
57+
return NULL;
58+
}
59+
60+
char * pos = new_string;
61+
for (size_t i = 0; i < string_array->size; i++) {
62+
if (i != 0) {
63+
memcpy(pos, separator, sep_length);
64+
pos += sep_length;
65+
}
66+
if (string_array->data[i]) {
67+
string_length = strlen(string_array->data[i]);
68+
memcpy(pos, string_array->data[i], string_length);
69+
pos += string_length;
70+
}
71+
}
72+
73+
*pos = '\0';
74+
75+
return new_string;
76+
}
77+
78+
#ifdef __cplusplus
79+
}
80+
#endif

test/test_join.cpp

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
// Copyright 2025 Open Source Robotics Foundation, Inc.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
#include "gtest/gtest.h"
16+
17+
#include "./allocator_testing_utils.h"
18+
#include "./time_bomb_allocator_testing_utils.h"
19+
#include "rcutils/error_handling.h"
20+
#include "rcutils/join.h"
21+
#include "rcutils/types/string_array.h"
22+
23+
#define LOG(expected, actual) printf("Expected: %s Actual: %s\n", expected, actual);
24+
25+
TEST(test_join, join) {
26+
rcutils_allocator_t allocator = rcutils_get_default_allocator();
27+
rcutils_allocator_t bad_allocator = rcutils_get_zero_initialized_allocator();
28+
rcutils_allocator_t time_bomb_allocator = get_time_bomb_allocator();
29+
rcutils_string_array_t tokens0 = rcutils_get_zero_initialized_string_array();
30+
rcutils_string_array_t tokens1 = rcutils_get_zero_initialized_string_array();
31+
rcutils_string_array_t tokens2 = rcutils_get_zero_initialized_string_array();
32+
char * new_string;
33+
34+
ASSERT_EQ(
35+
RCUTILS_RET_OK,
36+
rcutils_string_array_init(&tokens0, 0, &allocator));
37+
ASSERT_EQ(
38+
RCUTILS_RET_OK,
39+
rcutils_string_array_init(&tokens1, 1, &allocator));
40+
tokens1.data[0] = strdup("hallo");
41+
ASSERT_EQ(
42+
RCUTILS_RET_OK,
43+
rcutils_string_array_init(&tokens2, 2, &allocator));
44+
tokens2.data[0] = strdup("hello");
45+
tokens2.data[1] = strdup("world");
46+
47+
EXPECT_STREQ(
48+
nullptr,
49+
rcutils_join(NULL, "-", allocator));
50+
rcutils_reset_error();
51+
52+
EXPECT_STREQ(
53+
nullptr,
54+
rcutils_join(&tokens0, NULL, allocator));
55+
rcutils_reset_error();
56+
57+
EXPECT_STREQ(
58+
nullptr,
59+
rcutils_join(&tokens0, " ", bad_allocator));
60+
rcutils_reset_error();
61+
62+
// Allocating new_string fails
63+
set_time_bomb_allocator_malloc_count(time_bomb_allocator, 0);
64+
EXPECT_STREQ(
65+
nullptr,
66+
rcutils_join(&tokens2, " ", time_bomb_allocator));
67+
rcutils_reset_error();
68+
69+
new_string = rcutils_join(&tokens0, " ", allocator);
70+
EXPECT_STREQ("", new_string);
71+
allocator.deallocate(new_string, &allocator.state);
72+
73+
new_string = rcutils_join(&tokens1, " ", allocator);
74+
EXPECT_STREQ("hallo", new_string);
75+
allocator.deallocate(new_string, &allocator.state);
76+
77+
new_string = rcutils_join(&tokens2, "", allocator);
78+
EXPECT_STREQ("helloworld", new_string);
79+
allocator.deallocate(new_string, &allocator.state);
80+
81+
new_string = rcutils_join(&tokens2, " ", allocator);
82+
EXPECT_STREQ("hello world", new_string);
83+
allocator.deallocate(new_string, &allocator.state);
84+
85+
new_string = rcutils_join(&tokens2, " ... ", allocator);
86+
EXPECT_STREQ("hello ... world", new_string);
87+
allocator.deallocate(new_string, &allocator.state);
88+
89+
EXPECT_EQ(
90+
RCUTILS_RET_OK,
91+
rcutils_string_array_fini(&tokens0));
92+
}

0 commit comments

Comments
 (0)