1 /******************************************************************************* 2 3 copyright: Copyright (c) 2008 Kris Bell. All rights reserved 4 copyright: Normalization & Patterns copyright (c) 2006-2009 5 Max Samukha, Thomas Kühne, Grzegorz Adam Hankiewicz 6 7 license: BSD style: $(LICENSE) 8 9 version: Mar 2008: Initial version$(BR) 10 Oct 2009: Added PathUtil code 11 12 A more direct route to the file-system than FilePath. Use this 13 if you don't need path editing features. For example, if all you 14 want is to check some path exists, using this module would likely 15 be more convenient than FilePath: 16 --- 17 if (exists ("some/file/path")) 18 ... 19 --- 20 21 These functions may be less efficient than FilePath because they 22 generally attach a null to the filename for each underlying O/S 23 call. Use Path when you need pedestrian access to the file-system, 24 and are not manipulating the path components. Use FilePath where 25 path-editing or mutation is desired. 26 27 We encourage the use of "named import" with this module, such as: 28 --- 29 import Path = tango.io.Path; 30 31 if (Path.exists ("some/file/path")) 32 ... 33 --- 34 35 Also residing here is a lightweight path-parser, which splits a 36 filepath into constituent components. FilePath is based upon the 37 same PathParser: 38 --- 39 auto p = Path.parse ("some/file/path"); 40 auto path = p.path; 41 auto name = p.name; 42 auto suffix = p.suffix; 43 ... 44 --- 45 46 Path normalization and pattern-matching is also hosted here via 47 the normalize() and pattern() functions. See the doc towards the 48 end of this module. 49 50 Compile with -version=Win32SansUnicode to enable Win95 & Win32s 51 file support. 52 53 *******************************************************************************/ 54 55 module tango.io.Path; 56 57 private import tango.sys.Common; 58 59 public import tango.time.Time : Time, TimeSpan; 60 61 private import tango.io.model.IFile : FileConst, FileInfo; 62 63 public import tango.core.Exception : IOException, IllegalArgumentException; 64 65 private import tango.stdc.string : memmove; 66 67 private import tango.core.Octal; 68 69 70 /******************************************************************************* 71 72 Various imports 73 74 *******************************************************************************/ 75 76 version (Win32) 77 { 78 version (Win32SansUnicode) 79 { 80 private extern (C) int strlen (const char *s); 81 private alias WIN32_FIND_DATA FIND_DATA; 82 } 83 else 84 { 85 private extern (C) int wcslen (const wchar *s); 86 private alias WIN32_FIND_DATAW FIND_DATA; 87 } 88 } 89 90 version (Posix) 91 { 92 private import tango.stdc.stdio; 93 private import tango.stdc.string; 94 private import tango.stdc.posix.utime; 95 private import tango.stdc.posix.dirent; 96 } 97 98 99 /******************************************************************************* 100 101 Wraps the O/S specific calls with a D API. Note that these accept 102 null-terminated strings only, which is why it's not public. We need 103 this declared first to avoid forward-reference issues. 104 105 *******************************************************************************/ 106 107 package struct FS 108 { 109 /*********************************************************************** 110 111 TimeStamp information. Accurate to whatever the F/S supports. 112 113 ***********************************************************************/ 114 115 struct Stamps 116 { 117 Time created; /// Time created. 118 Time accessed; /// Last time accessed. 119 Time modified; /// Last time modified. 120 } 121 122 /*********************************************************************** 123 124 Some fruct glue for directory listings. 125 126 ***********************************************************************/ 127 128 struct Listing 129 { 130 const(char)[] folder; 131 bool allFiles; 132 133 int opApply (scope int delegate(ref FileInfo) dg) 134 { 135 char[256] tmp = void; 136 auto path = strz (folder, tmp); 137 138 // sanity check on Win32 ... 139 version (Win32) 140 { 141 bool kosher(){foreach (c; path) if (c is '\\') return false; return true;} 142 assert (kosher(), "attempting to use non-standard '\\' in a path for a folder listing"); 143 } 144 145 return list (path, dg, allFiles); 146 } 147 } 148 149 /*********************************************************************** 150 151 Throw an exception using the last known error. 152 153 ***********************************************************************/ 154 155 static void exception (const(char)[] filename) 156 { 157 exception (filename[0..$-1] ~ ": ", SysError.lastMsg); 158 } 159 160 /*********************************************************************** 161 162 Throw an IO exception. 163 164 ***********************************************************************/ 165 166 static void exception (const(char)[] prefix, const(char)[] error) 167 { 168 throw new IOException ((prefix ~ error).idup); 169 } 170 171 /*********************************************************************** 172 173 Return an adjusted path such that non-empty instances always 174 have a trailing separator. 175 176 Note: Allocates memory where path is not already terminated. 177 178 ***********************************************************************/ 179 180 static inout(char)[] padded (inout(char)[] path, char c = '/') 181 { 182 if (path.length && path[$-1] != c) 183 path = path ~ c; 184 return path; 185 } 186 187 /*********************************************************************** 188 189 Return an adjusted path such that non-empty instances always 190 have a leading separator. 191 192 Note: Allocates memory where path is not already terminated. 193 194 ***********************************************************************/ 195 196 static inout(char)[] paddedLeading (inout(char)[] path, char c = '/') 197 { 198 if (path.length && path[0] != c) 199 path = c ~ path; 200 return path; 201 } 202 203 /*********************************************************************** 204 205 Return an adjusted path such that non-empty instances do not 206 have a trailing separator. 207 208 ***********************************************************************/ 209 210 static inout(char)[] stripped (inout(char)[] path, char c = '/') 211 { 212 if (path.length && path[$-1] is c) 213 path = path [0 .. $-1]; 214 return path; 215 } 216 217 /*********************************************************************** 218 219 Join a set of path specs together. A path separator is 220 potentially inserted between each of the segments. 221 222 Note: Allocates memory. 223 224 ***********************************************************************/ 225 226 static char[] join (const(char[])[] paths...) 227 { 228 char[] result; 229 230 if (paths.length) 231 { 232 result ~= stripped(paths[0]); 233 234 foreach (path; paths[1 .. $-1]) 235 result ~= paddedLeading (stripped(path)); 236 237 result ~= paddedLeading(paths[$-1]); 238 239 return result; 240 } 241 return "".dup; 242 } 243 244 /*********************************************************************** 245 246 Append a terminating null onto a string, cheaply where 247 feasible. 248 249 Note: Allocates memory where the dst is too small. 250 251 ***********************************************************************/ 252 253 static char[] strz (const(char)[] src, char[] dst) 254 { 255 auto i = src.length + 1; 256 if (dst.length < i) 257 dst.length = i; 258 dst [0 .. i-1] = src[]; 259 dst[i-1] = 0; 260 return dst [0 .. i]; 261 } 262 263 /*********************************************************************** 264 265 Win32 API code 266 267 ***********************************************************************/ 268 269 version (Win32) 270 { 271 /*************************************************************** 272 273 Return a wchar[] instance of the path. 274 275 ***************************************************************/ 276 277 private static wchar[] toString16 (wchar[] tmp, const(char)[] path) 278 { 279 auto i = MultiByteToWideChar (CP_UTF8, 0, 280 cast(PCHAR)path.ptr, path.length, 281 tmp.ptr, tmp.length); 282 return tmp [0..i]; 283 } 284 285 /*************************************************************** 286 287 Return a char[] instance of the path. 288 289 ***************************************************************/ 290 291 private static char[] toString (char[] tmp, const(wchar[]) path) 292 { 293 auto i = WideCharToMultiByte (CP_UTF8, 0, path.ptr, path.length, 294 cast(PCHAR)tmp.ptr, tmp.length, null, null); 295 return tmp [0..i]; 296 } 297 298 /*************************************************************** 299 300 Get info about this path. 301 302 ***************************************************************/ 303 304 private static bool fileInfo (const(char)[] name, ref WIN32_FILE_ATTRIBUTE_DATA info) 305 { 306 version (Win32SansUnicode) 307 { 308 if (! GetFileAttributesExA (name.ptr, GetFileInfoLevelStandard, &info)) 309 return false; 310 } 311 else 312 { 313 wchar[MAX_PATH] tmp = void; 314 if (! GetFileAttributesExW (toString16(tmp, name).ptr, GetFileInfoLevelStandard, &info)) 315 return false; 316 } 317 318 return true; 319 } 320 321 /*************************************************************** 322 323 Get info about this path. 324 325 ***************************************************************/ 326 327 private static DWORD getInfo (const(char)[] name, ref WIN32_FILE_ATTRIBUTE_DATA info) 328 { 329 if (! fileInfo (name, info)) 330 exception (name); 331 return info.dwFileAttributes; 332 } 333 334 /*************************************************************** 335 336 Get flags for this path. 337 338 ***************************************************************/ 339 340 private static DWORD getFlags (const(char)[] name) 341 { 342 WIN32_FILE_ATTRIBUTE_DATA info = void; 343 344 return getInfo (name, info); 345 } 346 347 /*************************************************************** 348 349 Return whether the file or path exists. 350 351 ***************************************************************/ 352 353 static bool exists (const(char)[] name) 354 { 355 WIN32_FILE_ATTRIBUTE_DATA info = void; 356 357 return fileInfo (name, info); 358 } 359 360 /*************************************************************** 361 362 Return the file length (in bytes.) 363 364 ***************************************************************/ 365 366 static ulong fileSize (const(char)[] name) 367 { 368 WIN32_FILE_ATTRIBUTE_DATA info = void; 369 370 getInfo (name, info); 371 return (cast(ulong) info.nFileSizeHigh << 32) + 372 info.nFileSizeLow; 373 } 374 375 /*************************************************************** 376 377 Is this file writable? 378 379 ***************************************************************/ 380 381 static bool isWritable (const(char)[] name) 382 { 383 return (getFlags(name) & FILE_ATTRIBUTE_READONLY) is 0; 384 } 385 386 /*************************************************************** 387 388 Is this file actually a folder/directory? 389 390 ***************************************************************/ 391 392 static bool isFolder (const(char)[] name) 393 { 394 return (getFlags(name) & FILE_ATTRIBUTE_DIRECTORY) != 0; 395 } 396 397 /*************************************************************** 398 399 Is this a normal file? 400 401 ***************************************************************/ 402 403 static bool isFile (const(char)[] name) 404 { 405 return (getFlags(name) & FILE_ATTRIBUTE_DIRECTORY) == 0; 406 } 407 408 /*************************************************************** 409 410 Return timestamp information. 411 412 Timestamps are returns in a format dictated by the 413 file-system. For example NTFS keeps UTC time, 414 while FAT timestamps are based on the local time. 415 416 ***************************************************************/ 417 418 static Stamps timeStamps (const(char)[] name) 419 { 420 static Time convert (FILETIME time) 421 { 422 return Time (TimeSpan.Epoch1601 + *cast(long*) &time); 423 } 424 425 WIN32_FILE_ATTRIBUTE_DATA info = void; 426 Stamps time = void; 427 428 getInfo (name, info); 429 time.modified = convert (info.ftLastWriteTime); 430 time.accessed = convert (info.ftLastAccessTime); 431 time.created = convert (info.ftCreationTime); 432 return time; 433 } 434 435 /*************************************************************** 436 437 Set the accessed and modified timestamps of the 438 specified file. 439 440 ***************************************************************/ 441 442 static void timeStamps (const(char)[] name, Time accessed, Time modified) 443 { 444 void set (HANDLE h) 445 { 446 FILETIME m1, a1; 447 auto m = modified - Time.epoch1601; 448 auto a = accessed - Time.epoch1601; 449 *cast(long*) &a1.dwLowDateTime = m.ticks; 450 *cast(long*) &m1.dwLowDateTime = m.ticks; 451 if (SetFileTime (h, null, &a1, &m1) is 0) 452 exception (name); 453 } 454 455 createFile (name, &set); 456 } 457 458 /*************************************************************** 459 460 Transfer the content of another file to this one. 461 Throws an IOException upon failure. 462 463 ***************************************************************/ 464 465 static void copy (const(char)[] src, const(char)[] dst) 466 { 467 version (Win32SansUnicode) 468 { 469 if (! CopyFileA (src.ptr, dst.ptr, false)) 470 exception (src); 471 } 472 else 473 { 474 wchar[MAX_PATH+1] tmp1 = void; 475 wchar[MAX_PATH+1] tmp2 = void; 476 477 if (! CopyFileW (toString16(tmp1, src).ptr, toString16(tmp2, dst).ptr, false)) 478 exception (src); 479 } 480 } 481 482 /*************************************************************** 483 484 Remove the file/directory from the file-system. 485 Returns true on success - false otherwise. 486 487 ***************************************************************/ 488 489 static bool remove (const(char)[] name) 490 { 491 if (isFolder(name)) 492 { 493 version (Win32SansUnicode) 494 return RemoveDirectoryA (name.ptr) != 0; 495 else 496 { 497 wchar[MAX_PATH] tmp = void; 498 return RemoveDirectoryW (toString16(tmp, name).ptr) != 0; 499 } 500 } 501 else 502 version (Win32SansUnicode) 503 return DeleteFileA (name.ptr) != 0; 504 else 505 { 506 wchar[MAX_PATH] tmp = void; 507 return DeleteFileW (toString16(tmp, name).ptr) != 0; 508 } 509 } 510 511 /*************************************************************** 512 513 Change the name or location of a file/directory. 514 515 ***************************************************************/ 516 517 static void rename (const(char)[] src, const(char)[] dst) 518 { 519 const int Typical = MOVEFILE_REPLACE_EXISTING + 520 MOVEFILE_COPY_ALLOWED + 521 MOVEFILE_WRITE_THROUGH; 522 523 int result; 524 version (Win32SansUnicode) 525 result = MoveFileExA (src.ptr, dst.ptr, Typical); 526 else 527 { 528 wchar[MAX_PATH] tmp1 = void; 529 wchar[MAX_PATH] tmp2 = void; 530 result = MoveFileExW (toString16(tmp1, src).ptr, toString16(tmp2, dst).ptr, Typical); 531 } 532 533 if (! result) 534 exception (src); 535 } 536 537 /*************************************************************** 538 539 Create a new file. 540 541 ***************************************************************/ 542 543 static void createFile (const(char)[] name) 544 { 545 createFile (name, null); 546 } 547 548 /*************************************************************** 549 550 Create a new directory. 551 552 ***************************************************************/ 553 554 static void createFolder (const(char)[] name) 555 { 556 version (Win32SansUnicode) 557 { 558 if (! CreateDirectoryA (name.ptr, null)) 559 exception (name); 560 } 561 else 562 { 563 wchar[MAX_PATH] tmp = void; 564 if (! CreateDirectoryW (toString16(tmp, name).ptr, null)) 565 exception (name); 566 } 567 } 568 569 /*************************************************************** 570 571 List the set of filenames within this folder. 572 573 Each path and filename is passed to the provided 574 delegate, along with the path prefix and whether 575 the entry is a folder or not. 576 577 Note: Allocates a small memory buffer. 578 579 ***************************************************************/ 580 581 static int list (const(char)[] folder, scope int delegate(ref FileInfo) dg, bool all=false) 582 { 583 HANDLE h; 584 int ret; 585 const(char)[] prefix; 586 char[MAX_PATH+1] tmp = void; 587 FIND_DATA fileinfo = void; 588 589 version (Win32SansUnicode) 590 alias char T; 591 else 592 alias wchar T; 593 594 int next() 595 { 596 version (Win32SansUnicode) 597 return FindNextFileA (h, &fileinfo); 598 else 599 return FindNextFileW (h, &fileinfo); 600 } 601 602 static T[] padded (const(T)[] s, const(T)[] ext) 603 { 604 if (s.length && s[$-1] is '/') 605 return cast(T[])(s ~ ext); // Should be a safe cast here 606 return s ~ "/" ~ ext; 607 } 608 609 version (Win32SansUnicode) 610 h = FindFirstFileA (padded(folder[0..$-1], "*\0").ptr, &fileinfo); 611 else 612 { 613 wchar[MAX_PATH] host = void; 614 h = FindFirstFileW (padded(toString16(host, folder[0..$-1]), "*\0").ptr, &fileinfo); 615 } 616 617 if (h is INVALID_HANDLE_VALUE) 618 return ret; 619 620 scope (exit) 621 FindClose (h); 622 623 prefix = FS.padded (folder[0..$-1]); 624 do { 625 version (Win32SansUnicode) 626 { 627 auto len = strlen (fileinfo.cFileName.ptr); 628 auto str = fileinfo.cFileName.ptr [0 .. len]; 629 } 630 else 631 { 632 auto len = wcslen (fileinfo.cFileName.ptr); 633 auto str = toString (tmp, fileinfo.cFileName [0 .. len]); 634 } 635 636 // skip hidden/system files 637 if (all || (fileinfo.dwFileAttributes & (FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_HIDDEN)) is 0) 638 { 639 FileInfo info = void; 640 info.name = str; 641 info.path = prefix; 642 info.bytes = (cast(ulong) fileinfo.nFileSizeHigh << 32) + fileinfo.nFileSizeLow; 643 info.folder = (fileinfo.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0; 644 info.hidden = (fileinfo.dwFileAttributes & FILE_ATTRIBUTE_HIDDEN) != 0; 645 info.system = (fileinfo.dwFileAttributes & FILE_ATTRIBUTE_SYSTEM) != 0; 646 647 // skip "..." names 648 if (str.length > 3 || str != "..."[0 .. str.length]) 649 if ((ret = dg(info)) != 0) 650 break; 651 } 652 } while (next()); 653 654 return ret; 655 } 656 657 /*************************************************************** 658 659 Create a new file. 660 661 ***************************************************************/ 662 663 private static void createFile (const(char)[] name, scope void delegate(HANDLE) dg) 664 { 665 HANDLE h; 666 667 auto flags = dg !is null ? OPEN_EXISTING : CREATE_ALWAYS; 668 version (Win32SansUnicode) 669 h = CreateFileA (name.ptr, GENERIC_WRITE, 670 0, null, flags, FILE_ATTRIBUTE_NORMAL, 671 cast(HANDLE) 0); 672 else 673 { 674 wchar[MAX_PATH] tmp = void; 675 h = CreateFileW (toString16(tmp, name).ptr, GENERIC_WRITE, 676 0, null, flags, FILE_ATTRIBUTE_NORMAL, 677 cast(HANDLE) 0); 678 } 679 680 if (h is INVALID_HANDLE_VALUE) 681 exception (name); 682 683 if (dg !is null) 684 dg(h); 685 686 if (! CloseHandle (h)) 687 exception (name); 688 } 689 } 690 691 /*********************************************************************** 692 693 Posix-specific code. 694 695 ***********************************************************************/ 696 697 version (Posix) 698 { 699 /*************************************************************** 700 701 Get info about this path. 702 703 ***************************************************************/ 704 705 private static uint getInfo (const(char)[] name, ref stat_t stats) 706 { 707 if (posix.stat (name.ptr, &stats)) 708 exception (name); 709 710 return stats.st_mode; 711 } 712 713 /*************************************************************** 714 715 Return whether the file or path exists. 716 717 ***************************************************************/ 718 719 static bool exists (const(char)[] name) 720 { 721 stat_t stats = void; 722 return posix.stat (name.ptr, &stats) is 0; 723 } 724 725 /*************************************************************** 726 727 Return the file length (in bytes.) 728 729 ***************************************************************/ 730 731 static ulong fileSize (const(char)[] name) 732 { 733 stat_t stats = void; 734 735 getInfo (name, stats); 736 return cast(ulong) stats.st_size; 737 } 738 739 /*************************************************************** 740 741 Is this file writable? 742 743 ***************************************************************/ 744 745 static bool isWritable (const(char)[] name) 746 { 747 stat_t stats = void; 748 749 return (getInfo(name, stats) & O_RDONLY) is 0; 750 } 751 752 /*************************************************************** 753 754 Is this file actually a folder/directory? 755 756 ***************************************************************/ 757 758 static bool isFolder (const(char)[] name) 759 { 760 stat_t stats = void; 761 762 return (getInfo(name, stats) & S_IFMT) is S_IFDIR; 763 } 764 765 /*************************************************************** 766 767 Is this a normal file? 768 769 ***************************************************************/ 770 771 static bool isFile (const(char)[] name) 772 { 773 stat_t stats = void; 774 775 return (getInfo(name, stats) & S_IFMT) is S_IFREG; 776 } 777 778 /*************************************************************** 779 780 Return timestamp information. 781 782 Timestamps are returns in a format dictated by the 783 file-system. For example NTFS keeps UTC time, 784 while FAT timestamps are based on the local time. 785 786 ***************************************************************/ 787 788 static Stamps timeStamps (const(char)[] name) 789 { 790 static Time convert (typeof(stat_t.st_mtime) secs) 791 { 792 return Time.epoch1970 + 793 TimeSpan.fromSeconds(secs); 794 } 795 796 stat_t stats = void; 797 Stamps time = void; 798 799 getInfo (name, stats); 800 801 time.modified = convert (stats.st_mtime); 802 time.accessed = convert (stats.st_atime); 803 time.created = convert (stats.st_ctime); 804 return time; 805 } 806 807 /*************************************************************** 808 809 Set the accessed and modified timestamps of the 810 specified file. 811 812 ***************************************************************/ 813 814 static void timeStamps (const(char)[] name, Time accessed, Time modified) 815 { 816 utimbuf time = void; 817 time.actime = cast(time_t)(accessed - Time.epoch1970).seconds; 818 time.modtime = cast(time_t)(modified - Time.epoch1970).seconds; 819 if (utime (name.ptr, &time) is -1) 820 exception (name); 821 } 822 823 /*********************************************************************** 824 825 Transfer the content of another file to this one. Returns a 826 reference to this class on success, or throws an IOException 827 upon failure. 828 829 Note: Allocates a memory buffer. 830 831 ***********************************************************************/ 832 833 static void copy (const(char)[] source, const(char)[] dest) 834 { 835 auto src = posix.open (source.ptr, O_RDONLY, octal!(640)); 836 scope (exit) 837 if (src != -1) 838 posix.close (src); 839 840 auto dst = posix.open (dest.ptr, O_CREAT | O_RDWR, octal!(660)); 841 scope (exit) 842 if (dst != -1) 843 posix.close (dst); 844 845 if (src is -1 || dst is -1) 846 exception (source); 847 848 // copy content 849 ubyte[] buf = new ubyte [16 * 1024]; 850 auto read = posix.read (src, buf.ptr, buf.length); 851 while (read > 0) 852 { 853 auto p = buf.ptr; 854 do { 855 auto written = posix.write (dst, p, read); 856 p += written; 857 read -= written; 858 if (written is -1) 859 exception (dest); 860 } while (read > 0); 861 read = posix.read (src, buf.ptr, buf.length); 862 } 863 if (read is -1) 864 exception (source); 865 866 // copy timestamps 867 stat_t stats; 868 if (posix.stat (source.ptr, &stats)) 869 exception (source); 870 871 utimbuf utim; 872 utim.actime = stats.st_atime; 873 utim.modtime = stats.st_mtime; 874 if (utime (dest.ptr, &utim) is -1) 875 exception (dest); 876 } 877 878 /*************************************************************** 879 880 Remove the file/directory from the file-system. 881 Returns true on success - false otherwise. 882 883 ***************************************************************/ 884 885 static bool remove (const(char)[] name) 886 { 887 return tango.stdc.stdio.remove(name.ptr) != -1; 888 } 889 890 /*************************************************************** 891 892 Change the name or location of a file/directory. 893 894 ***************************************************************/ 895 896 static void rename (const(char)[] src, const(char)[] dst) 897 { 898 if (tango.stdc.stdio.rename (src.ptr, dst.ptr) is -1) 899 exception (src); 900 } 901 902 /*************************************************************** 903 904 Create a new file. 905 906 ***************************************************************/ 907 908 static void createFile (const(char)[] name) 909 { 910 int fd; 911 912 fd = posix.open (name.ptr, O_CREAT | O_WRONLY | O_TRUNC, octal!(660)); 913 if (fd is -1) 914 exception (name); 915 916 if (posix.close(fd) is -1) 917 exception (name); 918 } 919 920 /*************************************************************** 921 922 Create a new directory. 923 924 ***************************************************************/ 925 926 static void createFolder (const(char)[] name) 927 { 928 if (posix.mkdir (name.ptr, octal!(777))) 929 exception (name); 930 } 931 932 /*************************************************************** 933 934 List the set of filenames within this folder. 935 936 Each path and filename is passed to the provided 937 delegate, along with the path prefix and whether 938 the entry is a folder or not. 939 940 Note: Allocates and reuses a small memory buffer. 941 942 ***************************************************************/ 943 944 static int list (const(char)[] folder, scope int delegate(ref FileInfo) dg, bool all=false) 945 { 946 int ret; 947 DIR* dir; 948 dirent entry; 949 dirent* pentry; 950 stat_t sbuf; 951 const(char)[] prefix; 952 char[] sfnbuf; 953 954 dir = tango.stdc.posix.dirent.opendir (folder.ptr); 955 if (! dir) 956 return ret; 957 958 scope (exit) 959 tango.stdc.posix.dirent.closedir (dir); 960 961 // ensure a trailing '/' is present 962 prefix = FS.padded (folder[0..$-1]); 963 964 // prepare our filename buffer 965 sfnbuf = prefix.dup; 966 967 while (true) 968 { 969 // pentry is null at end of listing, or on an error 970 readdir_r (dir, &entry, &pentry); 971 if (pentry is null) 972 break; 973 974 auto len = tango.stdc..string.strlen (entry.d_name.ptr); 975 auto str = entry.d_name.ptr [0 .. len]; 976 ++len; // include the null 977 978 // resize the buffer as necessary ... 979 if (sfnbuf.length < prefix.length + len) 980 sfnbuf.length = prefix.length + len; 981 982 sfnbuf [prefix.length .. prefix.length + len] 983 = entry.d_name.ptr [0 .. len]; 984 985 // skip "..." names 986 if (str.length > 3 || str != "..."[0 .. str.length]) 987 { 988 FileInfo info = void; 989 info.bytes = 0; 990 info.name = str; 991 info.path = prefix; 992 info.hidden = str[0] is '.'; 993 info.folder = info.system = false; 994 995 if (! stat (sfnbuf.ptr, &sbuf)) 996 { 997 info.folder = (sbuf.st_mode & S_IFDIR) != 0; 998 if (info.folder is false) 999 { 1000 if ((sbuf.st_mode & S_IFREG) is 0) 1001 info.system = true; 1002 else 1003 info.bytes = cast(ulong) sbuf.st_size; 1004 } 1005 } 1006 if (all || (info.hidden | info.system) is false) 1007 if ((ret = dg(info)) != 0) 1008 break; 1009 } 1010 } 1011 return ret; 1012 } 1013 } 1014 } 1015 1016 1017 /******************************************************************************* 1018 1019 Parses a file path. 1020 1021 File paths containing non-ansi characters should be UTF-8 encoded. 1022 Supporting Unicode in this manner was deemed to be more suitable 1023 than providing a wchar version of PathParser, and is both consistent 1024 & compatible with the approach taken with the Uri class. 1025 1026 Note that patterns of adjacent '.' separators are treated specially 1027 in that they will be assigned to the name where there is no distinct 1028 suffix. In addition, a '.' at the start of a name signifies it does 1029 not belong to the suffix i.e. ".file" is a name rather than a suffix. 1030 Patterns of intermediate '.' characters will otherwise be assigned 1031 to the suffix, such that "file....suffix" includes the dots within 1032 the suffix itself. See method ext() for a suffix without dots. 1033 1034 Note also that normalization of path-separators does *not* occur by 1035 default. This means that usage of '\' characters should be explicitly 1036 converted beforehand into '/' instead (an exception is thrown in those 1037 cases where '\' is present). On-the-fly conversion is avoided because 1038 (a) the provided path is considered immutable and (b) we avoid taking 1039 a copy of the original path. Module FilePath exists at a higher level, 1040 without such contraints. 1041 1042 *******************************************************************************/ 1043 1044 struct PathParser(char_t = char) 1045 { 1046 package char_t[] fp; // filepath with trailing 1047 package int end_, // before any trailing 0 1048 ext_, // after rightmost '.' 1049 name_, // file/dir name 1050 folder_, // path before name 1051 suffix_; // including leftmost '.' 1052 1053 /*********************************************************************** 1054 1055 Parse the path spec. 1056 1057 ***********************************************************************/ 1058 1059 PathParser parse (char_t[] path) 1060 { 1061 return parse (path, path.length); 1062 } 1063 1064 /*********************************************************************** 1065 1066 Duplicate this path. 1067 1068 Note: Allocates memory for the path content. 1069 1070 ***********************************************************************/ 1071 1072 @property PathParser dup () const 1073 { 1074 PathParser ret; 1075 ret.fp = fp.dup; 1076 ret.end_ = end_; 1077 ret.ext_ = ext_; 1078 ret.name_ = name_; 1079 ret.folder_ = folder_; 1080 ret.suffix_ = suffix_; 1081 1082 return ret; 1083 } 1084 1085 /*********************************************************************** 1086 1087 Return the complete text of this filepath. 1088 1089 ***********************************************************************/ 1090 1091 inout(char_t)[] dString () inout 1092 { 1093 return fp [0 .. end_]; 1094 } 1095 1096 immutable(char)[] toString() const 1097 { 1098 return dString().idup; 1099 } 1100 1101 /*********************************************************************** 1102 1103 Return the root of this path. Roots are constructs such as 1104 "C:". 1105 1106 ***********************************************************************/ 1107 1108 @property inout(char_t)[] root () inout 1109 { 1110 return fp [0 .. folder_]; 1111 } 1112 1113 /*********************************************************************** 1114 1115 Return the file path. Paths may start and end with a "/". 1116 The root path is "/" and an unspecified path is returned as 1117 an empty string. Directory paths may be split such that the 1118 directory name is placed into the 'name' member; directory 1119 paths are treated no differently than file paths. 1120 1121 ***********************************************************************/ 1122 1123 1124 1125 @property inout(char_t)[] folder () inout 1126 { 1127 return fp [folder_ .. name_]; 1128 } 1129 1130 /*********************************************************************** 1131 1132 Returns a path representing the parent of this one. This 1133 will typically return the current path component, though 1134 with a special case where the name component is empty. In 1135 such cases, the path is scanned for a prior segment: 1136 $(UL 1137 $(LI normal: /x/y/z => /x/y) 1138 $(LI special: /x/y/ => /x) 1139 $(LI normal: /x => /) 1140 $(LI normal: / => [empty])) 1141 1142 Note that this returns a path suitable for splitting into 1143 path and name components (there's no trailing separator). 1144 1145 ***********************************************************************/ 1146 1147 @property inout(char_t)[] parent () inout 1148 { 1149 auto p = path; 1150 if (name.length is 0) 1151 for (int i=cast(int)p.length-1; --i > 0;) 1152 if (p[i] is FileConst.PathSeparatorChar) 1153 { 1154 p = p[0 .. i]; 1155 break; 1156 } 1157 return FS.stripped (p); 1158 } 1159 1160 /*********************************************************************** 1161 1162 Pop the rightmost element off this path, stripping off a 1163 trailing '/' as appropriate: 1164 $(UL 1165 $(LI /x/y/z => /x/y) 1166 $(LI /x/y/ => /x/y (note trailing '/' in the original)) 1167 $(LI /x/y => /x) 1168 $(LI /x => /) 1169 $(LI / => [empty])) 1170 1171 Note that this returns a path suitable for splitting into 1172 path and name components (there's no trailing separator). 1173 1174 ***********************************************************************/ 1175 1176 inout(char_t)[] pop () inout 1177 { 1178 return FS.stripped (path); 1179 } 1180 1181 /*********************************************************************** 1182 1183 Return the name of this file, or directory. 1184 1185 ***********************************************************************/ 1186 1187 @property inout(char_t)[] name () inout 1188 { 1189 return fp [name_ .. suffix_]; 1190 } 1191 1192 /*********************************************************************** 1193 1194 Ext is the tail of the filename, rightward of the rightmost 1195 '.' separator e.g. path "foo.bar" has ext "bar". Note that 1196 patterns of adjacent separators are treated specially - for 1197 example, ".." will wind up with no ext at all. 1198 1199 ***********************************************************************/ 1200 1201 @property char_t[] ext () 1202 { 1203 auto x = suffix; 1204 if (x.length) 1205 { 1206 if (ext_ is 0) 1207 foreach (c; x) 1208 { 1209 if (c is '.') 1210 ++ext_; 1211 else 1212 break; 1213 } 1214 x = x [ext_ .. $]; 1215 } 1216 return x; 1217 } 1218 1219 /*********************************************************************** 1220 1221 Suffix is like ext, but includes the separator e.g. path 1222 "foo.bar" has suffix ".bar". 1223 1224 ***********************************************************************/ 1225 1226 @property inout(char_t)[] suffix () inout 1227 { 1228 return fp [suffix_ .. end_]; 1229 } 1230 1231 /*********************************************************************** 1232 1233 Return the root + folder combination. 1234 1235 ***********************************************************************/ 1236 1237 @property inout(char_t)[] path () inout 1238 { 1239 return fp [0 .. name_]; 1240 } 1241 1242 /*********************************************************************** 1243 1244 Return the name + suffix combination. 1245 1246 ***********************************************************************/ 1247 1248 @property inout(char_t)[] file () inout 1249 { 1250 return fp [name_ .. end_]; 1251 } 1252 1253 /*********************************************************************** 1254 1255 Returns true if this path is *not* relative to the 1256 current working directory. 1257 1258 ***********************************************************************/ 1259 1260 @property const bool isAbsolute () 1261 { 1262 return (folder_ > 0) || 1263 (folder_ < end_ && fp[folder_] is FileConst.PathSeparatorChar); 1264 } 1265 1266 /*********************************************************************** 1267 1268 Returns true if this FilePath is empty. 1269 1270 ***********************************************************************/ 1271 1272 @property const bool isEmpty () 1273 { 1274 return end_ is 0; 1275 } 1276 1277 /*********************************************************************** 1278 1279 Returns true if this path has a parent. Note that a 1280 parent is defined by the presence of a path-separator in 1281 the path. This means 'foo' within "/foo" is considered a 1282 child of the root. 1283 1284 ***********************************************************************/ 1285 1286 @property const bool isChild () 1287 { 1288 return folder().length > 0; 1289 } 1290 1291 /*********************************************************************** 1292 1293 Does this path equate to the given text? We ignore trailing 1294 path-separators when testing equivalence. 1295 1296 ***********************************************************************/ 1297 1298 /*int opEquals (char[] s) 1299 { 1300 return FS.stripped(s) == FS.stripped(toString); 1301 }*/ 1302 const bool equals (const(char)[] s) 1303 { 1304 return FS.stripped(s) == FS.stripped(dString()); 1305 } 1306 1307 /*********************************************************************** 1308 1309 Parse the path spec with explicit end point. A '\' is 1310 considered illegal in the path and should be normalized 1311 out before this is invoked (the content managed here is 1312 considered immutable, and thus cannot be changed by this 1313 function.) 1314 1315 ***********************************************************************/ 1316 1317 package PathParser parse (char_t[] path, size_t end) 1318 { 1319 end_ = cast(int)end; 1320 fp = path; 1321 folder_ = 0; 1322 name_ = suffix_ = -1; 1323 1324 for (int i=end_; --i >= 0;) 1325 switch (fp[i]) 1326 { 1327 case FileConst.FileSeparatorChar: 1328 if (name_ < 0) 1329 if (suffix_ < 0 && i && fp[i-1] != '.') 1330 suffix_ = i; 1331 break; 1332 1333 case FileConst.PathSeparatorChar: 1334 if (name_ < 0) 1335 name_ = i + 1; 1336 break; 1337 1338 // Windows file separators are illegal. Use 1339 // standard() or equivalent to convert first 1340 case '\\': 1341 FS.exception ("unexpected '\\' character in path: ", path[0..end]); 1342 break; 1343 version (Win32) 1344 { 1345 case ':': 1346 folder_ = i + 1; 1347 break; 1348 } 1349 1350 default: 1351 break; 1352 } 1353 1354 if (name_ < 0) 1355 name_ = folder_; 1356 1357 if (suffix_ < 0 || suffix_ is name_) 1358 suffix_ = end_; 1359 1360 return this; 1361 } 1362 } 1363 1364 1365 /******************************************************************************* 1366 1367 Does this path currently exist? 1368 1369 *******************************************************************************/ 1370 1371 bool exists (const(char)[] name) 1372 { 1373 char[512] tmp = void; 1374 return FS.exists (FS.strz(name, tmp)); 1375 } 1376 1377 /******************************************************************************* 1378 1379 Returns the time of the last modification. Accurate 1380 to whatever the F/S supports, and in a format dictated 1381 by the file-system. For example NTFS keeps UTC time, 1382 while FAT timestamps are based on the local time. 1383 1384 *******************************************************************************/ 1385 1386 Time modified (const(char)[] name) 1387 { 1388 return timeStamps(name).modified; 1389 } 1390 1391 /******************************************************************************* 1392 1393 Returns the time of the last access. Accurate to 1394 whatever the F/S supports, and in a format dictated 1395 by the file-system. For example NTFS keeps UTC time, 1396 while FAT timestamps are based on the local time. 1397 1398 *******************************************************************************/ 1399 1400 Time accessed (const(char)[] name) 1401 { 1402 return timeStamps(name).accessed; 1403 } 1404 1405 /******************************************************************************* 1406 1407 Returns the time of file creation. Accurate to 1408 whatever the F/S supports, and in a format dictated 1409 by the file-system. For example NTFS keeps UTC time, 1410 while FAT timestamps are based on the local time. 1411 1412 *******************************************************************************/ 1413 1414 Time created (const(char)[] name) 1415 { 1416 return timeStamps(name).created; 1417 } 1418 1419 /******************************************************************************* 1420 1421 Return the file length (in bytes.) 1422 1423 *******************************************************************************/ 1424 1425 ulong fileSize (const(char)[] name) 1426 { 1427 char[512] tmp = void; 1428 return FS.fileSize (FS.strz(name, tmp)); 1429 } 1430 1431 /******************************************************************************* 1432 1433 Is this file writable? 1434 1435 *******************************************************************************/ 1436 1437 bool isWritable (const(char)[] name) 1438 { 1439 char[512] tmp = void; 1440 return FS.isWritable (FS.strz(name, tmp)); 1441 } 1442 1443 /******************************************************************************* 1444 1445 Is this file actually a folder/directory? 1446 1447 *******************************************************************************/ 1448 1449 bool isFolder (const(char)[] name) 1450 { 1451 char[512] tmp = void; 1452 return FS.isFolder (FS.strz(name, tmp)); 1453 } 1454 1455 /******************************************************************************* 1456 1457 Is this file actually a normal file? 1458 Not a directory or (on unix) a device file or link. 1459 1460 *******************************************************************************/ 1461 1462 bool isFile (const(char)[] name) 1463 { 1464 char[512] tmp = void; 1465 return FS.isFile (FS.strz(name, tmp)); 1466 } 1467 1468 /******************************************************************************* 1469 1470 Return timestamp information. 1471 1472 Timestamps are returns in a format dictated by the 1473 file-system. For example NTFS keeps UTC time, 1474 while FAT timestamps are based on the local time. 1475 1476 *******************************************************************************/ 1477 1478 FS.Stamps timeStamps (const(char)[] name) 1479 { 1480 char[512] tmp = void; 1481 return FS.timeStamps (FS.strz(name, tmp)); 1482 } 1483 1484 /******************************************************************************* 1485 1486 Set the accessed and modified timestamps of the specified file. 1487 1488 Since: 0.99.9 1489 1490 *******************************************************************************/ 1491 1492 void timeStamps (const(char)[] name, Time accessed, Time modified) 1493 { 1494 char[512] tmp = void; 1495 FS.timeStamps (FS.strz(name, tmp), accessed, modified); 1496 } 1497 1498 /******************************************************************************* 1499 1500 Remove the file/directory from the file-system. Returns true if 1501 successful, false otherwise. 1502 1503 *******************************************************************************/ 1504 1505 bool remove (const(char)[] name) 1506 { 1507 char[512] tmp = void; 1508 return FS.remove (FS.strz(name, tmp)); 1509 } 1510 1511 /******************************************************************************* 1512 1513 Remove the files and folders listed in the provided paths. Where 1514 folders are listed, they should be preceded by their contained 1515 files in order to be successfully removed. Returns a set of paths 1516 that failed to be removed (where .length is zero upon success). 1517 1518 The collate() function can be used to provide the input paths: 1519 --- 1520 remove (collate (".", "*.d", true)); 1521 --- 1522 1523 Use with great caution. 1524 1525 Note: May allocate memory. 1526 1527 Since: 0.99.9 1528 1529 *******************************************************************************/ 1530 1531 char[][] remove (char[][] paths) 1532 { 1533 char[][] failed; 1534 foreach (path; paths) 1535 if (! remove (path)) 1536 failed ~= path; 1537 return failed; 1538 } 1539 1540 /******************************************************************************* 1541 1542 Create a new file. 1543 1544 *******************************************************************************/ 1545 1546 void createFile (const(char)[] name) 1547 { 1548 char[512] tmp = void; 1549 FS.createFile (FS.strz(name, tmp)); 1550 } 1551 1552 /******************************************************************************* 1553 1554 Create a new directory. 1555 1556 *******************************************************************************/ 1557 1558 void createFolder (const(char)[] name) 1559 { 1560 char[512] tmp = void; 1561 FS.createFolder (FS.strz(name, tmp)); 1562 } 1563 1564 /******************************************************************************* 1565 1566 Create an entire path consisting of this folder along with 1567 all parent folders. The path should not contain '.' or '..' 1568 segments, which can be removed via the normalize() function. 1569 1570 Note that each segment is created as a folder, including the 1571 trailing segment. 1572 1573 Throws: IOException upon system errors. 1574 1575 Throws: IllegalArgumentException if a segment exists but as a 1576 file instead of a folder. 1577 1578 *******************************************************************************/ 1579 1580 void createPath (const(char)[] path) 1581 { 1582 void test (const(char)[] segment) 1583 { 1584 if (segment.length) 1585 { 1586 if (! exists (segment)) 1587 createFolder (segment); 1588 else 1589 if (! isFolder (segment)) 1590 throw new IllegalArgumentException (("Path.createPath :: file/folder conflict: " ~ segment).idup); 1591 } 1592 } 1593 1594 foreach (i, char c; path) 1595 if (c is '/') 1596 test (path [0 .. i]); 1597 test (path); 1598 } 1599 1600 /******************************************************************************* 1601 1602 Change the name or location of a file/directory. 1603 1604 *******************************************************************************/ 1605 1606 void rename (const(char)[] src, const(char)[] dst) 1607 { 1608 char[512] tmp1 = void; 1609 char[512] tmp2 = void; 1610 FS.rename (FS.strz(src, tmp1), FS.strz(dst, tmp2)); 1611 } 1612 1613 /******************************************************************************* 1614 1615 Transfer the content of one file to another. Throws 1616 an IOException upon failure. 1617 1618 *******************************************************************************/ 1619 1620 void copy (const(char)[] src, const(char)[] dst) 1621 { 1622 char[512] tmp1 = void; 1623 char[512] tmp2 = void; 1624 FS.copy (FS.strz(src, tmp1), FS.strz(dst, tmp2)); 1625 } 1626 1627 /******************************************************************************* 1628 1629 Provides foreach support via a fruct, as in 1630 --- 1631 foreach (info; children("myfolder")) 1632 ... 1633 --- 1634 1635 Each path and filename is passed to the foreach 1636 delegate, along with the path prefix and whether 1637 the entry is a folder or not. The info construct 1638 exposes the following attributes: 1639 --- 1640 char[] path 1641 char[] name 1642 ulong bytes 1643 bool folder 1644 --- 1645 1646 Argument 'all' controls whether hidden and system 1647 files are included - these are ignored by default. 1648 1649 *******************************************************************************/ 1650 1651 FS.Listing children (const(char)[] path, bool all=false) 1652 { 1653 return FS.Listing (path, all); 1654 } 1655 1656 /******************************************************************************* 1657 1658 Collate all files and folders from the given path whose name matches 1659 the given pattern. Folders will be traversed where recurse is enabled, 1660 and a set of matching names is returned as filepaths (including those 1661 folders which match the pattern.) 1662 1663 Note: Allocates memory for returned paths. 1664 1665 Since: 0.99.9 1666 1667 *******************************************************************************/ 1668 1669 char[][] collate (const(char)[] path, const(char)[] pattern, bool recurse=false) 1670 { 1671 char[][] list; 1672 1673 foreach (info; children (path)) 1674 { 1675 if (info.folder && recurse) 1676 list ~= collate (join(info.path, info.name), pattern, true); 1677 1678 if (patternMatch (info.name, pattern)) 1679 list ~= join (info.path, info.name); 1680 } 1681 return list; 1682 } 1683 1684 /******************************************************************************* 1685 1686 Join a set of path specs together. A path separator is 1687 potentially inserted between each of the segments. 1688 1689 Note: May allocate memory. 1690 1691 *******************************************************************************/ 1692 1693 char[] join (const(char[])[] paths...) 1694 { 1695 return FS.join (paths); 1696 } 1697 1698 /******************************************************************************* 1699 1700 Convert path separators to a standard format, using '/' as 1701 the path separator. This is compatible with Uri and all of 1702 the contemporary O/S which Tango supports. Known exceptions 1703 include the Windows command-line processor, which considers 1704 '/' characters to be switches instead. Use the native() 1705 method to support that. 1706 1707 Note: Mutates the provided path. 1708 1709 *******************************************************************************/ 1710 1711 char[] standard (char[] path) 1712 { 1713 return replace (path, '\\', '/'); 1714 } 1715 1716 /******************************************************************************* 1717 1718 Convert to native O/S path separators where that is required, 1719 such as when dealing with the Windows command-line. 1720 1721 Note: Mutates the provided path. Use this pattern to obtain a 1722 copy instead: native(path.dup); 1723 1724 *******************************************************************************/ 1725 1726 char[] native (char[] path) 1727 { 1728 version (Win32) 1729 replace (path, '/', '\\'); 1730 return path; 1731 } 1732 1733 /******************************************************************************* 1734 1735 Returns a path representing the parent of this one, with a special 1736 case concerning a trailing '/': 1737 $(UL 1738 $(LI normal: /x/y/z => /x/y) 1739 $(LI normal: /x/y/ => /x/y) 1740 $(LI special: /x/y/ => /x) 1741 $(LI normal: /x => /) 1742 $(LI normal: / => empty)) 1743 1744 The result can be split via parse(). 1745 1746 *******************************************************************************/ 1747 1748 inout(char)[] parent (inout(char)[] path) 1749 { 1750 return pop (FS.stripped (path)); 1751 } 1752 1753 /******************************************************************************* 1754 1755 Returns a path representing the parent of this one: 1756 $(UL 1757 $(LI normal: /x/y/z => /x/y) 1758 $(LI normal: /x/y/ => /x/y) 1759 $(LI normal: /x => /) 1760 $(LI normal: / => empty)) 1761 1762 The result can be split via parse(). 1763 1764 *******************************************************************************/ 1765 1766 inout(char)[] pop (inout(char)[] path) 1767 { 1768 int i = cast(int)path.length; 1769 while (i && path[--i] != '/') {} 1770 return path [0..i]; 1771 } 1772 1773 /******************************************************************************* 1774 1775 Break a path into "head" and "tail" components. For example: 1776 $(UL 1777 $(LI "/a/b/c" -> "/a","b/c") 1778 $(LI "a/b/c" -> "a","b/c")) 1779 1780 *******************************************************************************/ 1781 1782 inout(char)[] split (inout(char)[] path, out inout(char)[] head, out inout(char)[] tail) 1783 { 1784 head = path; 1785 if (path.length > 1) 1786 foreach (i, char c; path[1..$]) 1787 if (c is '/') 1788 { 1789 head = path [0 .. i+1]; 1790 tail = path [i+2 .. $]; 1791 break; 1792 } 1793 return path; 1794 } 1795 1796 /******************************************************************************* 1797 1798 Replace all path 'from' instances with 'to', in place (overwrites 1799 the provided path). 1800 1801 *******************************************************************************/ 1802 1803 char[] replace (char[] path, char from, char to) 1804 { 1805 foreach (ref char c; path) 1806 if (c is from) 1807 c = to; 1808 return path; 1809 } 1810 1811 /******************************************************************************* 1812 1813 Parse a path into its constituent components. 1814 1815 Note that the provided path is sliced, not duplicated. 1816 1817 *******************************************************************************/ 1818 1819 PathParser!(char_t) parse(char_t) (char_t[] path) 1820 { 1821 PathParser!(char_t) p; 1822 1823 p.parse (path); 1824 return p; 1825 } 1826 1827 /******************************************************************************* 1828 1829 *******************************************************************************/ 1830 1831 debug(UnitTest) 1832 { 1833 unittest 1834 { 1835 auto p = parse ("/foo/bar/file.ext".dup); 1836 assert (p.equals("/foo/bar/file.ext")); 1837 assert (p.folder == "/foo/bar/"); 1838 assert (p.path == "/foo/bar/"); 1839 assert (p.file == "file.ext"); 1840 assert (p.name == "file"); 1841 assert (p.suffix == ".ext"); 1842 assert (p.ext == "ext"); 1843 assert (p.isChild == true); 1844 assert (p.isEmpty == false); 1845 assert (p.isAbsolute == true); 1846 } 1847 } 1848 1849 1850 /****************************************************************************** 1851 1852 Matches a pattern against a filename. 1853 1854 Some characters of pattern have special a meaning (they are 1855 $(EM meta-characters)) and $(B can't) be escaped. These are: 1856 1857 $(TABLE 1858 $(TR 1859 $(TD $(B *)) 1860 $(TD Matches 0 or more instances of any character.)) 1861 $(TR 1862 $(TD $(B ?)) 1863 $(TD Matches exactly one instances of any character.)) 1864 $(TR 1865 $(TD $(B [)$(EM chars)$(B ])) 1866 $(TD Matches one instance of any character that appears 1867 between the brackets.)) 1868 $(TR 1869 $(TD $(B [!)$(EM chars)$(B ])) 1870 $(TD Matches one instance of any character that does not appear 1871 between the brackets after the exclamation mark.)) 1872 ) 1873 1874 Internally individual character comparisons are done calling 1875 charMatch(), so its rules apply here too. Note that path 1876 separators and dots don't stop a meta-character from matching 1877 further portions of the filename. 1878 1879 Returns: true if pattern matches filename, false otherwise. 1880 1881 Throws: Nothing. 1882 ----- 1883 version (Win32) 1884 { 1885 patternMatch("foo.bar", "*"); // => true 1886 patternMatch(r"foo/foo\bar", "f*b*r"); // => true 1887 patternMatch("foo.bar", "f?bar"); // => false 1888 patternMatch("Goo.bar", "[fg]???bar"); // => true 1889 patternMatch(r"d:\foo\bar", "d*foo?bar"); // => true 1890 } 1891 version (Posix) 1892 { 1893 patternMatch("Go*.bar", "[fg]???bar"); // => false 1894 patternMatch("/foo*home/bar", "?foo*bar"); // => true 1895 patternMatch("foobar", "foo?bar"); // => true 1896 } 1897 ----- 1898 1899 ******************************************************************************/ 1900 1901 bool patternMatch (const(char)[] filename, const(char)[] pattern) 1902 in 1903 { 1904 // Verify that pattern[] is valid 1905 bool inbracket = false; 1906 for (auto i=0; i < pattern.length; i++) 1907 { 1908 switch (pattern[i]) 1909 { 1910 case '[': 1911 assert(!inbracket); 1912 inbracket = true; 1913 break; 1914 case ']': 1915 assert(inbracket); 1916 inbracket = false; 1917 break; 1918 default: 1919 break; 1920 } 1921 } 1922 } 1923 body 1924 { 1925 int pi; 1926 int ni; 1927 char pc; 1928 char nc; 1929 int j; 1930 int not; 1931 int anymatch; 1932 1933 bool charMatch (char c1, char c2) 1934 { 1935 version (Win32) 1936 { 1937 if (c1 != c2) 1938 return ((c1 >= 'a' && c1 <= 'z') ? c1 - ('a' - 'A') : c1) == 1939 ((c2 >= 'a' && c2 <= 'z') ? c2 - ('a' - 'A') : c2); 1940 return true; 1941 } 1942 version (Posix) 1943 return c1 == c2; 1944 } 1945 1946 ni = 0; 1947 for (pi = 0; pi < pattern.length; pi++) 1948 { 1949 pc = pattern [pi]; 1950 switch (pc) 1951 { 1952 case '*': 1953 if (pi + 1 == pattern.length) 1954 goto match; 1955 for (j = ni; j < filename.length; j++) 1956 { 1957 if (patternMatch(filename[j .. filename.length], 1958 pattern[pi + 1 .. pattern.length])) 1959 goto match; 1960 } 1961 goto nomatch; 1962 1963 case '?': 1964 if (ni == filename.length) 1965 goto nomatch; 1966 ni++; 1967 break; 1968 1969 case '[': 1970 if (ni == filename.length) 1971 goto nomatch; 1972 nc = filename[ni]; 1973 ni++; 1974 not = 0; 1975 pi++; 1976 if (pattern[pi] == '!') 1977 { 1978 not = 1; 1979 pi++; 1980 } 1981 anymatch = 0; 1982 while (1) 1983 { 1984 pc = pattern[pi]; 1985 if (pc == ']') 1986 break; 1987 if (!anymatch && charMatch(nc, pc)) 1988 anymatch = 1; 1989 pi++; 1990 } 1991 if (!(anymatch ^ not)) 1992 goto nomatch; 1993 break; 1994 1995 default: 1996 if (ni == filename.length) 1997 goto nomatch; 1998 nc = filename[ni]; 1999 if (!charMatch(pc, nc)) 2000 goto nomatch; 2001 ni++; 2002 break; 2003 } 2004 } 2005 if (ni < filename.length) 2006 goto nomatch; 2007 2008 match: 2009 return true; 2010 2011 nomatch: 2012 return false; 2013 } 2014 2015 /******************************************************************************* 2016 2017 *******************************************************************************/ 2018 2019 debug (UnitTest) 2020 { 2021 unittest 2022 { 2023 version (Win32) 2024 assert(patternMatch("foo", "Foo")); 2025 version (Posix) 2026 assert(!patternMatch("foo", "Foo")); 2027 2028 assert(patternMatch("foo", "*")); 2029 assert(patternMatch("foo.bar", "*")); 2030 assert(patternMatch("foo.bar", "*.*")); 2031 assert(patternMatch("foo.bar", "foo*")); 2032 assert(patternMatch("foo.bar", "f*bar")); 2033 assert(patternMatch("foo.bar", "f*b*r")); 2034 assert(patternMatch("foo.bar", "f???bar")); 2035 assert(patternMatch("foo.bar", "[fg]???bar")); 2036 assert(patternMatch("foo.bar", "[!gh]*bar")); 2037 2038 assert(!patternMatch("foo", "bar")); 2039 assert(!patternMatch("foo", "*.*")); 2040 assert(!patternMatch("foo.bar", "f*baz")); 2041 assert(!patternMatch("foo.bar", "f*b*x")); 2042 assert(!patternMatch("foo.bar", "[gh]???bar")); 2043 assert(!patternMatch("foo.bar", "[!fg]*bar")); 2044 assert(!patternMatch("foo.bar", "[fg]???baz")); 2045 } 2046 } 2047 2048 2049 /******************************************************************************* 2050 2051 Normalizes a path component. 2052 $(UL 2053 $(LI $(B .) segments are removed) 2054 $(LI <segment>$(B /..) are removed)) 2055 2056 Multiple consecutive forward slashes are replaced with a single 2057 forward slash. On Windows, \ will be converted to / prior to any 2058 normalization. 2059 2060 Note that any number of .. segments at the front is ignored, 2061 unless it is an absolute path, in which case they are removed. 2062 2063 The input path is copied into either the provided buffer, or a heap 2064 allocated array if no buffer was provided. Normalization modifies 2065 this copy before returning the relevant slice. 2066 ----- 2067 normalize("/home/foo/./bar/../../john/doe"); // => "/home/john/doe" 2068 ----- 2069 2070 Note: Allocates memory. 2071 2072 *******************************************************************************/ 2073 2074 char[] normalize (const(char)[] in_path, char[] buf = null) 2075 { 2076 char[] path; // Mutable path 2077 size_t idx; // Current position 2078 size_t moveTo; // Position to move 2079 bool isAbsolute; // Whether the path is absolute 2080 enum {NodeStackLength = 64} 2081 2082 // Starting positions of regular path segments are pushed 2083 // on this stack to avoid backward scanning when .. segments 2084 // are encountered 2085 size_t[NodeStackLength] nodeStack; 2086 size_t nodeStackTop; 2087 2088 // Moves the path tail starting at the current position to 2089 // moveTo. Then sets the current position to moveTo. 2090 void move () 2091 { 2092 auto len = path.length - idx; 2093 memmove (path.ptr + moveTo, path.ptr + idx, len); 2094 path = path[0..moveTo + len]; 2095 idx = moveTo; 2096 } 2097 2098 // Checks if the character at the current position is a 2099 // separator. If true, normalizes the separator to '/' on 2100 // Windows and advances the current position to the next 2101 // character. 2102 bool isSep (ref size_t i) 2103 { 2104 char c = path[i]; 2105 version (Windows) 2106 { 2107 if (c == '\\') 2108 path[i] = '/'; 2109 else if (c != '/') 2110 return false; 2111 } 2112 else 2113 { 2114 if (c != '/') 2115 return false; 2116 } 2117 i++; 2118 return true; 2119 } 2120 2121 if (buf is null) 2122 path = in_path.dup; 2123 else 2124 path = buf[0..in_path.length] = in_path[]; 2125 2126 version (Windows) 2127 { 2128 // Skip Windows drive specifiers 2129 if (path.length >= 2 && path[1] == ':') 2130 { 2131 auto c = path[0]; 2132 2133 if (c >= 'a' && c <= 'z') 2134 { 2135 path[0] = cast(char)(c - 32); 2136 idx = 2; 2137 } 2138 else 2139 if (c >= 'a' && c <= 'z' || c >= 'A' && c <= 'Z') 2140 idx = 2; 2141 } 2142 } 2143 2144 if (idx == path.length) 2145 return path; 2146 2147 moveTo = idx; 2148 if (isSep(idx)) 2149 { 2150 moveTo++; // preserve root separator. 2151 isAbsolute = true; 2152 } 2153 2154 while (idx < path.length) 2155 { 2156 // Skip duplicate separators 2157 if (isSep(idx)) 2158 continue; 2159 2160 if (path[idx] == '.') 2161 { 2162 // leave the current position at the start of 2163 // the segment 2164 auto i = idx + 1; 2165 if (i < path.length && path[i] == '.') 2166 { 2167 i++; 2168 if (i == path.length || isSep(i)) 2169 { 2170 // It is a '..' segment. If the stack is not 2171 // empty, set moveTo and the current position 2172 // to the start position of the last found 2173 // regular segment 2174 if (nodeStackTop > 0) 2175 moveTo = nodeStack[--nodeStackTop]; 2176 2177 // If no regular segment start positions on the 2178 // stack, drop the .. segment if it is absolute 2179 // path or, otherwise, advance moveTo and the 2180 // current position to the character after the 2181 // '..' segment 2182 else 2183 if (!isAbsolute) 2184 { 2185 if (moveTo != idx) 2186 { 2187 i -= idx - moveTo; 2188 move(); 2189 } 2190 moveTo = i; 2191 } 2192 2193 idx = i; 2194 continue; 2195 } 2196 } 2197 2198 // If it is '.' segment, skip it. 2199 if (i == path.length || isSep(i)) 2200 { 2201 idx = i; 2202 continue; 2203 } 2204 } 2205 2206 // Remove excessive '/', '.' and/or '..' preceeding the 2207 // segment 2208 if (moveTo != idx) 2209 move(); 2210 2211 // Push the start position of the regular segment on the 2212 // stack 2213 assert (nodeStackTop < NodeStackLength); 2214 nodeStack[nodeStackTop++] = idx; 2215 2216 // Skip the regular segment and set moveTo to the position 2217 // after the segment (including the trailing '/' if present) 2218 for (; idx < path.length && !isSep(idx); idx++) 2219 {} 2220 moveTo = idx; 2221 } 2222 2223 if (moveTo != idx) 2224 move(); 2225 return path; 2226 } 2227 2228 /******************************************************************************* 2229 2230 *******************************************************************************/ 2231 2232 debug (UnitTest) 2233 { 2234 unittest 2235 { 2236 assert (normalize ("") == ""); 2237 assert (normalize ("/home/../john/../.tango/.htaccess") == "/.tango/.htaccess"); 2238 assert (normalize ("/home/../john/../.tango/foo.conf") == "/.tango/foo.conf"); 2239 assert (normalize ("/home/john/.tango/foo.conf") == "/home/john/.tango/foo.conf"); 2240 assert (normalize ("/foo/bar/.htaccess") == "/foo/bar/.htaccess"); 2241 assert (normalize ("foo/bar/././.") == "foo/bar/"); 2242 assert (normalize ("././foo/././././bar") == "foo/bar"); 2243 assert (normalize ("/foo/../john") == "/john"); 2244 assert (normalize ("foo/../john") == "john"); 2245 assert (normalize ("foo/bar/..") == "foo/"); 2246 assert (normalize ("foo/bar/../john") == "foo/john"); 2247 assert (normalize ("foo/bar/doe/../../john") == "foo/john"); 2248 assert (normalize ("foo/bar/doe/../../john/../bar") == "foo/bar"); 2249 assert (normalize ("./foo/bar/doe") == "foo/bar/doe"); 2250 assert (normalize ("./foo/bar/doe/../../john/../bar") == "foo/bar"); 2251 assert (normalize ("./foo/bar/../../john/../bar") == "bar"); 2252 assert (normalize ("foo/bar/./doe/../../john") == "foo/john"); 2253 assert (normalize ("../../foo/bar") == "../../foo/bar"); 2254 assert (normalize ("../../../foo/bar") == "../../../foo/bar"); 2255 assert (normalize ("d/") == "d/"); 2256 assert (normalize ("/home/john/./foo/bar.txt") == "/home/john/foo/bar.txt"); 2257 assert (normalize ("/home//john") == "/home/john"); 2258 2259 assert (normalize("/../../bar/") == "/bar/"); 2260 assert (normalize("/../../bar/../baz/./") == "/baz/"); 2261 assert (normalize("/../../bar/boo/../baz/.bar/.") == "/bar/baz/.bar/"); 2262 assert (normalize("../..///.///bar/..//..//baz/.//boo/..") == "../../../baz/"); 2263 assert (normalize("./bar/./..boo/./..bar././/") == "bar/..boo/..bar./"); 2264 assert (normalize("/bar/..") == "/"); 2265 assert (normalize("bar/") == "bar/"); 2266 assert (normalize(".../") == ".../"); 2267 assert (normalize("///../foo") == "/foo"); 2268 assert (normalize("./foo") == "foo"); 2269 auto buf = new char[100]; 2270 auto ret = normalize("foo/bar/./baz", buf); 2271 assert (ret.ptr == buf.ptr); 2272 assert (ret == "foo/bar/baz"); 2273 2274 version (Windows) 2275 { 2276 assert (normalize ("\\foo\\..\\john") == "/john"); 2277 assert (normalize ("foo\\..\\john") == "john"); 2278 assert (normalize ("foo\\bar\\..") == "foo/"); 2279 assert (normalize ("foo\\bar\\..\\john") == "foo/john"); 2280 assert (normalize ("foo\\bar\\doe\\..\\..\\john") == "foo/john"); 2281 assert (normalize ("foo\\bar\\doe\\..\\..\\john\\..\\bar") == "foo/bar"); 2282 assert (normalize (".\\foo\\bar\\doe") == "foo/bar/doe"); 2283 assert (normalize (".\\foo\\bar\\doe\\..\\..\\john\\..\\bar") == "foo/bar"); 2284 assert (normalize (".\\foo\\bar\\..\\..\\john\\..\\bar") == "bar"); 2285 assert (normalize ("foo\\bar\\.\\doe\\..\\..\\john") == "foo/john"); 2286 assert (normalize ("..\\..\\foo\\bar") == "../../foo/bar"); 2287 assert (normalize ("..\\..\\..\\foo\\bar") == "../../../foo/bar"); 2288 assert (normalize(r"C:") == "C:"); 2289 assert (normalize(r"C") == "C"); 2290 assert (normalize(r"c:\") == "C:/"); 2291 assert (normalize(r"C:\..\.\..\..\") == "C:/"); 2292 assert (normalize(r"c:..\.\boo\") == "C:../boo/"); 2293 assert (normalize(r"C:..\..\boo\foo\..\.\..\..\bar") == "C:../../../bar"); 2294 assert (normalize(r"C:boo\..") == "C:"); 2295 } 2296 } 2297 } 2298 2299 2300 /******************************************************************************* 2301 2302 *******************************************************************************/ 2303 2304 debug (Path) 2305 { 2306 import tango.io.Stdout; 2307 2308 void main() 2309 { 2310 foreach (file; collate (".", "*.d", true)) 2311 Stdout (file).newline; 2312 } 2313 }