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