1 /*******************************************************************************
2 
3     Subclass of ocean.io.FilePath to provide some extra functionality
4 
5     Copyright:
6         Copyright (c) 2004 Kris Bell.
7         Some parts copyright (c) 2009-2016 dunnhumby Germany GmbH.
8         All rights reserved.
9 
10     License:
11         Tango Dual License: 3-Clause BSD License / Academic Free License v3.0.
12         See LICENSE_TANGO.txt for details.
13 
14     Authors: Kris
15 
16 *******************************************************************************/
17 
18 module ocean.io.FilePath;
19 
20 import ocean.transition;
21 import ocean.core.Verify;
22 import ocean.io.Path;
23 import ocean.io.model.IFile : FileConst, FileInfo;
24 
25 import core.stdc.string : memmove;
26 import core.sys.posix.unistd : link;
27 
28 version (unittest) import ocean.core.Test;
29 
30 /*******************************************************************************
31 
32         Models a file path. These are expected to be used as the constructor
33         argument to various file classes. The intention is that they easily
34         convert to other representations such as absolute, canonical, or Url.
35 
36         File paths containing non-ansi characters should be UTF-8 encoded.
37         Supporting Unicode in this manner was deemed to be more suitable
38         than providing a wchar version of FilePath, and is both consistent
39         & compatible with the approach taken with the Uri class.
40 
41         FilePath is designed to be transformed, thus each mutating method
42         modifies the internal content. See module Path.d for a lightweight
43         immutable variation.
44 
45         Note that patterns of adjacent '.' separators are treated specially
46         in that they will be assigned to the name where there is no distinct
47         suffix. In addition, a '.' at the start of a name signifies it does
48         not belong to the suffix i.e. ".file" is a name rather than a suffix.
49         Patterns of intermediate '.' characters will otherwise be assigned
50         to the suffix, such that "file....suffix" includes the dots within
51         the suffix itself. See method ext() for a suffix without dots.
52 
53         Note that Win32 '\' characters are converted to '/' by default via
54         the FilePath constructor.
55 
56 *******************************************************************************/
57 
58 class FilePath : PathView
59 {
60     import core.sys.posix.sys.types: mode_t;
61 
62     private PathParser      p;              // the parsed path
63     private bool            dir_;           // this represents a dir?
64 
65 
66     /***********************************************************************
67 
68         Support path ~= x; syntax
69 
70     ***********************************************************************/
71 
72     public alias opOpAssign ( istring op = "~" ) = append;
73 
74 
75     /***********************************************************************
76 
77             Filter used for screening paths via toList().
78 
79     ***********************************************************************/
80 
81     public alias bool delegate (FilePath, bool) Filter;
82 
83     /***********************************************************************
84 
85             Call-site shortcut to create a FilePath instance. This
86             enables the same syntax as struct usage, so may expose
87             a migration path.
88 
89     ***********************************************************************/
90 
91     static FilePath opCall (cstring filepath = null)
92     {
93             return new FilePath (filepath);
94     }
95 
96     /***********************************************************************
97 
98             Create a FilePath from a copy of the provided string.
99 
100             FilePath assumes both path & name are present, and therefore
101             may split what is otherwise a logically valid path. That is,
102             the 'name' of a file is typically the path segment following
103             a rightmost path-separator. The intent is to treat files and
104             directories in the same manner; as a name with an optional
105             ancestral structure. It is possible to bias the interpretation
106             by adding a trailing path-separator to the argument. Doing so
107             will result in an empty name attribute.
108 
109             With regard to the filepath copy, we found the common case to
110             be an explicit .dup, whereas aliasing appeared to be rare by
111             comparison. We also noted a large proportion interacting with
112             C-oriented OS calls, implying the postfix of a null terminator.
113             Thus, FilePath combines both as a single operation.
114 
115             Note that Win32 '\' characters are normalized to '/' instead.
116 
117     ***********************************************************************/
118 
119     this (cstring filepath = null)
120     {
121             set (filepath, true);
122     }
123 
124     /***********************************************************************
125 
126             Return the complete text of this filepath.
127 
128     ***********************************************************************/
129 
130     final override istring toString ()
131     {
132             return p.toString;
133     }
134 
135     /***********************************************************************
136 
137             Duplicate this path.
138 
139     ***********************************************************************/
140 
141     final FilePath dup ()
142     {
143             return FilePath (toString);
144     }
145 
146     /***********************************************************************
147 
148             Return the complete text of this filepath as a null
149             terminated string for use with a C api. Use toString
150             instead for any D api.
151 
152             Note that the nul is always embedded within the string
153             maintained by FilePath, so there's no heap overhead when
154             making a C call.
155 
156     ***********************************************************************/
157 
158     final mstring cString ()
159     {
160             return p.fp [0 .. p.end_+1];
161     }
162 
163     /***********************************************************************
164 
165             Return the root of this path. Roots are constructs such as
166             "C:".
167 
168     ***********************************************************************/
169 
170     final cstring root ()
171     {
172             return p.root;
173     }
174 
175     /***********************************************************************
176 
177             Return the file path.
178 
179             Paths may start and end with a "/".
180             The root path is "/" and an unspecified path is returned as
181             an empty string. Directory paths may be split such that the
182             directory name is placed into the 'name' member; directory
183             paths are treated no differently than file paths.
184 
185     ***********************************************************************/
186 
187     final cstring folder ()
188     {
189             return p.folder;
190     }
191 
192     /***********************************************************************
193 
194             Returns a path representing the parent of this one. This
195             will typically return the current path component, though
196             with a special case where the name component is empty. In
197             such cases, the path is scanned for a prior segment:
198             $(UL
199               $(LI normal:  /x/y/z => /x/y)
200               $(LI special: /x/y/  => /x))
201 
202             Note that this returns a path suitable for splitting into
203             path and name components (there's no trailing separator).
204 
205             See pop() also, which is generally more useful when working
206             with FilePath instances.
207 
208     ***********************************************************************/
209 
210     final cstring parent ()
211     {
212             return p.parent;
213     }
214 
215     /***********************************************************************
216 
217             Return the name of this file, or directory.
218 
219     ***********************************************************************/
220 
221     final cstring name ()
222     {
223             return p.name;
224     }
225 
226     /***********************************************************************
227 
228             Ext is the tail of the filename, rightward of the rightmost
229             '.' separator e.g. path "foo.bar" has ext "bar". Note that
230             patterns of adjacent separators are treated specially; for
231             example, ".." will wind up with no ext at all.
232 
233     ***********************************************************************/
234 
235     final cstring ext ()
236     {
237             return p.ext;
238     }
239 
240     /***********************************************************************
241 
242             Suffix is like ext, but includes the separator e.g. path
243             "foo.bar" has suffix ".bar".
244 
245     ***********************************************************************/
246 
247     final cstring suffix ()
248     {
249             return p.suffix;
250     }
251 
252     /***********************************************************************
253 
254             Return the root + folder combination.
255 
256     ***********************************************************************/
257 
258     final cstring path ()
259     {
260             return p.path;
261     }
262 
263     /***********************************************************************
264 
265             Return the name + suffix combination.
266 
267     ***********************************************************************/
268 
269     final cstring file ()
270     {
271             return p.file;
272     }
273 
274     /***********************************************************************
275 
276             Returns true if all fields are identical. Note that some
277             combinations of operations may not produce an identical
278             set of fields. For example:
279             ---
280             FilePath("/foo").append("bar").pop == "/foo";
281             FilePath("/foo/").append("bar").pop != "/foo/";
282             ---
283 
284             The latter is different due to variance in how append
285             injects data, and how pop is expected to operate under
286             different circumstances (both examples produce the same
287             pop result, although the initial path is not identical).
288 
289             However, opEquals() can overlook minor distinctions such
290             as this example, and will return a match.
291 
292     ***********************************************************************/
293 
294     final override equals_t opEquals (Object o)
295     {
296             return (this is o) || (o && opEquals(o.toString));
297     }
298 
299     /***********************************************************************
300 
301             Does this FilePath match the given text? Note that some
302             combinations of operations may not produce an identical
303             set of fields. For example:
304             ---
305             FilePath("/foo").append("bar").pop == "/foo";
306             FilePath("/foo/").append("bar").pop != "/foo/";
307             ---
308 
309             The latter Is Different due to variance in how append
310             injects data, and how pop is expected to operate under
311             different circumstances (both examples produce the same
312             pop result, although the initial path is not identical).
313 
314             However, opEquals() can overlook minor distinctions such
315             as this example, and will return a match.
316 
317     ***********************************************************************/
318 
319     final int opEquals (cstring s)
320     {
321             return p.opEquals(s);
322     }
323 
324     /***********************************************************************
325 
326             Returns true if this FilePath is *not* relative to the
327             current working directory.
328 
329     ***********************************************************************/
330 
331     final bool isAbsolute ()
332     {
333             return p.isAbsolute;
334     }
335 
336     /***********************************************************************
337 
338             Returns true if this FilePath is empty.
339 
340     ***********************************************************************/
341 
342     final bool isEmpty ()
343     {
344             return p.isEmpty;
345     }
346 
347     /***********************************************************************
348 
349             Returns true if this FilePath has a parent. Note that a
350             parent is defined by the presence of a path-separator in
351             the path. This means 'foo' within "\foo" is considered a
352             child of the root.
353 
354     ***********************************************************************/
355 
356     final bool isChild ()
357     {
358             return p.isChild;
359     }
360 
361     /***********************************************************************
362 
363             Replace all 'from' instances with 'to'.
364 
365     ***********************************************************************/
366 
367     final FilePath replace (char from, char to)
368     {
369             .replace (p.path, from, to);
370             return this;
371     }
372 
373     /***********************************************************************
374 
375             Convert path separators to a standard format, using '/' as
376             the path separator. This is compatible with URI and all of
377             the contemporary O/S which Tango supports. Known exceptions
378             include the Windows command-line processor, which considers
379             '/' characters to be switches instead. Use the native()
380             method to support that.
381 
382             Note: mutates the current path.
383 
384     ***********************************************************************/
385 
386     final FilePath standard ()
387     {
388             .standard (p.path);
389             return this;
390     }
391 
392     /***********************************************************************
393 
394             Convert to native O/S path separators where that is required,
395             such as when dealing with the Windows command-line.
396 
397             Note: Mutates the current path. Use this pattern to obtain a
398             copy instead: path.dup.native
399 
400     ***********************************************************************/
401 
402     final FilePath native ()
403     {
404             .native (p.path);
405             return this;
406     }
407 
408     /***********************************************************************
409 
410             Concatenate text to this path; no separators are added.
411             See_also: $(SYMLINK FilePath.join, join)()
412 
413     ***********************************************************************/
414 
415     final FilePath cat (cstring[] others...)
416     {
417             foreach (other; others)
418                     {
419                     auto len = p.end_ + other.length;
420                     expand (len);
421                     p.fp [p.end_ .. len] = other;
422                     p.fp [len] = 0;
423                     p.end_ = cast(int) len;
424                     }
425             return parse;
426     }
427 
428     /***********************************************************************
429 
430             Append a folder to this path. A leading separator is added
431             as required.
432 
433     ***********************************************************************/
434 
435     final FilePath append (cstring path)
436     {
437             if (file.length)
438                 path = prefixed (path);
439             return cat (path);
440     }
441 
442     /***********************************************************************
443 
444             Prepend a folder to this path. A trailing separator is added
445             if needed.
446 
447     ***********************************************************************/
448 
449     final FilePath prepend (cstring path)
450     {
451             adjust (0, p.folder_, p.folder_, padded (path));
452             return parse;
453     }
454 
455     /***********************************************************************
456 
457             Reset the content of this path to that of another and
458             reparse.
459 
460     ***********************************************************************/
461 
462     FilePath set (FilePath path)
463     {
464             return set (path.toString, false);
465     }
466 
467     /***********************************************************************
468 
469             Reset the content of this path, and reparse. There's an
470             optional boolean flag to convert the path into standard
471             form, before parsing (converting '\' into '/').
472 
473     ***********************************************************************/
474 
475     final FilePath set (cstring path, bool convert = false)
476     {
477             p.end_ = cast(int) path.length;
478 
479             expand (p.end_);
480             if (p.end_)
481                {
482                p.fp[0 .. p.end_] = path;
483                if (convert)
484                    .standard (p.fp [0 .. p.end_]);
485                }
486 
487             p.fp[p.end_] = '\0';
488             return parse;
489     }
490 
491     /***********************************************************************
492 
493             Sidestep the normal lookup for paths that are known to
494             be folders. Where folder is true, file system lookups
495             will be skipped.
496 
497     ***********************************************************************/
498 
499     final FilePath isFolder (bool folder)
500     {
501             dir_ = folder;
502             return this;
503     }
504 
505     /***********************************************************************
506 
507             Replace the root portion of this path.
508 
509     ***********************************************************************/
510 
511     final FilePath root (cstring other)
512     {
513             auto x = adjust (0, p.folder_, p.folder_, padded (other, ':'));
514             p.folder_ += x;
515             p.suffix_ += x;
516             p.name_ += x;
517             return this;
518     }
519 
520     /***********************************************************************
521 
522             Replace the folder portion of this path. The folder will be
523             padded with a path-separator as required.
524 
525     ***********************************************************************/
526 
527     final FilePath folder (cstring other)
528     {
529             auto x = adjust (p.folder_, p.name_, p.name_ - p.folder_, padded (other));
530             p.suffix_ += x;
531             p.name_ += x;
532             return this;
533     }
534 
535     /***********************************************************************
536 
537             Replace the name portion of this path.
538 
539     ***********************************************************************/
540 
541     final FilePath name (cstring other)
542     {
543             auto x = adjust (p.name_, p.suffix_, p.suffix_ - p.name_, other);
544             p.suffix_ += x;
545             return this;
546     }
547 
548     /***********************************************************************
549 
550             Replace the suffix portion of this path. The suffix will be
551             prefixed with a file-separator as required.
552 
553     ***********************************************************************/
554 
555     final FilePath suffix (cstring other)
556     {
557             adjust (p.suffix_, p.end_, p.end_ - p.suffix_, prefixed (other, '.'));
558             return this;
559     }
560 
561     /***********************************************************************
562 
563             Replace the root and folder portions of this path and
564             reparse. The replacement will be padded with a path
565             separator as required.
566 
567     ***********************************************************************/
568 
569     final FilePath path (cstring other)
570     {
571             adjust (0, p.name_, p.name_, padded (other));
572             return parse;
573     }
574 
575     /***********************************************************************
576 
577             Replace the file and suffix portions of this path and
578             reparse. The replacement will be prefixed with a suffix
579             separator as required.
580 
581     ***********************************************************************/
582 
583     final FilePath file (cstring other)
584     {
585             adjust (p.name_, p.end_, p.end_ - p.name_, other);
586             return parse;
587     }
588 
589     /***********************************************************************
590 
591             Pop to the parent of the current filepath (in situ - mutates
592             this FilePath). Note that this differs from parent() in that
593             it does not include any special cases.
594 
595     ***********************************************************************/
596 
597     final FilePath pop ()
598     {
599             version (SpecialPop)
600                      p.end_ = p.parent.length;
601                else
602                   p.end_ = cast(int) p.pop.length;
603             p.fp[p.end_] = '\0';
604             return parse;
605     }
606 
607     /***********************************************************************
608 
609             Join a set of path specs together. A path separator is
610             potentially inserted between each of the segments.
611 
612     ***********************************************************************/
613 
614     static istring join (const(char[][]) paths...)
615     {
616             auto s = FS.join (paths);
617             return assumeUnique(s);
618     }
619 
620     /***********************************************************************
621 
622             Convert this FilePath to absolute format, using the given
623             prefix as necessary. If this FilePath is already absolute,
624             return it intact.
625 
626             Returns this FilePath, adjusted as necessary.
627 
628     ***********************************************************************/
629 
630     final FilePath absolute (cstring prefix)
631     {
632             if (! isAbsolute)
633                   prepend (padded(prefix));
634             return this;
635     }
636 
637     /***********************************************************************
638 
639             Return an adjusted path such that non-empty instances do not
640             have a trailing separator.
641 
642     ***********************************************************************/
643 
644     static cstring stripped (cstring path, char c = FileConst.PathSeparatorChar)
645     {
646             return FS.stripped (path, c);
647     }
648 
649     /***********************************************************************
650 
651             Return an adjusted path such that non-empty instances always
652             have a trailing separator.
653 
654     ***********************************************************************/
655 
656     static cstring padded (cstring path, char c = FileConst.PathSeparatorChar)
657     {
658             return FS.padded (path, c);
659     }
660 
661     /***********************************************************************
662 
663             Return an adjusted path such that non-empty instances always
664             have a prefixed separator.
665 
666     ***********************************************************************/
667 
668     static cstring prefixed (cstring s, char c = FileConst.PathSeparatorChar)
669     {
670             if (s.length && s[0] != c)
671                 s = c ~ s;
672             return s;
673     }
674 
675     /***********************************************************************
676 
677             Parse the path spec, and mutate '\' into '/' as necessary.
678 
679     ***********************************************************************/
680 
681     private final FilePath parse ()
682     {
683             p.parse (p.fp, p.end_);
684             return this;
685     }
686 
687     /***********************************************************************
688 
689             Potentially make room for more content.
690 
691     ***********************************************************************/
692 
693     private final void expand (size_t size)
694     {
695             ++size;
696             if (p.fp.length < size)
697                 p.fp.length = (size + 127) & ~127;
698     }
699 
700     /***********************************************************************
701 
702             Insert/delete internal content.
703 
704     ***********************************************************************/
705 
706     private final int adjust (int head, int tail, int len, cstring sub)
707     {
708             len = (cast(int) sub.length) - len;
709 
710             // don't destroy self-references!
711             if (len && sub.ptr >= p.fp.ptr+head+len && sub.ptr < p.fp.ptr+p.fp.length)
712                {
713                char[512] tmp = void;
714                verify(sub.length < tmp.length);
715                sub = tmp[0..sub.length] = sub;
716                }
717 
718             // make some room if necessary
719             expand (len + p.end_);
720 
721             // slide tail around to insert or remove space
722             memmove (p.fp.ptr+tail+len, p.fp.ptr+tail, p.end_ +1 - tail);
723 
724             // copy replacement
725             memmove (p.fp.ptr + head, sub.ptr, sub.length);
726 
727             // adjust length
728             p.end_ += len;
729             return len;
730     }
731 
732 
733     /* ****************************************************************** */
734     /* ******************** file system methods ************************* */
735     /* ****************************************************************** */
736 
737 
738     /***********************************************************************
739 
740             Create an entire path consisting of this folder along with
741             all parent folders. The path must not contain '.' or '..'
742             segments. Related methods include PathUtil.normalize() and
743             absolute().
744 
745             Note that each segment is created as a folder, including the
746             trailing segment.
747 
748             Returns: A chaining reference (this).
749 
750             Throws: IOException upon systen errors.
751 
752             Throws: IllegalArgumentException if a segment exists but as
753             a file instead of a folder.
754 
755     ***********************************************************************/
756 
757     final FilePath create ()
758     {
759             createPath (this.toString);
760             return this;
761     }
762 
763     /***********************************************************************
764 
765             List the set of filenames within this folder, using
766             the provided filter to control the list:
767             ---
768             bool delegate (FilePath path, bool isFolder) Filter;
769             ---
770 
771             Returning true from the filter includes the given path,
772             whilst returning false excludes it. Parameter 'isFolder'
773             indicates whether the path is a file or folder.
774 
775             Note that paths composed of '.' characters are ignored.
776 
777     ***********************************************************************/
778 
779     final FilePath[] toList (scope Filter filter = null)
780     {
781             FilePath[] paths;
782 
783             foreach (info; this)
784                     {
785                     auto p = from (info);
786 
787                     // test this entry for inclusion
788                     if (filter is null || filter (p, info.folder))
789                         paths ~= p;
790                     else
791                     {
792                        import core.memory;
793                        destroy(p);
794                        GC.free(cast(void*) p);
795                     }
796                     }
797             return paths;
798     }
799 
800     /***********************************************************************
801 
802             Construct a FilePath from the given FileInfo.
803 
804     ***********************************************************************/
805 
806     static FilePath from (ref FileInfo info)
807     {
808             char[512] tmp = void;
809 
810             auto len = info.path.length + info.name.length;
811             verify(tmp.length - len > 1);
812 
813             // construct full pathname
814             tmp [0 .. info.path.length] = info.path;
815             tmp [info.path.length .. len] = info.name;
816             return FilePath(tmp[0 .. len]).isFolder(info.folder);
817     }
818 
819     /***********************************************************************
820 
821             Does this path currently exist?.
822 
823     ***********************************************************************/
824 
825     final bool exists ()
826     {
827             return FS.exists (cString);
828     }
829 
830     /***********************************************************************
831 
832             Returns the time of the last modification. Accurate
833             to whatever the OS supports, and in a format dictated
834             by the file system. For example NTFS keeps UTC time,
835             while FAT timestamps are based on the local time.
836 
837     ***********************************************************************/
838 
839     final Time modified ()
840     {
841             return timeStamps.modified;
842     }
843 
844     /***********************************************************************
845 
846             Returns the time of the last access. Accurate to
847             whatever the OS supports, and in a format dictated
848             by the file system. For example NTFS keeps UTC time,
849             while FAT timestamps are based on the local time.
850 
851     ***********************************************************************/
852 
853     final Time accessed ()
854     {
855             return timeStamps.accessed;
856     }
857 
858     /***********************************************************************
859 
860             Returns the time of file creation. Accurate to
861             whatever the OS supports, and in a format dictated
862             by the file system. For example NTFS keeps UTC time,
863             while FAT timestamps are based on the local time.
864 
865     ***********************************************************************/
866 
867     final Time created ()
868     {
869             return timeStamps.created;
870     }
871 
872     /***********************************************************************
873 
874             Change the name or location of a file/directory, and
875             adopt the provided Path.
876 
877     ***********************************************************************/
878 
879     final FilePath rename (FilePath dst)
880     {
881             FS.rename (cString, dst.cString);
882             return this.set (dst);
883     }
884 
885     /***********************************************************************
886 
887             Transfer the content of another file to this one. Returns a
888             reference to this class on success, or throws an IOException
889             upon failure.
890 
891     ***********************************************************************/
892 
893     final FilePath copy (cstring source)
894     {
895             auto src = source~'\0';
896             FS.copy (assumeUnique(src), cString);
897             return this;
898     }
899 
900     /***********************************************************************
901 
902             Return the file length (in bytes).
903 
904     ***********************************************************************/
905 
906     final ulong fileSize ()
907     {
908             return FS.fileSize (cString);
909     }
910 
911     /***********************************************************************
912 
913             Is this file writable?
914 
915     ***********************************************************************/
916 
917     final bool isWritable ()
918     {
919             return FS.isWritable (cString);
920     }
921 
922     /***********************************************************************
923 
924             Is this file actually a folder/directory?
925 
926     ***********************************************************************/
927 
928     final bool isFolder ()
929     {
930             if (dir_)
931                 return true;
932 
933             return FS.isFolder (cString);
934     }
935 
936     /***********************************************************************
937 
938             Is this a regular file?
939 
940     ***********************************************************************/
941 
942     final bool isFile ()
943     {
944             if (dir_)
945                 return false;
946 
947             return FS.isFile (cString);
948     }
949 
950     /***********************************************************************
951 
952             Return timestamp information.
953 
954             Timstamps are returns in a format dictated by the
955             file system. For example NTFS keeps UTC time,
956             while FAT timestamps are based on the local time.
957 
958     ***********************************************************************/
959 
960     final Stamps timeStamps ()
961     {
962             return FS.timeStamps (cString);
963     }
964 
965     /***********************************************************************
966 
967             Transfer the content of another file to this one. Returns a
968             reference to this class on success, or throws an IOException
969             upon failure.
970 
971     ***********************************************************************/
972 
973     final FilePath copy (FilePath src)
974     {
975             FS.copy (src.cString, cString);
976             return this;
977     }
978 
979     /***********************************************************************
980 
981             Remove the file/directory from the file system.
982 
983     ***********************************************************************/
984 
985     final FilePath remove ()
986     {
987             FS.remove (cString);
988             return this;
989     }
990 
991     /***********************************************************************
992 
993            change the name or location of a file/directory, and
994            adopt the provided Path.
995 
996            Note: If dst is not zero terminated, a terminated will be added
997                  which means that allocation will happen.
998 
999     ***********************************************************************/
1000 
1001     final FilePath rename (cstring dst)
1002     {
1003         verify(dst !is null);
1004 
1005         if (dst[$-1] != '\0')
1006             dst ~= '\0';
1007 
1008         FS.rename (cString, dst);
1009 
1010         return this.set (dst, true);
1011     }
1012 
1013     /***********************************************************************
1014 
1015             Create a new file.
1016 
1017             Params:
1018                 mode = mode for the new file (defaults to 0660)
1019 
1020     ***********************************************************************/
1021 
1022     final FilePath createFile (mode_t mode = Octal!("660"))
1023     {
1024             FS.createFile (cString, mode);
1025             return this;
1026     }
1027 
1028     /***********************************************************************
1029 
1030             Create a new directory.
1031 
1032             Params:
1033                 mode = mode for the new directory (defaults to 0777)
1034 
1035     ***********************************************************************/
1036 
1037     final FilePath createFolder (mode_t mode = Octal!("777"))
1038     {
1039             FS.createFolder (cString, mode);
1040             return this;
1041     }
1042 
1043     /***********************************************************************
1044 
1045             List the set of filenames within this folder.
1046 
1047             Each path and filename is passed to the provided
1048             delegate, along with the path prefix and whether
1049             the entry is a folder or not.
1050 
1051             Returns the number of files scanned.
1052 
1053     ***********************************************************************/
1054 
1055     final int opApply (scope int delegate(ref FileInfo) dg)
1056     {
1057             return FS.list (cString, dg);
1058     }
1059 
1060     /***********************************************************************
1061 
1062         Create a new name for a file (also known as -hard-linking)
1063 
1064         Params:
1065             dst = FilePath with the new file name
1066 
1067         Returns:
1068             this.path set to the new destination location if it was moved,
1069             null otherwise.
1070 
1071         See_Also:
1072            man 2 link
1073 
1074     ***********************************************************************/
1075 
1076     public final FilePath link ( FilePath dst )
1077     {
1078         if (.link(this.cString().ptr, dst.cString().ptr) is -1)
1079         {
1080             FS.exception(this.toString());
1081         }
1082 
1083         return this;
1084     }
1085 }
1086 
1087 /*******************************************************************************
1088 
1089 *******************************************************************************/
1090 
1091 interface PathView
1092 {
1093     alias FS.Stamps         Stamps;
1094 
1095     /***********************************************************************
1096 
1097             Return the complete text of this filepath.
1098 
1099     ***********************************************************************/
1100 
1101     abstract istring toString ();
1102 
1103     /***********************************************************************
1104 
1105             Return the complete text of this filepath.
1106 
1107     ***********************************************************************/
1108 
1109     abstract mstring cString ();
1110 
1111     /***********************************************************************
1112 
1113             Return the root of this path. Roots are constructs such as
1114             "C:".
1115 
1116     ***********************************************************************/
1117 
1118     abstract cstring root ();
1119 
1120     /***********************************************************************
1121 
1122             Return the file path. Paths may start and end with a "/".
1123             The root path is "/" and an unspecified path is returned as
1124             an empty string. Directory paths may be split such that the
1125             directory name is placed into the 'name' member; directory
1126             paths are treated no differently than file paths.
1127 
1128     ***********************************************************************/
1129 
1130     abstract cstring folder ();
1131 
1132     /***********************************************************************
1133 
1134             Return the name of this file, or directory, excluding a
1135             suffix.
1136 
1137     ***********************************************************************/
1138 
1139     abstract cstring name ();
1140 
1141     /***********************************************************************
1142 
1143             Ext is the tail of the filename, rightward of the rightmost
1144             '.' separator e.g. path "foo.bar" has ext "bar". Note that
1145             patterns of adjacent separators are treated specially; for
1146             example, ".." will wind up with no ext at all.
1147 
1148     ***********************************************************************/
1149 
1150     abstract cstring ext ();
1151 
1152     /***********************************************************************
1153 
1154             Suffix is like ext, but includes the separator e.g. path
1155             "foo.bar" has suffix ".bar".
1156 
1157     ***********************************************************************/
1158 
1159     abstract cstring suffix ();
1160 
1161     /***********************************************************************
1162 
1163             Return the root + folder combination.
1164 
1165     ***********************************************************************/
1166 
1167     abstract cstring path ();
1168 
1169     /***********************************************************************
1170 
1171             Return the name + suffix combination.
1172 
1173     ***********************************************************************/
1174 
1175     abstract cstring file ();
1176 
1177     /***********************************************************************
1178 
1179             Returns true if this FilePath is *not* relative to the
1180             current working directory.
1181 
1182     ***********************************************************************/
1183 
1184     abstract bool isAbsolute ();
1185 
1186     /***********************************************************************
1187 
1188             Returns true if this FilePath is empty.
1189 
1190     ***********************************************************************/
1191 
1192     abstract bool isEmpty ();
1193 
1194     /***********************************************************************
1195 
1196             Returns true if this FilePath has a parent.
1197 
1198     ***********************************************************************/
1199 
1200     abstract bool isChild ();
1201 
1202     /***********************************************************************
1203 
1204             Does this path currently exist?
1205 
1206     ***********************************************************************/
1207 
1208     abstract bool exists ();
1209 
1210     /***********************************************************************
1211 
1212             Returns the time of the last modification. Accurate
1213             to whatever the OS supports.
1214 
1215     ***********************************************************************/
1216 
1217     abstract Time modified ();
1218 
1219     /***********************************************************************
1220 
1221             Returns the time of the last access. Accurate to
1222             whatever the OS supports.
1223 
1224     ***********************************************************************/
1225 
1226     abstract Time accessed ();
1227 
1228     /***********************************************************************
1229 
1230             Returns the time of file creation. Accurate to
1231             whatever the OS supports.
1232 
1233     ***********************************************************************/
1234 
1235     abstract Time created ();
1236 
1237     /***********************************************************************
1238 
1239             Return the file length (in bytes).
1240 
1241     ***********************************************************************/
1242 
1243     abstract ulong fileSize ();
1244 
1245     /***********************************************************************
1246 
1247             Is this file writable?
1248 
1249     ***********************************************************************/
1250 
1251     abstract bool isWritable ();
1252 
1253     /***********************************************************************
1254 
1255             Is this file actually a folder/directory?
1256 
1257     ***********************************************************************/
1258 
1259     abstract bool isFolder ();
1260 
1261     /***********************************************************************
1262 
1263             Return timestamp information.
1264 
1265     ***********************************************************************/
1266 
1267     abstract Stamps timeStamps ();
1268 }
1269 
1270 unittest
1271 {
1272     test (FilePath("/foo").append("bar").pop == "/foo");
1273     test (FilePath("/foo/").append("bar").pop == "/foo");
1274 
1275     auto fp = new FilePath(r"/home/foo/bar");
1276     fp ~= "john";
1277     test (fp == r"/home/foo/bar/john");
1278     fp.set (r"/");
1279     fp ~= "john";
1280     test (fp == r"/john");
1281     fp.set("foo.bar");
1282     fp ~= "john";
1283     test (fp == r"foo.bar/john");
1284     fp.set("");
1285     fp ~= "john";
1286     test (fp == r"john");
1287 
1288     fp.set(r"/home/foo/bar/john/foo.d");
1289     test (fp.pop == r"/home/foo/bar/john");
1290     test (fp.pop == r"/home/foo/bar");
1291     test (fp.pop == r"/home/foo");
1292     test (fp.pop == r"/home");
1293     test (fp.pop == r"/");
1294     test (fp.pop == r"");
1295 
1296     // special case for popping empty names
1297     fp.set (r"/home/foo/bar/john/");
1298     test (fp.parent == r"/home/foo/bar");
1299 
1300     fp = new FilePath;
1301     fp.set (r"/home/foo/bar/john/");
1302     test (fp.isAbsolute);
1303     test (fp.name == "");
1304     test (fp.folder == r"/home/foo/bar/john/");
1305     test (fp == r"/home/foo/bar/john/");
1306     test (fp.path == r"/home/foo/bar/john/");
1307     test (fp.file == "");
1308     test (fp.suffix == "");
1309     test (fp.root == "");
1310     test (fp.ext == "");
1311     test (fp.isChild);
1312 
1313     fp = new FilePath(r"/home/foo/bar/john");
1314     test (fp.isAbsolute);
1315     test (fp.name == "john");
1316     test (fp.folder == r"/home/foo/bar/");
1317     test (fp == r"/home/foo/bar/john");
1318     test (fp.path == r"/home/foo/bar/");
1319     test (fp.file == r"john");
1320     test (fp.suffix == "");
1321     test (fp.ext == "");
1322     test (fp.isChild);
1323 
1324     fp.pop;
1325     test (fp.isAbsolute);
1326     test (fp.name == "bar");
1327     test (fp.folder == r"/home/foo/");
1328     test (fp == r"/home/foo/bar");
1329     test (fp.path == r"/home/foo/");
1330     test (fp.file == r"bar");
1331     test (fp.suffix == r"");
1332     test (fp.ext == "");
1333     test (fp.isChild);
1334 
1335     fp.pop;
1336     test (fp.isAbsolute);
1337     test (fp.name == "foo");
1338     test (fp.folder == r"/home/");
1339     test (fp == r"/home/foo");
1340     test (fp.path == r"/home/");
1341     test (fp.file == r"foo");
1342     test (fp.suffix == r"");
1343     test (fp.ext == "");
1344     test (fp.isChild);
1345 
1346     fp.pop;
1347     test (fp.isAbsolute);
1348     test (fp.name == "home");
1349     test (fp.folder == r"/");
1350     test (fp == r"/home");
1351     test (fp.path == r"/");
1352     test (fp.file == r"home");
1353     test (fp.suffix == r"");
1354     test (fp.ext == "");
1355     test (fp.isChild);
1356 
1357     fp = new FilePath(r"foo/bar/john.doe");
1358     test (!fp.isAbsolute);
1359     test (fp.name == "john");
1360     test (fp.folder == r"foo/bar/");
1361     test (fp.suffix == r".doe");
1362     test (fp.file == r"john.doe");
1363     test (fp == r"foo/bar/john.doe");
1364     test (fp.ext == "doe");
1365     test (fp.isChild);
1366 
1367     fp = new FilePath(r"/doe");
1368     test (fp.isAbsolute);
1369     test (fp.suffix == r"");
1370     test (fp == r"/doe");
1371     test (fp.name == "doe");
1372     test (fp.folder == r"/");
1373     test (fp.file == r"doe");
1374     test (fp.ext == "");
1375     test (fp.isChild);
1376 
1377     fp = new FilePath(r"john.doe.foo");
1378     test (!fp.isAbsolute);
1379     test (fp.name == "john.doe");
1380     test (fp.folder == r"");
1381     test (fp.suffix == r".foo");
1382     test (fp == r"john.doe.foo");
1383     test (fp.file == r"john.doe.foo");
1384     test (fp.ext == "foo");
1385     test (!fp.isChild);
1386 
1387     fp = new FilePath(r".doe");
1388     test (!fp.isAbsolute);
1389     test (fp.suffix == r"");
1390     test (fp == r".doe");
1391     test (fp.name == ".doe");
1392     test (fp.folder == r"");
1393     test (fp.file == r".doe");
1394     test (fp.ext == "");
1395     test (!fp.isChild);
1396 
1397     fp = new FilePath(r"doe");
1398     test (!fp.isAbsolute);
1399     test (fp.suffix == r"");
1400     test (fp == r"doe");
1401     test (fp.name == "doe");
1402     test (fp.folder == r"");
1403     test (fp.file == r"doe");
1404     test (fp.ext == "");
1405     test (!fp.isChild);
1406 
1407     fp = new FilePath(r".");
1408     test (!fp.isAbsolute);
1409     test (fp.suffix == r"");
1410     test (fp == r".");
1411     test (fp.name == ".");
1412     test (fp.folder == r"");
1413     test (fp.file == r".");
1414     test (fp.ext == "");
1415     test (!fp.isChild);
1416 
1417     fp = new FilePath(r"..");
1418     test (!fp.isAbsolute);
1419     test (fp.suffix == r"");
1420     test (fp == r"..");
1421     test (fp.name == "..");
1422     test (fp.folder == r"");
1423     test (fp.file == r"..");
1424     test (fp.ext == "");
1425     test (!fp.isChild);
1426 
1427     fp = new FilePath(r"/a/b/c/d/e/foo.bar");
1428     test (fp.isAbsolute);
1429     fp.folder (r"/a/b/c/");
1430     test (fp.suffix == r".bar");
1431     test (fp == r"/a/b/c/foo.bar");
1432     test (fp.name == "foo");
1433     test (fp.folder == r"/a/b/c/");
1434     test (fp.file == r"foo.bar");
1435     test (fp.ext == "bar");
1436     test (fp.isChild);
1437 
1438     fp = new FilePath(r"/a/b/c/d/e/foo.bar");
1439     test (fp.isAbsolute);
1440     fp.folder (r"/a/b/c/d/e/f/g/");
1441     test (fp.suffix == r".bar");
1442     test (fp == r"/a/b/c/d/e/f/g/foo.bar");
1443     test (fp.name == "foo");
1444     test (fp.folder == r"/a/b/c/d/e/f/g/");
1445     test (fp.file == r"foo.bar");
1446     test (fp.ext == "bar");
1447     test (fp.isChild);
1448 
1449     fp = new FilePath(r"/foo/bar/test.bar");
1450     test (fp.path == "/foo/bar/");
1451     fp = new FilePath(r"\foo\bar\test.bar");
1452     test (fp.path == r"/foo/bar/");
1453 
1454     fp = new FilePath("");
1455     test (fp.isEmpty);
1456     test (!fp.isChild);
1457     test (!fp.isAbsolute);
1458     test (fp.suffix == r"");
1459     test (fp == r"");
1460     test (fp.name == "");
1461     test (fp.folder == r"");
1462     test (fp.file == r"");
1463     test (fp.ext == "");
1464 }