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