1 // =================================
  2 // Copyright (c) 2024 Seppo Laakko
  3 // Distributed under the MIT license
  4 // =================================
  5 
  6 using System;
  7 using System.Collections;
  8 
  9 namespace System.IO
 10 {
 11     [nodiscard]
 12     public Result<string> GetCurrentWorkingDirectory()
 13     {
 14         int errorId = 0;
 15         int handle = RtmGetCurrentWorkingDirectoryHandle(errorId);
 16         if (errorId > 0)
 17         {
 18             return Result<string>(ErrorId(errorId));
 19         }
 20         string currentWorkingDirectory = Path.MakeCanonical(RtmGetCurrentWorkingDirectory(handle));
 21         RtmFreeCurrentWorkingDirectoryHandle(handle);
 22         return Result<string>(currentWorkingDirectory);
 23     }
 24 
 25     [nodiscard]
 26     public Result<string> GetFullPath(const string& path)
 27     {
 28         string p = Path.MakeCanonical(path);
 29         if (Path.IsRelative(p))
 30         {
 31             auto result = GetCurrentWorkingDirectory();
 32             if (result.Error())
 33             {
 34                 return Result<string>(ErrorId(result.GetErrorId()));
 35             }
 36             p = result.Value();
 37             p.Append('/');
 38             p.Append(Path.MakeCanonical(path));
 39         }
 40         List<string> components = p.Split('/');
 41         long w = 0;
 42         long n = components.Count();
 43         for (long i = 0; i < n; ++i;)
 44         {
 45             string c = components[i];
 46             if (i == 0 || (!c.IsEmpty() && c != "."))
 47             {
 48                 if (c == "..")
 49                 {
 50                     --w;
 51                     if (w < 0)
 52                     {
 53                         string errorMessage = "path \'" + path + "\' is invalid";
 54                         int errorId = RtmAllocateError(errorMessage.Chars());
 55                         return Result<string>(ErrorId(errorId));
 56                     }
 57                 }
 58                 else
 59                 {
 60                     if (w != i)
 61                     {
 62                         components[w] = components[i];
 63                     }
 64                     ++w;
 65                 }
 66             }
 67         }
 68         if (w == 0)
 69         {
 70             return Result<string>("/");
 71         }
 72         else if (w == 1)
 73         {
 74             string p = components[0];
 75             if (p.Length() == 2 && IsAlpha(p[0]) && p[1] == ':')
 76             {
 77                 return Result<string>(p + "/");
 78             }
 79         }
 80         string result;
 81         for (long i = 0; i < w; ++i;)
 82         {
 83             if (i != 0)
 84             {
 85                 result.Append('/');
 86             }
 87             result.Append(components[i]);
 88         }
 89         if (result.IsEmpty())
 90         {
 91             return Result<string>("/");
 92         }
 93         else
 94         {
 95             return Result<string>(result);
 96         }
 97     }
 98 
 99     [nodiscard]
100     public Result<string> MakeRelativeDirPath(const string& dirPathconst string& referenceDirPath)
101     {
102         auto dp = GetFullPath(dirPath);
103         if (dp.Error())
104         {
105             return Result<string>(ErrorId(dp.GetErrorId()));
106         }
107         string p = dp.Value();
108         auto rd = GetFullPath(referenceDirPath);
109         if (rd.Error())
110         {
111             return Result<string>(ErrorId(rd.GetErrorId()));
112         }
113         string r = rd.Value();
114         if (p == r)
115         {
116             return Result<string>(string());
117         }
118         if (Path.GetDrive(p) != Path.GetDrive(r))
119         {
120             return Result<string>(p);
121         }
122         List<string> pc = p.Split('/');
123         List<string> rc = r.Split('/');
124         int n = cast<int>(Min(pc.Count()rc.Count()));
125         int m = 0;
126         for (; m < n; ++m;)
127         {
128             if (pc[m] != rc[m])
129             {
130                 break;
131             }
132         }
133         string result;
134         int rn = cast<int>(rc.Count());
135         for (int i = m; i < rn; ++i;)
136         {
137             result = Path.Combine(result"..");
138         }
139         int pn = cast<int>(pc.Count());
140         for (int i = m; i < pn; ++i;)
141         {
142             result = Path.Combine(resultpc[i]);
143         }
144         return Result<string>(result);
145     }
146 
147     public static class Path
148     {
149         public static string MakeCanonical(const string& path)
150         {
151             bool startsWithDriveLetter = false;
152             if (path.Length() >= 2 && IsAlpha(path[0]) && path[1] == ':')
153             {
154                 startsWithDriveLetter = true;
155             }
156             string result;
157             char prev = ' ';
158             bool first = true;
159             for (char c : path)
160             {
161                 if (first)
162                 {
163                     first = false;
164                     if (startsWithDriveLetter)
165                     {
166                         c = AsciiToUpper(c);
167                     }
168                 }
169                 if (c == '\\')
170                 {
171                     c = '/';
172                 }
173                 if (c == '/')
174                 {
175                     if (prev != '/')
176                     {
177                         result.Append(c);
178                     }
179                 }
180                 else
181                 {
182                     result.Append(c);
183                 }
184                 prev = c;
185             }
186             if (result.Length() == 3 && IsAlpha(result[0]) && result[1] == ':' && result[2] == '/')
187             {
188                 return result;
189             }
190             if (result == "/")
191             {
192                 return result;
193             }
194             if (!result.IsEmpty())
195             {
196                 if (result[result.Length() - 1] == '/')
197                 {
198                     return result.Substring(0result.Length() - 1);
199                 }
200             }
201             return result;
202         }
203         public static string ChangeExtension(const string& pathconst string& extension)
204         {
205             long lastDotPos = path.RFind('.');
206             long lastSlashPos = path.RFind('/');
207             if (lastSlashPos != -1 && lastDotPos < lastSlashPos)
208             {
209                 lastDotPos = -1;
210             }
211             if (extension.IsEmpty())
212             {
213                 if (lastDotPos != -1)
214                 {
215                     return path.Substring(0lastDotPos);
216                 }
217                 else
218                 {
219                     return path;
220                 }
221             }
222             else
223             {
224                 if (lastDotPos == -1)
225                 {
226                     if (extension[0] == '.')
227                     {
228                         return path + extension;
229                     }
230                     else
231                     {
232                         return path + "." + extension;
233                     }
234                 }
235                 else
236                 {
237                     if (extension[0] == '.')
238                     {
239                         return path.Substring(0lastDotPos) + extension;
240                     }
241                     else
242                     {
243                         return path.Substring(0 lastDotPos + 1) + extension;
244                     }
245                 }
246             }
247         }
248         public static bool HasExtension(const string& path)
249         {
250             string p = MakeCanonical(path);
251             long lastDotPos = p.RFind('.');
252             if (lastDotPos != -1)
253             {
254                 long lastColon = p.Find(':'lastDotPos + 1);
255                 long lastDirSep = p.Find('/'lastDotPos + 1);
256                 if (lastColon > lastDotPos || lastDirSep > lastDotPos)
257                 {
258                     return false;
259                 }
260                 else if (lastDotPos < p.Length() - 1)
261                 {
262                     return true;
263                 }
264                 else
265                 {
266                     return false;
267                 }
268             }
269             else
270             {
271                 return false;
272             }
273         }
274         public static string GetExtension(const string& path)
275         {
276             string p = MakeCanonical(path);
277             long lastDotPos = p.RFind('.');
278             if (lastDotPos != -1)
279             {
280                 if (p.Find('/'lastDotPos + 1) != -1)
281                 {
282                     return string();
283                 }
284                 else
285                 {
286                     return p.Substring(lastDotPos);
287                 }
288             }
289             else
290             {
291                 return string();
292             }
293         }
294         public static string GetDrive(const string& path)
295         {
296             if (path.Length() >= 2 && IsAlpha(path[0]) && path[1] == ':')
297             {
298                 char c = AsciiToUpper(path[0]);
299                 string s(c);
300                 s.Append(':');
301                 return s;
302             }
303             return string();
304         }
305 
306         public static string GetFileName(const string& path)
307         {
308             if (path.IsEmpty())
309             {
310                 return string();
311             }
312             else
313             {
314                 string p = MakeCanonical(path);
315                 char lastChar = p[p.Length() - 1];
316                 if (lastChar == '/' || lastChar == ':')
317                 {
318                     return string();
319                 }
320                 else
321                 {
322                     long lastDirSepPos = p.RFind('/');
323                     if (lastDirSepPos != -1)
324                     {
325                         return p.Substring(lastDirSepPos + 1);
326                     }
327                     else
328                     {
329                         return p;
330                     }
331                 }
332             }
333         }
334         public static string GetFileNameWithoutExtension(const string& path)
335         {
336             string fileName = GetFileName(path);
337             long lastDotPos = fileName.RFind('.');
338             if (lastDotPos != -1)
339             {
340                 return fileName.Substring(0lastDotPos);
341             }
342             else
343             {
344                 return fileName;
345             }
346         }
347         public static string GetDirectoryName(const string& path)
348         {
349             string p = MakeCanonical(path);
350             if (p.IsEmpty())
351             {
352                 return string();
353             }
354             else if (p.Length() == 3 && IsAlpha(p[0]) && p[1] == ':' && p[2] == '/')
355             {
356                 return string();
357             }
358             else
359             {
360                 long lastDirSepPos = p.RFind('/');
361                 if (lastDirSepPos != -1)
362                 {
363                     return p.Substring(0lastDirSepPos);
364                 }
365                 else
366                 {
367                     return string();
368                 }
369             }
370         }
371         public static string Combine(const string& path1const string& path2)
372         {
373             string p1 = MakeCanonical(path1);
374             string p2 = MakeCanonical(path2);
375             if (p1.IsEmpty())
376             {
377                 return p2;
378             }
379             else if (p2.IsEmpty())
380             {
381                 return p1;
382             }
383             else
384             {
385                 if (IsAbsolute(p2))
386                 {
387                     return p2;
388                 }
389                 else
390                 {
391                     string result = p1;
392                     if (p1[p1.Length() - 1] != '/')
393                     {
394                         result.Append('/');
395                     }
396                     result.Append(p2);
397                     return result;
398                 }
399             }
400         }
401         public static bool IsAbsolute(const string& path)
402         {
403             if (path.IsEmpty())
404             {
405                 return false;
406             }
407             else
408             {
409                 string p = MakeCanonical(path);
410                 if (p[0] == '/')
411                 {
412                     return true;
413                 }
414                 else if (p.Length() > 2 && IsAlpha(p[0]) && p[1] == ':' && p[2] == '/')
415                 {
416                     return true;
417                 }
418                 else
419                 {
420                     return false;
421                 }
422             }
423         }
424         public static bool IsRelative(const string& path)
425         {
426             return !IsAbsolute(path);
427         }
428         public static Result<string> GetParent(const string& path)
429         {
430             auto fp = GetFullPath(path);
431             if (fp.Error())
432             {
433                 return Result<string>(ErrorId(fp.GetErrorId()));
434             }
435             string fullPath = fp.Value();
436             long lastSlashPos = fullPath.RFind('/');
437             if (lastSlashPos == -1)
438             {
439                 return Result<string>(string());
440             }
441             else
442             {
443                 return Result<string>(fullPath.Substring(0lastSlashPos));
444             }
445         }
446     }