Mercurial > projects > ldc
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 } |