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.Utf8, 00045 mango.io.Buffer, 00046 mango.io.Writer, 00047 mango.io.Exception, 00048 mango.io.FileSystem, 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 ~ FileSystem.RootSeperatorString ~ 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 FileSystem.FileSeperatorChar: 00165 if (path < 0) 00166 { 00167 if (ext < 0) 00168 { 00169 // check for '..' sequence 00170 if (i > 1 && filepath[i-2] != FileSystem.FileSeperatorChar) 00171 ext = i; 00172 } 00173 suffix = i; 00174 } 00175 break; 00176 00177 case FileSystem.PathSeperatorChar: 00178 if (path < 0) 00179 path = i; 00180 break; 00181 00182 case FileSystem.RootSeperatorChar: 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 ***********************************************************************/ 00230 00231 void reset () 00232 { 00233 fp = null; 00234 fpWide = null; 00235 } 00236 00237 /*********************************************************************** 00238 00239 Returns true if this FilePath is *not* relative to the 00240 current working directory. 00241 00242 ***********************************************************************/ 00243 00244 bool isAbsolute () 00245 { 00246 return (root.length || 00247 (path.length && path[0] == FileSystem.PathSeperatorChar) 00248 ); 00249 } 00250 00251 /*********************************************************************** 00252 00253 Return the root of this path. Roots are constructs such as 00254 "c:". 00255 00256 ***********************************************************************/ 00257 00258 char[] getRoot () 00259 { 00260 return root; 00261 } 00262 00263 /*********************************************************************** 00264 00265 Return the file path. Paths start with a '/' but do not 00266 end with one. The root path is empty. Directory paths 00267 are split such that the directory name is placed into 00268 the 'name' member. 00269 00270 ***********************************************************************/ 00271 00272 char[] getPath () 00273 { 00274 return path; 00275 } 00276 00277 /*********************************************************************** 00278 00279 Return the name of this file, or directory. 00280 00281 ***********************************************************************/ 00282 00283 char[] getName () 00284 { 00285 return name; 00286 } 00287 00288 /*********************************************************************** 00289 00290 Return the file-extension, sans seperator 00291 00292 ***********************************************************************/ 00293 00294 char[] getExtension () 00295 { 00296 return ext; 00297 } 00298 00299 /*********************************************************************** 00300 00301 Suffix is like extension, except it can include multiple 00302 '.' sequences. For example, "wumpus1.foo.bar" has suffix 00303 "foo.bar" and extension "bar". 00304 00305 ***********************************************************************/ 00306 00307 char[] getSuffix () 00308 { 00309 return suffix; 00310 } 00311 00312 /*********************************************************************** 00313 00314 Convert this FilePath to a char[]. This is expected to 00315 execute optimally in most cases. 00316 00317 ***********************************************************************/ 00318 00319 char[] toString () 00320 { 00321 if (fp is null) 00322 { 00323 Buffer buf = new Buffer (MaxFilePathSize); 00324 00325 version (MangoOptimize) 00326 { 00327 if (root.length) 00328 buf.append(root).append(FileSystem.RootSeperatorString); 00329 00330 if (path.length) 00331 buf.append(path); 00332 00333 if (name.length) 00334 buf.append(name); 00335 00336 if (ext.length) 00337 buf.append(FileSystem.FileSeperatorString).append(ext); 00338 } 00339 else 00340 write (new DisplayWriter (buf)); 00341 00342 // get the assembled path 00343 char[] tmp = buf.toString; 00344 00345 // this will add a terminating '\0' 00346 fp = new char[tmp.length]; 00347 00348 // copy new filepath content 00349 fp[0..tmp.length] = tmp[0..tmp.length]; 00350 } 00351 return fp; 00352 } 00353 00354 /*********************************************************************** 00355 00356 Convert this FilePath to a Uri. Note that a root (such as a 00357 drive-letter, or device) is placed into the Uri authority 00358 00359 ***********************************************************************/ 00360 00361 MutableUri toUri () 00362 { 00363 MutableUri uri = new MutableUri(); 00364 00365 if (isAbsolute) 00366 uri.setScheme ("file"); 00367 00368 if (root.length) 00369 uri.setHost (root); 00370 00371 char[] s = path~name; 00372 if (ext.length) 00373 s ~= FileSystem.FileSeperatorString ~ ext; 00374 00375 version (Win32) 00376 Text.replace (s, FileSystem.PathSeperatorChar, '/'); 00377 uri.setPath (s); 00378 return uri; 00379 } 00380 00381 /*********************************************************************** 00382 00383 Write this FilePath to the given IWriter. This makes the 00384 FilePath compatible with all Writers 00385 00386 ***********************************************************************/ 00387 00388 void write (IWriter writer) 00389 { 00390 if (root.length) 00391 writer.put(root).put(FileSystem.RootSeperatorChar); 00392 00393 if (path.length) 00394 writer.put(path); 00395 00396 if (name.length) 00397 writer.put(name); 00398 00399 if (ext.length) 00400 writer.put(FileSystem.FileSeperatorChar).put(ext); 00401 } 00402 00403 00404 /*********************************************************************** 00405 00406 Return a zero terminated version of this file path. Note 00407 that the compiler places a zero at the end of each static 00408 string, as does the allocator for char[] requests. 00409 00410 In typical usage, this will not need to duplicate the path 00411 00412 ***********************************************************************/ 00413 00414 char[] toUtf8 () 00415 { 00416 char* p = fp; 00417 00418 // reconstruct if not terminated 00419 if (p && *(cast(char *) p + fp.length)) 00420 reset(); 00421 00422 return toString; 00423 } 00424 00425 /*********************************************************************** 00426 00427 ***********************************************************************/ 00428 00429 wchar[] toUtf16 () 00430 { 00431 if (! fpWide) 00432 { 00433 fpWide = Utf8.decode (toString); 00434 *(cast(wchar*) fpWide + fpWide.length) = 0; 00435 } 00436 return fpWide; 00437 } 00438 00439 /*********************************************************************** 00440 00441 Splice this FilePath onto the end of the provided base path. 00442 Output is return as a char[]. 00443 00444 ***********************************************************************/ 00445 00446 char[] splice (FilePath base) 00447 { 00448 return splice (base, new Buffer(MaxFilePathSize)).toString(); 00449 } 00450 00451 /*********************************************************************** 00452 00453 Splice this FilePath onto the end of the provided base path. 00454 Output is placed into the provided IBuffer. 00455 00456 ***********************************************************************/ 00457 00458 IBuffer splice (FilePath base, IBuffer buf) 00459 { 00460 if (base.root.length) 00461 buf.append(base.root).append(FileSystem.RootSeperatorString); 00462 00463 if (base.path.length) 00464 buf.append(base.path); 00465 00466 if (base.name.length) 00467 buf.append(base.name).append(FileSystem.PathSeperatorString); 00468 00469 if (path.length) 00470 buf.append(path).append(FileSystem.PathSeperatorString); 00471 00472 if (name.length) 00473 buf.append(name); 00474 00475 if (ext.length) 00476 buf.append(FileSystem.FileSeperatorString).append(ext); 00477 00478 return buf; 00479 } 00480 00481 /*********************************************************************** 00482 00483 Find the next parent of the FilePath. Returns a valid index 00484 to the seperator when present, -1 otherwise. 00485 00486 ***********************************************************************/ 00487 00488 private int locateParent () 00489 { 00490 int i = path.length; 00491 00492 // set new path to rightmost PathSeperator 00493 if (--i > 0) 00494 while (--i >= 0) 00495 if (path[i] == FileSystem.PathSeperatorChar) 00496 return i; 00497 return -1; 00498 } 00499 00500 /*********************************************************************** 00501 00502 Returns a FilePath representing the parent of this one. An 00503 exception is thrown if there is not parent (at the root). 00504 00505 ***********************************************************************/ 00506 00507 FilePath toParent () 00508 { 00509 // set new path to rightmost PathSeperator 00510 int i = locateParent(); 00511 00512 if (i >= 0) 00513 { 00514 FilePath parent = new FilePath (this); 00515 00516 // slice path subsection 00517 parent.path = path [0..i+1]; 00518 parent.reset (); 00519 return parent; 00520 } 00521 00522 // return null? throw exception? return null? Hmmmm ... 00523 throw new IOException ("Cannot create parent path for an orphan file"); 00524 } 00525 00526 /*********************************************************************** 00527 00528 Returns true if this FilePath has a parent. 00529 00530 ***********************************************************************/ 00531 00532 bool isChild () 00533 { 00534 return locateParent() >= 0; 00535 } 00536 00537 /*********************************************************************** 00538 00539 Return a cloned FilePath with a different name. 00540 00541 ***********************************************************************/ 00542 00543 FilePath toSibling (char[] name) 00544 { 00545 return toSibling (name, ext, suffix); 00546 } 00547 00548 /*********************************************************************** 00549 00550 Return a cloned FilePath with a different name and extension. 00551 Note that the suffix is destroyed. 00552 00553 ***********************************************************************/ 00554 00555 FilePath toSibling (char[] name, char[] ext) 00556 { 00557 return toSibling (name, ext, suffix); 00558 } 00559 00560 /*********************************************************************** 00561 00562 Return a cloned FilePath with a different name, extension, 00563 and suffix. 00564 00565 ***********************************************************************/ 00566 00567 FilePath toSibling (char[] name, char[] ext, char[] suffix) 00568 { 00569 FilePath sibling = new FilePath (this); 00570 00571 sibling.suffix = suffix; 00572 sibling.name = name; 00573 sibling.ext = ext; 00574 sibling.reset (); 00575 00576 return sibling; 00577 } 00578 } 00579 00580 00581 /******************************************************************************* 00582 00583 Mutable version of FilePath, which allows one to change individual 00584 attributes. A change to any attribute will cause method toString() 00585 to rebuild the output. 00586 00587 *******************************************************************************/ 00588 00589 class MutableFilePath : FilePath 00590 { 00591 /*********************************************************************** 00592 00593 Create an empty MutableFilePath 00594 00595 ***********************************************************************/ 00596 00597 this () 00598 { 00599 } 00600 00601 /*********************************************************************** 00602 00603 Create a MutableFilePath through reference to another. 00604 00605 ***********************************************************************/ 00606 00607 this (FilePath other) 00608 { 00609 super (other); 00610 } 00611 00612 /*********************************************************************** 00613 00614 Set the extension of this FilePath. 00615 00616 ***********************************************************************/ 00617 00618 private final MutableFilePath set (char[]* x, char[]* v) 00619 { 00620 *x = *v; 00621 reset (); 00622 return this; 00623 } 00624 00625 /*********************************************************************** 00626 00627 Set the extension of this FilePath. 00628 00629 ***********************************************************************/ 00630 00631 MutableFilePath setExt (char[] ext) 00632 { 00633 return set (&this.ext, &ext); 00634 } 00635 00636 /*********************************************************************** 00637 00638 Set the name of this FilePath. 00639 00640 ***********************************************************************/ 00641 00642 MutableFilePath setName (char[] name) 00643 { 00644 return set (&this.name, &name); 00645 } 00646 00647 /*********************************************************************** 00648 00649 Set the path of this FilePath. 00650 00651 ***********************************************************************/ 00652 00653 MutableFilePath setPath (char[] path) 00654 { 00655 return set (&this.path, &path); 00656 } 00657 00658 /*********************************************************************** 00659 00660 Set the root of this FilePath (such as "c:") 00661 00662 ***********************************************************************/ 00663 00664 MutableFilePath setRoot (char[] root) 00665 { 00666 return set (&this.root, &root); 00667 } 00668 00669 /*********************************************************************** 00670 00671 Set the suffix of this FilePath. 00672 00673 ***********************************************************************/ 00674 00675 MutableFilePath setSuffix (char[] suffix) 00676 { 00677 return set (&this.suffix, &suffix); 00678 } 00679 }