/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 | } |