1 /**
2 * \file path_helpers.c
3 * \brief Helper functions for path_getrelative.c and path_getabsolute.c
4 */
5
6 #include "premake.h"
7 #include <string.h>
8
9 #define USE_DRIVE_LETTERS PLATFORM_WINDOWS
10
has_drive_letter(const char * path)11 static int has_drive_letter(const char * path)
12 {
13 #if USE_DRIVE_LETTERS
14 char ch = path[0];
15 if (((ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z')) && path[1] == ':')
16 return 1;
17 #else
18 (void)path;
19 #endif // USE_DRIVE_LETTERS
20
21 return 0;
22 }
23
is_absolute_path(const char * path)24 int is_absolute_path(const char * path)
25 {
26 char ch = path[0];
27 if (ch == '\0')
28 {
29 return 0;
30 }
31
32 if (ch == '/' || ch == '\\' || ch == '$')
33 {
34 return 1;
35 }
36
37 if (has_drive_letter(path))
38 {
39 return 1;
40 }
41
42 return 0;
43 }
44
copy_current_directory(char * buffer,int bufsize)45 static char *copy_current_directory(char * buffer, int bufsize)
46 {
47 #if PLATFORM_WINDOWS
48 int len = GetCurrentDirectory(bufsize, buffer);
49 int i;
50 for (i = 0; i < len; i++)
51 buffer[i] = (buffer[i] == '\\' ? '/' : buffer[i]);
52 return buffer + len;
53 #else
54 char *ptr = getcwd(buffer, bufsize);
55 int len = ptr ? strlen(ptr) : 0;
56 return buffer + len;
57 #endif
58 }
59
60 #if USE_DRIVE_LETTERS
copy_current_drive_letter(char * buffer,int bufsize)61 static char *copy_current_drive_letter(char * buffer, int bufsize)
62 {
63 int len = GetCurrentDirectory(bufsize, buffer);
64 if (len >= 2 && buffer[1] == ':') {
65 buffer[2] = '\0';
66 return buffer + 2;
67 }
68 else {
69 buffer[0] = '\0';
70 return buffer;
71 }
72 }
73 #endif // USE_DRIVE_LETTERS
74
backtrack(char * path,char * start)75 static char *backtrack(char *path, char *start)
76 {
77 for (; path > start; --path) {
78 if (*path == '/') {
79 *path = '\0';
80 return path;
81 }
82 #if USE_DRIVE_LETTERS
83 else if (*path == ':' && (path == start + 1)) {
84 path[1] = '/';
85 path[2] = '\0';
86 return path + 2;
87 }
88 #endif
89 }
90
91 start[0] = '/';
92 start[1] = '\0';
93 return start + 1;
94 }
95
concat(char * path,const char * pathTerm,const char * token,const char * tokenTerm)96 static char *concat(char *path, const char *pathTerm, const char *token, const char *tokenTerm)
97 {
98 while (path < pathTerm && token < tokenTerm)
99 *path++ = *token++;
100 return path;
101 }
102
copy(const char str[],char * buffer,int bufsize)103 static char *copy(const char str[], char *buffer, int bufsize)
104 {
105 int i = 0;
106 for (; str[i] && i < bufsize - 1; i++)
107 buffer[i] = str[i];
108 buffer[i] = '\0';
109 return buffer + i;
110 }
111
get_absolute_path(const char path[],char * buffer,int bufsize)112 char *get_absolute_path(const char path[], char *buffer, int bufsize)
113 {
114 // There must be room for at least a drive letter, colon and slash.
115 if (bufsize < 4)
116 return 0;
117
118 char *dst = buffer;
119 char *term = buffer + bufsize - 1;
120
121 // If the path started as relative, prepend the current working directory
122 if (!is_absolute_path(path))
123 {
124 dst = copy_current_directory(buffer, bufsize);
125 }
126 #if USE_DRIVE_LETTERS
127 // Add drive letter to absolute paths without one
128 else if (!has_drive_letter(path) && path[0] != '$')
129 {
130 dst = copy_current_drive_letter(buffer, bufsize);
131 }
132 #endif
133
134 // Process the path one segment at a time.
135 int segment = 0;
136 const char *src = path;
137 const char *token = src;
138 for (; dst < term; ++src) {
139 if (*src == '/' || *src == '\\' || *src == '\0') {
140 int tokenlen = (int)(src - token);
141 if (tokenlen == 0) {
142 if (segment == 0)
143 *dst++ = '/';
144 }
145 else if (tokenlen == 1 && token[0] == '.') {
146 // do nothing
147 }
148 else if (tokenlen == 2 && token[0] == '.' && token[1] == '.') {
149 dst = backtrack(dst - 1, buffer);
150 }
151 else {
152 if (dst > buffer && dst[-1] != '/')
153 *dst++ = '/';
154 dst = concat(dst, term, token, src);
155 }
156 token = src + 1;
157 ++segment;
158 if (*src == '\0')
159 break;
160 }
161 }
162
163 #if USE_DRIVE_LETTERS
164 // Make sure drive letter is lowercase
165 buffer[0] = (char)tolower(buffer[0]);
166 #endif
167
168 // Chop trailing slash
169 if (dst > buffer && dst[-1] == '/')
170 --dst;
171
172 // Add a null terminator.
173 if (dst < term)
174 *dst = '\0';
175 else
176 *--dst = '\0';
177
178 return dst;
179 }
180
get_relative_path(const char src[],const char dst[],char * buffer,int bufsize)181 char *get_relative_path(const char src[], const char dst[], char *buffer, int bufsize)
182 {
183 // There must be room for at least a drive letter, colon and slash.
184 if (bufsize < 4)
185 return 0;
186
187 // If the dst path starts with $, return it as an absolute path.
188 if (dst[0] == '$')
189 return copy(dst, buffer, bufsize);
190
191 // Get the absolute source and destination paths.
192 char srcBuf[PATH_BUFSIZE];
193 char dstBuf[PATH_BUFSIZE];
194 char *srcEnd = get_absolute_path(src, srcBuf, PATH_BUFSIZE - 1);
195 char *dstEnd = get_absolute_path(dst, dstBuf, PATH_BUFSIZE - 1);
196
197 #if USE_DRIVE_LETTERS
198 // If the paths have different drive letters, return the absolute path.
199 if (srcBuf[0] != dstBuf[0])
200 return copy(dstBuf, buffer, bufsize);
201 #endif
202
203 // Append '/' to the end of each path.
204 *srcEnd++ = '/'; *srcEnd = '\0';
205 *dstEnd++ = '/'; *dstEnd = '\0';
206
207 // Find the shared path prefix of the src and dst paths.
208 int lastSlash = -1;
209 int i;
210 for (i = 0; srcBuf[i] == dstBuf[i]; i++) {
211 if (srcBuf[i] == '/') {
212 lastSlash = i;
213 }
214 else if (srcBuf[i] == '\0') {
215 // Paths are identical, so return '.'
216 buffer[0] = '.';
217 buffer[1] = '\0';
218 return buffer + 1;
219 }
220 }
221
222 // For each remaining path segment in src after the last shared slash,
223 // append '../' to the result.
224 char *resultPtr = buffer;
225 char *resultTerm = buffer + bufsize;
226 const char *ptr;
227 for (ptr = srcBuf + lastSlash + 1; *ptr; ++ptr) {
228 if (*ptr == '/' && resultPtr + 3 < resultTerm) {
229 resultPtr[0] = '.';
230 resultPtr[1] = '.';
231 resultPtr[2] = '/';
232 resultPtr += 3;
233 }
234 }
235
236 // Append the portion of the destination path beyond the last shared
237 // slash.
238 char *dstPtr;
239 for (dstPtr = dstBuf + lastSlash + 1; *dstPtr && resultPtr < resultTerm; )
240 *resultPtr++ = *dstPtr++;
241
242 // Remove the trailing slash.
243 if (resultPtr[-1] == '/')
244 --resultPtr;
245
246 // Add a null terminator.
247 if (resultPtr < resultTerm)
248 *resultPtr = '\0';
249 else
250 *--resultPtr = '\0';
251
252 return resultPtr;
253 }
254