1 // =================================
  2 // Copyright (c) 2021 Seppo Laakko
  3 // Distributed under the MIT license
  4 // =================================
  5 
  6 #include <soulng/util/Path.hpp>
  7 #include <soulng/util/TextUtils.hpp>
  8 #include <sys/stat.h>
  9 #include <fcntl.h>
 10 #include <stdlib.h>
 11 #include <vector>
 12 #include <string>
 13 #include <cctype>
 14 #ifdef _WIN32
 15     #include <direct.h>
 16 #elif defined(__linux) || defined(__unix) || defined(__posix) || defined(__unix__)
 17     #include <unistd.h>
 18 #endif
 19 #include <algorithm>
 20 
 21 namespace soulng { namespace util {
 22 
 23 std::string GetCurrentWorkingDirectory()
 24 {
 25     char buf[4096];
 26 #ifdef _WIN32
 27 
 28 #elif defined(__linux) || defined(__unix) || defined(__posix)
 29 
 30 #else
 31     #error unknown platform
 32 #endif
 33     if (wd != nullptr)
 34     {
 35         return Path::MakeCanonical(wd);
 36     }
 37     else
 38     {
 39         throw std::runtime_error("could not get current working directory");
 40     }
 41 }
 42 
 43 bool FileExists(const std::string& filePath)
 44 {
 45 #ifdef _WIN32
 46 
 47 
 48 
 49 
 50 
 51 
 52 
 53 
 54 
 55 
 56 #elif defined(__linux) || defined(__unix) || defined(__posix)
 57 
 58 
 59 
 60 
 61 
 62 
 63 
 64 
 65 
 66 
 67 #else
 68     #error unknown platform
 69 #endif
 70 }
 71 
 72 bool DirectoryExists(const std::string& directoryPath)
 73 {
 74 #ifdef _WIN32
 75 
 76 
 77 
 78 
 79 
 80 
 81 
 82 
 83 
 84 
 85 #elif defined(__linux) || defined(__unix) || defined(__posix)
 86 
 87 
 88 
 89 
 90 
 91 
 92 
 93 
 94 
 95 
 96 #else
 97     #error unknown platform
 98 #endif
 99 }
100 
101 bool PathExists(const std::string& path)
102 {
103 #ifdef _WIN32
104 
105 
106 #elif defined(__linux) || defined(__unix) || defined(__posix)
107 
108 
109 #else
110     #error unknown platform
111 #endif
112 }
113 
114 InvalidPathException::InvalidPathException(const std::string& message_): std::runtime_error(message_)
115 {
116 }
117 
118 std::string Path::MakeCanonical(const std::string& path)
119 {
120     bool startsWithDriveLetter = false;
121 #ifdef _WIN32
122 
123 
124 
125 
126 #endif
127     std::string result;
128     char prev = ' ';
129     bool first = true;
130     for (char c : path)
131     {
132         if (first)
133         {
134             first = false;
135             if (startsWithDriveLetter)
136             {
137                 c = std::toupper(static_cast<unsigned char>(c));
138             }
139         }
140         if (c == '\\')
141         {
142             c = '/';
143         }
144         if (c == '/')
145         {
146             if (prev != '/')
147             {
148                 result.append(1c);
149             }
150         }
151         else
152         {
153             result.append(1c);
154         }
155         prev = c;
156     }
157     if (result.length() == 3 && std::isalpha(result[0]) && result[1] == ':' && result[2] == '/')
158     {
159         return result;
160     }
161     if (result == "/")
162     {
163         return result;
164     }
165     if (!result.empty())
166     {
167         if (result[result.length() - 1] == '/')
168         {
169             return result.substr(0result.length() - 1);
170         }
171     }
172     return result;
173 }
174 
175 std::string Path::ChangeExtension(const std::string& pathconst std::string& extension)
176 {
177     std::string::size_type lastDotPos = path.rfind('.');
178     if (extension.empty())
179     {
180         if (lastDotPos != std::string::npos)
181         {
182             return path.substr(0lastDotPos);
183         }
184         else
185         {
186             return path;
187         }
188     }
189     else
190     {
191         if (lastDotPos == std::string::npos)
192         {
193             if (extension[0] == '.')
194             {
195                 return path + extension;
196             }
197             else
198             {
199                 return path + "." + extension;
200             }
201         }
202         else
203         {
204             if (extension[0] == '.')
205             {
206                 return path.substr(0lastDotPos) + extension;
207             }
208             else
209             {
210                 return path.substr(0lastDotPos + 1) + extension;
211             }
212         }
213     }
214 }
215 
216 bool Path::HasExtension(const std::string& path)
217 {
218     std::string::size_type lastDotPos = path.rfind('.');
219     if (lastDotPos != std::string::npos)
220     {
221         std::string::size_type lastColon = path.find(':'lastDotPos + 1);
222         std::string::size_type lastDirSep = path.find('/'lastDotPos + 1);
223         if (lastColon > lastDotPos || lastDirSep > lastDotPos)
224         {
225             return false;
226         }
227         else if (lastDotPos < path.length() - 1)
228         {
229             return true;
230         }
231         else
232         {
233             return false;
234         }
235     }
236     else
237     {
238         return false;
239     }
240 }
241 
242 std::string Path::GetExtension(const std::string& path)
243 {
244     std::string::size_type lastDotPos = path.rfind('.');
245     if (lastDotPos != std::string::npos)
246     {
247         if (path.find('/'lastDotPos + 1) != std::string::npos)
248         {
249             return std::string();
250         }
251         else
252         {
253             return path.substr(lastDotPos);
254         }
255     }
256     else
257     {
258         return std::string();
259     }
260 }
261 
262 std::string Path::GetDrive(const std::string& path)
263 {
264 #ifdef _WIN32
265 
266 
267 
268 
269 
270 
271 
272 #endif
273     return std::string();
274 }
275 
276 std::string Path::GetFileName(const std::string& path)
277 {
278     if (path.empty())
279     {
280         return std::string();
281     }
282     else
283     {
284         char lastChar = path[path.length() - 1];
285         if (lastChar == '/' || lastChar == ':')
286         {
287             return std::string();
288         }
289         else
290         {
291             std::string::size_type lastDirSepPos = path.rfind('/');
292             if (lastDirSepPos != std::string::npos)
293             {
294                 return path.substr(lastDirSepPos + 1);
295             }
296             else
297             {
298                 return path;
299             }
300         }
301     }
302 }
303 
304 std::string Path::GetFileNameWithoutExtension(const std::string& path)
305 {
306     std::string fileName = GetFileName(path);
307     std::string::size_type lastDotPos = fileName.rfind('.');
308     if (lastDotPos != std::string::npos)
309     {
310         return fileName.substr(0lastDotPos);
311     }
312     else
313     {
314         return fileName;
315     }
316 }
317 
318 
319 std::string Path::GetDirectoryName(const std::string& path)
320 {
321     if (path.empty())
322     {
323         return std::string();
324     }
325     else if (path.length() == 3 && std::isalpha(path[0]) && path[1] == ':' && path[2] == '/')
326     {
327         return std::string();
328     }
329     else
330     {
331         std::string::size_type lastDirSepPos = path.rfind('/');
332         if (lastDirSepPos != std::string::npos)
333         {
334             return path.substr(0lastDirSepPos);
335         }
336         else
337         {
338             return std::string();
339         }
340     }
341 }
342 
343 std::string Path::Combine(const std::string& path1const std::string& path2)
344 {
345     if (path1.empty())
346     {
347         return path2;
348     }
349     else if (path2.empty())
350     {
351         return path1;
352     }
353     else
354     {
355         if (IsAbsolute(path2))
356         {
357             return path2;
358         }
359         else
360         {
361             std::string result = path1;
362             if (result[result.length() - 1] != '/')
363             {
364                 result.append(1'/');
365             }
366             result.append(path2);
367             return result;
368         }
369     }
370 }
371 
372 bool Path::IsAbsolute(const std::string& path)
373 {
374     if (path.empty())
375     {
376         return false;
377     }
378     else
379     {
380         if (path[0] == '/')
381         {
382             return true;
383         }
384         else if (std::isalpha(path[0]) && path.length() > 2 && path[1] == ':' && path[2] == '/')
385         {
386             return true;
387         }
388         else
389         {
390             return false;
391         }
392     }
393 }
394 
395 bool Path::IsRelative(const std::string& path)
396 {
397     return !IsAbsolute(path);
398 }
399 
400 std::string GetFullPath(const std::string& path)
401 {
402     std::string p = Path::MakeCanonical(path);
403     if (Path::IsRelative(p))
404     {
405         p = GetCurrentWorkingDirectory();
406         p.append(1'/');
407         p.append(Path::MakeCanonical(path));
408     }
409     std::vector<std::string> components = Split(p'/');
410     int w = 0;
411     int n = int(components.size());
412     for (int i = 0; i < n; ++i)
413     {
414         const std::string& c = components[i];
415         if (i == 0 || (!c.empty() && c != "."))
416         {
417             if (c == "..")
418             {
419                 --w;
420                 if (w < 0)
421                 {
422                     throw InvalidPathException("path '" + path + "' is invalid");
423                 }
424             }
425             else
426             {
427                 if (w != i)
428                 {
429                     components[w] = components[i];
430                 }
431                 ++w;
432             }
433         }
434     }
435     if (w == 0)
436     {
437         return "/";
438     }
439     else if (w == 1)
440     {
441         const std::string& p = components[0];
442         if (p.length() == 2 && std::isalpha(p[0]) && p[1] == ':')
443         {
444             return p + "/";
445         }
446     }
447     std::string result;
448     for (int i = 0; i < w; ++i)
449     {
450         if (i != 0)
451         {
452             result.append(1'/');
453         }
454         result.append(components[i]);
455     }
456     return result;
457 }
458 
459 std::string MakeRelativeDirPath(const std::string& dirPathconst std::string& referenceDirPath)
460 {
461     std::string p = GetFullPath(dirPath);
462     std::string r = GetFullPath(referenceDirPath);
463     if (p == r)
464     {
465         return std::string();
466     }
467     if (Path::GetDrive(p) != Path::GetDrive(r))
468     {
469         return p;
470     }
471     std::vector<std::string> pc = Split(p'/');
472     std::vector<std::string> rc = Split(r'/');
473     int n = std::min(pc.size()rc.size());
474     int m = 0;
475     for (; m < n; ++m)
476     {
477         if (pc[m] != rc[m])
478         {
479             break;
480         }
481     }
482     std::string result;
483     for (int i = m; i < rc.size(); ++i)
484     {
485         result = Path::Combine(result"..");
486     }
487     for (int i = m; i < pc.size(); ++i)
488     {
489         result = Path::Combine(resultpc[i]);
490     }
491     return result;
492 }
493 
494 } }