1 /*******************************************************************************
2 
3         copyright:      Copyright (c) 2008 Kris Bell. All rights reserved
4         copyright:      Normalization & Patterns copyright (c) 2006-2009
5                         Max Samukha, Thomas Kühne, Grzegorz Adam Hankiewicz
6 
7         license:        BSD style: $(LICENSE)
8 
9         version:        Mar 2008: Initial version$(BR)
10                         Oct 2009: Added PathUtil code
11 
12         A more direct route to the file-system than FilePath. Use this
13         if you don't need path editing features. For example, if all you
14         want is to check some path exists, using this module would likely
15         be more convenient than FilePath:
16         ---
17         if (exists ("some/file/path"))
18             ...
19         ---
20 
21         These functions may be less efficient than FilePath because they
22         generally attach a null to the filename for each underlying O/S
23         call. Use Path when you need pedestrian access to the file-system,
24         and are not manipulating the path components. Use FilePath where
25         path-editing or mutation is desired.
26 
27         We encourage the use of "named import" with this module, such as:
28         ---
29         import Path = tango.io.Path;
30 
31         if (Path.exists ("some/file/path"))
32             ...
33         ---
34 
35         Also residing here is a lightweight path-parser, which splits a
36         filepath into constituent components. FilePath is based upon the
37         same PathParser:
38         ---
39         auto p = Path.parse ("some/file/path");
40         auto path = p.path;
41         auto name = p.name;
42         auto suffix = p.suffix;
43         ...
44         ---
45 
46         Path normalization and pattern-matching is also hosted here via
47         the normalize() and pattern() functions. See the doc towards the
48         end of this module.
49 
50         Compile with -version=Win32SansUnicode to enable Win95 & Win32s
51         file support.
52 
53 *******************************************************************************/
54 
55 module tango.io.Path;
56 
57 private import  tango.sys.Common;
58 
59 public  import  tango.time.Time : Time, TimeSpan;
60 
61 private import  tango.io.model.IFile : FileConst, FileInfo;
62 
63 public  import  tango.core.Exception : IOException, IllegalArgumentException;
64 
65 private import tango.stdc.string : memmove;
66 
67 private import tango.core.Octal;
68 
69 
70 /*******************************************************************************
71 
72         Various imports
73 
74 *******************************************************************************/
75 
76 version (Win32)
77         {
78         version (Win32SansUnicode)
79                 {
80                 private extern (C) int strlen (const char *s);
81                 private alias WIN32_FIND_DATA FIND_DATA;
82                 }
83              else
84                 {
85                 private extern (C) int wcslen (const wchar *s);
86                 private alias WIN32_FIND_DATAW FIND_DATA;
87                 }
88         }
89 
90 version (Posix)
91         {
92         private import tango.stdc.stdio;
93         private import tango.stdc.string;
94         private import tango.stdc.posix.utime;
95         private import tango.stdc.posix.dirent;
96         }
97 
98 
99 /*******************************************************************************
100 
101         Wraps the O/S specific calls with a D API. Note that these accept
102         null-terminated strings only, which is why it's not public. We need
103         this declared first to avoid forward-reference issues.
104 
105 *******************************************************************************/
106 
107 package struct FS
108 {
109         /***********************************************************************
110 
111                 TimeStamp information. Accurate to whatever the F/S supports.
112 
113         ***********************************************************************/
114 
115         struct Stamps
116         {
117                 Time created;  /// Time created.
118                 Time accessed; /// Last time accessed.
119                 Time modified; /// Last time modified.
120         }
121 
122         /***********************************************************************
123 
124                 Some fruct glue for directory listings.
125 
126         ***********************************************************************/
127 
128         struct Listing
129         {
130                 const(char)[] folder;
131                 bool   allFiles;
132 
133                 int opApply (scope int delegate(ref FileInfo) dg)
134                 {
135                         char[256] tmp = void;
136                         auto path = strz (folder, tmp);
137 
138                         // sanity check on Win32 ...
139                         version (Win32)
140                                 {
141                                 bool kosher(){foreach (c; path) if (c is '\\') return false; return true;}
142                                 assert (kosher(), "attempting to use non-standard '\\' in a path for a folder listing");
143                                 }
144 
145                         return list (path, dg, allFiles);
146                 }
147         }
148 
149         /***********************************************************************
150 
151                 Throw an exception using the last known error.
152 
153         ***********************************************************************/
154 
155         static void exception (const(char)[] filename)
156         {
157                 exception (filename[0..$-1] ~ ": ", SysError.lastMsg);
158         }
159 
160         /***********************************************************************
161 
162                 Throw an IO exception.
163 
164         ***********************************************************************/
165 
166         static void exception (const(char)[] prefix, const(char)[] error)
167         {
168                 throw new IOException ((prefix ~ error).idup);
169         }
170 
171         /***********************************************************************
172 
173                 Return an adjusted path such that non-empty instances always
174                 have a trailing separator.
175 
176                 Note: Allocates memory where path is not already terminated.
177 
178         ***********************************************************************/
179 
180         static inout(char)[] padded (inout(char)[] path, char c = '/')
181         {
182                 if (path.length && path[$-1] != c)
183                     path = path ~ c;
184                 return path;
185         }
186 
187         /***********************************************************************
188 
189                 Return an adjusted path such that non-empty instances always
190                 have a leading separator.
191 
192                 Note: Allocates memory where path is not already terminated.
193 
194         ***********************************************************************/
195 
196         static inout(char)[] paddedLeading (inout(char)[] path, char c = '/')
197         {
198                 if (path.length && path[0] != c)
199                     path = c ~ path;
200                 return path;
201         }
202 
203         /***********************************************************************
204 
205                 Return an adjusted path such that non-empty instances do not
206                 have a trailing separator.
207 
208         ***********************************************************************/
209 
210         static inout(char)[] stripped (inout(char)[] path, char c = '/')
211         {
212                 if (path.length && path[$-1] is c)
213                     path = path [0 .. $-1];
214                 return path;
215         }
216 
217         /***********************************************************************
218 
219                 Join a set of path specs together. A path separator is
220                 potentially inserted between each of the segments.
221 
222                 Note: Allocates memory.
223 
224         ***********************************************************************/
225 
226         static char[] join (const(char[])[] paths...)
227         {
228                 char[] result;
229 
230                 if (paths.length)
231                 {
232                     result ~= stripped(paths[0]);
233 
234                     foreach (path; paths[1 .. $-1])
235                         result ~= paddedLeading (stripped(path));
236 
237                     result ~= paddedLeading(paths[$-1]);
238 
239                    return result;
240                 }
241                 return "".dup;
242         }
243 
244         /***********************************************************************
245 
246                 Append a terminating null onto a string, cheaply where
247                 feasible.
248 
249                 Note: Allocates memory where the dst is too small.
250 
251         ***********************************************************************/
252 
253         static char[] strz (const(char)[] src, char[] dst)
254         {
255                 auto i = src.length + 1;
256                 if (dst.length < i)
257                     dst.length = i;
258                 dst [0 .. i-1] = src[];
259                 dst[i-1] = 0;
260                 return dst [0 .. i];
261         }
262 
263         /***********************************************************************
264 
265                 Win32 API code
266 
267         ***********************************************************************/
268 
269         version (Win32)
270         {
271                 /***************************************************************
272 
273                         Return a wchar[] instance of the path.
274 
275                 ***************************************************************/
276 
277                 private static wchar[] toString16 (wchar[] tmp, const(char)[] path)
278                 {
279                         auto i = MultiByteToWideChar (CP_UTF8, 0,
280                                                       cast(PCHAR)path.ptr, path.length,
281                                                       tmp.ptr, tmp.length);
282                         return tmp [0..i];
283                 }
284 
285                 /***************************************************************
286 
287                         Return a char[] instance of the path.
288 
289                 ***************************************************************/
290 
291                 private static char[] toString (char[] tmp, const(wchar[]) path)
292                 {
293                         auto i = WideCharToMultiByte (CP_UTF8, 0, path.ptr, path.length,
294                                                       cast(PCHAR)tmp.ptr, tmp.length, null, null);
295                         return tmp [0..i];
296                 }
297 
298                 /***************************************************************
299 
300                         Get info about this path.
301 
302                 ***************************************************************/
303 
304                 private static bool fileInfo (const(char)[] name, ref WIN32_FILE_ATTRIBUTE_DATA info)
305                 {
306                         version (Win32SansUnicode)
307                                 {
308                                 if (! GetFileAttributesExA (name.ptr, GetFileInfoLevelStandard, &info))
309                                       return false;
310                                 }
311                              else
312                                 {
313                                 wchar[MAX_PATH] tmp = void;
314                                 if (! GetFileAttributesExW (toString16(tmp, name).ptr, GetFileInfoLevelStandard, &info))
315                                       return false;
316                                 }
317 
318                         return true;
319                 }
320 
321                 /***************************************************************
322 
323                         Get info about this path.
324 
325                 ***************************************************************/
326 
327                 private static DWORD getInfo (const(char)[] name, ref WIN32_FILE_ATTRIBUTE_DATA info)
328                 {
329                         if (! fileInfo (name, info))
330                               exception (name);
331                         return info.dwFileAttributes;
332                 }
333 
334                 /***************************************************************
335 
336                         Get flags for this path.
337 
338                 ***************************************************************/
339 
340                 private static DWORD getFlags (const(char)[] name)
341                 {
342                         WIN32_FILE_ATTRIBUTE_DATA info = void;
343 
344                         return getInfo (name, info);
345                 }
346 
347                 /***************************************************************
348 
349                         Return whether the file or path exists.
350 
351                 ***************************************************************/
352 
353                 static bool exists (const(char)[] name)
354                 {
355                         WIN32_FILE_ATTRIBUTE_DATA info = void;
356 
357                         return fileInfo (name, info);
358                 }
359 
360                 /***************************************************************
361 
362                         Return the file length (in bytes.)
363 
364                 ***************************************************************/
365 
366                 static ulong fileSize (const(char)[] name)
367                 {
368                         WIN32_FILE_ATTRIBUTE_DATA info = void;
369 
370                         getInfo (name, info);
371                         return (cast(ulong) info.nFileSizeHigh << 32) +
372                                             info.nFileSizeLow;
373                 }
374 
375                 /***************************************************************
376 
377                         Is this file writable?
378 
379                 ***************************************************************/
380 
381                 static bool isWritable (const(char)[] name)
382                 {
383                         return (getFlags(name) & FILE_ATTRIBUTE_READONLY) is 0;
384                 }
385 
386                 /***************************************************************
387 
388                         Is this file actually a folder/directory?
389 
390                 ***************************************************************/
391 
392                 static bool isFolder (const(char)[] name)
393                 {
394                         return (getFlags(name) & FILE_ATTRIBUTE_DIRECTORY) != 0;
395                 }
396 
397                 /***************************************************************
398 
399                         Is this a normal file?
400 
401                 ***************************************************************/
402 
403                 static bool isFile (const(char)[] name)
404                 {
405                         return (getFlags(name) & FILE_ATTRIBUTE_DIRECTORY) == 0;
406                 }
407 
408                 /***************************************************************
409 
410                         Return timestamp information.
411 
412                         Timestamps are returns in a format dictated by the
413                         file-system. For example NTFS keeps UTC time,
414                         while FAT timestamps are based on the local time.
415 
416                 ***************************************************************/
417 
418                 static Stamps timeStamps (const(char)[] name)
419                 {
420                         static Time convert (FILETIME time)
421                         {
422                                 return Time (TimeSpan.Epoch1601 + *cast(long*) &time);
423                         }
424 
425                         WIN32_FILE_ATTRIBUTE_DATA info = void;
426                         Stamps                    time = void;
427 
428                         getInfo (name, info);
429                         time.modified = convert (info.ftLastWriteTime);
430                         time.accessed = convert (info.ftLastAccessTime);
431                         time.created  = convert (info.ftCreationTime);
432                         return time;
433                 }
434 
435                 /***************************************************************
436 
437                         Set the accessed and modified timestamps of the
438                         specified file.
439 
440                 ***************************************************************/
441 
442                 static void timeStamps (const(char)[] name, Time accessed, Time modified)
443                 {
444                         void set (HANDLE h)
445                         {
446                                 FILETIME m1, a1;
447                                 auto m = modified - Time.epoch1601;
448                                 auto a = accessed - Time.epoch1601;
449                                 *cast(long*) &a1.dwLowDateTime = m.ticks;
450                                 *cast(long*) &m1.dwLowDateTime = m.ticks;
451                                 if (SetFileTime (h, null, &a1, &m1) is 0)
452                                     exception (name);
453                         }
454 
455                         createFile (name, &set);
456                 }
457 
458                 /***************************************************************
459 
460                         Transfer the content of another file to this one.
461                         Throws an IOException upon failure.
462 
463                 ***************************************************************/
464 
465                 static void copy (const(char)[] src, const(char)[] dst)
466                 {
467                         version (Win32SansUnicode)
468                                 {
469                                 if (! CopyFileA (src.ptr, dst.ptr, false))
470                                       exception (src);
471                                 }
472                              else
473                                 {
474                                 wchar[MAX_PATH+1] tmp1 = void;
475                                 wchar[MAX_PATH+1] tmp2 = void;
476 
477                                 if (! CopyFileW (toString16(tmp1, src).ptr, toString16(tmp2, dst).ptr, false))
478                                       exception (src);
479                                 }
480                 }
481 
482                 /***************************************************************
483 
484                         Remove the file/directory from the file-system.
485                         Returns true on success - false otherwise.
486 
487                 ***************************************************************/
488 
489                 static bool remove (const(char)[] name)
490                 {
491                         if (isFolder(name))
492                            {
493                            version (Win32SansUnicode)
494                                     return RemoveDirectoryA (name.ptr) != 0;
495                                 else
496                                    {
497                                    wchar[MAX_PATH] tmp = void;
498                                    return RemoveDirectoryW (toString16(tmp, name).ptr) != 0;
499                                    }
500                            }
501                         else
502                            version (Win32SansUnicode)
503                                     return DeleteFileA (name.ptr) != 0;
504                                 else
505                                    {
506                                    wchar[MAX_PATH] tmp = void;
507                                    return DeleteFileW (toString16(tmp, name).ptr) != 0;
508                                    }
509                 }
510 
511                 /***************************************************************
512 
513                        Change the name or location of a file/directory.
514 
515                 ***************************************************************/
516 
517                 static void rename (const(char)[] src, const(char)[] dst)
518                 {
519                         const int Typical = MOVEFILE_REPLACE_EXISTING +
520                                             MOVEFILE_COPY_ALLOWED     +
521                                             MOVEFILE_WRITE_THROUGH;
522 
523                         int result;
524                         version (Win32SansUnicode)
525                                  result = MoveFileExA (src.ptr, dst.ptr, Typical);
526                              else
527                                 {
528                                 wchar[MAX_PATH] tmp1 = void;
529                                 wchar[MAX_PATH] tmp2 = void;
530                                 result = MoveFileExW (toString16(tmp1, src).ptr, toString16(tmp2, dst).ptr, Typical);
531                                 }
532 
533                         if (! result)
534                               exception (src);
535                 }
536 
537                 /***************************************************************
538 
539                         Create a new file.
540 
541                 ***************************************************************/
542 
543                 static void createFile (const(char)[] name)
544                 {
545                         createFile (name, null);
546                 }
547 
548                 /***************************************************************
549 
550                         Create a new directory.
551 
552                 ***************************************************************/
553 
554                 static void createFolder (const(char)[] name)
555                 {
556                         version (Win32SansUnicode)
557                                 {
558                                 if (! CreateDirectoryA (name.ptr, null))
559                                       exception (name);
560                                 }
561                              else
562                                 {
563                                 wchar[MAX_PATH] tmp = void;
564                                 if (! CreateDirectoryW (toString16(tmp, name).ptr, null))
565                                       exception (name);
566                                 }
567                 }
568 
569                 /***************************************************************
570 
571                         List the set of filenames within this folder.
572 
573                         Each path and filename is passed to the provided
574                         delegate, along with the path prefix and whether
575                         the entry is a folder or not.
576 
577                         Note: Allocates a small memory buffer.
578 
579                 ***************************************************************/
580 
581                 static int list (const(char)[] folder, scope int delegate(ref FileInfo) dg, bool all=false)
582                 {
583                         HANDLE                  h;
584                         int                     ret;
585                         const(char)[]           prefix;
586                         char[MAX_PATH+1]        tmp = void;
587                         FIND_DATA               fileinfo = void;
588 
589                         version (Win32SansUnicode)
590                                  alias char T;
591                               else
592                                  alias wchar T;
593 
594                         int next()
595                         {
596                                 version (Win32SansUnicode)
597                                          return FindNextFileA (h, &fileinfo);
598                                    else
599                                       return FindNextFileW (h, &fileinfo);
600                         }
601 
602                         static T[] padded (const(T)[] s, const(T)[] ext)
603                         {
604                                 if (s.length && s[$-1] is '/')
605                                     return cast(T[])(s ~ ext); // Should be a safe cast here
606                                 return s ~ "/" ~ ext;
607                         }
608 
609                         version (Win32SansUnicode)
610                                  h = FindFirstFileA (padded(folder[0..$-1], "*\0").ptr, &fileinfo);
611                              else
612                                 {
613                                 wchar[MAX_PATH] host = void;
614                                 h = FindFirstFileW (padded(toString16(host, folder[0..$-1]), "*\0").ptr, &fileinfo);
615                                 }
616 
617                         if (h is INVALID_HANDLE_VALUE)
618                             return ret;
619 
620                         scope (exit)
621                                FindClose (h);
622 
623                         prefix = FS.padded (folder[0..$-1]);
624                         do {
625                            version (Win32SansUnicode)
626                                    {
627                                    auto len = strlen (fileinfo.cFileName.ptr);
628                                    auto str = fileinfo.cFileName.ptr [0 .. len];
629                                    }
630                                 else
631                                    {
632                                    auto len = wcslen (fileinfo.cFileName.ptr);
633                                    auto str = toString (tmp, fileinfo.cFileName [0 .. len]);
634                                    }
635 
636                            // skip hidden/system files
637                            if (all || (fileinfo.dwFileAttributes & (FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_HIDDEN)) is 0)
638                               {
639                               FileInfo info = void;
640                               info.name   = str;
641                               info.path   = prefix;
642                               info.bytes  = (cast(ulong) fileinfo.nFileSizeHigh << 32) + fileinfo.nFileSizeLow;
643                               info.folder = (fileinfo.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0;
644                               info.hidden = (fileinfo.dwFileAttributes & FILE_ATTRIBUTE_HIDDEN) != 0;
645                               info.system = (fileinfo.dwFileAttributes & FILE_ATTRIBUTE_SYSTEM) != 0;
646 
647                               // skip "..." names
648                               if (str.length > 3 || str != "..."[0 .. str.length])
649                                   if ((ret = dg(info)) != 0)
650                                        break;
651                               }
652                            } while (next());
653 
654                         return ret;
655                 }
656 
657                 /***************************************************************
658 
659                         Create a new file.
660 
661                 ***************************************************************/
662 
663                 private static void createFile (const(char)[] name, scope void delegate(HANDLE) dg)
664                 {
665                         HANDLE h;
666 
667                         auto flags = dg !is null ? OPEN_EXISTING : CREATE_ALWAYS;
668                         version (Win32SansUnicode)
669                                  h = CreateFileA (name.ptr, GENERIC_WRITE,
670                                                   0, null, flags, FILE_ATTRIBUTE_NORMAL,
671                                                   cast(HANDLE) 0);
672                              else
673                                 {
674                                 wchar[MAX_PATH] tmp = void;
675                                 h = CreateFileW (toString16(tmp, name).ptr, GENERIC_WRITE,
676                                                  0, null, flags, FILE_ATTRIBUTE_NORMAL,
677                                                  cast(HANDLE) 0);
678                                 }
679 
680                         if (h is INVALID_HANDLE_VALUE)
681                             exception (name);
682 
683                         if (dg !is null)
684                             dg(h);
685 
686                         if (! CloseHandle (h))
687                               exception (name);
688                 }
689         }
690 
691         /***********************************************************************
692 
693                 Posix-specific code.
694 
695         ***********************************************************************/
696 
697         version (Posix)
698         {
699                 /***************************************************************
700 
701                         Get info about this path.
702 
703                 ***************************************************************/
704 
705                 private static uint getInfo (const(char)[] name, ref stat_t stats)
706                 {
707                         if (posix.stat (name.ptr, &stats))
708                             exception (name);
709 
710                         return stats.st_mode;
711                 }
712 
713                 /***************************************************************
714 
715                         Return whether the file or path exists.
716 
717                 ***************************************************************/
718 
719                 static bool exists (const(char)[] name)
720                 {
721                         stat_t stats = void;
722                         return posix.stat (name.ptr, &stats) is 0;
723                 }
724 
725                 /***************************************************************
726 
727                         Return the file length (in bytes.)
728 
729                 ***************************************************************/
730 
731                 static ulong fileSize (const(char)[] name)
732                 {
733                         stat_t stats = void;
734 
735                         getInfo (name, stats);
736                         return cast(ulong) stats.st_size;
737                 }
738 
739                 /***************************************************************
740 
741                         Is this file writable?
742 
743                 ***************************************************************/
744 
745                 static bool isWritable (const(char)[] name)
746                 {
747                         stat_t stats = void;
748 
749                         return (getInfo(name, stats) & O_RDONLY) is 0;
750                 }
751 
752                 /***************************************************************
753 
754                         Is this file actually a folder/directory?
755 
756                 ***************************************************************/
757 
758                 static bool isFolder (const(char)[] name)
759                 {
760                         stat_t stats = void;
761 
762                         return (getInfo(name, stats) & S_IFMT) is S_IFDIR;
763                 }
764 
765                 /***************************************************************
766 
767                         Is this a normal file?
768 
769                 ***************************************************************/
770 
771                 static bool isFile (const(char)[] name)
772                 {
773                         stat_t stats = void;
774 
775                         return (getInfo(name, stats) & S_IFMT) is S_IFREG;
776                 }
777 
778                 /***************************************************************
779 
780                         Return timestamp information.
781 
782                         Timestamps are returns in a format dictated by the
783                         file-system. For example NTFS keeps UTC time,
784                         while FAT timestamps are based on the local time.
785 
786                 ***************************************************************/
787 
788                 static Stamps timeStamps (const(char)[] name)
789                 {
790                         static Time convert (typeof(stat_t.st_mtime) secs)
791                         {
792                                 return Time.epoch1970 +
793                                        TimeSpan.fromSeconds(secs);
794                         }
795 
796                         stat_t stats = void;
797                         Stamps time  = void;
798 
799                         getInfo (name, stats);
800 
801                         time.modified = convert (stats.st_mtime);
802                         time.accessed = convert (stats.st_atime);
803                         time.created  = convert (stats.st_ctime);
804                         return time;
805                 }
806 
807                 /***************************************************************
808 
809                         Set the accessed and modified timestamps of the
810                         specified file.
811 
812                 ***************************************************************/
813 
814                 static void timeStamps (const(char)[] name, Time accessed, Time modified)
815                 {
816                         utimbuf time = void;
817                         time.actime = cast(time_t)(accessed - Time.epoch1970).seconds;
818                         time.modtime = cast(time_t)(modified - Time.epoch1970).seconds;
819                         if (utime (name.ptr, &time) is -1)
820                             exception (name);
821                 }
822 
823                 /***********************************************************************
824 
825                         Transfer the content of another file to this one. Returns a
826                         reference to this class on success, or throws an IOException
827                         upon failure.
828 
829                         Note: Allocates a memory buffer.
830 
831                 ***********************************************************************/
832 
833                 static void copy (const(char)[] source, const(char)[] dest)
834                 {
835                         auto src = posix.open (source.ptr, O_RDONLY, octal!(640));
836                         scope (exit)
837                                if (src != -1)
838                                    posix.close (src);
839 
840                         auto dst = posix.open (dest.ptr, O_CREAT | O_RDWR, octal!(660));
841                         scope (exit)
842                                if (dst != -1)
843                                    posix.close (dst);
844 
845                         if (src is -1 || dst is -1)
846                             exception (source);
847 
848                         // copy content
849                         ubyte[] buf = new ubyte [16 * 1024];
850                         auto read = posix.read (src, buf.ptr, buf.length);
851                         while (read > 0)
852                               {
853                               auto p = buf.ptr;
854                               do {
855                                  auto written = posix.write (dst, p, read);
856                                  p += written;
857                                  read -= written;
858                                  if (written is -1)
859                                      exception (dest);
860                                  } while (read > 0);
861                               read = posix.read (src, buf.ptr, buf.length);
862                               }
863                         if (read is -1)
864                             exception (source);
865 
866                         // copy timestamps
867                         stat_t stats;
868                         if (posix.stat (source.ptr, &stats))
869                             exception (source);
870 
871                         utimbuf utim;
872                         utim.actime = stats.st_atime;
873                         utim.modtime = stats.st_mtime;
874                         if (utime (dest.ptr, &utim) is -1)
875                             exception (dest);
876                 }
877 
878                 /***************************************************************
879 
880                         Remove the file/directory from the file-system.
881                         Returns true on success - false otherwise.
882 
883                 ***************************************************************/
884 
885                 static bool remove (const(char)[] name)
886                 {
887                         return tango.stdc.stdio.remove(name.ptr) != -1;
888                 }
889 
890                 /***************************************************************
891 
892                        Change the name or location of a file/directory.
893 
894                 ***************************************************************/
895 
896                 static void rename (const(char)[] src, const(char)[] dst)
897                 {
898                         if (tango.stdc.stdio.rename (src.ptr, dst.ptr) is -1)
899                             exception (src);
900                 }
901 
902                 /***************************************************************
903 
904                         Create a new file.
905 
906                 ***************************************************************/
907 
908                 static void createFile (const(char)[] name)
909                 {
910                         int fd;
911 
912                         fd = posix.open (name.ptr, O_CREAT | O_WRONLY | O_TRUNC, octal!(660));
913                         if (fd is -1)
914                             exception (name);
915 
916                         if (posix.close(fd) is -1)
917                             exception (name);
918                 }
919 
920                 /***************************************************************
921 
922                         Create a new directory.
923 
924                 ***************************************************************/
925 
926                 static void createFolder (const(char)[] name)
927                 {
928                         if (posix.mkdir (name.ptr, octal!(777)))
929                             exception (name);
930                 }
931 
932                 /***************************************************************
933 
934                         List the set of filenames within this folder.
935 
936                         Each path and filename is passed to the provided
937                         delegate, along with the path prefix and whether
938                         the entry is a folder or not.
939 
940                         Note: Allocates and reuses a small memory buffer.
941 
942                 ***************************************************************/
943 
944                 static int list (const(char)[] folder, scope int delegate(ref FileInfo) dg, bool all=false)
945                 {
946                         int             ret;
947                         DIR*            dir;
948                         dirent          entry;
949                         dirent*         pentry;
950                         stat_t          sbuf;
951                         const(char)[]   prefix;
952                         char[]          sfnbuf;
953 
954                         dir = tango.stdc.posix.dirent.opendir (folder.ptr);
955                         if (! dir)
956                               return ret;
957 
958                         scope (exit)
959                                tango.stdc.posix.dirent.closedir (dir);
960 
961                         // ensure a trailing '/' is present
962                         prefix = FS.padded (folder[0..$-1]);
963 
964                         // prepare our filename buffer
965                         sfnbuf = prefix.dup;
966 
967                         while (true)
968                               {
969                               // pentry is null at end of listing, or on an error
970                               readdir_r (dir, &entry, &pentry);
971                               if (pentry is null)
972                                   break;
973 
974                               auto len = tango.stdc..string.strlen (entry.d_name.ptr);
975                               auto str = entry.d_name.ptr [0 .. len];
976                               ++len;  // include the null
977 
978                               // resize the buffer as necessary ...
979                               if (sfnbuf.length < prefix.length + len)
980                                   sfnbuf.length = prefix.length + len;
981 
982                               sfnbuf [prefix.length .. prefix.length + len]
983                                       = entry.d_name.ptr [0 .. len];
984 
985                               // skip "..." names
986                               if (str.length > 3 || str != "..."[0 .. str.length])
987                                  {
988                                  FileInfo info = void;
989                                  info.bytes  = 0;
990                                  info.name   = str;
991                                  info.path   = prefix;
992                                  info.hidden = str[0] is '.';
993                                  info.folder = info.system = false;
994 
995                                  if (! stat (sfnbuf.ptr, &sbuf))
996                                  {
997                                     info.folder = (sbuf.st_mode & S_IFDIR) != 0;
998                                     if (info.folder is false)
999                                     {
1000                                         if ((sbuf.st_mode & S_IFREG) is 0)
1001                                              info.system = true;
1002                                         else
1003                                            info.bytes = cast(ulong) sbuf.st_size;
1004                                     }
1005                                  }
1006                                  if (all || (info.hidden | info.system) is false)
1007                                      if ((ret = dg(info)) != 0)
1008                                           break;
1009                                  }
1010                               }
1011                         return ret;
1012                 }
1013         }
1014 }
1015 
1016 
1017 /*******************************************************************************
1018 
1019         Parses a file path.
1020 
1021         File paths containing non-ansi characters should be UTF-8 encoded.
1022         Supporting Unicode in this manner was deemed to be more suitable
1023         than providing a wchar version of PathParser, and is both consistent
1024         & compatible with the approach taken with the Uri class.
1025 
1026         Note that patterns of adjacent '.' separators are treated specially
1027         in that they will be assigned to the name where there is no distinct
1028         suffix. In addition, a '.' at the start of a name signifies it does
1029         not belong to the suffix i.e. ".file" is a name rather than a suffix.
1030         Patterns of intermediate '.' characters will otherwise be assigned
1031         to the suffix, such that "file....suffix" includes the dots within
1032         the suffix itself. See method ext() for a suffix without dots.
1033 
1034         Note also that normalization of path-separators does *not* occur by
1035         default. This means that usage of '\' characters should be explicitly
1036         converted beforehand into '/' instead (an exception is thrown in those
1037         cases where '\' is present). On-the-fly conversion is avoided because
1038         (a) the provided path is considered immutable and (b) we avoid taking
1039         a copy of the original path. Module FilePath exists at a higher level,
1040         without such contraints.
1041 
1042 *******************************************************************************/
1043 
1044 struct PathParser(char_t = char)
1045 {
1046         package char_t[]       fp;                     // filepath with trailing
1047         package int            end_,                   // before any trailing 0
1048                                ext_,                   // after rightmost '.'
1049                                name_,                  // file/dir name
1050                                folder_,                // path before name
1051                                suffix_;                // including leftmost '.'
1052 
1053         /***********************************************************************
1054 
1055                 Parse the path spec.
1056 
1057         ***********************************************************************/
1058 
1059         PathParser parse (char_t[] path)
1060         {
1061                 return parse (path, path.length);
1062         }
1063 
1064         /***********************************************************************
1065 
1066                 Duplicate this path.
1067 
1068                 Note: Allocates memory for the path content.
1069 
1070         ***********************************************************************/
1071 
1072         @property PathParser dup () const
1073         {
1074                 PathParser ret;
1075                 ret.fp = fp.dup;
1076                 ret.end_ = end_;
1077                 ret.ext_ = ext_;
1078                 ret.name_ = name_;
1079                 ret.folder_ = folder_;
1080                 ret.suffix_ = suffix_;
1081 
1082                 return ret;
1083         }
1084 
1085         /***********************************************************************
1086 
1087                 Return the complete text of this filepath.
1088 
1089         ***********************************************************************/
1090 
1091         inout(char_t)[] dString () inout
1092         {
1093                 return fp [0 .. end_];
1094         }
1095         
1096         immutable(char)[] toString() const
1097         {
1098                 return dString().idup;
1099         }
1100 
1101         /***********************************************************************
1102 
1103                 Return the root of this path. Roots are constructs such as
1104                 "C:".
1105 
1106         ***********************************************************************/
1107 
1108         @property inout(char_t)[] root () inout
1109         {
1110                 return fp [0 .. folder_];
1111         }
1112 
1113         /***********************************************************************
1114 
1115                 Return the file path. Paths may start and end with a "/".
1116                 The root path is "/" and an unspecified path is returned as
1117                 an empty string. Directory paths may be split such that the
1118                 directory name is placed into the 'name' member; directory
1119                 paths are treated no differently than file paths.
1120 
1121         ***********************************************************************/
1122 
1123 
1124         
1125         @property inout(char_t)[] folder () inout
1126         {
1127                 return fp [folder_ .. name_];
1128         }
1129 
1130         /***********************************************************************
1131 
1132                 Returns a path representing the parent of this one. This
1133                 will typically return the current path component, though
1134                 with a special case where the name component is empty. In
1135                 such cases, the path is scanned for a prior segment:
1136                 $(UL
1137                   $(LI normal:  /x/y/z => /x/y)
1138                   $(LI special: /x/y/  => /x)
1139                   $(LI normal:  /x     => /)
1140                   $(LI normal:  /      => [empty]))
1141 
1142                 Note that this returns a path suitable for splitting into
1143                 path and name components (there's no trailing separator).
1144 
1145         ***********************************************************************/
1146 
1147         @property inout(char_t)[] parent () inout
1148         {
1149                 auto p = path;
1150                 if (name.length is 0)
1151                     for (int i=cast(int)p.length-1; --i > 0;)
1152                          if (p[i] is FileConst.PathSeparatorChar)
1153                             {
1154                             p = p[0 .. i];
1155                             break;
1156                             }
1157                 return FS.stripped (p);
1158         }
1159 
1160         /***********************************************************************
1161 
1162                 Pop the rightmost element off this path, stripping off a
1163                 trailing '/' as appropriate:
1164                 $(UL
1165                   $(LI /x/y/z => /x/y)
1166                   $(LI /x/y/  => /x/y  (note trailing '/' in the original))
1167                   $(LI /x/y   => /x)
1168                   $(LI /x     => /)
1169                   $(LI /      => [empty]))
1170 
1171                 Note that this returns a path suitable for splitting into
1172                 path and name components (there's no trailing separator).
1173 
1174         ***********************************************************************/
1175 
1176         inout(char_t)[] pop () inout
1177         {
1178                 return FS.stripped (path);
1179         }
1180 
1181         /***********************************************************************
1182 
1183                 Return the name of this file, or directory.
1184 
1185         ***********************************************************************/
1186 
1187         @property inout(char_t)[] name () inout
1188         {
1189                 return fp [name_ .. suffix_];
1190         }
1191 
1192         /***********************************************************************
1193 
1194                 Ext is the tail of the filename, rightward of the rightmost
1195                 '.' separator e.g. path "foo.bar" has ext "bar". Note that
1196                 patterns of adjacent separators are treated specially - for
1197                 example, ".." will wind up with no ext at all.
1198 
1199         ***********************************************************************/
1200 
1201         @property char_t[] ext ()
1202         {
1203                 auto x = suffix;
1204                 if (x.length)
1205                    {
1206                    if (ext_ is 0)
1207                        foreach (c; x)
1208                        {
1209                                 if (c is '.')
1210                                     ++ext_;
1211                                 else
1212                                    break;
1213                        }
1214                    x = x [ext_ .. $];
1215                    }
1216                 return x;
1217         }
1218 
1219         /***********************************************************************
1220 
1221                 Suffix is like ext, but includes the separator e.g. path
1222                 "foo.bar" has suffix ".bar".
1223 
1224         ***********************************************************************/
1225 
1226         @property inout(char_t)[] suffix () inout
1227         {
1228                 return fp [suffix_ .. end_];
1229         }
1230 
1231         /***********************************************************************
1232 
1233                 Return the root + folder combination.
1234 
1235         ***********************************************************************/
1236 
1237         @property inout(char_t)[] path () inout
1238         {
1239                 return fp [0 .. name_];
1240         }
1241 
1242         /***********************************************************************
1243 
1244                 Return the name + suffix combination.
1245 
1246         ***********************************************************************/
1247 
1248         @property inout(char_t)[] file () inout
1249         {
1250                 return fp [name_ .. end_];
1251         }
1252 
1253         /***********************************************************************
1254 
1255                 Returns true if this path is *not* relative to the
1256                 current working directory.
1257 
1258         ***********************************************************************/
1259 
1260         @property const bool isAbsolute ()
1261         {
1262                 return (folder_ > 0) ||
1263                        (folder_ < end_ && fp[folder_] is FileConst.PathSeparatorChar);
1264         }
1265 
1266         /***********************************************************************
1267 
1268                 Returns true if this FilePath is empty.
1269 
1270         ***********************************************************************/
1271 
1272         @property const bool isEmpty ()
1273         {
1274                 return end_ is 0;
1275         }
1276 
1277         /***********************************************************************
1278 
1279                 Returns true if this path has a parent. Note that a
1280                 parent is defined by the presence of a path-separator in
1281                 the path. This means 'foo' within "/foo" is considered a
1282                 child of the root.
1283 
1284         ***********************************************************************/
1285 
1286         @property const bool isChild ()
1287         {
1288                 return folder().length > 0;
1289         }
1290 
1291         /***********************************************************************
1292 
1293                 Does this path equate to the given text? We ignore trailing
1294                 path-separators when testing equivalence.
1295 
1296         ***********************************************************************/
1297 
1298         /*int opEquals (char[] s)
1299         {       
1300                 return FS.stripped(s) == FS.stripped(toString);
1301         }*/
1302         const bool equals (const(char)[] s)
1303         {
1304                 return FS.stripped(s) == FS.stripped(dString());
1305         }
1306 
1307         /***********************************************************************
1308 
1309                 Parse the path spec with explicit end point. A '\' is
1310                 considered illegal in the path and should be normalized
1311                 out before this is invoked (the content managed here is
1312                 considered immutable, and thus cannot be changed by this
1313                 function.)
1314 
1315         ***********************************************************************/
1316 
1317         package PathParser parse (char_t[] path, size_t end)
1318         {
1319                 end_ = cast(int)end;
1320                 fp = path;
1321                 folder_ = 0;
1322                 name_ = suffix_ = -1;
1323 
1324                 for (int i=end_; --i >= 0;)
1325                      switch (fp[i])
1326                             {
1327                             case FileConst.FileSeparatorChar:
1328                                  if (name_ < 0)
1329                                      if (suffix_ < 0 && i && fp[i-1] != '.')
1330                                          suffix_ = i;
1331                                  break;
1332 
1333                             case FileConst.PathSeparatorChar:
1334                                  if (name_ < 0)
1335                                      name_ = i + 1;
1336                                  break;
1337 
1338                             // Windows file separators are illegal. Use
1339                             // standard() or equivalent to convert first
1340                             case '\\':
1341                                  FS.exception ("unexpected '\\' character in path: ", path[0..end]);
1342                                  break;
1343                             version (Win32)
1344                             {
1345                             case ':':
1346                                  folder_ = i + 1;
1347                                  break;
1348                             }
1349 
1350                             default:
1351                                  break;
1352                             }
1353 
1354                 if (name_ < 0)
1355                     name_ = folder_;
1356 
1357                 if (suffix_ < 0 || suffix_ is name_)
1358                     suffix_ = end_;
1359 
1360                 return this;
1361         }
1362 }
1363 
1364 
1365 /*******************************************************************************
1366 
1367         Does this path currently exist?
1368 
1369 *******************************************************************************/
1370 
1371 bool exists (const(char)[] name)
1372 {
1373         char[512] tmp = void;
1374         return FS.exists (FS.strz(name, tmp));
1375 }
1376 
1377 /*******************************************************************************
1378 
1379         Returns the time of the last modification. Accurate
1380         to whatever the F/S supports, and in a format dictated
1381         by the file-system. For example NTFS keeps UTC time,
1382         while FAT timestamps are based on the local time.
1383 
1384 *******************************************************************************/
1385 
1386 Time modified (const(char)[] name)
1387 {
1388         return timeStamps(name).modified;
1389 }
1390 
1391 /*******************************************************************************
1392 
1393         Returns the time of the last access. Accurate to
1394         whatever the F/S supports, and in a format dictated
1395         by the file-system. For example NTFS keeps UTC time,
1396         while FAT timestamps are based on the local time.
1397 
1398 *******************************************************************************/
1399 
1400 Time accessed (const(char)[] name)
1401 {
1402         return timeStamps(name).accessed;
1403 }
1404 
1405 /*******************************************************************************
1406 
1407         Returns the time of file creation. Accurate to
1408         whatever the F/S supports, and in a format dictated
1409         by the file-system. For example NTFS keeps UTC time,
1410         while FAT timestamps are based on the local time.
1411 
1412 *******************************************************************************/
1413 
1414 Time created (const(char)[] name)
1415 {
1416         return timeStamps(name).created;
1417 }
1418 
1419 /*******************************************************************************
1420 
1421         Return the file length (in bytes.)
1422 
1423 *******************************************************************************/
1424 
1425 ulong fileSize (const(char)[] name)
1426 {
1427         char[512] tmp = void;
1428         return FS.fileSize (FS.strz(name, tmp));
1429 }
1430 
1431 /*******************************************************************************
1432 
1433         Is this file writable?
1434 
1435 *******************************************************************************/
1436 
1437 bool isWritable (const(char)[] name)
1438 {
1439         char[512] tmp = void;
1440         return FS.isWritable (FS.strz(name, tmp));
1441 }
1442 
1443 /*******************************************************************************
1444 
1445         Is this file actually a folder/directory?
1446 
1447 *******************************************************************************/
1448 
1449 bool isFolder (const(char)[] name)
1450 {
1451         char[512] tmp = void;
1452         return FS.isFolder (FS.strz(name, tmp));
1453 }
1454 
1455 /*******************************************************************************
1456 
1457         Is this file actually a normal file?
1458         Not a directory or (on unix) a device file or link.
1459 
1460 *******************************************************************************/
1461 
1462 bool isFile (const(char)[] name)
1463 {
1464         char[512] tmp = void;
1465         return FS.isFile (FS.strz(name, tmp));
1466 }
1467 
1468 /*******************************************************************************
1469 
1470         Return timestamp information.
1471 
1472         Timestamps are returns in a format dictated by the
1473         file-system. For example NTFS keeps UTC time,
1474         while FAT timestamps are based on the local time.
1475 
1476 *******************************************************************************/
1477 
1478 FS.Stamps timeStamps (const(char)[] name)
1479 {
1480         char[512] tmp = void;
1481         return FS.timeStamps (FS.strz(name, tmp));
1482 }
1483 
1484 /*******************************************************************************
1485 
1486         Set the accessed and modified timestamps of the specified file.
1487 
1488         Since: 0.99.9
1489 
1490 *******************************************************************************/
1491 
1492 void timeStamps (const(char)[] name, Time accessed, Time modified)
1493 {
1494         char[512] tmp = void;
1495         FS.timeStamps (FS.strz(name, tmp), accessed, modified);
1496 }
1497 
1498 /*******************************************************************************
1499 
1500         Remove the file/directory from the file-system. Returns true if
1501         successful, false otherwise.
1502 
1503 *******************************************************************************/
1504 
1505 bool remove (const(char)[] name)
1506 {
1507         char[512] tmp = void;
1508         return FS.remove (FS.strz(name, tmp));
1509 }
1510 
1511 /*******************************************************************************
1512 
1513         Remove the files and folders listed in the provided paths. Where
1514         folders are listed, they should be preceded by their contained
1515         files in order to be successfully removed. Returns a set of paths
1516         that failed to be removed (where .length is zero upon success).
1517 
1518         The collate() function can be used to provide the input paths:
1519         ---
1520         remove (collate (".", "*.d", true));
1521         ---
1522 
1523         Use with great caution.
1524 
1525         Note: May allocate memory.
1526 
1527         Since: 0.99.9
1528 
1529 *******************************************************************************/
1530 
1531 char[][] remove (char[][] paths)
1532 {
1533         char[][] failed;
1534         foreach (path; paths)
1535                  if (! remove (path))
1536                        failed ~= path;
1537         return failed;
1538 }
1539 
1540 /*******************************************************************************
1541 
1542         Create a new file.
1543 
1544 *******************************************************************************/
1545 
1546 void createFile (const(char)[] name)
1547 {
1548         char[512] tmp = void;
1549         FS.createFile (FS.strz(name, tmp));
1550 }
1551 
1552 /*******************************************************************************
1553 
1554         Create a new directory.
1555 
1556 *******************************************************************************/
1557 
1558 void createFolder (const(char)[] name)
1559 {
1560         char[512] tmp = void;
1561         FS.createFolder (FS.strz(name, tmp));
1562 }
1563 
1564 /*******************************************************************************
1565 
1566         Create an entire path consisting of this folder along with
1567         all parent folders. The path should not contain '.' or '..'
1568         segments, which can be removed via the normalize() function.
1569 
1570         Note that each segment is created as a folder, including the
1571         trailing segment.
1572 
1573         Throws: IOException upon system errors.
1574 
1575         Throws: IllegalArgumentException if a segment exists but as a
1576         file instead of a folder.
1577 
1578 *******************************************************************************/
1579 
1580 void createPath (const(char)[] path)
1581 {
1582         void test (const(char)[] segment)
1583         {
1584                 if (segment.length)
1585                 {
1586                     if (! exists (segment))
1587                           createFolder (segment);
1588                     else
1589                        if (! isFolder (segment))
1590                              throw new IllegalArgumentException (("Path.createPath :: file/folder conflict: " ~ segment).idup);
1591                 }
1592         }
1593 
1594         foreach (i, char c; path)
1595                  if (c is '/')
1596                      test (path [0 .. i]);
1597         test (path);
1598 }
1599 
1600 /*******************************************************************************
1601 
1602        Change the name or location of a file/directory.
1603 
1604 *******************************************************************************/
1605 
1606 void rename (const(char)[] src, const(char)[] dst)
1607 {
1608         char[512] tmp1 = void;
1609         char[512] tmp2 = void;
1610         FS.rename (FS.strz(src, tmp1), FS.strz(dst, tmp2));
1611 }
1612 
1613 /*******************************************************************************
1614 
1615         Transfer the content of one file to another. Throws
1616         an IOException upon failure.
1617 
1618 *******************************************************************************/
1619 
1620 void copy (const(char)[] src, const(char)[] dst)
1621 {
1622         char[512] tmp1 = void;
1623         char[512] tmp2 = void;
1624         FS.copy (FS.strz(src, tmp1), FS.strz(dst, tmp2));
1625 }
1626 
1627 /*******************************************************************************
1628 
1629         Provides foreach support via a fruct, as in
1630         ---
1631         foreach (info; children("myfolder"))
1632             ...
1633         ---
1634 
1635         Each path and filename is passed to the foreach
1636         delegate, along with the path prefix and whether
1637         the entry is a folder or not. The info construct
1638         exposes the following attributes:
1639         ---
1640         char[]  path
1641         char[]  name
1642         ulong   bytes
1643         bool    folder
1644         ---
1645 
1646         Argument 'all' controls whether hidden and system
1647         files are included - these are ignored by default.
1648 
1649 *******************************************************************************/
1650 
1651 FS.Listing children (const(char)[] path, bool all=false)
1652 {
1653         return FS.Listing (path, all);
1654 }
1655 
1656 /*******************************************************************************
1657 
1658         Collate all files and folders from the given path whose name matches
1659         the given pattern. Folders will be traversed where recurse is enabled,
1660         and a set of matching names is returned as filepaths (including those
1661         folders which match the pattern.)
1662 
1663         Note: Allocates memory for returned paths.
1664 
1665         Since: 0.99.9
1666 
1667 *******************************************************************************/
1668 
1669 char[][] collate (const(char)[] path, const(char)[] pattern, bool recurse=false)
1670 {
1671         char[][] list;
1672 
1673         foreach (info; children (path))
1674                 {
1675                 if (info.folder && recurse)
1676                     list ~= collate (join(info.path, info.name), pattern, true);
1677 
1678                 if (patternMatch (info.name, pattern))
1679                     list ~= join (info.path, info.name);
1680                 }
1681         return list;
1682 }
1683 
1684 /*******************************************************************************
1685 
1686         Join a set of path specs together. A path separator is
1687         potentially inserted between each of the segments.
1688 
1689         Note: May allocate memory.
1690 
1691 *******************************************************************************/
1692 
1693 char[] join (const(char[])[] paths...)
1694 {
1695         return FS.join (paths);
1696 }
1697 
1698 /*******************************************************************************
1699 
1700         Convert path separators to a standard format, using '/' as
1701         the path separator. This is compatible with Uri and all of
1702         the contemporary O/S which Tango supports. Known exceptions
1703         include the Windows command-line processor, which considers
1704         '/' characters to be switches instead. Use the native()
1705         method to support that.
1706 
1707         Note: Mutates the provided path.
1708 
1709 *******************************************************************************/
1710 
1711 char[] standard (char[] path)
1712 {
1713         return replace (path, '\\', '/');
1714 }
1715 
1716 /*******************************************************************************
1717 
1718         Convert to native O/S path separators where that is required,
1719         such as when dealing with the Windows command-line.
1720 
1721         Note: Mutates the provided path. Use this pattern to obtain a
1722         copy instead: native(path.dup);
1723 
1724 *******************************************************************************/
1725 
1726 char[] native (char[] path)
1727 {
1728         version (Win32)
1729                  replace (path, '/', '\\');
1730         return path;
1731 }
1732 
1733 /*******************************************************************************
1734 
1735         Returns a path representing the parent of this one, with a special
1736         case concerning a trailing '/':
1737         $(UL
1738           $(LI normal:  /x/y/z => /x/y)
1739           $(LI normal:  /x/y/  => /x/y)
1740           $(LI special: /x/y/  => /x)
1741           $(LI normal:  /x     => /)
1742           $(LI normal:  /      => empty))
1743 
1744         The result can be split via parse().
1745 
1746 *******************************************************************************/
1747 
1748 inout(char)[] parent (inout(char)[] path)
1749 {
1750         return pop (FS.stripped (path));
1751 }
1752 
1753 /*******************************************************************************
1754 
1755         Returns a path representing the parent of this one:
1756         $(UL
1757           $(LI normal:  /x/y/z => /x/y)
1758           $(LI normal:  /x/y/  => /x/y)
1759           $(LI normal:  /x     => /)
1760           $(LI normal:  /      => empty))
1761 
1762         The result can be split via parse().
1763 
1764 *******************************************************************************/
1765 
1766 inout(char)[] pop (inout(char)[] path)
1767 {
1768         int i = cast(int)path.length;
1769         while (i && path[--i] != '/') {}
1770         return path [0..i];
1771 }
1772 
1773 /*******************************************************************************
1774 
1775         Break a path into "head" and "tail" components. For example:
1776         $(UL
1777           $(LI "/a/b/c" -> "/a","b/c")
1778           $(LI "a/b/c" -> "a","b/c"))
1779 
1780 *******************************************************************************/
1781 
1782 inout(char)[] split (inout(char)[] path, out inout(char)[] head, out inout(char)[] tail)
1783 {
1784         head = path;
1785         if (path.length > 1)
1786             foreach (i, char c; path[1..$])
1787                      if (c is '/')
1788                         {
1789                         head = path [0 .. i+1];
1790                         tail = path [i+2 .. $];
1791                         break;
1792                         }
1793         return path;
1794 }
1795 
1796 /*******************************************************************************
1797 
1798         Replace all path 'from' instances with 'to', in place (overwrites
1799         the provided path).
1800 
1801 *******************************************************************************/
1802 
1803 char[] replace (char[] path, char from, char to)
1804 {
1805         foreach (ref char c; path)
1806                  if (c is from)
1807                      c = to;
1808         return path;
1809 }
1810 
1811 /*******************************************************************************
1812 
1813         Parse a path into its constituent components.
1814 
1815         Note that the provided path is sliced, not duplicated.
1816 
1817 *******************************************************************************/
1818 
1819 PathParser!(char_t) parse(char_t) (char_t[] path)
1820 {
1821         PathParser!(char_t) p;
1822 
1823         p.parse (path);
1824         return p;
1825 }
1826 
1827 /*******************************************************************************
1828 
1829 *******************************************************************************/
1830 
1831 debug(UnitTest)
1832 {
1833         unittest
1834         {
1835                 auto p = parse ("/foo/bar/file.ext".dup);
1836                 assert (p.equals("/foo/bar/file.ext"));
1837                 assert (p.folder == "/foo/bar/");
1838                 assert (p.path == "/foo/bar/");
1839                 assert (p.file == "file.ext");
1840                 assert (p.name == "file");
1841                 assert (p.suffix == ".ext");
1842                 assert (p.ext == "ext");
1843                 assert (p.isChild == true);
1844                 assert (p.isEmpty == false);
1845                 assert (p.isAbsolute == true);
1846         }
1847 }
1848 
1849 
1850 /******************************************************************************
1851 
1852         Matches a pattern against a filename.
1853 
1854         Some characters of pattern have special a meaning (they are
1855         $(EM meta-characters)) and $(B can't) be escaped. These are:
1856 
1857         $(TABLE
1858           $(TR
1859             $(TD $(B *))
1860             $(TD Matches 0 or more instances of any character.))
1861           $(TR
1862             $(TD $(B ?))
1863             $(TD Matches exactly one instances of any character.))
1864           $(TR
1865             $(TD $(B [)$(EM chars)$(B ]))
1866             $(TD Matches one instance of any character that appears
1867           between the brackets.))
1868           $(TR
1869             $(TD $(B [!)$(EM chars)$(B ]))
1870             $(TD Matches one instance of any character that does not appear
1871           between the brackets after the exclamation mark.))
1872         )
1873 
1874         Internally individual character comparisons are done calling
1875         charMatch(), so its rules apply here too. Note that path
1876         separators and dots don't stop a meta-character from matching
1877         further portions of the filename.
1878 
1879         Returns: true if pattern matches filename, false otherwise.
1880 
1881         Throws: Nothing.
1882         -----
1883         version (Win32)
1884         {
1885           patternMatch("foo.bar", "*"); // => true
1886           patternMatch(r"foo/foo\bar", "f*b*r"); // => true
1887           patternMatch("foo.bar", "f?bar"); // => false
1888           patternMatch("Goo.bar", "[fg]???bar"); // => true
1889           patternMatch(r"d:\foo\bar", "d*foo?bar"); // => true
1890         }
1891         version (Posix)
1892         {
1893           patternMatch("Go*.bar", "[fg]???bar"); // => false
1894           patternMatch("/foo*home/bar", "?foo*bar"); // => true
1895           patternMatch("foobar", "foo?bar"); // => true
1896         }
1897         -----
1898 
1899 ******************************************************************************/
1900 
1901 bool patternMatch (const(char)[] filename, const(char)[] pattern)
1902 in
1903 {
1904         // Verify that pattern[] is valid
1905         bool inbracket = false;
1906         for (auto i=0; i < pattern.length; i++)
1907             {
1908             switch (pattern[i])
1909                    {
1910                    case '[':
1911                         assert(!inbracket);
1912                         inbracket = true;
1913                         break;
1914                    case ']':
1915                         assert(inbracket);
1916                         inbracket = false;
1917                         break;
1918                    default:
1919                         break;
1920                    }
1921             }
1922 }
1923 body
1924 {
1925         int pi;
1926         int ni;
1927         char pc;
1928         char nc;
1929         int j;
1930         int not;
1931         int anymatch;
1932 
1933         bool charMatch (char c1, char c2)
1934         {
1935         version (Win32)
1936                 {
1937                 if (c1 != c2)
1938                     return ((c1 >= 'a' && c1 <= 'z') ? c1 - ('a' - 'A') : c1) ==
1939                            ((c2 >= 'a' && c2 <= 'z') ? c2 - ('a' - 'A') : c2);
1940                 return true;
1941                 }
1942         version (Posix)
1943                  return c1 == c2;
1944         }
1945 
1946         ni = 0;
1947         for (pi = 0; pi < pattern.length; pi++)
1948             {
1949             pc = pattern [pi];
1950             switch (pc)
1951                    {
1952                    case '*':
1953                         if (pi + 1 == pattern.length)
1954                             goto match;
1955                         for (j = ni; j < filename.length; j++)
1956                             {
1957                             if (patternMatch(filename[j .. filename.length],
1958                                 pattern[pi + 1 .. pattern.length]))
1959                                goto match;
1960                             }
1961                         goto nomatch;
1962 
1963                    case '?':
1964                         if (ni == filename.length)
1965                             goto nomatch;
1966                         ni++;
1967                         break;
1968 
1969                    case '[':
1970                         if (ni == filename.length)
1971                             goto nomatch;
1972                         nc = filename[ni];
1973                         ni++;
1974                         not = 0;
1975                         pi++;
1976                         if (pattern[pi] == '!')
1977                            {
1978                            not = 1;
1979                            pi++;
1980                            }
1981                         anymatch = 0;
1982                         while (1)
1983                               {
1984                               pc = pattern[pi];
1985                               if (pc == ']')
1986                                   break;
1987                               if (!anymatch && charMatch(nc, pc))
1988                                    anymatch = 1;
1989                               pi++;
1990                               }
1991                         if (!(anymatch ^ not))
1992                               goto nomatch;
1993                         break;
1994 
1995                    default:
1996                         if (ni == filename.length)
1997                             goto nomatch;
1998                         nc = filename[ni];
1999                         if (!charMatch(pc, nc))
2000                              goto nomatch;
2001                         ni++;
2002                         break;
2003                    }
2004             }
2005         if (ni < filename.length)
2006             goto nomatch;
2007 
2008         match:
2009             return true;
2010 
2011         nomatch:
2012             return false;
2013 }
2014 
2015 /*******************************************************************************
2016 
2017 *******************************************************************************/
2018 
2019 debug (UnitTest)
2020 {
2021         unittest
2022         {
2023         version (Win32)
2024         assert(patternMatch("foo", "Foo"));
2025         version (Posix)
2026         assert(!patternMatch("foo", "Foo"));
2027 
2028         assert(patternMatch("foo", "*"));
2029         assert(patternMatch("foo.bar", "*"));
2030         assert(patternMatch("foo.bar", "*.*"));
2031         assert(patternMatch("foo.bar", "foo*"));
2032         assert(patternMatch("foo.bar", "f*bar"));
2033         assert(patternMatch("foo.bar", "f*b*r"));
2034         assert(patternMatch("foo.bar", "f???bar"));
2035         assert(patternMatch("foo.bar", "[fg]???bar"));
2036         assert(patternMatch("foo.bar", "[!gh]*bar"));
2037 
2038         assert(!patternMatch("foo", "bar"));
2039         assert(!patternMatch("foo", "*.*"));
2040         assert(!patternMatch("foo.bar", "f*baz"));
2041         assert(!patternMatch("foo.bar", "f*b*x"));
2042         assert(!patternMatch("foo.bar", "[gh]???bar"));
2043         assert(!patternMatch("foo.bar", "[!fg]*bar"));
2044         assert(!patternMatch("foo.bar", "[fg]???baz"));
2045         }
2046 }
2047 
2048 
2049 /*******************************************************************************
2050 
2051         Normalizes a path component.
2052         $(UL
2053           $(LI $(B .) segments are removed)
2054           $(LI &lt;segment&gt;$(B /..) are removed))
2055 
2056         Multiple consecutive forward slashes are replaced with a single
2057         forward slash. On Windows, \ will be converted to / prior to any
2058         normalization.
2059 
2060         Note that any number of .. segments at the front is ignored,
2061         unless it is an absolute path, in which case they are removed.
2062 
2063         The input path is copied into either the provided buffer, or a heap
2064         allocated array if no buffer was provided. Normalization modifies
2065         this copy before returning the relevant slice.
2066         -----
2067         normalize("/home/foo/./bar/../../john/doe"); // => "/home/john/doe"
2068         -----
2069 
2070         Note: Allocates memory.
2071 
2072 *******************************************************************************/
2073 
2074 char[] normalize (const(char)[] in_path, char[] buf = null)
2075 {
2076         char[] path;            // Mutable path
2077         size_t  idx;            // Current position
2078         size_t  moveTo;         // Position to move
2079         bool    isAbsolute;     // Whether the path is absolute
2080         enum    {NodeStackLength = 64}
2081 
2082         // Starting positions of regular path segments are pushed
2083         // on this stack to avoid backward scanning when .. segments
2084         // are encountered
2085         size_t[NodeStackLength] nodeStack;
2086         size_t nodeStackTop;
2087 
2088         // Moves the path tail starting at the current position to
2089         // moveTo. Then sets the current position to moveTo.
2090         void move ()
2091         {
2092                 auto len = path.length - idx;
2093                 memmove (path.ptr + moveTo, path.ptr + idx, len);
2094                 path = path[0..moveTo + len];
2095                 idx = moveTo;
2096         }
2097 
2098         // Checks if the character at the current position is a
2099         // separator. If true, normalizes the separator to '/' on
2100         // Windows and advances the current position to the next
2101         // character.
2102         bool isSep (ref size_t i)
2103         {
2104                 char c = path[i];
2105                 version (Windows)
2106                         {
2107                         if (c == '\\')
2108                                 path[i] = '/';
2109                         else if (c != '/')
2110                                 return false;
2111                         }
2112                      else
2113                         {
2114                         if (c != '/')
2115                                 return false;
2116                         }
2117                 i++;
2118                 return true;
2119         }
2120 
2121         if (buf is null)
2122             path = in_path.dup;
2123         else
2124             path = buf[0..in_path.length] = in_path[];
2125 
2126         version (Windows)
2127         {
2128                 // Skip Windows drive specifiers
2129                 if (path.length >= 2 && path[1] == ':')
2130                    {
2131                    auto c = path[0];
2132 
2133                    if (c >= 'a' && c <= 'z')
2134                       {
2135                       path[0] = cast(char)(c - 32);
2136                       idx = 2;
2137                       }
2138                    else
2139                       if (c >= 'a' && c <= 'z' || c >= 'A' && c <= 'Z')
2140                           idx = 2;
2141                    }
2142         }
2143 
2144         if (idx == path.length)
2145             return path;
2146 
2147         moveTo = idx;
2148         if (isSep(idx))
2149            {
2150            moveTo++; // preserve root separator.
2151            isAbsolute = true;
2152            }
2153 
2154         while (idx < path.length)
2155               {
2156               // Skip duplicate separators
2157               if (isSep(idx))
2158                   continue;
2159 
2160               if (path[idx] == '.')
2161                  {
2162                  // leave the current position at the start of
2163                  // the segment
2164                  auto i = idx + 1;
2165                  if (i < path.length && path[i] == '.')
2166                     {
2167                     i++;
2168                     if (i == path.length || isSep(i))
2169                        {
2170                        // It is a '..' segment. If the stack is not
2171                        // empty, set moveTo and the current position
2172                        // to the start position of the last found
2173                        // regular segment
2174                        if (nodeStackTop > 0)
2175                            moveTo = nodeStack[--nodeStackTop];
2176 
2177                        // If no regular segment start positions on the
2178                        // stack, drop the .. segment if it is absolute
2179                        // path or, otherwise, advance moveTo and the
2180                        // current position to the character after the
2181                        // '..' segment
2182                        else
2183                           if (!isAbsolute)
2184                              {
2185                              if (moveTo != idx)
2186                                 {
2187                                 i -= idx - moveTo;
2188                                 move();
2189                                 }
2190                              moveTo = i;
2191                              }
2192 
2193                        idx = i;
2194                        continue;
2195                        }
2196                     }
2197 
2198                  // If it is '.' segment, skip it.
2199                  if (i == path.length || isSep(i))
2200                     {
2201                     idx = i;
2202                     continue;
2203                     }
2204                  }
2205 
2206               // Remove excessive '/', '.' and/or '..' preceeding the
2207               // segment
2208               if (moveTo != idx)
2209                   move();
2210 
2211               // Push the start position of the regular segment on the
2212               // stack
2213               assert (nodeStackTop < NodeStackLength);
2214               nodeStack[nodeStackTop++] = idx;
2215 
2216               // Skip the regular segment and set moveTo to the position
2217               // after the segment (including the trailing '/' if present)
2218               for (; idx < path.length && !isSep(idx); idx++)
2219                   {}
2220               moveTo = idx;
2221               }
2222 
2223         if (moveTo != idx)
2224             move();
2225         return path;
2226 }
2227 
2228 /*******************************************************************************
2229 
2230 *******************************************************************************/
2231 
2232 debug (UnitTest)
2233 {
2234         unittest
2235         {
2236         assert (normalize ("") == "");
2237         assert (normalize ("/home/../john/../.tango/.htaccess") == "/.tango/.htaccess");
2238         assert (normalize ("/home/../john/../.tango/foo.conf") == "/.tango/foo.conf");
2239         assert (normalize ("/home/john/.tango/foo.conf") == "/home/john/.tango/foo.conf");
2240         assert (normalize ("/foo/bar/.htaccess") == "/foo/bar/.htaccess");
2241         assert (normalize ("foo/bar/././.") == "foo/bar/");
2242         assert (normalize ("././foo/././././bar") == "foo/bar");
2243         assert (normalize ("/foo/../john") == "/john");
2244         assert (normalize ("foo/../john") == "john");
2245         assert (normalize ("foo/bar/..") == "foo/");
2246         assert (normalize ("foo/bar/../john") == "foo/john");
2247         assert (normalize ("foo/bar/doe/../../john") == "foo/john");
2248         assert (normalize ("foo/bar/doe/../../john/../bar") == "foo/bar");
2249         assert (normalize ("./foo/bar/doe") == "foo/bar/doe");
2250         assert (normalize ("./foo/bar/doe/../../john/../bar") == "foo/bar");
2251         assert (normalize ("./foo/bar/../../john/../bar") == "bar");
2252         assert (normalize ("foo/bar/./doe/../../john") == "foo/john");
2253         assert (normalize ("../../foo/bar") == "../../foo/bar");
2254         assert (normalize ("../../../foo/bar") == "../../../foo/bar");
2255         assert (normalize ("d/") == "d/");
2256         assert (normalize ("/home/john/./foo/bar.txt") == "/home/john/foo/bar.txt");
2257         assert (normalize ("/home//john") == "/home/john");
2258 
2259         assert (normalize("/../../bar/") == "/bar/");
2260         assert (normalize("/../../bar/../baz/./") == "/baz/");
2261         assert (normalize("/../../bar/boo/../baz/.bar/.") == "/bar/baz/.bar/");
2262         assert (normalize("../..///.///bar/..//..//baz/.//boo/..") == "../../../baz/");
2263         assert (normalize("./bar/./..boo/./..bar././/") == "bar/..boo/..bar./");
2264         assert (normalize("/bar/..") == "/");
2265         assert (normalize("bar/") == "bar/");
2266         assert (normalize(".../") == ".../");
2267         assert (normalize("///../foo") == "/foo");
2268         assert (normalize("./foo") == "foo");
2269         auto buf = new char[100];
2270         auto ret = normalize("foo/bar/./baz", buf);
2271         assert (ret.ptr == buf.ptr);
2272         assert (ret == "foo/bar/baz");
2273 
2274         version (Windows)
2275                 {
2276                 assert (normalize ("\\foo\\..\\john") == "/john");
2277                 assert (normalize ("foo\\..\\john") == "john");
2278                 assert (normalize ("foo\\bar\\..") == "foo/");
2279                 assert (normalize ("foo\\bar\\..\\john") == "foo/john");
2280                 assert (normalize ("foo\\bar\\doe\\..\\..\\john") == "foo/john");
2281                 assert (normalize ("foo\\bar\\doe\\..\\..\\john\\..\\bar") == "foo/bar");
2282                 assert (normalize (".\\foo\\bar\\doe") == "foo/bar/doe");
2283                 assert (normalize (".\\foo\\bar\\doe\\..\\..\\john\\..\\bar") == "foo/bar");
2284                 assert (normalize (".\\foo\\bar\\..\\..\\john\\..\\bar") == "bar");
2285                 assert (normalize ("foo\\bar\\.\\doe\\..\\..\\john") == "foo/john");
2286                 assert (normalize ("..\\..\\foo\\bar") == "../../foo/bar");
2287                 assert (normalize ("..\\..\\..\\foo\\bar") == "../../../foo/bar");
2288                 assert (normalize(r"C:") == "C:");
2289                 assert (normalize(r"C") == "C");
2290                 assert (normalize(r"c:\") == "C:/");
2291                 assert (normalize(r"C:\..\.\..\..\") == "C:/");
2292                 assert (normalize(r"c:..\.\boo\") == "C:../boo/");
2293                 assert (normalize(r"C:..\..\boo\foo\..\.\..\..\bar") == "C:../../../bar");
2294                 assert (normalize(r"C:boo\..") == "C:");
2295                 }
2296         }
2297 }
2298 
2299 
2300 /*******************************************************************************
2301 
2302 *******************************************************************************/
2303 
2304 debug (Path)
2305 {
2306         import tango.io.Stdout;
2307 
2308         void main()
2309         {
2310                 foreach (file; collate (".", "*.d", true))
2311                          Stdout (file).newline;
2312         }
2313 }