commit d3db909b464737863e9b54ae7c28393f0871aed4
parent c4edd894077a09547707f9ff76e4e188b361354b
Author: Chris Bracken <chris@bracken.jp>
Date: Fri, 20 Feb 2026 18:08:46 +0900
utils: improve relpath_from_dir
Diffstat:
| M | src/utils.c | | | 33 | +++++++++++++++++++++++++-------- |
| M | src/utils_tests.c | | | 78 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------------------- |
2 files changed, 84 insertions(+), 27 deletions(-)
diff --git a/src/utils.c b/src/utils.c
@@ -120,22 +120,39 @@ bool is_safe_mailto(const char* email) {
}
char* relpath_from_dir(const char* dir) {
- size_t num_components = 1;
- if (strcmp(dir, ".") != 0) {
- for (const char* p = dir; *p != '\0'; p++) {
- if (*p == '/') {
- num_components++;
- }
+ if (!dir || dir[0] == '\0' || strcmp(dir, ".") == 0) {
+ return estrdup("");
+ }
+
+ size_t depth = 0;
+ const char* p = dir;
+ /* Skip leading slashes. */
+ while (*p == '/') {
+ p++;
+ }
+
+ /* For each component, increment depth. Skip repeated slashes. */
+ while (*p != '\0') {
+ depth++;
+ while (*p != '\0' && *p != '/') {
+ p++;
+ }
+ while (*p == '/') {
+ p++;
}
}
+ if (depth == 0) {
+ return estrdup("");
+ }
+ /* Output the appropriate number of ../ for the depth. */
const char segment[] = "../";
const size_t segment_len = sizeof(segment) - 1;
- size_t relpath_size = (num_components * segment_len) + 1;
+ size_t relpath_size = (depth * segment_len) + 1;
char* relpath = ecalloc(relpath_size, 1);
char* ptr = relpath;
- for (size_t i = 0; i < num_components; i++) {
+ for (size_t i = 0; i < depth; i++) {
memcpy(ptr, segment, segment_len);
ptr += segment_len;
}
diff --git a/src/utils_tests.c b/src/utils_tests.c
@@ -70,32 +70,72 @@ UTEST(is_safe_mailto, RejectsInvalidCharacters) {
EXPECT_FALSE(is_safe_mailto("test@example.com\n"));
}
-UTEST(relpath_from_dir, SingleDirectory) {
- char* result = relpath_from_dir("folder");
- ASSERT_STREQ("../", result);
- free(result);
+UTEST(relpath_from_dir, EmptyOrNull) {
+ char* r1 = relpath_from_dir("");
+ EXPECT_STREQ("", r1);
+ free(r1);
+
+ char* r2 = relpath_from_dir(NULL);
+ EXPECT_STREQ("", r2);
+ free(r2);
+
+ char* r3 = relpath_from_dir(".");
+ EXPECT_STREQ("", r3);
+ free(r3);
}
-UTEST(relpath_from_dir, MultipleDirectories) {
- char* result = relpath_from_dir("path/to/somewhere");
- ASSERT_STREQ("../../../", result);
- free(result);
+UTEST(relpath_from_dir, SingleDirectory) {
+ char* r1 = relpath_from_dir("foo");
+ EXPECT_STREQ("../", r1);
+ free(r1);
+
+ char* r2 = relpath_from_dir("foo/");
+ EXPECT_STREQ("../", r2);
+ free(r2);
}
-UTEST(relpath_from_dir, CurrentDirectoryLiteral) {
- char* result = relpath_from_dir(".");
- ASSERT_STREQ("../", result);
- free(result);
+UTEST(relpath_from_dir, LeadingSlash) {
+ char* r2 = relpath_from_dir("/foo");
+ EXPECT_STREQ("../", r2);
+ free(r2);
}
UTEST(relpath_from_dir, TrailingSlash) {
- char* result = relpath_from_dir("folder/");
- ASSERT_STREQ("../../", result);
- free(result);
+ char* r2 = relpath_from_dir("foo/");
+ EXPECT_STREQ("../", r2);
+ free(r2);
}
-UTEST(relpath_from_dir, RootPath) {
- char* result = relpath_from_dir("/");
- ASSERT_STREQ("../../", result);
- free(result);
+UTEST(relpath_from_dir, NestedDirectories) {
+ char* r1 = relpath_from_dir("foo/bar");
+ EXPECT_STREQ("../../", r1);
+ free(r1);
+
+ char* r2 = relpath_from_dir("foo/bar/baz");
+ EXPECT_STREQ("../../../", r2);
+ free(r2);
+
+ char* r3 = relpath_from_dir("a/b/c/d/e");
+ EXPECT_STREQ("../../../../../", r3);
+ free(r3);
+}
+
+UTEST(relpath_from_dir, MultipleSlashes) {
+ char* r1 = relpath_from_dir("foo//bar");
+ EXPECT_STREQ("../../", r1);
+ free(r1);
+
+ char* r2 = relpath_from_dir("//foo///bar//");
+ EXPECT_STREQ("../../", r2);
+ free(r2);
+}
+
+UTEST(relpath_from_dir, AbsolutePath) {
+ char* r1 = relpath_from_dir("/foo");
+ EXPECT_STREQ("../", r1);
+ free(r1);
+
+ char* r2 = relpath_from_dir("/");
+ EXPECT_STREQ("", r2);
+ free(r2);
}