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