Coverage Report

Created: 2026-02-23 20:32

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/Users/alexjokela/projects/lattice/src/path_ops.c
Line
Count
Source
1
#include "path_ops.h"
2
#include <stdlib.h>
3
#include <string.h>
4
#include <stdbool.h>
5
6
15
char *path_join(const char **parts, size_t count) {
7
15
    if (count == 0) return strdup("");
8
9
    /* Calculate total length needed */
10
15
    size_t total = 0;
11
48
    for (size_t i = 0; i < count; i++) {
12
33
        total += strlen(parts[i]);
13
33
    }
14
    /* Room for separators between each part */
15
15
    total += count - 1;
16
17
15
    char *result = malloc(total + 1);
18
15
    if (!result) return strdup("");
19
20
15
    size_t pos = 0;
21
48
    for (size_t i = 0; i < count; i++) {
22
33
        const char *part = parts[i];
23
33
        size_t plen = strlen(part);
24
25
33
        if (i > 0) {
26
            /* Avoid double slashes: skip separator if previous ends with '/'
27
             * or current starts with '/' */
28
18
            bool prev_slash = (pos > 0 && result[pos - 1] == '/');
29
18
            bool curr_slash = (plen > 0 && part[0] == '/');
30
31
18
            if (prev_slash && curr_slash) {
32
                /* Skip the leading slash of the current part */
33
3
                part++;
34
3
                plen--;
35
15
            } else if (!prev_slash && !curr_slash) {
36
15
                result[pos++] = '/';
37
15
            }
38
            /* If exactly one has a slash, no separator needed */
39
18
        }
40
41
33
        memcpy(result + pos, part, plen);
42
33
        pos += plen;
43
33
    }
44
45
15
    result[pos] = '\0';
46
15
    return result;
47
15
}
48
49
12
char *path_dir(const char *path) {
50
12
    if (!path || path[0] == '\0') return strdup(".");
51
52
12
    size_t len = strlen(path);
53
54
    /* Find last '/' */
55
12
    const char *last_slash = NULL;
56
69
    for (size_t i = len; i > 0; i--) {
57
66
        if (path[i - 1] == '/') {
58
9
            last_slash = &path[i - 1];
59
9
            break;
60
9
        }
61
66
    }
62
63
12
    if (!last_slash) return strdup(".");
64
65
    /* Path is just "/" */
66
9
    if (last_slash == path) return strdup("/");
67
68
    /* Return everything before the last '/' */
69
6
    size_t dir_len = (size_t)(last_slash - path);
70
6
    char *result = malloc(dir_len + 1);
71
6
    memcpy(result, path, dir_len);
72
6
    result[dir_len] = '\0';
73
6
    return result;
74
9
}
75
76
9
char *path_base(const char *path) {
77
9
    if (!path || path[0] == '\0') return strdup("");
78
79
9
    size_t len = strlen(path);
80
81
    /* If path ends with '/', return "" */
82
9
    if (path[len - 1] == '/') return strdup("");
83
84
    /* Find last '/' */
85
6
    const char *last_slash = NULL;
86
51
    for (size_t i = len; i > 0; i--) {
87
48
        if (path[i - 1] == '/') {
88
3
            last_slash = &path[i - 1];
89
3
            break;
90
3
        }
91
48
    }
92
93
6
    if (!last_slash) return strdup(path);
94
95
3
    return strdup(last_slash + 1);
96
6
}
97
98
15
char *path_ext(const char *path) {
99
15
    if (!path || path[0] == '\0') return strdup("");
100
101
    /* Get the basename portion first */
102
15
    size_t len = strlen(path);
103
104
    /* Find last '/' to get basename start */
105
15
    const char *base_start = path;
106
129
    for (size_t i = len; i > 0; i--) {
107
117
        if (path[i - 1] == '/') {
108
3
            base_start = &path[i];
109
3
            break;
110
3
        }
111
117
    }
112
113
15
    size_t base_len = strlen(base_start);
114
15
    if (base_len == 0) return strdup("");
115
116
    /* Find last '.' in the basename */
117
15
    const char *last_dot = NULL;
118
75
    for (size_t i = base_len; i > 0; i--) {
119
72
        if (base_start[i - 1] == '.') {
120
12
            last_dot = &base_start[i - 1];
121
12
            break;
122
12
        }
123
72
    }
124
125
    /* No dot, or dot is the first char of basename (hidden file like .hidden) */
126
15
    if (!last_dot || last_dot == base_start) return strdup("");
127
128
9
    return strdup(last_dot);
129
15
}