00001 /******************************************************************************* 00002 00003 @file FilePath.d 00004 00005 Copyright (c) 2004 Kris Bell 00006 00007 This software is provided 'as-is', without any express or implied 00008 warranty. In no event will the authors be held liable for damages 00009 of any kind arising from the use of this software. 00010 00011 Permission is hereby granted to anyone to use this software for any 00012 purpose, including commercial applications, and to alter it and/or 00013 redistribute it freely, subject to the following restrictions: 00014 00015 1. The origin of this software must not be misrepresented; you must 00016 not claim that you wrote the original software. If you use this 00017 software in a product, an acknowledgment within documentation of 00018 said product would be appreciated but is not required. 00019 00020 2. Altered source versions must be plainly marked as such, and must 00021 not be misrepresented as being the original software. 00022 00023 3. This notice may not be removed or altered from any distribution 00024 of the source. 00025 00026 4. Derivative works are permitted, but they must carry this notice 00027 in full and credit the original source. 00028 00029 00030 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 00031 00032 00033 @version Initial version, March 2004 00034 @author Kris 00035 00036 00037 *******************************************************************************/ 00038 00039 module mango.io.FilePath; 00040 00041 private import mango.convert.Unicode; 00042 00043 private import mango.io.Buffer, 00044 mango.io.Exception, 00045 mango.io.FileConst; 00046 00047 private import mango.io.model.IWriter; 00048 00049 /******************************************************************************* 00050 00051 Models a file name. These are expected to be used as the constructor 00052 argument to File implementations. The intention is that they easily 00053 convert to other representations such as absolute, canonical, or Url. 00054 Note that this class is immutable. Use MutableFilePath if you wish 00055 to alter specific attributes. 00056 00057 File paths containing non-ansi characters should be UTF-8 encoded. 00058 Supporting Unicode in this manner was deemed to be more suitable 00059 than providing a wchar version of FilePath, and is both consistent 00060 & compatible with the approach taken with the Uri class. 00061 00062 *******************************************************************************/ 00063 00064 class FilePath : IWritable 00065 { 00066 private char[] fp, 00067 ext, 00068 name, 00069 path, 00070 root, 00071 suffix; 00072 00073 private wchar[] fpWide; 00074 00075 // version = MangoDebug; 00076 // version = MangoOptimize; 00077 00078 private static const int MaxFilePathSize = 1024; 00079 00080 /*********************************************************************** 00081 00082 Create an empty FilePath. This is strictly for subclass 00083 use. 00084 00085 ***********************************************************************/ 00086 00087 protected this () 00088 { 00089 } 00090 00091 /*********************************************************************** 00092 00093 Create a FilePath through reference to another. 00094 00095 ***********************************************************************/ 00096 00097 this (FilePath other) 00098 in { 00099 assert (other); 00100 } 00101 body 00102 { 00103 fp = other.fp; 00104 ext = other.ext; 00105 name = other.name; 00106 path = other.path; 00107 root = other.root; 00108 suffix = other.suffix; 00109 fpWide = other.fpWide; 00110 } 00111 /+ 00112 /*********************************************************************** 00113 00114 Create a FilePath from a Uri. Note that the Uri authority 00115 is used to house an optional root (device, drive-letter ...) 00116 00117 ***********************************************************************/ 00118 00119 this (Uri uri) 00120 { 00121 char[] path = uri.getPath(); 00122 00123 if (uri.getHost.length) 00124 path = uri.getHost ~ FileConst.RootSeparatorString ~ path; 00125 00126 this (path); 00127 } 00128 +/ 00129 /*********************************************************************** 00130 00131 Create a FilePath from the given string. Note the path 00132 is not duplicated here, so you are expected to provide 00133 an immutable copy for the lifetime of this object. 00134 00135 If you're not certain, duplicate the path first. 00136 00137 ***********************************************************************/ 00138 00139 this (char[] filepath) 00140 in { 00141 assert (filepath); 00142 assert(filepath.length > 0); 00143 } 00144 out { 00145 if (root) 00146 assert (root.length > 0); 00147 } 00148 body 00149 { 00150 int ext = -1, 00151 path = -1, 00152 root = -1, 00153 suffix = -1; 00154 00155 //printf ("FilePath: '%.*s'\n", filepath); 00156 00157 for (int i=filepath.length; i > 0; --i) 00158 switch (filepath[i-1]) 00159 { 00160 case FileConst.FileSeparatorChar: 00161 if (path < 0) 00162 { 00163 if (ext < 0) 00164 { 00165 // check for '..' sequence 00166 if (i > 1 && filepath[i-2] != FileConst.FileSeparatorChar) 00167 ext = i; 00168 } 00169 suffix = i; 00170 } 00171 break; 00172 00173 case FileConst.PathSeparatorChar: 00174 if (path < 0) 00175 path = i; 00176 break; 00177 00178 case FileConst.RootSeparatorChar: 00179 root = i; 00180 default: 00181 break; 00182 } 00183 00184 int i = filepath.length; 00185 00186 version (MangoDebug) 00187 { 00188 printf ("\n>>'%d' root:'%d' path:'%d' ext:'%d' suffix:'%d'\n", 00189 i, root, path, ext, suffix); 00190 } 00191 00192 if (ext >= 0) 00193 { 00194 this.ext = filepath [ext..i]; 00195 this.suffix = filepath [suffix..i]; 00196 --ext; 00197 } 00198 else 00199 ext = i; 00200 00201 if (root >= 1) 00202 this.root = filepath [0..root-1]; 00203 else 00204 root = 0; 00205 00206 if (path >= root) 00207 this.path = filepath [root..path]; 00208 else 00209 path = root; 00210 00211 this.name = filepath [path..ext]; 00212 00213 // save original 00214 this.fp = filepath; 00215 00216 version (MangoDebug) 00217 { 00218 printf (">>'%.*s' root:'%.*s' path:'%.*s' name:'%.*s' ext:'%.*s' suffix:'%.*s'\n", 00219 filepath, this.root, this.path, this.name, this.ext, this.suffix); 00220 } 00221 } 00222 00223 /*********************************************************************** 00224 00225 Convert path separators to the correct format. This mutates 00226 the provided 'path' content, so .dup it as necessary. 00227 00228 ***********************************************************************/ 00229 00230 static char[] normalize (inout char[] path) 00231 { 00232 version (Win32) 00233 { 00234 foreach (inout char c; path) 00235 if (c == '/') 00236 c = '\\'; 00237 } 00238 else 00239 { 00240 foreach (inout char c; path) 00241 if (c == '\\') 00242 c = '/'; 00243 } 00244 return path; 00245 } 00246 00247 /*********************************************************************** 00248 00249 ***********************************************************************/ 00250 00251 void reset () 00252 { 00253 fp = null; 00254 fpWide = null; 00255 } 00256 00257 /*********************************************************************** 00258 00259 Returns true if this FilePath is *not* relative to the 00260 current working directory. 00261 00262 ***********************************************************************/ 00263 00264 bool isAbsolute () 00265 { 00266 return cast(bool) (root.length || 00267 (path.length && path[0] == FileConst.PathSeparatorChar) 00268 ); 00269 } 00270 00271 /*********************************************************************** 00272 00273 Return the root of this path. Roots are constructs such as 00274 "c:". 00275 00276 ***********************************************************************/ 00277 00278 char[] getRoot () 00279 { 00280 return root; 00281 } 00282 00283 /*********************************************************************** 00284 00285 Return the file path. Paths start with a '/' but do not 00286 end with one. The root path is empty. Directory paths 00287 are split such that the directory name is placed into 00288 the 'name' member. 00289 00290 ***********************************************************************/ 00291 00292 char[] getPath () 00293 { 00294 return path; 00295 } 00296 00297 /*********************************************************************** 00298 00299 Return the name of this file, or directory. 00300 00301 ***********************************************************************/ 00302 00303 char[] getName () 00304 { 00305 return name; 00306 } 00307 00308 /*********************************************************************** 00309 00310 Return the file-extension, sans seperator 00311 00312 ***********************************************************************/ 00313 00314 char[] getExtension () 00315 { 00316 return ext; 00317 } 00318 00319 /*********************************************************************** 00320 00321 Suffix is like extension, except it can include multiple 00322 '.' sequences. For example, "wumpus1.foo.bar" has suffix 00323 "foo.bar" and extension "bar". 00324 00325 ***********************************************************************/ 00326 00327 char[] getSuffix () 00328 { 00329 return suffix; 00330 } 00331 00332 /*********************************************************************** 00333 00334 Convert this FilePath to a char[]. This is expected to 00335 execute optimally in most cases. 00336 00337 ***********************************************************************/ 00338 00339 char[] toString () 00340 { 00341 if (fp is null) 00342 { 00343 // get the assembled path 00344 char[] tmp = write (new Buffer(MaxFilePathSize)).toString; 00345 00346 // add a terminating '\0' 00347 fp = new char[tmp.length + 1]; 00348 fp[tmp.length] = 0; 00349 fp.length = fp.length - 1; 00350 00351 // copy new filepath content 00352 fp[] = tmp; 00353 } 00354 return fp; 00355 } 00356 00357 /*********************************************************************** 00358 00359 Write this FilePath to the given IWriter. This makes the 00360 FilePath compatible with all Writers. Note that we could 00361 leverage the write(IBuffer) method here, but that would 00362 bypass any special character converters attached to the 00363 IWriter. 00364 00365 ***********************************************************************/ 00366 00367 void write (IWriter write) 00368 { 00369 if (root.length) 00370 write (root) (FileConst.RootSeparatorChar); 00371 00372 if (path.length) 00373 write (path); 00374 00375 if (name.length) 00376 write (name); 00377 00378 if (ext.length) 00379 write (FileConst.FileSeparatorChar) (ext); 00380 } 00381 00382 /*********************************************************************** 00383 00384 Convert this FilePath to a char[] in the provided buffer 00385 00386 ***********************************************************************/ 00387 00388 IBuffer write (IBuffer buf) 00389 { 00390 if (root.length) 00391 buf.append(root).append(FileConst.RootSeparatorString); 00392 00393 if (path.length) 00394 buf.append(path); 00395 00396 if (name.length) 00397 buf.append(name); 00398 00399 if (ext.length) 00400 buf.append(FileConst.FileSeparatorString).append(ext); 00401 00402 return buf; 00403 } 00404 00405 /+ 00406 /*********************************************************************** 00407 00408 Convert this FilePath to a Uri. Note that a root (such as a 00409 drive-letter, or device) is placed into the Uri authority 00410 00411 ***********************************************************************/ 00412 00413 MutableUri toUri () 00414 { 00415 MutableUri uri = new MutableUri(); 00416 00417 if (isAbsolute) 00418 uri.setScheme ("file"); 00419 00420 if (root.length) 00421 uri.setHost (root); 00422 00423 char[] s = path~name; 00424 if (ext.length) 00425 s ~= FileConst.FileSeparatorString ~ ext; 00426 00427 version (Win32) 00428 Text.replace (s, FileConst.PathSeparatorChar, '/'); 00429 uri.setPath (s); 00430 return uri; 00431 } 00432 +/ 00433 00434 /*********************************************************************** 00435 00436 Return a zero terminated version of this file path. Note 00437 that the compiler places a zero at the end of each static 00438 string, as does the allocator for char[] requests. 00439 00440 In typical usage, this will not need to duplicate the path 00441 00442 ***********************************************************************/ 00443 00444 char[] toUtf8 () 00445 { 00446 char* p = fp; 00447 00448 // reconstruct if not terminated 00449 if (p && *(cast(char *) p + fp.length)) 00450 reset(); 00451 00452 return toString; 00453 } 00454 00455 /*********************************************************************** 00456 00457 ***********************************************************************/ 00458 00459 wchar[] toUtf16 () 00460 { 00461 if (! fpWide) 00462 { 00463 fpWide = Unicode.toUtf16 (toString); 00464 *(cast(wchar*) fpWide + fpWide.length) = 0; 00465 } 00466 return fpWide; 00467 } 00468 00469 /*********************************************************************** 00470 00471 Splice this FilePath onto the end of the provided base path. 00472 Output is return as a char[]. 00473 00474 ***********************************************************************/ 00475 00476 char[] splice (FilePath base) 00477 { 00478 return splice (base, new Buffer(MaxFilePathSize)).toString(); 00479 } 00480 00481 /*********************************************************************** 00482 00483 Splice this FilePath onto the end of the provided base path. 00484 Output is placed into the provided IBuffer. 00485 00486 ***********************************************************************/ 00487 00488 IBuffer splice (FilePath base, IBuffer buf) 00489 { 00490 int pos = buf.getLimit; 00491 base.write (buf); 00492 if (buf.getLimit != pos) 00493 buf.append (FileConst.PathSeparatorString); 00494 00495 if (path.length) 00496 buf.append(path); 00497 00498 if (name.length) 00499 buf.append(name); 00500 00501 if (ext.length) 00502 buf.append(FileConst.FileSeparatorString).append(ext); 00503 00504 return buf; 00505 } 00506 00507 /*********************************************************************** 00508 00509 Find the next parent of the FilePath. Returns a valid index 00510 to the seperator when present, -1 otherwise. 00511 00512 ***********************************************************************/ 00513 00514 private int locateParent () 00515 { 00516 int i = path.length; 00517 00518 // set new path to rightmost PathSeparator 00519 if (--i > 0) 00520 while (--i >= 0) 00521 if (path[i] == FileConst.PathSeparatorChar) 00522 return i; 00523 return -1; 00524 } 00525 00526 /*********************************************************************** 00527 00528 Returns a FilePath representing the parent of this one. An 00529 exception is thrown if there is not parent (at the root). 00530 00531 ***********************************************************************/ 00532 00533 FilePath toParent () 00534 { 00535 // set new path to rightmost PathSeparator 00536 int i = locateParent(); 00537 00538 if (i >= 0) 00539 { 00540 FilePath parent = new FilePath (this); 00541 00542 // slice path subsection 00543 parent.path = path [0..i+1]; 00544 parent.reset (); 00545 return parent; 00546 } 00547 00548 // return null? throw exception? return null? Hmmmm ... 00549 throw new IOException ("Cannot create parent path for an orphan file"); 00550 } 00551 00552 /*********************************************************************** 00553 00554 Returns true if this FilePath has a parent. 00555 00556 ***********************************************************************/ 00557 00558 bool isChild () 00559 { 00560 return cast (bool) (locateParent() >= 0); 00561 } 00562 00563 /*********************************************************************** 00564 00565 Return a cloned FilePath with a different name. 00566 00567 ***********************************************************************/ 00568 00569 FilePath toSibling (char[] name) 00570 { 00571 return toSibling (name, ext, suffix); 00572 } 00573 00574 /*********************************************************************** 00575 00576 Return a cloned FilePath with a different name and extension. 00577 Note that the suffix is destroyed. 00578 00579 ***********************************************************************/ 00580 00581 FilePath toSibling (char[] name, char[] ext) 00582 { 00583 return toSibling (name, ext, suffix); 00584 } 00585 00586 /*********************************************************************** 00587 00588 Return a cloned FilePath with a different name, extension, 00589 and suffix. 00590 00591 ***********************************************************************/ 00592 00593 FilePath toSibling (char[] name, char[] ext, char[] suffix) 00594 { 00595 FilePath sibling = new FilePath (this); 00596 00597 sibling.suffix = suffix; 00598 sibling.name = name; 00599 sibling.ext = ext; 00600 sibling.reset (); 00601 00602 return sibling; 00603 } 00604 } 00605 00606 00607 /******************************************************************************* 00608 00609 Mutable version of FilePath, which allows one to change individual 00610 attributes. A change to any attribute will cause method toString() 00611 to rebuild the output. 00612 00613 *******************************************************************************/ 00614 00615 class MutableFilePath : FilePath 00616 { 00617 /*********************************************************************** 00618 00619 Create an empty MutableFilePath 00620 00621 ***********************************************************************/ 00622 00623 this () 00624 { 00625 } 00626 00627 /*********************************************************************** 00628 00629 Create a MutableFilePath through reference to another. 00630 00631 ***********************************************************************/ 00632 00633 this (FilePath other) 00634 { 00635 super (other); 00636 } 00637 00638 /*********************************************************************** 00639 00640 Create a MutableFilePath via a filename. 00641 00642 ***********************************************************************/ 00643 00644 this (char[] name) 00645 { 00646 super (name); 00647 } 00648 00649 /*********************************************************************** 00650 00651 Set the extension of this FilePath. 00652 00653 ***********************************************************************/ 00654 00655 private final MutableFilePath set (char[]* x, char[]* v) 00656 { 00657 *x = *v; 00658 reset (); 00659 return this; 00660 } 00661 00662 /*********************************************************************** 00663 00664 Set the extension of this FilePath. 00665 00666 ***********************************************************************/ 00667 00668 MutableFilePath setExt (char[] ext) 00669 { 00670 return set (&this.ext, &ext); 00671 } 00672 00673 /*********************************************************************** 00674 00675 Set the name of this FilePath. 00676 00677 ***********************************************************************/ 00678 00679 MutableFilePath setName (char[] name) 00680 { 00681 return set (&this.name, &name); 00682 } 00683 00684 /*********************************************************************** 00685 00686 Set the path of this FilePath. 00687 00688 ***********************************************************************/ 00689 00690 MutableFilePath setPath (char[] path) 00691 { 00692 return set (&this.path, &path); 00693 } 00694 00695 /*********************************************************************** 00696 00697 Set the root of this FilePath (such as "c:") 00698 00699 ***********************************************************************/ 00700 00701 MutableFilePath setRoot (char[] root) 00702 { 00703 return set (&this.root, &root); 00704 } 00705 00706 /*********************************************************************** 00707 00708 Set the suffix of this FilePath. 00709 00710 ***********************************************************************/ 00711 00712 MutableFilePath setSuffix (char[] suffix) 00713 { 00714 return set (&this.suffix, &suffix); 00715 } 00716 }