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