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