1 // =================================
  2 // Copyright (c) 2022 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 GetFullPath(const string& path)
 19     {
 20         string cp = Path.MakeCanonical(path);
 21         string p = cp;
 22         if (Path.IsRelative(cp))
 23         {
 24             p.Clear();
 25             p.Append(GetCurrentWorkingDirectory());
 26             p.Append('/');
 27             p.Append(cp);
 28         }
 29         List<string> components = p.Split('/');
 30         long w = 0;
 31         long n = components.Count();
 32         for (long i = 0; i < n; ++i;)
 33         {
 34             string c = components[i];
 35             if (i == 0 || (!c.IsEmpty() && c != "."))
 36             {
 37                 if (c == "..")
 38                 {
 39                     --w;
 40                     if (w < 0)
 41                     {
 42                         throw InvalidPathException("path '" + path + "' is invalid");
 43                     }
 44                 }
 45                 else
 46                 {
 47                     if (w != i)
 48                     {
 49                         components[w] = components[i];
 50                     }
 51                     ++w;
 52                 }
 53             }
 54         }
 55         if (w == 0)
 56         {
 57             return "/";
 58         }
 59         string result;
 60         for (long i = 0; i < w; ++i;)
 61         {
 62             if (i != 0)
 63             {
 64                 result.Append('/');
 65             }
 66             result.Append(components[i]);
 67         }
 68         if (result.IsEmpty())
 69         {
 70             return "/";
 71         }
 72         else
 73         {
 74             return result;
 75         }
 76     }
 77     
 78     public static class Path
 79     {
 80         public static bool Exists(const string& path)
 81         {
 82             byte[statBufSize] statBuf;
 83             int result = stat(path.Chars()&statBuf[0]statBufSize);
 84             return result != -1;
 85         }
 86         public static nothrow string MakeCanonical(const string& path)
 87         {
 88             string result;
 89             char prev = ' ';
 90             for (char c : path)
 91             {
 92                 if (c == '/')
 93                 {
 94                     if (prev != '/')
 95                     {
 96                         result.Append(c);
 97                     }
 98                 }
 99                 else
100                 {
101                     result.Append(c);
102                 }
103                 prev = c;
104             }
105             if (result == "/")
106             {
107                 return result;
108             }
109             if (!result.IsEmpty())
110             {
111                 if (result[result.Length() - 1] == '/')
112                 {
113                     return result.Substring(0result.Length() - 1);
114                 }
115             }
116             return result;
117         }
118         public static nothrow string ChangeExtension(const string& pathconst string& extension)
119         {
120             long lastDotPos = path.RFind('.');
121             long lastSlashPos = path.RFind('/');
122             if (lastSlashPos != -1 && lastDotPos < lastSlashPos)
123             {
124                 lastDotPos = -1;
125             }
126             if (extension.IsEmpty())
127             {
128                 if (lastDotPos != -1)
129                 {
130                     return path.Substring(0lastDotPos);
131                 }
132                 else
133                 {
134                     return path;
135                 }
136             }
137             else
138             {
139                 if (lastDotPos == -1)
140                 {
141                     if (extension[0] == '.')
142                     {
143                         return path + extension;
144                     }
145                     else
146                     {
147                         return path + "." + extension;
148                     }
149                 }
150                 else
151                 {
152                     if (extension[0] == '.')
153                     {
154                         return path.Substring(0lastDotPos) + extension;
155                     }
156                     else
157                     {
158                         return path.Substring(0 lastDotPos + 1) + extension;
159                     }
160                 }
161             }
162         }
163         public static nothrow bool HasExtension(const string& path)
164         {
165             string p = MakeCanonical(path);
166             long lastDotPos = p.RFind('.');
167             if (lastDotPos != -1)
168             {
169                 long lastDirSep = p.Find('/'lastDotPos + 1);
170                 if (lastDirSep > lastDotPos)
171                 {
172                     return false;
173                 }
174                 else if (lastDotPos < p.Length() - 1)
175                 {
176                     return true;
177                 }
178                 else
179                 {
180                     return false;
181                 }
182             }
183             else
184             {
185                 return false;
186             }
187         }
188         public static nothrow string GetExtension(const string& path)
189         {
190             string p = MakeCanonical(path);
191             long lastDotPos = p.RFind('.');
192             if (lastDotPos != -1)
193             {
194                 if (p.Find('/'lastDotPos + 1) != -1)
195                 {
196                     return string();
197                 }
198                 else
199                 {
200                     return p.Substring(lastDotPos);
201                 }
202             }
203             else
204             {
205                 return string();
206             }
207         }
208         public static nothrow string GetFileName(const string& path)
209         {
210             if (path.IsEmpty())
211             {
212                 return string();
213             }
214             else
215             {
216                 string p = MakeCanonical(path);
217                 char lastChar = p[p.Length() - 1];
218                 if (lastChar == '/')
219                 {
220                     return string();
221                 }
222                 else
223                 {
224                     long lastDirSepPos = p.RFind('/');
225                     if (lastDirSepPos != -1)
226                     {
227                         return p.Substring(lastDirSepPos + 1);
228                     }
229                     else
230                     {
231                         return p;
232                     }
233                 }
234             }
235         }
236         public static nothrow string GetFileNameWithoutExtension(const string& path)
237         {
238             string fileName = GetFileName(path);
239             long lastDotPos = fileName.RFind('.');
240             if (lastDotPos != -1)
241             {
242                 return fileName.Substring(0lastDotPos);
243             }
244             else
245             {
246                 return fileName;
247             }
248         }
249         public static nothrow string GetDirectoryName(const string& path)
250         {
251             string p = MakeCanonical(path);
252             if (p.IsEmpty())
253             {
254                 return string();
255             }
256             else
257             {
258                 long lastDirSepPos = p.RFind('/');
259                 if (lastDirSepPos != -1)
260                 {
261                     return p.Substring(0lastDirSepPos);
262                 }
263                 else
264                 {
265                     return string();
266                 }
267             }
268         }
269         public static nothrow string Combine(const string& path1const string& path2)
270         {
271             string p1 = MakeCanonical(path1);
272             string p2 = MakeCanonical(path2);
273             if (p1.IsEmpty())
274             {
275                 return p2;
276             }
277             else if (p2.IsEmpty())
278             {
279                 return p1;
280             }
281             else
282             {
283                 if (IsAbsolute(p2))
284                 {
285                     return p2;
286                 }
287                 else
288                 {
289                     string result = p1;
290                     if (p1[p1.Length() - 1] != '/')
291                     {
292                         result.Append('/');
293                     }
294                     result.Append(p2);
295                     return result;
296                 }
297             }
298         }
299         public static nothrow bool IsAbsolute(const string& path)
300         {
301             if (path.IsEmpty())
302             {
303                 return false;
304             }
305             else
306             {
307                 string p = MakeCanonical(path);
308                 if (p[0] == '/')
309                 {
310                     return true;
311                 }
312                 else
313                 {
314                     return false;
315                 }
316             }
317         }
318         public static nothrow bool IsRelative(const string& path)
319         {
320             return !IsAbsolute(path);
321         }
322         public static string GetParent(const string& path)
323         {
324             string fullPath = GetFullPath(path);
325             long lastSlashPos = fullPath.RFind('/');
326             if (lastSlashPos == -1)
327             {
328                 return string();
329             }
330             else
331             {
332                 return fullPath.Substring(0lastSlashPos);
333             }
334         }
335     }
336 }