BLI_path_util: Add BLI_path_join

There weren't any convenient ways to join multiple paths in C
that accounted for corner cases.
This commit is contained in:
Campbell Barton 2017-03-24 17:29:48 +11:00
parent 0453c807e0
commit 50f9fc7a53
3 changed files with 185 additions and 0 deletions

View File

@ -52,6 +52,9 @@ void BLI_path_append(char *__restrict dst, const size_t maxlen,
const char *__restrict file) ATTR_NONNULL();
void BLI_join_dirfile(char *__restrict string, const size_t maxlen,
const char *__restrict dir, const char *__restrict file) ATTR_NONNULL();
size_t BLI_path_join(
char *__restrict dst, const size_t dst_len,
const char *path_first, ...) ATTR_NONNULL(1, 3) ATTR_SENTINEL(0);
const char *BLI_path_basename(const char *path) ATTR_NONNULL() ATTR_WARN_UNUSED_RESULT;
bool BLI_path_name_at_index(
const char *__restrict path, const int index,

View File

@ -1590,6 +1590,90 @@ void BLI_join_dirfile(char *__restrict dst, const size_t maxlen, const char *__r
BLI_strncpy(dst + dirlen, file, maxlen - dirlen);
}
/**
* Join multiple strings into a path, ensuring only a single path separator between each,
* and trailing slash is kept.
*
* \note If you want a trailing slash, add ``SEP_STR`` as the last path argument,
* duplicate slashes will be cleaned up.
*/
size_t BLI_path_join(char *__restrict dst, const size_t dst_len, const char *path, ...)
{
if (UNLIKELY(dst_len == 0)) {
return 0;
}
const size_t dst_last = dst_len - 1;
size_t ofs = BLI_strncpy_rlen(dst, path, dst_len);
if (ofs == dst_last) {
return ofs;
}
/* remove trailing slashes, unless there are _only_ trailing slashes
* (allow "//" as the first argument). */
bool has_trailing_slash = false;
if (ofs != 0) {
size_t len = ofs;
while ((len != 0) && ELEM(path[len - 1], SEP, ALTSEP)) {
len -= 1;
}
if (len != 0) {
ofs = len;
}
has_trailing_slash = (path[len] != '\0');
}
va_list args;
va_start(args, path);
while ((path = (const char *) va_arg(args, const char *))) {
has_trailing_slash = false;
const char *path_init = path;
while (ELEM(path[0], SEP, ALTSEP)) {
path++;
}
size_t len = strlen(path);
if (len != 0) {
while ((len != 0) && ELEM(path[len - 1], SEP, ALTSEP)) {
len -= 1;
}
if (len != 0) {
/* the very first path may have a slash at the end */
if (ofs && !ELEM(dst[ofs - 1], SEP, ALTSEP)) {
dst[ofs++] = SEP;
if (ofs == dst_last) {
break;
}
}
has_trailing_slash = (path[len] != '\0');
if (ofs + len >= dst_last) {
len = dst_last - ofs;
}
memcpy(&dst[ofs], path, len);
ofs += len;
if (ofs == dst_last) {
break;
}
}
}
else {
has_trailing_slash = (path_init != path);
}
}
va_end(args);
if (has_trailing_slash) {
if ((ofs != dst_last) && (ofs != 0) && (ELEM(dst[ofs - 1], SEP, ALTSEP) == 0)) {
dst[ofs++] = SEP;
}
}
BLI_assert(ofs <= dst_last);
dst[ofs] = '\0';
return ofs;
}
/**
* like pythons os.path.basename()
*

View File

@ -5,6 +5,7 @@
extern "C" {
#include "BLI_fileops.h"
#include "BLI_path_util.h"
#include "BLI_string.h"
#include "../../../source/blender/imbuf/IMB_imbuf.h"
#ifdef _WIN32
@ -245,6 +246,103 @@ TEST(path_util, NameAtIndex_NoneComplexNeg)
#undef AT_INDEX
#define JOIN(str_expect, out_size, ...) \
{ \
const char *expect = str_expect; \
char result[(out_size) + 1024]; \
/* check we don't write past the last byte */ \
result[out_size] = '\0'; \
BLI_path_join(result, out_size, __VA_ARGS__, NULL); \
/* simplify expected string */ \
BLI_str_replace_char(result, '\\', '/'); \
EXPECT_STREQ(result, expect); \
EXPECT_EQ(result[out_size], '\0'); \
} ((void)0)
/* BLI_path_join */
TEST(path_util, JoinNop)
{
JOIN("", 100, "");
JOIN("", 100, "", "");
JOIN("", 100, "", "", "");
JOIN("/", 100, "/", "", "");
JOIN("/", 100, "/", "/");
JOIN("/", 100, "/", "", "/");
JOIN("/", 100, "/", "", "/", "");
}
TEST(path_util, JoinSingle)
{
JOIN("test", 100, "test");
JOIN("", 100, "");
JOIN("a", 100, "a");
JOIN("/a", 100, "/a");
JOIN("a/", 100, "a/");
JOIN("/a/", 100, "/a/");
JOIN("/a/", 100, "/a//");
JOIN("//a/", 100, "//a//");
}
TEST(path_util, JoinTriple)
{
JOIN("/a/b/c", 100, "/a", "b", "c");
JOIN("/a/b/c", 100, "/a/", "/b/", "/c");
JOIN("/a/b/c", 100, "/a/b/", "/c");
JOIN("/a/b/c", 100, "/a/b/c");
JOIN("/a/b/c", 100, "/", "a/b/c");
JOIN("/a/b/c/", 100, "/a/", "/b/", "/c/");
JOIN("/a/b/c/", 100, "/a/b/c/");
JOIN("/a/b/c/", 100, "/a/b/", "/c/");
JOIN("/a/b/c/", 100, "/a/b/c", "/");
JOIN("/a/b/c/", 100, "/", "a/b/c", "/");
}
TEST(path_util, JoinTruncateShort)
{
JOIN("", 1, "/");
JOIN("/", 2, "/");
JOIN("a", 2, "", "aa");
JOIN("a", 2, "", "a/");
JOIN("a/b", 4, "a", "bc");
JOIN("ab/", 4, "ab", "c");
JOIN("/a/", 4, "/a", "b");
JOIN("/a/", 4, "/a/", "b/");
JOIN("/a/", 4, "/a", "/b/");
JOIN("/a/", 4, "/", "a/b/");
JOIN("//a", 4, "//", "a/b/");
JOIN("/a/b", 5, "/a", "b", "c");
}
TEST(path_util, JoinTruncateLong)
{
JOIN("", 1, "//", "//longer", "path");
JOIN("/", 2, "//", "//longer", "path");
JOIN("//", 3, "//", "//longer", "path");
JOIN("//l", 4, "//", "//longer", "path");
/* snip */
JOIN("//longe", 8, "//", "//longer", "path");
JOIN("//longer", 9, "//", "//longer", "path");
JOIN("//longer/", 10, "//", "//longer", "path");
JOIN("//longer/p", 11, "//", "//longer", "path");
JOIN("//longer/pa", 12, "//", "//longer", "path");
JOIN("//longer/pat", 13, "//", "//longer", "path");
JOIN("//longer/path", 14, "//", "//longer", "path"); // not truncated
JOIN("//longer/path", 14, "//", "//longer", "path/");
JOIN("//longer/path/", 15, "//", "//longer", "path/"); // not truncated
JOIN("//longer/path/", 15, "//", "//longer", "path/", "trunc");
JOIN("//longer/path/t", 16, "//", "//longer", "path/", "trunc");
}
TEST(path_util, JoinComplex)
{
JOIN("/a/b/c/d/e/f/g/", 100, "/", "\\a/b", "//////c/d", "", "e\\\\", "f", "g//");
JOIN("/aa/bb/cc/dd/ee/ff/gg/", 100, "/", "\\aa/bb", "//////cc/dd", "", "ee\\\\", "ff", "gg//");
JOIN("1/2/3/", 100, "1", "////////", "", "2", "3\\");
}
#undef JOIN
/* BLI_path_frame */
TEST(path_util, PathUtilFrame)