comparison tango/tango/io/FilePath.d @ 132:1700239cab2e trunk

[svn r136] MAJOR UNSTABLE UPDATE!!! Initial commit after moving to Tango instead of Phobos. Lots of bugfixes... This build is not suitable for most things.
author lindquist
date Fri, 11 Jan 2008 17:57:40 +0100
parents
children
comparison
equal deleted inserted replaced
131:5825d48b27d1 132:1700239cab2e
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
13 author: Kris
14
15 *******************************************************************************/
16
17 module tango.io.FilePath;
18
19 private import tango.sys.Common;
20
21 private import tango.io.FileConst;
22
23 private import tango.core.Exception;
24
25 private import tango.time.Time;
26
27 /*******************************************************************************
28
29 *******************************************************************************/
30
31 version (Win32)
32 {
33 version (Win32SansUnicode)
34 {
35 alias char T;
36 private extern (C) int strlen (char *s);
37 private alias WIN32_FIND_DATA FIND_DATA;
38 }
39 else
40 {
41 alias wchar T;
42 private extern (C) int wcslen (wchar *s);
43 private alias WIN32_FIND_DATAW FIND_DATA;
44 }
45 }
46
47 version (Posix)
48 {
49 private import tango.stdc.stdio;
50 private import tango.stdc.string;
51 private import tango.stdc.posix.utime;
52 private import tango.stdc.posix.dirent;
53 }
54
55 /*******************************************************************************
56
57 *******************************************************************************/
58
59 private extern (C) void memmove (void* dst, void* src, uint bytes);
60
61 /*******************************************************************************
62
63 Models a file path. These are expected to be used as the constructor
64 argument to various file classes. The intention is that they easily
65 convert to other representations such as absolute, canonical, or Url.
66
67 File paths containing non-ansi characters should be UTF-8 encoded.
68 Supporting Unicode in this manner was deemed to be more suitable
69 than providing a wchar version of FilePath, and is both consistent
70 & compatible with the approach taken with the Uri class.
71
72 FilePath is designed to be transformed, thus each mutating method
73 modifies the internal content. There is a read-only base-class
74 called PathView, which can be used to provide a view into the
75 content as desired.
76
77 Note that patterns of adjacent '.' separators are treated specially
78 in that they will be assigned to the name instead of the suffix. In
79 addition, a '.' at the start of a name signifies it does not belong
80 to the suffix i.e. ".file" is a name rather than a suffix.
81
82 Note also that normalization of path-separators occurs by default.
83 This means that the use of '\' characters with be converted into
84 '/' instead while parsing. To mutate the path into an O/S native
85 version, use the native() method. To obtain a copy instead, use the
86 path.dup.native sequence
87
88 Compile with -version=Win32SansUnicode to enable Win95 & Win32s file
89 support.
90
91 *******************************************************************************/
92
93 class FilePath : PathView
94 {
95 private char[] fp; // filepath with trailing 0
96
97 private bool dir_; // this represents a dir?
98
99 private int end_, // before the trailing 0
100 name_, // file/dir name
101 folder_, // path before name
102 suffix_; // after rightmost '.'
103
104 public alias set opAssign; // path = x;
105 public alias append opCatAssign; // path ~= x;
106
107 /***********************************************************************
108
109 Filter used for screening paths via toList()
110
111 ***********************************************************************/
112
113 public alias bool delegate (FilePath, bool) Filter;
114
115 /***********************************************************************
116
117 Passed around during file-scanning
118
119 ***********************************************************************/
120
121 struct FileInfo
122 {
123 char[] path,
124 name;
125 ulong bytes;
126 bool folder;
127 }
128
129 /***********************************************************************
130
131 Call-site shortcut to create a FilePath instance. This
132 enables the same syntax as struct usage, so may expose
133 a migration path
134
135 ***********************************************************************/
136
137 static FilePath opCall (char[] filepath = null)
138 {
139 return new FilePath (filepath);
140 }
141
142 /***********************************************************************
143
144 Create a FilePath from a copy of the provided string.
145
146 FilePath assumes both path & name are present, and therefore
147 may split what is otherwise a logically valid path. That is,
148 the 'name' of a file is typically the path segment following
149 a rightmost path-separator. The intent is to treat files and
150 directories in the same manner; as a name with an optional
151 ancestral structure. It is possible to bias the interpretation
152 by adding a trailing path-separator to the argument. Doing so
153 will result in an empty name attribute.
154
155 With regard to the filepath copy, we found the common case to
156 be an explicit .dup, whereas aliasing appeared to be rare by
157 comparison. We also noted a large proportion interacting with
158 C-oriented OS calls, implying the postfix of a null terminator.
159 Thus, FilePath combines both as a single operation.
160
161 ***********************************************************************/
162
163 this (char[] filepath = null)
164 {
165 set (filepath);
166 }
167
168 // now converts to '/' always. Use toNative to get Win32 paths
169 deprecated this (char[] filepath, bool native)
170 {
171 set (filepath);
172 }
173
174 /***********************************************************************
175
176 Return the complete text of this filepath
177
178 ***********************************************************************/
179
180 final char[] toString ()
181 {
182 return fp [0 .. end_];
183 }
184
185 /***********************************************************************
186
187 Duplicate this path
188
189 ***********************************************************************/
190
191 final FilePath dup ()
192 {
193 return FilePath (toString);
194 }
195
196 /***********************************************************************
197
198 Return the complete text of this filepath as a null
199 terminated string for use with a C api. Use toString
200 instead for any D api.
201
202 Note that the nul is always embedded within the string
203 maintained by FilePath, so there's no heap overhead when
204 making a C call
205
206 ***********************************************************************/
207
208 final char[] cString ()
209 {
210 return fp [0 .. end_+1];
211 }
212
213 /***********************************************************************
214
215 Return the root of this path. Roots are constructs such as
216 "c:"
217
218 ***********************************************************************/
219
220 final char[] root ()
221 {
222 return fp [0 .. folder_];
223 }
224
225 /***********************************************************************
226
227 Return the file path. Paths may start and end with a "/".
228 The root path is "/" and an unspecified path is returned as
229 an empty string. Directory paths may be split such that the
230 directory name is placed into the 'name' member; directory
231 paths are treated no differently than file paths
232
233 ***********************************************************************/
234
235 final char[] folder ()
236 {
237 return fp [folder_ .. name_];
238 }
239
240 /***********************************************************************
241
242 Returns a path representing the parent of this one. This
243 will typically return the current path component, though
244 with a special case where the name component is empty. In
245 such cases, the path is scanned for a prior segment:
246 ---
247 normal: /x/y/z => /x/y
248 special: /x/y/ => /x
249 ---
250
251 Note that this returns a path suitable for splitting into
252 path and name components (there's no trailing separator).
253
254 See pop() also, which is generally more useful when working
255 with FilePath instances
256
257 ***********************************************************************/
258
259 final char[] parent ()
260 {
261 auto p = path;
262 if (name.length is 0)
263 for (int i=p.length-1; --i > 0;)
264 if (p[i] is FileConst.PathSeparatorChar)
265 {
266 p = p[0 .. i];
267 break;
268 }
269 return stripped (p);
270 }
271
272 /***********************************************************************
273
274 Return the name of this file, or directory.
275
276 ***********************************************************************/
277
278 final char[] name ()
279 {
280 return fp [name_ .. suffix_];
281 }
282
283 /***********************************************************************
284
285 Ext is the tail of the filename, rightward of the rightmost
286 '.' separator e.g. path "foo.bar" has ext "bar". Note that
287 patterns of adjacent separators are treated specially; for
288 example, ".." will wind up with no ext at all
289
290 ***********************************************************************/
291
292 final char[] ext ()
293 {
294 auto x = suffix;
295 if (x.length)
296 x = x [1..$];
297 return x;
298 }
299
300 /***********************************************************************
301
302 Suffix is like ext, but includes the separator e.g. path
303 "foo.bar" has suffix ".bar"
304
305 ***********************************************************************/
306
307 final char[] suffix ()
308 {
309 return fp [suffix_ .. end_];
310 }
311
312 /***********************************************************************
313
314 return the root + folder combination
315
316 ***********************************************************************/
317
318 final char[] path ()
319 {
320 return fp [0 .. name_];
321 }
322
323 /***********************************************************************
324
325 return the name + suffix combination
326
327 ***********************************************************************/
328
329 final char[] file ()
330 {
331 return fp [name_ .. end_];
332 }
333
334 /***********************************************************************
335
336 Returns true if all fields are equal.
337
338 ***********************************************************************/
339
340 final override int opEquals (Object o)
341 {
342 return (this is o) || (o && toString == o.toString);
343 }
344
345 /***********************************************************************
346
347 Does this FilePath equate to the given text?
348
349 ***********************************************************************/
350
351 final override int opEquals (char[] s)
352 {
353 return toString() == s;
354 }
355
356 /***********************************************************************
357
358 Returns true if this FilePath is *not* relative to the
359 current working directory
360
361 ***********************************************************************/
362
363 final bool isAbsolute ()
364 {
365 return (folder_ > 0) ||
366 (folder_ < end_ && fp[folder_] is FileConst.PathSeparatorChar);
367 }
368
369 /***********************************************************************
370
371 Returns true if this FilePath is empty
372
373 ***********************************************************************/
374
375 final bool isEmpty ()
376 {
377 return end_ is 0;
378 }
379
380 /***********************************************************************
381
382 Returns true if this FilePath has a parent. Note that a
383 parent is defined by the presence of a path-separator in
384 the path. This means 'foo' within "\foo" is considered a
385 child of the root
386
387 ***********************************************************************/
388
389 final bool isChild ()
390 {
391 return folder.length > 0;
392 }
393
394 /***********************************************************************
395
396 Replace all 'from' instances with 'to'
397
398 ***********************************************************************/
399
400 final FilePath replace (char from, char to)
401 {
402 foreach (inout char c; path)
403 if (c is from)
404 c = to;
405 return this;
406 }
407
408 /***********************************************************************
409
410 Convert path separators to a standard format, using '/' as
411 the path separator. This is compatible with URI and all of
412 the contemporary O/S which Tango supports. Known exceptions
413 include the Windows command-line processor, which considers
414 '/' characters to be switches instead. Use the native()
415 method to support that.
416
417 Note: mutates the current path.
418
419 ***********************************************************************/
420
421 final FilePath standard ()
422 {
423 return replace ('\\', '/');
424 }
425
426 /***********************************************************************
427
428 Convert to native O/S path separators where that is required,
429 such as when dealing with the Windows command-line.
430
431 Note: mutates the current path. Use this pattern to obtain a
432 copy instead: path.dup.native
433
434 ***********************************************************************/
435
436 final FilePath native ()
437 {
438 version (Win32)
439 return replace ('/', '\\');
440 else
441 return this;
442 }
443
444 /***********************************************************************
445
446 Concatenate text to this path; no separators are added.
447 See join() also
448
449 ***********************************************************************/
450
451 final FilePath cat (char[][] others...)
452 {
453 foreach (other; others)
454 {
455 auto len = end_ + other.length;
456 expand (len);
457 fp [end_ .. len] = other;
458 fp [len] = 0;
459 end_ = len;
460 }
461 return parse;
462 }
463
464 /***********************************************************************
465
466 Append a folder to this path. A leading separator is added
467 as required
468
469 ***********************************************************************/
470
471 final FilePath append (char[] path)
472 {
473 if (file.length)
474 path = prefixed (path);
475 return cat (path);
476 }
477
478 /***********************************************************************
479
480 Prepend a folder to this path. A trailing separator is added
481 if needed
482
483 ***********************************************************************/
484
485 final FilePath prepend (char[] path)
486 {
487 adjust (0, folder_, folder_, padded (path));
488 return parse;
489 }
490
491 /***********************************************************************
492
493 Reset the content of this path to that of another and
494 reparse
495
496 ***********************************************************************/
497
498 FilePath set (FilePath path)
499 {
500 return set (path.toString);
501 }
502
503 /***********************************************************************
504
505 Reset the content of this path, and reparse.
506
507 ***********************************************************************/
508
509 final FilePath set (char[] path)
510 {
511 end_ = path.length;
512
513 expand (end_);
514 if (end_)
515 fp[0 .. end_] = path;
516
517 fp[end_] = '\0';
518 return parse;
519 }
520
521 /***********************************************************************
522
523 Sidestep the normal lookup for paths that are known to
524 be folders. Where folder is true, file-system lookups
525 will be skipped.
526
527 ***********************************************************************/
528
529 final FilePath isFolder (bool folder)
530 {
531 dir_ = folder;
532 return this;
533 }
534
535 /***********************************************************************
536
537 Replace the root portion of this path
538
539 ***********************************************************************/
540
541 final FilePath root (char[] other)
542 {
543 auto x = adjust (0, folder_, folder_, padded (other, ':'));
544 folder_ += x;
545 suffix_ += x;
546 name_ += x;
547 return this;
548 }
549
550 /***********************************************************************
551
552 Replace the folder portion of this path. The folder will be
553 padded with a path-separator as required
554
555 ***********************************************************************/
556
557 final FilePath folder (char[] other)
558 {
559 auto x = adjust (folder_, name_, name_ - folder_, padded (other));
560 suffix_ += x;
561 name_ += x;
562 return this;
563 }
564
565 /***********************************************************************
566
567 Replace the name portion of this path
568
569 ***********************************************************************/
570
571 final FilePath name (char[] other)
572 {
573 auto x = adjust (name_, suffix_, suffix_ - name_, other);
574 suffix_ += x;
575 return this;
576 }
577
578 /***********************************************************************
579
580 Replace the suffix portion of this path. The suffix will be
581 prefixed with a file-separator as required
582
583 ***********************************************************************/
584
585 final FilePath suffix (char[] other)
586 {
587 adjust (suffix_, end_, end_ - suffix_, prefixed (other, '.'));
588 return this;
589 }
590
591 /***********************************************************************
592
593 Replace the root and folder portions of this path and
594 reparse. The replacement will be padded with a path
595 separator as required
596
597 ***********************************************************************/
598
599 final FilePath path (char[] other)
600 {
601 adjust (0, name_, name_, padded (other));
602 return parse;
603 }
604
605 /***********************************************************************
606
607 Replace the file and suffix portions of this path and
608 reparse. The replacement will be prefixed with a suffix
609 separator as required
610
611 ***********************************************************************/
612
613 final FilePath file (char[] other)
614 {
615 adjust (name_, end_, end_ - name_, other);
616 return parse;
617 }
618
619 /***********************************************************************
620
621 Pop to the parent of the current filepath (in place)
622
623 ***********************************************************************/
624
625 final FilePath pop ()
626 {
627 auto path = parent();
628 end_ = path.length;
629 fp[end_] = '\0';
630 return parse;
631 }
632
633 /***********************************************************************
634
635 Join a set of path specs together. A path separator is
636 potentially inserted between each of the segments.
637
638 ***********************************************************************/
639
640 static char[] join (char[][] paths...)
641 {
642 char[] result;
643
644 foreach (path; paths)
645 result ~= padded (path);
646
647 return result.length ? result [0 .. $-1] : null;
648 }
649
650 /***********************************************************************
651
652 Return an adjusted path such that non-empty instances do not
653 have a trailing separator
654
655 ***********************************************************************/
656
657 static char[] stripped (char[] path, char c = FileConst.PathSeparatorChar)
658 {
659 if (path.length && path[$-1] is c)
660 path = path [0 .. $-1];
661 return path;
662 }
663
664 /***********************************************************************
665
666 Return an adjusted path such that non-empty instances always
667 have a trailing separator
668
669 ***********************************************************************/
670
671 static char[] padded (char[] path, char c = FileConst.PathSeparatorChar)
672 {
673 if (path.length && path[$-1] != c)
674 path = path ~ c;
675 return path;
676 }
677
678 /***********************************************************************
679
680 Return an adjusted path such that non-empty instances always
681 have a prefixed separator
682
683 ***********************************************************************/
684
685 static char[] prefixed (char[] s, char c = FileConst.PathSeparatorChar)
686 {
687 if (s.length && s[0] != c)
688 s = c ~ s;
689 return s;
690 }
691
692 /***********************************************************************
693
694 Parse the path spec
695
696 ***********************************************************************/
697
698 private final FilePath parse ()
699 {
700 folder_ = 0;
701 name_ = suffix_ = -1;
702
703 for (int i=end_; --i >= 0;)
704 switch (fp[i])
705 {
706 case FileConst.FileSeparatorChar:
707 if (name_ < 0)
708 if (suffix_ < 0 && i && fp[i-1] != '.')
709 suffix_ = i;
710 break;
711
712 version (Win32)
713 {
714 case '\\':
715 fp[i] = '/';
716 }
717 case FileConst.PathSeparatorChar:
718 if (name_ < 0)
719 name_ = i + 1;
720 break;
721
722 version (Win32)
723 {
724 case FileConst.RootSeparatorChar:
725 folder_ = i + 1;
726 break;
727 }
728
729 default:
730 break;
731 }
732
733 if (name_ < 0)
734 name_ = folder_;
735
736 if (suffix_ < 0 || suffix_ is name_)
737 suffix_ = end_;
738
739 return this;
740 }
741
742 /***********************************************************************
743
744 Potentially make room for more content
745
746 ***********************************************************************/
747
748 private final void expand (uint size)
749 {
750 ++size;
751 if (fp.length < size)
752 fp.length = (size + 127) & ~127;
753 }
754
755 /***********************************************************************
756
757 Insert/delete internal content
758
759 ***********************************************************************/
760
761 private final int adjust (int head, int tail, int len, char[] sub)
762 {
763 len = sub.length - len;
764
765 // don't destroy self-references!
766 if (len && sub.ptr >= fp.ptr+head+len && sub.ptr < fp.ptr+fp.length)
767 {
768 char[512] tmp = void;
769 assert (sub.length < tmp.length);
770 sub = tmp[0..sub.length] = sub;
771 }
772
773 // make some room if necessary
774 expand (len + end_);
775
776 // slide tail around to insert or remove space
777 memmove (fp.ptr+tail+len, fp.ptr+tail, end_ +1 - tail);
778
779 // copy replacement
780 memmove (fp.ptr + head, sub.ptr, sub.length);
781
782 // adjust length
783 end_ += len;
784 return len;
785 }
786
787
788 /**********************************************************************/
789 /********************** file-system methods ***************************/
790 /**********************************************************************/
791
792
793 /***********************************************************************
794
795 Does this path currently exist?
796
797 ***********************************************************************/
798
799 final bool exists ()
800 {
801 try {
802 fileSize();
803 return true;
804 } catch (IOException){}
805 return false;
806 }
807
808 /***********************************************************************
809
810 Returns the time of the last modification. Accurate
811 to whatever the OS supports
812
813 ***********************************************************************/
814
815 final Time modified ()
816 {
817 return timeStamps.modified;
818 }
819
820 /***********************************************************************
821
822 Returns the time of the last access. Accurate to
823 whatever the OS supports
824
825 ***********************************************************************/
826
827 final Time accessed ()
828 {
829 return timeStamps.accessed;
830 }
831
832 /***********************************************************************
833
834 Returns the time of file creation. Accurate to
835 whatever the OS supports
836
837 ***********************************************************************/
838
839 final Time created ()
840 {
841 return timeStamps.created;
842 }
843
844 /***********************************************************************
845
846 Create an entire path consisting of this folder along with
847 all parent folders. The path must not contain '.' or '..'
848 segments. Related methods include PathUtil.normalize() and
849 FileSystem.toAbsolute()
850
851 Note that each segment is created as a folder, including the
852 trailing segment.
853
854 Returns: a chaining reference (this)
855
856 Throws: IOException upon systen errors
857
858 Throws: IllegalArgumentException if the path contains invalid
859 path segment names (such as '.' or '..') or a segment
860 exists but as a file instead of a folder
861
862 ***********************************************************************/
863
864 final FilePath create ()
865 {
866 auto segment = name;
867 if (segment.length > 0)
868 {
869 if (segment == FileConst.CurrentDirString ||
870 segment == FileConst.ParentDirString)
871 badArg ("FilePath.create :: invalid path: ");
872
873 if (this.exists)
874 if (this.isFolder)
875 return this;
876 else
877 badArg ("FilePath.create :: file/folder conflict: ");
878
879 FilePath(this.parent).create;
880 return createFolder;
881 }
882 return this;
883 }
884
885 /***********************************************************************
886
887 List the set of filenames within this folder, using
888 the provided filter to control the list:
889 ---
890 bool delegate (FilePath path, bool isFolder) Filter
891 ---
892
893 Returning true from the filter includes the given path,
894 whilst returning false excludes it. Parameter 'isFolder'
895 indicates whether the path is a file or folder.
896
897 Note that paths composed of '.' characters are ignored.
898
899 ***********************************************************************/
900
901 final FilePath[] toList (Filter filter = null)
902 {
903 FilePath[] paths;
904
905 foreach (info; this)
906 {
907 auto p = from (info);
908
909 // test this entry for inclusion
910 if (filter is null || filter (p, info.folder))
911 paths ~= p;
912 else
913 delete p;
914 }
915
916 return paths;
917 }
918
919 /***********************************************************************
920
921 Construct a FilePath from the given FileInfo
922
923 ***********************************************************************/
924
925 static FilePath from (ref FileInfo info)
926 {
927 char[512] tmp = void;
928
929 auto len = info.path.length + info.name.length;
930 assert (len < tmp.length);
931
932 // construct full pathname
933 tmp [0 .. info.path.length] = info.path;
934 tmp [info.path.length .. len] = info.name;
935
936 return FilePath(tmp[0 .. len]).isFolder(info.folder);
937 }
938
939 /***********************************************************************
940
941 change the name or location of a file/directory, and
942 adopt the provided Path
943
944 ***********************************************************************/
945
946 final FilePath rename (FilePath dst)
947 {
948 return rename (dst.toString);
949 }
950
951 /***********************************************************************
952
953 Throw an exception using the last known error
954
955 ***********************************************************************/
956
957 private void exception ()
958 {
959 throw new IOException (toString ~ ": " ~ SysError.lastMsg);
960 }
961
962 /***********************************************************************
963
964 Throw an exception using the last known error
965
966 ***********************************************************************/
967
968 private void badArg (char[] msg)
969 {
970 throw new IllegalArgumentException (msg ~ toString);
971 }
972
973 /***********************************************************************
974
975 ***********************************************************************/
976
977 version (Win32)
978 {
979 /***************************************************************
980
981 return a wchar[] instance of the path
982
983 ***************************************************************/
984
985 private wchar[] toString16 (wchar[] tmp, char[] path)
986 {
987 auto i = MultiByteToWideChar (CP_UTF8, 0,
988 path.ptr, path.length,
989 tmp.ptr, tmp.length);
990 return tmp [0..i];
991 }
992
993 /***************************************************************
994
995 return a char[] instance of the path
996
997 ***************************************************************/
998
999 private char[] toString (char[] tmp, wchar[] path)
1000 {
1001 auto i = WideCharToMultiByte (CP_UTF8, 0, path.ptr, path.length,
1002 tmp.ptr, tmp.length, null, null);
1003 return tmp [0..i];
1004 }
1005
1006 /***************************************************************
1007
1008 return a wchar[] instance of the path
1009
1010 ***************************************************************/
1011
1012 private wchar[] name16 (wchar[] tmp, bool withNull=true)
1013 {
1014 int offset = withNull ? 0 : 1;
1015 return toString16 (tmp, this.cString[0 .. $-offset]);
1016 }
1017
1018 /***************************************************************
1019
1020 Get info about this path
1021
1022 ***************************************************************/
1023
1024 private DWORD getInfo (inout WIN32_FILE_ATTRIBUTE_DATA info)
1025 {
1026 version (Win32SansUnicode)
1027 {
1028 if (! GetFileAttributesExA (this.cString.ptr, GetFileInfoLevelStandard, &info))
1029 exception;
1030 }
1031 else
1032 {
1033 wchar[MAX_PATH] tmp = void;
1034 if (! GetFileAttributesExW (name16(tmp).ptr, GetFileInfoLevelStandard, &info))
1035 exception;
1036 }
1037
1038 return info.dwFileAttributes;
1039 }
1040
1041 /***************************************************************
1042
1043 Get flags for this path
1044
1045 ***************************************************************/
1046
1047 private DWORD getFlags ()
1048 {
1049 WIN32_FILE_ATTRIBUTE_DATA info = void;
1050
1051 return getInfo (info);
1052 }
1053
1054 /***************************************************************
1055
1056 Return the file length (in bytes)
1057
1058 ***************************************************************/
1059
1060 final ulong fileSize ()
1061 {
1062 WIN32_FILE_ATTRIBUTE_DATA info = void;
1063
1064 getInfo (info);
1065 return (cast(ulong) info.nFileSizeHigh << 32) +
1066 info.nFileSizeLow;
1067 }
1068
1069 /***************************************************************
1070
1071 Is this file writable?
1072
1073 ***************************************************************/
1074
1075 final bool isWritable ()
1076 {
1077 return (getFlags & FILE_ATTRIBUTE_READONLY) == 0;
1078 }
1079
1080 /***************************************************************
1081
1082 Is this file actually a folder/directory?
1083
1084 ***************************************************************/
1085
1086 final bool isFolder ()
1087 {
1088 if (dir_)
1089 return true;
1090
1091 return (getFlags & FILE_ATTRIBUTE_DIRECTORY) != 0;
1092 }
1093
1094 /***************************************************************
1095
1096 Return timestamp information
1097
1098 ***************************************************************/
1099
1100 final Stamps timeStamps ()
1101 {
1102 static Time convert (FILETIME time)
1103 {
1104 return Time (TimeSpan.Epoch1601 + *cast(long*) &time);
1105 }
1106
1107 WIN32_FILE_ATTRIBUTE_DATA info = void;
1108 Stamps time = void;
1109
1110 getInfo (info);
1111 time.modified = convert (info.ftLastWriteTime);
1112 time.accessed = convert (info.ftLastAccessTime);
1113 time.created = convert (info.ftCreationTime);
1114 return time;
1115 }
1116
1117 /***********************************************************************
1118
1119 Transfer the content of another file to this one. Returns a
1120 reference to this class on success, or throws an IOException
1121 upon failure.
1122
1123 ***********************************************************************/
1124
1125 final FilePath copy (char[] source)
1126 {
1127 auto src = new FilePath (source);
1128
1129 version (Win32SansUnicode)
1130 {
1131 if (! CopyFileA (src.cString.ptr, this.cString.ptr, false))
1132 exception;
1133 }
1134 else
1135 {
1136 wchar[MAX_PATH+1] tmp1 = void;
1137 wchar[MAX_PATH+1] tmp2 = void;
1138
1139 if (! CopyFileW (toString16(tmp1, src.cString).ptr, name16(tmp2).ptr, false))
1140 exception;
1141 }
1142
1143 return this;
1144 }
1145
1146 /***************************************************************
1147
1148 Remove the file/directory from the file-system
1149
1150 ***************************************************************/
1151
1152 final FilePath remove ()
1153 {
1154 if (isFolder)
1155 {
1156 version (Win32SansUnicode)
1157 {
1158 if (! RemoveDirectoryA (this.cString.ptr))
1159 exception;
1160 }
1161 else
1162 {
1163 wchar[MAX_PATH] tmp = void;
1164 if (! RemoveDirectoryW (name16(tmp).ptr))
1165 exception;
1166 }
1167 }
1168 else
1169 version (Win32SansUnicode)
1170 {
1171 if (! DeleteFileA (this.cString.ptr))
1172 exception;
1173 }
1174 else
1175 {
1176 wchar[MAX_PATH] tmp = void;
1177 if (! DeleteFileW (name16(tmp).ptr))
1178 exception;
1179 }
1180
1181 return this;
1182 }
1183
1184 /***************************************************************
1185
1186 change the name or location of a file/directory, and
1187 adopt the provided Path
1188
1189 ***************************************************************/
1190
1191 final FilePath rename (char[] dst)
1192 {
1193 const int Typical = MOVEFILE_REPLACE_EXISTING +
1194 MOVEFILE_COPY_ALLOWED +
1195 MOVEFILE_WRITE_THROUGH;
1196
1197 int result;
1198 char[] cstr = dst ~ '\0';
1199
1200 version (Win32SansUnicode)
1201 result = MoveFileExA (this.cString.ptr, cstr.ptr, Typical);
1202 else
1203 {
1204 wchar[MAX_PATH] tmp1 = void;
1205 wchar[MAX_PATH] tmp2 = void;
1206 result = MoveFileExW (name16(tmp1).ptr, toString16(tmp2, cstr).ptr, Typical);
1207 }
1208
1209 if (! result)
1210 exception;
1211
1212 this.set (dst);
1213 return this;
1214 }
1215
1216 /***************************************************************
1217
1218 Create a new file
1219
1220 ***************************************************************/
1221
1222 final FilePath createFile ()
1223 {
1224 HANDLE h;
1225
1226 version (Win32SansUnicode)
1227 h = CreateFileA (this.cString.ptr, GENERIC_WRITE,
1228 0, null, CREATE_ALWAYS,
1229 FILE_ATTRIBUTE_NORMAL, cast(HANDLE) 0);
1230 else
1231 {
1232 wchar[MAX_PATH] tmp = void;
1233 h = CreateFileW (name16(tmp).ptr, GENERIC_WRITE,
1234 0, null, CREATE_ALWAYS,
1235 FILE_ATTRIBUTE_NORMAL, cast(HANDLE) 0);
1236 }
1237
1238 if (h == INVALID_HANDLE_VALUE)
1239 exception;
1240
1241 if (! CloseHandle (h))
1242 exception;
1243
1244 return this;
1245 }
1246
1247 /***************************************************************
1248
1249 Create a new directory
1250
1251 ***************************************************************/
1252
1253 final FilePath createFolder ()
1254 {
1255 version (Win32SansUnicode)
1256 {
1257 if (! CreateDirectoryA (this.cString.ptr, null))
1258 exception;
1259 }
1260 else
1261 {
1262 wchar[MAX_PATH] tmp = void;
1263 if (! CreateDirectoryW (name16(tmp).ptr, null))
1264 exception;
1265 }
1266 return this;
1267 }
1268
1269 /***************************************************************
1270
1271 List the set of filenames within this folder.
1272
1273 Each path and filename is passed to the provided
1274 delegate, along with the path prefix and whether
1275 the entry is a folder or not.
1276
1277 Returns the number of files scanned.
1278
1279 ***************************************************************/
1280
1281 final int opApply (int delegate(ref FileInfo) dg)
1282 {
1283 HANDLE h;
1284 int ret;
1285 char[] prefix;
1286 char[MAX_PATH+1] tmp = void;
1287 FIND_DATA fileinfo = void;
1288
1289 int next()
1290 {
1291 version (Win32SansUnicode)
1292 return FindNextFileA (h, &fileinfo);
1293 else
1294 return FindNextFileW (h, &fileinfo);
1295 }
1296
1297 static T[] padded (T[] s, T[] ext)
1298 {
1299 if (s.length is 0 || s[$-1] != '\\')
1300 return s ~ "\\" ~ ext;
1301 return s ~ ext;
1302 }
1303
1304 version (Win32SansUnicode)
1305 h = FindFirstFileA (padded(this.toString, "*\0").ptr, &fileinfo);
1306 else
1307 {
1308 wchar[MAX_PATH] host = void;
1309 h = FindFirstFileW (padded(name16(host, false), "*\0").ptr, &fileinfo);
1310 }
1311
1312 if (h is INVALID_HANDLE_VALUE)
1313 exception;
1314
1315 scope (exit)
1316 FindClose (h);
1317
1318 prefix = FilePath.padded (this.toString);
1319 do {
1320 version (Win32SansUnicode)
1321 {
1322 auto len = strlen (fileinfo.cFileName.ptr);
1323 auto str = fileinfo.cFileName.ptr [0 .. len];
1324 }
1325 else
1326 {
1327 auto len = wcslen (fileinfo.cFileName.ptr);
1328 auto str = toString (tmp, fileinfo.cFileName [0 .. len]);
1329 }
1330
1331 // skip hidden/system files
1332 if ((fileinfo.dwFileAttributes & (FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_HIDDEN)) is 0)
1333 {
1334 FileInfo info = void;
1335 info.name = str;
1336 info.path = prefix;
1337 info.bytes = (cast(ulong) fileinfo.nFileSizeHigh << 32) + fileinfo.nFileSizeLow;
1338 info.folder = (fileinfo.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0;
1339
1340 // skip "..." names
1341 if (str.length > 3 || str != "..."[0 .. str.length])
1342 if ((ret = dg(info)) != 0)
1343 break;
1344 }
1345 } while (next);
1346
1347 return ret;
1348 }
1349 }
1350
1351
1352 /***********************************************************************
1353
1354 ***********************************************************************/
1355
1356 version (Posix)
1357 {
1358 /***************************************************************
1359
1360 Get info about this path
1361
1362 ***************************************************************/
1363
1364 private uint getInfo (inout stat_t stats)
1365 {
1366 if (posix.stat (this.cString.ptr, &stats))
1367 exception;
1368
1369 return stats.st_mode;
1370 }
1371
1372 /***************************************************************
1373
1374 Return the file length (in bytes)
1375
1376 ***************************************************************/
1377
1378 final ulong fileSize ()
1379 {
1380 stat_t stats = void;
1381
1382 getInfo (stats);
1383 return cast(ulong) stats.st_size; // 32 bits only
1384 }
1385
1386 /***************************************************************
1387
1388 Is this file writable?
1389
1390 ***************************************************************/
1391
1392 final bool isWritable ()
1393 {
1394 stat_t stats = void;
1395
1396 return (getInfo(stats) & O_RDONLY) == 0;
1397 }
1398
1399 /***************************************************************
1400
1401 Is this file actually a folder/directory?
1402
1403 ***************************************************************/
1404
1405 final bool isFolder ()
1406 {
1407 stat_t stats = void;
1408
1409 return (getInfo(stats) & S_IFDIR) != 0;
1410 }
1411
1412 /***************************************************************
1413
1414 Return timestamp information
1415
1416 ***************************************************************/
1417
1418 final Stamps timeStamps ()
1419 {
1420 static Time convert (timeval* tv)
1421 {
1422 return Time.epoch1970 +
1423 TimeSpan.seconds(tv.tv_sec) +
1424 TimeSpan.micros(tv.tv_usec);
1425 }
1426
1427 stat_t stats = void;
1428 Stamps time = void;
1429
1430 getInfo (stats);
1431
1432 time.modified = convert (cast(timeval*) &stats.st_mtime);
1433 time.accessed = convert (cast(timeval*) &stats.st_atime);
1434 time.created = convert (cast(timeval*) &stats.st_ctime);
1435 return time;
1436 }
1437
1438 /***********************************************************************
1439
1440 Transfer the content of another file to this one. Returns a
1441 reference to this class on success, or throws an IOException
1442 upon failure.
1443
1444 ***********************************************************************/
1445
1446 final FilePath copy (char[] source)
1447 {
1448 auto from = new FilePath (source);
1449
1450 auto src = posix.open (from.cString.ptr, O_RDONLY, 0640);
1451 scope (exit)
1452 if (src != -1)
1453 posix.close (src);
1454
1455 auto dst = posix.open (this.cString.ptr, O_CREAT | O_RDWR, 0660);
1456 scope (exit)
1457 if (dst != -1)
1458 posix.close (dst);
1459
1460 if (src is -1 || dst is -1)
1461 exception;
1462
1463 // copy content
1464 ubyte[] buf = new ubyte [16 * 1024];
1465 int read = posix.read (src, buf.ptr, buf.length);
1466 while (read > 0)
1467 {
1468 auto p = buf.ptr;
1469 do {
1470 int written = posix.write (dst, p, read);
1471 p += written;
1472 read -= written;
1473 if (written is -1)
1474 exception;
1475 } while (read > 0);
1476 read = posix.read (src, buf.ptr, buf.length);
1477 }
1478 if (read is -1)
1479 exception;
1480
1481 // copy timestamps
1482 stat_t stats;
1483 if (posix.stat (from.cString.ptr, &stats))
1484 exception;
1485
1486 utimbuf utim;
1487 utim.actime = stats.st_atime;
1488 utim.modtime = stats.st_mtime;
1489 if (utime (this.cString.ptr, &utim) is -1)
1490 exception;
1491
1492 return this;
1493 }
1494
1495 /***************************************************************
1496
1497 Remove the file/directory from the file-system
1498
1499 ***************************************************************/
1500
1501 final FilePath remove ()
1502 {
1503 if (isFolder)
1504 {
1505 if (posix.rmdir (this.cString.ptr))
1506 exception;
1507 }
1508 else
1509 if (tango.stdc.stdio.remove (this.cString.ptr) == -1)
1510 exception;
1511
1512 return this;
1513 }
1514
1515 /***************************************************************
1516
1517 change the name or location of a file/directory, and
1518 adopt the provided FilePath
1519
1520 ***************************************************************/
1521
1522 final FilePath rename (char[] dst)
1523 {
1524 char[] cstr = dst ~ '\0';
1525 if (tango.stdc.stdio.rename (this.cString.ptr, cstr.ptr) == -1)
1526 exception;
1527
1528 this.set (dst);
1529 return this;
1530 }
1531
1532 /***************************************************************
1533
1534 Create a new file
1535
1536 ***************************************************************/
1537
1538 final FilePath createFile ()
1539 {
1540 int fd;
1541
1542 fd = posix.open (this.cString.ptr, O_CREAT | O_WRONLY | O_TRUNC, 0660);
1543 if (fd == -1)
1544 exception;
1545
1546 if (posix.close(fd) == -1)
1547 exception;
1548
1549 return this;
1550 }
1551
1552 /***************************************************************
1553
1554 Create a new directory
1555
1556 ***************************************************************/
1557
1558 final FilePath createFolder ()
1559 {
1560 if (posix.mkdir (this.cString.ptr, 0777))
1561 exception;
1562
1563 return this;
1564 }
1565
1566 /***************************************************************
1567
1568 List the set of filenames within this folder.
1569
1570 Each path and filename is passed to the provided
1571 delegate, along with the path prefix and whether
1572 the entry is a folder or not.
1573
1574 Returns the number of files scanned.
1575
1576 ***************************************************************/
1577
1578 final int opApply (int delegate(ref FileInfo) dg)
1579 {
1580 int ret;
1581 DIR* dir;
1582 dirent entry;
1583 dirent* pentry;
1584 stat_t sbuf;
1585 char[] prefix;
1586 char[] sfnbuf;
1587
1588 dir = tango.stdc.posix.dirent.opendir (this.cString.ptr);
1589 if (! dir)
1590 exception;
1591
1592 scope (exit)
1593 tango.stdc.posix.dirent.closedir (dir);
1594
1595 // ensure a trailing '/' is present
1596 prefix = FilePath.padded (this.toString);
1597
1598 // prepare our filename buffer
1599 sfnbuf = prefix.dup;
1600
1601 // pentry is null at end of listing, or on an error
1602 while (readdir_r (dir, &entry, &pentry), pentry != null)
1603 {
1604 auto len = tango.stdc.string.strlen (entry.d_name.ptr);
1605 auto str = entry.d_name.ptr [0 .. len];
1606 ++len; // include the null
1607
1608 // resize the buffer as necessary ...
1609 if (sfnbuf.length < prefix.length + len)
1610 sfnbuf.length = prefix.length + len;
1611
1612 sfnbuf [prefix.length .. prefix.length + len]
1613 = entry.d_name.ptr [0 .. len];
1614
1615 // skip "..." names
1616 if (str.length > 3 || str != "..."[0 .. str.length])
1617 {
1618 if (stat (sfnbuf.ptr, &sbuf))
1619 exception;
1620
1621 FileInfo info = void;
1622 info.name = str;
1623 info.path = prefix;
1624 info.folder = (sbuf.st_mode & S_IFDIR) != 0;
1625 info.bytes = (sbuf.st_mode & S_IFREG) != 0 ? sbuf.st_size : 0;
1626
1627 if ((ret = dg(info)) != 0)
1628 break;
1629 }
1630 }
1631 return ret;
1632 }
1633 }
1634 }
1635
1636
1637
1638 /*******************************************************************************
1639
1640 *******************************************************************************/
1641
1642 interface PathView
1643 {
1644 /***********************************************************************
1645
1646 TimeStamp information. Accurate to whatever the OS supports
1647
1648 ***********************************************************************/
1649
1650 struct Stamps
1651 {
1652 Time created, /// time created
1653 accessed, /// last time accessed
1654 modified; /// last time modified
1655 }
1656
1657 /***********************************************************************
1658
1659 Return the complete text of this filepath
1660
1661 ***********************************************************************/
1662
1663 abstract char[] toString ();
1664
1665 /***********************************************************************
1666
1667 Return the complete text of this filepath
1668
1669 ***********************************************************************/
1670
1671 abstract char[] cString ();
1672
1673 /***********************************************************************
1674
1675 Return the root of this path. Roots are constructs such as
1676 "c:"
1677
1678 ***********************************************************************/
1679
1680 abstract char[] root ();
1681
1682 /***********************************************************************
1683
1684 Return the file path. Paths may start and end with a "/".
1685 The root path is "/" and an unspecified path is returned as
1686 an empty string. Directory paths may be split such that the
1687 directory name is placed into the 'name' member; directory
1688 paths are treated no differently than file paths
1689
1690 ***********************************************************************/
1691
1692 abstract char[] folder ();
1693
1694 /***********************************************************************
1695
1696 Return the name of this file, or directory, excluding a
1697 suffix.
1698
1699 ***********************************************************************/
1700
1701 abstract char[] name ();
1702
1703 /***********************************************************************
1704
1705 Ext is the tail of the filename, rightward of the rightmost
1706 '.' separator e.g. path "foo.bar" has ext "bar". Note that
1707 patterns of adjacent separators are treated specially; for
1708 example, ".." will wind up with no ext at all
1709
1710 ***********************************************************************/
1711
1712 abstract char[] ext ();
1713
1714 /***********************************************************************
1715
1716 Suffix is like ext, but includes the separator e.g. path
1717 "foo.bar" has suffix ".bar"
1718
1719 ***********************************************************************/
1720
1721 abstract char[] suffix ();
1722
1723 /***********************************************************************
1724
1725 return the root + folder combination
1726
1727 ***********************************************************************/
1728
1729 abstract char[] path ();
1730
1731 /***********************************************************************
1732
1733 return the name + suffix combination
1734
1735 ***********************************************************************/
1736
1737 abstract char[] file ();
1738
1739 /***********************************************************************
1740
1741 Returns true if this FilePath is *not* relative to the
1742 current working directory.
1743
1744 ***********************************************************************/
1745
1746 abstract bool isAbsolute ();
1747
1748 /***********************************************************************
1749
1750 Returns true if this FilePath is empty
1751
1752 ***********************************************************************/
1753
1754 abstract bool isEmpty ();
1755
1756 /***********************************************************************
1757
1758 Returns true if this FilePath has a parent
1759
1760 ***********************************************************************/
1761
1762 abstract bool isChild ();
1763
1764 /***********************************************************************
1765
1766 Does this path currently exist?
1767
1768 ***********************************************************************/
1769
1770 abstract bool exists ();
1771
1772 /***********************************************************************
1773
1774 Returns the time of the last modification. Accurate
1775 to whatever the OS supports
1776
1777 ***********************************************************************/
1778
1779 abstract Time modified ();
1780
1781 /***********************************************************************
1782
1783 Returns the time of the last access. Accurate to
1784 whatever the OS supports
1785
1786 ***********************************************************************/
1787
1788 abstract Time accessed ();
1789
1790 /***********************************************************************
1791
1792 Returns the time of file creation. Accurate to
1793 whatever the OS supports
1794
1795 ***********************************************************************/
1796
1797 abstract Time created ();
1798
1799 /***********************************************************************
1800
1801 Return the file length (in bytes)
1802
1803 ***********************************************************************/
1804
1805 abstract ulong fileSize ();
1806
1807 /***********************************************************************
1808
1809 Is this file writable?
1810
1811 ***********************************************************************/
1812
1813 abstract bool isWritable ();
1814
1815 /***********************************************************************
1816
1817 Is this file actually a folder/directory?
1818
1819 ***********************************************************************/
1820
1821 abstract bool isFolder ();
1822
1823 /***********************************************************************
1824
1825 Return timestamp information
1826
1827 ***********************************************************************/
1828
1829 abstract Stamps timeStamps ();
1830 }
1831
1832
1833
1834
1835
1836 /*******************************************************************************
1837
1838 *******************************************************************************/
1839
1840 debug (UnitTest)
1841 {
1842 unittest
1843 {
1844 version(Win32)
1845 {
1846 auto fp = new FilePath(r"C:/home/foo/bar");
1847 fp ~= "john";
1848 assert (fp == r"C:/home/foo/bar/john");
1849 fp = r"C:/";
1850 fp ~= "john";
1851 assert (fp == r"C:/john");
1852 fp = "foo.bar";
1853 fp ~= "john";
1854 assert (fp == r"foo.bar/john");
1855 fp = "";
1856 fp ~= "john";
1857 assert (fp == r"john");
1858
1859 fp = r"C:/home/foo/bar/john/foo.d";
1860 assert (fp.pop == r"C:/home/foo/bar/john");
1861 assert (fp.pop == r"C:/home/foo/bar");
1862 assert (fp.pop == r"C:/home/foo");
1863 assert (fp.pop == r"C:/home");
1864 assert (fp.pop == r"C:");
1865 assert (fp.pop == r"C:");
1866
1867 // special case for popping empty names
1868 fp = r"C:/home/foo/bar/john/";
1869 assert (fp.pop == r"C:/home/foo/bar", fp.toString);
1870
1871 fp = new FilePath;
1872 fp = r"C:/home/foo/bar/john/";
1873 assert (fp.isAbsolute);
1874 assert (fp.name == "");
1875 assert (fp.folder == r"/home/foo/bar/john/");
1876 assert (fp == r"C:/home/foo/bar/john/");
1877 assert (fp.path == r"C:/home/foo/bar/john/");
1878 assert (fp.file == r"");
1879 assert (fp.suffix == r"");
1880 assert (fp.root == r"C:");
1881 assert (fp.ext == "");
1882 assert (fp.isChild);
1883
1884 fp = new FilePath(r"C:/home/foo/bar/john");
1885 assert (fp.isAbsolute);
1886 assert (fp.name == "john");
1887 assert (fp.folder == r"/home/foo/bar/");
1888 assert (fp == r"C:/home/foo/bar/john");
1889 assert (fp.path == r"C:/home/foo/bar/");
1890 assert (fp.file == r"john");
1891 assert (fp.suffix == r"");
1892 assert (fp.ext == "");
1893 assert (fp.isChild);
1894
1895 fp.pop;
1896 assert (fp.isAbsolute);
1897 assert (fp.name == "bar");
1898 assert (fp.folder == r"/home/foo/");
1899 assert (fp == r"C:/home/foo/bar");
1900 assert (fp.path == r"C:/home/foo/");
1901 assert (fp.file == r"bar");
1902 assert (fp.suffix == r"");
1903 assert (fp.ext == "");
1904 assert (fp.isChild);
1905
1906 fp.pop;
1907 assert (fp.isAbsolute);
1908 assert (fp.name == "foo");
1909 assert (fp.folder == r"/home/");
1910 assert (fp == r"C:/home/foo");
1911 assert (fp.path == r"C:/home/");
1912 assert (fp.file == r"foo");
1913 assert (fp.suffix == r"");
1914 assert (fp.ext == "");
1915 assert (fp.isChild);
1916
1917 fp.pop;
1918 assert (fp.isAbsolute);
1919 assert (fp.name == "home");
1920 assert (fp.folder == r"/");
1921 assert (fp == r"C:/home");
1922 assert (fp.path == r"C:/");
1923 assert (fp.file == r"home");
1924 assert (fp.suffix == r"");
1925 assert (fp.ext == "");
1926 assert (fp.isChild);
1927
1928 fp = new FilePath(r"foo/bar/john.doe");
1929 assert (!fp.isAbsolute);
1930 assert (fp.name == "john");
1931 assert (fp.folder == r"foo/bar/");
1932 assert (fp.suffix == r".doe");
1933 assert (fp.file == r"john.doe");
1934 assert (fp == r"foo/bar/john.doe");
1935 assert (fp.ext == "doe");
1936 assert (fp.isChild);
1937
1938 fp = new FilePath(r"c:doe");
1939 assert (fp.isAbsolute);
1940 assert (fp.suffix == r"");
1941 assert (fp == r"c:doe");
1942 assert (fp.folder == r"");
1943 assert (fp.name == "doe");
1944 assert (fp.file == r"doe");
1945 assert (fp.ext == "");
1946 assert (!fp.isChild);
1947
1948 fp = new FilePath(r"/doe");
1949 assert (fp.isAbsolute);
1950 assert (fp.suffix == r"");
1951 assert (fp == r"/doe");
1952 assert (fp.name == "doe");
1953 assert (fp.folder == r"/");
1954 assert (fp.file == r"doe");
1955 assert (fp.ext == "");
1956 assert (fp.isChild);
1957
1958 fp = new FilePath(r"john.doe.foo");
1959 assert (!fp.isAbsolute);
1960 assert (fp.name == "john.doe");
1961 assert (fp.folder == r"");
1962 assert (fp.suffix == r".foo");
1963 assert (fp == r"john.doe.foo");
1964 assert (fp.file == r"john.doe.foo");
1965 assert (fp.ext == "foo");
1966 assert (!fp.isChild);
1967
1968 fp = new FilePath(r".doe");
1969 assert (!fp.isAbsolute);
1970 assert (fp.suffix == r"");
1971 assert (fp == r".doe");
1972 assert (fp.name == ".doe");
1973 assert (fp.folder == r"");
1974 assert (fp.file == r".doe");
1975 assert (fp.ext == "");
1976 assert (!fp.isChild);
1977
1978 fp = new FilePath(r"doe");
1979 assert (!fp.isAbsolute);
1980 assert (fp.suffix == r"");
1981 assert (fp == r"doe");
1982 assert (fp.name == "doe");
1983 assert (fp.folder == r"");
1984 assert (fp.file == r"doe");
1985 assert (fp.ext == "");
1986 assert (!fp.isChild);
1987
1988 fp = new FilePath(r".");
1989 assert (!fp.isAbsolute);
1990 assert (fp.suffix == r"");
1991 assert (fp == r".");
1992 assert (fp.name == ".");
1993 assert (fp.folder == r"");
1994 assert (fp.file == r".");
1995 assert (fp.ext == "");
1996 assert (!fp.isChild);
1997
1998 fp = new FilePath(r"..");
1999 assert (!fp.isAbsolute);
2000 assert (fp.suffix == r"");
2001 assert (fp == r"..");
2002 assert (fp.name == "..");
2003 assert (fp.folder == r"");
2004 assert (fp.file == r"..");
2005 assert (fp.ext == "");
2006 assert (!fp.isChild);
2007
2008 fp = new FilePath(r"c:/a/b/c/d/e/foo.bar");
2009 assert (fp.isAbsolute);
2010 fp.folder (r"/a/b/c/");
2011 assert (fp.suffix == r".bar");
2012 assert (fp == r"c:/a/b/c/foo.bar");
2013 assert (fp.name == "foo");
2014 assert (fp.folder == r"/a/b/c/");
2015 assert (fp.file == r"foo.bar");
2016 assert (fp.ext == "bar");
2017 assert (fp.isChild);
2018
2019 fp = new FilePath(r"c:/a/b/c/d/e/foo.bar");
2020 assert (fp.isAbsolute);
2021 fp.folder (r"/a/b/c/d/e/f/g/");
2022 assert (fp.suffix == r".bar");
2023 assert (fp == r"c:/a/b/c/d/e/f/g/foo.bar");
2024 assert (fp.name == "foo");
2025 assert (fp.folder == r"/a/b/c/d/e/f/g/");
2026 assert (fp.file == r"foo.bar");
2027 assert (fp.ext == "bar");
2028 assert (fp.isChild);
2029
2030 fp = new FilePath(r"C:\foo\bar\test.bar");
2031 assert (fp.path == "C:/foo/bar/");
2032 fp = new FilePath(r"C:/foo/bar/test.bar");
2033 assert (fp.path == r"C:/foo/bar/");
2034
2035 fp = new FilePath("");
2036 assert (fp.isEmpty);
2037 assert (!fp.isChild);
2038 assert (!fp.isAbsolute);
2039 assert (fp.suffix == r"");
2040 assert (fp == r"");
2041 assert (fp.name == "");
2042 assert (fp.folder == r"");
2043 assert (fp.file == r"");
2044 assert (fp.ext == "");
2045 /+
2046 fp = new FilePath(r"C:/foo/bar/test.bar");
2047 fp = new FilePath(fp.asPath ("foo"));
2048 assert (fp.name == r"test");
2049 assert (fp.folder == r"foo/");
2050 assert (fp.path == r"C:foo/");
2051 assert (fp.ext == ".bar");
2052
2053 fp = new FilePath(fp.asPath (""));
2054 assert (fp.name == r"test");
2055 assert (fp.folder == r"");
2056 assert (fp.path == r"C:");
2057 assert (fp.ext == ".bar");
2058
2059 fp = new FilePath(r"c:/joe/bar");
2060 assert(fp.cat(r"foo/bar/") == r"c:/joe/bar/foo/bar/");
2061 assert(fp.cat(new FilePath(r"foo/bar")).toString == r"c:/joe/bar/foo/bar");
2062
2063 assert (FilePath.join (r"a/b/c/d", r"e/f/" r"g") == r"a/b/c/d/e/f/g");
2064
2065 fp = new FilePath(r"C:/foo/bar/test.bar");
2066 assert (fp.asExt(null) == r"C:/foo/bar/test");
2067 assert (fp.asExt("foo") == r"C:/foo/bar/test.foo");
2068 +/
2069 }
2070 }
2071 }
2072
2073
2074 debug (FilePath)
2075 {
2076 import tango.io.Console;
2077
2078 void main()
2079 {
2080 Cout (FilePath("c:/temp/").file("foo.bar")).newline;
2081 }
2082
2083 }