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:
parent
0453c807e0
commit
50f9fc7a53
@ -52,6 +52,9 @@ void BLI_path_append(char *__restrict dst, const size_t maxlen,
|
|||||||
const char *__restrict file) ATTR_NONNULL();
|
const char *__restrict file) ATTR_NONNULL();
|
||||||
void BLI_join_dirfile(char *__restrict string, const size_t maxlen,
|
void BLI_join_dirfile(char *__restrict string, const size_t maxlen,
|
||||||
const char *__restrict dir, const char *__restrict file) ATTR_NONNULL();
|
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;
|
const char *BLI_path_basename(const char *path) ATTR_NONNULL() ATTR_WARN_UNUSED_RESULT;
|
||||||
bool BLI_path_name_at_index(
|
bool BLI_path_name_at_index(
|
||||||
const char *__restrict path, const int index,
|
const char *__restrict path, const int index,
|
||||||
|
@ -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);
|
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()
|
* like pythons os.path.basename()
|
||||||
*
|
*
|
||||||
|
@ -5,6 +5,7 @@
|
|||||||
extern "C" {
|
extern "C" {
|
||||||
#include "BLI_fileops.h"
|
#include "BLI_fileops.h"
|
||||||
#include "BLI_path_util.h"
|
#include "BLI_path_util.h"
|
||||||
|
#include "BLI_string.h"
|
||||||
#include "../../../source/blender/imbuf/IMB_imbuf.h"
|
#include "../../../source/blender/imbuf/IMB_imbuf.h"
|
||||||
|
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
@ -245,6 +246,103 @@ TEST(path_util, NameAtIndex_NoneComplexNeg)
|
|||||||
|
|
||||||
#undef AT_INDEX
|
#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 */
|
/* BLI_path_frame */
|
||||||
TEST(path_util, PathUtilFrame)
|
TEST(path_util, PathUtilFrame)
|
||||||
|
Loading…
Reference in New Issue
Block a user