[Bf-blender-cvs] [50f9fc7a53] master: BLI_path_util: Add BLI_path_join

Campbell Barton noreply at git.blender.org
Fri Mar 24 07:40:08 CET 2017


Commit: 50f9fc7a53b53a53b9fc90bcb20396426631d20f
Author: Campbell Barton
Date:   Fri Mar 24 17:29:48 2017 +1100
Branches: master
https://developer.blender.org/rB50f9fc7a53b53a53b9fc90bcb20396426631d20f

BLI_path_util: Add BLI_path_join

There weren't any convenient ways to join multiple paths in C
that accounted for corner cases.

===================================================================

M	source/blender/blenlib/BLI_path_util.h
M	source/blender/blenlib/intern/path_util.c
M	tests/gtests/blenlib/BLI_path_util_test.cc

===================================================================

diff --git a/source/blender/blenlib/BLI_path_util.h b/source/blender/blenlib/BLI_path_util.h
index a4f5c3c7a0..b6a55d34d1 100644
--- a/source/blender/blenlib/BLI_path_util.h
+++ b/source/blender/blenlib/BLI_path_util.h
@@ -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,
diff --git a/source/blender/blenlib/intern/path_util.c b/source/blender/blenlib/intern/path_util.c
index 955f603cda..7b765cfa93 100644
--- a/source/blender/blenlib/intern/path_util.c
+++ b/source/blender/blenlib/intern/path_util.c
@@ -1591,6 +1591,90 @@ void BLI_join_dirfile(char *__restrict dst, const size_t maxlen, const char *__r
 }
 
 /**
+ * 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()
  *
  * \return The pointer into \a path string immediately after last slash,
diff --git a/tests/gtests/blenlib/BLI_path_util_test.cc b/tests/gtests/blenlib/BLI_path_util_test.cc
index ccfa59503c..4c45bfd675 100644
--- a/tests/gtests/blenlib/BLI_path_util_test.cc
+++ b/tests/gtests/blenlib/BLI_path_util_test.cc
@@ -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)




More information about the Bf-blender-cvs mailing list