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