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