diff --git a/include/rcutils/filesystem.h b/include/rcutils/filesystem.h index 8275a207..ec3e2c52 100644 --- a/include/rcutils/filesystem.h +++ b/include/rcutils/filesystem.h @@ -144,6 +144,24 @@ rcutils_to_native_path( const char * path, rcutils_allocator_t allocator); +/// Expand user directory in path. +/** + * This function expands an initial '~' to the current user's home directory. + * The home directory is fetched using `rcutils_get_home_dir()`. + * This function returns a newly allocated string on success. + * It is up to the caller to release the memory once it is done with it by + * calling `deallocate` on the same allocator passed here. + * + * \param[in] path A null-terminated C string representing a path. + * \param[in] allocator + * \return path with expanded home directory on success, or + * \return `NULL` on invalid arguments, or + * \return `NULL` on failure. + */ +RCUTILS_PUBLIC +char * +rcutils_expand_user(const char * path, rcutils_allocator_t allocator); + /// Create the specified directory. /** * This function creates an absolutely-specified directory. diff --git a/src/filesystem.c b/src/filesystem.c index a05ebad9..8a709571 100644 --- a/src/filesystem.c +++ b/src/filesystem.c @@ -33,7 +33,9 @@ extern "C" #include "rcutils/error_handling.h" #include "rcutils/format_string.h" +#include "rcutils/get_env.h" #include "rcutils/repl_str.h" +#include "rcutils/strdup.h" #ifdef _WIN32 # define RCUTILS_PATH_DELIMITER "\\" @@ -181,6 +183,29 @@ rcutils_to_native_path( return rcutils_repl_str(path, "/", RCUTILS_PATH_DELIMITER, &allocator); } +char * +rcutils_expand_user(const char * path, rcutils_allocator_t allocator) +{ + if (NULL == path) { + return NULL; + } + + if ('~' != path[0]) { + return rcutils_strdup(path, allocator); + } + + const char * homedir = rcutils_get_home_dir(); + if (NULL == homedir) { + return NULL; + } + return rcutils_format_string_limit( + allocator, + strlen(homedir) + strlen(path), + "%s%s", + homedir, + path + 1); +} + bool rcutils_mkdir(const char * abs_path) { diff --git a/test/test_filesystem.cpp b/test/test_filesystem.cpp index 8e9f1c16..c53451ce 100644 --- a/test/test_filesystem.cpp +++ b/test/test_filesystem.cpp @@ -16,6 +16,7 @@ #include #include "rcutils/filesystem.h" +#include "rcutils/get_env.h" #include "osrf_testing_tools_cpp/scope_exit.hpp" @@ -307,6 +308,62 @@ TEST_F(TestFilesystemFixture, is_readable_and_writable) { } } +TEST_F(TestFilesystemFixture, expand_user) { + const char * homedir = rcutils_get_home_dir(); + ASSERT_STRNE(NULL, homedir); + const std::string homedir_str(homedir); + + { + // Invalid path arg + EXPECT_EQ(NULL, rcutils_expand_user(NULL, g_allocator)); + } + { + // No ~ + const char * path = "/this/path/has/no/tilde"; + char * ret_path = rcutils_expand_user(path, g_allocator); + EXPECT_STREQ(ret_path, path); + ASSERT_NE(ret_path, path); + g_allocator.deallocate(ret_path, g_allocator.state); + } + { + // No ~ and empty path + const char * path = ""; + char * ret_path = rcutils_expand_user(path, g_allocator); + EXPECT_STREQ(ret_path, path); + ASSERT_NE(ret_path, path); + g_allocator.deallocate(ret_path, g_allocator.state); + } + { + // With ~ but not leading + const char * path = "/prefix/~/my/dir"; + char * ret_path = rcutils_expand_user(path, g_allocator); + EXPECT_STREQ(ret_path, path); + ASSERT_NE(ret_path, path); + g_allocator.deallocate(ret_path, g_allocator.state); + } + { + // With ~ only + const char * path = "~"; + char * ret_path = rcutils_expand_user(path, g_allocator); + EXPECT_STREQ(ret_path, homedir); + g_allocator.deallocate(ret_path, g_allocator.state); + } + { + // With ~/ only + const char * path = "~/"; + char * ret_path = rcutils_expand_user(path, g_allocator); + EXPECT_STREQ(ret_path, (homedir_str + "/").c_str()); + g_allocator.deallocate(ret_path, g_allocator.state); + } + { + // Normal use-case + const char * path = "~/my/directory"; + char * ret_path = rcutils_expand_user(path, g_allocator); + EXPECT_STREQ(ret_path, (homedir_str + "/my/directory").c_str()); + g_allocator.deallocate(ret_path, g_allocator.state); + } +} + TEST_F(TestFilesystemFixture, mkdir) { { // Make a new directory