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.Exception, 00047 mango.io.FileConst; 00048 00049 private import mango.io.model.IWriter; 00050 00051 /******************************************************************************* 00052 00053 Models a file name. These are expected to be used as the constructor 00054 argument to File implementations. The intention is that they easily 00055 convert to other representations such as absolute, canonical, or Url. 00056 Note that this class is immutable. Use MutableFilePath if you wish 00057 to alter specific attributes. 00058 00059 File paths containing non-ansi characters should be UTF-8 encoded. 00060 Supporting Unicode in this manner was deemed to be more suitable 00061 than providing a wchar version of FilePath, and is both consistent 00062 & compatible with the approach taken with the Uri class. 00063 00064 *******************************************************************************/ 00065 00066 class FilePath : IWritable 00067 { 00068 private char[] fp, 00069 ext, 00070 name, 00071 path, 00072 root, 00073 suffix; 00074 00075 private wchar[] fpWide; 00076 00077 // version = MangoDebug; 00078 // version = MangoOptimize; 00079 00080 private static const int MaxFilePathSize = 1024; 00081 00082 /*********************************************************************** 00083 00084 Create an empty FilePath. This is strictly for subclass 00085 use. 00086 00087 ***********************************************************************/ 00088 00089 protected this () 00090 { 00091 } 00092 00093 /*********************************************************************** 00094 00095 Create a FilePath through reference to another. 00096 00097 ***********************************************************************/ 00098 00099 this (FilePath other) 00100 in { 00101 assert (other); 00102 } 00103 body 00104 { 00105 fp = other.fp; 00106 ext = other.ext; 00107 name = other.name; 00108 path = other.path; 00109 root = other.root; 00110 suffix = other.suffix; 00111 fpWide = other.fpWide; 00112 } 00113 /+ 00114 /*********************************************************************** 00115 00116 Create a FilePath from a Uri. Note that the Uri authority 00117 is used to house an optional root (device, drive-letter ...) 00118 00119 ***********************************************************************/ 00120 00121 this (Uri uri) 00122 { 00123 char[] path = uri.getPath(); 00124 00125 if (uri.getHost.length) 00126 path = uri.getHost ~ FileConst.RootSeparatorString ~ path; 00127 00128 this (path); 00129 } 00130 +/ 00131 /*********************************************************************** 00132 00133 Create a FilePath from the given string. Note the path 00134 is not duplicated here, so you are expected to provide 00135 an immutable copy for the lifetime of this object. 00136 00137 If you're not certain, duplicate the path first. 00138 00139 ***********************************************************************/ 00140 00141 this (char[] filepath) 00142 in { 00143 assert (filepath); 00144 assert(filepath.length > 0); 00145 } 00146 out { 00147 if (root) 00148 assert (root.length > 0); 00149 } 00150 body 00151 { 00152 int ext = -1, 00153 path = -1, 00154 root = -1, 00155 suffix = -1; 00156 00157 //printf ("FilePath: '%.*s'\n", filepath); 00158 00159 for (int i=filepath.length; i > 0; --i) 00160 switch (filepath[i-1]) 00161 { 00162 case FileConst.FileSeparatorChar: 00163 if (path < 0) 00164 { 00165 if (ext < 0) 00166 { 00167 // check for '..' sequence 00168 if (i > 1 && filepath[i-2] != FileConst.FileSeparatorChar) 00169 ext = i; 00170 } 00171 suffix = i; 00172 } 00173 break; 00174 00175 case FileConst.PathSeparatorChar: 00176 if (path < 0) 00177 path = i; 00178 break; 00179 00180 case FileConst.RootSeparatorChar: 00181 root = i; 00182 default: 00183 break; 00184 } 00185 00186 int i = filepath.length; 00187 00188 version (MangoDebug) 00189 { 00190 printf ("\n>>'%d' root:'%d' path:'%d' ext:'%d' suffix:'%d'\n", 00191 i, root, path, ext, suffix); 00192 } 00193 00194 if (ext >= 0) 00195 { 00196 this.ext = filepath [ext..i]; 00197 this.suffix = filepath [suffix..i]; 00198 --ext; 00199 } 00200 else 00201 ext = i; 00202 00203 if (root >= 1) 00204 this.root = filepath [0..root-1]; 00205 else 00206 root = 0; 00207 00208 if (path >= root) 00209 this.path = filepath [root..path]; 00210 else 00211 path = root; 00212 00213 this.name = filepath [path..ext]; 00214 00215 // save original 00216 this.fp = filepath; 00217 00218 version (MangoDebug) 00219 { 00220 printf (">>'%.*s' root:'%.*s' path:'%.*s' name:'%.*s' ext:'%.*s' suffix:'%.*s'\n", 00221 filepath, this.root, this.path, this.name, this.ext, this.suffix); 00222 } 00223 } 00224 00225 /*********************************************************************** 00226 00227 Convert path separators to the correct format. This mutates 00228 the provided 'path' content, so .dup it as necessary. 00229 00230 ***********************************************************************/ 00231 00232 static char[] normalize (inout char[] path) 00233 { 00234 version (Win32) 00235 { 00236 foreach (int i, char c; path) 00237 if (c == '/') 00238 path[i] = '\\'; 00239 } 00240 else 00241 { 00242 foreach (int i, char c; path) 00243 if (c == '\\') 00244 path[i] = '/'; 00245 } 00246 return path; 00247 } 00248 00249 /*********************************************************************** 00250 00251 ***********************************************************************/ 00252 00253 void reset () 00254 { 00255 fp = null; 00256 fpWide = null; 00257 } 00258 00259 /*********************************************************************** 00260 00261 Returns true if this FilePath is *not* relative to the 00262 current working directory. 00263 00264 ***********************************************************************/ 00265 00266 bool isAbsolute () 00267 { 00268 return (root.length || 00269 (path.length && path[0] == FileConst.PathSeparatorChar) 00270 ); 00271 } 00272 00273 /*********************************************************************** 00274 00275 Return the root of this path. Roots are constructs such as 00276 "c:". 00277 00278 ***********************************************************************/ 00279 00280 char[] getRoot () 00281 { 00282 return root; 00283 } 00284 00285 /*********************************************************************** 00286 00287 Return the file path. Paths start with a '/' but do not 00288 end with one. The root path is empty. Directory paths 00289 are split such that the directory name is placed into 00290 the 'name' member. 00291 00292 ***********************************************************************/ 00293 00294 char[] getPath () 00295 { 00296 return path; 00297 } 00298 00299 /*********************************************************************** 00300 00301 Return the name of this file, or directory. 00302 00303 ***********************************************************************/ 00304 00305 char[] getName () 00306 { 00307 return name; 00308 } 00309 00310 /*********************************************************************** 00311 00312 Return the file-extension, sans seperator 00313 00314 ***********************************************************************/ 00315 00316 char[] getExtension () 00317 { 00318 return ext; 00319 } 00320 00321 /*********************************************************************** 00322 00323 Suffix is like extension, except it can include multiple 00324 '.' sequences. For example, "wumpus1.foo.bar" has suffix 00325 "foo.bar" and extension "bar". 00326 00327 ***********************************************************************/ 00328 00329 char[] getSuffix () 00330 { 00331 return suffix; 00332 } 00333 00334 /*********************************************************************** 00335 00336 Convert this FilePath to a char[]. This is expected to 00337 execute optimally in most cases. 00338 00339 ***********************************************************************/ 00340 00341 char[] toString () 00342 { 00343 if (fp is null) 00344 { 00345 // get the assembled path 00346 char[] tmp = write (new Buffer(MaxFilePathSize)).toString; 00347 00348 // add a terminating '\0' 00349 fp = new char[tmp.length + 1]; 00350 fp[tmp.length] = 0; 00351 fp.length = fp.length - 1; 00352 00353 // copy new filepath content 00354 fp[] = tmp; 00355 } 00356 return fp; 00357 } 00358 00359 /*********************************************************************** 00360 00361 Write this FilePath to the given IWriter. This makes the 00362 FilePath compatible with all Writers. Note that we could 00363 leverage the write(IBuffer) method here, but that would 00364 bypass any special character converters attached to the 00365 IWriter. 00366 00367 ***********************************************************************/ 00368 00369 void write (IWriter write) 00370 { 00371 if (root.length) 00372 write (root) (FileConst.RootSeparatorChar); 00373 00374 if (path.length) 00375 write (path); 00376 00377 if (name.length) 00378 write (name); 00379 00380 if (ext.length) 00381 write (FileConst.FileSeparatorChar) (ext); 00382 } 00383 00384 /*********************************************************************** 00385 00386 Convert this FilePath to a char[] in the provided buffer 00387 00388 ***********************************************************************/ 00389 00390 IBuffer write (IBuffer buf) 00391 { 00392 if (root.length) 00393 buf.append(root).append(FileConst.RootSeparatorString); 00394 00395 if (path.length) 00396 buf.append(path); 00397 00398 if (name.length) 00399 buf.append(name); 00400 00401 if (ext.length) 00402 buf.append(FileConst.FileSeparatorString).append(ext); 00403 00404 return buf; 00405 } 00406 00407 /+ 00408 /*********************************************************************** 00409 00410 Convert this FilePath to a Uri. Note that a root (such as a 00411 drive-letter, or device) is placed into the Uri authority 00412 00413 ***********************************************************************/ 00414 00415 MutableUri toUri () 00416 { 00417 MutableUri uri = new MutableUri(); 00418 00419 if (isAbsolute) 00420 uri.setScheme ("file"); 00421 00422 if (root.length) 00423 uri.setHost (root); 00424 00425 char[] s = path~name; 00426 if (ext.length) 00427 s ~= FileConst.FileSeparatorString ~ ext; 00428 00429 version (Win32) 00430 Text.replace (s, FileConst.PathSeparatorChar, '/'); 00431 uri.setPath (s); 00432 return uri; 00433 } 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 }