1 /*******************************************************************************
2 
3         A more direct route to the file-system than FilePath. Use this
4         if you don't need path editing features. For example, if all you
5         want is to check some path exists, using this module would likely
6         be more convenient than FilePath:
7         ---
8         if (exists ("some/file/path"))
9             ...
10         ---
11 
12         These functions may be less efficient than FilePath because they
13         generally attach a null to the filename for each underlying O/S
14         call. Use Path when you need pedestrian access to the file-system,
15         and are not manipulating the path components. Use FilePath where
16         path-editing or mutation is desired.
17 
18         We encourage the use of "named import" with this module, such as:
19         ---
20         import Path = ocean.io.Path;
21 
22         if (Path.exists ("some/file/path"))
23             ...
24         ---
25 
26         Also residing here is a lightweight path-parser, which splits a
27         filepath into constituent components. FilePath is based upon the
28         same PathParser:
29         ---
30         auto p = Path.parse ("some/file/path");
31         auto path = p.path;
32         auto name = p.name;
33         auto suffix = p.suffix;
34         ...
35         ---
36 
37         Path normalization and pattern-matching is also hosted here via
38         the normalize() and pattern() functions. See the doc towards the
39         end of this module.
40 
41         Copyright:
42             Copyright (c) 2008 Kris Bell.
43             Normalization & Patterns copyright (c) 2006-2009 Max Samukha,
44                 Thomas Kühne, Grzegorz Adam Hankiewicz
45             Some parts copyright (c) 2009-2016 dunnhumby Germany GmbH.
46             All rights reserved.
47 
48         License:
49             Tango Dual License: 3-Clause BSD License / Academic Free License v3.0.
50             See LICENSE_TANGO.txt for details.
51 
52         Version:
53             Mar 2008: Initial version$(BR)
54             Oct 2009: Added PathUtil code
55 
56 *******************************************************************************/
57 
58 module ocean.io.Path;
59 
60 public import ocean.core.ExceptionDefinitions : IOException, IllegalArgumentException;
61 import ocean.core.Verify;
62 import ocean.io.model.IFile : FileConst, FileInfo;
63 import ocean.sys.Common;
64 public import ocean.time.Time : Time, TimeSpan;
65 import ocean.transition;
66 
67 import core.stdc.string : memmove;
68 
69 version (unittest) import ocean.core.Test;
70 
71 import core.stdc.stdio;
72 import core.stdc.string;
73 import core.sys.posix.utime;
74 import core.sys.posix.dirent;
75 
76 
77 /*******************************************************************************
78 
79         Wraps the O/S specific calls with a D API. Note that these accept
80         null-terminated strings only, which is why it's not public. We need
81         this declared first to avoid forward-reference issues.
82 
83 *******************************************************************************/
84 
85 package struct FS
86 {
87         /***********************************************************************
88 
89                 TimeStamp information. Accurate to whatever the F/S supports.
90 
91         ***********************************************************************/
92 
93         struct Stamps
94         {
95                 Time created;  /// Time created.
96                 Time accessed; /// Last time accessed.
97                 Time modified; /// Last time modified.
98         }
99 
100         /***********************************************************************
101 
102                 Some fruct glue for directory listings.
103 
104         ***********************************************************************/
105 
106         struct Listing
107         {
108                 cstring folder;
109                 bool   allFiles;
110 
111                 int opApply (scope int delegate(ref FileInfo) dg)
112                 {
113                         char[256] tmp = void;
114                         auto path = strz (folder, tmp);
115 
116                         return list (path, dg, allFiles);
117                 }
118         }
119 
120         /***********************************************************************
121 
122                 Throw an exception using the last known error.
123 
124         ***********************************************************************/
125 
126         static void exception (cstring filename)
127         {
128                 exception (filename[0..$-1] ~ ": ", SysError.lastMsg);
129         }
130 
131         /***********************************************************************
132 
133                 Throw an IO exception.
134 
135         ***********************************************************************/
136 
137         static void exception (cstring prefix, cstring error)
138         {
139                 throw new IOException (idup(prefix ~ error));
140         }
141 
142         /***********************************************************************
143 
144                 Return an adjusted path such that non-empty instances always
145                 have a trailing separator.
146 
147                 Note: Allocates memory where path is not already terminated.
148 
149         ***********************************************************************/
150 
151         static cstring padded (cstring path, char c = '/')
152         {
153                 if (path.length && path[$-1] != c)
154                     path = path ~ c;
155                 return path;
156         }
157 
158         /***********************************************************************
159 
160                 Return an adjusted path such that non-empty instances always
161                 have a leading separator.
162 
163                 Note: Allocates memory where path is not already terminated.
164 
165         ***********************************************************************/
166 
167         static cstring paddedLeading (cstring path, char c = '/')
168         {
169                 if (path.length && path[0] != c)
170                     path = c ~ path;
171                 return path;
172         }
173 
174         /***********************************************************************
175 
176                 Return an adjusted path such that non-empty instances do not
177                 have a trailing separator.
178 
179         ***********************************************************************/
180 
181         static cstring stripped (cstring path, char c = '/')
182         {
183                 if (path.length && path[$-1] is c)
184                     path = path [0 .. $-1];
185                 return path;
186         }
187 
188         /***********************************************************************
189 
190                 Join a set of path specs together. A path separator is
191                 potentially inserted between each of the segments.
192 
193                 Note: Allocates memory.
194 
195         ***********************************************************************/
196 
197         static mstring join (const(char[])[] paths...)
198         {
199                 mstring result;
200 
201                 if (paths.length)
202                 {
203                     result ~= stripped(paths[0]);
204 
205                     foreach (path; paths[1 .. $-1])
206                         result ~= paddedLeading (stripped(path));
207 
208                     result ~= paddedLeading(paths[$-1]);
209 
210                    return result;
211                 }
212 
213                 return null;
214         }
215 
216         /***********************************************************************
217 
218                 Append a terminating null onto a string, cheaply where
219                 feasible.
220 
221                 Note: Allocates memory where the dst is too small.
222 
223         ***********************************************************************/
224 
225         static mstring strz (cstring src, mstring dst)
226         {
227                 auto i = src.length + 1;
228                 if (dst.length < i)
229                     dst.length = i;
230                 dst [0 .. i-1] = src;
231                 dst[i-1] = 0;
232                 return dst [0 .. i];
233         }
234 
235     /***************************************************************************
236 
237         Get info about this path.
238 
239     ***************************************************************************/
240 
241     private static uint getInfo (cstring name, ref stat_t stats)
242     {
243         if (posix.stat (name.ptr, &stats))
244             exception (name);
245 
246         return stats.st_mode;
247     }
248 
249     /***************************************************************************
250 
251         Returns:
252             whether the file or path exists.
253 
254     ***************************************************************************/
255 
256     static bool exists (cstring name)
257     {
258         stat_t stats = void;
259         return posix.stat (name.ptr, &stats) is 0;
260     }
261 
262     /***************************************************************************
263 
264         Returns:
265             the file length (in bytes.)
266 
267     ***************************************************************************/
268 
269     static ulong fileSize (cstring name)
270     {
271         stat_t stats = void;
272 
273         getInfo (name, stats);
274         return cast(ulong) stats.st_size;
275     }
276 
277     /***************************************************************************
278 
279         Is this file writable?
280 
281     ***************************************************************************/
282 
283     static bool isWritable (cstring name)
284     {
285         return posix.access(name.ptr, W_OK) == 0;
286     }
287 
288     /***************************************************************************
289 
290         Returns:
291             true if the file exist and is readable for the effective uid.
292 
293     ***************************************************************************/
294 
295     static bool isReadable (cstring name)
296     {
297         return posix.access(name.ptr, R_OK) == 0;
298     }
299 
300     /***************************************************************************
301 
302         Is this file actually a folder/directory?
303 
304     ***************************************************************************/
305 
306     static bool isFolder (cstring name)
307     {
308         stat_t stats = void;
309 
310         return (getInfo(name, stats) & S_IFMT) is S_IFDIR;
311     }
312 
313     /***************************************************************************
314 
315         Is this a normal file?
316 
317     ***************************************************************************/
318 
319     static bool isFile (cstring name)
320     {
321         stat_t stats = void;
322 
323         return (getInfo(name, stats) & S_IFMT) is S_IFREG;
324     }
325 
326     /***************************************************************************
327 
328         Return timestamp information.
329 
330         Timestamps are returns in a format dictated by the file-system.
331         For example NTFS keeps UTC time, while FAT timestamps are based
332         on the local time.
333 
334     ***************************************************************************/
335 
336     static Stamps timeStamps (cstring name)
337     {
338         static Time convert (typeof(stat_t.st_mtime) secs)
339         {
340             return Time.epoch1970 +
341                 TimeSpan.fromSeconds(secs);
342         }
343 
344         stat_t stats = void;
345         Stamps time  = void;
346 
347         getInfo (name, stats);
348 
349         time.modified = convert (stats.st_mtime);
350         time.accessed = convert (stats.st_atime);
351         time.created  = convert (stats.st_ctime);
352         return time;
353     }
354 
355     /***************************************************************************
356 
357         Set the accessed and modified timestamps of the specified file.
358 
359     ***************************************************************************/
360 
361     static void timeStamps (cstring name, Time accessed, Time modified)
362     {
363         utimbuf time = void;
364         time.actime = (accessed - Time.epoch1970).seconds;
365         time.modtime = (modified - Time.epoch1970).seconds;
366         if (utime (name.ptr, &time) is -1)
367             exception (name);
368     }
369 
370     /***************************************************************************
371 
372         Transfer the content of another file to this one.
373 
374         Returns:
375             a reference to this class on success
376 
377         Throws:
378             `IOException` upon failure
379 
380          Note:
381             Allocates a memory buffer.
382 
383     ***************************************************************************/
384 
385     static void copy (cstring source, mstring dest)
386     {
387         auto src = posix.open (source.ptr, O_RDONLY, Octal!("640"));
388         scope (exit)
389             if (src != -1)
390                 posix.close (src);
391 
392         auto dst = posix.open (dest.ptr, O_CREAT | O_RDWR, Octal!("660"));
393         scope (exit)
394             if (dst != -1)
395                 posix.close (dst);
396 
397         if (src is -1 || dst is -1)
398             exception (source);
399 
400         // copy content
401         ubyte[] buf = new ubyte [16 * 1024];
402         auto read = posix.read (src, buf.ptr, buf.length);
403         while (read > 0)
404         {
405             auto p = buf.ptr;
406             do {
407                 auto written = posix.write (dst, p, read);
408                 p += written;
409                 read -= written;
410                 if (written is -1)
411                     exception (dest);
412             } while (read > 0);
413             read = posix.read (src, buf.ptr, buf.length);
414         }
415         if (read is -1)
416             exception (source);
417 
418         // copy timestamps
419         stat_t stats;
420         if (posix.stat (source.ptr, &stats))
421             exception (source);
422 
423         utimbuf utim;
424         utim.actime = stats.st_atime;
425         utim.modtime = stats.st_mtime;
426         if (utime (dest.ptr, &utim) is -1)
427             exception (dest);
428     }
429 
430     /***************************************************************************
431 
432         Remove the file/directory from the file-system.
433 
434         Returns:
435             `true` on success - `false` otherwise.
436 
437     ***************************************************************************/
438 
439     static bool remove (cstring name)
440     {
441         return core.stdc.stdio.remove(name.ptr) != -1;
442     }
443 
444     /***************************************************************************
445 
446         Change the name or location of a file/directory.
447 
448     ***************************************************************************/
449 
450     static void rename (cstring src, cstring dst)
451     {
452         if (core.stdc.stdio.rename(src.ptr, dst.ptr) is -1)
453             exception(src);
454     }
455 
456     /***************************************************************************
457 
458         Create a new file.
459 
460         Params:
461             mode = mode for the new file (defaults to 0660)
462 
463     ***************************************************************************/
464 
465     static void createFile (cstring name, mode_t mode = Octal!("660"))
466     {
467         int fd;
468 
469         fd = posix.open (name.ptr, O_CREAT | O_WRONLY | O_TRUNC,
470                          mode);
471         if (fd is -1)
472             exception (name);
473 
474         if (posix.close(fd) is -1)
475             exception (name);
476     }
477 
478     /***************************************************************************
479 
480         Create a new directory.
481 
482         Params:
483             mode = mode for the new directory (defaults to 0777)
484 
485     *************************************************************************/
486 
487     static void createFolder (cstring name, mode_t mode = Octal!("777"))
488     {
489         if (posix.mkdir (name.ptr, mode))
490             exception (name);
491     }
492 
493     /***************************************************************************
494 
495         List the set of filenames within this folder.
496 
497         Each path and filename is passed to the provided delegate,
498         along with the path prefix and whether the entry is a folder or not.
499 
500         Note: Allocates and reuses a small memory buffer.
501 
502     ***************************************************************************/
503 
504     static int list (cstring folder, scope int delegate(ref FileInfo) dg, bool all=false)
505     {
506         int             ret;
507         DIR*            dir;
508         dirent          entry;
509         dirent*         pentry;
510         stat_t          sbuf;
511         mstring          prefix;
512         mstring          sfnbuf;
513 
514         dir = core.sys.posix.dirent.opendir (folder.ptr);
515         if (! dir)
516             return ret;
517 
518         scope (exit)
519         {
520             import core.memory;
521             core.sys.posix.dirent.closedir (dir);
522             GC.free(sfnbuf.ptr);
523 
524             // only delete when we dupped it
525             if (folder[$-2] != '/')
526                 GC.free(prefix.ptr);
527         }
528 
529         // ensure a trailing '/' is present
530         if (folder[$-2] != '/')
531         {
532             prefix = folder.dup;
533             prefix[$-1] = '/';
534         }
535         else
536             prefix = folder[0 .. $-1].dup;
537 
538         // prepare our filename buffer
539         sfnbuf = new char[prefix.length + 256];
540         sfnbuf[0 .. prefix.length] = prefix[];
541 
542         while (true)
543         {
544             // pentry is null at end of listing, or on an error
545             readdir_r (dir, &entry, &pentry);
546             if (pentry is null)
547                 break;
548 
549             auto len = core.stdc..string.strlen (entry.d_name.ptr);
550             auto str = entry.d_name.ptr [0 .. len];
551             ++len;  // include the null
552 
553             // resize the buffer as necessary ...
554             if (sfnbuf.length < prefix.length + len)
555                 sfnbuf.length = prefix.length + len;
556 
557             sfnbuf [prefix.length .. prefix.length + len]
558                 = entry.d_name.ptr [0 .. len];
559 
560             // skip "..." names
561             if (str.length > 3 || str != "..."[0 .. str.length])
562             {
563                 FileInfo info = void;
564                 info.bytes  = 0;
565                 info.name   = idup(str);
566                 info.path   = idup(prefix);
567                 info.hidden = str[0] is '.';
568                 info.folder = info.system = false;
569 
570                 if (! stat (sfnbuf.ptr, &sbuf))
571                 {
572                     info.folder = (sbuf.st_mode & S_IFDIR) != 0;
573                     if (info.folder is false)
574                     {
575                         if ((sbuf.st_mode & S_IFREG) is 0)
576                             info.system = true;
577                         else
578                             info.bytes = cast(ulong) sbuf.st_size;
579                     }
580                 }
581                 if (all || (info.hidden | info.system) is false)
582                     if ((ret = dg(info)) != 0)
583                         break;
584             }
585         }
586         return ret;
587         assert(false);
588     }
589 }
590 
591 
592 /*******************************************************************************
593 
594         Parses a file path.
595 
596         File paths containing non-ansi characters should be UTF-8 encoded.
597         Supporting Unicode in this manner was deemed to be more suitable
598         than providing a wchar version of PathParser, and is both consistent
599         & compatible with the approach taken with the Uri class.
600 
601         Note that patterns of adjacent '.' separators are treated specially
602         in that they will be assigned to the name where there is no distinct
603         suffix. In addition, a '.' at the start of a name signifies it does
604         not belong to the suffix i.e. ".file" is a name rather than a suffix.
605         Patterns of intermediate '.' characters will otherwise be assigned
606         to the suffix, such that "file....suffix" includes the dots within
607         the suffix itself. See method ext() for a suffix without dots.
608 
609         Note also that normalization of path-separators does *not* occur by
610         default. This means that usage of '\' characters should be explicitly
611         converted beforehand into '/' instead (an exception is thrown in those
612         cases where '\' is present). On-the-fly conversion is avoided because
613         (a) the provided path is considered immutable and (b) we avoid taking
614         a copy of the original path. Module FilePath exists at a higher level,
615         without such contraints.
616 
617 *******************************************************************************/
618 
619 struct PathParser
620 {
621         package mstring  fp;                     // filepath with trailing
622         package int     end_,                   // before any trailing 0
623                         ext_,                   // after rightmost '.'
624                         name_,                  // file/dir name
625                         folder_,                // path before name
626                         suffix_;                // including leftmost '.'
627 
628         /***********************************************************************
629 
630                 Parse the path spec.
631 
632         ***********************************************************************/
633 
634         PathParser parse (mstring path)
635         {
636                 return parse (path, path.length);
637         }
638 
639         /***********************************************************************
640 
641                 Duplicate this path.
642 
643                 Note: Allocates memory for the path content.
644 
645         ***********************************************************************/
646 
647         PathParser dup ()
648         {
649                 auto ret = this;
650                 ret.fp = fp.dup;
651                 return ret;
652         }
653 
654         /***********************************************************************
655 
656                 Return the complete text of this filepath.
657 
658         ***********************************************************************/
659 
660         istring toString ()
661         {
662                 return idup(fp [0 .. end_]);
663         }
664 
665         /***********************************************************************
666 
667                 Return the root of this path. Roots are constructs such as
668                 "C:".
669 
670         ***********************************************************************/
671 
672         mstring root ()
673         {
674                 return fp [0 .. folder_];
675         }
676 
677         /***********************************************************************
678 
679                 Return the file path. Paths may start and end with a "/".
680                 The root path is "/" and an unspecified path is returned as
681                 an empty string. Directory paths may be split such that the
682                 directory name is placed into the 'name' member; directory
683                 paths are treated no differently than file paths.
684 
685         ***********************************************************************/
686 
687         mstring folder ()
688         {
689                 return fp [folder_ .. name_];
690         }
691 
692         /***********************************************************************
693 
694                 Returns a path representing the parent of this one. This
695                 will typically return the current path component, though
696                 with a special case where the name component is empty. In
697                 such cases, the path is scanned for a prior segment:
698                 $(UL
699                   $(LI normal:  /x/y/z => /x/y)
700                   $(LI special: /x/y/  => /x)
701                   $(LI normal:  /x     => /)
702                   $(LI normal:  /      => [empty]))
703 
704                 Note that this returns a path suitable for splitting into
705                 path and name components (there's no trailing separator).
706 
707         ***********************************************************************/
708 
709         cstring parent ()
710         {
711                 auto p = path;
712                 if (name.length is 0)
713                     for (int i=(cast(int) p.length) - 1; --i > 0;)
714                          if (p[i] is FileConst.PathSeparatorChar)
715                             {
716                             p = p[0 .. i];
717                             break;
718                             }
719                 return FS.stripped (p);
720         }
721 
722         /***********************************************************************
723 
724                 Pop the rightmost element off this path, stripping off a
725                 trailing '/' as appropriate:
726                 $(UL
727                   $(LI /x/y/z => /x/y)
728                   $(LI /x/y/  => /x/y  (note trailing '/' in the original))
729                   $(LI /x/y   => /x)
730                   $(LI /x     => /)
731                   $(LI /      => [empty]))
732 
733                 Note that this returns a path suitable for splitting into
734                 path and name components (there's no trailing separator).
735 
736         ***********************************************************************/
737 
738         cstring pop ()
739         {
740                 return FS.stripped (path);
741         }
742 
743         /***********************************************************************
744 
745                 Return the name of this file, or directory.
746 
747         ***********************************************************************/
748 
749         mstring name ()
750         {
751                 return fp [name_ .. suffix_];
752         }
753 
754         /***********************************************************************
755 
756                 Ext is the tail of the filename, rightward of the rightmost
757                 '.' separator e.g. path "foo.bar" has ext "bar". Note that
758                 patterns of adjacent separators are treated specially - for
759                 example, ".." will wind up with no ext at all.
760 
761         ***********************************************************************/
762 
763         mstring ext ()
764         {
765                 auto x = suffix;
766                 if (x.length)
767                 {
768                     if (ext_ is 0)
769                        foreach (c; x)
770                        {
771                            if (c is '.')
772                                ++ext_;
773                            else
774                                break;
775                        }
776                     x = x [ext_ .. $];
777                 }
778                 return x;
779         }
780 
781         /***********************************************************************
782 
783                 Suffix is like ext, but includes the separator e.g. path
784                 "foo.bar" has suffix ".bar".
785 
786         ***********************************************************************/
787 
788         mstring suffix ()
789         {
790                 return fp [suffix_ .. end_];
791         }
792 
793         /***********************************************************************
794 
795                 Return the root + folder combination.
796 
797         ***********************************************************************/
798 
799         mstring path ()
800         {
801                 return fp [0 .. name_];
802         }
803 
804         /***********************************************************************
805 
806                 Return the name + suffix combination.
807 
808         ***********************************************************************/
809 
810         mstring file ()
811         {
812                 return fp [name_ .. end_];
813         }
814 
815         /***********************************************************************
816 
817                 Returns true if this path is *not* relative to the
818                 current working directory.
819 
820         ***********************************************************************/
821 
822         bool isAbsolute ()
823         {
824                 return (folder_ > 0) ||
825                        (folder_ < end_ && fp[folder_] is FileConst.PathSeparatorChar);
826         }
827 
828         /***********************************************************************
829 
830                 Returns true if this FilePath is empty.
831 
832         ***********************************************************************/
833 
834         bool isEmpty ()
835         {
836                 return end_ is 0;
837         }
838 
839         /***********************************************************************
840 
841                 Returns true if this path has a parent. Note that a
842                 parent is defined by the presence of a path-separator in
843                 the path. This means 'foo' within "/foo" is considered a
844                 child of the root.
845 
846         ***********************************************************************/
847 
848         bool isChild ()
849         {
850                 return folder.length > 0;
851         }
852 
853         /***********************************************************************
854 
855                 Does this path equate to the given text? We ignore trailing
856                 path-separators when testing equivalence.
857 
858         ***********************************************************************/
859 
860         equals_t opEquals (cstring s)
861         {
862                 return FS.stripped(s) == FS.stripped(toString);
863         }
864 
865         /***********************************************************************
866 
867             Comparison to another PathParser, to avoid falling back to
868             auto-generated one
869 
870         ***********************************************************************/
871 
872         equals_t opEquals (PathParser rhs)
873         {
874             return FS.stripped(rhs.toString()) == FS.stripped(toString());
875         }
876 
877         /***********************************************************************
878 
879                 Parse the path spec with explicit end point. A '\' is
880                 considered illegal in the path and should be normalized
881                 out before this is invoked (the content managed here is
882                 considered immutable, and thus cannot be changed by this
883                 function.)
884 
885         ***********************************************************************/
886 
887         package PathParser parse (mstring path, size_t end)
888         {
889                 end_ = cast(int) end;
890                 fp = path;
891                 folder_ = 0;
892                 name_ = suffix_ = -1;
893 
894                 for (int i=end_; --i >= 0;)
895                      switch (fp[i])
896                             {
897                             case FileConst.FileSeparatorChar:
898                                  if (name_ < 0)
899                                      if (suffix_ < 0 && i && fp[i-1] != '.')
900                                          suffix_ = i;
901                                  break;
902 
903                             case FileConst.PathSeparatorChar:
904                                  if (name_ < 0)
905                                      name_ = i + 1;
906                                  break;
907 
908                             // Windows file separators are illegal. Use
909                             // standard() or equivalent to convert first
910                             case '\\':
911                                  FS.exception ("unexpected '\\' character in path: ", path[0..end]);
912                                  break;
913 
914                             default:
915                                  break;
916                             }
917 
918                 if (name_ < 0)
919                     name_ = folder_;
920 
921                 if (suffix_ < 0 || suffix_ is name_)
922                     suffix_ = end_;
923 
924                 return this;
925         }
926 }
927 
928 
929 /*******************************************************************************
930 
931         Does this path currently exist?
932 
933 *******************************************************************************/
934 
935 bool exists (cstring name)
936 {
937         char[512] tmp = void;
938         return FS.exists (FS.strz(name, tmp));
939 }
940 
941 /*******************************************************************************
942 
943         Returns the time of the last modification. Accurate
944         to whatever the F/S supports, and in a format dictated
945         by the file-system. For example NTFS keeps UTC time,
946         while FAT timestamps are based on the local time.
947 
948 *******************************************************************************/
949 
950 Time modified (cstring name)
951 {
952         return timeStamps(name).modified;
953 }
954 
955 /*******************************************************************************
956 
957         Returns the time of the last access. Accurate to
958         whatever the F/S supports, and in a format dictated
959         by the file-system. For example NTFS keeps UTC time,
960         while FAT timestamps are based on the local time.
961 
962 *******************************************************************************/
963 
964 Time accessed (cstring name)
965 {
966         return timeStamps(name).accessed;
967 }
968 
969 /*******************************************************************************
970 
971         Returns the time of file creation. Accurate to
972         whatever the F/S supports, and in a format dictated
973         by the file-system. For example NTFS keeps UTC time,
974         while FAT timestamps are based on the local time.
975 
976 *******************************************************************************/
977 
978 Time created (cstring name)
979 {
980         return timeStamps(name).created;
981 }
982 
983 /*******************************************************************************
984 
985         Return the file length (in bytes.)
986 
987 *******************************************************************************/
988 
989 ulong fileSize (cstring name)
990 {
991         char[512] tmp = void;
992         return FS.fileSize (FS.strz(name, tmp));
993 }
994 
995 /*******************************************************************************
996 
997         Is this file writable?
998 
999 *******************************************************************************/
1000 
1001 bool isWritable (cstring name)
1002 {
1003         char[512] tmp = void;
1004         return FS.isWritable (FS.strz(name, tmp));
1005 }
1006 
1007 /*******************************************************************************
1008 
1009     Returns:
1010         true if the file exist and is readable for the effective
1011         uid.
1012 
1013 *******************************************************************************/
1014 
1015 bool isReadable (cstring name)
1016 {
1017     char[512] tmp = void;
1018     return FS.isReadable(FS.strz(name, tmp));
1019 }
1020 
1021 /*******************************************************************************
1022 
1023         Is this file actually a folder/directory?
1024 
1025 *******************************************************************************/
1026 
1027 bool isFolder (cstring name)
1028 {
1029         char[512] tmp = void;
1030         return FS.isFolder (FS.strz(name, tmp));
1031 }
1032 
1033 /*******************************************************************************
1034 
1035         Is this file actually a normal file?
1036         Not a directory or (on unix) a device file or link.
1037 
1038 *******************************************************************************/
1039 
1040 bool isFile (cstring name)
1041 {
1042         char[512] tmp = void;
1043         return FS.isFile (FS.strz(name, tmp));
1044 }
1045 
1046 /*******************************************************************************
1047 
1048         Return timestamp information.
1049 
1050         Timestamps are returns in a format dictated by the
1051         file-system. For example NTFS keeps UTC time,
1052         while FAT timestamps are based on the local time.
1053 
1054 *******************************************************************************/
1055 
1056 FS.Stamps timeStamps (cstring name)
1057 {
1058         char[512] tmp = void;
1059         return FS.timeStamps (FS.strz(name, tmp));
1060 }
1061 
1062 /*******************************************************************************
1063 
1064         Set the accessed and modified timestamps of the specified file.
1065 
1066 *******************************************************************************/
1067 
1068 void timeStamps (cstring name, Time accessed, Time modified)
1069 {
1070         char[512] tmp = void;
1071         FS.timeStamps (FS.strz(name, tmp), accessed, modified);
1072 }
1073 
1074 /*******************************************************************************
1075 
1076         Remove the file/directory from the file-system. Returns true if
1077         successful, false otherwise.
1078 
1079 *******************************************************************************/
1080 
1081 bool remove (cstring name)
1082 {
1083         char[512] tmp = void;
1084         return FS.remove (FS.strz(name, tmp));
1085 }
1086 
1087 /*******************************************************************************
1088 
1089         Remove the files and folders listed in the provided paths. Where
1090         folders are listed, they should be preceded by their contained
1091         files in order to be successfully removed. Returns a set of paths
1092         that failed to be removed (where .length is zero upon success).
1093 
1094         The collate() function can be used to provide the input paths:
1095         ---
1096         remove (collate (".", "*.d", true));
1097         ---
1098 
1099         Use with great caution.
1100 
1101         Note: May allocate memory.
1102 
1103 *******************************************************************************/
1104 
1105 cstring[] remove (const(char[])[] paths)
1106 {
1107         cstring[] failed;
1108         foreach (path; paths)
1109                  if (! remove (path))
1110                        failed ~= path;
1111         return failed;
1112 }
1113 
1114 /*******************************************************************************
1115 
1116         Create a new file.
1117 
1118 *******************************************************************************/
1119 
1120 void createFile (cstring name)
1121 {
1122         char[512] tmp = void;
1123         FS.createFile (FS.strz(name, tmp));
1124 }
1125 
1126 /*******************************************************************************
1127 
1128         Create a new directory.
1129 
1130 *******************************************************************************/
1131 
1132 void createFolder (cstring name)
1133 {
1134         char[512] tmp = void;
1135         FS.createFolder (FS.strz(name, tmp));
1136 }
1137 
1138 /*******************************************************************************
1139 
1140         Create an entire path consisting of this folder along with
1141         all parent folders. The path should not contain '.' or '..'
1142         segments, which can be removed via the normalize() function.
1143 
1144         Note that each segment is created as a folder, including the
1145         trailing segment.
1146 
1147         Throws: IOException upon system errors.
1148 
1149         Throws: IllegalArgumentException if a segment exists but as a
1150         file instead of a folder.
1151 
1152 *******************************************************************************/
1153 
1154 void createPath (cstring path)
1155 {
1156         void test (cstring segment)
1157         {
1158             if (segment.length)
1159             {
1160                 if (! exists (segment))
1161                     createFolder (segment);
1162                 else
1163                     if (! isFolder (segment))
1164                         throw new IllegalArgumentException ("Path.createPath :: file/folder conflict: " ~ idup(segment));
1165             }
1166         }
1167 
1168         foreach (i, char c; path)
1169                  if (c is '/')
1170                      test (path [0 .. i]);
1171         test (path);
1172 }
1173 
1174 /*******************************************************************************
1175 
1176        Change the name or location of a file/directory.
1177 
1178 *******************************************************************************/
1179 
1180 void rename (cstring src, cstring dst)
1181 {
1182         char[512] tmp1 = void;
1183         char[512] tmp2 = void;
1184         FS.rename (FS.strz(src, tmp1), FS.strz(dst, tmp2));
1185 }
1186 
1187 /*******************************************************************************
1188 
1189         Transfer the content of one file to another. Throws
1190         an IOException upon failure.
1191 
1192 *******************************************************************************/
1193 
1194 void copy (cstring src, cstring dst)
1195 {
1196         char[512] tmp1 = void;
1197         char[512] tmp2 = void;
1198         FS.copy (FS.strz(src, tmp1), FS.strz(dst, tmp2));
1199 }
1200 
1201 /*******************************************************************************
1202 
1203         Provides foreach support via a fruct, as in
1204         ---
1205         foreach (info; children("myfolder"))
1206             ...
1207         ---
1208 
1209         Each path and filename is passed to the foreach
1210         delegate, along with the path prefix and whether
1211         the entry is a folder or not. The info construct
1212         exposes the following attributes:
1213         ---
1214         mstring  path
1215         mstring  name
1216         ulong   bytes
1217         bool    folder
1218         ---
1219 
1220         Argument 'all' controls whether hidden and system
1221         files are included - these are ignored by default.
1222 
1223 *******************************************************************************/
1224 
1225 FS.Listing children (cstring path, bool all=false)
1226 {
1227         return FS.Listing (path, all);
1228 }
1229 
1230 /*******************************************************************************
1231 
1232         Collate all files and folders from the given path whose name matches
1233         the given pattern. Folders will be traversed where recurse is enabled,
1234         and a set of matching names is returned as filepaths (including those
1235         folders which match the pattern.)
1236 
1237         Note: Allocates memory for returned paths.
1238 
1239 *******************************************************************************/
1240 
1241 mstring[] collate (cstring path, cstring pattern, bool recurse=false)
1242 {
1243         mstring[] list;
1244 
1245         foreach (info; children (path))
1246                 {
1247                 if (info.folder && recurse)
1248                     list ~= collate (join(info.path, info.name), pattern, true);
1249 
1250                 if (patternMatch (info.name, pattern))
1251                     list ~= join (info.path, info.name);
1252                 }
1253         return list;
1254 }
1255 
1256 /*******************************************************************************
1257 
1258         Join a set of path specs together. A path separator is
1259         potentially inserted between each of the segments.
1260 
1261         Note: May allocate memory.
1262 
1263 *******************************************************************************/
1264 
1265 mstring join (const(char[])[] paths...)
1266 {
1267         return FS.join (paths);
1268 }
1269 
1270 /*******************************************************************************
1271 
1272         Convert path separators to a standard format, using '/' as
1273         the path separator. This is compatible with Uri and all of
1274         the contemporary O/S which Tango supports. Known exceptions
1275         include the Windows command-line processor, which considers
1276         '/' characters to be switches instead. Use the native()
1277         method to support that.
1278 
1279         Note: Mutates the provided path.
1280 
1281 *******************************************************************************/
1282 
1283 mstring standard (mstring path)
1284 {
1285         return replace (path, '\\', '/');
1286 }
1287 
1288 /*******************************************************************************
1289 
1290         Convert to native O/S path separators where that is required,
1291         such as when dealing with the Windows command-line.
1292 
1293         Note: Mutates the provided path. Use this pattern to obtain a
1294         copy instead: native(path.dup);
1295 
1296 *******************************************************************************/
1297 
1298 mstring native (mstring path)
1299 {
1300         return path;
1301 }
1302 
1303 /*******************************************************************************
1304 
1305         Returns a path representing the parent of this one, with a special
1306         case concerning a trailing '/':
1307         $(UL
1308           $(LI normal:  /x/y/z => /x/y)
1309           $(LI normal:  /x/y/  => /x/y)
1310           $(LI special: /x/y/  => /x)
1311           $(LI normal:  /x     => /)
1312           $(LI normal:  /      => empty))
1313 
1314         The result can be split via parse().
1315 
1316 *******************************************************************************/
1317 
1318 cstring parent (cstring path)
1319 {
1320         return pop (FS.stripped (path));
1321 }
1322 
1323 /*******************************************************************************
1324 
1325         Returns a path representing the parent of this one:
1326         $(UL
1327           $(LI normal:  /x/y/z => /x/y)
1328           $(LI normal:  /x/y/  => /x/y)
1329           $(LI normal:  /x     => /)
1330           $(LI normal:  /      => empty))
1331 
1332         The result can be split via parse().
1333 
1334 *******************************************************************************/
1335 
1336 cstring pop (cstring path)
1337 {
1338         size_t i = path.length;
1339         while (i && path[--i] != '/') {}
1340         return path [0..i];
1341 }
1342 
1343 /*******************************************************************************
1344 
1345         Break a path into "head" and "tail" components. For example:
1346         $(UL
1347           $(LI "/a/b/c" -> "/a","b/c")
1348           $(LI "a/b/c" -> "a","b/c"))
1349 
1350 *******************************************************************************/
1351 
1352 mstring split (mstring path, out mstring head, out mstring tail)
1353 {
1354         head = path;
1355         if (path.length > 1)
1356             foreach (i, char c; path[1..$])
1357                      if (c is '/')
1358                         {
1359                         head = path [0 .. i+1];
1360                         tail = path [i+2 .. $];
1361                         break;
1362                         }
1363         return path;
1364 }
1365 
1366 /*******************************************************************************
1367 
1368         Replace all path 'from' instances with 'to', in place (overwrites
1369         the provided path).
1370 
1371 *******************************************************************************/
1372 
1373 mstring replace (mstring path, char from, char to)
1374 {
1375         foreach (ref char c; path)
1376                  if (c is from)
1377                      c = to;
1378         return path;
1379 }
1380 
1381 /*******************************************************************************
1382 
1383         Parse a path into its constituent components.
1384 
1385         Note that the provided path is sliced, not duplicated.
1386 
1387 *******************************************************************************/
1388 
1389 PathParser parse (mstring path)
1390 {
1391         PathParser p;
1392 
1393         p.parse (path);
1394         return p;
1395 }
1396 
1397 /*******************************************************************************
1398 
1399 *******************************************************************************/
1400 
1401 unittest
1402 {
1403     auto p = parse ("/foo/bar/file.ext".dup);
1404     test (p == "/foo/bar/file.ext");
1405     test (p.folder == "/foo/bar/");
1406     test (p.path == "/foo/bar/");
1407     test (p.file == "file.ext");
1408     test (p.name == "file");
1409     test (p.suffix == ".ext");
1410     test (p.ext == "ext");
1411     test (p.isChild == true);
1412     test (p.isEmpty == false);
1413     test (p.isAbsolute == true);
1414 }
1415 
1416 /******************************************************************************
1417 
1418         Matches a pattern against a filename.
1419 
1420         Some characters of pattern have special a meaning (they are
1421         $(EM meta-characters)) and $(B can't) be escaped. These are:
1422 
1423         $(TABLE
1424           $(TR
1425             $(TD $(B *))
1426             $(TD Matches 0 or more instances of any character.))
1427           $(TR
1428             $(TD $(B ?))
1429             $(TD Matches exactly one instances of any character.))
1430           $(TR
1431             $(TD $(B [)$(EM chars)$(B ]))
1432             $(TD Matches one instance of any character that appears
1433           between the brackets.))
1434           $(TR
1435             $(TD $(B [!)$(EM chars)$(B ]))
1436             $(TD Matches one instance of any character that does not appear
1437           between the brackets after the exclamation mark.))
1438         )
1439 
1440         Internally individual character comparisons are done calling
1441         charMatch(), so its rules apply here too. Note that path
1442         separators and dots don't stop a meta-character from matching
1443         further portions of the filename.
1444 
1445         Returns: true if pattern matches filename, false otherwise.
1446 
1447         Throws: Nothing.
1448         -----
1449         patternMatch("Go*.bar", "[fg]???bar"); // => false
1450         patternMatch("/foo*home/bar", "?foo*bar"); // => true
1451         patternMatch("foobar", "foo?bar"); // => true
1452         -----
1453 
1454 ******************************************************************************/
1455 
1456 bool patternMatch (cstring filename, cstring pattern)
1457 {
1458         // Verify that pattern[] is valid
1459         bool inbracket = false;
1460         for (auto i=0; i < pattern.length; i++)
1461         {
1462             switch (pattern[i])
1463             {
1464             case '[':
1465                 verify(!inbracket);
1466                 inbracket = true;
1467                 break;
1468             case ']':
1469                 verify(inbracket);
1470                 inbracket = false;
1471                 break;
1472             default:
1473                 break;
1474             }
1475         }
1476 
1477         int pi;
1478         int ni;
1479         char pc;
1480         char nc;
1481         int j;
1482         int not;
1483         int anymatch;
1484 
1485         bool charMatch (char c1, char c2)
1486         {
1487             return c1 == c2;
1488         }
1489 
1490         ni = 0;
1491         for (pi = 0; pi < pattern.length; pi++)
1492             {
1493             pc = pattern [pi];
1494             switch (pc)
1495                    {
1496                    case '*':
1497                         if (pi + 1 == pattern.length)
1498                             goto match;
1499                         for (j = ni; j < filename.length; j++)
1500                             {
1501                             if (patternMatch(filename[j .. filename.length],
1502                                 pattern[pi + 1 .. pattern.length]))
1503                                goto match;
1504                             }
1505                         goto nomatch;
1506 
1507                    case '?':
1508                         if (ni == filename.length)
1509                             goto nomatch;
1510                         ni++;
1511                         break;
1512 
1513                    case '[':
1514                         if (ni == filename.length)
1515                             goto nomatch;
1516                         nc = filename[ni];
1517                         ni++;
1518                         not = 0;
1519                         pi++;
1520                         if (pattern[pi] == '!')
1521                            {
1522                            not = 1;
1523                            pi++;
1524                            }
1525                         anymatch = 0;
1526                         while (1)
1527                               {
1528                               pc = pattern[pi];
1529                               if (pc == ']')
1530                                   break;
1531                               if (!anymatch && charMatch(nc, pc))
1532                                    anymatch = 1;
1533                               pi++;
1534                               }
1535                         if (!(anymatch ^ not))
1536                               goto nomatch;
1537                         break;
1538 
1539                    default:
1540                         if (ni == filename.length)
1541                             goto nomatch;
1542                         nc = filename[ni];
1543                         if (!charMatch(pc, nc))
1544                              goto nomatch;
1545                         ni++;
1546                         break;
1547                    }
1548             }
1549         if (ni < filename.length)
1550             goto nomatch;
1551 
1552         match:
1553             return true;
1554 
1555         nomatch:
1556             return false;
1557 }
1558 
1559 /*******************************************************************************
1560 
1561 *******************************************************************************/
1562 
1563 unittest
1564 {
1565     test(!patternMatch("foo", "Foo"));
1566 
1567     test(patternMatch("foo", "*"));
1568     test(patternMatch("foo.bar", "*"));
1569     test(patternMatch("foo.bar", "*.*"));
1570     test(patternMatch("foo.bar", "foo*"));
1571     test(patternMatch("foo.bar", "f*bar"));
1572     test(patternMatch("foo.bar", "f*b*r"));
1573     test(patternMatch("foo.bar", "f???bar"));
1574     test(patternMatch("foo.bar", "[fg]???bar"));
1575     test(patternMatch("foo.bar", "[!gh]*bar"));
1576 
1577     test(!patternMatch("foo", "bar"));
1578     test(!patternMatch("foo", "*.*"));
1579     test(!patternMatch("foo.bar", "f*baz"));
1580     test(!patternMatch("foo.bar", "f*b*x"));
1581     test(!patternMatch("foo.bar", "[gh]???bar"));
1582     test(!patternMatch("foo.bar", "[!fg]*bar"));
1583     test(!patternMatch("foo.bar", "[fg]???baz"));
1584 }
1585 
1586 /*******************************************************************************
1587 
1588         Normalizes a path component.
1589         $(UL
1590           $(LI $(B .) segments are removed)
1591           $(LI &lt;segment&gt;$(B /..) are removed))
1592 
1593         Multiple consecutive forward slashes are replaced with a single
1594         forward slash. On Windows, \ will be converted to / prior to any
1595         normalization.
1596 
1597         Note that any number of .. segments at the front is ignored,
1598         unless it is an absolute path, in which case they are removed.
1599 
1600         The input path is copied into either the provided buffer, or a heap
1601         allocated array if no buffer was provided. Normalization modifies
1602         this copy before returning the relevant slice.
1603         -----
1604         normalize("/home/foo/./bar/../../john/doe"); // => "/home/john/doe"
1605         -----
1606 
1607         Note: Allocates memory.
1608 
1609 *******************************************************************************/
1610 
1611 mstring normalize (cstring in_path, mstring buf = null)
1612 {
1613         size_t  idx;            // Current position
1614         size_t  moveTo;         // Position to move
1615         bool    isAbsolute;     // Whether the path is absolute
1616         enum    {NodeStackLength = 64}
1617         mstring path;           // resulting path to return
1618 
1619         // Starting positions of regular path segments are pushed
1620         // on this stack to avoid backward scanning when .. segments
1621         // are encountered
1622         size_t[NodeStackLength] nodeStack;
1623         size_t nodeStackTop;
1624 
1625         // Moves the path tail starting at the current position to
1626         // moveTo. Then sets the current position to moveTo.
1627         void move ()
1628         {
1629                 auto len = path.length - idx;
1630                 memmove (path.ptr + moveTo, path.ptr + idx, len);
1631                 path = path[0..moveTo + len];
1632                 idx = moveTo;
1633         }
1634 
1635         // Checks if the character at the current position is a
1636         // separator. If true, normalizes the separator to '/' on
1637         // Windows and advances the current position to the next
1638         // character.
1639         bool isSep (ref size_t i)
1640         {
1641                 char c = path[i];
1642                 if (c != '/')
1643                         return false;
1644                 i++;
1645                 return true;
1646         }
1647 
1648         if (buf is null)
1649             path = in_path.dup;
1650         else
1651             path = buf[0..in_path.length] = in_path[];
1652 
1653         if (idx == path.length)
1654             return path;
1655 
1656         moveTo = idx;
1657         if (isSep(idx))
1658            {
1659            moveTo++; // preserve root separator.
1660            isAbsolute = true;
1661            }
1662 
1663         while (idx < path.length)
1664               {
1665               // Skip duplicate separators
1666               if (isSep(idx))
1667                   continue;
1668 
1669               if (path[idx] == '.')
1670                  {
1671                  // leave the current position at the start of
1672                  // the segment
1673                  auto i = idx + 1;
1674                  if (i < path.length && path[i] == '.')
1675                     {
1676                     i++;
1677                     if (i == path.length || isSep(i))
1678                        {
1679                        // It is a '..' segment. If the stack is not
1680                        // empty, set moveTo and the current position
1681                        // to the start position of the last found
1682                        // regular segment
1683                        if (nodeStackTop > 0)
1684                            moveTo = nodeStack[--nodeStackTop];
1685 
1686                        // If no regular segment start positions on the
1687                        // stack, drop the .. segment if it is absolute
1688                        // path or, otherwise, advance moveTo and the
1689                        // current position to the character after the
1690                        // '..' segment
1691                        else
1692                           if (!isAbsolute)
1693                              {
1694                              if (moveTo != idx)
1695                                 {
1696                                 i -= idx - moveTo;
1697                                 move();
1698                                 }
1699                              moveTo = i;
1700                              }
1701 
1702                        idx = i;
1703                        continue;
1704                        }
1705                     }
1706 
1707                  // If it is '.' segment, skip it.
1708                  if (i == path.length || isSep(i))
1709                     {
1710                     idx = i;
1711                     continue;
1712                     }
1713                  }
1714 
1715               // Remove excessive '/', '.' and/or '..' preceeding the
1716               // segment
1717               if (moveTo != idx)
1718                   move();
1719 
1720               // Push the start position of the regular segment on the
1721               // stack
1722               verify(nodeStackTop < NodeStackLength);
1723               nodeStack[nodeStackTop++] = idx;
1724 
1725               // Skip the regular segment and set moveTo to the position
1726               // after the segment (including the trailing '/' if present)
1727               for (; idx < path.length && !isSep(idx); idx++)
1728                   {}
1729               moveTo = idx;
1730               }
1731 
1732         if (moveTo != idx)
1733             move();
1734         return path;
1735 }
1736 
1737 /*******************************************************************************
1738 
1739 *******************************************************************************/
1740 
1741 unittest
1742 {
1743     test (normalize ("") == "");
1744     test (normalize ("/home/../john/../.tango/.htaccess") == "/.tango/.htaccess");
1745     test (normalize ("/home/../john/../.tango/foo.conf") == "/.tango/foo.conf");
1746     test (normalize ("/home/john/.tango/foo.conf") == "/home/john/.tango/foo.conf");
1747     test (normalize ("/foo/bar/.htaccess") == "/foo/bar/.htaccess");
1748     test (normalize ("foo/bar/././.") == "foo/bar/");
1749     test (normalize ("././foo/././././bar") == "foo/bar");
1750     test (normalize ("/foo/../john") == "/john");
1751     test (normalize ("foo/../john") == "john");
1752     test (normalize ("foo/bar/..") == "foo/");
1753     test (normalize ("foo/bar/../john") == "foo/john");
1754     test (normalize ("foo/bar/doe/../../john") == "foo/john");
1755     test (normalize ("foo/bar/doe/../../john/../bar") == "foo/bar");
1756     test (normalize ("./foo/bar/doe") == "foo/bar/doe");
1757     test (normalize ("./foo/bar/doe/../../john/../bar") == "foo/bar");
1758     test (normalize ("./foo/bar/../../john/../bar") == "bar");
1759     test (normalize ("foo/bar/./doe/../../john") == "foo/john");
1760     test (normalize ("../../foo/bar") == "../../foo/bar");
1761     test (normalize ("../../../foo/bar") == "../../../foo/bar");
1762     test (normalize ("d/") == "d/");
1763     test (normalize ("/home/john/./foo/bar.txt") == "/home/john/foo/bar.txt");
1764     test (normalize ("/home//john") == "/home/john");
1765 
1766     test (normalize("/../../bar/") == "/bar/");
1767     test (normalize("/../../bar/../baz/./") == "/baz/");
1768     test (normalize("/../../bar/boo/../baz/.bar/.") == "/bar/baz/.bar/");
1769     test (normalize("../..///.///bar/..//..//baz/.//boo/..") == "../../../baz/");
1770     test (normalize("./bar/./..boo/./..bar././/") == "bar/..boo/..bar./");
1771     test (normalize("/bar/..") == "/");
1772     test (normalize("bar/") == "bar/");
1773     test (normalize(".../") == ".../");
1774     test (normalize("///../foo") == "/foo");
1775     test (normalize("./foo") == "foo");
1776     auto buf = new char[100];
1777     auto ret = normalize("foo/bar/./baz", buf);
1778     test (ret.ptr == buf.ptr);
1779     test (ret == "foo/bar/baz");
1780 }
1781 
1782 
1783 /*******************************************************************************
1784 
1785 *******************************************************************************/
1786 
1787 debug (Path)
1788 {
1789         import ocean.io.Stdout;
1790 
1791         void main()
1792         {
1793                 foreach (file; collate (".", "*.d", true))
1794                          Stdout (file).newline;
1795         }
1796 }