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